@pmelab/gtd 9.13.0 → 9.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -33
- package/dist/gtd.bundle.mjs +546 -565
- package/package.json +1 -1
- package/schema.json +1 -1
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -30940,19 +30940,26 @@ const make$1 = (layer, memoMap) => {
|
|
|
30940
30940
|
const make = make$1;
|
|
30941
30941
|
//#endregion
|
|
30942
30942
|
//#region src/Commentary.ts
|
|
30943
|
-
/**
|
|
30943
|
+
/**
|
|
30944
|
+
* One line of stderr commentary — `narrate` is gated entirely by how the
|
|
30945
|
+
* layer was built, never by the call site; `warn` is its ungated
|
|
30946
|
+
* counterpart, always written regardless of verbosity.
|
|
30947
|
+
*/
|
|
30944
30948
|
var Narrator = class Narrator extends Tag("Narrator")() {
|
|
30945
30949
|
/**
|
|
30946
30950
|
* `write` is the raw stderr sink (`CliIo.stderr` in production, so the
|
|
30947
30951
|
* in-memory `CliIo` used by `@inmem` e2e scenarios captures narration into
|
|
30948
30952
|
* the exact same buffer it already captures errors into). `verbose` gates
|
|
30949
|
-
* every line at construction time — the resulting service is a
|
|
30950
|
-
* writer when `false`, never a per-call check a narrating call site
|
|
30951
|
-
* remember to make.
|
|
30953
|
+
* every `narrate` line at construction time — the resulting service is a
|
|
30954
|
+
* no-op writer when `false`, never a per-call check a narrating call site
|
|
30955
|
+
* has to remember to make. `warn` bypasses that gate unconditionally.
|
|
30952
30956
|
*/
|
|
30953
|
-
static layer = (write, verbose) => succeed$2(Narrator, {
|
|
30954
|
-
|
|
30955
|
-
|
|
30957
|
+
static layer = (write, verbose) => succeed$2(Narrator, {
|
|
30958
|
+
narrate: (line) => sync$1(() => {
|
|
30959
|
+
if (verbose) write(`${line}\n`);
|
|
30960
|
+
}),
|
|
30961
|
+
warn: (line) => sync$1(() => write(`${line}\n`))
|
|
30962
|
+
});
|
|
30956
30963
|
};
|
|
30957
30964
|
/**
|
|
30958
30965
|
* An error carrying REMEDIATION alongside its message — the offending config
|
|
@@ -189713,7 +189720,7 @@ const STATE_FIELDS = {
|
|
|
189713
189720
|
fileRef: true,
|
|
189714
189721
|
rest: "rendered",
|
|
189715
189722
|
viz: "field",
|
|
189716
|
-
doc: "The agent harness system prompt stamped onto every one of this machine's own `prompt` states — emitted verbatim in `gtd next --json
|
|
189723
|
+
doc: "The agent harness system prompt stamped onto every one of this machine's own `prompt` states — emitted verbatim in `gtd next --json` for the driver to pass to its agent CLI, never interpreted by gtd. Machine-level only: a machine's `prompt` states share one resumed session, and a system prompt must be identical across every call of it. A `./` or `../` value is inlined from the declaring config file's directory. A machine declaring this with no `prompt` state is a config error."
|
|
189717
189724
|
},
|
|
189718
189725
|
/** The raw-state-name fallback lives in the consumer (driver/viewer), not here — gtd simply omits the field when unset. */
|
|
189719
189726
|
label: {
|
|
@@ -190254,25 +190261,64 @@ const validateState = (def, name, names) => {
|
|
|
190254
190261
|
];
|
|
190255
190262
|
};
|
|
190256
190263
|
/**
|
|
190264
|
+
* A `script`/`message` state with no `C` row commits nothing on a clean tree
|
|
190265
|
+
* (the documented no-op default) — that's often deliberate, but silent for a
|
|
190266
|
+
* state whose author never considered the clean case. Warn, don't error.
|
|
190267
|
+
* Three exemptions, all load-bearing:
|
|
190268
|
+
*
|
|
190269
|
+
* - The workflow's initial state — a `C` row there would author a commit on
|
|
190270
|
+
* every bare driver invocation.
|
|
190271
|
+
* - A `prompt` state — its clean step is an ATTEMPT by design, not a no-op.
|
|
190272
|
+
* - A `human`-actor state — `docs/driver.md`'s driver protocol lands a human
|
|
190273
|
+
* gate's OPENING beat unconditionally on every restart while a process
|
|
190274
|
+
* rests there ("Some gates could accept by INACTION..."), specifically
|
|
190275
|
+
* because today that's a harmless no-op when the state has no `C` row. A
|
|
190276
|
+
* `C` row on a human gate would turn every such restart into a real commit
|
|
190277
|
+
* before the human has acted at all — the exact hazard the initial-state
|
|
190278
|
+
* exemption above already exists to avoid, generalized to any human gate a
|
|
190279
|
+
* process can rest at more than once.
|
|
190280
|
+
*
|
|
190281
|
+
* No exemption for a bare `"* **"` catch-all row or a declared `file:` — a
|
|
190282
|
+
* `diff` pattern (including `"* **"`) never matches a clean tree
|
|
190283
|
+
* (`matchesPattern`), so neither says anything about the clean case; a state
|
|
190284
|
+
* with either and no `C` row still no-ops on a clean tree exactly like one
|
|
190285
|
+
* with neither.
|
|
190286
|
+
*/
|
|
190287
|
+
const validateHasCRow = (def, name, state) => {
|
|
190288
|
+
if (name === initialStateOf(def)) return [];
|
|
190289
|
+
if (state.actor === "human") return [];
|
|
190290
|
+
const kind = contentKindOf(state);
|
|
190291
|
+
if (kind !== "script" && kind !== "message") return [];
|
|
190292
|
+
if ((state.on ?? []).some(([pattern]) => pattern === "C")) return [];
|
|
190293
|
+
return [`state "${name}" declares no "C" row`];
|
|
190294
|
+
};
|
|
190295
|
+
/**
|
|
190257
190296
|
* Validate a `WorkflowDefinition`, returning human-readable error strings
|
|
190258
|
-
* (empty = valid). Pure — called at
|
|
190259
|
-
* state, `entries` shape
|
|
190260
|
-
*
|
|
190261
|
-
*
|
|
190262
|
-
*
|
|
190263
|
-
*
|
|
190264
|
-
* `src/StateFields.ts`'s
|
|
190297
|
+
* (empty = valid) plus non-fatal warning strings. Pure — called at
|
|
190298
|
+
* config-load time. Checks at least one state, `entries` shape
|
|
190299
|
+
* (`validateEntries`), per-state content/actor/mode/file shape
|
|
190300
|
+
* (`validateState`), `modes:` shape, and reachability from an entry root
|
|
190301
|
+
* (`validateReachability`, only when entries validated clean). Every
|
|
190302
|
+
* per-field rule not listed above (`on`/`retry` targets resolving, `mode`
|
|
190303
|
+
* naming a known vocabulary, etc.) is declared once in `src/StateFields.ts`'s
|
|
190304
|
+
* `STATE_FIELDS` table instead.
|
|
190265
190305
|
*/
|
|
190266
190306
|
const validateDefinition = (def) => {
|
|
190267
190307
|
const names = Object.keys(def.states);
|
|
190268
|
-
if (names.length === 0) return
|
|
190308
|
+
if (names.length === 0) return {
|
|
190309
|
+
errors: ["workflow must declare at least one state"],
|
|
190310
|
+
warnings: []
|
|
190311
|
+
};
|
|
190269
190312
|
const entriesErrors = validateEntries(def, names);
|
|
190270
|
-
return
|
|
190271
|
-
|
|
190272
|
-
|
|
190273
|
-
|
|
190274
|
-
|
|
190275
|
-
|
|
190313
|
+
return {
|
|
190314
|
+
errors: [
|
|
190315
|
+
...entriesErrors,
|
|
190316
|
+
...validateModes(def),
|
|
190317
|
+
...names.flatMap((name) => validateState(def, name, names)),
|
|
190318
|
+
...entriesErrors.length === 0 ? validateReachability(def, names) : []
|
|
190319
|
+
],
|
|
190320
|
+
warnings: names.flatMap((name) => validateHasCRow(def, name, def.states[name]))
|
|
190321
|
+
};
|
|
190276
190322
|
};
|
|
190277
190323
|
//#endregion
|
|
190278
190324
|
//#region src/Machines.ts
|
|
@@ -191863,19 +191909,20 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true, f
|
|
|
191863
191909
|
...summary !== void 0 ? { summary } : {}
|
|
191864
191910
|
};
|
|
191865
191911
|
assertScopesCoverStates(Object.keys(states), flattened.scopes, errors);
|
|
191866
|
-
const
|
|
191867
|
-
const allErrors = Array.from(/* @__PURE__ */ new Set([...errors, ...
|
|
191912
|
+
const definitionResult = validateDefinition(definition);
|
|
191913
|
+
const allErrors = Array.from(/* @__PURE__ */ new Set([...errors, ...definitionResult.errors]));
|
|
191868
191914
|
if (allErrors.length > 0) throw new Error(formatErrors(allErrors));
|
|
191869
191915
|
return {
|
|
191870
191916
|
definition,
|
|
191871
191917
|
vars,
|
|
191872
191918
|
tree: flattened.tree,
|
|
191873
|
-
scopes: flattened.scopes
|
|
191919
|
+
scopes: flattened.scopes,
|
|
191920
|
+
warnings: definitionResult.warnings
|
|
191874
191921
|
};
|
|
191875
191922
|
};
|
|
191876
191923
|
//#endregion
|
|
191877
191924
|
//#region src/workflows/unified.yaml
|
|
191878
|
-
var unified_default = "# gtd's BUILT-IN DEFAULT workflow — used when no `workflow:` key is\n# configured anywhere in the cwd→home config chain; `gtd init` seeds only\n# vars.testCommand + a modes: suggestion, never this file. Compiled through\n# the same `compileWorkflowConfig` a user's own `workflow:` goes through — no\n# privileged path. See skills/authoring/SKILL.md for how to read or edit it\n# (machines, patterns, templates) and docs/driver.md for how a driver runs it.\n#\n# One flow: any change to the tree starts the process. `idle` -> `unwind`\n# reverts that diff (intent survives only in history) -> a green-baseline\n# gate -> `design.triage` groups the reverted diff into ordered, classified\n# concerns and resolves the PRODUCT ones' open questions (`design.gate`) ->\n# `architecture.author` works out the *how* and resolves the TECHNICAL ones\n# (`architecture.gate`) -> `architecture.decompose` writes one package file\n# per concern -> the per-package queue (`packages.*`) builds and reviews each\n# -> the shared tail (`build.review.*`) decides sign-off (-> `idle`, landing\n# an ordinary commit that closes the process — every per-turn commit stays on\n# the branch; run `gtd summary` afterward for a closing-message prompt) vs.\n# actionable feedback (-> `re-unwind` -> back to `design.triage`, a full\n# re-plan lap that never builds on the human's hand-edit). `--entry\n# review-gate.check --var reviewBase=<commitish>` and `--entry fix-precheck`\n# are the two other entries into this same flow.\nvars:\n testCommand: npm test\n # plannerModel: the heavier tier for one-shot triage/design/review turns.\n # coderModel: the tier for build/fix turns. Both repointable via a\n # top-level `vars:` key or a `GTD_<NAME>` env var, like any other var.\n plannerModel: smart\n coderModel: base\n # Declared (not left undeclared) so `--var reviewBase=...` passes the CLI's\n # declared-name check; blank renders empty, which the review entry refuses.\n reviewBase: \"\"\n # gtd's own voice for generated files — adapted from the \"Spartan\" output\n # style (https://github.com/alexgreensh/attention-span, AGPL-3.0, version 0.6),\n # rewritten in gtd's own words for deliverables rather than chat replies;\n # no upstream text ships. A point-in-time derivation with no refresh\n # mechanism.\n styleBlock: |-\n This file is a deliverable, not a chat reply — size follows the work, and\n padding is the target of any cut. Lead with the answer immediately; never\n circle back to restate it once it has landed.\n\n Write flat, commanding sentences — commit to the claim, do not hedge it.\n Favor everyday vocabulary; where a term is unavoidable, define it in five\n words or fewer on first use.\n\n In markdown, bold carries the load: bold the claim, not the sentence around\n it — reading only the bold text must yield the full point and every risk.\n\n Never trim a risk, a number, a threshold, or a scoped condition to save\n space. This outranks every other rule here.\n\n Ship the artifact bare — no lead-in, no sign-off wrapped around it.\n Compressing is not dropping: a task with three load-bearing parts ships all\n three, each shorter, never two of three.\n\n One idea per block; break when the idea shifts. Flag risk in one blunt\n line rather than hedged prose. Never narrate the work — do it.\n styleFormatContract: |-\n This file is machine-read; its format contract outranks every style rule\n above it: keep every `##`/`###` heading, every `- [ ]` checkbox row, and\n every marker line exactly as specified — do not renumber or rename a\n heading or replace a checkbox list with prose. A parser reads these\n literally; a violation refuses the turn outright.\n\n Voice rules above govern the prose between these elements, never the\n elements themselves.\n\n # One persona per prompt-bearing machine below, stamped as its `system:`\n # (see src/StateFields.ts's `system`). `--system-prompt` REPLACES a\n # harness's default system prompt — including its injected cwd/env/\n # git-status block — so each persona is written self-contained, restating\n # that context explicitly rather than relying on it.\n\n # Shared conduct tail, appended after every persona's own role paragraph:\n # use tools without asking, orient yourself with git, and go inspect what\n # the turn's message names. Deliberately NOT named `*Persona`:\n # `templates.test.ts` derives its six-name persona set from\n # `it\\.vars\\.(\\w+Persona)`, so that suffix would silently grow the set.\n agentConduct: |-\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended: there is\n no one to grant permission, confirm a command, or approve a plan before\n you act. Investigate with real commands rather than assuming what a file,\n a commit, or a decision's status actually is, then act on what you find.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no branch, no recent history, no tree state. Run\n `git status`, `git log`, `ls` and the like yourself before you do\n anything else; that is the normal first move of this turn, not an extra\n step.\n\n The message you receive each turn names a commit, a range, or a file for\n you to go inspect yourself — `git show`, `git diff`, reading the file\n directly — never a diff or a summary rendered into the conversation. Go\n look before you act. Across the several turns one conversation may span,\n work from what you already read and settled earlier rather than\n re-deriving it from a cold read every time, unless the turn's own\n instructions tell you this is a fresh, cold pass.\n designPersona: |-\n You are the product-facing planning voice in an autonomous build pipeline\n called gtd. Your job is to turn a raw, half-formed sketch — a hand-edit to\n real code, a scratch note, or both — into an ordered, classified list of\n concerns a build can execute, and to carry a running conversation with a\n human about the product decisions that list depends on. You are read by\n that human, so write plainly and decisively; you are not read by a parser\n except where a state's own instructions say otherwise.\n architectPersona: |-\n You are the technical planning voice in an autonomous build pipeline\n called gtd. You take over once the product-facing concerns are settled,\n reading them cold — you do not carry over the product conversation, only\n its written, settled output. Your job is to work out the technical *how*\n for each settled concern — file and module structure, data models,\n library and tech-stack choices, error-handling strategy — raise the\n technical open questions a human still needs to decide, and once every\n question is answered, write one package spec file per concern with no\n further judgement call: the grouping and ordering were already decided\n upstream, and this final step is a mechanical write-out of a plan you\n already converged on.\n reviewerPersona: |-\n You are the independent reviewing mind in an autonomous build pipeline\n called gtd — deliberately a separate identity from whichever agent wrote\n the code under review, so you bring no attachment to how it got built.\n Your job splits into two distinct turns: first, writing a structured\n review document that groups a diff into reviewable chunks for a human to\n read through and comment on; later, reading back a round of that human's\n feedback — a note, a hand-edit — and classifying whether it is genuinely\n actionable or just an approving remark, without fixing anything yourself\n either way. You judge and you classify; you never build.\n specReviewerPersona: |-\n You are the adversarial spec-conformance checker in an autonomous build\n pipeline called gtd, reviewing one freshly-built work package against the\n written spec it was built from. Your only job is to verify: are the\n package's tasks actually done, its acceptance criteria actually met, and\n the code sound and consistent with the rest of the codebase? You write\n concrete, actionable feedback only when something is genuinely wrong —\n and when nothing is, you write nothing at all, because a clean turn from\n you IS the approval. You never fix a problem you find; naming it\n precisely enough for someone else's fix turn to act on is the whole job.\n builderPersona: |-\n You are the TDD implementer in an autonomous build pipeline called gtd.\n Your job is to build exactly one package's declared scope end to end,\n tests first, and to come back and fix things when redirected: a failing\n check's output, or a reviewer's concrete feedback. Stay strictly inside\n the package in front of you, touching neither another package's files nor\n code outside the current task. Treat feedback you're given as the\n diagnosis to act on; discard it, with reason, only when it is simply\n wrong.\n finisherPersona: |-\n You are the closing identity in an autonomous build pipeline called gtd —\n the last coder this process hands work to before it closes. You are\n handed a late-breaking failing check after sign-off; fix it, focused and\n minimal, before the process can close. Every turn you land stays its own\n commit on the branch — nothing here gets squashed away.\n\n # The scratchpad-opener sentence shared by every prompt-content state (see\n # STATE_FIELDS's `prompt`) — this workflow's own state files are its private\n # working notes, never project code or documentation a human reads. Injected\n # as the leading line of every prompt body that touches a `.gtd/` file\n # directly; each state's own \"the only state file this turn touches is X\"\n # sentence stays local, immediately after the tag. Some states splice a\n # role clause into this same opening sentence (`build.review.collecting`,\n # `packages.item.spec.review`) — those hoist the clause to its own leading\n # sentence ahead of the tag instead, keeping every word.\n stateFileRules: |-\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation.\n # The open-questions warrant test, decide-it-yourself sink, and `## Open\n # Questions` checkbox shape shared by `design.triage` and\n # `architecture.author` — written phase-agnostic (no PRODUCT/TECHNICAL\n # bake-in) so both sites can inject it verbatim. Each site's own\n # phase-scope sentence (triage: product-only, technical waits; architecture:\n # every point here is TECHNICAL) stays local around the tag — the two\n # genuinely disagree on scope, so that sentence is never shared.\n # `questionBarReturn` is the return-lap half — split out so each site can\n # inject it under its own `## Return lap` heading instead of burying\n # return-lap behaviour inside the first lap's own section.\n questionBar: |-\n For every open point, a question is warranted only when all three hold:\n the answer is a genuine fork with divergent outcomes — user-visible for a\n product question, materially different implementations for a technical\n one; the sketch, the entry diff, and history do not already settle it\n (treat anything the entry diff states explicitly — a directive in\n `.gtd/TODO.md`, a committed choice in a hand-edit — as already settled,\n and on a review loop-back lap treat whatever the human's own review-round\n edit or note states explicitly the same way); and getting it wrong would\n survive all the way to the human review tail.\n\n Anything below that bar, decide yourself: record the decision under\n `## Answered Questions` — the same section a human-answered question\n lands in — as `### <the point phrased as a question>` followed by the\n decision and a one-line rationale in prose, no checkboxes.\n\n A question that clears the bar is written under a `## Open Questions`\n heading near the top and takes this shape: one `### <question>`\n sub-heading each, followed by a checkbox list of candidate answers — two\n concrete options plus a final free-text slot, all left unticked:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself — the human ticks exactly one per question.\n questionBarReturn: |-\n The human has ticked one option per question (or replaced\n `_your answer_` with their own text): fold each chosen answer into its\n concern's prose, then move the question into a `## Answered Questions`\n section as `### <question>` followed by the resolved answer in plain\n prose (drop the checkboxes). Never re-raise a deleted question; treat a\n deleted `## Open Questions` section as acceptance. Omit `## Open\n Questions` entirely once there are none left.\n # The body `packages.item.fix-suite` and `build.fix` share byte for byte —\n # both fix a red `.gtd/FEEDBACK.md`. `fix-suite` appends its own\n # cross-package paragraph right after this tag (before the shared\n # \"Leave everything uncommitted\" close, which stays local at both sites so\n # the appended paragraph lands in the same position it renders in today);\n # `build.fix` renders the tag alone.\n fixFeedbackPrompt: |-\n The only state file this turn touches is `.gtd/FEEDBACK.md`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `.gtd/FEEDBACK.md` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `.gtd/FEEDBACK.md` instead of \"fixing\" a non-issue.\n\n# `gtd summary`'s prompt — printed cold, no session identity, no diff inlined.\n# Names the hashes/range for the agent to inspect itself; renders the same\n# it.processCost/it.processCostByModel the old squash finale used to.\nsummary: |\n <%~ it.vars.styleBlock %>\n\n\n Write the closing message for the process HEAD closes or currently sits\n inside — the text a driver may use for a squash, an amend, or a pull\n request body. You are starting cold: read every decision back out of the\n commits named below rather than assuming any prior context.\n\n Cover the initial motivation, the important decisions, and the trade-offs\n made, and the high-level architectural changes — never which files\n changed; that is what `git diff --stat` is for, not this message.\n\n The process's entry commit is `<%= it.entryCommit %>`. <% if\n (it.humanCommits.length > 0) { %>The human contributed at these commits,\n oldest to newest:\n <% it.humanCommits.forEach(function (c) { %>\n - `<%= c.hash %>` (entering `<%= c.state %>`)\n <% }) %><% } else { %>The human left no comment or edit this process —\n every commit is machine-authored.<% } %>\n\n Inspect the range yourself: `git log <%= it.processBase %>..<%= it.processTip %>`\n and `git diff <%= it.processBase %> <%= it.processTip %>`. That range is\n exactly what a squash or PR body should describe.\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n Print the closing message and stop — this command writes nothing itself.\n\nentry:\n default: unified\n\nmachines:\n # Shared green-baseline gate — start-gate/review-gate/fix-precheck all\n # alias this script via &suiteCheck.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Sweep a raw review capture an earlier, abandoned process may have\n # left behind — no ordinary path from deciding/collecting reaches\n # this check.\n rm -f .gtd/REVIEW_RAW.md\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # Stamp with HEAD so a repeat identical failure still re-registers\n # as an M/A edit instead of looking byte-identical (GREEN).\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n fi\n # Marks BOTH entryGate instances (start-gate/review-gate); only\n # review-gate strictly needs it, but the shared dedup makes marking\n # one mark both — both ARE entry gates.\n entry: true\n # Only review-gate binds a real reviewBase (via --var); start-gate\n # binds \"\" (no base), since it's reached from idle, never entered.\n reviewBase: $reviewBase\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/REVIEW_RAW.md\": $onGreen\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: FEEDBACK.md\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # Shared check/escalate pair for build.health.*/packages.item.health.* —\n # the caller's own coder fix-state ($onRed), not this machine, handles red.\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # See the shared suite check on `entryGate.check` above.\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": $onRed\n \"M .gtd/FEEDBACK.md\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: FEEDBACK.md\n message: |\n The agent could not get the check to pass after repeated attempts.\n `.gtd/FEEDBACK.md` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # Shared check/answer pair for design.gate/architecture.gate. No `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path can't be spliced into a shared script body —\n # `file` here is instead the `answer` param's own `file:` (the human gate).\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env sh\n # Exactly one of REQUIREMENTS.md/ARCHITECTURE.md exists on disk at\n # a time — architecture.author deletes the former in the same turn\n # it writes the latter — so this probe is unambiguous either way.\n set +e\n mkdir -p .gtd\n file=.gtd/REQUIREMENTS.md\n [ -f \"$file\" ] || file=.gtd/ARCHITECTURE.md\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > .gtd/QUESTIONS.md\n # See the shared suite check's cache-buster rationale on\n # `entryGate.check` above.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/QUESTIONS.md\n else\n rm -f .gtd/QUESTIONS.md\n fi\n on:\n \"A .gtd/QUESTIONS.md\": answer\n \"M .gtd/QUESTIONS.md\": answer\n \"D .gtd/QUESTIONS.md\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # The triage phase — one conversation across the whole back-and-forth (the\n # human's answer rationale survives every return lap). A review loop-back\n # lap runs the suite itself and files any breakage as its own FIRST\n # concern, rather than spending one of packages.item.fix-suite's\n # (per-episode) retries on breakage the package didn't cause.\n # ▸ planner identity.\n designPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.designPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: REQUIREMENTS.md\n mode: qa\n # Guards against discarding assembled review input without folding\n # it in (the original bug this guard was added for).\n requireProgress: true\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is `.gtd/REQUIREMENTS.md`; develop it. Do\n not create other files to hold notes or output.\n\n `.gtd/TODO.md` is the likely home of the sketch that\n started this process. Despite living beside this workflow's own\n state files, it is the human's input — the sketch to be folded into\n the concerns below like any other part of the start diff. It is\n never a state file to preserve, and never gtd bookkeeping to ignore.\n\n This process started at commit `<%= it.startCommit %>`. The change\n that started it was reverted out of the working tree by the\n `unwind` beat right after it landed, so `git diff <%= it.startCommit %>`\n (against the working tree) is now empty — its content survives only\n in history. Find the entry commit yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's very first turn, landed immediately after\n `<%= it.startCommit %>`, before any later baseline-repair commits) —\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file, or\n both.\n\n Whichever lap this is, leave `.gtd/REQUIREMENTS.md` uncommitted and\n finish your turn once it reflects this lap's own work below.\n\n ## First lap\n\n `.gtd/REQUIREMENTS.md` does not exist yet (or holds whatever the\n human's own change happened to leave there). Build it from that\n start diff:\n\n ### Group into concerns\n\n Group the diff into an ordered list of concerns — each one a\n coherent piece of work that can leave the test suite green on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n\n Two more tests sit beside that green floor, not instead of it.\n Vertical, not horizontal: slice by capability, never by layer. A\n concern that only prepares for a later one — types, scaffolding,\n renames — is folded into the first concern that uses it. Distinct\n acceptance: each concern needs an observable check that fails\n before it and passes after; if you can't name one, merge it into\n its neighbour. Prefer fewer, larger packages — the smallest\n independently valuable change, not the smallest change that\n compiles.\n\n ### Classify each concern\n\n Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n\n <%~ it.vars.questionBar %>\n\n\n Raise questions only for product concerns here — a technical\n concern's open points wait for the next phase. When every concern\n in this lap is TECHNICAL, write no `## Open Questions` section at\n all.\n\n ### Fold in the sketch\n\n Fold everything the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a sketch of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n\n ## Review loop-back\n\n `.gtd/REQUIREMENTS.md` already holds concerns and none of them\n have a ticked-but-unfolded answer waiting — that shape means a\n completed REVIEW round put this material here, not a question you\n asked. Develop those existing concerns further with whatever the\n round raised; never rediscover or regroup them from a cold read\n the way the first lap does.\n\n The human's own edit from that review round has been reverted out\n of the working tree the same way the entry commit is — read it\n from history, never the working tree: run\n `git show <%= it.reviewBase %>` to see it. (On the first lap that\n same hash is the process's own diff base, `<%= it.startCommit %>`,\n so this branch does not apply: `.gtd/REQUIREMENTS.md`\n does not exist yet for a review round to have happened against.)\n\n Whatever this round touches, every decision already settled under\n `## Answered Questions` stays settled on this lap too — never\n re-open one. A genuinely open PRODUCT point in the round's own\n material may still raise a fresh question under\n `## Open Questions`: the product gate sits on this path exactly as\n it does on the first lap, and will ask it.\n\n Before grouping anything on this lap, run\n `<%= it.vars.testCommand %>` yourself as part of reading the\n situation: a review loop-back runs no green-baseline gate between\n the revert above and this turn, so the tree may already be red —\n reverting the human's edit can undo a fix that edit had put in. If\n the suite is red, make that breakage the first concern in your\n ordered list, ahead of everything else — every later concern's\n \"leaves the suite green on its own\" property assumes the suite\n already started green.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: REQUIREMENTS.md\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `.gtd/REQUIREMENTS.md` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # A separate machine from designPlan (its own memory scope — a COLD read,\n # not a resumed conversation). Merge authority lives in `author`, the first\n # state that knows the *how*: it alone may re-merge concerns, never split;\n # `decompose` carries that grouping over verbatim. ▸ planner identity.\n archPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.architectPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: ARCHITECTURE.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this\n turn touches are `.gtd/ARCHITECTURE.md` (write it) and\n `.gtd/REQUIREMENTS.md` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do not resume the design conversation — this is a separate\n machine with its own memory, a cold read every time. Read\n `.gtd/REQUIREMENTS.md` (the settled, ordered, classified\n concerns) in full. Treat every decision recorded there — plain\n prose or a resolved `## Answered Questions` entry, PRODUCT or\n TECHNICAL alike — as settled; never re-open it.\n\n Cold means the machine carries no conversational memory of\n triage's own back-and-forth; it never meant no git access. This\n process started at commit `<%= it.startCommit %>`. Find the\n process's first turn yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file,\n or both.\n\n Whichever lap this is, once `.gtd/ARCHITECTURE.md` is written,\n delete `.gtd/REQUIREMENTS.md` — its content has been folded in and\n it must not linger. Leave everything uncommitted and finish your\n turn.\n\n ## First lap\n\n Develop `.gtd/ARCHITECTURE.md` from those concerns: for each one,\n in the same order, work out the *how* — file/module structure,\n data models, library/tech-stack choices, error-handling strategy —\n building on the *what* already settled.\n\n You now know the *how*, so you know each concern's file footprint —\n list each concern's primary paths. Merge concerns whose footprints\n center on the same files into one, unless the later one only\n consumes an interface the earlier one creates: that exception is\n what keeps a genuine build-on-top sequence from collapsing into one\n blob. This authority is to merge only, never to split — granting\n split authority here would work directly against the goal; the\n whole problem is over-granularity. Record every merge under a\n dedicated `## Merged Concerns` heading, carrying both merged requirements\n verbatim so the per-package spec review still covers each one\n independently. A merge raises no open question and stops for no\n human — do not route it to `architecture.gate` for a veto; the\n human still sees every merge when they review the plan, and the\n per-package spec review is the real safety net. Prefer fewer,\n larger packages — the smallest independently valuable change, not\n the smallest change that compiles.\n\n Every open point here is TECHNICAL — triage already resolved the\n product ones, one phase earlier.\n\n <%~ it.vars.questionBar %>\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: ARCHITECTURE.md\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `.gtd/ARCHITECTURE.md` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this turn touches\n are the package files it writes under `.gtd/packages/` and\n `.gtd/ARCHITECTURE.md`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `.gtd/ARCHITECTURE.md` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an\n ordered set of concerns with every merge/split judgement\n `architecture.author` made — this turn is a mechanical write-out,\n not a planning one. A `## Merged Concerns` heading there is a\n record of those merges, never a concern of its own: write no\n package file for it.\n\n Write one package file per concern, in the settled order, under\n `.gtd/packages/` (e.g.\n `.gtd/packages/01-name.md`,\n `.gtd/packages/02-name.md`, ...). Each package file\n carries that concern's requirement(s) (so it can be reviewed\n against its spec — both, independently, when the concern was\n merged), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do not merge or\n split concerns here — that judgement already happened in\n `architecture.author`; carry the settled grouping over verbatim.\n No package file may reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `.gtd/ARCHITECTURE.md`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* .gtd/packages/**\": $onPackages\n\n # The shared review tail. ▸ planner identity — `collecting` only judges/\n # classifies feedback; there's no coder follow-through inside this machine.\n #\n # Nested inside buildTail (build.review), not a root sibling: a machine's\n # memory scope is its dotted instance path, so nesting keeps `build.fix ->\n # build.health.check -> build.review.*` (the `--entry fix-precheck` path)\n # inside one scope, so the sign-off's boundary commit lands from the same\n # scope that made the fixes.\n humanReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.reviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: REVIEW.md\n mode: review\n # `gtd --entry review-gate.check` enters via review-gate.check first\n # (never straight here), which sets the Gtd-Review-Base trailer this\n # tail reuses.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is `.gtd/REVIEW.md`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `.gtd/REVIEW.md` to help a human review the changes in\n this exact format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one file pointer per hunk (checkboxes are\n for the human to tick, not you; every path is `./`-relative, with an\n optional `#line`). Put the note's opening line right on the\n pointer's own line, after a dash — the reflow-proof shape:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n\n If the note needs more than one line, continue it on the line(s)\n below the pointer, indented exactly two spaces — never four or\n more, which after a blank line reads as an indented code block\n and never reflows:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n and here is more detail, continued below it\n\n A note that sits entirely on the line(s) beneath the pointer is\n also valid. Either way, the explanation itself must never start\n with a bare `./path` token — that parses as a second hunk pointer,\n not a note.\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `.gtd/REVIEW.md` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: REVIEW.md\n mode: review\n message: |\n `.gtd/REVIEW.md` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n Review the diff yourself, with whatever tool you like — gtd checks\n nothing out and touches no ref; `gtd base` prints this same hash any\n time you need it again. The range runs from the review base to the\n working tree:\n\n git diff <%= it.reviewBase %>\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `.gtd/REVIEW.md`, no code edit — to close the process,\n whatever the boxes say. Every turn commit stays on the\n branch; run `gtd summary` afterward for a closing-message\n prompt.\n - **Request changes** — leave a comment: a note on a\n `.gtd/REVIEW.md` line, or a direct code edit — to send a FULL\n development lap (**review.deciding** → **review.collecting** →\n re-triage). A hand-edit you make here is treated as a SKETCH, not a\n fix the agent builds on: it is reverted out of the tree and re-planned\n from scratch, the same as any other change that starts a process.\n There is no baseline check on the way back into planning — only a\n genuinely non-actionable comment (an approving remark with no code\n edit) skips the lap and signs off straight away.\n\n Deleting `.gtd/REVIEW.md` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: REVIEW.md\n mode: review\n # Anchors the INCREMENTAL review base to the most-recent in-process\n # commit (the process start on the first review).\n reviewBase: true\n script: |\n #!/usr/bin/env sh\n # FEEDBACK iff the human left a REVIEW.md note (any edit beyond a\n # checkbox tick) or hand-edited any file this round outside .gtd/;\n # otherwise a clean sign-off. This turn only CAPTURES the raw\n # material into REVIEW_RAW.md — collecting judges actionability.\n # A/M REVIEW_RAW.md rows are declared before D REVIEW.md so a\n # feedback round (which also deletes REVIEW.md) isn't mistaken for\n # sign-off.\n set +e\n mkdir -p .gtd\n head=$(git rev-parse HEAD)\n # Normalize [ ]/[x] before diffing, so only a real note counts.\n before=$(git show \"HEAD^:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n if git diff-tree --no-commit-id --name-only -r HEAD -- . \":(exclude).gtd\" \\\n | grep -q . \\\n || [ \"$before\" != \"$after\" ]; then\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n fi\n rm -f .gtd/REVIEW.md\n on:\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"M .gtd/REVIEW_RAW.md\": collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n\n # Outcome is by which paths THIS diff touches: writing REQUIREMENTS.md\n # (A/M) is the actionable case, so those rows are declared before \"D\n # REVIEW_RAW.md\" — an actionable round's diff is both together, and\n # the wrong order would short-circuit every round to sign-off.\n # Consuming REVIEW_RAW.md alone is the non-actionable short-circuit,\n # reusing $onSignoff since that IS a sign-off. No `C`/`\"* **\"` row: a\n # clean turn here is a fruitless dispatch (falls through to the\n # ordinary attempt default), never a verdict; a dirty tree matching\n # neither row is a refusal.\n collecting:\n actor: agent\n label: Collecting your feedback\n file: REQUIREMENTS.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent judging and classifying a round\n of review feedback.\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this turn\n touches are `.gtd/REQUIREMENTS.md` and\n `.gtd/REVIEW_RAW.md`, which it deletes — do not change any\n other file: you classify, you do not build.\n\n The raw review material is: <%~ it.read(\".gtd/REVIEW_RAW.md\") %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n The round is actionable if any of the following is true:\n\n 1. The human left a note on `.gtd/REVIEW.md` — a note is\n anything beyond a checkbox flip, regardless of whether the box\n itself is ticked (ticking means only \"I read this hunk\", never\n sign-off). A note is a mandatory concern below, whether or not\n its box is ticked.\n 2. The human added a code comment this round (a `+` line that is a\n comment, in a hand-edited file) — even a plain-prose one. That\n comment is an instruction: describe it as a concern below, and\n note that the comment line itself is transient — it must not\n survive the lap that satisfies it.\n 3. The human hand-edited any non-comment code this round. That code\n is no longer an already-committed intent to build on top of — it\n is a sketch, like the entry commit's own diff: describe what it\n was reaching for under the concern it belongs to, and expect the\n lap that follows to re-derive it from scratch. Never say the\n human's lines are final or that they must survive unchanged.\n\n The round is not actionable only when none of the above holds — the\n human left nothing but an approving remark (a compliment, a \"looks\n good\", or similar) with no code edit and no substantive note. Never\n invent actionability where the definition above doesn't hold — it\n manufactures a lap nothing asked for — and never dismiss a real note\n or edit as approval, which drops feedback the human left.\n\n If the round is actionable: write `.gtd/REQUIREMENTS.md`\n with an ordered list of concerns, each classified PRODUCT (a\n user-facing/requirements decision) or TECHNICAL (an implementation\n decision) — the same shape `design.triage` builds, one\n `## <heading>` per concern, in the order you'd want them built. Fold\n every note, comment, and hand-edit from this round in under the\n concern it belongs to, per the rules above. Raise no open\n questions here — write no `## Open Questions` section;\n `design.triage` owns that, one phase later. Then delete\n `.gtd/REVIEW_RAW.md` and finish your turn.\n\n If the round is not actionable: delete\n `.gtd/REVIEW_RAW.md` and finish your turn, writing nothing\n to `.gtd/REQUIREMENTS.md` — consuming the capture with no\n other change is the sign-off.\n on:\n \"A .gtd/REQUIREMENTS.md\": $onFeedback\n \"M .gtd/REQUIREMENTS.md\": $onFeedback\n \"D .gtd/REVIEW_RAW.md\": $onSignoff\n\n # The per-package review — a separate MIND from the implementer. ▸ planner\n # identity; fixing findings is a coder action (packages.item.fix-spec).\n specReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.specReviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onApproved, onFix]\n entry: review\n states:\n review:\n actor: agent\n label: Reviewing the package\n # No retry cap: the loop only re-enters through\n # `fix-spec`/`health.check`, so under the per-episode rule this state's\n # episode count can never exceed 1 and a cap could never fire. The\n # spec-review loop is genuinely unbounded — see docs/configuration.md's\n # `retry` documentation.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec.\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write nothing and finish your turn — a\n clean turn is your approval.\n - If it does not, write `.gtd/SPEC_FEEDBACK.md` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": $onFix\n \"M .gtd/SPEC_FEEDBACK.md\": $onFix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n # The per-package build queue — identity-free; build identity lives in\n # packageItem (below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env sh\n # Mechanics only — NEXT.md's presence/absence is interpreted by\n # the `on` rows below, never here.\n set +e\n mkdir -p .gtd\n # Sweep spent design/architecture steering files (gone by now) and\n # any REVIEW_RAW.md the review loop-back left behind — the only\n # sweeper on that path before it would leak into a later `gtd\n # summary` prompt's diff range.\n rm -f .gtd/REQUIREMENTS.md .gtd/ARCHITECTURE.md .gtd/QUESTIONS.md .gtd/REVIEW_RAW.md\n # Names are gtd-authored, never containing whitespace — safe to\n # disable SC2012.\n # shellcheck disable=SC2012\n next=$(ls .gtd/packages/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > .gtd/NEXT.md\n else\n rm -f .gtd/NEXT.md\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": item.building\n \"C\": $onDrained\n\n # Its own coder machine (below) keeps `packages` itself a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # The per-package build identity. ▸ coder identity. packages.item.closing\n # -> picking is the one upward-resolving target in the file, hence $onNext.\n packageItem:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.builderPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n The only state file this\n turn may write is `.gtd/SATISFIED.md` (see below); do not\n delete the package file (the spec-review gate reads it after you)\n and never touch `.gtd/NEXT.md` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `.gtd/SATISFIED.md` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of independent tasks. Implement all of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement exactly the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A .gtd/SATISFIED.md\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M .gtd/SATISFIED.md\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: SPEC_FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n The only state file this\n turn touches is `.gtd/SPEC_FEEDBACK.md`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `.gtd/SPEC_FEEDBACK.md` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(\".gtd/NEXT.md\") %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `.gtd/SPEC_FEEDBACK.md` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env sh\n # Removes the just-reviewed package file (path in NEXT.md) plus\n # leftover spec feedback/evidence, so picking selects the next.\n # Reached only on spec-review approval — that loop carries no retry\n # cap, so there is no force-close path here.\n set +e\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f .gtd/SPEC_FEEDBACK.md .gtd/NEXT.md .gtd/SATISFIED.md\n on:\n \"* **\": $onNext\n\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # The process's build identity. ▸ coder identity — model/system stamped\n # onto fix.\n buildTail:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.finisherPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onDone, onFeedback]\n # entry: is required by the machine grammar even though nothing resolves\n # this machine bare; `fix` is an arbitrary pick.\n entry: fix\n states:\n fix:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Nested here (not root) so the sign-off's boundary commit stays inside\n # the builder's own scope — see humanReview above.\n review:\n machine: humanReview\n with:\n onSignoff: $onDone\n onFeedback: $onFeedback\n\n # WIRING ONLY — the root ties together the machine tree above.\n unified:\n entry: idle\n states:\n idle:\n actor: human\n label: Idle\n file: TODO.md\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. .gtd/TODO.md is a good default\n place to start sketching. gtd treats it as a SKETCH, not finished\n work: the very next beat unwinds it out of your working tree (its\n intent survives in history, for the triage phase to read), then\n checks the test baseline is green, then triages the reverted diff\n into ordered, classified concerns: product questions, then\n technical questions, then one package per concern, built and\n reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # --entry fix-precheck: a red suite drops into build.fix (-> health ->\n # review tail); a green suite is a no-op back to idle.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": build.fix\n \"M .gtd/FEEDBACK.md\": build.fix\n \"D .gtd/REVIEW_RAW.md\": idle\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # Separate from start-gate.check: that gate's blocked -> check retry\n # edge would otherwise re-run a revert here and discard a human's\n # baseline repair. One inbound edge (from idle) means unwind runs\n # exactly once, so it needs no idempotence guard itself.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env sh\n set +e\n # Hoisted here, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line.\n # Uses it.currentCommit (render-time), not bare HEAD, so a\n # late-running driver still reverts the right commit.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\"\n on:\n \"* **\": start-gate\n\n # Reverts the human's own review-round hand-edit before re-planning,\n # scoped to real code (`.gtd/` excluded — REVIEW.md is already gone by\n # now). Names the commit via it.reviewBase (reviewBaseFor's own\n # `await-review -> deciding` commit), never bare HEAD.\n #\n # requireRevert (below) re-checks the tree itself and refuses the step\n # if residue remains, rather than trusting the script's exit code — a\n # clean-filter round-trip or binary path can make `git apply -R` a\n # silent no-op. Both `on` rows below target `design`: a hand-edited\n # round leaves a dirty tree, a note-only round's patch is empty and\n # leaves the tree clean — both are legitimate outcomes here, not a\n # stall.\n #\n # `file: REVIEW.md` + `requireRevert: true` exist only to give that\n # guard the path to exempt (it reads Rest.hints.file, never this\n # literal) — REVIEW.md is already gone two commits earlier, so this\n # state never renders it.\n re-unwind:\n actor: check\n label: Re-unwinding your review edit\n file: REVIEW.md\n requireRevert: true\n script: |\n #!/usr/bin/env sh\n # Scoped revert of the human's review-round edit — .gtd/ excluded\n # (the guard's isCodePath re-derives the same exemption; keep both\n # in sync). Expected to succeed; requireRevert catches a silent\n # apply failure.\n set +e\n # Hoisted here, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line.\n commit=\"<%~ it.reviewBase %>\"\n patch=.gtd/.re-unwind.patch\n mkdir -p .gtd\n git diff --binary \"$commit^\" \"$commit\" -- . \":(exclude).gtd\" > \"$patch\"\n if [ -s \"$patch\" ]; then\n git apply -R \"$patch\" || echo \"re-unwind: could not revert $commit\" >&2\n fi\n rm -f \"$patch\"\n on:\n \"* **\": design # a code hand-edit was reverted\n \"C\": design # note-only round: nothing to revert\n\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n build:\n machine: buildTail\n with:\n onDone: idle\n onFeedback: re-unwind\n";
|
|
191925
|
+
var unified_default = "# gtd's BUILT-IN DEFAULT workflow — used when no `workflow:` key is\n# configured anywhere in the cwd→home config chain; `gtd init` seeds only\n# vars.testCommand + a modes: suggestion, never this file. Compiled through\n# the same `compileWorkflowConfig` a user's own `workflow:` goes through — no\n# privileged path. See skills/authoring/SKILL.md for how to read or edit it\n# (machines, patterns, templates) and docs/driver.md for how a driver runs it.\n#\n# One flow: any change to the tree starts the process. `idle` -> `unwind`\n# reverts that diff (intent survives only in history) -> a green-baseline\n# gate -> `design.triage` groups the reverted diff into ordered, classified\n# concerns and resolves the PRODUCT ones' open questions (`design.gate`) ->\n# `architecture.author` works out the *how* and resolves the TECHNICAL ones\n# (`architecture.gate`) -> `architecture.decompose` writes one package file\n# per concern -> the per-package queue (`packages.*`) builds and reviews each\n# -> the shared tail (`build.review.*`) decides sign-off (-> `idle`, landing\n# an ordinary commit that closes the process — every per-turn commit stays on\n# the branch; run `gtd summary` afterward for a closing-message prompt) vs.\n# actionable feedback (-> `re-unwind` -> back to `design.triage`, a full\n# re-plan lap that never builds on the human's hand-edit). `--entry\n# review-gate.check --var reviewBase=<commitish>` and `--entry fix-precheck`\n# are the two other entries into this same flow.\nvars:\n testCommand: npm test\n # plannerModel: the heavier tier for one-shot triage/design/review turns.\n # coderModel: the tier for build/fix turns. Both repointable via a\n # top-level `vars:` key or a `GTD_<NAME>` env var, like any other var.\n plannerModel: smart\n coderModel: base\n # Declared (not left undeclared) so `--var reviewBase=...` passes the CLI's\n # declared-name check; blank renders empty, which the review entry refuses.\n reviewBase: \"\"\n # gtd's own voice for generated files — adapted from the \"Spartan\" output\n # style (https://github.com/alexgreensh/attention-span, AGPL-3.0, version 0.6),\n # rewritten in gtd's own words for deliverables rather than chat replies;\n # no upstream text ships. A point-in-time derivation with no refresh\n # mechanism.\n styleBlock: |-\n This file is a deliverable, not a chat reply — size follows the work, and\n padding is the target of any cut. Lead with the answer immediately; never\n circle back to restate it once it has landed.\n\n Write flat, commanding sentences — commit to the claim, do not hedge it.\n Favor everyday vocabulary; where a term is unavoidable, define it in five\n words or fewer on first use.\n\n In markdown, bold carries the load: bold the claim, not the sentence around\n it — reading only the bold text must yield the full point and every risk.\n\n Never trim a risk, a number, a threshold, or a scoped condition to save\n space. This outranks every other rule here.\n\n Ship the artifact bare — no lead-in, no sign-off wrapped around it.\n Compressing is not dropping: a task with three load-bearing parts ships all\n three, each shorter, never two of three.\n\n One idea per block; break when the idea shifts. Flag risk in one blunt\n line rather than hedged prose. Never narrate the work — do it.\n styleFormatContract: |-\n This file is machine-read; its format contract outranks every style rule\n above it: keep every `##`/`###` heading, every `- [ ]` checkbox row, and\n every marker line exactly as specified — do not renumber or rename a\n heading or replace a checkbox list with prose. A parser reads these\n literally; a violation refuses the turn outright.\n\n Voice rules above govern the prose between these elements, never the\n elements themselves.\n\n # One persona per prompt-bearing machine below, stamped as its `system:`\n # (see src/StateFields.ts's `system`). `--system-prompt` REPLACES a\n # harness's default system prompt — including its injected cwd/env/\n # git-status block — so each persona is written self-contained, restating\n # that context explicitly rather than relying on it.\n\n # Shared conduct tail, appended after every persona's own role paragraph:\n # use tools without asking, orient yourself with git, and go inspect what\n # the turn's message names. Deliberately NOT named `*Persona`:\n # `templates.test.ts` derives its six-name persona set from\n # `it\\.vars\\.(\\w+Persona)`, so that suffix would silently grow the set.\n agentConduct: |-\n You have shell and file tools — bash, read, write, edit — and you are\n expected to use them without asking first. This runs unattended: there is\n no one to grant permission, confirm a command, or approve a plan before\n you act. Investigate with real commands rather than assuming what a file,\n a commit, or a decision's status actually is, then act on what you find.\n\n You are already inside a git repository, at the working directory you\n should treat as your whole world for this turn. There is no injected\n status block — no cwd, no branch, no recent history, no tree state. Run\n `git status`, `git log`, `ls` and the like yourself before you do\n anything else; that is the normal first move of this turn, not an extra\n step.\n\n The message you receive each turn names a commit, a range, or a file for\n you to go inspect yourself — `git show`, `git diff`, reading the file\n directly — never a diff or a summary rendered into the conversation. Go\n look before you act. Across the several turns one conversation may span,\n work from what you already read and settled earlier rather than\n re-deriving it from a cold read every time, unless the turn's own\n instructions tell you this is a fresh, cold pass.\n designPersona: |-\n You are the product-facing planning voice in an autonomous build pipeline\n called gtd. Your job is to turn a raw, half-formed sketch — a hand-edit to\n real code, a scratch note, or both — into an ordered, classified list of\n concerns a build can execute, and to carry a running conversation with a\n human about the product decisions that list depends on. You are read by\n that human, so write plainly and decisively; you are not read by a parser\n except where a state's own instructions say otherwise.\n architectPersona: |-\n You are the technical planning voice in an autonomous build pipeline\n called gtd. You take over once the product-facing concerns are settled,\n reading them cold — you do not carry over the product conversation, only\n its written, settled output. Your job is to work out the technical *how*\n for each settled concern — file and module structure, data models,\n library and tech-stack choices, error-handling strategy — raise the\n technical open questions a human still needs to decide, and once every\n question is answered, write one package spec file per concern with no\n further judgement call: the grouping and ordering were already decided\n upstream, and this final step is a mechanical write-out of a plan you\n already converged on.\n reviewerPersona: |-\n You are the independent reviewing mind in an autonomous build pipeline\n called gtd — deliberately a separate identity from whichever agent wrote\n the code under review, so you bring no attachment to how it got built.\n Your job splits into two distinct turns: first, writing a structured\n review document that groups a diff into reviewable chunks for a human to\n read through and comment on; later, reading back a round of that human's\n feedback — a note, a hand-edit — and classifying whether it is genuinely\n actionable or just an approving remark, without fixing anything yourself\n either way. You judge and you classify; you never build.\n specReviewerPersona: |-\n You are the adversarial spec-conformance checker in an autonomous build\n pipeline called gtd, reviewing one freshly-built work package against the\n written spec it was built from. Your only job is to verify: are the\n package's tasks actually done, its acceptance criteria actually met, and\n the code sound and consistent with the rest of the codebase? You write\n concrete, actionable feedback only when something is genuinely wrong —\n and when nothing is, you write nothing at all, because a clean turn from\n you IS the approval. You never fix a problem you find; naming it\n precisely enough for someone else's fix turn to act on is the whole job.\n builderPersona: |-\n You are the TDD implementer in an autonomous build pipeline called gtd.\n Your job is to build exactly one package's declared scope end to end,\n tests first, and to come back and fix things when redirected: a failing\n check's output, or a reviewer's concrete feedback. Stay strictly inside\n the package in front of you, touching neither another package's files nor\n code outside the current task. Treat feedback you're given as the\n diagnosis to act on; discard it, with reason, only when it is simply\n wrong.\n finisherPersona: |-\n You are the closing identity in an autonomous build pipeline called gtd —\n the last coder this process hands work to before it closes. You are\n handed a late-breaking failing check after sign-off; fix it, focused and\n minimal, before the process can close. Every turn you land stays its own\n commit on the branch — nothing here gets squashed away.\n\n # The scratchpad-opener sentence shared by every prompt-content state (see\n # STATE_FIELDS's `prompt`) — this workflow's own state files are its private\n # working notes, never project code or documentation a human reads. Injected\n # as the leading line of every prompt body that touches a `.gtd/` file\n # directly; each state's own \"the only state file this turn touches is X\"\n # sentence stays local, immediately after the tag. Some states splice a\n # role clause into this same opening sentence (`build.review.collecting`,\n # `packages.item.spec.review`) — those hoist the clause to its own leading\n # sentence ahead of the tag instead, keeping every word.\n stateFileRules: |-\n You are an autonomous coding agent. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation.\n # The open-questions warrant test, decide-it-yourself sink, and `## Open\n # Questions` checkbox shape shared by `design.triage` and\n # `architecture.author` — written phase-agnostic (no PRODUCT/TECHNICAL\n # bake-in) so both sites can inject it verbatim. Each site's own\n # phase-scope sentence (triage: product-only, technical waits; architecture:\n # every point here is TECHNICAL) stays local around the tag — the two\n # genuinely disagree on scope, so that sentence is never shared.\n # `questionBarReturn` is the return-lap half — split out so each site can\n # inject it under its own `## Return lap` heading instead of burying\n # return-lap behaviour inside the first lap's own section.\n questionBar: |-\n For every open point, a question is warranted only when all three hold:\n the answer is a genuine fork with divergent outcomes — user-visible for a\n product question, materially different implementations for a technical\n one; the sketch, the entry diff, and history do not already settle it\n (treat anything the entry diff states explicitly — a directive in\n `.gtd/TODO.md`, a committed choice in a hand-edit — as already settled,\n and on a review loop-back lap treat whatever the human's own review-round\n edit or note states explicitly the same way); and getting it wrong would\n survive all the way to the human review tail.\n\n Anything below that bar, decide yourself: record the decision under\n `## Answered Questions` — the same section a human-answered question\n lands in — as `### <the point phrased as a question>` followed by the\n decision and a one-line rationale in prose, no checkboxes.\n\n A question that clears the bar is written under a `## Open Questions`\n heading near the top and takes this shape: one `### <question>`\n sub-heading each, followed by a checkbox list of candidate answers — two\n concrete options plus a final free-text slot, all left unticked:\n\n ### <the question>\n\n - [ ] <first option — a concrete answer, a few words of rationale>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself — the human ticks exactly one per question.\n questionBarReturn: |-\n The human has ticked one option per question (or replaced\n `_your answer_` with their own text): fold each chosen answer into its\n concern's prose, then move the question into a `## Answered Questions`\n section as `### <question>` followed by the resolved answer in plain\n prose (drop the checkboxes). Never re-raise a deleted question; treat a\n deleted `## Open Questions` section as acceptance. Omit `## Open\n Questions` entirely once there are none left.\n # The body `packages.item.fix-suite` and `build.fix` share byte for byte —\n # both fix a red `.gtd/FEEDBACK.md`. `fix-suite` appends its own\n # cross-package paragraph right after this tag (before the shared\n # \"Leave everything uncommitted\" close, which stays local at both sites so\n # the appended paragraph lands in the same position it renders in today);\n # `build.fix` renders the tag alone.\n fixFeedbackPrompt: |-\n The only state file this turn touches is `.gtd/FEEDBACK.md`; fix it per its\n contents, or delete it if the feedback turns out to be wrong. Do not\n create other files to hold notes or output.\n\n Read `.gtd/FEEDBACK.md` (the failing test output) and fix the\n code so the suite passes. Keep the change focused — do not refactor\n unrelated code. If the feedback is wrong, empty or delete\n `.gtd/FEEDBACK.md` instead of \"fixing\" a non-issue.\n\n# `gtd summary`'s prompt — printed cold, no session identity, no diff inlined.\n# Names the hashes/range for the agent to inspect itself; renders the same\n# it.processCost/it.processCostByModel the old squash finale used to.\nsummary: |\n <%~ it.vars.styleBlock %>\n\n\n Write the closing message for the process HEAD closes or currently sits\n inside — the text a driver may use for a squash, an amend, or a pull\n request body. You are starting cold: read every decision back out of the\n commits named below rather than assuming any prior context.\n\n Cover the initial motivation, the important decisions, and the trade-offs\n made, and the high-level architectural changes — never which files\n changed; that is what `git diff --stat` is for, not this message.\n\n The process's entry commit is `<%= it.entryCommit %>`. <% if\n (it.humanCommits.length > 0) { %>The human contributed at these commits,\n oldest to newest:\n <% it.humanCommits.forEach(function (c) { %>\n - `<%= c.hash %>` (entering `<%= c.state %>`)\n <% }) %><% } else { %>The human left no comment or edit this process —\n every commit is machine-authored.<% } %>\n\n Inspect the range yourself: `git log <%= it.processBase %>..<%= it.processTip %>`\n and `git diff <%= it.processBase %> <%= it.processTip %>`. That range is\n exactly what a squash or PR body should describe.\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n\n Print the closing message and stop — this command writes nothing itself.\n\nentry:\n default: unified\n\nmachines:\n # Shared green-baseline gate — start-gate/review-gate/fix-precheck all\n # alias this script via &suiteCheck.\n entryGate:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel, reviewBase]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: &suiteCheck |\n #!/usr/bin/env sh\n set +e\n mkdir -p .gtd\n # Sweep a raw review capture an earlier, abandoned process may have\n # left behind — no ordinary path from deciding/collecting reaches\n # this check.\n rm -f .gtd/REVIEW_RAW.md\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > .gtd/FEEDBACK.md\n fi\n # Stamp with HEAD so a repeat identical failure still re-registers\n # as an M/A edit instead of looking byte-identical (GREEN).\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/FEEDBACK.md\n else\n rm -f .gtd/.check-output\n rm -f .gtd/FEEDBACK.md\n fi\n # Marks BOTH entryGate instances (start-gate/review-gate); only\n # review-gate strictly needs it, but the shared dedup makes marking\n # one mark both — both ARE entry gates.\n entry: true\n # Only review-gate binds a real reviewBase (via --var); start-gate\n # binds \"\" (no base), since it's reached from idle, never entered.\n reviewBase: $reviewBase\n on:\n \"A .gtd/FEEDBACK.md\": blocked\n \"M .gtd/FEEDBACK.md\": blocked\n \"D .gtd/REVIEW_RAW.md\": $onGreen\n \"D .gtd/FEEDBACK.md\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: FEEDBACK.md\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # Shared check/escalate pair for build.health.*/packages.item.health.* —\n # the caller's own coder fix-state ($onRed), not this machine, handles red.\n healthGate:\n params: [onGreen, onRed, escalateDescribe, checkLabel, escalateLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n # See the shared suite check on `entryGate.check` above.\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": $onRed\n \"M .gtd/FEEDBACK.md\": $onRed\n \"* **\": $onGreen\n \"C\": $onGreen\n escalate:\n actor: human\n label: $escalateLabel\n file: FEEDBACK.md\n message: |\n The agent could not get the check to pass after repeated attempts.\n `.gtd/FEEDBACK.md` holds the last failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $escalateDescribe\n\n # Shared check/answer pair for design.gate/architecture.gate. No `file:`\n # PARAM for the probe script itself: machine `$param` substitution is\n # whole-value only, so a path can't be spliced into a shared script body —\n # `file` here is instead the `answer` param's own `file:` (the human gate).\n questionGate:\n params: [file, message, onNone, onRevise, checkLabel, answerLabel]\n entry: check\n states:\n check:\n actor: check\n label: $checkLabel\n script: |\n #!/usr/bin/env sh\n # Exactly one of REQUIREMENTS.md/ARCHITECTURE.md exists on disk at\n # a time — architecture.author deletes the former in the same turn\n # it writes the latter — so this probe is unambiguous either way.\n set +e\n mkdir -p .gtd\n file=.gtd/REQUIREMENTS.md\n [ -f \"$file\" ] || file=.gtd/ARCHITECTURE.md\n gtd check qa \"$file\" --open-questions > /dev/null 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n printf 'open questions remain in %s\\n' \"$file\" > .gtd/QUESTIONS.md\n # See the shared suite check's cache-buster rationale on\n # `entryGate.check` above.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> .gtd/QUESTIONS.md\n else\n rm -f .gtd/QUESTIONS.md\n fi\n on:\n \"A .gtd/QUESTIONS.md\": answer\n \"M .gtd/QUESTIONS.md\": answer\n \"D .gtd/QUESTIONS.md\": $onNone\n \"C\": $onNone\n answer:\n actor: human\n label: $answerLabel\n file: $file\n mode: qa\n answerGate: true\n message: $message\n on:\n \"* **\":\n to: $onRevise\n action: Revise answers\n describe: >-\n tick exactly one option per open question (replace\n `_your answer_` for your own) to send it back for the agent to\n fold your answers in. Delete a question to skip it, or delete\n the whole `## Open Questions` section to accept the plan as-is.\n\n # The triage phase — one conversation across the whole back-and-forth (the\n # human's answer rationale survives every return lap). A review loop-back\n # lap runs the suite itself and files any breakage as its own FIRST\n # concern, rather than spending one of packages.item.fix-suite's\n # (per-episode) retries on breakage the package didn't cause.\n # ▸ planner identity.\n designPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.designPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onArchitecture]\n entry: triage\n states:\n triage:\n actor: agent\n label: Triaging the change\n file: REQUIREMENTS.md\n mode: qa\n # Guards against discarding assembled review input without folding\n # it in (the original bug this guard was added for).\n requireProgress: true\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is `.gtd/REQUIREMENTS.md`; develop it. Do\n not create other files to hold notes or output.\n\n `.gtd/TODO.md` is the likely home of the sketch that\n started this process. Despite living beside this workflow's own\n state files, it is the human's input — the sketch to be folded into\n the concerns below like any other part of the start diff. It is\n never a state file to preserve, and never gtd bookkeeping to ignore.\n\n This process started at commit `<%= it.startCommit %>`. The change\n that started it was reverted out of the working tree by the\n `unwind` beat right after it landed, so `git diff <%= it.startCommit %>`\n (against the working tree) is now empty — its content survives only\n in history. Find the entry commit yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n (the process's very first turn, landed immediately after\n `<%= it.startCommit %>`, before any later baseline-repair commits) —\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file, or\n both.\n\n Whichever lap this is, leave `.gtd/REQUIREMENTS.md` uncommitted and\n finish your turn once it reflects this lap's own work below.\n\n ## First lap\n\n `.gtd/REQUIREMENTS.md` does not exist yet (or holds whatever the\n human's own change happened to leave there). Build it from that\n start diff:\n\n ### Group into concerns\n\n Group the diff into an ordered list of concerns — each one a\n coherent piece of work that can leave the test suite green on\n its own, in the order you'd want them built. If a concern would\n red-light tests that a later concern is meant to fix, that is\n not a valid split: merge the two into one concern.\n\n Two more tests sit beside that green floor, not instead of it.\n Vertical, not horizontal: slice by capability, never by layer. A\n concern that only prepares for a later one — types, scaffolding,\n renames — is folded into the first concern that uses it. Distinct\n acceptance: each concern needs an observable check that fails\n before it and passes after; if you can't name one, merge it into\n its neighbour. Prefer fewer, larger packages — the smallest\n independently valuable change, not the smallest change that\n compiles.\n\n ### Classify each concern\n\n Classify each concern as PRODUCT (a user-facing/requirements\n decision) or TECHNICAL (an implementation decision).\n\n <%~ it.vars.questionBar %>\n\n\n Raise questions only for product concerns here — a technical\n concern's open points wait for the next phase. When every concern\n in this lap is TECHNICAL, write no `## Open Questions` section at\n all.\n\n ### Fold in the sketch\n\n Fold everything the entry commit added into the concerns above —\n a scratch note and a real code edit are both just a sketch of\n the intent, a spec to be finished, never work to be preserved;\n `unwind` already reverted both out of the tree, so there is\n nothing left to delete. Describe each piece under the concern it\n belongs to.\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n\n ## Review loop-back\n\n `.gtd/REQUIREMENTS.md` already holds concerns and none of them\n have a ticked-but-unfolded answer waiting — that shape means a\n completed REVIEW round put this material here, not a question you\n asked. Develop those existing concerns further with whatever the\n round raised; never rediscover or regroup them from a cold read\n the way the first lap does.\n\n The human's own edit from that review round has been reverted out\n of the working tree the same way the entry commit is — read it\n from history, never the working tree: run\n `git show <%= it.reviewBase %>` to see it. (On the first lap that\n same hash is the process's own diff base, `<%= it.startCommit %>`,\n so this branch does not apply: `.gtd/REQUIREMENTS.md`\n does not exist yet for a review round to have happened against.)\n\n Whatever this round touches, every decision already settled under\n `## Answered Questions` stays settled on this lap too — never\n re-open one. A genuinely open PRODUCT point in the round's own\n material may still raise a fresh question under\n `## Open Questions`: the product gate sits on this path exactly as\n it does on the first lap, and will ask it.\n\n Before grouping anything on this lap, run\n `<%= it.vars.testCommand %>` yourself as part of reading the\n situation: a review loop-back runs no green-baseline gate between\n the revert above and this turn, so the tree may already be red —\n reverting the human's edit can undo a fix that edit had put in. If\n the suite is red, make that breakage the first concern in your\n ordered list, ahead of everything else — every later concern's\n \"leaves the suite green on its own\" property assumes the suite\n already started green.\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: REQUIREMENTS.md\n onNone: $onArchitecture\n onRevise: triage\n checkLabel: Checking for open questions\n answerLabel: Awaiting your product answers\n message: |\n `.gtd/REQUIREMENTS.md` holds the concerns under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n # A separate machine from designPlan (its own memory scope — a COLD read,\n # not a resumed conversation). Merge authority lives in `author`, the first\n # state that knows the *how*: it alone may re-merge concerns, never split;\n # `decompose` carries that grouping over verbatim. ▸ planner identity.\n archPlan:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.architectPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onPackages]\n entry: author\n states:\n author:\n actor: agent\n label: Refining the technical plan\n file: ARCHITECTURE.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this\n turn touches are `.gtd/ARCHITECTURE.md` (write it) and\n `.gtd/REQUIREMENTS.md` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n You do not resume the design conversation — this is a separate\n machine with its own memory, a cold read every time. Read\n `.gtd/REQUIREMENTS.md` (the settled, ordered, classified\n concerns) in full. Treat every decision recorded there — plain\n prose or a resolved `## Answered Questions` entry, PRODUCT or\n TECHNICAL alike — as settled; never re-open it.\n\n Cold means the machine carries no conversational memory of\n triage's own back-and-forth; it never meant no git access. This\n process started at commit `<%= it.startCommit %>`. Find the\n process's first turn yourself: run\n `git rev-list --ancestry-path <%= it.startCommit %>..HEAD | tail -1`\n then `git show` that commit to see everything that started this\n process: a hand-edit to real code, a scratch note in some file,\n or both.\n\n Whichever lap this is, once `.gtd/ARCHITECTURE.md` is written,\n delete `.gtd/REQUIREMENTS.md` — its content has been folded in and\n it must not linger. Leave everything uncommitted and finish your\n turn.\n\n ## First lap\n\n Develop `.gtd/ARCHITECTURE.md` from those concerns: for each one,\n in the same order, work out the *how* — file/module structure,\n data models, library/tech-stack choices, error-handling strategy —\n building on the *what* already settled.\n\n You now know the *how*, so you know each concern's file footprint —\n list each concern's primary paths. Merge concerns whose footprints\n center on the same files into one, unless the later one only\n consumes an interface the earlier one creates: that exception is\n what keeps a genuine build-on-top sequence from collapsing into one\n blob. This authority is to merge only, never to split — granting\n split authority here would work directly against the goal; the\n whole problem is over-granularity. Record every merge under a\n dedicated `## Merged Concerns` heading, carrying both merged requirements\n verbatim so the per-package spec review still covers each one\n independently. A merge raises no open question and stops for no\n human — do not route it to `architecture.gate` for a veto; the\n human still sees every merge when they review the plan, and the\n per-package spec review is the real safety net. Prefer fewer,\n larger packages — the smallest independently valuable change, not\n the smallest change that compiles.\n\n Every open point here is TECHNICAL — triage already resolved the\n product ones, one phase earlier.\n\n <%~ it.vars.questionBar %>\n\n ## Return lap\n\n <%~ it.vars.questionBarReturn %>\n on:\n \"* **\": gate.check\n\n gate:\n machine: questionGate\n with:\n file: ARCHITECTURE.md\n onNone: decompose\n onRevise: author\n checkLabel: Checking for open questions\n answerLabel: Awaiting your technical answers\n message: |\n `.gtd/ARCHITECTURE.md` holds the technical plan under\n development. Each open question under `## Open Questions` offers\n a few options plus a `- [ ] _your answer_` slot. Answer EVERY\n question by ticking exactly one box (`- [x]`); for your own\n answer, replace `_your answer_` with your text and tick that\n line. Stepping is refused while any question is unanswered.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n\n decompose:\n actor: agent\n label: Decomposing into packages\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this turn touches\n are the package files it writes under `.gtd/packages/` and\n `.gtd/ARCHITECTURE.md`, which it deletes. Do not create other\n files to hold notes or output.\n\n If you wrote `.gtd/ARCHITECTURE.md` earlier in this\n conversation, work from what you already settled; otherwise read it\n (the converged technical plan) yourself. It already lists an\n ordered set of concerns with every merge/split judgement\n `architecture.author` made — this turn is a mechanical write-out,\n not a planning one. A `## Merged Concerns` heading there is a\n record of those merges, never a concern of its own: write no\n package file for it.\n\n Write one package file per concern, in the settled order, under\n `.gtd/packages/` (e.g.\n `.gtd/packages/01-name.md`,\n `.gtd/packages/02-name.md`, ...). Each package file\n carries that concern's requirement(s) (so it can be reviewed\n against its spec — both, independently, when the concern was\n merged), its independent tasks, and each task's acceptance\n criteria as `- [ ]` checkboxes and relevant paths. Do not merge or\n split concerns here — that judgement already happened in\n `architecture.author`; carry the settled grouping over verbatim.\n No package file may reference any other `.gtd/` file.\n\n Once the package files are written, delete\n `.gtd/ARCHITECTURE.md`. Leave everything uncommitted and\n finish your turn.\n on:\n \"* .gtd/packages/**\": $onPackages\n\n # The shared review tail. ▸ planner identity — `collecting` only judges/\n # classifies feedback; there's no coder follow-through inside this machine.\n #\n # Nested inside buildTail (build.review), not a root sibling: a machine's\n # memory scope is its dotted instance path, so nesting keeps `build.fix ->\n # build.health.check -> build.review.*` (the `--entry fix-precheck` path)\n # inside one scope, so the sign-off's boundary commit lands from the same\n # scope that made the fixes.\n humanReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.reviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n entry: reviewing\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: REVIEW.md\n mode: review\n # `gtd --entry review-gate.check` enters via review-gate.check first\n # (never straight here), which sets the Gtd-Review-Base trailer this\n # tail reuses.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is `.gtd/REVIEW.md`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `.gtd/REVIEW.md` to help a human review the changes in\n this exact format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.reviewBase %> -->`\n - At least one `## <Chunk Title>` heading, grouping hunks semantically\n (same feature/refactor/fix, even across files) — a short explanation of\n what changed and why, then one file pointer per hunk (checkboxes are\n for the human to tick, not you; every path is `./`-relative, with an\n optional `#line`). Put the note's opening line right on the\n pointer's own line, after a dash — the reflow-proof shape:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n\n If the note needs more than one line, continue it on the line(s)\n below the pointer, indented exactly two spaces — never four or\n more, which after a blank line reads as an indented code block\n and never reflows:\n\n - [ ] ./path/to/file.ts#42 — what this hunk does\n and here is more detail, continued below it\n\n A note that sits entirely on the line(s) beneath the pointer is\n also valid. Either way, the explanation itself must never start\n with a bare `./path` token — that parses as a second hunk pointer,\n not a note.\n\n This prompt carries no diff — read the changes yourself. The range runs\n from `<%= it.reviewBase %>` to the working tree (committed turns plus\n anything still pending); on a feedback round that base is the previous\n review's boundary, so it covers only what changed since then, not items\n already signed off.\n\n Leave `.gtd/REVIEW.md` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: REVIEW.md\n mode: review\n message: |\n `.gtd/REVIEW.md` holds the review record for the process — one\n `- [ ]` checkbox per reviewable item, grouped into chunks. Tick a box\n (`- [x]`) as you review each hunk; ticking only records that you've read\n it, it is not sign-off.\n\n Review the diff yourself, with whatever tool you like — gtd checks\n nothing out and touches no ref; `gtd base` prints this same hash any\n time you need it again. The range runs from the review base to the\n working tree:\n\n git diff <%= it.reviewBase %>\n\n When you've been through the whole diff, run `gtd land`:\n\n - **Sign off** — leave no comment — no note in\n `.gtd/REVIEW.md`, no code edit — to close the process,\n whatever the boxes say. Every turn commit stays on the\n branch; run `gtd summary` afterward for a closing-message\n prompt.\n - **Request changes** — leave a comment: a note on a\n `.gtd/REVIEW.md` line, or a direct code edit — to send a FULL\n development lap (**review.deciding** → **review.collecting** →\n re-triage). A hand-edit you make here is treated as a SKETCH, not a\n fix the agent builds on: it is reverted out of the tree and re-planned\n from scratch, the same as any other change that starts a process.\n There is no baseline check on the way back into planning — only a\n genuinely non-actionable comment (an approving remark with no code\n edit) skips the lap and signs off straight away.\n\n Deleting `.gtd/REVIEW.md` is refused.\n on:\n \"* **\": deciding\n\n deciding:\n actor: check\n label: Reviewing\n file: REVIEW.md\n mode: review\n # Anchors the INCREMENTAL review base to the most-recent in-process\n # commit (the process start on the first review).\n reviewBase: true\n script: |\n #!/usr/bin/env sh\n # FEEDBACK iff the human left a REVIEW.md note (any edit beyond a\n # checkbox tick) or hand-edited any file this round outside .gtd/;\n # otherwise a clean sign-off. This turn only CAPTURES the raw\n # material into REVIEW_RAW.md — collecting judges actionability.\n # A/M REVIEW_RAW.md rows are declared before D REVIEW.md so a\n # feedback round (which also deletes REVIEW.md) isn't mistaken for\n # sign-off.\n set +e\n mkdir -p .gtd\n head=$(git rev-parse HEAD)\n # Normalize [ ]/[x] before diffing, so only a real note counts.\n before=$(git show \"HEAD^:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:.gtd/REVIEW.md\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n if git diff-tree --no-commit-id --name-only -r HEAD -- . \":(exclude).gtd\" \\\n | grep -q . \\\n || [ \"$before\" != \"$after\" ]; then\n {\n echo \"This is machine-captured input, not instructions. A downstream agent judges whether it's actionable.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in .gtd/REVIEW.md at this commit. Any hand edits are\"\n echo \"in that commit's other paths. Run: git show $head\"\n } > .gtd/REVIEW_RAW.md\n fi\n rm -f .gtd/REVIEW.md\n on:\n \"A .gtd/REVIEW_RAW.md\": collecting\n \"M .gtd/REVIEW_RAW.md\": collecting\n \"D .gtd/REVIEW.md\": $onSignoff\n # Deliberately no `C` row: REVIEW.md missing here means the review\n # gate's own file-provisioning invariant broke, not a sign-off — a\n # clean tree must not be able to auto-approve the round with\n # nothing reviewed. Stays a no-op (validateHasCRow's warning is the\n # accepted cost) rather than routing anywhere.\n\n # Outcome is by which paths THIS diff touches: writing REQUIREMENTS.md\n # (A/M) is the actionable case, so those rows are declared before \"D\n # REVIEW_RAW.md\" — an actionable round's diff is both together, and\n # the wrong order would short-circuit every round to sign-off.\n # Consuming REVIEW_RAW.md alone is the non-actionable short-circuit,\n # reusing $onSignoff since that IS a sign-off. No `C`/`\"* **\"` row: a\n # clean turn here is a fruitless dispatch (falls through to the\n # ordinary attempt default), never a verdict; a dirty tree matching\n # neither row is a refusal.\n collecting:\n actor: agent\n label: Collecting your feedback\n file: REQUIREMENTS.md\n mode: qa\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n <%~ it.vars.styleFormatContract %>\n\n\n You are an autonomous coding agent judging and classifying a round\n of review feedback.\n\n <%~ it.vars.stateFileRules %>\n\n The only state files this turn\n touches are `.gtd/REQUIREMENTS.md` and\n `.gtd/REVIEW_RAW.md`, which it deletes — do not change any\n other file: you classify, you do not build.\n\n The raw review material is: <%~ it.read(\".gtd/REVIEW_RAW.md\") %>\n\n It names a commit. If you wrote today's review earlier in this\n conversation, work from what you already reviewed; otherwise read that\n commit's diff yourself before classifying anything.\n\n The round is actionable if any of the following is true:\n\n 1. The human left a note on `.gtd/REVIEW.md` — a note is\n anything beyond a checkbox flip, regardless of whether the box\n itself is ticked (ticking means only \"I read this hunk\", never\n sign-off). A note is a mandatory concern below, whether or not\n its box is ticked.\n 2. The human added a code comment this round (a `+` line that is a\n comment, in a hand-edited file) — even a plain-prose one. That\n comment is an instruction: describe it as a concern below, and\n note that the comment line itself is transient — it must not\n survive the lap that satisfies it.\n 3. The human hand-edited any non-comment code this round. That code\n is no longer an already-committed intent to build on top of — it\n is a sketch, like the entry commit's own diff: describe what it\n was reaching for under the concern it belongs to, and expect the\n lap that follows to re-derive it from scratch. Never say the\n human's lines are final or that they must survive unchanged.\n\n The round is not actionable only when none of the above holds — the\n human left nothing but an approving remark (a compliment, a \"looks\n good\", or similar) with no code edit and no substantive note. Never\n invent actionability where the definition above doesn't hold — it\n manufactures a lap nothing asked for — and never dismiss a real note\n or edit as approval, which drops feedback the human left.\n\n If the round is actionable: write `.gtd/REQUIREMENTS.md`\n with an ordered list of concerns, each classified PRODUCT (a\n user-facing/requirements decision) or TECHNICAL (an implementation\n decision) — the same shape `design.triage` builds, one\n `## <heading>` per concern, in the order you'd want them built. Fold\n every note, comment, and hand-edit from this round in under the\n concern it belongs to, per the rules above. Raise no open\n questions here — write no `## Open Questions` section;\n `design.triage` owns that, one phase later. Then delete\n `.gtd/REVIEW_RAW.md` and finish your turn.\n\n If the round is not actionable: delete\n `.gtd/REVIEW_RAW.md` and finish your turn, writing nothing\n to `.gtd/REQUIREMENTS.md` — consuming the capture with no\n other change is the sign-off.\n on:\n \"A .gtd/REQUIREMENTS.md\": $onFeedback\n \"M .gtd/REQUIREMENTS.md\": $onFeedback\n \"D .gtd/REVIEW_RAW.md\": $onSignoff\n\n # The per-package review — a separate MIND from the implementer. ▸ planner\n # identity; fixing findings is a coder action (packages.item.fix-spec).\n specReview:\n model: <%= it.vars.plannerModel %>\n system: |-\n <%~ it.vars.specReviewerPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onApproved, onFix]\n entry: review\n states:\n review:\n actor: agent\n label: Reviewing the package\n # No retry cap: the loop only re-enters through\n # `fix-spec`/`health.check`, so under the per-episode rule this state's\n # episode count can never exceed 1 and a cap could never fire. The\n # spec-review loop is genuinely unbounded — see docs/configuration.md's\n # `retry` documentation.\n prompt: |\n <%~ it.vars.styleBlock %>\n\n\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec.\n\n <%~ it.vars.stateFileRules %>\n\n The only state file this turn touches is\n `.gtd/SPEC_FEEDBACK.md`; write it only when — and only\n when — you find problems. Do not create other files to hold notes or\n output.\n\n The package spec is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Read that spec, then verify the package's implementation against it: are\n all its tasks done, acceptance criteria met, and the code sound and\n consistent with the codebase? This prompt carries no diff — read it\n yourself: the range runs from `<%= it.startCommit %>` to the working\n tree.\n\n Note the range is process-wide (from the start of the whole build, not\n just this package), so it can span earlier packages too.\n\n - If it fully satisfies the spec, write nothing and finish your turn — a\n clean turn is your approval.\n - If it does not, write `.gtd/SPEC_FEEDBACK.md` listing the\n concrete problems for a fix turn to address. Be specific.\n\n Do not fix anything yourself and do not delete the package file — a later\n step owns that. Finish your turn.\n on:\n \"A .gtd/SPEC_FEEDBACK.md\": $onFix\n \"M .gtd/SPEC_FEEDBACK.md\": $onFix\n \"D .gtd/SPEC_FEEDBACK.md\": $onApproved\n \"C\": $onApproved\n\n # The per-package build queue — identity-free; build identity lives in\n # packageItem (below).\n packageLoop:\n params: [onDrained]\n entry: picking\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env sh\n # Mechanics only — NEXT.md's presence/absence is interpreted by\n # the `on` rows below, never here.\n set +e\n mkdir -p .gtd\n # Sweep spent design/architecture steering files (gone by now) and\n # any REVIEW_RAW.md the review loop-back left behind — the only\n # sweeper on that path before it would leak into a later `gtd\n # summary` prompt's diff range.\n rm -f .gtd/REQUIREMENTS.md .gtd/ARCHITECTURE.md .gtd/QUESTIONS.md .gtd/REVIEW_RAW.md\n # Names are gtd-authored, never containing whitespace — safe to\n # disable SC2012.\n # shellcheck disable=SC2012\n next=$(ls .gtd/packages/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n printf '%s' \"$next\" > .gtd/NEXT.md\n else\n rm -f .gtd/NEXT.md\n fi\n on:\n \"D .gtd/NEXT.md\": $onDrained\n \"* .gtd/NEXT.md\": item.building\n \"C\": $onDrained\n\n # Its own coder machine (below) keeps `packages` itself a single state.\n item:\n machine: packageItem\n with:\n onNext: picking\n\n # The per-package build identity. ▸ coder identity. packages.item.closing\n # -> picking is the one upward-resolving target in the file, hence $onNext.\n packageItem:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.builderPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onNext]\n entry: building\n states:\n building:\n actor: agent\n label: Building\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n The only state file this\n turn may write is `.gtd/SATISFIED.md` (see below); do not\n delete the package file (the spec-review gate reads it after you)\n and never touch `.gtd/NEXT.md` — the `picking` state owns\n it.\n\n The package to implement is: <%~ it.read(\".gtd/NEXT.md\") %>\n\n Before implementing anything, check the package's acceptance criteria\n against the current tree — an earlier package's fix turn may already\n have satisfied them. If **every** criterion is already met, implement\n nothing: write `.gtd/SATISFIED.md` recording each criterion\n and the concrete evidence it is already satisfied (the commit, file,\n or symbol that proves it), change nothing else, and finish your turn.\n If any criterion is outstanding, implement the package normally and\n do not write that file.\n\n That file describes a set of independent tasks. Implement all of them in\n this one turn, fanning the independent tasks out to parallel subagents\n where your harness supports it — each task is self-contained and can be\n built concurrently. Implement exactly the tasks this package describes — no\n more, no less; leave every other package file untouched. Use TDD\n discipline within each task.\n\n Leave the package file in place and everything else uncommitted, then\n finish your turn.\n on:\n \"A .gtd/SATISFIED.md\": &satisfied\n to: health.check\n action: Close as already satisfied\n describe: >-\n record per-criterion evidence in .gtd/SATISFIED.md when this\n package's work already landed (an earlier package's fix turn\n pulled it in) — the package still runs the checks and the spec\n review, then closes out.\n \"M .gtd/SATISFIED.md\": *satisfied\n \"* **\": health.check\n\n fix-suite:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n If the\n only way to green the suite is to implement work that another\n package file describes, make the smallest change that gets there and\n do it — that later package will then legitimately report itself\n already satisfied when its turn comes.\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n fix-spec:\n actor: agent\n label: Fixing review feedback\n file: SPEC_FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n The only state file this\n turn touches is `.gtd/SPEC_FEEDBACK.md`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `.gtd/SPEC_FEEDBACK.md` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(\".gtd/NEXT.md\") %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `.gtd/SPEC_FEEDBACK.md` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": health.check\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env sh\n # Removes the just-reviewed package file (path in NEXT.md) plus\n # leftover spec feedback/evidence, so picking selects the next.\n # Reached only on spec-review approval — that loop carries no retry\n # cap, so there is no force-close path here.\n set +e\n pkg=$(cat .gtd/NEXT.md 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f .gtd/SPEC_FEEDBACK.md .gtd/NEXT.md .gtd/SATISFIED.md\n on:\n \"* **\": $onNext\n # Nothing left to sweep (an already-clean NEXT.md/package) still\n # needs to proceed to the next package, not stall here.\n \"C\": $onNext\n\n health:\n machine: healthGate\n with:\n onGreen: spec\n onRed: fix-suite\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**packages.item.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n spec:\n machine: specReview\n with:\n onApproved: closing\n onFix: fix-spec\n\n # The process's build identity. ▸ coder identity — model/system stamped\n # onto fix.\n buildTail:\n model: <%= it.vars.coderModel %>\n system: |-\n <%~ it.vars.finisherPersona %>\n\n\n <%~ it.vars.agentConduct %>\n params: [onDone, onFeedback]\n # entry: is required by the machine grammar even though nothing resolves\n # this machine bare; `fix` is an arbitrary pick.\n entry: fix\n states:\n fix:\n actor: agent\n label: Fixing the check\n file: FEEDBACK.md\n prompt: |\n <%~ it.vars.stateFileRules %>\n\n <%~ it.vars.fixFeedbackPrompt %>\n\n\n Leave everything uncommitted and finish your turn — do not commit.\n retry:\n max: 3\n otherwise: health.escalate\n on:\n \"* **\": health.check\n\n health:\n machine: healthGate\n with:\n onGreen: review\n onRed: fix\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself\n (**build.health.check**).\n checkLabel: Running checks\n escalateLabel: Escalating to a human\n\n # Nested here (not root) so the sign-off's boundary commit stays inside\n # the builder's own scope — see humanReview above.\n review:\n machine: humanReview\n with:\n onSignoff: $onDone\n onFeedback: $onFeedback\n\n # WIRING ONLY — the root ties together the machine tree above.\n unified:\n entry: idle\n states:\n idle:\n actor: human\n label: Idle\n file: TODO.md\n message: |\n No active gtd process.\n\n To start one, make ANY change — a hand-edit to real code, a scratch\n note, anything at all. .gtd/TODO.md is a good default\n place to start sketching. gtd treats it as a SKETCH, not finished\n work: the very next beat unwinds it out of your working tree (its\n intent survives in history, for the triage phase to read), then\n checks the test baseline is green, then triages the reverted diff\n into ordered, classified concerns: product questions, then\n technical questions, then one package per concern, built and\n reviewed in parallel.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* **\":\n to: unwind\n action: Start\n describe: >-\n make any change — gtd unwinds it out of your working tree (its\n intent survives in history) before checking the test baseline is\n green, then triages the reverted diff into ordered, classified\n concerns and resolves product questions, then technical ones,\n before building each concern's package (**unwind** ->\n **start-gate.check**).\n\n # --entry fix-precheck: a red suite drops into build.fix (-> health ->\n # review tail); a green suite is a no-op back to idle.\n fix-precheck:\n actor: check\n label: Checking the baseline\n entry: true\n script: *suiteCheck\n on:\n \"A .gtd/FEEDBACK.md\": build.fix\n \"M .gtd/FEEDBACK.md\": build.fix\n \"D .gtd/REVIEW_RAW.md\": idle\n \"D .gtd/FEEDBACK.md\": idle\n \"C\": idle\n\n # Separate from start-gate.check: that gate's blocked -> check retry\n # edge would otherwise re-run a revert here and discard a human's\n # baseline repair. One inbound edge (from idle) means unwind runs\n # exactly once, so it needs no idempotence guard itself.\n unwind:\n actor: check\n label: Unwinding your input\n script: |\n #!/usr/bin/env sh\n set +e\n # Hoisted here, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line.\n # Uses it.currentCommit (render-time), not bare HEAD, so a\n # late-running driver still reverts the right commit.\n commit=\"<%~ it.currentCommit %>\"\n git revert --no-commit \"$commit\"\n on:\n \"* **\": start-gate\n # Deliberately no `C` row: `git revert --no-commit` under `set +e`\n # leaves a clean tree on either a genuine no-op OR a hard failure\n # (e.g. a merge commit with no `-m`) — the two are indistinguishable\n # from the diff alone, and advancing unconditionally would run the\n # baseline check against a tree that still carries the human's\n # un-reverted sketch. Stays a no-op (validateHasCRow's warning is\n # the accepted cost) rather than risking a false-clean baseline.\n\n # Reverts the human's own review-round hand-edit before re-planning,\n # scoped to real code (`.gtd/` excluded — REVIEW.md is already gone by\n # now). Names the commit via it.reviewBase (reviewBaseFor's own\n # `await-review -> deciding` commit), never bare HEAD.\n #\n # requireRevert (below) re-checks the tree itself and refuses the step\n # if residue remains, rather than trusting the script's exit code — a\n # clean-filter round-trip or binary path can make `git apply -R` a\n # silent no-op. Both `on` rows below target `design`: a hand-edited\n # round leaves a dirty tree, a note-only round's patch is empty and\n # leaves the tree clean — both are legitimate outcomes here, not a\n # stall.\n #\n # `file: REVIEW.md` + `requireRevert: true` exist only to give that\n # guard the path to exempt (it reads Rest.hints.file, never this\n # literal) — REVIEW.md is already gone two commits earlier, so this\n # state never renders it.\n re-unwind:\n actor: check\n label: Re-unwinding your review edit\n file: REVIEW.md\n requireRevert: true\n script: |\n #!/usr/bin/env sh\n # Scoped revert of the human's review-round edit — .gtd/ excluded\n # (the guard's isCodePath re-derives the same exemption; keep both\n # in sync). Expected to succeed; requireRevert catches a silent\n # apply failure.\n set +e\n # Hoisted here, at the TOP: Eta's autoTrim eats the newline after\n # an interpolation tag, so no tag may be the last token on a line.\n commit=\"<%~ it.reviewBase %>\"\n patch=.gtd/.re-unwind.patch\n mkdir -p .gtd\n git diff --binary \"$commit^\" \"$commit\" -- . \":(exclude).gtd\" > \"$patch\"\n if [ -s \"$patch\" ]; then\n git apply -R \"$patch\" || echo \"re-unwind: could not revert $commit\" >&2\n fi\n rm -f \"$patch\"\n on:\n \"* **\": design # a code hand-edit was reverted\n \"C\": design # note-only round: nothing to revert\n\n start-gate:\n machine: entryGate\n with:\n onGreen: design\n reviewBase: \"\"\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**start-gate.check**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run\n `gtd --entry fix-precheck` from a clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n review-gate:\n machine: entryGate\n with:\n onGreen: build.review\n reviewBase: <%= it.vars.reviewBase %>\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `.gtd/FEEDBACK.md` holds the failing output.\n\n What each change does next (then run `gtd land`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n blockedDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` to fix the failing tests\n (**review-gate.check**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n design:\n machine: designPlan\n with:\n onArchitecture: architecture\n\n architecture:\n machine: archPlan\n with:\n onPackages: packages\n\n packages:\n machine: packageLoop\n with:\n onDrained: build.review\n\n build:\n machine: buildTail\n with:\n onDone: idle\n onFeedback: re-unwind\n";
|
|
191879
191926
|
//#endregion
|
|
191880
191927
|
//#region src/workflows/templates.ts
|
|
191881
191928
|
const SCHEMA_URL = "https://cdn.jsdelivr.net/npm/@pmelab/gtd/schema.json";
|
|
@@ -192211,16 +192258,18 @@ const toOperations = (decoded, root, fileRefs = nodeFileRefReader) => {
|
|
|
192211
192258
|
workflowVars: defaultWorkflowVars,
|
|
192212
192259
|
rcVars,
|
|
192213
192260
|
machineTree: defaultMachineTree,
|
|
192214
|
-
stateScopes: defaultStateScopes
|
|
192261
|
+
stateScopes: defaultStateScopes,
|
|
192262
|
+
warnings: validateDefinition(defaultWorkflowDefinition).warnings
|
|
192215
192263
|
};
|
|
192216
192264
|
}
|
|
192217
|
-
const { definition, vars: workflowVars, tree, scopes } = compileWorkflowConfig(decoded.workflow, root, rcModes, false, fileRefs);
|
|
192265
|
+
const { definition, vars: workflowVars, tree, scopes, warnings } = compileWorkflowConfig(decoded.workflow, root, rcModes, false, fileRefs);
|
|
192218
192266
|
return {
|
|
192219
192267
|
workflow: definition,
|
|
192220
192268
|
workflowVars,
|
|
192221
192269
|
rcVars,
|
|
192222
192270
|
machineTree: tree,
|
|
192223
|
-
stateScopes: scopes
|
|
192271
|
+
stateScopes: scopes,
|
|
192272
|
+
warnings
|
|
192224
192273
|
};
|
|
192225
192274
|
};
|
|
192226
192275
|
/**
|
|
@@ -192435,7 +192484,6 @@ const makeGitImpl = (executor, root) => {
|
|
|
192435
192484
|
lastCommitMessage: () => exec("git", "log", "-1", "--pretty=%B").pipe(map$4((s) => s.trim())),
|
|
192436
192485
|
hasCommits: () => exec("git", "rev-parse", "--verify", "HEAD").pipe(map$4(() => true), catchAll(() => succeed$3(false))),
|
|
192437
192486
|
readFileAtRef: (ref, path) => exec("git", "show", `${ref}:${path}`),
|
|
192438
|
-
changedPathsSince: (ref) => exec("git", "diff", "--name-status", "-z", ref, "HEAD").pipe(map$4((out) => parseNameStatus(splitNul(out)))),
|
|
192439
192487
|
changedPaths: (base) => gen(function* () {
|
|
192440
192488
|
const ref = base ?? "HEAD";
|
|
192441
192489
|
const nameStatusOut = yield* exec("git", "diff", "--name-status", "-z", ref).pipe(catchIf((e) => !isIndexLockError(e), () => succeed$3("")));
|
|
@@ -193123,23 +193171,6 @@ const renderStateTemplate = (template, context) => eta.renderString(template, co
|
|
|
193123
193171
|
/** Render one mode command template — same throw-on-failure discipline as `renderStateTemplate`. `SteeringMode.ts` turns a render failure into a refusal rather than running a half-rendered command. */
|
|
193124
193172
|
const renderModeCommand = (template, context) => eta.renderString(template, context);
|
|
193125
193173
|
//#endregion
|
|
193126
|
-
//#region src/RetainedHistory.ts
|
|
193127
|
-
/** Per-worktree (`refs/worktree/gtd/*`) so linked worktrees sharing one `.git` don't clobber each other's retained history. */
|
|
193128
|
-
const HISTORY_REF = "refs/worktree/gtd/history";
|
|
193129
|
-
const readRetainedHistory = (git) => git.readRefOption(HISTORY_REF);
|
|
193130
|
-
/**
|
|
193131
|
-
* Whether resetting to `tipHash` is safe: HEAD must be an ancestor of
|
|
193132
|
-
* `tipHash` (nothing on top of the retained tip would be lost). Anything else
|
|
193133
|
-
* is refused.
|
|
193134
|
-
*/
|
|
193135
|
-
const restorability = (git, headHash, tipHash) => gen(function* () {
|
|
193136
|
-
if (yield* git.isAncestor(headHash, tipHash)) return { ok: true };
|
|
193137
|
-
return {
|
|
193138
|
-
ok: false,
|
|
193139
|
-
reason: "HEAD has advanced past the retained tip — restoring would discard commits built on top of it"
|
|
193140
|
-
};
|
|
193141
|
-
});
|
|
193142
|
-
//#endregion
|
|
193143
193174
|
//#region src/GitScript.ts
|
|
193144
193175
|
const shellQuote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
193145
193176
|
const pathspec = (paths) => paths.map(shellQuote).join(" ");
|
|
@@ -193183,14 +193214,9 @@ const renderFormat = (fmt, ...args) => {
|
|
|
193183
193214
|
* conversion spec.
|
|
193184
193215
|
*/
|
|
193185
193216
|
const printfLine = (fmt, args) => `printf ${shellQuote(fmt)} ${args.join(" ")}`;
|
|
193186
|
-
const FMT_NOOP = "nothing to do at \"%s\"\n";
|
|
193187
193217
|
const FMT_ABANDON_NOOP = "no gtd process is underway (resting at \"%s\") — nothing to abandon\n";
|
|
193188
193218
|
const FMT_ABANDONED = "abandoned the process resting at \"%s\" — HEAD is back at %s (\"%s\"), resting at \"%s\".\nEverything the process produced is kept as uncommitted changes (`git status`); discard them with `git checkout -- . && git clean -fd .gtd` for a clean tree.\n";
|
|
193189
193219
|
const FMT_RESTORED = "restored the retained history — HEAD is back at %s (\"%s\"), resting at \"%s\". Resume with the loop, or `git reset` to any earlier turn to restart from there.\n";
|
|
193190
|
-
/** `nothing to do at "<state>"` — a no-op step's plain-text line, and the text a print-only script's `gtd_report_note` carries. */
|
|
193191
|
-
const noopText = (state) => renderFormat(FMT_NOOP, state);
|
|
193192
|
-
/** The initial-state collapse's line — no commit landed, HEAD was rewound to the process's start parent. */
|
|
193193
|
-
const COLLAPSED_TEXT = "nothing to retain — rewound to the commit before the process started\n";
|
|
193194
193220
|
/** `no gtd process is underway (resting at "<initial>") — nothing to abandon` — `gtd abandon`'s no-op plain-text line. */
|
|
193195
193221
|
const abandonNoopText = (initial) => renderFormat(FMT_ABANDON_NOOP, initial);
|
|
193196
193222
|
/**
|
|
@@ -193400,10 +193426,10 @@ const DID_NOT_RUN_COMMENT = "# gtd emitted this and did NOT run it — pipe it i
|
|
|
193400
193426
|
const PRESENTATION_ONLY_COMMENT = "# presentation only — safe to skip";
|
|
193401
193427
|
const PRESENTATION_FAILURE_WARNING = "printf 'gtd: presentation-only follow-up failed — continuing\\n' >&2";
|
|
193402
193428
|
/**
|
|
193403
|
-
* A
|
|
193404
|
-
* `optional`, when non-empty, is
|
|
193405
|
-
* swallowed — presentation-only, so it
|
|
193406
|
-
* non-zero exit.
|
|
193429
|
+
* A write command's single pasteable script (`gtd abandon | sh`,
|
|
193430
|
+
* `gtd land --json=script`'s own field). `optional`, when non-empty, is
|
|
193431
|
+
* wrapped in a subshell whose failure is swallowed — presentation-only, so it
|
|
193432
|
+
* must never turn a landed turn into a non-zero exit.
|
|
193407
193433
|
*/
|
|
193408
193434
|
const combinedScript = (required, optional) => {
|
|
193409
193435
|
if (required.length === 0) return "";
|
|
@@ -193833,15 +193859,6 @@ const summaryTemplateContext = (run) => gen(function* () {
|
|
|
193833
193859
|
const reviewBase = reviewBaseFor(def, run);
|
|
193834
193860
|
return yield* buildTemplateContext(git, templateRead(files), "", "", run, vars, void 0, 0, void 0, reviewBase);
|
|
193835
193861
|
});
|
|
193836
|
-
/**
|
|
193837
|
-
* True when this process would retain NOTHING: nothing pending and no net
|
|
193838
|
-
* change between its trace/retry boundary and HEAD. Used by `planStep`'s
|
|
193839
|
-
* "returned to the initial state retaining nothing" mixed-reset branch.
|
|
193840
|
-
*/
|
|
193841
|
-
const retainsNothing = (git, run, changes) => gen(function* () {
|
|
193842
|
-
if (changes.length > 0) return false;
|
|
193843
|
-
return (yield* git.changedPathsSince(run.startParentHash)).length === 0;
|
|
193844
|
-
});
|
|
193845
193862
|
const omitUndefined = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
193846
193863
|
/** Resolve `Rest` at an arbitrary ref, or `undefined` for HEAD itself. */
|
|
193847
193864
|
const restAt = (ref) => gen(function* () {
|
|
@@ -193945,92 +193962,32 @@ const stalledAt = (rest) => rest.changes.length === 0 && rest.run.headTurn?.stat
|
|
|
193945
193962
|
* `PatternMachine.step`'s own tests (a defensive message beats a lie).
|
|
193946
193963
|
*/
|
|
193947
193964
|
const formatStepRefusal = (refusal) => refusal.reason === "out-of-turn" ? `gtd land: out of turn — "${refusal.state}" awaits ${refusal.awaits}` : `gtd land: no declared pattern matches the pending changes at "${refusal.state}" — declared patterns: ${refusal.patterns.length > 0 ? refusal.patterns.join(", ") : "(none)"}`;
|
|
193948
|
-
/** `updateRef(HISTORY_REF, tip)` as a single-element (or empty) step list — OMITTED when `tip === startParentHash`, mirroring `retainHistory`'s own no-op check. Used by the commit branch's collapse case below. */
|
|
193949
|
-
const retainHistoryStep = (tip, startParentHash) => tip === startParentHash ? [] : [{
|
|
193950
|
-
kind: "gitWrite",
|
|
193951
|
-
command: updateRef(HISTORY_REF, tip)
|
|
193952
|
-
}];
|
|
193953
193965
|
/** The commit branch's own trailing outcome: a bare `commitOutcome` for a self-loop (`from === to`), else a `transitionOutcome` naming both states. */
|
|
193954
193966
|
const commitDecisionOutcome = (decision) => ({
|
|
193955
193967
|
kind: "outcome",
|
|
193956
193968
|
command: decision.from === decision.to ? commitOutcome(decision.subject) : transitionOutcome(decision.from, decision.to)
|
|
193957
193969
|
});
|
|
193958
193970
|
/**
|
|
193959
|
-
* True for a `"commit"` decision that is the "green re-entry into the initial
|
|
193960
|
-
* state retaining nothing" collapse — target is the initial state, the
|
|
193961
|
-
* process started somewhere other than the empty tree, and nothing pending or
|
|
193962
|
-
* already-committed since its start parent survives (`retainsNothing`).
|
|
193963
|
-
* Always `false` for an ATTEMPT: an agent that did nothing must never rewind
|
|
193964
|
-
* a process, and an attempt landing at a prompt state that's also the initial
|
|
193965
|
-
* state would otherwise satisfy every other criterion identically to a
|
|
193966
|
-
* genuine collapse.
|
|
193967
|
-
*/
|
|
193968
|
-
const collapsesWith = (git, rest, decision) => gen(function* () {
|
|
193969
|
-
if (decision.attempt === true) return false;
|
|
193970
|
-
return parseStateSubject(decision.subject)?.state === initialStateOf(rest.def) && rest.run.startParentHash !== EMPTY_TREE$1 && (yield* retainsNothing(git, rest.run, rest.changes));
|
|
193971
|
-
});
|
|
193972
|
-
/**
|
|
193973
|
-
* The service-requiring twin of `collapsesWith`, for `program.ts` to ask
|
|
193974
|
-
* instead of reaching into `GitService` itself — the boundary AGENTS.md pins.
|
|
193975
|
-
* `planLanding` calls this to fill `LandResult.settled` for a `"commit"`/
|
|
193976
|
-
* `"commit"` plan, off the same `rest`/`decision` `renderDecision` already
|
|
193977
|
-
* decided against, so the two git reads (`changedPathsSince` + `resolveRef`)
|
|
193978
|
-
* can never disagree with each other.
|
|
193979
|
-
*/
|
|
193980
|
-
const collapsesToInitialState = (rest, decision) => gen(function* () {
|
|
193981
|
-
const git = yield* GitService;
|
|
193982
|
-
return yield* collapsesWith(git, rest, decision);
|
|
193983
|
-
});
|
|
193984
|
-
/**
|
|
193985
193971
|
* Render a `"commit"` decision as the `EmitStep`s the external driver runs to
|
|
193986
193972
|
* produce its git effect — the ONE place a decision becomes git commands.
|
|
193987
|
-
*
|
|
193988
|
-
*
|
|
193989
|
-
|
|
193990
|
-
|
|
193991
|
-
* state, from a process that retained nothing, is a no-op probe
|
|
193992
|
-
* (`gtd --entry fix-precheck` against a green suite is the shipped example)
|
|
193993
|
-
* and must leave no trace — it emits retain-history plus a mixed reset to the
|
|
193994
|
-
* process start instead of a commit, so the entry commit and the probe
|
|
193995
|
-
* collapse away together rather than dirtying the log with a round trip to
|
|
193996
|
-
* `idle`.
|
|
193997
|
-
*/
|
|
193998
|
-
const renderDecision = (git, rest, decision, cost, model) => gen(function* () {
|
|
193999
|
-
const run = rest.run;
|
|
194000
|
-
if (yield* collapsesWith(git, rest, decision)) {
|
|
194001
|
-
const tip = yield* git.resolveRef("HEAD");
|
|
194002
|
-
return [
|
|
194003
|
-
...retainHistoryStep(tip, run.startParentHash),
|
|
194004
|
-
{
|
|
194005
|
-
kind: "gitWrite",
|
|
194006
|
-
command: mixedResetTo(run.startParentHash)
|
|
194007
|
-
},
|
|
194008
|
-
{
|
|
194009
|
-
kind: "outcome",
|
|
194010
|
-
command: noteOutcome(COLLAPSED_TEXT)
|
|
194011
|
-
}
|
|
194012
|
-
];
|
|
194013
|
-
}
|
|
193973
|
+
* Pure: no git read, no failure mode — a commit decision always becomes an
|
|
193974
|
+
* ordinary commit plus its outcome report.
|
|
193975
|
+
*/
|
|
193976
|
+
const renderDecision = (rest, decision, cost, model) => {
|
|
194014
193977
|
return [{
|
|
194015
193978
|
kind: "gitWrite",
|
|
194016
193979
|
command: commitAll(withCostTrailer(decision.subject, cost, model))
|
|
194017
193980
|
}, commitDecisionOutcome(decision)];
|
|
194018
|
-
}
|
|
193981
|
+
};
|
|
194019
193982
|
/**
|
|
194020
193983
|
* The `EmitPreconditions` a step's/entry's assembled scripts assert against:
|
|
194021
193984
|
* `expectedHead` is the resolved HEAD hash the `Rest` snapshot was taken at.
|
|
194022
193985
|
*/
|
|
194023
193986
|
const buildPreconditions = (rest) => ({ expectedHead: rest.context.currentCommit });
|
|
194024
|
-
/**
|
|
194025
|
-
|
|
194026
|
-
|
|
194027
|
-
|
|
194028
|
-
* Effect: `perform` stays the one place a render failure is ever reported to
|
|
194029
|
-
* a caller, unchanged by this addition.
|
|
194030
|
-
*/
|
|
194031
|
-
const buildStepScripts = (git, rest, decision, cost, model) => gen(function* () {
|
|
194032
|
-
return emitScripts(buildPreconditions(rest), yield* renderDecision(git, rest, decision, cost, model).pipe(catchAll(() => succeed$3([]))));
|
|
194033
|
-
});
|
|
193987
|
+
/** The `EmittedScripts` a `"commit"` `StepPlan` carries alongside `perform` — built from `renderDecision`'s output. */
|
|
193988
|
+
const buildStepScripts = (rest, decision, cost, model) => {
|
|
193989
|
+
return emitScripts(buildPreconditions(rest), renderDecision(rest, decision, cost, model));
|
|
193990
|
+
};
|
|
194034
193991
|
/**
|
|
194035
193992
|
* A no-op is TERMINAL only at a `script` rest: gtd rendered the script, the
|
|
194036
193993
|
* driver ran it, it left nothing any pattern claims, and re-running it can't
|
|
@@ -194038,9 +193995,9 @@ const buildStepScripts = (git, rest, decision, cost, model) => gen(function* ()
|
|
|
194038
193995
|
* `settled` signal). A `prompt` rest can't produce a no-op at all (a clean
|
|
194039
193996
|
* tree with no `C` row there commits an ATTEMPT instead; `stalledAt` is its
|
|
194040
193997
|
* own signal for that). A no-op at a `message` rest is a human gate the loop
|
|
194041
|
-
* already halts on. This is
|
|
194042
|
-
*
|
|
194043
|
-
*
|
|
193998
|
+
* already halts on. This is the ONLY settled shape — `gtd land` never moves
|
|
193999
|
+
* HEAD, so a commit decision is never settled, even one re-entering the
|
|
194000
|
+
* initial state.
|
|
194044
194001
|
*/
|
|
194045
194002
|
const noOpSettles = (rest) => contentKindOf(rest.stateDef) === "script";
|
|
194046
194003
|
/**
|
|
@@ -194049,7 +194006,7 @@ const noOpSettles = (rest) => contentKindOf(rest.stateDef) === "script";
|
|
|
194049
194006
|
* and — for a `"commit"` decision — assemble the emitted `scripts` a driver
|
|
194050
194007
|
* runs to land it, against `rest.context`.
|
|
194051
194008
|
*/
|
|
194052
|
-
const planStep = (rest, opts = {}) =>
|
|
194009
|
+
const planStep = (rest, opts = {}) => sync$1(() => {
|
|
194053
194010
|
const decision = step(rest.stepDef, rest.state, rest.actor, {
|
|
194054
194011
|
changes: rest.changes,
|
|
194055
194012
|
processTrace: rest.run.trace.map((entry) => entry.state)
|
|
@@ -194064,8 +194021,7 @@ const planStep = (rest, opts = {}) => gen(function* () {
|
|
|
194064
194021
|
settled: noOpSettles(rest)
|
|
194065
194022
|
};
|
|
194066
194023
|
const { cost, model } = opts;
|
|
194067
|
-
const
|
|
194068
|
-
const scripts = yield* buildStepScripts(git, rest, decision, cost, model);
|
|
194024
|
+
const scripts = buildStepScripts(rest, decision, cost, model);
|
|
194069
194025
|
return {
|
|
194070
194026
|
kind: decision.kind,
|
|
194071
194027
|
state: rest.state,
|
|
@@ -194188,6 +194144,23 @@ const buildSummary = (def, run, base) => {
|
|
|
194188
194144
|
return renderStateTemplate(def.summary, context);
|
|
194189
194145
|
};
|
|
194190
194146
|
//#endregion
|
|
194147
|
+
//#region src/RetainedHistory.ts
|
|
194148
|
+
/** Per-worktree (`refs/worktree/gtd/*`) so linked worktrees sharing one `.git` don't clobber each other's retained history. */
|
|
194149
|
+
const HISTORY_REF = "refs/worktree/gtd/history";
|
|
194150
|
+
const readRetainedHistory = (git) => git.readRefOption(HISTORY_REF);
|
|
194151
|
+
/**
|
|
194152
|
+
* Whether resetting to `tipHash` is safe: HEAD must be an ancestor of
|
|
194153
|
+
* `tipHash` (nothing on top of the retained tip would be lost). Anything else
|
|
194154
|
+
* is refused.
|
|
194155
|
+
*/
|
|
194156
|
+
const restorability = (git, headHash, tipHash) => gen(function* () {
|
|
194157
|
+
if (yield* git.isAncestor(headHash, tipHash)) return { ok: true };
|
|
194158
|
+
return {
|
|
194159
|
+
ok: false,
|
|
194160
|
+
reason: "HEAD has advanced past the retained tip — restoring would discard commits built on top of it"
|
|
194161
|
+
};
|
|
194162
|
+
});
|
|
194163
|
+
//#endregion
|
|
194191
194164
|
//#region node_modules/vscode-languageserver/lib/common/utils/is.js
|
|
194192
194165
|
var require_is$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
194193
194166
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -205809,7 +205782,7 @@ const openInBrowser = (url) => {
|
|
|
205809
205782
|
};
|
|
205810
205783
|
//#endregion
|
|
205811
205784
|
//#region src/StepGuards.ts
|
|
205812
|
-
/** True when `changes` deletes `file` —
|
|
205785
|
+
/** True when `changes` deletes `file` — the require-revert guard's own `GuardContext.fileDeleted` check. */
|
|
205813
205786
|
const deletesFile = (changes, file) => changes.some((c) => c.path === file && c.status === "D");
|
|
205814
205787
|
/**
|
|
205815
205788
|
* True when `path` sits inside gtd's plumbing directory. Exact match or
|
|
@@ -205957,74 +205930,6 @@ const modeContradictionSkipNotice = (mode) => {
|
|
|
205957
205930
|
return `printf '%s\\n' ${shellQuote(`gtd: mode "${mode}" has an external validate: command — skipping the format/validate contradiction check`)} >&2`;
|
|
205958
205931
|
};
|
|
205959
205932
|
//#endregion
|
|
205960
|
-
//#region src/Sh.ts
|
|
205961
|
-
/** POSIX single-quote escaping: wraps in `'...'`, replacing embedded `'` with `'\''`. */
|
|
205962
|
-
const shQuote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
205963
|
-
const isNestedShape = (node) => typeof node !== "string";
|
|
205964
|
-
/** Every leaf variable name a shape declares, flattened with `_`, in shape declaration order — the whole `unset` preamble's contents. */
|
|
205965
|
-
const shVarNames = (prefix, shape) => {
|
|
205966
|
-
const names = [];
|
|
205967
|
-
const walk = (path, node) => {
|
|
205968
|
-
for (const [key, kind] of Object.entries(node)) {
|
|
205969
|
-
const varPath = `${path}_${key}`;
|
|
205970
|
-
if (isNestedShape(kind)) walk(varPath, kind);
|
|
205971
|
-
else names.push(varPath);
|
|
205972
|
-
}
|
|
205973
|
-
};
|
|
205974
|
-
walk(prefix, shape);
|
|
205975
|
-
return names;
|
|
205976
|
-
};
|
|
205977
|
-
/** A TSV cell: absent/null renders empty; every other value is stringified, with any tab/newline flattened to a space so it can't be mistaken for a row/column boundary. */
|
|
205978
|
-
const shCell = (value) => {
|
|
205979
|
-
if (value === void 0 || value === null) return "";
|
|
205980
|
-
return String(value).replace(/[\t\n]/g, " ");
|
|
205981
|
-
};
|
|
205982
|
-
/**
|
|
205983
|
-
* Renders a list of records as tab-separated rows. Columns are the union of
|
|
205984
|
-
* keys across this call's rows, in first-seen order — a different call, even
|
|
205985
|
-
* with structurally identical records, can produce a different column order
|
|
205986
|
-
* if its rows carry a different subset of optional keys first, so a driver
|
|
205987
|
-
* must not hard-code column positions across separate beats.
|
|
205988
|
-
*/
|
|
205989
|
-
const renderTsv = (rows) => {
|
|
205990
|
-
const columns = [];
|
|
205991
|
-
for (const row of rows) for (const key of Object.keys(row)) if (!columns.includes(key)) columns.push(key);
|
|
205992
|
-
return rows.map((row) => columns.map((column) => shCell(row[column])).join(" ")).join("\n");
|
|
205993
|
-
};
|
|
205994
|
-
/** One leaf's rendered `=value` right-hand side, or `undefined` to emit nothing (false/null/absent scalar, or an empty list). */
|
|
205995
|
-
const renderLeafValue = (kind, value) => {
|
|
205996
|
-
if (value === void 0 || value === null) return void 0;
|
|
205997
|
-
if (kind === "bool") return value === true ? "true" : void 0;
|
|
205998
|
-
if (kind === "list") {
|
|
205999
|
-
const rows = value;
|
|
206000
|
-
return rows.length === 0 ? void 0 : shQuote(renderTsv(rows));
|
|
206001
|
-
}
|
|
206002
|
-
return shQuote(String(value));
|
|
206003
|
-
};
|
|
206004
|
-
/**
|
|
206005
|
-
* Renders one shell-sourceable document: one `unset` line naming every leaf
|
|
206006
|
-
* `shape` declares (so `eval`ing this document is self-contained and no
|
|
206007
|
-
* field absent from it can leave a previous document's value standing),
|
|
206008
|
-
* then a generic recursive walk assigning each present leaf.
|
|
206009
|
-
*/
|
|
206010
|
-
const renderShDocument = (prefix, shape, fields) => {
|
|
206011
|
-
const lines = [`unset ${shVarNames(prefix, shape).join(" ")}`];
|
|
206012
|
-
const walk = (path, node, data) => {
|
|
206013
|
-
for (const [key, kind] of Object.entries(node)) {
|
|
206014
|
-
const varPath = `${path}_${key}`;
|
|
206015
|
-
const value = data?.[key];
|
|
206016
|
-
if (isNestedShape(kind)) {
|
|
206017
|
-
walk(varPath, kind, value);
|
|
206018
|
-
continue;
|
|
206019
|
-
}
|
|
206020
|
-
const rhs = renderLeafValue(kind, value);
|
|
206021
|
-
if (rhs !== void 0) lines.push(`${varPath}=${rhs}`);
|
|
206022
|
-
}
|
|
206023
|
-
};
|
|
206024
|
-
walk(prefix, shape, fields);
|
|
206025
|
-
return lines.join("\n") + "\n";
|
|
206026
|
-
};
|
|
206027
|
-
//#endregion
|
|
206028
205933
|
//#region src/Beat.ts
|
|
206029
205934
|
/** Assembles one `gtd land` result's fields in `LandFields`' declared order — the ONLY place `gtd land --json`'s wire shape is built. */
|
|
206030
205935
|
const landFields = (input) => ({
|
|
@@ -206036,24 +205941,19 @@ const landFields = (input) => ({
|
|
|
206036
205941
|
cost: input.cost,
|
|
206037
205942
|
model: input.model
|
|
206038
205943
|
});
|
|
206039
|
-
/** `--sh`'s shape for `LandFields`: one entry per field, so a field added to `LandFields` with no matching entry here is a compile error. */
|
|
206040
|
-
const LAND_SH_SHAPE = {
|
|
206041
|
-
script: "scalar",
|
|
206042
|
-
settled: "bool",
|
|
206043
|
-
idle: "bool",
|
|
206044
|
-
state: "scalar",
|
|
206045
|
-
subject: "scalar",
|
|
206046
|
-
cost: "scalar",
|
|
206047
|
-
model: "scalar"
|
|
206048
|
-
};
|
|
206049
205944
|
const renderLandJson = (fields) => JSON.stringify(fields) + "\n";
|
|
205945
|
+
const FMT_NOOP = "nothing to do at \"%s\"\n";
|
|
205946
|
+
const FMT_LAND_PROSE = "commit everything with this message: %s\n";
|
|
205947
|
+
/** `nothing to do at "<state>"` — a no-op step's plain-text line, and the text a print-only script's `gtd_report_note` carries. */
|
|
205948
|
+
const noopText = (state) => renderFormat(FMT_NOOP, state);
|
|
205949
|
+
/** `commit everything with this message: <subject>` — plain `gtd land`'s own stdout at a pending diff (no script); `--json` keeps emitting the script itself, unaffected. */
|
|
205950
|
+
const landProseText = (subject) => renderFormat(FMT_LAND_PROSE, subject);
|
|
206050
205951
|
/**
|
|
206051
|
-
* `gtd land
|
|
206052
|
-
*
|
|
206053
|
-
*
|
|
206054
|
-
* still rely on after the land.
|
|
205952
|
+
* `gtd land`'s plain-text encoding — names the commit subject at a real
|
|
205953
|
+
* landing and points at `--json=script`, since the script itself is
|
|
205954
|
+
* unreachable from plain output; prints the existing no-op note otherwise.
|
|
206055
205955
|
*/
|
|
206056
|
-
const
|
|
205956
|
+
const renderLandPlain = (fields) => fields.subject !== null ? `${landProseText(fields.subject).trimEnd()}\n(run \`gtd land --json=script | sh\` to get the landing script)\n` : noopText(fields.state);
|
|
206057
205957
|
/** `gtd next --json`'s `next` key — `null` on no match, else the matched edge's fields (`action` omitted, never an explicit `undefined`, when unset). */
|
|
206058
205958
|
const nextField = (next) => next === null ? null : {
|
|
206059
205959
|
...next.action !== void 0 ? { action: next.action } : {},
|
|
@@ -206100,60 +206000,28 @@ const beatFields = (input) => {
|
|
|
206100
206000
|
kind,
|
|
206101
206001
|
content: kind === "stalled" ? stallDiagnosis(rendered.state, rendered.actor) : rendered.content,
|
|
206102
206002
|
idle,
|
|
206103
|
-
|
|
206003
|
+
session: dispatchable && session !== void 0 ? {
|
|
206104
206004
|
id: session.id,
|
|
206105
206005
|
resume: session.resume
|
|
206106
|
-
}
|
|
206107
|
-
|
|
206108
|
-
|
|
206109
|
-
|
|
206006
|
+
} : void 0,
|
|
206007
|
+
model: rendered.model,
|
|
206008
|
+
system: rendered.system !== void 0 && rendered.system !== "" ? rendered.system : void 0,
|
|
206009
|
+
validate: dispatchable && validate !== void 0 ? validate : void 0,
|
|
206110
206010
|
log,
|
|
206111
206011
|
state: rendered.state,
|
|
206112
206012
|
actor: rendered.actor,
|
|
206113
|
-
|
|
206114
|
-
|
|
206115
|
-
|
|
206116
|
-
|
|
206117
|
-
|
|
206013
|
+
label: rendered.label,
|
|
206014
|
+
memory: rendered.memory,
|
|
206015
|
+
file: rendered.file,
|
|
206016
|
+
mode: rendered.mode,
|
|
206017
|
+
edges: rendered.edges.length > 0 ? rendered.edges : void 0,
|
|
206118
206018
|
changes,
|
|
206119
206019
|
next: nextField(next),
|
|
206120
|
-
|
|
206121
|
-
|
|
206122
|
-
costByModel
|
|
206123
|
-
} : {}
|
|
206020
|
+
cost: hasCost ? cost : void 0,
|
|
206021
|
+
costByModel: hasCost ? costByModel : void 0
|
|
206124
206022
|
};
|
|
206125
206023
|
};
|
|
206126
|
-
/** `--sh`'s shape for `BeatFields`: one entry per field, so a field added to `BeatFields` with no matching entry here is a compile error. */
|
|
206127
|
-
const BEAT_SH_SHAPE = {
|
|
206128
|
-
kind: "scalar",
|
|
206129
|
-
content: "scalar",
|
|
206130
|
-
idle: "bool",
|
|
206131
|
-
session: {
|
|
206132
|
-
id: "scalar",
|
|
206133
|
-
resume: "bool"
|
|
206134
|
-
},
|
|
206135
|
-
model: "scalar",
|
|
206136
|
-
system: "scalar",
|
|
206137
|
-
validate: "scalar",
|
|
206138
|
-
log: "scalar",
|
|
206139
|
-
state: "scalar",
|
|
206140
|
-
actor: "scalar",
|
|
206141
|
-
label: "scalar",
|
|
206142
|
-
memory: "scalar",
|
|
206143
|
-
file: "scalar",
|
|
206144
|
-
mode: "scalar",
|
|
206145
|
-
edges: "list",
|
|
206146
|
-
changes: "list",
|
|
206147
|
-
next: {
|
|
206148
|
-
pattern: "scalar",
|
|
206149
|
-
target: "scalar",
|
|
206150
|
-
action: "scalar"
|
|
206151
|
-
},
|
|
206152
|
-
cost: "scalar",
|
|
206153
|
-
costByModel: "list"
|
|
206154
|
-
};
|
|
206155
206024
|
const renderBeatJson = (fields) => JSON.stringify(fields) + "\n";
|
|
206156
|
-
const renderBeatSh = (fields) => renderShDocument("gtd", BEAT_SH_SHAPE, fields);
|
|
206157
206025
|
/**
|
|
206158
206026
|
* True when the per-model breakdown adds information beyond the `Cost:` total:
|
|
206159
206027
|
* more than one model, or a single model that carries an actual `--model` tag
|
|
@@ -206205,11 +206073,15 @@ const beatHeaderLines = (fields) => {
|
|
|
206205
206073
|
};
|
|
206206
206074
|
/**
|
|
206207
206075
|
* The self-validation instruction appended to a `prompt` beat's plain output
|
|
206208
|
-
* only — a structured (`--json
|
|
206076
|
+
* only — a structured (`--json`) driver runs the `validate` field
|
|
206209
206077
|
* itself instead. Advisory either way: `gtd land` refuses a turn whose
|
|
206210
206078
|
* steering file is invalid regardless of whether this was followed.
|
|
206211
206079
|
*/
|
|
206212
206080
|
const selfValidateInstruction = (command, file) => `\nBefore finishing your turn, run \`${command}\` — it checks ${file} — and fix every violation it reports until it exits cleanly. Do not finish while it still reports violations.\n`;
|
|
206081
|
+
/** `script`'s plain-output instruction line — a literal constant, never a template. */
|
|
206082
|
+
const SCRIPT_INSTRUCTION = "Run this script:";
|
|
206083
|
+
/** `capture`'s plain-output instruction line — a literal constant, never a template. */
|
|
206084
|
+
const CAPTURE_INSTRUCTION = "The edit is already made — run `gtd land` to land it.";
|
|
206213
206085
|
/**
|
|
206214
206086
|
* `gtd next`'s plain-text encoding. At `kind === "prompt"` the status header
|
|
206215
206087
|
* is suppressed and only `content` plus the self-validation instruction is
|
|
@@ -206222,7 +206094,10 @@ const selfValidateInstruction = (command, file) => `\nBefore finishing your turn
|
|
|
206222
206094
|
const renderBeatPlain = (fields, selfValidateCommand) => {
|
|
206223
206095
|
const content = fields.content.endsWith("\n") ? fields.content : fields.content + "\n";
|
|
206224
206096
|
if (fields.kind === "prompt") return selfValidateCommand !== void 0 && fields.file !== void 0 ? content + selfValidateInstruction(selfValidateCommand, fields.file) : content;
|
|
206225
|
-
|
|
206097
|
+
const body = beatHeaderLines(fields).join("\n") + "\n\n" + content;
|
|
206098
|
+
if (fields.kind === "script") return `${SCRIPT_INSTRUCTION}\n` + body;
|
|
206099
|
+
if (fields.kind === "capture") return `${CAPTURE_INSTRUCTION}\n` + body;
|
|
206100
|
+
return body;
|
|
206226
206101
|
};
|
|
206227
206102
|
//#endregion
|
|
206228
206103
|
//#region src/WorktreeState.ts
|
|
@@ -206269,72 +206144,86 @@ set -eu
|
|
|
206269
206144
|
|
|
206270
206145
|
beat=1
|
|
206271
206146
|
while :; do
|
|
206272
|
-
# One
|
|
206273
|
-
#
|
|
206274
|
-
#
|
|
206275
|
-
#
|
|
206276
|
-
|
|
206277
|
-
|
|
206278
|
-
out="$(gtd next --sh)"
|
|
206279
|
-
eval "$out"
|
|
206147
|
+
# One value per invocation: \`gtd next --json=<path>\` is a pure read (peek
|
|
206148
|
+
# or dispatch, indistinguishable, always safe to call again), so nothing is
|
|
206149
|
+
# lost by reading \`kind\`/\`idle\` as two separate calls rather than one
|
|
206150
|
+
# combined document.
|
|
206151
|
+
kind="$(gtd next --json=kind)"
|
|
206152
|
+
idle="$(gtd next --json=idle)"
|
|
206280
206153
|
|
|
206281
|
-
# \`
|
|
206282
|
-
#
|
|
206283
|
-
#
|
|
206154
|
+
# \`idle\` (true iff the initial state, clean tree) is the one shape that
|
|
206155
|
+
# means the process is genuinely done — EXCEPT on the run's opening beat:
|
|
206156
|
+
# land it anyway, so a workflow whose initial state declares its own
|
|
206284
206157
|
# clean-tree "C" pattern still gets a chance to fire.
|
|
206285
|
-
if [ "$beat" -gt 1 ] && [ "\${
|
|
206158
|
+
if [ "$beat" -gt 1 ] && [ "\${idle:-}" = true ]; then
|
|
206286
206159
|
gtd next
|
|
206287
206160
|
exit 0
|
|
206288
206161
|
fi
|
|
206289
206162
|
|
|
206290
|
-
case "$
|
|
206291
|
-
stalled)
|
|
206163
|
+
case "$kind" in
|
|
206164
|
+
stalled)
|
|
206165
|
+
gtd next --json=content >&2
|
|
206166
|
+
exit 1 ;;
|
|
206292
206167
|
# you re-ran us resting here: you either edited something or accepted by
|
|
206293
206168
|
# editing nothing, so land the opening beat either way. Later beats are
|
|
206294
206169
|
# gates we just produced and you have not read yet — hand off.
|
|
206295
206170
|
message) [ "$beat" = 1 ] || { gtd next; exit 0; } ;;
|
|
206296
206171
|
capture) ;; # the human already acted — just land it
|
|
206297
206172
|
script)
|
|
206298
|
-
|
|
206173
|
+
# A \`script\` rest's plain output is prose, not pipeable into \`sh\` — the
|
|
206174
|
+
# raw script comes from \`--json=content\` instead.
|
|
206175
|
+
content="$(gtd next --json=content)"
|
|
206176
|
+
log="$(gtd next --json=log)"
|
|
206177
|
+
sh -c "$content" >>"$log" 2>&1 || true ;;
|
|
206299
206178
|
prompt)
|
|
206300
|
-
|
|
206301
|
-
|
|
206302
|
-
|
|
206303
|
-
|
|
206304
|
-
|
|
206305
|
-
|
|
206306
|
-
|
|
206307
|
-
|
|
206308
|
-
|
|
206179
|
+
session_id="$(gtd next --json=session.id)"
|
|
206180
|
+
resume="$(gtd next --json=session.resume)"
|
|
206181
|
+
model="$(gtd next --json=model)"
|
|
206182
|
+
system="$(gtd next --json=system)"
|
|
206183
|
+
validate="$(gtd next --json=validate)"
|
|
206184
|
+
log="$(gtd next --json=log)"
|
|
206185
|
+
# The prompt embeds a full diff, so it goes to the agent over stdin,
|
|
206186
|
+
# never as an argv positional — argv is capped (~1 MB on macOS, and
|
|
206187
|
+
# POSIX guarantees only 4 KB, ARG_MAX), and a diff crosses that far
|
|
206188
|
+
# sooner than you'd expect. Piping plain \`gtd next\` (not
|
|
206189
|
+
# --json=content) means the diff is rendered once, for this input,
|
|
206190
|
+
# rather than twice.
|
|
206191
|
+
agent_turn() { gtd next | claude -p "$1" "$session_id" \\
|
|
206192
|
+
\${model:+--model "$model"} \\
|
|
206193
|
+
\${system:+--system-prompt "$system"} \\
|
|
206194
|
+
--dangerously-skip-permissions >>"$log" 2>&1; }
|
|
206195
|
+
if [ "\${resume:-}" = true ]
|
|
206309
206196
|
then agent_turn --resume || agent_turn --session-id
|
|
206310
206197
|
else agent_turn --session-id || agent_turn --resume
|
|
206311
206198
|
fi
|
|
206312
206199
|
n=0
|
|
206313
|
-
while [ -n "\${
|
|
206200
|
+
while [ -n "\${validate:-}" ] && ! fix="$(sh -c "$validate" 2>&1)"; do
|
|
206314
206201
|
n=$((n + 1)) && [ "$n" -gt 3 ] && { printf '%s\\n' "$fix" >&2; exit 1; }
|
|
206315
|
-
# $fix IS the fix prompt, verbatim — piped for the same reason
|
|
206316
|
-
#
|
|
206202
|
+
# $fix IS the fix prompt, verbatim — piped for the same reason the
|
|
206203
|
+
# agent's own turn above is. Whether \`claude --resume\` re-applies the
|
|
206317
206204
|
# original session's model/system prompt is a harness detail gtd
|
|
206318
206205
|
# cannot verify from outside, so this passes the identical
|
|
206319
|
-
# $
|
|
206206
|
+
# $model/$system on both calls rather than assume it does —
|
|
206320
206207
|
# otherwise this fix turn might silently fall back to Claude Code's
|
|
206321
206208
|
# own defaults while the turn that produced the file ran under the
|
|
206322
206209
|
# workflow's own model and persona.
|
|
206323
|
-
printf '%s' "$fix" | claude -p --resume "$
|
|
206324
|
-
\${
|
|
206325
|
-
\${
|
|
206326
|
-
--dangerously-skip-permissions >>"$
|
|
206210
|
+
printf '%s' "$fix" | claude -p --resume "$session_id" \\
|
|
206211
|
+
\${model:+--model "$model"} \\
|
|
206212
|
+
\${system:+--system-prompt "$system"} \\
|
|
206213
|
+
--dangerously-skip-permissions >>"$log" 2>&1
|
|
206327
206214
|
done ;;
|
|
206328
206215
|
esac
|
|
206329
206216
|
|
|
206330
|
-
#
|
|
206331
|
-
#
|
|
206332
|
-
#
|
|
206333
|
-
|
|
206334
|
-
|
|
206335
|
-
|
|
206336
|
-
|
|
206337
|
-
|
|
206217
|
+
# Read every value the landing needs BEFORE piping the script to \`sh\`:
|
|
206218
|
+
# \`gtd land\` itself never mutates — it only plans and prints — so
|
|
206219
|
+
# \`settled\`/\`idle\`/\`script\` planned against the same untouched tree all
|
|
206220
|
+
# agree, whatever order they're read in, as long as all three are read
|
|
206221
|
+
# before the script runs and changes the tree out from under them.
|
|
206222
|
+
settled="$(gtd land --json=settled)"
|
|
206223
|
+
idle="$(gtd land --json=idle)"
|
|
206224
|
+
gtd land --json=script | sh
|
|
206225
|
+
if [ "\${settled:-}" = true ]; then
|
|
206226
|
+
[ "\${idle:-}" = true ] && gtd next
|
|
206338
206227
|
exit 0
|
|
206339
206228
|
fi
|
|
206340
206229
|
beat=$((beat + 1))
|
|
@@ -206342,102 +206231,104 @@ done`;
|
|
|
206342
206231
|
const EDIT_COMMAND = `#!/usr/bin/env sh
|
|
206343
206232
|
set -eu
|
|
206344
206233
|
cd "$(git rev-parse --show-toplevel)"
|
|
206345
|
-
|
|
206346
|
-
|
|
206347
|
-
f="\${gtd_file:-.gtd/TODO.md}"
|
|
206234
|
+
f="$(gtd next --json=file)"
|
|
206235
|
+
f="\${f:-.gtd/TODO.md}"
|
|
206348
206236
|
mkdir -p "$(dirname "$f")"
|
|
206349
206237
|
exec "\${EDITOR:-vi}" "$f"`;
|
|
206350
|
-
const HEADER = () => `gtd ${GTD_VERSION$1} — driver protocol\n\ngtd decides and prints; the driver executes. gtd never runs git, never runs\nyour agent,
|
|
206238
|
+
const HEADER = () => `gtd ${GTD_VERSION$1} — driver protocol\n\ngtd decides and prints; the driver executes. gtd never runs git, never runs\nyour agent, never runs a check script, and never runs a steering mode's own\nformat:/validate: command either — that pair is text in the script gtd next\n--json's validate field (or gtd validate) prints, for the driver to run\nitself ahead of gtd land.\n`;
|
|
206351
206239
|
const BEAT_PROTOCOL = `
|
|
206352
206240
|
## The beat protocol
|
|
206353
206241
|
|
|
206354
206242
|
Two reads and one write, repeated: ask -> act -> land.
|
|
206355
206243
|
|
|
206356
|
-
gtd next --
|
|
206357
|
-
|
|
206358
|
-
|
|
206359
|
-
gtd
|
|
206360
|
-
|
|
206244
|
+
gtd next --json=<path> which kind is next, and any other field, one pure
|
|
206245
|
+
scalar read per value (kind, content, idle,
|
|
206246
|
+
session.id, model, validate, log, ...)
|
|
206247
|
+
gtd next what to run/show, in plain text — the SAME rest
|
|
206248
|
+
the --json=<path> reads above are peeking at
|
|
206249
|
+
gtd land --json=<path> how to record what happened, and whether anything
|
|
206250
|
+
is still owed (script to run, settled to check)
|
|
206361
206251
|
|
|
206362
206252
|
Every beat that acts, lands. Every beat that rests, exits.
|
|
206363
206253
|
|
|
206364
|
-
|
|
206365
|
-
(\`
|
|
206366
|
-
never stored, so looking is free: nothing distinguishes a peek
|
|
206367
|
-
dispatch, and calling either one twice in a row is always safe. There
|
|
206368
|
-
separate claiming form.
|
|
206254
|
+
Every \`gtd next --json=<path>\`/\`gtd land --json=<path>\` read is a pure PEEK —
|
|
206255
|
+
every field it reports (\`session.id\`/\`session.resume\` included) is DERIVED
|
|
206256
|
+
from history, never stored, so looking is free: nothing distinguishes a peek
|
|
206257
|
+
from a dispatch, and calling either one twice in a row is always safe. There
|
|
206258
|
+
is no separate claiming form.
|
|
206369
206259
|
|
|
206370
|
-
\`gtd next\` has \`--json
|
|
206371
|
-
|
|
206372
|
-
|
|
206373
|
-
|
|
206374
|
-
|
|
206375
|
-
|
|
206376
|
-
|
|
206377
|
-
|
|
206260
|
+
\`gtd next\` has one structured encoding, \`--json\`: bare \`--json\` prints the
|
|
206261
|
+
whole document, \`--json=<path>\` (a dotted key path, e.g. \`kind\`, \`content\`,
|
|
206262
|
+
\`session.id\`) prints just that one value — a scalar raw and unquoted, a
|
|
206263
|
+
boolean as \`true\`/\`false\`, a list one JSON entry per line. An absent
|
|
206264
|
+
optional field prints nothing and exits 0; an unknown path is a usage error
|
|
206265
|
+
(exit 2). An AI driver reads \`kind\`/\`session.id\` and the rest straight off
|
|
206266
|
+
\`--json=<path>\`, one call per value, without parsing anything — no JSON
|
|
206267
|
+
library, no \`jq\`; this briefing's own reference driver reads every field this
|
|
206268
|
+
way. Plain \`gtd next\` (no flag) is NOT a parsing surface: \`content\` is the
|
|
206269
|
+
bare step in every encoding; plain adds the self-validation instruction at a
|
|
206378
206270
|
validatable \`prompt\` rest, and adds a status-summary header at every kind
|
|
206379
206271
|
EXCEPT \`prompt\` (those bytes are the agent's own input, so no header is
|
|
206380
|
-
prefixed there). A driver that wants \`content\` programmatically reads it
|
|
206381
|
-
|
|
206382
|
-
text.
|
|
206272
|
+
prefixed there). A driver that wants \`content\` programmatically reads it off
|
|
206273
|
+
\`--json=content\`, never by scraping plain text.
|
|
206383
206274
|
|
|
206384
|
-
The \`kind\` field (
|
|
206275
|
+
The \`kind\` field (\`--json=kind\`) selects what to do:
|
|
206385
206276
|
|
|
206386
|
-
- \`stalled\` — print \`
|
|
206277
|
+
- \`stalled\` — print \`--json=content\` (the diagnosis, already in hand) to
|
|
206387
206278
|
stderr and exit non-zero. Terminal: another dispatch would just repeat the
|
|
206388
206279
|
same fruitless turn.
|
|
206389
206280
|
- \`message\` — print plain \`gtd next\`'s output (the richer status header,
|
|
206390
|
-
not bare \`
|
|
206391
|
-
|
|
206281
|
+
not bare \`content\`) and exit 0. This is a human gate. EXCEPT on the run's
|
|
206282
|
+
opening beat: the human invoked you while resting there, so land it
|
|
206392
206283
|
instead of printing. Some gates route a \`"C"\` (clean-tree) pattern
|
|
206393
206284
|
onward, making "change nothing" a real decision that looks identical to a
|
|
206394
206285
|
gate nobody has read yet; landing an opening beat at a gate without one is
|
|
206395
206286
|
a benign no-op, and it prints on the next beat.
|
|
206396
206287
|
- \`capture\` — a human gate the human already acted on (the tree is dirty).
|
|
206397
206288
|
Land it immediately, no display needed.
|
|
206398
|
-
- \`script\` — run \`
|
|
206399
|
-
|
|
206400
|
-
in the exit code.
|
|
206401
|
-
- \`prompt\` — send \`
|
|
206402
|
-
|
|
206403
|
-
|
|
206289
|
+
- \`script\` — run \`--json=content\` with \`sh -c\`, appending its output to
|
|
206290
|
+
\`--json=log\`, and ignore its exit code — the outcome lives in the tree,
|
|
206291
|
+
not in the exit code.
|
|
206292
|
+
- \`prompt\` — send plain \`gtd next\`'s own output to your agent CLI over
|
|
206293
|
+
stdin (it's the bare content at this kind, so no separate \`--json=content\`
|
|
206294
|
+
read is needed), using the accompanying \`session.id\`/\`session.resume\`/
|
|
206295
|
+
\`model\`/\`validate\` fields, each its own \`--json=<path>\` read (see
|
|
206296
|
+
below).
|
|
206404
206297
|
`;
|
|
206405
206298
|
const JSON_FIELD_REFERENCE = `
|
|
206406
206299
|
## Field reference
|
|
206407
206300
|
|
|
206408
|
-
\`gtd next\` and \`gtd land\` each carry one field set,
|
|
206409
|
-
|
|
206410
|
-
|
|
206411
|
-
There is no version field — the field set itself is the
|
|
206412
|
-
breaking change to it is a major release. Every field below
|
|
206413
|
-
present unless marked "when set" — omitted under \`--json\`
|
|
206414
|
-
|
|
206415
|
-
|
|
206301
|
+
\`gtd next\` and \`gtd land\` each carry one field set, read through \`--json\`
|
|
206302
|
+
(bare for the whole document, \`--json=<path>\` for one value) — this driver's
|
|
206303
|
+
own beats read fields one at a time via \`--json=<path>\` throughout (see the
|
|
206304
|
+
beat protocol above). There is no version field — the field set itself is the
|
|
206305
|
+
contract, and a breaking change to it is a major release. Every field below
|
|
206306
|
+
is always present unless marked "when set" — omitted under bare \`--json\`
|
|
206307
|
+
(never \`null\`); under \`--json=<path>\` an absent field prints nothing and
|
|
206308
|
+
exits 0, so read it in a shell variable and guard on emptiness
|
|
206309
|
+
(\`\${x:-}\`/\`\${x:+...}\`) rather than assuming it is always populated.
|
|
206416
206310
|
|
|
206417
206311
|
\`gtd next\` fields — Always: \`kind\` (\`capture\`|\`message\`|\`script\`|\`prompt\`|
|
|
206418
206312
|
\`stalled\`), \`content\` (the bare step in every encoding — plain \`gtd next\`
|
|
206419
206313
|
adds the self-validation instruction at a validatable \`prompt\` rest, and a
|
|
206420
206314
|
status-summary header at every OTHER kind; \`content\` itself never carries
|
|
206421
206315
|
either), \`idle\` (\`true\` iff the resolved rest is the workflow's initial
|
|
206422
|
-
state with a clean tree
|
|
206423
|
-
|
|
206424
|
-
|
|
206425
|
-
|
|
206426
|
-
|
|
206427
|
-
|
|
206428
|
-
|
|
206429
|
-
|
|
206430
|
-
|
|
206431
|
-
file
|
|
206432
|
-
\`
|
|
206433
|
-
recorded (a prior \`gtd land --cost=<n>\`): \`cost\`, \`costByModel\`.
|
|
206316
|
+
state with a clean tree), \`log\`, \`state\`, \`actor\`, \`changes\` (which
|
|
206317
|
+
declared \`on\` pattern, if any, each pending change matches), \`next\` (the
|
|
206318
|
+
first declared \`on\` edge that would fire right now — \`{action?, pattern,
|
|
206319
|
+
target}\` — or \`null\` on no match). Only at \`kind: "prompt"\` (the DISPATCH
|
|
206320
|
+
BLOCK — absent at every other kind, a \`stalled\` beat included, by
|
|
206321
|
+
construction): \`session.id\`/\`session.resume\` — both DERIVED from history,
|
|
206322
|
+
never stored, so a plain peek is exactly as safe to call as a dispatch would
|
|
206323
|
+
be — and, when the state declares a validatable steering file, \`validate\`
|
|
206324
|
+
(the script that formats then validates it). When set: \`model\`, \`memory\`,
|
|
206325
|
+
\`label\`, \`file\`, \`mode\`, \`edges\`. When a cost has been recorded (a prior
|
|
206326
|
+
\`gtd land --cost=<n>\`): \`cost\`, \`costByModel\`.
|
|
206434
206327
|
|
|
206435
|
-
\`gtd land\` fields, in fixed order: \`script\` (the POSIX sh to run
|
|
206436
|
-
|
|
206437
|
-
|
|
206438
|
-
state
|
|
206439
|
-
(true iff the state landing rests at is the workflow's initial state),
|
|
206440
|
-
\`state\`, \`subject\`, \`cost\`, \`model\` (the last three \`null\`, never
|
|
206328
|
+
\`gtd land\` fields, in fixed order: \`script\` (the POSIX sh to run), \`settled\`
|
|
206329
|
+
(true for a no-op at a \`script\` rest — stop immediately, nothing more to
|
|
206330
|
+
read), \`idle\` (true iff the state landing rests at is the workflow's initial
|
|
206331
|
+
state), \`state\`, \`subject\`, \`cost\`, \`model\` (the last three \`null\`, never
|
|
206441
206332
|
omitted, for a genuine no-op).
|
|
206442
206333
|
|
|
206443
206334
|
### The error envelope
|
|
@@ -206449,63 +206340,62 @@ stdout stays byte-empty: \`{"state":"error","prompt":"<message>"}\` rides
|
|
|
206449
206340
|
or defect, exit 2 for a usage error (nothing was even attempted). This is
|
|
206450
206341
|
true of every command, not only \`gtd next\` — an invocation that carries (or
|
|
206451
206342
|
misuses) \`--json\` still gets this envelope on its own failure. A driver
|
|
206452
|
-
|
|
206453
|
-
|
|
206343
|
+
reading stdout on a failed run reads nothing; read stderr or the exit code
|
|
206344
|
+
to learn why.
|
|
206454
206345
|
`;
|
|
206455
206346
|
const DRIVER_OBLIGATIONS = `
|
|
206456
206347
|
## Driver obligations, in order
|
|
206457
206348
|
|
|
206458
|
-
1. Read the resolved rest's \`kind\` off \`gtd next --
|
|
206459
|
-
|
|
206460
|
-
|
|
206461
|
-
|
|
206462
|
-
|
|
206463
|
-
|
|
206464
|
-
|
|
206465
|
-
|
|
206466
|
-
|
|
206467
|
-
|
|
206468
|
-
|
|
206469
|
-
|
|
206470
|
-
|
|
206471
|
-
|
|
206472
|
-
|
|
206473
|
-
|
|
206474
|
-
|
|
206475
|
-
|
|
206476
|
-
|
|
206477
|
-
|
|
206478
|
-
|
|
206479
|
-
|
|
206480
|
-
re-invocation
|
|
206481
|
-
|
|
206482
|
-
|
|
206483
|
-
|
|
206484
|
-
|
|
206485
|
-
|
|
206486
|
-
overrides its path.
|
|
206487
|
-
5. Map \`gtd_session_id\`/\`gtd_session_resume\` onto the agent CLI's own
|
|
206349
|
+
1. Read the resolved rest's \`kind\` off \`gtd next --json=kind\` once per
|
|
206350
|
+
iteration. Exit code is uniformly \`0\` on success now — it never says
|
|
206351
|
+
whose turn is next; only \`1\`/\`2\` means gtd itself failed. There is no
|
|
206352
|
+
opening move: a human's pending edit arrives as a \`kind: "capture"\` beat,
|
|
206353
|
+
which you land immediately without executing anything. EXCEPT: don't
|
|
206354
|
+
trust \`idle\` on the run's very first iteration — land that beat first, so
|
|
206355
|
+
a workflow whose initial state declares its own clean-tree \`"C"\` pattern
|
|
206356
|
+
still gets one chance to advance before you conclude nothing is owed.
|
|
206357
|
+
2. Read the content to run/show off \`--json=content\` — never off plain
|
|
206358
|
+
\`gtd next\`, which is not a parsing surface (it wraps \`content\` in a
|
|
206359
|
+
status-summary header at every kind but \`prompt\`; see the beat protocol
|
|
206360
|
+
above). At a \`prompt\` beat, though, pipe plain \`gtd next\`'s own output to
|
|
206361
|
+
the agent instead of reading \`--json=content\` separately — it IS the bare
|
|
206362
|
+
content at that kind, so a second read would only render the same diff
|
|
206363
|
+
twice. Whichever form you read, hand it to the agent CLI over STDIN, never
|
|
206364
|
+
as a command-line argument — argv is capped (roughly 1 MB on macOS, and
|
|
206365
|
+
POSIX guarantees only 4 KB, \`ARG_MAX\`), both reachable by an ordinary
|
|
206366
|
+
diff, and an argv-passing driver fails on the first large one in a way
|
|
206367
|
+
that looks like an agent error rather than a driver bug.
|
|
206368
|
+
3. A \`kind: "stalled"\` beat prints \`--json=content\` to stderr and halts with
|
|
206369
|
+
a non-zero exit; a \`kind: "message"\` beat prints plain \`gtd next\`'s
|
|
206370
|
+
output and exits 0, unless it is the run's opening beat — land that one,
|
|
206371
|
+
since the human's re-invocation while resting there is itself their
|
|
206372
|
+
decision.
|
|
206373
|
+
4. Run scripts with their output appended to \`--json=log\` — gtd never
|
|
206374
|
+
creates or truncates that file itself; truncate it once per run.
|
|
206375
|
+
\`$GTD_LOOP_LOG\` overrides its path.
|
|
206376
|
+
5. Map \`--json=session.id\`/\`--json=session.resume\` onto the agent CLI's own
|
|
206488
206377
|
session flags — try \`resume\`'s hinted flag first and fall back to the
|
|
206489
206378
|
other on failure (\`resume\` is a hint, not a contract: nothing is stored,
|
|
206490
206379
|
so a crashed prior turn or an expired agent session recovers by itself).
|
|
206491
|
-
6. After a \`prompt\` beat, run the same
|
|
206380
|
+
6. After a \`prompt\` beat, run the same beat's own \`--json=validate\` script
|
|
206492
206381
|
(when set) and re-prompt its output verbatim on failure — the DRIVER
|
|
206493
206382
|
owns the retry cap, not gtd.
|
|
206494
206383
|
7. Land only a beat you acted on (\`capture\`/\`script\`/\`prompt\`) — a stray
|
|
206495
206384
|
\`gtd land\` at a clean \`prompt\` rest authors an empty attempt on purpose
|
|
206496
206385
|
(that IS the stall bookkeeping), so don't land beats you didn't dispatch.
|
|
206497
|
-
8.
|
|
206498
|
-
|
|
206499
|
-
|
|
206500
|
-
|
|
206501
|
-
|
|
206502
|
-
|
|
206503
|
-
|
|
206504
|
-
|
|
206505
|
-
|
|
206506
|
-
|
|
206507
|
-
|
|
206508
|
-
|
|
206386
|
+
8. Read \`--json=settled\`/\`--json=idle\` from \`gtd land\` BEFORE running the
|
|
206387
|
+
script — \`gtd land\` never mutates, it only plans and prints, so these
|
|
206388
|
+
reads and the \`--json=script\` read that follows all agree, planned
|
|
206389
|
+
against the same untouched tree. Pipe \`--json=script\`'s own output into
|
|
206390
|
+
\`sh\` — never a bare \`gtd land | sh\`, which would hand an empty script to
|
|
206391
|
+
\`sh\` on a refusal instead of stopping first.
|
|
206392
|
+
9. Check the settled/idle values read in step 8 — no second read needed to
|
|
206393
|
+
decide. \`settled = true\` means stop right there (a no-op at a \`script\`
|
|
206394
|
+
rest settles in place; nothing more to read); otherwise keep looping, and
|
|
206395
|
+
the run finishes when a LATER \`gtd next --json=idle\` reports \`true\`
|
|
206396
|
+
(read once more with plain \`gtd next\` only to show that gate's message,
|
|
206397
|
+
the decision to stop already made). Exit code carries none of this any
|
|
206398
|
+
more — every command exits \`0\` on success uniformly.
|
|
206509
206399
|
`;
|
|
206510
206400
|
const RECOVERY = `
|
|
206511
206401
|
## Recovery
|
|
@@ -206513,14 +206403,14 @@ const RECOVERY = `
|
|
|
206513
206403
|
gtd exiting 2 means nothing was even attempted — a usage error; exiting 1
|
|
206514
206404
|
means a refusal — nothing was emitted either. An emitted script exiting
|
|
206515
206405
|
non-zero when YOU run it means something MAY have partially happened. Both
|
|
206516
|
-
recover the same way: re-invoke gtd (\`gtd next --
|
|
206517
|
-
\`gtd land --
|
|
206518
|
-
never a cached plan — and emits whatever still needs to happen
|
|
206519
|
-
This works because every emitted script asserts its own HEAD
|
|
206520
|
-
so a script generated against a repository state that has
|
|
206521
|
-
refuses loudly instead of corrupting anything. Scripts are
|
|
206522
|
-
assert their own HEAD precondition — a driver needs no retry
|
|
206523
|
-
"ask gtd again".
|
|
206406
|
+
recover the same way: re-invoke gtd (\`gtd next --json=kind\`, \`gtd next\`,
|
|
206407
|
+
then \`gtd land --json=<path>\`). It re-reads the real repository state fresh
|
|
206408
|
+
every time — never a cached plan — and emits whatever still needs to happen
|
|
206409
|
+
from there. This works because every emitted script asserts its own HEAD
|
|
206410
|
+
precondition, so a script generated against a repository state that has
|
|
206411
|
+
since moved refuses loudly instead of corrupting anything. Scripts are
|
|
206412
|
+
re-runnable and assert their own HEAD precondition — a driver needs no retry
|
|
206413
|
+
logic beyond "ask gtd again".
|
|
206524
206414
|
`;
|
|
206525
206415
|
const referenceImplementation = () => `
|
|
206526
206416
|
## Building the user's driver: interview first, then adapt
|
|
@@ -206549,19 +206439,19 @@ follow in the same numbered list.
|
|
|
206549
206439
|
committing it first turns the config into the very first thing the
|
|
206550
206440
|
process plans against. Commit it, THEN start driving.
|
|
206551
206441
|
4. **Which agent should run the turns?** Whatever coding-agent CLI they
|
|
206552
|
-
already use (default: \`claude\`). Map \`
|
|
206553
|
-
|
|
206554
|
-
|
|
206555
|
-
|
|
206442
|
+
already use (default: \`claude\`). Map \`session.id\`/\`session.resume\`
|
|
206443
|
+
onto THAT agent's continuation mechanism (obligation 5); an agent with
|
|
206444
|
+
no session concept just ignores them — memory is an optimization, every
|
|
206445
|
+
prompt is self-contained.
|
|
206556
206446
|
5. **Under which permission model?** Fully autonomous turns (e.g.
|
|
206557
206447
|
\`--dangerously-skip-permissions\`), a sandbox, or the agent's default
|
|
206558
206448
|
prompting — their risk tolerance, their call. Also whether the workflow's
|
|
206559
|
-
own \`
|
|
206449
|
+
own \`model\` hints should be honored (default: yes).
|
|
206560
206450
|
6. **How do they want to invoke it?** A command on PATH (default:
|
|
206561
206451
|
\`~/.local/bin/gtd-loop\`), a project task-runner entry, a CI job step —
|
|
206562
206452
|
or no artifact at all: YOU drive the beats yourself, following the
|
|
206563
206453
|
obligations directly. Pick the runtime to match: bash, their language of
|
|
206564
|
-
choice, anything that
|
|
206454
|
+
choice, anything that reads \`--json=<path>\` and spawns subprocesses.
|
|
206565
206455
|
7. **What should happen at the boundaries?** Where the log goes (obligation
|
|
206566
206456
|
4), and whether halting at a human gate should do anything richer than
|
|
206567
206457
|
print — desktop notification, terminal-multiplexer status, editor focus —
|
|
@@ -206573,13 +206463,13 @@ follow in the same numbered list.
|
|
|
206573
206463
|
(\`~/.local/bin/gtd-loop\`) — the interview can override either name.
|
|
206574
206464
|
|
|
206575
206465
|
Then build it, and verify safely before the first real drive:
|
|
206576
|
-
\`gtd next --
|
|
206577
|
-
your kind dispatch against the table above, call them as often as you
|
|
206578
|
-
Nothing happens until you run an emitted script.
|
|
206466
|
+
\`gtd next --json=<path>\`/\`gtd next\` are both pure reads — read/parse them,
|
|
206467
|
+
check your kind dispatch against the table above, call them as often as you
|
|
206468
|
+
like. Nothing happens until you run an emitted script.
|
|
206579
206469
|
|
|
206580
|
-
The reference rendering in sh (no \`jq\`, no JSON parser at all —
|
|
206581
|
-
|
|
206582
|
-
|
|
206470
|
+
The reference rendering in sh (no \`jq\`, no JSON parser at all — each
|
|
206471
|
+
\`--json=<path>\` call prints its one value directly; the \`claude\` lines are
|
|
206472
|
+
what answers 1–2 replace):
|
|
206583
206473
|
|
|
206584
206474
|
\`\`\`bash
|
|
206585
206475
|
${MINIMAL_DRIVER}
|
|
@@ -206593,15 +206483,16 @@ a second artifact to the same interview (questions 8–9 above): an edit
|
|
|
206593
206483
|
command that opens the steering file the process is resting at right now,
|
|
206594
206484
|
suggested at \`~/.local/bin/gtd-edit\`.
|
|
206595
206485
|
|
|
206596
|
-
\`gtd next --
|
|
206597
|
-
|
|
206598
|
-
|
|
206599
|
-
|
|
206600
|
-
|
|
206601
|
-
\`gtd
|
|
206602
|
-
|
|
206603
|
-
|
|
206604
|
-
|
|
206486
|
+
\`gtd next --json=file\` already reports the file for every state that
|
|
206487
|
+
declares one — the edit command needs no new engine surface, just a read and
|
|
206488
|
+
an editor spawn. \`file\` is an OPTIONAL field: \`--json=file\` prints nothing
|
|
206489
|
+
(not the string \`null\`) at a state that declares no \`file:\`, so read it into
|
|
206490
|
+
a shell variable and fall back explicitly — \`f="$(gtd next --json=file)"\`
|
|
206491
|
+
then \`f="\${f:-.gtd/TODO.md}"\`, never assume the read populated anything. This
|
|
206492
|
+
is also the general rule for every optional field \`--json=<path>\` exposes:
|
|
206493
|
+
an absent field is silence, not the string \`null\` a \`jq .field\` recipe would
|
|
206494
|
+
print on the same input — code that treats empty output as "field genuinely
|
|
206495
|
+
absent" is correct, code that expects a literal \`null\` string is not.
|
|
206605
206496
|
|
|
206606
206497
|
This is also how a human starts a process at all: at the initial \`idle\`
|
|
206607
206498
|
state the resting file IS \`.gtd/TODO.md\`, so on a clean repository the edit
|
|
@@ -206621,19 +206512,108 @@ const PREREQUISITES = `
|
|
|
206621
206512
|
## Prerequisites and portability
|
|
206622
206513
|
|
|
206623
206514
|
- A POSIX \`sh\` (dash, ash, bash's own POSIX mode, etc.) — gtd's own emitted
|
|
206624
|
-
scripts (\`gtd land\`, \`gtd --entry <state>\`, \`gtd abandon\`,
|
|
206515
|
+
scripts (\`gtd land --json=script\`, \`gtd --entry <state>\`, \`gtd abandon\`,
|
|
206625
206516
|
\`gtd restore\`) are POSIX sh; captured, then piped into it (see obligation
|
|
206626
|
-
8 above).
|
|
206627
|
-
nothing beyond the same POSIX \`sh
|
|
206517
|
+
8 above). Reading \`gtd next --json=<path>\`/\`gtd land --json=<path>\`'s own
|
|
206518
|
+
output needs nothing beyond the same POSIX \`sh\` — no \`eval\`, no parser.
|
|
206628
206519
|
- \`gtd\` on \`PATH\` — a seeded mode \`validate:\` command is literally the
|
|
206629
206520
|
string \`gtd check <mode> '<file>'\`, resolved by NAME at script-run time.
|
|
206630
206521
|
Keep one \`gtd\` on \`PATH\`, consistently.
|
|
206631
206522
|
|
|
206632
|
-
Any runtime works: the contract is \`--json\`/\`--
|
|
206523
|
+
Any runtime works: the contract is \`--json\`/\`--json=<path>\` in, subprocesses
|
|
206524
|
+
out.
|
|
206633
206525
|
`;
|
|
206634
206526
|
const renderBriefing = () => HEADER() + BEAT_PROTOCOL + JSON_FIELD_REFERENCE + DRIVER_OBLIGATIONS + RECOVERY + referenceImplementation() + editCommand() + PREREQUISITES;
|
|
206635
206527
|
//#endregion
|
|
206528
|
+
//#region src/Select.ts
|
|
206529
|
+
/**
|
|
206530
|
+
* Renders a fully-walked leaf value: scalars/booleans stringify directly,
|
|
206531
|
+
* arrays become one `JSON.stringify` per entry, newline-joined.
|
|
206532
|
+
* `selectPath`'s loop already returns `absent` for `undefined`/`null` after
|
|
206533
|
+
* every segment (including the last), so neither ever reaches here.
|
|
206534
|
+
*/
|
|
206535
|
+
const toSelection = (value) => {
|
|
206536
|
+
if (Array.isArray(value)) return {
|
|
206537
|
+
kind: "value",
|
|
206538
|
+
text: value.map((entry) => JSON.stringify(entry)).join("\n")
|
|
206539
|
+
};
|
|
206540
|
+
if (typeof value === "object") return {
|
|
206541
|
+
kind: "value",
|
|
206542
|
+
text: JSON.stringify(value)
|
|
206543
|
+
};
|
|
206544
|
+
return {
|
|
206545
|
+
kind: "value",
|
|
206546
|
+
text: String(value)
|
|
206547
|
+
};
|
|
206548
|
+
};
|
|
206549
|
+
/** A segment that resolves to no key at all (missing from an object, or numeric/absent against an array) — distinct from a present key holding `undefined`. */
|
|
206550
|
+
const NOT_FOUND = Symbol("not-found");
|
|
206551
|
+
/**
|
|
206552
|
+
* One step of the walk: looks `segment` up on `current`, or reports
|
|
206553
|
+
* `NOT_FOUND` for a key that was never there (no array indexing; a primitive
|
|
206554
|
+
* has no keys). Presence is an OWN-property test
|
|
206555
|
+
* (`Object.prototype.hasOwnProperty`), never the `in` operator — `in` walks
|
|
206556
|
+
* the prototype chain, so it would resolve inherited members
|
|
206557
|
+
* (`constructor`, `toString`, `hasOwnProperty`, `valueOf`, ...) as real
|
|
206558
|
+
* document fields. An array declares no non-index own key the document ever
|
|
206559
|
+
* uses, so every array segment is `NOT_FOUND` (all-digit ones already were;
|
|
206560
|
+
* this also now excludes `length`/`map`/every other inherited array member).
|
|
206561
|
+
*/
|
|
206562
|
+
const resolveSegment = (current, segment) => {
|
|
206563
|
+
if (Array.isArray(current)) return NOT_FOUND;
|
|
206564
|
+
if (typeof current === "object" && current !== null) {
|
|
206565
|
+
const record = current;
|
|
206566
|
+
return Object.prototype.hasOwnProperty.call(record, segment) ? record[segment] : NOT_FOUND;
|
|
206567
|
+
}
|
|
206568
|
+
return NOT_FOUND;
|
|
206569
|
+
};
|
|
206570
|
+
/**
|
|
206571
|
+
* Walks `fields` by dotted key path. Never throws — any unwalkable shape
|
|
206572
|
+
* (a primitive mid-path, an array indexed by a numeric segment) degrades to
|
|
206573
|
+
* `unknown`, and a present-but-`undefined` value anywhere along the path
|
|
206574
|
+
* short-circuits the whole remaining path to `absent` rather than reporting
|
|
206575
|
+
* a key that was never actually missing.
|
|
206576
|
+
*/
|
|
206577
|
+
const selectPath = (fields, path) => {
|
|
206578
|
+
try {
|
|
206579
|
+
let current = fields;
|
|
206580
|
+
for (const segment of path.split(".")) {
|
|
206581
|
+
current = resolveSegment(current, segment);
|
|
206582
|
+
if (current === NOT_FOUND) return {
|
|
206583
|
+
kind: "unknown",
|
|
206584
|
+
path
|
|
206585
|
+
};
|
|
206586
|
+
if (current === void 0 || current === null) return { kind: "absent" };
|
|
206587
|
+
}
|
|
206588
|
+
return toSelection(current);
|
|
206589
|
+
} catch {
|
|
206590
|
+
return {
|
|
206591
|
+
kind: "unknown",
|
|
206592
|
+
path
|
|
206593
|
+
};
|
|
206594
|
+
}
|
|
206595
|
+
};
|
|
206596
|
+
//#endregion
|
|
206636
206597
|
//#region src/program.ts
|
|
206598
|
+
/**
|
|
206599
|
+
* A command-level failure that should exit like a CLI usage error (2), not a
|
|
206600
|
+
* generic runtime failure (1) — currently only the `--json=<unknown-path>`
|
|
206601
|
+
* selector case.
|
|
206602
|
+
*/
|
|
206603
|
+
var SelectorUsageError = class extends Error {};
|
|
206604
|
+
/**
|
|
206605
|
+
* The `--json=<path>` select branch, shared by `runNextCommand` and
|
|
206606
|
+
* `runLandCommand` so the two never drift on the unknown-selector message: a
|
|
206607
|
+
* `value` writes its text plus exactly one trailing newline, `absent` writes
|
|
206608
|
+
* nothing (the caller's normal success path continues), and `unknown` fails
|
|
206609
|
+
* with `SelectorUsageError` (mapped to `EXIT_USAGE_ERROR` by `Cli.ts`'s
|
|
206610
|
+
* `report`).
|
|
206611
|
+
*/
|
|
206612
|
+
const writeSelection = (out, fields, path) => gen(function* () {
|
|
206613
|
+
const selection = selectPath(fields, path);
|
|
206614
|
+
if (selection.kind === "value") out.write(`${selection.text}\n`);
|
|
206615
|
+
else if (selection.kind === "unknown") return yield* fail$2(new SelectorUsageError(`gtd: unknown --json selector "${selection.path}" — see \`gtd --help\``));
|
|
206616
|
+
});
|
|
206637
206617
|
/** `needs: "none"` skips the repo-root guard — the server is keyed on file name, not workflow state. */
|
|
206638
206618
|
const runLspCommand = () => startLspServer();
|
|
206639
206619
|
/** Emitted by the binary itself (`src/Install.ts`'s `renderBriefing()`) so it's always current. Writes nothing else; runs from any directory since it resolves no workflow state. */
|
|
@@ -206670,49 +206650,22 @@ const runInitCommand = (out) => gen(function* () {
|
|
|
206670
206650
|
* `normalizeTrailingNewline` rather than imported, to avoid a circular value
|
|
206671
206651
|
* dependency (`Cli.ts` already depends on this module). `LandResult.script`
|
|
206672
206652
|
* must carry its own trailing newline because it's embedded verbatim inside
|
|
206673
|
-
* the `--json
|
|
206653
|
+
* the `--json` document, never re-normalized at its tail.
|
|
206674
206654
|
*/
|
|
206675
206655
|
const normalizeScriptNewline = (script) => script.length === 0 ? script : `${script.replace(/\n+$/, "")}\n`;
|
|
206676
206656
|
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
206677
206657
|
const headPreconditions = (currentCommit) => ({ expectedHead: currentCommit });
|
|
206678
|
-
/**
|
|
206679
|
-
* The resting state's own steering-mode format/validate commands, embedded
|
|
206680
|
-
* ahead of the commit. Empty for a state declaring no `file:`+`mode:` pair;
|
|
206681
|
-
* an unknown mode name is a refusal.
|
|
206682
|
-
*
|
|
206683
|
-
* Also empty when the step deletes that file, or when it's absent from the
|
|
206684
|
-
* working tree — either way there's nothing left to format/validate.
|
|
206685
|
-
* Emitting `format:` anyway would make the step unlandable for a formatter
|
|
206686
|
-
* that errors on a missing path (e.g. `prettier --write`), since it's the
|
|
206687
|
-
* first command in a `set -euo pipefail` script and would abort the whole
|
|
206688
|
-
* thing — window close, commit and all — before anything could land.
|
|
206689
|
-
*/
|
|
206690
|
-
const steeringModeSteps = (rest) => gen(function* () {
|
|
206691
|
-
const file = rest.hints.file;
|
|
206692
|
-
const mode = rest.stateDef.mode;
|
|
206693
|
-
if (file === void 0 || mode === void 0) return [];
|
|
206694
|
-
if (deletesFile(rest.changes, file)) return [];
|
|
206695
|
-
if ((yield* RepoFiles).working(file) === void 0) return [];
|
|
206696
|
-
const resolved = resolveSteeringMode(rest.def, mode);
|
|
206697
|
-
if (resolved === void 0) return yield* fail$2(new Error(unknownModeMessage(rest.def, rest.state, mode)));
|
|
206698
|
-
return yield* renderSteeringModeCommandSteps(resolved, file, rest.context);
|
|
206699
|
-
});
|
|
206700
|
-
/** True for a `"commit"` decision that is an ATTEMPT (`PatternMachine.StepCommit.attempt`) — the one flag both `enforceStepGuards` and `buildRequiredScript` bypass their own steps for (see each call site). */
|
|
206658
|
+
/** True for a `"commit"` decision that is an ATTEMPT (`PatternMachine.StepCommit.attempt`) — the one flag `enforceStepGuards` bypasses its own steps for (see its call site). */
|
|
206701
206659
|
const isAttemptDecision = (decision) => decision.kind === "commit" && decision.attempt === true;
|
|
206702
206660
|
/** The subject the required script will produce. */
|
|
206703
206661
|
const previewSubject = (decision) => succeed$3(decision.subject);
|
|
206704
206662
|
/**
|
|
206705
|
-
* The `required` half: the
|
|
206706
|
-
*
|
|
206707
|
-
*
|
|
206708
|
-
*
|
|
206709
|
-
* breaking `stalledAt`'s derivation.
|
|
206663
|
+
* The `required` half: the HEAD assertion plus the commit steps, nothing
|
|
206664
|
+
* else. The steering-file format/validate pair is no longer part of the
|
|
206665
|
+
* landing script — that's a driver contract now (`gtd next --json`'s
|
|
206666
|
+
* `validate` field), not a gtd guarantee (see `resolveSelfValidateCommand`).
|
|
206710
206667
|
*/
|
|
206711
|
-
const buildRequiredScript = (rest, decision, cost, model) =>
|
|
206712
|
-
const git = yield* GitService;
|
|
206713
|
-
const steps = [...isAttemptDecision(decision) ? [] : yield* steeringModeSteps(rest), ...yield* renderDecision(git, rest, decision, cost, model)];
|
|
206714
|
-
return emitScripts(headPreconditions(rest.context.currentCommit), steps).required;
|
|
206715
|
-
});
|
|
206668
|
+
const buildRequiredScript = (rest, decision, cost, model) => succeed$3(emitScripts(headPreconditions(rest.context.currentCommit), renderDecision(rest, decision, cost, model)).required);
|
|
206716
206669
|
/**
|
|
206717
206670
|
* Decide the one resulting transition (a commit) for `gtd land`
|
|
206718
206671
|
* without performing it, authenticating as `rest.actor`. Refusals fail the
|
|
@@ -206749,7 +206702,6 @@ const planLanding = (opts = {}) => gen(function* () {
|
|
|
206749
206702
|
attempt: isAttemptDecision(decision)
|
|
206750
206703
|
});
|
|
206751
206704
|
const restingState = decision.to;
|
|
206752
|
-
const settled = yield* collapsesToInitialState(rest, decision);
|
|
206753
206705
|
const required = yield* buildRequiredScript(rest, decision, opts.cost, opts.model);
|
|
206754
206706
|
return {
|
|
206755
206707
|
state: restingState,
|
|
@@ -206757,7 +206709,7 @@ const planLanding = (opts = {}) => gen(function* () {
|
|
|
206757
206709
|
cost: opts.cost ?? null,
|
|
206758
206710
|
model: opts.model ?? null,
|
|
206759
206711
|
script: normalizeScriptNewline(combinedScript(required, "")),
|
|
206760
|
-
settled,
|
|
206712
|
+
settled: false,
|
|
206761
206713
|
idle: restingState === initialStateOf(rest.def)
|
|
206762
206714
|
};
|
|
206763
206715
|
});
|
|
@@ -206796,16 +206748,19 @@ const runBaseCommand = (out) => gen(function* () {
|
|
|
206796
206748
|
/**
|
|
206797
206749
|
* `gtd land [--cost=<n>] [--model=<name>]`: land whatever the tree shows at
|
|
206798
206750
|
* the currently resolved rest, authenticated as the rest's own actor.
|
|
206799
|
-
* Plain output
|
|
206800
|
-
*
|
|
206801
|
-
*
|
|
206802
|
-
*
|
|
206803
|
-
|
|
206804
|
-
|
|
206805
|
-
|
|
206806
|
-
|
|
206807
|
-
|
|
206808
|
-
|
|
206751
|
+
* Plain output is `renderLandPlain`'s prose — the commit subject plus a
|
|
206752
|
+
* pointer at `--json=script`, or the no-op note when nothing landed — never
|
|
206753
|
+
* the script itself; `--json` emits `script` (byte-identical to before)
|
|
206754
|
+
* alongside `settled`/`idle`/`state`/`subject`/`cost`/`model`, so it stays
|
|
206755
|
+
* the machine path a driver pipes to `sh`. Exit code is uniformly `EXIT_OK`
|
|
206756
|
+
* on success — whose turn is next lives in the following `gtd next --json`'s
|
|
206757
|
+
* `kind` field.
|
|
206758
|
+
*/
|
|
206759
|
+
const runLandCommand = (opts, json, out) => gen(function* () {
|
|
206760
|
+
const built = landFields(yield* planLanding(opts));
|
|
206761
|
+
if (json.kind === "document") out.write(renderLandJson(built));
|
|
206762
|
+
else if (json.kind === "select") yield* writeSelection(out, built, json.path);
|
|
206763
|
+
else out.write(renderLandPlain(built));
|
|
206809
206764
|
});
|
|
206810
206765
|
/**
|
|
206811
206766
|
* `gtd --entry <state> [--var <name>=<value> ...]` (`actor` always `"human"`):
|
|
@@ -206887,8 +206842,7 @@ const runAbandonCommand = (out) => gen(function* () {
|
|
|
206887
206842
|
out.write(combinedScript(required, ""));
|
|
206888
206843
|
});
|
|
206889
206844
|
/**
|
|
206890
|
-
* `gtd restore`: hard-reset HEAD back to the tip `gtd abandon`
|
|
206891
|
-
* initial-state collapse's own mixed reset) retained.
|
|
206845
|
+
* `gtd restore`: hard-reset HEAD back to the tip `gtd abandon` retained.
|
|
206892
206846
|
*
|
|
206893
206847
|
* Guarded by `restorability` so it never discards work it didn't create:
|
|
206894
206848
|
* refuses on a dirty working tree, no retained history, or HEAD having
|
|
@@ -206950,9 +206904,8 @@ const emitsValidatablePrompt = (rendered) => rendered.kind === "prompt" && rende
|
|
|
206950
206904
|
/**
|
|
206951
206905
|
* Render `resolved`'s format:/validate: commands as `EmitStep[]`, wrapping
|
|
206952
206906
|
* the last one with `onFailure: fixPromptInstruction(file)` when the
|
|
206953
|
-
* validator is a command —
|
|
206954
|
-
*
|
|
206955
|
-
* routable fix prompt.
|
|
206907
|
+
* validator is a command — `resolveValidateScript`'s own helper, split out
|
|
206908
|
+
* so its render logic isn't inlined into that function's body.
|
|
206956
206909
|
*/
|
|
206957
206910
|
const renderSteeringModeCommandSteps = (resolved, file, context) => gen(function* () {
|
|
206958
206911
|
const commands = yield* renderSteeringCommands(resolved, file, context);
|
|
@@ -207066,20 +207019,20 @@ const restBeatKind = (rest) => beatKindOf({
|
|
|
207066
207019
|
/** `idle` means exactly one thing: the machine rests at the workflow's initial state with a clean tree — the process is genuinely done. */
|
|
207067
207020
|
const restIsIdle = (rest) => rest.state === initialStateOf(rest.def) && rest.changes.length === 0;
|
|
207068
207021
|
/**
|
|
207069
|
-
* `gtd next`: pure emitter of the resolved rest's beat, in
|
|
207070
|
-
* `--json
|
|
207022
|
+
* `gtd next`: pure emitter of the resolved rest's beat, in two encodings —
|
|
207023
|
+
* `--json` and plain (the default). No mutation: nothing is written,
|
|
207071
207024
|
* so a peek and a would-be dispatch are the same call. Exit code is
|
|
207072
207025
|
* `EXIT_OK` unconditionally — whose turn is next lives in the beat
|
|
207073
207026
|
* document's own `kind` field, never the exit code.
|
|
207074
207027
|
*/
|
|
207075
|
-
const runNextCommand = (json,
|
|
207028
|
+
const runNextCommand = (json, out) => gen(function* () {
|
|
207076
207029
|
const rest = yield* currentRest;
|
|
207077
207030
|
const rendered = yield* renderRest(rest);
|
|
207078
207031
|
const fields = yield* gatherBeatFields(rest, rendered);
|
|
207079
207032
|
const narrator = yield* Narrator;
|
|
207080
207033
|
for (const change of fields.changes) yield* narrator.narrate(`pending: ${change.status} ${change.path} -> ${change.pattern ?? "(no match)"}`);
|
|
207081
|
-
if (json) out.write(renderBeatJson(fields));
|
|
207082
|
-
else if (
|
|
207034
|
+
if (json.kind === "document") out.write(renderBeatJson(fields));
|
|
207035
|
+
else if (json.kind === "select") yield* writeSelection(out, fields, json.path);
|
|
207083
207036
|
else {
|
|
207084
207037
|
const selfValidateCommand = emitsValidatablePrompt(rendered) ? yield* resolveSelfValidateCommand(rest.def, rendered.mode, rendered.file, rest.context).pipe(catchAll(() => succeed$3(void 0))) : void 0;
|
|
207085
207038
|
out.write(renderBeatPlain(fields, selfValidateCommand));
|
|
@@ -207183,7 +207136,7 @@ const computeNextMatch = (onEdges, changes) => {
|
|
|
207183
207136
|
}
|
|
207184
207137
|
return null;
|
|
207185
207138
|
};
|
|
207186
|
-
/** Everything one beat needs beyond the resolved rest itself, gathered once so plain/`--json
|
|
207139
|
+
/** Everything one beat needs beyond the resolved rest itself, gathered once so plain/`--json` can never describe different rests for the same beat. */
|
|
207187
207140
|
const gatherBeatFields = (rest, rendered) => gen(function* () {
|
|
207188
207141
|
const kind = restBeatKind(rest);
|
|
207189
207142
|
const session = kind === "prompt" ? resolveSession(rendered.memory, rendered.memoryResumed) : void 0;
|
|
@@ -207299,7 +207252,7 @@ const needsOf = (kind) => {
|
|
|
207299
207252
|
* unrepresentable: every branch returns `Effect<void>`, and `Cli.ts`'s
|
|
207300
207253
|
* `runCli` supplies `EXIT_OK` once, after this Effect succeeds.
|
|
207301
207254
|
*/
|
|
207302
|
-
const dispatchVoidCommand = (command, json,
|
|
207255
|
+
const dispatchVoidCommand = (command, json, out) => {
|
|
207303
207256
|
switch (command.kind) {
|
|
207304
207257
|
case "lsp": return runLspCommand();
|
|
207305
207258
|
case "init": return runInitCommand(out);
|
|
@@ -207307,11 +207260,11 @@ const dispatchVoidCommand = (command, json, sh, out) => {
|
|
|
207307
207260
|
case "land": return runLandCommand({
|
|
207308
207261
|
...command.cost !== void 0 ? { cost: command.cost } : {},
|
|
207309
207262
|
...command.model !== void 0 ? { model: command.model } : {}
|
|
207310
|
-
}, json,
|
|
207263
|
+
}, json, out);
|
|
207311
207264
|
case "entry": return runEntryCommand(command.actor, command.state, command.vars, out, command.label);
|
|
207312
207265
|
case "abandon": return runAbandonCommand(out);
|
|
207313
207266
|
case "restore": return runRestoreCommand(out);
|
|
207314
|
-
case "next": return runNextCommand(json,
|
|
207267
|
+
case "next": return runNextCommand(json, out);
|
|
207315
207268
|
case "validate": return runValidateCommand(out);
|
|
207316
207269
|
case "check": return runCheckCommand(command.mode, command.file, command.openQuestions ?? false);
|
|
207317
207270
|
case "install": return runInstallCommand(out);
|
|
@@ -207326,14 +207279,20 @@ const dispatchVoidCommand = (command, json, sh, out) => {
|
|
|
207326
207279
|
* (the `standaloneKinds` run bare). Both guard checks run before `dispatch`,
|
|
207327
207280
|
* so a refusal writes nothing and emits no script by construction.
|
|
207328
207281
|
*/
|
|
207329
|
-
const runCommand = (command, json,
|
|
207330
|
-
const dispatch = dispatchVoidCommand(command, json,
|
|
207282
|
+
const runCommand = (command, json, out) => {
|
|
207283
|
+
const dispatch = dispatchVoidCommand(command, json, out);
|
|
207331
207284
|
if (needsOf(command.kind) !== "state") return dispatch;
|
|
207332
207285
|
return gen(function* () {
|
|
207333
207286
|
const git = yield* GitService;
|
|
207334
207287
|
const fs = yield* FileSystem;
|
|
207335
207288
|
yield* assertRunningFromRepoRoot(git, fs);
|
|
207336
207289
|
yield* assertRepositoryHasCommits(git);
|
|
207290
|
+
const narrator = yield* Narrator;
|
|
207291
|
+
const config = yield* (yield* ConfigService).load.pipe(provideService(Narrator, {
|
|
207292
|
+
narrate: () => _void,
|
|
207293
|
+
warn: () => _void
|
|
207294
|
+
}));
|
|
207295
|
+
for (const warning of config.warnings) yield* narrator.warn(`gtd: warning: ${warning}`);
|
|
207337
207296
|
yield* dispatch;
|
|
207338
207297
|
});
|
|
207339
207298
|
};
|
|
@@ -207348,29 +207307,24 @@ const nonNegativeNumber = (raw, flag) => {
|
|
|
207348
207307
|
const FLAGS = [
|
|
207349
207308
|
{
|
|
207350
207309
|
name: "--json",
|
|
207351
|
-
arity:
|
|
207310
|
+
arity: "optional",
|
|
207352
207311
|
repeatable: false,
|
|
207353
207312
|
scope: (kind) => kind === "next" || kind === "land",
|
|
207354
|
-
decode: () => right(
|
|
207313
|
+
decode: ([raw]) => right(raw ?? ""),
|
|
207355
207314
|
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
207356
|
-
valueHint: "",
|
|
207357
|
-
help: ["(gtd next/gtd land only) output structured JSON", "instead of plain text. Mutually exclusive with --sh"],
|
|
207358
|
-
conflicts: ["--sh"]
|
|
207359
|
-
},
|
|
207360
|
-
{
|
|
207361
|
-
name: "--sh",
|
|
207362
|
-
arity: 0,
|
|
207363
|
-
repeatable: false,
|
|
207364
|
-
scope: (kind) => kind === "next" || kind === "land",
|
|
207365
|
-
decode: () => right(true),
|
|
207366
|
-
scopeError: "gtd: --sh is only valid for `gtd next`/`gtd land` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
207367
|
-
valueHint: "",
|
|
207315
|
+
valueHint: "<path>",
|
|
207368
207316
|
help: [
|
|
207369
|
-
"(gtd next/gtd land only) output
|
|
207370
|
-
"
|
|
207371
|
-
"
|
|
207372
|
-
|
|
207373
|
-
|
|
207317
|
+
"(gtd next/gtd land only) output structured JSON. Bare",
|
|
207318
|
+
"--json prints the whole document; --json=<path> (a dotted",
|
|
207319
|
+
"key path into that document, e.g. kind, content,",
|
|
207320
|
+
"session.id) prints just that value: a scalar raw and",
|
|
207321
|
+
"unquoted, a boolean as true/false, a list one JSON entry",
|
|
207322
|
+
"per line. An absent optional field prints nothing and",
|
|
207323
|
+
"exits 0 — including when an earlier segment of <path> is",
|
|
207324
|
+
"itself absent/null (e.g. session.id at a non-prompt rest),",
|
|
207325
|
+
"which never counts as unknown; an unknown path is a usage",
|
|
207326
|
+
"error (exit 2)."
|
|
207327
|
+
]
|
|
207374
207328
|
},
|
|
207375
207329
|
{
|
|
207376
207330
|
name: "--port",
|
|
@@ -207526,13 +207480,15 @@ const COMMAND_ROWS = [
|
|
|
207526
207480
|
"--cost=<n> (optionally --model=<name>) to record the",
|
|
207527
207481
|
"just-finished invocation's token cost and model on the",
|
|
207528
207482
|
"turn commit (summed into it.processCost/",
|
|
207529
|
-
"processCostByModel). Plain (the default) prints
|
|
207530
|
-
"
|
|
207531
|
-
"`gtd land
|
|
207532
|
-
"
|
|
207533
|
-
"
|
|
207534
|
-
"
|
|
207535
|
-
"
|
|
207483
|
+
"processCostByModel). Plain (the default) prints one",
|
|
207484
|
+
"human-readable sentence naming the commit subject (or a",
|
|
207485
|
+
"no-op note) plus a pointer to `gtd land --json=script | sh`",
|
|
207486
|
+
"to get the landing script — never the script itself.",
|
|
207487
|
+
"--json emits script (the actual POSIX sh",
|
|
207488
|
+
"a driver runs) alongside settled, idle, state (the",
|
|
207489
|
+
"post-land target), subject, cost and model. Exits 0 on",
|
|
207490
|
+
"success, 1 on any refusal — see the Exit codes section",
|
|
207491
|
+
"below"
|
|
207536
207492
|
]
|
|
207537
207493
|
},
|
|
207538
207494
|
{
|
|
@@ -207566,19 +207522,22 @@ const COMMAND_ROWS = [
|
|
|
207566
207522
|
details: [
|
|
207567
207523
|
"Print the resolved rest's beat (no mutation, safe to",
|
|
207568
207524
|
"poll), in one of three encodings. Plain (the default): a",
|
|
207569
|
-
"status summary, a blank line, then the step verbatim —",
|
|
207570
|
-
"
|
|
207571
|
-
"
|
|
207572
|
-
"
|
|
207573
|
-
"
|
|
207574
|
-
"
|
|
207575
|
-
"
|
|
207576
|
-
"
|
|
207577
|
-
"
|
|
207578
|
-
"
|
|
207579
|
-
"
|
|
207580
|
-
"
|
|
207581
|
-
"
|
|
207525
|
+
"status summary, a blank line, then the step verbatim — at a",
|
|
207526
|
+
"script/capture rest an instruction line ('Run this",
|
|
207527
|
+
"script:' / 'The edit is already made — run `gtd land` to",
|
|
207528
|
+
"land it.') precedes the status summary; at a prompt rest",
|
|
207529
|
+
"it's the bare step (plus the self-validation instruction",
|
|
207530
|
+
"when applicable) with no header at all, since those bytes",
|
|
207531
|
+
"are the agent's own input. --json emits the one structured",
|
|
207532
|
+
"surface gtd has: kind (capture|message|script|prompt|",
|
|
207533
|
+
"stalled) selects what a driver does, content is what it",
|
|
207534
|
+
"runs or shows, idle marks the workflow's initial state with",
|
|
207535
|
+
"a clean tree, plus the prompt session, model, validate",
|
|
207536
|
+
"script, log path, changes, next and the resting state's own",
|
|
207537
|
+
"fields. --json=<path> (a dotted key path into that same",
|
|
207538
|
+
"document, e.g. kind, content, session.id) prints just that",
|
|
207539
|
+
"value instead of the whole document — see --json's own help",
|
|
207540
|
+
"above. Exits 0 — see the Exit codes section below"
|
|
207582
207541
|
]
|
|
207583
207542
|
},
|
|
207584
207543
|
{
|
|
@@ -207737,6 +207696,7 @@ const tokenize = (tail) => {
|
|
|
207737
207696
|
jsonSeen
|
|
207738
207697
|
};
|
|
207739
207698
|
const eq = tok.indexOf("=");
|
|
207699
|
+
const alreadyPresent = present.has(row.name);
|
|
207740
207700
|
present.add(row.name);
|
|
207741
207701
|
if (row.name === "--json") jsonSeen = true;
|
|
207742
207702
|
if (row.arity === 0) {
|
|
@@ -207748,6 +207708,25 @@ const tokenize = (tail) => {
|
|
|
207748
207708
|
if (!byFlag.has(row.name)) byFlag.set(row.name, []);
|
|
207749
207709
|
continue;
|
|
207750
207710
|
}
|
|
207711
|
+
if (row.arity === "optional") {
|
|
207712
|
+
if (!row.repeatable && alreadyPresent) return {
|
|
207713
|
+
kind: "error",
|
|
207714
|
+
message: `gtd: ${row.name} may be given at most once`,
|
|
207715
|
+
jsonSeen
|
|
207716
|
+
};
|
|
207717
|
+
if (eq !== -1) {
|
|
207718
|
+
const value = tok.slice(eq + 1);
|
|
207719
|
+
if (value === "") return {
|
|
207720
|
+
kind: "error",
|
|
207721
|
+
message: `gtd: ${row.name} requires a value — use ${row.name}=${row.valueHint}`,
|
|
207722
|
+
jsonSeen
|
|
207723
|
+
};
|
|
207724
|
+
byFlag.set(row.name, [value]);
|
|
207725
|
+
continue;
|
|
207726
|
+
}
|
|
207727
|
+
if (!byFlag.has(row.name)) byFlag.set(row.name, []);
|
|
207728
|
+
continue;
|
|
207729
|
+
}
|
|
207751
207730
|
let raw;
|
|
207752
207731
|
if (eq !== -1) raw = tok.slice(eq + 1);
|
|
207753
207732
|
else {
|
|
@@ -207812,6 +207791,7 @@ const decodeFlags = (byFlag) => {
|
|
|
207812
207791
|
for (const row of FLAGS) {
|
|
207813
207792
|
const raws = byFlag.get(row.name);
|
|
207814
207793
|
if (raws === void 0) continue;
|
|
207794
|
+
if (row.arity === "optional" && raws.length === 0) continue;
|
|
207815
207795
|
const decoded = row.decode(raws);
|
|
207816
207796
|
if (isLeft(decoded)) return left(decoded.left);
|
|
207817
207797
|
bag[row.name] = decoded.right;
|
|
@@ -207875,16 +207855,17 @@ const parseArgv = (argv) => {
|
|
|
207875
207855
|
const decoded = decodeFlags(byFlag);
|
|
207876
207856
|
if (isLeft(decoded)) return usagePlan(decoded.left, jsonSeen);
|
|
207877
207857
|
const bag = decoded.right;
|
|
207878
|
-
const json = present.has("--json")
|
|
207879
|
-
|
|
207858
|
+
const json = !present.has("--json") ? { kind: "off" } : bag["--json"] !== void 0 ? {
|
|
207859
|
+
kind: "select",
|
|
207860
|
+
path: bag["--json"]
|
|
207861
|
+
} : { kind: "document" };
|
|
207880
207862
|
const verbose = present.has("--verbose");
|
|
207881
207863
|
if (kind === "land") {
|
|
207882
|
-
if (bag["--model"] !== void 0 && bag["--cost"] === void 0) return usagePlan("gtd: --model requires --cost — it tags the recorded cost with the model that ran",
|
|
207864
|
+
if (bag["--model"] !== void 0 && bag["--cost"] === void 0) return usagePlan("gtd: --model requires --cost — it tags the recorded cost with the model that ran", jsonSeen);
|
|
207883
207865
|
return {
|
|
207884
207866
|
kind: "command",
|
|
207885
207867
|
command: buildLandCommand(bag),
|
|
207886
207868
|
json,
|
|
207887
|
-
sh,
|
|
207888
207869
|
verbose
|
|
207889
207870
|
};
|
|
207890
207871
|
}
|
|
@@ -207900,7 +207881,6 @@ const parseArgv = (argv) => {
|
|
|
207900
207881
|
label
|
|
207901
207882
|
},
|
|
207902
207883
|
json,
|
|
207903
|
-
sh,
|
|
207904
207884
|
verbose
|
|
207905
207885
|
};
|
|
207906
207886
|
}
|
|
@@ -207912,7 +207892,6 @@ const parseArgv = (argv) => {
|
|
|
207912
207892
|
open: !(bag["--no-open"] ?? false)
|
|
207913
207893
|
},
|
|
207914
207894
|
json,
|
|
207915
|
-
sh,
|
|
207916
207895
|
verbose
|
|
207917
207896
|
};
|
|
207918
207897
|
if (kind === "check") return {
|
|
@@ -207924,14 +207903,12 @@ const parseArgv = (argv) => {
|
|
|
207924
207903
|
...bag["--open-questions"] !== void 0 ? { openQuestions: bag["--open-questions"] } : {}
|
|
207925
207904
|
},
|
|
207926
207905
|
json,
|
|
207927
|
-
sh,
|
|
207928
207906
|
verbose
|
|
207929
207907
|
};
|
|
207930
207908
|
return {
|
|
207931
207909
|
kind: "command",
|
|
207932
207910
|
command: { kind },
|
|
207933
207911
|
json,
|
|
207934
|
-
sh,
|
|
207935
207912
|
verbose
|
|
207936
207913
|
};
|
|
207937
207914
|
};
|
|
@@ -207971,10 +207948,14 @@ const bufferedArtifactOut = (io) => {
|
|
|
207971
207948
|
* The single envelope writer: `{state:"error",prompt}` on **stderr** under
|
|
207972
207949
|
* `--json`, `renderFailure` on stderr always, exit `EXIT_RUNTIME_ERROR`.
|
|
207973
207950
|
* stdout is never touched here — the command's `ArtifactOut` buffer was never
|
|
207974
|
-
* flushed, so a `--json` driver
|
|
207975
|
-
* read stderr or the exit code instead. `Effect.sandbox` means this also
|
|
207976
|
-
* fires for a DEFECT, not just a typed error.
|
|
207977
|
-
* error — those never build a
|
|
207951
|
+
* flushed, so a `--json` driver reading stdout on a failed run reads nothing
|
|
207952
|
+
* — it must read stderr or the exit code instead. `Effect.sandbox` means this also
|
|
207953
|
+
* fires for a DEFECT, not just a typed error. Unreached by an ordinary usage
|
|
207954
|
+
* error (an unknown flag, bad arity, a scope violation) — those never build a
|
|
207955
|
+
* layer at all. The one exception is `SelectorUsageError`: an unknown
|
|
207956
|
+
* `--json=<path>` selector can only be judged after the layer is built and
|
|
207957
|
+
* the fields object it's reduced against is fully resolved, so it fails HERE
|
|
207958
|
+
* rather than in `parseArgv` — `EXIT_USAGE_ERROR` still applies to it below.
|
|
207978
207959
|
*/
|
|
207979
207960
|
const report = (io, json) => (cause) => sync$1(() => {
|
|
207980
207961
|
const error = squash(cause);
|
|
@@ -207986,7 +207967,7 @@ const report = (io, json) => (cause) => sync$1(() => {
|
|
|
207986
207967
|
})}\n`);
|
|
207987
207968
|
}
|
|
207988
207969
|
io.stderr(`${renderFailure(error)}\n`);
|
|
207989
|
-
io.exit(1);
|
|
207970
|
+
io.exit(error instanceof SelectorUsageError ? 2 : 1);
|
|
207990
207971
|
});
|
|
207991
207972
|
const runCli = (argv, io) => {
|
|
207992
207973
|
const plan = parseArgv(argv);
|
|
@@ -208001,10 +207982,10 @@ const runCli = (argv, io) => {
|
|
|
208001
207982
|
io.exit(2);
|
|
208002
207983
|
});
|
|
208003
207984
|
const out = bufferedArtifactOut(io);
|
|
208004
|
-
return runCommand(plan.command, plan.json,
|
|
207985
|
+
return runCommand(plan.command, plan.json, out).pipe(map$4(() => {
|
|
208005
207986
|
out.flush();
|
|
208006
207987
|
io.exit(0);
|
|
208007
|
-
}), provide$2(io.layers(plan.verbose)), sandbox, catchAll((cause) => isInterruptedOnly(cause) ? failCause$2(cause) : report(io, plan.json)(cause)));
|
|
207988
|
+
}), provide$2(io.layers(plan.verbose)), sandbox, catchAll((cause) => isInterruptedOnly(cause) ? failCause$2(cause) : report(io, plan.json.kind !== "off")(cause)));
|
|
208008
207989
|
};
|
|
208009
207990
|
//#endregion
|
|
208010
207991
|
//#region src/main.ts
|