@ainova-systems/intelligence 0.11.0-rc.3 → 0.11.0-rc.5

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.
Files changed (35) hide show
  1. package/cli/commands/add.sh +17 -0
  2. package/cli/commands/doctor.sh +28 -7
  3. package/cli/commands/init.sh +51 -20
  4. package/cli/commands/install.sh +9 -3
  5. package/cli/commands/migrate.sh +11 -2
  6. package/cli/commands/registry.sh +53 -26
  7. package/cli/commands/remove.sh +13 -1
  8. package/cli/commands/search.sh +8 -7
  9. package/cli/commands/status.sh +5 -4
  10. package/cli/commands/sync.sh +19 -1
  11. package/cli/commands/update.sh +4 -0
  12. package/cli/commands/upgrade.sh +50 -9
  13. package/cli/lib/cli-common.sh +28 -47
  14. package/cli/lib/manifest.sh +79 -0
  15. package/cli/lib/registry.sh +77 -14
  16. package/cli/lib/semver.sh +5 -2
  17. package/engine/agents/intelligence-operator.md +4 -4
  18. package/engine/docs/ADAPTERS.md +2 -1
  19. package/engine/docs/CLI.md +14 -11
  20. package/engine/docs/CONVENTIONS.md +5 -3
  21. package/engine/rules/intelligence-authoring.md +4 -4
  22. package/engine/scripts/ENGINE_SHA +1 -0
  23. package/engine/scripts/lib/common.sh +6 -2
  24. package/engine/skills/intelligence-add-agent/SKILL.md +7 -7
  25. package/engine/skills/intelligence-add-rule/SKILL.md +5 -5
  26. package/engine/skills/intelligence-add-skill/SKILL.md +5 -5
  27. package/engine/skills/intelligence-extract-skill/SKILL.md +2 -2
  28. package/engine/skills/intelligence-install-adapter/SKILL.md +6 -6
  29. package/engine/skills/intelligence-learn-from-context/SKILL.md +1 -1
  30. package/engine/skills/intelligence-review-skills/SKILL.md +5 -5
  31. package/engine/skills/intelligence-sync/SKILL.md +5 -7
  32. package/engine/skills/intelligence-uninstall-adapter/SKILL.md +2 -2
  33. package/engine/skills/intelligence-update/SKILL.md +6 -0
  34. package/package.json +1 -1
  35. package/registry/index.yaml +4 -0
@@ -24,10 +24,15 @@ 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
- # Meta-skills that the CLI replaces with first-class commands — staging skips
28
- # them so a v2 project never carries a skill telling the agent to run a flow
29
- # the CLI already owns.
30
- CLI_OBSOLETE_SKILLS="intelligence-update intelligence-sync"
27
+ # The engine's own content as a package: OPTIONAL but auto-selected at init.
28
+ # Package by UX (manifest entry, lockfile row, list/search/remove), bundle by
29
+ # mechanics — at the version the CLI ships, it materializes from the npm
30
+ # bundle without network; only a cross-version install reaches git. The pin
31
+ # is held exactly at the bundled engine version and moved only by `upgrade`.
32
+ SYNC_PKG_NAME="@ainova-systems/sync"
33
+ SYNC_PKG_URL="https://github.com/ainova-systems/intelligence-sync.git"
34
+ SYNC_PKG_PATH="intelligence/sync"
35
+ SYNC_PKG_STORE=".intelligence/packages/@ainova-systems/sync"
31
36
 
32
37
  # --- Project detection ---------------------------------------------------
33
38
  # Sets: IP_MODE (v2|legacy|none), IP_ROOT, IP_UMBRELLA, IP_MODULE_DIR.
@@ -90,51 +95,26 @@ bundled_engine_version() {
90
95
  tr -d ' \t\r\n' < "$IS_ENGINE_DIR/scripts/VERSION"
91
96
  }
92
97
 
