5-phase-workflow 2.0.0 → 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
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
# foifi
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**foifi** is an opinionated AI development workflow layer that sits on top of Claude Code and Codex. It handles project setup, structured feature implementation, and code review — so you spend less time managing the AI and more time shipping.
|
|
4
|
+
|
|
5
|
+
### What it does
|
|
6
|
+
|
|
7
|
+
**Project setup** — The `/5:configure` command detects your stack, generates a `CLAUDE.md` / `AGENTS.md` tailored to your project, writes a `.5/index/` knowledge base, and installs project-specific skills and rules. This gives every AI session the right context from the start rather than letting the model guess.
|
|
8
|
+
|
|
9
|
+
**Status line** — foifi installs an informative Claude Code status line that surfaces the active feature, current workflow phase, and relevant state directly in the terminal footer. No more digging through files to remember where you left off.
|
|
10
|
+
|
|
11
|
+
**Structured implementation workflow** — Instead of asking Claude or Codex to "just implement this," foifi enforces a three-phase loop:
|
|
12
|
+
1. **Plan** (`/5:plan`) — writes a single human-reviewed `plan.md` with scope, acceptance criteria, component checklist, and decisions.
|
|
13
|
+
2. **Implement** (`/5:implement`) — an orchestrator agent turns the plan into a typed execution graph (`state.json`), then delegates each component to a focused executor agent. A verification agent checks completeness, correctness, and test coverage at the end of every run.
|
|
14
|
+
3. **Review** (`/5:review`) — triages changed files, produces structured findings, and feeds them into `/5:address-review-findings` for interactive fix decisions and PR replies.
|
|
15
|
+
|
|
16
|
+
This separation keeps planning readable, implementation mechanical, and review structured.
|
|
17
|
+
|
|
18
|
+
**Code review and findings** — `/5:review` triages changed files and produces structured `review-findings-*.md`. `/5:address-review-findings` presents each finding interactively, records `fix`/`wont_fix`/`wait` decisions, applies approved local fixes, handles PR comment replies, and keeps a decision log — all without losing context between sessions.
|
|
19
|
+
|
|
20
|
+
**Plan management helpers** — `/5:discuss-feature` refines an existing plan in conversation. `/5:split` breaks a large plan into smaller linked child plans. `/5:unlock` clears a stale planning lock. `/5:reconfigure` refreshes docs and skills when the project evolves.
|
|
21
|
+
|
|
22
|
+
**Codex support** — Every command has a `$5-*` Codex equivalent. Codex runs are token-budgeted: simple steps use a lighter model and low reasoning, complex or security-sensitive steps escalate automatically.
|
|
23
|
+
|
|
24
|
+
### The name
|
|
25
|
+
|
|
26
|
+
"foifi" is Swiss German for *five*. The name comes from the project's original 5-phase workflow. That workflow has since been streamlined into the current 3-phase plan → implement → review loop, but the name stuck — and all commands still carry the `/5:` prefix.
|
|
4
27
|
|
|
5
28
|
## Install
|
|
6
29
|
|
package/bin/install.js
CHANGED
|
@@ -569,6 +569,13 @@ During the planning phase ($5-plan):
|
|
|
569
569
|
- Do NOT write to any file outside \`.5/\`
|
|
570
570
|
- Do NOT write source code — only the unified plan and scan cache
|
|
571
571
|
- Do NOT spawn implementation agents — only Explore/research agents
|
|
572
|
+
|
|
573
|
+
## Update & Migration Notices (replaces statusline hooks)
|
|
574
|
+
At the very start of this skill, before doing anything else, read these two files if they exist:
|
|
575
|
+
- \`.5/.update-cache.json\` — if \`latestAvailableVersion\` is set, compare it to \`packageVersion\` in \`.5/version.json\`. If a newer version is available, tell the user: "foifi update available: <version> — run \`npx foifi --codex --upgrade\` to update."
|
|
576
|
+
- \`.5/.migration-v2\` — if this file exists, tell the user: "You upgraded from foifi v1 to v2. Run \`$5-reconfigure\` to update your project configuration."
|
|
577
|
+
|
|
578
|
+
Show each applicable notice once at the top of your response, then continue with the skill normally. Do not abort the skill because of a notice.
|
|
572
579
|
</codex_skill_adapter>`;
|
|
573
580
|
}
|
|
574
581
|
|
|
@@ -828,17 +835,20 @@ function cleanupOrphanedFiles(targetPath, dataDir) {
|
|
|
828
835
|
}
|
|
829
836
|
}
|
|
830
837
|
|
|
831
|
-
// Ensure .5/.gitignore exists and contains
|
|
838
|
+
// Ensure .5/.gitignore exists and contains transient runtime files
|
|
832
839
|
function ensureDotFiveGitignore(dataDir) {
|
|
833
840
|
const gitignorePath = path.join(dataDir, '.gitignore');
|
|
834
|
-
const
|
|
841
|
+
const entries = ['.update-cache.json', '.migration-v*', '.reconfig-reminder'];
|
|
835
842
|
if (fs.existsSync(gitignorePath)) {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
843
|
+
let content = fs.readFileSync(gitignorePath, 'utf8');
|
|
844
|
+
for (const entry of entries) {
|
|
845
|
+
if (!content.includes(entry)) {
|
|
846
|
+
content += '\n' + entry;
|
|
847
|
+
}
|
|
839
848
|
}
|
|
849
|
+
fs.writeFileSync(gitignorePath, content.trimEnd() + '\n');
|
|
840
850
|
} else {
|
|
841
|
-
fs.writeFileSync(gitignorePath,
|
|
851
|
+
fs.writeFileSync(gitignorePath, entries.join('\n') + '\n');
|
|
842
852
|
}
|
|
843
853
|
}
|
|
844
854
|
|
|
@@ -1070,6 +1080,52 @@ function performFreshInstall(targetPath, sourcePath, isGlobal) {
|
|
|
1070
1080
|
showCommandsHelp(isGlobal);
|
|
1071
1081
|
}
|
|
1072
1082
|
|
|
1083
|
+
// Rename create-* generated skill directories to {pattern} (drop the create- prefix).
|
|
1084
|
+
// Earlier versions named them create-dto, create-service, etc. The new convention
|
|
1085
|
+
// uses the bare pattern name so skills work for both create and update.
|
|
1086
|
+
function renameCreateSkills(targetPath) {
|
|
1087
|
+
const skillsDir = path.join(targetPath, 'skills');
|
|
1088
|
+
if (!fs.existsSync(skillsDir)) return;
|
|
1089
|
+
try {
|
|
1090
|
+
for (const entry of fs.readdirSync(skillsDir)) {
|
|
1091
|
+
if (!entry.startsWith('create-')) continue;
|
|
1092
|
+
const oldPath = path.join(skillsDir, entry);
|
|
1093
|
+
const newName = entry.slice('create-'.length);
|
|
1094
|
+
const newPath = path.join(skillsDir, newName);
|
|
1095
|
+
if (fs.existsSync(newPath)) continue; // don't clobber a user-created skill
|
|
1096
|
+
const skillFile = path.join(oldPath, 'SKILL.md');
|
|
1097
|
+
if (!fs.existsSync(skillFile)) continue; // skip non-workflow dirs
|
|
1098
|
+
fs.renameSync(oldPath, newPath);
|
|
1099
|
+
// Update name: and description: in the frontmatter to match
|
|
1100
|
+
const content = fs.readFileSync(path.join(newPath, 'SKILL.md'), 'utf8');
|
|
1101
|
+
const updated = content
|
|
1102
|
+
.replace(/^name: create-/m, 'name: ')
|
|
1103
|
+
.replace(/^description: Creates a /m, 'description: Creates or updates a ');
|
|
1104
|
+
fs.writeFileSync(path.join(newPath, 'SKILL.md'), updated);
|
|
1105
|
+
log.info(`Renamed skill: ${entry} → ${newName}`);
|
|
1106
|
+
}
|
|
1107
|
+
} catch (e) {}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// Remove `context: fork` from any skill SKILL.md files under .claude/skills/.
|
|
1111
|
+
// These were generated by earlier versions of configure-skills and cause loops.
|
|
1112
|
+
function removeContextForkFromSkills(targetPath) {
|
|
1113
|
+
const skillsDir = path.join(targetPath, 'skills');
|
|
1114
|
+
if (!fs.existsSync(skillsDir)) return;
|
|
1115
|
+
try {
|
|
1116
|
+
for (const entry of fs.readdirSync(skillsDir)) {
|
|
1117
|
+
const skillFile = path.join(skillsDir, entry, 'SKILL.md');
|
|
1118
|
+
if (!fs.existsSync(skillFile)) continue;
|
|
1119
|
+
const original = fs.readFileSync(skillFile, 'utf8');
|
|
1120
|
+
const updated = original.replace(/^context: fork\s*\n/m, '');
|
|
1121
|
+
if (updated !== original) {
|
|
1122
|
+
fs.writeFileSync(skillFile, updated);
|
|
1123
|
+
log.info(`Removed context: fork from skills/${entry}/SKILL.md`);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
} catch (e) {}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1073
1129
|
// Perform update (preserves user-created files, updates .5/ data directory)
|
|
1074
1130
|
function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
1075
1131
|
log.header(`Updating from ${versionInfo.installed || 'legacy'} to ${versionInfo.available}`);
|
|
@@ -1085,6 +1141,22 @@ function performUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
1085
1141
|
// Merge settings (deep merge preserves user customizations)
|
|
1086
1142
|
mergeSettings(targetPath, sourcePath);
|
|
1087
1143
|
|
|
1144
|
+
// Rename create-* skill dirs to bare pattern names (create-dto → dto)
|
|
1145
|
+
renameCreateSkills(targetPath);
|
|
1146
|
+
|
|
1147
|
+
// Strip `context: fork` from generated skills — caused infinite loops
|
|
1148
|
+
removeContextForkFromSkills(targetPath);
|
|
1149
|
+
|
|
1150
|
+
// Flag v1 → v2 major upgrade so statusline can prompt for reconfigure
|
|
1151
|
+
const prevMajor = versionInfo.installed ? parseInt(versionInfo.installed.split('.')[0], 10) : 0;
|
|
1152
|
+
const newMajor = versionInfo.available ? parseInt(versionInfo.available.split('.')[0], 10) : 0;
|
|
1153
|
+
if (!isNaN(prevMajor) && !isNaN(newMajor) && prevMajor < newMajor) {
|
|
1154
|
+
try {
|
|
1155
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
1156
|
+
fs.writeFileSync(path.join(dataDir, '.migration-v' + newMajor), '1');
|
|
1157
|
+
} catch (e) {}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1088
1160
|
// Update version.json (per-runtime, preserving other runtime's state)
|
|
1089
1161
|
writeVersionJson(dataDir, isGlobal, versionInfo.available);
|
|
1090
1162
|
ensureDotFiveGitignore(dataDir);
|
|
@@ -1325,6 +1397,19 @@ function performCodexUpdate(targetPath, sourcePath, isGlobal, versionInfo) {
|
|
|
1325
1397
|
const dataDir = getDataPath(isGlobal);
|
|
1326
1398
|
cleanupOrphanedFiles(targetPath, dataDir);
|
|
1327
1399
|
|
|
1400
|
+
// Rename create-* skill dirs to bare pattern names (create-dto → dto)
|
|
1401
|
+
renameCreateSkills(targetPath);
|
|
1402
|
+
|
|
1403
|
+
// Flag v1 → v2 major upgrade so skills can prompt for reconfigure
|
|
1404
|
+
const prevMajor = versionInfo.installed ? parseInt(versionInfo.installed.split('.')[0], 10) : 0;
|
|
1405
|
+
const newMajor = versionInfo.available ? parseInt(versionInfo.available.split('.')[0], 10) : 0;
|
|
1406
|
+
if (!isNaN(prevMajor) && !isNaN(newMajor) && prevMajor < newMajor) {
|
|
1407
|
+
try {
|
|
1408
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
1409
|
+
fs.writeFileSync(path.join(dataDir, '.migration-v' + newMajor), '1');
|
|
1410
|
+
} catch (e) {}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1328
1413
|
// Update version.json (per-runtime, preserving other runtime's state)
|
|
1329
1414
|
writeVersionJson(dataDir, isGlobal, versionInfo.available);
|
|
1330
1415
|
ensureDotFiveGitignore(dataDir);
|
package/package.json
CHANGED
|
@@ -177,9 +177,9 @@ After the skill completes, update `.5/version.json`:
|
|
|
177
177
|
|
|
178
178
|
### Step 8: Clean Up
|
|
179
179
|
|
|
180
|
-
Remove the `.5/.reconfig-reminder` flag
|
|
180
|
+
Remove the `.5/.reconfig-reminder` and `.5/.migration-v2` flag files if they exist:
|
|
181
181
|
```bash
|
|
182
|
-
rm -f .5/.reconfig-reminder
|
|
182
|
+
rm -f .5/.reconfig-reminder .5/.migration-v2
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
### Step 9: Report
|
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(' | '));
|
|
@@ -3,7 +3,6 @@ name: configure-docs-index
|
|
|
3
3
|
description: Analyzes the codebase, creates project documentation, generates a rebuildable codebase index, and updates AGENTS.md. Used during /5:implement CONFIGURE.
|
|
4
4
|
allowed-tools: Read, Write, Bash, Glob, Grep
|
|
5
5
|
model: sonnet
|
|
6
|
-
context: fork
|
|
7
6
|
user-invocable: false
|
|
8
7
|
---
|
|
9
8
|
|
|
@@ -3,7 +3,6 @@ name: configure-skills
|
|
|
3
3
|
description: Generates project-specific create-*/run-* skills and scoped rules from the current codebase. Used during /5:implement CONFIGURE.
|
|
4
4
|
allowed-tools: Read, Write, Bash, Glob, Grep, create-skill, scaffold-skill
|
|
5
5
|
model: sonnet
|
|
6
|
-
context: fork
|
|
7
6
|
user-invocable: false
|
|
8
7
|
---
|
|
9
8
|
|
|
@@ -77,24 +76,23 @@ For EACH pattern selected by the user in the unified plan:
|
|
|
77
76
|
|
|
78
77
|
### A2. Skill Template Structure
|
|
79
78
|
|
|
80
|
-
For each skill, create `.claude/skills/
|
|
79
|
+
For each skill, create `.claude/skills/{pattern}/SKILL.md`:
|
|
81
80
|
|
|
82
81
|
```yaml
|
|
83
82
|
---
|
|
84
|
-
name:
|
|
85
|
-
description: Creates a {Pattern} following project conventions at {location}.
|
|
83
|
+
name: {pattern}
|
|
84
|
+
description: Creates or updates a {Pattern} following project conventions at {location}.
|
|
86
85
|
allowed-tools: Read, Write, Glob, Grep
|
|
87
86
|
model: haiku
|
|
88
|
-
context: fork
|
|
89
87
|
user-invocable: true
|
|
90
88
|
---
|
|
91
89
|
```
|
|
92
90
|
|
|
93
91
|
```markdown
|
|
94
|
-
#
|
|
92
|
+
# {Pattern}
|
|
95
93
|
|
|
96
|
-
## What This Skill
|
|
97
|
-
|
|
94
|
+
## What This Skill Does
|
|
95
|
+
Creates or updates a {pattern} following this project's conventions.
|
|
98
96
|
|
|
99
97
|
## Detected Conventions
|
|
100
98
|
- **Location:** {detected-location}
|
|
@@ -103,14 +101,14 @@ A {pattern} following this project's conventions.
|
|
|
103
101
|
- **Imports:** {detected-import-pattern}
|
|
104
102
|
|
|
105
103
|
## Template
|
|
106
|
-
Based on {example-file},
|
|
104
|
+
Based on {example-file}, {patterns} should follow:
|
|
107
105
|
|
|
108
106
|
\`\`\`{language}
|
|
109
107
|
{template-derived-from-analysis}
|
|
110
108
|
\`\`\`
|
|
111
109
|
|
|
112
110
|
## Checklist
|
|
113
|
-
- [ ] File
|
|
111
|
+
- [ ] File at correct location
|
|
114
112
|
- [ ] Naming convention followed
|
|
115
113
|
- [ ] Required imports added
|
|
116
114
|
- [ ] {pattern-specific-items}
|
|
@@ -118,7 +116,7 @@ Based on {example-file}, new {patterns} should follow:
|
|
|
118
116
|
|
|
119
117
|
### A3. Pattern to Skill Name Mapping
|
|
120
118
|
|
|
121
|
-
**Rule:** Skill name is `
|
|
119
|
+
**Rule:** Skill name is `{pattern}` (e.g., `controller` → `controller`, `component` → `component`, `dto` → `dto`). For compound patterns, use the short form: `model/entity` → `model`, `api-route` → `api-route`.
|
|
122
120
|
|
|
123
121
|
---
|
|
124
122
|
|
|
@@ -153,7 +151,6 @@ name: run-{command}
|
|
|
153
151
|
description: Runs {command} for this project using {source}.
|
|
154
152
|
allowed-tools: Bash
|
|
155
153
|
model: haiku
|
|
156
|
-
context: fork
|
|
157
154
|
user-invocable: true
|
|
158
155
|
---
|
|
159
156
|
```
|