@relipa/ai-flow-kit 0.0.5-beta.1 → 0.0.6-beta.0

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/scripts/doctor.js CHANGED
@@ -1,48 +1,89 @@
1
- const fs = require('fs-extra');
2
- const path = require('path');
3
- const chalk = require('chalk');
4
-
5
- module.exports = async function doctor() {
6
- const projectDir = process.cwd();
7
- const errors = [];
8
-
9
- console.log(chalk.blue('Running health check for AI Flow Kit...\n'));
10
-
11
- // Check .claude directory
12
- if (!(await fs.pathExists(path.join(projectDir, '.claude', 'skills')))) {
13
- errors.push("Missing `.claude/skills` directory. Did you run `aiflow init`?");
14
- } else {
15
- console.log(chalk.green(' .claude/skills exists'));
16
- }
17
-
18
- // Check .rules directory
19
- if (!(await fs.pathExists(path.join(projectDir, '.rules')))) {
20
- errors.push("Missing `.rules` directory.");
21
- } else {
22
- console.log(chalk.green('✓ .rules exists'));
23
- }
24
-
25
- // Check CLAUDE.md template
26
- if (!(await fs.pathExists(path.join(projectDir, 'CLAUDE.md')))) {
27
- errors.push("Missing CLAUDE.md. Prompt AI Context is not set up.");
28
- } else {
29
- console.log(chalk.green('✓ CLAUDE.md exists'));
30
- }
31
-
32
- // Check .aiflow hidden state
33
- const stateFile = path.join(projectDir, '.aiflow', 'state.json');
34
- if (!(await fs.pathExists(stateFile))) {
35
- errors.push("Missing `.aiflow/state.json`. Version tracking is broken.");
36
- } else {
37
- const state = await fs.readJson(stateFile);
38
- console.log(chalk.green(`✓ Version tracking active (v${state.current_version})`));
39
- }
40
-
41
- if (errors.length > 0) {
42
- console.log(chalk.red('\nIssues found:'));
43
- errors.forEach(e => console.log(chalk.red(` - ${e}`)));
44
- console.log(chalk.yellow('\nTry running `aiflow init` or `aiflow update` to fix these files.'));
45
- } else {
46
- console.log(chalk.green('\n✨ Everything looks healthy! You are ready to fly.'));
47
- }
48
- };
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const { detectRtk, isRtkHookConfigured } = require('./init');
5
+
6
+ module.exports = async function doctor() {
7
+ const projectDir = process.cwd();
8
+ const errors = [];
9
+ const warnings = [];
10
+
11
+ console.log(chalk.blue('Running health check for AI Flow Kit...\n'));
12
+
13
+ // ── Core setup ─────────────────────────────────────────────
14
+ if (!(await fs.pathExists(path.join(projectDir, '.claude', 'skills')))) {
15
+ errors.push('Missing `.claude/skills` directory. Did you run `aiflow init`?');
16
+ } else {
17
+ console.log(chalk.green('✓ .claude/skills exists'));
18
+ }
19
+
20
+ if (!(await fs.pathExists(path.join(projectDir, '.rules')))) {
21
+ errors.push('Missing `.rules` directory.');
22
+ } else {
23
+ console.log(chalk.green('✓ .rules exists'));
24
+ }
25
+
26
+ if (!(await fs.pathExists(path.join(projectDir, 'CLAUDE.md')))) {
27
+ errors.push('Missing CLAUDE.md. AI context prompt is not set up.');
28
+ } else {
29
+ console.log(chalk.green('✓ CLAUDE.md exists'));
30
+ }
31
+
32
+ // ── Version tracking ───────────────────────────────────────
33
+ const stateFile = path.join(projectDir, '.aiflow', 'state.json');
34
+ if (!(await fs.pathExists(stateFile))) {
35
+ errors.push('Missing `.aiflow/state.json`. Version tracking is broken.');
36
+ } else {
37
+ const state = await fs.readJson(stateFile);
38
+ console.log(chalk.green(`✓ Version tracking active (v${state.current_version})`));
39
+ }
40
+
41
+ // ── SessionStart hook ──────────────────────────────────────
42
+ const settingsPath = path.join(projectDir, '.claude', 'settings.json');
43
+ if (await fs.pathExists(settingsPath)) {
44
+ const settings = await fs.readJson(settingsPath).catch(() => ({}));
45
+ const hookList = settings?.hooks?.SessionStart || [];
46
+ const hasSessionHook = hookList.some(h =>
47
+ (h.hooks || []).some(e => typeof e.command === 'string' && e.command.includes('session-start'))
48
+ );
49
+ if (hasSessionHook) {
50
+ console.log(chalk.green('✓ SessionStart hook configured'));
51
+ } else {
52
+ warnings.push('SessionStart hook not found in .claude/settings.json — run `aiflow init` to set it up.');
53
+ }
54
+ } else {
55
+ warnings.push('Missing .claude/settings.json — run `aiflow init`.');
56
+ }
57
+
58
+ // ── RTK token compression ──────────────────────────────────
59
+ console.log('');
60
+ const rtk = await detectRtk();
61
+ if (rtk.installed) {
62
+ const hookOk = await isRtkHookConfigured(projectDir);
63
+ if (hookOk) {
64
+ console.log(chalk.green(`✓ RTK installed (${rtk.version}) — token compression active`));
65
+ } else {
66
+ console.log(chalk.green(`✓ RTK installed (${rtk.version})`));
67
+ warnings.push('RTK is installed but hook not configured. Run `aiflow init` to enable token compression.');
68
+ }
69
+ } else {
70
+ console.log(chalk.gray('○ RTK not installed (optional) — token compression inactive'));
71
+ console.log(chalk.gray(' Install: cargo install rtk | More: https://github.com/rtk-ai/rtk'));
72
+ }
73
+
74
+ // ── Summary ────────────────────────────────────────────────
75
+ console.log('');
76
+ if (warnings.length > 0) {
77
+ console.log(chalk.yellow('Warnings:'));
78
+ warnings.forEach(w => console.log(chalk.yellow(` ⚠ ${w}`)));
79
+ console.log('');
80
+ }
81
+
82
+ if (errors.length > 0) {
83
+ console.log(chalk.red('Issues found:'));
84
+ errors.forEach(e => console.log(chalk.red(` ✗ ${e}`)));
85
+ console.log(chalk.yellow('\nTry running `aiflow init` or `aiflow update` to fix these files.'));
86
+ } else {
87
+ console.log(chalk.green('✨ Everything looks healthy! You are ready to fly.'));
88
+ }
89
+ };
package/scripts/guide.js CHANGED
@@ -113,17 +113,17 @@ function showWorkflow() {
113
113
 
114
114
  // Step 2: Load context
115
115
  console.log(chalk.bold.white(' Step 2 — Load context from ticket (per task)'));
116
- console.log(chalk.green(' aiflow use PROJ-33 --with-comments'));
117
- console.log(chalk.green(' aiflow prompt bug-fix'));
116
+ console.log(chalk.green(' aiflow use PROJ-33') + chalk.gray(' # Fast Mode (Default)'));
117
+ console.log(chalk.green(' aiflow use PROJ-33 --full') + chalk.gray(' # Full Mode (Optional)'));
118
118
  console.log(chalk.green(' claude'));
119
119
  console.log();
120
120
 
121
121
  // Gate workflow
122
122
  console.log(chalk.bold.white(' Step 3 — AI runs Strict Gate Workflow automatically'));
123
123
  console.log();
124
- console.log(chalk.yellow(' ⛩️ GATE 1') + chalk.gray(' — AI checks ticket for all 4 required components'));
125
- console.log(chalk.gray(' If missing ask one by one until complete'));
126
- console.log(chalk.gray(' Developer types "continue"move to Gate 2'));
124
+ console.log(chalk.yellow(' ⛩️ GATE 1') + chalk.gray(' — AI checks requirement and solution approach'));
125
+ console.log(chalk.gray(' Fast Mode (Default): Minimal Q&A, target < 5 min'));
126
+ console.log(chalk.gray(' Developer reviews requirement.md"APPROVED"'));
127
127
  console.log();
128
128
  console.log(chalk.yellow(' ⛩️ GATE 2') + chalk.gray(' — AI generates detailed spec + plan'));
129
129
  console.log(chalk.gray(' Developer reviews each section'));
@@ -158,8 +158,12 @@ function showCommands() {
158
158
  group: 'Setup',
159
159
  items: [
160
160
  ['aiflow init', '--framework <fw> --adapter <ad>', 'Initialize project'],
161
+ ['aiflow init', '--env <tools>', 'AI tools: cursor,gemini,copilot'],
162
+ ['aiflow init', '--with-rtk | --no-rtk', 'Enable/disable RTK compression'],
161
163
  ['aiflow doctor', '', 'Check setup health'],
164
+ ['aiflow doctor', '--verbose', 'Detailed health check output'],
162
165
  ['aiflow update', '', 'Update to the latest version'],
166
+ ['aiflow update', '--force', 'Force update even if already latest'],
163
167
  ['aiflow remove', '', 'Remove ai-flow-kit from project'],
164
168
  ['aiflow remove', '--version <ver>', 'Remove a cached version'],
165
169
  ['aiflow remove', '--global', 'Uninstall global package'],
@@ -169,16 +173,33 @@ function showCommands() {
169
173
  group: 'Workflow (use per task)',
170
174
  items: [
171
175
  ['aiflow use <TICKET-ID>', '', 'Load context from Backlog/Jira'],
176
+ ['aiflow use', '--fast', 'Fast Mode: minimal Q&A (Default)'],
177
+ ['aiflow use', '--full', 'Full Mode: deep analysis with Q&A'],
178
+ ['aiflow use', '--file <path>', 'Load from local text/json file'],
172
179
  ['aiflow use', '--manual', 'Enter context manually'],
173
180
  ['aiflow use', '--with-comments', 'Include all comments'],
174
181
  ['aiflow use', '--comments-last <n>', 'Only get last N comments'],
182
+ ['aiflow use', '--comments-from <n>', 'Get comments from index N onward'],
175
183
  ['aiflow prompt <type>', '', 'Generate AI prompt'],
176
184
  ['aiflow prompt', '--list', 'View all prompt types'],
177
185
  ['aiflow prompt', '--output <file>', 'Save prompt to file'],
178
186
  ['aiflow prompt', '--lang vietnamese', 'Prompt in Vietnamese'],
187
+ ['aiflow prompt', '--detail <level>', 'Detail: minimal|standard|comprehensive'],
179
188
  ['aiflow detect "<description>"', '', 'Auto-detect task type'],
180
189
  ]
181
190
  },
191
+ {
192
+ group: 'Task management',
193
+ items: [
194
+ ['aiflow task status', '', 'Show active and pending tasks'],
195
+ ['aiflow task list', '', 'List all saved tasks'],
196
+ ['aiflow task pause', '--note <text>', 'Pause current task and save progress'],
197
+ ['aiflow task switch <id>', '', 'Pause current and switch to another'],
198
+ ['aiflow task resume <id>', '', 'Resume a paused task'],
199
+ ['aiflow task reset <id>', '', 'Reset task to Gate 1 (keeps context)'],
200
+ ['aiflow task remove <id>', '', 'Permanently delete all task data'],
201
+ ]
202
+ },
182
203
  {
183
204
  group: 'Context management',
184
205
  items: [
@@ -206,6 +227,7 @@ function showCommands() {
206
227
  items: [
207
228
  ['aiflow validate <file>', '', 'Validate code against team rules'],
208
229
  ['aiflow validate <file>', '--fix', 'Auto-fix trailing whitespace'],
230
+ ['aiflow validate <file>', '--verbose', 'Detailed validation report'],
209
231
  ['aiflow validate <file>', '-r lenient', 'Only report critical errors'],
210
232
  ['aiflow validate <file>', '-r strict', 'Report all warnings'],
211
233
  ]
@@ -224,6 +246,7 @@ function showCommands() {
224
246
  items: [
225
247
  ['aiflow gate <n> start', '--ticket <id>', 'Log gate N started (AI calls this)'],
226
248
  ['aiflow gate <n> approved', '--ticket <id>', 'Log gate N approved (AI calls this)'],
249
+ ['aiflow gate <n> start', '--ai-tool <tool>', 'Specify AI tool name'],
227
250
  ]
228
251
  },
229
252
  {
@@ -3,29 +3,28 @@
3
3
  * SessionStart hook for ai-flow-kit
4
4
  * Injects:
5
5
  * 1. using-superpowers skill content
6
- * 2. Active ticket context + gate workflow (if context exists)
7
- * Cross-platform replacement for the bash session-start hook.
6
+ * 2. Active ticket context + gate-aware workflow instruction
8
7
  */
9
8
 
10
9
  const fs = require('fs');
11
10
  const path = require('path');
12
11
  const { record } = require('../telemetry/record');
13
12
 
14
- // Record session start telemetry
15
13
  record('session.start');
16
14
 
17
15
  // This script lives at .claude/hooks/session-start.js
18
- // Project root is 2 levels up from __dirname (.claude/hooks → .claude → project root)
16
+ // Project root is 2 levels up: .claude/hooks → .claude → project root
19
17
  const projectRoot = path.resolve(__dirname, '..', '..');
20
- const skillPath = path.join(projectRoot, '.claude', 'skills', 'using-superpowers', 'SKILL.md');
18
+ const skillPath = path.join(projectRoot, '.claude', 'skills', 'using-superpowers', 'SKILL.md');
21
19
  const contextPath = path.join(projectRoot, '.aiflow', 'context', 'current.json');
20
+ const tasksDir = path.join(projectRoot, '.aiflow', 'tasks');
22
21
 
23
22
  // ── 1. Load superpowers skill ──────────────────────────────────
24
23
  let skillContent = '';
25
24
  try {
26
25
  skillContent = fs.readFileSync(skillPath, 'utf-8');
27
26
  } catch (_) {
28
- // Skill file missing — continue without it
27
+ process.stderr.write('[aiflow] WARNING: using-superpowers skill missing — run `aiflow update`\n');
29
28
  }
30
29
 
31
30
  // ── 2. Load active ticket context ──────────────────────────────
@@ -34,7 +33,8 @@ try {
34
33
  if (fs.existsSync(contextPath)) {
35
34
  const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf-8'));
36
35
  if (ctx.taskId && ctx.title) {
37
- contextBlock = buildContextPrompt(ctx);
36
+ const taskState = loadTaskState(ctx.taskId);
37
+ contextBlock = buildContextPrompt(ctx, taskState);
38
38
  }
39
39
  }
40
40
  } catch (_) {
@@ -54,7 +54,6 @@ if (contextBlock) {
54
54
 
55
55
  const combined = parts.join('\n\n');
56
56
 
57
- // Escape for JSON string embedding
58
57
  const escaped = combined
59
58
  .replace(/\\/g, '\\\\')
60
59
  .replace(/"/g, '\\"')
@@ -71,15 +70,23 @@ process.stdout.write(JSON.stringify({
71
70
 
72
71
  process.exit(0);
73
72
 
74
- // ── Helper ─────────────────────────────────────────────────────
73
+ // ── Helpers ────────────────────────────────────────────────────
75
74
 
76
- function buildContextPrompt(ctx) {
77
- const taskType = ctx.taskType || 'feature';
75
+ function loadTaskState(taskId) {
76
+ try {
77
+ const statePath = path.join(tasksDir, taskId, 'task-state.json');
78
+ if (fs.existsSync(statePath)) {
79
+ return JSON.parse(fs.readFileSync(statePath, 'utf-8'));
80
+ }
81
+ } catch (_) {}
82
+ return null;
83
+ }
78
84
 
85
+ function buildContextPrompt(ctx, taskState) {
79
86
  const lines = [];
80
87
  lines.push('<ACTIVE_TASK>');
81
88
  lines.push(`**Active Ticket:** ${ctx.taskId} — ${ctx.title}`);
82
- lines.push(`**Type:** ${taskType}`);
89
+ lines.push(`**Type:** ${ctx.taskType || 'feature'}`);
83
90
  lines.push(`**Status:** ${ctx.status || 'Unknown'}`);
84
91
  lines.push(`**Assignee:** ${ctx.assignee || 'Unknown'}`);
85
92
  lines.push('');
@@ -92,54 +99,146 @@ function buildContextPrompt(ctx) {
92
99
 
93
100
  if (ctx.acceptanceCriteria && ctx.acceptanceCriteria.length > 0) {
94
101
  lines.push('**Acceptance Criteria:**');
95
- for (const c of ctx.acceptanceCriteria) {
96
- lines.push(`- ${c}`);
97
- }
102
+ for (const c of ctx.acceptanceCriteria) lines.push(`- ${c}`);
98
103
  lines.push('');
99
104
  }
100
105
 
101
106
  if (ctx.comments && ctx.comments.length > 0) {
102
107
  lines.push(`**Comments (${ctx.comments.length}):**`);
103
- // Include last 5 comments to keep context manageable
104
108
  const recent = ctx.comments.slice(-5);
105
- for (const c of recent) {
106
- lines.push(c);
107
- lines.push('');
108
- }
109
+ for (const c of recent) { lines.push(c); lines.push(''); }
109
110
  }
110
111
 
111
112
  if (ctx.context && ctx.context.files && ctx.context.files.length > 0) {
112
113
  lines.push('**Related Files:**');
113
- for (const f of ctx.context.files) {
114
- lines.push(`- \`${f}\``);
115
- }
114
+ for (const f of ctx.context.files) lines.push(`- \`${f}\``);
116
115
  lines.push('');
117
116
  }
118
117
 
119
- // ── Gate workflow instruction ──
120
118
  lines.push('---');
121
119
  lines.push('');
122
- lines.push('**AUTO-START GATE 1: You MUST begin analyzing this ticket immediately.**');
123
- lines.push('');
124
- lines.push(`INVOKE the \`read-study-requirement\` skill NOW for ticket ${ctx.taskId}.`);
125
- lines.push('');
126
- lines.push('Gate 1 process:');
127
- lines.push('1. Read the ticket context above');
128
- lines.push('2. Read CLAUDE.md — understand project architecture and conventions');
129
- lines.push('3. Read source code — identify related files, data flow, patterns');
130
- lines.push('4. If anything is unclear — ask ONE question at a time');
131
- lines.push('5. Output plan/' + ctx.taskId + '/requirement.md with:');
132
- lines.push(' - Requirements summary');
133
- lines.push(' - Source code analysis');
134
- lines.push(' - Proposed solution and approach');
135
- lines.push(' - Impact analysis');
136
- lines.push(' - Effort estimate');
137
- lines.push(' - Testing plan');
138
- lines.push('6. Display GATE 1 prompt and wait for APPROVED');
139
- lines.push('');
140
- lines.push('DO NOT wait for the developer to ask. START NOW.');
141
- lines.push('If you have not started automatically, begin as soon as the developer types **"start"**.');
142
- lines.push('</ACTIVE_TASK>');
143
120
 
121
+ // ── Gate-aware instruction ──────────────────────────────────
122
+ const mode = ctx.mode || 'fast';
123
+ const currentGate = taskState && taskState.currentGate ? taskState.currentGate : 1;
124
+ const isResume = taskState && taskState.pausedAt;
125
+
126
+ if (isResume && currentGate > 1) {
127
+ // Resuming a paused task — inject resume context
128
+ const gateApprovals = taskState.gateApprovals || {};
129
+ lines.push(`**RESUMING TASK: ${ctx.taskId} — Gate ${currentGate}**`);
130
+ lines.push('');
131
+ lines.push('This task was previously paused and is now being resumed.');
132
+ lines.push('');
133
+ lines.push('**Gate Progress:**');
134
+ for (let g = 1; g < currentGate; g++) {
135
+ const approvedAt = gateApprovals[String(g)];
136
+ const stamp = approvedAt ? ` — approved ${approvedAt}` : ' — approved';
137
+ lines.push(`- Gate ${g}: ✅ APPROVED${stamp}`);
138
+ }
139
+ lines.push(`- Gate ${currentGate}: 🔄 IN PROGRESS`);
140
+ lines.push('');
141
+ lines.push(`**DO NOT restart from Gate 1.** Resume directly at Gate ${currentGate} (${gateLabel(currentGate)}).`);
142
+ lines.push('');
143
+
144
+ if (currentGate === 2) {
145
+ lines.push('Gate 1 is already APPROVED. The requirement document is at:');
146
+ lines.push(` plan/${ctx.taskId}/requirement.md`);
147
+ lines.push('');
148
+ lines.push('Proceed to Gate 2: create the implementation plan.');
149
+ lines.push(buildModeInstruction(mode, 2));
150
+ } else if (currentGate === 3) {
151
+ lines.push('Gates 1 and 2 are already APPROVED. Plans are at:');
152
+ lines.push(` plan/${ctx.taskId}/requirement.md`);
153
+ lines.push(` plan/${ctx.taskId}/plan.md`);
154
+ lines.push('');
155
+ lines.push('Proceed to Gate 3: implement code following the plan (TDD).');
156
+ lines.push(buildModeInstruction(mode, 3));
157
+ } else if (currentGate === 4) {
158
+ lines.push('Gates 1–3 are APPROVED. Code has been implemented.');
159
+ lines.push('Proceed to Gate 4: run verification + impact-analysis + review checklist.');
160
+ lines.push(buildModeInstruction(mode, 4));
161
+ } else if (currentGate === 5) {
162
+ lines.push('Gates 1–4 are APPROVED. Self-review is done.');
163
+ lines.push('Proceed to Gate 5: guide the developer to create a Pull Request.');
164
+ }
165
+ if (taskState.notes) {
166
+ lines.push('');
167
+ lines.push(`**Note from developer:** ${taskState.notes}`);
168
+ }
169
+ } else {
170
+ // Fresh task — auto-start Gate 1
171
+ lines.push('**AUTO-START GATE 1: You MUST begin analyzing this ticket immediately.**');
172
+ lines.push('');
173
+ lines.push(`INVOKE the \`read-study-requirement\` skill NOW for ticket ${ctx.taskId}.`);
174
+ lines.push('');
175
+
176
+ if (mode === 'fast') {
177
+ lines.push('**MODE: fast** — Use the FAST TRACK path in the skill:');
178
+ lines.push('- Read ticket + identify changed files only (no deep source scan)');
179
+ lines.push('- Skip Q&A unless there is a critical blocking ambiguity');
180
+ lines.push('- Output a lite requirement doc (Sections 1, 3, 5 only)');
181
+ lines.push('- Target: Gate 1 complete in < 5 minutes');
182
+ } else {
183
+ lines.push('Gate 1 process:');
184
+ lines.push('1. Read the ticket context above');
185
+ lines.push('2. Read CLAUDE.md — understand project architecture and conventions');
186
+ lines.push('3. Read source code — identify related files, data flow, patterns');
187
+ lines.push('4. If anything is unclear — ask ONE question at a time');
188
+ lines.push('5. Output plan/' + ctx.taskId + '/requirement.md');
189
+ lines.push('6. Display GATE 1 prompt and wait for APPROVED');
190
+ }
191
+
192
+ lines.push('');
193
+ lines.push('DO NOT wait for the developer to ask. START NOW.');
194
+ lines.push('If you have not started automatically, begin as soon as the developer types **"start"**.');
195
+ }
196
+
197
+ lines.push('</ACTIVE_TASK>');
144
198
  return lines.join('\n');
145
199
  }
200
+
201
+ function gateLabel(n) {
202
+ const labels = {
203
+ 1: 'AI Analyze Requirement',
204
+ 2: 'Implementation Plan',
205
+ 3: 'Code Generation',
206
+ 4: 'AI Self-Review',
207
+ 5: 'Peer Review & PR',
208
+ };
209
+ return labels[n] || `Gate ${n}`;
210
+ }
211
+
212
+ function buildModeInstruction(mode, gate) {
213
+ if (mode !== 'fast') return ''; // full mode: không inject gì thêm, AI chạy đầy đủ các bước
214
+
215
+ const instructions = {
216
+ 2: [
217
+ '**MODE: fast** — Gate 2 fast track:',
218
+ '- Create a simple task list (task-level, NOT step-level)',
219
+ '- DO NOT invoke `superpowers:writing-plans`',
220
+ '- Format: numbered list of tasks, each with file + action',
221
+ '- No code snippets, no step-by-step TDD breakdown required',
222
+ ],
223
+ 3: [
224
+ '**MODE: fast** — Gate 3 fast track:',
225
+ '- DO NOT use `superpowers:subagent-driven-development`',
226
+ '- DO NOT spawn Spec Reviewer or Code Quality Reviewer subagents',
227
+ '- Implement ALL tasks directly in this session',
228
+ '- Write tests and implementation in the same pass per task',
229
+ '- Run the test suite ONCE at the end of all tasks (not after each step)',
230
+ '- If tests fail: fix inline, do not spawn a subagent',
231
+ ],
232
+ 4: [
233
+ '**MODE: fast** — Gate 4 fast track:',
234
+ '- Run the full test suite — this is MANDATORY',
235
+ '- Impact Analysis: quick scan only — reason from git diff and changed files.',
236
+ ' DO NOT grep the entire codebase. List affected files and assess impact level.',
237
+ '- Review checklist (3 items only): tests pass ✅, no hardcoded secrets ✅, no SQL injection ✅',
238
+ '- summary.md: short format (Implementation table + Test result only)',
239
+ ],
240
+ };
241
+
242
+ const lines = instructions[gate];
243
+ return lines ? '\n' + lines.join('\n') : '';
244
+ }