omniconductor 0.3.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +219 -0
  3. package/NOTICE +11 -0
  4. package/README.md +526 -0
  5. package/THIRD_PARTY_NOTICES.md +31 -0
  6. package/TRADEMARKS.md +26 -0
  7. package/VISION.md +106 -0
  8. package/adapters/README.md +121 -0
  9. package/adapters/claude/README.md +102 -0
  10. package/adapters/claude/SUPPORTED-FEATURES.md +66 -0
  11. package/adapters/claude/hookify-templates/.recipe-scoped +9 -0
  12. package/adapters/claude/hookify-templates/README.md +93 -0
  13. package/adapters/claude/hookify-templates/block-completion-claim-without-push.local.md.template +56 -0
  14. package/adapters/claude/hookify-templates/block-direct-push-protected-branch.local.md.template +18 -0
  15. package/adapters/claude/hookify-templates/block-force-push-protected-branch.local.md.template +42 -0
  16. package/adapters/claude/hookify-templates/block-server-secret-in-client.local.md.template +36 -0
  17. package/adapters/claude/hookify-templates/warn-any-type-added.local.md.template +44 -0
  18. package/adapters/claude/hookify-templates/warn-commit-without-pre-commit-review.local.md.template +52 -0
  19. package/adapters/claude/hookify-templates/warn-console-direct.local.md.template +55 -0
  20. package/adapters/claude/hookify-templates/warn-create-table-without-access-control.local.md.template +35 -0
  21. package/adapters/claude/hookify-templates/warn-current-work-without-remaining-tasks.local.md.template +36 -0
  22. package/adapters/claude/hookify-templates/warn-gh-pr-create-without-pre-merge-review.local.md.template +38 -0
  23. package/adapters/claude/hookify-templates/warn-hardcoded-text-without-i18n-key.local.md.template +21 -0
  24. package/adapters/claude/hookify-templates/warn-on-gh-pr-merge.local.md.template +33 -0
  25. package/adapters/claude/hookify-templates/warn-plan-spec-without-remaining-tasks.local.md.template +44 -0
  26. package/adapters/claude/hookify-templates/warn-raw-hex-instead-of-token.local.md.template +21 -0
  27. package/adapters/claude/hookify-templates/warn-security-definer-without-search-path.local.md.template +30 -0
  28. package/adapters/claude/hookify-templates/warn-stop-commit-without-current-work.local.md.template +47 -0
  29. package/adapters/claude/hookify-templates/warn-user-manual-completion.local.md.template +40 -0
  30. package/adapters/claude/transform-spec.md +140 -0
  31. package/adapters/claude/transform.sh +1169 -0
  32. package/adapters/codex/README.md +87 -0
  33. package/adapters/codex/SUPPORTED-FEATURES.md +66 -0
  34. package/adapters/codex/transform-spec.md +90 -0
  35. package/adapters/codex/transform.sh +748 -0
  36. package/adapters/copilot/README.md +88 -0
  37. package/adapters/copilot/SUPPORTED-FEATURES.md +73 -0
  38. package/adapters/copilot/transform-spec.md +111 -0
  39. package/adapters/copilot/transform.sh +757 -0
  40. package/adapters/cursor/README.md +87 -0
  41. package/adapters/cursor/SUPPORTED-FEATURES.md +90 -0
  42. package/adapters/cursor/transform-spec.md +124 -0
  43. package/adapters/cursor/transform.sh +731 -0
  44. package/adapters/gemini/README.md +93 -0
  45. package/adapters/gemini/SUPPORTED-FEATURES.md +80 -0
  46. package/adapters/gemini/transform-spec.md +102 -0
  47. package/adapters/gemini/transform.sh +844 -0
  48. package/adapters/windsurf/README.md +93 -0
  49. package/adapters/windsurf/SUPPORTED-FEATURES.md +67 -0
  50. package/adapters/windsurf/transform-spec.md +99 -0
  51. package/adapters/windsurf/transform.sh +727 -0
  52. package/bin/omniconductor.js +130 -0
  53. package/core/README.md +64 -0
  54. package/core/anti-patterns/README.md +142 -0
  55. package/core/anti-patterns/dynamic-system-prompt.md +113 -0
  56. package/core/anti-patterns/frequent-rule-file-edit.md +83 -0
  57. package/core/anti-patterns/large-file-read-no-range.md +90 -0
  58. package/core/anti-patterns/no-sub-agent-dispatch.md +89 -0
  59. package/core/anti-patterns/single-monolithic-rule-file.md +99 -0
  60. package/core/anti-patterns/skill-eager-load.md +83 -0
  61. package/core/anti-patterns/tool-call-spam.md +93 -0
  62. package/core/docs-templates/CURRENT_WORK.md +64 -0
  63. package/core/docs-templates/INDEX.md +57 -0
  64. package/core/docs-templates/PLANS.md +44 -0
  65. package/core/docs-templates/README.md +40 -0
  66. package/core/docs-templates/REMAINING_TASKS.md +43 -0
  67. package/core/docs-templates/TASKS.md +51 -0
  68. package/core/docs-templates/specs/_example.md +121 -0
  69. package/core/hooks/README.md +62 -0
  70. package/core/hooks/pretool-agent-routing.sh.template +97 -0
  71. package/core/hooks/pretool-commit-current-work-check.sh.template +103 -0
  72. package/core/hooks/pretool-commit-test-coverage-check.sh.template +116 -0
  73. package/core/hooks/pretool-large-file-read-guard.sh.template +117 -0
  74. package/core/hooks/stop-cache-hit-baseline-check.sh.template +133 -0
  75. package/core/hooks/stop-r6-review-check.sh.template +80 -0
  76. package/core/hooks/stop-session-log-check.sh.template +101 -0
  77. package/core/hooks/stop-trajectory-log.sh.template +96 -0
  78. package/core/memory-pattern/EXAMPLES.md +114 -0
  79. package/core/memory-pattern/README.md +133 -0
  80. package/core/recipes/README.md +46 -0
  81. package/core/recipes/auto-mock-data.md +82 -0
  82. package/core/recipes/branch-strategy.md +100 -0
  83. package/core/recipes/coding-conventions.md +123 -0
  84. package/core/recipes/database-discipline.md +65 -0
  85. package/core/recipes/debugging.md +143 -0
  86. package/core/recipes/design-system.md +30 -0
  87. package/core/recipes/i18n.md +118 -0
  88. package/core/recipes/monorepo.md +101 -0
  89. package/core/recipes/self-improvement.md +61 -0
  90. package/core/recipes/tdd.md +128 -0
  91. package/core/recipes/web-mobile-parity.md +87 -0
  92. package/core/reflector/SCHEDULING.md +83 -0
  93. package/core/reflector/prune-lessons.sh +105 -0
  94. package/core/reflector/reflect-brief.md +11 -0
  95. package/core/reflector/reflect.command.md +13 -0
  96. package/core/reflector/run-weekly.sh +54 -0
  97. package/core/reflector/trajectory-log.sh +55 -0
  98. package/core/roles/README.md +61 -0
  99. package/core/roles/builder.md +75 -0
  100. package/core/roles/designer.md +93 -0
  101. package/core/roles/helper.md +70 -0
  102. package/core/roles/planner.md +123 -0
  103. package/core/roles/reflector.md +72 -0
  104. package/core/roles/reviewer.md +104 -0
  105. package/core/roles/scribe.md +89 -0
  106. package/core/universal-rules/README.md +71 -0
  107. package/core/universal-rules/meta-discipline.md +292 -0
  108. package/core/universal-rules/operations.md +162 -0
  109. package/core/universal-rules/quality-gates.md +191 -0
  110. package/core/universal-rules/spec-as-you-go.md +123 -0
  111. package/core/universal-rules/workflow.md +153 -0
  112. package/core/workflow/PHASES.md +134 -0
  113. package/core/workflow/README.md +56 -0
  114. package/docs/MANUAL-INSTALL.md +456 -0
  115. package/package.json +52 -0
  116. package/tools/check-framework-purity.sh +91 -0
  117. package/tools/measure-tokens.sh +169 -0
  118. package/tools/validate-adapter-output.sh +611 -0
