intentdna 1.6.2 → 1.6.3

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.
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "intentdna",
11
11
  "description": "Declarative policy layer for AI agent behavior with plugin-managed hook runtime for Claude Code.",
12
- "version": "1.6.2",
12
+ "version": "1.6.3",
13
13
  "author": {
14
14
  "name": "Samuel"
15
15
  },
@@ -25,5 +25,5 @@
25
25
  ]
26
26
  }
27
27
  ],
28
- "version": "1.6.2"
28
+ "version": "1.6.3"
29
29
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intentdna",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "Declarative policy layer for AI agent behavior",
5
5
  "author": {
6
6
  "name": "Samuel"
package/README.md CHANGED
@@ -185,7 +185,9 @@ dna init --template <name> # Start from template
185
185
  dna sync <file> # Compile DNA → harness config
186
186
  dna import . # Import existing config → DNA
187
187
  dna verify # Check config integrity
188
- dna evolve <dna-id> # Generate epigenetic markers from outcomes
188
+ dna feedback --evolve # Preview marker changes from local classified signals
189
+ dna evolve <dna-id> # Preview marker changes from persisted outcomes
190
+ dna sync <file> --evolve # Mutating path: apply evolved markers before sync
189
191
  ```
190
192
 
191
193
  ---
@@ -207,8 +209,12 @@ Read more: [DESIGN.md](DESIGN.md)
207
209
 
208
210
  - [DESIGN.md](DESIGN.md) — long-term architecture and design
209
211
  - [ROADMAP.md](ROADMAP.md) — current stage, next plan, deferred work
212
+ - [docs/README.md](docs/README.md) — Repo-Wiki root and reading paths
213
+ - [spec/README.md](spec/README.md) — engineering spec index and status map
210
214
  - [spec/schema-spec.md](spec/schema-spec.md) — schema contract
211
215
  - [docs/references/](docs/references/) — distilled research and implementation references
216
+ - [docs/workflows/](docs/workflows/) — operational runbooks
217
+ - [docs/troubleshooting/](docs/troubleshooting/) — troubleshooting entry points
212
218
  - [docs/insights/](docs/insights/) — strategic and product-thinking documents
213
219
 
214
220
  ---
@@ -1,11 +1,14 @@
1
1
  /**
2
- * dna evolve <dna-id> [--preview] [--store-dir <path>]
2
+ * dna evolve <dna-id> [--preview] [--apply] [--store-dir <path>]
3
3
  *
4
- * Generate/update epigenetic markers from accumulated outcomes.
4
+ * Preview epigenetic marker changes from accumulated persisted outcomes.
5
+ * Applying markers from this command is intentionally not implemented in the
6
+ * local kernel; `dna sync --evolve` remains the explicit mutating sync path.
5
7
  */
6
8
  export interface EvolveOptions {
7
9
  dnaId: string;
8
- preview: boolean;
10
+ preview?: boolean;
11
+ apply?: boolean;
9
12
  storeDir?: string;
10
13
  }
11
14
  export declare function runEvolve(opts: EvolveOptions): Promise<number>;
@@ -1,47 +1,93 @@
1
1
  /**
2
- * dna evolve <dna-id> [--preview] [--store-dir <path>]
2
+ * dna evolve <dna-id> [--preview] [--apply] [--store-dir <path>]
3
3
  *
4
- * Generate/update epigenetic markers from accumulated outcomes.
4
+ * Preview epigenetic marker changes from accumulated persisted outcomes.
5
+ * Applying markers from this command is intentionally not implemented in the
6
+ * local kernel; `dna sync --evolve` remains the explicit mutating sync path.
5
7
  */
6
- import { EvolutionEngine } from "../../evolution/index.js";
8
+ import { readFile, readdir } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+ import { safePathComponent } from "../../hooks/state.js";
11
+ import { describeMarkerChanges } from "../../evolution/marker-gen.js";
12
+ import { DEFAULT_EVOLUTION_CONFIG } from "../../evolution/types.js";
7
13
  import { bold, green, yellow, dim, cyan } from "../util/format.js";
14
+ async function readOutcomesReadOnly(storeDir, dnaId) {
15
+ const safeDnaId = safePathComponent(dnaId, "dna_id");
16
+ const dir = join(storeDir, "outcomes", safeDnaId);
17
+ let files;
18
+ try {
19
+ files = await readdir(dir);
20
+ }
21
+ catch {
22
+ return [];
23
+ }
24
+ const outcomes = [];
25
+ for (const file of files) {
26
+ if (!file.endsWith(".json"))
27
+ continue;
28
+ try {
29
+ const raw = await readFile(join(dir, file), "utf-8");
30
+ outcomes.push(JSON.parse(raw));
31
+ }
32
+ catch {
33
+ // Skip corrupt outcome files in preview.
34
+ }
35
+ }
36
+ return outcomes.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
37
+ }
38
+ async function readMarkersReadOnly(storeDir, dnaId) {
39
+ const safeDnaId = safePathComponent(dnaId, "dna_id");
40
+ try {
41
+ const raw = await readFile(join(storeDir, "markers", `${safeDnaId}.json`), "utf-8");
42
+ const parsed = JSON.parse(raw);
43
+ return Array.isArray(parsed) ? parsed : [];
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ }
49
+ function printChanges(changes) {
50
+ process.stderr.write(bold("Marker changes preview:") + "\n\n");
51
+ for (const change of changes) {
52
+ switch (change.type) {
53
+ case "create":
54
+ process.stderr.write(` ${green("+ CREATE")} ${cyan(change.gene)} → ${change.action} ×${change.factor.toFixed(2)} (${change.feedback_count} feedbacks)\n`);
55
+ break;
56
+ case "update":
57
+ process.stderr.write(` ${yellow("~ UPDATE")} ${cyan(change.gene)} → ${change.action} ×${change.factor.toFixed(2)} (${change.feedback_count} feedbacks)\n`);
58
+ break;
59
+ case "neutral":
60
+ process.stderr.write(` ${dim("= NEUTRAL")} ${cyan(change.gene)} ×${change.factor.toFixed(2)} (${change.feedback_count} feedbacks)\n`);
61
+ break;
62
+ case "insufficient_data":
63
+ process.stderr.write(` ${dim("? INSUFFICIENT")} ${cyan(change.gene)} (${change.feedback_count}/${change.needed} feedbacks needed)\n`);
64
+ break;
65
+ }
66
+ }
67
+ }
8
68
  export async function runEvolve(opts) {
9
69
  if (!opts.dnaId) {
10
70
  process.stderr.write("Error: dna-id is required\n");
11
71
  return 2;
12
72
  }
73
+ if (opts.apply) {
74
+ process.stderr.write("Error: `dna evolve --apply` is not implemented in this kernel slice. " +
75
+ "`dna evolve` is preview-only; `dna sync --evolve` is the explicit mutating sync path.\n");
76
+ return 2;
77
+ }
13
78
  try {
14
- const engine = new EvolutionEngine(opts.storeDir ? { store_dir: opts.storeDir } : undefined);
15
- if (opts.preview) {
16
- const changes = await engine.preview(opts.dnaId);
17
- if (changes.length === 0) {
18
- process.stderr.write("No marker changes to generate (no outcomes recorded)\n");
19
- return 0;
20
- }
21
- process.stderr.write(bold("Marker changes preview:") + "\n\n");
22
- for (const change of changes) {
23
- switch (change.type) {
24
- case "create":
25
- process.stderr.write(` ${green("+ CREATE")} ${cyan(change.gene)} → ${change.action} ×${change.factor.toFixed(2)} (${change.feedback_count} feedbacks)\n`);
26
- break;
27
- case "update":
28
- process.stderr.write(` ${yellow("~ UPDATE")} ${cyan(change.gene)} → ${change.action} ×${change.factor.toFixed(2)} (${change.feedback_count} feedbacks)\n`);
29
- break;
30
- case "neutral":
31
- process.stderr.write(` ${dim("= NEUTRAL")} ${cyan(change.gene)} ×${change.factor.toFixed(2)} (${change.feedback_count} feedbacks)\n`);
32
- break;
33
- case "insufficient_data":
34
- process.stderr.write(` ${dim("? INSUFFICIENT")} ${cyan(change.gene)} (${change.feedback_count}/${change.needed} feedbacks needed)\n`);
35
- break;
36
- }
37
- }
79
+ const safeDnaId = safePathComponent(opts.dnaId, "dna_id");
80
+ const storeDir = opts.storeDir ?? DEFAULT_EVOLUTION_CONFIG.store_dir;
81
+ const outcomes = await readOutcomesReadOnly(storeDir, safeDnaId);
82
+ const existingMarkers = await readMarkersReadOnly(storeDir, safeDnaId);
83
+ const changes = describeMarkerChanges(outcomes, existingMarkers);
84
+ if (changes.length === 0) {
85
+ process.stderr.write("No marker changes to generate (no outcomes recorded)\n");
86
+ process.stderr.write("Preview only: no outcomes or markers were persisted.\n");
38
87
  return 0;
39
88
  }
40
- // Actually evolve
41
- const markers = await engine.evolve(opts.dnaId);
42
- process.stderr.write(`Evolved ${opts.dnaId}: ${markers.length} active markers\n`);
43
- // Output markers as JSON
44
- process.stdout.write(JSON.stringify(markers, null, 2) + "\n");
89
+ printChanges(changes);
90
+ process.stderr.write("\nPreview only: no outcomes or markers were persisted.\n");
45
91
  return 0;
46
92
  }
47
93
  catch (err) {
@@ -1,38 +1,13 @@
1
1
  /**
2
2
  * dna feedback [--days <N>] [--json] [--evolve]
3
3
  *
4
- * Analyze trace/audit data and produce actionable feedback for template optimization.
5
- * With --evolve: convert trace data into epigenetic markers (preview by default).
6
- *
7
- * Reads: .dna/state/trace/trace-*.jsonl
8
- * Outputs: human-readable report (or JSON with --json)
4
+ * Build a local kernel observability report from traces and verifier results.
5
+ * With --evolve: include a preview-only marker diff derived from local signals.
9
6
  */
10
7
  export interface FeedbackOptions {
11
8
  days: number;
12
9
  json: boolean;
13
10
  evolve: boolean;
14
- }
15
- export interface FeedbackReport {
16
- period_days: number;
17
- total_events: number;
18
- blocks: number;
19
- warns: number;
20
- allows: number;
21
- verifier_results: number;
22
- verifier_passes: number;
23
- verifier_failures: number;
24
- top_blocked_tools: Array<{
25
- tool: string;
26
- count: number;
27
- }>;
28
- top_blocked_paths: Array<{
29
- path: string;
30
- count: number;
31
- }>;
32
- top_block_reasons: Array<{
33
- reason: string;
34
- count: number;
35
- }>;
36
- suggestions: string[];
11
+ dnaId?: string;
37
12
  }
38
13
  export declare function runFeedback(opts: FeedbackOptions): Promise<number>;
@@ -1,196 +1,27 @@
1
1
  /**
2
2
  * dna feedback [--days <N>] [--json] [--evolve]
3
3
  *
4
- * Analyze trace/audit data and produce actionable feedback for template optimization.
5
- * With --evolve: convert trace data into epigenetic markers (preview by default).
6
- *
7
- * Reads: .dna/state/trace/trace-*.jsonl
8
- * Outputs: human-readable report (or JSON with --json)
4
+ * Build a local kernel observability report from traces and verifier results.
5
+ * With --evolve: include a preview-only marker diff derived from local signals.
9
6
  */
10
- import { readTraces, readVerifierResults } from "../../hooks/state.js";
11
- import { mergeOutcomes, summarizeConversion } from "../../evolution/trace-bridge.js";
12
- import { describeMarkerChanges } from "../../evolution/marker-gen.js";
13
- import { EvolutionStore } from "../../evolution/store.js";
14
- function aggregate(traces) {
15
- let blocks = 0, warns = 0, allows = 0;
16
- const toolCounts = new Map();
17
- const pathCounts = new Map();
18
- const reasonCounts = new Map();
19
- for (const t of traces) {
20
- if (t.decision === "block") {
21
- blocks++;
22
- if (t.tool_name)
23
- toolCounts.set(t.tool_name, (toolCounts.get(t.tool_name) ?? 0) + 1);
24
- if (t.target_path)
25
- pathCounts.set(t.target_path, (pathCounts.get(t.target_path) ?? 0) + 1);
26
- if (t.reason) {
27
- // Normalize reason to first line
28
- const shortReason = t.reason.split("\n")[0].replace(/^\[Intent DNA\]\s*/, "").slice(0, 100);
29
- reasonCounts.set(shortReason, (reasonCounts.get(shortReason) ?? 0) + 1);
30
- }
31
- }
32
- else if (t.decision === "warn") {
33
- warns++;
34
- }
35
- else {
36
- allows++;
37
- }
38
- }
39
- const sorted = (map, limit, key) => [...map.entries()]
40
- .sort((a, b) => b[1] - a[1])
41
- .slice(0, limit)
42
- .map(([k, count]) => ({ [key]: k, count }));
43
- return {
44
- total_events: traces.length,
45
- blocks,
46
- warns,
47
- allows,
48
- top_blocked_tools: sorted(toolCounts, 5, "tool"),
49
- top_blocked_paths: sorted(pathCounts, 5, "path"),
50
- top_block_reasons: sorted(reasonCounts, 5, "reason"),
51
- };
52
- }
53
- function generateSuggestions(report, verifierResults = 0) {
54
- const suggestions = [];
55
- if (report.blocks === 0 && report.warns === 0) {
56
- if (verifierResults > 0) {
57
- suggestions.push("No trace blocks or warnings in this window. Verifier activity is present, so review verifier pass/fail results alongside trace data.");
58
- }
59
- else {
60
- suggestions.push("No blocks or warnings — DNA constraints are not firing. Consider tightening rules or verifying hooks are active.");
61
- }
62
- return suggestions;
63
- }
64
- const blockRate = report.total_events > 0 ? report.blocks / report.total_events : 0;
65
- if (blockRate > 0.3) {
66
- suggestions.push(`High block rate (${(blockRate * 100).toFixed(0)}%). Review if DNA rules are too restrictive — this may slow development.`);
67
- }
68
- // Tool-specific suggestions
69
- for (const t of report.top_blocked_tools) {
70
- if (t.count >= 5) {
71
- suggestions.push(`Tool '${t.tool}' blocked ${t.count} times. Consider expanding permissions if this is expected usage.`);
72
- }
73
- }
74
- // Path-specific suggestions
75
- for (const p of report.top_blocked_paths) {
76
- if (p.count >= 3) {
77
- suggestions.push(`Path '${p.path}' blocked ${p.count} times. Consider adding to write scope if agents need access.`);
78
- }
79
- }
80
- // Repeated reason patterns
81
- for (const r of report.top_block_reasons) {
82
- if (r.count >= 3) {
83
- suggestions.push(`Repeated block: "${r.reason}" (${r.count}x). May indicate a rule-scope mismatch.`);
84
- }
85
- }
86
- if (suggestions.length === 0) {
87
- suggestions.push("Constraints are working within normal parameters. No action needed.");
88
- }
89
- return suggestions;
90
- }
91
- function formatReport(report) {
92
- const lines = [];
93
- lines.push(`DNA Feedback Report (last ${report.period_days} day${report.period_days > 1 ? "s" : ""})`);
94
- lines.push("=".repeat(50));
95
- lines.push("");
96
- lines.push(`Total events: ${report.total_events}`);
97
- lines.push(` Allowed: ${report.allows}`);
98
- lines.push(` Blocked: ${report.blocks}`);
99
- lines.push(` Warned: ${report.warns}`);
100
- lines.push("");
101
- if (report.verifier_results > 0) {
102
- lines.push(`Verifier results: ${report.verifier_results}`);
103
- lines.push(` Passed: ${report.verifier_passes}`);
104
- lines.push(` Failed: ${report.verifier_failures}`);
105
- lines.push("");
106
- }
107
- if (report.top_blocked_tools.length > 0) {
108
- lines.push("Top blocked tools:");
109
- for (const t of report.top_blocked_tools) {
110
- lines.push(` ${t.tool}: ${t.count}`);
111
- }
112
- lines.push("");
113
- }
114
- if (report.top_blocked_paths.length > 0) {
115
- lines.push("Top blocked paths:");
116
- for (const p of report.top_blocked_paths) {
117
- lines.push(` ${p.path}: ${p.count}`);
118
- }
119
- lines.push("");
120
- }
121
- if (report.top_block_reasons.length > 0) {
122
- lines.push("Top block reasons:");
123
- for (const r of report.top_block_reasons) {
124
- lines.push(` "${r.reason}": ${r.count}`);
125
- }
126
- lines.push("");
127
- }
128
- if (report.suggestions.length > 0) {
129
- lines.push("Suggestions:");
130
- for (const s of report.suggestions) {
131
- lines.push(` - ${s}`);
132
- }
133
- lines.push("");
134
- }
135
- return lines.join("\n");
136
- }
7
+ import { buildKernelReport, formatKernelReport } from "../../report/kernel-report.js";
137
8
  export async function runFeedback(opts) {
138
- const projectDir = process.cwd();
139
- const traces = await readTraces(projectDir, opts.days);
140
- const cutoff = new Date();
141
- cutoff.setDate(cutoff.getDate() - opts.days);
142
- const verifierResults = (await readVerifierResults(projectDir)).filter((result) => {
143
- const timestamp = Date.parse(result.timestamp);
144
- return Number.isNaN(timestamp) || timestamp >= cutoff.getTime();
9
+ const report = await buildKernelReport({
10
+ projectDir: process.cwd(),
11
+ days: opts.days,
12
+ includeMarkerPreview: opts.evolve,
13
+ dnaId: opts.dnaId,
145
14
  });
146
- if (traces.length === 0 && verifierResults.length === 0) {
15
+ if (report.summary.traces === 0 && report.summary.verifier_results === 0) {
147
16
  process.stderr.write(`No trace or verifier data found for the last ${opts.days} day(s).\n`);
148
17
  process.stderr.write("Signals are generated by dna-hook during Claude Code sessions.\n");
149
18
  return 0;
150
19
  }
151
- const stats = aggregate(traces);
152
- const verifierStats = {
153
- verifier_results: verifierResults.length,
154
- verifier_passes: verifierResults.filter((result) => result.status === "pass").length,
155
- verifier_failures: verifierResults.filter((result) => result.status === "fail").length,
156
- };
157
- const suggestions = generateSuggestions(stats, verifierStats.verifier_results);
158
- const report = { ...stats, ...verifierStats, period_days: opts.days, suggestions };
159
- if (opts.json && !opts.evolve) {
20
+ if (opts.json) {
160
21
  process.stdout.write(JSON.stringify(report, null, 2) + "\n");
161
22
  }
162
- else if (!opts.evolve) {
163
- process.stderr.write(formatReport(report));
164
- }
165
- // --evolve: convert normalized local signals → epigenetic markers
166
- if (opts.evolve) {
167
- const outcomes = mergeOutcomes(traces, verifierResults);
168
- if (outcomes.length === 0) {
169
- process.stderr.write(formatReport(report));
170
- process.stderr.write("\nNo actionable gene feedback found in traces — nothing to evolve.\n");
171
- return 0;
172
- }
173
- const store = new EvolutionStore();
174
- const existingMarkers = await store.loadMarkers("default");
175
- const changes = describeMarkerChanges(outcomes, existingMarkers);
176
- process.stderr.write(formatReport(report));
177
- process.stderr.write("\n" + summarizeConversion(outcomes) + "\n\n");
178
- process.stderr.write("Marker changes (preview):\n");
179
- for (const c of changes) {
180
- if (c.type === "create") {
181
- process.stderr.write(` + CREATE ${c.gene} → ${c.action} x${c.factor.toFixed(2)} (${c.feedback_count} feedbacks)\n`);
182
- }
183
- else if (c.type === "update") {
184
- process.stderr.write(` ~ UPDATE ${c.gene} → ${c.action} x${c.factor.toFixed(2)} (${c.feedback_count} feedbacks)\n`);
185
- }
186
- else if (c.type === "neutral") {
187
- process.stderr.write(` = NEUTRAL ${c.gene} x${c.factor.toFixed(2)} (${c.feedback_count} feedbacks)\n`);
188
- }
189
- else {
190
- process.stderr.write(` ? INSUFFICIENT ${c.gene} (${c.feedback_count}/${c.needed} needed)\n`);
191
- }
192
- }
193
- process.stderr.write("\nRun `dna evolve --apply` to persist markers.\n");
23
+ else {
24
+ process.stderr.write(formatKernelReport(report));
194
25
  }
195
26
  return 0;
196
27
  }
package/dist/cli/index.js CHANGED
@@ -27,9 +27,9 @@ Commands:
27
27
  compile Compile DNA files to framework configuration
28
28
  validate Validate DNA files for correctness
29
29
  show Show gene expression state
30
- evolve Generate epigenetic markers from outcomes
30
+ evolve Preview epigenetic marker changes from persisted outcomes
31
31
  epigenetic Record outcomes, view markers and summaries
32
- feedback Analyze trace data, suggest template optimizations (--days <N>, --json, --evolve)
32
+ feedback Build local kernel report (--days <N>, --json, --evolve preview, --dna-id <id>)
33
33
  templates Template management (deploy to global install)
34
34
 
35
35
  Options:
@@ -54,7 +54,7 @@ Examples:
54
54
  dna sync my.dna.json --agents .claude/agents
55
55
  dna sync my.dna.json --workflow ./scripts
56
56
  dna sync my.dna.json --target claude-md --inject CLAUDE.md --hooks .claude/hooks --agents .claude/agents --workflow ./scripts
57
- dna sync my.dna.json --target soul-md --inject SOUL.md --evolve
57
+ dna sync my.dna.json --target soul-md --inject SOUL.md --evolve # mutates markers before sync
58
58
  dna sync --remove --inject CLAUDE.md --hooks .claude/hooks --agents .claude/agents
59
59
  dna verify Verify synced files against .dna/lock
60
60
  dna verify --lock /path/to/.dna/lock
@@ -65,7 +65,8 @@ Examples:
65
65
  dna run --dna my.dna.json --workflow dev-pipeline --task P5.8 --dry-run
66
66
  dna compile my.dna.json --context work
67
67
  dna show my.dna.json --context work
68
- dna evolve my_dna_id --preview
68
+ dna evolve my_dna_id Preview marker changes without persisting
69
+ dna evolve my_dna_id --apply Unsupported in this kernel; use sync --evolve for explicit mutation
69
70
  `;
70
71
  async function main() {
71
72
  const args = process.argv.slice(2);
@@ -317,6 +318,7 @@ async function main() {
317
318
  args: rest,
318
319
  options: {
319
320
  preview: { type: "boolean", default: false },
321
+ apply: { type: "boolean", default: false },
320
322
  "store-dir": { type: "string" },
321
323
  },
322
324
  allowPositionals: true,
@@ -326,6 +328,7 @@ async function main() {
326
328
  const code = await runEvolve({
327
329
  dnaId: positionals[0] ?? "",
328
330
  preview: values.preview,
331
+ apply: values.apply,
329
332
  storeDir: values["store-dir"],
330
333
  });
331
334
  process.exit(code);
@@ -417,6 +420,7 @@ async function main() {
417
420
  days: { type: "string", short: "d", default: "7" },
418
421
  json: { type: "boolean", default: false },
419
422
  evolve: { type: "boolean", default: false },
423
+ "dna-id": { type: "string" },
420
424
  },
421
425
  strict: false,
422
426
  });
@@ -425,6 +429,7 @@ async function main() {
425
429
  days: parseInt(fbValues.days, 10) || 7,
426
430
  json: fbValues.json,
427
431
  evolve: fbValues.evolve,
432
+ dnaId: fbValues["dna-id"],
428
433
  });
429
434
  process.exit(code);
430
435
  break;
@@ -15,7 +15,7 @@
15
15
  * Fail-open: all errors → { continue: true, suppressOutput: true }
16
16
  */
17
17
  import type { ArtifactFact, ConstraintIR, VerifierSpec } from "../schema/types.js";
18
- import type { HookOutput } from "./protocol.js";
18
+ import type { HookEvent, HookOutput } from "./protocol.js";
19
19
  import { blockOutput } from "./protocol.js";
20
20
  import { readWorkflowState } from "./state.js";
21
21
  import type { VerifierResultEntry } from "./state.js";
@@ -51,8 +51,34 @@ declare function resolveWorkflowArtifactFacts(projectDir: string, ir: Constraint
51
51
  inputs?: Record<string, string>;
52
52
  resolved_variables?: Record<string, string>;
53
53
  }, sessionId?: string): Promise<ArtifactFact[]>;
