its-magic 0.1.2-39 → 0.1.2-42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/installer.ps1 +1 -1
- package/installer.sh +649 -643
- package/package.json +5 -1
- package/scripts/guard_installer_publish.py +88 -0
- package/scripts/remote_config_summary.py +243 -0
- package/template/.cursor/commands/auto.md +132 -17
- package/template/.cursor/commands/execute.md +6 -0
- package/template/.cursor/commands/qa.md +5 -0
- package/template/.cursor/rules/coding-standards.mdc +7 -0
- package/template/.cursor/scratchpad.local.example.md +20 -1
- package/template/.cursor/scratchpad.md +35 -10
- package/template/.cursorignore +5 -0
- package/template/.env.example +28 -0
- package/template/README.md +4 -0
- package/template/docs/engineering/auto-orchestration-reference.md +315 -27
- package/template/docs/engineering/context/installer-owned-paths.manifest +78 -72
- package/template/docs/engineering/runbook.md +1772 -1525
- package/template/docs/engineering/runtime-connectivity.md +47 -0
- package/template/docs/engineering/us-0084-remote-e2e.md +44 -0
- package/template/scripts/guard_installer_publish.py +88 -0
- package/template/scripts/remote_config_summary.py +243 -0
package/installer.sh
CHANGED
|
@@ -1,643 +1,649 @@
|
|
|
1
|
-
#!/usr/bin/env sh
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
# BUG-0004: keep startup shell options POSIX-safe for /bin/sh execution.
|
|
5
|
-
# Do not use bash-only "set" flags in this unconditional startup path.
|
|
6
|
-
|
|
7
|
-
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
8
|
-
SOURCE_ROOT="$SCRIPT_DIR/template"
|
|
9
|
-
MANIFEST_NAME="docs/engineering/context/installer-owned-paths.manifest"
|
|
10
|
-
REPO_URL="https://github.com/fl0wm0ti0n/its-magic"
|
|
11
|
-
APP_VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$SCRIPT_DIR/package.json" 2>/dev/null | head -n 1)
|
|
12
|
-
[ -z "$APP_VERSION" ] && APP_VERSION="unknown"
|
|
13
|
-
|
|
14
|
-
show_banner() {
|
|
15
|
-
printf "\n"
|
|
16
|
-
printf "\033[1;35m ██╗████████╗███████╗ ███╗ ███╗ █████╗ ██████╗ ██╗ ██████╗\033[0m\n"
|
|
17
|
-
printf "\033[1;35m ██║╚══██╔══╝██╔════╝ ████╗ ████║██╔══██╗██╔════╝ ██║██╔════╝\033[0m\n"
|
|
18
|
-
printf "\033[1;35m ██║ ██║ ███████╗█████╗██╔████╔██║███████║██║ ███╗██║██║ \033[0m\n"
|
|
19
|
-
printf "\033[1;36m ██║ ██║ ╚════██║╚════╝██║╚██╔╝██║██╔══██║██║ ██║██║██║ \033[0m\n"
|
|
20
|
-
printf "\033[1;36m ██║ ██║ ███████║ ██║ ╚═╝ ██║██║ ██║╚██████╔╝██║╚██████╗\033[0m\n"
|
|
21
|
-
printf "\033[1;36m ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝\033[0m\n"
|
|
22
|
-
printf "\n"
|
|
23
|
-
printf "\033[1;33m AI dev team\033[0m\n"
|
|
24
|
-
printf "\n"
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
show_help() {
|
|
28
|
-
show_banner
|
|
29
|
-
printf "its-magic v%s\n" "$APP_VERSION"
|
|
30
|
-
printf "Repository: %s\n\n" "$REPO_URL"
|
|
31
|
-
printf "Install AI dev team workflow files into any Cursor repository.\n\n"
|
|
32
|
-
printf "Usage:\n"
|
|
33
|
-
printf " its-magic --target <path> [--mode <mode>] [--backup] [--create]\n"
|
|
34
|
-
printf " its-magic --clean-repo [--target <path>] [--yes]\n"
|
|
35
|
-
printf " its-magic --help | --version\n\n"
|
|
36
|
-
printf "Install options:\n"
|
|
37
|
-
printf " --target <path> Path to the repository where workflow files are installed.\n"
|
|
38
|
-
printf " If omitted you will be prompted interactively.\n"
|
|
39
|
-
printf " --mode <mode> How to handle files that already exist in the target:\n"
|
|
40
|
-
printf " missing Only copy files that do not exist yet (default).\n"
|
|
41
|
-
printf " Safe for repos that already have some workflow files.\n"
|
|
42
|
-
printf " overwrite Replace every file, even if it already exists.\n"
|
|
43
|
-
printf " Combine with --backup to keep a snapshot first.\n"
|
|
44
|
-
printf " interactive Ask per file whether to overwrite or skip.\n"
|
|
45
|
-
printf " upgrade Update framework files while preserving user data.\n"
|
|
46
|
-
printf " Use after updating its-magic to a newer version.\n"
|
|
47
|
-
printf " --backup Before overwriting, save existing files to backups/<timestamp>/.\n"
|
|
48
|
-
printf " Ignored when mode is 'missing' (nothing gets replaced).\n"
|
|
49
|
-
printf " --create Create the target directory if it does not exist.\n\n"
|
|
50
|
-
printf " Note: installer bootstraps runbook TEST/LINT/TYPECHECK commands from\n"
|
|
51
|
-
printf " OS+stack detection; unresolved TEST_COMMAND fails fast with\n"
|
|
52
|
-
printf " [RUNBOOK_BOOTSTRAP_ERROR] diagnostics.\n"
|
|
53
|
-
printf " Note: scratchpad Model B: .cursor/scratchpad.md is\n"
|
|
54
|
-
printf " materialized when missing; Python 3 on PATH is required for validation.\n"
|
|
55
|
-
printf " Recovery: python installer.py --scratchpad-postinstall --target <repo> --mode missing\n\n"
|
|
56
|
-
printf "Clean options:\n"
|
|
57
|
-
printf " --clean-repo Remove all its-magic workflow artifacts from the target repo\n"
|
|
58
|
-
printf " (owned paths from installer manifest, including .cursor,\n"
|
|
59
|
-
printf " docs/product, docs/engineering, docs/user-guides, sprints,\n"
|
|
60
|
-
printf " handoffs, decisions, workflow scripts, CI files, and\n"
|
|
61
|
-
printf " installer metadata under its_magic/ (legacy .its-magic-version\n"
|
|
62
|
-
printf " is also removed when present). Your own source code is never touched.\n"
|
|
63
|
-
printf " --target <path> Repo to clean (default: current directory).\n"
|
|
64
|
-
printf " --yes Skip the confirmation prompt.\n\n"
|
|
65
|
-
printf "Info:\n"
|
|
66
|
-
printf " --help, -h Show this help and exit.\n"
|
|
67
|
-
printf " --version, -v Print the installed version and exit.\n\n"
|
|
68
|
-
printf "Examples:\n"
|
|
69
|
-
printf " its-magic --target . --mode missing Safe first-time setup\n"
|
|
70
|
-
printf " its-magic --target . --mode upgrade Update framework, keep user data\n"
|
|
71
|
-
printf " its-magic --target . --mode overwrite --backup Replace all files, keep backup\n"
|
|
72
|
-
printf " its-magic --clean-repo --target . --yes Remove workflow artifacts silently\n\n"
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
ensure_parent() {
|
|
76
|
-
dir=$(dirname "$1")
|
|
77
|
-
[ -d "$dir" ] || mkdir -p "$dir"
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
list_source_files() {
|
|
81
|
-
source_root="$1"
|
|
82
|
-
shift
|
|
83
|
-
for rel in "$@"; do
|
|
84
|
-
src="$source_root/$rel"
|
|
85
|
-
if [ -f "$src" ]; then
|
|
86
|
-
echo "$rel"
|
|
87
|
-
elif [ -d "$src" ]; then
|
|
88
|
-
find "$src" -type f | sed "s|^$source_root/||"
|
|
89
|
-
fi
|
|
90
|
-
done | sort -u
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
get_manifest_paths() {
|
|
94
|
-
section="$1"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
{
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
printf "%s" "
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
exit
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
exit
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
[ -f "$
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if [ -f "$target_root/
|
|
263
|
-
TEST_CANDIDATE="
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
"
|
|
289
|
-
command -v
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
BOOTSTRAP_VALID="
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
BOOTSTRAP_VALID="
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
--
|
|
378
|
-
--
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if [ ! -
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
fi
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
fi
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
fi
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if [ -z "$
|
|
475
|
-
printf "%s\n" "
|
|
476
|
-
exit 1
|
|
477
|
-
fi
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if [ "$
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
[ "$rel" = "$scratchpad_example_rel" ] && scratchpad_example_status="
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
[ "$
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
printf "
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if [ "$MODE" = "
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
printf "
|
|
642
|
-
exit
|
|
643
|
-
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# BUG-0004: keep startup shell options POSIX-safe for /bin/sh execution.
|
|
5
|
+
# Do not use bash-only "set" flags in this unconditional startup path.
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
8
|
+
SOURCE_ROOT="$SCRIPT_DIR/template"
|
|
9
|
+
MANIFEST_NAME="docs/engineering/context/installer-owned-paths.manifest"
|
|
10
|
+
REPO_URL="https://github.com/fl0wm0ti0n/its-magic"
|
|
11
|
+
APP_VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$SCRIPT_DIR/package.json" 2>/dev/null | head -n 1)
|
|
12
|
+
[ -z "$APP_VERSION" ] && APP_VERSION="unknown"
|
|
13
|
+
|
|
14
|
+
show_banner() {
|
|
15
|
+
printf "\n"
|
|
16
|
+
printf "\033[1;35m ██╗████████╗███████╗ ███╗ ███╗ █████╗ ██████╗ ██╗ ██████╗\033[0m\n"
|
|
17
|
+
printf "\033[1;35m ██║╚══██╔══╝██╔════╝ ████╗ ████║██╔══██╗██╔════╝ ██║██╔════╝\033[0m\n"
|
|
18
|
+
printf "\033[1;35m ██║ ██║ ███████╗█████╗██╔████╔██║███████║██║ ███╗██║██║ \033[0m\n"
|
|
19
|
+
printf "\033[1;36m ██║ ██║ ╚════██║╚════╝██║╚██╔╝██║██╔══██║██║ ██║██║██║ \033[0m\n"
|
|
20
|
+
printf "\033[1;36m ██║ ██║ ███████║ ██║ ╚═╝ ██║██║ ██║╚██████╔╝██║╚██████╗\033[0m\n"
|
|
21
|
+
printf "\033[1;36m ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝\033[0m\n"
|
|
22
|
+
printf "\n"
|
|
23
|
+
printf "\033[1;33m AI dev team\033[0m\n"
|
|
24
|
+
printf "\n"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
show_help() {
|
|
28
|
+
show_banner
|
|
29
|
+
printf "its-magic v%s\n" "$APP_VERSION"
|
|
30
|
+
printf "Repository: %s\n\n" "$REPO_URL"
|
|
31
|
+
printf "Install AI dev team workflow files into any Cursor repository.\n\n"
|
|
32
|
+
printf "Usage:\n"
|
|
33
|
+
printf " its-magic --target <path> [--mode <mode>] [--backup] [--create]\n"
|
|
34
|
+
printf " its-magic --clean-repo [--target <path>] [--yes]\n"
|
|
35
|
+
printf " its-magic --help | --version\n\n"
|
|
36
|
+
printf "Install options:\n"
|
|
37
|
+
printf " --target <path> Path to the repository where workflow files are installed.\n"
|
|
38
|
+
printf " If omitted you will be prompted interactively.\n"
|
|
39
|
+
printf " --mode <mode> How to handle files that already exist in the target:\n"
|
|
40
|
+
printf " missing Only copy files that do not exist yet (default).\n"
|
|
41
|
+
printf " Safe for repos that already have some workflow files.\n"
|
|
42
|
+
printf " overwrite Replace every file, even if it already exists.\n"
|
|
43
|
+
printf " Combine with --backup to keep a snapshot first.\n"
|
|
44
|
+
printf " interactive Ask per file whether to overwrite or skip.\n"
|
|
45
|
+
printf " upgrade Update framework files while preserving user data.\n"
|
|
46
|
+
printf " Use after updating its-magic to a newer version.\n"
|
|
47
|
+
printf " --backup Before overwriting, save existing files to backups/<timestamp>/.\n"
|
|
48
|
+
printf " Ignored when mode is 'missing' (nothing gets replaced).\n"
|
|
49
|
+
printf " --create Create the target directory if it does not exist.\n\n"
|
|
50
|
+
printf " Note: installer bootstraps runbook TEST/LINT/TYPECHECK commands from\n"
|
|
51
|
+
printf " OS+stack detection; unresolved TEST_COMMAND fails fast with\n"
|
|
52
|
+
printf " [RUNBOOK_BOOTSTRAP_ERROR] diagnostics.\n"
|
|
53
|
+
printf " Note: scratchpad Model B: .cursor/scratchpad.md is\n"
|
|
54
|
+
printf " materialized when missing; Python 3 on PATH is required for validation.\n"
|
|
55
|
+
printf " Recovery: python installer.py --scratchpad-postinstall --target <repo> --mode missing\n\n"
|
|
56
|
+
printf "Clean options:\n"
|
|
57
|
+
printf " --clean-repo Remove all its-magic workflow artifacts from the target repo\n"
|
|
58
|
+
printf " (owned paths from installer manifest, including .cursor,\n"
|
|
59
|
+
printf " docs/product, docs/engineering, docs/user-guides, sprints,\n"
|
|
60
|
+
printf " handoffs, decisions, workflow scripts, CI files, and\n"
|
|
61
|
+
printf " installer metadata under its_magic/ (legacy .its-magic-version\n"
|
|
62
|
+
printf " is also removed when present). Your own source code is never touched.\n"
|
|
63
|
+
printf " --target <path> Repo to clean (default: current directory).\n"
|
|
64
|
+
printf " --yes Skip the confirmation prompt.\n\n"
|
|
65
|
+
printf "Info:\n"
|
|
66
|
+
printf " --help, -h Show this help and exit.\n"
|
|
67
|
+
printf " --version, -v Print the installed version and exit.\n\n"
|
|
68
|
+
printf "Examples:\n"
|
|
69
|
+
printf " its-magic --target . --mode missing Safe first-time setup\n"
|
|
70
|
+
printf " its-magic --target . --mode upgrade Update framework, keep user data\n"
|
|
71
|
+
printf " its-magic --target . --mode overwrite --backup Replace all files, keep backup\n"
|
|
72
|
+
printf " its-magic --clean-repo --target . --yes Remove workflow artifacts silently\n\n"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
ensure_parent() {
|
|
76
|
+
dir=$(dirname "$1")
|
|
77
|
+
[ -d "$dir" ] || mkdir -p "$dir"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
list_source_files() {
|
|
81
|
+
source_root="$1"
|
|
82
|
+
shift
|
|
83
|
+
for rel in "$@"; do
|
|
84
|
+
src="$source_root/$rel"
|
|
85
|
+
if [ -f "$src" ]; then
|
|
86
|
+
echo "$rel"
|
|
87
|
+
elif [ -d "$src" ]; then
|
|
88
|
+
find "$src" -type f | sed "s|^$source_root/||"
|
|
89
|
+
fi
|
|
90
|
+
done | sort -u
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get_manifest_paths() {
|
|
94
|
+
section="$1"
|
|
95
|
+
# BUG-0008: strip trailing CR so CRLF manifests (Windows-published npm tarballs)
|
|
96
|
+
# still match [section] headers under POSIX awk on Linux.
|
|
97
|
+
awk -v s="$section" '
|
|
98
|
+
BEGIN { in_section=0 }
|
|
99
|
+
{
|
|
100
|
+
sub(/\r$/, "")
|
|
101
|
+
}
|
|
102
|
+
/^[[:space:]]*#/ { next }
|
|
103
|
+
/^[[:space:]]*$/ { next }
|
|
104
|
+
/^\[/ {
|
|
105
|
+
in_section = ($0 == "[" s "]")
|
|
106
|
+
next
|
|
107
|
+
}
|
|
108
|
+
{ if (in_section) print $0 }
|
|
109
|
+
' "$OWNERSHIP_MANIFEST"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
backup_files() {
|
|
113
|
+
target_root="$1"
|
|
114
|
+
shift
|
|
115
|
+
timestamp=$(date -u +"%Y%m%d-%H%M%SZ")
|
|
116
|
+
backup_root="$target_root/backups/$timestamp"
|
|
117
|
+
for rel in "$@"; do
|
|
118
|
+
src="$target_root/$rel"
|
|
119
|
+
if [ -f "$src" ]; then
|
|
120
|
+
dst="$backup_root/$rel"
|
|
121
|
+
ensure_parent "$dst"
|
|
122
|
+
cp -p "$src" "$dst"
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
echo "$backup_root"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
choose_mode() {
|
|
129
|
+
printf "%s\n" "Select install mode:"
|
|
130
|
+
printf "%s\n" "1) missing-only (copy only files that do not exist)"
|
|
131
|
+
printf "%s\n" "2) overwrite-all (replace existing files)"
|
|
132
|
+
printf "%s\n" "3) interactive (prompt per file)"
|
|
133
|
+
printf "%s\n" "4) upgrade (update framework files, preserve user data)"
|
|
134
|
+
printf "%s" "Enter 1, 2, 3, or 4: "
|
|
135
|
+
read -r choice
|
|
136
|
+
case "$choice" in
|
|
137
|
+
1) echo "missing" ;;
|
|
138
|
+
2) echo "overwrite" ;;
|
|
139
|
+
4) echo "upgrade" ;;
|
|
140
|
+
*) echo "interactive" ;;
|
|
141
|
+
esac
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
scratchpad_postinstall() {
|
|
145
|
+
target_root="$1"
|
|
146
|
+
mode="$2"
|
|
147
|
+
installer_py="$SCRIPT_DIR/installer.py"
|
|
148
|
+
if [ ! -f "$installer_py" ]; then
|
|
149
|
+
printf "%s\n" "[SCRATCHPAD_POSTINSTALL_ERROR] installer.py missing next to installer.sh."
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
153
|
+
python3 "$installer_py" --scratchpad-postinstall --target "$target_root" --mode "$mode" || exit $?
|
|
154
|
+
elif command -v python >/dev/null 2>&1; then
|
|
155
|
+
python "$installer_py" --scratchpad-postinstall --target "$target_root" --mode "$mode" || exit $?
|
|
156
|
+
else
|
|
157
|
+
printf "%s\n" "[SCRATCHPAD_POSTINSTALL_ERROR] PYTHON_NOT_FOUND: Python 3 is required for scratchpad materialization/validation (Model B)."
|
|
158
|
+
exit 1
|
|
159
|
+
fi
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
validate_install_completeness() {
|
|
163
|
+
target_root="$1"
|
|
164
|
+
installer_py="$SCRIPT_DIR/installer.py"
|
|
165
|
+
if [ ! -f "$installer_py" ]; then
|
|
166
|
+
printf "%s\n" "[INSTALL_COMPLETENESS_FAILED] installer.py missing next to installer.sh."
|
|
167
|
+
exit 1
|
|
168
|
+
fi
|
|
169
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
170
|
+
python3 "$installer_py" --validate-install-completeness --target "$target_root" || exit $?
|
|
171
|
+
elif command -v python >/dev/null 2>&1; then
|
|
172
|
+
python "$installer_py" --validate-install-completeness --target "$target_root" || exit $?
|
|
173
|
+
else
|
|
174
|
+
printf "%s\n" "[INSTALL_COMPLETENESS_FAILED] PYTHON_NOT_FOUND: Python is required for deterministic installer completeness validation."
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
classify_file() {
|
|
180
|
+
rel="$1"
|
|
181
|
+
case "$rel" in
|
|
182
|
+
README.md) echo "mixed" ;;
|
|
183
|
+
.cursor/commands/*|.cursor/rules/*|.cursor/agents/*|.cursor/skills/*) echo "framework" ;;
|
|
184
|
+
.cursor/hooks/*|.cursor/hooks.json|.cursor/scratchpad.local.example.md) echo "framework" ;;
|
|
185
|
+
.github/workflows/*|scripts/validate-and-push*|scripts/sync_push_gates.py|docs/engineering/context/*|its_magic/*) echo "framework" ;;
|
|
186
|
+
.its-magic-version|its_magic/.its-magic-version|its_magic/README.md) echo "framework" ;;
|
|
187
|
+
docs/product/*|docs/engineering/*|docs/user-guides/*) echo "user-data" ;;
|
|
188
|
+
sprints/*|handoffs/*|decisions/*) echo "user-data" ;;
|
|
189
|
+
*) echo "framework" ;;
|
|
190
|
+
esac
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
read_installed_version() {
|
|
194
|
+
primary="$1/its_magic/.its-magic-version"
|
|
195
|
+
legacy="$1/.its-magic-version"
|
|
196
|
+
if [ -f "$primary" ]; then
|
|
197
|
+
cat "$primary" | tr -d '\n'
|
|
198
|
+
return 0
|
|
199
|
+
fi
|
|
200
|
+
if [ -f "$legacy" ]; then
|
|
201
|
+
cat "$legacy" | tr -d '\n'
|
|
202
|
+
return 0
|
|
203
|
+
fi
|
|
204
|
+
printf "unknown"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
write_installed_version() {
|
|
208
|
+
vf="$1/its_magic/.its-magic-version"
|
|
209
|
+
ensure_parent "$vf"
|
|
210
|
+
printf "%s" "$2" > "$vf"
|
|
211
|
+
legacy="$1/.its-magic-version"
|
|
212
|
+
[ -f "$legacy" ] && rm -f "$legacy"
|
|
213
|
+
return 0
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
sync_root_readme_to_its_magic() {
|
|
217
|
+
target_root="$1"
|
|
218
|
+
[ -f "$target_root/README.md" ] || return 1
|
|
219
|
+
dst="$target_root/its_magic/README.md"
|
|
220
|
+
ensure_parent "$dst"
|
|
221
|
+
cp -p "$target_root/README.md" "$dst"
|
|
222
|
+
return 0
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
read_runbook_key() {
|
|
226
|
+
runbook_path="$1"
|
|
227
|
+
key="$2"
|
|
228
|
+
[ -f "$runbook_path" ] || { printf ""; return; }
|
|
229
|
+
awk -F: -v k="$key" '$1==k { sub(/^[[:space:]]*/, "", $2); print $2; exit }' "$runbook_path"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
write_runbook_key() {
|
|
233
|
+
runbook_path="$1"
|
|
234
|
+
key="$2"
|
|
235
|
+
value="$3"
|
|
236
|
+
[ -f "$runbook_path" ] || return 1
|
|
237
|
+
tmp="$runbook_path.tmp.$$"
|
|
238
|
+
awk -v k="$key" -v v="$value" '
|
|
239
|
+
BEGIN { changed=0 }
|
|
240
|
+
index($0, k":") == 1 && changed==0 { print k": "v; changed=1; next }
|
|
241
|
+
{ print $0 }
|
|
242
|
+
END { if (changed==0) exit 2 }
|
|
243
|
+
' "$runbook_path" > "$tmp" || { rm -f "$tmp"; return 1; }
|
|
244
|
+
mv "$tmp" "$runbook_path"
|
|
245
|
+
return 0
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
package_has_script() {
|
|
249
|
+
target_root="$1"
|
|
250
|
+
script_name="$2"
|
|
251
|
+
pkg="$target_root/package.json"
|
|
252
|
+
[ -f "$pkg" ] || return 1
|
|
253
|
+
command -v node >/dev/null 2>&1 || return 1
|
|
254
|
+
node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));const s=(p.scripts||{})[process.argv[2]];process.exit((typeof s==='string'&&s.trim())?0:1);" "$pkg" "$script_name" >/dev/null 2>&1
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
detect_runbook_defaults() {
|
|
258
|
+
target_root="$1"
|
|
259
|
+
TEST_CANDIDATE=""
|
|
260
|
+
LINT_CANDIDATE=""
|
|
261
|
+
TYPECHECK_CANDIDATE=""
|
|
262
|
+
if [ -f "$target_root/package.json" ] && package_has_script "$target_root" "test"; then
|
|
263
|
+
TEST_CANDIDATE="npm run test"
|
|
264
|
+
package_has_script "$target_root" "lint" && LINT_CANDIDATE="npm run lint"
|
|
265
|
+
package_has_script "$target_root" "typecheck" && TYPECHECK_CANDIDATE="npm run typecheck"
|
|
266
|
+
return 0
|
|
267
|
+
fi
|
|
268
|
+
if [ -f "$target_root/go.mod" ]; then
|
|
269
|
+
TEST_CANDIDATE="go test ./..."
|
|
270
|
+
return 0
|
|
271
|
+
fi
|
|
272
|
+
if [ -f "$target_root/pyproject.toml" ] || [ -f "$target_root/requirements.txt" ] || [ -f "$target_root/setup.py" ]; then
|
|
273
|
+
TEST_CANDIDATE="python -m pytest"
|
|
274
|
+
return 0
|
|
275
|
+
fi
|
|
276
|
+
if [ -f "$target_root/tests/run-tests.sh" ]; then
|
|
277
|
+
TEST_CANDIDATE="sh tests/run-tests.sh"
|
|
278
|
+
return 0
|
|
279
|
+
fi
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
validate_bootstrap_command() {
|
|
283
|
+
target_root="$1"
|
|
284
|
+
key="$2"
|
|
285
|
+
cmd="$3"
|
|
286
|
+
[ -n "$cmd" ] || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="${key}_UNDETECTED"; return 0; }
|
|
287
|
+
case "$cmd" in
|
|
288
|
+
"npm run "*)
|
|
289
|
+
command -v npm >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="NPM_NOT_FOUND"; return 0; }
|
|
290
|
+
script_name=$(printf "%s" "$cmd" | sed 's/^npm run[[:space:]]\+//')
|
|
291
|
+
package_has_script "$target_root" "$script_name" || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="NPM_SCRIPT_MISSING:$script_name"; return 0; }
|
|
292
|
+
BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"; return 0
|
|
293
|
+
;;
|
|
294
|
+
"python -m pytest")
|
|
295
|
+
command -v python >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="PYTHON_NOT_FOUND"; return 0; }
|
|
296
|
+
BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"; return 0
|
|
297
|
+
;;
|
|
298
|
+
"go test "*)
|
|
299
|
+
command -v go >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="GO_NOT_FOUND"; return 0; }
|
|
300
|
+
[ -f "$target_root/go.mod" ] || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="GO_MOD_MISSING"; return 0; }
|
|
301
|
+
BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"; return 0
|
|
302
|
+
;;
|
|
303
|
+
"sh "*)
|
|
304
|
+
command -v sh >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="SH_NOT_FOUND"; return 0; }
|
|
305
|
+
[ -f "$target_root/tests/run-tests.sh" ] || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="RUN_TESTS_SH_MISSING"; return 0; }
|
|
306
|
+
BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"; return 0
|
|
307
|
+
;;
|
|
308
|
+
esac
|
|
309
|
+
exe=$(printf "%s" "$cmd" | awk '{print $1}')
|
|
310
|
+
command -v "$exe" >/dev/null 2>&1 || { BOOTSTRAP_VALID="false"; BOOTSTRAP_REASON="EXECUTABLE_NOT_FOUND:$exe"; return 0; }
|
|
311
|
+
BOOTSTRAP_VALID="true"; BOOTSTRAP_REASON="OK"
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
bootstrap_runbook_commands() {
|
|
315
|
+
target_root="$1"
|
|
316
|
+
runbook="$target_root/docs/engineering/runbook.md"
|
|
317
|
+
[ -f "$runbook" ] || { BOOTSTRAP_OK="true"; BOOTSTRAP_NOTES=""; return 0; }
|
|
318
|
+
BOOTSTRAP_NOTES=""
|
|
319
|
+
APPLIED=""
|
|
320
|
+
detect_runbook_defaults "$target_root"
|
|
321
|
+
for key in TEST_COMMAND LINT_COMMAND TYPECHECK_COMMAND; do
|
|
322
|
+
current=$(read_runbook_key "$runbook" "$key")
|
|
323
|
+
[ -n "$current" ] && continue
|
|
324
|
+
candidate=""
|
|
325
|
+
[ "$key" = "TEST_COMMAND" ] && candidate="$TEST_CANDIDATE"
|
|
326
|
+
[ "$key" = "LINT_COMMAND" ] && candidate="$LINT_CANDIDATE"
|
|
327
|
+
[ "$key" = "TYPECHECK_COMMAND" ] && candidate="$TYPECHECK_CANDIDATE"
|
|
328
|
+
if [ -z "$candidate" ]; then
|
|
329
|
+
if [ "$key" = "TEST_COMMAND" ]; then
|
|
330
|
+
BOOTSTRAP_NOTES="${BOOTSTRAP_NOTES}[RUNBOOK_BOOTSTRAP_ERROR] TEST_COMMAND_UNRESOLVED: could not detect a valid baseline test command. Fix: define TEST_COMMAND in docs/engineering/runbook.md or add detectable stack markers (package.json scripts.test, pyproject.toml, go.mod)."$'\n'
|
|
331
|
+
fi
|
|
332
|
+
continue
|
|
333
|
+
fi
|
|
334
|
+
validate_bootstrap_command "$target_root" "$key" "$candidate"
|
|
335
|
+
if [ "$BOOTSTRAP_VALID" = "true" ]; then
|
|
336
|
+
if write_runbook_key "$runbook" "$key" "$candidate"; then
|
|
337
|
+
if [ -z "$APPLIED" ]; then APPLIED="$key=$candidate"; else APPLIED="$APPLIED, $key=$candidate"; fi
|
|
338
|
+
fi
|
|
339
|
+
elif [ "$key" = "TEST_COMMAND" ]; then
|
|
340
|
+
BOOTSTRAP_NOTES="${BOOTSTRAP_NOTES}[RUNBOOK_BOOTSTRAP_ERROR] TEST_COMMAND_INVALID:$BOOTSTRAP_REASON. Fix: set a valid TEST_COMMAND in docs/engineering/runbook.md."$'\n'
|
|
341
|
+
fi
|
|
342
|
+
done
|
|
343
|
+
[ -n "$APPLIED" ] && BOOTSTRAP_NOTES="${BOOTSTRAP_NOTES}[RUNBOOK_BOOTSTRAP] Applied defaults: $APPLIED"$'\n'
|
|
344
|
+
final_test=$(read_runbook_key "$runbook" "TEST_COMMAND")
|
|
345
|
+
if [ -n "$final_test" ]; then BOOTSTRAP_OK="true"; else BOOTSTRAP_OK="false"; fi
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
prompt_yes_no() {
|
|
349
|
+
label="$1"
|
|
350
|
+
default="$2"
|
|
351
|
+
suffix="y/N"
|
|
352
|
+
[ "$default" = "true" ] && suffix="Y/n"
|
|
353
|
+
printf "%s [%s]: " "$label" "$suffix"
|
|
354
|
+
read -r value
|
|
355
|
+
value=$(printf "%s" "$value" | tr 'A-Z' 'a-z')
|
|
356
|
+
if [ -z "$value" ]; then
|
|
357
|
+
[ "$default" = "true" ] && return 0 || return 1
|
|
358
|
+
fi
|
|
359
|
+
[ "$value" = "y" ] || [ "$value" = "yes" ]
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
TARGET=""
|
|
363
|
+
MODE=""
|
|
364
|
+
BACKUP="false"
|
|
365
|
+
CREATE="false"
|
|
366
|
+
CLEAN_REPO="false"
|
|
367
|
+
ASSUME_YES="false"
|
|
368
|
+
SHOW_HELP="false"
|
|
369
|
+
SHOW_VERSION="false"
|
|
370
|
+
|
|
371
|
+
if [ $# -eq 0 ]; then
|
|
372
|
+
SHOW_HELP="true"
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
while [ $# -gt 0 ]; do
|
|
376
|
+
case "$1" in
|
|
377
|
+
--target) TARGET="$2"; shift 2 ;;
|
|
378
|
+
--mode) MODE="$2"; shift 2 ;;
|
|
379
|
+
--backup) BACKUP="true"; shift 1 ;;
|
|
380
|
+
--create) CREATE="true"; shift 1 ;;
|
|
381
|
+
--clean-repo) CLEAN_REPO="true"; shift 1 ;;
|
|
382
|
+
--yes) ASSUME_YES="true"; shift 1 ;;
|
|
383
|
+
--help|-h) SHOW_HELP="true"; shift 1 ;;
|
|
384
|
+
--version|-v) SHOW_VERSION="true"; shift 1 ;;
|
|
385
|
+
*) shift 1 ;;
|
|
386
|
+
esac
|
|
387
|
+
done
|
|
388
|
+
|
|
389
|
+
if [ "$SHOW_VERSION" = "true" ]; then
|
|
390
|
+
printf "its-magic v%s\n" "$APP_VERSION"
|
|
391
|
+
exit 0
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
if [ "$SHOW_HELP" = "true" ]; then
|
|
395
|
+
show_help
|
|
396
|
+
exit 0
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
if [ ! -d "$SOURCE_ROOT" ]; then
|
|
400
|
+
printf "%s\n" "[INSTALL_SOURCE_ERROR] template directory is missing. Reinstall its-magic package."
|
|
401
|
+
exit 1
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
OWNERSHIP_MANIFEST="$SOURCE_ROOT/$MANIFEST_NAME"
|
|
405
|
+
if [ ! -f "$OWNERSHIP_MANIFEST" ]; then
|
|
406
|
+
FALLBACK_MANIFEST="$SCRIPT_DIR/$MANIFEST_NAME"
|
|
407
|
+
if [ -f "$FALLBACK_MANIFEST" ]; then
|
|
408
|
+
OWNERSHIP_MANIFEST="$FALLBACK_MANIFEST"
|
|
409
|
+
else
|
|
410
|
+
printf "%s\n" "[INSTALL_SOURCE_ERROR] installer-owned-paths.manifest not found. Reinstall its-magic package."
|
|
411
|
+
exit 1
|
|
412
|
+
fi
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
if [ "$CLEAN_REPO" = "true" ]; then
|
|
416
|
+
if [ -z "$TARGET" ]; then
|
|
417
|
+
TARGET="."
|
|
418
|
+
fi
|
|
419
|
+
if [ ! -d "$TARGET" ]; then
|
|
420
|
+
printf "%s\n" "Target directory does not exist."
|
|
421
|
+
exit 1
|
|
422
|
+
fi
|
|
423
|
+
TARGET_ROOT=$(cd "$TARGET" && pwd)
|
|
424
|
+
if [ "$ASSUME_YES" != "true" ]; then
|
|
425
|
+
if ! prompt_yes_no "Clean its-magic workflow artifacts in $TARGET_ROOT?" "false"; then
|
|
426
|
+
printf "%s\n" "Aborted."
|
|
427
|
+
exit 1
|
|
428
|
+
fi
|
|
429
|
+
fi
|
|
430
|
+
CLEAN_PATHS=$(get_manifest_paths "clean_paths")
|
|
431
|
+
if [ -z "$CLEAN_PATHS" ]; then
|
|
432
|
+
printf "%s\n" "[INSTALL_MANIFEST_ERROR] clean_paths section is empty in $OWNERSHIP_MANIFEST"
|
|
433
|
+
exit 1
|
|
434
|
+
fi
|
|
435
|
+
for rel in $CLEAN_PATHS; do
|
|
436
|
+
path="$TARGET_ROOT/$rel"
|
|
437
|
+
if [ -e "$path" ]; then
|
|
438
|
+
rm -rf "$path"
|
|
439
|
+
printf "%s\n" "Removed: $rel"
|
|
440
|
+
fi
|
|
441
|
+
done
|
|
442
|
+
printf "%s\n" "Clean completed."
|
|
443
|
+
exit 0
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
if [ -z "$TARGET" ]; then
|
|
447
|
+
printf "%s" "Target repository path: "
|
|
448
|
+
read -r TARGET
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
if [ ! -d "$TARGET" ]; then
|
|
452
|
+
if [ "$CREATE" = "true" ] || prompt_yes_no "Target missing. Create?" "false"; then
|
|
453
|
+
mkdir -p "$TARGET"
|
|
454
|
+
else
|
|
455
|
+
printf "%s\n" "Target directory does not exist."
|
|
456
|
+
exit 1
|
|
457
|
+
fi
|
|
458
|
+
fi
|
|
459
|
+
TARGET_ROOT=$(cd "$TARGET" && pwd)
|
|
460
|
+
|
|
461
|
+
if [ -z "$MODE" ]; then
|
|
462
|
+
MODE=$(choose_mode)
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
if [ "$MODE" = "overwrite" ] || [ "$MODE" = "interactive" ]; then
|
|
466
|
+
if [ "$BACKUP" = "false" ]; then
|
|
467
|
+
if prompt_yes_no "Backup existing files before overwrite?" "false"; then
|
|
468
|
+
BACKUP="true"
|
|
469
|
+
fi
|
|
470
|
+
fi
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
INCLUDE_PATHS=$(get_manifest_paths "install_include_paths")
|
|
474
|
+
if [ -z "$INCLUDE_PATHS" ]; then
|
|
475
|
+
printf "%s\n" "[INSTALL_MANIFEST_ERROR] install_include_paths section is empty in $OWNERSHIP_MANIFEST"
|
|
476
|
+
exit 1
|
|
477
|
+
fi
|
|
478
|
+
|
|
479
|
+
FILES=$(list_source_files "$SOURCE_ROOT" $INCLUDE_PATHS)
|
|
480
|
+
if [ -z "$FILES" ]; then
|
|
481
|
+
printf "%s\n" "No source files found to install."
|
|
482
|
+
exit 1
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
if [ "$BACKUP" = "true" ] && [ "$MODE" = "overwrite" ]; then
|
|
486
|
+
overwrite_candidates=""
|
|
487
|
+
for rel in $FILES; do
|
|
488
|
+
[ -f "$TARGET_ROOT/$rel" ] && overwrite_candidates="$overwrite_candidates $rel"
|
|
489
|
+
done
|
|
490
|
+
if [ -n "$overwrite_candidates" ]; then
|
|
491
|
+
backup_root=$(backup_files "$TARGET_ROOT" $overwrite_candidates)
|
|
492
|
+
printf "%s\n" "Backup created at: $backup_root"
|
|
493
|
+
fi
|
|
494
|
+
fi
|
|
495
|
+
|
|
496
|
+
if [ "$MODE" = "upgrade" ]; then
|
|
497
|
+
OLD_VER=$(read_installed_version "$TARGET_ROOT")
|
|
498
|
+
printf "\n\033[1;36mUpgrading from v%s to v%s\033[0m\n\n" "$OLD_VER" "$APP_VERSION"
|
|
499
|
+
|
|
500
|
+
if [ "$BACKUP" = "true" ]; then
|
|
501
|
+
backup_candidates=""
|
|
502
|
+
for rel in $FILES; do
|
|
503
|
+
cat=$(classify_file "$rel")
|
|
504
|
+
[ "$cat" = "framework" ] && [ -f "$TARGET_ROOT/$rel" ] && backup_candidates="$backup_candidates $rel"
|
|
505
|
+
done
|
|
506
|
+
if [ -n "$backup_candidates" ]; then
|
|
507
|
+
backup_root=$(backup_files "$TARGET_ROOT" $backup_candidates)
|
|
508
|
+
printf "%s\n" "Backup created at: $backup_root"
|
|
509
|
+
fi
|
|
510
|
+
fi
|
|
511
|
+
|
|
512
|
+
count_added=0; list_added=""
|
|
513
|
+
count_updated=0; list_updated=""
|
|
514
|
+
count_unchanged=0
|
|
515
|
+
count_preserved=0
|
|
516
|
+
count_review=0; list_review=""
|
|
517
|
+
scratchpad_example_rel=".cursor/scratchpad.local.example.md"
|
|
518
|
+
scratchpad_example_status="not-seen"
|
|
519
|
+
|
|
520
|
+
for rel in $FILES; do
|
|
521
|
+
src="$SOURCE_ROOT/$rel"
|
|
522
|
+
dst="$TARGET_ROOT/$rel"
|
|
523
|
+
cat=$(classify_file "$rel")
|
|
524
|
+
|
|
525
|
+
if [ ! -f "$dst" ]; then
|
|
526
|
+
ensure_parent "$dst"
|
|
527
|
+
cp -p "$src" "$dst"
|
|
528
|
+
count_added=$((count_added + 1))
|
|
529
|
+
list_added="$list_added $rel"
|
|
530
|
+
[ "$rel" = "$scratchpad_example_rel" ] && scratchpad_example_status="added"
|
|
531
|
+
continue
|
|
532
|
+
fi
|
|
533
|
+
|
|
534
|
+
if [ "$cat" = "framework" ]; then
|
|
535
|
+
if cmp -s "$src" "$dst"; then
|
|
536
|
+
count_unchanged=$((count_unchanged + 1))
|
|
537
|
+
[ "$rel" = "$scratchpad_example_rel" ] && scratchpad_example_status="unchanged"
|
|
538
|
+
else
|
|
539
|
+
ensure_parent "$dst"
|
|
540
|
+
cp -p "$src" "$dst"
|
|
541
|
+
count_updated=$((count_updated + 1))
|
|
542
|
+
list_updated="$list_updated $rel"
|
|
543
|
+
[ "$rel" = "$scratchpad_example_rel" ] && scratchpad_example_status="updated"
|
|
544
|
+
fi
|
|
545
|
+
continue
|
|
546
|
+
fi
|
|
547
|
+
|
|
548
|
+
if [ "$cat" = "user-data" ]; then
|
|
549
|
+
count_preserved=$((count_preserved + 1))
|
|
550
|
+
continue
|
|
551
|
+
fi
|
|
552
|
+
|
|
553
|
+
if [ "$cat" = "mixed" ]; then
|
|
554
|
+
count_preserved=$((count_preserved + 1))
|
|
555
|
+
if ! cmp -s "$src" "$dst"; then
|
|
556
|
+
count_review=$((count_review + 1))
|
|
557
|
+
list_review="$list_review $rel"
|
|
558
|
+
fi
|
|
559
|
+
continue
|
|
560
|
+
fi
|
|
561
|
+
done
|
|
562
|
+
|
|
563
|
+
scratchpad_postinstall "$TARGET_ROOT" "upgrade"
|
|
564
|
+
validate_install_completeness "$TARGET_ROOT"
|
|
565
|
+
|
|
566
|
+
write_installed_version "$TARGET_ROOT" "$APP_VERSION"
|
|
567
|
+
sync_root_readme_to_its_magic "$TARGET_ROOT" || true
|
|
568
|
+
bootstrap_runbook_commands "$TARGET_ROOT"
|
|
569
|
+
[ -n "$BOOTSTRAP_NOTES" ] && printf "%s" "$BOOTSTRAP_NOTES"
|
|
570
|
+
[ "$BOOTSTRAP_OK" = "true" ] || exit 1
|
|
571
|
+
|
|
572
|
+
show_banner
|
|
573
|
+
printf "\033[1;32mUpgrade complete: v%s -> v%s\033[0m\n\n" "$OLD_VER" "$APP_VERSION"
|
|
574
|
+
if [ "$count_added" -gt 0 ]; then
|
|
575
|
+
printf " \033[1;32mAdded (new): %s files\033[0m\n" "$count_added"
|
|
576
|
+
for f in $list_added; do printf " %s\n" "$f"; done
|
|
577
|
+
fi
|
|
578
|
+
if [ "$count_updated" -gt 0 ]; then
|
|
579
|
+
printf " \033[1;33mUpdated (framework): %s files\033[0m\n" "$count_updated"
|
|
580
|
+
for f in $list_updated; do printf " %s\n" "$f"; done
|
|
581
|
+
fi
|
|
582
|
+
printf " Unchanged: %s files\n" "$count_unchanged"
|
|
583
|
+
printf " Preserved (user): %s files\n" "$count_preserved"
|
|
584
|
+
[ "$scratchpad_example_status" = "not-seen" ] && scratchpad_example_status="not-in-manifest"
|
|
585
|
+
printf " Scratchpad example: %s (.cursor/scratchpad.local.example.md)\n" "$scratchpad_example_status"
|
|
586
|
+
printf " Scratchpad layers: post-install refreshed example-first, then baseline (see [SCRATCHPAD_LAYER] lines).\n"
|
|
587
|
+
[ -f "$TARGET_ROOT/.cursor/scratchpad.local.md" ] && printf " User local file: preserved (.cursor/scratchpad.local.md)\n"
|
|
588
|
+
if [ "$count_review" -gt 0 ]; then
|
|
589
|
+
printf "\n \033[1;35mReview recommended: %s files\033[0m\n" "$count_review"
|
|
590
|
+
for f in $list_review; do printf " %s\n" "$f"; done
|
|
591
|
+
printf " Check .cursor/scratchpad.local.example.md for new flags.\n"
|
|
592
|
+
fi
|
|
593
|
+
printf "\nRepository: %s\n\n" "$REPO_URL"
|
|
594
|
+
exit 0
|
|
595
|
+
fi
|
|
596
|
+
|
|
597
|
+
for rel in $FILES; do
|
|
598
|
+
src="$SOURCE_ROOT/$rel"
|
|
599
|
+
dst="$TARGET_ROOT/$rel"
|
|
600
|
+
if [ "$MODE" = "missing" ]; then
|
|
601
|
+
[ -f "$dst" ] && continue
|
|
602
|
+
ensure_parent "$dst"
|
|
603
|
+
cp -p "$src" "$dst"
|
|
604
|
+
continue
|
|
605
|
+
fi
|
|
606
|
+
if [ "$MODE" = "overwrite" ]; then
|
|
607
|
+
ensure_parent "$dst"
|
|
608
|
+
cp -p "$src" "$dst"
|
|
609
|
+
continue
|
|
610
|
+
fi
|
|
611
|
+
if [ "$MODE" = "interactive" ]; then
|
|
612
|
+
if [ ! -f "$dst" ]; then
|
|
613
|
+
ensure_parent "$dst"
|
|
614
|
+
cp -p "$src" "$dst"
|
|
615
|
+
continue
|
|
616
|
+
fi
|
|
617
|
+
printf "%s" "File exists: $rel | [o]verwrite [s]kip [q]uit: "
|
|
618
|
+
read -r answer
|
|
619
|
+
answer=$(printf "%s" "$answer" | tr 'A-Z' 'a-z')
|
|
620
|
+
if [ "$answer" = "q" ]; then
|
|
621
|
+
printf "%s\n" "Aborted."
|
|
622
|
+
exit 1
|
|
623
|
+
fi
|
|
624
|
+
if [ "$answer" = "o" ]; then
|
|
625
|
+
if [ "$BACKUP" = "true" ]; then
|
|
626
|
+
backup_root=$(backup_files "$TARGET_ROOT" "$rel")
|
|
627
|
+
printf "%s\n" "Backed up: $rel -> $backup_root"
|
|
628
|
+
fi
|
|
629
|
+
ensure_parent "$dst"
|
|
630
|
+
cp -p "$src" "$dst"
|
|
631
|
+
fi
|
|
632
|
+
fi
|
|
633
|
+
done
|
|
634
|
+
|
|
635
|
+
scratchpad_postinstall "$TARGET_ROOT" "$MODE"
|
|
636
|
+
validate_install_completeness "$TARGET_ROOT"
|
|
637
|
+
|
|
638
|
+
write_installed_version "$TARGET_ROOT" "$APP_VERSION"
|
|
639
|
+
sync_root_readme_to_its_magic "$TARGET_ROOT" || true
|
|
640
|
+
bootstrap_runbook_commands "$TARGET_ROOT"
|
|
641
|
+
[ -n "$BOOTSTRAP_NOTES" ] && printf "%s" "$BOOTSTRAP_NOTES"
|
|
642
|
+
[ "$BOOTSTRAP_OK" = "true" ] || exit 1
|
|
643
|
+
|
|
644
|
+
show_banner
|
|
645
|
+
printf "its-magic v%s\n" "$APP_VERSION"
|
|
646
|
+
printf "Repository: %s\n\n" "$REPO_URL"
|
|
647
|
+
printf "\033[1;32m Installation complete!\033[0m\n\n"
|
|
648
|
+
exit 0
|
|
649
|
+
|