93
- # --- Engine content staging ----------------------------------------------
94
- # The v2 project vendors no engine code; the engine's own rules / agents /
95
- # meta-skills / docs are staged into the package store so sources can reach
96
- # them as ordinary repo-relative dirs. Re-staged whenever the bundled engine
97
- # version differs from the `.version` stamp.
98
- # stage_engine_content <dest-dir> — unconditional copy of the engine's own
99
- # content into <dest-dir> plus the `.version` stamp.
100
- stage_engine_content() {
101
- local store="$1"
102
- rm -rf "$store"
103
- mkdir -p "$store"
104
- local d s name skip
105
- # `scripts` travels too: engine-shipped skills reference `<module>/scripts/…`
106
- # (adapters, docs) and `<module>` resolves here, so the paths they hand an
107
- # agent must exist. Running that sync.sh directly is harmless — outside CLI
108
- # mode it fails closed rather than generating against the wrong layout.
109
- for d in rules agents docs scripts; do
110
- [ -d "$IS_ENGINE_DIR/$d" ] && cp -R "$IS_ENGINE_DIR/$d" "$store/$d"
111
- done
112
- if [ -d "$IS_ENGINE_DIR/skills" ]; then
113
- mkdir -p "$store/skills"
114
- for s in "$IS_ENGINE_DIR"/skills/*/; do
115
- [ -d "$s" ] || continue
116
- name="$(basename "$s")"
117
- skip=0
118
- for d in $CLI_OBSOLETE_SKILLS; do
119
- [ "$name" = "$d" ] && skip=1
120
- done
121
- [ "$skip" = "1" ] && continue
122
- cp -R "$s" "$store/skills/$name"
123
- done
124
- fi
125
- bundled_engine_version > "$store/.version"
98
+ # --- The sync package's manifest/lock plumbing ---------------------------
99
+ # sync_pkg_entry <manifest> — write/refresh the package's manifest entry:
100
+ # name + exact pin + explicit url/path, so resolution never depends on any
101
+ # registry (a project registry shadowing the name cannot brick the engine).
102
+ sync_pkg_entry() {
103
+ local manifest="$1"
104
+ qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "version" "$(bundled_engine_version)"
105
+ qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "url" "$SYNC_PKG_URL"
106
+ qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "path" "$SYNC_PKG_PATH"
126
107
  }
127
108
 
128
- ensure_engine_staged() {
129
- local root="$1"
130
- local store="$root/.intelligence/engine"
131
- local bundled_ver staged_ver
132
- bundled_ver="$(bundled_engine_version)"
133
- staged_ver=""
134
- [ -f "$store/.version" ] && staged_ver="$(tr -d ' \t\r\n' < "$store/.version")"
135
- [ "$staged_ver" = "$bundled_ver" ] && return 0
136
- stage_engine_content "$store"
137
- echo " engine content staged: .intelligence/engine ($bundled_ver)"
109
+ # sync_pkg_install <root> — materialize the package into the store and lock
110
+ # it (offline at the bundled version — fetch_package's bundle-seed guard).
111
+ sync_pkg_install() {
112
+ local root="$1" ver sha
113
+ ver="$(bundled_engine_version)"
114
+ sha="$(fetch_package "$SYNC_PKG_URL" "v$ver" "$SYNC_PKG_PATH" "$root/$SYNC_PKG_STORE")"
115
+ wire_package_sources "$root/intelligence.yaml" "$SYNC_PKG_NAME" "$SYNC_PKG_STORE" "$root"
116
+ lock_upsert "$root/intelligence.lock" "$SYNC_PKG_NAME" "$ver" "$SYNC_PKG_URL" "$SYNC_PKG_PATH" "v$ver" "$sha"
117
+ echo " engine content installed: $SYNC_PKG_STORE (v$ver)"
138
118
  }
139
119
 
140
120
  # --- The engine env contract ---------------------------------------------
@@ -149,5 +129,6 @@ export_engine_env() {
149
129
  export IS_UMBRELLA_REL="$umbrella"
150
130
  export IS_MODULE_REL=".intelligence/engine"
151
131
  export IS_SYNC_CMD="intelligence sync"
132
+ export IS_MANIFEST_NAME="intelligence.yaml"
152
133
  export IS_PROTECTED_DIRS="$umbrella:.intelligence"
153
134
  }
@@ -194,6 +194,85 @@ qmap_delete_key() {
194
194
  '
195
195
  }
196
196
 
197
+ # --- registries: a trust LIST, not a scope map -----------------------------
198
+ # The block holds registry repo URLs in trust order. Two shapes are read so
199
+ # early manifests keep working: the list form (`- "url"`) and the retired
200
+ # flat-map form (`"@scope": "url"` — the scope label is ignored, the URL is
201
+ # simply another registry).
202
+
203
+ # registries_list <file> — registry URLs, one per line, manifest order.
204
+ registries_list() {
205
+ [ -f "$1" ] || return 0
206
+ awk '
207
+ { sub(/\r$/, "") }
208
+ /^registries:[ \t]*$/ { inb = 1; next }
209
+ inb && /^[^ #]/ { inb = 0 }
210
+ inb {
211
+ line = $0
212
+ if (line ~ /^[ \t]*-[ \t]*/) {
213
+ sub(/^[ \t]*-[ \t]*/, "", line)
214
+ } else if (line ~ /^ "/) {
215
+ c = index(line, ":")
216
+ if (c == 0) next
217
+ line = substr(line, c + 1)
218
+ } else next
219
+ sub(/^[ \t]+/, "", line)
220
+ gsub(/["\x27]/, "", line)
221
+ sub(/[ \t]+#.*$/, "", line)
222
+ sub(/[ \t]+$/, "", line)
223
+ if (line != "") print line
224
+ }
225
+ ' "$1"
226
+ }
227
+
228
+ # registries_add <file> <url> — idempotent append in list form.
229
+ registries_add() {
230
+ local file="$1" url="$2" existing
231
+ while IFS= read -r existing; do
232
+ [ "$existing" = "$url" ] && return 0
233
+ done < <(registries_list "$file")
234
+ _qmap_stage "$file" -v url="$url" '
235
+ function entry() { return " - \"" url "\"" }
236
+ { sub(/\r$/, "") }
237
+ /^registries:[ \t]*$/ { blockseen = 1; inb = 1; print; next }
238
+ inb && /^[^ #]/ { if (!done) { print entry(); done = 1 }; inb = 0 }
239
+ { last = $0; print }
240
+ END {
241
+ if (inb && !done) { print entry(); done = 1 }
242
+ if (!blockseen) {
243
+ if (last != "") print ""
244
+ print "registries:"
245
+ print entry()
246
+ }
247
+ }
248
+ '
249
+ }
250
+
251
+ # registries_remove <file> <url> — drop the entry, either shape.
252
+ registries_remove() {
253
+ local file="$1" url="$2"
254
+ [ -f "$file" ] || return 0
255
+ _qmap_stage "$file" -v url="$url" '
256
+ { sub(/\r$/, "") }
257
+ /^registries:[ \t]*$/ { inb = 1; print; next }
258
+ inb && /^[^ #]/ { inb = 0 }
259
+ inb {
260
+ line = $0
261
+ v = ""
262
+ if (line ~ /^[ \t]*-[ \t]*/) { v = line; sub(/^[ \t]*-[ \t]*/, "", v) }
263
+ else if (line ~ /^ "/) { c = index(line, ":"); if (c > 0) v = substr(line, c + 1) }
264
+ if (v != "") {
265
+ sub(/^[ \t]+/, "", v)
266
+ gsub(/["\x27]/, "", v)
267
+ sub(/[ \t]+#.*$/, "", v)
268
+ sub(/[ \t]+$/, "", v)
269
+ if (v == url) next
270
+ }
271
+ }
272
+ { print }
273
+ '
274
+ }
275
+
197
276
  # sources_remove_entry <file> <section> <entry> — remove `- "entry"` from
198
277
  # sources.<section>. Inverse of the engine-side _mig_add_source.
199
278
  sources_remove_entry() {
@@ -1,13 +1,19 @@
1
1
  #!/bin/bash
2
2
  # Name -> source resolution and package fetching.
3
3
  #
4
- # Resolution order (first hit wins):
5
- # 1. project `registries:` — a scope bound to a registry repo (a git repo
6
- # holding index.yaml); private orgs override anything shipped
4
+ # Resolution order (first registry to DECLARE the name wins):
5
+ # 1. project `registries:` — a trust LIST of registry repos (git repos
6
+ # holding index.yaml), consulted in manifest order; adding one is an
7
+ # explicit, committed act of trust in the names it declares
7
8
  # 2. the bundled default index (registry/index.yaml next to the CLI)
8
9
  # 3. convention: @org/name -> https://github.com/org/name.git, content at
9
10
  # the repo root — the zero-infrastructure default
10
11
  #
12
+ # The NAME is the trust anchor a developer reasons with; source integrity is
13
+ # a separate mechanism: the lock pins url+sha, a project-registry hit that
14
+ # shadows a bundled name with a different url warns loudly, and doctor flags
15
+ # resolution/lock url drift.
16
+ #
11
17
  # An index is itself fetched with git (never curl): auth, proxies and private
12
18
  # hosting all come for free, and the CLI keeps the engine's bash+awk+git-only
13
19
  # dependency footprint.
@@ -46,22 +52,33 @@ resolve_package_source() {
46
52
  local scope="${name%%/*}" short="${name#*/}"
47
53
  RES_URL=""; RES_PATH=""; RES_VIA=""
48
54
 
55
+ local reg_url index bundled burl
56
+ bundled="$(default_index_file)"
49
57
  if [ -n "$manifest" ] && [ -f "$manifest" ]; then
50
- local reg_url index
51
- reg_url="$(qmap_value "$manifest" "registries" "$scope")"
52
- if [ -n "$reg_url" ]; then
58
+ while IFS= read -r reg_url; do
59
+ [ -n "$reg_url" ] || continue
53
60
  index="$(_fetch_index "${reg_url#git+}")"
54
- [ -n "$index" ] || die "registry for scope '$scope' is unreachable or has no index.yaml: $reg_url"
61
+ if [ -z "$index" ]; then
62
+ echo " WARN: registry unreachable or missing index.yaml, skipped: $reg_url" >&2
63
+ continue
64
+ fi
55
65
  RES_URL="$(qmap_field "$index" "packages" "$name" "url")"
56
- RES_PATH="$(qmap_field "$index" "packages" "$name" "path")"
57
- [ -n "$RES_URL" ] || die "package '$name' not found in the '$scope' registry ($reg_url)"
58
- RES_VIA="registry:$reg_url"
59
- return 0
60
- fi
66
+ if [ -n "$RES_URL" ]; then
67
+ RES_PATH="$(qmap_field "$index" "packages" "$name" "path")"
68
+ 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
+ return 0
78
+ fi
79
+ done < <(registries_list "$manifest")
61
80
  fi
62
81
 
63
- local bundled
64
- bundled="$(default_index_file)"
65
82
  if [ -n "$bundled" ]; then
66
83
  RES_URL="$(qmap_field "$bundled" "packages" "$name" "url")"
67
84
  if [ -n "$RES_URL" ]; then
@@ -77,6 +94,36 @@ resolve_package_source() {
77
94
  return 0
78
95
  }
79
96
 
97
+ # suggest_similar <manifest-or-empty> <@scope/name> — "Did you mean" lines to
98
+ # stderr, matched on the short name (singular/plural tolerant) across every
99
+ # project registry and the bundled index.
100
+ suggest_similar() {
101
+ local manifest="$1" name="$2"
102
+ local want cand cshort reg_url index seen=" "
103
+ want="${name#*/}"; want="${want%s}"
104
+ _suggest_from() {
105
+ local idx="$1"
106
+ [ -n "$idx" ] && [ -f "$idx" ] || return 0
107
+ while IFS= read -r cand; do
108
+ [ -n "$cand" ] || continue
109
+ case "$seen" in *" $cand "*) continue ;; esac
110
+ cshort="${cand#*/}"; cshort="${cshort%s}"
111
+ if [ "$cshort" = "$want" ] && [ "$cand" != "$name" ]; then
112
+ seen="$seen$cand "
113
+ echo " Did you mean: intelligence add $cand" >&2
114
+ fi
115
+ done < <(qmap_keys "$idx" "packages")
116
+ }
117
+ if [ -n "$manifest" ] && [ -f "$manifest" ]; then
118
+ while IFS= read -r reg_url; do
119
+ [ -n "$reg_url" ] || continue
120
+ index="$(_fetch_index "${reg_url#git+}")"
121
+ _suggest_from "$index"
122
+ done < <(registries_list "$manifest")
123
+ fi
124
+ _suggest_from "$(default_index_file)"
125
+ }
126
+
80
127
  # fetch_package <url> <ref> <subpath> <dest-dir>
81
128
  # Shallow-clones url@ref (tag, branch, or SHA fallback), copies <subpath>
82
129
  # (or the repo root) into <dest-dir>, prints the resolved commit sha.
@@ -84,6 +131,22 @@ resolve_package_source() {
84
131
  # store holds the bytes the package published.
85
132
  fetch_package() {
86
133
  local url="$1" ref="$2" subpath="$3" dest="$4"
134
+ # Bundle seed: the engine-content package at the CLI's own version copies
135
+ # from the npm bundle — no network, which keeps init / fresh-clone install
136
+ # / migrate offline exactly like the staging they replace. Keyed on the
137
+ # full (url, path, ref) triple; any other version or source falls through
138
+ # to the normal clone.
139
+ if [ "$url" = "$SYNC_PKG_URL" ] && [ "$subpath" = "$SYNC_PKG_PATH" ] \
140
+ && [ "${ref#v}" = "$(bundled_engine_version)" ]; then
141
+ rm -rf "$dest"
142
+ mkdir -p "$dest"
143
+ cp -R "$IS_ENGINE_DIR/." "$dest/"
144
+ rm -rf "$dest/.git"
145
+ if [ -f "$IS_ENGINE_DIR/scripts/ENGINE_SHA" ]; then
146
+ tr -d ' \t\r\n' < "$IS_ENGINE_DIR/scripts/ENGINE_SHA"
147
+ fi
148
+ return 0
149
+ fi
87
150
  local tmp sha src
88
151
  local -a branch_arg=()
89
152
  [ -n "$ref" ] && branch_arg=(--branch "$ref")
package/cli/lib/semver.sh CHANGED
@@ -75,7 +75,10 @@ semver_match() {
75
75
  # onto their tag.
76
76
  list_remote_versions() {
77
77
  local url="$1"
78
- GIT_TERMINAL_PROMPT=0 git ls-remote --tags "$url" 2>/dev/null \
78
+ # `|| true` inside the pipeline: an unreachable repo must read as "no
79
+ # versions", not kill a pipefail caller mid-command-substitution —
80
+ # reachability is the CALLER's question (add probes it explicitly).
81
+ { GIT_TERMINAL_PROMPT=0 git ls-remote --tags "$url" 2>/dev/null || true; } \
79
82
  | awk '{
80
83
  sub(/\r$/, "")
81
84
  ref = $2
@@ -92,7 +95,7 @@ list_remote_versions() {
92
95
  # prints "<tag> <sha>". Peeled sha (the commit a tag object points at) wins.
93
96
  remote_tag_for_version() {
94
97
  local url="$1" ver="${2#v}"
95
- GIT_TERMINAL_PROMPT=0 git ls-remote --tags "$url" 2>/dev/null \
98
+ { GIT_TERMINAL_PROMPT=0 git ls-remote --tags "$url" 2>/dev/null || true; } \
96
99
  | awk -v want="$ver" '
97
100
  {
98
101
  sub(/\r$/, "")
@@ -19,10 +19,10 @@ that ships it.
19
19
 
20
20
  ## Expertise
21
21
 
22
- The bash-to-skill status contract (`docs/CONVENTIONS.md`, Migration & Module Contract). Every flow
23
- here is deterministic and fail-closed: `sync.sh` is a pure synchronizer that refuses across an
24
- un-applied schema, `update.sh` stages, verifies a sentinel and only then commits, and each skill
25
- branches on `IS_STATUS`. The work is running the right flow, reading the code it returns, and doing
22
+ The status contract (`<module>/docs/CONVENTIONS.md`, Migration & Module Contract). Every flow
23
+ here is deterministic and fail-closed: the sync is a pure synchronizer that refuses across an
24
+ un-applied schema, the update flow stages, verifies and only then commits, and every flow
25
+ reports `IS_STATUS`. The work is running the right flow, reading the code it returns, and doing
26
26
  what that code says - including stopping. The guards carry the decisions, which is why this agent
27
27
  runs on the standard tier: the failure mode is a loud refusal and a retry, not a plausible wrong
28
28
  answer.
@@ -110,7 +110,8 @@ The engine ships artifacts of its own — the `intelligence-authoring` rule and
110
110
  | Token | Expands to |
111
111
  |---|---|
112
112
  | `<umbrella>` | repo-relative umbrella dir (e.g. `Intelligence`) |
113
- | `<module>` | repo-relative engine module (e.g. `Intelligence/sync`; CLI setup: `.intelligence/engine`) |
113
+ | `<module>` | repo-relative engine module (e.g. `Intelligence/sync`; CLI setup: `.intelligence/packages/@ainova-systems/sync`) |
114
+ | `<manifest>` | the project's config file name — vendored: `config.yaml`, CLI setup: `intelligence.yaml` (`IS_MANIFEST_NAME`) |
114
115
  | `<sync-cmd>` | the sync invocation — vendored: `bash <module>/scripts/sync.sh`, CLI setup: `intelligence sync` (`IS_SYNC_CMD`) |
115
116
 
116
117
  Values are exported by `sync.sh` (`IS_UMBRELLA_REL`, `IS_MODULE_REL`; `IS_SYNC_CMD` comes from the CLI in CLI mode), derived from the detected layout. Expansion covers frontmatter and body, so `paths: ["<umbrella>/**"]` reaches Claude's `paths:`, Cursor's `globs:` and Copilot's `applyTo:` carrying the project's real folder name. A file written without `finalize_output_file` ships a literal `<umbrella>` into an IDE — CI fails the build if any generated output still contains a token.
@@ -16,35 +16,37 @@ project/
16
16
  ├── intelligence.yaml # manifest, at the repo root (like package.json)
17
17
  ├── intelligence.lock # resolved package state — commit it
18
18
  ├── intelligence/ # the project's own rules/ agents/ skills/
19
- ├── .intelligence/ # gitignored store: packages/<name>/, engine/, backup/
19
+ ├── .intelligence/ # gitignored store: packages/<name>/, backup/
20
20
  ├── .claude/ .cursor/ … # generated by sync
21
21
  └── AGENTS.md
22
22
  ```
23
23
 
24
- No engine code lives in the project. The engine ships inside the npm package; its own rules, agents, meta-skills and docs are staged into `.intelligence/engine/` (re-staged whenever the bundled engine version changes) and reach the outputs as ordinary sources. After a fresh clone, `intelligence install` restores the whole store from the lock.
24
+ No engine code lives in the project. The engine's *scripts* ship inside the npm package; the engine's *content* — the authoring rule, both engine agents, every `intelligence-*` meta-skill and the docs — is the package **`@ainova-systems/sync`**: auto-added by `init` (opt out with `--bare`), visible in `list`/`search`, pinned exactly to the bundled engine version, and materialized from the npm bundle without network whenever the pin matches (only a cross-version install reaches git). `intelligence update` never moves it — `intelligence upgrade` does, together with the schema stamp. Removing it (`remove @ainova-systems/sync --force`) drops the meta-content from the outputs while the sync engine itself keeps working. After a fresh clone, `intelligence install` restores the whole store from the lock.
25
25
 
26
26
  ## Commands
27
27
 
28
28
  | Command | Does |
29
29
  |---|---|
30
- | `init [--targets a,b] [--no-sync]` | Detect tools by their marker dirs, write a minimal root manifest, `.gitignore` the store, skeleton `intelligence/`, first sync. `agents` + `claude` are always on. |
30
+ | `init [--targets a,b] [--dir d] [--bare] [--no-sync]` | Detect tools by their marker dirs, write a minimal self-documenting root manifest, `.gitignore` the store, install `@ainova-systems/sync` (unless `--bare`), first sync. No dirs are created — authoring one later needs no config edit. |
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
- | `remove <name>` | Inverse of add: manifest, sources, lock, store. |
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). |
34
34
  | `update [name]` | Re-resolve ranges, refetch what moved, rewrite the lock. `ref:`-pinned packages never move here. |
35
- | `upgrade` | Bring the project to this CLI's engine: restage engine content, apply v2 schema migrations, restamp `sync_version`, sync. |
35
+ | `upgrade` | Bring the project to this CLI's engine: apply v2 schema migrations, move the `@ainova-systems/sync` pin to the bundled version and reinstall it, restamp `sync_version`, sync. |
36
36
  | `sync [target]` | Run the bundled engine against the manifest. In a vendored (v1) project it delegates to that project's own engine. |
37
37
  | `list` / `status` / `doctor` | Inspect; doctor exits 1 on inconsistencies (unlocked packages, missing store, stale stamp, dead sources). |
38
- | `registry <list\|add @scope <url>\|remove @scope>` | Bind a scope to a registry repo — rare, once per organization. |
38
+ | `registry <list\|add <url> [--force]\|remove <url>>` | Manage the trust list of registries — rare, once per organization. `add` fails closed on a URL with no `index.yaml` (`--force` records it anyway). |
39
39
  | `migrate [--dry-run] [--force]` | Convert a vendored setup to the CLI setup. Transactional; see below. |
40
40
 
41
41
  ## Packages
42
42
 
43
43
  **A package's name is its identity everywhere**: the `packages:` key in the manifest, the store directory (`.intelligence/packages/@scope/name/` — npm's nesting), and the lock key. What a package provides is a convention: whichever of `rules/`, `agents/`, `skills/` exist at its top level get wired into the matching `sources:` sections by `add`.
44
44
 
45
- **Name → repo resolution**, first hit wins:
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
- 1. `registries:` in the manifest — a scope bound to a *registry repo*: any git repo holding an `index.yaml`. Private registries are therefore just private repos; git auth covers access.
47
+ **Name → repo resolution** — the first registry to *declare* the name wins:
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.
48
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`).
49
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.
50
52
 
@@ -61,7 +63,7 @@ packages:
61
63
  "@ainova-systems/core":
62
64
  version: "^0.3.0" # or: ref: main / url: + path: (direct git specs)
63
65
  registries:
64
- "@acme": "https://github.com/acme/intelligence-registry.git"
66
+ - "https://github.com/acme/intelligence-registry.git"
65
67
  ```
66
68
 
67
69
  `project.intelligence_dir` names the content dir when it is not `intelligence/`. `sync_version` stays the frozen schema-contract key, stamped by the CLI with the bundled engine version — an npm prerelease suffix never reaches it.
@@ -75,14 +77,15 @@ The engine's CLI mode is an env contract, set by the dispatcher and honored only
75
77
  | `CONFIG_FILE` | `<root>/intelligence.yaml` |
76
78
  | `REPO_ROOT` | the project root |
77
79
  | `IS_UMBRELLA_REL` | content dir (`intelligence`) |
78
- | `IS_MODULE_REL` | `.intelligence/engine` |
80
+ | `IS_MODULE_REL` | `.intelligence/packages/@ainova-systems/sync` |
81
+ | `IS_MANIFEST_NAME` | `intelligence.yaml` (feeds the `<manifest>` token; vendored default `config.yaml`) |
79
82
  | `IS_SYNC_CMD` | `intelligence sync` (expanded for the `<sync-cmd>` token) |
80
83
  | `IS_PROTECTED_DIRS` | `<content-dir>:.intelligence` — restores output-path protection for a root manifest |
81
84
  | `IS_SUPPRESS_CLI_NOTE` | set to silence the vendored-flow recommendation note |
82
85
 
83
86
  ## migrate: vendored (v1) → CLI (v2)
84
87
 
85
- Fail-closed preconditions (clean worktree unless `--force`; no half-migrated state; project schema not newer than the engine; an older schema is first brought up by the engine's own migration chain). Then **stage** — the manifest is `config.yaml` transformed comment-preservingly (module sources → `.intelligence/engine/*`, `@pack/sub` references → store paths, `packs:` → `packages:` entries keeping their `ref:` pins), mirrored packs are *copied* from their mirrors (network untouched, sha carried from the `.pack` stamp), transient packs fetched — **verify** (staged sources exist; per-adapter `enabled`/`output` equality against the old config) — **commit**: store and manifest move in, a real sync must report `IS_STATUS=ok`, and only then are the vendored module, `config.yaml` (backed up to `.intelligence/backup/`) and the mirrors removed. Any earlier failure rolls back to an untouched project. `--dry-run` stages, verifies, prints, writes nothing.
88
+ Fail-closed preconditions (clean worktree unless `--force`; no half-migrated state; project schema not newer than the engine; an older schema is first brought up by the engine's own migration chain). Then **stage** — the manifest is `config.yaml` transformed comment-preservingly (module sources → the `@ainova-systems/sync` package store, `@pack/sub` references → store paths, `packs:` → `packages:` entries keeping their `ref:` pins), mirrored packs are *copied* from their mirrors (network untouched, sha carried from the `.pack` stamp), transient packs fetched — **verify** (staged sources exist; per-adapter `enabled`/`output` equality against the old config) — **commit**: store and manifest move in, a real sync must report `IS_STATUS=ok`, and only then are the vendored module, `config.yaml` (backed up to `.intelligence/backup/`) and the mirrors removed. Any earlier failure rolls back to an untouched project. `--dry-run` stages, verifies, prints, writes nothing.
86
89
 
87
90
  ## Developing and releasing the CLI
88
91
 
@@ -56,7 +56,8 @@ An artifact shipped *by the engine* cannot write the umbrella's name down — th
56
56
  | Token | Expands to | Example |
57
57
  |---|---|---|
58
58
  | `<umbrella>` | repo-relative umbrella dir | `Intelligence` |
59
- | `<module>` | repo-relative engine module | `Intelligence/sync` (CLI setup: `.intelligence/engine`) |
59
+ | `<module>` | repo-relative engine module | `Intelligence/sync` (CLI setup: `.intelligence/packages/@ainova-systems/sync`) |
60
+ | `<manifest>` | the project's config file, by name | vendored: `config.yaml`; CLI setup: `intelligence.yaml` |
60
61
  | `<sync-cmd>` | how a reader re-runs the sync | vendored: `bash Intelligence/sync/scripts/sync.sh`; CLI setup: `intelligence sync` |
61
62
 
62
63
  Expansion covers frontmatter and body alike, so `paths: ["<umbrella>/**"]` reaches Claude's `paths:`, Cursor's `globs:` and Copilot's `applyTo:` already carrying the project's real folder name. Project-authored artifacts may use the tokens too, but they have no reason to — they can simply name their own folders. `<sync-cmd>` exists because "run a sync" is spelled differently per setup: engine content says the token, and `IS_SYNC_CMD` (set by the CLI) picks the spelling; unset, it reproduces the vendored command string exactly.
@@ -408,12 +409,13 @@ Bash emits `IS_STATUS=<code> [IS_DETAIL=...]` on stdout and exits with the match
408
409
  |---|---|
409
410
  | `CONFIG_FILE` | `<root>/intelligence.yaml` (the root manifest) |
410
411
  | `REPO_ROOT` | project root |
411
- | `IS_UMBRELLA_REL` / `IS_MODULE_REL` | content dir (default `intelligence`) / `.intelligence/engine` |
412
+ | `IS_UMBRELLA_REL` / `IS_MODULE_REL` | content dir (default `intelligence`) / `.intelligence/packages/@ainova-systems/sync` |
413
+ | `IS_MANIFEST_NAME` | `intelligence.yaml` — feeds the `<manifest>` token (vendored default: `config.yaml`) |
412
414
  | `IS_SYNC_CMD` | `intelligence sync` (feeds the `<sync-cmd>` token) |
413
415
  | `IS_PROTECTED_DIRS` | colon-separated dirs `validate_output_path` must refuse — restores the source-tree protection a root manifest would otherwise disable |
414
416
  | `IS_SUPPRESS_CLI_NOTE` | silences the vendored-flow recommendation NOTE (stderr-only either way) |
415
417
 
416
- In CLI projects the `intelligence-sync` and `intelligence-update` meta-skills are not installed — `intelligence sync` / `intelligence update` replace them; the other meta-skills ship unchanged and reach outputs from `.intelligence/engine/skills`. `sync.sh` in CLI mode still never migrates: an outdated stamp exits `needs-update` and the CLI's own `upgrade` closes the gap. See `docs/CLI.md` for the full CLI surface.
418
+ In CLI projects the engine's content is the `@ainova-systems/sync` package (auto-added by `init`, pinned to the engine version, moved only by `intelligence upgrade`), so **every** meta-skill ships in both modes: `intelligence-sync` runs `<sync-cmd>` wherever it lands, and `intelligence-update` self-redirects to `intelligence upgrade` when it finds a root `intelligence.yaml`. `sync.sh` in CLI mode still never migrates: an outdated stamp exits `needs-update` and the CLI's own `upgrade` closes the gap. See `docs/CLI.md` for the full CLI surface.
417
419
 
418
420
  ## .gitignore Pattern
419
421
 
@@ -25,9 +25,9 @@ Three ways to shorten, in order of what they are worth:
25
25
 
26
26
  ## Source of truth
27
27
 
28
- Edit the sources listed in `config.yaml` — the `rules/`, `agents/` and `skills/` directories it names — and `config.yaml` itself. Everything else is derived: `.claude/`, `.cursor/`, `.github/{instructions,agents,skills}/`, `.codex/`, `.agents/skills/`, `.pi/`, `.opencode/` and `AGENTS.md` are **generated output**, and a hand edit there survives exactly until the next sync.
28
+ Edit the sources listed in `<manifest>` — the `rules/`, `agents/` and `skills/` directories it names — and `<manifest>` itself. A source that arrived from the engine or an installed package is not yours to edit either — it is replaced on the next update or install; change it in its own repository. Everything else is derived: `.claude/`, `.cursor/`, `.github/{instructions,agents,skills}/`, `.codex/`, `.agents/skills/`, `.pi/`, `.opencode/` and `AGENTS.md` are **generated output**, and a hand edit there survives exactly until the next sync.
29
29
 
30
- `<module>/` is the vendored engine. It owns its own rules, agents and meta-skills; `update.sh` replaces them wholesale, so a local edit there is lost at the next update. Fix it upstream instead.
30
+ `<module>/` is the engine's own content. It owns its own rules, agents and meta-skills, and every engine update replaces it wholesale, so a local edit there is lost. Fix it upstream instead.
31
31
 
32
32
  After any change: `<sync-cmd>`. A change that was not synced does not exist for any tool.
33
33
 
@@ -84,11 +84,11 @@ The verb just names the action — `add-`, `run-`, `review-`, `extract-`, `plan-
84
84
 
85
85
  Two verbs are told apart by what already exists. **`add-` puts one new member into a set that is already there** — a field on an existing type, a record among records, a component in the inventory the project keeps — so the noun names the member, and the number of files it takes to land is not the point. **`create-` brings the container itself into existence**, where nothing hosted it before. Neither verb describes how a skill is factored inside, so splitting a skill's internals never renames it.
86
86
 
87
- `intelligence-` is **reserved** for the engine's own artifacts. A project skill carrying that prefix is pruned by the updater — rename it.
87
+ `intelligence-` is **reserved** for the engine's own artifacts. A project skill carrying that prefix collides with engine ownership — everything under the prefix is the engine's to replace or remove on update — rename it.
88
88
 
89
89
  ### Shape
90
90
 
91
- - **A skill is executed, so it must not hardcode what can move.** Its steps are followed literally: a path, a command or a project name baked into a procedure breaks the moment the layout moves. Resolve them from a rule or from `config.yaml` instead. This does **not** apply to rules and agents — a rule's job is to *describe* the repository, so naming a path in prose is exactly right. Naming a path is description; baking one into a procedure is a defect waiting to fire.
91
+ - **A skill is executed, so it must not hardcode what can move.** Its steps are followed literally: a path, a command or a project name baked into a procedure breaks the moment the layout moves. Resolve them from a rule or from `<manifest>` instead. This does **not** apply to rules and agents — a rule's job is to *describe* the repository, so naming a path in prose is exactly right. Naming a path is description; baking one into a procedure is a defect waiting to fire.
92
92
  - **Keep everything the skill needs inside the skill's own folder.** The Agent Skills standard lets a skill ship `scripts/`, `references/` and `assets/` beside `SKILL.md`, and sync copies the whole directory, so a bundled helper travels with the skill to every tool. That is the default.
93
93
  - **Promote a helper out of the skill folder only when a second skill needs it** — then it belongs beside the source groups, and every skill resolves it the same way. The dividing line is reuse, not repetition: one skill's helper stays with that skill however often it runs.
94
94
  - **A helper is code.** It gets what code gets — a test, and a way to run it that does not assume one person's machine.
@@ -0,0 +1 @@
1
+ 40b7d3a7dd936c5ffb854f9e5f553720c48ef494
@@ -42,13 +42,16 @@ finalize_output_file() {
42
42
  # `<sync-cmd>` is how engine content says "run a sync": vendored setups
43
43
  # expand it to the script invocation (built from $mod, so it reproduces the
44
44
  # exact pre-token string), the CLI overrides it via IS_SYNC_CMD
45
- # (`intelligence sync`).
45
+ # (`intelligence sync`). `<manifest>` names the project's config file the
46
+ # same way: `config.yaml` vendored, `intelligence.yaml` under the CLI
47
+ # (IS_MANIFEST_NAME).
46
48
  local sc="${IS_SYNC_CMD:-bash $mod/scripts/sync.sh}"
49
+ local mf="${IS_MANIFEST_NAME:-config.yaml}"
47
50
  local tmp_file="$target.tmp"
48
51
  # Literal (index-based) substitution, not gsub: a regex replacement would
49
52
  # give `&` in a path its special meaning, and POSIX awk has no way to pass a
50
53
  # replacement string verbatim.
51
- awk -v umb="$umb" -v mod="$mod" -v sc="$sc" '
54
+ awk -v umb="$umb" -v mod="$mod" -v sc="$sc" -v mf="$mf" '
52
55
  function repl(s, from, to, out, i) {
53
56
  out = ""
54
57
  while ((i = index(s, from)) > 0) {
@@ -60,6 +63,7 @@ finalize_output_file() {
60
63
  {
61
64
  sub(/\r$/, "")
62
65
  $0 = repl($0, "<sync-cmd>", sc)
66
+ $0 = repl($0, "<manifest>", mf)
63
67
  $0 = repl($0, "<module>", mod)
64
68
  $0 = repl($0, "<umbrella>", umb)
65
69
  print
@@ -9,19 +9,19 @@ argument-hint: <domain> [description]
9
9
  ## Steps
10
10
 
11
11
  1. **Determine domain prefix** (the scope is required):
12
- - **Reuse the existing domain when one fits**: list `intelligence/agents/` and `intelligence/skills/`. If a domain prefix is already established for the target area (`backend-`, `frontend-`, `devops-`), use it. Introduce a new domain only when the scope is materially different from all existing ones.
12
+ - **Reuse the existing domain when one fits**: list `<umbrella>/agents/` and `<umbrella>/skills/`. If a domain prefix is already established for the target area (`backend-`, `frontend-`, `devops-`), use it. Introduce a new domain only when the scope is materially different from all existing ones.
13
13
  - **When no existing domain fits**, derive from repo structure:
14
- - Single / root project → use the project codename from `intelligence/config.yaml` → `project.name`
14
+ - Single / root project → use the project codename from `<manifest>` → `project.name`
15
15
  - Backend service / API component → `backend-`
16
16
  - Frontend / web / UI component → `frontend-`
17
17
  - Infrastructure, IaC, CI/CD, deployment → `devops-`
18
18
  - Shared library / common / cross-cutting code → `core-`
19
19
  - Test suites (e2e, integration) → `tests-`
20
- - Tool-internal (intelligence-sync itself) → `intelligence-`
20
+ - Tool-internal (intelligence-sync itself) → `intelligence-` (only inside the intelligence-sync repo — downstream projects must not use this prefix)
21
21
  - If the repo is a monorepo with named components (e.g., `apps/billing`, `services/auth`), prefer the component name as the domain (`billing-`, `auth-`).
22
22
  - **Every agent needs a domain prefix.** If the scope is unclear, ask the user before proceeding.
23
23
 
24
- 2. **Check existing agents**: Read `intelligence/agents/` to avoid duplicates. If an agent for this domain exists, ask user whether to update it instead.
24
+ 2. **Check existing agents**: Read `<umbrella>/agents/` to avoid duplicates. If an agent for this domain exists, ask user whether to update it instead.
25
25
 
26
26
  3. **Determine tier and access**:
27
27
  - Developer agents: `tier: heavy`, `access: full`
@@ -36,7 +36,7 @@ argument-hint: <domain> [description]
36
36
  - Build and test commands
37
37
  - Key conventions and forbidden patterns
38
38
 
39
- 5. **Create agent**: Write `intelligence/agents/<domain>-<role>.md` with frontmatter:
39
+ 5. **Create agent**: Write `<umbrella>/agents/<domain>-<role>.md` (create the directory if missing) with frontmatter:
40
40
  ```yaml
41
41
  ---
42
42
  name: <domain>-<role>
@@ -52,11 +52,11 @@ argument-hint: <domain> [description]
52
52
 
53
53
  6. **Write body** with sections: **Expertise** -> **Boundaries** -> **Build & Verify**
54
54
  - An agent is **thin**: who it is, where it stops, how it verifies. Everything else already reaches it.
55
- - **Do not tell the agent to read the rules.** Rules load on their own: Claude Code loads `.claude/rules/` into every custom subagent's startup context alongside `CLAUDE.md` (*Subagents → What loads at startup*), and Cursor / Copilot / Codex / Pi / opencode receive always-on rules inlined in `AGENTS.md`. A `Read intelligence/rules/<domain>.md before starting` line duplicates content the agent already has — double the tokens, and a second copy that drifts from the rule it copied.
55
+ - **Do not tell the agent to read the rules.** Rules load on their own: Claude Code loads `.claude/rules/` into every custom subagent's startup context alongside `CLAUDE.md` (*Subagents → What loads at startup*), and Cursor / Copilot / Codex / Pi / opencode receive always-on rules inlined in `AGENTS.md`. A `Read <umbrella>/rules/<domain>.md before starting` line duplicates content the agent already has — double the tokens, and a second copy that drifts from the rule it copied.
56
56
  - **Point at a rule, never restate it.** If you want to copy a rule into the agent, the rule is in the wrong place — move it, do not clone it.
57
57
  - **Do carry** what is genuinely the agent's own: its boundaries ("if the app is not running, stop — do not hand-write the output"), its verification commands, its definition of done.
58
58
  - All content must come from actual codebase analysis.
59
59
 
60
- 7. **Link existing skills**: Find skills in `intelligence/skills/` matching this domain prefix and add them to the agent's `skills:` frontmatter.
60
+ 7. **Link existing skills**: Find skills in `<umbrella>/skills/` matching this domain prefix and add them to the agent's `skills:` frontmatter.
61
61
 
62
62
  8. **Run `/intelligence-sync`** to distribute to all enabled IDE targets.
@@ -9,9 +9,9 @@ argument-hint: <name> [paths-glob]
9
9
  ## Steps
10
10
 
11
11
  1. **Determine rule name from domain** (the scope is required):
12
- - **Reuse the existing domain when one fits**: list `intelligence/rules/`. If a rule file covers the target area (e.g., `backend.md`, `frontend.md`), extend it. Introduce a new domain only when the scope is materially different from all existing rules.
12
+ - **Reuse the existing domain when one fits**: list `<umbrella>/rules/`. If a rule file covers the target area (e.g., `backend.md`, `frontend.md`), extend it. Introduce a new domain only when the scope is materially different from all existing rules.
13
13
  - **When no existing rule fits**, derive the filename from repo structure:
14
- - Single / root project → use the project codename from `intelligence/config.yaml` → `project.name` (e.g., `<codename>.md`)
14
+ - Single / root project → use the project codename from `<manifest>` → `project.name` (e.g., `<codename>.md`)
15
15
  - Backend service / API component → `backend.md`
16
16
  - Frontend / web / UI component → `frontend.md`
17
17
  - Infrastructure, IaC, CI/CD, deployment → `devops.md`
@@ -21,7 +21,7 @@ argument-hint: <name> [paths-glob]
21
21
  - If the repo is a monorepo with named components (e.g., `apps/billing`, `services/auth`), prefer the component name as the rule name (`billing.md`, `auth.md`).
22
22
  - **Rule filenames match the domain used by skills/agents.** If the scope is unclear, ask the user before proceeding.
23
23
 
24
- 2. **Check existing rules**: Read `intelligence/rules/` to detect overlapping scope — favor extending an existing rule over creating a new one.
24
+ 2. **Check existing rules**: Read `<umbrella>/rules/` to detect overlapping scope — favor extending an existing rule over creating a new one.
25
25
 
26
26
  3. **Determine scope**:
27
27
  - If paths glob provided — scoped rule with `paths:` frontmatter
@@ -34,7 +34,7 @@ argument-hint: <name> [paths-glob]
34
34
  - Build and test commands specific to this scope
35
35
  - Anti-patterns observed in code, each paired with the positive replacement that should adopt instead
36
36
 
37
- 5. **Create rule**: Write `intelligence/rules/<name>.md`:
37
+ 5. **Create rule**: Write `<umbrella>/rules/<name>.md` (create the directory if it does not exist — the sources list already covers it):
38
38
  ```yaml
39
39
  ---
40
40
  paths:
@@ -49,6 +49,6 @@ argument-hint: <name> [paths-glob]
49
49
  - Examples come from the actual codebase — reference real files
50
50
  - Every REQUIRED / Invariant / Pattern is backed by observed code
51
51
 
52
- 7. **Update config.yaml** if needed: Add source path to `sources.rules` if rule is in a new directory not yet listed.
52
+ 7. **Update `<manifest>` only when needed**: add the path to `sources.rules` only if the rule lives in a directory not already listed there — creating a pre-listed directory is enough.
53
53
 
54
54
  8. **Run `/intelligence-sync`** to distribute to all enabled IDE targets.