kratos-framework 1.27.55

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.
@@ -0,0 +1,920 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # ─────────────────────────────────────────────────────────────────────────────
5
+ # Kratos Framework Installer
6
+ # Installs, updates, validates, and reports on KRATOS installations.
7
+ # ─────────────────────────────────────────────────────────────────────────────
8
+
9
+ readonly VERSION="1.27.4"
10
+ readonly DEFAULT_GITHUB_REPO="https://github.com/jlouage/Kratos-framework.git"
11
+ readonly GITHUB_REPO="${KRATOS_REPO_URL:-$DEFAULT_GITHUB_REPO}"
12
+ readonly MANIFEST_REL="_kratos/_config/manifest.yaml"
13
+
14
+ # Temp dir tracking for cleanup
15
+ TEMP_CLONE_DIR=""
16
+
17
+ # ─── Colors & Formatting ────────────────────────────────────────────────────
18
+
19
+ if [[ -t 1 ]]; then
20
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'
21
+ BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'
22
+ RESET='\033[0m'
23
+ else
24
+ RED=''; GREEN=''; YELLOW=''; BLUE=''; CYAN=''; BOLD=''; DIM=''; RESET=''
25
+ fi
26
+
27
+ info() { printf "${BLUE}ℹ${RESET} %s\n" "$*"; }
28
+ success() { printf "${GREEN}✔${RESET} %s\n" "$*"; }
29
+ warn() { printf "${YELLOW}⚠${RESET} %s\n" "$*"; }
30
+ error() { printf "${RED}✖${RESET} %s\n" "$*" >&2; }
31
+ step() { printf "${CYAN}→${RESET} %s\n" "$*"; }
32
+ detail() { printf "${DIM} %s${RESET}\n" "$*"; }
33
+
34
+ # ─── Globals (set by argument parsing) ──────────────────────────────────────
35
+
36
+ CMD=""
37
+ SOURCE_FLAG=""
38
+ TARGET=""
39
+ OPT_YES=false
40
+ OPT_MINIMAL=false
41
+ OPT_DRY_RUN=false
42
+ OPT_VERBOSE=false
43
+
44
+ # ─── Utility Functions ──────────────────────────────────────────────────────
45
+
46
+ cleanup() {
47
+ if [[ -n "$TEMP_CLONE_DIR" && -d "$TEMP_CLONE_DIR" ]]; then
48
+ rm -rf "$TEMP_CLONE_DIR"
49
+ [[ "$OPT_VERBOSE" == true ]] && detail "Cleaned up temp dir: $TEMP_CLONE_DIR" || true
50
+ fi
51
+ }
52
+ trap cleanup EXIT
53
+
54
+ clone_from_github() {
55
+ if ! command -v git &>/dev/null; then
56
+ error "git is required to clone from GitHub but was not found."
57
+ error "Install git or provide a local source with --source."
58
+ exit 1
59
+ fi
60
+ TEMP_CLONE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/kratos-framework-XXXXXX")"
61
+ info "Cloning KRATOS from GitHub..." >&2
62
+ if git clone --depth 1 "$GITHUB_REPO" "$TEMP_CLONE_DIR" 2>/dev/null; then
63
+ success "Cloned to temporary directory" >&2
64
+ else
65
+ error "Failed to clone from $GITHUB_REPO"
66
+ exit 1
67
+ fi
68
+ echo "$TEMP_CLONE_DIR"
69
+ }
70
+
71
+ resolve_source() {
72
+ local resolved=""
73
+
74
+ # 1. --source flag
75
+ if [[ -n "$SOURCE_FLAG" ]]; then
76
+ resolved="$SOURCE_FLAG"
77
+ [[ "$OPT_VERBOSE" == true ]] && detail "Source from --source flag: $resolved" >&2
78
+ # 2. $KRATOS_SOURCE env var
79
+ elif [[ -n "${KRATOS_SOURCE:-}" ]]; then
80
+ resolved="$KRATOS_SOURCE"
81
+ [[ "$OPT_VERBOSE" == true ]] && detail "Source from \$KRATOS_SOURCE: $resolved" >&2
82
+ # 3. Self-detect: script's own directory
83
+ elif [[ -d "$(dirname "$(realpath "$0")")/_kratos" ]]; then
84
+ resolved="$(dirname "$(realpath "$0")")"
85
+ [[ "$OPT_VERBOSE" == true ]] && detail "Source from script location: $resolved" >&2
86
+ # 4. GitHub clone
87
+ else
88
+ resolved="$(clone_from_github)"
89
+ fi
90
+
91
+ echo "$resolved"
92
+ }
93
+
94
+ validate_source() {
95
+ local src="$1"
96
+ if [[ ! -f "$src/$MANIFEST_REL" ]]; then
97
+ error "Invalid KRATOS source: $src"
98
+ error "Expected $MANIFEST_REL but it was not found."
99
+ exit 1
100
+ fi
101
+ }
102
+
103
+ copy_if_missing() {
104
+ local src="$1" dst="$2"
105
+ if [[ -e "$dst" ]]; then
106
+ [[ "$OPT_VERBOSE" == true ]] && detail "Skipped (exists): $dst"
107
+ return 0
108
+ fi
109
+ if [[ "$OPT_DRY_RUN" == true ]]; then
110
+ detail "[dry-run] Would copy: $dst"
111
+ return 0
112
+ fi
113
+ mkdir -p "$(dirname "$dst")"
114
+ cp "$src" "$dst"
115
+ [[ "$OPT_VERBOSE" == true ]] && detail "Copied: $dst" || true
116
+ }
117
+
118
+ copy_with_backup() {
119
+ local src="$1" dst="$2" backup_dir="$3"
120
+ if [[ ! -e "$dst" ]]; then
121
+ if [[ "$OPT_DRY_RUN" == true ]]; then
122
+ detail "[dry-run] Would copy: $dst"
123
+ return 0
124
+ fi
125
+ mkdir -p "$(dirname "$dst")"
126
+ cp "$src" "$dst"
127
+ [[ "$OPT_VERBOSE" == true ]] && detail "Copied (new): $dst" || true
128
+ return 0
129
+ fi
130
+ # Compare files — skip if identical
131
+ if cmp -s "$src" "$dst"; then
132
+ [[ "$OPT_VERBOSE" == true ]] && detail "Unchanged: $dst" || true
133
+ return 0
134
+ fi
135
+ if [[ "$OPT_DRY_RUN" == true ]]; then
136
+ detail "[dry-run] Would update (with backup): $dst"
137
+ return 0
138
+ fi
139
+ # Back up the existing file
140
+ local rel_path="${dst#$TARGET/}"
141
+ local backup_path="$backup_dir/$rel_path"
142
+ mkdir -p "$(dirname "$backup_path")"
143
+ cp "$dst" "$backup_path"
144
+ cp "$src" "$dst"
145
+ [[ "$OPT_VERBOSE" == true ]] && detail "Updated (backed up): $dst" || true
146
+ }
147
+
148
+ append_if_missing() {
149
+ local file="$1" marker="$2" content="$3"
150
+ if [[ -f "$file" ]] && grep -qF "$marker" "$file"; then
151
+ [[ "$OPT_VERBOSE" == true ]] && detail "Already in $(basename "$file"): $marker"
152
+ return 0
153
+ fi
154
+ if [[ "$OPT_DRY_RUN" == true ]]; then
155
+ detail "[dry-run] Would append to $(basename "$file"): $marker"
156
+ return 0
157
+ fi
158
+ # Add a newline before appending if file exists and doesn't end with newline
159
+ if [[ -f "$file" ]] && [[ -s "$file" ]] && [[ "$(tail -c 1 "$file")" != "" ]]; then
160
+ printf '\n' >> "$file"
161
+ fi
162
+ printf '%s\n' "$content" >> "$file"
163
+ }
164
+
165
+ prompt_value() {
166
+ local prompt_text="$1" default="$2"
167
+ if [[ "$OPT_YES" == true ]]; then
168
+ echo "$default"
169
+ return 0
170
+ fi
171
+ local value
172
+ printf "${BOLD}%s${RESET} [%s]: " "$prompt_text" "$default" >&2
173
+ read -r value
174
+ echo "${value:-$default}"
175
+ }
176
+
177
+ extract_yaml_value() {
178
+ local file="$1" key="$2"
179
+ grep "^${key}:" "$file" 2>/dev/null | sed "s/^${key}:[[:space:]]*//" | sed 's/^"//;s/"$//' || echo ""
180
+ }
181
+
182
+ count_files() {
183
+ local dir="$1" pattern="${2:-*}"
184
+ find "$dir" -name "$pattern" -type f 2>/dev/null | wc -l | tr -d ' '
185
+ }
186
+
187
+ set_global_value() {
188
+ local file="$1" key="$2" value="$3"
189
+ if [[ ! -f "$file" ]]; then
190
+ return 0
191
+ fi
192
+ if grep -q "^${key}:" "$file"; then
193
+ if [[ "$(uname)" == "Darwin" ]]; then
194
+ sed -i '' "s/^${key}:.*/${key}: \"${value}\"/" "$file"
195
+ else
196
+ sed -i "s/^${key}:.*/${key}: \"${value}\"/" "$file"
197
+ fi
198
+ else
199
+ printf '%s: "%s"\n' "$key" "$value" >> "$file"
200
+ fi
201
+ }
202
+
203
+ # ─── cmd_init ───────────────────────────────────────────────────────────────
204
+
205
+ cmd_init() {
206
+ local source
207
+ source="$(resolve_source)"
208
+ validate_source "$source"
209
+
210
+ local src_version
211
+ src_version="$(extract_yaml_value "$source/_kratos/_config/global.yaml" "framework_version")"
212
+
213
+ printf "\n${BOLD}Kratos Framework Installer v%s${RESET}\n" "$VERSION"
214
+ printf " Source: %s\n" "$source"
215
+ printf " Target: %s\n" "$TARGET"
216
+ printf " Version: %s\n\n" "${src_version:-unknown}"
217
+
218
+ if [[ "$OPT_DRY_RUN" == true ]]; then
219
+ warn "Dry-run mode — no files will be written"
220
+ echo ""
221
+ fi
222
+
223
+ if [[ -d "$TARGET/_kratos" ]]; then
224
+ warn "Target already contains _kratos/ — use 'update' to refresh framework files."
225
+ if [[ "$OPT_YES" != true ]]; then
226
+ printf "Continue with init anyway? [y/N]: "
227
+ local confirm; read -r confirm
228
+ [[ "$confirm" =~ ^[Yy] ]] || { info "Aborted."; exit 0; }
229
+ fi
230
+ fi
231
+
232
+ # Step 1: Create target docs directories
233
+ step "Creating documentation directories..."
234
+ for dir in planning-artifacts implementation-artifacts test-artifacts creative-artifacts; do
235
+ if [[ "$OPT_DRY_RUN" == true ]]; then
236
+ detail "[dry-run] Would create: docs/$dir/"
237
+ else
238
+ mkdir -p "$TARGET/docs/$dir"
239
+ fi
240
+ done
241
+
242
+ # Step 2: Copy framework files (full or minimal profile)
243
+ step "Copying framework files..."
244
+ if [[ "$OPT_DRY_RUN" == true ]]; then
245
+ if [[ "$OPT_MINIMAL" == true ]]; then
246
+ detail "[dry-run] Would copy minimal KRATOS profile"
247
+ else
248
+ detail "[dry-run] Would copy _kratos/ (excluding checkpoints and .resolved/*.yaml)"
249
+ fi
250
+ else
251
+ mkdir -p "$TARGET/_kratos"
252
+ if [[ "$OPT_MINIMAL" == true ]]; then
253
+ local minimal_paths=(
254
+ "_config"
255
+ "_memory/config.yaml"
256
+ "core"
257
+ "dev"
258
+ "lifecycle/config.yaml"
259
+ "lifecycle/module-help.csv"
260
+ "lifecycle/workflows/4-implementation/dev-story"
261
+ "lifecycle/workflows/4-implementation/code-review"
262
+ "lifecycle/workflows/quick-flow"
263
+ )
264
+ for entry in "${minimal_paths[@]}"; do
265
+ local src_path="$source/_kratos/$entry"
266
+ local dst_path="$TARGET/_kratos/$entry"
267
+ [[ -e "$src_path" ]] || continue
268
+ if [[ -d "$src_path" ]]; then
269
+ mkdir -p "$dst_path"
270
+ rsync -a \
271
+ --exclude='_memory/checkpoints/*.yaml' \
272
+ --exclude='_memory/checkpoints/completed/*.yaml' \
273
+ --exclude='.resolved/*.yaml' \
274
+ --exclude='_memory/*-sidecar/*.md' \
275
+ --exclude='_memory/*-sidecar/*.yaml' \
276
+ "$src_path/" "$dst_path/"
277
+ else
278
+ mkdir -p "$(dirname "$dst_path")"
279
+ cp "$src_path" "$dst_path"
280
+ fi
281
+ done
282
+ else
283
+ rsync -a \
284
+ --exclude='_memory/checkpoints/*.yaml' \
285
+ --exclude='_memory/checkpoints/completed/*.yaml' \
286
+ --exclude='.resolved/*.yaml' \
287
+ --exclude='_memory/*-sidecar/*.md' \
288
+ --exclude='_memory/*-sidecar/*.yaml' \
289
+ "$source/_kratos/" "$TARGET/_kratos/"
290
+ fi
291
+ fi
292
+
293
+ # Step 3: Create memory directories with .gitkeep
294
+ step "Creating memory directories..."
295
+ local sidecar_dirs=("checkpoints" "checkpoints/completed")
296
+ if [[ "$OPT_MINIMAL" != true ]]; then
297
+ sidecar_dirs+=(
298
+ "architect-sidecar"
299
+ "devops-sidecar"
300
+ "orchestrator-sidecar"
301
+ "pm-sidecar"
302
+ "security-sidecar"
303
+ "sm-sidecar"
304
+ "storyteller-sidecar"
305
+ "tech-writer-sidecar"
306
+ "test-architect-sidecar"
307
+ )
308
+ fi
309
+ for dir in "${sidecar_dirs[@]}"; do
310
+ if [[ "$OPT_DRY_RUN" == true ]]; then
311
+ detail "[dry-run] Would create: _kratos/_memory/$dir/"
312
+ else
313
+ mkdir -p "$TARGET/_kratos/_memory/$dir"
314
+ touch "$TARGET/_kratos/_memory/$dir/.gitkeep"
315
+ fi
316
+ done
317
+
318
+ # Step 4: Create .resolved directories with .gitkeep
319
+ step "Creating .resolved directories..."
320
+ local resolved_dirs=("core" "lifecycle")
321
+ if [[ "$OPT_MINIMAL" != true ]]; then
322
+ resolved_dirs+=("creative" "testing")
323
+ fi
324
+ for mod in "${resolved_dirs[@]}"; do
325
+ if [[ "$OPT_DRY_RUN" == true ]]; then
326
+ detail "[dry-run] Would create: _kratos/$mod/.resolved/"
327
+ else
328
+ mkdir -p "$TARGET/_kratos/$mod/.resolved"
329
+ touch "$TARGET/_kratos/$mod/.resolved/.gitkeep"
330
+ fi
331
+ done
332
+
333
+ # Step 5: Customize global.yaml
334
+ step "Configuring global.yaml..."
335
+ local project_name user_name
336
+ local dir_name
337
+ dir_name="$(basename "$TARGET")"
338
+ project_name="$(prompt_value "Project name" "$dir_name")"
339
+ user_name="$(prompt_value "User name" "$(whoami)")"
340
+
341
+ if [[ "$OPT_DRY_RUN" == true ]]; then
342
+ detail "[dry-run] Would set project_name=$project_name, user_name=$user_name"
343
+ else
344
+ local global_file="$TARGET/_kratos/_config/global.yaml"
345
+ if [[ -f "$global_file" ]]; then
346
+ set_global_value "$global_file" "project_name" "$project_name"
347
+ set_global_value "$global_file" "user_name" "$user_name"
348
+ set_global_value "$global_file" "install_profile" "$([[ "$OPT_MINIMAL" == true ]] && echo minimal || echo full)"
349
+ fi
350
+ fi
351
+
352
+ # Step 6: Copy CLAUDE.md (skip if exists)
353
+ step "Setting up CLAUDE.md..."
354
+ if [[ -f "$source/CLAUDE.md" ]]; then
355
+ copy_if_missing "$source/CLAUDE.md" "$TARGET/CLAUDE.md"
356
+ fi
357
+
358
+ # Step 7: Copy slash commands to .claude/commands/
359
+ step "Installing slash commands..."
360
+ if [[ -d "$source/.claude/commands" ]]; then
361
+ if [[ "$OPT_DRY_RUN" == true ]]; then
362
+ local cmd_count
363
+ if [[ "$OPT_MINIMAL" == true ]]; then
364
+ cmd_count="10"
365
+ else
366
+ cmd_count="$(find "$source/.claude/commands" -name 'kratos*.md' -type f | wc -l | tr -d ' ')"
367
+ fi
368
+ detail "[dry-run] Would copy $cmd_count slash commands to .claude/commands/"
369
+ else
370
+ mkdir -p "$TARGET/.claude/commands"
371
+ if [[ "$OPT_MINIMAL" == true ]]; then
372
+ local minimal_commands=(
373
+ "kratos-help.md"
374
+ "kratos-build-configs.md"
375
+ "kratos-resume.md"
376
+ "kratos-quick-spec.md"
377
+ "kratos-quick-dev.md"
378
+ "kratos-dev-story.md"
379
+ "kratos-code-review.md"
380
+ "kratos-agent-senior-frontend.md"
381
+ "kratos-agent-senior-backend.md"
382
+ "kratos-agent-senior-fullstack.md"
383
+ )
384
+ for basename_cmd in "${minimal_commands[@]}"; do
385
+ local cmd_file="$source/.claude/commands/$basename_cmd"
386
+ [[ -f "$cmd_file" ]] || continue
387
+ copy_if_missing "$cmd_file" "$TARGET/.claude/commands/$basename_cmd"
388
+ done
389
+ else
390
+ for cmd_file in "$source/.claude/commands"/kratos*.md; do
391
+ [[ -f "$cmd_file" ]] || continue
392
+ local basename_cmd
393
+ basename_cmd="$(basename "$cmd_file")"
394
+ copy_if_missing "$cmd_file" "$TARGET/.claude/commands/$basename_cmd"
395
+ done
396
+ fi
397
+ fi
398
+ fi
399
+
400
+ # Step 8: Append KRATOS entries to .gitignore
401
+ step "Updating .gitignore..."
402
+ local gitignore_block
403
+ gitignore_block="$(cat <<'GITIGNORE'
404
+
405
+ # Kratos Framework — runtime artifacts
406
+ _kratos/_memory/checkpoints/*.yaml
407
+ _kratos/_memory/*-sidecar/*.md
408
+ _kratos/_memory/*-sidecar/*.yaml
409
+ _kratos/**/.resolved/*.yaml
410
+ !_kratos/**/.resolved/.gitkeep
411
+ GITIGNORE
412
+ )"
413
+ append_if_missing "$TARGET/.gitignore" "# Kratos Framework — runtime artifacts" "$gitignore_block"
414
+
415
+ # Summary
416
+ echo ""
417
+ success "Kratos Framework installed successfully!"
418
+ echo ""
419
+ printf " ${BOLD}Project:${RESET} %s\n" "$project_name"
420
+ printf " ${BOLD}User:${RESET} %s\n" "$user_name"
421
+ printf " ${BOLD}Version:${RESET} %s\n" "${src_version:-1.0.0}"
422
+ printf " ${BOLD}Target:${RESET} %s\n" "$TARGET"
423
+ echo ""
424
+ info "Next steps:"
425
+ detail "1. cd $TARGET"
426
+ detail "2. Run /kratos-build-configs in the Claude Code terminal to generate resolved configs"
427
+ if [[ "$OPT_MINIMAL" == true ]]; then
428
+ detail "3. Run /kratos-quick-spec or /kratos-agent-senior-fullstack to start with the lightweight profile"
429
+ detail "4. Minimal profile installed: add more commands or workflows later by running a full update from your fork"
430
+ else
431
+ detail "3. Run /kratos in the Claude Code terminal to start the orchestrator"
432
+ fi
433
+ echo ""
434
+ }
435
+
436
+ # ─── cmd_update ─────────────────────────────────────────────────────────────
437
+
438
+ cmd_update() {
439
+ local source
440
+ source="$(resolve_source)"
441
+ validate_source "$source"
442
+
443
+ if [[ ! -d "$TARGET/_kratos" ]]; then
444
+ error "No KRATOS installation found at $TARGET"
445
+ error "Run 'init' first to install KRATOS."
446
+ exit 1
447
+ fi
448
+
449
+ local src_version
450
+ src_version="$(extract_yaml_value "$source/_kratos/_config/global.yaml" "framework_version")"
451
+ local cur_version
452
+ cur_version="$(extract_yaml_value "$TARGET/_kratos/_config/global.yaml" "framework_version")"
453
+
454
+ printf "\n${BOLD}Kratos Framework Updater v%s${RESET}\n" "$VERSION"
455
+ printf " Source: %s (v%s)\n" "$source" "${src_version:-unknown}"
456
+ printf " Target: %s (v%s)\n\n" "$TARGET" "${cur_version:-unknown}"
457
+
458
+ if [[ "$OPT_DRY_RUN" == true ]]; then
459
+ warn "Dry-run mode — no files will be written"
460
+ echo ""
461
+ fi
462
+
463
+ if [[ "$OPT_YES" != true ]]; then
464
+ printf "Proceed with update? [y/N]: "
465
+ local confirm; read -r confirm
466
+ [[ "$confirm" =~ ^[Yy] ]] || { info "Aborted."; exit 0; }
467
+ fi
468
+
469
+ local timestamp
470
+ timestamp="$(date +%Y%m%d-%H%M%S)"
471
+ local backup_dir="$TARGET/_kratos/_backups/$timestamp"
472
+
473
+ # Update framework structure — these directories get fully refreshed
474
+ # NEVER touch: global.yaml, _memory/, .resolved/, CLAUDE.md
475
+ local update_targets=(
476
+ "core/engine"
477
+ "core/agents"
478
+ "core/tasks"
479
+ "core/workflows"
480
+ "lifecycle/agents"
481
+ "lifecycle/workflows"
482
+ "lifecycle/templates"
483
+ "lifecycle/checklists"
484
+ "lifecycle/teams"
485
+ "dev/agents"
486
+ "dev/skills"
487
+ "dev/knowledge"
488
+ "creative/agents"
489
+ "creative/workflows"
490
+ "creative/teams"
491
+ "creative/data"
492
+ "testing/agents"
493
+ "testing/workflows"
494
+ "testing/knowledge"
495
+ "_config/manifest.yaml"
496
+ "_config/agent-manifest.csv"
497
+ "_config/workflow-manifest.csv"
498
+ "_config/kratos-help.csv"
499
+ "_config/skill-manifest.csv"
500
+ "_config/task-manifest.csv"
501
+ )
502
+
503
+ step "Updating framework files..."
504
+ local updated=0 skipped=0 changed=0
505
+
506
+ for entry in "${update_targets[@]}"; do
507
+ local src_path="$source/_kratos/$entry"
508
+ local dst_path="$TARGET/_kratos/$entry"
509
+
510
+ if [[ ! -e "$src_path" ]]; then
511
+ [[ "$OPT_VERBOSE" == true ]] && detail "Source missing, skipped: $entry"
512
+ continue
513
+ fi
514
+
515
+ # Show progress for each update target
516
+ local entry_label="${entry}"
517
+ [[ "$OPT_VERBOSE" != true ]] && detail "Processing: $entry_label"
518
+
519
+ if [[ -f "$src_path" ]]; then
520
+ # Single file
521
+ copy_with_backup "$src_path" "$dst_path" "$backup_dir"
522
+ updated=$((updated + 1))
523
+ elif [[ -d "$src_path" ]]; then
524
+ # Directory — update each file
525
+ while IFS= read -r -d '' file; do
526
+ local rel="${file#$src_path/}"
527
+ copy_with_backup "$file" "$dst_path/$rel" "$backup_dir"
528
+ updated=$((updated + 1))
529
+ done < <(find "$src_path" -type f -print0) || true
530
+ fi
531
+ done
532
+
533
+ detail "Processed $updated file(s) across ${#update_targets[@]} targets"
534
+
535
+ # Update slash commands (add new ones, update existing with backup)
536
+ step "Updating slash commands..."
537
+ if [[ -d "$source/.claude/commands" ]]; then
538
+ mkdir -p "$TARGET/.claude/commands"
539
+ for cmd_file in "$source/.claude/commands"/kratos*.md; do
540
+ [[ -f "$cmd_file" ]] || continue
541
+ local basename_cmd
542
+ basename_cmd="$(basename "$cmd_file")"
543
+ copy_with_backup "$cmd_file" "$TARGET/.claude/commands/$basename_cmd" "$backup_dir"
544
+ done
545
+ fi
546
+
547
+ # Update version in global.yaml and CLAUDE.md
548
+ if [[ -n "$src_version" && "$src_version" != "$cur_version" ]]; then
549
+ step "Updating framework version: $cur_version → $src_version"
550
+ if [[ "$OPT_DRY_RUN" != true ]]; then
551
+ local global_file="$TARGET/_kratos/_config/global.yaml"
552
+ set_global_value "$global_file" "framework_version" "$src_version"
553
+ # Update version in CLAUDE.md heading
554
+ local claude_file="$TARGET/CLAUDE.md"
555
+ if [[ -f "$claude_file" ]]; then
556
+ if [[ "$(uname)" == "Darwin" ]]; then
557
+ sed -i '' "s/^# Kratos Framework v.*/# Kratos Framework v$src_version/" "$claude_file"
558
+ else
559
+ sed -i "s/^# Kratos Framework v.*/# Kratos Framework v$src_version/" "$claude_file"
560
+ fi
561
+ [[ "$OPT_VERBOSE" == true ]] && detail "Updated CLAUDE.md version heading" || true
562
+ fi
563
+ fi
564
+ fi
565
+
566
+ # Summary
567
+ echo ""
568
+ if [[ -d "$backup_dir" ]]; then
569
+ local backup_count
570
+ backup_count="$(find "$backup_dir" -type f | wc -l | tr -d ' ')"
571
+ success "Update complete! $backup_count file(s) backed up to:"
572
+ detail "$backup_dir"
573
+ else
574
+ success "Update complete! All files were already up to date."
575
+ fi
576
+ echo ""
577
+ info "Run /kratos-build-configs in the Claude Code terminal to regenerate resolved configs."
578
+ echo ""
579
+ }
580
+
581
+ # ─── cmd_validate ───────────────────────────────────────────────────────────
582
+
583
+ cmd_validate() {
584
+ if [[ ! -d "$TARGET/_kratos" ]]; then
585
+ error "No KRATOS installation found at $TARGET"
586
+ exit 1
587
+ fi
588
+
589
+ printf "\n${BOLD}Kratos Framework Validation${RESET}\n"
590
+ printf " Target: %s\n\n" "$TARGET"
591
+
592
+ local pass=0 fail=0
593
+
594
+ check() {
595
+ local label="$1" condition="$2"
596
+ if eval "$condition"; then
597
+ printf " ${GREEN}✔${RESET} %s\n" "$label"
598
+ pass=$((pass + 1))
599
+ else
600
+ printf " ${RED}✖${RESET} %s\n" "$label"
601
+ fail=$((fail + 1))
602
+ fi
603
+ }
604
+
605
+ # Manifest
606
+ check "manifest.yaml exists" "[[ -f '$TARGET/$MANIFEST_REL' ]]"
607
+
608
+ # Global config fields
609
+ local global="$TARGET/_kratos/_config/global.yaml"
610
+ check "global.yaml exists" "[[ -f '$global' ]]"
611
+ if [[ -f "$global" ]]; then
612
+ check "global.yaml has project_name" "grep -q '^project_name:' '$global'"
613
+ check "global.yaml has user_name" "grep -q '^user_name:' '$global'"
614
+ check "global.yaml has framework_version" "grep -q '^framework_version:' '$global'"
615
+ fi
616
+
617
+ local install_profile
618
+ install_profile="$(extract_yaml_value "$global" "install_profile")"
619
+ [[ -n "$install_profile" ]] || install_profile="full"
620
+
621
+ # Module directories
622
+ local modules=(core lifecycle dev)
623
+ if [[ "$install_profile" != "minimal" ]]; then
624
+ modules+=(creative testing)
625
+ fi
626
+ for mod in "${modules[@]}"; do
627
+ check "Module: $mod" "[[ -d '$TARGET/_kratos/$mod' ]]"
628
+ done
629
+
630
+ # .resolved directories
631
+ local resolved_modules=(core lifecycle)
632
+ if [[ "$install_profile" != "minimal" ]]; then
633
+ resolved_modules+=(creative testing)
634
+ fi
635
+ for mod in "${resolved_modules[@]}"; do
636
+ check ".resolved: $mod" "[[ -d '$TARGET/_kratos/$mod/.resolved' ]]"
637
+ done
638
+
639
+ # Sidecar directories
640
+ local sidecar_dirs=("checkpoints" "checkpoints/completed")
641
+ if [[ "$install_profile" != "minimal" ]]; then
642
+ sidecar_dirs+=(
643
+ "architect-sidecar" "devops-sidecar" "orchestrator-sidecar"
644
+ "pm-sidecar" "security-sidecar" "sm-sidecar"
645
+ "storyteller-sidecar" "tech-writer-sidecar" "test-architect-sidecar"
646
+ )
647
+ fi
648
+ for dir in "${sidecar_dirs[@]}"; do
649
+ check "Sidecar: $dir" "[[ -d '$TARGET/_kratos/_memory/$dir' ]]"
650
+ done
651
+
652
+ # CLAUDE.md
653
+ check "CLAUDE.md exists" "[[ -f '$TARGET/CLAUDE.md' ]]"
654
+
655
+ # Slash commands
656
+ check "Slash commands directory" "[[ -d '$TARGET/.claude/commands' ]]"
657
+ if [[ -d "$TARGET/.claude/commands" ]]; then
658
+ local cmd_count
659
+ cmd_count="$(find "$TARGET/.claude/commands" -name 'kratos*.md' -type f 2>/dev/null | wc -l | tr -d ' ')"
660
+ check "Slash commands present (found: $cmd_count)" "[[ $cmd_count -gt 0 ]]"
661
+ fi
662
+
663
+ # Docs directories
664
+ for dir in planning-artifacts implementation-artifacts test-artifacts creative-artifacts; do
665
+ check "Docs: $dir" "[[ -d '$TARGET/docs/$dir' ]]"
666
+ done
667
+
668
+ # Version
669
+ local version
670
+ version="$(extract_yaml_value "$TARGET/_kratos/_config/global.yaml" "framework_version")"
671
+
672
+ echo ""
673
+ if [[ $fail -eq 0 ]]; then
674
+ success "All $pass checks passed (v${version:-unknown})"
675
+ echo ""
676
+ return 0
677
+ else
678
+ error "$fail of $((pass + fail)) checks failed"
679
+ echo ""
680
+ exit 1
681
+ fi
682
+ }
683
+
684
+ # ─── cmd_status ─────────────────────────────────────────────────────────────
685
+
686
+ cmd_status() {
687
+ if [[ ! -d "$TARGET/_kratos" ]]; then
688
+ error "No KRATOS installation found at $TARGET"
689
+ exit 1
690
+ fi
691
+
692
+ local global="$TARGET/_kratos/_config/global.yaml"
693
+ local version project_name user_name
694
+ version="$(extract_yaml_value "$global" "framework_version")"
695
+ project_name="$(extract_yaml_value "$global" "project_name")"
696
+ user_name="$(extract_yaml_value "$global" "user_name")"
697
+
698
+ printf "\n${BOLD}Kratos Framework Status${RESET}\n\n"
699
+ local install_profile
700
+ install_profile="$(extract_yaml_value "$global" "install_profile")"
701
+ [[ -n "$install_profile" ]] || install_profile="full"
702
+
703
+ printf " ${BOLD}Version:${RESET} %s\n" "${version:-unknown}"
704
+ printf " ${BOLD}Project:${RESET} %s\n" "${project_name:-unknown}"
705
+ printf " ${BOLD}User:${RESET} %s\n" "${user_name:-unknown}"
706
+ printf " ${BOLD}Profile:${RESET} %s\n" "$install_profile"
707
+
708
+ # Modules
709
+ local expected_modules=(core lifecycle dev)
710
+ if [[ "$install_profile" != "minimal" ]]; then
711
+ expected_modules+=(creative testing)
712
+ fi
713
+ local installed_modules=()
714
+ for mod in "${expected_modules[@]}"; do
715
+ [[ -d "$TARGET/_kratos/$mod" ]] && installed_modules+=("$mod")
716
+ done
717
+ printf " ${BOLD}Modules:${RESET} %s (%s)\n" "${#installed_modules[@]}" "${installed_modules[*]}"
718
+
719
+ # Slash commands
720
+ local cmd_count=0
721
+ if [[ -d "$TARGET/.claude/commands" ]]; then
722
+ cmd_count="$(find "$TARGET/.claude/commands" -name 'kratos*.md' -type f 2>/dev/null | wc -l | tr -d ' ')"
723
+ fi
724
+ printf " ${BOLD}Commands:${RESET} %s slash commands\n" "$cmd_count"
725
+
726
+ # Sidecar status
727
+ local sidecar_count=0
728
+ local sidecar_populated=0
729
+ for dir in "$TARGET/_kratos/_memory"/*-sidecar; do
730
+ [[ -d "$dir" ]] || continue
731
+ ((sidecar_count++))
732
+ local file_count
733
+ file_count="$(find "$dir" -type f ! -name '.gitkeep' 2>/dev/null | wc -l | tr -d ' ')"
734
+ [[ $file_count -gt 0 ]] && ((sidecar_populated++))
735
+ done
736
+ printf " ${BOLD}Sidecars:${RESET} %s directories (%s with data)\n" "$sidecar_count" "$sidecar_populated"
737
+
738
+ # .resolved population
739
+ local resolved_count=0
740
+ local resolved_populated=0
741
+ local resolved_mods=(core lifecycle)
742
+ if [[ "$install_profile" != "minimal" ]]; then
743
+ resolved_mods+=(creative testing)
744
+ fi
745
+ for mod in "${resolved_mods[@]}"; do
746
+ local rdir="$TARGET/_kratos/$mod/.resolved"
747
+ [[ -d "$rdir" ]] || continue
748
+ ((resolved_count++))
749
+ local yaml_count
750
+ yaml_count="$(find "$rdir" -name '*.yaml' -type f 2>/dev/null | wc -l | tr -d ' ')"
751
+ [[ $yaml_count -gt 0 ]] && ((resolved_populated++))
752
+ done
753
+ printf " ${BOLD}.resolved:${RESET} %s directories (%s populated)\n" "$resolved_count" "$resolved_populated"
754
+
755
+ # Checkpoints
756
+ local checkpoint_count=0
757
+ if [[ -d "$TARGET/_kratos/_memory/checkpoints" ]]; then
758
+ checkpoint_count="$(find "$TARGET/_kratos/_memory/checkpoints" -name '*.yaml' -type f 2>/dev/null | wc -l | tr -d ' ')"
759
+ fi
760
+ printf " ${BOLD}Checkpoints:${RESET} %s\n" "$checkpoint_count"
761
+
762
+ echo ""
763
+ }
764
+
765
+ # ─── Usage & Argument Parsing ───────────────────────────────────────────────
766
+
767
+ usage() {
768
+ cat <<EOF
769
+ ${BOLD}Kratos Framework Installer v${VERSION}${RESET}
770
+
771
+ Usage: kratos-install.sh <command> [options] [target]
772
+
773
+ ${BOLD}Commands:${RESET}
774
+ init Install KRATOS into a project
775
+ update Update framework files (preserves config and memory)
776
+ validate Check installation integrity
777
+ status Show installation info
778
+
779
+ ${BOLD}Options:${RESET}
780
+ --source <path> Local KRATOS source (or clones from GitHub if omitted)
781
+ --yes Skip confirmation prompts
782
+ --minimal Install the lightweight profile (core, quick flow, senior dev agents)
783
+ --dry-run Show what would be done without making changes
784
+ --verbose Show detailed progress
785
+ --help Show this help message
786
+
787
+ ${BOLD}Environment:${RESET}
788
+ KRATOS_REPO_URL Override the Git clone source for your fork or private mirror
789
+
790
+ ${BOLD}Examples:${RESET}
791
+ kratos-install.sh init ~/my-new-project
792
+ kratos-install.sh init --source ~/local-kratos ~/my-project
793
+ kratos-install.sh update ~/my-existing-project
794
+ kratos-install.sh validate .
795
+ kratos-install.sh status .
796
+
797
+ ${BOLD}Source resolution order:${RESET}
798
+ 1. --source flag
799
+ 2. \$KRATOS_SOURCE environment variable
800
+ 3. Script's own directory (if it contains _kratos/)
801
+ 4. GitHub clone ($GITHUB_REPO)
802
+ EOF
803
+ }
804
+
805
+ parse_args() {
806
+ if [[ $# -eq 0 ]]; then
807
+ usage
808
+ exit 0
809
+ fi
810
+
811
+ # First argument is the command
812
+ case "$1" in
813
+ init|update|validate|status)
814
+ CMD="$1"
815
+ shift
816
+ ;;
817
+ --help|-h|help)
818
+ usage
819
+ exit 0
820
+ ;;
821
+ --version|-v)
822
+ echo "kratos-install.sh v${VERSION}"
823
+ exit 0
824
+ ;;
825
+ *)
826
+ error "Unknown command: $1"
827
+ echo ""
828
+ usage
829
+ exit 1
830
+ ;;
831
+ esac
832
+
833
+ # Parse remaining options
834
+ while [[ $# -gt 0 ]]; do
835
+ case "$1" in
836
+ --source)
837
+ if [[ -z "${2:-}" ]]; then
838
+ error "--source requires a path argument"
839
+ exit 1
840
+ fi
841
+ SOURCE_FLAG="$2"
842
+ shift 2
843
+ ;;
844
+ --yes|-y)
845
+ OPT_YES=true
846
+ shift
847
+ ;;
848
+ --minimal)
849
+ OPT_MINIMAL=true
850
+ shift
851
+ ;;
852
+ --dry-run)
853
+ OPT_DRY_RUN=true
854
+ shift
855
+ ;;
856
+ --verbose)
857
+ OPT_VERBOSE=true
858
+ shift
859
+ ;;
860
+ --help|-h)
861
+ usage
862
+ exit 0
863
+ ;;
864
+ -*)
865
+ error "Unknown option: $1"
866
+ exit 1
867
+ ;;
868
+ *)
869
+ if [[ -n "$TARGET" ]]; then
870
+ error "Unexpected argument: $1"
871
+ exit 1
872
+ fi
873
+ TARGET="$1"
874
+ shift
875
+ ;;
876
+ esac
877
+ done
878
+
879
+ # Default target to current directory
880
+ if [[ -z "$TARGET" ]]; then
881
+ TARGET="."
882
+ fi
883
+
884
+ # Resolve target to absolute path
885
+ if [[ "$TARGET" == "." ]]; then
886
+ TARGET="$(pwd)"
887
+ else
888
+ # Create target dir for init if it doesn't exist
889
+ if [[ "$CMD" == "init" && ! -d "$TARGET" ]]; then
890
+ if [[ "$OPT_DRY_RUN" == true ]]; then
891
+ detail "[dry-run] Would create target directory: $TARGET"
892
+ # Resolve to absolute path without cd
893
+ case "$TARGET" in
894
+ /*) ;; # already absolute
895
+ *) TARGET="$(pwd)/$TARGET" ;;
896
+ esac
897
+ else
898
+ mkdir -p "$TARGET"
899
+ TARGET="$(cd "$TARGET" && pwd)"
900
+ fi
901
+ else
902
+ TARGET="$(cd "$TARGET" 2>/dev/null && pwd || echo "$TARGET")"
903
+ fi
904
+ fi
905
+ }
906
+
907
+ # ─── Main ───────────────────────────────────────────────────────────────────
908
+
909
+ main() {
910
+ parse_args "$@"
911
+
912
+ case "$CMD" in
913
+ init) cmd_init ;;
914
+ update) cmd_update ;;
915
+ validate) cmd_validate ;;
916
+ status) cmd_status ;;
917
+ esac
918
+ }
919
+
920
+ main "$@"