mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
feat(preamble): office-hours' Q<N> form is its own subsection, so it stops copying the shared AskUserQuestion subsections (GSTA-96)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
4da2eba959
commit
5db328f2b9
6 files changed
+49
-86
No files matched your search
+12
-1
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
**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,365 bytes (its parity union goes from 160,989 to 154,653, under the pre-hoist 1.18 cap).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **`scripts/resolvers/preamble/generate-ask-user-format.ts`**: office-hours' `Q<N>` form and "a free-text reply answers the most recent unanswered `Q<N>`" move into one `### Open-ended questions` subsection appended to the AskUserQuestion section; no wording of the form changes, and other skills render byte-identically.
|
||||||
|
- **`scripts/resolvers/preamble-hoist.ts`**: a skill variant may add subsections after the shared ones. It keeps only its changed and added subsections, and its note says "Only these subsections replace the shared ones or, under a new heading, add to its end". Variants without additions keep the existing note.
|
||||||
|
- **plan-eng-review keeps its versions** of the shared Tool resolution, fallback, Format, Self-check, Voice, plan-mode invocation and Plan Status Footer text, because each differs on purpose (the Scope gate, D-numbering after the target menu, the em-dash rule for composed prose, the selected-report footer, its own self-check). Its cap is unchanged.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
- `test/helpers/skill-startup.ts` applies added subsections when it builds the effective text, so the hoisted-equals-inline check covers office-hours' new layout.
|
||||||
|
- `test/helpers/carve-guards.ts`: office-hours' parity cap goes down from 1.2279 to 1.1796, its measured union. No cap or pin goes up.
|
||||||
|
|
||||||
**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 content id, 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 session's first invocation costs about 170 tokens more than before (about 1,900 for office-hours, about 3,800 for plan-eng-review) and every later one saves about 5,800 (4,000 for office-hours, 2,200 for plan-eng-review). A /autoplan run over all four review phases reads 17,941 fewer tokens (191,871 → 173,930), because its review-skill methodology loads no longer repeat the preamble.
|
**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 content id, 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 session's first invocation costs about 170 tokens more than before (about 1,900 for office-hours, about 3,800 for plan-eng-review) and every later one saves about 5,800 (4,000 for office-hours, 2,200 for plan-eng-review). A /autoplan run over all four review phases reads 17,941 fewer tokens (191,871 → 173,930), because its review-skill methodology loads no longer repeat the preamble.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
@@ -12,7 +23,7 @@
|
|||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
- `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/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 copies of shared sections twice until a follow-up dedupes them.
|
- `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/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/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/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.
|
- `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.
|
||||||
|
|||||||
+3
-63
@@ -82,38 +82,11 @@ If `SKILL_PREFIX` is `"true"`, suggest/invoke `/gstack-*` names. Disk paths stay
|
|||||||
|
|
||||||
## AskUserQuestion Format
|
## AskUserQuestion Format
|
||||||
|
|
||||||
Only these subsections replace the shared ones; the rest of this section is shared.
|
Only these subsections replace the shared ones or, under a new heading, add to its end; the rest of this section is shared.
|
||||||
|
|
||||||
### When AskUserQuestion is unavailable or a call fails
|
### Open-ended questions
|
||||||
|
|
||||||
Tell these apart:
|
**Open-question prose form (`Q<N>`)** — for open-ended questions with no fixed option set (the Phase 2A/2B diagnostic questions) when you are in prose, use this form instead of a `D<N>` brief; a free-text reply answers the most recent unanswered `Q<N>`:
|
||||||
|
|
||||||
1. **Auto-decide denial (NOT a failure).** The result contains `[plan-tune auto-decide] <id> → <option>` — the preference hook as designed. Proceed with that option. Do NOT retry, do NOT fall back to prose.
|
|
||||||
2. **Genuine failure** — no variant in your tool list, OR the variant is present but the call returns an error / missing result (MCP transport error, empty result, host bug such as Conductor's flaky MCP variant above).
|
|
||||||
- If it was present and **errored** (not absent), retry the SAME call **once** — only if no answer could have surfaced (a missing-result error can arrive after the user saw the question; retrying would double-prompt, so if it may have reached them, treat it as pending and don't retry).
|
|
||||||
- Then branch on `SESSION_KIND` (echoed by the preamble; empty/absent ⇒ `interactive`):
|
|
||||||
- `spawned` → defer to the **Spawned session** block: auto-choose the recommended option. Never prose, never BLOCKED.
|
|
||||||
- `unattended` → auto-choose the recommended option; a consent, unrecommended question or approval gate becomes a pending gate item (Unattended session block).
|
|
||||||
- `headless` → `BLOCKED — AskUserQuestion unavailable`; stop and wait (no human can answer).
|
|
||||||
- `interactive` → **prose fallback** (below).
|
|
||||||
|
|
||||||
**Prose fallback — render the decision brief as a markdown message, not a tool call.** Same information as the tool format below in paragraphs, not ✅/❌ bullets. It MUST surface this triad:
|
|
||||||
|
|
||||||
1. **A clear ELI10 of the issue itself** — plain English on what's being decided and why it matters (the question, not per-choice), naming the stakes. Lead with it.
|
|
||||||
2. **Completeness scores per choice** — explicit on EACH choice, per the Completeness rule in the Format section below; never drop the score.
|
|
||||||
3. **The recommendation and why** — the `Recommendation: <choice> because <reason>` line plus the `(recommended)` marker on that choice.
|
|
||||||
|
|
||||||
Layout: a `D<N>` title; an explicit reply line listing the offered selectors; the issue ELI10; the Recommendation line; ONE paragraph per choice with its `(recommended)` marker, `Completeness: X/10`, and 2-4 sentences of reasoning (never a bare bullet list); a closing `Net:` line. With `QUESTION_TUNING: true`, append the checked `<gstack-qid:{question_id}>` to the explicit reply line. Split chains / 5+ options: one prose block per per-option call, in order. Before an interactive prose question, finish the tool calls that do not depend on its answer; then send the complete brief as the turn's final message and STOP and wait for the typed answer. An open-ended question with no options list uses the `Q<N>` form below instead of a `D<N>` brief; a free-text reply answers the most recent unanswered `Q<N>`. Do not publish an earlier copy during tool work or follow it with tools or a waiting message. In plan mode this satisfies end-of-turn like a tool call.
|
|
||||||
|
|
||||||
**Continuation — mapping a typed reply back to a brief.** Each brief carries a stable label (`D<N>`, or `D<N>.k` in a split chain) the user references (e.g. "3.2: B"). A bare letter maps to the most-recent UNANSWERED brief; if more than one is open (a split chain), do NOT guess — ask which `D<N>.k` it answers. Never apply a bare letter ambiguously across a chain.
|
|
||||||
|
|
||||||
**One-way / destructive confirmations in prose.** A one-way door (irreversible or destructive: delete, force-push, drop, overwrite) makes prose a WEAKER gate than the tool, so strengthen it: require an explicit typed confirmation (the exact option letter or word), state plainly what is irreversible, and NEVER proceed on a vague, partial or ambiguous reply — re-ask. Silence or "ok"/"sure" without the explicit choice is not-yet-confirmed.
|
|
||||||
|
|
||||||
### Format
|
|
||||||
|
|
||||||
Every AskUserQuestion is a decision brief and must be sent as tool_use, not prose — unless the documented failure fallback above applies (interactive session + the call is unavailable/erroring), when the prose fallback is the correct output.
|
|
||||||
|
|
||||||
**Open-question prose form (`Q<N>`)** — for open-ended questions with no fixed option set (the Phase 2A/2B diagnostic questions) when you are in prose:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Q<N> — <question, verbatim>
|
Q<N> — <question, verbatim>
|
||||||
@@ -124,39 +97,6 @@ Reply in your own words — I'll wait.
|
|||||||
|
|
||||||
Q-numbering starts at `Q1` per invocation, independent of D-numbering. Questions with discrete options always use `D<N>`.
|
Q-numbering starts at `Q1` per invocation, independent of D-numbering. Questions with discrete options always use `D<N>`.
|
||||||
|
|
||||||
```
|
|
||||||
D<N> — <one-line question title>
|
|
||||||
Project/branch/task: <1 short grounding sentence using _BRANCH>
|
|
||||||
ELI10: <plain English a 16-year-old could follow, 2-4 sentences, name the stakes>
|
|
||||||
Stakes if we pick wrong: <one sentence on what breaks, what user sees, what's lost>
|
|
||||||
Recommendation: <choice> because <one-line reason>
|
|
||||||
Completeness: A=X/10, B=Y/10 (or: Note: options differ in kind, not coverage — no completeness score)
|
|
||||||
Pros / cons:
|
|
||||||
A) <option label> (recommended)
|
|
||||||
✅ <pro — concrete, observable, ≥40 chars>
|
|
||||||
❌ <con — honest, ≥40 chars>
|
|
||||||
B) <option label>
|
|
||||||
✅ <pro>
|
|
||||||
❌ <con>
|
|
||||||
Net: <one-line synthesis of what you're actually trading off>
|
|
||||||
```
|
|
||||||
|
|
||||||
D-numbering: first question in a skill invocation is `D1`; increment yourself. This is a model-level instruction, not a runtime counter.
|
|
||||||
|
|
||||||
ELI10 is always present, in plain English, not function names; Recommendation is ALWAYS present. Keep the `(recommended)` label; AUTO_DECIDE depends on it.
|
|
||||||
|
|
||||||
Completeness: use `Completeness: N/10` only when options differ in coverage. 10 = complete, 7 = happy path, 3 = shortcut. If options differ in kind, write: `Note: options differ in kind, not coverage — no completeness score.`
|
|
||||||
|
|
||||||
Accepted shortcuts leave a trail: when the user selects an option that is BOTH Completeness ≤ 7 AND a durable-scope call (architecture or scope-cut, never a turn-level choice), log it via `gstack-decision-log` with the ceiling and the upgrade trigger in the rationale, and, while implementing that option (same edit, no follow-up question), mark each cut corner in code with `gstack-shortcut(dec-<id>): <ceiling>, upgrade when <trigger>` in the language's comment syntax. Never agent-initiated: the marker exists only downstream of the user's explicit choice. /retro harvests these into a debt ledger, joined on the decision id.
|
|
||||||
|
|
||||||
`Pros / cons:` in question text; descriptions use literal ✅/❌ bullets, not Pro:/Con:. Each real option: ≥2 pros and ≥1 con, ≥40 chars each. One-way/destructive escape: `✅ No cons — this is a hard-stop choice`.
|
|
||||||
|
|
||||||
Neutral posture: `Recommendation: <default> — this is a taste call, no strong preference either way`; `(recommended)` STAYS on the default option for AUTO_DECIDE.
|
|
||||||
|
|
||||||
Effort both-scales: when an option involves effort, label both human-team and CC+gstack time, e.g. `(human: ~2 days / CC: ~15 min)`, so AI compression is visible at decision time.
|
|
||||||
|
|
||||||
`Net:` line closes question text. Per-skill instructions may add stricter rules.
|
|
||||||
|
|
||||||
## Question Tuning (skip entirely if `QUESTION_TUNING: false`)
|
## Question Tuning (skip entirely if `QUESTION_TUNING: false`)
|
||||||
|
|
||||||
Before each decision brief (AskUserQuestion or Conductor/fallback prose), choose `question_id` from `~/.claude/skills/gstack/scripts/question-registry.ts` or `{skill}-{slug}`, then run `~/.claude/skills/gstack/bin/gstack-question-preference --check "<id>"`; for an unregistered id, write the question summary to `.gstack/tmp/qt.txt` (file-write tool) and append `--summary-file .gstack/tmp/qt.txt` (one-way keyword check). `AUTO_DECIDE` means choose the recommended option and say "Auto-decided [summary] → [option] (your preference). Change with /plan-tune." `ASK_NORMALLY` means ask.
|
Before each decision brief (AskUserQuestion or Conductor/fallback prose), choose `question_id` from `~/.claude/skills/gstack/scripts/question-registry.ts` or `{skill}-{slug}`, then run `~/.claude/skills/gstack/bin/gstack-question-preference --check "<id>"`; for an unregistered id, write the question summary to `.gstack/tmp/qt.txt` (file-write tool) and append `--summary-file .gstack/tmp/qt.txt` (one-way keyword check). `AUTO_DECIDE` means choose the recommended option and say "Auto-decided [summary] → [option] (your preference). Change with /plan-tune." `ASK_NORMALLY` means ask.
|
||||||
|
|||||||
@@ -70,10 +70,14 @@ const headingOf = (section: string) => section.split('\n', 1)[0]!;
|
|||||||
function variantSubsections(own: string, shared: string): string | null {
|
function variantSubsections(own: string, shared: string): string | null {
|
||||||
const ownParts = splitSections(own, '### ');
|
const ownParts = splitSections(own, '### ');
|
||||||
const sharedParts = splitSections(shared, '### ');
|
const sharedParts = splitSections(shared, '### ');
|
||||||
if (ownParts.length < 2 || ownParts.length !== sharedParts.length || ownParts[0] !== sharedParts[0]) return null;
|
if (ownParts.length < 2 || ownParts.length < sharedParts.length || ownParts[0] !== sharedParts[0]) return null;
|
||||||
if (ownParts.some((part, i) => headingOf(part) !== headingOf(sharedParts[i]!))) return null;
|
if (sharedParts.some((part, i) => headingOf(part) !== headingOf(ownParts[i]!))) return null;
|
||||||
const changed = ownParts.slice(1).filter((part, i) => part !== sharedParts[i + 1]);
|
const changed = ownParts.slice(1, sharedParts.length).filter((part, i) => part !== sharedParts[i + 1]);
|
||||||
return [`${headingOf(own)}\n\nOnly these subsections replace the shared ones; the rest of this section is shared.`, ...changed].join('\n\n');
|
const added = ownParts.slice(sharedParts.length);
|
||||||
|
const note = added.length
|
||||||
|
? 'Only these subsections replace the shared ones or, under a new heading, add to its end; the rest of this section is shared.'
|
||||||
|
: 'Only these subsections replace the shared ones; the rest of this section is shared.';
|
||||||
|
return [`${headingOf(own)}\n\n${note}`, ...changed, ...added].join('\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Where the installed SKILL.md finds its copy of the shared file. */
|
/** Where the installed SKILL.md finds its copy of the shared file. */
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Tell these apart:
|
|||||||
2. **Completeness scores per choice** — explicit on EACH choice, per the Completeness rule in the Format section below; never drop the score.
|
2. **Completeness scores per choice** — explicit on EACH choice, per the Completeness rule in the Format section below; never drop the score.
|
||||||
3. **The recommendation and why** — the \`Recommendation: <choice> because <reason>\` line plus the \`(recommended)\` marker on that choice.
|
3. **The recommendation and why** — the \`Recommendation: <choice> because <reason>\` line plus the \`(recommended)\` marker on that choice.
|
||||||
|
|
||||||
Layout: a \`D<N>\` title; an explicit reply line listing the offered selectors; the issue ELI10; the Recommendation line; ONE paragraph per choice with its \`(recommended)\` marker, \`Completeness: X/10\`, and 2-4 sentences of reasoning (never a bare bullet list); a closing \`Net:\` line. With \`QUESTION_TUNING: true\`, append the checked \`<gstack-qid:{question_id}>\` to the explicit reply line. Split chains / 5+ options: one prose block per per-option call, in order. Before an interactive prose question, finish the tool calls that do not depend on its answer; then send the complete brief as the turn's final message and STOP and wait for the typed answer.${openQuestions ? ' An open-ended question with no options list uses the `Q<N>` form below instead of a `D<N>` brief; a free-text reply answers the most recent unanswered `Q<N>`.' : ''} Do not publish an earlier copy during tool work or follow it with tools or a waiting message. In plan mode this satisfies end-of-turn like a tool call.
|
Layout: a \`D<N>\` title; an explicit reply line listing the offered selectors; the issue ELI10; the Recommendation line; ONE paragraph per choice with its \`(recommended)\` marker, \`Completeness: X/10\`, and 2-4 sentences of reasoning (never a bare bullet list); a closing \`Net:\` line. With \`QUESTION_TUNING: true\`, append the checked \`<gstack-qid:{question_id}>\` to the explicit reply line. Split chains / 5+ options: one prose block per per-option call, in order. Before an interactive prose question, finish the tool calls that do not depend on its answer; then send the complete brief as the turn's final message and STOP and wait for the typed answer. Do not publish an earlier copy during tool work or follow it with tools or a waiting message. In plan mode this satisfies end-of-turn like a tool call.
|
||||||
|
|
||||||
**Continuation — mapping a typed reply back to a brief.** Each brief carries a stable label (\`D<N>\`, or \`D<N>.k\` in a split chain) the user references (e.g. "3.2: B"). A bare letter maps to the most-recent UNANSWERED brief; if more than one is open (a split chain), do NOT guess — ask which \`D<N>.k\` it answers. Never apply a bare letter ambiguously across a chain.
|
**Continuation — mapping a typed reply back to a brief.** Each brief carries a stable label (\`D<N>\`, or \`D<N>.k\` in a split chain) the user references (e.g. "3.2: B"). A bare letter maps to the most-recent UNANSWERED brief; if more than one is open (a split chain), do NOT guess — ask which \`D<N>.k\` it answers. Never apply a bare letter ambiguously across a chain.
|
||||||
|
|
||||||
@@ -45,18 +45,7 @@ Layout: a \`D<N>\` title; an explicit reply line listing the offered selectors;
|
|||||||
### Format
|
### Format
|
||||||
|
|
||||||
Every AskUserQuestion is a decision brief and must be sent as tool_use, not prose — unless the documented failure fallback above applies (interactive session + the call is unavailable/erroring), when the prose fallback is the correct output.
|
Every AskUserQuestion is a decision brief and must be sent as tool_use, not prose — unless the documented failure fallback above applies (interactive session + the call is unavailable/erroring), when the prose fallback is the correct output.
|
||||||
${openQuestions ? `
|
|
||||||
**Open-question prose form (\`Q<N>\`)** — for open-ended questions with no fixed option set (the Phase 2A/2B diagnostic questions) when you are in prose:
|
|
||||||
|
|
||||||
\`\`\`text
|
|
||||||
Q<N> — <question, verbatim>
|
|
||||||
Why I'm asking: <1-2 sentences: stakes, what a weak answer would mean>
|
|
||||||
What a strong answer sounds like: <the section's "push until you hear" line>
|
|
||||||
Reply in your own words — I'll wait.
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
Q-numbering starts at \`Q1\` per invocation, independent of D-numbering. Questions with discrete options always use \`D<N>\`.
|
|
||||||
` : ''}
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
D<N> — <one-line question title>
|
D<N> — <one-line question title>
|
||||||
Project/branch/task: <1 short grounding sentence using _BRANCH>
|
Project/branch/task: <1 short grounding sentence using _BRANCH>
|
||||||
@@ -137,5 +126,18 @@ ${planReview ? `Before emitting a tool or prose decision brief, verify:
|
|||||||
- [ ] If you had 5+ options, you split (or batched into ≤4-groups) — did NOT drop any
|
- [ ] If you had 5+ options, you split (or batched into ≤4-groups) — did NOT drop any
|
||||||
- [ ] If you split, you checked dependencies between options before firing the chain
|
- [ ] If you split, you checked dependencies between options before firing the chain
|
||||||
- [ ] If a per-option Hold fires, you stopped the chain immediately (didn't queue)`}
|
- [ ] If a per-option Hold fires, you stopped the chain immediately (didn't queue)`}
|
||||||
`;
|
${openQuestions ? `
|
||||||
|
### Open-ended questions
|
||||||
|
|
||||||
|
**Open-question prose form (\`Q<N>\`)** — for open-ended questions with no fixed option set (the Phase 2A/2B diagnostic questions) when you are in prose, use this form instead of a \`D<N>\` brief; a free-text reply answers the most recent unanswered \`Q<N>\`:
|
||||||
|
|
||||||
|
\`\`\`text
|
||||||
|
Q<N> — <question, verbatim>
|
||||||
|
Why I'm asking: <1-2 sentences: stakes, what a weak answer would mean>
|
||||||
|
What a strong answer sounds like: <the section's "push until you hear" line>
|
||||||
|
Reply in your own words — I'll wait.
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Q-numbering starts at \`Q1\` per invocation, independent of D-numbering. Questions with discrete options always use \`D<N>\`.
|
||||||
|
` : ''}`;
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
|||||||
// 1.08 → 1.10: the scope-gate exceptions block (+ its adversarial-review
|
// 1.08 → 1.10: the scope-gate exceptions block (+ its adversarial-review
|
||||||
// hardening: host-anchored mode signal, precedence, passing-mention
|
// hardening: host-anchored mode signal, precedence, passing-mention
|
||||||
// guards) and the plan-mode preamble reword land the union at 1.092.
|
// guards) and the plan-mode preamble reword land the union at 1.092.
|
||||||
maxSizeRatio: 1.3290, // + clarity rules for saved decisions/setup gates + the Aside probe's failure reason; measured 1.1504. + test value bar and Tests to Retire in the lazy Test review section (~2.6KB); measured 1.168 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.173 (2026-09-30). + v1.91.12.0 merge of #2999 (headless rule: a disallowed question tool never qualifies) with #3002; measured 1.1741 (2026-10-01). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 146,905 / 124,597 = 1.1790 (2026-10-06). Combined Oct 6 wave head (A and B): measured 147,524 / 124,597 = 1.1840 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 147,693 / 124,597 = 1.1854 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 147,752 / 124,597 = 1.1858 (2026-10-07). + fix wave PR D: the prerequisite offer is skipped in spawned/headless sessions (#1958); measured 147,864 / 124,597 = 1.1867 (2026-10-07). + Oct 10 multi-agent wave PR D1 (reality rows): the Scope Challenge reality-rows block, the deep incident table (18 rows, each tied to the incident that earned it), the Completion summary line and the checklist row; measured 151,423 / 124,597 = 1.2153 (2026-10-10). + #280: the Design Doc Check finds the CEO plan (`gstack-ceo-plan-find`) and names it in the data rule, +325 bytes on main's 151,630; measured 151,955 / 124,597 = 1.2196 (2026-10-12). Fix wave PR 4 (owner-approved raise 1.2170 → 1.2196): #3182's timer-bounded native fallback (net −17 bytes) plus #280's cross-branch rule, offset by tightening the Design Doc Check; measured 151,950 / 124,597 = 1.2195 (2026-10-15). + GSTA-96 shared-preamble hoist (owner-approved raise, Garry 2026-10-25: "do it"): the union now holds sections/preamble.md, so a fresh invocation reads the pointer plus the shared file and its own copies of shared sections twice until their dedupe; measured 165,589 / 124,597 = 1.3290 (2026-10-25). Before: 1.2196.
|
maxSizeRatio: 1.3290, // + clarity rules for saved decisions/setup gates + the Aside probe's failure reason; measured 1.1504. + test value bar and Tests to Retire in the lazy Test review section (~2.6KB); measured 1.168 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.173 (2026-09-30). + v1.91.12.0 merge of #2999 (headless rule: a disallowed question tool never qualifies) with #3002; measured 1.1741 (2026-10-01). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 146,905 / 124,597 = 1.1790 (2026-10-06). Combined Oct 6 wave head (A and B): measured 147,524 / 124,597 = 1.1840 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 147,693 / 124,597 = 1.1854 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 147,752 / 124,597 = 1.1858 (2026-10-07). + fix wave PR D: the prerequisite offer is skipped in spawned/headless sessions (#1958); measured 147,864 / 124,597 = 1.1867 (2026-10-07). + Oct 10 multi-agent wave PR D1 (reality rows): the Scope Challenge reality-rows block, the deep incident table (18 rows, each tied to the incident that earned it), the Completion summary line and the checklist row; measured 151,423 / 124,597 = 1.2153 (2026-10-10). + #280: the Design Doc Check finds the CEO plan (`gstack-ceo-plan-find`) and names it in the data rule, +325 bytes on main's 151,630; measured 151,955 / 124,597 = 1.2196 (2026-10-12). Fix wave PR 4 (owner-approved raise 1.2170 → 1.2196): #3182's timer-bounded native fallback (net −17 bytes) plus #280's cross-branch rule, offset by tightening the Design Doc Check; measured 151,950 / 124,597 = 1.2195 (2026-10-15). + GSTA-96 shared-preamble hoist (owner-approved raise, Garry 2026-10-25: "do it"): the union now holds sections/preamble.md, so a fresh invocation reads the pointer plus the shared file and its own versions of shared sections; measured 165,589 / 124,597 = 1.3290 (2026-10-25). Those versions stay after the GSTA-96 dedupe (2026-10-26) because each differs on purpose: the Scope gate in Tool resolution, D-numbering after the target menu, the em-dash rule for composed prose, the selected-report footer, its own self-check and plan-mode invocation. Before: 1.2196.
|
||||||
},
|
},
|
||||||
'plan-design-review': {
|
'plan-design-review': {
|
||||||
skill: 'plan-design-review',
|
skill: 'plan-design-review',
|
||||||
@@ -353,7 +353,7 @@ do not launch the downstream skill or open a browser.`,
|
|||||||
maxSkeletonBytes: 95_900, // Office-hours + sketch outside voices include host guards and completion checks. + v1.91.19.0 wave: Q<N> open-question prose form (#2729, approved Q3), DESIGN_READY --version probe (B4), Aside ~/.local/bin fallback (E7), verdict-form outside voice (INV-1), plus v1.91.17.0's round accounting (#3022) merged in; measured 89,684 (2026-10-03). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); skeleton measured 92,590 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; skeleton measured 92,869 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; skeleton measured 92,930 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; skeleton measured 92,756 (2026-10-07). Merged with the guard area: measured skeleton 93,099 (2026-10-07). + fix wave PR D: the Phase 4.5 builder-profile read before the session log (#2801) and the pre-telemetry design-doc check (#1049); measured skeleton 94,877 (2026-10-07). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10).
|
maxSkeletonBytes: 95_900, // Office-hours + sketch outside voices include host guards and completion checks. + v1.91.19.0 wave: Q<N> open-question prose form (#2729, approved Q3), DESIGN_READY --version probe (B4), Aside ~/.local/bin fallback (E7), verdict-form outside voice (INV-1), plus v1.91.17.0's round accounting (#3022) merged in; measured 89,684 (2026-10-03). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); skeleton measured 92,590 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; skeleton measured 92,869 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; skeleton measured 92,930 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; skeleton measured 92,756 (2026-10-07). Merged with the guard area: measured skeleton 93,099 (2026-10-07). + fix wave PR D: the Phase 4.5 builder-profile read before the session log (#2801) and the pre-telemetry design-doc check (#1049); measured skeleton 94,877 (2026-10-07). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10).
|
||||||
minUnionBytes: 115_800, // Phase 4 wave 4; measured union 118,175
|
minUnionBytes: 115_800, // Phase 4 wave 4; measured union 118,175
|
||||||
mustContain: ['design doc', 'problem statement'],
|
mustContain: ['design doc', 'problem statement'],
|
||||||
maxSizeRatio: 1.2279, // + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 149,758 / 131,110 = 1.1422 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 150,037 / 131,110 = 1.1444 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 150,098 / 131,110 = 1.1448 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 150,011 / 131,110 = 1.1442 (2026-10-07). Merged with the guard area: measured 150,354 / 131,110 = 1.1468 (2026-10-07). + fix wave PR D: builder-profile read before the session log with Phase 6 using the carried values (#2801), the inline design doc before approval (#879) and the pre-telemetry design-doc check (#1049); measured 153,206 / 131,110 = 1.1685 (2026-10-07). + empty LAST_ASSIGNMENT/LAST_PROJECT guards in the welcome_back and regular greetings (backfilled profiles); measured 153,469 / 131,110 = 1.1705 (2026-10-08). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). + GSTA-96 shared-preamble hoist (owner-approved raise, Garry 2026-10-25: "do it"): the union now holds sections/preamble.md, so a fresh invocation reads the pointer plus the shared file and its own copies of shared sections twice until their dedupe; measured 160,989 / 131,110 = 1.2279 (2026-10-25). Before: 1.18.
|
maxSizeRatio: 1.1796, // + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 149,758 / 131,110 = 1.1422 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 150,037 / 131,110 = 1.1444 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 150,098 / 131,110 = 1.1448 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 150,011 / 131,110 = 1.1442 (2026-10-07). Merged with the guard area: measured 150,354 / 131,110 = 1.1468 (2026-10-07). + fix wave PR D: builder-profile read before the session log with Phase 6 using the carried values (#2801), the inline design doc before approval (#879) and the pre-telemetry design-doc check (#1049); measured 153,206 / 131,110 = 1.1685 (2026-10-07). + empty LAST_ASSIGNMENT/LAST_PROJECT guards in the welcome_back and regular greetings (backfilled profiles); measured 153,469 / 131,110 = 1.1705 (2026-10-08). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). + GSTA-96 shared-preamble hoist (owner-approved raise, Garry 2026-10-25: "do it"): the union now holds sections/preamble.md, so a fresh invocation reads the pointer plus the shared file and its own copies of shared sections; measured 160,989 / 131,110 = 1.2279 (2026-10-25). - GSTA-96 dedupe: the Q<N> open-question form is office-hours' own appended subsection, so its copies of the shared Format and fallback subsections go; measured 154,653 / 131,110 = 1.1796 (2026-10-26). Before: 1.18.
|
||||||
},
|
},
|
||||||
'document-release': {
|
'document-release': {
|
||||||
skill: 'document-release',
|
skill: 'document-release',
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ export function readSkillStartup(skillMd: string): string {
|
|||||||
return `${skill.slice(0, at)}${shared}\n${skill.slice(at)}`;
|
return `${skill.slice(0, at)}${shared}\n${skill.slice(at)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VARIANT_NOTE = 'Only these subsections replace the shared ones; the rest of this section is shared.';
|
const VARIANT_NOTES = [
|
||||||
|
'Only these subsections replace the shared ones; the rest of this section is shared.',
|
||||||
|
'Only these subsections replace the shared ones or, under a new heading, add to its end; the rest of this section is shared.',
|
||||||
|
];
|
||||||
|
|
||||||
/** Split markdown at one heading level outside code fences; parts are trimmed. */
|
/** Split markdown at one heading level outside code fences; parts are trimmed. */
|
||||||
function splitAt(text: string, marker: string): string[] {
|
function splitAt(text: string, marker: string): string[] {
|
||||||
@@ -80,9 +83,12 @@ export function readSkillEffective(skillMd: string): string {
|
|||||||
const effective = shared.map(section => {
|
const effective = shared.map(section => {
|
||||||
const override = overrides.get(headingOf(section));
|
const override = overrides.get(headingOf(section));
|
||||||
if (!override) return section;
|
if (!override) return section;
|
||||||
if (!override.includes(VARIANT_NOTE)) return override;
|
if (!VARIANT_NOTES.some(note => override.includes(note))) return override;
|
||||||
const replacements = new Map(splitAt(override, '### ').slice(1).map(part => [headingOf(part), part]));
|
const replacements = new Map(splitAt(override, '### ').slice(1).map(part => [headingOf(part), part]));
|
||||||
return splitAt(section, '### ').map(part => replacements.get(headingOf(part)) ?? part).join('\n\n');
|
const parts = splitAt(section, '### ');
|
||||||
|
const sharedHeads = new Set(parts.map(headingOf));
|
||||||
|
const added = [...replacements.values()].filter(part => !sharedHeads.has(headingOf(part)));
|
||||||
|
return [...parts.map(part => replacements.get(headingOf(part)) ?? part), ...added].join('\n\n');
|
||||||
});
|
});
|
||||||
return `${skill.slice(0, pointer + 1)}${effective.join('\n\n')}\n\n${kept.join('')}`;
|
return `${skill.slice(0, pointer + 1)}${effective.join('\n\n')}\n\n${kept.join('')}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user