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,757 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # CONDUCTOR — GitHub Copilot adapter transform.sh
4
+ #
5
+ # Reads core/ assets and writes them into a target project as native GitHub Copilot
6
+ # custom instructions files: .github/copilot-instructions.md (repo-wide) and
7
+ # .github/instructions/*.instructions.md (per-file with applyTo: globs).
8
+ #
9
+ # Strategic value: Copilot custom instructions are IDE-agnostic — a single file format
10
+ # is consumed by VS Code, Cursor (Copilot extension), Windsurf (Copilot adapter),
11
+ # JetBrains (Copilot plugin), and Neovim (copilot.vim). One adapter covers 5 IDEs.
12
+ #
13
+ # Usage:
14
+ # bash adapters/copilot/transform.sh <target-project> [--recipes=<comma-list>] [--dry-run]
15
+ # [--no-prompt] [--per-rule] [--force]
16
+ # bash adapters/copilot/transform.sh <target-project> --uninstall [--dry-run] [--force]
17
+ #
18
+ # Examples:
19
+ # bash adapters/copilot/transform.sh ~/Projects/my-app
20
+ # bash adapters/copilot/transform.sh ~/Projects/my-app --recipes=monorepo,i18n
21
+ # bash adapters/copilot/transform.sh /tmp/test --dry-run
22
+ # bash adapters/copilot/transform.sh . --no-prompt --recipes=coding-conventions
23
+ # bash adapters/copilot/transform.sh . --per-rule # split universal rules into 5 per-file files
24
+ # bash adapters/copilot/transform.sh . --uninstall # revert install
25
+ # bash adapters/copilot/transform.sh . --uninstall --dry-run
26
+ #
27
+ # Layer 2 transformation (default — single-file universal):
28
+ # core/universal-rules/{workflow,spec-as-you-go,quality-gates,operations,meta-discipline}.md
29
+ # → <target>/.github/copilot-instructions.md (concatenated, body only)
30
+ # core/recipes/<r>.md (selected) → <target>/.github/instructions/<r>.instructions.md (applyTo: from paths)
31
+ # core/docs-templates/*.md → <target>/docs/*.md
32
+ # core/hooks/* → SKIP (Reflector hook emitted via --recipes=self-improvement, ADR-032; other guards Claude-only, ADR-034)
33
+ # core/roles/* → SKIP (Copilot has no sub-agent dispatch)
34
+ #
35
+ # Layer 2 transformation (--per-rule alternative):
36
+ # core/universal-rules/<r>.md → <target>/.github/instructions/<r>.instructions.md (applyTo: '**')
37
+ # (everything else identical to default)
38
+ #
39
+ # Frontmatter translation:
40
+ # Conductor source frontmatter:
41
+ # ---
42
+ # paths:
43
+ # - "apps/**/*.ts"
44
+ # - "apps/**/*.tsx"
45
+ # ---
46
+ # Copilot output frontmatter:
47
+ # ---
48
+ # applyTo: 'apps/**/*.ts,apps/**/*.tsx'
49
+ # ---
50
+ # Copilot uses a single CSV glob string. Multi-line YAML lists are flattened to
51
+ # comma-separated. If always_loaded:true (or no paths), output applyTo: '**'.
52
+
53
+ set -eu
54
+
55
+ # ----- arg parsing --------------------------------------------------------
56
+
57
+ TARGET=""
58
+ RECIPES=""
59
+ DRY_RUN="false"
60
+ NO_PROMPT="false"
61
+ PER_RULE="false"
62
+ UNINSTALL="false"
63
+ FORCE="false"
64
+
65
+ WIZARD_APPLY_RULES="true"
66
+
67
+ while [ $# -gt 0 ]; do
68
+ case "$1" in
69
+ --recipes=*) RECIPES="${1#--recipes=}" ;;
70
+ --dry-run) DRY_RUN="true" ;;
71
+ --no-prompt) NO_PROMPT="true" ;;
72
+ --per-rule) PER_RULE="true" ;;
73
+ --uninstall|--rollback) UNINSTALL="true" ;;
74
+ --force) FORCE="true" ;;
75
+ --help|-h)
76
+ /bin/cat <<EOF
77
+ Usage: bash adapters/copilot/transform.sh <target-project> [options]
78
+
79
+ Options:
80
+ --recipes=A,B,C Comma-separated list of recipes to install
81
+ --dry-run Preview only — no files written
82
+ --no-prompt Skip interactive prompts; apply defaults (CI-safe)
83
+ --per-rule Split 5 universal rules into per-file .instructions.md
84
+ (default: concatenate into single .github/copilot-instructions.md)
85
+ --uninstall Revert a previous install using <target>/.conductor-manifest.json
86
+ (alias: --rollback). Restores backups when present, deletes
87
+ Conductor-emitted files when none.
88
+ --force Bypass uninstall safety checks (active worktrees, missing manifest)
89
+
90
+ Recipes available: web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement
91
+
92
+ Output (default):
93
+ <target>/.github/copilot-instructions.md (5 universal rules merged)
94
+ <target>/.github/instructions/<recipe>.instructions.md (per recipe, applyTo: from paths)
95
+ <target>/docs/{CURRENT_WORK,REMAINING_TASKS,PLANS,TASKS,INDEX}.md
96
+
97
+ Skipped (not supported by Copilot):
98
+ Sub-agent dispatch (roles) — Copilot has no Agent equivalent
99
+ PreToolUse / Stop hooks — Copilot has no commit-blocking hooks
100
+
101
+ IDE coverage: VS Code, Cursor (Copilot ext), Windsurf (Copilot adapter), JetBrains
102
+ (Copilot plugin), Neovim (copilot.vim) all read .github/instructions/.
103
+ EOF
104
+ exit 0
105
+ ;;
106
+ *)
107
+ if [ -z "$TARGET" ]; then
108
+ TARGET="$1"
109
+ else
110
+ echo "Unknown argument: $1" >&2
111
+ exit 1
112
+ fi
113
+ ;;
114
+ esac
115
+ shift
116
+ done
117
+
118
+ if [ -z "$TARGET" ]; then
119
+ echo "Error: target-project path is required." >&2
120
+ echo "Usage: bash adapters/copilot/transform.sh <target-project> [--recipes=...]" >&2
121
+ exit 1
122
+ fi
123
+
124
+ CONDUCTOR_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
125
+ CORE_ROOT="$CONDUCTOR_ROOT/core"
126
+ [ -d "$CORE_ROOT" ] || { echo "Error: core/ not found at $CORE_ROOT" >&2; exit 1; }
127
+
128
+ if [ "$DRY_RUN" = "true" ]; then
129
+ mkdir -p "$TARGET"
130
+ fi
131
+ TARGET_ABS="$(cd "$TARGET" 2>/dev/null && pwd)" || { echo "Error: target directory does not exist: $TARGET" >&2; exit 1; }
132
+
133
+ # ----- helpers ------------------------------------------------------------
134
+
135
+ log() {
136
+ if [ "$DRY_RUN" = "true" ]; then
137
+ echo "[dry-run] $*"
138
+ else
139
+ echo "[conductor] $*"
140
+ fi
141
+ }
142
+
143
+ mkdir_if_real() {
144
+ if [ "$DRY_RUN" = "true" ]; then
145
+ log "would mkdir -p $1"
146
+ else
147
+ mkdir -p "$1"
148
+ fi
149
+ }
150
+
151
+ # strip_frontmatter <src-file>
152
+ # Print only the body (everything after the second `---` line). If no frontmatter,
153
+ # print the whole file.
154
+ strip_frontmatter() {
155
+ /usr/bin/awk 'BEGIN{c=0; f=0} /^---$/{c++; if(c==2){f=1; next} else {next}} f==1 || c==0' "$1"
156
+ }
157
+
158
+ # extract_paths_csv <src-file>
159
+ # Read frontmatter `paths:` YAML list, return CSV glob string.
160
+ # Falls back to `applies_to:` (used by universal-rules). Returns empty if absent
161
+ # or if always_loaded:true is set.
162
+ extract_paths_csv() {
163
+ local src="$1"
164
+ # Always-loaded check first.
165
+ local always
166
+ always=$(/usr/bin/awk '
167
+ BEGIN{c=0; in_fm=0}
168
+ /^---$/{c++; if(c==1){in_fm=1; next} else {exit}}
169
+ in_fm && /^always_loaded:[[:space:]]*true/{print "true"; exit}
170
+ ' "$src")
171
+ if [ "$always" = "true" ]; then
172
+ echo "**"
173
+ return
174
+ fi
175
+ # Try paths: list (recipe style).
176
+ local paths_csv
177
+ paths_csv=$(/usr/bin/awk '
178
+ BEGIN{c=0; in_fm=0; in_paths=0; out=""}
179
+ /^---$/{c++; if(c==1){in_fm=1; next} else {exit}}
180
+ in_fm && /^paths:/{in_paths=1; next}
181
+ in_fm && in_paths && /^[[:space:]]*-[[:space:]]*"/{
182
+ gsub(/^[[:space:]]*-[[:space:]]*"/, "")
183
+ gsub(/"[[:space:]]*$/, "")
184
+ if(out=="") out=$0; else out=out "," $0
185
+ next
186
+ }
187
+ in_fm && in_paths && /^[^[:space:]-]/{in_paths=0}
188
+ END{print out}
189
+ ' "$src")
190
+ if [ -n "$paths_csv" ]; then
191
+ echo "$paths_csv"
192
+ return
193
+ fi
194
+ # Try applies_to: list (universal-rule style).
195
+ local applies_csv
196
+ applies_csv=$(/usr/bin/awk '
197
+ BEGIN{c=0; in_fm=0; in_a=0; out=""}
198
+ /^---$/{c++; if(c==1){in_fm=1; next} else {exit}}
199
+ in_fm && /^applies_to:/{
200
+ # Inline form: applies_to: ["a","b"]
201
+ if(match($0, /\[.*\]/)){
202
+ s=substr($0, RSTART+1, RLENGTH-2)
203
+ gsub(/"/, "", s); gsub(/[[:space:]]/, "", s)
204
+ print s; exit
205
+ }
206
+ in_a=1; next
207
+ }
208
+ in_fm && in_a && /^[[:space:]]*-[[:space:]]*"/{
209
+ gsub(/^[[:space:]]*-[[:space:]]*"/, "")
210
+ gsub(/"[[:space:]]*$/, "")
211
+ if(out=="") out=$0; else out=out "," $0
212
+ next
213
+ }
214
+ in_fm && in_a && /^[^[:space:]-]/{in_a=0}
215
+ END{if(out!="") print out}
216
+ ' "$src")
217
+ if [ -n "$applies_csv" ]; then
218
+ # all-tools sentinel maps to ** (matches everything).
219
+ case "$applies_csv" in
220
+ *all-tools*) echo "**" ;;
221
+ *) echo "$applies_csv" ;;
222
+ esac
223
+ return
224
+ fi
225
+ # Default fallback.
226
+ echo "**"
227
+ }
228
+
229
+ # write_copilot_per_file <src> <dest> <apply-to-csv>
230
+ # Emit a Copilot per-file instructions.md with applyTo: <csv> frontmatter,
231
+ # then strip the source's Conductor frontmatter and append the body.
232
+ write_copilot_per_file() {
233
+ local src="$1" dest="$2" applyto="$3"
234
+ if [ "$DRY_RUN" = "true" ]; then
235
+ log "would write $dest with applyTo: '$applyto'"
236
+ return
237
+ fi
238
+ /bin/cat > "$dest" <<EOF
239
+ ---
240
+ applyTo: '$applyto'
241
+ ---
242
+
243
+ EOF
244
+ strip_frontmatter "$src" >> "$dest"
245
+ }
246
+
247
+ # backup_if_exists <dest>
248
+ backup_if_exists() {
249
+ local dest="$1"
250
+ MANIFEST_LAST_BACKUP=""
251
+ if [ -f "$dest" ]; then
252
+ if [ "$DRY_RUN" = "true" ]; then
253
+ log "would back up existing $dest -> $dest.conductor-backup-<ts>"
254
+ else
255
+ local ts
256
+ ts="$(/bin/date +%Y%m%d-%H%M%S)"
257
+ local backup="${dest}.conductor-backup-${ts}"
258
+ /bin/cp "$dest" "$backup"
259
+ log " backed up existing $dest -> $backup"
260
+ MANIFEST_LAST_BACKUP="${backup#$TARGET_ABS/}"
261
+ fi
262
+ fi
263
+ }
264
+
265
+ # ----- manifest tracking (mirrors claude adapter ADR-020) ----------------
266
+
267
+ MANIFEST_PATH="$TARGET_ABS/.conductor-manifest.json"
268
+ MANIFEST_STAGE_PATH=""
269
+ MANIFEST_TS=""
270
+ MANIFEST_LAST_BACKUP=""
271
+
272
+ init_manifest() {
273
+ if [ "$DRY_RUN" = "true" ]; then
274
+ log "would init manifest staging at $MANIFEST_PATH.staging"
275
+ return
276
+ fi
277
+ MANIFEST_TS="$(/bin/date -u +%Y-%m-%dT%H:%M:%SZ)"
278
+ MANIFEST_STAGE_PATH="$TARGET_ABS/.conductor-manifest.json.staging"
279
+ /bin/rm -f "$MANIFEST_STAGE_PATH"
280
+ : > "$MANIFEST_STAGE_PATH"
281
+ }
282
+
283
+ record_emit() {
284
+ if [ "$DRY_RUN" = "true" ] || [ "$UNINSTALL" = "true" ]; then
285
+ return
286
+ fi
287
+ local relpath="$1" src="$2" backup="${3:-}"
288
+ local had_backup="false"
289
+ [ -n "$backup" ] && had_backup="true"
290
+ local esc_path esc_src esc_backup
291
+ esc_path="$(printf '%s' "$relpath" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
292
+ esc_src="$(printf '%s' "$src" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
293
+ esc_backup="$(printf '%s' "$backup" | /usr/bin/sed 's/\\/\\\\/g; s/"/\\"/g')"
294
+ printf ' {"path": "%s", "source": "%s", "had_backup": %s, "backup_path": "%s"},\n' \
295
+ "$esc_path" "$esc_src" "$had_backup" "$esc_backup" >> "$MANIFEST_STAGE_PATH"
296
+ }
297
+
298
+ finalize_manifest() {
299
+ if [ "$DRY_RUN" = "true" ]; then
300
+ log "would finalize manifest -> $MANIFEST_PATH"
301
+ return
302
+ fi
303
+ [ -z "$MANIFEST_STAGE_PATH" ] && return
304
+ [ -f "$MANIFEST_STAGE_PATH" ] || return
305
+
306
+ if [ -f "$MANIFEST_PATH" ]; then
307
+ backup_if_exists "$MANIFEST_PATH"
308
+ fi
309
+
310
+ local recipes_json="[]"
311
+ if [ -n "$RECIPES" ]; then
312
+ recipes_json="["
313
+ local IFS_BAK=$IFS
314
+ IFS=','
315
+ local first="true"
316
+ for r in $RECIPES; do
317
+ r="$(printf '%s' "$r" | /usr/bin/sed 's/^ *//; s/ *$//')"
318
+ [ -z "$r" ] && continue
319
+ if [ "$first" = "true" ]; then
320
+ recipes_json="${recipes_json}\"$r\""
321
+ first="false"
322
+ else
323
+ recipes_json="${recipes_json}, \"$r\""
324
+ fi
325
+ done
326
+ IFS=$IFS_BAK
327
+ recipes_json="${recipes_json}]"
328
+ fi
329
+
330
+ local entries
331
+ if [ -s "$MANIFEST_STAGE_PATH" ]; then
332
+ entries="$(/usr/bin/sed -e '$ s/,$//' "$MANIFEST_STAGE_PATH")"
333
+ else
334
+ entries=""
335
+ fi
336
+
337
+ /bin/cat > "$MANIFEST_PATH" <<EOF
338
+ {
339
+ "version": "v0.2.0",
340
+ "adapter": "copilot",
341
+ "install_timestamp": "$MANIFEST_TS",
342
+ "conductor_root": "$CONDUCTOR_ROOT",
343
+ "per_rule_mode": $PER_RULE,
344
+ "recipes_enabled": $recipes_json,
345
+ "emitted_files": [
346
+ $entries
347
+ ]
348
+ }
349
+ EOF
350
+ /bin/rm -f "$MANIFEST_STAGE_PATH"
351
+ log " wrote manifest $MANIFEST_PATH"
352
+ }
353
+
354
+ # ----- uninstall flow ----------------------------------------------------
355
+
356
+ do_uninstall() {
357
+ log "uninstall mode (target: $TARGET_ABS)"
358
+
359
+ if [ ! -f "$MANIFEST_PATH" ]; then
360
+ if [ "$FORCE" = "true" ]; then
361
+ log "WARNING: no manifest at $MANIFEST_PATH — proceeding under --force (legacy scan)"
362
+ uninstall_legacy_scan
363
+ return 0
364
+ fi
365
+ echo "Error: no manifest at $MANIFEST_PATH." >&2
366
+ echo " Re-run with --force to scan for legacy .conductor-backup-* files anyway." >&2
367
+ exit 1
368
+ fi
369
+
370
+ if [ -d "$TARGET_ABS/.git" ]; then
371
+ if [ -f "$TARGET_ABS/.git/MERGE_HEAD" ] || [ -f "$TARGET_ABS/.git/REBASE_HEAD" ] || [ -d "$TARGET_ABS/.git/rebase-merge" ]; then
372
+ if [ "$FORCE" != "true" ]; then
373
+ echo "Error: target has an active git operation (merge/rebase in progress)." >&2
374
+ echo " Resolve or pass --force to override." >&2
375
+ exit 1
376
+ fi
377
+ log "WARNING: active git operation — proceeding under --force"
378
+ fi
379
+ fi
380
+
381
+ log "loading manifest entries..."
382
+ local entries_count=0
383
+ local restored=0
384
+ local deleted=0
385
+ local missing=0
386
+
387
+ while IFS= read -r line; do
388
+ case "$line" in
389
+ *'"path":'*'"source":'*'"had_backup":'*) ;;
390
+ *) continue ;;
391
+ esac
392
+ entries_count=$((entries_count + 1))
393
+ local rel_path had_backup backup_path
394
+ rel_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"path": *"([^"]*)".*/\1/')"
395
+ had_backup="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"had_backup": *(true|false).*/\1/')"
396
+ backup_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"backup_path": *"([^"]*)".*/\1/')"
397
+
398
+ local abs_dest="$TARGET_ABS/$rel_path"
399
+ local abs_backup=""
400
+ [ -n "$backup_path" ] && abs_backup="$TARGET_ABS/$backup_path"
401
+
402
+ if [ "$had_backup" = "true" ] && [ -n "$abs_backup" ]; then
403
+ if [ -f "$abs_backup" ]; then
404
+ if [ "$DRY_RUN" = "true" ]; then
405
+ log " would restore $abs_backup -> $abs_dest"
406
+ else
407
+ /bin/mv -f "$abs_backup" "$abs_dest"
408
+ log " restored $abs_backup -> $abs_dest"
409
+ fi
410
+ restored=$((restored + 1))
411
+ else
412
+ if [ "$DRY_RUN" = "true" ]; then
413
+ log " would delete $abs_dest (backup $abs_backup missing)"
414
+ else
415
+ /bin/rm -f "$abs_dest"
416
+ log " deleted $abs_dest (backup $abs_backup missing)"
417
+ fi
418
+ missing=$((missing + 1))
419
+ fi
420
+ else
421
+ if [ -f "$abs_dest" ]; then
422
+ if [ "$DRY_RUN" = "true" ]; then
423
+ log " would delete $abs_dest"
424
+ else
425
+ /bin/rm -f "$abs_dest"
426
+ log " deleted $abs_dest"
427
+ fi
428
+ deleted=$((deleted + 1))
429
+ else
430
+ log " skip $abs_dest (already absent)"
431
+ fi
432
+ fi
433
+ done < "$MANIFEST_PATH"
434
+
435
+ if [ "$DRY_RUN" = "true" ]; then
436
+ log " would delete $MANIFEST_PATH"
437
+ for mb in "$MANIFEST_PATH".conductor-backup-*; do
438
+ [ -e "$mb" ] && log " would delete $mb"
439
+ done
440
+ else
441
+ /bin/rm -f "$MANIFEST_PATH"
442
+ for mb in "$MANIFEST_PATH".conductor-backup-*; do
443
+ [ -e "$mb" ] && /bin/rm -f "$mb"
444
+ done
445
+ log " deleted $MANIFEST_PATH"
446
+ fi
447
+
448
+ for d in .github/instructions .github/hooks .github/prompts .github/agents .github .conductor/reflect .conductor; do
449
+ local abs_d="$TARGET_ABS/$d"
450
+ if [ -d "$abs_d" ]; then
451
+ if [ "$DRY_RUN" = "true" ]; then
452
+ if [ -z "$(/bin/ls -A "$abs_d" 2>/dev/null)" ]; then
453
+ log " would rmdir empty $abs_d"
454
+ fi
455
+ else
456
+ /bin/rmdir "$abs_d" 2>/dev/null && log " rmdir empty $abs_d" || true
457
+ fi
458
+ fi
459
+ done
460
+
461
+ echo ""
462
+ echo "========================================================"
463
+ if [ "$DRY_RUN" = "true" ]; then
464
+ echo " Uninstall preview (dry-run)"
465
+ else
466
+ echo " Uninstall complete"
467
+ fi
468
+ echo " Target: $TARGET_ABS"
469
+ echo " Entries processed: $entries_count"
470
+ echo " Backups restored: $restored"
471
+ echo " Files deleted: $deleted"
472
+ echo " Backup-missing deletes: $missing"
473
+ echo "========================================================"
474
+ }
475
+
476
+ uninstall_legacy_scan() {
477
+ log "legacy scan mode — searching for .conductor-backup-* files under $TARGET_ABS"
478
+ local found=0
479
+ while IFS= read -r f; do
480
+ found=$((found + 1))
481
+ if [ "$DRY_RUN" = "true" ]; then
482
+ log " would delete legacy backup $f"
483
+ else
484
+ /bin/rm -f "$f"
485
+ log " deleted legacy backup $f"
486
+ fi
487
+ done < <(/usr/bin/find "$TARGET_ABS" -type f -name '*.conductor-backup-*' 2>/dev/null)
488
+ log "legacy scan: $found backup file(s)"
489
+ log "WARNING: legacy mode does not delete Conductor-emitted source files."
490
+ log " Manually remove .github/copilot-instructions.md and .github/instructions/*.instructions.md if desired."
491
+ }
492
+
493
+ if [ "$UNINSTALL" = "true" ]; then
494
+ do_uninstall
495
+ exit 0
496
+ fi
497
+
498
+ # ----- onboarding wizard --------------------------------------------------
499
+ # Adopter case = .github/copilot-instructions.md OR .github/instructions/ already exists.
500
+
501
+ IS_ADOPTER_CASE="false"
502
+ if [ -f "$TARGET_ABS/.github/copilot-instructions.md" ] || [ -d "$TARGET_ABS/.github/instructions" ]; then
503
+ IS_ADOPTER_CASE="true"
504
+ fi
505
+
506
+ if [ "$IS_ADOPTER_CASE" = "true" ] && [ "$NO_PROMPT" = "false" ] && [ "$DRY_RUN" = "false" ]; then
507
+ echo ""
508
+ echo "========================================================"
509
+ echo " Welcome to CONDUCTOR (Copilot adapter) setup"
510
+ echo " Target: $TARGET_ABS"
511
+ echo "========================================================"
512
+ echo ""
513
+ echo " Detected existing Copilot custom instructions."
514
+ echo " Existing files will be backed up to .conductor-backup-<timestamp> before overwrite."
515
+ echo ""
516
+
517
+ printf "Apply universal-rules? (Y/n): "
518
+ read -r _apply_answer
519
+ if [ "$_apply_answer" = "n" ] || [ "$_apply_answer" = "N" ]; then
520
+ WIZARD_APPLY_RULES="false"
521
+ echo " Skipping universal-rules installation."
522
+ fi
523
+
524
+ echo ""
525
+ echo "Available recipes:"
526
+ echo " web-mobile-parity, i18n, monorepo, branch-strategy, auto-mock-data, coding-conventions, tdd, debugging, database-discipline, design-system, self-improvement"
527
+ printf "Select recipes (comma-separated, blank for none): "
528
+ read -r _recipe_answer
529
+ if [ -n "$_recipe_answer" ]; then
530
+ RECIPES="$_recipe_answer"
531
+ echo " Recipes selected: $RECIPES"
532
+ fi
533
+
534
+ printf "Use --per-rule mode (split universal-rules into 5 separate per-file files)? (y/N): "
535
+ read -r _per_rule_answer
536
+ if [ "$_per_rule_answer" = "y" ] || [ "$_per_rule_answer" = "Y" ]; then
537
+ PER_RULE="true"
538
+ echo " Per-rule mode enabled (output: .github/instructions/{workflow,...}.instructions.md)."
539
+ fi
540
+
541
+ echo ""
542
+ elif [ "$IS_ADOPTER_CASE" = "true" ] && [ "$NO_PROMPT" = "true" ]; then
543
+ log "Adopter case detected — applying defaults (--no-prompt): rules=yes, recipes=${RECIPES:-(none)}, per_rule=$PER_RULE"
544
+ fi
545
+
546
+ # ----- step 1: universal rules → .github/copilot-instructions.md ---------
547
+
548
+ init_manifest
549
+
550
+ if [ "$WIZARD_APPLY_RULES" = "true" ]; then
551
+ if [ "$PER_RULE" = "true" ]; then
552
+ log "Step 1/4: universal-rules → .github/instructions/*.instructions.md (per-rule mode)"
553
+ mkdir_if_real "$TARGET_ABS/.github/instructions"
554
+ for rule in workflow spec-as-you-go quality-gates operations meta-discipline; do
555
+ src="$CORE_ROOT/universal-rules/$rule.md"
556
+ dest="$TARGET_ABS/.github/instructions/$rule.instructions.md"
557
+ if [ ! -f "$src" ]; then
558
+ echo "Warning: $src not found; skipping" >&2
559
+ continue
560
+ fi
561
+ backup_if_exists "$dest"
562
+ # Universal rules are always-loaded → applyTo: '**'
563
+ write_copilot_per_file "$src" "$dest" "**"
564
+ record_emit ".github/instructions/$rule.instructions.md" "core/universal-rules/$rule.md" "$MANIFEST_LAST_BACKUP"
565
+ done
566
+ else
567
+ log "Step 1/4: universal-rules → .github/copilot-instructions.md (single-file mode)"
568
+ mkdir_if_real "$TARGET_ABS/.github"
569
+ dest="$TARGET_ABS/.github/copilot-instructions.md"
570
+ backup_if_exists "$dest"
571
+ if [ "$DRY_RUN" = "true" ]; then
572
+ log "would synthesize $dest from 5 universal-rule bodies"
573
+ else
574
+ /bin/cat > "$dest" <<'HEADER_EOF'
575
+ # Project Custom Instructions (installed by CONDUCTOR — Copilot adapter)
576
+
577
+ > Loaded automatically by GitHub Copilot for every chat in this repository.
578
+ > Read the 5 universal rules below before any tool call.
579
+ > Source: https://github.com/<your-org>/conductor (universal-rules/*.md)
580
+
581
+ ## Topology note (Copilot)
582
+
583
+ GitHub Copilot does not have sub-agent dispatch or commit-blocking hooks. The 5
584
+ universal rules below are self-policed: the human (and Copilot Chat) must follow
585
+ the same Plan → Architecture → Tasks → Implementation → Review → Spec workflow
586
+ that Claude Code enforces with hooks. Two-stage code review degrades to the
587
+ Copilot PR review feature for Stage B (configure separately at the repo level).
588
+
589
+ ---
590
+
591
+ HEADER_EOF
592
+ for rule in workflow spec-as-you-go quality-gates operations meta-discipline; do
593
+ src="$CORE_ROOT/universal-rules/$rule.md"
594
+ if [ ! -f "$src" ]; then
595
+ echo "Warning: $src not found; skipping" >&2
596
+ continue
597
+ fi
598
+ echo "" >> "$dest"
599
+ echo "<!-- ===== universal-rule: $rule ===== -->" >> "$dest"
600
+ echo "" >> "$dest"
601
+ strip_frontmatter "$src" >> "$dest"
602
+ echo "" >> "$dest"
603
+ echo "---" >> "$dest"
604
+ done
605
+ log " wrote $dest ($(/usr/bin/wc -l < "$dest" | /usr/bin/tr -d ' ') lines)"
606
+ fi
607
+ record_emit ".github/copilot-instructions.md" "<synthesized:5-universal-rules>" "$MANIFEST_LAST_BACKUP"
608
+ fi
609
+ else
610
+ log "Step 1/4: universal-rules — skipped (user opted out)"
611
+ fi
612
+
613
+ # ----- step 2: recipes → .github/instructions/<r>.instructions.md --------
614
+
615
+ log "Step 2/4: recipes (opt-in) → .github/instructions/"
616
+ if [ -n "$RECIPES" ]; then
617
+ mkdir_if_real "$TARGET_ABS/.github/instructions"
618
+ IFS=',' read -ra RECIPE_LIST <<< "$RECIPES"
619
+ for r in "${RECIPE_LIST[@]}"; do
620
+ r="$(printf '%s' "$r" | /usr/bin/sed 's/^ *//; s/ *$//')"
621
+ [ -z "$r" ] && continue
622
+ src="$CORE_ROOT/recipes/$r.md"
623
+ dest="$TARGET_ABS/.github/instructions/$r.instructions.md"
624
+ if [ ! -f "$src" ]; then
625
+ echo "Warning: recipe '$r' not found at $src; skipping" >&2
626
+ continue
627
+ fi
628
+ backup_if_exists "$dest"
629
+ applyto="$(extract_paths_csv "$src")"
630
+ [ -z "$applyto" ] && applyto="**"
631
+ write_copilot_per_file "$src" "$dest" "$applyto"
632
+ log " recipe $r → applyTo: '$applyto'"
633
+ record_emit ".github/instructions/$r.instructions.md" "core/recipes/$r.md" "$MANIFEST_LAST_BACKUP"
634
+ done
635
+ else
636
+ log " (no recipes selected — pass --recipes=name1,name2 to install)"
637
+ fi
638
+
639
+ # ---- Step 2.6: self-improvement runtime (only with --recipes=self-improvement) ----
640
+ case ",$RECIPES," in
641
+ *",self-improvement,"*)
642
+ log "Step 2.6/4: self-improvement (Reflector) → hooks/prompt/agent"
643
+ if [ "$DRY_RUN" != "true" ]; then
644
+ /bin/mkdir -p "$TARGET_ABS/.conductor/reflect" "$TARGET_ABS/.github/hooks" "$TARGET_ABS/.github/prompts" "$TARGET_ABS/.github/agents"
645
+ gi="$TARGET_ABS/.gitignore"
646
+ grep -qxF '.conductor/' "$gi" 2>/dev/null || printf '\n# CONDUCTOR runtime (local trajectories/lessons)\n.conductor/\n' >> "$gi"
647
+ for s in trajectory-log prune-lessons run-weekly; do
648
+ d="$TARGET_ABS/.conductor/reflect/$s.sh"
649
+ backup_if_exists "$d"; /bin/cp "$CORE_ROOT/reflector/$s.sh" "$d"; /bin/chmod +x "$d"
650
+ record_emit ".conductor/reflect/$s.sh" "core/reflector/$s.sh" "$MANIFEST_LAST_BACKUP"
651
+ done
652
+ # scheduling assets: run-weekly.sh needs the brief; SCHEDULING.md documents registration
653
+ for m in reflect-brief SCHEDULING; do
654
+ d="$TARGET_ABS/.conductor/reflect/$m.md"
655
+ backup_if_exists "$d"; /bin/cp "$CORE_ROOT/reflector/$m.md" "$d"
656
+ record_emit ".conductor/reflect/$m.md" "core/reflector/$m.md" "$MANIFEST_LAST_BACKUP"
657
+ done
658
+ hc="$TARGET_ABS/.github/hooks/conductor-reflect.json"
659
+ if [ ! -f "$hc" ]; then
660
+ backup_if_exists "$hc"
661
+ /bin/cat > "$hc" <<'HOOK'
662
+ {
663
+ "version": 1,
664
+ "hooks": {
665
+ "agentStop": [ { "type": "command", "bash": "./.conductor/reflect/trajectory-log.sh", "timeoutSec": 10 } ]
666
+ }
667
+ }
668
+ HOOK
669
+ record_emit ".github/hooks/conductor-reflect.json" "<synthesized>" "$MANIFEST_LAST_BACKUP"
670
+ else
671
+ log " .github/hooks/conductor-reflect.json exists — add an agentStop hook calling ./.conductor/reflect/trajectory-log.sh manually"
672
+ fi
673
+ pr="$TARGET_ABS/.github/prompts/reflect.prompt.md"
674
+ backup_if_exists "$pr"
675
+ { printf -- "---\ndescription: 'Run the CONDUCTOR Reflector — propose lessons from recent sessions (propose-only)'\nagent: 'agent'\n---\n\n"; /bin/cat "$CORE_ROOT/reflector/reflect-brief.md"; } > "$pr"
676
+ record_emit ".github/prompts/reflect.prompt.md" "core/reflector/reflect-brief.md" "$MANIFEST_LAST_BACKUP"
677
+ ag="$TARGET_ABS/.github/agents/reflector.agent.md"
678
+ backup_if_exists "$ag"
679
+ { printf -- '---\nname: reflector\ndescription: "Reads session trajectories and proposes atomic lesson deltas. Propose-only; never applies."\n---\n\n'; strip_frontmatter "$CORE_ROOT/roles/reflector.md"; } > "$ag"
680
+ record_emit ".github/agents/reflector.agent.md" "core/roles/reflector.md" "$MANIFEST_LAST_BACKUP"
681
+ fi
682
+ ;;
683
+ esac
684
+
685
+ # ----- step 3: docs templates --------------------------------------------
686
+
687
+ log "Step 3/4: docs templates → docs/"
688
+ mkdir_if_real "$TARGET_ABS/docs"
689
+ mkdir_if_real "$TARGET_ABS/docs/specs"
690
+
691
+ for tpl in CURRENT_WORK REMAINING_TASKS PLANS TASKS INDEX; do
692
+ src="$CORE_ROOT/docs-templates/$tpl.md"
693
+ dest="$TARGET_ABS/docs/$tpl.md"
694
+ [ -f "$src" ] || continue
695
+ if [ -f "$dest" ]; then
696
+ log " $dest exists — leaving in place"
697
+ continue
698
+ fi
699
+ if [ "$DRY_RUN" = "true" ]; then
700
+ log "would copy $src -> $dest"
701
+ else
702
+ /bin/cp "$src" "$dest"
703
+ record_emit "docs/$tpl.md" "core/docs-templates/$tpl.md" ""
704
+ fi
705
+ done
706
+
707
+ if [ -f "$CORE_ROOT/docs-templates/specs/_example.md" ]; then
708
+ src="$CORE_ROOT/docs-templates/specs/_example.md"
709
+ dest="$TARGET_ABS/docs/specs/_example.md"
710
+ if [ ! -f "$dest" ]; then
711
+ if [ "$DRY_RUN" = "true" ]; then
712
+ log "would copy $src -> $dest"
713
+ else
714
+ /bin/cp "$src" "$dest"
715
+ record_emit "docs/specs/_example.md" "core/docs-templates/specs/_example.md" ""
716
+ fi
717
+ fi
718
+ fi
719
+
720
+ # ----- step 4: skip notice -----------------------------------------------
721
+
722
+ log "Step 4/4: skipped layers (Copilot has no equivalent)"
723
+ log " - core/roles/ → SKIP (no sub-agent dispatch)"
724
+ log " - core/hooks/ → SKIP except Reflector hook (--recipes=self-improvement, ADR-032; other guards Claude-only, ADR-034)"
725
+ log " - hookify-templates/ → SKIP (Claude Code plugin only)"
726
+
727
+ finalize_manifest
728
+
729
+ # ----- summary -----------------------------------------------------------
730
+
731
+ echo ""
732
+ echo "========================================================"
733
+ echo " Done."
734
+ echo " Target: $TARGET_ABS"
735
+ if [ "$PER_RULE" = "true" ]; then
736
+ echo " Universal rules: 5 → .github/instructions/*.instructions.md (per-rule mode)"
737
+ else
738
+ echo " Universal rules: 5 → .github/copilot-instructions.md (single-file mode)"
739
+ fi
740
+ echo " Recipes installed: ${RECIPES:-(none)}"
741
+ echo " Skipped (Copilot incompatibility): roles, hooks, hookify-templates"
742
+ echo ""
743
+ echo " Activation:"
744
+ echo " GitHub Copilot reads .github/copilot-instructions.md and .github/instructions/"
745
+ echo " automatically — no restart required. The same files work in:"
746
+ echo " - VS Code (Copilot extension)"
747
+ echo " - Cursor (Copilot extension)"
748
+ echo " - Windsurf (Copilot adapter)"
749
+ echo " - JetBrains IDEs (Copilot plugin)"
750
+ echo " - Neovim (copilot.vim)"
751
+ echo "========================================================"
752
+ echo ""
753
+ echo "Next steps for the project:"
754
+ echo " 1. Open the project in any IDE with Copilot enabled."
755
+ echo " 2. Edit docs/CURRENT_WORK.md with the project's current state."
756
+ echo " 3. Configure Copilot PR review at the repo level (Stage B analog)."
757
+ echo " 4. Verify: cat $TARGET_ABS/.github/copilot-instructions.md | head -30"