cc-dev-template 0.1.100 → 0.1.102
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/package.json +1 -1
- package/src/scripts/ship-policy.js +41 -28
- package/src/scripts/ship-state.js +66 -0
- package/src/scripts/statusline.js +9 -2
- package/src/skills/ship/SKILL.md +5 -16
- package/src/skills/ship/references/step-1-intent.md +1 -1
- package/src/skills/ship/references/step-2-questions.md +1 -1
- package/src/skills/ship/references/step-3-research.md +1 -1
- package/src/skills/ship/references/step-4-design.md +1 -1
- package/src/skills/ship/references/step-5-spec.md +2 -2
- package/src/skills/ship/references/step-6-verify.md +2 -2
- package/src/skills/ship/references/step-7-tasks.md +2 -2
- package/src/skills/ship/references/step-8-implement.md +1 -1
- package/src/skills/ship/references/step-9-reflect.md +1 -1
package/package.json
CHANGED
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
* of (phase, agent_type, tool_name, target_path) rules. No-op when
|
|
8
8
|
* ship is not active for the current session.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Per-session state: {cwd}/.claude/ship-sessions/{session_id}.json
|
|
11
|
+
* Created/updated by this hook when it intercepts `ship-state.js`
|
|
12
|
+
* Bash calls. The agent doesn't know its session_id — only the hook does.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
* Created by the
|
|
15
|
-
* This is what the hook reads for enforcement.
|
|
14
|
+
* Feature state: {spec_dir}/state.yaml
|
|
15
|
+
* Created/updated by ship-state.js (the CLI script the agent runs).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
const { readFileSync, writeFileSync, existsSync, mkdirSync, realpathSync } = require('fs');
|
|
18
|
+
const { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, realpathSync } = require('fs');
|
|
19
19
|
const { join, resolve, relative } = require('path');
|
|
20
20
|
|
|
21
21
|
// Tools that bypass all policy checks
|
|
@@ -61,16 +61,6 @@ function relPath(tool, input, cwd) {
|
|
|
61
61
|
return relative(cwd, abs);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function realResolve(p) {
|
|
65
|
-
const abs = resolve(p);
|
|
66
|
-
try { return realpathSync(abs); } catch {}
|
|
67
|
-
// File may not exist yet — resolve parent
|
|
68
|
-
const dir = resolve(abs, '..');
|
|
69
|
-
const base = require('path').basename(abs);
|
|
70
|
-
try { return join(realpathSync(dir), base); } catch {}
|
|
71
|
-
return abs;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
64
|
function under(p, prefix) {
|
|
75
65
|
const dir = prefix.replace(/\/$/, '');
|
|
76
66
|
return p === dir || p.startsWith(dir + '/');
|
|
@@ -101,8 +91,8 @@ function orchestratorPolicy(tool, input, cwd, specDir, phase) {
|
|
|
101
91
|
const p = relPath(tool, input, cwd);
|
|
102
92
|
if (p === null || p === undefined) return allow();
|
|
103
93
|
|
|
104
|
-
// State
|
|
105
|
-
if (p === `${specDir}/state.yaml`
|
|
94
|
+
// State file always writable
|
|
95
|
+
if (p === `${specDir}/state.yaml`) return allow();
|
|
106
96
|
|
|
107
97
|
// Per-phase permissions
|
|
108
98
|
const permFn = ORCHESTRATOR_WRITES[phase];
|
|
@@ -202,21 +192,44 @@ function main() {
|
|
|
202
192
|
const input = JSON.parse(readFileSync(0, 'utf-8'));
|
|
203
193
|
let cwd;
|
|
204
194
|
try { cwd = realpathSync(process.cwd()); } catch { cwd = process.cwd(); }
|
|
205
|
-
const stateFile = join(cwd, '.claude', 'ship-hook-state.json');
|
|
206
195
|
const sessionsDir = join(cwd, '.claude', 'ship-sessions');
|
|
207
196
|
const sessionFile = join(sessionsDir, `${input.session_id}.json`);
|
|
208
197
|
|
|
209
|
-
// Detect
|
|
210
|
-
if (
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
198
|
+
// Detect ship-state.js Bash calls — manage per-session state
|
|
199
|
+
if (input.tool_name === 'Bash' && input.tool_input) {
|
|
200
|
+
const cmd = input.tool_input.command || '';
|
|
201
|
+
const match = cmd.match(/ship-state\.js\s+(set|release)/);
|
|
202
|
+
if (match) {
|
|
203
|
+
const action = match[1];
|
|
204
|
+
if (action === 'set') {
|
|
205
|
+
const phaseMatch = cmd.match(/--phase\s+(\S+)/);
|
|
206
|
+
const featureMatch = cmd.match(/--feature\s+(\S+)/);
|
|
207
|
+
const specDirMatch = cmd.match(/--spec-dir\s+(\S+)/);
|
|
208
|
+
const subPhaseMatch = cmd.match(/--sub-phase\s+(?:"([^"]+)"|(\S+))/);
|
|
209
|
+
const subPhaseValue = subPhaseMatch ? (subPhaseMatch[1] || subPhaseMatch[2]) : null;
|
|
210
|
+
|
|
211
|
+
if (existsSync(sessionFile)) {
|
|
212
|
+
let state;
|
|
213
|
+
try { state = JSON.parse(readFileSync(sessionFile, 'utf-8')); } catch { state = {}; }
|
|
214
|
+
if (phaseMatch) { state.phase = phaseMatch[1]; state.sub_phase = null; }
|
|
215
|
+
if (featureMatch) state.feature = featureMatch[1];
|
|
216
|
+
if (specDirMatch) state.spec_dir = specDirMatch[1];
|
|
217
|
+
if (subPhaseValue) state.sub_phase = subPhaseValue;
|
|
218
|
+
writeFileSync(sessionFile, JSON.stringify(state, null, 2));
|
|
219
|
+
} else {
|
|
215
220
|
mkdirSync(sessionsDir, { recursive: true });
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
const state = {
|
|
222
|
+
phase: phaseMatch ? phaseMatch[1] : null,
|
|
223
|
+
feature: featureMatch ? featureMatch[1] : null,
|
|
224
|
+
spec_dir: specDirMatch ? specDirMatch[1] : null,
|
|
225
|
+
sub_phase: subPhaseValue,
|
|
226
|
+
};
|
|
227
|
+
writeFileSync(sessionFile, JSON.stringify(state, null, 2));
|
|
228
|
+
}
|
|
229
|
+
} else if (action === 'release') {
|
|
230
|
+
try { unlinkSync(sessionFile); } catch {}
|
|
218
231
|
}
|
|
219
|
-
process.exit(0);
|
|
232
|
+
process.exit(0);
|
|
220
233
|
}
|
|
221
234
|
}
|
|
222
235
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ship State CLI — manages feature state for the ship skill.
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* set --phase <p> --feature <f> --spec-dir <d> Initial claim or resume
|
|
8
|
+
* set --phase <p> --spec-dir <d> Phase transition
|
|
9
|
+
* set --sub-phase <s> Review loop tracking (no-op here, hook handles it)
|
|
10
|
+
* release Cleanup sessions directory
|
|
11
|
+
*
|
|
12
|
+
* The PreToolUse hook (ship-policy.js) intercepts these Bash calls to manage
|
|
13
|
+
* per-session state files. This script handles feature-level state (state.yaml).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } = require('fs');
|
|
17
|
+
const { join } = require('path');
|
|
18
|
+
|
|
19
|
+
function parseArgs(args) {
|
|
20
|
+
const flags = {};
|
|
21
|
+
for (let i = 0; i < args.length; i++) {
|
|
22
|
+
if (args[i].startsWith('--')) {
|
|
23
|
+
const key = args[i].slice(2);
|
|
24
|
+
const val = args[i + 1] && !args[i + 1].startsWith('--') ? args[++i] : true;
|
|
25
|
+
flags[key] = val;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return flags;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
32
|
+
|
|
33
|
+
if (command === 'set') {
|
|
34
|
+
const flags = parseArgs(rest);
|
|
35
|
+
|
|
36
|
+
// sub-phase only — hook handles per-session state, script is a no-op
|
|
37
|
+
if (flags['sub-phase'] && !flags.phase && !flags['spec-dir']) {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (flags['spec-dir'] && flags.phase) {
|
|
42
|
+
const specDir = flags['spec-dir'];
|
|
43
|
+
mkdirSync(specDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
const stateFile = join(specDir, 'state.yaml');
|
|
46
|
+
const existed = existsSync(stateFile);
|
|
47
|
+
let yaml;
|
|
48
|
+
|
|
49
|
+
if (existed) {
|
|
50
|
+
yaml = readFileSync(stateFile, 'utf-8');
|
|
51
|
+
yaml = yaml.replace(/^phase:.*$/m, `phase: ${flags.phase}`);
|
|
52
|
+
if (flags.feature) {
|
|
53
|
+
yaml = yaml.replace(/^feature:.*$/m, `feature: ${flags.feature}`);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
yaml = `feature: ${flags.feature || 'unknown'}\nphase: ${flags.phase}\ndir: ${specDir}\n`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
writeFileSync(stateFile, yaml);
|
|
60
|
+
}
|
|
61
|
+
} else if (command === 'release') {
|
|
62
|
+
rmSync('.claude/ship-sessions/', { recursive: true, force: true });
|
|
63
|
+
} else {
|
|
64
|
+
console.error(`Unknown command: ${command}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
@@ -20,8 +20,8 @@ const { homedir } = require('os');
|
|
|
20
20
|
|
|
21
21
|
// Usage API cache
|
|
22
22
|
const USAGE_CACHE_PATH = join(homedir(), '.claude', '.usage-cache.json');
|
|
23
|
-
const USAGE_CACHE_TTL =
|
|
24
|
-
const USAGE_HISTORY_MAX = 20; // ~
|
|
23
|
+
const USAGE_CACHE_TTL = 120000; // 2 minutes
|
|
24
|
+
const USAGE_HISTORY_MAX = 20; // ~40 min of readings at 2min intervals
|
|
25
25
|
|
|
26
26
|
// Background refresh mode: fetch usage data and write cache, then exit
|
|
27
27
|
if (process.argv.includes('--refresh')) {
|
|
@@ -373,6 +373,13 @@ function getUsageData() {
|
|
|
373
373
|
|
|
374
374
|
// Trigger background refresh if cache is stale
|
|
375
375
|
if (cacheAge > USAGE_CACHE_TTL) {
|
|
376
|
+
// Optimistic lock: claim the timestamp so other sessions see fresh cache
|
|
377
|
+
try {
|
|
378
|
+
const raw = readFileSync(USAGE_CACHE_PATH, 'utf-8');
|
|
379
|
+
const cache = JSON.parse(raw);
|
|
380
|
+
cache.timestamp = Date.now();
|
|
381
|
+
writeFileSync(USAGE_CACHE_PATH, JSON.stringify(cache));
|
|
382
|
+
} catch {}
|
|
376
383
|
try {
|
|
377
384
|
const child = spawn(process.execPath, [__filename, '--refresh'], {
|
|
378
385
|
detached: true,
|
package/src/skills/ship/SKILL.md
CHANGED
|
@@ -44,27 +44,16 @@ Look for `docs/specs/{feature-name}/state.yaml`.
|
|
|
44
44
|
| tasks | `references/step-7-tasks.md` |
|
|
45
45
|
| implement | `references/step-8-implement.md` |
|
|
46
46
|
|
|
47
|
-
Also
|
|
47
|
+
Also run: `node ~/.claude/scripts/ship-state.js set --phase {phase} --feature {feature} --spec-dir {dir}` (using values from state.yaml).
|
|
48
48
|
|
|
49
49
|
Read the step file for the current phase and follow its instructions.
|
|
50
50
|
|
|
51
|
-
**If state.yaml does not exist**, this is a new feature.
|
|
51
|
+
**If state.yaml does not exist**, this is a new feature. Run:
|
|
52
52
|
|
|
53
|
-
```yaml
|
|
54
|
-
feature: {feature-name}
|
|
55
|
-
phase: intent
|
|
56
|
-
dir: docs/specs/{feature-name}
|
|
57
53
|
```
|
|
58
|
-
|
|
59
|
-
Also write `.claude/ship-hook-state.json` (enables policy enforcement for this session):
|
|
60
|
-
|
|
61
|
-
```json
|
|
62
|
-
{
|
|
63
|
-
"phase": "intent",
|
|
64
|
-
"feature": "{feature-name}",
|
|
65
|
-
"spec_dir": "docs/specs/{feature-name}",
|
|
66
|
-
"sub_phase": null
|
|
67
|
-
}
|
|
54
|
+
node ~/.claude/scripts/ship-state.js set --phase intent --feature {feature-name} --spec-dir docs/specs/{feature-name}
|
|
68
55
|
```
|
|
69
56
|
|
|
57
|
+
This creates the spec directory, initializes state.yaml, and enables policy enforcement for this session.
|
|
58
|
+
|
|
70
59
|
Then read `references/step-1-intent.md` to begin.
|
|
@@ -45,6 +45,6 @@ Present the intent document to the user for confirmation. Adjust if they have co
|
|
|
45
45
|
|
|
46
46
|
## Task 3: Proceed
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Run: `node ~/.claude/scripts/ship-state.js set --phase questions --spec-dir {spec_dir}`
|
|
49
49
|
|
|
50
50
|
Use the Read tool on `references/step-2-questions.md` to generate research questions from the intent.
|
|
@@ -36,6 +36,6 @@ Update `questions.md` based on user feedback. The user may add questions about p
|
|
|
36
36
|
|
|
37
37
|
## Task 3: Proceed
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Run: `node ~/.claude/scripts/ship-state.js set --phase research --spec-dir {spec_dir}`
|
|
40
40
|
|
|
41
41
|
Use the Read tool on `references/step-3-research.md` to begin objective codebase research.
|
|
@@ -58,6 +58,6 @@ If the research is thin or missing critical areas, spawn the objective-researche
|
|
|
58
58
|
|
|
59
59
|
## Task 3: Proceed
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Run: `node ~/.claude/scripts/ship-state.js set --phase design --spec-dir {spec_dir}`
|
|
62
62
|
|
|
63
63
|
Use the Read tool on `references/step-4-design.md` to begin the design discussion with the user.
|
|
@@ -63,6 +63,6 @@ Present to the user for confirmation.
|
|
|
63
63
|
|
|
64
64
|
## Task 4: Proceed
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Run: `node ~/.claude/scripts/ship-state.js set --phase spec --spec-dir {spec_dir}`
|
|
67
67
|
|
|
68
68
|
Use the Read tool on `references/step-5-spec.md` to generate the implementation specification.
|
|
@@ -38,7 +38,7 @@ Agent tool:
|
|
|
38
38
|
|
|
39
39
|
## Task 3: Review Loop
|
|
40
40
|
|
|
41
|
-
Spawn a FRESH instance of spec-writer in review mode. At least one review is mandatory. Before each review cycle,
|
|
41
|
+
Spawn a FRESH instance of spec-writer in review mode. At least one review is mandatory. Before each review cycle, run: `node ~/.claude/scripts/ship-state.js set --sub-phase "review-cycle-N"` (N = cycle number, starting at 1).
|
|
42
42
|
|
|
43
43
|
```
|
|
44
44
|
Agent tool:
|
|
@@ -71,6 +71,6 @@ Revise based on user feedback. If changes are substantial, re-run the review loo
|
|
|
71
71
|
|
|
72
72
|
## Task 5: Proceed
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Run: `node ~/.claude/scripts/ship-state.js set --phase verify --spec-dir {spec_dir}`
|
|
75
75
|
|
|
76
76
|
Use the Read tool on `references/step-6-verify.md` to plan verification for the spec.
|
|
@@ -27,7 +27,7 @@ Agent tool:
|
|
|
27
27
|
|
|
28
28
|
## Task 2: Review Loop
|
|
29
29
|
|
|
30
|
-
Spawn a FRESH instance of test-planner in review mode. At least one review is mandatory. Before each review cycle,
|
|
30
|
+
Spawn a FRESH instance of test-planner in review mode. At least one review is mandatory. Before each review cycle, run: `node ~/.claude/scripts/ship-state.js set --sub-phase "review-cycle-N"` (N = cycle number, starting at 1).
|
|
31
31
|
|
|
32
32
|
```
|
|
33
33
|
Agent tool:
|
|
@@ -59,6 +59,6 @@ Ask the user if the verification strategy is complete. Revise based on feedback.
|
|
|
59
59
|
|
|
60
60
|
## Task 4: Proceed
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Run: `node ~/.claude/scripts/ship-state.js set --phase tasks --spec-dir {spec_dir}`
|
|
63
63
|
|
|
64
64
|
Use the Read tool on `references/step-7-tasks.md` to break the spec into implementation tasks.
|
|
@@ -25,7 +25,7 @@ Agent tool:
|
|
|
25
25
|
|
|
26
26
|
## Task 2: Review Loop
|
|
27
27
|
|
|
28
|
-
Spawn a FRESH instance of task-breakdown in review mode. Before each review cycle,
|
|
28
|
+
Spawn a FRESH instance of task-breakdown in review mode. Before each review cycle, run: `node ~/.claude/scripts/ship-state.js set --sub-phase "review-cycle-N"` (N = cycle number, starting at 1):
|
|
29
29
|
|
|
30
30
|
```
|
|
31
31
|
Agent tool:
|
|
@@ -55,6 +55,6 @@ Revise based on user feedback. If changes are substantial, re-run the review loo
|
|
|
55
55
|
|
|
56
56
|
## Task 4: Proceed
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Run: `node ~/.claude/scripts/ship-state.js set --phase implement --spec-dir {spec_dir}`
|
|
59
59
|
|
|
60
60
|
Use the Read tool on `references/step-8-implement.md` to begin implementation.
|
|
@@ -43,7 +43,7 @@ Run independent tasks (no dependency between them) in parallel when possible. Al
|
|
|
43
43
|
|
|
44
44
|
## Step 3: Finalize
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Run: `node ~/.claude/scripts/ship-state.js set --phase complete --spec-dir {spec_dir}`
|
|
47
47
|
|
|
48
48
|
Present a summary to the user:
|
|
49
49
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Cleanup
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Run: `node ~/.claude/scripts/ship-state.js release` — this cleans up per-session state and the sessions directory.
|
|
6
6
|
|
|
7
7
|
## Self-Assessment
|
|
8
8
|
|