sentinelayer-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +996 -0
- package/bin/create-sentinelayer.js +5 -0
- package/bin/sentinelayer-cli.js +5 -0
- package/bin/sl.js +5 -0
- package/package.json +54 -0
- package/src/agents/jules/config/definition.js +209 -0
- package/src/agents/jules/config/system-prompt.js +175 -0
- package/src/agents/jules/error-intake.js +51 -0
- package/src/agents/jules/fix-cycle.js +377 -0
- package/src/agents/jules/loop.js +367 -0
- package/src/agents/jules/pulse.js +319 -0
- package/src/agents/jules/stream.js +186 -0
- package/src/agents/jules/swarm/file-scanner.js +74 -0
- package/src/agents/jules/swarm/index.js +11 -0
- package/src/agents/jules/swarm/orchestrator.js +362 -0
- package/src/agents/jules/swarm/pattern-hunter.js +123 -0
- package/src/agents/jules/swarm/sub-agent.js +308 -0
- package/src/agents/jules/tools/auth-audit.js +222 -0
- package/src/agents/jules/tools/dispatch.js +327 -0
- package/src/agents/jules/tools/file-edit.js +180 -0
- package/src/agents/jules/tools/file-read.js +100 -0
- package/src/agents/jules/tools/frontend-analyze.js +570 -0
- package/src/agents/jules/tools/glob.js +168 -0
- package/src/agents/jules/tools/grep.js +228 -0
- package/src/agents/jules/tools/index.js +29 -0
- package/src/agents/jules/tools/path-guards.js +161 -0
- package/src/agents/jules/tools/runtime-audit.js +409 -0
- package/src/agents/jules/tools/shell.js +383 -0
- package/src/ai/aidenid.js +945 -0
- package/src/ai/client.js +508 -0
- package/src/ai/domain-target-store.js +268 -0
- package/src/ai/identity-store.js +270 -0
- package/src/ai/site-store.js +145 -0
- package/src/audit/agents/architecture.js +180 -0
- package/src/audit/agents/compliance.js +179 -0
- package/src/audit/agents/documentation.js +165 -0
- package/src/audit/agents/performance.js +145 -0
- package/src/audit/agents/security.js +215 -0
- package/src/audit/agents/testing.js +172 -0
- package/src/audit/orchestrator.js +557 -0
- package/src/audit/package.js +204 -0
- package/src/audit/registry.js +284 -0
- package/src/audit/replay.js +103 -0
- package/src/auth/http.js +113 -0
- package/src/auth/service.js +848 -0
- package/src/auth/session-store.js +345 -0
- package/src/cli.js +244 -0
- package/src/commands/ai/identity-lifecycle.js +1337 -0
- package/src/commands/ai/provision-governance.js +1246 -0
- package/src/commands/ai/shared.js +147 -0
- package/src/commands/ai.js +11 -0
- package/src/commands/apply.js +19 -0
- package/src/commands/audit.js +1147 -0
- package/src/commands/auth.js +366 -0
- package/src/commands/chat.js +191 -0
- package/src/commands/config.js +184 -0
- package/src/commands/cost.js +311 -0
- package/src/commands/daemon/core.js +850 -0
- package/src/commands/daemon/extended.js +1048 -0
- package/src/commands/daemon/shared.js +213 -0
- package/src/commands/daemon.js +11 -0
- package/src/commands/guide.js +174 -0
- package/src/commands/ingest.js +58 -0
- package/src/commands/init.js +55 -0
- package/src/commands/legacy-args.js +30 -0
- package/src/commands/mcp.js +404 -0
- package/src/commands/omargate.js +21 -0
- package/src/commands/persona.js +27 -0
- package/src/commands/plugin.js +260 -0
- package/src/commands/policy.js +132 -0
- package/src/commands/prompt.js +238 -0
- package/src/commands/review.js +704 -0
- package/src/commands/scan.js +788 -0
- package/src/commands/spec.js +716 -0
- package/src/commands/swarm.js +651 -0
- package/src/commands/telemetry.js +202 -0
- package/src/commands/watch.js +510 -0
- package/src/config/agent-dictionary.js +182 -0
- package/src/config/io.js +56 -0
- package/src/config/paths.js +18 -0
- package/src/config/schema.js +55 -0
- package/src/config/service.js +184 -0
- package/src/cost/budget.js +235 -0
- package/src/cost/history.js +188 -0
- package/src/cost/tracker.js +171 -0
- package/src/daemon/artifact-lineage.js +534 -0
- package/src/daemon/assignment-ledger.js +770 -0
- package/src/daemon/ast-parser-layer.js +258 -0
- package/src/daemon/budget-governor.js +633 -0
- package/src/daemon/callgraph-overlay.js +646 -0
- package/src/daemon/error-worker.js +626 -0
- package/src/daemon/hybrid-mapper.js +929 -0
- package/src/daemon/jira-lifecycle.js +632 -0
- package/src/daemon/operator-control.js +657 -0
- package/src/daemon/reliability-lane.js +471 -0
- package/src/daemon/watchdog.js +971 -0
- package/src/guide/generator.js +316 -0
- package/src/ingest/engine.js +918 -0
- package/src/legacy-cli.js +2435 -0
- package/src/mcp/registry.js +695 -0
- package/src/memory/blackboard.js +301 -0
- package/src/memory/retrieval.js +581 -0
- package/src/plugin/manifest.js +553 -0
- package/src/policy/packs.js +144 -0
- package/src/prompt/generator.js +106 -0
- package/src/review/ai-review.js +669 -0
- package/src/review/local-review.js +1284 -0
- package/src/review/replay.js +235 -0
- package/src/review/report.js +664 -0
- package/src/review/spec-binding.js +487 -0
- package/src/scan/generator.js +351 -0
- package/src/spec/generator.js +519 -0
- package/src/spec/regenerate.js +237 -0
- package/src/spec/templates.js +91 -0
- package/src/swarm/dashboard.js +247 -0
- package/src/swarm/factory.js +363 -0
- package/src/swarm/pentest.js +934 -0
- package/src/swarm/registry.js +419 -0
- package/src/swarm/report.js +158 -0
- package/src/swarm/runtime.js +576 -0
- package/src/swarm/scenario-dsl.js +272 -0
- package/src/telemetry/ledger.js +302 -0
- package/src/ui/markdown.js +220 -0
- package/src/ui/progress.js +100 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
function normalizeString(value) {
|
|
2
|
+
return String(value || "").trim();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function toPosixPath(value) {
|
|
6
|
+
return String(value || "").replace(/\\/g, "/");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function summarizeSeverity(findings = []) {
|
|
10
|
+
const summary = { P0: 0, P1: 0, P2: 0, P3: 0 };
|
|
11
|
+
for (const finding of findings) {
|
|
12
|
+
const severity = normalizeString(finding.severity).toUpperCase();
|
|
13
|
+
if (Object.prototype.hasOwnProperty.call(summary, severity)) {
|
|
14
|
+
summary[severity] += 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
summary.blocking = summary.P0 > 0 || summary.P1 > 0;
|
|
18
|
+
return summary;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildHotspots(ingest = {}) {
|
|
22
|
+
const indexed = Array.isArray(ingest.indexedFiles?.files) ? ingest.indexedFiles.files : [];
|
|
23
|
+
return indexed
|
|
24
|
+
.filter((file) => Number(file.loc || 0) >= 300)
|
|
25
|
+
.sort((left, right) => Number(right.loc || 0) - Number(left.loc || 0))
|
|
26
|
+
.slice(0, 20)
|
|
27
|
+
.map((file) => ({
|
|
28
|
+
path: toPosixPath(file.path),
|
|
29
|
+
language: file.language,
|
|
30
|
+
loc: Number(file.loc || 0),
|
|
31
|
+
sizeBytes: Number(file.sizeBytes || 0),
|
|
32
|
+
risk: Number(file.loc || 0) >= 700 ? "high" : "medium",
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function deriveArchitectureFindings({ findings = [], hotspots = [] } = {}) {
|
|
37
|
+
const derived = [];
|
|
38
|
+
const base = Array.isArray(findings) ? findings : [];
|
|
39
|
+
|
|
40
|
+
for (const finding of base) {
|
|
41
|
+
const message = normalizeString(finding.message).toLowerCase();
|
|
42
|
+
if (/n\+1|loop|query|component|cleanup|stale|coupling|dependency/.test(message)) {
|
|
43
|
+
derived.push(finding);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const hotspot of hotspots) {
|
|
48
|
+
const existing = derived.some((finding) => toPosixPath(finding.file) === hotspot.path);
|
|
49
|
+
if (existing) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
derived.push({
|
|
53
|
+
severity: hotspot.risk === "high" ? "P1" : "P2",
|
|
54
|
+
file: hotspot.path,
|
|
55
|
+
line: 1,
|
|
56
|
+
message: `Large architectural hotspot detected (${hotspot.loc} LOC).`,
|
|
57
|
+
excerpt: `${hotspot.language} module at ${hotspot.path}`,
|
|
58
|
+
ruleId: "SL-ARCH-001",
|
|
59
|
+
suggestedFix: "Split this module into bounded components with explicit interfaces.",
|
|
60
|
+
layer: "architecture",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return derived.slice(0, 120);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function estimateCouplingRisk(ingest = {}) {
|
|
68
|
+
const files = Array.isArray(ingest.indexedFiles?.files) ? ingest.indexedFiles.files : [];
|
|
69
|
+
const byDirectory = new Map();
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
const normalizedPath = toPosixPath(file.path);
|
|
72
|
+
const dir = normalizedPath.includes("/") ? normalizedPath.slice(0, normalizedPath.lastIndexOf("/")) : ".";
|
|
73
|
+
const existing = byDirectory.get(dir) || 0;
|
|
74
|
+
byDirectory.set(dir, existing + 1);
|
|
75
|
+
}
|
|
76
|
+
const denseDirs = [...byDirectory.entries()].filter((entry) => entry[1] >= 20);
|
|
77
|
+
return {
|
|
78
|
+
denseDirectoryCount: denseDirs.length,
|
|
79
|
+
denseDirectories: denseDirs
|
|
80
|
+
.sort((left, right) => right[1] - left[1])
|
|
81
|
+
.slice(0, 12)
|
|
82
|
+
.map((entry) => ({ directory: entry[0], fileCount: entry[1] })),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function estimateArchitectureScore(hotspots = [], summary = {}) {
|
|
87
|
+
const penalty =
|
|
88
|
+
hotspots.length * 3 +
|
|
89
|
+
Number(summary.P1 || 0) * 6 +
|
|
90
|
+
Number(summary.P2 || 0) * 3 +
|
|
91
|
+
Number(summary.P3 || 0);
|
|
92
|
+
return Math.max(0, 100 - Math.min(100, penalty));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildRecommendations({ hotspots = [], summary = {}, couplingRisk } = {}) {
|
|
96
|
+
const recommendations = [];
|
|
97
|
+
if (hotspots.length > 0) {
|
|
98
|
+
recommendations.push(
|
|
99
|
+
"Split high-LOC modules into bounded contexts and enforce explicit interface contracts."
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (Number(summary.P1 || 0) > 0) {
|
|
103
|
+
recommendations.push("Resolve all P1 architecture findings before onboarding additional feature work.");
|
|
104
|
+
}
|
|
105
|
+
if (couplingRisk.denseDirectoryCount > 0) {
|
|
106
|
+
recommendations.push(
|
|
107
|
+
"Introduce package-level ownership boundaries for dense directories to reduce cross-module coupling."
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (recommendations.length === 0) {
|
|
111
|
+
recommendations.push("Architecture signals are stable; continue tracking drift with periodic audits.");
|
|
112
|
+
}
|
|
113
|
+
return recommendations;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function runArchitectureSpecialist({
|
|
117
|
+
findings = [],
|
|
118
|
+
ingest = {},
|
|
119
|
+
} = {}) {
|
|
120
|
+
const hotspots = buildHotspots(ingest);
|
|
121
|
+
const architectureFindings = deriveArchitectureFindings({
|
|
122
|
+
findings,
|
|
123
|
+
hotspots,
|
|
124
|
+
});
|
|
125
|
+
const summary = summarizeSeverity(architectureFindings);
|
|
126
|
+
const couplingRisk = estimateCouplingRisk(ingest);
|
|
127
|
+
const architectureScore = estimateArchitectureScore(hotspots, summary);
|
|
128
|
+
const confidence = architectureFindings.length > 0 ? Math.max(0.72, 1 - architectureFindings.length * 0.002) : 0.9;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
schemaVersion: "1.0.0",
|
|
132
|
+
generatedAt: new Date().toISOString(),
|
|
133
|
+
summary: {
|
|
134
|
+
...summary,
|
|
135
|
+
findingCount: architectureFindings.length,
|
|
136
|
+
architectureScore,
|
|
137
|
+
},
|
|
138
|
+
confidence,
|
|
139
|
+
hotspots,
|
|
140
|
+
couplingRisk,
|
|
141
|
+
recommendations: buildRecommendations({
|
|
142
|
+
hotspots,
|
|
143
|
+
summary,
|
|
144
|
+
couplingRisk,
|
|
145
|
+
}),
|
|
146
|
+
findings: architectureFindings,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function renderArchitectureSpecialistMarkdown(report = {}) {
|
|
151
|
+
const hotspots = (report.hotspots || [])
|
|
152
|
+
.map((item) => `- ${item.path} (${item.loc} LOC, risk=${item.risk})`)
|
|
153
|
+
.join("\n");
|
|
154
|
+
const couplings = (report.couplingRisk?.denseDirectories || [])
|
|
155
|
+
.map((item) => `- ${item.directory}: ${item.fileCount} files`)
|
|
156
|
+
.join("\n");
|
|
157
|
+
const recommendations = (report.recommendations || []).map((item) => `- ${item}`).join("\n");
|
|
158
|
+
|
|
159
|
+
return `# ARCHITECTURE_AGENT_REPORT
|
|
160
|
+
|
|
161
|
+
Generated: ${report.generatedAt}
|
|
162
|
+
Architecture score: ${report.summary?.architectureScore ?? 0}/100
|
|
163
|
+
Confidence: ${((report.confidence || 0) * 100).toFixed(0)}%
|
|
164
|
+
|
|
165
|
+
Summary:
|
|
166
|
+
- Findings: P0=${report.summary?.P0 ?? 0} P1=${report.summary?.P1 ?? 0} P2=${report.summary?.P2 ?? 0} P3=${report.summary?.P3 ?? 0}
|
|
167
|
+
- Blocking: ${report.summary?.blocking ? "yes" : "no"}
|
|
168
|
+
- Total findings: ${report.summary?.findingCount ?? 0}
|
|
169
|
+
|
|
170
|
+
Hotspots:
|
|
171
|
+
${hotspots || "- none"}
|
|
172
|
+
|
|
173
|
+
Coupling density:
|
|
174
|
+
${couplings || "- none"}
|
|
175
|
+
|
|
176
|
+
Recommendations:
|
|
177
|
+
${recommendations || "- none"}
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
function normalizeString(value) {
|
|
2
|
+
return String(value || "").trim();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function toPosixPath(value) {
|
|
6
|
+
return String(value || "").replace(/\\/g, "/");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function summarizeSeverity(findings = []) {
|
|
10
|
+
const summary = { P0: 0, P1: 0, P2: 0, P3: 0 };
|
|
11
|
+
for (const finding of findings) {
|
|
12
|
+
const severity = normalizeString(finding.severity).toUpperCase();
|
|
13
|
+
if (Object.prototype.hasOwnProperty.call(summary, severity)) {
|
|
14
|
+
summary[severity] += 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
summary.blocking = summary.P0 > 0 || summary.P1 > 0;
|
|
18
|
+
return summary;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function classifyControl(finding = {}) {
|
|
22
|
+
const haystack = `${normalizeString(finding.message)} ${normalizeString(finding.ruleId)} ${normalizeString(
|
|
23
|
+
finding.file
|
|
24
|
+
)}`.toLowerCase();
|
|
25
|
+
if (/secret|token|credential|key|jwt/.test(haystack)) {
|
|
26
|
+
return { framework: "SOC2-CC6", control: "Secrets Management", severity: "P1" };
|
|
27
|
+
}
|
|
28
|
+
if (/auth|session|revoke|ttl|role|permission/.test(haystack)) {
|
|
29
|
+
return { framework: "SOC2-CC6", control: "Access Controls", severity: "P2" };
|
|
30
|
+
}
|
|
31
|
+
if (/telemetry|log|trace|audit/.test(haystack)) {
|
|
32
|
+
return { framework: "SOC2-CC7", control: "Auditability", severity: "P2" };
|
|
33
|
+
}
|
|
34
|
+
if (/data|pii|encryption|tls|privacy|retention/.test(haystack)) {
|
|
35
|
+
return { framework: "SOC2-CC8", control: "Data Protection", severity: "P1" };
|
|
36
|
+
}
|
|
37
|
+
if (/workflow|deploy|release|supply|dependency/.test(haystack)) {
|
|
38
|
+
return { framework: "SOC2-CC9", control: "Change Management", severity: "P2" };
|
|
39
|
+
}
|
|
40
|
+
return { framework: "SOC2-CC3", control: "Governance", severity: "P3" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function buildComplianceFindings({ findings = [], ingest = {} } = {}) {
|
|
44
|
+
const derived = [];
|
|
45
|
+
const scoped = Array.isArray(findings) ? findings : [];
|
|
46
|
+
for (const finding of scoped) {
|
|
47
|
+
const control = classifyControl(finding);
|
|
48
|
+
if (control.severity === "P3" && normalizeString(finding.severity).toUpperCase() === "P3") {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
derived.push({
|
|
52
|
+
...finding,
|
|
53
|
+
severity: normalizeString(finding.severity) ? finding.severity : control.severity,
|
|
54
|
+
complianceFramework: control.framework,
|
|
55
|
+
complianceControl: control.control,
|
|
56
|
+
layer: "compliance",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const riskSurfaces = Array.isArray(ingest.riskSurfaces) ? ingest.riskSurfaces : [];
|
|
61
|
+
for (const surface of riskSurfaces.slice(0, 20)) {
|
|
62
|
+
const normalizedSurface = normalizeString(surface.surface);
|
|
63
|
+
if (!normalizedSurface) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const representativePath = toPosixPath(surface.filePath || surface.path || ".");
|
|
67
|
+
const exists = derived.some((finding) => toPosixPath(finding.file) === representativePath);
|
|
68
|
+
if (exists) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const control =
|
|
72
|
+
normalizedSurface === "secrets"
|
|
73
|
+
? { framework: "SOC2-CC6", control: "Secrets Management", severity: "P1" }
|
|
74
|
+
: { framework: "SOC2-CC7", control: "Auditability", severity: "P2" };
|
|
75
|
+
derived.push({
|
|
76
|
+
severity: control.severity,
|
|
77
|
+
file: representativePath,
|
|
78
|
+
line: 1,
|
|
79
|
+
message: `Compliance risk surface detected for ${normalizedSurface}.`,
|
|
80
|
+
excerpt: `${normalizedSurface} surface observed in ingest`,
|
|
81
|
+
ruleId: "SL-COMP-001",
|
|
82
|
+
suggestedFix: "Document control ownership and enforce deterministic remediation evidence.",
|
|
83
|
+
complianceFramework: control.framework,
|
|
84
|
+
complianceControl: control.control,
|
|
85
|
+
layer: "compliance",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return derived.slice(0, 120);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function summarizeControls(findings = []) {
|
|
93
|
+
const controlMap = new Map();
|
|
94
|
+
for (const finding of findings) {
|
|
95
|
+
const framework = normalizeString(finding.complianceFramework || "SOC2-CC3");
|
|
96
|
+
const control = normalizeString(finding.complianceControl || "Governance");
|
|
97
|
+
const key = `${framework}::${control}`;
|
|
98
|
+
const existing = controlMap.get(key) || { framework, control, count: 0 };
|
|
99
|
+
existing.count += 1;
|
|
100
|
+
controlMap.set(key, existing);
|
|
101
|
+
}
|
|
102
|
+
return [...controlMap.values()].sort((left, right) => right.count - left.count);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function estimateComplianceScore(summary = {}, controlSummary = []) {
|
|
106
|
+
const penalty =
|
|
107
|
+
Number(summary.P1 || 0) * 8 +
|
|
108
|
+
Number(summary.P2 || 0) * 4 +
|
|
109
|
+
Number(summary.P3 || 0) +
|
|
110
|
+
controlSummary.length;
|
|
111
|
+
return Math.max(0, 100 - Math.min(100, penalty));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildRecommendations(summary = {}, controlSummary = []) {
|
|
115
|
+
const recommendations = [];
|
|
116
|
+
if (Number(summary.P1 || 0) > 0) {
|
|
117
|
+
recommendations.push("Close P1 compliance findings before release and attach evidence in run artifacts.");
|
|
118
|
+
}
|
|
119
|
+
if (controlSummary.some((item) => item.framework === "SOC2-CC6")) {
|
|
120
|
+
recommendations.push("Strengthen credential/access controls and verify rotation/revocation procedures.");
|
|
121
|
+
}
|
|
122
|
+
if (controlSummary.some((item) => item.framework === "SOC2-CC7")) {
|
|
123
|
+
recommendations.push("Improve audit telemetry coverage for remediation and production change traces.");
|
|
124
|
+
}
|
|
125
|
+
if (recommendations.length === 0) {
|
|
126
|
+
recommendations.push("Compliance posture is stable; keep continuous evidence capture in each audit run.");
|
|
127
|
+
}
|
|
128
|
+
return recommendations;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function runComplianceSpecialist({ findings = [], ingest = {} } = {}) {
|
|
132
|
+
const complianceFindings = buildComplianceFindings({
|
|
133
|
+
findings,
|
|
134
|
+
ingest,
|
|
135
|
+
});
|
|
136
|
+
const summary = summarizeSeverity(complianceFindings);
|
|
137
|
+
const controlSummary = summarizeControls(complianceFindings);
|
|
138
|
+
const complianceScore = estimateComplianceScore(summary, controlSummary);
|
|
139
|
+
const confidence = complianceFindings.length > 0 ? Math.max(0.76, 1 - complianceFindings.length * 0.002) : 0.9;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
schemaVersion: "1.0.0",
|
|
143
|
+
generatedAt: new Date().toISOString(),
|
|
144
|
+
summary: {
|
|
145
|
+
...summary,
|
|
146
|
+
findingCount: complianceFindings.length,
|
|
147
|
+
complianceScore,
|
|
148
|
+
},
|
|
149
|
+
confidence,
|
|
150
|
+
controlSummary,
|
|
151
|
+
recommendations: buildRecommendations(summary, controlSummary),
|
|
152
|
+
findings: complianceFindings,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function renderComplianceSpecialistMarkdown(report = {}) {
|
|
157
|
+
const controls = (report.controlSummary || [])
|
|
158
|
+
.map((item) => `- ${item.framework} :: ${item.control} (${item.count} findings)`)
|
|
159
|
+
.join("\n");
|
|
160
|
+
const recommendations = (report.recommendations || []).map((item) => `- ${item}`).join("\n");
|
|
161
|
+
|
|
162
|
+
return `# COMPLIANCE_AGENT_REPORT
|
|
163
|
+
|
|
164
|
+
Generated: ${report.generatedAt}
|
|
165
|
+
Compliance score: ${report.summary?.complianceScore ?? 0}/100
|
|
166
|
+
Confidence: ${((report.confidence || 0) * 100).toFixed(0)}%
|
|
167
|
+
|
|
168
|
+
Summary:
|
|
169
|
+
- Findings: P0=${report.summary?.P0 ?? 0} P1=${report.summary?.P1 ?? 0} P2=${report.summary?.P2 ?? 0} P3=${report.summary?.P3 ?? 0}
|
|
170
|
+
- Blocking: ${report.summary?.blocking ? "yes" : "no"}
|
|
171
|
+
- Total findings: ${report.summary?.findingCount ?? 0}
|
|
172
|
+
|
|
173
|
+
Control mapping:
|
|
174
|
+
${controls || "- none"}
|
|
175
|
+
|
|
176
|
+
Recommendations:
|
|
177
|
+
${recommendations || "- none"}
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
function normalizeString(value) {
|
|
2
|
+
return String(value || "").trim();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function toPosixPath(value) {
|
|
6
|
+
return String(value || "").replace(/\\/g, "/");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function summarizeSeverity(findings = []) {
|
|
10
|
+
const summary = { P0: 0, P1: 0, P2: 0, P3: 0 };
|
|
11
|
+
for (const finding of findings) {
|
|
12
|
+
const severity = normalizeString(finding.severity).toUpperCase();
|
|
13
|
+
if (Object.prototype.hasOwnProperty.call(summary, severity)) {
|
|
14
|
+
summary[severity] += 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
summary.blocking = summary.P0 > 0 || summary.P1 > 0;
|
|
18
|
+
return summary;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildDocumentationInventory(ingest = {}) {
|
|
22
|
+
const indexed = Array.isArray(ingest.indexedFiles?.files) ? ingest.indexedFiles.files : [];
|
|
23
|
+
const normalized = indexed.map((file) => ({
|
|
24
|
+
path: toPosixPath(file.path),
|
|
25
|
+
loc: Number(file.loc || 0),
|
|
26
|
+
language: file.language,
|
|
27
|
+
}));
|
|
28
|
+
const docFiles = normalized.filter((file) => /(^|\/)(docs?|guides?|adr|readme)/i.test(file.path));
|
|
29
|
+
const codeFiles = normalized.filter(
|
|
30
|
+
(file) => file.loc > 0 && !/(^|\/)(docs?|guides?|adr|readme)/i.test(file.path)
|
|
31
|
+
);
|
|
32
|
+
const docDensity = codeFiles.length > 0 ? docFiles.length / codeFiles.length : 0;
|
|
33
|
+
const undocumentedHotspots = codeFiles
|
|
34
|
+
.filter((file) => file.loc >= 320)
|
|
35
|
+
.filter((file) => {
|
|
36
|
+
const dir = file.path.includes("/") ? file.path.slice(0, file.path.lastIndexOf("/")) : ".";
|
|
37
|
+
return !docFiles.some((doc) => doc.path.startsWith(dir));
|
|
38
|
+
})
|
|
39
|
+
.sort((left, right) => right.loc - left.loc)
|
|
40
|
+
.slice(0, 20)
|
|
41
|
+
.map((file) => ({
|
|
42
|
+
path: file.path,
|
|
43
|
+
loc: file.loc,
|
|
44
|
+
language: file.language,
|
|
45
|
+
severity: file.loc >= 800 ? "P1" : "P2",
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
docFileCount: docFiles.length,
|
|
50
|
+
codeFileCount: codeFiles.length,
|
|
51
|
+
docDensity,
|
|
52
|
+
undocumentedHotspots,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildDocumentationFindings({ findings = [], inventory = {} } = {}) {
|
|
57
|
+
const scoped = Array.isArray(findings) ? findings : [];
|
|
58
|
+
const derived = [];
|
|
59
|
+
for (const finding of scoped) {
|
|
60
|
+
const haystack = `${normalizeString(finding.message)} ${normalizeString(finding.ruleId)} ${normalizeString(
|
|
61
|
+
finding.file
|
|
62
|
+
)}`.toLowerCase();
|
|
63
|
+
if (/doc|documentation|spec|guide|readme|runbook|playbook|adr/.test(haystack)) {
|
|
64
|
+
derived.push(finding);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const hotspot of inventory.undocumentedHotspots || []) {
|
|
69
|
+
const exists = derived.some((finding) => toPosixPath(finding.file) === hotspot.path);
|
|
70
|
+
if (exists) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
derived.push({
|
|
74
|
+
severity: hotspot.severity,
|
|
75
|
+
file: hotspot.path,
|
|
76
|
+
line: 1,
|
|
77
|
+
message: `Large module lacks nearby documentation coverage (${hotspot.loc} LOC).`,
|
|
78
|
+
excerpt: `${hotspot.language || "code"} module at ${hotspot.path}`,
|
|
79
|
+
ruleId: "SL-DOC-001",
|
|
80
|
+
suggestedFix: "Add a runbook/spec section describing responsibilities, dependencies, and failure modes.",
|
|
81
|
+
layer: "documentation",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return derived.slice(0, 120);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function estimateDocumentationScore(inventory = {}, summary = {}) {
|
|
89
|
+
const densityScore = Math.min(70, Math.round(Number(inventory.docDensity || 0) * 100));
|
|
90
|
+
const penalty = Number(summary.P1 || 0) * 8 + Number(summary.P2 || 0) * 4 + Number(summary.P3 || 0);
|
|
91
|
+
return Math.max(0, Math.min(100, densityScore + 30 - penalty));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildRecommendations(inventory = {}, summary = {}) {
|
|
95
|
+
const recommendations = [];
|
|
96
|
+
if (Number(summary.P1 || 0) > 0) {
|
|
97
|
+
recommendations.push("Close P1 documentation gaps for critical modules before release.");
|
|
98
|
+
}
|
|
99
|
+
if (inventory.undocumentedHotspots?.length > 0) {
|
|
100
|
+
recommendations.push("Create runbooks/spec addenda for high-LOC modules lacking nearby docs.");
|
|
101
|
+
}
|
|
102
|
+
if (Number(inventory.docDensity || 0) < 0.2) {
|
|
103
|
+
recommendations.push("Increase docs-to-code density with ADRs and operator-oriented troubleshooting guides.");
|
|
104
|
+
}
|
|
105
|
+
if (recommendations.length === 0) {
|
|
106
|
+
recommendations.push("Documentation posture is stable; maintain drift checks between code and specs.");
|
|
107
|
+
}
|
|
108
|
+
return recommendations;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function runDocumentationSpecialist({ findings = [], ingest = {} } = {}) {
|
|
112
|
+
const inventory = buildDocumentationInventory(ingest);
|
|
113
|
+
const documentationFindings = buildDocumentationFindings({
|
|
114
|
+
findings,
|
|
115
|
+
inventory,
|
|
116
|
+
});
|
|
117
|
+
const summary = summarizeSeverity(documentationFindings);
|
|
118
|
+
const documentationScore = estimateDocumentationScore(inventory, summary);
|
|
119
|
+
const confidence =
|
|
120
|
+
documentationFindings.length > 0 ? Math.max(0.72, 1 - documentationFindings.length * 0.0025) : 0.9;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
schemaVersion: "1.0.0",
|
|
124
|
+
generatedAt: new Date().toISOString(),
|
|
125
|
+
summary: {
|
|
126
|
+
...summary,
|
|
127
|
+
findingCount: documentationFindings.length,
|
|
128
|
+
documentationScore,
|
|
129
|
+
},
|
|
130
|
+
confidence,
|
|
131
|
+
inventory,
|
|
132
|
+
recommendations: buildRecommendations(inventory, summary),
|
|
133
|
+
findings: documentationFindings,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function renderDocumentationSpecialistMarkdown(report = {}) {
|
|
138
|
+
const hotspots = (report.inventory?.undocumentedHotspots || [])
|
|
139
|
+
.map((item) => `- ${item.path} (${item.loc} LOC, severity=${item.severity})`)
|
|
140
|
+
.join("\n");
|
|
141
|
+
const recommendations = (report.recommendations || []).map((item) => `- ${item}`).join("\n");
|
|
142
|
+
|
|
143
|
+
return `# DOCUMENTATION_AGENT_REPORT
|
|
144
|
+
|
|
145
|
+
Generated: ${report.generatedAt}
|
|
146
|
+
Documentation score: ${report.summary?.documentationScore ?? 0}/100
|
|
147
|
+
Confidence: ${((report.confidence || 0) * 100).toFixed(0)}%
|
|
148
|
+
|
|
149
|
+
Summary:
|
|
150
|
+
- Findings: P0=${report.summary?.P0 ?? 0} P1=${report.summary?.P1 ?? 0} P2=${report.summary?.P2 ?? 0} P3=${report.summary?.P3 ?? 0}
|
|
151
|
+
- Blocking: ${report.summary?.blocking ? "yes" : "no"}
|
|
152
|
+
- Total findings: ${report.summary?.findingCount ?? 0}
|
|
153
|
+
|
|
154
|
+
Documentation inventory:
|
|
155
|
+
- doc files: ${report.inventory?.docFileCount ?? 0}
|
|
156
|
+
- code files: ${report.inventory?.codeFileCount ?? 0}
|
|
157
|
+
- doc density: ${((report.inventory?.docDensity || 0) * 100).toFixed(1)}%
|
|
158
|
+
|
|
159
|
+
Undocumented hotspots:
|
|
160
|
+
${hotspots || "- none"}
|
|
161
|
+
|
|
162
|
+
Recommendations:
|
|
163
|
+
${recommendations || "- none"}
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
function normalizeString(value) {
|
|
2
|
+
return String(value || "").trim();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function toPosixPath(value) {
|
|
6
|
+
return String(value || "").replace(/\\/g, "/");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function summarizeSeverity(findings = []) {
|
|
10
|
+
const summary = { P0: 0, P1: 0, P2: 0, P3: 0 };
|
|
11
|
+
for (const finding of findings) {
|
|
12
|
+
const severity = normalizeString(finding.severity).toUpperCase();
|
|
13
|
+
if (Object.prototype.hasOwnProperty.call(summary, severity)) {
|
|
14
|
+
summary[severity] += 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
summary.blocking = summary.P0 > 0 || summary.P1 > 0;
|
|
18
|
+
return summary;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildRuntimeHotspots(ingest = {}) {
|
|
22
|
+
const indexed = Array.isArray(ingest.indexedFiles?.files) ? ingest.indexedFiles.files : [];
|
|
23
|
+
return indexed
|
|
24
|
+
.filter((file) => Number(file.loc || 0) >= 280)
|
|
25
|
+
.sort((left, right) => Number(right.loc || 0) - Number(left.loc || 0))
|
|
26
|
+
.slice(0, 20)
|
|
27
|
+
.map((file) => ({
|
|
28
|
+
path: toPosixPath(file.path),
|
|
29
|
+
loc: Number(file.loc || 0),
|
|
30
|
+
language: file.language,
|
|
31
|
+
severity: Number(file.loc || 0) >= 900 ? "P1" : "P2",
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildPerformanceFindings({ findings = [], runtimeHotspots = [] } = {}) {
|
|
36
|
+
const scoped = Array.isArray(findings) ? findings : [];
|
|
37
|
+
const derived = [];
|
|
38
|
+
for (const finding of scoped) {
|
|
39
|
+
const haystack = `${normalizeString(finding.message)} ${normalizeString(finding.ruleId)} ${normalizeString(
|
|
40
|
+
finding.file
|
|
41
|
+
)}`.toLowerCase();
|
|
42
|
+
if (/latency|loop|n\+1|query|performance|cache|hot path|throughput|timeout/.test(haystack)) {
|
|
43
|
+
derived.push(finding);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const hotspot of runtimeHotspots) {
|
|
48
|
+
const exists = derived.some((finding) => toPosixPath(finding.file) === hotspot.path);
|
|
49
|
+
if (exists) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
derived.push({
|
|
53
|
+
severity: hotspot.severity,
|
|
54
|
+
file: hotspot.path,
|
|
55
|
+
line: 1,
|
|
56
|
+
message: `Runtime hotspot candidate detected (${hotspot.loc} LOC).`,
|
|
57
|
+
excerpt: `${hotspot.language || "code"} module at ${hotspot.path}`,
|
|
58
|
+
ruleId: "SL-PERF-001",
|
|
59
|
+
suggestedFix: "Profile this path and split heavy loops/queries into bounded units with caching.",
|
|
60
|
+
layer: "performance",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return derived.slice(0, 120);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function estimatePerformanceScore(runtimeHotspots = [], summary = {}) {
|
|
68
|
+
const penalty =
|
|
69
|
+
runtimeHotspots.length * 2 +
|
|
70
|
+
Number(summary.P1 || 0) * 8 +
|
|
71
|
+
Number(summary.P2 || 0) * 4 +
|
|
72
|
+
Number(summary.P3 || 0);
|
|
73
|
+
return Math.max(0, 100 - Math.min(100, penalty));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildRecommendations({ runtimeHotspots = [], summary = {} } = {}) {
|
|
77
|
+
const recommendations = [];
|
|
78
|
+
if (Number(summary.P1 || 0) > 0) {
|
|
79
|
+
recommendations.push("Resolve P1 performance findings and add regression benchmarks before merge.");
|
|
80
|
+
}
|
|
81
|
+
if (runtimeHotspots.length > 0) {
|
|
82
|
+
recommendations.push("Profile top hotspot modules and document expected p95/p99 behavior for each.");
|
|
83
|
+
}
|
|
84
|
+
if (Number(summary.P2 || 0) >= 4) {
|
|
85
|
+
recommendations.push("Introduce staged load checks for high-change paths to prevent latency drift.");
|
|
86
|
+
}
|
|
87
|
+
if (recommendations.length === 0) {
|
|
88
|
+
recommendations.push("Performance posture is stable; continue periodic profiling on critical paths.");
|
|
89
|
+
}
|
|
90
|
+
return recommendations;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function runPerformanceSpecialist({ findings = [], ingest = {} } = {}) {
|
|
94
|
+
const runtimeHotspots = buildRuntimeHotspots(ingest);
|
|
95
|
+
const performanceFindings = buildPerformanceFindings({
|
|
96
|
+
findings,
|
|
97
|
+
runtimeHotspots,
|
|
98
|
+
});
|
|
99
|
+
const summary = summarizeSeverity(performanceFindings);
|
|
100
|
+
const performanceScore = estimatePerformanceScore(runtimeHotspots, summary);
|
|
101
|
+
const confidence =
|
|
102
|
+
performanceFindings.length > 0 ? Math.max(0.74, 1 - performanceFindings.length * 0.0025) : 0.9;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
schemaVersion: "1.0.0",
|
|
106
|
+
generatedAt: new Date().toISOString(),
|
|
107
|
+
summary: {
|
|
108
|
+
...summary,
|
|
109
|
+
findingCount: performanceFindings.length,
|
|
110
|
+
performanceScore,
|
|
111
|
+
},
|
|
112
|
+
confidence,
|
|
113
|
+
runtimeHotspots,
|
|
114
|
+
recommendations: buildRecommendations({
|
|
115
|
+
runtimeHotspots,
|
|
116
|
+
summary,
|
|
117
|
+
}),
|
|
118
|
+
findings: performanceFindings,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function renderPerformanceSpecialistMarkdown(report = {}) {
|
|
123
|
+
const hotspots = (report.runtimeHotspots || [])
|
|
124
|
+
.map((item) => `- ${item.path} (${item.loc} LOC, severity=${item.severity})`)
|
|
125
|
+
.join("\n");
|
|
126
|
+
const recommendations = (report.recommendations || []).map((item) => `- ${item}`).join("\n");
|
|
127
|
+
|
|
128
|
+
return `# PERFORMANCE_AGENT_REPORT
|
|
129
|
+
|
|
130
|
+
Generated: ${report.generatedAt}
|
|
131
|
+
Performance score: ${report.summary?.performanceScore ?? 0}/100
|
|
132
|
+
Confidence: ${((report.confidence || 0) * 100).toFixed(0)}%
|
|
133
|
+
|
|
134
|
+
Summary:
|
|
135
|
+
- Findings: P0=${report.summary?.P0 ?? 0} P1=${report.summary?.P1 ?? 0} P2=${report.summary?.P2 ?? 0} P3=${report.summary?.P3 ?? 0}
|
|
136
|
+
- Blocking: ${report.summary?.blocking ? "yes" : "no"}
|
|
137
|
+
- Total findings: ${report.summary?.findingCount ?? 0}
|
|
138
|
+
|
|
139
|
+
Runtime hotspots:
|
|
140
|
+
${hotspots || "- none"}
|
|
141
|
+
|
|
142
|
+
Recommendations:
|
|
143
|
+
${recommendations || "- none"}
|
|
144
|
+
`;
|
|
145
|
+
}
|