@ainova-systems/intelligence 0.11.0-rc.11 → 0.11.0-rc.14

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.
@@ -60,16 +60,38 @@ target_exists() {
60
60
  ' "$file"
61
61
  }
62
62
 
63
+ # target_names <manifest> — declared target keys in manifest order.
64
+ target_names() {
65
+ local file="$1"
66
+ [ -f "$file" ] || return 0
67
+ awk '
68
+ { sub(/\r$/, "") }
69
+ /^targets:[[:space:]]*$/ { in_targets = 1; next }
70
+ in_targets && /^[A-Za-z]/ { in_targets = 0 }
71
+ in_targets && /^ [a-z][a-z0-9_]*:/ {
72
+ line = $0
73
+ sub(/^ /, "", line)
74
+ sub(/:.*/, "", line)
75
+ print line
76
+ }
77
+ ' "$file"
78
+ }
79
+
63
80
  # target_set_enabled <manifest> <name> <true|false> <default-output>
64
81
  # Transactionally update only `enabled`, preserving every other target field
65
82
  # and comment. A missing target is added in the compact form used by init.
66
83
  target_set_enabled() {
67
84
  local file="$1" target="$2" enabled="$3" output="$4"
85
+ local target_missing=1
68
86
  [ -f "$file" ] || die "no such file: $file"
69
87
  case "$enabled" in true|false) ;; *) die "internal: invalid target state '$enabled'" ;; esac
88
+ target_exists "$file" "$target" && target_missing=0
70
89
  output="$(_yq_esc "$output")"
71
- _qmap_stage "$file" -v target="$target" -v enabled="$enabled" -v output="$output" '
90
+ _qmap_stage "$file" -v target="$target" -v enabled="$enabled" -v output="$output" -v target_missing="$target_missing" '
72
91
  function entry() { return " " target ": { enabled: " enabled ", output: \"" output "\" }" }
92
+ function insert_missing_target() {
93
+ if (in_targets && target_missing && !target_seen) { print entry(); target_seen = 1 }
94
+ }
73
95
  function add_enabled_to_inline(line, p) {
74
96
  p = index(line, "{")
75
97
  if (p > 0) return substr(line, 1, p) " enabled: " enabled "," substr(line, p + 1)
@@ -86,8 +108,20 @@ target_set_enabled() {
86
108
  }
87
109
  { sub(/\r$/, "") }
88
110
  /^targets:[[:space:]]*$/ { targets_seen = 1; in_targets = 1; print; next }
111
+ in_targets && target_missing && target_entry_seen && /^#/ {
112
+ flush_target()
113
+ insert_missing_target()
114
+ print
115
+ next
116
+ }
117
+ in_targets && target_missing && target_entry_seen && !in_target && /^[[:space:]]*$/ {
118
+ insert_missing_target()
119
+ print
120
+ next
121
+ }
89
122
  in_targets && /^[A-Za-z]/ { flush_targets() }
