intentdna 1.8.3 → 1.8.5
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 +2 -2
- package/dist/cli/commands/assets.d.ts +1 -0
- package/dist/cli/commands/assets.js +51 -17
- package/dist/cli/commands/sync.js +10 -8
- package/dist/cli/index.js +0 -0
- package/dist/hooks/cli.d.ts +3 -0
- package/dist/hooks/cli.js +108 -15
- package/dist/hooks/event-registry.js +1 -1
- package/dist/hooks/state.d.ts +2 -0
- package/dist/hooks/state.js +4 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/tools-artifacts.d.ts +2 -0
- package/dist/mcp/tools-artifacts.js +137 -0
- package/dist/runtime/claude-sync-target.d.ts +3 -1
- package/dist/runtime/claude-sync-target.js +16 -4
- package/dist/runtime/settings-adapter.d.ts +12 -0
- package/dist/runtime/settings-adapter.js +121 -3
- package/dist/runtime/skill-adapter.js +34 -4
- package/dist/runtime/verifier.d.ts +3 -1
- package/dist/runtime/verifier.js +82 -27
- package/dist/templates/flutter-rewrite.dna.yaml +19 -9
- package/hooks/hooks.json +1 -1
- package/package.json +1 -1
package/dist/runtime/verifier.js
CHANGED
|
@@ -3,6 +3,9 @@ import { readFile, realpath, stat } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
4
4
|
export const DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS = 30_000;
|
|
5
5
|
export const MAX_VERIFIER_EVIDENCE_BYTES = 4096;
|
|
6
|
+
export const VERIFIER_DIAGNOSTIC_PREFIX = "INTENTDNA_DIAGNOSTIC:";
|
|
7
|
+
export const MAX_VERIFIER_DIAGNOSTIC_BYTES = 2048;
|
|
8
|
+
const VERIFIER_DIAGNOSTIC_CODE = /^INTENTDNA_DIAGNOSTIC:[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
|
|
6
9
|
const SAFE_COMMAND_VARIABLE_RE = /^[A-Za-z0-9_-]+$/;
|
|
7
10
|
const SAFE_PATH_VARIABLE_RE = /^[-A-Za-z0-9_./]+$/;
|
|
8
11
|
export function resolveVerifierTemplate(template, variables) {
|
|
@@ -59,7 +62,7 @@ function resolveVerifierCommand(rawCommand, policy, variables) {
|
|
|
59
62
|
return { target: declaredCommand, evidence: "policy_denied", exit_code: 126, message: verifierCommandPolicyMessage(declaredCommand) };
|
|
60
63
|
}
|
|
61
64
|
const safeVariables = validateVerifierTemplateVariables(declaredCommand, variables, "command");
|
|
62
|
-
if (
|
|
65
|
+
if (safeVariables.valid === false) {
|
|
63
66
|
return { target: declaredCommand, evidence: safeVariables.evidence, exit_code: 126, message: safeVariables.message };
|
|
64
67
|
}
|
|
65
68
|
const command = resolveVerifierTemplate(declaredCommand, variables).trim();
|
|
@@ -137,8 +140,8 @@ export function isVerifierCommandAllowed(policy, command) {
|
|
|
137
140
|
return false;
|
|
138
141
|
return policy.allow_command_prefixes?.some((prefix) => normalized.startsWith(prefix)) ?? false;
|
|
139
142
|
}
|
|
140
|
-
export function verifierCommandPolicyMessage(
|
|
141
|
-
return
|
|
143
|
+
export function verifierCommandPolicyMessage(_command) {
|
|
144
|
+
return "Verifier command not allowed by verifier_policy";
|
|
142
145
|
}
|
|
143
146
|
export function isBuiltinAssertAllowed(policy, assertName) {
|
|
144
147
|
return policy?.allow_builtin_asserts?.includes(assertName) ?? false;
|
|
@@ -156,12 +159,52 @@ export function trimVerifierEvidence(raw) {
|
|
|
156
159
|
? trimmed.slice(0, MAX_VERIFIER_EVIDENCE_BYTES)
|
|
157
160
|
: trimmed;
|
|
158
161
|
}
|
|
162
|
+
function sanitizeVerifierDiagnosticLine(raw) {
|
|
163
|
+
return raw
|
|
164
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000D\u000E-\u001F\u007F]/g, "")
|
|
165
|
+
.trim();
|
|
166
|
+
}
|
|
167
|
+
function trimDiagnosticBytes(raw) {
|
|
168
|
+
const marker = "...[truncated]";
|
|
169
|
+
if (Buffer.byteLength(raw, "utf8") <= MAX_VERIFIER_DIAGNOSTIC_BYTES)
|
|
170
|
+
return raw;
|
|
171
|
+
let trimmed = raw;
|
|
172
|
+
while (trimmed.length > 0 && Buffer.byteLength(`${trimmed}${marker}`, "utf8") > MAX_VERIFIER_DIAGNOSTIC_BYTES) {
|
|
173
|
+
trimmed = trimmed.slice(0, -1);
|
|
174
|
+
}
|
|
175
|
+
return `${trimmed}${marker}`;
|
|
176
|
+
}
|
|
177
|
+
function extractVerifierDiagnostics(raw) {
|
|
178
|
+
return raw
|
|
179
|
+
.split(/\r?\n/)
|
|
180
|
+
.map((line) => sanitizeVerifierDiagnosticLine(line))
|
|
181
|
+
.filter((line) => VERIFIER_DIAGNOSTIC_CODE.test(line));
|
|
182
|
+
}
|
|
183
|
+
function collectVerifierDiagnostics(pendingLine, chunk, diagnostics) {
|
|
184
|
+
const lines = `${pendingLine}${chunk}`.split("\n");
|
|
185
|
+
const nextPendingLine = lines.pop() ?? "";
|
|
186
|
+
diagnostics.push(...extractVerifierDiagnostics(lines.join("\n")));
|
|
187
|
+
return nextPendingLine.length > MAX_VERIFIER_EVIDENCE_BYTES
|
|
188
|
+
? nextPendingLine.slice(0, MAX_VERIFIER_EVIDENCE_BYTES)
|
|
189
|
+
: nextPendingLine;
|
|
190
|
+
}
|
|
191
|
+
function summarizeCommandEvidenceFromStats(stdoutBytes, stderrBytes, diagnostics = []) {
|
|
192
|
+
const summary = stdoutBytes === 0 && stderrBytes === 0
|
|
193
|
+
? undefined
|
|
194
|
+
: `stdout_bytes=${stdoutBytes} stderr_bytes=${stderrBytes}`;
|
|
195
|
+
const diagnostic = diagnostics.length > 0
|
|
196
|
+
? trimDiagnosticBytes(diagnostics.join("\n"))
|
|
197
|
+
: undefined;
|
|
198
|
+
return trimVerifierEvidence([summary, diagnostic].filter(Boolean).join("\n"));
|
|
199
|
+
}
|
|
159
200
|
export function summarizeCommandEvidence(stdout, stderr) {
|
|
160
201
|
const stdoutBytes = Buffer.byteLength(stdout, "utf8");
|
|
161
202
|
const stderrBytes = Buffer.byteLength(stderr, "utf8");
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
203
|
+
return summarizeCommandEvidenceFromStats(stdoutBytes, stderrBytes, extractVerifierDiagnostics(`${stdout}\n${stderr}`));
|
|
204
|
+
}
|
|
205
|
+
function appendFailureDiagnostic(message, evidence) {
|
|
206
|
+
const diagnostics = extractVerifierDiagnostics(evidence ?? "").join("\n");
|
|
207
|
+
return diagnostics ? `${message}\n${diagnostics}` : message;
|
|
165
208
|
}
|
|
166
209
|
export async function execVerifierCommand(projectDir, command, timeoutMs = DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS, env) {
|
|
167
210
|
return new Promise((resolvePromise) => {
|
|
@@ -170,9 +213,12 @@ export async function execVerifierCommand(projectDir, command, timeoutMs = DEFAU
|
|
|
170
213
|
stdio: ["ignore", "pipe", "pipe"],
|
|
171
214
|
env: { ...process.env, ...(env ?? {}) },
|
|
172
215
|
});
|
|
173
|
-
let
|
|
174
|
-
let
|
|
216
|
+
let stdoutPendingLine = "";
|
|
217
|
+
let stderrPendingLine = "";
|
|
218
|
+
let stdoutBytes = 0;
|
|
219
|
+
let stderrBytes = 0;
|
|
175
220
|
let settled = false;
|
|
221
|
+
const diagnostics = [];
|
|
176
222
|
const settle = (result) => {
|
|
177
223
|
if (settled)
|
|
178
224
|
return;
|
|
@@ -183,29 +229,36 @@ export async function execVerifierCommand(projectDir, command, timeoutMs = DEFAU
|
|
|
183
229
|
const timeoutId = setTimeout(() => {
|
|
184
230
|
child.kill("SIGTERM");
|
|
185
231
|
setTimeout(() => child.kill("SIGKILL"), 1000).unref();
|
|
186
|
-
|
|
232
|
+
diagnostics.push(...extractVerifierDiagnostics(`${stdoutPendingLine}\n${stderrPendingLine}`));
|
|
233
|
+
settle({ passed: false, timedOut: true, exitCode: 124, evidence: summarizeCommandEvidenceFromStats(stdoutBytes, stderrBytes, diagnostics) });
|
|
187
234
|
}, timeoutMs);
|
|
188
235
|
child.stdout.on("data", (chunk) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
236
|
+
const text = String(chunk);
|
|
237
|
+
stdoutBytes += Buffer.byteLength(text, "utf8");
|
|
238
|
+
stdoutPendingLine = collectVerifierDiagnostics(stdoutPendingLine, text, diagnostics);
|
|
192
239
|
});
|
|
193
240
|
child.stderr.on("data", (chunk) => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
241
|
+
const text = String(chunk);
|
|
242
|
+
stderrBytes += Buffer.byteLength(text, "utf8");
|
|
243
|
+
stderrPendingLine = collectVerifierDiagnostics(stderrPendingLine, text, diagnostics);
|
|
197
244
|
});
|
|
198
245
|
child.on("error", () => settle({
|
|
199
246
|
passed: false,
|
|
200
247
|
timedOut: false,
|
|
201
248
|
exitCode: 1,
|
|
202
|
-
evidence:
|
|
249
|
+
evidence: summarizeCommandEvidenceFromStats(stdoutBytes, stderrBytes, [
|
|
250
|
+
...diagnostics,
|
|
251
|
+
...extractVerifierDiagnostics(`${stdoutPendingLine}\n${stderrPendingLine}`),
|
|
252
|
+
]),
|
|
203
253
|
}));
|
|
204
254
|
child.on("close", (code) => settle({
|
|
205
255
|
passed: code === 0,
|
|
206
256
|
timedOut: false,
|
|
207
257
|
exitCode: code ?? 1,
|
|
208
|
-
evidence:
|
|
258
|
+
evidence: summarizeCommandEvidenceFromStats(stdoutBytes, stderrBytes, [
|
|
259
|
+
...diagnostics,
|
|
260
|
+
...extractVerifierDiagnostics(`${stdoutPendingLine}\n${stderrPendingLine}`),
|
|
261
|
+
]),
|
|
209
262
|
}));
|
|
210
263
|
});
|
|
211
264
|
}
|
|
@@ -296,7 +349,7 @@ export async function runCompletionVerifier(completion, ir, context) {
|
|
|
296
349
|
}
|
|
297
350
|
if (completion.file_exists) {
|
|
298
351
|
const safeVariables = validateVerifierTemplateVariables(completion.file_exists, context.variables, "path");
|
|
299
|
-
if (
|
|
352
|
+
if (safeVariables.valid === false)
|
|
300
353
|
return { passed: false, target: completion.file_exists, evidence: safeVariables.evidence, message: safeVariables.message };
|
|
301
354
|
const target = resolveVerifierTemplate(completion.file_exists, context.variables);
|
|
302
355
|
if (hasUnresolvedVerifierTemplate(target))
|
|
@@ -316,7 +369,7 @@ export async function runCompletionVerifier(completion, ir, context) {
|
|
|
316
369
|
}
|
|
317
370
|
if (completion.file_not_empty) {
|
|
318
371
|
const safeVariables = validateVerifierTemplateVariables(completion.file_not_empty, context.variables, "path");
|
|
319
|
-
if (
|
|
372
|
+
if (safeVariables.valid === false)
|
|
320
373
|
return { passed: false, target: completion.file_not_empty, evidence: safeVariables.evidence, message: safeVariables.message };
|
|
321
374
|
const target = resolveVerifierTemplate(completion.file_not_empty, context.variables);
|
|
322
375
|
if (hasUnresolvedVerifierTemplate(target))
|
|
@@ -338,10 +391,10 @@ export async function runCompletionVerifier(completion, ir, context) {
|
|
|
338
391
|
}
|
|
339
392
|
if (completion.file_contains) {
|
|
340
393
|
const safeTargetVariables = validateVerifierTemplateVariables(completion.file_contains.path, context.variables, "path");
|
|
341
|
-
if (
|
|
394
|
+
if (safeTargetVariables.valid === false)
|
|
342
395
|
return { passed: false, target: completion.file_contains.path, evidence: safeTargetVariables.evidence, message: safeTargetVariables.message };
|
|
343
396
|
const safePatternVariables = validateVerifierTemplateVariables(completion.file_contains.pattern, context.variables);
|
|
344
|
-
if (
|
|
397
|
+
if (safePatternVariables.valid === false)
|
|
345
398
|
return { passed: false, target: completion.file_contains.pattern, evidence: safePatternVariables.evidence, message: safePatternVariables.message };
|
|
346
399
|
const target = resolveVerifierTemplate(completion.file_contains.path, context.variables);
|
|
347
400
|
const pattern = resolveVerifierTemplate(completion.file_contains.pattern, context.variables);
|
|
@@ -379,8 +432,8 @@ export async function runCompletionVerifier(completion, ir, context) {
|
|
|
379
432
|
message: commandResult.passed
|
|
380
433
|
? undefined
|
|
381
434
|
: commandResult.timedOut
|
|
382
|
-
? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms
|
|
383
|
-
:
|
|
435
|
+
? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms`
|
|
436
|
+
: appendFailureDiagnostic("Verifier command failed", commandResult.evidence),
|
|
384
437
|
};
|
|
385
438
|
}
|
|
386
439
|
export async function runCheckpointVerifier(checkpoint, ir, context) {
|
|
@@ -405,8 +458,8 @@ export async function runCheckpointVerifier(checkpoint, ir, context) {
|
|
|
405
458
|
message: commandResult.passed
|
|
406
459
|
? checkpoint.message
|
|
407
460
|
: commandResult.timedOut
|
|
408
|
-
? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms
|
|
409
|
-
:
|
|
461
|
+
? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms`
|
|
462
|
+
: appendFailureDiagnostic("Verifier command failed", commandResult.evidence),
|
|
410
463
|
};
|
|
411
464
|
}
|
|
412
465
|
if (checkpoint.assert === "clean_working_tree") {
|
|
@@ -454,8 +507,10 @@ export async function runCheckpointVerifier(checkpoint, ir, context) {
|
|
|
454
507
|
message: commandResult.passed
|
|
455
508
|
? checkpoint.message
|
|
456
509
|
: commandResult.timedOut
|
|
457
|
-
? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms
|
|
458
|
-
: checkpoint.message
|
|
510
|
+
? `Verifier command timed out after ${context.commandTimeoutMs ?? DEFAULT_VERIFIER_COMMAND_TIMEOUT_MS}ms`
|
|
511
|
+
: checkpoint.message
|
|
512
|
+
? appendFailureDiagnostic(checkpoint.message, commandResult.evidence)
|
|
513
|
+
: appendFailureDiagnostic("Verifier command failed", commandResult.evidence),
|
|
459
514
|
};
|
|
460
515
|
}
|
|
461
516
|
return { passed: false, target: checkpoint.assert, exit_code: 1, message: checkpoint.message };
|
|
@@ -166,7 +166,8 @@ context_files:
|
|
|
166
166
|
|
|
167
167
|
verifier_policy:
|
|
168
168
|
allow_commands:
|
|
169
|
-
- "node -e \"const fs=require('node:fs');const crypto=require('node:crypto');const path=require('node:path');const mod=process.env.ARGUMENTS||'$ARGUMENTS';const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG'];const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG'];const fail=m=>{
|
|
169
|
+
- "node -e \"const fs=require('node:fs');const crypto=require('node:crypto');const path=require('node:path');const mod=process.env.ARGUMENTS||'$ARGUMENTS';const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG'];const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG'];const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)};const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]);const legacySame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds);const modern=s=>s&&typeof s.sidecar_semantic_sha256==='string'&&typeof s.diagnosis_semantic_sha256==='string';const root=fs.realpathSync(process.cwd());const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');if(fs.existsSync(r)){const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r};const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8'));const hash=v=>crypto.createHash('sha256').update(v).digest('hex');const fileHash=p=>hash(fs.readFileSync(inside(p)));const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim());const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v);if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module');const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json';const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json';const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md';const behaviorPath='docs/behavior/'+mod+'.md';const data=readJson(contractPath);if(data.contract_version!=='diagnosis-contract/v1')fail('contract_version');if(data.module!==mod)fail('module_mismatch');if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path');if(!same(data.allowed_kinds,allowed))fail('allowed_kinds');const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures');const work=Array.isArray(data.work_items)?data.work_items:fail('work_items');if(!observed.length)fail('empty_observed_failures');if(!work.length)fail('empty_work_items');const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id);const ids=new Set();for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)}const workIds=new Set();for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)}const failureById=new Map();for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)inside(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)}const mapped=new Set();for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)inside(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');for(const p of item.v1_evidence_paths)inside(p);for(const p of item.v2_evidence_paths)inside(p)}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}}if(mapped.size!==ids.size)fail('unmapped_observed_failures');for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')}const counts=data.baseline&&data.baseline.counts;if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline');for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)}if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch');const summary=data.summary_counts||{};for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)}const snap=data.contract_snapshot||{};const baselineHash=fileHash(data.baseline.source_path);const diagnosisHash=fileHash(diagnosisPath);if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot');if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256');if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256');const stale=data.staleness;if(!stale||stale.is_stale!==false)fail('stale_contract');const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')};source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath));source(stale.baseline,data.baseline.source_path,'baseline',baselineHash);source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash);const semantic=JSON.parse(JSON.stringify(data));delete semantic.contract_snapshot.sidecar_semantic_sha256;delete semantic.contract_snapshot.diagnosis_artifact_sha256;delete semantic.staleness.diagnosis_artifact;const sidecarSemanticHash=hash(canon(semantic));const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath);if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic');if(fs.existsSync(inside(reviewPath))){const prev=readJson(reviewPath);if(prev.verdict==='REQUEST_REANALYSIS'){const ps=prev.contract_snapshot||{};const unchanged=modern(ps)?ps.sidecar_semantic_sha256===sidecarSemanticHash&&ps.diagnosis_semantic_sha256===diagnosisSemanticHash:legacySame(ps,snap);if(unchanged)fail('reanalysis_unchanged');const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(cm<=rm||dm<=rm)fail('reanalysis_not_rewritten')}}\""
|
|
170
|
+
- "node -e \"const fs=require('node:fs');const crypto=require('node:crypto');const path=require('node:path');const mod=process.env.ARGUMENTS||'$ARGUMENTS';const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG'];const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG'];const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)};const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]);const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds);const root=fs.realpathSync(process.cwd());const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');if(fs.existsSync(r)){const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r};const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8'));const hash=v=>crypto.createHash('sha256').update(v).digest('hex');const fileHash=p=>hash(fs.readFileSync(inside(p)));const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim());const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v);if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module');const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json';const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json';const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md';const behaviorPath='docs/behavior/'+mod+'.md';const data=readJson(contractPath);if(data.contract_version!=='diagnosis-contract/v1')fail('contract_version');if(data.module!==mod)fail('module_mismatch');if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path');if(!same(data.allowed_kinds,allowed))fail('allowed_kinds');const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures');const work=Array.isArray(data.work_items)?data.work_items:fail('work_items');if(!observed.length)fail('empty_observed_failures');if(!work.length)fail('empty_work_items');const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id);const ids=new Set();for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)}const workIds=new Set();for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)}const failureById=new Map();for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)inside(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)}const mapped=new Set();for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)inside(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');for(const p of item.v1_evidence_paths)inside(p);for(const p of item.v2_evidence_paths)inside(p)}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}}if(mapped.size!==ids.size)fail('unmapped_observed_failures');for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')}const counts=data.baseline&&data.baseline.counts;if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline');for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)}if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch');const summary=data.summary_counts||{};for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)}const snap=data.contract_snapshot||{};const baselineHash=fileHash(data.baseline.source_path);const diagnosisHash=fileHash(diagnosisPath);if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot');if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256');if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256');const stale=data.staleness;if(!stale||stale.is_stale!==false)fail('stale_contract');const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')};source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath));source(stale.baseline,data.baseline.source_path,'baseline',baselineHash);source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash);const semantic=JSON.parse(JSON.stringify(data));delete semantic.contract_snapshot.sidecar_semantic_sha256;delete semantic.contract_snapshot.diagnosis_artifact_sha256;delete semantic.staleness.diagnosis_artifact;const sidecarSemanticHash=hash(canon(semantic));const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath);if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic');const review=readJson(reviewPath);if(review.contract_version!=='diagnosis-contract-review/v1')fail('review_contract_version');if(review.contract_valid!==true)fail('review_contract_valid');if(review.verdict!=='APPROVE')fail('review_verdict');if(review.contract_artifact_reviewed!==contractPath)fail('review_contract_artifact_path');if(review.artifact_reviewed!==diagnosisPath)fail('review_artifact_path');if(!snapSame(review.contract_snapshot,snap))fail('review_contract_snapshot');if(!review.updated_at||Number.isNaN(Date.parse(review.updated_at)))fail('review_updated_at');if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence_paths');for(const p of review.evidence_paths)inside(p);const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(rm<cm||rm<dm)fail('review_stale');\""
|
|
170
171
|
allow_builtin_asserts:
|
|
171
172
|
- clean_working_tree
|
|
172
173
|
|
|
@@ -248,7 +249,7 @@ roles:
|
|
|
248
249
|
analyzer:
|
|
249
250
|
description: "Reads code and classifies failing tests. Writes diagnosis spec only."
|
|
250
251
|
tool_permissions:
|
|
251
|
-
allow: [Read, Grep, Glob, Write]
|
|
252
|
+
allow: [Read, Grep, Glob, Write, mcp__intentdna__dna_artifact_digest]
|
|
252
253
|
deny: [Bash, Edit, NotebookEdit]
|
|
253
254
|
scope:
|
|
254
255
|
read: ["**/*"]
|
|
@@ -263,13 +264,14 @@ roles:
|
|
|
263
264
|
- "The sidecar allowed_kinds array must be exactly BUG, UNIMPLEMENTED, INFRA, REMOVED, TEST_BUG in that order; PLACEHOLDER_CONTRACT is never a kind"
|
|
264
265
|
- "A placeholder-contract symptom may appear only in observed_failures.symptom and must map to concrete TEST_BUG and/or UNIMPLEMENTED work_items with evidence"
|
|
265
266
|
- "If reanalyzing after REQUEST_REANALYSIS, update every reviewer sections_to_fix item and report sections_updated"
|
|
267
|
+
- "Use mcp__intentdna__dna_artifact_digest for every exact contract SHA-256; never guess a digest or use Bash to compute one"
|
|
266
268
|
- "DO NOT include fix prescriptions, implementation order, call-site instructions, or Notes for Surgeon"
|
|
267
269
|
- "DO NOT run tests. DO NOT edit app code. Analysis only."
|
|
268
270
|
|
|
269
271
|
analysis_reviewer:
|
|
270
272
|
description: "Reviews analyzer output quality and writes durable diagnosis review verdict."
|
|
271
273
|
tool_permissions:
|
|
272
|
-
allow: [Read, Grep, Glob, Write]
|
|
274
|
+
allow: [Read, Grep, Glob, Write, mcp__intentdna__dna_artifact_digest]
|
|
273
275
|
deny: [Bash, Edit, NotebookEdit]
|
|
274
276
|
scope:
|
|
275
277
|
read: ["**/*"]
|
|
@@ -278,6 +280,7 @@ roles:
|
|
|
278
280
|
- "REQUIRED FIRST: Read all context files listed in SKILL.md"
|
|
279
281
|
- "Verify analyzer spec: v1 references exist? Classifications sound?"
|
|
280
282
|
- "Independently validate .dna/specs/diagnosis-$ARGUMENTS.contract.json against the template sidecar contract; do not use Bash"
|
|
283
|
+
- "Use mcp__intentdna__dna_artifact_digest to independently verify exact raw, normalized-text, and canonical-JSON SHA-256 values"
|
|
281
284
|
- "Check missing: any failing test not covered?"
|
|
282
285
|
- "Verify diagnosis remains evidence-only: no fix prescriptions, implementation order, call-site instructions, or Notes for Surgeon"
|
|
283
286
|
- "Output a structured verdict block with contract_version, contract_valid, failure_reason, contract_artifact_reviewed, contract_snapshot, verdict, artifact_reviewed, sections_to_fix, evidence_paths, confidence, summary, and updated_at"
|
|
@@ -486,16 +489,20 @@ workflows:
|
|
|
486
489
|
- observed_failures: one entry per failing/skipped/hung test with id, test_name, symptom, evidence_paths, and no kind field
|
|
487
490
|
- work_items: one or more entries mapping observed_failure_ids to kind, evidence_paths, v1_evidence_paths, and v2_evidence_paths
|
|
488
491
|
- summary_counts: exact count of work_items by BUG / UNIMPLEMENTED / INFRA / REMOVED / TEST_BUG
|
|
489
|
-
- contract_snapshot: baseline_source_sha256, diagnosis_artifact_sha256, observed_failure_count, work_item_count, allowed_kinds
|
|
490
|
-
- staleness:
|
|
492
|
+
- contract_snapshot: baseline_source_sha256, diagnosis_artifact_sha256, sidecar_semantic_sha256, diagnosis_semantic_sha256, observed_failure_count, work_item_count, allowed_kinds
|
|
493
|
+
- staleness: behavior_doc, baseline, and diagnosis_artifact entries each contain the exact project-relative path and current sha256; is_stale is false
|
|
494
|
+
- Obtain hashes with mcp__intentdna__dna_artifact_digest, which is read-only and project-relative: bytes mode for behavior/baseline/diagnosis, whitespace_normalized_text for diagnosis_semantic_sha256, and canonical_json for the sidecar with exclude_json_pointers=["/contract_snapshot/sidecar_semantic_sha256", "/contract_snapshot/diagnosis_artifact_sha256", "/staleness/diagnosis_artifact"] for sidecar_semantic_sha256. Write the returned values back to the sidecar and re-run the canonical digest to confirm it is stable. Never guess hashes.
|
|
491
495
|
7. Contract rules:
|
|
492
496
|
- observed_failures must not be empty; work_items must not be empty.
|
|
493
497
|
- Every observed_failure id must be mapped by at least one work_item.
|
|
494
498
|
- Every work_item kind must be one of the five allowed kinds exactly.
|
|
495
499
|
- PLACEHOLDER_CONTRACT is forbidden as a kind. If a placeholder-contract symptom exists, keep it in observed_failures.symptom and map it to concrete TEST_BUG and/or UNIMPLEMENTED work_items with evidence.
|
|
496
500
|
- REMOVED may be diagnosed but must be marked manual/user-approved before any future fix can execute it.
|
|
497
|
-
- Mixed unknown kinds, missing evidence, count mismatches, missing/changed contract_snapshot, and stale baseline/diagnosis snapshots make the contract invalid.
|
|
501
|
+
- Mixed unknown kinds, missing evidence, count mismatches, missing/changed contract_snapshot, and stale behavior/baseline/diagnosis snapshots make the contract invalid.
|
|
498
502
|
8. If this is a reanalysis after REQUEST_REANALYSIS or DIAGNOSIS_CONTRACT_INVALID, read the reviewer verdict first, update every section listed in sections_to_fix plus the summary table and contract sidecar, and report sections_updated in your final response. Reanalysis is not successful unless both the spec artifact and contract sidecar are rewritten.
|
|
503
|
+
9. Before handing off to review, the producer-side diagnosis contract completion gate must pass. It rejects grouped observations that hide per-test failures, duplicate/bad ids, unmapped observed_failures, missing evidence, count mismatches, bad hashes, stale state, and no-material-change reanalysis after REQUEST_REANALYSIS. Reanalysis compares the canonical sidecar with its self-digest and raw diagnosis snapshot fields omitted, plus a whitespace-normalized diagnosis hash, so a real sidecar-only correction is allowed while formatting-only Markdown changes are not.
|
|
504
|
+
completion:
|
|
505
|
+
- command_success: "node -e \"const fs=require('node:fs');const crypto=require('node:crypto');const path=require('node:path');const mod=process.env.ARGUMENTS||'$ARGUMENTS';const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG'];const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG'];const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)};const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]);const legacySame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds);const modern=s=>s&&typeof s.sidecar_semantic_sha256==='string'&&typeof s.diagnosis_semantic_sha256==='string';const root=fs.realpathSync(process.cwd());const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');if(fs.existsSync(r)){const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r};const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8'));const hash=v=>crypto.createHash('sha256').update(v).digest('hex');const fileHash=p=>hash(fs.readFileSync(inside(p)));const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim());const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v);if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module');const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json';const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json';const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md';const behaviorPath='docs/behavior/'+mod+'.md';const data=readJson(contractPath);if(data.contract_version!=='diagnosis-contract/v1')fail('contract_version');if(data.module!==mod)fail('module_mismatch');if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path');if(!same(data.allowed_kinds,allowed))fail('allowed_kinds');const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures');const work=Array.isArray(data.work_items)?data.work_items:fail('work_items');if(!observed.length)fail('empty_observed_failures');if(!work.length)fail('empty_work_items');const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id);const ids=new Set();for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)}const workIds=new Set();for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)}const failureById=new Map();for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)inside(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)}const mapped=new Set();for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)inside(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');for(const p of item.v1_evidence_paths)inside(p);for(const p of item.v2_evidence_paths)inside(p)}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}}if(mapped.size!==ids.size)fail('unmapped_observed_failures');for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')}const counts=data.baseline&&data.baseline.counts;if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline');for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)}if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch');const summary=data.summary_counts||{};for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)}const snap=data.contract_snapshot||{};const baselineHash=fileHash(data.baseline.source_path);const diagnosisHash=fileHash(diagnosisPath);if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot');if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256');if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256');const stale=data.staleness;if(!stale||stale.is_stale!==false)fail('stale_contract');const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')};source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath));source(stale.baseline,data.baseline.source_path,'baseline',baselineHash);source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash);const semantic=JSON.parse(JSON.stringify(data));delete semantic.contract_snapshot.sidecar_semantic_sha256;delete semantic.contract_snapshot.diagnosis_artifact_sha256;delete semantic.staleness.diagnosis_artifact;const sidecarSemanticHash=hash(canon(semantic));const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath);if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic');if(fs.existsSync(inside(reviewPath))){const prev=readJson(reviewPath);if(prev.verdict==='REQUEST_REANALYSIS'){const ps=prev.contract_snapshot||{};const unchanged=modern(ps)?ps.sidecar_semantic_sha256===sidecarSemanticHash&&ps.diagnosis_semantic_sha256===diagnosisSemanticHash:legacySame(ps,snap);if(unchanged)fail('reanalysis_unchanged');const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(cm<=rm||dm<=rm)fail('reanalysis_not_rewritten')}}\""
|
|
499
506
|
handoff:
|
|
500
507
|
produces:
|
|
501
508
|
- type: file
|
|
@@ -527,8 +534,9 @@ workflows:
|
|
|
527
534
|
- summary_counts match work_items by kind
|
|
528
535
|
- every work_item has evidence_paths and only allowed kinds
|
|
529
536
|
- PLACEHOLDER_CONTRACT is rejected as a kind but placeholder-contract symptoms are allowed when mapped to TEST_BUG and/or UNIMPLEMENTED
|
|
530
|
-
- contract_snapshot includes diagnosis_artifact_sha256, baseline_source_sha256, observed_failure_count, work_item_count, and allowed_kinds
|
|
531
|
-
- staleness.is_stale is false; stale
|
|
537
|
+
- contract_snapshot includes diagnosis_artifact_sha256, baseline_source_sha256, sidecar_semantic_sha256, diagnosis_semantic_sha256, observed_failure_count, work_item_count, and allowed_kinds
|
|
538
|
+
- staleness.is_stale is false and behavior_doc, baseline, and diagnosis_artifact path+sha256 entries match current project files; stale source snapshots are invalid
|
|
539
|
+
- independently recompute hashes with mcp__intentdna__dna_artifact_digest using the same bytes, whitespace_normalized_text, and canonical_json modes; canonical_json must omit /contract_snapshot/sidecar_semantic_sha256, /contract_snapshot/diagnosis_artifact_sha256, and /staleness/diagnosis_artifact
|
|
532
540
|
- REMOVED work_items are reviewable diagnosis facts only and must not authorize automatic fix execution
|
|
533
541
|
|
|
534
542
|
Output exactly one structured verdict block:
|
|
@@ -541,6 +549,8 @@ workflows:
|
|
|
541
549
|
"contract_snapshot": {
|
|
542
550
|
"baseline_source_sha256": "sha256 from contract sidecar",
|
|
543
551
|
"diagnosis_artifact_sha256": "sha256 from contract sidecar",
|
|
552
|
+
"sidecar_semantic_sha256": "canonical routing sidecar sha256 from contract sidecar",
|
|
553
|
+
"diagnosis_semantic_sha256": "whitespace-normalized diagnosis sha256 from contract sidecar",
|
|
544
554
|
"observed_failure_count": 0,
|
|
545
555
|
"work_item_count": 0,
|
|
546
556
|
"allowed_kinds": ["BUG", "UNIMPLEMENTED", "INFRA", "REMOVED", "TEST_BUG"]
|
|
@@ -560,7 +570,7 @@ workflows:
|
|
|
560
570
|
APPROVE → diagnosis complete.
|
|
561
571
|
REQUEST_REANALYSIS → sections_to_fix must be passed back to analyze. If contract_valid is false, set failure_reason to DIAGNOSIS_CONTRACT_INVALID and include the exact invalid rule in sections_to_fix. docs/behavior/blocked_items.md is historical context only and must not be treated as the current run verdict.
|
|
562
572
|
completion:
|
|
563
|
-
- command_success: "node -e \"const fs=require('node:fs');const crypto=require('node:crypto');const path=require('node:path');const mod=process.env.ARGUMENTS||'$ARGUMENTS';const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG'];const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG'];const fail=m=>{
|
|
573
|
+
- command_success: "node -e \"const fs=require('node:fs');const crypto=require('node:crypto');const path=require('node:path');const mod=process.env.ARGUMENTS||'$ARGUMENTS';const allowed=['BUG','UNIMPLEMENTED','INFRA','REMOVED','TEST_BUG'];const action=['BUG','UNIMPLEMENTED','INFRA','TEST_BUG'];const fail=m=>{console.error('INTENTDNA_DIAGNOSTIC:'+m);process.exit(1)};const same=(a,b)=>Array.isArray(a)&&a.length===b.length&&a.every((v,i)=>v===b[i]);const snapSame=(a,b)=>a&&b&&a.baseline_source_sha256===b.baseline_source_sha256&&a.diagnosis_artifact_sha256===b.diagnosis_artifact_sha256&&a.sidecar_semantic_sha256===b.sidecar_semantic_sha256&&a.diagnosis_semantic_sha256===b.diagnosis_semantic_sha256&&a.observed_failure_count===b.observed_failure_count&&a.work_item_count===b.work_item_count&&same(a.allowed_kinds,b.allowed_kinds);const root=fs.realpathSync(process.cwd());const inside=p=>{if(typeof p!=='string'||!p||path.isAbsolute(p))fail('path_invalid');const r=path.resolve(root,p);if(r!==root&&!r.startsWith(root+path.sep))fail('path_escape');if(fs.existsSync(r)){const real=fs.realpathSync(r);if(real!==root&&!real.startsWith(root+path.sep))fail('path_escape')}return r};const readJson=p=>JSON.parse(fs.readFileSync(inside(p),'utf8'));const hash=v=>crypto.createHash('sha256').update(v).digest('hex');const fileHash=p=>hash(fs.readFileSync(inside(p)));const normalizedDiagnosisHash=p=>hash(fs.readFileSync(inside(p),'utf8').replace(/\\s+/g,' ').trim());const canon=v=>Array.isArray(v)?'['+v.map(canon).join(',')+']':v&&typeof v==='object'?'{'+Object.keys(v).sort().map(k=>JSON.stringify(k)+':'+canon(v[k])).join(',')+'}':JSON.stringify(v);if(!/^[A-Za-z0-9_-]+$/.test(mod))fail('module');const contractPath='.dna/specs/diagnosis-'+mod+'.contract.json';const reviewPath='.dna/specs/diagnosis-'+mod+'.review.json';const diagnosisPath='.dna/specs/diagnosis-'+mod+'.md';const behaviorPath='docs/behavior/'+mod+'.md';const data=readJson(contractPath);if(data.contract_version!=='diagnosis-contract/v1')fail('contract_version');if(data.module!==mod)fail('module_mismatch');if(data.diagnosis_artifact!==diagnosisPath)fail('diagnosis_artifact_path');if(!same(data.allowed_kinds,allowed))fail('allowed_kinds');const observed=Array.isArray(data.observed_failures)?data.observed_failures:fail('observed_failures');const work=Array.isArray(data.work_items)?data.work_items:fail('work_items');if(!observed.length)fail('empty_observed_failures');if(!work.length)fail('empty_work_items');const idOk=id=>typeof id==='string'&&/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(id);const ids=new Set();for(const f of observed){if(!idOk(f&&f.id)||ids.has(f.id))fail('observed_failure_ids');ids.add(f.id)}const workIds=new Set();for(const item of work){if(!idOk(item&&item.id)||workIds.has(item.id))fail('work_item_ids');workIds.add(item.id)}const failureById=new Map();for(const f of observed){if(typeof f.test_name!=='string'||!f.test_name.trim())fail('observed_failure_test_name');if(typeof f.symptom!=='string'||!f.symptom.trim())fail('observed_failure_symptom');if(!Array.isArray(f.evidence_paths)||!f.evidence_paths.length)fail('observed_failure_evidence');for(const p of f.evidence_paths)inside(p);if(f.kind!==undefined)fail('observed_failure_kind_forbidden');failureById.set(f.id,f)}const mapped=new Set();for(const item of work){if(!allowed.includes(item.kind))fail('invalid_kind');if(item.kind==='PLACEHOLDER_CONTRACT')fail('placeholder_kind');if(!Array.isArray(item.observed_failure_ids)||!item.observed_failure_ids.length)fail('work_item_observed_failure_ids');if(!Array.isArray(item.evidence_paths)||!item.evidence_paths.length)fail('work_item_evidence');for(const p of item.evidence_paths)inside(p);if(action.includes(item.kind)){if(!Array.isArray(item.v1_evidence_paths)||!item.v1_evidence_paths.length)fail('work_item_v1_evidence');if(!Array.isArray(item.v2_evidence_paths)||!item.v2_evidence_paths.length)fail('work_item_v2_evidence');for(const p of item.v1_evidence_paths)inside(p);for(const p of item.v2_evidence_paths)inside(p)}for(const id of item.observed_failure_ids){const f=failureById.get(id);if(!f)fail('unknown_observed_failure_id');if(action.includes(item.kind)&&(!f.test_name||!String(f.test_name).trim()))fail('action_test_evidence');mapped.add(id)}}if(mapped.size!==ids.size)fail('unmapped_observed_failures');for(const f of observed.filter(x=>String(x.symptom).includes('PLACEHOLDER_CONTRACT'))){const kinds=new Set(work.filter(i=>i.observed_failure_ids.includes(f.id)).map(i=>i.kind));if(![...kinds].every(k=>k==='TEST_BUG'||k==='UNIMPLEMENTED')||kinds.size===0)fail('placeholder_mapping')}const counts=data.baseline&&data.baseline.counts;if(!data.baseline||!data.baseline.source_path||!counts)fail('baseline');for(const k of ['failed','skipped','hung']){if(!Number.isInteger(counts[k])||counts[k]<0)fail('baseline_count_'+k)}if(counts.failed+counts.skipped+counts.hung!==observed.length)fail('baseline_count_mismatch');const summary=data.summary_counts||{};for(const kind of allowed){if(!Number.isInteger(summary[kind])||summary[kind]!==work.filter(x=>x.kind===kind).length)fail('summary_count_'+kind)}const snap=data.contract_snapshot||{};const baselineHash=fileHash(data.baseline.source_path);const diagnosisHash=fileHash(diagnosisPath);if(snap.observed_failure_count!==observed.length||snap.work_item_count!==work.length||!same(snap.allowed_kinds,allowed))fail('contract_snapshot');if(snap.baseline_source_sha256!==baselineHash)fail('baseline_sha256');if(snap.diagnosis_artifact_sha256!==diagnosisHash)fail('diagnosis_sha256');const stale=data.staleness;if(!stale||stale.is_stale!==false)fail('stale_contract');const source=(entry,p,n,expected)=>{if(!entry||entry.path!==p)fail('staleness_'+n+'_path');if(entry.sha256!==expected)fail('staleness_'+n+'_sha256')};source(stale.behavior_doc,behaviorPath,'behavior_doc',fileHash(behaviorPath));source(stale.baseline,data.baseline.source_path,'baseline',baselineHash);source(stale.diagnosis_artifact,diagnosisPath,'diagnosis_artifact',diagnosisHash);const semantic=JSON.parse(JSON.stringify(data));delete semantic.contract_snapshot.sidecar_semantic_sha256;delete semantic.contract_snapshot.diagnosis_artifact_sha256;delete semantic.staleness.diagnosis_artifact;const sidecarSemanticHash=hash(canon(semantic));const diagnosisSemanticHash=normalizedDiagnosisHash(diagnosisPath);if(snap.sidecar_semantic_sha256!==sidecarSemanticHash||snap.diagnosis_semantic_sha256!==diagnosisSemanticHash)fail('contract_snapshot_semantic');const review=readJson(reviewPath);if(review.contract_version!=='diagnosis-contract-review/v1')fail('review_contract_version');if(review.contract_valid!==true)fail('review_contract_valid');if(review.verdict!=='APPROVE')fail('review_verdict');if(review.contract_artifact_reviewed!==contractPath)fail('review_contract_artifact_path');if(review.artifact_reviewed!==diagnosisPath)fail('review_artifact_path');if(!snapSame(review.contract_snapshot,snap))fail('review_contract_snapshot');if(!review.updated_at||Number.isNaN(Date.parse(review.updated_at)))fail('review_updated_at');if(!Array.isArray(review.evidence_paths)||!review.evidence_paths.length)fail('review_evidence_paths');for(const p of review.evidence_paths)inside(p);const cm=fs.statSync(inside(contractPath)).mtimeMs;const dm=fs.statSync(inside(diagnosisPath)).mtimeMs;const rm=fs.statSync(inside(reviewPath)).mtimeMs;if(rm<cm||rm<dm)fail('review_stale');\""
|
|
564
574
|
handoff:
|
|
565
575
|
consumes:
|
|
566
576
|
- type: file
|
package/hooks/hooks.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"PreToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook PreToolUse", "timeout": 5 }] }],
|
|
4
4
|
"PostToolUse": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook PostToolUse", "timeout": 3 }] }],
|
|
5
5
|
"UserPromptSubmit": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook UserPromptSubmit", "timeout": 5 }] }],
|
|
6
|
-
"SubagentStop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook SubagentStop", "timeout":
|
|
6
|
+
"SubagentStop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook SubagentStop", "timeout": 45 }] }],
|
|
7
7
|
"PreCompact": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook PreCompact", "timeout": 3 }] }],
|
|
8
8
|
"Notification": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook Notification", "timeout": 3 }] }],
|
|
9
9
|
"Stop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "dna-hook Stop", "timeout": 45 }] }],
|