@@ -0,0 +1,1169 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # CONDUCTOR — Claude Code adapter transform.sh
4
+ #
5
+ # Reads core/ assets and writes them into a target project as native Claude Code
6
+ # files: .claude/agents/, .claude/rules/, .claude/hooks/, CLAUDE.md, docs/*.
7
+ #
8
+ # Usage:
9
+ # bash adapters/claude/transform.sh <target-project> [--recipes=<comma-list>] [--dry-run]
10
+ # [--measure-baseline] [--no-prompt] [--check-anti-patterns]
11
+ # bash adapters/claude/transform.sh <target-project> --uninstall [--dry-run] [--force]
12
+ #
13
+ # Examples:
14
+ # bash adapters/claude/transform.sh ~/Projects/my-app
15
+ # bash adapters/claude/transform.sh ~/Projects/my-app --recipes=i18n,monorepo,coding-conventions
16
+ # bash adapters/claude/transform.sh /tmp/test-project --dry-run
17
+ # bash adapters/claude/transform.sh . --measure-baseline
18
+ # bash adapters/claude/transform.sh . --no-prompt --recipes=coding-conventions
19
+ # bash adapters/claude/transform.sh . --uninstall # revert install
20
+ # bash adapters/claude/transform.sh . --uninstall --dry-run # preview revert
21
+ # bash adapters/claude/transform.sh . --uninstall --force # bypass safety checks
22
+ #
23
+ # Layer 2 transformation:
24
+ # core/universal-rules/*.md → <target>/.claude/rules/*.md (paths frontmatter injected)
25
+ # core/roles/{6 files}.md → <target>/.claude/agents/*.md (Claude name/description/model frontmatter)
26
+ # core/recipes/*.md (selected) → <target>/.claude/rules/*.md
27
+ # core/hooks/*.sh.template → <target>/.claude/hooks/*.sh (placeholder substitution + chmod +x)
28
+ # core/docs-templates/*.md → <target>/docs/*.md (CURRENT_WORK, REMAINING_TASKS, etc.)
29
+ # <synthesized> → <target>/CLAUDE.md (orchestrator manual + role table)
30
+
31
+ set -eu
32
+
33
+ # ----- arg parsing --------------------------------------------------------
34
+
35
+ TARGET=""
36
+ RECIPES=""
37
+ DRY_RUN="false"
38
+ MEASURE_BASELINE="false"
39
+ NO_PROMPT="false"
40
+ CHECK_ANTI_PATTERNS="false"
41
+ UNINSTALL="false"
42
+ FORCE="false"
43
+
44
+ # Onboarding wizard state (set during interactive flow or --no-prompt defaults)
45
+ WIZARD_APPLY_RULES="true"
46
+ WIZARD_SHOW_ANTI_PATTERNS="false"
47
+
48
+ while [ $# -gt 0 ]; do
49
+ case "$1" in
50
+ --recipes=*) RECIPES="${1#--recipes=}" ;;
51
+ --dry-run) DRY_RUN="true" ;;
52
+ --measure-baseline) MEASURE_BASELINE="true" ;;
53
+ --no-prompt) NO_PROMPT="true" ;;
54
+ --check-anti-patterns) CHECK_ANTI_PATTERNS="true"; WIZARD_SHOW_ANTI_PATTERNS="true" ;;
55
+ --uninstall|--rollback) UNINSTALL="true" ;;
56
+ --force) FORCE="true" ;;
57
+ --help|-h)
58
+ /bin/cat <<EOF
59
+ Usage: bash adapters/claude/transform.sh <target-project> [options]
60
+
61
+ Options:
62
+ --recipes=A,B,C Comma-separated list of recipes to install
63
+ --dry-run Preview only — no files written
64
+ --measure-baseline After install, measure cache token baseline (opt-in)
65
+ --no-prompt Skip all interactive prompts; apply sensible defaults (CI-safe)
66
+ --check-anti-patterns Print anti-pattern catalog and pause 5 seconds
67
+ --uninstall Revert a previous install using <target>/.conductor-manifest.json
68
+ (alias: --rollback). Restores backups when present, deletes
69
+ Conductor-emitted files when none. Customizations not in the
70
+ manifest are preserved.
71
+ --force Bypass uninstall safety checks (active worktrees, missing manifest)
72
+
73
+ Recipes available: web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement
74
+ EOF
75
+ exit 0
76
+ ;;
77
+ *)
78
+ if [ -z "$TARGET" ]; then
79
+ TARGET="$1"
80
+ else
81
+ echo "Unknown argument: $1" >&2
82
+ exit 1
83
+ fi
84
+ ;;
85
+ esac
86
+ shift
87
+ done
88
+
89
+ if [ -z "$TARGET" ]; then
90
+ echo "Error: target-project path is required." >&2
91
+ echo "Usage: bash adapters/claude/transform.sh <target-project> [--recipes=...]" >&2
92
+ exit 1
93
+ fi
94
+
95
+ # Resolve CONDUCTOR root (where this script lives: adapters/claude/).
96
+ CONDUCTOR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
97
+ CORE_ROOT="$CONDUCTOR_ROOT/core"
98
+ TOOLS_ROOT="$CONDUCTOR_ROOT/tools"
99
+ [ -d "$CORE_ROOT" ] || { echo "Error: core/ not found at $CORE_ROOT" >&2; exit 1; }
100
+
101
+ if [ "$DRY_RUN" = "true" ]; then
102
+ mkdir -p "$TARGET"
103
+ fi
104
+ TARGET_ABS="$(cd "$TARGET" 2>/dev/null && pwd)" || { echo "Error: target directory does not exist or is not a directory: $TARGET" >&2; exit 1; }
105
+
106
+ # ----- helpers ------------------------------------------------------------
107
+
108
+ log() {
109
+ if [ "$DRY_RUN" = "true" ]; then
110
+ echo "[dry-run] $*"
111
+ else
112
+ echo "[conductor] $*"
113
+ fi
114
+ }
115
+
116
+ mkdir_if_real() {
117
+ if [ "$DRY_RUN" = "true" ]; then
118
+ log "would mkdir -p $1"
119
+ else
120
+ mkdir -p "$1"
121
+ fi
122
+ }
123
+
124
+ write_file() {
125
+ # write_file <dest> <content>
126
+ local dest="$1"
127
+ shift
128
+ if [ "$DRY_RUN" = "true" ]; then
129
+ log "would write $dest ($(echo "$*" | /usr/bin/wc -c | /usr/bin/tr -d ' ') bytes)"
130
+ else
131
+ /bin/cat > "$dest" <<EOF
132
+ $*
133
+ EOF
134
+ fi
135
+ }
136
+
137
+ copy_with_paths_frontmatter() {
138
+ # copy_with_paths_frontmatter <src> <dest> <paths-glob>
139
+ local src="$1" dest="$2" paths="$3"
140
+ if [ "$DRY_RUN" = "true" ]; then
141
+ log "would copy $src -> $dest with paths: $paths"
142
+ return
143
+ fi
144
+ # Inject paths frontmatter at top of file.
145
+ /bin/cat > "$dest" <<EOF
146
+ ---
147
+ paths:
148
+ - "$paths"
149
+ ---
150
+
151
+ EOF
152
+ /bin/cat "$src" >> "$dest"
153
+ }
154
+
155
+ substitute_template() {
156
+ # substitute_template <src> <dest>
157
+ # Replaces ${PLACEHOLDER} with environment values; falls back to defaults documented in template.
158
+ local src="$1" dest="$2"
159
+ if [ "$DRY_RUN" = "true" ]; then
160
+ log "would compile template $src -> $dest"
161
+ return
162
+ fi
163
+ /usr/bin/sed \
164
+ -e "s|\${CONDUCTOR_PROJECT_DIR}|${TARGET_ABS}|g" \
165
+ -e "s|\${CONDUCTOR_PROTECTED_BRANCHES}|develop\|main\|release|g" \
166
+ -e "s|\${CONDUCTOR_DIRECT_PUSH_BRANCHES}|develop\|main\|release\|HEAD|g" \
167
+ -e "s|\${CONDUCTOR_FORBIDDEN_SUBAGENT_TYPES}|general-purpose|g" \
168
+ -e "s|\${CONDUCTOR_COOLDOWN_SECONDS}|1800|g" \
169
+ -e "s|\${CONDUCTOR_STALE_MINUTES}|30|g" \
170
+ -e "s|\${CONDUCTOR_SOURCE_GLOB}|ts\|tsx|g" \
171
+ -e "s|\${CONDUCTOR_CURRENT_WORK_PATH}|${CONDUCTOR_CURRENT_WORK_PATH:-docs/CURRENT_WORK.md}|g" \
172
+ -e "s|\${CONDUCTOR_SPEC_PATH}|docs/specs/|g" \
173
+ -e "s|\${CONDUCTOR_REVIEW_COMMAND}|/code-review|g" \
174
+ "$src" > "$dest"
175
+ chmod +x "$dest"
176
+ }
177
+
178
+ substitute_hookify_template() {
179
+ # substitute_hookify_template <src> <dest>
180
+ # Variant of substitute_template for hookify .local.md files. Same placeholder vocabulary,
181
+ # but the output is a Markdown rule definition (NOT executable), so chmod +x is omitted.
182
+ # Adds hookify-specific placeholders (CURRENT_WORK_PATH, REMAINING_TASKS_PATH, PROJECT_NAME)
183
+ # used only by adapters/claude/hookify-templates/*.local.md.template.
184
+ local src="$1" dest="$2"
185
+ if [ "$DRY_RUN" = "true" ]; then
186
+ log "would compile hookify template $src -> $dest"
187
+ return
188
+ fi
189
+ # SERVER_SECRET_PATTERN default uses generic credential env-var name conventions (overridable), not a vendor reference.
190
+ # It is intentionally narrow (matches *_SECRET_KEY / *_PRIVATE_KEY / SERVICE_ROLE etc., NOT bare SECRET / API_KEY / JWT_SECRET)
191
+ # to avoid false positives on safe client vars; adopters broaden it via the CONDUCTOR_SERVER_SECRET_PATTERN env var.
192
+ /usr/bin/sed \
193
+ -e "s|\${CONDUCTOR_PROJECT_DIR}|${TARGET_ABS}|g" \
194
+ -e "s|\${CONDUCTOR_PROTECTED_BRANCHES}|${CONDUCTOR_PROTECTED_BRANCHES:-main\|release}|g" \
195
+ -e "s|\${CONDUCTOR_CURRENT_WORK_PATH}|${CONDUCTOR_CURRENT_WORK_PATH:-docs/CURRENT_WORK.md}|g" \
196
+ -e "s|\${CONDUCTOR_REMAINING_TASKS_PATH}|${CONDUCTOR_REMAINING_TASKS_PATH:-docs/REMAINING_TASKS.md}|g" \
197
+ -e "s|\${CONDUCTOR_SOURCE_GLOB}|${CONDUCTOR_SOURCE_GLOB:-apps/.*\\\\.(ts\|tsx)\$}|g" \
198
+ -e "s|\${CONDUCTOR_CLIENT_GLOB}|${CONDUCTOR_CLIENT_GLOB:-(src/(components\|hooks\|pages\|ui)\|public)/.*\\\\.(ts\|tsx\|js\|jsx)\$}|g" \
199
+ -e "s|\${CONDUCTOR_SERVER_SECRET_PATTERN}|${CONDUCTOR_SERVER_SECRET_PATTERN:-(SERVICE_ROLE_KEY\|SERVICE_ROLE\|_SECRET_KEY\|_PRIVATE_KEY\|ADMIN_API_KEY\|SECRET_ACCESS_KEY)}|g" \
200
+ -e "s|\${CONDUCTOR_PROJECT_NAME}|${CONDUCTOR_PROJECT_NAME:-your-project}|g" \
201
+ -e "s|\${CONDUCTOR_REVIEW_COMMAND}|/code-review|g" \
202
+ "$src" > "$dest"
203
+ }
204
+
205
+ # Read a single metric line from measure-tokens.sh output.
206
+ # Usage: extract_metric <label-substring> <output-text>
207
+ extract_metric() {
208
+ local label="$1" text="$2"
209
+ echo "$text" | /usr/bin/grep "$label" | /usr/bin/awk -F': ' '{print $2}' | /usr/bin/tr -d ' '
210
+ }
211
+
212
+ # backup_if_exists <dest>
213
+ # If <dest> is a regular file, copy it to <dest>.conductor-backup-<timestamp> before any
214
+ # downstream step overwrites it. Honors DRY_RUN (logs only). Idempotent across re-installs:
215
+ # every invocation creates a fresh timestamped backup, so two installs in the same second
216
+ # still differ by file (the second uses an incremented suffix only if the first second
217
+ # happens to collide). The timestamp resolution is 1s; collision in <1s is acceptable since
218
+ # transform.sh runs sequentially.
219
+ #
220
+ # Origin: ADR-019 (audit P0b — silent-overwrite risk on universal-rules / docs-templates /
221
+ # hooks). Existing CLAUDE.md backup pattern generalized to every emit target.
222
+ backup_if_exists() {
223
+ local dest="$1"
224
+ if [ -f "$dest" ]; then
225
+ local ts
226
+ ts="$(/bin/date +%Y%m%d-%H%M%S)"
227
+ local backup="${dest}.conductor-backup-${ts}"
228
+ if [ "$DRY_RUN" = "true" ]; then
229
+ log "would back up existing $dest -> $backup"
230
+ else
231
+ /bin/cp "$dest" "$backup"
232
+ log " backed up existing $dest -> $backup"
233
+ fi
234
+ fi
235
+ }
236
+
237
+ # ----- manifest tracking (ADR-020, audit P1b) ----------------------------
238
+ #
239
+ # Each install records every emitted file into <target>/.conductor-manifest.json so a future
240
+ # `--uninstall` invocation can revert with surgical precision (restore backups when present,
241
+ # delete Conductor-emitted-only files when not, leave adopter customizations untouched).
242
+ #
243
+ # Manifest is generated using only POSIX shell (no jq dependency). Format:
244
+ #
245
+ # {
246
+ # "version": "v0.2.0",
247
+ # "install_timestamp": "2026-05-10T12:00:00Z",
248
+ # "conductor_root": "/abs/path/to/conductor",
249
+ # "recipes_enabled": ["monorepo", "i18n"],
250
+ # "emitted_files": [
251
+ # { "path": ".claude/rules/workflow.md", "source": "core/universal-rules/workflow.md",
252
+ # "had_backup": true, "backup_path": ".claude/rules/workflow.md.conductor-backup-20260510-120000" },
253
+ # ...
254
+ # ]
255
+ # }
256
+ #
257
+ # Implementation notes:
258
+ # - During install: helper appends to a temp staging file as each emit succeeds;
259
+ # finalize_manifest() wraps it into well-formed JSON at the end.
260
+ # - During uninstall: load_manifest() parses the JSON line-by-line using grep/sed
261
+ # (no nested objects beyond two levels — flat enough for shell parsing).
262
+ # - Manifest itself is backed up on re-install (the helper calls backup_if_exists
263
+ # on the manifest path before writing fresh).
264
+
265
+ MANIFEST_PATH="$TARGET_ABS/.conductor-manifest.json"
266
+ MANIFEST_STAGE_PATH="" # set by init_manifest()
267
+ MANIFEST_TS="" # ISO-8601 install timestamp (set by init_manifest)
268
+ MANIFEST_LAST_BACKUP="" # backup_if_exists writes the backup path here; emit helpers read it
269
+
270
+ init_manifest() {
271
+ # Called once at install start (after wizard, before any emit).
272
+ if [ "$DRY_RUN" = "true" ]; then
273
+ log "would init manifest staging at $MANIFEST_PATH.staging"
274
+ return
275
+ fi
276
+ MANIFEST_TS="$(/bin/date -u +%Y-%m-%dT%H:%M:%SZ)"
277
+ MANIFEST_STAGE_PATH="$TARGET_ABS/.conductor-manifest.json.staging"
278
+ /bin/rm -f "$MANIFEST_STAGE_PATH"
279
+ : > "$MANIFEST_STAGE_PATH"
280
+ }
281
+
282
+ # record_emit <relative-path> <source-relative-to-conductor-root> [<backup-path-or-empty>]
283
+ # Append one emitted-file entry to the staging file. Skipped under DRY_RUN.
284
+ record_emit() {
285
+ if [ "$DRY_RUN" = "true" ] || [ "$UNINSTALL" = "true" ]; then
286
+ return
287
+ fi
288
+ local relpath="$1" src="$2" backup="${3:-}"
289
+ local had_backup="false"
290
+ [ -n "$backup" ] && had_backup="true"
291
+ # Escape backslashes and double quotes for JSON.
292
+ local esc_path esc_src esc_backup
293
+ esc_path="$(printf '%s' "$relpath" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
294
+ esc_src="$(printf '%s' "$src" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
295
+ esc_backup="$(printf '%s' "$backup" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
296
+ printf ' {"path": "%s", "source": "%s", "had_backup": %s, "backup_path": "%s"},\n' \
297
+ "$esc_path" "$esc_src" "$had_backup" "$esc_backup" >> "$MANIFEST_STAGE_PATH"
298
+ }
299
+
300
+ finalize_manifest() {
301
+ # Called at the end of install. Wraps staged entries into JSON, backs up old manifest,
302
+ # writes the new one.
303
+ if [ "$DRY_RUN" = "true" ]; then
304
+ log "would finalize manifest -> $MANIFEST_PATH"
305
+ return
306
+ fi
307
+ [ -z "$MANIFEST_STAGE_PATH" ] && return
308
+ [ -f "$MANIFEST_STAGE_PATH" ] || return
309
+
310
+ # Back up existing manifest before overwrite (idempotent re-install).
311
+ if [ -f "$MANIFEST_PATH" ]; then
312
+ backup_if_exists "$MANIFEST_PATH"
313
+ fi
314
+
315
+ # Build recipes_enabled JSON array.
316
+ local recipes_json="[]"
317
+ if [ -n "$RECIPES" ]; then
318
+ recipes_json="["
319
+ local IFS_BAK=$IFS
320
+ IFS=','
321
+ local first="true"
322
+ for r in $RECIPES; do
323
+ r="$(printf '%s' "$r" | /usr/bin/sed 's/^ *//; s/ *$//')"
324
+ [ -z "$r" ] && continue
325
+ if [ "$first" = "true" ]; then
326
+ recipes_json="${recipes_json}\"$r\""
327
+ first="false"
328
+ else
329
+ recipes_json="${recipes_json}, \"$r\""
330
+ fi
331
+ done
332
+ IFS=$IFS_BAK
333
+ recipes_json="${recipes_json}]"
334
+ fi
335
+
336
+ # Strip trailing comma from last staged line for valid JSON.
337
+ local entries
338
+ if [ -s "$MANIFEST_STAGE_PATH" ]; then
339
+ entries="$(/usr/bin/sed -e '$ s/,$//' "$MANIFEST_STAGE_PATH")"
340
+ else
341
+ entries=""
342
+ fi
343
+
344
+ /bin/cat > "$MANIFEST_PATH" <<EOF
345
+ {
346
+ "version": "v0.2.0",
347
+ "install_timestamp": "$MANIFEST_TS",
348
+ "conductor_root": "$CONDUCTOR_ROOT",
349
+ "recipes_enabled": $recipes_json,
350
+ "emitted_files": [
351
+ $entries
352
+ ]
353
+ }
354
+ EOF
355
+ /bin/rm -f "$MANIFEST_STAGE_PATH"
356
+ log " wrote manifest $MANIFEST_PATH"
357
+ }
358
+
359
+ # Wrappers around backup_if_exists / cp / write that also remember the backup path so the
360
+ # next record_emit() call can reference it. Keeps emit-step changes minimal.
361
+ backup_and_remember() {
362
+ # backup_and_remember <dest>
363
+ MANIFEST_LAST_BACKUP=""
364
+ if [ -f "$1" ]; then
365
+ if [ "$DRY_RUN" = "true" ]; then
366
+ log "would back up existing $1 -> $1.conductor-backup-<ts>"
367
+ MANIFEST_LAST_BACKUP=""
368
+ else
369
+ local ts
370
+ ts="$(/bin/date +%Y%m%d-%H%M%S)"
371
+ local backup="$1.conductor-backup-$ts"
372
+ /bin/cp "$1" "$backup"
373
+ log " backed up existing $1 -> $backup"
374
+ # Store relative-to-target path for portability.
375
+ MANIFEST_LAST_BACKUP="${backup#$TARGET_ABS/}"
376
+ fi
377
+ fi
378
+ }
379
+
380
+ # ----- uninstall flow (ADR-020, audit P1b) --------------------------------
381
+ #
382
+ # Logic:
383
+ # 1. Load manifest (require it unless --force).
384
+ # 2. Safety checks: warn on active worktrees, missing .git, etc. Unless --force, abort.
385
+ # 3. For each emitted file:
386
+ # had_backup=true → restore backup, delete backup file
387
+ # had_backup=false → delete file (truly fresh install path)
388
+ # 4. Delete manifest itself (always last).
389
+ # 5. DRY_RUN logs each action without performing it.
390
+
391
+ do_uninstall() {
392
+ log "uninstall mode (target: $TARGET_ABS)"
393
+
394
+ # Safety: missing manifest.
395
+ if [ ! -f "$MANIFEST_PATH" ]; then
396
+ if [ "$FORCE" = "true" ]; then
397
+ log "WARNING: no manifest at $MANIFEST_PATH — proceeding under --force (will only delete legacy backups it can find)"
398
+ uninstall_legacy_scan
399
+ return 0
400
+ fi
401
+ echo "Error: no manifest at $MANIFEST_PATH." >&2
402
+ echo " This target was either installed by a pre-manifest version or has already been uninstalled." >&2
403
+ echo " Re-run with --force to scan for legacy .conductor-backup-* files and delete them anyway:" >&2
404
+ echo " bash $0 $TARGET_ABS --uninstall --force" >&2
405
+ exit 1
406
+ fi
407
+
408
+ # Safety: active git worktree mid-rebase / merge.
409
+ if [ -d "$TARGET_ABS/.git" ]; then
410
+ if [ -f "$TARGET_ABS/.git/MERGE_HEAD" ] || [ -f "$TARGET_ABS/.git/REBASE_HEAD" ] || [ -d "$TARGET_ABS/.git/rebase-merge" ]; then
411
+ if [ "$FORCE" != "true" ]; then
412
+ echo "Error: target has an active git operation (merge/rebase in progress)." >&2
413
+ echo " Resolve the in-flight operation first, or pass --force to override." >&2
414
+ exit 1
415
+ fi
416
+ log "WARNING: active git operation detected — proceeding under --force"
417
+ fi
418
+ fi
419
+
420
+ # Parse manifest. Each emitted_files entry is one line with:
421
+ # {"path": "...", "source": "...", "had_backup": true|false, "backup_path": "..."},
422
+ log "loading manifest entries..."
423
+ local entries_count=0
424
+ local restored=0
425
+ local deleted=0
426
+ local missing=0
427
+
428
+ # Iterate manifest entries by line. Tolerate trailing comma absence on last entry.
429
+ while IFS= read -r line; do
430
+ case "$line" in
431
+ *'"path":'*'"source":'*'"had_backup":'*)
432
+ ;;
433
+ *) continue ;;
434
+ esac
435
+ entries_count=$((entries_count + 1))
436
+ local rel_path src had_backup backup_path
437
+ rel_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"path": *"([^"]*)".*/\1/')"
438
+ src="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"source": *"([^"]*)".*/\1/')"
439
+ had_backup="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"had_backup": *(true|false).*/\1/')"
440
+ backup_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"backup_path": *"([^"]*)".*/\1/')"
441
+
442
+ local abs_dest="$TARGET_ABS/$rel_path"
443
+ local abs_backup=""
444
+ [ -n "$backup_path" ] && abs_backup="$TARGET_ABS/$backup_path"
445
+
446
+ if [ "$had_backup" = "true" ] && [ -n "$abs_backup" ]; then
447
+ if [ -f "$abs_backup" ]; then
448
+ if [ "$DRY_RUN" = "true" ]; then
449
+ log " would restore $abs_backup -> $abs_dest"
450
+ else
451
+ /bin/mv -f "$abs_backup" "$abs_dest"
452
+ log " restored $abs_backup -> $abs_dest"
453
+ fi
454
+ restored=$((restored + 1))
455
+ else
456
+ if [ "$DRY_RUN" = "true" ]; then
457
+ log " would delete $abs_dest (backup $abs_backup missing)"
458
+ else
459
+ /bin/rm -f "$abs_dest"
460
+ log " deleted $abs_dest (backup $abs_backup missing)"
461
+ fi
462
+ missing=$((missing + 1))
463
+ fi
464
+ else
465
+ if [ -f "$abs_dest" ]; then
466
+ if [ "$DRY_RUN" = "true" ]; then
467
+ log " would delete $abs_dest"
468
+ else
469
+ /bin/rm -f "$abs_dest"
470
+ log " deleted $abs_dest"
471
+ fi
472
+ deleted=$((deleted + 1))
473
+ else
474
+ log " skip $abs_dest (already absent)"
475
+ fi
476
+ fi
477
+ done < "$MANIFEST_PATH"
478
+
479
+ # Remove the manifest itself + its own backups (.conductor-manifest.json.conductor-backup-*).
480
+ if [ "$DRY_RUN" = "true" ]; then
481
+ log " would delete $MANIFEST_PATH"
482
+ for mb in "$MANIFEST_PATH".conductor-backup-*; do
483
+ [ -e "$mb" ] && log " would delete $mb"
484
+ done
485
+ else
486
+ /bin/rm -f "$MANIFEST_PATH"
487
+ for mb in "$MANIFEST_PATH".conductor-backup-*; do
488
+ [ -e "$mb" ] && /bin/rm -f "$mb"
489
+ done
490
+ log " deleted $MANIFEST_PATH"
491
+ fi
492
+
493
+ # Try to clean up empty dirs left behind (children before parents). Includes the
494
+ # self-improvement gate dir .conductor/reflect/ — leaving it would keep the
495
+ # always-on trajectory hook active after uninstall.
496
+ for d in .claude/rules .claude/agents .claude/hooks .claude/commands .conductor/reflect .conductor .claude; do
497
+ local abs_d="$TARGET_ABS/$d"
498
+ if [ -d "$abs_d" ]; then
499
+ if [ "$DRY_RUN" = "true" ]; then
500
+ if [ -z "$(/bin/ls -A "$abs_d" 2>/dev/null)" ]; then
501
+ log " would rmdir empty $abs_d"
502
+ fi
503
+ else
504
+ /bin/rmdir "$abs_d" 2>/dev/null && log " rmdir empty $abs_d" || true
505
+ fi
506
+ fi
507
+ done
508
+
509
+ echo ""
510
+ echo "========================================================"
511
+ if [ "$DRY_RUN" = "true" ]; then
512
+ echo " Uninstall preview (dry-run)"
513
+ else
514
+ echo " Uninstall complete"
515
+ fi
516
+ echo " Target: $TARGET_ABS"
517
+ echo " Entries processed: $entries_count"
518
+ echo " Backups restored: $restored"
519
+ echo " Files deleted: $deleted"
520
+ echo " Backup-missing deletes: $missing"
521
+ echo "========================================================"
522
+ }
523
+
524
+ # Fallback when --force --uninstall is invoked with no manifest.
525
+ # Conservatively scans for *.conductor-backup-* files and offers to delete them.
526
+ uninstall_legacy_scan() {
527
+ log "legacy scan mode — searching for .conductor-backup-* files under $TARGET_ABS"
528
+ local found=0
529
+ while IFS= read -r f; do
530
+ found=$((found + 1))
531
+ if [ "$DRY_RUN" = "true" ]; then
532
+ log " would delete legacy backup $f"
533
+ else
534
+ /bin/rm -f "$f"
535
+ log " deleted legacy backup $f"
536
+ fi
537
+ done < <(/usr/bin/find "$TARGET_ABS" -type f -name '*.conductor-backup-*' 2>/dev/null)
538
+ log "legacy scan: $found backup file(s)"
539
+ log "WARNING: legacy mode does not delete Conductor-emitted source files (no manifest to identify them)."
540
+ log " Delete .claude/rules/{workflow,spec-as-you-go,quality-gates,operations,meta-discipline}.md"
541
+ log " and .claude/agents/{planner,builder,reviewer,helper,designer,scribe}.md manually if desired."
542
+ }
543
+
544
+ # Dispatch uninstall path before the install steps.
545
+ if [ "$UNINSTALL" = "true" ]; then
546
+ do_uninstall
547
+ exit 0
548
+ fi
549
+
550
+ # ----- onboarding wizard --------------------------------------------------
551
+ # Wizard semantic (ADR-019, audit P1a):
552
+ # - Truly fresh target (no `.claude/` AND no `CLAUDE.md`) → wizard SKIP, autopilot install.
553
+ # - Adopter case (`.claude/` OR `CLAUDE.md` already present) → wizard FIRE, walk the user
554
+ # through detect/apply/recipes/baseline/anti-pattern decisions before any emit.
555
+ # - --no-prompt always skips wizard regardless of state (CI / scripted installs).
556
+ # - --dry-run always skips wizard (no interactive prompts in preview mode).
557
+ #
558
+ # Rationale: the most common adopter (existing Claude Code project with `.claude/agents/` or
559
+ # a hand-written `CLAUDE.md`) was previously bypassing wizard entirely and getting default
560
+ # behavior. Inverting the trigger surfaces the choice points where overwrite/backup matters
561
+ # most.
562
+
563
+ IS_ADOPTER_CASE="false"
564
+ if [ -d "$TARGET_ABS/.claude" ] || [ -f "$TARGET_ABS/CLAUDE.md" ]; then
565
+ IS_ADOPTER_CASE="true"
566
+ fi
567
+
568
+ if [ "$IS_ADOPTER_CASE" = "true" ] && [ "$NO_PROMPT" = "false" ] && [ "$DRY_RUN" = "false" ]; then
569
+ echo ""
570
+ echo "========================================================"
571
+ echo " Welcome to CONDUCTOR setup"
572
+ echo " Target: $TARGET_ABS"
573
+ echo "========================================================"
574
+ echo ""
575
+
576
+ # 1. Detect existing rules?
577
+ printf "Detect existing rules? (y/N): "
578
+ read -r _detect_answer
579
+ if [ "$_detect_answer" = "y" ] || [ "$_detect_answer" = "Y" ]; then
580
+ _existing_rules=$(ls "$TARGET_ABS/.claude/rules/" 2>/dev/null | wc -l | /usr/bin/tr -d ' ')
581
+ _existing_agents=$(ls "$TARGET_ABS/.claude/agents/" 2>/dev/null | wc -l | /usr/bin/tr -d ' ')
582
+ echo " Found $_existing_rules rule files, $_existing_agents agent files in .claude/"
583
+ fi
584
+
585
+ # 2. Apply universal-rules + roles?
586
+ printf "Apply universal-rules + roles? (Y/n): "
587
+ read -r _apply_answer
588
+ if [ "$_apply_answer" = "n" ] || [ "$_apply_answer" = "N" ]; then
589
+ WIZARD_APPLY_RULES="false"
590
+ echo " Skipping universal-rules + roles installation."
591
+ fi
592
+
593
+ # 3. Select recipes
594
+ echo ""
595
+ echo "Available recipes:"
596
+ echo " web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement"
597
+ printf "Select recipes (comma-separated, or leave blank for none): "
598
+ read -r _recipe_answer
599
+ if [ -n "$_recipe_answer" ]; then
600
+ RECIPES="$_recipe_answer"
601
+ echo " Recipes selected: $RECIPES"
602
+ else
603
+ echo " No recipes selected."
604
+ fi
605
+
606
+ # 4. Measure cache baseline?
607
+ printf "Measure cache baseline? (recommended) (Y/n): "
608
+ read -r _baseline_answer
609
+ if [ "$_baseline_answer" = "n" ] || [ "$_baseline_answer" = "N" ]; then
610
+ MEASURE_BASELINE="false"
611
+ else
612
+ MEASURE_BASELINE="true"
613
+ echo " Baseline measurement enabled."
614
+ fi
615
+
616
+ # 5. Show anti-pattern catalog?
617
+ printf "Show anti-pattern catalog? (recommended) (Y/n): "
618
+ read -r _antipattern_answer
619
+ if [ "$_antipattern_answer" = "n" ] || [ "$_antipattern_answer" = "N" ]; then
620
+ WIZARD_SHOW_ANTI_PATTERNS="false"
621
+ else
622
+ WIZARD_SHOW_ANTI_PATTERNS="true"
623
+ CHECK_ANTI_PATTERNS="true"
624
+ fi
625
+
626
+ echo ""
627
+ elif [ "$IS_ADOPTER_CASE" = "true" ] && [ "$NO_PROMPT" = "true" ]; then
628
+ log "Adopter case detected — applying defaults (--no-prompt): rules=yes, recipes=${RECIPES:-(none)}, baseline=$MEASURE_BASELINE, anti-patterns=$CHECK_ANTI_PATTERNS"
629
+ fi
630
+
631
+ # ----- step 1: universal rules -------------------------------------------
632
+
633
+ # Initialize manifest before any emit happens.
634
+ init_manifest
635
+
636
+ if [ "$WIZARD_APPLY_RULES" = "true" ]; then
637
+ log "Step 1/6: universal-rules → .claude/rules/"
638
+ mkdir_if_real "$TARGET_ABS/.claude/rules"
639
+
640
+ for rule in workflow spec-as-you-go quality-gates operations meta-discipline; do
641
+ src="$CORE_ROOT/universal-rules/$rule.md"
642
+ dest="$TARGET_ABS/.claude/rules/$rule.md"
643
+ if [ ! -f "$src" ]; then
644
+ echo "Warning: $src not found; skipping" >&2
645
+ continue
646
+ fi
647
+ backup_and_remember "$dest"
648
+ copy_with_paths_frontmatter "$src" "$dest" "**"
649
+ record_emit ".claude/rules/$rule.md" "core/universal-rules/$rule.md" "$MANIFEST_LAST_BACKUP"
650
+ done
651
+ else
652
+ log "Step 1/6: universal-rules — skipped (user opted out)"
653
+ fi
654
+
655
+ # ----- step 2: roles -----------------------------------------------------
656
+
657
+ if [ "$WIZARD_APPLY_RULES" = "true" ]; then
658
+ log "Step 2/6: roles → .claude/agents/"
659
+ mkdir_if_real "$TARGET_ABS/.claude/agents"
660
+
661
+ # Map each universal role to Claude-native agent frontmatter.
662
+ declare_agent() {
663
+ # declare_agent <role> <description> <model>
664
+ local role="$1" desc="$2" model="$3"
665
+ local src="$CORE_ROOT/roles/$role.md"
666
+ local dest="$TARGET_ABS/.claude/agents/$role.md"
667
+ [ -f "$src" ] || { echo "Warning: $src not found; skipping" >&2; return; }
668
+ backup_and_remember "$dest"
669
+ if [ "$DRY_RUN" = "true" ]; then
670
+ log "would write Claude agent $dest (model=$model)"
671
+ return
672
+ fi
673
+ /bin/cat > "$dest" <<EOF
674
+ ---
675
+ name: $role
676
+ description: $desc
677
+ model: $model
678
+ ---
679
+
680
+ EOF
681
+ # Strip the universal CONDUCTOR frontmatter from src body, append the rest.
682
+ /usr/bin/awk 'BEGIN{f=0} /^---$/{c++; if(c==2){f=1; next}} f==1' "$src" >> "$dest"
683
+ record_emit ".claude/agents/$role.md" "core/roles/$role.md" "$MANIFEST_LAST_BACKUP"
684
+ }
685
+
686
+ declare_agent planner "Architecture, gap analysis, ADRs, trade-off decisions. No implementation code." opus
687
+ declare_agent builder "Multi-file or cross-cutting code implementation (3+ files)." opus
688
+ declare_agent reviewer "Plan validation before implementation. Read-only gatekeeper." opus
689
+ declare_agent helper "Single-file or 1-2-file work where the pattern is established." sonnet
690
+ declare_agent designer "UI / UX implementation. Visual components, design tokens, accessibility." sonnet
691
+ declare_agent scribe "Documentation sync after implementation. No code edits." sonnet
692
+ case ",$RECIPES," in *",self-improvement,"*)
693
+ declare_agent reflector "Reads session trajectories; proposes atomic lesson deltas for human approval. No code, no auto-apply." opus ;;
694
+ esac
695
+ else
696
+ log "Step 2/6: roles — skipped (user opted out)"
697
+ fi
698
+
699
+ # ----- step 3: recipes (opt-in) ------------------------------------------
700
+
701
+ log "Step 3/6: recipes (opt-in) → .claude/rules/"
702
+ if [ -n "$RECIPES" ]; then
703
+ IFS=',' read -ra RECIPE_LIST <<< "$RECIPES"
704
+ for r in "${RECIPE_LIST[@]}"; do
705
+ src="$CORE_ROOT/recipes/$r.md"
706
+ dest="$TARGET_ABS/.claude/rules/$r.md"
707
+ if [ ! -f "$src" ]; then
708
+ echo "Warning: recipe '$r' not found at $src; skipping" >&2
709
+ continue
710
+ fi
711
+ backup_and_remember "$dest"
712
+ copy_with_paths_frontmatter "$src" "$dest" "**"
713
+ record_emit ".claude/rules/$r.md" "core/recipes/$r.md" "$MANIFEST_LAST_BACKUP"
714
+ done
715
+ else
716
+ log " (no recipes selected — pass --recipes=name1,name2 to install)"
717
+ fi
718
+
719
+ # ----- step 4: hooks + settings.json -------------------------------------
720
+
721
+ log "Step 4/6: hooks → .claude/hooks/"
722
+ mkdir_if_real "$TARGET_ABS/.claude/hooks"
723
+
724
+ # Core hooks (always emitted when template exists). Optional hooks (cache-hit baseline, large-file
725
+ # read guard) emit only if their templates are present in the CONDUCTOR core/ tree, allowing the
726
+ # adapter to remain forward-compatible with P1.7 work in progress.
727
+ INSTALLED_HOOKS=()
728
+ for hook in pretool-agent-routing stop-session-log-check stop-r6-review-check stop-cache-hit-baseline-check pretool-large-file-read-guard pretool-commit-current-work-check pretool-commit-test-coverage-check stop-trajectory-log; do
729
+ src="$CORE_ROOT/hooks/$hook.sh.template"
730
+ dest="$TARGET_ABS/.claude/hooks/$hook.sh"
731
+ if [ ! -f "$src" ]; then
732
+ log " hook template $hook not found in core/hooks — skipping"
733
+ continue
734
+ fi
735
+ backup_and_remember "$dest"
736
+ substitute_template "$src" "$dest"
737
+ record_emit ".claude/hooks/$hook.sh" "core/hooks/$hook.sh.template" "$MANIFEST_LAST_BACKUP"
738
+ INSTALLED_HOOKS+=("$hook")
739
+ done
740
+
741
+ # Emit .claude/settings.json with permissions allowlist + hook registry. Pre-approves
742
+ # read-only / safe Bash commands to reduce permission-prompt friction.
743
+ # Mutating commands are intentionally excluded.
744
+ SETTINGS_PATH="$TARGET_ABS/.claude/settings.json"
745
+ if [ -f "$SETTINGS_PATH" ]; then
746
+ log " $SETTINGS_PATH exists — leaving in place (project may have local customizations)"
747
+ elif [ "$DRY_RUN" = "true" ]; then
748
+ log "would write $SETTINGS_PATH with permissions.allow + hooks registry"
749
+ else
750
+ /bin/cat > "$SETTINGS_PATH" <<'SETTINGS_EOF'
751
+ {
752
+ "$comment": "Project-level Claude Code settings, generated by CONDUCTOR Claude adapter. Personal settings go in settings.local.json. Permissions allowlist pre-approves read-only/safe Bash commands; mutating commands deliberately omitted.",
753
+ "permissions": {
754
+ "allow": [
755
+ "Bash(git status:*)",
756
+ "Bash(git diff:*)",
757
+ "Bash(git log:*)",
758
+ "Bash(git show:*)",
759
+ "Bash(git branch:*)",
760
+ "Bash(grep:*)",
761
+ "Bash(rg:*)",
762
+ "Bash(find:*)",
763
+ "Bash(ls:*)",
764
+ "Bash(wc:*)",
765
+ "Bash(cat:*)",
766
+ "Bash(head:*)",
767
+ "Bash(tail:*)",
768
+ "Bash(pwd)",
769
+ "Bash(which:*)",
770
+ "Bash(gh pr view:*)",
771
+ "Bash(gh pr list:*)",
772
+ "Bash(gh pr diff:*)",
773
+ "Bash(gh repo view:*)",
774
+ "Bash(npm ls:*)",
775
+ "Bash(npx tsc --noEmit:*)",
776
+ "Bash(bash -n:*)"
777
+ ]
778
+ },
779
+ "hooks": {
780
+ "PreToolUse": [
781
+ {
782
+ "matcher": "Agent",
783
+ "hooks": [
784
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pretool-agent-routing.sh" }
785
+ ]
786
+ },
787
+ {
788
+ "matcher": "Bash",
789
+ "hooks": [
790
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pretool-commit-current-work-check.sh" },
791
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pretool-commit-test-coverage-check.sh" }
792
+ ]
793
+ },
794
+ {
795
+ "matcher": "Read",
796
+ "hooks": [
797
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pretool-large-file-read-guard.sh" }
798
+ ]
799
+ }
800
+ ],
801
+ "Stop": [
802
+ {
803
+ "hooks": [
804
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop-session-log-check.sh" },
805
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop-r6-review-check.sh" },
806
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop-cache-hit-baseline-check.sh" },
807
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop-trajectory-log.sh" }
808
+ ]
809
+ }
810
+ ]
811
+ }
812
+ }
813
+ SETTINGS_EOF
814
+ log " wrote $SETTINGS_PATH ($(printf '%s' "${INSTALLED_HOOKS[*]}" | /usr/bin/wc -w | /usr/bin/tr -d ' ') hook(s) installed in .claude/hooks; settings.json registers 8 core hooks: 4 PreToolUse + 4 Stop)"
815
+ record_emit ".claude/settings.json" "<synthesized>" ""
816
+ fi
817
+
818
+ # ----- step 4.5: hookify rule templates ----------------------------------
819
+ #
820
+ # Hookify (a Claude Code plugin) reads .claude/hookify.<name>.local.md files at runtime and
821
+ # injects warning/blocker messages on Bash, file-edit, prompt, and stop events. This step copies
822
+ # the framework-curated universal hookify templates into the consumer project. Existing files are
823
+ # never overwritten — adopter customizations always win.
824
+ #
825
+ # Origin: 2026-05-09 reference-adopter sync (hookify rule port). See docs/DESIGN-DECISIONS.md ADR-018.
826
+ # Forward-compat: silent skip if the hookify-templates directory is empty or missing.
827
+
828
+ HOOKIFY_TEMPLATE_DIR="$CONDUCTOR_ROOT/adapters/claude/hookify-templates"
829
+ if [ -d "$HOOKIFY_TEMPLATE_DIR" ]; then
830
+ log "Step 4.5/6: hookify rules → .claude/hookify.*.local.md"
831
+ HOOKIFY_INSTALLED=0
832
+ HOOKIFY_SKIPPED=0
833
+ for tpl in "$HOOKIFY_TEMPLATE_DIR"/*.local.md.template; do
834
+ [ -f "$tpl" ] || continue
835
+ basename_tpl="$(basename "$tpl")"
836
+ # strip ".template" suffix; result e.g. "warn-console-direct.local.md"
837
+ out_name="${basename_tpl%.template}"
838
+ # Recipe-scoping (ADR-028): if this template is listed in .recipe-scoped, emit only when its recipe is selected.
839
+ # awk '$1==k' does a LITERAL field-1 compare (out_name contains regex-special dots) — also excludes
840
+ # '#' comment lines and prevents prefix/substring collisions. '|| true' keeps set -eu safe if the map is absent.
841
+ scoped_recipe="$(/usr/bin/awk -v k="$out_name" '$1==k {print $2}' "$HOOKIFY_TEMPLATE_DIR/.recipe-scoped" 2>/dev/null || true)"
842
+ if [ -n "$scoped_recipe" ]; then
843
+ case ",$RECIPES," in
844
+ *",$scoped_recipe,"*) : ;; # recipe selected — emit
845
+ *) log " $out_name — skipped (requires --recipes=$scoped_recipe)"; continue ;;
846
+ esac
847
+ fi
848
+ dest="$TARGET_ABS/.claude/$(echo "hookify.$out_name")"
849
+ if [ -f "$dest" ]; then
850
+ log " $dest exists — leaving in place (adopter customization wins)"
851
+ HOOKIFY_SKIPPED=$((HOOKIFY_SKIPPED + 1))
852
+ continue
853
+ fi
854
+ substitute_hookify_template "$tpl" "$dest"
855
+ record_emit ".claude/hookify.$out_name" "adapters/claude/hookify-templates/$basename_tpl" ""
856
+ HOOKIFY_INSTALLED=$((HOOKIFY_INSTALLED + 1))
857
+ done
858
+ log " hookify rules: $HOOKIFY_INSTALLED installed, $HOOKIFY_SKIPPED skipped (pre-existing)"
859
+ fi
860
+
861
+ # ---- Step 4.6: self-improvement runtime artifacts (only when recipe selected) ----
862
+ case ",$RECIPES," in
863
+ *",self-improvement,"*)
864
+ log "Step 4.6: emitting self-improvement runtime (prune script + /reflect command)"
865
+ # prune script → .conductor/reflect/prune-lessons.sh
866
+ if [ -f "$CORE_ROOT/reflector/prune-lessons.sh" ]; then
867
+ dest="$TARGET_ABS/.conductor/reflect/prune-lessons.sh"
868
+ if [ "$DRY_RUN" = "true" ]; then
869
+ log " would emit .conductor/reflect/prune-lessons.sh"
870
+ else
871
+ /bin/mkdir -p "$TARGET_ABS/.conductor/reflect"
872
+ backup_and_remember "$dest"
873
+ /bin/cp "$CORE_ROOT/reflector/prune-lessons.sh" "$dest"
874
+ /bin/chmod +x "$dest"
875
+ record_emit ".conductor/reflect/prune-lessons.sh" "core/reflector/prune-lessons.sh" "$MANIFEST_LAST_BACKUP"
876
+ fi
877
+ fi
878
+ # /reflect command → .claude/commands/reflect.md
879
+ if [ -f "$CORE_ROOT/reflector/reflect.command.md" ]; then
880
+ dest="$TARGET_ABS/.claude/commands/reflect.md"
881
+ if [ "$DRY_RUN" = "true" ]; then
882
+ log " would emit .claude/commands/reflect.md"
883
+ else
884
+ /bin/mkdir -p "$TARGET_ABS/.claude/commands"
885
+ backup_and_remember "$dest"
886
+ /bin/cp "$CORE_ROOT/reflector/reflect.command.md" "$dest"
887
+ record_emit ".claude/commands/reflect.md" "core/reflector/reflect.command.md" "$MANIFEST_LAST_BACKUP"
888
+ fi
889
+ fi
890
+ # scheduling assets → .conductor/reflect/ (weekly runner + brief + registration guide)
891
+ if [ "$DRY_RUN" = "true" ]; then
892
+ log " would emit .conductor/reflect/{run-weekly.sh,reflect-brief.md,SCHEDULING.md}"
893
+ else
894
+ /bin/mkdir -p "$TARGET_ABS/.conductor/reflect"
895
+ gi="$TARGET_ABS/.gitignore"
896
+ grep -qxF '.conductor/' "$gi" 2>/dev/null || printf '\n# CONDUCTOR runtime (local trajectories/lessons)\n.conductor/\n' >> "$gi"
897
+ for f in run-weekly.sh reflect-brief.md SCHEDULING.md; do
898
+ [ -f "$CORE_ROOT/reflector/$f" ] || continue
899
+ dest="$TARGET_ABS/.conductor/reflect/$f"
900
+ backup_and_remember "$dest"
901
+ /bin/cp "$CORE_ROOT/reflector/$f" "$dest"
902
+ case "$f" in *.sh) /bin/chmod +x "$dest" ;; esac
903
+ record_emit ".conductor/reflect/$f" "core/reflector/$f" "$MANIFEST_LAST_BACKUP"
904
+ done
905
+ fi
906
+ ;;
907
+ *)
908
+ # Not opted in: clear a stale gate + orphaned opt-in artifacts left by a prior
909
+ # opted-in install, so a recipe-less re-install is fully dormant (the always-on
910
+ # trajectory hook gates on .conductor/reflect/, and /reflect must not dangle).
911
+ if [ "$DRY_RUN" != "true" ] && [ -d "$TARGET_ABS/.conductor/reflect" ]; then
912
+ /bin/rm -f "$TARGET_ABS/.conductor/reflect/prune-lessons.sh" "$TARGET_ABS/.conductor/reflect/run-weekly.sh" "$TARGET_ABS/.conductor/reflect/reflect-brief.md" "$TARGET_ABS/.conductor/reflect/SCHEDULING.md"
913
+ /bin/rm -f "$TARGET_ABS/.claude/commands/reflect.md" "$TARGET_ABS/.claude/agents/reflector.md" 2>/dev/null || true
914
+ if /bin/rmdir "$TARGET_ABS/.conductor/reflect" 2>/dev/null; then
915
+ log "Step 4.6: self-improvement not selected — cleared stale .conductor/reflect gate + /reflect artifacts"
916
+ else
917
+ log "Step 4.6: WARNING — .conductor/reflect not empty; trajectory hook stays ACTIVE (remove that dir manually to disable)"
918
+ fi
919
+ fi
920
+ ;;
921
+ esac
922
+
923
+ # ----- step 5: docs templates --------------------------------------------
924
+
925
+ log "Step 5/6: docs templates → docs/"
926
+ mkdir_if_real "$TARGET_ABS/docs"
927
+ mkdir_if_real "$TARGET_ABS/docs/specs"
928
+
929
+ for tpl in CURRENT_WORK REMAINING_TASKS PLANS TASKS INDEX; do
930
+ src="$CORE_ROOT/docs-templates/$tpl.md"
931
+ dest="$TARGET_ABS/docs/$tpl.md"
932
+ [ -f "$src" ] || continue
933
+ if [ -f "$dest" ]; then
934
+ log " $dest exists — leaving in place"
935
+ continue
936
+ fi
937
+ if [ "$DRY_RUN" = "true" ]; then
938
+ log "would copy $src -> $dest"
939
+ else
940
+ /bin/cp "$src" "$dest"
941
+ record_emit "docs/$tpl.md" "core/docs-templates/$tpl.md" ""
942
+ fi
943
+ done
944
+
945
+ if [ -f "$CORE_ROOT/docs-templates/specs/_example.md" ]; then
946
+ src="$CORE_ROOT/docs-templates/specs/_example.md"
947
+ dest="$TARGET_ABS/docs/specs/_example.md"
948
+ if [ ! -f "$dest" ]; then
949
+ if [ "$DRY_RUN" = "true" ]; then
950
+ log "would copy $src -> $dest"
951
+ else
952
+ /bin/cp "$src" "$dest"
953
+ record_emit "docs/specs/_example.md" "core/docs-templates/specs/_example.md" ""
954
+ fi
955
+ fi
956
+ fi
957
+
958
+ # ----- step 6: synthesize CLAUDE.md --------------------------------------
959
+
960
+ log "Step 6/6: synthesize CLAUDE.md"
961
+ CLAUDE_MD="$TARGET_ABS/CLAUDE.md"
962
+
963
+ CLAUDE_MD_CONTENT=$(/bin/cat <<'EOF'
964
+ # Project Orchestrator Manual (installed by CONDUCTOR)
965
+
966
+ You are the orchestrator. You coordinate, delegate, and verify. You do not implement code yourself except for the smallest tasks — developer roles handle that.
967
+
968
+ Sub-agents in Claude Code are isolated and do not inherit this file. Every dispatch brief must be self-contained: objective, file paths, constraints, output path, stop condition.
969
+
970
+ ## ABSOLUTE rules (read before every tool call)
971
+
972
+ The following universal rules are loaded from `.claude/rules/` and apply to every turn:
973
+
974
+ | Rule file | Bundles |
975
+ |---|---|
976
+ | `workflow.md` | Plan-first, docs-first, 7-step, process-over-speed, never-skip |
977
+ | `spec-as-you-go.md` | Same-turn spec update, real-time docs sync |
978
+ | `quality-gates.md` | Pre-commit + pre-merge review, test sync, verify-after-changes |
979
+ | `operations.md` | Session continuity, completed-task delete, dev/prod sync |
980
+ | `meta-discipline.md` | Originality, ambiguity AMB-1..7 triggers, token economy, model routing, flat-with-leader |
981
+
982
+ If you catch yourself about to break one, STOP and fix course. Silent recovery is worse than explicit acknowledgment.
983
+
984
+ ## Roles available for dispatch
985
+
986
+ | Role | Model | When to use |
987
+ |---|---|---|
988
+ | `@planner` | Opus | Architecture, ADRs, gap analysis (no code) |
989
+ | `@builder` | Opus | Multi-file (3+) cross-cutting code |
990
+ | `@reviewer` | Opus | Plan validation (read-only) |
991
+ | `@helper` | Sonnet | Single-file work, established patterns |
992
+ | `@designer` | Sonnet | UI / UX, design tokens, accessibility |
993
+ | `@scribe` | Sonnet | Documentation sync (no code) |
994
+
995
+ Per `meta-discipline.md` section 6, the orchestrator classifies every task and passes `model: "opus" | "sonnet" | "haiku"` explicitly. The PreToolUse hook (`.claude/hooks/pretool-agent-routing.sh`) enforces this.
996
+
997
+ ## Topology — flat-with-leader
998
+
999
+ Roles do NOT dispatch each other. Multi-step work returns intermediate results to the orchestrator, which decides the next dispatch. See `meta-discipline.md` section 7.
1000
+
1001
+ ## Ambiguity policy
1002
+
1003
+ Default: ACT-WITH-DECLARATION (proceed with best-guess + surface assumption in response prefix).
1004
+
1005
+ Override: ASK (multiple-choice template) when any of AMB-1..7 fires:
1006
+ - AMB-1 deictic ("this", "like before"), AMB-2 unspecified scope, AMB-3 external system invocation, AMB-4 protected-branch merge, AMB-5 design decisions, AMB-6 dependency add, AMB-7 user manual action required.
1007
+
1008
+ Full catalog: `meta-discipline.md` section 3.
1009
+
1010
+ ## Session startup (lazy-load by default)
1011
+
1012
+ Auto-load on every session: `docs/CURRENT_WORK.md` only.
1013
+
1014
+ Lazy-load on demand:
1015
+ - `docs/architecture/README.md` — when designing / changing system structure.
1016
+ - `docs/specs/<area>.md` — when touching that area's code.
1017
+ - Recipe files in `.claude/rules/` — auto-loaded by Claude Code when matching files are touched (via `paths:` frontmatter).
1018
+
1019
+ ## Hooks installed
1020
+
1021
+ | Hook | Trigger | Action |
1022
+ |---|---|---|
1023
+ | `pretool-agent-routing.sh` | Agent tool dispatch | Block forbidden subagent_type, require explicit model |
1024
+ | `pretool-commit-current-work-check.sh` | Bash `git commit` | Soft `ask` warn (non-blocking) when 3+ source files are staged but CURRENT_WORK.md is not in the commit (skip: `CONDUCTOR_SKIP_CURRENT_WORK_HOOK=1`) |
1025
+ | `pretool-commit-test-coverage-check.sh` | Bash `git commit` | Soft `ask` warn (non-blocking, quality-gates Q3) when a new feature-shaped file is added with no new test in the commit (skip: `CONDUCTOR_SKIP_TEST_COVERAGE_HOOK=1`) |
1026
+ | `pretool-large-file-read-guard.sh` | Read tool | Block Read of files ≥ 500 lines without offset/limit; recommends range-read or Grep (override: `CONDUCTOR_ALLOW_LARGE_READ=1`) |
1027
+ | `stop-session-log-check.sh` | Session stop | Block stop when CURRENT_WORK.md / specs are stale after recent commits |
1028
+ | `stop-r6-review-check.sh` | Session stop | Remind to run pre-merge review on open PR |
1029
+ | `stop-cache-hit-baseline-check.sh` | Session stop | Non-blocking cache-hit-rate diagnostic vs baseline (skip: `CONDUCTOR_SKIP_CACHE_CHECK=1`) |
1030
+
1031
+ ## Prompt caching (recommended)
1032
+
1033
+ When using the Anthropic SDK directly, place this orchestrator manual + the universal-rules + recipes in the cacheable prefix. See the CONDUCTOR repo's `docs/PROMPT-CACHING-GUIDE.md` for the recommended structure.
1034
+ EOF
1035
+ )
1036
+
1037
+ backup_and_remember "$CLAUDE_MD"
1038
+ if [ "$DRY_RUN" = "true" ]; then
1039
+ log "would write CLAUDE.md ($(echo "$CLAUDE_MD_CONTENT" | /usr/bin/wc -c | /usr/bin/tr -d ' ') bytes)"
1040
+ else
1041
+ printf '%s\n' "$CLAUDE_MD_CONTENT" > "$CLAUDE_MD"
1042
+ record_emit "CLAUDE.md" "<synthesized>" "$MANIFEST_LAST_BACKUP"
1043
+ fi
1044
+
1045
+ # Finalize manifest now that all emits are complete (before optional baseline measurement).
1046
+ finalize_manifest
1047
+
1048
+ # ----- step 7 (optional): baseline measurement ---------------------------
1049
+
1050
+ BASELINE_HIT_RATE=""
1051
+
1052
+ if [ "$MEASURE_BASELINE" = "true" ]; then
1053
+ log "Step 7/9: baseline measurement (--measure-baseline)"
1054
+ BASELINE_DIR="$TARGET_ABS/.conductor"
1055
+ BASELINE_CSV="$BASELINE_DIR/baseline-$(date +%Y%m%d).csv"
1056
+ MEASURE_SCRIPT="$TOOLS_ROOT/measure-tokens.sh"
1057
+
1058
+ if [ ! -f "$MEASURE_SCRIPT" ]; then
1059
+ echo "Warning: measure-tokens.sh not found at $MEASURE_SCRIPT; skipping baseline" >&2
1060
+ else
1061
+ if [ "$DRY_RUN" = "true" ]; then
1062
+ log "would run: bash $MEASURE_SCRIPT --latest --export-csv=$BASELINE_CSV"
1063
+ log "would save baseline to $BASELINE_CSV"
1064
+ else
1065
+ mkdir -p "$BASELINE_DIR"
1066
+ echo ""
1067
+ echo "[conductor] Running baseline measurement..."
1068
+ MEASURE_OUTPUT=""
1069
+ if MEASURE_OUTPUT=$(bash "$MEASURE_SCRIPT" --latest --export-csv="$BASELINE_CSV" 2>&1); then
1070
+ echo "$MEASURE_OUTPUT"
1071
+ # Extract cache hit rate for step 8 decision.
1072
+ BASELINE_HIT_RATE=$(echo "$MEASURE_OUTPUT" | /usr/bin/grep "Cache hit rate" | /usr/bin/awk '{print $NF}' | /usr/bin/tr -d '%')
1073
+ echo ""
1074
+ echo "[conductor] Baseline saved: $BASELINE_CSV"
1075
+ else
1076
+ echo "Warning: baseline measurement failed (no Claude Code sessions found?)" >&2
1077
+ echo "$MEASURE_OUTPUT" >&2
1078
+ fi
1079
+ fi
1080
+ fi
1081
+ else
1082
+ if [ "$DRY_RUN" = "true" ] && [ "$MEASURE_BASELINE" = "false" ]; then
1083
+ log "Step 7/9: baseline measurement — skipped (pass --measure-baseline to enable)"
1084
+ fi
1085
+ fi
1086
+
1087
+ # ----- step 8 (conditional): anti-pattern advice -------------------------
1088
+
1089
+ # Show anti-patterns if:
1090
+ # a) --check-anti-patterns flag is set, OR
1091
+ # b) --measure-baseline ran and hit rate < 95%
1092
+
1093
+ SHOW_ANTIPATTERNS_NOW="false"
1094
+
1095
+ if [ "$CHECK_ANTI_PATTERNS" = "true" ] || [ "$WIZARD_SHOW_ANTI_PATTERNS" = "true" ]; then
1096
+ SHOW_ANTIPATTERNS_NOW="true"
1097
+ fi
1098
+
1099
+ # If baseline ran and hit rate is below 95, also show.
1100
+ if [ -n "$BASELINE_HIT_RATE" ]; then
1101
+ # Compare as integer (strip decimal part).
1102
+ HIT_RATE_INT="${BASELINE_HIT_RATE%%.*}"
1103
+ if [ -n "$HIT_RATE_INT" ] && [ "$HIT_RATE_INT" -lt 95 ] 2>/dev/null; then
1104
+ SHOW_ANTIPATTERNS_NOW="true"
1105
+ echo ""
1106
+ echo "[conductor] Cache hit rate is ${BASELINE_HIT_RATE}% (below 95% threshold) — showing anti-pattern catalog."
1107
+ fi
1108
+ fi
1109
+
1110
+ ANTIPATTERN_README="$CORE_ROOT/anti-patterns/README.md"
1111
+
1112
+ if [ "$SHOW_ANTIPATTERNS_NOW" = "true" ]; then
1113
+ log "Step 8/9: anti-pattern catalog"
1114
+ if [ "$DRY_RUN" = "true" ]; then
1115
+ log "would print $ANTIPATTERN_README (5 second pause)"
1116
+ else
1117
+ echo ""
1118
+ echo "========================================================"
1119
+ echo " CONDUCTOR Anti-Pattern Catalog"
1120
+ echo "========================================================"
1121
+ if [ -f "$ANTIPATTERN_README" ]; then
1122
+ /bin/cat "$ANTIPATTERN_README"
1123
+ else
1124
+ echo "(catalog not found at $ANTIPATTERN_README)"
1125
+ fi
1126
+ echo ""
1127
+ echo "========================================================"
1128
+ echo " Next steps:"
1129
+ echo " 1. Review $CORE_ROOT/anti-patterns/ for detailed fixes."
1130
+ echo " 2. Apply fixes (move volatile content below cache boundary,"
1131
+ echo " add Grep-before-Read discipline)."
1132
+ echo " 3. Re-measure in 1 week:"
1133
+ echo " bash $CONDUCTOR_ROOT/tools/measure-tokens.sh --latest"
1134
+ echo " --export-csv=<project>/.conductor/followup-$(date +%Y%m%d).csv"
1135
+ echo "========================================================"
1136
+ echo ""
1137
+ echo "(pausing 5 seconds — press Enter to continue sooner)"
1138
+ read -r -t 5 || true
1139
+ fi
1140
+ else
1141
+ if [ "$DRY_RUN" = "true" ]; then
1142
+ log "Step 8/9: anti-pattern catalog — skipped (pass --check-anti-patterns or hit rate < 95% to enable)"
1143
+ fi
1144
+ fi
1145
+
1146
+ # ----- step 9 (always): restart reminder ---------------------------------
1147
+
1148
+ log "Step 9/9: activation reminder"
1149
+ echo ""
1150
+ echo "========================================================"
1151
+ echo " Done."
1152
+ echo " Target: $TARGET_ABS"
1153
+ echo " Universal rules: 5"
1154
+ case ",$RECIPES," in
1155
+ *",self-improvement,"*) echo " Roles: 7 (incl. reflector)" ;;
1156
+ *) echo " Roles: 6" ;;
1157
+ esac
1158
+ echo " Recipes installed: ${RECIPES:-(none)}"
1159
+ echo " Hooks: ${#INSTALLED_HOOKS[@]} (${INSTALLED_HOOKS[*]:-none})"
1160
+ echo " Settings: .claude/settings.json (permissions allowlist + hooks registry)"
1161
+ echo ""
1162
+ echo " Run \`claude\` (Claude Code restart) to activate new rules."
1163
+ echo "========================================================"
1164
+ echo ""
1165
+ echo "Next steps for the project:"
1166
+ echo " 1. Open $TARGET_ABS in Claude Code."
1167
+ echo " 2. Edit docs/CURRENT_WORK.md with your project's current state."
1168
+ echo " 3. Edit CLAUDE.md if you have project-specific orchestrator rules to add."
1169
+ echo " 4. Verify hook installation: ls -la $TARGET_ABS/.claude/hooks/"