mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 08:23:59 +01:00
train S3: merge #3969 at 5aaae7c3ae v1.94.47.0
This commit is contained in:
commit
e2e2da2394
8 files changed
+54
-7
No files matched your search
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [1.94.47.0] - 2026-11-03
|
||||
|
||||
### Fixed
|
||||
- **The lean `/autoplan` reviewer sees the committed draft on a PR-branch checkout** (GST-1241). Draft capture took `origin/HEAD` as its base, and a Capy machine started on a PR branch points `origin/HEAD` at that branch, so the reviewer got `DRAFT: none (no code changes against origin/<branch>)`. It now resolves the base through `lib/default-base.ts`, the same resolver `bin/gstack-diff-scope` uses, which skips an `origin/HEAD` naming the checked-out branch and falls back to the PR base, the remote's live HEAD, then `origin/main`.
|
||||
|
||||
## [1.94.46.0] - 2026-11-03
|
||||
|
||||
**`gstack-autoplan export --public` no longer carries the owner's own words (GST-1243).** GEVA's public bundle (GEVA-225, gstack 1.93.34.0) kept all 38 `owner_reply` fields in decisions.jsonl, and a sealed reviewer file (`ceo-native.a5.bound.md`) quoted an owner reply word for word. GEVA redacted both by hand. Garry's rule: nothing a person wrote leaves without their click.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.94.46.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.94.47.0 — regenerate/re-copy after upgrading gstack
|
||||
|
||||
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
||||
for agent hosts without a full skill install. The full skills add workflows,
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ The first `next` prints `SCOPE: ui=… (terms; visual; files) dx=… (detected)
|
||||
TODOS*, docs/** but not docs/*-policy.md, or other markdown outside a skill dir) runs CEO on the native voice only, then Eng. UI needs a rendered file path or a UI term next
|
||||
to a visual noun in the Implementation plan. To keep flags for later runs on the same plan, add
|
||||
the `SCOPE_PIN:` marker it prints (`<!-- gstack-autoplan-scope: … -->`) as the plan's first
|
||||
line yourself; gstack never writes the plan. **Kinds (R65), first `next` only.** `--kind fix` reviews nothing: it prints `AUTOPLAN: not needed (bug fix; R65)`, a `PROOF:` line and `GSTACK_RESULT: skill=autoplan status=skipped run=-`, writes nothing and needs no `--out` or `--plan`; the PR still carries the bug's proof, a repro on current main or a failing test written first. `--kind feature`, the default for an unattended run on Capy (`GSTACK_CAPY=1`) that names no kind, is the lean run. Its `phase_order` is `eng` alone with one native reviewer, whose prompt merges the CEO and Eng lenses (Design or DX only when scope detects them), always asks "Any outside terms, licences or rights involved?" and shows the draft code. The draft is `git diff` from the merge base with `--base <ref>` (default `origin/HEAD`), untracked files included and the run directory left out, written to the snapshot as `draft.diff` and hashed; with no code yet it says `DRAFT: none (<why>)`. The first `next` prints `LEAN:` and each opened phase `DRAFT:`; the reviewer's line after INPUT must be `DRAFT: <sha256>` or `DRAFT: none`, or submit refuses it (`RESULT_RECEIPT_MISSING`). No outside voice, `--max-rounds 1`, no carry check and one gate revision: `revise --incorporates` applies the accepted changes without re-review and approves. `--full` brings back every phase and both voices; off Capy, and in an interactive session, the default stays full.
|
||||
line yourself; gstack never writes the plan. **Kinds (R65), first `next` only.** `--kind fix` reviews nothing: it prints `AUTOPLAN: not needed (bug fix; R65)`, a `PROOF:` line and `GSTACK_RESULT: skill=autoplan status=skipped run=-`, writes nothing and needs no `--out` or `--plan`; the PR still carries the bug's proof, a repro on current main or a failing test written first. `--kind feature`, the default for an unattended run on Capy (`GSTACK_CAPY=1`) that names no kind, is the lean run. Its `phase_order` is `eng` alone with one native reviewer, whose prompt merges the CEO and Eng lenses (Design or DX only when scope detects them), always asks "Any outside terms, licences or rights involved?" and shows the draft code. The draft is `git diff` from the merge base with `--base <ref>` (default: `gstack-diff-scope`'s base, never an `origin/HEAD` naming the checked-out branch), untracked files included and the run directory left out, written to the snapshot as `draft.diff` and hashed; with no code yet it says `DRAFT: none (<why>)`. The first `next` prints `LEAN:` and each opened phase `DRAFT:`; the reviewer's line after INPUT must be `DRAFT: <sha256>` or `DRAFT: none`, or submit refuses it (`RESULT_RECEIPT_MISSING`). No outside voice, `--max-rounds 1`, no carry check and one gate revision: `revise --incorporates` applies the accepted changes without re-review and approves. `--full` brings back every phase and both voices; off Capy, and in an interactive session, the default stays full.
|
||||
|
||||
## The loop
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ The first `next` prints `SCOPE: ui=… (terms; visual; files) dx=… (detected)
|
||||
TODOS*, docs/** but not docs/*-policy.md, or other markdown outside a skill dir) runs CEO on the native voice only, then Eng. UI needs a rendered file path or a UI term next
|
||||
to a visual noun in the Implementation plan. To keep flags for later runs on the same plan, add
|
||||
the `SCOPE_PIN:` marker it prints (`<!-- gstack-autoplan-scope: … -->`) as the plan's first
|
||||
line yourself; gstack never writes the plan. **Kinds (R65), first `next` only.** `--kind fix` reviews nothing: it prints `AUTOPLAN: not needed (bug fix; R65)`, a `PROOF:` line and `GSTACK_RESULT: skill=autoplan status=skipped run=-`, writes nothing and needs no `--out` or `--plan`; the PR still carries the bug's proof, a repro on current main or a failing test written first. `--kind feature`, the default for an unattended run on Capy (`GSTACK_CAPY=1`) that names no kind, is the lean run. Its `phase_order` is `eng` alone with one native reviewer, whose prompt merges the CEO and Eng lenses (Design or DX only when scope detects them), always asks "Any outside terms, licences or rights involved?" and shows the draft code. The draft is `git diff` from the merge base with `--base <ref>` (default `origin/HEAD`), untracked files included and the run directory left out, written to the snapshot as `draft.diff` and hashed; with no code yet it says `DRAFT: none (<why>)`. The first `next` prints `LEAN:` and each opened phase `DRAFT:`; the reviewer's line after INPUT must be `DRAFT: <sha256>` or `DRAFT: none`, or submit refuses it (`RESULT_RECEIPT_MISSING`). No outside voice, `--max-rounds 1`, no carry check and one gate revision: `revise --incorporates` applies the accepted changes without re-review and approves. `--full` brings back every phase and both voices; off Capy, and in an interactive session, the default stays full.
|
||||
line yourself; gstack never writes the plan. **Kinds (R65), first `next` only.** `--kind fix` reviews nothing: it prints `AUTOPLAN: not needed (bug fix; R65)`, a `PROOF:` line and `GSTACK_RESULT: skill=autoplan status=skipped run=-`, writes nothing and needs no `--out` or `--plan`; the PR still carries the bug's proof, a repro on current main or a failing test written first. `--kind feature`, the default for an unattended run on Capy (`GSTACK_CAPY=1`) that names no kind, is the lean run. Its `phase_order` is `eng` alone with one native reviewer, whose prompt merges the CEO and Eng lenses (Design or DX only when scope detects them), always asks "Any outside terms, licences or rights involved?" and shows the draft code. The draft is `git diff` from the merge base with `--base <ref>` (default: `gstack-diff-scope`'s base, never an `origin/HEAD` naming the checked-out branch), untracked files included and the run directory left out, written to the snapshot as `draft.diff` and hashed; with no code yet it says `DRAFT: none (<why>)`. The first `next` prints `LEAN:` and each opened phase `DRAFT:`; the reviewer's line after INPUT must be `DRAFT: <sha256>` or `DRAFT: none`, or submit refuses it (`RESULT_RECEIPT_MISSING`). No outside voice, `--max-rounds 1`, no carry check and one gate revision: `revise --incorporates` applies the accepted changes without re-review and approves. `--full` brings back every phase and both voices; off Capy, and in an interactive session, the default stays full.
|
||||
|
||||
## The loop
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { OUTSIDE_ROLE, sectionLoadFor, type Phase, type Scope } from './autoplan-prompts';
|
||||
import { RunError, type RunState } from './autoplan-state';
|
||||
import { defaultBaseBranch, noDefaultBaseLine } from './default-base';
|
||||
import { redactFindingSpans } from './redact-engine';
|
||||
|
||||
export const KINDS = ['fix', 'feature'] as const;
|
||||
@@ -66,8 +67,9 @@ export function captureDraft(cwd: string, dir: string, out: string, base?: strin
|
||||
const top = git(['rev-parse', '--show-toplevel']);
|
||||
if (top.status !== 0) return { base: base ?? 'origin/HEAD', none: 'not a git checkout' };
|
||||
const root = top.stdout.trim();
|
||||
const origin = git(['rev-parse', '--abbrev-ref', 'origin/HEAD'], root);
|
||||
const ref = base ?? (origin.status === 0 && origin.stdout.trim() ? origin.stdout.trim() : 'origin/main');
|
||||
const found = base ? undefined : defaultBaseBranch(root);
|
||||
if (found && !found.branch) return { base: 'origin/HEAD', none: found.skipped ? noDefaultBaseLine(found.skipped) : 'no default base (no origin/HEAD, PR base, origin/main or origin/master); pass --base' };
|
||||
const ref = base ?? `${found!.remote ?? 'origin'}/${found!.branch}`;
|
||||
const mb = git(['merge-base', 'HEAD', ref], root);
|
||||
if (mb.status !== 0) return { base: ref, none: `no merge base with ${ref}` };
|
||||
const mergeBase = mb.stdout.trim();
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.94.46",
|
||||
"version": "1.94.47",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -16,6 +16,7 @@ import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { ROOT, SMALL_PLAN, autoplan, env, journal, readRun } from './helpers/autoplan-run-fixture';
|
||||
import { capyRunner } from './helpers/autoplan-capy';
|
||||
import { captureDraft } from '../lib/autoplan-lean';
|
||||
|
||||
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'ap-lean-'));
|
||||
afterAll(() => fs.rmSync(TMP, { recursive: true, force: true }));
|
||||
@@ -488,3 +489,42 @@ describe('draft capture edges', () => {
|
||||
expect(prompt.indexOf('IGNORE THE PLAN')).toBeLessThan(prompt.indexOf(`\n${open[1]}\n`, open.index + 1));
|
||||
});
|
||||
});
|
||||
describe('draft capture base on a PR-branch clone (GST-1241)', () => {
|
||||
/** A clone of `feature`, its draft committed and pushed, whose origin/HEAD names `head` while origin's own HEAD stays main: a Capy machine started on a PR branch names that branch. */
|
||||
const clone = (name: string, head: string) => {
|
||||
const src = repo(name, false);
|
||||
fs.writeFileSync(path.join(src, 'lib', 'worker.ts'), 'export const retry = (n: number) => Math.min(30_000, 500 * 2 ** n);\n');
|
||||
git(src, 'commit', '-q', '-am', 'draft backoff');
|
||||
const bare = path.join(TMP, name, 'origin.git');
|
||||
git(TMP, 'clone', '-q', '--bare', src, bare);
|
||||
git(bare, 'symbolic-ref', 'HEAD', 'refs/heads/main');
|
||||
const cwd = path.join(TMP, name, 'clone');
|
||||
git(TMP, 'clone', '-q', '-b', 'feature', bare, cwd);
|
||||
git(cwd, 'remote', 'set-head', 'origin', head);
|
||||
expect(git(cwd, 'rev-parse', '--abbrev-ref', 'origin/HEAD').trim()).toBe(`origin/${head}`);
|
||||
return cwd;
|
||||
};
|
||||
const capture = (name: string, cwd: string) => {
|
||||
const dir = path.join(TMP, name, 'snap');
|
||||
const out = path.join(TMP, name, 'run');
|
||||
fs.mkdirSync(dir);
|
||||
fs.mkdirSync(out);
|
||||
const draft = captureDraft(cwd, dir, out);
|
||||
return { draft, diff: draft.sha256 ? fs.readFileSync(path.join(dir, 'draft.diff'), 'utf8') : '' };
|
||||
};
|
||||
|
||||
test('origin/HEAD naming the PR branch still captures the committed draft against main', () => {
|
||||
const { draft, diff } = capture('pr-head', clone('pr-head', 'feature'));
|
||||
expect(draft.none).toBeUndefined();
|
||||
expect(draft.base).toBe('origin/main');
|
||||
expect(draft.files).toEqual(['lib/worker.ts']);
|
||||
expect(diff).toContain('+export const retry = (n: number) => Math.min(30_000, 500 * 2 ** n);');
|
||||
});
|
||||
|
||||
test('control: origin/HEAD naming main captures the same draft', () => {
|
||||
const main = capture('main-head', clone('main-head', 'main'));
|
||||
expect(main.draft.base).toBe('origin/main');
|
||||
expect(main.draft.files).toEqual(['lib/worker.ts']);
|
||||
expect(main.diff).toContain('+export const retry = (n: number) => Math.min(30_000, 500 * 2 ** n);');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user