@ainova-systems/intelligence 0.11.0-rc.1 → 0.11.0-rc.11

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 (72) hide show
  1. package/README.md +19 -16
  2. package/cli/commands/adapter.sh +153 -0
  3. package/cli/commands/init.sh +171 -34
  4. package/cli/commands/package.sh +30 -0
  5. package/cli/commands/registry.sh +65 -30
  6. package/cli/commands/status.sh +20 -11
  7. package/cli/commands/sync.sh +9 -4
  8. package/cli/commands/update.sh +80 -46
  9. package/cli/engine-package.yaml +14 -0
  10. package/cli/intelligence +37 -26
  11. package/cli/internal/align-project.sh +134 -0
  12. package/cli/internal/check.sh +128 -0
  13. package/cli/internal/convert-legacy.sh +400 -0
  14. package/cli/{commands/add.sh → internal/package-add.sh} +37 -19
  15. package/cli/{commands/list.sh → internal/package-list.sh} +10 -5
  16. package/cli/{commands/remove.sh → internal/package-remove.sh} +16 -4
  17. package/cli/internal/package-search.sh +83 -0
  18. package/cli/internal/package-update.sh +103 -0
  19. package/cli/internal/restore.sh +130 -0
  20. package/cli/internal/target-state.sh +57 -0
  21. package/cli/lib/cli-common.sh +213 -63
  22. package/cli/lib/lockfile.sh +15 -9
  23. package/cli/lib/manifest.sh +257 -4
  24. package/cli/lib/registry.sh +96 -47
  25. package/cli/lib/semver.sh +12 -4
  26. package/engine/ENGINE_SHA +1 -0
  27. package/engine/{scripts/adapters → adapters}/_template.sh +10 -9
  28. package/engine/{scripts/adapters → adapters}/agents.sh +20 -25
  29. package/engine/{scripts/adapters → adapters}/opencode.sh +1 -1
  30. package/engine/{scripts/lib → lib}/common.sh +30 -538
  31. package/engine/lib/contract.sh +120 -0
  32. package/engine/sync.sh +238 -0
  33. package/package.json +5 -5
  34. package/{engine → packages/sync}/agents/intelligence-architect.md +5 -3
  35. package/{engine → packages/sync}/agents/intelligence-operator.md +10 -13
  36. package/packages/sync/references/adapters.md +252 -0
  37. package/packages/sync/references/conventions.md +385 -0
  38. package/{engine → packages/sync}/rules/intelligence-authoring.md +7 -7
  39. package/{engine → packages/sync}/skills/intelligence-add-agent/SKILL.md +7 -7
  40. package/{engine → packages/sync}/skills/intelligence-add-rule/SKILL.md +5 -5
  41. package/{engine → packages/sync}/skills/intelligence-add-skill/SKILL.md +5 -5
  42. package/{engine → packages/sync}/skills/intelligence-extract-skill/SKILL.md +2 -2
  43. package/packages/sync/skills/intelligence-install-adapter/SKILL.md +38 -0
  44. package/{engine → packages/sync}/skills/intelligence-learn-from-context/SKILL.md +3 -3
  45. package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +55 -0
  46. package/{engine → packages/sync}/skills/intelligence-review-skills/SKILL.md +7 -7
  47. package/packages/sync/skills/intelligence-sync/SKILL.md +20 -0
  48. package/packages/sync/skills/intelligence-uninstall-adapter/SKILL.md +24 -0
  49. package/packages/sync/skills/intelligence-update/SKILL.md +43 -0
  50. package/cli/commands/doctor.sh +0 -100
  51. package/cli/commands/install.sh +0 -84
  52. package/cli/commands/migrate.sh +0 -251
  53. package/cli/commands/upgrade.sh +0 -27
  54. package/engine/INIT.md +0 -498
  55. package/engine/docs/ADAPTERS.md +0 -212
  56. package/engine/docs/CLI.md +0 -91
  57. package/engine/docs/CONVENTIONS.md +0 -440
  58. package/engine/scripts/lib/layout.sh +0 -51
  59. package/engine/scripts/lib/migrations.sh +0 -708
  60. package/engine/scripts/sync.sh +0 -311
  61. package/engine/scripts/update.sh +0 -237
  62. package/engine/skills/intelligence-install-adapter/SKILL.md +0 -31
  63. package/engine/skills/intelligence-sync/SKILL.md +0 -18
  64. package/engine/skills/intelligence-uninstall-adapter/SKILL.md +0 -42
  65. package/engine/skills/intelligence-update/SKILL.md +0 -159
  66. package/registry/index.yaml +0 -15
  67. /package/engine/{scripts/VERSION → VERSION} +0 -0
  68. /package/engine/{scripts/adapters → adapters}/claude.sh +0 -0
  69. /package/engine/{scripts/adapters → adapters}/codex.sh +0 -0
  70. /package/engine/{scripts/adapters → adapters}/copilot.sh +0 -0
  71. /package/engine/{scripts/adapters → adapters}/cursor.sh +0 -0
  72. /package/engine/{scripts/adapters → adapters}/pi.sh +0 -0
