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 shouldInstallHooks = selectedGroups
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
- if (allowedDirectories && !allowedDirectories.has(directory)) continue;
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
- const stats = copyTree(sourceDir, destDir);
177
- summary[directory] = stats;
178
- allInstalledFiles.push(...stats.paths);
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
- if (shouldInstallHooks) {
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).filter(file => !file.endsWith('hooks.json'));
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
- 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."
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 test_denies_phrase_marker_without_structured_intent_contract() -> None:
33
+ def test_allows_phrase_marker_with_scope_anchors() -> None:
35
34
  payload = {
36
35
  "tool_name": "Task",
37
36
  "tool_input": {
38
- "prompt": "execution_intent: explicit and delegate now",
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
- response = json.loads(result.stdout)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 this object internally to drive refinement and audit.
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
- Present the final merged prompt and audit result to the user.
183
+ **Default user-facing audit output (compact table):**
184
184
 
185
- Share this raw object only when the user explicitly asks for debug details.
186
- Do not expose the raw internal object by default.
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 final audit report must include all fields below with `pass|fail`, direct quote evidence, and source reference:
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, require:
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.