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

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.
@@ -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
+ 5b60eb9e97337751d1e5390cae4919aedf26ab7a
@@ -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.13",
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 |
@@ -42,14 +42,25 @@ To stop syncing a target:
42
42
  intelligence adapter disable mytool
43
43
  ```
44
44
 
45
- Disabling changes only target state. Generated output is deliberately kept because a generic command cannot know which paths a custom adapter owns. Remove only documented owned paths after reviewing them. A disabled project adapter can be deleted with `intelligence adapter remove mytool`; removal prompts by default, accepts `--apply` for explicit non-interactive use, and also keeps generated output. Built-in adapter source cannot be removed. Use `intelligence adapter list` to inspect source, state and output.
45
+ Disabling changes only target state. Generated output is deliberately kept so disabling is reversible; the adapter contract identifies its paths for review. A disabled project adapter can be deleted with `intelligence adapter remove mytool`; removal prompts by default, accepts `--apply` for explicit non-interactive use, and also keeps generated output. Built-in adapter source cannot be removed. Use `intelligence adapter list` to inspect source, state and output.
46
46
 
47
- ## Required function
47
+ ## Required interface
48
48
 
49
- The file name and function name form the adapter's identity:
49
+ The file name, contract function and sync function form the adapter's identity:
50
50
 
51
51
  ```bash
52
52
  # <content-dir>/adapters/mytool.sh
53
+ adapter_contract_mytool() {
54
+ local output="${1%/}"
55
+ adapter_contract_version 1
56
+ adapter_contract_owned "$output/rules"
57
+ adapter_contract_owned "$output/agents"
58
+ adapter_contract_owned "$output/skills"
59
+ adapter_contract_ignore "$output/rules/"
60
+ adapter_contract_ignore "$output/agents/"
61
+ adapter_contract_ignore "$output/skills/"
62
+ }
63
+
53
64
  sync_to_mytool() {
54
65
  local repo_root="$1"
55
66
  local config_file="$2"
@@ -59,6 +70,25 @@ sync_to_mytool() {
59
70
  }
60
71
  ```
61
72
 
73
+ The contract accepts the configured repo-relative output path and emits only
74
+ records through these helpers:
75
+
76
+ | Record | Meaning |
77
+ |---|---|
78
+ | `adapter_contract_version 1` | Required interface version |
79
+ | `adapter_contract_requires <name>` | Another target that must be enabled for a full sync |
80
+ | `adapter_contract_owned <path>` | Path exclusively regenerated by this adapter |
81
+ | `adapter_contract_managed <path>` | Shared or marker-managed path modified by this adapter |
82
+ | `adapter_contract_legacy <path>` | Pre-Intelligence input preserved in the initial backup |
83
+ | `adapter_contract_preserve <path>` | Settings or state preserved in place and included in the initial backup |
84
+ | `adapter_contract_ignore <pattern>` | Exact `.gitignore` pattern managed on enable/init |
85
+ | `adapter_contract_include <pattern>` | Exact negated `.gitignore` pattern managed on enable/init |
86
+
87
+ All paths are repository-relative. The CLI refuses missing, malformed, unsafe,
88
+ or unsupported contracts before enabling or syncing an adapter. `owned` and
89
+ `managed` paths form the transactional write-set: if any adapter fails, the
90
+ engine restores every selected adapter path to its pre-sync state.
91
+
62
92
  The engine calls:
63
93
 
64
94
  ```text
@@ -177,8 +207,8 @@ Call `finalize_output_file` on every text file after transformation. It expands
177
207
 
178
208
  Adapters regenerate output, so cleanup is part of their public contract.
179
209
 
180
- 1. Delete only paths the adapter owns. Preserve sibling settings, commands, extensions, workflows and hand-authored files.
181
- 2. Make ownership obvious in `sync_to_<name>()`; the same list is what a user removes after disabling or uninstalling the adapter.
210
+ 1. Delete only paths declared `owned`. Preserve sibling settings, commands, extensions, workflows and hand-authored files.
211
+ 2. Keep `adapter_contract_<name>()` exactly aligned with every path `sync_to_<name>()` writes or deletes.
182
212
  3. Use marker-based cleanup when generated and hand-authored files share a directory. The OpenCode adapter is the reference implementation.
183
213
  4. Use `sync_open_skill_dirs` for `.agents/skills/`; multiple adapters share it.
184
214
  5. Write only beneath the supplied `output_dir`, except for an explicitly shared standard path handled by a shared helper.
@@ -211,6 +241,13 @@ sync_mytool_rules() {
211
241
  done < <(read_yaml_list "$config_file" "rules")
212
242
  }
213
243
 
244
+ adapter_contract_mytool() {
245
+ local output="${1%/}"
246
+ adapter_contract_version 1
247
+ adapter_contract_owned "$output/rules"
248
+ adapter_contract_ignore "$output/rules/"
249
+ }
250
+
214
251
  sync_to_mytool() {
215
252
  local repo_root="$1" config_file="$2" output_dir="$3"
216
253
 
@@ -234,6 +271,8 @@ Verify:
234
271
  - skill resources are present;
235
272
  - no literal layout tokens remain;
236
273
  - hand-authored siblings under the tool root survive;
274
+ - a deliberately failing later adapter restores all earlier output byte-for-byte;
275
+ - `intelligence status --check` accepts the contract and its Git policy;
237
276
  - output paths cannot overlap sources or escape the repository;
238
277
  - a second sync produces no Git diff.
239
278