@ainova-systems/intelligence 0.11.0-rc.1 → 0.11.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +19 -16
  2. package/cli/commands/adapter.sh +153 -0
  3. package/cli/commands/init.sh +171 -34
  4. package/cli/commands/package.sh +30 -0
  5. package/cli/commands/registry.sh +65 -30
  6. package/cli/commands/status.sh +20 -11
  7. package/cli/commands/sync.sh +9 -4
  8. package/cli/commands/update.sh +80 -46
  9. package/cli/engine-package.yaml +14 -0
  10. package/cli/intelligence +37 -26
  11. package/cli/internal/align-project.sh +134 -0
  12. package/cli/internal/check.sh +128 -0
  13. package/cli/internal/convert-legacy.sh +400 -0
  14. package/cli/{commands/add.sh → internal/package-add.sh} +37 -19
  15. package/cli/{commands/list.sh → internal/package-list.sh} +10 -5
  16. package/cli/{commands/remove.sh → internal/package-remove.sh} +16 -4
  17. package/cli/internal/package-search.sh +83 -0
  18. package/cli/internal/package-update.sh +103 -0
  19. package/cli/internal/restore.sh +130 -0
  20. package/cli/internal/target-state.sh +57 -0
  21. package/cli/lib/cli-common.sh +213 -63
  22. package/cli/lib/lockfile.sh +15 -9
  23. package/cli/lib/manifest.sh +257 -4
  24. package/cli/lib/registry.sh +96 -47
  25. package/cli/lib/semver.sh +12 -4
  26. package/engine/ENGINE_SHA +1 -0
  27. package/engine/{scripts/adapters → adapters}/_template.sh +10 -9
  28. package/engine/{scripts/adapters → adapters}/agents.sh +20 -25
  29. package/engine/{scripts/adapters → adapters}/opencode.sh +1 -1
  30. package/engine/{scripts/lib → lib}/common.sh +30 -538
  31. package/engine/lib/contract.sh +120 -0
  32. package/engine/sync.sh +238 -0
  33. package/package.json +5 -5
  34. package/{engine → packages/sync}/agents/intelligence-architect.md +5 -3
  35. package/{engine → packages/sync}/agents/intelligence-operator.md +10 -13
  36. package/packages/sync/references/adapters.md +252 -0
  37. package/packages/sync/references/conventions.md +385 -0
  38. package/{engine → packages/sync}/rules/intelligence-authoring.md +7 -7
  39. package/{engine → packages/sync}/skills/intelligence-add-agent/SKILL.md +7 -7
  40. package/{engine → packages/sync}/skills/intelligence-add-rule/SKILL.md +5 -5
  41. package/{engine → packages/sync}/skills/intelligence-add-skill/SKILL.md +5 -5
  42. package/{engine → packages/sync}/skills/intelligence-extract-skill/SKILL.md +2 -2
  43. package/packages/sync/skills/intelligence-install-adapter/SKILL.md +38 -0
  44. package/{engine → packages/sync}/skills/intelligence-learn-from-context/SKILL.md +3 -3
  45. package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +55 -0
  46. package/{engine → packages/sync}/skills/intelligence-review-skills/SKILL.md +7 -7
  47. package/packages/sync/skills/intelligence-sync/SKILL.md +20 -0
  48. package/packages/sync/skills/intelligence-uninstall-adapter/SKILL.md +24 -0
  49. package/packages/sync/skills/intelligence-update/SKILL.md +43 -0
  50. package/cli/commands/doctor.sh +0 -100
  51. package/cli/commands/install.sh +0 -84
  52. package/cli/commands/migrate.sh +0 -251
  53. package/cli/commands/upgrade.sh +0 -27
  54. package/engine/INIT.md +0 -498
  55. package/engine/docs/ADAPTERS.md +0 -212
  56. package/engine/docs/CLI.md +0 -91
  57. package/engine/docs/CONVENTIONS.md +0 -440
  58. package/engine/scripts/lib/layout.sh +0 -51
  59. package/engine/scripts/lib/migrations.sh +0 -708
  60. package/engine/scripts/sync.sh +0 -311
  61. package/engine/scripts/update.sh +0 -237
  62. package/engine/skills/intelligence-install-adapter/SKILL.md +0 -31
  63. package/engine/skills/intelligence-sync/SKILL.md +0 -18
  64. package/engine/skills/intelligence-uninstall-adapter/SKILL.md +0 -42
  65. package/engine/skills/intelligence-update/SKILL.md +0 -159
  66. package/registry/index.yaml +0 -15
  67. /package/engine/{scripts/VERSION → VERSION} +0 -0
  68. /package/engine/{scripts/adapters → adapters}/claude.sh +0 -0
  69. /package/engine/{scripts/adapters → adapters}/codex.sh +0 -0
  70. /package/engine/{scripts/adapters → adapters}/copilot.sh +0 -0
  71. /package/engine/{scripts/adapters → adapters}/cursor.sh +0 -0
  72. /package/engine/{scripts/adapters → adapters}/pi.sh +0 -0
