@tacuchi/agent-workflow-cli 21.8.0 → 21.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.
Files changed (60) hide show
  1. package/dist/adapters/git-cli.js +217 -0
  2. package/dist/adapters/git-cli.js.map +1 -1
  3. package/dist/application/flow/flow-service.js +7 -0
  4. package/dist/application/flow/flow-service.js.map +1 -1
  5. package/dist/application/flow/internal-actions.js +5 -0
  6. package/dist/application/flow/internal-actions.js.map +1 -1
  7. package/dist/application/history-table.js +37 -0
  8. package/dist/application/history-table.js.map +1 -1
  9. package/dist/application/local-proposal.js +37 -0
  10. package/dist/application/local-proposal.js.map +1 -1
  11. package/dist/application/retirement/apply.js +448 -0
  12. package/dist/application/retirement/apply.js.map +1 -0
  13. package/dist/application/retirement/attribution.js +395 -0
  14. package/dist/application/retirement/attribution.js.map +1 -0
  15. package/dist/application/retirement/graph.js +196 -0
  16. package/dist/application/retirement/graph.js.map +1 -0
  17. package/dist/application/retirement/history-events.js +158 -0
  18. package/dist/application/retirement/history-events.js.map +1 -0
  19. package/dist/application/retirement/journal.js +113 -0
  20. package/dist/application/retirement/journal.js.map +1 -0
  21. package/dist/application/retirement/prepare.js +233 -0
  22. package/dist/application/retirement/prepare.js.map +1 -0
  23. package/dist/application/retirement/preview.js +101 -0
  24. package/dist/application/retirement/preview.js.map +1 -0
  25. package/dist/application/retirement/resolve.js +356 -0
  26. package/dist/application/retirement/resolve.js.map +1 -0
  27. package/dist/application/session-create-service.js +103 -12
  28. package/dist/application/session-create-service.js.map +1 -1
  29. package/dist/application/session-custody-recorder.js +137 -0
  30. package/dist/application/session-custody-recorder.js.map +1 -0
  31. package/dist/application/session-custody-service.js +171 -0
  32. package/dist/application/session-custody-service.js.map +1 -0
  33. package/dist/application/status-service.js +5 -0
  34. package/dist/application/status-service.js.map +1 -1
  35. package/dist/application/workline-index-service.js +31 -0
  36. package/dist/application/workline-index-service.js.map +1 -1
  37. package/dist/application/worktree-service.js +47 -1
  38. package/dist/application/worktree-service.js.map +1 -1
  39. package/dist/cli/commands/index.js +6 -0
  40. package/dist/cli/commands/index.js.map +1 -1
  41. package/dist/cli/commands/retirement.js +137 -0
  42. package/dist/cli/commands/retirement.js.map +1 -0
  43. package/dist/cli/commands/session-create.js +4 -1
  44. package/dist/cli/commands/session-create.js.map +1 -1
  45. package/dist/cli/help-groups.js +4 -0
  46. package/dist/cli/help-groups.js.map +1 -1
  47. package/dist/domain/flow/authority.js +8 -0
  48. package/dist/domain/flow/authority.js.map +1 -1
  49. package/dist/domain/retirement/proposal.js +83 -0
  50. package/dist/domain/retirement/proposal.js.map +1 -0
  51. package/dist/domain/retirement/selector.js +109 -0
  52. package/dist/domain/retirement/selector.js.map +1 -0
  53. package/dist/domain/session/custody.js +176 -0
  54. package/dist/domain/session/custody.js.map +1 -0
  55. package/dist/domain/workline-node.js +56 -0
  56. package/dist/domain/workline-node.js.map +1 -0
  57. package/package.json +1 -1
  58. package/skills/w/commands/discard.md +49 -0
  59. package/skills/w/commands/reset.md +50 -0
  60. package/skills/w/context/MANIFEST.json +147 -139
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Which git effects a retired session may take with it — and which it may not.
3
+ *
4
+ * Attribution never reads a commit message, an author or a timestamp. It reads
5
+ * two things: the session's sealed baseline, and the receipts of what it did. That
6
+ * is the whole difference between "these changes are probably ours" and "these
7
+ * changes are ours", and only the second one may be discarded.
8
+ *
9
+ * Two shapes of ownership, and the rule follows the shape rather than the other
10
+ * way round:
11
+ *
12
+ * - **An exclusive unit.** The session works in its own worktree on its own
13
+ * branch. Everything uncommitted there is its own by construction, and every
14
+ * commit its receipts name is too.
15
+ * - **A shared checkout.** Anything already dirty at baseline belongs to somebody
16
+ * else, so only paths that are unambiguously the session's can be dropped — and
17
+ * when even one of them is not, the operation refuses rather than picking.
18
+ *
19
+ * Everything the reverts need is REHEARSED before anybody is asked: the commit is
20
+ * built in a temporary detached worktree, so "this would conflict" is an answer the
21
+ * preview can carry instead of a surprise the apply discovers. And the whole git
22
+ * side converges on ONE commit point, because two refs cannot be moved as one
23
+ * operation — a scope that would need two is refused while it is still a proposal.
24
+ */
25
+ import { join } from "node:path";
26
+ import { attributableCommits } from "../../domain/session/custody.js";
27
+ /**
28
+ * What one session's git footprint is, per source it recorded.
29
+ *
30
+ * A source whose repository cannot be read at all becomes a BLOCK and never an
31
+ * empty answer: "no changes to discard" and "we could not look" are the same
32
+ * output only if nobody cares which of the two it was.
33
+ */
34
+ export async function attributeGitEffects(git, custody, options) {
35
+ const dirty = [];
36
+ const reverts = [];
37
+ const blocks = [];
38
+ const publications = [];
39
+ for (const source of custody.sources) {
40
+ const tree = source.unit_path ?? source.path;
41
+ // The operation state comes FIRST: a repository in the middle of something has
42
+ // an index and a HEAD mid-transition, so nothing read from it is a baseline —
43
+ // including the changes below. Reading them first and then discarding them
44
+ // would be computing an attribution from a state we already decided not to
45
+ // trust.
46
+ const state = await git.operationState(tree);
47
+ if (state !== "clean") {
48
+ blocks.push({
49
+ alias: source.alias,
50
+ reason: `'${source.alias}' tiene una operación git en curso (${state})`,
51
+ contested: [],
52
+ action: `terminá o abortá ese ${state} y reintentá: no se prepara un retiro sobre un índice a medio camino`,
53
+ });
54
+ continue;
55
+ }
56
+ let changes;
57
+ try {
58
+ changes = await git.localChanges(tree);
59
+ }
60
+ catch (err) {
61
+ blocks.push({
62
+ alias: source.alias,
63
+ reason: `no se pudo leer el estado de '${tree}': ${message(err)}`,
64
+ contested: [],
65
+ action: "verificá que la fuente siga siendo un repositorio legible y reintentá",
66
+ });
67
+ continue;
68
+ }
69
+ const change = dirtyChangeOf(source, tree, changes, blocks);
70
+ if (change !== null)
71
+ dirty.push(change);
72
+ const commits = attributableCommits(custody, source.alias);
73
+ if (commits.length === 0)
74
+ continue;
75
+ const prepared = await prepareReverts(git, source, tree, custody, commits, options, blocks);
76
+ if (prepared === null)
77
+ continue;
78
+ reverts.push(...prepared.reverts);
79
+ publications.push(prepared.publication);
80
+ }
81
+ // ONE commit point or none. Two refs cannot be swapped as a single operation,
82
+ // and compensating the second with another commit would change history somebody
83
+ // can already see — so the topology is refused here, before anything mutates.
84
+ if (publications.length > 1) {
85
+ blocks.push({
86
+ alias: publications.map((p) => p.alias).join(", "),
87
+ reason: `el alcance tocaría ${publications.length} unidades de publicación Git y no hay un único punto de commit que las cubra`,
88
+ contested: publications.map((p) => `${p.alias}:${p.ref}`),
89
+ action: "retirá por separado el trabajo de cada fuente: ampliar esa topología necesita otro contrato, no una limpieza parcial",
90
+ });
91
+ }
92
+ return {
93
+ dirty,
94
+ reverts,
95
+ publication: publications.length === 1 ? publications[0] : null,
96
+ blocks,
97
+ };
98
+ }
99
+ /**
100
+ * The uncommitted delta this session may drop, or a block.
101
+ *
102
+ * In an exclusive unit the delta IS the session's — every shape of it: a rename is
103
+ * two paths and both go, a deletion is restored, an untracked file is removed, a
104
+ * mode flip and a symlink are changes like any other. In a shared checkout the
105
+ * paths that were already dirty at baseline are somebody else's, and a path that
106
+ * carries both authors' work cannot be separated by any per-file decision.
107
+ */
108
+ function dirtyChangeOf(source, tree, changes, blocks) {
109
+ if (changes.length === 0)
110
+ return null;
111
+ // A rename is two paths in the working tree, and dropping one of them would
112
+ // leave the other half applied.
113
+ const paths = [
114
+ ...new Set(changes.flatMap((c) => (c.from === null ? [c.path] : [c.path, c.from]))),
115
+ ]
116
+ .sort()
117
+ .filter((p) => p.length > 0);
118
+ if (source.unit_path !== null) {
119
+ return {
120
+ alias: source.alias,
121
+ tree,
122
+ paths,
123
+ baseline: source.baseline_head,
124
+ exclusive_unit: true,
125
+ };
126
+ }
127
+ const contested = paths.filter((path) => source.dirty_paths.includes(path));
128
+ if (contested.length > 0) {
129
+ blocks.push({
130
+ alias: source.alias,
131
+ reason: `en el checkout compartido de '${source.alias}' hay cambios que ya estaban sin commitear antes de esta sesión`,
132
+ contested,
133
+ action: "commiteá o guardá esos cambios ajenos y reintentá: el retiro no descarta un archivo que contiene trabajo de dos",
134
+ });
135
+ return null;
136
+ }
137
+ return {
138
+ alias: source.alias,
139
+ tree,
140
+ paths,
141
+ baseline: source.baseline_head,
142
+ exclusive_unit: false,
143
+ };
144
+ }
145
+ /**
146
+ * The reverts for one source, rehearsed, plus the single ref they land on.
147
+ *
148
+ * Order is reverse topology and it is material: undoing an ancestor before the
149
+ * descendant that builds on it conflicts. It comes from `isAncestor` and not from
150
+ * the order the receipts happen to be in, because a session can commit on top of
151
+ * work it did not do.
152
+ */
153
+ async function prepareReverts(git, source,
154
+ /** The working tree that has the moving ref checked out. */
155
+ tree, custody, commits, options, blocks) {
156
+ const repo = source.path;
157
+ const ref = refOf(source);
158
+ const expectedOld = await git.refValue(repo, ref);
159
+ if (expectedOld === null) {
160
+ blocks.push({
161
+ alias: source.alias,
162
+ reason: `'${ref}' no existe en ${source.alias}: no hay punto de commit al que aplicar los reverts`,
163
+ contested: [ref],
164
+ action: "revisá la rama de la unidad de esta sesión antes de retirar sus commits",
165
+ });
166
+ return null;
167
+ }
168
+ const ordered = await reverseTopology(git, repo, commits);
169
+ const external = await externalDescendants(git, repo, ordered, expectedOld);
170
+ if (external.length > 0) {
171
+ blocks.push({
172
+ alias: source.alias,
173
+ reason: `hay commits que dependen de los de esta sesión y quedan fuera del alcance en ${source.alias}`,
174
+ contested: external,
175
+ action: "esos commits no son de esta sesión: revertí o retirá primero ese trabajo, o dejá el alcance como está",
176
+ });
177
+ return null;
178
+ }
179
+ const rehearsal = await rehearse(git, repo, tree, ref, expectedOld, ordered, custody, source, options);
180
+ if ("reason" in rehearsal) {
181
+ blocks.push({ alias: source.alias, ...rehearsal });
182
+ return null;
183
+ }
184
+ return rehearsal;
185
+ }
186
+ /** The ref a session's commits live on: its unit's branch, else the source's. */
187
+ function refOf(source) {
188
+ return `refs/heads/${source.unit_branch ?? source.branch}`;
189
+ }
190
+ async function reverseTopology(git, repo, commits) {
191
+ const ordered = [...commits];
192
+ // A small insertion sort over `isAncestor`: the sets here are a session's own
193
+ // commits, so this is a handful of comparisons and not a graph walk.
194
+ const isBefore = new Map();
195
+ for (const a of ordered) {
196
+ const set = new Set();
197
+ for (const b of ordered) {
198
+ if (a !== b && (await git.isAncestor(repo, a, b)))
199
+ set.add(b);
200
+ }
201
+ isBefore.set(a, set);
202
+ }
203
+ // Descendants first: `a` comes before `b` when `b` is an ancestor of `a`.
204
+ return ordered.sort((a, b) => {
205
+ if (isBefore.get(b)?.has(a) === true)
206
+ return -1;
207
+ if (isBefore.get(a)?.has(b) === true)
208
+ return 1;
209
+ return a < b ? -1 : 1;
210
+ });
211
+ }
212
+ /**
213
+ * Commits that descend from what we are reverting and are NOT ours.
214
+ *
215
+ * Reverting under them is legitimate git — the revert is a new commit on top — but
216
+ * it silently undoes the ground somebody else's work stands on. So it blocks, and
217
+ * it names them.
218
+ */
219
+ async function externalDescendants(git, repo, commits, tip) {
220
+ const ours = new Set(commits);
221
+ const found = [];
222
+ for (const commit of commits) {
223
+ if (commit === tip || ours.has(tip))
224
+ continue;
225
+ // The tip descends from one of ours and is not one of ours: whatever sits
226
+ // between them belongs to somebody else.
227
+ if (await git.isAncestor(repo, commit, tip))
228
+ found.push(tip);
229
+ }
230
+ return [...new Set(found)];
231
+ }
232
+ /**
233
+ * Build the revert commits for real, in a throwaway detached worktree.
234
+ *
235
+ * Detached and temporary because a rehearsal must not be able to move a ref or
236
+ * leave a branch behind, and REAL because a simulation that only guessed whether
237
+ * the revert applies would be exactly the unverified claim the seal exists to
238
+ * prevent. What comes back is the tip the commit point will publish and the tree
239
+ * it must produce.
240
+ */
241
+ async function rehearse(git, repo,
242
+ /** Where the moving ref is checked out: the tree the result has to land in. */
243
+ tree, ref, expectedOld, ordered, custody, source, options) {
244
+ const worktree = join(options.scratchDir, `rehearsal-${options.opId}-${source.alias}`);
245
+ try {
246
+ await git.worktreeAddDetached(repo, worktree, expectedOld);
247
+ }
248
+ catch (err) {
249
+ return {
250
+ reason: `no se pudo preparar un árbol temporal para ensayar los reverts: ${message(err)}`,
251
+ contested: [],
252
+ action: "liberá espacio o revisá el repositorio y reintentá; nada se aplicó",
253
+ };
254
+ }
255
+ try {
256
+ const reverts = [];
257
+ for (const commit of ordered) {
258
+ const one = await rehearseOne(git, repo, ref, worktree, commit, custody, source);
259
+ if ("reason" in one)
260
+ return one;
261
+ reverts.push(one.revert);
262
+ }
263
+ const preparedTip = await git.refValue(worktree, "HEAD");
264
+ if (preparedTip === null || reverts.length === 0) {
265
+ return {
266
+ reason: `el ensayo de los reverts de ${source.alias} no produjo un commit`,
267
+ contested: [],
268
+ action: "revisá los receipts de la sesión: sin un tip preparado no hay punto de commit",
269
+ };
270
+ }
271
+ // THE precondition the probe found: the compare-and-swap moves the ref and not
272
+ // the working tree, so if syncing that tree can refuse, it has to refuse now.
273
+ // After the swap there is no way back that is not a rewrite.
274
+ //
275
+ // Asked against the tree that HAS the moving ref checked out — which for a
276
+ // session with its own unit is the unit, not the source's main checkout. Asking
277
+ // the wrong tree is the same as not asking: the main checkout sits on another
278
+ // branch and would answer yes to a question about somebody else's tree.
279
+ const syncable = await git.canSyncTree(tree, preparedTip);
280
+ if (!syncable.ok) {
281
+ return {
282
+ reason: `el árbol de trabajo de ${source.alias} no se puede llevar al resultado sin pisar cambios locales`,
283
+ contested: [],
284
+ action: "commiteá o guardá esos cambios y reintentá: la sincronización se comprueba antes del punto de commit, nunca después",
285
+ };
286
+ }
287
+ // The half the dry run cannot see: a path the result would CREATE where an
288
+ // untracked file already sits. `read-tree -n` has no working tree to look at,
289
+ // and the version that does (`-u`) would be performing the switch — which
290
+ // after the commit point is exactly the partial success this precondition
291
+ // exists to prevent.
292
+ const collisions = await untrackedCollisions(git, tree, preparedTip);
293
+ if (collisions.length > 0) {
294
+ return {
295
+ reason: `en ${source.alias} hay archivos sin trackear donde el resultado crearía los suyos`,
296
+ contested: collisions,
297
+ action: "movelos, borralos o commitealos y reintentá: el retiro no sobrescribe un archivo que nadie versionó",
298
+ };
299
+ }
300
+ const expectedTree = await git.treeOf(worktree, preparedTip);
301
+ if (expectedTree === null) {
302
+ return {
303
+ reason: `no se pudo leer el árbol resultante del ensayo en ${source.alias}`,
304
+ contested: [],
305
+ action: "revisá el repositorio y volvé a preparar; nada se aplicó",
306
+ };
307
+ }
308
+ return {
309
+ reverts,
310
+ publication: {
311
+ alias: source.alias,
312
+ repo,
313
+ ref,
314
+ expected_old: expectedOld,
315
+ expected_tree: expectedTree,
316
+ revert_count: reverts.length,
317
+ },
318
+ };
319
+ }
320
+ finally {
321
+ // The rehearsal leaves nothing behind, whichever way it went. Its commits stay
322
+ // reachable through the private ref the coordinator sets, never through a tree.
323
+ try {
324
+ await git.worktreeRemove(repo, worktree);
325
+ }
326
+ catch {
327
+ // Best-effort: a tree that cannot be removed is reported by `worktree list`
328
+ // as an orphan, which is a visible state and not a silent one.
329
+ }
330
+ }
331
+ }
332
+ /**
333
+ * One commit's revert, rehearsed and committed in the temporary tree.
334
+ *
335
+ * Two ways it can refuse and they mean different things: a conflict is work that
336
+ * cannot be undone cleanly, and an EMPTY result means the commit's effect is not
337
+ * in this branch at all — a receipt pointing at an abandoned side branch. Both
338
+ * stop the operation, and saying which one it was is what makes the refusal
339
+ * actionable.
340
+ */
341
+ async function rehearseOne(git, repo, ref, worktree, commit, custody, source) {
342
+ const receipt = receiptFor(custody, source.alias, commit);
343
+ const parents = receipt?.parents ?? [];
344
+ // A merge needs its mainline, and the recorded parents are what name it: the
345
+ // first parent is the side the branch was on, so that is the one kept.
346
+ const mainline = parents.length > 1 ? 1 : null;
347
+ const attempt = await git.rehearseRevert(worktree, commit, mainline);
348
+ if (!attempt.ok) {
349
+ return {
350
+ reason: `el revert de ${commit.slice(0, 12)} no aplica limpio en ${source.alias}`,
351
+ contested: attempt.conflicted.length > 0 ? attempt.conflicted : [commit],
352
+ action: "resolvé ese conflicto por fuera o dejá el alcance como está: el retiro no commitea un revert en conflicto",
353
+ };
354
+ }
355
+ try {
356
+ await git.commitIn(worktree, `revert ${commit.slice(0, 12)} (aw retiro)`);
357
+ }
358
+ catch {
359
+ return {
360
+ reason: `el revert de ${commit.slice(0, 12)} no aplica limpio en ${source.alias}: su efecto no está en ${ref}`,
361
+ contested: [commit],
362
+ action: "el receipt apunta a una rama que ya no contiene ese commit: no commitea un revert en conflicto ni uno vacío; revisá la unidad de esa sesión",
363
+ };
364
+ }
365
+ const refs = await git.refsContaining(repo, commit);
366
+ return {
367
+ revert: {
368
+ alias: source.alias,
369
+ commit,
370
+ parents,
371
+ ref,
372
+ // Published means "some remote already has it": the revert stays a new local
373
+ // commit either way, and the result declares the push as somebody else's step.
374
+ published: refs.some((name) => name.startsWith("refs/remotes/")),
375
+ mainline,
376
+ },
377
+ };
378
+ }
379
+ /** Untracked paths the result's tree would overwrite. Empty is the normal case. */
380
+ async function untrackedCollisions(git, tree, preparedTip) {
381
+ const untracked = (await git.localChanges(tree)).filter((c) => c.untracked).map((c) => c.path);
382
+ if (untracked.length === 0)
383
+ return [];
384
+ const inResult = new Set(await git.treePaths(tree, preparedTip));
385
+ return untracked.filter((path) => inResult.has(path)).sort();
386
+ }
387
+ function receiptFor(custody, alias, commit) {
388
+ return custody.effects.find((e) => e.alias === alias &&
389
+ e.after === commit &&
390
+ (e.kind === "commit" || e.kind === "unit_integrated"));
391
+ }
392
+ function message(err) {
393
+ return err instanceof Error ? err.message : String(err);
394
+ }
395
+ //# sourceMappingURL=attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.js","sourceRoot":"","sources":["../../../src/application/retirement/attribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * The provenance and ownership graph a retirement reads before it deletes
3
+ * anything.
4
+ *
5
+ * Three readings feed it and each one carries its own strength, which is the
6
+ * whole point of building a graph rather than a list: a plan's `Derived from`
7
+ * line and a session's sealed custody are PROVABLE, while a session's free-text
8
+ * `## Origin` is a hint. A closure computed over hints would delete work whose
9
+ * ownership nobody can demonstrate, so the hint is recorded as such and it is what
10
+ * makes the operation refuse instead of guessing.
11
+ *
12
+ * Nothing here decides anything. It answers "who descends from whom, and how do
13
+ * we know" — the resolver is what turns that into a closure, and the two are kept
14
+ * apart so the evidence can be inspected on its own.
15
+ */
16
+ import { join, relative } from "node:path";
17
+ import { custodyCompleteness } from "../../domain/session/custody.js";
18
+ import { formatNodeId, isProvable, nodeFromDocPath, } from "../../domain/workline-node.js";
19
+ import { locateRun, readRun } from "../flow/run-state-service.js";
20
+ import { readCustody } from "../session-custody-service.js";
21
+ import { buildWorklineIndex, } from "../workline-index-service.js";
22
+ export class RetirementGraph {
23
+ nodes = new Map();
24
+ edges = [];
25
+ add(node) {
26
+ this.nodes.set(formatNodeId(node.id), node);
27
+ }
28
+ addEdge(from, to, evidence) {
29
+ this.edges.push({ from, to, evidence });
30
+ }
31
+ get(id) {
32
+ return this.nodes.get(formatNodeId(id));
33
+ }
34
+ all() {
35
+ return [...this.nodes.values()];
36
+ }
37
+ /** Nodes that declare `id` as a parent, whatever the evidence. */
38
+ childrenOf(id) {
39
+ const key = formatNodeId(id);
40
+ return this.edges.filter((e) => formatNodeId(e.to) === key);
41
+ }
42
+ /** What `id` declares it descends from. */
43
+ parentsOf(id) {
44
+ const key = formatNodeId(id);
45
+ return this.edges.filter((e) => formatNodeId(e.from) === key);
46
+ }
47
+ /** Every session whose custody or origin points at this node. */
48
+ sessionsOf(id) {
49
+ return this.childrenOf(id)
50
+ .map((e) => this.get(e.from))
51
+ .filter((n) => n !== undefined && n.kind === "session");
52
+ }
53
+ /** Edges nobody can prove — the ones a destructive closure must refuse. */
54
+ unprovableInto(id) {
55
+ return this.childrenOf(id).filter((e) => !isProvable(e.evidence));
56
+ }
57
+ }
58
+ export async function buildRetirementGraph(deps) {
59
+ const index = await buildWorklineIndex(deps.fs, deps.env, deps.paths, {
60
+ ...(deps.git !== undefined ? { git: deps.git } : {}),
61
+ });
62
+ const graph = new RetirementGraph();
63
+ const root = deps.paths.workspaceDir();
64
+ for (const spec of index.specs)
65
+ addDoc(graph, root, "spec", spec);
66
+ for (const plan of index.plans)
67
+ addDoc(graph, root, "plan", plan);
68
+ for (const plan of index.plans) {
69
+ // The plan's own `Derived from` line, resolved against the real inventory.
70
+ // `ambiguous` and `unknown` contribute NO edge: an unproven provenance must
71
+ // not become a deletion path.
72
+ if (plan.spec.status !== "resolved")
73
+ continue;
74
+ graph.addEdge({ kind: "plan", key: plan.number }, { kind: "spec", key: plan.spec.number }, "derived-from");
75
+ }
76
+ for (const session of index.sessions) {
77
+ const node = await sessionNode(deps, root, session);
78
+ graph.add(node);
79
+ linkSession(graph, node, session);
80
+ }
81
+ return { graph, index };
82
+ }
83
+ function addDoc(graph, root, kind, doc) {
84
+ graph.add({
85
+ id: { kind, key: doc.number },
86
+ kind,
87
+ path: doc.file,
88
+ absolute_path: join(root, doc.file),
89
+ session: null,
90
+ });
91
+ }
92
+ /**
93
+ * A session's edges: its custody first, and the prose only when custody has
94
+ * nothing to say.
95
+ *
96
+ * The order matters and the fallback is deliberately weaker. A session sealed
97
+ * with typed parents states its provenance; one that only mentions a document in
98
+ * `## Origin` merely mentions it, and recording that as `origin-prose` is what
99
+ * lets the closure stop rather than treat the two as equivalent.
100
+ */
101
+ function linkSession(graph, node, session) {
102
+ const custody = node.session?.custody ?? null;
103
+ if (custody !== null && custody.parents.length > 0) {
104
+ for (const parent of custody.parents)
105
+ graph.addEdge(node.id, parent, "custody");
106
+ return;
107
+ }
108
+ if (session.linked_doc === null)
109
+ return;
110
+ const linked = nodeFromDocPath(session.linked_doc);
111
+ if (linked !== null)
112
+ graph.addEdge(node.id, linked, "origin-prose");
113
+ }
114
+ async function sessionNode(deps, root, session) {
115
+ const read = await readCustody(deps.fs, session.path);
116
+ const facts = {
117
+ folder: session.folder,
118
+ state: session.state,
119
+ type: session.type,
120
+ completion: await completionOf(deps, session, read),
121
+ custody: read.status === "present" ? read.custody : null,
122
+ custody_gap: custodyGap(read),
123
+ units: session.units,
124
+ };
125
+ return {
126
+ id: { kind: "session", key: session.folder },
127
+ kind: "session",
128
+ path: relative(root, session.path).split("\\").join("/"),
129
+ absolute_path: session.path,
130
+ session: facts,
131
+ };
132
+ }
133
+ function custodyGap(read) {
134
+ if (read.status === "absent") {
135
+ return "la sesión no tiene custodia: nació antes de que existiera el registro";
136
+ }
137
+ if (read.status === "unreadable")
138
+ return read.reason;
139
+ const verdict = custodyCompleteness(read.custody);
140
+ if (verdict.complete)
141
+ return null;
142
+ return verdict.gaps.map((g) => `${g.what}: ${g.why}`).join("; ");
143
+ }
144
+ /**
145
+ * Whether a session converged, from the only two things that can say so.
146
+ *
147
+ * The run state is the direct answer: a journey with no boundary pending has
148
+ * nothing left to do. The artifact is the indirect one, and it is needed because a
149
+ * session may have run without a flow: a plan that reads `done` was carried to its
150
+ * end by SOMETHING, and treating the session that did it as resettable would undo
151
+ * delivered work. `.closed` is deliberately NOT consulted — a session closed with
152
+ * work pending is exactly the case reset exists for.
153
+ */
154
+ async function completionOf(deps, session, read) {
155
+ const run = await readRun(deps.fs, locateRun(deps.paths, session.folder));
156
+ if (run.ok)
157
+ return run.state.boundary === null ? "converged" : "incomplete";
158
+ if (read.status !== "present")
159
+ return "unknown";
160
+ return (await artifactConverged(deps, read.custody)) ? "converged" : "incomplete";
161
+ }
162
+ /**
163
+ * Whether the document this session was working on already reached its end.
164
+ *
165
+ * Read from the document itself rather than from the board's summary, because
166
+ * this is the guard that decides a destructive question and it should not depend
167
+ * on a projection that may have been built for another purpose.
168
+ */
169
+ async function artifactConverged(deps, custody) {
170
+ for (const parent of custody.parents) {
171
+ const path = docPathOf(deps, custody, parent);
172
+ if (path === null)
173
+ continue;
174
+ let text;
175
+ try {
176
+ text = await deps.fs.readText(path);
177
+ }
178
+ catch {
179
+ continue;
180
+ }
181
+ if (parent.kind === "plan" && /^>\s*Estado:\s*done\s*$/m.test(text))
182
+ return true;
183
+ if (parent.kind === "spec" && /^status:\s*ready-for-plan\s*$/m.test(text))
184
+ return true;
185
+ }
186
+ return false;
187
+ }
188
+ /** The absolute path of a parent document, as the custody itself recorded it. */
189
+ function docPathOf(deps, custody, parent) {
190
+ const artifact = custody.artifacts.find((a) => {
191
+ const node = nodeFromDocPath(a.path);
192
+ return node !== null && node.kind === parent.kind && node.key === parent.key;
193
+ });
194
+ return artifact === undefined ? null : join(deps.paths.workspaceDir(), artifact.path);
195
+ }
196
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../../src/application/retirement/graph.ts"],"names":[],"mappings":"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"}