@pmelab/gtd 16.0.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 +8 -3
- package/dist/gtd.bundle.mjs +335 -110
- package/package.json +1 -1
- package/schema.json +33 -1
- package/src/flows/runtime.ts +27 -13
- package/src/workflows/health.ts +3 -2
- package/src/workflows/steps.ts +3 -3
- package/src/workflows/text.fixture.ts +3 -1
- package/src/workflows/text.test.ts +1 -0
- package/src/workflows/text.ts +2 -2
- package/src/workflows/unified.ts +1 -1
- package/src/workflows/vars.ts +18 -12
package/README.md
CHANGED
|
@@ -21,7 +21,9 @@ npm install -g @pmelab/gtd
|
|
|
21
21
|
|
|
22
22
|
Or run without installing (prefix every `gtd` below with `npx`) — see
|
|
23
23
|
[Configuration](https://github.com/pmelab/gtd/blob/main/docs/configuration.md)
|
|
24
|
-
for the settings most projects tune.
|
|
24
|
+
for the settings most projects tune. A process setting's value is committed to
|
|
25
|
+
Git history; keep secrets in an environment setting (see
|
|
26
|
+
[Settings](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#settings)).
|
|
25
27
|
|
|
26
28
|
Also install all three skill sources the bundled workflow names in its prompts —
|
|
27
29
|
none is optional. See
|
|
@@ -359,8 +361,11 @@ noted in steps 2, 3, and 4 below.
|
|
|
359
361
|
users only; model `haiku`, `--model <name>` overrides) and reuses its login.
|
|
360
362
|
With no `--provider`, `gtd judge run` picks jev when `TYPESAFE_API_KEY` is
|
|
361
363
|
set and non-empty and no `--model` is given, else llm — never falling back
|
|
362
|
-
across them;
|
|
363
|
-
|
|
364
|
+
across them; a `.gtdrc` `judge: { provider, model }` key and the
|
|
365
|
+
`GTD_JUDGE_PROVIDER` / `GTD_JUDGE_MODEL` environment variables choose the
|
|
366
|
+
judge without editing the driver (flags win over both); `--model` with
|
|
367
|
+
`--provider fixed`/`jev` is refused. jev and llm exit 1 with nothing on
|
|
368
|
+
stdout when they cannot answer every question):
|
|
364
369
|
- Every red round after the first: was the failure identical, new, or
|
|
365
370
|
progress?
|
|
366
371
|
- Before spending a review turn on a package: does the code already satisfy
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -30968,17 +30968,24 @@ const head = () => ctx().head();
|
|
|
30968
30968
|
const start = () => ctx().start();
|
|
30969
30969
|
/** The skill list `localName` (scoped from here, same as `agent()`) resolves to — for a prompt preamble. See `FlowContext.skillsFor` for `ownSkills`. */
|
|
30970
30970
|
const skillsFor = (localName, ownSkills) => ctx().skillsFor(localName, ownSkills);
|
|
30971
|
-
|
|
30972
|
-
|
|
30973
|
-
|
|
30974
|
-
|
|
30975
|
-
|
|
30976
|
-
getOwnPropertyDescriptor: (_target, key) => typeof key === "string" && key in ctx().vars ? {
|
|
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() ? {
|
|
30977
30976
|
enumerable: true,
|
|
30978
30977
|
configurable: true,
|
|
30979
|
-
value:
|
|
30978
|
+
value: read()[key]
|
|
30980
30979
|
} : void 0
|
|
30981
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);
|
|
30982
30989
|
/** The top-level `## ` heading texts of markdown `text`. */
|
|
30983
30990
|
const sections = (text) => ctx().sections(text);
|
|
30984
30991
|
/** The top-level `## ` sections of markdown `text`, each with its own markdown. */
|
|
@@ -31162,6 +31169,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
|
|
|
31162
31169
|
checkScript: () => checkScript,
|
|
31163
31170
|
codeChanges: () => codeChanges,
|
|
31164
31171
|
codeThreads: () => codeThreads,
|
|
31172
|
+
env: () => env,
|
|
31165
31173
|
glob: () => glob,
|
|
31166
31174
|
hasThreadFor: () => hasThreadFor,
|
|
31167
31175
|
head: () => head,
|
|
@@ -47614,7 +47622,7 @@ const formatCommitMessage = (spec) => {
|
|
|
47614
47622
|
const lines = [];
|
|
47615
47623
|
if (spec.step !== void 0) lines.push(`Gtd-Step: ${formatStepId(spec.step)}`);
|
|
47616
47624
|
if (spec.reviewBase !== void 0) lines.push(`Gtd-Review-Base: ${spec.reviewBase}`);
|
|
47617
|
-
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}`);
|
|
47618
47626
|
if (spec.cost !== void 0) lines.push(`Gtd-Cost: ${spec.cost.cost}${spec.cost.model !== void 0 ? ` ${spec.cost.model}` : ""}`);
|
|
47619
47627
|
for (const verdict of spec.judge ?? []) lines.push(`Gtd-Judge: ${JSON.stringify(verdict)}`);
|
|
47620
47628
|
const subject = formatSubject(spec.actor, spec.to, spec.from);
|
|
@@ -47975,6 +47983,7 @@ const replay = async (input) => {
|
|
|
47975
47983
|
}));
|
|
47976
47984
|
}),
|
|
47977
47985
|
vars: input.vars,
|
|
47986
|
+
env: input.env,
|
|
47978
47987
|
start: () => input.start,
|
|
47979
47988
|
skillsFor: (localName, ownSkills) => resolveSkills(scoped(localName), ownSkills) ?? [],
|
|
47980
47989
|
head: () => {
|
|
@@ -48387,7 +48396,7 @@ ${questionBarReturn}
|
|
|
48387
48396
|
never re-open one. A genuinely open PRODUCT point may still
|
|
48388
48397
|
raise a fresh \`## Open Questions\` entry: the product gate sits
|
|
48389
48398
|
on this path exactly as on the first lap
|
|
48390
|
-
- Before grouping anything, run \`${
|
|
48399
|
+
- Before grouping anything, run \`${env.testCommand}\`
|
|
48391
48400
|
yourself — a loop-back runs no green-baseline gate, so the
|
|
48392
48401
|
tree may already be red (reverting the edit can undo a fix it
|
|
48393
48402
|
made). If red, make that breakage the first concern, ahead of
|
|
@@ -48932,8 +48941,8 @@ const ARCHITECTURE = ".gtd/ARCHITECTURE.md";
|
|
|
48932
48941
|
const REVIEW = ".gtd/REVIEW.md";
|
|
48933
48942
|
const QUALITY = ".gtd/QUALITY.md";
|
|
48934
48943
|
const SPEC_FEEDBACK = ".gtd/SPEC_FEEDBACK.md";
|
|
48935
|
-
const planner = () =>
|
|
48936
|
-
const coder = () =>
|
|
48944
|
+
const planner = () => env.plannerModel ?? "";
|
|
48945
|
+
const coder = () => env.coderModel ?? "";
|
|
48937
48946
|
/** Turn the sketch since `base` into `.gtd/REQUIREMENTS.md`. */
|
|
48938
48947
|
const triage$1 = (base) => agentWithSkills("triage", designTriagePrompt(base), {
|
|
48939
48948
|
label: "Triaging the change",
|
|
@@ -49087,7 +49096,7 @@ const collecting = (capture) => agentWithSkills("review.collecting", buildReview
|
|
|
49087
49096
|
allowEmpty: true
|
|
49088
49097
|
});
|
|
49089
49098
|
/** Run the suite as step `name`; resolves `true` when it passed. A failure is in `.gtd/FEEDBACK.md`. */
|
|
49090
|
-
const baseline = (name, label = "Checking the baseline") => check(name,
|
|
49099
|
+
const baseline = (name, label = "Checking the baseline") => check(name, env.testCommand ?? "", {
|
|
49091
49100
|
report: FEEDBACK,
|
|
49092
49101
|
label
|
|
49093
49102
|
});
|
|
@@ -49139,7 +49148,7 @@ const healthy = async (fix, options = {}) => {
|
|
|
49139
49148
|
let fixes = options.fixesSoFar ?? 0;
|
|
49140
49149
|
let previous;
|
|
49141
49150
|
for (;;) {
|
|
49142
|
-
if (await check("health.check",
|
|
49151
|
+
if (await check("health.check", env.testCommand ?? "", {
|
|
49143
49152
|
report: ".gtd/FEEDBACK.md",
|
|
49144
49153
|
label: "Running checks",
|
|
49145
49154
|
sweepOnGreen: [".gtd/ESCALATION.md"]
|
|
@@ -49766,10 +49775,8 @@ const buildTail = (fixFirst, base) => scope("build", async () => {
|
|
|
49766
49775
|
});
|
|
49767
49776
|
//#endregion
|
|
49768
49777
|
//#region src/workflows/vars.ts
|
|
49778
|
+
/** Process settings. */
|
|
49769
49779
|
const defaults = {
|
|
49770
|
-
testCommand: "npm test",
|
|
49771
|
-
plannerModel: "smart",
|
|
49772
|
-
coderModel: "base",
|
|
49773
49780
|
reviewBase: "",
|
|
49774
49781
|
judgeBudgetBytes: "32768",
|
|
49775
49782
|
judgeIdenticalMinP: "0.7",
|
|
@@ -49778,6 +49785,12 @@ const defaults = {
|
|
|
49778
49785
|
architectureSkipMinP: "0.85",
|
|
49779
49786
|
qualityReviews: "owasp-security, ponytail-review, test-audit"
|
|
49780
49787
|
};
|
|
49788
|
+
/** Environment settings. */
|
|
49789
|
+
const envDefaults = {
|
|
49790
|
+
testCommand: "npm test",
|
|
49791
|
+
plannerModel: "smart",
|
|
49792
|
+
coderModel: "base"
|
|
49793
|
+
};
|
|
49781
49794
|
//#endregion
|
|
49782
49795
|
//#region src/workflows/skills.ts
|
|
49783
49796
|
const skills = {
|
|
@@ -49832,6 +49845,7 @@ var unified_exports = /* @__PURE__ */ __exportAll({
|
|
|
49832
49845
|
defaults: () => defaults,
|
|
49833
49846
|
describeEscalation: () => describeEscalation,
|
|
49834
49847
|
design: () => design,
|
|
49848
|
+
envDefaults: () => envDefaults,
|
|
49835
49849
|
escalate: () => escalate,
|
|
49836
49850
|
escalation: () => escalation,
|
|
49837
49851
|
escalationExhausted: () => escalationExhausted,
|
|
@@ -50646,7 +50660,17 @@ var Workspace = class Workspace extends Tag("Workspace")() {
|
|
|
50646
50660
|
/** The `vars:` shape: a flat name -> scalar map (`compileVarsMap` coerces every scalar to a string). */
|
|
50647
50661
|
const varsJsonSchema = {
|
|
50648
50662
|
type: "object",
|
|
50649
|
-
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.",
|
|
50650
50674
|
additionalProperties: { type: [
|
|
50651
50675
|
"string",
|
|
50652
50676
|
"number",
|
|
@@ -50700,13 +50724,46 @@ const UiSchema = Struct({
|
|
|
50700
50724
|
key: optional$1(String$.annotations({ description: "Path to a TLS private key file, enabling HTTPS. Requires `cert` too." })),
|
|
50701
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." }))
|
|
50702
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." });
|
|
50703
50731
|
const ConfigSchema = Struct({
|
|
50704
50732
|
vars: optional$1(Unknown.annotations({ jsonSchema: varsJsonSchema })),
|
|
50733
|
+
env: optional$1(Unknown.annotations({ jsonSchema: envJsonSchema })),
|
|
50734
|
+
judge: optional$1(JudgeSchema),
|
|
50705
50735
|
modes: optional$1(Unknown.annotations({ jsonSchema: modesJsonSchema })),
|
|
50706
50736
|
ui: optional$1(UiSchema),
|
|
50707
50737
|
skills: optional$1(Unknown.annotations({ jsonSchema: skillsJsonSchema }))
|
|
50708
50738
|
});
|
|
50709
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
|
|
50710
50767
|
//#region src/SteeringFormats.ts
|
|
50711
50768
|
/** The unrendered `validate:` template a workflow compiler seeds for a built-in mode — quoted so a path with spaces survives interpolation. */
|
|
50712
50769
|
const seededValidateCommand = (mode) => `gtd check ${mode} "$GTD_FILE"`;
|
|
@@ -50791,14 +50848,14 @@ const err = (path, message) => ({
|
|
|
50791
50848
|
origin: ""
|
|
50792
50849
|
});
|
|
50793
50850
|
/** A flat `name -> scalar` map; a malformed value is a load error and is dropped. */
|
|
50794
|
-
const compileVarsMap = (raw) => {
|
|
50851
|
+
const compileVarsMap = (raw, keyName) => {
|
|
50795
50852
|
const diagnostics = [];
|
|
50796
50853
|
if (raw === void 0) return {
|
|
50797
50854
|
vars: {},
|
|
50798
50855
|
diagnostics
|
|
50799
50856
|
};
|
|
50800
50857
|
if (!isPlainObject$3(raw)) {
|
|
50801
|
-
diagnostics.push(err([
|
|
50858
|
+
diagnostics.push(err([keyName], `"${keyName}" must be a mapping of name -> scalar value, got ${describeType(raw)}`));
|
|
50802
50859
|
return {
|
|
50803
50860
|
vars: {},
|
|
50804
50861
|
diagnostics
|
|
@@ -50806,8 +50863,12 @@ const compileVarsMap = (raw) => {
|
|
|
50806
50863
|
}
|
|
50807
50864
|
const vars = {};
|
|
50808
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
|
+
}
|
|
50809
50870
|
if (!isScalar(value)) {
|
|
50810
|
-
diagnostics.push(err([
|
|
50871
|
+
diagnostics.push(err([keyName, key], `"${keyName}.${key}" must be a string, number, or boolean, got ${describeType(value)}`));
|
|
50811
50872
|
continue;
|
|
50812
50873
|
}
|
|
50813
50874
|
vars[key] = String(value);
|
|
@@ -51002,9 +51063,16 @@ const withOrigin = (diagnostics, lookup) => diagnostics.map((d) => ({
|
|
|
51002
51063
|
...d,
|
|
51003
51064
|
origin: lookup(d.path)
|
|
51004
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
|
+
});
|
|
51005
51073
|
/**
|
|
51006
51074
|
* Merge every layer outermost→innermost and compile what a `.gtdrc` may carry:
|
|
51007
|
-
* `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
|
|
51008
51076
|
* leftover `workflow:` key is an error pointing there. Pure and total.
|
|
51009
51077
|
*/
|
|
51010
51078
|
const compileConfig = (layers) => {
|
|
@@ -51021,8 +51089,13 @@ const compileConfig = (layers) => {
|
|
|
51021
51089
|
const mergedConfig = merged;
|
|
51022
51090
|
const lookupIn = (path) => originAt(originTree, path);
|
|
51023
51091
|
const ui = mergedConfig["ui"];
|
|
51024
|
-
const
|
|
51092
|
+
const judge = mergedConfig["judge"];
|
|
51093
|
+
const { vars: rcVars, diagnostics: varsDiagnostics } = compileVarsMap(mergedConfig["vars"], "vars");
|
|
51025
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");
|
|
51026
51099
|
diagnostics.push(...withOrigin(deadSkillsVarDiagnostics(mergedConfig["vars"]), lookupIn));
|
|
51027
51100
|
const { modes: rcModes, diagnostics: modesDiagnostics } = compileModesMap(mergedConfig["modes"]);
|
|
51028
51101
|
diagnostics.push(...withOrigin(modesDiagnostics, lookupIn));
|
|
@@ -51043,6 +51116,10 @@ const compileConfig = (layers) => {
|
|
|
51043
51116
|
const seeded = Object.fromEntries(BUILT_IN_MODE_NAMES.map((name) => [name, { validate: seededValidateCommand(name) }]));
|
|
51044
51117
|
return {
|
|
51045
51118
|
rcVars,
|
|
51119
|
+
rcEnv,
|
|
51120
|
+
varsKeys,
|
|
51121
|
+
envKeys,
|
|
51122
|
+
...judge !== void 0 ? { judge } : {},
|
|
51046
51123
|
modes: mergeModes(seeded, rcModes),
|
|
51047
51124
|
...ui !== void 0 ? { ui } : {},
|
|
51048
51125
|
rcSkills,
|
|
@@ -51108,26 +51185,6 @@ const interpolate = (config, env) => {
|
|
|
51108
51185
|
};
|
|
51109
51186
|
};
|
|
51110
51187
|
//#endregion
|
|
51111
|
-
//#region src/workflow/vars.ts
|
|
51112
|
-
const PREFIX = "GTD_";
|
|
51113
|
-
/**
|
|
51114
|
-
* The merged vars, later wins: the workflow's own defaults, `.gtdrc` `vars:`,
|
|
51115
|
-
* the process's entry vars, then `GTD_<NAME>` for any name an earlier layer
|
|
51116
|
-
* declared (an env var never introduces a name).
|
|
51117
|
-
*/
|
|
51118
|
-
const resolveVars = (workflowVars, rcVars, entryVars, env) => {
|
|
51119
|
-
const merged = {
|
|
51120
|
-
...workflowVars,
|
|
51121
|
-
...rcVars,
|
|
51122
|
-
...entryVars
|
|
51123
|
-
};
|
|
51124
|
-
for (const name of Object.keys(merged)) {
|
|
51125
|
-
const value = env[PREFIX + name.toUpperCase()];
|
|
51126
|
-
if (value !== void 0) merged[name] = value;
|
|
51127
|
-
}
|
|
51128
|
-
return merged;
|
|
51129
|
-
};
|
|
51130
|
-
//#endregion
|
|
51131
51188
|
//#region node_modules/yaml/dist/nodes/identity.js
|
|
51132
51189
|
var require_identity = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
51133
51190
|
const ALIAS = Symbol.for("yaml.alias");
|
|
@@ -57932,31 +57989,49 @@ const decodeLevel = (level, env) => gen(function* () {
|
|
|
57932
57989
|
}))
|
|
57933
57990
|
};
|
|
57934
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
|
+
});
|
|
57935
58020
|
/**
|
|
57936
58021
|
* Build the whole config pipeline — discover levels by walking `Host`'s
|
|
57937
58022
|
* cwd→home chain (`ConfigDiscovery`, kept behind a `Context.Tag` since its
|
|
57938
58023
|
* production implementation reads real disk through cosmiconfig directly,
|
|
57939
58024
|
* incompatible with an in-memory `@inmem` `Workspace`), decode each level
|
|
57940
|
-
* individually against `ConfigSchema`, then
|
|
57941
|
-
* layer
|
|
57942
|
-
* which does its own merging and `./`/`../` file-ref inlining (through
|
|
57943
|
-
* `Workspace`, which — unlike discovery — both production and an in-memory
|
|
57944
|
-
* 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
|
|
57945
58027
|
* whole load: every level's own findings — decode AND compile alike — are
|
|
57946
|
-
* sorted/deduped together
|
|
57947
|
-
* 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
|
|
57948
58029
|
* layers reports both instead of just the first one reached.
|
|
57949
58030
|
*/
|
|
57950
58031
|
const load = gen(function* () {
|
|
57951
|
-
const narrator = yield* Narrator;
|
|
57952
58032
|
const host = yield* Host;
|
|
57953
58033
|
const discovery = yield* ConfigDiscovery;
|
|
57954
|
-
const levels = yield*
|
|
57955
|
-
for (const level of levels) yield* narrator.narrate(`config: layer ${level.filepath}`);
|
|
57956
|
-
const decoded = yield* forEach$2(levels, (level) => decodeLevel(level, host.env));
|
|
57957
|
-
const layers = decoded.flatMap((d) => d.layer !== void 0 ? [d.layer] : []);
|
|
57958
|
-
const decodeDiagnostics = decoded.flatMap((d) => d.diagnostics);
|
|
57959
|
-
const compiled = compileConfig(layers);
|
|
58034
|
+
const { levels, compiled, decodeDiagnostics } = yield* readRc;
|
|
57960
58035
|
const module = yield* discovery.workflowModule(host.root, host.home);
|
|
57961
58036
|
const loaded = yield* try_({
|
|
57962
58037
|
try: () => loadWorkflow(module),
|
|
@@ -57973,14 +58048,11 @@ const load = gen(function* () {
|
|
|
57973
58048
|
const diagnostics = dedupeDiagnostics(sortDiagnostics([
|
|
57974
58049
|
...decodeDiagnostics,
|
|
57975
58050
|
...compiled.diagnostics,
|
|
57976
|
-
...unknownSkillDiagnostics
|
|
58051
|
+
...unknownSkillDiagnostics,
|
|
58052
|
+
...wrongKindDiagnostics(loaded, compiled)
|
|
57977
58053
|
], layerOrder));
|
|
57978
|
-
|
|
57979
|
-
|
|
57980
|
-
const lines = fatal.map(formatDiagnostic);
|
|
57981
|
-
return yield* fail$6(new GtdError(`gtd config:\n${lines.map((line) => ` - ${line}`).join("\n")}`));
|
|
57982
|
-
}
|
|
57983
|
-
const initial = yield* firstStep(loaded, resolveVars(loaded.defaults, compiled.rcVars, {}, host.env), headTree(yield* Workspace));
|
|
58054
|
+
yield* failOnErrors(diagnostics);
|
|
58055
|
+
const initial = yield* firstStep(loaded, headTree(yield* Workspace));
|
|
57984
58056
|
return {
|
|
57985
58057
|
workflow: {
|
|
57986
58058
|
flow: loaded.flow,
|
|
@@ -57994,12 +58066,30 @@ const load = gen(function* () {
|
|
|
57994
58066
|
},
|
|
57995
58067
|
workflowVars: { ...loaded.defaults },
|
|
57996
58068
|
rcVars: compiled.rcVars,
|
|
58069
|
+
workflowEnv: { ...loaded.envDefaults },
|
|
58070
|
+
rcEnv: compiled.rcEnv,
|
|
57997
58071
|
...compiled.ui !== void 0 ? { ui: compiled.ui } : {},
|
|
57998
58072
|
warnings: diagnostics.filter((d) => d.severity === "warning"),
|
|
57999
58073
|
configFiles: levels.map((level) => level.filepath),
|
|
58000
58074
|
workflowFiles: loaded.dependencyFiles
|
|
58001
58075
|
};
|
|
58002
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
|
+
};
|
|
58003
58093
|
const TRANSFORM_MEMO_SIZE = 512;
|
|
58004
58094
|
const memoized = (transform) => {
|
|
58005
58095
|
const memo = /* @__PURE__ */ new Map();
|
|
@@ -58051,15 +58141,24 @@ const optionalFunction = (exports, name) => {
|
|
|
58051
58141
|
};
|
|
58052
58142
|
/**
|
|
58053
58143
|
* Read a workflow module: the default export is the flow; `defaults`,
|
|
58054
|
-
* `summary`, `base` and `steering` are optional; any other export is ignored.
|
|
58144
|
+
* `envDefaults`, `summary`, `base` and `steering` are optional; any other export is ignored.
|
|
58055
58145
|
*/
|
|
58056
58146
|
const fromModule = (exported, origin, dependencyFiles) => {
|
|
58057
58147
|
const exports = typeof exported === "object" && exported !== null ? exported : {};
|
|
58058
58148
|
const flow = exports.default;
|
|
58059
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`);
|
|
58060
58158
|
return {
|
|
58061
58159
|
flow,
|
|
58062
|
-
defaults
|
|
58160
|
+
defaults,
|
|
58161
|
+
envDefaults,
|
|
58063
58162
|
steering: stringRecord(exports, "steering"),
|
|
58064
58163
|
skills: skillsRecord(exports),
|
|
58065
58164
|
summary: optionalFunction(exports, "summary"),
|
|
@@ -58106,7 +58205,38 @@ const watchedEmptyTree = () => {
|
|
|
58106
58205
|
read: () => read
|
|
58107
58206
|
};
|
|
58108
58207
|
};
|
|
58109
|
-
|
|
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({
|
|
58110
58240
|
flow: loaded.flow,
|
|
58111
58241
|
episode: {
|
|
58112
58242
|
entry: void 0,
|
|
@@ -58117,6 +58247,7 @@ const replayToFirstStep = (loaded, vars, tree) => flatMap$4(promise(() => replay
|
|
|
58117
58247
|
commits: []
|
|
58118
58248
|
},
|
|
58119
58249
|
vars,
|
|
58250
|
+
env,
|
|
58120
58251
|
start: "",
|
|
58121
58252
|
budgetBytes: Number.MAX_SAFE_INTEGER
|
|
58122
58253
|
})), (outcome) => {
|
|
@@ -58127,15 +58258,22 @@ const replayToFirstStep = (loaded, vars, tree) => flatMap$4(promise(() => replay
|
|
|
58127
58258
|
/**
|
|
58128
58259
|
* The default entry's first step — where a finished process waits — found the
|
|
58129
58260
|
* only way a flow can be read: by replaying it over an empty history. Episode
|
|
58130
|
-
* boundaries are that step's commits, so it must
|
|
58131
|
-
*
|
|
58132
|
-
*
|
|
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.
|
|
58133
58265
|
*/
|
|
58134
|
-
const firstStep = (loaded,
|
|
58266
|
+
const firstStep = (loaded, head) => gen(function* () {
|
|
58135
58267
|
const empty = watchedEmptyTree();
|
|
58136
|
-
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;
|
|
58137
58275
|
if (!empty.read()) return name;
|
|
58138
|
-
const atHead = yield* replayToFirstStep(loaded, vars, head);
|
|
58276
|
+
const atHead = yield* replayToFirstStep(loaded, vars.settings, env.settings, head);
|
|
58139
58277
|
if (atHead === name) return name;
|
|
58140
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`));
|
|
58141
58279
|
});
|
|
@@ -58145,14 +58283,14 @@ var ConfigService = class ConfigService extends Tag("ConfigService")() {
|
|
|
58145
58283
|
//#endregion
|
|
58146
58284
|
//#region src/workflow/init.ts
|
|
58147
58285
|
const SCHEMA_URL = "https://cdn.jsdelivr.net/npm/@pmelab/gtd/schema.json";
|
|
58148
|
-
const
|
|
58286
|
+
const INIT_ENV = { testCommand: "npm test" };
|
|
58149
58287
|
const MODES_SUGGESTION = {
|
|
58150
58288
|
qa: { format: "npx prettier --write \"$GTD_FILE\"" },
|
|
58151
58289
|
review: { format: "npx prettier --write \"$GTD_FILE\"" }
|
|
58152
58290
|
};
|
|
58153
58291
|
const renderInitScaffold = () => ({ config: JSON.stringify({
|
|
58154
58292
|
$schema: SCHEMA_URL,
|
|
58155
|
-
|
|
58293
|
+
env: INIT_ENV,
|
|
58156
58294
|
modes: MODES_SUGGESTION
|
|
58157
58295
|
}, null, 2) + "\n" });
|
|
58158
58296
|
//#endregion
|
|
@@ -58449,12 +58587,37 @@ const llm = ({ env, cwd, model = DEFAULT_MODEL, timeoutMs = TIMEOUT_MS }) => asy
|
|
|
58449
58587
|
};
|
|
58450
58588
|
//#endregion
|
|
58451
58589
|
//#region src/judges/run.ts
|
|
58590
|
+
const PROVIDERS = [
|
|
58591
|
+
"fixed",
|
|
58592
|
+
"jev",
|
|
58593
|
+
"llm"
|
|
58594
|
+
];
|
|
58452
58595
|
const fail = (message) => {
|
|
58453
58596
|
throw new Error(`gtd judge run: ${message}`);
|
|
58454
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
|
+
};
|
|
58455
58617
|
/** The only place auto selection lives; it never falls back across providers. */
|
|
58456
|
-
const answererFor = (
|
|
58457
|
-
|
|
58618
|
+
const answererFor = (opts) => {
|
|
58619
|
+
const { answers, env, cwd } = opts;
|
|
58620
|
+
const { provider, model } = selectJudge(opts);
|
|
58458
58621
|
if (provider === "fixed") return fixed({
|
|
58459
58622
|
answersPath: answers,
|
|
58460
58623
|
env
|
|
@@ -63801,7 +63964,7 @@ const runOf = (history, location, closingHash = void 0) => {
|
|
|
63801
63964
|
trace: processCommits.map((c, i) => traceEntryOf(c.hash, parsed[i])),
|
|
63802
63965
|
costEntries: parsed.flatMap(costEntriesOf),
|
|
63803
63966
|
judgeVerdicts: parsed.flatMap((m) => m.judge),
|
|
63804
|
-
|
|
63967
|
+
pinnedVars: { ...first?.vars },
|
|
63805
63968
|
headTurn: headTurnOf(head),
|
|
63806
63969
|
closingHash,
|
|
63807
63970
|
episode: {
|
|
@@ -63957,6 +64120,20 @@ const pendingTree = (workspace, entries, rewrite) => {
|
|
|
63957
64120
|
};
|
|
63958
64121
|
};
|
|
63959
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
|
+
/**
|
|
63960
64137
|
* `judgeBudgetBytes` is the one var that refuses rather than disabling its
|
|
63961
64138
|
* mechanism when blanked: without a bound the judge payload is rejected
|
|
63962
64139
|
* anyway. Absent falls back to a conservative default.
|
|
@@ -63977,6 +64154,7 @@ const replayFor = (setup, pending) => replay({
|
|
|
63977
64154
|
commits: setup.episode
|
|
63978
64155
|
},
|
|
63979
64156
|
vars: setup.vars,
|
|
64157
|
+
env: setup.env,
|
|
63980
64158
|
start: setup.run.diffBase,
|
|
63981
64159
|
startTree: setup.run.diffBase === EMPTY_TREE$1 ? treeFromRecord({}) : commitTree(setup.workspace, setup.run.diffBase),
|
|
63982
64160
|
budgetBytes: setup.budget,
|
|
@@ -64119,7 +64297,7 @@ const restAt = (ref) => gen(function* () {
|
|
|
64119
64297
|
const host = yield* Host;
|
|
64120
64298
|
const def = config.workflow;
|
|
64121
64299
|
const run = yield* computeProcessRun(git, def, ref);
|
|
64122
|
-
const vars =
|
|
64300
|
+
const { vars, env } = settingsFor(config, run.pinnedVars, host.env);
|
|
64123
64301
|
const budget = yield* try_({
|
|
64124
64302
|
try: () => judgeBudgetBytes(vars),
|
|
64125
64303
|
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
@@ -64142,6 +64320,7 @@ const restAt = (ref) => gen(function* () {
|
|
|
64142
64320
|
def,
|
|
64143
64321
|
run,
|
|
64144
64322
|
vars,
|
|
64323
|
+
env,
|
|
64145
64324
|
budget,
|
|
64146
64325
|
workspace,
|
|
64147
64326
|
base,
|
|
@@ -64169,6 +64348,7 @@ const restAt = (ref) => gen(function* () {
|
|
|
64169
64348
|
trace: outcome.trace,
|
|
64170
64349
|
run,
|
|
64171
64350
|
vars,
|
|
64351
|
+
env,
|
|
64172
64352
|
changes,
|
|
64173
64353
|
memory: memory.key,
|
|
64174
64354
|
memoryResumed: memory.resumed,
|
|
@@ -64189,7 +64369,7 @@ const currentRest = restAt(void 0);
|
|
|
64189
64369
|
const entryRefusal = (rest, name, entryVars) => gen(function* () {
|
|
64190
64370
|
const config = yield* (yield* ConfigService).load;
|
|
64191
64371
|
const host = yield* Host;
|
|
64192
|
-
const vars =
|
|
64372
|
+
const { vars, env } = settingsFor(config, {}, host.env, entryVars);
|
|
64193
64373
|
const workspace = rest.setup.workspace;
|
|
64194
64374
|
const outcome = yield* promise(() => replay({
|
|
64195
64375
|
flow: rest.def.flow,
|
|
@@ -64202,6 +64382,7 @@ const entryRefusal = (rest, name, entryVars) => gen(function* () {
|
|
|
64202
64382
|
commits: []
|
|
64203
64383
|
},
|
|
64204
64384
|
vars,
|
|
64385
|
+
env,
|
|
64205
64386
|
start: "",
|
|
64206
64387
|
budgetBytes: rest.setup.budget,
|
|
64207
64388
|
skills: rest.def.skills,
|
|
@@ -64254,28 +64435,29 @@ const reviewGateRewrite = (def) => {
|
|
|
64254
64435
|
apply: (content) => reviewFormat.clearTicks(content)
|
|
64255
64436
|
};
|
|
64256
64437
|
};
|
|
64257
|
-
|
|
64258
|
-
|
|
64259
|
-
|
|
64260
|
-
* computed by replaying, never matched.
|
|
64261
|
-
*/
|
|
64262
|
-
const decideLanding = (rest, verdicts) => gen(function* () {
|
|
64263
|
-
const early = cleanLanding(rest);
|
|
64264
|
-
if (early !== void 0) return early;
|
|
64265
|
-
const outcome = yield* promise(() => replayFor(rest.setup, {
|
|
64266
|
-
tree: pendingTree(rest.setup.workspace, rest.setup.pendingWorktree(), reviewGateRewrite(rest.stepDef)),
|
|
64267
|
-
...verdicts !== void 0 ? { verdicts } : {}
|
|
64268
|
-
}));
|
|
64269
|
-
if (outcome.kind === "refused") return {
|
|
64438
|
+
const multilineRefusal = (vars) => {
|
|
64439
|
+
const multiline = multilineSetting(vars);
|
|
64440
|
+
return multiline === void 0 ? void 0 : {
|
|
64270
64441
|
kind: "refusal",
|
|
64271
|
-
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`
|
|
64272
64443
|
};
|
|
64273
|
-
|
|
64274
|
-
|
|
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) => {
|
|
64275
64454
|
if (rest.changes.length === 0 && rest.stepDef.kind === "script" && to === rest.state) return {
|
|
64276
64455
|
kind: "noop",
|
|
64277
64456
|
settled: true
|
|
64278
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;
|
|
64279
64461
|
return {
|
|
64280
64462
|
kind: "commit",
|
|
64281
64463
|
to,
|
|
@@ -64283,9 +64465,26 @@ const decideLanding = (rest, verdicts) => gen(function* () {
|
|
|
64283
64465
|
actor: rest.actor,
|
|
64284
64466
|
from: rest.state,
|
|
64285
64467
|
to,
|
|
64286
|
-
step: rest.step.id
|
|
64468
|
+
step: rest.step.id,
|
|
64469
|
+
...starts ? { vars: rest.vars } : {}
|
|
64287
64470
|
}
|
|
64288
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);
|
|
64289
64488
|
});
|
|
64290
64489
|
/** Where landing the pending turn would leave the process, or `undefined` when it would land nothing. */
|
|
64291
64490
|
const previewLanding = (rest) => decideLanding(rest, void 0).pipe(map$4((landing) => landing.kind === "commit" ? landing.to : void 0), catchAll(() => succeed$3(void 0)));
|
|
@@ -64318,7 +64517,7 @@ const summaryFor = (run) => gen(function* () {
|
|
|
64318
64517
|
})),
|
|
64319
64518
|
processCost: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
|
|
64320
64519
|
processCostByModel: costByModel(run.costEntries),
|
|
64321
|
-
|
|
64520
|
+
...settingsFor(config, run.pinnedVars, host.env)
|
|
64322
64521
|
});
|
|
64323
64522
|
});
|
|
64324
64523
|
//#endregion
|
|
@@ -64417,9 +64616,15 @@ const refusal = (message) => ({
|
|
|
64417
64616
|
kind: "refusal",
|
|
64418
64617
|
message
|
|
64419
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
|
+
});
|
|
64420
64625
|
/**
|
|
64421
64626
|
* `gtd --entry <name>`: start a brand NEW process at a workflow entry, writing
|
|
64422
|
-
* an opening commit that carries
|
|
64627
|
+
* an opening commit that carries every process setting as a `Gtd-Var:` trailer, plus a
|
|
64423
64628
|
* `Gtd-Review-Base:` trailer when the entry fixes the process's diff base.
|
|
64424
64629
|
* All validation is a REFUSAL, not an Effect failure.
|
|
64425
64630
|
*/
|
|
@@ -64432,11 +64637,16 @@ const planEntry = (current, actor, entry) => gen(function* () {
|
|
|
64432
64637
|
...config.workflowVars,
|
|
64433
64638
|
...config.rcVars
|
|
64434
64639
|
});
|
|
64435
|
-
const
|
|
64436
|
-
|
|
64640
|
+
const overrideRefusal = yield* checkOverrides(commandLabel, varOverrides, declaredNames, Object.keys({
|
|
64641
|
+
...config.workflowEnv,
|
|
64642
|
+
...config.rcEnv
|
|
64643
|
+
}));
|
|
64644
|
+
if (overrideRefusal !== void 0) return overrideRefusal;
|
|
64437
64645
|
let base;
|
|
64438
64646
|
const baseOf = current.def.base;
|
|
64439
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`);
|
|
64440
64650
|
const template = yield* try_({
|
|
64441
64651
|
try: () => baseOf?.(name, vars),
|
|
64442
64652
|
catch: (e) => /* @__PURE__ */ new Error(`${commandLabel}: ${e instanceof Error ? e.message : String(e)}`)
|
|
@@ -64459,7 +64669,7 @@ const planEntry = (current, actor, entry) => gen(function* () {
|
|
|
64459
64669
|
actor,
|
|
64460
64670
|
to: name,
|
|
64461
64671
|
...base !== void 0 ? { reviewBase: base } : {},
|
|
64462
|
-
vars
|
|
64672
|
+
vars
|
|
64463
64673
|
})
|
|
64464
64674
|
}
|
|
64465
64675
|
}, {
|
|
@@ -77045,10 +77255,12 @@ const readStdin = () => tryPromise({
|
|
|
77045
77255
|
/** Stdout is written once, after the answerer resolves — a rejection leaves it empty. */
|
|
77046
77256
|
const runJudgeRunCommand = (command, out) => gen(function* () {
|
|
77047
77257
|
const host = yield* Host;
|
|
77258
|
+
const { judge } = yield* loadRcConfig;
|
|
77048
77259
|
const input = yield* readStdin();
|
|
77049
77260
|
const verdict = yield* tryPromise({
|
|
77050
77261
|
try: () => runJudge({
|
|
77051
77262
|
provider: command.provider,
|
|
77263
|
+
configured: judge,
|
|
77052
77264
|
answers: command.answers,
|
|
77053
77265
|
model: command.model,
|
|
77054
77266
|
cwd: host.scratchDir,
|
|
@@ -77616,7 +77828,11 @@ const FLAGS = [
|
|
|
77616
77828
|
decode: ([raw]) => raw === "fixed" || raw === "jev" || raw === "llm" ? right(raw) : left(`gtd: --provider must be one of fixed, jev, llm — got "${raw ?? ""}"`),
|
|
77617
77829
|
scopeError: "gtd: --provider is only valid for `gtd judge run`",
|
|
77618
77830
|
valueHint: "<name>",
|
|
77619
|
-
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
|
+
]
|
|
77620
77836
|
},
|
|
77621
77837
|
{
|
|
77622
77838
|
name: "--answers",
|
|
@@ -77650,7 +77866,11 @@ const FLAGS = [
|
|
|
77650
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),
|
|
77651
77867
|
scopeError: "gtd: --model is only valid for `gtd land` (with --cost) or `gtd judge run`",
|
|
77652
77868
|
valueHint: "<name>",
|
|
77653
|
-
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
|
+
]
|
|
77654
77874
|
},
|
|
77655
77875
|
{
|
|
77656
77876
|
name: "--entry",
|
|
@@ -77679,6 +77899,7 @@ const FLAGS = [
|
|
|
77679
77899
|
if (eq <= 0) return left(`gtd: --var must be <name>=<value> with a non-empty name — got "${raw}"`);
|
|
77680
77900
|
const name = raw.slice(0, eq);
|
|
77681
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}`);
|
|
77682
77903
|
if (/[\r\n]/.test(value)) return left(`gtd: --var ${name} must be a single-line value`);
|
|
77683
77904
|
if (seen.has(name)) return left(`gtd: --var ${name} specified more than once`);
|
|
77684
77905
|
seen.add(name);
|
|
@@ -77689,9 +77910,10 @@ const FLAGS = [
|
|
|
77689
77910
|
scopeError: "gtd: --var requires --entry",
|
|
77690
77911
|
valueHint: "<name>=<value>",
|
|
77691
77912
|
help: [
|
|
77692
|
-
"(with --entry; repeatable)
|
|
77693
|
-
"
|
|
77694
|
-
"
|
|
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)"
|
|
77695
77917
|
]
|
|
77696
77918
|
},
|
|
77697
77919
|
{
|
|
@@ -77764,8 +77986,8 @@ const COMMAND_ROWS = [
|
|
|
77764
77986
|
arity: "none",
|
|
77765
77987
|
details: [
|
|
77766
77988
|
"Scaffold a minimal .gtdrc.json for this repo, seeding the",
|
|
77767
|
-
"
|
|
77768
|
-
"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",
|
|
77769
77991
|
"built-in workflow by default, so no workflow is written —",
|
|
77770
77992
|
"write a gtd.config.ts only to customize the workflow itself.",
|
|
77771
77993
|
"Takes no argument. Run once per repo; refuses if a gtd",
|
|
@@ -78009,8 +78231,11 @@ const COMMAND_ROWS = [
|
|
|
78009
78231
|
"does not cover are left out. --provider jev asks TypeSafe",
|
|
78010
78232
|
"(TYPESAFE_API_KEY). --provider llm asks the `claude` CLI",
|
|
78011
78233
|
"on PATH (Claude Code only), model haiku unless --model.",
|
|
78012
|
-
"No --provider:
|
|
78013
|
-
"
|
|
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",
|
|
78014
78239
|
"llm exit 1 when they cannot answer every question, with",
|
|
78015
78240
|
"nothing on stdout"
|
|
78016
78241
|
]
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"properties": {
|
|
6
6
|
"vars": {
|
|
7
7
|
"type": "object",
|
|
8
|
-
"description": "Flat name -> scalar map merged into the workflow's
|
|
8
|
+
"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.",
|
|
9
9
|
"additionalProperties": {
|
|
10
10
|
"type": [
|
|
11
11
|
"string",
|
|
@@ -14,6 +14,38 @@
|
|
|
14
14
|
]
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
+
"env": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"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.",
|
|
20
|
+
"additionalProperties": {
|
|
21
|
+
"type": [
|
|
22
|
+
"string",
|
|
23
|
+
"number",
|
|
24
|
+
"boolean"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"judge": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"required": [],
|
|
31
|
+
"properties": {
|
|
32
|
+
"provider": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"enum": [
|
|
35
|
+
"fixed",
|
|
36
|
+
"jev",
|
|
37
|
+
"llm"
|
|
38
|
+
],
|
|
39
|
+
"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."
|
|
40
|
+
},
|
|
41
|
+
"model": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"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."
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"description": "Which judge `gtd judge run` uses, when no flag or GTD_JUDGE_* variable says."
|
|
48
|
+
},
|
|
17
49
|
"modes": {
|
|
18
50
|
"type": "object",
|
|
19
51
|
"description": "Steering-file modes a workflow step's mode: may name. Each entry declares at least one of format/validate: shell commands gtd runs via bash with $GTD_FILE set to the steering file's path. format rewrites the file in place; validate exits 0 when valid, non-zero with findings on stdout/stderr otherwise. The halves layer independently, so naming a built-in mode (qa/review) and declaring only format: adds formatting while keeping gtd's own validation. gtd ships no formatter — bring your own (prettier, dprint, a script).",
|
package/src/flows/runtime.ts
CHANGED
|
@@ -136,6 +136,7 @@ export interface FlowContext {
|
|
|
136
136
|
readonly threads: (text: string) => readonly ThreadInfo[]
|
|
137
137
|
readonly codeThreads: () => readonly CodeThreadInfo[]
|
|
138
138
|
readonly vars: Readonly<Record<string, string>>
|
|
139
|
+
readonly env: Readonly<Record<string, string>>
|
|
139
140
|
readonly head: () => string
|
|
140
141
|
readonly start: () => string
|
|
141
142
|
/**
|
|
@@ -256,19 +257,31 @@ export const start = (): string => ctx().start()
|
|
|
256
257
|
export const skillsFor = (localName: string, ownSkills?: readonly string[]): readonly string[] =>
|
|
257
258
|
ctx().skillsFor(localName, ownSkills)
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
260
|
+
const settingsProxy = (
|
|
261
|
+
read: () => Readonly<Record<string, string>>,
|
|
262
|
+
): Readonly<Record<string, string>> =>
|
|
263
|
+
new Proxy(
|
|
264
|
+
{},
|
|
265
|
+
{
|
|
266
|
+
get: (_target, key) => (typeof key === "string" ? read()[key] : undefined),
|
|
267
|
+
has: (_target, key) => typeof key === "string" && key in read(),
|
|
268
|
+
ownKeys: () => Object.keys(read()),
|
|
269
|
+
getOwnPropertyDescriptor: (_target, key) =>
|
|
270
|
+
typeof key === "string" && key in read()
|
|
271
|
+
? { enumerable: true, configurable: true, value: read()[key] }
|
|
272
|
+
: undefined,
|
|
273
|
+
},
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
/** The process settings: pinned at process start, so flow code may branch on them. */
|
|
277
|
+
export const vars: Readonly<Record<string, string>> = settingsProxy(() => ctx().vars)
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The environment settings: resolved live on every invocation. Flow code must
|
|
281
|
+
* read them only where they cannot change the next step (step options, script
|
|
282
|
+
* bodies) — replay cannot enforce this.
|
|
283
|
+
*/
|
|
284
|
+
export const env: Readonly<Record<string, string>> = settingsProxy(() => ctx().env)
|
|
272
285
|
|
|
273
286
|
// ── Text utilities ──────────────────────────────────────────────────────────
|
|
274
287
|
|
|
@@ -343,6 +356,7 @@ export interface SummaryContext {
|
|
|
343
356
|
readonly processCost: number
|
|
344
357
|
readonly processCostByModel: readonly { readonly model: string; readonly cost: number }[]
|
|
345
358
|
readonly vars: Readonly<Record<string, string>>
|
|
359
|
+
readonly env: Readonly<Record<string, string>>
|
|
346
360
|
}
|
|
347
361
|
|
|
348
362
|
/** `gtd summary`'s prompt — a workflow module's optional `summary` export. */
|
package/src/workflows/health.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
answered,
|
|
3
3
|
check,
|
|
4
|
+
env,
|
|
4
5
|
human,
|
|
5
6
|
judge,
|
|
6
7
|
numeric,
|
|
@@ -17,7 +18,7 @@ export const FIX_CAP = 3
|
|
|
17
18
|
|
|
18
19
|
/** Run the suite as step `name`; resolves `true` when it passed. A failure is in `.gtd/FEEDBACK.md`. */
|
|
19
20
|
export const baseline = (name: string, label = "Checking the baseline"): Promise<boolean> =>
|
|
20
|
-
check(name,
|
|
21
|
+
check(name, env.testCommand ?? "", { report: FEEDBACK, label })
|
|
21
22
|
|
|
22
23
|
/** How many escalation rounds a run of red checks has spent — reset once the suite goes green. */
|
|
23
24
|
export interface EscalationCount {
|
|
@@ -95,7 +96,7 @@ export const healthy = async (
|
|
|
95
96
|
let fixes = options.fixesSoFar ?? 0
|
|
96
97
|
let previous: string | undefined
|
|
97
98
|
for (;;) {
|
|
98
|
-
const green = await check("health.check",
|
|
99
|
+
const green = await check("health.check", env.testCommand ?? "", {
|
|
99
100
|
report: FEEDBACK,
|
|
100
101
|
label: "Running checks",
|
|
101
102
|
// Swept only on green: an unresolved analysis survives every retry.
|
package/src/workflows/steps.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { env, human } from "../flows/index.js"
|
|
2
2
|
import * as t from "./text.js"
|
|
3
3
|
|
|
4
4
|
// The bundled workflow's single steps. A step's name is relative to the
|
|
@@ -14,8 +14,8 @@ export const REVIEW = ".gtd/REVIEW.md"
|
|
|
14
14
|
export const QUALITY = ".gtd/QUALITY.md"
|
|
15
15
|
export const SPEC_FEEDBACK = ".gtd/SPEC_FEEDBACK.md"
|
|
16
16
|
|
|
17
|
-
const planner = (): string =>
|
|
18
|
-
const coder = (): string =>
|
|
17
|
+
const planner = (): string => env.plannerModel ?? ""
|
|
18
|
+
const coder = (): string => env.coderModel ?? ""
|
|
19
19
|
|
|
20
20
|
// ── Planning ────────────────────────────────────────────────────────────────
|
|
21
21
|
|
|
@@ -5,10 +5,11 @@ import {
|
|
|
5
5
|
type StepRequest,
|
|
6
6
|
} from "../flows/index.js"
|
|
7
7
|
import { skills as bundledSkills } from "./skills.js"
|
|
8
|
-
import { defaults } from "./vars.js"
|
|
8
|
+
import { defaults, envDefaults } from "./vars.js"
|
|
9
9
|
|
|
10
10
|
export interface TextContext {
|
|
11
11
|
readonly vars?: Readonly<Record<string, string>>
|
|
12
|
+
readonly env?: Readonly<Record<string, string>>
|
|
12
13
|
readonly head?: string
|
|
13
14
|
readonly start?: string
|
|
14
15
|
readonly codeThreads?: readonly CodeThreadInfo[]
|
|
@@ -73,6 +74,7 @@ export const fixtureContext = (
|
|
|
73
74
|
threads: () => [],
|
|
74
75
|
codeThreads: () => context.codeThreads ?? [],
|
|
75
76
|
vars: { ...defaults, ...context.vars },
|
|
77
|
+
env: { ...envDefaults, ...context.env },
|
|
76
78
|
head: () => context.head ?? "",
|
|
77
79
|
start: () => context.start ?? "",
|
|
78
80
|
skillsFor: skillsForOf(context),
|
package/src/workflows/text.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
agent,
|
|
3
3
|
codeThreads,
|
|
4
|
+
env,
|
|
4
5
|
head,
|
|
5
6
|
skillsFor,
|
|
6
7
|
start,
|
|
7
|
-
vars,
|
|
8
8
|
type AgentOptions,
|
|
9
9
|
type SummaryContext,
|
|
10
10
|
} from "../flows/index.js"
|
|
@@ -195,7 +195,7 @@ ${questionBarReturn}
|
|
|
195
195
|
never re-open one. A genuinely open PRODUCT point may still
|
|
196
196
|
raise a fresh \`## Open Questions\` entry: the product gate sits
|
|
197
197
|
on this path exactly as on the first lap
|
|
198
|
-
- Before grouping anything, run \`${
|
|
198
|
+
- Before grouping anything, run \`${env.testCommand}\`
|
|
199
199
|
yourself — a loop-back runs no green-baseline gate, so the
|
|
200
200
|
tree may already be red (reverting the edit can undo a fix it
|
|
201
201
|
made). If red, make that breakage the first concern, ahead of
|
package/src/workflows/unified.ts
CHANGED
|
@@ -31,7 +31,7 @@ import * as t from "./text.js"
|
|
|
31
31
|
// re-exported below.
|
|
32
32
|
|
|
33
33
|
export { threads, type ThreadInfo } from "../flows/index.js"
|
|
34
|
-
export { defaults } from "./vars.js"
|
|
34
|
+
export { defaults, envDefaults } from "./vars.js"
|
|
35
35
|
export { skills } from "./skills.js"
|
|
36
36
|
export { agentWithSkills } from "./text.js"
|
|
37
37
|
export * from "./steps.js"
|
package/src/workflows/vars.ts
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
// The bundled workflow's
|
|
2
|
-
//
|
|
1
|
+
// The bundled workflow's settings, in two kinds. A process setting changes
|
|
2
|
+
// which step comes next, so it is pinned for the whole process at its start;
|
|
3
|
+
// an environment setting only changes how a step runs on this machine, so it
|
|
4
|
+
// is read live on every invocation. `.gtdrc` `vars:`/`env:`, `--var` (process
|
|
5
|
+
// settings only) and `GTD_<NAME>` override either.
|
|
6
|
+
|
|
7
|
+
/** Process settings. */
|
|
3
8
|
export const defaults: Readonly<Record<string, string>> = {
|
|
4
|
-
testCommand: "npm test",
|
|
5
|
-
plannerModel: "smart",
|
|
6
|
-
coderModel: "base",
|
|
7
9
|
reviewBase: "",
|
|
8
10
|
judgeBudgetBytes: "32768",
|
|
9
11
|
judgeIdenticalMinP: "0.7",
|
|
10
12
|
specPreJudge: "0.9",
|
|
11
13
|
reviewNoteActionable: "0.7",
|
|
12
14
|
architectureSkipMinP: "0.85",
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// skill list, so it stays a var — pairing with `build.quality.reviewing`'s
|
|
18
|
-
// entry in `./skills.ts`, which replaces the lens on every one of those
|
|
19
|
-
// turns.
|
|
15
|
+
// `qualityReviews` fans out into one turn per entry (`qualityLenses`, in
|
|
16
|
+
// `./review.ts`) rather than naming one step's skill list, so it is a
|
|
17
|
+
// setting — pairing with `build.quality.reviewing`'s entry in
|
|
18
|
+
// `./skills.ts`, which replaces the lens on every one of those turns.
|
|
20
19
|
qualityReviews: "owasp-security, ponytail-review, test-audit",
|
|
21
20
|
}
|
|
21
|
+
|
|
22
|
+
/** Environment settings. */
|
|
23
|
+
export const envDefaults: Readonly<Record<string, string>> = {
|
|
24
|
+
testCommand: "npm test",
|
|
25
|
+
plannerModel: "smart",
|
|
26
|
+
coderModel: "base",
|
|
27
|
+
}
|