@@ -1,32 +1,41 @@
1
1
  #!/bin/bash
2
- # intelligence status — project state at a glance.
2
+ # intelligence status [--check] — project state or deep consistency checks.
3
3
  set -euo pipefail
4
4
  source "$CLI_DIR/lib/cli-common.sh"
5
5
 
6
+ case "${1:-}" in
7
+ "") ;;
8
+ --check) [ $# -eq 1 ] || die "usage: intelligence status [--check]"; exec bash "$CLI_DIR/internal/check.sh" ;;
9
+ *) die "usage: intelligence status [--check]" ;;
10
+ esac
11
+
6
12
  detect_project
7
13
  case "$IP_MODE" in
8
- v2)
9
- echo "Project: $IP_ROOT (CLI setup)"
10
- echo "Manifest: intelligence.yaml (sync_version $(read_engine_stamp "$IP_ROOT/intelligence.yaml"))"
14
+ cli)
15
+ echo "Project: $IP_ROOT (Intelligence project)"
16
+ echo "Manifest: intelligence.yaml (schema_version $(read_schema_version "$IP_ROOT/intelligence.yaml"))"
11
17
  if [ -f "$IP_ROOT/intelligence.lock" ]; then
12
18
  echo "Lockfile: intelligence.lock"
19
+ elif [ -n "$(qmap_keys "$IP_ROOT/intelligence.yaml" "packages")" ]; then
20
+ echo "Lockfile: MISSING (manifest declares packages)"
13
21
  else
14
22
  echo "Lockfile: none (no packages added yet)"
15
23
  fi
16
- if [ -f "$IP_ROOT/.intelligence/engine/.version" ]; then
17
- echo "Store: .intelligence/engine at $(tr -d ' \t\r\n' < "$IP_ROOT/.intelligence/engine/.version")"
24
+ sync_locked="$(qmap_field "$IP_ROOT/intelligence.lock" "packages" "$SYNC_PKG_NAME" "resolved")"
25
+ if [ -n "$sync_locked" ]; then
26
+ echo "Content: $SYNC_PKG_NAME at $sync_locked"
18
27
  else
19
- echo "Store: not staged — run 'intelligence install'"
28
+ echo "Content: no $SYNC_PKG_NAME (bare setup)"
20
29
  fi
21
- echo "Engine: $(bundled_engine_version) (bundled)"
30
+ echo "Engine: $(bundled_engine_version) (bundled with the CLI)"
22
31
  ;;
23
32
  legacy)
24
- echo "Project: $IP_ROOT (vendored v1 setup)"
33
+ echo "Project: $IP_ROOT (legacy Intelligence Sync)"
25
34
  echo "Umbrella: $IP_UMBRELLA"
26
35
  echo "Engine: $(tr -d ' \t\r\n' < "$IP_MODULE_DIR/scripts/VERSION") (vendored at $IP_MODULE_DIR)"
27
- echo "Stamp: $(read_engine_stamp "$IP_UMBRELLA/config.yaml")"
36
+ echo "Stamp: $(top_scalar "$IP_UMBRELLA/config.yaml" "sync_version")"
28
37
  echo ""
29
- echo "Convert to the CLI setup: intelligence migrate"
38
+ echo "Convert to Intelligence: intelligence init"
30
39
  ;;
31
40
  *)
32
41
  echo "No intelligence project here. Start one: intelligence init"
@@ -5,15 +5,20 @@ source "$CLI_DIR/lib/cli-common.sh"
5
5
 
6
6
  detect_project
7
7
  case "$IP_MODE" in
8
- v2)
9
- ensure_engine_staged "$IP_ROOT"
8
+ cli)
9
+ # `sync` is the normal fresh-clone command. A newer globally installed
10
+ # CLI aligns the current Intelligence project first (except in CI, where a
11
+ # tracked migration must be reviewed and committed locally), and a
12
+ # missing ignored store is restored strictly from the committed lock.
13
+ ensure_project_current "$IP_ROOT"
14
+ restore_project_store_if_missing "$IP_ROOT"
10
15
  export_engine_env "$IP_ROOT"
11
- exec bash "$IS_ENGINE_DIR/scripts/sync.sh" "$@"
16
+ exec bash "$IS_ENGINE_DIR/sync.sh" "$@"
12
17
  ;;
13
18
  legacy)
14
19
  # A vendored project syncs with ITS engine — its pin is the contract,
15
20
  # and a newer bundled engine must not generate against an older schema.
16
- echo "NOTE: vendored (v1) setup — delegating to its own engine. 'intelligence migrate' converts it to the CLI setup." >&2
21
+ echo "NOTE: legacy Intelligence Sync project — delegating to $IP_MODULE_DIR/scripts/sync.sh. 'intelligence init' converts it into an Intelligence project." >&2
17
22
  exec bash "$IP_MODULE_DIR/scripts/sync.sh" "$@"
