cc-dev-template 0.1.99 → 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
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
8
|
+
* ship is not active for the current session.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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}` : '';
|
|
@@ -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. "
|
|
14
|
-
2. "
|
|
15
|
-
3. "
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|
|
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
|
|