@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.
@@ -137,10 +137,32 @@ case "$action" in
137
137
  assert_valid_target_name "$name"
138
138
  require_cli_project
139
139
  ensure_project_current "$IP_ROOT"
140
- bash "$CLI_DIR/internal/target-state.sh" "$action" "$name"
141
140
  if [ "$action" = "enable" ]; then
142
- exec bash "$CLI_DIR/commands/sync.sh"
141
+ state_stage="$(mktemp -d -t intelligence-adapter-state-XXXXXX)"
142
+ cp "$IP_ROOT/intelligence.yaml" "$state_stage/intelligence.yaml"
143
+ gitignore_existed=0
144
+ if [ -f "$IP_ROOT/.gitignore" ]; then
145
+ gitignore_existed=1
146
+ cp "$IP_ROOT/.gitignore" "$state_stage/gitignore"
147
+ fi
148
+ enable_rc=0
149
+ bash "$CLI_DIR/internal/target-state.sh" "$action" "$name" || enable_rc=$?
150
+ if [ "$enable_rc" -eq 0 ]; then
151
+ bash "$CLI_DIR/commands/sync.sh" || enable_rc=$?
152
+ fi
153
+ if [ "$enable_rc" -ne 0 ]; then
154
+ cp "$state_stage/intelligence.yaml" "$IP_ROOT/intelligence.yaml"
155
+ if [ "$gitignore_existed" -eq 1 ]; then
156
+ cp "$state_stage/gitignore" "$IP_ROOT/.gitignore"
157
+ else
158
+ rm -f "$IP_ROOT/.gitignore"
159
+ fi
160
+ echo "ERROR: adapter enable failed; manifest and Git policy were restored." >&2
161
+ fi
162
+ rm -rf "$state_stage"
163
+ exit "$enable_rc"
143
164
  fi
165
+ bash "$CLI_DIR/internal/target-state.sh" "$action" "$name"
144
166
  ;;
145
167
  remove)
146
168
  [ -n "$name" ] || die "usage: intelligence adapter remove <name> [--apply]"
@@ -148,6 +170,14 @@ case "$action" in
148
170
  adapter_remove "$@"
149
171
  ;;
150
172
  *)
173
+ case "$name" in
174
+ list)
175
+ die "adapter action comes first - run: intelligence adapter list"
176
+ ;;
177
+ create|enable|disable|remove)
178
+ die "adapter action comes first - run: intelligence adapter $name $action"
179
+ ;;
180
+ esac
151
181
  die "usage: intelligence adapter <list|create|enable|disable|remove> [name]"
152
182
  ;;
153
183
  esac
@@ -12,6 +12,47 @@ source "$CLI_DIR/lib/cli-common.sh"
12
12
 
13
13
  targets_arg="" no_sync=0 content_dir="intelligence" bare=0
14
14
  targets_set=0 dir_set=0 bare_set=0 preview=0 apply=0 force=0
15
+
16
+ print_new_project_onboarding() {
17
+ local heading="Intelligence ready"
18
+ [ "$no_sync" -eq 0 ] || heading="Next steps"
19
+
20
+ echo ""
21
+ echo "=== $heading ==="
22
+ if [ -n "${ONBOARDING_BACKUP_REL:-}" ]; then
23
+ echo " Existing AI instructions preserved:"
24
+ echo " $ONBOARDING_BACKUP_REL ($ONBOARDING_BACKUP_COUNT path(s))"
25
+ if [ "$bare" -eq 0 ]; then
26
+ echo " The learn skill will migrate them before proposing backup removal."
27
+ else
28
+ echo " Review and migrate them before removing this backup."
29
+ fi
30
+ fi
31
+ if [ "$no_sync" -eq 1 ]; then
32
+ echo " Run first:"
33
+ echo " intelligence sync"
34
+ fi
35
+ if [ "$bare" -eq 0 ]; then
36
+ echo " Finalize repository onboarding:"
37
+ echo " Ask your agent to run /intelligence-learn-from-context"
38
+ echo " It recognizes the initial backup, recovers setup if needed, then proposes repository-specific migration after approval."
39
+ echo " Recommended starter package:"
40
+ echo " intelligence package add @ainova-systems/core"
41
+ fi
42
+ echo " Review adapters:"
43
+ echo " intelligence adapter list"
44
+ echo " intelligence adapter enable codex"
45
+ echo " intelligence adapter disable cursor"
46
+ echo " Version control:"
47
+ echo " Generated adapter output is gitignored by CLI-owned path."
48
+ echo " Commit intelligence.yaml, intelligence.lock, intelligence/, AGENTS.md, and .github/."
49
+ echo " Shared tool settings remain trackable; review the exact ownership policy:"
50
+ echo " https://github.com/ainova-systems/intelligence/blob/main/packages/sync/references/conventions.md#generated-output-and-version-control"
51
+ echo " Write your own:"
52
+ echo " create $content_dir/rules/context.md, then intelligence sync"
53
+ echo " Docs: https://github.com/ainova-systems/intelligence#readme"
54
+ }
55
+
15
56
  while [ $# -gt 0 ]; do
16
57
  case "$1" in
17
58
  --targets) shift; targets_arg="${1:-}"; targets_set=1 ;;
