5-phase-workflow 1.9.5 → 2.0.1
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 +81 -410
- package/bin/install.js +385 -70
- package/bin/sync-agents.js +50 -11
- package/docs/findings.md +3 -3
- package/docs/workflow-guide.md +110 -1046
- package/package.json +6 -5
- package/src/agents/step-executor-agent.md +49 -0
- package/src/agents/step-orchestrator-agent.md +111 -0
- package/src/agents/verification-agent.md +78 -0
- package/src/commands/5/address-review-findings.md +69 -403
- package/src/commands/5/apply-review-findings.md +66 -0
- package/src/commands/5/configure.md +110 -76
- package/src/commands/5/discuss-feature.md +47 -57
- package/src/commands/5/eject.md +7 -6
- package/src/commands/5/implement.md +202 -0
- package/src/commands/5/plan.md +164 -0
- package/src/commands/5/reconfigure.md +32 -31
- package/src/commands/5/reply-pr-comments.md +46 -0
- package/src/commands/5/review.md +95 -0
- package/src/commands/5/split.md +190 -0
- package/src/commands/5/synchronize-agents.md +4 -4
- package/src/commands/5/triage-pr-comments.md +70 -0
- package/src/commands/5/update.md +8 -8
- package/src/hooks/check-updates.js +50 -7
- package/src/hooks/plan-guard.js +28 -22
- package/src/hooks/statusline.js +55 -4
- package/src/skills/configure-docs-index/SKILL.md +16 -21
- package/src/skills/configure-skills/SKILL.md +21 -24
- package/src/templates/AGENTS.md +94 -0
- package/src/templates/workflow/FIX-PLAN.md +1 -1
- package/src/templates/workflow/PLAN-COMPACT.md +42 -0
- package/src/templates/workflow/PLAN.md +58 -34
- package/src/templates/workflow/REVIEW-FINDINGS.md +7 -16
- package/src/templates/workflow/REVIEW-SUMMARY.md +5 -0
- package/src/templates/workflow/STATE.json +32 -3
- package/src/agents/component-executor.md +0 -57
- package/src/commands/5/implement-feature.md +0 -381
- package/src/commands/5/plan-feature.md +0 -293
- package/src/commands/5/plan-implementation.md +0 -333
- package/src/commands/5/quick-implement.md +0 -375
- package/src/commands/5/review-code.md +0 -212
- package/src/commands/5/verify-implementation.md +0 -277
- package/src/templates/workflow/FEATURE-SPEC.md +0 -100
- package/src/templates/workflow/VERIFICATION-REPORT.md +0 -103
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 5:split
|
|
3
|
+
description: Splits an existing plan into smaller linked child plans that can be implemented independently.
|
|
4
|
+
allowed-tools: Bash, Read, Write, Glob, Grep, AskUserQuestion, Agent
|
|
5
|
+
user-invocable: true
|
|
6
|
+
argument-hint: [feature-name]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<role>
|
|
10
|
+
You are a Plan Split Facilitator. You split one existing `.5/features/{feature}/plan.md` into multiple smaller linked plans.
|
|
11
|
+
You do NOT implement code. You do NOT modify source files. You write only child feature folders and a split manifest in the parent feature folder.
|
|
12
|
+
</role>
|
|
13
|
+
|
|
14
|
+
# Split Plan
|
|
15
|
+
|
|
16
|
+
Use this command when an existing plan is too large and should become multiple independently implementable plans.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Help the user decide how to split the parent plan.
|
|
21
|
+
- Suggest good split points based on implementation independence.
|
|
22
|
+
- Create child feature folders, each with its own `plan.md` and `codebase-scan.md`.
|
|
23
|
+
- Preserve traceability to the original parent plan and sibling split plans.
|
|
24
|
+
|
|
25
|
+
## Process
|
|
26
|
+
|
|
27
|
+
### Step 1: Locate Parent Plan
|
|
28
|
+
|
|
29
|
+
1. If `{feature-name}` was provided, use `.5/features/{feature-name}/plan.md`.
|
|
30
|
+
2. If only a ticket ID or prefix was provided, glob `.5/features/{prefix}*/plan.md`.
|
|
31
|
+
3. If no feature was provided:
|
|
32
|
+
- Find `.5/features/*/plan.md`.
|
|
33
|
+
- Prefer the most recently modified plan.
|
|
34
|
+
- If there are multiple plausible recent plans, ask the user to choose.
|
|
35
|
+
4. If no parent plan exists, stop and tell the user to run `/5:plan` first.
|
|
36
|
+
|
|
37
|
+
Read:
|
|
38
|
+
|
|
39
|
+
- Parent `.5/features/{feature}/plan.md`
|
|
40
|
+
- Parent `.5/features/{feature}/codebase-scan.md` if it exists
|
|
41
|
+
- `.5/config.json` only if needed for ticket naming conventions
|
|
42
|
+
|
|
43
|
+
Do not read source files unless the parent plan and scan are insufficient to identify safe split boundaries. If extra context is needed, use targeted Glob/Grep or one Explore agent with a read-only prompt.
|
|
44
|
+
|
|
45
|
+
### Step 2: Analyze Split Boundaries
|
|
46
|
+
|
|
47
|
+
Extract from the parent plan:
|
|
48
|
+
|
|
49
|
+
- Overview and desired outcome
|
|
50
|
+
- Scope in/out
|
|
51
|
+
- Acceptance criteria
|
|
52
|
+
- Decisions
|
|
53
|
+
- Existing patterns
|
|
54
|
+
- Module impact
|
|
55
|
+
- Component checklist
|
|
56
|
+
- Technical notes and constraints
|
|
57
|
+
|
|
58
|
+
Suggest 2-5 split options optimized for independently running `/5:implement`:
|
|
59
|
+
|
|
60
|
+
- Prefer components that touch different target files or modules.
|
|
61
|
+
- Keep each child plan with coherent acceptance criteria.
|
|
62
|
+
- Keep dependency order explicit when one child must land before another.
|
|
63
|
+
- Avoid technical-layer splits when they would create child plans that cannot be verified independently.
|
|
64
|
+
- Avoid splitting tightly coupled same-file changes unless the user explicitly wants it.
|
|
65
|
+
|
|
66
|
+
Present the recommendation compactly:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
Recommended split:
|
|
70
|
+
1. {child title} - {scope} - {why this boundary is independent}
|
|
71
|
+
2. {child title} - {scope} - {why this boundary is independent}
|
|
72
|
+
|
|
73
|
+
Dependency order: {none | child A before child B because ...}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then ask the user:
|
|
77
|
+
|
|
78
|
+
- How many child plans should be created?
|
|
79
|
+
- Which suggested boundaries should be used or changed?
|
|
80
|
+
|
|
81
|
+
Use AskUserQuestion for concrete choices. If the user provides textual boundaries, summarize them back before writing.
|
|
82
|
+
|
|
83
|
+
### Step 3: Confirm Each Child Plan
|
|
84
|
+
|
|
85
|
+
For each child plan, ask or confirm:
|
|
86
|
+
|
|
87
|
+
- Child title
|
|
88
|
+
- Child folder slug
|
|
89
|
+
- Scope in
|
|
90
|
+
- Scope out
|
|
91
|
+
- Acceptance criteria
|
|
92
|
+
- Component checklist rows copied or adapted from the parent
|
|
93
|
+
- Dependencies on sibling child plans
|
|
94
|
+
|
|
95
|
+
Folder naming:
|
|
96
|
+
|
|
97
|
+
- Use `{parent-feature}-{nn}-{child-slug}`.
|
|
98
|
+
- `nn` is 2-digit, starting at `01`.
|
|
99
|
+
- Sanitize slugs to lowercase kebab-case using only alphanumeric characters, dash, and underscore.
|
|
100
|
+
- If the folder already exists, ask whether to choose a new slug or stop. Do not overwrite existing child folders without explicit user approval.
|
|
101
|
+
|
|
102
|
+
Before writing files, show the final split summary and ask for confirmation.
|
|
103
|
+
|
|
104
|
+
### Step 4: Write Child Artifacts
|
|
105
|
+
|
|
106
|
+
For each confirmed child folder, create:
|
|
107
|
+
|
|
108
|
+
- `.5/features/{child-feature}/plan.md`
|
|
109
|
+
- `.5/features/{child-feature}/codebase-scan.md`
|
|
110
|
+
|
|
111
|
+
Use `.claude/templates/workflow/PLAN.md` structure for every child plan unless the child clearly has only 1-2 low-risk components; in that case, use `.claude/templates/workflow/PLAN-COMPACT.md`.
|
|
112
|
+
|
|
113
|
+
Each child `plan.md` must include a `## Split Metadata` section after the overview:
|
|
114
|
+
|
|
115
|
+
```markdown
|
|
116
|
+
## Split Metadata
|
|
117
|
+
|
|
118
|
+
- Parent plan: `.5/features/{parent-feature}/plan.md`
|
|
119
|
+
- Split manifest: `.5/features/{parent-feature}/split-manifest-{timestamp}.json`
|
|
120
|
+
- Split index: {N} of {total}
|
|
121
|
+
- Sibling plans:
|
|
122
|
+
- `.5/features/{sibling-feature}/plan.md`
|
|
123
|
+
- Dependency order: {none | sibling names and reason}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Child plan content rules:
|
|
127
|
+
|
|
128
|
+
- Copy only parent context relevant to that child.
|
|
129
|
+
- Keep decisions labeled `[DECIDED]`, `[FLEXIBLE]`, or `[DEFERRED]`.
|
|
130
|
+
- Acceptance criteria must be checkboxes and independently verifiable.
|
|
131
|
+
- Component checklist remains lean: component, action, target path, intent.
|
|
132
|
+
- Do not add step/model/pattern/verify columns.
|
|
133
|
+
- Do not include implementation code or pseudo-code.
|
|
134
|
+
|
|
135
|
+
Child `codebase-scan.md` rules:
|
|
136
|
+
|
|
137
|
+
- If the parent scan exists, copy only relevant patterns, likely target paths, test/build setup, risks, and unknowns for the child.
|
|
138
|
+
- If no parent scan exists, write:
|
|
139
|
+
|
|
140
|
+
```markdown
|
|
141
|
+
# Codebase Scan
|
|
142
|
+
|
|
143
|
+
No parent codebase scan was available when this plan was split.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Step 5: Write Split Manifest
|
|
147
|
+
|
|
148
|
+
Write `.5/features/{parent-feature}/split-manifest-{YYYYMMDD-HHmmss}.json`:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"parent": "{parent-feature}",
|
|
153
|
+
"sourcePlan": ".5/features/{parent-feature}/plan.md",
|
|
154
|
+
"createdAt": "{ISO-timestamp}",
|
|
155
|
+
"strategy": "implementation-independence",
|
|
156
|
+
"children": [
|
|
157
|
+
{
|
|
158
|
+
"index": 1,
|
|
159
|
+
"feature": "{child-feature}",
|
|
160
|
+
"plan": ".5/features/{child-feature}/plan.md",
|
|
161
|
+
"scope": "one-line child scope",
|
|
162
|
+
"dependsOn": []
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"rationale": "one concise paragraph explaining the split boundaries"
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Keep the parent `plan.md` unchanged.
|
|
170
|
+
|
|
171
|
+
### Step 6: Report
|
|
172
|
+
|
|
173
|
+
Output:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
Plan split complete.
|
|
177
|
+
|
|
178
|
+
Parent: .5/features/{parent-feature}/plan.md
|
|
179
|
+
Manifest: .5/features/{parent-feature}/split-manifest-{timestamp}.json
|
|
180
|
+
|
|
181
|
+
Child plans:
|
|
182
|
+
- .5/features/{child-1}/plan.md
|
|
183
|
+
- .5/features/{child-2}/plan.md
|
|
184
|
+
|
|
185
|
+
Implement separately with:
|
|
186
|
+
/5:implement {child-1}
|
|
187
|
+
/5:implement {child-2}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Stop immediately.
|
|
@@ -4,7 +4,6 @@ description: Synchronize user-generated skills, rules, and custom content betwee
|
|
|
4
4
|
allowed-tools: Bash, Read, AskUserQuestion
|
|
5
5
|
user-invocable: true
|
|
6
6
|
model: haiku
|
|
7
|
-
context: fork
|
|
8
7
|
---
|
|
9
8
|
|
|
10
9
|
<role>
|
|
@@ -20,14 +19,15 @@ Synchronizes user-generated content (skills, commands, agents, rules) between th
|
|
|
20
19
|
|
|
21
20
|
The script is `bin/sync-agents.js` in the workflow package. Find it by checking these paths in order:
|
|
22
21
|
|
|
23
|
-
1.
|
|
24
|
-
2. `./
|
|
22
|
+
1. `./.claude/bin/sync-agents.js` (installed Claude Code runtime helper)
|
|
23
|
+
2. `./bin/sync-agents.js` (development checkout / project root)
|
|
24
|
+
3. `./node_modules/foifi/bin/sync-agents.js` (local npm install)
|
|
25
25
|
|
|
26
26
|
Read `.5/version.json` if available — its location confirms the project root.
|
|
27
27
|
|
|
28
28
|
Store the resolved path for the following steps.
|
|
29
29
|
|
|
30
|
-
If the script cannot be found, tell the user: "Sync script not found. Update the workflow first: `npx
|
|
30
|
+
If the script cannot be found, tell the user: "Sync script not found. Update the workflow first: `npx foifi --upgrade`" and **stop**.
|
|
31
31
|
|
|
32
32
|
## Step 2: Dry Run
|
|
33
33
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 5:triage-pr-comments
|
|
3
|
+
description: Fetches, compacts, and categorizes GitHub PR review comments for a feature.
|
|
4
|
+
allowed-tools: Bash, Read, Write, AskUserQuestion, Agent
|
|
5
|
+
user-invocable: false
|
|
6
|
+
model: haiku
|
|
7
|
+
argument-hint: [feature-name]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<role>
|
|
11
|
+
You are a PR Comment Triage Coordinator. You reduce GitHub comment data before model analysis and record user decisions.
|
|
12
|
+
</role>
|
|
13
|
+
|
|
14
|
+
# Triage PR Comments
|
|
15
|
+
|
|
16
|
+
## Process
|
|
17
|
+
|
|
18
|
+
1. Find the PR for the current branch:
|
|
19
|
+
- `gh pr list --head "$(git branch --show-current)" --json number,url,title --limit 1`
|
|
20
|
+
- `gh repo view --json owner,name`
|
|
21
|
+
2. Fetch comments:
|
|
22
|
+
- Inline: `gh api repos/{owner}/{repo}/pulls/{number}/comments --paginate`
|
|
23
|
+
- General: `gh api repos/{owner}/{repo}/issues/{number}/comments --paginate`
|
|
24
|
+
3. Normalize before spawning an agent:
|
|
25
|
+
- Keep only `id`, `path`, `line`, `body`, `user.login`, `created_at`, `updated_at`, `in_reply_to_id`, and resolved/outdated flags.
|
|
26
|
+
- For issue comments, use `path: "PR"` and `line: 0`.
|
|
27
|
+
- Drop bot, empty, minimized, resolved, outdated, and duplicate records.
|
|
28
|
+
- Truncate bodies to the smallest useful actionable excerpt.
|
|
29
|
+
4. Spawn one low-context agent to categorize compact records against local findings.
|
|
30
|
+
5. Ask the user only about actionable/manual comments.
|
|
31
|
+
6. Write decisions to `.5/features/{feature}/pr-comment-decisions.json`.
|
|
32
|
+
|
|
33
|
+
## Categorization Prompt
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
Categorize compact PR comments.
|
|
37
|
+
|
|
38
|
+
Local findings:
|
|
39
|
+
{file:line:description list or "none"}
|
|
40
|
+
|
|
41
|
+
Comments:
|
|
42
|
+
{compact records}
|
|
43
|
+
|
|
44
|
+
Categories:
|
|
45
|
+
- actionable_fix
|
|
46
|
+
- duplicate
|
|
47
|
+
- manual
|
|
48
|
+
- skip
|
|
49
|
+
|
|
50
|
+
Output:
|
|
51
|
+
---PR-COMMENTS---
|
|
52
|
+
{id} | {file}:{line} | {category} | {description} | {duplicate_of or "none"} | {recommendation or "n/a"} | {one-sentence reasoning or "n/a"}
|
|
53
|
+
---END-PR-COMMENTS---
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Output
|
|
57
|
+
|
|
58
|
+
End with:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
---PR-TRIAGE---
|
|
62
|
+
STATUS: success | failed
|
|
63
|
+
ACTIONABLE: {N}
|
|
64
|
+
MANUAL: {N}
|
|
65
|
+
DUPLICATE: {N}
|
|
66
|
+
SKIPPED: {N}
|
|
67
|
+
DECISIONS: .5/features/{feature}/pr-comment-decisions.json
|
|
68
|
+
ERRORS: none | {compact summary}
|
|
69
|
+
---END-PR-TRIAGE---
|
|
70
|
+
```
|
package/src/commands/5/update.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: 5:update
|
|
3
|
-
description: Update
|
|
3
|
+
description: Update dev-workflow to the latest version
|
|
4
4
|
allowed-tools: Bash, Read, AskUserQuestion
|
|
5
5
|
user-invocable: true
|
|
6
6
|
model: haiku
|
|
7
|
-
context:
|
|
7
|
+
context: inherit
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
<role>
|
|
@@ -13,7 +13,7 @@ You do NOT modify workflow files manually. You do NOT touch user project files.
|
|
|
13
13
|
After reporting the result, you are DONE.
|
|
14
14
|
</role>
|
|
15
15
|
|
|
16
|
-
# Update
|
|
16
|
+
# Update dev-workflow
|
|
17
17
|
|
|
18
18
|
## Step 1: Check Current Version
|
|
19
19
|
|
|
@@ -22,13 +22,13 @@ Read `.5/version.json` and note the current `installedVersion`.
|
|
|
22
22
|
## Step 2: Run Upgrade
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npx
|
|
25
|
+
npx foifi@latest --upgrade
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
If this installation is running in Codex (workflow files live in `.codex/` or the workflow command was invoked as a `$5-...` skill), run this instead:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npx
|
|
31
|
+
npx foifi@latest --codex --upgrade
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Step 3: Confirm Upgrade
|
|
@@ -60,9 +60,9 @@ Read `.5/config.json` if it exists and extract `git.commitMessage.pattern` (defa
|
|
|
60
60
|
|
|
61
61
|
Build the commit message by applying the pattern:
|
|
62
62
|
- Replace `{ticket-id}` with an empty string (no ticket for upgrades) and trim any leading/trailing whitespace
|
|
63
|
-
- Replace `{short-description}` with `update
|
|
64
|
-
- If the pattern is the conventional format (`feat({ticket-id}): {short-description}`), use: `chore: update
|
|
65
|
-
- If no config or no pattern, use: `update
|
|
63
|
+
- Replace `{short-description}` with `update dev-workflow to {new-version}`
|
|
64
|
+
- If the pattern is the conventional format (`feat({ticket-id}): {short-description}`), use: `chore: update dev-workflow to {new-version}`
|
|
65
|
+
- If no config or no pattern, use: `update dev-workflow to {new-version}`
|
|
66
66
|
|
|
67
67
|
Stage **only** the workflow-managed files shown in `git status`.
|
|
68
68
|
|
|
@@ -68,7 +68,7 @@ async function getLatestVersion() {
|
|
|
68
68
|
return new Promise((resolve) => {
|
|
69
69
|
const https = require('https');
|
|
70
70
|
const req = https.get(
|
|
71
|
-
'https://registry.npmjs.org/
|
|
71
|
+
'https://registry.npmjs.org/foifi/latest',
|
|
72
72
|
{ timeout: 3000 },
|
|
73
73
|
(res) => {
|
|
74
74
|
if (res.statusCode !== 200) {
|
|
@@ -95,15 +95,58 @@ async function getLatestVersion() {
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// Compare semver versions
|
|
99
|
-
// Uses parseInt to handle pre-release tags (e.g., "2-beta" → 2)
|
|
98
|
+
// Compare semver versions, including pre-release ordering
|
|
100
99
|
function compareVersions(v1, v2) {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
100
|
+
const parsed1 = parseSemver(v1);
|
|
101
|
+
const parsed2 = parseSemver(v2);
|
|
103
102
|
|
|
104
103
|
for (let i = 0; i < 3; i++) {
|
|
105
|
-
if (
|
|
106
|
-
if (
|
|
104
|
+
if (parsed1.core[i] > parsed2.core[i]) return 1;
|
|
105
|
+
if (parsed1.core[i] < parsed2.core[i]) return -1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return comparePrerelease(parsed1.prerelease, parsed2.prerelease);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseSemver(version) {
|
|
112
|
+
const normalized = String(version || '').trim().replace(/^v/, '').split('+')[0];
|
|
113
|
+
const prereleaseIndex = normalized.indexOf('-');
|
|
114
|
+
const corePart = prereleaseIndex === -1 ? normalized : normalized.slice(0, prereleaseIndex);
|
|
115
|
+
const prereleasePart = prereleaseIndex === -1 ? '' : normalized.slice(prereleaseIndex + 1);
|
|
116
|
+
const core = corePart.split('.').slice(0, 3).map(part => parseInt(part, 10) || 0);
|
|
117
|
+
while (core.length < 3) core.push(0);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
core,
|
|
121
|
+
prerelease: prereleasePart ? prereleasePart.split(/[.-]/) : []
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function comparePrerelease(pre1, pre2) {
|
|
126
|
+
if (pre1.length === 0 && pre2.length === 0) return 0;
|
|
127
|
+
if (pre1.length === 0) return 1;
|
|
128
|
+
if (pre2.length === 0) return -1;
|
|
129
|
+
|
|
130
|
+
const length = Math.max(pre1.length, pre2.length);
|
|
131
|
+
for (let i = 0; i < length; i++) {
|
|
132
|
+
const id1 = pre1[i];
|
|
133
|
+
const id2 = pre2[i];
|
|
134
|
+
if (id1 === undefined) return -1;
|
|
135
|
+
if (id2 === undefined) return 1;
|
|
136
|
+
if (id1 === id2) continue;
|
|
137
|
+
|
|
138
|
+
const id1Numeric = /^[0-9]+$/.test(id1);
|
|
139
|
+
const id2Numeric = /^[0-9]+$/.test(id2);
|
|
140
|
+
if (id1Numeric && id2Numeric) {
|
|
141
|
+
const n1 = parseInt(id1, 10);
|
|
142
|
+
const n2 = parseInt(id2, 10);
|
|
143
|
+
if (n1 > n2) return 1;
|
|
144
|
+
if (n1 < n2) return -1;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (id1Numeric) return -1;
|
|
148
|
+
if (id2Numeric) return 1;
|
|
149
|
+
return id1 > id2 ? 1 : -1;
|
|
107
150
|
}
|
|
108
151
|
|
|
109
152
|
return 0;
|
package/src/hooks/plan-guard.js
CHANGED
|
@@ -48,8 +48,8 @@ process.stdin.on('end', () => {
|
|
|
48
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
49
|
: '';
|
|
50
50
|
process.stderr.write(
|
|
51
|
-
`BLOCKED: EnterPlanMode is not allowed during workflow planning
|
|
52
|
-
`
|
|
51
|
+
`BLOCKED: EnterPlanMode is not allowed during workflow planning. ` +
|
|
52
|
+
`dev-workflow has its own planning process. ` +
|
|
53
53
|
`REDIRECT: You are in ${phase || 'a planning phase'}. Return to your Progress Checklist. ` +
|
|
54
54
|
`Find the last "✓ Step N complete" you output, then continue with Step N+1. ` +
|
|
55
55
|
`Write your output to .5/features/{name}/ and output the completion message when done.${escalation}`
|
|
@@ -63,12 +63,12 @@ process.stdin.on('end', () => {
|
|
|
63
63
|
const blockCount = incrementBlockCount(workspaceDir);
|
|
64
64
|
const phase = getPlanningPhase(workspaceDir);
|
|
65
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.
|
|
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
67
|
: '';
|
|
68
68
|
process.stderr.write(
|
|
69
|
-
`BLOCKED: Only Explore agents are allowed during planning
|
|
69
|
+
`BLOCKED: Only Explore agents are allowed during planning. ` +
|
|
70
70
|
`Attempted: subagent_type="${agentType}". ` +
|
|
71
|
-
`You are in ${phase || '
|
|
71
|
+
`You are in ${phase || 'the planning phase'}. Implementation agents are /5:implement only. ` +
|
|
72
72
|
`REDIRECT: Return to your Progress Checklist. Find your last "✓ Step N complete" and continue with Step N+1. ` +
|
|
73
73
|
`If you need codebase information, use subagent_type=Explore. ` +
|
|
74
74
|
`If you are done planning, output the completion message and STOP.${escalation}`
|
|
@@ -88,8 +88,8 @@ process.stdin.on('end', () => {
|
|
|
88
88
|
? ` CRITICAL: Block #${blockCount}. You are repeatedly attempting to write files via Bash to bypass planning guards. STOP immediately.`
|
|
89
89
|
: '';
|
|
90
90
|
process.stderr.write(
|
|
91
|
-
`BLOCKED: File-writing Bash commands are not allowed during planning
|
|
92
|
-
`You are in ${phase || '
|
|
91
|
+
`BLOCKED: File-writing Bash commands are not allowed during planning. ` +
|
|
92
|
+
`You are in ${phase || 'the planning phase'}. ` +
|
|
93
93
|
`REDIRECT: Return to your Progress Checklist. Planners do not create or modify source files. ` +
|
|
94
94
|
`Complete your planning artifact and output the completion message.${escalation}`
|
|
95
95
|
);
|
|
@@ -110,33 +110,32 @@ process.stdin.on('end', () => {
|
|
|
110
110
|
const blockCount = incrementBlockCount(workspaceDir);
|
|
111
111
|
const isSourceFile = !filePath.includes('.5/') && !filePath.includes('.claude/');
|
|
112
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
|
|
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
114
|
: '';
|
|
115
115
|
const redirectMsg = isSourceFile
|
|
116
|
-
? `REDIRECT: You are in ${phase || '
|
|
117
|
-
`Source file creation happens in
|
|
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
118
|
`Return to your Progress Checklist — find your last "✓ Step N complete" and continue with Step N+1.`
|
|
119
119
|
: `REDIRECT: The path "${filePath}" is outside the allowed .5/ directory. ` +
|
|
120
120
|
`Check your file path — you should be writing to .5/features/{name}/.`;
|
|
121
121
|
process.stderr.write(
|
|
122
|
-
`BLOCKED: ${toolName} outside .5/ is not allowed during planning
|
|
122
|
+
`BLOCKED: ${toolName} outside .5/ is not allowed during planning. ` +
|
|
123
123
|
`Attempted: "${filePath}". ${redirectMsg}${escalation}`
|
|
124
124
|
);
|
|
125
125
|
process.exit(2);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
// Second check: during plan
|
|
129
|
-
if (phase === 'plan
|
|
128
|
+
// Second check: during plan, only allow specific files
|
|
129
|
+
if (normalizePlanningPhase(phase) === 'plan' && !isAllowedPlanFile(filePath, workspaceDir)) {
|
|
130
130
|
const blockCount = incrementBlockCount(workspaceDir);
|
|
131
131
|
const escalation = blockCount >= 3
|
|
132
|
-
? ` CRITICAL: Block #${blockCount}. You are attempting to create implementation artifacts during
|
|
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
133
|
: '';
|
|
134
134
|
process.stderr.write(
|
|
135
|
-
`BLOCKED: During plan
|
|
135
|
+
`BLOCKED: During plan, you may only write to .planning-active, codebase-scan.md, and plan.md. ` +
|
|
136
136
|
`Attempted: "${filePath}". ` +
|
|
137
|
-
`
|
|
138
|
-
`
|
|
139
|
-
`Do NOT create implementation plans, file lists, or any other artifacts.${escalation}`
|
|
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}`
|
|
140
139
|
);
|
|
141
140
|
process.exit(2);
|
|
142
141
|
}
|
|
@@ -149,18 +148,18 @@ process.stdin.on('end', () => {
|
|
|
149
148
|
}
|
|
150
149
|
});
|
|
151
150
|
|
|
152
|
-
function
|
|
151
|
+
function isAllowedPlanFile(filePath, workspaceDir) {
|
|
153
152
|
const resolved = path.resolve(workspaceDir, filePath);
|
|
154
153
|
const dotFiveDir = path.join(workspaceDir, '.5');
|
|
155
154
|
|
|
156
155
|
// Allow .5/.planning-active
|
|
157
156
|
if (resolved === path.join(dotFiveDir, '.planning-active')) return true;
|
|
158
157
|
|
|
159
|
-
// Allow .5/features/{name}/
|
|
158
|
+
// Allow .5/features/{name}/plan.md and .5/features/{name}/codebase-scan.md
|
|
160
159
|
const featuresDir = path.join(dotFiveDir, 'features');
|
|
161
160
|
if (resolved.startsWith(featuresDir + path.sep)) {
|
|
162
161
|
const basename = path.basename(resolved);
|
|
163
|
-
if (basename === '
|
|
162
|
+
if (basename === 'plan.md' || basename === 'codebase-scan.md') return true;
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
return false;
|
|
@@ -254,8 +253,15 @@ function getPlanningPhase(workspaceDir) {
|
|
|
254
253
|
}
|
|
255
254
|
}
|
|
256
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
|
+
|
|
257
263
|
function isFeatureInImplementationMode(workspaceDir, featureName) {
|
|
258
|
-
// Check if this specific feature has a state.json (created
|
|
264
|
+
// Check if this specific feature has a state.json (created by /5:implement)
|
|
259
265
|
const stateFile = path.join(
|
|
260
266
|
workspaceDir, '.5', 'features', featureName, 'state.json'
|
|
261
267
|
);
|
package/src/hooks/statusline.js
CHANGED
|
@@ -113,6 +113,11 @@ process.stdin.on('end', () => {
|
|
|
113
113
|
if (fs.existsSync(flagFile)) {
|
|
114
114
|
parts.push(`\x1b[35m↻ /5:reconfigure\x1b[0m`);
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
const migrationFlag = path.join(dir, '.5', '.migration-v2');
|
|
118
|
+
if (fs.existsSync(migrationFlag)) {
|
|
119
|
+
parts.push(`\x1b[31m⚠ v1→v2: /5:reconfigure\x1b[0m`);
|
|
120
|
+
}
|
|
116
121
|
} catch (e) {}
|
|
117
122
|
|
|
118
123
|
process.stdout.write(parts.join(' | '));
|
|
@@ -124,11 +129,57 @@ process.stdin.on('end', () => {
|
|
|
124
129
|
|
|
125
130
|
// Compare semver versions: returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
126
131
|
function compareVersions(v1, v2) {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
132
|
+
const parsed1 = parseSemver(v1);
|
|
133
|
+
const parsed2 = parseSemver(v2);
|
|
134
|
+
|
|
129
135
|
for (let i = 0; i < 3; i++) {
|
|
130
|
-
if (
|
|
131
|
-
if (
|
|
136
|
+
if (parsed1.core[i] > parsed2.core[i]) return 1;
|
|
137
|
+
if (parsed1.core[i] < parsed2.core[i]) return -1;
|
|
132
138
|
}
|
|
139
|
+
|
|
140
|
+
return comparePrerelease(parsed1.prerelease, parsed2.prerelease);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseSemver(version) {
|
|
144
|
+
const normalized = String(version || '').trim().replace(/^v/, '').split('+')[0];
|
|
145
|
+
const prereleaseIndex = normalized.indexOf('-');
|
|
146
|
+
const corePart = prereleaseIndex === -1 ? normalized : normalized.slice(0, prereleaseIndex);
|
|
147
|
+
const prereleasePart = prereleaseIndex === -1 ? '' : normalized.slice(prereleaseIndex + 1);
|
|
148
|
+
const core = corePart.split('.').slice(0, 3).map(part => parseInt(part, 10) || 0);
|
|
149
|
+
while (core.length < 3) core.push(0);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
core,
|
|
153
|
+
prerelease: prereleasePart ? prereleasePart.split(/[.-]/) : []
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function comparePrerelease(pre1, pre2) {
|
|
158
|
+
if (pre1.length === 0 && pre2.length === 0) return 0;
|
|
159
|
+
if (pre1.length === 0) return 1;
|
|
160
|
+
if (pre2.length === 0) return -1;
|
|
161
|
+
|
|
162
|
+
const length = Math.max(pre1.length, pre2.length);
|
|
163
|
+
for (let i = 0; i < length; i++) {
|
|
164
|
+
const id1 = pre1[i];
|
|
165
|
+
const id2 = pre2[i];
|
|
166
|
+
if (id1 === undefined) return -1;
|
|
167
|
+
if (id2 === undefined) return 1;
|
|
168
|
+
if (id1 === id2) continue;
|
|
169
|
+
|
|
170
|
+
const id1Numeric = /^[0-9]+$/.test(id1);
|
|
171
|
+
const id2Numeric = /^[0-9]+$/.test(id2);
|
|
172
|
+
if (id1Numeric && id2Numeric) {
|
|
173
|
+
const n1 = parseInt(id1, 10);
|
|
174
|
+
const n2 = parseInt(id2, 10);
|
|
175
|
+
if (n1 > n2) return 1;
|
|
176
|
+
if (n1 < n2) return -1;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (id1Numeric) return -1;
|
|
180
|
+
if (id2Numeric) return 1;
|
|
181
|
+
return id1 > id2 ? 1 : -1;
|
|
182
|
+
}
|
|
183
|
+
|
|
133
184
|
return 0;
|
|
134
185
|
}
|