@ainova-systems/intelligence 0.11.5 → 0.11.6

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.
@@ -82,7 +82,7 @@ while IFS= read -r name; do
82
82
  warn "$name is locked but not installed — run 'intelligence sync'"
83
83
  continue
84
84
  fi
85
- ok "$name @ $(qmap_field "$lock" "packages" "$name" "resolved")"
85
+ ok "$name @ $(pin_label "$m_ref" "$l_res" "$(qmap_field "$lock" "packages" "$name" "sha")")"
86
86
  fi
87
87
  done < <(qmap_keys "$manifest" "packages")
88
88
 
@@ -14,6 +14,7 @@ while IFS= read -r name; do
14
14
  range="$(qmap_field "$manifest" "packages" "$name" "version")"
15
15
  ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
16
16
  resolved="$(qmap_field "$lock" "packages" "$name" "resolved")"
17
+ sha="$(qmap_field "$lock" "packages" "$name" "sha")"
17
18
  if [ ! -f "$lock" ]; then
18
19
  state="lock missing — restore committed intelligence.lock"
19
20
  elif [ -d "$IP_ROOT/.intelligence/packages/$name" ]; then
@@ -24,7 +25,7 @@ while IFS= read -r name; do
24
25
  printf '%s %s locked:%s %s\n' \
25
26
  "$name" \
26
27
  "${ref:+ref:$ref}${ref:-${range:-*}}" \
27
- "${resolved:-<none>}" \
28
+ "$(pin_label "$ref" "$resolved" "$sha")" \
28
29
  "$state"
29
30
  done < <(qmap_keys "$manifest" "packages")
30
31
 
@@ -22,6 +22,20 @@ require_cli_project
22
22
  manifest="$IP_ROOT/intelligence.yaml"
23
23
  lock="$IP_ROOT/intelligence.lock"
24
24
 
25
+ # move_label <ref-or-empty> <from-resolved> <to-resolved> <from-sha> <to-sha>
26
+ # A version pin moves between tags, a ref pin between commits under one name —
27
+ # printing "main -> main" would report the move by hiding what moved.
28
+ move_label() {
29
+ local ref="$1" from="$2" to="$3" from_sha="$4" to_sha="$5"
30
+ if [ -z "$ref" ]; then
31
+ printf '%s -> %s' "${from:-<none>}" "$to"
32
+ elif [ -n "$from" ] && [ "$from" != "$ref" ]; then
33
+ printf '%s -> %s@%s' "$from" "$ref" "$(short_sha "$to_sha")"
34
+ else
35
+ printf '%s %s -> %s' "$ref" "$(short_sha "$from_sha")" "$(short_sha "$to_sha")"
36
+ fi
37
+ }
38
+
25
39
  moved=0 found=0
26
40
  while IFS= read -r name; do
27
41
  [ -n "$name" ] || continue
@@ -37,6 +51,7 @@ while IFS= read -r name; do
37
51
  fi
38
52
  current="$(qmap_field "$lock" "packages" "$name" "resolved")"
39
53
  locked_requested="$(qmap_field "$lock" "packages" "$name" "requested")"
54
+ locked_sha="$(qmap_field "$lock" "packages" "$name" "sha")"
40
55
  url="$(qmap_field "$lock" "packages" "$name" "url")"
41
56
  path="$(qmap_field "$lock" "packages" "$name" "path")"
42
57
  [ -n "$url" ] || die "$name has no source in intelligence.lock — restore the committed lock or re-add the package"
@@ -44,32 +59,69 @@ while IFS= read -r name; do
44
59
  range="$(qmap_field "$manifest" "packages" "$name" "version")"
45
60
  [ -n "$ref" ] || [ -n "$range" ] || die "$name has neither version nor ref in the manifest"
46
61
 
62
+ ref_moved=0 remote_sha=""
47
63
  if [ -n "$ref" ]; then
48
64
  tag="$ref"
49
65
  requested=""
