@ainova-systems/intelligence 0.11.0-rc.7 → 0.11.0-rc.8
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 +15 -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} +32 -14
- package/cli/{commands/migrate.sh → internal/migrate-v1.sh} +114 -25
- package/cli/{commands/add.sh → internal/package-add.sh} +3 -3
- 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 +97 -0
- package/cli/{commands/install.sh → internal/restore.sh} +3 -3
- package/cli/internal/target-state.sh +57 -0
- package/cli/{commands/upgrade.sh → internal/upgrade-v2.sh} +48 -8
- package/cli/lib/cli-common.sh +133 -7
- package/cli/lib/lockfile.sh +1 -1
- package/cli/lib/manifest.sh +86 -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/packages/sync/docs/ADAPTERS.md +0 -214
- package/packages/sync/docs/CONVENTIONS.md +0 -456
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
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
9
|
no_sync=0 force=0
|
|
10
10
|
for a in "$@"; do
|
|
@@ -24,7 +24,7 @@ if [ "$name" = "$SYNC_PKG_NAME" ] && [ "$force" -eq 0 ]; then
|
|
|
24
24
|
echo "ERROR: $SYNC_PKG_NAME is the engine's own content — removing it drops every" >&2
|
|
25
25
|
echo " intelligence-* meta-skill, the authoring rule and the engine agents from" >&2
|
|
26
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 remove $SYNC_PKG_NAME --force" >&2
|
|
27
|
+
echo " If that is what you want: intelligence package remove $SYNC_PKG_NAME --force" >&2
|
|
28
28
|
exit 1
|
|
29
29
|
fi
|
|
30
30
|
assert_valid_pkg_name "$name"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# Internal package search operation: what the registries offer and what this project
|
|
3
3
|
# already has. The catalogue view `list` deliberately is not: `list` answers
|
|
4
4
|
# "what does this project depend on", `search` answers "what could it".
|
|
5
5
|
#
|
|
@@ -27,7 +27,7 @@ pkg_state() {
|
|
|
27
27
|
elif [ -d "$IP_ROOT/.intelligence/packages/$name" ]; then
|
|
28
28
|
printf 'installed %s' "$(qmap_field "$IP_ROOT/intelligence.lock" "packages" "$name" "resolved")"
|
|
29
29
|
else
|
|
30
|
-
printf 'declared (run
|
|
30
|
+
printf 'declared (run sync)'
|
|
31
31
|
fi
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -75,9 +75,9 @@ if [ "$rows" -eq 0 ]; then
|
|
|
75
75
|
else
|
|
76
76
|
echo "No trusted registries (or they offer nothing) — intelligence registry add <repo-url>."
|
|
77
77
|
fi
|
|
78
|
-
echo "Explicit installs need no registry: intelligence add github:org/repo"
|
|
78
|
+
echo "Explicit installs need no registry: intelligence package add github:org/repo"
|
|
79
79
|
exit 0
|
|
80
80
|
fi
|
|
81
81
|
|
|
82
82
|
echo ""
|
|
83
|
-
echo "add one: intelligence add <name> | private registry: intelligence registry add <repo-url>"
|
|
83
|
+
echo "add one: intelligence package add <name> | private registry: intelligence registry add <repo-url>"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Internal package range update operation.
|
|
3
|
+
#
|
|
4
|
+
# Re-resolve version ranges against the remotes and rewrite the lock. Pinned
|
|
5
|
+
# packages (`ref:`) never move here — repin by editing the manifest or
|
|
6
|
+
# re-adding. `update <name>` limits the pass to one package.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
source "$CLI_DIR/lib/cli-common.sh"
|
|
9
|
+
|
|
10
|
+
only="" no_sync=0 preview=0
|
|
11
|
+
while [ $# -gt 0 ]; do
|
|
12
|
+
case "$1" in
|
|
13
|
+
--no-sync) no_sync=1 ;;
|
|
14
|
+
--preview) preview=1 ;;
|
|
15
|
+
@*) only="$1" ;;
|
|
16
|
+
*) die "unknown argument '$1'" ;;
|
|
17
|
+
esac
|
|
18
|
+
shift || true
|
|
19
|
+
done
|
|
20
|
+
|
|
21
|
+
require_v2
|
|
22
|
+
manifest="$IP_ROOT/intelligence.yaml"
|
|
23
|
+
lock="$IP_ROOT/intelligence.lock"
|
|
24
|
+
|
|
25
|
+
moved=0 found=0
|
|
26
|
+
while IFS= read -r name; do
|
|
27
|
+
[ -n "$name" ] || continue
|
|
28
|
+
# Manifest keys are untrusted input on their way into store paths.
|
|
29
|
+
assert_valid_pkg_name "$name"
|
|
30
|
+
[ -n "$only" ] && [ "$name" != "$only" ] && continue
|
|
31
|
+
# A named package that exists is "found" whatever happens next — a skip
|
|
32
|
+
# message followed by "not in the manifest" would contradict itself.
|
|
33
|
+
found=1
|
|
34
|
+
if [ "$name" = "$SYNC_PKG_NAME" ]; then
|
|
35
|
+
echo " $name: engine content follows the installed CLI — skipped"
|
|
36
|
+
continue
|
|
37
|
+
fi
|
|
38
|
+
ref="$(qmap_field "$manifest" "packages" "$name" "ref")"
|
|
39
|
+
[ -n "$ref" ] && { echo " $name: pinned to ref '$ref' — skipped"; continue; }
|
|
40
|
+
range="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
41
|
+
# An exact version is a pin, same as ref: — update never moves pins.
|
|
42
|
+
if [ -n "$range" ] && semver_is_stable "$range"; then
|
|
43
|
+
echo " $name: pinned to $range — edit the manifest to move it"
|
|
44
|
+
continue
|
|
45
|
+
fi
|
|
46
|
+
url="$(qmap_field "$manifest" "packages" "$name" "url")"
|
|
47
|
+
path="$(qmap_field "$manifest" "packages" "$name" "path")"
|
|
48
|
+
if [ -z "$url" ]; then
|
|
49
|
+
resolve_package_source "$manifest" "$name"
|
|
50
|
+
url="$RES_URL"; path="$RES_PATH"
|
|
51
|
+
fi
|
|
52
|
+
picked="$(list_remote_versions "$url" | semver_pick_highest "$range")"
|
|
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
|
|
62
|
+
echo " $name: ${current:-<none>} (up to date)"
|
|
63
|
+
continue
|
|
64
|
+
fi
|
|
65
|
+
if [ "$preview" -eq 1 ]; then
|
|
66
|
+
echo " $name: ${current:-<none>} -> $tag"
|
|
67
|
+
moved=$((moved + 1))
|
|
68
|
+
continue
|
|
69
|
+
fi
|
|
70
|
+
rel=".intelligence/packages/$name"
|
|
71
|
+
# Fetch into staging FIRST: a failed clone must leave the current install
|
|
72
|
+
# fully wired and intact. Only after success is the old shape unwired
|
|
73
|
+
# (the new version may have dropped a section dir), the store swapped,
|
|
74
|
+
# and the new shape wired.
|
|
75
|
+
staging="$IP_ROOT/.intelligence/.staging-$$"
|
|
76
|
+
rm -rf "$staging"
|
|
77
|
+
sha="$(fetch_package "$url" "$tag" "$path" "$staging")"
|
|
78
|
+
unwire_package_sources "$manifest" "$rel"
|
|
79
|
+
rm -rf "${IP_ROOT:?}/$rel"
|
|
80
|
+
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
81
|
+
mv "$staging" "$IP_ROOT/$rel"
|
|
82
|
+
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
83
|
+
lock_upsert "$lock" "$name" "$range" "$url" "$path" "$tag" "$sha"
|
|
84
|
+
echo " $name: ${current:-<none>} -> $tag"
|
|
85
|
+
moved=$((moved + 1))
|
|
86
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
87
|
+
|
|
88
|
+
[ -n "$only" ] && [ "$found" -eq 0 ] && die "package '$only' is not in the manifest"
|
|
89
|
+
if [ "$preview" -eq 1 ]; then
|
|
90
|
+
echo "updates available: $moved package(s)"
|
|
91
|
+
else
|
|
92
|
+
echo "updated: $moved package(s)"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
if [ "$preview" -eq 0 ] && [ "$moved" -gt 0 ] && [ "$no_sync" -eq 0 ]; then
|
|
96
|
+
exec bash "$CLI_DIR/commands/sync.sh"
|
|
97
|
+
fi
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# Internal locked-store restore operation.
|
|
3
3
|
#
|
|
4
4
|
# Restore .intelligence/ from intelligence.lock — the fresh-clone command.
|
|
5
5
|
# --frozen additionally refuses any manifest/lock divergence (CI mode) and
|
|
@@ -30,7 +30,7 @@ if [ "$frozen" -eq 1 ]; then
|
|
|
30
30
|
while IFS= read -r name; do
|
|
31
31
|
[ -n "$name" ] || continue
|
|
32
32
|
[ -n "$(qmap_field "$lock" "packages" "$name" "url")" ] \
|
|
33
|
-
|| die "
|
|
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
36
|
m_url="$(qmap_field "$manifest" "packages" "$name" "url")"
|
|
@@ -40,7 +40,7 @@ if [ "$frozen" -eq 1 ]; then
|
|
|
40
40
|
l_url="$(qmap_field "$lock" "packages" "$name" "url")"
|
|
41
41
|
l_path="$(qmap_field "$lock" "packages" "$name" "path")"
|
|
42
42
|
[ -z "$m_ver" ] || [ "$m_ver" = "$l_req" ] \
|
|
43
|
-
|| die "
|
|
43
|
+
|| die "locked restore: $name requests '$m_ver' but the lock recorded '$l_req' — reconcile the manifest and lock"
|
|
44
44
|
[ -z "$m_ref" ] || [ "$m_ref" = "$l_res" ] \
|
|
45
45
|
|| die "--frozen: $name pins ref '$m_ref' but the lock resolved '$l_res'"
|
|
46
46
|
[ -z "$m_url" ] || [ "$m_url" = "$l_url" ] \
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Internal adapter target-state operation.
|
|
3
|
+
#
|
|
4
|
+
# Change only the manifest. Syncing and deleting generated output are separate
|
|
5
|
+
# explicit actions: a generic target command cannot infer a custom adapter's
|
|
6
|
+
# ownership safely.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
source "$CLI_DIR/lib/cli-common.sh"
|
|
9
|
+
|
|
10
|
+
action="${1:-}"
|
|
11
|
+
name="${2:-}"
|
|
12
|
+
[ $# -le 2 ] || die "internal target state: expected <enable|disable> <name>"
|
|
13
|
+
case "$action" in enable|disable) ;; *) die "internal target state: expected <enable|disable> <name>" ;; esac
|
|
14
|
+
[ -n "$name" ] || die "internal target state: missing adapter name"
|
|
15
|
+
assert_valid_target_name "$name"
|
|
16
|
+
|
|
17
|
+
require_v2
|
|
18
|
+
manifest="$IP_ROOT/intelligence.yaml"
|
|
19
|
+
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
20
|
+
|
|
21
|
+
if [ "$action" = "enable" ]; then
|
|
22
|
+
project_adapter="$IP_ROOT/$content_dir/adapters/$name.sh"
|
|
23
|
+
bundled_adapter="$IS_ENGINE_DIR/adapters/$name.sh"
|
|
24
|
+
if [ ! -f "$project_adapter" ] && [ ! -f "$bundled_adapter" ]; then
|
|
25
|
+
die "adapter '$name' not found — create it first: intelligence adapter create $name"
|
|
26
|
+
fi
|
|
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
|
+
output="$(get_target_output "$manifest" "$name")"
|
|
39
|
+
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
40
|
+
target_set_enabled "$manifest" "$name" true "$output"
|
|
41
|
+
echo "enabled: $name (output: $output)"
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
target_exists "$manifest" "$name" || die "target '$name' is not in the manifest"
|
|
46
|
+
if [ "$name" = "agents" ]; then
|
|
47
|
+
for dependent in cursor copilot codex pi opencode; do
|
|
48
|
+
if [ "$(is_target_enabled "$manifest" "$dependent")" = "1" ]; then
|
|
49
|
+
die "target 'agents' is required by enabled target '$dependent' — disable '$dependent' first"
|
|
50
|
+
fi
|
|
51
|
+
done
|
|
52
|
+
fi
|
|
53
|
+
output="$(get_target_output "$manifest" "$name")"
|
|
54
|
+
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
55
|
+
target_set_enabled "$manifest" "$name" false "$output"
|
|
56
|
+
echo "disabled: $name"
|
|
57
|
+
echo " generated output was kept; remove only paths owned by this adapter"
|
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# Internal: bring the project to this CLI's engine: apply v2
|
|
3
3
|
# schema migrations, align the engine-content package to the bundled version,
|
|
4
|
-
# restamp
|
|
4
|
+
# restamp schema_version, sync.
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
7
7
|
|
|
8
|
+
no_sync=0
|
|
9
|
+
while [ $# -gt 0 ]; do
|
|
10
|
+
case "$1" in
|
|
11
|
+
--no-sync) no_sync=1 ;;
|
|
12
|
+
*) die "internal upgrade: unknown option '$1'" ;;
|
|
13
|
+
esac
|
|
14
|
+
shift
|
|
15
|
+
done
|
|
16
|
+
|
|
8
17
|
require_v2
|
|
9
18
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
10
19
|
|
|
11
|
-
stamp="$(
|
|
20
|
+
stamp="$(read_schema_version "$manifest")"
|
|
21
|
+
legacy_stamp="$(top_scalar "$manifest" "sync_version")"
|
|
22
|
+
schema_key_count="$(awk '/^schema_version:[[:space:]]*/ { n++ } END { print n + 0 }' "$manifest")"
|
|
23
|
+
legacy_key_count="$(awk '/^sync_version:[[:space:]]*/ { n++ } END { print n + 0 }' "$manifest")"
|
|
12
24
|
eng="$(bundled_engine_version)"
|
|
25
|
+
[ "$schema_key_count" -le 1 ] || die "manifest has duplicate schema_version keys"
|
|
26
|
+
[ "$legacy_key_count" -le 1 ] || die "manifest has duplicate sync_version keys"
|
|
13
27
|
if [ -n "$stamp" ] && _ver_gt "$stamp" "$eng"; then
|
|
14
|
-
die "manifest schema $stamp is newer than this CLI's engine $eng — update the CLI first
|
|
28
|
+
die "manifest schema $stamp is newer than this CLI's engine $eng — update the global CLI first"
|
|
29
|
+
fi
|
|
30
|
+
if [ -n "$legacy_stamp" ] && _ver_gt "$legacy_stamp" "$eng"; then
|
|
31
|
+
die "manifest schema $legacy_stamp is newer than this CLI's engine $eng — update the global CLI first"
|
|
15
32
|
fi
|
|
16
33
|
|
|
17
34
|
# --- v2 schema migrations -------------------------------------------------
|
|
@@ -19,6 +36,19 @@ fi
|
|
|
19
36
|
# Same discipline: ascending, append-only, each migration self-detects and
|
|
20
37
|
# no-ops when already applied.
|
|
21
38
|
|
|
39
|
+
# v2 migration 0: the original RC manifest called the permanent schema key
|
|
40
|
+
# `sync_version`. Rename it without changing its value; the final stamp below
|
|
41
|
+
# then aligns it with this engine. The archived v1 reader remains separate.
|
|
42
|
+
if [ -n "$legacy_stamp" ]; then
|
|
43
|
+
if [ -n "$stamp" ]; then
|
|
44
|
+
[ "$stamp" = "$legacy_stamp" ] || die "manifest has conflicting schema_version '$stamp' and sync_version '$legacy_stamp'"
|
|
45
|
+
_qmap_stage "$manifest" '$0 !~ /^sync_version:[[:space:]]*/'
|
|
46
|
+
else
|
|
47
|
+
_qmap_stage "$manifest" '{ sub(/^sync_version:/, "schema_version:"); print }'
|
|
48
|
+
fi
|
|
49
|
+
echo " migrating: sync_version -> schema_version"
|
|
50
|
+
fi
|
|
51
|
+
|
|
22
52
|
# v2 migration 1: staged engine content -> the @ainova-systems/sync package.
|
|
23
53
|
# Pre-package manifests listed `.intelligence/engine/{rules,agents,skills}`
|
|
24
54
|
# as sources fed by a CLI-staged copy; that copy is now an ordinary package
|
|
@@ -59,10 +89,20 @@ if [ "$have_pkg" -eq 1 ] && [ "$migrated" -eq 0 ]; then
|
|
|
59
89
|
sync_pkg_install "$IP_ROOT"
|
|
60
90
|
fi
|
|
61
91
|
if [ "$have_pkg" -eq 0 ] && [ "$migrated" -eq 0 ]; then
|
|
62
|
-
echo " NOTE: $SYNC_PKG_NAME is not in this manifest (bare setup) — engine meta-skills stay uninstalled; 'intelligence add $SYNC_PKG_NAME' opts back in." >&2
|
|
92
|
+
echo " NOTE: $SYNC_PKG_NAME is not in this manifest (bare setup) — engine meta-skills stay uninstalled; 'intelligence package add $SYNC_PKG_NAME' opts back in." >&2
|
|
63
93
|
fi
|
|
64
94
|
|
|
65
|
-
|
|
66
|
-
|
|
95
|
+
# A previous RC could leave this managed directory even after its source
|
|
96
|
+
# entries were already removed. Its presence is itself an upgrade predicate,
|
|
97
|
+
# so always close that post-condition.
|
|
98
|
+
if [ -d "$IP_ROOT/.intelligence/engine" ]; then
|
|
99
|
+
rm -rf "$IP_ROOT/.intelligence/engine"
|
|
100
|
+
echo " removed stale .intelligence/engine"
|
|
101
|
+
fi
|
|
67
102
|
|
|
68
|
-
|
|
103
|
+
stamp_schema_version "$manifest" "$eng"
|
|
104
|
+
echo " schema_version -> $eng"
|
|
105
|
+
|
|
106
|
+
if [ "$no_sync" -eq 0 ]; then
|
|
107
|
+
exec bash "$CLI_DIR/commands/sync.sh"
|
|
108
|
+
fi
|
package/cli/lib/cli-common.sh
CHANGED
|
@@ -54,8 +54,8 @@ source "$CLI_DIR/lib/lockfile.sh"
|
|
|
54
54
|
# The engine-content package: OPTIONAL but auto-selected at init. Package by
|
|
55
55
|
# UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
|
|
56
56
|
# at the version the CLI ships, it materializes from the npm bundle without
|
|
57
|
-
# network; only a cross-version
|
|
58
|
-
# at the bundled engine version and moved
|
|
57
|
+
# network; only a cross-version acquisition reaches git. The pin is held
|
|
58
|
+
# exactly at the bundled engine version and moved by lifecycle alignment.
|
|
59
59
|
#
|
|
60
60
|
# Its identity is DATA shipped with the distribution (cli/engine-package.yaml),
|
|
61
61
|
# never a name compiled into cli code — a fork edits the file.
|
|
@@ -113,7 +113,7 @@ require_v2() {
|
|
|
113
113
|
detect_project
|
|
114
114
|
case "$IP_MODE" in
|
|
115
115
|
v2) ;;
|
|
116
|
-
legacy) die "this is a vendored (v1) setup — run 'intelligence
|
|
116
|
+
legacy) die "this is a vendored (v1) setup — run 'intelligence init' to migrate it, or keep using its own flow" ;;
|
|
117
117
|
*) die "no intelligence project found here — run 'intelligence init'" ;;
|
|
118
118
|
esac
|
|
119
119
|
}
|
|
@@ -126,6 +126,14 @@ manifest_intelligence_dir() {
|
|
|
126
126
|
printf '%s' "${v:-intelligence}"
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
default_target_output() {
|
|
130
|
+
case "$1" in
|
|
131
|
+
agents) printf '%s' "AGENTS.md" ;;
|
|
132
|
+
copilot) printf '%s' ".github" ;;
|
|
133
|
+
*) printf '.%s' "$1" ;;
|
|
134
|
+
esac
|
|
135
|
+
}
|
|
136
|
+
|
|
129
137
|
bundled_engine_version() {
|
|
130
138
|
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/VERSION"
|
|
131
139
|
}
|
|
@@ -156,14 +164,132 @@ sync_pkg_install() {
|
|
|
156
164
|
# Everything the IS_CLI mode of sync.sh needs, derived from the manifest.
|
|
157
165
|
export_engine_env() {
|
|
158
166
|
local root="$1"
|
|
159
|
-
local
|
|
160
|
-
|
|
167
|
+
local content_rel
|
|
168
|
+
content_rel="$(manifest_intelligence_dir "$root/intelligence.yaml")"
|
|
161
169
|
export IS_CLI=1
|
|
162
170
|
export CONFIG_FILE="$root/intelligence.yaml"
|
|
163
171
|
export REPO_ROOT="$root"
|
|
164
|
-
export
|
|
172
|
+
export IS_CONTENT_REL="$content_rel"
|
|
165
173
|
export IS_MODULE_REL="$SYNC_PKG_STORE"
|
|
166
174
|
export IS_SYNC_CMD="intelligence sync"
|
|
167
175
|
export IS_MANIFEST_NAME="intelligence.yaml"
|
|
168
|
-
export IS_PROTECTED_DIRS="$
|
|
176
|
+
export IS_PROTECTED_DIRS="$content_rel:.intelligence"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# --- Project lifecycle preflight -----------------------------------------
|
|
180
|
+
# Public commands are intentionally few. They share this state gate so a CLI
|
|
181
|
+
# installed at a newer engine version brings the current v2 project forward
|
|
182
|
+
# before a mutating operation. The npm install itself cannot do that: it runs
|
|
183
|
+
# outside any project and does not know which repositories the user owns.
|
|
184
|
+
|
|
185
|
+
is_ci_environment() {
|
|
186
|
+
case "${CI:-}" in
|
|
187
|
+
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
|
188
|
+
*) return 1 ;;
|
|
189
|
+
esac
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Validate a repository-relative project content directory, including every
|
|
193
|
+
# existing symlinked path component. Call before writing a new manifest.
|
|
194
|
+
assert_safe_content_dir() {
|
|
195
|
+
local root="$1" content_dir="$2" repo_phys probe old_ifs part probe_phys
|
|
196
|
+
local -a parts
|
|
197
|
+
case "$content_dir" in
|
|
198
|
+
""|/*|.|..|../*|*/../*|*/..|.git|.git/*|.intelligence|.intelligence/*|*\\*|[A-Za-z]:*)
|
|
199
|
+
die "unsafe content directory '$content_dir'"
|
|
200
|
+
;;
|
|
201
|
+
esac
|
|
202
|
+
repo_phys="$(cd "$root" && pwd -P)"
|
|
203
|
+
probe="$root"
|
|
204
|
+
old_ifs="$IFS"; IFS='/'; read -r -a parts <<< "$content_dir"; IFS="$old_ifs"
|
|
205
|
+
for part in "${parts[@]}"; do
|
|
206
|
+
[ -n "$part" ] || continue
|
|
207
|
+
probe="$probe/$part"
|
|
208
|
+
if [ -e "$probe" ] || [ -L "$probe" ]; then
|
|
209
|
+
probe_phys="$(cd "$probe" 2>/dev/null && pwd -P)" \
|
|
210
|
+
|| die "cannot resolve project content path '$content_dir'"
|
|
211
|
+
case "$probe_phys" in
|
|
212
|
+
"$repo_phys"|"$repo_phys"/*) ;;
|
|
213
|
+
*) die "project content directory resolves outside the repository: '$content_dir'" ;;
|
|
214
|
+
esac
|
|
215
|
+
fi
|
|
216
|
+
done
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
project_needs_upgrade() {
|
|
220
|
+
local root="$1" manifest="$1/intelligence.yaml" stamp eng pinned locked name
|
|
221
|
+
[ -f "$manifest" ] || return 1
|
|
222
|
+
stamp="$(read_schema_version "$manifest")"
|
|
223
|
+
eng="$(bundled_engine_version)"
|
|
224
|
+
[ -z "$stamp" ] && return 0
|
|
225
|
+
[ -n "$(top_scalar "$manifest" "sync_version")" ] && return 0
|
|
226
|
+
_ver_gt "$eng" "$stamp" && return 0
|
|
227
|
+
[ -d "$root/.intelligence/engine" ] && return 0
|
|
228
|
+
|
|
229
|
+
while IFS= read -r name; do
|
|
230
|
+
[ "$name" = "$SYNC_PKG_NAME" ] || continue
|
|
231
|
+
pinned="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
232
|
+
locked="$(qmap_field "$root/intelligence.lock" "packages" "$name" "resolved")"
|
|
233
|
+
[ "$pinned" = "$eng" ] || return 0
|
|
234
|
+
if [ -f "$root/intelligence.lock" ]; then
|
|
235
|
+
[ -n "$locked" ] || return 0
|
|
236
|
+
[ "${locked#v}" = "$eng" ] || return 0
|
|
237
|
+
fi
|
|
238
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
239
|
+
return 1
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# A manifest with packages but no lock has no trustworthy resolved state from
|
|
243
|
+
# which lifecycle alignment can proceed. Never manufacture a partial lock.
|
|
244
|
+
project_has_packages() {
|
|
245
|
+
local root="$1" name found=1
|
|
246
|
+
while IFS= read -r name; do
|
|
247
|
+
[ -n "$name" ] || continue
|
|
248
|
+
assert_valid_pkg_name "$name"
|
|
249
|
+
found=0
|
|
250
|
+
done < <(qmap_keys "$root/intelligence.yaml" "packages")
|
|
251
|
+
return "$found"
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
ensure_project_current() {
|
|
255
|
+
local root="$1" explicit="${2:-}" manifest="$1/intelligence.yaml" stamp eng
|
|
256
|
+
check_version_compat "$manifest" || return $?
|
|
257
|
+
if project_has_packages "$root" && [ ! -f "$root/intelligence.lock" ]; then
|
|
258
|
+
die "manifest declares packages but intelligence.lock is absent — restore the committed lock before running project lifecycle commands"
|
|
259
|
+
fi
|
|
260
|
+
project_needs_upgrade "$root" || return 0
|
|
261
|
+
stamp="$(read_schema_version "$manifest")"
|
|
262
|
+
eng="$(bundled_engine_version)"
|
|
263
|
+
if is_ci_environment && [ "$explicit" != "--explicit" ]; then
|
|
264
|
+
die "project lifecycle requires alignment (stamp ${stamp:-unstamped}, engine $eng) — run 'intelligence init --apply' locally, review and commit the diff"
|
|
265
|
+
fi
|
|
266
|
+
echo " project alignment: stamp ${stamp:-unstamped}, engine $eng"
|
|
267
|
+
bash "$CLI_DIR/internal/upgrade-v2.sh" --no-sync
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
project_store_missing() {
|
|
271
|
+
local root="$1" manifest="$1/intelligence.yaml" name src
|
|
272
|
+
while IFS= read -r name; do
|
|
273
|
+
[ -n "$name" ] || continue
|
|
274
|
+
assert_valid_pkg_name "$name"
|
|
275
|
+
[ -d "$root/.intelligence/packages/$name" ] || return 0
|
|
276
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
277
|
+
for section in rules agents skills; do
|
|
278
|
+
while IFS= read -r src; do
|
|
279
|
+
case "$src" in
|
|
280
|
+
.intelligence/packages/*)
|
|
281
|
+
[ -d "$root/$src" ] || return 0
|
|
282
|
+
;;
|
|
283
|
+
esac
|
|
284
|
+
done < <(read_yaml_list "$manifest" "$section")
|
|
285
|
+
done
|
|
286
|
+
return 1
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
restore_project_store_if_missing() {
|
|
290
|
+
local root="$1"
|
|
291
|
+
project_store_missing "$root" || return 0
|
|
292
|
+
[ -f "$root/intelligence.lock" ] || die "package store is missing and intelligence.lock is absent — run 'intelligence init'"
|
|
293
|
+
echo " restoring package store from intelligence.lock"
|
|
294
|
+
bash "$CLI_DIR/internal/restore.sh" --frozen --no-sync
|
|
169
295
|
}
|
package/cli/lib/lockfile.sh
CHANGED
|
@@ -12,7 +12,7 @@ LOCK_SEP=$'\x1f'
|
|
|
12
12
|
|
|
13
13
|
# top_scalar <file> <key> — one top-level scalar (`key: "value"`), quotes and
|
|
14
14
|
# trailing comment stripped. The declared exception to "no new parsing": the
|
|
15
|
-
# engine's
|
|
15
|
+
# engine's read_schema_version is hardwired to its own key.
|
|
16
16
|
top_scalar() {
|
|
17
17
|
[ -f "$1" ] || return 0
|
|
18
18
|
awk -v key="$2" '
|
package/cli/lib/manifest.sh
CHANGED
|
@@ -36,6 +36,92 @@ assert_valid_pkg_name() {
|
|
|
36
36
|
esac
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
# Target names become adapter filenames and shell function suffixes, so keep
|
|
40
|
+
# them inside the portable shell-identifier subset used by built-in adapters.
|
|
41
|
+
assert_valid_target_name() {
|
|
42
|
+
case "$1" in
|
|
43
|
+
""|[!abcdefghijklmnopqrstuvwxyz]*|*[!abcdefghijklmnopqrstuvwxyz0123456789_]*)
|
|
44
|
+
die "invalid target name '$1' — expected [a-z][a-z0-9_]*"
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# target_exists <manifest> <name> — distinguish a disabled target from one
|
|
50
|
+
# that is not declared at all. Both inline and block target shapes count.
|
|
51
|
+
target_exists() {
|
|
52
|
+
local file="$1" target="$2"
|
|
53
|
+
[ -f "$file" ] || return 1
|
|
54
|
+
awk -v target="$target" '
|
|
55
|
+
{ sub(/\r$/, "") }
|
|
56
|
+
/^targets:[[:space:]]*$/ { in_targets = 1; next }
|
|
57
|
+
/^[A-Za-z]/ { in_targets = 0 }
|
|
58
|
+
in_targets && $0 ~ "^ " target ":[[:space:]]*" { found = 1; exit }
|
|
59
|
+
END { exit(found ? 0 : 1) }
|
|
60
|
+
' "$file"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# target_set_enabled <manifest> <name> <true|false> <default-output>
|
|
64
|
+
# Transactionally update only `enabled`, preserving every other target field
|
|
65
|
+
# and comment. A missing target is added in the compact form used by init.
|
|
66
|
+
target_set_enabled() {
|
|
67
|
+
local file="$1" target="$2" enabled="$3" output="$4"
|
|
68
|
+
[ -f "$file" ] || die "no such file: $file"
|
|
69
|
+
case "$enabled" in true|false) ;; *) die "internal: invalid target state '$enabled'" ;; esac
|
|
70
|
+
output="$(_yq_esc "$output")"
|
|
71
|
+
_qmap_stage "$file" -v target="$target" -v enabled="$enabled" -v output="$output" '
|
|
72
|
+
function entry() { return " " target ": { enabled: " enabled ", output: \"" output "\" }" }
|
|
73
|
+
function add_enabled_to_inline(line, p) {
|
|
74
|
+
p = index(line, "{")
|
|
75
|
+
if (p > 0) return substr(line, 1, p) " enabled: " enabled "," substr(line, p + 1)
|
|
76
|
+
return line
|
|
77
|
+
}
|
|
78
|
+
function flush_target() {
|
|
79
|
+
if (in_target && !enabled_seen) print " enabled: " enabled
|
|
80
|
+
in_target = 0
|
|
81
|
+
}
|
|
82
|
+
function flush_targets() {
|
|
83
|
+
flush_target()
|
|
84
|
+
if (in_targets && !target_seen) { print entry(); target_seen = 1 }
|
|
85
|
+
in_targets = 0
|
|
86
|
+
}
|
|
87
|
+
{ sub(/\r$/, "") }
|
|
88
|
+
/^targets:[[:space:]]*$/ { targets_seen = 1; in_targets = 1; print; next }
|
|
89
|
+
in_targets && /^[A-Za-z]/ { flush_targets() }
|
|
90
|
+
in_targets && /^ [A-Za-z]/ {
|
|
91
|
+
flush_target()
|
|
92
|
+
if ($0 ~ "^ " target ":[[:space:]]*") {
|
|
93
|
+
target_seen = 1
|
|
94
|
+
if (match($0, /enabled:[[:space:]]*(true|false)/)) {
|
|
95
|
+
print substr($0, 1, RSTART - 1) "enabled: " enabled substr($0, RSTART + RLENGTH)
|
|
96
|
+
enabled_seen = 1
|
|
97
|
+
} else if ($0 ~ /\{/) {
|
|
98
|
+
print add_enabled_to_inline($0)
|
|
99
|
+
enabled_seen = 1
|
|
100
|
+
} else {
|
|
101
|
+
print
|
|
102
|
+
in_target = 1
|
|
103
|
+
enabled_seen = 0
|
|
104
|
+
}
|
|
105
|
+
next
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
in_target && /^ enabled:[[:space:]]*/ {
|
|
109
|
+
print " enabled: " enabled
|
|
110
|
+
enabled_seen = 1
|
|
111
|
+
next
|
|
112
|
+
}
|
|
113
|
+
{ last = $0; print }
|
|
114
|
+
END {
|
|
115
|
+
flush_targets()
|
|
116
|
+
if (!targets_seen) {
|
|
117
|
+
if (last != "") print ""
|
|
118
|
+
print "targets:"
|
|
119
|
+
print entry()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
'
|
|
123
|
+
}
|
|
124
|
+
|
|
39
125
|
# qmap_keys <file> <block> — quoted keys, one per line.
|
|
40
126
|
qmap_keys() {
|
|
41
127
|
[ -f "$1" ] || return 0
|
package/cli/lib/registry.sh
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# visible, reviewable, deletable.
|
|
12
12
|
#
|
|
13
13
|
# The NAME is the trust anchor a developer reasons with; source integrity is
|
|
14
|
-
# a separate mechanism: the lock pins url+sha, and
|
|
14
|
+
# a separate mechanism: the lock pins url+sha, and `status --check` flags
|
|
15
15
|
# resolution/lock url drift.
|
|
16
16
|
#
|
|
17
17
|
# An index is itself fetched with git (never curl): auth, proxies and private
|
|
@@ -93,7 +93,7 @@ suggest_similar() {
|
|
|
93
93
|
cshort="${cand#*/}"; cshort="${cshort%s}"
|
|
94
94
|
if [ "$cshort" = "$want" ] && [ "$cand" != "$name" ]; then
|
|
95
95
|
seen="$seen$cand "
|
|
96
|
-
echo " Did you mean: intelligence add $cand" >&2
|
|
96
|
+
echo " Did you mean: intelligence package add $cand" >&2
|
|
97
97
|
fi
|
|
98
98
|
done < <(qmap_keys "$idx" "packages")
|
|
99
99
|
}
|
|
@@ -116,8 +116,8 @@ fetch_package() {
|
|
|
116
116
|
assert_safe_source_url "$url"
|
|
117
117
|
assert_safe_ref "$ref"
|
|
118
118
|
# Bundle seed: the engine-content package at the CLI's own version copies
|
|
119
|
-
# from the npm bundle — no network, which keeps init
|
|
120
|
-
#
|
|
119
|
+
# from the npm bundle — no network, which keeps init, sync restoration and
|
|
120
|
+
# archived-project conversion offline. Keyed on the
|
|
121
121
|
# full (url, path, ref) triple; any other version or source falls through
|
|
122
122
|
# to the normal clone.
|
|
123
123
|
if [ "$url" = "$SYNC_PKG_URL" ] && [ "$subpath" = "$SYNC_PKG_PATH" ] \
|
package/engine/ENGINE_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f2bfc5e3722bf6fb2f7309db2d248368d5b0cc5e
|