5-phase-workflow 1.4.1 → 1.4.3
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/bin/install.js +2 -1
- package/package.json +1 -1
- package/src/commands/5/plan-feature.md +84 -286
- package/src/commands/5/plan-implementation.md +87 -213
- package/src/commands/5/review-code.md +134 -494
- package/src/commands/5/update.md +17 -0
- package/src/hooks/check-updates.js +8 -11
- package/src/hooks/plan-guard.js +103 -0
- package/src/hooks/statusline.js +28 -3
- package/src/settings.json +12 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 5:update
|
|
3
|
+
description: Update the 5-Phase Workflow to the latest version
|
|
4
|
+
allowed-tools: Bash
|
|
5
|
+
context: inherit
|
|
6
|
+
user-invocable: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Update 5-Phase Workflow
|
|
10
|
+
|
|
11
|
+
Run the upgrade command to update to the latest version:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx 5-phase-workflow --upgrade
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
After the upgrade completes, confirm the new version was installed by checking `.claude/.5/version.json`.
|
|
@@ -53,24 +53,21 @@ async function checkForUpdates(workspaceDir) {
|
|
|
53
53
|
|
|
54
54
|
// Update last check time
|
|
55
55
|
versionData.updateCheckLastRun = new Date().toISOString();
|
|
56
|
-
fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
|
|
57
56
|
|
|
58
57
|
// Compare versions
|
|
59
58
|
const installed = versionData.installedVersion;
|
|
60
59
|
const latestVersion = await getLatestVersion();
|
|
61
60
|
|
|
62
|
-
if (
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (compareVersions(installed, latestVersion) < 0) {
|
|
69
|
-
// Show update notification
|
|
70
|
-
console.log(`\n\x1b[34mℹ\x1b[0m Update available: ${installed} → ${latestVersion}`);
|
|
71
|
-
console.log(` Run: \x1b[1mnpx 5-phase-workflow --upgrade\x1b[0m\n`);
|
|
61
|
+
if (latestVersion && compareVersions(installed, latestVersion) < 0) {
|
|
62
|
+
// Update available — persist for statusline to display
|
|
63
|
+
versionData.latestAvailableVersion = latestVersion;
|
|
64
|
+
} else {
|
|
65
|
+
// No update (or network failure) — clear any stale value
|
|
66
|
+
versionData.latestAvailableVersion = null;
|
|
72
67
|
}
|
|
73
68
|
|
|
69
|
+
// Single consolidated write
|
|
70
|
+
fs.writeFileSync(versionFile, JSON.stringify(versionData, null, 2));
|
|
74
71
|
process.exit(0);
|
|
75
72
|
}
|
|
76
73
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Plan Guard - PreToolUse Hook
|
|
4
|
+
// Prevents LLM breakout from planning phases by blocking:
|
|
5
|
+
// - Task agents other than Explore (when not in implementation mode)
|
|
6
|
+
// - Write operations outside .5/ (when not in implementation mode)
|
|
7
|
+
//
|
|
8
|
+
// Planning mode is detected by absence of any state.json in .5/features/.
|
|
9
|
+
// Once state.json exists (created in Phase 3), the feature has passed planning
|
|
10
|
+
// and all tools are allowed (implementation, verification, review phases).
|
|
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 Task and Write tools
|
|
24
|
+
if (toolName !== 'Task' && toolName !== 'Write') {
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const workspaceDir = data.cwd || data.workspace?.current_dir || process.cwd();
|
|
29
|
+
const toolInput = data.tool_input || {};
|
|
30
|
+
|
|
31
|
+
// Check if any feature is in implementation mode
|
|
32
|
+
if (isImplementationMode(workspaceDir)) {
|
|
33
|
+
process.exit(0); // All tools allowed during implementation
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Planning mode enforcement
|
|
37
|
+
if (toolName === 'Task') {
|
|
38
|
+
const agentType = toolInput.subagent_type || '';
|
|
39
|
+
if (agentType && agentType !== 'Explore') {
|
|
40
|
+
process.stderr.write(
|
|
41
|
+
`BLOCKED: Only Explore agents are allowed during planning phases. ` +
|
|
42
|
+
`Attempted: subagent_type="${agentType}". ` +
|
|
43
|
+
`To use other agent types, start implementation with /5:implement-feature.`
|
|
44
|
+
);
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (toolName === 'Write') {
|
|
50
|
+
const filePath = toolInput.file_path || '';
|
|
51
|
+
if (filePath && !isInsideDotFive(filePath, workspaceDir)) {
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
`BLOCKED: Writing outside .5/ is not allowed during planning phases. ` +
|
|
54
|
+
`Attempted: "${filePath}". ` +
|
|
55
|
+
`Planning commands may only write to .5/features/. ` +
|
|
56
|
+
`To write source files, start implementation with /5:implement-feature.`
|
|
57
|
+
);
|
|
58
|
+
process.exit(2);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.exit(0);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// Silent failure - don't block on parse errors
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function isInsideDotFive(filePath, workspaceDir) {
|
|
70
|
+
const resolved = path.resolve(workspaceDir, filePath);
|
|
71
|
+
const dotFiveDir = path.join(workspaceDir, '.5');
|
|
72
|
+
const claudeDotFiveDir = path.join(workspaceDir, '.claude', '.5');
|
|
73
|
+
return resolved.startsWith(dotFiveDir + path.sep) ||
|
|
74
|
+
resolved.startsWith(claudeDotFiveDir + path.sep) ||
|
|
75
|
+
resolved === dotFiveDir ||
|
|
76
|
+
resolved === claudeDotFiveDir;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isImplementationMode(workspaceDir) {
|
|
80
|
+
// If any state.json exists, the feature has passed planning phases.
|
|
81
|
+
// state.json is created in Phase 3 (implement-feature) and persists
|
|
82
|
+
// through Phase 4 (verify) and Phase 5 (review) with status "completed".
|
|
83
|
+
const featuresDir = path.join(workspaceDir, '.claude', '.5', 'features');
|
|
84
|
+
|
|
85
|
+
if (!fs.existsSync(featuresDir)) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const features = fs.readdirSync(featuresDir, { withFileTypes: true });
|
|
91
|
+
for (const entry of features) {
|
|
92
|
+
if (!entry.isDirectory()) continue;
|
|
93
|
+
const stateFile = path.join(featuresDir, entry.name, 'state.json');
|
|
94
|
+
if (fs.existsSync(stateFile)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// Can't read features dir - assume planning mode (safe default)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return false;
|
|
103
|
+
}
|
package/src/hooks/statusline.js
CHANGED
|
@@ -43,11 +43,36 @@ process.stdin.on('end', () => {
|
|
|
43
43
|
// Shorten directory path for display
|
|
44
44
|
const shortDir = dir.replace(os.homedir(), '~');
|
|
45
45
|
|
|
46
|
-
//
|
|
47
|
-
|
|
46
|
+
// Check for available update
|
|
47
|
+
let updateIndicator = '';
|
|
48
|
+
try {
|
|
49
|
+
const versionFile = path.join(dir, '.claude', '.5', 'version.json');
|
|
50
|
+
const versionData = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
|
|
51
|
+
const latest = versionData.latestAvailableVersion;
|
|
52
|
+
const installed = versionData.installedVersion;
|
|
53
|
+
if (latest && installed && compareVersions(installed, latest) < 0) {
|
|
54
|
+
updateIndicator = ` | \x1b[33m↑${latest} → /5:update\x1b[0m`;
|
|
55
|
+
}
|
|
56
|
+
} catch (e) {
|
|
57
|
+
// No version file or parse error — no indicator
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Build and output statusline: model | directory | context | update
|
|
61
|
+
const statusline = `\x1b[36m${model}\x1b[0m | \x1b[90m${shortDir}\x1b[0m${ctx}${updateIndicator}`;
|
|
48
62
|
process.stdout.write(statusline);
|
|
49
63
|
|
|
50
64
|
} catch (e) {
|
|
51
65
|
// Silent fail - don't break statusline on parse errors
|
|
52
66
|
}
|
|
53
|
-
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Compare semver versions: returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
70
|
+
function compareVersions(v1, v2) {
|
|
71
|
+
const parts1 = v1.split('.').map(Number);
|
|
72
|
+
const parts2 = v2.split('.').map(Number);
|
|
73
|
+
for (let i = 0; i < 3; i++) {
|
|
74
|
+
if (parts1[i] > parts2[i]) return 1;
|
|
75
|
+
if (parts1[i] < parts2[i]) return -1;
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
78
|
+
}
|