@@ -68,9 +109,25 @@ case "$IP_MODE" in
68
109
  ensure_project_current "$IP_ROOT"
69
110
  fi
70
111
  restore_project_store_if_missing "$IP_ROOT"
112
+ ensure_manifest_gitignore "$IP_ROOT" "$IP_ROOT/intelligence.yaml"
71
113
  [ "$no_sync" -eq 1 ] && exit 0
72
114
  cd "$IP_ROOT"
73
- exec bash "$CLI_DIR/commands/sync.sh"
115
+ sync_rc=0
116
+ bash "$CLI_DIR/commands/sync.sh" || sync_rc=$?
117
+ if [ "$sync_rc" -ne 0 ]; then
118
+ content_dir="$(manifest_intelligence_dir "$IP_ROOT/intelligence.yaml")"
119
+ recovery_backup=""
120
+ if [ -f "$IP_ROOT/$content_dir/_backup/manifest.tsv" ]; then
121
+ recovery_backup="; initial backup: $content_dir/_backup/manifest.tsv"
122
+ fi
123
+ echo "" >&2
124
+ echo "=== Intelligence setup needs recovery ===" >&2
125
+ echo " No partial adapter update was kept; sync restored its pre-run outputs." >&2
126
+ echo " Ask your agent to read:" >&2
127
+ echo " $SYNC_PKG_STORE/skills/intelligence-learn-from-context/SKILL.md" >&2
128
+ echo " and finalize this Intelligence setup$recovery_backup." >&2
129
+ fi
130
+ exit "$sync_rc"
74
131
  ;;
75
132
  legacy)
76
133
  [ "$targets_set" -eq 0 ] || die "--targets applies only when creating a new project"
@@ -117,17 +174,30 @@ if [ -n "$targets_arg" ]; then
117
174
  done
118
175
  else
119
176
  if [ -d "$root/.claude" ] || [ -f "$root/CLAUDE.md" ]; then targets="$targets claude"; fi
120
- [ -d "$root/.cursor" ] && targets="$targets cursor"
121
- [ -d "$root/.codex" ] && targets="$targets codex"
177
+ if [ -d "$root/.cursor" ] || [ -f "$root/.cursorrules" ]; then targets="$targets cursor"; fi
178
+ if [ -d "$root/.codex" ] || [ -d "$root/.agents" ]; then targets="$targets codex"; fi
122
179
  [ -d "$root/.pi" ] && targets="$targets pi"
123
180
  [ -d "$root/.opencode" ] && targets="$targets opencode"
124
- [ -d "$root/.github" ] && [ -d "$root/.github/instructions" ] && targets="$targets copilot"
181
+ if [ -f "$root/.github/copilot-instructions.md" ] \
182
+ || [ -d "$root/.github/instructions" ] \
183
+ || [ -d "$root/.github/agents" ] \
184
+ || [ -d "$root/.github/skills" ]; then
185
+ targets="$targets copilot"
186
+ fi
125
187
  if [ "$targets" = "agents" ]; then
