intentdna 1.6.5 → 1.7.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/cli/commands/compile.js +2 -47
- package/dist/cli/commands/context.d.ts +8 -0
- package/dist/cli/commands/context.js +63 -0
- package/dist/cli/commands/feedback.d.ts +1 -0
- package/dist/cli/commands/feedback.js +11 -0
- package/dist/cli/commands/init.js +11 -63
- package/dist/cli/commands/run.js +5 -4
- package/dist/cli/commands/show.js +2 -38
- package/dist/cli/commands/sync.d.ts +9 -6
- package/dist/cli/commands/sync.js +186 -184
- package/dist/cli/commands/templates.d.ts +10 -1
- package/dist/cli/commands/templates.js +50 -1
- package/dist/cli/commands/validate.js +15 -9
- package/dist/cli/commands/verify.js +97 -25
- package/dist/cli/index.js +76 -11
- package/dist/compiler/cascade.d.ts +3 -1
- package/dist/compiler/cascade.js +51 -0
- package/dist/compiler/compile.js +37 -0
- package/dist/compiler/diagnostics.d.ts +17 -0
- package/dist/compiler/diagnostics.js +30 -0
- package/dist/compiler/index.d.ts +3 -0
- package/dist/compiler/index.js +8 -11
- package/dist/compiler/input-resolver.d.ts +25 -0
- package/dist/compiler/input-resolver.js +175 -0
- package/dist/hooks/cli.d.ts +10 -1
- package/dist/hooks/cli.js +37 -22
- package/dist/hooks/state.d.ts +2 -0
- package/dist/hooks/state.js +23 -2
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-compile.js +18 -49
- package/dist/mcp/tools-context.d.ts +2 -0
- package/dist/mcp/tools-context.js +85 -0
- package/dist/mcp/tools-enforce.d.ts +2 -2
- package/dist/mcp/tools-enforce.js +19 -49
- package/dist/mcp/tools-observability.js +24 -0
- package/dist/report/kernel-signals.js +3 -0
- package/dist/report/report-package.d.ts +56 -0
- package/dist/report/report-package.js +85 -0
- package/dist/runtime/agent-md.d.ts +1 -0
- package/dist/runtime/agent-md.js +21 -3
- package/dist/runtime/context-sources.d.ts +14 -0
- package/dist/runtime/context-sources.js +60 -0
- package/dist/runtime/skill-adapter.d.ts +32 -4
- package/dist/runtime/skill-adapter.js +184 -9
- package/dist/runtime/workflow-runner.d.ts +1 -1
- package/dist/runtime/workflow-runner.js +1 -1
- package/dist/schema/types.d.ts +33 -0
- package/dist/schema/validate.js +156 -2
- package/dist/schema/validators/controllers.js +16 -0
- package/dist/signals/index.d.ts +10 -0
- package/dist/signals/index.js +90 -5
- package/dist/templates/catalog.d.ts +19 -0
- package/dist/templates/catalog.js +57 -0
- package/dist/templates/flutter-rewrite.dna.yaml +2 -2
- package/package.json +1 -1
- package/spec/foundation-hardening.md +2 -1
|
@@ -7,45 +7,8 @@
|
|
|
7
7
|
* - dna_sync: Run full sync pipeline (compile + inject + hooks)
|
|
8
8
|
*/
|
|
9
9
|
import { resolve } from "node:path";
|
|
10
|
-
import {
|
|
11
|
-
import { loadDNA, compileFromFiles } from "../compiler/index.js";
|
|
10
|
+
import { compileFromFiles, detectDNAConfigs, expandDNAInputFiles, formatDiagnostic, loadDNAWithDiagnostics } from "../compiler/index.js";
|
|
12
11
|
import { textResult, errorResult } from "./server.js";
|
|
13
|
-
async function fileExists(path) {
|
|
14
|
-
try {
|
|
15
|
-
await stat(path);
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
/** Auto-detect DNA config files in the project directory. */
|
|
23
|
-
async function detectConfigs(projectDir) {
|
|
24
|
-
const { readdir } = await import("node:fs/promises");
|
|
25
|
-
// Multi-config: .dna/configs/*.yaml
|
|
26
|
-
const configsDir = resolve(projectDir, ".dna", "configs");
|
|
27
|
-
try {
|
|
28
|
-
const files = await readdir(configsDir);
|
|
29
|
-
const yamls = files
|
|
30
|
-
.filter(f => f.endsWith(".yaml") || f.endsWith(".yml"))
|
|
31
|
-
.sort()
|
|
32
|
-
.map(f => resolve(configsDir, f));
|
|
33
|
-
if (yamls.length > 0)
|
|
34
|
-
return yamls;
|
|
35
|
-
}
|
|
36
|
-
catch { /* dir doesn't exist */ }
|
|
37
|
-
// Legacy single-config
|
|
38
|
-
const candidates = [
|
|
39
|
-
".dna/config.yaml", ".dna/config.yml", ".dna/config.json",
|
|
40
|
-
".dna.yaml", ".dna.yml", ".dna.json",
|
|
41
|
-
];
|
|
42
|
-
for (const name of candidates) {
|
|
43
|
-
const path = resolve(projectDir, name);
|
|
44
|
-
if (await fileExists(path))
|
|
45
|
-
return [path];
|
|
46
|
-
}
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
12
|
export function createCompileTools(projectDir) {
|
|
50
13
|
return [
|
|
51
14
|
// ── dna_validate ─────────────────────────────────────
|
|
@@ -62,20 +25,23 @@ export function createCompileTools(projectDir) {
|
|
|
62
25
|
const configPath = typeof args.config_path === "string"
|
|
63
26
|
? resolve(projectDir, args.config_path)
|
|
64
27
|
: undefined;
|
|
65
|
-
const files = configPath ? [configPath] : await
|
|
28
|
+
const files = configPath ? [configPath] : await detectDNAConfigs(projectDir);
|
|
66
29
|
if (files.length === 0)
|
|
67
30
|
return errorResult("No DNA config files found.");
|
|
68
31
|
const results = [];
|
|
69
32
|
let allValid = true;
|
|
70
33
|
for (const file of files) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
34
|
+
const result = await loadDNAWithDiagnostics(file);
|
|
35
|
+
const errors = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
36
|
+
if (errors.length > 0 || !result.dna) {
|
|
37
|
+
allValid = false;
|
|
38
|
+
results.push(`${file}: INVALID`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
74
41
|
results.push(`${file}: valid`);
|
|
75
42
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
results.push(`${file}: INVALID — ${err instanceof Error ? err.message : String(err)}`);
|
|
43
|
+
for (const diagnostic of result.diagnostics) {
|
|
44
|
+
results.push(` ${formatDiagnostic(diagnostic)}`);
|
|
79
45
|
}
|
|
80
46
|
}
|
|
81
47
|
results.unshift(allValid ? "All configs valid." : "Validation errors found.");
|
|
@@ -98,11 +64,12 @@ export function createCompileTools(projectDir) {
|
|
|
98
64
|
const configPath = typeof args.config_path === "string"
|
|
99
65
|
? resolve(projectDir, args.config_path)
|
|
100
66
|
: undefined;
|
|
101
|
-
const files = configPath ? [configPath] : await
|
|
67
|
+
const files = configPath ? [configPath] : await detectDNAConfigs(projectDir);
|
|
102
68
|
if (files.length === 0)
|
|
103
69
|
return errorResult("No DNA config files found.");
|
|
104
70
|
try {
|
|
105
|
-
const
|
|
71
|
+
const expandedFiles = await expandDNAInputFiles(files);
|
|
72
|
+
const ir = await compileFromFiles(expandedFiles, {
|
|
106
73
|
context: typeof args.context === "string" ? args.context : undefined,
|
|
107
74
|
role: typeof args.role === "string" ? args.role : undefined,
|
|
108
75
|
});
|
|
@@ -142,12 +109,13 @@ export function createCompileTools(projectDir) {
|
|
|
142
109
|
evolve: { type: "boolean", description: "Run evolution engine before compiling (default false)" },
|
|
143
110
|
context: { type: "string", description: "Active context name" },
|
|
144
111
|
role: { type: "string", description: "Active role name" },
|
|
112
|
+
mode: { type: "string", enum: ["auto", "plugin", "bin"], description: "Sync mode (default auto)" },
|
|
145
113
|
},
|
|
146
114
|
},
|
|
147
115
|
handler: async (args) => {
|
|
148
116
|
// Import sync command dynamically to avoid circular deps
|
|
149
|
-
const { runSync
|
|
150
|
-
const files = await
|
|
117
|
+
const { runSync } = await import("../cli/commands/sync.js");
|
|
118
|
+
const files = await detectDNAConfigs(projectDir);
|
|
151
119
|
if (files.length === 0)
|
|
152
120
|
return errorResult("No DNA config files found.");
|
|
153
121
|
const exitCode = await runSync({
|
|
@@ -160,6 +128,7 @@ export function createCompileTools(projectDir) {
|
|
|
160
128
|
skillsDir: resolve(projectDir, ".claude", "skills"),
|
|
161
129
|
settingsPath: resolve(projectDir, ".claude", "settings.json"),
|
|
162
130
|
remove: false,
|
|
131
|
+
mode: typeof args.mode === "string" && ["auto", "plugin", "bin"].includes(args.mode) ? args.mode : "auto",
|
|
163
132
|
});
|
|
164
133
|
if (exitCode === 0) {
|
|
165
134
|
return textResult("Sync completed successfully.");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { findContextSource, formatContextSource, loadContextSourceIndex, readContextSourceDocument, searchContextSources, } from "../runtime/context-sources.js";
|
|
3
|
+
import { errorResult, textResult } from "./server.js";
|
|
4
|
+
function filesArg(args, projectDir) {
|
|
5
|
+
if (typeof args.config_path === "string")
|
|
6
|
+
return [resolve(projectDir, args.config_path)];
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
export function createContextTools(projectDir) {
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
name: "dna_context_sources",
|
|
13
|
+
description: "List explicitly declared planning-only advisory context sources",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
handler: async (args) => {
|
|
21
|
+
const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
|
|
22
|
+
if (index.sources.length === 0)
|
|
23
|
+
return textResult("No advisory context sources declared.");
|
|
24
|
+
const lines = index.sources.map(formatContextSource);
|
|
25
|
+
if (index.planning_instructions.length > 0) {
|
|
26
|
+
lines.push("", "Planning instructions:");
|
|
27
|
+
for (const instruction of index.planning_instructions)
|
|
28
|
+
lines.push(`- ${instruction}`);
|
|
29
|
+
}
|
|
30
|
+
return textResult(lines.join("\n"));
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "dna_context_search",
|
|
35
|
+
description: "Search advisory context source metadata. This does not search repository files.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
query: { type: "string", description: "Metadata query" },
|
|
40
|
+
config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
|
|
41
|
+
},
|
|
42
|
+
required: ["query"],
|
|
43
|
+
},
|
|
44
|
+
handler: async (args) => {
|
|
45
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
46
|
+
const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
|
|
47
|
+
const matches = searchContextSources(index, query);
|
|
48
|
+
if (matches.length === 0)
|
|
49
|
+
return errorResult(`No advisory context sources matched '${query}'.`);
|
|
50
|
+
return textResult(matches.map(formatContextSource).join("\n"));
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "dna_get_context",
|
|
55
|
+
description: "Read a declared local advisory context document by source id",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
id: { type: "string", description: "Context source id" },
|
|
60
|
+
config_path: { type: "string", description: "Path to DNA config (auto-detected if omitted)" },
|
|
61
|
+
},
|
|
62
|
+
required: ["id"],
|
|
63
|
+
},
|
|
64
|
+
handler: async (args) => {
|
|
65
|
+
const id = typeof args.id === "string" ? args.id : "";
|
|
66
|
+
const index = await loadContextSourceIndex(projectDir, filesArg(args, projectDir));
|
|
67
|
+
const source = findContextSource(index, id);
|
|
68
|
+
if (!source)
|
|
69
|
+
return errorResult(`Unknown advisory context source '${id}'.`);
|
|
70
|
+
const doc = await readContextSourceDocument(projectDir, source);
|
|
71
|
+
const lines = [
|
|
72
|
+
formatContextSource(source),
|
|
73
|
+
"Planning-only advisory context; does not satisfy enforcement, handoff, or ArtifactManifest requirements.",
|
|
74
|
+
];
|
|
75
|
+
if (doc.content !== undefined) {
|
|
76
|
+
lines.push("", "---", doc.content);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
lines.push("No local document body is available for this source type.");
|
|
80
|
+
}
|
|
81
|
+
return textResult(lines.join("\n"));
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Intent DNA — MCP Enforce Tools (U2)
|
|
3
3
|
*
|
|
4
|
-
* Provides enforcement via MCP server with in-memory IR cache.
|
|
5
|
-
*
|
|
4
|
+
* Provides enforcement via a persistent MCP server with in-memory IR cache.
|
|
5
|
+
* MCP calls reuse the full hook runtime without reloading IR on every request.
|
|
6
6
|
*
|
|
7
7
|
* Tools:
|
|
8
8
|
* - dna_enforce: Run enforcement for a hook event (PreToolUse, PostToolUse, etc.)
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Intent DNA — MCP Enforce Tools (U2)
|
|
3
3
|
*
|
|
4
|
-
* Provides enforcement via MCP server with in-memory IR cache.
|
|
5
|
-
*
|
|
4
|
+
* Provides enforcement via a persistent MCP server with in-memory IR cache.
|
|
5
|
+
* MCP calls reuse the full hook runtime without reloading IR on every request.
|
|
6
6
|
*
|
|
7
7
|
* Tools:
|
|
8
8
|
* - dna_enforce: Run enforcement for a hook event (PreToolUse, PostToolUse, etc.)
|
|
9
9
|
*/
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
11
|
import { stat, readFile } from "node:fs/promises";
|
|
12
|
-
import {
|
|
12
|
+
import { silentOutput } from "../hooks/protocol.js";
|
|
13
13
|
import { hookEventsForSurface } from "../hooks/event-registry.js";
|
|
14
|
+
import { validateHookInput } from "../hooks/schema.js";
|
|
15
|
+
import { runHookEvent } from "../hooks/cli.js";
|
|
14
16
|
import { textResult, errorResult } from "./server.js";
|
|
15
17
|
// ── IR Cache ────────────────────────────────────────────────
|
|
16
18
|
/** In-memory IR cache with mtime-based invalidation. */
|
|
@@ -63,49 +65,17 @@ export class IRCache {
|
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
const VALID_EVENTS = new Set(hookEventsForSurface("mcp"));
|
|
66
|
-
function dispatchEnforce(event, ir, input, roles) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
cwd: typeof input.cwd === "string" ? input.cwd : undefined,
|
|
78
|
-
}, undefined, roles);
|
|
79
|
-
case "PostToolUse":
|
|
80
|
-
return enforcePostToolUse(ir, {
|
|
81
|
-
tool_name: String(input.tool_name ?? ""),
|
|
82
|
-
tool_input: (input.tool_input ?? {}),
|
|
83
|
-
tool_output: typeof input.tool_output === "string" ? input.tool_output : undefined,
|
|
84
|
-
agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
|
|
85
|
-
});
|
|
86
|
-
case "UserPromptSubmit":
|
|
87
|
-
return enforceUserPromptSubmit(ir, {
|
|
88
|
-
prompt: typeof input.prompt === "string" ? input.prompt : undefined,
|
|
89
|
-
});
|
|
90
|
-
case "SubagentStop":
|
|
91
|
-
return enforceSubagentStop(ir, {
|
|
92
|
-
agent_name: typeof input.agent_name === "string" ? input.agent_name : undefined,
|
|
93
|
-
agent_type: typeof input.agent_type === "string" ? input.agent_type : undefined,
|
|
94
|
-
});
|
|
95
|
-
case "PreCompact":
|
|
96
|
-
return enforcePreCompact(ir);
|
|
97
|
-
case "Notification":
|
|
98
|
-
return enforceNotification(ir, {
|
|
99
|
-
title: typeof input.title === "string" ? input.title : undefined,
|
|
100
|
-
message: typeof input.message === "string" ? input.message : undefined,
|
|
101
|
-
});
|
|
102
|
-
case "SessionStart":
|
|
103
|
-
return enforceSessionStart(ir, {
|
|
104
|
-
cwd: typeof input.cwd === "string" ? input.cwd : undefined,
|
|
105
|
-
sessionId: typeof input.session_id === "string" ? input.session_id : undefined,
|
|
106
|
-
});
|
|
107
|
-
default:
|
|
108
|
-
return null;
|
|
68
|
+
async function dispatchEnforce(event, ir, input, projectDir, roles) {
|
|
69
|
+
try {
|
|
70
|
+
const validation = validateHookInput(event, input);
|
|
71
|
+
if (!validation.valid)
|
|
72
|
+
return silentOutput();
|
|
73
|
+
const rawInput = validation.normalized;
|
|
74
|
+
const sessionId = typeof rawInput.sessionId === "string" ? rawInput.sessionId : undefined;
|
|
75
|
+
return await runHookEvent({ event, ir, rawInput, projectDir, sessionId, roles });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return silentOutput();
|
|
109
79
|
}
|
|
110
80
|
}
|
|
111
81
|
// ── Tool Registration ───────────────────────────────────────
|
|
@@ -114,8 +84,8 @@ export function createEnforceTools(projectDir) {
|
|
|
114
84
|
return [
|
|
115
85
|
{
|
|
116
86
|
name: "dna_enforce",
|
|
117
|
-
description: "Run DNA enforcement for a hook event
|
|
118
|
-
"
|
|
87
|
+
description: "Run DNA enforcement for a hook event inside the persistent MCP server. " +
|
|
88
|
+
"Uses cached IR and the same runtime path as the dna-hook binary.",
|
|
119
89
|
inputSchema: {
|
|
120
90
|
type: "object",
|
|
121
91
|
properties: {
|
|
@@ -141,7 +111,7 @@ export function createEnforceTools(projectDir) {
|
|
|
141
111
|
return errorResult("No compiled IR found. Run `dna sync` to compile your DNA config.");
|
|
142
112
|
}
|
|
143
113
|
const input = (args.input ?? {});
|
|
144
|
-
const result = dispatchEnforce(event, cached.ir, input, cached.roles);
|
|
114
|
+
const result = await dispatchEnforce(event, cached.ir, input, projectDir, cached.roles);
|
|
145
115
|
// Format as human-readable text
|
|
146
116
|
const lines = [];
|
|
147
117
|
lines.push(`Decision: ${result.continue ? "allow" : "BLOCK"}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { textResult } from "./server.js";
|
|
2
2
|
import { buildKernelReport, formatKernelReport } from "../report/kernel-report.js";
|
|
3
|
+
import { buildReportPackage } from "../report/report-package.js";
|
|
3
4
|
export function createObservabilityTools(projectDir) {
|
|
4
5
|
return [
|
|
5
6
|
{
|
|
@@ -30,5 +31,28 @@ export function createObservabilityTools(projectDir) {
|
|
|
30
31
|
"\nMCP observability only: this tool does not execute verifiers, does not enforce hook parity, and does not mutate state or markers.\n");
|
|
31
32
|
},
|
|
32
33
|
},
|
|
34
|
+
{
|
|
35
|
+
name: "dna_report_package",
|
|
36
|
+
description: "Read-only local evidence package with traces, verifier results, handoffs, diagnostics, deferrals, and fact-backed critical decisions.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
days: { type: "number", description: "Days to look back (default 7)" },
|
|
41
|
+
session_id: { type: "string", description: "Session ID filter" },
|
|
42
|
+
dna_id: { type: "string", description: "DNA ID for marker preview; derived from compiled IR if omitted and unambiguous" },
|
|
43
|
+
include_marker_preview: { type: "boolean", description: "Include preview-only marker changes (default false)" },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
handler: async (args) => {
|
|
47
|
+
const reportPackage = await buildReportPackage({
|
|
48
|
+
projectDir,
|
|
49
|
+
days: typeof args.days === "number" ? args.days : 7,
|
|
50
|
+
sessionId: typeof args.session_id === "string" ? args.session_id : undefined,
|
|
51
|
+
dnaId: typeof args.dna_id === "string" ? args.dna_id : undefined,
|
|
52
|
+
includeMarkerPreview: typeof args.include_marker_preview === "boolean" ? args.include_marker_preview : false,
|
|
53
|
+
});
|
|
54
|
+
return textResult(JSON.stringify(reportPackage, null, 2));
|
|
55
|
+
},
|
|
56
|
+
},
|
|
33
57
|
];
|
|
34
58
|
}
|
|
@@ -11,6 +11,8 @@ function extractGeneFromReason(reason) {
|
|
|
11
11
|
return sourceMatch?.[1];
|
|
12
12
|
}
|
|
13
13
|
function verifierSourceRef(result) {
|
|
14
|
+
if (result.result_id)
|
|
15
|
+
return `verifier:${result.result_id}`;
|
|
14
16
|
return [
|
|
15
17
|
"verifier",
|
|
16
18
|
result.workflow ?? "unknown-workflow",
|
|
@@ -21,6 +23,7 @@ function verifierSourceRef(result) {
|
|
|
21
23
|
}
|
|
22
24
|
function verifierEvidencePayload(result) {
|
|
23
25
|
const payload = {
|
|
26
|
+
result_id: result.result_id,
|
|
24
27
|
verifier_id: result.verifier_id,
|
|
25
28
|
when: result.when,
|
|
26
29
|
severity: result.severity,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DNAWorkflowState, TraceEntry, VerifierResultEntry } from "../hooks/state.js";
|
|
2
|
+
import type { CompletedArtifactEntry } from "../schema/types.js";
|
|
3
|
+
import { type KernelReport, type KernelReportOptions } from "./kernel-report.js";
|
|
4
|
+
export type EvidenceRef = {
|
|
5
|
+
type: "trace";
|
|
6
|
+
trace_id: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: "verifier_result";
|
|
9
|
+
result_id: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "artifact";
|
|
12
|
+
artifact_id: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
};
|
|
15
|
+
export interface CriticalDecision {
|
|
16
|
+
decision_id: string;
|
|
17
|
+
kind: "block" | "warn" | "verifier_failure" | "handoff_gate";
|
|
18
|
+
summary: string;
|
|
19
|
+
refs: EvidenceRef[];
|
|
20
|
+
}
|
|
21
|
+
export interface ReportPackage {
|
|
22
|
+
schema_version: "intentdna.report_package.v1";
|
|
23
|
+
generated_at: string;
|
|
24
|
+
source: KernelReport["source"];
|
|
25
|
+
kernel_report: KernelReport;
|
|
26
|
+
traces: TraceEntry[];
|
|
27
|
+
verifier_results: VerifierResultEntry[];
|
|
28
|
+
handoffs: {
|
|
29
|
+
workflow_state?: DNAWorkflowState;
|
|
30
|
+
completed_artifacts: CompletedArtifactEntry[];
|
|
31
|
+
artifact_refs: Array<{
|
|
32
|
+
artifact_id: string;
|
|
33
|
+
path: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
37
|
+
diagnostics: Array<{
|
|
38
|
+
severity: "error" | "warning" | "info";
|
|
39
|
+
message: string;
|
|
40
|
+
code?: string;
|
|
41
|
+
}>;
|
|
42
|
+
deferrals: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
refs: EvidenceRef[];
|
|
46
|
+
}>;
|
|
47
|
+
critical_decisions: CriticalDecision[];
|
|
48
|
+
mutation: {
|
|
49
|
+
command_class: "read-only" | "preview-only";
|
|
50
|
+
wrote_state: false;
|
|
51
|
+
wrote_evolution: false;
|
|
52
|
+
persisted_outcomes: false;
|
|
53
|
+
mutated_markers: false;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export declare function buildReportPackage(options: KernelReportOptions): Promise<ReportPackage>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readTraces, readVerifierResults, readWorkflowState } from "../hooks/state.js";
|
|
2
|
+
import { buildKernelReport } from "./kernel-report.js";
|
|
3
|
+
function withinDays(timestamp, days) {
|
|
4
|
+
const parsed = Date.parse(timestamp);
|
|
5
|
+
if (Number.isNaN(parsed))
|
|
6
|
+
return true;
|
|
7
|
+
return parsed >= Date.now() - days * 24 * 60 * 60 * 1000;
|
|
8
|
+
}
|
|
9
|
+
function artifactRefs(completed) {
|
|
10
|
+
const refs = [];
|
|
11
|
+
for (const entry of completed) {
|
|
12
|
+
for (const artifact of entry.artifacts) {
|
|
13
|
+
refs.push({
|
|
14
|
+
artifact_id: artifact.artifact_id ?? `${entry.step_id}:${artifact.type}:${artifact.path}`,
|
|
15
|
+
path: artifact.path,
|
|
16
|
+
metadata: artifact.metadata,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return refs;
|
|
21
|
+
}
|
|
22
|
+
function buildCriticalDecisions(traces, verifierResults, completed) {
|
|
23
|
+
const decisions = [];
|
|
24
|
+
for (const trace of traces) {
|
|
25
|
+
if (trace.decision !== "block" && trace.decision !== "warn")
|
|
26
|
+
continue;
|
|
27
|
+
decisions.push({
|
|
28
|
+
decision_id: `${trace.decision}:${trace.trace_id}`,
|
|
29
|
+
kind: trace.decision,
|
|
30
|
+
summary: trace.reason?.split("\n")[0] ?? `${trace.decision} decision`,
|
|
31
|
+
refs: [{ type: "trace", trace_id: trace.trace_id }],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
for (const result of verifierResults) {
|
|
35
|
+
if (result.status !== "fail")
|
|
36
|
+
continue;
|
|
37
|
+
decisions.push({
|
|
38
|
+
decision_id: `verifier_failure:${result.result_id ?? result.verifier_id}`,
|
|
39
|
+
kind: "verifier_failure",
|
|
40
|
+
summary: result.message ?? `Verifier failed: ${result.verifier_id}`,
|
|
41
|
+
refs: [{ type: "verifier_result", result_id: result.result_id ?? result.verifier_id }],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
for (const ref of artifactRefs(completed)) {
|
|
45
|
+
decisions.push({
|
|
46
|
+
decision_id: `handoff_gate:${ref.artifact_id}`,
|
|
47
|
+
kind: "handoff_gate",
|
|
48
|
+
summary: `Completed handoff artifact ${ref.artifact_id}`,
|
|
49
|
+
refs: [{ type: "artifact", artifact_id: ref.artifact_id, path: ref.path }],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return decisions;
|
|
53
|
+
}
|
|
54
|
+
export async function buildReportPackage(options) {
|
|
55
|
+
const days = options.days ?? 7;
|
|
56
|
+
const kernelReport = await buildKernelReport(options);
|
|
57
|
+
const traces = await readTraces(options.projectDir, days, options.sessionId);
|
|
58
|
+
const verifierResults = (await readVerifierResults(options.projectDir, options.sessionId))
|
|
59
|
+
.filter((result) => withinDays(result.timestamp, days));
|
|
60
|
+
const workflowState = await readWorkflowState(options.projectDir, options.sessionId, 0) ?? undefined;
|
|
61
|
+
const completedArtifacts = workflowState?.completed_artifacts ?? [];
|
|
62
|
+
return {
|
|
63
|
+
schema_version: "intentdna.report_package.v1",
|
|
64
|
+
generated_at: new Date().toISOString(),
|
|
65
|
+
source: kernelReport.source,
|
|
66
|
+
kernel_report: kernelReport,
|
|
67
|
+
traces,
|
|
68
|
+
verifier_results: verifierResults,
|
|
69
|
+
handoffs: {
|
|
70
|
+
workflow_state: workflowState,
|
|
71
|
+
completed_artifacts: completedArtifacts,
|
|
72
|
+
artifact_refs: artifactRefs(completedArtifacts),
|
|
73
|
+
},
|
|
74
|
+
diagnostics: [],
|
|
75
|
+
deferrals: [],
|
|
76
|
+
critical_decisions: buildCriticalDecisions(traces, verifierResults, completedArtifacts),
|
|
77
|
+
mutation: {
|
|
78
|
+
command_class: options.includeMarkerPreview ? "preview-only" : "read-only",
|
|
79
|
+
wrote_state: false,
|
|
80
|
+
wrote_evolution: false,
|
|
81
|
+
persisted_outcomes: false,
|
|
82
|
+
mutated_markers: false,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -31,6 +31,7 @@ export interface AgentMDResult {
|
|
|
31
31
|
* Convert camelCase or snake_case to kebab-case.
|
|
32
32
|
*/
|
|
33
33
|
export declare function toKebabCase(name: string): string;
|
|
34
|
+
export declare function assertSafeGeneratedName(name: string, label?: string): void;
|
|
34
35
|
/**
|
|
35
36
|
* Compile a single RoleDef into an agent MD file.
|
|
36
37
|
* Pure function, no side effects.
|
package/dist/runtime/agent-md.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* Output directory: .claude/agents/<name>.md
|
|
10
10
|
*/
|
|
11
11
|
import { mkdir, writeFile, readFile, readdir, rm } from "node:fs/promises";
|
|
12
|
-
import { join } from "node:path";
|
|
12
|
+
import { join, resolve, sep } from "node:path";
|
|
13
13
|
const SENTINEL_COMMENT = "<!-- intentdna:managed — do not edit manually -->";
|
|
14
|
+
const SAFE_GENERATED_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
14
15
|
// ── Helpers ────────────────────────────────────────────────
|
|
15
16
|
/**
|
|
16
17
|
* Convert camelCase or snake_case to kebab-case.
|
|
@@ -23,12 +24,26 @@ export function toKebabCase(name) {
|
|
|
23
24
|
.replace(/[_\s]+/g, "-")
|
|
24
25
|
.toLowerCase();
|
|
25
26
|
}
|
|
27
|
+
export function assertSafeGeneratedName(name, label = "generated name") {
|
|
28
|
+
if (!SAFE_GENERATED_NAME.test(name)) {
|
|
29
|
+
throw new Error(`${label} '${name}' must contain only lowercase letters, digits, and single hyphens`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function resolveContainedPath(baseDir, ...segments) {
|
|
33
|
+
const base = resolve(baseDir);
|
|
34
|
+
const target = resolve(baseDir, ...segments);
|
|
35
|
+
if (target !== base && !target.startsWith(`${base}${sep}`)) {
|
|
36
|
+
throw new Error(`generated path escapes output directory: ${target}`);
|
|
37
|
+
}
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
26
40
|
/**
|
|
27
41
|
* Render YAML frontmatter string (without --- delimiters).
|
|
28
42
|
*/
|
|
29
43
|
function renderFrontmatter(name, role, options) {
|
|
30
44
|
const prefix = options?.namePrefix ?? "dna-";
|
|
31
45
|
const fullName = `${prefix}${toKebabCase(name)}`;
|
|
46
|
+
assertSafeGeneratedName(fullName, "agent name");
|
|
32
47
|
const lines = [];
|
|
33
48
|
lines.push(`name: ${fullName}`);
|
|
34
49
|
lines.push(`description: "${escapeYamlString(role.description)}"`);
|
|
@@ -65,6 +80,7 @@ function renderConstraints(ir) {
|
|
|
65
80
|
function renderBody(name, role, ir, options) {
|
|
66
81
|
const prefix = options?.namePrefix ?? "dna-";
|
|
67
82
|
const fullName = `${prefix}${toKebabCase(name)}`;
|
|
83
|
+
assertSafeGeneratedName(fullName, "agent name");
|
|
68
84
|
const compiledAt = new Date().toISOString();
|
|
69
85
|
const includeDNA = options?.includeDNAGuidelines ?? true;
|
|
70
86
|
const sections = [];
|
|
@@ -184,7 +200,9 @@ function renderBody(name, role, ir, options) {
|
|
|
184
200
|
export function compileRoleToAgentMD(roleName, role, ir, options) {
|
|
185
201
|
const prefix = options?.namePrefix ?? "dna-";
|
|
186
202
|
const kebabName = toKebabCase(roleName);
|
|
187
|
-
const
|
|
203
|
+
const fullName = `${prefix}${kebabName}`;
|
|
204
|
+
assertSafeGeneratedName(fullName, "agent name");
|
|
205
|
+
const fileName = `${fullName}.md`;
|
|
188
206
|
const frontmatter = renderFrontmatter(roleName, role, options);
|
|
189
207
|
const body = renderBody(roleName, role, ir, options);
|
|
190
208
|
const content = `---\n${frontmatter}\n---\n\n${body}`;
|
|
@@ -207,7 +225,7 @@ export async function writeAgentMDFiles(results, outputDir) {
|
|
|
207
225
|
await mkdir(outputDir, { recursive: true });
|
|
208
226
|
const written = [];
|
|
209
227
|
for (const result of results) {
|
|
210
|
-
const path =
|
|
228
|
+
const path = resolveContainedPath(outputDir, result.fileName);
|
|
211
229
|
await writeFile(path, result.content, "utf-8");
|
|
212
230
|
written.push(path);
|
|
213
231
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CompiledAdvisoryContextSource } from "../schema/types.js";
|
|
2
|
+
export interface ContextSourceDocument {
|
|
3
|
+
source: CompiledAdvisoryContextSource;
|
|
4
|
+
content?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ContextSourceIndex {
|
|
7
|
+
sources: CompiledAdvisoryContextSource[];
|
|
8
|
+
planning_instructions: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function loadContextSourceIndex(projectDir: string, files?: string[]): Promise<ContextSourceIndex>;
|
|
11
|
+
export declare function searchContextSources(index: ContextSourceIndex, query: string): CompiledAdvisoryContextSource[];
|
|
12
|
+
export declare function findContextSource(index: ContextSourceIndex, id: string): CompiledAdvisoryContextSource | undefined;
|
|
13
|
+
export declare function readContextSourceDocument(projectDir: string, source: CompiledAdvisoryContextSource): Promise<ContextSourceDocument>;
|
|
14
|
+
export declare function formatContextSource(source: CompiledAdvisoryContextSource): string;
|