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.
- package/CHANGELOG.md +64 -0
- package/LICENSE +219 -0
- package/NOTICE +11 -0
- package/README.md +526 -0
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/TRADEMARKS.md +26 -0
- package/VISION.md +106 -0
- package/adapters/README.md +121 -0
- package/adapters/claude/README.md +102 -0
- package/adapters/claude/SUPPORTED-FEATURES.md +66 -0
- package/adapters/claude/hookify-templates/.recipe-scoped +9 -0
- package/adapters/claude/hookify-templates/README.md +93 -0
- package/adapters/claude/hookify-templates/block-completion-claim-without-push.local.md.template +56 -0
- package/adapters/claude/hookify-templates/block-direct-push-protected-branch.local.md.template +18 -0
- package/adapters/claude/hookify-templates/block-force-push-protected-branch.local.md.template +42 -0
- package/adapters/claude/hookify-templates/block-server-secret-in-client.local.md.template +36 -0
- package/adapters/claude/hookify-templates/warn-any-type-added.local.md.template +44 -0
- package/adapters/claude/hookify-templates/warn-commit-without-pre-commit-review.local.md.template +52 -0
- package/adapters/claude/hookify-templates/warn-console-direct.local.md.template +55 -0
- package/adapters/claude/hookify-templates/warn-create-table-without-access-control.local.md.template +35 -0
- package/adapters/claude/hookify-templates/warn-current-work-without-remaining-tasks.local.md.template +36 -0
- package/adapters/claude/hookify-templates/warn-gh-pr-create-without-pre-merge-review.local.md.template +38 -0
- package/adapters/claude/hookify-templates/warn-hardcoded-text-without-i18n-key.local.md.template +21 -0
- package/adapters/claude/hookify-templates/warn-on-gh-pr-merge.local.md.template +33 -0
- package/adapters/claude/hookify-templates/warn-plan-spec-without-remaining-tasks.local.md.template +44 -0
- package/adapters/claude/hookify-templates/warn-raw-hex-instead-of-token.local.md.template +21 -0
- package/adapters/claude/hookify-templates/warn-security-definer-without-search-path.local.md.template +30 -0
- package/adapters/claude/hookify-templates/warn-stop-commit-without-current-work.local.md.template +47 -0
- package/adapters/claude/hookify-templates/warn-user-manual-completion.local.md.template +40 -0
- package/adapters/claude/transform-spec.md +140 -0
- package/adapters/claude/transform.sh +1169 -0
- package/adapters/codex/README.md +87 -0
- package/adapters/codex/SUPPORTED-FEATURES.md +66 -0
- package/adapters/codex/transform-spec.md +90 -0
- package/adapters/codex/transform.sh +748 -0
- package/adapters/copilot/README.md +88 -0
- package/adapters/copilot/SUPPORTED-FEATURES.md +73 -0
- package/adapters/copilot/transform-spec.md +111 -0
- package/adapters/copilot/transform.sh +757 -0
- package/adapters/cursor/README.md +87 -0
- package/adapters/cursor/SUPPORTED-FEATURES.md +90 -0
- package/adapters/cursor/transform-spec.md +124 -0
- package/adapters/cursor/transform.sh +731 -0
- package/adapters/gemini/README.md +93 -0
- package/adapters/gemini/SUPPORTED-FEATURES.md +80 -0
- package/adapters/gemini/transform-spec.md +102 -0
- package/adapters/gemini/transform.sh +844 -0
- package/adapters/windsurf/README.md +93 -0
- package/adapters/windsurf/SUPPORTED-FEATURES.md +67 -0
- package/adapters/windsurf/transform-spec.md +99 -0
- package/adapters/windsurf/transform.sh +727 -0
- package/bin/omniconductor.js +130 -0
- package/core/README.md +64 -0
- package/core/anti-patterns/README.md +142 -0
- package/core/anti-patterns/dynamic-system-prompt.md +113 -0
- package/core/anti-patterns/frequent-rule-file-edit.md +83 -0
- package/core/anti-patterns/large-file-read-no-range.md +90 -0
- package/core/anti-patterns/no-sub-agent-dispatch.md +89 -0
- package/core/anti-patterns/single-monolithic-rule-file.md +99 -0
- package/core/anti-patterns/skill-eager-load.md +83 -0
- package/core/anti-patterns/tool-call-spam.md +93 -0
- package/core/docs-templates/CURRENT_WORK.md +64 -0
- package/core/docs-templates/INDEX.md +57 -0
- package/core/docs-templates/PLANS.md +44 -0
- package/core/docs-templates/README.md +40 -0
- package/core/docs-templates/REMAINING_TASKS.md +43 -0
- package/core/docs-templates/TASKS.md +51 -0
- package/core/docs-templates/specs/_example.md +121 -0
- package/core/hooks/README.md +62 -0
- package/core/hooks/pretool-agent-routing.sh.template +97 -0
- package/core/hooks/pretool-commit-current-work-check.sh.template +103 -0
- package/core/hooks/pretool-commit-test-coverage-check.sh.template +116 -0
- package/core/hooks/pretool-large-file-read-guard.sh.template +117 -0
- package/core/hooks/stop-cache-hit-baseline-check.sh.template +133 -0
- package/core/hooks/stop-r6-review-check.sh.template +80 -0
- package/core/hooks/stop-session-log-check.sh.template +101 -0
- package/core/hooks/stop-trajectory-log.sh.template +96 -0
- package/core/memory-pattern/EXAMPLES.md +114 -0
- package/core/memory-pattern/README.md +133 -0
- package/core/recipes/README.md +46 -0
- package/core/recipes/auto-mock-data.md +82 -0
- package/core/recipes/branch-strategy.md +100 -0
- package/core/recipes/coding-conventions.md +123 -0
- package/core/recipes/database-discipline.md +65 -0
- package/core/recipes/debugging.md +143 -0
- package/core/recipes/design-system.md +30 -0
- package/core/recipes/i18n.md +118 -0
- package/core/recipes/monorepo.md +101 -0
- package/core/recipes/self-improvement.md +61 -0
- package/core/recipes/tdd.md +128 -0
- package/core/recipes/web-mobile-parity.md +87 -0
- package/core/reflector/SCHEDULING.md +83 -0
- package/core/reflector/prune-lessons.sh +105 -0
- package/core/reflector/reflect-brief.md +11 -0
- package/core/reflector/reflect.command.md +13 -0
- package/core/reflector/run-weekly.sh +54 -0
- package/core/reflector/trajectory-log.sh +55 -0
- package/core/roles/README.md +61 -0
- package/core/roles/builder.md +75 -0
- package/core/roles/designer.md +93 -0
- package/core/roles/helper.md +70 -0
- package/core/roles/planner.md +123 -0
- package/core/roles/reflector.md +72 -0
- package/core/roles/reviewer.md +104 -0
- package/core/roles/scribe.md +89 -0
- package/core/universal-rules/README.md +71 -0
- package/core/universal-rules/meta-discipline.md +292 -0
- package/core/universal-rules/operations.md +162 -0
- package/core/universal-rules/quality-gates.md +191 -0
- package/core/universal-rules/spec-as-you-go.md +123 -0
- package/core/universal-rules/workflow.md +153 -0
- package/core/workflow/PHASES.md +134 -0
- package/core/workflow/README.md +56 -0
- package/docs/MANUAL-INSTALL.md +456 -0
- package/package.json +52 -0
- package/tools/check-framework-purity.sh +91 -0
- package/tools/measure-tokens.sh +169 -0
- 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/"
|