@ainova-systems/intelligence 0.11.6 → 0.11.8

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.
@@ -124,7 +124,9 @@ case "$IP_MODE" in
124
124
  if [ "$preview" -eq 1 ]; then
125
125
  check_version_compat "$IP_ROOT/intelligence.yaml"
126
126
  echo "project: Intelligence CLI at $IP_ROOT"
127
- if project_needs_upgrade "$IP_ROOT"; then
127
+ if project_stamped_ahead "$IP_ROOT"; then
128
+ echo " project schema $(read_schema_version "$IP_ROOT/intelligence.yaml") is ahead of engine $(bundled_engine_version) — left as is (update the CLI)"
129
+ elif project_needs_upgrade "$IP_ROOT"; then
128
130
  echo " would align project lifecycle with engine $(bundled_engine_version)"
129
131
  else
130
132
  echo " schema/content already match engine $(bundled_engine_version)"
@@ -48,7 +48,9 @@ fi
48
48
 
49
49
  echo ""
50
50
  echo "Project:"
51
- if project_needs_upgrade "$IP_ROOT"; then
51
+ if project_stamped_ahead "$IP_ROOT"; then
52
+ echo " schema $stamp is ahead of engine $eng — left as is; update the CLI to align it"
53
+ elif project_needs_upgrade "$IP_ROOT"; then
52
54
  echo " lifecycle alignment required (stamp ${stamp:-unstamped}, engine $eng)"
53
55
  project_change=1
54
56
  else
@@ -24,8 +24,10 @@ legacy_key_count="$(awk '/^sync_version:[[:space:]]*/ { n++ } END { print n + 0
24
24
  eng="$(bundled_engine_version)"
25
25
  [ "$schema_key_count" -le 1 ] || die "manifest has duplicate schema_version keys"
26
26
  [ "$legacy_key_count" -le 1 ] || die "manifest has duplicate sync_version keys"
27
+ # Alignment only ever moves a project UP to this engine. A project stamped
28
+ # ahead is never restamped downward, whatever the gap.
27
29
  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"
30
+ die "manifest schema $stamp is newer than this CLI's engine $eng — refusing to align it downward; update the global CLI first"
29
31
  fi
30
32
  if [ -n "$legacy_stamp" ] && _ver_gt "$legacy_stamp" "$eng"; then
31
33
  die "manifest schema $legacy_stamp is newer than this CLI's engine $eng — update the global CLI first"
@@ -5,6 +5,7 @@ source "$CLI_DIR/lib/cli-common.sh"
5
5
 
6
6
  problems=0
7
7
  warn() { echo " ✗ $*"; problems=$((problems + 1)); }
8
+ note() { echo " ! $*"; }
8
9
  ok() { echo " ✓ $*"; }
9
10
 
10
11
  echo "environment:"
@@ -36,7 +37,11 @@ eng="$(bundled_engine_version)"
36
37
  if [ -z "$stamp" ]; then
37
38
  warn "manifest has no schema_version — run 'intelligence init'"
38
39
  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
+ if [ "$(_ver_major "$stamp")" -gt "$(_ver_major "$eng")" ]; then
41
+ warn "manifest schema $stamp is a newer major than this CLI's engine $eng — update the CLI: npm i -g @ainova-systems/intelligence@latest"
42
+ else
43
+ note "manifest schema $stamp is newer than this CLI's engine $eng — the project uses a newer CLI; update it: npm i -g @ainova-systems/intelligence@latest"
44
+ fi
40
45
  elif _ver_gt "$eng" "$stamp"; then
41
46
  warn "manifest schema $stamp behind engine $eng — run 'intelligence init'"
42
47
  else
@@ -49,6 +54,8 @@ sync_locked="$(qmap_field "$lock" "packages" "$SYNC_PKG_NAME" "resolved")"
49
54
  if [ -n "$sync_locked" ]; then
50
55
  if [ "${sync_locked#v}" = "$eng" ]; then
51
56
  ok "$SYNC_PKG_NAME at $sync_locked (matches the engine)"
