codecartographer-pi 0.24.1 → 0.26.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/.codecarto/broadside/SKILL.md +20 -1
- package/.codecarto/workflow/scaffold-version.yaml +1 -1
- package/README.md +5 -4
- package/agent-skill/codecartographer/references/broadside.md +5 -1
- package/dist/core/broadside/client.d.ts +56 -0
- package/dist/core/broadside/client.js +200 -0
- package/dist/core/broadside/collect.d.ts +68 -0
- package/dist/core/broadside/collect.js +676 -0
- package/dist/core/broadside/constants.d.ts +51 -0
- package/dist/core/broadside/constants.js +74 -0
- package/dist/core/broadside/lenses.d.ts +31 -0
- package/dist/core/broadside/lenses.js +312 -0
- package/dist/core/broadside/models.d.ts +46 -0
- package/dist/core/broadside/models.js +321 -0
- package/dist/core/broadside/render.d.ts +20 -0
- package/dist/core/broadside/render.js +285 -0
- package/dist/core/broadside/repo.d.ts +58 -0
- package/dist/core/broadside/repo.js +592 -0
- package/dist/core/broadside/requests.d.ts +23 -0
- package/dist/core/broadside/requests.js +71 -0
- package/dist/core/broadside/results.d.ts +36 -0
- package/dist/core/broadside/results.js +163 -0
- package/dist/core/broadside/schemas.d.ts +2 -0
- package/dist/core/broadside/schemas.js +342 -0
- package/dist/core/broadside/state.d.ts +99 -0
- package/dist/core/broadside/state.js +384 -0
- package/dist/core/broadside/submit.d.ts +30 -0
- package/dist/core/broadside/submit.js +350 -0
- package/dist/core/broadside/types.d.ts +491 -0
- package/dist/core/broadside/types.js +107 -0
- package/dist/core/{broadside-verify.d.ts → broadside/verify.d.ts} +23 -2
- package/dist/core/{broadside-verify.js → broadside/verify.js} +43 -5
- package/dist/core/broadside.d.ts +14 -890
- package/dist/core/broadside.js +25 -3564
- package/dist/core/completion.js +91 -72
- package/dist/core/dashboard-writer.js +9 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +0 -1
- package/dist/core/library.d.ts +24 -1
- package/dist/core/library.js +46 -15
- package/dist/core/orchestrator-config.js +22 -8
- package/dist/core/status.d.ts +42 -23
- package/dist/core/status.js +163 -137
- package/dist/core/workspace.d.ts +2 -0
- package/dist/core/workspace.js +49 -25
- package/dist/core/yaml.js +9 -3
- package/dist/extensions/codecarto/auto-runner.d.ts +7 -0
- package/dist/extensions/codecarto/auto-runner.js +54 -23
- package/dist/extensions/codecarto/broadside-flags.d.ts +3 -1
- package/dist/extensions/codecarto/broadside-flags.js +13 -0
- package/dist/extensions/codecarto/index.js +13 -7
- package/dist/extensions/codecarto/phase-compaction.js +6 -2
- package/dist/mcp-server/server.d.ts +1 -0
- package/dist/mcp-server/server.js +28 -5
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Batch request building and cost estimation for a lens's slices.
|
|
2
|
+
//
|
|
3
|
+
// Split out of core/broadside.ts (#339); the barrel there re-exports every
|
|
4
|
+
// name, so `core/index.ts` and the tests see one module as before.
|
|
5
|
+
import { BROADSIDE_MODEL } from "./constants.js";
|
|
6
|
+
import { defaultReasoningFor } from "./types.js";
|
|
7
|
+
import { SCHEMAS } from "./schemas.js";
|
|
8
|
+
import { sanitizeId } from "./repo.js";
|
|
9
|
+
// ---------- request building ----------
|
|
10
|
+
export function buildBatchRequest(lens, info, slice, index, sliceCount, model = BROADSIDE_MODEL, maxTokensOverride, reasoningOverride) {
|
|
11
|
+
const moduleTag = sanitizeId(slice.moduleName);
|
|
12
|
+
const customId = sliceCount > 1 ? `${lens.id}-${moduleTag}-${index + 1}` : `${lens.id}-${moduleTag}`;
|
|
13
|
+
return {
|
|
14
|
+
custom_id: customId,
|
|
15
|
+
body: {
|
|
16
|
+
model,
|
|
17
|
+
messages: [
|
|
18
|
+
{ role: "system", content: lens.systemPrompt(info) },
|
|
19
|
+
{
|
|
20
|
+
role: "user",
|
|
21
|
+
content:
|
|
22
|
+
// A fallback scan is not "server source files": say what it is,
|
|
23
|
+
// so the model judges the trust boundary wherever it appears
|
|
24
|
+
// and does not report the missing server/ as a finding (#319).
|
|
25
|
+
(slice.fallback
|
|
26
|
+
? `NOTE: this repository has no source files under the paths this lens usually reads (${slice.fallback}). ` +
|
|
27
|
+
"What follows is every source file it has, after anything those paths did match; locate the trust boundary and the request-handling code wherever they live.\n\n"
|
|
28
|
+
: "") + lens.userPrompt(info, slice.content, slice.moduleName),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
response_format: { type: "json_schema", json_schema: SCHEMAS[lens.schemaName] },
|
|
32
|
+
max_tokens: maxTokensOverride ?? lens.maxTokens,
|
|
33
|
+
// Always sent, never inherited: an absent field means the model's
|
|
34
|
+
// own default, and that default is what truncated the JSON.
|
|
35
|
+
reasoning: reasoningOverride ?? lens.reasoning ?? defaultReasoningFor(),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Pre-flight cost estimate for one lens.
|
|
41
|
+
*
|
|
42
|
+
* Every slice is its own batch request, so both halves scale with the slice
|
|
43
|
+
* count. The output half used to be a single `maxTokens * 0.75` for the whole
|
|
44
|
+
* lens no matter how many requests it sent — on a repository that sliced into
|
|
45
|
+
* 13 modules that budgeted one request's output and shipped thirteen, and a
|
|
46
|
+
* live run came in at roughly 3x its estimate. Since this number is what
|
|
47
|
+
* `max_cost` binds against, under-counting it lets a run outspend the cap the
|
|
48
|
+
* user set.
|
|
49
|
+
*
|
|
50
|
+
* @param info - Repo info, when the caller has it: lets the estimate include
|
|
51
|
+
* the system prompt and JSON schema each request carries. Omitted, the
|
|
52
|
+
* estimate covers slice content only, which is what the old signature did.
|
|
53
|
+
*/
|
|
54
|
+
export function estimateCost(lens, slices, pricing, maxTokensOverride, info) {
|
|
55
|
+
// With repo info, size each request from the user prompt that would be
|
|
56
|
+
// sent, which is what the architecture lens is made of: it used to be
|
|
57
|
+
// estimated at a flat 6,000 chars while the entry point, manifest, README
|
|
58
|
+
// excerpt and file tree it carries ran to whatever they ran to (#249).
|
|
59
|
+
// Without info, the slice content alone is what the old signature covered.
|
|
60
|
+
const sliceChars = slices.reduce((sum, s) => sum + (info ? lens.userPrompt(info, s.content, s.moduleName).length : lens.maxChars === 0 ? 6000 : s.chars), 0);
|
|
61
|
+
// The system prompt and the response schema ride on every request, so they
|
|
62
|
+
// are paid once per slice rather than once per lens.
|
|
63
|
+
const perRequestOverhead = info
|
|
64
|
+
? (lens.systemPrompt(info)?.length ?? 0) + JSON.stringify(SCHEMAS[lens.schemaName] ?? {}).length
|
|
65
|
+
: 0;
|
|
66
|
+
const inputTokens = Math.ceil((sliceChars + perRequestOverhead * slices.length) / 4);
|
|
67
|
+
const outputTokens = slices.length * Math.ceil((maxTokensOverride ?? lens.maxTokens) * 0.75);
|
|
68
|
+
const cost = (inputTokens / 1_000_000) * pricing.inputPerM +
|
|
69
|
+
(outputTokens / 1_000_000) * pricing.outputPerM;
|
|
70
|
+
return { inputTokens, outputTokens, cost };
|
|
71
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type BroadsideLensId } from "./constants.ts";
|
|
2
|
+
import { type BatchRequest } from "./types.ts";
|
|
3
|
+
export declare function renderFindingsMarkdown(content: string): string;
|
|
4
|
+
export type StoredLensResult = {
|
|
5
|
+
lensId: BroadsideLensId;
|
|
6
|
+
customId: string;
|
|
7
|
+
moduleName: string;
|
|
8
|
+
content: string;
|
|
9
|
+
raw: Record<string, unknown>;
|
|
10
|
+
/** True when the content is not parseable JSON even after fence stripping —
|
|
11
|
+
* the telltale of an output cut off at max_tokens. */
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare function extractContent(result: Record<string, unknown>): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Parse lens content as JSON, tolerating the markdown code fences some models
|
|
17
|
+
* wrap structured output in (the same tolerance OpenRouter's headless-agent
|
|
18
|
+
* scaffold ships for --output-schema). Returns null when the content is not
|
|
19
|
+
* JSON at all — which for a strict json_schema request means the output was
|
|
20
|
+
* truncated at max_tokens, not that the model chose prose.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseLensJson(content: string): unknown | null;
|
|
23
|
+
export declare function saveLensResults(runDir: string, lensId: BroadsideLensId, batch: Record<string, unknown>): Promise<StoredLensResult[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Rebuild lens results from what a previous collect already wrote to disk.
|
|
26
|
+
*
|
|
27
|
+
* The post-passes are gated on having lens findings in hand, and a collect
|
|
28
|
+
* only holds the ones *it* polled. When an earlier collect saved every lens
|
|
29
|
+
* and then died before synthesis and triage ran — the batch window is long
|
|
30
|
+
* and a poll can easily be interrupted — the next collect finds every lens
|
|
31
|
+
* already terminal, skips them all, and would otherwise reach the post-pass
|
|
32
|
+
* gate with nothing to hand it. Reading the saved results back is what makes
|
|
33
|
+
* "a resumed collect can finish whichever is still pending" true.
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadSavedLensResults(runDir: string, lenses: BroadsideLensId[]): Promise<StoredLensResult[]>;
|
|
36
|
+
export declare function loadStoredRequests(runDir: string): Promise<Record<string, BatchRequest>>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Lens results as they come back and as they are stored: content extraction
|
|
2
|
+
// from a batch result, JSON parsing that tolerates code fences, markdown
|
|
3
|
+
// rendering, and the per-run files collect writes and later reads back.
|
|
4
|
+
//
|
|
5
|
+
// Split out of core/broadside.ts (#339); the barrel there re-exports every
|
|
6
|
+
// name, so `core/index.ts` and the tests see one module as before.
|
|
7
|
+
import { readFile, readdir, writeFile } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { pathExists } from "../utils.js";
|
|
10
|
+
import { sanitizeId } from "./repo.js";
|
|
11
|
+
export function renderFindingsMarkdown(content) {
|
|
12
|
+
const parsed = parseLensJson(content);
|
|
13
|
+
if (parsed === null)
|
|
14
|
+
return content;
|
|
15
|
+
return formatAsMarkdown(parsed);
|
|
16
|
+
}
|
|
17
|
+
function formatAsMarkdown(value, depth = 0) {
|
|
18
|
+
const indent = "\t".repeat(depth);
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
for (let i = 0; i < value.length; i++) {
|
|
22
|
+
const item = value[i];
|
|
23
|
+
if (item && typeof item === "object") {
|
|
24
|
+
const title = (item.title ?? item.name ?? item.module ?? item.area ?? item.platform ?? "");
|
|
25
|
+
lines.push(`${indent}${i + 1}. ${title}`);
|
|
26
|
+
lines.push(formatAsMarkdown(item, depth + 1));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
lines.push(`${indent}- ${String(item)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
34
|
+
if (value && typeof value === "object") {
|
|
35
|
+
const lines = [];
|
|
36
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
37
|
+
if (entryValue && typeof entryValue === "object") {
|
|
38
|
+
lines.push(`${indent}**${key}**:`);
|
|
39
|
+
lines.push(formatAsMarkdown(entryValue, depth + 1));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
lines.push(`${indent}- **${key}**: ${String(entryValue)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return lines.join("\n");
|
|
46
|
+
}
|
|
47
|
+
return `${indent}${String(value)}`;
|
|
48
|
+
}
|
|
49
|
+
export function extractContent(result) {
|
|
50
|
+
const response = result.response;
|
|
51
|
+
if (!response?.body)
|
|
52
|
+
return null;
|
|
53
|
+
const body = response.body;
|
|
54
|
+
const choices = body.choices;
|
|
55
|
+
const message = choices?.[0]?.message;
|
|
56
|
+
return typeof message?.content === "string" ? message.content : null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse lens content as JSON, tolerating the markdown code fences some models
|
|
60
|
+
* wrap structured output in (the same tolerance OpenRouter's headless-agent
|
|
61
|
+
* scaffold ships for --output-schema). Returns null when the content is not
|
|
62
|
+
* JSON at all — which for a strict json_schema request means the output was
|
|
63
|
+
* truncated at max_tokens, not that the model chose prose.
|
|
64
|
+
*/
|
|
65
|
+
export function parseLensJson(content) {
|
|
66
|
+
const trimmed = content.trim();
|
|
67
|
+
const fenced = /^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/.exec(trimmed);
|
|
68
|
+
const candidate = fenced ? fenced[1].trim() : trimmed;
|
|
69
|
+
if (!candidate.startsWith("{") && !candidate.startsWith("["))
|
|
70
|
+
return null;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(candidate);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function saveLensResults(runDir, lensId, batch) {
|
|
79
|
+
const results = Array.isArray(batch.results) ? batch.results : [];
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const result of results) {
|
|
82
|
+
const customId = String(result.custom_id ?? "unknown");
|
|
83
|
+
const content = extractContent(result);
|
|
84
|
+
if (content === null) {
|
|
85
|
+
if (result.error) {
|
|
86
|
+
await writeFile(join(runDir, `${sanitizeId(customId)}.error.json`), `${JSON.stringify(result.error, null, "\t")}\n`, "utf8");
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const parsed = parseLensJson(content);
|
|
91
|
+
const truncated = parsed === null;
|
|
92
|
+
if (parsed !== null) {
|
|
93
|
+
await writeFile(join(runDir, `${sanitizeId(customId)}.json`), `${JSON.stringify(parsed, null, "\t")}\n`, "utf8");
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Save the raw bytes verbatim so nothing is lost, but name the
|
|
97
|
+
// gap: an unparseable strict-schema response is a truncation.
|
|
98
|
+
await writeFile(join(runDir, `${sanitizeId(customId)}.json`), `${content}\n`, "utf8");
|
|
99
|
+
}
|
|
100
|
+
await writeFile(join(runDir, `${sanitizeId(customId)}.md`), renderFindingsMarkdown(content), "utf8");
|
|
101
|
+
out.push({
|
|
102
|
+
lensId,
|
|
103
|
+
customId,
|
|
104
|
+
moduleName: String(customId).replace(/^[a-z]+-/, ""),
|
|
105
|
+
content,
|
|
106
|
+
raw: result,
|
|
107
|
+
truncated,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Rebuild lens results from what a previous collect already wrote to disk.
|
|
114
|
+
*
|
|
115
|
+
* The post-passes are gated on having lens findings in hand, and a collect
|
|
116
|
+
* only holds the ones *it* polled. When an earlier collect saved every lens
|
|
117
|
+
* and then died before synthesis and triage ran — the batch window is long
|
|
118
|
+
* and a poll can easily be interrupted — the next collect finds every lens
|
|
119
|
+
* already terminal, skips them all, and would otherwise reach the post-pass
|
|
120
|
+
* gate with nothing to hand it. Reading the saved results back is what makes
|
|
121
|
+
* "a resumed collect can finish whichever is still pending" true.
|
|
122
|
+
*/
|
|
123
|
+
export async function loadSavedLensResults(runDir, lenses) {
|
|
124
|
+
if (!(await pathExists(runDir)))
|
|
125
|
+
return [];
|
|
126
|
+
const reserved = new Set(["requests.json", "run-meta.json", "synthesis.json", "triage.json"]);
|
|
127
|
+
const out = [];
|
|
128
|
+
// Longest lens id first: no id is a prefix of another today, but ordering
|
|
129
|
+
// keeps that from becoming a silent misattribution if one ever is.
|
|
130
|
+
const ordered = [...lenses].sort((a, b) => b.length - a.length);
|
|
131
|
+
for (const name of (await readdir(runDir)).sort()) {
|
|
132
|
+
if (!name.endsWith(".json") || name.endsWith(".error.json") || reserved.has(name) || name.startsWith("raw-"))
|
|
133
|
+
continue;
|
|
134
|
+
const customId = name.slice(0, -".json".length);
|
|
135
|
+
const lensId = ordered.find((id) => customId === id || customId.startsWith(`${id}-`));
|
|
136
|
+
if (!lensId)
|
|
137
|
+
continue;
|
|
138
|
+
const content = await readFile(join(runDir, name), "utf8").catch(() => null);
|
|
139
|
+
if (content === null)
|
|
140
|
+
continue;
|
|
141
|
+
out.push({
|
|
142
|
+
lensId,
|
|
143
|
+
customId,
|
|
144
|
+
moduleName: customId.replace(/^[a-z]+-/, ""),
|
|
145
|
+
content,
|
|
146
|
+
raw: {},
|
|
147
|
+
truncated: parseLensJson(content) === null,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
export async function loadStoredRequests(runDir) {
|
|
153
|
+
const path = join(runDir, "requests.json");
|
|
154
|
+
if (!(await pathExists(path)))
|
|
155
|
+
return {};
|
|
156
|
+
try {
|
|
157
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
158
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// The structured-output JSON schemas, one per lens plus synthesis and triage.
|
|
2
|
+
//
|
|
3
|
+
// Split out of core/broadside.ts (#339); the barrel there re-exports every
|
|
4
|
+
// name, so `core/index.ts` and the tests see one module as before.
|
|
5
|
+
// ---------- JSON schemas (one per lens, plus synthesis) ----------
|
|
6
|
+
export const SCHEMAS = {
|
|
7
|
+
architecture: {
|
|
8
|
+
name: "architecture_report",
|
|
9
|
+
strict: true,
|
|
10
|
+
schema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
tech_stack: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
language: { type: "string" },
|
|
17
|
+
version: { type: "string" },
|
|
18
|
+
build_system: { type: "string" },
|
|
19
|
+
key_dependencies: { type: "array", items: { type: "string" } },
|
|
20
|
+
},
|
|
21
|
+
required: ["language", "build_system"],
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
},
|
|
24
|
+
module_architecture: {
|
|
25
|
+
type: "array",
|
|
26
|
+
items: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
name: { type: "string" },
|
|
30
|
+
role: { type: "string" },
|
|
31
|
+
file_count: { type: "integer" },
|
|
32
|
+
depends_on: { type: "array", items: { type: "string" } },
|
|
33
|
+
},
|
|
34
|
+
required: ["name", "role"],
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
data_flow: { type: "string" },
|
|
39
|
+
entry_points: { type: "array", items: { type: "string" } },
|
|
40
|
+
notable_patterns: { type: "array", items: { type: "string" } },
|
|
41
|
+
},
|
|
42
|
+
required: ["tech_stack", "module_architecture", "data_flow", "entry_points"],
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
api_surface: {
|
|
47
|
+
name: "api_surface_report",
|
|
48
|
+
strict: true,
|
|
49
|
+
schema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
endpoints: {
|
|
53
|
+
type: "array",
|
|
54
|
+
items: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
method: { type: "string" },
|
|
58
|
+
path: { type: "string" },
|
|
59
|
+
handler: { type: "string" },
|
|
60
|
+
auth_required: { type: "boolean" },
|
|
61
|
+
description: { type: "string" },
|
|
62
|
+
},
|
|
63
|
+
required: ["method", "path", "handler", "auth_required"],
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
data_types: {
|
|
68
|
+
type: "array",
|
|
69
|
+
items: {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
name: { type: "string" },
|
|
73
|
+
kind: { type: "string" },
|
|
74
|
+
fields_summary: { type: "string" },
|
|
75
|
+
},
|
|
76
|
+
required: ["name", "kind"],
|
|
77
|
+
additionalProperties: false,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
authentication_flow: { type: "string" },
|
|
81
|
+
error_handling: { type: "string" },
|
|
82
|
+
},
|
|
83
|
+
required: ["endpoints"],
|
|
84
|
+
additionalProperties: false,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
security: {
|
|
88
|
+
name: "security_review_report",
|
|
89
|
+
strict: true,
|
|
90
|
+
schema: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {
|
|
93
|
+
findings: {
|
|
94
|
+
type: "array",
|
|
95
|
+
items: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
99
|
+
category: { type: "string" },
|
|
100
|
+
title: { type: "string" },
|
|
101
|
+
location: { type: "string" },
|
|
102
|
+
description: { type: "string" },
|
|
103
|
+
},
|
|
104
|
+
required: ["severity", "title", "description"],
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
overall_assessment: { type: "string" },
|
|
109
|
+
coverage_note: { type: "string" },
|
|
110
|
+
},
|
|
111
|
+
required: ["findings", "overall_assessment"],
|
|
112
|
+
additionalProperties: false,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
defect_mechanical: {
|
|
116
|
+
name: "defect_scan_report",
|
|
117
|
+
strict: true,
|
|
118
|
+
schema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
module: { type: "string" },
|
|
122
|
+
findings: {
|
|
123
|
+
type: "array",
|
|
124
|
+
items: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
severity: { type: "string", enum: ["high", "medium", "low"] },
|
|
128
|
+
pattern: { type: "string" },
|
|
129
|
+
title: { type: "string" },
|
|
130
|
+
location: { type: "string" },
|
|
131
|
+
description: { type: "string" },
|
|
132
|
+
suggestion: { type: "string" },
|
|
133
|
+
},
|
|
134
|
+
required: ["severity", "pattern", "title", "description"],
|
|
135
|
+
additionalProperties: false,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
patterns_checked: { type: "array", items: { type: "string" } },
|
|
139
|
+
files_scanned: { type: "integer" },
|
|
140
|
+
overall_notes: { type: "string" },
|
|
141
|
+
},
|
|
142
|
+
required: ["module", "findings", "patterns_checked", "files_scanned"],
|
|
143
|
+
additionalProperties: false,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
conventions: {
|
|
147
|
+
name: "conventions_report",
|
|
148
|
+
strict: true,
|
|
149
|
+
schema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
module: { type: "string" },
|
|
153
|
+
naming_conventions: {
|
|
154
|
+
type: "object",
|
|
155
|
+
properties: {
|
|
156
|
+
packages: { type: "string" },
|
|
157
|
+
types: { type: "string" },
|
|
158
|
+
functions: { type: "string" },
|
|
159
|
+
variables: { type: "string" },
|
|
160
|
+
files: { type: "string" },
|
|
161
|
+
tests: { type: "string" },
|
|
162
|
+
},
|
|
163
|
+
additionalProperties: false,
|
|
164
|
+
},
|
|
165
|
+
error_handling_pattern: { type: "string" },
|
|
166
|
+
logging_approach: { type: "string" },
|
|
167
|
+
test_patterns: { type: "string" },
|
|
168
|
+
code_organization: { type: "string" },
|
|
169
|
+
idioms: { type: "array", items: { type: "string" } },
|
|
170
|
+
inconsistencies: {
|
|
171
|
+
type: "array",
|
|
172
|
+
items: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
description: { type: "string" },
|
|
176
|
+
locations: { type: "array", items: { type: "string" } },
|
|
177
|
+
},
|
|
178
|
+
required: ["description"],
|
|
179
|
+
additionalProperties: false,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
promotable_conventions: {
|
|
183
|
+
type: "array",
|
|
184
|
+
items: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
title: { type: "string" },
|
|
188
|
+
rule: { type: "string" },
|
|
189
|
+
evidence: { type: "string" },
|
|
190
|
+
},
|
|
191
|
+
required: ["title", "rule"],
|
|
192
|
+
additionalProperties: false,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
files_scanned: { type: "integer" },
|
|
196
|
+
},
|
|
197
|
+
required: ["module", "naming_conventions", "files_scanned"],
|
|
198
|
+
additionalProperties: false,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
porting: {
|
|
202
|
+
name: "porting_surface_report",
|
|
203
|
+
strict: true,
|
|
204
|
+
schema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
module: { type: "string" },
|
|
208
|
+
platform_coupling: {
|
|
209
|
+
type: "array",
|
|
210
|
+
items: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
platform: { type: "string" },
|
|
214
|
+
mechanisms: { type: "array", items: { type: "string" } },
|
|
215
|
+
files: { type: "array", items: { type: "string" } },
|
|
216
|
+
},
|
|
217
|
+
required: ["platform", "mechanisms"],
|
|
218
|
+
additionalProperties: false,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
external_dependencies: {
|
|
222
|
+
type: "array",
|
|
223
|
+
items: {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
name: { type: "string" },
|
|
227
|
+
role: { type: "string" },
|
|
228
|
+
replaceability: { type: "string" },
|
|
229
|
+
},
|
|
230
|
+
required: ["name"],
|
|
231
|
+
additionalProperties: false,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
build_system_complexity: { type: "string" },
|
|
235
|
+
porting_risk_areas: {
|
|
236
|
+
type: "array",
|
|
237
|
+
items: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
area: { type: "string" },
|
|
241
|
+
risk: { type: "string", enum: ["low", "medium", "high"] },
|
|
242
|
+
notes: { type: "string" },
|
|
243
|
+
},
|
|
244
|
+
required: ["area", "risk"],
|
|
245
|
+
additionalProperties: false,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
files_scanned: { type: "integer" },
|
|
249
|
+
},
|
|
250
|
+
required: ["module", "platform_coupling", "files_scanned"],
|
|
251
|
+
additionalProperties: false,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
synthesis: {
|
|
255
|
+
name: "synthesis_report",
|
|
256
|
+
strict: true,
|
|
257
|
+
schema: {
|
|
258
|
+
type: "object",
|
|
259
|
+
properties: {
|
|
260
|
+
executive_summary: { type: "string" },
|
|
261
|
+
severity_summary: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
critical: { type: "integer" },
|
|
265
|
+
high: { type: "integer" },
|
|
266
|
+
medium: { type: "integer" },
|
|
267
|
+
low: { type: "integer" },
|
|
268
|
+
},
|
|
269
|
+
required: ["critical", "high", "medium", "low"],
|
|
270
|
+
additionalProperties: false,
|
|
271
|
+
},
|
|
272
|
+
top_findings: {
|
|
273
|
+
type: "array",
|
|
274
|
+
items: {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties: {
|
|
277
|
+
title: { type: "string" },
|
|
278
|
+
severity: { type: "string" },
|
|
279
|
+
source_lens: { type: "string" },
|
|
280
|
+
summary: { type: "string" },
|
|
281
|
+
},
|
|
282
|
+
required: ["title", "severity", "source_lens", "summary"],
|
|
283
|
+
additionalProperties: false,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
module_assessments: {
|
|
287
|
+
type: "array",
|
|
288
|
+
items: {
|
|
289
|
+
type: "object",
|
|
290
|
+
properties: {
|
|
291
|
+
module: { type: "string" },
|
|
292
|
+
quality_notes: { type: "string" },
|
|
293
|
+
risk_level: { type: "string", enum: ["low", "medium", "high"] },
|
|
294
|
+
},
|
|
295
|
+
required: ["module", "risk_level"],
|
|
296
|
+
additionalProperties: false,
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
porting_readiness: { type: "string" },
|
|
300
|
+
gaps_and_unknowns: { type: "array", items: { type: "string" } },
|
|
301
|
+
coverage: { type: "string" },
|
|
302
|
+
},
|
|
303
|
+
required: ["executive_summary", "severity_summary", "top_findings"],
|
|
304
|
+
additionalProperties: false,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
triage: {
|
|
308
|
+
name: "triage_report",
|
|
309
|
+
strict: true,
|
|
310
|
+
schema: {
|
|
311
|
+
type: "object",
|
|
312
|
+
properties: {
|
|
313
|
+
summary: { type: "string" },
|
|
314
|
+
items: {
|
|
315
|
+
type: "array",
|
|
316
|
+
items: {
|
|
317
|
+
type: "object",
|
|
318
|
+
properties: {
|
|
319
|
+
title: { type: "string" },
|
|
320
|
+
severity: { type: "string" },
|
|
321
|
+
module: { type: "string" },
|
|
322
|
+
impact: { type: "string", enum: ["high", "medium", "low"] },
|
|
323
|
+
difficulty: { type: "string", enum: ["high", "medium", "low"] },
|
|
324
|
+
priority: { type: "string" },
|
|
325
|
+
effort_estimate: { type: "string" },
|
|
326
|
+
rationale: { type: "string" },
|
|
327
|
+
},
|
|
328
|
+
required: ["title", "severity", "module", "impact", "difficulty", "priority", "rationale"],
|
|
329
|
+
additionalProperties: false,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
omitted: {
|
|
333
|
+
type: "array",
|
|
334
|
+
items: { type: "string" },
|
|
335
|
+
description: "Leads deliberately dropped from the queue and why (duplicates, too vague, out of scope)",
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
required: ["summary", "items"],
|
|
339
|
+
additionalProperties: false,
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
};
|