126
188
  echo " NOTE: no tool markers found (.claude/, CLAUDE.md, .cursor/, …) — only AGENTS.md is enabled." >&2
127
189
  echo " Name your tools explicitly: intelligence init --targets claude,cursor (or edit targets: later)" >&2
128
190
  fi
129
191
  fi
130
192
 
193
+ onboarding_count="$(onboarding_source_count "$root" "$content_dir" "$targets")"
194
+ onboarding_has_agents=0
195
+ [ -f "$root/AGENTS.md" ] && onboarding_has_agents=1
196
+ if [ "$preview" -eq 0 ] && [ "$onboarding_count" -gt 0 ] \
197
+ && [ -e "$root/$content_dir/_backup" ]; then
198
+ die "onboarding backup already exists at $content_dir/_backup - review or move it before rerunning init"
199
+ fi
200
+
131
201
  if [ "$preview" -eq 1 ]; then
132
202
  echo "project: no Intelligence setup found"
133
203
  echo " would create intelligence.yaml and intelligence.lock"
@@ -141,6 +211,9 @@ if [ "$preview" -eq 1 ]; then
141
211
  else
142
212
  echo " would enable adapters:$targets, then sync"
143
213
  fi
214
+ if [ "$onboarding_count" -gt 0 ]; then
215
+ echo " would preserve $onboarding_count existing AI instruction path(s) in $content_dir/_backup before sync"
216
+ fi
144
217
  exit 0
145
218
  fi
146
219
 
@@ -175,7 +248,21 @@ manifest="$root/intelligence.yaml"
175
248
  echo "# Generated output, one entry per tool. Add or drop tools freely."
176
249
  echo "targets:"
177
250
  for t in $targets; do
178
- echo " $t: { enabled: true, output: \"$(default_target_output "$t")\" }"
251
+ if [ "$t" = "agents" ] && [ "$onboarding_has_agents" -eq 1 ]; then
252
+ echo " agents:"
253
+ echo " enabled: true"
254
+ echo " output: \"AGENTS.md\""
255
+ echo " header: |"
256
+ echo " > Intelligence onboarding is pending. Before repository work, read"
257
+ echo " > \`$content_dir/_backup/AGENTS.md\`, which preserves the original project instructions."
258
+ if [ "$bare" -eq 0 ]; then
259
+ echo " > Run /intelligence-learn-from-context to migrate and verify them before removing the backup."
260
+ else
261
+ echo " > Review and migrate that backup before removing it."
262
+ fi
263
+ else
264
+ echo " $t: { enabled: true, output: \"$(default_target_output "$t")\" }"
265
+ fi
179
266
  done
180
267
  echo ""
181
268
  echo "# Packages are managed with 'intelligence package add|search|list':"
@@ -202,13 +289,10 @@ manifest="$root/intelligence.yaml"
202
289
  } > "$manifest"
203
290
 
204
291
  # The package store is restorable state, never committed — the npm model.
