@ainova-systems/intelligence 0.16.1 → 0.17.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/init.sh +1 -0
- package/cli/commands/sync.sh +6 -7
- package/cli/intelligence +2 -1
- package/cli/internal/check.sh +7 -0
- package/cli/internal/target-state.sh +21 -2
- package/cli/lib/cli-common.sh +1 -0
- package/cli/lib/sync-cache.sh +202 -0
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/engine/adapters/antigravity.sh +125 -27
- package/engine/adapters/cursor.sh +13 -5
- package/engine/lib/adapter-contract.sh +73 -0
- package/engine/sync.sh +46 -4
- package/package.json +1 -1
- package/packages/sync/references/adapters.md +24 -0
- package/packages/sync/references/conventions.md +2 -1
- package/packages/sync/skills/intelligence-sync/SKILL.md +4 -2
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ intelligence sync
|
|
|
33
33
|
| Command | What it does |
|
|
34
34
|
|---|---|
|
|
35
35
|
| `intelligence init [--preview\|--apply]` | Create, convert, restore or align a project |
|
|
36
|
-
| `intelligence sync [adapter]` | Restore locked content
|
|
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 |
|
|
39
39
|
| `intelligence package add\|remove\|list\|search` | Manage versioned Intelligence Packages |
|
package/cli/commands/init.sh
CHANGED
|
@@ -233,6 +233,7 @@ else
|
|
|
233
233
|
[ -d "$root/.pi" ] && targets="$targets pi"
|
|
234
234
|
[ -d "$root/.opencode" ] && targets="$targets opencode"
|
|
235
235
|
if [ -f "$root/GEMINI.md" ] \
|
|
236
|
+
|| [ -f "$root/.antigravity.md" ] \
|
|
236
237
|
|| [ -d "$root/.agents/rules" ] \
|
|
237
238
|
|| [ -d "$root/.agents/agents" ] \
|
|
238
239
|
|| [ -d "$root/.agents/workflows" ]; then
|
package/cli/commands/sync.sh
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# intelligence sync [target] [--compact] - render intelligence to enabled tools.
|
|
2
|
+
# intelligence sync [target] [--compact] [--force] - render intelligence to enabled tools.
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
5
5
|
|
|
6
6
|
compact=0
|
|
7
|
+
force=0
|
|
7
8
|
target=""
|
|
8
9
|
while [ $# -gt 0 ]; do
|
|
9
10
|
case "$1" in
|
|
10
11
|
--compact) compact=1 ;;
|
|
12
|
+
--force) force=1 ;;
|
|
11
13
|
-*) die "unknown option '$1'" ;;
|
|
12
14
|
*)
|
|
13
|
-
[ -z "$target" ] || die "usage: intelligence sync [adapter] [--compact]"
|
|
15
|
+
[ -z "$target" ] || die "usage: intelligence sync [adapter] [--compact] [--force]"
|
|
14
16
|
target="$1"
|
|
15
17
|
;;
|
|
16
18
|
esac
|
|
@@ -28,13 +30,10 @@ run_sync() {
|
|
|
28
30
|
ensure_project_current "$IP_ROOT" || return $?
|
|
29
31
|
restore_project_store_if_missing "$IP_ROOT" || return $?
|
|
30
32
|
export_engine_env "$IP_ROOT" || return $?
|
|
31
|
-
|
|
32
|
-
bash "$IS_ENGINE_DIR/sync.sh" "$target" || return $?
|
|
33
|
-
else
|
|
34
|
-
bash "$IS_ENGINE_DIR/sync.sh" || return $?
|
|
35
|
-
fi
|
|
33
|
+
sync_with_cache "$target" "$force" || return $?
|
|
36
34
|
;;
|
|
37
35
|
legacy)
|
|
36
|
+
[ "$force" -eq 0 ] || die "--force is unavailable for a legacy Intelligence Sync project - run 'intelligence init' to convert it first"
|
|
38
37
|
[ "$compact" -eq 0 ] || die "--compact is unavailable for a legacy Intelligence Sync project - run 'intelligence init' to convert it first"
|
|
39
38
|
# A vendored project syncs with its own engine: its pin is the
|
|
40
39
|
# contract, and a newer bundled engine must not generate against an
|
package/cli/intelligence
CHANGED
|
@@ -46,8 +46,9 @@ Usage: intelligence <command> [args]
|
|
|
46
46
|
init [--preview|--apply] Initialize, convert, restore or align this project
|
|
47
47
|
--targets a,b --dir name --bare --no-sync New-project options
|
|
48
48
|
--force Accept a dirty worktree during legacy project conversion
|
|
49
|
-
sync [adapter] [--compact] Restore locked content, then render enabled adapters
|
|
49
|
+
sync [adapter] [--compact] [--force] Restore locked content, then render enabled adapters
|
|
50
50
|
--compact On success show context, warnings and final status; on failure show diagnostics
|
|
51
|
+
--force Render again and refresh discovery of unconfigured source directories
|
|
51
52
|
update [@scope/name] Show an update plan; ask before applying it
|
|
52
53
|
--preview Show the plan without asking or writing
|
|
53
54
|
--apply Apply the plan without asking
|
package/cli/internal/check.sh
CHANGED
|
@@ -110,6 +110,9 @@ done < <(qmap_keys "$manifest" "packages")
|
|
|
110
110
|
# onboarding backup and Git policy. Validate every declared target, including
|
|
111
111
|
# disabled ones, so a later enable cannot reveal a stale custom adapter.
|
|
112
112
|
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
113
|
+
# Ownership across enabled adapters: two adapters claiming one owned path prune
|
|
114
|
+
# each other silently, and sync reports success for both.
|
|
115
|
+
adapter_claims_reset
|
|
113
116
|
while IFS= read -r target; do
|
|
114
117
|
[ -n "$target" ] || continue
|
|
115
118
|
output="$(get_target_output "$manifest" "$target")"
|
|
@@ -124,6 +127,10 @@ while IFS= read -r target; do
|
|
|
124
127
|
warn "adapter '$target' has an invalid ownership contract"
|
|
125
128
|
continue
|
|
126
129
|
fi
|
|
130
|
+
if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
|
|
131
|
+
adapter_claims_add_records "$target" "$records" \
|
|
132
|
+
|| warn "$IS_ADAPTER_CLAIM_CONFLICT"
|
|
133
|
+
fi
|
|
127
134
|
while IFS=$'\t' read -r kind value; do
|
|
128
135
|
if [ "$kind" = "requires" ] \
|
|
129
136
|
&& [ "$(is_target_enabled "$manifest" "$target")" = "1" ] \
|
|
@@ -29,13 +29,32 @@ if [ "$action" = "enable" ]; then
|
|
|
29
29
|
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
30
30
|
validate_adapter_contract_for "$IP_ROOT" "$content_dir" "$name" "$output" \
|
|
31
31
|
|| die "adapter '$name' has an invalid ownership contract"
|
|
32
|
+
records="$(adapter_records_for "$IP_ROOT" "$content_dir" "$name" "$output")" \
|
|
33
|
+
|| die "adapter '$name' has an invalid ownership contract"
|
|
32
34
|
while IFS= read -r required; do
|
|
33
35
|
[ -n "$required" ] || continue
|
|
34
36
|
if [ "$(is_target_enabled "$manifest" "$required")" != "1" ]; then
|
|
35
37
|
die "target '$name' requires target '$required' — enable it first"
|
|
36
38
|
fi
|
|
37
|
-
done < <(
|
|
38
|
-
|
|
39
|
+
done < <(awk -F '\t' '$1 == "requires" { print $2 }' <<< "$records")
|
|
40
|
+
|
|
41
|
+
# Two adapters claiming one owned path prune each other's output, and both
|
|
42
|
+
# syncs still report success. Refuse here, while the manifest is the only
|
|
43
|
+
# thing that would have to change.
|
|
44
|
+
adapter_claims_reset
|
|
45
|
+
while IFS= read -r enabled_target; do
|
|
46
|
+
[ -n "$enabled_target" ] || continue
|
|
47
|
+
[ "$enabled_target" = "$name" ] && continue
|
|
48
|
+
[ "$(is_target_enabled "$manifest" "$enabled_target")" = "1" ] || continue
|
|
49
|
+
enabled_output="$(get_target_output "$manifest" "$enabled_target")"
|
|
50
|
+
[ -n "$enabled_output" ] || enabled_output="$(default_target_output "$enabled_target")"
|
|
51
|
+
enabled_records="$(adapter_records_for "$IP_ROOT" "$content_dir" "$enabled_target" "$enabled_output" 2>/dev/null)" \
|
|
52
|
+
|| continue
|
|
53
|
+
adapter_claims_add_records "$enabled_target" "$enabled_records" || true
|
|
54
|
+
done < <(target_names "$manifest")
|
|
55
|
+
adapter_claims_add_records "$name" "$records" \
|
|
56
|
+
|| die "$IS_ADAPTER_CLAIM_CONFLICT"
|
|
57
|
+
|
|
39
58
|
target_set_enabled "$manifest" "$name" true "$output"
|
|
40
59
|
ensure_target_gitignore "$IP_ROOT" "$manifest" "$name"
|
|
41
60
|
ensure_manifest_publisher_ignores "$IP_ROOT" "$manifest"
|
package/cli/lib/cli-common.sh
CHANGED
|
@@ -86,6 +86,7 @@ source "$CLI_DIR/lib/lockfile.sh"
|
|
|
86
86
|
source "$CLI_DIR/lib/adapter-lifecycle.sh"
|
|
87
87
|
source "$CLI_DIR/lib/gitignore.sh"
|
|
88
88
|
source "$CLI_DIR/lib/onboarding.sh"
|
|
89
|
+
source "$CLI_DIR/lib/sync-cache.sh"
|
|
89
90
|
|
|
90
91
|
# The engine-content package: OPTIONAL but auto-selected at init. Package by
|
|
91
92
|
# UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Optional CLI optimization. Cache data never supplies paths or executable code:
|
|
3
|
+
# every dependency is derived again from the manifest and built-in contracts.
|
|
4
|
+
|
|
5
|
+
sync_cache_safe_path() {
|
|
6
|
+
local path="$1" probe="$1"
|
|
7
|
+
case "$path" in *[[:cntrl:]]*|*\\*|*\"*) return 1 ;; esac
|
|
8
|
+
# Include ancestors: find does not report a link above its starting path.
|
|
9
|
+
while [ -n "$probe" ] && [ "$probe" != / ]; do
|
|
10
|
+
case "$probe" in "$REPO_ROOT"|"$CLI_DIR"|"$IS_ENGINE_DIR") break ;; esac
|
|
11
|
+
[ ! -L "$probe" ] || return 1
|
|
12
|
+
probe="${probe%/*}"
|
|
13
|
+
done
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# One checked enumeration and one batch hash, including names, directory/file
|
|
17
|
+
# types and executable bits. Timestamps cannot prove content is unchanged.
|
|
18
|
+
# Unsupported paths/types are a cache miss, never an incomplete fingerprint.
|
|
19
|
+
sync_cache_fingerprint() (
|
|
20
|
+
local work="$1" path kind executable seen=$'\n'
|
|
21
|
+
local -a existing
|
|
22
|
+
existing=()
|
|
23
|
+
shift
|
|
24
|
+
: > "$work/entries" || return 1
|
|
25
|
+
: > "$work/files" || return 1
|
|
26
|
+
: > "$work/list" || return 1
|
|
27
|
+
for path in "$@"; do
|
|
28
|
+
sync_cache_safe_path "$path" || return 1
|
|
29
|
+
# Shared managed destinations are declared by several adapters. Walking
|
|
30
|
+
# the same tree again only multiplies filesystem probes on Windows.
|
|
31
|
+
case "$seen" in *$'\n'"$path"$'\n'*) continue ;; esac
|
|
32
|
+
seen="$seen$path"$'\n'
|
|
33
|
+
if [ -e "$path" ]; then
|
|
34
|
+
existing+=("$path")
|
|
35
|
+
else
|
|
36
|
+
printf 'missing\t%s\n' "$path" >> "$work/entries" || return 1
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
if [ "${#existing[@]}" -gt 0 ]; then
|
|
40
|
+
find "${existing[@]}" -print0 > "$work/list" || return 1
|
|
41
|
+
fi
|
|
42
|
+
while IFS= read -r -d '' path; do
|
|
43
|
+
# Starting-path ancestors were checked above. find does not follow
|
|
44
|
+
# links, so checking each returned entry once covers the entire tree.
|
|
45
|
+
case "$path" in *[[:cntrl:]]*|*\\*|*\"*) return 1 ;; esac
|
|
46
|
+
[ ! -L "$path" ] || return 1
|
|
47
|
+
executable=0
|
|
48
|
+
if [ -f "$path" ]; then
|
|
49
|
+
kind='file'
|
|
50
|
+
[ ! -x "$path" ] || executable=1
|
|
51
|
+
printf '%s\n' "$path" >> "$work/files" || return 1
|
|
52
|
+
elif [ -d "$path" ]; then
|
|
53
|
+
kind=directory
|
|
54
|
+
else
|
|
55
|
+
return 1
|
|
56
|
+
fi
|
|
57
|
+
printf '%s\t%s\t%s\n' "$kind" "$executable" "$path" >> "$work/entries" || return 1
|
|
58
|
+
done < "$work/list"
|
|
59
|
+
# find order need not be stable. Sort both streams; preserve path-to-hash
|
|
60
|
+
# association by feeding the sorted file list into a single git process.
|
|
61
|
+
LC_ALL=C sort -u "$work/entries" > "$work/sorted" || return 1
|
|
62
|
+
LC_ALL=C sort -u "$work/files" > "$work/paths" || return 1
|
|
63
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
64
|
+
# Git for Windows does not translate POSIX paths received on stdin.
|
|
65
|
+
cygpath -m -f "$work/paths" > "$work/native" || return 1
|
|
66
|
+
else
|
|
67
|
+
cp "$work/paths" "$work/native" || return 1
|
|
68
|
+
fi
|
|
69
|
+
git hash-object --no-filters --stdin-paths < "$work/native" > "$work/hashes" || return 1
|
|
70
|
+
cat "$work/sorted" "$work/paths" "$work/hashes" | git hash-object --stdin || return 1
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
sync_cache_dependencies() {
|
|
74
|
+
local src section adapter file output records kind value
|
|
75
|
+
SC_INPUTS=("$CONFIG_FILE" "$REPO_ROOT/intelligence.lock"
|
|
76
|
+
"$CLI_DIR/intelligence" "$CLI_DIR/engine-package.yaml"
|
|
77
|
+
"$CLI_DIR/commands" "$CLI_DIR/internal" "$CLI_DIR/lib" "$IS_ENGINE_DIR"
|
|
78
|
+
"$REPO_ROOT/$IS_CONTENT_REL/adapters")
|
|
79
|
+
SC_OUTPUTS=()
|
|
80
|
+
# Project adapters can read arbitrary files or environment and execute any
|
|
81
|
+
# code. Their dependencies cannot be inferred from their write contract.
|
|
82
|
+
for file in "$REPO_ROOT/$IS_CONTENT_REL/adapters"/*.sh; do
|
|
83
|
+
[ ! -e "$file" ] && [ ! -L "$file" ] || return 1
|
|
84
|
+
done
|
|
85
|
+
for section in rules agents skills; do
|
|
86
|
+
load_yaml_list "$CONFIG_FILE" "$section"
|
|
87
|
+
while IFS= read -r src; do
|
|
88
|
+
[ -n "$src" ] || continue
|
|
89
|
+
adapter_contract_safe_concrete_path "$src" || return 1
|
|
90
|
+
case "$src" in .|./|.intelligence|.intelligence/|.intelligence/sync-cache*) return 1 ;; esac
|
|
91
|
+
SC_INPUTS+=("$REPO_ROOT/$src")
|
|
92
|
+
done <<< "$IS_YAML_LIST"
|
|
93
|
+
done
|
|
94
|
+
load_targets_cache "$CONFIG_FILE"
|
|
95
|
+
for file in "$IS_ENGINE_DIR/adapters"/*.sh; do
|
|
96
|
+
adapter="${file##*/}"; adapter="${adapter%.sh}"
|
|
97
|
+
[ "$adapter" != _template ] || continue
|
|
98
|
+
target_enabled_var "$CONFIG_FILE" "$adapter"
|
|
99
|
+
[ "$IS_TGT_ENABLED" = 1 ] || continue
|
|
100
|
+
case "$adapter" in agents|antigravity|claude|codex|copilot|cursor|opencode|pi) ;; *) return 1 ;; esac
|
|
101
|
+
target_output_var "$CONFIG_FILE" "$adapter"
|
|
102
|
+
output="${IS_TGT_OUTPUT:-.$adapter}"
|
|
103
|
+
records="$(adapter_contract_records "$adapter" "$file" "$output")" || return 1
|
|
104
|
+
while IFS=$'\t' read -r kind value; do
|
|
105
|
+
case "$kind" in owned|managed) SC_OUTPUTS+=("$REPO_ROOT/$value") ;; esac
|
|
106
|
+
done <<< "$records"
|
|
107
|
+
done
|
|
108
|
+
[ "${#SC_OUTPUTS[@]}" -gt 0 ]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
sync_cache_input_hash() {
|
|
112
|
+
local fingerprint
|
|
113
|
+
fingerprint="$(sync_cache_fingerprint "$1" "${SC_INPUTS[@]}")" || return 1
|
|
114
|
+
# Explicit environment values that affect rendering, discovery or parsing.
|
|
115
|
+
# No secrets or inherited environment dump are persisted.
|
|
116
|
+
printf '%s\n' "$fingerprint" "$2" "$REPO_ROOT" "$CONFIG_FILE" \
|
|
117
|
+
"$IS_CONTENT_REL" "$IS_MODULE_REL" "$IS_SYNC_CMD" "$IS_MANIFEST_NAME" \
|
|
118
|
+
"$IS_PROTECTED_DIRS" "$BASH_VERSION" "${OSTYPE:-}" "${LANG:-}" \
|
|
119
|
+
"${LC_ALL:-}" "${LC_CTYPE:-}" "${LC_COLLATE:-}" "${PATH:-}" "$(umask)" | git hash-object --stdin
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
sync_cache_directory() {
|
|
123
|
+
local path="$REPO_ROOT/.intelligence/sync-cache" physical root_physical probe expected
|
|
124
|
+
sync_cache_safe_path "$path/state" || return 1
|
|
125
|
+
root_physical="$(cd "$REPO_ROOT" && pwd -P)" || return 1
|
|
126
|
+
probe="$path"; expected="$root_physical/.intelligence/sync-cache"
|
|
127
|
+
while [ ! -e "$probe" ]; do probe="${probe%/*}"; expected="${expected%/*}"; done
|
|
128
|
+
physical="$(cd "$probe" && pwd -P)" || return 1
|
|
129
|
+
[ "$physical" = "$expected" ] || return 1
|
|
130
|
+
if [ "${1:-}" = create ]; then mkdir -p "$path" || return 1; fi
|
|
131
|
+
SC_DIRECTORY="$path"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
sync_cache_read() {
|
|
135
|
+
local file="$1" input="$2" output="$3" work="$4" header report_hash actual
|
|
136
|
+
[ -f "$file" ] && [ ! -L "$file" ] || return 1
|
|
137
|
+
IFS= read -r header < "$file" || return 1
|
|
138
|
+
case "$header" in "intelligence-sync-cache-v1 $input $output "*) ;; *) return 1 ;; esac
|
|
139
|
+
report_hash="${header##* }"
|
|
140
|
+
case "$report_hash" in ''|*[!a-f0-9]*) return 1 ;; esac
|
|
141
|
+
[ "${#report_hash}" -eq 40 ] || [ "${#report_hash}" -eq 64 ] || return 1
|
|
142
|
+
tail -n +2 "$file" > "$work/report" || return 1
|
|
143
|
+
actual="$(git hash-object --stdin < "$work/report")" || return 1
|
|
144
|
+
[ "$actual" = "$report_hash" ] || return 1
|
|
145
|
+
grep -q '^IS_STATUS=ok\($\| \)' "$work/report" || return 1
|
|
146
|
+
grep -q '^=== Done:' "$work/report" || return 1
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Keep actionable diagnostics, including the multi-line unsynced-directory
|
|
150
|
+
# report that compact mode intentionally omits. Never replay renderer progress.
|
|
151
|
+
sync_cache_report() {
|
|
152
|
+
awk '
|
|
153
|
+
/^(WARNING:| WARN:)/ { warning=1; print; next }
|
|
154
|
+
warning && /^ / { print; next }
|
|
155
|
+
{ warning=0 }
|
|
156
|
+
/^(CONTEXT:|IS_STATUS=ok|=== Done:|=== WARNING:| NOT SYNCED:| Wire one in:)/ { print }
|
|
157
|
+
' "$1"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
sync_with_cache() (
|
|
161
|
+
local target="$1" force="$2" work before after outputs report_hash rc=0 cacheable=0
|
|
162
|
+
work="$(mktemp -d -t intelligence-cache-XXXXXX)" || return 1
|
|
163
|
+
trap 'rm -rf "$work"' EXIT
|
|
164
|
+
if sync_cache_dependencies && sync_cache_directory; then
|
|
165
|
+
before="$(sync_cache_input_hash "$work" "$target")" || before=""
|
|
166
|
+
[ -z "$before" ] || cacheable=1
|
|
167
|
+
fi
|
|
168
|
+
if [ "$cacheable" = 1 ] && [ "$force" = 0 ]; then
|
|
169
|
+
outputs="$(sync_cache_fingerprint "$work" "${SC_OUTPUTS[@]}")" || outputs=""
|
|
170
|
+
if [ -n "$outputs" ] && sync_cache_read "$SC_DIRECTORY/state" "$before" "$outputs" "$work"; then
|
|
171
|
+
echo ' Unchanged: verified sources and generated output; skipped rendering (use --force to refresh discovery).'
|
|
172
|
+
cat "$work/report"
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
# A failed or interrupted attempt must not leave a success cache reusable.
|
|
177
|
+
if [ "$cacheable" = 1 ]; then
|
|
178
|
+
rm -f "$SC_DIRECTORY/state" || cacheable=0
|
|
179
|
+
fi
|
|
180
|
+
# Buffer once so success diagnostics can be replayed without asking the
|
|
181
|
+
# engine to know about persistent CLI state. Preserve the engine exit code.
|
|
182
|
+
bash "$IS_ENGINE_DIR/sync.sh" "$target" > "$work/log" 2>&1 || rc=$?
|
|
183
|
+
cat "$work/log"
|
|
184
|
+
[ "$rc" = 0 ] || return "$rc"
|
|
185
|
+
[ "$cacheable" = 1 ] || return 0
|
|
186
|
+
after="$(sync_cache_input_hash "$work" "$target")" || return 0
|
|
187
|
+
[ "$before" = "$after" ] || return 0
|
|
188
|
+
outputs="$(sync_cache_fingerprint "$work" "${SC_OUTPUTS[@]}")" || return 0
|
|
189
|
+
sync_cache_report "$work/log" > "$work/report" || return 0
|
|
190
|
+
report_hash="$(git hash-object --stdin < "$work/report")" || return 0
|
|
191
|
+
# Recheck containment before publishing. mktemp+rename avoids following a
|
|
192
|
+
# pre-existing state-file link, and readers see only complete records.
|
|
193
|
+
sync_cache_directory create || return 0
|
|
194
|
+
local staged
|
|
195
|
+
staged="$(mktemp "$SC_DIRECTORY/state-XXXXXX")" || return 0
|
|
196
|
+
if { printf 'intelligence-sync-cache-v1 %s %s %s\n' "$after" "$outputs" "$report_hash"; cat "$work/report"; } > "$staged"; then
|
|
197
|
+
mv -f "$staged" "$SC_DIRECTORY/state" || rm -f "$staged"
|
|
198
|
+
else
|
|
199
|
+
rm -f "$staged"
|
|
200
|
+
fi
|
|
201
|
+
return 0
|
|
202
|
+
)
|
package/engine/ENGINE_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f23e407da080869b07481730d81b6ebce73a1d2e
|
package/engine/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.17.0
|
|
@@ -2,18 +2,32 @@
|
|
|
2
2
|
# intelligence-sync: Google Antigravity adapter
|
|
3
3
|
# Transforms source prompts to Antigravity's native resource model.
|
|
4
4
|
#
|
|
5
|
+
# Vendor sources checked 2026-09-20: antigravity.google/docs/{rules-workflows,
|
|
6
|
+
# skills,subagents} and /docs/cli/{best-practices,gcli-migration}. Where this
|
|
7
|
+
# adapter goes past what they state, the comment says so — the vendor documents
|
|
8
|
+
# less than the product implements, and an unmarked guess is what drifts.
|
|
9
|
+
#
|
|
5
10
|
# Rules:
|
|
6
|
-
# - Always-on (no `paths:`) -> SKIPPED here.
|
|
7
|
-
#
|
|
8
|
-
# always-on rule content into
|
|
9
|
-
# would double-load the same text.
|
|
11
|
+
# - Always-on (no `paths:`) -> SKIPPED here. The CLI docs tell a project to
|
|
12
|
+
# "Create a GEMINI.md or AGENTS.md file at your workspace root", and the
|
|
13
|
+
# `agents` adapter inlines always-on rule content into AGENTS.md. Emitting
|
|
14
|
+
# copies under .agents/rules/ would double-load the same text.
|
|
10
15
|
# - Path-scoped (with `paths:`) -> .agents/rules/<name>.md carrying
|
|
11
|
-
# `trigger: glob` and `globs
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
16
|
+
# `trigger: glob` and `globs:`. NOT vendor-documented: the rules page names
|
|
17
|
+
# four activation modes in prose (manual, always on, model decision, glob
|
|
18
|
+
# pattern) and no frontmatter key, format or example anywhere. These keys
|
|
19
|
+
# follow the Windsurf-lineage convention Antigravity inherits; re-check
|
|
20
|
+
# them whenever that page grows a real schema.
|
|
21
|
+
# - "Rules files are limited to 12,000 characters each" is documented, so a
|
|
22
|
+
# generated scoped rule above the limit warns (targets.antigravity.
|
|
23
|
+
# warn_rule_limit).
|
|
24
|
+
# - Workspace-root instruction files outrank AGENTS.md where both are read:
|
|
25
|
+
# GEMINI.md over AGENTS.md, and .antigravity.md over GEMINI.md in the CLI.
|
|
26
|
+
# The docs host states neither order — nor AGENTS.md support on its rules
|
|
27
|
+
# page — so both come from the CLI pages above plus third-party reports of
|
|
28
|
+
# IDE 1.20.3. The contract quarantines both files at onboarding either way:
|
|
29
|
+
# left in place, either silently overrides every synced rule, and a file
|
|
30
|
+
# the project does not have costs nothing to declare.
|
|
17
31
|
# Skills:
|
|
18
32
|
# - Copied to .agents/skills/ (Agent Skills open standard, shared with
|
|
19
33
|
# Codex, Pi and opencode). Antigravity reads that fixed workspace path,
|
|
@@ -38,17 +52,47 @@ adapter_contract_antigravity() {
|
|
|
38
52
|
adapter_contract_owned "$output/agents"
|
|
39
53
|
adapter_contract_managed ".agents/skills"
|
|
40
54
|
adapter_contract_legacy "GEMINI.md"
|
|
55
|
+
adapter_contract_legacy ".antigravity.md"
|
|
41
56
|
adapter_contract_ignore "GEMINI.md"
|
|
57
|
+
adapter_contract_ignore ".antigravity.md"
|
|
42
58
|
adapter_contract_ignore "$output/rules/"
|
|
43
59
|
adapter_contract_ignore "$output/agents/"
|
|
44
60
|
adapter_contract_ignore ".agents/skills/"
|
|
45
61
|
}
|
|
46
62
|
|
|
63
|
+
# Documented Antigravity limit: "Rules files are limited to 12,000 characters
|
|
64
|
+
# each." A rule over it is not truncated visibly — the tool decides what to do
|
|
65
|
+
# with it — so sync reports the overflow instead of guessing. A project may
|
|
66
|
+
# match another effective limit or turn the warning off.
|
|
67
|
+
# The count is awk's `length()`, which is characters where the awk build is
|
|
68
|
+
# locale-aware and bytes where it is not; the two agree on ASCII rules.
|
|
69
|
+
# Sets IS_AG_RULE_LIMIT (0 disables the check).
|
|
70
|
+
antigravity_rule_limit() {
|
|
71
|
+
local config_file="$1"
|
|
72
|
+
local setting
|
|
73
|
+
setting="$(get_target_field "$config_file" "antigravity" "warn_rule_limit")"
|
|
74
|
+
case "$setting" in
|
|
75
|
+
''|true) IS_AG_RULE_LIMIT=12000 ;;
|
|
76
|
+
false) IS_AG_RULE_LIMIT=0 ;;
|
|
77
|
+
*[!0-9]*|0|0*)
|
|
78
|
+
echo "ERROR: targets.antigravity.warn_rule_limit must be true, false, or a positive character count without leading zeros." >&2
|
|
79
|
+
return 1
|
|
80
|
+
;;
|
|
81
|
+
*) IS_AG_RULE_LIMIT="$setting" ;;
|
|
82
|
+
esac
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
sync_antigravity_rules() {
|
|
48
86
|
local repo_root="$1"
|
|
49
87
|
local config_file="$2"
|
|
50
88
|
local output_dir="$3"
|
|
51
89
|
|
|
90
|
+
# Read before the early return: a malformed setting is a manifest error
|
|
91
|
+
# whether or not this project happens to have a scoped rule today.
|
|
92
|
+
local rule_limit
|
|
93
|
+
antigravity_rule_limit "$config_file" || return 1
|
|
94
|
+
rule_limit="$IS_AG_RULE_LIMIT"
|
|
95
|
+
|
|
52
96
|
local src f
|
|
53
97
|
local -a files=()
|
|
54
98
|
load_yaml_list "$config_file" "rules"
|
|
@@ -78,25 +122,50 @@ sync_antigravity_rules() {
|
|
|
78
122
|
return 0
|
|
79
123
|
fi
|
|
80
124
|
|
|
81
|
-
# Path-scoped rule -> Glob activation (paths -> globs)
|
|
125
|
+
# Path-scoped rule -> Glob activation (paths -> globs). The rewrite is
|
|
126
|
+
# anchored inside the frontmatter block: a body line may legitimately start
|
|
127
|
+
# with `paths:` (a rule documenting rule syntax does exactly that), and
|
|
128
|
+
# rewriting it would corrupt the text it teaches.
|
|
129
|
+
#
|
|
130
|
+
# stdout carries nothing but the character-count overflow report, so the
|
|
131
|
+
# limit check costs no extra process.
|
|
132
|
+
local overflow
|
|
82
133
|
is_fin_awk_vars
|
|
83
|
-
awk "${IS_FIN_V[@]}" -v dst="$output_dir/rules" "$IS_AWK_LIB"'
|
|
134
|
+
overflow="$(awk "${IS_FIN_V[@]}" -v dst="$output_dir/rules" -v limit="$rule_limit" "$IS_AWK_LIB"'
|
|
135
|
+
function emit(line, rendered) {
|
|
136
|
+
rendered = fin_line(line)
|
|
137
|
+
print rendered > out
|
|
138
|
+
chars[out] += length(rendered) + 1
|
|
139
|
+
}
|
|
84
140
|
FNR == 1 {
|
|
85
141
|
if (out != "") close(out)
|
|
86
142
|
out = dst "/" base_name(FILENAME)
|
|
143
|
+
chars[out] = 0
|
|
144
|
+
fm = 0
|
|
87
145
|
}
|
|
88
|
-
{
|
|
89
|
-
sub(/\r$/, "")
|
|
90
|
-
sub(/^paths:/, "globs:")
|
|
91
|
-
}
|
|
146
|
+
{ sub(/\r$/, "") }
|
|
92
147
|
FNR == 1 {
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
if ($0 == "---") fm = 1
|
|
149
|
+
emit($0)
|
|
150
|
+
emit("trigger: glob")
|
|
95
151
|
next
|
|
96
152
|
}
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
|
|
153
|
+
fm == 1 && $0 == "---" { fm = 2; emit($0); next }
|
|
154
|
+
fm == 1 { sub(/^paths:/, "globs:") }
|
|
155
|
+
{ emit($0) }
|
|
156
|
+
END {
|
|
157
|
+
if (out != "") close(out)
|
|
158
|
+
if (limit > 0)
|
|
159
|
+
for (f in chars)
|
|
160
|
+
if (chars[f] > limit) printf "%s\t%d\n", f, chars[f]
|
|
161
|
+
}
|
|
162
|
+
' "${scoped[@]}" | sort)"
|
|
163
|
+
|
|
164
|
+
local over_file over_chars
|
|
165
|
+
while IFS=$'\t' read -r over_file over_chars; do
|
|
166
|
+
[ -n "$over_file" ] || continue
|
|
167
|
+
echo "WARNING: Antigravity limits a rule file to $rule_limit characters: ${over_file#"$repo_root/"} renders $over_chars; split the rule, shorten it, set targets.antigravity.warn_rule_limit to the effective limit, or set it to false to disable this warning." >&2
|
|
168
|
+
done <<< "$overflow"
|
|
100
169
|
|
|
101
170
|
for f in "${scoped[@]}"; do
|
|
102
171
|
base="${f##*/}"
|
|
@@ -146,18 +215,20 @@ sync_antigravity_agents() {
|
|
|
146
215
|
fi
|
|
147
216
|
header+="${LS}subagent: true"
|
|
148
217
|
header+="${LS}mainAgent: false"
|
|
149
|
-
# A readonly agent gets an explicit allowlist
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
218
|
+
# A readonly agent gets an explicit allowlist, and it may hold only
|
|
219
|
+
# names the vendor documents: "Specifying an unmapped or misspelled
|
|
220
|
+
# tool name in the `tools` list may cause the subagent process to
|
|
221
|
+
# hang during execution." The subagents page names four —
|
|
222
|
+
# view_file, replace_file_content, grep_search, run_command — and
|
|
223
|
+
# publishes no master list, so readonly takes the two that read.
|
|
224
|
+
# Web and directory tools appear in leaked prompt dumps only; a
|
|
225
|
+
# hang is too expensive a bet on an unpublished name.
|
|
153
226
|
# An unrestricted agent declares no list and inherits the main
|
|
154
227
|
# agent's tools, which is Antigravity's default.
|
|
155
228
|
if [ "$access" = "readonly" ]; then
|
|
156
229
|
header+="${LS}tools:"
|
|
157
230
|
header+="${LS} - view_file"
|
|
158
231
|
header+="${LS} - grep_search"
|
|
159
|
-
header+="${LS} - search_web"
|
|
160
|
-
header+="${LS} - read_url_content"
|
|
161
232
|
fi
|
|
162
233
|
header+="${LS}---"
|
|
163
234
|
header+="${LS}"
|
|
@@ -176,6 +247,31 @@ sync_antigravity_agents() {
|
|
|
176
247
|
echo " -> Agents: $count"
|
|
177
248
|
}
|
|
178
249
|
|
|
250
|
+
# Antigravity's resource paths are fixed, not configured: the docs call
|
|
251
|
+
# .agents/ a default with backward compatibility for .agent/, and name no
|
|
252
|
+
# setting that moves it. A configured output anywhere else therefore renders
|
|
253
|
+
# rules and agents the tool will never load, and leaves whatever the default
|
|
254
|
+
# path already holds behind as the only thing it does read. Sync still writes
|
|
255
|
+
# the requested path — rendering elsewhere for inspection is legitimate — but
|
|
256
|
+
# it says what the tool will actually see.
|
|
257
|
+
antigravity_warn_unread_output() {
|
|
258
|
+
local repo_root="$1"
|
|
259
|
+
local output_dir="$2"
|
|
260
|
+
|
|
261
|
+
# Compare the resolved path, not its spelling: `.agents`, `./.agents` and
|
|
262
|
+
# `.agents//` are the same directory, and the writer reaches it either way.
|
|
263
|
+
adapter_contract_rel_path "${output_dir#"$repo_root"/}"
|
|
264
|
+
local rel="$IS_ADAPTER_REL_PATH"
|
|
265
|
+
case "$rel" in
|
|
266
|
+
.agents|.agent) return 0 ;;
|
|
267
|
+
esac
|
|
268
|
+
|
|
269
|
+
echo "WARNING: Antigravity reads .agents/rules and .agents/agents (legacy .agent/), never a configured output: targets.antigravity.output '$rel' renders rules and agents no Antigravity surface loads." >&2
|
|
270
|
+
if [ -d "$repo_root/.agents/rules" ] || [ -d "$repo_root/.agents/agents" ]; then
|
|
271
|
+
echo " .agents/ still holds output from an earlier run; that stale copy is what Antigravity loads. Remove it, or set the output back to .agents." >&2
|
|
272
|
+
fi
|
|
273
|
+
}
|
|
274
|
+
|
|
179
275
|
sync_to_antigravity() {
|
|
180
276
|
local repo_root="$1"
|
|
181
277
|
local config_file="$2"
|
|
@@ -183,6 +279,8 @@ sync_to_antigravity() {
|
|
|
183
279
|
|
|
184
280
|
echo "=== Antigravity ==="
|
|
185
281
|
|
|
282
|
+
antigravity_warn_unread_output "$repo_root" "$output_dir"
|
|
283
|
+
|
|
186
284
|
rm -rf "$output_dir/rules" "$output_dir/agents"
|
|
187
285
|
mkdir -p "$output_dir/rules" "$output_dir/agents"
|
|
188
286
|
|
|
@@ -62,7 +62,10 @@ sync_cursor_rules() {
|
|
|
62
62
|
done < <(frontmatter_index "paths#" "${files[@]}")
|
|
63
63
|
[ "${#scoped[@]}" -gt 0 ] || return 0
|
|
64
64
|
|
|
65
|
-
# Path-scoped rule -> Auto Attached (paths -> globs)
|
|
65
|
+
# Path-scoped rule -> Auto Attached (paths -> globs). The rewrite is
|
|
66
|
+
# anchored inside the frontmatter block: a body line may legitimately start
|
|
67
|
+
# with `paths:` (a rule documenting rule syntax does exactly that), and
|
|
68
|
+
# rewriting it would corrupt the text it teaches.
|
|
66
69
|
is_fin_awk_vars
|
|
67
70
|
awk "${IS_FIN_V[@]}" -v dst="$output_dir/rules" "$IS_AWK_LIB"'
|
|
68
71
|
FNR == 1 {
|
|
@@ -70,12 +73,17 @@ sync_cursor_rules() {
|
|
|
70
73
|
nm = base_name(FILENAME)
|
|
71
74
|
sub(/\.md$/, ".mdc", nm)
|
|
72
75
|
out = dst "/" nm
|
|
76
|
+
fm = 0
|
|
73
77
|
}
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
{ sub(/\r$/, "") }
|
|
79
|
+
FNR == 1 {
|
|
80
|
+
if ($0 == "---") fm = 1
|
|
81
|
+
print fin_line($0) > out
|
|
82
|
+
print fin_line("alwaysApply: false") > out
|
|
83
|
+
next
|
|
77
84
|
}
|
|
78
|
-
|
|
85
|
+
fm == 1 && $0 == "---" { fm = 2; print fin_line($0) > out; next }
|
|
86
|
+
fm == 1 { sub(/^paths:/, "globs:") }
|
|
79
87
|
{ print fin_line($0) > out }
|
|
80
88
|
' "${scoped[@]}"
|
|
81
89
|
for f in "${scoped[@]}"; do
|
|
@@ -19,6 +19,79 @@ adapter_contract_function() {
|
|
|
19
19
|
printf 'adapter_contract_%s' "$1"
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
# --- cross-adapter ownership -------------------------------------------------
|
|
23
|
+
#
|
|
24
|
+
# `owned` is exclusive: one adapter writes and prunes that path. `managed` is
|
|
25
|
+
# shared on purpose — .agents/skills is filled by Antigravity, Codex, Pi and
|
|
26
|
+
# opencode from identical content — so two `managed` claims on one path are
|
|
27
|
+
# legal. Any other collision means two adapters prune each other: whichever
|
|
28
|
+
# runs last wins, the earlier adapter's output is gone, and both report
|
|
29
|
+
# success. `intelligence sync codex` with codex output set to `.agents` did
|
|
30
|
+
# exactly that to Antigravity's agents.
|
|
31
|
+
#
|
|
32
|
+
# Claims are compared by resolved path, never by spelling: `.agents`,
|
|
33
|
+
# `./.agents` and `.agents//` name one directory, `validate_output_path`
|
|
34
|
+
# resolves them alike, and a lexical comparison would miss the collision that
|
|
35
|
+
# actually happens on disk. (normalize_path_var comes from lib/common.sh, which
|
|
36
|
+
# both the engine and the CLI source before this file.)
|
|
37
|
+
#
|
|
38
|
+
# State is one "adapter<TAB>kind<TAB>path" record per line in IS_ADAPTER_CLAIMS.
|
|
39
|
+
# A conflict is reported through IS_ADAPTER_CLAIM_CONFLICT, never printed and
|
|
40
|
+
# never captured in a subshell: the accumulator has to survive the call.
|
|
41
|
+
# shellcheck disable=SC2034 # IS_ADAPTER_CLAIM_CONFLICT is read by callers in the engine and the CLI
|
|
42
|
+
adapter_claims_reset() {
|
|
43
|
+
IS_ADAPTER_CLAIMS=""
|
|
44
|
+
IS_ADAPTER_CLAIM_CONFLICT=""
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# adapter_contract_rel_path <path> — the repo-relative spelling every claim and
|
|
48
|
+
# every output comparison uses. Sets IS_ADAPTER_REL_PATH ("" for the repo root).
|
|
49
|
+
adapter_contract_rel_path() {
|
|
50
|
+
normalize_path_var "$1"
|
|
51
|
+
IS_ADAPTER_REL_PATH="${IS_NORM_PATH#/}"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# adapter_claims_add <adapter> <kind> <path>
|
|
55
|
+
# Registers the claim, or sets IS_ADAPTER_CLAIM_CONFLICT and returns 1.
|
|
56
|
+
# shellcheck disable=SC2034 # the conflict text is consumed by the caller
|
|
57
|
+
adapter_claims_add() {
|
|
58
|
+
local adapter="$1" kind="$2" path
|
|
59
|
+
adapter_contract_rel_path "$3"
|
|
60
|
+
path="$IS_ADAPTER_REL_PATH"
|
|
61
|
+
local prev_adapter prev_kind prev_path
|
|
62
|
+
while IFS=$'\t' read -r prev_adapter prev_kind prev_path; do
|
|
63
|
+
[ -n "$prev_adapter" ] || continue
|
|
64
|
+
[ "$prev_adapter" = "$adapter" ] && continue
|
|
65
|
+
case "$path" in
|
|
66
|
+
"$prev_path"|"$prev_path"/*) ;;
|
|
67
|
+
*) case "$prev_path" in "$path"/*) ;; *) continue ;; esac ;;
|
|
68
|
+
esac
|
|
69
|
+
# Two `managed` claims agree by design; anything else prunes.
|
|
70
|
+
[ "$kind" = "owned" ] || [ "$prev_kind" = "owned" ] || continue
|
|
71
|
+
if [ "$path" = "$prev_path" ]; then
|
|
72
|
+
IS_ADAPTER_CLAIM_CONFLICT="adapters '$prev_adapter' ($prev_kind) and '$adapter' ($kind) both claim '$path'; an owned path belongs to one adapter, so whichever syncs last prunes the other's output"
|
|
73
|
+
else
|
|
74
|
+
IS_ADAPTER_CLAIM_CONFLICT="adapter '$adapter' claims '$path' ($kind) nested in '$prev_path' ($prev_kind) claimed by '$prev_adapter'; whichever syncs last prunes the other's output"
|
|
75
|
+
fi
|
|
76
|
+
return 1
|
|
77
|
+
done <<< "$IS_ADAPTER_CLAIMS"
|
|
78
|
+
IS_ADAPTER_CLAIMS="$IS_ADAPTER_CLAIMS$adapter"$'\t'"$kind"$'\t'"$path"$'\n'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# adapter_claims_add_records <adapter> <records>
|
|
82
|
+
# Feeds every owned/managed record of one adapter through adapter_claims_add.
|
|
83
|
+
# Returns 1 on the first conflict, which stays in IS_ADAPTER_CLAIM_CONFLICT.
|
|
84
|
+
adapter_claims_add_records() {
|
|
85
|
+
local adapter="$1" records="$2" kind value
|
|
86
|
+
while IFS=$'\t' read -r kind value; do
|
|
87
|
+
case "$kind" in
|
|
88
|
+
owned|managed) ;;
|
|
89
|
+
*) continue ;;
|
|
90
|
+
esac
|
|
91
|
+
adapter_claims_add "$adapter" "$kind" "$value" || return 1
|
|
92
|
+
done <<< "$records"
|
|
93
|
+
}
|
|
94
|
+
|
|
22
95
|
# Reject records that could address anything outside the repository. Contract
|
|
23
96
|
# paths are always repo-relative; ignore/include records may contain globs.
|
|
24
97
|
adapter_contract_safe_path() {
|
package/engine/sync.sh
CHANGED
|
@@ -214,23 +214,50 @@ finish_sync_transaction() {
|
|
|
214
214
|
trap finish_sync_transaction EXIT
|
|
215
215
|
trap 'exit 130' INT TERM
|
|
216
216
|
|
|
217
|
+
# Ownership is checked across EVERY enabled adapter, not only the selected one:
|
|
218
|
+
# `intelligence sync codex` still prunes a path another adapter owns, so a
|
|
219
|
+
# filtered run that skipped the check would be the one that destroys output.
|
|
220
|
+
adapter_claims_reset
|
|
221
|
+
agents_dependents=""
|
|
222
|
+
|
|
223
|
+
# note_agents_dependent <adapter> — collect the AGENTS.md consumers for the
|
|
224
|
+
# root-path warning below. The list is built with its separators so no pattern
|
|
225
|
+
# substitution is needed: Bash 3.2 is the floor, and its ${v//} keeps the
|
|
226
|
+
# backslash of an escaped separator.
|
|
227
|
+
note_agents_dependent() {
|
|
228
|
+
[ -n "$agents_dependents" ] && agents_dependents="$agents_dependents, "
|
|
229
|
+
agents_dependents="$agents_dependents$1"
|
|
230
|
+
}
|
|
217
231
|
preflight_idx=0
|
|
218
232
|
while [ "$preflight_idx" -lt "${#ADAPTERS[@]}" ]; do
|
|
219
233
|
adapter="${ADAPTERS[$preflight_idx]}"
|
|
220
234
|
adapter_file="${ADAPTER_FILES[$preflight_idx]}"
|
|
221
235
|
preflight_idx=$((preflight_idx + 1))
|
|
222
|
-
if [ -n "$TARGET_FILTER" ] && [ "$adapter" != "$TARGET_FILTER" ]; then
|
|
223
|
-
continue
|
|
224
|
-
fi
|
|
225
236
|
target_enabled_var "$CONFIG_FILE" "$adapter"
|
|
226
237
|
[ "$IS_TGT_ENABLED" = "1" ] || continue
|
|
227
238
|
target_output_var "$CONFIG_FILE" "$adapter"
|
|
228
239
|
output="$IS_TGT_OUTPUT"
|
|
229
240
|
[ -n "$output" ] || output=".$adapter"
|
|
230
|
-
validate_output_path "$REPO_ROOT" "$CONFIG_FILE" "$adapter" "$REPO_ROOT/$output"
|
|
231
241
|
records="$(adapter_contract_records "$adapter" "$adapter_file" "$output")" || exit 1
|
|
242
|
+
|
|
243
|
+
if ! adapter_claims_add_records "$adapter" "$records"; then
|
|
244
|
+
echo "ERROR: $IS_ADAPTER_CLAIM_CONFLICT." >&2
|
|
245
|
+
echo " Give each adapter its own output: intelligence adapter list" >&2
|
|
246
|
+
exit 1
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
if [ -n "$TARGET_FILTER" ] && [ "$adapter" != "$TARGET_FILTER" ]; then
|
|
250
|
+
# Not selected: its claims are registered, its output is not touched.
|
|
251
|
+
while IFS=$'\t' read -r kind value; do
|
|
252
|
+
[ "$kind" = "requires" ] && [ "$value" = "agents" ] && note_agents_dependent "$adapter"
|
|
253
|
+
done <<< "$records"
|
|
254
|
+
continue
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
validate_output_path "$REPO_ROOT" "$CONFIG_FILE" "$adapter" "$REPO_ROOT/$output"
|
|
232
258
|
while IFS=$'\t' read -r kind value; do
|
|
233
259
|
[ "$kind" = "requires" ] || continue
|
|
260
|
+
[ "$value" = "agents" ] && note_agents_dependent "$adapter"
|
|
234
261
|
target_enabled_var "$CONFIG_FILE" "$value"
|
|
235
262
|
if [ "$IS_TGT_ENABLED" != "1" ]; then
|
|
236
263
|
echo "ERROR: targets.$adapter requires enabled target '$value'." >&2
|
|
@@ -244,6 +271,21 @@ while [ "$preflight_idx" -lt "${#ADAPTERS[@]}" ]; do
|
|
|
244
271
|
esac
|
|
245
272
|
done <<< "$records"
|
|
246
273
|
done
|
|
274
|
+
|
|
275
|
+
# Every adapter that requires `agents` skips always-on rules because AGENTS.md
|
|
276
|
+
# carries them — and each of those tools reads AGENTS.md at the workspace root
|
|
277
|
+
# only. Rendered anywhere else, the always-on rules reach no tool at all, and a
|
|
278
|
+
# stale root copy from an earlier run is what they keep reading.
|
|
279
|
+
if [ -n "$agents_dependents" ]; then
|
|
280
|
+
target_output_var "$CONFIG_FILE" "agents"
|
|
281
|
+
# Resolved, not lexical: `./` renders `./AGENTS.md`, which IS the
|
|
282
|
+
# workspace-root file every dependent adapter reads.
|
|
283
|
+
adapter_contract_rel_path "$(agents_output_path "${IS_TGT_OUTPUT:-.agents}")"
|
|
284
|
+
agents_rel="$IS_ADAPTER_REL_PATH"
|
|
285
|
+
if [ "$agents_rel" != "AGENTS.md" ]; then
|
|
286
|
+
echo "WARNING: targets.agents.output renders '$agents_rel', not the workspace-root AGENTS.md. These adapters skip always-on rules because AGENTS.md carries them, and they read it at the root only: $agents_dependents. No tool loads those rules from '$agents_rel'." >&2
|
|
287
|
+
fi
|
|
288
|
+
fi
|
|
247
289
|
SYNC_TX_ACTIVE=1
|
|
248
290
|
|
|
249
291
|
synced=0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainova-systems/intelligence",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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"
|
|
@@ -89,6 +89,15 @@ or unsupported contracts before enabling or syncing an adapter. `owned` and
|
|
|
89
89
|
`managed` paths form the transactional write-set: if any adapter fails, the
|
|
90
90
|
engine restores every selected adapter path to its pre-sync state.
|
|
91
91
|
|
|
92
|
+
`owned` is exclusive across enabled adapters. Two adapters may declare the same
|
|
93
|
+
`managed` path — that is how Antigravity, Codex, Pi and OpenCode share
|
|
94
|
+
`.agents/skills` — but an `owned` path that another enabled adapter also claims,
|
|
95
|
+
or that nests inside one, is refused by `sync`, `adapter enable` and
|
|
96
|
+
`status --check`. Without that refusal the adapter that syncs last prunes the
|
|
97
|
+
other's output and both runs report success. A filtered `sync <adapter>` is
|
|
98
|
+
checked the same way: it prunes the same paths while never loading the adapter
|
|
99
|
+
whose output it destroys.
|
|
100
|
+
|
|
92
101
|
For an existing `.vscodeignore`, `.npmignore`, or `.dockerignore`, enable/init
|
|
93
102
|
also excludes the configured adapter output plus its `owned`, `managed`, and
|
|
94
103
|
`legacy` paths from published or build artifacts. This packaging policy is
|
|
@@ -177,6 +186,21 @@ Every successful sync prints an adapter-agnostic `CONTEXT:` summary with source
|
|
|
177
186
|
|
|
178
187
|
The Codex adapter checks its `project_doc_max_bytes` default (32 KiB). When generated `AGENTS.md` exceeds it, sync prints the byte count and a sufficient Codex setting. The warning says "may truncate" because a developer can already have a larger override. `targets.codex.warn_project_doc_limit` accepts `true` or omission for the default threshold, a positive byte count to match another effective limit, or `false` to disable the Intelligence warning. Both inline and block target forms work, and the field does not change Codex configuration. The warning remains visible in `sync --compact`.
|
|
179
188
|
|
|
189
|
+
The Antigravity adapter checks the 12,000-character limit its documentation
|
|
190
|
+
states for a rule file. A generated scoped rule above it is reported with its
|
|
191
|
+
rendered character count, because the tool decides what to do with the excess
|
|
192
|
+
and never says so. `targets.antigravity.warn_rule_limit` accepts `true` or
|
|
193
|
+
omission for the documented limit, a positive character count to match another
|
|
194
|
+
effective limit, or `false` to disable the warning.
|
|
195
|
+
|
|
196
|
+
Antigravity also reads `.agents/rules` and `.agents/agents` only — its paths are
|
|
197
|
+
fixed, not configurable — so a `targets.antigravity.output` anywhere else warns
|
|
198
|
+
that nothing will load the result, and names an earlier `.agents/` copy still on
|
|
199
|
+
disk. The same applies to `AGENTS.md`: every adapter that requires the `agents`
|
|
200
|
+
target skips always-on rules because `AGENTS.md` carries them, and each reads it
|
|
201
|
+
at the workspace root, so a `targets.agents.output` elsewhere warns and lists the
|
|
202
|
+
adapters whose always-on rules then reach no tool.
|
|
203
|
+
|
|
180
204
|
### Skills
|
|
181
205
|
|
|
182
206
|
Skills follow the [Agent Skills standard](https://agentskills.io). Copy each skill directory as a complete bundle—not only `SKILL.md`—because its body may reference `references/`, `scripts/` or `assets/` beside it.
|
|
@@ -333,6 +333,7 @@ By default, commit the manifest, lock, project-owned content, `AGENTS.md`, and s
|
|
|
333
333
|
CLAUDE.md
|
|
334
334
|
.cursorrules
|
|
335
335
|
GEMINI.md
|
|
336
|
+
.antigravity.md
|
|
336
337
|
|
|
337
338
|
# Generated Claude and Cursor content; shared settings remain trackable
|
|
338
339
|
.claude/*
|
|
@@ -415,7 +416,7 @@ The permanent applied-schema key is the top-level scalar `schema_version` in `in
|
|
|
415
416
|
The public lifecycle is deliberately compact:
|
|
416
417
|
|
|
417
418
|
- `intelligence init [--preview|--apply]` is universal: it creates a new setup, aligns an existing Intelligence project, or plans/applies conversion of an eligible legacy Intelligence Sync project.
|
|
418
|
-
- `intelligence sync [adapter] [--compact]` first aligns an existing Intelligence project with the installed CLI
|
|
419
|
+
- `intelligence sync [adapter] [--compact] [--force]` first aligns an existing Intelligence project with the installed CLI and restores a missing store 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. In CI it refuses an alignment that would change tracked files and points to a local `intelligence init --apply` plus review/commit.
|
|
419
420
|
- `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.
|
|
420
421
|
- `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.
|
|
421
422
|
- `intelligence package add|remove|list|search` owns package inventory.
|
|
@@ -10,8 +10,10 @@ context: fork
|
|
|
10
10
|
1. Run `intelligence sync` (or `intelligence sync <adapter>` when one adapter
|
|
11
11
|
was requested). For Intelligence projects, this command first aligns project schema/content
|
|
12
12
|
with the installed CLI and restores a missing package store strictly from
|
|
13
|
-
`intelligence.lock`.
|
|
14
|
-
|
|
13
|
+
`intelligence.lock`. Unchanged inputs and outputs can reuse a successful run.
|
|
14
|
+
Use `--force` when a fresh render or a new search for unconfigured source
|
|
15
|
+
directories is requested; an unchanged run replays the previous diagnostics.
|
|
16
|
+
2. Require final `IS_STATUS=ok`; relay reported counts and any integrity,
|
|
15
17
|
model-drift or unsynced-source warnings.
|
|
16
18
|
3. In CI, a required tracked project upgrade is intentionally refused. Report
|
|
17
19
|
the instruction to run `intelligence init --apply` locally, review and
|