intentdna 1.6.2 → 1.6.4
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +7 -1
- package/dist/cli/commands/evolve.d.ts +6 -3
- package/dist/cli/commands/evolve.js +78 -32
- package/dist/cli/commands/feedback.d.ts +3 -28
- package/dist/cli/commands/feedback.js +12 -181
- package/dist/cli/index.js +9 -4
- package/dist/hooks/cli.d.ts +27 -1
- package/dist/hooks/cli.js +73 -4
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-observability.d.ts +2 -0
- package/dist/mcp/tools-observability.js +34 -0
- package/dist/report/index.d.ts +2 -0
- package/dist/report/index.js +2 -0
- package/dist/report/kernel-report.d.ts +79 -0
- package/dist/report/kernel-report.js +259 -0
- package/dist/report/kernel-signals.d.ts +38 -0
- package/dist/report/kernel-signals.js +228 -0
- package/dist/runtime/skill-adapter.js +103 -37
- package/dist/schema/types.d.ts +12 -2
- package/dist/schema/validate.js +46 -3
- package/dist/templates/flutter-rewrite.dna.yaml +53 -4
- package/package.json +1 -1
- package/spec/README.md +58 -0
package/dist/mcp/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createMCPServer } from "./server.js";
|
|
|
14
14
|
import { createStateTools } from "./tools-state.js";
|
|
15
15
|
import { createCompileTools } from "./tools-compile.js";
|
|
16
16
|
import { createEnforceTools } from "./tools-enforce.js";
|
|
17
|
+
import { createObservabilityTools } from "./tools-observability.js";
|
|
17
18
|
// Parse args
|
|
18
19
|
const args = process.argv.slice(2);
|
|
19
20
|
let projectDir = process.cwd();
|
|
@@ -36,5 +37,6 @@ const tools = [
|
|
|
36
37
|
...createStateTools(projectDir),
|
|
37
38
|
...createCompileTools(projectDir),
|
|
38
39
|
...createEnforceTools(projectDir),
|
|
40
|
+
...createObservabilityTools(projectDir),
|
|
39
41
|
];
|
|
40
42
|
createMCPServer({ name: "intentdna", version }, tools);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { textResult } from "./server.js";
|
|
2
|
+
import { buildKernelReport, formatKernelReport } from "../report/kernel-report.js";
|
|
3
|
+
export function createObservabilityTools(projectDir) {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
name: "dna_kernel_report",
|
|
7
|
+
description: "Observability-only local kernel report. Reads traces/verifier results and optional preview-only marker changes; does not execute verifiers or mutate state.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
days: { type: "number", description: "Days to look back (default 7)" },
|
|
12
|
+
session_id: { type: "string", description: "Session ID filter" },
|
|
13
|
+
dna_id: { type: "string", description: "DNA ID for marker preview; derived from compiled IR if omitted and unambiguous" },
|
|
14
|
+
include_marker_preview: { type: "boolean", description: "Include preview-only marker changes (default false)" },
|
|
15
|
+
format: { type: "string", enum: ["json", "text"], description: "Return JSON schema or text summary (default text)" },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
handler: async (args) => {
|
|
19
|
+
const report = await buildKernelReport({
|
|
20
|
+
projectDir,
|
|
21
|
+
days: typeof args.days === "number" ? args.days : 7,
|
|
22
|
+
sessionId: typeof args.session_id === "string" ? args.session_id : undefined,
|
|
23
|
+
dnaId: typeof args.dna_id === "string" ? args.dna_id : undefined,
|
|
24
|
+
includeMarkerPreview: typeof args.include_marker_preview === "boolean" ? args.include_marker_preview : false,
|
|
25
|
+
});
|
|
26
|
+
if (args.format === "json") {
|
|
27
|
+
return textResult(JSON.stringify(report, null, 2));
|
|
28
|
+
}
|
|
29
|
+
return textResult(formatKernelReport(report) +
|
|
30
|
+
"\nMCP observability only: this tool does not execute verifiers, does not enforce hook parity, and does not mutate state or markers.\n");
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { MarkerChange } from "../evolution/index.js";
|
|
2
|
+
import { type ClassifiedSignal, type KernelSignalClassification } from "./kernel-signals.js";
|
|
3
|
+
export interface KernelReportOptions {
|
|
4
|
+
projectDir: string;
|
|
5
|
+
days?: number;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
dnaId?: string;
|
|
8
|
+
includeMarkerPreview?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface KernelReport {
|
|
11
|
+
schema_version: "intentdna.kernel_report.v1";
|
|
12
|
+
generated_at: string;
|
|
13
|
+
source: {
|
|
14
|
+
project_dir: string;
|
|
15
|
+
session_id?: string;
|
|
16
|
+
dna_id?: string;
|
|
17
|
+
dna_id_source?: "explicit" | "compiled_ir_single_source" | "unresolved";
|
|
18
|
+
period_days: number;
|
|
19
|
+
};
|
|
20
|
+
summary: {
|
|
21
|
+
traces: number;
|
|
22
|
+
verifier_results: number;
|
|
23
|
+
signals: number;
|
|
24
|
+
marker_preview_changes?: number;
|
|
25
|
+
};
|
|
26
|
+
classifications: Record<KernelSignalClassification, number>;
|
|
27
|
+
quality: {
|
|
28
|
+
quality_positive: number;
|
|
29
|
+
quality_negative: number;
|
|
30
|
+
};
|
|
31
|
+
enforcement: {
|
|
32
|
+
blocks: number;
|
|
33
|
+
warns: number;
|
|
34
|
+
allows: number;
|
|
35
|
+
policy_denied_verifiers: number;
|
|
36
|
+
};
|
|
37
|
+
signals: ClassifiedSignal[];
|
|
38
|
+
verifier_summary: {
|
|
39
|
+
total: number;
|
|
40
|
+
passes: number;
|
|
41
|
+
failures: number;
|
|
42
|
+
};
|
|
43
|
+
trace_summary: {
|
|
44
|
+
total: number;
|
|
45
|
+
blocks: number;
|
|
46
|
+
warns: number;
|
|
47
|
+
allows: number;
|
|
48
|
+
top_blocked_tools: Array<{
|
|
49
|
+
tool: string;
|
|
50
|
+
count: number;
|
|
51
|
+
}>;
|
|
52
|
+
top_blocked_paths: Array<{
|
|
53
|
+
path: string;
|
|
54
|
+
count: number;
|
|
55
|
+
}>;
|
|
56
|
+
top_block_reasons: Array<{
|
|
57
|
+
reason: string;
|
|
58
|
+
count: number;
|
|
59
|
+
}>;
|
|
60
|
+
};
|
|
61
|
+
marker_preview?: {
|
|
62
|
+
preview_only: true;
|
|
63
|
+
dna_id?: string;
|
|
64
|
+
dna_id_source?: "explicit" | "compiled_ir_single_source" | "unresolved";
|
|
65
|
+
changes: MarkerChange[];
|
|
66
|
+
excluded_signal_refs: string[];
|
|
67
|
+
exclusion_reasons: Record<string, string>;
|
|
68
|
+
error?: string;
|
|
69
|
+
};
|
|
70
|
+
mutation: {
|
|
71
|
+
command_class: "read-only" | "preview-only";
|
|
72
|
+
wrote_state: false;
|
|
73
|
+
wrote_evolution: false;
|
|
74
|
+
persisted_outcomes: false;
|
|
75
|
+
mutated_markers: false;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export declare function buildKernelReport(options: KernelReportOptions): Promise<KernelReport>;
|
|
79
|
+
export declare function formatKernelReport(report: KernelReport): string;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readTraces, readVerifierResults, safePathComponent } from "../hooks/state.js";
|
|
4
|
+
import { describeMarkerChanges } from "../evolution/marker-gen.js";
|
|
5
|
+
import { classifiedSignalsToOutcomes, classifyKernelSignals, } from "./kernel-signals.js";
|
|
6
|
+
const CLASSIFICATIONS = [
|
|
7
|
+
"protective",
|
|
8
|
+
"friction",
|
|
9
|
+
"positive",
|
|
10
|
+
"negative",
|
|
11
|
+
"neutral",
|
|
12
|
+
"unknown",
|
|
13
|
+
];
|
|
14
|
+
function withinDays(timestamp, days) {
|
|
15
|
+
const parsed = Date.parse(timestamp);
|
|
16
|
+
if (Number.isNaN(parsed))
|
|
17
|
+
return true;
|
|
18
|
+
return parsed >= Date.now() - days * 24 * 60 * 60 * 1000;
|
|
19
|
+
}
|
|
20
|
+
function sortedCounts(items, limit, key) {
|
|
21
|
+
const counts = new Map();
|
|
22
|
+
for (const item of items)
|
|
23
|
+
counts.set(item, (counts.get(item) ?? 0) + 1);
|
|
24
|
+
return [...counts.entries()]
|
|
25
|
+
.sort((a, b) => b[1] - a[1])
|
|
26
|
+
.slice(0, limit)
|
|
27
|
+
.map(([value, count]) => ({ [key]: value, count }));
|
|
28
|
+
}
|
|
29
|
+
function buildTraceSummary(traces) {
|
|
30
|
+
const blocks = traces.filter((trace) => trace.decision === "block");
|
|
31
|
+
const warns = traces.filter((trace) => trace.decision === "warn").length;
|
|
32
|
+
const allows = traces.filter((trace) => trace.decision === "allow").length;
|
|
33
|
+
const reasons = blocks
|
|
34
|
+
.map((trace) => trace.reason?.split("\n")[0].replace(/^\[Intent DNA\]\s*/, "").slice(0, 100))
|
|
35
|
+
.filter((reason) => Boolean(reason));
|
|
36
|
+
return {
|
|
37
|
+
total: traces.length,
|
|
38
|
+
blocks: blocks.length,
|
|
39
|
+
warns,
|
|
40
|
+
allows,
|
|
41
|
+
top_blocked_tools: sortedCounts(blocks.map((trace) => trace.tool_name).filter((tool) => Boolean(tool)), 5, "tool"),
|
|
42
|
+
top_blocked_paths: sortedCounts(blocks.map((trace) => trace.target_path).filter((path) => Boolean(path)), 5, "path"),
|
|
43
|
+
top_block_reasons: sortedCounts(reasons, 5, "reason"),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function buildVerifierSummary(results) {
|
|
47
|
+
return {
|
|
48
|
+
total: results.length,
|
|
49
|
+
passes: results.filter((result) => result.status === "pass").length,
|
|
50
|
+
failures: results.filter((result) => result.status === "fail").length,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function readCompiledSourceIds(projectDir) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = await readFile(join(projectDir, ".dna", "compiled", "ir.json"), "utf-8");
|
|
56
|
+
const parsed = JSON.parse(raw);
|
|
57
|
+
const sourceIds = Array.isArray(parsed.source_dna_ids)
|
|
58
|
+
? parsed.source_dna_ids
|
|
59
|
+
: Array.isArray(parsed.ir?.source_dna_ids)
|
|
60
|
+
? parsed.ir.source_dna_ids
|
|
61
|
+
: [];
|
|
62
|
+
return sourceIds.filter((id) => typeof id === "string");
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function resolveDnaId(projectDir, explicitDnaId) {
|
|
69
|
+
if (explicitDnaId) {
|
|
70
|
+
try {
|
|
71
|
+
return { dnaId: safePathComponent(explicitDnaId, "dna_id"), source: "explicit" };
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return { source: "unresolved", error: "Invalid DNA ID." };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const sourceIds = await readCompiledSourceIds(projectDir);
|
|
78
|
+
const safeIds = [];
|
|
79
|
+
for (const id of sourceIds) {
|
|
80
|
+
try {
|
|
81
|
+
safeIds.push(safePathComponent(id, "dna_id"));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { source: "unresolved", error: "Invalid DNA ID." };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const uniqueIds = [...new Set(safeIds)];
|
|
88
|
+
if (uniqueIds.length === 1) {
|
|
89
|
+
return { dnaId: uniqueIds[0], source: "compiled_ir_single_source" };
|
|
90
|
+
}
|
|
91
|
+
if (uniqueIds.length > 1) {
|
|
92
|
+
return { source: "unresolved", error: "Multiple DNA IDs found; pass --dna-id." };
|
|
93
|
+
}
|
|
94
|
+
return { source: "unresolved", error: "Cannot derive DNA ID; run dna sync or pass --dna-id." };
|
|
95
|
+
}
|
|
96
|
+
async function loadMarkersReadOnly(projectDir, dnaId) {
|
|
97
|
+
try {
|
|
98
|
+
const safeDnaId = safePathComponent(dnaId, "dna_id");
|
|
99
|
+
const raw = await readFile(join(projectDir, ".dna", "evolution", "markers", `${safeDnaId}.json`), "utf-8");
|
|
100
|
+
const parsed = JSON.parse(raw);
|
|
101
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function buildMarkerPreview(projectDir, explicitDnaId, signals) {
|
|
108
|
+
const resolution = await resolveDnaId(projectDir, explicitDnaId);
|
|
109
|
+
if (!resolution.dnaId) {
|
|
110
|
+
const excluded = {};
|
|
111
|
+
for (const signal of signals)
|
|
112
|
+
excluded[signal.source_ref] = resolution.error ?? "DNA ID unresolved";
|
|
113
|
+
return {
|
|
114
|
+
preview_only: true,
|
|
115
|
+
dna_id_source: resolution.source,
|
|
116
|
+
changes: [],
|
|
117
|
+
excluded_signal_refs: signals.map((signal) => signal.source_ref),
|
|
118
|
+
exclusion_reasons: excluded,
|
|
119
|
+
error: resolution.error,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const { outcomes, excluded } = classifiedSignalsToOutcomes(signals, resolution.dnaId);
|
|
123
|
+
const existingMarkers = await loadMarkersReadOnly(projectDir, resolution.dnaId);
|
|
124
|
+
const changes = describeMarkerChanges(outcomes, existingMarkers);
|
|
125
|
+
const exclusionReasons = {};
|
|
126
|
+
for (const item of excluded)
|
|
127
|
+
exclusionReasons[item.source_ref] = item.reason;
|
|
128
|
+
return {
|
|
129
|
+
preview_only: true,
|
|
130
|
+
dna_id: resolution.dnaId,
|
|
131
|
+
dna_id_source: resolution.source,
|
|
132
|
+
changes,
|
|
133
|
+
excluded_signal_refs: excluded.map((item) => item.source_ref),
|
|
134
|
+
exclusion_reasons: exclusionReasons,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export async function buildKernelReport(options) {
|
|
138
|
+
const days = options.days ?? 7;
|
|
139
|
+
const traces = await readTraces(options.projectDir, days, options.sessionId);
|
|
140
|
+
const verifierResults = (await readVerifierResults(options.projectDir, options.sessionId))
|
|
141
|
+
.filter((result) => withinDays(result.timestamp, days));
|
|
142
|
+
const signals = classifyKernelSignals({ traces, verifierResults });
|
|
143
|
+
const classifications = Object.fromEntries(CLASSIFICATIONS.map((classification) => [classification, 0]));
|
|
144
|
+
for (const signal of signals)
|
|
145
|
+
classifications[signal.classification]++;
|
|
146
|
+
const traceSummary = buildTraceSummary(traces);
|
|
147
|
+
const verifierSummary = buildVerifierSummary(verifierResults);
|
|
148
|
+
const markerPreview = options.includeMarkerPreview
|
|
149
|
+
? await buildMarkerPreview(options.projectDir, options.dnaId, signals)
|
|
150
|
+
: undefined;
|
|
151
|
+
return {
|
|
152
|
+
schema_version: "intentdna.kernel_report.v1",
|
|
153
|
+
generated_at: new Date().toISOString(),
|
|
154
|
+
source: {
|
|
155
|
+
project_dir: options.projectDir,
|
|
156
|
+
session_id: options.sessionId,
|
|
157
|
+
dna_id: markerPreview?.dna_id ?? options.dnaId,
|
|
158
|
+
dna_id_source: markerPreview?.dna_id_source ?? (options.dnaId ? "explicit" : "unresolved"),
|
|
159
|
+
period_days: days,
|
|
160
|
+
},
|
|
161
|
+
summary: {
|
|
162
|
+
traces: traces.length,
|
|
163
|
+
verifier_results: verifierResults.length,
|
|
164
|
+
signals: signals.length,
|
|
165
|
+
marker_preview_changes: markerPreview?.changes.length,
|
|
166
|
+
},
|
|
167
|
+
classifications,
|
|
168
|
+
quality: {
|
|
169
|
+
quality_positive: signals.filter((signal) => signal.semantic === "quality-positive").length,
|
|
170
|
+
quality_negative: signals.filter((signal) => signal.semantic === "quality-negative").length,
|
|
171
|
+
},
|
|
172
|
+
enforcement: {
|
|
173
|
+
blocks: signals.filter((signal) => signal.semantic === "enforcement-block").length,
|
|
174
|
+
warns: signals.filter((signal) => signal.semantic === "enforcement-warn").length,
|
|
175
|
+
allows: signals.filter((signal) => signal.semantic === "enforcement-allow").length,
|
|
176
|
+
policy_denied_verifiers: signals.filter((signal) => signal.semantic === "policy-denied-verifier").length,
|
|
177
|
+
},
|
|
178
|
+
signals,
|
|
179
|
+
verifier_summary: verifierSummary,
|
|
180
|
+
trace_summary: traceSummary,
|
|
181
|
+
marker_preview: markerPreview,
|
|
182
|
+
mutation: {
|
|
183
|
+
command_class: options.includeMarkerPreview ? "preview-only" : "read-only",
|
|
184
|
+
wrote_state: false,
|
|
185
|
+
wrote_evolution: false,
|
|
186
|
+
persisted_outcomes: false,
|
|
187
|
+
mutated_markers: false,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export function formatKernelReport(report) {
|
|
192
|
+
const lines = [];
|
|
193
|
+
lines.push(`DNA Kernel Report (last ${report.source.period_days} day${report.source.period_days > 1 ? "s" : ""})`);
|
|
194
|
+
lines.push("=".repeat(50));
|
|
195
|
+
lines.push("");
|
|
196
|
+
lines.push(`Schema: ${report.schema_version}`);
|
|
197
|
+
lines.push(`Total events: ${report.trace_summary.total}`);
|
|
198
|
+
lines.push(` Allowed: ${report.trace_summary.allows}`);
|
|
199
|
+
lines.push(` Blocked: ${report.trace_summary.blocks}`);
|
|
200
|
+
lines.push(` Warned: ${report.trace_summary.warns}`);
|
|
201
|
+
lines.push("");
|
|
202
|
+
if (report.verifier_summary.total > 0) {
|
|
203
|
+
lines.push(`Verifier results: ${report.verifier_summary.total}`);
|
|
204
|
+
lines.push(` Passed: ${report.verifier_summary.passes}`);
|
|
205
|
+
lines.push(` Failed: ${report.verifier_summary.failures}`);
|
|
206
|
+
lines.push("");
|
|
207
|
+
}
|
|
208
|
+
lines.push(`Classified signals: ${report.summary.signals}`);
|
|
209
|
+
lines.push(` Positive: ${report.classifications.positive}`);
|
|
210
|
+
lines.push(` Negative: ${report.classifications.negative}`);
|
|
211
|
+
lines.push(` Friction: ${report.classifications.friction}`);
|
|
212
|
+
lines.push(` Protective: ${report.classifications.protective}`);
|
|
213
|
+
lines.push(` Neutral: ${report.classifications.neutral}`);
|
|
214
|
+
lines.push(` Unknown: ${report.classifications.unknown}`);
|
|
215
|
+
lines.push("");
|
|
216
|
+
if (report.trace_summary.top_blocked_tools.length > 0) {
|
|
217
|
+
lines.push("Top blocked tools:");
|
|
218
|
+
for (const item of report.trace_summary.top_blocked_tools)
|
|
219
|
+
lines.push(` ${item.tool}: ${item.count}`);
|
|
220
|
+
lines.push("");
|
|
221
|
+
}
|
|
222
|
+
if (report.trace_summary.top_blocked_paths.length > 0) {
|
|
223
|
+
lines.push("Top blocked paths:");
|
|
224
|
+
for (const item of report.trace_summary.top_blocked_paths)
|
|
225
|
+
lines.push(` ${item.path}: ${item.count}`);
|
|
226
|
+
lines.push("");
|
|
227
|
+
}
|
|
228
|
+
if (report.trace_summary.top_block_reasons.length > 0) {
|
|
229
|
+
lines.push("Top block reasons:");
|
|
230
|
+
for (const item of report.trace_summary.top_block_reasons)
|
|
231
|
+
lines.push(` \"${item.reason}\": ${item.count}`);
|
|
232
|
+
lines.push("");
|
|
233
|
+
}
|
|
234
|
+
if (report.marker_preview) {
|
|
235
|
+
lines.push("Marker preview:");
|
|
236
|
+
if (report.marker_preview.error) {
|
|
237
|
+
lines.push(` Error: ${report.marker_preview.error}`);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
lines.push(` DNA ID: ${report.marker_preview.dna_id}`);
|
|
241
|
+
lines.push(` Changes: ${report.marker_preview.changes.length}`);
|
|
242
|
+
for (const change of report.marker_preview.changes) {
|
|
243
|
+
const action = change.action ? ` → ${change.action} x${change.factor?.toFixed(2)}` : "";
|
|
244
|
+
lines.push(` ${change.type.toUpperCase()} ${change.gene}${action} (${change.feedback_count} feedbacks)`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
lines.push(` Excluded signals: ${report.marker_preview.excluded_signal_refs.length}`);
|
|
248
|
+
lines.push(" Preview only: no outcomes or markers were persisted.");
|
|
249
|
+
lines.push("");
|
|
250
|
+
}
|
|
251
|
+
lines.push("Mutation contract:");
|
|
252
|
+
lines.push(` Command class: ${report.mutation.command_class}`);
|
|
253
|
+
lines.push(" Wrote state: false");
|
|
254
|
+
lines.push(" Wrote evolution: false");
|
|
255
|
+
lines.push(" Persisted outcomes: false");
|
|
256
|
+
lines.push(" Mutated markers: false");
|
|
257
|
+
lines.push("");
|
|
258
|
+
return lines.join("\n");
|
|
259
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TraceEntry, VerifierResultEntry } from "../hooks/state.js";
|
|
2
|
+
import type { ExecutionOutcome } from "../evolution/types.js";
|
|
3
|
+
export type KernelSignalClassification = "protective" | "friction" | "positive" | "negative" | "neutral" | "unknown";
|
|
4
|
+
export type KernelEvolutionEffect = "helped" | "hindered" | "neutral";
|
|
5
|
+
export type KernelSignalSemantic = "quality-positive" | "quality-negative" | "enforcement-block" | "enforcement-warn" | "enforcement-allow" | "policy-denied-verifier" | "external";
|
|
6
|
+
export interface ClassifiedSignal {
|
|
7
|
+
id: string;
|
|
8
|
+
source_type: "trace" | "verifier" | "external";
|
|
9
|
+
source_ref: string;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
gene?: string;
|
|
12
|
+
semantic: KernelSignalSemantic;
|
|
13
|
+
classification: KernelSignalClassification;
|
|
14
|
+
classification_reason: string;
|
|
15
|
+
evolution_effect: KernelEvolutionEffect;
|
|
16
|
+
eligible_for_marker_preview: boolean;
|
|
17
|
+
evidence_ref?: string;
|
|
18
|
+
evidence_payload?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export interface KernelSignalContext {
|
|
21
|
+
friction_trace_ids?: string[];
|
|
22
|
+
friction_verifier_ids?: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface ClassifyKernelSignalsInput {
|
|
25
|
+
traces?: TraceEntry[];
|
|
26
|
+
verifierResults?: VerifierResultEntry[];
|
|
27
|
+
context?: KernelSignalContext;
|
|
28
|
+
}
|
|
29
|
+
export declare function classifyVerifierResult(result: VerifierResultEntry, context?: KernelSignalContext): ClassifiedSignal[];
|
|
30
|
+
export declare function classifyTraceEntry(trace: TraceEntry, context?: KernelSignalContext): ClassifiedSignal;
|
|
31
|
+
export declare function classifyKernelSignals(input: ClassifyKernelSignalsInput): ClassifiedSignal[];
|
|
32
|
+
export declare function classifiedSignalsToOutcomes(signals: ClassifiedSignal[], dnaId: string): {
|
|
33
|
+
outcomes: ExecutionOutcome[];
|
|
34
|
+
excluded: Array<{
|
|
35
|
+
source_ref: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
}>;
|
|
38
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
function extractGeneFromReason(reason) {
|
|
2
|
+
if (!reason)
|
|
3
|
+
return undefined;
|
|
4
|
+
const geneMatch = reason.match(/\(gene:\s*([A-Za-z0-9:_-]+)\)/);
|
|
5
|
+
if (geneMatch)
|
|
6
|
+
return geneMatch[1];
|
|
7
|
+
const roleMatch = reason.match(/Role '([A-Za-z0-9:_-]+)'/);
|
|
8
|
+
if (roleMatch)
|
|
9
|
+
return `scope:${roleMatch[1]}`;
|
|
10
|
+
const sourceMatch = reason.match(/source_gene:\s*([A-Za-z0-9:_-]+)/);
|
|
11
|
+
return sourceMatch?.[1];
|
|
12
|
+
}
|
|
13
|
+
function verifierSourceRef(result) {
|
|
14
|
+
return [
|
|
15
|
+
"verifier",
|
|
16
|
+
result.workflow ?? "unknown-workflow",
|
|
17
|
+
result.step_id ?? "unknown-step",
|
|
18
|
+
result.verifier_id,
|
|
19
|
+
result.timestamp,
|
|
20
|
+
].join(":");
|
|
21
|
+
}
|
|
22
|
+
function verifierEvidencePayload(result) {
|
|
23
|
+
const payload = {
|
|
24
|
+
verifier_id: result.verifier_id,
|
|
25
|
+
when: result.when,
|
|
26
|
+
severity: result.severity,
|
|
27
|
+
kind: result.kind,
|
|
28
|
+
workflow: result.workflow,
|
|
29
|
+
step_id: result.step_id,
|
|
30
|
+
target: result.target,
|
|
31
|
+
evidence: result.evidence,
|
|
32
|
+
exit_code: result.exit_code,
|
|
33
|
+
artifact: result.artifact,
|
|
34
|
+
message: result.message,
|
|
35
|
+
};
|
|
36
|
+
for (const key of Object.keys(payload)) {
|
|
37
|
+
if (payload[key] === undefined)
|
|
38
|
+
delete payload[key];
|
|
39
|
+
}
|
|
40
|
+
return Object.keys(payload).length > 0 ? payload : undefined;
|
|
41
|
+
}
|
|
42
|
+
function traceEvidencePayload(trace) {
|
|
43
|
+
const payload = {
|
|
44
|
+
trace_id: trace.trace_id,
|
|
45
|
+
event: trace.event,
|
|
46
|
+
tool_name: trace.tool_name,
|
|
47
|
+
agent_type: trace.agent_type,
|
|
48
|
+
workflow: trace.workflow,
|
|
49
|
+
step: trace.step,
|
|
50
|
+
decision: trace.decision,
|
|
51
|
+
reason: trace.reason,
|
|
52
|
+
target_path: trace.target_path,
|
|
53
|
+
duration_ms: trace.duration_ms,
|
|
54
|
+
};
|
|
55
|
+
for (const key of Object.keys(payload)) {
|
|
56
|
+
if (payload[key] === undefined)
|
|
57
|
+
delete payload[key];
|
|
58
|
+
}
|
|
59
|
+
return Object.keys(payload).length > 0 ? payload : undefined;
|
|
60
|
+
}
|
|
61
|
+
function isPolicyDeniedVerifier(result) {
|
|
62
|
+
return result.evidence === "policy_denied" || result.exit_code === 126;
|
|
63
|
+
}
|
|
64
|
+
export function classifyVerifierResult(result, context = {}) {
|
|
65
|
+
const genes = result.source_genes && result.source_genes.length > 0 ? result.source_genes : [undefined];
|
|
66
|
+
const sourceRef = verifierSourceRef(result);
|
|
67
|
+
const explicitFriction = context.friction_verifier_ids?.includes(result.verifier_id) ?? false;
|
|
68
|
+
return genes.map((gene) => {
|
|
69
|
+
if (isPolicyDeniedVerifier(result)) {
|
|
70
|
+
return {
|
|
71
|
+
id: `${sourceRef}:${gene ?? "unknown"}`,
|
|
72
|
+
source_type: "verifier",
|
|
73
|
+
source_ref: sourceRef,
|
|
74
|
+
timestamp: result.timestamp,
|
|
75
|
+
gene,
|
|
76
|
+
semantic: "policy-denied-verifier",
|
|
77
|
+
classification: explicitFriction ? "friction" : "unknown",
|
|
78
|
+
classification_reason: explicitFriction
|
|
79
|
+
? "policy-denied verifier explicitly classified as friction"
|
|
80
|
+
: "policy-denied verifier is raw evidence but not marker-preview evidence by default",
|
|
81
|
+
evolution_effect: explicitFriction ? "hindered" : "neutral",
|
|
82
|
+
eligible_for_marker_preview: Boolean(gene && explicitFriction),
|
|
83
|
+
evidence_ref: sourceRef,
|
|
84
|
+
evidence_payload: verifierEvidencePayload(result),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (result.status === "pass") {
|
|
88
|
+
return {
|
|
89
|
+
id: `${sourceRef}:${gene ?? "unknown"}`,
|
|
90
|
+
source_type: "verifier",
|
|
91
|
+
source_ref: sourceRef,
|
|
92
|
+
timestamp: result.timestamp,
|
|
93
|
+
gene,
|
|
94
|
+
semantic: "quality-positive",
|
|
95
|
+
classification: "positive",
|
|
96
|
+
classification_reason: "verifier pass is positive quality evidence",
|
|
97
|
+
evolution_effect: "helped",
|
|
98
|
+
eligible_for_marker_preview: Boolean(gene),
|
|
99
|
+
evidence_ref: sourceRef,
|
|
100
|
+
evidence_payload: verifierEvidencePayload(result),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
id: `${sourceRef}:${gene ?? "unknown"}`,
|
|
105
|
+
source_type: "verifier",
|
|
106
|
+
source_ref: sourceRef,
|
|
107
|
+
timestamp: result.timestamp,
|
|
108
|
+
gene,
|
|
109
|
+
semantic: "quality-negative",
|
|
110
|
+
classification: explicitFriction ? "friction" : "negative",
|
|
111
|
+
classification_reason: explicitFriction
|
|
112
|
+
? "verifier failure has explicit friction evidence"
|
|
113
|
+
: "verifier failure is negative quality evidence",
|
|
114
|
+
evolution_effect: "hindered",
|
|
115
|
+
eligible_for_marker_preview: Boolean(gene),
|
|
116
|
+
evidence_ref: sourceRef,
|
|
117
|
+
evidence_payload: verifierEvidencePayload(result),
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
export function classifyTraceEntry(trace, context = {}) {
|
|
122
|
+
const gene = extractGeneFromReason(trace.reason);
|
|
123
|
+
const sourceRef = `trace:${trace.trace_id}`;
|
|
124
|
+
const explicitFriction = context.friction_trace_ids?.includes(trace.trace_id) ?? false;
|
|
125
|
+
if (trace.decision === "allow") {
|
|
126
|
+
return {
|
|
127
|
+
id: sourceRef,
|
|
128
|
+
source_type: "trace",
|
|
129
|
+
source_ref: sourceRef,
|
|
130
|
+
timestamp: trace.timestamp,
|
|
131
|
+
gene,
|
|
132
|
+
semantic: "enforcement-allow",
|
|
133
|
+
classification: "neutral",
|
|
134
|
+
classification_reason: "enforcement allow is report-visible but not quality evidence",
|
|
135
|
+
evolution_effect: "neutral",
|
|
136
|
+
eligible_for_marker_preview: false,
|
|
137
|
+
evidence_ref: sourceRef,
|
|
138
|
+
evidence_payload: traceEvidencePayload(trace),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (trace.decision === "warn") {
|
|
142
|
+
return {
|
|
143
|
+
id: sourceRef,
|
|
144
|
+
source_type: "trace",
|
|
145
|
+
source_ref: sourceRef,
|
|
146
|
+
timestamp: trace.timestamp,
|
|
147
|
+
gene,
|
|
148
|
+
semantic: "enforcement-warn",
|
|
149
|
+
classification: "neutral",
|
|
150
|
+
classification_reason: "enforcement warn is report-visible but not marker-preview evidence by default",
|
|
151
|
+
evolution_effect: "neutral",
|
|
152
|
+
eligible_for_marker_preview: false,
|
|
153
|
+
evidence_ref: sourceRef,
|
|
154
|
+
evidence_payload: traceEvidencePayload(trace),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
id: sourceRef,
|
|
159
|
+
source_type: "trace",
|
|
160
|
+
source_ref: sourceRef,
|
|
161
|
+
timestamp: trace.timestamp,
|
|
162
|
+
gene,
|
|
163
|
+
semantic: "enforcement-block",
|
|
164
|
+
classification: explicitFriction ? "friction" : "protective",
|
|
165
|
+
classification_reason: explicitFriction
|
|
166
|
+
? "enforcement block has explicit friction evidence"
|
|
167
|
+
: "enforcement block is protective by default and excluded from marker preview",
|
|
168
|
+
evolution_effect: explicitFriction ? "hindered" : "neutral",
|
|
169
|
+
eligible_for_marker_preview: Boolean(gene && explicitFriction),
|
|
170
|
+
evidence_ref: sourceRef,
|
|
171
|
+
evidence_payload: traceEvidencePayload(trace),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
export function classifyKernelSignals(input) {
|
|
175
|
+
const traces = input.traces ?? [];
|
|
176
|
+
const verifierResults = input.verifierResults ?? [];
|
|
177
|
+
return [
|
|
178
|
+
...traces.map((trace) => classifyTraceEntry(trace, input.context)),
|
|
179
|
+
...verifierResults.flatMap((result) => classifyVerifierResult(result, input.context)),
|
|
180
|
+
].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
181
|
+
}
|
|
182
|
+
export function classifiedSignalsToOutcomes(signals, dnaId) {
|
|
183
|
+
const feedbackByGene = new Map();
|
|
184
|
+
const excluded = [];
|
|
185
|
+
for (const signal of signals) {
|
|
186
|
+
if (!signal.eligible_for_marker_preview) {
|
|
187
|
+
excluded.push({
|
|
188
|
+
source_ref: signal.source_ref,
|
|
189
|
+
reason: signal.classification_reason,
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (!signal.gene) {
|
|
194
|
+
excluded.push({ source_ref: signal.source_ref, reason: "signal has no gene" });
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (signal.classification === "protective" || signal.classification === "unknown") {
|
|
198
|
+
excluded.push({
|
|
199
|
+
source_ref: signal.source_ref,
|
|
200
|
+
reason: `${signal.classification} signals are not marker-preview evidence`,
|
|
201
|
+
});
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const existing = feedbackByGene.get(signal.gene) ?? [];
|
|
205
|
+
existing.push({
|
|
206
|
+
gene: signal.gene,
|
|
207
|
+
effect: signal.evolution_effect,
|
|
208
|
+
detail: signal.classification_reason,
|
|
209
|
+
});
|
|
210
|
+
feedbackByGene.set(signal.gene, existing);
|
|
211
|
+
}
|
|
212
|
+
const outcomes = [];
|
|
213
|
+
for (const [gene, feedback] of feedbackByGene) {
|
|
214
|
+
const helped = feedback.filter((item) => item.effect === "helped").length;
|
|
215
|
+
const hindered = feedback.filter((item) => item.effect === "hindered").length;
|
|
216
|
+
outcomes.push({
|
|
217
|
+
id: `kernel_signal_${gene}_${feedback.length}`,
|
|
218
|
+
timestamp: new Date().toISOString(),
|
|
219
|
+
dna_id: dnaId,
|
|
220
|
+
action: `kernel_signals:${gene} helped=${helped} hindered=${hindered}`,
|
|
221
|
+
active_genes: [gene],
|
|
222
|
+
constraints_applied: [gene],
|
|
223
|
+
success: helped >= hindered,
|
|
224
|
+
constraint_feedback: feedback,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return { outcomes, excluded };
|
|
228
|
+
}
|