205
- if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
206
- {
207
- [ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
208
- echo "# intelligence CLI package store (restored automatically by 'intelligence sync')"
209
- echo ".intelligence/"
210
- } >> "$root/.gitignore"
211
- fi
292
+ ensure_base_gitignore "$root"
293
+ for target in $targets; do
294
+ ensure_target_gitignore "$root" "$manifest" "$target"
295
+ done
212
296
 
213
297
  # The engine's own content (meta-skills, authoring rule, engine agents) is an
214
298
  # ordinary package: auto-selected, opted out with --bare, removable later with
@@ -218,16 +302,27 @@ if [ "$bare" -eq 0 ]; then
218
302
  sync_pkg_install "$root"
219
303
  fi
220
304
 
305
+ preserve_onboarding_sources "$root" "$content_dir" "$targets"
306
+
221
307
  echo "initialized: intelligence.yaml (targets:$(printf ' %s' $targets))"
222
308
  [ "$bare" -eq 1 ] && echo " bare setup: no packages — engine meta-skills not installed"
223
- echo " add packages: intelligence package add @ainova-systems/core (browse: intelligence package search)"
224
- echo " write your own: create $content_dir/rules/context.md, then intelligence sync"
225
- if [ "$bare" -eq 0 ]; then
226
- echo " tailor this repository: ask your agent to run /intelligence-learn-from-repository after sync"
227
- fi
228
- echo " docs: https://github.com/ainova-systems/intelligence#readme"
229
-
230
309
  if [ "$no_sync" -eq 0 ]; then
231
310
  cd "$root"
232
- exec bash "$CLI_DIR/commands/sync.sh"
311
+ sync_rc=0
312
+ bash "$CLI_DIR/commands/sync.sh" --compact || sync_rc=$?
313
+ if [ "$sync_rc" -ne 0 ]; then
314
+ echo "" >&2
315
+ echo "=== Intelligence setup needs recovery ===" >&2
316
+ echo " No partial adapter update was kept; sync restored its pre-run outputs." >&2
317
+ if [ "$bare" -eq 0 ]; then
318
+ echo " Ask your agent to read:" >&2
319
+ echo " $SYNC_PKG_STORE/skills/intelligence-learn-from-context/SKILL.md" >&2
320
+ echo " and finalize the initial Intelligence setup. The snapshot is $content_dir/_backup/manifest.tsv." >&2
321
+ else
322
+ echo " Fix the reported error, then rerun: intelligence init" >&2
323
+ fi
324
+ exit "$sync_rc"
325
+ fi
233
326
  fi
327
+
328
+ print_new_project_onboarding
@@ -1,27 +1,80 @@
1
1
  #!/bin/bash
2
- # intelligence sync [target] — render intelligence to every enabled tool.
2
+ # intelligence sync [target] [--compact] - render intelligence to enabled tools.
3
3
  set -euo pipefail
4
4
  source "$CLI_DIR/lib/cli-common.sh"
5
5
 
6
- detect_project
7
- case "$IP_MODE" in
8
- cli)
9
- # `sync` is the normal fresh-clone command. A newer globally installed
10
- # CLI aligns the current Intelligence project first (except in CI, where a
11
- # tracked migration must be reviewed and committed locally), and a
12
- # missing ignored store is restored strictly from the committed lock.
13
- ensure_project_current "$IP_ROOT"
14
- restore_project_store_if_missing "$IP_ROOT"
15
- export_engine_env "$IP_ROOT"
16
- exec bash "$IS_ENGINE_DIR/sync.sh" "$@"
17
- ;;
18
- legacy)
19
- # A vendored project syncs with ITS engine — its pin is the contract,
20
- # and a newer bundled engine must not generate against an older schema.
21
- echo "NOTE: legacy Intelligence Sync project — delegating to $IP_MODULE_DIR/scripts/sync.sh. 'intelligence init' converts it into an Intelligence project." >&2
22
- exec bash "$IP_MODULE_DIR/scripts/sync.sh" "$@"
23
- ;;
24
- *)
25
- die "no intelligence project found here — run 'intelligence init'"
26
- ;;
27
- esac
6
+ compact=0
7
+ target=""
8
+ while [ $# -gt 0 ]; do
9
+ case "$1" in
10
+ --compact) compact=1 ;;
11
+ -*) die "unknown option '$1'" ;;
12
+ *)
13
+ [ -z "$target" ] || die "usage: intelligence sync [adapter] [--compact]"
14
+ target="$1"
15
+ ;;
16
+ esac
17
+ shift
18
+ done
19
+
20
+ run_sync() {
21
+ detect_project || return $?
22
+ case "$IP_MODE" in
23
+ cli)
24
+ # `sync` is the normal fresh-clone command. A newer globally installed
25
+ # CLI aligns the current Intelligence project first (except in CI, where a
26
+ # tracked migration must be reviewed and committed locally), and a
27
+ # missing ignored store is restored strictly from the committed lock.
28
+ ensure_project_current "$IP_ROOT" || return $?
29
+ restore_project_store_if_missing "$IP_ROOT" || return $?
30
+ export_engine_env "$IP_ROOT" || return $?
31
+ if [ -n "$target" ]; then
32
+ bash "$IS_ENGINE_DIR/sync.sh" "$target" || return $?
33
+ else
34
+ bash "$IS_ENGINE_DIR/sync.sh" || return $?
35
+ fi
36
+ ;;
37
+ legacy)
38
+ [ "$compact" -eq 0 ] || die "--compact is unavailable for a legacy Intelligence Sync project - run 'intelligence init' to convert it first"
39
+ # A vendored project syncs with its own engine: its pin is the
40
+ # contract, and a newer bundled engine must not generate against an
41
+ # older schema.
42
+ echo "NOTE: legacy Intelligence Sync project - delegating to $IP_MODULE_DIR/scripts/sync.sh. 'intelligence init' converts it into an Intelligence project." >&2
43
+ if [ -n "$target" ]; then
44
+ bash "$IP_MODULE_DIR/scripts/sync.sh" "$target" || return $?
45
+ else
46
+ bash "$IP_MODULE_DIR/scripts/sync.sh" || return $?
47
+ fi
48
+ ;;
49
+ *)
50
+ die "no intelligence project found here - run 'intelligence init'"
51
+ ;;
52
+ esac
53
+ }
54
+
55
+ if [ "$compact" -eq 0 ]; then
56
+ run_sync
57
+ exit $?
58
+ fi
59
+
60
+ # Compact mode is intentionally quiet only on success. Buffering the complete
61
+ # combined stream means a failure still returns every diagnostic emitted by
62
+ # lifecycle preflight, locked restore or the engine, together with its real rc.
63
+ compact_output="$(mktemp -t intelligence-sync-XXXXXX)"
64
+ trap 'rm -f "$compact_output"' EXIT
65
+ rc=0
66
+ (run_sync) > "$compact_output" 2>&1 || rc=$?
67
+ if [ "$rc" -ne 0 ]; then
68
+ cat "$compact_output" >&2
69
+ exit "$rc"
70
+ fi
71
+
72
+ status_line="$(grep '^IS_STATUS=ok\($\| \)' "$compact_output" | tail -1 || true)"
73
+ done_line="$(grep '^=== Done:' "$compact_output" | tail -1 || true)"
74
+ if [ -z "$status_line" ] || [ -z "$done_line" ]; then
75
+ cat "$compact_output" >&2
76
+ echo "ERROR: sync succeeded without its final status contract" >&2
77
+ exit 1
78
+ fi
79
+ echo "$status_line"
80
+ echo "$done_line"
package/cli/intelligence CHANGED
@@ -46,7 +46,8 @@ 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] Restore locked content if needed, then render enabled adapters
49
+ sync [adapter] [--compact] Restore locked content, then render enabled adapters
50
+ --compact On success show only final status; on failure show diagnostics
50
51
  update [@scope/name] Show an update plan; ask before applying it
