@ainova-systems/intelligence 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -295,7 +295,8 @@ manifest="$root/intelligence.yaml"
295
295
  echo "# $content_dir/rules/context.md an always-on rule; describe the project,"
296
296
  echo "# its stack, and the constraints to respect"
297
297
  echo "# $content_dir/agents/<name>.md a persona (frontmatter: name, description,"
298
- echo "# tier: heavy|standard|light, access: full|readonly)"
298
+ echo "# tier: frontier|heavy|standard|light,"
299
+ echo "# access: full|readonly)"
299
300
  echo "# $content_dir/skills/<name>/SKILL.md a procedure invoked as /<name>"
300
301
  echo "# '.intelligence/' is CLI-managed (packages + engine content) — never edit it."
301
302
  echo "sources:"
@@ -102,7 +102,20 @@ while IFS= read -r name; do
102
102
  warn "$name is locked but not installed — run 'intelligence sync'"
103
103
  continue
104
104
  fi
105
- ok "$name @ $(pin_label "$m_ref" "$l_res" "$(qmap_field "$lock" "packages" "$name" "sha")")"
105
+ # Report what the store holds, never what the lock pins: they differ
106
+ # after a pull that moved the lock and left the ignored store behind.
107
+ l_url="$(qmap_field "$lock" "packages" "$name" "url")"
108
+ l_path="$(qmap_field "$lock" "packages" "$name" "path")"
109
+ l_sha="$(qmap_field "$lock" "packages" "$name" "sha")"
110
+ if ! store_record_get "$IP_ROOT" "$name"; then
111
+ warn "$name is installed, but the store does not record which commit it holds; the lock pins $(pin_label "$m_ref" "$l_res" "$l_sha") — run 'intelligence sync'"
112
+ elif [ "$IS_REC_URL" != "$l_url" ] || [ "$IS_REC_PATH" != "$l_path" ]; then
113
+ warn "$name was installed from ${IS_REC_URL}${IS_REC_PATH:+ (path $IS_REC_PATH)} but the lock names ${l_url}${l_path:+ (path $l_path)} — run 'intelligence sync'"
114
+ elif [ "$IS_REC_RESOLVED" != "$l_res" ] || [ "$IS_REC_SHA" != "$l_sha" ]; then
115
+ warn "$name holds $IS_REC_RESOLVED@$(short_sha "$IS_REC_SHA") but the lock pins $l_res@$(short_sha "$l_sha") — run 'intelligence sync'"
116
+ else
117
+ ok "$name @ $(pin_label "$m_ref" "$IS_REC_RESOLVED" "$IS_REC_SHA")"
118
+ fi
106
119
  fi
107
120
  done < <(qmap_keys "$manifest" "packages")
108
121
 
@@ -406,6 +406,11 @@ trap 'rollback; exit 130' INT TERM
406
406
  mv "$stage/.intelligence" "$root/.intelligence"
407
407
  cp "$manifest_stage" "$root/intelligence.yaml"
408
408
  lock_write_from_tsv "$root/intelligence.lock" "$lock_rows"
409
+ # The staged store holds exactly these rows; record them so sync trusts it.
410
+ while IFS="$LOCK_SEP" read -r rec_name _ rec_url rec_path rec_resolved rec_sha; do
411
+ [ -n "$rec_name" ] || continue
412
+ store_record_set "$root" "$rec_name" "$rec_url" "$rec_path" "$rec_resolved" "$rec_sha"
413
+ done < "$lock_rows"
409
414
  ensure_manifest_gitignore "$root" "$root/intelligence.yaml"
410
415
  ensure_manifest_publisher_ignores "$root" "$root/intelligence.yaml"
411
416
 
@@ -123,6 +123,10 @@ if [ -z "$ref" ]; then
123
123
  fi
124
124
 
125
125
  rel=".intelligence/packages/$name"
126
+ # Forget what the directory held before replacing it: an interruption then
127
+ # leaves the package unrecorded, which the next sync repairs, never a stale
128
+ # record vouching for new bytes.
129
+ store_record_remove "$IP_ROOT" "$name"
126
130
  sha="$(fetch_package "$url" "${ref:-$resolved_tag}" "$path" "$IP_ROOT/$rel")"
127
131
 
128
132
  wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
@@ -140,6 +144,7 @@ else
140
144
  fi
141
145
 
142
146
  lock_upsert "$lock" "$name" "$requested" "$url" "$path" "${ref:-$resolved_tag}" "$sha"
147
+ store_record_set "$IP_ROOT" "$name" "$url" "$path" "${ref:-$resolved_tag}" "$sha"
143
148
 
144
149
  sections=""
145
150
  for s in rules agents skills; do
@@ -43,6 +43,7 @@ unwire_package_sources "$manifest" "$rel"
43
43
  qmap_delete_key "$manifest" "packages" "$name"
44
44
  lock_remove "$lock" "$name"
45
45
  rm -rf "${IP_ROOT:?}/$rel"
46
+ store_record_remove "$IP_ROOT" "$name"
46
47
  # Drop the now-empty scope dir so the store stays tidy.
