@ainova-systems/intelligence 0.11.0-rc.7 → 0.11.0-rc.9
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 +19 -16
- package/cli/commands/adapter.sh +153 -0
- package/cli/commands/init.sh +113 -22
- package/cli/commands/package.sh +30 -0
- package/cli/commands/registry.sh +4 -2
- package/cli/commands/status.sh +12 -4
- package/cli/commands/sync.sh +7 -20
- package/cli/commands/update.sh +76 -70
- package/cli/engine-package.yaml +2 -2
- package/cli/intelligence +10 -12
- package/cli/{commands/doctor.sh → internal/check.sh} +30 -23
- package/cli/{commands/migrate.sh → internal/migrate-v1.sh} +117 -30
- package/cli/{commands/add.sh → internal/package-add.sh} +11 -16
- package/cli/{commands/list.sh → internal/package-list.sh} +9 -4
- package/cli/{commands/remove.sh → internal/package-remove.sh} +3 -3
- package/cli/{commands/search.sh → internal/package-search.sh} +4 -4
- package/cli/internal/package-update.sh +103 -0
- package/cli/{commands/install.sh → internal/restore.sh} +7 -18
- package/cli/internal/target-state.sh +57 -0
- package/cli/internal/upgrade-v2.sh +133 -0
- package/cli/lib/cli-common.sh +142 -12
- package/cli/lib/lockfile.sh +1 -1
- package/cli/lib/manifest.sh +109 -0
- package/cli/lib/registry.sh +4 -4
- package/engine/ENGINE_SHA +1 -1
- package/engine/adapters/_template.sh +10 -9
- package/engine/adapters/agents.sh +11 -11
- package/engine/adapters/opencode.sh +1 -1
- package/engine/lib/common.sh +14 -22
- package/engine/lib/contract.sh +14 -14
- package/engine/sync.sh +25 -20
- package/package.json +1 -1
- package/packages/sync/agents/intelligence-architect.md +5 -3
- package/packages/sync/agents/intelligence-operator.md +10 -13
- package/packages/sync/references/adapters.md +252 -0
- package/packages/sync/references/conventions.md +385 -0
- package/packages/sync/rules/intelligence-authoring.md +6 -6
- package/packages/sync/skills/intelligence-add-agent/SKILL.md +5 -5
- package/packages/sync/skills/intelligence-add-rule/SKILL.md +3 -3
- package/packages/sync/skills/intelligence-add-skill/SKILL.md +3 -3
- package/packages/sync/skills/intelligence-extract-skill/SKILL.md +2 -2
- package/packages/sync/skills/intelligence-install-adapter/SKILL.md +29 -22
- package/packages/sync/skills/intelligence-learn-from-context/SKILL.md +3 -3
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +55 -0
- package/packages/sync/skills/intelligence-review-skills/SKILL.md +6 -6
- package/packages/sync/skills/intelligence-sync/SKILL.md +13 -9
- package/packages/sync/skills/intelligence-uninstall-adapter/SKILL.md +19 -37
- package/packages/sync/skills/intelligence-update/SKILL.md +34 -156
- package/cli/commands/upgrade.sh +0 -68
- package/packages/sync/docs/ADAPTERS.md +0 -214
- package/packages/sync/docs/CONVENTIONS.md +0 -456
package/cli/commands/update.sh
CHANGED
|
@@ -1,87 +1,93 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# intelligence update [name] [--
|
|
2
|
+
# intelligence update [@scope/name] [--preview|--apply]
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# One update surface: show the installed-CLI/project/package plan, then either
|
|
5
|
+
# stop, ask interactively, or apply without a prompt. Updating the global npm
|
|
6
|
+
# installation remains an explicit package-manager operation.
|
|
7
7
|
set -euo pipefail
|
|
8
8
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
9
9
|
|
|
10
|
-
only=""
|
|
10
|
+
only="" mode="ask" preview_seen=0 apply_seen=0
|
|
11
11
|
while [ $# -gt 0 ]; do
|
|
12
12
|
case "$1" in
|
|
13
|
-
--
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
--preview) preview_seen=1; mode="preview" ;;
|
|
14
|
+
--apply) apply_seen=1; mode="apply" ;;
|
|
15
|
+
@*) [ -z "$only" ] || die "only one package may be selected"; only="$1" ;;
|
|
16
|
+
*) die "usage: intelligence update [@scope/name] [--preview|--apply]" ;;
|
|
16
17
|
esac
|
|
17
|
-
shift
|
|
18
|
+
shift
|
|
18
19
|
done
|
|
20
|
+
[ "$preview_seen" -eq 0 ] || [ "$apply_seen" -eq 0 ] || die "choose either --preview or --apply"
|
|
19
21
|
|
|
20
22
|
require_v2
|
|
21
23
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
22
|
-
|
|
24
|
+
check_version_compat "$manifest"
|
|
25
|
+
eng="$(bundled_engine_version)"
|
|
26
|
+
stamp="$(read_schema_version "$manifest")"
|
|
27
|
+
project_change=0
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
echo " $
|
|
35
|
-
|
|
29
|
+
echo "Update plan"
|
|
30
|
+
echo ""
|
|
31
|
+
echo "CLI:"
|
|
32
|
+
if [ -n "${INTELLIGENCE_NPM_VERSION:-}" ] && [ "${IS_SKIP_NPM_CHECK:-0}" != "1" ] && command -v npm >/dev/null 2>&1; then
|
|
33
|
+
channel="latest"
|
|
34
|
+
case "$INTELLIGENCE_NPM_VERSION" in *-*) channel="next" ;; esac
|
|
35
|
+
available="$(npm view "@ainova-systems/intelligence" "dist-tags.$channel" 2>/dev/null || true)"
|
|
36
|
+
if [ -z "$available" ]; then
|
|
37
|
+
echo " installed: $INTELLIGENCE_NPM_VERSION; registry check unavailable"
|
|
38
|
+
elif [ "$available" = "$INTELLIGENCE_NPM_VERSION" ]; then
|
|
39
|
+
echo " $INTELLIGENCE_NPM_VERSION (up to date on npm $channel)"
|
|
40
|
+
else
|
|
41
|
+
echo " $INTELLIGENCE_NPM_VERSION -> $available (npm $channel)"
|
|
42
|
+
echo " run: npm install -g @ainova-systems/intelligence@$channel"
|
|
43
|
+
echo " then rerun: intelligence update --apply"
|
|
36
44
|
fi
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
url="$RES_URL"; path="$RES_PATH"
|
|
50
|
-
fi
|
|
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
|
-
current="$(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
55
|
-
cur_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
56
|
-
cur_path="$(qmap_field "$lock" "packages" "$name" "path")"
|
|
57
|
-
# Same tag is not enough: a registry may re-point a name's source while
|
|
58
|
-
# keeping the version — the move doctor warns about must be actionable
|
|
59
|
-
# here, so url/path changes refetch too.
|
|
60
|
-
if [ "$tag" = "$current" ] && [ "$url" = "$cur_url" ] && [ "$path" = "$cur_path" ]; then
|
|
61
|
-
echo " $name: ${current:-<none>} (up to date)"
|
|
62
|
-
continue
|
|
63
|
-
fi
|
|
64
|
-
rel=".intelligence/packages/$name"
|
|
65
|
-
# Fetch into staging FIRST: a failed clone must leave the current install
|
|
66
|
-
# fully wired and intact. Only after success is the old shape unwired
|
|
67
|
-
# (the new version may have dropped a section dir), the store swapped,
|
|
68
|
-
# and the new shape wired.
|
|
69
|
-
staging="$IP_ROOT/.intelligence/.staging-$$"
|
|
70
|
-
rm -rf "$staging"
|
|
71
|
-
sha="$(fetch_package "$url" "$tag" "$path" "$staging")"
|
|
72
|
-
unwire_package_sources "$manifest" "$rel"
|
|
73
|
-
rm -rf "${IP_ROOT:?}/$rel"
|
|
74
|
-
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
75
|
-
mv "$staging" "$IP_ROOT/$rel"
|
|
76
|
-
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
77
|
-
lock_upsert "$lock" "$name" "$range" "$url" "$path" "$tag" "$sha"
|
|
78
|
-
echo " $name: ${current:-<none>} -> $tag"
|
|
79
|
-
moved=$((moved + 1))
|
|
80
|
-
done < <(qmap_keys "$manifest" "packages")
|
|
45
|
+
else
|
|
46
|
+
echo " source checkout/registry check skipped; engine $eng"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Project:"
|
|
51
|
+
if project_needs_upgrade "$IP_ROOT"; then
|
|
52
|
+
echo " lifecycle alignment required (stamp ${stamp:-unstamped}, engine $eng)"
|
|
53
|
+
project_change=1
|
|
54
|
+
else
|
|
55
|
+
echo " schema/content $eng (up to date)"
|
|
56
|
+
fi
|
|
81
57
|
|
|
82
|
-
|
|
83
|
-
|
|
58
|
+
if project_has_packages "$IP_ROOT" && [ ! -f "$IP_ROOT/intelligence.lock" ]; then
|
|
59
|
+
die "manifest declares packages but intelligence.lock is absent — restore the committed lock before planning or applying updates"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
pkg_args=(--preview)
|
|
63
|
+
[ -z "$only" ] || pkg_args+=("$only")
|
|
64
|
+
pkg_plan="$(bash "$CLI_DIR/internal/package-update.sh" "${pkg_args[@]}")"
|
|
65
|
+
echo ""
|
|
66
|
+
echo "Packages:"
|
|
67
|
+
printf '%s\n' "$pkg_plan"
|
|
68
|
+
package_changes="$(printf '%s\n' "$pkg_plan" | awk '/^updates available:/ {print $3; exit}')"
|
|
69
|
+
package_changes="${package_changes:-0}"
|
|
84
70
|
|
|
85
|
-
if [ "$
|
|
86
|
-
|
|
71
|
+
if [ "$mode" = "preview" ]; then
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
if [ "$project_change" -eq 0 ] && [ "$package_changes" -eq 0 ]; then
|
|
75
|
+
echo ""
|
|
76
|
+
echo "Nothing to apply with the currently installed CLI."
|
|
77
|
+
exit 0
|
|
87
78
|
fi
|
|
79
|
+
if [ "$mode" = "ask" ]; then
|
|
80
|
+
[ -t 0 ] || die "update requires confirmation — rerun with --preview or --apply"
|
|
81
|
+
printf '\nApply this plan? [Y/n] '
|
|
82
|
+
read -r answer
|
|
83
|
+
case "$answer" in
|
|
84
|
+
""|y|Y|yes|YES) ;;
|
|
85
|
+
*) echo "update cancelled"; exit 0 ;;
|
|
86
|
+
esac
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
ensure_project_current "$IP_ROOT"
|
|
90
|
+
apply_args=(--no-sync)
|
|
91
|
+
[ -z "$only" ] || apply_args+=("$only")
|
|
92
|
+
bash "$CLI_DIR/internal/package-update.sh" "${apply_args[@]}"
|
|
93
|
+
exec bash "$CLI_DIR/commands/sync.sh"
|
package/cli/engine-package.yaml
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
# The engine-content package is the one package whose content documents the
|
|
7
7
|
# engine scripts bundled with the CLI: init writes this entry into the
|
|
8
8
|
# manifest (visible, removable), its pin always equals the engine VERSION,
|
|
9
|
-
#
|
|
10
|
-
# bundle without network.
|
|
9
|
+
# lifecycle preflight aligns it with the installed CLI, and at the matching
|
|
10
|
+
# version it materializes from the bundle without network.
|
|
11
11
|
name: "@ainova-systems/sync"
|
|
12
12
|
url: "https://github.com/ainova-systems/intelligence.git"
|
|
13
13
|
path: "packages/sync"
|
package/cli/intelligence
CHANGED
|
@@ -43,19 +43,17 @@ intelligence — build, version and distribute AI agent intelligence
|
|
|
43
43
|
|
|
44
44
|
Usage: intelligence <command> [args]
|
|
45
45
|
|
|
46
|
-
init
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
update [name]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
init [--preview|--apply] Initialize, migrate, restore or align this project
|
|
47
|
+
--targets a,b --dir name --bare --no-sync New-project options
|
|
48
|
+
--force Accept a dirty worktree during v1 conversion
|
|
49
|
+
sync [adapter] Restore locked content if needed, then render enabled adapters
|
|
50
|
+
update [@scope/name] Show an update plan; ask before applying it
|
|
51
|
+
--preview Show the plan without asking or writing
|
|
52
|
+
--apply Apply the plan without asking
|
|
53
|
+
package <command> add | remove | list | search
|
|
54
|
+
adapter <command> list | create | enable | disable | remove
|
|
55
|
+
status [--check] Project state; --check runs deep consistency checks
|
|
55
56
|
registry <list|add|remove> Manage the trusted registry list (add <repo-url>)
|
|
56
|
-
migrate [--dry-run] Convert a vendored (v1) setup to the CLI setup
|
|
57
|
-
doctor Check manifest, lockfile, store and engine consistency
|
|
58
|
-
status Project state at a glance
|
|
59
57
|
version Print CLI and engine versions
|
|
60
58
|
EOF
|
|
61
59
|
exit 0
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# Internal deep consistency check. No writes; exits 1 on problems.
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
5
5
|
|
|
@@ -22,7 +22,7 @@ case "$IP_MODE" in
|
|
|
22
22
|
legacy)
|
|
23
23
|
echo "project: vendored (v1) at $IP_ROOT"
|
|
24
24
|
ok "vendored engine $(tr -d ' \t\r\n' < "$IP_MODULE_DIR/scripts/VERSION")"
|
|
25
|
-
echo " → 'intelligence
|
|
25
|
+
echo " → 'intelligence init' converts it to the CLI setup"
|
|
26
26
|
exit 0
|
|
27
27
|
;;
|
|
28
28
|
esac
|
|
@@ -31,16 +31,16 @@ echo "project: $IP_ROOT"
|
|
|
31
31
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
32
32
|
lock="$IP_ROOT/intelligence.lock"
|
|
33
33
|
|
|
34
|
-
stamp="$(
|
|
34
|
+
stamp="$(read_schema_version "$manifest")"
|
|
35
35
|
eng="$(bundled_engine_version)"
|
|
36
36
|
if [ -z "$stamp" ]; then
|
|
37
|
-
warn "manifest has no
|
|
37
|
+
warn "manifest has no schema_version — run 'intelligence init'"
|
|
38
38
|
elif _ver_gt "$stamp" "$eng"; then
|
|
39
39
|
warn "manifest schema $stamp is newer than this CLI's engine $eng — update the CLI: npm i -g @ainova-systems/intelligence@latest"
|
|
40
40
|
elif _ver_gt "$eng" "$stamp"; then
|
|
41
|
-
warn "manifest schema $stamp behind engine $eng — run 'intelligence
|
|
41
|
+
warn "manifest schema $stamp behind engine $eng — run 'intelligence init'"
|
|
42
42
|
else
|
|
43
|
-
ok "
|
|
43
|
+
ok "schema_version $stamp matches the engine"
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
46
|
# The engine-content package must sit at the bundled engine version — the
|
|
@@ -50,32 +50,39 @@ if [ -n "$sync_locked" ]; then
|
|
|
50
50
|
if [ "${sync_locked#v}" = "$eng" ]; then
|
|
51
51
|
ok "$SYNC_PKG_NAME at $sync_locked (matches the engine)"
|
|
52
52
|
else
|
|
53
|
-
warn "$SYNC_PKG_NAME locked at $sync_locked but the engine is $eng — run 'intelligence
|
|
53
|
+
warn "$SYNC_PKG_NAME locked at $sync_locked but the engine is $eng — run 'intelligence init'"
|
|
54
54
|
fi
|
|
55
55
|
fi
|
|
56
56
|
if [ -d "$IP_ROOT/.intelligence/engine" ]; then
|
|
57
|
-
warn "stale .intelligence/engine directory (pre-package layout) — run 'intelligence
|
|
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"
|
|
66
66
|
if [ -z "$(qmap_field "$lock" "packages" "$name" "url")" ]; then
|
|
67
|
-
warn "$name is in the manifest but not in intelligence.lock — run 'intelligence
|
|
68
|
-
elif [ ! -d "$IP_ROOT/.intelligence/packages/$name" ]; then
|
|
69
|
-
warn "$name is locked but not installed — run 'intelligence install'"
|
|
67
|
+
warn "$name is in the manifest but not in intelligence.lock — run 'intelligence init'"
|
|
70
68
|
else
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
m_ver="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
70
|
+
m_ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
71
|
+
l_req="$(qmap_field "$lock" "packages" "$name" "requested")"
|
|
72
|
+
l_res="$(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
73
|
+
[ -z "$m_ver" ] || [ "$m_ver" = "$l_req" ] \
|
|
74
|
+
|| warn "$name requests '$m_ver' but the lock recorded '$l_req' — run 'intelligence update --preview'"
|
|
75
|
+
[ -z "$m_ref" ] || [ "$m_ref" = "$l_res" ] \
|
|
76
|
+
|| warn "$name pins ref '$m_ref' but the lock resolved '$l_res'"
|
|
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
|
|
81
|
+
if [ ! -d "$IP_ROOT/.intelligence/packages/$name" ]; then
|
|
82
|
+
warn "$name is locked but not installed — run 'intelligence sync'"
|
|
83
|
+
continue
|
|
78
84
|
fi
|
|
85
|
+
ok "$name @ $(qmap_field "$lock" "packages" "$name" "resolved")"
|
|
79
86
|
fi
|
|
80
87
|
done < <(qmap_keys "$manifest" "packages")
|
|
81
88
|
|
|
@@ -87,7 +94,7 @@ if [ -f "$lock" ]; then
|
|
|
87
94
|
while IFS= read -r m; do
|
|
88
95
|
[ "$m" = "$name" ] && found=1
|
|
89
96
|
done < <(qmap_keys "$manifest" "packages")
|
|
90
|
-
[ "$found" -eq 1 ] || warn "$name is locked but absent from the manifest — 'intelligence remove $name' cleans it up"
|
|
97
|
+
[ "$found" -eq 1 ] || warn "$name is locked but absent from the manifest — 'intelligence package remove $name' cleans it up"
|
|
91
98
|
done < <(qmap_keys "$lock" "packages")
|
|
92
99
|
fi
|
|
93
100
|
|
|
@@ -102,7 +109,7 @@ for section in rules agents skills; do
|
|
|
102
109
|
if [ ! -d "$IP_ROOT/$src" ]; then
|
|
103
110
|
case "$src" in
|
|
104
111
|
# Store content is restorable state: absent means un-installed.
|
|
105
|
-
.intelligence/*) warn "sources.$section '$src' missing — run 'intelligence
|
|
112
|
+
.intelligence/*) warn "sources.$section '$src' missing — run 'intelligence sync'" ;;
|
|
106
113
|
# A project-owned dir that does not exist yet is a legitimate
|
|
107
114
|
# state — a project consuming only packages authors nothing of
|
|
108
115
|
# its own — and the engine simply skips it. Declared up front so
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# Internal v1-to-v2 migration operation.
|
|
3
3
|
# to the CLI setup: root intelligence.yaml, .intelligence/ store,
|
|
4
4
|
# intelligence.lock, no vendored engine.
|
|
5
5
|
#
|
|
@@ -24,7 +24,7 @@ done
|
|
|
24
24
|
detect_project
|
|
25
25
|
case "$IP_MODE" in
|
|
26
26
|
legacy) ;;
|
|
27
|
-
v2) die "already on the
|
|
27
|
+
v2) die "already on the v2 setup ($IP_ROOT/intelligence.yaml)" ;;
|
|
28
28
|
*) die "no vendored setup found here — 'intelligence init' starts a fresh project" ;;
|
|
29
29
|
esac
|
|
30
30
|
root="$IP_ROOT"
|
|
@@ -32,15 +32,22 @@ umbrella="$IP_UMBRELLA"
|
|
|
32
32
|
module_dir="$IP_MODULE_DIR"
|
|
33
33
|
config="$umbrella/config.yaml"
|
|
34
34
|
module_rel="${module_dir#"$root"/}"
|
|
35
|
+
umbrella_rel="${umbrella#"$root"/}"
|
|
35
36
|
|
|
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 v2 state; move it aside or restore the project before migrating"
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
37
42
|
if [ "$dry_run" -eq 0 ] && [ "$force" -eq 0 ]; then
|
|
38
43
|
if [ -n "$(git -C "$root" status --porcelain 2>/dev/null)" ]; then
|
|
39
44
|
die "working tree is not clean — commit or stash first (or --force); migrate wants a one-commit diff you can review and revert"
|
|
40
45
|
fi
|
|
41
46
|
fi
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
# `sync_version` belongs to the archived v1 format. The v2 contract uses
|
|
49
|
+
# `schema_version`, so conversion reads the legacy scalar explicitly.
|
|
50
|
+
stamp="$(top_scalar "$config" "sync_version")"
|
|
44
51
|
eng="$(bundled_engine_version)"
|
|
45
52
|
[ -n "$stamp" ] && _ver_gt "$stamp" "$eng" && die "project schema $stamp is newer than this CLI's engine $eng — update the CLI first"
|
|
46
53
|
|
|
@@ -50,9 +57,60 @@ get_pack_field() {
|
|
|
50
57
|
get_nested_yaml_value "$1" "packs" "$2" "$3"
|
|
51
58
|
}
|
|
52
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 v1 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 v1 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
|
+
|
|
53
108
|
# ---- Stage ----------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
56
114
|
cleanup_stage() { rm -rf "$stage"; }
|
|
57
115
|
trap cleanup_stage EXIT INT TERM
|
|
58
116
|
mkdir -p "$stage/.intelligence/packages"
|
|
@@ -67,7 +125,7 @@ if [ -z "$stamp" ] || _ver_gt "$V1_FINAL_SCHEMA" "$stamp"; then
|
|
|
67
125
|
echo "ERROR: project schema is ${stamp:-absent (pre-0.3.1)}, older than the final v1 schema $V1_FINAL_SCHEMA." >&2
|
|
68
126
|
echo " Bring it forward with its own engine first — that still works:" >&2
|
|
69
127
|
echo " bash $module_rel/scripts/update.sh --yes" >&2
|
|
70
|
-
echo " then re-run: intelligence
|
|
128
|
+
echo " then re-run: intelligence init" >&2
|
|
71
129
|
exit 1
|
|
72
130
|
fi
|
|
73
131
|
|
|
@@ -81,12 +139,21 @@ sync_pkg_sha="$(fetch_package "$SYNC_PKG_URL" "v$eng" "$SYNC_PKG_PATH" "$stage/$
|
|
|
81
139
|
pack_rows="$stage/packs.rows"
|
|
82
140
|
: > "$pack_rows"
|
|
83
141
|
seen_names=" "
|
|
142
|
+
seen_mirrors=" "
|
|
84
143
|
while IFS= read -r pack; do
|
|
85
144
|
[ -n "$pack" ] || continue
|
|
86
145
|
url="$(get_pack_field "$config" "$pack" "url")"
|
|
87
146
|
ref="$(get_pack_field "$config" "$pack" "ref")"
|
|
88
147
|
mirror="$(get_pack_field "$config" "$pack" "mirror")"
|
|
89
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
|
|
90
157
|
base="${url%.git}"; base="${base%/}"
|
|
91
158
|
repo="$(basename "$base")"
|
|
92
159
|
org="$(basename "$(dirname "$base")")"
|
|
@@ -125,8 +192,9 @@ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
|
125
192
|
echo " pack '$pack' -> $name (fetched)"
|
|
126
193
|
fi
|
|
127
194
|
if [ -z "$sha" ] || [ "$sha" = "unknown" ]; then
|
|
128
|
-
sha="$(GIT_TERMINAL_PROMPT=0 git ls-remote "$url" "$ref" 2>/dev/null | awk '{print $1; exit}')"
|
|
195
|
+
sha="$(GIT_TERMINAL_PROMPT=0 git ls-remote -- "$url" "$ref" 2>/dev/null | awk '{print $1; exit}')"
|
|
129
196
|
fi
|
|
197
|
+
[ -n "$ref" ] || ref="HEAD"
|
|
130
198
|
printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
|
|
131
199
|
"$name" "" "$url" "" "$ref" "$sha" >> "$lock_rows"
|
|
132
200
|
done < "$pack_rows"
|
|
@@ -192,19 +260,16 @@ while IFS= read -r line || [ -n "$line" ]; do
|
|
|
192
260
|
fi
|
|
193
261
|
echo "$line"
|
|
194
262
|
done < "$config" > "$manifest_stage"
|
|
195
|
-
|
|
263
|
+
stamp_schema_version "$manifest_stage" "$eng"
|
|
196
264
|
|
|
197
|
-
# packages:
|
|
265
|
+
# packages: requested pins only; resolved source details live in the lock.
|
|
198
266
|
while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
199
267
|
[ -n "$pack" ] || continue
|
|
200
|
-
qmap_set "$manifest_stage" "packages" "$name" "
|
|
201
|
-
[ -n "$ref" ] && qmap_set "$manifest_stage" "packages" "$name" "ref" "$ref"
|
|
268
|
+
qmap_set "$manifest_stage" "packages" "$name" "ref" "${ref:-HEAD}"
|
|
202
269
|
done < "$pack_rows"
|
|
203
270
|
|
|
204
271
|
# The engine's own content rides along as a package, pinned to the engine.
|
|
205
272
|
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "version" "$eng"
|
|
206
|
-
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "url" "$SYNC_PKG_URL"
|
|
207
|
-
qmap_set "$manifest_stage" "packages" "$SYNC_PKG_NAME" "path" "$SYNC_PKG_PATH"
|
|
208
273
|
printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
|
|
209
274
|
"$SYNC_PKG_NAME" "$eng" "$SYNC_PKG_URL" "$SYNC_PKG_PATH" "v$eng" "$sync_pkg_sha" >> "$lock_rows"
|
|
210
275
|
|
|
@@ -221,14 +286,24 @@ for section in rules agents skills; do
|
|
|
221
286
|
esac
|
|
222
287
|
done < <(read_yaml_list "$manifest_stage" "$section")
|
|
223
288
|
done
|
|
224
|
-
|
|
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
|
|
225
300
|
old_e="$(is_target_enabled "$config" "$adapter")"
|
|
226
301
|
new_e="$(is_target_enabled "$manifest_stage" "$adapter")"
|
|
227
302
|
[ "$old_e" = "$new_e" ] || { echo " TARGET DRIFT: $adapter enabled '$old_e' -> '$new_e'"; fail=1; }
|
|
228
303
|
old_o="$(get_target_output "$config" "$adapter")"
|
|
229
304
|
new_o="$(get_target_output "$manifest_stage" "$adapter")"
|
|
230
305
|
[ "$old_o" = "$new_o" ] || { echo " TARGET DRIFT: $adapter output '$old_o' -> '$new_o'"; fail=1; }
|
|
231
|
-
done
|
|
306
|
+
done < <({ target_keys "$config"; target_keys "$manifest_stage"; } | sort -u)
|
|
232
307
|
[ "$fail" -eq 0 ] || die "staged state failed verification — nothing changed"
|
|
233
308
|
|
|
234
309
|
if [ "$dry_run" -eq 1 ]; then
|
|
@@ -250,40 +325,51 @@ if [ -f "$root/.gitignore" ]; then
|
|
|
250
325
|
gitignore_existed=1
|
|
251
326
|
cp "$root/.gitignore" "$stage/gitignore.orig"
|
|
252
327
|
fi
|
|
253
|
-
|
|
254
|
-
{
|
|
255
|
-
[ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
|
|
256
|
-
echo "# intelligence CLI package store (restored by 'intelligence install')"
|
|
257
|
-
echo ".intelligence/"
|
|
258
|
-
} >> "$root/.gitignore"
|
|
259
|
-
fi
|
|
328
|
+
commit_active=1
|
|
260
329
|
rollback() {
|
|
330
|
+
[ "$commit_active" -eq 1 ] || return 0
|
|
331
|
+
commit_active=0
|
|
332
|
+
set +e
|
|
261
333
|
rm -rf "$root/.intelligence" "$root/intelligence.yaml" "$root/intelligence.lock"
|
|
262
334
|
if [ "$gitignore_existed" -eq 1 ]; then
|
|
263
335
|
cp "$stage/gitignore.orig" "$root/.gitignore"
|
|
264
336
|
else
|
|
265
337
|
rm -f "$root/.gitignore"
|
|
266
338
|
fi
|
|
339
|
+
rm -rf "$stage"
|
|
267
340
|
echo "rolled back — the vendored setup is untouched" >&2
|
|
268
341
|
}
|
|
269
|
-
|
|
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
|
|
270
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
|
|
271
358
|
mv "$stage/.intelligence" "$root/.intelligence"
|
|
272
359
|
cp "$manifest_stage" "$root/intelligence.yaml"
|
|
273
360
|
lock_write_from_tsv "$root/intelligence.lock" "$lock_rows"
|
|
274
361
|
|
|
275
362
|
sync_out="$(cd "$root" && IS_SUPPRESS_CLI_NOTE=1 bash "$CLI_DIR/commands/sync.sh" 2>&1)" || {
|
|
276
363
|
echo "$sync_out"
|
|
277
|
-
|
|
278
|
-
die "sync of the migrated state failed — rolled back"
|
|
364
|
+
die "sync of the migrated state failed"
|
|
279
365
|
}
|
|
280
366
|
echo "$sync_out" | grep -q '^IS_STATUS=ok' || {
|
|
281
367
|
echo "$sync_out"
|
|
282
|
-
|
|
283
|
-
die "sync of the migrated state did not report ok — rolled back"
|
|
368
|
+
die "sync of the migrated state did not report ok"
|
|
284
369
|
}
|
|
285
370
|
# Point of no return: the new state is verified. From here an interrupt must
|
|
286
371
|
# NOT roll back (the old setup is being removed) — only clean the stage.
|
|
372
|
+
commit_active=0
|
|
287
373
|
trap cleanup_stage EXIT INT TERM
|
|
288
374
|
|
|
289
375
|
# Only now is anything of the old setup removed — and config.yaml is kept as
|
|
@@ -298,7 +384,7 @@ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
|
298
384
|
esac
|
|
299
385
|
if [ -d "$root/$mirror" ]; then
|
|
300
386
|
rm -rf "${root:?}/$mirror"
|
|
301
|
-
# An emptied mirror parent (
|
|
387
|
+
# An emptied v1 mirror parent (for example intelligence/external/) goes with it.
|
|
302
388
|
rmdir "$(dirname "$root/$mirror")" 2>/dev/null || true
|
|
303
389
|
fi
|
|
304
390
|
done < "$pack_rows"
|
|
@@ -310,4 +396,5 @@ rmdir "$umbrella" 2>/dev/null || true
|
|
|
310
396
|
|
|
311
397
|
echo ""
|
|
312
398
|
echo "migrated. Review the diff, then commit. The old config.yaml is kept at .intelligence/backup/config.yaml."
|
|
313
|
-
echo "From now on: intelligence sync |
|
|
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 migrated project context."
|