18
23
  ;;
19
24
  *)
@@ -1,59 +1,93 @@
1
1
  #!/bin/bash
2
- # intelligence update [name] [--no-sync]
2
+ # intelligence update [@scope/name] [--preview|--apply]
3
3
  #
4
- # Re-resolve version ranges against the remotes and rewrite the lock. Pinned
5
- # packages (`ref:`) never move here — repin by editing the manifest or
6
- # re-adding. `update <name>` limits the pass to one package.
4
+ # One update surface: show the installed-CLI/project/package plan, then either
5
+ # stop, ask interactively, or apply without a prompt. Updating the global npm
6
+ # installation remains an explicit package-manager operation.
7
7
  set -euo pipefail
8
8
  source "$CLI_DIR/lib/cli-common.sh"
9
9
 
10
- only="" no_sync=0
10
+ only="" mode="ask" preview_seen=0 apply_seen=0
11
11
  while [ $# -gt 0 ]; do
12
12
  case "$1" in
13
- --no-sync) no_sync=1 ;;
14
- @*) only="$1" ;;
15
- *) die "unknown argument '$1'" ;;
13
+ --preview) preview_seen=1; mode="preview" ;;
14
+ --apply) apply_seen=1; mode="apply" ;;
15
+ @*) [ -z "$only" ] || die "only one package may be selected"; only="$1" ;;
16
+ *) die "usage: intelligence update [@scope/name] [--preview|--apply]" ;;
16
17
  esac
17
- shift || true
18
+ shift
18
19
  done
20
+ [ "$preview_seen" -eq 0 ] || [ "$apply_seen" -eq 0 ] || die "choose either --preview or --apply"
19
21
 
20
- require_v2
22
+ require_cli_project
21
23
  manifest="$IP_ROOT/intelligence.yaml"
22
- lock="$IP_ROOT/intelligence.lock"
23
-
24
- moved=0 checked=0
25
- while IFS= read -r name; do
26
- [ -n "$name" ] || continue
27
- [ -n "$only" ] && [ "$name" != "$only" ] && continue
28
- ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
29
- [ -n "$ref" ] && { echo " $name: pinned to ref '$ref' — skipped"; continue; }
30
- range="$(qmap_field "$manifest" "packages" "$name" "version")"
31
- url="$(qmap_field "$manifest" "packages" "$name" "url")"
32
- path="$(qmap_field "$manifest" "packages" "$name" "path")"
33
- if [ -z "$url" ]; then
34
- resolve_package_source "$manifest" "$name"
35
- url="$RES_URL"; path="$RES_PATH"
36
- fi
37
- checked=$((checked + 1))
38
- picked="$(list_remote_versions "$url" | semver_pick_highest "$range")"
39
- [ -n "$picked" ] || { echo " $name: nothing satisfies '${range:-*}' at $url" >&2; continue; }
40
- read -r tag _ <<< "$(remote_tag_for_version "$url" "$picked")"
41
- current="$(qmap_field "$lock" "packages" "$name" "resolved")"
42
- if [ "$tag" = "$current" ]; then
43
- echo " $name: ${current:-<none>} (up to date)"
44
- continue
24
+ check_version_compat "$manifest"
25
+ eng="$(bundled_engine_version)"
26
+ stamp="$(read_schema_version "$manifest")"
27
+ project_change=0
28
+
29
+ echo "Update plan"
30
+ echo ""
31
+ echo "CLI:"
32
+ if [ -n "${INTELLIGENCE_NPM_VERSION:-}" ] && [ "${IS_SKIP_NPM_CHECK:-0}" != "1" ] && command -v npm >/dev/null 2>&1; then
33
+ channel="latest"
34
+ case "$INTELLIGENCE_NPM_VERSION" in *-*) channel="next" ;; esac
35
+ available="$(npm view "@ainova-systems/intelligence" "dist-tags.$channel" 2>/dev/null || true)"
36
+ if [ -z "$available" ]; then
37
+ echo " installed: $INTELLIGENCE_NPM_VERSION; registry check unavailable"
38
+ elif [ "$available" = "$INTELLIGENCE_NPM_VERSION" ]; then
39
+ echo " $INTELLIGENCE_NPM_VERSION (up to date on npm $channel)"
40
+ else
41
+ echo " $INTELLIGENCE_NPM_VERSION -> $available (npm $channel)"
42
+ echo " run: npm install -g @ainova-systems/intelligence@$channel"
43
+ echo " then rerun: intelligence update --apply"
45
44
  fi
