Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 13 additions & 16 deletions .agents/skills/import-upstream-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,27 +111,24 @@ When accepted, rename the branch to `port/upstream/pr-<number>-<slug>`.

1. Immediately before delivery, fetch `origin/lastcode/main` again. If the port
parent moved, rebase the imported commits and rerun affected validation.
2. Before the guarded push, require a clean worktree and record the local head,
destination base, and existing remote topic SHA (or its absence). Then push
in a clean environment so the pre-push `pnpm lastcode:ci:quick` gate sees
ordinary Git/SSH variables. Do not export `GIT_SSH_COMMAND` or inject an SSH
command through Git configuration around the push; those settings flow into
tests that intentionally control `GIT_SSH`.
3. If Quick CI passes but the idle SSH transport subsequently dies, do not
treat its generic success line as an exact-head receipt. Require the local
head to equal the recorded head, the worktree to remain clean, the fetched
destination base to equal the recorded base, and the remote topic SHA (or
absence) to remain unchanged. Only then retry that recorded head with
`--no-verify` and an exact `--force-with-lease` tied to the recorded remote
topic state. Otherwise rerun the guarded push and its hook.
2. Before publishing, run the independent **Run Quick CI** Project Action from
the clean exact port head. After it resumes, verify its receipt still matches
the head and destination base, then decide whether and what to push. The
pre-push hook consumes that receipt; ordinary command-line use falls back to
synchronous Quick CI.
3. If transport fails after the hook accepts the receipt, verify the local head,
worktree, destination base, and remote topic state before retrying. Reuse the
same receipt only for the unchanged commit and base; otherwise rerun the
action.
4. Open a PR targeting `lastcode/main` only when explicitly requested. Include
the upstream PR and pinned head, observed state/date, import method,
adaptations, rationale, validation, closure/review context, and published
evidence.
5. For review and merge, follow `lastcode-pr` and
`.agents/skills/_references/external-review-mechanics.md`: require a terminal
clean Codex result for the exact head, zero unresolved threads, and a full
`pnpm lastcode:ci` stamp for the exact head/current base. Merge only through
clean Codex result for the exact head, zero unresolved threads, and a
successful exact-head/base GitHub CI run and aggregate `CI Gate`. Use **Wait
for PR** during passive review or CI waits, and merge only through
`pnpm lastcode:merge`.
6. Verify the merged commit on `origin/lastcode/main`. For a squash merge,
compare stable patch IDs so provenance verification does not depend on the
Expand All @@ -152,5 +149,5 @@ When accepted, rename the branch to `port/upstream/pr-<number>-<slug>`.

Report the source PR state and pinned head, destination base and port head,
import method and adaptations, validation and real-client evidence, PR/merge
state, exact-head review result, unresolved-thread count, full-CI stamp, and the
state, exact-head review result, unresolved-thread count, GitHub CI run, and the
merged commit or remaining blocker.
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,9 @@ Include:
- focused and integrated validation; and
- durable GitHub-hosted visual evidence for UI changes.

Before merge, refresh the PR snapshot and require the same exact head across the
clean Codex result, zero unresolved threads, full-CI stamp, and merge command.
Before merge, refresh the PR snapshot and require the same exact head and base
across the clean Codex result, zero unresolved threads, successful GitHub CI
run, aggregate `CI Gate`, and guarded merge command.

After a squash merge, verify stable patch equivalence:

Expand Down
17 changes: 12 additions & 5 deletions .agents/skills/lastcode-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: lastcode-pr
description: Deliver a LastCode-only change through its branch, local CI, Codex review, and guarded merge workflow. Use for Markover integration, LastCode identity or branding, personal conveniences, nightly checkpointing, ad-hoc releases, and any change intentionally not proposed to pingdotgg/t3code. Use upstream-fix instead when a general improvement should also be offered upstream.
description: Deliver a LastCode-only change through its branch, Quick CI, GitHub CI, Codex review, and guarded merge workflow. Use for Markover integration, LastCode identity or branding, personal conveniences, nightly checkpointing, ad-hoc releases, and any change intentionally not proposed to pingdotgg/t3code. Use upstream-fix instead when a general improvement should also be offered upstream.
---

