@pmelab/gtd 8.2.0 → 8.2.1
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 +22 -4
- package/dist/gtd.bundle.mjs +27 -827
- package/package.json +1 -3
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -4,7 +4,6 @@ const require = __createRequire(import.meta.url);
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import * as Crypto from "node:crypto";
|
|
7
|
-
import crypto from "node:crypto";
|
|
8
7
|
import * as NFS from "node:fs";
|
|
9
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
10
9
|
import * as OS from "node:os";
|
|
@@ -22096,7 +22095,7 @@ const scoped$1 = (effect) => unwrap$2(uninterruptibleMask((restore) => map$4(mak
|
|
|
22096
22095
|
/** @internal */
|
|
22097
22096
|
const scopedWith = (f) => unwrapScoped$4(map$4(scope, (scope) => flatMap$3(fromEffect$4(f(scope)), write$1)));
|
|
22098
22097
|
/** @internal */
|
|
22099
|
-
const splitLines$
|
|
22098
|
+
const splitLines$2 = () => suspend$4(() => {
|
|
22100
22099
|
let stringBuilder = "";
|
|
22101
22100
|
let midCRLF = false;
|
|
22102
22101
|
const splitLinesChunk = (chunk) => {
|
|
@@ -22679,7 +22678,7 @@ const runIntoQueueScoped = /*#__PURE__*/ dual(2, (self, queue) => {
|
|
|
22679
22678
|
/** @internal */
|
|
22680
22679
|
const scoped = (effect) => new StreamImpl(ensuring$1(scoped$1(pipe(effect, map$4(of$1))), _void));
|
|
22681
22680
|
/** @internal */
|
|
22682
|
-
const splitLines$
|
|
22681
|
+
const splitLines$1 = (self) => pipeThroughChannel(self, splitLines$2());
|
|
22683
22682
|
/** @internal */
|
|
22684
22683
|
const suspend$2 = (stream) => new StreamImpl(suspend$4(() => toChannel$2(stream())));
|
|
22685
22684
|
/** @internal */
|
|
@@ -22800,7 +22799,7 @@ const runCollect = runCollect$1;
|
|
|
22800
22799
|
* @since 2.0.0
|
|
22801
22800
|
* @category combinators
|
|
22802
22801
|
*/
|
|
22803
|
-
const splitLines
|
|
22802
|
+
const splitLines = splitLines$1;
|
|
22804
22803
|
/**
|
|
22805
22804
|
* Applies the transducer to the stream and emits its outputs.
|
|
22806
22805
|
*
|
|
@@ -29938,7 +29937,7 @@ const makeExecutor$1 = (start) => {
|
|
|
29938
29937
|
const stream = (command) => unwrapScoped$1(map$4(start(command), (process) => process.stdout));
|
|
29939
29938
|
const streamLines = (command, encoding) => {
|
|
29940
29939
|
const decoder = new TextDecoder(encoding);
|
|
29941
|
-
return splitLines
|
|
29940
|
+
return splitLines(mapChunks(stream(command), map$12((bytes) => decoder.decode(bytes))));
|
|
29942
29941
|
};
|
|
29943
29942
|
return {
|
|
29944
29943
|
[TypeId]: TypeId,
|
|
@@ -190632,7 +190631,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true) =
|
|
|
190632
190631
|
};
|
|
190633
190632
|
//#endregion
|
|
190634
190633
|
//#region src/workflows/unified.yaml
|
|
190635
|
-
var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See STATES.md §10 for the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this inline source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with SUB-MACHINES (Option C) ──────────────────────────────────\n#\n# This template is written with `submachines:`/`use:` (see src/Submachines.ts).\n# Expansion is a compile-time raw → raw pre-pass with NO runtime footprint: the\n# pure engine only ever sees the flat concrete states, and each invocation's\n# `as:` rename map spells out the concrete state name every expanded state gets,\n# so the compiled `WorkflowDefinition` is exactly what a hand-written flat\n# workflow would produce. The sub-machines split into DEDUP (a gate/loop\n# authored once, invoked several times) and ENCAPSULATION (a single-instance\n# cluster grouped for reading):\n#\n# * makeGreen (dedup ×2) — the check/fix/escalate health triple shared by\n# the simple build tail and the advanced package\n# loop (identical script/prompt/message).\n# * assertGreen (dedup ×3) — the green-baseline gate (check + human blocked)\n# guarding the simple, advanced and review entries.\n# * qaLoop (dedup ×2) — the checkbox Q&A author/answer loop shared by the\n# product (product-qa) and technical (technical-qa)\n# phases of the advanced flow.\n# * planLoop (encap) — the simple flow's plan-iteration loop.\n# * humanReview (encap) — the shared review tail (reviewing → await-review →\n# review-deciding → feedback collect/build).\n# * specGate (encap) — the advanced flow's per-package agentic review.\n# * packageLoop (encap) — the advanced flow's per-package build queue.\n#\n# ── Four entry points behind a green-baseline gate, one shared tail ─────────\n#\n# EVERY entry first runs the test suite and only proceeds when it is GREEN — you\n# never start new work (or a review) on top of a red baseline. A red run halts\n# at a human `*-blocked` gate (fix the failures and re-run, exactly like\n# the `escalate` states), or is what `gtd fix` exists to repair.\n#\n# The initial `idle` state FORKS on which steering file the human creates,\n# routing into that flow's OWN start gate:\n#\n# * the configured requirementsFile (default .gtd/REQUIREMENTS.md) ->\n# `spec-precheck` -> the ADVANCED flow (product + technical Q&A, package\n# decomposition, per-package parallel build + agentic spec-review).\n# * anything else (e.g. the configured todoFile, default .gtd/TODO.md) ->\n# `plan-precheck` -> the SIMPLE flow (one grilling Q&A loop, a monolithic\n# build, no decomposition, no agentic review).\n#\n# A third entry, `gtd review <commitish>`, jumps to `review-precheck`\n# (reviewEntry) and, once green, to `reviewing` to review a foreign branch\n# through the same shared tail. A fourth, `gtd fix`, jumps to `fix-precheck`\n# (fixEntry): a red suite drops straight into the shared `fixing` loop and out\n# through the review + squash tail; a green suite is a no-op back to `idle`.\n#\n# All three converge at `reviewing` -> `await-review`. Ticking a REVIEW.md box\n# means only \"I reviewed this hunk\"; a COMMENT (not an unticked box) is what asks\n# for changes. Every human step at `await-review` goes to `review-deciding`,\n# which inspects the step and routes:\n# * no comment (every box ticked, no REVIEW.md note, no code edit) -> the diff\n# is a bare REVIEW.md deletion -> squashing -> done (the ONLY path to the\n# finale — the whole cycle collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# review-deciding writes REVIEW_FEEDBACK.md -> feedback-building implements\n# it (completing any code the human hand-edited, never reverting their\n# lines) -> checking -> reviewing regenerates an INCREMENTAL REVIEW.md\n# (review-deciding carries `reviewBase: true`, so each feedback round's\n# review + checkout window covers only `last-review..HEAD`, i.e. the agent's\n# follow-through, not the human's own edit) -> await-review, looping until\n# sign-off.\n# Two dead ends never commit: the sign-off gate (src/program.ts) REFUSES a step\n# that leaves a box unticked with no comment (finish reviewing first) and a\n# deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health segment (`checking`/`fixing`/`escalate`) serves the simple\n# build and the feedback loop — its green edge targets\n# `reviewing`. The advanced per-package loop keeps its OWN health triple\n# (`package-checking`/`package-fixing`/`package-escalate`) because its green edge targets\n# the per-package `spec-review` gate instead.\n#\n# Every `on` pattern's path below is written as the matching var's Eta\n# template (e.g. `\"A <%= it.vars.feedbackFile %>\"`), not a literal `.gtd/...`\n# path — repointing a var reroutes the machine along with every template that\n# reads/writes it (see docs/configuration.md).\n\nvars:\n testCommand: npm test\n # Where gtd keeps its own scratch/bookkeeping (the check scripts' temp output\n # file, review-deciding's state-file exclusion). Independent of the per-file\n # vars below — relocating a file wholesale means setting both.\n stateDir: .gtd\n todoFile: .gtd/TODO.md\n requirementsFile: .gtd/REQUIREMENTS.md\n architectureFile: .gtd/ARCHITECTURE.md\n packagesDir: .gtd/packages\n nextFile: .gtd/NEXT.md\n reviewFile: .gtd/REVIEW.md\n reviewRawFile: .gtd/REVIEW_RAW.md\n reviewFeedbackFile: .gtd/REVIEW_FEEDBACK.md\n feedbackFile: .gtd/FEEDBACK.md\n specFeedbackFile: .gtd/SPEC_FEEDBACK.md\n commitMsgFile: .gtd/COMMIT_MSG.md\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_PLANNERMODEL` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot planning/technical-qa/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n\nsubmachines:\n # ── DEDUP: the check/fix/escalate health triple ───────────────────────────\n # Runs the suite; a red run records the configured feedbackFile, a green run\n # cleans it up.\n # The `on` rules turn that into \"fix\" vs \"$onGreen\". The fix turn retries up to\n # 3× before escalating to a human. Invoked for the shared build tail\n # (checking/fixing/escalate -> reviewing) and the advanced package loop\n # (package-checking/package-fixing/package-escalate -> spec-review).\n makeGreen:\n params: [onGreen, escalateDescribe, checkLabel, fixLabel, escalateLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n # NOT the shared `&suiteCheck` anchor (that lives on `assertGreen`,\n # aliased by it and by `fix-precheck`): this script also sweeps spent\n # steering files (`todoFile`, `reviewRawFile`, `reviewFeedbackFile`) —\n # a mechanic the entry/fix gates must NOT run, since they execute\n # before planning, while a fresh sketch legitimately still exists.\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n # Sweep steering files spent by the states that led here: the plan\n # (building), the raw review capture (feedback-collecting), and the\n # feedback instructions (feedback-building) — each `rm -f` is a\n # no-op when the file is already gone or never existed on this path.\n todo=\"<%~ it.vars.todoFile %>\"\n reviewRaw=\"<%~ it.vars.reviewRawFile %>\"\n reviewFeedback=\"<%~ it.vars.reviewFeedbackFile %>\"\n rm -f \"$todo\" \"$reviewRaw\" \"$reviewFeedback\"\n <%~ it.vars.testCommand %> > <%~ it.vars.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.vars.stateDir %>/.check-output ]; then\n mv <%~ it.vars.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": fix\n \"M <%= it.vars.feedbackFile %>\": fix\n \"* **\": $onGreen\n \"C\": $onGreen\n fix:\n actor: agent\n label: $fixLabel\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: escalate\n prompt: |\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. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; 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 `<%= it.vars.feedbackFile %>` (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 `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": check\n escalate:\n actor: human\n label: $escalateLabel\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\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 # ── DEDUP: the green-baseline gate ─────────────────────────────────────────\n # Runs the suite before any work starts (its script is the health check's test\n # wrapper — red -> the configured feedbackFile, green -> remove it); the `on` rules turn\n # that into \"halt at blocked\" vs \"proceed to $onGreen\". The script and blocked\n # message/describe differ per entry, so they ride in as `with:` params.\n assertGreen:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n # Anchored here and aliased by `fix-precheck` (`gtd fix` entry): one\n # suite-check script shared by all four call sites (this submachine's\n # ×3 uses plus `fix-precheck`) instead of four source copies. These gates\n # run BEFORE planning, so — unlike `makeGreen.check` — they must NOT\n # sweep `todoFile`/`requirementsFile`: the human's fresh sketch still\n # legitimately exists here.\n script: &suiteCheck |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n <%~ it.vars.testCommand %> > <%~ it.vars.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.vars.stateDir %>/.check-output ]; then\n mv <%~ it.vars.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": blocked\n \"M <%= it.vars.feedbackFile %>\": blocked\n \"D <%= it.vars.feedbackFile %>\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: <%= it.vars.feedbackFile %>\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # ── DEDUP: the checkbox Q&A author/answer loop ─────────────────────────────\n # The agent develops a plan file, surfacing `## Open Questions` as checkbox\n # lists; the human answers (answerGate refuses an unanswered step) and either\n # advances or sends it back. Invoked for the product (product-qa) and\n # technical (technical-qa) phases of the advanced flow.\n qaLoop:\n params:\n [\n authorFile,\n authorPrompt,\n answerMessage,\n onAdvance,\n advanceDescribe,\n editDescribe,\n authorLabel,\n answerLabel,\n ]\n states:\n author:\n actor: agent\n label: $authorLabel\n file: $authorFile\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: $authorPrompt\n on:\n \"* **\": answer\n answer:\n actor: human\n label: $answerLabel\n file: $authorFile\n mode: qa\n answerGate: true\n message: $answerMessage\n on:\n \"C\":\n to: $onAdvance\n action: Accept answers\n describe: $advanceDescribe\n \"* **\":\n to: author\n action: Revise answers\n describe: $editDescribe\n\n # ── ENCAPSULATION: the simple flow's plan-iteration loop ───────────────────\n planLoop:\n params: [onAccept]\n states:\n planning:\n actor: agent\n label: Refining the plan\n file: <%= it.vars.todoFile %>\n mode: prose\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\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. The only state file this\n turn touches is `<%= it.vars.todoFile %>`; develop it into the plan.\n Do not create other files to hold notes or output.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build — or, on a\n return lap, the plan you already wrote plus the human's edits and comments\n on it. Read it, explore the codebase, and develop it into a concrete\n implementation plan: the files to change, the approach, and the steps to\n take.\n\n Do NOT ask questions and do NOT add an \"Open Questions\" section — decide\n every open point yourself and write the plan as a set of concrete\n decisions. Where a decision is genuinely a judgement call, state the\n choice you made and, in one line, why. The human iterates on the plan by\n editing it directly, not by answering questions.\n\n On a return lap, `<%= it.vars.todoFile %>` carries the human's edits and\n inline comments on your previous plan. Fold every edit and comment into a\n revised, self-contained plan and remove the addressed comments — the plan\n is always the current, clean proposal, never a diff.\n\n Leave `<%= it.vars.todoFile %>` uncommitted and finish your turn — do\n not commit, and do not run `gtd step agent` yourself; the harness does\n that.\n on:\n \"* **\": await-plan\n\n await-plan:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.todoFile %>\n mode: prose\n message: |\n `<%= it.vars.todoFile %>` holds the proposed implementation plan. Read it,\n then either accept it as-is or edit it — rewrite a step, add a\n constraint, or drop an inline comment — to send it back for another round.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: $onAccept\n action: Accept plan\n describe: >-\n change nothing to move on to implementation (**building**).\n \"* **\":\n to: planning\n action: Revise plan\n describe: >-\n rewrite a step, add a constraint, or leave an inline comment to\n send it back for another round (**planning**).\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n humanReview:\n params: [onSignoff]\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n model: <%= it.vars.plannerModel %>\n memory: review\n # `gtd review <commitish>` (a plain non-gtd branch, e.g. a colleague's PR)\n # does NOT enter here directly — it enters `review-precheck` (reviewEntry)\n # first, which runs the green-baseline gate and, once green, transitions\n # here. The `Gtd-Review-Base:` trailer rides on that entry commit (the\n # process's oldest), so everything below still reuses the same\n # reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\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. The only state file this\n turn touches is `<%= it.vars.reviewFile %>`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the diff below in\n this EXACT format:\n\n - First non-blank line: `# Review: <%= it.currentCommit.slice(0, 7) %>`\n - Somewhere in the document: `<!-- base: <%= it.startCommit %> -->`\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 `- [ ] ./path/to/file.ts#42 — note` file\n pointer per hunk (checkboxes are for the human to tick, not you; every\n path is `./`-relative, with an optional `#line` and an optional\n ` — note`).\n\n The diff below is `it.reviewDiff` — on a feedback round it covers ONLY the\n changes made since the previous review, so you review just what changed,\n not items already signed off. Leave `<%= it.vars.reviewFile %>`\n uncommitted and finish your turn.\n <% if (it.reviewDiff.trim()) { %>\n\n ## Full diff under review\n\n ```diff\n <%~ it.reviewDiff %>\n ```\n <% } %>\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the cycle rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `review-deciding` commit —\n # so a feedback round shows only `last-review..HEAD` — or the process start\n # on the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `review-deciding`, which decides sign-off\n # vs. feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\". Two dead ends can't be told apart by a\n # file-pattern edge, so the sign-off gate (`enforceReviewSignoffGate` in\n # src/program.ts) catches them BEFORE the step commits and refuses: an\n # unfinished review (a box still `- [ ]` with no comment) and a deleted\n # REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the cycle — 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 When you've been through the whole diff, run `gtd step human`:\n\n - **Sign off** — tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to collapse the cycle into\n one commit (**squashing**).\n - **Request changes** — leave a comment: a note on a\n `<%= it.vars.reviewFile %>` line, or a direct code edit — to send it to a\n build + re-review round (**review-deciding** → **feedback-building**). A\n code edit counts as your own fix: the agent completes the follow-through\n it implies and never reverts your lines.\n\n Stepping with a box still unticked and no comment is refused (finish\n reviewing first); so is deleting `<%= it.vars.reviewFile %>`.\n on:\n \"* **\": review-deciding\n\n review-deciding:\n actor: check\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the cycle start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> review-deciding` commit; HEAD^ is the\n # `reviewing` commit that wrote the agent's original REVIEW.md (the sign-off\n # gate refuses an incomplete step, so review-deciding is entered exactly\n # once per round — nothing commits in between). It is FEEDBACK when the\n # human left a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\"\n # tick) OR a hand-edit to any file this round other than `.gtd/` or the\n # state's own reviewFile (which is var-configurable and may live outside\n # `.gtd/`, e.g. a root-level REVIEW.md — issue #128). Otherwise it is a\n # clean sign-off. This turn only CAPTURES the raw material into REVIEW_RAW.md\n # (the `feedback-collecting` agent then turns it into instructions); it does\n # NOT interpret it. Either way, remove REVIEW.md; what the resulting diff\n # MEANS is decided by this state's own `on` rules — the A/M REVIEW_RAW.md\n # rows are declared BEFORE the D REVIEW.md row so a feedback round (which\n # also deletes REVIEW.md) is captured as feedback, not mistaken for a\n # sign-off.\n set +e\n # Hoist the paths once, 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 — it\n # would glue the next line onto it and break the script. Below this point\n # everything is plain bash.\n stateDir=\"<%~ it.vars.stateDir %>\"\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n mkdir -p \"$(dirname \"$reviewRawFile\")\"\n # Which files did the human hand-edit this round, besides stateDir and the\n # reviewFile itself (var-configurable — may live outside stateDir, so it is\n # excluded by exact path, not just the stateDir prefix)?\n codeFiles=$(git diff-tree --no-commit-id --name-only -r HEAD \\\n | grep -v \"^$stateDir/\" | grep -vxF \"$reviewFile\")\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n notes=\"\"\n before=$(git show \"HEAD^:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n if [ \"$before\" != \"$after\" ]; then notes=1; fi\n if [ -n \"$codeFiles\" ] || [ -n \"$notes\" ]; then\n {\n echo \"Raw review material captured for classification. The human reviewed the diff and left the feedback below; a downstream agent turns it into an instruction list. This file is machine-captured input, not instructions.\"\n if [ -n \"$notes\" ]; then\n echo\n echo \"## Notes the human added to REVIEW.md this round\"\n echo\n echo '```diff'\n git show HEAD --format= -- \"$reviewFile\"\n echo '```'\n fi\n if [ -n \"$codeFiles\" ]; then\n echo\n echo \"## Files the human hand-edited this round (already committed)\"\n echo\n echo '```diff'\n git show HEAD --format= -- $codeFiles\n echo '```'\n fi\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A <%= it.vars.reviewRawFile %>\": feedback-collecting\n \"M <%= it.vars.reviewRawFile %>\": feedback-collecting\n \"D <%= it.vars.reviewFile %>\": $onSignoff\n\n feedback-collecting:\n actor: agent\n label: Collecting your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.plannerModel %>\n memory: review\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state files this\n turn touches are `<%= it.vars.reviewRawFile %>` (read it, then\n delete it) and `<%= it.vars.reviewFeedbackFile %>` (write it) — do\n NOT change any other file, you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\n\n Classify every piece of it into `<%= it.vars.reviewFeedbackFile %>` as a\n flat, numbered instruction list. Each item is `N. ./path/to/file.ts#line —\n <imperative>`. Read the two RAW sections and apply these rules exactly:\n\n 1. NOTES the human added to REVIEW.md — each note is a MANDATORY change,\n regardless of whether its checkbox is ticked (ticking means only \"I read\n this hunk\", never sign-off). Anchor each to the `./path#line` its review\n item names (or the chunk it sits under). Emit it as a concrete\n imperative.\n 2. Every COMMENT the human ADDED this round (a `+` line that is a code\n comment, in the hand-edited files) is a MANDATORY instruction the human\n is giving the builder — even a plain-prose one, even if its box is\n ticked. Emit an imperative to satisfy it and note that the comment line\n itself must be REMOVED once addressed. If a comment turns out to describe\n behaviour the code already has, the instruction is still \"confirm the\n code matches and remove the comment line\".\n 3. Every NON-comment code line the human hand-edited is an already-committed\n INTENT, NOT a do-then-delete item. Emit an instruction that names those\n files and says: their lines are final — complete ONLY the follow-through\n they imply (callers, tests, types, docs) and NEVER revert or rewrite\n them. Do not turn their own edit into a task.\n\n Split each hand-edited hunk per line: comment `+` lines follow rule 2, the\n surrounding real code follows rule 3.\n\n If — and only if — nothing in the raw material is actionable (e.g. the human\n left only an approving remark), write `<%= it.vars.reviewFeedbackFile %>`\n containing exactly one line: `NOTHING ACTIONABLE — <one-sentence reason>`.\n Never invent work to satisfy a non-actionable remark.\n\n Delete `<%= it.vars.reviewRawFile %>`, leave everything else uncommitted,\n and finish your turn.\n on:\n \"A <%= it.vars.reviewFeedbackFile %>\": feedback-building\n \"M <%= it.vars.reviewFeedbackFile %>\": feedback-building\n\n feedback-building:\n actor: agent\n label: Addressing your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n # Refuse a work-free turn: a step whose only pending change is deleting the\n # instructions file (the original \"captured then discarded\" bug). The edge\n # gate (`enforceFeedbackProgressGate` in src/program.ts) reads this flag and\n # exempts a `NOTHING ACTIONABLE` sentinel (a non-actionable review round,\n # which legitimately makes no code change). The pure engine carries it inert.\n requireProgress: true\n prompt: |\n You are an autonomous coding agent addressing review feedback. This\n workflow steers itself through its own state files — treat them as\n its private scratchpad, never as project code or documentation. The\n only state file this turn touches is\n `<%= it.vars.reviewFeedbackFile %>`; address it, then delete it. Do\n not create other files to hold notes or output.\n\n The instructions to implement are: <%~ it.read(it.vars.reviewFeedbackFile) %>\n\n This is a flat instruction list, not a diff — execute every item exactly as\n written, no Q&A, no re-planning, using TDD discipline:\n\n - An item that says the human's own lines are final: build ON TOP of them to\n complete only the follow-through it names (callers, tests, types, docs).\n NEVER revert or rewrite the lines they already committed.\n - An item derived from a human comment: satisfy it AND delete that comment\n line from the code (the human's review comments are transient — none of\n them survive this round).\n\n If the file's sole content is a `NOTHING ACTIONABLE` line, make no code\n change; just delete the instructions file.\n\n Delete `<%= it.vars.reviewFeedbackFile %>` once every item is addressed.\n Leave everything else uncommitted and finish your turn. Deleting the\n instructions file WITHOUT making the changes it asks for is refused — the\n feedback must actually be addressed.\n on:\n \"* **\": checking\n\n # ── ENCAPSULATION: the advanced flow's per-package agentic review ──────────\n specGate:\n params: [onApproved]\n states:\n spec-review:\n actor: agent\n label: Reviewing the package\n model: <%= it.vars.plannerModel %>\n memory: review\n # Bounded per cycle: once entered `max` times, a transition into it is\n # redirected to `closing` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `fixing` cap has (see STATES.md / docs/configuration.md).\n retry:\n max: 3\n otherwise: $onApproved\n prompt: |\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. This workflow steers itself through its own\n state files — treat them as its private scratchpad, never as project\n code or documentation. The only state file this turn touches is\n `<%= it.vars.specFeedbackFile %>`; 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(it.vars.nextFile) %>\n\n Read that spec, then verify the package's implementation (the diff below)\n against it: are all its tasks done, acceptance criteria met, and the code\n sound and consistent with the codebase?\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 `<%= it.vars.specFeedbackFile %>` 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 <% if (it.processDiff.trim()) { %>\n\n ## Diff under review\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"A <%= it.vars.specFeedbackFile %>\": spec-fix\n \"M <%= it.vars.specFeedbackFile %>\": spec-fix\n \"D <%= it.vars.specFeedbackFile %>\": $onApproved\n \"C\": $onApproved\n\n spec-fix:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n prompt: |\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. The only state file this\n turn touches is `<%= it.vars.specFeedbackFile %>`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": package-checking\n\n # ── ENCAPSULATION: the advanced flow's per-package build queue ─────────────\n packageLoop:\n params: [onDrained]\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n # Sweep the advanced flow's spent Q&A steering files: requirementsFile\n # is consumed by technical-qa, architectureFile by decompose — both are\n # gone by the time the package queue starts. No-op if already removed.\n requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n rm -f \"$requirements\" \"$architecture\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n mkdir -p \"$(dirname \"$nextFile\")\"\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D <%= it.vars.nextFile %>\": $onDrained\n \"* <%= it.vars.nextFile %>\": package-building\n \"C\": $onDrained\n\n package-building:\n actor: agent\n label: Building\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\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. This turn writes no state\n file: do NOT delete the package file (the `spec-review` gate reads\n it after you) and never touch `<%= it.vars.nextFile %>` — the\n `picking` state owns it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\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 \"* **\": package-checking\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback, so\n # `picking` selects the NEXT package (or falls through to review when the\n # queue is empty). Reached on approval (spec-review clean) and on the\n # spec-review retry cap (force-close, deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %>\n on:\n \"* **\": picking\n\nuse:\n # Shared health triple: checking/fixing/escalate -> reviewing.\n - submachine: makeGreen\n name: build\n as: { check: checking, fix: fixing, escalate: escalate }\n with:\n onGreen: reviewing\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself (**checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n # Advanced package health triple: package-checking/package-fixing/package-escalate -> spec-review.\n - submachine: makeGreen\n name: package\n as: { check: package-checking, fix: package-fixing, escalate: package-escalate }\n with:\n onGreen: spec-review\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself (**package-checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n\n # Green-baseline gate: simple flow entry.\n - submachine: assertGreen\n name: plan\n as: { check: plan-precheck, blocked: plan-blocked }\n with:\n onGreen: planning\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\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 (**plan-precheck**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run `gtd fix` from a\n clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: advanced flow entry.\n - submachine: assertGreen\n name: spec\n as: { check: spec-precheck, blocked: spec-blocked }\n with:\n onGreen: product-qa\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\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 (**spec-precheck**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run `gtd fix` from a\n clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: `gtd review` entry (reviewEntry -> reviewing).\n - submachine: assertGreen\n name: review\n as: { check: review-precheck, blocked: review-blocked }\n set:\n check: { reviewEntry: true }\n with:\n onGreen: reviewing\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\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-precheck**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Advanced flow: product Q&A loop (product-qa/product-answer -> technical-qa).\n - submachine: qaLoop\n name: product\n as: { author: product-qa, answer: product-answer }\n with:\n authorFile: <%= it.vars.requirementsFile %>\n onAdvance: technical-qa\n authorPrompt: |\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. The only state file this turn\n touches is `<%= it.vars.requirementsFile %>`; develop it. Do not\n create other files to hold notes or output.\n\n `<%= it.vars.requirementsFile %>` holds the product requirements (or the\n plan you already wrote plus the human's answers on a return lap). Read it,\n explore the codebase, and develop it into a concrete, PRODUCT-level plan\n in this one turn.\n\n Scope: product and user-facing decisions only. Leave implementation\n details (file/module structure, data models, tech-stack choices) for the\n next phase (`<%= it.vars.architectureFile %>`).\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading near the top: one `### <question>` sub-heading\n each, followed by a checkbox list of candidate answers — aim for 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. The\n last `- [ ] _your answer_` line is the human's own-answer slot; write it\n with the literal `_your answer_` placeholder and leave it for them.\n\n On a RETURN lap the human has ticked one option per question (or replaced\n `_your answer_` with their own text). For each answered question: fold the\n chosen answer into the plan prose, then MOVE the question down into a\n `## Answered Questions` section as `### <question>` followed by the\n resolved answer in plain PROSE (drop the checkboxes). Never re-raise a\n question the human deleted; if the human deleted the whole\n `## Open Questions` section, treat the plan as accepted and surface no new\n questions unless something is genuinely still blocking. Omit\n `## Open Questions` entirely once there are none left.\n\n Leave `<%= it.vars.requirementsFile %>` uncommitted and finish your turn.\n answerMessage: |\n `<%= it.vars.requirementsFile %>` holds the product plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n with no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to move on to\n technical Q&A (**technical-qa**).\n editDescribe: >-\n tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**product-qa**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the product plan\n answerLabel: Awaiting your answers\n\n # Advanced flow: technical Q&A loop (technical-qa/technical-answer -> decompose).\n - submachine: qaLoop\n name: technical\n as: { author: technical-qa, answer: technical-answer }\n with:\n authorFile: <%= it.vars.architectureFile %>\n onAdvance: decompose\n authorPrompt: |\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. The only state files this\n turn touches are `<%= it.vars.architectureFile %>` (write it) and\n `<%= it.vars.requirementsFile %>` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n Read `<%= it.vars.requirementsFile %>` (the converged product plan) and\n develop `<%= it.vars.architectureFile %>` from it in this one turn:\n file/module structure, data models, library/tech-stack choices,\n error-handling strategy — the *how*, building on the *what* already\n settled. Do not re-open product-level decisions; treat them as settled.\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading exactly as the grilling phase does: one\n `### <question>` each, followed by a checkbox list — TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one option\n per question (or replaced `_your answer_` with their own text): fold each\n chosen answer into the plan prose, then MOVE the question into a\n `## Answered Questions` section as `### <question>` + the resolved answer\n in plain PROSE (drop the checkboxes). Never re-raise a deleted question;\n treat a deleted `## Open Questions` section as acceptance. Omit\n `## Open Questions` once there are none left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in and it\n must not linger. Leave everything uncommitted and finish your turn.\n answerMessage: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n with no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to move on to\n decomposing the work into packages (**decompose**).\n editDescribe: >-\n tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**technical-qa**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the technical plan\n answerLabel: Awaiting your answers\n\n # Simple flow plan-iteration loop (planning/await-plan -> building).\n - submachine: planLoop\n with:\n onAccept: building\n\n # Shared review tail (reviewing -> await-review -> ... -> squashing).\n - submachine: humanReview\n with:\n onSignoff: squashing\n\n # Advanced per-package agentic review (spec-review/spec-fix -> closing).\n - submachine: specGate\n with:\n onApproved: closing\n\n # Advanced per-package build queue (picking/package-building/closing -> reviewing).\n - submachine: packageLoop\n with:\n onDrained: reviewing\n\nstates:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n initial: true\n label: Idle\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd cycle.\n\n To start one, create a steering file describing what you want built:\n\n - `<%= it.vars.todoFile %>` — a short sketch — starts the SIMPLE flow\n (plan, build, review).\n - `<%= it.vars.requirementsFile %>` — the product requirements — starts\n the ADVANCED flow (product + technical Q&A, package decomposition,\n parallel build, per-package agentic review).\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* <%= it.vars.requirementsFile %>\":\n to: spec-precheck\n action: Start advanced flow\n describe: >-\n save the product requirements to `.gtd/REQUIREMENTS.md` — gtd first\n checks the test baseline is green, then the agent develops them\n through product then technical Q&A, decomposes the work into\n packages, and builds each in parallel (**spec-precheck**).\n \"* **\":\n to: plan-precheck\n action: Start simple flow\n describe: >-\n save a short sketch to `.gtd/TODO.md` (or any other change) — gtd\n first checks the test baseline is green, then the agent reads it,\n explores the codebase, and develops it into a concrete\n implementation plan (**plan-precheck**).\n\n # ── Simple flow: monolithic build (plan loop is the `planLoop` sub-machine) ─\n building:\n actor: agent\n label: Building\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state file this turn touches is\n `<%= it.vars.todoFile %>`; delete it once its work is complete. Do not\n create other files to hold notes or output.\n\n The plan to implement is: <%~ it.read(it.vars.todoFile) %>\n\n Read that plan and implement it in full. Use TDD discipline: one test,\n then the implementation that passes it, then the next — never write all\n the tests first.\n\n Delete `<%= it.vars.todoFile %>` once the work it describes is complete\n and verified. Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": checking\n\n # ── Advanced flow: decomposition (Q&A loops are the `qaLoop` sub-machine) ───\n decompose:\n actor: agent\n label: Decomposing into packages\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state files this turn touches\n are the package files it writes under `<%= it.vars.packagesDir %>/` and\n `<%= it.vars.architectureFile %>`, which it deletes. Do not create other\n files to hold notes or output.\n\n Read `<%= it.vars.architectureFile %>` (the converged technical plan) and\n decompose it into an ordered set of WORK PACKAGES, one file each under\n `<%= it.vars.packagesDir %>/` (e.g. `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...). Order the packages so the\n test suite can pass again after each one lands.\n\n Each package file describes a set of INDEPENDENT tasks that can be\n implemented concurrently without ordering constraints between them — a\n single build turn will fan them out to parallel subagents. Give each task\n a clear description, acceptance criteria as `- [ ]` checkboxes, and the\n relevant paths. Also state the package's overall requirement so it can be\n reviewed against its spec. Do NOT put ordering-dependent tasks in the same\n package — split them across ordered packages instead.\n\n No package file may reference any other `.gtd/` file. Once the package\n files are written, delete `<%= it.vars.architectureFile %>`. Leave\n everything uncommitted and finish your turn.\n on:\n \"* <%= it.vars.packagesDir %>/**\": picking\n\n # ── Green-baseline gate: `gtd fix` entry ──────────────────────────────────\n # `gtd fix` enters HERE (fixEntry): run the suite, then a red run drops\n # straight into the shared `fixing` loop (-> checking -> review + squash\n # tail); a green run is a no-op back to `idle` (nothing to fix).\n fix-precheck:\n actor: check\n fixEntry: true\n label: Checking the baseline\n script: *suiteCheck\n on:\n \"A <%= it.vars.feedbackFile %>\": fixing\n \"M <%= it.vars.feedbackFile %>\": fixing\n \"D <%= it.vars.feedbackFile %>\": idle\n \"C\": idle\n\n # ── Shared tail: squash finale (review loop is the `humanReview` sub-machine) ─\n squashing:\n actor: agent\n label: Squashing\n file: <%= it.vars.commitMsgFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. The cycle is approved and done. This\n workflow steers itself through its own state files — treat them as its\n private scratchpad, never as project code or documentation. The only\n state file this turn touches is `<%= it.vars.commitMsgFile %>`; write\n it. Do not create other files to hold notes or output.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. The diff below is exactly what\n that commit will contain: `it.retainedDiff`, the changes THIS process\n made (its trace/retry boundary onward). For a normal build cycle that is\n the whole feature; for a `gtd review` process it is only the fixes made\n during the review — NOT the changeset that was under review.\n <% if (it.retainedDiff.trim()) { %>\n\n Write `<%= it.vars.commitMsgFile %>` with ONE conventional-commits message\n for the changes below (subject line `type(scope): subject`, imperative\n mood, <= 72 characters; a body explaining the why, trade-offs, and key\n decisions). Plain text, no markdown wrapper.\n\n ## Retained diff\n\n ```diff\n <%~ it.retainedDiff %>\n ```\n <% } else { %>\n\n This process retained NO changes (a clean review sign-off with no fixes).\n Write `<%= it.vars.commitMsgFile %>` with exactly this one line and\n nothing else:\n\n chore: human review\n <% } %>\n on:\n \"A <%= it.vars.commitMsgFile %>\": done\n \"M <%= it.vars.commitMsgFile %>\": done\n\n done:\n commit: |\n <%~ it.read(it.vars.commitMsgFile) %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n";
|
|
190634
|
+
var unified_default = "# The unified gtd workflow template — gtd's BUILT-IN DEFAULT workflow. When no\n# `workflow:` key is configured anywhere in the cwd→home config chain,\n# src/Config.ts runs this template directly, so a state command works with no\n# configuration at all. `gtd init` seeds only the settings a project tunes\n# (vars.testCommand + a modes: formatting suggestion), never the workflow\n# itself. See STATES.md §10 for the full walkthrough.\n#\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/templates.ts does the compiling). Every content string below\n# is INLINE (no `./`-relative file references): the bundled template ships\n# inside a single-file build (`dist/gtd.bundle.mjs`), so its content must\n# already be resolved at BUILD time. `renderInitConfig` (src/workflows/\n# templates.ts) can MATERIALIZE this inline source into a `.gtdrc.json`\n# `workflow:` value — the way to eject/customize the machine, and the hermetic\n# test fixture — but `gtd init` itself no longer writes the workflow.\n#\n# ── Authored with SUB-MACHINES (Option C) ──────────────────────────────────\n#\n# This template is written with `submachines:`/`use:` (see src/Submachines.ts).\n# Expansion is a compile-time raw → raw pre-pass with NO runtime footprint: the\n# pure engine only ever sees the flat concrete states, and each invocation's\n# `as:` rename map spells out the concrete state name every expanded state gets,\n# so the compiled `WorkflowDefinition` is exactly what a hand-written flat\n# workflow would produce. The sub-machines split into DEDUP (a gate/loop\n# authored once, invoked several times) and ENCAPSULATION (a single-instance\n# cluster grouped for reading):\n#\n# * makeGreen (dedup ×2) — the check/fix/escalate health triple shared by\n# the simple build tail and the advanced package\n# loop (identical script/prompt/message).\n# * assertGreen (dedup ×3) — the green-baseline gate (check + human blocked)\n# guarding the simple, advanced and review entries.\n# * qaLoop (dedup ×2) — the checkbox Q&A author/answer loop shared by the\n# product (product-qa) and technical (technical-qa)\n# phases of the advanced flow.\n# * planLoop (encap) — the simple flow's plan-iteration loop.\n# * humanReview (encap) — the shared review tail (reviewing → await-review →\n# review-deciding → feedback collect/build).\n# * specGate (encap) — the advanced flow's per-package agentic review.\n# * packageLoop (encap) — the advanced flow's per-package build queue.\n#\n# ── Four entry points behind a green-baseline gate, one shared tail ─────────\n#\n# EVERY entry first runs the test suite and only proceeds when it is GREEN — you\n# never start new work (or a review) on top of a red baseline. A red run halts\n# at a human `*-blocked` gate (fix the failures and re-run, exactly like\n# the `escalate` states), or is what `gtd fix` exists to repair.\n#\n# The initial `idle` state FORKS on which steering file the human creates,\n# routing into that flow's OWN start gate:\n#\n# * the configured requirementsFile (default .gtd/REQUIREMENTS.md) ->\n# `spec-precheck` -> the ADVANCED flow (product + technical Q&A, package\n# decomposition, per-package parallel build + agentic spec-review).\n# * anything else (e.g. the configured todoFile, default .gtd/TODO.md) ->\n# `plan-precheck` -> the SIMPLE flow (one grilling Q&A loop, a monolithic\n# build, no decomposition, no agentic review).\n#\n# A third entry, `gtd review <commitish>`, jumps to `review-precheck`\n# (reviewEntry) and, once green, to `reviewing` to review a foreign branch\n# through the same shared tail. A fourth, `gtd fix`, jumps to `fix-precheck`\n# (fixEntry): a red suite drops straight into the shared `fixing` loop and out\n# through the review + squash tail; a green suite is a no-op back to `idle`.\n#\n# All three converge at `reviewing` -> `await-review`. Ticking a REVIEW.md box\n# means only \"I reviewed this hunk\"; a COMMENT (not an unticked box) is what asks\n# for changes. Every human step at `await-review` goes to `review-deciding`,\n# which inspects the step and routes:\n# * no comment (every box ticked, no REVIEW.md note, no code edit) -> the diff\n# is a bare REVIEW.md deletion -> squashing -> done (the ONLY path to the\n# finale — the whole cycle collapses into one commit).\n# * any comment — a note in REVIEW.md OR a hand-edit to code — ->\n# review-deciding writes REVIEW_FEEDBACK.md -> feedback-building implements\n# it (completing any code the human hand-edited, never reverting their\n# lines) -> checking -> reviewing regenerates an INCREMENTAL REVIEW.md\n# (review-deciding carries `reviewBase: true`, so each feedback round's\n# review + checkout window covers only `last-review..HEAD`, i.e. the agent's\n# follow-through, not the human's own edit) -> await-review, looping until\n# sign-off.\n# Two dead ends never commit: the sign-off gate (src/program.ts) REFUSES a step\n# that leaves a box unticked with no comment (finish reviewing first) and a\n# deleted REVIEW.md (restore it and tick the boxes).\n#\n# The shared health segment (`checking`/`fixing`/`escalate`) serves the simple\n# build and the feedback loop — its green edge targets\n# `reviewing`. The advanced per-package loop keeps its OWN health triple\n# (`package-checking`/`package-fixing`/`package-escalate`) because its green edge targets\n# the per-package `spec-review` gate instead.\n#\n# Every `on` pattern's path below is written as the matching var's Eta\n# template (e.g. `\"A <%= it.vars.feedbackFile %>\"`), not a literal `.gtd/...`\n# path — repointing a var reroutes the machine along with every template that\n# reads/writes it (see docs/configuration.md).\n\nvars:\n testCommand: npm test\n # Where gtd keeps its own scratch/bookkeeping (the check scripts' temp output\n # file, review-deciding's state-file exclusion). Independent of the per-file\n # vars below — relocating a file wholesale means setting both.\n stateDir: .gtd\n todoFile: .gtd/TODO.md\n requirementsFile: .gtd/REQUIREMENTS.md\n architectureFile: .gtd/ARCHITECTURE.md\n packagesDir: .gtd/packages\n nextFile: .gtd/NEXT.md\n reviewFile: .gtd/REVIEW.md\n reviewRawFile: .gtd/REVIEW_RAW.md\n reviewFeedbackFile: .gtd/REVIEW_FEEDBACK.md\n feedbackFile: .gtd/FEEDBACK.md\n specFeedbackFile: .gtd/SPEC_FEEDBACK.md\n commitMsgFile: .gtd/COMMIT_MSG.md\n # The two opaque model tiers every agent state draws from — repoint them here\n # (or via a top-level `.gtdrc` `vars:` key / a `GTD_PLANNERMODEL` env var)\n # to change every state on that tier at once. `plannerModel` (default \"smart\")\n # is the heavier tier for the one-shot planning/technical-qa/review turns;\n # `coderModel` (default \"base\") is the tier for the build/fix turns.\n plannerModel: smart\n coderModel: base\n\nsubmachines:\n # ── DEDUP: the check/fix/escalate health triple ───────────────────────────\n # Runs the suite; a red run records the configured feedbackFile, a green run\n # cleans it up.\n # The `on` rules turn that into \"fix\" vs \"$onGreen\". The fix turn retries up to\n # 3× before escalating to a human. Invoked for the shared build tail\n # (checking/fixing/escalate -> reviewing) and the advanced package loop\n # (package-checking/package-fixing/package-escalate -> spec-review).\n makeGreen:\n params: [onGreen, escalateDescribe, checkLabel, fixLabel, escalateLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n # NOT the shared `&suiteCheck` anchor (that lives on `assertGreen`,\n # aliased by it and by `fix-precheck`): this script also sweeps spent\n # steering files (`todoFile`, `reviewRawFile`, `reviewFeedbackFile`) —\n # a mechanic the entry/fix gates must NOT run, since they execute\n # before planning, while a fresh sketch legitimately still exists.\n script: |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist every path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a\n # line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n # Sweep steering files spent by the states that led here: the plan\n # (building), the raw review capture (feedback-collecting), and the\n # feedback instructions (feedback-building) — each `rm -f` is a\n # no-op when the file is already gone or never existed on this path.\n todo=\"<%~ it.vars.todoFile %>\"\n reviewRaw=\"<%~ it.vars.reviewRawFile %>\"\n reviewFeedback=\"<%~ it.vars.reviewFeedbackFile %>\"\n rm -f \"$todo\" \"$reviewRaw\" \"$reviewFeedback\"\n <%~ it.vars.testCommand %> > <%~ it.vars.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.vars.stateDir %>/.check-output ]; then\n mv <%~ it.vars.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": fix\n \"M <%= it.vars.feedbackFile %>\": fix\n \"* **\": $onGreen\n \"C\": $onGreen\n fix:\n actor: agent\n label: $fixLabel\n file: <%= it.vars.feedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n retry:\n max: 3\n otherwise: escalate\n prompt: |\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. The only state file this\n turn touches is `<%= it.vars.feedbackFile %>`; 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 `<%= it.vars.feedbackFile %>` (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 `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": check\n escalate:\n actor: human\n label: $escalateLabel\n file: <%= it.vars.feedbackFile %>\n message: |\n The agent could not get the check to pass after repeated attempts.\n `<%= it.vars.feedbackFile %>` holds the last failing output.\n\n What each change does next (then run `gtd step human`):\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 # ── DEDUP: the green-baseline gate ─────────────────────────────────────────\n # Runs the suite before any work starts (its script is the health check's test\n # wrapper — red -> the configured feedbackFile, green -> remove it); the `on` rules turn\n # that into \"halt at blocked\" vs \"proceed to $onGreen\". The script and blocked\n # message/describe differ per entry, so they ride in as `with:` params.\n assertGreen:\n params: [onGreen, blockedMessage, blockedDescribe, checkLabel, blockedLabel]\n states:\n check:\n actor: check\n label: $checkLabel\n # Anchored here and aliased by `fix-precheck` (`gtd fix` entry): one\n # suite-check script shared by all four call sites (this submachine's\n # ×3 uses plus `fix-precheck`) instead of four source copies. These gates\n # run BEFORE planning, so — unlike `makeGreen.check` — they must NOT\n # sweep `todoFile`/`requirementsFile`: the human's fresh sketch still\n # legitimately exists here.\n script: &suiteCheck |\n #!/usr/bin/env bash\n # gtd check turn — run the suite; a red run records .gtd/FEEDBACK.md, a\n # green run cleans it up. The `on` rules decide what that means.\n set +e\n mkdir -p \"<%~ it.vars.stateDir %>\"\n # Hoist the feedback path once, at the TOP: Eta's autoTrim eats the\n # newline after an interpolation tag, so no tag may be the last token on\n # a line — it would glue the next line's `else`/`fi` onto it and break\n # the script. Below this point everything is plain bash.\n feedback=\"<%~ it.vars.feedbackFile %>\"\n mkdir -p \"$(dirname \"$feedback\")\"\n <%~ it.vars.testCommand %> > <%~ it.vars.stateDir %>/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s <%~ it.vars.stateDir %>/.check-output ]; then\n mv <%~ it.vars.stateDir %>/.check-output \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n printf 'the test command failed with exit code %s and produced no output.' \"$code\" > \"$feedback\"\n fi\n # A repeat identical failure would otherwise leave FEEDBACK.md\n # byte-identical to its committed copy — no diff, so the check would\n # look GREEN. Stamp each red run with the current HEAD (which always\n # advances between checks) so a still-red re-run always re-registers\n # as an M/A edit and takes the red edge.\n printf '\\n<!-- gtd check %s -->\\n' \"$(git rev-parse --short HEAD 2>/dev/null || echo pending)\" >> \"$feedback\"\n else\n rm -f <%~ it.vars.stateDir %>/.check-output\n rm -f \"$feedback\"\n fi\n on:\n \"A <%= it.vars.feedbackFile %>\": blocked\n \"M <%= it.vars.feedbackFile %>\": blocked\n \"D <%= it.vars.feedbackFile %>\": $onGreen\n \"C\": $onGreen\n blocked:\n actor: human\n label: $blockedLabel\n file: <%= it.vars.feedbackFile %>\n message: $blockedMessage\n on:\n \"* **\":\n to: check\n action: Retry check\n describe: $blockedDescribe\n\n # ── DEDUP: the checkbox Q&A author/answer loop ─────────────────────────────\n # The agent develops a plan file, surfacing `## Open Questions` as checkbox\n # lists; the human answers (answerGate refuses an unanswered step) and either\n # advances or sends it back. Invoked for the product (product-qa) and\n # technical (technical-qa) phases of the advanced flow.\n qaLoop:\n params:\n [\n authorFile,\n authorPrompt,\n answerMessage,\n onAdvance,\n advanceDescribe,\n editDescribe,\n authorLabel,\n answerLabel,\n ]\n states:\n author:\n actor: agent\n label: $authorLabel\n file: $authorFile\n mode: qa\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: $authorPrompt\n on:\n \"* **\": answer\n answer:\n actor: human\n label: $answerLabel\n file: $authorFile\n mode: qa\n answerGate: true\n message: $answerMessage\n on:\n \"C\":\n to: $onAdvance\n action: Accept answers\n describe: $advanceDescribe\n \"* **\":\n to: author\n action: Revise answers\n describe: $editDescribe\n\n # ── ENCAPSULATION: the simple flow's plan-iteration loop ───────────────────\n planLoop:\n params: [onAccept]\n states:\n planning:\n actor: agent\n label: Refining the plan\n file: <%= it.vars.todoFile %>\n mode: prose\n model: <%= it.vars.plannerModel %>\n memory: plan\n prompt: |\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. The only state file this\n turn touches is `<%= it.vars.todoFile %>`; develop it into the plan.\n Do not create other files to hold notes or output.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build — or, on a\n return lap, the plan you already wrote plus the human's edits and comments\n on it. Read it, explore the codebase, and develop it into a concrete\n implementation plan: the files to change, the approach, and the steps to\n take.\n\n Do NOT ask questions and do NOT add an \"Open Questions\" section — decide\n every open point yourself and write the plan as a set of concrete\n decisions. Where a decision is genuinely a judgement call, state the\n choice you made and, in one line, why. The human iterates on the plan by\n editing it directly, not by answering questions.\n\n On a return lap, `<%= it.vars.todoFile %>` carries the human's edits and\n inline comments on your previous plan. Fold every edit and comment into a\n revised, self-contained plan and remove the addressed comments — the plan\n is always the current, clean proposal, never a diff.\n\n Leave `<%= it.vars.todoFile %>` uncommitted and finish your turn — do\n not commit, and do not run `gtd step agent` yourself; the harness does\n that.\n on:\n \"* **\": await-plan\n\n await-plan:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.todoFile %>\n mode: prose\n message: |\n `<%= it.vars.todoFile %>` holds the proposed implementation plan. Read it,\n then either accept it as-is or edit it — rewrite a step, add a\n constraint, or drop an inline comment — to send it back for another round.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"C\":\n to: $onAccept\n action: Accept plan\n describe: >-\n change nothing to move on to implementation (**building**).\n \"* **\":\n to: planning\n action: Revise plan\n describe: >-\n rewrite a step, add a constraint, or leave an inline comment to\n send it back for another round (**planning**).\n\n # ── ENCAPSULATION: the shared review tail ──────────────────────────────────\n humanReview:\n params: [onSignoff]\n states:\n reviewing:\n actor: agent\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n model: <%= it.vars.plannerModel %>\n memory: review\n # `gtd review <commitish>` (a plain non-gtd branch, e.g. a colleague's PR)\n # does NOT enter here directly — it enters `review-precheck` (reviewEntry)\n # first, which runs the green-baseline gate and, once green, transitions\n # here. The `Gtd-Review-Base:` trailer rides on that entry commit (the\n # process's oldest), so everything below still reuses the same\n # reviewing/await-review/feedback machinery over <commitish>..HEAD.\n prompt: |\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. The only state file this\n turn touches is `<%= it.vars.reviewFile %>`; write it at exactly\n that path. Do not create other files to hold notes or output.\n\n Write `<%= it.vars.reviewFile %>` 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 `- [ ] ./path/to/file.ts#42 — note` file\n pointer per hunk (checkboxes are for the human to tick, not you; every\n path is `./`-relative, with an optional `#line` and an optional\n ` — 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 `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n on:\n \"* **\": await-review\n\n await-review:\n actor: human\n label: Awaiting your review\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the cycle rests here, gtd opens a \"review checkout window\": raw HEAD\n # is rewound to the review base (the most-recent `review-deciding` commit —\n # so a feedback round shows only `last-review..HEAD` — or the process start\n # on the first review) with the working tree untouched, surfacing the diff\n # as ordinary uncommitted changes. It closes on the next invocation.\n #\n # Every human step here routes to `review-deciding`, which decides sign-off\n # vs. feedback from the step's CONTENT (a comment, not a tick). Ticking a box\n # only records \"I reviewed this hunk\". Two dead ends can't be told apart by a\n # file-pattern edge, so the sign-off gate (`enforceReviewSignoffGate` in\n # src/program.ts) catches them BEFORE the step commits and refuses: an\n # unfinished review (a box still `- [ ]` with no comment) and a deleted\n # REVIEW.md.\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the cycle — 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 When you've been through the whole diff, run `gtd step human`:\n\n - **Sign off** — tick EVERY box and leave no comment — no note in\n `<%= it.vars.reviewFile %>`, no code edit — to collapse the cycle into\n one commit (**squashing**).\n - **Request changes** — leave a comment: a note on a\n `<%= it.vars.reviewFile %>` line, or a direct code edit — to send it to a\n build + re-review round (**review-deciding** → **feedback-building**). A\n code edit counts as your own fix: the agent completes the follow-through\n it implies and never reverts your lines.\n\n Stepping with a box still unticked and no comment is refused (finish\n reviewing first); so is deleting `<%= it.vars.reviewFile %>`.\n on:\n \"* **\": review-deciding\n\n review-deciding:\n actor: check\n label: Reviewing\n file: <%= it.vars.reviewFile %>\n mode: review\n # Anchors the INCREMENTAL review base: its most-recent in-process commit is\n # the point the previous review round ended, so the next `reviewing` turn\n # (and the review checkout window) covers only the changes since then. On\n # the first review there is no such commit, so the base is the cycle start.\n reviewBase: true\n script: |\n #!/usr/bin/env bash\n # gtd check turn — decide the human's review step from its CONTENT. HEAD is\n # the human's `await-review -> review-deciding` commit; HEAD^ is the\n # `reviewing` commit that wrote the agent's original REVIEW.md (the sign-off\n # gate refuses an incomplete step, so review-deciding is entered exactly\n # once per round — nothing commits in between). It is FEEDBACK when the\n # human left a comment: a note in REVIEW.md (any edit beyond a \"[ ]\"->\"[x]\"\n # tick) OR a hand-edit to any file this round other than `.gtd/` or the\n # state's own reviewFile (which is var-configurable and may live outside\n # `.gtd/`, e.g. a root-level REVIEW.md — issue #128). Otherwise it is a\n # clean sign-off. This turn only CAPTURES the raw material into REVIEW_RAW.md\n # (the `feedback-collecting` agent then turns it into instructions); it does\n # NOT interpret it. Either way, remove REVIEW.md; what the resulting diff\n # MEANS is decided by this state's own `on` rules — the A/M REVIEW_RAW.md\n # rows are declared BEFORE the D REVIEW.md row so a feedback round (which\n # also deletes REVIEW.md) is captured as feedback, not mistaken for a\n # sign-off.\n set +e\n # Hoist the paths once, 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 — it\n # would glue the next line onto it and break the script. Below this point\n # everything is plain bash.\n stateDir=\"<%~ it.vars.stateDir %>\"\n reviewFile=\"<%~ it.vars.reviewFile %>\"\n reviewRawFile=\"<%~ it.vars.reviewRawFile %>\"\n head=$(git rev-parse HEAD)\n mkdir -p \"$(dirname \"$reviewRawFile\")\"\n # A note is any REVIEW.md change beyond a checkbox flip: normalize every\n # \"[ ]\"/\"[x]\" to a placeholder, then compare the agent's original (HEAD^)\n # against the human's version (HEAD).\n before=$(git show \"HEAD^:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n after=$(git show \"HEAD:$reviewFile\" 2>/dev/null | sed -E 's/\\[[ xX]\\]/[_]/g')\n # Did the human hand-edit anything this round besides stateDir and the\n # reviewFile itself (var-configurable — may live outside stateDir, so it\n # is excluded by exact path, not just the stateDir prefix)?\n if git diff-tree --no-commit-id --name-only -r HEAD \\\n | grep -v \"^$stateDir/\" | grep -vxF \"$reviewFile\" | grep -q . \\\n || [ \"$before\" != \"$after\" ]; then\n {\n echo \"Raw review material captured for classification. The human reviewed the changes and left the feedback below; a downstream agent turns it into an instruction list. This file is machine-captured input, not instructions — it names a commit, never inlined content.\"\n echo\n echo \"Commit: $head\"\n echo \"The human's notes are in $reviewFile at this commit; their hand edits,\"\n echo \"if any, are in that commit's other paths. Run: git show $head\"\n } > \"$reviewRawFile\"\n fi\n rm -f \"$reviewFile\"\n on:\n \"A <%= it.vars.reviewRawFile %>\": feedback-collecting\n \"M <%= it.vars.reviewRawFile %>\": feedback-collecting\n \"D <%= it.vars.reviewFile %>\": $onSignoff\n\n feedback-collecting:\n actor: agent\n label: Collecting your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.plannerModel %>\n memory: review\n prompt: |\n You are an autonomous coding agent turning raw review feedback into an\n explicit instruction list for a builder. This workflow steers itself\n through its own state files — treat them as its private scratchpad,\n never as project code or documentation. The only state files this\n turn touches are `<%= it.vars.reviewRawFile %>` (read it, then\n delete it) and `<%= it.vars.reviewFeedbackFile %>` (write it) — do\n NOT change any other file, you classify, you do not build.\n\n The raw review material is: <%~ it.read(it.vars.reviewRawFile) %>\n\n It names a commit — read that commit's diff yourself before classifying\n anything.\n\n Classify every piece of it into `<%= it.vars.reviewFeedbackFile %>` as a\n flat, numbered instruction list. Each item is `N. ./path/to/file.ts#line —\n <imperative>`. Apply these rules exactly:\n\n 1. NOTES the human added to REVIEW.md — each note is a MANDATORY change,\n regardless of whether its checkbox is ticked (ticking means only \"I read\n this hunk\", never sign-off). Anchor each to the `./path#line` its review\n item names (or the chunk it sits under). Emit it as a concrete\n imperative.\n 2. Every COMMENT the human ADDED this round (a `+` line that is a code\n comment, in the hand-edited files) is a MANDATORY instruction the human\n is giving the builder — even a plain-prose one, even if its box is\n ticked. Emit an imperative to satisfy it and note that the comment line\n itself must be REMOVED once addressed. If a comment turns out to describe\n behaviour the code already has, the instruction is still \"confirm the\n code matches and remove the comment line\".\n 3. Every NON-comment code line the human hand-edited is an already-committed\n INTENT, NOT a do-then-delete item. Emit an instruction that names those\n files and says: their lines are final — complete ONLY the follow-through\n they imply (callers, tests, types, docs) and NEVER revert or rewrite\n them. Do not turn their own edit into a task.\n\n Split each hand-edited hunk per line: comment `+` lines follow rule 2, the\n surrounding real code follows rule 3.\n\n If — and only if — nothing in the raw material is actionable (e.g. the human\n left only an approving remark), write `<%= it.vars.reviewFeedbackFile %>`\n containing exactly one line: `NOTHING ACTIONABLE — <one-sentence reason>`.\n Never invent work to satisfy a non-actionable remark.\n\n Delete `<%= it.vars.reviewRawFile %>`, leave everything else uncommitted,\n and finish your turn.\n on:\n \"A <%= it.vars.reviewFeedbackFile %>\": feedback-building\n \"M <%= it.vars.reviewFeedbackFile %>\": feedback-building\n\n feedback-building:\n actor: agent\n label: Addressing your feedback\n file: <%= it.vars.reviewFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n # Refuse a work-free turn: a step whose only pending change is deleting the\n # instructions file (the original \"captured then discarded\" bug). The edge\n # gate (`enforceFeedbackProgressGate` in src/program.ts) reads this flag and\n # exempts a `NOTHING ACTIONABLE` sentinel (a non-actionable review round,\n # which legitimately makes no code change). The pure engine carries it inert.\n requireProgress: true\n prompt: |\n You are an autonomous coding agent addressing review feedback. This\n workflow steers itself through its own state files — treat them as\n its private scratchpad, never as project code or documentation. The\n only state file this turn touches is\n `<%= it.vars.reviewFeedbackFile %>`; address it, then delete it. Do\n not create other files to hold notes or output.\n\n The instructions to implement are: <%~ it.read(it.vars.reviewFeedbackFile) %>\n\n This is a flat instruction list, not a diff — execute every item exactly as\n written, no Q&A, no re-planning, using TDD discipline:\n\n - An item that says the human's own lines are final: build ON TOP of them to\n complete only the follow-through it names (callers, tests, types, docs).\n NEVER revert or rewrite the lines they already committed.\n - An item derived from a human comment: satisfy it AND delete that comment\n line from the code (the human's review comments are transient — none of\n them survive this round).\n\n If the file's sole content is a `NOTHING ACTIONABLE` line, make no code\n change; just delete the instructions file.\n\n Delete `<%= it.vars.reviewFeedbackFile %>` once every item is addressed.\n Leave everything else uncommitted and finish your turn. Deleting the\n instructions file WITHOUT making the changes it asks for is refused — the\n feedback must actually be addressed.\n on:\n \"* **\": checking\n\n # ── ENCAPSULATION: the advanced flow's per-package agentic review ──────────\n specGate:\n params: [onApproved]\n states:\n spec-review:\n actor: agent\n label: Reviewing the package\n model: <%= it.vars.plannerModel %>\n memory: review\n # Bounded per cycle: once entered `max` times, a transition into it is\n # redirected to `closing` (force-close the package and move on — the\n # unresolved concern is deferred to the shared human review at the tail,\n # which sees the whole diff anyway). NOTE the cap is PROCESS-scoped, so it\n # pools across packages — the same documented limitation the shared\n # `fixing` cap has (see STATES.md / docs/configuration.md).\n retry:\n max: 3\n otherwise: $onApproved\n prompt: |\n You are an autonomous coding agent reviewing a freshly-built work package\n against its own spec. This workflow steers itself through its own\n state files — treat them as its private scratchpad, never as project\n code or documentation. The only state file this turn touches is\n `<%= it.vars.specFeedbackFile %>`; 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(it.vars.nextFile) %>\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 `<%= it.vars.specFeedbackFile %>` 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 <%= it.vars.specFeedbackFile %>\": spec-fix\n \"M <%= it.vars.specFeedbackFile %>\": spec-fix\n \"D <%= it.vars.specFeedbackFile %>\": $onApproved\n \"C\": $onApproved\n\n spec-fix:\n actor: agent\n label: Fixing review feedback\n file: <%= it.vars.specFeedbackFile %>\n model: <%= it.vars.coderModel %>\n memory: fix\n prompt: |\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. The only state file this\n turn touches is `<%= it.vars.specFeedbackFile %>`; address it, then\n delete it. Do not create other files to hold notes or output.\n\n Read `<%= it.vars.specFeedbackFile %>` (the reviewer's concerns about the\n current package) and the package spec (`<%= it.read(it.vars.nextFile) %>`),\n then fix the code to resolve every concern. Keep the change focused.\n\n Delete `<%= it.vars.specFeedbackFile %>` once every concern is addressed.\n Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": package-checking\n\n # ── ENCAPSULATION: the advanced flow's per-package build queue ─────────────\n packageLoop:\n params: [onDrained]\n states:\n picking:\n actor: check\n label: Picking the next package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package queue arbiter) — the loop driver executes\n # this verbatim, then steps the check actor. Mechanics only: take the\n # first package file (by name) under the packages dir into NEXT.md, or\n # remove NEXT.md when the queue is empty. What NEXT.md's presence/absence\n # MEANS (another package to build vs. the queue closing out to review) is\n # decided by this state's own `on` rules at capture time — never here.\n set +e\n # Hoist the NEXT path once, at the TOP: Eta's autoTrim eats the newline\n # after an interpolation tag, so no tag may be the last token on a line —\n # it would glue the next line's `else`/`fi` onto it and break the script.\n # Below this point everything is plain bash.\n nextFile=\"<%~ it.vars.nextFile %>\"\n # Sweep the advanced flow's spent Q&A steering files: requirementsFile\n # is consumed by technical-qa, architectureFile by decompose — both are\n # gone by the time the package queue starts. No-op if already removed.\n requirements=\"<%~ it.vars.requirementsFile %>\"\n architecture=\"<%~ it.vars.architectureFile %>\"\n rm -f \"$requirements\" \"$architecture\"\n next=$(ls <%~ it.vars.packagesDir %>/*.md 2>/dev/null | head -n 1)\n if [ -n \"$next\" ]; then\n mkdir -p \"$(dirname \"$nextFile\")\"\n printf '%s' \"$next\" > \"$nextFile\"\n else\n rm -f \"$nextFile\"\n fi\n on:\n \"D <%= it.vars.nextFile %>\": $onDrained\n \"* <%= it.vars.nextFile %>\": package-building\n \"C\": $onDrained\n\n package-building:\n actor: agent\n label: Building\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\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. This turn writes no state\n file: do NOT delete the package file (the `spec-review` gate reads\n it after you) and never touch `<%= it.vars.nextFile %>` — the\n `picking` state owns it.\n\n The package to implement is: <%~ it.read(it.vars.nextFile) %>\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 \"* **\": package-checking\n\n closing:\n actor: check\n label: Closing out the package\n script: |\n #!/usr/bin/env bash\n # gtd check turn (the package closer) — remove the just-reviewed package\n # file (its path is in NEXT.md) plus any leftover spec feedback, so\n # `picking` selects the NEXT package (or falls through to review when the\n # queue is empty). Reached on approval (spec-review clean) and on the\n # spec-review retry cap (force-close, deferring the concern to the tail).\n set +e\n pkg=$(cat <%~ it.vars.nextFile %> 2>/dev/null)\n [ -n \"$pkg\" ] && rm -f \"$pkg\"\n rm -f <%~ it.vars.specFeedbackFile %> <%~ it.vars.nextFile %>\n on:\n \"* **\": picking\n\nuse:\n # Shared health triple: checking/fixing/escalate -> reviewing.\n - submachine: makeGreen\n name: build\n as: { check: checking, fix: fixing, escalate: escalate }\n with:\n onGreen: reviewing\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself (**checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n # Advanced package health triple: package-checking/package-fixing/package-escalate -> spec-review.\n - submachine: makeGreen\n name: package\n as: { check: package-checking, fix: package-fixing, escalate: package-escalate }\n with:\n onGreen: spec-review\n escalateDescribe: >-\n edit the code and/or `.gtd/FEEDBACK.md` yourself (**package-checking**).\n checkLabel: Running checks\n fixLabel: Fixing the check\n escalateLabel: Escalating to a human\n\n # Green-baseline gate: simple flow entry.\n - submachine: assertGreen\n name: plan\n as: { check: plan-precheck, blocked: plan-blocked }\n with:\n onGreen: planning\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\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 (**plan-precheck**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run `gtd fix` from a\n clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: advanced flow entry.\n - submachine: assertGreen\n name: spec\n as: { check: spec-precheck, blocked: spec-blocked }\n with:\n onGreen: product-qa\n blockedMessage: |\n The test baseline is red — gtd will not start new work on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\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 (**spec-precheck**). To repair the baseline as its own separate\n reviewed commit instead, abandon this start and run `gtd fix` from a\n clean `idle`.\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Green-baseline gate: `gtd review` entry (reviewEntry -> reviewing).\n - submachine: assertGreen\n name: review\n as: { check: review-precheck, blocked: review-blocked }\n set:\n check: { reviewEntry: true }\n with:\n onGreen: reviewing\n blockedMessage: |\n The test baseline is red — gtd will not start a review on a broken suite.\n `<%= it.vars.feedbackFile %>` holds the failing output.\n\n What each change does next (then run `gtd step human`):\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-precheck**).\n checkLabel: Checking the baseline\n blockedLabel: Baseline is red\n\n # Advanced flow: product Q&A loop (product-qa/product-answer -> technical-qa).\n - submachine: qaLoop\n name: product\n as: { author: product-qa, answer: product-answer }\n with:\n authorFile: <%= it.vars.requirementsFile %>\n onAdvance: technical-qa\n authorPrompt: |\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. The only state file this turn\n touches is `<%= it.vars.requirementsFile %>`; develop it. Do not\n create other files to hold notes or output.\n\n `<%= it.vars.requirementsFile %>` holds the product requirements (or the\n plan you already wrote plus the human's answers on a return lap). Read it,\n explore the codebase, and develop it into a concrete, PRODUCT-level plan\n in this one turn.\n\n Scope: product and user-facing decisions only. Leave implementation\n details (file/module structure, data models, tech-stack choices) for the\n next phase (`<%= it.vars.architectureFile %>`).\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading near the top: one `### <question>` sub-heading\n each, followed by a checkbox list of candidate answers — aim for 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. The\n last `- [ ] _your answer_` line is the human's own-answer slot; write it\n with the literal `_your answer_` placeholder and leave it for them.\n\n On a RETURN lap the human has ticked one option per question (or replaced\n `_your answer_` with their own text). For each answered question: fold the\n chosen answer into the plan prose, then MOVE the question down into a\n `## Answered Questions` section as `### <question>` followed by the\n resolved answer in plain PROSE (drop the checkboxes). Never re-raise a\n question the human deleted; if the human deleted the whole\n `## Open Questions` section, treat the plan as accepted and surface no new\n questions unless something is genuinely still blocking. Omit\n `## Open Questions` entirely once there are none left.\n\n Leave `<%= it.vars.requirementsFile %>` uncommitted and finish your turn.\n answerMessage: |\n `<%= it.vars.requirementsFile %>` holds the product plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n with no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to move on to\n technical Q&A (**technical-qa**).\n editDescribe: >-\n tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**product-qa**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the product plan\n answerLabel: Awaiting your answers\n\n # Advanced flow: technical Q&A loop (technical-qa/technical-answer -> decompose).\n - submachine: qaLoop\n name: technical\n as: { author: technical-qa, answer: technical-answer }\n with:\n authorFile: <%= it.vars.architectureFile %>\n onAdvance: decompose\n authorPrompt: |\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. The only state files this\n turn touches are `<%= it.vars.architectureFile %>` (write it) and\n `<%= it.vars.requirementsFile %>` (delete it once its content is\n folded in). Do not create other files to hold notes or output.\n\n Read `<%= it.vars.requirementsFile %>` (the converged product plan) and\n develop `<%= it.vars.architectureFile %>` from it in this one turn:\n file/module structure, data models, library/tech-stack choices,\n error-handling strategy — the *how*, building on the *what* already\n settled. Do not re-open product-level decisions; treat them as settled.\n\n For every open point you cannot settle yourself, add a question under a\n `## Open Questions` heading exactly as the grilling phase does: one\n `### <question>` each, followed by a checkbox list — TWO concrete options\n plus a final free-text slot, ALL left UNTICKED:\n\n ### <the question>\n\n - [ ] <first option>\n - [ ] <second option>\n - [ ] _your answer_\n\n Never tick a box yourself. On a RETURN lap the human has ticked one option\n per question (or replaced `_your answer_` with their own text): fold each\n chosen answer into the plan prose, then MOVE the question into a\n `## Answered Questions` section as `### <question>` + the resolved answer\n in plain PROSE (drop the checkboxes). Never re-raise a deleted question;\n treat a deleted `## Open Questions` section as acceptance. Omit\n `## Open Questions` once there are none left.\n\n Once `<%= it.vars.architectureFile %>` is written, delete\n `<%= it.vars.requirementsFile %>` — its content has been folded in and it\n must not linger. Leave everything uncommitted and finish your turn.\n answerMessage: |\n `<%= it.vars.architectureFile %>` holds the technical plan under\n development. Each question under `## Open Questions` offers a few options\n plus a `- [ ] _your answer_` slot. Answer EVERY question by ticking\n exactly one box (`- [x]`); for your own answer, replace `_your answer_`\n with your text and tick that line. Stepping is refused while any question\n is unanswered.\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n advanceDescribe: >-\n with no open questions left (the agent surfaced none, or you deleted\n the section and it finalized the plan), change nothing to move on to\n decomposing the work into packages (**decompose**).\n editDescribe: >-\n tick exactly one option per open question (replace `_your answer_` for\n your own) to send the plan back for the agent to fold your answers in\n (**technical-qa**). Delete a question to skip it, or delete the whole\n `## Open Questions` section to accept the plan as-is.\n authorLabel: Refining the technical plan\n answerLabel: Awaiting your answers\n\n # Simple flow plan-iteration loop (planning/await-plan -> building).\n - submachine: planLoop\n with:\n onAccept: building\n\n # Shared review tail (reviewing -> await-review -> ... -> squashing).\n - submachine: humanReview\n with:\n onSignoff: squashing\n\n # Advanced per-package agentic review (spec-review/spec-fix -> closing).\n - submachine: specGate\n with:\n onApproved: closing\n\n # Advanced per-package build queue (picking/package-building/closing -> reviewing).\n - submachine: packageLoop\n with:\n onDrained: reviewing\n\nstates:\n # ── Shared entry ────────────────────────────────────────────────────────\n idle:\n actor: human\n initial: true\n label: Idle\n # Every human-gate `message:` below closes with the same rendered \"What\n # each change does next\" list, built from `it.edges` (the resting state's\n # own `on` edges — each edge's `describe:` is the single source of truth).\n message: |\n No active gtd cycle.\n\n To start one, create a steering file describing what you want built:\n\n - `<%= it.vars.todoFile %>` — a short sketch — starts the SIMPLE flow\n (plan, build, review).\n - `<%= it.vars.requirementsFile %>` — the product requirements — starts\n the ADVANCED flow (product + technical Q&A, package decomposition,\n parallel build, per-package agentic review).\n\n What each change does next (then run `gtd step human`):\n <% it.edges.forEach(function (e) { if (e.describe) { %>\n <%~ \"- \" + (e.action ? \"**\" + e.action + \"** — \" : \"\") + e.describe + \"\\n\" %>\n <% } }) %>\n on:\n \"* <%= it.vars.requirementsFile %>\":\n to: spec-precheck\n action: Start advanced flow\n describe: >-\n save the product requirements to `.gtd/REQUIREMENTS.md` — gtd first\n checks the test baseline is green, then the agent develops them\n through product then technical Q&A, decomposes the work into\n packages, and builds each in parallel (**spec-precheck**).\n \"* **\":\n to: plan-precheck\n action: Start simple flow\n describe: >-\n save a short sketch to `.gtd/TODO.md` (or any other change) — gtd\n first checks the test baseline is green, then the agent reads it,\n explores the codebase, and develops it into a concrete\n implementation plan (**plan-precheck**).\n\n # ── Simple flow: monolithic build (plan loop is the `planLoop` sub-machine) ─\n building:\n actor: agent\n label: Building\n file: <%= it.vars.todoFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state file this turn touches is\n `<%= it.vars.todoFile %>`; delete it once its work is complete. Do not\n create other files to hold notes or output.\n\n The plan to implement is: <%~ it.read(it.vars.todoFile) %>\n\n Read that plan and implement it in full. Use TDD discipline: one test,\n then the implementation that passes it, then the next — never write all\n the tests first.\n\n Delete `<%= it.vars.todoFile %>` once the work it describes is complete\n and verified. Leave everything else uncommitted and finish your turn.\n on:\n \"* **\": checking\n\n # ── Advanced flow: decomposition (Q&A loops are the `qaLoop` sub-machine) ───\n decompose:\n actor: agent\n label: Decomposing into packages\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. This workflow steers itself through\n its own state files — treat them as its private scratchpad, never as\n project code or documentation. The only state files this turn touches\n are the package files it writes under `<%= it.vars.packagesDir %>/` and\n `<%= it.vars.architectureFile %>`, which it deletes. Do not create other\n files to hold notes or output.\n\n Read `<%= it.vars.architectureFile %>` (the converged technical plan) and\n decompose it into an ordered set of WORK PACKAGES, one file each under\n `<%= it.vars.packagesDir %>/` (e.g. `<%= it.vars.packagesDir %>/01-name.md`,\n `<%= it.vars.packagesDir %>/02-name.md`, ...). Order the packages so the\n test suite can pass again after each one lands.\n\n Each package file describes a set of INDEPENDENT tasks that can be\n implemented concurrently without ordering constraints between them — a\n single build turn will fan them out to parallel subagents. Give each task\n a clear description, acceptance criteria as `- [ ]` checkboxes, and the\n relevant paths. Also state the package's overall requirement so it can be\n reviewed against its spec. Do NOT put ordering-dependent tasks in the same\n package — split them across ordered packages instead.\n\n No package file may reference any other `.gtd/` file. Once the package\n files are written, delete `<%= it.vars.architectureFile %>`. Leave\n everything uncommitted and finish your turn.\n on:\n \"* <%= it.vars.packagesDir %>/**\": picking\n\n # ── Green-baseline gate: `gtd fix` entry ──────────────────────────────────\n # `gtd fix` enters HERE (fixEntry): run the suite, then a red run drops\n # straight into the shared `fixing` loop (-> checking -> review + squash\n # tail); a green run is a no-op back to `idle` (nothing to fix).\n fix-precheck:\n actor: check\n fixEntry: true\n label: Checking the baseline\n script: *suiteCheck\n on:\n \"A <%= it.vars.feedbackFile %>\": fixing\n \"M <%= it.vars.feedbackFile %>\": fixing\n \"D <%= it.vars.feedbackFile %>\": idle\n \"C\": idle\n\n # ── Shared tail: squash finale (review loop is the `humanReview` sub-machine) ─\n squashing:\n actor: agent\n label: Squashing\n file: <%= it.vars.commitMsgFile %>\n model: <%= it.vars.coderModel %>\n memory: build\n prompt: |\n You are an autonomous coding agent. The cycle is approved and done. This\n workflow steers itself through its own state files — treat them as its\n private scratchpad, never as project code or documentation. The only\n state file this turn touches is `<%= it.vars.commitMsgFile %>`; write\n it. Do not create other files to hold notes or output.\n\n Leave `<%= it.vars.commitMsgFile %>` uncommitted and finish your turn —\n entering this file squashes everything this process retains into one\n commit using its content as the message. This prompt carries no diff —\n read the range yourself; it runs from `<%= it.retainedBase %>` to the\n working tree, and is exactly what that commit will contain, the changes\n THIS process made (its trace/retry boundary onward). For a normal build\n cycle that is the whole feature; for a `gtd review` process it is only\n the fixes made during the review — NOT the changeset that was under\n review.\n\n If that range is non-empty, write `<%= it.vars.commitMsgFile %>` with ONE\n conventional-commits message for the changes (subject line\n `type(scope): subject`, imperative mood, <= 72 characters; a body\n explaining the why, trade-offs, and key decisions). Plain text, no\n markdown wrapper.\n\n If that range is empty (a clean review sign-off with no fixes), write\n `<%= it.vars.commitMsgFile %>` with exactly this one line and nothing\n else:\n\n chore: human review\n on:\n \"A <%= it.vars.commitMsgFile %>\": done\n \"M <%= it.vars.commitMsgFile %>\": done\n\n done:\n commit: |\n <%~ it.read(it.vars.commitMsgFile) %>\n\n Token cost: <%= it.processCost %>\n <% it.processCostByModel.forEach(function(m){ %>\n - <%= m.model %>: <%= m.cost %>\n <% }) %>\n";
|
|
190636
190635
|
//#endregion
|
|
190637
190636
|
//#region src/workflows/templates.ts
|
|
190638
190637
|
/**
|
|
@@ -191230,700 +191229,7 @@ var EnvVars = class EnvVars extends Tag("EnvVars")() {
|
|
|
191230
191229
|
static Live = EnvVars.layer(process.env);
|
|
191231
191230
|
};
|
|
191232
191231
|
//#endregion
|
|
191233
|
-
//#region node_modules/diff/libesm/diff/base.js
|
|
191234
|
-
var Diff = class {
|
|
191235
|
-
diff(oldStr, newStr, options = {}) {
|
|
191236
|
-
let callback;
|
|
191237
|
-
if (typeof options === "function") {
|
|
191238
|
-
callback = options;
|
|
191239
|
-
options = {};
|
|
191240
|
-
} else if ("callback" in options) callback = options.callback;
|
|
191241
|
-
const oldString = this.castInput(oldStr, options);
|
|
191242
|
-
const newString = this.castInput(newStr, options);
|
|
191243
|
-
const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
|
|
191244
|
-
const newTokens = this.removeEmpty(this.tokenize(newString, options));
|
|
191245
|
-
return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
|
|
191246
|
-
}
|
|
191247
|
-
diffWithOptionsObj(oldTokens, newTokens, options, callback) {
|
|
191248
|
-
var _a;
|
|
191249
|
-
const done = (value) => {
|
|
191250
|
-
value = this.postProcess(value, options);
|
|
191251
|
-
if (callback) {
|
|
191252
|
-
setTimeout(function() {
|
|
191253
|
-
callback(value);
|
|
191254
|
-
}, 0);
|
|
191255
|
-
return;
|
|
191256
|
-
} else return value;
|
|
191257
|
-
};
|
|
191258
|
-
const newLen = newTokens.length, oldLen = oldTokens.length;
|
|
191259
|
-
let editLength = 1;
|
|
191260
|
-
let maxEditLength = newLen + oldLen;
|
|
191261
|
-
if (options.maxEditLength != null) maxEditLength = Math.min(maxEditLength, options.maxEditLength);
|
|
191262
|
-
const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
|
|
191263
|
-
const abortAfterTimestamp = Date.now() + maxExecutionTime;
|
|
191264
|
-
const bestPath = [{
|
|
191265
|
-
oldPos: -1,
|
|
191266
|
-
lastComponent: void 0
|
|
191267
|
-
}];
|
|
191268
|
-
let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
|
|
191269
|
-
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
|
|
191270
|
-
let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
|
|
191271
|
-
const execEditLength = () => {
|
|
191272
|
-
for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
|
|
191273
|
-
let basePath;
|
|
191274
|
-
const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
|
|
191275
|
-
if (removePath) bestPath[diagonalPath - 1] = void 0;
|
|
191276
|
-
let canAdd = false;
|
|
191277
|
-
if (addPath) {
|
|
191278
|
-
const addPathNewPos = addPath.oldPos - diagonalPath;
|
|
191279
|
-
canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
|
|
191280
|
-
}
|
|
191281
|
-
const canRemove = removePath && removePath.oldPos + 1 < oldLen;
|
|
191282
|
-
if (!canAdd && !canRemove) {
|
|
191283
|
-
bestPath[diagonalPath] = void 0;
|
|
191284
|
-
continue;
|
|
191285
|
-
}
|
|
191286
|
-
if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) basePath = this.addToPath(addPath, true, false, 0, options);
|
|
191287
|
-
else basePath = this.addToPath(removePath, false, true, 1, options);
|
|
191288
|
-
newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
|
|
191289
|
-
if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
|
|
191290
|
-
else {
|
|
191291
|
-
bestPath[diagonalPath] = basePath;
|
|
191292
|
-
if (basePath.oldPos + 1 >= oldLen) maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
|
|
191293
|
-
if (newPos + 1 >= newLen) minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
|
|
191294
|
-
}
|
|
191295
|
-
}
|
|
191296
|
-
editLength++;
|
|
191297
|
-
};
|
|
191298
|
-
if (callback) (function exec() {
|
|
191299
|
-
setTimeout(function() {
|
|
191300
|
-
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) return callback(void 0);
|
|
191301
|
-
if (!execEditLength()) exec();
|
|
191302
|
-
}, 0);
|
|
191303
|
-
})();
|
|
191304
|
-
else while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
|
|
191305
|
-
const ret = execEditLength();
|
|
191306
|
-
if (ret) return ret;
|
|
191307
|
-
}
|
|
191308
|
-
}
|
|
191309
|
-
addToPath(path, added, removed, oldPosInc, options) {
|
|
191310
|
-
const last = path.lastComponent;
|
|
191311
|
-
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) return {
|
|
191312
|
-
oldPos: path.oldPos + oldPosInc,
|
|
191313
|
-
lastComponent: {
|
|
191314
|
-
count: last.count + 1,
|
|
191315
|
-
added,
|
|
191316
|
-
removed,
|
|
191317
|
-
previousComponent: last.previousComponent
|
|
191318
|
-
}
|
|
191319
|
-
};
|
|
191320
|
-
else return {
|
|
191321
|
-
oldPos: path.oldPos + oldPosInc,
|
|
191322
|
-
lastComponent: {
|
|
191323
|
-
count: 1,
|
|
191324
|
-
added,
|
|
191325
|
-
removed,
|
|
191326
|
-
previousComponent: last
|
|
191327
|
-
}
|
|
191328
|
-
};
|
|
191329
|
-
}
|
|
191330
|
-
extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
|
|
191331
|
-
const newLen = newTokens.length, oldLen = oldTokens.length;
|
|
191332
|
-
let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
|
|
191333
|
-
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
|
|
191334
|
-
newPos++;
|
|
191335
|
-
oldPos++;
|
|
191336
|
-
commonCount++;
|
|
191337
|
-
if (options.oneChangePerToken) basePath.lastComponent = {
|
|
191338
|
-
count: 1,
|
|
191339
|
-
previousComponent: basePath.lastComponent,
|
|
191340
|
-
added: false,
|
|
191341
|
-
removed: false
|
|
191342
|
-
};
|
|
191343
|
-
}
|
|
191344
|
-
if (commonCount && !options.oneChangePerToken) basePath.lastComponent = {
|
|
191345
|
-
count: commonCount,
|
|
191346
|
-
previousComponent: basePath.lastComponent,
|
|
191347
|
-
added: false,
|
|
191348
|
-
removed: false
|
|
191349
|
-
};
|
|
191350
|
-
basePath.oldPos = oldPos;
|
|
191351
|
-
return newPos;
|
|
191352
|
-
}
|
|
191353
|
-
equals(left, right, options) {
|
|
191354
|
-
if (options.comparator) return options.comparator(left, right);
|
|
191355
|
-
else return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
|
|
191356
|
-
}
|
|
191357
|
-
removeEmpty(array) {
|
|
191358
|
-
const ret = [];
|
|
191359
|
-
for (let i = 0; i < array.length; i++) if (array[i]) ret.push(array[i]);
|
|
191360
|
-
return ret;
|
|
191361
|
-
}
|
|
191362
|
-
castInput(value, options) {
|
|
191363
|
-
return value;
|
|
191364
|
-
}
|
|
191365
|
-
tokenize(value, options) {
|
|
191366
|
-
return Array.from(value);
|
|
191367
|
-
}
|
|
191368
|
-
join(chars) {
|
|
191369
|
-
return chars.join("");
|
|
191370
|
-
}
|
|
191371
|
-
postProcess(changeObjects, options) {
|
|
191372
|
-
return changeObjects;
|
|
191373
|
-
}
|
|
191374
|
-
get useLongestToken() {
|
|
191375
|
-
return false;
|
|
191376
|
-
}
|
|
191377
|
-
buildValues(lastComponent, newTokens, oldTokens) {
|
|
191378
|
-
const components = [];
|
|
191379
|
-
let nextComponent;
|
|
191380
|
-
while (lastComponent) {
|
|
191381
|
-
components.push(lastComponent);
|
|
191382
|
-
nextComponent = lastComponent.previousComponent;
|
|
191383
|
-
delete lastComponent.previousComponent;
|
|
191384
|
-
lastComponent = nextComponent;
|
|
191385
|
-
}
|
|
191386
|
-
components.reverse();
|
|
191387
|
-
const componentLen = components.length;
|
|
191388
|
-
let componentPos = 0, newPos = 0, oldPos = 0;
|
|
191389
|
-
for (; componentPos < componentLen; componentPos++) {
|
|
191390
|
-
const component = components[componentPos];
|
|
191391
|
-
if (!component.removed) {
|
|
191392
|
-
if (!component.added && this.useLongestToken) {
|
|
191393
|
-
let value = newTokens.slice(newPos, newPos + component.count);
|
|
191394
|
-
value = value.map(function(value, i) {
|
|
191395
|
-
const oldValue = oldTokens[oldPos + i];
|
|
191396
|
-
return oldValue.length > value.length ? oldValue : value;
|
|
191397
|
-
});
|
|
191398
|
-
component.value = this.join(value);
|
|
191399
|
-
} else component.value = this.join(newTokens.slice(newPos, newPos + component.count));
|
|
191400
|
-
newPos += component.count;
|
|
191401
|
-
if (!component.added) oldPos += component.count;
|
|
191402
|
-
} else {
|
|
191403
|
-
component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
|
|
191404
|
-
oldPos += component.count;
|
|
191405
|
-
}
|
|
191406
|
-
}
|
|
191407
|
-
return components;
|
|
191408
|
-
}
|
|
191409
|
-
};
|
|
191410
|
-
//#endregion
|
|
191411
|
-
//#region node_modules/diff/libesm/diff/character.js
|
|
191412
|
-
var CharacterDiff = class extends Diff {};
|
|
191413
|
-
new CharacterDiff();
|
|
191414
|
-
//#endregion
|
|
191415
|
-
//#region node_modules/diff/libesm/util/string.js
|
|
191416
|
-
function longestCommonPrefix(str1, str2) {
|
|
191417
|
-
let i;
|
|
191418
|
-
for (i = 0; i < str1.length && i < str2.length; i++) if (str1[i] != str2[i]) return str1.slice(0, i);
|
|
191419
|
-
return str1.slice(0, i);
|
|
191420
|
-
}
|
|
191421
|
-
function longestCommonSuffix(str1, str2) {
|
|
191422
|
-
let i;
|
|
191423
|
-
if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) return "";
|
|
191424
|
-
for (i = 0; i < str1.length && i < str2.length; i++) if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) return str1.slice(-i);
|
|
191425
|
-
return str1.slice(-i);
|
|
191426
|
-
}
|
|
191427
|
-
function replacePrefix(string, oldPrefix, newPrefix) {
|
|
191428
|
-
if (string.slice(0, oldPrefix.length) != oldPrefix) throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
|
|
191429
|
-
return newPrefix + string.slice(oldPrefix.length);
|
|
191430
|
-
}
|
|
191431
|
-
function replaceSuffix(string, oldSuffix, newSuffix) {
|
|
191432
|
-
if (!oldSuffix) return string + newSuffix;
|
|
191433
|
-
if (string.slice(-oldSuffix.length) != oldSuffix) throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`);
|
|
191434
|
-
return string.slice(0, -oldSuffix.length) + newSuffix;
|
|
191435
|
-
}
|
|
191436
|
-
function removePrefix(string, oldPrefix) {
|
|
191437
|
-
return replacePrefix(string, oldPrefix, "");
|
|
191438
|
-
}
|
|
191439
|
-
function removeSuffix(string, oldSuffix) {
|
|
191440
|
-
return replaceSuffix(string, oldSuffix, "");
|
|
191441
|
-
}
|
|
191442
|
-
function maximumOverlap(string1, string2) {
|
|
191443
|
-
return string2.slice(0, overlapCount(string1, string2));
|
|
191444
|
-
}
|
|
191445
|
-
function overlapCount(a, b) {
|
|
191446
|
-
let startA = 0;
|
|
191447
|
-
if (a.length > b.length) startA = a.length - b.length;
|
|
191448
|
-
let endB = b.length;
|
|
191449
|
-
if (a.length < b.length) endB = a.length;
|
|
191450
|
-
const map = Array(endB);
|
|
191451
|
-
let k = 0;
|
|
191452
|
-
map[0] = 0;
|
|
191453
|
-
for (let j = 1; j < endB; j++) {
|
|
191454
|
-
if (b[j] == b[k]) map[j] = map[k];
|
|
191455
|
-
else map[j] = k;
|
|
191456
|
-
while (k > 0 && b[j] != b[k]) k = map[k];
|
|
191457
|
-
if (b[j] == b[k]) k++;
|
|
191458
|
-
}
|
|
191459
|
-
k = 0;
|
|
191460
|
-
for (let i = startA; i < a.length; i++) {
|
|
191461
|
-
while (k > 0 && a[i] != b[k]) k = map[k];
|
|
191462
|
-
if (a[i] == b[k]) k++;
|
|
191463
|
-
}
|
|
191464
|
-
return k;
|
|
191465
|
-
}
|
|
191466
|
-
/**
|
|
191467
|
-
* Split a string into segments using a word segmenter, merging consecutive
|
|
191468
|
-
* segments if they are both whitespace segments. Whitespace segments can
|
|
191469
|
-
* appear adjacent to one another for two reasons:
|
|
191470
|
-
* - newlines always get their own segment
|
|
191471
|
-
* - where a diacritic is attached to a whitespace character in the text, the
|
|
191472
|
-
* segment ends after the diacritic, so e.g. " \u0300 " becomes two segments.
|
|
191473
|
-
* This function therefore runs the segmenter's .segment() method and then
|
|
191474
|
-
* merges consecutive segments of whitespace into a single part.
|
|
191475
|
-
*/
|
|
191476
|
-
function segment(string, segmenter) {
|
|
191477
|
-
const parts = [];
|
|
191478
|
-
for (const segmentObj of Array.from(segmenter.segment(string))) {
|
|
191479
|
-
const segment = segmentObj.segment;
|
|
191480
|
-
if (parts.length && /\s/.test(parts[parts.length - 1]) && /\s/.test(segment)) parts[parts.length - 1] += segment;
|
|
191481
|
-
else parts.push(segment);
|
|
191482
|
-
}
|
|
191483
|
-
return parts;
|
|
191484
|
-
}
|
|
191485
|
-
function trailingWs(string, segmenter) {
|
|
191486
|
-
if (segmenter) return leadingAndTrailingWs(string, segmenter)[1];
|
|
191487
|
-
let i;
|
|
191488
|
-
for (i = string.length - 1; i >= 0; i--) if (!string[i].match(/\s/)) break;
|
|
191489
|
-
return string.substring(i + 1);
|
|
191490
|
-
}
|
|
191491
|
-
function leadingWs(string, segmenter) {
|
|
191492
|
-
if (segmenter) return leadingAndTrailingWs(string, segmenter)[0];
|
|
191493
|
-
const match = string.match(/^\s*/);
|
|
191494
|
-
return match ? match[0] : "";
|
|
191495
|
-
}
|
|
191496
|
-
function leadingAndTrailingWs(string, segmenter) {
|
|
191497
|
-
if (!segmenter) return [leadingWs(string), trailingWs(string)];
|
|
191498
|
-
if (segmenter.resolvedOptions().granularity != "word") throw new Error("The segmenter passed must have a granularity of \"word\"");
|
|
191499
|
-
const segments = segment(string, segmenter);
|
|
191500
|
-
const firstSeg = segments[0];
|
|
191501
|
-
const lastSeg = segments[segments.length - 1];
|
|
191502
|
-
return [/\s/.test(firstSeg) ? firstSeg : "", /\s/.test(lastSeg) ? lastSeg : ""];
|
|
191503
|
-
}
|
|
191504
|
-
//#endregion
|
|
191505
|
-
//#region node_modules/diff/libesm/diff/word.js
|
|
191506
|
-
const extendedWordChars = "a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}";
|
|
191507
|
-
const tokenizeIncludingWhitespace = new RegExp(`[${extendedWordChars}]+|\\s+|[^${extendedWordChars}]`, "ug");
|
|
191508
|
-
var WordDiff = class extends Diff {
|
|
191509
|
-
equals(left, right, options) {
|
|
191510
|
-
if (options.ignoreCase) {
|
|
191511
|
-
left = left.toLowerCase();
|
|
191512
|
-
right = right.toLowerCase();
|
|
191513
|
-
}
|
|
191514
|
-
return left.trim() === right.trim();
|
|
191515
|
-
}
|
|
191516
|
-
tokenize(value, options = {}) {
|
|
191517
|
-
let parts;
|
|
191518
|
-
if (options.intlSegmenter) {
|
|
191519
|
-
const segmenter = options.intlSegmenter;
|
|
191520
|
-
if (segmenter.resolvedOptions().granularity != "word") throw new Error("The segmenter passed must have a granularity of \"word\"");
|
|
191521
|
-
parts = segment(value, segmenter);
|
|
191522
|
-
} else parts = value.match(tokenizeIncludingWhitespace) || [];
|
|
191523
|
-
const tokens = [];
|
|
191524
|
-
let prevPart = null;
|
|
191525
|
-
parts.forEach((part) => {
|
|
191526
|
-
if (/\s/.test(part)) if (prevPart == null) tokens.push(part);
|
|
191527
|
-
else tokens.push(tokens.pop() + part);
|
|
191528
|
-
else if (prevPart != null && /\s/.test(prevPart)) if (tokens[tokens.length - 1] == prevPart) tokens.push(tokens.pop() + part);
|
|
191529
|
-
else tokens.push(prevPart + part);
|
|
191530
|
-
else tokens.push(part);
|
|
191531
|
-
prevPart = part;
|
|
191532
|
-
});
|
|
191533
|
-
return tokens;
|
|
191534
|
-
}
|
|
191535
|
-
join(tokens) {
|
|
191536
|
-
return tokens.map((token, i) => {
|
|
191537
|
-
if (i == 0) return token;
|
|
191538
|
-
else return token.replace(/^\s+/, "");
|
|
191539
|
-
}).join("");
|
|
191540
|
-
}
|
|
191541
|
-
postProcess(changes, options) {
|
|
191542
|
-
if (!changes || options.oneChangePerToken) return changes;
|
|
191543
|
-
let lastKeep = null;
|
|
191544
|
-
let insertion = null;
|
|
191545
|
-
let deletion = null;
|
|
191546
|
-
changes.forEach((change) => {
|
|
191547
|
-
if (change.added) insertion = change;
|
|
191548
|
-
else if (change.removed) deletion = change;
|
|
191549
|
-
else {
|
|
191550
|
-
if (insertion || deletion) dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change, options.intlSegmenter);
|
|
191551
|
-
lastKeep = change;
|
|
191552
|
-
insertion = null;
|
|
191553
|
-
deletion = null;
|
|
191554
|
-
}
|
|
191555
|
-
});
|
|
191556
|
-
if (insertion || deletion) dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null, options.intlSegmenter);
|
|
191557
|
-
return changes;
|
|
191558
|
-
}
|
|
191559
|
-
};
|
|
191560
|
-
new WordDiff();
|
|
191561
|
-
function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep, segmenter) {
|
|
191562
|
-
if (deletion && insertion) {
|
|
191563
|
-
const [oldWsPrefix, oldWsSuffix] = leadingAndTrailingWs(deletion.value, segmenter);
|
|
191564
|
-
const [newWsPrefix, newWsSuffix] = leadingAndTrailingWs(insertion.value, segmenter);
|
|
191565
|
-
if (startKeep) {
|
|
191566
|
-
const commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix);
|
|
191567
|
-
startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix);
|
|
191568
|
-
deletion.value = removePrefix(deletion.value, commonWsPrefix);
|
|
191569
|
-
insertion.value = removePrefix(insertion.value, commonWsPrefix);
|
|
191570
|
-
}
|
|
191571
|
-
if (endKeep) {
|
|
191572
|
-
const commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix);
|
|
191573
|
-
endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix);
|
|
191574
|
-
deletion.value = removeSuffix(deletion.value, commonWsSuffix);
|
|
191575
|
-
insertion.value = removeSuffix(insertion.value, commonWsSuffix);
|
|
191576
|
-
}
|
|
191577
|
-
} else if (insertion) {
|
|
191578
|
-
if (startKeep) {
|
|
191579
|
-
const ws = leadingWs(insertion.value, segmenter);
|
|
191580
|
-
insertion.value = insertion.value.substring(ws.length);
|
|
191581
|
-
}
|
|
191582
|
-
if (endKeep) {
|
|
191583
|
-
const ws = leadingWs(endKeep.value, segmenter);
|
|
191584
|
-
endKeep.value = endKeep.value.substring(ws.length);
|
|
191585
|
-
}
|
|
191586
|
-
} else if (startKeep && endKeep) {
|
|
191587
|
-
const newWsFull = leadingWs(endKeep.value, segmenter), [delWsStart, delWsEnd] = leadingAndTrailingWs(deletion.value, segmenter);
|
|
191588
|
-
const newWsStart = longestCommonPrefix(newWsFull, delWsStart);
|
|
191589
|
-
deletion.value = removePrefix(deletion.value, newWsStart);
|
|
191590
|
-
const newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd);
|
|
191591
|
-
deletion.value = removeSuffix(deletion.value, newWsEnd);
|
|
191592
|
-
endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd);
|
|
191593
|
-
startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length));
|
|
191594
|
-
} else if (endKeep) {
|
|
191595
|
-
const endKeepWsPrefix = leadingWs(endKeep.value, segmenter);
|
|
191596
|
-
const overlap = maximumOverlap(trailingWs(deletion.value, segmenter), endKeepWsPrefix);
|
|
191597
|
-
deletion.value = removeSuffix(deletion.value, overlap);
|
|
191598
|
-
} else if (startKeep) {
|
|
191599
|
-
const overlap = maximumOverlap(trailingWs(startKeep.value, segmenter), leadingWs(deletion.value, segmenter));
|
|
191600
|
-
deletion.value = removePrefix(deletion.value, overlap);
|
|
191601
|
-
}
|
|
191602
|
-
}
|
|
191603
|
-
var WordsWithSpaceDiff = class extends Diff {
|
|
191604
|
-
tokenize(value) {
|
|
191605
|
-
const regex = new RegExp(`(\\r?\\n)|[${extendedWordChars}]+|[^\\S\\n\\r]+|[^${extendedWordChars}]`, "ug");
|
|
191606
|
-
return value.match(regex) || [];
|
|
191607
|
-
}
|
|
191608
|
-
};
|
|
191609
|
-
new WordsWithSpaceDiff();
|
|
191610
|
-
//#endregion
|
|
191611
|
-
//#region node_modules/diff/libesm/diff/line.js
|
|
191612
|
-
var LineDiff = class extends Diff {
|
|
191613
|
-
constructor() {
|
|
191614
|
-
super(...arguments);
|
|
191615
|
-
this.tokenize = tokenize;
|
|
191616
|
-
}
|
|
191617
|
-
equals(left, right, options) {
|
|
191618
|
-
if (options.ignoreWhitespace) {
|
|
191619
|
-
if (!options.newlineIsToken || !left.includes("\n")) left = left.trim();
|
|
191620
|
-
if (!options.newlineIsToken || !right.includes("\n")) right = right.trim();
|
|
191621
|
-
} else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
|
|
191622
|
-
if (left.endsWith("\n")) left = left.slice(0, -1);
|
|
191623
|
-
if (right.endsWith("\n")) right = right.slice(0, -1);
|
|
191624
|
-
}
|
|
191625
|
-
return super.equals(left, right, options);
|
|
191626
|
-
}
|
|
191627
|
-
};
|
|
191628
|
-
const lineDiff = new LineDiff();
|
|
191629
|
-
function diffLines(oldStr, newStr, options) {
|
|
191630
|
-
return lineDiff.diff(oldStr, newStr, options);
|
|
191631
|
-
}
|
|
191632
|
-
function tokenize(value, options) {
|
|
191633
|
-
if (options.stripTrailingCr) value = value.replace(/\r\n/g, "\n");
|
|
191634
|
-
const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
|
|
191635
|
-
if (!linesAndNewlines[linesAndNewlines.length - 1]) linesAndNewlines.pop();
|
|
191636
|
-
for (let i = 0; i < linesAndNewlines.length; i++) {
|
|
191637
|
-
const line = linesAndNewlines[i];
|
|
191638
|
-
if (i % 2 && !options.newlineIsToken) retLines[retLines.length - 1] += line;
|
|
191639
|
-
else retLines.push(line);
|
|
191640
|
-
}
|
|
191641
|
-
return retLines;
|
|
191642
|
-
}
|
|
191643
|
-
//#endregion
|
|
191644
|
-
//#region node_modules/diff/libesm/diff/sentence.js
|
|
191645
|
-
function isSentenceEndPunct(char) {
|
|
191646
|
-
return char == "." || char == "!" || char == "?";
|
|
191647
|
-
}
|
|
191648
|
-
var SentenceDiff = class extends Diff {
|
|
191649
|
-
tokenize(value) {
|
|
191650
|
-
var _a;
|
|
191651
|
-
const result = [];
|
|
191652
|
-
let tokenStartI = 0;
|
|
191653
|
-
for (let i = 0; i < value.length; i++) {
|
|
191654
|
-
if (i == value.length - 1) {
|
|
191655
|
-
result.push(value.slice(tokenStartI));
|
|
191656
|
-
break;
|
|
191657
|
-
}
|
|
191658
|
-
if (isSentenceEndPunct(value[i]) && value[i + 1].match(/\s/)) {
|
|
191659
|
-
result.push(value.slice(tokenStartI, i + 1));
|
|
191660
|
-
i = tokenStartI = i + 1;
|
|
191661
|
-
while ((_a = value[i + 1]) === null || _a === void 0 ? void 0 : _a.match(/\s/)) i++;
|
|
191662
|
-
result.push(value.slice(tokenStartI, i + 1));
|
|
191663
|
-
tokenStartI = i + 1;
|
|
191664
|
-
}
|
|
191665
|
-
}
|
|
191666
|
-
return result;
|
|
191667
|
-
}
|
|
191668
|
-
};
|
|
191669
|
-
new SentenceDiff();
|
|
191670
|
-
//#endregion
|
|
191671
|
-
//#region node_modules/diff/libesm/diff/css.js
|
|
191672
|
-
var CssDiff = class extends Diff {
|
|
191673
|
-
tokenize(value) {
|
|
191674
|
-
return value.split(/([{}:;,]|\s+)/);
|
|
191675
|
-
}
|
|
191676
|
-
};
|
|
191677
|
-
new CssDiff();
|
|
191678
|
-
//#endregion
|
|
191679
|
-
//#region node_modules/diff/libesm/diff/json.js
|
|
191680
|
-
var JsonDiff = class extends Diff {
|
|
191681
|
-
constructor() {
|
|
191682
|
-
super(...arguments);
|
|
191683
|
-
this.tokenize = tokenize;
|
|
191684
|
-
}
|
|
191685
|
-
get useLongestToken() {
|
|
191686
|
-
return true;
|
|
191687
|
-
}
|
|
191688
|
-
castInput(value, options) {
|
|
191689
|
-
const { undefinedReplacement, stringifyReplacer = (k, v) => typeof v === "undefined" ? undefinedReplacement : v } = options;
|
|
191690
|
-
return typeof value === "string" ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, " ");
|
|
191691
|
-
}
|
|
191692
|
-
equals(left, right, options) {
|
|
191693
|
-
return super.equals(left.replace(/,([\r\n])/g, "$1"), right.replace(/,([\r\n])/g, "$1"), options);
|
|
191694
|
-
}
|
|
191695
|
-
};
|
|
191696
|
-
new JsonDiff();
|
|
191697
|
-
function canonicalize(obj, stack, replacementStack, replacer, key) {
|
|
191698
|
-
stack = stack || [];
|
|
191699
|
-
replacementStack = replacementStack || [];
|
|
191700
|
-
if (replacer) obj = replacer(key === void 0 ? "" : key, obj);
|
|
191701
|
-
let i;
|
|
191702
|
-
for (i = 0; i < stack.length; i += 1) if (stack[i] === obj) return replacementStack[i];
|
|
191703
|
-
let canonicalizedObj;
|
|
191704
|
-
if ("[object Array]" === Object.prototype.toString.call(obj)) {
|
|
191705
|
-
stack.push(obj);
|
|
191706
|
-
canonicalizedObj = new Array(obj.length);
|
|
191707
|
-
replacementStack.push(canonicalizedObj);
|
|
191708
|
-
for (i = 0; i < obj.length; i += 1) canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i));
|
|
191709
|
-
stack.pop();
|
|
191710
|
-
replacementStack.pop();
|
|
191711
|
-
return canonicalizedObj;
|
|
191712
|
-
}
|
|
191713
|
-
if (obj && obj.toJSON) obj = obj.toJSON();
|
|
191714
|
-
if (typeof obj === "object" && obj !== null) {
|
|
191715
|
-
stack.push(obj);
|
|
191716
|
-
canonicalizedObj = {};
|
|
191717
|
-
replacementStack.push(canonicalizedObj);
|
|
191718
|
-
const sortedKeys = [];
|
|
191719
|
-
let key;
|
|
191720
|
-
for (key in obj)
|
|
191721
|
-
/* istanbul ignore else */
|
|
191722
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) sortedKeys.push(key);
|
|
191723
|
-
sortedKeys.sort();
|
|
191724
|
-
for (i = 0; i < sortedKeys.length; i += 1) {
|
|
191725
|
-
key = sortedKeys[i];
|
|
191726
|
-
canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
|
|
191727
|
-
}
|
|
191728
|
-
stack.pop();
|
|
191729
|
-
replacementStack.pop();
|
|
191730
|
-
} else canonicalizedObj = obj;
|
|
191731
|
-
return canonicalizedObj;
|
|
191732
|
-
}
|
|
191733
|
-
//#endregion
|
|
191734
|
-
//#region node_modules/diff/libesm/diff/array.js
|
|
191735
|
-
var ArrayDiff = class extends Diff {
|
|
191736
|
-
tokenize(value) {
|
|
191737
|
-
return value.slice();
|
|
191738
|
-
}
|
|
191739
|
-
join(value) {
|
|
191740
|
-
return value;
|
|
191741
|
-
}
|
|
191742
|
-
removeEmpty(value) {
|
|
191743
|
-
return value;
|
|
191744
|
-
}
|
|
191745
|
-
};
|
|
191746
|
-
new ArrayDiff();
|
|
191747
|
-
//#endregion
|
|
191748
|
-
//#region src/Diff.ts
|
|
191749
|
-
/**
|
|
191750
|
-
* Compute a git-compatible blob SHA (first 7 hex digits).
|
|
191751
|
-
* Matches `git hash-object <file>` for the given string content.
|
|
191752
|
-
*/
|
|
191753
|
-
function blobSha7(content) {
|
|
191754
|
-
const buf = Buffer.from(content, "utf8");
|
|
191755
|
-
const hash = crypto.createHash("sha1");
|
|
191756
|
-
hash.update(`blob ${buf.length}\0`);
|
|
191757
|
-
hash.update(buf);
|
|
191758
|
-
return hash.digest("hex").slice(0, 7);
|
|
191759
|
-
}
|
|
191760
|
-
/**
|
|
191761
|
-
* Detect binary content: a string is considered binary if it contains
|
|
191762
|
-
* a null byte (U+0000), matching git's heuristic.
|
|
191763
|
-
*/
|
|
191764
|
-
function isBinary(content) {
|
|
191765
|
-
return content.includes("\0");
|
|
191766
|
-
}
|
|
191767
|
-
/**
|
|
191768
|
-
* Format a hunk position: omit the `,count` when count === 1 (git convention).
|
|
191769
|
-
* e.g. count=1,start=1 → "1"; count=2,start=1 → "1,2"; count=0,start=0 → "0,0"
|
|
191770
|
-
*/
|
|
191771
|
-
function hunkPos(start, count) {
|
|
191772
|
-
if (count === 1) return String(start);
|
|
191773
|
-
return `${start},${count}`;
|
|
191774
|
-
}
|
|
191775
|
-
/**
|
|
191776
|
-
* Split a file string into lines, tracking whether the file ends with a newline.
|
|
191777
|
-
* Returns `{ lines, noNewline }` where `noNewline` means the file doesn't end with \n.
|
|
191778
|
-
*/
|
|
191779
|
-
function splitLines(content) {
|
|
191780
|
-
if (content === "") return {
|
|
191781
|
-
lines: [],
|
|
191782
|
-
noNewline: false
|
|
191783
|
-
};
|
|
191784
|
-
const noNewline = !content.endsWith("\n");
|
|
191785
|
-
return {
|
|
191786
|
-
lines: (content.endsWith("\n") ? content.slice(0, -1) : content).split("\n"),
|
|
191787
|
-
noNewline
|
|
191788
|
-
};
|
|
191789
|
-
}
|
|
191790
|
-
/** Convert `diffLines()` chunks into a flat annotated line list. */
|
|
191791
|
-
function buildAnnotatedLines(chunks) {
|
|
191792
|
-
const lines = [];
|
|
191793
|
-
for (let ci = 0; ci < chunks.length; ci++) {
|
|
191794
|
-
const chunk = chunks[ci];
|
|
191795
|
-
const { lines: chunkLines, noNewline } = splitLines(chunk.value);
|
|
191796
|
-
const isLast = ci === chunks.length - 1;
|
|
191797
|
-
for (let li = 0; li < chunkLines.length; li++) lines.push({
|
|
191798
|
-
type: chunk.added ? "added" : chunk.removed ? "removed" : "context",
|
|
191799
|
-
text: chunkLines[li],
|
|
191800
|
-
noNewline: isLast && li === chunkLines.length - 1 && noNewline
|
|
191801
|
-
});
|
|
191802
|
-
}
|
|
191803
|
-
return lines;
|
|
191804
|
-
}
|
|
191805
|
-
/** Group changed line indices (±context) into non-overlapping hunk ranges. */
|
|
191806
|
-
function buildHunkRanges(lines, context) {
|
|
191807
|
-
const changedIndices = /* @__PURE__ */ new Set();
|
|
191808
|
-
for (let i = 0; i < lines.length; i++) if (lines[i].type !== "context") changedIndices.add(i);
|
|
191809
|
-
if (changedIndices.size === 0) return [];
|
|
191810
|
-
const ranges = [];
|
|
191811
|
-
let current = null;
|
|
191812
|
-
for (const idx of [...changedIndices].sort((a, b) => a - b)) {
|
|
191813
|
-
const start = Math.max(0, idx - context);
|
|
191814
|
-
const end = Math.min(lines.length - 1, idx + context);
|
|
191815
|
-
if (current === null) current = {
|
|
191816
|
-
start,
|
|
191817
|
-
end
|
|
191818
|
-
};
|
|
191819
|
-
else if (start <= current.end + 1) current.end = Math.max(current.end, end);
|
|
191820
|
-
else {
|
|
191821
|
-
ranges.push(current);
|
|
191822
|
-
current = {
|
|
191823
|
-
start,
|
|
191824
|
-
end
|
|
191825
|
-
};
|
|
191826
|
-
}
|
|
191827
|
-
}
|
|
191828
|
-
if (current !== null) ranges.push(current);
|
|
191829
|
-
return ranges;
|
|
191830
|
-
}
|
|
191831
|
-
/** Render one hunk range given the pre-computed old/new line number maps. */
|
|
191832
|
-
function renderHunkRange(range, lines, oldLineOf, newLineOf) {
|
|
191833
|
-
const { start, end } = range;
|
|
191834
|
-
let oldStart = -1;
|
|
191835
|
-
let newStart = -1;
|
|
191836
|
-
let oldCount = 0;
|
|
191837
|
-
let newCount = 0;
|
|
191838
|
-
for (let i = start; i <= end; i++) {
|
|
191839
|
-
const ol = oldLineOf[i];
|
|
191840
|
-
const nl = newLineOf[i];
|
|
191841
|
-
if (ol !== -1) {
|
|
191842
|
-
if (oldStart === -1) oldStart = ol;
|
|
191843
|
-
oldCount++;
|
|
191844
|
-
}
|
|
191845
|
-
if (nl !== -1) {
|
|
191846
|
-
if (newStart === -1) newStart = nl;
|
|
191847
|
-
newCount++;
|
|
191848
|
-
}
|
|
191849
|
-
}
|
|
191850
|
-
if (oldStart === -1) oldStart = 0;
|
|
191851
|
-
if (newStart === -1) newStart = 0;
|
|
191852
|
-
const out = [];
|
|
191853
|
-
out.push(`@@ -${hunkPos(oldStart, oldCount)} +${hunkPos(newStart, newCount)} @@`);
|
|
191854
|
-
for (let i = start; i <= end; i++) {
|
|
191855
|
-
const line = lines[i];
|
|
191856
|
-
const prefix = line.type === "context" ? " " : line.type === "removed" ? "-" : "+";
|
|
191857
|
-
out.push(`${prefix}${line.text}`);
|
|
191858
|
-
if (line.noNewline) out.push("\");
|
|
191859
|
-
}
|
|
191860
|
-
return out;
|
|
191861
|
-
}
|
|
191862
|
-
/**
|
|
191863
|
-
* Render git-format unified diff hunks for a before→after content pair.
|
|
191864
|
-
* Uses 3 context lines (git default). Returns the hunk lines without a final newline.
|
|
191865
|
-
*/
|
|
191866
|
-
function renderHunks(before, after) {
|
|
191867
|
-
const context = 3;
|
|
191868
|
-
const allLines = buildAnnotatedLines(diffLines(before, after));
|
|
191869
|
-
if (allLines.length === 0) return [];
|
|
191870
|
-
const hunkRanges = buildHunkRanges(allLines, context);
|
|
191871
|
-
if (hunkRanges.length === 0) return [];
|
|
191872
|
-
const oldLineOf = [];
|
|
191873
|
-
const newLineOf = [];
|
|
191874
|
-
let oldLineNum = 1;
|
|
191875
|
-
let newLineNum = 1;
|
|
191876
|
-
for (const line of allLines) if (line.type === "context") {
|
|
191877
|
-
oldLineOf.push(oldLineNum++);
|
|
191878
|
-
newLineOf.push(newLineNum++);
|
|
191879
|
-
} else if (line.type === "removed") {
|
|
191880
|
-
oldLineOf.push(oldLineNum++);
|
|
191881
|
-
newLineOf.push(-1);
|
|
191882
|
-
} else {
|
|
191883
|
-
oldLineOf.push(-1);
|
|
191884
|
-
newLineOf.push(newLineNum++);
|
|
191885
|
-
}
|
|
191886
|
-
const output = [];
|
|
191887
|
-
for (const range of hunkRanges) output.push(...renderHunkRange(range, allLines, oldLineOf, newLineOf));
|
|
191888
|
-
return output;
|
|
191889
|
-
}
|
|
191890
|
-
/** Render the diff block for a single file (new, deleted, or modified). */
|
|
191891
|
-
function renderFileDiff(path, before, after, beforeSha, afterSha) {
|
|
191892
|
-
const lines = [`diff --git a/${path} b/${path}`];
|
|
191893
|
-
const binary = before !== null && isBinary(before) || after !== null && isBinary(after);
|
|
191894
|
-
if (before === null) {
|
|
191895
|
-
lines.push("new file mode 100644", `index ${beforeSha}..${afterSha}`);
|
|
191896
|
-
if (binary) lines.push(`Binary files /dev/null and b/${path} differ`);
|
|
191897
|
-
else lines.push("--- /dev/null", `+++ b/${path}`, ...renderHunks("", after));
|
|
191898
|
-
} else if (after === null) {
|
|
191899
|
-
lines.push("deleted file mode 100644", `index ${beforeSha}..${afterSha}`);
|
|
191900
|
-
if (binary) lines.push(`Binary files a/${path} and /dev/null differ`);
|
|
191901
|
-
else lines.push(`--- a/${path}`, "+++ /dev/null", ...renderHunks(before, ""));
|
|
191902
|
-
} else {
|
|
191903
|
-
lines.push(`index ${beforeSha}..${afterSha} 100644`);
|
|
191904
|
-
if (binary) lines.push(`Binary files a/${path} and b/${path} differ`);
|
|
191905
|
-
else lines.push(`--- a/${path}`, `+++ b/${path}`, ...renderHunks(before, after));
|
|
191906
|
-
}
|
|
191907
|
-
return lines;
|
|
191908
|
-
}
|
|
191909
|
-
/**
|
|
191910
|
-
* Render a git-format unified diff for a set of file changes.
|
|
191911
|
-
* Pure function — no IO, no Effect.
|
|
191912
|
-
*/
|
|
191913
|
-
const renderDiff = (files) => {
|
|
191914
|
-
const parts = [];
|
|
191915
|
-
for (const { path, before, after } of files) {
|
|
191916
|
-
if (before === after) continue;
|
|
191917
|
-
const beforeSha = before !== null ? blobSha7(before) : "0000000";
|
|
191918
|
-
const afterSha = after !== null ? blobSha7(after) : "0000000";
|
|
191919
|
-
parts.push(renderFileDiff(path, before, after, beforeSha, afterSha).join("\n"));
|
|
191920
|
-
}
|
|
191921
|
-
if (parts.length === 0) return "";
|
|
191922
|
-
return parts.join("\n") + "\n";
|
|
191923
|
-
};
|
|
191924
|
-
//#endregion
|
|
191925
191232
|
//#region src/Git.ts
|
|
191926
|
-
const EMPTY_TREE$3 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
191927
191233
|
/**
|
|
191928
191234
|
* Parse `git diff --name-status` output into `{ path, status }` pairs.
|
|
191929
191235
|
* Status codes: A (added), D (deleted), M (modified), R (renamed), C (copied), etc.
|
|
@@ -191958,39 +191264,6 @@ const parseNameStatus = (out) => {
|
|
|
191958
191264
|
return result;
|
|
191959
191265
|
};
|
|
191960
191266
|
/**
|
|
191961
|
-
* Filter paths by exclude list. A directory path `dir` excludes anything under
|
|
191962
|
-
* `dir/` or exactly `dir`. An entry prefixed with `!` is a re-include: the
|
|
191963
|
-
* named path stays even when another entry excludes it (e.g.
|
|
191964
|
-
* `[".gtd", "!.gtd/TODO.md"]` hides all workflow files except the plan).
|
|
191965
|
-
* Applied in JS so we don't pass `:(exclude)` pathspecs to git (which can
|
|
191966
|
-
* break on special path characters or certain git versions).
|
|
191967
|
-
*/
|
|
191968
|
-
const applyExcludes = (paths, exclude) => {
|
|
191969
|
-
if (exclude.length === 0) return [...paths];
|
|
191970
|
-
const keeps = exclude.filter((e) => e.startsWith("!")).map((e) => e.slice(1));
|
|
191971
|
-
const drops = exclude.filter((e) => !e.startsWith("!"));
|
|
191972
|
-
return paths.filter(({ path }) => {
|
|
191973
|
-
if (keeps.some((keep) => path === keep || path.startsWith(`${keep}/`))) return true;
|
|
191974
|
-
for (const ex of drops) if (path === ex || path.startsWith(`${ex}/`)) return false;
|
|
191975
|
-
return true;
|
|
191976
|
-
});
|
|
191977
|
-
};
|
|
191978
|
-
/**
|
|
191979
|
-
* Read a file from the worktree root (`root`).
|
|
191980
|
-
* Returns `null` if the file does not exist or is a directory (e.g. a submodule
|
|
191981
|
-
* gitlink whose worktree entry is a directory, not a regular file).
|
|
191982
|
-
*/
|
|
191983
|
-
const readWorktreeFile = (root, path) => {
|
|
191984
|
-
const resolved = join(root, path);
|
|
191985
|
-
if (!existsSync(resolved)) return null;
|
|
191986
|
-
try {
|
|
191987
|
-
return readFileSync(resolved, "utf8");
|
|
191988
|
-
} catch (e) {
|
|
191989
|
-
if (e instanceof Error && "code" in e && e.code === "EISDIR") return null;
|
|
191990
|
-
throw e;
|
|
191991
|
-
}
|
|
191992
|
-
};
|
|
191993
|
-
/**
|
|
191994
191267
|
* Run a command and return its stdout — FAILING on a non-zero exit code with
|
|
191995
191268
|
* the command line and stderr in the error message. `Command.string` alone
|
|
191996
191269
|
* only collects stdout and silently ignores exit codes, which used to make
|
|
@@ -192013,76 +191286,11 @@ const run = (root, ...args) => scoped$3(gen(function* () {
|
|
|
192013
191286
|
const makeGitImpl = (executor, root) => {
|
|
192014
191287
|
const exec = (...args) => run(root, ...args).pipe(provide$1(succeed$2(CommandExecutor, executor)));
|
|
192015
191288
|
return {
|
|
192016
|
-
diffHead: (exclude = []) => gen(function* () {
|
|
192017
|
-
const nameStatusOut = yield* exec("git", "diff", "--name-status", "HEAD").pipe(catchAll(() => succeed$3("")));
|
|
192018
|
-
const trackedPaths = parseNameStatus(nameStatusOut);
|
|
192019
|
-
const untracked = (yield* exec("git", "ls-files", "--others", "--exclude-standard", "-z")).split("\0").filter((s) => s.length > 0).map((path) => ({
|
|
192020
|
-
path,
|
|
192021
|
-
status: "A"
|
|
192022
|
-
}));
|
|
192023
|
-
const seen = /* @__PURE__ */ new Set();
|
|
192024
|
-
const allPaths = [];
|
|
192025
|
-
for (const entry of [...trackedPaths, ...untracked]) if (!seen.has(entry.path)) {
|
|
192026
|
-
seen.add(entry.path);
|
|
192027
|
-
allPaths.push(entry);
|
|
192028
|
-
}
|
|
192029
|
-
const filtered = applyExcludes(allPaths, exclude);
|
|
192030
|
-
if (filtered.length === 0) return "";
|
|
192031
|
-
return renderDiff(yield* all(filtered.map(({ path, status }) => gen(function* () {
|
|
192032
|
-
return {
|
|
192033
|
-
path,
|
|
192034
|
-
before: status === "A" ? null : yield* exec("git", "show", `HEAD:${path}`).pipe(map$4((s) => s), catchAll(() => succeed$3(null))),
|
|
192035
|
-
after: status === "D" ? null : readWorktreeFile(root, path)
|
|
192036
|
-
};
|
|
192037
|
-
})), { concurrency: "unbounded" }));
|
|
192038
|
-
}),
|
|
192039
191289
|
lastCommitSubject: (ref = "HEAD") => exec("git", "log", "-1", "--pretty=%s", ref).pipe(map$4((s) => s.trim())),
|
|
192040
191290
|
lastCommitMessage: () => exec("git", "log", "-1", "--pretty=%B").pipe(map$4((s) => s.trim())),
|
|
192041
191291
|
hasCommits: () => exec("git", "rev-parse", "--verify", "HEAD").pipe(map$4(() => true), catchAll(() => succeed$3(false))),
|
|
192042
|
-
diffRef: (ref, exclude = []) => gen(function* () {
|
|
192043
|
-
const nameStatusOut = yield* exec("git", "diff", "--name-status", ref, "HEAD").pipe(catchAll(() => succeed$3("")));
|
|
192044
|
-
const allPaths = parseNameStatus(nameStatusOut);
|
|
192045
|
-
const filtered = applyExcludes(allPaths, exclude);
|
|
192046
|
-
if (filtered.length === 0) return "";
|
|
192047
|
-
return renderDiff(yield* all(filtered.map(({ path, status }) => gen(function* () {
|
|
192048
|
-
return {
|
|
192049
|
-
path,
|
|
192050
|
-
before: status === "A" ? null : yield* exec("git", "show", `${ref}:${path}`).pipe(catchAll(() => succeed$3(null))),
|
|
192051
|
-
after: status === "D" ? null : yield* exec("git", "show", `HEAD:${path}`).pipe(catchAll(() => succeed$3(null)))
|
|
192052
|
-
};
|
|
192053
|
-
})), { concurrency: "unbounded" }));
|
|
192054
|
-
}),
|
|
192055
191292
|
readFileAtRef: (ref, path) => exec("git", "show", `${ref}:${path}`),
|
|
192056
|
-
|
|
192057
|
-
yield* exec("git", "rev-parse", "--verify", hash);
|
|
192058
|
-
const parent = yield* exec("git", "rev-parse", "--verify", `${hash}~1`).pipe(map$4((s) => s.trim()), catchAll(() => succeed$3(EMPTY_TREE$3)));
|
|
192059
|
-
const nameStatusOut = yield* exec("git", "diff", "--name-status", parent, hash).pipe(catchAll(() => succeed$3("")));
|
|
192060
|
-
const allPaths = parseNameStatus(nameStatusOut);
|
|
192061
|
-
const filtered = applyExcludes(allPaths, exclude);
|
|
192062
|
-
if (filtered.length === 0) return "";
|
|
192063
|
-
const gitlinkPointerAt = (ref, path) => exec("git", "ls-tree", ref, "--", path).pipe(map$4((out) => {
|
|
192064
|
-
const fields = out.trim().split(/\s+/);
|
|
192065
|
-
return fields[0] === "160000" && fields[2] ? `Subproject commit ${fields[2]}\n` : null;
|
|
192066
|
-
}), catchAll(() => succeed$3(null)));
|
|
192067
|
-
return renderDiff(yield* all(filtered.map(({ path, status }) => gen(function* () {
|
|
192068
|
-
const before = status === "A" ? null : yield* exec("git", "show", `${parent}:${path}`).pipe(catchAll(() => succeed$3(null)));
|
|
192069
|
-
const after = status === "D" ? null : yield* exec("git", "show", `${hash}:${path}`).pipe(catchAll(() => succeed$3(null)));
|
|
192070
|
-
if (before === null && after === null && status !== "A" && status !== "D") {
|
|
192071
|
-
const gitlinkBefore = yield* gitlinkPointerAt(parent, path);
|
|
192072
|
-
const gitlinkAfter = yield* gitlinkPointerAt(hash, path);
|
|
192073
|
-
if (gitlinkBefore !== null || gitlinkAfter !== null) return {
|
|
192074
|
-
path,
|
|
192075
|
-
before: gitlinkBefore,
|
|
192076
|
-
after: gitlinkAfter
|
|
192077
|
-
};
|
|
192078
|
-
}
|
|
192079
|
-
return {
|
|
192080
|
-
path,
|
|
192081
|
-
before,
|
|
192082
|
-
after
|
|
192083
|
-
};
|
|
192084
|
-
})), { concurrency: "unbounded" }));
|
|
192085
|
-
}),
|
|
191293
|
+
changedPathsSince: (ref) => exec("git", "diff", "--name-status", ref, "HEAD").pipe(map$4(parseNameStatus)),
|
|
192086
191294
|
changedPaths: () => gen(function* () {
|
|
192087
191295
|
const nameStatusOut = yield* exec("git", "diff", "--name-status", "HEAD").pipe(catchAll(() => succeed$3("")));
|
|
192088
191296
|
const trackedPaths = parseNameStatus(nameStatusOut);
|
|
@@ -192680,10 +191888,8 @@ const varsOnlyContext = (vars, state = "") => ({
|
|
|
192680
191888
|
previousCommit: "",
|
|
192681
191889
|
state,
|
|
192682
191890
|
actor: "",
|
|
192683
|
-
|
|
192684
|
-
|
|
192685
|
-
retainedDiff: "",
|
|
192686
|
-
lastDiff: "",
|
|
191891
|
+
reviewBase: "",
|
|
191892
|
+
retainedBase: "",
|
|
192687
191893
|
processCost: 0,
|
|
192688
191894
|
processCostByModel: [],
|
|
192689
191895
|
read: (path) => {
|
|
@@ -193012,29 +192218,14 @@ const withRenderedOn = (def, state, renderedOnEdges) => ({
|
|
|
193012
192218
|
* `--cost`/`--model` (folded into the process's committed cost entries so a
|
|
193013
192219
|
* `commit:` squash template sees the whole-process total AND per-model
|
|
193014
192220
|
* breakdown including the squashing step) — `0`/absent for the pure emitters
|
|
193015
|
-
* (`gtd next`/`gtd status`), where no step is being performed.
|
|
192221
|
+
* (`gtd next`/`gtd status`), where no step is being performed. No diff is ever
|
|
192222
|
+
* computed here — `it.reviewBase`/`it.retainedBase` are bases a template tells
|
|
192223
|
+
* the agent to `git diff` itself.
|
|
193016
192224
|
*/
|
|
193017
|
-
|
|
193018
|
-
const joinDiffs = (committed, pending) => [committed, pending].filter((d) => d.trim().length > 0).join("\n\n");
|
|
193019
|
-
/**
|
|
193020
|
-
* The committed half of `it.reviewDiff`: the diff from `reviewDiffBase` (the
|
|
193021
|
-
* previous review round's boundary) when it is a distinct base, else the
|
|
193022
|
-
* already-computed process-wide `committedDiff` (first review — reviewDiff
|
|
193023
|
-
* collapses to processDiff).
|
|
193024
|
-
*/
|
|
193025
|
-
const committedReviewDiffOf = (git, processDiffBase, reviewDiffBase, committedDiff) => reviewDiffBase !== void 0 && reviewDiffBase !== processDiffBase ? git.diffRef(reviewDiffBase).pipe(catchAll(() => succeed$3(""))) : succeed$3(committedDiff);
|
|
193026
|
-
const buildTemplateContext = (git, read, state, actor, run, vars, edges, currentCost = 0, currentModel, reviewDiffBase) => gen(function* () {
|
|
192225
|
+
const buildTemplateContext = (git, read, state, actor, run, vars, edges, currentCost = 0, currentModel, reviewBase) => gen(function* () {
|
|
193027
192226
|
const hasCommits = yield* git.hasCommits();
|
|
193028
192227
|
const currentCommit = hasCommits ? yield* git.resolveRef("HEAD") : "";
|
|
193029
192228
|
const previousCommit = hasCommits ? yield* git.resolveRef("HEAD~1").pipe(catchAll(() => succeed$3(run.startParentHash))) : "";
|
|
193030
|
-
const committedDiff = yield* git.diffRef(run.diffBase).pipe(catchAll(() => succeed$3("")));
|
|
193031
|
-
const pendingDiff = yield* git.diffHead().pipe(catchAll(() => succeed$3("")));
|
|
193032
|
-
const processDiff = joinDiffs(committedDiff, pendingDiff);
|
|
193033
|
-
const committedReviewDiff = yield* committedReviewDiffOf(git, run.diffBase, reviewDiffBase, committedDiff);
|
|
193034
|
-
const reviewDiff = joinDiffs(committedReviewDiff, pendingDiff);
|
|
193035
|
-
const committedRetainedDiff = run.diffBase === run.startParentHash ? committedDiff : yield* git.diffRef(run.startParentHash).pipe(catchAll(() => succeed$3("")));
|
|
193036
|
-
const retainedDiff = joinDiffs(committedRetainedDiff, pendingDiff);
|
|
193037
|
-
const lastDiff = run.trace.length > 0 ? yield* git.commitDiff(currentCommit).pipe(catchAll(() => succeed$3(""))) : "";
|
|
193038
192229
|
const stepEntry = currentCost > 0 || currentModel !== void 0 ? [{
|
|
193039
192230
|
cost: currentCost,
|
|
193040
192231
|
model: currentModel ?? "unspecified"
|
|
@@ -193046,10 +192237,8 @@ const buildTemplateContext = (git, read, state, actor, run, vars, edges, current
|
|
|
193046
192237
|
previousCommit,
|
|
193047
192238
|
state,
|
|
193048
192239
|
actor,
|
|
193049
|
-
|
|
193050
|
-
|
|
193051
|
-
retainedDiff,
|
|
193052
|
-
lastDiff,
|
|
192240
|
+
reviewBase: reviewBase ?? run.diffBase,
|
|
192241
|
+
retainedBase: run.startParentHash,
|
|
193053
192242
|
processCost: totalCostOf(allCostEntries),
|
|
193054
192243
|
processCostByModel: costByModel(allCostEntries),
|
|
193055
192244
|
read,
|
|
@@ -193058,6 +192247,16 @@ const buildTemplateContext = (git, read, state, actor, run, vars, edges, current
|
|
|
193058
192247
|
};
|
|
193059
192248
|
});
|
|
193060
192249
|
/**
|
|
192250
|
+
* True when this process would retain NOTHING: nothing pending and no net
|
|
192251
|
+
* change between its trace/retry boundary and HEAD. Replaces the old
|
|
192252
|
+
* `it.retainedDiff.trim() === ""` probe in program.ts — paths only, no
|
|
192253
|
+
* content rendering.
|
|
192254
|
+
*/
|
|
192255
|
+
const retainsNothing = (git, run, changes) => gen(function* () {
|
|
192256
|
+
if (changes.length > 0) return false;
|
|
192257
|
+
return (yield* git.changedPathsSince(run.startParentHash)).length === 0;
|
|
192258
|
+
});
|
|
192259
|
+
/**
|
|
193061
192260
|
* Render a state's declared `model:` hint (if any) through the SAME template
|
|
193062
192261
|
* context as its content — a plain string with no Eta tags (e.g. `"smart"`)
|
|
193063
192262
|
* passes through unchanged, but `model: "<%= it.vars.reviewModel %>"` now
|
|
@@ -205765,14 +204964,15 @@ const stepAsActor = (invoker, cost, model) => gen(function* () {
|
|
|
205765
204964
|
const executable = decision;
|
|
205766
204965
|
const renderedState = decision.kind === "squash" ? decision.state : rest.state;
|
|
205767
204966
|
const renderedStateOn = renderedState === rest.state ? renderedOn : yield* renderOnEdgesOrFail(rest.def.states[renderedState]?.on, vars);
|
|
205768
|
-
const
|
|
204967
|
+
const reviewBase = yield* reviewBaseHash(git, rest.def, run);
|
|
204968
|
+
const context = yield* buildTemplateContext(git, worktree.read, renderedState, invoker, run, vars, renderedStateOn, cost ?? 0, model, reviewBase);
|
|
205769
204969
|
yield* enforceSteeringGate(worktree.read, invoker, rest, context, decision.kind);
|
|
205770
204970
|
yield* enforceReviewSignoffGate(worktree.read, invoker, rest, context, changes, decision.kind);
|
|
205771
204971
|
yield* enforceFeedbackProgressGate(invoker, rest, context, changes, decision.kind);
|
|
205772
204972
|
yield* enforceAnswerCompletenessGate(worktree.read, invoker, rest, context, decision.kind);
|
|
205773
204973
|
if (decision.kind === "commit") {
|
|
205774
204974
|
const target = parseStateSubject(decision.subject)?.state;
|
|
205775
|
-
if (target === initialStateOf(rest.def) &&
|
|
204975
|
+
if (target === initialStateOf(rest.def) && run.startParentHash !== EMPTY_TREE && (yield* retainsNothing(git, run, changes))) {
|
|
205776
204976
|
yield* retainHistory(git, yield* git.resolveRef("HEAD"), run.startParentHash);
|
|
205777
204977
|
yield* git.mixedResetTo(run.startParentHash);
|
|
205778
204978
|
return {
|