66
+ # A ref is requested INTENT, and the lock's `resolved` column holds
67
+ # that same ref name — comparing the two asks a question no branch can
68
+ # ever answer differently. The sha column is the only record of where
69
+ # the ref actually pointed, so a ref pin is compared commit to commit:
70
+ # a moved branch, a re-cut tag and a `HEAD` pin all become visible,
71
+ # while a pin that IS a commit stays immutable by construction.
72
+ probe_rc=0
73
+ remote_sha="$(remote_sha_for_ref "$url" "$ref")" || probe_rc=$?
74
+ if [ "$probe_rc" -ne 0 ]; then
75
+ # No answer is not "no change": reporting up to date here would
76
+ # freeze the pin exactly as the ref-name comparison used to.
77
+ echo " WARN: $name: cannot reach $url — ref '$ref' was not checked" >&2
78
+ echo " $name: $ref (not checked — remote unreachable)"
79
+ continue
80
+ fi
81
+ # ls-remote advertises refs, not arbitrary objects, so a commit pin
82
+ # legitimately matches nothing — and cannot move. This verdict applies
83
+ # only once the lock already resolved to this ref; a manifest that now
84
+ # names a ref the lock never resolved is an ordinary move, and falls
85
+ # through to the fetch below.
86
+ if [ -z "$remote_sha" ] && [ "$ref" = "$current" ]; then
87
+ # The lock records the commit this ref resolved to, so a ref that
88
+ # prefixes it IS that commit. Asking the lock beats guessing from
89
+ # the ref's shape: a deleted branch named like a hex string would
90
+ # otherwise pass for a pin and swallow its own disappearance.
91
+ case "$locked_sha" in
92
+ "$ref"*)
93
+ echo " $name: $(short_sha "$ref") (pinned commit)"
94
+ ;;
95
+ *)
96
+ echo " WARN: $name pins ref '$ref', which $url no longer advertises" >&2
97
+ echo " $name: $ref (unresolvable — gone upstream)"
98
+ ;;
99
+ esac
100
+ continue
101
+ fi
102
+ [ -z "$remote_sha" ] || [ "$remote_sha" = "$locked_sha" ] || ref_moved=1
50
103
  else
51
104
  picked="$(list_remote_versions "$url" | semver_pick_highest "$range")"
52
105
  [ -n "$picked" ] || { echo " $name: nothing satisfies '$range' at $url" >&2; continue; }
53
106
  read -r tag _ <<< "$(remote_tag_for_version "$url" "$picked")"
54
107
  requested="$range"
55
108
  fi
56
- if [ "$tag" = "$current" ] && [ "$requested" != "$locked_requested" ]; then
109
+ if [ "$ref_moved" -eq 0 ] && [ "$tag" = "$current" ] && [ "$requested" != "$locked_requested" ]; then
57
110
  if [ "$preview" -eq 1 ]; then
58
111
  echo " $name: request ${locked_requested:-<none>} -> ${requested:-<ref>} (keeps $current)"
59
112
  else
60
- locked_sha="$(qmap_field "$lock" "packages" "$name" "sha")"
61
113
  lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$current" "$locked_sha"
62
114
  echo " $name: request ${locked_requested:-<none>} -> ${requested:-<ref>} (kept $current)"
63
115
  fi
64
116
  moved=$((moved + 1))
65
117
  continue
66
118
  fi
67
- if [ "$tag" = "$current" ]; then
68
- echo " $name: ${current:-<none>} (up to date)"
119
+ if [ "$ref_moved" -eq 0 ] && [ "$tag" = "$current" ]; then
120
+ echo " $name: $(pin_label "$ref" "$current" "$locked_sha") (up to date)"
69
121
  continue
70
122
  fi
71
123
  if [ "$preview" -eq 1 ]; then
72
- echo " $name: ${current:-<none>} -> $tag"
124
+ echo " $name: $(move_label "$ref" "$current" "$tag" "$locked_sha" "$remote_sha")"
73
125
  moved=$((moved + 1))
74
126
  continue
75
127
  fi
@@ -87,7 +139,7 @@ while IFS= read -r name; do
87
139
  mv "$staging" "$IP_ROOT/$rel"
88
140
  wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
89
141
  lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$tag" "$sha"
90
- echo " $name: ${current:-<none>} -> $tag"
142
+ echo " $name: $(move_label "$ref" "$current" "$tag" "$locked_sha" "$sha")"
91
143
  moved=$((moved + 1))
92
144
  done < <(qmap_keys "$manifest" "packages")
93
145
 
