5-phase-workflow 2.1.0 → 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 +58 -14
- package/docs/workflow-guide.md +25 -26
- package/package.json +1 -1
- package/src/agents/step-orchestrator-agent.md +10 -43
- 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 +44 -49
- 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 +14 -14
- 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
|
@@ -85,10 +85,6 @@ For each command found: record exact syntax, note variants (e.g., `test:unit`, `
|
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
87
|
"tools": {
|
|
88
|
-
"coderabbit": {
|
|
89
|
-
"available": false,
|
|
90
|
-
"authenticated": false
|
|
91
|
-
},
|
|
92
88
|
"ide": {
|
|
93
89
|
"available": false,
|
|
94
90
|
"type": null
|
|
@@ -100,7 +96,6 @@ For each command found: record exact syntax, note variants (e.g., `test:unit`, `
|
|
|
100
96
|
"available": false
|
|
101
97
|
}
|
|
102
98
|
},
|
|
103
|
-
"reviewTool": "native",
|
|
104
99
|
"git": {
|
|
105
100
|
"autoCommit": false,
|
|
106
101
|
"commitMessage": {
|
package/src/settings.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
name: '5-implement',
|
|
3
3
|
description: 'Execute a unified plan: derive steps, run executors in parallel waves, verify. Claude Code only; Codex uses the prose loop.',
|
|
4
|
-
whenToUse: 'Invoked by the /5:implement command when the Workflow tool is available. Reads args from the command, returns
|
|
4
|
+
whenToUse: 'Invoked by the /5:implement command when the Workflow tool is available. Reads args from the command, returns the run result for the command to persist to the slim state.json.',
|
|
5
5
|
phases: [
|
|
6
6
|
{ title: 'Orchestrate', detail: 'derive steps + components (agent for non-trivial plans, inline for compact)' },
|
|
7
7
|
{ title: 'Execute', detail: 'one executor agent per component; parallel components fire together per step' },
|
|
@@ -15,8 +15,12 @@ export const meta = {
|
|
|
15
15
|
// the canonical human-readable contract; keep these prompts in sync with them.
|
|
16
16
|
//
|
|
17
17
|
// This script has NO filesystem access. It orchestrates agents and RETURNS the
|
|
18
|
-
// final
|
|
19
|
-
// state.json
|
|
18
|
+
// final result. The /5:implement command (which has Write) persists the slim
|
|
19
|
+
// state.json and runs auto-commit after this returns.
|
|
20
|
+
//
|
|
21
|
+
// The execution graph (steps/components) is derived fresh on every run and is
|
|
22
|
+
// NOT persisted. Resume works because component names are copied verbatim from
|
|
23
|
+
// the plan's Component Checklist, so re-derivation is stable by name.
|
|
20
24
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
25
|
|
|
22
26
|
const RESULT_SCHEMA = {
|
|
@@ -128,6 +132,7 @@ Codebase scan: ${a.paths.scan} (read only if present)
|
|
|
128
132
|
Config: ${a.paths.config} (read only if present)
|
|
129
133
|
|
|
130
134
|
Turn the lean component checklist in the plan into steps + components:
|
|
135
|
+
- COPY EVERY COMPONENT NAME VERBATIM from the plan's Component Checklist. Resume matches components by name across runs, so a renamed or reformatted name makes completed work run twice.
|
|
131
136
|
- Group independent components into one step with mode "parallel"; use "sequential" only when components touch the same file, one imports another, or there is an explicit dependency.
|
|
132
137
|
- Prefer fewer steps. Tests run after the components they validate.
|
|
133
138
|
- model "haiku" by default (mechanical, UI, tests, docs, config, single-file). model "sonnet" only for complex logic, cross-module, security/auth, migrations, or public API.
|
|
@@ -260,18 +265,12 @@ async function runStep(step, components, a) {
|
|
|
260
265
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
261
266
|
|
|
262
267
|
const a = args || {}
|
|
263
|
-
const
|
|
264
|
-
const resumeDone = new Set(resume.completedComponents || [])
|
|
268
|
+
const resumeDone = new Set(a.completedComponents || [])
|
|
265
269
|
|
|
266
270
|
phase('Orchestrate')
|
|
271
|
+
if (resumeDone.size) log(`resume: ${resumeDone.size} component(s) already complete, matched by name`)
|
|
267
272
|
let plan
|
|
268
|
-
if (
|
|
269
|
-
// Resume: reuse the steps/components derived in the original run (stored in state.json).
|
|
270
|
-
// Re-deriving via the orchestrator agent is non-deterministic and could rename components,
|
|
271
|
-
// breaking name-based resume matching — so never re-orchestrate on resume.
|
|
272
|
-
log(`resume: reusing ${resume.steps.length} prior step(s); ${resumeDone.size} component(s) already complete`)
|
|
273
|
-
plan = { steps: resume.steps, pendingComponents: resume.pendingComponents }
|
|
274
|
-
} else if (a.isCompact && Array.isArray(a.components) && a.components.length) {
|
|
273
|
+
if (a.isCompact && Array.isArray(a.components) && a.components.length) {
|
|
275
274
|
log(`compact plan: ${a.components.length} component(s), no orchestrator agent`)
|
|
276
275
|
plan = compactSteps(a.components)
|
|
277
276
|
} else {
|
|
@@ -310,8 +309,9 @@ if (allPassed && !usedSonnet) {
|
|
|
310
309
|
const newlyCompleted = allItems.filter(i => i.result && i.result.status === 'success').map(i => i.component.name)
|
|
311
310
|
const completedComponents = [...new Set([...resumeDone, ...newlyCompleted])]
|
|
312
311
|
|
|
313
|
-
// Returned to the /5:implement command, which persists
|
|
314
|
-
// steps + components are returned
|
|
312
|
+
// Returned to the /5:implement command, which persists the slim state.json and auto-commits.
|
|
313
|
+
// steps + components are returned for reporting and per-step staging only — they are NOT persisted;
|
|
314
|
+
// the next run re-derives them, and resume matches on completedComponents by name.
|
|
315
315
|
return {
|
|
316
316
|
feature: a.feature,
|
|
317
317
|
steps,
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: 5:discuss-feature
|
|
3
|
-
description: Discusses and refines an existing unified plan through iterative Q&A. Use after /5:plan when requirements need clarification or changes. Updates the unified plan based on discussion.
|
|
4
|
-
allowed-tools: Read, Write, Glob, Grep, Agent, AskUserQuestion
|
|
5
|
-
user-invocable: true
|
|
6
|
-
context: inherit
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
<role>
|
|
10
|
-
You are a Feature Discussion Facilitator. You refine existing unified plans through Q&A.
|
|
11
|
-
You do NOT create new unified plans, create implementation plans, write code, or start implementation.
|
|
12
|
-
You read an existing plan.md, discuss it with the user, and update only the changed sections.
|
|
13
|
-
After updating the plan and informing the user, you are DONE.
|
|
14
|
-
</role>
|
|
15
|
-
|
|
16
|
-
# Discuss Feature Plan (Optional Iteration)
|
|
17
|
-
|
|
18
|
-
## Overview
|
|
19
|
-
|
|
20
|
-
This helper refines the unified `plan.md` created by `/5:plan`.
|
|
21
|
-
|
|
22
|
-
Use it when requirements, scope, acceptance criteria, decisions, or the component checklist need adjustment before `/5:implement`.
|
|
23
|
-
|
|
24
|
-
## ⚠️ CRITICAL SCOPE CONSTRAINT
|
|
25
|
-
|
|
26
|
-
**THIS COMMAND ONLY UPDATES THE UNIFIED PLAN. IT DOES NOT IMPLEMENT.**
|
|
27
|
-
|
|
28
|
-
Your job in this command:
|
|
29
|
-
✅ Read existing unified plan
|
|
30
|
-
✅ Ask what user wants to discuss
|
|
31
|
-
✅ Explore codebase if needed for context
|
|
32
|
-
✅ Ask clarifying questions
|
|
33
|
-
✅ Update unified plan
|
|
34
|
-
✅ Tell user to run /5:implement
|
|
35
|
-
|
|
36
|
-
Your job is NOT:
|
|
37
|
-
❌ Create new unified plans (use /5:plan)
|
|
38
|
-
❌ Start implementation
|
|
39
|
-
❌ Write any code
|
|
40
|
-
❌ Rewrite entire unified plan (only update changed sections)
|
|
41
|
-
|
|
42
|
-
**After updating the unified plan and informing the user, YOUR JOB IS COMPLETE. EXIT IMMEDIATELY.**
|
|
43
|
-
|
|
44
|
-
## Use Cases
|
|
45
|
-
|
|
46
|
-
Use this skill when:
|
|
47
|
-
- Requirements need clarification after initial planning
|
|
48
|
-
- User wants to add/modify/remove requirements
|
|
49
|
-
- Scope needs adjustment
|
|
50
|
-
- Technical constraints discovered during exploration
|
|
51
|
-
- Alternative approaches need evaluation
|
|
52
|
-
- Feature complexity needs reduction
|
|
53
|
-
- Acceptance criteria need refinement
|
|
54
|
-
|
|
55
|
-
## Prerequisites
|
|
56
|
-
|
|
57
|
-
Before invoking this skill, ensure:
|
|
58
|
-
1. Unified plan exists at `.5/features/{feature-name}/plan.md`
|
|
59
|
-
2. You have the feature name or ticket ID ready (e.g., "PROJ-1234-add-feature")
|
|
60
|
-
|
|
61
|
-
## Discussion Process
|
|
62
|
-
|
|
63
|
-
### Step 1: Extract Feature Name
|
|
64
|
-
|
|
65
|
-
If user provides only ticket ID (e.g., "PROJ-1234"), find the plan file:
|
|
66
|
-
- Use Glob: `.5/features/{TICKET-ID}-*/plan.md` (pattern from config)
|
|
67
|
-
- Match the ticket ID
|
|
68
|
-
- If multiple matches, ask user to specify
|
|
69
|
-
- If no match found, inform user and suggest running `/5:plan` first
|
|
70
|
-
|
|
71
|
-
If user provides full feature name (e.g., "PROJ-1234-add-feature"), use directly.
|
|
72
|
-
|
|
73
|
-
### Step 2: Read Plan
|
|
74
|
-
|
|
75
|
-
Read the unified plan from `.5/features/{feature-name}/plan.md`.
|
|
76
|
-
|
|
77
|
-
Extract current state:
|
|
78
|
-
- Ticket ID
|
|
79
|
-
- Overview
|
|
80
|
-
- What Changes
|
|
81
|
-
- Existing Patterns to Follow
|
|
82
|
-
- Constraints
|
|
83
|
-
- Scope
|
|
84
|
-
- Acceptance Criteria
|
|
85
|
-
- Decisions ([DECIDED]/[FLEXIBLE]/[DEFERRED])
|
|
86
|
-
- Module Impact
|
|
87
|
-
- Component Checklist
|
|
88
|
-
- Technical Notes
|
|
89
|
-
- Alternatives Considered
|
|
90
|
-
|
|
91
|
-
### Step 3: Initial Discussion Prompt
|
|
92
|
-
|
|
93
|
-
**FIRST ACTION:** Ask the user what they want to discuss using AskUserQuestion.
|
|
94
|
-
|
|
95
|
-
Prompt to use:
|
|
96
|
-
|
|
97
|
-
"What would you like to discuss or change about this feature?"
|
|
98
|
-
|
|
99
|
-
Provide context options for common scenarios:
|
|
100
|
-
- "Clarify existing requirements"
|
|
101
|
-
- "Add new requirements"
|
|
102
|
-
- "Remove or simplify requirements"
|
|
103
|
-
- "Change scope or approach"
|
|
104
|
-
- "Discuss technical constraints"
|
|
105
|
-
- "Review acceptance criteria"
|
|
106
|
-
- "Other (specify)"
|
|
107
|
-
|
|
108
|
-
Use `multiSelect: false` to get single focus area initially.
|
|
109
|
-
|
|
110
|
-
### Step 4: Contextual Exploration (Optional)
|
|
111
|
-
|
|
112
|
-
Based on the user's focus area, explore the codebase if needed:
|
|
113
|
-
|
|
114
|
-
**Index shortcut:** Before spawning Explore agents or running Glob/Grep, invoke the `use-index` skill to quickly orient in the codebase using the generated index.
|
|
115
|
-
|
|
116
|
-
**For technical constraint discussions:**
|
|
117
|
-
- Search for similar implementations
|
|
118
|
-
- Check existing patterns
|
|
119
|
-
- Identify integration points
|
|
120
|
-
|
|
121
|
-
**For scope changes:**
|
|
122
|
-
- Verify affected modules
|
|
123
|
-
- Check dependencies
|
|
124
|
-
- Identify ripple effects
|
|
125
|
-
|
|
126
|
-
Use Agent tool with subagent_type=Explore for complex exploration.
|
|
127
|
-
|
|
128
|
-
### Step 5: Interactive Q&A
|
|
129
|
-
|
|
130
|
-
Based on the discussion topic, ask targeted follow-up questions using AskUserQuestion.
|
|
131
|
-
|
|
132
|
-
Ask targeted follow-up questions informed by the unified plan and any codebase exploration. Focus on: requirements clarity, scope boundaries, edge cases, technical constraints, and acceptance criteria. Challenge assumptions constructively — suggest simpler alternatives when appropriate.
|
|
133
|
-
|
|
134
|
-
### Step 6: Iterative Refinement
|
|
135
|
-
|
|
136
|
-
After each round of Q&A:
|
|
137
|
-
1. Summarize what you've learned
|
|
138
|
-
2. Ask: "Would you like to discuss anything else about this feature?"
|
|
139
|
-
3. Provide options:
|
|
140
|
-
- "Yes, discuss another aspect"
|
|
141
|
-
- "Yes, continue this topic"
|
|
142
|
-
- "No, update the plan"
|
|
143
|
-
|
|
144
|
-
Allow multiple rounds of discussion until user is satisfied.
|
|
145
|
-
|
|
146
|
-
### Step 7: Update Plan
|
|
147
|
-
|
|
148
|
-
When user indicates they're done discussing, update `.5/features/{feature-name}/plan.md`:
|
|
149
|
-
|
|
150
|
-
**Update these sections based on discussion:**
|
|
151
|
-
|
|
152
|
-
1. **Overview** - If core understanding changed
|
|
153
|
-
2. **What Changes** - Add/modify/remove behavior
|
|
154
|
-
3. **Constraints** - Add new constraints discovered
|
|
155
|
-
4. **Scope** - If boundaries changed
|
|
156
|
-
5. **Acceptance Criteria** - Add/refine verification criteria
|
|
157
|
-
6. **Decisions** - Append new decisions tagged [DECIDED], [FLEXIBLE], or [DEFERRED]
|
|
158
|
-
7. **Component Checklist** - Adjust components only when scope or requirements changed
|
|
159
|
-
8. **Technical Notes / Alternatives Considered** - Document useful context from discussion
|
|
160
|
-
|
|
161
|
-
**Preserve existing content** - Only update sections that changed during discussion.
|
|
162
|
-
|
|
163
|
-
**Track changes** - Add a "Discussion History" section if it doesn't exist:
|
|
164
|
-
|
|
165
|
-
```markdown
|
|
166
|
-
## Discussion History
|
|
167
|
-
|
|
168
|
-
### Session 1: {Date} - {Topic}
|
|
169
|
-
**Changes made:**
|
|
170
|
-
- Added requirement: {X}
|
|
171
|
-
- Clarified: {Y}
|
|
172
|
-
- Removed: {Z}
|
|
173
|
-
|
|
174
|
-
**Rationale:** {Why these changes}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Step 8: Inform Developer
|
|
178
|
-
|
|
179
|
-
After updating the plan, tell the developer:
|
|
180
|
-
|
|
181
|
-
1. "Plan updated at `.5/features/{feature-name}/plan.md`"
|
|
182
|
-
2. Summarize key changes:
|
|
183
|
-
- "Added: {X}"
|
|
184
|
-
- "Modified: {Y}"
|
|
185
|
-
- "Removed: {Z}"
|
|
186
|
-
3. Ask: "Would you like to:"
|
|
187
|
-
- "Discuss more (run /5:discuss-feature again)"
|
|
188
|
-
- "Proceed to implementation (run `/clear` followed by `/5:implement {feature-name}`)"
|
|
189
|
-
- "Review the updated plan first"
|
package/src/commands/5/unlock.md
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: 5:unlock
|
|
3
|
-
description: Remove the planning guard lock to allow edits outside the workflow
|
|
4
|
-
allowed-tools: Bash
|
|
5
|
-
user-invocable: true
|
|
6
|
-
context: inherit
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Unlock Planning Guard
|
|
10
|
-
|
|
11
|
-
Remove the `.planning-active` marker file that restricts Write/Edit operations during planning phases.
|
|
12
|
-
|
|
13
|
-
## Process
|
|
14
|
-
|
|
15
|
-
Run this bash command to check and remove the marker in one step:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
if [ -f .5/.planning-active ]; then rm .5/.planning-active && echo "REMOVED"; else echo "ABSENT"; fi
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Based on the output, confirm to the user:
|
|
22
|
-
- If output contains "REMOVED": "Planning guard removed. You can now edit files freely."
|
|
23
|
-
- If output contains "ABSENT": "No planning guard was active. You're already free to edit files."
|
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
|
-
}
|