46
- rel=".intelligence/packages/$name"
47
- sha="$(fetch_package "$url" "$tag" "$path" "$IP_ROOT/$rel")"
48
- wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
49
- lock_upsert "$lock" "$name" "$range" "$url" "$path" "$tag" "$sha"
50
- echo " $name: ${current:-<none>} -> $tag"
51
- moved=$((moved + 1))
52
- done < <(qmap_keys "$manifest" "packages")
53
-
54
- [ -n "$only" ] && [ "$checked" -eq 0 ] && die "package '$only' is not in the manifest"
55
- echo "updated: $moved package(s)"
56
-
57
- if [ "$moved" -gt 0 ] && [ "$no_sync" -eq 0 ]; then
58
- exec bash "$CLI_DIR/commands/sync.sh"
45
+ else
46
+ echo " source checkout/registry check skipped; engine $eng"
47
+ fi
48
+
49
+ echo ""
50
+ echo "Project:"
51
+ if project_needs_upgrade "$IP_ROOT"; then
52
+ echo " lifecycle alignment required (stamp ${stamp:-unstamped}, engine $eng)"
53
+ project_change=1
54
+ else
55
+ echo " schema/content $eng (up to date)"
59
56
  fi
57
+
58
+ if project_has_packages "$IP_ROOT" && [ ! -f "$IP_ROOT/intelligence.lock" ]; then
59
+ die "manifest declares packages but intelligence.lock is absent — restore the committed lock before planning or applying updates"
60
+ fi
61
+
62
+ pkg_args=(--preview)
63
+ [ -z "$only" ] || pkg_args+=("$only")
64
+ pkg_plan="$(bash "$CLI_DIR/internal/package-update.sh" "${pkg_args[@]}")"
65
+ echo ""
66
+ echo "Packages:"
67
+ printf '%s\n' "$pkg_plan"
68
+ package_changes="$(printf '%s\n' "$pkg_plan" | awk '/^updates available:/ {print $3; exit}')"
69
+ package_changes="${package_changes:-0}"
70
+
71
+ if [ "$mode" = "preview" ]; then
72
+ exit 0
73
+ fi
74
+ if [ "$project_change" -eq 0 ] && [ "$package_changes" -eq 0 ]; then
75
+ echo ""
76
+ echo "Nothing to apply with the currently installed CLI."
77
+ exit 0
78
+ fi
79
+ if [ "$mode" = "ask" ]; then
80
+ [ -t 0 ] || die "update requires confirmation — rerun with --preview or --apply"
81
+ printf '\nApply this plan? [Y/n] '
82
+ read -r answer
83
+ case "$answer" in
84
+ ""|y|Y|yes|YES) ;;
85
+ *) echo "update cancelled"; exit 0 ;;
86
+ esac
87
+ fi
88
+
89
+ ensure_project_current "$IP_ROOT"
90
+ apply_args=(--no-sync)
91
+ [ -z "$only" ] || apply_args+=("$only")
92
+ bash "$CLI_DIR/internal/package-update.sh" "${apply_args[@]}"
93
+ exec bash "$CLI_DIR/commands/sync.sh"
@@ -0,0 +1,14 @@
1
+ # What THIS CLI distribution ships as its engine-content package, and which
2
+ # registry its init seeds into new manifests. Data, not code: a fork or an
3
+ # enterprise mirror edits this file (or its copy in the npm build) — nothing
4
+ # in cli/ names a vendor.
5
+ #
6
+ # The engine-content package is the one package whose content documents the
7
+ # engine scripts bundled with the CLI: init writes this entry into the
8
+ # manifest (visible, removable), its pin always equals the engine VERSION,
9
+ # lifecycle preflight aligns it with the installed CLI, and at the matching
10
+ # version it materializes from the bundle without network.
11
+ name: "@ainova-systems/sync"
12
+ url: "https://github.com/ainova-systems/intelligence.git"
13
+ path: "packages/sync"
14
+ default_registry: "https://github.com/ainova-systems/intelligence-registry.git"
package/cli/intelligence CHANGED
@@ -3,25 +3,20 @@
3
3
  #
4
4
  # Dispatcher only: each command lives in commands/<name>.sh so commands can be
5
5
  # ported to Node one at a time without touching the others. The engine is the
6
- # vendored bash engine bundled next to the CLI (npm package) or, when running
7
- # from the intelligence-sync repo itself, the repo's own module.
6
+ # bash engine shipped next to the CLI.
8
7
 
9
8
  set -euo pipefail
10
9
 
11
10
  CLI_DIR="$(cd "$(dirname "$0")" && pwd)"
12
11
  export CLI_DIR
13
12
 
14
- # Engine discovery, by role (scripts/sync.sh + scripts/VERSION), never by
15
- # guessing names beyond the two shipped layouts:
16
- # 1. npm package: <pkg>/cli/intelligence + <pkg>/engine/scripts/
17
- # 2. this repo: <repo>/cli/intelligence + <repo>/intelligence/sync/scripts/
13
+ # Engine discovery, by role (sync.sh + VERSION). The repo and the npm package
14
+ # ship one layout — <root>/{cli,engine,packages/sync} — so there is a single
15
+ # candidate rather than a per-distribution list.
18
16
  IS_ENGINE_DIR=""