47
48
  rmdir "$IP_ROOT/.intelligence/packages/${name%%/*}" 2>/dev/null || true
48
49
  echo "- $name"
@@ -174,11 +174,13 @@ while IFS= read -r name; do
174
174
  rm -rf "$staging"
175
175
  sha="$(fetch_package "$url" "$tag" "$path" "$staging")"
176
176
  unwire_package_sources "$manifest" "$rel"
177
+ store_record_remove "$IP_ROOT" "$name"
177
178
  rm -rf "${IP_ROOT:?}/$rel"
178
179
  mkdir -p "$(dirname "$IP_ROOT/$rel")"
179
180
  mv "$staging" "$IP_ROOT/$rel"
180
181
  wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
181
182
  lock_upsert "$lock" "$name" "$requested" "$url" "$path" "$tag" "$sha"
183
+ store_record_set "$IP_ROOT" "$name" "$url" "$path" "$tag" "$sha"
182
184
  # The widened intent is recorded only once its content is installed and
183
185
  # wired: a manifest saying ^0.6.1 over a failed fetch would describe a
184
186
  # state the project never reached.
@@ -82,14 +82,16 @@ if [ -n "$missing" ]; then
82
82
  read -r resolved_tag _ <<< "$(remote_tag_for_version "$url" "$picked")"
83
83
  fi
84
84
  rel=".intelligence/packages/$name"
85
+ store_record_remove "$IP_ROOT" "$name"
85
86
  sha="$(fetch_package "$url" "${ref:-$resolved_tag}" "$path" "$IP_ROOT/$rel")"
86
87
  wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
87
88
  lock_upsert "$lock" "$name" "$range" "$url" "$path" "${ref:-$resolved_tag}" "$sha"
89
+ store_record_set "$IP_ROOT" "$name" "$url" "$path" "${ref:-$resolved_tag}" "$sha"
88
90
  echo "+ $name@${resolved_tag:-${ref:-HEAD}} (resolved)"
89
91
  done
90
92
  fi
91
93
 
92
- # Restore every locked package that is not already in the store.
94
+ # Restore every locked package the store does not hold at its locked commit.
93
95
  if [ -f "$lock" ]; then
94
96
  while IFS="$LOCK_SEP" read -r name _requested url path resolved sha; do
95
97
  [ -n "$name" ] || continue
@@ -97,10 +99,11 @@ if [ -f "$lock" ]; then
97
99
  # on their way into rm -rf paths, and its urls into git argv.
98
100
  assert_valid_pkg_name "$name"
99
101
  rel=".intelligence/packages/$name"
100
- # --frozen never trusts what is already on disk: a pre-existing
101
- # (possibly substituted) store dir would otherwise be synced with no
102
- # sha verification at all.
103
- if [ -d "$IP_ROOT/$rel" ] && [ "$force" -eq 0 ] && [ "$frozen" -eq 0 ]; then
102
+ # A directory counts only with a record of the lock row it satisfies:
103
+ # one present without it may hold any commit. --frozen never trusts
104
+ # what is already on disk: a pre-existing (possibly substituted) store
105
+ # dir would otherwise be synced with no sha verification at all.
106
+ if [ -d "$IP_ROOT/$rel" ] && [ "$force" -eq 0 ] && [ "$frozen" -eq 0 ] && store_record_matches "$IP_ROOT" "$name" "$url" "$path" "$resolved" "$sha"; then
104
107
  continue
105
108
  fi
106
109
  # Fetch into staging and commit to the store only after the integrity
@@ -109,9 +112,11 @@ if [ -f "$lock" ]; then
109
112
  staging="$IP_ROOT/.intelligence/.staging-$$"
110
113
  rm -rf "$staging"
111
114
  fetch_package "$url" "$resolved" "$path" "$staging" "$sha" >/dev/null
115
+ store_record_remove "$IP_ROOT" "$name"
112
116
  rm -rf "${IP_ROOT:?}/$rel"
113
117
  mkdir -p "$(dirname "$IP_ROOT/$rel")"
114
118
  mv "$staging" "$IP_ROOT/$rel"
119
+ store_record_set "$IP_ROOT" "$name" "$url" "$path" "$resolved" "$sha"
115
120
  wire_package_sources "$manifest" "$name" "$rel" "$IP_ROOT"
116
121
  echo "= $name@${resolved:-HEAD}"
117
122
  done < <(lock_to_tsv "$lock")
