@ainova-systems/intelligence 0.11.0-rc.5 → 0.11.0-rc.7
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 +2 -2
- package/cli/commands/add.sh +26 -20
- package/cli/commands/init.sh +24 -9
- package/cli/commands/install.sh +55 -4
- package/cli/commands/migrate.sh +62 -9
- package/cli/commands/registry.sh +2 -9
- package/cli/commands/search.sh +4 -6
- package/cli/commands/sync.sh +1 -1
- package/cli/commands/update.sh +29 -5
- package/cli/engine-package.yaml +14 -0
- package/cli/intelligence +27 -15
- package/cli/lib/cli-common.sh +52 -17
- package/cli/lib/lockfile.sh +14 -8
- package/cli/lib/manifest.sh +59 -1
- package/cli/lib/registry.sh +41 -55
- package/cli/lib/semver.sh +4 -2
- package/engine/ENGINE_SHA +1 -0
- package/engine/{scripts/adapters → adapters}/agents.sh +10 -15
- package/engine/{scripts/lib → lib}/common.sh +15 -519
- package/engine/lib/contract.sh +120 -0
- package/engine/sync.sh +233 -0
- package/package.json +5 -5
- package/engine/INIT.md +0 -500
- package/engine/docs/CLI.md +0 -94
- package/engine/scripts/ENGINE_SHA +0 -1
- package/engine/scripts/lib/layout.sh +0 -51
- package/engine/scripts/lib/migrations.sh +0 -708
- package/engine/scripts/sync.sh +0 -311
- package/engine/scripts/update.sh +0 -237
- package/registry/index.yaml +0 -26
- /package/engine/{scripts/VERSION → VERSION} +0 -0
- /package/engine/{scripts/adapters → adapters}/_template.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/claude.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/codex.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/copilot.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/cursor.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/opencode.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/pi.sh +0 -0
- /package/{engine → packages/sync}/agents/intelligence-architect.md +0 -0
- /package/{engine → packages/sync}/agents/intelligence-operator.md +0 -0
- /package/{engine → packages/sync}/docs/ADAPTERS.md +0 -0
- /package/{engine → packages/sync}/docs/CONVENTIONS.md +0 -0
- /package/{engine → packages/sync}/rules/intelligence-authoring.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-add-agent/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-add-rule/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-add-skill/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-extract-skill/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-install-adapter/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-learn-from-context/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-review-skills/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-sync/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-uninstall-adapter/SKILL.md +0 -0
- /package/{engine → packages/sync}/skills/intelligence-update/SKILL.md +0 -0
package/README.md
CHANGED
|
@@ -38,9 +38,9 @@ intelligence install
|
|
|
38
38
|
| `intelligence update [pkg]` | Re-resolve version ranges, rewrite the lock |
|
|
39
39
|
| `intelligence sync [target]` | Render intelligence to every enabled tool |
|
|
40
40
|
| `intelligence list` / `doctor` / `status` | Inspect and verify |
|
|
41
|
-
| `intelligence registry add
|
|
41
|
+
| `intelligence registry add <repo-url>` | Trust a registry (a git repo with an `index.yaml`) |
|
|
42
42
|
| `intelligence migrate` | Convert a vendored intelligence-sync setup to the CLI |
|
|
43
43
|
|
|
44
|
-
Any git repo with `rules/`, `agents/` or `skills/` at its root is already an Intelligence Package: `intelligence add
|
|
44
|
+
Any git repo with `rules/`, `agents/` or `skills/` at its root is already an Intelligence Package: install it from an explicit source — `intelligence add github:your-org/your-repo` — with versions from its `x.y.z` git tags. Names (`@scope/name`) resolve only through registries this project explicitly trusts; a monorepo of packages needs one line in such an index.
|
|
45
45
|
|
|
46
46
|
Requires `git` (and on Windows the bash it ships with — Git for Windows). Engine and docs: [ainova-systems/intelligence-sync](https://github.com/ainova-systems/intelligence-sync).
|
package/cli/commands/add.sh
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
# intelligence add <spec> [--name @scope/name] [--no-sync]
|
|
3
3
|
#
|
|
4
4
|
# Specs:
|
|
5
|
-
# @scope/name[@range] resolved
|
|
6
|
-
# github:org/repo[#path] direct from GitHub, no registry involved
|
|
7
|
-
# git+<url>[@ref][#path] direct from any git host
|
|
5
|
+
# @scope/name[@range] resolved ONLY through the manifest's trusted registries
|
|
6
|
+
# github:org/repo[#path] explicit source: direct from GitHub, no registry involved
|
|
7
|
+
# git+<url>[@ref][#path] explicit source: direct from any git host
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
10
10
|
|
|
@@ -43,14 +43,15 @@ case "$spec" in
|
|
|
43
43
|
rest="${spec#git+}"
|
|
44
44
|
case "$rest" in *#*) path="${rest#*#}"; rest="${rest%%#*}" ;; esac
|
|
45
45
|
# ref = the part after the last `@` in the post-scheme URL, unless it
|
|
46
|
-
# contains `/`
|
|
47
|
-
# git
|
|
46
|
+
# contains `/` or `:` — either marks a user@host part (scp-like
|
|
47
|
+
# `git@host:repo.git` included), not a ref. A branch name containing
|
|
48
|
+
# `/` cannot be expressed here; pin it via `ref:` in the manifest.
|
|
48
49
|
base="${rest#*://}"
|
|
49
50
|
case "$base" in
|
|
50
51
|
*@*)
|
|
51
52
|
tail="${base##*@}"
|
|
52
53
|
case "$tail" in
|
|
53
|
-
|
|
54
|
+
*/*|*:*) ;;
|
|
54
55
|
*) ref="$tail"; rest="${rest%@"$tail"}" ;;
|
|
55
56
|
esac
|
|
56
57
|
;;
|
|
@@ -78,23 +79,24 @@ assert_valid_pkg_name "$name"
|
|
|
78
79
|
if [ "$mode" = "registry" ]; then
|
|
79
80
|
resolve_package_source "$manifest" "$name"
|
|
80
81
|
url="$RES_URL"; path="$RES_PATH"
|
|
82
|
+
if [ -z "$url" ]; then
|
|
83
|
+
echo "ERROR: no trusted registry declares '$name'." >&2
|
|
84
|
+
echo " A name installs only through a registry this project added explicitly" >&2
|
|
85
|
+
echo " ('intelligence registry list' shows them). Either add its registry:" >&2
|
|
86
|
+
echo " intelligence registry add <registry-repo-url>" >&2
|
|
87
|
+
echo " or install from an explicit source you name yourself:" >&2
|
|
88
|
+
echo " intelligence add github:org/repo | intelligence add git+<url>[@ref][#path]" >&2
|
|
89
|
+
suggest_similar "$manifest" "$name"
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
81
92
|
echo " resolving $name via $RES_VIA -> $url${path:+ ($path)}"
|
|
82
93
|
fi
|
|
83
94
|
|
|
84
|
-
# The source must exist before anything else is attempted —
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
if ! GIT_TERMINAL_PROMPT=0 git ls-remote "$url" >/dev/null 2>&1; then
|
|
95
|
+
# The source must exist before anything else is attempted — never die
|
|
96
|
+
# mid-pipeline with no message.
|
|
97
|
+
if ! GIT_TERMINAL_PROMPT=0 git ls-remote -- "$url" >/dev/null 2>&1; then
|
|
88
98
|
echo "ERROR: no reachable repository at $url" >&2
|
|
89
|
-
|
|
90
|
-
echo " No registry declares '$name', so it resolved by convention to github.com/${name#@}." >&2
|
|
91
|
-
echo " Either the name is misspelled ('intelligence search' lists what registries offer)," >&2
|
|
92
|
-
echo " the repo is private (check your git credentials), or its registry is not added yet:" >&2
|
|
93
|
-
echo " intelligence registry add <registry-repo-url>" >&2
|
|
94
|
-
suggest_similar "$manifest" "$name"
|
|
95
|
-
else
|
|
96
|
-
echo " Declared by ${RES_VIA:-the manifest} — check the URL and your git access." >&2
|
|
97
|
-
fi
|
|
99
|
+
echo " Declared by ${RES_VIA:-the spec} — check the URL and your git access." >&2
|
|
98
100
|
exit 1
|
|
99
101
|
fi
|
|
100
102
|
|
|
@@ -124,7 +126,11 @@ sha="$(fetch_package "$url" "${ref:-$resolved_tag}" "$path" "$IP_ROOT/$rel")"
|
|
|
124
126
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
125
127
|
|
|
126
128
|
# Manifest entry: a registry package records its range; a direct one records
|
|
127
|
-
# its source so resolution never has to guess it again.
|
|
129
|
+
# its source so resolution never has to guess it again. The entry is replaced
|
|
130
|
+
# WHOLE — re-adding under a different spec must not leave the old spec's
|
|
131
|
+
# fields behind (a stale ref: would freeze update, a stale url: would pin the
|
|
132
|
+
# old source).
|
|
133
|
+
qmap_delete_key "$manifest" "packages" "$name"
|
|
128
134
|
if [ "$mode" = "registry" ]; then
|
|
129
135
|
qmap_set "$manifest" "packages" "$name" "version" "$requested"
|
|
130
136
|
else
|
package/cli/commands/init.sh
CHANGED
|
@@ -33,15 +33,24 @@ esac
|
|
|
33
33
|
root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
34
34
|
root="$(cd "$root" && pwd)"
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
# Never invent a tool the project shows no trace of: every per-tool target is
|
|
37
|
+
# DETECTED from repo markers or named explicitly via --targets. Only `agents`
|
|
38
|
+
# (AGENTS.md, the tool-neutral standard every AGENTS-reading tool shares, and
|
|
39
|
+
# the invariant-required carrier once any of them is on) is unconditional.
|
|
40
|
+
targets="agents"
|
|
37
41
|
if [ -n "$targets_arg" ]; then
|
|
38
42
|
targets="agents $(printf '%s' "$targets_arg" | tr ',' ' ')"
|
|
39
43
|
else
|
|
44
|
+
if [ -d "$root/.claude" ] || [ -f "$root/CLAUDE.md" ]; then targets="$targets claude"; fi
|
|
40
45
|
[ -d "$root/.cursor" ] && targets="$targets cursor"
|
|
41
46
|
[ -d "$root/.codex" ] && targets="$targets codex"
|
|
42
47
|
[ -d "$root/.pi" ] && targets="$targets pi"
|
|
43
48
|
[ -d "$root/.opencode" ] && targets="$targets opencode"
|
|
44
49
|
[ -d "$root/.github" ] && [ -d "$root/.github/instructions" ] && targets="$targets copilot"
|
|
50
|
+
if [ "$targets" = "agents" ]; then
|
|
51
|
+
echo " NOTE: no tool markers found (.claude/, CLAUDE.md, .cursor/, …) — only AGENTS.md is enabled." >&2
|
|
52
|
+
echo " Name your tools explicitly: intelligence init --targets claude,cursor (or edit targets: later)" >&2
|
|
53
|
+
fi
|
|
45
54
|
fi
|
|
46
55
|
|
|
47
56
|
manifest="$root/intelligence.yaml"
|
|
@@ -88,14 +97,20 @@ manifest="$root/intelligence.yaml"
|
|
|
88
97
|
echo "# \"@ainova-systems/core\":"
|
|
89
98
|
echo "# version: \"^0.3.0\""
|
|
90
99
|
echo ""
|
|
91
|
-
echo "# Registries are
|
|
92
|
-
echo "#
|
|
93
|
-
echo "#
|
|
94
|
-
echo "#
|
|
95
|
-
echo "# pinned by url+sha in intelligence.lock
|
|
96
|
-
echo "#
|
|
97
|
-
echo "#
|
|
98
|
-
|
|
100
|
+
echo "# Registries are this project's TRUST LIST — the ONLY way a package name"
|
|
101
|
+
echo "# resolves (there is no built-in catalog and no name->github guessing;"
|
|
102
|
+
echo "# registry-less installs are always explicit: github:org/repo, git+<url>)."
|
|
103
|
+
echo "# Git repos holding an index.yaml, consulted in order; first to declare a"
|
|
104
|
+
echo "# name wins; sources are then pinned by url+sha in intelligence.lock."
|
|
105
|
+
echo "# Managed with 'intelligence registry add|remove'. Delete a line to"
|
|
106
|
+
echo "# distrust that catalog — including the default seeded below."
|
|
107
|
+
if [ "$bare" -eq 1 ] || [ -z "$DEFAULT_REGISTRY_URL" ]; then
|
|
108
|
+
echo "# registries:"
|
|
109
|
+
echo "# - \"https://github.com/acme/intelligence-registry.git\""
|
|
110
|
+
else
|
|
111
|
+
echo "registries:"
|
|
112
|
+
echo " - \"$DEFAULT_REGISTRY_URL\""
|
|
113
|
+
fi
|
|
99
114
|
} > "$manifest"
|
|
100
115
|
|
|
101
116
|
# The package store is restorable state, never committed — the npm model.
|
package/cli/commands/install.sh
CHANGED
|
@@ -22,17 +22,53 @@ require_v2
|
|
|
22
22
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
23
23
|
lock="$IP_ROOT/intelligence.lock"
|
|
24
24
|
|
|
25
|
+
# --frozen is the reproducibility contract: the lock must agree with the
|
|
26
|
+
# manifest on the package SET and on every requested-source field before a
|
|
27
|
+
# single byte is restored. Anything less lets a manifest edit ride on a stale
|
|
28
|
+
# lock — the exact drift the flag exists to refuse.
|
|
29
|
+
if [ "$frozen" -eq 1 ]; then
|
|
30
|
+
while IFS= read -r name; do
|
|
31
|
+
[ -n "$name" ] || continue
|
|
32
|
+
[ -n "$(qmap_field "$lock" "packages" "$name" "url")" ] \
|
|
33
|
+
|| die "--frozen: $name is in the manifest but not in intelligence.lock"
|
|
34
|
+
m_ver="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
35
|
+
m_ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
36
|
+
m_url="$(qmap_field "$manifest" "packages" "$name" "url")"
|
|
37
|
+
m_path="$(qmap_field "$manifest" "packages" "$name" "path")"
|
|
38
|
+
l_req="$(qmap_field "$lock" "packages" "$name" "requested")"
|
|
39
|
+
l_res="$(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
40
|
+
l_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
41
|
+
l_path="$(qmap_field "$lock" "packages" "$name" "path")"
|
|
42
|
+
[ -z "$m_ver" ] || [ "$m_ver" = "$l_req" ] \
|
|
43
|
+
|| die "--frozen: $name requests '$m_ver' but the lock recorded '$l_req' — run 'intelligence install' without --frozen"
|
|
44
|
+
[ -z "$m_ref" ] || [ "$m_ref" = "$l_res" ] \
|
|
45
|
+
|| die "--frozen: $name pins ref '$m_ref' but the lock resolved '$l_res'"
|
|
46
|
+
[ -z "$m_url" ] || [ "$m_url" = "$l_url" ] \
|
|
47
|
+
|| die "--frozen: $name declares url '$m_url' but the lock recorded '$l_url'"
|
|
48
|
+
[ -z "$m_path" ] || [ "$m_path" = "$l_path" ] \
|
|
49
|
+
|| die "--frozen: $name declares path '$m_path' but the lock recorded '$l_path'"
|
|
50
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
51
|
+
while IFS= read -r name; do
|
|
52
|
+
[ -n "$name" ] || continue
|
|
53
|
+
found=0
|
|
54
|
+
while IFS= read -r m; do
|
|
55
|
+
[ "$m" = "$name" ] && found=1
|
|
56
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
57
|
+
[ "$found" -eq 1 ] || die "--frozen: $name is locked but absent from the manifest"
|
|
58
|
+
done < <(qmap_keys "$lock" "packages")
|
|
59
|
+
fi
|
|
60
|
+
|
|
25
61
|
# Manifest packages missing from the lock: resolve them now (npm install
|
|
26
|
-
# semantics). Under --frozen
|
|
62
|
+
# semantics). Under --frozen the drift gate above has already refused.
|
|
27
63
|
missing=""
|
|
28
64
|
while IFS= read -r name; do
|
|
29
65
|
[ -n "$name" ] || continue
|
|
66
|
+
assert_valid_pkg_name "$name"
|
|
30
67
|
if [ ! -f "$lock" ] || [ -z "$(qmap_field "$lock" "packages" "$name" "url")" ]; then
|
|
31
68
|
missing="$missing $name"
|
|
32
69
|
fi
|
|
33
70
|
done < <(qmap_keys "$manifest" "packages")
|
|
34
71
|
if [ -n "$missing" ]; then
|
|
35
|
-
[ "$frozen" -eq 1 ] && die "lockfile is missing packages:$missing — run 'intelligence install' without --frozen"
|
|
36
72
|
for name in $missing; do
|
|
37
73
|
# The engine-content package needs no remote resolution: its pin IS
|
|
38
74
|
# the bundled engine version and it seeds from the bundle, offline.
|
|
@@ -66,20 +102,35 @@ fi
|
|
|
66
102
|
if [ -f "$lock" ]; then
|
|
67
103
|
while IFS="$LOCK_SEP" read -r name requested url path resolved sha; do
|
|
68
104
|
[ -n "$name" ] || continue
|
|
105
|
+
# The lock arrives with a cloned repo — its keys are untrusted input
|
|
106
|
+
# on their way into rm -rf paths, and its urls into git argv.
|
|
107
|
+
assert_valid_pkg_name "$name"
|
|
69
108
|
rel=".intelligence/packages/$name"
|
|
70
|
-
|
|
109
|
+
# --frozen never trusts what is already on disk: a pre-existing
|
|
110
|
+
# (possibly substituted) store dir would otherwise be synced with no
|
|
111
|
+
# sha verification at all.
|
|
112
|
+
if [ -d "$IP_ROOT/$rel" ] && [ "$force" -eq 0 ] && [ "$frozen" -eq 0 ]; then
|
|
71
113
|
continue
|
|
72
114
|
fi
|
|
73
|
-
|
|
115
|
+
# Fetch into staging and commit to the store only after the integrity
|
|
116
|
+
# verdict: content that violates the lock must never be left where a
|
|
117
|
+
# re-run (whose dir-exists check skips verification) would sync it.
|
|
118
|
+
staging="$IP_ROOT/.intelligence/.staging-$$"
|
|
119
|
+
rm -rf "$staging"
|
|
120
|
+
got="$(fetch_package "$url" "$resolved" "$path" "$staging")"
|
|
74
121
|
# An empty sha (bundle-seeded content on a dev build) means unknown,
|
|
75
122
|
# not moved — only two known-and-different shas are a finding.
|
|
76
123
|
if [ -n "$sha" ] && [ -n "$got" ] && [ "$got" != "$sha" ]; then
|
|
77
124
|
if [ "$frozen" -eq 1 ]; then
|
|
125
|
+
rm -rf "$staging"
|
|
78
126
|
die "$name: fetched $got but the lock pins $sha — the ref moved; refusing under --frozen"
|
|
79
127
|
fi
|
|
80
128
|
echo " WARN: $name resolved to $got, lock had $sha — updating the lock" >&2
|
|
81
129
|
lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$resolved" "$got"
|
|
82
130
|
fi
|
|
131
|
+
rm -rf "${IP_ROOT:?}/$rel"
|
|
132
|
+
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
133
|
+
mv "$staging" "$IP_ROOT/$rel"
|
|
83
134
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
84
135
|
echo "= $name@${resolved:-HEAD}"
|
|
85
136
|
done < <(lock_to_tsv "$lock")
|
package/cli/commands/migrate.sh
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# to the CLI setup: root intelligence.yaml, .intelligence/ store,
|
|
4
4
|
# intelligence.lock, no vendored engine.
|
|
5
5
|
#
|
|
6
|
-
# Transactional
|
|
6
|
+
# Transactional: STAGE everything next to the
|
|
7
7
|
# project, VERIFY the staged state, then COMMIT in an order where every
|
|
8
8
|
# destructive step happens only after a real sync of the new state reported
|
|
9
9
|
# IS_STATUS=ok. Any earlier failure leaves the project untouched.
|
|
@@ -44,12 +44,11 @@ stamp="$(read_engine_stamp "$config")"
|
|
|
44
44
|
eng="$(bundled_engine_version)"
|
|
45
45
|
[ -n "$stamp" ] && _ver_gt "$stamp" "$eng" && die "project schema $stamp is newer than this CLI's engine $eng — update the CLI first"
|
|
46
46
|
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
fi
|
|
47
|
+
# The v1 `packs:` block is the one v1 shape migrate still has to read, and the
|
|
48
|
+
# engine no longer knows it — the reader lives here, with its only consumer.
|
|
49
|
+
get_pack_field() {
|
|
50
|
+
get_nested_yaml_value "$1" "packs" "$2" "$3"
|
|
51
|
+
}
|
|
53
52
|
|
|
54
53
|
# ---- Stage ----------------------------------------------------------------
|
|
55
54
|
stage="$root/.intelligence-migrate-stage.$$"
|
|
@@ -58,6 +57,20 @@ cleanup_stage() { rm -rf "$stage"; }
|
|
|
58
57
|
trap cleanup_stage EXIT INT TERM
|
|
59
58
|
mkdir -p "$stage/.intelligence/packages"
|
|
60
59
|
|
|
60
|
+
# v2 carries no v1 migration chain: v1 is archived, and the archived engine is
|
|
61
|
+
# what knows how to walk an old project forward. So migrate accepts only a
|
|
62
|
+
# project already at the final v1 schema and names the exact remedy otherwise —
|
|
63
|
+
# a remedy that keeps working indefinitely, because the archive stays at the
|
|
64
|
+
# URL the project's own update.sh already clones.
|
|
65
|
+
V1_FINAL_SCHEMA="0.10.0"
|
|
66
|
+
if [ -z "$stamp" ] || _ver_gt "$V1_FINAL_SCHEMA" "$stamp"; then
|
|
67
|
+
echo "ERROR: project schema is ${stamp:-absent (pre-0.3.1)}, older than the final v1 schema $V1_FINAL_SCHEMA." >&2
|
|
68
|
+
echo " Bring it forward with its own engine first — that still works:" >&2
|
|
69
|
+
echo " bash $module_rel/scripts/update.sh --yes" >&2
|
|
70
|
+
echo " then re-run: intelligence migrate" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
61
74
|
echo "== staging =="
|
|
62
75
|
# Engine content arrives as the @ainova-systems/sync package, seeded from the
|
|
63
76
|
# bundle (fetch_package's guard — no network).
|
|
@@ -67,6 +80,7 @@ sync_pkg_sha="$(fetch_package "$SYNC_PKG_URL" "v$eng" "$SYNC_PKG_PATH" "$stage/$
|
|
|
67
80
|
# per-pack source facts. Recorded as rows for the config rewrite and the lock.
|
|
68
81
|
pack_rows="$stage/packs.rows"
|
|
69
82
|
: > "$pack_rows"
|
|
83
|
+
seen_names=" "
|
|
70
84
|
while IFS= read -r pack; do
|
|
71
85
|
[ -n "$pack" ] || continue
|
|
72
86
|
url="$(get_pack_field "$config" "$pack" "url")"
|
|
@@ -80,6 +94,12 @@ while IFS= read -r pack; do
|
|
|
80
94
|
case "$repo" in ""|*[!A-Za-z0-9._-]*) repo="$(printf '%s' "$repo" | tr -c 'A-Za-z0-9._-' '-')" ;; esac
|
|
81
95
|
name="@$org/$repo"
|
|
82
96
|
assert_valid_pkg_name "$name"
|
|
97
|
+
# Two packs deriving the same @org/repo would overwrite one store dir and
|
|
98
|
+
# duplicate the lock key — fail closed, the user renames a source.
|
|
99
|
+
case "$seen_names" in
|
|
100
|
+
*" $name "*) die "packs '$pack' and another both derive package name '$name' — sources must map to distinct names" ;;
|
|
101
|
+
esac
|
|
102
|
+
seen_names="$seen_names $name "
|
|
83
103
|
printf '%s\n' "$pack$LOCK_SEP$name$LOCK_SEP$url$LOCK_SEP$ref$LOCK_SEP$mirror"
|
|
84
104
|
done < <(read_yaml_keys "$config" "packs") >> "$pack_rows"
|
|
85
105
|
|
|
@@ -153,8 +173,19 @@ while IFS= read -r line || [ -n "$line" ]; do
|
|
|
153
173
|
*"- "*)
|
|
154
174
|
indent="${line%%-*}"
|
|
155
175
|
v="${line#*- }"
|
|
176
|
+
# A trailing inline comment survives the rewrite — split it
|
|
177
|
+
# off before touching quotes, or it would be mangled into the
|
|
178
|
+
# value.
|
|
179
|
+
trail=""
|
|
180
|
+
case "$v" in
|
|
181
|
+
*[[:space:]]"#"*)
|
|
182
|
+
trail=" #${v#*[[:space:]]#}"
|
|
183
|
+
v="${v%%[[:space:]]#*}"
|
|
184
|
+
;;
|
|
185
|
+
esac
|
|
186
|
+
v="${v%"${v##*[![:space:]]}"}"
|
|
156
187
|
v="${v%\"}"; v="${v#\"}"; v="${v%\'}"; v="${v#\'}"
|
|
157
|
-
echo "$indent- \"$(rewrite_source_value "$v")\""
|
|
188
|
+
echo "$indent- \"$(rewrite_source_value "$v")\"$trail"
|
|
158
189
|
continue
|
|
159
190
|
;;
|
|
160
191
|
esac
|
|
@@ -212,6 +243,13 @@ fi
|
|
|
212
243
|
|
|
213
244
|
# ---- Commit ---------------------------------------------------------------
|
|
214
245
|
echo "== committing =="
|
|
246
|
+
# .gitignore is part of the transaction: rollback must leave it exactly as
|
|
247
|
+
# found, absent included.
|
|
248
|
+
gitignore_existed=0
|
|
249
|
+
if [ -f "$root/.gitignore" ]; then
|
|
250
|
+
gitignore_existed=1
|
|
251
|
+
cp "$root/.gitignore" "$stage/gitignore.orig"
|
|
252
|
+
fi
|
|
215
253
|
if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
|
|
216
254
|
{
|
|
217
255
|
[ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
|
|
@@ -221,8 +259,15 @@ if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore
|
|
|
221
259
|
fi
|
|
222
260
|
rollback() {
|
|
223
261
|
rm -rf "$root/.intelligence" "$root/intelligence.yaml" "$root/intelligence.lock"
|
|
262
|
+
if [ "$gitignore_existed" -eq 1 ]; then
|
|
263
|
+
cp "$stage/gitignore.orig" "$root/.gitignore"
|
|
264
|
+
else
|
|
265
|
+
rm -f "$root/.gitignore"
|
|
266
|
+
fi
|
|
224
267
|
echo "rolled back — the vendored setup is untouched" >&2
|
|
225
268
|
}
|
|
269
|
+
# From the first moved file until success, an interrupt must roll back too.
|
|
270
|
+
trap 'rollback; exit 130' INT TERM
|
|
226
271
|
mv "$stage/.intelligence" "$root/.intelligence"
|
|
227
272
|
cp "$manifest_stage" "$root/intelligence.yaml"
|
|
228
273
|
lock_write_from_tsv "$root/intelligence.lock" "$lock_rows"
|
|
@@ -237,13 +282,21 @@ echo "$sync_out" | grep -q '^IS_STATUS=ok' || {
|
|
|
237
282
|
rollback
|
|
238
283
|
die "sync of the migrated state did not report ok — rolled back"
|
|
239
284
|
}
|
|
285
|
+
# Point of no return: the new state is verified. From here an interrupt must
|
|
286
|
+
# NOT roll back (the old setup is being removed) — only clean the stage.
|
|
287
|
+
trap cleanup_stage EXIT INT TERM
|
|
240
288
|
|
|
241
289
|
# Only now is anything of the old setup removed — and config.yaml is kept as
|
|
242
290
|
# a reviewable backup inside the store.
|
|
243
291
|
mkdir -p "$root/.intelligence/backup"
|
|
244
292
|
cp "$config" "$root/.intelligence/backup/config.yaml"
|
|
245
293
|
while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
246
|
-
|
|
294
|
+
# Containment before deletion: a mirror value is config input, and rm -rf
|
|
295
|
+
# must never follow it out of the repository.
|
|
296
|
+
case "$mirror" in
|
|
297
|
+
""|/*|*..*) [ -n "$mirror" ] && echo " WARN: mirror '$mirror' not repo-contained — left in place" >&2; continue ;;
|
|
298
|
+
esac
|
|
299
|
+
if [ -d "$root/$mirror" ]; then
|
|
247
300
|
rm -rf "${root:?}/$mirror"
|
|
248
301
|
# An emptied mirror parent (e.g. <umbrella>/external/) goes with it.
|
|
249
302
|
rmdir "$(dirname "$root/$mirror")" 2>/dev/null || true
|
package/cli/commands/registry.sh
CHANGED
|
@@ -28,15 +28,7 @@ case "$sub" in
|
|
|
28
28
|
done < <(registries_list "$IP_ROOT/intelligence.yaml")
|
|
29
29
|
[ "$found" -eq 0 ] && echo " (none)"
|
|
30
30
|
fi
|
|
31
|
-
|
|
32
|
-
if [ -n "$bundled" ]; then
|
|
33
|
-
echo "Bundled index:"
|
|
34
|
-
while IFS= read -r name; do
|
|
35
|
-
[ -n "$name" ] || continue
|
|
36
|
-
echo " $name -> $(qmap_field "$bundled" "packages" "$name" "url")"
|
|
37
|
-
done < <(qmap_keys "$bundled" "packages")
|
|
38
|
-
fi
|
|
39
|
-
echo "Fallback: @org/name -> https://github.com/org/name.git"
|
|
31
|
+
echo "Names resolve ONLY through the list above. Registry-less installs are explicit: github:org/repo, git+<url>."
|
|
40
32
|
;;
|
|
41
33
|
add)
|
|
42
34
|
url="${2:-}"
|
|
@@ -46,6 +38,7 @@ case "$sub" in
|
|
|
46
38
|
case "$url" in
|
|
47
39
|
@*) die "registries are added by URL — names carry their own scope. To install a package: intelligence add $url/<name>" ;;
|
|
48
40
|
esac
|
|
41
|
+
assert_safe_source_url "${url#git+}"
|
|
49
42
|
require_v2
|
|
50
43
|
# Fail closed: a URL that cannot produce an index is not a registry,
|
|
51
44
|
# and recording it would turn a typo here into a confusing failure at
|
package/cli/commands/search.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# "what does this project depend on", `search` answers "what could it".
|
|
5
5
|
#
|
|
6
6
|
# Sources, in resolution order (a name found earlier wins, exactly as `add`
|
|
7
|
-
# resolves it):
|
|
7
|
+
# resolves it): the manifest's trusted registries, in order.
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
10
10
|
|
|
@@ -69,15 +69,13 @@ if [ -n "$manifest" ]; then
|
|
|
69
69
|
emit_index "$index" "$url"
|
|
70
70
|
done < <(registries_list "$manifest")
|
|
71
71
|
fi
|
|
72
|
-
emit_index "$(default_index_file)" "bundled"
|
|
73
|
-
|
|
74
72
|
if [ "$rows" -eq 0 ]; then
|
|
75
73
|
if [ -n "$term" ]; then
|
|
76
|
-
echo "Nothing matching '$term' in
|
|
74
|
+
echo "Nothing matching '$term' in this project's trusted registries."
|
|
77
75
|
else
|
|
78
|
-
echo "No registries
|
|
76
|
+
echo "No trusted registries (or they offer nothing) — intelligence registry add <repo-url>."
|
|
79
77
|
fi
|
|
80
|
-
echo "
|
|
78
|
+
echo "Explicit installs need no registry: intelligence add github:org/repo"
|
|
81
79
|
exit 0
|
|
82
80
|
fi
|
|
83
81
|
|
package/cli/commands/sync.sh
CHANGED
|
@@ -26,7 +26,7 @@ case "$IP_MODE" in
|
|
|
26
26
|
die "installed content missing on disk:$missing — run 'intelligence install'"
|
|
27
27
|
fi
|
|
28
28
|
export_engine_env "$IP_ROOT"
|
|
29
|
-
exec bash "$IS_ENGINE_DIR/
|
|
29
|
+
exec bash "$IS_ENGINE_DIR/sync.sh" "$@"
|
|
30
30
|
;;
|
|
31
31
|
legacy)
|
|
32
32
|
# A vendored project syncs with ITS engine — its pin is the contract,
|
package/cli/commands/update.sh
CHANGED
|
@@ -21,10 +21,15 @@ require_v2
|
|
|
21
21
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
22
22
|
lock="$IP_ROOT/intelligence.lock"
|
|
23
23
|
|
|
24
|
-
moved=0
|
|
24
|
+
moved=0 found=0
|
|
25
25
|
while IFS= read -r name; do
|
|
26
26
|
[ -n "$name" ] || continue
|
|
27
|
+
# Manifest keys are untrusted input on their way into store paths.
|
|
28
|
+
assert_valid_pkg_name "$name"
|
|
27
29
|
[ -n "$only" ] && [ "$name" != "$only" ] && continue
|
|
30
|
+
# A named package that exists is "found" whatever happens next — a skip
|
|
31
|
+
# message followed by "not in the manifest" would contradict itself.
|
|
32
|
+
found=1
|
|
28
33
|
if [ "$name" = "$SYNC_PKG_NAME" ]; then
|
|
29
34
|
echo " $name: engine content moves with the engine — 'intelligence upgrade' owns it"
|
|
30
35
|
continue
|
|
@@ -32,30 +37,49 @@ while IFS= read -r name; do
|
|
|
32
37
|
ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
33
38
|
[ -n "$ref" ] && { echo " $name: pinned to ref '$ref' — skipped"; continue; }
|
|
34
39
|
range="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
40
|
+
# An exact version is a pin, same as ref: — update never moves pins.
|
|
41
|
+
if [ -n "$range" ] && semver_is_stable "$range"; then
|
|
42
|
+
echo " $name: pinned to $range — edit the manifest to move it"
|
|
43
|
+
continue
|
|
44
|
+
fi
|
|
35
45
|
url="$(qmap_field "$manifest" "packages" "$name" "url")"
|
|
36
46
|
path="$(qmap_field "$manifest" "packages" "$name" "path")"
|
|
37
47
|
if [ -z "$url" ]; then
|
|
38
48
|
resolve_package_source "$manifest" "$name"
|
|
39
49
|
url="$RES_URL"; path="$RES_PATH"
|
|
40
50
|
fi
|
|
41
|
-
checked=$((checked + 1))
|
|
42
51
|
picked="$(list_remote_versions "$url" | semver_pick_highest "$range")"
|
|
43
52
|
[ -n "$picked" ] || { echo " $name: nothing satisfies '${range:-*}' at $url" >&2; continue; }
|
|
44
53
|
read -r tag _ <<< "$(remote_tag_for_version "$url" "$picked")"
|
|
45
54
|
current="$(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
46
|
-
|
|
55
|
+
cur_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
56
|
+
cur_path="$(qmap_field "$lock" "packages" "$name" "path")"
|
|
57
|
+
# Same tag is not enough: a registry may re-point a name's source while
|
|
58
|
+
# keeping the version — the move doctor warns about must be actionable
|
|
59
|
+
# here, so url/path changes refetch too.
|
|
60
|
+
if [ "$tag" = "$current" ] && [ "$url" = "$cur_url" ] && [ "$path" = "$cur_path" ]; then
|
|
47
61
|
echo " $name: ${current:-<none>} (up to date)"
|
|
48
62
|
continue
|
|
49
63
|
fi
|
|
50
64
|
rel=".intelligence/packages/$name"
|
|
51
|
-
|
|
65
|
+
# Fetch into staging FIRST: a failed clone must leave the current install
|
|
66
|
+
# fully wired and intact. Only after success is the old shape unwired
|
|
67
|
+
# (the new version may have dropped a section dir), the store swapped,
|
|
68
|
+
# and the new shape wired.
|
|
69
|
+
staging="$IP_ROOT/.intelligence/.staging-$$"
|
|
70
|
+
rm -rf "$staging"
|
|
71
|
+
sha="$(fetch_package "$url" "$tag" "$path" "$staging")"
|
|
72
|
+
unwire_package_sources "$manifest" "$rel"
|
|
73
|
+
rm -rf "${IP_ROOT:?}/$rel"
|
|
74
|
+
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
75
|
+
mv "$staging" "$IP_ROOT/$rel"
|
|
52
76
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
53
77
|
lock_upsert "$lock" "$name" "$range" "$url" "$path" "$tag" "$sha"
|
|
54
78
|
echo " $name: ${current:-<none>} -> $tag"
|
|
55
79
|
moved=$((moved + 1))
|
|
56
80
|
done < <(qmap_keys "$manifest" "packages")
|
|
57
81
|
|
|
58
|
-
[ -n "$only" ] && [ "$
|
|
82
|
+
[ -n "$only" ] && [ "$found" -eq 0 ] && die "package '$only' is not in the manifest"
|
|
59
83
|
echo "updated: $moved package(s)"
|
|
60
84
|
|
|
61
85
|
if [ "$moved" -gt 0 ] && [ "$no_sync" -eq 0 ]; then
|
|
@@ -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
|
+
# `upgrade` moves it, and at the matching version it materializes from the
|
|
10
|
+
# 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
|
-
#
|
|
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 (
|
|
15
|
-
#
|
|
16
|
-
#
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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,10 +24,19 @@ 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/
|
|
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})
|
|
@@ -48,7 +52,7 @@ Usage: intelligence <command> [args]
|
|
|
48
52
|
list Installed packages with requested/locked versions
|
|
49
53
|
search [term] What the registries offer, and what this project has
|
|
50
54
|
sync [target] Render intelligence to every enabled tool (.claude, AGENTS.md, ...)
|
|
51
|
-
registry <list|add|remove>
|
|
55
|
+
registry <list|add|remove> Manage the trusted registry list (add <repo-url>)
|
|
52
56
|
migrate [--dry-run] Convert a vendored (v1) setup to the CLI setup
|
|
53
57
|
doctor Check manifest, lockfile, store and engine consistency
|
|
54
58
|
status Project state at a glance
|
|
@@ -57,13 +61,21 @@ EOF
|
|
|
57
61
|
exit 0
|
|
58
62
|
;;
|
|
59
63
|
version|-v|--version)
|
|
60
|
-
engine_ver="$(tr -d ' \t\r\n' < "$IS_ENGINE_DIR/
|
|
64
|
+
engine_ver="$(tr -d ' \t\r\n' < "$IS_ENGINE_DIR/VERSION")"
|
|
61
65
|
echo "${INTELLIGENCE_NPM_VERSION:-$engine_ver} (engine $engine_ver)"
|
|
62
66
|
exit 0
|
|
63
67
|
;;
|
|
64
68
|
esac
|
|
65
69
|
shift
|
|
66
70
|
|
|
71
|
+
# The command word builds a path — allow exactly lowercase words, so
|
|
72
|
+
# `intelligence ../x` can never exec a neighbouring script.
|
|
73
|
+
case "$cmd" in
|
|
74
|
+
*[!a-z-]*|-*)
|
|
75
|
+
echo "ERROR: unknown command '$cmd'. Run 'intelligence help'." >&2
|
|
76
|
+
exit 1
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
67
79
|
cmd_file="$CLI_DIR/commands/$cmd.sh"
|
|
68
80
|
if [ ! -f "$cmd_file" ]; then
|
|
69
81
|
echo "ERROR: unknown command '$cmd'. Run 'intelligence help'." >&2
|