peaks-loop 4.0.28 → 4.0.30
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/CHANGELOG.md +30 -0
- package/dist/cli/commands/_register.js +4 -0
- package/dist/cli/commands/dispatch-commands.d.ts +5 -41
- package/dist/cli/commands/dispatch-commands.js +59 -185
- package/dist/cli/commands/evidence-commands.d.ts +12 -0
- package/dist/cli/commands/evidence-commands.js +43 -0
- package/dist/cli/commands/fresh-context-commands.d.ts +15 -0
- package/dist/cli/commands/fresh-context-commands.js +24 -0
- package/dist/cli/commands/job-commands.js +14 -1
- package/dist/cli/commands/request-commands.js +15 -97
- package/dist/cli/commands/request-format-helpers.d.ts +19 -0
- package/dist/cli/commands/request-format-helpers.js +102 -0
- package/dist/cli/commands/worktree-auth-commands.js +2 -472
- package/dist/cli/commands/worktree-lease-commands.d.ts +21 -0
- package/dist/cli/commands/worktree-lease-commands.js +500 -0
- package/dist/services/artifacts/request-artifact-service.js +13 -3
- package/dist/services/code/auto-compact-lifecycle.d.ts +102 -0
- package/dist/services/code/auto-compact-lifecycle.js +235 -0
- package/dist/services/code/auto-compact-orchestrator.d.ts +1 -1
- package/dist/services/code/auto-compact-orchestrator.js +1 -222
- package/dist/services/codegraph/codegraph-autorefresh.d.ts +22 -0
- package/dist/services/codegraph/codegraph-autorefresh.js +91 -0
- package/dist/services/codegraph/codegraph-preflight-service.d.ts +53 -0
- package/dist/services/codegraph/codegraph-preflight-service.js +226 -0
- package/dist/services/context/build-dispatch-system-prompt.d.ts +52 -0
- package/dist/services/context/build-dispatch-system-prompt.js +70 -3
- package/dist/services/dispatch/dispatch-record-types.d.ts +278 -0
- package/dist/services/dispatch/dispatch-record-types.js +15 -0
- package/dist/services/dispatch/dispatch-record-upgrade.d.ts +5 -0
- package/dist/services/dispatch/dispatch-record-upgrade.js +230 -0
- package/dist/services/dispatch/dispatch-record-writer.d.ts +3 -278
- package/dist/services/dispatch/dispatch-record-writer.js +3 -245
- package/dist/services/dispatch/dispatch-sub-agent.d.ts +43 -0
- package/dist/services/dispatch/dispatch-sub-agent.js +55 -0
- package/dist/services/dispatch/isolation-lease.d.ts +43 -0
- package/dist/services/dispatch/isolation-lease.js +129 -0
- package/dist/services/evidence/evidence-generator.d.ts +26 -0
- package/dist/services/evidence/evidence-generator.js +349 -0
- package/dist/services/fresh-context/config.d.ts +1 -0
- package/dist/services/fresh-context/config.js +13 -0
- package/dist/services/fresh-context/fresh-context-block.d.ts +12 -0
- package/dist/services/fresh-context/fresh-context-block.js +40 -0
- package/dist/services/fresh-context/trigger-scan.d.ts +30 -0
- package/dist/services/fresh-context/trigger-scan.js +29 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +65 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +204 -0
- package/dist/services/skills/hooks-settings-service.d.ts +2 -64
- package/dist/services/skills/hooks-settings-service.js +2 -215
- package/dist/services/skills/skill-statusline-renderer.d.ts +2 -34
- package/dist/services/skills/skill-statusline-renderer.js +1 -184
- package/dist/services/skills/statusline-palette.d.ts +62 -0
- package/dist/services/skills/statusline-palette.js +190 -0
- package/dist/services/slice/slice-decompose-import-edges.d.ts +8 -0
- package/dist/services/slice/slice-decompose-import-edges.js +102 -0
- package/dist/services/slice/slice-decompose-service.js +3 -198
- package/dist/services/slice/slice-decompose-tarjan.d.ts +8 -0
- package/dist/services/slice/slice-decompose-tarjan.js +108 -0
- package/dist/services/standards/project-standards-service.d.ts +1 -9
- package/dist/services/standards/project-standards-service.js +3 -232
- package/dist/services/standards/standards-render.d.ts +23 -0
- package/dist/services/standards/standards-render.js +238 -0
- package/dist/services/standards/ui-library-dispatch-block.d.ts +27 -0
- package/dist/services/standards/ui-library-dispatch-block.js +48 -0
- package/dist/services/workspace/reconcile-migrate.d.ts +77 -0
- package/dist/services/workspace/reconcile-migrate.js +230 -0
- package/dist/services/workspace/reconcile-service.d.ts +0 -72
- package/dist/services/workspace/reconcile-service.js +3 -220
- package/dist/services/workspace/workspace-service.js +13 -2
- package/dist/shared/incrementing-number.d.ts +11 -0
- package/dist/shared/incrementing-number.js +16 -3
- package/package.json +5 -5
- package/skills/peaks-code/SKILL.md +6 -2
- package/skills/peaks-code/references/fresh-context-preflight.md +81 -0
- package/skills/peaks-code/references/sub-agent-dispatch.md +27 -8
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// src/services/codegraph/codegraph-preflight-service.ts
|
|
2
|
+
//
|
|
3
|
+
// Slice 2026-09-03-codegraph-preread (Option A) — pre-dispatch codegraph
|
|
4
|
+
// preflight for RD planning. peaks-code's RD dispatch path calls
|
|
5
|
+
// `buildCodegraphPreflightBlock` BEFORE composing the RD sub-agent prompt
|
|
6
|
+
// (src/cli/commands/dispatch-commands.ts) so the RD plans against the real
|
|
7
|
+
// module/file topology in the codegraph index, not LLM memory.
|
|
8
|
+
//
|
|
9
|
+
// The service does three things, all best-effort and fail-soft (it NEVER
|
|
10
|
+
// throws — every failure returns `{ available: false, note }` so the caller
|
|
11
|
+
// degrades to a "codegraph unavailable — proceeding on project-scan only"
|
|
12
|
+
// note and dispatch proceeds):
|
|
13
|
+
//
|
|
14
|
+
// 1. Ensure the schema exists: when `<projectRoot>/.codegraph/` is
|
|
15
|
+
// absent, run `codegraph init` + `codegraph index` (best-effort).
|
|
16
|
+
// 2. Skip-when-fresh: when `.codegraph/` already carries the
|
|
17
|
+
// peaks-loop marker, do NOT re-init / re-index on every dispatch
|
|
18
|
+
// (index is incremental; the sibling rid-2026-09-03-codegraph-autorefresh
|
|
19
|
+
// owns post-slice refresh). A foreign-schema `.codegraph/` is never
|
|
20
|
+
// clobbered — we fail-soft instead.
|
|
21
|
+
// 3. Read a BOUNDED project-structure summary from the index
|
|
22
|
+
// (`codegraph files --json`) and render it as a `## Codegraph
|
|
23
|
+
// structure` markdown block.
|
|
24
|
+
//
|
|
25
|
+
// Bounded-output contract: the rendered block never exceeds a safe prompt
|
|
26
|
+
// budget. The pure renderer `renderCodegraphStructureBlock` caps the
|
|
27
|
+
// directory histogram at CODEGRAPH_STRUCTURE_MAX_DIRS rows and the root-file
|
|
28
|
+
// listing at CODEGRAPH_STRUCTURE_MAX_ROOT_FILES entries; anything beyond
|
|
29
|
+
// those caps is summarized with a "… and N more" line and a `truncated: true`
|
|
30
|
+
// flag. Documented caps:
|
|
31
|
+
// - CODEGRAPH_STRUCTURE_MAX_DIRS = 40 directory rows
|
|
32
|
+
// - CODEGRAPH_STRUCTURE_MAX_ROOT_FILES = 12 bare root files
|
|
33
|
+
import { mkdirSync } from 'node:fs';
|
|
34
|
+
import { CODEGRAPH_DIR_NAME, createCodegraphInvocation, defaultCodegraphInitGuard, executeCodegraphInvocation, writeCodegraphMarker, } from './codegraph-service.js';
|
|
35
|
+
import { defaultCodegraphProcessRunner } from './codegraph-process-runner.js';
|
|
36
|
+
/** Cap for the directory histogram in the rendered structure block. */
|
|
37
|
+
export const CODEGRAPH_STRUCTURE_MAX_DIRS = 40;
|
|
38
|
+
/** Cap for bare root files listed in the rendered structure block. */
|
|
39
|
+
export const CODEGRAPH_STRUCTURE_MAX_ROOT_FILES = 12;
|
|
40
|
+
/** Strip a leading `./` (upstream codegraph paths may carry it). */
|
|
41
|
+
function normalizeCodegraphPath(path) {
|
|
42
|
+
return path.startsWith('./') ? path.slice(2) : path;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Pure renderer: turn the codegraph `files --json` payload into a bounded
|
|
46
|
+
* `## Codegraph structure` block. Files are aggregated into a directory
|
|
47
|
+
* histogram (immediate parent dir; bare root files bucket separately),
|
|
48
|
+
* sorted by file count descending then name ascending, and capped at
|
|
49
|
+
* `maxDirs` rows / `maxRootFiles` root entries. Exported separately so the
|
|
50
|
+
* bounded-output contract is unit-testable without a filesystem.
|
|
51
|
+
*/
|
|
52
|
+
export function renderCodegraphStructureBlock(files, options = {}) {
|
|
53
|
+
const maxDirs = options.maxDirs ?? CODEGRAPH_STRUCTURE_MAX_DIRS;
|
|
54
|
+
const maxRootFiles = options.maxRootFiles ?? CODEGRAPH_STRUCTURE_MAX_ROOT_FILES;
|
|
55
|
+
const dirCounts = new Map();
|
|
56
|
+
const rootFiles = [];
|
|
57
|
+
let total = 0;
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
const raw = normalizeCodegraphPath(file.path).replace(/\\/g, '/');
|
|
60
|
+
if (raw.length === 0)
|
|
61
|
+
continue;
|
|
62
|
+
total += 1;
|
|
63
|
+
const slash = raw.lastIndexOf('/');
|
|
64
|
+
if (slash === -1) {
|
|
65
|
+
rootFiles.push(raw);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const dir = raw.slice(0, slash);
|
|
69
|
+
dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
const sortedDirs = [...dirCounts.entries()]
|
|
72
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
73
|
+
const shownDirs = sortedDirs.slice(0, maxDirs);
|
|
74
|
+
const truncatedDirs = sortedDirs.length > maxDirs;
|
|
75
|
+
const sortedRoot = [...rootFiles].sort((a, b) => a.localeCompare(b));
|
|
76
|
+
const shownRoot = sortedRoot.slice(0, maxRootFiles);
|
|
77
|
+
const truncatedRoot = sortedRoot.length > maxRootFiles;
|
|
78
|
+
const lines = ['## Codegraph structure', ''];
|
|
79
|
+
lines.push(total === 0
|
|
80
|
+
? 'No files are indexed yet. Run `peaks codegraph index` before dispatching planning work for symbol-accurate structure.'
|
|
81
|
+
: `${total} file${total === 1 ? '' : 's'} indexed in the codegraph index:`);
|
|
82
|
+
for (const [dir, count] of shownDirs) {
|
|
83
|
+
lines.push(`- \`${dir}/\` — ${count} file${count === 1 ? '' : 's'}`);
|
|
84
|
+
}
|
|
85
|
+
if (truncatedDirs) {
|
|
86
|
+
lines.push(`- … and ${sortedDirs.length - maxDirs} more director${sortedDirs.length - maxDirs === 1 ? 'y' : 'ies'}`);
|
|
87
|
+
}
|
|
88
|
+
if (sortedRoot.length > 0) {
|
|
89
|
+
lines.push(`- (root) — ${shownRoot.map((f) => `\`${f}\``).join(', ')}${truncatedRoot ? ' …' : ''}`);
|
|
90
|
+
}
|
|
91
|
+
const block = lines.join('\n').replace(/\s+$/, '') + '\n\n';
|
|
92
|
+
return { block, total, truncated: truncatedDirs || truncatedRoot };
|
|
93
|
+
}
|
|
94
|
+
function errorMessage(error) {
|
|
95
|
+
return error instanceof Error ? error.message : String(error);
|
|
96
|
+
}
|
|
97
|
+
function firstMeaningfulLine(text) {
|
|
98
|
+
const trimmed = text.trim();
|
|
99
|
+
if (trimmed.length === 0)
|
|
100
|
+
return 'no upstream output';
|
|
101
|
+
const first = trimmed.split(/\r?\n/)[0];
|
|
102
|
+
return first !== undefined ? first.slice(0, 200) : 'no upstream output';
|
|
103
|
+
}
|
|
104
|
+
function parseFilesPayload(stdout) {
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(stdout);
|
|
107
|
+
if (Array.isArray(parsed)) {
|
|
108
|
+
const entries = parsed
|
|
109
|
+
.filter((entry) => typeof entry === 'object' && entry !== null && 'path' in entry)
|
|
110
|
+
.map((entry) => ({ path: typeof entry.path === 'string' ? entry.path : '' }))
|
|
111
|
+
.filter((entry) => entry.path.length > 0);
|
|
112
|
+
return { ok: true, entries };
|
|
113
|
+
}
|
|
114
|
+
// JSON but not an array — not the shape we expect.
|
|
115
|
+
return { ok: false, entries: [] };
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Not JSON at all — e.g. the upstream text path ("No files indexed…").
|
|
119
|
+
return { ok: false, entries: [] };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function readStructure(projectRoot, runner) {
|
|
123
|
+
let result;
|
|
124
|
+
try {
|
|
125
|
+
const invocation = createCodegraphInvocation({ subcommand: 'files', project: projectRoot, json: true });
|
|
126
|
+
result = await executeCodegraphInvocation(invocation, runner);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return { available: false, note: `codegraph files unavailable: ${errorMessage(error)}` };
|
|
130
|
+
}
|
|
131
|
+
if (result.exitCode !== 0) {
|
|
132
|
+
return {
|
|
133
|
+
available: false,
|
|
134
|
+
note: `codegraph files failed (exit ${String(result.exitCode)}): ${firstMeaningfulLine(result.stderr || result.stdout)}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const { ok, entries } = parseFilesPayload(result.stdout);
|
|
138
|
+
if (!ok) {
|
|
139
|
+
return {
|
|
140
|
+
available: false,
|
|
141
|
+
note: 'codegraph files returned no parseable structure — run `peaks codegraph index` before dispatch for symbol-accurate structure.',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (entries.length === 0) {
|
|
145
|
+
return {
|
|
146
|
+
available: false,
|
|
147
|
+
note: 'codegraph index has no files — run `peaks codegraph index` before dispatch for symbol-accurate structure.',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const summary = renderCodegraphStructureBlock(entries);
|
|
151
|
+
return {
|
|
152
|
+
available: true,
|
|
153
|
+
block: summary.block,
|
|
154
|
+
fileCount: summary.total,
|
|
155
|
+
truncated: summary.truncated,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Pre-dispatch codegraph preflight. Returns a `## Codegraph structure`
|
|
160
|
+
* block when the index is (or becomes) readable; otherwise an
|
|
161
|
+
* `{ available: false, note }` result. NEVER throws — the caller must be
|
|
162
|
+
* able to degrade gracefully on every failure path.
|
|
163
|
+
*
|
|
164
|
+
* Behavior matrix (acceptance criteria):
|
|
165
|
+
* - `.codegraph/` absent → init + index (best-effort), then read.
|
|
166
|
+
* - `.codegraph/` present with peaks-loop marker → skip init/index
|
|
167
|
+
* (fresh), read directly. No redundant re-index on every dispatch.
|
|
168
|
+
* - `.codegraph/` present WITHOUT marker (foreign schema) → fail-soft;
|
|
169
|
+
* never clobber a foreign store.
|
|
170
|
+
*
|
|
171
|
+
* The optional `runner` mirrors `CodegraphProcessRunner` and is the ONLY
|
|
172
|
+
* injected boundary (tests fake the upstream binary).
|
|
173
|
+
*/
|
|
174
|
+
export async function buildCodegraphPreflightBlock(projectRoot, runner) {
|
|
175
|
+
const processRunner = runner ?? defaultCodegraphProcessRunner;
|
|
176
|
+
const guard = defaultCodegraphInitGuard(projectRoot);
|
|
177
|
+
if (guard.status === 'conflict-foreign-schema') {
|
|
178
|
+
return {
|
|
179
|
+
available: false,
|
|
180
|
+
note: `codegraph unavailable: ${CODEGRAPH_DIR_NAME}/ exists with a non-peaks-loop schema and was not touched. Move or rename the foreign directory, then re-run \`peaks codegraph init\` to enable pre-dispatch structure reads.`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (guard.status === 'fresh') {
|
|
184
|
+
// 1. init (best-effort). Upstream creates the `.codegraph/` dir; we
|
|
185
|
+
// stamp the peaks-loop marker afterwards so the NEXT dispatch hits
|
|
186
|
+
// the noop (skip-when-fresh) branch.
|
|
187
|
+
try {
|
|
188
|
+
const initResult = await executeCodegraphInvocation(createCodegraphInvocation({ subcommand: 'init', project: projectRoot }), processRunner);
|
|
189
|
+
if (initResult.exitCode !== 0) {
|
|
190
|
+
return {
|
|
191
|
+
available: false,
|
|
192
|
+
note: `codegraph init failed (exit ${String(initResult.exitCode)}): ${firstMeaningfulLine(initResult.stderr || initResult.stdout)}`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
// Upstream init creates `.codegraph/`; mkdir is a no-op in the real
|
|
197
|
+
// path and lets the marker write succeed even when the runner is
|
|
198
|
+
// faked (tests). Best-effort: failure must not undo the init.
|
|
199
|
+
mkdirSync(guard.codegraphDir, { recursive: true });
|
|
200
|
+
writeCodegraphMarker(guard.codegraphDir);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Best-effort: a marker-write failure must not undo the init.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
return { available: false, note: `codegraph init unavailable: ${errorMessage(error)}` };
|
|
208
|
+
}
|
|
209
|
+
// 2. index (best-effort).
|
|
210
|
+
try {
|
|
211
|
+
const indexResult = await executeCodegraphInvocation(createCodegraphInvocation({ subcommand: 'index', project: projectRoot, quiet: true }), processRunner);
|
|
212
|
+
if (indexResult.exitCode !== 0) {
|
|
213
|
+
return {
|
|
214
|
+
available: false,
|
|
215
|
+
note: `codegraph index failed (exit ${String(indexResult.exitCode)}): ${firstMeaningfulLine(indexResult.stderr || indexResult.stdout)}`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
return { available: false, note: `codegraph index unavailable: ${errorMessage(error)}` };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// guard.status === 'noop-already-peaks-loop' (or we just initialized):
|
|
224
|
+
// the schema exists — do NOT re-index, go straight to the bounded read.
|
|
225
|
+
return readStructure(projectRoot, processRunner);
|
|
226
|
+
}
|
|
@@ -28,6 +28,46 @@ export interface DispatchPromptInput {
|
|
|
28
28
|
* a byte-counted estimate.
|
|
29
29
|
*/
|
|
30
30
|
contextProbe?: ContextPercentProbe | null;
|
|
31
|
+
/**
|
|
32
|
+
* Slice 2026-09-03-codegraph-preread (Option A): pre-composed
|
|
33
|
+
* `## Codegraph structure` markdown block, read from the codegraph
|
|
34
|
+
* index BEFORE the RD sub-agent's task body is composed so the RD
|
|
35
|
+
* plans against real module/file topology, not LLM memory.
|
|
36
|
+
*
|
|
37
|
+
* - `undefined` → no codegraph block (legacy callers unchanged).
|
|
38
|
+
* - `null` → codegraph was attempted but is unavailable; the composer
|
|
39
|
+
* renders a fixed "codegraph unavailable — proceeding on project-scan
|
|
40
|
+
* only" note (fail-soft; never hard-blocks a dispatch).
|
|
41
|
+
* - `string` → the block, rendered verbatim between the context window
|
|
42
|
+
* and the memory/task content.
|
|
43
|
+
*/
|
|
44
|
+
codegraphBlock?: string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Slice 2026-09-06-ui-lib-dispatch-priority: pre-composed
|
|
47
|
+
* `## Project stack` markdown block, rendered from the detected
|
|
48
|
+
* project context (component library + CSS framework + build tool) by
|
|
49
|
+
* the dispatch site BEFORE the sub-agent's task body is composed so
|
|
50
|
+
* the RD/UI sub-agent sees the library-first directive in-context.
|
|
51
|
+
*
|
|
52
|
+
* - `undefined` → no project-stack block (legacy callers unchanged).
|
|
53
|
+
* - `null` → the project has no detected component library; the
|
|
54
|
+
* composer renders nothing (byte-identical degradation).
|
|
55
|
+
* - `string` → the block, rendered verbatim between the codegraph
|
|
56
|
+
* block and the memory/task content.
|
|
57
|
+
*/
|
|
58
|
+
projectStackBlock?: string | null;
|
|
59
|
+
/**
|
|
60
|
+
* Slice 2026-09-07-search-first-preflight: the orchestrator-synthesized
|
|
61
|
+
* `## Fresh context` block (≤5 binding directives from a Context7 →
|
|
62
|
+
* WebSearch preflight), read from `.peaks/_runtime/<sessionId>/fresh-context.md`.
|
|
63
|
+
*
|
|
64
|
+
* - `undefined` → no fresh-context block (legacy callers unchanged).
|
|
65
|
+
* - `null` → the block is unavailable (missing file / no `## Fresh context`
|
|
66
|
+
* heading); the composer renders nothing (byte-identical degradation).
|
|
67
|
+
* - `string` → the block, rendered verbatim after the project-stack block
|
|
68
|
+
* and before the memory/task content.
|
|
69
|
+
*/
|
|
70
|
+
freshContextBlock?: string | null;
|
|
31
71
|
}
|
|
32
72
|
/**
|
|
33
73
|
* Slice 2026-07-29-worktree-l1: Layer 1 of the 3-layer worktree governance
|
|
@@ -88,6 +128,10 @@ export declare const LIFECYCLE_RULES = "## Sub-agent lifecycle rules (locked 202
|
|
|
88
128
|
* `${formatTestToolDetection()}\n\n${taskBody}`. Today's pre-change behavior
|
|
89
129
|
* produced the same string from `src/cli/commands/dispatch-commands.ts:220`,
|
|
90
130
|
* so the unavailable branch MUST return `taskBody` (NOT a `# title\n\n` wrap).
|
|
131
|
+
* The contract holds for callers that do not pass `codegraphBlock` (all
|
|
132
|
+
* non-RD roles). Slice 2026-09-03-codegraph-preread deliberately inserts a
|
|
133
|
+
* codegraph structure block (or its fail-soft unavailable note) for RD
|
|
134
|
+
* dispatches between the context window and the memory/task content.
|
|
91
135
|
*
|
|
92
136
|
* Available branch prepends the memory block before the `## Task` heading so
|
|
93
137
|
* `## Project memory …` always sits above the task brief (never pushed below
|
|
@@ -99,3 +143,11 @@ export declare const LIFECYCLE_RULES = "## Sub-agent lifecycle rules (locked 202
|
|
|
99
143
|
* refusal is in scope before any task-specific prose arrives.
|
|
100
144
|
*/
|
|
101
145
|
export declare function buildDispatchSystemPrompt(input: DispatchPromptInput): string;
|
|
146
|
+
/**
|
|
147
|
+
* Slice 2026-09-03-codegraph-preread: fixed degradation string emitted
|
|
148
|
+
* when the RD dispatch preflight requested a codegraph structure read but
|
|
149
|
+
* the index could not be resolved (absent + init failure, foreign schema,
|
|
150
|
+
* unparseable output). Kept as a constant so the unavailable branch is
|
|
151
|
+
* byte-stable and trivially testable.
|
|
152
|
+
*/
|
|
153
|
+
export declare const CODEGRAPH_UNAVAILABLE_BLOCK = "## Codegraph structure\n\ncodegraph unavailable \u2014 proceeding on project-scan only.\n";
|
|
@@ -83,6 +83,10 @@ export const LIFECYCLE_RULES = `## Sub-agent lifecycle rules (locked 2026-08-01)
|
|
|
83
83
|
* `${formatTestToolDetection()}\n\n${taskBody}`. Today's pre-change behavior
|
|
84
84
|
* produced the same string from `src/cli/commands/dispatch-commands.ts:220`,
|
|
85
85
|
* so the unavailable branch MUST return `taskBody` (NOT a `# title\n\n` wrap).
|
|
86
|
+
* The contract holds for callers that do not pass `codegraphBlock` (all
|
|
87
|
+
* non-RD roles). Slice 2026-09-03-codegraph-preread deliberately inserts a
|
|
88
|
+
* codegraph structure block (or its fail-soft unavailable note) for RD
|
|
89
|
+
* dispatches between the context window and the memory/task content.
|
|
86
90
|
*
|
|
87
91
|
* Available branch prepends the memory block before the `## Task` heading so
|
|
88
92
|
* `## Project memory …` always sits above the task brief (never pushed below
|
|
@@ -94,12 +98,75 @@ export const LIFECYCLE_RULES = `## Sub-agent lifecycle rules (locked 2026-08-01)
|
|
|
94
98
|
* refusal is in scope before any task-specific prose arrives.
|
|
95
99
|
*/
|
|
96
100
|
export function buildDispatchSystemPrompt(input) {
|
|
97
|
-
const { taskBody, memoryBlock, contextProbe } = input;
|
|
101
|
+
const { taskBody, memoryBlock, contextProbe, codegraphBlock, projectStackBlock, freshContextBlock } = input;
|
|
98
102
|
const contextBlock = renderContextBlock(contextProbe ?? null);
|
|
103
|
+
const codegraphText = renderCodegraphBlock(codegraphBlock);
|
|
104
|
+
const projectStackText = renderProjectStackBlock(projectStackBlock);
|
|
105
|
+
const freshContextText = renderFreshContextBlock(freshContextBlock);
|
|
99
106
|
if (memoryBlock.available === true && typeof memoryBlock.block === 'string') {
|
|
100
|
-
return `${L1_WORKTREE_GOVERNANCE_BLOCK}\n${LIFECYCLE_RULES}\n${contextBlock}${memoryBlock.block}\n## Task\n${taskBody}`;
|
|
107
|
+
return `${L1_WORKTREE_GOVERNANCE_BLOCK}\n${LIFECYCLE_RULES}\n${contextBlock}${codegraphText}${projectStackText}${freshContextText}${memoryBlock.block}\n## Task\n${taskBody}`;
|
|
101
108
|
}
|
|
102
|
-
return `${L1_WORKTREE_GOVERNANCE_BLOCK}\n${LIFECYCLE_RULES}\n${contextBlock}${taskBody}`;
|
|
109
|
+
return `${L1_WORKTREE_GOVERNANCE_BLOCK}\n${LIFECYCLE_RULES}\n${contextBlock}${codegraphText}${projectStackText}${freshContextText}${taskBody}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Slice 2026-09-03-codegraph-preread: fixed degradation string emitted
|
|
113
|
+
* when the RD dispatch preflight requested a codegraph structure read but
|
|
114
|
+
* the index could not be resolved (absent + init failure, foreign schema,
|
|
115
|
+
* unparseable output). Kept as a constant so the unavailable branch is
|
|
116
|
+
* byte-stable and trivially testable.
|
|
117
|
+
*/
|
|
118
|
+
export const CODEGRAPH_UNAVAILABLE_BLOCK = '## Codegraph structure\n\ncodegraph unavailable — proceeding on project-scan only.\n';
|
|
119
|
+
/**
|
|
120
|
+
* Render the codegraph insertion for a dispatch prompt.
|
|
121
|
+
*
|
|
122
|
+
* - `undefined` → empty (the composer is byte-identical to the legacy
|
|
123
|
+
* shape for callers that did not opt into a codegraph pre-read).
|
|
124
|
+
* - `null` → the fixed CODEGRAPH_UNAVAILABLE_BLOCK note.
|
|
125
|
+
* - `string` → the pre-composed block from the codegraph preflight
|
|
126
|
+
* service, verbatim.
|
|
127
|
+
*
|
|
128
|
+
* Every non-empty variant is normalized to end on its own paragraph
|
|
129
|
+
* (`\n\n`) so the following block (project memory or task body) starts
|
|
130
|
+
* cleanly regardless of the caller's trailing newline habits.
|
|
131
|
+
*/
|
|
132
|
+
function renderCodegraphBlock(codegraphBlock) {
|
|
133
|
+
if (codegraphBlock === undefined)
|
|
134
|
+
return '';
|
|
135
|
+
const text = codegraphBlock === null ? CODEGRAPH_UNAVAILABLE_BLOCK : codegraphBlock;
|
|
136
|
+
return `${text.replace(/\s+$/, '')}\n\n`;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Render the project-stack insertion for a dispatch prompt.
|
|
140
|
+
*
|
|
141
|
+
* - `undefined` / `null` → empty (the composer is byte-identical to the
|
|
142
|
+
* legacy shape — no dangling "Project stack" heading).
|
|
143
|
+
* - `string` → the pre-composed block from the dispatch site, verbatim.
|
|
144
|
+
*
|
|
145
|
+
* Mirrors `renderCodegraphBlock`: every non-empty variant is normalized
|
|
146
|
+
* to end on its own paragraph (`\n\n`) so the following block (project
|
|
147
|
+
* memory or task body) starts cleanly.
|
|
148
|
+
*/
|
|
149
|
+
function renderProjectStackBlock(projectStackBlock) {
|
|
150
|
+
if (projectStackBlock === undefined || projectStackBlock === null)
|
|
151
|
+
return '';
|
|
152
|
+
return `${projectStackBlock.replace(/\s+$/, '')}\n\n`;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Render the fresh-context insertion for a dispatch prompt.
|
|
156
|
+
*
|
|
157
|
+
* - `undefined` / `null` → empty (the composer is byte-identical to the
|
|
158
|
+
* legacy shape — no dangling "Fresh context" heading).
|
|
159
|
+
* - `string` → the pre-composed block from the fresh-context preflight
|
|
160
|
+
* synthesis, verbatim.
|
|
161
|
+
*
|
|
162
|
+
* Mirrors `renderProjectStackBlock`: every non-empty variant is normalized
|
|
163
|
+
* to end on its own paragraph (`\n\n`) so the following block (project
|
|
164
|
+
* memory or task body) starts cleanly.
|
|
165
|
+
*/
|
|
166
|
+
function renderFreshContextBlock(freshContextBlock) {
|
|
167
|
+
if (freshContextBlock === undefined || freshContextBlock === null)
|
|
168
|
+
return '';
|
|
169
|
+
return `${freshContextBlock.replace(/\s+$/, '')}\n\n`;
|
|
103
170
|
}
|
|
104
171
|
/**
|
|
105
172
|
* Slice 2026-07-29-context-evaluation-accuracy: emit a
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import type { SubAgentToolCall } from './sub-agent-dispatcher.js';
|
|
2
|
+
/** G6.3 Heartbeat entry — single update written by a running sub-agent. */
|
|
3
|
+
export interface Heartbeat {
|
|
4
|
+
readonly at: string;
|
|
5
|
+
readonly status: HeartbeatStatus;
|
|
6
|
+
readonly progress: number;
|
|
7
|
+
readonly note: string | null;
|
|
8
|
+
}
|
|
9
|
+
export type HeartbeatStatus = 'queued' | 'running' | 'finalizing' | 'done' | 'failed' | 'stale' | 'cancelled' | 'no-execution' | 'never-started' | 'unreadable';
|
|
10
|
+
export type DispatchRecordStatus = 'queued' | 'running' | 'finalizing' | 'done' | 'failed' | 'cancelled' | 'no-execution' | 'stale' | 'never-started' | 'unreadable';
|
|
11
|
+
export type DispatchOutcome = 'success' | 'failed' | 'timeout' | 'cancelled' | 'no-execution';
|
|
12
|
+
/** G2+G5+G6 dispatch record schema (AC-26 + AC-34). */
|
|
13
|
+
export interface DispatchRecord {
|
|
14
|
+
/**
|
|
15
|
+
* Slice 2026-07-29-worktree-l2-extended Part 4.C: schema v3 makes
|
|
16
|
+
* `leaseId` a structurally required field (was `leaseId?: string | null`
|
|
17
|
+
* in v2). The v3 upgrade is a "fill in" migration: every dispatch
|
|
18
|
+
* writer knows its lease id at construction time (Part 2.C's
|
|
19
|
+
* --isolation worktree spawns it; non-isolation dispatches stamp
|
|
20
|
+
* `null`). Readers tolerate both v2 and v3 on disk (see
|
|
21
|
+
* `upgradeRecord`); the `?` was a Part 4.A ergonomic concession
|
|
22
|
+
* to keep 4 unit-test literal sites from breaking the build. v3
|
|
23
|
+
* moves the optional off the type and adds the field to the
|
|
24
|
+
* 4 literal sites in one pass.
|
|
25
|
+
*
|
|
26
|
+
* Slice 2026-07-29-worktree-l2-extended Part 7: schema v3.1 adds
|
|
27
|
+
* `isolationStartedAt: string | null` for the L4 isolation
|
|
28
|
+
* bridge (Part 8 container POC). It's the ISO timestamp of
|
|
29
|
+
* when the isolation mode was set up (e.g. when the worktree
|
|
30
|
+
* was spawned). `null` means the dispatch did not request
|
|
31
|
+
* isolation. The `version` field stays at 3 because the
|
|
32
|
+
* v3 → v3.1 transition is additive; the new field is
|
|
33
|
+
* defaulted to `null` on read so v3 records upgrade cleanly.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Phase A Task 8: schema bumped to v4.1.0 (additive). The bump
|
|
37
|
+
* is purely additive — new fields (`mode`, `vendor`,
|
|
38
|
+
* `autoCompactEvents`, `tokenUsage`) all default safely on
|
|
39
|
+
* read for legacy v4.0.0 / v3.2 / v3.1 / v3 / v2 / v1 records.
|
|
40
|
+
* No existing field semantics changed.
|
|
41
|
+
*/
|
|
42
|
+
readonly version: '4.1.0';
|
|
43
|
+
readonly createdAt: string;
|
|
44
|
+
readonly completedAt: string | null;
|
|
45
|
+
readonly outcome: DispatchOutcome;
|
|
46
|
+
readonly artifactPaths: readonly string[];
|
|
47
|
+
readonly disposed: boolean;
|
|
48
|
+
readonly disposedAt: string | null;
|
|
49
|
+
readonly role: string;
|
|
50
|
+
readonly requestId: string;
|
|
51
|
+
readonly sessionId: string;
|
|
52
|
+
readonly prompt: string;
|
|
53
|
+
readonly toolCall: SubAgentToolCall;
|
|
54
|
+
/** G5 batch id (AC-27) — uuid-like opaque token grouping one batch. */
|
|
55
|
+
readonly batchId: string;
|
|
56
|
+
/** G6 fields (AC-34) — backward compat: defaults on read. */
|
|
57
|
+
readonly heartbeats: readonly Heartbeat[];
|
|
58
|
+
readonly lastBeatAt: string | null;
|
|
59
|
+
readonly status: DispatchRecordStatus;
|
|
60
|
+
readonly stage: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Slice 2026-07-29-worktree-l2-extended Part 3.A + Part 4.C: the
|
|
63
|
+
* worktree lease id stamped on this dispatch (via `peaks sub-agent
|
|
64
|
+
* dispatch --isolation worktree`). The release hook (see
|
|
65
|
+
* markCompleted + `peaks sub-agent heartbeat --status done`)
|
|
66
|
+
* reads this field to auto-call `peaks worktree release` when
|
|
67
|
+
* the sub-agent finalizes. `null` means the dispatch did not
|
|
68
|
+
* request isolation and no release will fire. Persisted for
|
|
69
|
+
* audit + idempotency so a re-read of an old record still
|
|
70
|
+
* surfaces the lease id even if the on-disk lease file has since
|
|
71
|
+
* been gc'd.
|
|
72
|
+
*
|
|
73
|
+
* v3 (Part 4.C) makes this structurally required. v2 records
|
|
74
|
+
* missing the field upgrade to `null` on read (see
|
|
75
|
+
* `upgradeRecord`).
|
|
76
|
+
*/
|
|
77
|
+
readonly leaseId: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Slice 2026-07-29-worktree-l2-extended Part 7: ISO timestamp of
|
|
80
|
+
* when the isolation mode was set up. For `--isolation worktree`
|
|
81
|
+
* this is the moment `peaks worktree spawn` returned; for
|
|
82
|
+
* `--isolation container` (Part 8) it's when the container
|
|
83
|
+
* runtime reported the container as running. `null` when the
|
|
84
|
+
* dispatch did not request isolation. Lets the dashboard
|
|
85
|
+
* compute isolation duration (now - isolationStartedAt) without
|
|
86
|
+
* cross-referencing the metrics stream.
|
|
87
|
+
*/
|
|
88
|
+
readonly isolationStartedAt: string | null;
|
|
89
|
+
/**
|
|
90
|
+
* Slice 2026-08-01-subagent-merge-and-e2e (Task 7): v3.2 schema
|
|
91
|
+
* bump. One entry per pid the parent best-effort-killed during
|
|
92
|
+
* the service-shutdown phase of the merge-back pipeline (see
|
|
93
|
+
* src/services/dispatch/service-shutdown.ts). Empty array when
|
|
94
|
+
* the sub-agent did not register any services. The shape is the
|
|
95
|
+
* union of ServiceKillOutcome (skipped=false) and
|
|
96
|
+
* ServiceKillSkipped (skipped=true) — kept as a plain object
|
|
97
|
+
* here so the on-disk schema does not lock onto the helper's
|
|
98
|
+
* narrower union. The reader (merge-back-runner.ts) interprets
|
|
99
|
+
* each entry based on the `skipped` field.
|
|
100
|
+
*/
|
|
101
|
+
readonly serviceKill: ReadonlyArray<{
|
|
102
|
+
readonly pid: number;
|
|
103
|
+
readonly name: string;
|
|
104
|
+
readonly signal: string;
|
|
105
|
+
readonly exitCode: number | null;
|
|
106
|
+
readonly skipped?: boolean;
|
|
107
|
+
readonly reason?: string;
|
|
108
|
+
}>;
|
|
109
|
+
/**
|
|
110
|
+
* Slice 2026-08-01-subagent-merge-and-e2e (Task 7): v3.2 schema
|
|
111
|
+
* bump. Counts how many merge attempts the parent session has
|
|
112
|
+
* made against this dispatch's branch. The conflict-replay
|
|
113
|
+
* orchestrator bumps this on each retry (bounded to ONE re-dispatch
|
|
114
|
+
* per merge attempt; multi-conflict cases escalate). Persisted so
|
|
115
|
+
* the dashboard can render the retry count without replaying the
|
|
116
|
+
* merge transcript.
|
|
117
|
+
*/
|
|
118
|
+
readonly mergeBackAttempts: number;
|
|
119
|
+
/**
|
|
120
|
+
* Slice 4.0.8 RD §4 D4c (presence-lease-graph): the workflow id +
|
|
121
|
+
* graph node id + graphRef this dispatch is bound to. Persisted
|
|
122
|
+
* directly in the dispatch record (NOT in a sidecar) so the
|
|
123
|
+
* envelope-writer `markCompleted` can auto-transition the bound
|
|
124
|
+
* graph node to `envelope-received` with `ackStatus=pending` in
|
|
125
|
+
* one protected update.
|
|
126
|
+
*
|
|
127
|
+
* The schema bump from `3.2 → 4.0.0` is BREAKING in the sense
|
|
128
|
+
* that the literal type is narrowed. The optional `?` keeps back-
|
|
129
|
+
* compat for old records that pre-date the binding (the
|
|
130
|
+
* `upgradeRecord` reader defaults them to `null`).
|
|
131
|
+
*/
|
|
132
|
+
readonly workflowId: string | null;
|
|
133
|
+
readonly graphNodeId: string | null;
|
|
134
|
+
readonly graphRef: string | null;
|
|
135
|
+
/**
|
|
136
|
+
* Phase A Task 8: dispatch execution mode. `in-process` is the
|
|
137
|
+
* current behavior (LLM-side runner, no separate OS process).
|
|
138
|
+
* `detached` is the new path: `peaks sub-agent dispatch --mode
|
|
139
|
+
* detached` spawns a real child OS process running a different
|
|
140
|
+
* LLM vendor (claude / codex / copilot) and reports back via
|
|
141
|
+
* the dispatch record. v4.1.0 is the additive bump; legacy v4.0.0
|
|
142
|
+
* records upgrade to `in-process` on read.
|
|
143
|
+
*/
|
|
144
|
+
readonly mode: 'in-process' | 'detached';
|
|
145
|
+
/**
|
|
146
|
+
* Phase A Task 8: vendor id when `mode='detached'`. `null` when
|
|
147
|
+
* the dispatch is in-process. Reserved for future use; current
|
|
148
|
+
* detached sub-agents use `claude` but the schema also accepts
|
|
149
|
+
* `codex` and `copilot` for the vendor-neutral adapter layer.
|
|
150
|
+
*/
|
|
151
|
+
readonly vendor: 'claude' | 'codex' | 'copilot' | null;
|
|
152
|
+
/**
|
|
153
|
+
* Phase A Task 8: G8 autoCompact events accumulated by the child
|
|
154
|
+
* LLM during a detached run. Each event records the threshold
|
|
155
|
+
* that fired (0.85 = first warning, 0.95 = second warning) plus
|
|
156
|
+
* token counts before/after. Empty for in-process dispatches
|
|
157
|
+
* and for legacy records upgraded on read.
|
|
158
|
+
*/
|
|
159
|
+
readonly autoCompactEvents: ReadonlyArray<{
|
|
160
|
+
readonly at: number;
|
|
161
|
+
readonly threshold: '0.85' | '0.95';
|
|
162
|
+
readonly tokensBefore: number;
|
|
163
|
+
readonly tokensAfter: number;
|
|
164
|
+
readonly scratchFile?: string;
|
|
165
|
+
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Phase A Task 8: G8 token-usage accounting for detached sub-agents.
|
|
168
|
+
* Detached runs have "unlimited spend but recorded" semantics —
|
|
169
|
+
* the cost is recorded for audit but not enforced. `null` when
|
|
170
|
+
* the dispatch is in-process (no detached accounting) or for
|
|
171
|
+
* legacy records upgraded on read.
|
|
172
|
+
*/
|
|
173
|
+
readonly tokenUsage: {
|
|
174
|
+
readonly promptTokens: number;
|
|
175
|
+
readonly completionTokens: number;
|
|
176
|
+
readonly totalCostUsd?: number;
|
|
177
|
+
} | null;
|
|
178
|
+
}
|
|
179
|
+
/** Input for the initial write. */
|
|
180
|
+
export type WriteInitialDispatchInput = {
|
|
181
|
+
projectRoot: string;
|
|
182
|
+
sessionId: string;
|
|
183
|
+
requestId: string;
|
|
184
|
+
role: string;
|
|
185
|
+
prompt: string;
|
|
186
|
+
toolCall: SubAgentToolCall;
|
|
187
|
+
batchId: string;
|
|
188
|
+
/** Override the timestamp (testing). */
|
|
189
|
+
now?: () => Date;
|
|
190
|
+
/**
|
|
191
|
+
* Slice 2026-07-29-worktree-l2-extended Part 3.A: the worktree
|
|
192
|
+
* lease id this dispatch owns (set by `peaks sub-agent dispatch
|
|
193
|
+
* --isolation worktree`). Persisted so the finalize-time release
|
|
194
|
+
* hook in `markCompleted` can fire even after the dispatch
|
|
195
|
+
* process exits. Optional; absent when the dispatch did not
|
|
196
|
+
* request isolation.
|
|
197
|
+
*/
|
|
198
|
+
leaseId?: string | null;
|
|
199
|
+
/**
|
|
200
|
+
* Slice 2026-07-29-worktree-l2-extended Part 7: ISO timestamp
|
|
201
|
+
* when the isolation mode was set up. Optional on the input
|
|
202
|
+
* (defaults to `null`); dispatch-commands.ts passes the spawn
|
|
203
|
+
* time when `--isolation` is requested.
|
|
204
|
+
*/
|
|
205
|
+
isolationStartedAt?: string | null;
|
|
206
|
+
/**
|
|
207
|
+
* Slice 4.0.8: workflow graph binding for the dispatch. Defaults
|
|
208
|
+
* to `null` so a non-graph dispatch (legacy CLI flow, ad-hoc
|
|
209
|
+
* dispatch) still writes a v4.0.0 record.
|
|
210
|
+
*/
|
|
211
|
+
workflowId?: string | null;
|
|
212
|
+
graphNodeId?: string | null;
|
|
213
|
+
graphRef?: string | null;
|
|
214
|
+
/**
|
|
215
|
+
* Phase A Task 8: dispatch execution mode. Default `'in-process'`
|
|
216
|
+
* preserves the current LLM-side runner behavior. `'detached'`
|
|
217
|
+
* triggers the new real-OS-process path via
|
|
218
|
+
* `peaks sub-agent dispatch --mode detached`.
|
|
219
|
+
*/
|
|
220
|
+
mode?: 'in-process' | 'detached';
|
|
221
|
+
/**
|
|
222
|
+
* Phase A Task 8: vendor id when `mode='detached'`. Required by
|
|
223
|
+
* the adapter layer to know which CLI / runtime to spawn. The
|
|
224
|
+
* schema accepts the three vendors peaks-loop has adapters for
|
|
225
|
+
* (claude / codex / copilot). Ignored when `mode='in-process'`.
|
|
226
|
+
*/
|
|
227
|
+
vendor?: 'claude' | 'codex' | 'copilot';
|
|
228
|
+
/**
|
|
229
|
+
* Phase A Task 8: G8 autoCompact events accumulated by the child
|
|
230
|
+
* LLM. Optional on input — most dispatches start with an empty
|
|
231
|
+
* array and the detached runner appends events as they fire.
|
|
232
|
+
*/
|
|
233
|
+
autoCompactEvents?: Array<{
|
|
234
|
+
at: number;
|
|
235
|
+
threshold: '0.85' | '0.95';
|
|
236
|
+
tokensBefore: number;
|
|
237
|
+
tokensAfter: number;
|
|
238
|
+
scratchFile?: string;
|
|
239
|
+
}>;
|
|
240
|
+
/**
|
|
241
|
+
* Phase A Task 8: G8 token-usage accounting. Detached runs
|
|
242
|
+
* record spend for audit (unlimited, but persisted). Optional
|
|
243
|
+
* on input; the detached runner fills this in as it streams
|
|
244
|
+
* usage from the vendor API.
|
|
245
|
+
*/
|
|
246
|
+
tokenUsage?: {
|
|
247
|
+
promptTokens: number;
|
|
248
|
+
completionTokens: number;
|
|
249
|
+
totalCostUsd?: number;
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
/** Heartbeat write input. */
|
|
253
|
+
export type AppendHeartbeatInput = {
|
|
254
|
+
recordPath: string;
|
|
255
|
+
status: HeartbeatStatus;
|
|
256
|
+
progress: number;
|
|
257
|
+
note?: string;
|
|
258
|
+
now?: () => Date;
|
|
259
|
+
};
|
|
260
|
+
/** Lifecycle transition input. */
|
|
261
|
+
export type LifecycleInput = {
|
|
262
|
+
recordPath: string;
|
|
263
|
+
outcome: DispatchOutcome;
|
|
264
|
+
status: DispatchRecordStatus;
|
|
265
|
+
artifactPaths?: readonly string[];
|
|
266
|
+
now?: () => Date;
|
|
267
|
+
/**
|
|
268
|
+
* Slice 2026-06-23-audit-4th #A4: trusted project root. Required
|
|
269
|
+
* so the active-dispatches index can be updated without deriving
|
|
270
|
+
* the root from the recordPath (the same anti-pattern that
|
|
271
|
+
* audit-3rd #1 fixed for heartbeat). The CLI / LLM-side runner
|
|
272
|
+
* passes this from `--project` or `process.cwd()`.
|
|
273
|
+
*/
|
|
274
|
+
projectRoot?: string;
|
|
275
|
+
};
|
|
276
|
+
export declare const MAX_PROMPT_BYTES: number;
|
|
277
|
+
export declare const NOTE_MAX_CHARS = 200;
|
|
278
|
+
export declare const MS_PER_DAY: number;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRD-002b slice 2 — extract dispatch-record size budgets (max-prompt
|
|
3
|
+
* bytes, note truncation cap) + time-math primitives so the
|
|
4
|
+
* no-magic-numbers rule stops flagging the writer pipeline.
|
|
5
|
+
*/
|
|
6
|
+
const BYTES_PER_KB = 1024;
|
|
7
|
+
const MAX_PROMPT_KB = 256;
|
|
8
|
+
export const MAX_PROMPT_BYTES = MAX_PROMPT_KB * BYTES_PER_KB;
|
|
9
|
+
export const NOTE_MAX_CHARS = 200;
|
|
10
|
+
const MS_PER_SECOND = 1_000;
|
|
11
|
+
const SECONDS_PER_MINUTE = 60;
|
|
12
|
+
const MINUTES_PER_HOUR = 60;
|
|
13
|
+
const HOURS_PER_DAY = 24;
|
|
14
|
+
export const MS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND;
|
|
15
|
+
const REDACTION_MAX_SCAN_DEPTH = 20;
|