@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "61.0.0",
3
+ "version": "62.0.0",
4
4
  "description": "Drive the gtd loop inside Claude Code: agent turns run as subagents, gtd decides every beat",
5
5
  "author": {
6
6
  "name": "Philipp Melab"
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 ${PIN_FORMAT} gtd ${version}\n`), gzipSync(source, { level: 9 })]);
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
- const configNow = config.fingerprint;
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
- /** Repository-relative paths of the config files today's config and the process's own record name; one outside the repository cannot be read at a ref, so it is left out. */
26188
- const inRepoConfigPaths = (root, files, recorded) => {
26189
- const paths = new Set(Object.keys(recorded));
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, run, hash) => gen(function* () {
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), run.pinnedConfig?.files ?? {})) if ((yield* orElseSucceed(git.readFileAtRef(hash, path), () => void 0)) !== workspace.readSync(path)) differing.push(path);
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, run, hash);
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