@ainova-systems/intelligence 0.11.0-rc.7 → 0.11.0-rc.9

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.
Files changed (51) hide show
  1. package/README.md +19 -16
  2. package/cli/commands/adapter.sh +153 -0
  3. package/cli/commands/init.sh +113 -22
  4. package/cli/commands/package.sh +30 -0
  5. package/cli/commands/registry.sh +4 -2
  6. package/cli/commands/status.sh +12 -4
  7. package/cli/commands/sync.sh +7 -20
  8. package/cli/commands/update.sh +76 -70
  9. package/cli/engine-package.yaml +2 -2
  10. package/cli/intelligence +10 -12
  11. package/cli/{commands/doctor.sh → internal/check.sh} +30 -23
  12. package/cli/{commands/migrate.sh → internal/migrate-v1.sh} +117 -30
  13. package/cli/{commands/add.sh → internal/package-add.sh} +11 -16
  14. package/cli/{commands/list.sh → internal/package-list.sh} +9 -4
  15. package/cli/{commands/remove.sh → internal/package-remove.sh} +3 -3
  16. package/cli/{commands/search.sh → internal/package-search.sh} +4 -4
  17. package/cli/internal/package-update.sh +103 -0
  18. package/cli/{commands/install.sh → internal/restore.sh} +7 -18
  19. package/cli/internal/target-state.sh +57 -0
  20. package/cli/internal/upgrade-v2.sh +133 -0
  21. package/cli/lib/cli-common.sh +142 -12
  22. package/cli/lib/lockfile.sh +1 -1
  23. package/cli/lib/manifest.sh +109 -0
  24. package/cli/lib/registry.sh +4 -4
  25. package/engine/ENGINE_SHA +1 -1
  26. package/engine/adapters/_template.sh +10 -9
  27. package/engine/adapters/agents.sh +11 -11
  28. package/engine/adapters/opencode.sh +1 -1
  29. package/engine/lib/common.sh +14 -22
  30. package/engine/lib/contract.sh +14 -14
  31. package/engine/sync.sh +25 -20
  32. package/package.json +1 -1
  33. package/packages/sync/agents/intelligence-architect.md +5 -3
  34. package/packages/sync/agents/intelligence-operator.md +10 -13
  35. package/packages/sync/references/adapters.md +252 -0
  36. package/packages/sync/references/conventions.md +385 -0
  37. package/packages/sync/rules/intelligence-authoring.md +6 -6
  38. package/packages/sync/skills/intelligence-add-agent/SKILL.md +5 -5
  39. package/packages/sync/skills/intelligence-add-rule/SKILL.md +3 -3
  40. package/packages/sync/skills/intelligence-add-skill/SKILL.md +3 -3
  41. package/packages/sync/skills/intelligence-extract-skill/SKILL.md +2 -2
  42. package/packages/sync/skills/intelligence-install-adapter/SKILL.md +29 -22
  43. package/packages/sync/skills/intelligence-learn-from-context/SKILL.md +3 -3
  44. package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +55 -0
  45. package/packages/sync/skills/intelligence-review-skills/SKILL.md +6 -6
  46. package/packages/sync/skills/intelligence-sync/SKILL.md +13 -9
  47. package/packages/sync/skills/intelligence-uninstall-adapter/SKILL.md +19 -37
  48. package/packages/sync/skills/intelligence-update/SKILL.md +34 -156
  49. package/cli/commands/upgrade.sh +0 -68
  50. package/packages/sync/docs/ADAPTERS.md +0 -214
  51. package/packages/sync/docs/CONVENTIONS.md +0 -456
@@ -54,8 +54,8 @@ source "$CLI_DIR/lib/lockfile.sh"
54
54
  # The engine-content package: OPTIONAL but auto-selected at init. Package by
55
55
  # UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
56
56
  # at the version the CLI ships, it materializes from the npm bundle without
57
- # network; only a cross-version install reaches git. The pin is held exactly
58
- # at the bundled engine version and moved only by `upgrade`.
57
+ # network; only a cross-version acquisition reaches git. The pin is held
58
+ # exactly at the bundled engine version and moved by lifecycle alignment.
59
59
  #
60
60
  # Its identity is DATA shipped with the distribution (cli/engine-package.yaml),
61
61
  # never a name compiled into cli code — a fork edits the file.
@@ -113,7 +113,7 @@ require_v2() {
113
113
  detect_project
114
114
  case "$IP_MODE" in
115
115
  v2) ;;
116
- legacy) die "this is a vendored (v1) setup — run 'intelligence migrate' first, or keep using its own flow" ;;
116
+ legacy) die "this is a vendored (v1) setup — run 'intelligence init' to migrate it, or keep using its own flow" ;;
117
117
  *) die "no intelligence project found here — run 'intelligence init'" ;;
