@ainova-systems/intelligence 0.11.0-rc.8 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/cli/commands/adapter.sh +46 -6
- package/cli/commands/init.sh +138 -33
- package/cli/commands/package.sh +2 -2
- package/cli/commands/registry.sh +3 -3
- package/cli/commands/status.sh +4 -4
- package/cli/commands/sync.sh +76 -23
- package/cli/commands/update.sh +1 -1
- package/cli/intelligence +18 -3
- package/cli/internal/{upgrade-v2.sh → align-project.sh} +33 -7
- package/cli/internal/check.sh +49 -20
- package/cli/internal/{migrate-v1.sh → convert-legacy.sh} +36 -34
- package/cli/internal/package-add.sh +9 -14
- package/cli/internal/package-list.sh +1 -1
- package/cli/internal/package-remove.sh +1 -1
- package/cli/internal/package-search.sh +1 -1
- package/cli/internal/package-update.sh +31 -25
- package/cli/internal/restore.sh +5 -16
- package/cli/internal/target-state.sh +26 -18
- package/cli/lib/adapter-lifecycle.sh +33 -0
- package/cli/lib/cli-common.sh +28 -20
- package/cli/lib/gitignore.sh +174 -0
- package/cli/lib/manifest.sh +58 -1
- package/cli/lib/onboarding.sh +63 -0
- package/engine/ENGINE_SHA +1 -1
- package/engine/adapters/_template.sh +19 -2
- package/engine/adapters/agents.sh +14 -3
- package/engine/adapters/claude.sh +15 -0
- package/engine/adapters/codex.sh +11 -1
- package/engine/adapters/copilot.sh +11 -0
- package/engine/adapters/cursor.sh +15 -0
- package/engine/adapters/opencode.sh +11 -0
- package/engine/adapters/pi.sh +18 -5
- package/engine/lib/adapter-contract.sh +100 -0
- package/engine/lib/common.sh +5 -0
- package/engine/lib/contract.sh +2 -2
- package/engine/sync.sh +87 -25
- package/package.json +1 -1
- package/packages/sync/agents/intelligence-architect.md +2 -2
- package/packages/sync/references/adapters.md +51 -7
- package/packages/sync/references/conventions.md +57 -22
- package/packages/sync/references/onboarding-migration.md +79 -0
- package/packages/sync/skills/intelligence-install-adapter/SKILL.md +12 -5
- package/packages/sync/skills/intelligence-learn-from-context/SKILL.md +23 -4
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +90 -38
- package/packages/sync/skills/intelligence-review-skills/SKILL.md +1 -1
- package/packages/sync/skills/intelligence-sync/SKILL.md +1 -1
package/cli/internal/check.sh
CHANGED
|
@@ -20,9 +20,9 @@ case "$IP_MODE" in
|
|
|
20
20
|
exit 0
|
|
21
21
|
;;
|
|
22
22
|
legacy)
|
|
23
|
-
echo "project:
|
|
23
|
+
echo "project: legacy Intelligence Sync at $IP_ROOT"
|
|
24
24
|
ok "vendored engine $(tr -d ' \t\r\n' < "$IP_MODULE_DIR/scripts/VERSION")"
|
|
25
|
-
echo " → 'intelligence init' converts it
|
|
25
|
+
echo " → 'intelligence init' converts it into an Intelligence project"
|
|
26
26
|
exit 0
|
|
27
27
|
;;
|
|
28
28
|
esac
|
|
@@ -57,9 +57,9 @@ if [ -d "$IP_ROOT/.intelligence/engine" ]; then
|
|
|
57
57
|
warn "stale .intelligence/engine directory (pre-package layout) — run 'intelligence init'"
|
|
58
58
|
fi
|
|
59
59
|
|
|
60
|
-
# Manifest packages: names valid,
|
|
61
|
-
#
|
|
62
|
-
#
|
|
60
|
+
# Manifest packages: names valid, requested intent only, locked and installed.
|
|
61
|
+
# The lock is authoritative for resolved source URL/path until an explicit
|
|
62
|
+
# package re-add changes that trust decision.
|
|
63
63
|
while IFS= read -r name; do
|
|
64
64
|
[ -n "$name" ] || continue
|
|
65
65
|
assert_valid_pkg_name "$name"
|
|
@@ -68,35 +68,64 @@ while IFS= read -r name; do
|
|
|
68
68
|
else
|
|
69
69
|
m_ver="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
70
70
|
m_ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
71
|
-
m_url="$(qmap_field "$manifest" "packages" "$name" "url")"
|
|
72
|
-
m_path="$(qmap_field "$manifest" "packages" "$name" "path")"
|
|
73
71
|
l_req="$(qmap_field "$lock" "packages" "$name" "requested")"
|
|
74
72
|
l_res="$(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
75
|
-
l_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
76
|
-
l_path="$(qmap_field "$lock" "packages" "$name" "path")"
|
|
77
73
|
[ -z "$m_ver" ] || [ "$m_ver" = "$l_req" ] \
|
|
78
74
|
|| warn "$name requests '$m_ver' but the lock recorded '$l_req' — run 'intelligence update --preview'"
|
|
79
75
|
[ -z "$m_ref" ] || [ "$m_ref" = "$l_res" ] \
|
|
80
76
|
|| warn "$name pins ref '$m_ref' but the lock resolved '$l_res'"
|
|
81
|
-
[ -
|
|
82
|
-
||
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
if [ -n "$(qmap_field "$manifest" "packages" "$name" "url")" ] \
|
|
78
|
+
|| [ -n "$(qmap_field "$manifest" "packages" "$name" "path")" ]; then
|
|
79
|
+
warn "$name stores source details in the manifest — run 'intelligence init --apply' to move them to the lock-only model"
|
|
80
|
+
fi
|
|
85
81
|
if [ ! -d "$IP_ROOT/.intelligence/packages/$name" ]; then
|
|
86
82
|
warn "$name is locked but not installed — run 'intelligence sync'"
|
|
87
83
|
continue
|
|
88
84
|
fi
|
|
89
85
|
ok "$name @ $(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
90
|
-
if [ -z "$(qmap_field "$manifest" "packages" "$name" "url")" ]; then
|
|
91
|
-
locked_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
92
|
-
resolve_package_source "$manifest" "$name" 2>/dev/null || true
|
|
93
|
-
if [ -n "${RES_URL:-}" ] && [ -n "$locked_url" ] && [ "$RES_URL" != "$locked_url" ]; then
|
|
94
|
-
warn "$name now resolves to $RES_URL but the lock pins $locked_url — a registry re-pointed the name; verify with 'intelligence update --preview'"
|
|
95
|
-
fi
|
|
96
|
-
fi
|
|
97
86
|
fi
|
|
98
87
|
done < <(qmap_keys "$manifest" "packages")
|
|
99
88
|
|
|
89
|
+
# Adapter contracts are the shared source of truth for dependencies, rollback,
|
|
90
|
+
# onboarding backup and Git policy. Validate every declared target, including
|
|
91
|
+
# disabled ones, so a later enable cannot reveal a stale custom adapter.
|
|
92
|
+
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
93
|
+
while IFS= read -r target; do
|
|
94
|
+
[ -n "$target" ] || continue
|
|
95
|
+
output="$(get_target_output "$manifest" "$target")"
|
|
96
|
+
[ -n "$output" ] || output="$(default_target_output "$target")"
|
|
97
|
+
adapter_file="$(adapter_file_for "$IP_ROOT" "$content_dir" "$target" 2>/dev/null || true)"
|
|
98
|
+
if [ -z "$adapter_file" ]; then
|
|
99
|
+
warn "adapter '$target' is declared but its implementation is missing"
|
|
100
|
+
continue
|
|
101
|
+
fi
|
|
102
|
+
records="$(adapter_contract_records "$target" "$adapter_file" "$output" 2>/dev/null || true)"
|
|
103
|
+
if ! printf '%s\n' "$records" | grep -Fqx $'version\t1'; then
|
|
104
|
+
warn "adapter '$target' has an invalid ownership contract"
|
|
105
|
+
continue
|
|
106
|
+
fi
|
|
107
|
+
while IFS=$'\t' read -r kind value; do
|
|
108
|
+
if [ "$kind" = "requires" ] \
|
|
109
|
+
&& [ "$(is_target_enabled "$manifest" "$target")" = "1" ] \
|
|
110
|
+
&& [ "$(is_target_enabled "$manifest" "$value")" != "1" ]; then
|
|
111
|
+
warn "enabled adapter '$target' requires enabled adapter '$value'"
|
|
112
|
+
fi
|
|
113
|
+
if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
|
|
114
|
+
case "$kind" in
|
|
115
|
+
ignore)
|
|
116
|
+
grep -Fqx -- "$value" "$IP_ROOT/.gitignore" 2>/dev/null \
|
|
117
|
+
|| warn "adapter '$target' Git policy is missing '$value'"
|
|
118
|
+
;;
|
|
119
|
+
include)
|
|
120
|
+
grep -Fqx -- "!$value" "$IP_ROOT/.gitignore" 2>/dev/null \
|
|
121
|
+
|| warn "adapter '$target' Git policy is missing '!$value'"
|
|
122
|
+
;;
|
|
123
|
+
esac
|
|
124
|
+
fi
|
|
125
|
+
done <<< "$records"
|
|
126
|
+
ok "adapter $target contract v1"
|
|
127
|
+
done < <(target_names "$manifest")
|
|
128
|
+
|
|
100
129
|
# Lock orphans.
|
|
101
130
|
if [ -f "$lock" ]; then
|
|
102
131
|
while IFS= read -r name; do
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Internal
|
|
3
|
-
# to
|
|
2
|
+
# Internal conversion from legacy Intelligence Sync to Intelligence CLI.
|
|
3
|
+
# to an Intelligence project: root intelligence.yaml, .intelligence/ store,
|
|
4
4
|
# intelligence.lock, no vendored engine.
|
|
5
5
|
#
|
|
6
6
|
# Transactional: STAGE everything next to the
|
|
@@ -24,8 +24,8 @@ done
|
|
|
24
24
|
detect_project
|
|
25
25
|
case "$IP_MODE" in
|
|
26
26
|
legacy) ;;
|
|
27
|
-
|
|
28
|
-
*) die "no
|
|
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
29
|
esac
|
|
30
30
|
root="$IP_ROOT"
|
|
31
31
|
umbrella="$IP_UMBRELLA"
|
|
@@ -36,22 +36,22 @@ umbrella_rel="${umbrella#"$root"/}"
|
|
|
36
36
|
|
|
37
37
|
for existing in intelligence.yaml intelligence.lock .intelligence; do
|
|
38
38
|
if [ -e "$root/$existing" ] || [ -L "$root/$existing" ]; then
|
|
39
|
-
die "$existing already exists at the root — conflicting
|
|
39
|
+
die "$existing already exists at the root — conflicting Intelligence CLI state; move it aside or restore the project before converting"
|
|
40
40
|
fi
|
|
41
41
|
done
|
|
42
42
|
if [ "$dry_run" -eq 0 ] && [ "$force" -eq 0 ]; then
|
|
43
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);
|
|
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
45
|
fi
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
|
-
# `sync_version` belongs to the
|
|
48
|
+
# `sync_version` belongs to the legacy Intelligence Sync format. Intelligence uses
|
|
49
49
|
# `schema_version`, so conversion reads the legacy scalar explicitly.
|
|
50
50
|
stamp="$(top_scalar "$config" "sync_version")"
|
|
51
51
|
eng="$(bundled_engine_version)"
|
|
52
52
|
[ -n "$stamp" ] && _ver_gt "$stamp" "$eng" && die "project schema $stamp is newer than this CLI's engine $eng — update the CLI first"
|
|
53
53
|
|
|
54
|
-
# The
|
|
54
|
+
# The legacy `packs:` block is the one Intelligence Sync shape conversion still reads, and the
|
|
55
55
|
# engine no longer knows it — the reader lives here, with its only consumer.
|
|
56
56
|
get_pack_field() {
|
|
57
57
|
get_nested_yaml_value "$1" "packs" "$2" "$3"
|
|
@@ -73,10 +73,10 @@ validate_mirror_path() {
|
|
|
73
73
|
case "$canon" in "$root"/*) ;; *) die "pack '$pack' mirror escapes the repository: '$mirror'" ;; esac
|
|
74
74
|
rel="${canon#"$root"/}"
|
|
75
75
|
|
|
76
|
-
# A
|
|
76
|
+
# A legacy mirror may live below the content directory (the normal location is
|
|
77
77
|
# intelligence/external/<pack>), but it must never own the directory itself,
|
|
78
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
|
|
79
|
+
case "$umbrella_rel" in "$rel"|"$rel"/*) die "pack '$pack' mirror '$mirror' contains the legacy content directory" ;; esac
|
|
80
80
|
for protected in .git .intelligence "$module_rel"; do
|
|
81
81
|
paths_overlap "$rel" "$protected" && die "pack '$pack' mirror '$mirror' overlaps protected path '$protected'"
|
|
82
82
|
done
|
|
@@ -115,14 +115,14 @@ cleanup_stage() { rm -rf "$stage"; }
|
|
|
115
115
|
trap cleanup_stage EXIT INT TERM
|
|
116
116
|
mkdir -p "$stage/.intelligence/packages"
|
|
117
117
|
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
# project already at the final
|
|
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
121
|
# a remedy that keeps working indefinitely, because the archive stays at the
|
|
122
122
|
# URL the project's own update.sh already clones.
|
|
123
|
-
|
|
124
|
-
if [ -z "$stamp" ] || _ver_gt "$
|
|
125
|
-
echo "ERROR: project schema is ${stamp:-absent (pre-0.3.1)}, older than the final
|
|
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
126
|
echo " Bring it forward with its own engine first — that still works:" >&2
|
|
127
127
|
echo " bash $module_rel/scripts/update.sh --yes" >&2
|
|
128
128
|
echo " then re-run: intelligence init" >&2
|
|
@@ -134,7 +134,7 @@ echo "== staging =="
|
|
|
134
134
|
# bundle (fetch_package's guard — no network).
|
|
135
135
|
sync_pkg_sha="$(fetch_package "$SYNC_PKG_URL" "v$eng" "$SYNC_PKG_PATH" "$stage/$SYNC_PKG_STORE")"
|
|
136
136
|
|
|
137
|
-
# Legacy pack name ->
|
|
137
|
+
# Legacy pack name -> Intelligence package name (@org/repo derived from the url), plus
|
|
138
138
|
# per-pack source facts. Recorded as rows for the config rewrite and the lock.
|
|
139
139
|
pack_rows="$stage/packs.rows"
|
|
140
140
|
: > "$pack_rows"
|
|
@@ -194,6 +194,7 @@ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
|
194
194
|
if [ -z "$sha" ] || [ "$sha" = "unknown" ]; then
|
|
195
195
|
sha="$(GIT_TERMINAL_PROMPT=0 git ls-remote -- "$url" "$ref" 2>/dev/null | awk '{print $1; exit}')"
|
|
196
196
|
fi
|
|
197
|
+
[ -n "$ref" ] || ref="HEAD"
|
|
197
198
|
printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
|
|
198
199
|
"$name" "" "$url" "" "$ref" "$sha" >> "$lock_rows"
|
|
199
200
|
done < "$pack_rows"
|
|
@@ -261,17 +262,14 @@ while IFS= read -r line || [ -n "$line" ]; do
|
|
|
261
262
|
done < "$config" > "$manifest_stage"
|
|
262
263
|
stamp_schema_version "$manifest_stage" "$eng"
|
|
263
264
|
|
|
264
|
-
# packages:
|
|
265
|
+
# packages: requested pins only; resolved source details live in the lock.
|
|
265
266
|
while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
266
267
|
[ -n "$pack" ] || continue
|
|
267
|
-
qmap_set "$manifest_stage" "packages" "$name" "
|
|
268
|
-
[ -n "$ref" ] && qmap_set "$manifest_stage" "packages" "$name" "ref" "$ref"
|
|
268
|
+
qmap_set "$manifest_stage" "packages" "$name" "ref" "${ref:-HEAD}"
|
|
269
269
|
done < "$pack_rows"
|
|
270
270
|
|
|
271
271
|
# The engine's own content rides along as a package, pinned to the engine.
|
|
272
272
|
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "version" "$eng"
|
|
273
|
-
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "url" "$SYNC_PKG_URL"
|
|
274
|
-
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "path" "$SYNC_PKG_PATH"
|
|
275
273
|
printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
|
|
276
274
|
"$SYNC_PKG_NAME" "$eng" "$SYNC_PKG_URL" "$SYNC_PKG_PATH" "v$eng" "$sync_pkg_sha" >> "$lock_rows"
|
|
277
275
|
|
|
@@ -327,6 +325,10 @@ if [ -f "$root/.gitignore" ]; then
|
|
|
327
325
|
gitignore_existed=1
|
|
328
326
|
cp "$root/.gitignore" "$stage/gitignore.orig"
|
|
329
327
|
fi
|
|
328
|
+
while IFS= read -r policy_file; do
|
|
329
|
+
[ -f "$root/$policy_file" ] || continue
|
|
330
|
+
cp "$root/$policy_file" "$stage/$policy_file.orig"
|
|
331
|
+
done < <(publisher_ignore_file_names)
|
|
330
332
|
commit_active=1
|
|
331
333
|
rollback() {
|
|
332
334
|
[ "$commit_active" -eq 1 ] || return 0
|
|
@@ -338,6 +340,10 @@ rollback() {
|
|
|
338
340
|
else
|
|
339
341
|
rm -f "$root/.gitignore"
|
|
340
342
|
fi
|
|
343
|
+
while IFS= read -r policy_file; do
|
|
344
|
+
[ -f "$stage/$policy_file.orig" ] || continue
|
|
345
|
+
cp "$stage/$policy_file.orig" "$root/$policy_file"
|
|
346
|
+
done < <(publisher_ignore_file_names)
|
|
341
347
|
rm -rf "$stage"
|
|
342
348
|
echo "rolled back — the vendored setup is untouched" >&2
|
|
343
349
|
}
|
|
@@ -350,24 +356,19 @@ rollback_on_exit() {
|
|
|
350
356
|
# plain set -e/EXIT failure rather than only an explicit sync refusal.
|
|
351
357
|
trap rollback_on_exit EXIT
|
|
352
358
|
trap 'rollback; exit 130' INT TERM
|
|
353
|
-
if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
|
|
354
|
-
{
|
|
355
|
-
[ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
|
|
356
|
-
echo "# intelligence CLI package store (restored automatically by 'intelligence sync')"
|
|
357
|
-
echo ".intelligence/"
|
|
358
|
-
} >> "$root/.gitignore"
|
|
359
|
-
fi
|
|
360
359
|
mv "$stage/.intelligence" "$root/.intelligence"
|
|
361
360
|
cp "$manifest_stage" "$root/intelligence.yaml"
|
|
362
361
|
lock_write_from_tsv "$root/intelligence.lock" "$lock_rows"
|
|
362
|
+
ensure_manifest_gitignore "$root" "$root/intelligence.yaml"
|
|
363
|
+
ensure_manifest_publisher_ignores "$root" "$root/intelligence.yaml"
|
|
363
364
|
|
|
364
365
|
sync_out="$(cd "$root" && IS_SUPPRESS_CLI_NOTE=1 bash "$CLI_DIR/commands/sync.sh" 2>&1)" || {
|
|
365
366
|
echo "$sync_out"
|
|
366
|
-
die "sync of the
|
|
367
|
+
die "sync of the converted state failed"
|
|
367
368
|
}
|
|
368
369
|
echo "$sync_out" | grep -q '^IS_STATUS=ok' || {
|
|
369
370
|
echo "$sync_out"
|
|
370
|
-
die "sync of the
|
|
371
|
+
die "sync of the converted state did not report ok"
|
|
371
372
|
}
|
|
372
373
|
# Point of no return: the new state is verified. From here an interrupt must
|
|
373
374
|
# NOT roll back (the old setup is being removed) — only clean the stage.
|
|
@@ -386,7 +387,7 @@ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
|
386
387
|
esac
|
|
387
388
|
if [ -d "$root/$mirror" ]; then
|
|
388
389
|
rm -rf "${root:?}/$mirror"
|
|
389
|
-
# An emptied
|
|
390
|
+
# An emptied legacy mirror parent (for example intelligence/external/) goes with it.
|
|
390
391
|
rmdir "$(dirname "$root/$mirror")" 2>/dev/null || true
|
|
391
392
|
fi
|
|
392
393
|
done < "$pack_rows"
|
|
@@ -397,6 +398,7 @@ rm -f "$config"
|
|
|
397
398
|
rmdir "$umbrella" 2>/dev/null || true
|
|
398
399
|
|
|
399
400
|
echo ""
|
|
400
|
-
echo "
|
|
401
|
+
echo "converted. Review the diff, then commit. The old config.yaml is kept at .intelligence/backup/config.yaml."
|
|
401
402
|
echo "From now on: intelligence sync | package | update | status."
|
|
402
|
-
|
|
403
|
+
report_tracked_managed_ignores "$root" "$root/intelligence.yaml"
|
|
404
|
+
echo "Next: ask your agent to run /intelligence-learn-from-repository to verify setup and review the converted project context."
|
|
@@ -23,7 +23,7 @@ while [ $# -gt 0 ]; do
|
|
|
23
23
|
shift || true
|
|
24
24
|
done
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
require_cli_project
|
|
27
27
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
28
28
|
lock="$IP_ROOT/intelligence.lock"
|
|
29
29
|
|
|
@@ -117,6 +117,7 @@ if [ -z "$ref" ]; then
|
|
|
117
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"
|
|
118
118
|
fi
|
|
119
119
|
echo " NOTE: no version tags at $url — pinning the default branch head" >&2
|
|
120
|
+
ref="HEAD"
|
|
120
121
|
fi
|
|
121
122
|
fi
|
|
122
123
|
|
|
@@ -125,22 +126,16 @@ sha="$(fetch_package "$url" "${ref:-$resolved_tag}" "$path" "$IP_ROOT/$rel")"
|
|
|
125
126
|
|
|
126
127
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
127
128
|
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
# fields behind (a stale ref: would freeze update, a stale url: would pin the
|
|
132
|
-
# old source).
|
|
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.
|
|
133
132
|
qmap_delete_key "$manifest" "packages" "$name"
|
|
134
|
-
if [ "$
|
|
133
|
+
if [ -n "$ref" ]; then
|
|
134
|
+
qmap_set "$manifest" "packages" "$name" "ref" "$ref"
|
|
135
|
+
elif [ -n "$requested" ]; then
|
|
135
136
|
qmap_set "$manifest" "packages" "$name" "version" "$requested"
|
|
136
137
|
else
|
|
137
|
-
|
|
138
|
-
[ -n "$path" ] && qmap_set "$manifest" "packages" "$name" "path" "$path"
|
|
139
|
-
if [ -n "$ref" ]; then
|
|
140
|
-
qmap_set "$manifest" "packages" "$name" "ref" "$ref"
|
|
141
|
-
elif [ -n "$requested" ]; then
|
|
142
|
-
qmap_set "$manifest" "packages" "$name" "version" "$requested"
|
|
143
|
-
fi
|
|
138
|
+
die "internal: package '$name' has neither a requested version nor ref"
|
|
144
139
|
fi
|
|
145
140
|
|
|
146
141
|
lock_upsert "$lock" "$name" "$requested" "$url" "$path" "${ref:-$resolved_tag}" "$sha"
|
|
@@ -12,7 +12,7 @@ term="${1:-}"
|
|
|
12
12
|
|
|
13
13
|
detect_project
|
|
14
14
|
manifest=""
|
|
15
|
-
[ "$IP_MODE" = "
|
|
15
|
+
[ "$IP_MODE" = "cli" ] && manifest="$IP_ROOT/intelligence.yaml"
|
|
16
16
|
|
|
17
17
|
# State of one package name in this project: installed / declared / available.
|
|
18
18
|
pkg_state() {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Internal package range update operation.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Resolve requested versions/refs against each package's locked source and
|
|
5
|
+
# rewrite the lock. `update <name>` limits the pass to one package; re-adding
|
|
6
|
+
# is the explicit operation for changing a source URL/path.
|
|
7
7
|
set -euo pipefail
|
|
8
8
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@ while [ $# -gt 0 ]; do
|
|
|
18
18
|
shift || true
|
|
19
19
|
done
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
require_cli_project
|
|
22
22
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
23
23
|
lock="$IP_ROOT/intelligence.lock"
|
|
24
24
|
|
|
@@ -35,30 +35,36 @@ while IFS= read -r name; do
|
|
|
35
35
|
echo " $name: engine content follows the installed CLI — skipped"
|
|
36
36
|
continue
|
|
37
37
|
fi
|
|
38
|
+
current="$(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
39
|
+
locked_requested="$(qmap_field "$lock" "packages" "$name" "requested")"
|
|
40
|
+
url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
41
|
+
path="$(qmap_field "$lock" "packages" "$name" "path")"
|
|
42
|
+
[ -n "$url" ] || die "$name has no source in intelligence.lock — restore the committed lock or re-add the package"
|
|
38
43
|
ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
39
|
-
[ -n "$ref" ] && { echo " $name: pinned to ref '$ref' — skipped"; continue; }
|
|
40
44
|
range="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
[ -n "$ref" ] || [ -n "$range" ] || die "$name has neither version nor ref in the manifest"
|
|
46
|
+
|
|
47
|
+
if [ -n "$ref" ]; then
|
|
48
|
+
tag="$ref"
|
|
49
|
+
requested=""
|
|
50
|
+
else
|
|
51
|
+
picked="$(list_remote_versions "$url" | semver_pick_highest "$range")"
|
|
52
|
+
[ -n "$picked" ] || { echo " $name: nothing satisfies '$range' at $url" >&2; continue; }
|
|
53
|
+
read -r tag _ <<< "$(remote_tag_for_version "$url" "$picked")"
|
|
54
|
+
requested="$range"
|
|
45
55
|
fi
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
if [ "$tag" = "$current" ] && [ "$requested" != "$locked_requested" ]; then
|
|
57
|
+
if [ "$preview" -eq 1 ]; then
|
|
58
|
+
echo " $name: request ${locked_requested:-<none>} -> ${requested:-<ref>} (keeps $current)"
|
|
59
|
+
else
|
|
60
|
+
locked_sha="$(qmap_field "$lock" "packages" "$name" "sha")"
|
|
61
|
+
lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$current" "$locked_sha"
|
|
62
|
+
echo " $name: request ${locked_requested:-<none>} -> ${requested:-<ref>} (kept $current)"
|
|
63
|
+
fi
|
|
64
|
+
moved=$((moved + 1))
|
|
65
|
+
continue
|
|
51
66
|
fi
|
|
52
|
-
|
|
53
|
-
[ -n "$picked" ] || { echo " $name: nothing satisfies '${range:-*}' at $url" >&2; continue; }
|
|
54
|
-
read -r tag _ <<< "$(remote_tag_for_version "$url" "$picked")"
|
|
55
|
-
current="$(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
56
|
-
cur_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
57
|
-
cur_path="$(qmap_field "$lock" "packages" "$name" "path")"
|
|
58
|
-
# Same tag is not enough: a registry may re-point a name's source while
|
|
59
|
-
# keeping the version — the move doctor warns about must be actionable
|
|
60
|
-
# here, so url/path changes refetch too.
|
|
61
|
-
if [ "$tag" = "$current" ] && [ "$url" = "$cur_url" ] && [ "$path" = "$cur_path" ]; then
|
|
67
|
+
if [ "$tag" = "$current" ]; then
|
|
62
68
|
echo " $name: ${current:-<none>} (up to date)"
|
|
63
69
|
continue
|
|
64
70
|
fi
|
|
@@ -80,7 +86,7 @@ while IFS= read -r name; do
|
|
|
80
86
|
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
81
87
|
mv "$staging" "$IP_ROOT/$rel"
|
|
82
88
|
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
83
|
-
lock_upsert "$lock" "$name" "$
|
|
89
|
+
lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$tag" "$sha"
|
|
84
90
|
echo " $name: ${current:-<none>} -> $tag"
|
|
85
91
|
moved=$((moved + 1))
|
|
86
92
|
done < <(qmap_keys "$manifest" "packages")
|
package/cli/internal/restore.sh
CHANGED
|
@@ -18,12 +18,12 @@ while [ $# -gt 0 ]; do
|
|
|
18
18
|
shift || true
|
|
19
19
|
done
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
require_cli_project
|
|
22
22
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
23
23
|
lock="$IP_ROOT/intelligence.lock"
|
|
24
24
|
|
|
25
25
|
# --frozen is the reproducibility contract: the lock must agree with the
|
|
26
|
-
# manifest on the package SET and on every requested-
|
|
26
|
+
# manifest on the package SET and on every requested-intent field before a
|
|
27
27
|
# single byte is restored. Anything less lets a manifest edit ride on a stale
|
|
28
28
|
# lock — the exact drift the flag exists to refuse.
|
|
29
29
|
if [ "$frozen" -eq 1 ]; then
|
|
@@ -33,20 +33,12 @@ if [ "$frozen" -eq 1 ]; then
|
|
|
33
33
|
|| die "locked restore: $name is in the manifest but not in intelligence.lock"
|
|
34
34
|
m_ver="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
35
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
36
|
l_req="$(qmap_field "$lock" "packages" "$name" "requested")"
|
|
39
37
|
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
38
|
[ -z "$m_ver" ] || [ "$m_ver" = "$l_req" ] \
|
|
43
39
|
|| die "locked restore: $name requests '$m_ver' but the lock recorded '$l_req' — reconcile the manifest and lock"
|
|
44
40
|
[ -z "$m_ref" ] || [ "$m_ref" = "$l_res" ] \
|
|
45
41
|
|| 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
42
|
done < <(qmap_keys "$manifest" "packages")
|
|
51
43
|
while IFS= read -r name; do
|
|
52
44
|
[ -n "$name" ] || continue
|
|
@@ -76,14 +68,11 @@ if [ -n "$missing" ]; then
|
|
|
76
68
|
sync_pkg_install "$IP_ROOT"
|
|
77
69
|
continue
|
|
78
70
|
fi
|
|
79
|
-
url="$(qmap_field "$manifest" "packages" "$name" "url")"
|
|
80
|
-
path="$(qmap_field "$manifest" "packages" "$name" "path")"
|
|
81
71
|
ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
82
72
|
range="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
fi
|
|
73
|
+
resolve_package_source "$manifest" "$name"
|
|
74
|
+
url="$RES_URL"; path="$RES_PATH"
|
|
75
|
+
[ -n "$url" ] || die "no trusted registry declares '$name' and it is absent from intelligence.lock"
|
|
87
76
|
resolved_tag=""
|
|
88
77
|
if [ -z "$ref" ]; then
|
|
89
78
|
picked="$(list_remote_versions "$url" | semver_pick_highest "$range")"
|
|
@@ -14,7 +14,7 @@ case "$action" in enable|disable) ;; *) die "internal target state: expected <en
|
|
|
14
14
|
[ -n "$name" ] || die "internal target state: missing adapter name"
|
|
15
15
|
assert_valid_target_name "$name"
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
require_cli_project
|
|
18
18
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
19
19
|
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
20
20
|
|
|
@@ -25,31 +25,39 @@ if [ "$action" = "enable" ]; then
|
|
|
25
25
|
die "adapter '$name' not found — create it first: intelligence adapter create $name"
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
|
-
# These adapters intentionally omit always-on rules because their tools
|
|
29
|
-
# read AGENTS.md. Do not create a manifest that full sync must refuse.
|
|
30
|
-
case "$name" in
|
|
31
|
-
cursor|copilot|codex|pi|opencode)
|
|
32
|
-
if [ "$(is_target_enabled "$manifest" "agents")" != "1" ]; then
|
|
33
|
-
die "target '$name' requires target 'agents' — enable it first"
|
|
34
|
-
fi
|
|
35
|
-
;;
|
|
36
|
-
esac
|
|
37
|
-
|
|
38
28
|
output="$(get_target_output "$manifest" "$name")"
|
|
39
29
|
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
30
|
+
validate_adapter_contract_for "$IP_ROOT" "$content_dir" "$name" "$output" \
|
|
31
|
+
|| die "adapter '$name' has an invalid ownership contract"
|
|
32
|
+
while IFS= read -r required; do
|
|
33
|
+
[ -n "$required" ] || continue
|
|
34
|
+
if [ "$(is_target_enabled "$manifest" "$required")" != "1" ]; then
|
|
35
|
+
die "target '$name' requires target '$required' — enable it first"
|
|
36
|
+
fi
|
|
37
|
+
done < <(adapter_records_for "$IP_ROOT" "$content_dir" "$name" "$output" \
|
|
38
|
+
| awk -F '\t' '$1 == "requires" { print $2 }')
|
|
40
39
|
target_set_enabled "$manifest" "$name" true "$output"
|
|
40
|
+
ensure_target_gitignore "$IP_ROOT" "$manifest" "$name"
|
|
41
|
+
ensure_manifest_publisher_ignores "$IP_ROOT" "$manifest"
|
|
41
42
|
echo "enabled: $name (output: $output)"
|
|
42
43
|
exit 0
|
|
43
44
|
fi
|
|
44
45
|
|
|
45
46
|
target_exists "$manifest" "$name" || die "target '$name' is not in the manifest"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
while IFS= read -r dependent; do
|
|
48
|
+
[ -n "$dependent" ] || continue
|
|
49
|
+
[ "$dependent" = "$name" ] && continue
|
|
50
|
+
[ "$(is_target_enabled "$manifest" "$dependent")" = "1" ] || continue
|
|
51
|
+
dependent_output="$(get_target_output "$manifest" "$dependent")"
|
|
52
|
+
[ -n "$dependent_output" ] || dependent_output="$(default_target_output "$dependent")"
|
|
53
|
+
dependent_records="$(adapter_records_for "$IP_ROOT" "$content_dir" "$dependent" "$dependent_output")" \
|
|
54
|
+
|| die "adapter '$dependent' has an invalid ownership contract"
|
|
55
|
+
if awk -F '\t' -v required="$name" \
|
|
56
|
+
'$1 == "requires" && $2 == required { found=1 } END { exit(found ? 0 : 1) }' \
|
|
57
|
+
<<< "$dependent_records"; then
|
|
58
|
+
die "target '$name' is required by enabled target '$dependent' — disable '$dependent' first"
|
|
59
|
+
fi
|
|
60
|
+
done < <(target_names "$manifest")
|
|
53
61
|
output="$(get_target_output "$manifest" "$name")"
|
|
54
62
|
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
55
63
|
target_set_enabled "$manifest" "$name" false "$output"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CLI consumers of the engine's declarative adapter contract.
|
|
3
|
+
|
|
4
|
+
adapter_file_for() {
|
|
5
|
+
local root="$1" content_dir="$2" name="$3"
|
|
6
|
+
if [ -f "$root/$content_dir/adapters/$name.sh" ]; then
|
|
7
|
+
printf '%s\n' "$root/$content_dir/adapters/$name.sh"
|
|
8
|
+
elif [ -f "$IS_ENGINE_DIR/adapters/$name.sh" ]; then
|
|
9
|
+
printf '%s\n' "$IS_ENGINE_DIR/adapters/$name.sh"
|
|
10
|
+
else
|
|
11
|
+
return 1
|
|
12
|
+
fi
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
adapter_records_for() {
|
|
16
|
+
local root="$1" content_dir="$2" name="$3" output="$4" file
|
|
17
|
+
file="$(adapter_file_for "$root" "$content_dir" "$name")" || {
|
|
18
|
+
echo "ERROR: adapter '$name' not found" >&2
|
|
19
|
+
return 1
|
|
20
|
+
}
|
|
21
|
+
adapter_contract_records "$name" "$file" "$output"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
validate_adapter_contract_for() {
|
|
25
|
+
local root="$1" content_dir="$2" name="$3" output="$4"
|
|
26
|
+
adapter_records_for "$root" "$content_dir" "$name" "$output" >/dev/null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
adapter_contract_paths() {
|
|
30
|
+
local root="$1" content_dir="$2" name="$3" output="$4"
|
|
31
|
+
adapter_records_for "$root" "$content_dir" "$name" "$output" \
|
|
32
|
+
| awk -F '\t' '$1 == "owned" || $1 == "managed" || $1 == "legacy" || $1 == "preserve" { print $2 }'
|
|
33
|
+
}
|