@pmelab/gtd 62.0.0 → 63.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +14 -8
- package/dist/chunks/{testkit-XQeYCNMn.mjs → testkit-DwypA7cH.mjs} +183 -28
- package/dist/gtd.bundle.mjs +182 -26
- package/dist/gtd.core.mjs +80 -77
- package/dist/testkit/gherkin.mjs +1 -1
- package/dist/testkit.mjs +1 -1
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +2 -0
- package/src/cli/Cli.ts +12 -0
- package/src/cli/Launch.ts +1 -0
- package/src/cli/Program.test.ts +9 -7
- package/src/cli/Program.ts +51 -0
- package/src/edge/Edge.test.ts +3 -3
- package/src/edge/Edge.ts +68 -3
- package/src/edge/index.ts +1 -0
- package/src/emit/GitScript.ts +7 -0
- package/src/emit/index.ts +1 -0
- package/src/replay/Format.test.ts +26 -7
- package/src/replay/Format.ts +3 -2
- package/src/replay/Replay.test.ts +87 -0
- package/src/replay/Replay.ts +111 -23
- package/src/replay/Trailers.test.ts +10 -10
- package/src/replay/Trailers.ts +9 -0
- package/src/replay/index.ts +1 -0
- package/src/step/planStart.test.ts +2 -2
- package/src/step/planStep.test.ts +4 -4
- package/src/twins/EmittedScriptRecognizer.ts +3 -2
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -29501,9 +29501,9 @@ const withoutPath = (tree, hidden) => {
|
|
|
29501
29501
|
const OLDEST_READABLE_FORMAT = 1;
|
|
29502
29502
|
const short$1 = (hash) => hash.slice(0, 7);
|
|
29503
29503
|
const formatFault = (commits) => {
|
|
29504
|
-
const foreign = commits.find((c) => c.format < OLDEST_READABLE_FORMAT || c.format >
|
|
29504
|
+
const foreign = commits.find((c) => c.format < OLDEST_READABLE_FORMAT || c.format > 3);
|
|
29505
29505
|
if (foreign === void 0) return void 0;
|
|
29506
|
-
return `gtd: commit ${short$1(foreign.hash)} is written in history format ${foreign.format}, but this gtd reads formats ${OLDEST_READABLE_FORMAT} to
|
|
29506
|
+
return `gtd: commit ${short$1(foreign.hash)} is written in history format ${foreign.format}, but this gtd reads formats ${OLDEST_READABLE_FORMAT} to 3 — run a gtd release that reads format ${foreign.format} to continue this process`;
|
|
29507
29507
|
};
|
|
29508
29508
|
//#endregion
|
|
29509
29509
|
//#region src/replay/Trailers.ts
|
|
@@ -29634,6 +29634,9 @@ const TRAILER_READERS = {
|
|
|
29634
29634
|
"Gtd-Pin": (value, into) => {
|
|
29635
29635
|
if (value === "add" || value === "remove") into.pin ??= value;
|
|
29636
29636
|
},
|
|
29637
|
+
"Gtd-Anchor": (value, into) => {
|
|
29638
|
+
if (HASH_RE.test(value)) into.anchor ??= value;
|
|
29639
|
+
},
|
|
29637
29640
|
"Gtd-Format": (value, into) => {
|
|
29638
29641
|
if (FORMAT_RE.test(value)) into.format ??= Number(value);
|
|
29639
29642
|
},
|
|
@@ -29657,6 +29660,7 @@ const parseCommitMessage = (message) => {
|
|
|
29657
29660
|
kept: void 0,
|
|
29658
29661
|
approved: void 0,
|
|
29659
29662
|
pin: void 0,
|
|
29663
|
+
anchor: void 0,
|
|
29660
29664
|
judge: [],
|
|
29661
29665
|
marks: [],
|
|
29662
29666
|
vars: {},
|
|
@@ -29686,9 +29690,10 @@ const formatCommitMessage = (spec) => {
|
|
|
29686
29690
|
...(spec.marks ?? []).map((m) => `Gtd-Mark: ${m.name}=${m.value}`),
|
|
29687
29691
|
...trailer("Approved", spec.approved && encodeApproval(spec.approved)),
|
|
29688
29692
|
...trailer("Pin", spec.pin),
|
|
29693
|
+
...trailer("Anchor", spec.anchor),
|
|
29689
29694
|
...trailer("Branch", spec.branch),
|
|
29690
29695
|
...trailer("Version", spec.version),
|
|
29691
|
-
`Gtd-Format:
|
|
29696
|
+
`Gtd-Format: 3`
|
|
29692
29697
|
];
|
|
29693
29698
|
return `${formatSubject(spec.actor, spec.to, spec.from)}\n\n${lines.join("\n")}`;
|
|
29694
29699
|
};
|
|
@@ -29782,6 +29787,27 @@ const budgeted = (evidence, budgetBytes) => {
|
|
|
29782
29787
|
};
|
|
29783
29788
|
const sameSkills = (a, b) => (a?.length ?? 0) === (b?.length ?? 0) && (a ?? []).every((skill, i) => skill === b?.[i]);
|
|
29784
29789
|
const samePersona = (a, b) => a.model === b.model && a.system === b.system && sameSkills(a.skills, b.skills) && sameAccess(a.access, b.access);
|
|
29790
|
+
const anchorOf = (parsed) => parsed.pin === "add" ? parsed.anchor : void 0;
|
|
29791
|
+
const isAnchor = (commit) => anchorOf(commit.parsed) !== void 0;
|
|
29792
|
+
/**
|
|
29793
|
+
* The commits every anchor sets aside — those between the commit it names and
|
|
29794
|
+
* itself — or a fault for an anchor naming no commit still read.
|
|
29795
|
+
*/
|
|
29796
|
+
const setAsideBy = (base, all) => {
|
|
29797
|
+
const setAside = /* @__PURE__ */ new Set();
|
|
29798
|
+
if (all.every((c) => anchorOf(c.parsed) === void 0)) return { setAside };
|
|
29799
|
+
let kept = [];
|
|
29800
|
+
for (const commit of all) {
|
|
29801
|
+
const anchor = anchorOf(commit.parsed);
|
|
29802
|
+
if (anchor !== void 0) {
|
|
29803
|
+
const at = anchor === base ? -1 : kept.indexOf(anchor);
|
|
29804
|
+
if (anchor !== base && at === -1) return { fault: `gtd: commit ${short(commit.hash)} anchors the process at ${short(anchor)}, which is no commit replay reads in this episode` };
|
|
29805
|
+
for (const hash of kept.splice(at + 1)) setAside.add(hash);
|
|
29806
|
+
}
|
|
29807
|
+
kept.push(commit.hash);
|
|
29808
|
+
}
|
|
29809
|
+
return { setAside };
|
|
29810
|
+
};
|
|
29785
29811
|
/**
|
|
29786
29812
|
* Replay one episode: run its entry flow, answering each step from the
|
|
29787
29813
|
* episode's commits in order, until a step has no commit left — the rest.
|
|
@@ -29855,19 +29881,39 @@ const withoutPin = (input) => {
|
|
|
29855
29881
|
};
|
|
29856
29882
|
const replay = async (raw) => {
|
|
29857
29883
|
const input = withoutPin(raw);
|
|
29858
|
-
const
|
|
29884
|
+
const all = input.episode.commits.map((c) => ({
|
|
29859
29885
|
...c,
|
|
29860
29886
|
parsed: parseCommitMessage(c.message)
|
|
29861
29887
|
}));
|
|
29888
|
+
const resolved = setAsideBy(input.episode.base.hash, all);
|
|
29889
|
+
if ("fault" in resolved) return {
|
|
29890
|
+
kind: "failed",
|
|
29891
|
+
message: resolved.fault
|
|
29892
|
+
};
|
|
29893
|
+
const setAside = resolved.setAside;
|
|
29894
|
+
const commits = all.filter((c) => !setAside.has(c.hash));
|
|
29895
|
+
const pinned = (raw.episode.commits.at(-1)?.tree ?? raw.episode.base.tree).paths().includes(PIN_PATH);
|
|
29862
29896
|
const treeAt = (hash) => hash === input.episode.base.hash ? input.episode.base.tree : commits.find((c) => c.hash === hash)?.tree ?? (hash === input.start ? input.startTree : void 0);
|
|
29863
29897
|
let cursor = 0;
|
|
29864
29898
|
let pendingUsed = false;
|
|
29865
29899
|
let position = input.episode.base;
|
|
29866
29900
|
const skipPins = () => {
|
|
29867
|
-
while (cursor < commits.length && commits[cursor].parsed.pin !== void 0)
|
|
29901
|
+
while (cursor < commits.length && commits[cursor].parsed.pin !== void 0) {
|
|
29902
|
+
const commit = commits[cursor];
|
|
29903
|
+
cursor++;
|
|
29904
|
+
if (!isAnchor(commit)) continue;
|
|
29905
|
+
position = commit;
|
|
29906
|
+
const to = commit.parsed.parsed?.to;
|
|
29907
|
+
expectedNext = to === void 0 || to === UNPIN_STATE ? void 0 : {
|
|
29908
|
+
name: to,
|
|
29909
|
+
hash: commit.hash
|
|
29910
|
+
};
|
|
29911
|
+
anchorPending = expectedNext !== void 0;
|
|
29912
|
+
}
|
|
29868
29913
|
};
|
|
29869
29914
|
let previousPosition = input.episode.base;
|
|
29870
29915
|
let expectedNext;
|
|
29916
|
+
let anchorPending = false;
|
|
29871
29917
|
const occurrences = /* @__PURE__ */ new Map();
|
|
29872
29918
|
const personas = /* @__PURE__ */ new Map();
|
|
29873
29919
|
const scopes = [];
|
|
@@ -30010,14 +30056,18 @@ const replay = async (raw) => {
|
|
|
30010
30056
|
}
|
|
30011
30057
|
return samePersona(seen, identity) ? void 0 : `gtd: "${step.name}" runs with a different model, system prompt, skills or file access than an earlier agent step in its conversation in memory scope "${step.memoryScope || "root"}" — a conversation has one identity`;
|
|
30012
30058
|
};
|
|
30013
|
-
const DIVERGED = "the workflow changed under this process; run `gtd abandon` to start over";
|
|
30059
|
+
const DIVERGED = pinned ? "the workflow changed under this process; run `gtd repin` to continue under it from there, or `gtd abandon` to start over" : "the workflow changed under this process; run `gtd abandon` to start over";
|
|
30060
|
+
const diverged = (message, reached) => ({
|
|
30061
|
+
kind: "divergence",
|
|
30062
|
+
message: `${message} — ${DIVERGED}`,
|
|
30063
|
+
at: position.hash,
|
|
30064
|
+
reached: reached?.name
|
|
30065
|
+
});
|
|
30014
30066
|
const blockerAt = (step) => {
|
|
30015
30067
|
const expected = expectedNext;
|
|
30016
30068
|
expectedNext = void 0;
|
|
30017
|
-
|
|
30018
|
-
|
|
30019
|
-
message: `gtd: commit ${short(expected.hash)} names "${expected.name}" as the next step, but replay reached "${step.name}" — ${DIVERGED}`
|
|
30020
|
-
};
|
|
30069
|
+
anchorPending = false;
|
|
30070
|
+
if (expected !== void 0 && expected.name !== step.name) return diverged(`gtd: commit ${short(expected.hash)} names "${expected.name}" as the next step, but replay reached "${step.name}"`, step);
|
|
30021
30071
|
const error = unknownOptions(step) ?? approveConflict(step) ?? modeWithoutFile(step) ?? checkIdentity(step);
|
|
30022
30072
|
return error === void 0 ? void 0 : {
|
|
30023
30073
|
kind: "failed",
|
|
@@ -30028,33 +30078,30 @@ const replay = async (raw) => {
|
|
|
30028
30078
|
if (step.request.kind !== "human" || step.request.options.approve !== true) return void 0;
|
|
30029
30079
|
const approved = commit.parsed.approved;
|
|
30030
30080
|
if (approved !== void 0 && formatStepId(approved.step) === expected) return void 0;
|
|
30031
|
-
return {
|
|
30032
|
-
kind: "divergence",
|
|
30033
|
-
message: `gtd: commit ${short(commit.hash)} completes approval gate "${expected}" but records no Gtd-Approved for it — ${DIVERGED}`
|
|
30034
|
-
};
|
|
30081
|
+
return diverged(`gtd: commit ${short(commit.hash)} completes approval gate "${expected}" but records no Gtd-Approved for it`, step);
|
|
30035
30082
|
};
|
|
30036
30083
|
const consumeCommit = (step, commit) => {
|
|
30037
30084
|
const recorded = commit.parsed.step;
|
|
30038
30085
|
const expected = formatStepId(step.id);
|
|
30039
30086
|
if (recorded === void 0 || formatStepId(recorded) !== expected) {
|
|
30040
30087
|
const what = recorded === void 0 ? "records no step" : `records step "${formatStepId(recorded)}"`;
|
|
30041
|
-
return finish({
|
|
30042
|
-
kind: "divergence",
|
|
30043
|
-
message: `gtd: commit ${short(commit.hash)} ${what}, but replay expected "${expected}" — ${DIVERGED}`
|
|
30044
|
-
});
|
|
30088
|
+
return finish(diverged(`gtd: commit ${short(commit.hash)} ${what}, but replay expected "${expected}"`, step));
|
|
30045
30089
|
}
|
|
30046
30090
|
const unapproved = missingApproval(step, commit, expected);
|
|
30047
30091
|
if (unapproved !== void 0) return finish(unapproved);
|
|
30048
30092
|
cursor++;
|
|
30049
30093
|
advance(commit);
|
|
30094
|
+
expectNextOf(commit);
|
|
30050
30095
|
skipPins();
|
|
30051
30096
|
noteKept(step, commit);
|
|
30097
|
+
return replyFor(step.request, commit.parsed.judge);
|
|
30098
|
+
};
|
|
30099
|
+
const expectNextOf = (commit) => {
|
|
30052
30100
|
const to = commit.parsed.parsed?.to;
|
|
30053
30101
|
if (to !== void 0 && to !== UNPIN_STATE) expectedNext = {
|
|
30054
30102
|
name: to,
|
|
30055
30103
|
hash: commit.hash
|
|
30056
30104
|
};
|
|
30057
|
-
return replyFor(step.request, commit.parsed.judge);
|
|
30058
30105
|
};
|
|
30059
30106
|
const noteKept = (step, commit) => {
|
|
30060
30107
|
if (commit.parsed.kept === void 0) return;
|
|
@@ -30126,10 +30173,8 @@ const replay = async (raw) => {
|
|
|
30126
30173
|
};
|
|
30127
30174
|
const endOfFlow = (via) => {
|
|
30128
30175
|
skipPins();
|
|
30129
|
-
|
|
30130
|
-
|
|
30131
|
-
message: `gtd: the flow ended, but commit ${short(commits[cursor].hash)} continues the episode — ${DIVERGED}`
|
|
30132
|
-
} : {
|
|
30176
|
+
if (anchorPending && cursor === commits.length) return diverged(`gtd: the flow ended, but commit ${short(expectedNext.hash)} continues the process at "${expectedNext.name}"`, void 0);
|
|
30177
|
+
return cursor < commits.length ? diverged(`gtd: the flow ended, but commit ${short(commits[cursor].hash)} continues the episode`, void 0) : {
|
|
30133
30178
|
kind: "ended",
|
|
30134
30179
|
via,
|
|
30135
30180
|
trace,
|
|
@@ -30139,6 +30184,7 @@ const replay = async (raw) => {
|
|
|
30139
30184
|
};
|
|
30140
30185
|
const changesSince = (hash) => {
|
|
30141
30186
|
const tree = treeAt(hash);
|
|
30187
|
+
if (setAside.has(hash)) throw new Error(`gtd: changesSince(${hash}): a repin set ${short(hash)} aside — pass a hash this run read from head() or start()`);
|
|
30142
30188
|
if (tree === void 0) throw new Error(`gtd: changesSince(${hash}): ${hash} is not the episode base or one of its commits — pass a hash this run read from head() or start(), not one captured earlier, read from state, or from another branch`);
|
|
30143
30189
|
return changesBetween(tree, position.tree);
|
|
30144
30190
|
};
|
|
@@ -31335,6 +31381,12 @@ const pinScript = (path) => {
|
|
|
31335
31381
|
const slash = path.lastIndexOf("/");
|
|
31336
31382
|
return `${slash === -1 ? "" : `mkdir -p ${shellQuote$1(path.slice(0, slash))}\n`}gtd bundle >${p} || { rm -f -- ${p}; exit 1; }`;
|
|
31337
31383
|
};
|
|
31384
|
+
/** `gtd repin`'s pin write: a failed `gtd bundle` leaves the committed pin in place. */
|
|
31385
|
+
const repinScript = (path) => {
|
|
31386
|
+
const p = shellQuote$1(path);
|
|
31387
|
+
const next = shellQuote$1(`${path}.new`);
|
|
31388
|
+
return `gtd bundle >${next} || { rm -f -- ${next}; exit 1; }\nmv -f -- ${next} ${p}`;
|
|
31389
|
+
};
|
|
31338
31390
|
/** An unpin beat's script, and `gtd abandon`'s removal of the pin. */
|
|
31339
31391
|
const unpinScript = (path) => `rm -f -- ${shellQuote$1(path)}`;
|
|
31340
31392
|
/**
|
|
@@ -31563,6 +31615,12 @@ const FMT_ABANDONED = "abandoned the process resting at \"%s\" — HEAD is back
|
|
|
31563
31615
|
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";
|
|
31564
31616
|
/** `no gtd process is underway (resting at "<initial>") — nothing to abandon` — `gtd abandon`'s no-op plain-text line. */
|
|
31565
31617
|
const abandonNoopText = (initial) => renderFormat(FMT_ABANDON_NOOP, initial);
|
|
31618
|
+
/** A landed self-loop/target-change transition. */
|
|
31619
|
+
const transitionOutcome = (from, to) => printfLine("%s %s → %s\n", [
|
|
31620
|
+
"'->'",
|
|
31621
|
+
shellQuote$1(from),
|
|
31622
|
+
shellQuote$1(to)
|
|
31623
|
+
]);
|
|
31566
31624
|
/** One already-rendered plain line. */
|
|
31567
31625
|
const noteOutcome = (text) => printfLine("%s\n", [shellQuote$1(text)]);
|
|
31568
31626
|
/**
|
|
@@ -32084,11 +32142,21 @@ const approvalEntryOf = (hash, message) => message.approved === void 0 ? [] : [{
|
|
|
32084
32142
|
email: message.approved.email,
|
|
32085
32143
|
commit: hash
|
|
32086
32144
|
}];
|
|
32145
|
+
const setAsideIn = (history, location) => {
|
|
32146
|
+
const anchored = setAsideBy(hashAt(history, location.baseIndex) ?? EMPTY_TREE$1, history.slice(location.baseIndex + 1).map((c) => ({
|
|
32147
|
+
hash: c.hash,
|
|
32148
|
+
parsed: parseCommitMessage(c.message)
|
|
32149
|
+
})));
|
|
32150
|
+
return "setAside" in anchored ? anchored.setAside : /* @__PURE__ */ new Set();
|
|
32151
|
+
};
|
|
32087
32152
|
const buildRun = (history, location, closingHash) => {
|
|
32088
32153
|
const allProcessCommits = history.slice(location.processStart);
|
|
32089
32154
|
const allParsed = allProcessCommits.map((c) => parseCommitMessage(c.message));
|
|
32090
32155
|
const processCommits = allProcessCommits.filter((_, i) => allParsed[i].pin !== "add");
|
|
32091
32156
|
const parsed = allParsed.filter((m) => m.pin !== "add");
|
|
32157
|
+
const setAside = setAsideIn(history, location);
|
|
32158
|
+
const answering = parsed.filter((_, i) => !setAside.has(processCommits[i].hash));
|
|
32159
|
+
const answeringCommits = processCommits.filter((c) => !setAside.has(c.hash));
|
|
32092
32160
|
const first = parsed[0];
|
|
32093
32161
|
const startParentHash = hashAt(history, location.processStart - 1) ?? EMPTY_TREE$1;
|
|
32094
32162
|
const head = history[history.length - 1];
|
|
@@ -32100,10 +32168,10 @@ const buildRun = (history, location, closingHash) => {
|
|
|
32100
32168
|
diffBase: first?.reviewBase ?? startParentHash,
|
|
32101
32169
|
trace: processCommits.map((c, i) => traceEntryOf(c.hash, parsed[i])),
|
|
32102
32170
|
costEntries: parsed.flatMap((m, i) => costEntriesOf(processCommits[i].hash, m)),
|
|
32103
|
-
judgeVerdicts:
|
|
32171
|
+
judgeVerdicts: answering.flatMap((m) => m.judge),
|
|
32104
32172
|
pinCommitted: allParsed.some((m) => m.pin === "add"),
|
|
32105
|
-
marks:
|
|
32106
|
-
approvals:
|
|
32173
|
+
marks: answering.flatMap((m) => m.marks),
|
|
32174
|
+
approvals: answeringCommits.flatMap((c, i) => approvalEntryOf(c.hash, answering[i])),
|
|
32107
32175
|
pinnedVars: { ...first?.vars },
|
|
32108
32176
|
headTurn: headTurnOf(head),
|
|
32109
32177
|
closingHash,
|
|
@@ -32532,6 +32600,43 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
32532
32600
|
};
|
|
32533
32601
|
});
|
|
32534
32602
|
const currentRest = restAt(void 0);
|
|
32603
|
+
/**
|
|
32604
|
+
* Where `gtd repin` anchors a pinned process the live workflow no longer
|
|
32605
|
+
* replays: the commit replay last stood on, and the step the live workflow
|
|
32606
|
+
* reaches from there on HEAD's tree — the unpin when the flow ends instead.
|
|
32607
|
+
* `undefined` when there is nothing to anchor: no divergence, or no pin.
|
|
32608
|
+
*/
|
|
32609
|
+
const repinAnchor = gen(function* () {
|
|
32610
|
+
const git = yield* GitService;
|
|
32611
|
+
const config = yield* (yield* ConfigService).load;
|
|
32612
|
+
const workspace = yield* Workspace;
|
|
32613
|
+
const run = yield* computeProcessRun(git, config.workflow);
|
|
32614
|
+
const { def, setup } = yield* setupFor(config, run, workspace, yield* Host);
|
|
32615
|
+
const diverged = yield* promise(() => replayFor(setup));
|
|
32616
|
+
if (diverged.kind !== "divergence" || !headHasPin(workspace, run)) return void 0;
|
|
32617
|
+
const trial = {
|
|
32618
|
+
hash: "",
|
|
32619
|
+
message: formatCommitMessage({
|
|
32620
|
+
actor: "check",
|
|
32621
|
+
from: PIN_STATES.add,
|
|
32622
|
+
to: PIN_STATES.add,
|
|
32623
|
+
pin: "add",
|
|
32624
|
+
anchor: diverged.at
|
|
32625
|
+
}),
|
|
32626
|
+
tree: setup.episode.at(-1)?.tree ?? setup.base.tree
|
|
32627
|
+
};
|
|
32628
|
+
const outcome = yield* promise(() => replayFor({
|
|
32629
|
+
...setup,
|
|
32630
|
+
episode: [...setup.episode, trial]
|
|
32631
|
+
}));
|
|
32632
|
+
if (outcome.kind !== "divergence") return yield* fail$8(replayError(outcome) ?? /* @__PURE__ */ new Error("gtd repin: replay found no step"));
|
|
32633
|
+
const to = outcome.reached ?? PIN_STATES.remove;
|
|
32634
|
+
if (run.workflow === void 0 && to === def.initial) return yield* fail$8(/* @__PURE__ */ new Error(`gtd repin: the live workflow continues this process at "${to}", the default workflow's first step, where a finished process waits — run \`gtd abandon\` to start over`));
|
|
32635
|
+
return {
|
|
32636
|
+
at: diverged.at,
|
|
32637
|
+
to
|
|
32638
|
+
};
|
|
32639
|
+
});
|
|
32535
32640
|
/** The step the process rests at and the trace that reached it — at an unpin, the flow's last step. */
|
|
32536
32641
|
const restingOn = (outcome, pin) => {
|
|
32537
32642
|
if (outcome.kind === "rest") return {
|
|
@@ -34054,6 +34159,44 @@ const runRestoreCommand = (json, out) => gen(function* () {
|
|
|
34054
34159
|
]).required;
|
|
34055
34160
|
yield* writeScript(json, out, combinedScript(required, ""));
|
|
34056
34161
|
});
|
|
34162
|
+
/** The step an ordinary repin rests at — the one the process rests at now. */
|
|
34163
|
+
const repinTarget = gen(function* () {
|
|
34164
|
+
const rest = yield* currentRest;
|
|
34165
|
+
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;
|
|
34166
|
+
if (refusal !== void 0) return yield* fail$8(/* @__PURE__ */ new Error(`gtd repin: ${refusal}`));
|
|
34167
|
+
return rest.step.name;
|
|
34168
|
+
});
|
|
34169
|
+
/**
|
|
34170
|
+
* `gtd repin`: the pin beat's script and landing, on demand. It runs live: a
|
|
34171
|
+
* pinned process the live workflow no longer replays is anchored where it
|
|
34172
|
+
* diverged, and any other failure to read the rest refuses before any pin is
|
|
34173
|
+
* written.
|
|
34174
|
+
*/
|
|
34175
|
+
const runRepinCommand = (json, out) => gen(function* () {
|
|
34176
|
+
const anchor = yield* repinAnchor;
|
|
34177
|
+
const to = anchor?.to ?? (yield* repinTarget);
|
|
34178
|
+
const message = formatCommitMessage({
|
|
34179
|
+
actor: "check",
|
|
34180
|
+
from: PIN_STATES.add,
|
|
34181
|
+
to,
|
|
34182
|
+
pin: "add",
|
|
34183
|
+
...anchor !== void 0 ? { anchor: anchor.at } : {}
|
|
34184
|
+
});
|
|
34185
|
+
yield* writeScript(json, out, combinedScript(emitScripts([
|
|
34186
|
+
{
|
|
34187
|
+
kind: "gitWrite",
|
|
34188
|
+
command: repinScript(PIN_PATH)
|
|
34189
|
+
},
|
|
34190
|
+
{
|
|
34191
|
+
kind: "gitWrite",
|
|
34192
|
+
command: commitPath(PIN_PATH, message)
|
|
34193
|
+
},
|
|
34194
|
+
{
|
|
34195
|
+
kind: "outcome",
|
|
34196
|
+
command: transitionOutcome(PIN_STATES.add, to)
|
|
34197
|
+
}
|
|
34198
|
+
]).required, ""));
|
|
34199
|
+
});
|
|
34057
34200
|
/**
|
|
34058
34201
|
* The mode's own validate command for `file`: a declared `validate:` command
|
|
34059
34202
|
* verbatim; a `"builtin"` validator or no mode at all names the leaf
|
|
@@ -34321,6 +34464,7 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
34321
34464
|
case "start": return runStartCommand(command, json, out);
|
|
34322
34465
|
case "abandon": return runAbandonCommand(json, out);
|
|
34323
34466
|
case "restore": return runRestoreCommand(json, out);
|
|
34467
|
+
case "repin": return runRepinCommand(json, out);
|
|
34324
34468
|
case "next": return runNextCommand(json, out);
|
|
34325
34469
|
case "validate": return runValidateCommand(json, out);
|
|
34326
34470
|
case "check":
|
|
@@ -34450,6 +34594,7 @@ const SCRIPT_KINDS = /* @__PURE__ */ new Set([
|
|
|
34450
34594
|
"start",
|
|
34451
34595
|
"abandon",
|
|
34452
34596
|
"restore",
|
|
34597
|
+
"repin",
|
|
34453
34598
|
"validate",
|
|
34454
34599
|
"historyFetch"
|
|
34455
34600
|
]);
|
|
@@ -34717,6 +34862,16 @@ With no <name>, list every workflow and door it can start. An unknown <name>, th
|
|
|
34717
34862
|
summary: "Bring back the last abandoned process",
|
|
34718
34863
|
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.`
|
|
34719
34864
|
},
|
|
34865
|
+
{
|
|
34866
|
+
token: "repin",
|
|
34867
|
+
kind: "repin",
|
|
34868
|
+
arity: "none",
|
|
34869
|
+
group: "Start and stop",
|
|
34870
|
+
summary: "Pin the process to the live gtd and workflow",
|
|
34871
|
+
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. When the live workflow no longer replays the process, the commit continues from the divergence: a Gtd-Anchor trailer names the last commit it replays, the later commits stay as files only, and the process rests at the step the live workflow reached there. Refuses when no process is underway, when the next beat is a pin or unpin beat already, when an unpinned process diverges, or when the live workflow would continue at the default workflow's first step.
|
|
34872
|
+
|
|
34873
|
+
s=$(gtd repin --json=script) && sh -c "$s"`
|
|
34874
|
+
},
|
|
34720
34875
|
{
|
|
34721
34876
|
token: "summary",
|
|
34722
34877
|
kind: "summary",
|
|
@@ -35418,6 +35573,7 @@ const LIVE_KINDS = /* @__PURE__ */ new Set([
|
|
|
35418
35573
|
"lsp",
|
|
35419
35574
|
"guide",
|
|
35420
35575
|
"bundle",
|
|
35576
|
+
"repin",
|
|
35421
35577
|
"historyList",
|
|
35422
35578
|
"historyShow",
|
|
35423
35579
|
"historyMetrics",
|