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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class DNADiagnosticsError extends Error {
|
|
2
|
+
diagnostics;
|
|
3
|
+
constructor(message, diagnostics) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "DNADiagnosticsError";
|
|
6
|
+
this.diagnostics = diagnostics;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function hasDiagnosticErrors(diagnostics) {
|
|
10
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
11
|
+
}
|
|
12
|
+
export function formatDiagnostic(diagnostic) {
|
|
13
|
+
const location = [diagnostic.file, diagnostic.path].filter(Boolean).join(":");
|
|
14
|
+
const prefix = location ? `${location}: ` : "";
|
|
15
|
+
const hint = diagnostic.hint ? ` (${diagnostic.hint})` : "";
|
|
16
|
+
return `${diagnostic.severity.toUpperCase()} ${diagnostic.code}: ${prefix}${diagnostic.message}${hint}`;
|
|
17
|
+
}
|
|
18
|
+
export function formatDiagnostics(diagnostics) {
|
|
19
|
+
return diagnostics.map(formatDiagnostic).join("\n");
|
|
20
|
+
}
|
|
21
|
+
export function diagnosticsFromError(error, file) {
|
|
22
|
+
if (error instanceof DNADiagnosticsError)
|
|
23
|
+
return error.diagnostics;
|
|
24
|
+
return [{
|
|
25
|
+
severity: "error",
|
|
26
|
+
code: "unexpected_error",
|
|
27
|
+
message: error instanceof Error ? error.message : String(error),
|
|
28
|
+
file,
|
|
29
|
+
}];
|
|
30
|
+
}
|
package/dist/compiler/index.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export { activateDNA } from "./activate.js";
|
|
|
10
10
|
export { compileDNA } from "./compile.js";
|
|
11
11
|
export { compileWorkflow } from "./workflow.js";
|
|
12
12
|
export type { CompileWorkflowOptions, CompileWorkflowResult, CompileWorkflowError, } from "./workflow.js";
|
|
13
|
+
export type { Diagnostic, DiagnosticSeverity } from "./diagnostics.js";
|
|
14
|
+
export { DNADiagnosticsError, formatDiagnostic, formatDiagnostics } from "./diagnostics.js";
|
|
15
|
+
export { assertNoNamespaceCollisions, checkNamespaceCollisions, detectDNAConfigs, expandDNAInputFiles, loadDNAWithDiagnostics, resolveDNAInputs, resolveSpeciesReference, } from "./input-resolver.js";
|
|
13
16
|
/**
|
|
14
17
|
* Load and parse a DNA file from disk.
|
|
15
18
|
*/
|
package/dist/compiler/index.js
CHANGED
|
@@ -4,29 +4,26 @@
|
|
|
4
4
|
* Full compilation pipeline:
|
|
5
5
|
* DNA files → parse → cascade → activate (+ epigenetic) → compile → IR
|
|
6
6
|
*/
|
|
7
|
-
import { readFile } from "node:fs/promises";
|
|
8
|
-
import { validateDNA } from "../schema/validate.js";
|
|
9
|
-
import { parseYAML } from "../schema/yaml-parser.js";
|
|
10
7
|
import { cascadeDNA } from "./cascade.js";
|
|
11
8
|
import { activateDNA } from "./activate.js";
|
|
12
9
|
import { compileDNA } from "./compile.js";
|
|
10
|
+
import { DNADiagnosticsError, formatDiagnostics } from "./diagnostics.js";
|
|
11
|
+
import { loadDNAWithDiagnostics } from "./input-resolver.js";
|
|
13
12
|
export { cascadeDNA } from "./cascade.js";
|
|
14
13
|
export { activateDNA } from "./activate.js";
|
|
15
14
|
export { compileDNA } from "./compile.js";
|
|
16
15
|
export { compileWorkflow } from "./workflow.js";
|
|
16
|
+
export { DNADiagnosticsError, formatDiagnostic, formatDiagnostics } from "./diagnostics.js";
|
|
17
|
+
export { assertNoNamespaceCollisions, checkNamespaceCollisions, detectDNAConfigs, expandDNAInputFiles, loadDNAWithDiagnostics, resolveDNAInputs, resolveSpeciesReference, } from "./input-resolver.js";
|
|
17
18
|
/**
|
|
18
19
|
* Load and parse a DNA file from disk.
|
|
19
20
|
*/
|
|
20
21
|
export async function loadDNA(filePath) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const result = validateDNA(dna);
|
|
25
|
-
if (!result.valid) {
|
|
26
|
-
const messages = result.errors.map((e) => ` ${e.path}: ${e.message}`).join("\n");
|
|
27
|
-
throw new Error(`Invalid DNA file ${filePath}:\n${messages}`);
|
|
22
|
+
const result = await loadDNAWithDiagnostics(filePath);
|
|
23
|
+
if (!result.dna) {
|
|
24
|
+
throw new DNADiagnosticsError(`Invalid DNA file ${filePath}:\n${formatDiagnostics(result.diagnostics)}`, result.diagnostics);
|
|
28
25
|
}
|
|
29
|
-
return dna;
|
|
26
|
+
return result.dna;
|
|
30
27
|
}
|
|
31
28
|
/**
|
|
32
29
|
* Full compilation pipeline: load DNA files, cascade, activate context, compile to IR.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IntentDNA } from "../schema/types.js";
|
|
2
|
+
import type { Diagnostic } from "./diagnostics.js";
|
|
3
|
+
export interface ResolveDNAInputOptions {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
includeDiagnostics?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ResolvedDNAInputs {
|
|
8
|
+
files: string[];
|
|
9
|
+
dnas: IntentDNA[];
|
|
10
|
+
diagnostics: Diagnostic[];
|
|
11
|
+
}
|
|
12
|
+
export declare function parseDNAFile(filePath: string): Promise<IntentDNA>;
|
|
13
|
+
export declare function resolveSpeciesReference(ref: string): string | null;
|
|
14
|
+
export declare function detectDNAConfigs(projectDir?: string): Promise<string[]>;
|
|
15
|
+
export interface ExpandDNAInputOptions {
|
|
16
|
+
cwd?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function expandDNAInputFiles(files: string[], options?: ExpandDNAInputOptions): Promise<string[]>;
|
|
19
|
+
export declare function checkNamespaceCollisions(configPaths: string[]): Promise<Diagnostic[]>;
|
|
20
|
+
export declare function assertNoNamespaceCollisions(configPaths: string[]): Promise<void>;
|
|
21
|
+
export declare function loadDNAWithDiagnostics(filePath: string): Promise<{
|
|
22
|
+
dna?: IntentDNA;
|
|
23
|
+
diagnostics: Diagnostic[];
|
|
24
|
+
}>;
|
|
25
|
+
export declare function resolveDNAInputs(files: string[], options?: ResolveDNAInputOptions): Promise<ResolvedDNAInputs>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { validateDNA } from "../schema/validate.js";
|
|
5
|
+
import { parseYAML } from "../schema/yaml-parser.js";
|
|
6
|
+
import { DNADiagnosticsError, hasDiagnosticErrors } from "./diagnostics.js";
|
|
7
|
+
const DNA_CONFIG_CANDIDATES = [
|
|
8
|
+
".dna/config.yaml",
|
|
9
|
+
".dna/config.yml",
|
|
10
|
+
".dna/config.json",
|
|
11
|
+
".dna.yaml",
|
|
12
|
+
".dna.yml",
|
|
13
|
+
".dna.json",
|
|
14
|
+
];
|
|
15
|
+
async function fileExists(path) {
|
|
16
|
+
try {
|
|
17
|
+
await stat(path);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function parseDNAContent(filePath, raw) {
|
|
25
|
+
const isYAML = filePath.endsWith(".yaml") || filePath.endsWith(".yml");
|
|
26
|
+
return (isYAML ? parseYAML(raw) : JSON.parse(raw));
|
|
27
|
+
}
|
|
28
|
+
export async function parseDNAFile(filePath) {
|
|
29
|
+
const raw = await readFile(filePath, "utf-8");
|
|
30
|
+
return parseDNAContent(filePath, raw);
|
|
31
|
+
}
|
|
32
|
+
export function resolveSpeciesReference(ref) {
|
|
33
|
+
if (!ref.startsWith("species:"))
|
|
34
|
+
return null;
|
|
35
|
+
const name = ref.slice("species:".length);
|
|
36
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
return resolve(thisDir, "..", "species", `${name}.dna.json`);
|
|
38
|
+
}
|
|
39
|
+
export async function detectDNAConfigs(projectDir = process.cwd()) {
|
|
40
|
+
const configsDir = resolve(projectDir, ".dna", "configs");
|
|
41
|
+
try {
|
|
42
|
+
const files = await readdir(configsDir);
|
|
43
|
+
const configs = files
|
|
44
|
+
.filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"))
|
|
45
|
+
.sort()
|
|
46
|
+
.map((file) => resolve(configsDir, file));
|
|
47
|
+
if (configs.length > 0)
|
|
48
|
+
return configs;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Directory does not exist.
|
|
52
|
+
}
|
|
53
|
+
for (const candidate of DNA_CONFIG_CANDIDATES) {
|
|
54
|
+
const path = resolve(projectDir, candidate);
|
|
55
|
+
if (await fileExists(path))
|
|
56
|
+
return [path];
|
|
57
|
+
}
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
export async function expandDNAInputFiles(files, options) {
|
|
61
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
62
|
+
const expanded = [];
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
const add = (path, position) => {
|
|
65
|
+
if (seen.has(path))
|
|
66
|
+
return;
|
|
67
|
+
seen.add(path);
|
|
68
|
+
if (position === "front")
|
|
69
|
+
expanded.unshift(path);
|
|
70
|
+
else
|
|
71
|
+
expanded.push(path);
|
|
72
|
+
};
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
const abs = resolve(cwd, file);
|
|
75
|
+
add(abs, "back");
|
|
76
|
+
try {
|
|
77
|
+
const dna = await parseDNAFile(abs);
|
|
78
|
+
for (const ref of dna.cascade?.inherits ?? []) {
|
|
79
|
+
const speciesPath = resolveSpeciesReference(ref);
|
|
80
|
+
if (speciesPath)
|
|
81
|
+
add(speciesPath, "front");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Validation/loading reports the concrete parse error later.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return expanded;
|
|
89
|
+
}
|
|
90
|
+
export async function checkNamespaceCollisions(configPaths) {
|
|
91
|
+
const diagnostics = [];
|
|
92
|
+
const namespaces = new Map();
|
|
93
|
+
for (const configPath of configPaths) {
|
|
94
|
+
try {
|
|
95
|
+
const data = await parseDNAFile(configPath);
|
|
96
|
+
const namespace = typeof data.namespace === "string" ? data.namespace : undefined;
|
|
97
|
+
if (!namespace)
|
|
98
|
+
continue;
|
|
99
|
+
const previous = namespaces.get(namespace);
|
|
100
|
+
if (previous) {
|
|
101
|
+
diagnostics.push({
|
|
102
|
+
severity: "error",
|
|
103
|
+
code: "namespace_conflict",
|
|
104
|
+
message: `Namespace "${namespace}" conflict: ${previous} and ${configPath}. Each template must have a unique namespace.`,
|
|
105
|
+
file: configPath,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
namespaces.set(namespace, configPath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Parse/validation errors are reported by the load step.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return diagnostics;
|
|
117
|
+
}
|
|
118
|
+
export async function assertNoNamespaceCollisions(configPaths) {
|
|
119
|
+
const diagnostics = await checkNamespaceCollisions(configPaths);
|
|
120
|
+
if (diagnostics.length > 0) {
|
|
121
|
+
throw new DNADiagnosticsError(diagnostics[0].message, diagnostics);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export async function loadDNAWithDiagnostics(filePath) {
|
|
125
|
+
const diagnostics = [];
|
|
126
|
+
try {
|
|
127
|
+
const dna = await parseDNAFile(filePath);
|
|
128
|
+
const result = validateDNA(dna);
|
|
129
|
+
for (const warning of result.warnings ?? []) {
|
|
130
|
+
diagnostics.push({
|
|
131
|
+
severity: "warning",
|
|
132
|
+
code: "schema_warning",
|
|
133
|
+
message: warning.message,
|
|
134
|
+
file: filePath,
|
|
135
|
+
path: warning.path,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
for (const error of result.errors) {
|
|
139
|
+
diagnostics.push({
|
|
140
|
+
severity: "error",
|
|
141
|
+
code: "schema_error",
|
|
142
|
+
message: error.message,
|
|
143
|
+
file: filePath,
|
|
144
|
+
path: error.path,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return { dna: result.valid ? dna : undefined, diagnostics };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
diagnostics.push({
|
|
151
|
+
severity: "error",
|
|
152
|
+
code: "parse_error",
|
|
153
|
+
message: error instanceof Error ? error.message : String(error),
|
|
154
|
+
file: filePath,
|
|
155
|
+
});
|
|
156
|
+
return { diagnostics };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export async function resolveDNAInputs(files, options) {
|
|
160
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
161
|
+
const inputFiles = files.length > 0 ? files : await detectDNAConfigs(cwd);
|
|
162
|
+
const expandedFiles = await expandDNAInputFiles(inputFiles, { cwd });
|
|
163
|
+
const diagnostics = await checkNamespaceCollisions(expandedFiles);
|
|
164
|
+
const dnas = [];
|
|
165
|
+
for (const file of expandedFiles) {
|
|
166
|
+
const result = await loadDNAWithDiagnostics(file);
|
|
167
|
+
diagnostics.push(...result.diagnostics);
|
|
168
|
+
if (result.dna)
|
|
169
|
+
dnas.push(result.dna);
|
|
170
|
+
}
|
|
171
|
+
if (hasDiagnosticErrors(diagnostics)) {
|
|
172
|
+
throw new DNADiagnosticsError("DNA input resolution failed", diagnostics);
|
|
173
|
+
}
|
|
174
|
+
return { files: expandedFiles, dnas, diagnostics };
|
|
175
|
+
}
|
package/dist/hooks/cli.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Fail-open: all errors → { continue: true, suppressOutput: true }
|
|
16
16
|
*/
|
|
17
|
-
import type { ArtifactFact, ConstraintIR, VerifierSpec } from "../schema/types.js";
|
|
17
|
+
import type { ArtifactFact, ConstraintIR, RoleDef, VerifierSpec } from "../schema/types.js";
|
|
18
18
|
import type { HookEvent, HookOutput } from "./protocol.js";
|
|
19
19
|
import { blockOutput } from "./protocol.js";
|
|
20
20
|
import { readWorkflowState } from "./state.js";
|
|
@@ -39,6 +39,15 @@ export declare function computeSummary(traces: Array<{
|
|
|
39
39
|
reason?: string;
|
|
40
40
|
}>): SessionSummary;
|
|
41
41
|
export declare function formatSummary(s: SessionSummary): string | null;
|
|
42
|
+
export interface RunHookEventOptions {
|
|
43
|
+
event: HookEvent;
|
|
44
|
+
ir: ConstraintIR;
|
|
45
|
+
rawInput: Record<string, unknown>;
|
|
46
|
+
projectDir: string;
|
|
47
|
+
sessionId?: string;
|
|
48
|
+
roles?: Record<string, RoleDef>;
|
|
49
|
+
}
|
|
50
|
+
export declare function runHookEvent(options: RunHookEventOptions): Promise<HookOutput>;
|
|
42
51
|
declare function recordProducedArtifacts(projectDir: string, ir: ConstraintIR, wfState: {
|
|
43
52
|
workflow: string;
|
|
44
53
|
current_step: string;
|
package/dist/hooks/cli.js
CHANGED
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
* Fail-open: all errors → { continue: true, suppressOutput: true }
|
|
16
16
|
*/
|
|
17
17
|
import { mkdir, readFile, realpath, stat, unlink, writeFile } from "node:fs/promises";
|
|
18
|
+
import { realpathSync } from "node:fs";
|
|
18
19
|
import { spawn } from "node:child_process";
|
|
19
20
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
20
21
|
import { randomUUID } from "node:crypto";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
21
23
|
import { readStdin, writeOutput, silentOutput, allowOutput, blockOutput, stopOutput } from "./protocol.js";
|
|
22
24
|
import { validateHookInput } from "./schema.js";
|
|
23
25
|
import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, extractBashWritePaths, checkReflectionLimit, checkContextReadiness, checkWorkflowBoundary, } from "./enforce.js";
|
|
@@ -101,14 +103,18 @@ async function main() {
|
|
|
101
103
|
writeOutput(silentOutput());
|
|
102
104
|
return;
|
|
103
105
|
}
|
|
106
|
+
const output = await runHookEvent({ event, ir, rawInput, projectDir, sessionId });
|
|
107
|
+
writeOutput(output);
|
|
108
|
+
}
|
|
109
|
+
export async function runHookEvent(options) {
|
|
110
|
+
const { event, ir, rawInput, projectDir, sessionId, roles } = options;
|
|
104
111
|
const state = {};
|
|
105
112
|
let wfStateRaw = null;
|
|
106
113
|
// Load workflow state for events that need it (handoff context + PreCompact preservation)
|
|
107
114
|
if (event === "PreToolUse" || event === "PostToolUse" || event === "PreCompact") {
|
|
108
115
|
const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
|
|
109
116
|
if ("output" in workflowState) {
|
|
110
|
-
|
|
111
|
-
return;
|
|
117
|
+
return workflowState.output;
|
|
112
118
|
}
|
|
113
119
|
wfStateRaw = workflowState.state;
|
|
114
120
|
if (wfStateRaw && wfStateRaw.active) {
|
|
@@ -121,9 +127,8 @@ async function main() {
|
|
|
121
127
|
};
|
|
122
128
|
const artifactFacts = await resolveWorkflowArtifactFactsForHook(projectDir, ir, wfStateRaw, event, sessionId);
|
|
123
129
|
if ("output" in artifactFacts) {
|
|
124
|
-
writeOutput(artifactFacts.output);
|
|
125
130
|
appendArtifactResolverTrace(projectDir, event, wfStateRaw, artifactFacts.output, sessionId);
|
|
126
|
-
return;
|
|
131
|
+
return artifactFacts.output;
|
|
127
132
|
}
|
|
128
133
|
state.artifactFacts = artifactFacts.facts;
|
|
129
134
|
}
|
|
@@ -139,7 +144,6 @@ async function main() {
|
|
|
139
144
|
if (event === "PreToolUse") {
|
|
140
145
|
const gateResult = await handlePreToolGates(ir, rawInput, wfStateRaw, projectDir, sessionId);
|
|
141
146
|
if (gateResult) {
|
|
142
|
-
writeOutput(gateResult.output);
|
|
143
147
|
appendTrace(projectDir, {
|
|
144
148
|
trace_id: randomUUID(),
|
|
145
149
|
event,
|
|
@@ -152,24 +156,22 @@ async function main() {
|
|
|
152
156
|
duration_ms: 0,
|
|
153
157
|
timestamp: new Date().toISOString(),
|
|
154
158
|
}, sessionId).catch(() => { });
|
|
155
|
-
return;
|
|
159
|
+
return gateResult.output;
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
// Special handling for Stop — needs async workflow state read + session summary
|
|
159
163
|
if (event === "Stop") {
|
|
160
164
|
const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
|
|
161
165
|
if ("output" in workflowState) {
|
|
162
|
-
|
|
163
|
-
return;
|
|
166
|
+
return workflowState.output;
|
|
164
167
|
}
|
|
165
168
|
const wfState = workflowState.state;
|
|
166
169
|
let stopArtifactFacts = [];
|
|
167
170
|
if (wfState?.active) {
|
|
168
171
|
const artifactFacts = await finalizeAndResolveArtifactsForHook(projectDir, ir, wfState, event, sessionId);
|
|
169
172
|
if ("output" in artifactFacts) {
|
|
170
|
-
writeOutput(artifactFacts.output);
|
|
171
173
|
appendArtifactResolverTrace(projectDir, event, wfState, artifactFacts.output, sessionId);
|
|
172
|
-
return;
|
|
174
|
+
return artifactFacts.output;
|
|
173
175
|
}
|
|
174
176
|
stopArtifactFacts = artifactFacts.facts;
|
|
175
177
|
}
|
|
@@ -217,7 +219,6 @@ async function main() {
|
|
|
217
219
|
}
|
|
218
220
|
}
|
|
219
221
|
catch { /* fail-open */ }
|
|
220
|
-
writeOutput(stopOutput);
|
|
221
222
|
// Trace for Stop
|
|
222
223
|
appendTrace(projectDir, {
|
|
223
224
|
trace_id: randomUUID(),
|
|
@@ -228,11 +229,11 @@ async function main() {
|
|
|
228
229
|
duration_ms: 0,
|
|
229
230
|
timestamp: new Date().toISOString(),
|
|
230
231
|
}, sessionId).catch(() => { });
|
|
231
|
-
return;
|
|
232
|
+
return stopOutput;
|
|
232
233
|
}
|
|
233
234
|
// Dispatch to enforcement engine with timing
|
|
234
235
|
const start = Date.now();
|
|
235
|
-
let result = dispatch(event, ir, rawInput, state);
|
|
236
|
+
let result = dispatch(event, ir, rawInput, state, roles);
|
|
236
237
|
let output = result?.output ?? silentOutput();
|
|
237
238
|
const durationMs = Date.now() - start;
|
|
238
239
|
// PostToolUse side effects: session read tracking + surgeon reflection gate.
|
|
@@ -304,7 +305,6 @@ async function main() {
|
|
|
304
305
|
}
|
|
305
306
|
catch { /* fail-open: pattern detection never blocks */ }
|
|
306
307
|
}
|
|
307
|
-
writeOutput(output);
|
|
308
308
|
// Trace logging (async, fail-open)
|
|
309
309
|
const decision = output.continue === false ? "block"
|
|
310
310
|
: output.hookSpecificOutput?.additionalContext?.startsWith("WARN") ? "warn"
|
|
@@ -339,9 +339,10 @@ async function main() {
|
|
|
339
339
|
session_id: sessionId,
|
|
340
340
|
}).catch(() => { }); // Fail-open
|
|
341
341
|
}
|
|
342
|
+
return output;
|
|
342
343
|
}
|
|
343
344
|
// ── Dispatch ───────────────────────────────────────────────
|
|
344
|
-
function dispatch(event, ir, input, state) {
|
|
345
|
+
function dispatch(event, ir, input, state, roles) {
|
|
345
346
|
switch (event) {
|
|
346
347
|
case "PreToolUse":
|
|
347
348
|
return enforcePreToolUse(ir, {
|
|
@@ -351,7 +352,7 @@ function dispatch(event, ir, input, state) {
|
|
|
351
352
|
cwd: typeof input.cwd === "string" ? input.cwd : undefined,
|
|
352
353
|
sessionId: typeof input.session_id === "string" ? input.session_id
|
|
353
354
|
: typeof input.sessionId === "string" ? input.sessionId : undefined,
|
|
354
|
-
}, state);
|
|
355
|
+
}, state, roles);
|
|
355
356
|
case "PostToolUse":
|
|
356
357
|
return enforcePostToolUse(ir, {
|
|
357
358
|
tool_name: String(input.tool_name ?? ""),
|
|
@@ -574,7 +575,7 @@ async function finalizeAndResolveArtifacts(projectDir, ir, wfState, sessionId) {
|
|
|
574
575
|
}
|
|
575
576
|
function artifactResolverErrorOutput(event, error) {
|
|
576
577
|
const detail = error instanceof Error ? error.message : String(error);
|
|
577
|
-
return blockOutput(`[Intent DNA] ${event} artifact resolver failed: ${detail}
|
|
578
|
+
return blockOutput(`[Intent DNA] ${event} artifact resolver failed: ${detail}. Fix the workflow/session identifiers in DNA state, then rerun the hook or \`dna sync\`.`);
|
|
578
579
|
}
|
|
579
580
|
async function readWorkflowStateForHook(projectDir, event, sessionId) {
|
|
580
581
|
try {
|
|
@@ -689,7 +690,8 @@ export function appendStopVerifierWarnings(output, verifierResults, currentStep)
|
|
|
689
690
|
if (warningVerifierFailures.length === 0)
|
|
690
691
|
return output;
|
|
691
692
|
const warningText = `[Intent DNA] Verifier warnings at step '${currentStep ?? "unknown"}':\n` +
|
|
692
|
-
warningVerifierFailures.map((result) => ` - ${result.message ?? result.verifier_id}`).join("\n")
|
|
693
|
+
warningVerifierFailures.map((result) => ` - ${result.message ?? result.verifier_id}`).join("\n") +
|
|
694
|
+
"\nFix the warning above before relying on this step, or rerun the verifier after repair.";
|
|
693
695
|
return appendOutputText(output, warningText, "Stop");
|
|
694
696
|
}
|
|
695
697
|
function trimEvidence(raw) {
|
|
@@ -1521,7 +1523,20 @@ function toKebabCase(s) {
|
|
|
1521
1523
|
.toLowerCase();
|
|
1522
1524
|
}
|
|
1523
1525
|
// ── Entry Point ────────────────────────────────────────────
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1526
|
+
function isDirectEntryPoint() {
|
|
1527
|
+
const entry = process.argv[1];
|
|
1528
|
+
if (!entry)
|
|
1529
|
+
return false;
|
|
1530
|
+
try {
|
|
1531
|
+
return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
|
|
1532
|
+
}
|
|
1533
|
+
catch {
|
|
1534
|
+
return resolve(entry) === resolve(fileURLToPath(import.meta.url));
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (isDirectEntryPoint()) {
|
|
1538
|
+
main().catch(() => {
|
|
1539
|
+
// Fail-open: never block Claude Code on unexpected errors
|
|
1540
|
+
writeOutput(silentOutput());
|
|
1541
|
+
});
|
|
1542
|
+
}
|
package/dist/hooks/state.d.ts
CHANGED
|
@@ -136,6 +136,7 @@ export declare function writeSessionReads(projectDir: string, state: SessionRead
|
|
|
136
136
|
*/
|
|
137
137
|
export declare function appendSessionRead(projectDir: string, filePath: string, sessionId?: string): Promise<void>;
|
|
138
138
|
export interface VerifierResultEntry {
|
|
139
|
+
result_id?: string;
|
|
139
140
|
verifier_id: string;
|
|
140
141
|
when: VerifierWhen;
|
|
141
142
|
severity: VerifierSeverity;
|
|
@@ -151,6 +152,7 @@ export interface VerifierResultEntry {
|
|
|
151
152
|
message?: string;
|
|
152
153
|
timestamp: string;
|
|
153
154
|
}
|
|
155
|
+
export declare function verifierResultId(result: Omit<VerifierResultEntry, "result_id"> | VerifierResultEntry): string;
|
|
154
156
|
export declare function readVerifierResults(projectDir: string, sessionId?: string): Promise<VerifierResultEntry[]>;
|
|
155
157
|
export declare function writeVerifierResults(projectDir: string, results: VerifierResultEntry[], sessionId?: string): Promise<void>;
|
|
156
158
|
export declare function appendVerifierResult(projectDir: string, result: VerifierResultEntry, sessionId?: string): Promise<void>;
|
package/dist/hooks/state.js
CHANGED
|
@@ -330,13 +330,34 @@ export async function appendSessionRead(projectDir, filePath, sessionId) {
|
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
332
|
const VERIFIER_RESULTS_FILE = "workflow/verifier-results.json";
|
|
333
|
+
export function verifierResultId(result) {
|
|
334
|
+
return "vr_" + createHash("sha256")
|
|
335
|
+
.update(JSON.stringify({
|
|
336
|
+
verifier_id: result.verifier_id,
|
|
337
|
+
when: result.when,
|
|
338
|
+
severity: result.severity,
|
|
339
|
+
kind: result.kind,
|
|
340
|
+
workflow: result.workflow ?? "",
|
|
341
|
+
step_id: result.step_id ?? "",
|
|
342
|
+
status: result.status,
|
|
343
|
+
target: result.target ?? "",
|
|
344
|
+
evidence: result.evidence ?? "",
|
|
345
|
+
artifact: result.artifact ?? "",
|
|
346
|
+
timestamp: result.timestamp,
|
|
347
|
+
}))
|
|
348
|
+
.digest("hex")
|
|
349
|
+
.slice(0, 16);
|
|
350
|
+
}
|
|
351
|
+
function normalizeVerifierResult(result) {
|
|
352
|
+
return result.result_id ? result : { ...result, result_id: verifierResultId(result) };
|
|
353
|
+
}
|
|
333
354
|
async function readVerifierResultsFile(projectDir, sessionId) {
|
|
334
355
|
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
335
356
|
const filePath = join(stateDir, VERIFIER_RESULTS_FILE);
|
|
336
357
|
try {
|
|
337
358
|
const raw = await readFile(filePath, "utf-8");
|
|
338
359
|
const parsed = JSON.parse(raw);
|
|
339
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
360
|
+
return Array.isArray(parsed) ? parsed.map(normalizeVerifierResult) : [];
|
|
340
361
|
}
|
|
341
362
|
catch {
|
|
342
363
|
return [];
|
|
@@ -381,7 +402,7 @@ export async function writeVerifierResults(projectDir, results, sessionId) {
|
|
|
381
402
|
}
|
|
382
403
|
export async function appendVerifierResult(projectDir, result, sessionId) {
|
|
383
404
|
const results = await readVerifierResultsFile(projectDir, sessionId);
|
|
384
|
-
results.push(result);
|
|
405
|
+
results.push(normalizeVerifierResult(result));
|
|
385
406
|
await writeVerifierResults(projectDir, results, sessionId);
|
|
386
407
|
}
|
|
387
408
|
const TRACE_DIR = "trace";
|
package/dist/mcp/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createStateTools } from "./tools-state.js";
|
|
|
15
15
|
import { createCompileTools } from "./tools-compile.js";
|
|
16
16
|
import { createEnforceTools } from "./tools-enforce.js";
|
|
17
17
|
import { createObservabilityTools } from "./tools-observability.js";
|
|
18
|
+
import { createContextTools } from "./tools-context.js";
|
|
18
19
|
// Parse args
|
|
19
20
|
const args = process.argv.slice(2);
|
|
20
21
|
let projectDir = process.cwd();
|
|
@@ -36,6 +37,7 @@ catch { /* use default */ }
|
|
|
36
37
|
const tools = [
|
|
37
38
|
...createStateTools(projectDir),
|
|
38
39
|
...createCompileTools(projectDir),
|
|
40
|
+
...createContextTools(projectDir),
|
|
39
41
|
...createEnforceTools(projectDir),
|
|
40
42
|
...createObservabilityTools(projectDir),
|
|
41
43
|
];
|