claude-dev-env 1.7.0 → 1.8.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/agents/deep-research.md +170 -0
- package/bin/install.mjs +98 -102
- package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +68 -0
- package/hooks/blocking/agent-execution-intent-gate.py +83 -0
- package/hooks/blocking/prompt-workflow-stop-guard.py +131 -0
- package/hooks/blocking/prompt_workflow_gate_core.py +161 -0
- package/hooks/blocking/test_agent_execution_intent_gate.py +106 -0
- package/hooks/blocking/test_context_control_policy_files.py +27 -0
- package/hooks/blocking/test_prompt_workflow_gate_core.py +68 -0
- package/hooks/blocking/test_prompt_workflow_stop_guard.py +144 -0
- package/hooks/hooks.json +10 -0
- package/package.json +1 -6
- package/rules/prompt-workflow-context-controls.md +48 -0
- package/skills/agent-prompt/SKILL.md +200 -0
- package/skills/deep-research/SKILL.md +80 -0
- package/skills/dream/SKILL.md +118 -0
- package/skills/prompt-generator/REFERENCE.md +150 -0
- package/skills/prompt-generator/REFINEMENT_PIPELINE_RUNBOOK.md +174 -0
- package/skills/prompt-generator/SKILL.md +333 -0
- package/skills/research-mode/SKILL.md +53 -0
- package/skills/session-log/SKILL.md +237 -0
- package/skills/session-tidy/SKILL.md +181 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-research
|
|
3
|
+
description: Use this agent for iterative, multi-source deep research that produces comprehensive Obsidian reports with full citations. Official-docs-first methodology with anti-hallucination constraints. Examples:
|
|
4
|
+
|
|
5
|
+
<example>
|
|
6
|
+
Context: User wants thorough research on a technical topic
|
|
7
|
+
user: "Research the current state of WebSocket authentication best practices"
|
|
8
|
+
assistant: "I'll use the deep-research agent to conduct iterative multi-source research and produce a cited report."
|
|
9
|
+
<commentary>
|
|
10
|
+
Multi-source research requiring iteration and synthesis — exactly what deep-research handles.
|
|
11
|
+
</commentary>
|
|
12
|
+
</example>
|
|
13
|
+
|
|
14
|
+
<example>
|
|
15
|
+
Context: User needs a landscape survey with citations
|
|
16
|
+
user: "Compare the major vector database options for production RAG systems in 2026"
|
|
17
|
+
assistant: "I'll launch the deep-research agent to survey the landscape across multiple sources."
|
|
18
|
+
<commentary>
|
|
19
|
+
Broad survey requiring many sources, comparison, and synthesis — deep-research with exhaustive depth.
|
|
20
|
+
</commentary>
|
|
21
|
+
</example>
|
|
22
|
+
|
|
23
|
+
model: opus
|
|
24
|
+
color: cyan
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
You are a Deep Research agent. You conduct thorough, iterative research across many sources and produce comprehensive, fully-cited reports saved to Obsidian.
|
|
28
|
+
|
|
29
|
+
You receive a `<research_brief>` from the orchestrating skill. Your job is to execute the research.
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
On receiving the research brief, write the state file:
|
|
34
|
+
|
|
35
|
+
`.deep-research-state.md`:
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
---
|
|
39
|
+
topic: "[from brief]"
|
|
40
|
+
brief: "[one-line summary from brief]"
|
|
41
|
+
iteration: 0
|
|
42
|
+
max_iterations: [from brief]
|
|
43
|
+
status: researching
|
|
44
|
+
source_count: 0
|
|
45
|
+
official_docs_found: false
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Sources Found
|
|
49
|
+
|
|
50
|
+
(none yet)
|
|
51
|
+
|
|
52
|
+
## Key Findings
|
|
53
|
+
|
|
54
|
+
(none yet)
|
|
55
|
+
|
|
56
|
+
## Gaps Remaining
|
|
57
|
+
|
|
58
|
+
- Initial broad survey needed
|
|
59
|
+
|
|
60
|
+
## Next Iteration Focus
|
|
61
|
+
|
|
62
|
+
- Locate official vendor/creator documentation for the topic
|
|
63
|
+
- Broad survey searches on the topic
|
|
64
|
+
- Identify major themes and authoritative sources
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then immediately begin the first iteration.
|
|
68
|
+
|
|
69
|
+
## Anti-Hallucination Constraints (ALWAYS ACTIVE)
|
|
70
|
+
|
|
71
|
+
These three constraints apply to every claim, finding, and recommendation. Violating any invalidates the work.
|
|
72
|
+
|
|
73
|
+
### 1. Say "I don't know"
|
|
74
|
+
No credible source for a claim? Say so. Don't guess. Don't infer. Record the gap in the state file.
|
|
75
|
+
|
|
76
|
+
### 2. Cite everything
|
|
77
|
+
Every claim must cite: an external source with URL, a named expert/paper/researcher, or official documentation. If you cannot find a supporting source, retract the claim.
|
|
78
|
+
|
|
79
|
+
### 3. Direct quotes for factual grounding
|
|
80
|
+
Extract actual text from sources before analyzing. Ground responses in word-for-word quotes, not paraphrased summaries.
|
|
81
|
+
|
|
82
|
+
## Iteration Protocol
|
|
83
|
+
|
|
84
|
+
Each iteration, follow these steps in order:
|
|
85
|
+
|
|
86
|
+
### Step 1: Read State
|
|
87
|
+
|
|
88
|
+
Read `.deep-research-state.md`. Understand: sources found, key findings, remaining gaps, next focus.
|
|
89
|
+
|
|
90
|
+
First iteration? State is empty — start with official docs, then broad survey.
|
|
91
|
+
|
|
92
|
+
### Step 2: Research (Search + Analyze)
|
|
93
|
+
|
|
94
|
+
Use available search and fetch tools aggressively and in parallel.
|
|
95
|
+
|
|
96
|
+
**Official docs first** — In early iterations, your primary objective is to locate and deeply read the official vendor/creator documentation for the topic. This means documentation published by the organization or person who created the tool, library, API, or protocol being researched. Exhaust official sources before broadening to secondary ones.
|
|
97
|
+
|
|
98
|
+
If no official documentation exists for the primary topic, record this explicitly as a gap in the state file. The absence of official docs is itself a finding — do not silently move on.
|
|
99
|
+
|
|
100
|
+
**Strategy by iteration phase:**
|
|
101
|
+
- **Early (1-3)**: Official docs first. Locate vendor/creator documentation. Read it deeply, extract direct quotes. Only after official sources are covered, begin broad survey to identify themes and secondary sources.
|
|
102
|
+
- **Middle (4-8)**: Deep dives into secondary sources. Fill gaps that official docs don't cover. Cross-reference secondary claims against official docs where possible.
|
|
103
|
+
- **Late (9+)**: Synthesis and gap-filling. Target remaining gaps, resolve contradictions between sources. Prefer official docs when sources disagree.
|
|
104
|
+
|
|
105
|
+
**Source classification** — When recording sources in the state file, tag each as:
|
|
106
|
+
- `[official]` — published by the vendor, creator, or maintainer of the tool/technology
|
|
107
|
+
- `[secondary]` — everything else (blog posts, tutorials, community content, third-party analysis)
|
|
108
|
+
|
|
109
|
+
### Step 3: Update State
|
|
110
|
+
|
|
111
|
+
Update `.deep-research-state.md` with:
|
|
112
|
+
- New sources (title, URL, one-line relevance summary, [official] or [secondary] tag)
|
|
113
|
+
- Key findings with citations
|
|
114
|
+
- Updated gaps list
|
|
115
|
+
- Next iteration focus (specific queries and angles)
|
|
116
|
+
- Increment `iteration` and `source_count` in frontmatter
|
|
117
|
+
- Update `official_docs_found` if official docs were located
|
|
118
|
+
|
|
119
|
+
### Step 4: Continue or Complete?
|
|
120
|
+
|
|
121
|
+
**Continue** if:
|
|
122
|
+
- Significant gaps remain
|
|
123
|
+
- Key questions from the brief are unanswered
|
|
124
|
+
- Promising leads not yet followed
|
|
125
|
+
- Source count below the brief's target depth
|
|
126
|
+
- Current iteration < max_iterations
|
|
127
|
+
|
|
128
|
+
**Complete** if:
|
|
129
|
+
- All key questions answered with citations
|
|
130
|
+
- Source target met or exceeded
|
|
131
|
+
- Remaining gaps are minor or out of scope
|
|
132
|
+
- Diminishing returns from further searching
|
|
133
|
+
|
|
134
|
+
If continuing, loop back to Step 1 for the next iteration. If complete, proceed to the Completion Process.
|
|
135
|
+
|
|
136
|
+
### Completion Process
|
|
137
|
+
|
|
138
|
+
1. Compile findings from state file into a structured report:
|
|
139
|
+
- Executive Summary (2-3 paragraphs, cite everything)
|
|
140
|
+
- Detailed Findings (organized by theme, not source; direct quotes blockquoted; every claim cited)
|
|
141
|
+
- Analysis (cross-cutting synthesis grounded in findings above)
|
|
142
|
+
- Limitations and Gaps (unanswered questions, source biases, whether official docs were available)
|
|
143
|
+
- Sources (numbered bibliography with [official]/[secondary] tags)
|
|
144
|
+
- Research Methodology (iterations, source count, date)
|
|
145
|
+
|
|
146
|
+
2. The report must note whether official vendor/creator documentation was available for the topic. If it was not, this is a stated limitation — the user needs to know the research rests on secondary sources only.
|
|
147
|
+
|
|
148
|
+
3. Write to Obsidian via `mcp__obsidian__write_note`:
|
|
149
|
+
- Path: `Research/[topic-slug].md`
|
|
150
|
+
- Include YAML frontmatter: type (deep-research), topic, date, sources count, iterations, official_docs_available (true/false), tags
|
|
151
|
+
- Every factual claim has an inline citation
|
|
152
|
+
- Full numbered bibliography at the end with [official]/[secondary] tags
|
|
153
|
+
|
|
154
|
+
4. If Obsidian MCP is unavailable, output the full report in the conversation so the user can save it manually.
|
|
155
|
+
|
|
156
|
+
### If max iterations reached without completion
|
|
157
|
+
|
|
158
|
+
- Compile what you have into a partial report
|
|
159
|
+
- Mark incomplete sections clearly
|
|
160
|
+
- Add "Future Research" section listing remaining gaps
|
|
161
|
+
- Still write to Obsidian
|
|
162
|
+
|
|
163
|
+
## Output to Parent
|
|
164
|
+
|
|
165
|
+
After completion, your return message to the parent should include:
|
|
166
|
+
- Obsidian note path where the report was saved (or "output inline" if MCP unavailable)
|
|
167
|
+
- Total sources consulted (with official vs secondary breakdown)
|
|
168
|
+
- Total iterations used
|
|
169
|
+
- Whether official vendor documentation was found
|
|
170
|
+
- Any significant gaps or limitations
|
package/bin/install.mjs
CHANGED
|
@@ -5,7 +5,6 @@ import { join, dirname, resolve, relative } from 'node:path';
|
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { execSync } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { createRequire } from 'node:module';
|
|
9
8
|
|
|
10
9
|
const CLAUDE_HOME = join(homedir(), '.claude');
|
|
11
10
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -13,7 +12,32 @@ const MANIFEST_FILE = join(CLAUDE_HOME, '.claude-dev-env-manifest.json');
|
|
|
13
12
|
const PACKAGE_NAME = 'claude-dev-env';
|
|
14
13
|
|
|
15
14
|
const CONTENT_DIRECTORIES = ['rules', 'docs', 'commands', 'agents'];
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
const INSTALL_GROUPS = {
|
|
17
|
+
core: {
|
|
18
|
+
description: 'Development standards, hooks, agents, commands',
|
|
19
|
+
skills: [
|
|
20
|
+
'anthropic-plan', 'everything-search', 'ingest',
|
|
21
|
+
'npm-creator', 'pr-review-responder', 'readability-review',
|
|
22
|
+
'recall', 'remember', 'rule-audit', 'rule-creator',
|
|
23
|
+
'skill-writer', 'tdd-team'
|
|
24
|
+
],
|
|
25
|
+
includeDirectories: ['rules', 'docs', 'commands', 'agents'],
|
|
26
|
+
includeAllHooks: true,
|
|
27
|
+
},
|
|
28
|
+
prompts: {
|
|
29
|
+
description: 'Prompt engineering tools',
|
|
30
|
+
skills: ['prompt-generator', 'agent-prompt'],
|
|
31
|
+
},
|
|
32
|
+
journal: {
|
|
33
|
+
description: 'Session logging and memory',
|
|
34
|
+
skills: ['dream', 'session-log', 'session-tidy'],
|
|
35
|
+
},
|
|
36
|
+
research: {
|
|
37
|
+
description: 'Deep research and citation tools',
|
|
38
|
+
skills: ['deep-research', 'research-mode'],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
17
41
|
|
|
18
42
|
function detectPython() {
|
|
19
43
|
const candidates = [
|
|
@@ -116,74 +140,14 @@ function mergeHooks(pythonCommand) {
|
|
|
116
140
|
return groupCount;
|
|
117
141
|
}
|
|
118
142
|
|
|
119
|
-
function findSiblingPackage(packageName) {
|
|
120
|
-
const workspaceRoot = resolve(PACKAGE_ROOT, '..', '..');
|
|
121
|
-
const workspacePath = join(workspaceRoot, 'packages', packageName);
|
|
122
|
-
if (existsSync(join(workspacePath, 'package.json'))) {
|
|
123
|
-
return workspacePath;
|
|
124
|
-
}
|
|
125
|
-
const require = createRequire(import.meta.url);
|
|
126
|
-
try {
|
|
127
|
-
const packageJsonPath = require.resolve(join(packageName, 'package.json'));
|
|
128
|
-
return dirname(packageJsonPath);
|
|
129
|
-
} catch { /* not found */ }
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function installSiblingContent(siblingRoot, allInstalledFiles) {
|
|
134
|
-
const siblingName = JSON.parse(readFileSync(join(siblingRoot, 'package.json'), 'utf8')).name;
|
|
135
|
-
console.log(` Installing sibling: ${siblingName}`);
|
|
136
|
-
let totalFiles = 0;
|
|
137
|
-
const skillsSource = join(siblingRoot, 'skills');
|
|
138
|
-
if (existsSync(skillsSource)) {
|
|
139
|
-
const skillDirs = readdirSync(skillsSource, { withFileTypes: true }).filter(entry => entry.isDirectory());
|
|
140
|
-
for (const skillDir of skillDirs) {
|
|
141
|
-
const stats = copyTree(join(skillsSource, skillDir.name), join(CLAUDE_HOME, 'skills', skillDir.name));
|
|
142
|
-
allInstalledFiles.push(...stats.paths);
|
|
143
|
-
totalFiles += stats.created + stats.updated;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const agentsSource = join(siblingRoot, 'agents');
|
|
147
|
-
if (existsSync(agentsSource)) {
|
|
148
|
-
const stats = copyTree(agentsSource, join(CLAUDE_HOME, 'agents'));
|
|
149
|
-
allInstalledFiles.push(...stats.paths);
|
|
150
|
-
totalFiles += stats.created + stats.updated;
|
|
151
|
-
}
|
|
152
|
-
const rulesSource = join(siblingRoot, 'rules');
|
|
153
|
-
if (existsSync(rulesSource)) {
|
|
154
|
-
const stats = copyTree(rulesSource, join(CLAUDE_HOME, 'rules'));
|
|
155
|
-
allInstalledFiles.push(...stats.paths);
|
|
156
|
-
totalFiles += stats.created + stats.updated;
|
|
157
|
-
}
|
|
158
|
-
const hooksSource = join(siblingRoot, 'hooks');
|
|
159
|
-
if (existsSync(hooksSource)) {
|
|
160
|
-
const filesToCopy = collectFiles(hooksSource).filter(file => !file.endsWith('hooks.json'));
|
|
161
|
-
const hooksDestination = join(CLAUDE_HOME, 'hooks');
|
|
162
|
-
for (const sourceFile of filesToCopy) {
|
|
163
|
-
const relativePath = relative(hooksSource, sourceFile);
|
|
164
|
-
const destFile = join(hooksDestination, relativePath);
|
|
165
|
-
mkdirSync(dirname(destFile), { recursive: true });
|
|
166
|
-
const existed = existsSync(destFile);
|
|
167
|
-
copyFileSync(sourceFile, destFile);
|
|
168
|
-
allInstalledFiles.push(destFile);
|
|
169
|
-
totalFiles++;
|
|
170
|
-
if (existed) {
|
|
171
|
-
console.log(` ↻ ${join('hooks', relativePath)} (updated)`);
|
|
172
|
-
} else {
|
|
173
|
-
console.log(` ✓ ${join('hooks', relativePath)} (new)`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return totalFiles;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
143
|
function writeManifest(installedFiles) {
|
|
181
144
|
const manifest = { package: PACKAGE_NAME, version: '1.0.0', installedAt: new Date().toISOString(), files: installedFiles };
|
|
182
145
|
writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2) + '\n');
|
|
183
146
|
}
|
|
184
147
|
|
|
185
|
-
function install() {
|
|
186
|
-
|
|
148
|
+
function install(selectedGroups) {
|
|
149
|
+
const groupLabel = selectedGroups ? `groups: ${selectedGroups.join(', ')}` : 'all';
|
|
150
|
+
console.log(`\nInstalling ${PACKAGE_NAME} (${groupLabel})...\n`);
|
|
187
151
|
const pythonCommand = detectPython();
|
|
188
152
|
if (!pythonCommand) {
|
|
189
153
|
console.error('ERROR: Python 3 not found. Install Python 3.8+ and ensure python3, python, or py is on PATH.');
|
|
@@ -191,9 +155,21 @@ function install() {
|
|
|
191
155
|
}
|
|
192
156
|
console.log(` Python: ${pythonCommand}`);
|
|
193
157
|
mkdirSync(CLAUDE_HOME, { recursive: true });
|
|
158
|
+
|
|
159
|
+
const allowedSkills = selectedGroups
|
|
160
|
+
? new Set(selectedGroups.flatMap(groupName => INSTALL_GROUPS[groupName].skills || []))
|
|
161
|
+
: null;
|
|
162
|
+
const allowedDirectories = selectedGroups
|
|
163
|
+
? new Set(selectedGroups.flatMap(groupName => INSTALL_GROUPS[groupName].includeDirectories || []))
|
|
164
|
+
: null;
|
|
165
|
+
const shouldInstallHooks = selectedGroups
|
|
166
|
+
? selectedGroups.some(groupName => INSTALL_GROUPS[groupName].includeAllHooks)
|
|
167
|
+
: true;
|
|
168
|
+
|
|
194
169
|
const allInstalledFiles = [];
|
|
195
170
|
const summary = {};
|
|
196
171
|
for (const directory of CONTENT_DIRECTORIES) {
|
|
172
|
+
if (allowedDirectories && !allowedDirectories.has(directory)) continue;
|
|
197
173
|
const sourceDir = join(PACKAGE_ROOT, directory);
|
|
198
174
|
if (!existsSync(sourceDir)) continue;
|
|
199
175
|
const destDir = join(CLAUDE_HOME, directory);
|
|
@@ -208,6 +184,7 @@ function install() {
|
|
|
208
184
|
let skillsUpdated = 0;
|
|
209
185
|
const skillPaths = [];
|
|
210
186
|
for (const skillDir of skillDirs) {
|
|
187
|
+
if (allowedSkills && !allowedSkills.has(skillDir.name)) continue;
|
|
211
188
|
const stats = copyTree(join(skillsSource, skillDir.name), join(CLAUDE_HOME, 'skills', skillDir.name));
|
|
212
189
|
skillsCreated += stats.created;
|
|
213
190
|
skillsUpdated += stats.updated;
|
|
@@ -216,36 +193,28 @@ function install() {
|
|
|
216
193
|
summary.skills = { created: skillsCreated, updated: skillsUpdated, paths: skillPaths };
|
|
217
194
|
allInstalledFiles.push(...skillPaths);
|
|
218
195
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
copyFileSync(sourceFile, destFile);
|
|
241
|
-
allInstalledFiles.push(destFile);
|
|
242
|
-
if (existed) { hooksUpdated++; } else { hooksCreated++; }
|
|
196
|
+
if (shouldInstallHooks) {
|
|
197
|
+
const hooksSource = join(PACKAGE_ROOT, 'hooks');
|
|
198
|
+
if (existsSync(hooksSource)) {
|
|
199
|
+
const hooksDestination = join(CLAUDE_HOME, 'hooks');
|
|
200
|
+
const filesToCopy = collectFiles(hooksSource).filter(file => !file.endsWith('hooks.json'));
|
|
201
|
+
let hooksCreated = 0;
|
|
202
|
+
let hooksUpdated = 0;
|
|
203
|
+
for (const sourceFile of filesToCopy) {
|
|
204
|
+
const relativePath = relative(hooksSource, sourceFile);
|
|
205
|
+
const destFile = join(hooksDestination, relativePath);
|
|
206
|
+
mkdirSync(dirname(destFile), { recursive: true });
|
|
207
|
+
const existed = existsSync(destFile);
|
|
208
|
+
copyFileSync(sourceFile, destFile);
|
|
209
|
+
allInstalledFiles.push(destFile);
|
|
210
|
+
if (existed) { hooksUpdated++; } else { hooksCreated++; }
|
|
211
|
+
}
|
|
212
|
+
summary.hookFiles = { created: hooksCreated, updated: hooksUpdated };
|
|
213
|
+
console.log(` Hook files: ${hooksCreated} new, ${hooksUpdated} updated`);
|
|
214
|
+
const groupCount = mergeHooks(pythonCommand);
|
|
215
|
+
summary.hookGroups = groupCount;
|
|
216
|
+
console.log(` Hook groups: ${groupCount} merged into settings.json`);
|
|
243
217
|
}
|
|
244
|
-
summary.hookFiles = { created: hooksCreated, updated: hooksUpdated };
|
|
245
|
-
console.log(` Hook files: ${hooksCreated} new, ${hooksUpdated} updated`);
|
|
246
|
-
const groupCount = mergeHooks(pythonCommand);
|
|
247
|
-
summary.hookGroups = groupCount;
|
|
248
|
-
console.log(` Hook groups: ${groupCount} merged into settings.json`);
|
|
249
218
|
}
|
|
250
219
|
writeManifest(allInstalledFiles);
|
|
251
220
|
console.log(`\nInstalled ${PACKAGE_NAME}:`);
|
|
@@ -259,9 +228,6 @@ function install() {
|
|
|
259
228
|
const { created, updated } = summary.skills;
|
|
260
229
|
console.log(` skills: ${created + updated} files (${created} new, ${updated} updated)`);
|
|
261
230
|
}
|
|
262
|
-
if (summary.siblings) {
|
|
263
|
-
console.log(` workspace siblings: ${summary.siblings} files`);
|
|
264
|
-
}
|
|
265
231
|
if (summary.hookFiles) {
|
|
266
232
|
console.log(` hooks: ${summary.hookFiles.created + summary.hookFiles.updated} files, ${summary.hookGroups} groups in settings.json`);
|
|
267
233
|
}
|
|
@@ -319,15 +285,45 @@ function printHelp() {
|
|
|
319
285
|
${PACKAGE_NAME} - Claude Code development standards installer
|
|
320
286
|
|
|
321
287
|
Usage:
|
|
322
|
-
npx ${PACKAGE_NAME} Install
|
|
323
|
-
npx ${PACKAGE_NAME} --
|
|
288
|
+
npx ${PACKAGE_NAME} Install everything
|
|
289
|
+
npx ${PACKAGE_NAME} --only X Install specific groups
|
|
290
|
+
npx ${PACKAGE_NAME} --uninstall Remove installed files
|
|
324
291
|
npx ${PACKAGE_NAME} --help Show this help
|
|
325
292
|
|
|
293
|
+
Groups:
|
|
294
|
+
core Development standards, hooks, agents, commands
|
|
295
|
+
prompts Prompt engineering tools
|
|
296
|
+
journal Session logging and memory
|
|
297
|
+
research Deep research and citation tools
|
|
298
|
+
|
|
299
|
+
Examples:
|
|
300
|
+
npx ${PACKAGE_NAME} --only prompts
|
|
301
|
+
npx ${PACKAGE_NAME} --only prompts,research
|
|
302
|
+
|
|
326
303
|
Install location: ~/.claude/
|
|
327
304
|
`);
|
|
328
305
|
}
|
|
329
306
|
|
|
330
307
|
const args = process.argv.slice(2);
|
|
331
|
-
if (args.includes('--help') || args.includes('-h'))
|
|
332
|
-
|
|
333
|
-
else
|
|
308
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
309
|
+
printHelp();
|
|
310
|
+
} else if (args.includes('--uninstall')) {
|
|
311
|
+
uninstall();
|
|
312
|
+
} else {
|
|
313
|
+
const onlyIndex = args.indexOf('--only');
|
|
314
|
+
let selectedGroups = null;
|
|
315
|
+
if (onlyIndex !== -1) {
|
|
316
|
+
const onlyValue = args[onlyIndex + 1];
|
|
317
|
+
if (!onlyValue || onlyValue.startsWith('--')) {
|
|
318
|
+
console.error(`ERROR: --only requires a comma-separated list of groups.\nAvailable groups: ${Object.keys(INSTALL_GROUPS).join(', ')}`);
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
selectedGroups = onlyValue.split(',').map(name => name.trim());
|
|
322
|
+
const invalidGroups = selectedGroups.filter(name => !INSTALL_GROUPS[name]);
|
|
323
|
+
if (invalidGroups.length > 0) {
|
|
324
|
+
console.error(`ERROR: Unknown group(s): ${invalidGroups.join(', ')}\nAvailable groups: ${Object.keys(INSTALL_GROUPS).join(', ')}`);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
install(selectedGroups);
|
|
329
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Prompt Workflow Hook Specs
|
|
2
|
+
|
|
3
|
+
Deterministic runtime gates for prompt workflows.
|
|
4
|
+
|
|
5
|
+
## Gate: Execution Intent (PreToolUse Task/Agent)
|
|
6
|
+
|
|
7
|
+
- Hook: `hooks/blocking/agent-execution-intent-gate.py`
|
|
8
|
+
- Event: `PreToolUse`
|
|
9
|
+
- Matcher: `Task|Agent`
|
|
10
|
+
- Fail condition:
|
|
11
|
+
- Missing structured execution intent contract field:
|
|
12
|
+
- `tool_input.execution_intent: explicit|execute|delegate`, or
|
|
13
|
+
- `tool_input.execution_intent_explicit: true`, or
|
|
14
|
+
- `tool_input.metadata.execution_intent: explicit|execute|delegate`
|
|
15
|
+
- Missing required scope anchors in launch payload (always enforced when execution launch is evaluated)
|
|
16
|
+
- Compatibility fallback:
|
|
17
|
+
- Text markers are only accepted when `PROMPT_WORKFLOW_ALLOW_TEXT_INTENT_FALLBACK=1` is set.
|
|
18
|
+
- Fallback usage is logged to stderr.
|
|
19
|
+
- Action: `deny` with concrete missing requirement list.
|
|
20
|
+
|
|
21
|
+
## Gate: Leakage + Checklist + Scope (Stop)
|
|
22
|
+
|
|
23
|
+
- Hook: `hooks/blocking/prompt-workflow-stop-guard.py`
|
|
24
|
+
- Event: `Stop`
|
|
25
|
+
- Fail condition:
|
|
26
|
+
- Raw internal refinement object appears in assistant output without explicit debug intent
|
|
27
|
+
- Prompt-workflow response detected but deterministic checklist container is missing
|
|
28
|
+
- Prompt-workflow response detected and required deterministic checklist rows are missing
|
|
29
|
+
- Prompt-workflow response detected and required scope anchors are missing
|
|
30
|
+
- Prompt-workflow response detected and runtime context-control signals are missing
|
|
31
|
+
- Scope-bound text uses banned ambiguous scope terms
|
|
32
|
+
- Action: `block` with correction reason.
|
|
33
|
+
|
|
34
|
+
## Required Scope Anchors
|
|
35
|
+
|
|
36
|
+
- `target_local_roots`
|
|
37
|
+
- `target_canonical_roots`
|
|
38
|
+
- `target_file_globs`
|
|
39
|
+
- `comparison_basis`
|
|
40
|
+
- `completion_boundary`
|
|
41
|
+
|
|
42
|
+
## Required Deterministic Checklist Rows
|
|
43
|
+
|
|
44
|
+
- `structured_scoped_instructions`
|
|
45
|
+
- `sequential_steps_present`
|
|
46
|
+
- `positive_framing`
|
|
47
|
+
- `acceptance_criteria_defined`
|
|
48
|
+
- `safety_reversibility_language`
|
|
49
|
+
- `no_destructive_shortcuts_guidance`
|
|
50
|
+
- `concrete_output_contract`
|
|
51
|
+
- `scope_boundary_present`
|
|
52
|
+
- `explicit_scope_anchors_present`
|
|
53
|
+
- `all_instructions_artifact_bound`
|
|
54
|
+
- `no_ambiguous_scope_terms`
|
|
55
|
+
- `completion_boundary_measurable`
|
|
56
|
+
- `citation_grounding_policy_present`
|
|
57
|
+
- `source_priority_rules_present`
|
|
58
|
+
|
|
59
|
+
## Runtime Context-Control Signals
|
|
60
|
+
|
|
61
|
+
- `base_minimal_instruction_layer: true`
|
|
62
|
+
- `on_demand_skill_loading: true`
|
|
63
|
+
|
|
64
|
+
These two signals are runtime-checked by the Stop guard whenever a prompt-workflow response is detected.
|
|
65
|
+
|
|
66
|
+
## Deterministic Boundary
|
|
67
|
+
|
|
68
|
+
These hooks enforce only structural/runtime checks. Semantic quality remains in auditor layer.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse gate for Task/Agent execution intent and scope anchors."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from prompt_workflow_gate_core import (
|
|
11
|
+
has_explicit_execution_intent,
|
|
12
|
+
has_structured_execution_intent,
|
|
13
|
+
missing_scope_anchors,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _deny(reason: str) -> None:
|
|
18
|
+
response = {
|
|
19
|
+
"hookSpecificOutput": {
|
|
20
|
+
"hookEventName": "PreToolUse",
|
|
21
|
+
"permissionDecision": "deny",
|
|
22
|
+
"permissionDecisionReason": reason,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
print(json.dumps(response))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
try:
|
|
30
|
+
hook_input = json.load(sys.stdin)
|
|
31
|
+
except json.JSONDecodeError:
|
|
32
|
+
sys.exit(0)
|
|
33
|
+
|
|
34
|
+
tool_name = str(hook_input.get("tool_name", ""))
|
|
35
|
+
if tool_name not in {"Task", "Agent"}:
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
|
|
38
|
+
tool_input = hook_input.get("tool_input", {})
|
|
39
|
+
prompt_text = str(tool_input.get("prompt", ""))
|
|
40
|
+
description = str(tool_input.get("description", ""))
|
|
41
|
+
combined_text = f"{description}\n{prompt_text}"
|
|
42
|
+
|
|
43
|
+
if not has_structured_execution_intent(tool_input):
|
|
44
|
+
allow_text_fallback = os.getenv(
|
|
45
|
+
"PROMPT_WORKFLOW_ALLOW_TEXT_INTENT_FALLBACK", ""
|
|
46
|
+
).strip().lower() in {"1", "true", "yes"}
|
|
47
|
+
text_intent_detected = has_explicit_execution_intent(combined_text)
|
|
48
|
+
if allow_text_fallback and text_intent_detected:
|
|
49
|
+
print(
|
|
50
|
+
"PROMPT-WORKFLOW GATE: compatibility text-intent fallback used; "
|
|
51
|
+
"structured execution intent contract should be provided.",
|
|
52
|
+
file=sys.stderr,
|
|
53
|
+
)
|
|
54
|
+
else:
|
|
55
|
+
fallback_note = ""
|
|
56
|
+
if text_intent_detected:
|
|
57
|
+
print(
|
|
58
|
+
"PROMPT-WORKFLOW GATE: text intent marker detected without structured "
|
|
59
|
+
"execution intent contract.",
|
|
60
|
+
file=sys.stderr,
|
|
61
|
+
)
|
|
62
|
+
fallback_note = " Legacy text marker was detected but is not sufficient."
|
|
63
|
+
_deny(
|
|
64
|
+
"BLOCKED: Missing structured execution intent signal for Agent/Task launch. "
|
|
65
|
+
"Provide `tool_input.execution_intent: explicit` or "
|
|
66
|
+
"`tool_input.execution_intent_explicit: true`."
|
|
67
|
+
+ fallback_note
|
|
68
|
+
)
|
|
69
|
+
sys.exit(0)
|
|
70
|
+
|
|
71
|
+
missing_anchors = missing_scope_anchors(combined_text)
|
|
72
|
+
if missing_anchors:
|
|
73
|
+
_deny(
|
|
74
|
+
"BLOCKED: Scope anchors missing for prompt workflow execution: "
|
|
75
|
+
+ ", ".join(missing_anchors)
|
|
76
|
+
)
|
|
77
|
+
sys.exit(0)
|
|
78
|
+
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
main()
|