@gobing-ai/spur 0.4.1 → 0.4.2
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/marketplace.json +1 -1
- package/config/config.global.yaml +4 -2
- package/config/templates/AGENTS.md +1 -1
- package/config/workflows/wrapup-pipeline.yaml +1 -1
- package/package.json +10 -10
- package/plugins/sp/README.md +1 -2
- package/plugins/sp/agents/super-planner.md +5 -6
- package/plugins/sp/commands/dev-review-session.md +2 -1
- package/plugins/sp/commands/dev-runall.md +18 -9
- package/plugins/sp/hooks/context-owner.ts +56 -0
- package/plugins/sp/hooks/context-post-tool.ts +23 -0
- package/plugins/sp/hooks/context-session-start.ts +49 -27
- package/plugins/sp/hooks/context-session-stop.ts +131 -30
- package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
- package/plugins/sp/lib/idea-handoff.generated.mjs +4 -2
- package/plugins/sp/lib/inline-run.generated.mjs +47 -30
- package/plugins/sp/lib/plan-projection.generated.d.mts +19 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +113 -0
- package/plugins/sp/lib/quality-gate.generated.d.mts +0 -12
- package/plugins/sp/lib/quality-gate.generated.mjs +48 -230
- package/plugins/sp/lib/transcript.ts +49 -21
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +154 -1
- package/plugins/sp/scripts/batch-plan.ts +48 -3
- package/plugins/sp/scripts/inline-run-setup.mjs +24 -13
- package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
- package/plugins/sp/scripts/pr-reviewing.ts +34 -9
- package/plugins/sp/scripts/quality-gate.mjs +25 -207
- package/plugins/sp/scripts/run-summary.mjs +24 -13
- package/plugins/sp/scripts/session-timeline.mjs +26 -25
- package/plugins/sp/scripts/session-timeline.ts +4 -18
- package/plugins/sp/scripts/task-diffstat.mjs +32 -29
- package/plugins/sp/scripts/task-diffstat.ts +34 -34
- package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
- package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
- package/plugins/sp/skills/session-review/SKILL.md +5 -3
- package/plugins/sp/skills/spur-cli/references/decision.md +9 -2
- package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +27 -0
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +5 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +43 -4
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -1
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +13 -11
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +8 -13
- package/spur.js +14261 -2846
- package/web/_astro/{BoardApp.D66k_s_T.js → BoardApp.B3GQZvj8.js} +1 -1
- package/web/_astro/{BoardApp.D6sjAJr_.js → BoardApp.BT7L2kSb.js} +192 -192
- package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
- package/web/_astro/{arc._BUadrdH.js → arc.vaT7tPOB.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.Dh7xS1Pp.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DFDVpfXm.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.V7_3_glh.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
- package/web/_astro/channel.D4nG9YLf.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.De1yx7Jz.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BDhHFuxx.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DDLkWnmC.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.CwfiLnA7.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.CtGDmUnM.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BOLjpw73.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BDhQls6O.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.76qBynDF.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.N0mpPHT0.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.N0mpPHT0.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CKknFfjB.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.Cw3ZWBAh.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.D3yIKCRD.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.D51dO83Z.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Bb-FViDj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.D3mN8S6x.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.dMMyPcbJ.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.AzbvzY4C.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BR5I4OZb.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DFqvhGkn.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.ByJbhH6J.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CInHjQR3.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
- package/web/_astro/{index.COh_kBxN.css → index.BhSwN8yY.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.Cm0dGVjJ.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.4IvLa2MW.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DS87Cl-k.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
- package/web/_astro/{linear.MzmRrNER.js → linear.BzGnBwNL.js} +1 -1
- package/web/_astro/{mermaid.core.fK-WNswS.js → mermaid.core.CSbTpIVz.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BqipOHhY.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.0nBiPqFG.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.Szj7Zpx5.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.D4UR6TWo.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DAE7BdOs.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.BP9ZVq25.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BQW7mIxk.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.PbK4nDih.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.Ci_GvUpo.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DNw1Y5qx.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CwlkaaNe.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.MI6ZuiH_.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/commands/dev-parallel.md +0 -37
- package/web/_astro/TaskDetail.BynLnMNc.js +0 -1
- package/web/_astro/channel.DZVfto-r.js +0 -1
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
* per-turn and tore down after turn 1), and on Pi/omp it maps to `session_shutdown` — off the
|
|
9
9
|
* `agent_end`/Stop event where blocking gates (e.g. cc/anti-hallucination) live and would collide.
|
|
10
10
|
*
|
|
11
|
+
* **Owner-gated finalization (task 1159).** A nested `agent.run` reuses its parent's pointer but
|
|
12
|
+
* used to finalize and delete it on its own shutdown, so every later parent tool event landed in a
|
|
13
|
+
* dead ledger. Shutdown now proceeds only when the caller's host-native identity equals the owner
|
|
14
|
+
* recorded on the pointer, so a child's shutdown is a no-op.
|
|
15
|
+
*
|
|
11
16
|
* **Fail-open contract:** every error path — missing `.spur/context/`, missing/unparseable
|
|
12
17
|
* `.session.json`, write failure — exits 0 with no output.
|
|
13
18
|
*
|
|
@@ -17,7 +22,11 @@
|
|
|
17
22
|
|
|
18
23
|
import { appendFileSync, existsSync, readFileSync, rmSync } from 'node:fs';
|
|
19
24
|
import { join } from 'node:path';
|
|
20
|
-
import { getEnvVar } from '../lib/env';
|
|
25
|
+
import { getEnvVar, getEnvVars } from '../lib/env';
|
|
26
|
+
import { claudeHookOwnerFromPayloadText } from './context-owner';
|
|
27
|
+
// Type-only import: erased at bundle time, so this hook keeps its minimal
|
|
28
|
+
// node:fs/node:path/env dependency surface (sp-plugin-standalone contract).
|
|
29
|
+
import type { SessionOwner } from './context-session-start';
|
|
21
30
|
|
|
22
31
|
interface LedgerEvent {
|
|
23
32
|
session: string;
|
|
@@ -25,16 +34,51 @@ interface LedgerEvent {
|
|
|
25
34
|
tokens?: number;
|
|
26
35
|
}
|
|
27
36
|
|
|
37
|
+
interface SessionPointer {
|
|
38
|
+
session?: unknown;
|
|
39
|
+
owner?: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Parse and validate an owner read from the pointer or supplied by a caller. */
|
|
43
|
+
function validSessionOwner(raw: unknown): SessionOwner | null {
|
|
44
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
45
|
+
const host = (raw as { host?: unknown }).host;
|
|
46
|
+
const sessionId = (raw as { sessionId?: unknown }).sessionId;
|
|
47
|
+
if (host !== 'claude' && host !== 'pi') return null;
|
|
48
|
+
if (typeof sessionId !== 'string') return null;
|
|
49
|
+
const trimmed = sessionId.trim();
|
|
50
|
+
return trimmed.length > 0 ? { host, sessionId: trimmed } : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sameOwner(a: SessionOwner, b: SessionOwner): boolean {
|
|
54
|
+
return a.host === b.host && a.sessionId === b.sessionId;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Read and validate the private session pointer, or null when absent/corrupt. */
|
|
58
|
+
function readPointer(sessionFile: string): SessionPointer | null {
|
|
59
|
+
if (!existsSync(sessionFile)) return null;
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(readFileSync(sessionFile, 'utf-8')) as SessionPointer;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
28
67
|
/** Scan the token-ledger JSONL for this session's events and compute rollup totals. */
|
|
29
68
|
function computeSessionTotals(
|
|
30
69
|
ledgerPath: string,
|
|
31
70
|
sessionId: string,
|
|
32
71
|
): { reads: number; writes: number; tokens: number } {
|
|
33
|
-
if (!existsSync(ledgerPath)) return { reads: 0, writes: 0, tokens: 0 };
|
|
34
72
|
let reads = 0;
|
|
35
73
|
let writes = 0;
|
|
36
74
|
let tokens = 0;
|
|
37
|
-
|
|
75
|
+
let lines: string[];
|
|
76
|
+
try {
|
|
77
|
+
lines = readFileSync(ledgerPath, 'utf-8').split('\n');
|
|
78
|
+
} catch {
|
|
79
|
+
return { reads, writes, tokens }; // missing/unreadable ledger → zero totals
|
|
80
|
+
}
|
|
81
|
+
for (const line of lines) {
|
|
38
82
|
if (!line.trim()) continue;
|
|
39
83
|
try {
|
|
40
84
|
const evt = JSON.parse(line) as LedgerEvent;
|
|
@@ -49,43 +93,86 @@ function computeSessionTotals(
|
|
|
49
93
|
return { reads, writes, tokens };
|
|
50
94
|
}
|
|
51
95
|
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* event. Shared by this hook's entrypoint and Pi's `session_shutdown` so both hosts roll up
|
|
56
|
-
* identically. Returns null on every fail-open path.
|
|
57
|
-
*/
|
|
58
|
-
export function recordSessionEnd(dir: string, now: () => Date = () => new Date()): Record<string, unknown> | null {
|
|
59
|
-
const sessionFile = join(dir, '.session.json');
|
|
60
|
-
if (!existsSync(sessionFile)) return null;
|
|
61
|
-
|
|
62
|
-
let sessionId = '';
|
|
96
|
+
/** Return the existing `session_end` row for `sessionId`, or null when the ledger has none. */
|
|
97
|
+
function findSessionEnd(ledgerPath: string, sessionId: string): Record<string, unknown> | null {
|
|
98
|
+
let lines: string[];
|
|
63
99
|
try {
|
|
64
|
-
|
|
65
|
-
sessionId = session.session ?? '';
|
|
100
|
+
lines = readFileSync(ledgerPath, 'utf-8').split('\n');
|
|
66
101
|
} catch {
|
|
67
102
|
return null;
|
|
68
103
|
}
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
if (!line.trim()) continue;
|
|
106
|
+
try {
|
|
107
|
+
const evt = JSON.parse(line) as Record<string, unknown>;
|
|
108
|
+
if (evt.type === 'session_end' && evt.session === sessionId) return evt;
|
|
109
|
+
} catch {
|
|
110
|
+
// skip unparseable lines
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Finalize one session (task 0969 R3; owner-gated task 1159): read the `.session.json` pointer,
|
|
118
|
+
* compute the session's rollup totals, append the `session_end` row, remove the pointer
|
|
119
|
+
* (best-effort) and return the event. Shared by this hook's entrypoint and Pi's `session_shutdown`
|
|
120
|
+
* so both hosts roll up identically. Returns null on every fail-open path.
|
|
121
|
+
*
|
|
122
|
+
* `caller` is the identity of the process firing shutdown. Finalization requires it to be a valid
|
|
123
|
+
* identity that **equals** the pointer's recorded owner; a missing/mismatched/malformed caller, a
|
|
124
|
+
* legacy ownerless pointer or a corrupt pointer all return null with no writes and no removal.
|
|
125
|
+
* Ownerless pointers stay readable and reusable for tool recording — the bounded legacy policy is
|
|
126
|
+
* that an unattributable session is retired by the existing idle-start expiry, never claimed here.
|
|
127
|
+
*
|
|
128
|
+
* Repeat safety: before appending, an existing `session_end` row for the ledger suppresses a
|
|
129
|
+
* second one, so a replay after a failed pointer cleanup neither duplicates the row nor strands the
|
|
130
|
+
* pointer. Removal re-reads the pointer and requires both the ledger id and the owner to be
|
|
131
|
+
* unchanged, so a pointer replaced since the first read is left untouched.
|
|
132
|
+
*/
|
|
133
|
+
export function recordSessionEnd(
|
|
134
|
+
dir: string,
|
|
135
|
+
now: () => Date = () => new Date(),
|
|
136
|
+
caller?: SessionOwner,
|
|
137
|
+
): Record<string, unknown> | null {
|
|
138
|
+
const sessionFile = join(dir, '.session.json');
|
|
139
|
+
const pointer = readPointer(sessionFile);
|
|
140
|
+
if (!pointer) return null;
|
|
69
141
|
|
|
142
|
+
const sessionId = typeof pointer.session === 'string' ? pointer.session : '';
|
|
70
143
|
if (!sessionId) return null;
|
|
71
144
|
|
|
72
|
-
|
|
145
|
+
// Ownership gate: only the identity recorded at creation may finalize or remove.
|
|
146
|
+
const pointerOwner = validSessionOwner(pointer.owner);
|
|
147
|
+
const callerOwner = validSessionOwner(caller);
|
|
148
|
+
if (!pointerOwner || !callerOwner || !sameOwner(pointerOwner, callerOwner)) return null;
|
|
73
149
|
|
|
74
|
-
const
|
|
75
|
-
ts: now().toISOString(),
|
|
76
|
-
session: sessionId,
|
|
77
|
-
type: 'session_end' as const,
|
|
78
|
-
totals,
|
|
79
|
-
};
|
|
150
|
+
const ledgerPath = join(dir, 'token-ledger.jsonl');
|
|
80
151
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
152
|
+
let event: Record<string, unknown>;
|
|
153
|
+
const existing = findSessionEnd(ledgerPath, sessionId);
|
|
154
|
+
if (existing) {
|
|
155
|
+
// Replay: the end row survived an earlier failed pointer cleanup. Retry the removal below
|
|
156
|
+
// without appending a duplicate.
|
|
157
|
+
event = existing;
|
|
158
|
+
} else {
|
|
159
|
+
const totals = computeSessionTotals(ledgerPath, sessionId);
|
|
160
|
+
event = { ts: now().toISOString(), session: sessionId, type: 'session_end', totals };
|
|
161
|
+
try {
|
|
162
|
+
appendFileSync(ledgerPath, `${JSON.stringify(event)}\n`);
|
|
163
|
+
} catch {
|
|
164
|
+
return null; // retain the pointer so a later owning shutdown can retry
|
|
165
|
+
}
|
|
85
166
|
}
|
|
86
167
|
|
|
168
|
+
// Remove only the exact pointer finalized above: re-read and require the ledger id and owner
|
|
169
|
+
// to be unchanged, so a pointer replaced during recording survives.
|
|
87
170
|
try {
|
|
88
|
-
|
|
171
|
+
const current = readPointer(sessionFile);
|
|
172
|
+
const currentOwner = current ? validSessionOwner(current.owner) : null;
|
|
173
|
+
if (current?.session === sessionId && currentOwner && sameOwner(currentOwner, pointerOwner)) {
|
|
174
|
+
rmSync(sessionFile, { force: true });
|
|
175
|
+
}
|
|
89
176
|
} catch {
|
|
90
177
|
// cleanup is best-effort
|
|
91
178
|
}
|
|
@@ -93,10 +180,24 @@ export function recordSessionEnd(dir: string, now: () => Date = () => new Date()
|
|
|
93
180
|
return event;
|
|
94
181
|
}
|
|
95
182
|
|
|
96
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Finalize the session for a raw Claude stdin payload (task 1159, testable half).
|
|
185
|
+
*
|
|
186
|
+
* Malformed/absent text carries no identity, so shutdown fails closed; the dir resolution lives here
|
|
187
|
+
* rather than in the entrypoint so the untestable subprocess body stays a few lines.
|
|
188
|
+
*/
|
|
189
|
+
export function recordSessionEndFromPayloadText(
|
|
190
|
+
text: string,
|
|
191
|
+
env: NodeJS.ProcessEnv = getEnvVars(),
|
|
192
|
+
): Record<string, unknown> | null {
|
|
193
|
+
const dir = join(getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd(), '.spur', 'context');
|
|
194
|
+
return recordSessionEnd(dir, undefined, claudeHookOwnerFromPayloadText(text, env) ?? undefined);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Entrypoint — thin stdin adapter (fail-open); logic lives in the exported core above.
|
|
97
198
|
if (import.meta.main) {
|
|
98
199
|
try {
|
|
99
|
-
|
|
200
|
+
recordSessionEndFromPayloadText(await Bun.stdin.text());
|
|
100
201
|
} catch {
|
|
101
202
|
/* fail-open: every error path exits 0 */
|
|
102
203
|
}
|
|
@@ -26,11 +26,11 @@ import { spawnSync } from 'node:child_process';
|
|
|
26
26
|
import { existsSync, realpathSync } from 'node:fs';
|
|
27
27
|
import { homedir } from 'node:os';
|
|
28
28
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
29
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
29
|
+
import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
30
30
|
import { getEnvVar, getEnvVars } from '../../lib/env';
|
|
31
31
|
import { reportLifecycleHook } from '../agent-lifecycle';
|
|
32
32
|
import { recordToolUseEvent, type ToolPayload } from '../context-post-tool';
|
|
33
|
-
import { recordSessionStart } from '../context-session-start';
|
|
33
|
+
import { recordSessionStart, type SessionOwner } from '../context-session-start';
|
|
34
34
|
import { recordSessionEnd } from '../context-session-stop';
|
|
35
35
|
import { classifyCommand } from '../destructive-policy';
|
|
36
36
|
import { couldBeTaskFile } from '../task-file-policy';
|
|
@@ -44,6 +44,16 @@ const spurContextDir = (): string => join(process.cwd(), '.spur', 'context');
|
|
|
44
44
|
|
|
45
45
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Pi session owner identity (task 1159): the extension's own session-manager id, stable for the
|
|
49
|
+
* life of the Pi session and distinct from any nested child session. Read here — not from a PID or
|
|
50
|
+
* `SPUR_RUN_ID` — so a nested child's `session_shutdown` cannot claim the parent's pointer.
|
|
51
|
+
*/
|
|
52
|
+
function piSessionOwner(ctx: ExtensionContext): SessionOwner | null {
|
|
53
|
+
const sessionId = ctx.sessionManager.getSessionId().trim();
|
|
54
|
+
return sessionId.length > 0 ? { host: 'pi', sessionId } : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
type TaskOwnership = 'owned' | 'unowned' | 'unknown';
|
|
48
58
|
|
|
49
59
|
/**
|
|
@@ -238,32 +248,35 @@ export default function (pi: ExtensionAPI): void {
|
|
|
238
248
|
});
|
|
239
249
|
|
|
240
250
|
// ── context-post-tool: shared recordToolUseEvent (task 0969 R1) ──
|
|
241
|
-
pi.on('tool_result', async (event) => {
|
|
251
|
+
pi.on('tool_result', async (event, ctx) => {
|
|
242
252
|
try {
|
|
243
253
|
const payload = normalizePiToolEvent(
|
|
244
254
|
event.toolName,
|
|
245
255
|
event.input as Record<string, unknown> | undefined,
|
|
246
256
|
(event as { content?: unknown }).content,
|
|
247
257
|
);
|
|
248
|
-
|
|
258
|
+
// Host-native session id, as Claude's payload carries (H21 parity).
|
|
259
|
+
const sessionId = piSessionOwner(ctx)?.sessionId;
|
|
260
|
+
if (payload)
|
|
261
|
+
recordToolUseEvent(spurContextDir(), sessionId ? { ...payload, session_id: sessionId } : payload);
|
|
249
262
|
} catch {
|
|
250
263
|
// fail-open
|
|
251
264
|
}
|
|
252
265
|
});
|
|
253
266
|
|
|
254
267
|
// ── context-session-start: shared recordSessionStart (R2) ────────
|
|
255
|
-
pi.on('session_start', async () => {
|
|
268
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
256
269
|
try {
|
|
257
|
-
recordSessionStart(spurContextDir(), getEnvVars(), undefined, 'pi');
|
|
270
|
+
recordSessionStart(spurContextDir(), getEnvVars(), undefined, 'pi', piSessionOwner(ctx) ?? undefined);
|
|
258
271
|
} catch {
|
|
259
272
|
// fail-open
|
|
260
273
|
}
|
|
261
274
|
});
|
|
262
275
|
|
|
263
276
|
// ── context-session-stop: shared recordSessionEnd (R3) ──────────
|
|
264
|
-
pi.on('session_shutdown', async () => {
|
|
277
|
+
pi.on('session_shutdown', async (_event, ctx) => {
|
|
265
278
|
try {
|
|
266
|
-
recordSessionEnd(spurContextDir());
|
|
279
|
+
recordSessionEnd(spurContextDir(), undefined, piSessionOwner(ctx) ?? undefined);
|
|
267
280
|
} catch {
|
|
268
281
|
// fail-open
|
|
269
282
|
}
|
|
@@ -322,7 +322,7 @@ END`;var MEASURE_COLS="messages, tool_calls, skill_calls, fresh_input_tokens, ca
|
|
|
322
322
|
''
|
|
323
323
|
)
|
|
324
324
|
ELSE ''
|
|
325
|
-
END`;var POST_WATERMARK_TABLES=[...KEYED_ROLLUP_TABLES,...GLOBAL_RANKED_ROLLUP_TABLES,"history_daily_stats","history_board_source_daily"];var HISTORY_IMPORT_SCHEMA_VERSION="0.5.
|
|
325
|
+
END`;var POST_WATERMARK_TABLES=[...KEYED_ROLLUP_TABLES,...GLOBAL_RANKED_ROLLUP_TABLES,"history_daily_stats","history_board_source_daily"];var HISTORY_IMPORT_SCHEMA_VERSION="0.5.22",HISTORY_IMPORT_SCHEMA_SQL=`
|
|
326
326
|
CREATE TABLE IF NOT EXISTS history_import_checkpoint (
|
|
327
327
|
source TEXT NOT NULL,
|
|
328
328
|
source_file TEXT NOT NULL,
|
|
@@ -625,6 +625,8 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_inbox_messages_request_key ON inbox_messag
|
|
|
625
625
|
`,INBOX_MESSAGES_REQUEST_KEY_SCHEMA_SQL=`
|
|
626
626
|
ALTER TABLE inbox_messages ADD COLUMN request_key TEXT;
|
|
627
627
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_inbox_messages_request_key ON inbox_messages (request_key) WHERE request_key IS NOT NULL;
|
|
628
|
+
`,INBOX_MESSAGES_READ_AT_SCHEMA_SQL=`
|
|
629
|
+
ALTER TABLE inbox_messages ADD COLUMN read_at INTEGER;
|
|
628
630
|
`,QUEUE_JOBS_SCHEMA_SQL=`
|
|
629
631
|
CREATE TABLE IF NOT EXISTS queue_jobs (
|
|
630
632
|
id TEXT PRIMARY KEY,
|
|
@@ -1404,7 +1406,7 @@ ALTER TABLE queue_jobs ADD COLUMN timeout_ms INTEGER;
|
|
|
1404
1406
|
ALTER TABLE queue_jobs ADD COLUMN timeout_unlimited INTEGER NOT NULL DEFAULT 0;
|
|
1405
1407
|
ALTER TABLE queue_jobs ADD COLUMN attempt_token TEXT;
|
|
1406
1408
|
ALTER TABLE queue_jobs ADD COLUMN lease_expires_at INTEGER;
|
|
1407
|
-
`,CLI_MIGRATIONS=[{id:"0000_spur_cli_foundation",sql:CLI_SCHEMA_SQL},{id:"0001_spur_cli_team_inbox",sql:INBOX_MESSAGES_SCHEMA_SQL},{id:"0002_spur_cli_rule_history",sql:RULE_ENGINE_SCHEMA_SQL},{id:"0003_spur_cli_planning",sql:PLANNING_SCHEMA_SQL},{id:"0004_spur_cli_queue_jobs",sql:QUEUE_JOBS_SCHEMA_SQL},{id:"0005_spur_cli_run_pid",sql:RUN_PID_COLUMN_SCHEMA_SQL,addColumnIfMissing:{table:"runs",column:"pid"}},{id:"0006_spur_cli_system_events",sql:SYSTEM_EVENTS_SCHEMA_SQL},{id:"0007_spur_cli_runs_external_key",sql:RUNS_EXTERNAL_KEY_COLUMN_SCHEMA_SQL,addColumnIfMissing:{table:"runs",column:"external_key"}},{id:"0008_spur_cli_system_events_correlation",sql:SYSTEM_EVENTS_CORRELATION_COLUMNS_SCHEMA_SQL,addColumnIfMissing:{table:"system_events",column:"sequence"}},{id:"0009_spur_cli_history_message_run_idx",sql:HISTORY_MESSAGE_RUN_INDEX_SCHEMA_SQL},{id:"0010_spur_cli_coordination_runs",sql:COORDINATION_RUNS_SCHEMA_SQL},{id:"0011_spur_cli_system_events_sequence_idx",sql:SYSTEM_EVENTS_SEQUENCE_INDEX_SCHEMA_SQL},{id:"0012_spur_cli_history_tool_call_args_raw",sql:HISTORY_TOOL_CALL_ARGS_RAW_SCHEMA_SQL,addColumnIfMissing:{table:"history_tool_call",column:"args_raw"}},{id:"0013_spur_cli_history_run_session",sql:HISTORY_RUN_SESSION_SCHEMA_SQL},{id:"0014_spur_cli_system_events_name_occurred_idx",sql:SYSTEM_EVENTS_NAME_OCCURRED_INDEX_SCHEMA_SQL},{id:"0015_spur_cli_history_tool_call_call_id",sql:HISTORY_TOOL_CALL_CALL_ID_SCHEMA_SQL,addColumnIfMissing:{table:"history_tool_call",column:"call_id"}},{id:"0016_spur_cli_history_message_ts_nullable",sql:HISTORY_MESSAGE_TS_NULLABLE_SCHEMA_SQL},{id:"0017_spur_cli_runs_status_completed_to_done",sql:"UPDATE runs SET status = 'done' WHERE status = 'completed'"},{id:"0018_spur_cli_history_message_request_id",sql:HISTORY_MESSAGE_REQUEST_ID_SCHEMA_SQL,addColumnIfMissing:{table:"history_message",column:"request_id"}},{id:"0019_spur_cli_history_etl_tables_drop",sql:HISTORY_ETL_TABLES_DROP_SCHEMA_SQL},{id:"0020_spur_cli_history_board_query_indexes",sql:HISTORY_BOARD_QUERY_INDEXES_SCHEMA_SQL},{id:"0021_spur_cli_history_board_rollups",sql:HISTORY_BOARD_ROLLUPS_SCHEMA_SQL},{id:"0022_spur_cli_history_performance_indexes",sql:HISTORY_PERFORMANCE_INDEXES_SCHEMA_SQL},{id:"0023_spur_cli_history_message_request_id_idx",sql:HISTORY_MESSAGE_REQUEST_ID_INDEX_SCHEMA_SQL},{id:"0024_spur_cli_history_checkpoint_identity",sql:"ALTER TABLE history_import_checkpoint ADD COLUMN source_size INTEGER",addColumnIfMissing:{table:"history_import_checkpoint",column:"source_size"}},{id:"0025_spur_cli_history_checkpoint_identity_mtime",sql:"ALTER TABLE history_import_checkpoint ADD COLUMN source_mtime_ms REAL",addColumnIfMissing:{table:"history_import_checkpoint",column:"source_mtime_ms"}},{id:"0026_spur_cli_history_message_duration_source",sql:"ALTER TABLE history_message ADD COLUMN duration_source TEXT",addColumnIfMissing:{table:"history_message",column:"duration_source"}},{id:"0027_spur_cli_history_refresh_active_unique",sql:HISTORY_REFRESH_ACTIVE_UNIQUE_SCHEMA_SQL},{id:"0028_spur_cli_history_task_session",sql:HISTORY_TASK_SESSION_SCHEMA_SQL},{id:"0029_spur_cli_history_tool_call_indexes",sql:HISTORY_TOOL_CALL_INDEXES_SCHEMA_SQL},{id:"0030_spur_cli_history_board_covering_indexes",sql:HISTORY_BOARD_COVERING_INDEXES_SCHEMA_SQL},{id:"0031_spur_cli_history_board_tool_stats_columns",sql:HISTORY_BOARD_TOOL_STATS_COLUMNS_SCHEMA_SQL},{id:"0032_spur_cli_history_board_skill_5m",sql:HISTORY_BOARD_SKILL_5M_SCHEMA_SQL},{id:"0033_spur_cli_importer_schema_version",sql:`INSERT OR REPLACE INTO "__spur_cli_migrations" (id, applied_at) VALUES ('${IMPORTER_SCHEMA_LEDGER_PREFIX}${HISTORY_IMPORT_SCHEMA_VERSION}', strftime('%s', 'now') * 1000);`},{id:"0034_spur_cli_history_tool_identity",sql:HISTORY_TOOL_IDENTITY_SCHEMA_SQL},{id:"0035_spur_cli_history_measure_vector",sql:HISTORY_MEASURE_VECTOR_SCHEMA_SQL},{id:"0036_spur_cli_history_rollup_watermark",sql:HISTORY_ROLLUP_WATERMARK_SCHEMA_SQL},{id:"0037_spur_cli_history_dimension_marts",sql:HISTORY_DIMENSION_MARTS_SCHEMA_SQL},{id:"0038_spur_cli_retention_compaction_meta",sql:RETENTION_COMPACTION_META_SCHEMA_SQL},{id:"0039_spur_cli_bounded_rollup_derivations",sql:BOUNDED_ROLLUP_DERIVATIONS_SCHEMA_SQL,addColumnIfMissing:{table:"history_board_source_daily",column:"raw_messages"}},{id:"0040_spur_cli_agent_executor_updates",sql:AGENT_EXECUTOR_UPDATES_SCHEMA_SQL},{id:"0041_spur_cli_queue_jobs_deadline_lease_columns",sql:QUEUE_JOBS_DEADLINE_LEASE_COLUMNS_SCHEMA_SQL,addColumnIfMissing:{table:"queue_jobs",column:"timeout_ms"}},{id:"0042_spur_cli_history_model_fallback_index",sql:HISTORY_MESSAGE_MODEL_FALLBACK_INDEX_SCHEMA_SQL},{id:"0043_spur_cli_inbox_messages_request_key",sql:INBOX_MESSAGES_REQUEST_KEY_SCHEMA_SQL,addColumnIfMissing:{table:"inbox_messages",column:"request_key"}},{id:"0044_spur_cli_coordination_runs_receipt_columns",sql:COORDINATION_RUNS_RECEIPT_COLUMNS_SCHEMA_SQL,addColumnIfMissing:{table:"coordination_runs",column:"message_ids_json"}},{id:"0045_spur_cli_project_claims",sql:PROJECT_CLAIMS_SCHEMA_SQL},{id:"0046_spur_cli_project_strategy",sql:PROJECT_STRATEGY_SCHEMA_SQL},{id:"0047_spur_cli_scheduler_custom_active_unique",sql:SCHEDULER_CUSTOM_ACTIVE_UNIQUE_SCHEMA_SQL},{id:"0048_spur_cli_agent_executor_updates_owner_columns",sql:AGENT_EXECUTOR_UPDATES_OWNER_COLUMNS_SCHEMA_SQL,addColumnIfMissing:{table:"agent_executor_updates",column:"owner"}},{id:"0049_spur_cli_runs_terminal_reason",sql:CLI_RUNS_TERMINAL_REASON_SCHEMA_SQL,addColumnIfMissing:{table:"runs",column:"terminal_reason"}},{id:"0050_spur_cli_history_board_skill_5m_capability_grain",sql:HISTORY_BOARD_SKILL_5M_CAPABILITY_GRAIN_SCHEMA_SQL},{id:"0051_spur_cli_coordination_runs_parent",sql:COORDINATION_RUNS_PARENT_SCHEMA_SQL,addColumnIfMissing:{table:"coordination_runs",column:"parent_run_id"}},{id:"0052_spur_cli_decision_logs",sql:DECISION_LOGS_SCHEMA_SQL}];init_zod();init_zod();var STAGE_REGISTRY_SCHEMA_VERSION={major:1,minor:3},stageSchemaVersionSchema=exports_external.object({major:exports_external.number().int().nonnegative(),minor:exports_external.number().int().nonnegative()}).strict();var MUTATION_CLASSES=["none","corpus","code","tests","verdict","learnings","driver","irreversible"];var inlineExecutionSchema=exports_external.object({kind:exports_external.literal("inline"),current_agent_allowed:exports_external.literal(!0),may_reuse_captured_layers:exports_external.boolean().optional()}).strict(),subprocessExecutionSchema=exports_external.object({kind:exports_external.literal("subprocess"),current_agent_allowed:exports_external.literal(!1),via:exports_external.literal("spur-agent-run")}).strict(),deterministicExecutionSchema=exports_external.object({kind:exports_external.literal("deterministic"),current_agent_allowed:exports_external.literal(!1),executor:exports_external.enum(["cli","script"])}).strict(),hitlExecutionSchema=exports_external.object({kind:exports_external.literal("hitl"),current_agent_allowed:exports_external.literal(!0),gate_timing:exports_external.enum(["pre","post","both"])}).strict(),irreversibleExecutionSchema=exports_external.object({kind:exports_external.literal("irreversible"),requires_operator_intent:exports_external.literal(!0),current_agent_allowed:exports_external.boolean(),rollback_disclaimer:exports_external.string().min(1)}).strict(),executionKindSchema=exports_external.discriminatedUnion("kind",[inlineExecutionSchema,subprocessExecutionSchema,deterministicExecutionSchema,hitlExecutionSchema,irreversibleExecutionSchema]),ARTIFACT_DIRECTIONS=["input","output"],stageArtifactSchema=exports_external.object({kind:exports_external.string().min(1),direction:exports_external.enum(ARTIFACT_DIRECTIONS),identity:exports_external.string().optional(),description:exports_external.string().optional(),required:exports_external.boolean().optional().default(!0)}).strict(),stageGateSchema=exports_external.object({name:exports_external.string().min(1),timing:exports_external.enum(["pre","post","transition"]),min_verdict:exports_external.enum(["pass","partial","fail"]).optional(),description:exports_external.string().optional()}).strict(),stageRetryPolicySchema=exports_external.object({max_attempts:exports_external.number().int().min(1),terminal_stop:exports_external.enum(["block","escalate","fail"]),timeout_seconds:exports_external.number().int().positive().optional()}).strict(),CAPABILITY_TIERS=["cheap","standard","capable-1","capable-2","capable-3"],LEGACY_CAPABLE_ALIAS="capable";function normalizeCapabilityTier(value){return value===LEGACY_CAPABLE_ALIAS?"capable-1":value}var capabilityTierSchema=exports_external.preprocess(normalizeCapabilityTier,exports_external.enum(CAPABILITY_TIERS)),objectiveEscalationTriggerSchema=exports_external.enum(["gate-fail","timeout","insufficient-evidence","retry-exhausted","resource-exhaustion","auth"]),stageModelPolicySchema=exports_external.object({min_tier:capabilityTierSchema,fallback:exports_external.array(exports_external.object({tier:capabilityTierSchema,trigger:objectiveEscalationTriggerSchema}).strict()),override_key:exports_external.string().optional()}).strict();var CONTEXT_LAYER_NAMES=["harness-policy","project-authority","stage-contract","task-state","indexed-evidence","run-state","tool-observations"],stageContextLayerSchema=exports_external.object({layer:exports_external.enum(CONTEXT_LAYER_NAMES),required:exports_external.literal(!0)}).strict(),stageEventSchema=exports_external.object({name:exports_external.string().min(1),description:exports_external.string().optional()}).strict(),STAGE_ID_PATTERN=/^[a-z][a-z0-9-]*$/,stageRecordSchema=exports_external.object({schema_version:stageSchemaVersionSchema,id:exports_external.string().regex(STAGE_ID_PATTERN,{message:"stage id must be lowercase kebab-case"}),aliases:exports_external.array(exports_external.string().min(1)).optional().default([]),description:exports_external.string().min(1),artifacts:exports_external.array(stageArtifactSchema).min(1),reasoning_skill:exports_external.string().min(1),required_references:exports_external.array(exports_external.string().min(1)).optional().default([]),gates:exports_external.array(stageGateSchema).optional().default([]),mutation_class:exports_external.enum(MUTATION_CLASSES),retry:stageRetryPolicySchema,model_policy:stageModelPolicySchema,context_layers:exports_external.array(stageContextLayerSchema).optional().default([]),observability:exports_external.array(stageEventSchema).optional().default([]),execution:executionKindSchema}).strict();var REGISTERED_CANONICAL_STAGES=[{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"refine",aliases:["dev-refine"],description:"dev-refine: Q&A refinement, section filling, AC tightening",artifacts:[{kind:"task-section",direction:"input",required:!0,description:"Background/Requirements sections"},{kind:"task-section",direction:"output",required:!0,description:"Q&A/Design/Plan/AC sections"}],reasoning_skill:"sp:spur-dev",required_references:[],gates:[],mutation_class:"corpus",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[{tier:"capable-2",trigger:"gate-fail"},{tier:"capable-2",trigger:"auth"},{tier:"capable-2",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"plan",aliases:["dev-plan"],description:"dev-plan: feature intake -> AC generation -> decomposition",artifacts:[{kind:"feature-frontmatter",direction:"input",required:!0},{kind:"task-batch",direction:"output",required:!0}],reasoning_skill:"sp:spur-dev",required_references:[],gates:[{name:"feature-check",timing:"post",min_verdict:"pass"},{name:"batch-create",timing:"post",min_verdict:"pass"}],mutation_class:"corpus",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{min_tier:"capable-2",fallback:[{tier:"capable-3",trigger:"gate-fail"},{tier:"capable-3",trigger:"auth"},{tier:"capable-3",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"implement",aliases:["dev-run"],description:"dev-run --mode implement: code edits in worktree",artifacts:[{kind:"worktree-diff",direction:"output",required:!0},{kind:"task-section",direction:"output",required:!0,identity:"Solution"}],reasoning_skill:"sp:code-implementation",required_references:[],gates:[],mutation_class:"code",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[{tier:"capable-1",trigger:"gate-fail"},{tier:"capable-1",trigger:"timeout"},{tier:"capable-1",trigger:"auth"},{tier:"capable-1",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"test",aliases:["dev-unit","dev-fixall"],description:"dev-unit: generate/extend tests to coverage target",artifacts:[{kind:"test-file",direction:"output",required:!0},{kind:"coverage-report",direction:"output",required:!1}],reasoning_skill:"sp:code-testing",required_references:[],gates:[],mutation_class:"tests",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{fallback:[{tier:"capable-1",trigger:"gate-fail"},{tier:"capable-1",trigger:"auth"},{tier:"capable-1",trigger:"resource-exhaustion"}],min_tier:"standard"},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"verify",aliases:["dev-verify"],description:"dev-verify: SECUA review + requirements traceability",artifacts:[{kind:"worktree-diff",direction:"input",required:!0},{kind:"verdict-artifact",direction:"output",required:!0,identity:"<wbs>-verdict.json"}],reasoning_skill:"sp:code-verification",required_references:[],gates:[{name:"verdict-artifact",timing:"post",min_verdict:"pass"},{name:"strict-core",timing:"post",min_verdict:"pass"}],mutation_class:"verdict",retry:{max_attempts:2,terminal_stop:"escalate"},model_policy:{min_tier:"capable-1",fallback:[{tier:"capable-2",trigger:"auth"},{tier:"capable-2",trigger:"resource-exhaustion"},{tier:"capable-2",trigger:"gate-fail"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"wrap",aliases:["dev-wrap"],description:"dev-wrap: learnings/doc-sync/feature transition",artifacts:[{kind:"learning-entry",direction:"output",required:!0}],reasoning_skill:"sp:spur-dev",required_references:[],gates:[{name:"task-check",timing:"pre",min_verdict:"pass"}],mutation_class:"learnings",retry:{max_attempts:2,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[{tier:"capable-1",trigger:"gate-fail"},{tier:"capable-1",trigger:"auth"},{tier:"capable-1",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"hitl",current_agent_allowed:!0,gate_timing:"both"}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"review",aliases:["dev-review"],description:"dev-review: multi-dimensional code review (coordinator writes the combined Review)",artifacts:[{kind:"review-findings",direction:"output",required:!0,identity:"Review"}],reasoning_skill:"sp:code-verification",required_references:[],gates:[],mutation_class:"verdict",retry:{max_attempts:2,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[{tier:"capable-1",trigger:"gate-fail"},{tier:"capable-1",trigger:"auth"},{tier:"capable-1",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"record",aliases:["dev-record"],description:"record: deterministic Testing write-back from the verdict artifact; bare-Review fallback only",artifacts:[{kind:"verdict-artifact",direction:"input",required:!0,identity:"<wbs>-verdict.json"},{kind:"task-section",direction:"output",required:!0,identity:"Testing"},{kind:"task-section",direction:"output",required:!1,identity:"Review",description:"fallback-only: backfills Review only when the section is bare; never overwrites authored Review"}],reasoning_skill:"inline",required_references:[],gates:[],mutation_class:"corpus",retry:{max_attempts:1,terminal_stop:"block"},model_policy:{min_tier:"cheap",fallback:[]},context_layers:[],observability:[],execution:{kind:"deterministic",current_agent_allowed:!1,executor:"cli"}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"dogfood",aliases:["dev-dogfood"],description:"dev-dogfood: end-to-end driver test",artifacts:[{kind:"dogfood-report",direction:"output",required:!0},{kind:"monitor-ledger",direction:"output",required:!1}],reasoning_skill:"sp:dogfood-testing",required_references:[],gates:[],mutation_class:"driver",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{min_tier:"capable-1",fallback:[{tier:"capable-2",trigger:"auth"},{tier:"capable-2",trigger:"resource-exhaustion"},{tier:"capable-2",trigger:"gate-fail"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"brainstorm",aliases:["dev-brainstorm"],description:"dev-brainstorm: structured ideation",artifacts:[{kind:"brainstorm-outline",direction:"output",required:!0}],reasoning_skill:"sp:brainstorm",required_references:[],gates:[],mutation_class:"corpus",retry:{max_attempts:2,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"changelog",aliases:["dev-changelog"],description:"dev-changelog: generate changelog from git commits",artifacts:[{kind:"changelog-entry",direction:"output",required:!0}],reasoning_skill:"inline",required_references:[],gates:[],mutation_class:"none",retry:{max_attempts:1,terminal_stop:"block"},model_policy:{min_tier:"cheap",fallback:[]},context_layers:[],observability:[],execution:{kind:"deterministic",current_agent_allowed:!1,executor:"cli"}}],CANONICAL_STAGE_BY_KEY=new Map;for(let s2 of REGISTERED_CANONICAL_STAGES){CANONICAL_STAGE_BY_KEY.set(s2.id,s2);for(let a2 of s2.aliases??[])CANONICAL_STAGE_BY_KEY.set(a2,s2)}var CACHEABILITY_CLASSES=["stable-prefix-eligible","volatile"],SENSITIVITY_CLASSES=["public","internal","confidential"],envelopeProvenanceSchema=exports_external.object({owner:exports_external.string().min(1),schema_version:exports_external.string().min(1),source_revision:exports_external.string().nullable().optional(),generated_at:exports_external.string().datetime()}).strict(),envelopeSizeBudgetSchema=exports_external.object({max_bytes:exports_external.number().int().positive()}).strict(),envelopeLayerSchema=exports_external.object({layer:exports_external.enum(CONTEXT_LAYER_NAMES),content:exports_external.string(),size_bytes:exports_external.number().int().nonnegative(),content_hash:exports_external.string().length(64),provenance:envelopeProvenanceSchema,cacheability:exports_external.enum(CACHEABILITY_CLASSES),sensitivity:exports_external.enum(SENSITIVITY_CLASSES).default("internal"),size_budget:envelopeSizeBudgetSchema.optional(),disclosure_handle:exports_external.string().optional()}).strict(),STABLE_LAYER_NAMES=["harness-policy","project-authority","stage-contract","task-state","indexed-evidence"],VOLATILE_LAYER_NAMES=["run-state","tool-observations"];var envelopeSchema=exports_external.object({schema_version:exports_external.string().default("1.0"),layers:exports_external.array(envelopeLayerSchema).min(1),stage_id:exports_external.string().optional(),run_id:exports_external.string().optional(),assembled_at:exports_external.string().datetime(),total_size_bytes:exports_external.number().int().nonnegative()}).strict(),projectSnapshotSchema=exports_external.object({name:exports_external.string(),version:exports_external.string(),task_counts:exports_external.object({backlog:exports_external.number().int().nonnegative(),todo:exports_external.number().int().nonnegative(),wip:exports_external.number().int().nonnegative(),testing:exports_external.number().int().nonnegative(),done:exports_external.number().int().nonnegative(),blocked:exports_external.number().int().nonnegative(),cancelled:exports_external.number().int().nonnegative()}).strict(),feature_counts:exports_external.object({backlog:exports_external.number().int().nonnegative(),active:exports_external.number().int().nonnegative(),verifying:exports_external.number().int().nonnegative(),done:exports_external.number().int().nonnegative(),blocked:exports_external.number().int().nonnegative(),cancelled:exports_external.number().int().nonnegative()}).strict(),active_features:exports_external.array(exports_external.string()),content_hash:exports_external.string().length(64).optional()}).strict(),taskSnapshotSchema=exports_external.object({wbs:exports_external.string().regex(/^\d{4}$/),name:exports_external.string(),status:exports_external.string(),priority:exports_external.string().optional(),feature_id:exports_external.string().optional(),acceptance_criteria:exports_external.string().optional(),locked_qa:exports_external.array(exports_external.string()).optional(),solution:exports_external.string().optional(),content_hash:exports_external.string().length(64).optional()}).strict(),featureSnapshotSchema=exports_external.object({id:exports_external.string(),name:exports_external.string(),status:exports_external.string(),priority:exports_external.string().optional(),scenarios:exports_external.array(exports_external.string()).optional(),content_hash:exports_external.string().length(64).optional()}).strict();var ALL_LAYER_NAMES=[...STABLE_LAYER_NAMES,...VOLATILE_LAYER_NAMES],LAYER_ORDER={};ALL_LAYER_NAMES.forEach((name,idx)=>{LAYER_ORDER[name]=idx});init_dist2();var TASK_CANONICAL_SECTIONS=["Background","Requirements","Acceptance Criteria","Q&A","Design","Plan","Root Cause","Solution","Testing","Review","References","History"],FEATURE_CANONICAL_SECTIONS=["Goal","Scope","Acceptance Criteria","Tasks","Notes","History"],UNIVERSAL_SECTIONS=["History","References","Notes"],HEADING_LEVELS={task:3,feature:2},START_MARKER="<!-- AUTO-GENERATED by spur feature refresh -->",END_MARKER="<!-- END AUTO-GENERATED -->",MARKER_START_RE=/<!--[ \t]*AUTO-GENERATED.*?-->/,MARKER_END_RE=/<!--[ \t]*END[ \t]*AUTO-GENERATED.*?-->/;function findHeadings(body,hashes){let hits=[],prefix=`${hashes} `,inCodeBlock=!1,lineStart=0;for(let i2=0;i2<=body.length;i2++){if(i2!==body.length&&body[i2]!==`
|
|
1409
|
+
`,CLI_MIGRATIONS=[{id:"0000_spur_cli_foundation",sql:CLI_SCHEMA_SQL},{id:"0001_spur_cli_team_inbox",sql:INBOX_MESSAGES_SCHEMA_SQL},{id:"0002_spur_cli_rule_history",sql:RULE_ENGINE_SCHEMA_SQL},{id:"0003_spur_cli_planning",sql:PLANNING_SCHEMA_SQL},{id:"0004_spur_cli_queue_jobs",sql:QUEUE_JOBS_SCHEMA_SQL},{id:"0005_spur_cli_run_pid",sql:RUN_PID_COLUMN_SCHEMA_SQL,addColumnIfMissing:{table:"runs",column:"pid"}},{id:"0006_spur_cli_system_events",sql:SYSTEM_EVENTS_SCHEMA_SQL},{id:"0007_spur_cli_runs_external_key",sql:RUNS_EXTERNAL_KEY_COLUMN_SCHEMA_SQL,addColumnIfMissing:{table:"runs",column:"external_key"}},{id:"0008_spur_cli_system_events_correlation",sql:SYSTEM_EVENTS_CORRELATION_COLUMNS_SCHEMA_SQL,addColumnIfMissing:{table:"system_events",column:"sequence"}},{id:"0009_spur_cli_history_message_run_idx",sql:HISTORY_MESSAGE_RUN_INDEX_SCHEMA_SQL},{id:"0010_spur_cli_coordination_runs",sql:COORDINATION_RUNS_SCHEMA_SQL},{id:"0011_spur_cli_system_events_sequence_idx",sql:SYSTEM_EVENTS_SEQUENCE_INDEX_SCHEMA_SQL},{id:"0012_spur_cli_history_tool_call_args_raw",sql:HISTORY_TOOL_CALL_ARGS_RAW_SCHEMA_SQL,addColumnIfMissing:{table:"history_tool_call",column:"args_raw"}},{id:"0013_spur_cli_history_run_session",sql:HISTORY_RUN_SESSION_SCHEMA_SQL},{id:"0014_spur_cli_system_events_name_occurred_idx",sql:SYSTEM_EVENTS_NAME_OCCURRED_INDEX_SCHEMA_SQL},{id:"0015_spur_cli_history_tool_call_call_id",sql:HISTORY_TOOL_CALL_CALL_ID_SCHEMA_SQL,addColumnIfMissing:{table:"history_tool_call",column:"call_id"}},{id:"0016_spur_cli_history_message_ts_nullable",sql:HISTORY_MESSAGE_TS_NULLABLE_SCHEMA_SQL},{id:"0017_spur_cli_runs_status_completed_to_done",sql:"UPDATE runs SET status = 'done' WHERE status = 'completed'"},{id:"0018_spur_cli_history_message_request_id",sql:HISTORY_MESSAGE_REQUEST_ID_SCHEMA_SQL,addColumnIfMissing:{table:"history_message",column:"request_id"}},{id:"0019_spur_cli_history_etl_tables_drop",sql:HISTORY_ETL_TABLES_DROP_SCHEMA_SQL},{id:"0020_spur_cli_history_board_query_indexes",sql:HISTORY_BOARD_QUERY_INDEXES_SCHEMA_SQL},{id:"0021_spur_cli_history_board_rollups",sql:HISTORY_BOARD_ROLLUPS_SCHEMA_SQL},{id:"0022_spur_cli_history_performance_indexes",sql:HISTORY_PERFORMANCE_INDEXES_SCHEMA_SQL},{id:"0023_spur_cli_history_message_request_id_idx",sql:HISTORY_MESSAGE_REQUEST_ID_INDEX_SCHEMA_SQL},{id:"0024_spur_cli_history_checkpoint_identity",sql:"ALTER TABLE history_import_checkpoint ADD COLUMN source_size INTEGER",addColumnIfMissing:{table:"history_import_checkpoint",column:"source_size"}},{id:"0025_spur_cli_history_checkpoint_identity_mtime",sql:"ALTER TABLE history_import_checkpoint ADD COLUMN source_mtime_ms REAL",addColumnIfMissing:{table:"history_import_checkpoint",column:"source_mtime_ms"}},{id:"0026_spur_cli_history_message_duration_source",sql:"ALTER TABLE history_message ADD COLUMN duration_source TEXT",addColumnIfMissing:{table:"history_message",column:"duration_source"}},{id:"0027_spur_cli_history_refresh_active_unique",sql:HISTORY_REFRESH_ACTIVE_UNIQUE_SCHEMA_SQL},{id:"0028_spur_cli_history_task_session",sql:HISTORY_TASK_SESSION_SCHEMA_SQL},{id:"0029_spur_cli_history_tool_call_indexes",sql:HISTORY_TOOL_CALL_INDEXES_SCHEMA_SQL},{id:"0030_spur_cli_history_board_covering_indexes",sql:HISTORY_BOARD_COVERING_INDEXES_SCHEMA_SQL},{id:"0031_spur_cli_history_board_tool_stats_columns",sql:HISTORY_BOARD_TOOL_STATS_COLUMNS_SCHEMA_SQL},{id:"0032_spur_cli_history_board_skill_5m",sql:HISTORY_BOARD_SKILL_5M_SCHEMA_SQL},{id:"0033_spur_cli_importer_schema_version",sql:`INSERT OR REPLACE INTO "__spur_cli_migrations" (id, applied_at) VALUES ('${IMPORTER_SCHEMA_LEDGER_PREFIX}${HISTORY_IMPORT_SCHEMA_VERSION}', strftime('%s', 'now') * 1000);`},{id:"0034_spur_cli_history_tool_identity",sql:HISTORY_TOOL_IDENTITY_SCHEMA_SQL},{id:"0035_spur_cli_history_measure_vector",sql:HISTORY_MEASURE_VECTOR_SCHEMA_SQL},{id:"0036_spur_cli_history_rollup_watermark",sql:HISTORY_ROLLUP_WATERMARK_SCHEMA_SQL},{id:"0037_spur_cli_history_dimension_marts",sql:HISTORY_DIMENSION_MARTS_SCHEMA_SQL},{id:"0038_spur_cli_retention_compaction_meta",sql:RETENTION_COMPACTION_META_SCHEMA_SQL},{id:"0039_spur_cli_bounded_rollup_derivations",sql:BOUNDED_ROLLUP_DERIVATIONS_SCHEMA_SQL,addColumnIfMissing:{table:"history_board_source_daily",column:"raw_messages"}},{id:"0040_spur_cli_agent_executor_updates",sql:AGENT_EXECUTOR_UPDATES_SCHEMA_SQL},{id:"0041_spur_cli_queue_jobs_deadline_lease_columns",sql:QUEUE_JOBS_DEADLINE_LEASE_COLUMNS_SCHEMA_SQL,addColumnIfMissing:{table:"queue_jobs",column:"timeout_ms"}},{id:"0042_spur_cli_history_model_fallback_index",sql:HISTORY_MESSAGE_MODEL_FALLBACK_INDEX_SCHEMA_SQL},{id:"0043_spur_cli_inbox_messages_request_key",sql:INBOX_MESSAGES_REQUEST_KEY_SCHEMA_SQL,addColumnIfMissing:{table:"inbox_messages",column:"request_key"}},{id:"0044_spur_cli_coordination_runs_receipt_columns",sql:COORDINATION_RUNS_RECEIPT_COLUMNS_SCHEMA_SQL,addColumnIfMissing:{table:"coordination_runs",column:"message_ids_json"}},{id:"0045_spur_cli_project_claims",sql:PROJECT_CLAIMS_SCHEMA_SQL},{id:"0046_spur_cli_project_strategy",sql:PROJECT_STRATEGY_SCHEMA_SQL},{id:"0047_spur_cli_scheduler_custom_active_unique",sql:SCHEDULER_CUSTOM_ACTIVE_UNIQUE_SCHEMA_SQL},{id:"0048_spur_cli_agent_executor_updates_owner_columns",sql:AGENT_EXECUTOR_UPDATES_OWNER_COLUMNS_SCHEMA_SQL,addColumnIfMissing:{table:"agent_executor_updates",column:"owner"}},{id:"0049_spur_cli_runs_terminal_reason",sql:CLI_RUNS_TERMINAL_REASON_SCHEMA_SQL,addColumnIfMissing:{table:"runs",column:"terminal_reason"}},{id:"0050_spur_cli_history_board_skill_5m_capability_grain",sql:HISTORY_BOARD_SKILL_5M_CAPABILITY_GRAIN_SCHEMA_SQL},{id:"0051_spur_cli_coordination_runs_parent",sql:COORDINATION_RUNS_PARENT_SCHEMA_SQL,addColumnIfMissing:{table:"coordination_runs",column:"parent_run_id"}},{id:"0052_spur_cli_decision_logs",sql:DECISION_LOGS_SCHEMA_SQL},{id:"0053_spur_cli_inbox_messages_read_at",sql:INBOX_MESSAGES_READ_AT_SCHEMA_SQL,addColumnIfMissing:{table:"inbox_messages",column:"read_at"}}];init_zod();init_zod();var STAGE_REGISTRY_SCHEMA_VERSION={major:1,minor:3},stageSchemaVersionSchema=exports_external.object({major:exports_external.number().int().nonnegative(),minor:exports_external.number().int().nonnegative()}).strict();var MUTATION_CLASSES=["none","corpus","code","tests","verdict","learnings","driver","irreversible"];var inlineExecutionSchema=exports_external.object({kind:exports_external.literal("inline"),current_agent_allowed:exports_external.literal(!0),may_reuse_captured_layers:exports_external.boolean().optional()}).strict(),subprocessExecutionSchema=exports_external.object({kind:exports_external.literal("subprocess"),current_agent_allowed:exports_external.literal(!1),via:exports_external.literal("spur-agent-run")}).strict(),deterministicExecutionSchema=exports_external.object({kind:exports_external.literal("deterministic"),current_agent_allowed:exports_external.literal(!1),executor:exports_external.enum(["cli","script"])}).strict(),hitlExecutionSchema=exports_external.object({kind:exports_external.literal("hitl"),current_agent_allowed:exports_external.literal(!0),gate_timing:exports_external.enum(["pre","post","both"])}).strict(),irreversibleExecutionSchema=exports_external.object({kind:exports_external.literal("irreversible"),requires_operator_intent:exports_external.literal(!0),current_agent_allowed:exports_external.boolean(),rollback_disclaimer:exports_external.string().min(1)}).strict(),executionKindSchema=exports_external.discriminatedUnion("kind",[inlineExecutionSchema,subprocessExecutionSchema,deterministicExecutionSchema,hitlExecutionSchema,irreversibleExecutionSchema]),ARTIFACT_DIRECTIONS=["input","output"],stageArtifactSchema=exports_external.object({kind:exports_external.string().min(1),direction:exports_external.enum(ARTIFACT_DIRECTIONS),identity:exports_external.string().optional(),description:exports_external.string().optional(),required:exports_external.boolean().optional().default(!0)}).strict(),stageGateSchema=exports_external.object({name:exports_external.string().min(1),timing:exports_external.enum(["pre","post","transition"]),min_verdict:exports_external.enum(["pass","partial","fail"]).optional(),description:exports_external.string().optional()}).strict(),stageRetryPolicySchema=exports_external.object({max_attempts:exports_external.number().int().min(1),terminal_stop:exports_external.enum(["block","escalate","fail"]),timeout_seconds:exports_external.number().int().positive().optional()}).strict(),CAPABILITY_TIERS=["cheap","standard","capable-1","capable-2","capable-3"],LEGACY_CAPABLE_ALIAS="capable";function normalizeCapabilityTier(value){return value===LEGACY_CAPABLE_ALIAS?"capable-1":value}var capabilityTierSchema=exports_external.preprocess(normalizeCapabilityTier,exports_external.enum(CAPABILITY_TIERS)),objectiveEscalationTriggerSchema=exports_external.enum(["gate-fail","timeout","insufficient-evidence","retry-exhausted","resource-exhaustion","auth"]),stageModelPolicySchema=exports_external.object({min_tier:capabilityTierSchema,fallback:exports_external.array(exports_external.object({tier:capabilityTierSchema,trigger:objectiveEscalationTriggerSchema}).strict()),override_key:exports_external.string().optional()}).strict();var CONTEXT_LAYER_NAMES=["harness-policy","project-authority","stage-contract","task-state","indexed-evidence","run-state","tool-observations"],stageContextLayerSchema=exports_external.object({layer:exports_external.enum(CONTEXT_LAYER_NAMES),required:exports_external.literal(!0)}).strict(),stageEventSchema=exports_external.object({name:exports_external.string().min(1),description:exports_external.string().optional()}).strict(),STAGE_ID_PATTERN=/^[a-z][a-z0-9-]*$/,stageRecordSchema=exports_external.object({schema_version:stageSchemaVersionSchema,id:exports_external.string().regex(STAGE_ID_PATTERN,{message:"stage id must be lowercase kebab-case"}),aliases:exports_external.array(exports_external.string().min(1)).optional().default([]),description:exports_external.string().min(1),artifacts:exports_external.array(stageArtifactSchema).min(1),reasoning_skill:exports_external.string().min(1),required_references:exports_external.array(exports_external.string().min(1)).optional().default([]),gates:exports_external.array(stageGateSchema).optional().default([]),mutation_class:exports_external.enum(MUTATION_CLASSES),retry:stageRetryPolicySchema,model_policy:stageModelPolicySchema,context_layers:exports_external.array(stageContextLayerSchema).optional().default([]),observability:exports_external.array(stageEventSchema).optional().default([]),execution:executionKindSchema}).strict();var REGISTERED_CANONICAL_STAGES=[{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"refine",aliases:["dev-refine"],description:"dev-refine: Q&A refinement, section filling, AC tightening",artifacts:[{kind:"task-section",direction:"input",required:!0,description:"Background/Requirements sections"},{kind:"task-section",direction:"output",required:!0,description:"Q&A/Design/Plan/AC sections"}],reasoning_skill:"sp:spur-dev",required_references:[],gates:[],mutation_class:"corpus",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[{tier:"capable-2",trigger:"gate-fail"},{tier:"capable-2",trigger:"auth"},{tier:"capable-2",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"plan",aliases:["dev-plan"],description:"dev-plan: feature intake -> AC generation -> decomposition",artifacts:[{kind:"feature-frontmatter",direction:"input",required:!0},{kind:"task-batch",direction:"output",required:!0}],reasoning_skill:"sp:spur-dev",required_references:[],gates:[{name:"feature-check",timing:"post",min_verdict:"pass"},{name:"batch-create",timing:"post",min_verdict:"pass"}],mutation_class:"corpus",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{min_tier:"capable-2",fallback:[{tier:"capable-3",trigger:"gate-fail"},{tier:"capable-3",trigger:"auth"},{tier:"capable-3",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"implement",aliases:["dev-run"],description:"dev-run --mode implement: code edits in worktree",artifacts:[{kind:"worktree-diff",direction:"output",required:!0},{kind:"task-section",direction:"output",required:!0,identity:"Solution"}],reasoning_skill:"sp:code-implementation",required_references:[],gates:[],mutation_class:"code",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[{tier:"capable-1",trigger:"gate-fail"},{tier:"capable-1",trigger:"timeout"},{tier:"capable-1",trigger:"auth"},{tier:"capable-1",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"test",aliases:["dev-unit","dev-fixall"],description:"dev-unit: generate/extend tests to coverage target",artifacts:[{kind:"test-file",direction:"output",required:!0},{kind:"coverage-report",direction:"output",required:!1}],reasoning_skill:"sp:code-testing",required_references:[],gates:[],mutation_class:"tests",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{fallback:[{tier:"capable-1",trigger:"gate-fail"},{tier:"capable-1",trigger:"auth"},{tier:"capable-1",trigger:"resource-exhaustion"}],min_tier:"standard"},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"verify",aliases:["dev-verify"],description:"dev-verify: SECUA review + requirements traceability",artifacts:[{kind:"worktree-diff",direction:"input",required:!0},{kind:"verdict-artifact",direction:"output",required:!0,identity:"<wbs>-verdict.json"}],reasoning_skill:"sp:code-verification",required_references:[],gates:[{name:"verdict-artifact",timing:"post",min_verdict:"pass"},{name:"strict-core",timing:"post",min_verdict:"pass"}],mutation_class:"verdict",retry:{max_attempts:2,terminal_stop:"escalate"},model_policy:{min_tier:"capable-1",fallback:[{tier:"capable-2",trigger:"auth"},{tier:"capable-2",trigger:"resource-exhaustion"},{tier:"capable-2",trigger:"gate-fail"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"wrap",aliases:["dev-wrap"],description:"dev-wrap: learnings/doc-sync/feature transition",artifacts:[{kind:"learning-entry",direction:"output",required:!0}],reasoning_skill:"sp:spur-dev",required_references:[],gates:[{name:"task-check",timing:"pre",min_verdict:"pass"}],mutation_class:"learnings",retry:{max_attempts:2,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[{tier:"capable-1",trigger:"gate-fail"},{tier:"capable-1",trigger:"auth"},{tier:"capable-1",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"hitl",current_agent_allowed:!0,gate_timing:"both"}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"review",aliases:["dev-review"],description:"dev-review: multi-dimensional code review (coordinator writes the combined Review)",artifacts:[{kind:"review-findings",direction:"output",required:!0,identity:"Review"}],reasoning_skill:"sp:code-verification",required_references:[],gates:[],mutation_class:"verdict",retry:{max_attempts:2,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[{tier:"capable-1",trigger:"gate-fail"},{tier:"capable-1",trigger:"auth"},{tier:"capable-1",trigger:"resource-exhaustion"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"record",aliases:["dev-record"],description:"record: deterministic Testing write-back from the verdict artifact; bare-Review fallback only",artifacts:[{kind:"verdict-artifact",direction:"input",required:!0,identity:"<wbs>-verdict.json"},{kind:"task-section",direction:"output",required:!0,identity:"Testing"},{kind:"task-section",direction:"output",required:!1,identity:"Review",description:"fallback-only: backfills Review only when the section is bare; never overwrites authored Review"}],reasoning_skill:"inline",required_references:[],gates:[],mutation_class:"corpus",retry:{max_attempts:1,terminal_stop:"block"},model_policy:{min_tier:"cheap",fallback:[]},context_layers:[],observability:[],execution:{kind:"deterministic",current_agent_allowed:!1,executor:"cli"}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"dogfood",aliases:["dev-dogfood"],description:"dev-dogfood: end-to-end driver test",artifacts:[{kind:"dogfood-report",direction:"output",required:!0},{kind:"monitor-ledger",direction:"output",required:!1}],reasoning_skill:"sp:dogfood-testing",required_references:[],gates:[],mutation_class:"driver",retry:{max_attempts:3,terminal_stop:"block"},model_policy:{min_tier:"capable-1",fallback:[{tier:"capable-2",trigger:"auth"},{tier:"capable-2",trigger:"resource-exhaustion"},{tier:"capable-2",trigger:"gate-fail"}]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"brainstorm",aliases:["dev-brainstorm"],description:"dev-brainstorm: structured ideation",artifacts:[{kind:"brainstorm-outline",direction:"output",required:!0}],reasoning_skill:"sp:brainstorm",required_references:[],gates:[],mutation_class:"corpus",retry:{max_attempts:2,terminal_stop:"block"},model_policy:{min_tier:"standard",fallback:[]},context_layers:[],observability:[],execution:{kind:"inline",current_agent_allowed:!0}},{schema_version:STAGE_REGISTRY_SCHEMA_VERSION,id:"changelog",aliases:["dev-changelog"],description:"dev-changelog: generate changelog from git commits",artifacts:[{kind:"changelog-entry",direction:"output",required:!0}],reasoning_skill:"inline",required_references:[],gates:[],mutation_class:"none",retry:{max_attempts:1,terminal_stop:"block"},model_policy:{min_tier:"cheap",fallback:[]},context_layers:[],observability:[],execution:{kind:"deterministic",current_agent_allowed:!1,executor:"cli"}}],CANONICAL_STAGE_BY_KEY=new Map;for(let s2 of REGISTERED_CANONICAL_STAGES){CANONICAL_STAGE_BY_KEY.set(s2.id,s2);for(let a2 of s2.aliases??[])CANONICAL_STAGE_BY_KEY.set(a2,s2)}var CACHEABILITY_CLASSES=["stable-prefix-eligible","volatile"],SENSITIVITY_CLASSES=["public","internal","confidential"],envelopeProvenanceSchema=exports_external.object({owner:exports_external.string().min(1),schema_version:exports_external.string().min(1),source_revision:exports_external.string().nullable().optional(),generated_at:exports_external.string().datetime()}).strict(),envelopeSizeBudgetSchema=exports_external.object({max_bytes:exports_external.number().int().positive()}).strict(),envelopeLayerSchema=exports_external.object({layer:exports_external.enum(CONTEXT_LAYER_NAMES),content:exports_external.string(),size_bytes:exports_external.number().int().nonnegative(),content_hash:exports_external.string().length(64),provenance:envelopeProvenanceSchema,cacheability:exports_external.enum(CACHEABILITY_CLASSES),sensitivity:exports_external.enum(SENSITIVITY_CLASSES).default("internal"),size_budget:envelopeSizeBudgetSchema.optional(),disclosure_handle:exports_external.string().optional()}).strict(),STABLE_LAYER_NAMES=["harness-policy","project-authority","stage-contract","task-state","indexed-evidence"],VOLATILE_LAYER_NAMES=["run-state","tool-observations"];var envelopeSchema=exports_external.object({schema_version:exports_external.string().default("1.0"),layers:exports_external.array(envelopeLayerSchema).min(1),stage_id:exports_external.string().optional(),run_id:exports_external.string().optional(),assembled_at:exports_external.string().datetime(),total_size_bytes:exports_external.number().int().nonnegative()}).strict(),projectSnapshotSchema=exports_external.object({name:exports_external.string(),version:exports_external.string(),task_counts:exports_external.object({backlog:exports_external.number().int().nonnegative(),todo:exports_external.number().int().nonnegative(),wip:exports_external.number().int().nonnegative(),testing:exports_external.number().int().nonnegative(),done:exports_external.number().int().nonnegative(),blocked:exports_external.number().int().nonnegative(),cancelled:exports_external.number().int().nonnegative()}).strict(),feature_counts:exports_external.object({backlog:exports_external.number().int().nonnegative(),active:exports_external.number().int().nonnegative(),verifying:exports_external.number().int().nonnegative(),done:exports_external.number().int().nonnegative(),blocked:exports_external.number().int().nonnegative(),cancelled:exports_external.number().int().nonnegative()}).strict(),active_features:exports_external.array(exports_external.string()),content_hash:exports_external.string().length(64).optional()}).strict(),taskSnapshotSchema=exports_external.object({wbs:exports_external.string().regex(/^\d{4}$/),name:exports_external.string(),status:exports_external.string(),priority:exports_external.string().optional(),feature_id:exports_external.string().optional(),acceptance_criteria:exports_external.string().optional(),locked_qa:exports_external.array(exports_external.string()).optional(),solution:exports_external.string().optional(),content_hash:exports_external.string().length(64).optional()}).strict(),featureSnapshotSchema=exports_external.object({id:exports_external.string(),name:exports_external.string(),status:exports_external.string(),priority:exports_external.string().optional(),scenarios:exports_external.array(exports_external.string()).optional(),content_hash:exports_external.string().length(64).optional()}).strict();var ALL_LAYER_NAMES=[...STABLE_LAYER_NAMES,...VOLATILE_LAYER_NAMES],LAYER_ORDER={};ALL_LAYER_NAMES.forEach((name,idx)=>{LAYER_ORDER[name]=idx});init_dist2();var TASK_CANONICAL_SECTIONS=["Background","Requirements","Acceptance Criteria","Q&A","Design","Plan","Root Cause","Solution","Testing","Review","References","History"],FEATURE_CANONICAL_SECTIONS=["Goal","Scope","Acceptance Criteria","Tasks","Notes","History"],UNIVERSAL_SECTIONS=["History","References","Notes"],HEADING_LEVELS={task:3,feature:2},START_MARKER="<!-- AUTO-GENERATED by spur feature refresh -->",END_MARKER="<!-- END AUTO-GENERATED -->",MARKER_START_RE=/<!--[ \t]*AUTO-GENERATED.*?-->/,MARKER_END_RE=/<!--[ \t]*END[ \t]*AUTO-GENERATED.*?-->/;function findHeadings(body,hashes){let hits=[],prefix=`${hashes} `,inCodeBlock=!1,lineStart=0;for(let i2=0;i2<=body.length;i2++){if(i2!==body.length&&body[i2]!==`
|
|
1408
1410
|
`)continue;let line=body.slice(lineStart,i2);if(line.startsWith("```"))inCodeBlock=!inCodeBlock;else if(!inCodeBlock&&line.startsWith(prefix)){let name=line.slice(prefix.length).trim();hits.push({index:lineStart,headingLine:line,name})}lineStart=i2+1}return hits}function findUnclosedFenceLine(text3){let inCodeBlock=!1,openLine=0,lineNo=0,lineStart=0;for(let i2=0;i2<=text3.length;i2++){if(i2!==text3.length&&text3[i2]!==`
|
|
1409
1411
|
`)continue;let line=text3.slice(lineStart,i2);if(lineNo++,line.startsWith("```"))if(inCodeBlock)inCodeBlock=!1;else inCodeBlock=!0,openLine=lineNo;lineStart=i2+1}return inCodeBlock?openLine:null}function normalizeYamlScalars(data){let out={};for(let[key,value]of Object.entries(data))if(value instanceof Date)out[key]=value.toISOString();else if(value!==null&&typeof value==="object"&&!Array.isArray(value))out[key]=normalizeYamlScalars(value);else out[key]=value;return out}class MarkdownDocument{_domain;_frontmatterBlock;_frontmatter;_preamble;_sections;_demotedHeadings=[];_duplicateSectionNames=[];_unclosedFenceLine;constructor(domain2,frontmatterBlock,frontmatter,preamble,sections,unclosedFenceLine){this._domain=domain2,this._frontmatterBlock=frontmatterBlock,this._frontmatter=frontmatter,this._preamble=preamble,this._sections=sections,this._unclosedFenceLine=unclosedFenceLine}static parse(content,domain2){let level=HEADING_LEVELS[domain2],hashes="#".repeat(level),frontmatterBlock="",frontmatter=null,body=content,fmMatch=/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(content);if(fmMatch&&fmMatch.index===0){let raw=fmMatch[1]??"",data={};try{let parsed=$parse(raw);if(parsed!==null&&typeof parsed==="object"&&!Array.isArray(parsed))data=normalizeYamlScalars(parsed)}catch{}frontmatter={raw,data},frontmatterBlock=fmMatch[0],body=content.slice(fmMatch[0].length)}let bodyUnclosedFenceLine=findUnclosedFenceLine(body),unclosedFenceLine=bodyUnclosedFenceLine===null?null:bodyUnclosedFenceLine+(frontmatterBlock.match(/\n/g)??[]).length,hits=findHeadings(body,hashes),first=hits[0],preamble=first!==void 0?body.slice(0,first.index):body,allSections=hits.map((hit,idx)=>{let next=hits[idx+1],end=next!==void 0?next.index:body.length;return{name:hit.name,headingLine:hit.headingLine,originalText:body.slice(hit.index,end),modifiedText:null}}),seen=new Set,sections=[],duplicateNames=[];for(let s2 of allSections)if(seen.has(s2.name))duplicateNames.push(s2.name);else seen.add(s2.name),sections.push(s2);let doc2=new MarkdownDocument(domain2,frontmatterBlock,frontmatter,preamble,sections,unclosedFenceLine);return doc2._duplicateSectionNames.push(...duplicateNames),doc2}get domain(){return this._domain}replacePreamble(newBody){this._preamble=newBody}get frontmatter(){return this._frontmatter}get frontmatterRaw(){return this._frontmatter?.raw??null}get frontmatterData(){return this._frontmatter?.data??null}get bodyWithoutFrontmatter(){let result=this._preamble;for(let section of this._sections)result+=section.modifiedText??section.originalText;return result}get sectionNames(){return this._sections.map((s2)=>s2.name)}get demotedHeadings(){return this._demotedHeadings}get duplicateSectionNames(){return this._duplicateSectionNames}unclosedFenceLine(){return this._unclosedFenceLine}assertFenceBalance(name,body){let docFenceLine=this._unclosedFenceLine;if(docFenceLine!==null)throw Error(`Cannot write section "${name}": the document has an unclosed code fence at line ${docFenceLine}; later sections are hidden and would be dropped by this write. Close the fence and re-parse.`);if(this._duplicateSectionNames.length>0){let names=this._duplicateSectionNames.map((n2)=>`"${n2}"`).join(", ");throw Error(`Cannot write section "${name}": the document has duplicate top-level sections (${names}); the serialized result would have fewer sections than the input and the duplicates would be dropped. Merge or remove the duplicates and re-parse.`)}let bodyFenceLine=findUnclosedFenceLine(body);if(bodyFenceLine!==null)throw Error(`Cannot write section "${name}": the new body has an unclosed code fence (opening at body line ${bodyFenceLine}); writing it would leave the document inside an open fence and hide later sections.`)}get canonicalSections(){let base2=this._domain==="task"?TASK_CANONICAL_SECTIONS:FEATURE_CANONICAL_SECTIONS,extra=UNIVERSAL_SECTIONS.filter((s2)=>!base2.includes(s2));return[...base2,...extra]}validateSectionName(name){if(!this.canonicalSections.includes(name))throw Error(`Unknown section "${name}" for domain "${this._domain}". Canonical sections: ${this.canonicalSections.join(", ")}.`)}findSection(name){return this._sections.find((s2)=>s2.name===name)}hasSection(name){return this.findSection(name)!==void 0}getSection(name){this.validateSectionName(name);let section=this.findSection(name);if(section===void 0)return null;let text3=section.modifiedText??section.originalText,newlineIdx=text3.indexOf(`
|
|
1410
1412
|
`);return newlineIdx===-1?"":text3.slice(newlineIdx+1)}replaceSection(name,body){this.validateSectionName(name),this.assertFenceBalance(name,body);let cleaned=this.stripSameLevelHeadings(body),section=this.findSection(name);if(section!==void 0){let trimmed=cleaned.trim();section.modifiedText=trimmed.length>0?`${section.headingLine}
|