@pmelab/gtd 58.2.0 → 59.0.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/.claude-plugin/plugin.json +1 -1
- package/README.md +120 -395
- package/dist/chunks/{Schema-B3o6eRNU.mjs → testkit-07Z_ENch.mjs} +13503 -3460
- package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
- package/dist/flows.mjs +1 -1
- package/dist/gtd.bundle.mjs +484 -497
- package/dist/testkit/gherkin.mjs +145 -65
- package/dist/testkit.mjs +1 -1
- package/dist/wire.mjs +1 -1
- package/package.json +3 -42
- package/skills/authoring/SKILL.md +34 -27
- package/src/cli/Cli.test.ts +148 -71
- package/src/cli/Cli.ts +384 -463
- package/src/cli/Help.test.ts +47 -0
- package/src/cli/Help.ts +70 -0
- package/src/cli/Program.test.ts +127 -33
- package/src/cli/Program.ts +70 -25
- package/src/edge/Edge.ts +4 -4
- package/src/emit/Emit.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/flags/index.test.ts +0 -13
- package/src/flags/index.ts +1 -3
- package/src/flows/factory.ts +3 -1
- package/src/gherkin/index.ts +135 -71
- package/src/gherkin/messages.test.ts +90 -0
- package/src/gherkin/messages.ts +84 -0
- package/src/install/Install.test.ts +14 -11
- package/src/install/Install.ts +38 -15
- package/src/install/index.ts +1 -2
- package/src/replay/Replay.test.ts +1 -1
- package/src/replay/index.ts +1 -1
- package/src/step/planStart.ts +6 -1
- package/src/testkit/TestRepo.test.ts +47 -2
- package/src/testkit/TestRepo.ts +51 -6
- package/src/twins/EmittedScriptRecognizer.ts +5 -8
- package/src/twins/FlowScripts.ts +1 -1
- package/src/wire/Land.ts +1 -1
- package/src/workflow/index.ts +1 -0
- package/src/workflow/load.test.ts +138 -113
- package/src/workflow/load.ts +7 -7
- package/src/workflow/skills.test.ts +5 -5
- package/src/workflow/skills.ts +1 -1
- package/src/workflow/vars.test.ts +30 -3
- package/src/workflow/vars.ts +34 -2
- package/src/workflow-kit/index.ts +1 -1
- package/dist/chunks/reserved-lMYCaapY.mjs +0 -1990
- package/dist/chunks/review-Bfaror38.mjs +0 -427
- package/dist/chunks/tail-Bd7L940p.mjs +0 -355
- package/dist/chunks/testkit-JDtpNKyi.mjs +0 -10410
- package/dist/workflows/cleanup.mjs +0 -36
- package/dist/workflows/feature.mjs +0 -1018
- package/dist/workflows/fix.mjs +0 -41
- package/dist/workflows/review.mjs +0 -45
- package/dist/workflows/rollout.mjs +0 -48
- package/dist/workflows.mjs +0 -40651
- package/src/install/ConfigScaffold.ts +0 -22
- package/src/workflows/cleanup/cleanup-path.feature +0 -94
- package/src/workflows/cleanup/cleanup.feature +0 -315
- package/src/workflows/cleanup/cleanup.test.ts +0 -276
- package/src/workflows/cleanup/cleanup.ts +0 -52
- package/src/workflows/cleanup/index.ts +0 -6
- package/src/workflows/feature/acceptance-criteria-tier.feature +0 -25
- package/src/workflows/feature/acceptance-criteria.feature +0 -389
- package/src/workflows/feature/criteria-coverage.feature +0 -102
- package/src/workflows/feature/criteria.test.ts +0 -147
- package/src/workflows/feature/criteria.ts +0 -267
- package/src/workflows/feature/fast-path.feature +0 -93
- package/src/workflows/feature/feature.test.ts +0 -338
- package/src/workflows/feature/feature.ts +0 -156
- package/src/workflows/feature/green.ts +0 -19
- package/src/workflows/feature/index.ts +0 -18
- package/src/workflows/feature/lens-models.feature +0 -69
- package/src/workflows/feature/lens-review.feature +0 -566
- package/src/workflows/feature/naive-acceptance.feature +0 -147
- package/src/workflows/feature/naive-test.feature +0 -263
- package/src/workflows/feature/one-step-land.feature +0 -93
- package/src/workflows/feature/planning.ts +0 -41
- package/src/workflows/feature/refactor-absorbs.feature +0 -131
- package/src/workflows/feature/refactor-first.feature +0 -158
- package/src/workflows/feature/refactor-lens-models.feature +0 -79
- package/src/workflows/feature/refactor-rebuild.feature +0 -273
- package/src/workflows/feature/refactor-review.feature +0 -1528
- package/src/workflows/feature/refactor.test.ts +0 -1220
- package/src/workflows/feature/refactor.ts +0 -368
- package/src/workflows/feature/release-flag.feature +0 -167
- package/src/workflows/feature/release-flag.test.ts +0 -49
- package/src/workflows/feature/release-flag.ts +0 -46
- package/src/workflows/feature/review-gate.feature +0 -241
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -142
- package/src/workflows/feature/risk-tier.feature +0 -351
- package/src/workflows/feature/seam-rejection.feature +0 -146
- package/src/workflows/feature/seam.feature +0 -297
- package/src/workflows/feature/seam.test.ts +0 -527
- package/src/workflows/feature/seam.ts +0 -426
- package/src/workflows/feature/standards.feature +0 -354
- package/src/workflows/feature/steps.test.ts +0 -65
- package/src/workflows/feature/steps.ts +0 -45
- package/src/workflows/feature/tier-pin.feature +0 -66
- package/src/workflows/feature/tier.test.ts +0 -166
- package/src/workflows/feature/tier.ts +0 -56
- package/src/workflows/fix/fix-lens-origin.feature +0 -114
- package/src/workflows/fix/fix-quality.feature +0 -212
- package/src/workflows/fix/fix.feature +0 -79
- package/src/workflows/fix/fix.test.ts +0 -74
- package/src/workflows/fix/fix.ts +0 -52
- package/src/workflows/fix/index.ts +0 -6
- package/src/workflows/index.ts +0 -47
- package/src/workflows/review/index.ts +0 -6
- package/src/workflows/review/review-door.feature +0 -199
- package/src/workflows/review/review.test.ts +0 -27
- package/src/workflows/review/review.ts +0 -60
- package/src/workflows/rollout/index.ts +0 -6
- package/src/workflows/rollout/rollout.feature +0 -510
- package/src/workflows/rollout/rollout.test.ts +0 -263
- package/src/workflows/rollout/rollout.ts +0 -82
- package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +0 -98
- package/src/workflows/shared/access.test.ts +0 -62
- package/src/workflows/shared/access.ts +0 -24
- package/src/workflows/shared/config.fixture.ts +0 -12
- package/src/workflows/shared/fences.ts +0 -24
- package/src/workflows/shared/health.test.ts +0 -148
- package/src/workflows/shared/health.ts +0 -191
- package/src/workflows/shared/learn.test.ts +0 -140
- package/src/workflows/shared/learn.ts +0 -201
- package/src/workflows/shared/lenses.test.ts +0 -39
- package/src/workflows/shared/lenses.ts +0 -294
- package/src/workflows/shared/options.test.ts +0 -157
- package/src/workflows/shared/options.ts +0 -92
- package/src/workflows/shared/progress.feature +0 -124
- package/src/workflows/shared/prose.ts +0 -263
- package/src/workflows/shared/recheck.feature +0 -60
- package/src/workflows/shared/registry.test.ts +0 -345
- package/src/workflows/shared/registry.ts +0 -183
- package/src/workflows/shared/reserved.ts +0 -11
- package/src/workflows/shared/review.test.ts +0 -1062
- package/src/workflows/shared/review.ts +0 -446
- package/src/workflows/shared/skills.test.ts +0 -121
- package/src/workflows/shared/skills.ts +0 -49
- package/src/workflows/shared/standards.test.ts +0 -90
- package/src/workflows/shared/standards.ts +0 -156
- package/src/workflows/shared/steps.test.ts +0 -219
- package/src/workflows/shared/steps.ts +0 -201
- package/src/workflows/shared/tail.test.ts +0 -454
- package/src/workflows/shared/tail.ts +0 -264
- package/src/workflows/shared/text.fixture.ts +0 -128
- package/src/workflows/shared/text.test.ts +0 -1430
- package/src/workflows/shared/text.ts +0 -1700
- package/src/workflows/shared/threads.test.ts +0 -129
- package/src/workflows/shared/vars.ts +0 -47
- package/src/workflows/shared/workflows.steps.ts +0 -54
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -20888,12 +20888,7 @@ const restorability = (git, headHash, tipHash) => gen(function* () {
|
|
|
20888
20888
|
});
|
|
20889
20889
|
//#endregion
|
|
20890
20890
|
//#region src/flags/index.ts
|
|
20891
|
-
const FLAGS$1 = [
|
|
20892
|
-
name: "risk_tier",
|
|
20893
|
-
owner: "pmelab",
|
|
20894
|
-
expiry: "2027-01-15",
|
|
20895
|
-
kind: "release"
|
|
20896
|
-
}];
|
|
20891
|
+
const FLAGS$1 = [];
|
|
20897
20892
|
const BOOL = {
|
|
20898
20893
|
"0": false,
|
|
20899
20894
|
false: false,
|
|
@@ -20935,6 +20930,7 @@ const findPackageJson = (fromUrl) => {
|
|
|
20935
20930
|
const GTD_VERSION = _require(findPackageJson(import.meta.url)).version;
|
|
20936
20931
|
//#endregion
|
|
20937
20932
|
//#region src/install/Install.ts
|
|
20933
|
+
const WORKFLOWS_REPO = "https://github.com/pmelab/gtd-workflows";
|
|
20938
20934
|
const MINIMAL_DRIVER = `#!/usr/bin/env sh
|
|
20939
20935
|
set -eu
|
|
20940
20936
|
|
|
@@ -21016,7 +21012,7 @@ while :; do
|
|
|
21016
21012
|
system="$(gtd next --json=system)"
|
|
21017
21013
|
validate="$(gtd next --json=validate)"
|
|
21018
21014
|
log="$(gtd next --json=log)"
|
|
21019
|
-
# \`model\` is a hint, not a model name —
|
|
21015
|
+
# \`model\` is a hint, not a model name — gtd-workflows' default ships
|
|
21020
21016
|
# \`smart\` and \`base\`, which \`claude --model\` rejects. Resolve them
|
|
21021
21017
|
# from your environment; unset, the hint is dropped and the agent runs
|
|
21022
21018
|
# its own default. Any other value is a real model name.
|
|
@@ -21338,14 +21334,37 @@ follow in the same numbered list.
|
|
|
21338
21334
|
existing config, the project's own README/docs) and ask the user what
|
|
21339
21335
|
they're trying to accomplish with gtd here — don't assume a fresh
|
|
21340
21336
|
install or silently start a process on their behalf.
|
|
21341
|
-
2. **Make sure a \`gtd.config.ts\` governs the repository.**
|
|
21342
|
-
|
|
21343
|
-
|
|
21344
|
-
|
|
21345
|
-
|
|
21346
|
-
|
|
21347
|
-
|
|
21348
|
-
|
|
21337
|
+
2. **Make sure a \`gtd.config.ts\` governs the repository.** gtd ships no
|
|
21338
|
+
workflow: the config imports the workflows it runs. Ask the user which
|
|
21339
|
+
way they want:
|
|
21340
|
+
- **A workflow from ${WORKFLOWS_REPO}.** Its \`default\` is a feature →
|
|
21341
|
+
review → fix workflow. It is used from a checkout pinned on this
|
|
21342
|
+
machine:
|
|
21343
|
+
|
|
21344
|
+
\`\`\`sh
|
|
21345
|
+
git clone ${WORKFLOWS_REPO} ~/.local/share/gtd/workflows
|
|
21346
|
+
~/.local/share/gtd/workflows/bin/gtd-wf pin origin/main # or a tag
|
|
21347
|
+
~/.local/share/gtd/workflows/bin/gtd-wf config default > gtd.config.ts
|
|
21348
|
+
\`\`\`
|
|
21349
|
+
|
|
21350
|
+
\`pin\` checks the ref out and bundles each workflow; \`config\` prints a
|
|
21351
|
+
config importing the bundle by its ABSOLUTE path. Tell the user what
|
|
21352
|
+
that means: the committed config works only where the checkout sits at
|
|
21353
|
+
that same path, so a teammate or CI needs their own checkout there (or
|
|
21354
|
+
their own config). If a checkout exists already, \`gtd-wf status\`
|
|
21355
|
+
shows its pin — reuse it rather than cloning again. Read the
|
|
21356
|
+
repository's README for the workflows it offers and their options.
|
|
21357
|
+
- **Their own workflow.** A workflow is TypeScript the config exports,
|
|
21358
|
+
written against \`@pmelab/gtd/flows\` — see
|
|
21359
|
+
https://github.com/pmelab/gtd/blob/main/docs/configuration.md, and
|
|
21360
|
+
https://github.com/pmelab/gtd/blob/main/docs/testing.md to test it. A
|
|
21361
|
+
copy of a gtd-workflows workflow (\`gtd-wf new <name> --from default\`
|
|
21362
|
+
in a clone of it) is a good start.
|
|
21363
|
+
|
|
21364
|
+
Repository settings — steering-file modes, \`gtd ui\`, per-scope skills
|
|
21365
|
+
and access — go in the config's \`config\` export; workflow settings are
|
|
21366
|
+
options of the workflow factory it exports.
|
|
21367
|
+
3. **Commit the config before the first drive.** An uncommitted config is a
|
|
21349
21368
|
pending change, so starting a process without committing it first turns
|
|
21350
21369
|
the config into the very first thing the process plans against. Commit
|
|
21351
21370
|
it, THEN start driving.
|
|
@@ -21371,7 +21390,8 @@ follow in the same numbered list.
|
|
|
21371
21390
|
from every prompt line and writing no model exports, so every turn runs
|
|
21372
21391
|
on the chosen CLI's own default tier.
|
|
21373
21392
|
6. **If model hints are honored, resolve them to real model identifiers.**
|
|
21374
|
-
|
|
21393
|
+
A workflow may declare model hints — gtd-workflows' \`default\` ships
|
|
21394
|
+
\`plannerModel: smart\` and \`coderModel: base\`.
|
|
21375
21395
|
**Those are opaque hints, not model names** — gtd never interprets them,
|
|
21376
21396
|
and passing \`--model smart\` to a real CLI fails on the first prompt
|
|
21377
21397
|
beat. Derive the chosen CLI's own available models at install time — its
|
|
@@ -21468,11 +21488,8 @@ hands off to \`gtd-build\`, which halts at the blocked gate and prints it,
|
|
|
21468
21488
|
rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
|
|
21469
21489
|
straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
|
|
21470
21490
|
\`gtd doors\` lists the doors a repository offers (those of the workflows its
|
|
21471
|
-
config exports —
|
|
21472
|
-
\`
|
|
21473
|
-
(remove it), which a config adds by exporting \`rollout\` from
|
|
21474
|
-
\`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
|
|
21475
|
-
\`@pmelab/gtd/workflows/cleanup\` — plus any it declares). Refuses with
|
|
21491
|
+
config exports — gtd-workflows' \`default\` brings \`fix\` and
|
|
21492
|
+
\`review [base]\` — plus any the config declares). Refuses with
|
|
21476
21493
|
\`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
|
|
21477
21494
|
missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
|
|
21478
21495
|
|
|
@@ -21587,28 +21604,6 @@ Any runtime works: the contract is \`--json\`/\`--json=<path>\` in, subprocesses
|
|
|
21587
21604
|
out.
|
|
21588
21605
|
`;
|
|
21589
21606
|
const renderBriefing = () => HEADER() + BEAT_PROTOCOL + JSON_FIELD_REFERENCE + DRIVER_OBLIGATIONS + RECOVERY + commandSuite() + EDITOR_INTEGRATION + PREREQUISITES;
|
|
21590
|
-
//#endregion
|
|
21591
|
-
//#region src/install/ConfigScaffold.ts
|
|
21592
|
-
const CONFIG_SCAFFOLD = `// The workflows gtd runs here: gtd's shipped feature, review and fix.
|
|
21593
|
-
// Every exported function is startable with \`gtd --workflow <name>\`; the
|
|
21594
|
-
// default export is what a change in the tree starts. Each workflow brings
|
|
21595
|
-
// its own doors: \`gtd door review [base]\` and \`gtd door fix\`. Export \`rollout\`
|
|
21596
|
-
// from \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
|
|
21597
|
-
// \`@pmelab/gtd/workflows/cleanup\` to add \`gtd door rollout <flag>\` and
|
|
21598
|
-
// \`gtd door cleanup <flag>\`.
|
|
21599
|
-
export {
|
|
21600
|
-
feature as default,
|
|
21601
|
-
feature,
|
|
21602
|
-
defaults,
|
|
21603
|
-
envDefaults,
|
|
21604
|
-
summary,
|
|
21605
|
-
steering,
|
|
21606
|
-
skills,
|
|
21607
|
-
access,
|
|
21608
|
-
} from "@pmelab/gtd/workflows/feature"
|
|
21609
|
-
export { review } from "@pmelab/gtd/workflows/review"
|
|
21610
|
-
export { fix } from "@pmelab/gtd/workflows/fix"
|
|
21611
|
-
`;
|
|
21612
21607
|
var FlowsProtocolError = class extends Error {
|
|
21613
21608
|
constructor(engine, flows) {
|
|
21614
21609
|
super(`gtd: the workflow speaks flows protocol ${flows}, but the engine installed protocol ${engine} — ${engine < flows ? "upgrade the gtd that runs it" : "upgrade the @pmelab/gtd the workflow imports"}`);
|
|
@@ -22073,7 +22068,7 @@ const defineWorkflow = (schema, build) => {
|
|
|
22073
22068
|
options
|
|
22074
22069
|
},
|
|
22075
22070
|
then: () => {
|
|
22076
|
-
throw new Error("a configured workflow is not a flow —
|
|
22071
|
+
throw new Error("a configured workflow is not a flow — await the flow functions it is built from instead");
|
|
22077
22072
|
}
|
|
22078
22073
|
});
|
|
22079
22074
|
return Object.assign(factory, { [FACTORY]: internals });
|
|
@@ -27856,7 +27851,7 @@ function tokenizeTitleBefore(effects, ok, nok) {
|
|
|
27856
27851
|
return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
|
|
27857
27852
|
}
|
|
27858
27853
|
}
|
|
27859
|
-
var definition, titleBefore;
|
|
27854
|
+
var definition$1, titleBefore;
|
|
27860
27855
|
var init_definition = __esmMin((() => {
|
|
27861
27856
|
init_micromark_factory_destination();
|
|
27862
27857
|
init_micromark_factory_label();
|
|
@@ -27865,7 +27860,7 @@ var init_definition = __esmMin((() => {
|
|
|
27865
27860
|
init_micromark_factory_whitespace();
|
|
27866
27861
|
init_micromark_util_character();
|
|
27867
27862
|
init_micromark_util_normalize_identifier();
|
|
27868
|
-
definition = {
|
|
27863
|
+
definition$1 = {
|
|
27869
27864
|
name: "definition",
|
|
27870
27865
|
tokenize: tokenizeDefinition
|
|
27871
27866
|
};
|
|
@@ -30853,7 +30848,7 @@ var init_constructs = __esmMin((() => {
|
|
|
30853
30848
|
[57]: list,
|
|
30854
30849
|
[62]: blockQuote
|
|
30855
30850
|
};
|
|
30856
|
-
contentInitial = { [91]: definition };
|
|
30851
|
+
contentInitial = { [91]: definition$1 };
|
|
30857
30852
|
flowInitial = {
|
|
30858
30853
|
[-2]: codeIndented,
|
|
30859
30854
|
[-1]: codeIndented,
|
|
@@ -38001,7 +37996,7 @@ const landProseText = (subject) => renderFormat(FMT_LAND_PROSE, subject);
|
|
|
38001
37996
|
* `gtd judge answer` (the only other caller of `planLanding`/`landFields`)
|
|
38002
37997
|
* passes its own two-word name so the hint stays runnable verbatim.
|
|
38003
37998
|
*/
|
|
38004
|
-
const renderLandPlain = (fields, command = "land") => fields.subject !== null ? `${landProseText(fields.subject).trimEnd()}\n(
|
|
37999
|
+
const renderLandPlain = (fields, command = "land") => fields.subject !== null ? `${landProseText(fields.subject).trimEnd()}\n(to land it: s=$(gtd ${command} --json=script) && sh -c "$s")\n` : noopText(fields.state);
|
|
38005
38000
|
//#endregion
|
|
38006
38001
|
//#region src/wire/Judge.ts
|
|
38007
38002
|
const judgeJson = (state, questions) => JSON.stringify({
|
|
@@ -38953,13 +38948,35 @@ const resolveVars = (workflowVars, entryVars, env) => {
|
|
|
38953
38948
|
}
|
|
38954
38949
|
return merged;
|
|
38955
38950
|
};
|
|
38951
|
+
const FLAG_VALUES = {
|
|
38952
|
+
"0": "0",
|
|
38953
|
+
false: "0",
|
|
38954
|
+
"1": "1",
|
|
38955
|
+
true: "1"
|
|
38956
|
+
};
|
|
38957
|
+
const flagVariable = (setting) => `GTD_FLAG_${setting.slice(5).toUpperCase()}`;
|
|
38958
|
+
/** The `flag_<name>` settings a workflow declares that gtd's own flags lack: the workflow's own release flags. */
|
|
38959
|
+
const ownFlags = (workflowVars, flags) => Object.keys(workflowVars).filter((name) => name.startsWith("flag_") && !Object.hasOwn(flags, name));
|
|
38960
|
+
/** Why a workflow's own release flag cannot start, if one is set to anything but 1, 0, true or false. */
|
|
38961
|
+
const ownFlagRefusal = (workflowVars, flags, env) => {
|
|
38962
|
+
const bad = ownFlags(workflowVars, flags).find((name) => {
|
|
38963
|
+
const raw = env[flagVariable(name)] ?? "";
|
|
38964
|
+
return raw !== "" && !Object.hasOwn(FLAG_VALUES, raw.toLowerCase());
|
|
38965
|
+
});
|
|
38966
|
+
return bad === void 0 ? void 0 : `${flagVariable(bad)} must be 1, 0, true or false`;
|
|
38967
|
+
};
|
|
38956
38968
|
/**
|
|
38957
38969
|
* `resolveVars` for a process being STARTED: `flags` land between the workflow
|
|
38958
|
-
* defaults and `--var`, for already-declared names only
|
|
38959
|
-
*
|
|
38970
|
+
* defaults and `--var`, for already-declared names only, and so does
|
|
38971
|
+
* `GTD_FLAG_<NAME>` for a `flag_<name>` the workflow declares itself. A running
|
|
38972
|
+
* process never calls this — it replays its pinned record.
|
|
38960
38973
|
*/
|
|
38961
38974
|
const resolveStartVars = (workflowVars, flags, entryVars, env) => {
|
|
38962
38975
|
const declared = Object.fromEntries(Object.entries(flags).filter(([name]) => Object.hasOwn(workflowVars, name)));
|
|
38976
|
+
for (const name of ownFlags(workflowVars, flags)) {
|
|
38977
|
+
const value = FLAG_VALUES[(env[flagVariable(name)] ?? "").toLowerCase()];
|
|
38978
|
+
if (value !== void 0) declared[name] = value;
|
|
38979
|
+
}
|
|
38963
38980
|
return resolveVars({
|
|
38964
38981
|
...workflowVars,
|
|
38965
38982
|
...declared
|
|
@@ -39123,7 +39140,7 @@ const dedupeDiagnostics = (diagnostics) => {
|
|
|
39123
39140
|
//#endregion
|
|
39124
39141
|
//#region src/workflow/load.ts
|
|
39125
39142
|
init_esm();
|
|
39126
|
-
const configError = (diagnostics) => new GtdError(`gtd config:\n${diagnostics.filter((d) => d.severity === "error").map((d) => ` - ${formatDiagnostic(d)}`).join("\n")}`);
|
|
39143
|
+
const configError = (diagnostics) => new GtdError(`gtd: config:\n${diagnostics.filter((d) => d.severity === "error").map((d) => ` - ${formatDiagnostic(d)}`).join("\n")}`);
|
|
39127
39144
|
const failOnErrors = (diagnostics) => diagnostics.some((d) => d.severity === "error") ? fail$7(configError(diagnostics)) : _void;
|
|
39128
39145
|
const MIGRATION_DOC = "https://github.com/pmelab/gtd/blob/main/docs/configuration.md#migrating-from-gtdrc";
|
|
39129
39146
|
/**
|
|
@@ -39139,7 +39156,7 @@ const refuseLegacyRc = gen(function* () {
|
|
|
39139
39156
|
const found = files.filter((f) => f.owned).map((f) => f.path);
|
|
39140
39157
|
if (found.length === 0) return;
|
|
39141
39158
|
return yield* fail$7(new GtdError([
|
|
39142
|
-
"gtd config:",
|
|
39159
|
+
"gtd: config:",
|
|
39143
39160
|
...found.map((file) => ` - ${file}: no longer read — delete it`),
|
|
39144
39161
|
" gtd 23 removed the .gtdrc files; move each key they set:",
|
|
39145
39162
|
" vars → an option of the workflow factory in gtd.config.ts, or its `defaults` export",
|
|
@@ -39163,7 +39180,7 @@ const load = gen(function* () {
|
|
|
39163
39180
|
const missing = {
|
|
39164
39181
|
severity: "error",
|
|
39165
39182
|
path: [],
|
|
39166
|
-
message: `no workflows configured: no ${WORKFLOW_MODULE} here or above —
|
|
39183
|
+
message: `no workflows configured: no ${WORKFLOW_MODULE} here or above — gtd ships no workflow; \`gtd install\` says where to get one`,
|
|
39167
39184
|
origin: host.root
|
|
39168
39185
|
};
|
|
39169
39186
|
return yield* fail$7(configError([missing]));
|
|
@@ -39175,7 +39192,7 @@ const load = gen(function* () {
|
|
|
39175
39192
|
});
|
|
39176
39193
|
const loaded = yield* try_({
|
|
39177
39194
|
try: () => loadWorkflows(module, shipped),
|
|
39178
|
-
catch: (e) => new GtdError(`gtd config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
|
|
39195
|
+
catch: (e) => new GtdError(`gtd: config:\n - ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
|
|
39179
39196
|
});
|
|
39180
39197
|
const compiled = loaded.config;
|
|
39181
39198
|
const diagnostics = dedupeDiagnostics(sortDiagnostics([...compiled.diagnostics, ...loaded.diagnostics]));
|
|
@@ -39680,7 +39697,7 @@ const replayToFirstStep = (loaded, vars, env, tree) => flatMap$4(promise(() => r
|
|
|
39680
39697
|
})), (outcome) => {
|
|
39681
39698
|
if (outcome.kind === "rest") return succeed$3(outcome.rest.name);
|
|
39682
39699
|
const problem = outcome.kind === "failed" || outcome.kind === "refused" ? outcome.message.replace(/^gtd: /, "") : "the default entry must begin at a step — that step is where a finished process waits";
|
|
39683
|
-
return fail$7(new GtdError(`gtd config:\n - ${loaded.file.origin}: ${problem}`));
|
|
39700
|
+
return fail$7(new GtdError(`gtd: config:\n - ${loaded.file.origin}: ${problem}`));
|
|
39684
39701
|
});
|
|
39685
39702
|
/**
|
|
39686
39703
|
* The default entry's first step — where a finished process waits — found the
|
|
@@ -39696,13 +39713,13 @@ const firstStep = (loaded, head) => gen(function* () {
|
|
|
39696
39713
|
const env = watchedEmptySettings();
|
|
39697
39714
|
const outcome = yield* either(replayToFirstStep(loaded, vars.settings, env.settings, empty.tree));
|
|
39698
39715
|
const reads = [...settingReads("process", vars), ...settingReads("environment", env)];
|
|
39699
|
-
if (reads.length > 0) return yield* fail$7(new GtdError(`gtd config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start reads ${reads.join(" and ")} — that step is where a finished process waits, so it must not depend on a setting`));
|
|
39716
|
+
if (reads.length > 0) return yield* fail$7(new GtdError(`gtd: config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start reads ${reads.join(" and ")} — that step is where a finished process waits, so it must not depend on a setting`));
|
|
39700
39717
|
if (outcome._tag === "Left") return yield* fail$7(outcome.left);
|
|
39701
39718
|
const name = outcome.right;
|
|
39702
39719
|
if (!empty.read()) return name;
|
|
39703
39720
|
const atHead = yield* replayToFirstStep(loaded, vars.settings, env.settings, head);
|
|
39704
39721
|
if (atHead === name) return name;
|
|
39705
|
-
return yield* fail$7(new GtdError(`gtd config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start depends on the repository's files ("${name}" without them, "${atHead}" at HEAD) — that step is where a finished process waits, so it must not change as files do`));
|
|
39722
|
+
return yield* fail$7(new GtdError(`gtd: config:\n - ${loaded.file.origin}: the flow's first step on an ordinary start depends on the repository's files ("${name}" without them, "${atHead}" at HEAD) — that step is where a finished process waits, so it must not change as files do`));
|
|
39706
39723
|
});
|
|
39707
39724
|
var ConfigService = class ConfigService extends Tag("ConfigService")() {
|
|
39708
39725
|
static Live = succeed$2(ConfigService, { load });
|
|
@@ -39710,7 +39727,7 @@ var ConfigService = class ConfigService extends Tag("ConfigService")() {
|
|
|
39710
39727
|
//#endregion
|
|
39711
39728
|
//#region src/workflow/skills.ts
|
|
39712
39729
|
init_esm();
|
|
39713
|
-
const fail$4 = (origin, message) => new GtdError(`gtd config:\n - ${origin}: ${message}`);
|
|
39730
|
+
const fail$4 = (origin, message) => new GtdError(`gtd: config:\n - ${origin}: ${message}`);
|
|
39714
39731
|
const checkShape = (value) => {
|
|
39715
39732
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("skills export must return an object of scope -> skill names");
|
|
39716
39733
|
for (const [scope, names] of Object.entries(value)) if (!Array.isArray(names) || names.some((n) => typeof n !== "string")) throw new Error(`skills export: "${scope}" must be an array of skill names`);
|
|
@@ -39919,7 +39936,11 @@ const planStart = (current, actor, start) => gen(function* () {
|
|
|
39919
39936
|
const overrideRefusal = yield* checkOverrides(commandLabel, varOverrides, Object.keys(workflow.vars), Object.keys(workflow.env));
|
|
39920
39937
|
if (overrideRefusal !== void 0) return overrideRefusal;
|
|
39921
39938
|
const baseOf = workflow.def.base;
|
|
39922
|
-
const
|
|
39939
|
+
const flags = yield* StartFlags;
|
|
39940
|
+
const hostEnv = (yield* Host).env;
|
|
39941
|
+
const badFlag = ownFlagRefusal(workflow.vars, flags, hostEnv);
|
|
39942
|
+
if (badFlag !== void 0) return refusal(`${commandLabel}: ${badFlag}`);
|
|
39943
|
+
const vars = resolveStartVars(workflow.vars, flags, varOverrides, hostEnv);
|
|
39923
39944
|
const multiline = multilineSetting(vars);
|
|
39924
39945
|
if (multiline !== void 0) return refusal(`${commandLabel}: process setting "${multiline}" spans several lines — a process setting is pinned in a commit trailer and must be a single line`);
|
|
39925
39946
|
const template = yield* try_({
|
|
@@ -40493,7 +40514,7 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
40493
40514
|
const step = outcome.rest;
|
|
40494
40515
|
yield* (yield* Narrator).narrate(`rest resolved: ${step.name} (awaits ${step.actor})`);
|
|
40495
40516
|
const stepDef = stepDefOf(step);
|
|
40496
|
-
if (stepDef.mode !== void 0 && def.modes[stepDef.mode] === void 0) return yield* fail$7(/* @__PURE__ */ new Error(`gtd config: step "${step.name}": mode "${stepDef.mode}" is not a mode this workflow knows (${knownModes(def).join(", ")})`));
|
|
40517
|
+
if (stepDef.mode !== void 0 && def.modes[stepDef.mode] === void 0) return yield* fail$7(/* @__PURE__ */ new Error(`gtd: config: step "${step.name}": mode "${stepDef.mode}" is not a mode this workflow knows (${knownModes(def).join(", ")})`));
|
|
40497
40518
|
const changes = ref === void 0 ? (yield* pendingChanges(git, stepDef.approve === true)).map((e) => ({
|
|
40498
40519
|
status: normalizeStatus(e.status),
|
|
40499
40520
|
path: e.path
|
|
@@ -40782,11 +40803,11 @@ const historySteps = (run) => {
|
|
|
40782
40803
|
};
|
|
40783
40804
|
});
|
|
40784
40805
|
};
|
|
40785
|
-
const versionWarningFor = (recorded) => recorded === GTD_VERSION ? void 0 : `gtd: warning: this history was recorded by ${recorded === void 0 ? "an unknown gtd version" : `gtd ${recorded}`} and is replayed by gtd ${GTD_VERSION} —
|
|
40806
|
+
const versionWarningFor = (recorded) => recorded === GTD_VERSION ? void 0 : `gtd: warning: this history was recorded by ${recorded === void 0 ? "an unknown gtd version" : `gtd ${recorded}`} and is replayed by gtd ${GTD_VERSION} — the engine the workflow runs on may have changed since`;
|
|
40786
40807
|
/**
|
|
40787
40808
|
* Replays the closed run at `tip` under today's config. The workflow is not
|
|
40788
|
-
* fingerprinted for a version change:
|
|
40789
|
-
*
|
|
40809
|
+
* fingerprinted for a version change: the engine under it moves with every
|
|
40810
|
+
* upgrade, and the version warning is the only signal.
|
|
40790
40811
|
*/
|
|
40791
40812
|
const historyTrail = (tip) => gen(function* () {
|
|
40792
40813
|
const git = yield* GitService;
|
|
@@ -41033,7 +41054,7 @@ const emitScripts = (required = [], optional = []) => ({
|
|
|
41033
41054
|
optional: assembleScript(optional)
|
|
41034
41055
|
});
|
|
41035
41056
|
/** Exported so `src/twins/EmittedScriptRecognizer.ts` can recognize these by exact string comparison. `printf`, not `echo`, avoids shell-specific backslash-escape quirks. */
|
|
41036
|
-
const DID_NOT_RUN_COMMENT = "# gtd emitted this and did NOT run it —
|
|
41057
|
+
const DID_NOT_RUN_COMMENT = "# gtd emitted this and did NOT run it — run it with `sh` to apply it";
|
|
41037
41058
|
const PRESENTATION_ONLY_COMMENT = "# presentation only — safe to skip";
|
|
41038
41059
|
const PRESENTATION_FAILURE_WARNING = "printf 'gtd: presentation-only follow-up failed — continuing\\n' >&2";
|
|
41039
41060
|
/**
|
|
@@ -42015,9 +42036,8 @@ const selfGtd = () => {
|
|
|
42015
42036
|
const runLspCommand = () => flatMap$4(ToolCommands, (tools) => tools.lsp(selfGtd()));
|
|
42016
42037
|
/**
|
|
42017
42038
|
* Prints the briefing, emitted by the binary itself so it's always current.
|
|
42018
|
-
*
|
|
42019
|
-
*
|
|
42020
|
-
* Anywhere else it writes nothing.
|
|
42039
|
+
* gtd ships no workflow, so where no `gtd.config.ts` governs it says where to
|
|
42040
|
+
* get one; it writes nothing.
|
|
42021
42041
|
*/
|
|
42022
42042
|
const runInstallCommand = (out) => gen(function* () {
|
|
42023
42043
|
const briefing = renderBriefing();
|
|
@@ -42027,8 +42047,7 @@ const runInstallCommand = (out) => gen(function* () {
|
|
|
42027
42047
|
if (topLevel._tag === "Left") return;
|
|
42028
42048
|
if ((yield* host.realPath(topLevel.right)) !== (yield* host.realPath(host.root))) return;
|
|
42029
42049
|
if ((yield* (yield* ConfigDiscovery).workflowModule(host.root, host.home)) !== void 0) return;
|
|
42030
|
-
yield* (yield*
|
|
42031
|
-
yield* (yield* Narrator).warn(`gtd install: wrote ${WORKFLOW_MODULE}, importing gtd's shipped workflows — commit it before your first \`gtd land\``);
|
|
42050
|
+
yield* (yield* Narrator).warn(`gtd install: no ${WORKFLOW_MODULE} governs here, and gtd ships no workflow — get one from ${WORKFLOWS_REPO} or write your own (step 2 of the briefing above)`);
|
|
42032
42051
|
});
|
|
42033
42052
|
/**
|
|
42034
42053
|
* Exactly one trailing newline — duplicated from `Cli.ts`'s own
|
|
@@ -42216,7 +42235,10 @@ const runLandCommand = (opts, json, out) => gen(function* () {
|
|
|
42216
42235
|
const runStartCommand = (actor, workflow, varOverrides, out, commandLabel) => gen(function* () {
|
|
42217
42236
|
const config = yield* (yield* ConfigService).load;
|
|
42218
42237
|
const resolved = config.workflowNamed(workflow);
|
|
42219
|
-
if (resolved === void 0)
|
|
42238
|
+
if (resolved === void 0) {
|
|
42239
|
+
const startable = config.workflowNames.length > 0 ? `startable: ${config.workflowNames.join(", ")}` : "this config exports no workflow besides its default, which an ordinary start runs";
|
|
42240
|
+
return yield* fail$7(new GtdUsageError(`gtd --workflow: unknown workflow "${workflow}" — ${startable}`));
|
|
42241
|
+
}
|
|
42220
42242
|
const rest = yield* restAt(void 0, varOverrides);
|
|
42221
42243
|
const started = yield* startRefusal(rest, resolved, varOverrides);
|
|
42222
42244
|
const plan = yield* planStart({
|
|
@@ -42230,9 +42252,24 @@ const runStartCommand = (actor, workflow, varOverrides, out, commandLabel) => ge
|
|
|
42230
42252
|
config: rest.config
|
|
42231
42253
|
});
|
|
42232
42254
|
if (plan.kind === "refusal") return yield* fail$7(new Error(plan.message));
|
|
42233
|
-
out.write(landingScript(ScriptSurface.render(plan.steps)));
|
|
42255
|
+
out.write(listingCommitted(landingScript(ScriptSurface.render(plan.steps)), rest.changes));
|
|
42234
42256
|
});
|
|
42257
|
+
/**
|
|
42258
|
+
* A start commits the whole tree (`git add -A`), so its script says up front
|
|
42259
|
+
* which paths that is. Comment lines only: control characters are masked so a
|
|
42260
|
+
* path can never end the comment and become a command.
|
|
42261
|
+
*/
|
|
42262
|
+
const listingCommitted = (script, changes) => {
|
|
42263
|
+
if (changes.length === 0) return script;
|
|
42264
|
+
const listing = ["# commits every change in the working tree (git add -A):", ...changes.map((change) => `# ${change.status} ${change.path.replace(/\p{Cc}/gu, "?")}`)].join("\n");
|
|
42265
|
+
const at = script.indexOf("\n\n");
|
|
42266
|
+
return at === -1 ? script : `${script.slice(0, at)}\n\n${listing}${script.slice(at)}`;
|
|
42267
|
+
};
|
|
42235
42268
|
const doorSynopsis = (name, door) => [name, ...(door.args ?? []).map((arg) => arg.optional === true ? `[${arg.name}]` : `<${arg.name}>`)].join(" ");
|
|
42269
|
+
const noSuchDoor = (name, doors) => {
|
|
42270
|
+
const listing = doors.size > 0 ? `doors: ${[...doors].map(([n, d]) => doorSynopsis(n, d)).join(", ")}` : "this config has no doors";
|
|
42271
|
+
return new GtdUsageError(name === void 0 ? `gtd door: missing door name — ${listing}` : `gtd door: unknown door "${name}" — ${listing}`);
|
|
42272
|
+
};
|
|
42236
42273
|
/**
|
|
42237
42274
|
* `gtd door <name> [args…]`: `gtd --workflow` with the door's workflow and the
|
|
42238
42275
|
* process settings its `vars` derive from the positional args. A wrong name or
|
|
@@ -42240,11 +42277,8 @@ const doorSynopsis = (name, door) => [name, ...(door.args ?? []).map((arg) => ar
|
|
|
42240
42277
|
*/
|
|
42241
42278
|
const runDoorCommand = (name, args, out) => gen(function* () {
|
|
42242
42279
|
const config = yield* (yield* ConfigService).load;
|
|
42243
|
-
const door = config.doors.get(name);
|
|
42244
|
-
if (door === void 0)
|
|
42245
|
-
const known = [...config.doors];
|
|
42246
|
-
return yield* fail$7(new GtdUsageError(`gtd door: unknown door "${name}" — doors: ${known.map(([n, d]) => doorSynopsis(n, d)).join(", ")}`));
|
|
42247
|
-
}
|
|
42280
|
+
const door = name === void 0 ? void 0 : config.doors.get(name);
|
|
42281
|
+
if (name === void 0 || door === void 0) return yield* fail$7(noSuchDoor(name, config.doors));
|
|
42248
42282
|
const declared = door.args ?? [];
|
|
42249
42283
|
const required = declared.filter((arg) => arg.optional !== true).length;
|
|
42250
42284
|
if (args.length < required || args.length > declared.length) return yield* fail$7(new GtdUsageError(`gtd door ${name}: ${args.length < required ? "missing" : "too many"} arguments — usage: gtd door ${doorSynopsis(name, door)}`));
|
|
@@ -42262,6 +42296,7 @@ const runDoorCommand = (name, args, out) => gen(function* () {
|
|
|
42262
42296
|
const runDoorsCommand = (json, out) => gen(function* () {
|
|
42263
42297
|
const doors = [...(yield* (yield* ConfigService).load).doors];
|
|
42264
42298
|
if (json.kind === "off") {
|
|
42299
|
+
if (doors.length === 0) yield* (yield* Narrator).warn("gtd doors: this config has no doors — a workflow factory's `doors` option or the config's `doors` export adds one");
|
|
42265
42300
|
out.write(doors.map(([n, d]) => `${doorSynopsis(n, d)} → ${d.workflow}\n`).join(""));
|
|
42266
42301
|
return;
|
|
42267
42302
|
}
|
|
@@ -42409,6 +42444,13 @@ const runNextCommand = (json, out) => gen(function* () {
|
|
|
42409
42444
|
else if (json.kind === "select") yield* writeSelection(out, fields, json.path);
|
|
42410
42445
|
else {
|
|
42411
42446
|
const validateCommand = emitsValidatablePrompt(rendered) ? selfValidateCommand(rest.def, rest.state, rendered.mode, rendered.file) : void 0;
|
|
42447
|
+
if (DEMAND_BRIEFING[fields.kind].suppressHeader) {
|
|
42448
|
+
const shown = renderBeatPlain({
|
|
42449
|
+
...fields,
|
|
42450
|
+
kind: "message"
|
|
42451
|
+
});
|
|
42452
|
+
yield* narrator.warn(shown.slice(0, shown.indexOf("\n\n")));
|
|
42453
|
+
}
|
|
42412
42454
|
out.write(renderBeatPlain(fields, validateCommand));
|
|
42413
42455
|
}
|
|
42414
42456
|
});
|
|
@@ -42564,12 +42606,13 @@ const runUiCliCommand = (command, out) => gen(function* () {
|
|
|
42564
42606
|
* cwds (e.g. macOS /tmp → /private/tmp) match.
|
|
42565
42607
|
*/
|
|
42566
42608
|
const assertRunningFromRepoRoot = (git, host) => gen(function* () {
|
|
42567
|
-
const topLevel = yield* git.topLevel();
|
|
42568
|
-
if ((yield* host.realPath(topLevel)) !== (yield* host.realPath(host.root))) return yield* fail$7(/* @__PURE__ */ new Error(`gtd
|
|
42609
|
+
const topLevel = yield* git.topLevel().pipe(mapError$1((e) => /not a git repository/.test(e.message) ? /* @__PURE__ */ new Error("gtd: not a git repository — run gtd inside one (`git init` makes one)") : e));
|
|
42610
|
+
if ((yield* host.realPath(topLevel)) !== (yield* host.realPath(host.root))) return yield* fail$7(/* @__PURE__ */ new Error(`gtd: run gtd from the repository root — cd ${shellWord(topLevel)} (the current directory is ${host.root})`));
|
|
42569
42611
|
});
|
|
42612
|
+
const shellWord = (path) => /^[\w@%+=:,./-]+$/.test(path) ? path : `'${path.replace(/'/g, "'\\''")}'`;
|
|
42570
42613
|
/** A command that derives workflow state needs at least one commit too — a commitless repository has no HEAD to resolve a `Rest` at. */
|
|
42571
42614
|
const assertRepositoryHasCommits = (git) => gen(function* () {
|
|
42572
|
-
if (!(yield* git.hasCommits())) return yield* fail$7(/* @__PURE__ */ new Error("gtd
|
|
42615
|
+
if (!(yield* git.hasCommits())) return yield* fail$7(/* @__PURE__ */ new Error("gtd: the repository has no commits yet — make an initial commit, then run gtd again"));
|
|
42573
42616
|
});
|
|
42574
42617
|
/**
|
|
42575
42618
|
* What a command kind needs before it may run. Defined here, not in
|
|
@@ -42656,6 +42699,57 @@ const runCommand = (command, json, out) => {
|
|
|
42656
42699
|
});
|
|
42657
42700
|
};
|
|
42658
42701
|
//#endregion
|
|
42702
|
+
//#region src/cli/Help.ts
|
|
42703
|
+
const HELP_WIDTH = 80;
|
|
42704
|
+
/**
|
|
42705
|
+
* Word-wraps prose to `width` columns, every line prefixed by `indent`.
|
|
42706
|
+
* Paragraphs are separated by a blank line; a paragraph whose lines start
|
|
42707
|
+
* with two spaces is an example and is kept verbatim (indented, not wrapped).
|
|
42708
|
+
*/
|
|
42709
|
+
const wrapParagraph = (paragraph, width, indent) => {
|
|
42710
|
+
const lines = [];
|
|
42711
|
+
let line = "";
|
|
42712
|
+
for (const word of paragraph.match(/\S*`[^`]*`\S*|\S+/g) ?? []) if (line !== "" && indent.length + line.length + 1 + word.length > width) {
|
|
42713
|
+
lines.push(indent + line);
|
|
42714
|
+
line = word;
|
|
42715
|
+
} else line = line === "" ? word : `${line} ${word}`;
|
|
42716
|
+
if (line !== "") lines.push(indent + line);
|
|
42717
|
+
return lines;
|
|
42718
|
+
};
|
|
42719
|
+
const wrap = (text, width = HELP_WIDTH, indent = "") => text.split("\n\n").flatMap((paragraph, i) => [...i > 0 ? [""] : [], ...paragraph.startsWith(" ") ? paragraph.split("\n").map((l) => indent + l) : wrapParagraph(paragraph, width, indent)]);
|
|
42720
|
+
/** `term` and `description` side by side, the description wrapped into the column after `column`; a term too wide for it gets a line of its own. */
|
|
42721
|
+
const definition = (term, description, column) => {
|
|
42722
|
+
const cell = ` ${term}`;
|
|
42723
|
+
const body = wrap(description, HELP_WIDTH, " ".repeat(column));
|
|
42724
|
+
if (cell.length + 1 >= column) return [cell, ...body];
|
|
42725
|
+
const [first = "", ...rest] = body;
|
|
42726
|
+
return [cell.padEnd(column) + first.trimStart(), ...rest];
|
|
42727
|
+
};
|
|
42728
|
+
/** Optimal-string-alignment distance: Levenshtein plus adjacent transpositions, so `nxet` is one edit from `next`. */
|
|
42729
|
+
const distance = (a, b) => {
|
|
42730
|
+
const d = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
42731
|
+
for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) {
|
|
42732
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
42733
|
+
let best = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
42734
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) best = Math.min(best, d[i - 2][j - 2] + 1);
|
|
42735
|
+
d[i][j] = best;
|
|
42736
|
+
}
|
|
42737
|
+
return d[a.length][b.length];
|
|
42738
|
+
};
|
|
42739
|
+
/** The candidate closest to `word`, when it is a plausible typo of it: at most one edit per three characters, and at least one. */
|
|
42740
|
+
const closest = (word, candidates) => {
|
|
42741
|
+
const limit = Math.max(1, Math.floor(word.length / 3));
|
|
42742
|
+
let best;
|
|
42743
|
+
for (const name of candidates) {
|
|
42744
|
+
const d = distance(word, name);
|
|
42745
|
+
if (d <= limit && (best === void 0 || d < best.d)) best = {
|
|
42746
|
+
name,
|
|
42747
|
+
d
|
|
42748
|
+
};
|
|
42749
|
+
}
|
|
42750
|
+
return best?.name;
|
|
42751
|
+
};
|
|
42752
|
+
//#endregion
|
|
42659
42753
|
//#region src/cli/Cli.ts
|
|
42660
42754
|
init_esm();
|
|
42661
42755
|
const nonNegativeNumber = (raw, flag) => {
|
|
@@ -42684,20 +42778,8 @@ const FLAGS = [
|
|
|
42684
42778
|
decode: ([raw]) => right(raw ?? ""),
|
|
42685
42779
|
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd history list`/`gtd history show`/`gtd history metrics`/`gtd history judges`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
42686
42780
|
valueHint: "<path>",
|
|
42687
|
-
help:
|
|
42688
|
-
|
|
42689
|
-
"list/gtd history show/gtd history metrics/gtd history judges/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
42690
|
-
"--json=<path> (a dotted key path into that document, e.g.",
|
|
42691
|
-
"kind, content, session.id) prints just that value: a",
|
|
42692
|
-
"scalar raw and unquoted, a boolean as true/false, a list",
|
|
42693
|
-
"one JSON entry per line — a numeric segment (e.g.",
|
|
42694
|
-
"skills.0) indexes into one entry of a list field instead.",
|
|
42695
|
-
"An absent optional field prints nothing and exits 0 —",
|
|
42696
|
-
"including when an earlier segment of <path> is itself",
|
|
42697
|
-
"absent/null (e.g. session.id at a non-prompt rest), which",
|
|
42698
|
-
"never counts as unknown; an unknown path is a usage error",
|
|
42699
|
-
"(exit 2)."
|
|
42700
|
-
]
|
|
42781
|
+
help: "Print the JSON document, or with --json=<path> one field of it (e.g. --json=kind): a scalar bare, a list one entry per line, nothing for an absent field",
|
|
42782
|
+
helpFor: { strays: "Print one entry of the array (--json=0), nothing past its end" }
|
|
42701
42783
|
},
|
|
42702
42784
|
{
|
|
42703
42785
|
name: "--port",
|
|
@@ -42710,11 +42792,7 @@ const FLAGS = [
|
|
|
42710
42792
|
},
|
|
42711
42793
|
scopeError: "gtd: --port is only valid for `gtd ui`",
|
|
42712
42794
|
valueHint: "<n>",
|
|
42713
|
-
help:
|
|
42714
|
-
"(gtd ui only) the tailscale serve port (default: the first",
|
|
42715
|
-
"free of 8443, 10000, 443), or the bind port when --host",
|
|
42716
|
-
"opts out of serve (default: a free port)"
|
|
42717
|
-
]
|
|
42795
|
+
help: "The tailscale serve port (default: the first free of 8443, 10000, 443), or with --host the port to bind (default: any free one)"
|
|
42718
42796
|
},
|
|
42719
42797
|
{
|
|
42720
42798
|
name: "--host",
|
|
@@ -42724,11 +42802,7 @@ const FLAGS = [
|
|
|
42724
42802
|
decode: ([raw]) => raw === void 0 || raw.trim() === "" || /[\r\n]/.test(raw) ? left("gtd: --host must be a non-empty, single-line value") : right(raw),
|
|
42725
42803
|
scopeError: "gtd: --host is only valid for `gtd ui`",
|
|
42726
42804
|
valueHint: "<addr>",
|
|
42727
|
-
help:
|
|
42728
|
-
"(gtd ui only) opt out of the default tailscale serve front",
|
|
42729
|
-
"door and bind this address directly instead, showing it in",
|
|
42730
|
-
"the printed URL"
|
|
42731
|
-
]
|
|
42805
|
+
help: "Bind this address directly instead of publishing through tailscale serve"
|
|
42732
42806
|
},
|
|
42733
42807
|
{
|
|
42734
42808
|
name: "--self-signed",
|
|
@@ -42738,7 +42812,7 @@ const FLAGS = [
|
|
|
42738
42812
|
decode: () => right(true),
|
|
42739
42813
|
scopeError: "gtd: --self-signed is only valid for `gtd ui`",
|
|
42740
42814
|
valueHint: "",
|
|
42741
|
-
help:
|
|
42815
|
+
help: "Bind directly with a throwaway self-signed certificate instead of the configured ui.cert/ui.key"
|
|
42742
42816
|
},
|
|
42743
42817
|
{
|
|
42744
42818
|
name: "--dev",
|
|
@@ -42748,7 +42822,7 @@ const FLAGS = [
|
|
|
42748
42822
|
decode: () => right(true),
|
|
42749
42823
|
scopeError: "gtd: --dev is only valid for `gtd ui`",
|
|
42750
42824
|
valueHint: "",
|
|
42751
|
-
help:
|
|
42825
|
+
help: "Serve local development sources instead of the packaged build"
|
|
42752
42826
|
},
|
|
42753
42827
|
{
|
|
42754
42828
|
name: "--provider",
|
|
@@ -42758,11 +42832,7 @@ const FLAGS = [
|
|
|
42758
42832
|
decode: ([raw]) => raw === "fixed" || raw === "jev" || raw === "llm" ? right(raw) : left(`gtd: --provider must be one of fixed, jev, llm — got "${raw ?? ""}"`),
|
|
42759
42833
|
scopeError: "gtd: --provider is only valid for `gtd judge run`",
|
|
42760
42834
|
valueHint: "<name>",
|
|
42761
|
-
help:
|
|
42762
|
-
"(gtd judge run only) the answerer: fixed | jev | llm;",
|
|
42763
|
-
"omitted: GTD_JUDGE_PROVIDER, then judge.json, then jev",
|
|
42764
|
-
"when TYPESAFE_API_KEY is set, else llm"
|
|
42765
|
-
]
|
|
42835
|
+
help: "The answerer: fixed, jev or llm (default: $GTD_JUDGE_PROVIDER, then ~/.config/gtd/judge.json, then jev when $TYPESAFE_API_KEY is set, else llm)"
|
|
42766
42836
|
},
|
|
42767
42837
|
{
|
|
42768
42838
|
name: "--answers",
|
|
@@ -42772,11 +42842,7 @@ const FLAGS = [
|
|
|
42772
42842
|
decode: ([raw]) => raw === void 0 || raw === "" ? left("gtd: --answers must be a non-empty path") : right(raw),
|
|
42773
42843
|
scopeError: "gtd: --answers is only valid for `gtd judge run`",
|
|
42774
42844
|
valueHint: "<path>",
|
|
42775
|
-
help: [
|
|
42776
|
-
"(gtd judge run --provider fixed only) a JSON verdict array",
|
|
42777
|
-
"[{ id, answer, p }]; else the GTD_JUDGE_ANSWERS env var",
|
|
42778
|
-
"(inline JSON) is read"
|
|
42779
|
-
]
|
|
42845
|
+
help: "With --provider fixed: a JSON verdict file [{ id, answer, p }] (default: $GTD_JUDGE_ANSWERS, inline JSON)"
|
|
42780
42846
|
},
|
|
42781
42847
|
{
|
|
42782
42848
|
name: "--cost",
|
|
@@ -42786,7 +42852,7 @@ const FLAGS = [
|
|
|
42786
42852
|
decode: ([raw]) => nonNegativeNumber(raw ?? "", "--cost"),
|
|
42787
42853
|
scopeError: "gtd: --cost is only valid for `gtd land` — a start is not a metered agent turn",
|
|
42788
42854
|
valueHint: "<n>",
|
|
42789
|
-
help:
|
|
42855
|
+
help: "Record the token cost of the invocation that just finished on the commit"
|
|
42790
42856
|
},
|
|
42791
42857
|
{
|
|
42792
42858
|
name: "--model",
|
|
@@ -42796,11 +42862,11 @@ const FLAGS = [
|
|
|
42796
42862
|
decode: ([raw]) => raw === void 0 || raw.trim() === "" || /[\r\n]/.test(raw) ? left("gtd: --model must be a non-empty, single-line value") : right(raw),
|
|
42797
42863
|
scopeError: "gtd: --model is only valid for `gtd land` (with --cost) or `gtd judge run`",
|
|
42798
42864
|
valueHint: "<name>",
|
|
42799
|
-
help:
|
|
42800
|
-
|
|
42801
|
-
"
|
|
42802
|
-
"GTD_JUDGE_MODEL, then judge.json, then sonnet"
|
|
42803
|
-
|
|
42865
|
+
help: "",
|
|
42866
|
+
helpFor: {
|
|
42867
|
+
land: "With --cost: the model that cost was spent on",
|
|
42868
|
+
judgeRun: "The claude model the llm answerer uses (default: $GTD_JUDGE_MODEL, then judge.json, then sonnet)"
|
|
42869
|
+
}
|
|
42804
42870
|
},
|
|
42805
42871
|
{
|
|
42806
42872
|
name: "--workflow",
|
|
@@ -42810,7 +42876,7 @@ const FLAGS = [
|
|
|
42810
42876
|
decode: ([raw]) => right(raw ?? ""),
|
|
42811
42877
|
scopeError: "gtd: --workflow is only valid with no other command — use the bare `gtd --workflow <name>` form; landing and starting are different verbs",
|
|
42812
42878
|
valueHint: "<name>",
|
|
42813
|
-
help:
|
|
42879
|
+
help: "Start a new process on the named workflow"
|
|
42814
42880
|
},
|
|
42815
42881
|
{
|
|
42816
42882
|
name: "--var",
|
|
@@ -42841,14 +42907,11 @@ const FLAGS = [
|
|
|
42841
42907
|
},
|
|
42842
42908
|
scopeError: "gtd: --var requires --workflow (or `gtd history judges`)",
|
|
42843
42909
|
valueHint: "<name>=<value>",
|
|
42844
|
-
help:
|
|
42845
|
-
|
|
42846
|
-
"
|
|
42847
|
-
"
|
|
42848
|
-
|
|
42849
|
-
"under this threshold: judgeIdenticalMinP or",
|
|
42850
|
-
"qualityTriageMinP"
|
|
42851
|
-
]
|
|
42910
|
+
help: "",
|
|
42911
|
+
helpFor: {
|
|
42912
|
+
start: "Pin one of the workflow's declared settings for the new process (repeatable)",
|
|
42913
|
+
historyJudges: "Re-decide every past verdict under this threshold: judgeIdenticalMinP or qualityTriageMinP (repeatable)"
|
|
42914
|
+
}
|
|
42852
42915
|
},
|
|
42853
42916
|
{
|
|
42854
42917
|
name: "--jsonl",
|
|
@@ -42858,7 +42921,7 @@ const FLAGS = [
|
|
|
42858
42921
|
decode: () => right(true),
|
|
42859
42922
|
scopeError: "gtd: --jsonl is only valid for `gtd history judges`",
|
|
42860
42923
|
valueHint: "",
|
|
42861
|
-
help:
|
|
42924
|
+
help: "Print one JSON line per verdict instead of the report",
|
|
42862
42925
|
conflicts: ["--json", "--var"]
|
|
42863
42926
|
},
|
|
42864
42927
|
{
|
|
@@ -42869,12 +42932,7 @@ const FLAGS = [
|
|
|
42869
42932
|
decode: () => right(true),
|
|
42870
42933
|
scopeError: "gtd: --open-questions is only valid for `gtd check`",
|
|
42871
42934
|
valueHint: "",
|
|
42872
|
-
help:
|
|
42873
|
-
"(gtd check only) ignore <mode>'s structural findings and",
|
|
42874
|
-
"instead run the qa open-questions predicate over <file>,",
|
|
42875
|
-
"printing each unanswered question one per line and exiting",
|
|
42876
|
-
"non-zero when any remain"
|
|
42877
|
-
]
|
|
42935
|
+
help: "Instead of <mode>'s findings, list the unanswered questions in <file>; exits 1 when any remain"
|
|
42878
42936
|
},
|
|
42879
42937
|
{
|
|
42880
42938
|
name: "--open-threads",
|
|
@@ -42884,15 +42942,7 @@ const FLAGS = [
|
|
|
42884
42942
|
decode: () => right(true),
|
|
42885
42943
|
scopeError: "gtd: --open-threads is only valid for `gtd check`",
|
|
42886
42944
|
valueHint: "",
|
|
42887
|
-
help:
|
|
42888
|
-
"(gtd check only) ignore <mode>'s structural findings and",
|
|
42889
|
-
"instead list every open thread (a footnote whose last",
|
|
42890
|
-
"entry is the agent's) over <file>, one per line with its",
|
|
42891
|
-
"first H entry, exiting non-zero when any are open. With no",
|
|
42892
|
-
"<mode>/<file>, lists code-comment threads in the files the",
|
|
42893
|
-
"current process changed (read from its diff base, unlike",
|
|
42894
|
-
"the rest of `gtd check`)"
|
|
42895
|
-
]
|
|
42945
|
+
help: "Instead of <mode>'s findings, list the open threads in <file>; exits 1 when any are open. Alone, lists the open code-comment threads in the files the current process changed"
|
|
42896
42946
|
},
|
|
42897
42947
|
{
|
|
42898
42948
|
name: "--verbose",
|
|
@@ -42902,11 +42952,7 @@ const FLAGS = [
|
|
|
42902
42952
|
decode: () => right(true),
|
|
42903
42953
|
scopeError: "gtd: --verbose is valid for every command — this error is unreachable",
|
|
42904
42954
|
valueHint: "",
|
|
42905
|
-
help:
|
|
42906
|
-
"enable stderr narration for this invocation: which rest",
|
|
42907
|
-
"resolved and how config resolved across layers. Aliased",
|
|
42908
|
-
"to -v"
|
|
42909
|
-
]
|
|
42955
|
+
help: "Narrate on stderr what gtd resolved and where its config came from"
|
|
42910
42956
|
}
|
|
42911
42957
|
];
|
|
42912
42958
|
/**
|
|
@@ -42925,205 +42971,39 @@ const flagByToken = (token) => {
|
|
|
42925
42971
|
return FLAGS.find((f) => f.name === bareName);
|
|
42926
42972
|
};
|
|
42927
42973
|
const COMMAND_ROWS = [
|
|
42928
|
-
{
|
|
42929
|
-
token: "land",
|
|
42930
|
-
kind: "land",
|
|
42931
|
-
arity: "none",
|
|
42932
|
-
details: [
|
|
42933
|
-
"Land whatever the tree now shows at the currently resolved",
|
|
42934
|
-
"rest — a human capture, an agent/check turn, or an empty",
|
|
42935
|
-
"attempt (a fruitless prompt turn). Pass",
|
|
42936
|
-
"--cost=<n> (optionally --model=<name>) to record the",
|
|
42937
|
-
"just-finished invocation's token cost and model on the",
|
|
42938
|
-
"turn commit (summed into it.processCost/",
|
|
42939
|
-
"processCostByModel). Plain (the default) prints one",
|
|
42940
|
-
"human-readable sentence naming the commit subject (or a",
|
|
42941
|
-
"no-op note) plus a pointer to `gtd land --json=script | sh`",
|
|
42942
|
-
"to get the landing script — never the script itself.",
|
|
42943
|
-
"--json emits script (the actual POSIX sh",
|
|
42944
|
-
"a driver runs) alongside settled, idle, state (the",
|
|
42945
|
-
"post-land target), subject, cost and model. Exits 0 on",
|
|
42946
|
-
"success, 1 on any refusal — see the Exit codes section",
|
|
42947
|
-
"below"
|
|
42948
|
-
]
|
|
42949
|
-
},
|
|
42950
|
-
{
|
|
42951
|
-
token: "abandon",
|
|
42952
|
-
kind: "abandon",
|
|
42953
|
-
arity: "none",
|
|
42954
|
-
details: [
|
|
42955
|
-
"End the process currently underway without completing it:",
|
|
42956
|
-
"rewind HEAD to the commit the process started from,",
|
|
42957
|
-
"keeping everything it produced as uncommitted changes. A",
|
|
42958
|
-
"no-op when no process is underway"
|
|
42959
|
-
]
|
|
42960
|
-
},
|
|
42961
|
-
{
|
|
42962
|
-
token: "restore",
|
|
42963
|
-
kind: "restore",
|
|
42964
|
-
arity: "none",
|
|
42965
|
-
details: [
|
|
42966
|
-
"Hard-reset HEAD back to the tip retained by the last",
|
|
42967
|
-
"abandon (refs/worktree/gtd/history), bringing back an",
|
|
42968
|
-
"abandoned process's turns. Refuses on a dirty working",
|
|
42969
|
-
"tree, when there is no retained history, or when HEAD has",
|
|
42970
|
-
"advanced past the retained tip with commits that would be",
|
|
42971
|
-
"lost"
|
|
42972
|
-
]
|
|
42973
|
-
},
|
|
42974
42974
|
{
|
|
42975
42975
|
token: "next",
|
|
42976
42976
|
kind: "next",
|
|
42977
42977
|
arity: "none",
|
|
42978
|
-
|
|
42979
|
-
|
|
42980
|
-
|
|
42981
|
-
|
|
42982
|
-
|
|
42983
|
-
|
|
42984
|
-
|
|
42985
|
-
"it's the bare step (plus the self-validation instruction",
|
|
42986
|
-
"when applicable) with no header at all, since those bytes",
|
|
42987
|
-
"are the agent's own input. --json emits the one structured",
|
|
42988
|
-
"surface gtd has: kind (capture|message|script|prompt|",
|
|
42989
|
-
"stalled) selects what a driver does, content is what it",
|
|
42990
|
-
"runs or shows, idle marks the workflow's initial state with",
|
|
42991
|
-
"a clean tree, plus the prompt session, model, validate",
|
|
42992
|
-
"script, log path, changes, next and the resting state's own",
|
|
42993
|
-
"fields. --json=<path> (a dotted key path into that same",
|
|
42994
|
-
"document, e.g. kind, content, session.id) prints just that",
|
|
42995
|
-
"value instead of the whole document — see --json's own help",
|
|
42996
|
-
"above. Exits 0 — see the Exit codes section below"
|
|
42997
|
-
]
|
|
42998
|
-
},
|
|
42999
|
-
{
|
|
43000
|
-
token: "validate",
|
|
43001
|
-
kind: "validate",
|
|
43002
|
-
arity: "none",
|
|
43003
|
-
details: [
|
|
43004
|
-
"Print the script that formats (when declared) then",
|
|
43005
|
-
"validates the resolved rest's steering file, using its",
|
|
43006
|
-
"mode's commands (its file:/mode:), instead of running it —",
|
|
43007
|
-
"a driver runs the script and reads the findings from its",
|
|
43008
|
-
"own exit code/output. Always exits 0; prints \"nothing to",
|
|
43009
|
-
"validate\" when the resolved rest declares nothing to run.",
|
|
43010
|
-
"On a non-zero validate exit the emitted script prints a",
|
|
43011
|
-
"ready-to-send fix prompt (instruction + findings) and",
|
|
43012
|
-
"exits with the validator's own code"
|
|
43013
|
-
]
|
|
43014
|
-
},
|
|
43015
|
-
{
|
|
43016
|
-
token: "lsp",
|
|
43017
|
-
kind: "lsp",
|
|
43018
|
-
arity: "none",
|
|
43019
|
-
details: ["Start the LSP server for .gtd/ steering files (stdio)"]
|
|
43020
|
-
},
|
|
43021
|
-
{
|
|
43022
|
-
token: "ui",
|
|
43023
|
-
kind: "ui",
|
|
43024
|
-
arity: "none",
|
|
43025
|
-
details: [
|
|
43026
|
-
"Expose gtd's web/phone client for THIS worktree — the",
|
|
43027
|
-
"invoking directory, never a configured list of roots — and",
|
|
43028
|
-
"refuses outside a repository like every other state command.",
|
|
43029
|
-
"By default, publishes through `tailscale serve` (reachable",
|
|
43030
|
-
"from anywhere on the tailnet, including over a DERP relay)",
|
|
43031
|
-
"with tailscaled terminating TLS, walking 8443, then 10000,",
|
|
43032
|
-
"then 443 until one publishes; falls back to binding a local",
|
|
43033
|
-
"HTTPS server directly, never refusing, when none of the",
|
|
43034
|
-
"three works. --host <addr> opts out of serve and binds that",
|
|
43035
|
-
"address directly instead; --port <n> overrides the serve",
|
|
43036
|
-
"port (default: the first free of 8443, 10000, 443), or the",
|
|
43037
|
-
"bind port when --host is given (default: a free port);",
|
|
43038
|
-
"--self-signed also opts out of serve, generating a",
|
|
43039
|
-
"throwaway TLS certificate instead of the configured",
|
|
43040
|
-
"ui.cert/ui.key; --dev runs against local development sources",
|
|
43041
|
-
"instead of the packaged build"
|
|
43042
|
-
]
|
|
43043
|
-
},
|
|
43044
|
-
{
|
|
43045
|
-
token: "check",
|
|
43046
|
-
kind: "check",
|
|
43047
|
-
arity: { names: ["mode", "file"] },
|
|
43048
|
-
details: [
|
|
43049
|
-
"Read <file> and run the built-in steering format named",
|
|
43050
|
-
"<mode> (see `gtd validate`'s modes: qa, review, findings)",
|
|
43051
|
-
"over its contents, printing each finding one per line and",
|
|
43052
|
-
"exiting non-zero when there are any. Resolves no workflow",
|
|
43053
|
-
"state and reads no config — standalone, runnable from any",
|
|
43054
|
-
"directory with <mode>/<file> given explicitly. This is what a",
|
|
43055
|
-
"workflow's emitted validation script invokes as a leaf step.",
|
|
43056
|
-
"--open-questions runs the qa unanswered-questions predicate",
|
|
43057
|
-
"instead (see --help); --open-threads lists open threads.",
|
|
43058
|
-
"`gtd check --open-threads` alone lists open code-comment",
|
|
43059
|
-
"threads (`// H: ...`) in the files the process changed;",
|
|
43060
|
-
"it prints nothing when no process is underway."
|
|
43061
|
-
]
|
|
43062
|
-
},
|
|
43063
|
-
{
|
|
43064
|
-
token: "uncheck",
|
|
43065
|
-
kind: "uncheck",
|
|
43066
|
-
arity: { name: "file" },
|
|
43067
|
-
details: [
|
|
43068
|
-
"Read <file> and reset every review-mode `- [x]`/`- [X]`",
|
|
43069
|
-
"file-pointer box back to `- [ ]`, writing the result back",
|
|
43070
|
-
"only when the bytes actually changed. Resolves no workflow",
|
|
43071
|
-
"state and reads no config — standalone, runnable from any",
|
|
43072
|
-
"directory with <file> given explicitly. This is what the",
|
|
43073
|
-
"landing script runs ahead of the commit at the human review",
|
|
43074
|
-
"gate, so no tick ever reaches a commit. Takes no <mode> —",
|
|
43075
|
-
"it means review-mode file pointers and nothing else, never",
|
|
43076
|
-
"qa-mode's answered-question boxes. A missing file writes",
|
|
43077
|
-
"nothing and exits 0"
|
|
43078
|
-
]
|
|
43079
|
-
},
|
|
43080
|
-
{
|
|
43081
|
-
token: "install",
|
|
43082
|
-
kind: "install",
|
|
43083
|
-
arity: "none",
|
|
43084
|
-
details: [
|
|
43085
|
-
"Print a complete, self-contained briefing that teaches an",
|
|
43086
|
-
"agent (or a human) to build a gtd driver in any shell or",
|
|
43087
|
-
"runtime — the self-serve version of",
|
|
43088
|
-
"https://github.com/pmelab/gtd/blob/main/docs/driver.md's",
|
|
43089
|
-
"'Writing your own driver'. At a repository root no",
|
|
43090
|
-
"gtd.config.ts governs yet, it also writes one importing each",
|
|
43091
|
-
"shipped workflow from @pmelab/gtd/workflows/<name>; anywhere",
|
|
43092
|
-
"else it writes nothing."
|
|
43093
|
-
]
|
|
42978
|
+
group: "Core loop",
|
|
42979
|
+
summary: "Print what should happen next",
|
|
42980
|
+
details: `Print what should happen next in the process, changing nothing — safe to poll.
|
|
42981
|
+
|
|
42982
|
+
Plain output is for a person: a status header (the step, who acts, the pending changes, where landing them would go), then the step's content. At a prompt the header goes to stderr, so stdout is exactly the agent's input.
|
|
42983
|
+
|
|
42984
|
+
--json prints one JSON document for a driver: its kind (capture, message, script, prompt or stalled) says what to do, its content what to run or show. --json=<path> prints one field of it; an unknown path is a usage error (exit 2).`
|
|
43094
42985
|
},
|
|
43095
42986
|
{
|
|
43096
|
-
token: "
|
|
43097
|
-
kind: "
|
|
42987
|
+
token: "land",
|
|
42988
|
+
kind: "land",
|
|
43098
42989
|
arity: "none",
|
|
43099
|
-
|
|
43100
|
-
|
|
43101
|
-
|
|
43102
|
-
|
|
43103
|
-
|
|
43104
|
-
|
|
43105
|
-
|
|
43106
|
-
|
|
43107
|
-
|
|
43108
|
-
"Refuses (exit 1) when the workflow declares no summary:",
|
|
43109
|
-
"template, or when the resolved run has no commits to name —",
|
|
43110
|
-
"it is runnable any time before the next thing lands on the",
|
|
43111
|
-
"branch"
|
|
43112
|
-
]
|
|
42990
|
+
group: "Core loop",
|
|
42991
|
+
summary: "Record what the tree now shows",
|
|
42992
|
+
details: `Record what the working tree now shows at the current step: a person's edit, an agent's or a check's turn, or an empty attempt when an agent changed nothing.
|
|
42993
|
+
|
|
42994
|
+
gtd writes nothing itself. Plain output names the commit it would make; --json=script prints the script that makes it:
|
|
42995
|
+
|
|
42996
|
+
s=$(gtd land --json=script) && sh -c "$s"
|
|
42997
|
+
|
|
42998
|
+
--json prints the whole document: the script, the step the process lands at, the commit subject, and the recorded cost and model. A refusal exits 1.`
|
|
43113
42999
|
},
|
|
43114
43000
|
{
|
|
43115
|
-
token: "
|
|
43116
|
-
kind: "
|
|
43001
|
+
token: "validate",
|
|
43002
|
+
kind: "validate",
|
|
43117
43003
|
arity: "none",
|
|
43118
|
-
|
|
43119
|
-
|
|
43120
|
-
|
|
43121
|
-
"bare, newline-terminated, and nothing else. Writes nothing:",
|
|
43122
|
-
"no git, no state transition, no session identity. Before",
|
|
43123
|
-
"the first review round it's the process's diff base;",
|
|
43124
|
-
"afterward it's the most-recent review round's boundary.",
|
|
43125
|
-
"Refuses (exit 1) when no process is underway."
|
|
43126
|
-
]
|
|
43004
|
+
group: "Core loop",
|
|
43005
|
+
summary: "Print the script that checks the steering file",
|
|
43006
|
+
details: `Print the script that formats, then validates, the current step's steering file with its mode's commands. gtd runs nothing: the script's exit code is the verdict, and on a failure it prints a ready-to-send fix prompt. Prints "nothing to validate" when the step declares no file to check.`
|
|
43127
43007
|
},
|
|
43128
43008
|
{
|
|
43129
43009
|
token: "door",
|
|
@@ -43132,73 +43012,55 @@ const COMMAND_ROWS = [
|
|
|
43132
43012
|
name: "name",
|
|
43133
43013
|
variadic: "args"
|
|
43134
43014
|
},
|
|
43135
|
-
|
|
43136
|
-
|
|
43137
|
-
|
|
43138
|
-
|
|
43139
|
-
|
|
43140
|
-
"`fix`, `review [base]`, `rollout <flag>` and",
|
|
43141
|
-
"`cleanup <flag>`), plus the config's `doors` export. An",
|
|
43142
|
-
"unknown door, or too few or too many args, is a usage error",
|
|
43143
|
-
"(exit 2) naming the doors or the door's synopsis"
|
|
43144
|
-
]
|
|
43015
|
+
group: "Start and stop",
|
|
43016
|
+
summary: "Start a process through a named shortcut",
|
|
43017
|
+
details: `Start a process through a door: a named shortcut to a workflow that maps its positional args to the workflow's settings. It prints the same start script \`gtd --workflow\` does. Each workflow the config exports brings its own doors, plus the config's \`doors\` export.
|
|
43018
|
+
|
|
43019
|
+
With no <name>, an unknown one, or the wrong number of args, it lists the doors and exits 2.`
|
|
43145
43020
|
},
|
|
43146
43021
|
{
|
|
43147
43022
|
token: "doors",
|
|
43148
43023
|
kind: "doors",
|
|
43149
43024
|
arity: "none",
|
|
43150
|
-
|
|
43151
|
-
|
|
43152
|
-
|
|
43153
|
-
"args: [{ name, optional }] } instead; --json=<path> reads one",
|
|
43154
|
-
"value off it. Writes nothing"
|
|
43155
|
-
]
|
|
43025
|
+
group: "Start and stop",
|
|
43026
|
+
summary: "List the doors",
|
|
43027
|
+
details: `List every door, sorted: one \`name [args] → workflow\` line each. --json prints a JSON array of { name, workflow, args: [{ name, optional }] }. Writes nothing.`
|
|
43156
43028
|
},
|
|
43157
43029
|
{
|
|
43158
|
-
token: "
|
|
43159
|
-
kind: "
|
|
43030
|
+
token: "abandon",
|
|
43031
|
+
kind: "abandon",
|
|
43160
43032
|
arity: "none",
|
|
43161
|
-
|
|
43162
|
-
|
|
43163
|
-
|
|
43164
|
-
|
|
43165
|
-
|
|
43166
|
-
"the resolved rest declares no judge:"
|
|
43167
|
-
]
|
|
43033
|
+
group: "Start and stop",
|
|
43034
|
+
summary: "Stop the process, keeping its changes",
|
|
43035
|
+
details: `Stop the process underway without finishing it. The printed script moves HEAD back to the commit the process started from and keeps everything it produced as uncommitted changes; \`gtd restore\` undoes it. With no process underway the script does nothing.
|
|
43036
|
+
|
|
43037
|
+
s=$(gtd abandon) && sh -c "$s"`
|
|
43168
43038
|
},
|
|
43169
43039
|
{
|
|
43170
|
-
token: "
|
|
43171
|
-
kind: "
|
|
43040
|
+
token: "restore",
|
|
43041
|
+
kind: "restore",
|
|
43172
43042
|
arity: "none",
|
|
43173
|
-
|
|
43174
|
-
|
|
43175
|
-
|
|
43176
|
-
"against an Effect Schema built from those same question",
|
|
43177
|
-
"ids. Refuses (exit 1) when the resolved rest declares no",
|
|
43178
|
-
"judge:. Exits 2 (a usage error, like an unknown --json",
|
|
43179
|
-
"selector) when stdin isn't valid JSON or the verdict",
|
|
43180
|
-
"doesn't decode against the pending question ids."
|
|
43181
|
-
]
|
|
43043
|
+
group: "Start and stop",
|
|
43044
|
+
summary: "Bring back the last abandoned process",
|
|
43045
|
+
details: `Bring back the process the last \`gtd abandon\` stopped: the printed script resets HEAD to that process's last commit. Refuses on a dirty working tree, when nothing was abandoned, or when commits made since would be lost.`
|
|
43182
43046
|
},
|
|
43183
43047
|
{
|
|
43184
|
-
token: "
|
|
43185
|
-
kind: "
|
|
43048
|
+
token: "summary",
|
|
43049
|
+
kind: "summary",
|
|
43186
43050
|
arity: "none",
|
|
43187
|
-
|
|
43188
|
-
|
|
43189
|
-
|
|
43190
|
-
|
|
43191
|
-
|
|
43192
|
-
|
|
43193
|
-
|
|
43194
|
-
|
|
43195
|
-
|
|
43196
|
-
|
|
43197
|
-
|
|
43198
|
-
|
|
43199
|
-
|
|
43200
|
-
"every question, with nothing on stdout"
|
|
43201
|
-
]
|
|
43051
|
+
group: "Inspect",
|
|
43052
|
+
summary: "Print the prompt for the closing message",
|
|
43053
|
+
details: `Print the prompt for an agent to write the closing message of the process HEAD closes or sits inside: its first commit, each commit a person made, the diff range to read, and its cost. Writes nothing — what to do with the answer (a squash message, a PR body) is the driver's call.
|
|
43054
|
+
|
|
43055
|
+
Refuses when the workflow declares no summary template, or when the process has no commits.`
|
|
43056
|
+
},
|
|
43057
|
+
{
|
|
43058
|
+
token: "base",
|
|
43059
|
+
kind: "base",
|
|
43060
|
+
arity: "none",
|
|
43061
|
+
group: "Inspect",
|
|
43062
|
+
summary: "Print the commit a review diff starts from",
|
|
43063
|
+
details: `Print the commit a review diff should start from, bare: the process's diff base before the first review round, the latest round's boundary after it. Writes nothing; refuses when no process is underway.`
|
|
43202
43064
|
},
|
|
43203
43065
|
{
|
|
43204
43066
|
token: "strays",
|
|
@@ -43207,45 +43069,27 @@ const COMMAND_ROWS = [
|
|
|
43207
43069
|
name: "range",
|
|
43208
43070
|
variadic: "glob"
|
|
43209
43071
|
},
|
|
43210
|
-
|
|
43211
|
-
|
|
43212
|
-
|
|
43213
|
-
|
|
43214
|
-
|
|
43215
|
-
"globs in the shell. stdout: a sorted JSON array, [] when",
|
|
43216
|
-
"clean; stderr: a summary, plus one stray: line per path with",
|
|
43217
|
-
"--verbose. --json=<n> picks one entry, nothing past the end;",
|
|
43218
|
-
"a symlink is a path like any other, never followed; a",
|
|
43219
|
-
"submodule counts as its path, whatever diff.ignoreSubmodules",
|
|
43220
|
-
"or .gitmodules ignore say. Runs in any git repository, no",
|
|
43221
|
-
"process needed. Exits 10 when",
|
|
43222
|
-
"strays were found, 1 on an unknown revision, 2 on a bad range"
|
|
43223
|
-
]
|
|
43072
|
+
group: "Inspect",
|
|
43073
|
+
summary: "List changed files outside the globs",
|
|
43074
|
+
details: `List the files changed in <range> (<base>..<tip>, an empty side is HEAD) that match none of the globs. Globs are anchored at the repository root: * stays within one path segment, ** crosses them, everything else is literal — quote them in the shell.
|
|
43075
|
+
|
|
43076
|
+
Prints a sorted JSON array ([] when clean) and a summary on stderr; --verbose adds one stray: line per path. Runs in any git repository, no process needed. Exits 10 when strays were found, 1 on an unknown revision, 2 on a bad range.`
|
|
43224
43077
|
},
|
|
43225
43078
|
{
|
|
43226
43079
|
token: "history list",
|
|
43227
43080
|
kind: "historyList",
|
|
43228
43081
|
arity: "none",
|
|
43229
|
-
|
|
43230
|
-
|
|
43231
|
-
|
|
43232
|
-
"--json prints one array of { ref, entry, branch, tip, date,",
|
|
43233
|
-
"subject }. Writes nothing and never replays; with no refs it",
|
|
43234
|
-
"hints at `gtd history fetch | sh` and still exits 0"
|
|
43235
|
-
]
|
|
43082
|
+
group: "Inspect",
|
|
43083
|
+
summary: "List finished processes",
|
|
43084
|
+
details: `List the finished processes this clone keeps, newest first: entry, branch, tip, date and subject. --json prints an array of { ref, entry, branch, tip, date, subject }. Writes nothing; with none kept it suggests \`gtd history fetch\`.`
|
|
43236
43085
|
},
|
|
43237
43086
|
{
|
|
43238
43087
|
token: "history show",
|
|
43239
43088
|
kind: "historyShow",
|
|
43240
43089
|
arity: { name: "ref" },
|
|
43241
|
-
|
|
43242
|
-
|
|
43243
|
-
|
|
43244
|
-
"entry hash (or unique prefix) or a branch name. Refuses",
|
|
43245
|
-
"(exit 1) when a config file differs from its content at the",
|
|
43246
|
-
"ref's tip. A version mismatch only warns on stderr. --json",
|
|
43247
|
-
"prints the trail as one document; writes nothing"
|
|
43248
|
-
]
|
|
43090
|
+
group: "Inspect",
|
|
43091
|
+
summary: "Replay one finished process",
|
|
43092
|
+
details: `Replay one finished process under today's config and print its steps, judge verdicts and approvals. <ref> is a full ref, an entry hash or a unique prefix of one, or a branch name. Refuses when a config file changed since; a different gtd version only warns. --json prints the trail as one document. Writes nothing.`
|
|
43249
43093
|
},
|
|
43250
43094
|
{
|
|
43251
43095
|
token: "history metrics",
|
|
@@ -43254,18 +43098,9 @@ const COMMAND_ROWS = [
|
|
|
43254
43098
|
name: "ref",
|
|
43255
43099
|
many: true
|
|
43256
43100
|
},
|
|
43257
|
-
|
|
43258
|
-
|
|
43259
|
-
|
|
43260
|
-
"model and phase with the double work of a rebuild, quality-lap",
|
|
43261
|
-
"findings per lens, and red checks that passed on their",
|
|
43262
|
-
"re-check — per process and in total. Each <ref>",
|
|
43263
|
-
"is read as by history show, but a branch name means every",
|
|
43264
|
-
"process kept from it; none means every ref.",
|
|
43265
|
-
"Reads commit subjects and trailers, never replays, so a",
|
|
43266
|
-
"changed config is no refusal. --json prints { processes, total",
|
|
43267
|
-
"} as one document; writes nothing"
|
|
43268
|
-
]
|
|
43101
|
+
group: "Inspect",
|
|
43102
|
+
summary: "Report cost and rework of finished processes",
|
|
43103
|
+
details: `Report how finished processes went: builds that landed first time or were rebuilt, cost by model and phase, review findings per lens, and red checks that passed when re-run — per process and in total. <ref> is read as by \`gtd history show\`, but a branch name means every process kept from it; no <ref> means all of them. Never replays, so a changed config is no reason to refuse. --json prints { processes, total }. Writes nothing.`
|
|
43269
43104
|
},
|
|
43270
43105
|
{
|
|
43271
43106
|
token: "history judges",
|
|
@@ -43274,18 +43109,9 @@ const COMMAND_ROWS = [
|
|
|
43274
43109
|
name: "ref",
|
|
43275
43110
|
many: true
|
|
43276
43111
|
},
|
|
43277
|
-
|
|
43278
|
-
|
|
43279
|
-
|
|
43280
|
-
"and p bucket, how many verdicts later events overturned.",
|
|
43281
|
-
"--var <name>=<value> re-decides every past verdict under",
|
|
43282
|
-
"another threshold and prints what would have changed;",
|
|
43283
|
-
"--jsonl prints one { process, judge, commit, step, question,",
|
|
43284
|
-
"state, answer, p, outcome } line per verdict instead. <ref>",
|
|
43285
|
-
"is read as by history metrics. Never replays and asks no",
|
|
43286
|
-
"model. --json prints { processes, total, whatIf }; writes",
|
|
43287
|
-
"nothing"
|
|
43288
|
-
]
|
|
43112
|
+
group: "Inspect",
|
|
43113
|
+
summary: "Calibrate the judges of finished processes",
|
|
43114
|
+
details: `Calibrate the judges of finished processes from their recorded verdicts: per judge and confidence bucket, how many verdicts later events overturned. <ref> is read as by \`gtd history metrics\`. Never replays and asks no model. --json prints { processes, total, whatIf }. Writes nothing.`
|
|
43289
43115
|
},
|
|
43290
43116
|
{
|
|
43291
43117
|
token: "history fetch",
|
|
@@ -43294,69 +43120,205 @@ const COMMAND_ROWS = [
|
|
|
43294
43120
|
name: "remote",
|
|
43295
43121
|
optional: true
|
|
43296
43122
|
},
|
|
43297
|
-
|
|
43298
|
-
|
|
43299
|
-
|
|
43300
|
-
|
|
43301
|
-
|
|
43123
|
+
group: "Inspect",
|
|
43124
|
+
summary: "Print a script that fetches finished processes",
|
|
43125
|
+
details: `Print a script that fetches every finished process kept on <remote> (default: origin) and reports how many arrived. gtd runs no git itself:
|
|
43126
|
+
|
|
43127
|
+
s=$(gtd history fetch) && sh -c "$s"`
|
|
43128
|
+
},
|
|
43129
|
+
{
|
|
43130
|
+
token: "ui",
|
|
43131
|
+
kind: "ui",
|
|
43132
|
+
arity: "none",
|
|
43133
|
+
group: "Inspect",
|
|
43134
|
+
summary: "Serve the web and phone client",
|
|
43135
|
+
details: `Serve gtd's web and phone client for this worktree. By default it publishes through \`tailscale serve\`, reachable from anywhere on the tailnet, on the first of ports 8443, 10000 and 443 that works; when none does it serves HTTPS locally instead of refusing.`
|
|
43136
|
+
},
|
|
43137
|
+
{
|
|
43138
|
+
token: "install",
|
|
43139
|
+
kind: "install",
|
|
43140
|
+
arity: "none",
|
|
43141
|
+
group: "Driver plumbing",
|
|
43142
|
+
summary: "Print a briefing for writing a driver",
|
|
43143
|
+
details: `Print a self-contained briefing that teaches an agent or a person to build a gtd driver in any shell or runtime, and where to get a workflow — gtd ships none. Writes nothing.`
|
|
43144
|
+
},
|
|
43145
|
+
{
|
|
43146
|
+
token: "judge",
|
|
43147
|
+
kind: "judge",
|
|
43148
|
+
arity: "none",
|
|
43149
|
+
group: "Driver plumbing",
|
|
43150
|
+
summary: "Print the pending judgment",
|
|
43151
|
+
details: `Print the current step's pending judgment: the prepared state, its questions and their criteria — the judge field \`gtd next --json\` carries. Writes nothing; refuses when the step declares no judge.`
|
|
43152
|
+
},
|
|
43153
|
+
{
|
|
43154
|
+
token: "judge answer",
|
|
43155
|
+
kind: "judgeAnswer",
|
|
43156
|
+
arity: "none",
|
|
43157
|
+
group: "Driver plumbing",
|
|
43158
|
+
summary: "Land a verdict read from stdin",
|
|
43159
|
+
details: `Read a verdict from stdin — one { id, answer, p } entry per pending question — and land it the way \`gtd land\` does: plain output names the commit, --json=script prints the script. Refuses when the step declares no judge; a verdict that is not JSON, or names a question that was not asked, is a usage error (exit 2).`
|
|
43160
|
+
},
|
|
43161
|
+
{
|
|
43162
|
+
token: "judge run",
|
|
43163
|
+
kind: "judgeRun",
|
|
43164
|
+
arity: "none",
|
|
43165
|
+
group: "Driver plumbing",
|
|
43166
|
+
summary: "Answer a judgment with a model",
|
|
43167
|
+
details: `Answer a judgment: read the \`gtd judge --json\` document from stdin and print the verdict [{ id, answer, p }] \`gtd judge answer\` reads. Needs no repository.
|
|
43168
|
+
|
|
43169
|
+
The fixed answerer reads --answers and leaves out the questions it does not cover; jev asks TypeSafe ($TYPESAFE_API_KEY); llm asks the \`claude\` CLI on PATH. jev and llm exit 1, printing nothing, when they cannot answer every question.`
|
|
43170
|
+
},
|
|
43171
|
+
{
|
|
43172
|
+
token: "check",
|
|
43173
|
+
kind: "check",
|
|
43174
|
+
arity: { names: ["mode", "file"] },
|
|
43175
|
+
group: "Driver plumbing",
|
|
43176
|
+
summary: "Check a steering file's format",
|
|
43177
|
+
details: `Run the steering format <mode> (qa, review or findings) over <file> and print each finding on a line of its own; exits 1 when there are any. Reads no workflow and runs from any directory — emitted validation scripts call it.`
|
|
43178
|
+
},
|
|
43179
|
+
{
|
|
43180
|
+
token: "uncheck",
|
|
43181
|
+
kind: "uncheck",
|
|
43182
|
+
arity: { name: "file" },
|
|
43183
|
+
group: "Driver plumbing",
|
|
43184
|
+
summary: "Untick a review file's boxes",
|
|
43185
|
+
details: `Reset every ticked review box (- [x]) in <file> to - [ ], writing the file only when it changes; a missing file writes nothing. Reads no workflow and runs from any directory — the landing script at a review gate calls it, so no tick is ever committed.`
|
|
43186
|
+
},
|
|
43187
|
+
{
|
|
43188
|
+
token: "lsp",
|
|
43189
|
+
kind: "lsp",
|
|
43190
|
+
arity: "none",
|
|
43191
|
+
group: "Driver plumbing",
|
|
43192
|
+
summary: "Start the steering-file language server",
|
|
43193
|
+
details: `Start the language server for .gtd/ steering files on stdio.`
|
|
43302
43194
|
}
|
|
43303
43195
|
];
|
|
43304
43196
|
const commandByToken = (token) => COMMAND_ROWS.find((r) => r.token === token);
|
|
43305
|
-
|
|
43306
|
-
const
|
|
43307
|
-
|
|
43308
|
-
|
|
43309
|
-
|
|
43310
|
-
|
|
43311
|
-
const
|
|
43312
|
-
const
|
|
43313
|
-
const
|
|
43197
|
+
/** Every word that may follow `gtd`, for "did you mean". */
|
|
43198
|
+
const COMMAND_WORDS = [.../* @__PURE__ */ new Set([
|
|
43199
|
+
...COMMAND_ROWS.map((r) => r.token.split(" ")[0]),
|
|
43200
|
+
"help",
|
|
43201
|
+
"version"
|
|
43202
|
+
])];
|
|
43203
|
+
const TOP_COLUMN = 28;
|
|
43204
|
+
const OPTION_COLUMN = 22;
|
|
43205
|
+
const synopsis = (row) => {
|
|
43206
|
+
const { token, arity } = row;
|
|
43207
|
+
if (arity === "none") return token;
|
|
43208
|
+
if ("names" in arity) return `${token} <${arity.names[0]}> <${arity.names[1]}>`;
|
|
43209
|
+
if ("variadic" in arity) return `${token} <${arity.name}> [${arity.variadic}...]`;
|
|
43210
|
+
if ("optional" in arity) return `${token} [${arity.name}]`;
|
|
43211
|
+
if ("many" in arity) return `${token} [${arity.name}...]`;
|
|
43212
|
+
return `${token} <${arity.name}>`;
|
|
43213
|
+
};
|
|
43314
43214
|
const SPACE_FORM_FLAGS = /* @__PURE__ */ new Set(["--workflow", "--var"]);
|
|
43315
43215
|
const flagHeader = (row) => {
|
|
43316
43216
|
if (row.valueHint === "") return row.name;
|
|
43217
|
+
if (row.arity === "optional") return `${row.name}[=${row.valueHint}]`;
|
|
43317
43218
|
return SPACE_FORM_FLAGS.has(row.name) ? `${row.name} ${row.valueHint}` : `${row.name}=${row.valueHint}`;
|
|
43318
43219
|
};
|
|
43220
|
+
const START_ROW = {
|
|
43221
|
+
term: "--workflow <name>",
|
|
43222
|
+
summary: "Start a process on a workflow; --var <name>=<value> pins one of its settings"
|
|
43223
|
+
};
|
|
43224
|
+
const GLOBAL_OPTIONS = [
|
|
43225
|
+
["-v, --verbose", "Narrate on stderr what gtd resolved"],
|
|
43226
|
+
["-h, --help", "Print help; gtd <command> --help for one command"],
|
|
43227
|
+
["-V, --version", "Print the version"]
|
|
43228
|
+
];
|
|
43229
|
+
const EXTRA_TOPICS = [[
|
|
43230
|
+
"help [command]",
|
|
43231
|
+
"Print help for gtd or one command",
|
|
43232
|
+
"Print gtd's overview, or with <command> that command's own help."
|
|
43233
|
+
], [
|
|
43234
|
+
"version",
|
|
43235
|
+
"Print the version",
|
|
43236
|
+
"Print gtd's version."
|
|
43237
|
+
]];
|
|
43238
|
+
const EXAMPLES = ` $ gtd --workflow <name> start a process
|
|
43239
|
+
$ gtd next see what happens next
|
|
43240
|
+
$ s=$(gtd land --json=script) && sh -c "$s" land the turn
|
|
43241
|
+
$ gtd help land one command's details`;
|
|
43242
|
+
const lines = (block) => block.map((l) => `${l}\n`).join("");
|
|
43243
|
+
const GROUPS = [
|
|
43244
|
+
"Core loop",
|
|
43245
|
+
"Start and stop",
|
|
43246
|
+
"Inspect",
|
|
43247
|
+
"Driver plumbing"
|
|
43248
|
+
];
|
|
43319
43249
|
const renderHelp = () => {
|
|
43320
|
-
const
|
|
43321
|
-
const
|
|
43322
|
-
|
|
43323
|
-
|
|
43324
|
-
|
|
43325
|
-
|
|
43326
|
-
|
|
43327
|
-
|
|
43328
|
-
|
|
43329
|
-
|
|
43330
|
-
].join("")
|
|
43250
|
+
const groups = GROUPS.map((group) => {
|
|
43251
|
+
const rows = COMMAND_ROWS.filter((r) => r.group === group).flatMap((r) => definition(synopsis(r), r.summary, TOP_COLUMN));
|
|
43252
|
+
const start = group === "Start and stop" ? definition(START_ROW.term, START_ROW.summary, TOP_COLUMN) : [];
|
|
43253
|
+
const extra = group === "Driver plumbing" ? EXTRA_TOPICS.flatMap(([term, summary]) => definition(term, summary, TOP_COLUMN)) : [];
|
|
43254
|
+
return `${group}:\n${lines([
|
|
43255
|
+
...start,
|
|
43256
|
+
...rows,
|
|
43257
|
+
...extra
|
|
43258
|
+
])}`;
|
|
43259
|
+
});
|
|
43260
|
+
return "Usage: gtd <command> [options]\n\n" + lines(wrap("gtd reads a repository's git state and prints what should happen next in its workflow. It never acts on it: a driver, or you, runs what it prints.")) + "\n" + groups.join("\n") + "\nOptions:\n" + lines(GLOBAL_OPTIONS.flatMap(([term, text]) => definition(term, text, TOP_COLUMN))) + `\nExamples:\n${EXAMPLES}\n\n` + lines(wrap("Run `gtd help <command>` for a command's details and options, and `gtd install` to learn how to drive gtd."));
|
|
43261
|
+
};
|
|
43262
|
+
/** Shown on stderr above a bare `gtd`'s usage error — the overview stays one `gtd help` away. */
|
|
43263
|
+
const SHORT_USAGE = `Usage: gtd <command> [options]
|
|
43264
|
+
|
|
43265
|
+
gtd --workflow <name> start a process
|
|
43266
|
+
gtd next print what should happen next
|
|
43267
|
+
gtd land record what the tree now shows
|
|
43268
|
+
|
|
43269
|
+
`;
|
|
43270
|
+
const renderCommandHelp = (row) => {
|
|
43271
|
+
const options = FLAGS.filter((f) => f.name !== "--verbose" && f.scope(row.kind)).flatMap((f) => definition(flagHeader(f), f.helpFor?.[row.kind] ?? f.help, OPTION_COLUMN));
|
|
43272
|
+
const global = definition("-v, --verbose", "Narrate on stderr what gtd resolved", OPTION_COLUMN);
|
|
43273
|
+
return `Usage: gtd ${synopsis(row)}${options.length > 0 ? " [options]" : ""}\n\n` + lines(wrap(row.details)) + `\nOptions:\n${lines([...options, ...global])}`;
|
|
43274
|
+
};
|
|
43275
|
+
/** `gtd help <topic…>`'s text: one command's help, every `history` subcommand's for `history`, or undefined for an unknown topic. */
|
|
43276
|
+
const renderTopicHelp = (topic) => {
|
|
43277
|
+
const two = commandByToken(topic.slice(0, 2).join(" "));
|
|
43278
|
+
if (two !== void 0 && topic.length >= 2) return renderCommandHelp(two);
|
|
43279
|
+
const [first = ""] = topic;
|
|
43280
|
+
const one = commandByToken(first);
|
|
43281
|
+
if (one !== void 0) return renderCommandHelp(one);
|
|
43282
|
+
const extra = EXTRA_TOPICS.find(([term]) => term.split(" ")[0] === first);
|
|
43283
|
+
if (extra !== void 0) return `Usage: gtd ${extra[0]}\n\n${lines(wrap(extra[2]))}`;
|
|
43284
|
+
const family = COMMAND_ROWS.filter((r) => r.token.startsWith(`${first} `));
|
|
43285
|
+
if (first !== "" && family.length > 0) return family.map(renderCommandHelp).join("\n");
|
|
43286
|
+
};
|
|
43287
|
+
/** A usage error's tail: the nearest candidate when `word` looks like a typo of one, and where the full list is. */
|
|
43288
|
+
const didYouMean = (word, candidates, prefix = "", pointer = "`gtd help` for every command") => {
|
|
43289
|
+
const guess = closest(word, candidates);
|
|
43290
|
+
return guess === void 0 ? ` — run ${pointer}` : ` — did you mean \`${prefix}${guess}\`? Run ${pointer}`;
|
|
43291
|
+
};
|
|
43292
|
+
const unknownOption = (tok, jsonSeen) => {
|
|
43293
|
+
const candidates = tok.startsWith("--") ? FLAGS.map((f) => f.name) : [];
|
|
43294
|
+
return {
|
|
43295
|
+
kind: "error",
|
|
43296
|
+
message: `gtd: unknown option '${tok}'${didYouMean(tok.split("=")[0], candidates, "", "`gtd help <command>` for its options")}`,
|
|
43297
|
+
jsonSeen
|
|
43298
|
+
};
|
|
43331
43299
|
};
|
|
43332
43300
|
const tokenize = (tail) => {
|
|
43333
43301
|
const positionals = [];
|
|
43334
43302
|
const byFlag = /* @__PURE__ */ new Map();
|
|
43335
43303
|
const present = /* @__PURE__ */ new Set();
|
|
43336
43304
|
let jsonSeen = false;
|
|
43305
|
+
let afterBareJson;
|
|
43337
43306
|
for (let i = 0; i < tail.length; i++) {
|
|
43338
43307
|
const original = tail[i];
|
|
43339
43308
|
const tok = SINGLE_DASH_ALIASES.get(original) ?? original;
|
|
43340
|
-
if (!tok.startsWith("
|
|
43309
|
+
if (!tok.startsWith("-") || tok === "-") {
|
|
43310
|
+
if (tail[i - 1] === "--json") afterBareJson ??= original;
|
|
43341
43311
|
positionals.push(original);
|
|
43342
43312
|
continue;
|
|
43343
43313
|
}
|
|
43344
|
-
const row = flagByToken(tok);
|
|
43345
|
-
if (row === void 0) return
|
|
43346
|
-
kind: "error",
|
|
43347
|
-
message: `gtd: unknown option '${tok}' — see \`gtd --help\``,
|
|
43348
|
-
jsonSeen
|
|
43349
|
-
};
|
|
43314
|
+
const row = tok.startsWith("--") ? flagByToken(tok) : void 0;
|
|
43315
|
+
if (row === void 0) return unknownOption(tok, jsonSeen);
|
|
43350
43316
|
const eq = tok.indexOf("=");
|
|
43351
43317
|
const alreadyPresent = present.has(row.name);
|
|
43352
43318
|
present.add(row.name);
|
|
43353
43319
|
if (row.name === "--json") jsonSeen = true;
|
|
43354
43320
|
if (row.arity === 0) {
|
|
43355
|
-
if (eq !== -1) return
|
|
43356
|
-
kind: "error",
|
|
43357
|
-
message: `gtd: unknown option '${tok}' — see \`gtd --help\``,
|
|
43358
|
-
jsonSeen
|
|
43359
|
-
};
|
|
43321
|
+
if (eq !== -1) return unknownOption(tok, jsonSeen);
|
|
43360
43322
|
if (!byFlag.has(row.name)) byFlag.set(row.name, []);
|
|
43361
43323
|
continue;
|
|
43362
43324
|
}
|
|
@@ -43407,7 +43369,8 @@ const tokenize = (tail) => {
|
|
|
43407
43369
|
positionals,
|
|
43408
43370
|
byFlag,
|
|
43409
43371
|
present,
|
|
43410
|
-
jsonSeen
|
|
43372
|
+
jsonSeen,
|
|
43373
|
+
...afterBareJson !== void 0 ? { afterBareJson } : {}
|
|
43411
43374
|
};
|
|
43412
43375
|
};
|
|
43413
43376
|
const usagePlan = (message, json, stdout = "") => ({
|
|
@@ -43459,18 +43422,38 @@ const buildLandCommand = (bag) => ({
|
|
|
43459
43422
|
...bag["--cost"] !== void 0 ? { cost: bag["--cost"] } : {},
|
|
43460
43423
|
...bag["--model"] !== void 0 ? { model: bag["--model"] } : {}
|
|
43461
43424
|
});
|
|
43462
|
-
const
|
|
43463
|
-
|
|
43425
|
+
const HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h"]);
|
|
43426
|
+
/** `--help`/`-h` anywhere: the named command's own help, else the overview — never a usage error, so a typo'd command still gets help. */
|
|
43427
|
+
const helpPlan = (argv) => {
|
|
43428
|
+
const tokenized = tokenize(argv.slice(2).filter((tok) => !HELP_FLAGS.has(tok)));
|
|
43429
|
+
const topic = tokenized.kind === "ok" ? tokenized.positionals : [];
|
|
43430
|
+
return {
|
|
43431
|
+
kind: "output",
|
|
43432
|
+
stdout: renderTopicHelp(topic) ?? renderHelp()
|
|
43433
|
+
};
|
|
43434
|
+
};
|
|
43435
|
+
/** `gtd help [topic…]`: an unknown topic is a usage error naming the nearest command. */
|
|
43436
|
+
const helpCommandPlan = (topic, json) => {
|
|
43437
|
+
if (topic.length === 0) return {
|
|
43464
43438
|
kind: "output",
|
|
43465
43439
|
stdout: renderHelp()
|
|
43466
43440
|
};
|
|
43441
|
+
const text = renderTopicHelp(topic);
|
|
43442
|
+
if (text !== void 0) return {
|
|
43443
|
+
kind: "output",
|
|
43444
|
+
stdout: text
|
|
43445
|
+
};
|
|
43446
|
+
return usagePlan(`gtd: help: no command '${topic.join(" ")}'${didYouMean(topic[0], COMMAND_WORDS, "gtd help ")}`, json);
|
|
43447
|
+
};
|
|
43448
|
+
const parseArgv = (argv) => {
|
|
43449
|
+
if (argv.some((tok) => HELP_FLAGS.has(tok))) return helpPlan(argv);
|
|
43467
43450
|
if (argv.includes("--version") || argv.includes("-V")) return {
|
|
43468
43451
|
kind: "output",
|
|
43469
43452
|
stdout: `${GTD_VERSION}\n`
|
|
43470
43453
|
};
|
|
43471
43454
|
const tokenized = tokenize(argv.slice(2));
|
|
43472
43455
|
if (tokenized.kind === "error") return usagePlan(tokenized.message, tokenized.jsonSeen);
|
|
43473
|
-
const { positionals, byFlag, present, jsonSeen } = tokenized;
|
|
43456
|
+
const { positionals, byFlag, present, jsonSeen, afterBareJson } = tokenized;
|
|
43474
43457
|
const first = positionals[0];
|
|
43475
43458
|
const workflowRaw = byFlag.get("--workflow")?.[0];
|
|
43476
43459
|
const workflowPresent = workflowRaw !== void 0;
|
|
@@ -43478,13 +43461,14 @@ const parseArgv = (argv) => {
|
|
|
43478
43461
|
kind: "output",
|
|
43479
43462
|
stdout: `${GTD_VERSION}\n`
|
|
43480
43463
|
};
|
|
43481
|
-
if (first === "help") return
|
|
43482
|
-
kind: "output",
|
|
43483
|
-
stdout: renderHelp()
|
|
43484
|
-
};
|
|
43464
|
+
if (first === "help") return helpCommandPlan(positionals.slice(1), jsonSeen);
|
|
43485
43465
|
const isHistory = first === "history";
|
|
43486
43466
|
const row = first === void 0 ? void 0 : commandByToken(isHistory ? `history ${positionals[1] ?? ""}` : first);
|
|
43487
|
-
if (isHistory && row === void 0)
|
|
43467
|
+
if (isHistory && row === void 0) {
|
|
43468
|
+
const subcommands = COMMAND_ROWS.filter((r) => r.token.startsWith("history ")).map((r) => r.token.split(" ")[1]);
|
|
43469
|
+
const typo = closest(positionals[1] ?? "", subcommands);
|
|
43470
|
+
return usagePlan(typo !== void 0 ? `gtd history: unknown subcommand '${positionals[1]}' — did you mean \`gtd history ${typo}\`?` : "gtd history: expected a subcommand — list, show <ref>, metrics [ref...], judges [ref...] or fetch [remote]", jsonSeen);
|
|
43471
|
+
}
|
|
43488
43472
|
const judgeAnswer = row?.kind === "judge" && positionals[1] === "answer";
|
|
43489
43473
|
const selectsStart = workflowPresent && first === void 0;
|
|
43490
43474
|
const judgeRun = row?.kind === "judge" && positionals[1] === "run";
|
|
@@ -43492,15 +43476,18 @@ const parseArgv = (argv) => {
|
|
|
43492
43476
|
if (row === void 0 && !selectsStart) {
|
|
43493
43477
|
const violation = scopeViolation(void 0, present);
|
|
43494
43478
|
if (violation !== void 0) return usagePlan(violation, jsonSeen);
|
|
43495
|
-
if (first === void 0) return usagePlan("gtd: missing command —
|
|
43496
|
-
return usagePlan(`unknown command '${first}'`, jsonSeen);
|
|
43479
|
+
if (first === void 0) return usagePlan("gtd: missing command — run `gtd help` for every command, or `gtd install` to learn how to drive gtd", jsonSeen, SHORT_USAGE);
|
|
43480
|
+
return usagePlan(`gtd: unknown command '${first}'${didYouMean(first, COMMAND_WORDS, "gtd ")}`, jsonSeen);
|
|
43497
43481
|
}
|
|
43498
43482
|
const restPositionals = first === void 0 ? positionals : positionals.slice(judgeAnswer || judgeRun || isHistory ? 2 : 1);
|
|
43499
43483
|
if (kind === "start" && first === void 0) {
|
|
43500
43484
|
if (restPositionals.length > 0) return usagePlan(`gtd: too many arguments — expected none, got: ${restPositionals.join(", ")}`, jsonSeen);
|
|
43501
43485
|
} else {
|
|
43502
|
-
const arityMsg = kind === "check" && restPositionals.length === 0 && present.has("--open-threads") ? void 0 : arityError(isHistory ? row.token : first, restPositionals, row.arity);
|
|
43503
|
-
if (arityMsg !== void 0)
|
|
43486
|
+
const arityMsg = kind === "check" && restPositionals.length === 0 && present.has("--open-threads") || kind === "door" && restPositionals.length === 0 ? void 0 : arityError(isHistory ? row.token : first, restPositionals, row.arity);
|
|
43487
|
+
if (arityMsg !== void 0) {
|
|
43488
|
+
const hint = afterBareJson !== void 0 && restPositionals.includes(afterBareJson) ? ` — to print one field, write --json=${afterBareJson}` : "";
|
|
43489
|
+
return usagePlan(arityMsg + hint, jsonSeen);
|
|
43490
|
+
}
|
|
43504
43491
|
}
|
|
43505
43492
|
const violation = scopeViolation(kind, present);
|
|
43506
43493
|
if (violation !== void 0) return usagePlan(violation, jsonSeen);
|
|
@@ -43542,7 +43529,7 @@ const parseArgv = (argv) => {
|
|
|
43542
43529
|
kind: "command",
|
|
43543
43530
|
command: {
|
|
43544
43531
|
kind: "door",
|
|
43545
|
-
name: restPositionals[0],
|
|
43532
|
+
...restPositionals[0] !== void 0 ? { name: restPositionals[0] } : {},
|
|
43546
43533
|
args: restPositionals.slice(1)
|
|
43547
43534
|
},
|
|
43548
43535
|
json,
|