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,731 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # CONDUCTOR — Cursor adapter transform.sh
4
+ #
5
+ # Reads core/ assets and writes them into a target project as native Cursor
6
+ # files: .cursor/rules/*.mdc, optional .cursorrules (legacy bundle), docs/*.
7
+ #
8
+ # Usage:
9
+ # bash adapters/cursor/transform.sh <target-project> [--recipes=<comma-list>] [--dry-run]
10
+ # [--no-prompt] [--legacy-cursorrules]
11
+ # bash adapters/cursor/transform.sh <target-project> --uninstall [--dry-run] [--force]
12
+ #
13
+ # Examples:
14
+ # bash adapters/cursor/transform.sh ~/Projects/my-app
15
+ # bash adapters/cursor/transform.sh ~/Projects/my-app --recipes=i18n,monorepo
16
+ # bash adapters/cursor/transform.sh /tmp/test-project --dry-run
17
+ # bash adapters/cursor/transform.sh . --no-prompt --legacy-cursorrules
18
+ # bash adapters/cursor/transform.sh . --uninstall # revert install
19
+ # bash adapters/cursor/transform.sh . --uninstall --force # bypass safety checks
20
+ #
21
+ # Layer 2 transformation (per ADR-004 honesty + ADR-021):
22
+ # core/universal-rules/*.md → <target>/.cursor/rules/*.mdc (alwaysApply:true, modern Cursor format)
23
+ # core/recipes/*.md (selected) → <target>/.cursor/rules/*.mdc (path-scoped via globs:)
24
+ # core/docs-templates/*.md → <target>/docs/*.md (CURRENT_WORK, REMAINING_TASKS, etc.)
25
+ # <synthesized> → <target>/.cursorrules (only if --legacy-cursorrules)
26
+ # core/hooks/*.sh.template → SKIPPED (Reflector hook emitted via --recipes=self-improvement, ADR-032; other guards Claude-only, ADR-034)
27
+ # core/roles/*.md → SKIPPED (Cursor has no sub-agent dispatch)
28
+ # adapters/claude/hookify-... → SKIPPED (Claude-only plugin)
29
+
30
+ set -eu
31
+
32
+ # ----- arg parsing --------------------------------------------------------
33
+
34
+ TARGET=""
35
+ RECIPES=""
36
+ DRY_RUN="false"
37
+ NO_PROMPT="false"
38
+ UNINSTALL="false"
39
+ FORCE="false"
40
+ LEGACY_CURSORRULES="false"
41
+
42
+ # Onboarding wizard state
43
+ WIZARD_APPLY_RULES="true"
44
+
45
+ while [ $# -gt 0 ]; do
46
+ case "$1" in
47
+ --recipes=*) RECIPES="${1#--recipes=}" ;;
48
+ --dry-run) DRY_RUN="true" ;;
49
+ --no-prompt) NO_PROMPT="true" ;;
50
+ --uninstall|--rollback) UNINSTALL="true" ;;
51
+ --force) FORCE="true" ;;
52
+ --legacy-cursorrules) LEGACY_CURSORRULES="true" ;;
53
+ --help|-h)
54
+ /bin/cat <<EOF
55
+ Usage: bash adapters/cursor/transform.sh <target-project> [options]
56
+
57
+ Options:
58
+ --recipes=A,B,C Comma-separated list of recipes to install
59
+ --dry-run Preview only — no files written
60
+ --no-prompt Skip all interactive prompts; apply sensible defaults (CI-safe)
61
+ --legacy-cursorrules ALSO emit a flat .cursorrules bundle alongside .cursor/rules/*.mdc
62
+ (for Cursor versions < 0.45 that only read .cursorrules).
63
+ Default off — modern .mdc format is the canonical surface.
64
+ --uninstall Revert a previous install using <target>/.conductor-manifest.json
65
+ (alias: --rollback). Restores backups when present, deletes
66
+ Conductor-emitted files when none. Customizations not in the
67
+ manifest are preserved.
68
+ --force Bypass uninstall safety checks (active worktrees, missing manifest)
69
+
70
+ Recipes available: web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement
71
+
72
+ What this adapter does NOT install (per ADR-004 honesty + ADR-021):
73
+ - Hook guards (CONDUCTOR emits the Reflector hook when --recipes=self-improvement, ADR-032; other guards remain Claude-only, ADR-034)
74
+ - Sub-agent personas (Cursor has no sub-agent dispatch — single chat session per task)
75
+ - Hookify rule templates (Claude-only plugin)
76
+ EOF
77
+ exit 0
78
+ ;;
79
+ *)
80
+ if [ -z "$TARGET" ]; then
81
+ TARGET="$1"
82
+ else
83
+ echo "Unknown argument: $1" >&2
84
+ exit 1
85
+ fi
86
+ ;;
87
+ esac
88
+ shift
89
+ done
90
+
91
+ if [ -z "$TARGET" ]; then
92
+ echo "Error: target-project path is required." >&2
93
+ echo "Usage: bash adapters/cursor/transform.sh <target-project> [--recipes=...]" >&2
94
+ exit 1
95
+ fi
96
+
97
+ # Resolve CONDUCTOR root (where this script lives: adapters/cursor/).
98
+ CONDUCTOR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
99
+ CORE_ROOT="$CONDUCTOR_ROOT/core"
100
+ [ -d "$CORE_ROOT" ] || { echo "Error: core/ not found at $CORE_ROOT" >&2; exit 1; }
101
+
102
+ if [ "$DRY_RUN" = "true" ]; then
103
+ mkdir -p "$TARGET"
104
+ fi
105
+ TARGET_ABS="$(cd "$TARGET" 2>/dev/null && pwd)" || { echo "Error: target directory does not exist or is not a directory: $TARGET" >&2; exit 1; }
106
+
107
+ # ----- helpers ------------------------------------------------------------
108
+
109
+ log() {
110
+ if [ "$DRY_RUN" = "true" ]; then
111
+ echo "[dry-run] $*"
112
+ else
113
+ echo "[conductor] $*"
114
+ fi
115
+ }
116
+
117
+ mkdir_if_real() {
118
+ if [ "$DRY_RUN" = "true" ]; then
119
+ log "would mkdir -p $1"
120
+ else
121
+ mkdir -p "$1"
122
+ fi
123
+ }
124
+
125
+ # Strip the CONDUCTOR universal frontmatter (first --- ... --- block) from src body.
126
+ # Print body to stdout. Used by .mdc emit + legacy .cursorrules bundling.
127
+ strip_frontmatter() {
128
+ local src="$1"
129
+ /usr/bin/awk 'BEGIN{f=0} /^---$/{c++; if(c==2){f=1; next}} f==1' "$src"
130
+ }
131
+
132
+ # Derive a `description:` value from a markdown file. First H1 line, or filename.
133
+ derive_description() {
134
+ local src="$1"
135
+ local desc
136
+ desc="$(strip_frontmatter "$src" | /usr/bin/grep -m1 '^# ' | /usr/bin/sed -e 's/^# *//' -e 's/"/\\"/g' | /usr/bin/head -c 120)"
137
+ if [ -z "$desc" ]; then
138
+ desc="$(basename "$src" .md)"
139
+ fi
140
+ printf '%s' "$desc"
141
+ }
142
+
143
+ # Derive `globs:` from a recipe's filename. Universal rules use ["**"].
144
+ # This is best-effort — the spec keeps it simple: each recipe maps to a sensible glob default.
145
+ # Adopter is expected to tighten globs after install if they want stricter scoping.
146
+ derive_globs_for_recipe() {
147
+ local recipe_id="$1"
148
+ case "$recipe_id" in
149
+ monorepo) echo '"apps/**", "packages/**"' ;;
150
+ web-mobile-parity) echo '"apps/web/**", "apps/mobile/**", "packages/shared/**"' ;;
151
+ i18n) echo '"**/i18n/**", "**/translations.ts", "**/locales/**"' ;;
152
+ branch-strategy) echo '"**"' ;;
153
+ auto-mock-data) echo '"**/*.sql", "**/migrations/**", "**/seeds/**"' ;;
154
+ coding-conventions) echo '"**/*.ts", "**/*.tsx"' ;;
155
+ database-discipline) echo '"**/*.sql", "**/migrations/**"' ;;
156
+ design-system) echo '"**/*.tsx", "**/*.css", "**/*.scss"' ;;
157
+ tdd) echo '"**/*.test.*", "**/*.spec.*", "**/__tests__/**", "**/e2e/**"' ;;
158
+ *) echo '"**"' ;;
159
+ esac
160
+ }
161
+
162
+ # Emit a `.mdc` file from a `core/*.md` source.
163
+ # emit_mdc <src> <dest> <description> <globs-yaml-array> <alwaysApply-bool>
164
+ emit_mdc() {
165
+ local src="$1" dest="$2" desc="$3" globs="$4" always="$5"
166
+ if [ "$DRY_RUN" = "true" ]; then
167
+ log "would write $dest (alwaysApply=$always, globs=$globs)"
168
+ return
169
+ fi
170
+ /bin/cat > "$dest" <<EOF
171
+ ---
172
+ description: "$desc"
173
+ globs: [$globs]
174
+ alwaysApply: $always
175
+ ---
176
+
177
+ EOF
178
+ strip_frontmatter "$src" >> "$dest"
179
+ }
180
+
181
+ # backup_if_exists <dest>
182
+ # If <dest> is a regular file, copy it to <dest>.conductor-backup-<timestamp> before any
183
+ # downstream step overwrites it. Honors DRY_RUN. Idempotent across re-installs.
184
+ # Origin: ADR-019 (Claude adapter pattern, mirrored here per ADR-021).
185
+ backup_if_exists() {
186
+ local dest="$1"
187
+ if [ -f "$dest" ]; then
188
+ local ts
189
+ ts="$(/bin/date +%Y%m%d-%H%M%S)"
190
+ local backup="${dest}.conductor-backup-${ts}"
191
+ if [ "$DRY_RUN" = "true" ]; then
192
+ log "would back up existing $dest -> $backup"
193
+ else
194
+ /bin/cp "$dest" "$backup"
195
+ log " backed up existing $dest -> $backup"
196
+ fi
197
+ fi
198
+ }
199
+
200
+ # ----- manifest tracking (ADR-020, mirrored per ADR-021) ------------------
201
+ #
202
+ # Format identical to Claude adapter's manifest. POSIX shell + sed only — no jq.
203
+
204
+ MANIFEST_PATH="$TARGET_ABS/.conductor-manifest.json"
205
+ MANIFEST_STAGE_PATH=""
206
+ MANIFEST_TS=""
207
+ MANIFEST_LAST_BACKUP=""
208
+
209
+ init_manifest() {
210
+ if [ "$DRY_RUN" = "true" ]; then
211
+ log "would init manifest staging at $MANIFEST_PATH.staging"
212
+ return
213
+ fi
214
+ MANIFEST_TS="$(/bin/date -u +%Y-%m-%dT%H:%M:%SZ)"
215
+ MANIFEST_STAGE_PATH="$TARGET_ABS/.conductor-manifest.json.staging"
216
+ /bin/rm -f "$MANIFEST_STAGE_PATH"
217
+ : > "$MANIFEST_STAGE_PATH"
218
+ }
219
+
220
+ record_emit() {
221
+ if [ "$DRY_RUN" = "true" ] || [ "$UNINSTALL" = "true" ]; then
222
+ return
223
+ fi
224
+ local relpath="$1" src="$2" backup="${3:-}"
225
+ local had_backup="false"
226
+ [ -n "$backup" ] && had_backup="true"
227
+ local esc_path esc_src esc_backup
228
+ esc_path="$(printf '%s' "$relpath" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
229
+ esc_src="$(printf '%s' "$src" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
230
+ esc_backup="$(printf '%s' "$backup" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
231
+ printf ' {"path": "%s", "source": "%s", "had_backup": %s, "backup_path": "%s"},\n' \
232
+ "$esc_path" "$esc_src" "$had_backup" "$esc_backup" >> "$MANIFEST_STAGE_PATH"
233
+ }
234
+
235
+ finalize_manifest() {
236
+ if [ "$DRY_RUN" = "true" ]; then
237
+ log "would finalize manifest -> $MANIFEST_PATH"
238
+ return
239
+ fi
240
+ [ -z "$MANIFEST_STAGE_PATH" ] && return
241
+ [ -f "$MANIFEST_STAGE_PATH" ] || return
242
+
243
+ if [ -f "$MANIFEST_PATH" ]; then
244
+ backup_if_exists "$MANIFEST_PATH"
245
+ fi
246
+
247
+ local recipes_json="[]"
248
+ if [ -n "$RECIPES" ]; then
249
+ recipes_json="["
250
+ local IFS_BAK=$IFS
251
+ IFS=','
252
+ local first="true"
253
+ for r in $RECIPES; do
254
+ r="$(printf '%s' "$r" | /usr/bin/sed 's/^ *//; s/ *$//')"
255
+ [ -z "$r" ] && continue
256
+ if [ "$first" = "true" ]; then
257
+ recipes_json="${recipes_json}\"$r\""
258
+ first="false"
259
+ else
260
+ recipes_json="${recipes_json}, \"$r\""
261
+ fi
262
+ done
263
+ IFS=$IFS_BAK
264
+ recipes_json="${recipes_json}]"
265
+ fi
266
+
267
+ local entries
268
+ if [ -s "$MANIFEST_STAGE_PATH" ]; then
269
+ entries="$(/usr/bin/sed -e '$ s/,$//' "$MANIFEST_STAGE_PATH")"
270
+ else
271
+ entries=""
272
+ fi
273
+
274
+ /bin/cat > "$MANIFEST_PATH" <<EOF
275
+ {
276
+ "version": "v0.2.0",
277
+ "adapter": "cursor",
278
+ "install_timestamp": "$MANIFEST_TS",
279
+ "conductor_root": "$CONDUCTOR_ROOT",
280
+ "recipes_enabled": $recipes_json,
281
+ "legacy_cursorrules": $LEGACY_CURSORRULES,
282
+ "emitted_files": [
283
+ $entries
284
+ ]
285
+ }
286
+ EOF
287
+ /bin/rm -f "$MANIFEST_STAGE_PATH"
288
+ log " wrote manifest $MANIFEST_PATH"
289
+ }
290
+
291
+ backup_and_remember() {
292
+ MANIFEST_LAST_BACKUP=""
293
+ if [ -f "$1" ]; then
294
+ if [ "$DRY_RUN" = "true" ]; then
295
+ log "would back up existing $1 -> $1.conductor-backup-<ts>"
296
+ MANIFEST_LAST_BACKUP=""
297
+ else
298
+ local ts
299
+ ts="$(/bin/date +%Y%m%d-%H%M%S)"
300
+ local backup="$1.conductor-backup-$ts"
301
+ /bin/cp "$1" "$backup"
302
+ log " backed up existing $1 -> $backup"
303
+ MANIFEST_LAST_BACKUP="${backup#$TARGET_ABS/}"
304
+ fi
305
+ fi
306
+ }
307
+
308
+ # ----- uninstall flow (mirrored from Claude adapter) ----------------------
309
+
310
+ do_uninstall() {
311
+ log "uninstall mode (target: $TARGET_ABS)"
312
+
313
+ if [ ! -f "$MANIFEST_PATH" ]; then
314
+ if [ "$FORCE" = "true" ]; then
315
+ log "WARNING: no manifest at $MANIFEST_PATH — proceeding under --force (legacy backup scan only)"
316
+ uninstall_legacy_scan
317
+ return 0
318
+ fi
319
+ echo "Error: no manifest at $MANIFEST_PATH." >&2
320
+ echo " This target was either installed by a pre-manifest version or has already been uninstalled." >&2
321
+ echo " Re-run with --force to scan for legacy .conductor-backup-* files and delete them anyway:" >&2
322
+ echo " bash $0 $TARGET_ABS --uninstall --force" >&2
323
+ exit 1
324
+ fi
325
+
326
+ if [ -d "$TARGET_ABS/.git" ]; then
327
+ if [ -f "$TARGET_ABS/.git/MERGE_HEAD" ] || [ -f "$TARGET_ABS/.git/REBASE_HEAD" ] || [ -d "$TARGET_ABS/.git/rebase-merge" ]; then
328
+ if [ "$FORCE" != "true" ]; then
329
+ echo "Error: target has an active git operation (merge/rebase in progress)." >&2
330
+ echo " Resolve the in-flight operation first, or pass --force to override." >&2
331
+ exit 1
332
+ fi
333
+ log "WARNING: active git operation detected — proceeding under --force"
334
+ fi
335
+ fi
336
+
337
+ log "loading manifest entries..."
338
+ local entries_count=0
339
+ local restored=0
340
+ local deleted=0
341
+ local missing=0
342
+
343
+ while IFS= read -r line; do
344
+ case "$line" in
345
+ *'"path":'*'"source":'*'"had_backup":'*)
346
+ ;;
347
+ *) continue ;;
348
+ esac
349
+ entries_count=$((entries_count + 1))
350
+ local rel_path src had_backup backup_path
351
+ rel_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"path": *"([^"]*)".*/\1/')"
352
+ src="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"source": *"([^"]*)".*/\1/')"
353
+ had_backup="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"had_backup": *(true|false).*/\1/')"
354
+ backup_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"backup_path": *"([^"]*)".*/\1/')"
355
+
356
+ local abs_dest="$TARGET_ABS/$rel_path"
357
+ local abs_backup=""
358
+ [ -n "$backup_path" ] && abs_backup="$TARGET_ABS/$backup_path"
359
+
360
+ if [ "$had_backup" = "true" ] && [ -n "$abs_backup" ]; then
361
+ if [ -f "$abs_backup" ]; then
362
+ if [ "$DRY_RUN" = "true" ]; then
363
+ log " would restore $abs_backup -> $abs_dest"
364
+ else
365
+ /bin/mv -f "$abs_backup" "$abs_dest"
366
+ log " restored $abs_backup -> $abs_dest"
367
+ fi
368
+ restored=$((restored + 1))
369
+ else
370
+ if [ "$DRY_RUN" = "true" ]; then
371
+ log " would delete $abs_dest (backup $abs_backup missing)"
372
+ else
373
+ /bin/rm -f "$abs_dest"
374
+ log " deleted $abs_dest (backup $abs_backup missing)"
375
+ fi
376
+ missing=$((missing + 1))
377
+ fi
378
+ else
379
+ if [ -f "$abs_dest" ]; then
380
+ if [ "$DRY_RUN" = "true" ]; then
381
+ log " would delete $abs_dest"
382
+ else
383
+ /bin/rm -f "$abs_dest"
384
+ log " deleted $abs_dest"
385
+ fi
386
+ deleted=$((deleted + 1))
387
+ else
388
+ log " skip $abs_dest (already absent)"
389
+ fi
390
+ fi
391
+ done < "$MANIFEST_PATH"
392
+
393
+ if [ "$DRY_RUN" = "true" ]; then
394
+ log " would delete $MANIFEST_PATH"
395
+ for mb in "$MANIFEST_PATH".conductor-backup-*; do
396
+ [ -e "$mb" ] && log " would delete $mb"
397
+ done
398
+ else
399
+ /bin/rm -f "$MANIFEST_PATH"
400
+ for mb in "$MANIFEST_PATH".conductor-backup-*; do
401
+ [ -e "$mb" ] && /bin/rm -f "$mb"
402
+ done
403
+ log " deleted $MANIFEST_PATH"
404
+ fi
405
+
406
+ # Try to clean up empty .cursor/rules and .cursor dirs left behind.
407
+ # (children before parents so nested empties collapse in one pass)
408
+ for d in .cursor/rules .cursor/skills/reflect .cursor/skills .cursor/agents .cursor .conductor/reflect .conductor; do
409
+ local abs_d="$TARGET_ABS/$d"
410
+ if [ -d "$abs_d" ]; then
411
+ if [ "$DRY_RUN" = "true" ]; then
412
+ if [ -z "$(/bin/ls -A "$abs_d" 2>/dev/null)" ]; then
413
+ log " would rmdir empty $abs_d"
414
+ fi
415
+ else
416
+ /bin/rmdir "$abs_d" 2>/dev/null && log " rmdir empty $abs_d" || true
417
+ fi
418
+ fi
419
+ done
420
+
421
+ echo ""
422
+ echo "========================================================"
423
+ if [ "$DRY_RUN" = "true" ]; then
424
+ echo " Uninstall preview (dry-run)"
425
+ else
426
+ echo " Uninstall complete"
427
+ fi
428
+ echo " Target: $TARGET_ABS"
429
+ echo " Entries processed: $entries_count"
430
+ echo " Backups restored: $restored"
431
+ echo " Files deleted: $deleted"
432
+ echo " Backup-missing deletes: $missing"
433
+ echo "========================================================"
434
+ }
435
+
436
+ uninstall_legacy_scan() {
437
+ log "legacy scan mode — searching for .conductor-backup-* files under $TARGET_ABS"
438
+ local found=0
439
+ while IFS= read -r f; do
440
+ found=$((found + 1))
441
+ if [ "$DRY_RUN" = "true" ]; then
442
+ log " would delete legacy backup $f"
443
+ else
444
+ /bin/rm -f "$f"
445
+ log " deleted legacy backup $f"
446
+ fi
447
+ done < <(/usr/bin/find "$TARGET_ABS" -type f -name '*.conductor-backup-*' 2>/dev/null)
448
+ log "legacy scan: $found backup file(s)"
449
+ log "WARNING: legacy mode does not delete Conductor-emitted source files (no manifest)."
450
+ log " Delete .cursor/rules/{workflow,spec-as-you-go,quality-gates,operations,meta-discipline}.mdc manually if desired."
451
+ }
452
+
453
+ if [ "$UNINSTALL" = "true" ]; then
454
+ do_uninstall
455
+ exit 0
456
+ fi
457
+
458
+ # ----- onboarding wizard --------------------------------------------------
459
+ # Wizard fires when adopter signal is detected: existing .cursor/ OR existing .cursorrules.
460
+ # Otherwise (truly fresh target) wizard is skipped.
461
+
462
+ IS_ADOPTER_CASE="false"
463
+ if [ -d "$TARGET_ABS/.cursor" ] || [ -f "$TARGET_ABS/.cursorrules" ]; then
464
+ IS_ADOPTER_CASE="true"
465
+ fi
466
+
467
+ if [ "$IS_ADOPTER_CASE" = "true" ] && [ "$NO_PROMPT" = "false" ] && [ "$DRY_RUN" = "false" ]; then
468
+ echo ""
469
+ echo "========================================================"
470
+ echo " Welcome to CONDUCTOR setup (Cursor adapter)"
471
+ echo " Target: $TARGET_ABS"
472
+ echo "========================================================"
473
+ echo ""
474
+
475
+ printf "Detect existing rules? (y/N): "
476
+ read -r _detect_answer
477
+ if [ "$_detect_answer" = "y" ] || [ "$_detect_answer" = "Y" ]; then
478
+ _existing_rules=$(ls "$TARGET_ABS/.cursor/rules/" 2>/dev/null | wc -l | /usr/bin/tr -d ' ')
479
+ _has_legacy="no"
480
+ [ -f "$TARGET_ABS/.cursorrules" ] && _has_legacy="yes"
481
+ echo " Found $_existing_rules .mdc rule files in .cursor/rules/, .cursorrules present: $_has_legacy"
482
+ fi
483
+
484
+ printf "Apply universal-rules? (Y/n): "
485
+ read -r _apply_answer
486
+ if [ "$_apply_answer" = "n" ] || [ "$_apply_answer" = "N" ]; then
487
+ WIZARD_APPLY_RULES="false"
488
+ echo " Skipping universal-rules installation."
489
+ fi
490
+
491
+ echo ""
492
+ echo "Available recipes:"
493
+ echo " web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement"
494
+ printf "Select recipes (comma-separated, or leave blank for none): "
495
+ read -r _recipe_answer
496
+ if [ -n "$_recipe_answer" ]; then
497
+ RECIPES="$_recipe_answer"
498
+ echo " Recipes selected: $RECIPES"
499
+ else
500
+ echo " No recipes selected."
501
+ fi
502
+
503
+ printf "Also emit legacy .cursorrules bundle? (for Cursor < 0.45) (y/N): "
504
+ read -r _legacy_answer
505
+ if [ "$_legacy_answer" = "y" ] || [ "$_legacy_answer" = "Y" ]; then
506
+ LEGACY_CURSORRULES="true"
507
+ echo " Will emit .cursorrules bundle."
508
+ fi
509
+
510
+ echo ""
511
+ elif [ "$IS_ADOPTER_CASE" = "true" ] && [ "$NO_PROMPT" = "true" ]; then
512
+ log "Adopter case detected — applying defaults (--no-prompt): rules=yes, recipes=${RECIPES:-(none)}, legacy_cursorrules=$LEGACY_CURSORRULES"
513
+ fi
514
+
515
+ # ----- step 1: universal rules -> .cursor/rules/*.mdc --------------------
516
+
517
+ init_manifest
518
+
519
+ UNIVERSAL_RULES="workflow spec-as-you-go quality-gates operations meta-discipline"
520
+
521
+ if [ "$WIZARD_APPLY_RULES" = "true" ]; then
522
+ log "Step 1/4: universal-rules → .cursor/rules/"
523
+ mkdir_if_real "$TARGET_ABS/.cursor/rules"
524
+
525
+ for rule in $UNIVERSAL_RULES; do
526
+ src="$CORE_ROOT/universal-rules/$rule.md"
527
+ dest="$TARGET_ABS/.cursor/rules/$rule.mdc"
528
+ if [ ! -f "$src" ]; then
529
+ echo "Warning: $src not found; skipping" >&2
530
+ continue
531
+ fi
532
+ backup_and_remember "$dest"
533
+ desc="$(derive_description "$src")"
534
+ # All universal rules are always-loaded per core/universal-rules/README.md → alwaysApply: true
535
+ emit_mdc "$src" "$dest" "$desc" '"**"' "true"
536
+ record_emit ".cursor/rules/$rule.mdc" "core/universal-rules/$rule.md" "$MANIFEST_LAST_BACKUP"
537
+ done
538
+ else
539
+ log "Step 1/4: universal-rules — skipped (user opted out)"
540
+ fi
541
+
542
+ # ----- step 2: recipes (opt-in) -> .cursor/rules/*.mdc -------------------
543
+
544
+ log "Step 2/4: recipes (opt-in) → .cursor/rules/"
545
+ INSTALLED_RECIPES=""
546
+ if [ -n "$RECIPES" ]; then
547
+ mkdir_if_real "$TARGET_ABS/.cursor/rules"
548
+ IFS=',' read -ra RECIPE_LIST <<< "$RECIPES"
549
+ for r in "${RECIPE_LIST[@]}"; do
550
+ r="$(printf '%s' "$r" | /usr/bin/sed 's/^ *//; s/ *$//')"
551
+ [ -z "$r" ] && continue
552
+ src="$CORE_ROOT/recipes/$r.md"
553
+ dest="$TARGET_ABS/.cursor/rules/$r.mdc"
554
+ if [ ! -f "$src" ]; then
555
+ echo "Warning: recipe '$r' not found at $src; skipping" >&2
556
+ continue
557
+ fi
558
+ backup_and_remember "$dest"
559
+ desc="$(derive_description "$src")"
560
+ globs="$(derive_globs_for_recipe "$r")"
561
+ # Recipes are path-scoped (alwaysApply: false) — Cursor lazy-loads when a matching path is touched.
562
+ emit_mdc "$src" "$dest" "$desc" "$globs" "false"
563
+ record_emit ".cursor/rules/$r.mdc" "core/recipes/$r.md" "$MANIFEST_LAST_BACKUP"
564
+ INSTALLED_RECIPES="$INSTALLED_RECIPES $r"
565
+ done
566
+ else
567
+ log " (no recipes selected — pass --recipes=name1,name2 to install)"
568
+ fi
569
+
570
+ # ----- step 2.5: optional legacy .cursorrules bundle ---------------------
571
+ #
572
+ # When --legacy-cursorrules is set, also emit a flat .cursorrules file at the project root that
573
+ # concatenates the universal-rules body + selected recipes. This is for Cursor versions < 0.45
574
+ # that don't yet read .cursor/rules/*.mdc. Modern Cursor reads BOTH (per ADR-021) — adopters can
575
+ # safely keep both surfaces during a transition.
576
+
577
+ if [ "$LEGACY_CURSORRULES" = "true" ]; then
578
+ log "Step 2.5/4: legacy bundle → .cursorrules"
579
+ CURSORRULES_DEST="$TARGET_ABS/.cursorrules"
580
+ backup_and_remember "$CURSORRULES_DEST"
581
+ if [ "$DRY_RUN" = "true" ]; then
582
+ log "would write $CURSORRULES_DEST (universal + recipes bundle)"
583
+ else
584
+ {
585
+ echo "# CONDUCTOR — .cursorrules (legacy bundle)"
586
+ echo ""
587
+ echo "> Auto-generated by adapters/cursor/transform.sh with --legacy-cursorrules."
588
+ echo "> Modern Cursor (>= 0.45) reads .cursor/rules/*.mdc. This file is for older versions."
589
+ echo "> Edit .cursor/rules/*.mdc as the source of truth; re-run the adapter to regenerate."
590
+ echo ""
591
+ if [ "$WIZARD_APPLY_RULES" = "true" ]; then
592
+ for rule in $UNIVERSAL_RULES; do
593
+ src="$CORE_ROOT/universal-rules/$rule.md"
594
+ [ -f "$src" ] || continue
595
+ echo "## Universal rule: $rule"
596
+ echo ""
597
+ strip_frontmatter "$src"
598
+ echo ""
599
+ done
600
+ fi
601
+ if [ -n "$INSTALLED_RECIPES" ]; then
602
+ for r in $INSTALLED_RECIPES; do
603
+ src="$CORE_ROOT/recipes/$r.md"
604
+ [ -f "$src" ] || continue
605
+ echo "## Recipe: $r"
606
+ echo ""
607
+ strip_frontmatter "$src"
608
+ echo ""
609
+ done
610
+ fi
611
+ } > "$CURSORRULES_DEST"
612
+ record_emit ".cursorrules" "<synthesized>" "$MANIFEST_LAST_BACKUP"
613
+ log " wrote $CURSORRULES_DEST"
614
+ fi
615
+ fi
616
+
617
+ # ---- Step 2.6: self-improvement runtime (only with --recipes=self-improvement) ----
618
+ case ",$RECIPES," in
619
+ *",self-improvement,"*)
620
+ log "Step 2.6/4: self-improvement (Reflector) → hooks/skills/agents"
621
+ if [ "$DRY_RUN" != "true" ]; then
622
+ /bin/mkdir -p "$TARGET_ABS/.conductor/reflect" "$TARGET_ABS/.cursor/skills/reflect" "$TARGET_ABS/.cursor/agents"
623
+ gi="$TARGET_ABS/.gitignore"
624
+ grep -qxF '.conductor/' "$gi" 2>/dev/null || printf '\n# CONDUCTOR runtime (local trajectories/lessons)\n.conductor/\n' >> "$gi"
625
+ # portable scripts
626
+ for s in trajectory-log prune-lessons run-weekly; do
627
+ d="$TARGET_ABS/.conductor/reflect/$s.sh"
628
+ backup_and_remember "$d"; /bin/cp "$CORE_ROOT/reflector/$s.sh" "$d"; /bin/chmod +x "$d"
629
+ record_emit ".conductor/reflect/$s.sh" "core/reflector/$s.sh" "$MANIFEST_LAST_BACKUP"
630
+ done
631
+ # scheduling assets: run-weekly.sh needs the brief; SCHEDULING.md documents registration
632
+ for m in reflect-brief SCHEDULING; do
633
+ d="$TARGET_ABS/.conductor/reflect/$m.md"
634
+ backup_and_remember "$d"; /bin/cp "$CORE_ROOT/reflector/$m.md" "$d"
635
+ record_emit ".conductor/reflect/$m.md" "core/reflector/$m.md" "$MANIFEST_LAST_BACKUP"
636
+ done
637
+ # hook config — write only if absent (never clobber a user's hooks.json)
638
+ hc="$TARGET_ABS/.cursor/hooks.json"
639
+ if [ ! -f "$hc" ]; then
640
+ backup_and_remember "$hc"
641
+ /bin/cat > "$hc" <<'HOOK'
642
+ {
643
+ "version": 1,
644
+ "hooks": {
645
+ "stop": [ { "command": "./.conductor/reflect/trajectory-log.sh" } ]
646
+ }
647
+ }
648
+ HOOK
649
+ record_emit ".cursor/hooks.json" "<synthesized>" "$MANIFEST_LAST_BACKUP"
650
+ else
651
+ log " .cursor/hooks.json exists — add a stop entry calling ./.conductor/reflect/trajectory-log.sh manually"
652
+ fi
653
+ # /reflect skill (self-contained brief)
654
+ sk="$TARGET_ABS/.cursor/skills/reflect/SKILL.md"
655
+ backup_and_remember "$sk"
656
+ { printf -- '---\nname: reflect\ndescription: Run the CONDUCTOR Reflector — propose lessons from recent sessions (propose-only).\ndisable-model-invocation: true\n---\n\n'; /bin/cat "$CORE_ROOT/reflector/reflect-brief.md"; } > "$sk"
657
+ record_emit ".cursor/skills/reflect/SKILL.md" "core/reflector/reflect-brief.md" "$MANIFEST_LAST_BACKUP"
658
+ # reflector agent (native persona) — strip core frontmatter, add Cursor frontmatter
659
+ ag="$TARGET_ABS/.cursor/agents/reflector.md"
660
+ backup_and_remember "$ag"
661
+ { printf -- '---\nname: reflector\ndescription: Reads session trajectories and proposes atomic lesson deltas. Propose-only; never applies.\nmodel: inherit\nreadonly: true\n---\n\n'; strip_frontmatter "$CORE_ROOT/roles/reflector.md"; } > "$ag"
662
+ record_emit ".cursor/agents/reflector.md" "core/roles/reflector.md" "$MANIFEST_LAST_BACKUP"
663
+ fi
664
+ ;;
665
+ esac
666
+
667
+ # ----- step 3: docs templates --------------------------------------------
668
+
669
+ log "Step 3/4: docs templates → docs/"
670
+ mkdir_if_real "$TARGET_ABS/docs"
671
+ mkdir_if_real "$TARGET_ABS/docs/specs"
672
+
673
+ for tpl in CURRENT_WORK REMAINING_TASKS PLANS TASKS INDEX; do
674
+ src="$CORE_ROOT/docs-templates/$tpl.md"
675
+ dest="$TARGET_ABS/docs/$tpl.md"
676
+ [ -f "$src" ] || continue
677
+ if [ -f "$dest" ]; then
678
+ log " $dest exists — leaving in place"
679
+ continue
680
+ fi
681
+ if [ "$DRY_RUN" = "true" ]; then
682
+ log "would copy $src -> $dest"
683
+ else
684
+ /bin/cp "$src" "$dest"
685
+ record_emit "docs/$tpl.md" "core/docs-templates/$tpl.md" ""
686
+ fi
687
+ done
688
+
689
+ if [ -f "$CORE_ROOT/docs-templates/specs/_example.md" ]; then
690
+ src="$CORE_ROOT/docs-templates/specs/_example.md"
691
+ dest="$TARGET_ABS/docs/specs/_example.md"
692
+ if [ ! -f "$dest" ]; then
693
+ if [ "$DRY_RUN" = "true" ]; then
694
+ log "would copy $src -> $dest"
695
+ else
696
+ /bin/cp "$src" "$dest"
697
+ record_emit "docs/specs/_example.md" "core/docs-templates/specs/_example.md" ""
698
+ fi
699
+ fi
700
+ fi
701
+
702
+ # Finalize manifest after all emits.
703
+ finalize_manifest
704
+
705
+ # ----- step 4: completion summary ----------------------------------------
706
+
707
+ log "Step 4/4: activation reminder"
708
+ echo ""
709
+ echo "========================================================"
710
+ echo " Done."
711
+ echo " Target: $TARGET_ABS"
712
+ echo " Adapter: cursor"
713
+ echo " Universal rules: 5 (.cursor/rules/*.mdc, alwaysApply:true)"
714
+ echo " Recipes installed:${INSTALLED_RECIPES:- (none)}"
715
+ if [ "$LEGACY_CURSORRULES" = "true" ]; then
716
+ echo " Legacy .cursorrules bundle: emitted"
717
+ fi
718
+ echo ""
719
+ echo " Skipped (per ADR-004 honesty):"
720
+ echo " - Hooks: CONDUCTOR emits the Reflector hook when --recipes=self-improvement (ADR-032); other guards remain Claude-only (ADR-034)."
721
+ echo " - Sub-agent personas: Cursor has no sub-agent dispatch — single chat session per task."
722
+ echo " - Hookify rule templates: Claude-only plugin."
723
+ echo ""
724
+ echo " Activation: reload Cursor window (Cmd/Ctrl+Shift+P → 'Developer: Reload Window')."
725
+ echo "========================================================"
726
+ echo ""
727
+ echo "Next steps for the project:"
728
+ echo " 1. Open $TARGET_ABS in Cursor."
729
+ echo " 2. Edit docs/CURRENT_WORK.md with your project's current state."
730
+ echo " 3. Verify rule loading: open the Cursor settings → Rules tab → confirm 5 universal + recipes shown."
731
+ echo " 4. Tighten recipe globs if needed (.cursor/rules/<recipe>.mdc has a sensible default)."