claude-dev-env 1.8.0 → 1.8.2
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/bin/install.mjs
CHANGED
|
@@ -28,6 +28,13 @@ const INSTALL_GROUPS = {
|
|
|
28
28
|
prompts: {
|
|
29
29
|
description: 'Prompt engineering tools',
|
|
30
30
|
skills: ['prompt-generator', 'agent-prompt'],
|
|
31
|
+
includeHookFiles: [
|
|
32
|
+
'blocking/agent-execution-intent-gate.py',
|
|
33
|
+
'blocking/prompt_workflow_gate_core.py',
|
|
34
|
+
'blocking/prompt-workflow-stop-guard.py',
|
|
35
|
+
'HOOK_SPECS_PROMPT_WORKFLOW.md',
|
|
36
|
+
],
|
|
37
|
+
includeRules: ['prompt-workflow-context-controls.md'],
|
|
31
38
|
},
|
|
32
39
|
journal: {
|
|
33
40
|
description: 'Session logging and memory',
|
|
@@ -162,20 +169,45 @@ function install(selectedGroups) {
|
|
|
162
169
|
const allowedDirectories = selectedGroups
|
|
163
170
|
? new Set(selectedGroups.flatMap(groupName => INSTALL_GROUPS[groupName].includeDirectories || []))
|
|
164
171
|
: null;
|
|
165
|
-
const
|
|
172
|
+
const shouldInstallAllHooks = selectedGroups
|
|
166
173
|
? selectedGroups.some(groupName => INSTALL_GROUPS[groupName].includeAllHooks)
|
|
167
174
|
: true;
|
|
175
|
+
const allowedHookFiles = selectedGroups
|
|
176
|
+
? new Set(selectedGroups.flatMap(groupName => INSTALL_GROUPS[groupName].includeHookFiles || []))
|
|
177
|
+
: null;
|
|
178
|
+
const allowedRules = selectedGroups
|
|
179
|
+
? new Set(selectedGroups.flatMap(groupName => INSTALL_GROUPS[groupName].includeRules || []))
|
|
180
|
+
: null;
|
|
168
181
|
|
|
169
182
|
const allInstalledFiles = [];
|
|
170
183
|
const summary = {};
|
|
171
184
|
for (const directory of CONTENT_DIRECTORIES) {
|
|
172
|
-
|
|
185
|
+
const hasFullAccess = !allowedDirectories || allowedDirectories.has(directory);
|
|
186
|
+
const hasPartialRules = directory === 'rules' && allowedRules && allowedRules.size > 0;
|
|
187
|
+
if (!hasFullAccess && !hasPartialRules) continue;
|
|
173
188
|
const sourceDir = join(PACKAGE_ROOT, directory);
|
|
174
189
|
if (!existsSync(sourceDir)) continue;
|
|
175
190
|
const destDir = join(CLAUDE_HOME, directory);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
191
|
+
if (hasFullAccess) {
|
|
192
|
+
const stats = copyTree(sourceDir, destDir);
|
|
193
|
+
summary[directory] = stats;
|
|
194
|
+
allInstalledFiles.push(...stats.paths);
|
|
195
|
+
} else if (hasPartialRules) {
|
|
196
|
+
let rulesCreated = 0;
|
|
197
|
+
let rulesUpdated = 0;
|
|
198
|
+
for (const ruleFile of allowedRules) {
|
|
199
|
+
const sourcePath = join(sourceDir, ruleFile);
|
|
200
|
+
if (!existsSync(sourcePath)) continue;
|
|
201
|
+
const destPath = join(destDir, ruleFile);
|
|
202
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
203
|
+
const existed = existsSync(destPath);
|
|
204
|
+
copyFileSync(sourcePath, destPath);
|
|
205
|
+
allInstalledFiles.push(destPath);
|
|
206
|
+
if (existed) { rulesUpdated++; } else { rulesCreated++; }
|
|
207
|
+
console.log(` ${existed ? '\u21bb' : '\u2713'} ${join(directory, ruleFile)} (${existed ? 'updated' : 'new'})`);
|
|
208
|
+
}
|
|
209
|
+
summary[directory] = { created: rulesCreated, updated: rulesUpdated, paths: [] };
|
|
210
|
+
}
|
|
179
211
|
}
|
|
180
212
|
const skillsSource = join(PACKAGE_ROOT, 'skills');
|
|
181
213
|
if (existsSync(skillsSource)) {
|
|
@@ -193,11 +225,18 @@ function install(selectedGroups) {
|
|
|
193
225
|
summary.skills = { created: skillsCreated, updated: skillsUpdated, paths: skillPaths };
|
|
194
226
|
allInstalledFiles.push(...skillPaths);
|
|
195
227
|
}
|
|
196
|
-
|
|
228
|
+
const shouldInstallAnyHooks = shouldInstallAllHooks || (allowedHookFiles && allowedHookFiles.size > 0);
|
|
229
|
+
if (shouldInstallAnyHooks) {
|
|
197
230
|
const hooksSource = join(PACKAGE_ROOT, 'hooks');
|
|
198
231
|
if (existsSync(hooksSource)) {
|
|
199
232
|
const hooksDestination = join(CLAUDE_HOME, 'hooks');
|
|
200
|
-
const filesToCopy = collectFiles(hooksSource)
|
|
233
|
+
const filesToCopy = collectFiles(hooksSource)
|
|
234
|
+
.filter(file => !file.endsWith('hooks.json'))
|
|
235
|
+
.filter(file => {
|
|
236
|
+
if (shouldInstallAllHooks) return true;
|
|
237
|
+
const relativePath = relative(hooksSource, file).replace(/\\/g, '/');
|
|
238
|
+
return allowedHookFiles.has(relativePath);
|
|
239
|
+
});
|
|
201
240
|
let hooksCreated = 0;
|
|
202
241
|
let hooksUpdated = 0;
|
|
203
242
|
for (const sourceFile of filesToCopy) {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
-
import os
|
|
8
7
|
import sys
|
|
9
8
|
|
|
10
9
|
from prompt_workflow_gate_core import (
|
|
@@ -41,30 +40,11 @@ def main() -> None:
|
|
|
41
40
|
combined_text = f"{description}\n{prompt_text}"
|
|
42
41
|
|
|
43
42
|
if not has_structured_execution_intent(tool_input):
|
|
44
|
-
|
|
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."
|
|
43
|
+
if not has_explicit_execution_intent(combined_text):
|
|
63
44
|
_deny(
|
|
64
45
|
"BLOCKED: Missing structured execution intent signal for Agent/Task launch. "
|
|
65
46
|
"Provide `tool_input.execution_intent: explicit` or "
|
|
66
47
|
"`tool_input.execution_intent_explicit: true`."
|
|
67
|
-
+ fallback_note
|
|
68
48
|
)
|
|
69
49
|
sys.exit(0)
|
|
70
50
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Tests for agent-execution-intent-gate hook."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import os
|
|
5
4
|
import subprocess
|
|
6
5
|
import sys
|
|
7
6
|
from pathlib import Path
|
|
@@ -31,18 +30,23 @@ def test_denies_task_without_explicit_intent_marker() -> None:
|
|
|
31
30
|
assert "structured execution intent signal" in response["hookSpecificOutput"]["permissionDecisionReason"]
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
def
|
|
33
|
+
def test_allows_phrase_marker_with_scope_anchors() -> None:
|
|
35
34
|
payload = {
|
|
36
35
|
"tool_name": "Task",
|
|
37
36
|
"tool_input": {
|
|
38
|
-
"prompt":
|
|
37
|
+
"prompt": (
|
|
38
|
+
"execution_intent: explicit\n"
|
|
39
|
+
"target_local_roots\n"
|
|
40
|
+
"target_canonical_roots\n"
|
|
41
|
+
"target_file_globs\n"
|
|
42
|
+
"comparison_basis\n"
|
|
43
|
+
"completion_boundary\n"
|
|
44
|
+
),
|
|
39
45
|
"description": "explicit delegation intent",
|
|
40
46
|
},
|
|
41
47
|
}
|
|
42
48
|
result = _run_hook(payload)
|
|
43
|
-
|
|
44
|
-
assert response["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
45
|
-
assert "Missing structured execution intent signal" in response["hookSpecificOutput"]["permissionDecisionReason"]
|
|
49
|
+
assert result.stdout.strip() == ""
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
def test_denies_when_scope_anchors_missing() -> None:
|
|
@@ -78,29 +82,3 @@ def test_allows_when_intent_and_scope_anchors_present() -> None:
|
|
|
78
82
|
result = _run_hook(payload)
|
|
79
83
|
assert result.stdout.strip() == ""
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
def test_text_intent_fallback_is_logged_and_allowed_when_enabled() -> None:
|
|
83
|
-
payload = {
|
|
84
|
-
"tool_name": "Task",
|
|
85
|
-
"tool_input": {
|
|
86
|
-
"description": "delegate now",
|
|
87
|
-
"prompt": (
|
|
88
|
-
"execution_intent: explicit\n"
|
|
89
|
-
"target_local_roots\n"
|
|
90
|
-
"target_canonical_roots\n"
|
|
91
|
-
"target_file_globs\n"
|
|
92
|
-
"comparison_basis\n"
|
|
93
|
-
"completion_boundary\n"
|
|
94
|
-
),
|
|
95
|
-
},
|
|
96
|
-
}
|
|
97
|
-
result = subprocess.run(
|
|
98
|
-
[sys.executable, str(SCRIPT_PATH)],
|
|
99
|
-
input=json.dumps(payload),
|
|
100
|
-
text=True,
|
|
101
|
-
capture_output=True,
|
|
102
|
-
check=False,
|
|
103
|
-
env={**os.environ, "PROMPT_WORKFLOW_ALLOW_TEXT_INTENT_FALLBACK": "1"},
|
|
104
|
-
)
|
|
105
|
-
assert result.stdout.strip() == ""
|
|
106
|
-
assert "compatibility text-intent fallback used" in result.stderr
|
package/package.json
CHANGED
|
@@ -178,12 +178,36 @@ Required section list is immutable for this pipeline: `role`, `context`, `instru
|
|
|
178
178
|
|
|
179
179
|
### 11. Internal refinement object format (required by default mode)
|
|
180
180
|
|
|
181
|
-
When step 10 is active (default), build
|
|
181
|
+
When step 10 is active (default), build the refinement and audit state internally. Present the final merged prompt and a compact audit summary to the user. Keep the full internal object private unless the user explicitly asks for debug details.
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
**Default user-facing audit output (compact table):**
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
```
|
|
186
|
+
**Audit: <overall_status>** | checklist_results: <pass_count>/14
|
|
187
|
+
|
|
188
|
+
| Check | Status |
|
|
189
|
+
|-------|--------|
|
|
190
|
+
| structured_scoped_instructions | pass |
|
|
191
|
+
| sequential_steps_present | pass |
|
|
192
|
+
| positive_framing | pass |
|
|
193
|
+
| acceptance_criteria_defined | pass |
|
|
194
|
+
| safety_reversibility_language | pass |
|
|
195
|
+
| no_destructive_shortcuts_guidance | pass |
|
|
196
|
+
| concrete_output_contract | pass |
|
|
197
|
+
| scope_boundary_present | pass |
|
|
198
|
+
| explicit_scope_anchors_present | pass |
|
|
199
|
+
| all_instructions_artifact_bound | pass |
|
|
200
|
+
| no_ambiguous_scope_terms | pass |
|
|
201
|
+
| completion_boundary_measurable | pass |
|
|
202
|
+
| citation_grounding_policy_present | pass |
|
|
203
|
+
| source_priority_rules_present | pass |
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Replace `<overall_status>` with `pass` or `fail`. Replace `<pass_count>` with the actual count. Replace each row's `pass` with `fail` where applicable.
|
|
207
|
+
|
|
208
|
+
**Debug mode (full JSON, shown only when user requests debug details):**
|
|
209
|
+
|
|
210
|
+
When the user explicitly asks for debug details ("show debug", "show internal", "raw internal object", "pipeline object"), output the full internal object:
|
|
187
211
|
|
|
188
212
|
```json
|
|
189
213
|
{
|
|
@@ -213,13 +237,21 @@ Do not expose the raw internal object by default.
|
|
|
213
237
|
"corrective_edits",
|
|
214
238
|
"retry_count"
|
|
215
239
|
]
|
|
240
|
+
},
|
|
241
|
+
"checklist_results": {
|
|
242
|
+
"<row_name>": {
|
|
243
|
+
"status": "pass|fail",
|
|
244
|
+
"evidence_quote": "exact quote used for verification",
|
|
245
|
+
"source_ref": "URL or local path",
|
|
246
|
+
"fix_if_fail": "concrete edit text (empty only if pass)"
|
|
247
|
+
}
|
|
216
248
|
}
|
|
217
249
|
}
|
|
218
250
|
```
|
|
219
251
|
|
|
220
252
|
### 12. Deterministic compliance checklist fields (audit reports all)
|
|
221
253
|
|
|
222
|
-
If step 10 is active (default), the
|
|
254
|
+
If step 10 is active (default), the audit must evaluate all 14 fields below. Each row name must appear as a literal substring in the user-facing output (the compact table satisfies this).
|
|
223
255
|
|
|
224
256
|
- `structured_scoped_instructions`
|
|
225
257
|
- `sequential_steps_present`
|
|
@@ -236,12 +268,14 @@ If step 10 is active (default), the final audit report must include all fields b
|
|
|
236
268
|
- `citation_grounding_policy_present`
|
|
237
269
|
- `source_priority_rules_present`
|
|
238
270
|
|
|
239
|
-
For each checklist row,
|
|
271
|
+
For each checklist row, maintain internally:
|
|
240
272
|
- `status`: `pass|fail`
|
|
241
273
|
- `evidence_quote`: exact quote used for verification
|
|
242
274
|
- `source_ref`: URL or local path
|
|
243
275
|
- `fix_if_fail`: concrete edit text (empty only if pass)
|
|
244
276
|
|
|
277
|
+
The compact table (step 11) shows `status` per row. The `evidence_quote`, `source_ref`, and `fix_if_fail` fields are internal-only and appear only in debug mode.
|
|
278
|
+
|
|
245
279
|
Scope quality rule for generated prompts:
|
|
246
280
|
- Bind every major instruction to explicit artifacts from the scope block.
|
|
247
281
|
- Prefer concrete references (paths/globs/comparisons) over context-relative wording.
|