@pmelab/gtd 24.2.0 → 24.3.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 +26 -2
- package/dist/chunks/{Schema-Dh-mkwkq.mjs → Schema-B5yK7QrT.mjs} +47 -1
- package/dist/chunks/{reserved-ejn-LVYO.mjs → reserved-DJwU5C4z.mjs} +1 -1
- package/dist/gtd.bundle.mjs +337 -55
- package/dist/testkit.mjs +477 -65
- package/dist/workflows/feature.mjs +1 -1
- package/dist/workflows/fix.mjs +1 -1
- package/dist/workflows/review.mjs +1 -1
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +57 -0
- package/src/cli/Cli.ts +89 -9
- package/src/cli/History.test.ts +225 -0
- package/src/cli/History.ts +143 -0
- package/src/cli/Output.ts +68 -0
- package/src/cli/Program.test.ts +7 -1
- package/src/cli/Program.ts +23 -64
- package/src/edge/Edge.test.ts +256 -1
- package/src/edge/Edge.ts +217 -39
- package/src/edge/index.ts +1 -0
- package/src/emit/GitScript.test.ts +27 -0
- package/src/emit/GitScript.ts +18 -2
- package/src/emit/index.ts +1 -0
- package/src/platform/Git.ts +48 -10
- package/src/platform/RetainedHistory.test.ts +14 -0
- package/src/platform/RetainedHistory.ts +5 -0
- package/src/platform/index.ts +7 -1
- package/src/replay/Trailers.test.ts +32 -0
- package/src/replay/Trailers.ts +17 -0
- package/src/step/LandStep.ts +3 -1
- package/src/step/RepoSnapshot.ts +7 -1
- package/src/step/planStart.test.ts +6 -3
- package/src/step/planStep.test.ts +29 -1
- package/src/step/planStep.ts +8 -0
- package/src/testkit/EmittedScriptRecognizer.ts +1 -1
- package/src/testkit/FakeGitOperations.ts +6 -2
- package/src/testkit/GitTiers.ts +133 -2
- package/src/testkit/InMemRepo.ts +26 -6
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -6826,7 +6826,7 @@ var init_label = __esmMin((() => {
|
|
|
6826
6826
|
}));
|
|
6827
6827
|
//#endregion
|
|
6828
6828
|
//#region node_modules/effect/dist/esm/internal/core-effect.js
|
|
6829
|
-
var annotateLogs$1, asSome, try_$1, catchTag$1, diffFiberRefs, bind, bindTo, match$4, fiberRefs, ignore$1, logWithLevel, logWarning$1, logError$1, mapErrorCause, negate$1, patchFiberRefs, promise$1, provideService$1, provideServiceEffect, sandbox$1, sleep$1, succeedNone, summarized, tapErrorCause$1, tryPromise$1, tryMap$1, updateFiberRefs, when$1, serviceOption$1;
|
|
6829
|
+
var annotateLogs$1, asSome, try_$1, catchTag$1, diffFiberRefs, bind, bindTo, match$4, fiberRefs, ignore$1, logWithLevel, logWarning$1, logError$1, mapErrorCause, negate$1, orElseSucceed$1, patchFiberRefs, promise$1, provideService$1, provideServiceEffect, sandbox$1, sleep$1, succeedNone, summarized, tapErrorCause$1, tryPromise$1, tryMap$1, updateFiberRefs, when$1, serviceOption$1;
|
|
6830
6830
|
var init_core_effect = __esmMin((() => {
|
|
6831
6831
|
init_Clock();
|
|
6832
6832
|
init_Context();
|
|
@@ -6910,6 +6910,7 @@ var init_core_effect = __esmMin((() => {
|
|
|
6910
6910
|
onSuccess: succeed$8
|
|
6911
6911
|
}));
|
|
6912
6912
|
negate$1 = (self) => map$8(self, (b) => !b);
|
|
6913
|
+
orElseSucceed$1 = /*#__PURE__*/ dual(2, (self, evaluate) => orElse$2(self, () => sync$2(evaluate)));
|
|
6913
6914
|
patchFiberRefs = (patch) => updateFiberRefs((fiberId, fiberRefs) => pipe(patch, patch$2(fiberId, fiberRefs)));
|
|
6914
6915
|
promise$1 = (evaluate) => evaluate.length >= 1 ? async_((resolve, signal) => {
|
|
6915
6916
|
try {
|
|
@@ -11426,7 +11427,7 @@ var init_MutableQueue = __esmMin((() => {
|
|
|
11426
11427
|
}));
|
|
11427
11428
|
//#endregion
|
|
11428
11429
|
//#region node_modules/effect/dist/esm/Effect.js
|
|
11429
|
-
var isEffect, all$1, forEach$2, async, withFiberRuntime, fail$7, failCause$2, die, dieMessage, gen, promise, succeed$3, suspend$5, sync$1, _void, catchAll, catchAllCause, catchTag, ignore, sandbox, retry, try_, tryMap, tryPromise, interrupt, interruptible, uninterruptible, uninterruptibleMask, as, asVoid, map$4, mapBoth$1, mapError$1, negate, acquireRelease, acquireUseRelease$2, addFinalizer, ensuring$2, onError, scope$1, scopeWith, scopedWith$1, scoped$3, fiberIdWith, forkDaemon, forkIn, forkScoped, timeout, provide$1, provideService, serviceOption, either, exit$1, intoDeferred, when, flatMap$4, andThen, flatten$5, race, raceWith, tap, tapErrorCause, repeat, match$2, matchCause, matchCauseEffect, matchEffect, logWarning, logError, annotateLogs, orDie, orElse$1, runtime, unsafeMakeSemaphore, makeSemaphore, unsafeMakeLatch, runFork$1, runSync, zip$1, zipLeft, zipRight$1, zipWith, fnUntraced;
|
|
11430
|
+
var isEffect, all$1, forEach$2, async, withFiberRuntime, fail$7, failCause$2, die, dieMessage, gen, promise, succeed$3, suspend$5, sync$1, _void, catchAll, catchAllCause, catchTag, ignore, sandbox, retry, try_, tryMap, tryPromise, interrupt, interruptible, uninterruptible, uninterruptibleMask, as, asVoid, map$4, mapBoth$1, mapError$1, negate, acquireRelease, acquireUseRelease$2, addFinalizer, ensuring$2, onError, scope$1, scopeWith, scopedWith$1, scoped$3, fiberIdWith, forkDaemon, forkIn, forkScoped, timeout, provide$1, provideService, serviceOption, either, exit$1, intoDeferred, when, flatMap$4, andThen, flatten$5, race, raceWith, tap, tapErrorCause, repeat, match$2, matchCause, matchCauseEffect, matchEffect, logWarning, logError, annotateLogs, orDie, orElse$1, orElseSucceed, runtime, unsafeMakeSemaphore, makeSemaphore, unsafeMakeLatch, runFork$1, runSync, zip$1, zipLeft, zipRight$1, zipWith, fnUntraced;
|
|
11430
11431
|
var init_Effect = __esmMin((() => {
|
|
11431
11432
|
init_core_effect();
|
|
11432
11433
|
init_core();
|
|
@@ -11507,6 +11508,7 @@ var init_Effect = __esmMin((() => {
|
|
|
11507
11508
|
annotateLogs = annotateLogs$1;
|
|
11508
11509
|
orDie = orDie$1;
|
|
11509
11510
|
orElse$1 = orElse$2;
|
|
11511
|
+
orElseSucceed = orElseSucceed$1;
|
|
11510
11512
|
runtime = runtime$1;
|
|
11511
11513
|
unsafeMakeSemaphore = unsafeMakeSemaphore$1;
|
|
11512
11514
|
makeSemaphore = makeSemaphore$1;
|
|
@@ -20243,8 +20245,17 @@ const resolvedRefOrCorrupted = (ref, hash) => /^[0-9a-f]{40}$/.test(hash) ? succ
|
|
|
20243
20245
|
const makeGitImpl = (executor, root) => {
|
|
20244
20246
|
const exec = (...args) => run$4(root, ...args).pipe(provide$1(succeed$2(CommandExecutor, executor)));
|
|
20245
20247
|
return {
|
|
20246
|
-
lastCommitSubject: (ref = "HEAD") => exec("git", "log", "-1", "--pretty=%s", ref).pipe(map$4((s) => s.trim())),
|
|
20247
|
-
lastCommitMessage: () => exec("git", "log", "-1", "--pretty=%B").pipe(map$4((s) => s.trim())),
|
|
20248
|
+
lastCommitSubject: (ref = "HEAD") => exec("git", "log", "-1", "--pretty=%s", "--end-of-options", ref).pipe(map$4((s) => s.trim())),
|
|
20249
|
+
lastCommitMessage: (ref = "HEAD") => exec("git", "log", "-1", "--pretty=%B", "--end-of-options", ref).pipe(map$4((s) => s.trim())),
|
|
20250
|
+
currentBranch: () => exec("git", "symbolic-ref", "--quiet", "--short", "HEAD").pipe(map$4((s) => s.trim()), map$4((name) => some(name)), catchAll(() => succeed$3(none$4()))),
|
|
20251
|
+
listRefs: (prefix) => exec("git", "for-each-ref", "--sort=-committerdate", "--format=%(refname)%09%(objectname)%09%(committerdate:iso-strict)", prefix).pipe(map$4((out) => out.split("\n").filter((line) => line.length > 0).map((line) => {
|
|
20252
|
+
const [ref = "", hash = "", date = ""] = line.split(" ");
|
|
20253
|
+
return {
|
|
20254
|
+
ref,
|
|
20255
|
+
hash,
|
|
20256
|
+
date
|
|
20257
|
+
};
|
|
20258
|
+
}))),
|
|
20248
20259
|
hasCommits: () => exec("git", "rev-parse", "--verify", "HEAD").pipe(map$4(() => true), catchAll(() => succeed$3(false))),
|
|
20249
20260
|
readFileAtRef: (ref, path) => exec("git", "show", `${ref}:${path}`),
|
|
20250
20261
|
changedPaths: (base) => gen(function* () {
|
|
@@ -20264,7 +20275,7 @@ const makeGitImpl = (executor, root) => {
|
|
|
20264
20275
|
}
|
|
20265
20276
|
return all;
|
|
20266
20277
|
}),
|
|
20267
|
-
resolveRef: (ref) => exec("git", "rev-parse", "--verify", ref).pipe(map$4((s) => s.trim()), flatMap$4((hash) => resolvedRefOrCorrupted(ref, hash))),
|
|
20278
|
+
resolveRef: (ref) => exec("git", "rev-parse", "--verify", "--end-of-options", ref).pipe(map$4((s) => s.trim()), flatMap$4((hash) => resolvedRefOrCorrupted(ref, hash))),
|
|
20268
20279
|
readRefOption: (ref) => exec("git", "rev-parse", "--verify", "--quiet", ref).pipe(map$4((s) => some(s.trim())), catchAll(() => succeed$3(none$4()))),
|
|
20269
20280
|
isAncestor: (a, b) => run$4(root, "git", "merge-base", "--is-ancestor", a, b).pipe(provide$1(succeed$2(CommandExecutor, executor)), map$4(() => true), catchAll(() => succeed$3(false))),
|
|
20270
20281
|
mergeBase: (a, b) => exec("git", "merge-base", a, b).pipe(map$4((s) => some(s.trim())), catchAll(() => succeed$3(none$4()))),
|
|
@@ -20920,6 +20931,9 @@ const loopLogPath = gen(function* () {
|
|
|
20920
20931
|
init_esm();
|
|
20921
20932
|
/** Per-worktree (`refs/worktree/gtd/*`) so linked worktrees sharing one `.git` don't clobber each other's retained history. */
|
|
20922
20933
|
const HISTORY_REF = "refs/worktree/gtd/history";
|
|
20934
|
+
/** One ref per finished process, kept past the branch's deletion; distinct from the per-worktree `HISTORY_REF`. */
|
|
20935
|
+
const PROCESS_HISTORY_PREFIX = "refs/gtd/history/";
|
|
20936
|
+
const processHistoryRef = (hash) => `${PROCESS_HISTORY_PREFIX}${hash}`;
|
|
20923
20937
|
const readRetainedHistory = (git) => git.readRefOption(HISTORY_REF);
|
|
20924
20938
|
/**
|
|
20925
20939
|
* Whether resetting to `tipHash` is safe: HEAD must be an ancestor of
|
|
@@ -37547,6 +37561,12 @@ const TRAILER_READERS = {
|
|
|
37547
37561
|
"Gtd-Review-Base": (value, into) => {
|
|
37548
37562
|
into.reviewBase ??= value.split(/\s/)[0];
|
|
37549
37563
|
},
|
|
37564
|
+
"Gtd-Branch": (value, into) => {
|
|
37565
|
+
if (value !== "") into.branch ??= value;
|
|
37566
|
+
},
|
|
37567
|
+
"Gtd-Version": (value, into) => {
|
|
37568
|
+
if (value !== "") into.version ??= value;
|
|
37569
|
+
},
|
|
37550
37570
|
"Gtd-Format": (value, into) => {
|
|
37551
37571
|
if (FORMAT_RE.test(value)) into.format ??= Number(value);
|
|
37552
37572
|
},
|
|
@@ -37565,6 +37585,8 @@ const parseCommitMessage = (message) => {
|
|
|
37565
37585
|
step: void 0,
|
|
37566
37586
|
workflow: void 0,
|
|
37567
37587
|
reviewBase: void 0,
|
|
37588
|
+
branch: void 0,
|
|
37589
|
+
version: void 0,
|
|
37568
37590
|
judge: [],
|
|
37569
37591
|
vars: {},
|
|
37570
37592
|
config: {},
|
|
@@ -37590,6 +37612,8 @@ const formatCommitMessage = (spec) => {
|
|
|
37590
37612
|
for (const [path, hash] of sortedEntries(spec.config)) lines.push(`Gtd-Config: ${hash} ${encodePath(path)}`);
|
|
37591
37613
|
if (spec.cost !== void 0) lines.push(`Gtd-Cost: ${spec.cost.cost}${spec.cost.model !== void 0 ? ` ${spec.cost.model}` : ""}`);
|
|
37592
37614
|
for (const verdict of spec.judge ?? []) lines.push(`Gtd-Judge: ${JSON.stringify(verdict)}`);
|
|
37615
|
+
if (spec.branch !== void 0) lines.push(`Gtd-Branch: ${spec.branch}`);
|
|
37616
|
+
if (spec.version !== void 0) lines.push(`Gtd-Version: ${spec.version}`);
|
|
37593
37617
|
lines.push(`Gtd-Format: 1`);
|
|
37594
37618
|
return `${formatSubject(spec.actor, spec.to, spec.from)}\n\n${lines.join("\n")}`;
|
|
37595
37619
|
};
|
|
@@ -38902,6 +38926,14 @@ const planStep = (snapshot, opts = {}) => {
|
|
|
38902
38926
|
message
|
|
38903
38927
|
}
|
|
38904
38928
|
},
|
|
38929
|
+
...landing.keepHistory !== void 0 ? [{
|
|
38930
|
+
kind: "gitWrite",
|
|
38931
|
+
write: {
|
|
38932
|
+
kind: "updateRef",
|
|
38933
|
+
ref: landing.keepHistory,
|
|
38934
|
+
target: "HEAD"
|
|
38935
|
+
}
|
|
38936
|
+
}] : [],
|
|
38905
38937
|
outcome
|
|
38906
38938
|
]
|
|
38907
38939
|
};
|
|
@@ -39214,25 +39246,38 @@ const currentRun = gen(function* () {
|
|
|
39214
39246
|
const config = yield* (yield* ConfigService).load;
|
|
39215
39247
|
return yield* computeProcessRun(git, config.workflow);
|
|
39216
39248
|
});
|
|
39217
|
-
|
|
39218
|
-
|
|
39219
|
-
const git = yield* GitService;
|
|
39220
|
-
const def = (yield* (yield* ConfigService).load).workflow;
|
|
39221
|
-
const headEntry = (yield* git.subjectHistory(1, 0))[0];
|
|
39222
|
-
if (headEntry === void 0) {
|
|
39249
|
+
const runAtTip = (git, def, tip) => gen(function* () {
|
|
39250
|
+
if (tip === void 0) {
|
|
39223
39251
|
const history = yield* historyUpTo(git, def, void 0);
|
|
39224
39252
|
return yield* runOf(history, locateEpisode(def, history));
|
|
39225
39253
|
}
|
|
39226
|
-
if (!(parseCommitMessage(
|
|
39227
|
-
const history = yield* historyUpTo(git, def,
|
|
39254
|
+
if (!(parseCommitMessage(tip.subject).parsed?.to === def.initial)) {
|
|
39255
|
+
const history = yield* historyUpTo(git, def, tip.hash);
|
|
39228
39256
|
return yield* runOf(history, locateEpisode(def, history));
|
|
39229
39257
|
}
|
|
39230
|
-
const parentRef = `${
|
|
39258
|
+
const parentRef = `${tip.hash}~1`;
|
|
39231
39259
|
const before = yield* historyUpTo(git, def, parentRef);
|
|
39232
39260
|
const location = locateEpisode(def, before);
|
|
39233
|
-
let closing = yield* git.commitHistory(parentRef,
|
|
39234
|
-
if (closing.length === 0) closing = yield* git.commitHistory(void 0,
|
|
39235
|
-
return yield* runOf([...before, ...closing], { ...location },
|
|
39261
|
+
let closing = yield* git.commitHistory(parentRef, tip.hash);
|
|
39262
|
+
if (closing.length === 0) closing = yield* git.commitHistory(void 0, tip.hash);
|
|
39263
|
+
return yield* runOf([...before, ...closing], { ...location }, tip.hash);
|
|
39264
|
+
});
|
|
39265
|
+
/** The process HEAD closes or sits inside — `gtd summary`'s run. */
|
|
39266
|
+
const summaryRun = gen(function* () {
|
|
39267
|
+
const git = yield* GitService;
|
|
39268
|
+
const def = (yield* (yield* ConfigService).load).workflow;
|
|
39269
|
+
return yield* runAtTip(git, def, (yield* git.subjectHistory(1, 0))[0]);
|
|
39270
|
+
});
|
|
39271
|
+
/** The process whose finishing commit is `tip` — reads from `tip`'s own ancestry, so it works when no branch reaches it. */
|
|
39272
|
+
const closedRunAt = (tip) => gen(function* () {
|
|
39273
|
+
const git = yield* GitService;
|
|
39274
|
+
const def = (yield* (yield* ConfigService).load).workflow;
|
|
39275
|
+
const hash = yield* git.resolveRef(tip);
|
|
39276
|
+
const subject = yield* git.lastCommitSubject(hash);
|
|
39277
|
+
return yield* runAtTip(git, def, {
|
|
39278
|
+
hash,
|
|
39279
|
+
subject
|
|
39280
|
+
});
|
|
39236
39281
|
});
|
|
39237
39282
|
const commitTree = (workspace, hash) => {
|
|
39238
39283
|
let entries;
|
|
@@ -39452,13 +39497,7 @@ const costByModel = (entries) => {
|
|
|
39452
39497
|
cost
|
|
39453
39498
|
})).sort((a, b) => b.cost - a.cost || a.model.localeCompare(b.model));
|
|
39454
39499
|
};
|
|
39455
|
-
|
|
39456
|
-
const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
39457
|
-
const git = yield* GitService;
|
|
39458
|
-
const config = yield* (yield* ConfigService).load;
|
|
39459
|
-
const workspace = yield* Workspace;
|
|
39460
|
-
const host = yield* Host;
|
|
39461
|
-
const run = yield* computeProcessRun(git, config.workflow, ref);
|
|
39500
|
+
const setupFor = (config, run, workspace, host, startOverrides = {}) => gen(function* () {
|
|
39462
39501
|
const resolved = yield* workflowOf(config, run);
|
|
39463
39502
|
const def = resolved.def;
|
|
39464
39503
|
const { vars, env } = settingsFor(resolved, run.pinnedVars, host.env, startOverrides);
|
|
@@ -39482,19 +39521,34 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
39482
39521
|
tree: commitTree(workspace, c.hash)
|
|
39483
39522
|
}));
|
|
39484
39523
|
let pendingMemo;
|
|
39485
|
-
|
|
39524
|
+
return {
|
|
39525
|
+
resolved,
|
|
39486
39526
|
def,
|
|
39487
|
-
run,
|
|
39488
39527
|
vars,
|
|
39489
39528
|
env,
|
|
39490
|
-
|
|
39491
|
-
|
|
39492
|
-
|
|
39493
|
-
|
|
39494
|
-
|
|
39495
|
-
|
|
39496
|
-
|
|
39529
|
+
setup: {
|
|
39530
|
+
def,
|
|
39531
|
+
run,
|
|
39532
|
+
vars,
|
|
39533
|
+
env,
|
|
39534
|
+
budget,
|
|
39535
|
+
skills,
|
|
39536
|
+
access,
|
|
39537
|
+
workspace,
|
|
39538
|
+
base,
|
|
39539
|
+
episode,
|
|
39540
|
+
pendingWorktree: () => pendingMemo ??= workspace.worktreeSync()
|
|
39541
|
+
}
|
|
39497
39542
|
};
|
|
39543
|
+
});
|
|
39544
|
+
/** Resolve the rest at `ref`, or at HEAD when `ref` is `undefined`. */
|
|
39545
|
+
const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
39546
|
+
const git = yield* GitService;
|
|
39547
|
+
const config = yield* (yield* ConfigService).load;
|
|
39548
|
+
const workspace = yield* Workspace;
|
|
39549
|
+
const host = yield* Host;
|
|
39550
|
+
const run = yield* computeProcessRun(git, config.workflow, ref);
|
|
39551
|
+
const { resolved, def, vars, env, setup } = yield* setupFor(config, run, workspace, host, startOverrides);
|
|
39498
39552
|
const outcome = yield* promise(() => replayFor(setup));
|
|
39499
39553
|
const error = replayError(outcome);
|
|
39500
39554
|
if (error !== void 0 || outcome.kind !== "rest") return yield* fail$7(error ?? /* @__PURE__ */ new Error("gtd: replay found no rest"));
|
|
@@ -39636,14 +39690,21 @@ const initialStepRefusal = (workflow, step, initial) => workflow !== void 0 && s
|
|
|
39636
39690
|
kind: "refusal",
|
|
39637
39691
|
message: `gtd: workflow "${workflow}" reaches step "${step}", the default workflow's first step — that is where a finished process waits, so a started process must never land there; rename the step`
|
|
39638
39692
|
} : void 0;
|
|
39639
|
-
const
|
|
39640
|
-
|
|
39693
|
+
const closingTrailers = (branch) => ({
|
|
39694
|
+
version: GTD_VERSION,
|
|
39695
|
+
...isSome(branch) ? { branch: branch.value } : {}
|
|
39696
|
+
});
|
|
39697
|
+
const isSettledNoop = (rest, to) => rest.changes.length === 0 && rest.stepDef.kind === "script" && to === rest.state;
|
|
39698
|
+
const landingTo = (rest, to, branch) => {
|
|
39699
|
+
if (isSettledNoop(rest, to)) return {
|
|
39641
39700
|
kind: "noop",
|
|
39642
39701
|
settled: true
|
|
39643
39702
|
};
|
|
39644
|
-
const
|
|
39703
|
+
const underway = !noProcessUnderway(rest);
|
|
39704
|
+
const starts = !underway && to !== rest.def.initial;
|
|
39645
39705
|
const refused = starts ? multilineRefusal(rest.vars) : void 0;
|
|
39646
39706
|
if (refused !== void 0) return refused;
|
|
39707
|
+
const finishes = underway && to === rest.def.initial;
|
|
39647
39708
|
return {
|
|
39648
39709
|
kind: "commit",
|
|
39649
39710
|
to,
|
|
@@ -39655,8 +39716,10 @@ const landingTo = (rest, to) => {
|
|
|
39655
39716
|
...starts ? {
|
|
39656
39717
|
vars: rest.vars,
|
|
39657
39718
|
config: rest.config
|
|
39658
|
-
} : {}
|
|
39659
|
-
|
|
39719
|
+
} : {},
|
|
39720
|
+
...finishes ? closingTrailers(branch) : {}
|
|
39721
|
+
},
|
|
39722
|
+
...finishes ? { keepHistory: processHistoryRef(rest.run.startHash) } : {}
|
|
39660
39723
|
};
|
|
39661
39724
|
};
|
|
39662
39725
|
/**
|
|
@@ -39664,7 +39727,7 @@ const landingTo = (rest, to) => {
|
|
|
39664
39727
|
* replay with the working tree as the pending turn. The target step is
|
|
39665
39728
|
* computed by replaying, never matched.
|
|
39666
39729
|
*/
|
|
39667
|
-
const decideLanding = (rest, verdicts) => gen(function* () {
|
|
39730
|
+
const decideLanding = (rest, verdicts, branch) => gen(function* () {
|
|
39668
39731
|
const early = cleanLanding(rest);
|
|
39669
39732
|
if (early !== void 0) return early;
|
|
39670
39733
|
if (rest.stepDef.kind === "prompt" && rest.stepDef.access?.write != null) {
|
|
@@ -39680,13 +39743,14 @@ const decideLanding = (rest, verdicts) => gen(function* () {
|
|
|
39680
39743
|
}));
|
|
39681
39744
|
const to = yield* landingTarget(outcome, rest);
|
|
39682
39745
|
if (typeof to !== "string") return to;
|
|
39683
|
-
return landingTo(rest, to);
|
|
39746
|
+
return landingTo(rest, to, branch);
|
|
39684
39747
|
});
|
|
39685
39748
|
/** Where landing the pending turn would leave the process, or `undefined` when it would land nothing. */
|
|
39686
|
-
const previewLanding = (rest) => decideLanding(rest, void 0).pipe(map$4((landing) => landing.kind === "commit" ? landing.to : void 0), catchAll(() => succeed$3(void 0)));
|
|
39749
|
+
const previewLanding = (rest) => decideLanding(rest, void 0, none$4()).pipe(map$4((landing) => landing.kind === "commit" ? landing.to : void 0), catchAll(() => succeed$3(void 0)));
|
|
39687
39750
|
/** Every fact the pure landing planner needs, read once: the rest, its steering file, and the landing decision. */
|
|
39688
39751
|
const snapshotFromRest = (rest, verdicts) => gen(function* () {
|
|
39689
|
-
const
|
|
39752
|
+
const branch = yield* (yield* GitService).currentBranch();
|
|
39753
|
+
const landing = yield* decideLanding(rest, verdicts, branch);
|
|
39690
39754
|
return {
|
|
39691
39755
|
stepDef: rest.stepDef,
|
|
39692
39756
|
state: rest.state,
|
|
@@ -39717,6 +39781,66 @@ const summaryFor = (run) => gen(function* () {
|
|
|
39717
39781
|
...settingsFor(resolved, run.pinnedVars, host.env)
|
|
39718
39782
|
});
|
|
39719
39783
|
});
|
|
39784
|
+
/** 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. */
|
|
39785
|
+
const inRepoConfigPaths = (root, files, recorded) => {
|
|
39786
|
+
const paths = new Set(Object.keys(recorded));
|
|
39787
|
+
for (const file of files) paths.add(relative(root, resolve(root, file)).split(sep).join("/"));
|
|
39788
|
+
return [...paths].filter((p) => p !== "" && !p.startsWith("..") && !isAbsolute(p)).sort();
|
|
39789
|
+
};
|
|
39790
|
+
const configRefusal = (tip, paths) => /* @__PURE__ */ new Error(`gtd history: refused — config differs at ${tip.slice(0, 7)}: ${paths.join(", ")}. A history ref replays under today's config, so it is unreadable after the config changes`);
|
|
39791
|
+
const differingConfigPaths = (git, workspace, root, config, run, hash) => gen(function* () {
|
|
39792
|
+
const differing = [];
|
|
39793
|
+
for (const path of inRepoConfigPaths(root, config.workflowFiles, run.pinnedConfig?.files ?? {})) if ((yield* orElseSucceed(git.readFileAtRef(hash, path), () => void 0)) !== workspace.readSync(path)) differing.push(path);
|
|
39794
|
+
return differing;
|
|
39795
|
+
});
|
|
39796
|
+
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");
|
|
39797
|
+
const historySteps = (run) => {
|
|
39798
|
+
const messages = new Map(run.episode.commits.map((c) => [c.hash, c.message]));
|
|
39799
|
+
return run.trace.map((entry) => {
|
|
39800
|
+
const message = parseCommitMessage(messages.get(entry.hash) ?? "");
|
|
39801
|
+
return {
|
|
39802
|
+
hash: entry.hash,
|
|
39803
|
+
subject: (messages.get(entry.hash) ?? "").split("\n")[0] ?? "",
|
|
39804
|
+
actor: entry.actor,
|
|
39805
|
+
from: message.parsed?.from,
|
|
39806
|
+
to: entry.state,
|
|
39807
|
+
verdicts: message.judge
|
|
39808
|
+
};
|
|
39809
|
+
});
|
|
39810
|
+
};
|
|
39811
|
+
const versionWarningFor = (recorded) => recorded === GTD_VERSION ? void 0 : `gtd: warning: this history was recorded by ${recorded === void 0 ? "an unknown gtd version" : `gtd ${recorded}`} and is replayed by gtd ${GTD_VERSION} — a workflow imported from @pmelab/gtd/workflows may have changed since`;
|
|
39812
|
+
/**
|
|
39813
|
+
* Replays the closed run at `tip` under today's config. The workflow is not
|
|
39814
|
+
* fingerprinted for a version change: one imported from `@pmelab/gtd/workflows`
|
|
39815
|
+
* moves with every upgrade, and the version warning is the only signal.
|
|
39816
|
+
*/
|
|
39817
|
+
const historyTrail = (tip) => gen(function* () {
|
|
39818
|
+
const git = yield* GitService;
|
|
39819
|
+
const config = yield* (yield* ConfigService).load;
|
|
39820
|
+
const workspace = yield* Workspace;
|
|
39821
|
+
const host = yield* Host;
|
|
39822
|
+
const hash = yield* git.resolveRef(tip);
|
|
39823
|
+
const run = yield* closedRunAt(hash);
|
|
39824
|
+
const differing = yield* differingConfigPaths(git, workspace, host.root, config, run, hash);
|
|
39825
|
+
if (differing.length > 0) return yield* fail$7(configRefusal(hash, differing));
|
|
39826
|
+
const { setup } = yield* setupFor(config, run, workspace, host);
|
|
39827
|
+
yield* workspace.episodeTrees(run.episode.base, run.episode.commits.map((c) => c.hash));
|
|
39828
|
+
const outcome = yield* promise(() => replayFor(setup));
|
|
39829
|
+
if (outcome.kind !== "ended") return yield* fail$7(notClosedError(hash, outcome));
|
|
39830
|
+
const steps = historySteps(run);
|
|
39831
|
+
const recorded = parseCommitMessage(yield* git.lastCommitMessage(hash)).version;
|
|
39832
|
+
const versionWarning = versionWarningFor(recorded);
|
|
39833
|
+
return {
|
|
39834
|
+
tip: hash,
|
|
39835
|
+
steps,
|
|
39836
|
+
ended: outcome.via,
|
|
39837
|
+
version: {
|
|
39838
|
+
recorded,
|
|
39839
|
+
running: GTD_VERSION
|
|
39840
|
+
},
|
|
39841
|
+
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
39842
|
+
};
|
|
39843
|
+
});
|
|
39720
39844
|
//#endregion
|
|
39721
39845
|
//#region src/emit/GitScript.ts
|
|
39722
39846
|
const shellQuote$1 = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
@@ -39738,13 +39862,24 @@ const commitAllowEmpty = (message) => {
|
|
|
39738
39862
|
`fi`
|
|
39739
39863
|
].join("\n");
|
|
39740
39864
|
};
|
|
39741
|
-
/** `git add -A
|
|
39742
|
-
const commitAll = (message) => `git add -A
|
|
39865
|
+
/** `git add -A || exit 1` (an `&&` left side would not abort under `set -e`) then `commitAllowEmpty`. */
|
|
39866
|
+
const commitAll = (message) => `git add -A || exit 1\n${commitAllowEmpty(message)}`;
|
|
39743
39867
|
const mixedResetTo = (ref) => `git reset --mixed ${shellQuote$1(ref)}`;
|
|
39744
39868
|
const hardResetTo = (ref) => `git reset --hard ${shellQuote$1(ref)}`;
|
|
39745
39869
|
const updateRef = (ref, hash) => `git update-ref ${shellQuote$1(ref)} ${shellQuote$1(hash)}`;
|
|
39746
39870
|
/** `git update-ref -d <ref>` — idempotent: deleting a missing ref is already a no-op in real git. */
|
|
39747
39871
|
const deleteRef = (ref) => `git update-ref -d ${shellQuote$1(ref)}`;
|
|
39872
|
+
/**
|
|
39873
|
+
* No `+` on the refspec: a local ref the remote's tip does not descend from
|
|
39874
|
+
* fails the fetch instead of being overwritten (a fast-forward still applies).
|
|
39875
|
+
*/
|
|
39876
|
+
const fetchHistoryRefs = (remote) => [
|
|
39877
|
+
`set -eu`,
|
|
39878
|
+
`before=$(git for-each-ref --format='x' 'refs/gtd/history/' | wc -l)`,
|
|
39879
|
+
`git fetch ${shellQuote$1(remote)} 'refs/gtd/history/*:refs/gtd/history/*'`,
|
|
39880
|
+
`after=$(git for-each-ref --format='x' 'refs/gtd/history/' | wc -l)`,
|
|
39881
|
+
`printf '%s history refs arrived\\n' "$((after - before))"`
|
|
39882
|
+
].join("\n");
|
|
39748
39883
|
/** Marks an emitted block as print-only — mirrors `OutcomeScript.ts`'s `OUTCOME_MARKER` verbatim, so `src/testkit/EmittedScriptRecognizer.ts` recognizes either path's output identically. */
|
|
39749
39884
|
const OUTCOME_MARKER$1 = "# gtd: outcome (print-only)";
|
|
39750
39885
|
const printfLine$1 = (fmt, args) => `${OUTCOME_MARKER$1}\nprintf ${shellQuote$1(fmt.replace(/\n/g, "\\n"))} ${args.join(" ")}`;
|
|
@@ -39762,6 +39897,7 @@ const renderOutcome = (outcome) => {
|
|
|
39762
39897
|
const renderGitWrite = (write) => {
|
|
39763
39898
|
switch (write.kind) {
|
|
39764
39899
|
case "commitAll": return commitAll(write.message);
|
|
39900
|
+
case "updateRef": return updateRef(write.ref, write.target);
|
|
39765
39901
|
}
|
|
39766
39902
|
};
|
|
39767
39903
|
/**
|
|
@@ -40133,10 +40269,9 @@ init_esm();
|
|
|
40133
40269
|
*/
|
|
40134
40270
|
var ToolCommands = class extends Tag("ToolCommands")() {};
|
|
40135
40271
|
//#endregion
|
|
40136
|
-
//#region src/cli/
|
|
40272
|
+
//#region src/cli/Output.ts
|
|
40137
40273
|
init_esm();
|
|
40138
40274
|
init_wire();
|
|
40139
|
-
init_steering();
|
|
40140
40275
|
/**
|
|
40141
40276
|
* A command-level failure that should exit like a CLI usage error (2), not a
|
|
40142
40277
|
* generic runtime failure (1): an unknown `--json=<path>` selector, or a
|
|
@@ -40146,12 +40281,97 @@ init_steering();
|
|
|
40146
40281
|
* about the resolved rest's own state.
|
|
40147
40282
|
*/
|
|
40148
40283
|
var SelectorUsageError = class extends Error {};
|
|
40149
|
-
/** The `--json=<path>` select branch, shared by
|
|
40284
|
+
/** The `--json=<path>` select branch, shared by the emitting commands so none drift on the unknown-selector message. `absent` writes nothing and lets the caller's success path continue. */
|
|
40150
40285
|
const writeSelection = (out, fields, path) => gen(function* () {
|
|
40151
40286
|
const selection = selectPath(fields, path);
|
|
40152
40287
|
if (selection.kind === "value") out.write(`${selection.text}\n`);
|
|
40153
40288
|
else if (selection.kind === "unknown") return yield* fail$7(new SelectorUsageError(`gtd: unknown --json selector "${selection.path}" — see \`gtd --help\``));
|
|
40154
40289
|
});
|
|
40290
|
+
//#endregion
|
|
40291
|
+
//#region src/cli/History.ts
|
|
40292
|
+
init_esm();
|
|
40293
|
+
/** Newest tip first. */
|
|
40294
|
+
const historyEntries = gen(function* () {
|
|
40295
|
+
const git = yield* GitService;
|
|
40296
|
+
const refs = (yield* git.listRefs(PROCESS_HISTORY_PREFIX)).filter(({ ref }) => ENTRY_HASH.test(ref.slice(17)));
|
|
40297
|
+
return yield* forEach$2(refs, ({ ref, hash, date }) => gen(function* () {
|
|
40298
|
+
const entry = ref.slice(17);
|
|
40299
|
+
return {
|
|
40300
|
+
ref,
|
|
40301
|
+
entry,
|
|
40302
|
+
branch: parseCommitMessage(yield* git.lastCommitMessage(hash)).branch ?? null,
|
|
40303
|
+
tip: hash,
|
|
40304
|
+
date,
|
|
40305
|
+
subject: yield* git.lastCommitSubject(entry)
|
|
40306
|
+
};
|
|
40307
|
+
}));
|
|
40308
|
+
});
|
|
40309
|
+
const ENTRY_HASH = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
|
|
40310
|
+
const HEX = /^[0-9a-f]+$/i;
|
|
40311
|
+
/**
|
|
40312
|
+
* Lookup order: full ref; then entry hash or hex prefix (unique → it, several
|
|
40313
|
+
* → fail listing them); then branch name (newest tip first, as `entries` is
|
|
40314
|
+
* ordered). A hex-looking name that matches no entry still resolves as a
|
|
40315
|
+
* branch. No match → fail.
|
|
40316
|
+
*/
|
|
40317
|
+
const resolveHistoryRef = (arg, entries) => {
|
|
40318
|
+
const byRef = entries.find((e) => e.ref === arg);
|
|
40319
|
+
if (byRef !== void 0) return succeed$3(byRef);
|
|
40320
|
+
if (HEX.test(arg)) {
|
|
40321
|
+
const lower = arg.toLowerCase();
|
|
40322
|
+
const matches = entries.filter((e) => e.entry.toLowerCase().startsWith(lower));
|
|
40323
|
+
if (matches.length === 1) return succeed$3(matches[0]);
|
|
40324
|
+
if (matches.length > 1) return fail$7(/* @__PURE__ */ new Error(`gtd history: "${arg}" is ambiguous — it matches:\n${matches.map((e) => ` ${e.entry} ${e.branch ?? "-"}`).join("\n")}`));
|
|
40325
|
+
}
|
|
40326
|
+
const byBranch = entries.find((e) => e.branch === arg);
|
|
40327
|
+
if (byBranch !== void 0) return succeed$3(byBranch);
|
|
40328
|
+
return fail$7(/* @__PURE__ */ new Error(`gtd history: no history ref matches "${arg}"`));
|
|
40329
|
+
};
|
|
40330
|
+
const seven = (hash) => hash.slice(0, 7);
|
|
40331
|
+
/** Writes `document` per `json` (bare → whole, `=path` → one value); returns false for `off`. */
|
|
40332
|
+
const writeJson = (json, out, document) => gen(function* () {
|
|
40333
|
+
if (json.kind === "off") return false;
|
|
40334
|
+
if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
|
|
40335
|
+
else yield* writeSelection(out, document, json.path);
|
|
40336
|
+
return true;
|
|
40337
|
+
});
|
|
40338
|
+
const runHistoryListCommand = (json, out) => gen(function* () {
|
|
40339
|
+
const entries = yield* historyEntries;
|
|
40340
|
+
if (yield* writeJson(json, out, entries)) return;
|
|
40341
|
+
if (entries.length === 0) {
|
|
40342
|
+
out.write("no history refs; run gtd history fetch | sh to pull them\n");
|
|
40343
|
+
return;
|
|
40344
|
+
}
|
|
40345
|
+
out.write(entries.map((e) => `${seven(e.entry)} ${e.branch ?? "-"} ${seven(e.tip)} ${e.date} ${e.subject}\n`).join(""));
|
|
40346
|
+
});
|
|
40347
|
+
const runHistoryShowCommand = (ref, json, out) => gen(function* () {
|
|
40348
|
+
const entry = yield* resolveHistoryRef(ref, yield* historyEntries);
|
|
40349
|
+
const trail = yield* historyTrail(entry.tip);
|
|
40350
|
+
const document = {
|
|
40351
|
+
ref: entry.ref,
|
|
40352
|
+
entry: entry.entry,
|
|
40353
|
+
branch: entry.branch,
|
|
40354
|
+
...trail
|
|
40355
|
+
};
|
|
40356
|
+
if (yield* writeJson(json, out, document)) return;
|
|
40357
|
+
if (trail.versionWarning !== void 0) yield* (yield* Narrator).warn(trail.versionWarning);
|
|
40358
|
+
const lines = [`${entry.ref} (branch ${entry.branch ?? "-"}, tip ${seven(entry.tip)})`];
|
|
40359
|
+
for (const step of trail.steps) {
|
|
40360
|
+
lines.push(`${seven(step.hash)} ${step.subject}`);
|
|
40361
|
+
for (const v of step.verdicts) lines.push(` judge ${v.id} = ${v.answer} (p=${v.p})`);
|
|
40362
|
+
}
|
|
40363
|
+
lines.push(`ended (${trail.ended})`);
|
|
40364
|
+
out.write(`${lines.join("\n")}\n`);
|
|
40365
|
+
});
|
|
40366
|
+
/** Prints the script and never runs git: gtd's promise that it only decides and prints. */
|
|
40367
|
+
const runHistoryFetchCommand = (remote, out) => sync$1(() => {
|
|
40368
|
+
out.write(`${combinedScript(fetchHistoryRefs(remote), "")}\n`);
|
|
40369
|
+
});
|
|
40370
|
+
//#endregion
|
|
40371
|
+
//#region src/cli/Program.ts
|
|
40372
|
+
init_esm();
|
|
40373
|
+
init_wire();
|
|
40374
|
+
init_steering();
|
|
40155
40375
|
const selfGtd = () => {
|
|
40156
40376
|
const script = process.argv[1];
|
|
40157
40377
|
return script === void 0 ? ["gtd"] : [process.execPath, script];
|
|
@@ -40773,6 +40993,9 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
40773
40993
|
case "judge": return runJudgeCommand(json, out);
|
|
40774
40994
|
case "judgeAnswer": return runJudgeAnswerCommand(json, out);
|
|
40775
40995
|
case "judgeRun": return runJudgeRunCommand(command, out);
|
|
40996
|
+
case "historyList": return runHistoryListCommand(json, out);
|
|
40997
|
+
case "historyShow": return runHistoryShowCommand(command.ref, json, out);
|
|
40998
|
+
case "historyFetch": return runHistoryFetchCommand(command.remote, out);
|
|
40776
40999
|
}
|
|
40777
41000
|
};
|
|
40778
41001
|
/**
|
|
@@ -40813,13 +41036,13 @@ const FLAGS = [
|
|
|
40813
41036
|
name: "--json",
|
|
40814
41037
|
arity: "optional",
|
|
40815
41038
|
repeatable: false,
|
|
40816
|
-
scope: (kind) => kind === "next" || kind === "land" || kind === "judge" || kind === "judgeAnswer" || kind === "doors",
|
|
41039
|
+
scope: (kind) => kind === "next" || kind === "land" || kind === "judge" || kind === "judgeAnswer" || kind === "doors" || kind === "historyList" || kind === "historyShow",
|
|
40817
41040
|
decode: ([raw]) => right(raw ?? ""),
|
|
40818
|
-
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
41041
|
+
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd history list`/`gtd history show` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
40819
41042
|
valueHint: "<path>",
|
|
40820
41043
|
help: [
|
|
40821
|
-
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors
|
|
40822
|
-
"structured JSON. Bare --json prints the whole document;",
|
|
41044
|
+
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
|
|
41045
|
+
"list/gtd history show only) output structured JSON. Bare --json prints the whole document;",
|
|
40823
41046
|
"--json=<path> (a dotted key path into that document, e.g.",
|
|
40824
41047
|
"kind, content, session.id) prints just that value: a",
|
|
40825
41048
|
"scalar raw and unquoted, a boolean as true/false, a list",
|
|
@@ -41327,6 +41550,44 @@ const COMMAND_ROWS = [
|
|
|
41327
41550
|
"repository. jev and llm exit 1 when they cannot answer",
|
|
41328
41551
|
"every question, with nothing on stdout"
|
|
41329
41552
|
]
|
|
41553
|
+
},
|
|
41554
|
+
{
|
|
41555
|
+
token: "history list",
|
|
41556
|
+
kind: "historyList",
|
|
41557
|
+
arity: "none",
|
|
41558
|
+
details: [
|
|
41559
|
+
"List the finished processes kept behind refs/gtd/history/*,",
|
|
41560
|
+
"newest first: entry, branch, tip, date and subject per line.",
|
|
41561
|
+
"--json prints one array of { ref, entry, branch, tip, date,",
|
|
41562
|
+
"subject }. Writes nothing and never replays; with no refs it",
|
|
41563
|
+
"hints at `gtd history fetch | sh` and still exits 0"
|
|
41564
|
+
]
|
|
41565
|
+
},
|
|
41566
|
+
{
|
|
41567
|
+
token: "history show",
|
|
41568
|
+
kind: "historyShow",
|
|
41569
|
+
arity: { name: "ref" },
|
|
41570
|
+
details: [
|
|
41571
|
+
"Replay one kept process under today's config and print its",
|
|
41572
|
+
"steps and judge verdicts. <ref> is a full ref, an entry hash",
|
|
41573
|
+
"(or unique prefix) or a branch name. Refuses (exit 1) when a",
|
|
41574
|
+
"config file differs from its content at the ref's tip. A",
|
|
41575
|
+
"version mismatch only warns on stderr. --json prints the",
|
|
41576
|
+
"trail as one document; writes nothing"
|
|
41577
|
+
]
|
|
41578
|
+
},
|
|
41579
|
+
{
|
|
41580
|
+
token: "history fetch",
|
|
41581
|
+
kind: "historyFetch",
|
|
41582
|
+
arity: {
|
|
41583
|
+
name: "remote",
|
|
41584
|
+
optional: true
|
|
41585
|
+
},
|
|
41586
|
+
details: [
|
|
41587
|
+
"Print a script that fetches every refs/gtd/history/* from",
|
|
41588
|
+
"<remote> (default origin) and reports how many arrived — run",
|
|
41589
|
+
"it with `gtd history fetch | sh`. Never runs git itself"
|
|
41590
|
+
]
|
|
41330
41591
|
}
|
|
41331
41592
|
];
|
|
41332
41593
|
const commandByToken = (token) => COMMAND_ROWS.find((r) => r.token === token);
|
|
@@ -41346,7 +41607,7 @@ const flagHeader = (row) => {
|
|
|
41346
41607
|
};
|
|
41347
41608
|
const renderHelp = () => {
|
|
41348
41609
|
const commandBlocks = COMMAND_ROWS.map((row) => {
|
|
41349
|
-
const header = row.arity === "none" ? row.token : "names" in row.arity ? `${row.token} <${row.arity.names[0]}> <${row.arity.names[1]}>` : "variadic" in row.arity ? `${row.token} <${row.arity.name}> [${row.arity.variadic}...]` : `${row.token} <${row.arity.name}>`;
|
|
41610
|
+
const header = row.arity === "none" ? row.token : "names" in row.arity ? `${row.token} <${row.arity.names[0]}> <${row.arity.names[1]}>` : "variadic" in row.arity ? `${row.token} <${row.arity.name}> [${row.arity.variadic}...]` : "optional" in row.arity ? `${row.token} [${row.arity.name}]` : `${row.token} <${row.arity.name}>`;
|
|
41350
41611
|
return renderBlock(header, row.details);
|
|
41351
41612
|
});
|
|
41352
41613
|
return `Usage: gtd [command] [options]\n\nCommands:\n${[
|
|
@@ -41457,6 +41718,7 @@ const conflictViolation = (present) => {
|
|
|
41457
41718
|
const arityError = (cmd, rest, arity) => {
|
|
41458
41719
|
if (arity === "none") return rest.length > 0 ? `gtd ${cmd}: too many arguments — expected none, got: ${rest.join(", ")}` : void 0;
|
|
41459
41720
|
if ("names" in arity) return pairArityError(cmd, rest, arity.names);
|
|
41721
|
+
if ("optional" in arity) return rest.length > 1 ? `gtd ${cmd}: too many arguments — expected at most one ${arity.name}, got: ${rest.join(", ")}` : void 0;
|
|
41460
41722
|
if (rest.length === 0) return `gtd ${cmd}: missing ${arity.name} argument`;
|
|
41461
41723
|
if (!("variadic" in arity) && rest.length > 1) return `gtd ${cmd}: too many arguments — expected one ${arity.name}, got: ${rest.join(", ")}`;
|
|
41462
41724
|
};
|
|
@@ -41507,7 +41769,9 @@ const parseArgv = (argv) => {
|
|
|
41507
41769
|
kind: "output",
|
|
41508
41770
|
stdout: renderHelp()
|
|
41509
41771
|
};
|
|
41510
|
-
const
|
|
41772
|
+
const isHistory = first === "history";
|
|
41773
|
+
const row = first === void 0 ? void 0 : commandByToken(isHistory ? `history ${positionals[1] ?? ""}` : first);
|
|
41774
|
+
if (isHistory && row === void 0) return usagePlan("gtd history: expected a subcommand — list, show <ref> or fetch [remote]", jsonSeen);
|
|
41511
41775
|
const judgeAnswer = row?.kind === "judge" && positionals[1] === "answer";
|
|
41512
41776
|
const selectsStart = workflowPresent && first === void 0;
|
|
41513
41777
|
const judgeRun = row?.kind === "judge" && positionals[1] === "run";
|
|
@@ -41518,11 +41782,11 @@ const parseArgv = (argv) => {
|
|
|
41518
41782
|
if (first === void 0) return usagePlan("gtd: missing command — gtd decides and prints, a driver executes; run `gtd install`, or copy one from https://github.com/pmelab/gtd/blob/main/docs/driver.md's \"A complete minimal driver\" section and run that, or see usage above (`gtd --help`)", jsonSeen, renderHelp());
|
|
41519
41783
|
return usagePlan(`unknown command '${first}'`, jsonSeen);
|
|
41520
41784
|
}
|
|
41521
|
-
const restPositionals = first === void 0 ? positionals : positionals.slice(judgeAnswer || judgeRun ? 2 : 1);
|
|
41785
|
+
const restPositionals = first === void 0 ? positionals : positionals.slice(judgeAnswer || judgeRun || isHistory ? 2 : 1);
|
|
41522
41786
|
if (kind === "start" && first === void 0) {
|
|
41523
41787
|
if (restPositionals.length > 0) return usagePlan(`gtd: too many arguments — expected none, got: ${restPositionals.join(", ")}`, jsonSeen);
|
|
41524
41788
|
} else {
|
|
41525
|
-
const arityMsg = kind === "check" && restPositionals.length === 0 && present.has("--open-threads") ? void 0 : arityError(first, restPositionals, row.arity);
|
|
41789
|
+
const arityMsg = kind === "check" && restPositionals.length === 0 && present.has("--open-threads") ? void 0 : arityError(isHistory ? row.token : first, restPositionals, row.arity);
|
|
41526
41790
|
if (arityMsg !== void 0) return usagePlan(arityMsg, jsonSeen);
|
|
41527
41791
|
}
|
|
41528
41792
|
const violation = scopeViolation(kind, present);
|
|
@@ -41611,6 +41875,24 @@ const parseArgv = (argv) => {
|
|
|
41611
41875
|
verbose
|
|
41612
41876
|
};
|
|
41613
41877
|
}
|
|
41878
|
+
if (kind === "historyShow") return {
|
|
41879
|
+
kind: "command",
|
|
41880
|
+
command: {
|
|
41881
|
+
kind: "historyShow",
|
|
41882
|
+
ref: restPositionals[0]
|
|
41883
|
+
},
|
|
41884
|
+
json,
|
|
41885
|
+
verbose
|
|
41886
|
+
};
|
|
41887
|
+
if (kind === "historyFetch") return {
|
|
41888
|
+
kind: "command",
|
|
41889
|
+
command: {
|
|
41890
|
+
kind: "historyFetch",
|
|
41891
|
+
remote: restPositionals[0] ?? "origin"
|
|
41892
|
+
},
|
|
41893
|
+
json,
|
|
41894
|
+
verbose
|
|
41895
|
+
};
|
|
41614
41896
|
if (kind === "uncheck") return {
|
|
41615
41897
|
kind: "command",
|
|
41616
41898
|
command: {
|