90
123
  in_targets && /^ [A-Za-z]/ {
124
+ target_entry_seen = 1
91
125
  flush_target()
92
126
  if ($0 ~ "^ " target ":[[:space:]]*") {
93
127
  target_seen = 1
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ # Preserve the pre-Intelligence state described by enabled adapter contracts.
3
+ # The snapshot is data-only and explicitly identifies itself as initial
4
+ # onboarding state so recovery skills never mistake it for generated output.
5
+
6
+ onboarding_source_paths() {
7
+ local root="$1" content_dir="$2" targets="$3" target output
8
+ for target in $targets; do
9
+ output="$(default_target_output "$target")"
10
+ adapter_contract_paths "$root" "$content_dir" "$target" "$output"
11
+ done | awk 'NF && !seen[$0]++'
12
+ }
13
+
14
+ onboarding_source_count() {
15
+ local root="$1" content_dir="$2" targets="$3" rel count=0
16
+ while IFS= read -r rel; do
17
+ [ -e "$root/$rel" ] || [ -L "$root/$rel" ] || continue
18
+ count=$((count + 1))
19
+ done < <(onboarding_source_paths "$root" "$content_dir" "$targets")
20
+ printf '%s\n' "$count"
21
+ }
22
+
23
+ preserve_onboarding_sources() {
24
+ local root="$1" content_dir="$2" targets="$3"
25
+ local rel src dest backup_rel backup stage target
26
+ backup_rel="$content_dir/_backup"
27
+ backup="$root/$backup_rel"
28
+ ONBOARDING_BACKUP_COUNT="$(onboarding_source_count "$root" "$content_dir" "$targets")"
29
+ ONBOARDING_BACKUP_REL=""
30
+ export ONBOARDING_BACKUP_COUNT ONBOARDING_BACKUP_REL
31
+ [ "$ONBOARDING_BACKUP_COUNT" -gt 0 ] || return 0
32
+ [ ! -e "$backup" ] || die "onboarding backup already exists at $backup_rel - review or move it before rerunning init"
33
+
34
+ mkdir -p "$root/$content_dir"
35
+ stage="$root/$content_dir/.backup-stage.$$"
36
+ [ ! -e "$stage" ] || die "temporary onboarding backup path already exists"
37
+ mkdir -p "$stage"
38
+
39
+ {
40
+ printf '# Intelligence initial-state backup contract v1\n'
41
+ printf 'state\tinitial-onboarding\n'
42
+ printf 'source\tpre-intelligence\n'
43
+ for target in $targets; do printf 'target\t%s\n' "$target"; done
44
+ } > "$stage/manifest.tsv"
45
+
46
+ while IFS= read -r rel; do
47
+ src="$root/$rel"
48
+ [ -e "$src" ] || [ -L "$src" ] || continue
49
+ dest="$stage/$rel"
50
+ mkdir -p "$(dirname "$dest")"
51
+ if ! cp -R "$src" "$dest"; then
52
+ rm -rf "$stage"
53
+ die "failed to preserve initial AI instruction path '$rel'"
54
+ fi
55
+ printf 'path\t%s\n' "$rel" >> "$stage/manifest.tsv"
56
+ done < <(onboarding_source_paths "$root" "$content_dir" "$targets")
57
+
58
+ mv "$stage" "$backup"
59
+ ensure_gitignore_header "$root"
60
+ gitignore_add_line "$root" "$backup_rel/"
61
+ ONBOARDING_BACKUP_REL="$backup_rel"
62
+ export ONBOARDING_BACKUP_REL
63
+ }
package/engine/ENGINE_SHA CHANGED
@@ -1 +1 @@
1
- 9be4945e8194e82d53d510e72b7a30769ff30e95
1
+ 920c2d9bf4364337c4a6dcb6557e38ff4111b972
@@ -7,8 +7,9 @@
7
7
  # occurrence with your adapter name before sourcing.
8
8
  #
9
9
  # Required:
10
- # 1. Implement sync_to_<name>() and its three content transforms
11
- # 2. Enable it after implementation: intelligence adapter enable <name>
10
+ # 1. Keep adapter_contract_<name>() accurate for every path this file writes
11
+ # 2. Implement sync_to_<name>() and its three content transforms
12
+ # 3. Enable it after implementation: intelligence adapter enable <name>
12
13
  # This adds the target to intelligence.yaml when it is absent:
13
14
  # targets:
14
15
  # <name>: { enabled: true, output: ".<name>" }
@@ -35,6 +36,22 @@
35
36
  # loaded. Keep this template position-independent: after scaffolding it lives
36
37
  # under the project's content directory, not beside engine/lib/.
37
38
 
39
+ # Ownership contract. Paths are repository-relative and derive from the
40
+ # configured target output passed as $1. `owned` paths are exclusively managed;
41
+ # `managed` paths share a directory with another adapter or hand-authored files.
42
+ # Add `legacy` inputs to the initial backup, `preserve` for settings that must
43
+ # never be replaced, and explicit ignore/include records for Git policy.
44
+ adapter_contract_<name>() {
45
+ local output="${1%/}"
46
+ adapter_contract_version 1
47
+ adapter_contract_owned "$output/rules"
48
+ adapter_contract_owned "$output/agents"
49
+ adapter_contract_owned "$output/skills"
50
+ adapter_contract_ignore "$output/rules/"
51
+ adapter_contract_ignore "$output/agents/"
52
+ adapter_contract_ignore "$output/skills/"
53
+ }
54
+
38
55
  # Sync rules for <agent-name>
39
56
  # Typical transformations:
40
57
  # - Copy as-is (like Claude)
@@ -223,6 +223,16 @@ agents_md_append_rules_list() {
223
223
  }
224
224
 
225
225
  # Main entry point for AGENTS.md adapter
226
+ adapter_contract_agents() {
227
+ local output="$1"
228
+ if [[ "$output" == */ ]] || [[ "$output" != *.md ]]; then
229
+ output="${output%/}/AGENTS.md"
230
+ fi
231
+ adapter_contract_version 1
232
+ adapter_contract_owned "$output"
233
+ adapter_contract_legacy "AGENTS.md"
234
+ }
235
+
226
236
  sync_to_agents() {
227
237
  local repo_root="$1"
228
238
  local config_file="$2"
@@ -231,10 +241,11 @@ sync_to_agents() {
231
241
  echo "=== AGENTS.md ==="
232
242
 
233
243
  # output_dir points at the target file path (e.g., /repo/AGENTS.md).
234
- # If it looks like a directory (trailing slash, existing dir, or no .md
235
- # extension), append default filename.
244
+ # If it looks like a directory lexically (trailing slash or no .md
245
+ # extension), append the default filename. The ownership contract uses the
246
+ # same rule, so filesystem state cannot make its write-set ambiguous.
236
247
  local output_file="$output_dir"
237
- if [ -d "$output_file" ] || [[ "$output_file" == */ ]] || [[ "$output_file" != *.md ]]; then
248
+ if [[ "$output_file" == */ ]] || [[ "$output_file" != *.md ]]; then
238
249
  output_file="${output_file%/}/AGENTS.md"
239
250
  fi
240
251
 
@@ -8,6 +8,21 @@
8
8
 
9
9
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
10
10
 
11
+ adapter_contract_claude() {
12
+ local output="${1%/}"
13
+ adapter_contract_version 1
14
+ adapter_contract_owned "$output/rules"
15
+ adapter_contract_owned "$output/agents"
16
+ adapter_contract_owned "$output/skills"
17
+ adapter_contract_legacy "CLAUDE.md"
18
+ adapter_contract_legacy "$output/commands"
19
+ adapter_contract_preserve "$output/settings.json"
20
+ adapter_contract_preserve "$output/settings.local.json"
21
+ adapter_contract_ignore "CLAUDE.md"
22
+ adapter_contract_ignore "$output/*"
23
+ adapter_contract_include "$output/settings.json"
24
+ }
25
+
11
26
  # Sync rules to Claude format (copy as-is, normalize LF)
12
27
  sync_claude_rules() {
13
28
  local repo_root="$1"
@@ -13,6 +13,16 @@
13
13
 
14
14
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
15
15
 
16
+ adapter_contract_codex() {
17
+ local output="${1%/}"
18
+ adapter_contract_version 1
19
+ adapter_contract_requires agents
20
+ adapter_contract_managed ".agents/skills"
21
+ adapter_contract_owned "$output/agents"
22
+ adapter_contract_ignore ".agents/skills/"
23
+ adapter_contract_ignore "$output/agents/"
24
+ }
25
+
16
26
  # Sync skills to Codex format (.agents/skills/{name}/SKILL.md)
17
27
  sync_codex_skills() {
18
28
  local repo_root="$1"
@@ -111,7 +121,7 @@ sync_to_codex() {
111
121
  sync_codex_skills "$repo_root" "$config_file" "$skills_dir"
112
122
 
113
123
  # Agents -> .codex/agents/
114
- local agents_dir="$repo_root/.codex/agents"
124
+ local agents_dir="$output_dir/agents"
115
125
  rm -rf "$agents_dir"
116
126
  mkdir -p "$agents_dir"
117
127
  sync_codex_agents "$repo_root" "$config_file" "$agents_dir"
@@ -14,6 +14,17 @@
14
14
 
15
15
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
16
16
 
17
+ adapter_contract_copilot() {
18
+ local output="${1%/}"
19
+ adapter_contract_version 1
20
+ adapter_contract_requires agents
21
+ adapter_contract_owned "$output/instructions"
22
+ adapter_contract_owned "$output/prompts"
23
+ adapter_contract_owned "$output/agents"
24
+ adapter_contract_owned "$output/skills"
25
+ adapter_contract_legacy "$output/copilot-instructions.md"
26
+ }
27
+
17
28
  # Sync rules to Copilot format. Only path-scoped rules are emitted —
18
29
  # always-on rules live in AGENTS.md (read by Copilot natively).
19
30
  sync_copilot_rules() {
@@ -13,6 +13,21 @@
13
13
 
14
14
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
15
15
 
16
+ adapter_contract_cursor() {
17
+ local output="${1%/}"
18
+ adapter_contract_version 1
19
+ adapter_contract_requires agents
20
+ adapter_contract_owned "$output/rules"
21
+ adapter_contract_owned "$output/agents"
22
+ adapter_contract_owned "$output/skills"
23
+ adapter_contract_legacy ".cursorrules"
24
+ adapter_contract_legacy "$output/commands"
25
+ adapter_contract_preserve "$output/settings.json"
26
+ adapter_contract_ignore ".cursorrules"
27
+ adapter_contract_ignore "$output/*"
28
+ adapter_contract_include "$output/settings.json"
29
+ }
30
+
16
31
  # Sync rules to Cursor format (.md -> .mdc, paths -> globs)
17
32
  sync_cursor_rules() {
18
33
  local repo_root="$1"
@@ -26,6 +26,17 @@
26
26
 
27
27
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
28
28
 
29
+ adapter_contract_opencode() {
30
+ local output="${1%/}"
31
+ adapter_contract_version 1
32
+ adapter_contract_requires agents
33
+ adapter_contract_managed ".agents/skills"
34
+ adapter_contract_owned "$output/agents"
35
+ adapter_contract_managed "$output/commands"
36
+ adapter_contract_ignore ".agents/skills/"
37
+ adapter_contract_ignore "$output/agents/"
38
+ }
39
+
29
40
  # Strip frontmatter, return body only.
30
41
  # Direct call to the shared `strip_frontmatter` helper in lib/common.sh —
31
42
  # duplicating the awk block here would violate the "all parsing in common.sh"
@@ -15,6 +15,20 @@
15
15
 
16
16
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
17
17
 
18
+ adapter_contract_pi() {
19
+ local output="${1%/}"
20
+ adapter_contract_version 1
21
+ adapter_contract_requires agents
22
+ adapter_contract_managed ".agents/skills"
23
+ adapter_contract_owned "$output/intelligence-sync"
24
+ adapter_contract_managed "$output/extensions"
25
+ adapter_contract_managed "$output/prompts"
26
+ adapter_contract_ignore ".agents/skills/"
27
+ adapter_contract_ignore "$output/intelligence-sync/"
28
+ adapter_contract_ignore "$output/extensions/intelligence-sync-rules.ts"
29
+ adapter_contract_ignore "$output/prompts/intelligence-agent-*.md"
30
+ }
31
+
18
32
  pi_ts_escape() {
19
33
  local s="$1"
20
34
  s="${s//\\/\\\\}"
@@ -193,13 +207,12 @@ sync_pi_agents() {
193
207
 
194
208
  access_note=""
195
209
  if [ "$access" = "readonly" ]; then
196
- access_note=$(cat <<'EOF'
197
- ## Access Mode
210
+ # Bash 3.2 (the macOS system shell) misparses an apostrophe in
211
+ # a quoted heredoc nested inside command substitution.
212
+ access_note="## Access Mode
198
213
 
199
214
  Default to read-only analysis. Do not change files or run mutating commands unless the user explicitly asks you to override this agent's normal restriction.
200
-
201
- EOF
202
- )
215
+ "
203
216
  fi
204
217
 
205
218
  {
@@ -0,0 +1,100 @@
1
+ #!/bin/bash
2
+ # Declarative adapter ownership contract shared by the engine and CLI.
3
+ #
4
+ # Every adapter exposes adapter_contract_<name> <configured-output>. The
5
+ # function emits tab-separated records through the helpers below. Keeping the
6
+ # declaration beside sync_to_<name>() makes backup, rollback, git policy and
7
+ # lifecycle checks consume the same ownership model as the writer itself.
8
+
9
+ adapter_contract_version() { printf 'version\t%s\n' "$1"; }
10
+ adapter_contract_requires() { printf 'requires\t%s\n' "$1"; }
11
+ adapter_contract_owned() { printf 'owned\t%s\n' "$1"; }
12
+ adapter_contract_managed() { printf 'managed\t%s\n' "$1"; }
13
+ adapter_contract_legacy() { printf 'legacy\t%s\n' "$1"; }
14
+ adapter_contract_preserve() { printf 'preserve\t%s\n' "$1"; }
15
+ adapter_contract_ignore() { printf 'ignore\t%s\n' "$1"; }
16
+ adapter_contract_include() { printf 'include\t%s\n' "$1"; }
17
+
18
+ adapter_contract_function() {
19
+ printf 'adapter_contract_%s' "$1"
20
+ }
21
+
22
+ # Reject records that could address anything outside the repository. Contract
23
+ # paths are always repo-relative; ignore/include records may contain globs.
24
+ adapter_contract_safe_path() {
25
+ local path="$1"
26
+ case "$path" in
27
+ ""|/*|*\\*|[A-Za-z]:*|..|../*|*/../*|*/..|*$'\t'*|*$'\n'*) return 1 ;;
28
+ *) return 0 ;;
29
+ esac
30
+ }
31
+
32
+ adapter_contract_safe_concrete_path() {
33
+ adapter_contract_safe_path "$1" || return 1
34
+ case "$1" in
35
+ *'*'*|*'?'*|*'['*) return 1 ;;
36
+ *) return 0 ;;
37
+ esac
38
+ }
39
+
40
+ # adapter_contract_records <adapter-name> <adapter-file> <configured-output>
41
+ # Source and query in a subshell so a project adapter cannot leak shell state
42
+ # into the caller. Project adapters are trusted executable code during sync;
43
+ # the isolation here is for correctness, not a security boundary.
44
+ adapter_contract_records() (
45
+ local name="$1" file="$2" output="$3" fn line kind value saw_version=0
46
+ # shellcheck source=/dev/null
47
+ source "$file"
48
+ fn="$(adapter_contract_function "$name")"
49
+ declare -F "$fn" >/dev/null 2>&1 || {
50
+ echo "ERROR: adapter '$name' has no $fn contract" >&2
51
+ return 1
52
+ }
53
+ while IFS= read -r line; do
54
+ [ -n "$line" ] || continue
55
+ kind="${line%%$'\t'*}"
56
+ if [ "$kind" = "$line" ]; then
57
+ echo "ERROR: adapter '$name' emitted a malformed contract record" >&2
58
+ return 1
59
+ fi
60
+ value="${line#*$'\t'}"
61
+ case "$kind" in
62
+ version)
63
+ [ "$value" = "1" ] || {
64
+ echo "ERROR: adapter '$name' uses unsupported contract version '$value'" >&2
65
+ return 1
66
+ }
67
+ saw_version=1
68
+ ;;
69
+ requires)
70
+ case "$value" in
71
+ ""|[!abcdefghijklmnopqrstuvwxyz]*|*[!abcdefghijklmnopqrstuvwxyz0123456789_]*)
72
+ echo "ERROR: adapter '$name' declares invalid requirement '$value'" >&2
73
+ return 1
74
+ ;;
75
+ esac
76
+ ;;
77
+ owned|managed|legacy|preserve)
78
+ adapter_contract_safe_concrete_path "$value" || {
79
+ echo "ERROR: adapter '$name' declares unsafe $kind path '$value'" >&2
80
+ return 1
81
+ }
82
+ ;;
83
+ ignore|include)
84
+ adapter_contract_safe_path "$value" || {
85
+ echo "ERROR: adapter '$name' declares unsafe $kind path '$value'" >&2
86
+ return 1
87
+ }
88
+ ;;
89
+ *)
90
+ echo "ERROR: adapter '$name' emitted unknown contract record '$kind'" >&2
91
+ return 1
92
+ ;;
93
+ esac
94
+ printf '%s\n' "$line"
95
+ done < <("$fn" "$output")
96
+ [ "$saw_version" -eq 1 ] || {
97
+ echo "ERROR: adapter '$name' contract did not declare version 1" >&2
98
+ return 1
99
+ }
100
+ )
package/engine/sync.sh CHANGED
@@ -19,6 +19,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
19
19
 
20
20
  source "$SCRIPT_DIR/lib/common.sh"
21
21
  source "$SCRIPT_DIR/lib/contract.sh"
22
+ source "$SCRIPT_DIR/lib/adapter-contract.sh"
22
23
 
23
24
  if [ -z "${CONFIG_FILE:-}" ] || [ ! -f "${CONFIG_FILE:-}" ]; then
24
25
  is_status config-missing "CONFIG_FILE=${CONFIG_FILE:-}"
@@ -85,31 +86,6 @@ echo " Config: $CONFIG_FILE"
85
86
  echo " Root: $REPO_ROOT"
86
87
  echo ""
87
88
 
88
- # Invariant: AGENTS.md is the canonical carrier of always-on rules for
89
- # Cursor / Copilot / Codex / Pi / opencode (their adapters skip always-on
90
- # rules to avoid duplication, since each tool reads AGENTS.md natively for
91
- # baseline project context). If those targets are enabled, `agents` must
92
- # also be enabled — otherwise always-on rules go nowhere for those tools.
93
- # Skip the check when the user requested a single target via $TARGET_FILTER:
94
- # they may be syncing only one IDE intentionally.
95
- if [ -z "$TARGET_FILTER" ]; then
96
- agents_enabled=$(is_target_enabled "$CONFIG_FILE" "agents")
97
- if [ "$agents_enabled" != "1" ]; then
98
- # AGENTS.md-dependent adapters: any tool whose adapter skips always-on
99
- # rule emission (because the tool reads AGENTS.md natively) must be
100
- # listed here. Add new adapters to this list when they ship.
101
- for tool in cursor copilot codex pi opencode; do
102
- if [ "$(is_target_enabled "$CONFIG_FILE" "$tool")" = "1" ]; then
103
- echo "ERROR: targets.$tool is enabled but targets.agents is not." >&2
104
- echo " $tool relies on AGENTS.md to deliver always-on rules — without it," >&2
105
- echo " always-on rules would be invisible to $tool." >&2
106
- echo " Either enable targets.agents in $CONFIG_FILE, or disable targets.$tool." >&2
107
- exit 1
108
- fi
109
- done
110
- fi
111
- fi
112
-
113
89
  # Lint frontmatter across all source files (rules, agents, skills).
114
90
  # Catches issues like unquoted colons that strict YAML consumers reject.
115
91
  for section in rules agents skills; do
@@ -167,6 +143,88 @@ for adapters_dir in "$SCRIPT_DIR/adapters" "$INTELLIGENCE_DIR/adapters"; do
167
143
  done
168
144
  done
169
145
 
146
+ # Validate every selected adapter contract before any output is touched, then
147
+ # snapshot the declared write-set. If a later adapter fails, the EXIT handler
148
+ # restores all earlier adapter outputs so sync is atomic from the repository's
149
+ # point of view.
150
+ SYNC_TX_DIR="$(mktemp -d -t intelligence-sync-XXXXXX)"
151
+ SYNC_TX_INDEX="$SYNC_TX_DIR/paths.tsv"
152
+ SYNC_TX_SEEN="$SYNC_TX_DIR/seen"
153
+ mkdir -p "$SYNC_TX_DIR/data"
154
+ : > "$SYNC_TX_INDEX"
155
+ : > "$SYNC_TX_SEEN"
156
+ SYNC_TX_ACTIVE=0
157
+
158
+ snapshot_sync_path() {
159
+ local adapter_name="$1" rel="$2" src index present=0
160
+ grep -Fqx -- "$rel" "$SYNC_TX_SEEN" && return 0
161
+ printf '%s\n' "$rel" >> "$SYNC_TX_SEEN"
162
+ validate_output_path "$REPO_ROOT" "$CONFIG_FILE" "$adapter_name" "$REPO_ROOT/$rel"
163
+ index="$(wc -l < "$SYNC_TX_INDEX" | tr -d ' ')"
164
+ src="$REPO_ROOT/$rel"
165
+ if [ -e "$src" ] || [ -L "$src" ]; then
166
+ cp -a "$src" "$SYNC_TX_DIR/data/$index"
167
+ present=1
168
+ fi
169
+ printf '%s\t%s\t%s\n' "$index" "$rel" "$present" >> "$SYNC_TX_INDEX"
170
+ }
171
+
172
+ restore_sync_snapshot() {
173
+ local index rel present dst
174
+ while IFS=$'\t' read -r index rel present; do
175
+ [ -n "$rel" ] || continue
176
+ dst="$REPO_ROOT/$rel"
177
+ rm -rf "$dst"
178
+ if [ "$present" = "1" ]; then
179
+ mkdir -p "$(dirname "$dst")"
180
+ cp -a "$SYNC_TX_DIR/data/$index" "$dst"
181
+ fi
182
+ done < "$SYNC_TX_INDEX"
183
+ }
184
+
185
+ finish_sync_transaction() {
186
+ local rc=$?
187
+ trap - EXIT INT TERM
188
+ set +e
189
+ if [ "${SYNC_TX_ACTIVE:-0}" = "1" ] && [ "$rc" -ne 0 ]; then
190
+ restore_sync_snapshot
191
+ echo "ERROR: sync failed; all adapter-owned paths were restored to their pre-sync state." >&2
192
+ fi
193
+ rm -rf "$SYNC_TX_DIR"
194
+ exit "$rc"
195
+ }
196
+ trap finish_sync_transaction EXIT
197
+ trap 'exit 130' INT TERM
198
+
199
+ preflight_idx=0
200
+ while [ "$preflight_idx" -lt "${#ADAPTERS[@]}" ]; do
201
+ adapter="${ADAPTERS[$preflight_idx]}"
202
+ adapter_file="${ADAPTER_FILES[$preflight_idx]}"
203
+ preflight_idx=$((preflight_idx + 1))
204
+ if [ -n "$TARGET_FILTER" ] && [ "$adapter" != "$TARGET_FILTER" ]; then
205
+ continue
206
+ fi
207
+ [ "$(is_target_enabled "$CONFIG_FILE" "$adapter")" = "1" ] || continue
208
+ output="$(get_target_output "$CONFIG_FILE" "$adapter")"
209
+ [ -n "$output" ] || output=".$adapter"
210
+ validate_output_path "$REPO_ROOT" "$CONFIG_FILE" "$adapter" "$REPO_ROOT/$output"
211
+ records="$(adapter_contract_records "$adapter" "$adapter_file" "$output")" || exit 1
212
+ while IFS=$'\t' read -r kind value; do
213
+ [ "$kind" = "requires" ] || continue
214
+ if [ "$(is_target_enabled "$CONFIG_FILE" "$value")" != "1" ]; then
215
+ echo "ERROR: targets.$adapter requires enabled target '$value'." >&2
216
+ echo " Enable it first: intelligence adapter enable $value" >&2
217
+ exit 1
218
+ fi
219
+ done <<< "$records"
220
+ while IFS=$'\t' read -r kind value; do
221
+ case "$kind" in
222
+ owned|managed) snapshot_sync_path "$adapter" "$value" ;;
223
+ esac
224
+ done <<< "$records"
225
+ done
226
+ SYNC_TX_ACTIVE=1
227
+
170
228
  synced=0
171
229
  adapter_count=${#ADAPTERS[@]}
172
230
  adapter_idx=0
@@ -224,6 +282,10 @@ if [ $synced -eq 0 ]; then
224
282
  exit 1
225
283
  fi
226
284
 
285
+ SYNC_TX_ACTIVE=0
286
+ rm -rf "$SYNC_TX_DIR"
287
+ trap - EXIT INT TERM
288
+
227
289
  # Warn about unsynced directories
228
290
  warn_unsynced "$REPO_ROOT" "$CONFIG_FILE"
229
291
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainova-systems/intelligence",
3
- "version": "0.11.0-rc.11",
3
+ "version": "0.11.0-rc.14",
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"
@@ -47,7 +47,7 @@ The per-artifact checks are procedure, so they live in the meta-skills rather th
47
47
  | `intelligence-extract-skill` | turn an observed workflow into a skill |
48
48
  | `intelligence-review-skills` | audit the layer for duplication, drift, size, hardcoded paths |
49
49
  | `intelligence-learn-from-repository` | propose the initial project-owned layer from repository evidence |
50
- | `intelligence-learn-from-context` | fold a session's lessons back into the layer |
50
+ | `intelligence-learn-from-context` | recover/finalize onboarding, or later fold a session lesson into the layer |
51
51
  | `intelligence-sync` | project the source to every tool channel |
52
52
  | `intelligence-update` | interpret and apply the CLI's unified update plan |
53
53
  | `intelligence-install-adapter` / `intelligence-uninstall-adapter` | research and manage a tool adapter |