54
+ type HookArtifactResult = {
55
+ facts: ArtifactFact[];
56
+ } | {
57
+ output: HookOutput;
58
+ };
59
+ type HookWorkflowStateResult = {
60
+ state: Awaited<ReturnType<typeof readWorkflowState>>;
61
+ } | {
62
+ output: HookOutput;
63
+ };
64
+ declare function readWorkflowStateForHook(projectDir: string, event: HookEvent, sessionId?: string): Promise<HookWorkflowStateResult>;
65
+ declare function resolveWorkflowArtifactFactsForHook(projectDir: string, ir: ConstraintIR, wfState: {
66
+ workflow: string;
67
+ current_step: string;
68
+ inputs?: Record<string, string>;
69
+ resolved_variables?: Record<string, string>;
70
+ }, event: HookEvent, sessionId?: string): Promise<HookArtifactResult>;
71
+ declare function finalizeAndResolveArtifactsForHook(projectDir: string, ir: ConstraintIR, wfState: {
72
+ workflow: string;
73
+ current_step: string;
74
+ inputs?: Record<string, string>;
75
+ resolved_variables?: Record<string, string>;
76
+ }, event: HookEvent, sessionId?: string): Promise<HookArtifactResult>;
54
77
  export declare const recordProducedArtifactsForTest: typeof recordProducedArtifacts;
