openplanr 1.16.2 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/operate.d.ts.map +1 -1
- package/dist/cli/commands/operate.js +185 -6
- package/dist/cli/commands/operate.js.map +1 -1
- package/dist/services/operate/advisors.d.ts +46 -1
- package/dist/services/operate/advisors.d.ts.map +1 -1
- package/dist/services/operate/advisors.js +124 -13
- package/dist/services/operate/advisors.js.map +1 -1
- package/dist/services/operate/artifacts.d.ts +27 -1
- package/dist/services/operate/artifacts.d.ts.map +1 -1
- package/dist/services/operate/artifacts.js +103 -1
- package/dist/services/operate/artifacts.js.map +1 -1
- package/dist/services/operate/cadence.d.ts +50 -0
- package/dist/services/operate/cadence.d.ts.map +1 -0
- package/dist/services/operate/cadence.js +56 -0
- package/dist/services/operate/cadence.js.map +1 -0
- package/dist/services/operate/citation-resolution.d.ts +101 -0
- package/dist/services/operate/citation-resolution.d.ts.map +1 -0
- package/dist/services/operate/citation-resolution.js +308 -0
- package/dist/services/operate/citation-resolution.js.map +1 -0
- package/dist/services/operate/config.d.ts +67 -1
- package/dist/services/operate/config.d.ts.map +1 -1
- package/dist/services/operate/config.js +158 -9
- package/dist/services/operate/config.js.map +1 -1
- package/dist/services/operate/decision-brief.d.ts +148 -0
- package/dist/services/operate/decision-brief.d.ts.map +1 -0
- package/dist/services/operate/decision-brief.js +194 -0
- package/dist/services/operate/decision-brief.js.map +1 -0
- package/dist/services/operate/doctor.d.ts.map +1 -1
- package/dist/services/operate/doctor.js +151 -3
- package/dist/services/operate/doctor.js.map +1 -1
- package/dist/services/operate/engine.d.ts +66 -2
- package/dist/services/operate/engine.d.ts.map +1 -1
- package/dist/services/operate/engine.js +219 -7
- package/dist/services/operate/engine.js.map +1 -1
- package/dist/services/operate/event-store.d.ts +35 -1
- package/dist/services/operate/event-store.d.ts.map +1 -1
- package/dist/services/operate/event-store.js +142 -29
- package/dist/services/operate/event-store.js.map +1 -1
- package/dist/services/operate/evidence-cache.d.ts +25 -1
- package/dist/services/operate/evidence-cache.d.ts.map +1 -1
- package/dist/services/operate/evidence-cache.js +62 -1
- package/dist/services/operate/evidence-cache.js.map +1 -1
- package/dist/services/operate/evidence.d.ts +20 -1
- package/dist/services/operate/evidence.d.ts.map +1 -1
- package/dist/services/operate/evidence.js +119 -0
- package/dist/services/operate/evidence.js.map +1 -1
- package/dist/services/operate/index.d.ts.map +1 -1
- package/dist/services/operate/index.js +72 -5
- package/dist/services/operate/index.js.map +1 -1
- package/dist/services/operate/interaction/action-service.d.ts +17 -0
- package/dist/services/operate/interaction/action-service.d.ts.map +1 -1
- package/dist/services/operate/interaction/action-service.js +19 -0
- package/dist/services/operate/interaction/action-service.js.map +1 -1
- package/dist/services/operate/journal.d.ts.map +1 -1
- package/dist/services/operate/journal.js +11 -5
- package/dist/services/operate/journal.js.map +1 -1
- package/dist/services/operate/maintenance.d.ts.map +1 -1
- package/dist/services/operate/maintenance.js +9 -2
- package/dist/services/operate/maintenance.js.map +1 -1
- package/dist/services/operate/migration.d.ts +68 -0
- package/dist/services/operate/migration.d.ts.map +1 -0
- package/dist/services/operate/migration.js +235 -0
- package/dist/services/operate/migration.js.map +1 -0
- package/dist/services/operate/mission-dispatch.d.ts +193 -0
- package/dist/services/operate/mission-dispatch.d.ts.map +1 -0
- package/dist/services/operate/mission-dispatch.js +444 -0
- package/dist/services/operate/mission-dispatch.js.map +1 -0
- package/dist/services/operate/projection-persistence.d.ts +6 -0
- package/dist/services/operate/projection-persistence.d.ts.map +1 -1
- package/dist/services/operate/projection-persistence.js +87 -11
- package/dist/services/operate/projection-persistence.js.map +1 -1
- package/dist/services/operate/projection.d.ts +26 -1
- package/dist/services/operate/projection.d.ts.map +1 -1
- package/dist/services/operate/projection.js +62 -0
- package/dist/services/operate/projection.js.map +1 -1
- package/dist/services/operate/protocol.d.ts +46 -2
- package/dist/services/operate/protocol.d.ts.map +1 -1
- package/dist/services/operate/protocol.js +57 -5
- package/dist/services/operate/protocol.js.map +1 -1
- package/dist/services/operate/read-only-providers.d.ts +37 -0
- package/dist/services/operate/read-only-providers.d.ts.map +1 -1
- package/dist/services/operate/read-only-providers.js +130 -0
- package/dist/services/operate/read-only-providers.js.map +1 -1
- package/dist/services/operate/reports.d.ts +13 -0
- package/dist/services/operate/reports.d.ts.map +1 -1
- package/dist/services/operate/reports.js +217 -1
- package/dist/services/operate/reports.js.map +1 -1
- package/dist/services/operate/routes.d.ts.map +1 -1
- package/dist/services/operate/routes.js +79 -5
- package/dist/services/operate/routes.js.map +1 -1
- package/dist/services/operate/types.d.ts +120 -4
- package/dist/services/operate/types.d.ts.map +1 -1
- package/dist/services/operate/types.js +13 -0
- package/dist/services/operate/types.js.map +1 -1
- package/dist/services/operate/workspace.d.ts +7 -0
- package/dist/services/operate/workspace.d.ts.map +1 -1
- package/dist/services/operate/workspace.js +14 -5
- package/dist/services/operate/workspace.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { readdir, readFile, realpath, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { resolveOperatingPipelineRoot } from './protocol.js';
|
|
5
|
+
import { executeGitReadOnly } from './read-only-providers.js';
|
|
6
|
+
import { compareSensitivity } from './redaction.js';
|
|
7
|
+
import { OperateError, } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* The bounded read-only capability set for a mission-mode lens. Kept in lockstep
|
|
10
|
+
* with the pipeline's `MISSION_READ_ONLY_TOOLS`: no write, execute, network, or
|
|
11
|
+
* environment tool is present, so a grant assembled from it can never authorize
|
|
12
|
+
* a mutating or escaping action.
|
|
13
|
+
*/
|
|
14
|
+
export const MISSION_READ_ONLY_TOOLS = Object.freeze([
|
|
15
|
+
'file-read',
|
|
16
|
+
'glob',
|
|
17
|
+
'content-search',
|
|
18
|
+
'git-log',
|
|
19
|
+
'git-show',
|
|
20
|
+
'git-diff',
|
|
21
|
+
'git-blame',
|
|
22
|
+
]);
|
|
23
|
+
function isReadOnlyTool(tool) {
|
|
24
|
+
return MISSION_READ_ONLY_TOOLS.includes(tool);
|
|
25
|
+
}
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Dispatch-mode resolution (FR4 / E-004) + runtime enforceability (FR2)
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* The registry default dispatch mode for a role. The v1.3 role registry carries
|
|
31
|
+
* a `dispatchMode` field; while it is still published without one every role
|
|
32
|
+
* defaults to `mission`, and only a role explicitly marked `pack` opts out —
|
|
33
|
+
* exactly the pipeline's own `role?.dispatchMode ?? 'mission'` convention.
|
|
34
|
+
*/
|
|
35
|
+
export function operatingRegistryDispatchMode(role) {
|
|
36
|
+
return role?.dispatchMode === 'pack' ? 'pack' : 'mission';
|
|
37
|
+
}
|
|
38
|
+
let cachedRuntimeEnforcement = null;
|
|
39
|
+
/**
|
|
40
|
+
* Load the pipeline's runtime registry and derive, per runtime, whether it
|
|
41
|
+
* natively enforces the bounded read-only boundary. The classification is the
|
|
42
|
+
* published `capabilities.toolIsolation === 'enforced'` bit — the same one the
|
|
43
|
+
* pipeline's adapter handoff uses — so it stays a single source of truth rather
|
|
44
|
+
* than a field the registry schema cannot carry.
|
|
45
|
+
*
|
|
46
|
+
* Fails CLOSED: if the mission-capable pipeline root or its runtime module
|
|
47
|
+
* cannot be resolved, nothing is treated as enforcing, so a runtime whose
|
|
48
|
+
* isolation cannot be verified never receives a native lens (DoD).
|
|
49
|
+
*/
|
|
50
|
+
async function loadRuntimeEnforcement() {
|
|
51
|
+
cachedRuntimeEnforcement ??= (async () => {
|
|
52
|
+
try {
|
|
53
|
+
const root = resolveOperatingPipelineRoot({ requireMission: true });
|
|
54
|
+
if (!root)
|
|
55
|
+
return () => false;
|
|
56
|
+
const module = (await import(pathToFileURL(path.join(root, 'lib', 'pipeline', 'runtime.mjs')).href));
|
|
57
|
+
const adapters = module.listRuntimeAdapters();
|
|
58
|
+
return (runtime) => {
|
|
59
|
+
if (!runtime || runtime === 'auto')
|
|
60
|
+
return false;
|
|
61
|
+
let id;
|
|
62
|
+
try {
|
|
63
|
+
id = module.normalizeRuntime(runtime);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const adapter = adapters.find((entry) => entry.id === id);
|
|
69
|
+
return adapter?.capabilities?.toolIsolation === 'enforced';
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return () => false;
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
return cachedRuntimeEnforcement;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether the given runtime natively enforces the mission read-only boundary.
|
|
80
|
+
* `claude-code` enforces; `codex` and `cursor` are advisory and do not.
|
|
81
|
+
*/
|
|
82
|
+
export async function operatingRuntimeEnforcesBoundedReadOnly(runtime) {
|
|
83
|
+
return (await loadRuntimeEnforcement())(runtime);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve one role's effective dispatch isolation.
|
|
87
|
+
*
|
|
88
|
+
* FR4 states Codex SHOULD dispatch natively, but FR2's fail-closed rule
|
|
89
|
+
* overrides it: a runtime whose tool isolation is only advisory (`codex`,
|
|
90
|
+
* `cursor`) cannot guarantee the bounded read-only boundary, so it routes to the
|
|
91
|
+
* structured provider path until its isolation is enforceable. Only a runtime
|
|
92
|
+
* that natively enforces tool isolation (`toolIsolation === 'enforced'`, i.e.
|
|
93
|
+
* `claude-code`) hosting a native-isolated adapter receives a native mission
|
|
94
|
+
* lens; every other combination fails closed here. This is the exact FR2/FR4
|
|
95
|
+
* reconciliation the spec calls out, recorded in the returned `reconciliation`
|
|
96
|
+
* so it appears in the dispatch provenance.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveOperatingDispatchMode(input) {
|
|
99
|
+
const mode = input.override ?? input.registryDefault;
|
|
100
|
+
if (mode === 'pack') {
|
|
101
|
+
return {
|
|
102
|
+
roleId: input.roleId,
|
|
103
|
+
mode,
|
|
104
|
+
isolation: 'enforced-empty-tools',
|
|
105
|
+
native: false,
|
|
106
|
+
reconciliation: input.override === 'pack'
|
|
107
|
+
? 'dispatchModeOverrides rolled this role back to the v1.2 empty-tool pack path'
|
|
108
|
+
: 'registry default selects the v1.2 empty-tool pack path',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (input.runtimeEnforcesBoundedReadOnly && input.adapterNativeCapable) {
|
|
112
|
+
return {
|
|
113
|
+
roleId: input.roleId,
|
|
114
|
+
mode,
|
|
115
|
+
isolation: 'enforced-read-only-bounded',
|
|
116
|
+
native: true,
|
|
117
|
+
reconciliation: 'runtime natively enforces tool isolation; a bounded read-only mission lens is dispatched',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
roleId: input.roleId,
|
|
122
|
+
mode,
|
|
123
|
+
isolation: 'fail-closed-structured-provider',
|
|
124
|
+
native: false,
|
|
125
|
+
reconciliation: input.runtimeEnforcesBoundedReadOnly
|
|
126
|
+
? 'adapter cannot host a bounded native lens; falling back to the structured provider path'
|
|
127
|
+
: 'runtime tool isolation is advisory or unverifiable (FR2 fail-closed overrides FR4); routing to the structured provider path',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Sensitivity-ceiling root narrowing (FR2)
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
/**
|
|
134
|
+
* The top-level path segment a declared read root is derived from. Mission roots
|
|
135
|
+
* are single top-level directory segments (see `deriveOperatingDeclaredRoots`).
|
|
136
|
+
*/
|
|
137
|
+
function topSegment(relativePath) {
|
|
138
|
+
const [top] = relativePath.split('/');
|
|
139
|
+
return top && top.length > 0 ? top : null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Narrow a role's declared read roots by DENY-LISTING any root that contains an
|
|
143
|
+
* evidence item above the role's sensitivity ceiling, so no above-ceiling file
|
|
144
|
+
* is readable even inside a granted root. Pass the FULL (pre-ceiling-filter)
|
|
145
|
+
* evidence index: the narrowing needs to see the above-ceiling items the packet
|
|
146
|
+
* index itself excludes. Combined with the read-time ceiling check on the
|
|
147
|
+
* bounded reader, this is the belt-and-suspenders that preserves the pack-era
|
|
148
|
+
* filter-before-handoff guarantee under agent-driven reads.
|
|
149
|
+
*/
|
|
150
|
+
export function narrowMissionRootsToCeiling(input) {
|
|
151
|
+
const denied = new Set();
|
|
152
|
+
for (const item of input.evidenceIndex) {
|
|
153
|
+
if (!item.path)
|
|
154
|
+
continue;
|
|
155
|
+
if (compareSensitivity(item.sensitivity, input.ceiling) <= 0)
|
|
156
|
+
continue;
|
|
157
|
+
const top = topSegment(item.path);
|
|
158
|
+
if (top)
|
|
159
|
+
denied.add(top);
|
|
160
|
+
}
|
|
161
|
+
return input.declaredRoots.filter((root) => !denied.has(root)).sort();
|
|
162
|
+
}
|
|
163
|
+
const MAX_MISSION_READ_BYTES = 256 * 1024;
|
|
164
|
+
const MAX_MISSION_WALK_ENTRIES = 5_000;
|
|
165
|
+
function refuseCapability(tool) {
|
|
166
|
+
throw new OperateError('E_OPERATE_PROVIDER_READ_ONLY', `Mission tool "${tool}" is not in the bounded read-only grant; write, execute, ` +
|
|
167
|
+
'network, and environment access are refused.', { tool, allowed: [...MISSION_READ_ONLY_TOOLS] });
|
|
168
|
+
}
|
|
169
|
+
function resolvedRoots(boundary) {
|
|
170
|
+
return boundary.roots.map((root) => path.resolve(root));
|
|
171
|
+
}
|
|
172
|
+
function isContained(roots, absolute) {
|
|
173
|
+
return roots.some((root) => absolute === root || absolute.startsWith(`${root}${path.sep}`));
|
|
174
|
+
}
|
|
175
|
+
function resolveWithinRoots(boundary, requestPath) {
|
|
176
|
+
if (typeof requestPath !== 'string' || requestPath.length === 0) {
|
|
177
|
+
throw new OperateError('E_OPERATE_PATH_ESCAPE', 'Mission read requires a non-empty path.');
|
|
178
|
+
}
|
|
179
|
+
const roots = resolvedRoots(boundary);
|
|
180
|
+
const candidates = path.isAbsolute(requestPath)
|
|
181
|
+
? [path.resolve(requestPath)]
|
|
182
|
+
: roots.map((root) => path.resolve(root, requestPath));
|
|
183
|
+
for (const candidate of candidates) {
|
|
184
|
+
if (isContained(roots, candidate))
|
|
185
|
+
return candidate;
|
|
186
|
+
}
|
|
187
|
+
throw new OperateError('E_OPERATE_PATH_ESCAPE', `Mission read target "${requestPath}" resolves outside the declared read roots.`, { roots: [...boundary.roots] });
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Defeat symlink escapes: after path-based containment, resolve the real target
|
|
191
|
+
* and re-check it against the real roots. Both sides are realpath-resolved so a
|
|
192
|
+
* platform-level symlink on the temp namespace (e.g. macOS `/var`→`/private/var`)
|
|
193
|
+
* does not produce a false escape.
|
|
194
|
+
*/
|
|
195
|
+
async function assertNoSymlinkEscape(boundary, absolute) {
|
|
196
|
+
let real;
|
|
197
|
+
try {
|
|
198
|
+
real = await realpath(absolute);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Target does not exist yet; the caller's read/stat surfaces the real error.
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const realRoots = await Promise.all(resolvedRoots(boundary).map((root) => realpath(root).catch(() => root)));
|
|
205
|
+
if (!isContained(realRoots, real)) {
|
|
206
|
+
throw new OperateError('E_OPERATE_PATH_ESCAPE', `Mission read target "${absolute}" escapes the declared read roots through a symlink.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function classify(boundary, absolute) {
|
|
210
|
+
return (boundary.sensitivityByPath?.get(absolute) ?? boundary.defaultSensitivity ?? boundary.ceiling);
|
|
211
|
+
}
|
|
212
|
+
function assertBelowCeiling(boundary, absolute) {
|
|
213
|
+
const sensitivity = classify(boundary, absolute);
|
|
214
|
+
if (compareSensitivity(sensitivity, boundary.ceiling) > 0) {
|
|
215
|
+
throw new OperateError('E_OPERATE_EVIDENCE_REJECTED', `Mission read of "${absolute}" is ${sensitivity}, above the ${boundary.ceiling} ceiling.`, { sensitivity, ceiling: boundary.ceiling });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function readBoundedFile(boundary, requestPath) {
|
|
219
|
+
const absolute = resolveWithinRoots(boundary, requestPath);
|
|
220
|
+
await assertNoSymlinkEscape(boundary, absolute);
|
|
221
|
+
assertBelowCeiling(boundary, absolute);
|
|
222
|
+
const info = await stat(absolute);
|
|
223
|
+
if (!info.isFile()) {
|
|
224
|
+
throw new OperateError('E_OPERATE_EVIDENCE_REJECTED', `Mission read target "${absolute}" is not a file.`);
|
|
225
|
+
}
|
|
226
|
+
if (info.size > MAX_MISSION_READ_BYTES) {
|
|
227
|
+
throw new OperateError('E_OPERATE_EVIDENCE_REJECTED', `Mission read target "${absolute}" exceeds the ${MAX_MISSION_READ_BYTES}-byte read bound.`);
|
|
228
|
+
}
|
|
229
|
+
return readFile(absolute, 'utf8');
|
|
230
|
+
}
|
|
231
|
+
async function* walkRoot(root) {
|
|
232
|
+
let visited = 0;
|
|
233
|
+
const stack = [root];
|
|
234
|
+
while (stack.length > 0) {
|
|
235
|
+
const current = stack.pop();
|
|
236
|
+
const entries = await readdir(current, { withFileTypes: true }).catch(() => null);
|
|
237
|
+
if (!entries)
|
|
238
|
+
continue;
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
if (entry.name === '.git' || entry.name === 'node_modules')
|
|
241
|
+
continue;
|
|
242
|
+
const child = path.join(current, entry.name);
|
|
243
|
+
if (entry.isDirectory()) {
|
|
244
|
+
stack.push(child);
|
|
245
|
+
}
|
|
246
|
+
else if (entry.isFile()) {
|
|
247
|
+
visited += 1;
|
|
248
|
+
if (visited > MAX_MISSION_WALK_ENTRIES)
|
|
249
|
+
return;
|
|
250
|
+
yield child;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Render a filesystem path with forward-slash separators so a glob pattern —
|
|
257
|
+
* always authored with `/` — matches identically on every platform. `path.join`
|
|
258
|
+
* yields `\` separators on Windows, which the pattern's `/` can never match, so
|
|
259
|
+
* a recursive `.ts` glob would otherwise return nothing on Windows. Normalizing
|
|
260
|
+
* only the candidate fed to the matcher (never the stored/returned path) keeps
|
|
261
|
+
* the emitted matches in native form for the caller's own containment checks. On
|
|
262
|
+
* POSIX `path.sep` is `/`, so this is a no-op.
|
|
263
|
+
*/
|
|
264
|
+
function toGlobMatchPath(target) {
|
|
265
|
+
return target.split(path.sep).join('/');
|
|
266
|
+
}
|
|
267
|
+
function simplePatternToRegExp(pattern) {
|
|
268
|
+
let out = '';
|
|
269
|
+
let index = 0;
|
|
270
|
+
while (index < pattern.length) {
|
|
271
|
+
const char = pattern[index];
|
|
272
|
+
if (char === '*') {
|
|
273
|
+
if (pattern[index + 1] === '*') {
|
|
274
|
+
out += '.*';
|
|
275
|
+
index += 2;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
out += '[^/]*';
|
|
279
|
+
index += 1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
out += char.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
284
|
+
index += 1;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return new RegExp(`${out}$`);
|
|
288
|
+
}
|
|
289
|
+
async function globWithinRoots(boundary, pattern, root) {
|
|
290
|
+
const roots = root ? [resolveWithinRoots(boundary, root)] : resolvedRoots(boundary);
|
|
291
|
+
const matcher = simplePatternToRegExp(pattern || '**/*');
|
|
292
|
+
const matches = [];
|
|
293
|
+
for (const searchRoot of roots) {
|
|
294
|
+
for await (const file of walkRoot(searchRoot)) {
|
|
295
|
+
if (!matcher.test(toGlobMatchPath(file)))
|
|
296
|
+
continue;
|
|
297
|
+
try {
|
|
298
|
+
assertBelowCeiling(boundary, file);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
matches.push(file);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return [...new Set(matches)].sort();
|
|
307
|
+
}
|
|
308
|
+
async function contentSearchWithinRoots(boundary, query, root) {
|
|
309
|
+
if (typeof query !== 'string' || query.length === 0) {
|
|
310
|
+
throw new OperateError('E_OPERATE_EVIDENCE_REJECTED', 'Mission content-search requires a query.');
|
|
311
|
+
}
|
|
312
|
+
const roots = root ? [resolveWithinRoots(boundary, root)] : resolvedRoots(boundary);
|
|
313
|
+
const hits = [];
|
|
314
|
+
for (const searchRoot of roots) {
|
|
315
|
+
for await (const file of walkRoot(searchRoot)) {
|
|
316
|
+
try {
|
|
317
|
+
assertBelowCeiling(boundary, file);
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const info = await stat(file).catch(() => null);
|
|
323
|
+
if (!info?.isFile() || info.size > MAX_MISSION_READ_BYTES)
|
|
324
|
+
continue;
|
|
325
|
+
const content = await readFile(file, 'utf8').catch(() => null);
|
|
326
|
+
if (content === null)
|
|
327
|
+
continue;
|
|
328
|
+
content.split(/\r?\n/).forEach((text, index) => {
|
|
329
|
+
if (text.includes(query))
|
|
330
|
+
hits.push({ path: file, line: index + 1, text: text.slice(0, 512) });
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return hits;
|
|
335
|
+
}
|
|
336
|
+
function assertGitArgsBounded(args) {
|
|
337
|
+
for (const argument of args) {
|
|
338
|
+
if (typeof argument !== 'string') {
|
|
339
|
+
throw new OperateError('E_OPERATE_PROVIDER_READ_ONLY', 'Mission git arguments must be strings.');
|
|
340
|
+
}
|
|
341
|
+
if (argument.startsWith('--output') || argument === '-o' || argument.includes('../..')) {
|
|
342
|
+
throw new OperateError('E_OPERATE_PROVIDER_READ_ONLY', 'Mission git arguments cannot redirect output.');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async function runBoundedGit(boundary, subcommand, request) {
|
|
347
|
+
const repositoryRoot = boundary.repositoryRoot
|
|
348
|
+
? path.resolve(boundary.repositoryRoot)
|
|
349
|
+
: resolvedRoots(boundary)[0];
|
|
350
|
+
if (!repositoryRoot) {
|
|
351
|
+
throw new OperateError('E_OPERATE_PROVIDER_READ_ONLY', 'Mission git tools require a repository root.');
|
|
352
|
+
}
|
|
353
|
+
const extra = request.args ?? [];
|
|
354
|
+
assertGitArgsBounded(extra);
|
|
355
|
+
const args = [subcommand, ...extra];
|
|
356
|
+
if (request.path) {
|
|
357
|
+
// Any path argument stays confined to the declared roots.
|
|
358
|
+
const confined = resolveWithinRoots(boundary, request.path);
|
|
359
|
+
args.push('--', path.relative(repositoryRoot, confined) || '.');
|
|
360
|
+
}
|
|
361
|
+
// `executeGitReadOnly` re-asserts the read-only git allowlist (no mutation,
|
|
362
|
+
// no repository/config overrides) as a second, independent gate.
|
|
363
|
+
return executeGitReadOnly(repositoryRoot, args);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* The single audited entry point for a mission lens's tool use. Every request is
|
|
367
|
+
* checked against the bounded read-only grant BEFORE any filesystem or git
|
|
368
|
+
* access: a tool outside the grant (write, execute, network, environment, or any
|
|
369
|
+
* unknown surface) is refused, a target outside the declared roots is refused as
|
|
370
|
+
* a path escape, and a target above the sensitivity ceiling is refused at read
|
|
371
|
+
* time. There is deliberately no write/execute/network/environment tool to
|
|
372
|
+
* invoke — those channels do not exist on this surface at all.
|
|
373
|
+
*/
|
|
374
|
+
export async function invokeMissionTool(boundary, request) {
|
|
375
|
+
const tool = String(request.tool ?? '');
|
|
376
|
+
if (!isReadOnlyTool(tool))
|
|
377
|
+
refuseCapability(tool);
|
|
378
|
+
switch (tool) {
|
|
379
|
+
case 'file-read': {
|
|
380
|
+
const requestPath = String(request.path ?? '');
|
|
381
|
+
return {
|
|
382
|
+
tool: 'file-read',
|
|
383
|
+
path: requestPath,
|
|
384
|
+
content: await readBoundedFile(boundary, requestPath),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
case 'glob': {
|
|
388
|
+
const pattern = request.pattern ?? '**/*';
|
|
389
|
+
const root = request.root;
|
|
390
|
+
return { tool: 'glob', matches: await globWithinRoots(boundary, pattern, root) };
|
|
391
|
+
}
|
|
392
|
+
case 'content-search': {
|
|
393
|
+
const query = String(request.query ?? '');
|
|
394
|
+
const root = request.root;
|
|
395
|
+
return {
|
|
396
|
+
tool: 'content-search',
|
|
397
|
+
matches: await contentSearchWithinRoots(boundary, query, root),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
case 'git-log':
|
|
401
|
+
case 'git-show':
|
|
402
|
+
case 'git-diff':
|
|
403
|
+
case 'git-blame': {
|
|
404
|
+
const subcommand = tool.slice('git-'.length);
|
|
405
|
+
const typed = request;
|
|
406
|
+
return { tool, output: await runBoundedGit(boundary, subcommand, typed) };
|
|
407
|
+
}
|
|
408
|
+
default:
|
|
409
|
+
return refuseCapability(tool);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* The concrete callable tool surface handed to an in-process native harness:
|
|
414
|
+
* exactly the read-only tools, each bound to this boundary. There is no write,
|
|
415
|
+
* execute, network, or environment callable on the returned object, so a harness
|
|
416
|
+
* that walks it can never reach a mutating or escaping capability.
|
|
417
|
+
*/
|
|
418
|
+
export function createMissionToolset(boundary) {
|
|
419
|
+
const toolset = {};
|
|
420
|
+
for (const tool of MISSION_READ_ONLY_TOOLS) {
|
|
421
|
+
toolset[tool] = (request) => invokeMissionTool(boundary, { ...request, tool });
|
|
422
|
+
}
|
|
423
|
+
return toolset;
|
|
424
|
+
}
|
|
425
|
+
// ---------------------------------------------------------------------------
|
|
426
|
+
// Fan-out orchestration (FR2 parallel/sequential, FR4 determinism)
|
|
427
|
+
// ---------------------------------------------------------------------------
|
|
428
|
+
/**
|
|
429
|
+
* Fan a per-item dispatch out in parallel where the adapter reports
|
|
430
|
+
* `parallelDispatch: true`, sequentially otherwise. Results are always returned
|
|
431
|
+
* in the SAME order as `items`, so the caller can restore registry order and the
|
|
432
|
+
* reduced events are byte-identical across parallel and sequential dispatch.
|
|
433
|
+
*/
|
|
434
|
+
export async function runMissionDispatchFanOut(input) {
|
|
435
|
+
if (input.parallel) {
|
|
436
|
+
return Promise.all(input.items.map((item) => input.run(item)));
|
|
437
|
+
}
|
|
438
|
+
const results = [];
|
|
439
|
+
for (const item of input.items) {
|
|
440
|
+
results.push(await input.run(item));
|
|
441
|
+
}
|
|
442
|
+
return results;
|
|
443
|
+
}
|
|
444
|
+
//# sourceMappingURL=mission-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission-dispatch.js","sourceRoot":"","sources":["../../../src/services/operate/mission-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EACL,YAAY,GAIb,MAAM,YAAY,CAAC;AAuDpB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,CAAC;IACnD,WAAW;IACX,MAAM;IACN,gBAAgB;IAChB,SAAS;IACT,UAAU;IACV,UAAU;IACV,WAAW;CACH,CAAC,CAAC;AAIZ,SAAS,cAAc,CAAC,IAAY;IAClC,OAAQ,uBAA6C,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,8EAA8E;AAC9E,wEAAwE;AACxE,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,UAAU,6BAA6B,CAAC,IAE7C;IACC,OAAO,IAAI,EAAE,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC;AAED,IAAI,wBAAwB,GAA6D,IAAI,CAAC;AAO9F;;;;;;;;;;GAUG;AACH,KAAK,UAAU,sBAAsB;IACnC,wBAAwB,KAAK,CAAC,KAAK,IAAI,EAAE;QACvC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,4BAA4B,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;YACpE,IAAI,CAAC,IAAI;gBAAE,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC;YAC9B,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAC1B,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CACtE,CAAqC,CAAC;YACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC9C,OAAO,CAAC,OAAO,EAAE,EAAE;gBACjB,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,MAAM;oBAAE,OAAO,KAAK,CAAC;gBACjD,IAAI,EAAU,CAAC;gBACf,IAAI,CAAC;oBACH,EAAE,GAAG,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;gBACxC,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC1D,OAAO,OAAO,EAAE,YAAY,EAAE,aAAa,KAAK,UAAU,CAAC;YAC7D,CAAC,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC;QACrB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IACL,OAAO,wBAAwB,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uCAAuC,CAC3D,OAA2B;IAE3B,OAAO,CAAC,MAAM,sBAAsB,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAM5C;IACC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,eAAe,CAAC;IACrD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI;YACJ,SAAS,EAAE,sBAAsB;YACjC,MAAM,EAAE,KAAK;YACb,cAAc,EACZ,KAAK,CAAC,QAAQ,KAAK,MAAM;gBACvB,CAAC,CAAC,8EAA8E;gBAChF,CAAC,CAAC,wDAAwD;SAC/D,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,8BAA8B,IAAI,KAAK,CAAC,oBAAoB,EAAE,CAAC;QACvE,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI;YACJ,SAAS,EAAE,4BAA4B;YACvC,MAAM,EAAE,IAAI;YACZ,cAAc,EACZ,0FAA0F;SAC7F,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,IAAI;QACJ,SAAS,EAAE,iCAAiC;QAC5C,MAAM,EAAE,KAAK;QACb,cAAc,EAAE,KAAK,CAAC,8BAA8B;YAClD,CAAC,CAAC,yFAAyF;YAC3F,CAAC,CAAC,6HAA6H;KAClI,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,2CAA2C;AAC3C,8EAA8E;AAE9E;;;GAGG;AACH,SAAS,UAAU,CAAC,YAAoB;IACtC,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,2BAA2B,CAAC,KAI3C;IACC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,SAAS;QACzB,IAAI,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAS;QACvE,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,GAAG;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACxE,CAAC;AA4CD,MAAM,sBAAsB,GAAG,GAAG,GAAG,IAAI,CAAC;AAC1C,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAEvC,SAAS,gBAAgB,CAAC,IAAY;IACpC,MAAM,IAAI,YAAY,CACpB,8BAA8B,EAC9B,iBAAiB,IAAI,2DAA2D;QAC9E,8CAA8C,EAChD,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,uBAAuB,CAAC,EAAE,CAChD,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,QAA6B;IAClD,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,WAAW,CAAC,KAAwB,EAAE,QAAgB;IAC7D,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAC9F,CAAC;AAED,SAAS,kBAAkB,CAAC,QAA6B,EAAE,WAAmB;IAC5E,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,YAAY,CAAC,uBAAuB,EAAE,yCAAyC,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAC7C,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC7B,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;IACzD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IACtD,CAAC;IACD,MAAM,IAAI,YAAY,CACpB,uBAAuB,EACvB,wBAAwB,WAAW,6CAA6C,EAChF,EAAE,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,CAC/B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,qBAAqB,CAClC,QAA6B,EAC7B,QAAgB;IAEhB,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;QAC7E,OAAO;IACT,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CACjC,aAAa,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CACxE,CAAC;IACF,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,YAAY,CACpB,uBAAuB,EACvB,wBAAwB,QAAQ,sDAAsD,CACvF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,QAA6B,EAAE,QAAgB;IAC/D,OAAO,CACL,QAAQ,CAAC,iBAAiB,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,kBAAkB,IAAI,QAAQ,CAAC,OAAO,CAC7F,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,QAA6B,EAAE,QAAgB;IACzE,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjD,IAAI,kBAAkB,CAAC,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,YAAY,CACpB,6BAA6B,EAC7B,oBAAoB,QAAQ,QAAQ,WAAW,eAAe,QAAQ,CAAC,OAAO,WAAW,EACzF,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAC3C,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,QAA6B,EAC7B,WAAmB;IAEnB,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC3D,MAAM,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChD,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,YAAY,CACpB,6BAA6B,EAC7B,wBAAwB,QAAQ,kBAAkB,CACnD,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,GAAG,sBAAsB,EAAE,CAAC;QACvC,MAAM,IAAI,YAAY,CACpB,6BAA6B,EAC7B,wBAAwB,QAAQ,iBAAiB,sBAAsB,mBAAmB,CAC3F,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAY;IACnC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,KAAK,GAAa,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,EAAY,CAAC;QACtC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAClF,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;gBAAE,SAAS;YACrE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1B,OAAO,IAAI,CAAC,CAAC;gBACb,IAAI,OAAO,GAAG,wBAAwB;oBAAE,OAAO;gBAC/C,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,eAAe,CAAC,MAAc;IACrC,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,qBAAqB,CAAC,OAAe;IAC5C,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAW,CAAC;QACtC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC/B,GAAG,IAAI,IAAI,CAAC;gBACZ,KAAK,IAAI,CAAC,CAAC;YACb,CAAC;iBAAM,CAAC;gBACN,GAAG,IAAI,OAAO,CAAC;gBACf,KAAK,IAAI,CAAC,CAAC;YACb,CAAC;QACH,CAAC;aAAM,CAAC;YACN,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;YACjD,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IACD,OAAO,IAAI,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,QAA6B,EAC7B,OAAe,EACf,IAAa;IAEb,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpF,MAAM,OAAO,GAAG,qBAAqB,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC;IACzD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;gBAAE,SAAS;YACnD,IAAI,CAAC;gBACH,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,wBAAwB,CACrC,QAA6B,EAC7B,KAAa,EACb,IAAa;IAEb,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,YAAY,CACpB,6BAA6B,EAC7B,0CAA0C,CAC3C,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpF,MAAM,IAAI,GAAwD,EAAE,CAAC;IACrE,KAAK,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC;gBACH,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,IAAI,GAAG,sBAAsB;gBAAE,SAAS;YACpE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAC/D,IAAI,OAAO,KAAK,IAAI;gBAAE,SAAS;YAC/B,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBAC7C,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;oBACtB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YACzE,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAuB;IACnD,KAAK,MAAM,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,IAAI,YAAY,CACpB,8BAA8B,EAC9B,wCAAwC,CACzC,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACvF,MAAM,IAAI,YAAY,CACpB,8BAA8B,EAC9B,+CAA+C,CAChD,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,QAA6B,EAC7B,UAA6C,EAC7C,OAA2C;IAE3C,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc;QAC5C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;QACvC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,YAAY,CACpB,8BAA8B,EAC9B,8CAA8C,CAC/C,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;IACjC,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC;IACpC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,0DAA0D;QAC1D,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;IAClE,CAAC;IACD,4EAA4E;IAC5E,iEAAiE;IACjE,OAAO,kBAAkB,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAA6B,EAC7B,OAA2B;IAE3B,MAAM,IAAI,GAAG,MAAM,CAAE,OAA8B,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;QAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAClD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,WAAW,GAAG,MAAM,CAAE,OAA8B,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YACvE,OAAO;gBACL,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,MAAM,eAAe,CAAC,QAAQ,EAAE,WAAW,CAAC;aACtD,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,OAAO,GAAI,OAAgC,CAAC,OAAO,IAAI,MAAM,CAAC;YACpE,MAAM,IAAI,GAAI,OAA6B,CAAC,IAAI,CAAC;YACjD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;QACnF,CAAC;QACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;YACtB,MAAM,KAAK,GAAG,MAAM,CAAE,OAA+B,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;YACnE,MAAM,IAAI,GAAI,OAA6B,CAAC,IAAI,CAAC;YACjD,OAAO;gBACL,IAAI,EAAE,gBAAgB;gBACtB,OAAO,EAAE,MAAM,wBAAwB,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC;aAC/D,CAAC;QACJ,CAAC;QACD,KAAK,SAAS,CAAC;QACf,KAAK,UAAU,CAAC;QAChB,KAAK,UAAU,CAAC;QAChB,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAsC,CAAC;YAClF,MAAM,KAAK,GAAG,OAA6C,CAAC;YAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;QAC5E,CAAC;QACD;YACE,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA6B;IAK7B,MAAM,OAAO,GAAG,EAGf,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,uBAAuB,EAAE,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAC1B,iBAAiB,CAAC,QAAQ,EAAE,EAAE,GAAI,OAAkB,EAAE,IAAI,EAAwB,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8EAA8E;AAC9E,mEAAmE;AACnE,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAe,KAI5D;IACC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -51,5 +51,11 @@ export declare const OPERATING_PROJECTION_PATHS: {
|
|
|
51
51
|
readonly decisions: ".planr/operate/projections/decisions.md";
|
|
52
52
|
readonly dataGaps: ".planr/operate/projections/data-gaps.md";
|
|
53
53
|
readonly backlog: ".planr/operate/projections/backlog.md";
|
|
54
|
+
readonly evidenceIndex: ".planr/operate/evidence-index.json";
|
|
55
|
+
readonly brief: ".planr/operate/brief.md";
|
|
56
|
+
readonly findings: ".planr/operate/findings.md";
|
|
57
|
+
readonly readableDecisions: ".planr/operate/decisions.md";
|
|
58
|
+
readonly gaps: ".planr/operate/gaps.md";
|
|
59
|
+
readonly routes: ".planr/operate/routes.md";
|
|
54
60
|
};
|
|
55
61
|
//# sourceMappingURL=projection-persistence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projection-persistence.d.ts","sourceRoot":"","sources":["../../../src/services/operate/projection-persistence.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"projection-persistence.d.ts","sourceRoot":"","sources":["../../../src/services/operate/projection-persistence.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAgB,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AA8DxF,MAAM,MAAM,8BAA8B,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAE5E,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,8BAA8B,CAAC;IACvC,cAAc,EAAE,UAAU,MAAM,EAAE,CAAC;IACnC,YAAY,EAAE,UAAU,MAAM,EAAE,GAAG,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,UAAU,MAAM,EAAE,CAAC;IAChC,SAAS,EAAE,kBAAkB,CAAC;IAC9B,aAAa,EAAE,UAAU,MAAM,EAAE,CAAC;IAClC,KAAK,EAAE,uBAAuB,EAAE,CAAC;IACjC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,wBAAwB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,gCAAiC,SAAQ,0BAA0B;IAClF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAgTD,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,cAAc,GAAG,uBAAuB,EAAE,CAgB/F;AAkED,wBAAsB,+BAA+B,CAAC,KAAK,EAAE;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,cAAc,CAAC;CACvB,GAAG,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAItC;AAED,wBAAsB,iCAAiC,CAAC,KAAK,EAAE;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,cAAc,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB;AAED,wBAAsB,qCAAqC,CAAC,KAAK,EAAE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,cAAc,CAAC;CACvB,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAsDtC;AAED,wBAAsB,2BAA2B,CAAC,KAAK,EAAE;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACzD,GAAG,OAAO,CAAC,gCAAgC,CAAC,CA6B5C;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;;;CAa7B,CAAC"}
|
|
@@ -2,12 +2,14 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { canonicalDigest, canonicalize, sha256Digest } from './canonical.js';
|
|
4
4
|
import { applyJournalTransaction, prepareJournalTransaction, } from './journal.js';
|
|
5
|
-
import { renderOperatingBrief, selectCycleState } from './projection.js';
|
|
5
|
+
import { OPERATING_BOARD_ROLES, renderOperatingBoardReport, renderOperatingBrief, renderOperatingEvidenceIndex, selectCycleState, } from './projection.js';
|
|
6
6
|
import { assertOperatingArtifact } from './protocol.js';
|
|
7
7
|
import { operatingStalledItems } from './stalled-item-service.js';
|
|
8
8
|
import { OperateError } from './types.js';
|
|
9
|
-
const
|
|
9
|
+
const OPERATE_ROOT = '.planr/operate';
|
|
10
|
+
const PROJECTION_ROOT = `${OPERATE_ROOT}/projections`;
|
|
10
11
|
const STATE_PATH = `${PROJECTION_ROOT}/state.json`;
|
|
12
|
+
const EVIDENCE_INDEX_PATH = `${OPERATE_ROOT}/evidence-index.json`;
|
|
11
13
|
const MARKDOWN_PROJECTIONS = [
|
|
12
14
|
{
|
|
13
15
|
relativePath: `${PROJECTION_ROOT}/register.md`,
|
|
@@ -30,7 +32,39 @@ const MARKDOWN_PROJECTIONS = [
|
|
|
30
32
|
render: renderBacklogRegister,
|
|
31
33
|
},
|
|
32
34
|
];
|
|
35
|
+
// FR5 readable tree: one consolidated Markdown file per register rendered at the
|
|
36
|
+
// top level (the workspace path getters T-001 added), above the `.state/`
|
|
37
|
+
// internals. `findings.md`/`decisions.md`/`gaps.md` reuse the register renderers
|
|
38
|
+
// so the readable tree and the projections directory stay byte-consistent.
|
|
39
|
+
const READABLE_TREE_PROJECTIONS = [
|
|
40
|
+
{
|
|
41
|
+
relativePath: `${OPERATE_ROOT}/brief.md`,
|
|
42
|
+
markerName: 'operate-brief',
|
|
43
|
+
render: renderOperatingBrief,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
relativePath: `${OPERATE_ROOT}/findings.md`,
|
|
47
|
+
markerName: 'operate-findings-register',
|
|
48
|
+
render: renderFindingRegister,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
relativePath: `${OPERATE_ROOT}/decisions.md`,
|
|
52
|
+
markerName: 'operate-decisions-register',
|
|
53
|
+
render: renderDecisionRegister,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
relativePath: `${OPERATE_ROOT}/gaps.md`,
|
|
57
|
+
markerName: 'operate-data-gaps-register',
|
|
58
|
+
render: renderDataGapRegister,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
relativePath: `${OPERATE_ROOT}/routes.md`,
|
|
62
|
+
markerName: 'operate-routes-register',
|
|
63
|
+
render: renderRouteRegister,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
33
66
|
const CYCLE_BRIEF_MARKER = 'operate-cycle-brief';
|
|
67
|
+
const BOARD_MARKER = 'operate-board';
|
|
34
68
|
function value(record, key, fallback = '') {
|
|
35
69
|
const candidate = record[key];
|
|
36
70
|
if (typeof candidate === 'string' && candidate.trim()) {
|
|
@@ -137,6 +171,30 @@ function renderBacklogRegister(state) {
|
|
|
137
171
|
: ['| — | — | — | 0 | No parked findings. | — | — |']),
|
|
138
172
|
].join('\n');
|
|
139
173
|
}
|
|
174
|
+
function renderRouteRegister(state) {
|
|
175
|
+
const routes = [...state.routes].sort((left, right) => left.id.localeCompare(right.id));
|
|
176
|
+
return [
|
|
177
|
+
...generatedHeader(state, 'Operating Routes'),
|
|
178
|
+
'| Route | Cycle | State | Findings | Actions | Route digest |',
|
|
179
|
+
'|---|---|---|---|---:|---|',
|
|
180
|
+
...(routes.length > 0
|
|
181
|
+
? routes.map((route) => {
|
|
182
|
+
const actions = Array.isArray(route.actions)
|
|
183
|
+
? route.actions
|
|
184
|
+
: [];
|
|
185
|
+
const findingIds = Array.isArray(route.findingIds)
|
|
186
|
+
? route.findingIds.filter((entry) => typeof entry === 'string')
|
|
187
|
+
: [
|
|
188
|
+
...new Set(actions
|
|
189
|
+
.map((action) => action.findingId)
|
|
190
|
+
.filter((entry) => typeof entry === 'string')),
|
|
191
|
+
];
|
|
192
|
+
const actionCount = typeof route.actionCount === 'number' ? route.actionCount : actions.length;
|
|
193
|
+
return `| ${markdownCell(route.id)} | ${markdownCell(route.cycleId)} | ${markdownCell(route.state)} | ${markdownCell(findingIds.sort().join(', '))} | ${markdownCell(actionCount, '0')} | ${markdownCell(route.routeDigest)} |`;
|
|
194
|
+
})
|
|
195
|
+
: ['| — | — | — | — | 0 | No routes recorded. |']),
|
|
196
|
+
].join('\n');
|
|
197
|
+
}
|
|
140
198
|
function renderCycleBrief(state, cycleId) {
|
|
141
199
|
const selected = selectCycleState(state, cycleId);
|
|
142
200
|
const brief = renderOperatingBrief(selected);
|
|
@@ -201,33 +259,44 @@ function spliceProjectionBlock(existing, markerName, managedContent) {
|
|
|
201
259
|
return `${existing.slice(0, beginStart)}${block}${existing.slice(afterStart)}`;
|
|
202
260
|
}
|
|
203
261
|
function markdownProjectionSpecs(state) {
|
|
204
|
-
const registers = MARKDOWN_PROJECTIONS.map((projection) => ({
|
|
262
|
+
const registers = [...MARKDOWN_PROJECTIONS, ...READABLE_TREE_PROJECTIONS].map((projection) => ({
|
|
205
263
|
relativePath: projection.relativePath,
|
|
206
264
|
markerName: projection.markerName,
|
|
207
265
|
managedContent: projection.render(state),
|
|
208
266
|
}));
|
|
209
|
-
const
|
|
267
|
+
const cycleArtifacts = [...state.cycles]
|
|
210
268
|
.filter((cycle) => cycle.state === 'reviewable' || cycle.state === 'closed')
|
|
211
269
|
.sort((left, right) => left.id.localeCompare(right.id))
|
|
212
|
-
.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
270
|
+
.flatMap((cycle) => [
|
|
271
|
+
{
|
|
272
|
+
relativePath: `${OPERATE_ROOT}/cycles/${cycle.id}/brief.md`,
|
|
273
|
+
markerName: CYCLE_BRIEF_MARKER,
|
|
274
|
+
managedContent: renderCycleBrief(state, cycle.id),
|
|
275
|
+
},
|
|
276
|
+
...OPERATING_BOARD_ROLES.map((role) => ({
|
|
277
|
+
relativePath: `${OPERATE_ROOT}/cycles/${cycle.id}/board/${role.id}.md`,
|
|
278
|
+
markerName: BOARD_MARKER,
|
|
279
|
+
managedContent: renderOperatingBoardReport(state, cycle.id, role),
|
|
280
|
+
})),
|
|
281
|
+
]);
|
|
282
|
+
return [...registers, ...cycleArtifacts];
|
|
218
283
|
}
|
|
219
284
|
export function renderOperatingProjectionFiles(state) {
|
|
220
285
|
const stateFile = {
|
|
221
286
|
relativePath: STATE_PATH,
|
|
222
287
|
content: `${canonicalize(state)}\n`,
|
|
223
288
|
};
|
|
289
|
+
const evidenceIndexFile = {
|
|
290
|
+
relativePath: EVIDENCE_INDEX_PATH,
|
|
291
|
+
content: renderOperatingEvidenceIndex(state),
|
|
292
|
+
};
|
|
224
293
|
const markdownFiles = markdownProjectionSpecs(state).map((projection) => ({
|
|
225
294
|
relativePath: projection.relativePath,
|
|
226
295
|
markerName: projection.markerName,
|
|
227
296
|
managedContent: projection.managedContent,
|
|
228
297
|
content: spliceProjectionBlock('', projection.markerName, projection.managedContent),
|
|
229
298
|
}));
|
|
230
|
-
return [stateFile, ...markdownFiles];
|
|
299
|
+
return [stateFile, evidenceIndexFile, ...markdownFiles];
|
|
231
300
|
}
|
|
232
301
|
async function inspectProjectionFile(projectRoot, expected) {
|
|
233
302
|
const expectedBytes = expected.markerName ? (expected.managedContent ?? '') : expected.content;
|
|
@@ -389,5 +458,12 @@ export const OPERATING_PROJECTION_PATHS = {
|
|
|
389
458
|
decisions: `${PROJECTION_ROOT}/decisions.md`,
|
|
390
459
|
dataGaps: `${PROJECTION_ROOT}/data-gaps.md`,
|
|
391
460
|
backlog: `${PROJECTION_ROOT}/backlog.md`,
|
|
461
|
+
// FR5 readable tree at the workspace path getters (T-001).
|
|
462
|
+
evidenceIndex: EVIDENCE_INDEX_PATH,
|
|
463
|
+
brief: `${OPERATE_ROOT}/brief.md`,
|
|
464
|
+
findings: `${OPERATE_ROOT}/findings.md`,
|
|
465
|
+
readableDecisions: `${OPERATE_ROOT}/decisions.md`,
|
|
466
|
+
gaps: `${OPERATE_ROOT}/gaps.md`,
|
|
467
|
+
routes: `${OPERATE_ROOT}/routes.md`,
|
|
392
468
|
};
|
|
393
469
|
//# sourceMappingURL=projection-persistence.js.map
|