118
118
  esac
119
119
  }
@@ -126,19 +126,25 @@ manifest_intelligence_dir() {
126
126
  printf '%s' "${v:-intelligence}"
127
127
  }
128
128
 
129
+ default_target_output() {
130
+ case "$1" in
131
+ agents) printf '%s' "AGENTS.md" ;;
132
+ copilot) printf '%s' ".github" ;;
133
+ *) printf '.%s' "$1" ;;
134
+ esac
135
+ }
136
+
129
137
  bundled_engine_version() {
130
138
  tr -d ' \t\r\n' < "$IS_ENGINE_DIR/VERSION"
131
139
  }
132
140
 
133
141
  # --- The sync package's manifest/lock plumbing ---------------------------
134
- # sync_pkg_entry <manifest> — write/refresh the package's manifest entry:
135
- # name + exact pin + explicit url/path, so resolution never depends on any
136
- # registry (a project registry shadowing the name cannot brick the engine).
142
+ # sync_pkg_entry <manifest> — write/refresh only the requested exact pin.
143
+ # The built-in source lives in engine-package.yaml and resolved source state
144
+ # belongs exclusively to intelligence.lock.
137
145
  sync_pkg_entry() {
138
146
  local manifest="$1"
139
147
  qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "version" "$(bundled_engine_version)"
140
- qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "url" "$SYNC_PKG_URL"
141
- qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "path" "$SYNC_PKG_PATH"
142
148
  }
143
149
 
144
150
  # sync_pkg_install <root> — materialize the package into the store and lock
