@pmelab/gtd 27.7.1 → 27.9.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 +10 -5
- package/dist/chunks/{wire-C12dahrH.mjs → wire-D3Rc5r22.mjs} +14 -1
- package/dist/flows.mjs +16 -1
- package/dist/gtd.bundle.mjs +203 -32
- package/dist/testkit.mjs +172 -33
- package/dist/wire.mjs +2 -2
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +39 -3
- package/src/cli/Cli.ts +23 -2
- package/src/cli/Program.test.ts +3 -1
- package/src/cli/Program.ts +22 -0
- package/src/edge/Edge.test.ts +217 -34
- package/src/edge/Edge.ts +131 -39
- package/src/edge/index.ts +1 -0
- package/src/flows/runtime.test.ts +27 -0
- package/src/flows/runtime.ts +18 -0
- package/src/replay/Replay.test.ts +169 -0
- package/src/replay/Replay.ts +42 -2
- package/src/replay/Slices.test.ts +73 -0
- package/src/replay/Slices.ts +45 -0
- package/src/replay/Trailers.test.ts +11 -0
- package/src/replay/Trailers.ts +3 -1
- package/src/replay/index.ts +1 -0
- package/src/wire/Slices.ts +25 -0
- package/src/wire/WireSchema.test.ts +18 -0
- package/src/wire/golden/1/slices.json +11 -0
- package/src/wire/index.ts +1 -0
package/README.md
CHANGED
|
@@ -649,7 +649,7 @@ gtd is one package, `@pmelab/gtd`, and every entrypoint shares its version and
|
|
|
649
649
|
|
|
650
650
|
| Import | What it is |
|
|
651
651
|
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
652
|
-
| `@pmelab/gtd/flows` | The flow API a workflow is written in: the steps (`agent`, `human`, `run`, …), workflow factories, `defineConfig` and `FLOWS_PROTOCOL`.
|
|
652
|
+
| `@pmelab/gtd/flows` | The flow API a workflow is written in: the steps (`agent`, `human`, `run`, …), `scope` and `slice` (named spans of a process), workflow factories, `defineConfig` and `FLOWS_PROTOCOL`. |
|
|
653
653
|
| `@pmelab/gtd/wire` | The driver protocol: `WIRE_SCHEMA`, the exit codes and the types of every `--json` document. |
|
|
654
654
|
| `@pmelab/gtd/steering` | The steering-file formats (`qa`, `review`) and the review-note readers. |
|
|
655
655
|
| `@pmelab/gtd/workflow-kit` | Policy-free building blocks to compose a workflow from: health checks, quality laps, scenario freezing, review tails. |
|
|
@@ -662,10 +662,15 @@ install for any of these but `./testkit`: gtd resolves a `gtd.config.ts`'s
|
|
|
662
662
|
`@pmelab/gtd/*` imports to its own copy.
|
|
663
663
|
|
|
664
664
|
The `gtd` bin's commands — `next`, `land`, `abandon`, `restore`, `validate`,
|
|
665
|
-
`check`, `uncheck`, `install`, `summary`, `base`, `door`, `doors`, `
|
|
666
|
-
`help`, `history list`, `history show`, `history fetch`, `strays`,
|
|
667
|
-
`ui`, `lsp`, `loop
|
|
668
|
-
|
|
665
|
+
`check`, `uncheck`, `install`, `summary`, `base`, `door`, `doors`, `slices`,
|
|
666
|
+
`version`, `help`, `history list`, `history show`, `history fetch`, `strays`,
|
|
667
|
+
and the tools `ui`, `lsp`, `loop`, `judge` (`judge answer`, `judge run`) — are
|
|
668
|
+
listed in the
|
|
669
|
+
[CLI reference](https://github.com/pmelab/gtd/blob/main/docs/cli.md).
|
|
670
|
+
`gtd slices` lists the spans a flow's `slice()` calls mark. It refuses (exit 1)
|
|
671
|
+
when HEAD neither closes nor sits inside a process, or when today's config
|
|
672
|
+
disagrees with history about the process; a version mismatch only warns on
|
|
673
|
+
stderr.
|
|
669
674
|
|
|
670
675
|
## License
|
|
671
676
|
|
|
@@ -475,4 +475,17 @@ const compile = (glob) => {
|
|
|
475
475
|
};
|
|
476
476
|
const globMatches = (path, glob) => compile(glob).test(path);
|
|
477
477
|
//#endregion
|
|
478
|
-
|
|
478
|
+
//#region src/wire/Slices.ts
|
|
479
|
+
/** The ONLY place `gtd slices --json`'s shape is built; copies exactly the four fields, in order. */
|
|
480
|
+
const slicesDocument = (slices) => ({
|
|
481
|
+
schema: 1,
|
|
482
|
+
slices: slices.map(({ name, base, tip, closed }) => ({
|
|
483
|
+
name,
|
|
484
|
+
base,
|
|
485
|
+
tip,
|
|
486
|
+
closed
|
|
487
|
+
}))
|
|
488
|
+
});
|
|
489
|
+
const renderSlicesJson = (document) => JSON.stringify(document) + "\n";
|
|
490
|
+
//#endregion
|
|
491
|
+
export { WIRE_SCHEMA as A, EXIT_RUNTIME_ERROR as C, EXIT_USAGE_ERROR as D, EXIT_STRAYS_FOUND as E, ACCESS_REFUSAL as O, EXIT_OK as S, EXIT_SIGTERM as T, BEAT_KINDS as _, selectPath as a, stallDiagnosis as b, landProseText as c, renderLandPlain as d, DEMAND_BRIEFING as f, statusOf as g, renderBeatJson as h, resolveSession as i, renderFormat as j, UNATTRIBUTED_MODEL as k, noopText as l, beatDocument as m, slicesDocument as n, judgeJson as o, renderBeatPlain as p, globMatches as r, landFields as s, renderSlicesJson as t, renderLandJson as u, beatKindOf as v, EXIT_SIGINT as w, EXIT_CODES as x, demandOf as y };
|
package/dist/flows.mjs
CHANGED
|
@@ -86,6 +86,20 @@ const scope = async (scope, fn) => {
|
|
|
86
86
|
context.popScope();
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* Mark the span `fn` covers as one named slice of the process. Lands no
|
|
91
|
+
* commit, prefixes no step name, opens no memory scope.
|
|
92
|
+
*/
|
|
93
|
+
const slice = async (name, fn) => {
|
|
94
|
+
const context = ctx();
|
|
95
|
+
if (typeof context.enterSlice !== "function") return fn();
|
|
96
|
+
context.enterSlice(name);
|
|
97
|
+
try {
|
|
98
|
+
return await fn();
|
|
99
|
+
} finally {
|
|
100
|
+
context.leaveSlice?.();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
89
103
|
const read = (path) => ctx().read(path);
|
|
90
104
|
/** Every path in the tree matching `pattern` (`*` stays within a segment, `**` crosses them). */
|
|
91
105
|
const glob = (pattern) => ctx().glob(pattern);
|
|
@@ -652,10 +666,11 @@ var flows_exports = /* @__PURE__ */ __exportAll({
|
|
|
652
666
|
sections: () => sections,
|
|
653
667
|
showScript: () => showScript,
|
|
654
668
|
skillsFor: () => skillsFor,
|
|
669
|
+
slice: () => slice,
|
|
655
670
|
start: () => start,
|
|
656
671
|
threads: () => threads,
|
|
657
672
|
vars: () => vars,
|
|
658
673
|
wrote: () => wrote
|
|
659
674
|
});
|
|
660
675
|
//#endregion
|
|
661
|
-
export { FLOWS_PROTOCOL, FlowsProtocolError, accessFor, agent, answered, changes, changesSince, check, checkScript, codeChanges, codeThreads, configProblems, configure, defineConfig, defineWorkflow, displayPath, env, glob, hasThreadFor, head, human, installContext, isFactoryWorkflow, judge, judgeDiffShape, kept, mark, matches, moveScript, numeric, openQuestions, protocolMismatch, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restart, restoreScript, revertScript, run, scope, seamGate, sectionBodies, sections, showScript, skillsFor, start, flows_exports as t, threads, vars, wrote };
|
|
676
|
+
export { FLOWS_PROTOCOL, FlowsProtocolError, accessFor, agent, answered, changes, changesSince, check, checkScript, codeChanges, codeThreads, configProblems, configure, defineConfig, defineWorkflow, displayPath, env, glob, hasThreadFor, head, human, installContext, isFactoryWorkflow, judge, judgeDiffShape, kept, mark, matches, moveScript, numeric, openQuestions, protocolMismatch, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restart, restoreScript, revertScript, run, scope, seamGate, sectionBodies, sections, showScript, skillsFor, slice, start, flows_exports as t, threads, vars, wrote };
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -21724,6 +21724,20 @@ const scope = async (scope, fn) => {
|
|
|
21724
21724
|
context.popScope();
|
|
21725
21725
|
}
|
|
21726
21726
|
};
|
|
21727
|
+
/**
|
|
21728
|
+
* Mark the span `fn` covers as one named slice of the process. Lands no
|
|
21729
|
+
* commit, prefixes no step name, opens no memory scope.
|
|
21730
|
+
*/
|
|
21731
|
+
const slice = async (name, fn) => {
|
|
21732
|
+
const context = ctx();
|
|
21733
|
+
if (typeof context.enterSlice !== "function") return fn();
|
|
21734
|
+
context.enterSlice(name);
|
|
21735
|
+
try {
|
|
21736
|
+
return await fn();
|
|
21737
|
+
} finally {
|
|
21738
|
+
context.leaveSlice?.();
|
|
21739
|
+
}
|
|
21740
|
+
};
|
|
21727
21741
|
const read = (path) => ctx().read(path);
|
|
21728
21742
|
/** Every path in the tree matching `pattern` (`*` stays within a segment, `**` crosses them). */
|
|
21729
21743
|
const glob = (pattern) => ctx().glob(pattern);
|
|
@@ -22290,6 +22304,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
|
|
|
22290
22304
|
sections: () => sections,
|
|
22291
22305
|
showScript: () => showScript,
|
|
22292
22306
|
skillsFor: () => skillsFor,
|
|
22307
|
+
slice: () => slice,
|
|
22293
22308
|
start: () => start,
|
|
22294
22309
|
threads: () => threads,
|
|
22295
22310
|
vars: () => vars,
|
|
@@ -37637,6 +37652,22 @@ var init_Glob = __esmMin((() => {
|
|
|
37637
37652
|
globMatches = (path, glob) => compile(glob).test(path);
|
|
37638
37653
|
}));
|
|
37639
37654
|
//#endregion
|
|
37655
|
+
//#region src/wire/Slices.ts
|
|
37656
|
+
var slicesDocument, renderSlicesJson;
|
|
37657
|
+
var init_Slices = __esmMin((() => {
|
|
37658
|
+
init_constants();
|
|
37659
|
+
slicesDocument = (slices) => ({
|
|
37660
|
+
schema: 1,
|
|
37661
|
+
slices: slices.map(({ name, base, tip, closed }) => ({
|
|
37662
|
+
name,
|
|
37663
|
+
base,
|
|
37664
|
+
tip,
|
|
37665
|
+
closed
|
|
37666
|
+
}))
|
|
37667
|
+
});
|
|
37668
|
+
renderSlicesJson = (document) => JSON.stringify(document) + "\n";
|
|
37669
|
+
}));
|
|
37670
|
+
//#endregion
|
|
37640
37671
|
//#region src/wire/index.ts
|
|
37641
37672
|
var init_wire = __esmMin((() => {
|
|
37642
37673
|
init_constants();
|
|
@@ -37651,6 +37682,7 @@ var init_wire = __esmMin((() => {
|
|
|
37651
37682
|
init_Select();
|
|
37652
37683
|
init_Sessions();
|
|
37653
37684
|
init_Glob();
|
|
37685
|
+
init_Slices();
|
|
37654
37686
|
}));
|
|
37655
37687
|
//#endregion
|
|
37656
37688
|
//#region src/replay/RefName.ts
|
|
@@ -37670,6 +37702,32 @@ const refNameFault = (name) => {
|
|
|
37670
37702
|
}
|
|
37671
37703
|
};
|
|
37672
37704
|
//#endregion
|
|
37705
|
+
//#region src/replay/Slices.ts
|
|
37706
|
+
const IMPLICIT_SLICE = "main";
|
|
37707
|
+
/** Same shape as mark()'s name rule; kept local so @pmelab/gtd/flows publishes no regex. */
|
|
37708
|
+
const SLICE_NAME = /^[a-z][a-z0-9-]*$/;
|
|
37709
|
+
/**
|
|
37710
|
+
* Folds boundaries into ranges: each kept span's base is the previous kept
|
|
37711
|
+
* span's `leftAt`, so a gap before a span belongs to it, and the last span
|
|
37712
|
+
* runs to the episode tip. A closed span that landed nothing is dropped.
|
|
37713
|
+
*/
|
|
37714
|
+
const sliceRanges = (spans, episode) => {
|
|
37715
|
+
if (spans.some((s) => s.enteredAt === "" || s.leftAt === "")) throw new Error("sliceRanges: a pending turn's \"\" hash is not a commit");
|
|
37716
|
+
const kept = spans.filter((s) => s.leftAt === void 0 || s.enteredAt !== s.leftAt);
|
|
37717
|
+
if (kept.length === 0) return [{
|
|
37718
|
+
name: IMPLICIT_SLICE,
|
|
37719
|
+
base: episode.base,
|
|
37720
|
+
tip: episode.tip,
|
|
37721
|
+
open: true
|
|
37722
|
+
}];
|
|
37723
|
+
return kept.map((s, i) => ({
|
|
37724
|
+
name: s.name,
|
|
37725
|
+
base: i === 0 ? episode.base : kept[i - 1].leftAt ?? episode.base,
|
|
37726
|
+
tip: i === kept.length - 1 ? episode.tip : s.leftAt ?? episode.tip,
|
|
37727
|
+
open: i === kept.length - 1
|
|
37728
|
+
}));
|
|
37729
|
+
};
|
|
37730
|
+
//#endregion
|
|
37673
37731
|
//#region src/replay/Tree.ts
|
|
37674
37732
|
const treeFromRecord = (files) => ({
|
|
37675
37733
|
paths: () => Object.keys(files).sort(),
|
|
@@ -37826,7 +37884,7 @@ const TRAILER_READERS = {
|
|
|
37826
37884
|
if (value !== "") into.branch ??= value;
|
|
37827
37885
|
},
|
|
37828
37886
|
"Gtd-Version": (value, into) => {
|
|
37829
|
-
if (value !== "") into.version ??= value;
|
|
37887
|
+
if (value !== "" && !/[\u0000-\u001f\u007f-\u009f]/.test(value)) into.version ??= value;
|
|
37830
37888
|
},
|
|
37831
37889
|
"Gtd-Kept": (value, into) => {
|
|
37832
37890
|
if (HASH_RE.test(value)) into.kept ??= value;
|
|
@@ -38017,6 +38075,7 @@ const replay = async (input) => {
|
|
|
38017
38075
|
const keptTips = /* @__PURE__ */ new Map();
|
|
38018
38076
|
let landed;
|
|
38019
38077
|
const marks = [];
|
|
38078
|
+
const slices = [];
|
|
38020
38079
|
let outcome;
|
|
38021
38080
|
let signal;
|
|
38022
38081
|
const settled = new Promise((resolve) => {
|
|
@@ -38223,7 +38282,8 @@ const replay = async (input) => {
|
|
|
38223
38282
|
rest: step,
|
|
38224
38283
|
trace,
|
|
38225
38284
|
landed,
|
|
38226
|
-
marks
|
|
38285
|
+
marks,
|
|
38286
|
+
slices: slices.map((s) => ({ ...s }))
|
|
38227
38287
|
});
|
|
38228
38288
|
return landPending(step, input.pending);
|
|
38229
38289
|
};
|
|
@@ -38262,7 +38322,8 @@ const replay = async (input) => {
|
|
|
38262
38322
|
via,
|
|
38263
38323
|
trace,
|
|
38264
38324
|
landed,
|
|
38265
|
-
marks
|
|
38325
|
+
marks,
|
|
38326
|
+
slices: slices.map((s) => ({ ...s }))
|
|
38266
38327
|
};
|
|
38267
38328
|
const changesSince = (hash) => {
|
|
38268
38329
|
const tree = treeAt(hash);
|
|
@@ -38287,6 +38348,31 @@ const replay = async (input) => {
|
|
|
38287
38348
|
scopes.pop();
|
|
38288
38349
|
scopeCallIds.pop();
|
|
38289
38350
|
},
|
|
38351
|
+
enterSlice: (name) => {
|
|
38352
|
+
if (outcome !== void 0) return;
|
|
38353
|
+
const fail = (message) => {
|
|
38354
|
+
finish({
|
|
38355
|
+
kind: "failed",
|
|
38356
|
+
message
|
|
38357
|
+
});
|
|
38358
|
+
throw new Stop();
|
|
38359
|
+
};
|
|
38360
|
+
if (typeof name !== "string" || !SLICE_NAME.test(name)) fail(`gtd: slice "${name}": name must match ${SLICE_NAME}`);
|
|
38361
|
+
if (name === "main") fail(`gtd: slice "${IMPLICIT_SLICE}" is reserved for the implicit slice`);
|
|
38362
|
+
const open = slices.find((s) => s.leftAt === void 0);
|
|
38363
|
+
if (open !== void 0) fail(`gtd: slice "${name}" is entered inside slice "${open.name}" — slices do not nest`);
|
|
38364
|
+
if (slices.some((s) => s.name === name)) fail(`gtd: slice "${name}" is entered twice in this episode — a loop must sit inside its slice`);
|
|
38365
|
+
slices.push({
|
|
38366
|
+
name,
|
|
38367
|
+
enteredAt: position.hash,
|
|
38368
|
+
leftAt: void 0
|
|
38369
|
+
});
|
|
38370
|
+
},
|
|
38371
|
+
leaveSlice: () => {
|
|
38372
|
+
if (outcome !== void 0) return;
|
|
38373
|
+
const open = slices.find((s) => s.leftAt === void 0);
|
|
38374
|
+
if (open !== void 0) open.leftAt = position.hash;
|
|
38375
|
+
},
|
|
38290
38376
|
read: (path) => position.tree.read(path),
|
|
38291
38377
|
glob: (pattern) => position.tree.paths().filter((path) => globMatches(path, pattern)),
|
|
38292
38378
|
changes: () => changesBetween(previousPosition.tree, position.tree),
|
|
@@ -39678,22 +39764,14 @@ const runAtTip = (git, def, tip) => gen(function* () {
|
|
|
39678
39764
|
if (closing.length === 0) closing = yield* git.commitHistory(void 0, tip.hash);
|
|
39679
39765
|
return yield* runOf([...before, ...closing], { ...location }, tip.hash);
|
|
39680
39766
|
});
|
|
39681
|
-
|
|
39682
|
-
const summaryRun = gen(function* () {
|
|
39683
|
-
const git = yield* GitService;
|
|
39684
|
-
const def = (yield* (yield* ConfigService).load).workflow;
|
|
39767
|
+
const runAtHead = (git, def) => gen(function* () {
|
|
39685
39768
|
return yield* runAtTip(git, def, (yield* git.subjectHistory(1, 0))[0]);
|
|
39686
39769
|
});
|
|
39687
|
-
/** The process
|
|
39688
|
-
const
|
|
39770
|
+
/** The process HEAD closes or sits inside — `gtd summary`'s run. */
|
|
39771
|
+
const summaryRun = gen(function* () {
|
|
39689
39772
|
const git = yield* GitService;
|
|
39690
39773
|
const def = (yield* (yield* ConfigService).load).workflow;
|
|
39691
|
-
|
|
39692
|
-
const subject = yield* git.lastCommitSubject(hash);
|
|
39693
|
-
return yield* runAtTip(git, def, {
|
|
39694
|
-
hash,
|
|
39695
|
-
subject
|
|
39696
|
-
});
|
|
39774
|
+
return yield* runAtHead(git, def);
|
|
39697
39775
|
});
|
|
39698
39776
|
const commitTree = (workspace, hash) => {
|
|
39699
39777
|
let entries;
|
|
@@ -39976,6 +40054,10 @@ const pendingChanges = (git, withIndex) => gen(function* () {
|
|
|
39976
40054
|
const seen = new Set(worktree.map((e) => e.path));
|
|
39977
40055
|
return [...worktree, ...(yield* git.stagedPaths()).filter((e) => !seen.has(e.path))];
|
|
39978
40056
|
});
|
|
40057
|
+
const sliceRangesOf = (slices, run) => sliceRanges(slices, {
|
|
40058
|
+
base: run.startParentHash,
|
|
40059
|
+
tip: run.trace.at(-1)?.hash ?? run.startParentHash
|
|
40060
|
+
});
|
|
39979
40061
|
/** Resolve the rest at `ref`, or at HEAD when `ref` is `undefined`. */
|
|
39980
40062
|
const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
39981
40063
|
const git = yield* GitService;
|
|
@@ -40023,6 +40105,49 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
40023
40105
|
});
|
|
40024
40106
|
const currentRest = restAt(void 0);
|
|
40025
40107
|
/**
|
|
40108
|
+
* The slices of the process HEAD closes or sits inside, current episode only,
|
|
40109
|
+
* stack order. Reads like `historyTrail`, never `restAt`: it narrates and
|
|
40110
|
+
* writes nothing, and returns a version warning for a closed run.
|
|
40111
|
+
*/
|
|
40112
|
+
const processSlices = gen(function* () {
|
|
40113
|
+
const git = yield* GitService;
|
|
40114
|
+
const config = yield* (yield* ConfigService).load;
|
|
40115
|
+
const workspace = yield* Workspace;
|
|
40116
|
+
const host = yield* Host;
|
|
40117
|
+
const run = yield* runAtHead(git, config.workflow);
|
|
40118
|
+
if (run.trace.length === 0) return yield* fail$7(/* @__PURE__ */ new Error("gtd slices: refused — HEAD neither closes nor sits inside a process"));
|
|
40119
|
+
const closing = run.closingHash;
|
|
40120
|
+
let outcome;
|
|
40121
|
+
let versionWarning;
|
|
40122
|
+
if (closing !== void 0) {
|
|
40123
|
+
const replayed = yield* replayFinishedProcess(config, run, closing, {
|
|
40124
|
+
command: "gtd slices",
|
|
40125
|
+
configReason: "HEAD's process replays under today's config; revert or stash the edit to list its slices",
|
|
40126
|
+
notEnded: (o) => slicesDivergence(run, o)
|
|
40127
|
+
});
|
|
40128
|
+
outcome = replayed.outcome;
|
|
40129
|
+
versionWarning = replayed.versionWarning;
|
|
40130
|
+
} else {
|
|
40131
|
+
const { setup } = yield* setupFor(config, run, workspace, host);
|
|
40132
|
+
outcome = yield* promise(() => replayFor(setup));
|
|
40133
|
+
if (outcome.kind !== "rest") return yield* fail$7(slicesDivergence(run, outcome));
|
|
40134
|
+
}
|
|
40135
|
+
return {
|
|
40136
|
+
slices: sliceRangesOf(outcome.slices, run).map(({ name, base, tip, open }) => ({
|
|
40137
|
+
name,
|
|
40138
|
+
base,
|
|
40139
|
+
tip,
|
|
40140
|
+
closed: closing !== void 0 || !open
|
|
40141
|
+
})),
|
|
40142
|
+
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
40143
|
+
};
|
|
40144
|
+
});
|
|
40145
|
+
/** Every `gtd slices` refusal reads `gtd slices: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
|
|
40146
|
+
const slicesDivergence = (run, outcome) => {
|
|
40147
|
+
const reason = outcome.kind === "rest" && run.closingHash !== void 0 ? `${run.closingHash.slice(0, 7)} closes a process that does not end under today's config` : replayError(outcome)?.message.replace(/^gtd: /, "") ?? "replay failed";
|
|
40148
|
+
return /* @__PURE__ */ new Error(`gtd slices: refused — ${reason}`);
|
|
40149
|
+
};
|
|
40150
|
+
/**
|
|
40026
40151
|
* Whether `gtd --workflow <name>` can start a process here: the workflow must
|
|
40027
40152
|
* reach a step from the tree the opening commit would capture. That step is
|
|
40028
40153
|
* what the opening commit's subject names.
|
|
@@ -40256,7 +40381,7 @@ const inRepoConfigPaths = (root, files, recorded) => {
|
|
|
40256
40381
|
for (const file of files) paths.add(relative(root, resolve(root, file)).split(sep).join("/"));
|
|
40257
40382
|
return [...paths].filter((p) => p !== "" && !p.startsWith("..") && !isAbsolute(p)).sort();
|
|
40258
40383
|
};
|
|
40259
|
-
const configRefusal = (tip, paths) => /* @__PURE__ */ new Error(
|
|
40384
|
+
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}`);
|
|
40260
40385
|
const differingConfigPaths = (git, workspace, root, config, run, hash) => gen(function* () {
|
|
40261
40386
|
const differing = [];
|
|
40262
40387
|
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);
|
|
@@ -40287,22 +40412,19 @@ const versionWarningFor = (recorded) => recorded === GTD_VERSION ? void 0 : `gtd
|
|
|
40287
40412
|
const historyTrail = (tip) => gen(function* () {
|
|
40288
40413
|
const git = yield* GitService;
|
|
40289
40414
|
const config = yield* (yield* ConfigService).load;
|
|
40290
|
-
const workspace = yield* Workspace;
|
|
40291
|
-
const host = yield* Host;
|
|
40292
40415
|
const hash = yield* git.resolveRef(tip);
|
|
40293
|
-
const
|
|
40294
|
-
const
|
|
40295
|
-
|
|
40296
|
-
|
|
40297
|
-
|
|
40298
|
-
const outcome = yield*
|
|
40299
|
-
|
|
40300
|
-
|
|
40301
|
-
|
|
40302
|
-
const versionWarning = versionWarningFor(recorded);
|
|
40416
|
+
const subject = yield* git.lastCommitSubject(hash);
|
|
40417
|
+
const run = yield* runAtTip(git, config.workflow, {
|
|
40418
|
+
hash,
|
|
40419
|
+
subject
|
|
40420
|
+
});
|
|
40421
|
+
const { outcome, recorded, versionWarning } = yield* replayFinishedProcess(config, run, hash, {
|
|
40422
|
+
command: "gtd history",
|
|
40423
|
+
notEnded: (o) => notClosedError(hash, o)
|
|
40424
|
+
});
|
|
40303
40425
|
return {
|
|
40304
40426
|
tip: hash,
|
|
40305
|
-
steps,
|
|
40427
|
+
steps: historySteps(run),
|
|
40306
40428
|
ended: outcome.via,
|
|
40307
40429
|
version: {
|
|
40308
40430
|
recorded,
|
|
@@ -40311,6 +40433,24 @@ const historyTrail = (tip) => gen(function* () {
|
|
|
40311
40433
|
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
40312
40434
|
};
|
|
40313
40435
|
});
|
|
40436
|
+
/** Refuses on config drift before replaying, so a stale config never runs the flow. */
|
|
40437
|
+
const replayFinishedProcess = (config, run, hash, how) => gen(function* () {
|
|
40438
|
+
const git = yield* GitService;
|
|
40439
|
+
const workspace = yield* Workspace;
|
|
40440
|
+
const host = yield* Host;
|
|
40441
|
+
const differing = yield* differingConfigPaths(git, workspace, host.root, config, run, hash);
|
|
40442
|
+
if (differing.length > 0) return yield* fail$7(configRefusal(how.command, hash, differing, how.configReason));
|
|
40443
|
+
const { setup } = yield* setupFor(config, run, workspace, host);
|
|
40444
|
+
const outcome = yield* promise(() => replayFor(setup));
|
|
40445
|
+
if (outcome.kind !== "ended") return yield* fail$7(how.notEnded(outcome));
|
|
40446
|
+
const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
|
|
40447
|
+
const versionWarning = versionWarningFor(recorded);
|
|
40448
|
+
return {
|
|
40449
|
+
outcome,
|
|
40450
|
+
recorded,
|
|
40451
|
+
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
40452
|
+
};
|
|
40453
|
+
});
|
|
40314
40454
|
//#endregion
|
|
40315
40455
|
//#region src/emit/GitScript.ts
|
|
40316
40456
|
const shellQuote$1 = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
@@ -41183,6 +41323,17 @@ const runDoorsCommand = (json, out) => gen(function* () {
|
|
|
41183
41323
|
if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
|
|
41184
41324
|
else yield* writeSelection(out, document, json.path);
|
|
41185
41325
|
});
|
|
41326
|
+
const runSlicesCommand = (json, out) => gen(function* () {
|
|
41327
|
+
const { slices, versionWarning } = yield* processSlices;
|
|
41328
|
+
if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
|
|
41329
|
+
if (json.kind === "off") {
|
|
41330
|
+
out.write(slices.map((s) => `${s.name} ${s.base}..${s.tip}\n`).join(""));
|
|
41331
|
+
return;
|
|
41332
|
+
}
|
|
41333
|
+
const document = slicesDocument(slices);
|
|
41334
|
+
if (json.kind === "document") out.write(renderSlicesJson(document));
|
|
41335
|
+
else yield* writeSelection(out, document, json.path);
|
|
41336
|
+
});
|
|
41186
41337
|
/**
|
|
41187
41338
|
* Ends the process underway without completing it. Nothing is discarded: a
|
|
41188
41339
|
* `git reset --mixed` to the process's start commit leaves every turn
|
|
@@ -41518,6 +41669,7 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
41518
41669
|
case "start": return runStartCommand(command.actor, command.workflow, command.vars, out, command.label);
|
|
41519
41670
|
case "door": return runDoorCommand(command.name, command.args, out);
|
|
41520
41671
|
case "doors": return runDoorsCommand(json, out);
|
|
41672
|
+
case "slices": return runSlicesCommand(json, out);
|
|
41521
41673
|
case "abandon": return runAbandonCommand(out);
|
|
41522
41674
|
case "restore": return runRestoreCommand(out);
|
|
41523
41675
|
case "next": return runNextCommand(json, out);
|
|
@@ -41576,12 +41728,12 @@ const FLAGS = [
|
|
|
41576
41728
|
name: "--json",
|
|
41577
41729
|
arity: "optional",
|
|
41578
41730
|
repeatable: false,
|
|
41579
|
-
scope: (kind) => kind === "next" || kind === "land" || kind === "judge" || kind === "judgeAnswer" || kind === "doors" || kind === "historyList" || kind === "historyShow" || kind === "strays",
|
|
41731
|
+
scope: (kind) => kind === "next" || kind === "land" || kind === "judge" || kind === "judgeAnswer" || kind === "doors" || kind === "slices" || kind === "historyList" || kind === "historyShow" || kind === "strays",
|
|
41580
41732
|
decode: ([raw]) => right(raw ?? ""),
|
|
41581
|
-
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd history list`/`gtd history show`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
41733
|
+
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd slices`/`gtd history list`/`gtd history show`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
41582
41734
|
valueHint: "<path>",
|
|
41583
41735
|
help: [
|
|
41584
|
-
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
|
|
41736
|
+
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd slices/gtd history",
|
|
41585
41737
|
"list/gtd history show/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
41586
41738
|
"--json=<path> (a dotted key path into that document, e.g.",
|
|
41587
41739
|
"kind, content, session.id) prints just that value: a",
|
|
@@ -42051,6 +42203,25 @@ const COMMAND_ROWS = [
|
|
|
42051
42203
|
"value off it. Writes nothing"
|
|
42052
42204
|
]
|
|
42053
42205
|
},
|
|
42206
|
+
{
|
|
42207
|
+
token: "slices",
|
|
42208
|
+
kind: "slices",
|
|
42209
|
+
arity: "none",
|
|
42210
|
+
details: [
|
|
42211
|
+
"List the slices of the process HEAD closes or sits inside,",
|
|
42212
|
+
"first slice first: one `name base..tip` line each (full",
|
|
42213
|
+
"hashes; plain output carries no promise). --json prints",
|
|
42214
|
+
"{ schema: 1, slices: [{ name, base, tip, closed }] };",
|
|
42215
|
+
"--json=<path> reads one value off it. Writes nothing: no",
|
|
42216
|
+
"git, no state transition, no session identity. When HEAD",
|
|
42217
|
+
"closes a process, every slice is listed as closed; a gtd",
|
|
42218
|
+
"version mismatch only warns on stderr. Refuses (exit 1)",
|
|
42219
|
+
"when HEAD neither closes nor sits inside a process, or when",
|
|
42220
|
+
"today's config disagrees with history about the process:",
|
|
42221
|
+
"HEAD closes it and its config files differ from the working",
|
|
42222
|
+
"tree, or the replay diverged"
|
|
42223
|
+
]
|
|
42224
|
+
},
|
|
42054
42225
|
{
|
|
42055
42226
|
token: "judge",
|
|
42056
42227
|
kind: "judge",
|