5-phase-workflow 1.4.3 → 1.5.0

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.
@@ -23,7 +23,7 @@ process.stdin.on('end', () => {
23
23
  });
24
24
 
25
25
  async function checkForUpdates(workspaceDir) {
26
- const versionFile = path.join(workspaceDir, '.claude', '.5', 'version.json');
26
+ const versionFile = path.join(workspaceDir, '.5', 'version.json');
27
27
 
28
28
  // Check if version.json exists
29
29
  if (!fs.existsSync(versionFile)) {
@@ -104,9 +104,10 @@ async function getLatestVersion() {
104
104
  }
105
105
 
106
106
  // Compare semver versions
107
+ // Uses parseInt to handle pre-release tags (e.g., "2-beta" → 2)
107
108
  function compareVersions(v1, v2) {
108
- const parts1 = v1.split('.').map(Number);
109
- const parts2 = v2.split('.').map(Number);
109
+ const parts1 = v1.split('.').map(p => parseInt(p, 10) || 0);
110
+ const parts2 = v2.split('.').map(p => parseInt(p, 10) || 0);
110
111
 
111
112
  for (let i = 0; i < 3; i++) {
112
113
  if (parts1[i] > parts2[i]) return 1;
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ let input = '';
6
+ process.stdin.setEncoding('utf8');
7
+ process.stdin.on('data', chunk => input += chunk);
8
+ process.stdin.on('end', () => {
9
+ try {
10
+ const data = JSON.parse(input);
11
+ const cwd = data.cwd || process.cwd();
12
+ const configFile = path.join(cwd, '.5', 'config.json');
13
+
14
+ if (fs.existsSync(configFile)) {
15
+ process.exit(0);
16
+ }
17
+
18
+ // No config — block with helpful message
19
+ process.stderr.write(
20
+ 'Configuration not found. Please run /5:configure first to set up your project.\n\n' +
21
+ 'The configure command will:\n' +
22
+ ' - Detect your project type and build commands\n' +
23
+ ' - Set up ticket tracking conventions\n' +
24
+ ' - Write project configuration'
25
+ );
26
+ process.exit(2);
27
+ } catch (e) {
28
+ process.exit(0); // Silent failure — don't block on parse errors
29
+ }
30
+ });
@@ -3,11 +3,11 @@
3
3
  // Plan Guard - PreToolUse Hook
4
4
  // Prevents LLM breakout from planning phases by blocking:
5
5
  // - Task agents other than Explore (when not in implementation mode)
6
- // - Write operations outside .5/ (when not in implementation mode)
6
+ // - Write/Edit operations outside .5/ (when not in implementation mode)
7
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).
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
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
@@ -20,17 +20,24 @@ process.stdin.on('end', () => {
20
20
  const data = JSON.parse(input);
21
21
  const toolName = data.tool_name || '';
22
22
 
23
- // Short-circuit: only check Task and Write tools
24
- if (toolName !== 'Task' && toolName !== 'Write') {
23
+ // Short-circuit: only check Task, Write, and Edit tools
24
+ if (toolName !== 'Task' && toolName !== 'Write' && toolName !== 'Edit') {
25
25
  process.exit(0);
26
26
  }
27
27
 
28
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
+
29
35
  const toolInput = data.tool_input || {};
30
36
 
31
- // Check if any feature is in implementation mode
32
- if (isImplementationMode(workspaceDir)) {
33
- process.exit(0); // All tools allowed during implementation
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
34
41
  }
35
42
 
36
43
  // Planning mode enforcement
@@ -40,20 +47,22 @@ process.stdin.on('end', () => {
40
47
  process.stderr.write(
41
48
  `BLOCKED: Only Explore agents are allowed during planning phases. ` +
42
49
  `Attempted: subagent_type="${agentType}". ` +
43
- `To use other agent types, start implementation with /5:implement-feature.`
50
+ `To use other agent types, start implementation with /5:implement-feature. ` +
51
+ `If you're not in a planning phase, run /5:unlock to clear the planning lock.`
44
52
  );
45
53
  process.exit(2);
46
54
  }
47
55
  }
48
56
 
49
- if (toolName === 'Write') {
57
+ if (toolName === 'Write' || toolName === 'Edit') {
50
58
  const filePath = toolInput.file_path || '';
51
59
  if (filePath && !isInsideDotFive(filePath, workspaceDir)) {
52
60
  process.stderr.write(
53
- `BLOCKED: Writing outside .5/ is not allowed during planning phases. ` +
61
+ `BLOCKED: ${toolName} outside .5/ is not allowed during planning phases. ` +
54
62
  `Attempted: "${filePath}". ` +
55
63
  `Planning commands may only write to .5/features/. ` +
56
- `To write source files, start implementation with /5:implement-feature.`
64
+ `To modify source files, start implementation with /5:implement-feature. ` +
65
+ `If you're not in a planning phase, run /5:unlock to clear the planning lock.`
57
66
  );
58
67
  process.exit(2);
59
68
  }
@@ -69,35 +78,73 @@ process.stdin.on('end', () => {
69
78
  function isInsideDotFive(filePath, workspaceDir) {
70
79
  const resolved = path.resolve(workspaceDir, filePath);
71
80
  const dotFiveDir = path.join(workspaceDir, '.5');
72
- const claudeDotFiveDir = path.join(workspaceDir, '.claude', '.5');
73
81
  return resolved.startsWith(dotFiveDir + path.sep) ||
74
- resolved.startsWith(claudeDotFiveDir + path.sep) ||
75
- resolved === dotFiveDir ||
76
- resolved === claudeDotFiveDir;
82
+ resolved === dotFiveDir;
77
83
  }
78
84
 
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');
85
+ function getTargetFeature(toolName, toolInput, workspaceDir) {
86
+ // Extract the feature name from the tool input context
87
+ const featuresDir = path.join(workspaceDir, '.5', 'features');
84
88
 
85
- if (!fs.existsSync(featuresDir)) {
86
- return false;
89
+ if (toolName === 'Write' || toolName === 'Edit') {
90
+ // Check if the file path is inside a feature directory
91
+ const filePath = toolInput.file_path || '';
92
+ const resolved = path.resolve(workspaceDir, filePath);
93
+ if (resolved.startsWith(featuresDir + path.sep)) {
94
+ // Extract feature name: .5/features/{feature-name}/...
95
+ const relative = resolved.slice(featuresDir.length + 1);
96
+ const featureName = relative.split(path.sep)[0];
97
+ if (featureName) return featureName;
98
+ }
87
99
  }
88
100
 
101
+ if (toolName === 'Task') {
102
+ // Check the task prompt for feature name references
103
+ const prompt = toolInput.prompt || '';
104
+ const desc = toolInput.description || '';
105
+ const combined = prompt + ' ' + desc;
106
+
107
+ // Try to match a feature directory that exists
108
+ try {
109
+ if (fs.existsSync(featuresDir)) {
110
+ const features = fs.readdirSync(featuresDir, { withFileTypes: true });
111
+ for (const entry of features) {
112
+ if (!entry.isDirectory()) continue;
113
+ if (combined.includes(entry.name)) {
114
+ return entry.name;
115
+ }
116
+ }
117
+ }
118
+ } catch (e) {
119
+ // Ignore read errors
120
+ }
121
+ }
122
+
123
+ return null;
124
+ }
125
+
126
+ function isPlanningActive(workspaceDir) {
127
+ const markerPath = path.join(workspaceDir, '.5', '.planning-active');
128
+ if (!fs.existsSync(markerPath)) return false;
89
129
  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;
130
+ const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
131
+ if (marker.startedAt) {
132
+ const elapsed = Date.now() - new Date(marker.startedAt).getTime();
133
+ if (elapsed > 4 * 60 * 60 * 1000) {
134
+ try { fs.unlinkSync(markerPath); } catch (e) {}
135
+ return false;
96
136
  }
97
137
  }
138
+ return true;
98
139
  } catch (e) {
99
- // Can't read features dir - assume planning mode (safe default)
140
+ return true; // Unreadable marker fail-safe, assume active
100
141
  }
142
+ }
101
143
 
102
- return false;
144
+ function isFeatureInImplementationMode(workspaceDir, featureName) {
145
+ // Check if this specific feature has a state.json (created in Phase 3)
146
+ const stateFile = path.join(
147
+ workspaceDir, '.5', 'features', featureName, 'state.json'
148
+ );
149
+ return fs.existsSync(stateFile);
103
150
  }
@@ -46,7 +46,7 @@ process.stdin.on('end', () => {
46
46
  // Check for available update
47
47
  let updateIndicator = '';
48
48
  try {
49
- const versionFile = path.join(dir, '.claude', '.5', 'version.json');
49
+ const versionFile = path.join(dir, '.5', 'version.json');
50
50
  const versionData = JSON.parse(fs.readFileSync(versionFile, 'utf8'));
51
51
  const latest = versionData.latestAvailableVersion;
52
52
  const installed = versionData.installedVersion;
@@ -67,9 +67,10 @@ process.stdin.on('end', () => {
67
67
  });
68
68
 
69
69
  // Compare semver versions: returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2
70
+ // Uses parseInt to handle pre-release tags (e.g., "2-beta" → 2)
70
71
  function compareVersions(v1, v2) {
71
- const parts1 = v1.split('.').map(Number);
72
- const parts2 = v2.split('.').map(Number);
72
+ const parts1 = v1.split('.').map(p => parseInt(p, 10) || 0);
73
+ const parts2 = v2.split('.').map(p => parseInt(p, 10) || 0);
73
74
  for (let i = 0; i < 3; i++) {
74
75
  if (parts1[i] > parts2[i]) return 1;
75
76
  if (parts1[i] < parts2[i]) return -1;
package/src/settings.json CHANGED
@@ -18,7 +18,17 @@
18
18
  ],
19
19
  "PreToolUse": [
20
20
  {
21
- "matcher": "",
21
+ "matcher": "Task",
22
+ "hooks": [
23
+ {
24
+ "type": "command",
25
+ "command": "node .claude/hooks/config-guard.js",
26
+ "timeout": 5
27
+ }
28
+ ]
29
+ },
30
+ {
31
+ "matcher": "Task|Write|Edit",
22
32
  "hooks": [
23
33
  {
24
34
  "type": "command",
@@ -17,7 +17,7 @@ This skill executes build tasks with auto-detection of the build system and suff
17
17
 
18
18
  The skill automatically detects the build system using:
19
19
 
20
- 1. **Config file** (`.claude/.5/config.json`) - if `build.command` is specified
20
+ 1. **Config file** (`.5/config.json`) - if `build.command` is specified
21
21
  2. **Auto-detection** - by examining project files:
22
22
  - `package.json` → npm/yarn/pnpm
23
23
  - `build.gradle` or `build.gradle.kts` → Gradle
@@ -46,7 +46,7 @@ When invoked, the skill expects:
46
46
 
47
47
  ### 1. Load Configuration
48
48
 
49
- Read `.claude/.5/config.json` if it exists:
49
+ Read `.5/config.json` if it exists:
50
50
 
51
51
  ```json
52
52
  {
@@ -62,46 +62,7 @@ If commands are specified, use them. Otherwise, auto-detect.
62
62
 
63
63
  ### 2. Detect Build System
64
64
 
65
- If no config, examine project files to detect build system:
66
-
67
- ```bash
68
- # Check for package.json
69
- if [ -f "package.json" ]; then
70
- # Check for lock files to determine package manager
71
- if [ -f "pnpm-lock.yaml" ]; then
72
- BUILD_TOOL="pnpm"
73
- elif [ -f "yarn.lock" ]; then
74
- BUILD_TOOL="yarn"
75
- else
76
- BUILD_TOOL="npm"
77
- fi
78
- fi
79
-
80
- # Check for Gradle
81
- if [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
82
- BUILD_TOOL="gradle"
83
- fi
84
-
85
- # Check for Maven
86
- if [ -f "pom.xml" ]; then
87
- BUILD_TOOL="mvn"
88
- fi
89
-
90
- # Check for Cargo
91
- if [ -f "Cargo.toml" ]; then
92
- BUILD_TOOL="cargo"
93
- fi
94
-
95
- # Check for Go
96
- if [ -f "go.mod" ]; then
97
- BUILD_TOOL="go"
98
- fi
99
-
100
- # Check for Make
101
- if [ -f "Makefile" ]; then
102
- BUILD_TOOL="make"
103
- fi
104
- ```
65
+ If no config, detect by checking project files: `package.json` + lock files (npm/yarn/pnpm), `build.gradle` (gradle), `pom.xml` (mvn), `Cargo.toml` (cargo), `go.mod` (go), `Makefile` (make).
105
66
 
106
67
  ### 3. Determine Build Command
107
68
 
@@ -133,45 +94,7 @@ Execute the command and capture output.
133
94
 
134
95
  ### 5. Parse Build Output
135
96
 
136
- Analyze output to identify:
137
-
138
- #### Success Indicators
139
-
140
- Tool-specific success patterns:
141
- - npm/yarn/pnpm: No error messages, process exits with 0
142
- - Gradle: `BUILD SUCCESSFUL`
143
- - Maven: `BUILD SUCCESS`
144
- - Cargo: `Finished` or `Compiling`
145
- - Go: No error output
146
- - Make: No error messages
147
-
148
- #### Error Types
149
-
150
- **Compilation Errors**:
151
- ```
152
- /path/to/file.ext:42: error: ...
153
- ```
154
- Extract: file path, line number, error message
155
-
156
- **Dependency Issues**:
157
- ```
158
- Could not resolve dependencies
159
- Module not found
160
- ```
161
- Suggest: `npm install`, `./gradlew --refresh-dependencies`, etc.
162
-
163
- **Out of Memory**:
164
- ```
165
- JavaScript heap out of memory
166
- Java heap space
167
- ```
168
- Suggest: Increase memory allocation
169
-
170
- **Tool Not Found**:
171
- ```
172
- command not found: npm
173
- ```
174
- Suggest: Install the build tool
97
+ Determine success/failure from tool-specific patterns (exit code, `BUILD SUCCESSFUL`, `BUILD SUCCESS`, `Finished`, etc.). For failures, extract file paths, line numbers, and error messages. Identify error type (compilation, dependency, memory, tool not found) and suggest appropriate fix.
175
98
 
176
99
  ### 6. Format Output
177
100
 
@@ -200,28 +123,6 @@ SUGGESTIONS:
200
123
  - {actionable suggestion based on error type}
201
124
  ```
202
125
 
203
- ## Common Build Scenarios
204
-
205
- ### First-Time Build
206
-
207
- May fail with dependency issues. Suggestions:
208
- - npm: `npm install`
209
- - gradle: Remove `--offline` flag temporarily
210
- - cargo: `cargo fetch`
211
-
212
- ### Incremental Build Issues
213
-
214
- Stale cache or artifacts. Suggestions:
215
- - Try `clean` target
216
- - Clear cache manually
217
-
218
- ### Memory Issues
219
-
220
- Build runs out of memory. Suggestions:
221
- - npm: `export NODE_OPTIONS="--max-old-space-size=4096"`
222
- - gradle: Add `org.gradle.jvmargs=-Xmx4g` to `gradle.properties`
223
- - maven: `export MAVEN_OPTS="-Xmx4g"`
224
-
225
126
  ## Error Handling
226
127
 
227
128
  - If build tool cannot be detected, return error with list of checked locations
@@ -237,38 +138,11 @@ Build runs out of memory. Suggestions:
237
138
  - DO NOT assume a specific build system - always detect or use config
238
139
  - DO NOT use overly short timeouts (builds can be slow)
239
140
 
240
- ## Examples
241
-
242
- ### Example 1: Auto-detect npm and build
141
+ ## Example
243
142
 
244
143
  ```
245
144
  User: /build-project
246
-
247
- Skill: [Detects package.json, uses npm]
248
- Skill: [Runs: npm run build]
249
- Skill: [Reports success with duration]
250
- ```
251
-
252
- ### Example 2: Gradle with module
253
-
254
- ```
255
- User: /build-project module=user-service
256
-
257
- Skill: [Detects build.gradle]
258
- Skill: [Runs: ./gradlew :user-service:build -x test --offline]
259
- Skill: [Reports success]
260
- ```
261
-
262
- ### Example 3: Build failure
263
-
264
- ```
265
- User: /build-project
266
-
267
- Skill: [Detects Cargo.toml]
268
- Skill: [Runs: cargo build]
269
- Skill: [Detects compilation error]
270
- Skill: [Reports: File src/main.rs:42, Error: mismatched types]
271
- Skill: [Suggests: Fix type error in src/main.rs:42]
145
+ Skill: [Detects npm] → [Runs: npm run build] → [Reports success with duration]
272
146
  ```
273
147
 
274
148
  ## Related Documentation