@@ -156,14 +162,138 @@ sync_pkg_install() {
156
162
  # Everything the IS_CLI mode of sync.sh needs, derived from the manifest.
157
163
  export_engine_env() {
158
164
  local root="$1"
159
- local umbrella
160
- umbrella="$(manifest_intelligence_dir "$root/intelligence.yaml")"
165
+ local content_rel
166
+ content_rel="$(manifest_intelligence_dir "$root/intelligence.yaml")"
161
167
  export IS_CLI=1
162
168
  export CONFIG_FILE="$root/intelligence.yaml"
163
169
  export REPO_ROOT="$root"
164
- export IS_UMBRELLA_REL="$umbrella"
170
+ export IS_CONTENT_REL="$content_rel"
165
171
  export IS_MODULE_REL="$SYNC_PKG_STORE"
166
172
  export IS_SYNC_CMD="intelligence sync"
167
173
  export IS_MANIFEST_NAME="intelligence.yaml"
168
- export IS_PROTECTED_DIRS="$umbrella:.intelligence"
174
+ export IS_PROTECTED_DIRS="$content_rel:.intelligence"
175
+ }
176
+
177
+ # --- Project lifecycle preflight -----------------------------------------
178
+ # Public commands are intentionally few. They share this state gate so a CLI
179
+ # installed at a newer engine version brings the current v2 project forward
180
+ # before a mutating operation. The npm install itself cannot do that: it runs
181
+ # outside any project and does not know which repositories the user owns.
182
+
183
+ is_ci_environment() {
184
+ case "${CI:-}" in
185
+ 1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
186
+ *) return 1 ;;
187
+ esac
188
+ }
189
+
190
+ # Validate a repository-relative project content directory, including every
191
+ # existing symlinked path component. Call before writing a new manifest.
192
+ assert_safe_content_dir() {
193
+ local root="$1" content_dir="$2" repo_phys probe old_ifs part probe_phys
194
+ local -a parts
195
+ case "$content_dir" in
196
+ ""|/*|.|..|../*|*/../*|*/..|.git|.git/*|.intelligence|.intelligence/*|*\\*|[A-Za-z]:*)
197
+ die "unsafe content directory '$content_dir'"
198
+ ;;
199
+ esac
200
+ repo_phys="$(cd "$root" && pwd -P)"
201
+ probe="$root"
202
+ old_ifs="$IFS"; IFS='/'; read -r -a parts <<< "$content_dir"; IFS="$old_ifs"
203
+ for part in "${parts[@]}"; do
204
+ [ -n "$part" ] || continue
205
+ probe="$probe/$part"
206
+ if [ -e "$probe" ] || [ -L "$probe" ]; then
207
+ probe_phys="$(cd "$probe" 2>/dev/null && pwd -P)" \
208
+ || die "cannot resolve project content path '$content_dir'"
209
+ case "$probe_phys" in
210
+ "$repo_phys"|"$repo_phys"/*) ;;
211
+ *) die "project content directory resolves outside the repository: '$content_dir'" ;;
212
+ esac
213
+ fi
214
+ done
215
+ }
216
+
217
+ project_needs_upgrade() {
218
+ local root="$1" manifest="$1/intelligence.yaml" stamp eng pinned locked name
219
+ [ -f "$manifest" ] || return 1
220
+ stamp="$(read_schema_version "$manifest")"
221
+ eng="$(bundled_engine_version)"
222
+ [ -z "$stamp" ] && return 0
223
+ [ -n "$(top_scalar "$manifest" "sync_version")" ] && return 0
224
+ _ver_gt "$eng" "$stamp" && return 0
225
+ [ -d "$root/.intelligence/engine" ] && return 0
226
+
227
+ while IFS= read -r name; do
228
+ [ -n "$name" ] || continue
229
+ # Early RC manifests mixed requested intent with resolved source
230
+ # details. Source URL/path now live only in the required lockfile.
231
+ [ -n "$(qmap_field "$manifest" "packages" "$name" "url")" ] && return 0
232
+ [ -n "$(qmap_field "$manifest" "packages" "$name" "path")" ] && return 0
233
+ if [ "$name" = "$SYNC_PKG_NAME" ]; then
234
+ pinned="$(qmap_field "$manifest" "packages" "$name" "version")"
235
+ locked="$(qmap_field "$root/intelligence.lock" "packages" "$name" "resolved")"
236
+ [ "$pinned" = "$eng" ] || return 0
237
+ if [ -f "$root/intelligence.lock" ]; then
238
+ [ -n "$locked" ] || return 0
239
+ [ "${locked#v}" = "$eng" ] || return 0
240
+ fi
241
+ fi
242
+ done < <(qmap_keys "$manifest" "packages")
243
+ return 1
244
+ }
245
+
246
+ # A manifest with packages but no lock has no trustworthy resolved state from
247
+ # which lifecycle alignment can proceed. Never manufacture a partial lock.
248
+ project_has_packages() {
249
+ local root="$1" name found=1
250
+ while IFS= read -r name; do
251
+ [ -n "$name" ] || continue
252
+ assert_valid_pkg_name "$name"
253
+ found=0
254
+ done < <(qmap_keys "$root/intelligence.yaml" "packages")
255
+ return "$found"
256
+ }
257
+
258
+ ensure_project_current() {
259
+ local root="$1" explicit="${2:-}" manifest="$1/intelligence.yaml" stamp eng
260
+ check_version_compat "$manifest" || return $?
261
+ if project_has_packages "$root" && [ ! -f "$root/intelligence.lock" ]; then
262
+ die "manifest declares packages but intelligence.lock is absent — restore the committed lock before running project lifecycle commands"
263
+ fi
264
+ project_needs_upgrade "$root" || return 0
265
+ stamp="$(read_schema_version "$manifest")"
266
+ eng="$(bundled_engine_version)"
267
+ if is_ci_environment && [ "$explicit" != "--explicit" ]; then
268
+ die "project lifecycle requires alignment (stamp ${stamp:-unstamped}, engine $eng) — run 'intelligence init --apply' locally, review and commit the diff"
269
+ fi
270
+ echo " project alignment: stamp ${stamp:-unstamped}, engine $eng"
271
+ bash "$CLI_DIR/internal/upgrade-v2.sh" --no-sync
272
+ }
273
+
274
+ project_store_missing() {
275
+ local root="$1" manifest="$1/intelligence.yaml" name src
276
+ while IFS= read -r name; do
277
+ [ -n "$name" ] || continue
278
+ assert_valid_pkg_name "$name"
279
+ [ -d "$root/.intelligence/packages/$name" ] || return 0
280
+ done < <(qmap_keys "$manifest" "packages")
281
+ for section in rules agents skills; do
282
+ while IFS= read -r src; do
283
+ case "$src" in
284
+ .intelligence/packages/*)
285
+ [ -d "$root/$src" ] || return 0
286
+ ;;
287
+ esac
288
+ done < <(read_yaml_list "$manifest" "$section")
289
+ done
290
+ return 1
291
+ }
292
+
293
+ restore_project_store_if_missing() {
294
+ local root="$1"
295
+ project_store_missing "$root" || return 0
296
+ [ -f "$root/intelligence.lock" ] || die "package store is missing and intelligence.lock is absent — run 'intelligence init'"
297
+ echo " restoring package store from intelligence.lock"
298
+ bash "$CLI_DIR/internal/restore.sh" --frozen --no-sync
169
299
  }
@@ -12,7 +12,7 @@ LOCK_SEP=$'\x1f'
12
12
 
13
13
  # top_scalar <file> <key> — one top-level scalar (`key: "value"`), quotes and
14
14
  # trailing comment stripped. The declared exception to "no new parsing": the
15
- # engine's read_engine_stamp is hardwired to its own key.
15
+ # engine's read_schema_version is hardwired to its own key.
16
16
  top_scalar() {
17
17
  [ -f "$1" ] || return 0
18
18
  awk -v key="$2" '
@@ -36,6 +36,92 @@ assert_valid_pkg_name() {
36
36
  esac
37
37
  }
38
38
 
39
+ # Target names become adapter filenames and shell function suffixes, so keep
40
+ # them inside the portable shell-identifier subset used by built-in adapters.
41
+ assert_valid_target_name() {
42
+ case "$1" in
43
+ ""|[!abcdefghijklmnopqrstuvwxyz]*|*[!abcdefghijklmnopqrstuvwxyz0123456789_]*)
44
+ die "invalid target name '$1' — expected [a-z][a-z0-9_]*"
45
+ ;;
46
+ esac
47
+ }
48
+
49
+ # target_exists <manifest> <name> — distinguish a disabled target from one
50
+ # that is not declared at all. Both inline and block target shapes count.
51
+ target_exists() {
52
+ local file="$1" target="$2"
53
+ [ -f "$file" ] || return 1
54
+ awk -v target="$target" '
55
+ { sub(/\r$/, "") }
56
+ /^targets:[[:space:]]*$/ { in_targets = 1; next }
57
+ /^[A-Za-z]/ { in_targets = 0 }
58
+ in_targets && $0 ~ "^ " target ":[[:space:]]*" { found = 1; exit }
59
+ END { exit(found ? 0 : 1) }
60
+ ' "$file"
61
+ }
62
+
63
+ # target_set_enabled <manifest> <name> <true|false> <default-output>
64
+ # Transactionally update only `enabled`, preserving every other target field
65
+ # and comment. A missing target is added in the compact form used by init.
66
+ target_set_enabled() {
67
+ local file="$1" target="$2" enabled="$3" output="$4"
68
+ [ -f "$file" ] || die "no such file: $file"
69
+ case "$enabled" in true|false) ;; *) die "internal: invalid target state '$enabled'" ;; esac
70
+ output="$(_yq_esc "$output")"
71
+ _qmap_stage "$file" -v target="$target" -v enabled="$enabled" -v output="$output" '
72
+ function entry() { return " " target ": { enabled: " enabled ", output: \"" output "\" }" }
73
+ function add_enabled_to_inline(line, p) {
74
+ p = index(line, "{")
75
+ if (p > 0) return substr(line, 1, p) " enabled: " enabled "," substr(line, p + 1)
76
+ return line
77
+ }
78
+ function flush_target() {
79
+ if (in_target && !enabled_seen) print " enabled: " enabled
80
+ in_target = 0
81
+ }
82
+ function flush_targets() {
83
+ flush_target()
84
+ if (in_targets && !target_seen) { print entry(); target_seen = 1 }
85
+ in_targets = 0
86
+ }
87
+ { sub(/\r$/, "") }
88
+ /^targets:[[:space:]]*$/ { targets_seen = 1; in_targets = 1; print; next }
89
+ in_targets && /^[A-Za-z]/ { flush_targets() }
90
+ in_targets && /^ [A-Za-z]/ {
91
+ flush_target()
92
+ if ($0 ~ "^ " target ":[[:space:]]*") {
93
+ target_seen = 1
94
+ if (match($0, /enabled:[[:space:]]*(true|false)/)) {
95
+ print substr($0, 1, RSTART - 1) "enabled: " enabled substr($0, RSTART + RLENGTH)
96
+ enabled_seen = 1
97
+ } else if ($0 ~ /\{/) {
98
+ print add_enabled_to_inline($0)
99
+ enabled_seen = 1
100
+ } else {
101
+ print
102
+ in_target = 1
103
+ enabled_seen = 0
104
+ }
105
+ next
106
+ }
107
+ }
108
+ in_target && /^ enabled:[[:space:]]*/ {
109
+ print " enabled: " enabled
110
+ enabled_seen = 1
111
+ next
112
+ }
113
+ { last = $0; print }
114
+ END {
115
+ flush_targets()
116
+ if (!targets_seen) {
117
+ if (last != "") print ""
118
+ print "targets:"
119
+ print entry()
120
+ }
121
+ }
122
+ '
123
+ }
124
+
39
125
  # qmap_keys <file> <block> — quoted keys, one per line.
