intentdna 1.4.4 → 1.4.6
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/cli/commands/feedback.d.ts +33 -0
- package/dist/cli/commands/feedback.js +140 -0
- package/dist/cli/commands/generate.js +3 -0
- package/dist/cli/commands/setup.d.ts +1 -0
- package/dist/cli/commands/setup.js +125 -0
- package/dist/cli/index.js +21 -1
- package/dist/compiler/workflow.d.ts +17 -1
- package/dist/compiler/workflow.js +63 -1
- package/dist/hooks/cli.d.ts +20 -1
- package/dist/hooks/cli.js +61 -5
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/runtime/skill-adapter.js +8 -0
- package/dist/runtime/workflow-runner.js +95 -3
- package/dist/schema/types.d.ts +10 -0
- package/dist/templates/incident-response.dna.yaml +66 -0
- package/dist/templates/migration-safety.dna.yaml +67 -0
- package/dist/templates/performance-audit.dna.yaml +67 -0
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dna feedback [--days <N>] [--json]
|
|
3
|
+
*
|
|
4
|
+
* Analyze trace/audit data and produce actionable feedback for template optimization.
|
|
5
|
+
*
|
|
6
|
+
* Reads: .dna/state/trace/trace-*.jsonl
|
|
7
|
+
* Outputs: human-readable report (or JSON with --json)
|
|
8
|
+
*/
|
|
9
|
+
export interface FeedbackOptions {
|
|
10
|
+
days: number;
|
|
11
|
+
json: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface FeedbackReport {
|
|
14
|
+
period_days: number;
|
|
15
|
+
total_events: number;
|
|
16
|
+
blocks: number;
|
|
17
|
+
warns: number;
|
|
18
|
+
allows: number;
|
|
19
|
+
top_blocked_tools: Array<{
|
|
20
|
+
tool: string;
|
|
21
|
+
count: number;
|
|
22
|
+
}>;
|
|
23
|
+
top_blocked_paths: Array<{
|
|
24
|
+
path: string;
|
|
25
|
+
count: number;
|
|
26
|
+
}>;
|
|
27
|
+
top_block_reasons: Array<{
|
|
28
|
+
reason: string;
|
|
29
|
+
count: number;
|
|
30
|
+
}>;
|
|
31
|
+
suggestions: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function runFeedback(opts: FeedbackOptions): Promise<number>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dna feedback [--days <N>] [--json]
|
|
3
|
+
*
|
|
4
|
+
* Analyze trace/audit data and produce actionable feedback for template optimization.
|
|
5
|
+
*
|
|
6
|
+
* Reads: .dna/state/trace/trace-*.jsonl
|
|
7
|
+
* Outputs: human-readable report (or JSON with --json)
|
|
8
|
+
*/
|
|
9
|
+
import { readTraces } from "../../hooks/state.js";
|
|
10
|
+
function aggregate(traces) {
|
|
11
|
+
let blocks = 0, warns = 0, allows = 0;
|
|
12
|
+
const toolCounts = new Map();
|
|
13
|
+
const pathCounts = new Map();
|
|
14
|
+
const reasonCounts = new Map();
|
|
15
|
+
for (const t of traces) {
|
|
16
|
+
if (t.decision === "block") {
|
|
17
|
+
blocks++;
|
|
18
|
+
if (t.tool_name)
|
|
19
|
+
toolCounts.set(t.tool_name, (toolCounts.get(t.tool_name) ?? 0) + 1);
|
|
20
|
+
if (t.target_path)
|
|
21
|
+
pathCounts.set(t.target_path, (pathCounts.get(t.target_path) ?? 0) + 1);
|
|
22
|
+
if (t.reason) {
|
|
23
|
+
// Normalize reason to first line
|
|
24
|
+
const shortReason = t.reason.split("\n")[0].replace(/^\[Intent DNA\]\s*/, "").slice(0, 100);
|
|
25
|
+
reasonCounts.set(shortReason, (reasonCounts.get(shortReason) ?? 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (t.decision === "warn") {
|
|
29
|
+
warns++;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
allows++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const sorted = (map, limit, key) => [...map.entries()]
|
|
36
|
+
.sort((a, b) => b[1] - a[1])
|
|
37
|
+
.slice(0, limit)
|
|
38
|
+
.map(([k, count]) => ({ [key]: k, count }));
|
|
39
|
+
return {
|
|
40
|
+
total_events: traces.length,
|
|
41
|
+
blocks,
|
|
42
|
+
warns,
|
|
43
|
+
allows,
|
|
44
|
+
top_blocked_tools: sorted(toolCounts, 5, "tool"),
|
|
45
|
+
top_blocked_paths: sorted(pathCounts, 5, "path"),
|
|
46
|
+
top_block_reasons: sorted(reasonCounts, 5, "reason"),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function generateSuggestions(report) {
|
|
50
|
+
const suggestions = [];
|
|
51
|
+
if (report.blocks === 0 && report.warns === 0) {
|
|
52
|
+
suggestions.push("No blocks or warnings — DNA constraints are not firing. Consider tightening rules or verifying hooks are active.");
|
|
53
|
+
return suggestions;
|
|
54
|
+
}
|
|
55
|
+
const blockRate = report.total_events > 0 ? report.blocks / report.total_events : 0;
|
|
56
|
+
if (blockRate > 0.3) {
|
|
57
|
+
suggestions.push(`High block rate (${(blockRate * 100).toFixed(0)}%). Review if DNA rules are too restrictive — this may slow development.`);
|
|
58
|
+
}
|
|
59
|
+
// Tool-specific suggestions
|
|
60
|
+
for (const t of report.top_blocked_tools) {
|
|
61
|
+
if (t.count >= 5) {
|
|
62
|
+
suggestions.push(`Tool '${t.tool}' blocked ${t.count} times. Consider expanding permissions if this is expected usage.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Path-specific suggestions
|
|
66
|
+
for (const p of report.top_blocked_paths) {
|
|
67
|
+
if (p.count >= 3) {
|
|
68
|
+
suggestions.push(`Path '${p.path}' blocked ${p.count} times. Consider adding to write scope if agents need access.`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Repeated reason patterns
|
|
72
|
+
for (const r of report.top_block_reasons) {
|
|
73
|
+
if (r.count >= 3) {
|
|
74
|
+
suggestions.push(`Repeated block: "${r.reason}" (${r.count}x). May indicate a rule-scope mismatch.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (suggestions.length === 0) {
|
|
78
|
+
suggestions.push("Constraints are working within normal parameters. No action needed.");
|
|
79
|
+
}
|
|
80
|
+
return suggestions;
|
|
81
|
+
}
|
|
82
|
+
function formatReport(report) {
|
|
83
|
+
const lines = [];
|
|
84
|
+
lines.push(`DNA Feedback Report (last ${report.period_days} day${report.period_days > 1 ? "s" : ""})`);
|
|
85
|
+
lines.push("=".repeat(50));
|
|
86
|
+
lines.push("");
|
|
87
|
+
lines.push(`Total events: ${report.total_events}`);
|
|
88
|
+
lines.push(` Allowed: ${report.allows}`);
|
|
89
|
+
lines.push(` Blocked: ${report.blocks}`);
|
|
90
|
+
lines.push(` Warned: ${report.warns}`);
|
|
91
|
+
lines.push("");
|
|
92
|
+
if (report.top_blocked_tools.length > 0) {
|
|
93
|
+
lines.push("Top blocked tools:");
|
|
94
|
+
for (const t of report.top_blocked_tools) {
|
|
95
|
+
lines.push(` ${t.tool}: ${t.count}`);
|
|
96
|
+
}
|
|
97
|
+
lines.push("");
|
|
98
|
+
}
|
|
99
|
+
if (report.top_blocked_paths.length > 0) {
|
|
100
|
+
lines.push("Top blocked paths:");
|
|
101
|
+
for (const p of report.top_blocked_paths) {
|
|
102
|
+
lines.push(` ${p.path}: ${p.count}`);
|
|
103
|
+
}
|
|
104
|
+
lines.push("");
|
|
105
|
+
}
|
|
106
|
+
if (report.top_block_reasons.length > 0) {
|
|
107
|
+
lines.push("Top block reasons:");
|
|
108
|
+
for (const r of report.top_block_reasons) {
|
|
109
|
+
lines.push(` "${r.reason}": ${r.count}`);
|
|
110
|
+
}
|
|
111
|
+
lines.push("");
|
|
112
|
+
}
|
|
113
|
+
if (report.suggestions.length > 0) {
|
|
114
|
+
lines.push("Suggestions:");
|
|
115
|
+
for (const s of report.suggestions) {
|
|
116
|
+
lines.push(` - ${s}`);
|
|
117
|
+
}
|
|
118
|
+
lines.push("");
|
|
119
|
+
}
|
|
120
|
+
return lines.join("\n");
|
|
121
|
+
}
|
|
122
|
+
export async function runFeedback(opts) {
|
|
123
|
+
const projectDir = process.cwd();
|
|
124
|
+
const traces = await readTraces(projectDir, opts.days);
|
|
125
|
+
if (traces.length === 0) {
|
|
126
|
+
process.stderr.write(`No trace data found for the last ${opts.days} day(s).\n`);
|
|
127
|
+
process.stderr.write("Traces are generated by dna-hook during Claude Code sessions.\n");
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
const stats = aggregate(traces);
|
|
131
|
+
const suggestions = generateSuggestions(stats);
|
|
132
|
+
const report = { ...stats, period_days: opts.days, suggestions };
|
|
133
|
+
if (opts.json) {
|
|
134
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
process.stderr.write(formatReport(report));
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
@@ -38,6 +38,9 @@ const TEMPLATE_KEYWORDS = {
|
|
|
38
38
|
"brainstorming-first": ["brainstorm", "explore", "ideation", "探索"],
|
|
39
39
|
"branch-finishing": ["branch", "finish", "cleanup", "pr prep"],
|
|
40
40
|
"pr-submitter": ["pr", "pull request", "submit"],
|
|
41
|
+
"migration-safety": ["migration", "migrate", "upgrade", "database migration", "dependency upgrade", "迁移"],
|
|
42
|
+
"incident-response": ["incident", "production issue", "hotfix", "outage", "postmortem", "事故"],
|
|
43
|
+
"performance-audit": ["performance", "profiling", "benchmark", "optimize", "latency", "性能"],
|
|
41
44
|
};
|
|
42
45
|
function scoreTemplates(description) {
|
|
43
46
|
const lower = description.toLowerCase();
|
|
@@ -95,7 +95,132 @@ function isPluginRegistered() {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
// ── Main ──────────────────────────────────────────────────
|
|
98
|
+
/**
|
|
99
|
+
* Get the currently installed version of intentdna.
|
|
100
|
+
*/
|
|
101
|
+
function getInstalledVersion() {
|
|
102
|
+
try {
|
|
103
|
+
const result = execSync("npm list -g intentdna --json 2>/dev/null", {
|
|
104
|
+
encoding: "utf-8",
|
|
105
|
+
timeout: 10000,
|
|
106
|
+
});
|
|
107
|
+
const data = JSON.parse(result);
|
|
108
|
+
return data?.dependencies?.intentdna?.version ?? null;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the latest published version from npm registry.
|
|
116
|
+
*/
|
|
117
|
+
function getLatestVersion() {
|
|
118
|
+
try {
|
|
119
|
+
return execSync("npm view intentdna version 2>/dev/null", {
|
|
120
|
+
encoding: "utf-8",
|
|
121
|
+
timeout: 10000,
|
|
122
|
+
}).trim();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Upgrade flow: npm update → re-register marketplace → re-install plugin.
|
|
130
|
+
*/
|
|
131
|
+
async function runUpgrade(opts) {
|
|
132
|
+
// Step 1: Detect claude CLI
|
|
133
|
+
const claudeVersion = detectClaudeCLI();
|
|
134
|
+
if (!claudeVersion) {
|
|
135
|
+
log("Claude Code CLI not detected. Cannot upgrade plugin.");
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
log(`Claude Code detected: ${claudeVersion}`);
|
|
139
|
+
// Step 2: Check versions
|
|
140
|
+
const currentVer = getInstalledVersion();
|
|
141
|
+
const latestVer = getLatestVersion();
|
|
142
|
+
if (currentVer)
|
|
143
|
+
log(`Current version: ${currentVer}`);
|
|
144
|
+
if (latestVer)
|
|
145
|
+
log(`Latest version: ${latestVer}`);
|
|
146
|
+
if (currentVer && latestVer && currentVer === latestVer) {
|
|
147
|
+
log("Already at latest version.");
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Step 3: npm update
|
|
151
|
+
log("");
|
|
152
|
+
log("Updating intentdna...");
|
|
153
|
+
try {
|
|
154
|
+
execSync("npm update -g intentdna", {
|
|
155
|
+
encoding: "utf-8",
|
|
156
|
+
timeout: 60000,
|
|
157
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
158
|
+
});
|
|
159
|
+
const newVer = getInstalledVersion();
|
|
160
|
+
log(`Updated to: ${newVer ?? "unknown"}`);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
164
|
+
log(`npm update failed: ${msg}`);
|
|
165
|
+
log("Try manually: npm update -g intentdna");
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Step 4: Re-resolve package path
|
|
170
|
+
const pkgPath = await resolvePackagePath();
|
|
171
|
+
if (!pkgPath) {
|
|
172
|
+
log("Error: Could not find intentdna package after update.");
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
log(`Package path: ${pkgPath}`);
|
|
176
|
+
// Step 5: Re-register marketplace (picks up new plugin.json version)
|
|
177
|
+
try {
|
|
178
|
+
log("");
|
|
179
|
+
log(`Re-registering marketplace: ${pkgPath}`);
|
|
180
|
+
execSync(`claude plugin marketplace add "${pkgPath}"`, {
|
|
181
|
+
encoding: "utf-8",
|
|
182
|
+
timeout: 30000,
|
|
183
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
184
|
+
});
|
|
185
|
+
log("Marketplace updated.");
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
189
|
+
log(`Warning: marketplace re-register failed: ${msg}`);
|
|
190
|
+
}
|
|
191
|
+
// Step 6: Re-install plugin (picks up new hooks.json)
|
|
192
|
+
try {
|
|
193
|
+
const installCmd = `claude plugin install intentdna@intentdna --scope ${opts.scope}`;
|
|
194
|
+
log(`Re-installing plugin: ${installCmd}`);
|
|
195
|
+
execSync(installCmd, {
|
|
196
|
+
encoding: "utf-8",
|
|
197
|
+
timeout: 30000,
|
|
198
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
199
|
+
});
|
|
200
|
+
log("Plugin re-installed.");
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
204
|
+
log(`Warning: plugin re-install failed: ${msg}`);
|
|
205
|
+
}
|
|
206
|
+
// Step 7: Verify
|
|
207
|
+
if (isPluginRegistered()) {
|
|
208
|
+
log("");
|
|
209
|
+
log("Upgrade complete. Run 'dna sync' to recompile your DNA templates.");
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
log("");
|
|
214
|
+
log("Upgrade completed but plugin verification failed.");
|
|
215
|
+
log("Try: claude plugin list");
|
|
216
|
+
return 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
98
219
|
export async function runSetup(opts) {
|
|
220
|
+
// Upgrade mode
|
|
221
|
+
if (opts.upgrade) {
|
|
222
|
+
return runUpgrade(opts);
|
|
223
|
+
}
|
|
99
224
|
const scope = opts.scope;
|
|
100
225
|
// Step 1: Detect claude CLI
|
|
101
226
|
const claudeVersion = detectClaudeCLI();
|
package/dist/cli/index.js
CHANGED
|
@@ -17,7 +17,7 @@ const HELP = `Intent DNA CLI v0.3.0
|
|
|
17
17
|
Usage: dna <command> [options]
|
|
18
18
|
|
|
19
19
|
Commands:
|
|
20
|
-
setup Register intentdna as a Claude Code plugin (--scope user|project, --yes)
|
|
20
|
+
setup Register intentdna as a Claude Code plugin (--scope user|project, --upgrade, --yes)
|
|
21
21
|
guard Zero-config guardrails — detect environment, apply safety rules
|
|
22
22
|
sync Compile + inject DNA into target file (CLAUDE.md, SOUL.md, etc.)
|
|
23
23
|
verify Verify synced files match .dna/lock checksums (drift detection)
|
|
@@ -29,6 +29,7 @@ Commands:
|
|
|
29
29
|
show Show gene expression state
|
|
30
30
|
evolve Generate epigenetic markers from outcomes
|
|
31
31
|
epigenetic Record outcomes, view markers and summaries
|
|
32
|
+
feedback Analyze trace data, suggest template optimizations (--days <N>, --json)
|
|
32
33
|
|
|
33
34
|
Options:
|
|
34
35
|
--help, -h Show help for a command
|
|
@@ -387,6 +388,7 @@ async function main() {
|
|
|
387
388
|
options: {
|
|
388
389
|
scope: { type: "string", short: "s", default: "user" },
|
|
389
390
|
yes: { type: "boolean", short: "y", default: false },
|
|
391
|
+
upgrade: { type: "boolean", short: "u", default: false },
|
|
390
392
|
},
|
|
391
393
|
strict: false,
|
|
392
394
|
});
|
|
@@ -399,6 +401,24 @@ async function main() {
|
|
|
399
401
|
const code = await runSetup({
|
|
400
402
|
scope: setupScope,
|
|
401
403
|
yes: setupValues.yes,
|
|
404
|
+
upgrade: setupValues.upgrade,
|
|
405
|
+
});
|
|
406
|
+
process.exit(code);
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
case "feedback": {
|
|
410
|
+
const { values: fbValues } = parseArgs({
|
|
411
|
+
args: rest,
|
|
412
|
+
options: {
|
|
413
|
+
days: { type: "string", short: "d", default: "7" },
|
|
414
|
+
json: { type: "boolean", default: false },
|
|
415
|
+
},
|
|
416
|
+
strict: false,
|
|
417
|
+
});
|
|
418
|
+
const { runFeedback } = await import("./commands/feedback.js");
|
|
419
|
+
const code = await runFeedback({
|
|
420
|
+
days: parseInt(fbValues.days, 10) || 7,
|
|
421
|
+
json: fbValues.json,
|
|
402
422
|
});
|
|
403
423
|
process.exit(code);
|
|
404
424
|
break;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Performs topological sort, parallel group detection, transition compilation,
|
|
6
6
|
* retry config resolution, cycle detection, and optional Mermaid diagram generation.
|
|
7
7
|
*/
|
|
8
|
-
import type { WorkflowDef, WorkflowPlan, RoleDef } from "../schema/types.js";
|
|
8
|
+
import type { WorkflowDef, WorkflowStepDef, WorkflowPlan, RoleDef, RawWorkflowStepEntry } from "../schema/types.js";
|
|
9
9
|
export interface CompileWorkflowOptions {
|
|
10
10
|
/** Generate Mermaid diagram (default: false) */
|
|
11
11
|
mermaid?: boolean;
|
|
@@ -21,6 +21,22 @@ export interface CompileWorkflowResult {
|
|
|
21
21
|
plan?: WorkflowPlan;
|
|
22
22
|
errors: CompileWorkflowError[];
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Expand parallel blocks into regular steps with depends_on.
|
|
26
|
+
*
|
|
27
|
+
* A parallel block:
|
|
28
|
+
* - parallel:
|
|
29
|
+
* isolation: worktree
|
|
30
|
+
* steps: [{id: "a", role: "x"}, {id: "b", role: "y"}]
|
|
31
|
+
*
|
|
32
|
+
* Becomes:
|
|
33
|
+
* - {id: "a", role: "x", depends_on: [<prev_step>], isolation: worktree}
|
|
34
|
+
* - {id: "b", role: "y", depends_on: [<prev_step>], isolation: worktree}
|
|
35
|
+
*
|
|
36
|
+
* The step immediately before the parallel block is the dependency.
|
|
37
|
+
* If no previous step, the parallel steps have no depends_on.
|
|
38
|
+
*/
|
|
39
|
+
export declare function expandParallelBlocks(steps: RawWorkflowStepEntry[]): WorkflowStepDef[];
|
|
24
40
|
/**
|
|
25
41
|
* Check if two sets of write glob patterns have overlapping scope.
|
|
26
42
|
* Uses prefix-based matching: "src/xx" and "src/lib/xx" overlap.
|
|
@@ -6,6 +6,66 @@
|
|
|
6
6
|
* retry config resolution, cycle detection, and optional Mermaid diagram generation.
|
|
7
7
|
*/
|
|
8
8
|
// ── Internal helpers ─────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Type guard: check if a step entry is a parallel block (syntax sugar).
|
|
11
|
+
*/
|
|
12
|
+
function isParallelBlock(entry) {
|
|
13
|
+
return "parallel" in entry && typeof entry.parallel === "object";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Expand parallel blocks into regular steps with depends_on.
|
|
17
|
+
*
|
|
18
|
+
* A parallel block:
|
|
19
|
+
* - parallel:
|
|
20
|
+
* isolation: worktree
|
|
21
|
+
* steps: [{id: "a", role: "x"}, {id: "b", role: "y"}]
|
|
22
|
+
*
|
|
23
|
+
* Becomes:
|
|
24
|
+
* - {id: "a", role: "x", depends_on: [<prev_step>], isolation: worktree}
|
|
25
|
+
* - {id: "b", role: "y", depends_on: [<prev_step>], isolation: worktree}
|
|
26
|
+
*
|
|
27
|
+
* The step immediately before the parallel block is the dependency.
|
|
28
|
+
* If no previous step, the parallel steps have no depends_on.
|
|
29
|
+
*/
|
|
30
|
+
export function expandParallelBlocks(steps) {
|
|
31
|
+
const result = [];
|
|
32
|
+
let lastStepId = null;
|
|
33
|
+
for (const entry of steps) {
|
|
34
|
+
if (isParallelBlock(entry)) {
|
|
35
|
+
const block = entry.parallel;
|
|
36
|
+
const expandedIds = [];
|
|
37
|
+
for (const inner of block.steps) {
|
|
38
|
+
const expanded = {
|
|
39
|
+
...inner,
|
|
40
|
+
// Add depends_on from last step before this block
|
|
41
|
+
depends_on: lastStepId
|
|
42
|
+
? [...(inner.depends_on ?? []), lastStepId]
|
|
43
|
+
: inner.depends_on,
|
|
44
|
+
// Inherit isolation from parallel block if step doesn't override
|
|
45
|
+
isolation: inner.isolation ?? block.isolation,
|
|
46
|
+
};
|
|
47
|
+
result.push(expanded);
|
|
48
|
+
expandedIds.push(inner.id);
|
|
49
|
+
}
|
|
50
|
+
// After expansion, "lastStepId" stays as the set of expanded IDs.
|
|
51
|
+
// The next step should depend on ALL parallel steps.
|
|
52
|
+
// We can't set a single lastStepId, so we track it differently:
|
|
53
|
+
// We set lastStepId to null and let the NEXT regular step
|
|
54
|
+
// depend on all expanded IDs via explicit depends_on in the input.
|
|
55
|
+
// Actually, the simplest approach: peek at the next step.
|
|
56
|
+
// If the next step already has depends_on referencing these IDs, good.
|
|
57
|
+
// Otherwise, we need a way to inject deps.
|
|
58
|
+
// Since this is sugar, we rely on the user specifying depends_on
|
|
59
|
+
// on the join step (like in the spec example).
|
|
60
|
+
lastStepId = null; // Reset — next step must use explicit depends_on
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
result.push(entry);
|
|
64
|
+
lastStepId = entry.id;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
9
69
|
/**
|
|
10
70
|
* If no step in the workflow has depends_on, inject implicit sequential
|
|
11
71
|
* dependencies based on array order (step[i] depends_on step[i-1]).
|
|
@@ -332,8 +392,10 @@ export function compileWorkflow(workflow, options) {
|
|
|
332
392
|
});
|
|
333
393
|
return { ok: false, errors };
|
|
334
394
|
}
|
|
395
|
+
// 1.5. Expand parallel blocks (syntax sugar) into regular steps
|
|
396
|
+
const expandedSteps = expandParallelBlocks(workflow.steps);
|
|
335
397
|
// 2. Infer implicit deps if no step has depends_on
|
|
336
|
-
const resolvedSteps = inferImplicitDeps(
|
|
398
|
+
const resolvedSteps = inferImplicitDeps(expandedSteps);
|
|
337
399
|
// 3. Build dependency graph
|
|
338
400
|
const graph = buildDependencyGraph(resolvedSteps);
|
|
339
401
|
// 4. Topological sort + cycle detection
|
package/dist/hooks/cli.d.ts
CHANGED
|
@@ -14,4 +14,23 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Fail-open: all errors → { continue: true, suppressOutput: true }
|
|
16
16
|
*/
|
|
17
|
-
export {
|
|
17
|
+
export interface SessionSummary {
|
|
18
|
+
total: number;
|
|
19
|
+
blocks: number;
|
|
20
|
+
warns: number;
|
|
21
|
+
topBlockedTools: Array<{
|
|
22
|
+
tool: string;
|
|
23
|
+
count: number;
|
|
24
|
+
}>;
|
|
25
|
+
topBlockedPaths: Array<{
|
|
26
|
+
path: string;
|
|
27
|
+
count: number;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
export declare function computeSummary(traces: Array<{
|
|
31
|
+
decision: string;
|
|
32
|
+
tool_name?: string;
|
|
33
|
+
target_path?: string;
|
|
34
|
+
reason?: string;
|
|
35
|
+
}>): SessionSummary;
|
|
36
|
+
export declare function formatSummary(s: SessionSummary): string | null;
|
package/dist/hooks/cli.js
CHANGED
|
@@ -17,15 +17,54 @@
|
|
|
17
17
|
import { readFile, stat } from "node:fs/promises";
|
|
18
18
|
import { resolve } from "node:path";
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
20
|
-
import { readStdin, writeOutput, silentOutput } from "./protocol.js";
|
|
20
|
+
import { readStdin, writeOutput, silentOutput, allowOutput } from "./protocol.js";
|
|
21
21
|
import { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, } from "./enforce.js";
|
|
22
|
-
import { appendAudit, readWorkflowState, appendTrace, rotateTraces } from "./state.js";
|
|
22
|
+
import { appendAudit, readWorkflowState, appendTrace, rotateTraces, readTraces } from "./state.js";
|
|
23
23
|
// ── Constants ──────────────────────────────────────────────
|
|
24
24
|
const DEFAULT_IR_PATH = ".dna/compiled/ir.json";
|
|
25
25
|
const VALID_EVENTS = new Set([
|
|
26
26
|
"PreToolUse", "PostToolUse", "UserPromptSubmit",
|
|
27
27
|
"SubagentStop", "PreCompact", "Notification", "Stop", "SessionStart",
|
|
28
28
|
]);
|
|
29
|
+
export function computeSummary(traces) {
|
|
30
|
+
let blocks = 0, warns = 0;
|
|
31
|
+
const toolCounts = new Map();
|
|
32
|
+
const pathCounts = new Map();
|
|
33
|
+
for (const t of traces) {
|
|
34
|
+
if (t.decision === "block") {
|
|
35
|
+
blocks++;
|
|
36
|
+
if (t.tool_name)
|
|
37
|
+
toolCounts.set(t.tool_name, (toolCounts.get(t.tool_name) ?? 0) + 1);
|
|
38
|
+
if (t.target_path)
|
|
39
|
+
pathCounts.set(t.target_path, (pathCounts.get(t.target_path) ?? 0) + 1);
|
|
40
|
+
}
|
|
41
|
+
else if (t.decision === "warn") {
|
|
42
|
+
warns++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const topBlockedTools = [...toolCounts.entries()]
|
|
46
|
+
.sort((a, b) => b[1] - a[1])
|
|
47
|
+
.slice(0, 3)
|
|
48
|
+
.map(([tool, count]) => ({ tool, count }));
|
|
49
|
+
const topBlockedPaths = [...pathCounts.entries()]
|
|
50
|
+
.sort((a, b) => b[1] - a[1])
|
|
51
|
+
.slice(0, 3)
|
|
52
|
+
.map(([path, count]) => ({ path, count }));
|
|
53
|
+
return { total: traces.length, blocks, warns, topBlockedTools, topBlockedPaths };
|
|
54
|
+
}
|
|
55
|
+
export function formatSummary(s) {
|
|
56
|
+
if (s.blocks === 0 && s.warns === 0)
|
|
57
|
+
return null;
|
|
58
|
+
const lines = [];
|
|
59
|
+
lines.push(`[Intent DNA] Session summary: ${s.total} events, ${s.blocks} blocked, ${s.warns} warned`);
|
|
60
|
+
if (s.topBlockedTools.length > 0) {
|
|
61
|
+
lines.push(" Top blocked tools: " + s.topBlockedTools.map(t => `${t.tool}(${t.count})`).join(", "));
|
|
62
|
+
}
|
|
63
|
+
if (s.topBlockedPaths.length > 0) {
|
|
64
|
+
lines.push(" Top blocked paths: " + s.topBlockedPaths.map(p => `${p.path}(${p.count})`).join(", "));
|
|
65
|
+
}
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
29
68
|
// ── Main ───────────────────────────────────────────────────
|
|
30
69
|
async function main() {
|
|
31
70
|
// Parse args
|
|
@@ -67,7 +106,7 @@ async function main() {
|
|
|
67
106
|
state.existingArtifactPaths = await scanConsumedArtifactPaths(projectDir, ir, wfState.workflow, wfState.current_step);
|
|
68
107
|
}
|
|
69
108
|
}
|
|
70
|
-
// Special handling for Stop — needs async workflow state read
|
|
109
|
+
// Special handling for Stop — needs async workflow state read + session summary
|
|
71
110
|
if (event === "Stop") {
|
|
72
111
|
const wfState = await readWorkflowState(projectDir, sessionId);
|
|
73
112
|
const stopContext = wfState ? {
|
|
@@ -78,11 +117,28 @@ async function main() {
|
|
|
78
117
|
started_at: wfState.started_at,
|
|
79
118
|
completed_artifacts: wfState.completed_artifacts,
|
|
80
119
|
} : null;
|
|
81
|
-
|
|
120
|
+
let stopOutput = enforceStop(ir, {
|
|
82
121
|
cwd: typeof rawInput.cwd === "string" ? rawInput.cwd : undefined,
|
|
83
122
|
session_id: sessionId,
|
|
84
123
|
stop_reason: typeof rawInput.stop_reason === "string" ? rawInput.stop_reason : undefined,
|
|
85
124
|
}, stopContext);
|
|
125
|
+
// Session summary: aggregate block/warn stats from trace
|
|
126
|
+
try {
|
|
127
|
+
const traces = await readTraces(projectDir, 1);
|
|
128
|
+
const summary = computeSummary(traces);
|
|
129
|
+
const summaryText = formatSummary(summary);
|
|
130
|
+
if (summaryText) {
|
|
131
|
+
if (stopOutput.suppressOutput) {
|
|
132
|
+
// Was silent → upgrade to allow with summary
|
|
133
|
+
stopOutput = allowOutput(summaryText, "Stop");
|
|
134
|
+
}
|
|
135
|
+
else if (stopOutput.reason) {
|
|
136
|
+
// Was a block → append summary to reason
|
|
137
|
+
stopOutput.reason += "\n\n" + summaryText;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch { /* fail-open: summary failure never blocks */ }
|
|
86
142
|
writeOutput(stopOutput);
|
|
87
143
|
// Trace for Stop
|
|
88
144
|
appendTrace(projectDir, {
|
|
@@ -91,7 +147,7 @@ async function main() {
|
|
|
91
147
|
workflow: wfState?.workflow,
|
|
92
148
|
step: wfState?.current_step,
|
|
93
149
|
decision: stopOutput.continue === false ? "block" : "allow",
|
|
94
|
-
duration_ms:
|
|
150
|
+
duration_ms: 0,
|
|
95
151
|
timestamp: new Date().toISOString(),
|
|
96
152
|
}).catch(() => { });
|
|
97
153
|
return;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -7,3 +7,4 @@
|
|
|
7
7
|
export { type HookEvent, type HookInput, type HookOutput, type HookInputBase, type PreToolUseInput, type PostToolUseInput, type UserPromptSubmitInput, type SubagentStopInput, type PreCompactInput, type NotificationInput, type StopInput, type SessionStartInput as SessionStartHookInput, readStdin, writeOutput, allowOutput, blockOutput, escalateOutput, silentOutput, } from "./protocol.js";
|
|
8
8
|
export { type EnforceState, type SessionStartInput, type StopEnforceInput, type StopWorkflowContext, enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, checkWriteAllowed, globToPrefix, evaluateGateCondition, resolveToolTarget, } from "./enforce.js";
|
|
9
9
|
export { type DNAWorkflowState, type AuditEntry, resolveStateDir, readWorkflowState, writeWorkflowState, clearWorkflowState, appendAudit, } from "./state.js";
|
|
10
|
+
export { type SessionSummary, computeSummary, formatSummary, } from "./cli.js";
|
package/dist/hooks/index.js
CHANGED
|
@@ -10,3 +10,5 @@ export { readStdin, writeOutput, allowOutput, blockOutput, escalateOutput, silen
|
|
|
10
10
|
export { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, checkWriteAllowed, globToPrefix, evaluateGateCondition, resolveToolTarget, } from "./enforce.js";
|
|
11
11
|
// State management
|
|
12
12
|
export { resolveStateDir, readWorkflowState, writeWorkflowState, clearWorkflowState, appendAudit, } from "./state.js";
|
|
13
|
+
// Session summary
|
|
14
|
+
export { computeSummary, formatSummary, } from "./cli.js";
|
|
@@ -63,6 +63,14 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
|
|
|
63
63
|
}
|
|
64
64
|
lines.push("</Steps>");
|
|
65
65
|
lines.push("");
|
|
66
|
+
// Execution Policy — force-complete standard
|
|
67
|
+
lines.push("<Execution_Policy>");
|
|
68
|
+
lines.push("- Use TodoWrite to track each step as pending/in_progress/completed.");
|
|
69
|
+
lines.push("- After completing each step, immediately proceed to the next — do not stop, summarize, or wait for confirmation.");
|
|
70
|
+
lines.push("- If a step fails, mark it as failed in TodoWrite, log the error, and continue to the next non-dependent step.");
|
|
71
|
+
lines.push("- Do not ask the user for permission between steps — the workflow is pre-approved.");
|
|
72
|
+
lines.push("</Execution_Policy>");
|
|
73
|
+
lines.push("");
|
|
66
74
|
// Tool_Usage — only if roles have permissions or scope
|
|
67
75
|
const toolUsageLines = [];
|
|
68
76
|
for (const roleName of usedRoles) {
|
|
@@ -22,6 +22,13 @@ const SENTINEL_COMMENT = "# intentdna:managed -- do not edit manually";
|
|
|
22
22
|
function sanitizeForBash(id) {
|
|
23
23
|
return id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize a step ID for safe use in shell paths and git branch names.
|
|
27
|
+
* Allows alphanumeric, hyphen, underscore, and dot only.
|
|
28
|
+
*/
|
|
29
|
+
function sanitizeForShell(id) {
|
|
30
|
+
return id.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
31
|
+
}
|
|
25
32
|
/**
|
|
26
33
|
* Get the agent name for a role, using toKebabCase and the configured prefix.
|
|
27
34
|
*/
|
|
@@ -203,7 +210,8 @@ function generateGroupExecution(group, stepMap, options, plan) {
|
|
|
203
210
|
const lines = [];
|
|
204
211
|
const isParallel = group.step_ids.length > 1;
|
|
205
212
|
if (isParallel) {
|
|
206
|
-
|
|
213
|
+
const isoLabel = group.isolation === "worktree" ? "parallel, worktree" : "parallel";
|
|
214
|
+
lines.push(` # Group ${group.group_index}: ${group.step_ids.join(", ")} (${isoLabel})`);
|
|
207
215
|
}
|
|
208
216
|
else {
|
|
209
217
|
lines.push(` # Group ${group.group_index}: ${group.step_ids[0]} (sequential)`);
|
|
@@ -213,8 +221,62 @@ function generateGroupExecution(group, stepMap, options, plan) {
|
|
|
213
221
|
const varId = sanitizeForBash(id);
|
|
214
222
|
lines.push(` STEP_${varId}_STATUS=0`);
|
|
215
223
|
}
|
|
216
|
-
if (isParallel) {
|
|
217
|
-
//
|
|
224
|
+
if (isParallel && group.isolation === "worktree") {
|
|
225
|
+
// Worktree-isolated parallel execution
|
|
226
|
+
lines.push("");
|
|
227
|
+
lines.push(' MAIN_BRANCH=$(git rev-parse --abbrev-ref HEAD)');
|
|
228
|
+
// Create worktrees
|
|
229
|
+
for (const id of group.step_ids) {
|
|
230
|
+
const safeId = sanitizeForShell(id);
|
|
231
|
+
lines.push(` git worktree add ".dna/worktrees/${safeId}" -b "dna-wt-${safeId}" HEAD 2>/dev/null`);
|
|
232
|
+
}
|
|
233
|
+
// Background jobs in worktree dirs
|
|
234
|
+
for (const id of group.step_ids) {
|
|
235
|
+
const step = stepMap.get(id);
|
|
236
|
+
const varId = sanitizeForBash(id);
|
|
237
|
+
const safeId = sanitizeForShell(id);
|
|
238
|
+
const agent = agentName(step.role, options.agentPrefix);
|
|
239
|
+
const prompt = escapeShellSingleQuote(step.prompt ?? step.description);
|
|
240
|
+
lines.push("");
|
|
241
|
+
lines.push(` (cd ".dna/worktrees/${safeId}" && run_agent "${agent}" '${prompt}' "${safeId}") &`);
|
|
242
|
+
lines.push(` PID_${varId}=$!`);
|
|
243
|
+
}
|
|
244
|
+
lines.push("");
|
|
245
|
+
// Wait for all
|
|
246
|
+
for (const id of group.step_ids) {
|
|
247
|
+
const step = stepMap.get(id);
|
|
248
|
+
const varId = sanitizeForBash(id);
|
|
249
|
+
if (step.optional) {
|
|
250
|
+
lines.push(` wait $PID_${varId} || {`);
|
|
251
|
+
lines.push(` STEP_${varId}_STATUS=1`);
|
|
252
|
+
lines.push(` echo "[$(date '+%H:%M:%S')] Optional step '${step.id}' failed, continuing..."`);
|
|
253
|
+
lines.push(" }");
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
lines.push(` wait $PID_${varId} || STEP_${varId}_STATUS=1`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Merge worktrees back (escalate on conflict)
|
|
260
|
+
lines.push("");
|
|
261
|
+
lines.push(" # Merge worktrees back to main branch");
|
|
262
|
+
for (const id of group.step_ids) {
|
|
263
|
+
const varId = sanitizeForBash(id);
|
|
264
|
+
const safeId = sanitizeForShell(id);
|
|
265
|
+
lines.push(` if [ "$STEP_${varId}_STATUS" -eq 0 ]; then`);
|
|
266
|
+
lines.push(` merge_worktree "dna-wt-${safeId}" "${safeId}"`);
|
|
267
|
+
lines.push(" fi");
|
|
268
|
+
}
|
|
269
|
+
// Cleanup worktrees
|
|
270
|
+
lines.push("");
|
|
271
|
+
lines.push(" # Cleanup worktrees");
|
|
272
|
+
for (const id of group.step_ids) {
|
|
273
|
+
const safeId = sanitizeForShell(id);
|
|
274
|
+
lines.push(` git worktree remove ".dna/worktrees/${safeId}" 2>/dev/null || true`);
|
|
275
|
+
lines.push(` git branch -D "dna-wt-${safeId}" 2>/dev/null || true`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else if (isParallel) {
|
|
279
|
+
// Standard parallel execution (no isolation)
|
|
218
280
|
for (const id of group.step_ids) {
|
|
219
281
|
const step = stepMap.get(id);
|
|
220
282
|
const varId = sanitizeForBash(id);
|
|
@@ -248,6 +310,32 @@ function generateGroupExecution(group, stepMap, options, plan) {
|
|
|
248
310
|
}
|
|
249
311
|
return lines;
|
|
250
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Check if any parallel group in the plan uses worktree isolation.
|
|
315
|
+
*/
|
|
316
|
+
function needsWorktreeSupport(plan) {
|
|
317
|
+
return plan.parallel_groups.some(g => g.isolation === "worktree" && g.step_ids.length > 1);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Generate the merge_worktree bash helper function.
|
|
321
|
+
* Only included in scripts that have worktree-isolated parallel groups.
|
|
322
|
+
*/
|
|
323
|
+
function generateMergeWorktreeFn() {
|
|
324
|
+
return [
|
|
325
|
+
"# Merge a worktree branch back to main (escalate on conflict)",
|
|
326
|
+
"merge_worktree() {",
|
|
327
|
+
' local branch="$1" name="$2"',
|
|
328
|
+
' if ! git merge --no-commit --no-ff "$branch" 2>/dev/null; then',
|
|
329
|
+
" git merge --abort",
|
|
330
|
+
' echo "[Intent DNA] CONFLICT: $name conflicts with current branch. Manual resolution required."',
|
|
331
|
+
' echo "[Intent DNA] Branch preserved: $branch"',
|
|
332
|
+
" return 1",
|
|
333
|
+
" fi",
|
|
334
|
+
' git commit -m "merge: $name" --no-edit 2>/dev/null || true',
|
|
335
|
+
"}",
|
|
336
|
+
"",
|
|
337
|
+
];
|
|
338
|
+
}
|
|
251
339
|
/**
|
|
252
340
|
* Generate transition check code after all groups execute.
|
|
253
341
|
*/
|
|
@@ -316,6 +404,10 @@ export function compileWorkflowToShell(plan, options) {
|
|
|
316
404
|
// run_agent function
|
|
317
405
|
lines.push(...generateRunAgentFn(opts));
|
|
318
406
|
lines.push("");
|
|
407
|
+
// merge_worktree function (only if needed)
|
|
408
|
+
if (needsWorktreeSupport(plan)) {
|
|
409
|
+
lines.push(...generateMergeWorktreeFn());
|
|
410
|
+
}
|
|
319
411
|
// Main execution
|
|
320
412
|
if (plan.retry.max_retries > 0) {
|
|
321
413
|
// With retry loop
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -154,6 +154,16 @@ export interface WorkflowDef {
|
|
|
154
154
|
default_isolation?: "none" | "worktree" | "auto";
|
|
155
155
|
merge_strategy?: "escalate";
|
|
156
156
|
}
|
|
157
|
+
/** Parallel block syntax sugar — expanded by compiler into steps with depends_on */
|
|
158
|
+
export interface ParallelBlockDef {
|
|
159
|
+
parallel: {
|
|
160
|
+
isolation?: "none" | "worktree" | "auto";
|
|
161
|
+
merge_strategy?: "escalate";
|
|
162
|
+
steps: WorkflowStepDef[];
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Raw workflow step entry: either a regular step or a parallel block (syntax sugar) */
|
|
166
|
+
export type RawWorkflowStepEntry = WorkflowStepDef | ParallelBlockDef;
|
|
157
167
|
export interface EpigeneticEffect {
|
|
158
168
|
gene?: string;
|
|
159
169
|
action?: ModifierAction;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# incident-response: 生产事故响应
|
|
2
|
+
# 适用: 线上故障排查、紧急修复、事后复盘
|
|
3
|
+
# 核心: 快速止血、根因分析、防止复发
|
|
4
|
+
|
|
5
|
+
version: "0.1.0"
|
|
6
|
+
id: template_incident_response
|
|
7
|
+
name: Incident Response
|
|
8
|
+
type: project
|
|
9
|
+
namespace: ir
|
|
10
|
+
|
|
11
|
+
cascade:
|
|
12
|
+
inherits: ["species:default"]
|
|
13
|
+
priority: 100
|
|
14
|
+
|
|
15
|
+
genes:
|
|
16
|
+
triage_first:
|
|
17
|
+
description: Assess severity and blast radius before fixing
|
|
18
|
+
codons:
|
|
19
|
+
- type: attract
|
|
20
|
+
target: severity_assessment
|
|
21
|
+
- type: attract
|
|
22
|
+
target: blast_radius_check
|
|
23
|
+
- type: repel
|
|
24
|
+
target: premature_fix
|
|
25
|
+
- type: sense
|
|
26
|
+
signal: production_alert
|
|
27
|
+
response: prioritize_investigation
|
|
28
|
+
|
|
29
|
+
minimal_fix:
|
|
30
|
+
description: Apply the smallest change that stops the bleeding
|
|
31
|
+
codons:
|
|
32
|
+
- type: attract
|
|
33
|
+
target: targeted_hotfix
|
|
34
|
+
- type: attract
|
|
35
|
+
target: revert_option
|
|
36
|
+
- type: repel
|
|
37
|
+
target: refactor_during_incident
|
|
38
|
+
- type: weight
|
|
39
|
+
a: speed
|
|
40
|
+
b: elegance
|
|
41
|
+
ratio: 0.9
|
|
42
|
+
|
|
43
|
+
evidence_driven:
|
|
44
|
+
description: Collect evidence before hypothesizing
|
|
45
|
+
codons:
|
|
46
|
+
- type: attract
|
|
47
|
+
target: logs_and_metrics
|
|
48
|
+
- type: attract
|
|
49
|
+
target: reproduction_steps
|
|
50
|
+
- type: repel
|
|
51
|
+
target: guessing_root_cause
|
|
52
|
+
- type: threshold
|
|
53
|
+
condition: "has_evidence == true"
|
|
54
|
+
action: escalate
|
|
55
|
+
|
|
56
|
+
prevent_recurrence:
|
|
57
|
+
description: Every fix must include prevention measures
|
|
58
|
+
codons:
|
|
59
|
+
- type: attract
|
|
60
|
+
target: regression_test
|
|
61
|
+
- type: attract
|
|
62
|
+
target: monitoring_alert
|
|
63
|
+
- type: repel
|
|
64
|
+
target: fix_without_test
|
|
65
|
+
|
|
66
|
+
contexts: {}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# migration-safety: 安全迁移
|
|
2
|
+
# 适用: 数据库迁移、依赖升级、框架版本迁移、API 版本切换
|
|
3
|
+
# 核心: 回滚优先、渐进式、验证每一步
|
|
4
|
+
|
|
5
|
+
version: "0.1.0"
|
|
6
|
+
id: template_migration_safety
|
|
7
|
+
name: Migration Safety
|
|
8
|
+
type: project
|
|
9
|
+
namespace: mig
|
|
10
|
+
|
|
11
|
+
cascade:
|
|
12
|
+
inherits: ["species:default"]
|
|
13
|
+
priority: 100
|
|
14
|
+
|
|
15
|
+
genes:
|
|
16
|
+
rollback_first:
|
|
17
|
+
description: Every migration step must have a rollback plan
|
|
18
|
+
codons:
|
|
19
|
+
- type: attract
|
|
20
|
+
target: reversible_changes
|
|
21
|
+
- type: attract
|
|
22
|
+
target: rollback_script
|
|
23
|
+
- type: repel
|
|
24
|
+
target: destructive_migration
|
|
25
|
+
- type: threshold
|
|
26
|
+
condition: "has_rollback_plan == true"
|
|
27
|
+
action: block
|
|
28
|
+
|
|
29
|
+
incremental_migration:
|
|
30
|
+
description: Migrate incrementally, verify at each stage
|
|
31
|
+
codons:
|
|
32
|
+
- type: attract
|
|
33
|
+
target: phased_migration
|
|
34
|
+
- type: attract
|
|
35
|
+
target: canary_first
|
|
36
|
+
- type: repel
|
|
37
|
+
target: big_bang_migration
|
|
38
|
+
- type: weight
|
|
39
|
+
a: safety
|
|
40
|
+
b: speed
|
|
41
|
+
ratio: 0.9
|
|
42
|
+
|
|
43
|
+
backward_compatible:
|
|
44
|
+
description: Maintain backward compatibility during transition
|
|
45
|
+
codons:
|
|
46
|
+
- type: attract
|
|
47
|
+
target: dual_write
|
|
48
|
+
- type: attract
|
|
49
|
+
target: feature_flags
|
|
50
|
+
- type: repel
|
|
51
|
+
target: breaking_change_without_migration
|
|
52
|
+
- type: sense
|
|
53
|
+
signal: deprecation_warning
|
|
54
|
+
response: plan_migration_path
|
|
55
|
+
|
|
56
|
+
verify_data_integrity:
|
|
57
|
+
description: Validate data consistency after each migration step
|
|
58
|
+
codons:
|
|
59
|
+
- type: attract
|
|
60
|
+
target: data_validation_checks
|
|
61
|
+
- type: attract
|
|
62
|
+
target: diff_before_after
|
|
63
|
+
- type: threshold
|
|
64
|
+
condition: "data_loss == false"
|
|
65
|
+
action: block
|
|
66
|
+
|
|
67
|
+
contexts: {}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# performance-audit: 性能优化
|
|
2
|
+
# 适用: 性能分析、基准测试、瓶颈排查、优化实施
|
|
3
|
+
# 核心: 先测量再优化、数据驱动、避免过早优化
|
|
4
|
+
|
|
5
|
+
version: "0.1.0"
|
|
6
|
+
id: template_performance_audit
|
|
7
|
+
name: Performance Audit
|
|
8
|
+
type: project
|
|
9
|
+
namespace: perf
|
|
10
|
+
|
|
11
|
+
cascade:
|
|
12
|
+
inherits: ["species:default"]
|
|
13
|
+
priority: 100
|
|
14
|
+
|
|
15
|
+
genes:
|
|
16
|
+
measure_first:
|
|
17
|
+
description: Profile and benchmark before making any change
|
|
18
|
+
codons:
|
|
19
|
+
- type: attract
|
|
20
|
+
target: profiling_data
|
|
21
|
+
- type: attract
|
|
22
|
+
target: baseline_benchmark
|
|
23
|
+
- type: repel
|
|
24
|
+
target: optimize_without_measurement
|
|
25
|
+
- type: threshold
|
|
26
|
+
condition: "has_baseline_metrics == true"
|
|
27
|
+
action: escalate
|
|
28
|
+
|
|
29
|
+
targeted_optimization:
|
|
30
|
+
description: Optimize the actual bottleneck, not hunches
|
|
31
|
+
codons:
|
|
32
|
+
- type: attract
|
|
33
|
+
target: hotspot_analysis
|
|
34
|
+
- type: attract
|
|
35
|
+
target: algorithmic_improvement
|
|
36
|
+
- type: repel
|
|
37
|
+
target: premature_optimization
|
|
38
|
+
- type: weight
|
|
39
|
+
a: correctness
|
|
40
|
+
b: performance
|
|
41
|
+
ratio: 0.7
|
|
42
|
+
|
|
43
|
+
regression_prevention:
|
|
44
|
+
description: Ensure optimizations don't break functionality
|
|
45
|
+
codons:
|
|
46
|
+
- type: attract
|
|
47
|
+
target: before_after_comparison
|
|
48
|
+
- type: attract
|
|
49
|
+
target: existing_tests_pass
|
|
50
|
+
- type: repel
|
|
51
|
+
target: optimization_breaks_tests
|
|
52
|
+
- type: threshold
|
|
53
|
+
condition: "test_regression == false"
|
|
54
|
+
action: block
|
|
55
|
+
|
|
56
|
+
document_results:
|
|
57
|
+
description: Record measurements and decisions for future reference
|
|
58
|
+
codons:
|
|
59
|
+
- type: attract
|
|
60
|
+
target: performance_report
|
|
61
|
+
- type: attract
|
|
62
|
+
target: optimization_rationale
|
|
63
|
+
- type: sense
|
|
64
|
+
signal: performance_improvement
|
|
65
|
+
response: record_technique
|
|
66
|
+
|
|
67
|
+
contexts: {}
|