# LastCode PR
Expand Down Expand Up @@ -30,7 +30,10 @@ and should be proposed upstream, switch to `upstream-fix`.
for backend or automation behavior.
2. Run the smallest relevant tests, lint, and typecheck required by `AGENTS.md`.
3. Rebase onto the latest `origin/lastcode/main` before publishing.
4. Push the branch; the pre-push hook must pass `pnpm lastcode:ci:quick` locally.
4. Run the independent **Run Quick CI** Project Action. After it resumes with a
receipt for the exact clean head and workstream base, decide whether and what
to push. The pre-push hook consumes that receipt; ordinary command-line use
falls back to synchronous `pnpm lastcode:ci:quick`.
5. Open a PR targeting `lastcode/main` only when the user explicitly asks.

An open PR targeting `lastcode/main` pauses promotion of a new nightly onto the
Expand All @@ -54,9 +57,13 @@ current GitHub thread and review-query mechanics.
`../_references/external-review-mechanics.md`. Do not merge until Codex gives
an explicit clean result or every finding for the exact current head has a
durable handled state, and no review thread remains unresolved.
4. Run `pnpm lastcode:ci` from a clean branch. Its full-CI stamp must match the
exact head and fetched `origin/lastcode/main` base.
4. Use the independent **Wait for PR** Project Action while exact-head GitHub CI
or Codex review is passive. It resumes only for an actionable result. Rebase,
fix, retry, or request another review only after inspecting that result.
5. Use `pnpm lastcode:merge`; do not bypass the guarded merge in the GitHub UI.
The wrapper independently revalidates a successful exact-head/base GitHub CI
run and aggregate `CI Gate`, a clean current PR, and an unchanged fetched
base immediately before its exact-head squash merge.
6. Verify the PR is merged and `origin/lastcode/main` contains the merge result.
7. If the work is tracked by Markover, confirm the GitHub terminal state and
run the service-free command from the Markover checkout:
Expand All @@ -71,6 +78,6 @@ external blocker requires the user.

## Handoff

Report the PR URL, merged commit or current head, focused and full validation,
Report the PR URL, merged commit or current head, focused and GitHub CI validation,
Codex review result, unresolved-thread count, and Markover state when applicable.
If no PR was requested, report the local branch and commit without publishing it.
35 changes: 16 additions & 19 deletions docs/lastcode/release.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# LastCode Local Release Workflow

LastCode uses local validation and ad-hoc macOS releases. Ordinary branch CI
remains local. The one GitHub-hosted packaging path is manually dispatched for
an exact Intel checkpoint or revision; releases have no schedule.
LastCode uses a local waste-prevention gate, GitHub-hosted pull-request CI, and
ad-hoc macOS releases. The GitHub-hosted packaging path is manually dispatched
for an exact Intel checkpoint or revision; releases have no schedule.

Nightly source tracking is documented separately in
[Nightly Checkpoint Workflow](nightly-workflow.md).
Expand All @@ -18,7 +18,7 @@ pnpm lastcode:ci:quick
Quick CI is a local waste-prevention gate, not merge authority. It checks the
exact branch diff for whitespace errors, repository formatting and lint, and
workspace types. The implementing agent runs focused tests for changed behavior;
GitHub CI and the transitional full local gate retain comprehensive tests,
GitHub CI retains comprehensive tests,
Electron setup, builds, Rust, native analysis, and release smoke coverage.

Agents run the independent **Run Quick CI** Project Action before a push. A
Expand Down Expand Up @@ -80,28 +80,25 @@ case at a time. This takes longer than the task-runner defaults but avoids
cross-suite state leaks plus memory and CPU contention between the web, mobile,
desktop, and server suites on the single local build machine.

Before merging a LastCode PR, run the full gate from a clean feature branch:

```bash
pnpm lastcode:ci
```