@@ -0,0 +1,400 @@
1
+ #!/bin/bash
2
+ # Internal conversion from legacy Intelligence Sync to Intelligence CLI.
3
+ # to an Intelligence project: root intelligence.yaml, .intelligence/ store,
4
+ # intelligence.lock, no vendored engine.
5
+ #
6
+ # Transactional: STAGE everything next to the
7
+ # project, VERIFY the staged state, then COMMIT in an order where every
8
+ # destructive step happens only after a real sync of the new state reported
9
+ # IS_STATUS=ok. Any earlier failure leaves the project untouched.
10
+ set -euo pipefail
11
+ source "$CLI_DIR/lib/cli-common.sh"
12
+
13
+ dry_run=0 force=0
14
+ while [ $# -gt 0 ]; do
15
+ case "$1" in
16
+ --dry-run) dry_run=1 ;;
17
+ --force) force=1 ;;
18
+ *) die "unknown option '$1'" ;;
19
+ esac
20
+ shift || true
21
+ done
22
+
23
+ # ---- Preconditions (fail closed, nothing written) -------------------------
24
+ detect_project
25
+ case "$IP_MODE" in
26
+ legacy) ;;
27
+ cli) die "already an Intelligence project ($IP_ROOT/intelligence.yaml)" ;;
28
+ *) die "no legacy Intelligence Sync project found here — 'intelligence init' starts a fresh project" ;;
29
+ esac
30
+ root="$IP_ROOT"
31
+ umbrella="$IP_UMBRELLA"
32
+ module_dir="$IP_MODULE_DIR"
33
+ config="$umbrella/config.yaml"
34
+ module_rel="${module_dir#"$root"/}"
35
+ umbrella_rel="${umbrella#"$root"/}"
36
+
37
+ for existing in intelligence.yaml intelligence.lock .intelligence; do
38
+ if [ -e "$root/$existing" ] || [ -L "$root/$existing" ]; then
39
+ die "$existing already exists at the root — conflicting Intelligence CLI state; move it aside or restore the project before converting"
40
+ fi
41
+ done
42
+ if [ "$dry_run" -eq 0 ] && [ "$force" -eq 0 ]; then
43
+ if [ -n "$(git -C "$root" status --porcelain 2>/dev/null)" ]; then
44
+ die "working tree is not clean — commit or stash first (or --force); conversion produces a one-commit diff you can review and revert"
45
+ fi
46
+ fi
47
+
48
+ # `sync_version` belongs to the legacy Intelligence Sync format. Intelligence uses
49
+ # `schema_version`, so conversion reads the legacy scalar explicitly.
50
+ stamp="$(top_scalar "$config" "sync_version")"
51
+ eng="$(bundled_engine_version)"
52
+ [ -n "$stamp" ] && _ver_gt "$stamp" "$eng" && die "project schema $stamp is newer than this CLI's engine $eng — update the CLI first"
53
+
54
+ # The legacy `packs:` block is the one Intelligence Sync shape conversion still reads, and the
55
+ # engine no longer knows it — the reader lives here, with its only consumer.
56
+ get_pack_field() {
57
+ get_nested_yaml_value "$1" "packs" "$2" "$3"
58
+ }
59
+
60
+ paths_overlap() {
61
+ case "$1" in "$2"|"$2"/*) return 0 ;; esac
62
+ case "$2" in "$1"|"$1"/*) return 0 ;; esac
63
+ return 1
64
+ }
65
+
66
+ validate_mirror_path() {
67
+ local pack="$1" mirror="$2" canon rel src src_canon src_rel target output protected mirror_phys
68
+ [ -n "$mirror" ] || return 0
69
+ case "$mirror" in
70
+ .|/*|*..*|*\\*|[A-Za-z]:*) die "pack '$pack' has unsafe mirror path '$mirror'" ;;
71
+ esac
72
+ canon="$(normalize_path "$root/$mirror")"
73
+ case "$canon" in "$root"/*) ;; *) die "pack '$pack' mirror escapes the repository: '$mirror'" ;; esac
74
+ rel="${canon#"$root"/}"
75
+
76
+ # A legacy mirror may live below the content directory (the normal location is
77
+ # intelligence/external/<pack>), but it must never own the directory itself,
78
+ # the vendored module, CLI state, authored sources, or generated outputs.
79
+ case "$umbrella_rel" in "$rel"|"$rel"/*) die "pack '$pack' mirror '$mirror' contains the legacy content directory" ;; esac
80
+ for protected in .git .intelligence "$module_rel"; do
81
+ paths_overlap "$rel" "$protected" && die "pack '$pack' mirror '$mirror' overlaps protected path '$protected'"
82
+ done
83
+ for section in rules agents skills; do
84
+ while IFS= read -r src; do
85
+ case "$src" in ""|git+*|@*) continue ;; esac
86
+ src_canon="$(normalize_path "$root/$src")"
87
+ case "$src_canon" in "$root"/*) src_rel="${src_canon#"$root"/}" ;; *) continue ;; esac
88
+ paths_overlap "$rel" "$src_rel" \
89
+ && die "pack '$pack' mirror '$mirror' overlaps configured source '$src'"
90
+ done < <(read_yaml_list "$config" "$section")
91
+ done
92
+ while IFS= read -r target; do
93
+ [ -n "$target" ] || continue
94
+ output="$(get_target_output "$config" "$target")"
95
+ [ -n "$output" ] || continue
96
+ case "$output" in /*|*..*) continue ;; esac
97
+ paths_overlap "$rel" "${output%/}" \
98
+ && die "pack '$pack' mirror '$mirror' overlaps target '$target' output '$output'"
99
+ done < <(read_yaml_keys "$config" "targets")
100
+
101
+ if [ -d "$canon" ]; then
102
+ mirror_phys="$(cd "$canon" && pwd -P)"
103
+ case "$mirror_phys" in "$root"|"$root"/*) ;; *) die "pack '$pack' mirror resolves outside the repository: '$mirror'" ;; esac
104
+ [ -f "$canon/.pack" ] || die "pack '$pack' mirror '$mirror' has no .pack ownership stamp — refusing to copy or delete project-owned content"
105
+ fi
106
+ }
107
+
108
+ # ---- Stage ----------------------------------------------------------------
109
+ if [ "$dry_run" -eq 1 ]; then
110
+ stage="$(mktemp -d -t intelligence-migrate-XXXXXX 2>/dev/null || mktemp -d)"
111
+ else
112
+ stage="$(mktemp -d "$root/.intelligence-migrate-stage.XXXXXX")"
113
+ fi
114
+ cleanup_stage() { rm -rf "$stage"; }
115
+ trap cleanup_stage EXIT INT TERM
116
+ mkdir -p "$stage/.intelligence/packages"
117
+
118
+ # Intelligence carries no legacy migration chain: the archived Intelligence Sync
119
+ # engine knows how to walk an old project forward. Conversion accepts only a
120
+ # project already at the final legacy schema and names the exact remedy otherwise —
121
+ # a remedy that keeps working indefinitely, because the archive stays at the
122
+ # URL the project's own update.sh already clones.
123
+ LEGACY_FINAL_SCHEMA="0.10.0"
124
+ if [ -z "$stamp" ] || _ver_gt "$LEGACY_FINAL_SCHEMA" "$stamp"; then
125
+ echo "ERROR: project schema is ${stamp:-absent (pre-0.3.1)}, older than the final legacy Intelligence Sync schema $LEGACY_FINAL_SCHEMA." >&2
126
+ echo " Bring it forward with its own engine first — that still works:" >&2
127
+ echo " bash $module_rel/scripts/update.sh --yes" >&2
128
+ echo " then re-run: intelligence init" >&2
129
+ exit 1
130
+ fi
131
+
132
+ echo "== staging =="
133
+ # Engine content arrives as the @ainova-systems/sync package, seeded from the
134
+ # bundle (fetch_package's guard — no network).
135
+ sync_pkg_sha="$(fetch_package "$SYNC_PKG_URL" "v$eng" "$SYNC_PKG_PATH" "$stage/$SYNC_PKG_STORE")"
136
+
137
+ # Legacy pack name -> Intelligence package name (@org/repo derived from the url), plus
138
+ # per-pack source facts. Recorded as rows for the config rewrite and the lock.
139
+ pack_rows="$stage/packs.rows"
140
+ : > "$pack_rows"
141
+ seen_names=" "
142
+ seen_mirrors=" "
143
+ while IFS= read -r pack; do
144
+ [ -n "$pack" ] || continue
145
+ url="$(get_pack_field "$config" "$pack" "url")"
146
+ ref="$(get_pack_field "$config" "$pack" "ref")"
147
+ mirror="$(get_pack_field "$config" "$pack" "mirror")"
148
+ [ -n "$url" ] || die "pack '$pack' has no url in config.yaml"
149
+ assert_safe_source_url "$url"
150
+ assert_safe_ref "$ref"
151
+ validate_mirror_path "$pack" "$mirror"
152
+ if [ -n "$mirror" ]; then
153
+ mirror_key="$(normalize_path "$root/$mirror")"
154
+ case "$seen_mirrors" in *" $mirror_key "*) die "pack '$pack' shares mirror '$mirror' with another pack" ;; esac
155
+ seen_mirrors="$seen_mirrors$mirror_key "
156
+ fi
157
+ base="${url%.git}"; base="${base%/}"
158
+ repo="$(basename "$base")"
159
+ org="$(basename "$(dirname "$base")")"
160
+ case "$org" in ""|*[!A-Za-z0-9._-]*) org="local" ;; esac
161
+ case "$repo" in ""|*[!A-Za-z0-9._-]*) repo="$(printf '%s' "$repo" | tr -c 'A-Za-z0-9._-' '-')" ;; esac
162
+ name="@$org/$repo"
163
+ assert_valid_pkg_name "$name"
164
+ # Two packs deriving the same @org/repo would overwrite one store dir and
165
+ # duplicate the lock key — fail closed, the user renames a source.
166
+ case "$seen_names" in
167
+ *" $name "*) die "packs '$pack' and another both derive package name '$name' — sources must map to distinct names" ;;
168
+ esac
169
+ seen_names="$seen_names $name "
170
+ printf '%s\n' "$pack$LOCK_SEP$name$LOCK_SEP$url$LOCK_SEP$ref$LOCK_SEP$mirror"
171
+ done < <(read_yaml_keys "$config" "packs") >> "$pack_rows"
172
+
173
+ # Stage the store: mirrored packs are COPIED from their mirrors (content the
174
+ # repo already holds — the network is not consulted, and the mirror is not
175
+ # touched); transient packs are fetched.
176
+ lock_rows="$stage/lock.rows"
177
+ : > "$lock_rows"
178
+ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
179
+ [ -n "$pack" ] || continue
180
+ dest="$stage/.intelligence/packages/$name"
181
+ sha=""
182
+ if [ -n "$mirror" ] && [ -d "$root/$mirror" ]; then
183
+ mkdir -p "$dest"
184
+ cp -R "$root/$mirror/." "$dest/"
185
+ if [ -f "$dest/.pack" ]; then
186
+ sha="$(awk -F= '$1 == "sha" {print $2; exit}' "$dest/.pack")"
187
+ rm -f "$dest/.pack"
188
+ fi
189
+ echo " pack '$pack' -> $name (from mirror $mirror)"
190
+ else
191
+ sha="$(fetch_package "$url" "$ref" "" "$dest")"
192
+ echo " pack '$pack' -> $name (fetched)"
193
+ fi
194
+ if [ -z "$sha" ] || [ "$sha" = "unknown" ]; then
195
+ sha="$(GIT_TERMINAL_PROMPT=0 git ls-remote -- "$url" "$ref" 2>/dev/null | awk '{print $1; exit}')"
196
+ fi
197
+ [ -n "$ref" ] || ref="HEAD"
198
+ printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
199
+ "$name" "" "$url" "" "$ref" "$sha" >> "$lock_rows"
200
+ done < "$pack_rows"
201
+
202
+ # The manifest: config.yaml transformed line by line — comments and unknown
203
+ # blocks survive verbatim; `sync_version` and `packs:` are dropped (restamped
204
+ # / converted); sources entries are rewritten onto the store.
205
+ manifest_stage="$stage/intelligence.yaml"
206
+ rewrite_source_value() {
207
+ local v="$1" pack name rest
208
+ case "$v" in
209
+ "$module_rel"/rules|"$module_rel"/agents|"$module_rel"/skills)
210
+ printf '%s/%s' "$SYNC_PKG_STORE" "${v##*/}"
211
+ return 0 ;;
212
+ @*)
213
+ rest="${v#@}"
214
+ pack="${rest%%/*}"
215
+ while IFS="$LOCK_SEP" read -r p name _ _ _; do
216
+ if [ "$p" = "$pack" ]; then
217
+ if [ "$rest" = "$pack" ]; then
218
+ printf '.intelligence/packages/%s' "$name"
219
+ else
220
+ printf '.intelligence/packages/%s/%s' "$name" "${rest#*/}"
221
+ fi
222
+ return 0
223
+ fi
224
+ done < "$pack_rows"
225
+ ;;
226
+ esac
227
+ printf '%s' "$v"
228
+ }
229
+ in_packs=0 in_sources=0
230
+ while IFS= read -r line || [ -n "$line" ]; do
231
+ line="${line%$'\r'}"
232
+ case "$line" in
233
+ sync_version:*) continue ;;
234
+ packs:*) in_packs=1; continue ;;
235
+ sources:*) in_sources=1; in_packs=0; echo "$line"; continue ;;
236
+ [!\ \#]*) in_packs=0; in_sources=0 ;;
237
+ esac
238
+ [ "$in_packs" -eq 1 ] && continue
239
+ if [ "$in_sources" -eq 1 ]; then
240
+ case "$line" in
241
+ *"- "*)
242
+ indent="${line%%-*}"
243
+ v="${line#*- }"
244
+ # A trailing inline comment survives the rewrite — split it
245
+ # off before touching quotes, or it would be mangled into the
246
+ # value.
247
+ trail=""
248
+ case "$v" in
249
+ *[[:space:]]"#"*)
250
+ trail=" #${v#*[[:space:]]#}"
251
+ v="${v%%[[:space:]]#*}"
252
+ ;;
253
+ esac
254
+ v="${v%"${v##*[![:space:]]}"}"
255
+ v="${v%\"}"; v="${v#\"}"; v="${v%\'}"; v="${v#\'}"
256
+ echo "$indent- \"$(rewrite_source_value "$v")\"$trail"
257
+ continue
258
+ ;;
259
+ esac
260
+ fi
261
+ echo "$line"
262
+ done < "$config" > "$manifest_stage"
263
+ stamp_schema_version "$manifest_stage" "$eng"
264
+
265
+ # packages: requested pins only; resolved source details live in the lock.
266
+ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
267
+ [ -n "$pack" ] || continue
268
+ qmap_set "$manifest_stage" "packages" "$name" "ref" "${ref:-HEAD}"
269
+ done < "$pack_rows"
270
+
271
+ # The engine's own content rides along as a package, pinned to the engine.
272
+ qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "version" "$eng"
273
+ printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
274
+ "$SYNC_PKG_NAME" "$eng" "$SYNC_PKG_URL" "$SYNC_PKG_PATH" "v$eng" "$sync_pkg_sha" >> "$lock_rows"
275
+
276
+ # ---- Verify ---------------------------------------------------------------
277
+ echo "== verifying staged state =="
278
+ fail=0
279
+ for section in rules agents skills; do
280
+ while IFS= read -r src; do
281
+ [ -n "$src" ] || continue
282
+ case "$src" in
283
+ .intelligence/*) [ -d "$stage/$src" ] || { echo " MISSING (staged): $src"; fail=1; } ;;
284
+ git+*|@*) echo " UNCONVERTED source token: $src"; fail=1 ;;
285
+ *) [ -d "$root/$src" ] || echo " NOTE: local source '$src' does not exist (kept — same as before)" ;;
286
+ esac
287
+ done < <(read_yaml_list "$manifest_stage" "$section")
288
+ done
289
+ target_keys() {
290
+ awk '
291
+ /^targets:[[:space:]]*$/ { in_targets=1; next }
292
+ in_targets && /^[^[:space:]#]/ { exit }
293
+ in_targets && /^ [A-Za-z_][A-Za-z0-9_]*:/ {
294
+ line=$0; sub(/^ /, "", line); sub(/:.*/, "", line); print line
295
+ }
296
+ ' "$1"
297
+ }
298
+ while IFS= read -r adapter; do
299
+ [ -n "$adapter" ] || continue
300
+ old_e="$(is_target_enabled "$config" "$adapter")"
301
+ new_e="$(is_target_enabled "$manifest_stage" "$adapter")"
302
+ [ "$old_e" = "$new_e" ] || { echo " TARGET DRIFT: $adapter enabled '$old_e' -> '$new_e'"; fail=1; }
303
+ old_o="$(get_target_output "$config" "$adapter")"
304
+ new_o="$(get_target_output "$manifest_stage" "$adapter")"
305
+ [ "$old_o" = "$new_o" ] || { echo " TARGET DRIFT: $adapter output '$old_o' -> '$new_o'"; fail=1; }
306
+ done < <({ target_keys "$config"; target_keys "$manifest_stage"; } | sort -u)
307
+ [ "$fail" -eq 0 ] || die "staged state failed verification — nothing changed"
308
+
309
+ if [ "$dry_run" -eq 1 ]; then
310
+ echo ""
311
+ echo "== dry run — the project is untouched. Staged manifest: =="
312
+ cat "$manifest_stage"
313
+ echo ""
314
+ echo "Would: write intelligence.yaml + intelligence.lock, move the store in,"
315
+ echo "sync, then remove $module_rel/, config.yaml and the pack mirrors."
316
+ exit 0
317
+ fi
318
+
319
+ # ---- Commit ---------------------------------------------------------------
320
+ echo "== committing =="
321
+ # .gitignore is part of the transaction: rollback must leave it exactly as
322
+ # found, absent included.
323
+ gitignore_existed=0
324
+ if [ -f "$root/.gitignore" ]; then
325
+ gitignore_existed=1
326
+ cp "$root/.gitignore" "$stage/gitignore.orig"
327
+ fi
328
+ commit_active=1
329
+ rollback() {
330
+ [ "$commit_active" -eq 1 ] || return 0
331
+ commit_active=0
332
+ set +e
333
+ rm -rf "$root/.intelligence" "$root/intelligence.yaml" "$root/intelligence.lock"
334
+ if [ "$gitignore_existed" -eq 1 ]; then
335
+ cp "$stage/gitignore.orig" "$root/.gitignore"
336
+ else
337
+ rm -f "$root/.gitignore"
338
+ fi
339
+ rm -rf "$stage"
340
+ echo "rolled back — the vendored setup is untouched" >&2
341
+ }
342
+ rollback_on_exit() {
343
+ local rc=$?
344
+ rollback
345
+ exit "$rc"
346
+ }
347
+ # Every live write through verified sync is covered by rollback, including a
348
+ # plain set -e/EXIT failure rather than only an explicit sync refusal.
349
+ trap rollback_on_exit EXIT
350
+ trap 'rollback; exit 130' INT TERM
351
+ if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
352
+ {
353
+ [ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
354
+ echo "# intelligence CLI package store (restored automatically by 'intelligence sync')"
355
+ echo ".intelligence/"
356
+ } >> "$root/.gitignore"
357
+ fi
358
+ mv "$stage/.intelligence" "$root/.intelligence"
359
+ cp "$manifest_stage" "$root/intelligence.yaml"
360
+ lock_write_from_tsv "$root/intelligence.lock" "$lock_rows"
361
+
362
+ sync_out="$(cd "$root" && IS_SUPPRESS_CLI_NOTE=1 bash "$CLI_DIR/commands/sync.sh" 2>&1)" || {
363
+ echo "$sync_out"
364
+ die "sync of the converted state failed"
365
+ }
366
+ echo "$sync_out" | grep -q '^IS_STATUS=ok' || {
367
+ echo "$sync_out"
368
+ die "sync of the converted state did not report ok"
369
+ }
370
+ # Point of no return: the new state is verified. From here an interrupt must
371
+ # NOT roll back (the old setup is being removed) — only clean the stage.
372
+ commit_active=0
373
+ trap cleanup_stage EXIT INT TERM
374
+
375
+ # Only now is anything of the old setup removed — and config.yaml is kept as
376
+ # a reviewable backup inside the store.
377
+ mkdir -p "$root/.intelligence/backup"
378
+ cp "$config" "$root/.intelligence/backup/config.yaml"
379
+ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
380
+ # Containment before deletion: a mirror value is config input, and rm -rf
381
+ # must never follow it out of the repository.
382
+ case "$mirror" in
383
+ ""|/*|*..*) [ -n "$mirror" ] && echo " WARN: mirror '$mirror' not repo-contained — left in place" >&2; continue ;;
384
+ esac
385
+ if [ -d "$root/$mirror" ]; then
386
+ rm -rf "${root:?}/$mirror"
387
+ # An emptied legacy mirror parent (for example intelligence/external/) goes with it.
388
+ rmdir "$(dirname "$root/$mirror")" 2>/dev/null || true
389
+ fi
390
+ done < "$pack_rows"
391
+ rm -rf "$module_dir"
392
+ rm -f "$config"
393
+ # An emptied umbrella dir (config-only setups) is dropped; one with project
394
+ # content stays.
395
+ rmdir "$umbrella" 2>/dev/null || true
396
+
397
+ echo ""
398
+ echo "converted. Review the diff, then commit. The old config.yaml is kept at .intelligence/backup/config.yaml."
399
+ echo "From now on: intelligence sync | package | update | status."
400
+ echo "Next: ask your agent to run /intelligence-learn-from-repository to review the converted project context."
@@ -1,15 +1,15 @@
1
1
  #!/bin/bash
2
- # intelligence add <spec> [--name @scope/name] [--no-sync]
2
+ # Internal package add operation.
3
3
  #
4
4
  # Specs:
5
- # @scope/name[@range] resolved via registries / index / convention
6
- # github:org/repo[#path] direct from GitHub, no registry involved
7
- # git+<url>[@ref][#path] direct from any git host
5
+ # @scope/name[@range] resolved ONLY through the manifest's trusted registries
6
+ # github:org/repo[#path] explicit source: direct from GitHub, no registry involved
7
+ # git+<url>[@ref][#path] explicit source: direct from any git host
8
8
  set -euo pipefail
9
9
  source "$CLI_DIR/lib/cli-common.sh"
10
10
 
11
11
  spec="${1:-}"
12
- [ -n "$spec" ] || die "usage: intelligence add <@scope/name[@range] | github:org/repo[#path] | git+<url>[@ref][#path]>"
12
+ [ -n "$spec" ] || die "usage: intelligence package add <@scope/name[@range] | github:org/repo[#path] | git+<url>[@ref][#path]>"
13
13
  shift || true
14
14
 
15
15
  no_sync=0
@@ -23,7 +23,7 @@ while [ $# -gt 0 ]; do
23
23
  shift || true
24
24
  done
25
25
 
26
- require_v2
26
+ require_cli_project
27
27
  manifest="$IP_ROOT/intelligence.yaml"
28
28
  lock="$IP_ROOT/intelligence.lock"
29
29
 
@@ -43,14 +43,15 @@ case "$spec" in
43
43
  rest="${spec#git+}"
44
44
  case "$rest" in *#*) path="${rest#*#}"; rest="${rest%%#*}" ;; esac
45
45
  # ref = the part after the last `@` in the post-scheme URL, unless it
46
- # contains `/` (then it is a user@host, not a ref) — the engine's own
47
- # git+ grammar.
46
+ # contains `/` or `:` — either marks a user@host part (scp-like
47
+ # `git@host:repo.git` included), not a ref. A branch name containing
48
+ # `/` cannot be expressed here; pin it via `ref:` in the manifest.
48
49
  base="${rest#*://}"
49
50
  case "$base" in
50
51
  *@*)
51
52
  tail="${base##*@}"
52
53
  case "$tail" in
53
- */*) ;;
54
+ */*|*:*) ;;
54
55
  *) ref="$tail"; rest="${rest%@"$tail"}" ;;
55
56
  esac
56
57
  ;;
@@ -78,9 +79,27 @@ assert_valid_pkg_name "$name"
78
79
  if [ "$mode" = "registry" ]; then
79
80
  resolve_package_source "$manifest" "$name"
80
81
  url="$RES_URL"; path="$RES_PATH"
82
+ if [ -z "$url" ]; then
83
+ echo "ERROR: no trusted registry declares '$name'." >&2
84
+ echo " A name installs only through a registry this project added explicitly" >&2
85
+ echo " ('intelligence registry list' shows them). Either add its registry:" >&2
86
+ echo " intelligence registry add <registry-repo-url>" >&2
87
+ echo " or install from an explicit source you name yourself:" >&2
88
+ echo " intelligence package add github:org/repo | intelligence package add git+<url>[@ref][#path]" >&2
89
+ suggest_similar "$manifest" "$name"
90
+ exit 1
91
+ fi
81
92
  echo " resolving $name via $RES_VIA -> $url${path:+ ($path)}"
82
93
  fi
83
94
 
95
+ # The source must exist before anything else is attempted — never die
96
+ # mid-pipeline with no message.
97
+ if ! GIT_TERMINAL_PROMPT=0 git ls-remote -- "$url" >/dev/null 2>&1; then
98
+ echo "ERROR: no reachable repository at $url" >&2
99
+ echo " Declared by ${RES_VIA:-the spec} — check the URL and your git access." >&2
100
+ exit 1
101
+ fi
102
+
84
103
  # Pick the version: an explicit ref pins; otherwise the highest stable tag
85
104
  # matching the range (npm's behaviour, `^highest` becoming the requested range
86
105
  # when none was given).
@@ -98,6 +117,7 @@ if [ -z "$ref" ]; then
98
117
  die "'$name' has no stable version tags at $url — a registry package must be versioned; use github:/git+ with @<ref> to pin a branch or commit"
99
118
  fi
100
119
  echo " NOTE: no version tags at $url — pinning the default branch head" >&2
120
+ ref="HEAD"
101
121
  fi
102
122
  fi
103
123
 
@@ -106,18 +126,16 @@ sha="$(fetch_package "$url" "${ref:-$resolved_tag}" "$path" "$IP_ROOT/$rel")"
106
126
 
107
127
  wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
108
128
 
109
- # Manifest entry: a registry package records its range; a direct one records
110
- # its source so resolution never has to guess it again.
111
- if [ "$mode" = "registry" ]; then
129
+ # The manifest records requested intent only. The resolved source URL/path,
130
+ # tag/ref and SHA live in the required lock. Re-adding a package is the
131
+ # explicit operation for changing its trusted source.
132
+ qmap_delete_key "$manifest" "packages" "$name"
133
+ if [ -n "$ref" ]; then
134
+ qmap_set "$manifest" "packages" "$name" "ref" "$ref"
135
+ elif [ -n "$requested" ]; then
112
136
  qmap_set "$manifest" "packages" "$name" "version" "$requested"
113
137
  else
114
- qmap_set "$manifest" "packages" "$name" "url" "$url"
115
- [ -n "$path" ] && qmap_set "$manifest" "packages" "$name" "path" "$path"
116
- if [ -n "$ref" ]; then
117
- qmap_set "$manifest" "packages" "$name" "ref" "$ref"
118
- elif [ -n "$requested" ]; then
119
- qmap_set "$manifest" "packages" "$name" "version" "$requested"
120
- fi
138
+ die "internal: package '$name' has neither a requested version nor ref"
121
139
  fi
122
140
 
123
141
  lock_upsert "$lock" "$name" "$requested" "$url" "$path" "${ref:-$resolved_tag}" "$sha"
@@ -1,9 +1,9 @@
1
1
  #!/bin/bash
2
- # intelligence list — packages with requested / locked / installed state.
2
+ # Internal package list operation.
3
3
  set -euo pipefail
4
4
  source "$CLI_DIR/lib/cli-common.sh"
5
5
 
6
- require_v2
6
+ require_cli_project
7
7
  manifest="$IP_ROOT/intelligence.yaml"
8
8
  lock="$IP_ROOT/intelligence.lock"
9
9
 
@@ -14,8 +14,13 @@ while IFS= read -r name; do
14
14
  range="$(qmap_field "$manifest" "packages" "$name" "version")"
15
15
  ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
16
16
  resolved="$(qmap_field "$lock" "packages" "$name" "resolved")"
17
- state="missing — run 'intelligence install'"
18
- [ -d "$IP_ROOT/.intelligence/packages/$name" ] && state="installed"
17
+ if [ ! -f "$lock" ]; then
18
+ state="lock missing — restore committed intelligence.lock"
19
+ elif [ -d "$IP_ROOT/.intelligence/packages/$name" ]; then
20
+ state="installed"
21
+ else
22
+ state="store missing — run 'intelligence sync'"
23
+ fi
19
24
  printf '%s %s locked:%s %s\n' \
20
25
  "$name" \
21
26
  "${ref:+ref:$ref}${ref:-${range:-*}}" \
@@ -24,5 +29,5 @@ while IFS= read -r name; do
24
29
  done < <(qmap_keys "$manifest" "packages")
25
30
 
26
31
  if [ "$count" -eq 0 ]; then
27
- echo "No packages. Add one: intelligence add @ainova-systems/core"
32
+ echo "No packages. Add one: intelligence package add @ainova-systems/core"
28
33
  fi
@@ -1,20 +1,32 @@
1
1
  #!/bin/bash
2
- # intelligence remove <@scope/name> [--no-sync]
2
+ # Internal package remove operation.
3
3
  set -euo pipefail
4
4
  source "$CLI_DIR/lib/cli-common.sh"
5
5
 
6
6
  name="${1:-}"
7
- [ -n "$name" ] || die "usage: intelligence remove <@scope/name>"
7
+ [ -n "$name" ] || die "usage: intelligence package remove <@scope/name>"
8
8
  shift || true
9
- no_sync=0
9
+ no_sync=0 force=0
10
10
  for a in "$@"; do
11
11
  case "$a" in
12
12
  --no-sync) no_sync=1 ;;
13
+ --force) force=1 ;;
13
14
  *) die "unknown option '$a'" ;;
14
15
  esac
15
16
  done
16
17
 
17
- require_v2
18
+ require_cli_project
19
+
20
+ # Removing the engine-content package guts the outputs (meta-skills, the
21
+ # authoring rule, both engine agents disappear) while everything still
22
+ # reports ok — legal, but never by accident.
23
+ if [ "$name" = "$SYNC_PKG_NAME" ] && [ "$force" -eq 0 ]; then
24
+ echo "ERROR: $SYNC_PKG_NAME is the engine's own content — removing it drops every" >&2
25
+ echo " intelligence-* meta-skill, the authoring rule and the engine agents from" >&2
26
+ echo " the outputs. The sync engine itself keeps working (it ships with the CLI)." >&2
27
+ echo " If that is what you want: intelligence package remove $SYNC_PKG_NAME --force" >&2
28
+ exit 1
29
+ fi
18
30
  assert_valid_pkg_name "$name"
19
31
  manifest="$IP_ROOT/intelligence.yaml"
20
32
  lock="$IP_ROOT/intelligence.lock"
@@ -0,0 +1,83 @@
1
+ #!/bin/bash
2
+ # Internal package search operation: what the registries offer and what this project
3
+ # already has. The catalogue view `list` deliberately is not: `list` answers
4
+ # "what does this project depend on", `search` answers "what could it".
5
+ #
6
+ # Sources, in resolution order (a name found earlier wins, exactly as `add`
7
+ # resolves it): the manifest's trusted registries, in order.
8
+ set -euo pipefail
9
+ source "$CLI_DIR/lib/cli-common.sh"
10
+
11
+ term="${1:-}"
12
+
13
+ detect_project
14
+ manifest=""
15
+ [ "$IP_MODE" = "cli" ] && manifest="$IP_ROOT/intelligence.yaml"
16
+
17
+ # State of one package name in this project: installed / declared / available.
18
+ pkg_state() {
19
+ local name="$1"
20
+ [ -n "$manifest" ] || { printf 'available'; return 0; }
21
+ local declared=0 k
22
+ while IFS= read -r k; do
23
+ [ "$k" = "$name" ] && declared=1
24
+ done < <(qmap_keys "$manifest" "packages")
25
+ if [ "$declared" -eq 0 ]; then
26
+ printf 'available'
27
+ elif [ -d "$IP_ROOT/.intelligence/packages/$name" ]; then
28
+ printf 'installed %s' "$(qmap_field "$IP_ROOT/intelligence.lock" "packages" "$name" "resolved")"
29
+ else
30
+ printf 'declared (run sync)'
31
+ fi
32
+ }
33
+
34
+ seen=" "
35
+ rows=0
36
+ # emit_index <index-file> <origin-label>
37
+ # Registries are consulted in trust order; the first to declare a name wins,
38
+ # so a name already seen is not repeated (that IS the resolution order).
39
+ emit_index() {
40
+ local index="$1" origin="$2" name desc state
41
+ [ -n "$index" ] && [ -f "$index" ] || return 0
42
+ while IFS= read -r name; do
43
+ [ -n "$name" ] || continue
44
+ case "$seen" in *" $name "*) continue ;; esac
45
+ if [ -n "$term" ]; then
46
+ desc="$(qmap_field "$index" "packages" "$name" "description")"
47
+ case "$name$desc" in
48
+ *"$term"*) ;;
49
+ *) continue ;;
50
+ esac
51
+ fi
52
+ seen="$seen$name "
53
+ desc="$(qmap_field "$index" "packages" "$name" "description")"
54
+ state="$(pkg_state "$name")"
55
+ printf '%-34s %-22s %s\n' "$name" "$state" "${desc:-$(qmap_field "$index" "packages" "$name" "url")}"
56
+ [ "$origin" = "bundled" ] || printf '%-34s %s\n' "" " via $origin"
57
+ rows=$((rows + 1))
58
+ done < <(qmap_keys "$index" "packages")
59
+ }
60
+
61
+ if [ -n "$manifest" ]; then
62
+ while IFS= read -r url; do
63
+ [ -n "$url" ] || continue
64
+ index="$(_fetch_index "${url#git+}")"
65
+ if [ -z "$index" ]; then
66
+ echo " WARN: registry unreachable or missing index.yaml: $url" >&2
67
+ continue
68
+ fi
69
+ emit_index "$index" "$url"
70
+ done < <(registries_list "$manifest")
71
+ fi
72
+ if [ "$rows" -eq 0 ]; then
73
+ if [ -n "$term" ]; then
74
+ echo "Nothing matching '$term' in this project's trusted registries."
75
+ else
76
+ echo "No trusted registries (or they offer nothing) — intelligence registry add <repo-url>."
77
+ fi
78
+ echo "Explicit installs need no registry: intelligence package add github:org/repo"
79
+ exit 0
80
+ fi
81
+
82
+ echo ""
83
+ echo "add one: intelligence package add <name> | private registry: intelligence registry add <repo-url>"