intentdna 1.5.13 → 1.5.15
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/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.js +24 -11
- package/dist/cli/commands/sync.d.ts +9 -4
- package/dist/cli/commands/sync.js +61 -23
- package/dist/hooks/cli.d.ts +30 -0
- package/dist/hooks/cli.js +168 -59
- package/dist/hooks/enforce.d.ts +13 -12
- package/dist/hooks/enforce.js +61 -46
- package/dist/hooks/protocol.d.ts +11 -0
- package/dist/hooks/schema.d.ts +22 -0
- package/dist/hooks/schema.js +86 -0
- package/dist/hooks/state-manager.d.ts +48 -0
- package/dist/hooks/state-manager.js +135 -0
- package/dist/hooks/state.d.ts +8 -0
- package/dist/hooks/state.js +8 -0
- package/dist/mcp/tools-enforce.js +6 -2
- package/dist/templates/flutter-rewrite.dna.yaml +22 -17
- package/package.json +3 -2
- package/spec/hooks-infra-harness-hardening.md +72 -26
- package/spec/template-version-namespace-fix.md +653 -0
- package/.claude-plugin/hooks/hooks.json +0 -12
- package/.claude-plugin/marketplace.json +0 -16
- package/.claude-plugin/plugin.json +0 -5
- package/LICENSE +0 -69
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent DNA — DNAStateManager
|
|
3
|
+
*
|
|
4
|
+
* Class-based facade over state.ts module functions. Bundles projectDir +
|
|
5
|
+
* sessionId into a single object so call sites don't repeat those args.
|
|
6
|
+
* Adds experience chain persistence (markdown + structured JSON) and
|
|
7
|
+
* lifecycle operations (init / cleanup / isStale) that state.ts lacks.
|
|
8
|
+
*
|
|
9
|
+
* Existing state.ts functions remain the canonical I/O primitives — this
|
|
10
|
+
* class delegates to them. Prefer this class in new code; state.ts exports
|
|
11
|
+
* are @deprecated for direct use.
|
|
12
|
+
*/
|
|
13
|
+
import { readFile, mkdir, unlink, stat } from "node:fs/promises";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { resolveStateDir, readWorkflowState as rwState, writeWorkflowState as wwState, clearWorkflowState as cwState, appendAudit as aAudit, readSurgeonAttempts as rSurg, writeSurgeonAttempts as wSurg, readSessionReads as rReads, appendSessionRead as aRead, atomicWrite, } from "./state.js";
|
|
16
|
+
const EXPERIENCE_JSON = "workflow/experience.json";
|
|
17
|
+
const EXPERIENCE_MD = "workflow/experience.md";
|
|
18
|
+
const DEFAULT_STALENESS_MS = 2 * 60 * 60 * 1000; // 2h — matches state.ts
|
|
19
|
+
// ── Manager ────────────────────────────────────────────────
|
|
20
|
+
export class DNAStateManager {
|
|
21
|
+
projectDir;
|
|
22
|
+
sessionId;
|
|
23
|
+
constructor(projectDir, sessionId) {
|
|
24
|
+
this.projectDir = projectDir;
|
|
25
|
+
this.sessionId = sessionId;
|
|
26
|
+
}
|
|
27
|
+
// ── Workflow state ──
|
|
28
|
+
readWorkflowState(stalenessMs = DEFAULT_STALENESS_MS) {
|
|
29
|
+
return rwState(this.projectDir, this.sessionId, stalenessMs);
|
|
30
|
+
}
|
|
31
|
+
writeWorkflowState(state) {
|
|
32
|
+
return wwState(this.projectDir, state, this.sessionId);
|
|
33
|
+
}
|
|
34
|
+
clearWorkflowState() {
|
|
35
|
+
return cwState(this.projectDir, this.sessionId);
|
|
36
|
+
}
|
|
37
|
+
// ── Audit log ──
|
|
38
|
+
appendAudit(entry) {
|
|
39
|
+
return aAudit(this.projectDir, entry);
|
|
40
|
+
}
|
|
41
|
+
// ── Surgeon attempts ──
|
|
42
|
+
readSurgeonAttempts() {
|
|
43
|
+
return rSurg(this.projectDir, this.sessionId);
|
|
44
|
+
}
|
|
45
|
+
writeSurgeonAttempts(state) {
|
|
46
|
+
return wSurg(this.projectDir, state, this.sessionId);
|
|
47
|
+
}
|
|
48
|
+
// ── Session reads ──
|
|
49
|
+
readSessionReads() {
|
|
50
|
+
return rReads(this.projectDir, this.sessionId);
|
|
51
|
+
}
|
|
52
|
+
appendSessionRead(filePath) {
|
|
53
|
+
return aRead(this.projectDir, filePath, this.sessionId);
|
|
54
|
+
}
|
|
55
|
+
// ── Experience chain (new) ──
|
|
56
|
+
async readExperienceChain() {
|
|
57
|
+
const path = this.path(EXPERIENCE_JSON);
|
|
58
|
+
try {
|
|
59
|
+
const raw = await readFile(path, "utf-8");
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async writeExperienceChain(chain) {
|
|
68
|
+
await atomicWrite(this.path(EXPERIENCE_JSON), JSON.stringify(chain, null, 2));
|
|
69
|
+
await atomicWrite(this.path(EXPERIENCE_MD), renderExperienceMarkdown(chain));
|
|
70
|
+
}
|
|
71
|
+
async appendExperience(entry) {
|
|
72
|
+
const chain = await this.readExperienceChain();
|
|
73
|
+
chain.push(entry);
|
|
74
|
+
await this.writeExperienceChain(chain);
|
|
75
|
+
}
|
|
76
|
+
// ── Lifecycle ──
|
|
77
|
+
/** Ensure session directory structure exists. */
|
|
78
|
+
async init() {
|
|
79
|
+
await mkdir(resolveStateDir(this.projectDir, this.sessionId), { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Remove this manager's state files.
|
|
83
|
+
* Callers that need trace rotation should invoke rotateTraces() from state.ts separately.
|
|
84
|
+
*/
|
|
85
|
+
async cleanup() {
|
|
86
|
+
const dir = resolveStateDir(this.projectDir, this.sessionId);
|
|
87
|
+
for (const rel of ["workflow.json", EXPERIENCE_JSON, EXPERIENCE_MD, "workflow/surgeon-attempts.json", "workflow/session-reads.json"]) {
|
|
88
|
+
await unlink(join(dir, rel)).catch(() => { });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** True when workflow.json started_at exceeds the staleness threshold. */
|
|
92
|
+
async isStale(stalenessMs = DEFAULT_STALENESS_MS) {
|
|
93
|
+
const wfPath = join(resolveStateDir(this.projectDir, this.sessionId), "workflow.json");
|
|
94
|
+
try {
|
|
95
|
+
const raw = await readFile(wfPath, "utf-8");
|
|
96
|
+
const state = JSON.parse(raw);
|
|
97
|
+
if (!state.started_at)
|
|
98
|
+
return false;
|
|
99
|
+
return Date.now() - new Date(state.started_at).getTime() > stalenessMs;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// No workflow.json — fall back to directory mtime for unparseable state
|
|
103
|
+
try {
|
|
104
|
+
const s = await stat(wfPath);
|
|
105
|
+
return Date.now() - s.mtimeMs > stalenessMs;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
path(rel) {
|
|
113
|
+
return join(resolveStateDir(this.projectDir, this.sessionId), rel);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// ── Markdown rendering ────────────────────────────────────
|
|
117
|
+
function renderExperienceMarkdown(chain) {
|
|
118
|
+
if (chain.length === 0) {
|
|
119
|
+
return "# Experience chain\n\n_Empty — no attempts yet._\n";
|
|
120
|
+
}
|
|
121
|
+
const lines = ["# Experience chain", ""];
|
|
122
|
+
for (const e of chain) {
|
|
123
|
+
lines.push(`## Round ${e.round} — ${e.timestamp}`);
|
|
124
|
+
lines.push("");
|
|
125
|
+
lines.push(`**Analysis:** ${e.analysis}`);
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push(`**Attempt:** ${e.attempt}`);
|
|
128
|
+
lines.push("");
|
|
129
|
+
lines.push(`**Result:** ${e.result}`);
|
|
130
|
+
lines.push("");
|
|
131
|
+
lines.push(`**Lesson:** ${e.lesson}`);
|
|
132
|
+
lines.push("");
|
|
133
|
+
}
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
package/dist/hooks/state.d.ts
CHANGED
|
@@ -56,20 +56,24 @@ export interface SurgeonAttemptState {
|
|
|
56
56
|
export declare function resolveStateDir(projectDir: string, sessionId?: string): string;
|
|
57
57
|
/**
|
|
58
58
|
* Read current workflow state. Returns null if not found or stale.
|
|
59
|
+
* @deprecated Prefer `new DNAStateManager(projectDir, sessionId).readWorkflowState()`.
|
|
59
60
|
*/
|
|
60
61
|
export declare function readWorkflowState(projectDir: string, sessionId?: string, stalenessMs?: number): Promise<DNAWorkflowState | null>;
|
|
61
62
|
/**
|
|
62
63
|
* Write workflow state atomically.
|
|
64
|
+
* @deprecated Prefer `DNAStateManager.writeWorkflowState()`.
|
|
63
65
|
*/
|
|
64
66
|
export declare function writeWorkflowState(projectDir: string, state: DNAWorkflowState, sessionId?: string): Promise<void>;
|
|
65
67
|
/**
|
|
66
68
|
* Clear workflow state (workflow complete).
|
|
69
|
+
* @deprecated Prefer `DNAStateManager.clearWorkflowState()`.
|
|
67
70
|
*/
|
|
68
71
|
export declare function clearWorkflowState(projectDir: string, sessionId?: string): Promise<void>;
|
|
69
72
|
/**
|
|
70
73
|
* Append an entry to the audit log with dedup protection.
|
|
71
74
|
* Dedup key: event + tool_name + timestamp (second-level).
|
|
72
75
|
* Log file: `.dna/audit/violations-YYYY-MM-DD.log` (JSON Lines format)
|
|
76
|
+
* @deprecated Prefer `DNAStateManager.appendAudit()`.
|
|
73
77
|
*/
|
|
74
78
|
export declare function appendAudit(projectDir: string, entry: AuditEntry): Promise<void>;
|
|
75
79
|
/**
|
|
@@ -84,10 +88,12 @@ export declare function appendCompletedArtifact(projectDir: string, stepId: stri
|
|
|
84
88
|
export declare function atomicWrite(filePath: string, data: string): Promise<void>;
|
|
85
89
|
/**
|
|
86
90
|
* Read surgeon attempt state. Returns default state if not found.
|
|
91
|
+
* @deprecated Prefer `DNAStateManager.readSurgeonAttempts()`.
|
|
87
92
|
*/
|
|
88
93
|
export declare function readSurgeonAttempts(projectDir: string, sessionId?: string): Promise<SurgeonAttemptState>;
|
|
89
94
|
/**
|
|
90
95
|
* Write surgeon attempt state atomically.
|
|
96
|
+
* @deprecated Prefer `DNAStateManager.writeSurgeonAttempts()`.
|
|
91
97
|
*/
|
|
92
98
|
export declare function writeSurgeonAttempts(projectDir: string, state: SurgeonAttemptState, sessionId?: string): Promise<void>;
|
|
93
99
|
/** Tracks which files a session has Read — for context gate enforcement */
|
|
@@ -97,6 +103,7 @@ export interface SessionReadsState {
|
|
|
97
103
|
}
|
|
98
104
|
/**
|
|
99
105
|
* Read session reads state. Returns empty reads if not found.
|
|
106
|
+
* @deprecated Prefer `DNAStateManager.readSessionReads()`.
|
|
100
107
|
*/
|
|
101
108
|
export declare function readSessionReads(projectDir: string, sessionId?: string): Promise<SessionReadsState>;
|
|
102
109
|
/**
|
|
@@ -105,6 +112,7 @@ export declare function readSessionReads(projectDir: string, sessionId?: string)
|
|
|
105
112
|
export declare function writeSessionReads(projectDir: string, state: SessionReadsState, sessionId?: string): Promise<void>;
|
|
106
113
|
/**
|
|
107
114
|
* Append a file path to session reads (dedup).
|
|
115
|
+
* @deprecated Prefer `DNAStateManager.appendSessionRead()`.
|
|
108
116
|
*/
|
|
109
117
|
export declare function appendSessionRead(projectDir: string, filePath: string, sessionId?: string): Promise<void>;
|
|
110
118
|
/** Trace entry for hook call observability */
|
package/dist/hooks/state.js
CHANGED
|
@@ -30,6 +30,7 @@ export function resolveStateDir(projectDir, sessionId) {
|
|
|
30
30
|
// ── Workflow State ─────────────────────────────────────────
|
|
31
31
|
/**
|
|
32
32
|
* Read current workflow state. Returns null if not found or stale.
|
|
33
|
+
* @deprecated Prefer `new DNAStateManager(projectDir, sessionId).readWorkflowState()`.
|
|
33
34
|
*/
|
|
34
35
|
export async function readWorkflowState(projectDir, sessionId, stalenessMs = DEFAULT_STALENESS_MS) {
|
|
35
36
|
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
@@ -51,6 +52,7 @@ export async function readWorkflowState(projectDir, sessionId, stalenessMs = DEF
|
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
54
|
* Write workflow state atomically.
|
|
55
|
+
* @deprecated Prefer `DNAStateManager.writeWorkflowState()`.
|
|
54
56
|
*/
|
|
55
57
|
export async function writeWorkflowState(projectDir, state, sessionId) {
|
|
56
58
|
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
@@ -60,6 +62,7 @@ export async function writeWorkflowState(projectDir, state, sessionId) {
|
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
64
|
* Clear workflow state (workflow complete).
|
|
65
|
+
* @deprecated Prefer `DNAStateManager.clearWorkflowState()`.
|
|
63
66
|
*/
|
|
64
67
|
export async function clearWorkflowState(projectDir, sessionId) {
|
|
65
68
|
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
@@ -76,6 +79,7 @@ export async function clearWorkflowState(projectDir, sessionId) {
|
|
|
76
79
|
* Append an entry to the audit log with dedup protection.
|
|
77
80
|
* Dedup key: event + tool_name + timestamp (second-level).
|
|
78
81
|
* Log file: `.dna/audit/violations-YYYY-MM-DD.log` (JSON Lines format)
|
|
82
|
+
* @deprecated Prefer `DNAStateManager.appendAudit()`.
|
|
79
83
|
*/
|
|
80
84
|
export async function appendAudit(projectDir, entry) {
|
|
81
85
|
const auditDir = join(projectDir, ".dna", "audit");
|
|
@@ -153,6 +157,7 @@ function defaultSurgeonState() {
|
|
|
153
157
|
}
|
|
154
158
|
/**
|
|
155
159
|
* Read surgeon attempt state. Returns default state if not found.
|
|
160
|
+
* @deprecated Prefer `DNAStateManager.readSurgeonAttempts()`.
|
|
156
161
|
*/
|
|
157
162
|
export async function readSurgeonAttempts(projectDir, sessionId) {
|
|
158
163
|
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
@@ -167,6 +172,7 @@ export async function readSurgeonAttempts(projectDir, sessionId) {
|
|
|
167
172
|
}
|
|
168
173
|
/**
|
|
169
174
|
* Write surgeon attempt state atomically.
|
|
175
|
+
* @deprecated Prefer `DNAStateManager.writeSurgeonAttempts()`.
|
|
170
176
|
*/
|
|
171
177
|
export async function writeSurgeonAttempts(projectDir, state, sessionId) {
|
|
172
178
|
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
@@ -176,6 +182,7 @@ export async function writeSurgeonAttempts(projectDir, state, sessionId) {
|
|
|
176
182
|
const SESSION_READS_FILE = "workflow/session-reads.json";
|
|
177
183
|
/**
|
|
178
184
|
* Read session reads state. Returns empty reads if not found.
|
|
185
|
+
* @deprecated Prefer `DNAStateManager.readSessionReads()`.
|
|
179
186
|
*/
|
|
180
187
|
export async function readSessionReads(projectDir, sessionId) {
|
|
181
188
|
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
@@ -198,6 +205,7 @@ export async function writeSessionReads(projectDir, state, sessionId) {
|
|
|
198
205
|
}
|
|
199
206
|
/**
|
|
200
207
|
* Append a file path to session reads (dedup).
|
|
208
|
+
* @deprecated Prefer `DNAStateManager.appendSessionRead()`.
|
|
201
209
|
*/
|
|
202
210
|
export async function appendSessionRead(projectDir, filePath, sessionId) {
|
|
203
211
|
const state = await readSessionReads(projectDir, sessionId);
|
|
@@ -66,6 +66,10 @@ const VALID_EVENTS = new Set([
|
|
|
66
66
|
"SubagentStop", "PreCompact", "Notification", "SessionStart",
|
|
67
67
|
]);
|
|
68
68
|
function dispatchEnforce(event, ir, input, roles) {
|
|
69
|
+
const result = dispatchEnforceInner(event, ir, input, roles);
|
|
70
|
+
return result?.output ?? { continue: true, suppressOutput: true };
|
|
71
|
+
}
|
|
72
|
+
function dispatchEnforceInner(event, ir, input, roles) {
|
|
69
73
|
switch (event) {
|
|
70
74
|
case "PreToolUse":
|
|
71
75
|
return enforcePreToolUse(ir, {
|
|
@@ -100,10 +104,10 @@ function dispatchEnforce(event, ir, input, roles) {
|
|
|
100
104
|
case "SessionStart":
|
|
101
105
|
return enforceSessionStart(ir, {
|
|
102
106
|
cwd: typeof input.cwd === "string" ? input.cwd : undefined,
|
|
103
|
-
|
|
107
|
+
sessionId: typeof input.session_id === "string" ? input.session_id : undefined,
|
|
104
108
|
});
|
|
105
109
|
default:
|
|
106
|
-
return
|
|
110
|
+
return null;
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
// ── Tool Registration ───────────────────────────────────────
|
|
@@ -131,9 +131,9 @@ context_files:
|
|
|
131
131
|
- "docs/behavior/{{ARGUMENTS}}.md"
|
|
132
132
|
analysis_reviewer:
|
|
133
133
|
- "docs/behavior/{{ARGUMENTS}}.md"
|
|
134
|
-
- ".
|
|
134
|
+
- ".dna/specs/diagnosis-{{ARGUMENTS}}.md"
|
|
135
135
|
surgeon:
|
|
136
|
-
- ".
|
|
136
|
+
- ".dna/specs/diagnosis-{{ARGUMENTS}}.md"
|
|
137
137
|
test_runner:
|
|
138
138
|
- "docs/behavior/{{ARGUMENTS}}.md"
|
|
139
139
|
|
|
@@ -201,7 +201,7 @@ roles:
|
|
|
201
201
|
read: ["**/*"]
|
|
202
202
|
write: ["lib/**", "v2/**", "test/**"]
|
|
203
203
|
instructions:
|
|
204
|
-
- "REQUIRED FIRST: Read context files — CLAUDE.md, docs/refactoring-workflow-v2.md, v2/docs/PROVIDER_DESIGN.md, and the diagnosis spec (.
|
|
204
|
+
- "REQUIRED FIRST: Read context files — CLAUDE.md, docs/refactoring-workflow-v2.md, v2/docs/PROVIDER_DESIGN.md, and the diagnosis spec (.dna/specs/diagnosis-$ARGUMENTS.md). Then read v1 corresponding file before any Edit. Hook will block Edit if context files are not read."
|
|
205
205
|
- Fix only the identified breakpoint or missing implementation
|
|
206
206
|
- Read v1 to understand intent, rewrite in v2 framework style
|
|
207
207
|
- Do not copy v1 code verbatim — adapt to v2 architecture
|
|
@@ -235,17 +235,17 @@ roles:
|
|
|
235
235
|
|
|
236
236
|
# ── v2 角色: diagnosis + fix workflow ──
|
|
237
237
|
analyzer:
|
|
238
|
-
description: "Reads code and classifies failing tests.
|
|
238
|
+
description: "Reads code and classifies failing tests. Writes diagnosis spec only."
|
|
239
239
|
tool_permissions:
|
|
240
|
-
allow: [Read, Grep, Glob]
|
|
241
|
-
deny: [Bash, Edit,
|
|
240
|
+
allow: [Read, Grep, Glob, Write]
|
|
241
|
+
deny: [Bash, Edit, NotebookEdit]
|
|
242
242
|
scope:
|
|
243
243
|
read: ["**/*"]
|
|
244
|
-
write: []
|
|
244
|
+
write: [".dna/specs/**"]
|
|
245
245
|
instructions:
|
|
246
246
|
- "REQUIRED FIRST: Read all context files listed in SKILL.md"
|
|
247
|
-
- "For each failing test: read test code + v1 impl + v2 impl"
|
|
248
|
-
- "Classify each
|
|
247
|
+
- "For each failing, skipped, and hung test: read test code + v1 impl + v2 impl"
|
|
248
|
+
- "Classify each as BUG / UNIMPLEMENTED / INFRA / REMOVED / TEST_BUG (skipped tests are usually UNIMPLEMENTED)"
|
|
249
249
|
- "Output diagnosis spec with v1 code snippets + v2 current state"
|
|
250
250
|
- "DO NOT run tests. DO NOT edit code. Analysis only."
|
|
251
251
|
|
|
@@ -324,6 +324,10 @@ workflows:
|
|
|
324
324
|
- Do NOT use `sleep N && check` polling — run commands in foreground
|
|
325
325
|
- Do NOT use `timeout Nm flutter test` — let tests run to completion
|
|
326
326
|
- Do NOT rewrite existing test files from scratch — update incrementally
|
|
327
|
+
|
|
328
|
+
Report results and STOP.
|
|
329
|
+
|
|
330
|
+
Next step for user: Run /dna-frw-diagnosis $ARGUMENTS to analyze failing tests.
|
|
327
331
|
handoff:
|
|
328
332
|
consumes:
|
|
329
333
|
- type: file
|
|
@@ -514,19 +518,19 @@ workflows:
|
|
|
514
518
|
For module $ARGUMENTS, analyze failing tests in baseline:
|
|
515
519
|
1. Read behavior doc: docs/behavior/$ARGUMENTS.md
|
|
516
520
|
2. Read test results from last behavior-lock run
|
|
517
|
-
3. For each failing test:
|
|
521
|
+
3. For each failing, skipped, and hung test:
|
|
518
522
|
a. Read test code (what behavior does it expect?)
|
|
519
523
|
b. Read v1 implementation (how did v1 do this?)
|
|
520
524
|
c. Read v2 current state (what's missing/wrong?)
|
|
521
|
-
d. Classify: BUG / INFRA / REMOVED / TEST_BUG
|
|
522
|
-
4. Write .
|
|
525
|
+
d. Classify: BUG / UNIMPLEMENTED / INFRA / REMOVED / TEST_BUG
|
|
526
|
+
4. Write .dna/specs/diagnosis-$ARGUMENTS.md with:
|
|
523
527
|
- For each failure: v1 code snippet + v2 current state + classification
|
|
524
528
|
- NO fix prescriptions (surgeon decides how to implement)
|
|
525
529
|
- NO running tests (analysis only)
|
|
526
530
|
handoff:
|
|
527
531
|
produces:
|
|
528
532
|
- type: file
|
|
529
|
-
path: ".
|
|
533
|
+
path: ".dna/specs/diagnosis-$ARGUMENTS.md"
|
|
530
534
|
description: "Diagnosis spec for module"
|
|
531
535
|
|
|
532
536
|
- id: review
|
|
@@ -535,7 +539,7 @@ workflows:
|
|
|
535
539
|
max_attempts: 2
|
|
536
540
|
description: "Review analyzer output quality"
|
|
537
541
|
prompt: |
|
|
538
|
-
Read context files + .
|
|
542
|
+
Read context files + .dna/specs/diagnosis-$ARGUMENTS.md
|
|
539
543
|
|
|
540
544
|
Verify:
|
|
541
545
|
1. All failing tests from baseline covered?
|
|
@@ -548,7 +552,7 @@ workflows:
|
|
|
548
552
|
handoff:
|
|
549
553
|
consumes:
|
|
550
554
|
- type: file
|
|
551
|
-
path: ".
|
|
555
|
+
path: ".dna/specs/diagnosis-$ARGUMENTS.md"
|
|
552
556
|
description: "Diagnosis spec from analyzer"
|
|
553
557
|
produces:
|
|
554
558
|
- type: summary
|
|
@@ -572,11 +576,12 @@ workflows:
|
|
|
572
576
|
|
|
573
577
|
Read context + diagnosis spec first.
|
|
574
578
|
|
|
575
|
-
Process issues in priority order: INFRA → BUG → TEST_BUG
|
|
579
|
+
Process issues in priority order: INFRA → BUG → UNIMPLEMENTED → TEST_BUG
|
|
576
580
|
(REMOVED skipped — needs user confirmation)
|
|
577
581
|
|
|
578
582
|
For each issue:
|
|
579
583
|
- BUG: read v1 file, edit v2 (use Riverpod per refactoring-workflow-v2.md)
|
|
584
|
+
- UNIMPLEMENTED: read v1 impl, implement in v2 style, remove skip marker from test
|
|
580
585
|
- INFRA: edit test_helpers only, do NOT touch v2/lib
|
|
581
586
|
- TEST_BUG: re-read v1, edit test to match v1 behavior
|
|
582
587
|
|
|
@@ -588,7 +593,7 @@ workflows:
|
|
|
588
593
|
handoff:
|
|
589
594
|
consumes:
|
|
590
595
|
- type: file
|
|
591
|
-
path: ".
|
|
596
|
+
path: ".dna/specs/diagnosis-$ARGUMENTS.md"
|
|
592
597
|
description: "Diagnosis spec"
|
|
593
598
|
produces:
|
|
594
599
|
- type: git_commit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "intentdna",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.15",
|
|
4
4
|
"description": "Intent DNA — Declarative policy layer for AI agent behavior",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"license": "Apache-2.0",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "https://github.com/lushan1314/intentdna.git"
|
|
23
|
+
"url": "git+https://github.com/lushan1314/intentdna-repo.git"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"ai",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
],
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^25.3.3",
|
|
44
|
+
"fast-check": "^4.7.0",
|
|
44
45
|
"typescript": "^5.9.3",
|
|
45
46
|
"vitest": "^4.0.18"
|
|
46
47
|
}
|
|
@@ -93,6 +93,8 @@ npm test
|
|
|
93
93
|
**测试**: +20
|
|
94
94
|
**破坏性**: 无(纯增量)
|
|
95
95
|
|
|
96
|
+
**实施状态(2026-04-20 更新)**: 与 Phase 1B 合并完成,产出 +108 测试(schema+contract ≈18 属 1A,其余属 1B 断言重写)。评审 GO with reservations。未提交,3 个 blocker 待修(见本 spec 末尾"Phase 1A+1B 合并 Followup"章节)。
|
|
97
|
+
|
|
96
98
|
### 1A.1 新建 src/hooks/schema.ts
|
|
97
99
|
|
|
98
100
|
```typescript
|
|
@@ -163,6 +165,8 @@ npm test # 全绿,+20 新测试
|
|
|
163
165
|
**破坏性**: 是(限定在此 phase)
|
|
164
166
|
**依赖**: Phase 1A
|
|
165
167
|
|
|
168
|
+
**实施状态(2026-04-20 更新)**: 与 Phase 1A 一起完成(autopilot 超范围)。类型/签名/断言重写都到位,未提交。
|
|
169
|
+
|
|
166
170
|
### 1B.1 protocol.ts 新增类型
|
|
167
171
|
|
|
168
172
|
```typescript
|
|
@@ -261,13 +265,15 @@ npm test # 全绿,+15 新测试
|
|
|
261
265
|
|
|
262
266
|
---
|
|
263
267
|
|
|
264
|
-
## Phase 2A: DNAStateManager
|
|
268
|
+
## Phase 2A: DNAStateManager ✓ (2026-04-21)
|
|
265
269
|
|
|
266
270
|
**目标**: 统一所有 state 操作,支持 session reads + surgeon attempts + experience chain
|
|
267
|
-
**测试**: +
|
|
271
|
+
**测试**: +21 (state-manager.test.ts)
|
|
268
272
|
**破坏性**: 无(旧函数 @deprecated 保留)
|
|
269
273
|
**可与 Phase 1A 并行**
|
|
270
274
|
|
|
275
|
+
**实施**: `src/hooks/state-manager.ts` — 类 facade 封装 workflow / audit / surgeon / session reads,新增 experience chain (json + markdown dual-write) 和 init/cleanup/isStale 生命周期。state.ts 全部被 manager 替代的函数加 `@deprecated`。1076 tests pass (1055 existing + 21 new)。
|
|
276
|
+
|
|
271
277
|
### 2A.1 新建 src/hooks/state-manager.ts
|
|
272
278
|
|
|
273
279
|
```typescript
|
|
@@ -343,13 +349,15 @@ npm test # 全绿,+20 新测试
|
|
|
343
349
|
|
|
344
350
|
---
|
|
345
351
|
|
|
346
|
-
## Phase 2B: Context Gate + Reflection Gate + Workflow Boundary
|
|
352
|
+
## Phase 2B: Context Gate + Reflection Gate + Workflow Boundary ✓ (2026-04-21)
|
|
347
353
|
|
|
348
354
|
**目标**: 3 个核心 Harness 加固机制
|
|
349
|
-
**测试**: +
|
|
355
|
+
**测试**: +18 (hooks-harness.test.ts)
|
|
350
356
|
**破坏性**: 无
|
|
351
357
|
**依赖**: Phase 1B (EnforceResult) + Phase 2A (StateManager)
|
|
352
358
|
|
|
359
|
+
**实施**: `src/hooks/cli.ts:handlePreToolGates()` — PreToolUse 前置门,按优先级评估 workflow_boundary 和 context_gate。Context gate 已从 v1.5.12 的 PostToolUse warn 模式迁移到 PreToolUse block 模式(spec 已确认去掉 warn 过渡期)。Reflection gate 保持 v1.5.10 PostToolUse 实现。Session-read 追踪由 PostToolUse Read 路径写入 `.dna/state/sessions/<id>/workflow/session-reads.json`。1094 tests pass (1076 + 18 new)。
|
|
360
|
+
|
|
353
361
|
### 2B.1 Context Gate — src/hooks/cli.ts
|
|
354
362
|
|
|
355
363
|
在 PreToolUse 处理中新增:
|
|
@@ -508,13 +516,20 @@ npm test # 全绿,+15 新测试
|
|
|
508
516
|
|
|
509
517
|
---
|
|
510
518
|
|
|
511
|
-
## Phase 3: Stop Hook 智能编排 +
|
|
519
|
+
## Phase 3: Stop Hook 智能编排 + Property Tests ✓ (2026-04-21)
|
|
512
520
|
|
|
513
|
-
**目标**: Stop hook 从"继续工作"升级为"输出下一步 Agent 调用"
|
|
514
|
-
**测试**: +
|
|
521
|
+
**目标**: Stop hook 从"继续工作"升级为"输出下一步 Agent 调用";property-based 不变量验证
|
|
522
|
+
**测试**: +22 (9 stop-guidance + 13 property)
|
|
515
523
|
**破坏性**: 无
|
|
516
524
|
**依赖**: Phase 2B
|
|
517
525
|
|
|
526
|
+
**实施**:
|
|
527
|
+
- `src/hooks/cli.ts:buildWorkflowGuidance()` — Stop hook 读 workflow state + reflection-config sidecar + surgeon attempts,按 (完成 / surgeon 停滞 / 正常) 三种情况输出 STOP 指令或 `Agent(subagent_type=..., prompt=...)` 调用模板。
|
|
528
|
+
- `test/property/enforce-invariants.test.ts` — fast-check 属性测试 13 条,覆盖 checkContextReadiness 幂等性、checkReflectionLimit 三种分支边界、checkWorkflowBoundary completed=true/false 对偶、extractBashWritePaths / validateHookInput 不抛错 + 归一化一致性。
|
|
529
|
+
- **跳过**: subprocess integration tests(`spawn dist/hooks/cli.js` 全链路)— 需要预先 `npm run build`,留待后续补充。不影响 gate 逻辑验证,因为 buildWorkflowGuidance / handlePreToolGates 都是可直接导入的纯/半纯函数,已有完整单测。
|
|
530
|
+
|
|
531
|
+
1116 tests pass.
|
|
532
|
+
|
|
518
533
|
### 3.1 Stop Hook 智能编排 — src/hooks/cli.ts
|
|
519
534
|
|
|
520
535
|
在 enforceStop 中,读 workflow state 后输出明确的下一步指令:
|
|
@@ -669,27 +684,58 @@ dna sync # 新 agent/skill 生成
|
|
|
669
684
|
- [ ] trace metadata 不进入 HookOutput(CC 协议边界)
|
|
670
685
|
- [ ] mcp/tools-enforce.ts 同步更新
|
|
671
686
|
|
|
672
|
-
### Phase 2A
|
|
673
|
-
- [
|
|
674
|
-
- [
|
|
675
|
-
- [
|
|
676
|
-
- [
|
|
677
|
-
- [
|
|
678
|
-
- [
|
|
679
|
-
|
|
680
|
-
### Phase 2B
|
|
681
|
-
- [
|
|
682
|
-
- [
|
|
683
|
-
- [
|
|
684
|
-
- [
|
|
685
|
-
- [
|
|
686
|
-
|
|
687
|
-
### Phase 3
|
|
688
|
-
- [
|
|
689
|
-
- [ ] Integration tests: spawn dna-hook 全链路
|
|
690
|
-
- [
|
|
687
|
+
### Phase 2A ✓
|
|
688
|
+
- [x] DNAStateManager class 实现
|
|
689
|
+
- [x] Session reads 追踪
|
|
690
|
+
- [x] Surgeon attempts 追踪
|
|
691
|
+
- [x] Experience chain 读写(json + markdown dual-write)
|
|
692
|
+
- [x] init/cleanup/isStale 生命周期
|
|
693
|
+
- [x] state.ts 旧函数 @deprecated 保留
|
|
694
|
+
|
|
695
|
+
### Phase 2B ✓
|
|
696
|
+
- [x] Context gate: block Edit/Bash/Write if context files not read (PreToolUse, block mode)
|
|
697
|
+
- [x] Reflection gate: detect no-progress, warn/handoff/skip (PostToolUse, v1.5.10+)
|
|
698
|
+
- [x] Workflow boundary: block Skill() after workflow completed (wfState.active=false)
|
|
699
|
+
- [x] PostToolUse Read → appendSessionRead
|
|
700
|
+
- [x] parseGreenCount 支持 3 种格式 + null 安全 (v1.5.10+ TEST_PASSED_RE)
|
|
701
|
+
|
|
702
|
+
### Phase 3 ✓ (partial — integration tests deferred)
|
|
703
|
+
- [x] Stop hook 读 workflow state 输出 Agent() 调用指令 (buildWorkflowGuidance)
|
|
704
|
+
- [ ] Integration tests: spawn dna-hook 全链路 (deferred — 需 build 先行)
|
|
705
|
+
- [x] Property tests: 13 个不变量 (fast-check)
|
|
691
706
|
|
|
692
707
|
### Phase 4
|
|
693
708
|
- [ ] dna verify --health 通过
|
|
694
709
|
- [ ] lwk_flutter diagnosis → fix 流程验证
|
|
695
710
|
- [ ] 发版
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## Phase 1A+1B 合并 Followup(2026-04-20 评审产出 → 2026-04-21 已修)
|
|
715
|
+
|
|
716
|
+
autopilot 超范围将 1A+1B 一并完成,3 个 blocker 均已修复:
|
|
717
|
+
|
|
718
|
+
### Blocker 1: schema.ts 接入 cli.ts ✓
|
|
719
|
+
|
|
720
|
+
**实施**: `src/hooks/cli.ts:24,125-137` — 在 `readStdin` 后、任何业务逻辑前调用 `validateHookInput(event, rawStdin)`;无效输入 fail-open(stderr 警告 + silent exit),有效输入使用 `validation.normalized` 作为后续 `rawInput`(session_id 已归一化为 sessionId)。
|
|
721
|
+
|
|
722
|
+
### Blocker 2: matched_rule 语义修正 ✓
|
|
723
|
+
|
|
724
|
+
**实施**: `src/hooks/protocol.ts:100` — MatchedRule 枚举新增 `"validator"`;`src/hooks/enforce.ts:155-160` — audit-only 路径使用 `"validator"`,bash scope 违规仍用 `"scope"`(真实路径匹配)。
|
|
725
|
+
|
|
726
|
+
### Blocker 3: commit 信息如实 ✓
|
|
727
|
+
|
|
728
|
+
**实施**: commit message 明确标注 "Phase 1A+1B 合并(autopilot 超范围)+ 3 blocker 修复"。
|
|
729
|
+
|
|
730
|
+
### Phase 1A/1B 验收全 PASS
|
|
731
|
+
|
|
732
|
+
- [x] schema.ts validateHookInput() 实现 + 接入 cli.ts
|
|
733
|
+
- [x] 8 个真实 CC fixture 文件 + contract tests 全链路通过
|
|
734
|
+
- [x] SessionStartInput snake_case bug 已修
|
|
735
|
+
- [x] EnforceResult + TraceMetadata 类型定义
|
|
736
|
+
- [x] matched_rule 包含 context_gate / reflection_gate / workflow_boundary / validator
|
|
737
|
+
- [x] 8 个 enforce 函数返回 EnforceResult
|
|
738
|
+
- [x] cli.ts 不再猜 decision(读 `result.output`)
|
|
739
|
+
- [x] trace metadata 不进入 HookOutput(CC 协议边界)
|
|
740
|
+
- [x] mcp/tools-enforce.ts 同步更新
|
|
741
|
+
- [x] 全量测试 1055 passed
|