@pmelab/gtd 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gtd.bundle.mjs +427 -26
- package/docs/cli.md +44 -2
- package/docs/configuration.md +58 -0
- package/docs/design/state-machine-validation.md +437 -0
- package/docs/upgrading.md +8 -2
- package/package.json +1 -1
- package/schema.json +103 -4
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -189117,6 +189117,10 @@ const contentKindOf = (state) => {
|
|
|
189117
189117
|
};
|
|
189118
189118
|
/** True when a state is a commit (final, squash) state. */
|
|
189119
189119
|
const isCommitState = (state) => state.commit !== void 0;
|
|
189120
|
+
/** True when a rest at `state` should open the review checkout window (see `StateDef.reviewWindow`). Safe for an unknown state name (returns `false`). */
|
|
189121
|
+
const isReviewWindowState = (def, state) => def.states[state]?.reviewWindow === true;
|
|
189122
|
+
/** True when `state` anchors the review window's diff base (see `StateDef.reviewBase`). Safe for an unknown state name (returns `false`). */
|
|
189123
|
+
const isReviewBaseState = (def, state) => def.states[state]?.reviewBase === true;
|
|
189120
189124
|
/**
|
|
189121
189125
|
* `gtd(<actor>): <state>` — the subject a step commit carries. Per decision
|
|
189122
189126
|
* 2, `<actor>` is WHO AUTHORED THE STEP (the invoker), and `<state>` is the
|
|
@@ -189405,6 +189409,19 @@ const validateMode = (name, state) => {
|
|
|
189405
189409
|
if (isCommitState(state)) errors.push(`state "${name}": a commit state cannot declare "mode"`);
|
|
189406
189410
|
return errors;
|
|
189407
189411
|
};
|
|
189412
|
+
/**
|
|
189413
|
+
* `reviewWindow`/`reviewBase`, when present, are booleans (the compiler
|
|
189414
|
+
* enforces the type) — forbidden on a commit state, same rule family as
|
|
189415
|
+
* `model`/`file`/`mode`: a commit state is never at rest, so no window ever
|
|
189416
|
+
* opens or anchors there.
|
|
189417
|
+
*/
|
|
189418
|
+
const validateReviewWindow = (name, state) => {
|
|
189419
|
+
if (!isCommitState(state)) return [];
|
|
189420
|
+
const errors = [];
|
|
189421
|
+
if (state.reviewWindow !== void 0) errors.push(`state "${name}": a commit state cannot declare "reviewWindow"`);
|
|
189422
|
+
if (state.reviewBase !== void 0) errors.push(`state "${name}": a commit state cannot declare "reviewBase"`);
|
|
189423
|
+
return errors;
|
|
189424
|
+
};
|
|
189408
189425
|
/** Every `on` row parses, and its target names a defined state. */
|
|
189409
189426
|
const validateOnEdges = (name, state, names) => {
|
|
189410
189427
|
const errors = [];
|
|
@@ -189422,6 +189439,37 @@ const validateRetry = (name, state, names) => {
|
|
|
189422
189439
|
if (!Number.isInteger(state.retry.max) || state.retry.max < 0) errors.push(`state "${name}": retry.max must be a non-negative integer`);
|
|
189423
189440
|
return errors;
|
|
189424
189441
|
};
|
|
189442
|
+
/**
|
|
189443
|
+
* Every state is reachable from the initial state by walking `on` targets and
|
|
189444
|
+
* `retry.otherwise` redirects (a redirect ENTERS its `otherwise` state exactly
|
|
189445
|
+
* like an `on` match enters its target — see `applyRetry` — so both are real
|
|
189446
|
+
* edges). Plain BFS; targets naming undefined states are skipped here (they
|
|
189447
|
+
* are `validateOnEdges`/`validateRetry` findings of their own). Only called
|
|
189448
|
+
* when `validateInitial` found no problem: with zero or several `initial`
|
|
189449
|
+
* states there is no well-defined start to walk from, and reporting every
|
|
189450
|
+
* state as unreachable would bury the real finding.
|
|
189451
|
+
*
|
|
189452
|
+
* An unreachable state is an ERROR, not a warning: a workflow is bound to a
|
|
189453
|
+
* project and edited as a project-wide change, so "kept on purpose for manual
|
|
189454
|
+
* entry via a hand-authored subject" is not a supported authoring pattern —
|
|
189455
|
+
* an unreachable state is a typo'd rename or a leftover, and silently-dead
|
|
189456
|
+
* config is exactly what load-time validation exists to catch.
|
|
189457
|
+
*/
|
|
189458
|
+
const validateReachability = (def, names) => {
|
|
189459
|
+
const initial = names.find((name) => def.states[name].initial === true);
|
|
189460
|
+
const visited = /* @__PURE__ */ new Set([initial]);
|
|
189461
|
+
const queue = [initial];
|
|
189462
|
+
while (queue.length > 0) {
|
|
189463
|
+
const state = def.states[queue.shift()];
|
|
189464
|
+
const targets = (state.on ?? []).map(([, target]) => target);
|
|
189465
|
+
if (state.retry !== void 0) targets.push(state.retry.otherwise);
|
|
189466
|
+
for (const target of targets) if (def.states[target] !== void 0 && !visited.has(target)) {
|
|
189467
|
+
visited.add(target);
|
|
189468
|
+
queue.push(target);
|
|
189469
|
+
}
|
|
189470
|
+
}
|
|
189471
|
+
return names.filter((name) => !visited.has(name)).map((name) => `state "${name}" is unreachable from initial state "${initial}" (no "on" target or "retry.otherwise" leads to it)`);
|
|
189472
|
+
};
|
|
189425
189473
|
/** All per-state rule checkers, run over one state. */
|
|
189426
189474
|
const validateState = (def, name, names) => {
|
|
189427
189475
|
const state = def.states[name];
|
|
@@ -189432,7 +189480,8 @@ const validateState = (def, name, names) => {
|
|
|
189432
189480
|
...validateRetry(name, state, names),
|
|
189433
189481
|
...validateModel(name, state),
|
|
189434
189482
|
...validateFile(name, state),
|
|
189435
|
-
...validateMode(name, state)
|
|
189483
|
+
...validateMode(name, state),
|
|
189484
|
+
...validateReviewWindow(name, state)
|
|
189436
189485
|
];
|
|
189437
189486
|
};
|
|
189438
189487
|
/**
|
|
@@ -189449,12 +189498,21 @@ const validateState = (def, name, names) => {
|
|
|
189449
189498
|
* never declared on a commit state; `file`, when present, is a non-empty
|
|
189450
189499
|
* string and is never declared on a commit state; `mode`, when present, is
|
|
189451
189500
|
* one of the closed vocabulary (`qa`/`review`), requires a sibling `file`,
|
|
189452
|
-
* and is never declared on a commit state
|
|
189501
|
+
* and is never declared on a commit state; `reviewWindow`/`reviewBase`, when
|
|
189502
|
+
* present, are never declared on a commit state; every state is reachable from
|
|
189503
|
+
* the initial state by walking `on` targets and `retry.otherwise` redirects
|
|
189504
|
+
* (checked only when the initial-state rule itself passed — see
|
|
189505
|
+
* `validateReachability`).
|
|
189453
189506
|
*/
|
|
189454
189507
|
const validateDefinition = (def) => {
|
|
189455
189508
|
const names = Object.keys(def.states);
|
|
189456
189509
|
if (names.length === 0) return ["workflow must declare at least one state"];
|
|
189457
|
-
|
|
189510
|
+
const initialErrors = validateInitial(def, names);
|
|
189511
|
+
return [
|
|
189512
|
+
...initialErrors,
|
|
189513
|
+
...names.flatMap((name) => validateState(def, name, names)),
|
|
189514
|
+
...initialErrors.length === 0 ? validateReachability(def, names) : []
|
|
189515
|
+
];
|
|
189458
189516
|
};
|
|
189459
189517
|
//#endregion
|
|
189460
189518
|
//#region src/PatternConfig.ts
|
|
@@ -189578,7 +189636,9 @@ const KNOWN_STATE_KEYS = /* @__PURE__ */ new Set([
|
|
|
189578
189636
|
"retry",
|
|
189579
189637
|
"model",
|
|
189580
189638
|
"file",
|
|
189581
|
-
"mode"
|
|
189639
|
+
"mode",
|
|
189640
|
+
"reviewWindow",
|
|
189641
|
+
"reviewBase"
|
|
189582
189642
|
]);
|
|
189583
189643
|
const KNOWN_TOP_KEYS = /* @__PURE__ */ new Set(["vars", "states"]);
|
|
189584
189644
|
const formatErrors = (errors) => `workflow config:\n${errors.map((e) => ` - ${e}`).join("\n")}`;
|
|
@@ -189691,13 +189751,21 @@ const compileMode = (raw, name, errors) => {
|
|
|
189691
189751
|
return raw.mode;
|
|
189692
189752
|
};
|
|
189693
189753
|
/** The `initial` field: `true` only when the raw value is the literal boolean `true`. */
|
|
189694
|
-
const compileInitial = (raw, name, errors) =>
|
|
189695
|
-
|
|
189696
|
-
|
|
189697
|
-
|
|
189754
|
+
const compileInitial = (raw, name, errors) => compileBooleanFlag(raw, "initial", name, errors);
|
|
189755
|
+
/**
|
|
189756
|
+
* A boolean state flag (`initial`/`reviewWindow`/`reviewBase`): `true` only
|
|
189757
|
+
* when the raw value is the literal `true`; a non-boolean is a config error;
|
|
189758
|
+
* `false` (or absent) compiles away to `undefined` so it never lands in the
|
|
189759
|
+
* `StateDef` — `false` and "unset" mean the same thing for every such flag.
|
|
189760
|
+
*/
|
|
189761
|
+
const compileBooleanFlag = (raw, key, name, errors) => {
|
|
189762
|
+
const value = raw[key];
|
|
189763
|
+
if (value === void 0) return void 0;
|
|
189764
|
+
if (value !== true && value !== false) {
|
|
189765
|
+
errors.push(`state "${name}": "${key}" must be a boolean`);
|
|
189698
189766
|
return;
|
|
189699
189767
|
}
|
|
189700
|
-
return
|
|
189768
|
+
return value === true ? true : void 0;
|
|
189701
189769
|
};
|
|
189702
189770
|
const assembleContentFields = (content) => ({
|
|
189703
189771
|
...content.script !== void 0 ? { script: content.script } : {},
|
|
@@ -189721,7 +189789,9 @@ const assembleStateDef = (parts) => ({
|
|
|
189721
189789
|
retry: parts.retry,
|
|
189722
189790
|
model: parts.model,
|
|
189723
189791
|
file: parts.file,
|
|
189724
|
-
mode: parts.mode
|
|
189792
|
+
mode: parts.mode,
|
|
189793
|
+
reviewWindow: parts.reviewWindow,
|
|
189794
|
+
reviewBase: parts.reviewBase
|
|
189725
189795
|
}),
|
|
189726
189796
|
...assembleContentFields(parts.content)
|
|
189727
189797
|
});
|
|
@@ -189741,7 +189811,9 @@ const compileState = (name, raw, configDir, errors) => {
|
|
|
189741
189811
|
retry: compileRetry(raw.retry, name, errors),
|
|
189742
189812
|
model: compileModel(raw, name, errors),
|
|
189743
189813
|
file: compileFile(raw, name, errors),
|
|
189744
|
-
mode: compileMode(raw, name, errors)
|
|
189814
|
+
mode: compileMode(raw, name, errors),
|
|
189815
|
+
reviewWindow: compileBooleanFlag(raw, "reviewWindow", name, errors),
|
|
189816
|
+
reviewBase: compileBooleanFlag(raw, "reviewBase", name, errors)
|
|
189745
189817
|
});
|
|
189746
189818
|
};
|
|
189747
189819
|
/**
|
|
@@ -189791,7 +189863,7 @@ const compileWorkflowConfig = (raw, configDir) => {
|
|
|
189791
189863
|
* default.yaml's content values start with `./`/`../`, so
|
|
189792
189864
|
* `compileWorkflowConfig` never resolves a file reference against it.
|
|
189793
189865
|
*/
|
|
189794
|
-
const compiled = compileWorkflowConfig((0, import_dist$1.parse)("# The bundled default gtd workflow (v3 \"pattern machine\" — see\n# docs/design/pattern-machine-plan.md, and\n# docs/design/steering-file-loops.md for the two validation loops below).\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/default.ts does the compiling).\n#\n# Every content string below is INLINE (no `./`-relative file references):\n# the bundled default ships inside a single-file build\n# (`dist/gtd.bundle.mjs`), so its content must already be resolved at BUILD\n# time rather than read from disk at RUNTIME — the same reason\n# `src/prompts/*.md` are imported through tsdown's `.md`-as-text loader\n# instead of read via `fs` at runtime. `default.yaml` itself is imported the\n# same way (tsdown/vitest both treat `.yaml` as text — see\n# tsdown.config.ts / tests/vitest.rawMd.ts), so this file never needs a\n# sibling prompts/ directory to exist on disk once installed.\n#\n# States, in cycle order — 12 states, no squash:\n# idle (initial, human, message)\n# -> grilling (agent, prompt): reads .gtd/TODO.md's sketch (or a prior\n# lap's feedback), explores the codebase, and develops it into a concrete\n# implementation plan, stating any open question it can't settle itself\n# under a `## Open Questions` heading (see \"Loop 1\" below) rather than\n# guessing silently.\n# grilling -> todo-validating (check, script): mechanics-only parser port\n# of the open-questions format (see src/OpenQuestions.ts's module doc for\n# the executable spec this is a bash rendition of). Malformed ->\n# .gtd/FORMAT.md -> back to grilling; valid -> grilling-answer.\n# todo-validating -> grilling-answer (human, message): tick off suggested\n# defaults by replacing them with `Answer:` lines; a clean step accepts\n# every remaining default -> building; any edit loops back to grilling.\n# grilling-answer -> building (agent, prompt): implements the plan in\n# .gtd/TODO.md directly (no task decomposition, no per-task queue) and\n# deletes .gtd/TODO.md once the work is complete and verified.\n# building -> checking (check, script): runs the test command inline;\n# A/M .gtd/FEEDBACK.md -> fixing (capped, redirects to escalate); D\n# .gtd/FEEDBACK.md (a green run that just cleaned up prior feedback) or C\n# (green, nothing to clean) -> reviewing.\n# fixing (agent, prompt) <-> checking\n# escalate (human, message): manual intervention, then back to checking\n# checking (green) -> reviewing (agent, prompt): writes .gtd/REVIEW.md in\n# the checkbox review format (see \"Loop 2\" below), grouping the cycle's\n# full diff into reviewable chunks.\n# reviewing -> review-validating (check, script): mechanics-only parser\n# port of the review format (see src/ReviewDoc.ts's module doc for the\n# executable spec). Malformed -> .gtd/FORMAT.md -> back to reviewing;\n# valid -> await-review.\n# review-validating -> await-review (human, message): tick a pointer's `- [\n# ]` to `- [x]` to approve that item; deleting .gtd/REVIEW.md outright is\n# the power-user approve-everything shortcut; anything else pending\n# (code edits with REVIEW.md untouched) is feedback straight to grilling.\n# await-review -> review-deciding (check, script), entered by any `M\n# .gtd/REVIEW.md` step: if no unticked `- [ ]` pointer remains, the cycle\n# is approved (rm .gtd/REVIEW.md -> idle); otherwise the still-unticked\n# pointers (with their notes) are extracted into a fresh .gtd/TODO.md and\n# .gtd/REVIEW.md is removed (-> grilling, feedback lap).\n#\n# The cycle ends at human approval, resting back at idle — there is no\n# squash state. The cycle's commits stay in history; whether/how to squash\n# them (amend, interactive rebase, a PR's squash-merge) is entirely the\n# human's business once they're done reviewing — gtd makes no assumption.\n# That squash-flavored finale (a `squashing` prompt state + a `done` commit\n# state) still exists as an ENGINE capability (see STATES.md §8) and is kept\n# as part of the fuller machine at docs/examples/advanced-workflow.md.\n#\n# A `gtd(<actor>): idle` commit — entering the workflow's own initial state —\n# is therefore a PROCESS BOUNDARY (see `computeProcessRun` in src/Edge.ts):\n# the next cycle's `retry` counts, `startCommit`, and diffs never reach back\n# across an approved cycle's own idle-entering commit.\n#\n# HYGIENE INVARIANT (asserted in e2e — see default-workflow.feature): an\n# approved cycle leaves `.gtd/` empty. FEEDBACK.md is cleaned up by a green\n# `checking` run; FORMAT.md by a valid validator run (either loop); REVIEW.md\n# by the review-deciding approval branch; TODO.md by `building`.\n#\n# Loop 1 — TODO.md open questions (src/OpenQuestions.ts is this format's\n# EXECUTABLE SPEC — its unit tests are the format's spec tests; the\n# `todo-validating`/`review-validating` bash scripts below are a pragmatic\n# grep/awk PORT of the exact same rules, not a full markdown parser. Keep\n# both in sync by hand — there is no shared code path between the two\n# implementations, by design: the engine stays git/filesystem/Effect-free,\n# and the LSP's parsers stay dependency-free).\n#\n# Loop 2 — REVIEW.md checkboxes: same dual-implementation contract, this time\n# against src/ReviewDoc.ts.\n#\n# `grilling` and `reviewing` both declare `model: smart` — an opaque harness\n# hint (see the comment at `grilling` below) for their heavier one-shot\n# planning/reviewing turns. Every other state is unset (harness default).\n#\n# `vars.testCommand` is this workflow's own declared default for `checking`'s\n# script (see it.vars in PatternTemplates.ts) — override it per-repo with a\n# top-level `.gtdrc` `vars: { testCommand: ... }` key, or per-invocation with\n# a `GTD_VAR_testCommand` environment variable (highest precedence).\n#\n# `vars.todoFile`/`reviewFile`/`feedbackFile` are this workflow's ONE source\n# of truth for the three steering-file paths every prompt/script below reads\n# or writes — each appears in templates as `<%~ it.vars.todoFile %>` etc\n# (see docs/design/state-file-association.md §2). Every state that rests on\n# one of these files also declares a `file:`/`mode:` pair (see STATES.md's\n# property table) so `gtd status`/`gtd next --json` and the LSP (`gtd lsp`)\n# know which file a human/editor should look at, and in what format\n# (`qa` for TODO.md's open-questions format, `review` for REVIEW.md's\n# checkbox format) — `fixing`/`escalate` declare `file:` alone (FEEDBACK.md\n# is plain text, no LSP format). KNOWN LIMITATION: `on` pattern keys below are\n# NOT Eta templates — they keep the LITERAL `.gtd/...` paths matching these\n# vars' DEFAULT values. Repointing `todoFile`/`reviewFile`/`feedbackFile` via\n# `.gtdrc`'s top-level `vars:` or a `GTD_VAR_` override changes what `file:`\n# renders to (and what a template reads/writes) WITHOUT changing what the\n# `on` patterns match against — desyncing the machine. See\n# docs/configuration.md's `file:`/`mode:` section for the full caveat.\n#\n# A fuller machine — two-phase Q&A planning, an architecture phase, task\n# decomposition, a deterministic per-task builder loop, agent-prepared\n# review, and the squash finale (`squashing` + `done`) — is preserved as a\n# copy-paste `.gtdrc` example at docs/examples/advanced-workflow.md.\n\nvars:\n testCommand: npm test\n todoFile: .gtd/TODO.md\n reviewFile: .gtd/REVIEW.md\n feedbackFile: .gtd/FEEDBACK.md\n\nstates:\n idle:\n actor: human\n initial: true\n message: |\n No active gtd cycle.\n\n To start one: write `.gtd/TODO.md` with a short sketch of what you want\n built — a few sentences is enough — then run `gtd step human`.\n on:\n \"* **\": grilling\n\n grilling:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n # `model` is an opaque harness hint — gtd never interprets this string, it\n # only passes it through to `gtd next --json`/`gtd status --json` so the\n # driving loop can map it onto whatever models its agent harness\n # provides. \"smart\" here just names the harness-chosen tier for the\n # heavier planning turn; states without a `model` use the harness's\n # default.\n model: smart\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop. Never\n touch `.gtd/FORMAT.md` yourself if it exists — a validator writes it,\n and only the validator removes it once you've fixed the underlying\n problem.\n\n If `.gtd/FORMAT.md` exists, it holds format findings from your last\n draft of `<%= it.vars.todoFile %>` — fix those findings first, then\n continue below.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build (or\n feedback from a prior review lap). Read it, explore the codebase, and\n develop it into a concrete implementation plan.\n\n For every question you cannot settle yourself, add it under a\n `## Open Questions` heading, one `### <question>` sub-heading each,\n whose body's FIRST non-blank line is exactly\n `Suggested default: <your answer>` — the human can accept it as-is or\n override it. Omit the section entirely once there are no open\n questions. If `<%= it.vars.todoFile %>` already carries\n `## Open Questions` entries from a previous lap, fold any\n `Answer: <text>` line into the plan; every remaining entry's body must\n still start with either `Suggested default: ...` or `Answer: ...` —\n never anything else.\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 \"* **\": todo-validating\n\n todo-validating:\n actor: check\n file: <%= it.vars.todoFile %>\n mode: qa\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: a pragmatic grep/awk port of\n # src/OpenQuestions.ts's parser rules (the format's executable spec) —\n # free-form prose, plus an OPTIONAL \"## Open Questions\" section; every\n # \"### \" sub-heading directly under it is one question whose body's\n # first non-blank line must start with \"Suggested default: \" or\n # \"Answer: \". A violation is one finding line (file + line + rule) in\n # .gtd/FORMAT.md; a clean parse removes any stale .gtd/FORMAT.md\n # instead. What FORMAT.md's presence/absence MEANS (another drafting\n # round vs. proceeding to the human) is decided by this state's own\n # `on` rules at capture time — never here.\n set +e\n mkdir -p .gtd\n awk '\n BEGIN { in_section = 0; have_question = 0; question = \"\"; qline = 0; body_seen = 0 }\n {\n trimmed = $0\n gsub(/^[ \\t]+|[ \\t]+$/, \"\", trimmed)\n\n level = 0\n if (match(trimmed, /^#+/)) level = RLENGTH\n\n if (in_section && level >= 1 && level <= 2) {\n if (have_question && !body_seen) {\n printf \"<%~ it.vars.todoFile %>:%d: open question \\\"%s\\\" is missing a \\\"Suggested default: ...\\\" or \\\"Answer: ...\\\" line\\n\", qline, question\n }\n in_section = 0\n have_question = 0\n }\n\n if (trimmed == \"## Open Questions\") { in_section = 1; next }\n\n if (in_section && level == 3) {\n if (have_question && !body_seen) {\n printf \"<%~ it.vars.todoFile %>:%d: open question \\\"%s\\\" is missing a \\\"Suggested default: ...\\\" or \\\"Answer: ...\\\" line\\n\", qline, question\n }\n question = trimmed\n sub(/^### */, \"\", question)\n qline = NR\n have_question = 1\n body_seen = 0\n next\n }\n\n if (in_section && have_question && !body_seen && trimmed != \"\") {\n if (trimmed ~ /^Suggested default: *.+/ || trimmed ~ /^Answer: *.+/) {\n body_seen = 1\n } else {\n printf \"<%~ it.vars.todoFile %>:%d: open question \\\"%s\\\" is missing a \\\"Suggested default: ...\\\" or \\\"Answer: ...\\\" line\\n\", qline, question\n body_seen = 1\n }\n }\n }\n END {\n if (in_section && have_question && !body_seen) {\n printf \"<%~ it.vars.todoFile %>:%d: open question \\\"%s\\\" is missing a \\\"Suggested default: ...\\\" or \\\"Answer: ...\\\" line\\n\", qline, question\n }\n }\n ' <%~ it.vars.todoFile %> > .gtd/.format-findings\n\n if [ -s .gtd/.format-findings ]; then\n mv .gtd/.format-findings .gtd/FORMAT.md\n else\n rm -f .gtd/.format-findings\n rm -f .gtd/FORMAT.md\n fi\n on:\n \"A .gtd/FORMAT.md\": grilling\n \"M .gtd/FORMAT.md\": grilling\n \"D .gtd/FORMAT.md\": grilling-answer\n \"C\": grilling-answer\n\n grilling-answer:\n actor: human\n file: <%= it.vars.todoFile %>\n mode: qa\n message: |\n `<%= it.vars.todoFile %>` holds the plan under development, with any\n open questions under `## Open Questions` — each carrying a suggested\n default.\n\n To answer a question, edit its entry in place (replace\n `Suggested default: ...` with `Answer: ...`, or add further detail),\n then run `gtd step human`.\n\n To accept all suggested defaults as-is and move on to implementation,\n run `gtd step human` with no edits.\n on:\n \"C\": building\n \"* **\": grilling\n\n building:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create or edit anything under `.gtd/` except deleting\n `<%= it.vars.todoFile %>`, which this prompt tells you to delete once\n its work is complete.\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\n all 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 checking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: run the test suite and record a\n # red run's output as .gtd/FEEDBACK.md. What that output MEANS (another\n # fix round vs escalation) is decided by the workflow's own `on`/`retry`\n # rules at capture time — never here.\n #\n # `it.vars.testCommand` defaults to \"npm test\" (this workflow's own\n # `vars:` above) — override it with a top-level `.gtdrc`\n # `vars: { testCommand: ... }` key, or a `GTD_VAR_testCommand`\n # environment variable (highest precedence).\n #\n # A green run also removes any `<%= it.vars.feedbackFile %>` left over\n # from an earlier red run — there is no squash to discard it for us\n # anymore, so the script cleans it up itself before moving on to\n # review.\n set +e\n mkdir -p .gtd\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output <%~ it.vars.feedbackFile %>\n else\n rm -f .gtd/.check-output\n printf 'npm test failed with exit code %s and produced no output.' \"$code\" > <%~ it.vars.feedbackFile %>\n fi\n else\n rm -f .gtd/.check-output\n rm -f <%~ it.vars.feedbackFile %>\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": reviewing\n \"C\": reviewing\n\n fixing:\n actor: agent\n file: <%= it.vars.feedbackFile %>\n retry:\n max: 3\n otherwise: escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix\n the code so the suite passes. Keep the change focused — do not\n refactor unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue; the\n machine picks the dispute up either way.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": checking\n\n escalate:\n actor: human\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 Investigate and fix it yourself (editing code and/or\n `<%= it.vars.feedbackFile %>`), then run `gtd step human` to try the\n check again.\n on:\n \"* **\": checking\n\n reviewing:\n actor: agent\n file: <%= it.vars.reviewFile %>\n mode: review\n model: smart\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewFile %>`, which this prompt tells you to write.\n Never touch `.gtd/FORMAT.md` yourself if it exists — a validator writes\n it, and only the validator removes it once you've fixed the underlying\n problem.\n\n If `.gtd/FORMAT.md` exists, it holds format findings from your last\n draft of `<%= it.vars.reviewFile %>` — fix those findings first, then\n continue below.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the cycle's\n full diff (inlined below) in 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\n of what changed and why, then one\n `- [ ] ./path/to/file.ts#42 — note` file pointer per hunk (checkboxes\n are for the human to tick, not you; every path is `./`-relative, with\n an optional `#line` and an optional ` — note`).\n\n Leave `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n <% if (it.processDiff.trim()) { %>\n\n ## Full cycle diff\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"* **\": review-validating\n\n review-validating:\n actor: check\n file: <%= it.vars.reviewFile %>\n mode: review\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: a pragmatic grep/awk port of\n # src/ReviewDoc.ts's parser rules (the format's executable spec) — a\n # `# Review: <hash>` header as the first non-blank line, a\n # `<!-- base: <hash> -->` comment, and at least one `##` chunk with at\n # least one `- [ ]`/`- [x]` file pointer. A violation is one finding\n # line (file + line + rule) in .gtd/FORMAT.md; a clean parse removes any\n # stale .gtd/FORMAT.md instead. What FORMAT.md's presence/absence MEANS\n # (another drafting round vs. handing off to the human) is decided by\n # this state's own `on` rules at capture time — never here.\n set +e\n mkdir -p .gtd\n awk '\n BEGIN {\n first_nonblank_seen = 0\n header_ok = 0\n base_ok = 0\n chunk_open = 0\n chunk_title = \"\"\n chunk_line = 0\n chunk_files = 0\n chunk_count = 0\n }\n {\n trimmed = $0\n gsub(/^[ \\t]+|[ \\t]+$/, \"\", trimmed)\n\n if (!first_nonblank_seen && trimmed != \"\") {\n first_nonblank_seen = 1\n if (trimmed ~ /^# Review: [^ ]+$/) {\n header_ok = 1\n } else {\n printf \"<%~ it.vars.reviewFile %>:%d: first non-blank line must be \\\"# Review: <hash>\\\"\\n\", NR\n }\n }\n\n if (trimmed ~ /^<!-- *base: *[^ ]+ *-->$/) base_ok = 1\n\n if (trimmed ~ /^## [^ ]/) {\n if (chunk_open && chunk_files == 0) {\n printf \"<%~ it.vars.reviewFile %>:%d: chunk \\\"%s\\\" has no file pointers\\n\", chunk_line, chunk_title\n }\n chunk_open = 1\n chunk_title = trimmed\n sub(/^## /, \"\", chunk_title)\n chunk_line = NR\n chunk_files = 0\n chunk_count++\n next\n }\n\n if (chunk_open) {\n if (trimmed ~ /^-[ \\t]*\\[[ xX]\\][ \\t]*\\.\\/[^ \\t]+/) {\n chunk_files++\n } else if (trimmed ~ /^-[ \\t]*\\[/) {\n printf \"<%~ it.vars.reviewFile %>:%d: file pointer does not match \\\"- [ ] ./path#line — note\\\"\\n\", NR\n }\n }\n }\n END {\n if (chunk_open && chunk_files == 0) {\n printf \"<%~ it.vars.reviewFile %>:%d: chunk \\\"%s\\\" has no file pointers\\n\", chunk_line, chunk_title\n }\n if (!header_ok) {\n print \"<%~ it.vars.reviewFile %>:1: missing or malformed \\\"# Review: <hash>\\\" header as first line\"\n }\n if (!base_ok) {\n print \"<%~ it.vars.reviewFile %>:1: missing \\\"<!-- base: <hash> -->\\\" comment\"\n }\n if (chunk_count == 0) {\n print \"<%~ it.vars.reviewFile %>:1: no \\\"##\\\" chunks found\"\n }\n }\n ' <%~ it.vars.reviewFile %> > .gtd/.format-findings\n\n if [ -s .gtd/.format-findings ]; then\n mv .gtd/.format-findings .gtd/FORMAT.md\n else\n rm -f .gtd/.format-findings\n rm -f .gtd/FORMAT.md\n fi\n on:\n \"A .gtd/FORMAT.md\": reviewing\n \"M .gtd/FORMAT.md\": reviewing\n \"D .gtd/FORMAT.md\": await-review\n \"C\": await-review\n\n await-review:\n actor: human\n file: <%= it.vars.reviewFile %>\n mode: review\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the completed\n cycle — tick a pointer's `- [ ]` box to `- [x]` to approve that item;\n add notes, untick, or edit code for feedback.\n\n To approve: tick every box, then run `gtd step human`.\n\n To approve the whole cycle without ticking every box individually:\n delete `<%= it.vars.reviewFile %>` and run `gtd step human`.\n\n To request changes: edit `<%= it.vars.reviewFile %>`\n (ticking/unticking boxes, adding notes) and/or the code, then run\n `gtd step human` — any edit to `<%= it.vars.reviewFile %>` sends the\n cycle to a deterministic check that either closes the cycle out (every\n box ticked) or extracts the still-unticked items into a fresh\n `<%= it.vars.todoFile %>` and routes back through grilling with that\n feedback. Code-only edits (leaving `<%= it.vars.reviewFile %>`\n untouched) go straight back to grilling.\n on:\n \"D .gtd/REVIEW.md\": idle\n \"M .gtd/REVIEW.md\": review-deciding\n \"* **\": grilling\n\n review-deciding:\n actor: check\n file: <%= it.vars.reviewFile %>\n mode: review\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: if .gtd/REVIEW.md still has any\n # unticked \"- [ ]\" pointer, extract those pointers (with their notes)\n # into a fresh .gtd/TODO.md; either way, remove .gtd/REVIEW.md. What\n # the resulting diff MEANS (feedback lap vs. full approval) is decided\n # by this state's own `on` rules at capture time — never here. The\n # A/M .gtd/TODO.md row is declared BEFORE the D .gtd/REVIEW.md row (see\n # this workflow's `on` map below) so a feedback lap — which always also\n # deletes .gtd/REVIEW.md — is captured as feedback, not mistaken for an\n # approval.\n set +e\n mkdir -p .gtd\n if grep -qE '^[[:space:]]*-[[:space:]]*\\[[[:space:]]*\\]' <%~ it.vars.reviewFile %> 2>/dev/null; then\n {\n echo \"Feedback from review — address these before continuing:\"\n echo\n grep -E '^[[:space:]]*-[[:space:]]*\\[[[:space:]]*\\]' <%~ it.vars.reviewFile %>\n } > <%~ it.vars.todoFile %>\n fi\n rm -f <%~ it.vars.reviewFile %>\n on:\n \"A .gtd/TODO.md\": grilling\n \"M .gtd/TODO.md\": grilling\n \"D .gtd/REVIEW.md\": idle\n \"C\": await-review\n"), ".");
|
|
189866
|
+
const compiled = compileWorkflowConfig((0, import_dist$1.parse)("# The bundled default gtd workflow (v3 \"pattern machine\" — see\n# docs/design/pattern-machine-plan.md, and\n# docs/design/steering-file-loops.md for the two validation loops below).\n# Compiled through the exact same `compileWorkflowConfig` a user's `.gtdrc`\n# `workflow:` key goes through — no privileged code path\n# (src/workflows/default.ts does the compiling).\n#\n# Every content string below is INLINE (no `./`-relative file references):\n# the bundled default ships inside a single-file build\n# (`dist/gtd.bundle.mjs`), so its content must already be resolved at BUILD\n# time rather than read from disk at RUNTIME — the same reason\n# `src/prompts/*.md` are imported through tsdown's `.md`-as-text loader\n# instead of read via `fs` at runtime. `default.yaml` itself is imported the\n# same way (tsdown/vitest both treat `.yaml` as text — see\n# tsdown.config.ts / tests/vitest.rawMd.ts), so this file never needs a\n# sibling prompts/ directory to exist on disk once installed.\n#\n# States, in cycle order — 12 states, no squash:\n# idle (initial, human, message)\n# -> grilling (agent, prompt): reads .gtd/TODO.md's sketch (or a prior\n# lap's feedback), explores the codebase, and develops it into a concrete\n# implementation plan, stating any open question it can't settle itself\n# under a `## Open Questions` heading (see \"Loop 1\" below) rather than\n# guessing silently.\n# grilling -> todo-validating (check, script): mechanics-only parser port\n# of the open-questions format (see src/OpenQuestions.ts's module doc for\n# the executable spec this is a bash rendition of). Malformed ->\n# .gtd/FORMAT.md -> back to grilling; valid -> grilling-answer.\n# todo-validating -> grilling-answer (human, message): tick off suggested\n# defaults by replacing them with `Answer:` lines; a clean step accepts\n# every remaining default -> building; any edit loops back to grilling.\n# grilling-answer -> building (agent, prompt): implements the plan in\n# .gtd/TODO.md directly (no task decomposition, no per-task queue) and\n# deletes .gtd/TODO.md once the work is complete and verified.\n# building -> checking (check, script): runs the test command inline;\n# A/M .gtd/FEEDBACK.md -> fixing (capped, redirects to escalate); D\n# .gtd/FEEDBACK.md (a green run that just cleaned up prior feedback) or C\n# (green, nothing to clean) -> reviewing.\n# fixing (agent, prompt) <-> checking\n# escalate (human, message): manual intervention, then back to checking\n# checking (green) -> reviewing (agent, prompt): writes .gtd/REVIEW.md in\n# the checkbox review format (see \"Loop 2\" below), grouping the cycle's\n# full diff into reviewable chunks.\n# reviewing -> review-validating (check, script): mechanics-only parser\n# port of the review format (see src/ReviewDoc.ts's module doc for the\n# executable spec). Malformed -> .gtd/FORMAT.md -> back to reviewing;\n# valid -> await-review.\n# review-validating -> await-review (human, message): tick a pointer's `- [\n# ]` to `- [x]` to approve that item; deleting .gtd/REVIEW.md outright is\n# the power-user approve-everything shortcut; anything else pending\n# (code edits with REVIEW.md untouched) is feedback straight to grilling.\n# await-review -> review-deciding (check, script), entered by any `M\n# .gtd/REVIEW.md` step: if no unticked `- [ ]` pointer remains, the cycle\n# is approved (rm .gtd/REVIEW.md -> idle); otherwise the still-unticked\n# pointers (with their notes) are extracted into a fresh .gtd/TODO.md and\n# .gtd/REVIEW.md is removed (-> grilling, feedback lap).\n#\n# The cycle ends at human approval, resting back at idle — there is no\n# squash state. The cycle's commits stay in history; whether/how to squash\n# them (amend, interactive rebase, a PR's squash-merge) is entirely the\n# human's business once they're done reviewing — gtd makes no assumption.\n# That squash-flavored finale (a `squashing` prompt state + a `done` commit\n# state) still exists as an ENGINE capability (see STATES.md §8) and is kept\n# as part of the fuller machine at docs/examples/advanced-workflow.md.\n#\n# A `gtd(<actor>): idle` commit — entering the workflow's own initial state —\n# is therefore a PROCESS BOUNDARY (see `computeProcessRun` in src/Edge.ts):\n# the next cycle's `retry` counts, `startCommit`, and diffs never reach back\n# across an approved cycle's own idle-entering commit.\n#\n# HYGIENE INVARIANT (asserted in e2e — see default-workflow.feature): an\n# approved cycle leaves `.gtd/` empty. FEEDBACK.md is cleaned up by a green\n# `checking` run; FORMAT.md by a valid validator run (either loop); REVIEW.md\n# by the review-deciding approval branch; TODO.md by `building`.\n#\n# Loop 1 — TODO.md open questions (src/OpenQuestions.ts is this format's\n# EXECUTABLE SPEC — its unit tests are the format's spec tests; the\n# `todo-validating`/`review-validating` bash scripts below are a pragmatic\n# grep/awk PORT of the exact same rules, not a full markdown parser. Keep\n# both in sync by hand — there is no shared code path between the two\n# implementations, by design: the engine stays git/filesystem/Effect-free,\n# and the LSP's parsers stay dependency-free).\n#\n# Loop 2 — REVIEW.md checkboxes: same dual-implementation contract, this time\n# against src/ReviewDoc.ts.\n#\n# `grilling` and `reviewing` both declare `model: smart` — an opaque harness\n# hint (see the comment at `grilling` below) for their heavier one-shot\n# planning/reviewing turns. Every other state is unset (harness default).\n#\n# `vars.testCommand` is this workflow's own declared default for `checking`'s\n# script (see it.vars in PatternTemplates.ts) — override it per-repo with a\n# top-level `.gtdrc` `vars: { testCommand: ... }` key, or per-invocation with\n# a `GTD_VAR_testCommand` environment variable (highest precedence).\n#\n# `vars.todoFile`/`reviewFile`/`feedbackFile` are this workflow's ONE source\n# of truth for the three steering-file paths every prompt/script below reads\n# or writes — each appears in templates as `<%~ it.vars.todoFile %>` etc\n# (see docs/design/state-file-association.md §2). Every state that rests on\n# one of these files also declares a `file:`/`mode:` pair (see STATES.md's\n# property table) so `gtd status`/`gtd next --json` and the LSP (`gtd lsp`)\n# know which file a human/editor should look at, and in what format\n# (`qa` for TODO.md's open-questions format, `review` for REVIEW.md's\n# checkbox format) — `fixing`/`escalate` declare `file:` alone (FEEDBACK.md\n# is plain text, no LSP format). KNOWN LIMITATION: `on` pattern keys below are\n# NOT Eta templates — they keep the LITERAL `.gtd/...` paths matching these\n# vars' DEFAULT values. Repointing `todoFile`/`reviewFile`/`feedbackFile` via\n# `.gtdrc`'s top-level `vars:` or a `GTD_VAR_` override changes what `file:`\n# renders to (and what a template reads/writes) WITHOUT changing what the\n# `on` patterns match against — desyncing the machine. See\n# docs/configuration.md's `file:`/`mode:` section for the full caveat.\n#\n# A fuller machine — two-phase Q&A planning, an architecture phase, task\n# decomposition, a deterministic per-task builder loop, agent-prepared\n# review, and the squash finale (`squashing` + `done`) — is preserved as a\n# copy-paste `.gtdrc` example at docs/examples/advanced-workflow.md.\n\nvars:\n testCommand: npm test\n todoFile: .gtd/TODO.md\n reviewFile: .gtd/REVIEW.md\n feedbackFile: .gtd/FEEDBACK.md\n\nstates:\n idle:\n actor: human\n initial: true\n message: |\n No active gtd cycle.\n\n To start one: write `.gtd/TODO.md` with a short sketch of what you want\n built — a few sentences is enough — then run `gtd step human`.\n on:\n \"* **\": grilling\n\n grilling:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n # `model` is an opaque harness hint — gtd never interprets this string, it\n # only passes it through to `gtd next --json`/`gtd status --json` so the\n # driving loop can map it onto whatever models its agent harness\n # provides. \"smart\" here just names the harness-chosen tier for the\n # heavier planning turn; states without a `model` use the harness's\n # default.\n model: smart\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.todoFile %>`, which this prompt tells you to develop. Never\n touch `.gtd/FORMAT.md` yourself if it exists — a validator writes it,\n and only the validator removes it once you've fixed the underlying\n problem.\n\n If `.gtd/FORMAT.md` exists, it holds format findings from your last\n draft of `<%= it.vars.todoFile %>` — fix those findings first, then\n continue below.\n\n `<%= it.vars.todoFile %>` holds a short sketch of what to build (or\n feedback from a prior review lap). Read it, explore the codebase, and\n develop it into a concrete implementation plan.\n\n For every question you cannot settle yourself, add it under a\n `## Open Questions` heading, one `### <question>` sub-heading each,\n whose body's FIRST non-blank line is exactly\n `Suggested default: <your answer>` — the human can accept it as-is or\n override it. Omit the section entirely once there are no open\n questions. If `<%= it.vars.todoFile %>` already carries\n `## Open Questions` entries from a previous lap, fold any\n `Answer: <text>` line into the plan; every remaining entry's body must\n still start with either `Suggested default: ...` or `Answer: ...` —\n never anything else.\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 \"* **\": todo-validating\n\n todo-validating:\n actor: check\n file: <%= it.vars.todoFile %>\n mode: qa\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: a pragmatic grep/awk port of\n # src/OpenQuestions.ts's parser rules (the format's executable spec) —\n # free-form prose, plus an OPTIONAL \"## Open Questions\" section; every\n # \"### \" sub-heading directly under it is one question whose body's\n # first non-blank line must start with \"Suggested default: \" or\n # \"Answer: \". A violation is one finding line (file + line + rule) in\n # .gtd/FORMAT.md; a clean parse removes any stale .gtd/FORMAT.md\n # instead. What FORMAT.md's presence/absence MEANS (another drafting\n # round vs. proceeding to the human) is decided by this state's own\n # `on` rules at capture time — never here.\n set +e\n mkdir -p .gtd\n awk '\n BEGIN { in_section = 0; have_question = 0; question = \"\"; qline = 0; body_seen = 0 }\n {\n trimmed = $0\n gsub(/^[ \\t]+|[ \\t]+$/, \"\", trimmed)\n\n level = 0\n if (match(trimmed, /^#+/)) level = RLENGTH\n\n if (in_section && level >= 1 && level <= 2) {\n if (have_question && !body_seen) {\n printf \"<%~ it.vars.todoFile %>:%d: open question \\\"%s\\\" is missing a \\\"Suggested default: ...\\\" or \\\"Answer: ...\\\" line\\n\", qline, question\n }\n in_section = 0\n have_question = 0\n }\n\n if (trimmed == \"## Open Questions\") { in_section = 1; next }\n\n if (in_section && level == 3) {\n if (have_question && !body_seen) {\n printf \"<%~ it.vars.todoFile %>:%d: open question \\\"%s\\\" is missing a \\\"Suggested default: ...\\\" or \\\"Answer: ...\\\" line\\n\", qline, question\n }\n question = trimmed\n sub(/^### */, \"\", question)\n qline = NR\n have_question = 1\n body_seen = 0\n next\n }\n\n if (in_section && have_question && !body_seen && trimmed != \"\") {\n if (trimmed ~ /^Suggested default: *.+/ || trimmed ~ /^Answer: *.+/) {\n body_seen = 1\n } else {\n printf \"<%~ it.vars.todoFile %>:%d: open question \\\"%s\\\" is missing a \\\"Suggested default: ...\\\" or \\\"Answer: ...\\\" line\\n\", qline, question\n body_seen = 1\n }\n }\n }\n END {\n if (in_section && have_question && !body_seen) {\n printf \"<%~ it.vars.todoFile %>:%d: open question \\\"%s\\\" is missing a \\\"Suggested default: ...\\\" or \\\"Answer: ...\\\" line\\n\", qline, question\n }\n }\n ' <%~ it.vars.todoFile %> > .gtd/.format-findings\n\n if [ -s .gtd/.format-findings ]; then\n mv .gtd/.format-findings .gtd/FORMAT.md\n else\n rm -f .gtd/.format-findings\n rm -f .gtd/FORMAT.md\n fi\n on:\n \"A .gtd/FORMAT.md\": grilling\n \"M .gtd/FORMAT.md\": grilling\n \"D .gtd/FORMAT.md\": grilling-answer\n \"C\": grilling-answer\n\n grilling-answer:\n actor: human\n file: <%= it.vars.todoFile %>\n mode: qa\n message: |\n `<%= it.vars.todoFile %>` holds the plan under development, with any\n open questions under `## Open Questions` — each carrying a suggested\n default.\n\n To answer a question, edit its entry in place (replace\n `Suggested default: ...` with `Answer: ...`, or add further detail),\n then run `gtd step human`.\n\n To accept all suggested defaults as-is and move on to implementation,\n run `gtd step human` with no edits.\n on:\n \"C\": building\n \"* **\": grilling\n\n building:\n actor: agent\n file: <%= it.vars.todoFile %>\n mode: qa\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create or edit anything under `.gtd/` except deleting\n `<%= it.vars.todoFile %>`, which this prompt tells you to delete once\n its work is complete.\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\n all 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 checking:\n actor: check\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: run the test suite and record a\n # red run's output as .gtd/FEEDBACK.md. What that output MEANS (another\n # fix round vs escalation) is decided by the workflow's own `on`/`retry`\n # rules at capture time — never here.\n #\n # `it.vars.testCommand` defaults to \"npm test\" (this workflow's own\n # `vars:` above) — override it with a top-level `.gtdrc`\n # `vars: { testCommand: ... }` key, or a `GTD_VAR_testCommand`\n # environment variable (highest precedence).\n #\n # A green run also removes any `<%= it.vars.feedbackFile %>` left over\n # from an earlier red run — there is no squash to discard it for us\n # anymore, so the script cleans it up itself before moving on to\n # review.\n set +e\n mkdir -p .gtd\n <%~ it.vars.testCommand %> > .gtd/.check-output 2>&1\n code=$?\n if [ \"$code\" -ne 0 ]; then\n if [ -s .gtd/.check-output ]; then\n mv .gtd/.check-output <%~ it.vars.feedbackFile %>\n else\n rm -f .gtd/.check-output\n printf 'npm test failed with exit code %s and produced no output.' \"$code\" > <%~ it.vars.feedbackFile %>\n fi\n else\n rm -f .gtd/.check-output\n rm -f <%~ it.vars.feedbackFile %>\n fi\n on:\n \"A .gtd/FEEDBACK.md\": fixing\n \"M .gtd/FEEDBACK.md\": fixing\n \"D .gtd/FEEDBACK.md\": reviewing\n \"C\": reviewing\n\n fixing:\n actor: agent\n file: <%= it.vars.feedbackFile %>\n retry:\n max: 3\n otherwise: escalate\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.feedbackFile %>`, which this prompt tells you to address.\n\n Read `<%= it.vars.feedbackFile %>` (the failing test output) and fix\n the code so the suite passes. Keep the change focused — do not\n refactor unrelated code. If the feedback is wrong, empty or delete\n `<%= it.vars.feedbackFile %>` instead of \"fixing\" a non-issue; the\n machine picks the dispute up either way.\n\n Leave everything uncommitted and finish your turn — do not commit.\n on:\n \"* **\": checking\n\n escalate:\n actor: human\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 Investigate and fix it yourself (editing code and/or\n `<%= it.vars.feedbackFile %>`), then run `gtd step human` to try the\n check again.\n on:\n \"* **\": checking\n\n reviewing:\n actor: agent\n file: <%= it.vars.reviewFile %>\n mode: review\n model: smart\n prompt: |\n You are an autonomous coding agent. `.gtd/` holds this workflow's own\n state — never create, edit, or delete anything under `.gtd/` except\n `<%= it.vars.reviewFile %>`, which this prompt tells you to write.\n Never touch `.gtd/FORMAT.md` yourself if it exists — a validator writes\n it, and only the validator removes it once you've fixed the underlying\n problem.\n\n If `.gtd/FORMAT.md` exists, it holds format findings from your last\n draft of `<%= it.vars.reviewFile %>` — fix those findings first, then\n continue below.\n\n Write `<%= it.vars.reviewFile %>` to help a human review the cycle's\n full diff (inlined below) in 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\n of what changed and why, then one\n `- [ ] ./path/to/file.ts#42 — note` file pointer per hunk (checkboxes\n are for the human to tick, not you; every path is `./`-relative, with\n an optional `#line` and an optional ` — note`).\n\n Leave `<%= it.vars.reviewFile %>` uncommitted and finish your turn.\n <% if (it.processDiff.trim()) { %>\n\n ## Full cycle diff\n\n ```diff\n <%~ it.processDiff %>\n ```\n <% } %>\n on:\n \"* **\": review-validating\n\n review-validating:\n actor: check\n file: <%= it.vars.reviewFile %>\n mode: review\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: a pragmatic grep/awk port of\n # src/ReviewDoc.ts's parser rules (the format's executable spec) — a\n # `# Review: <hash>` header as the first non-blank line, a\n # `<!-- base: <hash> -->` comment, and at least one `##` chunk with at\n # least one `- [ ]`/`- [x]` file pointer. A violation is one finding\n # line (file + line + rule) in .gtd/FORMAT.md; a clean parse removes any\n # stale .gtd/FORMAT.md instead. What FORMAT.md's presence/absence MEANS\n # (another drafting round vs. handing off to the human) is decided by\n # this state's own `on` rules at capture time — never here.\n set +e\n mkdir -p .gtd\n awk '\n BEGIN {\n first_nonblank_seen = 0\n header_ok = 0\n base_ok = 0\n chunk_open = 0\n chunk_title = \"\"\n chunk_line = 0\n chunk_files = 0\n chunk_count = 0\n }\n {\n trimmed = $0\n gsub(/^[ \\t]+|[ \\t]+$/, \"\", trimmed)\n\n if (!first_nonblank_seen && trimmed != \"\") {\n first_nonblank_seen = 1\n if (trimmed ~ /^# Review: [^ ]+$/) {\n header_ok = 1\n } else {\n printf \"<%~ it.vars.reviewFile %>:%d: first non-blank line must be \\\"# Review: <hash>\\\"\\n\", NR\n }\n }\n\n if (trimmed ~ /^<!-- *base: *[^ ]+ *-->$/) base_ok = 1\n\n if (trimmed ~ /^## [^ ]/) {\n if (chunk_open && chunk_files == 0) {\n printf \"<%~ it.vars.reviewFile %>:%d: chunk \\\"%s\\\" has no file pointers\\n\", chunk_line, chunk_title\n }\n chunk_open = 1\n chunk_title = trimmed\n sub(/^## /, \"\", chunk_title)\n chunk_line = NR\n chunk_files = 0\n chunk_count++\n next\n }\n\n if (chunk_open) {\n if (trimmed ~ /^-[ \\t]*\\[[ xX]\\][ \\t]*\\.\\/[^ \\t]+/) {\n chunk_files++\n } else if (trimmed ~ /^-[ \\t]*\\[/) {\n printf \"<%~ it.vars.reviewFile %>:%d: file pointer does not match \\\"- [ ] ./path#line — note\\\"\\n\", NR\n }\n }\n }\n END {\n if (chunk_open && chunk_files == 0) {\n printf \"<%~ it.vars.reviewFile %>:%d: chunk \\\"%s\\\" has no file pointers\\n\", chunk_line, chunk_title\n }\n if (!header_ok) {\n print \"<%~ it.vars.reviewFile %>:1: missing or malformed \\\"# Review: <hash>\\\" header as first line\"\n }\n if (!base_ok) {\n print \"<%~ it.vars.reviewFile %>:1: missing \\\"<!-- base: <hash> -->\\\" comment\"\n }\n if (chunk_count == 0) {\n print \"<%~ it.vars.reviewFile %>:1: no \\\"##\\\" chunks found\"\n }\n }\n ' <%~ it.vars.reviewFile %> > .gtd/.format-findings\n\n if [ -s .gtd/.format-findings ]; then\n mv .gtd/.format-findings .gtd/FORMAT.md\n else\n rm -f .gtd/.format-findings\n rm -f .gtd/FORMAT.md\n fi\n on:\n \"A .gtd/FORMAT.md\": reviewing\n \"M .gtd/FORMAT.md\": reviewing\n \"D .gtd/FORMAT.md\": await-review\n \"C\": await-review\n\n await-review:\n actor: human\n file: <%= it.vars.reviewFile %>\n mode: review\n # While the cycle rests here for human review, gtd opens a \"review checkout\n # window\": HEAD and the index are temporarily rewound to this cycle's start\n # (the process boundary — no `reviewBase` state is declared, so the base\n # defaults to it) with the working tree untouched, so the whole cycle diff\n # surfaces as ordinary uncommitted changes in the editor's git integration.\n # The window closes automatically on the next invocation, once the cycle\n # rests anywhere else (see src/ReviewWindow.ts, STATES.md §11).\n reviewWindow: true\n message: |\n `<%= it.vars.reviewFile %>` holds the review record for the completed\n cycle — tick a pointer's `- [ ]` box to `- [x]` to approve that item;\n add notes, untick, or edit code for feedback.\n\n To approve: tick every box, then run `gtd step human`.\n\n To approve the whole cycle without ticking every box individually:\n delete `<%= it.vars.reviewFile %>` and run `gtd step human`.\n\n To request changes: edit `<%= it.vars.reviewFile %>`\n (ticking/unticking boxes, adding notes) and/or the code, then run\n `gtd step human` — any edit to `<%= it.vars.reviewFile %>` sends the\n cycle to a deterministic check that either closes the cycle out (every\n box ticked) or extracts the still-unticked items into a fresh\n `<%= it.vars.todoFile %>` and routes back through grilling with that\n feedback. Code-only edits (leaving `<%= it.vars.reviewFile %>`\n untouched) go straight back to grilling.\n on:\n \"D .gtd/REVIEW.md\": idle\n \"M .gtd/REVIEW.md\": review-deciding\n \"* **\": grilling\n\n review-deciding:\n actor: check\n file: <%= it.vars.reviewFile %>\n mode: review\n script: |\n #!/usr/bin/env bash\n # gtd check turn — the driver (`gtd run`) executes this verbatim, then\n # steps the check actor. Mechanics only: if .gtd/REVIEW.md still has any\n # unticked \"- [ ]\" pointer, extract those pointers (with their notes)\n # into a fresh .gtd/TODO.md; either way, remove .gtd/REVIEW.md. What\n # the resulting diff MEANS (feedback lap vs. full approval) is decided\n # by this state's own `on` rules at capture time — never here. The\n # A/M .gtd/TODO.md row is declared BEFORE the D .gtd/REVIEW.md row (see\n # this workflow's `on` map below) so a feedback lap — which always also\n # deletes .gtd/REVIEW.md — is captured as feedback, not mistaken for an\n # approval.\n set +e\n mkdir -p .gtd\n if grep -qE '^[[:space:]]*-[[:space:]]*\\[[[:space:]]*\\]' <%~ it.vars.reviewFile %> 2>/dev/null; then\n {\n echo \"Feedback from review — address these before continuing:\"\n echo\n grep -E '^[[:space:]]*-[[:space:]]*\\[[[:space:]]*\\]' <%~ it.vars.reviewFile %>\n } > <%~ it.vars.todoFile %>\n fi\n rm -f <%~ it.vars.reviewFile %>\n on:\n \"A .gtd/TODO.md\": grilling\n \"M .gtd/TODO.md\": grilling\n \"D .gtd/REVIEW.md\": idle\n \"C\": await-review\n"), ".");
|
|
189795
189867
|
const defaultWorkflowDefinition = compiled.definition;
|
|
189796
189868
|
/** The default workflow's own declared `vars:` defaults — `{ testCommand: "npm test" }`, overridable via a top-level `.gtdrc` `vars:` key or a `GTD_VAR_testCommand` environment variable (see `src/Edge.ts`'s `resolveVars`). */
|
|
189797
189869
|
const defaultWorkflowVars = compiled.vars;
|
|
@@ -189811,6 +189883,22 @@ var Cwd = class Cwd extends Tag("Cwd")() {
|
|
|
189811
189883
|
* `./Edge.js`'s `resolveVars`). There are no other blessed config keys (see
|
|
189812
189884
|
* `./Config.js`'s module docstring for why).
|
|
189813
189885
|
*
|
|
189886
|
+
* Both keys decode as `Schema.Unknown`: the shape is validated structurally
|
|
189887
|
+
* by the workflow compiler (`src/PatternConfig.ts`), not by effect/schema —
|
|
189888
|
+
* the shape is deep and recursive, and the compiler's errors carry rule
|
|
189889
|
+
* coordinates a flat schema error cannot. The `jsonSchema` ANNOTATIONS below
|
|
189890
|
+
* exist for one consumer only: `scripts/generate-schema.ts`, which publishes
|
|
189891
|
+
* `schema.json` for editor-side autocompletion/validation
|
|
189892
|
+
* (yaml-language-server et al). They describe the same shape the compiler
|
|
189893
|
+
* enforces, minus the rules JSON Schema cannot express (exactly one content
|
|
189894
|
+
* kind per state, exactly one `initial: true` across the workflow, `on`/
|
|
189895
|
+
* `retry.otherwise` targets naming defined states, reachability) — the
|
|
189896
|
+
* compiler stays the source of truth; the annotation is the editor's first
|
|
189897
|
+
* net, never a second validator to keep behaviorally in sync. When the
|
|
189898
|
+
* compiler's accepted shape changes (a new state key, a new content kind),
|
|
189899
|
+
* update the annotation here alongside `src/PatternConfig.ts`'s
|
|
189900
|
+
* `KNOWN_STATE_KEYS`.
|
|
189901
|
+
*
|
|
189814
189902
|
* Kept in its own module, separate from `./Config.js`, so `scripts/generate-
|
|
189815
189903
|
* schema.ts` (run via `jiti`, a plain TS-via-Babel loader with no bundler-
|
|
189816
189904
|
* style pluggable per-extension loaders) can import JUST the schema without
|
|
@@ -189819,9 +189907,108 @@ var Cwd = class Cwd extends Tag("Cwd")() {
|
|
|
189819
189907
|
* loader, something `jiti` has no equivalent for and doesn't need here: the
|
|
189820
189908
|
* schema shape never depends on the bundled default workflow's content.
|
|
189821
189909
|
*/
|
|
189910
|
+
/** The `vars:` shape (top-level AND inside `workflow:`): a flat name -> scalar map (`compileVarsMap` coerces every scalar to a string). */
|
|
189911
|
+
const varsJsonSchema = {
|
|
189912
|
+
type: "object",
|
|
189913
|
+
description: "Flat name -> scalar map merged into every template's it.vars. Scalars are coerced to strings.",
|
|
189914
|
+
additionalProperties: { type: [
|
|
189915
|
+
"string",
|
|
189916
|
+
"number",
|
|
189917
|
+
"boolean"
|
|
189918
|
+
] }
|
|
189919
|
+
};
|
|
189920
|
+
/** The whole `workflow:` value — see `PatternConfig.ts`'s module docstring for the authoritative schema. */
|
|
189921
|
+
const workflowJsonSchema = {
|
|
189922
|
+
type: "object",
|
|
189923
|
+
description: "The whole machine definition: named states (plus the workflow's own vars: defaults). Compiled and validated by gtd at load time; content strings starting with ./ or ../ are file references inlined from the config file's directory.",
|
|
189924
|
+
additionalProperties: false,
|
|
189925
|
+
required: ["states"],
|
|
189926
|
+
properties: {
|
|
189927
|
+
vars: varsJsonSchema,
|
|
189928
|
+
states: {
|
|
189929
|
+
type: "object",
|
|
189930
|
+
description: "The workflow's named states. At least one; exactly one with initial: true.",
|
|
189931
|
+
minProperties: 1,
|
|
189932
|
+
additionalProperties: {
|
|
189933
|
+
type: "object",
|
|
189934
|
+
description: "One workflow state. Declare exactly one content kind (script/prompt/message/commit). A commit state is final: no actor, no on.",
|
|
189935
|
+
additionalProperties: false,
|
|
189936
|
+
properties: {
|
|
189937
|
+
actor: {
|
|
189938
|
+
type: "string",
|
|
189939
|
+
description: "Who acts at this state. Required on every non-commit state; forbidden on a commit state."
|
|
189940
|
+
},
|
|
189941
|
+
script: {
|
|
189942
|
+
type: "string",
|
|
189943
|
+
description: "Content kind: a shell script (Eta template). `gtd run` executes it verbatim via bash, then steps this state's actor."
|
|
189944
|
+
},
|
|
189945
|
+
prompt: {
|
|
189946
|
+
type: "string",
|
|
189947
|
+
description: "Content kind: an agent prompt (Eta template), emitted by `gtd next`."
|
|
189948
|
+
},
|
|
189949
|
+
message: {
|
|
189950
|
+
type: "string",
|
|
189951
|
+
description: "Content kind: a human-facing message (Eta template), emitted by `gtd next`."
|
|
189952
|
+
},
|
|
189953
|
+
commit: {
|
|
189954
|
+
type: "string",
|
|
189955
|
+
description: "Content kind: entering this state ends the process by squashing it into one commit with this message (Eta template). Final — no actor, no on."
|
|
189956
|
+
},
|
|
189957
|
+
on: {
|
|
189958
|
+
type: "object",
|
|
189959
|
+
description: "Ordered map of change pattern -> target state. Patterns: \"C\" (clean tree) or \"<A|M|D|*> <glob>\" over the pending diff; first declared match wins. Every target must name a defined state, and every non-initial state must be reachable through these edges (or a retry.otherwise).",
|
|
189960
|
+
additionalProperties: { type: "string" }
|
|
189961
|
+
},
|
|
189962
|
+
initial: {
|
|
189963
|
+
type: "boolean",
|
|
189964
|
+
description: "Exactly one state in the workflow must declare initial: true (and it must not be a commit state)."
|
|
189965
|
+
},
|
|
189966
|
+
retry: {
|
|
189967
|
+
type: "object",
|
|
189968
|
+
description: "Redirect transitions INTO this state once it has been entered `max` times in the current process.",
|
|
189969
|
+
additionalProperties: false,
|
|
189970
|
+
required: ["max", "otherwise"],
|
|
189971
|
+
properties: {
|
|
189972
|
+
max: {
|
|
189973
|
+
type: "integer",
|
|
189974
|
+
minimum: 0,
|
|
189975
|
+
description: "Entries allowed this process before redirecting."
|
|
189976
|
+
},
|
|
189977
|
+
otherwise: {
|
|
189978
|
+
type: "string",
|
|
189979
|
+
description: "Defined state to redirect to once over the cap."
|
|
189980
|
+
}
|
|
189981
|
+
}
|
|
189982
|
+
},
|
|
189983
|
+
model: {
|
|
189984
|
+
type: "string",
|
|
189985
|
+
description: "Opaque harness hint passed through `gtd next --json`/`gtd status --json` (e.g. \"smart\"). Never interpreted by gtd. Forbidden on a commit state."
|
|
189986
|
+
},
|
|
189987
|
+
file: {
|
|
189988
|
+
type: "string",
|
|
189989
|
+
description: "The state's steering file: an Eta template naming the file a human/editor should look at while the machine rests here. Forbidden on a commit state."
|
|
189990
|
+
},
|
|
189991
|
+
mode: {
|
|
189992
|
+
type: "string",
|
|
189993
|
+
enum: ["qa", "review"],
|
|
189994
|
+
description: "The steering file's format, dispatched on by the LSP. Requires a sibling `file:`. Forbidden on a commit state."
|
|
189995
|
+
},
|
|
189996
|
+
reviewWindow: {
|
|
189997
|
+
type: "boolean",
|
|
189998
|
+
description: "When true, gtd opens a review checkout window while the machine rests here — HEAD/index are rewound to the review base so the whole base..HEAD diff surfaces as uncommitted changes in the editor. Forbidden on a commit state."
|
|
189999
|
+
},
|
|
190000
|
+
reviewBase: {
|
|
190001
|
+
type: "boolean",
|
|
190002
|
+
description: "Marks the state whose most-recent in-process commit anchors the review window's diff base; absent any, the base is the process start. Forbidden on a commit state."
|
|
190003
|
+
}
|
|
190004
|
+
}
|
|
190005
|
+
}
|
|
190006
|
+
}
|
|
190007
|
+
}
|
|
190008
|
+
};
|
|
189822
190009
|
const ConfigSchema = Struct({
|
|
189823
|
-
workflow: optional(Unknown),
|
|
189824
|
-
vars: optional(Unknown)
|
|
190010
|
+
workflow: optional(Unknown.annotations({ jsonSchema: workflowJsonSchema })),
|
|
190011
|
+
vars: optional(Unknown.annotations({ jsonSchema: varsJsonSchema }))
|
|
189825
190012
|
});
|
|
189826
190013
|
//#endregion
|
|
189827
190014
|
//#region src/Config.ts
|
|
@@ -190735,7 +190922,7 @@ const renderDiff = (files) => {
|
|
|
190735
190922
|
};
|
|
190736
190923
|
//#endregion
|
|
190737
190924
|
//#region src/Git.ts
|
|
190738
|
-
const EMPTY_TREE$
|
|
190925
|
+
const EMPTY_TREE$2 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
190739
190926
|
/**
|
|
190740
190927
|
* Parse `git diff --name-status` output into `{ path, status }` pairs.
|
|
190741
190928
|
* Status codes: A (added), D (deleted), M (modified), R (renamed), C (copied), etc.
|
|
@@ -190865,7 +191052,7 @@ const makeGitImpl = (executor, root) => {
|
|
|
190865
191052
|
}),
|
|
190866
191053
|
commitDiff: (hash, exclude = []) => gen(function* () {
|
|
190867
191054
|
yield* exec("git", "rev-parse", "--verify", hash);
|
|
190868
|
-
const parent = yield* exec("git", "rev-parse", "--verify", `${hash}~1`).pipe(map$4((s) => s.trim()), catchAll(() => succeed$3(EMPTY_TREE$
|
|
191055
|
+
const parent = yield* exec("git", "rev-parse", "--verify", `${hash}~1`).pipe(map$4((s) => s.trim()), catchAll(() => succeed$3(EMPTY_TREE$2)));
|
|
190869
191056
|
const nameStatusOut = yield* exec("git", "diff", "--name-status", parent, hash).pipe(catchAll(() => succeed$3("")));
|
|
190870
191057
|
const allPaths = parseNameStatus(nameStatusOut);
|
|
190871
191058
|
const filtered = applyExcludes(allPaths, exclude);
|
|
@@ -190909,6 +191096,8 @@ const makeGitImpl = (executor, root) => {
|
|
|
190909
191096
|
return all;
|
|
190910
191097
|
}),
|
|
190911
191098
|
resolveRef: (ref) => exec("git", "rev-parse", "--verify", ref).pipe(map$4((s) => s.trim()), flatMap$4((hash) => /^[0-9a-f]{40}$/.test(hash) ? succeed$3(hash) : fail$2(/* @__PURE__ */ new Error(`Invalid ref: ${ref}`)))),
|
|
191099
|
+
readRefOption: (ref) => exec("git", "rev-parse", "--verify", "--quiet", ref).pipe(map$4((s) => some(s.trim())), catchAll(() => succeed$3(none$4()))),
|
|
191100
|
+
isAncestor: (a, b) => run(root, "git", "merge-base", "--is-ancestor", a, b).pipe(provide$1(succeed$2(CommandExecutor, executor)), map$4(() => true), catchAll(() => succeed$3(false))),
|
|
190912
191101
|
topLevel: () => exec("git", "rev-parse", "--show-toplevel").pipe(map$4((s) => s.trim())),
|
|
190913
191102
|
commitHistory: (base) => {
|
|
190914
191103
|
const range = base !== void 0 ? `${base}..HEAD` : void 0;
|
|
@@ -190943,7 +191132,12 @@ const makeGitImpl = (executor, root) => {
|
|
|
190943
191132
|
discardPending: () => gen(function* () {
|
|
190944
191133
|
yield* exec("git", "add", "-A");
|
|
190945
191134
|
yield* exec("git", "reset", "--hard", "HEAD");
|
|
190946
|
-
}).pipe(asVoid)
|
|
191135
|
+
}).pipe(asVoid),
|
|
191136
|
+
updateRef: (ref, hash) => exec("git", "update-ref", ref, hash).pipe(asVoid),
|
|
191137
|
+
deleteRef: (ref) => exec("git", "update-ref", "-d", ref).pipe(asVoid),
|
|
191138
|
+
mixedResetTo: (ref) => exec("git", "reset", "--mixed", ref).pipe(asVoid),
|
|
191139
|
+
restoreStagedFrom: (source, paths) => paths.length === 0 ? _void : exec("git", "restore", "--staged", `--source=${source}`, "--", ...paths).pipe(catchAll(() => _void)),
|
|
191140
|
+
addIntentToAdd: () => exec("git", "add", "--intent-to-add", ".").pipe(asVoid)
|
|
190947
191141
|
};
|
|
190948
191142
|
};
|
|
190949
191143
|
const makeLiveEffect = gen(function* () {
|
|
@@ -191493,8 +191687,8 @@ const renderStateTemplate = (template, context) => eta.renderString(template, co
|
|
|
191493
191687
|
* one commit (or one squash). A caller that wants several transitions issues
|
|
191494
191688
|
* several invocations.
|
|
191495
191689
|
*/
|
|
191496
|
-
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
191497
|
-
const subjectOf = (message) => (message.split("\n")[0] ?? "").trim();
|
|
191690
|
+
const EMPTY_TREE$1 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
191691
|
+
const subjectOf$1 = (message) => (message.split("\n")[0] ?? "").trim();
|
|
191498
191692
|
/** Collapse an arbitrary git status letter to the pattern grammar's closed `A|M|D` set (mirrors the plan's decision 5 — only those three are meaningful statuses). */
|
|
191499
191693
|
const normalizeStatus = (raw) => raw === "A" ? "A" : raw === "D" ? "D" : "M";
|
|
191500
191694
|
/** Resolve HEAD's subject against the active workflow definition (the bundled default, or a compiled `.gtdrc` `workflow:` key). */
|
|
@@ -191538,20 +191732,20 @@ const pendingChanges = (git) => git.changedPaths().pipe(map$4((entries) => entri
|
|
|
191538
191732
|
const computeProcessRun = (git, def) => gen(function* () {
|
|
191539
191733
|
if (!(yield* git.hasCommits())) return {
|
|
191540
191734
|
startHash: "",
|
|
191541
|
-
startParentHash: EMPTY_TREE,
|
|
191735
|
+
startParentHash: EMPTY_TREE$1,
|
|
191542
191736
|
trace: []
|
|
191543
191737
|
};
|
|
191544
191738
|
const initialState = initialStateOf(def);
|
|
191545
191739
|
const history = yield* git.commitHistory();
|
|
191546
191740
|
let i = history.length - 1;
|
|
191547
191741
|
while (i >= 0) {
|
|
191548
|
-
const parsed = parseStateSubject(subjectOf(history[i].message));
|
|
191742
|
+
const parsed = parseStateSubject(subjectOf$1(history[i].message));
|
|
191549
191743
|
if (parsed === void 0 || parsed.state === initialState) break;
|
|
191550
191744
|
i--;
|
|
191551
191745
|
}
|
|
191552
191746
|
const startIdx = i + 1;
|
|
191553
|
-
const trace = history.slice(startIdx).map((h) => parseStateSubject(subjectOf(h.message)).state);
|
|
191554
|
-
const startParentHash = i >= 0 ? history[i].hash : EMPTY_TREE;
|
|
191747
|
+
const trace = history.slice(startIdx).map((h) => parseStateSubject(subjectOf$1(h.message)).state);
|
|
191748
|
+
const startParentHash = i >= 0 ? history[i].hash : EMPTY_TREE$1;
|
|
191555
191749
|
return {
|
|
191556
191750
|
startHash: startIdx < history.length ? history[startIdx].hash : history[history.length - 1].hash,
|
|
191557
191751
|
startParentHash,
|
|
@@ -191675,7 +191869,7 @@ const executeDecision = (git, run, decision, context) => gen(function* () {
|
|
|
191675
191869
|
yield* git.discardPending();
|
|
191676
191870
|
return {
|
|
191677
191871
|
kind: "squash",
|
|
191678
|
-
subject: subjectOf(message)
|
|
191872
|
+
subject: subjectOf$1(message)
|
|
191679
191873
|
};
|
|
191680
191874
|
}
|
|
191681
191875
|
case "noop": return {
|
|
@@ -191685,6 +191879,120 @@ const executeDecision = (git, run, decision, context) => gen(function* () {
|
|
|
191685
191879
|
}
|
|
191686
191880
|
});
|
|
191687
191881
|
//#endregion
|
|
191882
|
+
//#region src/ReviewWindow.ts
|
|
191883
|
+
/**
|
|
191884
|
+
* The review checkout window (v3 re-introduction — see
|
|
191885
|
+
* `docs/design/pattern-machine-plan.md`'s follow-up note and STATES.md §11).
|
|
191886
|
+
* While a process RESTS at a state declaring `reviewWindow: true` (the
|
|
191887
|
+
* bundled default's `await-review`), HEAD and the index are temporarily
|
|
191888
|
+
* rewound to the review base with the working tree untouched, so the entire
|
|
191889
|
+
* `base..HEAD` diff surfaces as ordinary uncommitted changes in any editor's
|
|
191890
|
+
* standard git integration (SCM panel, gutters, per-file diffs, discard-hunk).
|
|
191891
|
+
*
|
|
191892
|
+
* v2 hard-wired this to a single `awaiting-review` gate; v3 drives it purely
|
|
191893
|
+
* from the DECLARATIVE `reviewWindow`/`reviewBase` state properties (see
|
|
191894
|
+
* `PatternMachine.StateDef`) — the pure engine never observes an open window,
|
|
191895
|
+
* exactly as before.
|
|
191896
|
+
*
|
|
191897
|
+
* Lifecycle — driven from the program edge (`src/program.ts`), bracketing
|
|
191898
|
+
* every state subcommand:
|
|
191899
|
+
*
|
|
191900
|
+
* - `closeReviewWindow` runs BEFORE anything reads or mutates state, keyed
|
|
191901
|
+
* solely on `refs/gtd/review-head` existing: `git reset --mixed
|
|
191902
|
+
* refs/gtd/review-head` restores HEAD/index exactly, leaving only the
|
|
191903
|
+
* reviewer's own edits dirty (captured by the resting state's own `on`
|
|
191904
|
+
* patterns like any other pending change). The pure machine therefore never
|
|
191905
|
+
* sees the window.
|
|
191906
|
+
* - `openReviewWindow` runs AFTER the subcommand finishes and self-guards on
|
|
191907
|
+
* the resolved rest declaring `reviewWindow: true`: it saves HEAD to
|
|
191908
|
+
* `refs/gtd/review-head` (the base to `refs/gtd/review-base`), then `git
|
|
191909
|
+
* reset --mixed <base>`. It re-arms after read-only commands (`gtd next` /
|
|
191910
|
+
* `gtd status`) and refused invocations too, so the editor's diff view stays
|
|
191911
|
+
* consistent no matter which command the loop last ran.
|
|
191912
|
+
*
|
|
191913
|
+
* Every open/close step is idempotent under re-entry, so a crash at any point
|
|
191914
|
+
* is recovered by the next invocation's close (the saved ref also keeps the
|
|
191915
|
+
* real head GC-reachable for the window's whole lifetime).
|
|
191916
|
+
*/
|
|
191917
|
+
const REVIEW_HEAD_REF = "refs/gtd/review-head";
|
|
191918
|
+
const REVIEW_BASE_REF = "refs/gtd/review-base";
|
|
191919
|
+
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
191920
|
+
const subjectOf = (message) => (message.split("\n")[0] ?? "").trim();
|
|
191921
|
+
/**
|
|
191922
|
+
* The diff base while the window is open: the hash of the most-recent
|
|
191923
|
+
* in-process turn commit that ENTERED a `reviewBase` state, or `undefined`
|
|
191924
|
+
* when the workflow declares no such state (the caller then falls back to the
|
|
191925
|
+
* process start). Walks the current process run's commits (oldest→newest, via
|
|
191926
|
+
* `commitHistory(startParentHash)`), so it never reaches across the process
|
|
191927
|
+
* boundary into a previous cycle.
|
|
191928
|
+
*/
|
|
191929
|
+
const reviewBaseHash = (git, def, run) => gen(function* () {
|
|
191930
|
+
const history = yield* git.commitHistory(run.startParentHash);
|
|
191931
|
+
let base;
|
|
191932
|
+
for (const commit of history) {
|
|
191933
|
+
const parsed = parseStateSubject(subjectOf(commit.message));
|
|
191934
|
+
if (parsed !== void 0 && isReviewBaseState(def, parsed.state)) base = commit.hash;
|
|
191935
|
+
}
|
|
191936
|
+
return base;
|
|
191937
|
+
});
|
|
191938
|
+
/**
|
|
191939
|
+
* Restore the real head if a review checkout window is open; no-op otherwise.
|
|
191940
|
+
* Keyed on `refs/gtd/review-head` existence, NOT on any config or machine
|
|
191941
|
+
* state, so it always runs first and the machine never sees the window.
|
|
191942
|
+
*
|
|
191943
|
+
* Fails loudly — refs left in place — when HEAD is no longer on the reviewed
|
|
191944
|
+
* branch (the base is not an ancestor of HEAD, e.g. after a branch switch): a
|
|
191945
|
+
* mixed reset there would rewrite the wrong branch's tip. Manual commits on
|
|
191946
|
+
* top of the base pass the guard: the reset keeps their content in the working
|
|
191947
|
+
* tree, where the next turn's capture picks it up as review feedback.
|
|
191948
|
+
*/
|
|
191949
|
+
const closeReviewWindow = gen(function* () {
|
|
191950
|
+
const git = yield* GitService;
|
|
191951
|
+
const savedHead = yield* git.readRefOption(REVIEW_HEAD_REF);
|
|
191952
|
+
if (isNone(savedHead)) return { closed: false };
|
|
191953
|
+
const base = yield* git.readRefOption(REVIEW_BASE_REF);
|
|
191954
|
+
if (isSome(base) && base.value !== EMPTY_TREE) {
|
|
191955
|
+
if (!(yield* git.isAncestor(base.value, "HEAD"))) return yield* fail$2(/* @__PURE__ */ new Error("a gtd review checkout window is open but HEAD has moved off the reviewed branch — return to it, or restore manually with `git reset --mixed refs/gtd/review-head && git update-ref -d refs/gtd/review-head && git update-ref -d refs/gtd/review-base`"));
|
|
191956
|
+
}
|
|
191957
|
+
yield* git.mixedResetTo(REVIEW_HEAD_REF);
|
|
191958
|
+
yield* git.deleteRef(REVIEW_HEAD_REF);
|
|
191959
|
+
yield* git.deleteRef(REVIEW_BASE_REF);
|
|
191960
|
+
return { closed: true };
|
|
191961
|
+
});
|
|
191962
|
+
/**
|
|
191963
|
+
* Open (or re-arm) the review checkout window. Self-guarded: a no-op unless
|
|
191964
|
+
* the resolved rest's state declares `reviewWindow: true` and a distinct base
|
|
191965
|
+
* commit exists, so the caller invokes it unconditionally after every state
|
|
191966
|
+
* subcommand.
|
|
191967
|
+
*
|
|
191968
|
+
* The base is the most-recent in-process `reviewBase` commit
|
|
191969
|
+
* (`reviewBaseHash`) or, absent any such state, the process start
|
|
191970
|
+
* (`startParentHash`). When that resolves to the empty tree (a process with no
|
|
191971
|
+
* prior commit) or to HEAD itself (an empty process), there is nothing to
|
|
191972
|
+
* surface and the window stays closed.
|
|
191973
|
+
*
|
|
191974
|
+
* Ordering is crash-safe: base ref → head ref → mixed reset → `.gtd/` index
|
|
191975
|
+
* pin → intent-to-add. A crash before the head-ref write leaves only a stale
|
|
191976
|
+
* base ref (overwritten on the next open); a crash after it leaves HEAD ==
|
|
191977
|
+
* review-head, which the next invocation's close restores as a no-op.
|
|
191978
|
+
*/
|
|
191979
|
+
const openReviewWindow = gen(function* () {
|
|
191980
|
+
const git = yield* GitService;
|
|
191981
|
+
const def = (yield* ConfigService).workflow;
|
|
191982
|
+
if (!(yield* git.hasCommits())) return { opened: false };
|
|
191983
|
+
if (!isReviewWindowState(def, resolveState(def, yield* git.lastCommitSubject()))) return { opened: false };
|
|
191984
|
+
const run = yield* computeProcessRun(git, def);
|
|
191985
|
+
const base = (yield* reviewBaseHash(git, def, run)) ?? run.startParentHash;
|
|
191986
|
+
const headHash = yield* git.resolveRef("HEAD");
|
|
191987
|
+
if (base === EMPTY_TREE || base === headHash) return { opened: false };
|
|
191988
|
+
yield* git.updateRef(REVIEW_BASE_REF, base);
|
|
191989
|
+
yield* git.updateRef(REVIEW_HEAD_REF, headHash);
|
|
191990
|
+
yield* git.mixedResetTo(base);
|
|
191991
|
+
yield* git.restoreStagedFrom(REVIEW_HEAD_REF, [".gtd"]);
|
|
191992
|
+
yield* git.addIntentToAdd();
|
|
191993
|
+
return { opened: true };
|
|
191994
|
+
});
|
|
191995
|
+
//#endregion
|
|
191688
191996
|
//#region node_modules/prettier/doc.mjs
|
|
191689
191997
|
var doc_exports = /* @__PURE__ */ __exportAll({
|
|
191690
191998
|
builders: () => builders,
|
|
@@ -364489,6 +364797,76 @@ const startLspServer = () => gen(function* () {
|
|
|
364489
364797
|
});
|
|
364490
364798
|
});
|
|
364491
364799
|
//#endregion
|
|
364800
|
+
//#region src/Mermaid.ts
|
|
364801
|
+
/**
|
|
364802
|
+
* `gtd mermaid`: a pure emitter of the active workflow's SHAPE as Mermaid
|
|
364803
|
+
* `stateDiagram-v2` source — one node per state, the `[*] -->` initial-state
|
|
364804
|
+
* marker, one edge per declared `on` row (labeled with its raw pattern
|
|
364805
|
+
* string), `--> [*]` for every commit state (final, no outgoing edges), and
|
|
364806
|
+
* one `note right of` per rest naming its actor/content-kind/retry cap. No
|
|
364807
|
+
* git, no Effect, no template rendering: same purity discipline as
|
|
364808
|
+
* `PatternMachine.ts` — a plain function of an already-`validateDefinition`d
|
|
364809
|
+
* `WorkflowDefinition` (see `Config.ts`'s `toOperations`/`compileWorkflowConfig`
|
|
364810
|
+
* — every `WorkflowDefinition` this module ever sees has already been
|
|
364811
|
+
* validated, so an `on` target or `retry.otherwise` is trusted to name a
|
|
364812
|
+
* defined state rather than re-checked here).
|
|
364813
|
+
*/
|
|
364814
|
+
/**
|
|
364815
|
+
* Mermaid identifiers must avoid quotes/newlines inside a quoted label —
|
|
364816
|
+
* collapse both so the label stays on one line and the diagram stays
|
|
364817
|
+
* parseable even if a state name or `on` pattern happens to carry either.
|
|
364818
|
+
*/
|
|
364819
|
+
const escapeLabel = (text) => text.replace(/"/g, "'").replace(/\r?\n/g, " ");
|
|
364820
|
+
/**
|
|
364821
|
+
* A Mermaid-safe node id for one state name: every non-word character folds
|
|
364822
|
+
* to `_` (state names may carry hyphens — `todo-validating`,
|
|
364823
|
+
* `review-deciding` — which Mermaid's bare identifier grammar doesn't
|
|
364824
|
+
* guarantee), and a leading digit gets an `s_` prefix (Mermaid ids can't
|
|
364825
|
+
* start with one). The exact original name still reaches the diagram via
|
|
364826
|
+
* this alias's `state "<name>" as <alias>` declaration.
|
|
364827
|
+
*/
|
|
364828
|
+
const aliasFor = (name) => {
|
|
364829
|
+
const folded = name.replace(/[^A-Za-z0-9_]/g, "_");
|
|
364830
|
+
return /^[0-9]/.test(folded) ? `s_${folded}` : folded;
|
|
364831
|
+
};
|
|
364832
|
+
/** One rest's actor/content-kind/retry-cap summary, for a `note right of` line. */
|
|
364833
|
+
const stateNote = (state) => {
|
|
364834
|
+
const parts = [state.actor, contentKindOf(state)].filter((p) => p !== void 0);
|
|
364835
|
+
const retry = state.retry !== void 0 ? [`retry ${state.retry.max}→${state.retry.otherwise}`] : [];
|
|
364836
|
+
return [...parts, ...retry].join(" · ");
|
|
364837
|
+
};
|
|
364838
|
+
/**
|
|
364839
|
+
* Render `def`'s shape as Mermaid `stateDiagram-v2` source. One
|
|
364840
|
+
* `state "<name>" as <alias>` declaration per state up front (so every
|
|
364841
|
+
* transition/note below can reference the safe alias while the rendered
|
|
364842
|
+
* diagram still shows the exact declared name), then the initial-state
|
|
364843
|
+
* marker, then one edge per `on` row (declaration order — same "first match
|
|
364844
|
+
* wins" order the engine itself evaluates), then `<alias> --> [*]` for every
|
|
364845
|
+
* commit state, then one actor/content-kind/retry note per rest.
|
|
364846
|
+
*/
|
|
364847
|
+
const renderMermaid = (def) => {
|
|
364848
|
+
const names = Object.keys(def.states);
|
|
364849
|
+
const alias = new Map(names.map((name) => [name, aliasFor(name)]));
|
|
364850
|
+
const lines = ["stateDiagram-v2"];
|
|
364851
|
+
for (const name of names) lines.push(` state "${escapeLabel(name)}" as ${alias.get(name)}`);
|
|
364852
|
+
lines.push(` [*] --> ${alias.get(initialStateOf(def))}`);
|
|
364853
|
+
for (const name of names) {
|
|
364854
|
+
const state = def.states[name];
|
|
364855
|
+
const from = alias.get(name);
|
|
364856
|
+
if (isCommitState(state)) {
|
|
364857
|
+
lines.push(` ${from} --> [*]`);
|
|
364858
|
+
continue;
|
|
364859
|
+
}
|
|
364860
|
+
for (const [pattern, target] of state.on ?? []) lines.push(` ${from} --> ${alias.get(target)} : ${escapeLabel(pattern)}`);
|
|
364861
|
+
}
|
|
364862
|
+
for (const name of names) {
|
|
364863
|
+
const state = def.states[name];
|
|
364864
|
+
if (isCommitState(state)) continue;
|
|
364865
|
+
lines.push(` note right of ${alias.get(name)} : ${escapeLabel(stateNote(state))}`);
|
|
364866
|
+
}
|
|
364867
|
+
return lines.join("\n") + "\n";
|
|
364868
|
+
};
|
|
364869
|
+
//#endregion
|
|
364492
364870
|
//#region src/program.ts
|
|
364493
364871
|
const _require = createRequire(import.meta.url);
|
|
364494
364872
|
const GTD_VERSION = _require("../package.json").version;
|
|
@@ -364504,6 +364882,8 @@ Commands:
|
|
|
364504
364882
|
actor (the built-in script driver)
|
|
364505
364883
|
status Print the resolved rest's state/actor and which declared
|
|
364506
364884
|
pattern (if any) each pending change matches (no mutation)
|
|
364885
|
+
mermaid Print the active workflow's shape as Mermaid
|
|
364886
|
+
stateDiagram-v2 source (no mutation)
|
|
364507
364887
|
format <file> Format a markdown file in place
|
|
364508
364888
|
lsp Start the LSP server for .gtd/ steering files (stdio)
|
|
364509
364889
|
|
|
@@ -364714,11 +365094,27 @@ const runStatusCommand = (argv, json, write) => gen(function* () {
|
|
|
364714
365094
|
if (json) writeStatusJson(write, rest, statusChanges, model, file);
|
|
364715
365095
|
else writeStatusPlain(write, rest, statusChanges, model, file);
|
|
364716
365096
|
});
|
|
365097
|
+
/**
|
|
365098
|
+
* `gtd mermaid`: pure emitter of the active workflow's SHAPE (not the
|
|
365099
|
+
* resolved rest) as Mermaid `stateDiagram-v2` source — see `src/Mermaid.ts`.
|
|
365100
|
+
* Needs only `ConfigService` (no HEAD resolution, no rendering), but is
|
|
365101
|
+
* dispatched alongside `next`/`status` since it still depends on the active
|
|
365102
|
+
* `.gtdrc` — unlike `format`/`lsp`, which need neither git nor config — so it
|
|
365103
|
+
* goes through the same repository-root guard and auto-init. Rejects
|
|
365104
|
+
* `--json`: there is no structured shape to emit beyond the Mermaid source
|
|
365105
|
+
* itself.
|
|
365106
|
+
*/
|
|
365107
|
+
const runMermaidCommand = (argv, json, write) => gen(function* () {
|
|
365108
|
+
if (json) return yield* fail$2(/* @__PURE__ */ new Error("gtd mermaid does not accept --json"));
|
|
365109
|
+
yield* rejectExtraArgs("mermaid", argv);
|
|
365110
|
+
write(renderMermaid((yield* ConfigService).workflow));
|
|
365111
|
+
});
|
|
364717
365112
|
const KNOWN_SUBCOMMANDS = [
|
|
364718
365113
|
"step",
|
|
364719
365114
|
"next",
|
|
364720
365115
|
"run",
|
|
364721
|
-
"status"
|
|
365116
|
+
"status",
|
|
365117
|
+
"mermaid"
|
|
364722
365118
|
];
|
|
364723
365119
|
/**
|
|
364724
365120
|
* `--version`/`-v` or `--help`/`-h`: short-circuits before any git or state
|
|
@@ -364768,6 +365164,7 @@ const dispatchKnownSubcommand = (sub, argv, json, write) => {
|
|
|
364768
365164
|
case "next": return runNextCommand(json, write);
|
|
364769
365165
|
case "run": return runRunCommand(argv, json, write);
|
|
364770
365166
|
case "status": return runStatusCommand(argv, json, write);
|
|
365167
|
+
case "mermaid": return runMermaidCommand(argv, json, write);
|
|
364771
365168
|
}
|
|
364772
365169
|
};
|
|
364773
365170
|
/**
|
|
@@ -364799,8 +365196,12 @@ function makeProgram(opts = {}) {
|
|
|
364799
365196
|
const git = yield* GitService;
|
|
364800
365197
|
const fs = yield* FileSystem;
|
|
364801
365198
|
yield* assertRunningFromRepoRoot(git, fs);
|
|
365199
|
+
yield* closeReviewWindow;
|
|
364802
365200
|
yield* (yield* ConfigInit).ensure;
|
|
364803
|
-
yield* dispatchKnownSubcommand(sub, argv, json, write);
|
|
365201
|
+
const outcome = yield* either(dispatchKnownSubcommand(sub, argv, json, write));
|
|
365202
|
+
const rearm = yield* either(openReviewWindow);
|
|
365203
|
+
if (isLeft(outcome)) return yield* fail$2(outcome.left);
|
|
365204
|
+
if (isLeft(rearm)) return yield* fail$2(rearm.left);
|
|
364804
365205
|
}).pipe(json ? catchAll((error) => sync$1(() => write(JSON.stringify({
|
|
364805
365206
|
state: "error",
|
|
364806
365207
|
prompt: error.message
|