@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/cli/lib/cli-common.sh
CHANGED
|
@@ -7,32 +7,67 @@
|
|
|
7
7
|
# CLI-owned parsing lives in lib/manifest.sh (quoted-key `packages:` /
|
|
8
8
|
# `registries:` blocks, which the engine deliberately never reads).
|
|
9
9
|
|
|
10
|
-
# Engine libraries (readers, is_status, IS_RC_*, engine_version).
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
source "$IS_ENGINE_DIR/
|
|
14
|
-
source "$IS_ENGINE_DIR/scripts/lib/layout.sh"
|
|
15
|
-
source "$IS_ENGINE_DIR/scripts/lib/migrations.sh"
|
|
10
|
+
# Engine libraries (readers, is_status, IS_RC_*, engine_version). CLI_DIR /
|
|
11
|
+
# IS_ENGINE_DIR come exported from the dispatcher.
|
|
12
|
+
source "$IS_ENGINE_DIR/lib/common.sh"
|
|
13
|
+
source "$IS_ENGINE_DIR/lib/contract.sh"
|
|
16
14
|
|
|
17
15
|
die() {
|
|
18
16
|
echo "ERROR: $*" >&2
|
|
19
17
|
exit 1
|
|
20
18
|
}
|
|
21
19
|
|
|
20
|
+
# --- Untrusted-input guards ----------------------------------------------
|
|
21
|
+
# Manifest, lock and registry indexes arrive with a cloned repo: every value
|
|
22
|
+
# they carry is attacker-adjacent. URLs and refs reach git argv (a leading
|
|
23
|
+
# `-` would be an option — `--upload-pack=<cmd>` is code execution), names
|
|
24
|
+
# become store paths fed to rm -rf. Guard at the choke points, loudly.
|
|
25
|
+
|
|
26
|
+
# assert_safe_source_url <url> — scheme allowlist (the engine's own list) or
|
|
27
|
+
# scp-like user@host:path; never option-shaped, never quote-bearing.
|
|
28
|
+
assert_safe_source_url() {
|
|
29
|
+
local url="$1"
|
|
30
|
+
case "$url" in
|
|
31
|
+
""|-*) die "unsafe source url '$url' — option-shaped or empty" ;;
|
|
32
|
+
*[\"\'\ ]*) die "unsafe source url '$url' — quotes or spaces" ;;
|
|
33
|
+
https://*|http://*|ssh://*|git://*|file://*) ;;
|
|
34
|
+
*@*:*) ;;
|
|
35
|
+
*) die "unsafe source url '$url' — allowed: https, http, ssh, git, file, or user@host:path" ;;
|
|
36
|
+
esac
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# assert_safe_ref <ref-or-empty> — tags/branches/SHAs; never option-shaped.
|
|
40
|
+
assert_safe_ref() {
|
|
41
|
+
local ref="$1"
|
|
42
|
+
[ -z "$ref" ] && return 0
|
|
43
|
+
case "$ref" in
|
|
44
|
+
-*) die "unsafe git ref '$ref' — option-shaped" ;;
|
|
45
|
+
*[\"\'\ \\]*) die "unsafe git ref '$ref'" ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
22
49
|
source "$CLI_DIR/lib/manifest.sh"
|
|
23
50
|
source "$CLI_DIR/lib/semver.sh"
|
|
24
51
|
source "$CLI_DIR/lib/registry.sh"
|
|
25
52
|
source "$CLI_DIR/lib/lockfile.sh"
|
|
26
53
|
|
|
27
|
-
# The engine
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
# The engine-content package: OPTIONAL but auto-selected at init. Package by
|
|
55
|
+
# UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
|
|
56
|
+
# at the version the CLI ships, it materializes from the npm bundle without
|
|
57
|
+
# network; only a cross-version install reaches git. The pin is held exactly
|
|
58
|
+
# at the bundled engine version and moved only by `upgrade`.
|
|
59
|
+
#
|
|
60
|
+
# Its identity is DATA shipped with the distribution (cli/engine-package.yaml),
|
|
61
|
+
# never a name compiled into cli code — a fork edits the file.
|
|
62
|
+
_EPKG="$CLI_DIR/engine-package.yaml"
|
|
63
|
+
SYNC_PKG_NAME="$(top_scalar "$_EPKG" "name")"
|
|
64
|
+
SYNC_PKG_URL="$(top_scalar "$_EPKG" "url")"
|
|
65
|
+
SYNC_PKG_PATH="$(top_scalar "$_EPKG" "path")"
|
|
66
|
+
# Read by commands (init seeds it) — per-file shellcheck cannot see that.
|
|
67
|
+
# shellcheck disable=SC2034
|
|
68
|
+
DEFAULT_REGISTRY_URL="$(top_scalar "$_EPKG" "default_registry")"
|
|
69
|
+
[ -n "$SYNC_PKG_NAME" ] || die "corrupt CLI installation: $_EPKG is missing or has no 'name'"
|
|
70
|
+
SYNC_PKG_STORE=".intelligence/packages/$SYNC_PKG_NAME"
|
|
36
71
|
|
|
37
72
|
# --- Project detection ---------------------------------------------------
|
|
38
73
|
# Sets: IP_MODE (v2|legacy|none), IP_ROOT, IP_UMBRELLA, IP_MODULE_DIR.
|
|
@@ -92,7 +127,7 @@ manifest_intelligence_dir() {
|
|
|
92
127
|
}
|
|
93
128
|
|
|
94
129
|
bundled_engine_version() {
|
|
95
|
-
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/
|
|
130
|
+
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/VERSION"
|
|
96
131
|
}
|
|
97
132
|
|
|
98
133
|
# --- The sync package's manifest/lock plumbing ---------------------------
|
|
@@ -127,7 +162,7 @@ export_engine_env() {
|
|
|
127
162
|
export CONFIG_FILE="$root/intelligence.yaml"
|
|
128
163
|
export REPO_ROOT="$root"
|
|
129
164
|
export IS_UMBRELLA_REL="$umbrella"
|
|
130
|
-
export IS_MODULE_REL="
|
|
165
|
+
export IS_MODULE_REL="$SYNC_PKG_STORE"
|
|
131
166
|
export IS_SYNC_CMD="intelligence sync"
|
|
132
167
|
export IS_MANIFEST_NAME="intelligence.yaml"
|
|
133
168
|
export IS_PROTECTED_DIRS="$umbrella:.intelligence"
|
package/cli/lib/lockfile.sh
CHANGED
|
@@ -54,14 +54,18 @@ lock_write_from_tsv() {
|
|
|
54
54
|
echo "lockfile_version: $LOCKFILE_VERSION"
|
|
55
55
|
echo "engine_version: \"$(bundled_engine_version)\""
|
|
56
56
|
echo "packages:"
|
|
57
|
+
# Values reach a double-quoted YAML scalar: a `"` or `\` in any of
|
|
58
|
+
# them (registry-supplied url/path included) must be escaped, or it
|
|
59
|
+
# would break or alias the neighbouring field.
|
|
57
60
|
awk -F"$LOCK_SEP" '
|
|
61
|
+
function esc(s) { gsub(/\\/, "\\\\", s); gsub(/"/, "\\\"", s); return s }
|
|
58
62
|
NF >= 6 {
|
|
59
|
-
print " \"" $1 "\":"
|
|
60
|
-
if ($2 != "") print " requested: \"" $2 "\""
|
|
61
|
-
print " url: \"" $3 "\""
|
|
62
|
-
if ($4 != "") print " path: \"" $4 "\""
|
|
63
|
-
if ($5 != "") print " resolved: \"" $5 "\""
|
|
64
|
-
print " sha: \"" $6 "\""
|
|
63
|
+
print " \"" esc($1) "\":"
|
|
64
|
+
if ($2 != "") print " requested: \"" esc($2) "\""
|
|
65
|
+
print " url: \"" esc($3) "\""
|
|
66
|
+
if ($4 != "") print " path: \"" esc($4) "\""
|
|
67
|
+
if ($5 != "") print " resolved: \"" esc($5) "\""
|
|
68
|
+
print " sha: \"" esc($6) "\""
|
|
65
69
|
}
|
|
66
70
|
' "$tsv"
|
|
67
71
|
} > "$tmp"
|
|
@@ -72,7 +76,8 @@ lock_write_from_tsv() {
|
|
|
72
76
|
# Replace-or-append one entry, keeping the others.
|
|
73
77
|
lock_upsert() {
|
|
74
78
|
local lock="$1" name="$2" requested="$3" url="$4" path="$5" resolved="$6" sha="$7"
|
|
75
|
-
local tsv
|
|
79
|
+
local tsv
|
|
80
|
+
tsv="$(mktemp -t intelligence-lock-XXXXXX 2>/dev/null || mktemp)"
|
|
76
81
|
{
|
|
77
82
|
lock_to_tsv "$lock" | awk -F"$LOCK_SEP" -v n="$name" '$1 != n'
|
|
78
83
|
printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
|
|
@@ -86,7 +91,8 @@ lock_upsert() {
|
|
|
86
91
|
lock_remove() {
|
|
87
92
|
local lock="$1" name="$2"
|
|
88
93
|
[ -f "$lock" ] || return 0
|
|
89
|
-
local tsv
|
|
94
|
+
local tsv
|
|
95
|
+
tsv="$(mktemp -t intelligence-lock-XXXXXX 2>/dev/null || mktemp)"
|
|
90
96
|
lock_to_tsv "$lock" | awk -F"$LOCK_SEP" -v n="$name" '$1 != n' > "$tsv"
|
|
91
97
|
if [ -s "$tsv" ]; then
|
|
92
98
|
lock_write_from_tsv "$lock" "$tsv"
|
package/cli/lib/manifest.sh
CHANGED
|
@@ -24,6 +24,16 @@ assert_valid_pkg_name() {
|
|
|
24
24
|
case "$1" in
|
|
25
25
|
*[!@/A-Za-z0-9._-]*|*/*/*) die "invalid package name '$1' — allowed: letters, digits, . _ -, one '/'" ;;
|
|
26
26
|
esac
|
|
27
|
+
# Both segments become store path components fed to rm -rf: an empty one
|
|
28
|
+
# ('@scope/') resolves to the whole scope dir, and a dot segment walks the
|
|
29
|
+
# tree — either would turn one bad --name into deleting other packages.
|
|
30
|
+
local scope="${1%%/*}" short="${1#*/}"
|
|
31
|
+
case "${scope#@}" in
|
|
32
|
+
""|.|..) die "invalid package name '$1' — empty or dot scope" ;;
|
|
33
|
+
esac
|
|
34
|
+
case "$short" in
|
|
35
|
+
""|.|..) die "invalid package name '$1' — empty or dot name" ;;
|
|
36
|
+
esac
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
# qmap_keys <file> <block> — quoted keys, one per line.
|
|
@@ -104,9 +114,19 @@ _qmap_stage() {
|
|
|
104
114
|
|
|
105
115
|
# qmap_set <file> <block> <key> <field> <value> — upsert one field, creating
|
|
106
116
|
# the block and the key as needed.
|
|
117
|
+
# _yq_esc <string> — escape \ and " for a double-quoted YAML scalar.
|
|
118
|
+
_yq_esc() {
|
|
119
|
+
local s="$1"
|
|
120
|
+
s="${s//\\/\\\\}"
|
|
121
|
+
printf '%s' "${s//\"/\\\"}"
|
|
122
|
+
}
|
|
123
|
+
|
|
107
124
|
qmap_set() {
|
|
108
125
|
local file="$1" block="$2" key="$3" field="$4" value="$5"
|
|
109
126
|
[ -f "$file" ] || die "no such file: $file"
|
|
127
|
+
# url/path values can carry a `"`; escape before it reaches the quoted
|
|
128
|
+
# scalar the writer emits.
|
|
129
|
+
value="$(_yq_esc "$value")"
|
|
110
130
|
_qmap_stage "$file" -v block="$block" -v key="$key" -v field="$field" -v value="$value" '
|
|
111
131
|
function keyline() { return " \"" key "\":" }
|
|
112
132
|
function fieldline() { return " " field ": \"" value "\"" }
|
|
@@ -153,6 +173,7 @@ qmap_set() {
|
|
|
153
173
|
qmap_set_value() {
|
|
154
174
|
local file="$1" block="$2" key="$3" value="$4"
|
|
155
175
|
[ -f "$file" ] || die "no such file: $file"
|
|
176
|
+
value="$(_yq_esc "$value")"
|
|
156
177
|
_qmap_stage "$file" -v block="$block" -v key="$key" -v value="$value" '
|
|
157
178
|
function entry() { return " \"" key "\": \"" value "\"" }
|
|
158
179
|
{ sub(/\r$/, "") }
|
|
@@ -272,9 +293,46 @@ registries_remove() {
|
|
|
272
293
|
{ print }
|
|
273
294
|
'
|
|
274
295
|
}
|
|
296
|
+
# True (0) if <config> already lists <entry> under any sources section. Quoted
|
|
297
|
+
# and bare spellings both count - a manifest may hold either.
|
|
298
|
+
_mig_has_source() {
|
|
299
|
+
local config="$1" entry="$2"
|
|
300
|
+
[ -f "$config" ] || return 1
|
|
301
|
+
grep -Fq -- "\"$entry\"" "$config" || grep -Fq -- "- $entry" "$config"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# sources_add_entry_first <file> <section> <entry> — idempotent insert at the
|
|
306
|
+
# TOP of sources.<section> (creating sources:/section as needed). Package
|
|
307
|
+
# entries go through this so project-owned entries stay later in the list —
|
|
308
|
+
# adapters copy sources in order and the last write wins, which is exactly
|
|
309
|
+
# the documented "your file overrides the package's" behavior.
|
|
310
|
+
sources_add_entry_first() {
|
|
311
|
+
local file="$1" section="$2" entry="$3"
|
|
312
|
+
_mig_has_source "$file" "$entry" && return 0
|
|
313
|
+
_qmap_stage "$file" -v section="$section" -v entry="$entry" '
|
|
314
|
+
function line() { return " - \"" entry "\"" }
|
|
315
|
+
{ sub(/\r$/, "") }
|
|
316
|
+
/^sources:[ \t]*$/ { ins = 1; sourceseen = 1; print; next }
|
|
317
|
+
ins && /^[^ #]/ {
|
|
318
|
+
if (!secseen && !done) { print " " section ":"; print line(); done = 1 }
|
|
319
|
+
ins = 0
|
|
320
|
+
}
|
|
321
|
+
ins && $0 ~ "^ " section ":[ \t]*$" { secseen = 1; print; print line(); done = 1; next }
|
|
322
|
+
{ print }
|
|
323
|
+
END {
|
|
324
|
+
if (ins && !secseen && !done) { print " " section ":"; print line(); done = 1 }
|
|
325
|
+
if (!sourceseen) {
|
|
326
|
+
print "sources:"
|
|
327
|
+
print " " section ":"
|
|
328
|
+
print line()
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
'
|
|
332
|
+
}
|
|
275
333
|
|
|
276
334
|
# sources_remove_entry <file> <section> <entry> — remove `- "entry"` from
|
|
277
|
-
# sources.<section>.
|
|
335
|
+
# sources.<section>.
|
|
278
336
|
sources_remove_entry() {
|
|
279
337
|
local file="$1" section="$2" entry="$3"
|
|
280
338
|
[ -f "$file" ] || return 0
|
package/cli/lib/registry.sh
CHANGED
|
@@ -1,42 +1,46 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Name -> source resolution and package fetching.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
4
|
+
# A name resolves ONLY through the manifest's `registries:` trust list (git
|
|
5
|
+
# repos holding index.yaml, consulted in order; the first to declare the name
|
|
6
|
+
# wins). There is deliberately no built-in catalog and no name->github
|
|
7
|
+
# convention: the CLI core knows no vendor, and a name nobody explicitly
|
|
8
|
+
# trusted must never silently turn into an install from a guessed URL.
|
|
9
|
+
# Sources without a registry are always EXPLICIT: github:org/repo, git+<url>.
|
|
10
|
+
# Vendor defaults exist only as lines `init` writes into the manifest —
|
|
11
|
+
# visible, reviewable, deletable.
|
|
11
12
|
#
|
|
12
13
|
# The NAME is the trust anchor a developer reasons with; source integrity is
|
|
13
|
-
# a separate mechanism: the lock pins url+sha,
|
|
14
|
-
# shadows a bundled name with a different url warns loudly, and doctor flags
|
|
14
|
+
# a separate mechanism: the lock pins url+sha, and doctor flags
|
|
15
15
|
# resolution/lock url drift.
|
|
16
16
|
#
|
|
17
17
|
# An index is itself fetched with git (never curl): auth, proxies and private
|
|
18
18
|
# hosting all come for free, and the CLI keeps the engine's bash+awk+git-only
|
|
19
19
|
# dependency footprint.
|
|
20
20
|
|
|
21
|
-
default_index_file() {
|
|
22
|
-
local f="$CLI_DIR/../registry/index.yaml"
|
|
23
|
-
[ -f "$f" ] && printf '%s' "$f"
|
|
24
|
-
return 0
|
|
25
|
-
}
|
|
26
|
-
|
|
27
21
|
# _fetch_index <registry-repo-url> — clones the registry repo (shallow) and
|
|
28
22
|
# prints the path of its index.yaml; empty on failure. Cached per run+url.
|
|
29
23
|
_fetch_index() {
|
|
30
|
-
local url="$1"
|
|
31
|
-
|
|
24
|
+
local url="$1" key tmp
|
|
25
|
+
assert_safe_source_url "$url"
|
|
26
|
+
# Per-run private cache dir under an UNPREDICTABLE mktemp path (a fixed
|
|
27
|
+
# /tmp/...-$$ would be trusted on cache hit and pre-creatable by others).
|
|
28
|
+
# No EXIT trap: _fetch_index is always called inside $( ), so a trap would
|
|
29
|
+
# fire in that subshell and delete the dir before the caller reads it. The
|
|
30
|
+
# dir is small and OS-reaped from the temp root; IS_REMOTE_CACHE-style
|
|
31
|
+
# persistence is exported so repeated calls in one command share it.
|
|
32
|
+
if [ -z "${IS_CLI_INDEX_CACHE:-}" ]; then
|
|
33
|
+
IS_CLI_INDEX_CACHE="$(mktemp -d -t intelligence-cli-index-XXXXXX 2>/dev/null || mktemp -d)"
|
|
34
|
+
export IS_CLI_INDEX_CACHE
|
|
35
|
+
fi
|
|
36
|
+
local cache="$IS_CLI_INDEX_CACHE"
|
|
32
37
|
key="$(printf '%s' "$url" | cksum | awk '{print $1}')"
|
|
33
38
|
if [ -f "$cache/$key/index.yaml" ]; then
|
|
34
39
|
printf '%s' "$cache/$key/index.yaml"
|
|
35
40
|
return 0
|
|
36
41
|
fi
|
|
37
|
-
mkdir -p "$cache"
|
|
38
42
|
tmp="$cache/$key"
|
|
39
|
-
if GIT_TERMINAL_PROMPT=0 git -c core.autocrlf=false clone --depth 1 --quiet "$url" "$tmp" 2>/dev/null \
|
|
43
|
+
if GIT_TERMINAL_PROMPT=0 git -c core.autocrlf=false clone --depth 1 --quiet -- "$url" "$tmp" 2>/dev/null \
|
|
40
44
|
&& [ -f "$tmp/index.yaml" ]; then
|
|
41
45
|
printf '%s' "$tmp/index.yaml"
|
|
42
46
|
fi
|
|
@@ -49,11 +53,9 @@ _fetch_index() {
|
|
|
49
53
|
# shellcheck disable=SC2034
|
|
50
54
|
resolve_package_source() {
|
|
51
55
|
local manifest="$1" name="$2"
|
|
52
|
-
local scope="${name%%/*}" short="${name#*/}"
|
|
53
56
|
RES_URL=""; RES_PATH=""; RES_VIA=""
|
|
54
57
|
|
|
55
|
-
local reg_url index
|
|
56
|
-
bundled="$(default_index_file)"
|
|
58
|
+
local reg_url index
|
|
57
59
|
if [ -n "$manifest" ] && [ -f "$manifest" ]; then
|
|
58
60
|
while IFS= read -r reg_url; do
|
|
59
61
|
[ -n "$reg_url" ] || continue
|
|
@@ -66,37 +68,18 @@ resolve_package_source() {
|
|
|
66
68
|
if [ -n "$RES_URL" ]; then
|
|
67
69
|
RES_PATH="$(qmap_field "$index" "packages" "$name" "path")"
|
|
68
70
|
RES_VIA="registry:$reg_url"
|
|
69
|
-
# Shadowing a bundled name with a DIFFERENT source is legal
|
|
70
|
-
# (that is what overriding means) but never silent.
|
|
71
|
-
if [ -n "$bundled" ]; then
|
|
72
|
-
burl="$(qmap_field "$bundled" "packages" "$name" "url")"
|
|
73
|
-
if [ -n "$burl" ] && [ "$burl" != "$RES_URL" ]; then
|
|
74
|
-
echo " WARN: $name from this registry overrides the bundled source ($burl)" >&2
|
|
75
|
-
fi
|
|
76
|
-
fi
|
|
77
71
|
return 0
|
|
78
72
|
fi
|
|
79
73
|
done < <(registries_list "$manifest")
|
|
80
74
|
fi
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
RES_URL="$(qmap_field "$bundled" "packages" "$name" "url")"
|
|
84
|
-
if [ -n "$RES_URL" ]; then
|
|
85
|
-
RES_PATH="$(qmap_field "$bundled" "packages" "$name" "path")"
|
|
86
|
-
RES_VIA="index"
|
|
87
|
-
return 0
|
|
88
|
-
fi
|
|
89
|
-
fi
|
|
90
|
-
|
|
91
|
-
RES_URL="https://github.com/${scope#@}/$short.git"
|
|
92
|
-
RES_PATH=""
|
|
93
|
-
RES_VIA="convention"
|
|
75
|
+
# No trusted registry declares the name. Deliberately NOT an invented
|
|
76
|
+
# URL: the caller reports it, with suggestions.
|
|
94
77
|
return 0
|
|
95
78
|
}
|
|
96
79
|
|
|
97
80
|
# suggest_similar <manifest-or-empty> <@scope/name> — "Did you mean" lines to
|
|
98
81
|
# stderr, matched on the short name (singular/plural tolerant) across every
|
|
99
|
-
#
|
|
82
|
+
# trusted registry.
|
|
100
83
|
suggest_similar() {
|
|
101
84
|
local manifest="$1" name="$2"
|
|
102
85
|
local want cand cshort reg_url index seen=" "
|
|
@@ -121,7 +104,6 @@ suggest_similar() {
|
|
|
121
104
|
_suggest_from "$index"
|
|
122
105
|
done < <(registries_list "$manifest")
|
|
123
106
|
fi
|
|
124
|
-
_suggest_from "$(default_index_file)"
|
|
125
107
|
}
|
|
126
108
|
|
|
127
109
|
# fetch_package <url> <ref> <subpath> <dest-dir>
|
|
@@ -131,6 +113,8 @@ suggest_similar() {
|
|
|
131
113
|
# store holds the bytes the package published.
|
|
132
114
|
fetch_package() {
|
|
133
115
|
local url="$1" ref="$2" subpath="$3" dest="$4"
|
|
116
|
+
assert_safe_source_url "$url"
|
|
117
|
+
assert_safe_ref "$ref"
|
|
134
118
|
# Bundle seed: the engine-content package at the CLI's own version copies
|
|
135
119
|
# from the npm bundle — no network, which keeps init / fresh-clone install
|
|
136
120
|
# / migrate offline exactly like the staging they replace. Keyed on the
|
|
@@ -138,12 +122,14 @@ fetch_package() {
|
|
|
138
122
|
# to the normal clone.
|
|
139
123
|
if [ "$url" = "$SYNC_PKG_URL" ] && [ "$subpath" = "$SYNC_PKG_PATH" ] \
|
|
140
124
|
&& [ "${ref#v}" = "$(bundled_engine_version)" ]; then
|
|
125
|
+
[ -n "${IS_BUNDLED_PKG_DIR:-}" ] && [ -d "$IS_BUNDLED_PKG_DIR" ] \
|
|
126
|
+
|| die "bundled engine content not found next to the CLI — reinstall @ainova-systems/intelligence"
|
|
141
127
|
rm -rf "$dest"
|
|
142
128
|
mkdir -p "$dest"
|
|
143
|
-
cp -R "$
|
|
129
|
+
cp -R "$IS_BUNDLED_PKG_DIR/." "$dest/"
|
|
144
130
|
rm -rf "$dest/.git"
|
|
145
|
-
if [ -f "$IS_ENGINE_DIR/
|
|
146
|
-
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/
|
|
131
|
+
if [ -f "$IS_ENGINE_DIR/ENGINE_SHA" ]; then
|
|
132
|
+
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/ENGINE_SHA"
|
|
147
133
|
fi
|
|
148
134
|
return 0
|
|
149
135
|
fi
|
|
@@ -152,20 +138,20 @@ fetch_package() {
|
|
|
152
138
|
[ -n "$ref" ] && branch_arg=(--branch "$ref")
|
|
153
139
|
tmp="$(mktemp -d -t intelligence-pkg-XXXXXX 2>/dev/null || mktemp -d)"
|
|
154
140
|
if ! GIT_TERMINAL_PROMPT=0 git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
|
|
155
|
-
clone --depth 1 "${branch_arg[@]+"${branch_arg[@]}"}" --quiet "$url" "$tmp/clone" 2>/dev/null; then
|
|
141
|
+
clone --depth 1 "${branch_arg[@]+"${branch_arg[@]}"}" --quiet -- "$url" "$tmp/clone" 2>/dev/null; then
|
|
156
142
|
# A SHA is not clonable with --branch — fall back to a full clone + checkout.
|
|
157
143
|
rm -rf "$tmp/clone"
|
|
158
144
|
GIT_TERMINAL_PROMPT=0 git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
|
|
159
|
-
clone --quiet "$url" "$tmp/clone" 2>/dev/null \
|
|
145
|
+
clone --quiet -- "$url" "$tmp/clone" 2>/dev/null \
|
|
160
146
|
|| { rm -rf "$tmp"; die "cannot clone $url"; }
|
|
161
|
-
[ -z "$ref" ] || git -C "$tmp/clone" checkout --quiet "$ref" \
|
|
147
|
+
[ -z "$ref" ] || git -C "$tmp/clone" checkout --quiet "$ref" -- \
|
|
162
148
|
|| { rm -rf "$tmp"; die "ref '$ref' not found in $url"; }
|
|
163
149
|
fi
|
|
164
150
|
sha="$(git -C "$tmp/clone" rev-parse HEAD)"
|
|
165
151
|
src="$tmp/clone"
|
|
166
152
|
if [ -n "$subpath" ]; then
|
|
167
153
|
case "$subpath" in
|
|
168
|
-
|
|
154
|
+
*..*|/*|*[\"\']*) rm -rf "$tmp"; die "unsafe path '$subpath'" ;;
|
|
169
155
|
esac
|
|
170
156
|
src="$tmp/clone/$subpath"
|
|
171
157
|
[ -d "$src" ] || { rm -rf "$tmp"; die "path '$subpath' not found in $url${ref:+@$ref}" ; }
|
|
@@ -182,14 +168,14 @@ fetch_package() {
|
|
|
182
168
|
|
|
183
169
|
# wire_package_sources <manifest> <@scope/name> <store-rel-dir>
|
|
184
170
|
# The provides convention: whichever of rules/agents/skills the installed
|
|
185
|
-
# package has becomes a sources entry
|
|
186
|
-
#
|
|
171
|
+
# package has becomes a sources entry — inserted FIRST in its section, so
|
|
172
|
+
# project-owned entries come later and win on same-named artifacts.
|
|
187
173
|
wire_package_sources() {
|
|
188
174
|
local manifest="$1" name="$2" rel="$3" root="$4"
|
|
189
175
|
local section
|
|
190
176
|
for section in rules agents skills; do
|
|
191
177
|
if [ -d "$root/$rel/$section" ]; then
|
|
192
|
-
|
|
178
|
+
sources_add_entry_first "$manifest" "$section" "$rel/$section"
|
|
193
179
|
fi
|
|
194
180
|
done
|
|
195
181
|
}
|
package/cli/lib/semver.sh
CHANGED
|
@@ -75,10 +75,11 @@ semver_match() {
|
|
|
75
75
|
# onto their tag.
|
|
76
76
|
list_remote_versions() {
|
|
77
77
|
local url="$1"
|
|
78
|
+
assert_safe_source_url "$url"
|
|
78
79
|
# `|| true` inside the pipeline: an unreachable repo must read as "no
|
|
79
80
|
# versions", not kill a pipefail caller mid-command-substitution —
|
|
80
81
|
# reachability is the CALLER's question (add probes it explicitly).
|
|
81
|
-
{ GIT_TERMINAL_PROMPT=0 git ls-remote --tags "$url" 2>/dev/null || true; } \
|
|
82
|
+
{ GIT_TERMINAL_PROMPT=0 git ls-remote --tags -- "$url" 2>/dev/null || true; } \
|
|
82
83
|
| awk '{
|
|
83
84
|
sub(/\r$/, "")
|
|
84
85
|
ref = $2
|
|
@@ -95,7 +96,8 @@ list_remote_versions() {
|
|
|
95
96
|
# prints "<tag> <sha>". Peeled sha (the commit a tag object points at) wins.
|
|
96
97
|
remote_tag_for_version() {
|
|
97
98
|
local url="$1" ver="${2#v}"
|
|
98
|
-
|
|
99
|
+
assert_safe_source_url "$url"
|
|
100
|
+
{ GIT_TERMINAL_PROMPT=0 git ls-remote --tags -- "$url" 2>/dev/null || true; } \
|
|
99
101
|
| awk -v want="$ver" '
|
|
100
102
|
{
|
|
101
103
|
sub(/\r$/, "")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
557cde6997dce612e0e8a5a11adc2472173dcec0
|
|
@@ -13,25 +13,20 @@
|
|
|
13
13
|
|
|
14
14
|
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
15
15
|
|
|
16
|
-
# Repo-relative paths to the
|
|
17
|
-
# never hardcoded. The
|
|
18
|
-
# `
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# driven directly (tests), never assume a folder name.
|
|
16
|
+
# Repo-relative paths to the project's content dir and to the installed sync
|
|
17
|
+
# package, taken from the env contract — never hardcoded. The content dir is
|
|
18
|
+
# named by the project (`intelligence/`, a codename) and the package sits in
|
|
19
|
+
# the store; a literal path here would print a sync command that does not
|
|
20
|
+
# exist, and would be wrong outright on a case-sensitive filesystem — in the
|
|
21
|
+
# one document Cursor, Copilot and Codex all read as canonical.
|
|
22
|
+
# The defaults only cover an adapter driven directly (tests); sync.sh always
|
|
23
|
+
# exports both.
|
|
25
24
|
agents_md_umbrella_rel() {
|
|
26
|
-
|
|
27
|
-
[ -n "$rel" ] || rel="$(repo_rel_dir "$repo_root" "${LS_UMBRELLA_DIR:-$repo_root/intelligence}")"
|
|
28
|
-
printf '%s' "${rel:-intelligence}"
|
|
25
|
+
printf '%s' "${IS_UMBRELLA_REL:-intelligence}"
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
agents_md_module_rel() {
|
|
32
|
-
|
|
33
|
-
[ -n "$rel" ] || rel="$(repo_rel_dir "$repo_root" "${LS_MODULE_DIR:-$repo_root/intelligence/sync}")"
|
|
34
|
-
printf '%s' "${rel:-intelligence/sync}"
|
|
29
|
+
printf '%s' "${IS_MODULE_REL:-.intelligence/packages/@ainova-systems/sync}"
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
# Append the static header block from config.yaml (or a fallback).
|