@ainova-systems/intelligence 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/cli/commands/adapter.sh +3 -3
- package/cli/commands/init.sh +6 -6
- package/cli/commands/package.sh +3 -3
- package/cli/commands/registry.sh +2 -2
- package/cli/commands/source.sh +2 -2
- package/cli/commands/sync.sh +8 -19
- package/cli/intelligence +6 -0
- package/cli/internal/check.sh +13 -7
- package/cli/lib/cli-common.sh +70 -27
- package/cli/lib/lockfile.sh +19 -7
- package/cli/lib/sync-cache.sh +162 -38
- package/cli/lib/sync-lock.sh +40 -0
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/engine/adapters/claude-plugin/hooks/register.js +3 -0
- package/engine/lib/contract.sh +2 -2
- package/engine/sync.sh +20 -11
- package/package.json +1 -1
- package/packages/sync/references/conventions.md +6 -6
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +5 -2
- package/packages/sync/skills/intelligence-sync/SKILL.md +7 -6
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ intelligence sync
|
|
|
32
32
|
|
|
33
33
|
| Command | What it does |
|
|
34
34
|
|---|---|
|
|
35
|
-
| `intelligence init [--preview\|--apply]` | Create, convert
|
|
35
|
+
| `intelligence init [--preview\|--apply]` | Create, convert or restore a project |
|
|
36
36
|
| `intelligence sync [adapter] [--compact] [--force]` | Restore locked content, then render or reuse verified unchanged output; `--force` renders and refreshes repository discovery |
|
|
37
37
|
| `intelligence update [@scope/name] [--latest] [--preview\|--apply]` | Plan or apply project and ranged-package updates; `--latest` crosses one package's range |
|
|
38
38
|
| `intelligence upgrade [--next] [--preview\|--apply]` | Replace the installed CLI with the newest version on its npm channel |
|
package/cli/commands/adapter.sh
CHANGED
|
@@ -47,7 +47,7 @@ adapter_create() {
|
|
|
47
47
|
[ -n "$name" ] && [ $# -eq 0 ] || die "usage: intelligence adapter create <name>"
|
|
48
48
|
assert_valid_target_name "$name"
|
|
49
49
|
require_cli_project
|
|
50
|
-
|
|
50
|
+
project_as_committed "$IP_ROOT"
|
|
51
51
|
local manifest="$IP_ROOT/intelligence.yaml" content_dir repo_phys probe old_ifs part probe_phys
|
|
52
52
|
local template adapter_dir adapter_phys dest tmp
|
|
53
53
|
local -a parts
|
|
@@ -95,7 +95,7 @@ adapter_remove() {
|
|
|
95
95
|
[ -n "$name" ] || die "usage: intelligence adapter remove <name> [--apply]"
|
|
96
96
|
assert_valid_target_name "$name"
|
|
97
97
|
require_cli_project
|
|
98
|
-
|
|
98
|
+
project_as_committed "$IP_ROOT"
|
|
99
99
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
100
100
|
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
101
101
|
case "$content_dir" in
|
|
@@ -140,7 +140,7 @@ case "$action" in
|
|
|
140
140
|
require_cli_project
|
|
141
141
|
# Held through the final render: target-state and sync join it.
|
|
142
142
|
project_lock_hold "$IP_ROOT"
|
|
143
|
-
|
|
143
|
+
project_as_committed "$IP_ROOT"
|
|
144
144
|
if [ "$action" = "enable" ]; then
|
|
145
145
|
state_stage="$(mktemp -d -t intelligence-adapter-state-XXXXXX)"
|
|
146
146
|
cp "$IP_ROOT/intelligence.yaml" "$state_stage/intelligence.yaml"
|
package/cli/commands/init.sh
CHANGED
|
@@ -132,7 +132,11 @@ case "$IP_MODE" in
|
|
|
132
132
|
if project_stamped_ahead "$IP_ROOT"; then
|
|
133
133
|
echo " project schema $(read_schema_version "$IP_ROOT/intelligence.yaml") is ahead of engine $(bundled_engine_version) — left as is (update the CLI)"
|
|
134
134
|
elif project_needs_upgrade "$IP_ROOT"; then
|
|
135
|
-
|
|
135
|
+
if [ "$IS_UPGRADE_KIND" = behind ]; then
|
|
136
|
+
echo " project versions trail engine $(bundled_engine_version) — kept as committed; intelligence update moves them"
|
|
137
|
+
else
|
|
138
|
+
echo " project needs a migration to engine $(bundled_engine_version) — run: intelligence update"
|
|
139
|
+
fi
|
|
136
140
|
else
|
|
137
141
|
echo " schema/content already match engine $(bundled_engine_version)"
|
|
138
142
|
fi
|
|
@@ -151,11 +155,7 @@ case "$IP_MODE" in
|
|
|
151
155
|
fi
|
|
152
156
|
exit 0
|
|
153
157
|
fi
|
|
154
|
-
|
|
155
|
-
ensure_project_current "$IP_ROOT" --explicit
|
|
156
|
-
else
|
|
157
|
-
ensure_project_current "$IP_ROOT"
|
|
158
|
-
fi
|
|
158
|
+
project_as_committed "$IP_ROOT"
|
|
159
159
|
restore_project_store_if_missing "$IP_ROOT"
|
|
160
160
|
ensure_manifest_gitignore "$IP_ROOT" "$IP_ROOT/intelligence.yaml"
|
|
161
161
|
ensure_manifest_publisher_ignores "$IP_ROOT" "$IP_ROOT/intelligence.yaml"
|
package/cli/commands/package.sh
CHANGED
|
@@ -10,17 +10,17 @@ shift
|
|
|
10
10
|
case "$action" in
|
|
11
11
|
add)
|
|
12
12
|
require_cli_project
|
|
13
|
-
|
|
13
|
+
project_as_committed "$IP_ROOT"
|
|
14
14
|
exec bash "$CLI_DIR/internal/package-add.sh" "$@"
|
|
15
15
|
;;
|
|
16
16
|
remove)
|
|
17
17
|
require_cli_project
|
|
18
|
-
|
|
18
|
+
project_as_committed "$IP_ROOT"
|
|
19
19
|
exec bash "$CLI_DIR/internal/package-remove.sh" "$@"
|
|
20
20
|
;;
|
|
21
21
|
alias)
|
|
22
22
|
require_cli_project
|
|
23
|
-
|
|
23
|
+
project_as_committed "$IP_ROOT"
|
|
24
24
|
exec bash "$CLI_DIR/internal/package-alias.sh" "$@"
|
|
25
25
|
;;
|
|
26
26
|
list)
|
package/cli/commands/registry.sh
CHANGED
|
@@ -40,7 +40,7 @@ case "$sub" in
|
|
|
40
40
|
esac
|
|
41
41
|
assert_safe_source_url "${url#git+}"
|
|
42
42
|
require_cli_project
|
|
43
|
-
|
|
43
|
+
project_as_committed "$IP_ROOT"
|
|
44
44
|
# Fail closed: a URL that cannot produce an index is not a registry,
|
|
45
45
|
# and recording it would turn a typo here into a confusing failure at
|
|
46
46
|
# the first add. --force covers the one honest case: the registry is
|
|
@@ -71,7 +71,7 @@ case "$sub" in
|
|
|
71
71
|
url="${2:-}"
|
|
72
72
|
[ -n "$url" ] || die "usage: intelligence registry remove <url>"
|
|
73
73
|
require_cli_project
|
|
74
|
-
|
|
74
|
+
project_as_committed "$IP_ROOT"
|
|
75
75
|
registries_remove "$IP_ROOT/intelligence.yaml" "$url"
|
|
76
76
|
# The retired flat-map form also stored scope keys — drop one if the
|
|
77
77
|
# argument names it, so old manifests clean up with the old command.
|
package/cli/commands/source.sh
CHANGED
|
@@ -139,7 +139,7 @@ case "$action" in
|
|
|
139
139
|
dir="$(normalize_source_dir "$dir")"
|
|
140
140
|
assert_valid_source_dir "$IP_ROOT" "$dir"
|
|
141
141
|
[ "$anchor" != "$dir" ] || die "cannot place '$dir' relative to itself"
|
|
142
|
-
|
|
142
|
+
project_as_committed "$IP_ROOT"
|
|
143
143
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
144
144
|
|
|
145
145
|
# The anchor may be spelled differently from the entry it means — a
|
|
@@ -189,7 +189,7 @@ case "$action" in
|
|
|
189
189
|
require_cli_project
|
|
190
190
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
191
191
|
assert_not_package_reference "$manifest" "$dir" remove
|
|
192
|
-
|
|
192
|
+
project_as_committed "$IP_ROOT"
|
|
193
193
|
|
|
194
194
|
if ! sources_has_entry "$manifest" "$section" "$dir"; then
|
|
195
195
|
echo "not listed: sources.$section does not hold $dir"
|
package/cli/commands/sync.sh
CHANGED
|
@@ -58,11 +58,10 @@ run_sync() {
|
|
|
58
58
|
# One writer per project at a time: the store restore and the
|
|
59
59
|
# render below both rewrite shared state (sync-lock.sh).
|
|
60
60
|
hold_project
|
|
61
|
-
# `sync` is the normal fresh-clone command.
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
ensure_project_current "$IP_ROOT" || return $?
|
|
61
|
+
# `sync` is the normal fresh-clone command. It renders the project as
|
|
62
|
+
# committed, whichever CLI runs it, and restores a missing ignored
|
|
63
|
+
# store strictly from the committed lock.
|
|
64
|
+
project_as_committed "$IP_ROOT" || return $?
|
|
66
65
|
restore_project_store_if_missing "$IP_ROOT" || return $?
|
|
67
66
|
export_engine_env "$IP_ROOT" || return $?
|
|
68
67
|
sync_with_cache "$target" "$force" || return $?
|
|
@@ -88,12 +87,10 @@ run_sync() {
|
|
|
88
87
|
esac
|
|
89
88
|
}
|
|
90
89
|
|
|
91
|
-
# run_check — the same preflight as sync,
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# allowed: it is reproducible state, and the comparison needs it.
|
|
90
|
+
# run_check — the same preflight as sync, with everything but the verdict on
|
|
91
|
+
# stderr. Restoring the ignored package store from the lock is allowed: it is
|
|
92
|
+
# reproducible state, and the comparison needs it.
|
|
95
93
|
run_check() {
|
|
96
|
-
local stamp
|
|
97
94
|
detect_project || return $?
|
|
98
95
|
case "$IP_MODE" in
|
|
99
96
|
cli) ;;
|
|
@@ -111,15 +108,7 @@ run_check() {
|
|
|
111
108
|
# shellcheck disable=SC2034 # read by sync-lock.sh when the lock is taken
|
|
112
109
|
SYNC_LOCK_KIND=check
|
|
113
110
|
hold_project
|
|
114
|
-
|
|
115
|
-
if project_needs_upgrade "$IP_ROOT"; then
|
|
116
|
-
read_schema_version_var "$IP_ROOT/intelligence.yaml"
|
|
117
|
-
stamp="$IS_SCHEMA_VERSION"
|
|
118
|
-
bundled_engine_version_var
|
|
119
|
-
IS_CHECK_OUT_OF_DATE=1
|
|
120
|
-
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"
|
|
121
|
-
return "$IS_RC_OUT_OF_DATE"
|
|
122
|
-
fi
|
|
111
|
+
project_as_committed "$IP_ROOT" || return $?
|
|
123
112
|
restore_project_store_if_missing "$IP_ROOT" keep-sources >&2 || return $?
|
|
124
113
|
export_engine_env "$IP_ROOT" || return $?
|
|
125
114
|
sync_check "$target" "$force" || return $?
|
package/cli/intelligence
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
|
|
10
|
+
# Bash keeps a PWD inherited from its parent when it names the current
|
|
11
|
+
# directory, so a Windows caller can hand over `D:/x` or `D:\x` for what every
|
|
12
|
+
# other caller sees as `/d/x`. Taken once here, one spelling gives the project
|
|
13
|
+
# one root and the CLI one location, whoever started it.
|
|
14
|
+
case "$PWD" in [A-Za-z]:*) cd -P . ;; esac
|
|
15
|
+
|
|
10
16
|
case "$0" in
|
|
11
17
|
*/*) CLI_DIR="${0%/*}"; [ -n "$CLI_DIR" ] || CLI_DIR="/" ;;
|
|
12
18
|
*) CLI_DIR="." ;;
|
package/cli/internal/check.sh
CHANGED
|
@@ -41,7 +41,7 @@ fi
|
|
|
41
41
|
stamp="$(read_schema_version "$manifest")"
|
|
42
42
|
eng="$(bundled_engine_version)"
|
|
43
43
|
if [ -z "$stamp" ]; then
|
|
44
|
-
warn "manifest has no schema_version — run 'intelligence
|
|
44
|
+
warn "manifest has no schema_version — run 'intelligence update'"
|
|
45
45
|
elif _ver_gt "$stamp" "$eng"; then
|
|
46
46
|
if [ "$(_ver_major "$stamp")" -gt "$(_ver_major "$eng")" ]; then
|
|
47
47
|
warn "manifest schema $stamp is a newer major than this CLI's engine $eng — update the CLI: intelligence upgrade (--next for a prerelease line)"
|
|
@@ -49,13 +49,17 @@ elif _ver_gt "$stamp" "$eng"; then
|
|
|
49
49
|
note "manifest schema $stamp is newer than this CLI's engine $eng — the project uses a newer CLI; update it: intelligence upgrade (--next for a prerelease line)"
|
|
50
50
|
fi
|
|
51
51
|
elif _ver_gt "$eng" "$stamp"; then
|
|
52
|
-
|
|
52
|
+
if [ "$(_ver_major "$stamp")" = "$(_ver_major "$eng")" ]; then
|
|
53
|
+
note "manifest schema $stamp is older than this CLI's engine $eng — kept as committed; move it with: intelligence update"
|
|
54
|
+
else
|
|
55
|
+
warn "manifest schema $stamp is an older major than this CLI's engine $eng — run 'intelligence update'"
|
|
56
|
+
fi
|
|
53
57
|
else
|
|
54
58
|
ok "schema_version $stamp matches the engine"
|
|
55
59
|
fi
|
|
56
60
|
|
|
57
|
-
# The engine-content package
|
|
58
|
-
#
|
|
61
|
+
# The engine-content package follows the project's own version: the stamp,
|
|
62
|
+
# not the CLI, decides which content renders (decision 0023).
|
|
59
63
|
sync_locked=""
|
|
60
64
|
if [ "$lock_valid" -eq 1 ]; then
|
|
61
65
|
sync_locked="$(qmap_field "$lock" "packages" "$SYNC_PKG_NAME" "resolved")"
|
|
@@ -65,12 +69,14 @@ if [ -n "$sync_locked" ]; then
|
|
|
65
69
|
ok "$SYNC_PKG_NAME at $sync_locked (matches the engine)"
|
|
66
70
|
elif [ "${sync_locked#v}" = "$stamp" ] && _ver_gt "$stamp" "$eng"; then
|
|
67
71
|
note "$SYNC_PKG_NAME at $sync_locked (matches the project schema; the engine is $eng)"
|
|
72
|
+
elif [ "${sync_locked#v}" = "$stamp" ] && [ "$(_ver_major "$stamp")" = "$(_ver_major "$eng")" ]; then
|
|
73
|
+
note "$SYNC_PKG_NAME at $sync_locked (matches the project schema; intelligence update moves it to $eng)"
|
|
68
74
|
else
|
|
69
|
-
warn "$SYNC_PKG_NAME locked at $sync_locked but the engine is $eng — run 'intelligence
|
|
75
|
+
warn "$SYNC_PKG_NAME locked at $sync_locked but the engine is $eng — run 'intelligence update'"
|
|
70
76
|
fi
|
|
71
77
|
fi
|
|
72
78
|
if [ -d "$IP_ROOT/.intelligence/engine" ]; then
|
|
73
|
-
warn "stale .intelligence/engine directory (pre-package layout) — run 'intelligence
|
|
79
|
+
warn "stale .intelligence/engine directory (pre-package layout) — run 'intelligence update'"
|
|
74
80
|
fi
|
|
75
81
|
|
|
76
82
|
# Manifest packages: names valid, requested intent only, locked and installed.
|
|
@@ -96,7 +102,7 @@ while IFS= read -r name; do
|
|
|
96
102
|
|| warn "$name pins ref '$m_ref' but the lock resolved '$l_res'"
|
|
97
103
|
if [ -n "$(qmap_field "$manifest" "packages" "$name" "url")" ] \
|
|
98
104
|
|| [ -n "$(qmap_field "$manifest" "packages" "$name" "path")" ]; then
|
|
99
|
-
warn "$name stores source details in the manifest — run 'intelligence
|
|
105
|
+
warn "$name stores source details in the manifest — run 'intelligence update' to move them to the lock-only model"
|
|
100
106
|
fi
|
|
101
107
|
if [ ! -d "$IP_ROOT/.intelligence/packages/$name" ]; then
|
|
102
108
|
warn "$name is locked but not installed — run 'intelligence sync'"
|
package/cli/lib/cli-common.sh
CHANGED
|
@@ -344,17 +344,10 @@ export_engine_env() {
|
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
# --- Project lifecycle preflight -----------------------------------------
|
|
347
|
-
# Public commands are intentionally few. They share this state gate
|
|
348
|
-
#
|
|
349
|
-
#
|
|
350
|
-
#
|
|
351
|
-
|
|
352
|
-
is_ci_environment() {
|
|
353
|
-
case "${CI:-}" in
|
|
354
|
-
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
|
355
|
-
*) return 1 ;;
|
|
356
|
-
esac
|
|
357
|
-
}
|
|
347
|
+
# Public commands are intentionally few. They share this state gate. Only
|
|
348
|
+
# `update` moves a project's versions forward (decision 0023); every other
|
|
349
|
+
# command works on the project as committed, or refuses one that needs a
|
|
350
|
+
# migration first.
|
|
358
351
|
|
|
359
352
|
# Validate a repository-relative project content directory, including every
|
|
360
353
|
# existing symlinked path component. Call before writing a new manifest.
|
|
@@ -395,23 +388,37 @@ project_stamped_ahead() {
|
|
|
395
388
|
[ -n "$stamp" ] && _ver_gt "$stamp" "$IS_BUNDLED_ENGINE_VERSION"
|
|
396
389
|
}
|
|
397
390
|
|
|
391
|
+
# project_needs_upgrade <root> — true when `update` would align the project.
|
|
392
|
+
# IS_UPGRADE_KIND says how far behind it is: `behind` when only the schema
|
|
393
|
+
# stamp or the sync content pin trails this engine within its major, which
|
|
394
|
+
# every command renders around, with IS_UPGRADE_DESC naming what trails;
|
|
395
|
+
# `migrate` when the project needs a change only alignment applies (no stamp,
|
|
396
|
+
# an older major, a legacy manifest shape, a sync content pin that is no
|
|
397
|
+
# version).
|
|
398
398
|
project_needs_upgrade() {
|
|
399
399
|
local root="$1" manifest="$1/intelligence.yaml" stamp eng pinned locked name url path rows
|
|
400
|
-
local lname _requested _url _path resolved _sha
|
|
400
|
+
local lname _requested _url _path resolved _sha behind=0 pin_behind="" value
|
|
401
|
+
IS_UPGRADE_KIND="" IS_UPGRADE_DESC=""
|
|
401
402
|
[ -f "$manifest" ] || return 1
|
|
402
403
|
read_schema_version_var "$manifest"
|
|
403
404
|
stamp="$IS_SCHEMA_VERSION"
|
|
404
405
|
bundled_engine_version_var
|
|
405
406
|
eng="$IS_BUNDLED_ENGINE_VERSION"
|
|
406
|
-
[ -z "$stamp" ]
|
|
407
|
+
if [ -z "$stamp" ]; then IS_UPGRADE_KIND=migrate; return 0; fi
|
|
407
408
|
# A project stamped ahead belongs to a newer CLI. Aligning it here would
|
|
408
409
|
# restamp the schema and re-pin the engine content DOWNWARD, and the next
|
|
409
410
|
# teammate on the current CLI would move both back: leave it as found.
|
|
410
411
|
project_stamped_ahead "$root" && return 1
|
|
411
412
|
_qmap_read_var top "$manifest" '' sync_version || true
|
|
412
|
-
[ -
|
|
413
|
-
|
|
414
|
-
|
|
413
|
+
if [ -n "$IS_QMAP_OUT" ] || [ -d "$root/.intelligence/engine" ]; then
|
|
414
|
+
IS_UPGRADE_KIND=migrate; return 0
|
|
415
|
+
fi
|
|
416
|
+
if _ver_gt "$eng" "$stamp"; then
|
|
417
|
+
if [ "$(_ver_major "$stamp")" != "$(_ver_major "$eng")" ]; then
|
|
418
|
+
IS_UPGRADE_KIND=migrate; return 0
|
|
419
|
+
fi
|
|
420
|
+
behind=1
|
|
421
|
+
fi
|
|
415
422
|
|
|
416
423
|
# One manifest pass for every package's fields, not a reader per field.
|
|
417
424
|
_qmap_read_var fieldrows "$manifest" packages '' 'url path version' || true
|
|
@@ -420,8 +427,7 @@ project_needs_upgrade() {
|
|
|
420
427
|
[ -n "$name" ] || continue
|
|
421
428
|
# Early RC manifests mixed requested intent with resolved source
|
|
422
429
|
# details. Source URL/path now live only in the required lockfile.
|
|
423
|
-
[ -n "$url" ]
|
|
424
|
-
[ -n "$path" ] && return 0
|
|
430
|
+
if [ -n "$url" ] || [ -n "$path" ]; then IS_UPGRADE_KIND=migrate; return 0; fi
|
|
425
431
|
if [ "$name" = "$SYNC_PKG_NAME" ]; then
|
|
426
432
|
# The first lock row for the name: what qmap_field would decode.
|
|
427
433
|
locked=""
|
|
@@ -431,14 +437,52 @@ project_needs_upgrade() {
|
|
|
431
437
|
locked="$resolved"
|
|
432
438
|
break
|
|
433
439
|
done <<< "$IS_QMAP_OUT"
|
|
434
|
-
[ "$
|
|
435
|
-
|
|
436
|
-
[ -n "$locked" ] || return 0
|
|
437
|
-
[ "${locked#v}" = "$eng" ] || return 0
|
|
440
|
+
if [ -f "$root/intelligence.lock" ] && [ -z "$locked" ]; then
|
|
441
|
+
IS_UPGRADE_KIND=migrate; return 0
|
|
438
442
|
fi
|
|
443
|
+
# The manifest pin, then the lock row. One ahead of this engine is
|
|
444
|
+
# a newer CLI's, left as found like a stamp ahead (decision 0005).
|
|
445
|
+
for value in "$pinned" "${locked#v}"; do
|
|
446
|
+
[ -n "$value" ] && [ "$value" != "$eng" ] || continue
|
|
447
|
+
case "$value" in [0-9]*.[0-9]*.[0-9]*) ;; *) IS_UPGRADE_KIND=migrate; return 0 ;; esac
|
|
448
|
+
_ver_gt "$eng" "$value" || continue
|
|
449
|
+
[ "$(_ver_major "$value")" = "$(_ver_major "$eng")" ] || { IS_UPGRADE_KIND=migrate; return 0; }
|
|
450
|
+
behind=1
|
|
451
|
+
[ -n "$pin_behind" ] || pin_behind="$value"
|
|
452
|
+
done
|
|
439
453
|
fi
|
|
440
454
|
done <<< "$rows"
|
|
441
|
-
return 1
|
|
455
|
+
[ "$behind" = 1 ] || return 1
|
|
456
|
+
IS_UPGRADE_KIND=behind
|
|
457
|
+
# What the warning names: only the versions that actually trail.
|
|
458
|
+
if _ver_gt "$eng" "$stamp"; then IS_UPGRADE_DESC="schema $stamp"; fi
|
|
459
|
+
[ -z "$pin_behind" ] || IS_UPGRADE_DESC="${IS_UPGRADE_DESC:+$IS_UPGRADE_DESC, }$SYNC_PKG_NAME $pin_behind"
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
# project_as_committed <root> — the gate of every project-aware command but
|
|
463
|
+
# `update`: the preflight, then the project as committed. One whose versions
|
|
464
|
+
# only trail this engine within its major proceeds behind one warning and
|
|
465
|
+
# keeps its tracked bytes; one that needs a migration is refused with
|
|
466
|
+
# IS_STATUS=needs-update and exit 6.
|
|
467
|
+
project_as_committed() {
|
|
468
|
+
local root="$1" stamp
|
|
469
|
+
project_preflight "$root" || return $?
|
|
470
|
+
project_needs_upgrade "$root" || return 0
|
|
471
|
+
read_schema_version_var "$root/intelligence.yaml"
|
|
472
|
+
stamp="$IS_SCHEMA_VERSION"
|
|
473
|
+
bundled_engine_version_var
|
|
474
|
+
if [ "$IS_UPGRADE_KIND" = behind ]; then
|
|
475
|
+
# Once per command, however many processes check on the way to the
|
|
476
|
+
# engine; unindented, so `sync --compact` keeps it.
|
|
477
|
+
if [ "${IS_PROJECT_BEHIND_WARNED:-}" != "$stamp" ]; then
|
|
478
|
+
echo "WARNING: project versions ($IS_UPGRADE_DESC) trail this CLI's engine $IS_BUNDLED_ENGINE_VERSION — they stay as committed; move them with: intelligence update" >&2
|
|
479
|
+
export IS_PROJECT_BEHIND_WARNED="$stamp"
|
|
480
|
+
fi
|
|
481
|
+
return 0
|
|
482
|
+
fi
|
|
483
|
+
is_status needs-update "stamp=${stamp:-unstamped} engine=$IS_BUNDLED_ENGINE_VERSION"
|
|
484
|
+
echo "ERROR: the project needs a migration this command does not apply (schema ${stamp:-unstamped}, engine $IS_BUNDLED_ENGINE_VERSION) — run: intelligence update" >&2
|
|
485
|
+
return "$IS_RC_NEEDS_UPDATE"
|
|
442
486
|
}
|
|
443
487
|
|
|
444
488
|
# A manifest with packages but no lock has no trustworthy resolved state from
|
|
@@ -466,17 +510,16 @@ project_preflight() {
|
|
|
466
510
|
fi
|
|
467
511
|
}
|
|
468
512
|
|
|
513
|
+
# ensure_project_current <root> — align the project to this engine. `update`
|
|
514
|
+
# alone calls it (decision 0023).
|
|
469
515
|
ensure_project_current() {
|
|
470
|
-
local root="$1"
|
|
516
|
+
local root="$1" manifest="$1/intelligence.yaml" stamp eng
|
|
471
517
|
project_preflight "$root" || return $?
|
|
472
518
|
project_needs_upgrade "$root" || return 0
|
|
473
519
|
read_schema_version_var "$manifest"
|
|
474
520
|
stamp="$IS_SCHEMA_VERSION"
|
|
475
521
|
bundled_engine_version_var
|
|
476
522
|
eng="$IS_BUNDLED_ENGINE_VERSION"
|
|
477
|
-
if is_ci_environment && [ "$explicit" != "--explicit" ]; then
|
|
478
|
-
die "project lifecycle requires alignment (stamp ${stamp:-unstamped}, engine $eng) — run 'intelligence init --apply' locally, review and commit the diff"
|
|
479
|
-
fi
|
|
480
523
|
echo " project alignment: stamp ${stamp:-unstamped}, engine $eng"
|
|
481
524
|
bash "$CLI_DIR/internal/align-project.sh" --no-sync || return $?
|
|
482
525
|
project_reads_refresh "$root"
|
package/cli/lib/lockfile.sh
CHANGED
|
@@ -70,6 +70,15 @@ lock_validate_checks() {
|
|
|
70
70
|
continue
|
|
71
71
|
fi
|
|
72
72
|
fi
|
|
73
|
+
if [ -z "$sha" ] && [ "$url" = "$SYNC_PKG_URL" ] && [ "$path" = "$SYNC_PKG_PATH" ]; then
|
|
74
|
+
# Bundle metadata pins the content a CLI ships; only this CLI's own
|
|
75
|
+
# version restores without a commit. update moves an older pin to it;
|
|
76
|
+
# a newer one needs a newer CLI.
|
|
77
|
+
if [[ "$resolved" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] && _ver_gt "${resolved#v}" "$current"; then
|
|
78
|
+
die "locked restore: $name $resolved has a missing or invalid commit SHA, and this CLI bundles v$current — update the CLI: intelligence upgrade"
|
|
79
|
+
fi
|
|
80
|
+
die "locked restore: $name $resolved has a missing or invalid commit SHA, and this CLI bundles v$current — run: intelligence update"
|
|
81
|
+
fi
|
|
73
82
|
[[ "$sha" =~ ^[0-9a-f]{40}$ || "$sha" =~ ^[0-9a-f]{64}$ ]] \
|
|
74
83
|
|| die "locked restore: $name has a missing or invalid commit SHA — restore a valid intelligence.lock"
|
|
75
84
|
done <<< "$rows"
|
|
@@ -128,16 +137,19 @@ lock_write_from_tsv() {
|
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
# lock_upsert <lock> <name> <requested> <url> <path> <resolved> <sha>
|
|
131
|
-
# Replace
|
|
140
|
+
# Replace one entry where it stands, or append a new one, keeping the others:
|
|
141
|
+
# an update changes only that package's lines in the committed lock.
|
|
132
142
|
lock_upsert() {
|
|
133
143
|
local lock="$1" name="$2" requested="$3" url="$4" path="$5" resolved="$6" sha="$7"
|
|
134
|
-
local tsv
|
|
144
|
+
local tsv row
|
|
135
145
|
tsv="$(mktemp -t intelligence-lock-XXXXXX 2>/dev/null || mktemp)"
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
printf -v row '%s\037%s\037%s\037%s\037%s\037%s' \
|
|
147
|
+
"$name" "$requested" "$url" "$path" "$resolved" "$sha"
|
|
148
|
+
lock_to_tsv "$lock" | IS_LOCK_ROW="$row" awk -F"$LOCK_SEP" -v n="$name" '
|
|
149
|
+
$1 == n { if (!done) print ENVIRON["IS_LOCK_ROW"]; done = 1; next }
|
|
150
|
+
{ print }
|
|
151
|
+
END { if (!done) print ENVIRON["IS_LOCK_ROW"] }
|
|
152
|
+
' > "$tsv"
|
|
141
153
|
lock_write_from_tsv "$lock" "$tsv"
|
|
142
154
|
rm -f "$tsv"
|
|
143
155
|
}
|
package/cli/lib/sync-cache.sh
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
#
|
|
5
5
|
# The record keeps three fingerprints apart (decision 0018), so a check can say
|
|
6
6
|
# which side moved:
|
|
7
|
-
# tooling the CLI and engine code
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# than answer "sync needed" from it.
|
|
7
|
+
# tooling the CLI and engine code and the adapter filter. A change here is
|
|
8
|
+
# not the project's: a check renders rather than answer "sync
|
|
9
|
+
# needed" from it.
|
|
11
10
|
# inputs the manifest, the lock, every configured source including skill
|
|
12
11
|
# resources and installed packages, and the project adapter folder.
|
|
13
12
|
# outputs every path the enabled built-in adapters own or manage.
|
|
14
|
-
# Each fingerprint lists names, entry kinds and executable state beside
|
|
15
|
-
# hashes; timestamps
|
|
13
|
+
# Each fingerprint lists relative names, entry kinds and executable state beside
|
|
14
|
+
# content hashes; timestamps, the project's location and the caller's
|
|
15
|
+
# environment never count. `.intelligence/sync-cache/state` holds the
|
|
16
16
|
# record and the report a hit replays; `seal` holds the state file's own hash,
|
|
17
17
|
# so a record is trusted only whole.
|
|
18
18
|
|
|
19
|
-
SC_FORMAT='intelligence-sync-cache-
|
|
19
|
+
SC_FORMAT='intelligence-sync-cache-v3'
|
|
20
20
|
SC_SAFE_SEEN=$'\n'
|
|
21
21
|
|
|
22
22
|
sync_cache_safe_path() {
|
|
@@ -155,6 +155,19 @@ sync_cache_dependencies() {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
# --- Fingerprint ---------------------------------------------------------------
|
|
158
|
+
# sync_cache_label_var <path> — IS_SC_LABEL, the name a fingerprint records for
|
|
159
|
+
# a path: relative to the engine or CLI directory for tooling, to the project
|
|
160
|
+
# otherwise. Where the project or the CLI lives, and how that path is spelled,
|
|
161
|
+
# never decides whether the same files are current.
|
|
162
|
+
sync_cache_label_var() {
|
|
163
|
+
case "$1" in
|
|
164
|
+
"$IS_ENGINE_DIR"|"$IS_ENGINE_DIR"/*) IS_SC_LABEL="engine${1#"$IS_ENGINE_DIR"}" ;;
|
|
165
|
+
"$CLI_DIR"|"$CLI_DIR"/*) IS_SC_LABEL="cli${1#"$CLI_DIR"}" ;;
|
|
166
|
+
"$REPO_ROOT"/*) IS_SC_LABEL="${1#"$REPO_ROOT/"}" ;;
|
|
167
|
+
*) IS_SC_LABEL="$1" ;;
|
|
168
|
+
esac
|
|
169
|
+
}
|
|
170
|
+
|
|
158
171
|
# _sync_cache_starts <group> <path>... — queue a group's start points. A missing
|
|
159
172
|
# one is recorded as missing; a repeated one is walked once.
|
|
160
173
|
_sync_cache_starts() {
|
|
@@ -171,7 +184,8 @@ _sync_cache_starts() {
|
|
|
171
184
|
SC_STARTS+=("$path")
|
|
172
185
|
SC_START_GROUPS+=("$group")
|
|
173
186
|
else
|
|
174
|
-
|
|
187
|
+
sync_cache_label_var "$path"
|
|
188
|
+
SC_MISSING+=("$group missing $IS_SC_LABEL")
|
|
175
189
|
fi
|
|
176
190
|
done
|
|
177
191
|
}
|
|
@@ -190,7 +204,7 @@ _SC_AWK_LISTING='
|
|
|
190
204
|
starts = split(ENVIRON["SC_AWK_STARTS"], rows, "\n")
|
|
191
205
|
for (i = 1; i <= starts; i++) {
|
|
192
206
|
split(rows[i], field, "\t")
|
|
193
|
-
group[i] = field[1]; posix[i] = field[2]; native[i] = field[3]
|
|
207
|
+
group[i] = field[1]; posix[i] = field[2]; native[i] = field[3]; label[i] = field[4]
|
|
194
208
|
}
|
|
195
209
|
probe = ENVIRON["SC_AWK_EXEC_PROBE"]
|
|
196
210
|
stream = (ENVIRON["SC_AWK_STREAM"] == 1)
|
|
@@ -209,7 +223,7 @@ _SC_AWK_LISTING='
|
|
|
209
223
|
if (!last) { bad = 1; return }
|
|
210
224
|
}
|
|
211
225
|
g = group[last]
|
|
212
|
-
listing[g] = listing[g] kind " " entry "\037"
|
|
226
|
+
listing[g] = listing[g] kind " " label[last] substr(entry, length(posix[last]) + 1) "\037"
|
|
213
227
|
if (kind == "d") return
|
|
214
228
|
k = ++files[g]
|
|
215
229
|
path = native[last] substr(entry, length(posix[last]) + 1)
|
|
@@ -248,7 +262,7 @@ _SC_AWK_LISTING='
|
|
|
248
262
|
# the listing stage tells kinds without a stat per entry. git hashes the files
|
|
249
263
|
# while the walk is still running.
|
|
250
264
|
sync_cache_fingerprint() {
|
|
251
|
-
local target="$1" hashes i j n value ordered=0 starts_env="" state=""
|
|
265
|
+
local target="$1" hashes i j n value ordered=0 starts_env="" state=""
|
|
252
266
|
local tooling inputs outputs listing saved_ifs="$IFS"
|
|
253
267
|
local -a hash_lines=() expression=() keep=() starts=() groups=() natives=() parts=() count=()
|
|
254
268
|
SC_STARTS=() SC_START_GROUPS=() SC_MISSING=() SC_START_SEEN=$'\n'
|
|
@@ -299,7 +313,8 @@ sync_cache_fingerprint() {
|
|
|
299
313
|
for ((j = 0; j < ${#starts[@]}; j++)); do
|
|
300
314
|
value="${starts[j]}"
|
|
301
315
|
[ "$SC_TRANSLATE" != 1 ] || value="${natives[j]}"
|
|
302
|
-
|
|
316
|
+
sync_cache_label_var "${starts[j]}"
|
|
317
|
+
starts_env="$starts_env${groups[j]}"$'\t'"${starts[j]}"$'\t'"$value"$'\t'"$IS_SC_LABEL"$'\n'
|
|
303
318
|
done
|
|
304
319
|
starts_env="${starts_env%$'\n'}"
|
|
305
320
|
expression=(\( ! -type d ! -type f -exec false {} + \)
|
|
@@ -359,15 +374,11 @@ sync_cache_fingerprint() {
|
|
|
359
374
|
[ "${count[0]}" -eq 0 ] || { printf -v value '%s\037' "${hash_lines[@]:0:${count[0]}}"; tooling="$tooling$value"; }
|
|
360
375
|
[ "${count[1]}" -eq 0 ] || { printf -v value '%s\037' "${hash_lines[@]:${count[0]}:${count[1]}}"; inputs="$inputs$value"; }
|
|
361
376
|
[ "${count[2]}" -eq 0 ] || { printf -v value '%s\037' "${hash_lines[@]:$((count[0] + count[1])):${count[2]}}"; outputs="$outputs$value"; }
|
|
362
|
-
#
|
|
363
|
-
#
|
|
364
|
-
#
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
for value in "$target" "$REPO_ROOT" "$CONFIG_FILE" "$IS_CONTENT_REL" "$IS_MODULE_REL" \
|
|
368
|
-
"$IS_SYNC_CMD" "$IS_MANIFEST_NAME" "$IS_PROTECTED_DIRS" "$BASH_VERSION" "${OSTYPE:-}" \
|
|
369
|
-
"${LANG:-}" "${LC_ALL:-}" "${LC_CTYPE:-}" "${LC_COLLATE:-}" "${PATH:-}" "$umask_value" \
|
|
370
|
-
"exec-probe=$SC_EXEC_PROBE" "walk-ordered=$ordered"; do
|
|
377
|
+
# The adapter filter selects what renders; the two listing flags say how
|
|
378
|
+
# the listings above were made. Nothing else of the invocation counts:
|
|
379
|
+
# the code, the manifest and the files decide what sync writes, wherever
|
|
380
|
+
# the project lives and whoever starts the CLI.
|
|
381
|
+
for value in "filter=$target" "exec-probe=$SC_EXEC_PROBE" "walk-ordered=$ordered"; do
|
|
371
382
|
case "$value" in *[[:cntrl:]]*) return 1 ;; esac
|
|
372
383
|
tooling="${tooling}env $value"$'\037'
|
|
373
384
|
done
|
|
@@ -514,9 +525,15 @@ sync_with_cache() {
|
|
|
514
525
|
|
|
515
526
|
# The write contract marks links whose spelling must remain unchanged: they
|
|
516
527
|
# are compared, never dereferenced, and are not renderer read inputs.
|
|
528
|
+
#
|
|
529
|
+
# SC_CHECK_OUTPUTS holds the selected adapters' paths; SC_CHECK_ALL_OUTPUTS
|
|
530
|
+
# every enabled adapter's, since a filtered render still reads what the others
|
|
531
|
+
# generated. SC_CHECK_PROJECT_ADAPTER is 1 when an enabled adapter is the
|
|
532
|
+
# project's own code.
|
|
517
533
|
sync_check_write_paths() {
|
|
518
534
|
local filter="$1" dir file name i output adapter kind value
|
|
519
535
|
local -a names=() files=() batch=()
|
|
536
|
+
SC_CHECK_PROJECT_ADAPTER=0
|
|
520
537
|
for dir in "$IS_ENGINE_DIR/adapters" "$REPO_ROOT/$IS_CONTENT_REL/adapters"; do
|
|
521
538
|
for file in "$dir"/*.sh; do
|
|
522
539
|
[ -f "$file" ] || continue
|
|
@@ -530,41 +547,142 @@ sync_check_write_paths() {
|
|
|
530
547
|
i=0
|
|
531
548
|
while [ "$i" -lt "${#names[@]}" ]; do
|
|
532
549
|
name="${names[i]}"; file="${files[i]}"; i=$((i + 1))
|
|
533
|
-
[ -z "$filter" ] || [ "$name" = "$filter" ] || continue
|
|
534
550
|
target_enabled_var "$CONFIG_FILE" "$name"
|
|
535
551
|
[ "$IS_TGT_ENABLED" = 1 ] || continue
|
|
552
|
+
case "$file" in "$REPO_ROOT/$IS_CONTENT_REL/adapters/"*) SC_CHECK_PROJECT_ADAPTER=1 ;; esac
|
|
536
553
|
target_output_var "$CONFIG_FILE" "$name"
|
|
537
554
|
output="${IS_TGT_OUTPUT:-.$name}"
|
|
538
555
|
batch+=("$name" "$file" "$output")
|
|
539
556
|
done
|
|
540
|
-
SC_CHECK_OUTPUTS=()
|
|
557
|
+
SC_CHECK_OUTPUTS=() SC_CHECK_ALL_OUTPUTS=()
|
|
541
558
|
[ "${#batch[@]}" -gt 0 ] || return 0
|
|
542
559
|
adapter_contract_records_batch "$SC_SCRATCH.check-contract" "${batch[@]}" > "$SC_SCRATCH.check-records" || return 1
|
|
543
560
|
while IFS=$'\t' read -r adapter kind value; do
|
|
544
|
-
case "$kind" in
|
|
561
|
+
case "$kind" in
|
|
562
|
+
owned|managed)
|
|
563
|
+
SC_CHECK_ALL_OUTPUTS+=("$value")
|
|
564
|
+
[ -z "$filter" ] || [ "$adapter" = "$filter" ] || continue
|
|
565
|
+
SC_CHECK_OUTPUTS+=("$value")
|
|
566
|
+
;;
|
|
567
|
+
esac
|
|
545
568
|
done < "$SC_SCRATCH.check-records"
|
|
546
569
|
}
|
|
547
570
|
|
|
571
|
+
# sync_check_unlinked_path <rel> — true when no existing component of the
|
|
572
|
+
# project path, the last included, is a link. Copying such a path alone would
|
|
573
|
+
# leave its link pointing at something the copy does not hold.
|
|
574
|
+
sync_check_unlinked_path() {
|
|
575
|
+
local rest="$1" probe="$REPO_ROOT" part
|
|
576
|
+
while [ -n "$rest" ]; do
|
|
577
|
+
part="${rest%%/*}"
|
|
578
|
+
if [ "$part" = "$rest" ]; then rest=""; else rest="${rest#*/}"; fi
|
|
579
|
+
probe="$probe/$part"
|
|
580
|
+
[ ! -L "$probe" ] || return 1
|
|
581
|
+
[ -e "$probe" ] || return 0
|
|
582
|
+
done
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
# sync_check_copy_paths — SC_CHECK_COPY, the existing project paths a render
|
|
586
|
+
# by built-in adapters reads or writes, none inside another. Return 1 when only
|
|
587
|
+
# a copy of the whole project is faithful: a project adapter may read any file,
|
|
588
|
+
# and a link on the way to an input may point anywhere in the project.
|
|
589
|
+
# Everything else the engine reads in the project is a directory listing the
|
|
590
|
+
# check takes from the live tree (warn_unsynced).
|
|
591
|
+
sync_check_copy_paths() {
|
|
592
|
+
local section src rel other kept
|
|
593
|
+
local -a wanted=("${CONFIG_FILE#"$REPO_ROOT/"}" intelligence.lock .intelligence/packages
|
|
594
|
+
"$IS_CONTENT_REL/adapters" .agents/rules .agents/agents)
|
|
595
|
+
SC_CHECK_COPY=()
|
|
596
|
+
[ "$SC_CHECK_PROJECT_ADAPTER" = 0 ] || return 1
|
|
597
|
+
for section in rules agents skills; do
|
|
598
|
+
load_yaml_list "$CONFIG_FILE" "$section"
|
|
599
|
+
while IFS= read -r src; do
|
|
600
|
+
case "$src" in
|
|
601
|
+
''|/*|..|../*|*/..|*/../*) continue ;; # Outside the project: read where it is.
|
|
602
|
+
esac
|
|
603
|
+
wanted+=("$src")
|
|
604
|
+
done <<< "$IS_YAML_LIST"
|
|
605
|
+
done
|
|
606
|
+
wanted+=(${SC_CHECK_ALL_OUTPUTS[@]+"${SC_CHECK_ALL_OUTPUTS[@]}"})
|
|
607
|
+
for rel in "${wanted[@]}"; do
|
|
608
|
+
normalize_path_var "/$rel"
|
|
609
|
+
rel="${IS_NORM_PATH#/}"
|
|
610
|
+
case "$rel" in ''|.git|.git/*) return 1 ;; esac
|
|
611
|
+
sync_check_unlinked_path "$rel" || return 1
|
|
612
|
+
[ -e "$REPO_ROOT/$rel" ] || continue
|
|
613
|
+
kept=()
|
|
614
|
+
for other in ${SC_CHECK_COPY[@]+"${SC_CHECK_COPY[@]}"}; do
|
|
615
|
+
case "$rel" in "$other"|"$other"/*) continue 2 ;; esac
|
|
616
|
+
case "$other" in "$rel"/*) continue ;; esac
|
|
617
|
+
kept+=("$other")
|
|
618
|
+
done
|
|
619
|
+
SC_CHECK_COPY=(${kept[@]+"${kept[@]}"} "$rel")
|
|
620
|
+
done
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
# sync_check_holds <private-project> <rel> — true when the private copy holds
|
|
624
|
+
# the project path: inside a copied entry, never a directory made only as the
|
|
625
|
+
# parent of one. An empty SC_CHECK_COPY means the whole project was copied.
|
|
626
|
+
sync_check_holds() {
|
|
627
|
+
local entry
|
|
628
|
+
[ -e "$1/$2" ] || [ -L "$1/$2" ] || return 1
|
|
629
|
+
[ "${#SC_CHECK_COPY[@]}" -gt 0 ] || return 0
|
|
630
|
+
for entry in "${SC_CHECK_COPY[@]}"; do
|
|
631
|
+
case "$2" in "$entry"|"$entry"/*) return 0 ;; esac
|
|
632
|
+
done
|
|
633
|
+
return 1
|
|
634
|
+
}
|
|
635
|
+
|
|
548
636
|
# Check renders use a private project tree. A rollback in the live tree would
|
|
549
|
-
# overwrite unrelated edits made while the renderer was running.
|
|
637
|
+
# overwrite unrelated edits made while the renderer was running. The tree holds
|
|
638
|
+
# only what the render reads and writes: a copy of the whole project took
|
|
639
|
+
# minutes and gigabytes where dependency and build trees sit beside the sources.
|
|
550
640
|
sync_check_render() (
|
|
551
|
-
local target="$1" verdict="$2" staging entry link target_link manifest_rel rel output is_output original_target original_root="$REPO_ROOT" rc=0
|
|
552
|
-
local -a SC_CHECK_OUTPUTS=()
|
|
641
|
+
local target="$1" verdict="$2" staging entry link target_link manifest_rel rel output is_output original_target original_root="$REPO_ROOT" rc=0 parent
|
|
642
|
+
local -a SC_CHECK_OUTPUTS=() SC_CHECK_ALL_OUTPUTS=() SC_CHECK_COPY=() parents=() batch=()
|
|
643
|
+
local SC_CHECK_PROJECT_ADAPTER=0
|
|
553
644
|
sync_check_write_paths "$target" || return 1
|
|
554
645
|
manifest_rel="${CONFIG_FILE#"$REPO_ROOT/"}"
|
|
555
646
|
staging="$(mktemp -d -t intelligence-check-XXXXXX)" || return 1
|
|
556
647
|
trap 'rm -rf "$staging"' EXIT
|
|
557
648
|
trap 'exit 130' INT
|
|
558
649
|
trap 'exit 143' TERM
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
650
|
+
# A run killed outright never reaches its trap; the lock's release or
|
|
651
|
+
# takeover removes the copy it names here.
|
|
652
|
+
: > "$staging/.intelligence-check" || return 1
|
|
653
|
+
printf '%s\n' "$staging" > "$SC_SCRATCH.tmpdir" || return 1
|
|
654
|
+
mkdir "$staging/project" || return 1
|
|
655
|
+
if sync_check_copy_paths; then
|
|
656
|
+
# One copy per parent directory: on Git Bash each process costs tens
|
|
657
|
+
# of milliseconds.
|
|
658
|
+
for rel in ${SC_CHECK_COPY[@]+"${SC_CHECK_COPY[@]}"}; do
|
|
659
|
+
parent=.
|
|
660
|
+
case "$rel" in */*) parent="${rel%/*}" ;; esac
|
|
661
|
+
for entry in ${parents[@]+"${parents[@]}"}; do
|
|
662
|
+
[ "$entry" != "$parent" ] || continue 2
|
|
663
|
+
done
|
|
664
|
+
parents+=("$parent")
|
|
665
|
+
done
|
|
666
|
+
for parent in ${parents[@]+"${parents[@]}"}; do
|
|
667
|
+
batch=()
|
|
668
|
+
for rel in "${SC_CHECK_COPY[@]}"; do
|
|
669
|
+
entry=.
|
|
670
|
+
case "$rel" in */*) entry="${rel%/*}" ;; esac
|
|
671
|
+
[ "$entry" != "$parent" ] || batch+=("$REPO_ROOT/$rel")
|
|
672
|
+
done
|
|
673
|
+
mkdir -p "$staging/project/$parent" && cp -a "${batch[@]}" "$staging/project/$parent/" || { rc=1; break; }
|
|
674
|
+
done
|
|
675
|
+
else
|
|
676
|
+
SC_CHECK_COPY=()
|
|
677
|
+
for entry in "$REPO_ROOT"/* "$REPO_ROOT"/.[!.]* "$REPO_ROOT"/..?*; do
|
|
678
|
+
[ -e "$entry" ] || [ -L "$entry" ] || continue
|
|
679
|
+
# Git history is not a renderer input. The project lock contains the
|
|
680
|
+
# check's scratch buffers and must stay solely in the real project.
|
|
681
|
+
[ "${entry##*/}" != .git ] || continue
|
|
682
|
+
cp -a "$entry" "$staging/project/" || rc=$?
|
|
683
|
+
[ "$rc" = 0 ] || break
|
|
684
|
+
done
|
|
685
|
+
fi
|
|
568
686
|
if [ "$rc" = 0 ]; then
|
|
569
687
|
# Never remove or rewrite through a copied directory link: it may
|
|
570
688
|
# still point at the live project or an external source.
|
|
@@ -575,7 +693,9 @@ sync_check_render() (
|
|
|
575
693
|
while IFS= read -r -d '' link; do
|
|
576
694
|
target_link="$(readlink "$link")" || { rc=1; break; }
|
|
577
695
|
# Output links need no relocation: comparison never follows them.
|
|
578
|
-
# Internal absolute links still move with their copied project
|
|
696
|
+
# Internal absolute links still move with their copied project;
|
|
697
|
+
# one into a part of the project the copy does not hold keeps
|
|
698
|
+
# reading the live path, which a render only ever reads.
|
|
579
699
|
rel="${link#"$staging/project/"}"
|
|
580
700
|
is_output=0
|
|
581
701
|
for output in ${SC_CHECK_OUTPUTS[@]+"${SC_CHECK_OUTPUTS[@]}"}; do
|
|
@@ -586,6 +706,7 @@ sync_check_render() (
|
|
|
586
706
|
rm "$link" && ln -s "$staging/project" "$link" || { rc=1; break; }
|
|
587
707
|
;;
|
|
588
708
|
"$REPO_ROOT"/*)
|
|
709
|
+
sync_check_holds "$staging/project" "${target_link#"$REPO_ROOT/"}" || continue
|
|
589
710
|
rm "$link" && ln -s "$staging/project/${target_link#"$REPO_ROOT/"}" "$link" || { rc=1; break; }
|
|
590
711
|
;;
|
|
591
712
|
/*) ;; # Absolute external links already retain their resolution.
|
|
@@ -596,7 +717,10 @@ sync_check_render() (
|
|
|
596
717
|
original_target="$IS_NORM_PATH"
|
|
597
718
|
case "$original_target" in
|
|
598
719
|
"$REPO_ROOT") original_target="$staging/project" ;;
|
|
599
|
-
"$REPO_ROOT"/*)
|
|
720
|
+
"$REPO_ROOT"/*)
|
|
721
|
+
entry="${original_target#"$REPO_ROOT/"}"
|
|
722
|
+
if sync_check_holds "$staging/project" "$entry"; then original_target="$staging/project/$entry"; fi
|
|
723
|
+
;;
|
|
600
724
|
esac
|
|
601
725
|
normalize_path_var "${link%/*}/$target_link"
|
|
602
726
|
[ "$IS_NORM_PATH" != "$original_target" ] || continue
|
package/cli/lib/sync-lock.sh
CHANGED
|
@@ -117,6 +117,36 @@ sync_lock_held_by_check() {
|
|
|
117
117
|
[ "$kind" = check ]
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
# sync_lock_tmpdirs <lock> — SYNC_LOCK_TMPDIRS, the temporary directories
|
|
121
|
+
# outside the lock that its holder's runs named in `scratch.<pid>.tmpdir`
|
|
122
|
+
# files. A run killed outright never reaches the trap that removes its own, so
|
|
123
|
+
# whoever releases or takes over the lock removes them. A record is only a
|
|
124
|
+
# file in the project, so it names nothing to remove unless the directory is
|
|
125
|
+
# one the CLI made: an absolute, real `intelligence-check-*` directory holding
|
|
126
|
+
# the marker `.intelligence-check` that the check writes first.
|
|
127
|
+
sync_lock_tmpdirs() {
|
|
128
|
+
local record dir
|
|
129
|
+
SYNC_LOCK_TMPDIRS=()
|
|
130
|
+
for record in "$1"/scratch.*.tmpdir; do
|
|
131
|
+
[ -f "$record" ] || continue
|
|
132
|
+
dir=""
|
|
133
|
+
IFS= read -r dir < "$record" || [ -n "$dir" ] || continue
|
|
134
|
+
case "$dir" in /*) ;; *) continue ;; esac
|
|
135
|
+
case "$dir" in *[[:cntrl:]]*|*/./*|*/../*|*/.|*/..) continue ;; esac
|
|
136
|
+
case "${dir##*/}" in intelligence-check-?*) ;; *) continue ;; esac
|
|
137
|
+
[ -d "$dir" ] && [ ! -L "$dir" ] && [ -f "$dir/.intelligence-check" ] || continue
|
|
138
|
+
SYNC_LOCK_TMPDIRS+=("$dir")
|
|
139
|
+
done
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Removing an abandoned copy can take long; it never keeps the lock looking
|
|
143
|
+
# ownerless or a takeover in progress.
|
|
144
|
+
sync_lock_remove_tmpdirs() {
|
|
145
|
+
[ "${#SYNC_LOCK_TMPDIRS[@]}" -eq 0 ] || rm -rf "${SYNC_LOCK_TMPDIRS[@]}" 2>/dev/null || true
|
|
146
|
+
SYNC_LOCK_TMPDIRS=()
|
|
147
|
+
}
|
|
148
|
+
SYNC_LOCK_TMPDIRS=()
|
|
149
|
+
|
|
120
150
|
# sync_lock_acquire <root> — take <root>/.intelligence/sync.lock, or refuse.
|
|
121
151
|
# A writer waits for each check that holds it for up to SYNC_LOCK_CHECK_WAIT
|
|
122
152
|
# seconds; a check refuses at once.
|
|
@@ -127,6 +157,7 @@ sync_lock_acquire() {
|
|
|
127
157
|
[ -d "$root/.intelligence" ] || mkdir -p "$root/.intelligence" || die "cannot create $root/.intelligence"
|
|
128
158
|
while :; do
|
|
129
159
|
took=0
|
|
160
|
+
SYNC_LOCK_TMPDIRS=()
|
|
130
161
|
if mkdir "$lock" 2>/dev/null; then
|
|
131
162
|
took=1
|
|
132
163
|
elif sync_lock_is_stale "$lock" && mkdir "$lock.takeover" 2>/dev/null; then
|
|
@@ -136,10 +167,15 @@ sync_lock_acquire() {
|
|
|
136
167
|
# Judge again under it: another run may have taken the lock over
|
|
137
168
|
# since this one looked.
|
|
138
169
|
if sync_lock_is_stale "$lock"; then
|
|
170
|
+
sync_lock_tmpdirs "$lock"
|
|
139
171
|
rm -rf "$lock"
|
|
140
172
|
mkdir "$lock" 2>/dev/null && took=1
|
|
141
173
|
fi
|
|
142
174
|
rmdir "$lock.takeover"
|
|
175
|
+
# Their records went with the lock: remove them now even when
|
|
176
|
+
# another run took it first. A lock this run took gets its owner
|
|
177
|
+
# before any of that time is spent.
|
|
178
|
+
[ "$took" = 1 ] || sync_lock_remove_tmpdirs
|
|
143
179
|
elif [ -d "$lock.takeover" ] && [ -n "$(find "$lock.takeover" -maxdepth 0 -mmin +1 2>/dev/null)" ]; then
|
|
144
180
|
# A takeover runs for milliseconds. Removing an abandoned one is itself
|
|
145
181
|
# a check-then-delete race, so it is left to a person.
|
|
@@ -147,6 +183,7 @@ sync_lock_acquire() {
|
|
|
147
183
|
fi
|
|
148
184
|
if [ "$took" = 1 ]; then
|
|
149
185
|
sync_lock_write_owner "$lock" || { rm -rf "$lock"; die "cannot write $lock/owner"; }
|
|
186
|
+
sync_lock_remove_tmpdirs
|
|
150
187
|
SYNC_LOCK_HELD="$lock"
|
|
151
188
|
return 0
|
|
152
189
|
fi
|
|
@@ -224,6 +261,9 @@ project_lock_hold() {
|
|
|
224
261
|
sync_lock_release() {
|
|
225
262
|
[ -n "$SYNC_LOCK_HELD" ] || return 0
|
|
226
263
|
if sync_lock_read_owner "$SYNC_LOCK_HELD" && [ "$IS_LOCK_TOKEN" = "$SYNC_LOCK_TOKEN" ]; then
|
|
264
|
+
# While the owner still stands, so a waiter keeps waiting.
|
|
265
|
+
sync_lock_tmpdirs "$SYNC_LOCK_HELD"
|
|
266
|
+
sync_lock_remove_tmpdirs
|
|
227
267
|
# A check's owner goes first: `rm -rf` alone could expose a moment with
|
|
228
268
|
# the owner still there and the kind gone, and a command waiting for
|
|
229
269
|
# the check would be refused in it. Without the owner, a waiter sees a
|
package/engine/ENGINE_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
79201cedae74ec96e67c3aa94e267f6b1e226c23
|
package/engine/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.23.0
|
|
@@ -196,6 +196,9 @@ async function runCheck($) {
|
|
|
196
196
|
// session's check — is not a fault: the last verdict stays, and the check
|
|
197
197
|
// asks again shortly instead of queueing ahead of a waiting sync.
|
|
198
198
|
else if (LOCK_REFUSAL.test(result.stderr || '')) retrySoon($)
|
|
199
|
+
// A project that needs a migration names the command that applies it on
|
|
200
|
+
// stderr; the status detail only names the versions.
|
|
201
|
+
else if (parsed.status === 'needs-update') setState('error', reasonLine(result.stderr) || 'run intelligence update')
|
|
199
202
|
else setState('error', parsed.detail || reasonLine(result.stderr) || 'exit ' + result.exitCode)
|
|
200
203
|
show($)
|
|
201
204
|
}
|
package/engine/lib/contract.sh
CHANGED
|
@@ -83,9 +83,9 @@ IS_RC_CONFIG_MISSING=2 # no manifest found
|
|
|
83
83
|
IS_RC_AMBIGUOUS=3 # conflicting state; agent/human-only — bash never emits this itself
|
|
84
84
|
IS_RC_AHEAD=4 # project stamped newer than this engine understands
|
|
85
85
|
IS_RC_ABORTED_INCOMPLETE=5 # staged state incomplete; the project was left untouched
|
|
86
|
-
IS_RC_NEEDS_UPDATE=6 #
|
|
86
|
+
IS_RC_NEEDS_UPDATE=6 # a migration only `intelligence update` applies (no stamp, older major)
|
|
87
87
|
# `intelligence sync --check` only: generated files differ from what sync would
|
|
88
|
-
# leave
|
|
88
|
+
# leave (IS_STATUS=out-of-date). It shares 2 with
|
|
89
89
|
# config-missing, which a check never reports: the CLI found the manifest, and
|
|
90
90
|
# the check turns any other failure that ends in 2 into 1.
|
|
91
91
|
IS_RC_OUT_OF_DATE=2
|
package/engine/sync.sh
CHANGED
|
@@ -39,10 +39,10 @@ _vc_rc=0
|
|
|
39
39
|
check_version_compat "$_cf" || _vc_rc=$?
|
|
40
40
|
if [ "$_vc_rc" -ne 0 ]; then exit "$_vc_rc"; fi
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
42
|
+
# sync is a PURE synchronizer: it never changes a schema. The manifest is
|
|
43
|
+
# additive within a major, so an older stamp of the same major renders as
|
|
44
|
+
# committed behind one warning (decision 0023); an older major, or no stamp at
|
|
45
|
+
# all, needs the migration only `intelligence update` applies.
|
|
46
46
|
read_schema_version_var "$_cf"
|
|
47
47
|
_stamp="$IS_SCHEMA_VERSION"
|
|
48
48
|
engine_version_var
|
|
@@ -51,13 +51,18 @@ _eng="$IS_ENGINE_VERSION"
|
|
|
51
51
|
if [ -z "$_stamp" ]; then
|
|
52
52
|
is_status needs-update "stamped= engine=$_eng (no schema_version)"
|
|
53
53
|
echo "ERROR: the manifest has no schema_version — schema un-applied." >&2
|
|
54
|
-
echo " Run: intelligence
|
|
54
|
+
echo " Run: intelligence update" >&2
|
|
55
55
|
exit "$IS_RC_NEEDS_UPDATE"
|
|
56
56
|
elif [ -n "$_eng" ] && _ver_gt "$_eng" "$_stamp"; then
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if [ "$(_ver_major "$_stamp")" != "$(_ver_major "$_eng")" ]; then
|
|
58
|
+
is_status needs-update "stamped=$_stamp engine=$_eng"
|
|
59
|
+
echo "ERROR: project at $_stamp but engine is $_eng — an older major schema." >&2
|
|
60
|
+
echo " Run: intelligence update" >&2
|
|
61
|
+
exit "$IS_RC_NEEDS_UPDATE"
|
|
62
|
+
fi
|
|
63
|
+
if [ "${IS_PROJECT_BEHIND_WARNED:-}" != "$_stamp" ]; then
|
|
64
|
+
echo "WARNING: project versions (schema $_stamp) trail this CLI's engine $_eng — they stay as committed; move them with: intelligence update" >&2
|
|
65
|
+
fi
|
|
61
66
|
fi
|
|
62
67
|
|
|
63
68
|
# Normalize REPO_ROOT and CONFIG_FILE to one `cd && pwd` spelling so
|
|
@@ -742,10 +747,14 @@ SYNC_TX_ACTIVE=0
|
|
|
742
747
|
# defaults move forward — e.g., gpt-5.5 -> gpt-5.6).
|
|
743
748
|
# Concurrently, the scan runs beside the other two and beside removing the
|
|
744
749
|
# snapshots; the buffers print in the order above.
|
|
750
|
+
# A check renders in a copy that holds only the project's inputs and outputs; the
|
|
751
|
+
# scan lists directories and reads no bytes, so it looks at the live project.
|
|
752
|
+
UNSYNCED_ROOT="$REPO_ROOT"
|
|
753
|
+
[ -z "${IS_SYNC_CHECK:-}" ] || [ -z "${IS_SYNC_CHECK_ROOT:-}" ] || UNSYNCED_ROOT="$IS_SYNC_CHECK_ROOT"
|
|
745
754
|
if [ "$SYNC_PARALLEL" = 1 ]; then
|
|
746
755
|
rm -rf "$SYNC_TX_DIR/data" &
|
|
747
756
|
SYNC_BG_PIDS+=("$!")
|
|
748
|
-
warn_unsynced "$
|
|
757
|
+
warn_unsynced "$UNSYNCED_ROOT" "$CONFIG_FILE" > "$SYNC_TX_DIR/unsynced" 2>&1 &
|
|
749
758
|
SYNC_BG_PIDS+=("$!")
|
|
750
759
|
{
|
|
751
760
|
report_context_source_sizes "$REPO_ROOT" "$CONFIG_FILE"
|
|
@@ -758,7 +767,7 @@ if [ "$SYNC_PARALLEL" = 1 ]; then
|
|
|
758
767
|
else
|
|
759
768
|
rm -rf "$SYNC_TX_DIR"
|
|
760
769
|
trap - EXIT INT TERM
|
|
761
|
-
warn_unsynced "$
|
|
770
|
+
warn_unsynced "$UNSYNCED_ROOT" "$CONFIG_FILE"
|
|
762
771
|
report_context_source_sizes "$REPO_ROOT" "$CONFIG_FILE"
|
|
763
772
|
report_model_drift "$CONFIG_FILE"
|
|
764
773
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainova-systems/intelligence",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Build, version and distribute AI agent intelligence across your organization — one CLI, versioned Intelligence Packages, and a sync engine for Claude Code, Cursor, Copilot, Codex, Pi and OpenCode.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"intelligence": "bin/intelligence.js"
|
|
@@ -125,7 +125,7 @@ Practice:
|
|
|
125
125
|
|
|
126
126
|
Commit `intelligence.lock`. It records requested versions, source URLs and paths, resolved refs and commit SHAs. After cloning, or after a pull that moved the lock, `intelligence sync` restores every package the store lacks or holds at another commit, strictly from that lock, before rendering; manifest/lock or SHA drift is refused. Re-run `package add` when deliberately changing a source.
|
|
127
127
|
|
|
128
|
-
`@ainova-systems/sync` is ordinary package content exact-pinned to the bundled engine version. `intelligence init` installs it unless `--bare` is used.
|
|
128
|
+
`@ainova-systems/sync` is ordinary package content exact-pinned to the bundled engine version. `intelligence init` installs it unless `--bare` is used. Only `intelligence update` moves that pin and `schema_version` to the installed CLI; every other command keeps them as committed, and package-range updates never move the pin independently.
|
|
129
129
|
|
|
130
130
|
## Layout tokens
|
|
131
131
|
|
|
@@ -504,15 +504,15 @@ The permanent applied-schema key is the top-level scalar `schema_version` in `in
|
|
|
504
504
|
|
|
505
505
|
The public lifecycle is deliberately compact:
|
|
506
506
|
|
|
507
|
-
- `intelligence init [--preview|--apply]` is universal: it creates a new setup,
|
|
508
|
-
- `intelligence sync [adapter] [--compact] [--force] [--check]` first
|
|
507
|
+
- `intelligence init [--preview|--apply]` is universal: it creates a new setup, restores an existing Intelligence project as committed, or plans/applies conversion of an eligible legacy Intelligence Sync project.
|
|
508
|
+
- `intelligence sync [adapter] [--compact] [--force] [--check]` first restores every package the store lacks or holds at another commit, strictly from `intelligence.lock`. It skips rendering when local inputs and outputs match a previous successful run. Changes or missing outputs trigger rendering; `--force` always renders and refreshes the search for unconfigured source directories. An unchanged run replays the last full run's useful diagnostics. Compact mode shows context sizes, actionable warnings and final status on success, and all diagnostics on failure. It renders an Intelligence project at its committed versions: versions that trail the installed CLI within its major render behind one warning naming `intelligence update`; a project that needs a migration first — no `schema_version` or an older major — is refused with `needs-update`, locally and in CI alike. `--check` changes no generated file: it prints one `IS_STATUS` line and exits 0 when generated files are up to date and 2 (`out-of-date`) when sync would change them; it never moves a version.
|
|
509
509
|
- `intelligence update [@scope/name] [--preview|--apply]` is the only update surface. It prints the CLI/project/package plan; default mode prompts, `--preview` never writes, and `--apply` does not prompt. It never moves `ref:` pins.
|
|
510
510
|
- `intelligence upgrade [--next] [--preview|--apply]` replaces the installed CLI with the newest version on its npm channel (`next` for a prerelease or with `--next`, otherwise `latest`) with the same modes. It touches no project, never downgrades, and refuses an installation that npm did not make.
|
|
511
511
|
- `intelligence package add|remove|list|search` owns package inventory.
|
|
512
512
|
- `intelligence adapter list|create|enable|disable|remove` owns adapter inventory and target state.
|
|
513
513
|
- `intelligence status [--check]` reports state; `--check` runs deep consistency checks.
|
|
514
514
|
|
|
515
|
-
Implement Intelligence schema changes as idempotent structural checks. Stage and verify replacement state before deleting or replacing prior state. A stale engine refuses a manifest whose `schema_version` is a newer major; a newer minor or patch within the same major warns once and proceeds without restamping the project.
|
|
515
|
+
Implement Intelligence schema changes as idempotent structural checks. Stage and verify replacement state before deleting or replacing prior state. A stale engine refuses a manifest whose `schema_version` is a newer major; a newer minor or patch within the same major warns once and proceeds without restamping the project. A project behind the engine stays as committed until `intelligence update` closes the gap.
|
|
516
516
|
|
|
517
517
|
Breaking changelog entries use a `### Breaking` checklist of verifiable post-conditions. The `intelligence-upgrade` skill reads every release across the version gap, chooses the package/CLI/project command sequence and verifies those conditions after the deterministic command completes.
|
|
518
518
|
|
|
@@ -526,11 +526,11 @@ The engine emits one machine-readable line, `IS_STATUS=<code> [IS_DETAIL=...]`,
|
|
|
526
526
|
| `migrated` | 0 | Initialization converted an older layout |
|
|
527
527
|
| `error` | 1 | Generic failure |
|
|
528
528
|
| `config-missing` | 2 | Required manifest is absent |
|
|
529
|
-
| `out-of-date` | 2 | `sync --check` only: sync would change generated files
|
|
529
|
+
| `out-of-date` | 2 | `sync --check` only: sync would change generated files. A check never reports `config-missing`, and turns any other failure that would end in 2 into 1 |
|
|
530
530
|
| `ambiguous` | 3 | Conflicting state requiring agent/human judgment; reserved |
|
|
531
531
|
| `ahead-of-engine` | 4 | Manifest schema is a newer major than the engine (a newer minor or patch warns and continues) |
|
|
532
532
|
| `aborted-incomplete` | 5 | Staged replacement was incomplete; prior state remains |
|
|
533
|
-
| `needs-update` | 6 |
|
|
533
|
+
| `needs-update` | 6 | The project needs a migration only `intelligence update` applies: no `schema_version`, or an older major |
|
|
534
534
|
|
|
535
535
|
Callers capture the real code with `command || rc=$?`. Do not use `if ! command; then rc=$?`; inside that branch `$?` is the status of the negation.
|
|
536
536
|
|
|
@@ -28,8 +28,11 @@ mechanics; this skill supplies repository judgement.
|
|
|
28
28
|
3. Run `intelligence status --check`. If the project is missing, inconsistent,
|
|
29
29
|
or its first sync failed, run `intelligence init --preview`, show the exact
|
|
30
30
|
repair plan, and request approval. After approval run
|
|
31
|
-
`intelligence init --apply`.
|
|
32
|
-
|
|
31
|
+
`intelligence init --apply`. When the check or the plan names
|
|
32
|
+
`intelligence update` instead, the project's versions need moving: show
|
|
33
|
+
`intelligence update --preview` and run `intelligence update --apply` only
|
|
34
|
+
after approval. Do not reproduce manifest, package, adapter, backup, or
|
|
35
|
+
ignore-file mechanics manually.
|
|
33
36
|
4. Run `intelligence sync`. Sync is transactional: a failure restores every
|
|
34
37
|
adapter-owned path. Resolve the reported cause and retry. Do not begin
|
|
35
38
|
semantic migration until `IS_STATUS=ok` and `intelligence status --check`
|
|
@@ -8,16 +8,17 @@ context: fork
|
|
|
8
8
|
# Sync intelligence
|
|
9
9
|
|
|
10
10
|
1. Run `intelligence sync` (or `intelligence sync <adapter>` when one adapter
|
|
11
|
-
was requested). For Intelligence projects, this command
|
|
12
|
-
|
|
13
|
-
`intelligence.lock`. Unchanged inputs and outputs can reuse a successful run.
|
|
11
|
+
was requested). For Intelligence projects, this command renders the project
|
|
12
|
+
at its committed versions and first restores a missing package store strictly
|
|
13
|
+
from `intelligence.lock`. Unchanged inputs and outputs can reuse a successful run.
|
|
14
14
|
Use `--force` when a fresh render or a new search for unconfigured source
|
|
15
15
|
directories is requested; an unchanged run replays the previous diagnostics.
|
|
16
16
|
2. Require final `IS_STATUS=ok`; relay reported counts and any integrity,
|
|
17
17
|
model-drift or unsynced-source warnings.
|
|
18
|
-
3.
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
3. A warning that project versions trail the CLI is not a failure: relay it.
|
|
19
|
+
`IS_STATUS=needs-update` (exit 6) means the project needs a migration only
|
|
20
|
+
`intelligence update` applies; report that, and never edit the manifest or
|
|
21
|
+
lock by hand to get past it. A frozen restore refusal means the
|
|
21
22
|
manifest and lock disagree or the locked commit cannot be verified/restored—report it without
|
|
22
23
|
hand-copying package content.
|
|
23
24
|
4. On a probable Intelligence defect, first ask whether to collect a sanitized
|