@try-works/dsh-recursive-mode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/cordis.patch.yml +12 -0
  2. package/lib/bootstrap.d.ts +35 -0
  3. package/lib/client/board.d.ts +10 -0
  4. package/lib/client/contract.d.ts +51 -0
  5. package/lib/client/derive.d.ts +92 -0
  6. package/lib/client/index.d.ts +21 -0
  7. package/lib/client/inspector.d.ts +10 -0
  8. package/lib/client/node.d.ts +71 -0
  9. package/lib/client/settings.d.ts +6 -0
  10. package/lib/client/slots.d.ts +7 -0
  11. package/lib/client/strip.d.ts +7 -0
  12. package/lib/client.d.ts +10 -0
  13. package/lib/client.js +490 -0
  14. package/lib/closeout.d.ts +23 -0
  15. package/lib/commands.d.ts +51 -0
  16. package/lib/delegation.d.ts +92 -0
  17. package/lib/enforcement.d.ts +53 -0
  18. package/lib/events.d.ts +173 -0
  19. package/lib/handoff.d.ts +51 -0
  20. package/lib/index.d.ts +40 -0
  21. package/lib/lifecycle.d.ts +107 -0
  22. package/lib/lock.d.ts +92 -0
  23. package/lib/policy.d.ts +12 -0
  24. package/lib/projection.d.ts +29 -0
  25. package/lib/recursive_closeout.tool.d.ts +8 -0
  26. package/lib/recursive_init.tool.d.ts +2 -0
  27. package/lib/recursive_lint.tool.d.ts +2 -0
  28. package/lib/recursive_lock.tool.d.ts +2 -0
  29. package/lib/recursive_scratch.tool.d.ts +7 -0
  30. package/lib/recursive_status.tool.d.ts +2 -0
  31. package/lib/review.d.ts +39 -0
  32. package/lib/router.d.ts +77 -0
  33. package/lib/run.d.ts +29 -0
  34. package/lib/runtime.d.ts +241 -0
  35. package/lib/scratch.d.ts +18 -0
  36. package/lib/status.d.ts +19 -0
  37. package/lib/types.d.ts +104 -0
  38. package/lib/workspace.d.ts +50 -0
  39. package/package.json +119 -0
  40. package/preset/recursive/agent.cordis.yml +282 -0
  41. package/preset/recursive/preset.yml +3 -0
  42. package/scripts/install-recursive-mode.ps1 +956 -0
  43. package/scripts/install-recursive-mode.py +750 -0
  44. package/scripts/lint-recursive-run.py +2868 -0
  45. package/scripts/recursive-closeout.py +541 -0
  46. package/scripts/recursive-init.py +356 -0
  47. package/scripts/recursive-lock.py +302 -0
  48. package/scripts/recursive-status.py +2124 -0
  49. package/scripts/recursive_phase_rules.py +367 -0
  50. package/scripts/recursive_router_lib.py +2282 -0
  51. package/scripts/test-recursive-mode-smoke.ts +204 -0
  52. package/scripts/verify-locks.py +353 -0
  53. package/src/bootstrap.ts +118 -0
  54. package/src/client/board.tsx +61 -0
  55. package/src/client/contract.ts +58 -0
  56. package/src/client/derive.ts +241 -0
  57. package/src/client/index.ts +28 -0
  58. package/src/client/inspector.tsx +49 -0
  59. package/src/client/node.ts +156 -0
  60. package/src/client/settings.tsx +18 -0
  61. package/src/client/slots.ts +67 -0
  62. package/src/client/strip.tsx +28 -0
  63. package/src/client.ts +11 -0
  64. package/src/closeout.ts +183 -0
  65. package/src/commands.ts +142 -0
  66. package/src/delegation.ts +306 -0
  67. package/src/enforcement.ts +180 -0
  68. package/src/events.ts +173 -0
  69. package/src/handoff.ts +165 -0
  70. package/src/index.ts +283 -0
  71. package/src/lifecycle.ts +235 -0
  72. package/src/lock.ts +369 -0
  73. package/src/policy.ts +56 -0
  74. package/src/projection.ts +237 -0
  75. package/src/recursive_closeout.tool.ts +35 -0
  76. package/src/recursive_init.tool.ts +28 -0
  77. package/src/recursive_lint.tool.ts +29 -0
  78. package/src/recursive_lock.tool.ts +33 -0
  79. package/src/recursive_scratch.tool.ts +42 -0
  80. package/src/recursive_status.tool.ts +24 -0
  81. package/src/review.ts +178 -0
  82. package/src/router.ts +197 -0
  83. package/src/run.ts +85 -0
  84. package/src/runtime.ts +564 -0
  85. package/src/scratch.ts +85 -0
  86. package/src/status.ts +194 -0
  87. package/src/types.ts +112 -0
  88. package/src/workspace.ts +67 -0
