@ainova-systems/intelligence 0.18.0 → 0.18.1
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.
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/engine/lib/common.sh +142 -10
- package/package.json +1 -1
- package/packages/sync/references/conventions.md +2 -2
package/engine/ENGINE_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7dfbb01c2182f3de05bff94f3e5e080cf267f8c6
|
package/engine/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.18.
|
|
1
|
+
0.18.1
|
package/engine/lib/common.sh
CHANGED
|
@@ -677,12 +677,23 @@ sync_open_skill_dirs() {
|
|
|
677
677
|
# `agents/openai.yaml` beside SKILL.md. Source skills stay tool-neutral: the
|
|
678
678
|
# policy file is derived here, in the open-standard tree Codex reads, so every
|
|
679
679
|
# adapter sharing that tree writes the same bytes. A skill that ships its own
|
|
680
|
-
# `agents/openai.yaml` keeps
|
|
680
|
+
# `agents/openai.yaml` keeps the rest of it: the output copy gains the policy
|
|
681
|
+
# when the author's file sets none, and a file whose policy says otherwise, or
|
|
682
|
+
# that sync cannot read as saying it, refuses the render — an owner-only skill
|
|
683
|
+
# Codex can still select is the exact failure the field exists to prevent, so
|
|
684
|
+
# it never passes as a success.
|
|
681
685
|
open_skill_invocation_policies() {
|
|
682
|
-
local output_dir="$1" path flag rows
|
|
683
|
-
local -a skill_mds=() policy_dirs=() policies=()
|
|
686
|
+
local output_dir="$1" path dir flag rows refusals
|
|
687
|
+
local -a skill_mds=() policy_dirs=() policies=() authored=()
|
|
684
688
|
for path in "$output_dir"/*/SKILL.md; do
|
|
685
|
-
|
|
689
|
+
dir="${path%/SKILL.md}"
|
|
690
|
+
# A link is emitted as-is (see _skill_bundle_note): reading through it
|
|
691
|
+
# or writing beside it would reach outside the output tree.
|
|
692
|
+
if [ -L "$dir" ] || [ -L "$path" ]; then
|
|
693
|
+
echo " WARN: ${dir##*/}/SKILL.md is reached through a symlink — Codex gets no invocation policy derived for it" >&2
|
|
694
|
+
elif [ -f "$path" ]; then
|
|
695
|
+
skill_mds+=("$path")
|
|
696
|
+
fi
|
|
686
697
|
done
|
|
687
698
|
[ "${#skill_mds[@]}" -gt 0 ] || return 0
|
|
688
699
|
# Captured, not streamed: a reader cut short must fail the render rather
|
|
@@ -693,13 +704,32 @@ open_skill_invocation_policies() {
|
|
|
693
704
|
}
|
|
694
705
|
while IFS=$'\x1f' read -r path flag; do
|
|
695
706
|
[ "$flag" = "true" ] || continue
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
[ -L "$path" ]
|
|
699
|
-
|
|
707
|
+
dir="${path%/SKILL.md}"
|
|
708
|
+
path="$dir/agents"
|
|
709
|
+
if [ -L "$path" ] || [ -L "$path/openai.yaml" ]; then
|
|
710
|
+
echo " WARN: ${dir##*/}/agents/openai.yaml is a symlink — left as-is, its invocation policy is not enforced" >&2
|
|
711
|
+
continue
|
|
712
|
+
fi
|
|
713
|
+
if [ -s "$path/openai.yaml" ]; then
|
|
714
|
+
authored+=("$path/openai.yaml")
|
|
715
|
+
continue
|
|
716
|
+
fi
|
|
700
717
|
policy_dirs+=("$path")
|
|
701
718
|
policies+=("$path/openai.yaml")
|
|
702
719
|
done <<< "$rows"
|
|
720
|
+
if [ "${#authored[@]}" -gt 0 ]; then
|
|
721
|
+
refusals="$(enforce_authored_invocation_policies "${authored[@]}")" || {
|
|
722
|
+
echo "ERROR: could not read or rewrite agents/openai.yaml under $output_dir" >&2
|
|
723
|
+
return 1
|
|
724
|
+
}
|
|
725
|
+
if [ -n "$refusals" ]; then
|
|
726
|
+
while IFS=$'\t' read -r path flag; do
|
|
727
|
+
path="${path%/agents/openai.yaml}"
|
|
728
|
+
echo "ERROR: ${path##*/} sets disable-model-invocation: true, but its agents/openai.yaml $flag — make them agree, or Codex can still select the skill" >&2
|
|
729
|
+
done <<< "$refusals"
|
|
730
|
+
return 1
|
|
731
|
+
fi
|
|
732
|
+
fi
|
|
703
733
|
[ "${#policies[@]}" -gt 0 ] || return 0
|
|
704
734
|
mkdir -p "${policy_dirs[@]}"
|
|
705
735
|
for path in "${policies[@]}"; do
|
|
@@ -709,6 +739,94 @@ open_skill_invocation_policies() {
|
|
|
709
739
|
finalize_output_files "${policies[@]}"
|
|
710
740
|
}
|
|
711
741
|
|
|
742
|
+
# enforce_authored_invocation_policies <openai.yaml>... — one awk pass over
|
|
743
|
+
# author-owned Codex files of skills that set `disable-model-invocation: true`.
|
|
744
|
+
# A file already setting `policy.allow_implicit_invocation: false` is left
|
|
745
|
+
# alone; one without that key gains it, inside an existing `policy:` block or
|
|
746
|
+
# as a new block, every other line kept. Only the plain boolean on a direct
|
|
747
|
+
# child of `policy:` counts — the key Codex reads. A file that sets it
|
|
748
|
+
# otherwise, or whose `policy:` sync cannot read or extend safely, is printed
|
|
749
|
+
# as `<file>\t<reason>` and left untouched for the caller to refuse.
|
|
750
|
+
enforce_authored_invocation_policies() {
|
|
751
|
+
awk '
|
|
752
|
+
function reset() {
|
|
753
|
+
file = ""; n = 0; haspol = 0; inpol = 0; polline = 0
|
|
754
|
+
inline = ""; child = ""; val = ""; hasval = 0; bad = ""
|
|
755
|
+
}
|
|
756
|
+
# A one-line policy counts only as a flat flow mapping of plain
|
|
757
|
+
# scalars holding the key itself; a quoted or nested value is not read.
|
|
758
|
+
function inline_ok(s, body, parts, k, i, p) {
|
|
759
|
+
if (s !~ /^\{.*\}$/) return 0
|
|
760
|
+
body = substr(s, 2, length(s) - 2)
|
|
761
|
+
if (body ~ /[{}"\047]/ || index(body, "[") || index(body, "]")) return 0
|
|
762
|
+
k = split(body, parts, ",")
|
|
763
|
+
for (i = 1; i <= k; i++) {
|
|
764
|
+
p = parts[i]
|
|
765
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", p)
|
|
766
|
+
if (p ~ /^allow_implicit_invocation[[:space:]]*:[[:space:]]*false$/) return 1
|
|
767
|
+
}
|
|
768
|
+
return 0
|
|
769
|
+
}
|
|
770
|
+
function finish( i, ind) {
|
|
771
|
+
if (file == "") return
|
|
772
|
+
if (bad == "" && inline != "" && !inline_ok(inline))
|
|
773
|
+
bad = "has policy: " inline ", which does not plainly set allow_implicit_invocation: false"
|
|
774
|
+
if (bad == "" && hasval && val != "false")
|
|
775
|
+
bad = "sets policy.allow_implicit_invocation: " (val == "" ? "null" : val)
|
|
776
|
+
if (bad != "") { printf "%s\t%s\n", file, bad; reset(); return }
|
|
777
|
+
if (inline != "" || hasval) { reset(); return }
|
|
778
|
+
ind = (child != "") ? child : " "
|
|
779
|
+
for (i = 1; i <= n; i++) {
|
|
780
|
+
print L[i] > file
|
|
781
|
+
if (haspol && i == polline) print ind "allow_implicit_invocation: false" > file
|
|
782
|
+
}
|
|
783
|
+
if (!haspol) {
|
|
784
|
+
print "policy:" > file
|
|
785
|
+
print " allow_implicit_invocation: false" > file
|
|
786
|
+
}
|
|
787
|
+
close(file)
|
|
788
|
+
reset()
|
|
789
|
+
}
|
|
790
|
+
BEGIN { reset() }
|
|
791
|
+
FNR == 1 { finish(); file = FILENAME }
|
|
792
|
+
{ sub(/\r$/, ""); L[++n] = $0 }
|
|
793
|
+
/^["\047]?policy["\047]?[[:space:]]*:/ {
|
|
794
|
+
if (haspol) bad = "has more than one policy: key"
|
|
795
|
+
haspol = 1; polline = n; inpol = 1
|
|
796
|
+
rest = $0
|
|
797
|
+
sub(/^[^:]*:[[:space:]]*/, "", rest)
|
|
798
|
+
sub(/[[:space:]]+#.*$/, "", rest)
|
|
799
|
+
sub(/^#.*$/, "", rest)
|
|
800
|
+
sub(/[[:space:]]+$/, "", rest)
|
|
801
|
+
if (rest != "") { inline = rest; inpol = 0 }
|
|
802
|
+
next
|
|
803
|
+
}
|
|
804
|
+
inpol && /^[^[:space:]#]/ { inpol = 0 }
|
|
805
|
+
# The first indented line fixes the depth of the direct children of
|
|
806
|
+
# policy:, and a deeper line belongs to another key, not the one Codex
|
|
807
|
+
# reads.
|
|
808
|
+
inpol && /^[[:space:]]+[^[:space:]#]/ {
|
|
809
|
+
match($0, /^[[:space:]]+/)
|
|
810
|
+
cur = substr($0, 1, RLENGTH)
|
|
811
|
+
if (child == "") {
|
|
812
|
+
child = cur
|
|
813
|
+
if ($0 ~ /^[[:space:]]+-([[:space:]]|$)/) bad = "has a policy: that is not a mapping"
|
|
814
|
+
} else if (length(cur) < length(child)) {
|
|
815
|
+
bad = "has a policy: block indented inconsistently"
|
|
816
|
+
}
|
|
817
|
+
if (cur == child && $0 ~ /^[[:space:]]+["\047]?allow_implicit_invocation["\047]?[[:space:]]*:/) {
|
|
818
|
+
v = $0
|
|
819
|
+
sub(/^[^:]*:[[:space:]]*/, "", v)
|
|
820
|
+
sub(/[[:space:]]+#.*$/, "", v)
|
|
821
|
+
sub(/^#.*$/, "", v)
|
|
822
|
+
sub(/[[:space:]]+$/, "", v)
|
|
823
|
+
hasval = 1; val = v
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
END { finish() }
|
|
827
|
+
' "$@"
|
|
828
|
+
}
|
|
829
|
+
|
|
712
830
|
# Lint YAML frontmatter for common pitfalls (unquoted colons, leading tabs).
|
|
713
831
|
# Print warnings to stderr; do not fail. Strict consumers (Codex CLI) reject
|
|
714
832
|
# these files with cryptic messages — catching them in sync gives better DX.
|
|
@@ -1010,7 +1128,11 @@ load_model_tiers() {
|
|
|
1010
1128
|
}
|
|
1011
1129
|
|
|
1012
1130
|
# resolve_model_var <tier> — set IS_MODEL from the tiers load_model_tiers
|
|
1013
|
-
# resolved. An empty tier resolves to heavy, like get_model's default.
|
|
1131
|
+
# resolved. An empty tier resolves to heavy, like get_model's default. A tier
|
|
1132
|
+
# the tool has no default for and the manifest overrides nowhere — a typo, as a
|
|
1133
|
+
# rule — still renders an empty model, but never silently: the run warns once
|
|
1134
|
+
# per tool and tier, unindented on purpose because `sync --compact` keeps only
|
|
1135
|
+
# `WARNING:` lines.
|
|
1014
1136
|
# shellcheck disable=SC2034 # IS_MODEL is the return channel read by adapters
|
|
1015
1137
|
resolve_model_var() {
|
|
1016
1138
|
case "$1" in
|
|
@@ -1018,7 +1140,17 @@ resolve_model_var() {
|
|
|
1018
1140
|
heavy|"") IS_MODEL="$IS_MODEL_HEAVY" ;;
|
|
1019
1141
|
standard) IS_MODEL="$IS_MODEL_STANDARD" ;;
|
|
1020
1142
|
light) IS_MODEL="$IS_MODEL_LIGHT" ;;
|
|
1021
|
-
*)
|
|
1143
|
+
*)
|
|
1144
|
+
IS_MODEL="$(get_model "$IS_MODEL_CFG" "$IS_MODEL_IDE" "$1")"
|
|
1145
|
+
[ -n "$IS_MODEL" ] && return 0
|
|
1146
|
+
case "${IS_MODEL_WARNED:-|}" in
|
|
1147
|
+
*"|$IS_MODEL_IDE:$1|"*) ;;
|
|
1148
|
+
*)
|
|
1149
|
+
IS_MODEL_WARNED="${IS_MODEL_WARNED:-|}$IS_MODEL_IDE:$1|"
|
|
1150
|
+
echo "WARNING: no $IS_MODEL_IDE model for tier '$1' — agents with it get an empty model; use frontier, heavy, standard or light, or set models.$IS_MODEL_IDE.$1" >&2
|
|
1151
|
+
;;
|
|
1152
|
+
esac
|
|
1153
|
+
;;
|
|
1022
1154
|
esac
|
|
1023
1155
|
}
|
|
1024
1156
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainova-systems/intelligence",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.1",
|
|
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"
|
|
@@ -194,7 +194,7 @@ Do not instruct an agent to read rules or restate their content. Claude loads it
|
|
|
194
194
|
|
|
195
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.
|
|
196
196
|
|
|
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.
|
|
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. Another tier name works only where `models.<tool>.<tier>` defines it; elsewhere — a typo, as a rule — the agent renders with an empty `model` and sync warns once per tool and tier.
|
|
198
198
|
|
|
199
199
|
### Access mappings
|
|
200
200
|
|
|
@@ -254,7 +254,7 @@ argument-hint: "<route-name>"
|
|
|
254
254
|
|
|
255
255
|
Standard optional fields (`license`, `compatibility`, `metadata`, `allowed-tools`) and tool extensions pass through unchanged. A tool ignores fields it does not understand.
|
|
256
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
|
|
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 everything else in it: sync adds the policy when the file sets none, and refuses one that sets it otherwise or not as a plain `allow_implicit_invocation: false` directly under `policy:`.
|
|
258
258
|
|
|
259
259
|
These limits reject a skill instead of degrading it:
|
|
260
260
|
|