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.
- package/README.md +18 -10
- package/bin/install.js +177 -218
- package/docs/workflow-guide.md +4 -4
- package/package.json +1 -1
- package/src/commands/5/configure.md +124 -328
- package/src/commands/5/discuss-feature.md +7 -172
- package/src/commands/5/implement-feature.md +40 -152
- package/src/commands/5/plan-feature.md +15 -8
- package/src/commands/5/plan-implementation.md +14 -10
- package/src/commands/5/quick-implement.md +41 -142
- package/src/commands/5/review-code.md +4 -14
- package/src/commands/5/unlock.md +23 -0
- package/src/commands/5/update.md +41 -4
- package/src/commands/5/verify-implementation.md +34 -201
- package/src/hooks/check-updates.js +4 -3
- package/src/hooks/config-guard.js +30 -0
- package/src/hooks/plan-guard.js +79 -32
- package/src/hooks/statusline.js +4 -3
- package/src/settings.json +11 -1
- package/src/skills/build-project/SKILL.md +6 -132
- package/src/skills/configure-project/SKILL.md +26 -215
- package/src/skills/run-tests/SKILL.md +7 -210
- package/src/templates/workflow/QUICK-PLAN.md +0 -17
|
@@ -23,7 +23,7 @@ process.stdin.on('end', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
async function checkForUpdates(workspaceDir) {
|
|
26
|
-
const versionFile = path.join(workspaceDir, '.
|
|
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(
|
|
109
|
-
const parts2 = v2.split('.').map(
|
|
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
|
+
});
|
package/src/hooks/plan-guard.js
CHANGED
|
@@ -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
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
|
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
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
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:
|
|
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
|
|
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
|
|
75
|
-
resolved === dotFiveDir ||
|
|
76
|
-
resolved === claudeDotFiveDir;
|
|
82
|
+
resolved === dotFiveDir;
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
function
|
|
80
|
-
//
|
|
81
|
-
|
|
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 (
|
|
86
|
-
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return
|
|
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
|
-
//
|
|
140
|
+
return true; // Unreadable marker → fail-safe, assume active
|
|
100
141
|
}
|
|
142
|
+
}
|
|
101
143
|
|
|
102
|
-
|
|
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
|
}
|
package/src/hooks/statusline.js
CHANGED
|
@@ -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, '.
|
|
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(
|
|
72
|
-
const parts2 = v2.split('.').map(
|
|
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** (`.
|
|
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 `.
|
|
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,
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|