package/lib/client.js ADDED
@@ -0,0 +1,490 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@try-works/dsh-recursive-mode",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ //#region src/client/derive.ts
9
+ const KANBAN_LANES = [
10
+ {
11
+ id: "0",
12
+ label: "Worktree & Requirements",
13
+ groups: ["00"]
14
+ },
15
+ {
16
+ id: "1/1.5",
17
+ label: "Analysis",
18
+ groups: ["01", "01.5"]
19
+ },
20
+ {
21
+ id: "2",
22
+ label: "TO-BE Plan",
23
+ groups: ["02"]
24
+ },
25
+ {
26
+ id: "3/3.5",
27
+ label: "Implementation + Code Review",
28
+ groups: ["03", "03.5"]
29
+ },
30
+ {
31
+ id: "4",
32
+ label: "Tests",
33
+ groups: ["04"]
34
+ },
35
+ {
36
+ id: "5",
37
+ label: "Manual QA",
38
+ groups: ["05"]
39
+ },
40
+ {
41
+ id: "6-8",
42
+ label: "Closeout",
43
+ groups: [
44
+ "06",
45
+ "07",
46
+ "08"
47
+ ]
48
+ }
49
+ ];
50
+ const GROUP_ORDER = [
51
+ "00",
52
+ "01",
53
+ "01.5",
54
+ "02",
55
+ "03",
56
+ "03.5",
57
+ "04",
58
+ "05",
59
+ "06",
60
+ "07",
61
+ "08"
62
+ ];
63
+ /** Mandatory phase groups: a run is only 'locked'-valid when every one of these is present and LOCKED (01.5 is optional). */
64
+ const MANDATORY_GROUPS = [
65
+ "00",
66
+ "01",
67
+ "02",
68
+ "03",
69
+ "03.5",
70
+ "04",
71
+ "05",
72
+ "06",
73
+ "07",
74
+ "08"
75
+ ];
76
+ /** Map a group id to its ordinal for stable ordering. */
77
+ const GROUP_INDEX$1 = new Map(GROUP_ORDER.map((g, i) => [g, i]));
78
+ /** Group id for a phase filename (e.g. '03.5-code-review.md' -> '03.5'); null for non-phase names. */
79
+ function phaseGroupOf$1(phase) {
80
+ const m = /^(\d{2}(?:\.\d)?)(?:-|$)/.exec(phase);
81
+ if (m === null) return null;
82
+ const g = m[1];
83
+ return GROUP_INDEX$1.has(g) ? g : null;
84
+ }
85
+ /** Kanban lane id for a phase filename. */
86
+ function laneOf(phase) {
87
+ const g = phaseGroupOf$1(phase);
88
+ if (g === null) return null;
89
+ for (const lane of KANBAN_LANES) if (lane.groups.includes(g)) return lane.id;
90
+ return null;
91
+ }
92
+ /**
93
+ * The run's current-phase lane: the highest-ordered phase group present in the
94
+ * card wins (a run is "at" its furthest phase). No phases -> lane 0.
95
+ */
96
+ function columnForRun(card) {
97
+ let best = null;
98
+ let bestIndex = -1;
99
+ for (const key of Object.keys(card.phases)) {
100
+ const g = phaseGroupOf$1(key);
101
+ if (g === null) continue;
102
+ const idx = GROUP_INDEX$1.get(g) ?? -1;
103
+ if (idx > bestIndex) {
104
+ bestIndex = idx;
105
+ best = g;
106
+ }
107
+ }
108
+ if (best === null) return "0";
109
+ return laneOf(best + "-x") ?? "0";
110
+ }
111
+ /** Derive §11.4 presentation facts from one card (no fs, no session). */
112
+ function cardFacts(card) {
113
+ const groups = /* @__PURE__ */ new Set();
114
+ for (const key of Object.keys(card.phases)) {
115
+ const g = phaseGroupOf$1(key);
116
+ if (g !== null) groups.add(g);
117
+ }
118
+ const lockedGroups = /* @__PURE__ */ new Set();
119
+ for (const key of Object.keys(card.phases)) {
120
+ const g = phaseGroupOf$1(key);
121
+ if (g !== null && card.phases[key].status === "LOCKED") lockedGroups.add(g);
122
+ }
123
+ const lockedCount = lockedGroups.size;
124
+ const totalPhases = groups.size;
125
+ const subagents = Object.values(card.subagents).map((s) => ({
126
+ childId: s.childId,
127
+ role: s.role,
128
+ provider: s.provider,
129
+ status: s.status ?? "done"
130
+ }));
131
+ let currentPhase = null;
132
+ let currentIdx = -1;
133
+ for (const key of Object.keys(card.phases)) {
134
+ const g = phaseGroupOf$1(key);
135
+ if (g === null) continue;
136
+ const idx = GROUP_INDEX$1.get(g) ?? -1;
137
+ if (idx > currentIdx) {
138
+ currentIdx = idx;
139
+ currentPhase = key;
140
+ }
141
+ }
142
+ const tampered = Object.keys(card.tampers).length > 0;
143
+ const allMandatoryLocked = MANDATORY_GROUPS.every((g) => {
144
+ const row = Object.entries(card.phases).find(([key]) => phaseGroupOf$1(key) === g);
145
+ return row !== void 0 && row[1].status === "LOCKED";
146
+ });
147
+ const lockValidity = tampered ? "tampered" : allMandatoryLocked ? "locked" : "in-progress";
148
+ return {
149
+ runId: card.runId,
150
+ worktreeRoot: card.worktreeRoot,
151
+ repo: card.repo,
152
+ template: card.template,
153
+ state: card.state,
154
+ stateReason: card.stateReason,
155
+ lockedCount,
156
+ totalPhases,
157
+ progress: totalPhases === 0 ? 0 : lockedCount / totalPhases,
158
+ tampered,
159
+ gateBlocked: card.gateBlocked !== void 0,
160
+ gateKind: card.gateBlocked?.kind,
161
+ merged: card.mergedToRepoRoot !== void 0,
162
+ mergedToRepoRoot: card.mergedToRepoRoot,
163
+ lockValidity,
164
+ currentPhase,
165
+ subagents
166
+ };
167
+ }
168
+ /**
169
+ * Expand a card's phase chain into full inspector rows: the always-shown
170
+ * '03.5' slot (§11.10 mandatory deviation) plus 01.5 when present, and
171
+ * 06/07/08 as three distinct rows (never one merged lane).
172
+ */
173
+ function expandPhaseRows(card) {
174
+ const byGroup = /* @__PURE__ */ new Map();
175
+ for (const [fileName, row] of Object.entries(card.phases)) {
176
+ const g = phaseGroupOf$1(fileName);
177
+ if (g !== null) byGroup.set(g, row);
178
+ }
179
+ const rows = [];
180
+ for (const g of GROUP_ORDER) {
181
+ if (g === "01.5" && !byGroup.has(g)) continue;
182
+ const row = byGroup.get(g);
183
+ if (row === void 0) {
184
+ if (g === "03.5") rows.push({
185
+ phase: g,
186
+ fileName: null,
187
+ status: "—",
188
+ present: false
189
+ });
190
+ continue;
191
+ }
192
+ rows.push({
193
+ phase: g,
194
+ fileName: row.phase,
195
+ status: row.status,
196
+ lockedAt: row.lockedAt,
197
+ lockHash: row.lockHash,
198
+ present: true
199
+ });
200
+ }
201
+ return rows;
202
+ }
203
+ //#endregion
204
+ //#region src/client/board.tsx
205
+ /**
206
+ * Board overlay (Phase D R5, §11.8): the kanban view over the 'recursive'
207
+ * projection. Pure read-only renderer — no filesystem, no run-file scraping.
208
+ * Cards are grouped by worktree root (the projection's outer key) and mapped
209
+ * to their current-phase lane via derive.columnForRun.
210
+ */
211
+ /** Flatten the worktree-grouped projection into a stable run list. */
212
+ function listRuns(projection) {
213
+ if (projection === void 0) return [];
214
+ const runs = [];
215
+ for (const worktreeRoot of Object.keys(projection)) for (const runId of Object.keys(projection[worktreeRoot])) runs.push(projection[worktreeRoot][runId]);
216
+ return runs;
217
+ }
218
+ function Board({ useProjection }) {
219
+ const runs = listRuns(useProjection("recursive"));
220
+ if (runs.length === 0) return (0, react.createElement)("div", {
221
+ className: "rec-board",
222
+ "data-empty": true
223
+ }, (0, react.createElement)("p", { className: "rec-board-empty" }, "No recursive runs in this workspace yet."));
224
+ return (0, react.createElement)("div", { className: "rec-board" }, KANBAN_LANES.map((lane) => {
225
+ const laneRuns = runs.filter((r) => columnForRun(r) === lane.id);
226
+ return (0, react.createElement)("section", {
227
+ key: lane.id,
228
+ className: "rec-lane"
229
+ }, (0, react.createElement)("h2", { className: "rec-lane-title" }, lane.label), laneRuns.map((run) => {
230
+ const facts = cardFacts(run);
231
+ return (0, react.createElement)("article", {
232
+ key: run.worktreeRoot + "\0" + run.runId,
233
+ className: "rec-card"
234
+ }, (0, react.createElement)("div", { className: "rec-card-head" }, (0, react.createElement)("strong", {}, run.runId), facts.tampered && (0, react.createElement)("span", {
235
+ className: "rec-badge rec-badge-tamper",
236
+ title: "tampered"
237
+ }, "!")), (0, react.createElement)("div", { className: "rec-card-progress" }, (0, react.createElement)("div", {
238
+ className: "rec-card-progress-fill",
239
+ style: { width: Math.round(facts.progress * 100) + "%" }
240
+ })), (0, react.createElement)("div", { className: "rec-card-meta" }, (0, react.createElement)("span", {}, facts.currentPhase ?? "no phases"), (0, react.createElement)("span", { className: "rec-badge" }, facts.state)));
241
+ }));
242
+ }));
243
+ }
244
+ //#endregion
245
+ //#region src/client/strip.tsx
246
+ /**
247
+ * Status strip (Phase D R7, §11.6): a thin glance at the active run's phase,
248
+ * gate state, and tamper badge, docked at conversation.input.dock. Renders
249
+ * nothing when no run is current.
250
+ */
251
+ function StatusStrip({ useProjection }) {
252
+ const runs = listRuns(useProjection("recursive"));
253
+ if (runs.length === 0) return null;
254
+ const active = runs[runs.length - 1];
255
+ const facts = cardFacts(active);
256
+ return (0, react.createElement)("div", { className: "rec-strip" }, (0, react.createElement)("span", { className: "rec-strip-run" }, active.runId), (0, react.createElement)("span", { className: "rec-badge" }, facts.currentPhase ?? "no phases"), facts.gateBlocked && (0, react.createElement)("span", { className: "rec-badge rec-badge-gate" }, "gate blocked"), facts.tampered && (0, react.createElement)("span", { className: "rec-badge rec-badge-tamper" }, "tampered"));
257
+ }
258
+ //#endregion
259
+ //#region src/client/settings.tsx
260
+ /**
261
+ * Recursive settings page (Phase D R8, §11.8): a settings.section list entry
262
+ * surfacing the host-owned enforcement config. The client is read-mostly; all
263
+ * writes ride the host config path (never run files).
264
+ */
265
+ function RecursiveSettings({ close }) {
266
+ return (0, react.createElement)("div", { className: "rec-settings" }, (0, react.createElement)("h2", {}, "Recursive"), (0, react.createElement)("p", {}, "Enforcement policy, scratch format, provider defaults, and preset install path are configured on the host. The client is read-only (§11.9)."), (0, react.createElement)("button", { onClick: close }, "Close"));
267
+ }
268
+ //#endregion
269
+ //#region src/client/slots.ts
270
+ /**
271
+ * Slot registrations (Phase D R5/R6/R7/R8, §11.8): the sidebar launcher, the
272
+ * shell.overlay board, the conversation.input.dock status strip, and the
273
+ * settings.section page. The details-seat inspector is a swap driven by the
274
+ * board cards' click (R6); the strip + board + settings are additive seats.
275
+ *
276
+ * Self-contained structural seam: the real slot registry + runtime standard
277
+ * kit arrive from @deepseek-ai/dsh-client-ui-slots + @deepseek-ai/dsh-client-runtime
278
+ * at compose time; this module consumes the minimal ClientContext shape
279
+ * (contract.ts). The client is READ-ONLY (R9) — no filesystem, no mutation.
280
+ */
281
+ function registerSlots(ctx) {
282
+ const disposers = [];
283
+ disposers.push(ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
284
+ name: "sidebar.footer.action",
285
+ id: "recursive",
286
+ order: 50,
287
+ label: "Recursive runs"
288
+ }, () => (0, react.createElement)("button", {
289
+ className: "rec-launcher",
290
+ title: "Recursive runs"
291
+ }, "⧉"))));
292
+ disposers.push(ctx.slots.inject("shell.overlay", () => ctx.slots.register({
293
+ name: "shell.overlay",
294
+ id: "recursive-board",
295
+ order: 10
296
+ }, (props) => {
297
+ if (props?.useProjection === void 0) return null;
298
+ return (0, react.createElement)(Board, { useProjection: props.useProjection });
299
+ })));
300
+ disposers.push(ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
301
+ name: "conversation.input.dock",
302
+ id: "recursive-strip",
303
+ order: 20
304
+ }, (props) => {
305
+ if (props?.useProjection === void 0) return null;
306
+ return (0, react.createElement)(StatusStrip, { useProjection: props.useProjection });
307
+ })));
308
+ disposers.push(ctx.slots.inject("settings.section", () => ctx.slots.register({
309
+ name: "settings.section",
310
+ id: "recursive",
311
+ order: 90,
312
+ label: "Recursive"
313
+ }, (props) => (0, react.createElement)(RecursiveSettings, { close: props.close }))));
314
+ return () => {
315
+ for (const d of disposers) d();
316
+ };
317
+ }
318
+ //#endregion
319
+ //#region src/client/node.ts
320
+ const RECURSIVE_NODE_KIND = "recursive/run";
321
+ const GROUP_INDEX = new Map([
322
+ "00",
323
+ "01",
324
+ "01.5",
325
+ "02",
326
+ "03",
327
+ "03.5",
328
+ "04",
329
+ "05",
330
+ "06",
331
+ "07",
332
+ "08"
333
+ ].map((g, i) => [g, i]));
334
+ function phaseGroupOf(phase) {
335
+ const m = /^(\d{2}(?:\.\d)?)(?:-|$)/.exec(phase);
336
+ return m ? m[1] : null;
337
+ }
338
+ function currentPhaseOf(phases) {
339
+ let best = null;
340
+ let bestIdx = -1;
341
+ for (const key of Object.keys(phases)) {
342
+ const g = phaseGroupOf(key);
343
+ if (g === null) continue;
344
+ const idx = GROUP_INDEX.get(g) ?? -1;
345
+ if (idx > bestIdx) {
346
+ bestIdx = idx;
347
+ best = key;
348
+ }
349
+ }
350
+ return best;
351
+ }
352
+ function nodeDataOf(s) {
353
+ const lockedCount = Object.values(s.phases).filter((p) => p.status === "LOCKED").length;
354
+ return {
355
+ runId: s.runId,
356
+ worktreeRoot: s.worktreeRoot,
357
+ state: s.state,
358
+ currentPhase: currentPhaseOf(s.phases),
359
+ lockedCount,
360
+ totalPhases: Object.keys(s.phases).length,
361
+ tampered: s.tampered,
362
+ gateBlocked: s.gateBlocked,
363
+ ...s.mergedToRepoRoot === void 0 ? {} : { mergedToRepoRoot: s.mergedToRepoRoot }
364
+ };
365
+ }
366
+ /** Extract a stable (runId, worktreeRoot) business id + lifecycle role from one event. */
367
+ function matchRecursiveNode(event) {
368
+ if (typeof event.type !== "string" || !event.type.startsWith("recursive/")) return null;
369
+ const data = event.data ?? {};
370
+ const runId = typeof data.runId === "string" ? data.runId : "";
371
+ const worktreeRoot = typeof data.worktreeRoot === "string" ? data.worktreeRoot : "";
372
+ if (runId === "" || worktreeRoot === "") return null;
373
+ return {
374
+ id: worktreeRoot + "\0" + runId,
375
+ role: event.type === "recursive/run-created" ? "start" : "update"
376
+ };
377
+ }
378
+ /** Fold one recursive/* event into the node state. */
379
+ function foldRecursiveNode(s, event) {
380
+ const data = event.data ?? {};
381
+ const runId = typeof data.runId === "string" ? data.runId : "";
382
+ const worktreeRoot = typeof data.worktreeRoot === "string" ? data.worktreeRoot : "";
383
+ const base = s ?? {
384
+ runId,
385
+ worktreeRoot,
386
+ state: "active",
387
+ phases: {},
388
+ tampered: false,
389
+ gateBlocked: false
390
+ };
391
+ switch (event.type) {
392
+ case "recursive/run-created":
393
+ base.runId = runId;
394
+ base.worktreeRoot = worktreeRoot;
395
+ return base;
396
+ case "recursive/phase": {
397
+ const phase = String(data.phase ?? "");
398
+ if (phase !== "") base.phases[phase] = {
399
+ phase,
400
+ status: String(data.status ?? "")
401
+ };
402
+ return base;
403
+ }
404
+ case "recursive/phase-locked": {
405
+ const phase = String(data.phase ?? "");
406
+ if (phase !== "") base.phases[phase] = {
407
+ phase,
408
+ status: "LOCKED"
409
+ };
410
+ return base;
411
+ }
412
+ case "recursive/gate-blocked":
413
+ base.gateBlocked = true;
414
+ return base;
415
+ case "recursive/tamper":
416
+ base.tampered = true;
417
+ return base;
418
+ case "recursive/run-state":
419
+ base.state = typeof data.state === "string" ? data.state : base.state;
420
+ return base;
421
+ case "recursive/run-merged": {
422
+ const repoRoot = typeof data.repoRoot === "string" ? data.repoRoot : "";
423
+ if (repoRoot !== "") {
424
+ base.mergedToRepoRoot = repoRoot;
425
+ base.worktreeRoot = repoRoot;
426
+ }
427
+ return base;
428
+ }
429
+ default: return base;
430
+ }
431
+ }
432
+ /** The structural ConversationNodeDefinition (host links to the real contract). */
433
+ const recursiveNodeDefinition = {
434
+ kind: RECURSIVE_NODE_KIND,
435
+ target: "chat",
436
+ match: matchRecursiveNode,
437
+ start: (_ctx, match) => foldRecursiveNode(void 0, match.event),
438
+ update: (ctx, match) => foldRecursiveNode(ctx.state, match.event),
439
+ buildViewNode: (ctx) => {
440
+ const state = ctx.state;
441
+ if (state === void 0) return null;
442
+ return {
443
+ key: ctx.key,
444
+ kind: RECURSIVE_NODE_KIND,
445
+ id: ctx.id,
446
+ target: "chat",
447
+ data: nodeDataOf(state)
448
+ };
449
+ }
450
+ };
451
+ //#endregion
452
+ //#region src/client/inspector.tsx
453
+ /**
454
+ * Drill-down inspector (Phase D R6, §11.5): the details-seat swap rendering
455
+ * one run's phase chain (with the always-shown 3.5 row + full 6/7/8 rows),
456
+ * lock badges, and tamper/gate facts — all from the projection, never disk.
457
+ */
458
+ function Inspector({ runId, worktreeRoot, useProjection, onBackToToolDetails }) {
459
+ const card = useProjection("recursive")?.[worktreeRoot]?.[runId];
460
+ if (card === void 0) return (0, react.createElement)("div", { className: "rec-inspector" }, (0, react.createElement)("p", {}, "Run not found: " + runId));
461
+ const facts = cardFacts(card);
462
+ const rows = expandPhaseRows(card);
463
+ return (0, react.createElement)("div", { className: "rec-inspector" }, (0, react.createElement)("div", { className: "rec-inspector-head" }, (0, react.createElement)("button", {
464
+ onClick: onBackToToolDetails,
465
+ className: "rec-back"
466
+ }, "← back to tool details"), (0, react.createElement)("h2", {}, runId), (0, react.createElement)("span", { className: "rec-badge" }, facts.state), facts.tampered && (0, react.createElement)("span", { className: "rec-badge rec-badge-tamper" }, "tampered")), (0, react.createElement)("section", { className: "rec-inspector-section" }, (0, react.createElement)("h3", {}, "Phases"), rows.map((row) => (0, react.createElement)("div", {
467
+ key: row.phase,
468
+ className: "rec-phase-row"
469
+ }, (0, react.createElement)("span", { className: "rec-phase-id" }, row.phase), (0, react.createElement)("span", { className: "rec-phase-name" }, row.fileName ?? "—"), (0, react.createElement)("span", { className: "rec-badge" }, row.status), row.lockHash !== void 0 && (0, react.createElement)("code", { className: "rec-lockhash" }, "#" + row.lockHash.slice(0, 8))))), facts.gateBlocked && (0, react.createElement)("section", { className: "rec-inspector-section" }, (0, react.createElement)("h3", {}, "Gate"), (0, react.createElement)("p", {}, "Blocked (" + (facts.gateKind ?? "") + ")")));
470
+ }
471
+ //#endregion
472
+ //#region src/client/index.ts
473
+ /** Browser-half plugin entry (R4). */
474
+ function apply(ctx) {
475
+ registerSlots(ctx);
476
+ }
477
+ //#endregion
478
+ exports.Board = Board;
479
+ exports.Inspector = Inspector;
480
+ exports.RECURSIVE_NODE_KIND = RECURSIVE_NODE_KIND;
481
+ exports.RecursiveSettings = RecursiveSettings;
482
+ exports.StatusStrip = StatusStrip;
483
+ exports.apply = apply;
484
+ exports.foldRecursiveNode = foldRecursiveNode;
485
+ exports.listRuns = listRuns;
486
+ exports.matchRecursiveNode = matchRecursiveNode;
487
+ exports.recursiveNodeDefinition = recursiveNodeDefinition;
488
+ return module.exports;
489
+ }
490
+ });
@@ -0,0 +1,23 @@
1
+ export interface CloseoutPhaseConfig {
2
+ file: string;
3
+ label: string;
4
+ scopeNote: string;
5
+ todoItems: string[];
6
+ }
7
+ export declare const PHASE_CONFIG: Record<string, CloseoutPhaseConfig>;
8
+ export interface CloseoutResult {
9
+ phase: string;
10
+ file: string;
11
+ created: string[];
12
+ existing: string[];
13
+ }
14
+ export interface CloseoutOptions {
15
+ /** Enforce prerequisite-lock gating (default true). */
16
+ strict?: boolean;
17
+ }
18
+ /**
19
+ * Create or update a closeout receipt stub for the given phase.
20
+ * Returns the file + created/existing lists. When strict, refuses phases whose
21
+ * prerequisite artifacts are not all LOCKED (reuses lock.ts chain validation).
22
+ */
23
+ export declare function closeoutPhase(runDir: string, phase: string, opts?: CloseoutOptions): CloseoutResult;
@@ -0,0 +1,51 @@
1
+ import type { RecursiveRuntime } from './runtime.ts';
2
+ export type RecursiveVerb = 'status' | 'spec' | 'worktree' | 'init' | 'lock' | 'qa' | 'closeout' | 'addendum' | 'review' | 'scratch' | 'bootstrap' | 'list' | 'help';
3
+ export declare const PRESET_VERBS: RecursiveVerb[];
4
+ export declare const GLOBAL_VERBS: RecursiveVerb[];
5
+ export declare const ALL_VERBS: RecursiveVerb[];
6
+ export interface ParsedRecursiveCommand {
7
+ verb: string;
8
+ arg: string;
9
+ }
10
+ export declare function parseRecursiveCommand(rawInput: string): ParsedRecursiveCommand;
11
+ export type RecursiveCommandResult = {
12
+ kind: 'success';
13
+ text?: string;
14
+ } | {
15
+ kind: 'error';
16
+ text: string;
17
+ };
18
+ /**
19
+ * Execute a /recursive command line against a workspace root (the session's
20
+ * control-plane root). Pure + workspace-scoped: runId outside root errors.
21
+ */
22
+ export declare function executeRecursiveCommand(root: string, rawInput: string): RecursiveCommandResult;
23
+ export interface RecursiveCommandDefinition {
24
+ name: string;
25
+ description: string;
26
+ input: {
27
+ hint: string;
28
+ };
29
+ handler: (inv: RecursiveCommandInvocation) => RecursiveCommandResult | Promise<RecursiveCommandResult>;
30
+ }
31
+ export interface RecursiveCommandInvocation {
32
+ rawInput: string;
33
+ agent?: {
34
+ session?: {
35
+ header?: {
36
+ cwd?: string;
37
+ };
38
+ };
39
+ } | null;
40
+ }
41
+ export interface RecursiveCommandRuntime {
42
+ register(def: RecursiveCommandDefinition): () => void;
43
+ }
44
+ /**
45
+ * Register the /recursive command on a context with commands (Stage A).
46
+ * The handler resolves the session workspace root via the agent's cwd and
47
+ * executes scoped to that root only (R1).
48
+ */
49
+ export declare function registerRecursiveCommand(ctx: {
50
+ commands: RecursiveCommandRuntime;
51
+ }, recursive: RecursiveRuntime): () => void;
@@ -0,0 +1,92 @@
1
+ /** Minimal host-realm contract for ctx.subagents (the seam we call). */
2
+ export interface SubagentsRuntimeLike {
3
+ start(name: string, request: SubagentStartRequestLike): Promise<SubagentResultLike>;
4
+ getProvider?(name: string): unknown;
5
+ list?(): unknown;
6
+ }
7
+ export interface SubagentStartRequestLike {
8
+ prompt: unknown[];
9
+ label?: string;
10
+ outputSchema?: Record<string, unknown>;
11
+ toolFilter?: unknown;
12
+ maxDepth?: number;
13
+ persona?: string;
14
+ parent?: unknown;
15
+ signal?: unknown;
16
+ }
17
+ export interface SubagentResultLike {
18
+ output?: string;
19
+ structured?: unknown;
20
+ stopReason?: string;
21
+ success?: boolean;
22
+ }
23
+ export interface DelegationError extends Error {
24
+ code?: string;
25
+ }
26
+ export declare function delegationError(message: string, code: string): DelegationError;
27
+ /** Object-rooted output schema for a delegated review. */
28
+ export declare function reviewOutputSchema(): Record<string, unknown>;
29
+ /** Default toolFilter for a delegated reviewer (run-relevant, workspace-scoped). */
30
+ export declare function defaultReviewToolFilter(): unknown;
31
+ /**
32
+ * Call ctx.subagents.start() with the full request. Fail loud on capability
33
+ * mismatch, missing provider, or unsupported schema (never silent).
34
+ */
35
+ export declare function delegate(input: {
36
+ subagents: SubagentsRuntimeLike;
37
+ provider: string;
38
+ request: SubagentStartRequestLike;
39
+ }): Promise<SubagentResultLike>;
40
+ export interface Reference {
41
+ path: string;
42
+ lineRange?: string;
43
+ }
44
+ export interface ReferenceCheck {
45
+ ok: boolean;
46
+ failures: string[];
47
+ checked: {
48
+ path: string;
49
+ ok: boolean;
50
+ reason?: string;
51
+ }[];
52
+ }
53
+ /**
54
+ * Validate every claimed reference: the path exists under root, and a line
55
+ * range (e.g. '10-20' or '10') is within the file's line count.
56
+ */
57
+ export declare function validateReferences(root: string, references: Reference[]): ReferenceCheck;
58
+ export interface ActionRecordInput {
59
+ root: string;
60
+ runId: string;
61
+ subagentId: string;
62
+ phase: string;
63
+ purpose: string;
64
+ executionMode: string;
65
+ artifactPath?: string;
66
+ upstreamArtifacts?: string[];
67
+ reviewBundle?: string;
68
+ diffBasis?: string;
69
+ codeRefs?: string[];
70
+ auditQuestions?: string[];
71
+ actionsTaken?: string[];
72
+ createdFiles?: string[];
73
+ modifiedFiles?: string[];
74
+ reviewedFiles?: string[];
75
+ findings?: string[];
76
+ success: boolean;
77
+ stopReason?: string;
78
+ }
79
+ /**
80
+ * Write a durable action record under subagents/ with the canonical sections
81
+ * (matches recursive-subagent-action.py). A success:false attempt is written
82
+ * with a failed status and is NOT accepted.
83
+ */
84
+ export declare function writeActionRecord(input: ActionRecordInput): string;
85
+ /**
86
+ * Accept/reject a delegation result: a success:false or non-completed
87
+ * stopReason is a failed attempt (diagnostics preserved, NOT accepted).
88
+ */
89
+ export declare function evaluateDelegationResult(result: SubagentResultLike): {
90
+ accepted: boolean;
91
+ reason: string;
92
+ };