@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,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __createRequire } from 'module';
3
3
  const require = __createRequire(import.meta.url);
4
- import { createRequire, isBuiltin } from "node:module";
4
+ import { createRequire, isBuiltin, registerHooks } from "node:module";
5
5
  import * as OS from "node:os";
6
6
  import { homedir, networkInterfaces, tmpdir } from "node:os";
7
7
  import * as NodeChildProcess from "node:child_process";
@@ -14,7 +14,7 @@ import * as NFS from "node:fs";
14
14
  import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
15
15
  import * as Path from "node:path";
16
16
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
17
- import { gzipSync } from "node:zlib";
17
+ import { gunzipSync, gzipSync } from "node:zlib";
18
18
  import * as NodeUrl from "node:url";
19
19
  import { fileURLToPath, pathToFileURL } from "node:url";
20
20
  import { stripVTControlCharacters } from "node:util";
@@ -12054,6 +12054,57 @@ const restorability = (git, headHash, tipHash) => gen(function* () {
12054
12054
  };
12055
12055
  });
12056
12056
  //#endregion
12057
+ //#region src/platform/PinFile.ts
12058
+ const livePinFile = (cwd, path) => () => {
12059
+ const file = join(cwd, path);
12060
+ if (existsSync(file)) try {
12061
+ return {
12062
+ path: file,
12063
+ bytes: readFileSync(file)
12064
+ };
12065
+ } catch {
12066
+ return {
12067
+ path: file,
12068
+ bytes: /* @__PURE__ */ new Uint8Array()
12069
+ };
12070
+ }
12071
+ try {
12072
+ return {
12073
+ path: file,
12074
+ bytes: execFileSync("git", ["show", `HEAD:${path}`], {
12075
+ cwd,
12076
+ stdio: [
12077
+ "ignore",
12078
+ "pipe",
12079
+ "ignore"
12080
+ ]
12081
+ })
12082
+ };
12083
+ } catch {
12084
+ return;
12085
+ }
12086
+ };
12087
+ /**
12088
+ * Runs a pin's module as this process's main program. Node refuses an unknown
12089
+ * extension, so a load hook serves the unpacked source under the pin's own
12090
+ * URL — `import.meta.url` stays a file in the repository.
12091
+ */
12092
+ const runPinModule = async (path, source) => {
12093
+ const url = pathToFileURL(path).href;
12094
+ registerHooks({
12095
+ resolve: (specifier, context, nextResolve) => specifier === url ? {
12096
+ url,
12097
+ shortCircuit: true
12098
+ } : nextResolve(specifier, context),
12099
+ load: (requested, context, nextLoad) => requested === url ? {
12100
+ format: "module",
12101
+ source,
12102
+ shortCircuit: true
12103
+ } : nextLoad(requested, context)
12104
+ });
12105
+ await import(url);
12106
+ };
12107
+ //#endregion
12057
12108
  //#region src/flags/index.ts
12058
12109
  const FLAGS$1 = [];