51
52
  --preview Show the plan without asking or writing
52
53
  --apply Apply the plan without asking
@@ -76,6 +77,20 @@ case "$cmd" in
76
77
  esac
77
78
  cmd_file="$CLI_DIR/commands/$cmd.sh"
78
79
  if [ ! -f "$cmd_file" ]; then
80
+ case "$cmd" in
81
+ adapters)
82
+ echo "ERROR: unknown command 'adapters'. Use the singular command: intelligence adapter list" >&2
83
+ exit 1
84
+ ;;
85
+ packages)
86
+ echo "ERROR: unknown command 'packages'. Use the singular command: intelligence package list" >&2
87
+ exit 1
88
+ ;;
89
+ registries)
90
+ echo "ERROR: unknown command 'registries'. Use the singular command: intelligence registry list" >&2
91
+ exit 1
92
+ ;;
93
+ esac
79
94
  echo "ERROR: unknown command '$cmd'. Run 'intelligence help'." >&2
80
95
  exit 1
81
96
  fi
@@ -86,6 +86,46 @@ while IFS= read -r name; do
86
86
  fi
87
87
  done < <(qmap_keys "$manifest" "packages")
88
88
 
89
+ # Adapter contracts are the shared source of truth for dependencies, rollback,
90
+ # onboarding backup and Git policy. Validate every declared target, including
91
+ # disabled ones, so a later enable cannot reveal a stale custom adapter.
92
+ content_dir="$(manifest_intelligence_dir "$manifest")"
93
+ while IFS= read -r target; do
94
+ [ -n "$target" ] || continue
95
+ output="$(get_target_output "$manifest" "$target")"
96
+ [ -n "$output" ] || output="$(default_target_output "$target")"
97
+ adapter_file="$(adapter_file_for "$IP_ROOT" "$content_dir" "$target" 2>/dev/null || true)"
98
+ if [ -z "$adapter_file" ]; then
99
+ warn "adapter '$target' is declared but its implementation is missing"
100
+ continue
101
+ fi
102
+ records="$(adapter_contract_records "$target" "$adapter_file" "$output" 2>/dev/null || true)"
103
+ if ! printf '%s\n' "$records" | grep -Fqx $'version\t1'; then
104
+ warn "adapter '$target' has an invalid ownership contract"
105
+ continue
106
+ fi
107
+ while IFS=$'\t' read -r kind value; do
108
+ if [ "$kind" = "requires" ] \
109
+ && [ "$(is_target_enabled "$manifest" "$target")" = "1" ] \
110
+ && [ "$(is_target_enabled "$manifest" "$value")" != "1" ]; then
111
+ warn "enabled adapter '$target' requires enabled adapter '$value'"
112
+ fi
113
+ if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
114
+ case "$kind" in
115
+ ignore)
116
+ grep -Fqx -- "$value" "$IP_ROOT/.gitignore" 2>/dev/null \
117
+ || warn "adapter '$target' Git policy is missing '$value'"
118
+ ;;
119
+ include)
120
+ grep -Fqx -- "!$value" "$IP_ROOT/.gitignore" 2>/dev/null \
121
+ || warn "adapter '$target' Git policy is missing '!$value'"
122
+ ;;
123
+ esac
124
+ fi
125
+ done <<< "$records"
126
+ ok "adapter $target contract v1"
127
+ done < <(target_names "$manifest")
128
+
89
129
  # Lock orphans.
