@ainova-systems/intelligence 0.19.0 → 0.21.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 +2 -1
- package/cli/commands/registry.sh +1 -1
- package/cli/commands/source.sh +1 -1
- package/cli/commands/sync.sh +78 -6
- package/cli/intelligence +15 -2
- package/cli/internal/check.sh +39 -0
- package/cli/internal/convert-legacy.sh +1 -1
- package/cli/internal/package-search.sh +1 -1
- package/cli/internal/package-update.sh +2 -2
- package/cli/internal/restore.sh +3 -2
- package/cli/lib/cli-common.sh +145 -33
- package/cli/lib/gitignore.sh +19 -2
- package/cli/lib/lockfile.sh +14 -4
- package/cli/lib/manifest.sh +147 -1
- package/cli/lib/qmap.awk +97 -32
- package/cli/lib/registry.sh +1 -1
- package/cli/lib/sync-cache.sh +596 -142
- package/cli/lib/sync-lock.sh +19 -2
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/engine/adapters/_template.sh +32 -8
- package/engine/adapters/agents.sh +47 -52
- package/engine/adapters/antigravity.sh +71 -53
- package/engine/adapters/claude.sh +42 -57
- package/engine/adapters/codex.sh +77 -16
- package/engine/adapters/copilot.sh +54 -65
- package/engine/adapters/cursor.sh +88 -78
- package/engine/adapters/opencode.sh +23 -32
- package/engine/adapters/pi.sh +140 -42
- package/engine/lib/adapter-contract.sh +36 -4
- package/engine/lib/common.sh +1388 -165
- package/engine/lib/contract.sh +5 -0
- package/engine/sync.sh +145 -38
- package/package.json +1 -1
- package/packages/sync/references/adapters.md +48 -38
- package/packages/sync/references/conventions.md +61 -12
- package/packages/sync/references/onboarding-migration.md +4 -4
- package/packages/sync/rules/intelligence-authoring.md +2 -2
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +7 -5
- package/packages/sync/skills/intelligence-review-context/references/audit-checks.md +1 -1
- package/packages/sync/skills/intelligence-update-context/references/rules.md +5 -0
package/cli/commands/init.sh
CHANGED
|
@@ -356,7 +356,8 @@ manifest="$root/intelligence.yaml"
|
|
|
356
356
|
} > "$manifest"
|
|
357
357
|
|
|
358
358
|
# The package store is restorable state, never committed — the npm model.
|
|
359
|
-
|
|
359
|
+
# Local rules, agents and skills stay on this machine (decision 0021).
|
|
360
|
+
ensure_base_gitignore "$root" "$content_dir"
|
|
360
361
|
for target in $targets; do
|
|
361
362
|
ensure_target_gitignore "$root" "$manifest" "$target"
|
|
362
363
|
done
|
package/cli/commands/registry.sh
CHANGED
|
@@ -51,7 +51,7 @@ case "$sub" in
|
|
|
51
51
|
echo " index.yaml reachable — $n package(s):"
|
|
52
52
|
qmap_keys "$index" "packages" | sed 's/^/ /'
|
|
53
53
|
elif [ "$force" -eq 1 ]; then
|
|
54
|
-
echo "
|
|
54
|
+
echo "WARNING: no index.yaml at $url — recording anyway (--force)." >&2
|
|
55
55
|
else
|
|
56
56
|
echo "ERROR: no index.yaml found at $url — not adding it." >&2
|
|
57
57
|
echo " A registry is a git repo with index.yaml at its root, mapping names to sources:" >&2
|
package/cli/commands/source.sh
CHANGED
|
@@ -168,7 +168,7 @@ case "$action" in
|
|
|
168
168
|
echo "added: $dir"
|
|
169
169
|
fi
|
|
170
170
|
if [ ! -d "$IP_ROOT/$dir" ]; then
|
|
171
|
-
echo "
|
|
171
|
+
echo "WARNING: $dir does not exist yet — sync skips a missing source and renders it once the directory appears." >&2
|
|
172
172
|
fi
|
|
173
173
|
print_section "$manifest" "$IP_ROOT" "$section"
|
|
174
174
|
echo "Run 'intelligence sync' to render it."
|
package/cli/commands/sync.sh
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# intelligence sync [target] [--compact] [--force] - render intelligence
|
|
2
|
+
# intelligence sync [target] [--compact] [--force] [--check] - render intelligence
|
|
3
|
+
# to enabled tools, or report whether rendering would change anything.
|
|
3
4
|
set -euo pipefail
|
|
4
5
|
|
|
6
|
+
# Only sync_check may request a comparison from the engine.
|
|
7
|
+
unset IS_SYNC_CHECK IS_SYNC_CHECK_ROOT
|
|
8
|
+
|
|
5
9
|
# Print before library loading and lifecycle checks so a content verification
|
|
6
|
-
# never looks idle. Compact output remains a buffered machine-readable report
|
|
10
|
+
# never looks idle. Compact output remains a buffered machine-readable report,
|
|
11
|
+
# and a check prints only its verdict.
|
|
7
12
|
compact=0
|
|
13
|
+
check=0
|
|
8
14
|
for argument in "$@"; do
|
|
9
|
-
|
|
15
|
+
case "$argument" in
|
|
16
|
+
--compact) compact=1 ;;
|
|
17
|
+
--check) check=1 ;;
|
|
18
|
+
esac
|
|
10
19
|
done
|
|
11
|
-
[ "$compact" -ne 0 ] || echo 'Checking project...'
|
|
20
|
+
[ "$compact" -ne 0 ] || [ "$check" -ne 0 ] || echo 'Checking project...'
|
|
21
|
+
# The CLI's package descriptor is read in the same pass as the project's
|
|
22
|
+
# manifest and lock, once this command holds the project lock.
|
|
23
|
+
# shellcheck disable=SC2034 # read by cli-common.sh while it loads
|
|
24
|
+
CLI_DEFER_SYNC_PKG=1
|
|
12
25
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
13
26
|
force=0
|
|
14
27
|
target=""
|
|
@@ -16,22 +29,35 @@ while [ $# -gt 0 ]; do
|
|
|
16
29
|
case "$1" in
|
|
17
30
|
--compact) compact=1 ;;
|
|
18
31
|
--force) force=1 ;;
|
|
32
|
+
--check) check=1 ;;
|
|
19
33
|
-*) die "unknown option '$1'" ;;
|
|
20
34
|
*)
|
|
21
|
-
[ -z "$target" ] || die "usage: intelligence sync [adapter] [--compact] [--force]"
|
|
35
|
+
[ -z "$target" ] || die "usage: intelligence sync [adapter] [--compact] [--force] [--check]"
|
|
22
36
|
target="$1"
|
|
23
37
|
;;
|
|
24
38
|
esac
|
|
25
39
|
shift
|
|
26
40
|
done
|
|
27
41
|
|
|
42
|
+
# Set where a check concludes that sync is needed, so a failure that happens to
|
|
43
|
+
# end in the same status is never mistaken for that verdict.
|
|
44
|
+
IS_CHECK_OUT_OF_DATE=0
|
|
45
|
+
|
|
46
|
+
# hold_project — take the project lock, then read the package descriptor,
|
|
47
|
+
# manifest and lock once for the whole preflight.
|
|
48
|
+
hold_project() {
|
|
49
|
+
project_lock_hold "$IP_ROOT"
|
|
50
|
+
project_reads_preload "$IP_ROOT"
|
|
51
|
+
load_sync_package_identity
|
|
52
|
+
}
|
|
53
|
+
|
|
28
54
|
run_sync() {
|
|
29
55
|
detect_project || return $?
|
|
30
56
|
case "$IP_MODE" in
|
|
31
57
|
cli)
|
|
32
58
|
# One writer per project at a time: the store restore and the
|
|
33
59
|
# render below both rewrite shared state (sync-lock.sh).
|
|
34
|
-
|
|
60
|
+
hold_project
|
|
35
61
|
# `sync` is the normal fresh-clone command. A newer globally installed
|
|
36
62
|
# CLI aligns the current Intelligence project first (except in CI, where a
|
|
37
63
|
# tracked migration must be reviewed and committed locally), and a
|
|
@@ -42,6 +68,7 @@ run_sync() {
|
|
|
42
68
|
sync_with_cache "$target" "$force" || return $?
|
|
43
69
|
;;
|
|
44
70
|
legacy)
|
|
71
|
+
load_sync_package_identity
|
|
45
72
|
[ "$force" -eq 0 ] || die "--force is unavailable for a legacy Intelligence Sync project - run 'intelligence init' to convert it first"
|
|
46
73
|
[ "$compact" -eq 0 ] || die "--compact is unavailable for a legacy Intelligence Sync project - run 'intelligence init' to convert it first"
|
|
47
74
|
# A vendored project syncs with its own engine: its pin is the
|
|
@@ -55,11 +82,56 @@ run_sync() {
|
|
|
55
82
|
fi
|
|
56
83
|
;;
|
|
57
84
|
*)
|
|
85
|
+
load_sync_package_identity
|
|
58
86
|
die "no intelligence project found here - run 'intelligence init'"
|
|
59
87
|
;;
|
|
60
88
|
esac
|
|
61
89
|
}
|
|
62
90
|
|
|
91
|
+
# run_check — the same preflight as sync, except that tracked alignment is never
|
|
92
|
+
# applied (it is reported as needed, in CI or not) and everything but the
|
|
93
|
+
# verdict goes to stderr. Restoring the ignored package store from the lock is
|
|
94
|
+
# allowed: it is reproducible state, and the comparison needs it.
|
|
95
|
+
run_check() {
|
|
96
|
+
local stamp
|
|
97
|
+
detect_project || return $?
|
|
98
|
+
case "$IP_MODE" in
|
|
99
|
+
cli) ;;
|
|
100
|
+
legacy)
|
|
101
|
+
load_sync_package_identity
|
|
102
|
+
die "--check is unavailable for a legacy Intelligence Sync project - run 'intelligence init' to convert it first"
|
|
103
|
+
;;
|
|
104
|
+
*)
|
|
105
|
+
load_sync_package_identity
|
|
106
|
+
die "no intelligence project found here - run 'intelligence init'"
|
|
107
|
+
;;
|
|
108
|
+
esac
|
|
109
|
+
hold_project
|
|
110
|
+
project_preflight "$IP_ROOT" || return $?
|
|
111
|
+
if project_needs_upgrade "$IP_ROOT"; then
|
|
112
|
+
read_schema_version_var "$IP_ROOT/intelligence.yaml"
|
|
113
|
+
stamp="$IS_SCHEMA_VERSION"
|
|
114
|
+
bundled_engine_version_var
|
|
115
|
+
IS_CHECK_OUT_OF_DATE=1
|
|
116
|
+
is_status out-of-date "the project needs alignment (stamp ${stamp:-unstamped}, engine $IS_BUNDLED_ENGINE_VERSION); run 'intelligence init --apply' locally, review and commit the diff"
|
|
117
|
+
return "$IS_RC_OUT_OF_DATE"
|
|
118
|
+
fi
|
|
119
|
+
restore_project_store_if_missing "$IP_ROOT" keep-sources >&2 || return $?
|
|
120
|
+
export_engine_env "$IP_ROOT" || return $?
|
|
121
|
+
sync_check "$target" "$force" || return $?
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if [ "$check" -eq 1 ]; then
|
|
125
|
+
# --compact changes nothing here: the verdict is already one status line,
|
|
126
|
+
# and a failure's diagnostics already reach stderr.
|
|
127
|
+
rc=0
|
|
128
|
+
run_check || rc=$?
|
|
129
|
+
if [ "$rc" = "$IS_RC_OUT_OF_DATE" ] && [ "$IS_CHECK_OUT_OF_DATE" != 1 ]; then
|
|
130
|
+
rc=1
|
|
131
|
+
fi
|
|
132
|
+
exit "$rc"
|
|
133
|
+
fi
|
|
134
|
+
|
|
63
135
|
if [ "$compact" -eq 0 ]; then
|
|
64
136
|
run_sync
|
|
65
137
|
exit $?
|
package/cli/intelligence
CHANGED
|
@@ -11,7 +11,11 @@ case "$0" in
|
|
|
11
11
|
*/*) CLI_DIR="${0%/*}"; [ -n "$CLI_DIR" ] || CLI_DIR="/" ;;
|
|
12
12
|
*) CLI_DIR="." ;;
|
|
13
13
|
esac
|
|
14
|
-
|
|
14
|
+
# An absolute spelling without `.` or `..` segments or doubled slashes is
|
|
15
|
+
# already what `cd && pwd` prints; on Git Bash that subshell costs a fork.
|
|
16
|
+
case "$CLI_DIR" in
|
|
17
|
+
*/|*/./*|*/.|*/../*|*/..|*//*|[!/]*) CLI_DIR="$(cd "$CLI_DIR" && pwd)" ;;
|
|
18
|
+
esac
|
|
15
19
|
export CLI_DIR
|
|
16
20
|
|
|
17
21
|
# Engine discovery, by role (sync.sh + VERSION). The repo and the npm package
|
|
@@ -52,9 +56,10 @@ Usage: intelligence <command> [args]
|
|
|
52
56
|
init [--preview|--apply] Initialize, convert, restore or align this project
|
|
53
57
|
--targets a,b --dir name --bare --no-sync New-project options
|
|
54
58
|
--force Accept a dirty worktree during legacy project conversion
|
|
55
|
-
sync [adapter] [--compact] [--force] Restore locked content, then render enabled adapters
|
|
59
|
+
sync [adapter] [--compact] [--force] [--check] Restore locked content, then render enabled adapters
|
|
56
60
|
--compact On success show context, warnings and final status; on failure show diagnostics
|
|
57
61
|
--force Render again and refresh discovery of unconfigured source directories
|
|
62
|
+
--check Change no generated file; exit 0 when up to date, 2 when sync is needed
|
|
58
63
|
update [@scope/name] Show an update plan; ask before applying it
|
|
59
64
|
--preview Show the plan without asking or writing
|
|
60
65
|
--apply Apply the plan without asking
|
|
@@ -109,4 +114,12 @@ if [ ! -f "$cmd_file" ]; then
|
|
|
109
114
|
echo "ERROR: unknown command '$cmd'. Run 'intelligence help'." >&2
|
|
110
115
|
exit 1
|
|
111
116
|
fi
|
|
117
|
+
# `sync` runs on every save hook and CI check, so it runs in this process: a
|
|
118
|
+
# second bash start is a large share of an unchanged sync on Git Bash. It
|
|
119
|
+
# exits on every path and reads nothing of this script but the exports.
|
|
120
|
+
if [ "$cmd" = sync ]; then
|
|
121
|
+
# shellcheck source=commands/sync.sh
|
|
122
|
+
source "$cmd_file"
|
|
123
|
+
exit $?
|
|
124
|
+
fi
|
|
112
125
|
exec bash "$cmd_file" "$@"
|
package/cli/internal/check.sh
CHANGED
|
@@ -198,6 +198,14 @@ while IFS= read -r target; do
|
|
|
198
198
|
ok "adapter $target contract v1"
|
|
199
199
|
done < <(target_names "$manifest")
|
|
200
200
|
|
|
201
|
+
# A local rule, agent or skill is one these patterns name (decision 0021): the
|
|
202
|
+
# engine keeps it out of every committed output, and without the pattern Git
|
|
203
|
+
# would still offer to commit the file itself.
|
|
204
|
+
while IFS= read -r pattern; do
|
|
205
|
+
ignore_file_has_line "$IP_ROOT/.gitignore" "$pattern" \
|
|
206
|
+
|| warn "local artifact Git policy is missing '$pattern' — run 'intelligence init'"
|
|
207
|
+
done < <(local_artifact_ignore_patterns "$content_dir")
|
|
208
|
+
|
|
201
209
|
# Lock orphans.
|
|
202
210
|
if [ "$lock_valid" -eq 1 ] && [ -f "$lock" ]; then
|
|
203
211
|
while IFS= read -r name; do
|
|
@@ -214,6 +222,7 @@ fi
|
|
|
214
222
|
# is exactly why doctor names it). Each entry is judged by the directory the
|
|
215
223
|
# engine renders it as, read through the same parser: a package reference by
|
|
216
224
|
# its store path, one that resolves to nothing by why.
|
|
225
|
+
local_specs=()
|
|
217
226
|
for section in rules agents skills; do
|
|
218
227
|
seen=$'\n'
|
|
219
228
|
while IFS=$'\037' read -r src state dir alias holders; do
|
|
@@ -269,10 +278,40 @@ for section in rules agents skills; do
|
|
|
269
278
|
# authoring later needs no config edit.
|
|
270
279
|
*) ok "sources.$section '$src' — not created yet (optional)" ;;
|
|
271
280
|
esac
|
|
281
|
+
continue
|
|
272
282
|
fi
|
|
283
|
+
# A project-owned source is where a tracked file can carry a local
|
|
284
|
+
# name; store content is restorable state Git never tracks.
|
|
285
|
+
case "$dir" in
|
|
286
|
+
.intelligence/*) ;;
|
|
287
|
+
*)
|
|
288
|
+
if [ "$section" = skills ]; then
|
|
289
|
+
local_specs+=(":(glob)${dir%/}/*.local/SKILL.md")
|
|
290
|
+
else
|
|
291
|
+
local_specs+=(":(glob)${dir%/}/*.local.md")
|
|
292
|
+
fi
|
|
293
|
+
;;
|
|
294
|
+
esac
|
|
273
295
|
done < <(read_source_entries "$manifest" "$section")
|
|
274
296
|
done
|
|
275
297
|
|
|
298
|
+
# A name makes an artifact local (decision 0021), and .gitignore never applies
|
|
299
|
+
# to a file Git already tracks: one committed before its name meant anything,
|
|
300
|
+
# or added by hand, renders only into ignored output while every clone holds
|
|
301
|
+
# it. Git and the engine would disagree about it, so it is named here.
|
|
302
|
+
if [ "${#local_specs[@]}" -gt 0 ] \
|
|
303
|
+
&& git -C "$IP_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
304
|
+
if tracked_local="$(git -C "$IP_ROOT" -c core.quotePath=false ls-files -- "${local_specs[@]}")"; then
|
|
305
|
+
while IFS= read -r path; do
|
|
306
|
+
[ -n "$path" ] || continue
|
|
307
|
+
case "$path" in */SKILL.md) path="${path%/SKILL.md}/" ;; esac
|
|
308
|
+
warn "$path is tracked by Git, but its name makes it local: sync renders it only into ignored output — rename it to share it, or 'git rm --cached' it to keep it on this machine"
|
|
309
|
+
done <<< "$tracked_local"
|
|
310
|
+
else
|
|
311
|
+
warn "tracked local artifacts could not be listed — 'git ls-files' failed"
|
|
312
|
+
fi
|
|
313
|
+
fi
|
|
314
|
+
|
|
276
315
|
if [ "$problems" -eq 0 ]; then
|
|
277
316
|
echo "all good."
|
|
278
317
|
else
|
|
@@ -435,7 +435,7 @@ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
|
435
435
|
# Containment before deletion: a mirror value is config input, and rm -rf
|
|
436
436
|
# must never follow it out of the repository.
|
|
437
437
|
case "$mirror" in
|
|
438
|
-
""|/*|*..*) [ -n "$mirror" ] && echo "
|
|
438
|
+
""|/*|*..*) [ -n "$mirror" ] && echo "WARNING: mirror '$mirror' not repo-contained — left in place" >&2; continue ;;
|
|
439
439
|
esac
|
|
440
440
|
if [ -d "$root/$mirror" ]; then
|
|
441
441
|
rm -rf "${root:?}/$mirror"
|
|
@@ -63,7 +63,7 @@ if [ -n "$manifest" ]; then
|
|
|
63
63
|
[ -n "$url" ] || continue
|
|
64
64
|
index="$(_fetch_index "${url#git+}")"
|
|
65
65
|
if [ -z "$index" ]; then
|
|
66
|
-
echo "
|
|
66
|
+
echo "WARNING: registry unreachable or missing index.yaml: $url" >&2
|
|
67
67
|
continue
|
|
68
68
|
fi
|
|
69
69
|
emit_index "$index" "$url"
|
|
@@ -85,7 +85,7 @@ while IFS= read -r name; do
|
|
|
85
85
|
if [ "$probe_rc" -ne 0 ]; then
|
|
86
86
|
# No answer is not "no change": reporting up to date here would
|
|
87
87
|
# freeze the pin exactly as the ref-name comparison used to.
|
|
88
|
-
echo "
|
|
88
|
+
echo "WARNING: $name: cannot reach $url — ref '$ref' was not checked" >&2
|
|
89
89
|
echo " $name: $ref (not checked — remote unreachable)"
|
|
90
90
|
continue
|
|
91
91
|
fi
|
|
@@ -104,7 +104,7 @@ while IFS= read -r name; do
|
|
|
104
104
|
echo " $name: $(short_sha "$ref") (pinned commit)"
|
|
105
105
|
;;
|
|
106
106
|
*)
|
|
107
|
-
echo "
|
|
107
|
+
echo "WARNING: $name pins ref '$ref', which $url no longer advertises" >&2
|
|
108
108
|
echo " $name: $ref (unresolvable — gone upstream)"
|
|
109
109
|
;;
|
|
110
110
|
esac
|
package/cli/internal/restore.sh
CHANGED
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
set -euo pipefail
|
|
8
8
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
9
9
|
|
|
10
|
-
frozen=0 force=0 no_sync=0
|
|
10
|
+
frozen=0 force=0 no_sync=0 keep_sources=0
|
|
11
11
|
while [ $# -gt 0 ]; do
|
|
12
12
|
case "$1" in
|
|
13
13
|
--frozen) frozen=1 ;;
|
|
14
14
|
--force) force=1 ;;
|
|
15
15
|
--no-sync) no_sync=1 ;;
|
|
16
|
+
--keep-sources) keep_sources=1 ;;
|
|
16
17
|
*) die "unknown option '$1'" ;;
|
|
17
18
|
esac
|
|
18
19
|
shift || true
|
|
@@ -117,7 +118,7 @@ if [ -f "$lock" ]; then
|
|
|
117
118
|
mkdir -p "$(dirname "$IP_ROOT/$rel")"
|
|
118
119
|
mv "$staging" "$IP_ROOT/$rel"
|
|
119
120
|
store_record_set "$IP_ROOT" "$name" "$url" "$path" "$resolved" "$sha"
|
|
120
|
-
wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
121
|
+
[ "$keep_sources" = 1 ] || wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
|
|
121
122
|
echo "= $name@${resolved:-HEAD}"
|
|
122
123
|
done < <(lock_to_tsv "$lock")
|
|
123
124
|
fi
|
package/cli/lib/cli-common.sh
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Shared plumbing for every CLI command.
|
|
3
|
-
# dispatcher execs
|
|
2
|
+
# Shared plumbing for every CLI command. Each command is its own script (the
|
|
3
|
+
# dispatcher execs it, or sources `sync`), so each one sources this file first.
|
|
4
4
|
#
|
|
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
|
|
@@ -99,23 +99,32 @@ source "$CLI_DIR/lib/sync-lock.sh"
|
|
|
99
99
|
# Its identity is DATA shipped with the distribution (cli/engine-package.yaml),
|
|
100
100
|
# never a name compiled into cli code — a fork edits the file.
|
|
101
101
|
_EPKG="$CLI_DIR/engine-package.yaml"
|
|
102
|
-
|
|
102
|
+
_EPKG_FIELDS='name url path default_registry'
|
|
103
|
+
SYNC_PKG_NAME="" SYNC_PKG_URL="" SYNC_PKG_PATH="" SYNC_PKG_STORE=""
|
|
103
104
|
# Read by commands (init seeds it) — per-file shellcheck cannot see that.
|
|
104
105
|
# shellcheck disable=SC2034
|
|
105
106
|
DEFAULT_REGISTRY_URL=""
|
|
106
|
-
|
|
107
|
+
|
|
108
|
+
# load_sync_package_identity — read the four fields in one pass. Every command
|
|
109
|
+
# loads this file first and gets them at once; `sync` sets CLI_DEFER_SYNC_PKG
|
|
110
|
+
# before sourcing and reads them in the same awk pass as the project's manifest
|
|
111
|
+
# and lock (project_reads_preload), once it holds the project lock.
|
|
107
112
|
# shellcheck disable=SC2034 # DEFAULT_REGISTRY_URL is read by commands, as above
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
load_sync_package_identity() {
|
|
114
|
+
local key value
|
|
115
|
+
_qmap_read_var tops "$_EPKG" '' "$_EPKG_FIELDS" || true
|
|
116
|
+
while IFS="$LOCK_SEP" read -r key value; do
|
|
117
|
+
case "$key" in
|
|
118
|
+
name) SYNC_PKG_NAME="$value" ;;
|
|
119
|
+
url) SYNC_PKG_URL="$value" ;;
|
|
120
|
+
path) SYNC_PKG_PATH="$value" ;;
|
|
121
|
+
default_registry) DEFAULT_REGISTRY_URL="$value" ;;
|
|
122
|
+
esac
|
|
123
|
+
done <<< "$IS_QMAP_OUT"
|
|
124
|
+
[ -n "$SYNC_PKG_NAME" ] || die "corrupt CLI installation: $_EPKG is missing or has no 'name'"
|
|
125
|
+
SYNC_PKG_STORE=".intelligence/packages/$SYNC_PKG_NAME"
|
|
126
|
+
}
|
|
127
|
+
[ "${CLI_DEFER_SYNC_PKG:-0}" = 1 ] || load_sync_package_identity
|
|
119
128
|
|
|
120
129
|
# --- Project detection ---------------------------------------------------
|
|
121
130
|
# Sets: IP_MODE (cli|legacy|none), IP_ROOT, IP_UMBRELLA, IP_MODULE_DIR.
|
|
@@ -257,9 +266,14 @@ source_entry_problem() {
|
|
|
257
266
|
}
|
|
258
267
|
|
|
259
268
|
manifest_intelligence_dir() {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
269
|
+
manifest_intelligence_dir_var "$1"
|
|
270
|
+
printf '%s' "$IS_CONTENT_DIR"
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# manifest_intelligence_dir_var <manifest> — the same answer in IS_CONTENT_DIR.
|
|
274
|
+
manifest_intelligence_dir_var() {
|
|
275
|
+
get_yaml_field_var "$1" "project" "intelligence_dir"
|
|
276
|
+
IS_CONTENT_DIR="${IS_YAML_FIELD:-intelligence}"
|
|
263
277
|
}
|
|
264
278
|
|
|
265
279
|
default_target_output() {
|
|
@@ -317,7 +331,8 @@ sync_pkg_install() {
|
|
|
317
331
|
export_engine_env() {
|
|
318
332
|
local root="$1"
|
|
319
333
|
local content_rel
|
|
320
|
-
|
|
334
|
+
manifest_intelligence_dir_var "$root/intelligence.yaml"
|
|
335
|
+
content_rel="$IS_CONTENT_DIR"
|
|
321
336
|
export IS_CLI=1
|
|
322
337
|
export CONFIG_FILE="$root/intelligence.yaml"
|
|
323
338
|
export REPO_ROOT="$root"
|
|
@@ -382,6 +397,7 @@ project_stamped_ahead() {
|
|
|
382
397
|
|
|
383
398
|
project_needs_upgrade() {
|
|
384
399
|
local root="$1" manifest="$1/intelligence.yaml" stamp eng pinned locked name url path rows
|
|
400
|
+
local lname _requested _url _path resolved _sha
|
|
385
401
|
[ -f "$manifest" ] || return 1
|
|
386
402
|
read_schema_version_var "$manifest"
|
|
387
403
|
stamp="$IS_SCHEMA_VERSION"
|
|
@@ -392,12 +408,14 @@ project_needs_upgrade() {
|
|
|
392
408
|
# restamp the schema and re-pin the engine content DOWNWARD, and the next
|
|
393
409
|
# teammate on the current CLI would move both back: leave it as found.
|
|
394
410
|
project_stamped_ahead "$root" && return 1
|
|
395
|
-
|
|
411
|
+
_qmap_read_var top "$manifest" '' sync_version || true
|
|
412
|
+
[ -z "$IS_QMAP_OUT" ] || return 0
|
|
396
413
|
_ver_gt "$eng" "$stamp" && return 0
|
|
397
414
|
[ -d "$root/.intelligence/engine" ] && return 0
|
|
398
415
|
|
|
399
416
|
# One manifest pass for every package's fields, not a reader per field.
|
|
400
|
-
|
|
417
|
+
_qmap_read_var fieldrows "$manifest" packages '' 'url path version' || true
|
|
418
|
+
rows="$IS_QMAP_OUT"
|
|
401
419
|
while IFS="$LOCK_SEP" read -r name url path pinned; do
|
|
402
420
|
[ -n "$name" ] || continue
|
|
403
421
|
# Early RC manifests mixed requested intent with resolved source
|
|
@@ -405,7 +423,14 @@ project_needs_upgrade() {
|
|
|
405
423
|
[ -n "$url" ] && return 0
|
|
406
424
|
[ -n "$path" ] && return 0
|
|
407
425
|
if [ "$name" = "$SYNC_PKG_NAME" ]; then
|
|
408
|
-
|
|
426
|
+
# The first lock row for the name: what qmap_field would decode.
|
|
427
|
+
locked=""
|
|
428
|
+
_qmap_read_var rows "$root/intelligence.lock" packages || true
|
|
429
|
+
while IFS="$LOCK_SEP" read -r lname _requested _url _path resolved _sha; do
|
|
430
|
+
[ "$lname" = "$name" ] || continue
|
|
431
|
+
locked="$resolved"
|
|
432
|
+
break
|
|
433
|
+
done <<< "$IS_QMAP_OUT"
|
|
409
434
|
[ "$pinned" = "$eng" ] || return 0
|
|
410
435
|
if [ -f "$root/intelligence.lock" ]; then
|
|
411
436
|
[ -n "$locked" ] || return 0
|
|
@@ -420,21 +445,30 @@ project_needs_upgrade() {
|
|
|
420
445
|
# which lifecycle alignment can proceed. Never manufacture a partial lock.
|
|
421
446
|
project_has_packages() {
|
|
422
447
|
local root="$1" name found=1
|
|
448
|
+
_qmap_read_var keys "$root/intelligence.yaml" "packages" || true
|
|
423
449
|
while IFS= read -r name; do
|
|
424
450
|
[ -n "$name" ] || continue
|
|
425
451
|
assert_valid_pkg_name "$name"
|
|
426
452
|
found=0
|
|
427
|
-
done
|
|
453
|
+
done <<< "$IS_QMAP_OUT"
|
|
428
454
|
return "$found"
|
|
429
455
|
}
|
|
430
456
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
457
|
+
# project_preflight <root> — what every project-aware command checks before it
|
|
458
|
+
# may align or write: schema compatibility (a newer major refuses with 4), a
|
|
459
|
+
# valid lock, and a lock for whatever packages the manifest declares.
|
|
460
|
+
project_preflight() {
|
|
461
|
+
local root="$1"
|
|
462
|
+
check_version_compat "$root/intelligence.yaml" || return $?
|
|
434
463
|
validate_project_lock "$root" || return $?
|
|
435
464
|
if project_has_packages "$root" && [ ! -f "$root/intelligence.lock" ]; then
|
|
436
465
|
die "manifest declares packages but intelligence.lock is absent — restore the committed lock before running project lifecycle commands"
|
|
437
466
|
fi
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
ensure_project_current() {
|
|
470
|
+
local root="$1" explicit="${2:-}" manifest="$1/intelligence.yaml" stamp eng
|
|
471
|
+
project_preflight "$root" || return $?
|
|
438
472
|
project_needs_upgrade "$root" || return 0
|
|
439
473
|
read_schema_version_var "$manifest"
|
|
440
474
|
stamp="$IS_SCHEMA_VERSION"
|
|
@@ -444,7 +478,8 @@ ensure_project_current() {
|
|
|
444
478
|
die "project lifecycle requires alignment (stamp ${stamp:-unstamped}, engine $eng) — run 'intelligence init --apply' locally, review and commit the diff"
|
|
445
479
|
fi
|
|
446
480
|
echo " project alignment: stamp ${stamp:-unstamped}, engine $eng"
|
|
447
|
-
bash "$CLI_DIR/internal/align-project.sh" --no-sync
|
|
481
|
+
bash "$CLI_DIR/internal/align-project.sh" --no-sync || return $?
|
|
482
|
+
project_reads_refresh "$root"
|
|
448
483
|
}
|
|
449
484
|
|
|
450
485
|
# A present lock is validated even when there is no missing store to restore.
|
|
@@ -452,7 +487,8 @@ ensure_project_current() {
|
|
|
452
487
|
validate_project_lock() {
|
|
453
488
|
local lock="$1/intelligence.lock" output line rc=0
|
|
454
489
|
if [ -e "$lock" ] || [ -L "$lock" ]; then
|
|
455
|
-
|
|
490
|
+
# The capture is the subshell that contains the guards' `die`.
|
|
491
|
+
output="$(lock_validate_checks "$lock" "${2---metadata}" "$1/intelligence.yaml" 2>&1)" || rc=$?
|
|
456
492
|
while IFS= read -r line; do
|
|
457
493
|
[ -n "$line" ] || continue
|
|
458
494
|
case "$line" in
|
|
@@ -472,6 +508,74 @@ validate_project_lock() {
|
|
|
472
508
|
fi
|
|
473
509
|
}
|
|
474
510
|
|
|
511
|
+
# --- Reads shared by one command's preflight -----------------------------
|
|
512
|
+
# A sync reads the CLI's package descriptor, the manifest and the lock several
|
|
513
|
+
# times on its way to the engine. Once it holds the project lock it answers all
|
|
514
|
+
# of those reads with one qmap pass and one pass of the engine's manifest
|
|
515
|
+
# readers; a child that may rewrite either document (alignment, restore) is
|
|
516
|
+
# followed by project_reads_refresh. Without a preload every reader takes its
|
|
517
|
+
# ordinary path.
|
|
518
|
+
IS_PROJECT_READS_ROOT=""
|
|
519
|
+
|
|
520
|
+
project_reads_preload() {
|
|
521
|
+
local root="$1" manifest="$1/intelligence.yaml" lock="$1/intelligence.lock" s=$'\037'
|
|
522
|
+
local qmap=0 view=0 out qmap_pid="" view_pid=""
|
|
523
|
+
local -a view_args=("$manifest" project intelligence_dir rules agents skills)
|
|
524
|
+
project_reads_reset
|
|
525
|
+
qmap_preload_plan \
|
|
526
|
+
"tops$s$_EPKG$s$s$_EPKG_FIELDS$s$s" \
|
|
527
|
+
"keys$s$manifest${s}packages$s$s$s" \
|
|
528
|
+
"fieldrows$s$manifest${s}packages$s${s}url path version$s" \
|
|
529
|
+
"top$s$manifest$s${s}sync_version$s$s" \
|
|
530
|
+
"lock$s$lock${s}packages$s$s$s$LOCKFILE_VERSION" \
|
|
531
|
+
"rows$s$lock${s}packages$s$s$s" && qmap=1
|
|
532
|
+
[ ! -f "$manifest" ] || [ ! -r "$manifest" ] || view=1
|
|
533
|
+
if project_lock_scratch "$root"; then
|
|
534
|
+
# Both passes at once: starting their processes is most of their cost.
|
|
535
|
+
if [ "$qmap" = 1 ]; then
|
|
536
|
+
qmap_preload_run > "$IS_LOCK_SCRATCH.qmap" &
|
|
537
|
+
qmap_pid=$!
|
|
538
|
+
fi
|
|
539
|
+
if [ "$view" = 1 ]; then
|
|
540
|
+
manifest_view_run "${view_args[@]}" > "$IS_LOCK_SCRATCH.view" &
|
|
541
|
+
view_pid=$!
|
|
542
|
+
fi
|
|
543
|
+
if [ "$qmap" = 1 ] && wait "$qmap_pid"; then
|
|
544
|
+
qmap_preload_store "$(< "$IS_LOCK_SCRATCH.qmap")"
|
|
545
|
+
fi
|
|
546
|
+
if [ "$view" = 1 ]; then
|
|
547
|
+
if wait "$view_pid"; then
|
|
548
|
+
manifest_view_store "$(< "$IS_LOCK_SCRATCH.view")" "${view_args[@]}"
|
|
549
|
+
else
|
|
550
|
+
manifest_view_reset
|
|
551
|
+
fi
|
|
552
|
+
fi
|
|
553
|
+
else
|
|
554
|
+
if [ "$qmap" = 1 ] && out="$(qmap_preload_run)"; then
|
|
555
|
+
qmap_preload_store "$out"
|
|
556
|
+
fi
|
|
557
|
+
[ "$view" = 0 ] || load_manifest_view "${view_args[@]}" || manifest_view_reset
|
|
558
|
+
fi
|
|
559
|
+
IS_PROJECT_READS_ROOT="$root"
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
project_reads_reset() {
|
|
563
|
+
qmap_memo_reset
|
|
564
|
+
manifest_view_reset
|
|
565
|
+
IS_PROJECT_READS_ROOT=""
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
# project_reads_refresh <root> — after a child may have rewritten the manifest
|
|
569
|
+
# or the lock: read them again if this process preloaded them.
|
|
570
|
+
project_reads_refresh() {
|
|
571
|
+
[ "$IS_PROJECT_READS_ROOT" = "$1" ] || return 0
|
|
572
|
+
project_reads_preload "$1"
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
# CLI and engine share one list-cache format. Writers invalidate both the
|
|
576
|
+
# qmap answers and the engine view through qmap_memo_reset.
|
|
577
|
+
_yaml_list_var() { load_yaml_list "$1" "$2"; }
|
|
578
|
+
|
|
475
579
|
# Read-only diagnosis must distinguish usable installed metadata from metadata
|
|
476
580
|
# which could actually restore a missing store.
|
|
477
581
|
check_project_lock() {
|
|
@@ -540,28 +644,31 @@ _store_record_write() {
|
|
|
540
644
|
# project_store_missing <root> — a package the manifest or lock names is absent
|
|
541
645
|
# from the store, or holds something other than its lock row pins.
|
|
542
646
|
project_store_missing() {
|
|
543
|
-
local root="$1" manifest="$1/intelligence.yaml" name src
|
|
647
|
+
local root="$1" manifest="$1/intelligence.yaml" name src section
|
|
648
|
+
_qmap_read_var keys "$manifest" "packages" || true
|
|
544
649
|
while IFS= read -r name; do
|
|
545
650
|
[ -n "$name" ] || continue
|
|
546
651
|
assert_valid_pkg_name "$name"
|
|
547
652
|
[ -d "$root/.intelligence/packages/$name" ] || return 0
|
|
548
|
-
done
|
|
653
|
+
done <<< "$IS_QMAP_OUT"
|
|
549
654
|
# The parser hands a package reference over as the store directory it
|
|
550
655
|
# names, so every spelling is checked here as that directory.
|
|
551
656
|
for section in rules agents skills; do
|
|
657
|
+
_yaml_list_var "$manifest" "$section"
|
|
552
658
|
while IFS= read -r src; do
|
|
553
659
|
case "$src" in
|
|
554
660
|
.intelligence/packages/*)
|
|
555
661
|
[ -d "$root/$src" ] || return 0
|
|
556
662
|
;;
|
|
557
663
|
esac
|
|
558
|
-
done
|
|
664
|
+
done <<< "$IS_YAML_LIST"
|
|
559
665
|
done
|
|
560
666
|
# Present is not enough: a `git pull` moves the lock and leaves the ignored
|
|
561
667
|
# store on the commit it held before.
|
|
562
668
|
local rows _requested url path resolved sha
|
|
563
669
|
[ -f "$root/intelligence.lock" ] || return 1
|
|
564
|
-
rows
|
|
670
|
+
_qmap_read_var rows "$root/intelligence.lock" packages || true
|
|
671
|
+
rows="$IS_QMAP_OUT"
|
|
565
672
|
while IFS="$LOCK_SEP" read -r name _requested url path resolved sha; do
|
|
566
673
|
[ -n "$name" ] || continue
|
|
567
674
|
assert_valid_pkg_name "$name"
|
|
@@ -576,5 +683,10 @@ restore_project_store_if_missing() {
|
|
|
576
683
|
project_store_missing "$root" || return 0
|
|
577
684
|
[ -f "$root/intelligence.lock" ] || die "package store is missing and intelligence.lock is absent — run 'intelligence init'"
|
|
578
685
|
echo " restoring package store from intelligence.lock"
|
|
579
|
-
|
|
686
|
+
if [ "${2:-}" = keep-sources ]; then
|
|
687
|
+
bash "$CLI_DIR/internal/restore.sh" --frozen --no-sync --keep-sources || return $?
|
|
688
|
+
else
|
|
689
|
+
bash "$CLI_DIR/internal/restore.sh" --frozen --no-sync || return $?
|
|
690
|
+
fi
|
|
691
|
+
project_reads_refresh "$root"
|
|
580
692
|
}
|