omniconductor 0.5.0 → 1.0.1

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 (52) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +59 -19
  3. package/VISION.md +2 -2
  4. package/adapters/README.md +8 -8
  5. package/adapters/claude/README.md +3 -6
  6. package/adapters/claude/SUPPORTED-FEATURES.md +4 -4
  7. package/adapters/claude/metadata.json +39 -0
  8. package/adapters/claude/transform-spec.md +1 -1
  9. package/adapters/claude/transform.sh +208 -38
  10. package/adapters/codex/README.md +23 -27
  11. package/adapters/codex/metadata.json +35 -0
  12. package/adapters/codex/transform-spec.md +5 -6
  13. package/adapters/codex/transform.sh +284 -37
  14. package/adapters/copilot/README.md +26 -29
  15. package/adapters/copilot/metadata.json +36 -0
  16. package/adapters/copilot/transform.sh +126 -35
  17. package/adapters/cursor/README.md +31 -30
  18. package/adapters/cursor/metadata.json +35 -0
  19. package/adapters/cursor/transform.sh +117 -28
  20. package/adapters/gemini/README.md +14 -15
  21. package/adapters/gemini/metadata.json +36 -0
  22. package/adapters/gemini/transform.sh +312 -36
  23. package/adapters/windsurf/README.md +20 -19
  24. package/adapters/windsurf/metadata.json +36 -0
  25. package/adapters/windsurf/transform.sh +139 -55
  26. package/bin/doctor.js +257 -0
  27. package/bin/omniconductor.js +15 -2
  28. package/core/anti-patterns/frequent-rule-file-edit.md +1 -1
  29. package/core/anti-patterns/single-monolithic-rule-file.md +1 -1
  30. package/core/hooks/README.md +1 -0
  31. package/core/hooks/pretool-loop-guard.sh.template +177 -0
  32. package/core/recipes/README.md +6 -4
  33. package/core/recipes/loop-engineering.md +73 -0
  34. package/core/universal-rules/README.md +4 -4
  35. package/core/universal-rules/meta-discipline.md +4 -4
  36. package/core/universal-rules/spec-as-you-go.md +1 -1
  37. package/docs/ADAPTER-LIVE-VERIFICATION.md +73 -0
  38. package/docs/COMPARISON.md +133 -0
  39. package/docs/COMPATIBILITY-MATRIX.md +126 -0
  40. package/docs/DESIGN-DECISIONS.md +1268 -0
  41. package/docs/MANUAL-INSTALL.md +15 -15
  42. package/docs/PUBLICATION-POLICY.md +46 -0
  43. package/docs/PUBLISH-GUIDE.md +141 -0
  44. package/package.json +7 -1
  45. package/tools/check-adapter-metadata.sh +211 -0
  46. package/tools/check-stale-tokens.sh +130 -0
  47. package/tools/generate-adapter-docs.js +123 -0
  48. package/tools/live-verify.sh +195 -0
  49. package/tools/manifest-safety.sh +118 -0
  50. package/tools/stale-tokens.txt +32 -0
  51. package/tools/test-install-modes.sh +277 -0
  52. package/tools/validate-adapter-output.sh +1 -1
@@ -26,13 +26,14 @@
26
26
  # core/recipes/coding-conventions → <target>/.gemini/styleguide.md (Gemini style-guide convention; opt-in)
27
27
  # core/docs-templates/*.md → <target>/docs/*.md (CURRENT_WORK, REMAINING_TASKS, etc.)
28
28
  # core/hooks/*.sh.template → SKIPPED (Reflector hook emitted via --recipes=self-improvement, ADR-032; other guards Claude-only, ADR-034)
29
- # core/roles/*.md → SKIPPED (Gemini has no sub-agent dispatch)
29
+ # core/roles/*.md → SKIPPED (role emission is Claude-only today; Gemini supports sub-agents natively — ADR-031)
30
30
  #
31
31
  # Gemini reality (per adapters/gemini/SUPPORTED-FEATURES.md):
32
32
  # - Single always-loaded rule file (GEMINI.md). No per-pattern rule scoping.
33
- # - No sub-agents, no per-call model routing, no built-in memory dir. CONDUCTOR
33
+ # - Gemini CLI supports sub-agents / hooks / per-call model routing natively
34
+ # (ADR-031), but this adapter does not emit them yet (Phase 2). CONDUCTOR
34
35
  # emits the Reflector hook when --recipes=self-improvement (ADR-032); other
35
- # guards remain Claude-only (ADR-034).
36
+ # guards remain Claude-only emission (ADR-034).
36
37
  # The bundle below carries the rule TEXT honestly; Claude-only enforcement mechanisms
37
38
  # are noted as self-policed for Gemini, never faked.
38
39
 
@@ -42,6 +43,7 @@ set -eu
42
43
 
43
44
  TARGET=""
44
45
  RECIPES=""
46
+ MODE="full"
45
47
  DRY_RUN="false"
46
48
  NO_PROMPT="false"
47
49
  UNINSTALL="false"
@@ -53,6 +55,7 @@ WIZARD_APPLY_RULES="true"
53
55
  while [ $# -gt 0 ]; do
54
56
  case "$1" in
55
57
  --recipes=*) RECIPES="${1#--recipes=}" ;;
58
+ --mode=*) MODE="${1#--mode=}" ;;
56
59
  --dry-run) DRY_RUN="true" ;;
57
60
  --no-prompt) NO_PROMPT="true" ;;
58
61
  --uninstall|--rollback) UNINSTALL="true" ;;