90
130
  if [ -f "$lock" ]; then
91
131
  while IFS= read -r name; do
@@ -348,16 +348,10 @@ rollback_on_exit() {
348
348
  # plain set -e/EXIT failure rather than only an explicit sync refusal.
349
349
  trap rollback_on_exit EXIT
350
350
  trap 'rollback; exit 130' INT TERM
351
- if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
352
- {
353
- [ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
354
- echo "# intelligence CLI package store (restored automatically by 'intelligence sync')"
355
- echo ".intelligence/"
356
- } >> "$root/.gitignore"
357
- fi
358
351
  mv "$stage/.intelligence" "$root/.intelligence"
359
352
  cp "$manifest_stage" "$root/intelligence.yaml"
360
353
  lock_write_from_tsv "$root/intelligence.lock" "$lock_rows"
354
+ ensure_manifest_gitignore "$root" "$root/intelligence.yaml"
361
355
 
362
356
  sync_out="$(cd "$root" && IS_SUPPRESS_CLI_NOTE=1 bash "$CLI_DIR/commands/sync.sh" 2>&1)" || {
363
357
  echo "$sync_out"
@@ -397,4 +391,4 @@ rmdir "$umbrella" 2>/dev/null || true
397
391
  echo ""
398
392
  echo "converted. Review the diff, then commit. The old config.yaml is kept at .intelligence/backup/config.yaml."
399
393
  echo "From now on: intelligence sync | package | update | status."
400
- echo "Next: ask your agent to run /intelligence-learn-from-repository to review the converted project context."
394
+ echo "Next: ask your agent to run /intelligence-learn-from-context to verify setup and review the converted project context."
@@ -25,31 +25,38 @@ if [ "$action" = "enable" ]; then
25
25
  die "adapter '$name' not found — create it first: intelligence adapter create $name"
26
26
  fi
27
27
 
28
- # These adapters intentionally omit always-on rules because their tools
29
- # read AGENTS.md. Do not create a manifest that full sync must refuse.
30
- case "$name" in
31
- cursor|copilot|codex|pi|opencode)
32
- if [ "$(is_target_enabled "$manifest" "agents")" != "1" ]; then
33
- die "target '$name' requires target 'agents' — enable it first"
34
- fi
35
- ;;
36
- esac
37
-
38
28
  output="$(get_target_output "$manifest" "$name")"
39
29
  [ -n "$output" ] || output="$(default_target_output "$name")"
30
+ validate_adapter_contract_for "$IP_ROOT" "$content_dir" "$name" "$output" \
31
+ || die "adapter '$name' has an invalid ownership contract"
32
+ while IFS= read -r required; do
33
+ [ -n "$required" ] || continue
34
+ if [ "$(is_target_enabled "$manifest" "$required")" != "1" ]; then
35
+ die "target '$name' requires target '$required' — enable it first"
36
+ fi
37
+ done < <(adapter_records_for "$IP_ROOT" "$content_dir" "$name" "$output" \
38
+ | awk -F '\t' '$1 == "requires" { print $2 }')
40
39
  target_set_enabled "$manifest" "$name" true "$output"
40
+ ensure_target_gitignore "$IP_ROOT" "$manifest" "$name"
41
41
  echo "enabled: $name (output: $output)"
42
42
  exit 0
43
43
  fi
44
44
 
45
45
  target_exists "$manifest" "$name" || die "target '$name' is not in the manifest"
46
- if [ "$name" = "agents" ]; then
47
- for dependent in cursor copilot codex pi opencode; do
48
- if [ "$(is_target_enabled "$manifest" "$dependent")" = "1" ]; then
49
- die "target 'agents' is required by enabled target '$dependent' — disable '$dependent' first"
50
- fi
51
- done
52
- fi
46
+ while IFS= read -r dependent; do
47
+ [ -n "$dependent" ] || continue
48
+ [ "$dependent" = "$name" ] && continue
49
+ [ "$(is_target_enabled "$manifest" "$dependent")" = "1" ] || continue
50
+ dependent_output="$(get_target_output "$manifest" "$dependent")"
51
+ [ -n "$dependent_output" ] || dependent_output="$(default_target_output "$dependent")"
52
+ dependent_records="$(adapter_records_for "$IP_ROOT" "$content_dir" "$dependent" "$dependent_output")" \
53
+ || die "adapter '$dependent' has an invalid ownership contract"
54
+ if awk -F '\t' -v required="$name" \
55
+ '$1 == "requires" && $2 == required { found=1 } END { exit(found ? 0 : 1) }' \
56
+ <<< "$dependent_records"; then
57
+ die "target '$name' is required by enabled target '$dependent' — disable '$dependent' first"
58
+ fi
59
+ done < <(target_names "$manifest")
53
60
  output="$(get_target_output "$manifest" "$name")"
54
61
  [ -n "$output" ] || output="$(default_target_output "$name")"
55
62
  target_set_enabled "$manifest" "$name" false "$output"
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ # CLI consumers of the engine's declarative adapter contract.
3
+
4
+ adapter_file_for() {
5
+ local root="$1" content_dir="$2" name="$3"
6
+ if [ -f "$root/$content_dir/adapters/$name.sh" ]; then
7
+ printf '%s\n' "$root/$content_dir/adapters/$name.sh"
8
+ elif [ -f "$IS_ENGINE_DIR/adapters/$name.sh" ]; then
9
+ printf '%s\n' "$IS_ENGINE_DIR/adapters/$name.sh"
10
+ else
11
+ return 1
12
+ fi
13
+ }
14
+
15
+ adapter_records_for() {
16
+ local root="$1" content_dir="$2" name="$3" output="$4" file
17
+ file="$(adapter_file_for "$root" "$content_dir" "$name")" || {
18
+ echo "ERROR: adapter '$name' not found" >&2
19
+ return 1
20
+ }
21
+ adapter_contract_records "$name" "$file" "$output"
22
+ }
23
+
24
+ validate_adapter_contract_for() {
25
+ local root="$1" content_dir="$2" name="$3" output="$4"
26
+ adapter_records_for "$root" "$content_dir" "$name" "$output" >/dev/null
27
+ }
28
+
29
+ adapter_contract_paths() {
30
+ local root="$1" content_dir="$2" name="$3" output="$4"
31
+ adapter_records_for "$root" "$content_dir" "$name" "$output" \
32
+ | awk -F '\t' '$1 == "owned" || $1 == "managed" || $1 == "legacy" || $1 == "preserve" { print $2 }'
33
+ }
@@ -11,6 +11,7 @@
11
11
  # IS_ENGINE_DIR come exported from the dispatcher.
12
12
  source "$IS_ENGINE_DIR/lib/common.sh"
13
13
  source "$IS_ENGINE_DIR/lib/contract.sh"
14
+ source "$IS_ENGINE_DIR/lib/adapter-contract.sh"
14
15
 
15
16
  die() {
16
17
  echo "ERROR: $*" >&2
@@ -50,6 +51,9 @@ source "$CLI_DIR/lib/manifest.sh"
50
51
  source "$CLI_DIR/lib/semver.sh"
51
52
  source "$CLI_DIR/lib/registry.sh"
52
53
  source "$CLI_DIR/lib/lockfile.sh"
54
+ source "$CLI_DIR/lib/adapter-lifecycle.sh"
55
+ source "$CLI_DIR/lib/gitignore.sh"
56
+ source "$CLI_DIR/lib/onboarding.sh"
53
57
 
54
58
  # The engine-content package: OPTIONAL but auto-selected at init. Package by
55
59
  # UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+ # Generated-output policy derived from each adapter's ownership contract.
3
+
4
+ gitignore_add_line() {
5
+ local root="$1" line="$2" file="$1/.gitignore"
6
+ if [ -f "$file" ] && grep -Fqx -- "$line" "$file"; then
7
+ return 0
8
+ fi
9
+ printf '%s\n' "$line" >> "$file"
10
+ }
11
+
12
+ ensure_gitignore_header() {
13
+ local root="$1" file="$1/.gitignore"
14
+ if [ ! -f "$file" ] || ! grep -Fqx '# Intelligence generated state and tool output' "$file"; then
15
+ if [ -f "$file" ] && [ -s "$file" ] && [ -n "$(tail -c 1 "$file" 2>/dev/null)" ]; then
16
+ printf '\n' >> "$file"
17
+ fi
18
+ printf '%s\n' '# Intelligence generated state and tool output' >> "$file"
19
+ fi
20
+ }
21
+
22
+ ensure_base_gitignore() {
23
+ local root="$1"
24
+ ensure_gitignore_header "$root"
25
+ gitignore_add_line "$root" '.intelligence/'
26
+ }
27
+
28
+ ensure_target_gitignore() {
29
+ local root="$1" manifest="$2" target="$3" content_dir output kind value records
30
+ ensure_gitignore_header "$root"
31
+ content_dir="$(manifest_intelligence_dir "$manifest")"
32
+ output="$(get_target_output "$manifest" "$target")"
33
+ [ -n "$output" ] || output="$(default_target_output "$target")"
34
+ records="$(adapter_records_for "$root" "$content_dir" "$target" "$output")" \
35
+ || die "adapter '$target' has an invalid ownership contract"
36
+ while IFS=$'\t' read -r kind value; do
37
+ case "$kind" in
38
+ ignore) gitignore_add_line "$root" "$value" ;;
39
+ include) gitignore_add_line "$root" "!$value" ;;
40
+ esac
41
+ done <<< "$records"
42
+ }
43
+
44
+ ensure_manifest_gitignore() {
45
+ local root="$1" manifest="$2" target
46
+ ensure_base_gitignore "$root"
47
+ while IFS= read -r target; do
48
+ [ -n "$target" ] || continue
49
+ if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
50
+ ensure_target_gitignore "$root" "$manifest" "$target"
51
+ fi
52
+ done < <(target_names "$manifest")
53
+ }
@@ -60,6 +60,23 @@ 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.