57
+ elif [ "${sync_locked#v}" = "$stamp" ] && _ver_gt "$stamp" "$eng"; then
58
+ note "$SYNC_PKG_NAME at $sync_locked (matches the project schema; the engine is $eng)"
52
59
  else
53
60
  warn "$SYNC_PKG_NAME locked at $sync_locked but the engine is $eng — run 'intelligence init'"
54
61
  fi
@@ -2,8 +2,8 @@
2
2
  # Internal locked-store restore operation.
3
3
  #
4
4
  # Restore .intelligence/ from intelligence.lock — the fresh-clone command.
5
- # --frozen additionally refuses any manifest/lock divergence (CI mode) and
6
- # fails on a sha mismatch instead of re-resolving.
5
+ # --frozen additionally refuses manifest/lock divergence (CI mode).
6
+ # Locked packages always restore their recorded commit, never the ref's new HEAD.
7
7
  set -euo pipefail
8
8
  source "$CLI_DIR/lib/cli-common.sh"
9
9
 
@@ -50,6 +50,20 @@ if [ "$frozen" -eq 1 ]; then
50
50
  done < <(qmap_keys "$lock" "packages")
51
51
  fi
52
52
 
53
+ # Check every required commit identity before restoring any package. Only the
54
+ # matching bundled content may carry the historical empty development-build SHA.
55
+ while IFS="$LOCK_SEP" read -r name _requested url path resolved sha; do
56
+ [ -n "$name" ] || continue
57
+ assert_valid_pkg_name "$name"
58
+ assert_safe_source_url "$url"
59
+ assert_safe_ref "$resolved"
60
+ if [ -z "$sha" ] && is_bundle_source "$url" "$resolved" "$path"; then
61
+ continue
62
+ fi
63
+ [[ "$sha" =~ ^[0-9a-f]{40}$ || "$sha" =~ ^[0-9a-f]{64}$ ]] \
64
+ || die "locked restore: $name has a missing or invalid commit SHA — restore a valid intelligence.lock"
65
+ done < <(lock_to_tsv "$lock")
66
+
53
67
  # Manifest packages missing from the lock: resolve them now (npm install
54
68
  # semantics). Under --frozen the drift gate above has already refused.
55
69
  missing=""
@@ -89,7 +103,7 @@ fi
89
103
 
90
104
  # Restore every locked package that is not already in the store.
91
105
  if [ -f "$lock" ]; then
92
- while IFS="$LOCK_SEP" read -r name requested url path resolved sha; do
106
+ while IFS="$LOCK_SEP" read -r name _requested url path resolved sha; do
93
107
  [ -n "$name" ] || continue
94
108
  # The lock arrives with a cloned repo — its keys are untrusted input
95
109
  # on their way into rm -rf paths, and its urls into git argv.
@@ -106,17 +120,7 @@ if [ -f "$lock" ]; then
106
120
  # re-run (whose dir-exists check skips verification) would sync it.
107
121
  staging="$IP_ROOT/.intelligence/.staging-$$"
108
122
  rm -rf "$staging"
109
- got="$(fetch_package "$url" "$resolved" "$path" "$staging")"
110
- # An empty sha (bundle-seeded content on a dev build) means unknown,
111
- # not moved — only two known-and-different shas are a finding.
112
- if [ -n "$sha" ] && [ -n "$got" ] && [ "$got" != "$sha" ]; then
113
- if [ "$frozen" -eq 1 ]; then
114
- rm -rf "$staging"
115
- die "$name: fetched $got but the lock pins $sha — the ref moved; refusing under --frozen"
116
- fi
117
- echo " WARN: $name resolved to $got, lock had $sha — updating the lock" >&2
118
- lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$resolved" "$got"
119
- fi
123
+ fetch_package "$url" "$resolved" "$path" "$staging" "$sha" >/dev/null
120
124
  rm -rf "${IP_ROOT:?}/$rel"
121
125
  mkdir -p "$(dirname "$IP_ROOT/$rel")"
