5-phase-workflow 2.0.6 → 3.0.0-beta-2
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/README.md +16 -17
- package/bin/install.js +113 -15
- package/docs/workflow-guide.md +25 -26
- package/package.json +1 -1
- package/src/agents/step-executor-agent.md +6 -8
- package/src/agents/step-orchestrator-agent.md +10 -44
- package/src/agents/verification-agent.md +10 -45
- package/src/commands/5/commit.md +14 -83
- package/src/commands/5/configure.md +8 -29
- package/src/commands/5/implement.md +85 -139
- package/src/commands/5/plan.md +129 -109
- package/src/commands/5/reconfigure.md +3 -3
- package/src/commands/5/review.md +53 -37
- package/src/commands/5/split.md +15 -119
- package/src/references/configure-tables.md +0 -5
- package/src/settings.json +0 -10
- package/src/workflows/5-implement.js +328 -0
- package/src/commands/5/discuss-feature.md +0 -189
- package/src/commands/5/unlock.md +0 -23
- package/src/hooks/plan-guard.js +0 -269
- package/src/templates/workflow/STATE.json +0 -43
package/src/hooks/plan-guard.js
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Plan Guard - PreToolUse Hook
|
|
4
|
-
// Prevents LLM breakout from planning phases by blocking:
|
|
5
|
-
// - Agent/Task agents other than Explore (when not in implementation mode)
|
|
6
|
-
// - Write/Edit operations outside .5/ (when not in implementation mode)
|
|
7
|
-
//
|
|
8
|
-
// Planning mode is detected per-feature by checking if that specific feature's
|
|
9
|
-
// state.json exists. Only the feature in implementation mode gets unrestricted
|
|
10
|
-
// tool access. Other features remain in planning mode.
|
|
11
|
-
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
|
|
15
|
-
let input = '';
|
|
16
|
-
process.stdin.setEncoding('utf8');
|
|
17
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
18
|
-
process.stdin.on('end', () => {
|
|
19
|
-
try {
|
|
20
|
-
const data = JSON.parse(input);
|
|
21
|
-
const toolName = data.tool_name || '';
|
|
22
|
-
|
|
23
|
-
// Short-circuit: only check Agent, Task, Write, Edit, EnterPlanMode, and Bash tools
|
|
24
|
-
if (toolName !== 'Agent' && toolName !== 'Task' && toolName !== 'Write' && toolName !== 'Edit' && toolName !== 'EnterPlanMode' && toolName !== 'Bash') {
|
|
25
|
-
process.exit(0);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const workspaceDir = data.cwd || data.workspace?.current_dir || process.cwd();
|
|
29
|
-
|
|
30
|
-
// If no planning phase is active, allow all tools
|
|
31
|
-
if (!isPlanningActive(workspaceDir)) {
|
|
32
|
-
process.exit(0);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const toolInput = data.tool_input || {};
|
|
36
|
-
|
|
37
|
-
// Determine which feature is being targeted and check its state
|
|
38
|
-
const targetFeature = getTargetFeature(toolName, toolInput, workspaceDir);
|
|
39
|
-
if (targetFeature && isFeatureInImplementationMode(workspaceDir, targetFeature)) {
|
|
40
|
-
process.exit(0); // Tools allowed for features in implementation mode
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Planning mode enforcement
|
|
44
|
-
if (toolName === 'EnterPlanMode') {
|
|
45
|
-
const blockCount = incrementBlockCount(workspaceDir);
|
|
46
|
-
const phase = getPlanningPhase(workspaceDir);
|
|
47
|
-
const escalation = blockCount >= 3
|
|
48
|
-
? ` CRITICAL: Block #${blockCount}. You have attempted to break out of planning ${blockCount} times. You MUST return to your current step in the Progress Checklist, complete your planning artifact, output the completion message, and STOP. Do NOT attempt any other action.`
|
|
49
|
-
: '';
|
|
50
|
-
process.stderr.write(
|
|
51
|
-
`BLOCKED: EnterPlanMode is not allowed during workflow planning. ` +
|
|
52
|
-
`dev-workflow has its own planning process. ` +
|
|
53
|
-
`REDIRECT: You are in ${phase || 'a planning phase'}. Return to your Progress Checklist. ` +
|
|
54
|
-
`Find the last "✓ Step N complete" you output, then continue with Step N+1. ` +
|
|
55
|
-
`Write your output to .5/features/{name}/ and output the completion message when done.${escalation}`
|
|
56
|
-
);
|
|
57
|
-
process.exit(2);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (toolName === 'Agent' || toolName === 'Task') {
|
|
61
|
-
const agentType = toolInput.subagent_type || '';
|
|
62
|
-
if (agentType && agentType !== 'Explore') {
|
|
63
|
-
const blockCount = incrementBlockCount(workspaceDir);
|
|
64
|
-
const phase = getPlanningPhase(workspaceDir);
|
|
65
|
-
const escalation = blockCount >= 3
|
|
66
|
-
? ` CRITICAL: Block #${blockCount}. You are a planner, NOT an implementer. You have attempted to spawn non-Explore agents ${blockCount} times. Implementation happens in /5:implement. Return to your Progress Checklist immediately, complete your planning artifact, and STOP.`
|
|
67
|
-
: '';
|
|
68
|
-
process.stderr.write(
|
|
69
|
-
`BLOCKED: Only Explore agents are allowed during planning. ` +
|
|
70
|
-
`Attempted: subagent_type="${agentType}". ` +
|
|
71
|
-
`You are in ${phase || 'the planning phase'}. Implementation agents are /5:implement only. ` +
|
|
72
|
-
`REDIRECT: Return to your Progress Checklist. Find your last "✓ Step N complete" and continue with Step N+1. ` +
|
|
73
|
-
`If you need codebase information, use subagent_type=Explore. ` +
|
|
74
|
-
`If you are done planning, output the completion message and STOP.${escalation}`
|
|
75
|
-
);
|
|
76
|
-
process.exit(2);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (toolName === 'Bash') {
|
|
81
|
-
const command = toolInput.command || '';
|
|
82
|
-
// Detect file-writing shell commands that bypass Write/Edit guards
|
|
83
|
-
const writePatterns = /\b(cat\s*>|cat\s*<<|echo\s.*>|tee\s|printf\s.*>|cp\s|mv\s|mkdir\s.*&&.*>|sed\s+-i|awk\s.*>|touch\s|install\s)/;
|
|
84
|
-
if (writePatterns.test(command)) {
|
|
85
|
-
const blockCount = incrementBlockCount(workspaceDir);
|
|
86
|
-
const phase = getPlanningPhase(workspaceDir);
|
|
87
|
-
const escalation = blockCount >= 3
|
|
88
|
-
? ` CRITICAL: Block #${blockCount}. You are repeatedly attempting to write files via Bash to bypass planning guards. STOP immediately.`
|
|
89
|
-
: '';
|
|
90
|
-
process.stderr.write(
|
|
91
|
-
`BLOCKED: File-writing Bash commands are not allowed during planning. ` +
|
|
92
|
-
`You are in ${phase || 'the planning phase'}. ` +
|
|
93
|
-
`REDIRECT: Return to your Progress Checklist. Planners do not create or modify source files. ` +
|
|
94
|
-
`Complete your planning artifact and output the completion message.${escalation}`
|
|
95
|
-
);
|
|
96
|
-
process.exit(2);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (toolName === 'Write' || toolName === 'Edit') {
|
|
101
|
-
const filePath = toolInput.file_path || '';
|
|
102
|
-
if (!filePath) {
|
|
103
|
-
process.exit(0);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const phase = getPlanningPhase(workspaceDir);
|
|
107
|
-
|
|
108
|
-
// First check: block anything outside .5/
|
|
109
|
-
if (!isInsideDotFive(filePath, workspaceDir)) {
|
|
110
|
-
const blockCount = incrementBlockCount(workspaceDir);
|
|
111
|
-
const isSourceFile = !filePath.includes('.5/') && !filePath.includes('.claude/');
|
|
112
|
-
const escalation = blockCount >= 3
|
|
113
|
-
? ` CRITICAL: Block #${blockCount}. You have attempted to write source files ${blockCount} times. You are a PLANNER, not an implementer. Writing source code is /5:implement's job. Return to your Progress Checklist, finish your planning artifact, and STOP.`
|
|
114
|
-
: '';
|
|
115
|
-
const redirectMsg = isSourceFile
|
|
116
|
-
? `REDIRECT: You are in ${phase || 'the planning phase'}. You may ONLY write to .5/features/. ` +
|
|
117
|
-
`Source file creation happens in /5:implement. ` +
|
|
118
|
-
`Return to your Progress Checklist — find your last "✓ Step N complete" and continue with Step N+1.`
|
|
119
|
-
: `REDIRECT: The path "${filePath}" is outside the allowed .5/ directory. ` +
|
|
120
|
-
`Check your file path — you should be writing to .5/features/{name}/.`;
|
|
121
|
-
process.stderr.write(
|
|
122
|
-
`BLOCKED: ${toolName} outside .5/ is not allowed during planning. ` +
|
|
123
|
-
`Attempted: "${filePath}". ${redirectMsg}${escalation}`
|
|
124
|
-
);
|
|
125
|
-
process.exit(2);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Second check: during plan, only allow specific files
|
|
129
|
-
if (normalizePlanningPhase(phase) === 'plan' && !isAllowedPlanFile(filePath, workspaceDir)) {
|
|
130
|
-
const blockCount = incrementBlockCount(workspaceDir);
|
|
131
|
-
const escalation = blockCount >= 3
|
|
132
|
-
? ` CRITICAL: Block #${blockCount}. You are attempting to create implementation artifacts during planning. Planning ONLY produces plan.md and codebase-scan.md. STOP and output your completion message.`
|
|
133
|
-
: '';
|
|
134
|
-
process.stderr.write(
|
|
135
|
-
`BLOCKED: During plan, you may only write to .planning-active, codebase-scan.md, and plan.md. ` +
|
|
136
|
-
`Attempted: "${filePath}". ` +
|
|
137
|
-
`REDIRECT: If you have already written plan.md, output the completion message and STOP. ` +
|
|
138
|
-
`Do NOT create source files or state.json during planning.${escalation}`
|
|
139
|
-
);
|
|
140
|
-
process.exit(2);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
process.exit(0);
|
|
145
|
-
} catch (e) {
|
|
146
|
-
// Silent failure - don't block on parse errors
|
|
147
|
-
process.exit(0);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
function isAllowedPlanFile(filePath, workspaceDir) {
|
|
152
|
-
const resolved = path.resolve(workspaceDir, filePath);
|
|
153
|
-
const dotFiveDir = path.join(workspaceDir, '.5');
|
|
154
|
-
|
|
155
|
-
// Allow .5/.planning-active
|
|
156
|
-
if (resolved === path.join(dotFiveDir, '.planning-active')) return true;
|
|
157
|
-
|
|
158
|
-
// Allow .5/features/{name}/plan.md and .5/features/{name}/codebase-scan.md
|
|
159
|
-
const featuresDir = path.join(dotFiveDir, 'features');
|
|
160
|
-
if (resolved.startsWith(featuresDir + path.sep)) {
|
|
161
|
-
const basename = path.basename(resolved);
|
|
162
|
-
if (basename === 'plan.md' || basename === 'codebase-scan.md') return true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function isInsideDotFive(filePath, workspaceDir) {
|
|
169
|
-
const resolved = path.resolve(workspaceDir, filePath);
|
|
170
|
-
const dotFiveDir = path.join(workspaceDir, '.5');
|
|
171
|
-
return resolved.startsWith(dotFiveDir + path.sep) ||
|
|
172
|
-
resolved === dotFiveDir;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function getTargetFeature(toolName, toolInput, workspaceDir) {
|
|
176
|
-
// Extract the feature name from the tool input context
|
|
177
|
-
const featuresDir = path.join(workspaceDir, '.5', 'features');
|
|
178
|
-
|
|
179
|
-
if (toolName === 'Write' || toolName === 'Edit') {
|
|
180
|
-
// Check if the file path is inside a feature directory
|
|
181
|
-
const filePath = toolInput.file_path || '';
|
|
182
|
-
const resolved = path.resolve(workspaceDir, filePath);
|
|
183
|
-
if (resolved.startsWith(featuresDir + path.sep)) {
|
|
184
|
-
// Extract feature name: .5/features/{feature-name}/...
|
|
185
|
-
const relative = resolved.slice(featuresDir.length + 1);
|
|
186
|
-
const featureName = relative.split(path.sep)[0];
|
|
187
|
-
if (featureName) return featureName;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (toolName === 'Agent' || toolName === 'Task') {
|
|
192
|
-
// Check the task prompt for feature name references
|
|
193
|
-
const prompt = toolInput.prompt || '';
|
|
194
|
-
const desc = toolInput.description || '';
|
|
195
|
-
const combined = prompt + ' ' + desc;
|
|
196
|
-
|
|
197
|
-
// Try to match a feature directory that exists
|
|
198
|
-
try {
|
|
199
|
-
if (fs.existsSync(featuresDir)) {
|
|
200
|
-
const features = fs.readdirSync(featuresDir, { withFileTypes: true });
|
|
201
|
-
for (const entry of features) {
|
|
202
|
-
if (!entry.isDirectory()) continue;
|
|
203
|
-
if (combined.includes(entry.name)) {
|
|
204
|
-
return entry.name;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
} catch (e) {
|
|
209
|
-
// Ignore read errors
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function isPlanningActive(workspaceDir) {
|
|
217
|
-
const markerPath = path.join(workspaceDir, '.5', '.planning-active');
|
|
218
|
-
if (!fs.existsSync(markerPath)) return false;
|
|
219
|
-
try {
|
|
220
|
-
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
221
|
-
if (marker.startedAt) {
|
|
222
|
-
const elapsed = Date.now() - new Date(marker.startedAt).getTime();
|
|
223
|
-
if (elapsed > 4 * 60 * 60 * 1000) {
|
|
224
|
-
try { fs.unlinkSync(markerPath); } catch (e) {}
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return true;
|
|
229
|
-
} catch (e) {
|
|
230
|
-
return true; // Unreadable marker → fail-safe, assume active
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function incrementBlockCount(workspaceDir) {
|
|
235
|
-
const markerPath = path.join(workspaceDir, '.5', '.planning-active');
|
|
236
|
-
try {
|
|
237
|
-
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
238
|
-
marker.blockCount = (marker.blockCount || 0) + 1;
|
|
239
|
-
fs.writeFileSync(markerPath, JSON.stringify(marker, null, 2));
|
|
240
|
-
return marker.blockCount;
|
|
241
|
-
} catch (e) {
|
|
242
|
-
return 1;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function getPlanningPhase(workspaceDir) {
|
|
247
|
-
const markerPath = path.join(workspaceDir, '.5', '.planning-active');
|
|
248
|
-
try {
|
|
249
|
-
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
250
|
-
return marker.phase || null;
|
|
251
|
-
} catch (e) {
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function normalizePlanningPhase(phase) {
|
|
257
|
-
if (!phase) return 'plan';
|
|
258
|
-
if (phase === 'plan-feature' || phase === 'plan-implementation') return 'plan';
|
|
259
|
-
if (phase === 'plan') return 'plan';
|
|
260
|
-
return 'plan';
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function isFeatureInImplementationMode(workspaceDir, featureName) {
|
|
264
|
-
// Check if this specific feature has a state.json (created by /5:implement)
|
|
265
|
-
const stateFile = path.join(
|
|
266
|
-
workspaceDir, '.5', 'features', featureName, 'state.json'
|
|
267
|
-
);
|
|
268
|
-
return fs.existsSync(stateFile);
|
|
269
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"ticket": "{TICKET-ID}",
|
|
3
|
-
"feature": "{feature-name}",
|
|
4
|
-
"status": "in-progress",
|
|
5
|
-
"currentStep": 1,
|
|
6
|
-
"totalSteps": 0,
|
|
7
|
-
"steps": [
|
|
8
|
-
{
|
|
9
|
-
"number": 1,
|
|
10
|
-
"name": "{step-name}",
|
|
11
|
-
"mode": "parallel",
|
|
12
|
-
"model": "haiku",
|
|
13
|
-
"components": []
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
"pendingComponents": [
|
|
17
|
-
{
|
|
18
|
-
"name": "{component-name}",
|
|
19
|
-
"action": "create",
|
|
20
|
-
"step": 1,
|
|
21
|
-
"mode": "parallel",
|
|
22
|
-
"model": "haiku",
|
|
23
|
-
"file": "{target-path}",
|
|
24
|
-
"sourceFile": null,
|
|
25
|
-
"description": "{one-sentence intent}",
|
|
26
|
-
"dependsOn": [],
|
|
27
|
-
"patternRefs": [],
|
|
28
|
-
"verifyCommands": [],
|
|
29
|
-
"notes": []
|
|
30
|
-
}
|
|
31
|
-
],
|
|
32
|
-
"completedComponents": [],
|
|
33
|
-
"recentFailures": [],
|
|
34
|
-
"baseline": {},
|
|
35
|
-
"latestCommandResults": [],
|
|
36
|
-
"verificationStatus": "pending",
|
|
37
|
-
"verificationResults": {},
|
|
38
|
-
"verifiedAt": null,
|
|
39
|
-
"latestCommitResults": [],
|
|
40
|
-
"eventLog": ".5/features/{feature-name}/state-events.jsonl",
|
|
41
|
-
"startedAt": "{ISO-timestamp}",
|
|
42
|
-
"lastUpdated": "{ISO-timestamp}"
|
|
43
|
-
}
|