19
- for _cand in "$CLI_DIR/../engine" "$CLI_DIR/../intelligence/sync"; do
20
- if [ -f "$_cand/scripts/sync.sh" ] && [ -f "$_cand/scripts/VERSION" ]; then
21
- IS_ENGINE_DIR="$(cd "$_cand" && pwd)"
22
- break
23
- fi
24
- done
17
+ if [ -f "$CLI_DIR/../engine/sync.sh" ] && [ -f "$CLI_DIR/../engine/VERSION" ]; then
18
+ IS_ENGINE_DIR="$(cd "$CLI_DIR/../engine" && pwd)"
19
+ fi
25
20
  if [ -z "$IS_ENGINE_DIR" ]; then
26
21
  echo "ERROR: bundled sync engine not found next to the CLI ($CLI_DIR)." >&2
27
22
  echo " Reinstall: npm i -g @ainova-systems/intelligence" >&2
@@ -29,40 +24,56 @@ if [ -z "$IS_ENGINE_DIR" ]; then
29
24
  fi
30
25
  export IS_ENGINE_DIR
31
26
 
27
+ # The engine's own content, shipped beside the engine as the seed for the
28
+ # @ainova-systems/sync package at exactly this CLI's version. Separate from the
29
+ # engine dir: the store must receive rules/agents/skills, never engine scripts.
30
+ IS_BUNDLED_PKG_DIR=""
31
+ if [ -d "$CLI_DIR/../packages/sync" ]; then
32
+ IS_BUNDLED_PKG_DIR="$(cd "$CLI_DIR/../packages/sync" && pwd)"
33
+ fi
34
+ export IS_BUNDLED_PKG_DIR
35
+
32
36
  cmd="${1:-}"
33
37
  case "$cmd" in
34
38
  ""|help|-h|--help)
35
- engine_ver="$(tr -d ' \t\r\n' < "$IS_ENGINE_DIR/scripts/VERSION")"
39
+ engine_ver="$(tr -d ' \t\r\n' < "$IS_ENGINE_DIR/VERSION")"
36
40
  cat <<EOF
37
41
  intelligence — build, version and distribute AI agent intelligence
38
42
  (engine $engine_ver${INTELLIGENCE_NPM_VERSION:+, package $INTELLIGENCE_NPM_VERSION})
39
43
 
40
44
  Usage: intelligence <command> [args]
41
45
 
42
- init Set up this project (manifest, content skeleton, targets)
43
- add <spec> Add a package: @scope/name[@range] | github:org/repo[#path] | git+<url>[@ref][#path]
44
- remove <name> Remove a package
45
- install [--frozen] Restore .intelligence/ exactly from intelligence.lock
46
- update [name] Re-resolve version ranges and rewrite the lockfile
47
- upgrade Upgrade the project to this CLI's engine (restage + restamp)
48
- list Installed packages with requested/locked versions
49
- sync [target] Render intelligence to every enabled tool (.claude, AGENTS.md, ...)
50
- registry <list|add|remove> Bind a scope to a registry index
51
- migrate [--dry-run] Convert a vendored (v1) setup to the CLI setup
52
- doctor Check manifest, lockfile, store and engine consistency
53
- status Project state at a glance
46
+ init [--preview|--apply] Initialize, convert, restore or align this project
47
+ --targets a,b --dir name --bare --no-sync New-project options
48
+ --force Accept a dirty worktree during legacy project conversion
49
+ sync [adapter] Restore locked content if needed, then render enabled adapters
50
+ update [@scope/name] Show an update plan; ask before applying it
51
+ --preview Show the plan without asking or writing
52
+ --apply Apply the plan without asking
53
+ package <command> add | remove | list | search
54
+ adapter <command> list | create | enable | disable | remove
55
+ status [--check] Project state; --check runs deep consistency checks
56
+ registry <list|add|remove> Manage the trusted registry list (add <repo-url>)
54
57
  version Print CLI and engine versions
55
58
  EOF
56
59
  exit 0
57
60
  ;;
58
61
  version|-v|--version)
59
- engine_ver="$(tr -d ' \t\r\n' < "$IS_ENGINE_DIR/scripts/VERSION")"
62
+ engine_ver="$(tr -d ' \t\r\n' < "$IS_ENGINE_DIR/VERSION")"
60
63
  echo "${INTELLIGENCE_NPM_VERSION:-$engine_ver} (engine $engine_ver)"
61
64
  exit 0
62
65
  ;;
63
66
  esac
64
67
  shift
65
68
 
69
+ # The command word builds a path — allow exactly lowercase words, so
70
+ # `intelligence ../x` can never exec a neighbouring script.
71
+ case "$cmd" in
72
+ *[!a-z-]*|-*)
73
+ echo "ERROR: unknown command '$cmd'. Run 'intelligence help'." >&2
74
+ exit 1
75
+ ;;
76
+ esac
66
77
  cmd_file="$CLI_DIR/commands/$cmd.sh"