@@ -303,9 +303,11 @@ sync_pkg_entry() {
303
303
  sync_pkg_install() {
304
304
  local root="$1" ver sha
305
305
  ver="$(bundled_engine_version)"
306
+ store_record_remove "$root" "$SYNC_PKG_NAME"
306
307
  sha="$(fetch_package "$SYNC_PKG_URL" "v$ver" "$SYNC_PKG_PATH" "$root/$SYNC_PKG_STORE")"
307
308
  wire_package_sources "$root/intelligence.yaml" "$SYNC_PKG_NAME" "$SYNC_PKG_STORE" "$root"
308
309
  lock_upsert "$root/intelligence.lock" "$SYNC_PKG_NAME" "$ver" "$SYNC_PKG_URL" "$SYNC_PKG_PATH" "v$ver" "$sha"
310
+ store_record_set "$root" "$SYNC_PKG_NAME" "$SYNC_PKG_URL" "$SYNC_PKG_PATH" "v$ver" "$sha"
309
311
  echo " engine content installed: $SYNC_PKG_STORE (v$ver)"
310
312
  }
311
313
 
@@ -477,6 +479,65 @@ check_project_lock() {
477
479
  validate_project_lock "$1" "$mode"
478
480
  }
479
481
 
482
+ # --- What the store holds (decision 0012) ---------------------------------
483
+ # The store keeps plain files only, so which commit a package directory holds is
484
+ # recorded beside the packages, never inside one (the engine would render it):
485
+ # .intelligence/packages/.installed, one row per package — name, url, path,
486
+ # resolved, sha, separated like the lock's rows — written whenever a package is
487
+ # installed from the lock row it now satisfies. A directory without a row, or
488
+ # with one that differs from the lock, is not what the lock pins.
489
+ STORE_RECORD=".intelligence/packages/.installed"
490
+
491
+ # store_record_get <root> <name> — set IS_REC_URL / _PATH / _RESOLVED / _SHA;
492
+ # returns 1 when the store records nothing for <name>.
493
+ store_record_get() {
494
+ local file="$1/$STORE_RECORD" name url path resolved sha
495
+ IS_REC_URL="" IS_REC_PATH="" IS_REC_RESOLVED="" IS_REC_SHA=""
496
+ [ -f "$file" ] || return 1
497
+ while IFS="$LOCK_SEP" read -r name url path resolved sha; do
498
+ [ "$name" = "$2" ] || continue
499
+ IS_REC_URL="$url" IS_REC_PATH="$path" IS_REC_RESOLVED="$resolved" IS_REC_SHA="$sha"
500
+ return 0
501
+ done < "$file"
502
+ return 1
503
+ }
504
+
505
+ # store_record_matches <root> <name> <url> <path> <resolved> <sha>
506
+ store_record_matches() {
507
+ store_record_get "$1" "$2" || return 1
508
+ [ "$IS_REC_URL" = "$3" ] && [ "$IS_REC_PATH" = "$4" ] \
509
+ && [ "$IS_REC_RESOLVED" = "$5" ] && [ "$IS_REC_SHA" = "$6" ]
510
+ }
511
+
512
+ # store_record_set <root> <name> <url> <path> <resolved> <sha> — record what
513
+ # <name> now holds; store_record_remove <root> <name> forgets it. Both rewrite
514
+ # the file through a temp file, so a reader never sees half a row.
515
+ store_record_set() {
516
+ _store_record_write "$@"
517
+ }
518
+
519
+ store_record_remove() {
520
+ _store_record_write "$1" "$2"
521
+ }
522
+
523
+ _store_record_write() {
524
+ local root="$1" target="$2" file="$1/$STORE_RECORD" tmp name rest
525
+ mkdir -p "$root/.intelligence/packages" || die "cannot create $root/.intelligence/packages"
526
+ tmp="$file.tmp.$$"
527
+ {
528
+ if [ -f "$file" ]; then
529
+ while IFS="$LOCK_SEP" read -r name rest; do
530
+ [ -n "$name" ] && [ "$name" != "$target" ] || continue
531
+ printf '%s%s%s\n' "$name" "$LOCK_SEP" "$rest"
532
+ done < "$file"
533
+ fi
534
+ [ "$#" -lt 6 ] || printf '%s%s%s%s%s%s%s%s%s\n' "$target" "$LOCK_SEP" "$3" "$LOCK_SEP" "$4" "$LOCK_SEP" "$5" "$LOCK_SEP" "$6"
535
+ } > "$tmp" || { rm -f "$tmp"; die "cannot write $file"; }
536
+ mv -f "$tmp" "$file" || { rm -f "$tmp"; die "cannot write $file"; }
537
+ }
538
+
539
+ # project_store_missing <root> — a package the manifest or lock names is absent
540
+ # from the store, or holds something other than its lock row pins.
480
541
  project_store_missing() {
481
542
  local root="$1" manifest="$1/intelligence.yaml" name src
482
543
  while IFS= read -r name; do
@@ -493,6 +554,17 @@ project_store_missing() {
493
554
  esac
494
555
  done < <(read_yaml_list "$manifest" "$section")
495
556
  done
557
+ # Present is not enough: a `git pull` moves the lock and leaves the ignored
558
+ # store on the commit it held before.
559
+ local rows _requested url path resolved sha
560
+ [ -f "$root/intelligence.lock" ] || return 1
561
+ rows="$(lock_to_tsv "$root/intelligence.lock")"
562
+ while IFS="$LOCK_SEP" read -r name _requested url path resolved sha; do
563
+ [ -n "$name" ] || continue
564
+ assert_valid_pkg_name "$name"
565
+ [ -d "$root/.intelligence/packages/$name" ] || return 0
566
+ store_record_matches "$root" "$name" "$url" "$path" "$resolved" "$sha" || return 0
567
+ done <<< "$rows"
496
568
  return 1
497
569
  }
498
570
 
package/engine/ENGINE_SHA CHANGED
@@ -1 +1 @@
1
- 97484ac7ccc2e093642b833edfa8cae7bfe47c9e
1
+ 1bf045d314f2c2f58a70d5a74130b5f5376b56e4
package/engine/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.2
1
+ 0.18.0
@@ -103,7 +103,8 @@ sync_codex_agents() {
103
103
  name="${path##*/}"; name="${name%.md}"
104
104
  resolve_model_var "$tier"
105
105
  case "$tier" in
106
- heavy) effort="high" ;;
106
+ frontier) effort="xhigh" ;;
107
+ heavy|"") effort="high" ;;
107
108
  standard) effort="medium" ;;
