gaia-framework 1.54.0 → 1.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -460,7 +460,7 @@ The single source of truth is `_gaia/_config/global.yaml`:
460
460
 
461
461
  ```yaml
462
462
  framework_name: "GAIA"
463
- framework_version: "1.49.0"
463
+ framework_version: "1.54.1"
464
464
  user_name: "your-name"
465
465
  project_name: "your-project"
466
466
  ```
@@ -35,9 +35,9 @@ function findBash() {
35
35
 
36
36
  // 1. Try Git for Windows FIRST (preferred — simpler path mapping)
37
37
  const gitBashPaths = [
38
- join(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
39
- join(process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "Git", "bin", "bash.exe"),
40
- join(process.env.LOCALAPPDATA || "", "Programs", "Git", "bin", "bash.exe"),
38
+ path.join(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
39
+ path.join(process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "Git", "bin", "bash.exe"),
40
+ path.join(process.env.LOCALAPPDATA || "", "Programs", "Git", "bin", "bash.exe"),
41
41
  ];
42
42
 
43
43
  for (const p of gitBashPaths) {
@@ -83,9 +83,9 @@ function info(message) {
83
83
  }
84
84
 
85
85
  function cleanup() {
86
- if (tempDir && existsSync(tempDir)) {
86
+ if (tempDir && fs.existsSync(tempDir)) {
87
87
  try {
88
- rmSync(tempDir, { recursive: true, force: true });
88
+ fs.rmSync(tempDir, { recursive: true, force: true });
89
89
  } catch {
90
90
  // Best-effort cleanup
91
91
  }
@@ -94,7 +94,7 @@ function cleanup() {
94
94
 
95
95
  function ensureGit() {
96
96
  try {
97
- execSync("git --version", { stdio: "ignore" });
97
+ childProcess.execSync("git --version", { stdio: "ignore" });
98
98
  } catch {
99
99
  fail(
100
100
  "git is required but was not found.\n" +
@@ -104,8 +104,7 @@ function ensureGit() {
104
104
  }
105
105
 
106
106
  function readPackageVersion(pkgPath) {
107
- const { readFileSync } = require("fs");
108
- const raw = readFileSync(pkgPath, "utf8");
107
+ const raw = fs.readFileSync(pkgPath, "utf8");
109
108
  const pkg = JSON.parse(raw);
110
109
  if (!pkg.version) {
111
110
  throw new Error(`No version field found in ${pkgPath}`);
@@ -143,7 +142,15 @@ Examples:
143
142
 
144
143
  // ─── Main ───────────────────────────────────────────────────────────────────
145
144
 
146
- function main() {
145
+ function main(deps) {
146
+ // Dependency injection for testability — defaults to real modules
147
+ const _exec = deps && deps.execSync || childProcess.execSync;
148
+ const _execFile = deps && deps.execFileSync || childProcess.execFileSync;
149
+ const _mkdtemp = deps && deps.mkdtempSync || fs.mkdtempSync;
150
+ const _exists = deps && deps.existsSync || fs.existsSync;
151
+ const _join = deps && deps.join || path.join;
152
+ const _tmpdir = deps && deps.tmpdir || os.tmpdir;
153
+
147
154
  const args = process.argv.slice(2);
148
155
 
149
156
  // Handle help / no args
@@ -190,7 +197,7 @@ function main() {
190
197
  info("Cloning GAIA framework from GitHub...");
191
198
 
192
199
  try {
193
- execSync(`git clone --depth 1 ${REPO_URL} "${tempDir}"`, {
200
+ _exec(`git clone --depth 1 ${REPO_URL} "${tempDir}"`, {
194
201
  stdio: ["ignore", "ignore", "pipe"],
195
202
  });
196
203
  } catch (err) {
@@ -201,8 +208,8 @@ function main() {
201
208
  }
202
209
 
203
210
  // Locate the installer script
204
- const scriptPath = join(tempDir, SCRIPT_NAME);
205
- if (!existsSync(scriptPath)) {
211
+ const scriptPath = _join(tempDir, SCRIPT_NAME);
212
+ if (!_exists(scriptPath)) {
206
213
  fail(`Installer script not found in cloned repo: ${SCRIPT_NAME}`);
207
214
  }
208
215
 
@@ -246,4 +253,8 @@ function main() {
246
253
  }
247
254
  }
248
255
 
249
- main();
256
+ if (require.main === module) {
257
+ main();
258
+ }
259
+
260
+ module.exports = { findBash, ensureGit, showUsage, fail, info, cleanup, readPackageVersion, main };
package/gaia-install.sh CHANGED
@@ -6,7 +6,7 @@ set -euo pipefail
6
6
  # Installs, updates, validates, and reports on GAIA installations.
7
7
  # ─────────────────────────────────────────────────────────────────────────────
8
8
 
9
- readonly VERSION="1.54.0"
9
+ readonly VERSION="1.54.1"
10
10
  readonly GITHUB_REPO="https://github.com/jlouage/Gaia-framework.git"
11
11
  readonly MANIFEST_REL="_gaia/_config/manifest.yaml"
12
12
 
@@ -105,6 +105,11 @@ clone_from_github() {
105
105
  echo "$TEMP_CLONE_DIR"
106
106
  }
107
107
 
108
+ normalize_path() {
109
+ local p="${1//\\//}"
110
+ echo "$p"
111
+ }
112
+
108
113
  resolve_source() {
109
114
  local resolved=""
110
115
 
@@ -116,10 +121,14 @@ resolve_source() {
116
121
  elif [[ -n "${GAIA_SOURCE:-}" ]]; then
117
122
  resolved="$GAIA_SOURCE"
118
123
  [[ "$OPT_VERBOSE" == true ]] && detail "Source from \$GAIA_SOURCE: $resolved" >&2
119
- # 3. Self-detect: script's own directory
120
- elif [[ -d "$(dirname "$(realpath "$0")")/_gaia" ]]; then
124
+ # 3. Self-detect: script's own directory (guard realpath for Git Bash compatibility)
125
+ elif command -v realpath &>/dev/null && [[ -d "$(dirname "$(realpath "$0")")/_gaia" ]]; then
121
126
  resolved="$(dirname "$(realpath "$0")")"
122
127
  [[ "$OPT_VERBOSE" == true ]] && detail "Source from script location: $resolved" >&2
128
+ # 3b. Fallback when realpath is unavailable (e.g., Git Bash without MSYS2 extras)
129
+ elif [[ -d "$(cd "$(dirname "$0")" && pwd)/_gaia" ]]; then
130
+ resolved="$(cd "$(dirname "$0")" && pwd)"
131
+ [[ "$OPT_VERBOSE" == true ]] && detail "Source from script location (cd fallback): $resolved" >&2
123
132
  # 4. GitHub clone
124
133
  else
125
134
  resolved="$(clone_from_github)"
@@ -182,6 +191,77 @@ copy_with_backup() {
182
191
  [[ "$OPT_VERBOSE" == true ]] && detail "Updated (backed up): $dst" || true
183
192
  }
184
193
 
194
+ # Remove .resolved/*.yaml files from target _gaia/ directory.
195
+ # Called after cp/tar copy to replicate rsync's --exclude behavior.
196
+ # Only .resolved/*.yaml is relevant to the _gaia/ subtree; the other rsync
197
+ # --exclude patterns target _memory/ paths outside _gaia/ and never match.
198
+ clean_resolved_yaml() {
199
+ local target_gaia="$1"
200
+ find "$target_gaia" -path '*/.resolved/*.yaml' -delete 2>/dev/null || true
201
+ }
202
+
203
+ # Copy _gaia/ directory from source to target using a fallback chain:
204
+ # rsync → cp -rp → tar
205
+ # Tries each tool in order. If a tool is found but fails at runtime (e.g.,
206
+ # broken rsync stub on Windows), falls through to the next tool. Exits with
207
+ # a diagnostic error if all tools fail or none are available.
208
+ #
209
+ # Excludes .resolved/*.yaml files from the final output (rsync handles this
210
+ # natively via --exclude; cp and tar do a post-copy cleanup).
211
+ #
212
+ # Note: No symlinks currently exist in _gaia/. If symlinks are introduced
213
+ # in the future, verify that cp -rp behavior matches rsync -a on all
214
+ # target platforms (ADR-004).
215
+ copy_gaia_files() {
216
+ local src="$1" dst="$2"
217
+ local copy_done=false
218
+
219
+ # Try rsync first (preferred — handles excludes natively)
220
+ if command -v rsync >/dev/null 2>&1; then
221
+ if rsync -a \
222
+ --exclude='_memory/checkpoints/*.yaml' \
223
+ --exclude='_memory/checkpoints/completed/*.yaml' \
224
+ --exclude='.resolved/*.yaml' \
225
+ --exclude='_memory/*-sidecar/*.md' \
226
+ --exclude='_memory/*-sidecar/*.yaml' \
227
+ "$src/_gaia/" "$dst/_gaia/" 2>/dev/null; then
228
+ detail "Copied framework files using rsync"
229
+ copy_done=true
230
+ else
231
+ detail "rsync found but failed — trying fallback methods"
232
+ fi
233
+ fi
234
+
235
+ # Fallback: cp -rp (preserves permissions like rsync -a)
236
+ if [[ "$copy_done" == false ]] && command -v cp >/dev/null 2>&1; then
237
+ if cp -rp "$src/_gaia/" "$dst/_gaia/" 2>/dev/null; then
238
+ clean_resolved_yaml "$dst/_gaia"
239
+ detail "Copied framework files using cp -rp (rsync unavailable)"
240
+ copy_done=true
241
+ else
242
+ error "cp failed to copy framework files — check permissions and disk space"
243
+ exit 1
244
+ fi
245
+ fi
246
+
247
+ # Fallback: tar (last resort — available on virtually all POSIX systems)
248
+ if [[ "$copy_done" == false ]] && command -v tar >/dev/null 2>&1; then
249
+ if (tar -cf - -C "$src" _gaia | tar -xf - -C "$dst") 2>/dev/null; then
250
+ clean_resolved_yaml "$dst/_gaia"
251
+ detail "Copied framework files using tar (rsync and cp unavailable)"
252
+ copy_done=true
253
+ else
254
+ error "tar failed to copy framework files — check permissions and disk space"
255
+ exit 1
256
+ fi
257
+ fi
258
+
259
+ if [[ "$copy_done" == false ]]; then
260
+ error "No suitable copy tool found (tried rsync, cp, tar). Cannot copy framework files."
261
+ exit 1
262
+ fi
263
+ }
264
+
185
265
  append_if_missing() {
186
266
  local file="$1" marker="$2" content="$3"
187
267
  if [[ -f "$file" ]] && grep -qF "$marker" "$file"; then
@@ -261,18 +341,22 @@ cmd_init() {
261
341
  done
262
342
 
263
343
  # Step 2: Copy _gaia/ recursively (excluding checkpoints and .resolved/*.yaml)
344
+ # Uses rsync if available, falls back to cp -rp, then tar (ADR-004: Windows best-effort)
264
345
  step "Copying framework files..."
265
346
  if [[ "$OPT_DRY_RUN" == true ]]; then
266
- detail "[dry-run] Would copy _gaia/ (excluding checkpoints and .resolved/*.yaml)"
347
+ if command -v rsync >/dev/null 2>&1; then
348
+ detail "[dry-run] Would copy _gaia/ using rsync (excluding checkpoints and .resolved/*.yaml)"
349
+ elif command -v cp >/dev/null 2>&1; then
350
+ detail "[dry-run] Would copy _gaia/ using cp -rp (rsync unavailable)"
351
+ elif command -v tar >/dev/null 2>&1; then
352
+ detail "[dry-run] Would copy _gaia/ using tar (rsync and cp unavailable)"
353
+ else
354
+ error "No suitable copy tool found (tried rsync, cp, tar)"
355
+ exit 1
356
+ fi
267
357
  else
268
358
  mkdir -p "$TARGET/_gaia"
269
- rsync -a \
270
- --exclude='_memory/checkpoints/*.yaml' \
271
- --exclude='_memory/checkpoints/completed/*.yaml' \
272
- --exclude='.resolved/*.yaml' \
273
- --exclude='_memory/*-sidecar/*.md' \
274
- --exclude='_memory/*-sidecar/*.yaml' \
275
- "$source/_gaia/" "$TARGET/_gaia/"
359
+ copy_gaia_files "$source" "$TARGET"
276
360
  fi
277
361
 
278
362
  # Step 3: Create _memory/ directory tree at project root (ADR-013)
@@ -351,7 +435,10 @@ cmd_init() {
351
435
  else
352
436
  local global_file="$TARGET/_gaia/_config/global.yaml"
353
437
  if [[ -f "$global_file" ]]; then
354
- # Use portable sed for both macOS and Linux
438
+ # Use portable sed for both macOS and Linux.
439
+ # macOS (Darwin) requires sed -i '' (empty extension). Linux/GNU sed uses sed -i (no arg).
440
+ # Git Bash on Windows: uname returns "MINGW64_NT-*", which falls into the else branch
441
+ # (GNU sed), which is correct — Git for Windows ships GNU sed.
355
442
  if [[ "$(uname)" == "Darwin" ]]; then
356
443
  sed -i '' "s|^project_name:.*|project_name: \"$project_name\"|" "$global_file"
357
444
  sed -i '' "s|^user_name:.*|user_name: \"$user_name\"|" "$global_file"
@@ -430,6 +517,21 @@ GITIGNORE
430
517
  echo ""
431
518
  }
432
519
 
520
+ # ─── find_files_in_dir ──────────────────────────────────────────────────────
521
+ # Lists all files in a directory tree. Uses null-delimited output (find -print0)
522
+ # when supported, falls back to newline-delimited output on systems where -print0
523
+ # is unavailable (e.g., Windows Git Bash with busybox find).
524
+ # GAIA file paths never contain newlines, so the newline-delimited fallback is safe.
525
+
526
+ find_files_in_dir() {
527
+ local dir="$1"
528
+ if find /dev/null -maxdepth 0 -print0 2>/dev/null | head -c0 2>/dev/null; then
529
+ find "$dir" -type f -print0
530
+ else
531
+ find "$dir" -type f
532
+ fi
533
+ }
534
+
433
535
  # ─── cmd_update ─────────────────────────────────────────────────────────────
434
536
 
435
537
  cmd_update() {
@@ -497,6 +599,12 @@ cmd_update() {
497
599
  step "Updating framework files..."
498
600
  local updated=0 skipped=0 changed=0
499
601
 
602
+ # Feature-detect find -print0 support once before the loop (E6-S2)
603
+ local use_print0=false
604
+ if find /dev/null -maxdepth 0 -print0 2>/dev/null | head -c0 2>/dev/null; then
605
+ use_print0=true
606
+ fi
607
+
500
608
  for entry in "${update_targets[@]}"; do
501
609
  local src_path="$source/_gaia/$entry"
502
610
  local dst_path="$TARGET/_gaia/$entry"
@@ -515,12 +623,24 @@ cmd_update() {
515
623
  copy_with_backup "$src_path" "$dst_path" "$backup_dir"
516
624
  updated=$((updated + 1))
517
625
  elif [[ -d "$src_path" ]]; then
518
- # Directory — update each file
519
- while IFS= read -r -d '' file; do
520
- local rel="${file#$src_path/}"
521
- copy_with_backup "$file" "$dst_path/$rel" "$backup_dir"
522
- updated=$((updated + 1))
523
- done < <(find "$src_path" -type f -print0) || true
626
+ # Directory — update each file via find_files_in_dir helper
627
+ if [[ "$use_print0" == true ]]; then
628
+ # Null-delimited path (macOS/Linux with GNU or BSD find)
629
+ while IFS= read -r -d '' file; do
630
+ local rel="${file#$src_path/}"
631
+ copy_with_backup "$file" "$dst_path/$rel" "$backup_dir"
632
+ updated=$((updated + 1))
633
+ done < <(find_files_in_dir "$src_path") || true
634
+ else
635
+ # Newline-delimited fallback (Windows Git Bash / busybox find)
636
+ # Safe because GAIA file paths never contain newlines
637
+ while IFS= read -r file; do
638
+ [[ -z "$file" ]] && continue
639
+ local rel="${file#$src_path/}"
640
+ copy_with_backup "$file" "$dst_path/$rel" "$backup_dir"
641
+ updated=$((updated + 1))
642
+ done < <(find_files_in_dir "$src_path") || true
643
+ fi
524
644
  fi
525
645
  done
526
646
 
@@ -830,7 +950,7 @@ parse_args() {
830
950
  error "--source requires a path argument"
831
951
  exit 1
832
952
  fi
833
- SOURCE_FLAG="$2"
953
+ SOURCE_FLAG="$(normalize_path "$2")"
834
954
  shift 2
835
955
  ;;
836
956
  --yes|-y)
@@ -858,7 +978,7 @@ parse_args() {
858
978
  error "Unexpected argument: $1"
859
979
  exit 1
860
980
  fi
861
- TARGET="$1"
981
+ TARGET="$(normalize_path "$1")"
862
982
  shift
863
983
  ;;
864
984
  esac
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "gaia-framework",
3
- "version": "1.54.0",
3
+ "version": "1.54.1",
4
4
  "description": "GAIA — Generative Agile Intelligence Architecture installer",
5
5
  "bin": {
6
6
  "gaia-framework": "./bin/gaia-framework.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "vitest run",
9
+ "test": "vitest run --coverage",
10
10
  "test:watch": "vitest",
11
11
  "test:coverage": "vitest run --coverage",
12
12
  "test:unit": "vitest run test/unit",