@pmelab/gtd 15.10.0 → 17.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/README.md +12 -4
- package/dist/gtd.bundle.mjs +599 -165
- package/package.json +1 -1
- package/schema.json +43 -1
- package/src/flows/runtime.ts +40 -13
- package/src/workflows/health.ts +3 -2
- package/src/workflows/review.ts +6 -2
- package/src/workflows/skills.test.ts +93 -0
- package/src/workflows/skills.ts +37 -0
- package/src/workflows/steps.test.ts +75 -68
- package/src/workflows/steps.ts +50 -64
- package/src/workflows/text.fixture.ts +39 -1
- package/src/workflows/text.test.ts +22 -28
- package/src/workflows/text.ts +21 -25
- package/src/workflows/unified.ts +2 -1
- package/src/workflows/vars.ts +18 -14
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -30966,17 +30966,26 @@ const changesSince = (hash, pattern) => {
|
|
|
30966
30966
|
const head = () => ctx().head();
|
|
30967
30967
|
/** The process's diff base: the commit before it began, or the base `gtd --entry` fixed. */
|
|
30968
30968
|
const start = () => ctx().start();
|
|
30969
|
-
/** The
|
|
30970
|
-
const
|
|
30971
|
-
|
|
30972
|
-
|
|
30973
|
-
|
|
30974
|
-
|
|
30969
|
+
/** The skill list `localName` (scoped from here, same as `agent()`) resolves to — for a prompt preamble. See `FlowContext.skillsFor` for `ownSkills`. */
|
|
30970
|
+
const skillsFor = (localName, ownSkills) => ctx().skillsFor(localName, ownSkills);
|
|
30971
|
+
const settingsProxy = (read) => new Proxy({}, {
|
|
30972
|
+
get: (_target, key) => typeof key === "string" ? read()[key] : void 0,
|
|
30973
|
+
has: (_target, key) => typeof key === "string" && key in read(),
|
|
30974
|
+
ownKeys: () => Object.keys(read()),
|
|
30975
|
+
getOwnPropertyDescriptor: (_target, key) => typeof key === "string" && key in read() ? {
|
|
30975
30976
|
enumerable: true,
|
|
30976
30977
|
configurable: true,
|
|
30977
|
-
value:
|
|
30978
|
+
value: read()[key]
|
|
30978
30979
|
} : void 0
|
|
30979
30980
|
});
|
|
30981
|
+
/** The process settings: pinned at process start, so flow code may branch on them. */
|
|
30982
|
+
const vars = settingsProxy(() => ctx().vars);
|
|
30983
|
+
/**
|
|
30984
|
+
* The environment settings: resolved live on every invocation. Flow code must
|
|
30985
|
+
* read them only where they cannot change the next step (step options, script
|
|
30986
|
+
* bodies) — replay cannot enforce this.
|
|
30987
|
+
*/
|
|
30988
|
+
const env = settingsProxy(() => ctx().env);
|
|
30980
30989
|
/** The top-level `## ` heading texts of markdown `text`. */
|
|
30981
30990
|
const sections = (text) => ctx().sections(text);
|
|
30982
30991
|
/** The top-level `## ` sections of markdown `text`, each with its own markdown. */
|
|
@@ -31160,6 +31169,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
|
|
|
31160
31169
|
checkScript: () => checkScript,
|
|
31161
31170
|
codeChanges: () => codeChanges,
|
|
31162
31171
|
codeThreads: () => codeThreads,
|
|
31172
|
+
env: () => env,
|
|
31163
31173
|
glob: () => glob,
|
|
31164
31174
|
hasThreadFor: () => hasThreadFor,
|
|
31165
31175
|
head: () => head,
|
|
@@ -31185,6 +31195,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
|
|
|
31185
31195
|
scope: () => scope,
|
|
31186
31196
|
sectionBodies: () => sectionBodies,
|
|
31187
31197
|
sections: () => sections,
|
|
31198
|
+
skillsFor: () => skillsFor,
|
|
31188
31199
|
start: () => start,
|
|
31189
31200
|
threads: () => threads,
|
|
31190
31201
|
vars: () => vars,
|
|
@@ -47611,7 +47622,7 @@ const formatCommitMessage = (spec) => {
|
|
|
47611
47622
|
const lines = [];
|
|
47612
47623
|
if (spec.step !== void 0) lines.push(`Gtd-Step: ${formatStepId(spec.step)}`);
|
|
47613
47624
|
if (spec.reviewBase !== void 0) lines.push(`Gtd-Review-Base: ${spec.reviewBase}`);
|
|
47614
|
-
for (const [name, value] of Object.entries(spec.vars ?? {})) lines.push(`Gtd-Var: ${name}=${value}`);
|
|
47625
|
+
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}`);
|
|
47615
47626
|
if (spec.cost !== void 0) lines.push(`Gtd-Cost: ${spec.cost.cost}${spec.cost.model !== void 0 ? ` ${spec.cost.model}` : ""}`);
|
|
47616
47627
|
for (const verdict of spec.judge ?? []) lines.push(`Gtd-Judge: ${JSON.stringify(verdict)}`);
|
|
47617
47628
|
const subject = formatSubject(spec.actor, spec.to, spec.from);
|
|
@@ -47773,7 +47784,11 @@ const replay = async (input) => {
|
|
|
47773
47784
|
truncated: judgeCuts.get(request) ?? []
|
|
47774
47785
|
} : void 0;
|
|
47775
47786
|
const judgeCuts = /* @__PURE__ */ new WeakMap();
|
|
47776
|
-
const
|
|
47787
|
+
const resolveSkills = (name, ownSkills) => {
|
|
47788
|
+
const resolved = input.configuredSkills?.[name] ?? ownSkills ?? input.skills?.[name];
|
|
47789
|
+
return resolved !== void 0 && resolved.length === 0 ? void 0 : resolved;
|
|
47790
|
+
};
|
|
47791
|
+
const resolve = (request, name) => {
|
|
47777
47792
|
if (request.kind === "agent") {
|
|
47778
47793
|
const persona = {};
|
|
47779
47794
|
for (const s of scopes) {
|
|
@@ -47784,7 +47799,8 @@ const replay = async (input) => {
|
|
|
47784
47799
|
...request,
|
|
47785
47800
|
options: {
|
|
47786
47801
|
...persona,
|
|
47787
|
-
...request.options
|
|
47802
|
+
...request.options,
|
|
47803
|
+
skills: resolveSkills(name, request.options.skills)
|
|
47788
47804
|
}
|
|
47789
47805
|
};
|
|
47790
47806
|
}
|
|
@@ -47803,7 +47819,7 @@ const replay = async (input) => {
|
|
|
47803
47819
|
const name = scoped(request.name);
|
|
47804
47820
|
const occurrence = (occurrences.get(name) ?? 0) + 1;
|
|
47805
47821
|
occurrences.set(name, occurrence);
|
|
47806
|
-
const resolved = resolve(request);
|
|
47822
|
+
const resolved = resolve(request, name);
|
|
47807
47823
|
const reached = {
|
|
47808
47824
|
id: {
|
|
47809
47825
|
name,
|
|
@@ -47967,7 +47983,9 @@ const replay = async (input) => {
|
|
|
47967
47983
|
}));
|
|
47968
47984
|
}),
|
|
47969
47985
|
vars: input.vars,
|
|
47986
|
+
env: input.env,
|
|
47970
47987
|
start: () => input.start,
|
|
47988
|
+
skillsFor: (localName, ownSkills) => resolveSkills(scoped(localName), ownSkills) ?? [],
|
|
47971
47989
|
head: () => {
|
|
47972
47990
|
const rest = commits.slice(cursor);
|
|
47973
47991
|
return rest.length > 0 && rest.every((c) => c.parsed.step === void 0) ? rest[rest.length - 1].hash : position.hash;
|
|
@@ -48241,21 +48259,18 @@ const withSkills = (skills, prompt) => {
|
|
|
48241
48259
|
if (skills === void 0 || skills.trim() === "") return prompt;
|
|
48242
48260
|
return `${skillsPreamble.replaceAll("{skills}", skills)}\n\n${prompt}`;
|
|
48243
48261
|
};
|
|
48244
|
-
/** `skills:` split on `,`, each name trimmed, empty entries dropped — `[]` for `undefined`, `""`, or blank. */
|
|
48245
|
-
const splitSkills = (raw) => (raw ?? "").split(",").map((name) => name.trim()).filter((name) => name.length > 0);
|
|
48246
48262
|
/**
|
|
48247
|
-
* An `agent()` step
|
|
48248
|
-
*
|
|
48249
|
-
*
|
|
48250
|
-
*
|
|
48263
|
+
* An `agent()` step whose preamble names its resolved skills: `skillsFor`
|
|
48264
|
+
* resolves `name` (scoped from here, same as `agent()`'s own resolver) for
|
|
48265
|
+
* the preamble prose, passing `options.skills` through as `ownSkills` — the
|
|
48266
|
+
* same precedence tier `agent()`'s own wire resolver gives a call's own
|
|
48267
|
+
* `skills` option, so a step that declares one (`reviewQuality`'s per-turn
|
|
48268
|
+
* lens) gets it in the preamble too, not just on the wire, whenever no
|
|
48269
|
+
* bundled or configured entry outranks it. Both reads share one resolver
|
|
48270
|
+
* (`Replay.ts`'s `resolveSkills`), so the preamble and the wire can't drift
|
|
48271
|
+
* apart.
|
|
48251
48272
|
*/
|
|
48252
|
-
const agentWithSkills = (name,
|
|
48253
|
-
const list = splitSkills(skills);
|
|
48254
|
-
return agent(name, withSkills(list.join(", "), prompt), {
|
|
48255
|
-
...options,
|
|
48256
|
-
skills: list
|
|
48257
|
-
});
|
|
48258
|
-
};
|
|
48273
|
+
const agentWithSkills = (name, prompt, options = {}) => agent(name, withSkills(skillsFor(name, options.skills).join(", "), prompt), options);
|
|
48259
48274
|
const unwindFailure = (commit) => `gtd could not unwind ${commit} out of your working tree.`;
|
|
48260
48275
|
const idleMessage = () => `No active gtd process.
|
|
48261
48276
|
|
|
@@ -48381,7 +48396,7 @@ ${questionBarReturn}
|
|
|
48381
48396
|
never re-open one. A genuinely open PRODUCT point may still
|
|
48382
48397
|
raise a fresh \`## Open Questions\` entry: the product gate sits
|
|
48383
48398
|
on this path exactly as on the first lap
|
|
48384
|
-
- Before grouping anything, run \`${
|
|
48399
|
+
- Before grouping anything, run \`${env.testCommand}\`
|
|
48385
48400
|
yourself — a loop-back runs no green-baseline gate, so the
|
|
48386
48401
|
tree may already be red (reverting the edit can undo a fix it
|
|
48387
48402
|
made). If red, make that breakage the first concern, ahead of
|
|
@@ -48899,10 +48914,13 @@ every commit is machine-authored.
|
|
|
48899
48914
|
high-level architectural changes — never which files changed; \`git
|
|
48900
48915
|
diff --stat\` is for that, not this message
|
|
48901
48916
|
- If the range removes or renames a documented config key, CLI flag, or
|
|
48902
|
-
workflow state
|
|
48903
|
-
|
|
48904
|
-
|
|
48905
|
-
|
|
48917
|
+
workflow state, OR makes a previously-accepted config value now fail to
|
|
48918
|
+
load (a stricter validation, an escape a value must now spell differently)
|
|
48919
|
+
— a public surface, not an internal one, either way — add a literal
|
|
48920
|
+
\`BREAKING CHANGE:\` footer naming what breaks and, for the second case,
|
|
48921
|
+
the exact escape or rewrite a committed \`.gtdrc\` needs. A \`!\` on the
|
|
48922
|
+
type prefix alone (\`feat!:\`/\`refactor!:\`) is NOT enough: this repo's
|
|
48923
|
+
own commit-analyzer has missed that marker before, cutting no release
|
|
48906
48924
|
until a follow-up commit carried the literal footer instead
|
|
48907
48925
|
|
|
48908
48926
|
The process's entry commit is \`${it.entryCommit}\`. ${human}Inspect the range: \`git log ${it.processBase}..${it.processTip}\`
|
|
@@ -48923,24 +48941,24 @@ const ARCHITECTURE = ".gtd/ARCHITECTURE.md";
|
|
|
48923
48941
|
const REVIEW = ".gtd/REVIEW.md";
|
|
48924
48942
|
const QUALITY = ".gtd/QUALITY.md";
|
|
48925
48943
|
const SPEC_FEEDBACK = ".gtd/SPEC_FEEDBACK.md";
|
|
48926
|
-
const planner = () =>
|
|
48927
|
-
const coder = () =>
|
|
48944
|
+
const planner = () => env.plannerModel ?? "";
|
|
48945
|
+
const coder = () => env.coderModel ?? "";
|
|
48928
48946
|
/** Turn the sketch since `base` into `.gtd/REQUIREMENTS.md`. */
|
|
48929
|
-
const triage$1 = (base) => agentWithSkills("triage",
|
|
48947
|
+
const triage$1 = (base) => agentWithSkills("triage", designTriagePrompt(base), {
|
|
48930
48948
|
label: "Triaging the change",
|
|
48931
48949
|
file: REQUIREMENTS,
|
|
48932
48950
|
mode: "qa",
|
|
48933
48951
|
model: planner(),
|
|
48934
48952
|
system: designSystem()
|
|
48935
48953
|
});
|
|
48936
|
-
const author = () => agentWithSkills("author",
|
|
48954
|
+
const author = () => agentWithSkills("author", architectureAuthorPrompt(), {
|
|
48937
48955
|
label: "Refining the technical plan",
|
|
48938
48956
|
file: ARCHITECTURE,
|
|
48939
48957
|
mode: "qa",
|
|
48940
48958
|
model: planner(),
|
|
48941
48959
|
system: architectSystem()
|
|
48942
48960
|
});
|
|
48943
|
-
const decompose = () => agentWithSkills("decompose",
|
|
48961
|
+
const decompose = () => agentWithSkills("decompose", architectureDecomposePrompt(), {
|
|
48944
48962
|
label: "Decomposing into packages",
|
|
48945
48963
|
model: planner(),
|
|
48946
48964
|
system: architectSystem()
|
|
@@ -48959,37 +48977,37 @@ const answerTechnicalQuestions = () => human("gate.answer", {
|
|
|
48959
48977
|
mode: "qa",
|
|
48960
48978
|
acceptClean: true
|
|
48961
48979
|
});
|
|
48962
|
-
const build = (pkg) => agentWithSkills("building",
|
|
48980
|
+
const build = (pkg) => agentWithSkills("building", packagesItemBuildingPrompt(pkg), {
|
|
48963
48981
|
label: "Building",
|
|
48964
48982
|
model: coder(),
|
|
48965
48983
|
system: builderSystem()
|
|
48966
48984
|
});
|
|
48967
|
-
const fixSuite = () => agentWithSkills("fix-suite",
|
|
48985
|
+
const fixSuite = () => agentWithSkills("fix-suite", packagesItemFixSuitePrompt(), {
|
|
48968
48986
|
label: "Fixing the check",
|
|
48969
48987
|
file: FEEDBACK,
|
|
48970
48988
|
model: coder(),
|
|
48971
48989
|
system: builderSystem()
|
|
48972
48990
|
});
|
|
48973
|
-
const fixSpec = (pkg) => agentWithSkills("fix-spec",
|
|
48991
|
+
const fixSpec = (pkg) => agentWithSkills("fix-spec", packagesItemFixSpecPrompt(pkg), {
|
|
48974
48992
|
label: "Fixing review feedback",
|
|
48975
48993
|
file: SPEC_FEEDBACK,
|
|
48976
48994
|
model: coder(),
|
|
48977
48995
|
system: builderSystem()
|
|
48978
48996
|
});
|
|
48979
48997
|
/** Review `pkg` against its spec, focused on the `failing` sections the pre-judge could not clear. */
|
|
48980
|
-
const reviewPackage = (pkg, failing = []) => agentWithSkills("spec.review",
|
|
48998
|
+
const reviewPackage = (pkg, failing = []) => agentWithSkills("spec.review", packagesItemSpecReviewPrompt(pkg, failing), {
|
|
48981
48999
|
label: "Reviewing the package",
|
|
48982
49000
|
model: planner(),
|
|
48983
49001
|
system: specReviewerSystem(),
|
|
48984
49002
|
allowEmpty: true
|
|
48985
49003
|
});
|
|
48986
|
-
const fix = () => agentWithSkills("fix",
|
|
49004
|
+
const fix = () => agentWithSkills("fix", buildFixPrompt(), {
|
|
48987
49005
|
label: "Fixing the check",
|
|
48988
49006
|
file: FEEDBACK,
|
|
48989
49007
|
model: coder(),
|
|
48990
49008
|
system: finisherSystem()
|
|
48991
49009
|
});
|
|
48992
|
-
const describeEscalation = () => agentWithSkills("health.describe",
|
|
49010
|
+
const describeEscalation = () => agentWithSkills("health.describe", healthDescribePrompt(), {
|
|
48993
49011
|
label: "Describing the escalation",
|
|
48994
49012
|
file: FEEDBACK,
|
|
48995
49013
|
model: coder(),
|
|
@@ -49008,15 +49026,21 @@ const escalationExhausted = () => human("health.exhausted", {
|
|
|
49008
49026
|
file: ESCALATION,
|
|
49009
49027
|
acceptClean: true
|
|
49010
49028
|
});
|
|
49011
|
-
/**
|
|
49012
|
-
|
|
49029
|
+
/**
|
|
49030
|
+
* One quality review, through the skill `lens`. `lens` rides as this turn's
|
|
49031
|
+
* own `skills` option — a `.gtdrc` `build.quality.reviewing` entry still
|
|
49032
|
+
* overrides it (config beats a flow-supplied list same as any other step),
|
|
49033
|
+
* but absent one the lens itself is what the turn loads by default.
|
|
49034
|
+
*/
|
|
49035
|
+
const reviewQuality = (lens) => agentWithSkills("quality.reviewing", buildQualityReviewingPrompt(lens), {
|
|
49013
49036
|
label: "Reviewing (one quality lens)",
|
|
49014
49037
|
file: QUALITY,
|
|
49015
49038
|
model: planner(),
|
|
49016
49039
|
system: reviewerSystem(),
|
|
49017
|
-
allowEmpty: true
|
|
49040
|
+
allowEmpty: true,
|
|
49041
|
+
skills: [lens]
|
|
49018
49042
|
});
|
|
49019
|
-
const fixQuality = () => agentWithSkills("fix-quality",
|
|
49043
|
+
const fixQuality = () => agentWithSkills("fix-quality", buildFixQualityPrompt(), {
|
|
49020
49044
|
label: "Fixing quality findings",
|
|
49021
49045
|
file: QUALITY,
|
|
49022
49046
|
model: coder(),
|
|
@@ -49024,7 +49048,7 @@ const fixQuality = () => agentWithSkills("fix-quality", vars.reviewFixSkills, bu
|
|
|
49024
49048
|
allowEmpty: true
|
|
49025
49049
|
});
|
|
49026
49050
|
/** Write `.gtd/REVIEW.md` over everything since `base`, carrying the answers of commit `carry` when given. */
|
|
49027
|
-
const reviewing = (base, carry) => agentWithSkills("review.reviewing",
|
|
49051
|
+
const reviewing = (base, carry) => agentWithSkills("review.reviewing", buildReviewReviewingPrompt(base, carry), {
|
|
49028
49052
|
label: "Reviewing",
|
|
49029
49053
|
file: REVIEW,
|
|
49030
49054
|
mode: "review",
|
|
@@ -49033,7 +49057,7 @@ const reviewing = (base, carry) => agentWithSkills("review.reviewing", vars.revi
|
|
|
49033
49057
|
base
|
|
49034
49058
|
});
|
|
49035
49059
|
/** Answer every `question` note inline in `.gtd/REVIEW.md`. */
|
|
49036
|
-
const answerReviewQuestions = (notes) => agentWithSkills("review.answer-review-questions",
|
|
49060
|
+
const answerReviewQuestions = (notes) => agentWithSkills("review.answer-review-questions", buildReviewAnswerQuestionsPrompt(notes), {
|
|
49037
49061
|
label: "Answering your questions",
|
|
49038
49062
|
file: REVIEW,
|
|
49039
49063
|
mode: "review",
|
|
@@ -49044,7 +49068,7 @@ const answerReviewQuestions = (notes) => agentWithSkills("review.answer-review-q
|
|
|
49044
49068
|
* Fix every `nit` note in one turn. Its name puts it in the `build.review`
|
|
49045
49069
|
* conversation, which has one identity — so it runs as the reviewer, not the coder.
|
|
49046
49070
|
*/
|
|
49047
|
-
const fixNits = (notes) => agentWithSkills("review.fix-nits",
|
|
49071
|
+
const fixNits = (notes) => agentWithSkills("review.fix-nits", buildReviewFixNitsPrompt(notes), {
|
|
49048
49072
|
label: "Fixing your nits",
|
|
49049
49073
|
file: REVIEW,
|
|
49050
49074
|
model: planner(),
|
|
@@ -49063,7 +49087,7 @@ const reviewMissing = (round) => human("review.review-missing", {
|
|
|
49063
49087
|
label: "Nothing to review"
|
|
49064
49088
|
});
|
|
49065
49089
|
/** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
|
|
49066
|
-
const collecting = (capture) => agentWithSkills("review.collecting",
|
|
49090
|
+
const collecting = (capture) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture), {
|
|
49067
49091
|
label: "Collecting your feedback",
|
|
49068
49092
|
file: REQUIREMENTS,
|
|
49069
49093
|
mode: "qa",
|
|
@@ -49072,7 +49096,7 @@ const collecting = (capture) => agentWithSkills("review.collecting", vars.review
|
|
|
49072
49096
|
allowEmpty: true
|
|
49073
49097
|
});
|
|
49074
49098
|
/** Run the suite as step `name`; resolves `true` when it passed. A failure is in `.gtd/FEEDBACK.md`. */
|
|
49075
|
-
const baseline = (name, label = "Checking the baseline") => check(name,
|
|
49099
|
+
const baseline = (name, label = "Checking the baseline") => check(name, env.testCommand ?? "", {
|
|
49076
49100
|
report: FEEDBACK,
|
|
49077
49101
|
label
|
|
49078
49102
|
});
|
|
@@ -49124,7 +49148,7 @@ const healthy = async (fix, options = {}) => {
|
|
|
49124
49148
|
let fixes = options.fixesSoFar ?? 0;
|
|
49125
49149
|
let previous;
|
|
49126
49150
|
for (;;) {
|
|
49127
|
-
if (await check("health.check",
|
|
49151
|
+
if (await check("health.check", env.testCommand ?? "", {
|
|
49128
49152
|
report: ".gtd/FEEDBACK.md",
|
|
49129
49153
|
label: "Running checks",
|
|
49130
49154
|
sweepOnGreen: [".gtd/ESCALATION.md"]
|
|
@@ -49528,8 +49552,8 @@ const architecturePass = async () => {
|
|
|
49528
49552
|
};
|
|
49529
49553
|
//#endregion
|
|
49530
49554
|
//#region src/workflows/review.ts
|
|
49531
|
-
/** The
|
|
49532
|
-
const qualityLenses = () =>
|
|
49555
|
+
/** The lenses the quality lap reviews with, one turn each: the `qualityReviews` var, split on `,` and trimmed. Unlike a `skills:` entry, this fans out into one whole turn per entry rather than naming one step's skill list — see `build.quality.reviewing` in `./skills.ts` for the (separate) skills a lens turn itself loads. */
|
|
49556
|
+
const qualityLenses = () => (vars.qualityReviews ?? "").split(",").map((lens) => lens.trim()).filter((lens) => lens.length > 0);
|
|
49533
49557
|
/**
|
|
49534
49558
|
* One review turn per lens over the whole change, each appending what it
|
|
49535
49559
|
* finds blocking to `.gtd/QUALITY.md`. Resolves `"findings"` when that file
|
|
@@ -49751,27 +49775,46 @@ const buildTail = (fixFirst, base) => scope("build", async () => {
|
|
|
49751
49775
|
});
|
|
49752
49776
|
//#endregion
|
|
49753
49777
|
//#region src/workflows/vars.ts
|
|
49778
|
+
/** Process settings. */
|
|
49754
49779
|
const defaults = {
|
|
49755
|
-
testCommand: "npm test",
|
|
49756
|
-
plannerModel: "smart",
|
|
49757
|
-
coderModel: "base",
|
|
49758
49780
|
reviewBase: "",
|
|
49759
49781
|
judgeBudgetBytes: "32768",
|
|
49760
49782
|
judgeIdenticalMinP: "0.7",
|
|
49761
49783
|
specPreJudge: "0.9",
|
|
49762
49784
|
reviewNoteActionable: "0.7",
|
|
49763
49785
|
architectureSkipMinP: "0.85",
|
|
49764
|
-
triageSkills: "spec-driven-development, planning-and-task-breakdown",
|
|
49765
|
-
architectureSkills: "api-and-interface-design, documentation-and-adrs, ponytail",
|
|
49766
|
-
decomposeSkills: "incremental-implementation, planning-and-task-breakdown",
|
|
49767
|
-
buildSkills: "test-driven-development, incremental-implementation",
|
|
49768
|
-
fixSkills: "debugging-and-error-recovery",
|
|
49769
|
-
reviewFixSkills: "incremental-implementation, code-simplification",
|
|
49770
|
-
reviewSkills: "code-review-and-quality",
|
|
49771
|
-
specReviewSkills: "code-review-and-quality, spec-driven-development",
|
|
49772
|
-
escalateSkills: "debugging-and-error-recovery",
|
|
49773
49786
|
qualityReviews: "owasp-security, ponytail-review, test-audit"
|
|
49774
49787
|
};
|
|
49788
|
+
/** Environment settings. */
|
|
49789
|
+
const envDefaults = {
|
|
49790
|
+
testCommand: "npm test",
|
|
49791
|
+
plannerModel: "smart",
|
|
49792
|
+
coderModel: "base"
|
|
49793
|
+
};
|
|
49794
|
+
//#endregion
|
|
49795
|
+
//#region src/workflows/skills.ts
|
|
49796
|
+
const skills = {
|
|
49797
|
+
"design.triage": ["spec-driven-development", "planning-and-task-breakdown"],
|
|
49798
|
+
"architecture.author": [
|
|
49799
|
+
"api-and-interface-design",
|
|
49800
|
+
"documentation-and-adrs",
|
|
49801
|
+
"ponytail"
|
|
49802
|
+
],
|
|
49803
|
+
"architecture.decompose": ["incremental-implementation", "planning-and-task-breakdown"],
|
|
49804
|
+
"packages.item.building": ["test-driven-development", "incremental-implementation"],
|
|
49805
|
+
"packages.item.fix-suite": ["debugging-and-error-recovery"],
|
|
49806
|
+
"packages.item.fix-spec": ["incremental-implementation", "code-simplification"],
|
|
49807
|
+
"packages.item.spec.review": ["code-review-and-quality", "spec-driven-development"],
|
|
49808
|
+
"packages.item.health.describe": ["debugging-and-error-recovery"],
|
|
49809
|
+
"build.fix": ["debugging-and-error-recovery"],
|
|
49810
|
+
"build.health.describe": ["debugging-and-error-recovery"],
|
|
49811
|
+
"build.quality.reviewing": [],
|
|
49812
|
+
"build.fix-quality": ["incremental-implementation", "code-simplification"],
|
|
49813
|
+
"build.review.reviewing": ["code-review-and-quality"],
|
|
49814
|
+
"build.review.answer-review-questions": ["code-review-and-quality"],
|
|
49815
|
+
"build.review.fix-nits": ["incremental-implementation", "code-simplification"],
|
|
49816
|
+
"build.review.collecting": ["code-review-and-quality"]
|
|
49817
|
+
};
|
|
49775
49818
|
//#endregion
|
|
49776
49819
|
//#region src/workflows/unified.ts
|
|
49777
49820
|
var unified_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -49802,6 +49845,7 @@ var unified_exports = /* @__PURE__ */ __exportAll({
|
|
|
49802
49845
|
defaults: () => defaults,
|
|
49803
49846
|
describeEscalation: () => describeEscalation,
|
|
49804
49847
|
design: () => design,
|
|
49848
|
+
envDefaults: () => envDefaults,
|
|
49805
49849
|
escalate: () => escalate,
|
|
49806
49850
|
escalation: () => escalation,
|
|
49807
49851
|
escalationExhausted: () => escalationExhausted,
|
|
@@ -49827,6 +49871,7 @@ var unified_exports = /* @__PURE__ */ __exportAll({
|
|
|
49827
49871
|
reviewPackage: () => reviewPackage,
|
|
49828
49872
|
reviewQuality: () => reviewQuality,
|
|
49829
49873
|
reviewing: () => reviewing,
|
|
49874
|
+
skills: () => skills,
|
|
49830
49875
|
specReview: () => specReview,
|
|
49831
49876
|
steering: () => steering,
|
|
49832
49877
|
summary: () => summary,
|
|
@@ -50615,7 +50660,17 @@ var Workspace = class Workspace extends Tag("Workspace")() {
|
|
|
50615
50660
|
/** The `vars:` shape: a flat name -> scalar map (`compileVarsMap` coerces every scalar to a string). */
|
|
50616
50661
|
const varsJsonSchema = {
|
|
50617
50662
|
type: "object",
|
|
50618
|
-
description: "Flat name -> scalar map merged into the workflow's
|
|
50663
|
+
description: "Flat name -> scalar map merged into the workflow's process settings (defaults), pinned for the whole process at its start. Scalars are coerced to strings.",
|
|
50664
|
+
additionalProperties: { type: [
|
|
50665
|
+
"string",
|
|
50666
|
+
"number",
|
|
50667
|
+
"boolean"
|
|
50668
|
+
] }
|
|
50669
|
+
};
|
|
50670
|
+
/** The `env:` shape: environment settings, read live on every invocation and never pinned to a process. */
|
|
50671
|
+
const envJsonSchema = {
|
|
50672
|
+
type: "object",
|
|
50673
|
+
description: "Flat name -> scalar map merged into the workflow's environment settings (envDefaults) — values that change how a step runs on this machine, like the test command or a model hint. Read fresh on every gtd call, never pinned to a process. GTD_<NAME> environment variables override these entries. Scalars are coerced to strings.",
|
|
50619
50674
|
additionalProperties: { type: [
|
|
50620
50675
|
"string",
|
|
50621
50676
|
"number",
|
|
@@ -50643,6 +50698,15 @@ const modesJsonSchema = {
|
|
|
50643
50698
|
}
|
|
50644
50699
|
}
|
|
50645
50700
|
};
|
|
50701
|
+
/** The `skills:` shape: full step name -> its skill list (`compileSkillsMap`). An entry REPLACES the step's bundled list wholesale, never adds to it — and the schema can validate an entry's VALUE but never its KEY, since step names come from the workflow in play, not a fixed set. */
|
|
50702
|
+
const skillsJsonSchema = {
|
|
50703
|
+
type: "object",
|
|
50704
|
+
description: "Flat step full-name -> skill-name array map. Each entry REPLACES the named step's bundled skill list wholesale (never merges into it); [] means no skills at all for that step, and no preamble. A key naming a step the workflow in play does not declare is a load error listing the known names — the schema itself cannot validate a key, only a value's shape.",
|
|
50705
|
+
additionalProperties: {
|
|
50706
|
+
type: "array",
|
|
50707
|
+
items: { type: "string" }
|
|
50708
|
+
}
|
|
50709
|
+
};
|
|
50646
50710
|
/**
|
|
50647
50711
|
* A real (not `Unknown`) schema, unlike `vars`/`modes`: it is a flat settings
|
|
50648
50712
|
* struct with no per-mode map to compile, so its JSON Schema derives from the
|
|
@@ -50660,12 +50724,46 @@ const UiSchema = Struct({
|
|
|
50660
50724
|
key: optional$1(String$.annotations({ description: "Path to a TLS private key file, enabling HTTPS. Requires `cert` too." })),
|
|
50661
50725
|
format: optional$1(String$.annotations({ description: "Shell command run after every UI write, before it resolves ($GTD_FILE is the written file's absolute path). A non-zero exit or missing binary never reverts the write or refuses it — it's reported to the client as a notice naming the command and its exit code. Absent means no command runs at all." }))
|
|
50662
50726
|
}).annotations({ description: "Settings for `gtd ui`: where it listens, and optional TLS." });
|
|
50727
|
+
const JudgeSchema = Struct({
|
|
50728
|
+
provider: optional$1(Literal("fixed", "jev", "llm").annotations({ description: "Judge provider `gtd judge run` uses when no --provider flag and no GTD_JUDGE_PROVIDER is set. Absent means auto: jev when TYPESAFE_API_KEY is set, otherwise llm." })),
|
|
50729
|
+
model: optional$1(String$.annotations({ description: "Model for provider llm, used when no --model flag and no GTD_JUDGE_MODEL is set. Pairing it with another provider is an error." }))
|
|
50730
|
+
}).annotations({ description: "Which judge `gtd judge run` uses, when no flag or GTD_JUDGE_* variable says." });
|
|
50663
50731
|
const ConfigSchema = Struct({
|
|
50664
50732
|
vars: optional$1(Unknown.annotations({ jsonSchema: varsJsonSchema })),
|
|
50733
|
+
env: optional$1(Unknown.annotations({ jsonSchema: envJsonSchema })),
|
|
50734
|
+
judge: optional$1(JudgeSchema),
|
|
50665
50735
|
modes: optional$1(Unknown.annotations({ jsonSchema: modesJsonSchema })),
|
|
50666
|
-
ui: optional$1(UiSchema)
|
|
50736
|
+
ui: optional$1(UiSchema),
|
|
50737
|
+
skills: optional$1(Unknown.annotations({ jsonSchema: skillsJsonSchema }))
|
|
50667
50738
|
});
|
|
50668
50739
|
//#endregion
|
|
50740
|
+
//#region src/workflow/vars.ts
|
|
50741
|
+
const PREFIX = "GTD_";
|
|
50742
|
+
/**
|
|
50743
|
+
* One kind of setting, later wins: the workflow's own defaults, the `.gtdrc`
|
|
50744
|
+
* map (`vars:` or `env:`), `--var` overrides (process settings only; environment
|
|
50745
|
+
* settings pass `{}`), then `GTD_<NAME>` for any name an earlier layer declared
|
|
50746
|
+
* (a shell variable never introduces a name).
|
|
50747
|
+
*/
|
|
50748
|
+
const resolveVars = (workflowVars, rcVars, entryVars, env) => {
|
|
50749
|
+
const merged = {
|
|
50750
|
+
...workflowVars,
|
|
50751
|
+
...rcVars,
|
|
50752
|
+
...entryVars
|
|
50753
|
+
};
|
|
50754
|
+
for (const name of Object.keys(merged)) {
|
|
50755
|
+
const value = env[PREFIX + name.toUpperCase()];
|
|
50756
|
+
if (value !== void 0) merged[name] = value;
|
|
50757
|
+
}
|
|
50758
|
+
return merged;
|
|
50759
|
+
};
|
|
50760
|
+
/** The first process setting whose value spans lines — a `Gtd-Var:` trailer cannot carry it. */
|
|
50761
|
+
const multilineSetting = (vars) => Object.keys(vars).sort().find((name) => /[\r\n]/.test(vars[name]));
|
|
50762
|
+
const SETTING_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
50763
|
+
/** A name is written verbatim into a `Gtd-Var: <name>=<value>` trailer, so `=` or a newline would corrupt it. */
|
|
50764
|
+
const isSettingName = (name) => SETTING_NAME.test(name);
|
|
50765
|
+
const SETTING_NAME_RULE = "a setting name is a letter or \"_\", then letters, digits or \"_\"";
|
|
50766
|
+
//#endregion
|
|
50669
50767
|
//#region src/SteeringFormats.ts
|
|
50670
50768
|
/** The unrendered `validate:` template a workflow compiler seeds for a built-in mode — quoted so a path with spaces survives interpolation. */
|
|
50671
50769
|
const seededValidateCommand = (mode) => `gtd check ${mode} "$GTD_FILE"`;
|
|
@@ -50736,7 +50834,7 @@ const dedupeDiagnostics = (diagnostics) => {
|
|
|
50736
50834
|
};
|
|
50737
50835
|
//#endregion
|
|
50738
50836
|
//#region src/workflow/compile.ts
|
|
50739
|
-
const isPlainObject$
|
|
50837
|
+
const isPlainObject$3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
50740
50838
|
const describeType = (v) => {
|
|
50741
50839
|
if (v === null) return "null";
|
|
50742
50840
|
if (Array.isArray(v)) return "array";
|
|
@@ -50750,14 +50848,14 @@ const err = (path, message) => ({
|
|
|
50750
50848
|
origin: ""
|
|
50751
50849
|
});
|
|
50752
50850
|
/** A flat `name -> scalar` map; a malformed value is a load error and is dropped. */
|
|
50753
|
-
const compileVarsMap = (raw) => {
|
|
50851
|
+
const compileVarsMap = (raw, keyName) => {
|
|
50754
50852
|
const diagnostics = [];
|
|
50755
50853
|
if (raw === void 0) return {
|
|
50756
50854
|
vars: {},
|
|
50757
50855
|
diagnostics
|
|
50758
50856
|
};
|
|
50759
|
-
if (!isPlainObject$
|
|
50760
|
-
diagnostics.push(err([
|
|
50857
|
+
if (!isPlainObject$3(raw)) {
|
|
50858
|
+
diagnostics.push(err([keyName], `"${keyName}" must be a mapping of name -> scalar value, got ${describeType(raw)}`));
|
|
50761
50859
|
return {
|
|
50762
50860
|
vars: {},
|
|
50763
50861
|
diagnostics
|
|
@@ -50765,8 +50863,12 @@ const compileVarsMap = (raw) => {
|
|
|
50765
50863
|
}
|
|
50766
50864
|
const vars = {};
|
|
50767
50865
|
for (const [key, value] of Object.entries(raw)) {
|
|
50866
|
+
if (!isSettingName(key)) {
|
|
50867
|
+
diagnostics.push(err([keyName], `"${keyName}" key ${JSON.stringify(key)} is not a valid setting name — ${SETTING_NAME_RULE}`));
|
|
50868
|
+
continue;
|
|
50869
|
+
}
|
|
50768
50870
|
if (!isScalar(value)) {
|
|
50769
|
-
diagnostics.push(err([
|
|
50871
|
+
diagnostics.push(err([keyName, key], `"${keyName}.${key}" must be a string, number, or boolean, got ${describeType(value)}`));
|
|
50770
50872
|
continue;
|
|
50771
50873
|
}
|
|
50772
50874
|
vars[key] = String(value);
|
|
@@ -50776,10 +50878,75 @@ const compileVarsMap = (raw) => {
|
|
|
50776
50878
|
diagnostics
|
|
50777
50879
|
};
|
|
50778
50880
|
};
|
|
50881
|
+
const isStringArray$1 = (v) => Array.isArray(v) && v.every((entry) => typeof entry === "string");
|
|
50882
|
+
/** A flat `step full name -> skill list` map; a malformed value is a load error and is dropped. */
|
|
50883
|
+
const compileSkillsMap = (raw) => {
|
|
50884
|
+
const diagnostics = [];
|
|
50885
|
+
if (raw === void 0) return {
|
|
50886
|
+
skills: {},
|
|
50887
|
+
diagnostics
|
|
50888
|
+
};
|
|
50889
|
+
if (!isPlainObject$3(raw)) {
|
|
50890
|
+
diagnostics.push(err(["skills"], `"skills" must be a mapping of step name -> array of skill names, got ${describeType(raw)}`));
|
|
50891
|
+
return {
|
|
50892
|
+
skills: {},
|
|
50893
|
+
diagnostics
|
|
50894
|
+
};
|
|
50895
|
+
}
|
|
50896
|
+
const skills = Object.create(null);
|
|
50897
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
50898
|
+
if (!isStringArray$1(value)) {
|
|
50899
|
+
diagnostics.push(err(["skills", key], `"skills.${key}" must be an array of skill names (strings), got ${describeType(value)}`));
|
|
50900
|
+
continue;
|
|
50901
|
+
}
|
|
50902
|
+
skills[key] = value;
|
|
50903
|
+
}
|
|
50904
|
+
return {
|
|
50905
|
+
skills,
|
|
50906
|
+
diagnostics
|
|
50907
|
+
};
|
|
50908
|
+
};
|
|
50909
|
+
/**
|
|
50910
|
+
* The nine `*Skills` workflow vars this package deletes, each naming the
|
|
50911
|
+
* `.gtdrc` `skills:` step key(s) that replace it — four of the nine fan out to
|
|
50912
|
+
* several keys, because a var once shared between steps now addresses
|
|
50913
|
+
* them independently. Deliberately the only place gtd special-cases a var
|
|
50914
|
+
* name by string; meant to be deleted a major release after this ships.
|
|
50915
|
+
*/
|
|
50916
|
+
const DEAD_SKILLS_VARS = {
|
|
50917
|
+
triageSkills: ["design.triage"],
|
|
50918
|
+
architectureSkills: ["architecture.author"],
|
|
50919
|
+
decomposeSkills: ["architecture.decompose"],
|
|
50920
|
+
buildSkills: ["packages.item.building"],
|
|
50921
|
+
fixSkills: ["packages.item.fix-suite", "build.fix"],
|
|
50922
|
+
reviewFixSkills: [
|
|
50923
|
+
"packages.item.fix-spec",
|
|
50924
|
+
"build.fix-quality",
|
|
50925
|
+
"build.review.fix-nits"
|
|
50926
|
+
],
|
|
50927
|
+
reviewSkills: [
|
|
50928
|
+
"build.review.reviewing",
|
|
50929
|
+
"build.review.answer-review-questions",
|
|
50930
|
+
"build.review.collecting"
|
|
50931
|
+
],
|
|
50932
|
+
specReviewSkills: ["packages.item.spec.review"],
|
|
50933
|
+
escalateSkills: ["packages.item.health.describe", "build.health.describe"]
|
|
50934
|
+
};
|
|
50935
|
+
/** `vars:` entries naming one of the nine deleted `*Skills` vars — scoped to `.gtdrc` `vars:` only; there is no environment scan anywhere in this check, so a `GTD_BUILDSKILLS` gets neither this diagnostic nor any other. */
|
|
50936
|
+
const deadSkillsVarDiagnostics = (rawVars) => {
|
|
50937
|
+
if (!isPlainObject$3(rawVars)) return [];
|
|
50938
|
+
const diagnostics = [];
|
|
50939
|
+
for (const key of Object.keys(rawVars)) {
|
|
50940
|
+
const replacement = DEAD_SKILLS_VARS[key];
|
|
50941
|
+
if (replacement === void 0) continue;
|
|
50942
|
+
diagnostics.push(err(["vars", key], `"vars.${key}" was removed — skills are now configured per step, under the "skills:" key, addressed by: ${replacement.join(", ")}`));
|
|
50943
|
+
}
|
|
50944
|
+
return diagnostics;
|
|
50945
|
+
};
|
|
50779
50946
|
const MODE_COMMAND_KEYS = ["format", "validate"];
|
|
50780
50947
|
/** One `modes:` entry; malformed entries are load errors and are dropped. */
|
|
50781
50948
|
const compileMode = (name, entry, diagnostics) => {
|
|
50782
|
-
if (!isPlainObject$
|
|
50949
|
+
if (!isPlainObject$3(entry)) {
|
|
50783
50950
|
diagnostics.push(err(["modes", name], `mode "${name}": must be an object with "format" and/or "validate", got ${describeType(entry)}`));
|
|
50784
50951
|
return;
|
|
50785
50952
|
}
|
|
@@ -50809,7 +50976,7 @@ const compileModesMap = (raw) => {
|
|
|
50809
50976
|
modes: {},
|
|
50810
50977
|
diagnostics
|
|
50811
50978
|
};
|
|
50812
|
-
if (!isPlainObject$
|
|
50979
|
+
if (!isPlainObject$3(raw)) {
|
|
50813
50980
|
diagnostics.push(err(["modes"], `"modes" must be a mapping of mode name -> { format, validate }, got ${describeType(raw)}`));
|
|
50814
50981
|
return {
|
|
50815
50982
|
modes: {},
|
|
@@ -50838,7 +51005,7 @@ const mergeModes = (base, override) => {
|
|
|
50838
51005
|
};
|
|
50839
51006
|
/** Stamp a whole (possibly deeply nested) value with ONE origin, recursively — used when `inner` replaces `base` wholesale: every key inside still needs its own node, or a LATER layer partially overriding just one nested key would silently lose the rest of this subtree's origin. */
|
|
50840
51007
|
const uniformOrigin = (value, origin) => {
|
|
50841
|
-
if (!isPlainObject$
|
|
51008
|
+
if (!isPlainObject$3(value)) return { self: origin };
|
|
50842
51009
|
const children = {};
|
|
50843
51010
|
for (const [key, v] of Object.entries(value)) children[key] = uniformOrigin(v, origin);
|
|
50844
51011
|
return {
|
|
@@ -50856,7 +51023,7 @@ const uniformOrigin = (value, origin) => {
|
|
|
50856
51023
|
* findings, not one origin for the whole merged key.
|
|
50857
51024
|
*/
|
|
50858
51025
|
const mergeWithOrigin = (base, baseOrigin, inner, innerOrigin) => {
|
|
50859
|
-
if (!isPlainObject$
|
|
51026
|
+
if (!isPlainObject$3(base) || !isPlainObject$3(inner)) return {
|
|
50860
51027
|
value: inner,
|
|
50861
51028
|
origin: uniformOrigin(inner, innerOrigin)
|
|
50862
51029
|
};
|
|
@@ -50896,9 +51063,16 @@ const withOrigin = (diagnostics, lookup) => diagnostics.map((d) => ({
|
|
|
50896
51063
|
...d,
|
|
50897
51064
|
origin: lookup(d.path)
|
|
50898
51065
|
}));
|
|
51066
|
+
const layerKeys = (layers, name) => layers.flatMap((layer) => {
|
|
51067
|
+
const own = isPlainObject$3(layer.value) ? layer.value[name] : void 0;
|
|
51068
|
+
return isPlainObject$3(own) ? Object.keys(own).map((key) => ({
|
|
51069
|
+
key,
|
|
51070
|
+
origin: layer.origin
|
|
51071
|
+
})) : [];
|
|
51072
|
+
});
|
|
50899
51073
|
/**
|
|
50900
51074
|
* Merge every layer outermost→innermost and compile what a `.gtdrc` may carry:
|
|
50901
|
-
* `vars`, `modes` and `ui`. A workflow is defined only in `gtd.config.ts`, so a
|
|
51075
|
+
* `vars`, `env`, `judge`, `modes` and `ui`. A workflow is defined only in `gtd.config.ts`, so a
|
|
50902
51076
|
* leftover `workflow:` key is an error pointing there. Pure and total.
|
|
50903
51077
|
*/
|
|
50904
51078
|
const compileConfig = (layers) => {
|
|
@@ -50907,7 +51081,7 @@ const compileConfig = (layers) => {
|
|
|
50907
51081
|
let merged = {};
|
|
50908
51082
|
let originTree = { self: BUILT_IN_ORIGIN };
|
|
50909
51083
|
for (const layer of layers) {
|
|
50910
|
-
const value = isPlainObject$
|
|
51084
|
+
const value = isPlainObject$3(layer.value) ? layer.value : {};
|
|
50911
51085
|
const step = mergeWithOrigin(merged, originTree, value, layer.origin);
|
|
50912
51086
|
merged = step.value;
|
|
50913
51087
|
originTree = step.origin;
|
|
@@ -50915,37 +51089,100 @@ const compileConfig = (layers) => {
|
|
|
50915
51089
|
const mergedConfig = merged;
|
|
50916
51090
|
const lookupIn = (path) => originAt(originTree, path);
|
|
50917
51091
|
const ui = mergedConfig["ui"];
|
|
50918
|
-
const
|
|
51092
|
+
const judge = mergedConfig["judge"];
|
|
51093
|
+
const { vars: rcVars, diagnostics: varsDiagnostics } = compileVarsMap(mergedConfig["vars"], "vars");
|
|
50919
51094
|
diagnostics.push(...withOrigin(varsDiagnostics, lookupIn));
|
|
51095
|
+
const { vars: rcEnv, diagnostics: envDiagnostics } = compileVarsMap(mergedConfig["env"], "env");
|
|
51096
|
+
diagnostics.push(...withOrigin(envDiagnostics, lookupIn));
|
|
51097
|
+
const varsKeys = layerKeys(layers, "vars");
|
|
51098
|
+
const envKeys = layerKeys(layers, "env");
|
|
51099
|
+
diagnostics.push(...withOrigin(deadSkillsVarDiagnostics(mergedConfig["vars"]), lookupIn));
|
|
50920
51100
|
const { modes: rcModes, diagnostics: modesDiagnostics } = compileModesMap(mergedConfig["modes"]);
|
|
50921
51101
|
diagnostics.push(...withOrigin(modesDiagnostics, lookupIn));
|
|
51102
|
+
const skillsKeys = [];
|
|
51103
|
+
for (const layer of layers) {
|
|
51104
|
+
if (!isPlainObject$3(layer.value)) continue;
|
|
51105
|
+
const own = compileSkillsMap(layer.value["skills"]);
|
|
51106
|
+
diagnostics.push(...own.diagnostics.map((d) => ({
|
|
51107
|
+
...d,
|
|
51108
|
+
origin: layer.origin
|
|
51109
|
+
})));
|
|
51110
|
+
skillsKeys.push(...Object.keys(own.skills).map((key) => ({
|
|
51111
|
+
key,
|
|
51112
|
+
origin: layer.origin
|
|
51113
|
+
})));
|
|
51114
|
+
}
|
|
51115
|
+
const { skills: rcSkills } = compileSkillsMap(mergedConfig["skills"]);
|
|
50922
51116
|
const seeded = Object.fromEntries(BUILT_IN_MODE_NAMES.map((name) => [name, { validate: seededValidateCommand(name) }]));
|
|
50923
51117
|
return {
|
|
50924
51118
|
rcVars,
|
|
51119
|
+
rcEnv,
|
|
51120
|
+
varsKeys,
|
|
51121
|
+
envKeys,
|
|
51122
|
+
...judge !== void 0 ? { judge } : {},
|
|
50925
51123
|
modes: mergeModes(seeded, rcModes),
|
|
50926
51124
|
...ui !== void 0 ? { ui } : {},
|
|
51125
|
+
rcSkills,
|
|
51126
|
+
skillsKeys,
|
|
50927
51127
|
diagnostics: dedupeDiagnostics(sortDiagnostics(diagnostics, layerOrder))
|
|
50928
51128
|
};
|
|
50929
51129
|
};
|
|
50930
51130
|
//#endregion
|
|
50931
|
-
//#region src/workflow/
|
|
50932
|
-
const
|
|
51131
|
+
//#region src/workflow/interpolate.ts
|
|
51132
|
+
const VAR_PATTERN = /\$\$|\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
51133
|
+
const isPlainObject$2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
50933
51134
|
/**
|
|
50934
|
-
*
|
|
50935
|
-
*
|
|
50936
|
-
*
|
|
51135
|
+
* `modes.*.format`, `modes.*.validate` and `ui.format` — gtd hands these
|
|
51136
|
+
* straight to bash, where `$` already means the shell's own expansion. The
|
|
51137
|
+
* predicate reads only the config PATH shape, never the value: sniffing for
|
|
51138
|
+
* `$GTD_FILE` would exempt whichever command happens not to name it, and
|
|
51139
|
+
* silently interpolate one that names something else instead.
|
|
50937
51140
|
*/
|
|
50938
|
-
const
|
|
50939
|
-
|
|
50940
|
-
|
|
50941
|
-
|
|
50942
|
-
|
|
50943
|
-
|
|
50944
|
-
|
|
50945
|
-
|
|
50946
|
-
|
|
51141
|
+
const isBashExemptPath = (path) => {
|
|
51142
|
+
if (path.length === 3 && path[0] === "modes" && (path[2] === "format" || path[2] === "validate")) return true;
|
|
51143
|
+
if (path.length === 2 && path[0] === "ui" && path[1] === "format") return true;
|
|
51144
|
+
return false;
|
|
51145
|
+
};
|
|
51146
|
+
const interpolateString = (value, path, env, diagnostics) => value.replace(VAR_PATTERN, (match, braced, bare) => {
|
|
51147
|
+
if (match === "$$") return "$";
|
|
51148
|
+
const name = braced ?? bare;
|
|
51149
|
+
if (name === void 0) return match;
|
|
51150
|
+
const resolved = env[name];
|
|
51151
|
+
if (resolved !== void 0) return resolved;
|
|
51152
|
+
diagnostics.push({
|
|
51153
|
+
severity: "error",
|
|
51154
|
+
path,
|
|
51155
|
+
message: `"${match}" references environment variable "${name}", which is not set`,
|
|
51156
|
+
origin: ""
|
|
51157
|
+
});
|
|
51158
|
+
return match;
|
|
51159
|
+
});
|
|
51160
|
+
const walk = (value, path, env, diagnostics) => {
|
|
51161
|
+
if (typeof value === "string") return isBashExemptPath(path) ? value : interpolateString(value, path, env, diagnostics);
|
|
51162
|
+
if (Array.isArray(value)) return value.map((v, i) => walk(v, [...path, i], env, diagnostics));
|
|
51163
|
+
if (isPlainObject$2(value)) {
|
|
51164
|
+
const out = {};
|
|
51165
|
+
for (const [key, v] of Object.entries(value)) out[key] = walk(v, [...path, key], env, diagnostics);
|
|
51166
|
+
return out;
|
|
50947
51167
|
}
|
|
50948
|
-
return
|
|
51168
|
+
return value;
|
|
51169
|
+
};
|
|
51170
|
+
/**
|
|
51171
|
+
* Expand `$NAME`/`${NAME}` from `env` over every string leaf of a decoded
|
|
51172
|
+
* `.gtdrc` layer, except the three paths gtd hands to bash. `$$` yields a
|
|
51173
|
+
* literal `$`; any other `$` not followed by a valid name (a trailing `$`,
|
|
51174
|
+
* `$1`, `$-`, `$ `) is left untouched. Exactly one pass: `String.replace`
|
|
51175
|
+
* never rescans a replacement's own text, so an environment variable's value
|
|
51176
|
+
* can never be read back as config syntax. An unset name is a load-error
|
|
51177
|
+
* `Diagnostic` naming the config path; the original `$NAME` text is left in
|
|
51178
|
+
* place rather than blanked.
|
|
51179
|
+
*/
|
|
51180
|
+
const interpolate = (config, env) => {
|
|
51181
|
+
const diagnostics = [];
|
|
51182
|
+
return {
|
|
51183
|
+
value: walk(config, [], env, diagnostics),
|
|
51184
|
+
diagnostics
|
|
51185
|
+
};
|
|
50949
51186
|
};
|
|
50950
51187
|
//#endregion
|
|
50951
51188
|
//#region node_modules/yaml/dist/nodes/identity.js
|
|
@@ -57718,7 +57955,7 @@ const dropOptionalUndefinedArtifacts = (all) => {
|
|
|
57718
57955
|
* findings surface together through `load`'s single sorted/deduped report,
|
|
57719
57956
|
* the same as every other producer in this package.
|
|
57720
57957
|
*/
|
|
57721
|
-
const decodeLevel = (level) => gen(function* () {
|
|
57958
|
+
const decodeLevel = (level, env) => gen(function* () {
|
|
57722
57959
|
if (!isPlainObject$1(level.config)) return {
|
|
57723
57960
|
layer: void 0,
|
|
57724
57961
|
diagnostics: [{
|
|
@@ -57739,53 +57976,83 @@ const decodeLevel = (level) => gen(function* () {
|
|
|
57739
57976
|
origin: level.filepath
|
|
57740
57977
|
}))
|
|
57741
57978
|
};
|
|
57979
|
+
const interpolated = interpolate(result.right, env);
|
|
57742
57980
|
return {
|
|
57743
57981
|
layer: {
|
|
57744
57982
|
origin: level.filepath,
|
|
57745
57983
|
dir: dirname(level.filepath),
|
|
57746
|
-
value:
|
|
57984
|
+
value: interpolated.value
|
|
57747
57985
|
},
|
|
57748
|
-
diagnostics:
|
|
57986
|
+
diagnostics: interpolated.diagnostics.map((d) => ({
|
|
57987
|
+
...d,
|
|
57988
|
+
origin: level.filepath
|
|
57989
|
+
}))
|
|
57749
57990
|
};
|
|
57750
57991
|
});
|
|
57992
|
+
/** Everything the `.gtdrc` half of a load reads, findings not yet judged. */
|
|
57993
|
+
const readRc = gen(function* () {
|
|
57994
|
+
const narrator = yield* Narrator;
|
|
57995
|
+
const host = yield* Host;
|
|
57996
|
+
const levels = yield* (yield* ConfigDiscovery).levels(host.root, host.home);
|
|
57997
|
+
for (const level of levels) yield* narrator.narrate(`config: layer ${level.filepath}`);
|
|
57998
|
+
const decoded = yield* forEach$2(levels, (level) => decodeLevel(level, host.env));
|
|
57999
|
+
return {
|
|
58000
|
+
levels,
|
|
58001
|
+
compiled: compileConfig(decoded.flatMap((d) => d.layer !== void 0 ? [d.layer] : [])),
|
|
58002
|
+
decodeDiagnostics: decoded.flatMap((d) => d.diagnostics)
|
|
58003
|
+
};
|
|
58004
|
+
});
|
|
58005
|
+
const failOnErrors = (diagnostics) => {
|
|
58006
|
+
const fatal = diagnostics.filter((d) => d.severity === "error");
|
|
58007
|
+
if (fatal.length === 0) return _void;
|
|
58008
|
+
const lines = fatal.map(formatDiagnostic);
|
|
58009
|
+
return fail$6(new GtdError(`gtd config:\n${lines.map((line) => ` - ${line}`).join("\n")}`));
|
|
58010
|
+
};
|
|
58011
|
+
/**
|
|
58012
|
+
* The `.gtdrc` half of `load`: no repository, no `gtd.config.ts` evaluation.
|
|
58013
|
+
* Any error diagnostic fails it.
|
|
58014
|
+
*/
|
|
58015
|
+
const loadRcConfig = gen(function* () {
|
|
58016
|
+
const { levels, compiled, decodeDiagnostics } = yield* readRc;
|
|
58017
|
+
yield* failOnErrors(dedupeDiagnostics(sortDiagnostics([...decodeDiagnostics, ...compiled.diagnostics], levels.map((level) => level.filepath))));
|
|
58018
|
+
return compiled;
|
|
58019
|
+
});
|
|
57751
58020
|
/**
|
|
57752
58021
|
* Build the whole config pipeline — discover levels by walking `Host`'s
|
|
57753
58022
|
* cwd→home chain (`ConfigDiscovery`, kept behind a `Context.Tag` since its
|
|
57754
58023
|
* production implementation reads real disk through cosmiconfig directly,
|
|
57755
58024
|
* incompatible with an in-memory `@inmem` `Workspace`), decode each level
|
|
57756
|
-
* individually against `ConfigSchema`, then
|
|
57757
|
-
* layer
|
|
57758
|
-
* which does its own merging and `./`/`../` file-ref inlining (through
|
|
57759
|
-
* `Workspace`, which — unlike discovery — both production and an in-memory
|
|
57760
|
-
* scenario back identically). A decode failure never short-circuits the
|
|
58025
|
+
* individually against `ConfigSchema`, then compile every successfully-decoded
|
|
58026
|
+
* layer (`src/workflow/compile.ts`). A decode failure never short-circuits the
|
|
57761
58027
|
* whole load: every level's own findings — decode AND compile alike — are
|
|
57762
|
-
* sorted/deduped together
|
|
57763
|
-
* failures included) into ONE report, so a config broken at two ancestor
|
|
58028
|
+
* sorted/deduped together into ONE report, so a config broken at two ancestor
|
|
57764
58029
|
* layers reports both instead of just the first one reached.
|
|
57765
58030
|
*/
|
|
57766
58031
|
const load = gen(function* () {
|
|
57767
|
-
const narrator = yield* Narrator;
|
|
57768
58032
|
const host = yield* Host;
|
|
57769
58033
|
const discovery = yield* ConfigDiscovery;
|
|
57770
|
-
const levels = yield*
|
|
57771
|
-
for (const level of levels) yield* narrator.narrate(`config: layer ${level.filepath}`);
|
|
57772
|
-
const decoded = yield* forEach$2(levels, decodeLevel);
|
|
57773
|
-
const layers = decoded.flatMap((d) => d.layer !== void 0 ? [d.layer] : []);
|
|
57774
|
-
const decodeDiagnostics = decoded.flatMap((d) => d.diagnostics);
|
|
57775
|
-
const compiled = compileConfig(layers);
|
|
58034
|
+
const { levels, compiled, decodeDiagnostics } = yield* readRc;
|
|
57776
58035
|
const module = yield* discovery.workflowModule(host.root, host.home);
|
|
57777
58036
|
const loaded = yield* try_({
|
|
57778
58037
|
try: () => loadWorkflow(module),
|
|
57779
58038
|
catch: (e) => new GtdError(`gtd config:\n - ${module?.filepath ?? "(built-in default)"}: ${e instanceof Error ? e.message : String(e)}`)
|
|
57780
58039
|
});
|
|
57781
58040
|
const layerOrder = [...levels.map((level) => level.filepath), ...module ? [module.filepath] : []];
|
|
57782
|
-
const
|
|
57783
|
-
const
|
|
57784
|
-
|
|
57785
|
-
|
|
57786
|
-
|
|
57787
|
-
|
|
57788
|
-
|
|
58041
|
+
const knownSkillNames = Object.keys(loaded.skills).sort();
|
|
58042
|
+
const unknownSkillDiagnostics = compiled.skillsKeys.filter(({ key }) => !Object.hasOwn(loaded.skills, key)).map(({ key, origin }) => ({
|
|
58043
|
+
severity: "error",
|
|
58044
|
+
path: ["skills", key],
|
|
58045
|
+
message: `"skills.${key}" names a step this workflow does not declare — known step names: ${knownSkillNames.join(", ")}`,
|
|
58046
|
+
origin
|
|
58047
|
+
}));
|
|
58048
|
+
const diagnostics = dedupeDiagnostics(sortDiagnostics([
|
|
58049
|
+
...decodeDiagnostics,
|
|
58050
|
+
...compiled.diagnostics,
|
|
58051
|
+
...unknownSkillDiagnostics,
|
|
58052
|
+
...wrongKindDiagnostics(loaded, compiled)
|
|
58053
|
+
], layerOrder));
|
|
58054
|
+
yield* failOnErrors(diagnostics);
|
|
58055
|
+
const initial = yield* firstStep(loaded, headTree(yield* Workspace));
|
|
57789
58056
|
return {
|
|
57790
58057
|
workflow: {
|
|
57791
58058
|
flow: loaded.flow,
|
|
@@ -57793,16 +58060,36 @@ const load = gen(function* () {
|
|
|
57793
58060
|
base: loaded.base,
|
|
57794
58061
|
steering: loaded.steering,
|
|
57795
58062
|
modes: compiled.modes,
|
|
58063
|
+
skills: loaded.skills,
|
|
58064
|
+
configuredSkills: compiled.rcSkills,
|
|
57796
58065
|
initial
|
|
57797
58066
|
},
|
|
57798
58067
|
workflowVars: { ...loaded.defaults },
|
|
57799
58068
|
rcVars: compiled.rcVars,
|
|
58069
|
+
workflowEnv: { ...loaded.envDefaults },
|
|
58070
|
+
rcEnv: compiled.rcEnv,
|
|
57800
58071
|
...compiled.ui !== void 0 ? { ui: compiled.ui } : {},
|
|
57801
58072
|
warnings: diagnostics.filter((d) => d.severity === "warning"),
|
|
57802
58073
|
configFiles: levels.map((level) => level.filepath),
|
|
57803
58074
|
workflowFiles: loaded.dependencyFiles
|
|
57804
58075
|
};
|
|
57805
58076
|
});
|
|
58077
|
+
/** A name under the wrong key (or under both) in a `.gtdrc` layer: each is an error at its own origin. */
|
|
58078
|
+
const wrongKindDiagnostics = (loaded, compiled) => {
|
|
58079
|
+
const error = (path, message, origin) => ({
|
|
58080
|
+
severity: "error",
|
|
58081
|
+
path,
|
|
58082
|
+
message,
|
|
58083
|
+
origin
|
|
58084
|
+
});
|
|
58085
|
+
const diagnostics = [];
|
|
58086
|
+
for (const { key, origin } of compiled.varsKeys) if (Object.hasOwn(loaded.envDefaults, key)) diagnostics.push(error(["vars", key], `"vars.${key}" is an environment setting — move it under "env:"`, origin));
|
|
58087
|
+
for (const { key, origin } of compiled.envKeys) {
|
|
58088
|
+
if (Object.hasOwn(loaded.defaults, key)) diagnostics.push(error(["env", key], `"env.${key}" is a process setting — move it under "vars:"`, origin));
|
|
58089
|
+
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));
|
|
58090
|
+
}
|
|
58091
|
+
return diagnostics;
|
|
58092
|
+
};
|
|
57806
58093
|
const TRANSFORM_MEMO_SIZE = 512;
|
|
57807
58094
|
const memoized = (transform) => {
|
|
57808
58095
|
const memo = /* @__PURE__ */ new Map();
|
|
@@ -57839,6 +58126,13 @@ const stringRecord = (exports, name) => {
|
|
|
57839
58126
|
if (typeof value !== "object" || value === null || Object.values(value).some((entry) => typeof entry !== "string")) throw new Error(`the "${name}" export is not a record of strings`);
|
|
57840
58127
|
return value;
|
|
57841
58128
|
};
|
|
58129
|
+
const isStringArray = (v) => Array.isArray(v) && v.every((entry) => typeof entry === "string");
|
|
58130
|
+
/** Read a module's optional `skills` export: full step name -> skill list, mirroring `stringRecord` for the vars-shaped exports. */
|
|
58131
|
+
const skillsRecord = (exports) => {
|
|
58132
|
+
const value = exports["skills"] ?? {};
|
|
58133
|
+
if (typeof value !== "object" || value === null || Object.values(value).some((entry) => !isStringArray(entry))) throw new Error(`the "skills" export is not a record of skill-name arrays`);
|
|
58134
|
+
return value;
|
|
58135
|
+
};
|
|
57842
58136
|
const optionalFunction = (exports, name) => {
|
|
57843
58137
|
const value = exports[name];
|
|
57844
58138
|
if (value === void 0) return void 0;
|
|
@@ -57847,16 +58141,26 @@ const optionalFunction = (exports, name) => {
|
|
|
57847
58141
|
};
|
|
57848
58142
|
/**
|
|
57849
58143
|
* Read a workflow module: the default export is the flow; `defaults`,
|
|
57850
|
-
* `summary`, `base` and `steering` are optional; any other export is ignored.
|
|
58144
|
+
* `envDefaults`, `summary`, `base` and `steering` are optional; any other export is ignored.
|
|
57851
58145
|
*/
|
|
57852
58146
|
const fromModule = (exported, origin, dependencyFiles) => {
|
|
57853
58147
|
const exports = typeof exported === "object" && exported !== null ? exported : {};
|
|
57854
58148
|
const flow = exports.default;
|
|
57855
58149
|
if (typeof flow !== "function") throw new Error("the default export is not a flow — export default an async function");
|
|
58150
|
+
const defaults = stringRecord(exports, "defaults");
|
|
58151
|
+
const envDefaults = stringRecord(exports, "envDefaults");
|
|
58152
|
+
for (const [exportName, record] of [["defaults", defaults], ["envDefaults", envDefaults]]) {
|
|
58153
|
+
const bad = Object.keys(record).find((name) => !isSettingName(name));
|
|
58154
|
+
if (bad !== void 0) throw new Error(`the "${exportName}" export declares ${JSON.stringify(bad)}, not a valid setting name — ${SETTING_NAME_RULE}`);
|
|
58155
|
+
}
|
|
58156
|
+
const both = Object.keys(defaults).filter((name) => Object.hasOwn(envDefaults, name));
|
|
58157
|
+
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`);
|
|
57856
58158
|
return {
|
|
57857
58159
|
flow,
|
|
57858
|
-
defaults
|
|
58160
|
+
defaults,
|
|
58161
|
+
envDefaults,
|
|
57859
58162
|
steering: stringRecord(exports, "steering"),
|
|
58163
|
+
skills: skillsRecord(exports),
|
|
57860
58164
|
summary: optionalFunction(exports, "summary"),
|
|
57861
58165
|
base: optionalFunction(exports, "base"),
|
|
57862
58166
|
origin,
|
|
@@ -57901,7 +58205,38 @@ const watchedEmptyTree = () => {
|
|
|
57901
58205
|
read: () => read
|
|
57902
58206
|
};
|
|
57903
58207
|
};
|
|
57904
|
-
|
|
58208
|
+
/** An empty settings record that logs every read: a named `get`/`has`, or an enumeration. */
|
|
58209
|
+
const watchedEmptySettings = () => {
|
|
58210
|
+
const names = /* @__PURE__ */ new Set();
|
|
58211
|
+
let enumerated = false;
|
|
58212
|
+
return {
|
|
58213
|
+
settings: new Proxy({}, {
|
|
58214
|
+
get: (_target, key) => {
|
|
58215
|
+
if (typeof key === "string") names.add(key);
|
|
58216
|
+
},
|
|
58217
|
+
has: (_target, key) => {
|
|
58218
|
+
if (typeof key === "string") names.add(key);
|
|
58219
|
+
return false;
|
|
58220
|
+
},
|
|
58221
|
+
ownKeys: () => {
|
|
58222
|
+
enumerated = true;
|
|
58223
|
+
return [];
|
|
58224
|
+
}
|
|
58225
|
+
}),
|
|
58226
|
+
names,
|
|
58227
|
+
enumerated: () => enumerated
|
|
58228
|
+
};
|
|
58229
|
+
};
|
|
58230
|
+
const settingReads = (kind, watch) => {
|
|
58231
|
+
const reads = [];
|
|
58232
|
+
if (watch.names.size > 0) {
|
|
58233
|
+
const sorted = [...watch.names].sort().map((name) => JSON.stringify(name));
|
|
58234
|
+
reads.push(`the ${kind} setting${sorted.length > 1 ? "s" : ""} ${sorted.join(", ")}`);
|
|
58235
|
+
}
|
|
58236
|
+
if (watch.enumerated()) reads.push(`every ${kind} setting`);
|
|
58237
|
+
return reads;
|
|
58238
|
+
};
|
|
58239
|
+
const replayToFirstStep = (loaded, vars, env, tree) => flatMap$4(promise(() => replay({
|
|
57905
58240
|
flow: loaded.flow,
|
|
57906
58241
|
episode: {
|
|
57907
58242
|
entry: void 0,
|
|
@@ -57912,6 +58247,7 @@ const replayToFirstStep = (loaded, vars, tree) => flatMap$4(promise(() => replay
|
|
|
57912
58247
|
commits: []
|
|
57913
58248
|
},
|
|
57914
58249
|
vars,
|
|
58250
|
+
env,
|
|
57915
58251
|
start: "",
|
|
57916
58252
|
budgetBytes: Number.MAX_SAFE_INTEGER
|
|
57917
58253
|
})), (outcome) => {
|
|
@@ -57922,15 +58258,22 @@ const replayToFirstStep = (loaded, vars, tree) => flatMap$4(promise(() => replay
|
|
|
57922
58258
|
/**
|
|
57923
58259
|
* The default entry's first step — where a finished process waits — found the
|
|
57924
58260
|
* only way a flow can be read: by replaying it over an empty history. Episode
|
|
57925
|
-
* boundaries are that step's commits, so it must
|
|
57926
|
-
*
|
|
57927
|
-
*
|
|
58261
|
+
* boundaries are that step's commits, so it must depend on neither a setting
|
|
58262
|
+
* (edited mid-process, it would move the boundary before pinned values are
|
|
58263
|
+
* read) nor the tree: a flow that reads the repository before reaching it is
|
|
58264
|
+
* replayed again over HEAD, and must reach the same step there.
|
|
57928
58265
|
*/
|
|
57929
|
-
const firstStep = (loaded,
|
|
58266
|
+
const firstStep = (loaded, head) => gen(function* () {
|
|
57930
58267
|
const empty = watchedEmptyTree();
|
|
57931
|
-
const
|
|
58268
|
+
const vars = watchedEmptySettings();
|
|
58269
|
+
const env = watchedEmptySettings();
|
|
58270
|
+
const outcome = yield* either(replayToFirstStep(loaded, vars.settings, env.settings, empty.tree));
|
|
58271
|
+
const reads = [...settingReads("process", vars), ...settingReads("environment", env)];
|
|
58272
|
+
if (reads.length > 0) return yield* fail$6(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`));
|
|
58273
|
+
if (outcome._tag === "Left") return yield* fail$6(outcome.left);
|
|
58274
|
+
const name = outcome.right;
|
|
57932
58275
|
if (!empty.read()) return name;
|
|
57933
|
-
const atHead = yield* replayToFirstStep(loaded, vars, head);
|
|
58276
|
+
const atHead = yield* replayToFirstStep(loaded, vars.settings, env.settings, head);
|
|
57934
58277
|
if (atHead === name) return name;
|
|
57935
58278
|
return yield* fail$6(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`));
|
|
57936
58279
|
});
|
|
@@ -57940,14 +58283,14 @@ var ConfigService = class ConfigService extends Tag("ConfigService")() {
|
|
|
57940
58283
|
//#endregion
|
|
57941
58284
|
//#region src/workflow/init.ts
|
|
57942
58285
|
const SCHEMA_URL = "https://cdn.jsdelivr.net/npm/@pmelab/gtd/schema.json";
|
|
57943
|
-
const
|
|
58286
|
+
const INIT_ENV = { testCommand: "npm test" };
|
|
57944
58287
|
const MODES_SUGGESTION = {
|
|
57945
58288
|
qa: { format: "npx prettier --write \"$GTD_FILE\"" },
|
|
57946
58289
|
review: { format: "npx prettier --write \"$GTD_FILE\"" }
|
|
57947
58290
|
};
|
|
57948
58291
|
const renderInitScaffold = () => ({ config: JSON.stringify({
|
|
57949
58292
|
$schema: SCHEMA_URL,
|
|
57950
|
-
|
|
58293
|
+
env: INIT_ENV,
|
|
57951
58294
|
modes: MODES_SUGGESTION
|
|
57952
58295
|
}, null, 2) + "\n" });
|
|
57953
58296
|
//#endregion
|
|
@@ -58244,12 +58587,37 @@ const llm = ({ env, cwd, model = DEFAULT_MODEL, timeoutMs = TIMEOUT_MS }) => asy
|
|
|
58244
58587
|
};
|
|
58245
58588
|
//#endregion
|
|
58246
58589
|
//#region src/judges/run.ts
|
|
58590
|
+
const PROVIDERS = [
|
|
58591
|
+
"fixed",
|
|
58592
|
+
"jev",
|
|
58593
|
+
"llm"
|
|
58594
|
+
];
|
|
58247
58595
|
const fail = (message) => {
|
|
58248
58596
|
throw new Error(`gtd judge run: ${message}`);
|
|
58249
58597
|
};
|
|
58598
|
+
const envProvider = (env) => {
|
|
58599
|
+
const value = env["GTD_JUDGE_PROVIDER"] || void 0;
|
|
58600
|
+
if (value !== void 0 && !PROVIDERS.includes(value)) return fail(`GTD_JUDGE_PROVIDER must be one of ${PROVIDERS.join(", ")} — got "${value}"`);
|
|
58601
|
+
return value;
|
|
58602
|
+
};
|
|
58603
|
+
const checkPairing = (resolved) => resolved.model !== void 0 && resolved.provider !== void 0 && resolved.provider !== "llm" ? fail(`a model only applies to provider llm, not ${resolved.provider}`) : resolved;
|
|
58604
|
+
/**
|
|
58605
|
+
* Per field, the first one set wins: flag, then `GTD_JUDGE_PROVIDER` /
|
|
58606
|
+
* `GTD_JUDGE_MODEL` (empty counts as unset), then `.gtdrc` `judge:`. The
|
|
58607
|
+
* provider/model pairing is checked on the resolved pair, whichever layer
|
|
58608
|
+
* supplied each half.
|
|
58609
|
+
*/
|
|
58610
|
+
const selectJudge = ({ provider, model, configured, env }) => {
|
|
58611
|
+
const resolved = {
|
|
58612
|
+
provider: provider ?? envProvider(env) ?? configured?.provider,
|
|
58613
|
+
model: model ?? (env["GTD_JUDGE_MODEL"] || void 0) ?? configured?.model
|
|
58614
|
+
};
|
|
58615
|
+
return checkPairing(resolved);
|
|
58616
|
+
};
|
|
58250
58617
|
/** The only place auto selection lives; it never falls back across providers. */
|
|
58251
|
-
const answererFor = (
|
|
58252
|
-
|
|
58618
|
+
const answererFor = (opts) => {
|
|
58619
|
+
const { answers, env, cwd } = opts;
|
|
58620
|
+
const { provider, model } = selectJudge(opts);
|
|
58253
58621
|
if (provider === "fixed") return fixed({
|
|
58254
58622
|
answersPath: answers,
|
|
58255
58623
|
env
|
|
@@ -63596,7 +63964,7 @@ const runOf = (history, location, closingHash = void 0) => {
|
|
|
63596
63964
|
trace: processCommits.map((c, i) => traceEntryOf(c.hash, parsed[i])),
|
|
63597
63965
|
costEntries: parsed.flatMap(costEntriesOf),
|
|
63598
63966
|
judgeVerdicts: parsed.flatMap((m) => m.judge),
|
|
63599
|
-
|
|
63967
|
+
pinnedVars: { ...first?.vars },
|
|
63600
63968
|
headTurn: headTurnOf(head),
|
|
63601
63969
|
closingHash,
|
|
63602
63970
|
episode: {
|
|
@@ -63752,6 +64120,20 @@ const pendingTree = (workspace, entries, rewrite) => {
|
|
|
63752
64120
|
};
|
|
63753
64121
|
};
|
|
63754
64122
|
/**
|
|
64123
|
+
* The one resolver of both setting kinds. Process settings: the recorded
|
|
64124
|
+
* (pinned) value wins per name, anything unrecorded resolves live — the
|
|
64125
|
+
* fallback for a process started before pinning existed. Environment settings
|
|
64126
|
+
* are always live. `entryOverrides` are `--var` values, passed only while a
|
|
64127
|
+
* process is being entered.
|
|
64128
|
+
*/
|
|
64129
|
+
const settingsFor = (config, pinnedVars, hostEnv, entryOverrides = {}) => ({
|
|
64130
|
+
vars: {
|
|
64131
|
+
...resolveVars(config.workflowVars, config.rcVars, entryOverrides, hostEnv),
|
|
64132
|
+
...pinnedVars
|
|
64133
|
+
},
|
|
64134
|
+
env: resolveVars(config.workflowEnv, config.rcEnv, {}, hostEnv)
|
|
64135
|
+
});
|
|
64136
|
+
/**
|
|
63755
64137
|
* `judgeBudgetBytes` is the one var that refuses rather than disabling its
|
|
63756
64138
|
* mechanism when blanked: without a bound the judge payload is rejected
|
|
63757
64139
|
* anyway. Absent falls back to a conservative default.
|
|
@@ -63772,9 +64154,12 @@ const replayFor = (setup, pending) => replay({
|
|
|
63772
64154
|
commits: setup.episode
|
|
63773
64155
|
},
|
|
63774
64156
|
vars: setup.vars,
|
|
64157
|
+
env: setup.env,
|
|
63775
64158
|
start: setup.run.diffBase,
|
|
63776
64159
|
startTree: setup.run.diffBase === EMPTY_TREE$1 ? treeFromRecord({}) : commitTree(setup.workspace, setup.run.diffBase),
|
|
63777
64160
|
budgetBytes: setup.budget,
|
|
64161
|
+
skills: setup.def.skills,
|
|
64162
|
+
configuredSkills: setup.def.configuredSkills,
|
|
63778
64163
|
...pending !== void 0 ? { pending } : {}
|
|
63779
64164
|
});
|
|
63780
64165
|
const replayError = (outcome) => {
|
|
@@ -63912,7 +64297,7 @@ const restAt = (ref) => gen(function* () {
|
|
|
63912
64297
|
const host = yield* Host;
|
|
63913
64298
|
const def = config.workflow;
|
|
63914
64299
|
const run = yield* computeProcessRun(git, def, ref);
|
|
63915
|
-
const vars =
|
|
64300
|
+
const { vars, env } = settingsFor(config, run.pinnedVars, host.env);
|
|
63916
64301
|
const budget = yield* try_({
|
|
63917
64302
|
try: () => judgeBudgetBytes(vars),
|
|
63918
64303
|
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
@@ -63935,6 +64320,7 @@ const restAt = (ref) => gen(function* () {
|
|
|
63935
64320
|
def,
|
|
63936
64321
|
run,
|
|
63937
64322
|
vars,
|
|
64323
|
+
env,
|
|
63938
64324
|
budget,
|
|
63939
64325
|
workspace,
|
|
63940
64326
|
base,
|
|
@@ -63962,6 +64348,7 @@ const restAt = (ref) => gen(function* () {
|
|
|
63962
64348
|
trace: outcome.trace,
|
|
63963
64349
|
run,
|
|
63964
64350
|
vars,
|
|
64351
|
+
env,
|
|
63965
64352
|
changes,
|
|
63966
64353
|
memory: memory.key,
|
|
63967
64354
|
memoryResumed: memory.resumed,
|
|
@@ -63982,7 +64369,7 @@ const currentRest = restAt(void 0);
|
|
|
63982
64369
|
const entryRefusal = (rest, name, entryVars) => gen(function* () {
|
|
63983
64370
|
const config = yield* (yield* ConfigService).load;
|
|
63984
64371
|
const host = yield* Host;
|
|
63985
|
-
const vars =
|
|
64372
|
+
const { vars, env } = settingsFor(config, {}, host.env, entryVars);
|
|
63986
64373
|
const workspace = rest.setup.workspace;
|
|
63987
64374
|
const outcome = yield* promise(() => replay({
|
|
63988
64375
|
flow: rest.def.flow,
|
|
@@ -63995,8 +64382,11 @@ const entryRefusal = (rest, name, entryVars) => gen(function* () {
|
|
|
63995
64382
|
commits: []
|
|
63996
64383
|
},
|
|
63997
64384
|
vars,
|
|
64385
|
+
env,
|
|
63998
64386
|
start: "",
|
|
63999
|
-
budgetBytes: rest.setup.budget
|
|
64387
|
+
budgetBytes: rest.setup.budget,
|
|
64388
|
+
skills: rest.def.skills,
|
|
64389
|
+
configuredSkills: rest.def.configuredSkills
|
|
64000
64390
|
}));
|
|
64001
64391
|
if (outcome.kind === "refused") return outcome.message;
|
|
64002
64392
|
if (outcome.kind === "rest") return outcome.entryRead ? void 0 : `"${name}" is not an enterable state — this workflow reads no entry`;
|
|
@@ -64045,28 +64435,29 @@ const reviewGateRewrite = (def) => {
|
|
|
64045
64435
|
apply: (content) => reviewFormat.clearTicks(content)
|
|
64046
64436
|
};
|
|
64047
64437
|
};
|
|
64048
|
-
|
|
64049
|
-
|
|
64050
|
-
|
|
64051
|
-
* computed by replaying, never matched.
|
|
64052
|
-
*/
|
|
64053
|
-
const decideLanding = (rest, verdicts) => gen(function* () {
|
|
64054
|
-
const early = cleanLanding(rest);
|
|
64055
|
-
if (early !== void 0) return early;
|
|
64056
|
-
const outcome = yield* promise(() => replayFor(rest.setup, {
|
|
64057
|
-
tree: pendingTree(rest.setup.workspace, rest.setup.pendingWorktree(), reviewGateRewrite(rest.stepDef)),
|
|
64058
|
-
...verdicts !== void 0 ? { verdicts } : {}
|
|
64059
|
-
}));
|
|
64060
|
-
if (outcome.kind === "refused") return {
|
|
64438
|
+
const multilineRefusal = (vars) => {
|
|
64439
|
+
const multiline = multilineSetting(vars);
|
|
64440
|
+
return multiline === void 0 ? void 0 : {
|
|
64061
64441
|
kind: "refusal",
|
|
64062
|
-
message:
|
|
64442
|
+
message: `gtd: process setting "${multiline}" spans several lines — a process setting is pinned in a commit trailer and must be a single line`
|
|
64063
64443
|
};
|
|
64064
|
-
|
|
64065
|
-
|
|
64444
|
+
};
|
|
64445
|
+
const landingTarget = (outcome, rest) => {
|
|
64446
|
+
if (outcome.kind === "refused") return succeed$3({
|
|
64447
|
+
kind: "refusal",
|
|
64448
|
+
message: outcome.message
|
|
64449
|
+
});
|
|
64450
|
+
if (outcome.kind === "divergence" || outcome.kind === "failed") return fail$6(new Error(outcome.message));
|
|
64451
|
+
return succeed$3(outcome.kind === "rest" ? outcome.rest.name : rest.def.initial);
|
|
64452
|
+
};
|
|
64453
|
+
const landingTo = (rest, to) => {
|
|
64066
64454
|
if (rest.changes.length === 0 && rest.stepDef.kind === "script" && to === rest.state) return {
|
|
64067
64455
|
kind: "noop",
|
|
64068
64456
|
settled: true
|
|
64069
64457
|
};
|
|
64458
|
+
const starts = noProcessUnderway(rest) && to !== rest.def.initial;
|
|
64459
|
+
const refused = starts ? multilineRefusal(rest.vars) : void 0;
|
|
64460
|
+
if (refused !== void 0) return refused;
|
|
64070
64461
|
return {
|
|
64071
64462
|
kind: "commit",
|
|
64072
64463
|
to,
|
|
@@ -64074,9 +64465,26 @@ const decideLanding = (rest, verdicts) => gen(function* () {
|
|
|
64074
64465
|
actor: rest.actor,
|
|
64075
64466
|
from: rest.state,
|
|
64076
64467
|
to,
|
|
64077
|
-
step: rest.step.id
|
|
64468
|
+
step: rest.step.id,
|
|
64469
|
+
...starts ? { vars: rest.vars } : {}
|
|
64078
64470
|
}
|
|
64079
64471
|
};
|
|
64472
|
+
};
|
|
64473
|
+
/**
|
|
64474
|
+
* What landing the pending turn at `rest` does — a pure decision from a
|
|
64475
|
+
* replay with the working tree as the pending turn. The target step is
|
|
64476
|
+
* computed by replaying, never matched.
|
|
64477
|
+
*/
|
|
64478
|
+
const decideLanding = (rest, verdicts) => gen(function* () {
|
|
64479
|
+
const early = cleanLanding(rest);
|
|
64480
|
+
if (early !== void 0) return early;
|
|
64481
|
+
const outcome = yield* promise(() => replayFor(rest.setup, {
|
|
64482
|
+
tree: pendingTree(rest.setup.workspace, rest.setup.pendingWorktree(), reviewGateRewrite(rest.stepDef)),
|
|
64483
|
+
...verdicts !== void 0 ? { verdicts } : {}
|
|
64484
|
+
}));
|
|
64485
|
+
const to = yield* landingTarget(outcome, rest);
|
|
64486
|
+
if (typeof to !== "string") return to;
|
|
64487
|
+
return landingTo(rest, to);
|
|
64080
64488
|
});
|
|
64081
64489
|
/** Where landing the pending turn would leave the process, or `undefined` when it would land nothing. */
|
|
64082
64490
|
const previewLanding = (rest) => decideLanding(rest, void 0).pipe(map$4((landing) => landing.kind === "commit" ? landing.to : void 0), catchAll(() => succeed$3(void 0)));
|
|
@@ -64109,7 +64517,7 @@ const summaryFor = (run) => gen(function* () {
|
|
|
64109
64517
|
})),
|
|
64110
64518
|
processCost: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
|
|
64111
64519
|
processCostByModel: costByModel(run.costEntries),
|
|
64112
|
-
|
|
64520
|
+
...settingsFor(config, run.pinnedVars, host.env)
|
|
64113
64521
|
});
|
|
64114
64522
|
});
|
|
64115
64523
|
//#endregion
|
|
@@ -64208,9 +64616,15 @@ const refusal = (message) => ({
|
|
|
64208
64616
|
kind: "refusal",
|
|
64209
64617
|
message
|
|
64210
64618
|
});
|
|
64619
|
+
const checkOverrides = (commandLabel, varOverrides, declaredNames, envNames) => gen(function* () {
|
|
64620
|
+
const environmentOnly = Object.keys(varOverrides).filter((n) => !declaredNames.includes(n) && envNames.includes(n));
|
|
64621
|
+
if (environmentOnly.length > 0) return yield* fail$6(new GtdUsageError(`${commandLabel}: --var pins process settings only; ${environmentOnly.join(", ")} ${environmentOnly.length === 1 ? "is an environment setting" : "are environment settings"} — set it under "env:" in .gtdrc or with a GTD_<NAME> environment variable`));
|
|
64622
|
+
const undeclared = Object.keys(varOverrides).filter((n) => !declaredNames.includes(n));
|
|
64623
|
+
if (undeclared.length > 0) return refusal(`${commandLabel}: --var name(s) not declared by this workflow: ${undeclared.join(", ")} — declared: ${declaredNames.length > 0 ? declaredNames.join(", ") : "(none)"}`);
|
|
64624
|
+
});
|
|
64211
64625
|
/**
|
|
64212
64626
|
* `gtd --entry <name>`: start a brand NEW process at a workflow entry, writing
|
|
64213
|
-
* an opening commit that carries
|
|
64627
|
+
* an opening commit that carries every process setting as a `Gtd-Var:` trailer, plus a
|
|
64214
64628
|
* `Gtd-Review-Base:` trailer when the entry fixes the process's diff base.
|
|
64215
64629
|
* All validation is a REFUSAL, not an Effect failure.
|
|
64216
64630
|
*/
|
|
@@ -64223,11 +64637,16 @@ const planEntry = (current, actor, entry) => gen(function* () {
|
|
|
64223
64637
|
...config.workflowVars,
|
|
64224
64638
|
...config.rcVars
|
|
64225
64639
|
});
|
|
64226
|
-
const
|
|
64227
|
-
|
|
64640
|
+
const overrideRefusal = yield* checkOverrides(commandLabel, varOverrides, declaredNames, Object.keys({
|
|
64641
|
+
...config.workflowEnv,
|
|
64642
|
+
...config.rcEnv
|
|
64643
|
+
}));
|
|
64644
|
+
if (overrideRefusal !== void 0) return overrideRefusal;
|
|
64228
64645
|
let base;
|
|
64229
64646
|
const baseOf = current.def.base;
|
|
64230
64647
|
const vars = resolveVars(config.workflowVars, config.rcVars, varOverrides, (yield* Host).env);
|
|
64648
|
+
const multiline = multilineSetting(vars);
|
|
64649
|
+
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`);
|
|
64231
64650
|
const template = yield* try_({
|
|
64232
64651
|
try: () => baseOf?.(name, vars),
|
|
64233
64652
|
catch: (e) => /* @__PURE__ */ new Error(`${commandLabel}: ${e instanceof Error ? e.message : String(e)}`)
|
|
@@ -64250,7 +64669,7 @@ const planEntry = (current, actor, entry) => gen(function* () {
|
|
|
64250
64669
|
actor,
|
|
64251
64670
|
to: name,
|
|
64252
64671
|
...base !== void 0 ? { reviewBase: base } : {},
|
|
64253
|
-
vars
|
|
64672
|
+
vars
|
|
64254
64673
|
})
|
|
64255
64674
|
}
|
|
64256
64675
|
}, {
|
|
@@ -76836,10 +77255,12 @@ const readStdin = () => tryPromise({
|
|
|
76836
77255
|
/** Stdout is written once, after the answerer resolves — a rejection leaves it empty. */
|
|
76837
77256
|
const runJudgeRunCommand = (command, out) => gen(function* () {
|
|
76838
77257
|
const host = yield* Host;
|
|
77258
|
+
const { judge } = yield* loadRcConfig;
|
|
76839
77259
|
const input = yield* readStdin();
|
|
76840
77260
|
const verdict = yield* tryPromise({
|
|
76841
77261
|
try: () => runJudge({
|
|
76842
77262
|
provider: command.provider,
|
|
77263
|
+
configured: judge,
|
|
76843
77264
|
answers: command.answers,
|
|
76844
77265
|
model: command.model,
|
|
76845
77266
|
cwd: host.scratchDir,
|
|
@@ -77407,7 +77828,11 @@ const FLAGS = [
|
|
|
77407
77828
|
decode: ([raw]) => raw === "fixed" || raw === "jev" || raw === "llm" ? right(raw) : left(`gtd: --provider must be one of fixed, jev, llm — got "${raw ?? ""}"`),
|
|
77408
77829
|
scopeError: "gtd: --provider is only valid for `gtd judge run`",
|
|
77409
77830
|
valueHint: "<name>",
|
|
77410
|
-
help: [
|
|
77831
|
+
help: [
|
|
77832
|
+
"(gtd judge run only) the answerer: fixed | jev | llm;",
|
|
77833
|
+
"omitted: GTD_JUDGE_PROVIDER, then .gtdrc judge:, then jev",
|
|
77834
|
+
"when TYPESAFE_API_KEY is set, else llm"
|
|
77835
|
+
]
|
|
77411
77836
|
},
|
|
77412
77837
|
{
|
|
77413
77838
|
name: "--answers",
|
|
@@ -77441,7 +77866,11 @@ const FLAGS = [
|
|
|
77441
77866
|
decode: ([raw]) => raw === void 0 || raw.trim() === "" || /[\r\n]/.test(raw) ? left("gtd: --model must be a non-empty, single-line value") : right(raw),
|
|
77442
77867
|
scopeError: "gtd: --model is only valid for `gtd land` (with --cost) or `gtd judge run`",
|
|
77443
77868
|
valueHint: "<name>",
|
|
77444
|
-
help: [
|
|
77869
|
+
help: [
|
|
77870
|
+
"(gtd land, with --cost) tag that cost's model",
|
|
77871
|
+
"(gtd judge run, llm answerer) the claude model; omitted:",
|
|
77872
|
+
"GTD_JUDGE_MODEL, then .gtdrc judge:, then haiku"
|
|
77873
|
+
]
|
|
77445
77874
|
},
|
|
77446
77875
|
{
|
|
77447
77876
|
name: "--entry",
|
|
@@ -77470,6 +77899,7 @@ const FLAGS = [
|
|
|
77470
77899
|
if (eq <= 0) return left(`gtd: --var must be <name>=<value> with a non-empty name — got "${raw}"`);
|
|
77471
77900
|
const name = raw.slice(0, eq);
|
|
77472
77901
|
const value = raw.slice(eq + 1);
|
|
77902
|
+
if (!isSettingName(name)) return left(`gtd: --var ${JSON.stringify(name)} is not a valid setting name — ${SETTING_NAME_RULE}`);
|
|
77473
77903
|
if (/[\r\n]/.test(value)) return left(`gtd: --var ${name} must be a single-line value`);
|
|
77474
77904
|
if (seen.has(name)) return left(`gtd: --var ${name} specified more than once`);
|
|
77475
77905
|
seen.add(name);
|
|
@@ -77480,9 +77910,10 @@ const FLAGS = [
|
|
|
77480
77910
|
scopeError: "gtd: --var requires --entry",
|
|
77481
77911
|
valueHint: "<name>=<value>",
|
|
77482
77912
|
help: [
|
|
77483
|
-
"(with --entry; repeatable)
|
|
77484
|
-
"
|
|
77485
|
-
"
|
|
77913
|
+
"(with --entry; repeatable) pin a process setting for the",
|
|
77914
|
+
"new process; the name must already be declared by the",
|
|
77915
|
+
"workflow's defaults or the .gtdrc vars: (environment",
|
|
77916
|
+
"settings are not pinnable)"
|
|
77486
77917
|
]
|
|
77487
77918
|
},
|
|
77488
77919
|
{
|
|
@@ -77555,8 +77986,8 @@ const COMMAND_ROWS = [
|
|
|
77555
77986
|
arity: "none",
|
|
77556
77987
|
details: [
|
|
77557
77988
|
"Scaffold a minimal .gtdrc.json for this repo, seeding the",
|
|
77558
|
-
"
|
|
77559
|
-
"command) and a Prettier formatting suggestion. gtd runs its",
|
|
77989
|
+
"environment setting you are most likely to change (the test",
|
|
77990
|
+
"command, under env:) and a Prettier formatting suggestion. gtd runs its",
|
|
77560
77991
|
"built-in workflow by default, so no workflow is written —",
|
|
77561
77992
|
"write a gtd.config.ts only to customize the workflow itself.",
|
|
77562
77993
|
"Takes no argument. Run once per repo; refuses if a gtd",
|
|
@@ -77800,8 +78231,11 @@ const COMMAND_ROWS = [
|
|
|
77800
78231
|
"does not cover are left out. --provider jev asks TypeSafe",
|
|
77801
78232
|
"(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
|
|
77802
78233
|
"on PATH (Claude Code only), model haiku unless --model.",
|
|
77803
|
-
"No --provider:
|
|
77804
|
-
"
|
|
78234
|
+
"No --provider: GTD_JUDGE_PROVIDER, then .gtdrc judge:",
|
|
78235
|
+
"provider, else jev when TYPESAFE_API_KEY is set and no",
|
|
78236
|
+
"model is given, else llm; --model likewise falls back to",
|
|
78237
|
+
"GTD_JUDGE_MODEL, then judge: model. Needs no repository,",
|
|
78238
|
+
"only .gtdrc files. jev and",
|
|
77805
78239
|
"llm exit 1 when they cannot answer every question, with",
|
|
77806
78240
|
"nothing on stdout"
|
|
77807
78241
|
]
|