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.
@@ -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
@@ -36,16 +36,6 @@
36
36
  "timeout": 5
37
37
  }
38
38
  ]
39
- },
40
- {
41
- "matcher": "Task|Agent|Write|Edit|EnterPlanMode|Bash",
42
- "hooks": [
43
- {
44
- "type": "command",
45
- "command": "node .claude/hooks/plan-guard.js",
46
- "timeout": 5
47
- }
48
- ]
49
39
  }
50
40
  ]
51
41
  }
@@ -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 final state for the command to persist to state.json.',
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 state object. The /5:implement command (which has Write) persists
19
- // state.json + state-events.jsonl and runs auto-commit after this returns.
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 resume = a.resume || {}
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 (Array.isArray(resume.steps) && resume.steps.length && Array.isArray(resume.pendingComponents) && resume.pendingComponents.length) {
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 state.json + state-events.jsonl and auto-commits.
314
- // steps + components are returned so a later resume can pass them back (deterministic, no re-orchestration).
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"
@@ -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."
@@ -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
- }