12059
12110
  const BOOL = {
@@ -29513,13 +29564,7 @@ const parseJson = (raw) => {
29513
29564
  const TRAILER_RE = /^(Gtd-[A-Za-z-]+):[ \t]*(.*?)[ \t]*$/gm;
29514
29565
  const COST_RE = /^([0-9]+(?:\.[0-9]+)?)(?:[ \t]+(.+))?$/;
29515
29566
  const VAR_RE = /^([^=\s]+)=(.*)$/;
29516
- const CONFIG_RE = /^([0-9a-f]{16}) (.+)$/;
29517
29567
  const CONTROL_CHARACTER$1 = /[\x00-\x1f\x7f-\x9f\u2028\u2029]/;
29518
- const encodePath = (path) => path.trim() !== path || path.startsWith("\"") || CONTROL_CHARACTER$1.test(path) ? JSON.stringify(path) : path;
29519
- const decodePath = (raw) => {
29520
- const path = raw.startsWith("\"") ? parseJson(raw) : raw;
29521
- return typeof path === "string" && path !== "" && !CONTROL_CHARACTER$1.test(path) ? path : void 0;
29522
- };
29523
29568
  const decodeApproval = (raw) => {
29524
29569
  const parsed = parseJson(raw);
29525
29570
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
@@ -29571,11 +29616,6 @@ const TRAILER_READERS = {
29571
29616
  const kv = VAR_RE.exec(value);
29572
29617
  if (kv !== null) into.vars[kv[1]] = kv[2];
29573
29618
  },
29574
- "Gtd-Config": (value, into) => {
29575
- const entry = CONFIG_RE.exec(value);
29576
- const path = entry === null ? void 0 : decodePath(entry[2]);
29577
- if (path !== void 0) into.config[path] = entry[1];
29578
- },
29579
29619
  "Gtd-Review-Base": (value, into) => {
29580
29620
  into.reviewBase ??= value.split(/\s/)[0];
29581
29621
  },
@@ -29620,7 +29660,6 @@ const parseCommitMessage = (message) => {
29620
29660
  judge: [],
29621
29661
  marks: [],
29622
29662
  vars: {},
29623
- config: {},
29624
29663
  cost: [],
29625
29664
  format: void 0
29626
29665
  };
@@ -29642,7 +29681,6 @@ const formatCommitMessage = (spec) => {
29642
29681
  ...trailer("Workflow", spec.workflow),
29643
29682
  ...trailer("Review-Base", spec.reviewBase),
29644
29683
  ...sortedEntries(spec.vars).map(([name, value]) => `Gtd-Var: ${name}=${value}`),
29645
- ...sortedEntries(spec.config).map(([path, hash]) => `Gtd-Config: ${hash} ${encodePath(path)}`),
29646
29684
  ...trailer("Cost", costValue(spec.cost)),
29647
29685
  ...(spec.judge ?? []).map((verdict) => `Gtd-Judge: ${JSON.stringify(verdict)}`),
29648
29686
  ...(spec.marks ?? []).map((m) => `Gtd-Mark: ${m.name}=${m.value}`),
@@ -30564,7 +30602,8 @@ const load = gen(function* () {
30564
30602
  },
30565
30603
  ...compiled.ui !== void 0 ? { ui: compiled.ui } : {},
30566
30604
  warnings: diagnostics.filter((d) => d.severity === "warning"),
30567
- fingerprint: pinned?.fingerprint ?? configFingerprint(loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk)
30605
+ fingerprint: pinned?.fingerprint ?? configFingerprint(loaded.file.dependencyFiles, host.root, module.read ?? readFromDisk),
30606
+ drift: pinned === void 0 ? [] : changedConfigFiles(pinned.fingerprint, configFingerprint(Object.keys(pinned.fingerprint).map((file) => resolve(host.root, file)), host.root, readFromDisk))
30568
30607
  };
30569
30608
  });
30570
30609
  const readFromDisk = (path) => {
@@ -31218,8 +31257,6 @@ const knownModes = (def) => Object.keys(def.modes);
31218
31257
  //#endregion
31219
31258
  //#region src/workflow/pin.ts
31220
31259
  init_dist$1();
31221
- /** Bumped when a launcher could no longer run an older pin the same way. */
31222
- const PIN_FORMAT = 1;
31223
31260
  /**
31224
31261
  * The engine a pin carries, as ES module source. Live, the `gtd.core.mjs` the
31225
31262
  * running gtd ships beside its own bundle.
@@ -31245,8 +31282,26 @@ const pin = (module, root, version) => gen(function* () {
31245
31282
  try: () => pinnedWorkflowSource(module, root, version),
31246
31283
  catch: (e) => new GtdError(`gtd bundle: ${module.filepath}: ${e instanceof Error ? e.message : String(e)}`)
31247
31284
  })) + core.replace(/^#!.*\n/, "");
31248
- return Buffer.concat([Buffer.from(`gtdpin ${PIN_FORMAT} gtd ${version}\n`), gzipSync(source, { level: 9 })]);
31285
+ return Buffer.concat([Buffer.from(`gtdpin 1 gtd ${version}\n`), gzipSync(source, { level: 9 })]);
31249
31286
  });
31287
+ /** A pin's header line, read without unpacking it; undefined when the bytes are no pin. */
31288
+ const pinHeader = (bytes) => {
31289
+ const end = bytes.indexOf(10);
31290
+ const header = end < 0 ? null : /^gtdpin (\d+) gtd (\S+)$/.exec(Buffer.from(bytes.subarray(0, end)).toString());
31291
+ return header === null ? void 0 : {
31292
+ format: Number(header[1]),
31293
+ version: header[2]
31294
+ };
31295
+ };
31296
+ /** A pin's header and module source. */
31297
+ const readPin = (bytes) => {
31298
+ const header = pinHeader(bytes);
31299
+ if (header === void 0) throw new Error("not a gtd pin");
31300
+ return {
31301
+ ...header,
31302
+ source: gunzipSync(bytes.subarray(bytes.indexOf(10) + 1)).toString("utf8")
31303
+ };
31304
+ };
31250
31305
  //#endregion
31251
31306
  //#region src/emit/GitScript.ts
31252
31307
  const shellQuote$1 = (value) => `'${value.replace(/'/g, "'\\''")}'`;
@@ -31280,6 +31335,12 @@ const pinScript = (path) => {
31280
31335
  const slash = path.lastIndexOf("/");
31281
31336
  return `${slash === -1 ? "" : `mkdir -p ${shellQuote$1(path.slice(0, slash))}\n`}gtd bundle >${p} || { rm -f -- ${p}; exit 1; }`;
31282
31337
  };
31338
+ /** `gtd repin`'s pin write: a failed `gtd bundle` leaves the committed pin in place. */
31339
+ const repinScript = (path) => {
31340
+ const p = shellQuote$1(path);
31341
+ const next = shellQuote$1(`${path}.new`);
31342
+ return `gtd bundle >${next} || { rm -f -- ${next}; exit 1; }\nmv -f -- ${next} ${p}`;
31343
+ };
31283
31344
  /** An unpin beat's script, and `gtd abandon`'s removal of the pin. */
31284
31345
  const unpinScript = (path) => `rm -f -- ${shellQuote$1(path)}`;
31285
31346
  /**
@@ -31508,6 +31569,12 @@ const FMT_ABANDONED = "abandoned the process resting at \"%s\" — HEAD is back
31508
31569
  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";
31509
31570
  /** `no gtd process is underway (resting at "<initial>") — nothing to abandon` — `gtd abandon`'s no-op plain-text line. */
31510
31571
  const abandonNoopText = (initial) => renderFormat(FMT_ABANDON_NOOP, initial);
31572
+ /** A landed self-loop/target-change transition. */
31573
+ const transitionOutcome = (from, to) => printfLine("%s %s → %s\n", [
31574
+ "'->'",
31575
+ shellQuote$1(from),
31576
+ shellQuote$1(to)
31577
+ ]);
31511
31578
  /** One already-rendered plain line. */
31512
31579
  const noteOutcome = (text) => printfLine("%s\n", [shellQuote$1(text)]);
31513
31580
  /**
@@ -31876,8 +31943,7 @@ const checkOverrides = (commandLabel, varOverrides, declaredNames, envNames) =>
31876
31943
  * `gtd start <name>`: start a brand NEW process on a workflow, writing an
31877
31944
  * opening commit that names the workflow's first step and carries a
31878
31945
  * `Gtd-Workflow:` trailer, every process setting as a `Gtd-Var:` trailer, plus
31879
- * a `Gtd-Review-Base:` trailer when the workflow fixes the process's diff base,
31880
- * and a `Gtd-Config:` trailer per config file.
31946
+ * and a `Gtd-Review-Base:` trailer when the workflow fixes the process's diff base.
31881
31947
  * All validation is a REFUSAL, not an Effect failure.
31882
31948
  */
31883
31949
  const planStart = (current, actor, start) => gen(function* () {
@@ -31915,8 +31981,7 @@ const planStart = (current, actor, start) => gen(function* () {
31915
31981
  to: firstStep,
31916
31982
  workflow: name,
31917
31983
  ...base !== void 0 ? { reviewBase: base } : {},
31918
- vars,
31919
- config: start.config
31984
+ vars
31920
31985
  })
31921
31986
  }
31922
31987
  }, {
@@ -32025,15 +32090,6 @@ const runOf = (history, location, closingHash = void 0) => {
32025
32090
  const fault = historyFormatFault(history, location);
32026
32091
  return fault === void 0 ? succeed$1(buildRun(history, location, closingHash)) : fail$8(new Error(fault));
32027
32092
  };
32028
- const pinnedConfigOf = (history) => {
32029
- for (let i = history.length - 1; i >= 0; i--) {
32030
- const files = parseCommitMessage(history[i].message).config;
32031
- if (Object.keys(files).length > 0) return {
32032
- commit: history[i].hash,
32033
- files
32034
- };
32035
- }
32036
- };
32037
32093
  const approvalEntryOf = (hash, message) => message.approved === void 0 ? [] : [{
32038
32094
  step: formatStepId(message.approved.step),
32039
32095
  name: message.approved.name,
@@ -32061,7 +32117,6 @@ const buildRun = (history, location, closingHash) => {
32061
32117
  marks: parsed.flatMap((m) => m.marks),
32062
32118
  approvals: processCommits.flatMap((c, i) => approvalEntryOf(c.hash, parsed[i])),
32063
32119
  pinnedVars: { ...first?.vars },
32064
- pinnedConfig: pinnedConfigOf(history.slice(Math.max(location.baseIndex, 0))),
32065
32120
  headTurn: headTurnOf(head),
32066
32121
  closingHash,
32067
32122
  episode: {
@@ -32151,18 +32206,6 @@ const findBoundaryBase = (git, def, head) => gen(function* () {
32151
32206
  });
32152
32207
  const historyUpTo = (git, def, head) => flatMap(findBoundaryBase(git, def, head), ({ base, head: resolvedHead }) => git.commitHistory(base, resolvedHead));
32153
32208
  const computeProcessRun = (git, def, head) => flatMap(historyUpTo(git, def, head), (history) => runOf(history, locateEpisode(def, history)));
32154
- const changedSinceStart = (pinned, current, workspace) => {
32155
- if (pinned === void 0) return [];
32156
- return changedConfigFiles(pinned.files, current).filter((path) => {
32157
- const committed = workspace.readCommittedSync(path, pinned.commit);
32158
- return current[path] === void 0 || committed === void 0 || contentHash(committed) !== current[path];
32159
- });
32160
- };
32161
- const warnConfigChanges = (run, current, workspace) => gen(function* () {
32162
- const changed = changedSinceStart(run.pinnedConfig, current, workspace);
32163
- if (changed.length === 0) return;
32164
- 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`);
32165
- });
32166
32209
  /** The run alone, never replaying — `gtd abandon` must work even when replay would refuse, short of a history it cannot read. */
32167
32210
  const currentRun = gen(function* () {
32168
32211
  const git = yield* GitService;
@@ -32478,8 +32521,7 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
32478
32521
  status: normalizeStatus(e.status),
32479
32522
  path: e.path
32480
32523
  })) : [];
32481
- const configNow = config.fingerprint;
32482
- if (ref === void 0) yield* warnConfigChanges(run, configNow, workspace);
32524
+ 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`);
32483
32525
  return {
32484
32526
  def,
32485
32527
  workflowName: resolved.name,
@@ -32493,7 +32535,6 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
32493
32535
  run,
32494
32536
  vars,
32495
32537
  env,
32496
- config: configNow,
32497
32538
  changes,
32498
32539
  memory: memory.key,
32499
32540
  memoryResumed: memory.resumed,
@@ -32757,10 +32798,7 @@ const pinRefusal = (pin) => ({
32757
32798
  kind: "refusal",
32758
32799
  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`
32759
32800
  });
32760
- const startTrailers = (rest, starts) => starts ? {
32761
- vars: rest.vars,
32762
- config: rest.config
32763
- } : {};
32801
+ const startTrailers = (rest, starts) => starts ? { vars: rest.vars } : {};
32764
32802
  const outcomeMarks = (outcome) => outcome.kind === "rest" || outcome.kind === "ended" ? outcome.marks : [];
32765
32803
  /**
32766
32804
  * What landing the pending turn at `rest` does — a pure decision from a
@@ -32831,16 +32869,20 @@ const summaryFor = (run) => gen(function* () {
32831
32869
  if (summary === void 0 || run.trace.length === 0) return { prompt: void 0 };
32832
32870
  return { prompt: summary(summaryInput(run, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))) };
32833
32871
  });
32834
- /** 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. */
32835
- const inRepoConfigPaths = (root, files, recorded) => {
32836
- const paths = new Set(Object.keys(recorded));
32872
+ /**
32873
+ * Repository-relative paths of today's config files; one outside the
32874
+ * repository cannot be read at a ref, so it is left out. A file only the old
32875
+ * config read needs no check: whichever file imported it differs too.
32876
+ */
32877
+ const inRepoConfigPaths = (root, files) => {
32878
+ const paths = /* @__PURE__ */ new Set();
32837
32879
  for (const file of files) paths.add(relative(root, resolve(root, file)).split(sep).join("/"));
32838
32880
  return [...paths].filter((p) => p !== "" && !p.startsWith("..") && !isAbsolute(p)).sort();
32839
32881
  };
32840
32882
  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}`);
32841
- const differingConfigPaths = (git, workspace, root, config, run, hash) => gen(function* () {
32883
+ const differingConfigPaths = (git, workspace, root, config, hash) => gen(function* () {
32842
32884
  const differing = [];
32843
- 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);
32885
+ 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);
32844
32886
  return differing;
32845
32887
  });
32846
32888
  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");
@@ -32897,7 +32939,7 @@ const replayFinishedProcess = (config, run, hash, how) => gen(function* () {
32897
32939
  const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
32898
32940
  const versionWarning = versionWarningFor(recorded);
32899
32941
  const withWarning = (err) => versionWarning === void 0 ? err : /* @__PURE__ */ new Error(`${err.message}\n${versionWarning}`);
32900
- const differing = yield* differingConfigPaths(git, workspace, host.root, config, run, hash);
32942
+ const differing = yield* differingConfigPaths(git, workspace, host.root, config, hash);
32901
32943
  if (differing.length > 0) return yield* fail$8(withWarning(configRefusal(how.command, hash, differing, how.configReason)));
32902
32944
  const { setup } = yield* setupFor(config, run, workspace, host);
32903
32945
  const outcome = yield* promise(() => replayFor(setup));
@@ -33868,8 +33910,7 @@ const startWorkflow = (workflow, varOverrides, json, out, commandLabel) => gen(f
33868
33910
  workflow: resolved,
33869
33911
  first: started,
33870
33912
  commandLabel,
33871
- vars: varOverrides,
33872
- config: rest.config
33913
+ vars: varOverrides
33873
33914
  });
33874
33915
  if (plan.kind === "refusal") return yield* fail$8(new Error(plan.message));
33875
33916
  yield* writeScript(json, out, listingCommitted(landingScript(ScriptSurface.render(plan.steps)), rest.changes));
@@ -34026,6 +34067,36 @@ const runRestoreCommand = (json, out) => gen(function* () {
34026
34067
  yield* writeScript(json, out, combinedScript(required, ""));
34027
34068
  });
34028
34069
  /**
34070
+ * `gtd repin`: the pin beat's script and landing, on demand. It runs live,
34071
+ * so reading the rest first refuses a live workflow that cannot replay the
34072
+ * process before any pin is written.
34073
+ */
34074
+ const runRepinCommand = (json, out) => gen(function* () {
34075
+ const rest = yield* currentRest;
34076
+ 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;
34077
+ if (refusal !== void 0) return yield* fail$8(/* @__PURE__ */ new Error(`gtd repin: ${refusal}`));
34078
+ const message = formatCommitMessage({
34079
+ actor: "check",
34080
+ from: PIN_STATES.add,
34081
+ to: rest.step.name,
34082
+ pin: "add"
34083
+ });
34084
+ yield* writeScript(json, out, combinedScript(emitScripts([
34085
+ {
34086
+ kind: "gitWrite",
34087
+ command: repinScript(PIN_PATH)
34088
+ },
34089
+ {
34090
+ kind: "gitWrite",
34091
+ command: commitPath(PIN_PATH, message)
34092
+ },
34093
+ {
34094
+ kind: "outcome",
34095
+ command: transitionOutcome(PIN_STATES.add, rest.step.name)
34096
+ }
34097
+ ]).required, ""));
34098
+ });
34099
+ /**
34029
34100
  * The mode's own validate command for `file`: a declared `validate:` command
34030
34101
  * verbatim; a `"builtin"` validator or no mode at all names the leaf
34031
34102
  * `gtd check <mode>` invocation instead, so there's always something concrete
@@ -34292,6 +34363,7 @@ const dispatchVoidCommand = (command, json, out) => {
34292
34363
  case "start": return runStartCommand(command, json, out);
34293
34364
  case "abandon": return runAbandonCommand(json, out);
34294
34365
  case "restore": return runRestoreCommand(json, out);
34366
+ case "repin": return runRepinCommand(json, out);
34295
34367
  case "next": return runNextCommand(json, out);
34296
34368
  case "validate": return runValidateCommand(json, out);
34297
34369
  case "check":
@@ -34421,6 +34493,7 @@ const SCRIPT_KINDS = /* @__PURE__ */ new Set([
34421
34493
  "start",
34422
34494
  "abandon",
34423
34495
  "restore",
34496
+ "repin",
34424
34497
  "validate",
34425
34498
  "historyFetch"
34426
34499
  ]);
@@ -34688,6 +34761,16 @@ With no <name>, list every workflow and door it can start. An unknown <name>, th
34688
34761
  summary: "Bring back the last abandoned process",
34689
34762
  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.`
34690
34763
  },
34764
+ {
34765
+ token: "repin",
34766
+ kind: "repin",
34767
+ arity: "none",
34768
+ group: "Start and stop",
34769
+ summary: "Pin the process to the live gtd and workflow",
34770
+ 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.
34771
+
34772
+ s=$(gtd repin --json=script) && sh -c "$s"`
34773
+ },
34691
34774
  {
34692
34775
  token: "summary",
34693
34776
  kind: "summary",
@@ -34872,6 +34955,7 @@ const flagHeader = (row) => {
34872
34955
  };
34873
34956
  const GLOBAL_OPTIONS = [
34874
34957
  ["-v, --verbose", "Narrate on stderr what gtd resolved"],
34958
+ ["--live", "Run this gtd, not the process's pin; it may misread the process"],
34875
34959
  ["-h, --help", "Print help; gtd <command> --help for one command"],
34876
34960
  ["-V, --version", "Print the version"],
34877
34961
  ["--", "End the options: every word after it is an argument"]
@@ -34883,7 +34967,7 @@ const EXTRA_TOPICS = [[
34883
34967
  ], [
34884
34968
  "version",
34885
34969
  "Print the version",
34886
- "Print gtd's version."
34970
+ "Print gtd's version, and the version of a pinned process's gtd."
34887
34971
  ]];
34888
34972
  const EXAMPLES = ` $ gtd start list what can start
34889
34973
  $ gtd start <name> start a process
@@ -35381,6 +35465,109 @@ const runCli = (argv, io) => {
35381
35465
  }), provide(io.layers(plan.verbose)), sandbox, catch_((cause) => hasInterruptsOnly(cause) ? failCause$1(cause) : report(io, plan.json.kind !== "off")(cause)));
35382
35466
  };
35383
35467
  //#endregion
35468
+ //#region src/cli/Launch.ts
35469
+ /** What the core a pin carries lacks, or what must read the live state: these never hand over. */
35470
+ const LIVE_KINDS = /* @__PURE__ */ new Set([
35471
+ "ui",
35472
+ "lsp",
35473
+ "guide",
35474
+ "bundle",
35475
+ "repin",
35476
+ "historyList",
35477
+ "historyShow",
35478
+ "historyMetrics",
35479
+ "historyJudges",
35480
+ "historyFetch"
35481
+ ]);
35482
+ const LIVE_FLAG = "--live";
35483
+ const optionWords = (argv) => {
35484
+ const end = argv.indexOf("--", 2);
35485
+ return argv.slice(2, end < 0 ? void 0 : end);
35486
+ };
35487
+ const withoutLiveFlag = (argv) => {
35488
+ const end = argv.indexOf("--", 2);
35489
+ return argv.filter((word, i) => word !== LIVE_FLAG || i < 2 || end >= 0 && i > end);
35490
+ };
35491
+ /**
35492
+ * Whether this gtd runs the command or hands it to the process's pin. A pin
35493
+ * this gtd cannot run is refused, never guessed at; a usage error the live
35494
+ * parser finds is still the pin's to judge, since its engine may read
35495
+ * another CLI.
35496
+ */
35497
+ const launchFor = (argv, pinFile) => {
35498
+ if (globalThis[PIN_KEY] !== void 0) return {
35499
+ kind: "live",
35500
+ argv
35501
+ };
35502
+ const live = optionWords(argv).includes(LIVE_FLAG);
35503
+ const rest = withoutLiveFlag(argv);
35504
+ if (live) return {
35505
+ kind: "live",
35506
+ argv: rest
35507
+ };
35508
+ const plan = parseArgv(rest);
35509
+ const version = plan.kind === "output" && plan.stdout === `${GTD_VERSION}\n`;
35510
+ if (plan.kind === "output" && !version) return {
35511
+ kind: "live",
35512
+ argv: rest
35513
+ };
35514
+ if (plan.kind === "command" && LIVE_KINDS.has(plan.command.kind)) return {
35515
+ kind: "live",
35516
+ argv: rest
35517
+ };
35518
+ const found = pinFile();
35519
+ if (found === void 0) return {
35520
+ kind: "live",
35521
+ argv: rest
35522
+ };
35523
+ const header = pinHeader(found.bytes);
35524
+ if (version) return {
35525
+ kind: "output",
35526
+ stdout: header === void 0 ? plan.stdout : `${GTD_VERSION} (this process is pinned to gtd ${header.version})\n`
35527
+ };
35528
+ if (header === void 0 || header.format !== 1) {
35529
+ const what = header === void 0 ? "is no pin this gtd can read" : `is pin format ${header.format}, made by gtd ${header.version}`;
35530
+ return {
35531
+ kind: "refuse",
35532
+ message: `gtd: ${found.path} ${what}; gtd ${GTD_VERSION} runs pin format 1 — upgrade gtd (\`gtd --live\` runs this gtd instead, and may misread the process)`
35533
+ };
35534
+ }
35535
+ return {
35536
+ kind: "pin",
35537
+ argv: rest,
35538
+ path: found.path,
35539
+ version: header.version,
35540
+ source: () => readPin(found.bytes).source
35541
+ };
35542
+ };
35543
+ const isJsonFlag = (word) => word === "--json" || word.startsWith("--json=");
35544
+ const refusalText = (argv, message) => (optionWords(argv).some(isJsonFlag) ? `${JSON.stringify({
35545
+ state: "error",
35546
+ prompt: message
35547
+ })}\n` : "") + `${message}\n`;
35548
+ const runPin = async (launch, launcher) => {
35549
+ const words = optionWords(launch.argv);
35550
+ if (words.includes("--verbose") || words.includes("-v")) launcher.stderr(`launcher: running the pin ${launch.path} (gtd ${launch.version})\n`);
35551
+ try {
35552
+ await launcher.pin(launch);
35553
+ } catch (error) {
35554
+ const reason = error instanceof Error ? error.message : String(error);
35555
+ launcher.stderr(`gtd: could not run the pin ${launch.path}: ${reason}\n`);
35556
+ launcher.exit(1);
35557
+ }
35558
+ };
35559
+ const runLauncher = async (argv, pinFile, launcher) => {
35560
+ const launch = launchFor(argv, pinFile);
35561
+ switch (launch.kind) {
35562
+ case "live": return launcher.live(launch.argv);
35563
+ case "output": return launcher.stdout(launch.stdout);
35564
+ case "refuse":
35565
+ launcher.stderr(refusalText(argv, launch.message));
35566
+ return launcher.exit(1);
35567
+ case "pin": return runPin(launch, launcher);
35568
+ }
35569
+ };
35570
+ //#endregion
35384
35571
  //#region node_modules/@trpc/server/dist/codes-DagpWZLc.mjs
35385
35572
  /**
35386
35573
  * Ensures there are no duplicate keys when building a procedure.
@@ -51407,25 +51594,40 @@ const liveToolCommands = effect(ToolCommands, gen(function* () {
51407
51594
  //#endregion
51408
51595
  //#region src/main.ts
51409
51596
  init_dist$1();
51410
- let deathSignal;
51411
- process.once("SIGINT", () => {
51412
- deathSignal = "SIGINT";
51413
- });
51414
- process.once("SIGTERM", () => {
51415
- deathSignal ??= "SIGTERM";
51416
- });
51417
- process.once("SIGHUP", () => {
51418
- deathSignal = "SIGHUP";
51419
- process.kill(process.pid, "SIGTERM");
51597
+ const runLive = (argv) => {
51598
+ let deathSignal;
51599
+ process.once("SIGINT", () => {
51600
+ deathSignal = "SIGINT";
51601
+ });
51602
+ process.once("SIGTERM", () => {
51603
+ deathSignal ??= "SIGTERM";
51604
+ });
51605
+ process.once("SIGHUP", () => {
51606
+ deathSignal = "SIGHUP";
51607
+ process.kill(process.pid, "SIGTERM");
51608
+ });
51609
+ runMain(runCli(argv, nodeCliIo(liveToolCommands)), { teardown: (exit, onExit) => {
51610
+ if (deathSignal === void 0) {
51611
+ defaultTeardown(exit, onExit);
51612
+ return;
51613
+ }
51614
+ const signal = deathSignal;
51615
+ process.removeAllListeners(signal);
51616
+ process.kill(process.pid, signal);
51617
+ } });
51618
+ };
51619
+ await runLauncher(process.argv, livePinFile(process.cwd(), PIN_PATH), {
51620
+ stdout: (chunk) => {
51621
+ process.stdout.write(chunk);
51622
+ },
51623
+ stderr: (chunk) => {
51624
+ process.stderr.write(chunk);
51625
+ },
51626
+ exit: (code) => {
51627
+ process.exitCode = code;
51628
+ },
51629
+ live: runLive,
51630
+ pin: (launch) => runPinModule(launch.path, launch.source())
51420
51631
  });
51421
- runMain(runCli(process.argv, nodeCliIo(liveToolCommands)), { teardown: (exit, onExit) => {
51422
- if (deathSignal === void 0) {
51423
- defaultTeardown(exit, onExit);
51424
- return;
51425
- }
51426
- const signal = deathSignal;
51427
- process.removeAllListeners(signal);
51428
- process.kill(process.pid, signal);
51429
- } });
51430
51632
  //#endregion
51431
51633
  export {};