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
|
@@ -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.
|
|
12
|
+
"version": "1.6.4",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "Samuel"
|
|
15
15
|
},
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
]
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
|
-
"version": "1.6.
|
|
28
|
+
"version": "1.6.4"
|
|
29
29
|
}
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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 {
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
process.stderr.write(
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
*
|
|
5
|
-
* With --evolve:
|
|
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
|
-
*
|
|
5
|
-
* With --evolve:
|
|
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 {
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
163
|
-
process.stderr.write(
|
|
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
|
|
30
|
+
evolve Preview epigenetic marker changes from persisted outcomes
|
|
31
31
|
epigenetic Record outcomes, view markers and summaries
|
|
32
|
-
feedback
|
|
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
|
|
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;
|
package/dist/hooks/cli.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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;
|