@@ -63,6 +66,17 @@ Usage: bash adapters/gemini/transform.sh <target-project> [options]
63
66
 
64
67
  Options:
65
68
  --recipes=A,B,C Comma-separated list of recipes to install (appended into GEMINI.md)
69
+ --mode=<m> Install preset (ADR-044). One of:
70
+ full (default) everything this adapter emits today
71
+ minimal discipline text + session continuity only
72
+ (GEMINI.md + docs/; no styleguide, no Reflector runtime)
73
+ strict full, but ABORT (exit 3) if GEMINI.md already exists
74
+ (never overwrites a baseline, even with backup)
75
+ recipes-only ONLY the selected recipes, appended to GEMINI.md as a
76
+ marked block (requires --recipes=; block-aware uninstall)
77
+ reflector-only the self-improvement loop standalone (recipe text as a
78
+ marked block + Reflector runtime; least-conflicting with
79
+ other frameworks like Spec Kit / BMAD)
66
80
  --dry-run Preview only — no files written
67
81
  --no-prompt Skip all interactive prompts; apply sensible defaults (CI-safe)
68
82
  --uninstall Revert a previous install using <target>/.conductor-manifest.json
@@ -71,7 +85,7 @@ Options:
71
85
  manifest are preserved.
72
86
  --force Bypass uninstall safety checks (active worktrees, missing manifest)
73
87
 
74
- Recipes available: web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement, git-hygiene
88
+ Recipes available: web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement, git-hygiene, loop-engineering
75
89
 
76
90
  Gemini single-file model:
77
91
  - All 5 universal rules + selected recipes are bundled into one always-loaded GEMINI.md.
@@ -81,8 +95,8 @@ Gemini single-file model:
81
95
 
82
96
  What this adapter does NOT install (per ADR-004 honesty + ADR-021):
83
97
  - Hook guards (CONDUCTOR emits the Reflector hook when --recipes=self-improvement, ADR-032; other guards remain Claude-only, ADR-034)
84
- - Sub-agent personas (Gemini has no sub-agent dispatch single chat session per task)
85
- - Per-call model routing (single model per Gemini session)
98
+ - Sub-agent personas (not yet emitted for Gemini the tool supports sub-agents natively, ADR-031; agent emission is Phase 2)
99
+ - Per-call model routing (supported natively by Gemini CLI, ADR-031; not yet automated by CONDUCTOR)
86
100
  - Built-in memory directory (DIY at .memory/ — see the note inside GEMINI.md)
87
101
  EOF
88
102
  exit 0
@@ -105,11 +119,31 @@ if [ -z "$TARGET" ]; then
105
119
  exit 1
106
120
  fi
107
121
 
122
+ case "$MODE" in
123
+ full|minimal|strict|recipes-only|reflector-only) : ;;
124
+ *) echo "Error: unknown --mode '$MODE' (one of: full, minimal, strict, recipes-only, reflector-only)" >&2; exit 1 ;;
125
+ esac
126
+ if [ "$MODE" = "reflector-only" ]; then
127
+ if [ -n "$RECIPES" ] && [ "$RECIPES" != "self-improvement" ]; then
128
+ echo "NOTE: --mode=reflector-only ignores --recipes (installs self-improvement only)" >&2
129
+ fi
130
+ RECIPES="self-improvement"
131
+ fi
132
+ if [ "$MODE" = "recipes-only" ] && [ -z "$RECIPES" ] && [ "$UNINSTALL" != "true" ]; then
133
+ echo "Error: --mode=recipes-only requires --recipes=A,B,..." >&2
134
+ exit 1
135
+ fi
136
+
108
137
  # Resolve CONDUCTOR root (where this script lives: adapters/gemini/).
109
138
  CONDUCTOR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
110
139
  CORE_ROOT="$CONDUCTOR_ROOT/core"
111
140
  [ -d "$CORE_ROOT" ] || { echo "Error: core/ not found at $CORE_ROOT" >&2; exit 1; }
112
141
 
142
+ # CONDUCTOR package version for the manifest — parsed at runtime from package.json
143
+ # so releases never drift the manifest (falls back to "unknown" on any error).
144
+ CONDUCTOR_VERSION="$(/usr/bin/sed -n -E 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$CONDUCTOR_ROOT/package.json" 2>/dev/null | /usr/bin/head -n 1)"
145
+ [ -n "$CONDUCTOR_VERSION" ] || CONDUCTOR_VERSION="unknown"
146
+
113
147
  if [ "$DRY_RUN" = "true" ]; then
114
148
  mkdir -p "$TARGET"
115
149
  fi
@@ -203,6 +237,9 @@ MANIFEST_STAGE_PATH=""
203
237
  MANIFEST_TS=""
204
238
  MANIFEST_LAST_BACKUP=""
205
239
 