55
78
  export declare const resolveWorkflowArtifactFactsForTest: typeof resolveWorkflowArtifactFacts;
79
+ export declare const readWorkflowStateForHookTest: typeof readWorkflowStateForHook;
80
+ export declare const resolveWorkflowArtifactFactsForHookTest: typeof resolveWorkflowArtifactFactsForHook;
81
+ export declare const finalizeAndResolveArtifactsForHookTest: typeof finalizeAndResolveArtifactsForHook;
56
82
  export declare function appendStopVerifierWarnings(output: HookOutput, verifierResults: VerifierResultEntry[], currentStep?: string): HookOutput;
57
83
  export declare function runVerifiersForTest(projectDir: string, ir: ConstraintIR, workflowState: {
58
84
  workflow: string;
package/dist/hooks/cli.js CHANGED
@@ -107,7 +107,12 @@ async function main() {
107
107
  let wfStateRaw = null;
108
108
  // Load workflow state for events that need it (handoff context + PreCompact preservation)
109
109
  if (event === "PreToolUse" || event === "PostToolUse" || event === "PreCompact") {
110
- wfStateRaw = await readWorkflowState(projectDir, sessionId);
110
+ const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
111
+ if ("output" in workflowState) {
112
+ writeOutput(workflowState.output);
113
+ return;
114
+ }
115
+ wfStateRaw = workflowState.state;
111
116
  if (wfStateRaw && wfStateRaw.active) {
112
117
  state.workflowState = {
113
118
  current_step: wfStateRaw.current_step,
@@ -116,7 +121,13 @@ async function main() {
116
121
  completed_artifacts: wfStateRaw.completed_artifacts,
117
122
  iteration: wfStateRaw.iteration, // G4: pass iteration for state-driven rules
118
123
  };
119
- state.artifactFacts = await resolveWorkflowArtifactFacts(projectDir, ir, wfStateRaw, sessionId);
124
+ const artifactFacts = await resolveWorkflowArtifactFactsForHook(projectDir, ir, wfStateRaw, event, sessionId);
125
+ if ("output" in artifactFacts) {
126
+ writeOutput(artifactFacts.output);
127
+ appendArtifactResolverTrace(projectDir, event, wfStateRaw, artifactFacts.output, sessionId);
128
+ return;
129
+ }
130
+ state.artifactFacts = artifactFacts.facts;
120
131
  }
121
132
  }
122
133
  if (event === "PreToolUse" && wfStateRaw?.active) {
@@ -148,7 +159,22 @@ async function main() {
148
159
  }
149
160
  // Special handling for Stop — needs async workflow state read + session summary
150
161
  if (event === "Stop") {
151
- const wfState = await readWorkflowState(projectDir, sessionId);
162
+ const workflowState = await readWorkflowStateForHook(projectDir, event, sessionId);
163
+ if ("output" in workflowState) {
164
+ writeOutput(workflowState.output);
165
+ return;
166
+ }
167
+ const wfState = workflowState.state;
168
+ let stopArtifactFacts = [];
169
+ if (wfState?.active) {
170
+ const artifactFacts = await finalizeAndResolveArtifactsForHook(projectDir, ir, wfState, event, sessionId);
171
+ if ("output" in artifactFacts) {
172
+ writeOutput(artifactFacts.output);
173
+ appendArtifactResolverTrace(projectDir, event, wfState, artifactFacts.output, sessionId);
174
+ return;
175
+ }
176
+ stopArtifactFacts = artifactFacts.facts;
177
+ }
152
178
  const stopContext = wfState ? {
153
179
  active: wfState.active,
154
180
  workflow: wfState.workflow,
@@ -156,7 +182,7 @@ async function main() {
156
182
  current_role: wfState.current_role,
157
183
  started_at: wfState.started_at,
158
184
  completed_artifacts: wfState.completed_artifacts,
159
- artifact_facts: wfState.active ? await finalizeAndResolveArtifacts(projectDir, ir, wfState, sessionId) : [],
185
+ artifact_facts: stopArtifactFacts,
160
186
  } : null;
161
187
  const verifierResults = stopContext?.active
162
188
  ? await runStopVerifiersForTest(projectDir, ir, stopContext, sessionId)
@@ -548,8 +574,51 @@ async function finalizeAndResolveArtifacts(projectDir, ir, wfState, sessionId) {
548
574
  await recordProducedArtifacts(projectDir, ir, wfState, sessionId);
549
575
  return resolveWorkflowArtifactFacts(projectDir, ir, wfState, sessionId);
550
576
  }
577
+ function artifactResolverErrorOutput(event, error) {
578
+ const detail = error instanceof Error ? error.message : String(error);
579
+ return blockOutput(`[Intent DNA] ${event} artifact resolver failed: ${detail}`);
580
+ }
581
+ async function readWorkflowStateForHook(projectDir, event, sessionId) {
582
+ try {
583
+ return { state: await readWorkflowState(projectDir, sessionId) };
584
+ }
585
+ catch (error) {
586
+ return { output: artifactResolverErrorOutput(event, error) };
587
+ }
588
+ }
589
+ async function resolveWorkflowArtifactFactsForHook(projectDir, ir, wfState, event, sessionId) {
590
+ try {
591
+ return { facts: await resolveWorkflowArtifactFacts(projectDir, ir, wfState, sessionId) };
592
+ }
593
+ catch (error) {
594
+ return { output: artifactResolverErrorOutput(event, error) };
595
+ }
596
+ }
597
+ async function finalizeAndResolveArtifactsForHook(projectDir, ir, wfState, event, sessionId) {
598
+ try {
599
+ return { facts: await finalizeAndResolveArtifacts(projectDir, ir, wfState, sessionId) };
600
+ }
601
+ catch (error) {
602
+ return { output: artifactResolverErrorOutput(event, error) };
603
+ }
604
+ }
605
+ function appendArtifactResolverTrace(projectDir, event, wfState, output, sessionId) {
606
+ appendTrace(projectDir, {
607
+ trace_id: randomUUID(),
608
+ event,
609
+ workflow: wfState.workflow,
610
+ step: wfState.current_step,
611
+ decision: "block",
612
+ reason: output.reason,
613
+ duration_ms: 0,
614
+ timestamp: new Date().toISOString(),
615
+ }, sessionId).catch(() => { });
616
+ }
551
617
  export const recordProducedArtifactsForTest = recordProducedArtifacts;
552
618
  export const resolveWorkflowArtifactFactsForTest = resolveWorkflowArtifactFacts;
619
+ export const readWorkflowStateForHookTest = readWorkflowStateForHook;
620
+ export const resolveWorkflowArtifactFactsForHookTest = resolveWorkflowArtifactFactsForHook;
621
+ export const finalizeAndResolveArtifactsForHookTest = finalizeAndResolveArtifactsForHook;
553
622
  function clearBlockingVerifierCheckpoints(ir, workflowState) {
554
623
  if (!workflowState || !ir.verifier_specs || ir.verifier_specs.length === 0)
555
624
  return ir;