@ainova-systems/intelligence 0.11.0-rc.5 → 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 +18 -13
- package/cli/commands/init.sh +24 -9
- package/cli/commands/install.sh +47 -3
- package/cli/commands/migrate.sh +30 -7
- package/cli/commands/registry.sh +1 -9
- package/cli/commands/search.sh +4 -6
- package/cli/commands/update.sh +19 -4
- package/cli/engine-package.yaml +14 -0
- package/cli/lib/cli-common.sh +18 -10
- package/cli/lib/manifest.sh +39 -0
- package/cli/lib/registry.sh +16 -44
- package/engine/docs/CLI.md +2 -6
- package/engine/scripts/ENGINE_SHA +1 -1
- package/engine/scripts/sync.sh +1 -1
- package/package.json +2 -3
- package/registry/index.yaml +0 -26
package/cli/commands/add.sh
CHANGED
|
@@ -78,23 +78,24 @@ 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
|
|
|
84
|
-
# The source must exist before anything else is attempted —
|
|
85
|
-
#
|
|
86
|
-
# die mid-pipeline with no message.
|
|
94
|
+
# The source must exist before anything else is attempted — never die
|
|
95
|
+
# mid-pipeline with no message.
|
|
87
96
|
if ! GIT_TERMINAL_PROMPT=0 git ls-remote "$url" >/dev/null 2>&1; then
|
|
88
97
|
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
|
|
98
|
+
echo " Declared by ${RES_VIA:-the spec} — check the URL and your git access." >&2
|
|
98
99
|
exit 1
|
|
99
100
|
fi
|
|
100
101
|
|
|
@@ -124,7 +125,11 @@ sha="$(fetch_package "$url" "${ref:-$resolved_tag}" "$path" "$IP_ROOT/$rel")"
|
|
|
124
125
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
125
126
|
|
|
126
127
|
# Manifest entry: a registry package records its range; a direct one records
|
|
127
|
-
# 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"
|
|
128
133
|
if [ "$mode" = "registry" ]; then
|
|
129
134
|
qmap_set "$manifest" "packages" "$name" "version" "$requested"
|
|
130
135
|
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,8 +22,44 @@ 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
|
|
@@ -32,7 +68,6 @@ while IFS= read -r name; do
|
|
|
32
68
|
fi
|
|
33
69
|
done < <(qmap_keys "$manifest" "packages")
|
|
34
70
|
if [ -n "$missing" ]; then
|
|
35
|
-
[ "$frozen" -eq 1 ] && die "lockfile is missing packages:$missing — run 'intelligence install' without --frozen"
|
|
36
71
|
for name in $missing; do
|
|
37
72
|
# The engine-content package needs no remote resolution: its pin IS
|
|
38
73
|
# the bundled engine version and it seeds from the bundle, offline.
|
|
@@ -70,16 +105,25 @@ if [ -f "$lock" ]; then
|
|
|
70
105
|
if [ -d "$IP_ROOT/$rel" ] && [ "$force" -eq 0 ]; then
|
|
71
106
|
continue
|
|
72
107
|
fi
|
|
73
|
-
|
|
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")"
|
|
74
114
|
# An empty sha (bundle-seeded content on a dev build) means unknown,
|
|
75
115
|
# not moved — only two known-and-different shas are a finding.
|
|
76
116
|
if [ -n "$sha" ] && [ -n "$got" ] && [ "$got" != "$sha" ]; then
|
|
77
117
|
if [ "$frozen" -eq 1 ]; then
|
|
118
|
+
rm -rf "$staging"
|
|
78
119
|
die "$name: fetched $got but the lock pins $sha — the ref moved; refusing under --frozen"
|
|
79
120
|
fi
|
|
80
121
|
echo " WARN: $name resolved to $got, lock had $sha — updating the lock" >&2
|
|
81
122
|
lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$resolved" "$got"
|
|
82
123
|
fi
|
|
124
|
+
rm -rf "${IP_ROOT:?}/$rel"
|
|
125
|
+
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
126
|
+
mv "$staging" "$IP_ROOT/$rel"
|
|
83
127
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
84
128
|
echo "= $name@${resolved:-HEAD}"
|
|
85
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,6 +51,24 @@ 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
|
|
63
74
|
# bundle (fetch_package's guard — no network).
|
|
@@ -212,6 +223,13 @@ fi
|
|
|
212
223
|
|
|
213
224
|
# ---- Commit ---------------------------------------------------------------
|
|
214
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
|
|
215
233
|
if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
|
|
216
234
|
{
|
|
217
235
|
[ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
|
|
@@ -221,6 +239,11 @@ if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore
|
|
|
221
239
|
fi
|
|
222
240
|
rollback() {
|
|
223
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
|
|
224
247
|
echo "rolled back — the vendored setup is untouched" >&2
|
|
225
248
|
}
|
|
226
249
|
mv "$stage/.intelligence" "$root/.intelligence"
|
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:-}"
|
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/update.sh
CHANGED
|
@@ -21,10 +21,13 @@ 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
27
|
[ -n "$only" ] && [ "$name" != "$only" ] && continue
|
|
28
|
+
# A named package that exists is "found" whatever happens next — a skip
|
|
29
|
+
# message followed by "not in the manifest" would contradict itself.
|
|
30
|
+
found=1
|
|
28
31
|
if [ "$name" = "$SYNC_PKG_NAME" ]; then
|
|
29
32
|
echo " $name: engine content moves with the engine — 'intelligence upgrade' owns it"
|
|
30
33
|
continue
|
|
@@ -32,22 +35,34 @@ while IFS= read -r name; do
|
|
|
32
35
|
ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
33
36
|
[ -n "$ref" ] && { echo " $name: pinned to ref '$ref' — skipped"; continue; }
|
|
34
37
|
range="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
38
|
+
# An exact version is a pin, same as ref: — update never moves pins.
|
|
39
|
+
if [ -n "$range" ] && semver_is_stable "$range"; then
|
|
40
|
+
echo " $name: pinned to $range — edit the manifest to move it"
|
|
41
|
+
continue
|
|
42
|
+
fi
|
|
35
43
|
url="$(qmap_field "$manifest" "packages" "$name" "url")"
|
|
36
44
|
path="$(qmap_field "$manifest" "packages" "$name" "path")"
|
|
37
45
|
if [ -z "$url" ]; then
|
|
38
46
|
resolve_package_source "$manifest" "$name"
|
|
39
47
|
url="$RES_URL"; path="$RES_PATH"
|
|
40
48
|
fi
|
|
41
|
-
checked=$((checked + 1))
|
|
42
49
|
picked="$(list_remote_versions "$url" | semver_pick_highest "$range")"
|
|
43
50
|
[ -n "$picked" ] || { echo " $name: nothing satisfies '${range:-*}' at $url" >&2; continue; }
|
|
44
51
|
read -r tag _ <<< "$(remote_tag_for_version "$url" "$picked")"
|
|
45
52
|
current="$(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
46
|
-
|
|
53
|
+
cur_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
54
|
+
cur_path="$(qmap_field "$lock" "packages" "$name" "path")"
|
|
55
|
+
# Same tag is not enough: a registry may re-point a name's source while
|
|
56
|
+
# keeping the version — the move doctor warns about must be actionable
|
|
57
|
+
# here, so url/path changes refetch too.
|
|
58
|
+
if [ "$tag" = "$current" ] && [ "$url" = "$cur_url" ] && [ "$path" = "$cur_path" ]; then
|
|
47
59
|
echo " $name: ${current:-<none>} (up to date)"
|
|
48
60
|
continue
|
|
49
61
|
fi
|
|
50
62
|
rel=".intelligence/packages/$name"
|
|
63
|
+
# The new version may have dropped a top-level section dir: unwire the old
|
|
64
|
+
# shape first, or a dangling store-backed source bricks the next sync.
|
|
65
|
+
unwire_package_sources "$manifest" "$rel"
|
|
51
66
|
sha="$(fetch_package "$url" "$tag" "$path" "$IP_ROOT/$rel")"
|
|
52
67
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
53
68
|
lock_upsert "$lock" "$name" "$range" "$url" "$path" "$tag" "$sha"
|
|
@@ -55,7 +70,7 @@ while IFS= read -r name; do
|
|
|
55
70
|
moved=$((moved + 1))
|
|
56
71
|
done < <(qmap_keys "$manifest" "packages")
|
|
57
72
|
|
|
58
|
-
[ -n "$only" ] && [ "$
|
|
73
|
+
[ -n "$only" ] && [ "$found" -eq 0 ] && die "package '$only' is not in the manifest"
|
|
59
74
|
echo "updated: $moved package(s)"
|
|
60
75
|
|
|
61
76
|
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-sync.git"
|
|
13
|
+
path: "intelligence/sync"
|
|
14
|
+
default_registry: "https://github.com/ainova-systems/intelligence-registry.git"
|
package/cli/lib/cli-common.sh
CHANGED
|
@@ -24,15 +24,23 @@ source "$CLI_DIR/lib/semver.sh"
|
|
|
24
24
|
source "$CLI_DIR/lib/registry.sh"
|
|
25
25
|
source "$CLI_DIR/lib/lockfile.sh"
|
|
26
26
|
|
|
27
|
-
# The engine
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
# The engine-content package: OPTIONAL but auto-selected at init. Package by
|
|
28
|
+
# UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
|
|
29
|
+
# at the version the CLI ships, it materializes from the npm bundle without
|
|
30
|
+
# network; only a cross-version install reaches git. The pin is held exactly
|
|
31
|
+
# at the bundled engine version and moved only by `upgrade`.
|
|
32
|
+
#
|
|
33
|
+
# Its identity is DATA shipped with the distribution (cli/engine-package.yaml),
|
|
34
|
+
# never a name compiled into cli code — a fork edits the file.
|
|
35
|
+
_EPKG="$CLI_DIR/engine-package.yaml"
|
|
36
|
+
SYNC_PKG_NAME="$(top_scalar "$_EPKG" "name")"
|
|
37
|
+
SYNC_PKG_URL="$(top_scalar "$_EPKG" "url")"
|
|
38
|
+
SYNC_PKG_PATH="$(top_scalar "$_EPKG" "path")"
|
|
39
|
+
# Read by commands (init seeds it) — per-file shellcheck cannot see that.
|
|
40
|
+
# shellcheck disable=SC2034
|
|
41
|
+
DEFAULT_REGISTRY_URL="$(top_scalar "$_EPKG" "default_registry")"
|
|
42
|
+
[ -n "$SYNC_PKG_NAME" ] || die "corrupt CLI installation: $_EPKG is missing or has no 'name'"
|
|
43
|
+
SYNC_PKG_STORE=".intelligence/packages/$SYNC_PKG_NAME"
|
|
36
44
|
|
|
37
45
|
# --- Project detection ---------------------------------------------------
|
|
38
46
|
# Sets: IP_MODE (v2|legacy|none), IP_ROOT, IP_UMBRELLA, IP_MODULE_DIR.
|
|
@@ -127,7 +135,7 @@ export_engine_env() {
|
|
|
127
135
|
export CONFIG_FILE="$root/intelligence.yaml"
|
|
128
136
|
export REPO_ROOT="$root"
|
|
129
137
|
export IS_UMBRELLA_REL="$umbrella"
|
|
130
|
-
export IS_MODULE_REL="
|
|
138
|
+
export IS_MODULE_REL="$SYNC_PKG_STORE"
|
|
131
139
|
export IS_SYNC_CMD="intelligence sync"
|
|
132
140
|
export IS_MANIFEST_NAME="intelligence.yaml"
|
|
133
141
|
export IS_PROTECTED_DIRS="$umbrella:.intelligence"
|
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.
|
|
@@ -273,6 +283,35 @@ registries_remove() {
|
|
|
273
283
|
'
|
|
274
284
|
}
|
|
275
285
|
|
|
286
|
+
# sources_add_entry_first <file> <section> <entry> — idempotent insert at the
|
|
287
|
+
# TOP of sources.<section> (creating sources:/section as needed). Package
|
|
288
|
+
# entries go through this so project-owned entries stay later in the list —
|
|
289
|
+
# adapters copy sources in order and the last write wins, which is exactly
|
|
290
|
+
# the documented "your file overrides the package's" behavior.
|
|
291
|
+
sources_add_entry_first() {
|
|
292
|
+
local file="$1" section="$2" entry="$3"
|
|
293
|
+
_mig_has_source "$file" "$entry" && return 0
|
|
294
|
+
_qmap_stage "$file" -v section="$section" -v entry="$entry" '
|
|
295
|
+
function line() { return " - \"" entry "\"" }
|
|
296
|
+
{ sub(/\r$/, "") }
|
|
297
|
+
/^sources:[ \t]*$/ { ins = 1; sourceseen = 1; print; next }
|
|
298
|
+
ins && /^[^ #]/ {
|
|
299
|
+
if (!secseen && !done) { print " " section ":"; print line(); done = 1 }
|
|
300
|
+
ins = 0
|
|
301
|
+
}
|
|
302
|
+
ins && $0 ~ "^ " section ":[ \t]*$" { secseen = 1; print; print line(); done = 1; next }
|
|
303
|
+
{ print }
|
|
304
|
+
END {
|
|
305
|
+
if (ins && !secseen && !done) { print " " section ":"; print line(); done = 1 }
|
|
306
|
+
if (!sourceseen) {
|
|
307
|
+
print "sources:"
|
|
308
|
+
print " " section ":"
|
|
309
|
+
print line()
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
'
|
|
313
|
+
}
|
|
314
|
+
|
|
276
315
|
# sources_remove_entry <file> <section> <entry> — remove `- "entry"` from
|
|
277
316
|
# sources.<section>. Inverse of the engine-side _mig_add_source.
|
|
278
317
|
sources_remove_entry() {
|
package/cli/lib/registry.sh
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
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() {
|
|
@@ -49,11 +43,9 @@ _fetch_index() {
|
|
|
49
43
|
# shellcheck disable=SC2034
|
|
50
44
|
resolve_package_source() {
|
|
51
45
|
local manifest="$1" name="$2"
|
|
52
|
-
local scope="${name%%/*}" short="${name#*/}"
|
|
53
46
|
RES_URL=""; RES_PATH=""; RES_VIA=""
|
|
54
47
|
|
|
55
|
-
local reg_url index
|
|
56
|
-
bundled="$(default_index_file)"
|
|
48
|
+
local reg_url index
|
|
57
49
|
if [ -n "$manifest" ] && [ -f "$manifest" ]; then
|
|
58
50
|
while IFS= read -r reg_url; do
|
|
59
51
|
[ -n "$reg_url" ] || continue
|
|
@@ -66,37 +58,18 @@ resolve_package_source() {
|
|
|
66
58
|
if [ -n "$RES_URL" ]; then
|
|
67
59
|
RES_PATH="$(qmap_field "$index" "packages" "$name" "path")"
|
|
68
60
|
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
61
|
return 0
|
|
78
62
|
fi
|
|
79
63
|
done < <(registries_list "$manifest")
|
|
80
64
|
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"
|
|
65
|
+
# No trusted registry declares the name. Deliberately NOT an invented
|
|
66
|
+
# URL: the caller reports it, with suggestions.
|
|
94
67
|
return 0
|
|
95
68
|
}
|
|
96
69
|
|
|
97
70
|
# suggest_similar <manifest-or-empty> <@scope/name> — "Did you mean" lines to
|
|
98
71
|
# stderr, matched on the short name (singular/plural tolerant) across every
|
|
99
|
-
#
|
|
72
|
+
# trusted registry.
|
|
100
73
|
suggest_similar() {
|
|
101
74
|
local manifest="$1" name="$2"
|
|
102
75
|
local want cand cshort reg_url index seen=" "
|
|
@@ -121,7 +94,6 @@ suggest_similar() {
|
|
|
121
94
|
_suggest_from "$index"
|
|
122
95
|
done < <(registries_list "$manifest")
|
|
123
96
|
fi
|
|
124
|
-
_suggest_from "$(default_index_file)"
|
|
125
97
|
}
|
|
126
98
|
|
|
127
99
|
# fetch_package <url> <ref> <subpath> <dest-dir>
|
|
@@ -182,14 +154,14 @@ fetch_package() {
|
|
|
182
154
|
|
|
183
155
|
# wire_package_sources <manifest> <@scope/name> <store-rel-dir>
|
|
184
156
|
# The provides convention: whichever of rules/agents/skills the installed
|
|
185
|
-
# package has becomes a sources entry
|
|
186
|
-
#
|
|
157
|
+
# package has becomes a sources entry — inserted FIRST in its section, so
|
|
158
|
+
# project-owned entries come later and win on same-named artifacts.
|
|
187
159
|
wire_package_sources() {
|
|
188
160
|
local manifest="$1" name="$2" rel="$3" root="$4"
|
|
189
161
|
local section
|
|
190
162
|
for section in rules agents skills; do
|
|
191
163
|
if [ -d "$root/$rel/$section" ]; then
|
|
192
|
-
|
|
164
|
+
sources_add_entry_first "$manifest" "$section" "$rel/$section"
|
|
193
165
|
fi
|
|
194
166
|
done
|
|
195
167
|
}
|
package/engine/docs/CLI.md
CHANGED
|
@@ -27,7 +27,7 @@ No engine code lives in the project. The engine's *scripts* ship inside the npm
|
|
|
27
27
|
|
|
28
28
|
| Command | Does |
|
|
29
29
|
|---|---|
|
|
30
|
-
| `init [--targets a,b] [--dir d] [--bare] [--no-sync]` |
|
|
30
|
+
| `init [--targets a,b] [--dir d] [--bare] [--no-sync]` | Enable `agents` (the tool-neutral AGENTS.md) plus every tool DETECTED by repo markers (`.claude`/`CLAUDE.md`, `.cursor`, `.codex`, `.pi`, `.opencode`, `.github/instructions`) or named via `--targets` — a tool is never invented. Writes a minimal self-documenting root manifest, `.gitignore`s the store, installs `@ainova-systems/sync` (unless `--bare`), first sync. No dirs are created. |
|
|
31
31
|
| `add <spec> [--name @s/n] [--no-sync]` | Resolve → fetch → wire sources → manifest entry → lock → sync. Specs: `@scope/name[@range]`, `github:org/repo[#path]`, `git+<url>[@ref][#path]`. |
|
|
32
32
|
| `remove <name> [--force]` | Inverse of add: manifest, sources, lock, store. |
|
|
33
33
|
| `install [--frozen] [--force]` | Restore the store exactly from the lock; resolve manifest packages the lock lacks (`--frozen` refuses instead, and fails on sha drift). |
|
|
@@ -44,11 +44,7 @@ No engine code lives in the project. The engine's *scripts* ship inside the npm
|
|
|
44
44
|
|
|
45
45
|
**Names are global.** The name is the trust anchor a developer reasons with — the same `@scope/name` means the same package in every project, every lock and every conversation. A registry never renames anything, and two versions of one name cannot coexist in a project: intelligence artifacts land in each tool's flat namespace (`.claude/skills/<name>/`, the `AGENTS.md` tables), so duplicates would collide file-by-file. Need pieces of two versions? That is a different package — fork it under a different name. Need to override one artifact? Put a same-named file in your own content dir; project sources are listed after package sources.
|
|
46
46
|
|
|
47
|
-
**Name → repo resolution
|
|
48
|
-
|
|
49
|
-
1. `registries:` in the manifest — a **trust list** of registry repos (git repos holding an `index.yaml`), consulted in order. Adding one is an explicit, committed, reviewable act of trust in the names it declares; private registries are just private repos, git auth covers access. A project-registry hit that shadows a bundled name with a *different* source warns loudly, and `doctor` flags a name whose current resolution url no longer matches the lock.
|
|
50
|
-
2. The bundled default index (`registry/index.yaml`) — needed exactly when a name is not a repo (monorepos: `@ainova-systems/core` → `intelligence-dev-packs` at `packs/core`).
|
|
51
|
-
3. Convention: `@org/name` → `https://github.com/org/name.git`, content at the repo root. Zero infrastructure: any repo with the three dirs is already a package.
|
|
47
|
+
**Name → repo resolution: the trust list is the ONLY resolver.** `registries:` in the manifest holds registry repos (git repos with an `index.yaml`), consulted in order — the first to declare the name wins. There is deliberately **no built-in catalog and no `@org/name` → github guessing**: the CLI core knows no vendor, and a name nobody explicitly trusted never turns into an install from an invented URL. A name no trusted registry declares is refused with suggestions. Installs without a registry are always **explicit sources**: `github:org/repo`, `git+<url>[@ref][#path]`. Vendor defaults exist only as lines `init` seeds into the manifest — visible, reviewable, deletable (`doctor` still flags a name whose current resolution url no longer matches the lock).
|
|
52
48
|
|
|
53
49
|
**Versions are git tags.** Ranges (`^1.2.0`, `~1.2.0`, exact, `latest`) match stable `x.y.z` tags (optional `v` prefix) listed via `git ls-remote` — no clone to resolve. `add` without a range picks the highest stable tag and records `^that`. A branch or commit pin is `ref:`, the escape hatch that ranges never touch. Prerelease-suffixed tags are invisible to ranges by design.
|
|
54
50
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3a5cd174c73bd3918ac0a01df8d54a6f3d98b485
|
package/engine/scripts/sync.sh
CHANGED
|
@@ -94,7 +94,7 @@ if [ "${IS_CLI:-0}" = "1" ]; then
|
|
|
94
94
|
# `/d/...`, and prefix comparisons downstream need one spelling.
|
|
95
95
|
CONFIG_FILE="$(cd "$(dirname "$CONFIG_FILE")" && pwd)/$(basename "$CONFIG_FILE")"
|
|
96
96
|
IS_UMBRELLA_REL="${IS_UMBRELLA_REL:-intelligence}"
|
|
97
|
-
IS_MODULE_REL="${IS_MODULE_REL:-.intelligence/
|
|
97
|
+
IS_MODULE_REL="${IS_MODULE_REL:-.intelligence/packages/@ainova-systems/sync}"
|
|
98
98
|
# Project-owned adapters keep their v1 home: <umbrella>/adapters/.
|
|
99
99
|
INTELLIGENCE_DIR="$REPO_ROOT/$IS_UMBRELLA_REL"
|
|
100
100
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainova-systems/intelligence",
|
|
3
|
-
"version": "0.11.0-rc.
|
|
3
|
+
"version": "0.11.0-rc.6",
|
|
4
4
|
"description": "Build, version and distribute AI agent intelligence across your organization — one CLI, versioned Intelligence Packages, and a sync engine for Claude Code, Cursor, Copilot, Codex, Pi and OpenCode.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"intelligence": "bin/intelligence.js"
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"bin",
|
|
10
10
|
"cli",
|
|
11
|
-
"engine"
|
|
12
|
-
"registry"
|
|
11
|
+
"engine"
|
|
13
12
|
],
|
|
14
13
|
"engines": {
|
|
15
14
|
"node": ">=18"
|
package/registry/index.yaml
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# The default Intelligence Registry index, bundled with the CLI.
|
|
2
|
-
#
|
|
3
|
-
# Maps a package name to the git repo (and path inside it) the package lives
|
|
4
|
-
# in. Needed exactly when a name is not the repo — monorepos like
|
|
5
|
-
# intelligence-dev-packs. A name absent here falls through to the convention:
|
|
6
|
-
# @org/name -> https://github.com/org/name.git, content at the repo root.
|
|
7
|
-
# Organizations override or extend this per scope with `intelligence registry
|
|
8
|
-
# add @scope <registry-repo-url>`.
|
|
9
|
-
#
|
|
10
|
-
# This copy travels inside the CLI package. The same index lives at
|
|
11
|
-
# https://github.com/ainova-systems/intelligence-registry and can be bound
|
|
12
|
-
# explicitly (`intelligence registry add @ainova-systems <url>`) to read the
|
|
13
|
-
# newest entries without waiting for a CLI release.
|
|
14
|
-
packages:
|
|
15
|
-
"@ainova-systems/sync":
|
|
16
|
-
url: "https://github.com/ainova-systems/intelligence-sync.git"
|
|
17
|
-
path: "intelligence/sync"
|
|
18
|
-
description: "The engine's own content: authoring rule, engine agents and the intelligence-* meta-skills. Auto-installed by init; pinned to the engine version."
|
|
19
|
-
"@ainova-systems/core":
|
|
20
|
-
url: "https://github.com/ainova-systems/intelligence-dev-packs.git"
|
|
21
|
-
path: "packs/core"
|
|
22
|
-
description: "Engineering discipline for AI-first development: context engineering, artifact-derived status, review and diagnosis loops."
|
|
23
|
-
"@ainova-systems/spec":
|
|
24
|
-
url: "https://github.com/ainova-systems/intelligence-dev-packs.git"
|
|
25
|
-
path: "packs/spec"
|
|
26
|
-
description: "Spec-driven delivery: specifications, decision records, sliced execution."
|