@@ -47,6 +47,27 @@ assert_safe_ref() {
47
47
  esac
48
48
  }
49
49
 
50
+ # --- Resolved-pin display ------------------------------------------------
51
+ # A `ref:` pin's resolved column holds the ref NAME, which never changes: the
52
+ # sha is the only field that says which commit is installed. Every surface
53
+ # reporting such a pin prints both, so a branch that stopped moving is visible
54
+ # without asking the remote.
55
+
56
+ short_sha() {
57
+ [ -n "$1" ] || { printf '<none>'; return 0; }
58
+ printf '%s' "${1:0:7}"
59
+ }
60
+
61
+ # pin_label <ref-or-empty> <resolved> <sha>
62
+ pin_label() {
63
+ local ref="$1" resolved="$2" sha="$3"
64
+ if [ -n "$ref" ]; then
65
+ printf '%s@%s' "${resolved:-$ref}" "$(short_sha "$sha")"
66
+ else
67
+ printf '%s' "${resolved:-<none>}"
68
+ fi
69
+ }
70
+
50
71
  source "$CLI_DIR/lib/manifest.sh"
51
72
  source "$CLI_DIR/lib/semver.sh"
52
73
  source "$CLI_DIR/lib/registry.sh"
package/cli/lib/semver.sh CHANGED
@@ -120,6 +120,39 @@ remote_tag_for_version() {
120
120
  }'
121
121
  }
122
122
 
123
+ # remote_sha_for_ref <git-url> <ref> — the commit a branch/tag/HEAD pin points
124
+ # at on the remote right now; empty when the remote advertises no such ref
125
+ # (a pin that IS a commit, or a ref deleted upstream). Exit 2 means the remote
126
+ # could not be asked at all: a check that produced no answer must never be
127
+ # read by the caller as "nothing changed".
128
+ #
129
+ # Three git behaviours this mirrors deliberately, because a sha that is not the
130
+ # one `fetch_package` would land on is worse than no answer:
131
+ # - `git ls-remote <url> main` also matches `refs/heads/feature/main`
132
+ # (patterns match at slash boundaries), so candidates are selected by full
133
+ # ref name, never by the pattern that fetched them;
134
+ # - an annotated tag advertises the tag OBJECT on `refs/tags/<t>` and the
135
+ # commit on the peeled `refs/tags/<t>^{}`, while the lock records the
136
+ # commit — so peeled wins, and the peel line only appears when its own
137
+ # pattern is asked for;
138
+ # - on a name shared by a branch and a tag, `git clone --branch <ref>` takes
139
+ # the branch, so `refs/heads` outranks `refs/tags` here too.
140
+ remote_sha_for_ref() {
141
+ local url="$1" ref="$2" out
142
+ assert_safe_source_url "$url"
143
+ assert_safe_ref "$ref"
144
+ [ -n "$ref" ] || return 0
145
+ out="$(GIT_TERMINAL_PROMPT=0 git ls-remote -- "$url" \
146
+ "$ref" "$ref^{}" "refs/heads/$ref" "refs/tags/$ref" "refs/tags/$ref^{}" 2>/dev/null)" \
147
+ || return 2
148
+ printf '%s\n' "$out" | awk -v ref="$ref" '
149
+ { sub(/\r$/, ""); if ($2 != "") seen[$2] = $1 }
150
+ END {
151
+ split("refs/heads/" ref "\037refs/tags/" ref "^{}\037refs/tags/" ref "\037" ref "^{}\037" ref, C, "\037")
152
+ for (i = 1; i <= 5; i++) if (C[i] in seen) { print seen[C[i]]; exit }
153
+ }'
154
+ }
155
+
123
156
  # semver_pick_highest <range> — reads versions on stdin, prints the highest
124
157
  # one matching the range (empty output when nothing matches).
125
158
  semver_pick_highest() {
package/engine/ENGINE_SHA CHANGED
@@ -1 +1 @@
1
- bd688bcdb5443d808a72ed4e63805a65255a7d02
1
+ 512d06382f43fa910c32ccc275a5fc286500d4e2
package/engine/VERSION CHANGED
@@ -1 +1 @@
1
- 0.11.5
1
+ 0.11.6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainova-systems/intelligence",
3
- "version": "0.11.5",
3
+ "version": "0.11.6",
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"