sentinelayer-cli 0.25.0 → 0.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agents/jules/tools/investor-dd.js +279 -0
- package/src/audit/agents/architecture.js +4 -1
- package/src/audit/agents/documentation.js +4 -4
- package/src/audit/agents/path-classification.js +120 -0
- package/src/audit/agents/performance.js +4 -1
- package/src/audit/agents/testing.js +5 -7
- package/src/commands/action-options.js +27 -0
- package/src/commands/audit.js +84 -71
- package/src/commands/review.js +47 -40
- package/src/legacy-cli.js +15 -0
- package/src/review/investor-dd-orchestrator.js +7 -16
- package/src/review/investor-dd-persona-runner.js +91 -27
- package/src/review/investor-dd-progress.js +13 -27
- package/src/review/scan-modes.js +2 -2
|
@@ -41,21 +41,9 @@ import { renderInvestorDdHtml } from "./investor-dd-html-report.js";
|
|
|
41
41
|
import { runDevTestBotPhase } from "./investor-dd-devtestbot.js";
|
|
42
42
|
import { redactDdEmailError } from "./dd-report-email-client.js";
|
|
43
43
|
import { buildInvestorDdProgress } from "./investor-dd-progress.js";
|
|
44
|
+
import { FULL_DEPTH_PERSONAS } from "./scan-modes.js";
|
|
44
45
|
|
|
45
|
-
const INVESTOR_DD_PERSONAS = Object.freeze([
|
|
46
|
-
"security",
|
|
47
|
-
"backend",
|
|
48
|
-
"code-quality",
|
|
49
|
-
"testing",
|
|
50
|
-
"data-layer",
|
|
51
|
-
"reliability",
|
|
52
|
-
"release",
|
|
53
|
-
"observability",
|
|
54
|
-
"infrastructure",
|
|
55
|
-
"supply-chain",
|
|
56
|
-
"documentation",
|
|
57
|
-
"ai-governance",
|
|
58
|
-
]);
|
|
46
|
+
const INVESTOR_DD_PERSONAS = Object.freeze([...FULL_DEPTH_PERSONAS]);
|
|
59
47
|
|
|
60
48
|
/**
|
|
61
49
|
* Walk the target repo and return a list of relative POSIX file paths.
|
|
@@ -148,7 +136,10 @@ function buildSummaryMarkdown({ runId, summary, routing, byPersona }) {
|
|
|
148
136
|
lines.push("");
|
|
149
137
|
lines.push("| Persona | Files routed | Files visited | Findings |");
|
|
150
138
|
lines.push("|---|---:|---:|---:|");
|
|
151
|
-
|
|
139
|
+
const reportPersonas = Array.isArray(summary.personas) && summary.personas.length > 0
|
|
140
|
+
? summary.personas
|
|
141
|
+
: INVESTOR_DD_PERSONAS;
|
|
142
|
+
for (const personaId of reportPersonas) {
|
|
152
143
|
const record = byPersona[personaId] || {};
|
|
153
144
|
const routed = (routing[personaId] || []).length;
|
|
154
145
|
const visited = Array.isArray(record.visited) ? record.visited.length : 0;
|
|
@@ -264,7 +255,7 @@ async function triggerReportEmail({ reportEmail, runResult, dryRun, emit }) {
|
|
|
264
255
|
* @param {string} params.rootPath
|
|
265
256
|
* @param {string} [params.outputDir] - Defaults to `<rootPath>/.sentinelayer/runs/<runId>`.
|
|
266
257
|
* @param {object} [params.budgetOptions] - Overrides from CLI: { maxUsd, maxRuntimeMinutes, maxParallel }.
|
|
267
|
-
* @param {string[]} [params.personas] - Override persona list; defaults to all
|
|
258
|
+
* @param {string[]} [params.personas] - Override persona list; defaults to all 13.
|
|
268
259
|
* @param {Function} [params.onEvent] - Extra event sink (NDJSON stream is always written).
|
|
269
260
|
* @param {boolean} [params.dryRun] - If true, skip tool execution, emit plan.json + stub report only.
|
|
270
261
|
* @param {string[]|null} [params.compliancePacks] - Compliance pack IDs to run (default: all seven).
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified per-persona runner for investor-DD (#investor-dd-4..15).
|
|
3
3
|
*
|
|
4
|
-
* Wires the
|
|
4
|
+
* Wires the 13 domain personas (security, backend, code-quality, testing,
|
|
5
5
|
* data-layer, reliability, release, observability, infrastructure,
|
|
6
|
-
* supply-chain, documentation, ai-governance) into the
|
|
6
|
+
* supply-chain, frontend, documentation, ai-governance) into the review
|
|
7
7
|
* loop. Each persona dispatches its declared domain tools against every
|
|
8
8
|
* file the router assigns to it, collects findings, and returns a
|
|
9
9
|
* per-persona coverage proof.
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
* (LLM-driven) layer sits on top in a later PR. Determinism is the
|
|
13
13
|
* investor-DD ground-truth floor; the LLM layer is additive.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* via a dedicated live-validator dispatcher.
|
|
15
|
+
* Repo-scoped tools such as Jules' frontend analyzer run once per persona
|
|
16
|
+
* with the full routed file set; file-scoped tools retain the per-file loop.
|
|
18
17
|
*/
|
|
19
18
|
|
|
20
19
|
import { AI_GOVERNANCE_TOOLS } from "../agents/ai-governance/tools/index.js";
|
|
@@ -29,11 +28,12 @@ import { RELIABILITY_TOOLS } from "../agents/reliability/tools/index.js";
|
|
|
29
28
|
import { SECURITY_TOOLS } from "../agents/security/tools/index.js";
|
|
30
29
|
import { SUPPLY_CHAIN_TOOLS } from "../agents/supply-chain/tools/index.js";
|
|
31
30
|
import { TESTING_TOOLS } from "../agents/testing/tools/index.js";
|
|
31
|
+
import { FRONTEND_TOOLS } from "../agents/jules/tools/investor-dd.js";
|
|
32
32
|
|
|
33
33
|
import { checkBudget, createBudgetState } from "./investor-dd-file-loop.js";
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Registry of persona → tool map.
|
|
36
|
+
* Registry of persona → tool map.
|
|
37
37
|
*/
|
|
38
38
|
export const INVESTOR_DD_PERSONA_TOOL_REGISTRY = Object.freeze({
|
|
39
39
|
security: SECURITY_TOOLS,
|
|
@@ -46,6 +46,7 @@ export const INVESTOR_DD_PERSONA_TOOL_REGISTRY = Object.freeze({
|
|
|
46
46
|
observability: OBSERVABILITY_TOOLS,
|
|
47
47
|
infrastructure: INFRASTRUCTURE_TOOLS,
|
|
48
48
|
"supply-chain": SUPPLY_CHAIN_TOOLS,
|
|
49
|
+
frontend: FRONTEND_TOOLS,
|
|
49
50
|
documentation: DOCUMENTATION_TOOLS,
|
|
50
51
|
"ai-governance": AI_GOVERNANCE_TOOLS,
|
|
51
52
|
});
|
|
@@ -66,31 +67,15 @@ export function getPersonaTools(personaId) {
|
|
|
66
67
|
return Object.values(map);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
* Dispatch every tool for the persona against a single file scope. Each
|
|
71
|
-
* tool handler is invoked with `{ rootPath, files: [file] }` because
|
|
72
|
-
* every persona's tool contract accepts this shape (see #A13-#A22).
|
|
73
|
-
*
|
|
74
|
-
* @param {object} params
|
|
75
|
-
* @param {string} params.personaId
|
|
76
|
-
* @param {string} params.file - Single file (relative to rootPath).
|
|
77
|
-
* @param {string} params.rootPath - Repo root.
|
|
78
|
-
* @param {object} params.budget - Shared budget state.
|
|
79
|
-
* @param {Function} [params.onEvent]
|
|
80
|
-
* @returns {Promise<{findings: Array, toolInvocations: Array, stoppedEarly: boolean}>}
|
|
81
|
-
*/
|
|
82
|
-
export async function runPersonaOnFile({
|
|
70
|
+
async function runTools({
|
|
83
71
|
personaId,
|
|
84
72
|
file,
|
|
73
|
+
files = null,
|
|
85
74
|
rootPath,
|
|
86
75
|
budget,
|
|
76
|
+
tools,
|
|
87
77
|
onEvent = () => {},
|
|
88
78
|
} = {}) {
|
|
89
|
-
if (!personaId) throw new TypeError("runPersonaOnFile requires personaId");
|
|
90
|
-
if (!file) throw new TypeError("runPersonaOnFile requires file");
|
|
91
|
-
if (!rootPath) throw new TypeError("runPersonaOnFile requires rootPath");
|
|
92
|
-
|
|
93
|
-
const tools = getPersonaTools(personaId);
|
|
94
79
|
const findings = [];
|
|
95
80
|
const toolInvocations = [];
|
|
96
81
|
let stoppedEarly = false;
|
|
@@ -112,7 +97,7 @@ export async function runPersonaOnFile({
|
|
|
112
97
|
onEvent({ type: "persona_file_tool_call", personaId, file, tool: tool.id });
|
|
113
98
|
|
|
114
99
|
try {
|
|
115
|
-
const results = await tool.handler({ rootPath, files: [file] });
|
|
100
|
+
const results = await tool.handler({ rootPath, files: Array.isArray(files) ? files : [file] });
|
|
116
101
|
if (budget) budget.toolCalls = (budget.toolCalls || 0) + 1;
|
|
117
102
|
const normalized = Array.isArray(results) ? results : [];
|
|
118
103
|
for (const f of normalized) {
|
|
@@ -142,6 +127,40 @@ export async function runPersonaOnFile({
|
|
|
142
127
|
return { findings, toolInvocations, stoppedEarly };
|
|
143
128
|
}
|
|
144
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Dispatch every tool for the persona against a single file scope. Each
|
|
132
|
+
* tool handler is invoked with `{ rootPath, files: [file] }` because
|
|
133
|
+
* every persona's tool contract accepts this shape (see #A13-#A22).
|
|
134
|
+
*
|
|
135
|
+
* @param {object} params
|
|
136
|
+
* @param {string} params.personaId
|
|
137
|
+
* @param {string} params.file - Single file (relative to rootPath).
|
|
138
|
+
* @param {string} params.rootPath - Repo root.
|
|
139
|
+
* @param {object} params.budget - Shared budget state.
|
|
140
|
+
* @param {Function} [params.onEvent]
|
|
141
|
+
* @returns {Promise<{findings: Array, toolInvocations: Array, stoppedEarly: boolean}>}
|
|
142
|
+
*/
|
|
143
|
+
export async function runPersonaOnFile({
|
|
144
|
+
personaId,
|
|
145
|
+
file,
|
|
146
|
+
rootPath,
|
|
147
|
+
budget,
|
|
148
|
+
onEvent = () => {},
|
|
149
|
+
} = {}) {
|
|
150
|
+
if (!personaId) throw new TypeError("runPersonaOnFile requires personaId");
|
|
151
|
+
if (!file) throw new TypeError("runPersonaOnFile requires file");
|
|
152
|
+
if (!rootPath) throw new TypeError("runPersonaOnFile requires rootPath");
|
|
153
|
+
|
|
154
|
+
return runTools({
|
|
155
|
+
personaId,
|
|
156
|
+
file,
|
|
157
|
+
rootPath,
|
|
158
|
+
budget,
|
|
159
|
+
tools: getPersonaTools(personaId),
|
|
160
|
+
onEvent,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
145
164
|
/**
|
|
146
165
|
* Run a single persona across its assigned files in the router output.
|
|
147
166
|
*
|
|
@@ -167,6 +186,50 @@ export async function runPersonaAcrossFiles({
|
|
|
167
186
|
const visited = [];
|
|
168
187
|
const skipped = [];
|
|
169
188
|
let terminationReason = "ok";
|
|
189
|
+
const tools = getPersonaTools(personaId);
|
|
190
|
+
const repoScopedTools = tools.filter((tool) => tool.scope === "repo");
|
|
191
|
+
const fileScopedTools = tools.filter((tool) => tool.scope !== "repo");
|
|
192
|
+
|
|
193
|
+
if (repoScopedTools.length > 0) {
|
|
194
|
+
const budgetCheck = checkBudget(safeBudget);
|
|
195
|
+
if (!budgetCheck.ok) {
|
|
196
|
+
terminationReason = budgetCheck.reason;
|
|
197
|
+
skipped.push(...files);
|
|
198
|
+
onEvent({ type: "persona_file_skipped", personaId, file: "__repo__", stopReason: budgetCheck.reason });
|
|
199
|
+
} else {
|
|
200
|
+
onEvent({ type: "persona_file_start", personaId, file: "__repo__", fileCount: files.length });
|
|
201
|
+
const { findings, toolInvocations, stoppedEarly } = await runTools({
|
|
202
|
+
personaId,
|
|
203
|
+
file: "__repo__",
|
|
204
|
+
files,
|
|
205
|
+
rootPath,
|
|
206
|
+
budget: safeBudget,
|
|
207
|
+
tools: repoScopedTools,
|
|
208
|
+
onEvent,
|
|
209
|
+
});
|
|
210
|
+
perFile.push({ file: "__repo__", scope: "repo", files, findings, toolInvocations, stoppedEarly });
|
|
211
|
+
allFindings.push(...findings);
|
|
212
|
+
visited.push(...files);
|
|
213
|
+
onEvent({
|
|
214
|
+
type: "persona_file_complete",
|
|
215
|
+
personaId,
|
|
216
|
+
file: "__repo__",
|
|
217
|
+
findingCount: findings.length,
|
|
218
|
+
toolCount: toolInvocations.length,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (fileScopedTools.length === 0) {
|
|
224
|
+
return {
|
|
225
|
+
personaId,
|
|
226
|
+
perFile,
|
|
227
|
+
findings: allFindings,
|
|
228
|
+
visited,
|
|
229
|
+
skipped,
|
|
230
|
+
terminationReason,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
170
233
|
|
|
171
234
|
for (const file of files) {
|
|
172
235
|
const budgetCheck = checkBudget(safeBudget);
|
|
@@ -178,11 +241,12 @@ export async function runPersonaAcrossFiles({
|
|
|
178
241
|
}
|
|
179
242
|
|
|
180
243
|
onEvent({ type: "persona_file_start", personaId, file });
|
|
181
|
-
const { findings, toolInvocations, stoppedEarly } = await
|
|
244
|
+
const { findings, toolInvocations, stoppedEarly } = await runTools({
|
|
182
245
|
personaId,
|
|
183
246
|
file,
|
|
184
247
|
rootPath,
|
|
185
248
|
budget: safeBudget,
|
|
249
|
+
tools: fileScopedTools,
|
|
186
250
|
onEvent,
|
|
187
251
|
});
|
|
188
252
|
perFile.push({ file, findings, toolInvocations, stoppedEarly });
|
|
@@ -1,20 +1,8 @@
|
|
|
1
|
+
import { FULL_DEPTH_PERSONAS } from "./scan-modes.js";
|
|
2
|
+
|
|
1
3
|
export const INVESTOR_DD_PROGRESS_VERSION = "investor_dd_progress_v1";
|
|
2
4
|
|
|
3
|
-
export const INVESTOR_DD_EXPECTED_PERSONAS = Object.freeze([
|
|
4
|
-
"security",
|
|
5
|
-
"backend",
|
|
6
|
-
"code-quality",
|
|
7
|
-
"testing",
|
|
8
|
-
"data-layer",
|
|
9
|
-
"reliability",
|
|
10
|
-
"release",
|
|
11
|
-
"observability",
|
|
12
|
-
"infrastructure",
|
|
13
|
-
"supply-chain",
|
|
14
|
-
"documentation",
|
|
15
|
-
"ai-governance",
|
|
16
|
-
"frontend",
|
|
17
|
-
]);
|
|
5
|
+
export const INVESTOR_DD_EXPECTED_PERSONAS = Object.freeze([...FULL_DEPTH_PERSONAS]);
|
|
18
6
|
|
|
19
7
|
const REQUIRED_FOR_SELLABLE = Object.freeze([
|
|
20
8
|
"persona_roster",
|
|
@@ -335,17 +323,15 @@ export function buildInvestorDdProgress({
|
|
|
335
323
|
),
|
|
336
324
|
},
|
|
337
325
|
findingCount: Array.isArray(findings) ? findings.length : 0,
|
|
338
|
-
nextRecommendedSlices:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
"
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
"require live reconciliation and report-email proof for sellable DD closeout",
|
|
349
|
-
],
|
|
326
|
+
nextRecommendedSlices: [
|
|
327
|
+
...(byId(capabilities, "persona_roster")?.status === "complete"
|
|
328
|
+
? []
|
|
329
|
+
: missingPersonas.includes("frontend")
|
|
330
|
+
? ["add the missing frontend/Jules persona to the default Investor-DD roster"]
|
|
331
|
+
: ["run the full 13-persona roster before claiming sellable DD coverage"]),
|
|
332
|
+
"wire Senti session id and live usage counters into Investor-DD runs",
|
|
333
|
+
"add per-agent token/time/LOC/customer-price/margin telemetry",
|
|
334
|
+
"require live reconciliation and report-email proof for sellable DD closeout",
|
|
335
|
+
],
|
|
350
336
|
};
|
|
351
337
|
}
|
package/src/review/scan-modes.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// frontend / observability / reliability.
|
|
35
35
|
// - "compliance" — cross-cutting; distributed across security / ai-governance /
|
|
36
36
|
// supply-chain / infrastructure / documentation via subagent dispatch.
|
|
37
|
-
const FULL_DEPTH_PERSONAS = [
|
|
37
|
+
export const FULL_DEPTH_PERSONAS = Object.freeze([
|
|
38
38
|
"security",
|
|
39
39
|
"backend",
|
|
40
40
|
"code-quality",
|
|
@@ -48,7 +48,7 @@ const FULL_DEPTH_PERSONAS = [
|
|
|
48
48
|
"frontend",
|
|
49
49
|
"documentation",
|
|
50
50
|
"ai-governance",
|
|
51
|
-
];
|
|
51
|
+
]);
|
|
52
52
|
|
|
53
53
|
const SCAN_MODES = {
|
|
54
54
|
baseline: ["security"],
|