122
126
  mv "$staging" "$IP_ROOT/$rel"
@@ -239,12 +239,26 @@ assert_safe_content_dir() {
239
239
  done
240
240
  }
241
241
 
242
+ # project_stamped_ahead <root> — the manifest is stamped newer than this CLI's
243
+ # engine, at any SemVer level. Every caller runs check_version_compat first,
244
+ # which has already refused a newer major, so what reaches this predicate is
245
+ # the admitted minor/patch gap; the predicate itself does not re-judge the gap.
246
+ project_stamped_ahead() {
247
+ local stamp
248
+ stamp="$(read_schema_version "$1/intelligence.yaml")"
249
+ [ -n "$stamp" ] && _ver_gt "$stamp" "$(bundled_engine_version)"
250
+ }
251
+
242
252
  project_needs_upgrade() {
243
253
  local root="$1" manifest="$1/intelligence.yaml" stamp eng pinned locked name
244
254
  [ -f "$manifest" ] || return 1
245
255
  stamp="$(read_schema_version "$manifest")"
246
256
  eng="$(bundled_engine_version)"
247
257
  [ -z "$stamp" ] && return 0
258
+ # A project stamped ahead belongs to a newer CLI. Aligning it here would
259
+ # restamp the schema and re-pin the engine content DOWNWARD, and the next
260
+ # teammate on the current CLI would move both back: leave it as found.
261
+ project_stamped_ahead "$root" && return 1
248
262
  [ -n "$(top_scalar "$manifest" "sync_version")" ] && return 0
249
263
  _ver_gt "$eng" "$stamp" && return 0
250
264
  [ -d "$root/.intelligence/engine" ] && return 0
@@ -106,13 +106,20 @@ suggest_similar() {
106
106
  fi
107
107
  }
108
108
 
109
- # fetch_package <url> <ref> <subpath> <dest-dir>
109
+ # is_bundle_source <url> <ref> <subpath>
110
+ is_bundle_source() {
111
+ [ "$1" = "$SYNC_PKG_URL" ] && [ "$3" = "$SYNC_PKG_PATH" ] \
112
+ && [ "${2#v}" = "$(bundled_engine_version)" ]
113
+ }
114
+
115
+ # fetch_package <url> <ref> <subpath> <dest-dir> [locked-sha]
110
116
  # Shallow-clones url@ref (tag, branch, or SHA fallback), copies <subpath>
111
117
  # (or the repo root) into <dest-dir>, prints the resolved commit sha.
118
+ # A locked SHA selects and verifies the commit independently of the ref name.
112
119
  # The clone pins autocrlf/eol/symlinks exactly like the engine does, so the
113
120
  # store holds the bytes the package published.
114
121
  fetch_package() {
115
- local url="$1" ref="$2" subpath="$3" dest="$4"
122
+ local url="$1" ref="$2" subpath="$3" dest="$4" locked_sha="${5:-}" bundle_sha=""
116
123
  assert_safe_source_url "$url"
117
124
  assert_safe_ref "$ref"
118
125
  # Bundle seed: the engine-content package at the CLI's own version copies
@@ -120,34 +127,63 @@ fetch_package() {
120
127
  # archived-project conversion offline. Keyed on the
121
128
  # full (url, path, ref) triple; any other version or source falls through
122
129
  # to the normal clone.
123
- if [ "$url" = "$SYNC_PKG_URL" ] && [ "$subpath" = "$SYNC_PKG_PATH" ] \
124
- && [ "${ref#v}" = "$(bundled_engine_version)" ]; then
130
+ if is_bundle_source "$url" "$ref" "$subpath"; then
131
+ if [ -f "$IS_ENGINE_DIR/ENGINE_SHA" ]; then
132
+ bundle_sha="$(tr -d ' \t\r\n' < "$IS_ENGINE_DIR/ENGINE_SHA")"
133
+ fi
134
+ fi
135
+ if is_bundle_source "$url" "$ref" "$subpath" \
136
+ && { [ -z "$locked_sha" ] || [ -z "$bundle_sha" ] || [ "$locked_sha" = "$bundle_sha" ]; }; then
125
137
  [ -n "${IS_BUNDLED_PKG_DIR:-}" ] && [ -d "$IS_BUNDLED_PKG_DIR" ] \
126
138
  || die "bundled engine content not found next to the CLI — reinstall @ainova-systems/intelligence"
127
139
  rm -rf "$dest"
128
140
  mkdir -p "$dest"
129
141
  cp -R "$IS_BUNDLED_PKG_DIR/." "$dest/"
130
142
  rm -rf "$dest/.git"
131
- if [ -f "$IS_ENGINE_DIR/ENGINE_SHA" ]; then
132
- tr -d ' \t\r\n' < "$IS_ENGINE_DIR/ENGINE_SHA"
143
+ if [ "$#" -ge 5 ] && { [ -z "$locked_sha" ] || [ -z "$bundle_sha" ]; }; then
144
+ echo "WARNING: bundled sync content restored without commit verification (development bundle or lock has no SHA)" >&2
133
145
  fi
146
+ printf '%s' "$bundle_sha"
134
147
  return 0
135
148
  fi
136
149
  local tmp sha src
137
150
  local -a branch_arg=()
138
151
  [ -n "$ref" ] && branch_arg=(--branch "$ref")
139
152
  tmp="$(mktemp -d -t intelligence-pkg-XXXXXX 2>/dev/null || mktemp -d)"
140
- if ! GIT_TERMINAL_PROMPT=0 git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
153
+ if [ -n "$locked_sha" ]; then
154
+ local -a format_arg=()
155
+ [ "${#locked_sha}" -ne 64 ] || format_arg=(--object-format=sha256)
156
+ git init --quiet "${format_arg[@]+"${format_arg[@]}"}" "$tmp/clone" \
157
+ || { rm -rf "$tmp"; die "cannot stage locked commit $locked_sha"; }
158
+ # Request the object directly, including retained commits no longer named
159
+ # by a branch/tag. Servers that forbid SHA wants may still advertise a
160
+ # descendant; fetch their history before declaring the commit unavailable.
161
+ if ! GIT_TERMINAL_PROMPT=0 git -C "$tmp/clone" fetch --quiet --no-tags --depth 1 -- "$url" "$locked_sha" 2>/dev/null; then
162
+ GIT_TERMINAL_PROMPT=0 git -C "$tmp/clone" fetch --quiet --tags -- "$url" \
163
+ '+refs/heads/*:refs/remotes/locked/*' 2>/dev/null \
164
+ || { rm -rf "$tmp"; die "cannot fetch locked commit $locked_sha from $url — check source access and commit availability"; }
165
+ fi
166
+ [ "$(git -C "$tmp/clone" cat-file -t "$locked_sha" 2>/dev/null || true)" = commit ] \
167
+ || { rm -rf "$tmp"; die "locked commit $locked_sha unavailable from $url — refusing to substitute '$ref' or HEAD"; }
168
+ git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
169
+ -C "$tmp/clone" checkout --quiet --detach "$locked_sha" -- \
170
+ || { rm -rf "$tmp"; die "cannot check out locked commit $locked_sha from $url"; }
171
+ elif ! GIT_TERMINAL_PROMPT=0 git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
141
172
  clone --depth 1 "${branch_arg[@]+"${branch_arg[@]}"}" --quiet -- "$url" "$tmp/clone" 2>/dev/null; then
142
173
  # A SHA is not clonable with --branch — fall back to a full clone + checkout.
143
174
  rm -rf "$tmp/clone"
144
175
  GIT_TERMINAL_PROMPT=0 git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
145
176
  clone --quiet -- "$url" "$tmp/clone" 2>/dev/null \
146
177
  || { rm -rf "$tmp"; die "cannot clone $url"; }
147
- [ -z "$ref" ] || git -C "$tmp/clone" checkout --quiet "$ref" -- \
178
+ [ -z "$ref" ] || git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
179
+ -C "$tmp/clone" checkout --quiet "$ref" -- \
148
180
  || { rm -rf "$tmp"; die "ref '$ref' not found in $url"; }
149
181
  fi
150
182
  sha="$(git -C "$tmp/clone" rev-parse HEAD)"
183
+ if [ -n "$locked_sha" ] && [ "$sha" != "$locked_sha" ]; then
184
+ rm -rf "$tmp"
185
+ die "fetched $sha but the lock pins $locked_sha — refusing package replacement"
186
+ fi
151
187
  src="$tmp/clone"
152
188
  if [ -n "$subpath" ]; then
153
189
  case "$subpath" in
package/engine/ENGINE_SHA CHANGED
@@ -1 +1 @@
1
- 512d06382f43fa910c32ccc275a5fc286500d4e2
1
+ 3413f7d6f9ef181368b16b90e36d77360cb5f479
package/engine/VERSION CHANGED
@@ -1 +1 @@
1
- 0.11.6
1
+ 0.11.8
@@ -100,21 +100,41 @@ _ver_gt() {
100
100
  return 1
101
101
  }
102
102
 
103
- # check_version_compat <config_file> — refuse to operate on a project whose
104
- # config schema is stamped newer than this engine knows (a stale engine must
105
- # never rewrite/sync a newer schema). Emits status + returns IS_RC_AHEAD on
106
- # conflict, else 0.
103
+ # _ver_major V → the leading numeric field of a version ("0.11.6" → 0).
104
+ _ver_major() {
105
+ local m="${1%%.*}"
106
+ m="${m//[!0-9]/}"
107
+ printf '%s' "$((10#${m:-0}))"
108
+ }
109
+
110
+ # check_version_compat <config_file> — a stale engine on a project stamped
111
+ # NEWER than itself. The gap decides:
112
+ # * a newer MAJOR is refused: the manifest may carry shapes this engine
113
+ # cannot read, and rendering or rewriting it would destroy them. Emits
114
+ # status + returns IS_RC_AHEAD;
115
+ # * a newer minor or patch within the same major warns once and returns 0:
116
+ # the schema is additive there, so an older engine renders what it
117
+ # understands. It never restamps the project — the CLI's preflight leaves
118
+ # a project stamped ahead exactly as it found it (project_needs_upgrade).
107
119
  check_version_compat() {
108
120
  local cf="$1" stamp eng
109
121
  stamp="$(read_schema_version "$cf")"
110
122
  [ -n "$stamp" ] || return 0
111
123
  eng="$(engine_version)"
112
124
  [ -n "$eng" ] || return 0
113
- if _ver_gt "$stamp" "$eng"; then
125
+ _ver_gt "$stamp" "$eng" || return 0
126
+ if [ "$(_ver_major "$stamp")" -gt "$(_ver_major "$eng")" ]; then
114
127
  is_status ahead-of-engine "stamp=$stamp engine=$eng"
115
- echo " ERROR: project stamped $stamp but this engine is $eng — refusing." >&2
128
+ echo " ERROR: project stamped $stamp but this engine is $eng — a newer major schema; refusing." >&2
116
129
  echo " Update the CLI first: npm i -g @ainova-systems/intelligence@latest" >&2
117
130
  return "$IS_RC_AHEAD"
118
131
  fi
132
+ # One warning per command, however many processes re-check on the way to
133
+ # the engine (CLI preflight first, then the engine itself). The line has
134
+ # no indent on purpose: `sync --compact` keeps `WARNING:` lines.
135
+ if [ "${IS_SCHEMA_AHEAD_WARNED:-}" != "$stamp" ]; then
136
+ echo "WARNING: project schema $stamp is newer than this CLI's engine $eng — the project uses a newer CLI; update it: npm i -g @ainova-systems/intelligence@latest" >&2
137
+ export IS_SCHEMA_AHEAD_WARNED="$stamp"
138
+ fi
119
139
  return 0
120
140
  }
package/engine/sync.sh CHANGED
@@ -31,7 +31,8 @@ fi
31
31
  # Schema version lives in the manifest (the frozen contract key).
32
32
  _cf="$CONFIG_FILE"
33
33
 
34
- # Stale engine vs project schema stamped NEWER (ahead-of-engine) → refuse.
34
+ # Stale engine vs project schema stamped NEWER (ahead-of-engine): a newer
35
+ # major refuses, a newer minor/patch warns and renders (see contract.sh).
35
36
  _vc_rc=0
36
37
  check_version_compat "$_cf" || _vc_rc=$?
37
38
  if [ "$_vc_rc" -ne 0 ]; then exit "$_vc_rc"; fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainova-systems/intelligence",
3
- "version": "0.11.6",
3
+ "version": "0.11.8",
4
4
  "description": "Build, version and distribute AI agent intelligence across your organization — one CLI, versioned Intelligence Packages, and a sync engine for Claude Code, Cursor, Copilot, Codex, Pi and OpenCode.",
5
5
  "bin": {
6
6
  "intelligence": "bin/intelligence.js"
@@ -391,7 +391,7 @@ The public lifecycle is deliberately compact:
391
391
  - `intelligence adapter list|create|enable|disable|remove` owns adapter inventory and target state.
392
392
  - `intelligence status [--check]` reports state; `--check` runs deep consistency checks.
393
393
 
394
- Implement Intelligence schema changes as idempotent structural checks. Stage and verify replacement state before deleting or replacing prior state. A stale engine refuses a manifest whose `schema_version` is newer; normal project entry points close a behind-project gap through lifecycle preflight.
394
+ Implement Intelligence schema changes as idempotent structural checks. Stage and verify replacement state before deleting or replacing prior state. A stale engine refuses a manifest whose `schema_version` is a newer major; a newer minor or patch within the same major warns once and proceeds without restamping the project. Normal project entry points close a behind-project gap through lifecycle preflight.
395
395
 
396
396
  Breaking changelog entries use a `### Breaking` checklist of verifiable post-conditions. The update skill reads every release across the version gap, chooses the package/CLI/project command sequence and verifies those conditions after the deterministic command completes.
397
397
 
@@ -406,7 +406,7 @@ The engine emits one machine-readable line, `IS_STATUS=<code> [IS_DETAIL=...]`,
406
406
  | `error` | 1 | Generic failure |
407
407
  | `config-missing` | 2 | Required manifest is absent |
408
408
  | `ambiguous` | 3 | Conflicting state requiring agent/human judgment; reserved |
409
- | `ahead-of-engine` | 4 | Manifest schema is newer than the engine |
409
+ | `ahead-of-engine` | 4 | Manifest schema is a newer major than the engine (a newer minor or patch warns and continues) |
410
410
  | `aborted-incomplete` | 5 | Staged replacement was incomplete; prior state remains |
411
411
  | `needs-update` | 6 | Project schema is behind the engine; rerun through a public lifecycle command |
412
412
 
@@ -11,12 +11,12 @@ context: fork
11
11
  was requested). For Intelligence projects, this command first aligns project schema/content
12
12
  with the installed CLI and restores a missing package store strictly from
13
13
  `intelligence.lock`.
14
- 2. Require final `IS_STATUS=ok`; relay per-adapter counts and any model-drift
15
- or unsynced-source warnings.
14
+ 2. Require final `IS_STATUS=ok`; relay per-adapter counts and any integrity,
15
+ model-drift or unsynced-source warnings.
16
16
  3. In CI, a required tracked project upgrade is intentionally refused. Report
17
17
  the instruction to run `intelligence init --apply` locally, review and
18
18
  commit its diff; never bypass the gate. A frozen restore refusal means the
19
- manifest and lock disagree or a pinned ref moved—report it without
19
+ manifest and lock disagree or the locked commit cannot be verified/restored—report it without
20
20
  hand-copying package content.
21
21
  4. On a probable Intelligence defect, first ask whether to collect a sanitized
22
22
  issue draft; collect nothing before approval and never use telemetry. Include