@pmelab/gtd 61.0.0 → 62.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "61.0.0",
3
+ "version": "62.1.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
@@ -624,10 +624,10 @@ install for any of these but the testkit's: gtd resolves a `gtd.config.ts`'s
624
624
  `@pmelab/gtd/*` imports to its own copy.
625
625
 
626
626
  The `gtd` bin's commands — `next`, `land`, `start`, `abandon`, `restore`,
627
- `validate`, `check`, `uncheck`, `guide`, `bundle`, `summary`, `base`, `version`,
628
- `help`, `history list`, `history show`, `history metrics`, `history judges`,
629
- `history fetch`, `strays`, the tools `ui`, `lsp`, `judge` (`judge answer`,
630
- `judge run`) — are listed in the
627
+ `repin`, `validate`, `check`, `uncheck`, `guide`, `bundle`, `summary`, `base`,
628
+ `version`, `help`, `history list`, `history show`, `history metrics`,
629
+ `history judges`, `history fetch`, `strays`, the tools `ui`, `lsp`, `judge`
630
+ (`judge answer`, `judge run`) — are listed in the
631
631
  [CLI reference](https://github.com/pmelab/gtd/blob/main/docs/cli.md). Every
632
632
  command that prints a script takes `--json=script`, and `--` ends the options.
633
633
 
@@ -643,6 +643,17 @@ 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`, `repin`, `help`, `version` and the `history` commands
650
+ always run live, and `gtd --live <command>` runs the live engine for any other,
651
+ at the risk of misreading the process. To fix a workflow mid-process, edit it
652
+ and run `gtd repin`: its script commits a fresh pin from the live engine and
653
+ config, and the process continues on it. A pin in a format your gtd does not
654
+ know is refused: upgrade gtd. See
655
+ [Pinned processes](https://github.com/pmelab/gtd/blob/main/docs/cli.md#pinned-processes).
656
+
646
657
  ## License
647
658
 
648
659
  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
@@ -24626,6 +24610,12 @@ const pinScript = (path) => {
24626
24610
  const slash = path.lastIndexOf("/");
24627
24611
  return `${slash === -1 ? "" : `mkdir -p ${shellQuote(path.slice(0, slash))}\n`}gtd bundle >${p} || { rm -f -- ${p}; exit 1; }`;
24628
24612
  };
24613
+ /** `gtd repin`'s pin write: a failed `gtd bundle` leaves the committed pin in place. */
24614
+ const repinScript = (path) => {
24615
+ const p = shellQuote(path);
24616
+ const next = shellQuote(`${path}.new`);
24617
+ return `gtd bundle >${next} || { rm -f -- ${next}; exit 1; }\nmv -f -- ${next} ${p}`;
24618
+ };
24629
24619
  /** An unpin beat's script, and `gtd abandon`'s removal of the pin. */
24630
24620
  const unpinScript = (path) => `rm -f -- ${shellQuote(path)}`;
24631
24621
  /**
@@ -24866,6 +24856,12 @@ const FMT_ABANDONED = "abandoned the process resting at \"%s\" — HEAD is back
24866
24856
  const FMT_RESTORED = "restored the retained history — HEAD is back at %s (\"%s\"), resting at \"%s\". Resume with the loop, or `git reset` to any earlier turn to restart from there.\n";
24867
24857
  /** `no gtd process is underway (resting at "<initial>") — nothing to abandon` — `gtd abandon`'s no-op plain-text line. */
24868
24858
  const abandonNoopText = (initial) => renderFormat(FMT_ABANDON_NOOP, initial);
24859
+ /** A landed self-loop/target-change transition. */
24860
+ const transitionOutcome = (from, to) => printfLine("%s %s → %s\n", [
24861
+ "'->'",
24862
+ shellQuote(from),
24863
+ shellQuote(to)
24864
+ ]);
24869
24865
  /** One already-rendered plain line. */
24870
24866
  const noteOutcome = (text) => printfLine("%s\n", [shellQuote(text)]);
24871
24867
  /**
@@ -25231,8 +25227,7 @@ const checkOverrides = (commandLabel, varOverrides, declaredNames, envNames) =>
25231
25227
  * `gtd start <name>`: start a brand NEW process on a workflow, writing an
25232
25228
  * opening commit that names the workflow's first step and carries a
25233
25229
  * `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.
25230
+ * and a `Gtd-Review-Base:` trailer when the workflow fixes the process's diff base.
25236
25231
  * All validation is a REFUSAL, not an Effect failure.
25237
25232
  */
25238
25233
  const planStart = (current, actor, start) => gen(function* () {
@@ -25270,8 +25265,7 @@ const planStart = (current, actor, start) => gen(function* () {
25270
25265
  to: firstStep,
25271
25266
  workflow: name,
25272
25267
  ...base !== void 0 ? { reviewBase: base } : {},
25273
- vars,
25274
- config: start.config
25268
+ vars
25275
25269
  })
25276
25270
  }
25277
25271
  }, {
@@ -25378,15 +25372,6 @@ const runOf = (history, location, closingHash = void 0) => {
25378
25372
  const fault = historyFormatFault(history, location);
25379
25373
  return fault === void 0 ? succeed$1(buildRun(history, location, closingHash)) : fail$4(new Error(fault));
25380
25374
  };
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
25375
  const approvalEntryOf = (hash, message) => message.approved === void 0 ? [] : [{
25391
25376
  step: formatStepId(message.approved.step),
25392
25377
  name: message.approved.name,
@@ -25414,7 +25399,6 @@ const buildRun = (history, location, closingHash) => {
25414
25399
  marks: parsed.flatMap((m) => m.marks),
25415
25400
  approvals: processCommits.flatMap((c, i) => approvalEntryOf(c.hash, parsed[i])),
25416
25401
  pinnedVars: { ...first?.vars },
25417
- pinnedConfig: pinnedConfigOf(history.slice(Math.max(location.baseIndex, 0))),
25418
25402
  headTurn: headTurnOf(head),
25419
25403
  closingHash,
25420
25404
  episode: {
@@ -25504,18 +25488,6 @@ const findBoundaryBase = (git, def, head) => gen(function* () {
25504
25488
  });
25505
25489
  const historyUpTo = (git, def, head) => flatMap(findBoundaryBase(git, def, head), ({ base, head: resolvedHead }) => git.commitHistory(base, resolvedHead));
25506
25490
  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
25491
  /** The run alone, never replaying — `gtd abandon` must work even when replay would refuse, short of a history it cannot read. */
25520
25492
  const currentRun = gen(function* () {
25521
25493
  const git = yield* GitService;
@@ -25831,8 +25803,7 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
25831
25803
  status: normalizeStatus(e.status),
25832
25804
  path: e.path
25833
25805
  })) : [];
25834
- const configNow = config.fingerprint;
25835
- if (ref === void 0) yield* warnConfigChanges(run, configNow, workspace);
25806
+ 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
25807
  return {
25837
25808
  def,
25838
25809
  workflowName: resolved.name,
@@ -25846,7 +25817,6 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
25846
25817
  run,
25847
25818
  vars,
25848
25819
  env,
25849
- config: configNow,
25850
25820
  changes,
25851
25821
  memory: memory.key,
25852
25822
  memoryResumed: memory.resumed,
@@ -26110,10 +26080,7 @@ const pinRefusal = (pin) => ({
26110
26080
  kind: "refusal",
26111
26081
  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
26082
  });
26113
- const startTrailers = (rest, starts) => starts ? {
26114
- vars: rest.vars,
26115
- config: rest.config
26116
- } : {};
26083
+ const startTrailers = (rest, starts) => starts ? { vars: rest.vars } : {};
26117
26084
  const outcomeMarks = (outcome) => outcome.kind === "rest" || outcome.kind === "ended" ? outcome.marks : [];
26118
26085
  /**
26119
26086
  * What landing the pending turn at `rest` does — a pure decision from a
@@ -26184,16 +26151,20 @@ const summaryFor = (run) => gen(function* () {
26184
26151
  if (summary === void 0 || run.trace.length === 0) return { prompt: void 0 };
26185
26152
  return { prompt: summary(summaryInput(run, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))) };
26186
26153
  });
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));
26154
+ /**
26155
+ * Repository-relative paths of today's config files; one outside the
26156
+ * repository cannot be read at a ref, so it is left out. A file only the old
26157
+ * config read needs no check: whichever file imported it differs too.
26158
+ */
26159
+ const inRepoConfigPaths = (root, files) => {
26160
+ const paths = /* @__PURE__ */ new Set();
26190
26161
  for (const file of files) paths.add(relative(root, resolve(root, file)).split(sep).join("/"));
26191
26162
  return [...paths].filter((p) => p !== "" && !p.startsWith("..") && !isAbsolute(p)).sort();
26192
26163
  };
26193
26164
  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* () {
26165
+ const differingConfigPaths = (git, workspace, root, config, hash) => gen(function* () {
26195
26166
  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);
26167
+ 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
26168
  return differing;
26198
26169
  });
26199
26170
  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 +26221,7 @@ const replayFinishedProcess = (config, run, hash, how) => gen(function* () {
26250
26221
  const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
26251
26222
  const versionWarning = versionWarningFor(recorded);
26252
26223
  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);
26224
+ const differing = yield* differingConfigPaths(git, workspace, host.root, config, hash);
26254
26225
  if (differing.length > 0) return yield* fail$4(withWarning(configRefusal(how.command, hash, differing, how.configReason)));
26255
26226
  const { setup } = yield* setupFor(config, run, workspace, host);
26256
26227
  const outcome = yield* promise(() => replayFor(setup));
@@ -27212,8 +27183,7 @@ const startWorkflow = (workflow, varOverrides, json, out, commandLabel) => gen(f
27212
27183
  workflow: resolved,
27213
27184
  first: started,
27214
27185
  commandLabel,
27215
- vars: varOverrides,
27216
- config: rest.config
27186
+ vars: varOverrides
27217
27187
  });
27218
27188
  if (plan.kind === "refusal") return yield* fail$4(new Error(plan.message));
27219
27189
  yield* writeScript(json, out, listingCommitted(landingScript(ScriptSurface.render(plan.steps)), rest.changes));
@@ -27370,6 +27340,36 @@ const runRestoreCommand = (json, out) => gen(function* () {
27370
27340
  yield* writeScript(json, out, combinedScript(required, ""));
27371
27341
  });
27372
27342
  /**
27343
+ * `gtd repin`: the pin beat's script and landing, on demand. It runs live,
27344
+ * so reading the rest first refuses a live workflow that cannot replay the
27345
+ * process before any pin is written.
27346
+ */
27347
+ const runRepinCommand = (json, out) => gen(function* () {
27348
+ const rest = yield* currentRest;
27349
+ const refusal = rest.pin === "add" ? "the next beat pins the process already — run it (`gtd next --json=content`), then land" : rest.pin === "remove" ? "the flow has ended — its unpin beat is next, so there is nothing to repin" : noProcessUnderway(rest) ? "no gtd process is underway — nothing to repin" : void 0;
27350
+ if (refusal !== void 0) return yield* fail$4(/* @__PURE__ */ new Error(`gtd repin: ${refusal}`));
27351
+ const message = formatCommitMessage({
27352
+ actor: "check",
27353
+ from: PIN_STATES.add,
27354
+ to: rest.step.name,
27355
+ pin: "add"
27356
+ });
27357
+ yield* writeScript(json, out, combinedScript(emitScripts([
27358
+ {
27359
+ kind: "gitWrite",
27360
+ command: repinScript(PIN_PATH)
27361
+ },
27362
+ {
27363
+ kind: "gitWrite",
27364
+ command: commitPath(PIN_PATH, message)
27365
+ },
27366
+ {
27367
+ kind: "outcome",
27368
+ command: transitionOutcome(PIN_STATES.add, rest.step.name)
27369
+ }
27370
+ ]).required, ""));
27371
+ });
27372
+ /**
27373
27373
  * The mode's own validate command for `file`: a declared `validate:` command
27374
27374
  * verbatim; a `"builtin"` validator or no mode at all names the leaf
27375
27375
  * `gtd check <mode>` invocation instead, so there's always something concrete
@@ -27636,6 +27636,7 @@ const dispatchVoidCommand = (command, json, out) => {
27636
27636
  case "start": return runStartCommand(command, json, out);
27637
27637
  case "abandon": return runAbandonCommand(json, out);
27638
27638
  case "restore": return runRestoreCommand(json, out);
27639
+ case "repin": return runRepinCommand(json, out);
27639
27640
  case "next": return runNextCommand(json, out);
27640
27641
  case "validate": return runValidateCommand(json, out);
27641
27642
  case "check":
@@ -27764,6 +27765,7 @@ const SCRIPT_KINDS = /* @__PURE__ */ new Set([
27764
27765
  "start",
27765
27766
  "abandon",
27766
27767
  "restore",
27768
+ "repin",
27767
27769
  "validate",
27768
27770
  "historyFetch"
27769
27771
  ]);
@@ -28031,6 +28033,16 @@ With no <name>, list every workflow and door it can start. An unknown <name>, th
28031
28033
  summary: "Bring back the last abandoned process",
28032
28034
  details: `Bring back the process the last \`gtd abandon\` stopped: the printed script resets HEAD to that process's last commit. Refuses on a dirty working tree, when nothing was abandoned, or when commits made since would be lost.`
28033
28035
  },
28036
+ {
28037
+ token: "repin",
28038
+ kind: "repin",
28039
+ arity: "none",
28040
+ group: "Start and stop",
28041
+ summary: "Pin the process to the live gtd and workflow",
28042
+ details: `Switch the process underway to the live engine and workflow, the way out when the workflow needs a fix mid-process: the printed script runs the pin beat again, writing \`gtd bundle\`'s output to .gtd/process.gtdpin and committing that file alone. Always runs on the gtd on PATH, never on the pin. Refuses when no process is underway, when the next beat is a pin or unpin beat already, or when the live workflow does not replay the process.
28043
+
28044
+ s=$(gtd repin --json=script) && sh -c "$s"`
28045
+ },
28034
28046
  {
28035
28047
  token: "summary",
28036
28048
  kind: "summary",
@@ -28215,6 +28227,7 @@ const flagHeader = (row) => {
28215
28227
  };
28216
28228
  const GLOBAL_OPTIONS = [
28217
28229
  ["-v, --verbose", "Narrate on stderr what gtd resolved"],
28230
+ ["--live", "Run this gtd, not the process's pin; it may misread the process"],
28218
28231
  ["-h, --help", "Print help; gtd <command> --help for one command"],
28219
28232
  ["-V, --version", "Print the version"],
28220
28233
  ["--", "End the options: every word after it is an argument"]
@@ -28226,7 +28239,7 @@ const EXTRA_TOPICS = [[
28226
28239
  ], [
28227
28240
  "version",
28228
28241
  "Print the version",
28229
- "Print gtd's version."
28242
+ "Print gtd's version, and the version of a pinned process's gtd."
28230
28243
  ]];
28231
28244
  const EXAMPLES = ` $ gtd start list what can start
28232
28245
  $ gtd start <name> start a process
@@ -29933,9 +29946,9 @@ const recognizeGitBuilders = (repo, block) => {
29933
29946
  };
29934
29947
  /** What the in-memory `gtd bundle` prints: a pin header, and no engine behind it. */
29935
29948
  const IN_MEMORY_PIN = "gtdpin 1 gtd in-memory\n";
29936
- /** A pin or unpin beat's script. */
29949
+ /** A pin or unpin beat's script, or `gtd repin`'s. */
29937
29950
  const recognizePinScripts = (repo, block) => {
29938
- if (block === pinScript(".gtd/process.gtdpin")) {
29951
+ if (block === pinScript(".gtd/process.gtdpin") || block === repinScript(".gtd/process.gtdpin")) {
29939
29952
  repo.writeFile(PIN_PATH, IN_MEMORY_PIN);
29940
29953
  return { kind: "applied" };
29941
29954
  }