@pmelab/gtd 23.0.0 → 23.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -3
- package/dist/gtd.bundle.mjs +83 -8
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +7 -3
package/README.md
CHANGED
|
@@ -60,9 +60,11 @@ enforces it at the OS level. See
|
|
|
60
60
|
|
|
61
61
|
Run `gtd install` at your repository's root. It writes a `gtd.config.ts` that
|
|
62
62
|
runs gtd's shipped workflows — commit it; gtd refuses to run without one — and
|
|
63
|
-
prints a briefing.
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
prints a briefing. The repository need not be a JavaScript project: gtd resolves
|
|
64
|
+
the config's `@pmelab/gtd/*` imports itself, so a Python, Rust or notes
|
|
65
|
+
repository needs no `package.json` and no install. Pipe that briefing into your
|
|
66
|
+
coding agent and answer its questions. It should build you a script that will
|
|
67
|
+
autonomously run the process for you. Ask your agent if you have any questions.
|
|
66
68
|
|
|
67
69
|
## Why
|
|
68
70
|
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -49222,6 +49222,13 @@ const parseJson$1 = (raw) => {
|
|
|
49222
49222
|
const TRAILER_RE = /^(Gtd-[A-Za-z-]+):[ \t]*(.*?)[ \t]*$/gm;
|
|
49223
49223
|
const COST_RE = /^([0-9]+(?:\.[0-9]+)?)(?:[ \t]+(.+))?$/;
|
|
49224
49224
|
const VAR_RE = /^([^=\s]+)=(.*)$/;
|
|
49225
|
+
const CONFIG_RE = /^([0-9a-f]{16}) (.+)$/;
|
|
49226
|
+
const CONTROL_CHARACTER$1 = /[\x00-\x1f\x7f-\x9f]/;
|
|
49227
|
+
const encodePath = (path) => path.trim() !== path || path.startsWith("\"") || CONTROL_CHARACTER$1.test(path) ? JSON.stringify(path) : path;
|
|
49228
|
+
const decodePath = (raw) => {
|
|
49229
|
+
const path = raw.startsWith("\"") ? parseJson$1(raw) : raw;
|
|
49230
|
+
return typeof path === "string" && path !== "" && !CONTROL_CHARACTER$1.test(path) ? path : void 0;
|
|
49231
|
+
};
|
|
49225
49232
|
const FORMAT_RE = /^[1-9][0-9]*$/;
|
|
49226
49233
|
const TRAILER_READERS = {
|
|
49227
49234
|
"Gtd-Step": (value, into) => {
|
|
@@ -49238,6 +49245,11 @@ const TRAILER_READERS = {
|
|
|
49238
49245
|
const kv = VAR_RE.exec(value);
|
|
49239
49246
|
if (kv !== null) into.vars[kv[1]] = kv[2];
|
|
49240
49247
|
},
|
|
49248
|
+
"Gtd-Config": (value, into) => {
|
|
49249
|
+
const entry = CONFIG_RE.exec(value);
|
|
49250
|
+
const path = entry === null ? void 0 : decodePath(entry[2]);
|
|
49251
|
+
if (path !== void 0) into.config[path] = entry[1];
|
|
49252
|
+
},
|
|
49241
49253
|
"Gtd-Review-Base": (value, into) => {
|
|
49242
49254
|
into.reviewBase ??= value.split(/\s/)[0];
|
|
49243
49255
|
},
|
|
@@ -49261,6 +49273,7 @@ const parseCommitMessage = (message) => {
|
|
|
49261
49273
|
reviewBase: void 0,
|
|
49262
49274
|
judge: [],
|
|
49263
49275
|
vars: {},
|
|
49276
|
+
config: {},
|
|
49264
49277
|
cost: [],
|
|
49265
49278
|
format: void 0
|
|
49266
49279
|
};
|
|
@@ -49272,13 +49285,15 @@ const parseCommitMessage = (message) => {
|
|
|
49272
49285
|
format: trailers.format ?? 1
|
|
49273
49286
|
};
|
|
49274
49287
|
};
|
|
49288
|
+
const sortedEntries = (record = {}) => Object.entries(record).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
49275
49289
|
/** Trailer order is fixed so the same spec always yields the same bytes. */
|
|
49276
49290
|
const formatCommitMessage = (spec) => {
|
|
49277
49291
|
const lines = [];
|
|
49278
49292
|
if (spec.step !== void 0) lines.push(`Gtd-Step: ${formatStepId(spec.step)}`);
|
|
49279
49293
|
if (spec.workflow !== void 0) lines.push(`Gtd-Workflow: ${spec.workflow}`);
|
|
49280
49294
|
if (spec.reviewBase !== void 0) lines.push(`Gtd-Review-Base: ${spec.reviewBase}`);
|
|
49281
|
-
for (const [name, value] of
|
|
49295
|
+
for (const [name, value] of sortedEntries(spec.vars)) lines.push(`Gtd-Var: ${name}=${value}`);
|
|
49296
|
+
for (const [path, hash] of sortedEntries(spec.config)) lines.push(`Gtd-Config: ${hash} ${encodePath(path)}`);
|
|
49282
49297
|
if (spec.cost !== void 0) lines.push(`Gtd-Cost: ${spec.cost.cost}${spec.cost.model !== void 0 ? ` ${spec.cost.model}` : ""}`);
|
|
49283
49298
|
for (const verdict of spec.judge ?? []) lines.push(`Gtd-Judge: ${JSON.stringify(verdict)}`);
|
|
49284
49299
|
lines.push(`Gtd-Format: 1`);
|
|
@@ -50438,6 +50453,35 @@ const resolveScopeAccess = (def, skills, vars) => gen(function* () {
|
|
|
50438
50453
|
//#region src/workflow/Workflow.ts
|
|
50439
50454
|
const knownModes = (def) => Object.keys(def.modes);
|
|
50440
50455
|
//#endregion
|
|
50456
|
+
//#region src/workflow/fingerprint.ts
|
|
50457
|
+
const HASH_LENGTH = 16;
|
|
50458
|
+
const CONTROL_CHARACTER = /[\x00-\x1f\x7f-\x9f]/;
|
|
50459
|
+
const contentHash = (content) => createHash("sha256").update(content).digest("hex").slice(0, HASH_LENGTH);
|
|
50460
|
+
const realOr = (path) => {
|
|
50461
|
+
try {
|
|
50462
|
+
return realpathSync(path);
|
|
50463
|
+
} catch {
|
|
50464
|
+
return path;
|
|
50465
|
+
}
|
|
50466
|
+
};
|
|
50467
|
+
const configFingerprint = (files, root) => {
|
|
50468
|
+
const fingerprint = {};
|
|
50469
|
+
const realRoot = realOr(root);
|
|
50470
|
+
for (const file of files) {
|
|
50471
|
+
const path = relative(realRoot, realOr(file)).split(sep).join("/");
|
|
50472
|
+
if (CONTROL_CHARACTER.test(path)) continue;
|
|
50473
|
+
let content;
|
|
50474
|
+
try {
|
|
50475
|
+
content = readFileSync(file);
|
|
50476
|
+
} catch {
|
|
50477
|
+
continue;
|
|
50478
|
+
}
|
|
50479
|
+
fingerprint[path] = contentHash(content);
|
|
50480
|
+
}
|
|
50481
|
+
return fingerprint;
|
|
50482
|
+
};
|
|
50483
|
+
const changedConfigFiles = (recorded, current) => [.../* @__PURE__ */ new Set([...Object.keys(recorded), ...Object.keys(current)])].filter((path) => recorded[path] !== current[path]).sort();
|
|
50484
|
+
//#endregion
|
|
50441
50485
|
//#region src/judges/providers/fixed.ts
|
|
50442
50486
|
const fail$3 = (message) => {
|
|
50443
50487
|
throw new Error(`gtd judge run: ${message}`);
|
|
@@ -56082,7 +56126,8 @@ const checkOverrides = (commandLabel, varOverrides, declaredNames, envNames) =>
|
|
|
56082
56126
|
* `gtd --workflow <name>`: start a brand NEW process on a workflow, writing an
|
|
56083
56127
|
* opening commit that names the workflow's first step and carries a
|
|
56084
56128
|
* `Gtd-Workflow:` trailer, every process setting as a `Gtd-Var:` trailer, plus
|
|
56085
|
-
* a `Gtd-Review-Base:` trailer when the workflow fixes the process's diff base
|
|
56129
|
+
* a `Gtd-Review-Base:` trailer when the workflow fixes the process's diff base,
|
|
56130
|
+
* and a `Gtd-Config:` trailer per config file.
|
|
56086
56131
|
* All validation is a REFUSAL, not an Effect failure.
|
|
56087
56132
|
*/
|
|
56088
56133
|
const planStart = (current, actor, start) => gen(function* () {
|
|
@@ -56117,7 +56162,8 @@ const planStart = (current, actor, start) => gen(function* () {
|
|
|
56117
56162
|
to: firstStep,
|
|
56118
56163
|
workflow: name,
|
|
56119
56164
|
...base !== void 0 ? { reviewBase: base } : {},
|
|
56120
|
-
vars
|
|
56165
|
+
vars,
|
|
56166
|
+
config: start.config
|
|
56121
56167
|
})
|
|
56122
56168
|
}
|
|
56123
56169
|
}, {
|
|
@@ -56218,6 +56264,15 @@ const runOf = (history, location, closingHash = void 0) => {
|
|
|
56218
56264
|
const fault = historyFormatFault(history, location);
|
|
56219
56265
|
return fault === void 0 ? succeed$3(buildRun(history, location, closingHash)) : fail$7(new Error(fault));
|
|
56220
56266
|
};
|
|
56267
|
+
const pinnedConfigOf = (history) => {
|
|
56268
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
56269
|
+
const files = parseCommitMessage(history[i].message).config;
|
|
56270
|
+
if (Object.keys(files).length > 0) return {
|
|
56271
|
+
commit: history[i].hash,
|
|
56272
|
+
files
|
|
56273
|
+
};
|
|
56274
|
+
}
|
|
56275
|
+
};
|
|
56221
56276
|
const buildRun = (history, location, closingHash) => {
|
|
56222
56277
|
const processCommits = history.slice(location.processStart);
|
|
56223
56278
|
const parsed = processCommits.map((c) => parseCommitMessage(c.message));
|
|
@@ -56234,6 +56289,7 @@ const buildRun = (history, location, closingHash) => {
|
|
|
56234
56289
|
costEntries: parsed.flatMap(costEntriesOf),
|
|
56235
56290
|
judgeVerdicts: parsed.flatMap((m) => m.judge),
|
|
56236
56291
|
pinnedVars: { ...first?.vars },
|
|
56292
|
+
pinnedConfig: pinnedConfigOf(history.slice(Math.max(location.baseIndex, 0))),
|
|
56237
56293
|
headTurn: headTurnOf(head),
|
|
56238
56294
|
closingHash,
|
|
56239
56295
|
episode: {
|
|
@@ -56322,6 +56378,18 @@ const findBoundaryBase = (git, def, head) => gen(function* () {
|
|
|
56322
56378
|
});
|
|
56323
56379
|
const historyUpTo = (git, def, head) => flatMap$4(findBoundaryBase(git, def, head), ({ base, head: resolvedHead }) => git.commitHistory(base, resolvedHead));
|
|
56324
56380
|
const computeProcessRun = (git, def, head) => flatMap$4(historyUpTo(git, def, head), (history) => runOf(history, locateEpisode(def, history)));
|
|
56381
|
+
const changedSinceStart = (pinned, current, workspace) => {
|
|
56382
|
+
if (pinned === void 0) return [];
|
|
56383
|
+
return changedConfigFiles(pinned.files, current).filter((path) => {
|
|
56384
|
+
const committed = workspace.readCommittedSync(path, pinned.commit);
|
|
56385
|
+
return current[path] === void 0 || committed === void 0 || contentHash(committed) !== current[path];
|
|
56386
|
+
});
|
|
56387
|
+
};
|
|
56388
|
+
const warnConfigChanges = (run, current, workspace) => gen(function* () {
|
|
56389
|
+
const changed = changedSinceStart(run.pinnedConfig, current, workspace);
|
|
56390
|
+
if (changed.length === 0) return;
|
|
56391
|
+
yield* (yield* Narrator).warn(`gtd: warning: the config changed since this process started (${changed.join(", ")}) — the rest of the process replays under the changed config`);
|
|
56392
|
+
});
|
|
56325
56393
|
/** The run alone, never replaying — `gtd abandon` must work even when replay would refuse, short of a history it cannot read. */
|
|
56326
56394
|
const currentRun = gen(function* () {
|
|
56327
56395
|
const git = yield* GitService;
|
|
@@ -56621,6 +56689,8 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
56621
56689
|
path: e.path
|
|
56622
56690
|
})) : [];
|
|
56623
56691
|
const memory = memoryOf(outcome.trace, run);
|
|
56692
|
+
const configNow = configFingerprint(config.workflowFiles, host.root);
|
|
56693
|
+
if (ref === void 0) yield* warnConfigChanges(run, configNow, workspace);
|
|
56624
56694
|
return {
|
|
56625
56695
|
def,
|
|
56626
56696
|
workflowName: resolved.name,
|
|
@@ -56632,6 +56702,7 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
56632
56702
|
run,
|
|
56633
56703
|
vars,
|
|
56634
56704
|
env,
|
|
56705
|
+
config: configNow,
|
|
56635
56706
|
changes,
|
|
56636
56707
|
memory: memory.key,
|
|
56637
56708
|
memoryResumed: memory.resumed,
|
|
@@ -56763,7 +56834,10 @@ const landingTo = (rest, to) => {
|
|
|
56763
56834
|
from: rest.state,
|
|
56764
56835
|
to,
|
|
56765
56836
|
step: rest.step.id,
|
|
56766
|
-
...starts ? {
|
|
56837
|
+
...starts ? {
|
|
56838
|
+
vars: rest.vars,
|
|
56839
|
+
config: rest.config
|
|
56840
|
+
} : {}
|
|
56767
56841
|
}
|
|
56768
56842
|
};
|
|
56769
56843
|
};
|
|
@@ -69222,8 +69296,8 @@ const normalizeScriptNewline = (script) => script.length === 0 ? script : `${scr
|
|
|
69222
69296
|
const landingScript = (required) => combinedScript(required, "");
|
|
69223
69297
|
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
69224
69298
|
/** Decides `gtd land`'s one transition without performing it. Refusals fail the Effect; a no-op returns `subject: null` and empty scripts. */
|
|
69225
|
-
const planLanding = (opts = {}) => gen(function* () {
|
|
69226
|
-
const rest = yield* currentRest;
|
|
69299
|
+
const planLanding = (opts = {}, current) => gen(function* () {
|
|
69300
|
+
const rest = current ?? (yield* currentRest);
|
|
69227
69301
|
const outcome = planStep(yield* snapshotFromRest(rest, opts.judge), opts);
|
|
69228
69302
|
if (outcome.kind === "refusal") return yield* fail$7(new Error(outcome.message));
|
|
69229
69303
|
if (outcome.kind === "noop") {
|
|
@@ -69358,7 +69432,7 @@ const runJudgeAnswerCommand = (json, out) => gen(function* () {
|
|
|
69358
69432
|
catch: () => new SelectorUsageError("gtd judge answer: stdin is not valid JSON")
|
|
69359
69433
|
});
|
|
69360
69434
|
const verdict = yield* decodeUnknown(verdictSchemaFor(ids))(parsed).pipe(mapError$1((e) => new SelectorUsageError(`gtd judge answer: the verdict on stdin does not match the pending questions — ${e.message}`)));
|
|
69361
|
-
const built = landFields(yield* planLanding({ judge: verdict }));
|
|
69435
|
+
const built = landFields(yield* planLanding({ judge: verdict }, rest));
|
|
69362
69436
|
if (json.kind === "document") out.write(renderLandJson(built));
|
|
69363
69437
|
else if (json.kind === "select") yield* writeSelection(out, built, json.path);
|
|
69364
69438
|
else out.write(renderLandPlain(built, "judge answer"));
|
|
@@ -69398,7 +69472,8 @@ const runStartCommand = (actor, workflow, varOverrides, out, commandLabel) => ge
|
|
|
69398
69472
|
workflow: resolved,
|
|
69399
69473
|
first: started,
|
|
69400
69474
|
commandLabel,
|
|
69401
|
-
vars: varOverrides
|
|
69475
|
+
vars: varOverrides,
|
|
69476
|
+
config: rest.config
|
|
69402
69477
|
});
|
|
69403
69478
|
if (plan.kind === "refusal") return yield* fail$7(new Error(plan.message));
|
|
69404
69479
|
out.write(landingScript(ScriptSurface.render(plan.steps)));
|
package/package.json
CHANGED
|
@@ -186,9 +186,13 @@ certainly a bug.
|
|
|
186
186
|
## Rules for flow code
|
|
187
187
|
|
|
188
188
|
Flow code is replayed on every command, so it must reach the same steps every
|
|
189
|
-
time it sees the same history. `run()` bodies
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
time it sees the same history. Only `run()` bodies are exempt: module top-level
|
|
190
|
+
code and every local import are evaluated on every command too, so they read no
|
|
191
|
+
`process.env`, clock, network or files either (a per-machine value is an
|
|
192
|
+
`envDefaults` setting). A process records the config's hashes when it starts and
|
|
193
|
+
warns whenever it differs. gtd does not read the source ahead of time: breaking
|
|
194
|
+
a rule shows up when replay runs, as an error or, for nondeterminism, as a
|
|
195
|
+
divergence later.
|
|
192
196
|
|
|
193
197
|
- No IO or nondeterminism in flow code — no clock, randomness, environment,
|
|
194
198
|
network or filesystem. Read the tree through the helpers; do IO inside a
|