40
126
  qmap_keys() {
41
127
  [ -f "$1" ] || return 0
@@ -215,6 +301,29 @@ qmap_delete_key() {
215
301
  '
216
302
  }
217
303
 
304
+ # qmap_delete_field <file> <block> <key> <field> — drop one field from a
305
+ # quoted-key entry without disturbing its other fields or surrounding YAML.
306
+ qmap_delete_field() {
307
+ local file="$1" block="$2" key="$3" field="$4"
308
+ [ -f "$file" ] || return 0
309
+ _qmap_stage "$file" -v block="$block" -v key="$key" -v field="$field" '
310
+ { sub(/\r$/, "") }
311
+ $0 ~ "^" block ":[ \t]*$" { inb = 1; print; next }
312
+ inb && /^[^ #]/ { inb = 0; ink = 0 }
313
+ inb && /^ "/ {
314
+ s = substr($0, 4); q = index(s, "\"")
315
+ ink = (q > 0 && substr(s, 1, q - 1) == key)
316
+ print; next
317
+ }
318
+ inb && ink && /^ [A-Za-z_]/ {
319
+ line = $0; sub(/^ /, "", line)
320
+ c = index(line, ":")
321
+ if (c > 0 && substr(line, 1, c - 1) == field) next
322
+ }
323
+ { print }
324
+ '
325
+ }
326
+
218
327
  # --- registries: a trust LIST, not a scope map -----------------------------
219
328
  # The block holds registry repo URLs in trust order. Two shapes are read so
220
329
  # early manifests keep working: the list form (`- "url"`) and the retired
@@ -11,7 +11,7 @@
11
11
  # visible, reviewable, deletable.
12
12
  #
13
13
  # The NAME is the trust anchor a developer reasons with; source integrity is
14
- # a separate mechanism: the lock pins url+sha, and doctor flags
14
+ # a separate mechanism: the lock pins url+sha, and `status --check` flags
15
15
  # resolution/lock url drift.
16
16
  #
17
17
  # An index is itself fetched with git (never curl): auth, proxies and private
@@ -93,7 +93,7 @@ suggest_similar() {
93
93
  cshort="${cand#*/}"; cshort="${cshort%s}"
94
94
  if [ "$cshort" = "$want" ] && [ "$cand" != "$name" ]; then
95
95
  seen="$seen$cand "
96
- echo " Did you mean: intelligence add $cand" >&2
96
+ echo " Did you mean: intelligence package add $cand" >&2
97
97
  fi
98
98
  done < <(qmap_keys "$idx" "packages")
99
99
  }
@@ -116,8 +116,8 @@ fetch_package() {
116
116
  assert_safe_source_url "$url"
117
117
  assert_safe_ref "$ref"
118
118
  # Bundle seed: the engine-content package at the CLI's own version copies
119
- # from the npm bundle — no network, which keeps init / fresh-clone install
120
- # / migrate offline exactly like the staging they replace. Keyed on the
119
+ # from the npm bundle — no network, which keeps init, sync restoration and
120
+ # archived-project conversion offline. Keyed on the
121
121
  # full (url, path, ref) triple; any other version or source falls through
122
122
  # to the normal clone.
123
123
  if [ "$url" = "$SYNC_PKG_URL" ] && [ "$subpath" = "$SYNC_PKG_PATH" ] \
package/engine/ENGINE_SHA CHANGED
@@ -1 +1 @@
1
- 557cde6997dce612e0e8a5a11adc2472173dcec0
1
+ af238e67e8a9469edf6d2058e169de52cbeb93af
@@ -1,22 +1,21 @@
1
1
  #!/bin/bash
2
- # intelligence-sync: Adapter template
3
- # Copy this file to create a new IDE adapter.
2
+ # Intelligence CLI: project adapter template
3
+ # Scaffold this file with: intelligence adapter create <name>
4
4
  #
5
5
  # This file is NOT executable as-is — `<name>` placeholders below would be
6
6
  # parsed by bash as input redirection (`<` operator). Replace every
7
7
  # occurrence with your adapter name before sourcing.
8
8
  #
9
9
  # Required:
10
- # 1. Name the file: <ide-name>.sh (e.g., myide.sh)
11
- # 2. Replace every `<name>` placeholder with your adapter name
12
- # 3. Implement sync_to_<name>() function
13
- # 4. Add target to config.yaml:
10
+ # 1. Implement sync_to_<name>() and its three content transforms
11
+ # 2. Enable it after implementation: intelligence adapter enable <name>
12
+ # This adds the target to intelligence.yaml when it is absent:
14
13
  # targets:
15
14
  # <name>: { enabled: true, output: ".<name>" }
16
15
  #
17
16
  # The sync_to_<name>() function receives:
18
17
  # $1 = repo_root — absolute path to the project root
19
- # $2 = config_file — absolute path to config.yaml
18
+ # $2 = config_file — absolute path to intelligence.yaml
20
19
  # $3 = output_dir — absolute path to the output directory (e.g., .myide/)
21
20
  #
22
21
  # Available library functions (from lib/common.sh):
@@ -29,10 +28,12 @@
29
28
  # get_model_default(ide, tier) — hardcoded default for ide:tier
30
29
  # map_access_to_claude_tools(access) — full->"" (no tools list; inherits all), readonly->restricted
31
30
  # map_access_to_claude_disallowed(access) — readonly->"Write, Edit", full->""
32
- # read_yaml_list(config, section) — read list from config.yaml
31
+ # read_yaml_list(manifest, section) — read a source list from intelligence.yaml
33
32
  # get_target_field(config, target, field) — read a target's config field
34
33
 
35
- source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
34
+ # Project adapters are sourced by engine/sync.sh after the shared library is
35
+ # loaded. Keep this template position-independent: after scaffolding it lives
36
+ # under the project's content directory, not beside engine/lib/.
36
37
 
37
38
  # Sync rules for <agent-name>
38
39
  # Typical transformations:
@@ -21,8 +21,8 @@ source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
21
21
  # one document Cursor, Copilot and Codex all read as canonical.
22
22
  # The defaults only cover an adapter driven directly (tests); sync.sh always
23
23
  # exports both.
24
- agents_md_umbrella_rel() {
25
- printf '%s' "${IS_UMBRELLA_REL:-intelligence}"
24
+ agents_md_content_rel() {
25
+ printf '%s' "${IS_CONTENT_REL:-intelligence}"
26
26
  }
27
27
 
28
28
  agents_md_module_rel() {
@@ -196,14 +196,14 @@ agents_md_append_rules_list() {
196
196
  # Path-scoped rules stay in tool-specific channels (.cursor/rules/,
197
197
  # .github/instructions/) so monorepo scoping is preserved.
198
198
  if [ "${#global_rule_files[@]}" -gt 0 ]; then
199
- local umbrella_rel
200
- umbrella_rel="$(agents_md_umbrella_rel "$repo_root")"
199
+ local content_rel
200
+ content_rel="$(agents_md_content_rel "$repo_root")"
201
201
  {
202
202
  echo "---"
203
203
  echo ""
204
204
  echo "## Project Context"
205
205
  echo ""
206
- echo "<!-- Inlined from always-on rules in $umbrella_rel/rules/ -->"
206
+ echo "<!-- Inlined from always-on rules in $content_rel/rules/ -->"
207
207
  echo ""
208
208
  } >> "$output"
209
209
  local rf
@@ -240,17 +240,17 @@ sync_to_agents() {
240
240
 
241
241
  mkdir -p "$(dirname "$output_file")"
242
242
 
243
- local umbrella_rel module_rel
244
- umbrella_rel="$(agents_md_umbrella_rel "$repo_root")"
243
+ local content_rel module_rel
244
+ content_rel="$(agents_md_content_rel "$repo_root")"
245
245
  module_rel="$(agents_md_module_rel "$repo_root")"
246
246
 
247
247
  # These two strings are written into a COMMITTED file. An absolute path here
248
248
  # means the derivation failed, and a machine-specific path in AGENTS.md is
249
249
  # worse than a failed sync — fail loudly instead.
250
- case "$umbrella_rel$module_rel" in
250
+ case "$content_rel$module_rel" in
251
251
  /*|*:[\\/]*)
252
252
  echo " ERROR: AGENTS.md paths did not resolve relative to the repo root:" >&2
253
- echo " umbrella='$umbrella_rel' module='$module_rel' repo_root='$repo_root'" >&2
253
+ echo " content='$content_rel' module='$module_rel' repo_root='$repo_root'" >&2
254
254
  return 1
255
255
  ;;
256
256
  esac
@@ -261,7 +261,7 @@ sync_to_agents() {
261
261
 
262
262
  {
263
263
  echo "<!-- Generated by intelligence-sync. Do not edit manually. -->"
264
- echo "<!-- Source: $umbrella_rel/ | Sync: $sync_cmd -->"
264
+ echo "<!-- Source: $content_rel/ | Sync: $sync_cmd -->"
265
265
  echo ""
266
266
  } > "$output_file"
267
267
 
@@ -270,7 +270,7 @@ sync_to_agents() {
270
270
  {
271
271
  echo "## Intelligence"
272
272
  echo ""
273
- echo "Source of truth: \`$umbrella_rel/\` | Sync: \`$sync_cmd\`"
273
+ echo "Source of truth: \`$content_rel/\` | Sync: \`$sync_cmd\`"
274
274
  echo ""
275
275
  } >> "$output_file"
276
276
 
@@ -29,7 +29,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
29
29
  # Strip frontmatter, return body only.
30
30
  # Direct call to the shared `strip_frontmatter` helper in lib/common.sh —
31
31
  # duplicating the awk block here would violate the "all parsing in common.sh"
32
- # convention (see CLAUDE.md / docs/CONVENTIONS.md).
32
+ # convention (see CLAUDE.md / packages/sync/references/conventions.md).
33
33
 
34
34
  sync_opencode_skills() {
35
35
  local repo_root="$1"
@@ -16,16 +16,14 @@ normalize_file_to_lf() {
16
16
 
17
17
  # --- Layout tokens -----------------------------------------------------------
18
18
  #
19
- # The umbrella folder is named by the project (`intelligence/`, `Intelligence/`,
20
- # a codename) — so an artifact SHIPPED BY THE ENGINE cannot write that name
21
- # down. A rule that scopes itself to the intelligence layer needs `paths:` to
22
- # say "the umbrella", and an agent body needs to name the sync command. Both are
23
- # spelled with tokens, expanded here at output time:
19
+ # The content directory is named by the project (`intelligence/`,
20
+ # `Intelligence/`, a codename), so package-shipped artifacts cannot hardcode
21
+ # it. Layout-sensitive paths and commands use tokens expanded at output time:
24
22
  #
25
- # <umbrella> -> the repo-relative umbrella dir (e.g. `Intelligence`)
26
- # <module> -> the repo-relative engine module (e.g. `Intelligence/sync`)
23
+ # <content-dir> -> the repo-relative project content dir (e.g. `Intelligence`)
24
+ # <module> -> the repo-relative sync package store path
27
25
  #
28
- # Values come from IS_UMBRELLA_REL / IS_MODULE_REL, which sync.sh derives from
26
+ # Values come from IS_CONTENT_REL / IS_MODULE_REL, which the CLI derives from
29
27
  # the detected layout (never hardcoded) and exports before any adapter runs.
30
28
  # Expansion happens in EVERY generated file, frontmatter and body alike, so a
31
29
  # scoped rule reaches Claude's `paths:`, Cursor's `globs:` and Copilot's
@@ -34,24 +32,18 @@ normalize_file_to_lf() {
34
32
  # finalize_output_file <file>
35
33
  # The single exit gate for every file an adapter writes: expand layout tokens,
36
34
  # then normalize CRLF -> LF. Adapters MUST call this (not normalize_file_to_lf)
37
- # on each output — a missed call ships a literal `<umbrella>` into an IDE.
35
+ # on each output — a missed call ships a literal `<content-dir>` into an IDE.
38
36
  finalize_output_file() {
39
37
  local target="$1"
40
- local umb="${IS_UMBRELLA_REL:-intelligence}"
41
- local mod="${IS_MODULE_REL:-intelligence/sync}"
42
- # `<sync-cmd>` is how engine content says "run a sync": vendored setups
43
- # expand it to the script invocation (built from $mod, so it reproduces the
44
- # exact pre-token string), the CLI overrides it via IS_SYNC_CMD
45
- # (`intelligence sync`). `<manifest>` names the project's config file the
46
- # same way: `config.yaml` vendored, `intelligence.yaml` under the CLI
47
- # (IS_MANIFEST_NAME).
48
- local sc="${IS_SYNC_CMD:-bash $mod/scripts/sync.sh}"
49
- local mf="${IS_MANIFEST_NAME:-config.yaml}"
38
+ local content="${IS_CONTENT_REL:-intelligence}"
39
+ local mod="${IS_MODULE_REL:-.intelligence/packages/@ainova-systems/sync}"
40
+ local sc="${IS_SYNC_CMD:-intelligence sync}"
41
+ local mf="${IS_MANIFEST_NAME:-intelligence.yaml}"
50
42
  local tmp_file="$target.tmp"
51
43
  # Literal (index-based) substitution, not gsub: a regex replacement would
52
44
  # give `&` in a path its special meaning, and POSIX awk has no way to pass a
53
45
  # replacement string verbatim.
54
- awk -v umb="$umb" -v mod="$mod" -v sc="$sc" -v mf="$mf" '
46
+ awk -v content="$content" -v mod="$mod" -v sc="$sc" -v mf="$mf" '
55
47
  function repl(s, from, to, out, i) {
56
48
  out = ""
57
49
  while ((i = index(s, from)) > 0) {
@@ -65,7 +57,7 @@ finalize_output_file() {
65
57
  $0 = repl($0, "<sync-cmd>", sc)
66
58
  $0 = repl($0, "<manifest>", mf)
67
59
  $0 = repl($0, "<module>", mod)
68
- $0 = repl($0, "<umbrella>", umb)
60
+ $0 = repl($0, "<content-dir>", content)
69
61
  print
70
62
  }
71
63
  ' "$target" > "$tmp_file"
@@ -798,7 +790,7 @@ warn_unsynced() {
798
790
  # from its location — it comes from the env contract the CLI exports.
799
791
  local intel_basename
800
792
  if [ "${IS_CLI:-0}" = "1" ]; then
801
- intel_basename="$(basename "${IS_UMBRELLA_REL:-intelligence}")"
793
+ intel_basename="$(basename "${IS_CONTENT_REL:-intelligence}")"
802
794
  else
803
795
  intel_basename="$(basename "$(dirname "$config_file")")"
804
796
  fi
@@ -1,16 +1,16 @@
1
1
  #!/bin/bash
2
- # shellcheck disable=SC2034 # IS_RC_*/IS_VERSION_KEY are the public bash<->CLI contract, consumed by the scripts that source this lib
2
+ # shellcheck disable=SC2034 # IS_RC_*/IS_SCHEMA_VERSION_KEY are the public bash<->CLI contract, consumed by the scripts that source this lib
3
3
  # intelligence-sync: the version and status contract.
4
4
  # Source this file — never execute directly.
5
5
  #
6
6
  # Two things live here, and both are contracts other programs depend on:
7
- # * the applied-schema stamp (`sync_version` in the manifest) with its
7
+ # * the applied-schema stamp (`schema_version` in the manifest) with its
8
8
  # read / write / compare helpers, and
9
9
  # * the IS_STATUS / IS_RC_* codes every engine flow reports.
10
10
  #
11
- # Schema migrations themselves are NOT here: the CLI owns them (v2 migrations
12
- # live in `intelligence upgrade`), and a v1 project is brought forward by the
13
- # archived v1 engine before `intelligence migrate` converts it.
11
+ # Schema migrations themselves are NOT here: the CLI owns them behind
12
+ # `intelligence init`, and a v1 project is brought forward by the archived v1
13
+ # engine before `intelligence init` converts it.
14
14
 
15
15
  # The applied-schema version is a managed key in the manifest.
16
16
  #
@@ -18,13 +18,13 @@
18
18
  # Anything else in the manifest may be reshaped; the name, location and shape of
19
19
  # this key never are - so any engine, however old or new, can always read "what
20
20
  # schema is this?" before parsing the rest.
21
- IS_VERSION_KEY="sync_version"
21
+ IS_SCHEMA_VERSION_KEY="schema_version"
22
22
 
23
- # read_engine_stamp <config_file> → applied version, or "" if absent.
24
- read_engine_stamp() {
23
+ # read_schema_version <config_file> → applied version, or "" if absent.
24
+ read_schema_version() {
25
25
  local cf="$1"
26
26
  [ -f "$cf" ] || return 0
27
- awk -v k="$IS_VERSION_KEY" '
27
+ awk -v k="$IS_SCHEMA_VERSION_KEY" '
28
28
  { sub(/\r$/, "") }
29
29
  $0 ~ "^" k ":" {
30
30
  v = $0; sub(/^[^:]*:[[:space:]]*/, "", v)
@@ -35,14 +35,14 @@ read_engine_stamp() {
35
35
  ' "$cf"
36
36
  }
37
37
 
38
- # stamp_version <config_file> <version> — idempotent, transactional upsert of
38
+ # stamp_schema_version <config_file> <version> — idempotent, transactional upsert of
39
39
  # the contract key (replace in place if present, else append at top level).
40
40
  # No-op if config.yaml does not exist yet (pre-bootstrap).
41
- stamp_version() {
41
+ stamp_schema_version() {
42
42
  local cf="$1" ver="$2"
43
43
  [ -f "$cf" ] || return 0
44
44
  local tmp="$cf.ver.tmp"
45
- awk -v k="$IS_VERSION_KEY" -v val="$ver" '
45
+ awk -v k="$IS_SCHEMA_VERSION_KEY" -v val="$ver" '
46
46
  { sub(/\r$/, "") }
47
47
  $0 ~ "^" k ":" { print k ": \"" val "\""; found=1; next }
48
48
  { print }
@@ -62,7 +62,7 @@ IS_RC_CONFIG_MISSING=2 # no manifest found
62
62
  IS_RC_AMBIGUOUS=3 # conflicting state; agent/human-only — bash never emits this itself
63
63
  IS_RC_AHEAD=4 # project stamped newer than this engine understands
64
64
  IS_RC_ABORTED_INCOMPLETE=5 # staged state incomplete; the project was left untouched
65
- IS_RC_NEEDS_UPDATE=6 # pending schema changes (stamp < engine) — run `intelligence upgrade` first
65
+ IS_RC_NEEDS_UPDATE=6 # pending schema changes (stamp < engine) — run `intelligence init --apply`
66
66
 
67
67
  # is_status <code-name> [detail] — emit one parseable line for the skill.
68
68
  is_status() {
@@ -106,7 +106,7 @@ _ver_gt() {
106
106
  # conflict, else 0.
107
107
  check_version_compat() {
108
108
  local cf="$1" stamp eng
109
- stamp="$(read_engine_stamp "$cf")"
109
+ stamp="$(read_schema_version "$cf")"
110
110
  [ -n "$stamp" ] || return 0
111
111
  eng="$(engine_version)"
112
112
  [ -n "$eng" ] || return 0