cc-dev-template 0.1.99 → 0.1.101

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-dev-template",
3
- "version": "0.1.99",
3
+ "version": "0.1.101",
4
4
  "description": "Structured AI-assisted development framework for Claude Code",
5
5
  "bin": {
6
6
  "cc-dev-template": "./bin/install.js"
@@ -19,14 +19,14 @@ You do NOT know what feature is being built. You only have questions. Answer the
19
19
 
20
20
  1. Review the questions provided in your prompt
21
21
  2. For each question, explore the codebase using Grep, Glob, and Read
22
- 3. Write findings to the output path provided in your prompt
22
+ 3. Use Edit to replace your placeholder in the research file with your findings (the placeholder and file path are specified in your prompt)
23
23
 
24
24
  ## Output Format
25
25
 
26
- For each question:
26
+ Use Edit to replace your placeholder with content in this format — note the `###` heading level since `##` is used for category headers:
27
27
 
28
28
  ```markdown
29
- ## Q: {Original question}
29
+ ### Q: {Original question}
30
30
 
31
31
  **Finding**: {Your answer with specific details}
32
32
 
@@ -42,7 +42,7 @@ For each question:
42
42
  After answering all questions, add a final section:
43
43
 
44
44
  ```markdown
45
- ## Additional Observations
45
+ ### Additional Observations
46
46
 
47
47
  {Anything noteworthy you discovered while researching that wasn't directly asked about but seems relevant to understanding this area of the codebase}
48
48
  ```
@@ -5,12 +5,17 @@
5
5
  *
6
6
  * PreToolUse hook that checks every tool call against a policy matrix
7
7
  * of (phase, agent_type, tool_name, target_path) rules. No-op when
8
- * ship is not active (state file missing or session mismatch).
8
+ * ship is not active for the current session.
9
9
  *
10
- * State: {cwd}/.claude/ship-hook-state.json
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.
13
+ *
14
+ * Feature state: {spec_dir}/state.yaml
15
+ * Created/updated by ship-state.js (the CLI script the agent runs).
11
16
  */
12
17
 
13
- const { readFileSync, writeFileSync, existsSync } = require('fs');
18
+ const { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, realpathSync } = require('fs');
14
19
  const { join, resolve, relative } = require('path');
15
20
 
16
21
  // Tools that bypass all policy checks
@@ -86,8 +91,8 @@ function orchestratorPolicy(tool, input, cwd, specDir, phase) {
86
91
  const p = relPath(tool, input, cwd);
87
92
  if (p === null || p === undefined) return allow();
88
93
 
89
- // State files always writable
90
- if (p === `${specDir}/state.yaml` || p === '.claude/ship-hook-state.json') return allow();
94
+ // State file always writable
95
+ if (p === `${specDir}/state.yaml`) return allow();
91
96
 
92
97
  // Per-phase permissions
93
98
  const permFn = ORCHESTRATOR_WRITES[phase];
@@ -128,10 +133,10 @@ function objectiveResearcherPolicy(tool, input, cwd, specDir) {
128
133
  return allow();
129
134
  }
130
135
 
131
- if (tool === 'Write') {
136
+ if (tool === 'Write' || tool === 'Edit') {
132
137
  if (p === null) return allow();
133
138
  if (p !== undefined && p.startsWith(`${specDir}/research`) && p.endsWith('.md')) return allow();
134
- return block(`objective-researcher can only write to ${specDir}/research-*.md`);
139
+ return block(`objective-researcher can only write to ${specDir}/research.md`);
135
140
  }
136
141
 
137
142
  if (tool === 'Bash') return allow();
@@ -185,24 +190,58 @@ const AGENT_POLICIES = {
185
190
 
186
191
  function main() {
187
192
  const input = JSON.parse(readFileSync(0, 'utf-8'));
188
- const cwd = process.cwd();
189
- const stateFile = join(cwd, '.claude', 'ship-hook-state.json');
193
+ let cwd;
194
+ try { cwd = realpathSync(process.cwd()); } catch { cwd = process.cwd(); }
195
+ const sessionsDir = join(cwd, '.claude', 'ship-sessions');
196
+ const sessionFile = join(sessionsDir, `${input.session_id}.json`);
197
+
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 {
220
+ mkdirSync(sessionsDir, { recursive: true });
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 {}
231
+ }
232
+ process.exit(0);
233
+ }
234
+ }
190
235
 
191
- if (!existsSync(stateFile)) process.exit(0);
236
+ // No per-session file = not a ship session — completely invisible
237
+ if (!existsSync(sessionFile)) process.exit(0);
238
+
239
+ if (BYPASS_TOOLS.has(input.tool_name)) process.exit(0);
192
240
 
193
241
  let state;
194
- try { state = JSON.parse(readFileSync(stateFile, 'utf-8')); }
242
+ try { state = JSON.parse(readFileSync(sessionFile, 'utf-8')); }
195
243
  catch { process.exit(0); }
196
244
 
197
- // First-touch: inject session_id if missing
198
- if (!state.session_id) {
199
- state.session_id = input.session_id;
200
- try { writeFileSync(stateFile, JSON.stringify(state, null, 2)); } catch {}
201
- }
202
-
203
- if (state.session_id !== input.session_id) process.exit(0);
204
- if (BYPASS_TOOLS.has(input.tool_name)) process.exit(0);
205
-
206
245
  const caller = input.agent_type || 'orchestrator';
207
246
  const tool = input.tool_name;
208
247
  const toolInput = input.tool_input || {};
@@ -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
+ }
@@ -560,13 +560,35 @@ function main() {
560
560
  usageLines.push(makeBoxLine(usageDisplay));
561
561
  }
562
562
 
563
- // Ship phase line (if active)
563
+ // Ship phase line (if active) — read from per-session files
564
564
  const shipLines = [];
565
565
  try {
566
- const shipStatePath = join(data.workspace.project_dir, '.claude', 'ship-hook-state.json');
567
- const shipStat = statSync(shipStatePath);
568
- if (Date.now() - shipStat.mtimeMs < 300000) { // < 5 min old
569
- const shipState = JSON.parse(readFileSync(shipStatePath, 'utf-8'));
566
+ const sessionsDir = join(data.workspace.project_dir, '.claude', 'ship-sessions');
567
+ let shipState = null;
568
+
569
+ // Try session_id first (if available in input), otherwise most recently modified
570
+ if (data.session_id) {
571
+ const sessionFile = join(sessionsDir, `${data.session_id}.json`);
572
+ const st = statSync(sessionFile);
573
+ if (Date.now() - st.mtimeMs < 300000) {
574
+ shipState = JSON.parse(readFileSync(sessionFile, 'utf-8'));
575
+ }
576
+ } else {
577
+ // Fallback: most recently modified session file
578
+ const files = readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
579
+ let latest = null;
580
+ let latestMtime = 0;
581
+ for (const f of files) {
582
+ const fp = join(sessionsDir, f);
583
+ const st = statSync(fp);
584
+ if (st.mtimeMs > latestMtime) { latestMtime = st.mtimeMs; latest = fp; }
585
+ }
586
+ if (latest && Date.now() - latestMtime < 300000) {
587
+ shipState = JSON.parse(readFileSync(latest, 'utf-8'));
588
+ }
589
+ }
590
+
591
+ if (shipState) {
570
592
  const phase = (shipState.phase || '').toUpperCase();
571
593
  const feature = shipState.feature || '';
572
594
  const subPhase = shipState.sub_phase ? ` ${shipState.sub_phase}` : '';
@@ -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 write/update `.claude/ship-hook-state.json` with `phase`, `feature`, and `spec_dir` from state.yaml (set `sub_phase` to `null`).
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. Create the spec directory and an initial state.yaml:
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
- Update `{spec_dir}/state.yaml` — set `phase: questions`. Update `.claude/ship-hook-state.json` set `phase` to `"questions"`, `sub_phase` to `null`.
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
- Update `{spec_dir}/state.yaml` — set `phase: research`. Update `.claude/ship-hook-state.json` set `phase` to `"research"`, `sub_phase` to `null`.
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.
@@ -10,34 +10,41 @@ Questions are distributed across parallel researchers by category — each agent
10
10
 
11
11
  Create these tasks and work through them in order:
12
12
 
13
- 1. "Group questions and distribute to parallel researchers" — spawn multiple objective-researchers
14
- 2. "Merge research" — combine results into a single file
15
- 3. "Review research with user" — present findings
16
- 4. "Begin design discussion" — proceed to the next phase
13
+ 1. "Create research skeleton and distribute to parallel researchers" — spawn multiple objective-researchers
14
+ 2. "Review research with user" — present findings
15
+ 3. "Begin design discussion" — proceed to the next phase
17
16
 
18
17
  ## Task 1: Distribute Research
19
18
 
20
19
  Read `{spec_dir}/questions.md` yourself. Identify the category sections (the `##` headers the question-generator produces).
21
20
 
22
- For each category, spawn an `objective-researcher` with that category's questions passed inline. Spawn ALL agents in parallel use multiple Agent tool calls in a single message.
21
+ Create a skeleton `{spec_dir}/research.md` with a `## {Category Name}` header for each category and a unique placeholder line beneath it:
23
22
 
24
- Each agent writes to a separate file to avoid write conflicts:
23
+ ```markdown
24
+ ## Authentication
25
+
26
+ <!-- PENDING: authentication -->
27
+
28
+ ## Database Schema
29
+
30
+ <!-- PENDING: database-schema -->
31
+ ```
32
+
33
+ Then for each category, spawn an `objective-researcher` with that category's questions passed inline. Spawn ALL agents in parallel — use multiple Agent tool calls in a single message.
34
+
35
+ Tell each researcher to use Edit to replace its placeholder in the shared research file:
25
36
 
26
37
  ```
27
38
  Agent tool:
28
39
  subagent_type: "objective-researcher"
29
- prompt: "Research the codebase to answer these questions. Write your findings to {spec_dir}/research-{category-slug}.md.\n\n{paste this category's questions here}"
40
+ prompt: "Research the codebase to answer these questions. Use Edit to replace the placeholder <!-- PENDING: {category-slug} --> in {spec_dir}/research.md with your findings.\n\n{paste this category's questions here}"
30
41
  ```
31
42
 
32
43
  If there are many small categories (6+), group related ones together to keep the agent count reasonable — 3 to 5 parallel agents is the sweet spot.
33
44
 
34
45
  The objective-researcher has full codebase access (Read, Grep, Glob, Bash) but no knowledge of the feature being built. It receives only the questions via its prompt — it never reads from docs/.
35
46
 
36
- ## Task 2: Merge Research
37
-
38
- After all researchers complete, read each `research-{slug}.md` file. Concatenate them into a single `{spec_dir}/research.md` (preserving the section structure from each file). Then delete the individual `research-{slug}.md` files — downstream steps only need the merged file.
39
-
40
- ## Task 3: Review Research
47
+ ## Task 2: Review Research
41
48
 
42
49
  Read `{spec_dir}/research.md` and present a summary to the user. Highlight:
43
50
 
@@ -49,8 +56,8 @@ The user may add context the researcher missed, or flag patterns that are outdat
49
56
 
50
57
  If the research is thin or missing critical areas, spawn the objective-researcher again with additional targeted questions.
51
58
 
52
- ## Task 4: Proceed
59
+ ## Task 3: Proceed
53
60
 
54
- Update `{spec_dir}/state.yaml` — set `phase: design`. Update `.claude/ship-hook-state.json` set `phase` to `"design"`, `sub_phase` to `null`.
61
+ Run: `node ~/.claude/scripts/ship-state.js set --phase design --spec-dir {spec_dir}`
55
62
 
56
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
- Update `{spec_dir}/state.yaml` — set `phase: spec`. Update `.claude/ship-hook-state.json` set `phase` to `"spec"`, `sub_phase` to `null`.
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, update `.claude/ship-hook-state.json` `sub_phase` to `"review-cycle-N"` (N = cycle number, starting at 1).
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
- Update `{spec_dir}/state.yaml` — set `phase: verify`. Update `.claude/ship-hook-state.json` set `phase` to `"verify"`, `sub_phase` to `null`.
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, update `.claude/ship-hook-state.json` `sub_phase` to `"review-cycle-N"` (N = cycle number, starting at 1).
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
- Update `{spec_dir}/state.yaml` — set `phase: tasks`. Update `.claude/ship-hook-state.json` set `phase` to `"tasks"`, `sub_phase` to `null`.
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, update `.claude/ship-hook-state.json` `sub_phase` to `"review-cycle-N"` (N = cycle number, starting at 1):
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
- Update `{spec_dir}/state.yaml` — set `phase: implement`. Update `.claude/ship-hook-state.json` set `phase` to `"implement"`, `sub_phase` to `null`.
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
- Update `{spec_dir}/state.yaml` — set `phase: complete`. Update `.claude/ship-hook-state.json` set `phase` to `"complete"`, `sub_phase` to `null`.
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
- Delete `.claude/ship-hook-state.json` — policy enforcement is no longer needed for this feature.
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