@ainova-systems/intelligence 0.18.0 → 0.19.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/cli/commands/init.sh +6 -1
- package/cli/commands/package.sh +8 -3
- package/cli/commands/source.sh +55 -16
- package/cli/intelligence +1 -1
- package/cli/internal/check.sh +64 -13
- package/cli/internal/package-add.sh +25 -4
- package/cli/internal/package-alias.sh +51 -0
- package/cli/internal/package-list.sh +16 -2
- package/cli/internal/package-remove.sh +3 -1
- package/cli/internal/package-update.sh +12 -4
- package/cli/lib/adapter-lifecycle.sh +5 -2
- package/cli/lib/cli-common.sh +4 -1
- package/cli/lib/gitignore.sh +128 -34
- package/cli/lib/manifest.sh +168 -20
- package/cli/lib/registry.sh +18 -13
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/engine/adapters/_template.sh +6 -2
- package/engine/adapters/agents.sh +3 -0
- package/engine/adapters/claude.sh +21 -10
- package/engine/adapters/codex.sh +8 -12
- package/engine/adapters/copilot.sh +46 -2
- package/engine/adapters/cursor.sh +6 -1
- package/engine/lib/adapter-contract.sh +17 -6
- package/engine/lib/common.sh +559 -59
- package/engine/sync.sh +9 -0
- package/package.json +1 -1
- package/packages/sync/references/adapters.md +39 -9
- package/packages/sync/references/conventions.md +46 -9
- package/packages/sync/references/onboarding-migration.md +3 -3
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +3 -2
- package/packages/sync/skills/intelligence-review-context/SKILL.md +2 -1
- package/packages/sync/skills/intelligence-update-context/SKILL.md +4 -2
- package/packages/sync/skills/intelligence-update-context/references/agents.md +7 -3
- package/packages/sync/skills/intelligence-update-context/references/skills.md +3 -1
package/cli/commands/init.sh
CHANGED
|
@@ -84,7 +84,11 @@ print_new_project_onboarding() {
|
|
|
84
84
|
echo " intelligence adapter disable cursor"
|
|
85
85
|
echo " Version control:"
|
|
86
86
|
echo " Generated adapter output is gitignored by CLI-owned path."
|
|
87
|
-
echo " Commit intelligence.yaml, intelligence.lock, intelligence/, AGENTS.md
|
|
87
|
+
echo " Commit intelligence.yaml, intelligence.lock, intelligence/, and AGENTS.md."
|
|
88
|
+
if [ "$(is_target_enabled "$manifest" copilot)" = "1" ]; then
|
|
89
|
+
echo " Copilot on github.com reads the repository, not a sync: to commit its output, set"
|
|
90
|
+
echo " targets.copilot.commit_output: true, then run intelligence init."
|
|
91
|
+
fi
|
|
88
92
|
echo " Existing .vscodeignore, .npmignore, and .dockerignore files receive packaging exclusions."
|
|
89
93
|
echo " Shared tool settings remain trackable; review the exact ownership policy:"
|
|
90
94
|
echo " https://github.com/ainova-systems/intelligence/blob/main/packages/sync/references/conventions.md#generated-output-and-version-control"
|
|
@@ -296,6 +300,7 @@ manifest="$root/intelligence.yaml"
|
|
|
296
300
|
echo "# its stack, and the constraints to respect"
|
|
297
301
|
echo "# $content_dir/agents/<name>.md a persona (frontmatter: name, description,"
|
|
298
302
|
echo "# tier: frontier|heavy|standard|light,"
|
|
303
|
+
echo "# effort: low|medium|high|xhigh|max|ultra,"
|
|
299
304
|
echo "# access: full|readonly)"
|
|
300
305
|
echo "# $content_dir/skills/<name>/SKILL.md a procedure invoked as /<name>"
|
|
301
306
|
echo "# '.intelligence/' is CLI-managed (packages + engine content) — never edit it."
|
package/cli/commands/package.sh
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# intelligence package <add|remove|list|search> ...
|
|
2
|
+
# intelligence package <add|remove|alias|list|search> ...
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
5
5
|
|
|
6
6
|
action="${1:-}"
|
|
7
|
-
[ -n "$action" ] || die "usage: intelligence package <add|remove|list|search> [args]"
|
|
7
|
+
[ -n "$action" ] || die "usage: intelligence package <add|remove|alias|list|search> [args]"
|
|
8
8
|
shift
|
|
9
9
|
|
|
10
10
|
case "$action" in
|
|
@@ -18,6 +18,11 @@ case "$action" in
|
|
|
18
18
|
ensure_project_current "$IP_ROOT"
|
|
19
19
|
exec bash "$CLI_DIR/internal/package-remove.sh" "$@"
|
|
20
20
|
;;
|
|
21
|
+
alias)
|
|
22
|
+
require_cli_project
|
|
23
|
+
ensure_project_current "$IP_ROOT"
|
|
24
|
+
exec bash "$CLI_DIR/internal/package-alias.sh" "$@"
|
|
25
|
+
;;
|
|
21
26
|
list)
|
|
22
27
|
exec bash "$CLI_DIR/internal/package-list.sh" "$@"
|
|
23
28
|
;;
|
|
@@ -25,6 +30,6 @@ case "$action" in
|
|
|
25
30
|
exec bash "$CLI_DIR/internal/package-search.sh" "$@"
|
|
26
31
|
;;
|
|
27
32
|
*)
|
|
28
|
-
die "usage: intelligence package <add|remove|list|search> [args]"
|
|
33
|
+
die "usage: intelligence package <add|remove|alias|list|search> [args]"
|
|
29
34
|
;;
|
|
30
35
|
esac
|
package/cli/commands/source.sh
CHANGED
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
# that ships it) belongs after the store entries and before the project's.
|
|
11
11
|
#
|
|
12
12
|
# Installed package content is NOT managed here: `.intelligence/` entries are
|
|
13
|
-
# written by `package add` and removed by `package remove`, and
|
|
14
|
-
#
|
|
13
|
+
# written by `package add` and removed by `package remove`, and so are the
|
|
14
|
+
# entries that name a package's directory by its full name or alias
|
|
15
|
+
# (`@scope/name/<dir>`, `<alias>:<dir>`, decision 0019). Any of those spellings
|
|
16
|
+
# is still a valid `--before` / `--after` anchor: that is how a directory lands
|
|
17
|
+
# next to a package.
|
|
15
18
|
set -euo pipefail
|
|
16
19
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
17
20
|
|
|
@@ -36,11 +39,32 @@ assert_source_section() {
|
|
|
36
39
|
# repaired — a source entry decides which artifact wins, so a path the user did
|
|
37
40
|
# not type is the wrong kind of help.
|
|
38
41
|
|
|
42
|
+
# assert_not_package_reference <manifest> <dir> <add|remove> — refuse an entry
|
|
43
|
+
# the engine reads as a package reference: a directory of a package `packages:`
|
|
44
|
+
# declares, by full name or alias, or an alias no package declares. It is
|
|
45
|
+
# package territory however it is spelled.
|
|
46
|
+
assert_not_package_reference() {
|
|
47
|
+
local state rendered rest pkg
|
|
48
|
+
IFS=$'\037' read -r state rendered <<< "$(sources_classify_entry "$1" "$2")"
|
|
49
|
+
rest="${rendered#.intelligence/packages/}"
|
|
50
|
+
pkg="${rest%%/*}/"
|
|
51
|
+
rest="${rest#*/}"
|
|
52
|
+
pkg="$pkg${rest%%/*}"
|
|
53
|
+
case "$state/$3" in
|
|
54
|
+
path/*) return 0 ;;
|
|
55
|
+
ok/add) die "'$2' names a directory of the installed package $pkg — 'intelligence package add' wires a package's directories into sources" ;;
|
|
56
|
+
ok/remove) die "'$2' is installed package content — remove the package instead: intelligence package remove $pkg" ;;
|
|
57
|
+
*/add) die "'$2' is a package reference, not a project directory — 'intelligence package add' wires a package's directories into sources" ;;
|
|
58
|
+
*) die "'$2' is a package reference, not a project directory — 'intelligence package remove <@scope/name>' removes a package's entries" ;;
|
|
59
|
+
esac
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
# assert_valid_source_dir <root> <dir> — refuse what the engine cannot report.
|
|
40
63
|
# The shape checks are `source_entry_problem`, shared with `status --check` so
|
|
41
64
|
# one definition covers both the entry about to be written and the one already
|
|
42
|
-
# in the manifest; the
|
|
43
|
-
#
|
|
65
|
+
# in the manifest; the package rules are this command's alone, because a
|
|
66
|
+
# package entry is legitimate when `package add` wrote it or a person spelled
|
|
67
|
+
# it by the package's name.
|
|
44
68
|
assert_valid_source_dir() {
|
|
45
69
|
local root="$1" dir="$2" problem
|
|
46
70
|
[ -n "$dir" ] || die "$USAGE"
|
|
@@ -50,26 +74,34 @@ assert_valid_source_dir() {
|
|
|
50
74
|
die "'$dir' is inside the CLI-managed package store — install content with 'intelligence package add', and it is wired into sources automatically"
|
|
51
75
|
;;
|
|
52
76
|
esac
|
|
77
|
+
assert_not_package_reference "$root/intelligence.yaml" "$dir" add
|
|
53
78
|
problem="$(source_entry_problem "$root" "$dir")"
|
|
54
79
|
[ -z "$problem" ] || die "invalid source '$dir': it $problem"
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
# Print one section with its override direction and the state of each entry.
|
|
58
83
|
# Position is the whole point of the block, so every mutation ends by showing
|
|
59
|
-
# the order it produced rather than only naming what it wrote.
|
|
84
|
+
# the order it produced rather than only naming what it wrote. Entries are
|
|
85
|
+
# listed as written and judged by the directory the engine renders them as.
|
|
60
86
|
print_section() {
|
|
61
|
-
local manifest="$1" root="$2" section="$3" entry n=0 note
|
|
87
|
+
local manifest="$1" root="$2" section="$3" entry state dir n=0 note
|
|
62
88
|
echo "sources.$section (a later entry overrides an earlier one):"
|
|
63
|
-
|
|
89
|
+
[ -f "$manifest" ] || { echo " (none)"; return 0; }
|
|
90
|
+
while IFS=$'\037' read -r entry state dir _; do
|
|
64
91
|
[ -n "$entry" ] || continue
|
|
65
92
|
n=$((n + 1))
|
|
66
93
|
note=""
|
|
67
|
-
case "$
|
|
68
|
-
|
|
94
|
+
case "$state" in
|
|
95
|
+
path|ok)
|
|
96
|
+
case "$dir" in
|
|
97
|
+
.intelligence/*) note=" package" ;;
|
|
98
|
+
esac
|
|
99
|
+
[ -d "$root/$dir" ] || note="$note MISSING"
|
|
100
|
+
;;
|
|
101
|
+
*) note=" UNRESOLVED — intelligence status --check" ;;
|
|
69
102
|
esac
|
|
70
|
-
[ -d "$root/$entry" ] || note="$note MISSING"
|
|
71
103
|
printf ' %d. %s%s\n' "$n" "$entry" "$note"
|
|
72
|
-
done < <(
|
|
104
|
+
done < <(read_source_entries "$manifest" "$section")
|
|
73
105
|
[ "$n" -eq 0 ] && echo " (none)"
|
|
74
106
|
return 0
|
|
75
107
|
}
|
|
@@ -110,10 +142,16 @@ case "$action" in
|
|
|
110
142
|
ensure_project_current "$IP_ROOT"
|
|
111
143
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
112
144
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
145
|
+
# The anchor may be spelled differently from the entry it means — a
|
|
146
|
+
# package's store path, full name or alias — so it is matched by the
|
|
147
|
+
# directory it names and placed against the entry as written.
|
|
148
|
+
if [ -n "$anchor" ]; then
|
|
149
|
+
if ! listed="$(sources_find_entry "$manifest" "$section" "$anchor")"; then
|
|
150
|
+
echo "ERROR: sources.$section does not list '$anchor'." >&2
|
|
151
|
+
print_section "$manifest" "$IP_ROOT" "$section" >&2
|
|
152
|
+
exit 1
|
|
153
|
+
fi
|
|
154
|
+
anchor="$listed"
|
|
117
155
|
fi
|
|
118
156
|
|
|
119
157
|
if sources_has_entry "$manifest" "$section" "$dir"; then
|
|
@@ -149,8 +187,9 @@ case "$action" in
|
|
|
149
187
|
esac
|
|
150
188
|
|
|
151
189
|
require_cli_project
|
|
152
|
-
ensure_project_current "$IP_ROOT"
|
|
153
190
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
191
|
+
assert_not_package_reference "$manifest" "$dir" remove
|
|
192
|
+
ensure_project_current "$IP_ROOT"
|
|
154
193
|
|
|
155
194
|
if ! sources_has_entry "$manifest" "$section" "$dir"; then
|
|
156
195
|
echo "not listed: sources.$section does not hold $dir"
|
package/cli/intelligence
CHANGED
|
@@ -61,7 +61,7 @@ Usage: intelligence <command> [args]
|
|
|
61
61
|
--latest Move one named package past its range to the newest version
|
|
62
62
|
upgrade [--next] [--preview|--apply] Replace the installed CLI with the newest npm version
|
|
63
63
|
--next Follow the prerelease line instead of the stable one
|
|
64
|
-
package <command> add | remove | list | search
|
|
64
|
+
package <command> add | remove | alias | list | search
|
|
65
65
|
source <command> add | remove | list — the sources: block (own directories)
|
|
66
66
|
adapter <command> list | create | enable | disable | remove
|
|
67
67
|
status [--check] Project state; --check runs deep consistency checks
|
package/cli/internal/check.sh
CHANGED
|
@@ -119,6 +119,18 @@ while IFS= read -r name; do
|
|
|
119
119
|
fi
|
|
120
120
|
done < <(qmap_keys "$manifest" "packages")
|
|
121
121
|
|
|
122
|
+
# Aliases name packages in sources: (decision 0019). One the engine would not
|
|
123
|
+
# resolve, or one two packages state, leaves every <alias>:<dir> entry naming
|
|
124
|
+
# nothing. An invalid alias is never echoed: it is manifest input that may hold
|
|
125
|
+
# anything.
|
|
126
|
+
while IFS=$'\037' read -r name alias verdict; do
|
|
127
|
+
[ -n "$name" ] || continue
|
|
128
|
+
case "$verdict" in
|
|
129
|
+
invalid) warn "$name declares an alias that is not one — expected $IS_PKG_ALIAS_RULE: intelligence package alias $name <alias>" ;;
|
|
130
|
+
ambiguous) warn "$name declares alias '$alias', which another package declares too — an alias names one package" ;;
|
|
131
|
+
esac
|
|
132
|
+
done < <(read_package_aliases "$manifest")
|
|
133
|
+
|
|
122
134
|
# Adapter contracts are the shared source of truth for dependencies, rollback,
|
|
123
135
|
# onboarding backup and Git policy. Validate every declared target, including
|
|
124
136
|
# disabled ones, so a later enable cannot reveal a stale custom adapter.
|
|
@@ -135,7 +147,7 @@ while IFS= read -r target; do
|
|
|
135
147
|
warn "adapter '$target' is declared but its implementation is missing"
|
|
136
148
|
continue
|
|
137
149
|
fi
|
|
138
|
-
records="$(adapter_contract_records "$target" "$adapter_file" "$output" 2>/dev/null || true)"
|
|
150
|
+
records="$(adapter_contract_records "$target" "$adapter_file" "$output" "$manifest" 2>/dev/null || true)"
|
|
139
151
|
if ! printf '%s\n' "$records" | grep -Fqx $'version\t1'; then
|
|
140
152
|
warn "adapter '$target' has an invalid ownership contract"
|
|
141
153
|
continue
|
|
@@ -150,18 +162,29 @@ while IFS= read -r target; do
|
|
|
150
162
|
&& [ "$(is_target_enabled "$manifest" "$value")" != "1" ]; then
|
|
151
163
|
warn "enabled adapter '$target' requires enabled adapter '$value'"
|
|
152
164
|
fi
|
|
165
|
+
# Presence is the writer's own test: a line `init` treats as present
|
|
166
|
+
# and leaves alone must not be reported missing here, or the warning
|
|
167
|
+
# can never clear — a CRLF .gitignore on Linux and macOS did exactly that.
|
|
153
168
|
if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
|
|
154
169
|
case "$kind" in
|
|
155
170
|
ignore)
|
|
156
|
-
|
|
157
|
-
|| warn "adapter '$target' Git policy is missing '$value'"
|
|
171
|
+
ignore_file_has_line "$IP_ROOT/.gitignore" "$value" \
|
|
172
|
+
|| warn "adapter '$target' Git policy is missing '$value' — run 'intelligence init'"
|
|
173
|
+
;;
|
|
174
|
+
unignore)
|
|
175
|
+
if gitignore_managed_has_line "$IP_ROOT" "$value"; then
|
|
176
|
+
warn "adapter '$target' Git policy keeps '$value' tracked, but .gitignore still ignores it — run 'intelligence init'"
|
|
177
|
+
fi
|
|
158
178
|
;;
|
|
159
179
|
include)
|
|
160
|
-
if !
|
|
161
|
-
warn "adapter '$target' Git policy is missing '!$value'"
|
|
180
|
+
if ! ignore_file_has_line "$IP_ROOT/.gitignore" "!$value"; then
|
|
181
|
+
warn "adapter '$target' Git policy is missing '!$value' — run 'intelligence init'"
|
|
162
182
|
elif git -C "$IP_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
183
|
+
# init moves the negation chain last, which beats any
|
|
184
|
+
# later rule in .gitignore; a deeper .gitignore still
|
|
185
|
+
# wins, so the finding also says how to find it.
|
|
163
186
|
if git -C "$IP_ROOT" check-ignore -q --no-index -- "$value"; then
|
|
164
|
-
warn "adapter '$target' Git policy cannot re-include '$value' because another ignore rule still wins"
|
|
187
|
+
warn "adapter '$target' Git policy cannot re-include '$value' because another ignore rule still wins — run 'intelligence init'; if it still wins, 'git check-ignore -v --no-index -- $value' names the rule"
|
|
165
188
|
else
|
|
166
189
|
ignore_rc=$?
|
|
167
190
|
[ "$ignore_rc" -eq 1 ] \
|
|
@@ -188,22 +211,50 @@ if [ "$lock_valid" -eq 1 ] && [ -f "$lock" ]; then
|
|
|
188
211
|
fi
|
|
189
212
|
|
|
190
213
|
# Sources must exist (a missing dir is silently skipped by the engine, which
|
|
191
|
-
# is exactly why doctor names it).
|
|
214
|
+
# is exactly why doctor names it). Each entry is judged by the directory the
|
|
215
|
+
# engine renders it as, read through the same parser: a package reference by
|
|
216
|
+
# its store path, one that resolves to nothing by why.
|
|
192
217
|
for section in rules agents skills; do
|
|
193
|
-
|
|
218
|
+
seen=$'\n'
|
|
219
|
+
while IFS=$'\037' read -r src state dir alias holders; do
|
|
194
220
|
[ -n "$src" ] || continue
|
|
221
|
+
case "$state" in
|
|
222
|
+
unknown|ambiguous|invalid)
|
|
223
|
+
source_reference_problem_var "$state" "$alias" "$holders"
|
|
224
|
+
case "$state" in
|
|
225
|
+
unknown) warn "sources.$section '$src' $IS_SOURCE_PROBLEM — declare it with 'intelligence package alias <@scope/name> $alias', or correct the entry" ;;
|
|
226
|
+
*) warn "sources.$section '$src' $IS_SOURCE_PROBLEM" ;;
|
|
227
|
+
esac
|
|
228
|
+
continue
|
|
229
|
+
;;
|
|
230
|
+
esac
|
|
231
|
+
# One package directory under two spellings is listed twice, and the
|
|
232
|
+
# later one moves the package past every entry between them.
|
|
233
|
+
case "$dir" in
|
|
234
|
+
.intelligence/packages/*)
|
|
235
|
+
case "$seen" in
|
|
236
|
+
*$'\n'"$dir"$'\037'*)
|
|
237
|
+
first="${seen#*$'\n'"$dir"$'\037'}"
|
|
238
|
+
first="${first%%$'\n'*}"
|
|
239
|
+
[ "$first" = "$src" ] \
|
|
240
|
+
|| warn "sources.$section names $dir twice, as '$first' and as '$src' — keep one: where it stands decides which artifacts win"
|
|
241
|
+
;;
|
|
242
|
+
*) seen="$seen$dir"$'\037'"$src"$'\n' ;;
|
|
243
|
+
esac
|
|
244
|
+
;;
|
|
245
|
+
esac
|
|
195
246
|
case "$src" in
|
|
196
|
-
git
|
|
247
|
+
git+*) continue ;;
|
|
197
248
|
esac
|
|
198
249
|
# A hand-edited entry the engine cannot render is worse than a missing
|
|
199
250
|
# one: it is skipped without a word and the sync still reports ok.
|
|
200
|
-
problem="$(source_entry_problem "$IP_ROOT" "$
|
|
251
|
+
problem="$(source_entry_problem "$IP_ROOT" "$dir")"
|
|
201
252
|
if [ -n "$problem" ]; then
|
|
202
253
|
warn "sources.$section '$src' $problem"
|
|
203
254
|
continue
|
|
204
255
|
fi
|
|
205
|
-
if [ ! -d "$IP_ROOT/$
|
|
206
|
-
case "$
|
|
256
|
+
if [ ! -d "$IP_ROOT/$dir" ]; then
|
|
257
|
+
case "$dir" in
|
|
207
258
|
# Store content is restorable state: absent means un-installed.
|
|
208
259
|
.intelligence/*)
|
|
209
260
|
if [ "$lock_valid" -eq 1 ]; then
|
|
@@ -219,7 +270,7 @@ for section in rules agents skills; do
|
|
|
219
270
|
*) ok "sources.$section '$src' — not created yet (optional)" ;;
|
|
220
271
|
esac
|
|
221
272
|
fi
|
|
222
|
-
done < <(
|
|
273
|
+
done < <(read_source_entries "$manifest" "$section")
|
|
223
274
|
done
|
|
224
275
|
|
|
225
276
|
if [ "$problems" -eq 0 ]; then
|
|
@@ -9,15 +9,18 @@ set -euo pipefail
|
|
|
9
9
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
10
10
|
|
|
11
11
|
spec="${1:-}"
|
|
12
|
-
[ -n "$spec" ] || die "usage: intelligence package add <@scope/name[@range] | github:org/repo[#path] | git+<url>[@ref][#path]>"
|
|
12
|
+
[ -n "$spec" ] || die "usage: intelligence package add <@scope/name[@range] | github:org/repo[#path] | git+<url>[@ref][#path]> [--name @scope/name] [--alias <alias>] [--no-sync]"
|
|
13
13
|
shift || true
|
|
14
14
|
|
|
15
15
|
no_sync=0
|
|
16
16
|
name_override=""
|
|
17
|
+
alias_given=0
|
|
18
|
+
alias_req=""
|
|
17
19
|
while [ $# -gt 0 ]; do
|
|
18
20
|
case "$1" in
|
|
19
21
|
--no-sync) no_sync=1 ;;
|
|
20
22
|
--name) shift; name_override="${1:-}" ;;
|
|
23
|
+
--alias) shift; alias_given=1; alias_req="${1:-}" ;;
|
|
21
24
|
*) die "unknown option '$1'" ;;
|
|
22
25
|
esac
|
|
23
26
|
shift || true
|
|
@@ -77,6 +80,20 @@ esac
|
|
|
77
80
|
[ -n "$name_override" ] && name="$name_override"
|
|
78
81
|
assert_valid_pkg_name "$name"
|
|
79
82
|
|
|
83
|
+
# The alias is settled before anything is fetched or written: a refusal leaves
|
|
84
|
+
# the manifest, the lock and the store as they were. Re-adding a package keeps
|
|
85
|
+
# the alias it declares unless --alias names another one, and never takes away
|
|
86
|
+
# an alias `sources:` still spells entries with.
|
|
87
|
+
alias="$(package_alias_of "$manifest" "$name")"
|
|
88
|
+
if [ "$alias_given" -eq 1 ]; then
|
|
89
|
+
assert_valid_alias "$alias_req"
|
|
90
|
+
assert_alias_free "$manifest" "$name" "$alias_req"
|
|
91
|
+
if [ -n "$alias" ] && [ "$alias" != "$alias_req" ]; then
|
|
92
|
+
assert_alias_unused "$manifest" "$name" "$alias" replace
|
|
93
|
+
fi
|
|
94
|
+
alias="$alias_req"
|
|
95
|
+
fi
|
|
96
|
+
|
|
80
97
|
if [ "$mode" = "registry" ]; then
|
|
81
98
|
resolve_package_source "$manifest" "$name"
|
|
82
99
|
url="$RES_URL"; path="$RES_PATH"
|
|
@@ -129,8 +146,6 @@ rel=".intelligence/packages/$name"
|
|
|
129
146
|
store_record_remove "$IP_ROOT" "$name"
|
|
130
147
|
sha="$(fetch_package "$url" "${ref:-$resolved_tag}" "$path" "$IP_ROOT/$rel")"
|
|
131
148
|
|
|
132
|
-
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
133
|
-
|
|
134
149
|
# The manifest records requested intent only. The resolved source URL/path,
|
|
135
150
|
# tag/ref and SHA live in the required lock. Re-adding a package is the
|
|
136
151
|
# explicit operation for changing its trusted source.
|
|
@@ -142,6 +157,12 @@ elif [ -n "$requested" ]; then
|
|
|
142
157
|
else
|
|
143
158
|
die "internal: package '$name' has neither a requested version nor ref"
|
|
144
159
|
fi
|
|
160
|
+
[ -z "$alias" ] || qmap_set "$manifest" "packages" "$name" "alias" "$alias"
|
|
161
|
+
|
|
162
|
+
# Wired once the package and its alias are declared, so a section that already
|
|
163
|
+
# names one of its directories as @scope/name/<dir> or <alias>:<dir> is left as
|
|
164
|
+
# it stands instead of gaining the store path beside it.
|
|
165
|
+
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
145
166
|
|
|
146
167
|
lock_upsert "$lock" "$name" "$requested" "$url" "$path" "${ref:-$resolved_tag}" "$sha"
|
|
147
168
|
store_record_set "$IP_ROOT" "$name" "$url" "$path" "${ref:-$resolved_tag}" "$sha"
|
|
@@ -150,7 +171,7 @@ sections=""
|
|
|
150
171
|
for s in rules agents skills; do
|
|
151
172
|
[ -d "$IP_ROOT/$rel/$s" ] && sections="$sections $s"
|
|
152
173
|
done
|
|
153
|
-
echo "+ $name@${resolved_tag:-${ref:-HEAD}} (${sections# })"
|
|
174
|
+
echo "+ $name@${resolved_tag:-${ref:-HEAD}} (${sections# })${alias:+ alias $alias}"
|
|
154
175
|
|
|
155
176
|
if [ "$no_sync" -eq 0 ]; then
|
|
156
177
|
exec bash "$CLI_DIR/commands/sync.sh"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Internal package alias operation: set, replace or remove the alias a package
|
|
3
|
+
# declares in `packages:`. `sources:` spells that package's directories as
|
|
4
|
+
# <alias>:<dir> (decision 0019); the alias is the CLI's to write, like the rest
|
|
5
|
+
# of the block, and one alias names exactly one package.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
source "$CLI_DIR/lib/cli-common.sh"
|
|
8
|
+
|
|
9
|
+
USAGE="usage: intelligence package alias <@scope/name> <alias>
|
|
10
|
+
intelligence package alias <@scope/name> --remove"
|
|
11
|
+
|
|
12
|
+
[ $# -eq 2 ] || die "$USAGE"
|
|
13
|
+
name="$1"
|
|
14
|
+
want="$2"
|
|
15
|
+
|
|
16
|
+
require_cli_project
|
|
17
|
+
project_lock_hold "$IP_ROOT"
|
|
18
|
+
manifest="$IP_ROOT/intelligence.yaml"
|
|
19
|
+
assert_valid_pkg_name "$name"
|
|
20
|
+
|
|
21
|
+
known=0
|
|
22
|
+
while IFS= read -r k; do
|
|
23
|
+
[ "$k" = "$name" ] && known=1
|
|
24
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
25
|
+
[ "$known" -eq 1 ] || die "package '$name' is not in the manifest — 'intelligence package list' shows the declared ones"
|
|
26
|
+
|
|
27
|
+
current="$(package_alias_of "$manifest" "$name")"
|
|
28
|
+
|
|
29
|
+
if [ "$want" = "--remove" ]; then
|
|
30
|
+
# An entry can only spell a well-formed alias, so only a well-formed one can
|
|
31
|
+
# still be in use; a malformed one is dropped like any other.
|
|
32
|
+
[ -z "$current" ] || assert_alias_unused "$manifest" "$name" "$current" remove
|
|
33
|
+
if [ -z "$(qmap_field "$manifest" "packages" "$name" "alias")" ]; then
|
|
34
|
+
echo "$name declares no alias"
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
qmap_delete_field "$manifest" "packages" "$name" "alias"
|
|
38
|
+
echo "$name: alias removed"
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
case "$want" in -*) die "$USAGE" ;; esac
|
|
43
|
+
assert_valid_alias "$want"
|
|
44
|
+
if [ "$current" = "$want" ]; then
|
|
45
|
+
echo "$name: alias $want (unchanged)"
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
assert_alias_free "$manifest" "$name" "$want"
|
|
49
|
+
[ -z "$current" ] || assert_alias_unused "$manifest" "$name" "$current" replace
|
|
50
|
+
qmap_set "$manifest" "packages" "$name" "alias" "$want"
|
|
51
|
+
echo "$name: alias $want — sources: may name its directories $want:<dir>"
|
|
@@ -9,6 +9,10 @@ lock="$IP_ROOT/intelligence.lock"
|
|
|
9
9
|
lock_valid=1
|
|
10
10
|
if ! check_project_lock "$IP_ROOT"; then lock_valid=0; fi
|
|
11
11
|
|
|
12
|
+
# Aliases come from the parser the engine resolves them with, in one pass; an
|
|
13
|
+
# alias it would not resolve is shown as such, never echoed.
|
|
14
|
+
aliases="$(read_package_aliases "$manifest")"
|
|
15
|
+
|
|
12
16
|
count=0
|
|
13
17
|
while IFS= read -r name; do
|
|
14
18
|
[ -n "$name" ] || continue
|
|
@@ -29,11 +33,21 @@ while IFS= read -r name; do
|
|
|
29
33
|
else
|
|
30
34
|
state="store missing — run 'intelligence sync'"
|
|
31
35
|
fi
|
|
32
|
-
|
|
36
|
+
alias_note=""
|
|
37
|
+
while IFS=$'\037' read -r a_name a_alias a_verdict; do
|
|
38
|
+
[ "$a_name" = "$name" ] || continue
|
|
39
|
+
case "$a_verdict" in
|
|
40
|
+
ok) alias_note=" alias:$a_alias" ;;
|
|
41
|
+
ambiguous) alias_note=" alias:$a_alias (another package declares it too)" ;;
|
|
42
|
+
*) alias_note=" alias:invalid — intelligence status --check" ;;
|
|
43
|
+
esac
|
|
44
|
+
done <<< "$aliases"
|
|
45
|
+
printf '%s %s locked:%s %s%s\n' \
|
|
33
46
|
"$name" \
|
|
34
47
|
"${ref:+ref:$ref}${ref:-${range:-*}}" \
|
|
35
48
|
"$(pin_label "$ref" "$resolved" "$sha")" \
|
|
36
|
-
"$state"
|
|
49
|
+
"$state" \
|
|
50
|
+
"$alias_note"
|
|
37
51
|
done < <(qmap_keys "$manifest" "packages")
|
|
38
52
|
|
|
39
53
|
if [ "$count" -eq 0 ]; then
|
|
@@ -39,7 +39,9 @@ while IFS= read -r k; do
|
|
|
39
39
|
done < <(qmap_keys "$manifest" "packages")
|
|
40
40
|
[ "$known" -eq 1 ] || die "package '$name' is not in the manifest"
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
# Every entry naming the package, in every spelling, goes while `packages:`
|
|
43
|
+
# still declares it and its alias: that is what resolves the references.
|
|
44
|
+
unwire_package_sources "$manifest" "$name"
|
|
43
45
|
qmap_delete_key "$manifest" "packages" "$name"
|
|
44
46
|
lock_remove "$lock" "$name"
|
|
45
47
|
rm -rf "${IP_ROOT:?}/$rel"
|
|
@@ -167,13 +167,21 @@ while IFS= read -r name; do
|
|
|
167
167
|
fi
|
|
168
168
|
rel=".intelligence/packages/$name"
|
|
169
169
|
# Fetch into staging FIRST: a failed clone must leave the current install
|
|
170
|
-
# fully wired and intact. Only after success
|
|
171
|
-
#
|
|
172
|
-
#
|
|
170
|
+
# fully wired and intact. Only after success are the sections the new
|
|
171
|
+
# version dropped unwired, in every spelling, the store swapped, and the
|
|
172
|
+
# sections it gained wired. A section it keeps stays where it stands in
|
|
173
|
+
# the list: position is the override order between packages, and an
|
|
174
|
+
# update changes content, not which package wins.
|
|
173
175
|
staging="$IP_ROOT/.intelligence/.staging-$$"
|
|
174
176
|
rm -rf "$staging"
|
|
175
177
|
sha="$(fetch_package "$url" "$tag" "$path" "$staging")"
|
|
176
|
-
|
|
178
|
+
dropped=()
|
|
179
|
+
for section in rules agents skills; do
|
|
180
|
+
[ -d "$staging/$section" ] || dropped+=("$section")
|
|
181
|
+
done
|
|
182
|
+
if [ "${#dropped[@]}" -gt 0 ]; then
|
|
183
|
+
unwire_package_sources "$manifest" "$name" "${dropped[@]}"
|
|
184
|
+
fi
|
|
177
185
|
store_record_remove "$IP_ROOT" "$name"
|
|
178
186
|
rm -rf "${IP_ROOT:?}/$rel"
|
|
179
187
|
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
@@ -12,13 +12,16 @@ adapter_file_for() {
|
|
|
12
12
|
fi
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
# adapter_records_for <root> <content-dir> <name> <output> [manifest] — pass the
|
|
16
|
+
# manifest whenever the caller applies or checks Git policy; ownership callers
|
|
17
|
+
# may omit it (engine/lib/adapter-contract.sh).
|
|
15
18
|
adapter_records_for() {
|
|
16
|
-
local root="$1" content_dir="$2" name="$3" output="$4" file
|
|
19
|
+
local root="$1" content_dir="$2" name="$3" output="$4" manifest="${5:-}" file
|
|
17
20
|
file="$(adapter_file_for "$root" "$content_dir" "$name")" || {
|
|
18
21
|
echo "ERROR: adapter '$name' not found" >&2
|
|
19
22
|
return 1
|
|
20
23
|
}
|
|
21
|
-
adapter_contract_records "$name" "$file" "$output"
|
|
24
|
+
adapter_contract_records "$name" "$file" "$output" "$manifest"
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
validate_adapter_contract_for() {
|
package/cli/lib/cli-common.sh
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
# All YAML reading goes through the engine's lib/common.sh — the CLI never
|
|
6
6
|
# grows a parallel parser for shapes the engine can already read. The only
|
|
7
7
|
# CLI-owned parsing lives in lib/manifest.sh (quoted-key `packages:` /
|
|
8
|
-
# `registries:` blocks, which the engine
|
|
8
|
+
# `registries:` blocks, which the engine never reads beyond the package names
|
|
9
|
+
# and aliases its list parser resolves `sources:` references against).
|
|
9
10
|
|
|
10
11
|
# Engine libraries (readers, is_status, IS_RC_*, engine_version). CLI_DIR /
|
|
11
12
|
# IS_ENGINE_DIR come exported from the dispatcher.
|
|
@@ -545,6 +546,8 @@ project_store_missing() {
|
|
|
545
546
|
assert_valid_pkg_name "$name"
|
|
546
547
|
[ -d "$root/.intelligence/packages/$name" ] || return 0
|
|
547
548
|
done < <(qmap_keys "$manifest" "packages")
|
|
549
|
+
# The parser hands a package reference over as the store directory it
|
|
550
|
+
# names, so every spelling is checked here as that directory.
|
|
548
551
|
for section in rules agents skills; do
|
|
549
552
|
while IFS= read -r src; do
|
|
550
553
|
case "$src" in
|