intentdna 1.5.11 → 1.5.12
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/compiler/cascade.d.ts +5 -0
- package/dist/compiler/cascade.js +8 -0
- package/dist/compiler/compile.js +1 -0
- package/dist/hooks/cli.js +57 -2
- package/dist/hooks/enforce.d.ts +9 -0
- package/dist/hooks/enforce.js +9 -0
- package/dist/hooks/state.d.ts +17 -0
- package/dist/hooks/state.js +33 -0
- package/dist/runtime/skill-adapter.js +35 -0
- package/dist/schema/types.d.ts +10 -0
- package/dist/schema/validate.js +27 -0
- package/dist/templates/flutter-rewrite.dna.yaml +185 -3
- package/package.json +1 -1
|
@@ -20,6 +20,11 @@ export interface CascadedDNA {
|
|
|
20
20
|
source_ids: string[];
|
|
21
21
|
/** Warnings from cascade process (e.g., cross-namespace gene collisions) */
|
|
22
22
|
warnings?: string[];
|
|
23
|
+
context_files?: {
|
|
24
|
+
mandatory?: string[];
|
|
25
|
+
per_role?: Record<string, string[]>;
|
|
26
|
+
per_workflow?: Record<string, string[]>;
|
|
27
|
+
};
|
|
23
28
|
}
|
|
24
29
|
/**
|
|
25
30
|
* Cascade multiple DNA layers into a single merged DNA.
|
package/dist/compiler/cascade.js
CHANGED
|
@@ -156,6 +156,13 @@ export function cascadeDNA(layers) {
|
|
|
156
156
|
allMarkers.push(...dna.epigenetic.markers);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
// context_files: highest-priority DNA wins (last in sorted layers)
|
|
160
|
+
let contextFiles;
|
|
161
|
+
for (const dna of sorted) {
|
|
162
|
+
if (dna.context_files) {
|
|
163
|
+
contextFiles = dna.context_files;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
159
166
|
return {
|
|
160
167
|
genes: mergedGenes,
|
|
161
168
|
contexts: mergedContexts,
|
|
@@ -164,5 +171,6 @@ export function cascadeDNA(layers) {
|
|
|
164
171
|
epigenetic_markers: allMarkers,
|
|
165
172
|
source_ids: sourceIds,
|
|
166
173
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
174
|
+
context_files: contextFiles,
|
|
167
175
|
};
|
|
168
176
|
}
|
package/dist/compiler/compile.js
CHANGED
|
@@ -290,5 +290,6 @@ export function compileDNA(activated, cascaded) {
|
|
|
290
290
|
roles_scope_map: rolesScopeMap.length > 0 ? rolesScopeMap : undefined,
|
|
291
291
|
step_checkpoints: stepCheckpoints.length > 0 ? stepCheckpoints : undefined,
|
|
292
292
|
workflows_ir: workflowsIR.length > 0 ? workflowsIR : undefined,
|
|
293
|
+
context_files: cascaded?.context_files,
|
|
293
294
|
};
|
|
294
295
|
}
|
package/dist/hooks/cli.js
CHANGED
|
@@ -18,8 +18,8 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
18
18
|
import { join, resolve } from "node:path";
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
20
20
|
import { readStdin, writeOutput, silentOutput, allowOutput } from "./protocol.js";
|
|
21
|
-
import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, extractBashWritePaths, checkReflectionLimit, } from "./enforce.js";
|
|
22
|
-
import { appendAudit, readWorkflowState, appendTrace, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, writeSurgeonAttempts } from "./state.js";
|
|
21
|
+
import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, extractBashWritePaths, checkReflectionLimit, checkContextReadiness, } from "./enforce.js";
|
|
22
|
+
import { appendAudit, readWorkflowState, appendTrace, rotateTraces, readTraces, cleanStaleState, readSurgeonAttempts, writeSurgeonAttempts, appendSessionRead, readSessionReads } from "./state.js";
|
|
23
23
|
// ── Constants ──────────────────────────────────────────────
|
|
24
24
|
const DEFAULT_IR_PATH = ".dna/compiled/ir.json";
|
|
25
25
|
const VALID_EVENTS = new Set([
|
|
@@ -164,6 +164,34 @@ async function main() {
|
|
|
164
164
|
const durationMs = Date.now() - start;
|
|
165
165
|
// Surgeon reflection gate (PostToolUse only, async)
|
|
166
166
|
if (event === "PostToolUse") {
|
|
167
|
+
// 1. Track session reads
|
|
168
|
+
try {
|
|
169
|
+
const toolName = String(rawInput.tool_name ?? "");
|
|
170
|
+
if (toolName === "Read") {
|
|
171
|
+
const readPath = typeof rawInput.tool_input?.file_path === "string"
|
|
172
|
+
? rawInput.tool_input.file_path : undefined;
|
|
173
|
+
if (readPath) {
|
|
174
|
+
await appendSessionRead(projectDir, readPath, sessionId);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// 2. Context gate (Phase 1: warn mode)
|
|
178
|
+
const writeTools = new Set(["Edit", "Write", "Bash"]);
|
|
179
|
+
if (writeTools.has(toolName)) {
|
|
180
|
+
const wfStateForCtx = await readWorkflowState(projectDir, sessionId);
|
|
181
|
+
if (wfStateForCtx?.active && ir.source_dna_ids.length > 0) {
|
|
182
|
+
const contextGateOutput = await handleContextGate(ir, wfStateForCtx, projectDir, sessionId);
|
|
183
|
+
if (contextGateOutput) {
|
|
184
|
+
if (output.suppressOutput) {
|
|
185
|
+
output = contextGateOutput;
|
|
186
|
+
}
|
|
187
|
+
else if (output.reason) {
|
|
188
|
+
output = { ...output, reason: output.reason + "\n" + (contextGateOutput.reason ?? "") };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch { /* fail-open */ }
|
|
167
195
|
try {
|
|
168
196
|
const reflectionOutput = await handleSurgeonReflection(ir, rawInput, projectDir, sessionId);
|
|
169
197
|
if (reflectionOutput) {
|
|
@@ -476,6 +504,33 @@ async function loadReflectionConfig(projectDir, _workflowName, _stepId) {
|
|
|
476
504
|
return null;
|
|
477
505
|
}
|
|
478
506
|
}
|
|
507
|
+
// ── Context Gate ─────────────────────────────────────────
|
|
508
|
+
/**
|
|
509
|
+
* Check if required context files have been read before allowing writes.
|
|
510
|
+
* Phase 1: warn mode (allow + inject warning).
|
|
511
|
+
* Returns null if all context files read or no context_files configured.
|
|
512
|
+
*/
|
|
513
|
+
async function handleContextGate(ir, wfState, projectDir, sessionId) {
|
|
514
|
+
if (!ir.context_files)
|
|
515
|
+
return null;
|
|
516
|
+
const required = [];
|
|
517
|
+
if (ir.context_files.mandatory)
|
|
518
|
+
required.push(...ir.context_files.mandatory);
|
|
519
|
+
if (ir.context_files.per_role?.[wfState.current_role]) {
|
|
520
|
+
required.push(...ir.context_files.per_role[wfState.current_role]);
|
|
521
|
+
}
|
|
522
|
+
if (ir.context_files.per_workflow?.[wfState.workflow]) {
|
|
523
|
+
required.push(...ir.context_files.per_workflow[wfState.workflow]);
|
|
524
|
+
}
|
|
525
|
+
if (required.length === 0)
|
|
526
|
+
return null;
|
|
527
|
+
const sessionReadsState = await readSessionReads(projectDir, sessionId);
|
|
528
|
+
const result = checkContextReadiness(sessionReadsState.read_files, required);
|
|
529
|
+
if (!result.ready) {
|
|
530
|
+
return allowOutput(`WARN [Intent DNA] Context Gate: Required files not yet read: ${result.missing.join(", ")}. Read them before continuing.`);
|
|
531
|
+
}
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
479
534
|
// ── Entry Point ────────────────────────────────────────────
|
|
480
535
|
main().catch(() => {
|
|
481
536
|
// Fail-open: never block Claude Code on unexpected errors
|
package/dist/hooks/enforce.d.ts
CHANGED
|
@@ -148,3 +148,12 @@ export interface ReflectionResult {
|
|
|
148
148
|
* Pure function — no I/O.
|
|
149
149
|
*/
|
|
150
150
|
export declare function checkReflectionLimit(failCount: number, maxAttempts: number, handoffCount: number, maxHandoffs: number): ReflectionResult;
|
|
151
|
+
export interface ContextReadinessResult {
|
|
152
|
+
ready: boolean;
|
|
153
|
+
missing: string[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if all required context files have been read in this session.
|
|
157
|
+
* Pure function — no I/O.
|
|
158
|
+
*/
|
|
159
|
+
export declare function checkContextReadiness(sessionReads: string[], required: string[]): ContextReadinessResult;
|
package/dist/hooks/enforce.js
CHANGED
|
@@ -703,3 +703,12 @@ export function checkReflectionLimit(failCount, maxAttempts, handoffCount, maxHa
|
|
|
703
703
|
}
|
|
704
704
|
return { action: "skip", reason: `No progress after ${maxAttempts} attempts and ${maxHandoffs} handoffs exhausted — skipping` };
|
|
705
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Check if all required context files have been read in this session.
|
|
708
|
+
* Pure function — no I/O.
|
|
709
|
+
*/
|
|
710
|
+
export function checkContextReadiness(sessionReads, required) {
|
|
711
|
+
const readSet = new Set(sessionReads);
|
|
712
|
+
const missing = required.filter(f => !readSet.has(f));
|
|
713
|
+
return { ready: missing.length === 0, missing };
|
|
714
|
+
}
|
package/dist/hooks/state.d.ts
CHANGED
|
@@ -90,6 +90,23 @@ export declare function readSurgeonAttempts(projectDir: string, sessionId?: stri
|
|
|
90
90
|
* Write surgeon attempt state atomically.
|
|
91
91
|
*/
|
|
92
92
|
export declare function writeSurgeonAttempts(projectDir: string, state: SurgeonAttemptState, sessionId?: string): Promise<void>;
|
|
93
|
+
/** Tracks which files a session has Read — for context gate enforcement */
|
|
94
|
+
export interface SessionReadsState {
|
|
95
|
+
session_id: string;
|
|
96
|
+
read_files: string[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Read session reads state. Returns empty reads if not found.
|
|
100
|
+
*/
|
|
101
|
+
export declare function readSessionReads(projectDir: string, sessionId?: string): Promise<SessionReadsState>;
|
|
102
|
+
/**
|
|
103
|
+
* Write session reads state atomically.
|
|
104
|
+
*/
|
|
105
|
+
export declare function writeSessionReads(projectDir: string, state: SessionReadsState, sessionId?: string): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Append a file path to session reads (dedup).
|
|
108
|
+
*/
|
|
109
|
+
export declare function appendSessionRead(projectDir: string, filePath: string, sessionId?: string): Promise<void>;
|
|
93
110
|
/** Trace entry for hook call observability */
|
|
94
111
|
export interface TraceEntry {
|
|
95
112
|
trace_id: string;
|
package/dist/hooks/state.js
CHANGED
|
@@ -173,6 +173,39 @@ export async function writeSurgeonAttempts(projectDir, state, sessionId) {
|
|
|
173
173
|
const filePath = join(stateDir, SURGEON_ATTEMPTS_FILE);
|
|
174
174
|
await atomicWrite(filePath, JSON.stringify(state, null, 2));
|
|
175
175
|
}
|
|
176
|
+
const SESSION_READS_FILE = "workflow/session-reads.json";
|
|
177
|
+
/**
|
|
178
|
+
* Read session reads state. Returns empty reads if not found.
|
|
179
|
+
*/
|
|
180
|
+
export async function readSessionReads(projectDir, sessionId) {
|
|
181
|
+
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
182
|
+
const filePath = join(stateDir, SESSION_READS_FILE);
|
|
183
|
+
try {
|
|
184
|
+
const raw = await readFile(filePath, "utf-8");
|
|
185
|
+
return JSON.parse(raw);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return { session_id: sessionId ?? "", read_files: [] };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Write session reads state atomically.
|
|
193
|
+
*/
|
|
194
|
+
export async function writeSessionReads(projectDir, state, sessionId) {
|
|
195
|
+
const stateDir = resolveStateDir(projectDir, sessionId);
|
|
196
|
+
const filePath = join(stateDir, SESSION_READS_FILE);
|
|
197
|
+
await atomicWrite(filePath, JSON.stringify(state, null, 2));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Append a file path to session reads (dedup).
|
|
201
|
+
*/
|
|
202
|
+
export async function appendSessionRead(projectDir, filePath, sessionId) {
|
|
203
|
+
const state = await readSessionReads(projectDir, sessionId);
|
|
204
|
+
if (!state.read_files.includes(filePath)) {
|
|
205
|
+
state.read_files.push(filePath);
|
|
206
|
+
await writeSessionReads(projectDir, state, sessionId);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
176
209
|
const TRACE_DIR = "trace";
|
|
177
210
|
const MAX_TRACE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
178
211
|
const TRACE_RETENTION_DAYS = 7;
|
|
@@ -42,6 +42,41 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
|
|
|
42
42
|
lines.push("</Purpose>");
|
|
43
43
|
lines.push("");
|
|
44
44
|
}
|
|
45
|
+
// Required Context — context files that must be read before any work
|
|
46
|
+
if (ir?.context_files) {
|
|
47
|
+
const contextFiles = [];
|
|
48
|
+
if (ir.context_files.mandatory)
|
|
49
|
+
contextFiles.push(...ir.context_files.mandatory);
|
|
50
|
+
// per_workflow for this workflow
|
|
51
|
+
if (ir.context_files.per_workflow?.[plan.source_workflow]) {
|
|
52
|
+
contextFiles.push(...ir.context_files.per_workflow[plan.source_workflow]);
|
|
53
|
+
}
|
|
54
|
+
// per_role for all roles used in this workflow
|
|
55
|
+
if (ir.context_files.per_role) {
|
|
56
|
+
for (const step of plan.steps) {
|
|
57
|
+
const roleFiles = ir.context_files.per_role[step.role];
|
|
58
|
+
if (roleFiles) {
|
|
59
|
+
for (const f of roleFiles) {
|
|
60
|
+
if (!contextFiles.includes(f))
|
|
61
|
+
contextFiles.push(f);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (contextFiles.length > 0) {
|
|
67
|
+
lines.push("<Required_Context>");
|
|
68
|
+
lines.push("Your context is EMPTY at startup. You MUST read these files first:");
|
|
69
|
+
lines.push("");
|
|
70
|
+
for (let i = 0; i < contextFiles.length; i++) {
|
|
71
|
+
lines.push(`${i + 1}. ${contextFiles[i]}`);
|
|
72
|
+
}
|
|
73
|
+
lines.push("");
|
|
74
|
+
lines.push("Without reading these, you cannot perform your role.");
|
|
75
|
+
lines.push("Hook will warn on first Edit/Bash/Write until these are read.");
|
|
76
|
+
lines.push("</Required_Context>");
|
|
77
|
+
lines.push("");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
45
80
|
// Steps — each step MUST be executed via Agent() tool call
|
|
46
81
|
const usedRoles = new Set(plan.steps.map((s) => s.role));
|
|
47
82
|
// Pre-compute handoff targets: steps that are pointed to by another step's handoff_to
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -232,6 +232,11 @@ export interface IntentDNA {
|
|
|
232
232
|
workflows?: Record<string, WorkflowDef>;
|
|
233
233
|
variables?: Record<string, string | VariableDef>;
|
|
234
234
|
mcp?: Record<string, MCPServerDef>;
|
|
235
|
+
context_files?: {
|
|
236
|
+
mandatory?: string[];
|
|
237
|
+
per_role?: Record<string, string[]>;
|
|
238
|
+
per_workflow?: Record<string, string[]>;
|
|
239
|
+
};
|
|
235
240
|
epigenetic: {
|
|
236
241
|
markers: EpigeneticMarker[];
|
|
237
242
|
};
|
|
@@ -338,6 +343,11 @@ export interface ConstraintIR {
|
|
|
338
343
|
roles_scope_map?: RoleScopeEntry[];
|
|
339
344
|
step_checkpoints?: StepCheckpointIR[];
|
|
340
345
|
workflows_ir?: WorkflowIR[];
|
|
346
|
+
context_files?: {
|
|
347
|
+
mandatory?: string[];
|
|
348
|
+
per_role?: Record<string, string[]>;
|
|
349
|
+
per_workflow?: Record<string, string[]>;
|
|
350
|
+
};
|
|
341
351
|
}
|
|
342
352
|
/** A compiled workflow step with resolved metadata */
|
|
343
353
|
export interface WorkflowStep {
|
package/dist/schema/validate.js
CHANGED
|
@@ -478,6 +478,33 @@ export function validateDNA(dna) {
|
|
|
478
478
|
}
|
|
479
479
|
}
|
|
480
480
|
}
|
|
481
|
+
// Validate context_files
|
|
482
|
+
if (dna.context_files) {
|
|
483
|
+
const cf = dna.context_files;
|
|
484
|
+
if (cf.per_role) {
|
|
485
|
+
for (const [roleName, paths] of Object.entries(cf.per_role)) {
|
|
486
|
+
if (roleNamesSet.size > 0 && !roleNamesSet.has(roleName)) {
|
|
487
|
+
warnings.push({ path: `context_files.per_role.${roleName}`, message: `references unknown role '${roleName}'` });
|
|
488
|
+
}
|
|
489
|
+
if (!Array.isArray(paths)) {
|
|
490
|
+
errors.push({ path: `context_files.per_role.${roleName}`, message: "must be an array of strings" });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (cf.per_workflow) {
|
|
495
|
+
const wfNames = new Set(Object.keys(dna.workflows ?? {}));
|
|
496
|
+
if (dna.workflow)
|
|
497
|
+
wfNames.add(dna.workflow.name);
|
|
498
|
+
for (const [wfName, paths] of Object.entries(cf.per_workflow)) {
|
|
499
|
+
if (wfNames.size > 0 && !wfNames.has(wfName)) {
|
|
500
|
+
warnings.push({ path: `context_files.per_workflow.${wfName}`, message: `references unknown workflow '${wfName}'` });
|
|
501
|
+
}
|
|
502
|
+
if (!Array.isArray(paths)) {
|
|
503
|
+
errors.push({ path: `context_files.per_workflow.${wfName}`, message: "must be an array of strings" });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
481
508
|
// Validate epigenetic markers
|
|
482
509
|
for (let i = 0; i < (dna.epigenetic?.markers?.length ?? 0); i++) {
|
|
483
510
|
const marker = dna.epigenetic.markers[i];
|
|
@@ -119,6 +119,24 @@ genes:
|
|
|
119
119
|
|
|
120
120
|
contexts: {}
|
|
121
121
|
|
|
122
|
+
# 上下文文件 — subagent 必读清单
|
|
123
|
+
context_files:
|
|
124
|
+
mandatory:
|
|
125
|
+
- CLAUDE.md
|
|
126
|
+
- docs/refactoring-workflow-v2.md
|
|
127
|
+
- v2/docs/PROVIDER_DESIGN.md
|
|
128
|
+
- v2/docs/memory/flutter_architecture_guide.md
|
|
129
|
+
per_role:
|
|
130
|
+
analyzer:
|
|
131
|
+
- "docs/behavior/{{ARGUMENTS}}.md"
|
|
132
|
+
analysis_reviewer:
|
|
133
|
+
- "docs/behavior/{{ARGUMENTS}}.md"
|
|
134
|
+
- ".omc/specs/diagnosis-{{ARGUMENTS}}.md"
|
|
135
|
+
surgeon:
|
|
136
|
+
- ".omc/specs/diagnosis-{{ARGUMENTS}}.md"
|
|
137
|
+
test_runner:
|
|
138
|
+
- "docs/behavior/{{ARGUMENTS}}.md"
|
|
139
|
+
|
|
122
140
|
|
|
123
141
|
roles:
|
|
124
142
|
# ── Phase 0/3: behavior-lock 角色 ──
|
|
@@ -214,6 +232,51 @@ roles:
|
|
|
214
232
|
- Do not add methods v1 doesn't have
|
|
215
233
|
- Do not remove methods v1 has
|
|
216
234
|
|
|
235
|
+
# ── v2 角色: diagnosis + fix workflow ──
|
|
236
|
+
analyzer:
|
|
237
|
+
description: "Reads code and classifies failing tests. Read-only."
|
|
238
|
+
tool_permissions:
|
|
239
|
+
allow: [Read, Grep, Glob]
|
|
240
|
+
deny: [Bash, Edit, Write, NotebookEdit]
|
|
241
|
+
scope:
|
|
242
|
+
read: ["**/*"]
|
|
243
|
+
write: []
|
|
244
|
+
instructions:
|
|
245
|
+
- "REQUIRED FIRST: Read all context files listed in SKILL.md"
|
|
246
|
+
- "For each failing test: read test code + v1 impl + v2 impl"
|
|
247
|
+
- "Classify each failure as BUG / INFRA / REMOVED / TEST_BUG"
|
|
248
|
+
- "Output diagnosis spec with v1 code snippets + v2 current state"
|
|
249
|
+
- "DO NOT run tests. DO NOT edit code. Analysis only."
|
|
250
|
+
|
|
251
|
+
analysis_reviewer:
|
|
252
|
+
description: "Reviews analyzer output quality. Read-only."
|
|
253
|
+
tool_permissions:
|
|
254
|
+
allow: [Read, Grep, Glob]
|
|
255
|
+
deny: [Bash, Edit, Write, NotebookEdit]
|
|
256
|
+
scope:
|
|
257
|
+
read: ["**/*"]
|
|
258
|
+
write: []
|
|
259
|
+
instructions:
|
|
260
|
+
- "REQUIRED FIRST: Read all context files listed in SKILL.md"
|
|
261
|
+
- "Verify analyzer spec: v1 references exist? Classifications sound?"
|
|
262
|
+
- "Check missing: any failing test not covered?"
|
|
263
|
+
- "Output: APPROVE or REQUEST_REANALYSIS with specific feedback"
|
|
264
|
+
|
|
265
|
+
test_runner:
|
|
266
|
+
description: "Runs tests and reports progress delta."
|
|
267
|
+
tool_permissions:
|
|
268
|
+
allow: [Read, Grep, Glob, Bash]
|
|
269
|
+
deny: [Edit, Write, NotebookEdit]
|
|
270
|
+
scope:
|
|
271
|
+
read: ["**/*"]
|
|
272
|
+
write: []
|
|
273
|
+
instructions:
|
|
274
|
+
- "Run flutter test for the target module"
|
|
275
|
+
- "Parse output: green/red/skip counts"
|
|
276
|
+
- "Compare with baseline: fixed / new_red / stable"
|
|
277
|
+
- "Classify red tests by type (compile/logic/widget/hung)"
|
|
278
|
+
- "Output progress delta, not test content"
|
|
279
|
+
|
|
217
280
|
workflows:
|
|
218
281
|
behavior-lock:
|
|
219
282
|
name: Behavior Lock
|
|
@@ -253,9 +316,13 @@ workflows:
|
|
|
253
316
|
Scenario 2 (re-run/incremental):
|
|
254
317
|
- Read the incremental diff from behavior doc
|
|
255
318
|
- Update existing tests incrementally — do NOT rewrite all tests (preserves rescue progress)
|
|
256
|
-
- Run
|
|
257
|
-
-
|
|
258
|
-
|
|
319
|
+
- Run ALL tests for this module (do NOT skip previously green tests)
|
|
320
|
+
- Compare with last baseline:
|
|
321
|
+
GREEN→RED = REGRESSION (mark HIGH_PRIORITY)
|
|
322
|
+
RED→GREEN = FIXED
|
|
323
|
+
NEW tests = new coverage
|
|
324
|
+
REMOVED tests = coverage shrunk
|
|
325
|
+
- Update baseline with full result
|
|
259
326
|
- Git commit: "behavior-lock($ARGUMENTS): N tests (X green, Y red from behavior change)"
|
|
260
327
|
|
|
261
328
|
BANNED patterns:
|
|
@@ -436,3 +503,118 @@ workflows:
|
|
|
436
503
|
role: core_aligner
|
|
437
504
|
description: "Compare {{v1_path}}/ and {{v2_path}}/core/ module by module. Fix mismatches."
|
|
438
505
|
prompt: "Compare v1 ({{v1_path}}/) and v2 core ({{v2_path}}/core/lib/). List mismatches. Fix by copying v1 logic, only changing DI (GetX→Riverpod). Run flutter analyze after each fix."
|
|
506
|
+
|
|
507
|
+
diagnosis:
|
|
508
|
+
name: Diagnosis
|
|
509
|
+
description: "Analyze failing tests, classify root causes, output diagnosis spec for module $ARGUMENTS"
|
|
510
|
+
steps:
|
|
511
|
+
- id: analyze
|
|
512
|
+
role: analyzer
|
|
513
|
+
max_attempts: 2
|
|
514
|
+
on_fail: retry_with_feedback
|
|
515
|
+
description: "Classify failing tests and output diagnosis spec"
|
|
516
|
+
prompt: |
|
|
517
|
+
Read all required context files first (enforced by hook).
|
|
518
|
+
|
|
519
|
+
For module $ARGUMENTS, analyze failing tests in baseline:
|
|
520
|
+
1. Read behavior doc: docs/behavior/$ARGUMENTS.md
|
|
521
|
+
2. Read test results from last behavior-lock run
|
|
522
|
+
3. For each failing test:
|
|
523
|
+
a. Read test code (what behavior does it expect?)
|
|
524
|
+
b. Read v1 implementation (how did v1 do this?)
|
|
525
|
+
c. Read v2 current state (what's missing/wrong?)
|
|
526
|
+
d. Classify: BUG / INFRA / REMOVED / TEST_BUG
|
|
527
|
+
4. Write .omc/specs/diagnosis-$ARGUMENTS.md with:
|
|
528
|
+
- For each failure: v1 code snippet + v2 current state + classification
|
|
529
|
+
- NO fix prescriptions (surgeon decides how to implement)
|
|
530
|
+
- NO running tests (analysis only)
|
|
531
|
+
handoff:
|
|
532
|
+
produces:
|
|
533
|
+
- type: file
|
|
534
|
+
path: ".omc/specs/diagnosis-$ARGUMENTS.md"
|
|
535
|
+
description: "Diagnosis spec for module"
|
|
536
|
+
|
|
537
|
+
- id: review
|
|
538
|
+
role: analysis_reviewer
|
|
539
|
+
depends_on: [analyze]
|
|
540
|
+
max_attempts: 2
|
|
541
|
+
description: "Review analyzer output quality"
|
|
542
|
+
prompt: |
|
|
543
|
+
Read context files + .omc/specs/diagnosis-$ARGUMENTS.md
|
|
544
|
+
|
|
545
|
+
Verify:
|
|
546
|
+
1. All failing tests from baseline covered?
|
|
547
|
+
2. v1 code references actually exist at claimed locations?
|
|
548
|
+
3. Classifications reasonable? (INFRA not mistaken for BUG)
|
|
549
|
+
4. Enough detail for surgeon to act?
|
|
550
|
+
|
|
551
|
+
APPROVE → diagnosis complete
|
|
552
|
+
REQUEST_REANALYSIS → list specific gaps, back to analyze
|
|
553
|
+
handoff:
|
|
554
|
+
consumes:
|
|
555
|
+
- type: file
|
|
556
|
+
path: ".omc/specs/diagnosis-$ARGUMENTS.md"
|
|
557
|
+
description: "Diagnosis spec from analyzer"
|
|
558
|
+
produces:
|
|
559
|
+
- type: summary
|
|
560
|
+
description: "Review verdict (APPROVE or REQUEST_REANALYSIS)"
|
|
561
|
+
|
|
562
|
+
fix:
|
|
563
|
+
name: Fix
|
|
564
|
+
description: "Read diagnosis spec and fix v2 code for module $ARGUMENTS with reflection gate"
|
|
565
|
+
steps:
|
|
566
|
+
- id: fix_bugs
|
|
567
|
+
role: surgeon
|
|
568
|
+
max_attempts: 3
|
|
569
|
+
on_fail: handoff
|
|
570
|
+
handoff_to: fix_bugs
|
|
571
|
+
max_handoffs: 3
|
|
572
|
+
on_handoff_exhausted: skip
|
|
573
|
+
blocked_items_path: "docs/behavior/blocked_items.md"
|
|
574
|
+
description: "Fix issues according to diagnosis spec classifications"
|
|
575
|
+
prompt: |
|
|
576
|
+
Read context + diagnosis spec first.
|
|
577
|
+
|
|
578
|
+
Process issues in priority order: INFRA → BUG → TEST_BUG
|
|
579
|
+
(REMOVED skipped — needs user confirmation)
|
|
580
|
+
|
|
581
|
+
For each issue:
|
|
582
|
+
- BUG: read v1 file, edit v2 (use Riverpod per refactoring-workflow-v2.md)
|
|
583
|
+
- INFRA: edit test_helpers only, do NOT touch v2/lib
|
|
584
|
+
- TEST_BUG: re-read v1, edit test to match v1 behavior
|
|
585
|
+
|
|
586
|
+
After each fix: run tests ONCE for that specific file.
|
|
587
|
+
|
|
588
|
+
If experience_chain is non-empty:
|
|
589
|
+
- You have tried approaches that failed
|
|
590
|
+
- Read them, use a DIFFERENT approach
|
|
591
|
+
handoff:
|
|
592
|
+
consumes:
|
|
593
|
+
- type: file
|
|
594
|
+
path: ".omc/specs/diagnosis-$ARGUMENTS.md"
|
|
595
|
+
description: "Diagnosis spec"
|
|
596
|
+
produces:
|
|
597
|
+
- type: git_commit
|
|
598
|
+
description: "Fix commit"
|
|
599
|
+
|
|
600
|
+
- id: progress_check
|
|
601
|
+
role: test_runner
|
|
602
|
+
depends_on: [fix_bugs]
|
|
603
|
+
description: "Verify progress and output report"
|
|
604
|
+
prompt: |
|
|
605
|
+
Run full module test suite for $ARGUMENTS.
|
|
606
|
+
Compare with diagnosis spec baseline.
|
|
607
|
+
|
|
608
|
+
Output report:
|
|
609
|
+
- Fixed: N tests now green
|
|
610
|
+
- New red: M tests that regressed
|
|
611
|
+
- Blocked: K tests skipped (see blocked_items.md)
|
|
612
|
+
- Remaining: R tests still failing
|
|
613
|
+
handoff:
|
|
614
|
+
consumes:
|
|
615
|
+
- type: git_commit
|
|
616
|
+
from: fix_bugs
|
|
617
|
+
description: "Fix commit from surgeon"
|
|
618
|
+
produces:
|
|
619
|
+
- type: summary
|
|
620
|
+
description: "Progress report with test delta"
|