@pmelab/gtd 61.0.0 → 62.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +9 -0
- package/dist/chunks/{testkit-C9sq0x07.mjs → testkit-XQeYCNMn.mjs} +20 -61
- package/dist/gtd.bundle.mjs +227 -80
- package/dist/gtd.core.mjs +110 -110
- package/dist/testkit/gherkin.mjs +1 -1
- package/dist/testkit.mjs +1 -1
- package/package.json +1 -1
- package/src/cli/Cli.ts +6 -1
- package/src/cli/Launch.test.ts +52 -0
- package/src/cli/Launch.ts +143 -0
- package/src/cli/Program.test.ts +1 -2
- package/src/cli/Program.ts +0 -1
- package/src/cli/index.ts +1 -0
- package/src/edge/Edge.test.ts +3 -3
- package/src/edge/Edge.ts +17 -71
- package/src/platform/PinFile.ts +61 -0
- package/src/platform/index.ts +1 -0
- package/src/replay/Trailers.test.ts +0 -37
- package/src/replay/Trailers.ts +2 -23
- package/src/step/planStart.test.ts +0 -1
- package/src/step/planStart.ts +1 -5
- package/src/twins/PinFile.test.ts +107 -0
- package/src/twins/PinFile.ts +12 -0
- package/src/twins/index.ts +1 -0
- package/src/workflow/fingerprint.ts +2 -2
- package/src/workflow/index.ts +2 -2
- package/src/workflow/load.ts +14 -1
- package/src/workflow/pin.ts +15 -7
package/README.md
CHANGED
|
@@ -643,6 +643,15 @@ Both are ordinary `script` beats a driver runs unattended; see
|
|
|
643
643
|
[Pin and unpin beats](https://github.com/pmelab/gtd/blob/main/docs/driver.md#pin-and-unpin-beats).
|
|
644
644
|
Histories with pin commits are history format 2, which an older gtd refuses.
|
|
645
645
|
|
|
646
|
+
While a process is pinned, the `gtd` on your `PATH` hands every command over to
|
|
647
|
+
the pin, so editing the workflow or upgrading gtd mid-process changes nothing;
|
|
648
|
+
gtd notes on stderr when the live config differs from the pinned one. `ui`,
|
|
649
|
+
`lsp`, `guide`, `bundle`, `help`, `version` and the `history` commands always
|
|
650
|
+
run live, and `gtd --live <command>` runs the live engine for any other, at the
|
|
651
|
+
risk of misreading the process. A pin in a format your gtd does not know is
|
|
652
|
+
refused: upgrade gtd. See
|
|
653
|
+
[Pinned processes](https://github.com/pmelab/gtd/blob/main/docs/cli.md#pinned-processes).
|
|
654
|
+
|
|
646
655
|
## License
|
|
647
656
|
|
|
648
657
|
MIT
|
|
@@ -22873,13 +22873,7 @@ const parseJson = (raw) => {
|
|
|
22873
22873
|
const TRAILER_RE = /^(Gtd-[A-Za-z-]+):[ \t]*(.*?)[ \t]*$/gm;
|
|
22874
22874
|
const COST_RE = /^([0-9]+(?:\.[0-9]+)?)(?:[ \t]+(.+))?$/;
|
|
22875
22875
|
const VAR_RE = /^([^=\s]+)=(.*)$/;
|
|
22876
|
-
const CONFIG_RE = /^([0-9a-f]{16}) (.+)$/;
|
|
22877
22876
|
const CONTROL_CHARACTER$1 = /[\x00-\x1f\x7f-\x9f\u2028\u2029]/;
|
|
22878
|
-
const encodePath = (path) => path.trim() !== path || path.startsWith("\"") || CONTROL_CHARACTER$1.test(path) ? JSON.stringify(path) : path;
|
|
22879
|
-
const decodePath = (raw) => {
|
|
22880
|
-
const path = raw.startsWith("\"") ? parseJson(raw) : raw;
|
|
22881
|
-
return typeof path === "string" && path !== "" && !CONTROL_CHARACTER$1.test(path) ? path : void 0;
|
|
22882
|
-
};
|
|
22883
22877
|
const decodeApproval = (raw) => {
|
|
22884
22878
|
const parsed = parseJson(raw);
|
|
22885
22879
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
@@ -22931,11 +22925,6 @@ const TRAILER_READERS = {
|
|
|
22931
22925
|
const kv = VAR_RE.exec(value);
|
|
22932
22926
|
if (kv !== null) into.vars[kv[1]] = kv[2];
|
|
22933
22927
|
},
|
|
22934
|
-
"Gtd-Config": (value, into) => {
|
|
22935
|
-
const entry = CONFIG_RE.exec(value);
|
|
22936
|
-
const path = entry === null ? void 0 : decodePath(entry[2]);
|
|
22937
|
-
if (path !== void 0) into.config[path] = entry[1];
|
|
22938
|
-
},
|
|
22939
22928
|
"Gtd-Review-Base": (value, into) => {
|
|
22940
22929
|
into.reviewBase ??= value.split(/\s/)[0];
|
|
22941
22930
|
},
|
|
@@ -22980,7 +22969,6 @@ const parseCommitMessage = (message) => {
|
|
|
22980
22969
|
judge: [],
|
|
22981
22970
|
marks: [],
|
|
22982
22971
|
vars: {},
|
|
22983
|
-
config: {},
|
|
22984
22972
|
cost: [],
|
|
22985
22973
|
format: void 0
|
|
22986
22974
|
};
|
|
@@ -23002,7 +22990,6 @@ const formatCommitMessage = (spec) => {
|
|
|
23002
22990
|
...trailer("Workflow", spec.workflow),
|
|
23003
22991
|
...trailer("Review-Base", spec.reviewBase),
|
|
23004
22992
|
...sortedEntries(spec.vars).map(([name, value]) => `Gtd-Var: ${name}=${value}`),
|
|
23005
|
-
...sortedEntries(spec.config).map(([path, hash]) => `Gtd-Config: ${hash} ${encodePath(path)}`),
|
|
23006
22993
|
...trailer("Cost", costValue(spec.cost)),
|
|
23007
22994
|
...(spec.judge ?? []).map((verdict) => `Gtd-Judge: ${JSON.stringify(verdict)}`),
|
|
23008
22995
|
...(spec.marks ?? []).map((m) => `Gtd-Mark: ${m.name}=${m.value}`),
|
|
@@ -23912,7 +23899,8 @@ const load = gen(function* () {
|
|
|
23912
23899
|
},
|
|
23913
23900
|
...compiled.ui !== void 0 ? { ui: compiled.ui } : {},
|
|
23914
23901
|
warnings: diagnostics.filter((d) => d.severity === "warning"),
|
|
23915
|
-
fingerprint: pinned?.fingerprint ?? configFingerprint(loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk)
|
|
23902
|
+
fingerprint: pinned?.fingerprint ?? configFingerprint(loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk),
|
|
23903
|
+
drift: pinned === void 0 ? [] : changedConfigFiles(pinned.fingerprint, configFingerprint(Object.keys(pinned.fingerprint).map((file) => resolve(host.root, file)), host.root, readFromDisk))
|
|
23916
23904
|
};
|
|
23917
23905
|
});
|
|
23918
23906
|
const readFromDisk = (path) => {
|
|
@@ -24562,10 +24550,6 @@ const resolveScopeAccess = (def, skills, vars) => gen(function* () {
|
|
|
24562
24550
|
//#endregion
|
|
24563
24551
|
//#region src/workflow/Workflow.ts
|
|
24564
24552
|
const knownModes = (def) => Object.keys(def.modes);
|
|
24565
|
-
//#endregion
|
|
24566
|
-
//#region src/workflow/pin.ts
|
|
24567
|
-
/** Bumped when a launcher could no longer run an older pin the same way. */
|
|
24568
|
-
const PIN_FORMAT = 1;
|
|
24569
24553
|
/**
|
|
24570
24554
|
* The engine a pin carries, as ES module source. Live, the `gtd.core.mjs` the
|
|
24571
24555
|
* running gtd ships beside its own bundle.
|
|
@@ -24591,7 +24575,7 @@ const pin = (module, root, version) => gen(function* () {
|
|
|
24591
24575
|
try: () => pinnedWorkflowSource(module, root, version),
|
|
24592
24576
|
catch: (e) => new GtdError(`gtd bundle: ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
|
|
24593
24577
|
})) + core.replace(/^#!.*\n/, "");
|
|
24594
|
-
return Buffer.concat([Buffer.from(`gtdpin
|
|
24578
|
+
return Buffer.concat([Buffer.from(`gtdpin 1 gtd ${version}\n`), gzipSync(source, { level: 9 })]);
|
|
24595
24579
|
});
|
|
24596
24580
|
//#endregion
|
|
24597
24581
|
//#region src/emit/GitScript.ts
|
|
@@ -25231,8 +25215,7 @@ const checkOverrides = (commandLabel, varOverrides, declaredNames, envNames) =>
|
|
|
25231
25215
|
* `gtd start <name>`: start a brand NEW process on a workflow, writing an
|
|
25232
25216
|
* opening commit that names the workflow's first step and carries a
|
|
25233
25217
|
* `Gtd-Workflow:` trailer, every process setting as a `Gtd-Var:` trailer, plus
|
|
25234
|
-
* a `Gtd-Review-Base:` trailer when the workflow fixes the process's diff base
|
|
25235
|
-
* and a `Gtd-Config:` trailer per config file.
|
|
25218
|
+
* and a `Gtd-Review-Base:` trailer when the workflow fixes the process's diff base.
|
|
25236
25219
|
* All validation is a REFUSAL, not an Effect failure.
|
|
25237
25220
|
*/
|
|
25238
25221
|
const planStart = (current, actor, start) => gen(function* () {
|
|
@@ -25270,8 +25253,7 @@ const planStart = (current, actor, start) => gen(function* () {
|
|
|
25270
25253
|
to: firstStep,
|
|
25271
25254
|
workflow: name,
|
|
25272
25255
|
...base !== void 0 ? { reviewBase: base } : {},
|
|
25273
|
-
vars
|
|
25274
|
-
config: start.config
|
|
25256
|
+
vars
|
|
25275
25257
|
})
|
|
25276
25258
|
}
|
|
25277
25259
|
}, {
|
|
@@ -25378,15 +25360,6 @@ const runOf = (history, location, closingHash = void 0) => {
|
|
|
25378
25360
|
const fault = historyFormatFault(history, location);
|
|
25379
25361
|
return fault === void 0 ? succeed$1(buildRun(history, location, closingHash)) : fail$4(new Error(fault));
|
|
25380
25362
|
};
|
|
25381
|
-
const pinnedConfigOf = (history) => {
|
|
25382
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
25383
|
-
const files = parseCommitMessage(history[i].message).config;
|
|
25384
|
-
if (Object.keys(files).length > 0) return {
|
|
25385
|
-
commit: history[i].hash,
|
|
25386
|
-
files
|
|
25387
|
-
};
|
|
25388
|
-
}
|
|
25389
|
-
};
|
|
25390
25363
|
const approvalEntryOf = (hash, message) => message.approved === void 0 ? [] : [{
|
|
25391
25364
|
step: formatStepId(message.approved.step),
|
|
25392
25365
|
name: message.approved.name,
|
|
@@ -25414,7 +25387,6 @@ const buildRun = (history, location, closingHash) => {
|
|
|
25414
25387
|
marks: parsed.flatMap((m) => m.marks),
|
|
25415
25388
|
approvals: processCommits.flatMap((c, i) => approvalEntryOf(c.hash, parsed[i])),
|
|
25416
25389
|
pinnedVars: { ...first?.vars },
|
|
25417
|
-
pinnedConfig: pinnedConfigOf(history.slice(Math.max(location.baseIndex, 0))),
|
|
25418
25390
|
headTurn: headTurnOf(head),
|
|
25419
25391
|
closingHash,
|
|
25420
25392
|
episode: {
|
|
@@ -25504,18 +25476,6 @@ const findBoundaryBase = (git, def, head) => gen(function* () {
|
|
|
25504
25476
|
});
|
|
25505
25477
|
const historyUpTo = (git, def, head) => flatMap(findBoundaryBase(git, def, head), ({ base, head: resolvedHead }) => git.commitHistory(base, resolvedHead));
|
|
25506
25478
|
const computeProcessRun = (git, def, head) => flatMap(historyUpTo(git, def, head), (history) => runOf(history, locateEpisode(def, history)));
|
|
25507
|
-
const changedSinceStart = (pinned, current, workspace) => {
|
|
25508
|
-
if (pinned === void 0) return [];
|
|
25509
|
-
return changedConfigFiles(pinned.files, current).filter((path) => {
|
|
25510
|
-
const committed = workspace.readCommittedSync(path, pinned.commit);
|
|
25511
|
-
return current[path] === void 0 || committed === void 0 || contentHash(committed) !== current[path];
|
|
25512
|
-
});
|
|
25513
|
-
};
|
|
25514
|
-
const warnConfigChanges = (run, current, workspace) => gen(function* () {
|
|
25515
|
-
const changed = changedSinceStart(run.pinnedConfig, current, workspace);
|
|
25516
|
-
if (changed.length === 0) return;
|
|
25517
|
-
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`);
|
|
25518
|
-
});
|
|
25519
25479
|
/** The run alone, never replaying — `gtd abandon` must work even when replay would refuse, short of a history it cannot read. */
|
|
25520
25480
|
const currentRun = gen(function* () {
|
|
25521
25481
|
const git = yield* GitService;
|
|
@@ -25831,8 +25791,7 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
25831
25791
|
status: normalizeStatus(e.status),
|
|
25832
25792
|
path: e.path
|
|
25833
25793
|
})) : [];
|
|
25834
|
-
|
|
25835
|
-
if (ref === void 0) yield* warnConfigChanges(run, configNow, workspace);
|
|
25794
|
+
if (ref === void 0 && config.drift.length > 0) yield* (yield* Narrator).warn(`gtd: notice: the live config differs from the pinned one (${config.drift.join(", ")}); \`gtd repin\` to switch`);
|
|
25836
25795
|
return {
|
|
25837
25796
|
def,
|
|
25838
25797
|
workflowName: resolved.name,
|
|
@@ -25846,7 +25805,6 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
25846
25805
|
run,
|
|
25847
25806
|
vars,
|
|
25848
25807
|
env,
|
|
25849
|
-
config: configNow,
|
|
25850
25808
|
changes,
|
|
25851
25809
|
memory: memory.key,
|
|
25852
25810
|
memoryResumed: memory.resumed,
|
|
@@ -26110,10 +26068,7 @@ const pinRefusal = (pin) => ({
|
|
|
26110
26068
|
kind: "refusal",
|
|
26111
26069
|
message: pin === "add" ? `gtd land: ${PIN_PATH} is missing — run the pin beat's script (\`gtd next --json=content\`); if its \`gtd bundle\` fails, fix what it reports, or run \`gtd abandon\`` : `gtd land: ${PIN_PATH} is still there — run the unpin beat's script (\`gtd next --json=content\`), then land`
|
|
26112
26070
|
});
|
|
26113
|
-
const startTrailers = (rest, starts) => starts ? {
|
|
26114
|
-
vars: rest.vars,
|
|
26115
|
-
config: rest.config
|
|
26116
|
-
} : {};
|
|
26071
|
+
const startTrailers = (rest, starts) => starts ? { vars: rest.vars } : {};
|
|
26117
26072
|
const outcomeMarks = (outcome) => outcome.kind === "rest" || outcome.kind === "ended" ? outcome.marks : [];
|
|
26118
26073
|
/**
|
|
26119
26074
|
* What landing the pending turn at `rest` does — a pure decision from a
|
|
@@ -26184,16 +26139,20 @@ const summaryFor = (run) => gen(function* () {
|
|
|
26184
26139
|
if (summary === void 0 || run.trace.length === 0) return { prompt: void 0 };
|
|
26185
26140
|
return { prompt: summary(summaryInput(run, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))) };
|
|
26186
26141
|
});
|
|
26187
|
-
/**
|
|
26188
|
-
|
|
26189
|
-
|
|
26142
|
+
/**
|
|
26143
|
+
* Repository-relative paths of today's config files; one outside the
|
|
26144
|
+
* repository cannot be read at a ref, so it is left out. A file only the old
|
|
26145
|
+
* config read needs no check: whichever file imported it differs too.
|
|
26146
|
+
*/
|
|
26147
|
+
const inRepoConfigPaths = (root, files) => {
|
|
26148
|
+
const paths = /* @__PURE__ */ new Set();
|
|
26190
26149
|
for (const file of files) paths.add(relative(root, resolve(root, file)).split(sep).join("/"));
|
|
26191
26150
|
return [...paths].filter((p) => p !== "" && !p.startsWith("..") && !isAbsolute(p)).sort();
|
|
26192
26151
|
};
|
|
26193
26152
|
const configRefusal = (command, tip, paths, reason = "A history ref replays under today's config, so it is unreadable after the config changes") => /* @__PURE__ */ new Error(`${command}: refused — config differs at ${tip.slice(0, 7)}: ${paths.join(", ")}. ${reason}`);
|
|
26194
|
-
const differingConfigPaths = (git, workspace, root, config,
|
|
26153
|
+
const differingConfigPaths = (git, workspace, root, config, hash) => gen(function* () {
|
|
26195
26154
|
const differing = [];
|
|
26196
|
-
for (const path of inRepoConfigPaths(root, Object.keys(config.fingerprint)
|
|
26155
|
+
for (const path of inRepoConfigPaths(root, Object.keys(config.fingerprint))) if ((yield* orElseSucceed(git.readFileAtRef(hash, path), () => void 0)) !== workspace.readSync(path)) differing.push(path);
|
|
26197
26156
|
return differing;
|
|
26198
26157
|
});
|
|
26199
26158
|
const notClosedError = (hash, outcome) => outcome.kind === "rest" ? /* @__PURE__ */ new Error(`gtd history: ${hash.slice(0, 7)} does not close a process under today's config`) : replayError(outcome) ?? /* @__PURE__ */ new Error("gtd history: replay failed");
|
|
@@ -26250,7 +26209,7 @@ const replayFinishedProcess = (config, run, hash, how) => gen(function* () {
|
|
|
26250
26209
|
const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
|
|
26251
26210
|
const versionWarning = versionWarningFor(recorded);
|
|
26252
26211
|
const withWarning = (err) => versionWarning === void 0 ? err : /* @__PURE__ */ new Error(`${err.message}\n${versionWarning}`);
|
|
26253
|
-
const differing = yield* differingConfigPaths(git, workspace, host.root, config,
|
|
26212
|
+
const differing = yield* differingConfigPaths(git, workspace, host.root, config, hash);
|
|
26254
26213
|
if (differing.length > 0) return yield* fail$4(withWarning(configRefusal(how.command, hash, differing, how.configReason)));
|
|
26255
26214
|
const { setup } = yield* setupFor(config, run, workspace, host);
|
|
26256
26215
|
const outcome = yield* promise(() => replayFor(setup));
|
|
@@ -27212,8 +27171,7 @@ const startWorkflow = (workflow, varOverrides, json, out, commandLabel) => gen(f
|
|
|
27212
27171
|
workflow: resolved,
|
|
27213
27172
|
first: started,
|
|
27214
27173
|
commandLabel,
|
|
27215
|
-
vars: varOverrides
|
|
27216
|
-
config: rest.config
|
|
27174
|
+
vars: varOverrides
|
|
27217
27175
|
});
|
|
27218
27176
|
if (plan.kind === "refusal") return yield* fail$4(new Error(plan.message));
|
|
27219
27177
|
yield* writeScript(json, out, listingCommitted(landingScript(ScriptSurface.render(plan.steps)), rest.changes));
|
|
@@ -28215,6 +28173,7 @@ const flagHeader = (row) => {
|
|
|
28215
28173
|
};
|
|
28216
28174
|
const GLOBAL_OPTIONS = [
|
|
28217
28175
|
["-v, --verbose", "Narrate on stderr what gtd resolved"],
|
|
28176
|
+
["--live", "Run this gtd, not the process's pin; it may misread the process"],
|
|
28218
28177
|
["-h, --help", "Print help; gtd <command> --help for one command"],
|
|
28219
28178
|
["-V, --version", "Print the version"],
|
|
28220
28179
|
["--", "End the options: every word after it is an argument"]
|
|
@@ -28226,7 +28185,7 @@ const EXTRA_TOPICS = [[
|
|
|
28226
28185
|
], [
|
|
28227
28186
|
"version",
|
|
28228
28187
|
"Print the version",
|
|
28229
|
-
"Print gtd's version."
|
|
28188
|
+
"Print gtd's version, and the version of a pinned process's gtd."
|
|
28230
28189
|
]];
|
|
28231
28190
|
const EXAMPLES = ` $ gtd start list what can start
|
|
28232
28191
|
$ gtd start <name> start a process
|