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.
@@ -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 (!latestVersion || installed === latestVersion) {
63
- // No update available
64
- process.exit(0);
65
- }
66
-
67
- // Check if update is available (installed < latest)
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
+ }
@@ -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
- // Build and output statusline: model | directory | context
47
- const statusline = `\x1b[36m${model}\x1b[0m | \x1b[90m${shortDir}\x1b[0m${ctx}`;
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
+ }
package/src/settings.json CHANGED
@@ -15,6 +15,18 @@
15
15
  }
16
16
  ]
17
17
  }
18
+ ],
19
+ "PreToolUse": [
20
+ {
21
+ "matcher": "",
22
+ "hooks": [
23
+ {
24
+ "type": "command",
25
+ "command": "node .claude/hooks/plan-guard.js",
26
+ "timeout": 5
27
+ }
28
+ ]
29
+ }
18
30
  ]
19
31
  }
20
32
  }