The full PR gate fetches `origin/lastcode/main`, verifies that the tested head
contains that exact base commit, and runs formatting, linting, workspace
typechecks and tests, desktop build assertions, Rust tests, native static
analysis, and release smoke tests. Success writes a local stamp bound to both the
head commit and tested base.
The independent **Wait for PR** Project Action waits for both Codex review and
the exact GitHub pull-request workflow run. GitHub CI records the PR number,
head, base, and synthetic merge commit in the workflow run identity and exposes
one stable aggregate `CI Gate`. Head or base drift invalidates the result and
requires the agent to decide whether to rebase, republish, and request review
again.

Merge the current ready PR with:

```bash
pnpm lastcode:merge
```

The merge wrapper refuses dirty worktrees, unstamped commits, stale bases, draft
or conflicting PRs, and PRs that do not target `lastcode/main`. It squash-merges
with an exact-head guard, then requests an immediate checkpoint-daemon run. The
daemon publishes a new installable LastCode revision when no new upstream
The merge wrapper refuses dirty worktrees, missing or stale exact GitHub CI,
stale bases, draft or non-clean PRs, and PRs that do not target
`lastcode/main`. It fetches and checks the base again after reading CI, then
squash-merges with an exact-head guard and requests an immediate
checkpoint-daemon run. The daemon publishes a new installable LastCode revision
when no new upstream
nightly is waiting. Failure to start the service is reported without lying about
the already-completed GitHub merge; the hourly service run remains the repair
path. The request never terminates a daemon run already in progress.
Expand Down
31 changes: 31 additions & 0 deletions scripts/lastcode-github-ci.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
githubCiRunTitle,
githubCiRunsArgs,
githubCiWorkflowArgs,
readGithubCi,
testedMergeShaFromGithubCiRunTitle,
type GithubCiEvaluationInput,
} from "./lastcode-github-ci.ts";
Expand Down Expand Up @@ -68,6 +69,36 @@ describe("LastCode GitHub CI evidence", () => {
]);
});

it("reads workflow, rule, exact run, and aggregate job evidence through one shared path", () => {
const calls: string[] = [];
const responses = new Map<string, unknown>([
[githubCiWorkflowArgs("lastobelus/lastCode").join(" "), { state: "active" }],
[githubBranchRulesArgs("lastobelus/lastCode", "lastcode/main").join(" "), []],
[
githubCiRunsArgs("lastobelus/lastCode", HEAD).join(" "),
{ workflow_runs: input().workflowRuns },
],
[githubCiJobsArgs("lastobelus/lastCode", 456).join(" "), { jobs: input().jobs }],
]);
const evidence = readGithubCi(
"lastobelus/lastCode",
{
number: 104,
headRefOid: HEAD,
baseRefOid: BASE,
baseRefName: "lastcode/main",
},
<T>(args: ReadonlyArray<string>): T => {
const key = args.join(" ");
calls.push(key);
return responses.get(key) as T;
},
);

expect(evidence).toMatchObject({ state: "satisfied", reason: "exact-run", runId: 456 });
expect(calls).toEqual([...responses.keys()]);
});