240
+ # shellcheck source=../../tools/manifest-safety.sh
241
+ . "$CONDUCTOR_ROOT/tools/manifest-safety.sh"
242
+
206
243
  init_manifest() {
207
244
  if [ "$DRY_RUN" = "true" ]; then
208
245
  log "would init manifest staging at $MANIFEST_PATH.staging"
@@ -221,12 +258,13 @@ record_emit() {
221
258
  local relpath="$1" src="$2" backup="${3:-}"
222
259
  local had_backup="false"
223
260
  [ -n "$backup" ] && had_backup="true"
224
- local esc_path esc_src esc_backup
261
+ local esc_path esc_src esc_backup emitted_sha
225
262
  esc_path="$(printf '%s' "$relpath" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
226
263
  esc_src="$(printf '%s' "$src" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
227
264
  esc_backup="$(printf '%s' "$backup" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
228
- printf ' {"path": "%s", "source": "%s", "had_backup": %s, "backup_path": "%s"},\n' \
229
- "$esc_path" "$esc_src" "$had_backup" "$esc_backup" >> "$MANIFEST_STAGE_PATH"
265
+ emitted_sha="$(conductor_sha256_file "$TARGET_ABS/$relpath")"
266
+ printf ' {"path": "%s", "source": "%s", "had_backup": %s, "backup_path": "%s", "sha256": "%s"},\n' \
267
+ "$esc_path" "$esc_src" "$had_backup" "$esc_backup" "$emitted_sha" >> "$MANIFEST_STAGE_PATH"
230
268
  }
231
269
 
232
270
  finalize_manifest() {
@@ -270,8 +308,9 @@ finalize_manifest() {
270
308
 
271
309
  /bin/cat > "$MANIFEST_PATH" <<EOF
272
310
  {
273
- "version": "v0.2.0",
311
+ "version": "v$CONDUCTOR_VERSION",
274
312
  "adapter": "gemini",
313
+ "mode": "$MODE",
275
314
  "install_timestamp": "$MANIFEST_TS",
276
315
  "conductor_root": "$CONDUCTOR_ROOT",
277
316
  "recipes_enabled": $recipes_json,
@@ -285,20 +324,98 @@ EOF
285
324
  }
286
325
 
287
326
  backup_and_remember() {
288
- MANIFEST_LAST_BACKUP=""
289
- if [ -f "$1" ]; then
290
- if [ "$DRY_RUN" = "true" ]; then
291
- log "would back up existing $1 -> $1.conductor-backup-<ts>"
292
- MANIFEST_LAST_BACKUP=""
293
- else
294
- local ts
295
- ts="$(/bin/date +%Y%m%d-%H%M%S)"
296
- local backup="$1.conductor-backup-$ts"
297
- /bin/cp "$1" "$backup"
298
- log " backed up existing $1 -> $backup"
299
- MANIFEST_LAST_BACKUP="${backup#$TARGET_ABS/}"
327
+ conductor_manifest_backup_and_remember "$1"
328
+ }
329
+
330
+ # ----- marked append-blocks (ADR-044, --mode=recipes-only / reflector-only) ----
331
+ #
332
+ # Single-file tools can't take recipes as separate files, so à-la-carte modes
333
+ # APPEND a marked block to the existing baseline instead of overwriting it:
334
+ # <!-- conductor:block <name> -->
335
+ # ...content...
336
+ # <!-- /conductor:block <name> -->
337
+ # The manifest records {"type": "block", "sha256": <hash of content>, "created_file"}.
338
+ # Uninstall strips the block only when its content hash still matches (a customized
339
+ # block is left in place with a warning — backup ≠ silently destroying user edits).
340
+
341
+ sha256_of() {
342
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum | /usr/bin/awk '{print $1}'
343
+ else /usr/bin/shasum -a 256 | /usr/bin/awk '{print $1}'; fi
344
+ }
345
+
346
+ # append_block <abs_file> <block_name> <content_file>
347
+ # Sets BLOCK_SHA + BLOCK_CREATED. Replaces the block if markers already exist.
348
+ append_block() {
349
+ local f="$1" name="$2" content="$3" rel entry expected_sha current_sha open_count close_count
350
+ local open="<!-- conductor:block $name -->" close="<!-- /conductor:block $name -->"
351
+ BLOCK_CREATED="false"; BLOCK_SHA=""
352
+ if [ "$DRY_RUN" = "true" ]; then
353
+ log "would append marked block '$name' to $f"
354
+ return
355
+ fi
356
+ # Content must never contain the marker syntax itself — a colliding line would
357
+ # truncate extraction AND stripping while the hash guard computes the same
358
+ # truncation on both sides (silent data loss). Refuse instead.
359
+ if /usr/bin/grep -qE '<!-- /?conductor:block ' "$content"; then
360
+ echo "Error: block content contains the conductor:block marker syntax — refusing to append." >&2
361
+ /bin/rm -f "${MANIFEST_STAGE_PATH:-}"
362
+ exit 1
363
+ fi
364
+ if [ ! -f "$f" ]; then
365
+ BLOCK_CREATED="true"
366
+ : > "$f"
367
+ else
368
+ rel="${f#$TARGET_ABS/}"
369
+ open_count="$(/usr/bin/grep -cF "$open" "$f" || true)"
370
+ close_count="$(/usr/bin/grep -cF "$close" "$f" || true)"
371
+ if [ "$open_count" -ne 0 ] || [ "$close_count" -ne 0 ]; then
372
+ if [ "$open_count" -ne 1 ] || [ "$close_count" -ne 1 ]; then
373
+ echo "Error: found an unpaired or duplicate '$name' CONDUCTOR marker in $f; refusing to change user content." >&2
374
+ /bin/rm -f "${MANIFEST_STAGE_PATH:-}"
375
+ exit 1
376
+ fi
377
+ entry="$(conductor_manifest_block_entry "$rel" "$name" 2>/dev/null || true)"
378
+ if [ -z "$entry" ]; then
379
+ echo "Error: '$name' marker in $f is not owned by this install manifest; refusing to replace user content." >&2
380
+ /bin/rm -f "${MANIFEST_STAGE_PATH:-}"
381
+ exit 1
382
+ fi
383
+ expected_sha="$(conductor_manifest_field "$entry" sha256 2>/dev/null || true)"
384
+ current_sha="$(/usr/bin/awk -v o="$open" -v c="$close" '$0==o{b=1;next} $0==c{b=0;next} b' "$f" | sha256_of)"
385
+ if [ -z "$expected_sha" ] || [ "$current_sha" != "$expected_sha" ]; then
386
+ echo "Error: managed '$name' block in $f was customized; refusing to overwrite it." >&2
387
+ /bin/rm -f "${MANIFEST_STAGE_PATH:-}"
388
+ exit 1
389
+ fi
390
+ # The manifest owns this one, unmodified block, so replacement is safe.
391
+ /usr/bin/awk -v o="$open" -v c="$close" '$0==o{inb=1; if (heldset && held ~ /^[[:space:]]*$/) heldset=0; next} $0==c{inb=0;next} inb{next} {if (heldset) print held; held=$0; heldset=1} END{if (heldset) print held}' "$f" > "$f.conductor-tmp"
392
+ /bin/mv "$f.conductor-tmp" "$f"
393
+ log " replaced existing block '$name' in $f"
300
394
  fi
301
395
  fi
396
+ { echo ""; echo "$open"; /bin/cat "$content"; echo "$close"; } >> "$f"
397
+ BLOCK_SHA="$(/usr/bin/awk -v o="$open" -v c="$close" '$0==o{b=1;next} $0==c{b=0;next} b' "$f" | sha256_of)"
398
+ log " appended block '$name' to $f (sha256 $(printf '%s' "$BLOCK_SHA" | /usr/bin/cut -c1-12)...)"
399
+ }
400
+
401
+ record_emit_block() {
402
+ if [ "$DRY_RUN" = "true" ] || [ "$UNINSTALL" = "true" ]; then return; fi
403
+ local relpath="$1" name="$2" sha="$3" created="$4"
404
+ printf ' {"path": "%s", "type": "block", "block": "%s", "sha256": "%s", "created_file": %s},\n' \
405
+ "$relpath" "$name" "$sha" "$created" >> "$MANIFEST_STAGE_PATH"
406
+ }
407
+
408
+ # ----- framework detection (ADR-044 — suggest, NEVER auto-switch) ----------
409
+
410
+ detect_coexisting_frameworks() {
411
+ local found=""
412
+ [ -d "$TARGET_ABS/.specify" ] && found="$found Spec-Kit"
413
+ { [ -d "$TARGET_ABS/_bmad" ] || [ -d "$TARGET_ABS/.bmad-core" ]; } && found="$found BMAD"
414
+ if [ -n "$found" ] && [ "$MODE" = "full" ]; then
415
+ log "NOTE: detected coexisting framework(s):$found"
416
+ log " Consider --mode=recipes-only or --mode=reflector-only to coexist without"
417
+ log " overlapping workflow rules (suggestion only — nothing was changed)."
418
+ fi
302
419
  }
303
420
 
304
421
  # ----- uninstall flow (mirrored from Cursor/Claude adapter) ---------------
@@ -335,24 +452,80 @@ do_uninstall() {
335
452
  local restored=0
336
453
  local deleted=0
337
454
  local missing=0
455
+ local preserved=0
456
+
457
+ local blocks_removed=0
458
+ local blocks_kept=0
338
459
 
339
460
  while IFS= read -r line; do
340
461
  case "$line" in
462
+ *'"type": "block"'*)
463
+ # Marked append-block entry (ADR-044) — strip the block, hash-guarded.
464
+ entries_count=$((entries_count + 1))
465
+ local b_rel b_name b_sha b_created b_abs b_open b_close b_cur b_cur_sha
466
+ b_rel="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"path": *"([^"]*)".*/\1/')"
467
+ b_name="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"block": *"([^"]*)".*/\1/')"
468
+ b_sha="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"sha256": *"([^"]*)".*/\1/')"
469
+ b_created="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"created_file": *(true|false).*/\1/')"
470
+ b_abs="$TARGET_ABS/$b_rel"
471
+ b_open="<!-- conductor:block $b_name -->"
472
+ b_close="<!-- /conductor:block $b_name -->"
473
+ if [ ! -f "$b_abs" ]; then
474
+ log " skip block '$b_name' ($b_rel already absent)"
475
+ continue
476
+ fi
477
+ if ! /usr/bin/grep -qF "$b_open" "$b_abs"; then
478
+ log " skip block '$b_name' (markers already removed from $b_rel)"
479
+ continue
480
+ fi
481
+ # Hash via the same direct pipe as emit-time (a shell-variable roundtrip
482
+ # strips trailing newlines and breaks the comparison).
483
+ b_cur_sha="$(/usr/bin/awk -v o="$b_open" -v c="$b_close" '$0==o{b=1;next} $0==c{b=0;next} b' "$b_abs" | sha256_of)"
484
+ if [ "$b_cur_sha" = "$b_sha" ]; then
485
+ if [ "$DRY_RUN" = "true" ]; then
486
+ log " would strip block '$b_name' from $b_rel"
487
+ else
488
+ /usr/bin/awk -v o="$b_open" -v c="$b_close" '$0==o{inb=1; if (heldset && held ~ /^[[:space:]]*$/) heldset=0; next} $0==c{inb=0;next} inb{next} {if (heldset) print held; held=$0; heldset=1} END{if (heldset) print held}' "$b_abs" > "$b_abs.conductor-tmp"
489
+ /bin/mv "$b_abs.conductor-tmp" "$b_abs"
490
+ log " stripped block '$b_name' from $b_rel"
491
+ if [ "$b_created" = "true" ] && ! /usr/bin/grep -q '[^[:space:]]' "$b_abs"; then
492
+ /bin/rm -f "$b_abs"
493
+ log " deleted $b_rel (file was created by CONDUCTOR and is now empty)"
494
+ fi
495
+ fi
496
+ blocks_removed=$((blocks_removed + 1))
497
+ else
498
+ log " WARNING: block '$b_name' in $b_rel was customized (hash mismatch) — left in place"
499
+ blocks_kept=$((blocks_kept + 1))
500
+ fi
501
+ continue
502
+ ;;
341
503
  *'"path":'*'"source":'*'"had_backup":'*)
342
504
  ;;
343
505
  *) continue ;;
344
506
  esac
345
507
  entries_count=$((entries_count + 1))
346
- local rel_path src had_backup backup_path
508
+ local rel_path src had_backup backup_path expected_sha
347
509
  rel_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"path": *"([^"]*)".*/\1/')"
348
510
  src="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"source": *"([^"]*)".*/\1/')"
349
511
  had_backup="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"had_backup": *(true|false).*/\1/')"
350
512
  backup_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"backup_path": *"([^"]*)".*/\1/')"
513
+ expected_sha="$(conductor_manifest_field "$line" sha256 2>/dev/null || true)"
351
514
 
352
515
  local abs_dest="$TARGET_ABS/$rel_path"
353
516
  local abs_backup=""
354
517
  [ -n "$backup_path" ] && abs_backup="$TARGET_ABS/$backup_path"
355
518
 
519
+ if [ -f "$abs_dest" ] && ! conductor_manifest_file_matches "$abs_dest" "$expected_sha"; then
520
+ if [ -z "$expected_sha" ]; then
521
+ log " WARNING: preserving $rel_path (legacy manifest has no checksum)"
522
+ else
523
+ log " WARNING: preserving user-modified $rel_path"
524
+ fi
525
+ preserved=$((preserved + 1))
526
+ continue
527
+ fi
528
+
356
529
  if [ "$had_backup" = "true" ] && [ -n "$abs_backup" ]; then
357
530
  if [ -f "$abs_backup" ]; then
358
531
  if [ "$DRY_RUN" = "true" ]; then
@@ -425,6 +598,10 @@ do_uninstall() {
425
598
  echo " Backups restored: $restored"
426
599
  echo " Files deleted: $deleted"
427
600
  echo " Backup-missing deletes: $missing"
601
+ [ "$preserved" -gt 0 ] && echo " User-modified files preserved: $preserved"
602
+ if [ "$blocks_removed" -gt 0 ] || [ "$blocks_kept" -gt 0 ]; then
603
+ echo " Blocks stripped: $blocks_removed (customized blocks left: $blocks_kept)"
604
+ fi
428
605
  echo "========================================================"
429
606
  }
430
607
 
@@ -459,6 +636,24 @@ if [ -f "$TARGET_ABS/GEMINI.md" ] || [ -d "$TARGET_ABS/.gemini" ]; then
459
636
  IS_ADOPTER_CASE="true"
460
637
  fi
461
638
 
639
+ detect_coexisting_frameworks
640
+
641
+ # --mode=strict: never overwrite an existing baseline, even with a backup (ADR-044).
642
+ if [ "$MODE" = "strict" ]; then
643
+ for _sf in "GEMINI.md" ".gemini/styleguide.md"; do
644
+ if [ -f "$TARGET_ABS/$_sf" ]; then
645
+ echo "Error (--mode=strict): $TARGET_ABS/$_sf already exists — strict mode aborts instead of overwriting a baseline." >&2
646
+ echo " Use --mode=full (timestamped backup + manifest-based restore), or move the file first." >&2
647
+ exit 3
648
+ fi
649
+ done
650
+ fi
651
+
652
+ # À-la-carte modes are non-interactive by design (they never touch the universal-rule choice).
653
+ if [ "$MODE" != "full" ] && [ "$MODE" != "strict" ]; then
654
+ NO_PROMPT="true"
655
+ fi
656
+
462
657
  if [ "$IS_ADOPTER_CASE" = "true" ] && [ "$NO_PROMPT" = "false" ] && [ "$DRY_RUN" = "false" ]; then
463
658
  echo ""
464
659
  echo "========================================================"
@@ -486,7 +681,7 @@ if [ "$IS_ADOPTER_CASE" = "true" ] && [ "$NO_PROMPT" = "false" ] && [ "$DRY_RUN"
486
681
 
487
682
  echo ""
488
683
  echo "Available recipes:"
489
- echo " web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement, git-hygiene"
684
+ echo " web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement, git-hygiene, loop-engineering"
490
685
  printf "Select recipes (comma-separated, or leave blank for none): "
491
686
  read -r _recipe_answer
492
687
  if [ -n "$_recipe_answer" ]; then
@@ -520,6 +715,8 @@ if [ -n "$RECIPES" ]; then
520
715
  IFS="$_old_ifs"
521
716
  fi
522
717
 
718
+ if [ "$MODE" != "recipes-only" ] && [ "$MODE" != "reflector-only" ]; then
719
+
523
720
  log "Step 1/3: GEMINI.md bundle → $GEMINI_DEST"
524
721
 
525
722
  if [ -f "$GEMINI_DEST" ] && [ "$DRY_RUN" = "false" ]; then
@@ -547,19 +744,22 @@ else
547
744
  **EN** — You are the lead orchestrator for **{{PROJECT_NAME}}**. You translate the
548
745
  user's intent into a disciplined Plan → Architecture → Tasks → Implementation →
549
746
  Review → Spec workflow. The universal rules below are your operating floor; every
550
- turn inherits them. Gemini CLI runs a single chat session per task — there are no
551
- sub-agents and no enforcement hooks here, so these rules are **self-policed**: you
552
- follow them by reading them, not because a hook blocks you.
747
+ turn inherits them. Gemini CLI supports sub-agents and hooks natively (ADR-031),
748
+ but CONDUCTOR's Gemini adapter currently emits rule text (plus the Reflector loop)
749
+ only full hook/agent emission is Phase 2 so these rules are **self-policed**:
750
+ you follow them by reading them, not because an emitted hook blocks you.
553
751
 
554
752
  **KO** — 당신은 **{{PROJECT_NAME}}** 의 리드 오케스트레이터입니다. 사용자의 의도를
555
753
  Plan → Architecture → Tasks → Implementation → Review → Spec 워크플로로 옮깁니다.
556
- 아래 universal rule 은 모든 턴이 상속하는 기본 규칙입니다. Gemini CLI 는 작업당 단일
557
- 세션으로 동작하며 sub-agent enforcement hook 없습니다 따라서 이 규칙들은
754
+ 아래 universal rule 은 모든 턴이 상속하는 기본 규칙입니다. Gemini CLI 는 sub-agent
755
+ hook 네이티브로 지원하지만 (ADR-031), CONDUCTOR Gemini adapter 현재 rule 텍스트
756
+ (+ Reflector loop) 만 생성합니다 (hook/agent 자동 생성은 Phase 2) — 따라서 이 규칙들은
558
757
  **자기 규율(self-policed)** 로 지켜야 합니다.
559
758
 
560
759
  > **Note (Gemini)**: Claude Code enforces parts of these rules with PreToolUse / Stop
561
- > hooks and sub-agent dispatch. Gemini CLI has none of those surfaces. Where a rule
562
- > mentions a Claude-only mechanism, treat it as self-policed: read the rule, follow it.
760
+ > hooks and sub-agent dispatch. Gemini CLI has those surfaces natively too (ADR-031),
761
+ > but CONDUCTOR does not emit hooks/agents here yet (Phase 2). Where a rule mentions a
762
+ > Claude-only mechanism, treat it as self-policed: read the rule, follow it.
563
763
 
564
764
  ## ABSOLUTE rules (summary) / 절대 규칙 요약
565
765
 
@@ -699,8 +899,68 @@ FOOTER_EOF
699
899
  fi
700
900
  record_emit "GEMINI.md" "<synthesized:5-universal-rules+workflow>" "$MANIFEST_LAST_BACKUP"
701
901
 
902
+ else
903
+ # ----- à-la-carte modes: marked block appended to GEMINI.md (ADR-044) ------
904
+ BLOCK_NAME="recipes"; [ "$MODE" = "reflector-only" ] && BLOCK_NAME="reflector"
905
+ log "Step 1/3: --mode=$MODE — '$BLOCK_NAME' marked block → $GEMINI_DEST (no full bundle)"
906
+ if [ "$DRY_RUN" = "true" ]; then
907
+ log "would append marked block '$BLOCK_NAME' (selected recipes: $RECIPES) to $GEMINI_DEST"
908
+ else
909
+ _blk="$(/usr/bin/mktemp "${TMPDIR:-/tmp}/conductor-block.XXXXXX")"
910
+ {
911
+ echo "# CONDUCTOR — à la carte (--mode=$MODE)"
912
+ echo ""
913
+ echo "> Installed by CONDUCTOR WITHOUT the universal-rule bundle. This is a managed"
914
+ echo "> block: --uninstall strips it when unmodified. Full workflow: --mode=full."
915
+ echo ""
916
+ IFS=',' read -ra RECIPE_LIST <<< "$RECIPES"
917
+ for r in "${RECIPE_LIST[@]}"; do
918
+ r="$(printf '%s' "$r" | /usr/bin/sed 's/^ *//; s/ *$//')"
919
+ [ -z "$r" ] && continue
920
+ src="$CORE_ROOT/recipes/$r.md"
921
+ if [ ! -f "$src" ]; then
922
+ echo "Warning: recipe '$r' not found at $src; skipping" >&2
923
+ continue
924
+ fi
925
+ echo "## Recipe — $r"
926
+ echo ""
927
+ emit_rule_body "$src"
928
+ echo ""
929
+ INSTALLED_RECIPES="$INSTALLED_RECIPES $r"
930
+ done
931
+ } > "$_blk"
932
+ if [ -z "${INSTALLED_RECIPES// /}" ]; then
933
+ /bin/rm -f "$_blk"
934
+ echo "Error: --mode=$MODE resolved ZERO valid recipes from '--recipes=$RECIPES' — nothing to install (check the names)." >&2
935
+ /bin/rm -f "$MANIFEST_STAGE_PATH"
936
+ exit 1
937
+ fi
938
+ append_block "$GEMINI_DEST" "$BLOCK_NAME" "$_blk"
939
+ /bin/rm -f "$_blk"
940
+ record_emit_block "GEMINI.md" "$BLOCK_NAME" "$BLOCK_SHA" "$BLOCK_CREATED"
941
+ # Preserve OTHER à-la-carte blocks from a previous install (e.g. recipes-only
942
+ # then reflector-only): carry their manifest entries forward so uninstall can
943
+ # still strip them (ADR-044 review fix — cross-mode orphaned block).
944
+ if [ -f "$MANIFEST_PATH" ]; then
945
+ while IFS= read -r _prev; do
946
+ case "$_prev" in *'"type": "block"'*) : ;; *) continue ;; esac
947
+ _pname="$(printf '%s' "$_prev" | /usr/bin/sed -E 's/.*"block": *"([^"]*)".*/\1/')"
948
+ [ "$_pname" = "$BLOCK_NAME" ] && continue
949
+ if /usr/bin/grep -qF "<!-- conductor:block $_pname -->" "$GEMINI_DEST" 2>/dev/null; then
950
+ printf '%s\n' "$_prev" | /usr/bin/sed 's/,*$/,/' >> "$MANIFEST_STAGE_PATH"
951
+ log " preserved previous block '$_pname' in manifest"
952
+ fi
953
+ done < "$MANIFEST_PATH"
954
+ fi
955
+ fi
956
+ fi
957
+
702
958
  # ----- step 2: .gemini/styleguide.md (opt-in: coding-conventions) ---------
703
959
 
960
+ if [ "$MODE" != "full" ] && [ "$MODE" != "strict" ]; then
961
+ # À la carte: the marked block is the sole carrier (no side files); minimal ships text only.
962
+ WANT_STYLEGUIDE="false"
963
+ fi
704
964
  log "Step 2/3: .gemini/styleguide.md (opt-in via --recipes=coding-conventions)"
705
965
  if [ "$WANT_STYLEGUIDE" = "true" ]; then
706
966
  STYLE_SRC="$CORE_ROOT/recipes/coding-conventions.md"
@@ -732,7 +992,13 @@ fi
732
992
 
733
993
  # ----- self-improvement (opt-in: self-improvement recipe) ------------------
734
994
 
735
- case ",$RECIPES," in
995
+ if [ "$MODE" = "minimal" ]; then
996
+ RECIPES_FOR_RUNTIME=""
997
+ log "Step: self-improvement runtime — skipped (--mode=minimal ships text only)"
998
+ else
999
+ RECIPES_FOR_RUNTIME="$RECIPES"
1000
+ fi
1001
+ case ",$RECIPES_FOR_RUNTIME," in
736
1002
  *",self-improvement,"*)
737
1003
  log "Step: self-improvement (Reflector) → .gemini hooks/command/agent"
738
1004
  if [ "$DRY_RUN" != "true" ]; then
@@ -781,6 +1047,9 @@ esac
781
1047
 
782
1048
  # ----- step 3: docs templates --------------------------------------------
783
1049
 
1050
+ if [ "$MODE" = "recipes-only" ] || [ "$MODE" = "reflector-only" ]; then
1051
+ log "Step 3/3: docs templates — skipped (--mode=$MODE is à la carte; docs ship with full/minimal)"
1052
+ else
784
1053
  log "Step 3/3: docs templates → docs/"
785
1054
  mkdir_if_real "$TARGET_ABS/docs"
786
1055
  mkdir_if_real "$TARGET_ABS/docs/specs"
@@ -815,6 +1084,8 @@ if [ -f "$CORE_ROOT/docs-templates/specs/_example.md" ]; then
815
1084
  fi
816
1085
 
817
1086
  # Finalize manifest after all emits.
1087
+ fi
1088
+
818
1089
  finalize_manifest
819
1090
 
820
1091
  # ----- completion summary -------------------------------------------------
@@ -824,14 +1095,19 @@ echo "========================================================"
824
1095
  echo " Done."
825
1096
  echo " Target: $TARGET_ABS"
826
1097
  echo " Adapter: gemini"
827
- echo " GEMINI.md: 1 bundled file (5 universal rules + workflow, always-loaded)"
1098
+ echo " Mode: $MODE"
1099
+ if [ "$MODE" = "recipes-only" ] || [ "$MODE" = "reflector-only" ]; then
1100
+ echo " GEMINI.md: marked à-la-carte block appended (no universal-rule bundle)"
1101
+ else
1102
+ echo " GEMINI.md: 1 bundled file (5 universal rules + workflow, always-loaded)"
1103
+ fi
828
1104
  echo " Style guide: $([ "$WANT_STYLEGUIDE" = "true" ] && echo ".gemini/styleguide.md emitted" || echo "(not emitted — select coding-conventions)")"
829
1105
  echo " Recipes installed:${INSTALLED_RECIPES:- (none)}"
830
1106
  echo ""
831
1107
  echo " Skipped (per ADR-004 honesty):"
832
1108
  echo " - Hooks: CONDUCTOR emits the Reflector hook when --recipes=self-improvement (ADR-032); other guards remain Claude-only (ADR-034)."
833
- echo " - Sub-agent personas: Gemini has no sub-agent dispatchsingle chat session per task."
834
- echo " - Per-call model routing: single model per Gemini session."
1109
+ echo " - Sub-agent personas: not yet emitted for Gemini (tool supports sub-agents nativelyADR-031; Phase 2)."
1110
+ echo " - Per-call model routing: supported natively by Gemini CLI (ADR-031); not yet automated by CONDUCTOR."
835
1111
  echo " - Built-in memory: DIY at .memory/ (see the note inside GEMINI.md)."
836
1112
  echo ""
837
1113
  echo " Activation: open $TARGET_ABS with Gemini CLI — it loads GEMINI.md automatically."
@@ -839,6 +1115,6 @@ echo "========================================================"
839
1115
  echo ""
840
1116
  echo "Next steps for the project:"
841
1117
  echo " 1. Replace {{PROJECT_NAME}} in GEMINI.md (and .gemini/styleguide.md if present)."
842
- echo " 2. Edit docs/CURRENT_WORK.md with your project's current state."
1118
+ [ -d "$TARGET_ABS/docs" ] && echo " 2. Edit docs/CURRENT_WORK.md with your project's current state."
843
1119
  echo " 3. Add .memory/ to .gitignore if you adopt the DIY memory pattern."
844
1120
  echo " 4. Rename docs/specs/_example.md → docs/specs/<your-area>.md."
@@ -6,15 +6,12 @@ Windsurf (the AI-IDE) is a T3 target because:
6
6
  - It supports a directory of additional rules at `.devin/rules/*.md` (preferred; legacy `.windsurf/rules/` is still read).
7
7
  - Its workflow is similar to Cursor's but with less per-pattern scoping.
8
8
 
9
- It is **T3** because:
9
+ **Tool capability vs CONDUCTOR emission (ADR-031):** as of 2026 Windsurf / Devin Desktop ships hooks (12 events — but **no session/stop events**, the one real Stop-style-enforcement gap), sub-agent dispatch (Devin Local), custom agents, per-task model routing, commands, and built-in memory. What is limited today is what CONDUCTOR **emits** for it — rule text + docs + the opt-in Reflector loop; the enforcement guard hooks, role agents, and model-routing config are Phase-2 emission (ADR-034). Real tool-side caveats: no glob rule-scoping (all `.devin/rules/*.md` load together) and no desktop scheduler.
10
10
 
11
- - No per-pattern rule scoping (rules in `.devin/rules/` all load together; no glob filtering).
12
- - ❌ No sub-agent dispatch.
13
- - No hooks.
14
- - ❌ No per-call model routing.
15
- - ❌ No built-in memory directory.
11
+ **Tier**: T3 (see `docs/COMPATIBILITY-MATRIX.md` the missing session/stop hook events keep it below T2).
12
+
13
+ > Enumerable facts about this adapter (output paths / tier / capabilities / live verification / headless CLI) are machine-readable in [`metadata.json`](./metadata.json) and CI-checked against `transform.sh` + the validator (ADR-040).
16
14
 
17
- **Tier**: T3 — Basic. Rule TEXT installs and groups well, but enforcement and scoping are minimal.
18
15
 
19
16
  ## Installation path
20
17
 
@@ -39,11 +36,12 @@ bash adapters/windsurf/transform.sh <target> --uninstall
39
36
  ├── .windsurfrules # Always-loaded baseline (orchestrator manual + ABSOLUTE rules)
40
37
  ├── .devin/ # Preferred rules dir (legacy .windsurf/rules/ still read)
41
38
  │ └── rules/
42
- │ ├── operations.md
43
- │ ├── coding-conventions.md
44
- │ ├── token-economy.md
39
+ │ ├── workflow.md
45
40
  │ ├── spec-as-you-go.md
46
- └── model-routing.md
41
+ ├── quality-gates.md
42
+ │ ├── operations.md
43
+ │ ├── meta-discipline.md
44
+ │ └── <recipe>.md # per --recipes=
47
45
  └── docs/
48
46
  ├── CURRENT_WORK.md # Universal templates
49
47
  ├── REMAINING_TASKS.md
@@ -53,22 +51,25 @@ bash adapters/windsurf/transform.sh <target> --uninstall
53
51
  └── specs/_example.md
54
52
  ```
55
53
 
56
- ## Native features supported
54
+ - `--recipes=self-improvement` additionally emits the Reflector loop: trajectory hook config (`.windsurf/hooks.json`, riding `post_cascade_response_with_transcript` — Windsurf has no session/stop event), the `/reflect` workflow (`.windsurf/workflows/reflect.md`), a reflector persona rule (`.devin/rules/reflector.md`, `trigger: manual`), prune script, and the `.conductor/reflect/` weekly runner (ADR-032/033).
55
+
56
+ ## Native features supported (emitted today)
57
57
 
58
58
  - ✅ Always-loaded baseline (`.windsurfrules`).
59
59
  - ✅ Directory-based rule loading (`.devin/rules/`; legacy `.windsurf/rules/` still read).
60
60
  - ✅ All universal rule TEXT.
61
61
  - ✅ All doc templates.
62
+ - ✅ Reflector loop (opt-in recipe).
62
63
 
63
- ## Features NOT supported
64
+ ## Not emitted yet (Phase 2 — Windsurf supports most of these natively, ADR-031/034)
64
65
 
65
- | Feature | Workaround |
66
+ | Feature | Interim workaround |
66
67
  |---|---|
67
- | Per-pattern rule scoping | All rules in `.devin/rules/` load together. No glob filtering. |
68
- | Sub-agent dispatch | Human plays orchestrator. |
69
- | Hooks | Pair with project pre-commit git hooks. |
70
- | Per-call model routing | Single model per session. |
71
- | Built-in memory directory | DIY at `.memory/`. |
68
+ | Per-pattern rule scoping | All rules in `.devin/rules/` load together. No glob filtering (tool-side). |
69
+ | Enforcement guard hooks | Windsurf has hooks but **no session/stop events** (tool-side gap) — Stop-style enforcement isn't possible; self-police or pair with pre-commit git hooks. |
70
+ | The 6 role agents (sub-agent dispatch) | Devin Local has native sub-agents; CONDUCTOR doesn't emit its role definitions yet. Human plays orchestrator. |
71
+ | Per-call model-routing config | Pick the model per task via the tool's own model selection. |
72
+ | 4-type memory pattern | Self-managed at `.memory/` (gitignored); the tool's built-in memory is separate. |
72
73
 
73
74
  ## After install — first steps
74
75