@ainova-systems/intelligence 0.11.0-rc.4 → 0.11.0-rc.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.
- package/cli/commands/add.sh +23 -1
- package/cli/commands/doctor.sh +28 -7
- package/cli/commands/init.sh +67 -21
- package/cli/commands/install.sh +55 -5
- package/cli/commands/migrate.sh +41 -9
- package/cli/commands/registry.sh +54 -35
- package/cli/commands/remove.sh +13 -1
- package/cli/commands/search.sh +12 -13
- package/cli/commands/status.sh +5 -4
- package/cli/commands/sync.sh +19 -1
- package/cli/commands/update.sh +23 -4
- package/cli/commands/upgrade.sh +50 -9
- package/cli/engine-package.yaml +14 -0
- package/cli/lib/cli-common.sh +37 -48
- package/cli/lib/manifest.sh +118 -0
- package/cli/lib/registry.sh +74 -39
- package/cli/lib/semver.sh +5 -2
- package/engine/agents/intelligence-operator.md +4 -4
- package/engine/docs/ADAPTERS.md +2 -1
- package/engine/docs/CLI.md +12 -13
- package/engine/docs/CONVENTIONS.md +5 -3
- package/engine/rules/intelligence-authoring.md +4 -4
- package/engine/scripts/ENGINE_SHA +1 -0
- package/engine/scripts/lib/common.sh +6 -2
- package/engine/scripts/sync.sh +1 -1
- package/engine/skills/intelligence-add-agent/SKILL.md +7 -7
- package/engine/skills/intelligence-add-rule/SKILL.md +5 -5
- package/engine/skills/intelligence-add-skill/SKILL.md +5 -5
- package/engine/skills/intelligence-extract-skill/SKILL.md +2 -2
- package/engine/skills/intelligence-install-adapter/SKILL.md +6 -6
- package/engine/skills/intelligence-learn-from-context/SKILL.md +1 -1
- package/engine/skills/intelligence-review-skills/SKILL.md +5 -5
- package/engine/skills/intelligence-sync/SKILL.md +5 -7
- package/engine/skills/intelligence-uninstall-adapter/SKILL.md +2 -2
- package/engine/skills/intelligence-update/SKILL.md +6 -0
- package/package.json +2 -3
- package/registry/index.yaml +0 -22
package/cli/commands/add.sh
CHANGED
|
@@ -78,9 +78,27 @@ assert_valid_pkg_name "$name"
|
|
|
78
78
|
if [ "$mode" = "registry" ]; then
|
|
79
79
|
resolve_package_source "$manifest" "$name"
|
|
80
80
|
url="$RES_URL"; path="$RES_PATH"
|
|
81
|
+
if [ -z "$url" ]; then
|
|
82
|
+
echo "ERROR: no trusted registry declares '$name'." >&2
|
|
83
|
+
echo " A name installs only through a registry this project added explicitly" >&2
|
|
84
|
+
echo " ('intelligence registry list' shows them). Either add its registry:" >&2
|
|
85
|
+
echo " intelligence registry add <registry-repo-url>" >&2
|
|
86
|
+
echo " or install from an explicit source you name yourself:" >&2
|
|
87
|
+
echo " intelligence add github:org/repo | intelligence add git+<url>[@ref][#path]" >&2
|
|
88
|
+
suggest_similar "$manifest" "$name"
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
81
91
|
echo " resolving $name via $RES_VIA -> $url${path:+ ($path)}"
|
|
82
92
|
fi
|
|
83
93
|
|
|
94
|
+
# The source must exist before anything else is attempted — never die
|
|
95
|
+
# mid-pipeline with no message.
|
|
96
|
+
if ! GIT_TERMINAL_PROMPT=0 git ls-remote "$url" >/dev/null 2>&1; then
|
|
97
|
+
echo "ERROR: no reachable repository at $url" >&2
|
|
98
|
+
echo " Declared by ${RES_VIA:-the spec} — check the URL and your git access." >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
|
|
84
102
|
# Pick the version: an explicit ref pins; otherwise the highest stable tag
|
|
85
103
|
# matching the range (npm's behaviour, `^highest` becoming the requested range
|
|
86
104
|
# when none was given).
|
|
@@ -107,7 +125,11 @@ sha="$(fetch_package "$url" "${ref:-$resolved_tag}" "$path" "$IP_ROOT/$rel")"
|
|
|
107
125
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
108
126
|
|
|
109
127
|
# Manifest entry: a registry package records its range; a direct one records
|
|
110
|
-
# its source so resolution never has to guess it again.
|
|
128
|
+
# its source so resolution never has to guess it again. The entry is replaced
|
|
129
|
+
# WHOLE — re-adding under a different spec must not leave the old spec's
|
|
130
|
+
# fields behind (a stale ref: would freeze update, a stale url: would pin the
|
|
131
|
+
# old source).
|
|
132
|
+
qmap_delete_key "$manifest" "packages" "$name"
|
|
111
133
|
if [ "$mode" = "registry" ]; then
|
|
112
134
|
qmap_set "$manifest" "packages" "$name" "version" "$requested"
|
|
113
135
|
else
|
package/cli/commands/doctor.sh
CHANGED
|
@@ -43,14 +43,23 @@ else
|
|
|
43
43
|
ok "sync_version $stamp matches the engine"
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 upgrade'"
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
if [ -d "$IP_ROOT/.intelligence/engine" ]; then
|
|
57
|
+
warn "stale .intelligence/engine directory (pre-package layout) — run 'intelligence upgrade'"
|
|
51
58
|
fi
|
|
52
59
|
|
|
53
|
-
# Manifest packages: names valid, locked, installed
|
|
60
|
+
# Manifest packages: names valid, locked, installed — and the lock's source
|
|
61
|
+
# still the one resolution would pick today (a registry silently re-pointing
|
|
62
|
+
# an installed name is the dependency-confusion tail this check cuts off).
|
|
54
63
|
while IFS= read -r name; do
|
|
55
64
|
[ -n "$name" ] || continue
|
|
56
65
|
assert_valid_pkg_name "$name"
|
|
@@ -60,6 +69,13 @@ while IFS= read -r name; do
|
|
|
60
69
|
warn "$name is locked but not installed — run 'intelligence install'"
|
|
61
70
|
else
|
|
62
71
|
ok "$name @ $(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
72
|
+
if [ -z "$(qmap_field "$manifest" "packages" "$name" "url")" ]; then
|
|
73
|
+
locked_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
74
|
+
resolve_package_source "$manifest" "$name" 2>/dev/null || true
|
|
75
|
+
if [ -n "${RES_URL:-}" ] && [ -n "$locked_url" ] && [ "$RES_URL" != "$locked_url" ]; then
|
|
76
|
+
warn "$name now resolves to $RES_URL but the lock pins $locked_url — a registry re-pointed the name; verify before 'intelligence update'"
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
63
79
|
fi
|
|
64
80
|
done < <(qmap_keys "$manifest" "packages")
|
|
65
81
|
|
|
@@ -85,8 +101,13 @@ for section in rules agents skills; do
|
|
|
85
101
|
esac
|
|
86
102
|
if [ ! -d "$IP_ROOT/$src" ]; then
|
|
87
103
|
case "$src" in
|
|
104
|
+
# Store content is restorable state: absent means un-installed.
|
|
88
105
|
.intelligence/*) warn "sources.$section '$src' missing — run 'intelligence install'" ;;
|
|
89
|
-
|
|
106
|
+
# A project-owned dir that does not exist yet is a legitimate
|
|
107
|
+
# state — a project consuming only packages authors nothing of
|
|
108
|
+
# its own — and the engine simply skips it. Declared up front so
|
|
109
|
+
# authoring later needs no config edit.
|
|
110
|
+
*) ok "sources.$section '$src' — not created yet (optional)" ;;
|
|
90
111
|
esac
|
|
91
112
|
fi
|
|
92
113
|
done < <(read_yaml_list "$manifest" "$section")
|
package/cli/commands/init.sh
CHANGED
|
@@ -8,15 +8,21 @@
|
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
10
10
|
|
|
11
|
-
targets_arg="" no_sync=0
|
|
11
|
+
targets_arg="" no_sync=0 content_dir="intelligence" bare=0
|
|
12
12
|
while [ $# -gt 0 ]; do
|
|
13
13
|
case "$1" in
|
|
14
14
|
--targets) shift; targets_arg="${1:-}" ;;
|
|
15
|
+
--dir) shift; content_dir="${1:-}" ;;
|
|
15
16
|
--no-sync) no_sync=1 ;;
|
|
17
|
+
--bare) bare=1 ;;
|
|
16
18
|
*) die "unknown option '$1'" ;;
|
|
17
19
|
esac
|
|
18
20
|
shift || true
|
|
19
21
|
done
|
|
22
|
+
[ -n "$content_dir" ] || die "--dir needs a directory name"
|
|
23
|
+
case "$content_dir" in
|
|
24
|
+
/*|*..*|.intelligence|.intelligence/*) die "--dir must be a plain directory inside the repo, and not the package store" ;;
|
|
25
|
+
esac
|
|
20
26
|
|
|
21
27
|
detect_project
|
|
22
28
|
case "$IP_MODE" in
|
|
@@ -27,37 +33,55 @@ esac
|
|
|
27
33
|
root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
28
34
|
root="$(cd "$root" && pwd)"
|
|
29
35
|
|
|
30
|
-
|
|
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"
|
|
31
41
|
if [ -n "$targets_arg" ]; then
|
|
32
42
|
targets="agents $(printf '%s' "$targets_arg" | tr ',' ' ')"
|
|
33
43
|
else
|
|
44
|
+
if [ -d "$root/.claude" ] || [ -f "$root/CLAUDE.md" ]; then targets="$targets claude"; fi
|
|
34
45
|
[ -d "$root/.cursor" ] && targets="$targets cursor"
|
|
35
46
|
[ -d "$root/.codex" ] && targets="$targets codex"
|
|
36
47
|
[ -d "$root/.pi" ] && targets="$targets pi"
|
|
37
48
|
[ -d "$root/.opencode" ] && targets="$targets opencode"
|
|
38
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
|
|
39
54
|
fi
|
|
40
55
|
|
|
41
56
|
manifest="$root/intelligence.yaml"
|
|
42
57
|
{
|
|
43
58
|
echo "# Intelligence manifest — what this project's AI tooling knows and where it goes."
|
|
44
|
-
echo "# Managed by the intelligence CLI
|
|
59
|
+
echo "# Managed by the intelligence CLI. Reference: https://github.com/ainova-systems/intelligence-sync"
|
|
45
60
|
echo "project:"
|
|
46
61
|
echo " name: \"$(basename "$root")\""
|
|
62
|
+
[ "$content_dir" = "intelligence" ] || echo " intelligence_dir: \"$content_dir\""
|
|
47
63
|
echo ""
|
|
64
|
+
echo "# Schema version of this manifest. The CLI keeps it in step with its engine."
|
|
48
65
|
echo "sync_version: \"$(bundled_engine_version)\""
|
|
49
66
|
echo ""
|
|
67
|
+
echo "# Where content comes from. Nothing here has to exist: a directory that is"
|
|
68
|
+
echo "# absent is skipped, so a project consuming only packages authors nothing of"
|
|
69
|
+
echo "# its own. To write your own, create the directory and sync — no config edit:"
|
|
70
|
+
echo "# $content_dir/rules/context.md an always-on rule; describe the project,"
|
|
71
|
+
echo "# its stack, and the constraints to respect"
|
|
72
|
+
echo "# $content_dir/agents/<name>.md a persona (frontmatter: name, description,"
|
|
73
|
+
echo "# tier: heavy|standard|light, access: full|readonly)"
|
|
74
|
+
echo "# $content_dir/skills/<name>/SKILL.md a procedure invoked as /<name>"
|
|
75
|
+
echo "# '.intelligence/' is CLI-managed (packages + engine content) — never edit it."
|
|
50
76
|
echo "sources:"
|
|
51
77
|
echo " rules:"
|
|
52
|
-
echo " - \"
|
|
53
|
-
echo " - \".intelligence/engine/rules\""
|
|
78
|
+
echo " - \"$content_dir/rules\""
|
|
54
79
|
echo " agents:"
|
|
55
|
-
echo " - \"
|
|
56
|
-
echo " - \".intelligence/engine/agents\""
|
|
80
|
+
echo " - \"$content_dir/agents\""
|
|
57
81
|
echo " skills:"
|
|
58
|
-
echo " - \"
|
|
59
|
-
echo " - \".intelligence/engine/skills\""
|
|
82
|
+
echo " - \"$content_dir/skills\""
|
|
60
83
|
echo ""
|
|
84
|
+
echo "# Generated output, one entry per tool. Add or drop tools freely."
|
|
61
85
|
echo "targets:"
|
|
62
86
|
for t in $targets; do
|
|
63
87
|
case "$t" in
|
|
@@ -65,18 +89,30 @@ manifest="$root/intelligence.yaml"
|
|
|
65
89
|
*) echo " $t: { enabled: true, output: \".$t\" }" ;;
|
|
66
90
|
esac
|
|
67
91
|
done
|
|
92
|
+
echo ""
|
|
93
|
+
echo "# Packages are managed with 'intelligence add' / 'search' / 'list':"
|
|
94
|
+
echo "# intelligence add @ainova-systems/core from the registry"
|
|
95
|
+
echo "# intelligence add github:org/repo any repo with rules|agents|skills"
|
|
96
|
+
echo "# packages:"
|
|
97
|
+
echo "# \"@ainova-systems/core\":"
|
|
98
|
+
echo "# version: \"^0.3.0\""
|
|
99
|
+
echo ""
|
|
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
|
|
68
114
|
} > "$manifest"
|
|
69
115
|
|
|
70
|
-
mkdir -p "$root/intelligence/rules" "$root/intelligence/agents" "$root/intelligence/skills"
|
|
71
|
-
if [ ! -f "$root/intelligence/rules/context.md" ]; then
|
|
72
|
-
cat > "$root/intelligence/rules/context.md" <<EOF
|
|
73
|
-
# Project Context
|
|
74
|
-
|
|
75
|
-
<!-- Always-on context every AI tool receives. Describe what this project is,
|
|
76
|
-
its stack, and the constraints an agent must respect. -->
|
|
77
|
-
EOF
|
|
78
|
-
fi
|
|
79
|
-
|
|
80
116
|
# The package store is restorable state, never committed — the npm model.
|
|
81
117
|
if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
|
|
82
118
|
{
|
|
@@ -86,9 +122,19 @@ if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore
|
|
|
86
122
|
} >> "$root/.gitignore"
|
|
87
123
|
fi
|
|
88
124
|
|
|
89
|
-
|
|
125
|
+
# The engine's own content (meta-skills, authoring rule, engine agents) is an
|
|
126
|
+
# ordinary package: auto-selected, opted out with --bare, removable later with
|
|
127
|
+
# 'intelligence remove @ainova-systems/sync --force'.
|
|
128
|
+
if [ "$bare" -eq 0 ]; then
|
|
129
|
+
sync_pkg_entry "$manifest"
|
|
130
|
+
sync_pkg_install "$root"
|
|
131
|
+
fi
|
|
132
|
+
|
|
90
133
|
echo "initialized: intelligence.yaml (targets:$(printf ' %s' $targets))"
|
|
91
|
-
echo "
|
|
134
|
+
[ "$bare" -eq 1 ] && echo " bare setup: no packages — engine meta-skills not installed"
|
|
135
|
+
echo " add packages: intelligence add @ainova-systems/core (browse: intelligence search)"
|
|
136
|
+
echo " write your own: create $content_dir/rules/context.md, then intelligence sync"
|
|
137
|
+
echo " docs: https://github.com/ainova-systems/intelligence-sync#readme"
|
|
92
138
|
|
|
93
139
|
if [ "$no_sync" -eq 0 ]; then
|
|
94
140
|
cd "$root"
|
package/cli/commands/install.sh
CHANGED
|
@@ -22,10 +22,44 @@ require_v2
|
|
|
22
22
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
23
23
|
lock="$IP_ROOT/intelligence.lock"
|
|
24
24
|
|
|
25
|
-
|
|
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
|
|
26
60
|
|
|
27
61
|
# Manifest packages missing from the lock: resolve them now (npm install
|
|
28
|
-
# semantics). Under --frozen
|
|
62
|
+
# semantics). Under --frozen the drift gate above has already refused.
|
|
29
63
|
missing=""
|
|
30
64
|
while IFS= read -r name; do
|
|
31
65
|
[ -n "$name" ] || continue
|
|
@@ -34,8 +68,13 @@ while IFS= read -r name; do
|
|
|
34
68
|
fi
|
|
35
69
|
done < <(qmap_keys "$manifest" "packages")
|
|
36
70
|
if [ -n "$missing" ]; then
|
|
37
|
-
[ "$frozen" -eq 1 ] && die "lockfile is missing packages:$missing — run 'intelligence install' without --frozen"
|
|
38
71
|
for name in $missing; do
|
|
72
|
+
# The engine-content package needs no remote resolution: its pin IS
|
|
73
|
+
# the bundled engine version and it seeds from the bundle, offline.
|
|
74
|
+
if [ "$name" = "$SYNC_PKG_NAME" ]; then
|
|
75
|
+
sync_pkg_install "$IP_ROOT"
|
|
76
|
+
continue
|
|
77
|
+
fi
|
|
39
78
|
url="$(qmap_field "$manifest" "packages" "$name" "url")"
|
|
40
79
|
path="$(qmap_field "$manifest" "packages" "$name" "path")"
|
|
41
80
|
ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
@@ -66,14 +105,25 @@ if [ -f "$lock" ]; then
|
|
|
66
105
|
if [ -d "$IP_ROOT/$rel" ] && [ "$force" -eq 0 ]; then
|
|
67
106
|
continue
|
|
68
107
|
fi
|
|
69
|
-
|
|
70
|
-
|
|
108
|
+
# Fetch into staging and commit to the store only after the integrity
|
|
109
|
+
# verdict: content that violates the lock must never be left where a
|
|
110
|
+
# re-run (whose dir-exists check skips verification) would sync it.
|
|
111
|
+
staging="$IP_ROOT/.intelligence/.staging-$$"
|
|
112
|
+
rm -rf "$staging"
|
|
113
|
+
got="$(fetch_package "$url" "$resolved" "$path" "$staging")"
|
|
114
|
+
# An empty sha (bundle-seeded content on a dev build) means unknown,
|
|
115
|
+
# not moved — only two known-and-different shas are a finding.
|
|
116
|
+
if [ -n "$sha" ] && [ -n "$got" ] && [ "$got" != "$sha" ]; then
|
|
71
117
|
if [ "$frozen" -eq 1 ]; then
|
|
118
|
+
rm -rf "$staging"
|
|
72
119
|
die "$name: fetched $got but the lock pins $sha — the ref moved; refusing under --frozen"
|
|
73
120
|
fi
|
|
74
121
|
echo " WARN: $name resolved to $got, lock had $sha — updating the lock" >&2
|
|
75
122
|
lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$resolved" "$got"
|
|
76
123
|
fi
|
|
124
|
+
rm -rf "${IP_ROOT:?}/$rel"
|
|
125
|
+
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
126
|
+
mv "$staging" "$IP_ROOT/$rel"
|
|
77
127
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
78
128
|
echo "= $name@${resolved:-HEAD}"
|
|
79
129
|
done < <(lock_to_tsv "$lock")
|
package/cli/commands/migrate.sh
CHANGED
|
@@ -44,13 +44,6 @@ 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
|
-
# A schema gap is closed by the engine's own proven chain before conversion.
|
|
48
|
-
if [ "$dry_run" -eq 0 ] && { [ -z "$stamp" ] || _ver_gt "$eng" "$stamp"; }; then
|
|
49
|
-
echo "== applying the engine migration chain ($stamp -> $eng) =="
|
|
50
|
-
run_migrations "$umbrella" "$(basename "$module_dir")" "" || die "engine migration chain failed (IS_STATUS above) — nothing converted"
|
|
51
|
-
stamp_version "$config" "$eng"
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
47
|
# ---- Stage ----------------------------------------------------------------
|
|
55
48
|
stage="$root/.intelligence-migrate-stage.$$"
|
|
56
49
|
[ "$dry_run" -eq 1 ] && stage="$(mktemp -d -t intelligence-migrate-XXXXXX 2>/dev/null || mktemp -d)"
|
|
@@ -58,8 +51,28 @@ cleanup_stage() { rm -rf "$stage"; }
|
|
|
58
51
|
trap cleanup_stage EXIT INT TERM
|
|
59
52
|
mkdir -p "$stage/.intelligence/packages"
|
|
60
53
|
|
|
54
|
+
# A schema gap is closed by the engine's own proven chain before conversion.
|
|
55
|
+
# Dry-run must preview the SAME pipeline, so it runs the chain too — against
|
|
56
|
+
# a staged copy of the umbrella, leaving the project untouched.
|
|
57
|
+
if [ -z "$stamp" ] || _ver_gt "$eng" "$stamp"; then
|
|
58
|
+
if [ "$dry_run" -eq 1 ]; then
|
|
59
|
+
echo "== applying the engine migration chain ($stamp -> $eng) to a staged copy =="
|
|
60
|
+
cp -R "$umbrella" "$stage/umbrella"
|
|
61
|
+
run_migrations "$stage/umbrella" "$(basename "$module_dir")" "" > "$stage/chain.log" 2>&1 \
|
|
62
|
+
|| { tail -5 "$stage/chain.log" >&2; die "engine migration chain failed in the staged copy — a real migrate would fail the same way"; }
|
|
63
|
+
stamp_version "$stage/umbrella/config.yaml" "$eng"
|
|
64
|
+
config="$stage/umbrella/config.yaml"
|
|
65
|
+
else
|
|
66
|
+
echo "== applying the engine migration chain ($stamp -> $eng) =="
|
|
67
|
+
run_migrations "$umbrella" "$(basename "$module_dir")" "" || die "engine migration chain failed (IS_STATUS above) — nothing converted"
|
|
68
|
+
stamp_version "$config" "$eng"
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
61
72
|
echo "== staging =="
|
|
62
|
-
|
|
73
|
+
# Engine content arrives as the @ainova-systems/sync package, seeded from the
|
|
74
|
+
# bundle (fetch_package's guard — no network).
|
|
75
|
+
sync_pkg_sha="$(fetch_package "$SYNC_PKG_URL" "v$eng" "$SYNC_PKG_PATH" "$stage/$SYNC_PKG_STORE")"
|
|
63
76
|
|
|
64
77
|
# Legacy pack name -> v2 package name (@org/repo derived from the url), plus
|
|
65
78
|
# per-pack source facts. Recorded as rows for the config rewrite and the lock.
|
|
@@ -117,7 +130,7 @@ rewrite_source_value() {
|
|
|
117
130
|
local v="$1" pack name rest
|
|
118
131
|
case "$v" in
|
|
119
132
|
"$module_rel"/rules|"$module_rel"/agents|"$module_rel"/skills)
|
|
120
|
-
printf '
|
|
133
|
+
printf '%s/%s' "$SYNC_PKG_STORE" "${v##*/}"
|
|
121
134
|
return 0 ;;
|
|
122
135
|
@*)
|
|
123
136
|
rest="${v#@}"
|
|
@@ -168,6 +181,13 @@ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
|
168
181
|
[ -n "$ref" ] && qmap_set "$manifest_stage" "packages" "$name" "ref" "$ref"
|
|
169
182
|
done < "$pack_rows"
|
|
170
183
|
|
|
184
|
+
# The engine's own content rides along as a package, pinned to the engine.
|
|
185
|
+
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "version" "$eng"
|
|
186
|
+
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "url" "$SYNC_PKG_URL"
|
|
187
|
+
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "path" "$SYNC_PKG_PATH"
|
|
188
|
+
printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
|
|
189
|
+
"$SYNC_PKG_NAME" "$eng" "$SYNC_PKG_URL" "$SYNC_PKG_PATH" "v$eng" "$sync_pkg_sha" >> "$lock_rows"
|
|
190
|
+
|
|
171
191
|
# ---- Verify ---------------------------------------------------------------
|
|
172
192
|
echo "== verifying staged state =="
|
|
173
193
|
fail=0
|
|
@@ -203,6 +223,13 @@ fi
|
|
|
203
223
|
|
|
204
224
|
# ---- Commit ---------------------------------------------------------------
|
|
205
225
|
echo "== committing =="
|
|
226
|
+
# .gitignore is part of the transaction: rollback must leave it exactly as
|
|
227
|
+
# found, absent included.
|
|
228
|
+
gitignore_existed=0
|
|
229
|
+
if [ -f "$root/.gitignore" ]; then
|
|
230
|
+
gitignore_existed=1
|
|
231
|
+
cp "$root/.gitignore" "$stage/gitignore.orig"
|
|
232
|
+
fi
|
|
206
233
|
if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
|
|
207
234
|
{
|
|
208
235
|
[ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
|
|
@@ -212,6 +239,11 @@ if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore
|
|
|
212
239
|
fi
|
|
213
240
|
rollback() {
|
|
214
241
|
rm -rf "$root/.intelligence" "$root/intelligence.yaml" "$root/intelligence.lock"
|
|
242
|
+
if [ "$gitignore_existed" -eq 1 ]; then
|
|
243
|
+
cp "$stage/gitignore.orig" "$root/.gitignore"
|
|
244
|
+
else
|
|
245
|
+
rm -f "$root/.gitignore"
|
|
246
|
+
fi
|
|
215
247
|
echo "rolled back — the vendored setup is untouched" >&2
|
|
216
248
|
}
|
|
217
249
|
mv "$stage/.intelligence" "$root/.intelligence"
|
package/cli/commands/registry.sh
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# intelligence registry <list | add
|
|
2
|
+
# intelligence registry <list | add <registry-repo-url> [--force] | remove <url>>
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# The registries block is a TRUST LIST: git repos holding an index.yaml, in
|
|
5
|
+
# trust order. Adding one is a committed, reviewable act — after it, every
|
|
6
|
+
# name that registry declares installs like a bundled one. No scope labels:
|
|
7
|
+
# names carry their own scope, and the first registry to declare a name wins.
|
|
6
8
|
set -euo pipefail
|
|
7
9
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
8
10
|
|
|
@@ -11,54 +13,71 @@ case "$sub" in
|
|
|
11
13
|
list)
|
|
12
14
|
detect_project
|
|
13
15
|
if [ "$IP_MODE" = "v2" ] && [ -f "$IP_ROOT/intelligence.yaml" ]; then
|
|
14
|
-
echo "Project
|
|
16
|
+
echo "Project registries (intelligence.yaml, trust order):"
|
|
15
17
|
found=0
|
|
16
|
-
while IFS= read -r
|
|
17
|
-
[ -n "$
|
|
18
|
+
while IFS= read -r url; do
|
|
19
|
+
[ -n "$url" ] || continue
|
|
18
20
|
found=1
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
index="$(_fetch_index "${url#git+}")"
|
|
22
|
+
if [ -n "$index" ]; then
|
|
23
|
+
n="$(qmap_keys "$index" "packages" | grep -c . || true)"
|
|
24
|
+
echo " $url — $n package(s)"
|
|
25
|
+
else
|
|
26
|
+
echo " $url — UNREACHABLE or no index.yaml"
|
|
27
|
+
fi
|
|
28
|
+
done < <(registries_list "$IP_ROOT/intelligence.yaml")
|
|
21
29
|
[ "$found" -eq 0 ] && echo " (none)"
|
|
22
30
|
fi
|
|
23
|
-
|
|
24
|
-
if [ -n "$bundled" ]; then
|
|
25
|
-
echo "Bundled index:"
|
|
26
|
-
while IFS= read -r name; do
|
|
27
|
-
[ -n "$name" ] || continue
|
|
28
|
-
echo " $name -> $(qmap_field "$bundled" "packages" "$name" "url")"
|
|
29
|
-
done < <(qmap_keys "$bundled" "packages")
|
|
30
|
-
fi
|
|
31
|
-
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>."
|
|
32
32
|
;;
|
|
33
33
|
add)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
url="${2:-}"
|
|
35
|
+
force=0
|
|
36
|
+
[ "${3:-}" = "--force" ] && force=1
|
|
37
|
+
[ -n "$url" ] || die "usage: intelligence registry add <registry-repo-url> [--force]"
|
|
38
|
+
case "$url" in
|
|
39
|
+
@*) die "registries are added by URL — names carry their own scope. To install a package: intelligence add $url/<name>" ;;
|
|
40
|
+
esac
|
|
37
41
|
require_v2
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
+
# Fail closed: a URL that cannot produce an index is not a registry,
|
|
43
|
+
# and recording it would turn a typo here into a confusing failure at
|
|
44
|
+
# the first add. --force covers the one honest case: the registry is
|
|
45
|
+
# not published (or reachable) yet.
|
|
42
46
|
index="$(_fetch_index "${url#git+}")"
|
|
43
47
|
if [ -n "$index" ]; then
|
|
44
48
|
n="$(qmap_keys "$index" "packages" | grep -c . || true)"
|
|
45
|
-
echo " index.yaml reachable — $n package(s)
|
|
49
|
+
echo " index.yaml reachable — $n package(s):"
|
|
50
|
+
qmap_keys "$index" "packages" | sed 's/^/ /'
|
|
51
|
+
elif [ "$force" -eq 1 ]; then
|
|
52
|
+
echo " WARN: no index.yaml at $url — recording anyway (--force)." >&2
|
|
46
53
|
else
|
|
47
|
-
echo "
|
|
48
|
-
echo "
|
|
49
|
-
echo "
|
|
54
|
+
echo "ERROR: no index.yaml found at $url — not adding it." >&2
|
|
55
|
+
echo " A registry is a git repo with index.yaml at its root, mapping names to sources:" >&2
|
|
56
|
+
echo " packages:" >&2
|
|
57
|
+
echo " \"@acme/backend\":" >&2
|
|
58
|
+
echo " url: \"https://github.com/acme/intelligence.git\"" >&2
|
|
59
|
+
echo " path: \"packs/backend\" # omit when the repo IS the package" >&2
|
|
60
|
+
echo " Format and an example: https://github.com/ainova-systems/intelligence-registry" >&2
|
|
61
|
+
echo " A single repository needs no registry at all: intelligence add github:org/repo" >&2
|
|
62
|
+
echo " Recording it ahead of a registry that does not exist yet: --force" >&2
|
|
63
|
+
exit 1
|
|
50
64
|
fi
|
|
51
|
-
|
|
52
|
-
echo "
|
|
65
|
+
registries_add "$IP_ROOT/intelligence.yaml" "$url"
|
|
66
|
+
echo "added: $url"
|
|
53
67
|
;;
|
|
54
68
|
remove)
|
|
55
|
-
|
|
56
|
-
[ -n "$
|
|
69
|
+
url="${2:-}"
|
|
70
|
+
[ -n "$url" ] || die "usage: intelligence registry remove <url>"
|
|
57
71
|
require_v2
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
registries_remove "$IP_ROOT/intelligence.yaml" "$url"
|
|
73
|
+
# The retired flat-map form also stored scope keys — drop one if the
|
|
74
|
+
# argument names it, so old manifests clean up with the old command.
|
|
75
|
+
case "$url" in
|
|
76
|
+
@*) qmap_delete_key "$IP_ROOT/intelligence.yaml" "registries" "$url" ;;
|
|
77
|
+
esac
|
|
78
|
+
echo "removed: $url"
|
|
60
79
|
;;
|
|
61
80
|
*)
|
|
62
|
-
die "usage: intelligence registry <list | add
|
|
81
|
+
die "usage: intelligence registry <list | add <url> [--force] | remove <url>>"
|
|
63
82
|
;;
|
|
64
83
|
esac
|
package/cli/commands/remove.sh
CHANGED
|
@@ -6,15 +6,27 @@ source "$CLI_DIR/lib/cli-common.sh"
|
|
|
6
6
|
name="${1:-}"
|
|
7
7
|
[ -n "$name" ] || die "usage: intelligence remove <@scope/name>"
|
|
8
8
|
shift || true
|
|
9
|
-
no_sync=0
|
|
9
|
+
no_sync=0 force=0
|
|
10
10
|
for a in "$@"; do
|
|
11
11
|
case "$a" in
|
|
12
12
|
--no-sync) no_sync=1 ;;
|
|
13
|
+
--force) force=1 ;;
|
|
13
14
|
*) die "unknown option '$a'" ;;
|
|
14
15
|
esac
|
|
15
16
|
done
|
|
16
17
|
|
|
17
18
|
require_v2
|
|
19
|
+
|
|
20
|
+
# Removing the engine-content package guts the outputs (meta-skills, the
|
|
21
|
+
# authoring rule, both engine agents disappear) while everything still
|
|
22
|
+
# reports ok — legal, but never by accident.
|
|
23
|
+
if [ "$name" = "$SYNC_PKG_NAME" ] && [ "$force" -eq 0 ]; then
|
|
24
|
+
echo "ERROR: $SYNC_PKG_NAME is the engine's own content — removing it drops every" >&2
|
|
25
|
+
echo " intelligence-* meta-skill, the authoring rule and the engine agents from" >&2
|
|
26
|
+
echo " the outputs. The sync engine itself keeps working (it ships with the CLI)." >&2
|
|
27
|
+
echo " If that is what you want: intelligence remove $SYNC_PKG_NAME --force" >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
18
30
|
assert_valid_pkg_name "$name"
|
|
19
31
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
20
32
|
lock="$IP_ROOT/intelligence.lock"
|
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
|
|
|
@@ -33,6 +33,9 @@ pkg_state() {
|
|
|
33
33
|
|
|
34
34
|
seen=" "
|
|
35
35
|
rows=0
|
|
36
|
+
# emit_index <index-file> <origin-label>
|
|
37
|
+
# Registries are consulted in trust order; the first to declare a name wins,
|
|
38
|
+
# so a name already seen is not repeated (that IS the resolution order).
|
|
36
39
|
emit_index() {
|
|
37
40
|
local index="$1" origin="$2" name desc state
|
|
38
41
|
[ -n "$index" ] && [ -f "$index" ] || return 0
|
|
@@ -56,29 +59,25 @@ emit_index() {
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
if [ -n "$manifest" ]; then
|
|
59
|
-
while IFS= read -r
|
|
60
|
-
[ -n "$scope" ] || continue
|
|
61
|
-
url="$(qmap_value "$manifest" "registries" "$scope")"
|
|
62
|
+
while IFS= read -r url; do
|
|
62
63
|
[ -n "$url" ] || continue
|
|
63
64
|
index="$(_fetch_index "${url#git+}")"
|
|
64
65
|
if [ -z "$index" ]; then
|
|
65
|
-
echo " WARN: registry
|
|
66
|
+
echo " WARN: registry unreachable or missing index.yaml: $url" >&2
|
|
66
67
|
continue
|
|
67
68
|
fi
|
|
68
|
-
emit_index "$index" "$
|
|
69
|
-
done < <(
|
|
69
|
+
emit_index "$index" "$url"
|
|
70
|
+
done < <(registries_list "$manifest")
|
|
70
71
|
fi
|
|
71
|
-
emit_index "$(default_index_file)" "bundled"
|
|
72
|
-
|
|
73
72
|
if [ "$rows" -eq 0 ]; then
|
|
74
73
|
if [ -n "$term" ]; then
|
|
75
|
-
echo "Nothing matching '$term' in
|
|
74
|
+
echo "Nothing matching '$term' in this project's trusted registries."
|
|
76
75
|
else
|
|
77
|
-
echo "No registries
|
|
76
|
+
echo "No trusted registries (or they offer nothing) — intelligence registry add <repo-url>."
|
|
78
77
|
fi
|
|
79
|
-
echo "
|
|
78
|
+
echo "Explicit installs need no registry: intelligence add github:org/repo"
|
|
80
79
|
exit 0
|
|
81
80
|
fi
|
|
82
81
|
|
|
83
82
|
echo ""
|
|
84
|
-
echo "add one: intelligence add <name> | private registry: intelligence registry add
|
|
83
|
+
echo "add one: intelligence add <name> | private registry: intelligence registry add <repo-url>"
|