it("satisfies disabled, non-required hosted CI without a run", () => {
for (const state of ["disabled_fork", "disabled_inactivity", "disabled_manually"]) {
expect(
Expand Down
50 changes: 50 additions & 0 deletions scripts/lastcode-github-ci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ export interface GithubCiEvaluationInput {
readonly jobs: ReadonlyArray<GithubWorkflowJob> | null;
}

export interface GithubCiPullRequest {
readonly number: number;
readonly headRefOid: string;
readonly baseRefOid: string;
readonly baseRefName: string;
}

export type RunGithubJson = <T>(args: ReadonlyArray<string>) => T;

type WorkflowRunsResponse = {
readonly workflow_runs?: ReadonlyArray<GithubWorkflowRun>;
};

type WorkflowJobsResponse = {
readonly jobs?: ReadonlyArray<GithubWorkflowJob>;
};

const runTimestamp = (run: GithubWorkflowRun): number => {
const parsed = Date.parse(run.created_at ?? "");
return Number.isNaN(parsed) ? 0 : parsed;
Expand Down Expand Up @@ -126,6 +143,39 @@ export function requiredGithubCiGate(branchRules: ReadonlyArray<GithubBranchRule
);
}

export function readGithubCi(
repository: string,
pullRequest: GithubCiPullRequest,
runGithubJson: RunGithubJson,
): GithubCiEvidence {
const workflow = runGithubJson<GithubWorkflow>(githubCiWorkflowArgs(repository));
const branchRules = runGithubJson<ReadonlyArray<GithubBranchRule>>(
githubBranchRulesArgs(repository, pullRequest.baseRefName),
);
const workflowRuns =
runGithubJson<WorkflowRunsResponse>(githubCiRunsArgs(repository, pullRequest.headRefOid))
.workflow_runs ?? [];
const evaluation = {
workflow,
branchRules,
pullRequestNumber: pullRequest.number,
headSha: pullRequest.headRefOid,
baseSha: pullRequest.baseRefOid,
workflowRuns,
jobs: null,
} as const;
const provisional = evaluateGithubCi(evaluation);
const completedSuccessRun = workflowRuns.find(
(run) =>
run.id === provisional.runId && run.status === "completed" && run.conclusion === "success",
);
if (!completedSuccessRun?.id) return provisional;
const jobs =
runGithubJson<WorkflowJobsResponse>(githubCiJobsArgs(repository, completedSuccessRun.id))
.jobs ?? [];
return evaluateGithubCi({ ...evaluation, jobs });
}

const configurationFailure = (detail: string): GithubCiEvidence => ({
state: "failure",
reason: "configuration",
Expand Down
40 changes: 36 additions & 4 deletions scripts/lastcode-merge.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vite-plus/test";

import { validatePullRequestForMerge } from "./lastcode-merge.ts";
import { validateGithubCiForMerge, validatePullRequestForMerge } from "./lastcode-merge.ts";

const mergeablePullRequest = {
number: 12,
Expand All @@ -11,16 +11,17 @@ const mergeablePullRequest = {
baseRefName: "lastcode/main",
baseRefOid: "base-sha",
mergeable: "MERGEABLE",
mergeStateStatus: "CLEAN",
} as const;

describe("lastcode-merge", () => {
it("accepts an open LastCode PR at the stamped head", () => {
it("accepts an open, clean LastCode PR at the exact head and base", () => {
expect(() =>
validatePullRequestForMerge(mergeablePullRequest, "head-sha", "base-sha"),
).not.toThrow();
});

it("rejects drafts, stale heads, conflicts, and other base branches", () => {
it("rejects drafts, stale heads, non-clean merge state, and other base branches", () => {
expect(() =>
validatePullRequestForMerge(
{ ...mergeablePullRequest, isDraft: true },
Expand All @@ -40,7 +41,14 @@ describe("lastcode-merge", () => {
"head-sha",
"base-sha",
),
).toThrow("merge conflicts");
).toThrow("not cleanly mergeable");
expect(() =>
validatePullRequestForMerge(
{ ...mergeablePullRequest, mergeStateStatus: "BEHIND" },
"head-sha",
"base-sha",
),
).toThrow("not cleanly mergeable");
expect(() =>
validatePullRequestForMerge(
{ ...mergeablePullRequest, baseRefName: "main" },
Expand All @@ -49,4 +57,28 @@ describe("lastcode-merge", () => {
),
).toThrow("not 'lastcode/main'");
});

it("accepts only a successful exact GitHub CI run", () => {
expect(() =>
validateGithubCiForMerge({
state: "satisfied",
reason: "exact-run",
runId: 456,
testedMergeSha: "a".repeat(40),
}),
).not.toThrow();
expect(() => validateGithubCiForMerge({ state: "satisfied", reason: "not-expected" })).toThrow(
"not merge-ready",
);
expect(() =>
validateGithubCiForMerge({ state: "pending", reason: "run-in-progress", runId: 456 }),
).toThrow("run Wait for PR again");
expect(() =>
validateGithubCiForMerge({
state: "failure",
reason: "terminal-run",
detail: "Workflow failed.",
}),
).toThrow("Workflow failed");
});
});
Loading