@pmelab/gtd 19.0.0 → 20.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +41 -39
- package/claude/hooks/doors.ts +34 -0
- package/claude/hooks/drive.ts +1 -0
- package/claude/hooks/register.tsx +12 -13
- package/claude/hooks/ship.ts +3 -2
- package/dist/gtd.bundle.mjs +516 -268
- package/package.json +2 -2
- package/skills/authoring/SKILL.md +39 -27
- package/src/flows/runtime.ts +22 -17
- package/src/workflows/access.test.ts +3 -3
- package/src/workflows/{unified.ts → bundled.ts} +30 -27
- package/src/workflows/index.ts +1 -1
- package/src/workflows/review.test.ts +3 -3
- package/src/workflows/review.ts +5 -5
- package/src/workflows/skills.test.ts +2 -2
- package/src/workflows/steps.test.ts +2 -2
- package/src/workflows/steps.ts +1 -1
- package/src/workflows/text.ts +1 -1
- package/claude/hooks/entry.ts +0 -34
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -30964,7 +30964,7 @@ const changesSince = (hash, pattern) => {
|
|
|
30964
30964
|
};
|
|
30965
30965
|
/** The commit the process stands on at this point of the flow. */
|
|
30966
30966
|
const head = () => ctx().head();
|
|
30967
|
-
/** The process's diff base: the commit before it began, or the base `gtd --
|
|
30967
|
+
/** The process's diff base: the commit before it began, or the base `gtd --workflow` fixed. */
|
|
30968
30968
|
const start = () => ctx().start();
|
|
30969
30969
|
/** The skill list `localName` (scoped from here, same as `agent()`) resolves to — for a prompt preamble. */
|
|
30970
30970
|
const skillsFor = (localName) => ctx().skillsFor(localName);
|
|
@@ -47622,6 +47622,9 @@ const TRAILER_READERS = {
|
|
|
47622
47622
|
"Gtd-Step": (value, into) => {
|
|
47623
47623
|
into.step ??= parseStepId(value);
|
|
47624
47624
|
},
|
|
47625
|
+
"Gtd-Workflow": (value, into) => {
|
|
47626
|
+
if (value !== "") into.workflow ??= value;
|
|
47627
|
+
},
|
|
47625
47628
|
"Gtd-Judge": (value, into) => {
|
|
47626
47629
|
const parsed = parseJson(value);
|
|
47627
47630
|
if (isVerdict(parsed)) into.judge.push(parsed);
|
|
@@ -47646,6 +47649,7 @@ const parseCommitMessage = (message) => {
|
|
|
47646
47649
|
const subject = (newline === -1 ? message : message.slice(0, newline)).trim();
|
|
47647
47650
|
const trailers = {
|
|
47648
47651
|
step: void 0,
|
|
47652
|
+
workflow: void 0,
|
|
47649
47653
|
reviewBase: void 0,
|
|
47650
47654
|
judge: [],
|
|
47651
47655
|
vars: {},
|
|
@@ -47662,6 +47666,7 @@ const parseCommitMessage = (message) => {
|
|
|
47662
47666
|
const formatCommitMessage = (spec) => {
|
|
47663
47667
|
const lines = [];
|
|
47664
47668
|
if (spec.step !== void 0) lines.push(`Gtd-Step: ${formatStepId(spec.step)}`);
|
|
47669
|
+
if (spec.workflow !== void 0) lines.push(`Gtd-Workflow: ${spec.workflow}`);
|
|
47665
47670
|
if (spec.reviewBase !== void 0) lines.push(`Gtd-Review-Base: ${spec.reviewBase}`);
|
|
47666
47671
|
for (const [name, value] of Object.entries(spec.vars ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) lines.push(`Gtd-Var: ${name}=${value}`);
|
|
47667
47672
|
if (spec.cost !== void 0) lines.push(`Gtd-Cost: ${spec.cost.cost}${spec.cost.model !== void 0 ? ` ${spec.cost.model}` : ""}`);
|
|
@@ -47769,11 +47774,6 @@ const unknownOptions = (step) => {
|
|
|
47769
47774
|
return `gtd: step "${step.name}": unknown key(s) ${unknown.join(", ")} in ${step.kind}() options`;
|
|
47770
47775
|
};
|
|
47771
47776
|
const replay = async (input) => {
|
|
47772
|
-
let entryRead = false;
|
|
47773
|
-
const args = { get entry() {
|
|
47774
|
-
entryRead = true;
|
|
47775
|
-
return input.episode.entry;
|
|
47776
|
-
} };
|
|
47777
47777
|
const commits = input.episode.commits.map((c) => ({
|
|
47778
47778
|
...c,
|
|
47779
47779
|
parsed: parseCommitMessage(c.message)
|
|
@@ -47964,8 +47964,7 @@ const replay = async (input) => {
|
|
|
47964
47964
|
kind: "rest",
|
|
47965
47965
|
rest: step,
|
|
47966
47966
|
trace,
|
|
47967
|
-
landed
|
|
47968
|
-
entryRead
|
|
47967
|
+
landed
|
|
47969
47968
|
});
|
|
47970
47969
|
pendingUsed = true;
|
|
47971
47970
|
landed = step;
|
|
@@ -48072,7 +48071,7 @@ const replay = async (input) => {
|
|
|
48072
48071
|
};
|
|
48073
48072
|
installContext(context);
|
|
48074
48073
|
try {
|
|
48075
|
-
const flowDone = input.flow(
|
|
48074
|
+
const flowDone = input.flow({}).then(() => {
|
|
48076
48075
|
if (outcome === void 0 && trace.length === 0) outcome = {
|
|
48077
48076
|
kind: "failed",
|
|
48078
48077
|
message: "gtd: the flow returned without reaching any step"
|
|
@@ -48381,7 +48380,7 @@ const startGateBlockedMessage = () => `The test baseline is red — gtd will not
|
|
|
48381
48380
|
\`.gtd/FEEDBACK.md\` holds the failing output.
|
|
48382
48381
|
|
|
48383
48382
|
What each change does next (then run \`gtd land\`):
|
|
48384
|
-
- **Retry check** — edit the code and/or \`.gtd/FEEDBACK.md\` to fix the failing tests (**start-gate.check**). To repair the baseline as its own separate reviewed commit instead, abandon this start and run \`gtd --
|
|
48383
|
+
- **Retry check** — edit the code and/or \`.gtd/FEEDBACK.md\` to fix the failing tests (**start-gate.check**). To repair the baseline as its own separate reviewed commit instead, abandon this start and run \`gtd --workflow fix\` from a clean \`idle\`.
|
|
48385
48384
|
`;
|
|
48386
48385
|
const reviewGateBlockedMessage = () => `The test baseline is red — gtd will not start a review on a broken suite.
|
|
48387
48386
|
\`.gtd/FEEDBACK.md\` holds the failing output.
|
|
@@ -49141,7 +49140,7 @@ const fixSuite = () => agentWithSkills("fix.suite.fixing", packagesItemFixSuiteP
|
|
|
49141
49140
|
model: coder(),
|
|
49142
49141
|
system: builderSystem()
|
|
49143
49142
|
});
|
|
49144
|
-
const
|
|
49143
|
+
const fixCheck = (built) => agentWithSkills("fix", buildFixPrompt(built), {
|
|
49145
49144
|
label: "Fixing the check",
|
|
49146
49145
|
file: FEEDBACK,
|
|
49147
49146
|
model: coder(),
|
|
@@ -49687,7 +49686,7 @@ const routeNotes = async (notes, r) => {
|
|
|
49687
49686
|
}
|
|
49688
49687
|
if (nits.length > 0) {
|
|
49689
49688
|
await guarded(r.frozen, () => fixNits(nits), "review")();
|
|
49690
|
-
await healthy(guarded(r.frozen,
|
|
49689
|
+
await healthy(guarded(r.frozen, fixCheck, "review"), { escalations: r.escalations });
|
|
49691
49690
|
}
|
|
49692
49691
|
if (edits.length > 0) {
|
|
49693
49692
|
await r.close();
|
|
@@ -49750,7 +49749,7 @@ const reviewOnce = async (base, carry, escalations, frozen) => {
|
|
|
49750
49749
|
const risks = reviewRisks(read(".gtd/REVIEW.md") ?? "");
|
|
49751
49750
|
if (risks.length === 0) return;
|
|
49752
49751
|
await guarded(frozen, () => fixRisks(risks), "review")();
|
|
49753
|
-
await healthy(guarded(frozen,
|
|
49752
|
+
await healthy(guarded(frozen, fixCheck, "review"), { escalations });
|
|
49754
49753
|
await reviewing(base, carry);
|
|
49755
49754
|
};
|
|
49756
49755
|
/**
|
|
@@ -49759,7 +49758,7 @@ const reviewOnce = async (base, carry, escalations, frozen) => {
|
|
|
49759
49758
|
* another planning lap, questions are answered in place, nits fixed in one
|
|
49760
49759
|
* batch, praise dropped.
|
|
49761
49760
|
*/
|
|
49762
|
-
const review = async (base, escalations = { rounds: 0 }, frozen) => {
|
|
49761
|
+
const review$1 = async (base, escalations = { rounds: 0 }, frozen) => {
|
|
49763
49762
|
let carry;
|
|
49764
49763
|
for (;;) {
|
|
49765
49764
|
await reviewOnce(base, carry, escalations, frozen);
|
|
@@ -49783,9 +49782,9 @@ const review = async (base, escalations = { rounds: 0 }, frozen) => {
|
|
|
49783
49782
|
const buildTail = (fixFirst, base, built) => scope("build", async () => {
|
|
49784
49783
|
const frozen = built?.frozen;
|
|
49785
49784
|
const escalations = { rounds: 0 };
|
|
49786
|
-
const guardedFix = guarded(frozen, () =>
|
|
49785
|
+
const guardedFix = guarded(frozen, () => fixCheck());
|
|
49787
49786
|
let redFirst = fixFirst;
|
|
49788
|
-
if (!fixFirst) await healthy(guarded(frozen, () =>
|
|
49787
|
+
if (!fixFirst) await healthy(guarded(frozen, () => fixCheck(built)), {
|
|
49789
49788
|
escalations,
|
|
49790
49789
|
sweepOnGreen: [ARCHITECTURE]
|
|
49791
49790
|
});
|
|
@@ -49801,7 +49800,7 @@ const buildTail = (fixFirst, base, built) => scope("build", async () => {
|
|
|
49801
49800
|
}
|
|
49802
49801
|
const lap = lapped ? "clean" : await qualityLap();
|
|
49803
49802
|
lapped = true;
|
|
49804
|
-
if (lap === "clean") return review(base, escalations, frozen);
|
|
49803
|
+
if (lap === "clean") return review$1(base, escalations, frozen);
|
|
49805
49804
|
if (await fixQualityFindings(escalations, frozen)) await healthy(guardedFix, { escalations });
|
|
49806
49805
|
else redFirst = true;
|
|
49807
49806
|
}
|
|
@@ -49856,8 +49855,8 @@ const access = (vars) => ({
|
|
|
49856
49855
|
...Object.fromEntries(lensesOf(vars.qualityReviews ?? "").map((lens) => [`build.quality.${lens}`, { write: [] }]))
|
|
49857
49856
|
});
|
|
49858
49857
|
//#endregion
|
|
49859
|
-
//#region src/workflows/
|
|
49860
|
-
var
|
|
49858
|
+
//#region src/workflows/bundled.ts
|
|
49859
|
+
var bundled_exports = /* @__PURE__ */ __exportAll({
|
|
49861
49860
|
ARCHITECTURE: () => ARCHITECTURE,
|
|
49862
49861
|
ESCALATION: () => ESCALATION,
|
|
49863
49862
|
FEEDBACK: () => FEEDBACK,
|
|
@@ -49884,17 +49883,20 @@ var unified_exports = /* @__PURE__ */ __exportAll({
|
|
|
49884
49883
|
collecting: () => collecting,
|
|
49885
49884
|
declaredTests: () => declaredTests,
|
|
49886
49885
|
decompose: () => decompose,
|
|
49887
|
-
default: () =>
|
|
49886
|
+
default: () => feature,
|
|
49888
49887
|
defaults: () => defaults,
|
|
49889
49888
|
describeEscalation: () => describeEscalation,
|
|
49890
49889
|
design: () => design,
|
|
49890
|
+
doors: () => doors,
|
|
49891
49891
|
driftedScenarios: () => driftedScenarios,
|
|
49892
49892
|
envDefaults: () => envDefaults,
|
|
49893
49893
|
escalate: () => escalate,
|
|
49894
49894
|
escalation: () => escalation,
|
|
49895
49895
|
escalationExhausted: () => escalationExhausted,
|
|
49896
49896
|
fastSuiteGuard: () => fastSuiteGuard,
|
|
49897
|
+
feature: () => feature,
|
|
49897
49898
|
fix: () => fix,
|
|
49899
|
+
fixCheck: () => fixCheck,
|
|
49898
49900
|
fixNits: () => fixNits,
|
|
49899
49901
|
fixQuality: () => fixQuality,
|
|
49900
49902
|
fixQualityFindings: () => fixQualityFindings,
|
|
@@ -49986,34 +49988,35 @@ const ordinaryStart = async () => {
|
|
|
49986
49988
|
await gate("start-gate", startGateBlockedMessage());
|
|
49987
49989
|
await planAndBuild(start());
|
|
49988
49990
|
};
|
|
49989
|
-
|
|
49990
|
-
|
|
49991
|
-
"
|
|
49992
|
-
|
|
49993
|
-
|
|
49994
|
-
|
|
49995
|
-
|
|
49996
|
-
|
|
49997
|
-
|
|
49998
|
-
|
|
49999
|
-
|
|
50000
|
-
if (entry === "review-gate.check") {
|
|
50001
|
-
await gate("review-gate", reviewGateBlockedMessage());
|
|
50002
|
-
return afterTail(await buildTail(false, start()));
|
|
50003
|
-
}
|
|
50004
|
-
if (entry === "start-gate.check") {
|
|
50005
|
-
await gate("start-gate", startGateBlockedMessage());
|
|
50006
|
-
return planAndBuild(start());
|
|
50007
|
-
}
|
|
50008
|
-
refuse(`"${entry}" is not an enterable state — enterable states:\n${ENTRIES.map((name) => ` ${name}`).join("\n")}`);
|
|
50009
|
-
}
|
|
49991
|
+
/** Repair a red baseline through the build tail, as its own reviewed commit. */
|
|
49992
|
+
const fix = async () => {
|
|
49993
|
+
if (await baseline("fix-precheck")) return;
|
|
49994
|
+
return afterTail(await buildTail(true, start()));
|
|
49995
|
+
};
|
|
49996
|
+
/** Pure review of everything since `reviewBase`. */
|
|
49997
|
+
const review = async () => {
|
|
49998
|
+
await gate("review-gate", reviewGateBlockedMessage());
|
|
49999
|
+
return afterTail(await buildTail(false, start()));
|
|
50000
|
+
};
|
|
50001
|
+
const feature = ordinaryStart;
|
|
50010
50002
|
const summary = summaryPrompt;
|
|
50011
50003
|
const steering = {
|
|
50012
50004
|
[REQUIREMENTS]: "qa",
|
|
50013
50005
|
[ARCHITECTURE]: "qa",
|
|
50014
50006
|
[REVIEW]: "review"
|
|
50015
50007
|
};
|
|
50016
|
-
const base = (
|
|
50008
|
+
const base = (workflow, vars) => workflow === "review" ? vars.reviewBase ?? "" : void 0;
|
|
50009
|
+
const doors = {
|
|
50010
|
+
fix: { workflow: "fix" },
|
|
50011
|
+
review: {
|
|
50012
|
+
workflow: "review",
|
|
50013
|
+
args: [{
|
|
50014
|
+
name: "base",
|
|
50015
|
+
optional: true
|
|
50016
|
+
}],
|
|
50017
|
+
vars: ({ base }) => base === void 0 ? {} : { reviewBase: base }
|
|
50018
|
+
}
|
|
50019
|
+
};
|
|
50017
50020
|
//#endregion
|
|
50018
50021
|
//#region src/platform/Host.ts
|
|
50019
50022
|
const toError$1 = (e) => e instanceof Error ? e : new Error(String(e));
|
|
@@ -50206,6 +50209,8 @@ const makeGitImpl = (executor, root) => {
|
|
|
50206
50209
|
resolveRef: (ref) => exec("git", "rev-parse", "--verify", ref).pipe(map$4((s) => s.trim()), flatMap$4((hash) => resolvedRefOrCorrupted(ref, hash))),
|
|
50207
50210
|
readRefOption: (ref) => exec("git", "rev-parse", "--verify", "--quiet", ref).pipe(map$4((s) => some(s.trim())), catchAll(() => succeed$3(none$4()))),
|
|
50208
50211
|
isAncestor: (a, b) => run$2(root, "git", "merge-base", "--is-ancestor", a, b).pipe(provide$2(succeed$2(CommandExecutor, executor)), map$4(() => true), catchAll(() => succeed$3(false))),
|
|
50212
|
+
mergeBase: (a, b) => exec("git", "merge-base", a, b).pipe(map$4((s) => some(s.trim())), catchAll(() => succeed$3(none$4()))),
|
|
50213
|
+
defaultBranch: () => exec("git", "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD").pipe(map$4((s) => s.trim()), catchAll(() => succeed$3("")), map$4((name) => name === "" ? "main" : name)),
|
|
50209
50214
|
topLevel: () => exec("git", "rev-parse", "--show-toplevel").pipe(map$4((s) => s.trim())),
|
|
50210
50215
|
gitDir: () => exec("git", "rev-parse", "--absolute-git-dir").pipe(map$4((s) => s.trim())),
|
|
50211
50216
|
gitCommonDir: () => exec("git", "rev-parse", "--path-format=absolute", "--git-common-dir").pipe(map$4((s) => s.trim())),
|
|
@@ -58112,45 +58117,71 @@ const load = gen(function* () {
|
|
|
58112
58117
|
const { levels, compiled, decodeDiagnostics } = yield* readRc;
|
|
58113
58118
|
const module = yield* discovery.workflowModule(host.root, host.home);
|
|
58114
58119
|
const loaded = yield* try_({
|
|
58115
|
-
try: () =>
|
|
58120
|
+
try: () => loadWorkflows(module),
|
|
58116
58121
|
catch: (e) => new GtdError(`gtd config:\n - ${module?.filepath ?? "(built-in default)"}: ${e instanceof Error ? e.message : String(e)}`)
|
|
58117
58122
|
});
|
|
58118
58123
|
const layerOrder = [...levels.map((level) => level.filepath), ...module ? [module.filepath] : []];
|
|
58119
58124
|
const diagnostics = dedupeDiagnostics(sortDiagnostics([
|
|
58120
58125
|
...decodeDiagnostics,
|
|
58121
58126
|
...compiled.diagnostics,
|
|
58122
|
-
...wrongKindDiagnostics(loaded, compiled)
|
|
58127
|
+
...wrongKindDiagnostics(loaded.files, compiled)
|
|
58123
58128
|
], layerOrder));
|
|
58124
58129
|
yield* failOnErrors(diagnostics);
|
|
58125
|
-
const initial = yield* firstStep(loaded, headTree(yield* Workspace));
|
|
58130
|
+
const initial = yield* firstStep(loaded.default, headTree(yield* Workspace));
|
|
58131
|
+
const steering = Object.assign({}, ...loaded.files.map((f) => f.steering));
|
|
58132
|
+
const scopesOf = (file) => {
|
|
58133
|
+
const vars = resolveVars(file.defaults, compiled.rcVars, {}, host.env);
|
|
58134
|
+
try {
|
|
58135
|
+
return [...Object.keys(file.skills(vars)), ...Object.keys(file.access(vars))];
|
|
58136
|
+
} catch {
|
|
58137
|
+
return [];
|
|
58138
|
+
}
|
|
58139
|
+
};
|
|
58140
|
+
const define = (entry) => ({
|
|
58141
|
+
flow: entry.flow,
|
|
58142
|
+
summary: entry.file.summary,
|
|
58143
|
+
base: entry.file.base,
|
|
58144
|
+
steering,
|
|
58145
|
+
modes: compiled.modes,
|
|
58146
|
+
skills: entry.file.skills,
|
|
58147
|
+
configuredSkills: compiled.rcSkills,
|
|
58148
|
+
skillsKeys: compiled.skillsKeys,
|
|
58149
|
+
access: entry.file.access,
|
|
58150
|
+
configuredAccess: compiled.access,
|
|
58151
|
+
accessKeys: compiled.accessKeys,
|
|
58152
|
+
knownScopes: () => loaded.files.filter((f) => f !== entry.file).flatMap(scopesOf),
|
|
58153
|
+
skillsOrigin: entry.file.origin,
|
|
58154
|
+
initial
|
|
58155
|
+
});
|
|
58156
|
+
const names = [...loaded.catalogue.keys()].sort();
|
|
58157
|
+
const resolve = (name, entry) => ({
|
|
58158
|
+
name,
|
|
58159
|
+
def: define(entry),
|
|
58160
|
+
vars: { ...entry.file.defaults },
|
|
58161
|
+
env: { ...entry.file.envDefaults }
|
|
58162
|
+
});
|
|
58163
|
+
const defaultWorkflow = resolve(loaded.default.name, loaded.default);
|
|
58126
58164
|
return {
|
|
58127
|
-
workflow:
|
|
58128
|
-
|
|
58129
|
-
|
|
58130
|
-
|
|
58131
|
-
|
|
58132
|
-
|
|
58133
|
-
|
|
58134
|
-
configuredSkills: compiled.rcSkills,
|
|
58135
|
-
skillsKeys: compiled.skillsKeys,
|
|
58136
|
-
access: loaded.access,
|
|
58137
|
-
configuredAccess: compiled.access,
|
|
58138
|
-
accessKeys: compiled.accessKeys,
|
|
58139
|
-
skillsOrigin: module?.filepath ?? "(built-in default)",
|
|
58140
|
-
initial
|
|
58165
|
+
workflow: defaultWorkflow.def,
|
|
58166
|
+
defaultWorkflow,
|
|
58167
|
+
workflowNames: names,
|
|
58168
|
+
doors: loaded.doors,
|
|
58169
|
+
workflowNamed: (name) => {
|
|
58170
|
+
const entry = loaded.catalogue.get(name);
|
|
58171
|
+
return entry === void 0 ? void 0 : resolve(name, entry);
|
|
58141
58172
|
},
|
|
58142
|
-
workflowVars: { ...loaded.defaults },
|
|
58143
58173
|
rcVars: compiled.rcVars,
|
|
58144
|
-
workflowEnv: { ...loaded.envDefaults },
|
|
58145
58174
|
rcEnv: compiled.rcEnv,
|
|
58146
58175
|
...compiled.ui !== void 0 ? { ui: compiled.ui } : {},
|
|
58147
58176
|
warnings: diagnostics.filter((d) => d.severity === "warning"),
|
|
58148
58177
|
configFiles: levels.map((level) => level.filepath),
|
|
58149
|
-
workflowFiles: loaded.dependencyFiles
|
|
58178
|
+
workflowFiles: loaded.files.flatMap((f) => f.dependencyFiles)
|
|
58150
58179
|
};
|
|
58151
58180
|
});
|
|
58152
58181
|
/** A name under the wrong key (or under both) in a `.gtdrc` layer: each is an error at its own origin. */
|
|
58153
|
-
const wrongKindDiagnostics = (
|
|
58182
|
+
const wrongKindDiagnostics = (files, compiled) => {
|
|
58183
|
+
const isEnvOnly = (key) => files.some((f) => Object.hasOwn(f.envDefaults, key)) && !files.some((f) => Object.hasOwn(f.defaults, key));
|
|
58184
|
+
const isVarOnly = (key) => files.some((f) => Object.hasOwn(f.defaults, key)) && !files.some((f) => Object.hasOwn(f.envDefaults, key));
|
|
58154
58185
|
const error = (path, message, origin) => ({
|
|
58155
58186
|
severity: "error",
|
|
58156
58187
|
path,
|
|
@@ -58158,9 +58189,9 @@ const wrongKindDiagnostics = (loaded, compiled) => {
|
|
|
58158
58189
|
origin
|
|
58159
58190
|
});
|
|
58160
58191
|
const diagnostics = [];
|
|
58161
|
-
for (const { key, origin } of compiled.varsKeys) if (
|
|
58192
|
+
for (const { key, origin } of compiled.varsKeys) if (isEnvOnly(key)) diagnostics.push(error(["vars", key], `"vars.${key}" is an environment setting — move it under "env:"`, origin));
|
|
58162
58193
|
for (const { key, origin } of compiled.envKeys) {
|
|
58163
|
-
if (
|
|
58194
|
+
if (isVarOnly(key)) diagnostics.push(error(["env", key], `"env.${key}" is a process setting — move it under "vars:"`, origin));
|
|
58164
58195
|
if (compiled.varsKeys.some((v) => v.key === key)) diagnostics.push(error(["env", key], `"${key}" is declared under both "vars:" and "env:" — a setting is one kind or the other`, origin));
|
|
58165
58196
|
}
|
|
58166
58197
|
return diagnostics;
|
|
@@ -58188,7 +58219,7 @@ const jiti = () => {
|
|
|
58188
58219
|
return jitiModule.createJiti(import.meta.url, {
|
|
58189
58220
|
virtualModules: {
|
|
58190
58221
|
"@pmelab/gtd/flows": flows_exports,
|
|
58191
|
-
"@pmelab/gtd/workflow":
|
|
58222
|
+
"@pmelab/gtd/workflow": bundled_exports
|
|
58192
58223
|
},
|
|
58193
58224
|
fsCache: false,
|
|
58194
58225
|
moduleCache: false,
|
|
@@ -58225,14 +58256,19 @@ const optionalFunction = (exports, name) => {
|
|
|
58225
58256
|
if (typeof value !== "function") throw new Error(`the "${name}" export is not a function`);
|
|
58226
58257
|
return value;
|
|
58227
58258
|
};
|
|
58228
|
-
|
|
58229
|
-
|
|
58230
|
-
|
|
58231
|
-
|
|
58232
|
-
|
|
58233
|
-
|
|
58234
|
-
|
|
58235
|
-
|
|
58259
|
+
const RESERVED_EXPORTS = /* @__PURE__ */ new Set([
|
|
58260
|
+
"default",
|
|
58261
|
+
"summary",
|
|
58262
|
+
"base",
|
|
58263
|
+
"defaults",
|
|
58264
|
+
"envDefaults",
|
|
58265
|
+
"steering",
|
|
58266
|
+
"skills",
|
|
58267
|
+
"access",
|
|
58268
|
+
"doors"
|
|
58269
|
+
]);
|
|
58270
|
+
/** A module's per-file exports: `defaults`, `envDefaults`, `steering`, `skills`, `access`, `summary`, `base`. */
|
|
58271
|
+
const fileFrom = (exports, origin, dependencyFiles) => {
|
|
58236
58272
|
const defaults = stringRecord(exports, "defaults");
|
|
58237
58273
|
const envDefaults = stringRecord(exports, "envDefaults");
|
|
58238
58274
|
for (const [exportName, record] of [["defaults", defaults], ["envDefaults", envDefaults]]) {
|
|
@@ -58242,7 +58278,6 @@ const fromModule = (exported, origin, dependencyFiles) => {
|
|
|
58242
58278
|
const both = Object.keys(defaults).filter((name) => Object.hasOwn(envDefaults, name));
|
|
58243
58279
|
if (both.length > 0) throw new Error(`${both.map((name) => `"${name}"`).join(", ")} declared in both "defaults" and "envDefaults" — a setting is a process setting or an environment setting, not both`);
|
|
58244
58280
|
return {
|
|
58245
|
-
flow,
|
|
58246
58281
|
defaults,
|
|
58247
58282
|
envDefaults,
|
|
58248
58283
|
steering: stringRecord(exports, "steering"),
|
|
@@ -58254,23 +58289,120 @@ const fromModule = (exported, origin, dependencyFiles) => {
|
|
|
58254
58289
|
dependencyFiles
|
|
58255
58290
|
};
|
|
58256
58291
|
};
|
|
58292
|
+
const DOOR_NAME_RULE = /^[a-z][a-z0-9-]*$/;
|
|
58293
|
+
/** Why one door's `args` are malformed, or `undefined` when they are well-formed. */
|
|
58294
|
+
const argsProblem = (args) => {
|
|
58295
|
+
if (args === void 0) return void 0;
|
|
58296
|
+
if (!Array.isArray(args)) return `has "args" that is not an array`;
|
|
58297
|
+
const seen = /* @__PURE__ */ new Set();
|
|
58298
|
+
let optional = false;
|
|
58299
|
+
for (const arg of args) {
|
|
58300
|
+
if (typeof arg?.name !== "string") return `has an arg without a "name"`;
|
|
58301
|
+
if (seen.has(arg.name)) return `declares duplicate arg "${arg.name}"`;
|
|
58302
|
+
seen.add(arg.name);
|
|
58303
|
+
if (arg.optional === true) optional = true;
|
|
58304
|
+
else if (optional) return `declares required arg "${arg.name}" after an optional one`;
|
|
58305
|
+
}
|
|
58306
|
+
};
|
|
58307
|
+
/** Why one door is malformed, or `undefined` when it is well-formed. */
|
|
58308
|
+
const doorProblem = (name, door) => {
|
|
58309
|
+
if (!DOOR_NAME_RULE.test(name)) return `is not a valid door name — it must match ${DOOR_NAME_RULE}`;
|
|
58310
|
+
const { workflow, args, vars } = door ?? {};
|
|
58311
|
+
if (typeof workflow !== "string") return `must be an object with a "workflow" name`;
|
|
58312
|
+
const varsOk = vars === void 0 || typeof vars === "function";
|
|
58313
|
+
return argsProblem(args) ?? (varsOk ? void 0 : `has "vars" that is not a function`);
|
|
58314
|
+
};
|
|
58315
|
+
/** A module's `doors` export, shape-checked; the workflow names are checked later, against the merged catalogue. */
|
|
58316
|
+
const doorsFrom = (exports) => {
|
|
58317
|
+
const value = exports["doors"];
|
|
58318
|
+
if (value === void 0) return /* @__PURE__ */ new Map();
|
|
58319
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`the "doors" export is not a record of doors`);
|
|
58320
|
+
for (const [name, door] of Object.entries(value)) {
|
|
58321
|
+
const problem = doorProblem(name, door);
|
|
58322
|
+
if (problem !== void 0) throw new Error(`door "${name}" ${problem}`);
|
|
58323
|
+
}
|
|
58324
|
+
return new Map(Object.entries(value));
|
|
58325
|
+
};
|
|
58326
|
+
/** Merge bundled and repo doors (repo wins), then check each names a workflow the merged catalogue holds. */
|
|
58327
|
+
const mergeDoors = (bundled, repo, catalogue) => {
|
|
58328
|
+
const doors = new Map([...bundled, ...repo].sort(([a], [b]) => a.localeCompare(b)));
|
|
58329
|
+
for (const [name, door] of doors) if (!catalogue.has(door.workflow)) throw new Error(`door "${name}" names workflow "${door.workflow}", which is not startable — known: ${[...catalogue.keys()].sort().join(", ")}`);
|
|
58330
|
+
return doors;
|
|
58331
|
+
};
|
|
58332
|
+
const namespaceOf = (exported) => typeof exported === "object" && exported !== null ? exported : {};
|
|
58333
|
+
/**
|
|
58334
|
+
* The bundled module is never scanned: its helper exports stay helpers, and
|
|
58335
|
+
* exactly these three are startable.
|
|
58336
|
+
*/
|
|
58337
|
+
const loadBundled = () => {
|
|
58338
|
+
const file = fileFrom(namespaceOf(bundled_exports), BUILT_IN_ORIGIN, []);
|
|
58339
|
+
const { feature, review, fix } = bundled_exports;
|
|
58340
|
+
return {
|
|
58341
|
+
file,
|
|
58342
|
+
doors: doorsFrom(namespaceOf(bundled_exports)),
|
|
58343
|
+
catalogue: /* @__PURE__ */ new Map([
|
|
58344
|
+
["feature", {
|
|
58345
|
+
flow: feature,
|
|
58346
|
+
file
|
|
58347
|
+
}],
|
|
58348
|
+
["review", {
|
|
58349
|
+
flow: review,
|
|
58350
|
+
file
|
|
58351
|
+
}],
|
|
58352
|
+
["fix", {
|
|
58353
|
+
flow: fix,
|
|
58354
|
+
file
|
|
58355
|
+
}]
|
|
58356
|
+
])
|
|
58357
|
+
};
|
|
58358
|
+
};
|
|
58257
58359
|
/**
|
|
58258
|
-
* Evaluate `gtd.config.ts
|
|
58259
|
-
*
|
|
58260
|
-
*
|
|
58261
|
-
*
|
|
58262
|
-
* collecting every real file jiti resolved evaluating this
|
|
58263
|
-
* AND every relative import it pulled in, transitively.
|
|
58264
|
-
* (`@pmelab/gtd/flows`) never touch it — no real file backs them.
|
|
58360
|
+
* Evaluate `gtd.config.ts` and overlay it on the bundled workflows. Every
|
|
58361
|
+
* exported function of the repo file but the reserved names is a workflow
|
|
58362
|
+
* named by its export. `cache` is jiti's OWN per-call module cache (unrelated
|
|
58363
|
+
* to the instance's `moduleCache: false`) — passing an empty object in has
|
|
58364
|
+
* the side effect of collecting every real file jiti resolved evaluating this
|
|
58365
|
+
* module, module AND every relative import it pulled in, transitively.
|
|
58265
58366
|
*/
|
|
58266
|
-
const
|
|
58267
|
-
|
|
58367
|
+
const loadWorkflows = (module) => {
|
|
58368
|
+
const bundled = loadBundled();
|
|
58369
|
+
const bundledDefault = {
|
|
58370
|
+
flow: feature,
|
|
58371
|
+
file: bundled.file,
|
|
58372
|
+
name: "feature"
|
|
58373
|
+
};
|
|
58374
|
+
if (module === void 0) return {
|
|
58375
|
+
files: [bundled.file],
|
|
58376
|
+
catalogue: bundled.catalogue,
|
|
58377
|
+
doors: mergeDoors(bundled.doors, /* @__PURE__ */ new Map(), bundled.catalogue),
|
|
58378
|
+
default: bundledDefault
|
|
58379
|
+
};
|
|
58268
58380
|
const cache = {};
|
|
58269
|
-
const
|
|
58381
|
+
const exports = namespaceOf(jiti().evalModule(module.source, {
|
|
58270
58382
|
filename: module.filepath,
|
|
58271
58383
|
cache
|
|
58272
|
-
});
|
|
58273
|
-
|
|
58384
|
+
}));
|
|
58385
|
+
const file = fileFrom(exports, module.filepath, Object.keys(cache));
|
|
58386
|
+
const catalogue = new Map(bundled.catalogue);
|
|
58387
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
58388
|
+
if (RESERVED_EXPORTS.has(name) || typeof value !== "function") continue;
|
|
58389
|
+
catalogue.set(name, {
|
|
58390
|
+
flow: value,
|
|
58391
|
+
file
|
|
58392
|
+
});
|
|
58393
|
+
}
|
|
58394
|
+
const flow = exports.default;
|
|
58395
|
+
if (flow !== void 0 && typeof flow !== "function") throw new Error("the default export is not a function");
|
|
58396
|
+
return {
|
|
58397
|
+
files: [bundled.file, file],
|
|
58398
|
+
catalogue,
|
|
58399
|
+
doors: mergeDoors(bundled.doors, doorsFrom(exports), catalogue),
|
|
58400
|
+
default: flow === void 0 ? bundledDefault : {
|
|
58401
|
+
flow,
|
|
58402
|
+
file,
|
|
58403
|
+
name: "default"
|
|
58404
|
+
}
|
|
58405
|
+
};
|
|
58274
58406
|
};
|
|
58275
58407
|
const headTree = (workspace) => {
|
|
58276
58408
|
let entries;
|
|
@@ -58326,7 +58458,6 @@ const settingReads = (kind, watch) => {
|
|
|
58326
58458
|
const replayToFirstStep = (loaded, vars, env, tree) => flatMap$4(promise(() => replay({
|
|
58327
58459
|
flow: loaded.flow,
|
|
58328
58460
|
episode: {
|
|
58329
|
-
entry: void 0,
|
|
58330
58461
|
base: {
|
|
58331
58462
|
hash: "",
|
|
58332
58463
|
tree
|
|
@@ -58340,7 +58471,7 @@ const replayToFirstStep = (loaded, vars, env, tree) => flatMap$4(promise(() => r
|
|
|
58340
58471
|
})), (outcome) => {
|
|
58341
58472
|
if (outcome.kind === "rest") return succeed$3(outcome.rest.name);
|
|
58342
58473
|
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";
|
|
58343
|
-
return fail$7(new GtdError(`gtd config:\n - ${loaded.origin}: ${problem}`));
|
|
58474
|
+
return fail$7(new GtdError(`gtd config:\n - ${loaded.file.origin}: ${problem}`));
|
|
58344
58475
|
});
|
|
58345
58476
|
/**
|
|
58346
58477
|
* The default entry's first step — where a finished process waits — found the
|
|
@@ -58356,13 +58487,13 @@ const firstStep = (loaded, head) => gen(function* () {
|
|
|
58356
58487
|
const env = watchedEmptySettings();
|
|
58357
58488
|
const outcome = yield* either(replayToFirstStep(loaded, vars.settings, env.settings, empty.tree));
|
|
58358
58489
|
const reads = [...settingReads("process", vars), ...settingReads("environment", env)];
|
|
58359
|
-
if (reads.length > 0) return yield* fail$7(new GtdError(`gtd config:\n - ${loaded.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`));
|
|
58490
|
+
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`));
|
|
58360
58491
|
if (outcome._tag === "Left") return yield* fail$7(outcome.left);
|
|
58361
58492
|
const name = outcome.right;
|
|
58362
58493
|
if (!empty.read()) return name;
|
|
58363
58494
|
const atHead = yield* replayToFirstStep(loaded, vars.settings, env.settings, head);
|
|
58364
58495
|
if (atHead === name) return name;
|
|
58365
|
-
return yield* fail$7(new GtdError(`gtd config:\n - ${loaded.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`));
|
|
58496
|
+
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`));
|
|
58366
58497
|
});
|
|
58367
58498
|
var ConfigService = class ConfigService extends Tag("ConfigService")() {
|
|
58368
58499
|
static Live = succeed$2(ConfigService, { load });
|
|
@@ -58401,6 +58532,7 @@ const checkKeys = (keys, known, message, section) => {
|
|
|
58401
58532
|
origin
|
|
58402
58533
|
})), [...new Set(keys.map((k) => k.origin))])));
|
|
58403
58534
|
};
|
|
58535
|
+
const knownTo = (def, own) => [.../* @__PURE__ */ new Set([...own, ...def.knownScopes?.() ?? []])];
|
|
58404
58536
|
/**
|
|
58405
58537
|
* Skill lists for the settings actually in use. `.gtdrc` `skills:` keys are
|
|
58406
58538
|
* judged here, not at config load: only now are the pinned/entry vars known.
|
|
@@ -58410,7 +58542,8 @@ const resolveScopeSkills = (def, vars) => gen(function* () {
|
|
|
58410
58542
|
try: () => checkShape(def.skills(vars)),
|
|
58411
58543
|
catch: (e) => fail$4(def.skillsOrigin, e instanceof Error ? e.message : String(e))
|
|
58412
58544
|
});
|
|
58413
|
-
|
|
58545
|
+
const known = knownTo(def, Object.keys(skills));
|
|
58546
|
+
yield* checkKeys(def.skillsKeys, known, unknownSkillsKeyMessage, "skills");
|
|
58414
58547
|
return skills;
|
|
58415
58548
|
});
|
|
58416
58549
|
const checkAccessShape = (value) => {
|
|
@@ -58430,7 +58563,8 @@ const resolveScopeAccess = (def, skills, vars) => gen(function* () {
|
|
|
58430
58563
|
try: () => checkAccessShape(def.access(vars)),
|
|
58431
58564
|
catch: (e) => fail$4(def.skillsOrigin, e instanceof Error ? e.message : String(e))
|
|
58432
58565
|
});
|
|
58433
|
-
|
|
58566
|
+
const known = knownTo(def, [...Object.keys(skills), ...Object.keys(access)]);
|
|
58567
|
+
yield* checkKeys(def.accessKeys, known, unknownAccessKeyMessage, "access");
|
|
58434
58568
|
return access;
|
|
58435
58569
|
});
|
|
58436
58570
|
//#endregion
|
|
@@ -63819,14 +63953,16 @@ const demandOf = (input) => {
|
|
|
63819
63953
|
* instead of failing loudly.
|
|
63820
63954
|
*/
|
|
63821
63955
|
const statusOf = (input) => {
|
|
63822
|
-
const { rendered, idle, log, changes, next, cost, costByModel } = input;
|
|
63956
|
+
const { rendered, idle, initial, workflow, log, changes, next, cost, costByModel } = input;
|
|
63823
63957
|
return {
|
|
63824
63958
|
idle,
|
|
63959
|
+
initial,
|
|
63825
63960
|
model: rendered.model,
|
|
63826
63961
|
system: rendered.system !== void 0 && rendered.system !== "" ? rendered.system : void 0,
|
|
63827
63962
|
log,
|
|
63828
63963
|
state: rendered.state,
|
|
63829
63964
|
actor: rendered.actor,
|
|
63965
|
+
workflow,
|
|
63830
63966
|
label: rendered.label,
|
|
63831
63967
|
memory: rendered.memory,
|
|
63832
63968
|
file: rendered.file,
|
|
@@ -63875,7 +64011,7 @@ const dispatchFieldsOf = (demand) => {
|
|
|
63875
64011
|
}
|
|
63876
64012
|
};
|
|
63877
64013
|
/**
|
|
63878
|
-
* Flatten a `Demand` plus its `BeatStatus` into the single
|
|
64014
|
+
* Flatten a `Demand` plus its `BeatStatus` into the single 23-key document
|
|
63879
64015
|
* `gtd next --json` emits — the ONLY place the two are joined. `cost`/
|
|
63880
64016
|
* `costByModel` are omitted together, exactly when no cost was recorded
|
|
63881
64017
|
* (`cost <= 0`).
|
|
@@ -63887,6 +64023,7 @@ const beatDocument = (demand, status) => {
|
|
|
63887
64023
|
kind: demand.kind,
|
|
63888
64024
|
content: demand.content,
|
|
63889
64025
|
idle: status.idle,
|
|
64026
|
+
initial: status.initial,
|
|
63890
64027
|
session,
|
|
63891
64028
|
model: status.model,
|
|
63892
64029
|
system: status.system,
|
|
@@ -63894,6 +64031,7 @@ const beatDocument = (demand, status) => {
|
|
|
63894
64031
|
log: status.log,
|
|
63895
64032
|
state: status.state,
|
|
63896
64033
|
actor: status.actor,
|
|
64034
|
+
workflow: status.workflow,
|
|
63897
64035
|
label: status.label,
|
|
63898
64036
|
memory: status.memory,
|
|
63899
64037
|
file: status.file,
|
|
@@ -63932,7 +64070,7 @@ const definedFields = (entries) => {
|
|
|
63932
64070
|
return result;
|
|
63933
64071
|
};
|
|
63934
64072
|
/**
|
|
63935
|
-
* The header block's own lines — `State:`/`Awaits:`/optional `Label:`/
|
|
64073
|
+
* The header block's own lines — `State:`/`Awaits:`/`Workflow:`/optional `Label:`/
|
|
63936
64074
|
* `Model:`/`Memory:`/`File:`/`Mode:`/`Cost:`(+breakdown)/`Pending:`/`Next:`,
|
|
63937
64075
|
* in that order. Used by `renderBeatPlain`'s header-showing kinds below.
|
|
63938
64076
|
*/
|
|
@@ -63947,6 +64085,7 @@ const beatHeaderLines = (document) => {
|
|
|
63947
64085
|
return [
|
|
63948
64086
|
`State: ${document.state}`,
|
|
63949
64087
|
`Awaits: ${document.actor}`,
|
|
64088
|
+
`Workflow: ${document.workflow}`,
|
|
63950
64089
|
...Object.entries(optional).map(([key, value]) => `${key}: ${value}`),
|
|
63951
64090
|
...costStatusLines(document.cost ?? 0, document.costByModel ?? []),
|
|
63952
64091
|
...pendingStatusLines(document.changes),
|
|
@@ -64148,7 +64287,7 @@ const accessRefusal = (changes, write) => {
|
|
|
64148
64287
|
].join("\n");
|
|
64149
64288
|
};
|
|
64150
64289
|
//#endregion
|
|
64151
|
-
//#region src/step/
|
|
64290
|
+
//#region src/step/planStart.ts
|
|
64152
64291
|
const refusal = (message) => ({
|
|
64153
64292
|
kind: "refusal",
|
|
64154
64293
|
message
|
|
@@ -64160,43 +64299,42 @@ const checkOverrides = (commandLabel, varOverrides, declaredNames, envNames) =>
|
|
|
64160
64299
|
if (undeclared.length > 0) return refusal(`${commandLabel}: --var name(s) not declared by this workflow: ${undeclared.join(", ")} — declared: ${declaredNames.length > 0 ? declaredNames.join(", ") : "(none)"}`);
|
|
64161
64300
|
});
|
|
64162
64301
|
/**
|
|
64163
|
-
* `gtd --
|
|
64164
|
-
*
|
|
64165
|
-
* `Gtd-
|
|
64302
|
+
* `gtd --workflow <name>`: start a brand NEW process on a workflow, writing an
|
|
64303
|
+
* opening commit that names the workflow's first step and carries a
|
|
64304
|
+
* `Gtd-Workflow:` trailer, every process setting as a `Gtd-Var:` trailer, plus
|
|
64305
|
+
* a `Gtd-Review-Base:` trailer when the workflow fixes the process's diff base.
|
|
64166
64306
|
* All validation is a REFUSAL, not an Effect failure.
|
|
64167
64307
|
*/
|
|
64168
|
-
const
|
|
64169
|
-
const {
|
|
64170
|
-
|
|
64171
|
-
if (current.
|
|
64308
|
+
const planStart = (current, actor, start) => gen(function* () {
|
|
64309
|
+
const { workflow, first, commandLabel, vars: varOverrides } = start;
|
|
64310
|
+
const name = workflow.name;
|
|
64311
|
+
if (!current.idle) return refusal(`${commandLabel}: a process is already underway (resting at "${current.state}") — finish it, or run \`gtd abandon\`, before starting another`);
|
|
64312
|
+
if ("refusal" in first) return refusal(`${commandLabel}: ${first.refusal}`);
|
|
64313
|
+
const { firstStep } = first;
|
|
64172
64314
|
const config = yield* (yield* ConfigService).load;
|
|
64173
64315
|
const declaredNames = Object.keys({
|
|
64174
|
-
...
|
|
64316
|
+
...workflow.vars,
|
|
64175
64317
|
...config.rcVars
|
|
64176
64318
|
});
|
|
64177
64319
|
const overrideRefusal = yield* checkOverrides(commandLabel, varOverrides, declaredNames, Object.keys({
|
|
64178
|
-
...
|
|
64320
|
+
...workflow.env,
|
|
64179
64321
|
...config.rcEnv
|
|
64180
64322
|
}));
|
|
64181
64323
|
if (overrideRefusal !== void 0) return overrideRefusal;
|
|
64182
|
-
|
|
64183
|
-
const
|
|
64184
|
-
const vars = resolveVars(config.workflowVars, config.rcVars, varOverrides, (yield* Host).env);
|
|
64324
|
+
const baseOf = workflow.def.base;
|
|
64325
|
+
const vars = resolveVars(workflow.vars, config.rcVars, varOverrides, (yield* Host).env);
|
|
64185
64326
|
const multiline = multilineSetting(vars);
|
|
64186
64327
|
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`);
|
|
64187
64328
|
const template = yield* try_({
|
|
64188
64329
|
try: () => baseOf?.(name, vars),
|
|
64189
64330
|
catch: (e) => /* @__PURE__ */ new Error(`${commandLabel}: ${e instanceof Error ? e.message : String(e)}`)
|
|
64190
64331
|
});
|
|
64191
|
-
|
|
64192
|
-
|
|
64193
|
-
|
|
64194
|
-
base = resolved;
|
|
64195
|
-
}
|
|
64196
|
-
const subject = formatSubject(actor, name);
|
|
64332
|
+
const base = template === void 0 ? void 0 : yield* resolveBase(commandLabel, template);
|
|
64333
|
+
if (typeof base === "object") return base;
|
|
64334
|
+
const subject = formatSubject(actor, firstStep);
|
|
64197
64335
|
return {
|
|
64198
|
-
kind: "
|
|
64199
|
-
|
|
64336
|
+
kind: "start",
|
|
64337
|
+
workflow: name,
|
|
64200
64338
|
subject,
|
|
64201
64339
|
steps: [{
|
|
64202
64340
|
kind: "gitWrite",
|
|
@@ -64204,7 +64342,8 @@ const planEntry = (current, actor, entry) => gen(function* () {
|
|
|
64204
64342
|
kind: "commitAll",
|
|
64205
64343
|
message: formatCommitMessage({
|
|
64206
64344
|
actor,
|
|
64207
|
-
to:
|
|
64345
|
+
to: firstStep,
|
|
64346
|
+
workflow: name,
|
|
64208
64347
|
...base !== void 0 ? { reviewBase: base } : {},
|
|
64209
64348
|
vars
|
|
64210
64349
|
})
|
|
@@ -64218,16 +64357,21 @@ const planEntry = (current, actor, entry) => gen(function* () {
|
|
|
64218
64357
|
}]
|
|
64219
64358
|
};
|
|
64220
64359
|
});
|
|
64221
|
-
/**
|
|
64222
|
-
|
|
64223
|
-
|
|
64224
|
-
|
|
64360
|
+
/**
|
|
64361
|
+
* The workflow's diff base: `git merge-base <base> HEAD`, where a blank base
|
|
64362
|
+
* means the default branch. The merge-base is what gets pinned, so a base on
|
|
64363
|
+
* another branch reviews exactly this branch's own commits.
|
|
64364
|
+
*/
|
|
64365
|
+
const resolveBase = (commandLabel, value) => gen(function* () {
|
|
64225
64366
|
const git = yield* GitService;
|
|
64226
|
-
const
|
|
64227
|
-
|
|
64228
|
-
|
|
64229
|
-
if (resolvedBase.
|
|
64230
|
-
|
|
64367
|
+
const rendered = value.trim();
|
|
64368
|
+
const target = rendered === "" ? yield* git.defaultBranch() : rendered;
|
|
64369
|
+
const resolvedBase = yield* either(git.resolveRef(target));
|
|
64370
|
+
if (resolvedBase._tag === "Left") return refusal(`${commandLabel}: "${target}" does not resolve to a commit`);
|
|
64371
|
+
const mergeBase = yield* git.mergeBase(resolvedBase.right, "HEAD");
|
|
64372
|
+
if (isNone(mergeBase)) return refusal(`${commandLabel}: "${target}" shares no common ancestor with HEAD`);
|
|
64373
|
+
if (mergeBase.value === (yield* git.resolveRef("HEAD"))) return refusal(`${commandLabel}: nothing to review: HEAD has no commits beyond ${target}`);
|
|
64374
|
+
return mergeBase.value;
|
|
64231
64375
|
});
|
|
64232
64376
|
//#endregion
|
|
64233
64377
|
//#region src/Edge.ts
|
|
@@ -64240,9 +64384,10 @@ const ACTORS = /* @__PURE__ */ new Set([
|
|
|
64240
64384
|
]);
|
|
64241
64385
|
/**
|
|
64242
64386
|
* Where the episode HEAD belongs to begins, read off subjects alone. Walking
|
|
64243
|
-
* back from HEAD: a
|
|
64244
|
-
* `gtd --
|
|
64245
|
-
* episode as its first commit
|
|
64387
|
+
* back from HEAD: a bare `gtd(human): <step>` commit with no `Gtd-Step` is what
|
|
64388
|
+
* `gtd --workflow` writes — no landing ever produces one — and opens an
|
|
64389
|
+
* episode as its first commit (its `Gtd-Workflow` trailer names the workflow;
|
|
64390
|
+
* a legacy `--entry` opening has none); a commit that is not a gtd step commit, or one
|
|
64246
64391
|
* entering the flow's first step (a finished episode), bounds the episode
|
|
64247
64392
|
* from below.
|
|
64248
64393
|
*/
|
|
@@ -64251,18 +64396,21 @@ const locateEpisode = (def, history) => {
|
|
|
64251
64396
|
const message = parseCommitMessage(history[i].message);
|
|
64252
64397
|
const subject = message.parsed;
|
|
64253
64398
|
if (subject !== void 0 && message.step === void 0 && subject.from === void 0 && subject.actor === "human") return {
|
|
64254
|
-
|
|
64399
|
+
workflow: message.workflow,
|
|
64400
|
+
legacyOpening: message.workflow === void 0,
|
|
64255
64401
|
baseIndex: i,
|
|
64256
64402
|
processStart: i
|
|
64257
64403
|
};
|
|
64258
64404
|
if (subject === void 0 || !ACTORS.has(subject.actor) || subject.to === def.initial) return {
|
|
64259
|
-
|
|
64405
|
+
workflow: void 0,
|
|
64406
|
+
legacyOpening: false,
|
|
64260
64407
|
baseIndex: i,
|
|
64261
64408
|
processStart: i + 1
|
|
64262
64409
|
};
|
|
64263
64410
|
}
|
|
64264
64411
|
return {
|
|
64265
|
-
|
|
64412
|
+
workflow: void 0,
|
|
64413
|
+
legacyOpening: false,
|
|
64266
64414
|
baseIndex: -1,
|
|
64267
64415
|
processStart: 0
|
|
64268
64416
|
};
|
|
@@ -64296,7 +64444,8 @@ const runOf = (history, location, closingHash = void 0) => {
|
|
|
64296
64444
|
const startParentHash = hashAt(history, location.processStart - 1) ?? EMPTY_TREE$1;
|
|
64297
64445
|
const head = history[history.length - 1];
|
|
64298
64446
|
return {
|
|
64299
|
-
|
|
64447
|
+
workflow: location.workflow,
|
|
64448
|
+
legacyOpening: location.legacyOpening,
|
|
64300
64449
|
startHash: hashAt(history, location.processStart) ?? head?.hash ?? EMPTY_TREE$1,
|
|
64301
64450
|
startParentHash,
|
|
64302
64451
|
diffBase: first?.reviewBase ?? startParentHash,
|
|
@@ -64323,10 +64472,10 @@ const PAGE_TIERS = [
|
|
|
64323
64472
|
* gtd step commit, or one landing the flow's initial state, bounds an
|
|
64324
64473
|
* episode from below — EXCEPT a bare `gtd(human): <x>` subject (no `→`),
|
|
64325
64474
|
* which is NEVER a boundary here, whatever `<x>` is. That bare shape is
|
|
64326
|
-
* `locateEpisode`'s own `opening` branch (
|
|
64475
|
+
* `locateEpisode`'s own `opening` branch (a started episode's first commit),
|
|
64327
64476
|
* which takes priority there over the initial-state check no matter what the
|
|
64328
|
-
*
|
|
64329
|
-
* where the two branches would otherwise disagree. A bare human commit can
|
|
64477
|
+
* workflow's first step is named — including one named after `def.initial`
|
|
64478
|
+
* itself, where the two branches would otherwise disagree. A bare human commit can
|
|
64330
64479
|
* also be a self-loop step landing rather than a real opening (this
|
|
64331
64480
|
* subject-only pass can't read the `Gtd-Step` trailer that would tell them
|
|
64332
64481
|
* apart), so ruling it out here can only make `findBoundaryBase` walk a FEW
|
|
@@ -64462,16 +64611,24 @@ const pendingTree = (workspace, entries, rewrite) => {
|
|
|
64462
64611
|
* The one resolver of both setting kinds. Process settings: the recorded
|
|
64463
64612
|
* (pinned) value wins per name, anything unrecorded resolves live — the
|
|
64464
64613
|
* fallback for a process started before pinning existed. Environment settings
|
|
64465
|
-
* are always live. `
|
|
64466
|
-
* process is being
|
|
64614
|
+
* are always live. `startOverrides` are `--var` values, passed only while a
|
|
64615
|
+
* process is being started.
|
|
64467
64616
|
*/
|
|
64468
|
-
const settingsFor = (config, pinnedVars, hostEnv,
|
|
64617
|
+
const settingsFor = (config, workflow, pinnedVars, hostEnv, startOverrides = {}) => ({
|
|
64469
64618
|
vars: {
|
|
64470
|
-
...resolveVars(
|
|
64619
|
+
...resolveVars(workflow.vars, config.rcVars, startOverrides, hostEnv),
|
|
64471
64620
|
...pinnedVars
|
|
64472
64621
|
},
|
|
64473
|
-
env: resolveVars(
|
|
64622
|
+
env: resolveVars(workflow.env, config.rcEnv, {}, hostEnv)
|
|
64474
64623
|
});
|
|
64624
|
+
/** The workflow a run replays on: its pinned one, else the default. */
|
|
64625
|
+
const workflowOf = (config, run) => {
|
|
64626
|
+
if (run.legacyOpening) return fail$7(/* @__PURE__ */ new Error("gtd: this process was opened by `gtd --entry`, which no longer exists — run `gtd abandon` to start over"));
|
|
64627
|
+
if (run.workflow === void 0) return succeed$3(config.defaultWorkflow);
|
|
64628
|
+
const named = config.workflowNamed(run.workflow);
|
|
64629
|
+
if (named !== void 0) return succeed$3(named);
|
|
64630
|
+
return fail$7(/* @__PURE__ */ new Error(`gtd: this process runs workflow "${run.workflow}", which gtd.config.ts no longer defines — run \`gtd abandon\` to start over`));
|
|
64631
|
+
};
|
|
64475
64632
|
/**
|
|
64476
64633
|
* `judgeBudgetBytes` is the one var that refuses rather than disabling its
|
|
64477
64634
|
* mechanism when blanked: without a bound the judge payload is rejected
|
|
@@ -64488,7 +64645,6 @@ const judgeBudgetBytes = (vars) => {
|
|
|
64488
64645
|
const replayFor = (setup, pending) => replay({
|
|
64489
64646
|
flow: setup.def.flow,
|
|
64490
64647
|
episode: {
|
|
64491
|
-
entry: setup.run.entry,
|
|
64492
64648
|
base: setup.base,
|
|
64493
64649
|
commits: setup.episode
|
|
64494
64650
|
},
|
|
@@ -64506,7 +64662,7 @@ const replayFor = (setup, pending) => replay({
|
|
|
64506
64662
|
const replayError = (outcome) => {
|
|
64507
64663
|
if (outcome.kind === "divergence" || outcome.kind === "failed") return new Error(outcome.message);
|
|
64508
64664
|
if (outcome.kind === "refused") return /* @__PURE__ */ new Error(`gtd: ${outcome.message}`);
|
|
64509
|
-
if (outcome.kind === "ended") return /* @__PURE__ */ new Error("gtd: history ends the episode, but HEAD does not enter the default
|
|
64665
|
+
if (outcome.kind === "ended") return /* @__PURE__ */ new Error("gtd: history ends the episode, but HEAD does not enter the default workflow's first step — the workflow changed under this process; run `gtd abandon` to start over");
|
|
64510
64666
|
};
|
|
64511
64667
|
const judgeDocument = (step) => step.request.kind === "judge" ? JSON.stringify({
|
|
64512
64668
|
state: step.request.evidence,
|
|
@@ -64633,14 +64789,15 @@ const costByModel = (entries) => {
|
|
|
64633
64789
|
})).sort((a, b) => b.cost - a.cost || a.model.localeCompare(b.model));
|
|
64634
64790
|
};
|
|
64635
64791
|
/** Resolve the rest at `ref`, or at HEAD when `ref` is `undefined`. */
|
|
64636
|
-
const restAt = (ref,
|
|
64792
|
+
const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
64637
64793
|
const git = yield* GitService;
|
|
64638
64794
|
const config = yield* (yield* ConfigService).load;
|
|
64639
64795
|
const workspace = yield* Workspace;
|
|
64640
64796
|
const host = yield* Host;
|
|
64641
|
-
const
|
|
64642
|
-
const
|
|
64643
|
-
const
|
|
64797
|
+
const run = yield* computeProcessRun(git, config.workflow, ref);
|
|
64798
|
+
const resolved = yield* workflowOf(config, run);
|
|
64799
|
+
const def = resolved.def;
|
|
64800
|
+
const { vars, env } = settingsFor(config, resolved, run.pinnedVars, host.env, startOverrides);
|
|
64644
64801
|
const skills = yield* resolveScopeSkills(def, vars);
|
|
64645
64802
|
const access = yield* resolveScopeAccess(def, skills, vars);
|
|
64646
64803
|
const budget = yield* try_({
|
|
@@ -64688,6 +64845,7 @@ const restAt = (ref, entryOverrides = {}) => gen(function* () {
|
|
|
64688
64845
|
const memory = memoryOf(outcome.trace, run);
|
|
64689
64846
|
return {
|
|
64690
64847
|
def,
|
|
64848
|
+
workflowName: resolved.name,
|
|
64691
64849
|
state: step.name,
|
|
64692
64850
|
stepDef,
|
|
64693
64851
|
actor: step.actor,
|
|
@@ -64709,21 +64867,20 @@ const restAt = (ref, entryOverrides = {}) => gen(function* () {
|
|
|
64709
64867
|
});
|
|
64710
64868
|
const currentRest = restAt(void 0);
|
|
64711
64869
|
/**
|
|
64712
|
-
*
|
|
64713
|
-
*
|
|
64714
|
-
* the opening commit
|
|
64870
|
+
* Whether `gtd --workflow <name>` can start a process here: the workflow must
|
|
64871
|
+
* reach a step from the tree the opening commit would capture. That step is
|
|
64872
|
+
* what the opening commit's subject names.
|
|
64715
64873
|
*/
|
|
64716
|
-
const
|
|
64874
|
+
const startRefusal = (rest, resolved, startVars) => gen(function* () {
|
|
64717
64875
|
const config = yield* (yield* ConfigService).load;
|
|
64718
64876
|
const host = yield* Host;
|
|
64719
|
-
const { vars, env } = settingsFor(config, {}, host.env,
|
|
64720
|
-
const skills = yield* resolveScopeSkills(
|
|
64721
|
-
const access = yield* resolveScopeAccess(
|
|
64877
|
+
const { vars, env } = settingsFor(config, resolved, {}, host.env, startVars);
|
|
64878
|
+
const skills = yield* resolveScopeSkills(resolved.def, vars);
|
|
64879
|
+
const access = yield* resolveScopeAccess(resolved.def, skills, vars);
|
|
64722
64880
|
const workspace = rest.setup.workspace;
|
|
64723
64881
|
const outcome = yield* promise(() => replay({
|
|
64724
|
-
flow:
|
|
64882
|
+
flow: resolved.def.flow,
|
|
64725
64883
|
episode: {
|
|
64726
|
-
entry: name,
|
|
64727
64884
|
base: {
|
|
64728
64885
|
hash: "",
|
|
64729
64886
|
tree: pendingTree(workspace, rest.setup.pendingWorktree(), void 0)
|
|
@@ -64735,13 +64892,14 @@ const entryRefusal = (rest, name, entryVars) => gen(function* () {
|
|
|
64735
64892
|
start: "",
|
|
64736
64893
|
budgetBytes: rest.setup.budget,
|
|
64737
64894
|
skills,
|
|
64738
|
-
configuredSkills:
|
|
64895
|
+
configuredSkills: resolved.def.configuredSkills,
|
|
64739
64896
|
access,
|
|
64740
|
-
configuredAccess:
|
|
64897
|
+
configuredAccess: resolved.def.configuredAccess
|
|
64741
64898
|
}));
|
|
64742
|
-
if (outcome.kind === "
|
|
64743
|
-
if (outcome.kind === "
|
|
64744
|
-
|
|
64899
|
+
if (outcome.kind === "rest") return { firstStep: outcome.rest.name };
|
|
64900
|
+
if (outcome.kind === "refused") return { refusal: outcome.message };
|
|
64901
|
+
const why = outcome.kind === "ended" ? "the flow reaches no step" : outcome.message;
|
|
64902
|
+
return { refusal: `workflow "${resolved.name}" cannot start — ${why}` };
|
|
64745
64903
|
});
|
|
64746
64904
|
/** The review window's diff base at the rest. */
|
|
64747
64905
|
const reviewBaseFor = (rest) => rest.step.request.options.base ?? rest.run.diffBase;
|
|
@@ -64759,8 +64917,8 @@ const renderRest = (rest) => succeed$3({
|
|
|
64759
64917
|
* attempt at this very agent rest, and another dispatch would repeat it.
|
|
64760
64918
|
*/
|
|
64761
64919
|
const stalledAt = (rest) => rest.changes.length === 0 && rest.stepDef.kind === "prompt" && rest.stepDef.allowEmpty !== true && rest.run.headTurn?.state === rest.state && rest.run.headTurn.actor === rest.actor && !rest.run.headTurn.step && rest.run.headTurn.empty;
|
|
64762
|
-
/** No process is underway: the default
|
|
64763
|
-
const noProcessUnderway = (rest) => rest.run.
|
|
64920
|
+
/** No process is underway: the default workflow's first step, first visit — a dirty tree there is a turn not yet landed, not a process. */
|
|
64921
|
+
const noProcessUnderway = (rest) => rest.run.workflow === void 0 && rest.state === rest.def.initial && rest.step.id.occurrence === 1;
|
|
64764
64922
|
/** `idle` means exactly one thing: no process underway, clean tree. */
|
|
64765
64923
|
const restIsIdle = (rest) => noProcessUnderway(rest) && rest.changes.length === 0;
|
|
64766
64924
|
const isHumanReviewGate = (def) => def.actor === "human" && def.mode === "review";
|
|
@@ -64799,8 +64957,19 @@ const landingTarget = (outcome, rest) => {
|
|
|
64799
64957
|
message: outcome.message
|
|
64800
64958
|
});
|
|
64801
64959
|
if (outcome.kind === "divergence" || outcome.kind === "failed") return fail$7(new Error(outcome.message));
|
|
64802
|
-
|
|
64960
|
+
if (outcome.kind !== "rest") return succeed$3(rest.def.initial);
|
|
64961
|
+
const reserved = initialStepRefusal(rest.run.workflow, outcome.rest.name, rest.def.initial);
|
|
64962
|
+
return succeed$3(reserved === void 0 ? outcome.rest.name : reserved);
|
|
64803
64963
|
};
|
|
64964
|
+
/**
|
|
64965
|
+
* A step commit entering the default's first step is what ends an episode, so
|
|
64966
|
+
* a named workflow landing there would read back as finished. Its opening
|
|
64967
|
+
* commit may name that step: a bare opening is never read as a boundary.
|
|
64968
|
+
*/
|
|
64969
|
+
const initialStepRefusal = (workflow, step, initial) => workflow !== void 0 && step === initial ? {
|
|
64970
|
+
kind: "refusal",
|
|
64971
|
+
message: `gtd: workflow "${workflow}" reaches step "${step}", the default workflow's first step — that is where a finished process waits, so a started process must never land there; rename the step`
|
|
64972
|
+
} : void 0;
|
|
64804
64973
|
const landingTo = (rest, to) => {
|
|
64805
64974
|
if (rest.changes.length === 0 && rest.stepDef.kind === "script" && to === rest.state) return {
|
|
64806
64975
|
kind: "noop",
|
|
@@ -64862,7 +65031,8 @@ const snapshotFromRest = (rest, verdicts) => gen(function* () {
|
|
|
64862
65031
|
const summaryFor = (run) => gen(function* () {
|
|
64863
65032
|
const config = yield* (yield* ConfigService).load;
|
|
64864
65033
|
const host = yield* Host;
|
|
64865
|
-
const
|
|
65034
|
+
const resolved = yield* workflowOf(config, run);
|
|
65035
|
+
const summary = resolved.def.summary;
|
|
64866
65036
|
if (summary === void 0 || run.trace.length === 0) return void 0;
|
|
64867
65037
|
const entryCommit = run.trace[0].hash;
|
|
64868
65038
|
return summary({
|
|
@@ -64875,7 +65045,7 @@ const summaryFor = (run) => gen(function* () {
|
|
|
64875
65045
|
})),
|
|
64876
65046
|
processCost: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
|
|
64877
65047
|
processCostByModel: costByModel(run.costEntries),
|
|
64878
|
-
...settingsFor(config, run.pinnedVars, host.env)
|
|
65048
|
+
...settingsFor(config, resolved, run.pinnedVars, host.env)
|
|
64879
65049
|
});
|
|
64880
65050
|
});
|
|
64881
65051
|
//#endregion
|
|
@@ -76754,22 +76924,15 @@ f="$(gtd next --json=file)"
|
|
|
76754
76924
|
f="\${f:-.gtd/TODO.md}"
|
|
76755
76925
|
mkdir -p "$(dirname "$f")"
|
|
76756
76926
|
exec "\${EDITOR:-vi}" "$f"`;
|
|
76757
|
-
const
|
|
76927
|
+
const DOOR_COMMAND = `#!/usr/bin/env sh
|
|
76758
76928
|
set -eu
|
|
76759
76929
|
GTD_BUILD=~/.local/bin/gtd-build
|
|
76760
76930
|
if [ -z "\${1:-}" ]; then
|
|
76761
|
-
echo "usage: gtd-
|
|
76931
|
+
echo "usage: gtd-door <name> [args...]" >&2
|
|
76762
76932
|
exit 2
|
|
76763
76933
|
fi
|
|
76764
76934
|
cd "$(git rev-parse --show-toplevel)"
|
|
76765
|
-
script="$(gtd
|
|
76766
|
-
sh -c "$script"
|
|
76767
|
-
exec "$GTD_BUILD"`;
|
|
76768
|
-
const FIX_COMMAND = `#!/usr/bin/env sh
|
|
76769
|
-
set -eu
|
|
76770
|
-
GTD_BUILD=~/.local/bin/gtd-build
|
|
76771
|
-
cd "$(git rev-parse --show-toplevel)"
|
|
76772
|
-
script="$(gtd --entry fix-precheck)"
|
|
76935
|
+
script="$(gtd door "$@")"
|
|
76773
76936
|
sh -c "$script"
|
|
76774
76937
|
exec "$GTD_BUILD"`;
|
|
76775
76938
|
const HEADER = () => `gtd ${GTD_VERSION$1} — driver protocol\n\ngtd decides and prints; the driver executes. gtd never runs git, never runs\nyour agent, never runs a check script, and never runs a steering mode's own\nformat:/validate: command either — that pair is text in the script gtd next\n--json's validate field (or gtd validate) prints, for the driver to run\nitself ahead of gtd land.\n`;
|
|
@@ -76850,7 +77013,8 @@ exits 0, so read it in a shell variable and guard on emptiness
|
|
|
76850
77013
|
adds the self-validation instruction at a validatable \`prompt\` rest, and a
|
|
76851
77014
|
status-summary header at every OTHER kind; \`content\` itself never carries
|
|
76852
77015
|
either), \`idle\` (\`true\` iff the resolved rest is the workflow's initial
|
|
76853
|
-
state with a clean tree), \`
|
|
77016
|
+
state with a clean tree), \`initial\` (\`true\` iff no process is underway —
|
|
77017
|
+
the default workflow's first step, first visit — whatever the tree holds), \`log\`, \`state\`, \`actor\`, \`changes\` (which
|
|
76854
77018
|
declared \`on\` pattern, if any, each pending change matches), \`next\` (the
|
|
76855
77019
|
first declared \`on\` edge that would fire right now — \`{action?, pattern,
|
|
76856
77020
|
target}\` — or \`null\` on no match). Only at \`kind: "prompt"\` (the DISPATCH
|
|
@@ -76944,7 +77108,7 @@ const DRIVER_OBLIGATIONS = `
|
|
|
76944
77108
|
\`--json=judge\`, pipes that rendered \`{ state, questions }\` document to a
|
|
76945
77109
|
judge model, and pipes ITS verdict (one \`{ id, answer, p }\` per pending
|
|
76946
77110
|
question, as JSON on stdin) to \`gtd judge answer\` — the FIFTH command
|
|
76947
|
-
that changes anything, alongside \`gtd land\`/\`gtd --
|
|
77111
|
+
that changes anything, alongside \`gtd land\`/\`gtd --workflow\`/\`gtd
|
|
76948
77112
|
abandon\`/\`gtd restore\`. It follows the exact same required-half /
|
|
76949
77113
|
optional-half script contract as \`gtd land\` in obligation 8 above:
|
|
76950
77114
|
read \`--json=script\` (and, if you want them, \`--json=settled\`/
|
|
@@ -76986,10 +77150,10 @@ logic beyond "ask gtd again".
|
|
|
76986
77150
|
const commandSuite = () => `
|
|
76987
77151
|
## Building the user's command suite: interview first, then adapt
|
|
76988
77152
|
|
|
76989
|
-
The obligations above are the contract; the
|
|
77153
|
+
The obligations above are the contract; the three command bodies below are one
|
|
76990
77154
|
WORKED EXAMPLE of it. Do not copy them blindly, and do not guess the user's
|
|
76991
77155
|
setup — INVESTIGATE first, then INTERVIEW, then build a suite shaped by both.
|
|
76992
|
-
Hold ONE conversation, not
|
|
77156
|
+
Hold ONE conversation, not three: the setup steps below come first, because a
|
|
76993
77157
|
repo that is not set up yet cannot be driven, and the suite-shape questions
|
|
76994
77158
|
follow in the same numbered list.
|
|
76995
77159
|
|
|
@@ -77045,8 +77209,8 @@ follow in the same numbered list.
|
|
|
77045
77209
|
wrapped in \`# gtd-install: model exports\` / \`# gtd-install: end\`
|
|
77046
77210
|
markers — NOT into \`.gtdrc\`. They stay per machine and are never
|
|
77047
77211
|
committed, so one person's model choice binds nobody else on the repo.
|
|
77048
|
-
Two consequences follow, both worth stating to the user: \`gtd-
|
|
77049
|
-
|
|
77212
|
+
Two consequences follow, both worth stating to the user: \`gtd-door\`
|
|
77213
|
+
inherits these exports only because it \`exec\`s
|
|
77050
77214
|
\`gtd-build\` — anything that drives beats without going through
|
|
77051
77215
|
\`gtd-build\` sees the raw \`smart\`/\`base\` hints and fails; and \`GTD_*\` is
|
|
77052
77216
|
the highest-precedence config layer, so these exports silently win over
|
|
@@ -77061,11 +77225,11 @@ follow in the same numbered list.
|
|
|
77061
77225
|
obligations directly. Pick the runtime to match: bash, their language of
|
|
77062
77226
|
choice, anything that reads \`--json=<path>\` and spawns subprocesses.
|
|
77063
77227
|
9. **Where should the suite live, which editor should \`gtd-edit\` spawn, and
|
|
77064
|
-
does anything need renaming?** Suggest \`~/.local/bin\` for all
|
|
77065
|
-
\`gtd-build\`, \`gtd-edit\`, \`gtd-
|
|
77228
|
+
does anything need renaming?** Suggest \`~/.local/bin\` for all three —
|
|
77229
|
+
\`gtd-build\`, \`gtd-edit\`, \`gtd-door\` — and \`$EDITOR\` (never
|
|
77066
77230
|
a hardcoded editor) for the one that opens files. The interview can
|
|
77067
|
-
rename any of the
|
|
77068
|
-
RESOLVED path baked into \`GTD_BUILD\` in the
|
|
77231
|
+
rename any of the three; whatever name is chosen for \`gtd-build\` is the
|
|
77232
|
+
RESOLVED path baked into \`GTD_BUILD\` in the \`gtd-door\` body below —
|
|
77069
77233
|
never the literal string \`gtd-build\`.
|
|
77070
77234
|
|
|
77071
77235
|
Then build it, and verify safely before the first real drive:
|
|
@@ -77074,9 +77238,9 @@ check your kind dispatch against the table above, call them as often as you
|
|
|
77074
77238
|
like. Nothing happens until you run an emitted script.
|
|
77075
77239
|
|
|
77076
77240
|
The reference rendering in sh (no \`jq\`, no JSON parser at all — each
|
|
77077
|
-
\`--json=<path>\` call prints its one value directly).
|
|
77078
|
-
|
|
77079
|
-
suite's resolved \`gtd-build\` path.
|
|
77241
|
+
\`--json=<path>\` call prints its one value directly). \`gtd-door\` takes the
|
|
77242
|
+
\`gtd-build\` path from one convention: \`GTD_BUILD\` is set once, at the top, to
|
|
77243
|
+
the suite's resolved \`gtd-build\` path.
|
|
77080
77244
|
|
|
77081
77245
|
### \`gtd-build\` — the driver loop
|
|
77082
77246
|
|
|
@@ -77114,41 +77278,33 @@ are ready. Default path: \`~/.local/bin/gtd-edit\`.
|
|
|
77114
77278
|
${EDIT_COMMAND}
|
|
77115
77279
|
\`\`\`
|
|
77116
77280
|
|
|
77117
|
-
### \`gtd-
|
|
77118
|
-
|
|
77119
|
-
Runs \`gtd --entry review-gate.check --var reviewBase=<commitish>\`, captured
|
|
77120
|
-
by command substitution — never a pipe: a pipeline reports only its LAST
|
|
77121
|
-
command's exit status, so \`gtd --entry ... | sh\` under \`set -e\` would sail
|
|
77122
|
-
past a refusal and \`exec\` a loop over a process that was never started. It
|
|
77123
|
-
runs the captured script, then \`exec\`s the suite's RESOLVED \`gtd-build\`
|
|
77124
|
-
path — never the literal string \`gtd-build\`. This is one invocation that
|
|
77125
|
-
carries the review all the way to its next human gate, not just a starter:
|
|
77126
|
-
\`gtd-review\` on a RED baseline hands off to \`gtd-build\`, which halts at the
|
|
77127
|
-
blocked gate and prints it, rather than starting the review. Refuses with
|
|
77128
|
-
\`usage: gtd-review <commitish>\` on stderr and exit \`2\` when the commitish is
|
|
77129
|
-
missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-review\`.
|
|
77130
|
-
|
|
77131
|
-
\`\`\`bash
|
|
77132
|
-
${REVIEW_COMMAND}
|
|
77133
|
-
\`\`\`
|
|
77281
|
+
### \`gtd-door <name> [args...]\` — start a process through a door and drive it
|
|
77134
77282
|
|
|
77135
|
-
|
|
77136
|
-
|
|
77137
|
-
|
|
77138
|
-
|
|
77283
|
+
Runs \`gtd door <name> [args...]\`, captured by command substitution — never a
|
|
77284
|
+
pipe: a pipeline reports only its LAST command's exit status, so
|
|
77285
|
+
\`gtd door ... | sh\` under \`set -e\` would sail past a refusal and \`exec\` a
|
|
77286
|
+
loop over a process that was never started. It runs the captured script, then
|
|
77287
|
+
\`exec\`s the suite's RESOLVED \`gtd-build\` path — never the literal string
|
|
77288
|
+
\`gtd-build\`. This is one invocation that carries the process all the way to
|
|
77289
|
+
its next human gate, not just a starter: \`gtd-door review\` on a RED baseline
|
|
77290
|
+
hands off to \`gtd-build\`, which halts at the blocked gate and prints it,
|
|
77291
|
+
rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
|
|
77139
77292
|
straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
|
|
77140
|
-
|
|
77293
|
+
\`gtd doors\` lists the doors a repository offers (the bundled \`fix\` and
|
|
77294
|
+
\`review [base]\`, plus any its config declares). Refuses with
|
|
77295
|
+
\`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
|
|
77296
|
+
missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
|
|
77141
77297
|
|
|
77142
77298
|
\`\`\`bash
|
|
77143
|
-
${
|
|
77299
|
+
${DOOR_COMMAND}
|
|
77144
77300
|
\`\`\`
|
|
77145
77301
|
${REINSTALL}`;
|
|
77146
77302
|
const REINSTALL = `
|
|
77147
77303
|
## Re-installing: detect and adapt, don't blindly overwrite
|
|
77148
77304
|
|
|
77149
77305
|
A second install on a machine that already has a suite is not a fresh
|
|
77150
|
-
install — read each of the
|
|
77151
|
-
(\`gtd-build\`, \`gtd-edit\`, \`gtd-
|
|
77306
|
+
install — read each of the three suite paths before writing anything
|
|
77307
|
+
(\`gtd-build\`, \`gtd-edit\`, \`gtd-door\`), and branch per path:
|
|
77152
77308
|
|
|
77153
77309
|
- Absent — install it, as in a fresh install.
|
|
77154
77310
|
- Present and content-equal to what this gtd version would emit — say so,
|
|
@@ -77170,10 +77326,16 @@ markers is unchanged, and re-asks nothing — the resolved model names are
|
|
|
77170
77326
|
per machine, so without this exemption every single re-install would
|
|
77171
77327
|
report drift on the one command everybody has.
|
|
77172
77328
|
|
|
77173
|
-
This check scopes to exactly the
|
|
77329
|
+
This check scopes to exactly the three suite paths above, plus the superseded
|
|
77330
|
+
pair below. \`gtd-loop\` is
|
|
77174
77331
|
outside it entirely — never read, never diffed, never touched by it in any
|
|
77175
77332
|
way — an existing \`gtd-loop\` survives untouched beside the new
|
|
77176
77333
|
\`gtd-build\`, and cleaning it up is the human's own call.
|
|
77334
|
+
|
|
77335
|
+
\`gtd-fix\` and \`gtd-review\` are superseded by \`gtd-door\` (\`gtd-door fix\`,
|
|
77336
|
+
\`gtd-door review [base]\`). When either is still present beside the suite,
|
|
77337
|
+
name it to the user as superseded and ask before removing it — never remove
|
|
77338
|
+
one unasked.
|
|
77177
77339
|
`;
|
|
77178
77340
|
const EDITOR_INTEGRATION = `
|
|
77179
77341
|
## Editor integration: offer it, don't bury it
|
|
@@ -77221,7 +77383,7 @@ const PREREQUISITES = `
|
|
|
77221
77383
|
## Prerequisites and portability
|
|
77222
77384
|
|
|
77223
77385
|
- A POSIX \`sh\` (dash, ash, bash's own POSIX mode, etc.) — gtd's own emitted
|
|
77224
|
-
scripts (\`gtd land --json=script\`, \`gtd --
|
|
77386
|
+
scripts (\`gtd land --json=script\`, \`gtd --workflow <name>\`, \`gtd abandon\`,
|
|
77225
77387
|
\`gtd restore\`, \`gtd judge answer --json=script\`) are POSIX sh; captured,
|
|
77226
77388
|
then piped into it (see obligation 8 above). Reading
|
|
77227
77389
|
\`gtd next --json=<path>\`/\`gtd land --json=<path>\`'s own output needs
|
|
@@ -77359,7 +77521,7 @@ const normalizeScriptNewline = (script) => script.length === 0 ? script : `${scr
|
|
|
77359
77521
|
/**
|
|
77360
77522
|
* `combinedScript`, narrowed to accept only an already-guarded
|
|
77361
77523
|
* `RunnableScript` for `required` — the one boundary `ScriptSurface`'s brand
|
|
77362
|
-
* exists to protect: `gtd land`/`gtd --
|
|
77524
|
+
* exists to protect: `gtd land`/`gtd --workflow`'s emitted script. Every OTHER
|
|
77363
77525
|
* `combinedScript` call (`abandon`/`restore`/steering validate, below) builds
|
|
77364
77526
|
* from `emitScripts` directly and keeps the unbranded signature — those
|
|
77365
77527
|
* paths land no guard-gated git write for a brand to protect.
|
|
@@ -77522,29 +77684,77 @@ const runLandCommand = (opts, json, out) => gen(function* () {
|
|
|
77522
77684
|
else out.write(renderLandPlain(built));
|
|
77523
77685
|
});
|
|
77524
77686
|
/**
|
|
77525
|
-
* Starts a brand new process
|
|
77526
|
-
* whatever the working tree carries at
|
|
77527
|
-
* than demanding a clean tree.
|
|
77687
|
+
* Starts a brand new process on `<workflow>`, always as `"human"`. Captures
|
|
77688
|
+
* whatever the working tree carries at the start, like an ordinary land,
|
|
77689
|
+
* rather than demanding a clean tree.
|
|
77528
77690
|
*
|
|
77529
|
-
* Refused when a process is already
|
|
77530
|
-
*
|
|
77531
|
-
* something that is not an ancestor of (and differs from)
|
|
77691
|
+
* An unknown name is a usage error. Refused when a process is already
|
|
77692
|
+
* underway, the workflow reaches no step, a `--var` name is undeclared, or
|
|
77693
|
+
* `base` renders to something that is not an ancestor of (and differs from)
|
|
77694
|
+
* HEAD.
|
|
77532
77695
|
*/
|
|
77533
|
-
const
|
|
77696
|
+
const runStartCommand = (actor, workflow, varOverrides, out, commandLabel) => gen(function* () {
|
|
77697
|
+
const config = yield* (yield* ConfigService).load;
|
|
77698
|
+
const resolved = config.workflowNamed(workflow);
|
|
77699
|
+
if (resolved === void 0) return yield* fail$7(new GtdUsageError(`gtd --workflow: unknown workflow "${workflow}" — startable: ${config.workflowNames.join(", ")}`));
|
|
77534
77700
|
const rest = yield* restAt(void 0, varOverrides);
|
|
77535
|
-
const
|
|
77536
|
-
|
|
77701
|
+
const started = yield* startRefusal(rest, resolved, varOverrides);
|
|
77702
|
+
const plan = yield* planStart({
|
|
77537
77703
|
state: rest.state,
|
|
77538
|
-
idle: noProcessUnderway(rest)
|
|
77539
|
-
entryRefusal: yield* entryRefusal(rest, entryState, varOverrides)
|
|
77704
|
+
idle: noProcessUnderway(rest)
|
|
77540
77705
|
}, actor, {
|
|
77541
|
-
|
|
77706
|
+
workflow: resolved,
|
|
77707
|
+
first: started,
|
|
77542
77708
|
commandLabel,
|
|
77543
77709
|
vars: varOverrides
|
|
77544
77710
|
});
|
|
77545
77711
|
if (plan.kind === "refusal") return yield* fail$7(new Error(plan.message));
|
|
77546
77712
|
out.write(landingScript(ScriptSurface.render(plan.steps)));
|
|
77547
77713
|
});
|
|
77714
|
+
const doorSynopsis = (name, door) => [name, ...(door.args ?? []).map((arg) => arg.optional === true ? `[${arg.name}]` : `<${arg.name}>`)].join(" ");
|
|
77715
|
+
/**
|
|
77716
|
+
* `gtd door <name> [args…]`: `gtd --workflow` with the door's workflow and the
|
|
77717
|
+
* process settings its `vars` derive from the positional args. A wrong name or
|
|
77718
|
+
* arity is a usage error naming what would have worked.
|
|
77719
|
+
*/
|
|
77720
|
+
const runDoorCommand = (name, args, out) => gen(function* () {
|
|
77721
|
+
const config = yield* (yield* ConfigService).load;
|
|
77722
|
+
const door = config.doors.get(name);
|
|
77723
|
+
if (door === void 0) {
|
|
77724
|
+
const known = [...config.doors];
|
|
77725
|
+
return yield* fail$7(new GtdUsageError(`gtd door: unknown door "${name}" — doors: ${known.map(([n, d]) => doorSynopsis(n, d)).join(", ")}`));
|
|
77726
|
+
}
|
|
77727
|
+
const declared = door.args ?? [];
|
|
77728
|
+
const required = declared.filter((arg) => arg.optional !== true).length;
|
|
77729
|
+
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)}`));
|
|
77730
|
+
const named = {};
|
|
77731
|
+
declared.forEach((arg, i) => {
|
|
77732
|
+
named[arg.name] = args[i];
|
|
77733
|
+
});
|
|
77734
|
+
const vars = yield* try_({
|
|
77735
|
+
try: () => door.vars?.(named) ?? {},
|
|
77736
|
+
catch: (e) => /* @__PURE__ */ new Error(`gtd door ${name}: ${e instanceof Error ? e.message : String(e)}`)
|
|
77737
|
+
});
|
|
77738
|
+
yield* runStartCommand("human", door.workflow, { ...vars }, out, `gtd door ${name}`);
|
|
77739
|
+
});
|
|
77740
|
+
/** `gtd doors`: every door and its args, sorted by name — plain lines, or one JSON array. */
|
|
77741
|
+
const runDoorsCommand = (json, out) => gen(function* () {
|
|
77742
|
+
const doors = [...(yield* (yield* ConfigService).load).doors];
|
|
77743
|
+
if (json.kind === "off") {
|
|
77744
|
+
out.write(doors.map(([n, d]) => `${doorSynopsis(n, d)} → ${d.workflow}\n`).join(""));
|
|
77745
|
+
return;
|
|
77746
|
+
}
|
|
77747
|
+
const document = doors.map(([n, d]) => ({
|
|
77748
|
+
name: n,
|
|
77749
|
+
workflow: d.workflow,
|
|
77750
|
+
args: (d.args ?? []).map((arg) => ({
|
|
77751
|
+
name: arg.name,
|
|
77752
|
+
optional: arg.optional === true
|
|
77753
|
+
}))
|
|
77754
|
+
}));
|
|
77755
|
+
if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
|
|
77756
|
+
else yield* writeSelection(out, document, json.path);
|
|
77757
|
+
});
|
|
77548
77758
|
/**
|
|
77549
77759
|
* Ends the process underway without completing it. Nothing is discarded: a
|
|
77550
77760
|
* `git reset --mixed` to the process's start commit leaves every turn
|
|
@@ -77801,6 +78011,8 @@ const gatherBeatDocument = (rest, rendered) => gen(function* () {
|
|
|
77801
78011
|
}), statusOf({
|
|
77802
78012
|
rendered,
|
|
77803
78013
|
idle: restIsIdle(rest),
|
|
78014
|
+
initial: noProcessUnderway(rest),
|
|
78015
|
+
workflow: rest.workflowName,
|
|
77804
78016
|
log,
|
|
77805
78017
|
changes: computeStatusChanges(rest.changes),
|
|
77806
78018
|
next: yield* computeNextMatch(rest),
|
|
@@ -77887,7 +78099,9 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
77887
78099
|
...command.cost !== void 0 ? { cost: command.cost } : {},
|
|
77888
78100
|
...command.model !== void 0 ? { model: command.model } : {}
|
|
77889
78101
|
}, json, out);
|
|
77890
|
-
case "
|
|
78102
|
+
case "start": return runStartCommand(command.actor, command.workflow, command.vars, out, command.label);
|
|
78103
|
+
case "door": return runDoorCommand(command.name, command.args, out);
|
|
78104
|
+
case "doors": return runDoorsCommand(json, out);
|
|
77891
78105
|
case "abandon": return runAbandonCommand(out);
|
|
77892
78106
|
case "restore": return runRestoreCommand(out);
|
|
77893
78107
|
case "next": return runNextCommand(json, out);
|
|
@@ -77951,12 +78165,12 @@ const FLAGS = [
|
|
|
77951
78165
|
name: "--json",
|
|
77952
78166
|
arity: "optional",
|
|
77953
78167
|
repeatable: false,
|
|
77954
|
-
scope: (kind) => kind === "next" || kind === "land" || kind === "judge" || kind === "judgeAnswer",
|
|
78168
|
+
scope: (kind) => kind === "next" || kind === "land" || kind === "judge" || kind === "judgeAnswer" || kind === "doors",
|
|
77955
78169
|
decode: ([raw]) => right(raw ?? ""),
|
|
77956
|
-
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
78170
|
+
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
77957
78171
|
valueHint: "<path>",
|
|
77958
78172
|
help: [
|
|
77959
|
-
"(gtd next/gtd land/gtd judge/gtd judge answer only) output",
|
|
78173
|
+
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors only) output",
|
|
77960
78174
|
"structured JSON. Bare --json prints the whole document;",
|
|
77961
78175
|
"--json=<path> (a dotted key path into that document, e.g.",
|
|
77962
78176
|
"kind, content, session.id) prints just that value: a",
|
|
@@ -78055,7 +78269,7 @@ const FLAGS = [
|
|
|
78055
78269
|
repeatable: false,
|
|
78056
78270
|
scope: (kind) => kind === "land",
|
|
78057
78271
|
decode: ([raw]) => nonNegativeNumber(raw ?? "", "--cost"),
|
|
78058
|
-
scopeError: "gtd: --cost is only valid for `gtd land` —
|
|
78272
|
+
scopeError: "gtd: --cost is only valid for `gtd land` — a start is not a metered agent turn",
|
|
78059
78273
|
valueHint: "<n>",
|
|
78060
78274
|
help: ["(gtd land only) record the invocation's token cost"]
|
|
78061
78275
|
},
|
|
@@ -78074,24 +78288,20 @@ const FLAGS = [
|
|
|
78074
78288
|
]
|
|
78075
78289
|
},
|
|
78076
78290
|
{
|
|
78077
|
-
name: "--
|
|
78291
|
+
name: "--workflow",
|
|
78078
78292
|
arity: 1,
|
|
78079
78293
|
repeatable: false,
|
|
78080
|
-
scope: (kind) => kind === "
|
|
78294
|
+
scope: (kind) => kind === "start",
|
|
78081
78295
|
decode: ([raw]) => right(raw ?? ""),
|
|
78082
|
-
scopeError: "gtd: --
|
|
78083
|
-
valueHint: "<
|
|
78084
|
-
help: [
|
|
78085
|
-
"(with no command at all) start a brand new process,",
|
|
78086
|
-
"handing <state> to the workflow as its entry —",
|
|
78087
|
-
"authenticated as human"
|
|
78088
|
-
]
|
|
78296
|
+
scopeError: "gtd: --workflow is only valid with no other command — use the bare `gtd --workflow <name>` form; landing and starting are different verbs",
|
|
78297
|
+
valueHint: "<name>",
|
|
78298
|
+
help: ["(with no command at all) start a brand new process on", "the named workflow — authenticated as human"]
|
|
78089
78299
|
},
|
|
78090
78300
|
{
|
|
78091
78301
|
name: "--var",
|
|
78092
78302
|
arity: 1,
|
|
78093
78303
|
repeatable: true,
|
|
78094
|
-
scope: (kind) => kind === "
|
|
78304
|
+
scope: (kind) => kind === "start",
|
|
78095
78305
|
decode: (raws) => {
|
|
78096
78306
|
const vars = {};
|
|
78097
78307
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -78108,10 +78318,10 @@ const FLAGS = [
|
|
|
78108
78318
|
}
|
|
78109
78319
|
return right(vars);
|
|
78110
78320
|
},
|
|
78111
|
-
scopeError: "gtd: --var requires --
|
|
78321
|
+
scopeError: "gtd: --var requires --workflow",
|
|
78112
78322
|
valueHint: "<name>=<value>",
|
|
78113
78323
|
help: [
|
|
78114
|
-
"(with --
|
|
78324
|
+
"(with --workflow; repeatable) pin a process setting for the",
|
|
78115
78325
|
"new process; the name must already be declared by the",
|
|
78116
78326
|
"workflow's defaults or the .gtdrc vars: (environment",
|
|
78117
78327
|
"settings are not pinnable)"
|
|
@@ -78394,6 +78604,34 @@ const COMMAND_ROWS = [
|
|
|
78394
78604
|
"Refuses (exit 1) when no process is underway."
|
|
78395
78605
|
]
|
|
78396
78606
|
},
|
|
78607
|
+
{
|
|
78608
|
+
token: "door",
|
|
78609
|
+
kind: "door",
|
|
78610
|
+
arity: {
|
|
78611
|
+
name: "name",
|
|
78612
|
+
variadic: "args"
|
|
78613
|
+
},
|
|
78614
|
+
details: [
|
|
78615
|
+
"Start a process through a named shortcut, mapping the",
|
|
78616
|
+
"positional args to that workflow's process settings — the",
|
|
78617
|
+
"same start `gtd --workflow` makes, printed the same way.",
|
|
78618
|
+
"Doors come from the config's `doors` export; the bundled",
|
|
78619
|
+
"ones are `fix` and `review [base]`. An unknown door, or too",
|
|
78620
|
+
"few or too many args, is a usage error (exit 2) naming the",
|
|
78621
|
+
"doors or the door's synopsis"
|
|
78622
|
+
]
|
|
78623
|
+
},
|
|
78624
|
+
{
|
|
78625
|
+
token: "doors",
|
|
78626
|
+
kind: "doors",
|
|
78627
|
+
arity: "none",
|
|
78628
|
+
details: [
|
|
78629
|
+
"List every door, sorted: one `name [args] → workflow` line",
|
|
78630
|
+
"each. --json prints one JSON array of { name, workflow,",
|
|
78631
|
+
"args: [{ name, optional }] } instead; --json=<path> reads one",
|
|
78632
|
+
"value off it. Writes nothing"
|
|
78633
|
+
]
|
|
78634
|
+
},
|
|
78397
78635
|
{
|
|
78398
78636
|
token: "judge",
|
|
78399
78637
|
kind: "judge",
|
|
@@ -78449,23 +78687,23 @@ const renderBlock = (header, lines) => {
|
|
|
78449
78687
|
const headerCell = ` ${header}`;
|
|
78450
78688
|
return (headerCell.length < COLUMN ? `${headerCell.padEnd(COLUMN)}${first ?? ""}\n` : `${headerCell}\n${" ".repeat(COLUMN)}${first ?? ""}\n`) + rest.map((l) => `${" ".repeat(COLUMN)}${l}\n`).join("");
|
|
78451
78689
|
};
|
|
78452
|
-
const
|
|
78690
|
+
const START_SHORT_FORM = renderBlock("(no command) --workflow <name>", ["Starts a new process authenticated as human, e.g.", "'gtd --workflow <name>'"]);
|
|
78453
78691
|
const VERSION_BLOCK = renderBlock("version", ["Print version and exit"]);
|
|
78454
78692
|
const HELP_BLOCK = renderBlock("help", ["Print this help and exit"]);
|
|
78455
|
-
const SPACE_FORM_FLAGS = /* @__PURE__ */ new Set(["--
|
|
78693
|
+
const SPACE_FORM_FLAGS = /* @__PURE__ */ new Set(["--workflow", "--var"]);
|
|
78456
78694
|
const flagHeader = (row) => {
|
|
78457
78695
|
if (row.valueHint === "") return row.name;
|
|
78458
78696
|
return SPACE_FORM_FLAGS.has(row.name) ? `${row.name} ${row.valueHint}` : `${row.name}=${row.valueHint}`;
|
|
78459
78697
|
};
|
|
78460
78698
|
const renderHelp = () => {
|
|
78461
78699
|
const commandBlocks = COMMAND_ROWS.map((row) => {
|
|
78462
|
-
const header = row.arity === "none" ? row.token : "names" in row.arity ? `${row.token} <${row.arity.names[0]}> <${row.arity.names[1]}>` : `${row.token} <${row.arity.name}>`;
|
|
78700
|
+
const header = row.arity === "none" ? row.token : "names" in row.arity ? `${row.token} <${row.arity.names[0]}> <${row.arity.names[1]}>` : "variadic" in row.arity ? `${row.token} <${row.arity.name}> [${row.arity.variadic}...]` : `${row.token} <${row.arity.name}>`;
|
|
78463
78701
|
return renderBlock(header, row.details);
|
|
78464
78702
|
});
|
|
78465
78703
|
return `Usage: gtd [command] [options]\n\nCommands:\n${[
|
|
78466
78704
|
commandBlocks[0],
|
|
78467
78705
|
commandBlocks[1],
|
|
78468
|
-
|
|
78706
|
+
START_SHORT_FORM,
|
|
78469
78707
|
...commandBlocks.slice(2),
|
|
78470
78708
|
VERSION_BLOCK,
|
|
78471
78709
|
HELP_BLOCK
|
|
@@ -78570,14 +78808,14 @@ const conflictViolation = (present) => {
|
|
|
78570
78808
|
};
|
|
78571
78809
|
const arityError = (cmd, rest, arity) => {
|
|
78572
78810
|
if (arity === "none") return rest.length > 0 ? `gtd ${cmd}: too many arguments — expected none, got: ${rest.join(", ")}` : void 0;
|
|
78573
|
-
if ("names" in arity)
|
|
78574
|
-
const [first, second] = arity.names;
|
|
78575
|
-
if (rest.length < 2) return rest.length === 0 ? `gtd ${cmd}: missing ${first} and ${second} arguments` : `gtd ${cmd}: missing ${second} argument`;
|
|
78576
|
-
if (rest.length > 2) return `gtd ${cmd}: too many arguments — expected ${first} and ${second}, got: ${rest.join(", ")}`;
|
|
78577
|
-
return;
|
|
78578
|
-
}
|
|
78811
|
+
if ("names" in arity) return pairArityError(cmd, rest, arity.names);
|
|
78579
78812
|
if (rest.length === 0) return `gtd ${cmd}: missing ${arity.name} argument`;
|
|
78580
|
-
if (rest.length > 1) return `gtd ${cmd}: too many arguments — expected one ${arity.name}, got: ${rest.join(", ")}`;
|
|
78813
|
+
if (!("variadic" in arity) && rest.length > 1) return `gtd ${cmd}: too many arguments — expected one ${arity.name}, got: ${rest.join(", ")}`;
|
|
78814
|
+
};
|
|
78815
|
+
const pairArityError = (cmd, rest, [first, second]) => {
|
|
78816
|
+
if (rest.length === 0) return `gtd ${cmd}: missing ${first} and ${second} arguments`;
|
|
78817
|
+
if (rest.length === 1) return `gtd ${cmd}: missing ${second} argument`;
|
|
78818
|
+
if (rest.length > 2) return `gtd ${cmd}: too many arguments — expected ${first} and ${second}, got: ${rest.join(", ")}`;
|
|
78581
78819
|
};
|
|
78582
78820
|
/** Decode every present flag into a bag of values, or the first decode error encountered. */
|
|
78583
78821
|
const decodeFlags = (byFlag) => {
|
|
@@ -78611,8 +78849,8 @@ const parseArgv = (argv) => {
|
|
|
78611
78849
|
if (tokenized.kind === "error") return usagePlan(tokenized.message, tokenized.jsonSeen);
|
|
78612
78850
|
const { positionals, byFlag, present, jsonSeen } = tokenized;
|
|
78613
78851
|
const first = positionals[0];
|
|
78614
|
-
const
|
|
78615
|
-
const
|
|
78852
|
+
const workflowRaw = byFlag.get("--workflow")?.[0];
|
|
78853
|
+
const workflowPresent = workflowRaw !== void 0;
|
|
78616
78854
|
if (first === "version") return {
|
|
78617
78855
|
kind: "output",
|
|
78618
78856
|
stdout: `${GTD_VERSION}\n`
|
|
@@ -78623,17 +78861,17 @@ const parseArgv = (argv) => {
|
|
|
78623
78861
|
};
|
|
78624
78862
|
const row = first === void 0 ? void 0 : commandByToken(first);
|
|
78625
78863
|
const judgeAnswer = row?.kind === "judge" && positionals[1] === "answer";
|
|
78626
|
-
const
|
|
78864
|
+
const selectsStart = workflowPresent && first === void 0;
|
|
78627
78865
|
const judgeRun = row?.kind === "judge" && positionals[1] === "run";
|
|
78628
|
-
const kind =
|
|
78629
|
-
if (row === void 0 && !
|
|
78866
|
+
const kind = selectsStart ? "start" : judgeAnswer ? "judgeAnswer" : judgeRun ? "judgeRun" : row?.kind;
|
|
78867
|
+
if (row === void 0 && !selectsStart) {
|
|
78630
78868
|
const violation = scopeViolation(void 0, present);
|
|
78631
78869
|
if (violation !== void 0) return usagePlan(violation, jsonSeen);
|
|
78632
78870
|
if (first === void 0) return usagePlan("gtd: missing command — gtd decides and prints, a driver executes; run `gtd install`, or copy one from https://github.com/pmelab/gtd/blob/main/docs/driver.md's \"A complete minimal driver\" section and run that, or see usage above (`gtd --help`)", jsonSeen, renderHelp());
|
|
78633
78871
|
return usagePlan(`unknown command '${first}'`, jsonSeen);
|
|
78634
78872
|
}
|
|
78635
78873
|
const restPositionals = first === void 0 ? positionals : positionals.slice(judgeAnswer || judgeRun ? 2 : 1);
|
|
78636
|
-
if (kind === "
|
|
78874
|
+
if (kind === "start" && first === void 0) {
|
|
78637
78875
|
if (restPositionals.length > 0) return usagePlan(`gtd: too many arguments — expected none, got: ${restPositionals.join(", ")}`, jsonSeen);
|
|
78638
78876
|
} else {
|
|
78639
78877
|
const arityMsg = kind === "check" && restPositionals.length === 0 && present.has("--open-threads") ? void 0 : arityError(first, restPositionals, row.arity);
|
|
@@ -78660,14 +78898,14 @@ const parseArgv = (argv) => {
|
|
|
78660
78898
|
verbose
|
|
78661
78899
|
};
|
|
78662
78900
|
}
|
|
78663
|
-
if (kind === "
|
|
78664
|
-
const label = `gtd --
|
|
78901
|
+
if (kind === "start") {
|
|
78902
|
+
const label = `gtd --workflow ${workflowRaw}`;
|
|
78665
78903
|
return {
|
|
78666
78904
|
kind: "command",
|
|
78667
78905
|
command: {
|
|
78668
|
-
kind: "
|
|
78906
|
+
kind: "start",
|
|
78669
78907
|
actor: "human",
|
|
78670
|
-
|
|
78908
|
+
workflow: workflowRaw,
|
|
78671
78909
|
vars: bag["--var"] ?? {},
|
|
78672
78910
|
label
|
|
78673
78911
|
},
|
|
@@ -78675,6 +78913,16 @@ const parseArgv = (argv) => {
|
|
|
78675
78913
|
verbose
|
|
78676
78914
|
};
|
|
78677
78915
|
}
|
|
78916
|
+
if (kind === "door") return {
|
|
78917
|
+
kind: "command",
|
|
78918
|
+
command: {
|
|
78919
|
+
kind: "door",
|
|
78920
|
+
name: restPositionals[0],
|
|
78921
|
+
args: restPositionals.slice(1)
|
|
78922
|
+
},
|
|
78923
|
+
json,
|
|
78924
|
+
verbose
|
|
78925
|
+
};
|
|
78678
78926
|
if (kind === "ui") return {
|
|
78679
78927
|
kind: "command",
|
|
78680
78928
|
command: {
|