67
78
  if [ ! -f "$cmd_file" ]; then
68
79
  echo "ERROR: unknown command '$cmd'. Run 'intelligence help'." >&2
@@ -0,0 +1,134 @@
1
+ #!/bin/bash
2
+ # Internal: bring an Intelligence project to this CLI's engine: apply schema
3
+ # migrations, align the engine-content package to the bundled version,
4
+ # restamp schema_version, sync.
5
+ set -euo pipefail
6
+ source "$CLI_DIR/lib/cli-common.sh"
7
+
8
+ no_sync=0
9
+ while [ $# -gt 0 ]; do
10
+ case "$1" in
11
+ --no-sync) no_sync=1 ;;
12
+ *) die "internal upgrade: unknown option '$1'" ;;
13
+ esac
14
+ shift
15
+ done
16
+
17
+ require_cli_project
18
+ manifest="$IP_ROOT/intelligence.yaml"
19
+
20
+ stamp="$(read_schema_version "$manifest")"
21
+ legacy_stamp="$(top_scalar "$manifest" "sync_version")"
22
+ schema_key_count="$(awk '/^schema_version:[[:space:]]*/ { n++ } END { print n + 0 }' "$manifest")"
23
+ legacy_key_count="$(awk '/^sync_version:[[:space:]]*/ { n++ } END { print n + 0 }' "$manifest")"
24
+ eng="$(bundled_engine_version)"
25
+ [ "$schema_key_count" -le 1 ] || die "manifest has duplicate schema_version keys"
26
+ [ "$legacy_key_count" -le 1 ] || die "manifest has duplicate sync_version keys"
27
+ if [ -n "$stamp" ] && _ver_gt "$stamp" "$eng"; then
28
+ die "manifest schema $stamp is newer than this CLI's engine $eng — update the global CLI first"
29
+ fi
30
+ if [ -n "$legacy_stamp" ] && _ver_gt "$legacy_stamp" "$eng"; then
31
+ die "manifest schema $legacy_stamp is newer than this CLI's engine $eng — update the global CLI first"
32
+ fi
33
+
34
+ # --- project schema migrations --------------------------------------------
35
+ # The engine's own chain owns vendored layouts; this one owns the manifest.
36
+ # Same discipline: ascending, append-only, each migration self-detects and
37
+ # no-ops when already applied.
38
+
39
+ # migration 0: the original RC manifest called the permanent schema key
40
+ # `sync_version`. Rename it without changing its value; the final stamp below
41
+ # then aligns it with this engine. The legacy Intelligence Sync reader remains
42
+ # separate.
43
+ if [ -n "$legacy_stamp" ]; then
44
+ if [ -n "$stamp" ]; then
45
+ [ "$stamp" = "$legacy_stamp" ] || die "manifest has conflicting schema_version '$stamp' and sync_version '$legacy_stamp'"
46
+ _qmap_stage "$manifest" '$0 !~ /^sync_version:[[:space:]]*/'
47
+ else
48
+ _qmap_stage "$manifest" '{ sub(/^sync_version:/, "schema_version:"); print }'
49
+ fi
50
+ echo " migrating: sync_version -> schema_version"
51
+ fi
52
+
53
+ # migration 1: package entries contain requested intent only. Early RCs
54
+ # also copied resolved url/path into the manifest. Preserve the existing lock
55
+ # as the trusted source and remove that duplicate representation. A direct
56
+ # unversioned package becomes an explicit HEAD pin so its manifest entry stays
57
+ # meaningful after the source fields are removed.
58
+ source_fields_migrated=0
59
+ while IFS= read -r name; do
60
+ [ -n "$name" ] || continue
61
+ m_url="$(qmap_field "$manifest" "packages" "$name" "url")"
62
+ m_path="$(qmap_field "$manifest" "packages" "$name" "path")"
63
+ [ -n "$m_url" ] || [ -n "$m_path" ] || continue
64
+ m_ver="$(qmap_field "$manifest" "packages" "$name" "version")"
65
+ m_ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
66
+ if [ -z "$m_ver" ] && [ -z "$m_ref" ]; then
67
+ locked_ref="$(qmap_field "$IP_ROOT/intelligence.lock" "packages" "$name" "resolved")"
68
+ qmap_set "$manifest" "packages" "$name" "ref" "${locked_ref:-HEAD}"
69
+ fi
70
+ qmap_delete_field "$manifest" "packages" "$name" "url"
71
+ qmap_delete_field "$manifest" "packages" "$name" "path"
72
+ source_fields_migrated=1
73
+ done < <(qmap_keys "$manifest" "packages")
74
+ if [ "$source_fields_migrated" -eq 1 ]; then
75
+ echo " migrating: package url/path -> intelligence.lock"
76
+ fi
77
+
78
+ # migration 2: staged engine content -> the @ainova-systems/sync package.
79
+ # Pre-package manifests listed `.intelligence/engine/{rules,agents,skills}`
80
+ # as sources fed by a CLI-staged copy; that copy is now an ordinary package
81
+ # entry seeded from the bundle. Post-condition: no `.intelligence/engine`
82
+ # source entries, the package present in manifest+lock+store, stale dir gone.
83
+ migrated=0
84
+ for section in rules agents skills; do
85
+ while IFS= read -r src; do
86
+ case "$src" in
87
+ .intelligence/engine/*)
88
+ sources_remove_entry "$manifest" "$section" "$src"
89
+ migrated=1
90
+ ;;
91
+ esac
92
+ done < <(read_yaml_list "$manifest" "$section")
93
+ done
94
+ if [ "$migrated" -eq 1 ]; then
95
+ echo " migrating: staged engine content -> $SYNC_PKG_NAME package"
96
+ rm -rf "$IP_ROOT/.intelligence/engine"
97
+ sync_pkg_entry "$manifest"
98
+ sync_pkg_install "$IP_ROOT"
99
+ fi
100
+
101
+ # --- steady state ---------------------------------------------------------
102
+ # Align the engine-content package with the bundled engine. Only a manifest
103
+ # that HAS the entry is touched — a --bare project opted out, and upgrade
104
+ # respects that.
105
+ have_pkg=0
106
+ while IFS= read -r name; do
107
+ [ "$name" = "$SYNC_PKG_NAME" ] && have_pkg=1
108
+ done < <(qmap_keys "$manifest" "packages")
109
+ if [ "$have_pkg" -eq 1 ] && [ "$migrated" -eq 0 ]; then
110
+ pinned="$(qmap_field "$manifest" "packages" "$SYNC_PKG_NAME" "version")"
111
+ if [ "$pinned" != "$eng" ]; then
112
+ echo " $SYNC_PKG_NAME: $pinned -> $eng"
113
+ fi
114
+ sync_pkg_entry "$manifest"
115
+ sync_pkg_install "$IP_ROOT"
116
+ fi
117
+ if [ "$have_pkg" -eq 0 ] && [ "$migrated" -eq 0 ]; then
118
+ echo " NOTE: $SYNC_PKG_NAME is not in this manifest (bare setup) — engine meta-skills stay uninstalled; 'intelligence package add $SYNC_PKG_NAME' opts back in." >&2
119
+ fi
120
+
121
+ # A previous RC could leave this managed directory even after its source
122
+ # entries were already removed. Its presence is itself an upgrade predicate,
123
+ # so always close that post-condition.
124
+ if [ -d "$IP_ROOT/.intelligence/engine" ]; then
125
+ rm -rf "$IP_ROOT/.intelligence/engine"
126
+ echo " removed stale .intelligence/engine"
127
+ fi
128
+
129
+ stamp_schema_version "$manifest" "$eng"
130
+ echo " schema_version -> $eng"
131
+
132
+ if [ "$no_sync" -eq 0 ]; then
133
+ exec bash "$CLI_DIR/commands/sync.sh"
134
+ fi
@@ -0,0 +1,128 @@
1
+ #!/bin/bash
2
+ # Internal deep consistency check. No writes; exits 1 on problems.
3
+ set -euo pipefail
4
+ source "$CLI_DIR/lib/cli-common.sh"
5
+
6
+ problems=0
7
+ warn() { echo " ✗ $*"; problems=$((problems + 1)); }
8
+ ok() { echo " ✓ $*"; }
9
+
10
+ echo "environment:"
11
+ command -v git >/dev/null 2>&1 && ok "git $(git --version | awk '{print $3}')" || warn "git not found"
12
+ command -v awk >/dev/null 2>&1 && ok "awk present" || warn "awk not found"
13
+ ok "bash $BASH_VERSION"
14
+ ok "engine $(bundled_engine_version) at $IS_ENGINE_DIR"
15
+
16
+ detect_project
17
+ case "$IP_MODE" in
18
+ none)
19
+ echo "project: none here — 'intelligence init' starts one"
20
+ exit 0
21
+ ;;
22
+ legacy)
23
+ echo "project: legacy Intelligence Sync at $IP_ROOT"
24
+ ok "vendored engine $(tr -d ' \t\r\n' < "$IP_MODULE_DIR/scripts/VERSION")"
25
+ echo " → 'intelligence init' converts it into an Intelligence project"
26
+ exit 0
27
+ ;;
28
+ esac
29
+
30
+ echo "project: $IP_ROOT"
31
+ manifest="$IP_ROOT/intelligence.yaml"
32
+ lock="$IP_ROOT/intelligence.lock"
33
+
34
+ stamp="$(read_schema_version "$manifest")"
35
+ eng="$(bundled_engine_version)"
36
+ if [ -z "$stamp" ]; then
37
+ warn "manifest has no schema_version — run 'intelligence init'"
38
+ elif _ver_gt "$stamp" "$eng"; then
39
+ warn "manifest schema $stamp is newer than this CLI's engine $eng — update the CLI: npm i -g @ainova-systems/intelligence@latest"
40
+ elif _ver_gt "$eng" "$stamp"; then
41
+ warn "manifest schema $stamp behind engine $eng — run 'intelligence init'"
42
+ else
43
+ ok "schema_version $stamp matches the engine"
44
+ fi
45
+
46
+ # The engine-content package must sit at the bundled engine version — the
47
+ # content documents exactly the scripts the CLI executes.
48
+ sync_locked="$(qmap_field "$lock" "packages" "$SYNC_PKG_NAME" "resolved")"
49
+ if [ -n "$sync_locked" ]; then
50
+ if [ "${sync_locked#v}" = "$eng" ]; then
51
+ ok "$SYNC_PKG_NAME at $sync_locked (matches the engine)"
52
+ else
53
+ warn "$SYNC_PKG_NAME locked at $sync_locked but the engine is $eng — run 'intelligence init'"
54
+ fi
55
+ fi
56
+ if [ -d "$IP_ROOT/.intelligence/engine" ]; then
57
+ warn "stale .intelligence/engine directory (pre-package layout) — run 'intelligence init'"
58
+ fi
59
+
60
+ # Manifest packages: names valid, requested intent only, locked and installed.
61
+ # The lock is authoritative for resolved source URL/path until an explicit
62
+ # package re-add changes that trust decision.
63
+ while IFS= read -r name; do
64
+ [ -n "$name" ] || continue
65
+ assert_valid_pkg_name "$name"
66
+ if [ -z "$(qmap_field "$lock" "packages" "$name" "url")" ]; then
67
+ warn "$name is in the manifest but not in intelligence.lock — run 'intelligence init'"
68
+ else
69
+ m_ver="$(qmap_field "$manifest" "packages" "$name" "version")"
70
+ m_ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
71
+ l_req="$(qmap_field "$lock" "packages" "$name" "requested")"
72
+ l_res="$(qmap_field "$lock" "packages" "$name" "resolved")"
73
+ [ -z "$m_ver" ] || [ "$m_ver" = "$l_req" ] \
74
+ || warn "$name requests '$m_ver' but the lock recorded '$l_req' — run 'intelligence update --preview'"
75
+ [ -z "$m_ref" ] || [ "$m_ref" = "$l_res" ] \
76
+ || warn "$name pins ref '$m_ref' but the lock resolved '$l_res'"
77
+ if [ -n "$(qmap_field "$manifest" "packages" "$name" "url")" ] \
78
+ || [ -n "$(qmap_field "$manifest" "packages" "$name" "path")" ]; then
79
+ warn "$name stores source details in the manifest — run 'intelligence init --apply' to move them to the lock-only model"
80
+ fi
81
+ if [ ! -d "$IP_ROOT/.intelligence/packages/$name" ]; then
82
+ warn "$name is locked but not installed — run 'intelligence sync'"
83
+ continue
84
+ fi
85
+ ok "$name @ $(qmap_field "$lock" "packages" "$name" "resolved")"
86
+ fi
87
+ done < <(qmap_keys "$manifest" "packages")
88
+
89
+ # Lock orphans.
90
+ if [ -f "$lock" ]; then
91
+ while IFS= read -r name; do
92
+ [ -n "$name" ] || continue
93
+ found=0
94
+ while IFS= read -r m; do
95
+ [ "$m" = "$name" ] && found=1
96
+ done < <(qmap_keys "$manifest" "packages")
97
+ [ "$found" -eq 1 ] || warn "$name is locked but absent from the manifest — 'intelligence package remove $name' cleans it up"
98
+ done < <(qmap_keys "$lock" "packages")
99
+ fi
100
+
101
+ # Sources must exist (a missing dir is silently skipped by the engine, which
102
+ # is exactly why doctor names it).
103
+ for section in rules agents skills; do
104
+ while IFS= read -r src; do
105
+ [ -n "$src" ] || continue
106
+ case "$src" in
107
+ git+*|@*) continue ;;
108
+ esac
109
+ if [ ! -d "$IP_ROOT/$src" ]; then
110
+ case "$src" in
111
+ # Store content is restorable state: absent means un-installed.
112
+ .intelligence/*) warn "sources.$section '$src' missing — run 'intelligence sync'" ;;
113
+ # A project-owned dir that does not exist yet is a legitimate
114
+ # state — a project consuming only packages authors nothing of
115
+ # its own — and the engine simply skips it. Declared up front so
116
+ # authoring later needs no config edit.
117
+ *) ok "sources.$section '$src' — not created yet (optional)" ;;
118
+ esac
119
+ fi
120
+ done < <(read_yaml_list "$manifest" "$section")
121
+ done
122
+
123
+ if [ "$problems" -eq 0 ]; then
124
+ echo "all good."
125
+ else
126
+ echo "$problems problem(s)."
127
+ exit 1
128
+ fi