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