cc-dev-template 0.1.98 → 0.1.100

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.98",
3
+ "version": "0.1.100",
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
+ * Shared mailbox: {cwd}/.claude/ship-hook-state.json
11
+ * Written by orchestrator (doesn't know its session_id).
12
+ *
13
+ * Per-session: {cwd}/.claude/ship-sessions/{session_id}.json
14
+ * Created by the hook when it intercepts a Write to the mailbox.
15
+ * This is what the hook reads for enforcement.
11
16
  */
12
17
 
13
- const { readFileSync, writeFileSync, existsSync } = require('fs');
18
+ const { readFileSync, writeFileSync, existsSync, mkdirSync, realpathSync } = require('fs');
14
19
  const { join, resolve, relative } = require('path');
15
20
 
16
21
  // Tools that bypass all policy checks
@@ -56,6 +61,16 @@ function relPath(tool, input, cwd) {
56
61
  return relative(cwd, abs);
57
62
  }
58
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
+
59
74
  function under(p, prefix) {
60
75
  const dir = prefix.replace(/\/$/, '');
61
76
  return p === dir || p.startsWith(dir + '/');
@@ -86,7 +101,7 @@ function orchestratorPolicy(tool, input, cwd, specDir, phase) {
86
101
  const p = relPath(tool, input, cwd);
87
102
  if (p === null || p === undefined) return allow();
88
103
 
89
- // State files always writable
104
+ // State/mailbox files always writable
90
105
  if (p === `${specDir}/state.yaml` || p === '.claude/ship-hook-state.json') return allow();
91
106
 
92
107
  // Per-phase permissions
@@ -128,10 +143,10 @@ function objectiveResearcherPolicy(tool, input, cwd, specDir) {
128
143
  return allow();
129
144
  }
130
145
 
131
- if (tool === 'Write') {
146
+ if (tool === 'Write' || tool === 'Edit') {
132
147
  if (p === null) return allow();
133
148
  if (p !== undefined && p.startsWith(`${specDir}/research`) && p.endsWith('.md')) return allow();
134
- return block(`objective-researcher can only write to ${specDir}/research-*.md`);
149
+ return block(`objective-researcher can only write to ${specDir}/research.md`);
135
150
  }
136
151
 
137
152
  if (tool === 'Bash') return allow();
@@ -185,24 +200,35 @@ const AGENT_POLICIES = {
185
200
 
186
201
  function main() {
187
202
  const input = JSON.parse(readFileSync(0, 'utf-8'));
188
- const cwd = process.cwd();
203
+ let cwd;
204
+ try { cwd = realpathSync(process.cwd()); } catch { cwd = process.cwd(); }
189
205
  const stateFile = join(cwd, '.claude', 'ship-hook-state.json');
206
+ const sessionsDir = join(cwd, '.claude', 'ship-sessions');
207
+ const sessionFile = join(sessionsDir, `${input.session_id}.json`);
208
+
209
+ // Detect writes to the shared mailbox — copy content to per-session file
210
+ if ((input.tool_name === 'Write' || input.tool_name === 'Edit') && input.tool_input) {
211
+ const targetPath = input.tool_input.file_path;
212
+ if (targetPath && realResolve(targetPath) === stateFile) {
213
+ if (input.tool_name === 'Write') {
214
+ try {
215
+ mkdirSync(sessionsDir, { recursive: true });
216
+ writeFileSync(sessionFile, input.tool_input.content);
217
+ } catch {}
218
+ }
219
+ process.exit(0); // Allow the write (Edit is allowed too, per-session file stays slightly stale)
220
+ }
221
+ }
190
222
 
191
- if (!existsSync(stateFile)) process.exit(0);
223
+ // No per-session file = not a ship session — completely invisible
224
+ if (!existsSync(sessionFile)) process.exit(0);
225
+
226
+ if (BYPASS_TOOLS.has(input.tool_name)) process.exit(0);
192
227
 
193
228
  let state;
194
- try { state = JSON.parse(readFileSync(stateFile, 'utf-8')); }
229
+ try { state = JSON.parse(readFileSync(sessionFile, 'utf-8')); }
195
230
  catch { process.exit(0); }
196
231
 
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
232
  const caller = input.agent_type || 'orchestrator';
207
233
  const tool = input.tool_name;
208
234
  const toolInput = input.tool_input || {};
@@ -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}` : '';
@@ -57,6 +57,45 @@ Configuration: No special setting needed
57
57
 
58
58
  Configuration: `context: fork` (optionally add `agent: Explore` or `agent: Plan` for specialized behavior)
59
59
 
60
+ ## Design Constraints
61
+
62
+ Only relevant for skills that orchestrate sub-agents or have clear role boundaries. Skip this section for simple informational skills or single-step workflows.
63
+
64
+ ### Constraint Mechanisms
65
+
66
+ From weakest to strongest:
67
+
68
+ | Mechanism | What It Does | Use When |
69
+ |-----------|-------------|----------|
70
+ | Prompt instructions | Advisory guidance in skill/agent text | Soft guidance, not security-critical |
71
+ | `allowed-tools` (skill frontmatter) | Auto-approves listed tools (no user prompt). Does NOT block anything. | Reducing user friction for known-safe tools |
72
+ | `tools` (agent frontmatter) | Grants ONLY listed tools to sub-agent. Everything else denied. | Coarse tool-level restriction (e.g., read-only agent) |
73
+ | PreToolUse hook (settings.json) | Script checks every tool call against policy rules. Can inspect file paths, commands, agent identity. | Path-level enforcement, per-phase rules, cross-agent policies |
74
+ | Inline content passing | Pass file contents in Agent prompt instead of letting agent read files. Combined with restricted `tools`, agent physically cannot access unauthorized files. | Contamination prevention, strongest isolation |
75
+
76
+ ### Constraint Design Questions
77
+
78
+ Ask the user these questions when the skill involves sub-agents:
79
+
80
+ - Should the orchestrator be restricted? (e.g., blocked from source code, forced to delegate)
81
+ - Should sub-agents only access specific files/directories?
82
+ - Are there contamination concerns? (agents that should be blind to certain information)
83
+ - Should certain deliverables only be writable by specific agents?
84
+
85
+ ### Constraint Map
86
+
87
+ If constraints are needed, produce a constraint map before designing frontmatter: which agent can do what, and which mechanism enforces each rule. This feeds into the frontmatter design and may require a hook script.
88
+
89
+ Example format:
90
+ ```
91
+ Orchestrator: no Bash, no source code reads → PreToolUse hook
92
+ Researcher agent: tools: Read, Grep, Glob, Write → agent frontmatter
93
+ Writer agent: only writes to docs/output/ → PreToolUse hook (path check)
94
+ Reviewer agent: receives content inline, no file reads → inline content passing + tools: Write
95
+ ```
96
+
97
+ Reference the creating-sub-agents skill for detailed sub-agent constraint configuration (tool sets, permission modes, hook examples).
98
+
60
99
  ## Design the Frontmatter
61
100
 
62
101
  Every skill has YAML frontmatter. Required and optional fields:
@@ -75,7 +114,7 @@ Every skill has YAML frontmatter. Required and optional fields:
75
114
  | `argument-hint` | Placeholder shown after slash command (e.g., `[issue-number]`, `[filename]`) |
76
115
  | `disable-model-invocation` | Set `true` to prevent Claude from auto-activating — use for side-effect workflows like deploy or commit |
77
116
  | `user-invocable` | Set `false` to hide from the `/` menu — use for background knowledge skills |
78
- | `allowed-tools` | Restrict which tools the skill can use (e.g., `Read, Grep, Glob` for read-only) |
117
+ | `allowed-tools` | Pre-approve tools for auto-acceptance (user won't be prompted for these). Does NOT restrict — unlisted tools still work, they just require user approval. |
79
118
  | `model` | Override the model used when this skill is active |
80
119
  | `context` | Set `fork` to run in an isolated sub-agent context |
81
120
  | `agent` | Sub-agent type when `context: fork` is set (`Explore`, `Plan`, `general-purpose`, or custom) |
@@ -49,6 +49,12 @@ Go through each file and verify:
49
49
  - For informational skills: reflection section at the end of SKILL.md?
50
50
  - For forked-context skills: "Skill Observations" section in the output format?
51
51
 
52
+ ### Constraints
53
+ - If the skill spawns sub-agents: are tool grants appropriate for each agent's role?
54
+ - Is `allowed-tools` used correctly? (auto-approval only, not restriction)
55
+ - If path-level restrictions are needed: is a PreToolUse hook designed?
56
+ - Are constraint decisions documented in the skill (so future editors understand why)?
57
+
52
58
  ## Run Validation
53
59
 
54
60
  ```bash
@@ -167,6 +167,14 @@ if (!frontmatter.valid) {
167
167
  null,
168
168
  'Remove all < and > characters from frontmatter values');
169
169
  }
170
+
171
+ // Check: allowed-tools usage awareness
172
+ if (frontmatter.data['allowed-tools']) {
173
+ addFinding('CONSTRAINT', 'info',
174
+ 'Skill uses allowed-tools — this pre-approves tools for auto-acceptance, it does NOT restrict tool access. If restriction is intended, use the tools field on sub-agent definitions instead.',
175
+ null,
176
+ 'See creating-sub-agents skill for sub-agent tool restriction patterns');
177
+ }
170
178
  }
171
179
 
172
180
  // Check 3: Second person violations
@@ -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,7 +56,7 @@ 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
61
  Update `{spec_dir}/state.yaml` — set `phase: design`. Update `.claude/ship-hook-state.json` — set `phase` to `"design"`, `sub_phase` to `null`.
55
62
 
@@ -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
+ Delete `.claude/ship-hook-state.json` and the `.claude/ship-sessions/` directory — policy enforcement is no longer needed for this feature.
6
6
 
7
7
  ## Self-Assessment
8
8