108
109
  light) effort="low" ;;
109
110
  *) effort="medium" ;;
@@ -661,6 +661,7 @@ sync_open_skill_dirs() {
661
661
  # target gets it — not just this open-standard dir.
662
662
  if [ "$count" -gt 0 ]; then
663
663
  copy_skill_bundle_dirs "$output_dir" "${skill_dirs[@]}"
664
+ open_skill_invocation_policies "$output_dir" || return 1
664
665
  fi
665
666
 
666
667
  log+=" -> Skills: $count"$'\n'
@@ -670,6 +671,44 @@ sync_open_skill_dirs() {
670
671
  IS_OPEN_SKILLS_LOG="$log"
671
672
  }
672
673
 
674
+ # open_skill_invocation_policies <open_skills_dir> — `disable-model-invocation:
675
+ # true` keeps a skill out of automatic selection in Claude Code, Cursor and
676
+ # Copilot, but Codex ignores that key and reads the same intent from
677
+ # `agents/openai.yaml` beside SKILL.md. Source skills stay tool-neutral: the
678
+ # policy file is derived here, in the open-standard tree Codex reads, so every
679
+ # adapter sharing that tree writes the same bytes. A skill that ships its own
680
+ # `agents/openai.yaml` keeps it — that is the author configuring Codex directly.
681
+ open_skill_invocation_policies() {
682
+ local output_dir="$1" path flag rows
683
+ local -a skill_mds=() policy_dirs=() policies=()
684
+ for path in "$output_dir"/*/SKILL.md; do
685
+ [ -f "$path" ] && [ ! -L "$path" ] && skill_mds+=("$path")
686
+ done
687
+ [ "${#skill_mds[@]}" -gt 0 ] || return 0
688
+ # Captured, not streamed: a reader cut short must fail the render rather
689
+ # than quietly drop a policy and hand the skill back to the model.
690
+ rows="$(frontmatter_index "disable-model-invocation" "${skill_mds[@]}")" || {
691
+ echo "ERROR: could not read skill frontmatter under $output_dir" >&2
692
+ return 1
693
+ }
694
+ while IFS=$'\x1f' read -r path flag; do
695
+ [ "$flag" = "true" ] || continue
696
+ path="${path%/SKILL.md}/agents"
697
+ # A symlinked `agents` would carry the write outside the output tree.
698
+ [ -L "$path" ] && continue
699
+ [ -e "$path/openai.yaml" ] || [ -L "$path/openai.yaml" ] && continue
700
+ policy_dirs+=("$path")
701
+ policies+=("$path/openai.yaml")
702
+ done <<< "$rows"
703
+ [ "${#policies[@]}" -gt 0 ] || return 0
704
+ mkdir -p "${policy_dirs[@]}"
705
+ for path in "${policies[@]}"; do
706
+ printf '%s\n' '# Generated by intelligence-sync from disable-model-invocation in SKILL.md. Do not edit manually.' \
707
+ 'policy:' ' allow_implicit_invocation: false' > "$path"
708
+ done
709
+ finalize_output_files "${policies[@]}"
710
+ }
711
+
673
712
  # Lint YAML frontmatter for common pitfalls (unquoted colons, leading tabs).
674
713
  # Print warnings to stderr; do not fail. Strict consumers (Codex CLI) reject
675
714
  # these files with cryptic messages — catching them in sync gives better DX.
@@ -816,33 +855,47 @@ has_paths() {
816
855
 
817
856
  # --- Tier/Access Mappings ---
818
857
 
819
- # Hardcoded defaults: ide:tier -> model name.
858
+ # Hardcoded defaults: ide:tier -> model name. Where a tool has fewer models
859
+ # than there are tiers, tiers share one (decision 0013). cli/tests/unit-engine.sh
860
+ # pins this table independently, so a bump updates its expected matrix too.
820
861
  # When you bump these, re-run sync in projects; any project whose config.yaml
821
862
  # `models:` section diverges from these will print a drift warning so users
822
863
  # know their override is now stale.
823
864
  # get_model_default_var sets IS_MODEL_DEFAULT; get_model_default prints it.
824
865
  get_model_default_var() {
825
866
  case "$1:$2" in
867
+ # Haiku 4.5 retires no sooner than 2026-10-15 and has no successor, so
868
+ # light shares standard's Sonnet on every Claude-backed target.
869
+ claude:frontier) IS_MODEL_DEFAULT="fable" ;;
826
870
  claude:heavy) IS_MODEL_DEFAULT="opus" ;;
827
871
  claude:standard) IS_MODEL_DEFAULT="sonnet" ;;
828
- claude:light) IS_MODEL_DEFAULT="haiku" ;;
872
+ claude:light) IS_MODEL_DEFAULT="sonnet" ;;
873
+ # Cursor documents only `inherit` or a concrete model ID, so every
874
+ # tier follows the session model unless the manifest pins one.
875
+ cursor:frontier) IS_MODEL_DEFAULT="inherit" ;;
829
876
  cursor:heavy) IS_MODEL_DEFAULT="inherit" ;;
830
877
  cursor:standard) IS_MODEL_DEFAULT="inherit" ;;
831
- cursor:light) IS_MODEL_DEFAULT="fast" ;;
832
- copilot:heavy) IS_MODEL_DEFAULT="gpt-5.6-sol" ;;
833
- copilot:standard) IS_MODEL_DEFAULT="gpt-5.6-terra" ;;
834
- copilot:light) IS_MODEL_DEFAULT="gpt-5.6-luna" ;;
835
- codex:heavy) IS_MODEL_DEFAULT="gpt-5.6-sol" ;;
836
- codex:standard) IS_MODEL_DEFAULT="gpt-5.6-terra" ;;
837
- codex:light) IS_MODEL_DEFAULT="gpt-5.6-luna" ;;
838
- # Antigravity accepts only inherit|flash|pro, so standard and light
839
- # share `flash` — there is no third tier to map onto.
878
+ cursor:light) IS_MODEL_DEFAULT="inherit" ;;
879
+ # GPT-6 has no mid model, so frontier and heavy share Astra; Codex
880
+ # separates them by reasoning effort instead.
881
+ copilot:frontier) IS_MODEL_DEFAULT="gpt-6-astra" ;;
882
+ copilot:heavy) IS_MODEL_DEFAULT="gpt-6-astra" ;;
883
+ copilot:standard) IS_MODEL_DEFAULT="gpt-6.1-sol" ;;
884
+ copilot:light) IS_MODEL_DEFAULT="gpt-6-luna" ;;
885
+ codex:frontier) IS_MODEL_DEFAULT="gpt-6-astra" ;;
886
+ codex:heavy) IS_MODEL_DEFAULT="gpt-6-astra" ;;
887
+ codex:standard) IS_MODEL_DEFAULT="gpt-6.1-sol" ;;
888
+ codex:light) IS_MODEL_DEFAULT="gpt-6-luna" ;;
889
+ # Antigravity accepts only inherit|flash|pro, so frontier and heavy
890
+ # share `pro` and standard and light share `flash`.
891
+ antigravity:frontier) IS_MODEL_DEFAULT="pro" ;;
840
892
  antigravity:heavy) IS_MODEL_DEFAULT="pro" ;;
841
893
  antigravity:standard) IS_MODEL_DEFAULT="flash" ;;
842
894
  antigravity:light) IS_MODEL_DEFAULT="flash" ;;
843
- opencode:heavy) IS_MODEL_DEFAULT="anthropic/claude-opus-4-8" ;;
844
- opencode:standard) IS_MODEL_DEFAULT="anthropic/claude-sonnet-5" ;;
845
- opencode:light) IS_MODEL_DEFAULT="anthropic/claude-haiku-4-5-20251001" ;;
895
+ opencode:frontier) IS_MODEL_DEFAULT="anthropic/claude-fable-5-1" ;;
896
+ opencode:heavy) IS_MODEL_DEFAULT="anthropic/claude-opus-5-5" ;;
897
+ opencode:standard) IS_MODEL_DEFAULT="anthropic/claude-sonnet-5-5" ;;
898
+ opencode:light) IS_MODEL_DEFAULT="anthropic/claude-sonnet-5-5" ;;
846
899
  *) IS_MODEL_DEFAULT="" ;;
847
900
  esac
848
901
  }
@@ -925,25 +978,29 @@ get_model() {
925
978
  fi
926
979
  }
927
980
 
928
- # load_model_tiers <config_file> <ide> — resolve the three standard tiers
929
- # once per adapter run (IS_MODEL_HEAVY / IS_MODEL_STANDARD / IS_MODEL_LIGHT)
930
- # so per-file loops map tier -> model without forking. resolve_model_var
931
- # consumes them; a non-standard tier value still goes through get_model so a
932
- # `models:` override for it keeps working. One manifest pass reads all three.
981
+ # load_model_tiers <config_file> <ide> — resolve the four standard tiers
982
+ # once per adapter run (IS_MODEL_FRONTIER / IS_MODEL_HEAVY /
983
+ # IS_MODEL_STANDARD / IS_MODEL_LIGHT) so per-file loops map tier -> model
984
+ # without forking. resolve_model_var consumes them; a non-standard tier value
985
+ # still goes through get_model so a `models:` override for it keeps working.
986
+ # One manifest pass reads all four.
933
987
  load_model_tiers() {
934
- local config_file="$1" ide="$2" overrides="" tier val heavy="" standard="" light=""
988
+ local config_file="$1" ide="$2" overrides="" tier val frontier="" heavy="" standard="" light=""
935
989
  IS_MODEL_CFG="$config_file"
936
990
  IS_MODEL_IDE="$ide"
937
991
  if [ -n "$config_file" ] && [ -f "$config_file" ]; then
938
- overrides="$(_nested_yaml_values tagged "$config_file" "models" "$ide" heavy standard light)"
992
+ overrides="$(_nested_yaml_values tagged "$config_file" "models" "$ide" frontier heavy standard light)"
939
993
  fi
940
994
  while IFS=$'\037' read -r tier val; do
941
995
  case "$tier" in
996
+ frontier) frontier="$val" ;;
942
997
  heavy) heavy="$val" ;;
943
998
  standard) standard="$val" ;;
944
999
  light) light="$val" ;;
945
1000
  esac
946
1001
  done <<< "$overrides"
1002
+ get_model_default_var "$ide" frontier
1003
+ IS_MODEL_FRONTIER="${frontier:-$IS_MODEL_DEFAULT}"
947
1004
  get_model_default_var "$ide" heavy
948
1005
  IS_MODEL_HEAVY="${heavy:-$IS_MODEL_DEFAULT}"
949
1006
  get_model_default_var "$ide" standard
@@ -957,6 +1014,7 @@ load_model_tiers() {
957
1014
  # shellcheck disable=SC2034 # IS_MODEL is the return channel read by adapters
958
1015
  resolve_model_var() {
959
1016
  case "$1" in
1017
+ frontier) IS_MODEL="$IS_MODEL_FRONTIER" ;;
960
1018
  heavy|"") IS_MODEL="$IS_MODEL_HEAVY" ;;
961
1019
  standard) IS_MODEL="$IS_MODEL_STANDARD" ;;
962
1020
  light) IS_MODEL="$IS_MODEL_LIGHT" ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainova-systems/intelligence",
3
- "version": "0.17.2",
3
+ "version": "0.18.0",
4
4
  "description": "Build, version and distribute AI agent intelligence across your organization — one CLI, versioned Intelligence Packages, and a sync engine for Claude Code, Cursor, Copilot, Codex, Pi and OpenCode.",
5
5
  "bin": {
6
6
  "intelligence": "bin/intelligence.js"
@@ -5,8 +5,6 @@ tier: standard
5
5
  access: full
6
6
  skills:
7
7
  - intelligence-sync
8
- - intelligence-upgrade
9
- - intelligence-manage-adapters
10
8
  ---
11
9
 
12
10
  # Intelligence operator
@@ -28,6 +26,11 @@ the operation in prose.
28
26
  - **Every flow goes through its skill.** The steps and their guards live in `intelligence-sync`,
29
27
  `intelligence-upgrade` and `intelligence-manage-adapters`;
30
28
  improvising around them produces an unverified version of the same work.
29
+ - **Upgrades and adapter changes are the owner's to start.** `intelligence-upgrade` moves CLI and
30
+ package versions, the lock and migrations; `intelligence-manage-adapters` adds or removes whole
31
+ tool outputs. Both are decisions about the layer, so they run only when the owner invokes them,
32
+ and a sync never previews an update or inspects adapters on its own. Name the command the owner
33
+ can run and stop there.
31
34
  - **Operating is not authoring.** A change to what an artifact says - a rule body, an agent persona,
32
35
  a skill's steps - belongs to `intelligence-architect` and the authoring meta-skills. This agent
33
36
  ships what exists and never decides what should exist.
@@ -152,12 +152,12 @@ Use the engine library instead of copying parsers or file-handling logic.
152
152
  | `frontmatter_index(keys, file...)` | Read several frontmatter scalars for many files in one pass (`\x1f`-separated rows; special key `paths#` counts `paths:` lines). |
153
153
  | `has_frontmatter(file)` / `has_paths(file)` | Inspect source shape. |
154
154
  | `strip_frontmatter(file)` | Emit the body without its first frontmatter block. |
155
- | `get_model(config, tool, tier)` | Resolve a `heavy`, `standard` or `light` model, including manifest overrides. |
155
+ | `get_model(config, tool, tier)` | Resolve a `frontier`, `heavy`, `standard` or `light` model, including manifest overrides. |
156
156
  | `get_model_default(tool, tier)` | Read the built-in model default. |
157
- | `load_model_tiers(config, tool)` / `resolve_model_var(tier)` | Resolve the three standard tiers once, then map per file without subprocesses. |
157
+ | `load_model_tiers(config, tool)` / `resolve_model_var(tier)` | Resolve the four standard tiers once, then map per file without subprocesses. |
158
158
  | `copy_skill_bundle(src, dest)` | Copy `SKILL.md` and all resources safely, normalize Markdown and quote free-text frontmatter. |
159
159
  | `copy_skill_bundle_dirs(dest_root, src...)` | Batch form: copy every skill directory into `dest_root/<name>` with one copy and one finalize pass. |
160
- | `sync_open_skill_dirs(root, config, dest)` | Own and populate a shared Agent Skills directory such as `.agents/skills/`. |
160
+ | `sync_open_skill_dirs(root, config, dest)` | Own and populate a shared Agent Skills directory such as `.agents/skills/`, deriving Codex's `agents/openai.yaml` for a skill with `disable-model-invocation: true`. |
161
161
  | `finalize_output_file(file)` | Expand layout tokens and normalize line endings; required for every emitted text file. |
162
162
  | `finalize_output_files(file...)` / `finalize_copy_files(dest, src...)` | Batch forms: finalize in place, or copy-and-finalize into a directory, in one process. |
163
163
  | `emit_wrapped_bodies(spec)` | Emit many header + source-body + tail outputs in one process (see `engine/lib/common.sh` for the spec format). |
@@ -121,7 +121,7 @@ Practice:
121
121
  - **`latest` and `*` are for a package you own and release in lockstep.** Anywhere else they hand an upstream author write access to your agents' behavior between two syncs.
122
122
  - **Never pin a range to dodge a broken release.** Pin the exact version (`1.4.2`), record why, and remove the pin when the fix ships — a narrowed range hides the reason and outlives the incident.
123
123
 
124
- Commit `intelligence.lock`. It records requested versions, source URLs and paths, resolved refs and commit SHAs. After cloning, `intelligence sync` restores a missing store strictly from that lock before rendering; manifest/lock or SHA drift is refused. Re-run `package add` when deliberately changing a source.
124
+ Commit `intelligence.lock`. It records requested versions, source URLs and paths, resolved refs and commit SHAs. After cloning, or after a pull that moved the lock, `intelligence sync` restores every package the store lacks or holds at another commit, strictly from that lock, before rendering; manifest/lock or SHA drift is refused. Re-run `package add` when deliberately changing a source.
125
125
 
126
126
  `@ainova-systems/sync` is ordinary package content exact-pinned to the bundled engine version. `intelligence init` installs it unless `--bare` is used. Lifecycle preflight keeps that pin and `schema_version` aligned with the installed CLI; package-range updates never move it independently.
127
127
 
@@ -185,11 +185,14 @@ Do not instruct an agent to read rules or restate their content. Claude loads it
185
185
 
186
186
  ### Tier mappings
187
187
 
188
- | Tier | Claude | Cursor | Copilot / Codex | OpenCode | Typical use |
189
- |---|---|---|---|---|---|
190
- | `heavy` | `opus` | `inherit` | `gpt-5.6-sol` | `anthropic/claude-opus-4-8` | implementation, complex reasoning, migration |
191
- | `standard` | `sonnet` | `inherit` | `gpt-5.6-terra` | `anthropic/claude-sonnet-5` | review, validation, analysis |
192
- | `light` | `haiku` | `fast` | `gpt-5.6-luna` | `anthropic/claude-haiku-4-5-20251001` | lookups and simple formatting |
188
+ | Tier | Claude | Cursor | Copilot / Codex | OpenCode | Antigravity | Typical use |
189
+ |---|---|---|---|---|---|---|
190
+ | `frontier` | `fable` | `inherit` | `gpt-6-astra` | `anthropic/claude-fable-5-1` | `pro` | long-horizon autonomous work, the hardest reasoning, where capability outweighs cost |
191
+ | `heavy` | `opus` | `inherit` | `gpt-6-astra` | `anthropic/claude-opus-5-5` | `pro` | implementation, complex reasoning, migration |
192
+ | `standard` | `sonnet` | `inherit` | `gpt-6.1-sol` | `anthropic/claude-sonnet-5-5` | `flash` | review, validation, analysis |
193
+ | `light` | `sonnet` | `inherit` | `gpt-6-luna` | `anthropic/claude-sonnet-5-5` | `flash` | lookups and simple formatting |
194
+
195
+ Codex also receives a reasoning effort per tier — `xhigh`, `high`, `medium`, `low` — which is what separates `frontier` from `heavy` in Codex. Copilot has no effort field, so its `frontier` and `heavy` agents are identical. Claude's Haiku 4.5 is retiring with no successor announced, so `light` shares `standard`'s Sonnet in Claude and OpenCode; Cursor and Antigravity have fewer native levels than there are tiers, so several tiers share one value there too.
193
196
 
194
197
  The vocabulary is tool-neutral. Adapters resolve it through `get_model()`. Override a default under `models.<tool>.<tier>` in `intelligence.yaml` only when the project needs a pin; sync reports drift when that override differs from the current default.
195
198
 
@@ -251,6 +254,8 @@ argument-hint: "<route-name>"
251
254
 
252
255
  Standard optional fields (`license`, `compatibility`, `metadata`, `allowed-tools`) and tool extensions pass through unchanged. A tool ignores fields it does not understand.
253
256
 
257
+ `disable-model-invocation: true` makes a skill one only the owner starts, as a slash command: use it for a procedure that changes versions, the lock or generated outputs, where an agent selecting it unasked is the failure. Claude Code, Cursor and Copilot read the field itself; Codex does not, so sync writes `agents/openai.yaml` with `allow_implicit_invocation: false` beside the skill in `.agents/skills/`. Keep the source to the field — a skill that ships its own `agents/openai.yaml` keeps it unchanged.
258
+
254
259
  These limits reject a skill instead of degrading it:
255
260
 
256
261
  | Field | Limit | Failure mode |
@@ -416,7 +421,7 @@ The permanent applied-schema key is the top-level scalar `schema_version` in `in
416
421
  The public lifecycle is deliberately compact:
417
422
 
418
423
  - `intelligence init [--preview|--apply]` is universal: it creates a new setup, aligns an existing Intelligence project, or plans/applies conversion of an eligible legacy Intelligence Sync project.
419
- - `intelligence sync [adapter] [--compact] [--force]` first aligns an existing Intelligence project with the installed CLI and restores a missing store strictly from `intelligence.lock`. It skips rendering when local inputs and outputs match a previous successful run. Changes or missing outputs trigger rendering; `--force` always renders and refreshes the search for unconfigured source directories. An unchanged run replays the last full run's useful diagnostics. Compact mode shows context sizes, actionable warnings and final status on success, and all diagnostics on failure. In CI it refuses an alignment that would change tracked files and points to a local `intelligence init --apply` plus review/commit.
424
+ - `intelligence sync [adapter] [--compact] [--force]` first aligns an existing Intelligence project with the installed CLI and restores every package the store lacks or holds at another commit, strictly from `intelligence.lock`. It skips rendering when local inputs and outputs match a previous successful run. Changes or missing outputs trigger rendering; `--force` always renders and refreshes the search for unconfigured source directories. An unchanged run replays the last full run's useful diagnostics. Compact mode shows context sizes, actionable warnings and final status on success, and all diagnostics on failure. In CI it refuses an alignment that would change tracked files and points to a local `intelligence init --apply` plus review/commit.
420
425
  - `intelligence update [@scope/name] [--preview|--apply]` is the only update surface. It prints the CLI/project/package plan; default mode prompts, `--preview` never writes, and `--apply` does not prompt. It never moves `ref:` pins.
421
426
  - `intelligence upgrade [--next] [--preview|--apply]` replaces the installed CLI with the newest version on its npm channel (`next` for a prerelease or with `--next`, otherwise `latest`) with the same modes. It touches no project, never downgrades, and refuses an installation that npm did not make.
422
427
  - `intelligence package add|remove|list|search` owns package inventory.
@@ -109,6 +109,6 @@ The goal is subtraction, above. These are only the line past which something is
109
109
 
110
110
  ## Verifying a change to this layer
111
111
 
112
- The per-artifact authoring checks belong to `intelligence-update-context`. Session learning, repository onboarding, and accepted review findings use that same procedure. Invoke `intelligence-learn-from-session` to capture a lesson or workflow, `intelligence-learn-from-repository` for initial migration and recovery, and `intelligence-review-context` for audits and reductions. Operational work uses `intelligence-sync`, `intelligence-upgrade`, or `intelligence-manage-adapters`.
112
+ The per-artifact authoring checks belong to `intelligence-update-context`. Session learning, repository onboarding, and accepted review findings use that same procedure. Invoke `intelligence-learn-from-session` to capture a lesson or workflow, `intelligence-learn-from-repository` for initial migration and recovery, and `intelligence-review-context` for audits and reductions. Operational work uses `intelligence-sync`. `intelligence-upgrade` and `intelligence-manage-adapters` change versions and generated outputs, so only the owner invokes them.
113
113
 
114
114
  A change to this layer is done when `<sync-cmd>` reports `IS_STATUS=ok` and the skill you invoked reports clean.
@@ -3,6 +3,7 @@ name: intelligence-manage-adapters
3
3
  description: "Enable, disable, or remove adapters and assess generated-output cleanup"
4
4
  argument-hint: "<enable|disable|remove> <adapter-name>"
5
5
  agent: intelligence-operator
6
+ disable-model-invocation: true
6
7
  ---
7
8
 
8
9
  # Manage adapters
@@ -4,9 +4,11 @@
4
4
  `<domain>-<role>` and determine its expertise from the source evidence.
5
5
  2. Select tier and access: implementation normally uses `heavy` and `full`;
6
6
  review or validation uses `standard` and `readonly`; simple lookup uses
7
- `light` and `readonly`. Check the target's actual permission mapping when
8
- external read tools are required. If native read-only restrictions exclude
9
- those tools, use `full` only with a clear read-only boundary in the body.
7
+ `light` and `readonly`. Reserve `frontier` for long-horizon work whose
8
+ difficulty justifies the most capable and most expensive model. Check the
9
+ target's actual permission mapping when external read tools are required.
10
+ If native read-only restrictions exclude those tools, use `full` only with a
11
+ clear read-only boundary in the body.
10
12
  3. Keep the body thin: Expertise, Boundaries, and Build & Verify. Carry its own
11
13
  completion criteria and limitations. Reference constraints by name instead
12
14
  of copying rules, and put reusable procedures in skills.
@@ -3,6 +3,7 @@ name: intelligence-upgrade
3
3
  description: "Upgrade Intelligence and installed packages with migration checks"
4
4
  argument-hint: "[@scope/name]"
5
5
  agent: intelligence-operator
6
+ disable-model-invocation: true
6
7
  ---
7
8
 
8
9
  # Upgrade Intelligence and packages