@ainova-systems/intelligence 0.20.0 → 0.21.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.
@@ -35,23 +35,15 @@ sync_claude_rules() {
35
35
  local config_file="$2"
36
36
  local output_dir="$3"
37
37
 
38
- local src f
39
- local -a files=()
40
- load_yaml_list "$config_file" "rules"
41
- local list="$IS_YAML_LIST"
42
- while IFS= read -r src; do
43
- [ -z "$src" ] && continue
44
- local dir="$repo_root/$src"
45
- [ -d "$dir" ] || continue
46
- for f in "$dir"/*.md; do
47
- [ -f "$f" ] || continue
48
- files+=("$f")
49
- done
50
- done <<< "$list"
51
- [ "${#files[@]}" -gt 0 ] || return 0
52
- finalize_copy_files "$output_dir/rules" "${files[@]}"
53
- for f in "${files[@]}"; do
54
- echo " rule: ${f##*/}"
38
+ local name
39
+ # Every source reaches this adapter through the source catalog (decision
40
+ # 0021): rendered names, overrides and local artifacts are settled there.
41
+ output_rel_var "$repo_root" "$output_dir"
42
+ source_artifacts_var "$repo_root" "$config_file" rules claude "$IS_OUTPUT_REL/rules/{}.md"
43
+ [ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
44
+ finalize_copy_named "$output_dir/rules" "$IS_SOURCE_NAME_MAP" "${IS_SOURCE_FILES[@]}"
45
+ for name in "${IS_SOURCE_NAMES[@]}"; do
46
+ echo " rule: $name.md"
55
47
  done
56
48
  }
57
49
 
@@ -61,29 +53,19 @@ sync_claude_skills() {
61
53
  local config_file="$2"
62
54
  local output_dir="$3"
63
55
 
64
- local src d skill_name
56
+ local i name
65
57
  local -a skill_dirs=()
66
- # The inventory decides which skills render, symlinked ones included
67
- # (decision 0017); the glob keeps this adapter's own listing order.
68
- skill_source_inventory "$repo_root" "$config_file"
69
- load_yaml_list "$config_file" "skills"
70
- local list="$IS_YAML_LIST"
71
- while IFS= read -r src; do
72
- [ -z "$src" ] && continue
73
- local dir="$repo_root/$src"
74
- [ -d "$dir" ] || continue
75
- for d in "$dir"/*/; do
76
- [ -d "$d" ] || continue
77
- [ -f "$d/SKILL.md" ] || continue
78
- skill_source_rendered "$d" || continue
79
- skill_dirs+=("$d")
80
- done
81
- done <<< "$list"
82
- [ "${#skill_dirs[@]}" -gt 0 ] || return 0
83
- copy_skill_bundle_dirs_for claude "$output_dir/skills" "${skill_dirs[@]}"
84
- for d in "${skill_dirs[@]}"; do
85
- skill_name="${d%/}"
86
- echo " skill: ${skill_name##*/}"
58
+ # The catalog's skills are the ones the inventory renders, symlinked ones
59
+ # included (decision 0017).
60
+ output_rel_var "$repo_root" "$output_dir"
61
+ source_artifacts_var "$repo_root" "$config_file" skills claude "$IS_OUTPUT_REL/skills/{}"
62
+ [ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
63
+ for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
64
+ skill_dirs+=("${IS_SOURCE_FILES[i]%/SKILL.md}/")
65
+ done
66
+ copy_skill_bundle_dirs_named claude "$output_dir/skills" "$IS_SOURCE_NAME_MAP" "${skill_dirs[@]}"
67
+ for name in "${IS_SOURCE_NAMES[@]}"; do
68
+ echo " skill: $name"
87
69
  done
88
70
  }
89
71
 
@@ -94,32 +76,27 @@ sync_claude_agents() {
94
76
  local config_file="$2"
95
77
  local output_dir="$3"
96
78
 
97
- local src f
98
- local -a files=()
99
- load_yaml_list "$config_file" "agents"
100
- local list="$IS_YAML_LIST"
101
- while IFS= read -r src; do
102
- [ -z "$src" ] && continue
103
- local dir="$repo_root/$src"
104
- [ -d "$dir" ] || continue
105
- for f in "$dir"/*.md; do
106
- [ -f "$f" ] || continue
107
- files+=("$f")
108
- done
109
- done <<< "$list"
110
- [ "${#files[@]}" -gt 0 ] || return 0
79
+ local i
80
+ local -a files=() names=()
81
+ output_rel_var "$repo_root" "$output_dir"
82
+ source_artifacts_var "$repo_root" "$config_file" agents claude "$IS_OUTPUT_REL/agents/{}.md"
83
+ [ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
84
+ files=("${IS_SOURCE_FILES[@]}")
85
+ names=("${IS_SOURCE_NAMES[@]}")
86
+ local name_map="$IS_SOURCE_NAME_MAP"
111
87
 
112
88
  load_model_tiers "$config_file" "claude"
113
89
 
114
90
  local path tier access effort spec="" report=""
91
+ i=0
115
92
  while IFS=$'\x1f' read -r path tier access effort; do
116
- [ -n "$path" ] || continue
117
93
  resolve_model_var "$tier"
118
94
  map_effort_var claude "$effort"
119
95
  map_access_to_claude_tools_var "$access"
120
96
  map_access_to_claude_disallowed_var "$access"
121
97
  spec+="$path"$'\x1f'"$IS_MODEL"$'\x1f'"$IS_CLAUDE_TOOLS"$'\x1f'"$IS_CLAUDE_DISALLOWED"$'\x1f'"$IS_EFFORT"$'\n'
122
- report+=" agent: ${path##*/} (tier=$tier -> model=$IS_MODEL, access=$access)"$'\n'
98
+ report+=" agent: ${names[i]}.md (tier=$tier -> model=$IS_MODEL, access=$access)"$'\n'
99
+ i=$((i + 1))
123
100
  done < <(frontmatter_index "tier,access,effort" "${files[@]}")
124
101
 
125
102
  # The transform used to run per file with the values injected via -v;
@@ -128,7 +105,7 @@ sync_claude_agents() {
128
105
  # becomes Claude's level in place, or goes when there is none; a later
129
106
  # one goes too, since frontmatter_index read only the first.
130
107
  is_fin_awk_vars
131
- IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
108
+ IS_NAME_MAP="$name_map" IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
132
109
  BEGIN {
133
110
  US = sprintf("%c", 31)
134
111
  n = split(ENVIRON["IS_MAP_SPEC"], recs, "\n")
@@ -140,16 +117,18 @@ sync_claude_agents() {
140
117
  }
141
118
  FNR == 1 {
142
119
  if (out != "") close(out)
143
- out = dst "/" base_name(FILENAME)
120
+ out = dst "/" art_file(FILENAME, ".md")
144
121
  count = 0
145
122
  fm = 0
146
123
  effort_seen = 0
124
+ tgt_reset()
147
125
  }
148
126
  /^tier:/ { next }
149
127
  /^access:/ { next }
150
128
  { sub(/\r$/, "") }
151
129
  FNR == 1 && $0 == "---" { fm = 1 }
152
130
  /^---$/ { count++ }
131
+ fm && count == 1 && !/^---$/ && tgt_drop($0) { next }
153
132
  fm && count == 1 && substr($0, 1, 7) == "effort:" {
154
133
  if (!effort_seen++ && EFFORT[FILENAME] != "") print fin_line("effort: " EFFORT[FILENAME]) > out
155
134
  next
@@ -163,8 +142,8 @@ sync_claude_agents() {
163
142
  ' "${files[@]}"
164
143
  # awk never reads a record from an empty source; the old per-file shell
165
144
  # redirect still created an empty output for it.
166
- for f in "${files[@]}"; do
167
- [ -s "$f" ] || : > "$output_dir/agents/${f##*/}"
145
+ for ((i = 0; i < ${#files[@]}; i++)); do
146
+ [ -s "${files[i]}" ] || : > "$output_dir/agents/${names[i]}.md"
168
147
  done
169
148
  printf '%s' "$report"
170
149
  }
@@ -6,6 +6,13 @@
6
6
  # inlines always-on rules into AGENTS.md, so this adapter only emits skills
7
7
  # and subagent definitions.
8
8
  #
9
+ # Rules: an always-on rule AGENTS.md does not carry — a local one, or one whose
10
+ # targets leave `agents` out — goes into AGENTS.override.md beside it
11
+ # (decision 0021). Codex reads that file instead of AGENTS.md in the same
12
+ # directory, so it holds the whole AGENTS.md plus those rules. The contract
13
+ # ignores it, and it exists only while such a rule does; one Intelligence did
14
+ # not generate is never touched, and refuses a render that needs the path.
15
+ #
9
16
  # Skills: copy skill directories in full to .agents/skills/{name}/ (Codex
10
17
  # reads from $REPO_ROOT/.agents/skills exclusively per official docs)
11
18
  # Agents: -> .codex/agents/{name}.toml (name, description, model,
@@ -26,8 +33,61 @@ adapter_contract_codex() {
26
33
  adapter_contract_requires agents
27
34
  adapter_contract_managed ".agents/skills"
28
35
  adapter_contract_owned "$output/agents"
36
+ # Marker-managed: a hand-written override is left alone.
37
+ adapter_contract_managed "AGENTS.override.md"
29
38
  adapter_contract_ignore ".agents/skills/"
30
39
  adapter_contract_ignore "$output/agents/"
40
+ adapter_contract_ignore "AGENTS.override.md"
41
+ }
42
+
43
+ CODEX_OVERRIDE_MARKER="<!-- Generated by intelligence-sync. Do not edit manually. -->"
44
+
45
+ # sync_codex_override <repo_root> <config_file> — write AGENTS.override.md
46
+ # while an always-on rule AGENTS.md does not carry is for Codex, and remove a
47
+ # generated one otherwise.
48
+ sync_codex_override() {
49
+ local repo_root="$1" config_file="$2" override="$1/AGENTS.override.md" first="" agents_file i
50
+ local -a files=() names=()
51
+ source_artifacts_var "$repo_root" "$config_file" rules codex "AGENTS.override.md"
52
+ for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
53
+ [ "${IS_SOURCE_SCOPED[i]}" = 0 ] && [ "${IS_SOURCE_IN_AGENTS[i]}" = 0 ] || continue
54
+ files+=("${IS_SOURCE_FILES[i]}")
55
+ names+=("${IS_SOURCE_NAMES[i]}")
56
+ done
57
+ if [ -f "$override" ]; then
58
+ IFS= read -r first < "$override" || true
59
+ if [ "${first%$'\r'}" != "$CODEX_OVERRIDE_MARKER" ]; then
60
+ [ "${#files[@]}" -gt 0 ] || return 0
61
+ echo "ERROR: AGENTS.override.md was not generated by intelligence-sync, but ${names[*]} need it: always-on rules for Codex that AGENTS.md does not carry go there. Move its guidance into a rule, remove it, then sync." >&2
62
+ return 1
63
+ fi
64
+ fi
65
+ if [ "${#files[@]}" -eq 0 ]; then
66
+ rm -f "$override"
67
+ return 0
68
+ fi
69
+ target_output_var "$config_file" "agents"
70
+ agents_output_path_var "${IS_TGT_OUTPUT:-.agents}"
71
+ agents_file="$repo_root/$IS_AGENTS_OUTPUT_PATH"
72
+ {
73
+ if [ -f "$agents_file" ]; then
74
+ cat "$agents_file"
75
+ else
76
+ printf '%s\n\n' "$CODEX_OVERRIDE_MARKER"
77
+ fi
78
+ echo "---"
79
+ echo ""
80
+ echo "## Codex Project Context"
81
+ echo ""
82
+ echo "<!-- Always-on rules AGENTS.md does not carry. Codex reads this file instead of AGENTS.md. -->"
83
+ echo ""
84
+ emit_rule_bodies "${files[@]}"
85
+ } > "$override"
86
+ finalize_output_file "$override"
87
+ for ((i = 0; i < ${#names[@]}; i++)); do
88
+ echo " rule: ${names[i]} (always-on, AGENTS.override.md)"
89
+ done
90
+ echo " -> AGENTS.override.md"
31
91
  }
32
92
 
33
93
  # Codex-specific guard for its documented project-instruction byte budget.
@@ -52,6 +112,8 @@ codex_warn_project_doc_size() {
52
112
  target_output_var "$config_file" "agents"
53
113
  local output
54
114
  output="$(agents_output_path "${IS_TGT_OUTPUT:-.agents}")"
115
+ # Codex reads AGENTS.override.md instead of AGENTS.md when it exists.
116
+ [ ! -f "$repo_root/AGENTS.override.md" ] || output="AGENTS.override.md"
55
117
 
56
118
  local output_file="$repo_root/$output"
57
119
  [ -f "$output_file" ] || return 0
@@ -83,19 +145,13 @@ sync_codex_agents() {
83
145
  local config_file="$2"
84
146
  local output_dir="$3"
85
147
 
86
- local src f
87
- local -a files=()
88
- load_yaml_list "$config_file" "agents"
89
- local list="$IS_YAML_LIST"
90
- while IFS= read -r src; do
91
- [ -z "$src" ] && continue
92
- local dir="$repo_root/$src"
93
- [ -d "$dir" ] || continue
94
- for f in "$dir"/*.md; do
95
- [ -f "$f" ] || continue
96
- files+=("$f")
97
- done
98
- done <<< "$list"
148
+ local -a files=() names=()
149
+ output_rel_var "$repo_root" "$output_dir"
150
+ source_artifacts_var "$repo_root" "$config_file" agents codex "$IS_OUTPUT_REL/{}.toml"
151
+ if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
152
+ files=("${IS_SOURCE_FILES[@]}")
153
+ names=("${IS_SOURCE_NAMES[@]}")
154
+ fi
99
155
 
100
156
  local count=0
101
157
  if [ "${#files[@]}" -gt 0 ]; then
@@ -103,8 +159,7 @@ sync_codex_agents() {
103
159
 
104
160
  local path tier access description effort name sandbox spec="" report="" LS=$'\x1e'
105
161
  while IFS=$'\x1f' read -r path tier access description effort; do
106
- [ -n "$path" ] || continue
107
- name="${path##*/}"; name="${name%.md}"
162
+ name="${names[count]}"
108
163
  resolve_model_var "$tier"
109
164
  map_effort_var codex "$effort"
110
165
  case "$access" in
@@ -155,5 +210,9 @@ sync_to_codex() {
155
210
  mkdir -p "$agents_dir"
156
211
  sync_codex_agents "$repo_root" "$config_file" "$agents_dir"
157
212
 
213
+ # Rules AGENTS.md does not carry -> AGENTS.override.md. The contract
214
+ # requires `agents`, so AGENTS.md has rendered by now.
215
+ sync_codex_override "$repo_root" "$config_file" || return 1
216
+
158
217
  codex_warn_project_doc_size "$repo_root" "$config_file"
159
218
  }
@@ -8,7 +8,9 @@
8
8
  # natively, and the `agents` adapter inlines always-on rule content
9
9
  # into AGENTS.md. We also do not generate .github/copilot-instructions.md
10
10
  # because Copilot bug copilot-cli#489 makes AGENTS.md ignored when
11
- # copilot-instructions.md is present.
11
+ # copilot-instructions.md is present. An always-on rule AGENTS.md does not
12
+ # carry — a local one, or one whose targets leave `agents` out — becomes
13
+ # an .instructions.md file with `applyTo: "**"` (decision 0021).
12
14
  # Skills: copy skill directories in full to .github/skills/{name}/
13
15
  # Agents: -> .github/agents/{name}.agent.md (description, tools, model)
14
16
  #
@@ -100,8 +102,8 @@ copilot_paths_rows() {
100
102
  ' "$@"
101
103
  }
102
104
 
103
- # Sync rules to Copilot format. Only path-scoped rules are emitted —
104
- # always-on rules live in AGENTS.md (read by Copilot natively).
105
+ # Sync rules to Copilot format. Path-scoped rules are emitted, and an always-on
106
+ # rule only when AGENTS.md (read by Copilot natively) does not carry it.
105
107
  sync_copilot_rules() {
106
108
  local repo_root="$1"
107
109
  local config_file="$2"
@@ -112,35 +114,28 @@ sync_copilot_rules() {
112
114
 
113
115
  local scoped_count=0
114
116
 
115
- local src f
116
- local -a files=()
117
- load_yaml_list "$config_file" "rules"
118
- local list="$IS_YAML_LIST"
119
- while IFS= read -r src; do
120
- [ -z "$src" ] && continue
121
- local dir="$repo_root/$src"
122
- [ -d "$dir" ] || continue
123
- for f in "$dir"/*.md; do
124
- [ -f "$f" ] || continue
125
- files+=("$f")
126
- done
127
- done <<< "$list"
128
-
129
- local -a scoped=()
130
- if [ "${#files[@]}" -gt 0 ]; then
131
- local path hp
132
- while IFS=$'\x1f' read -r path hp; do
133
- [ -n "$path" ] || continue
134
- [ "$hp" -eq 0 ] && continue
135
- scoped+=("$path")
136
- done < <(frontmatter_index "paths#" "${files[@]}")
137
- fi
117
+ local i always_count=0 spec="" report="" LS=$'\x1e'
118
+ local -a scoped=() scoped_names=()
119
+ output_rel_var "$repo_root" "$instructions_dir"
120
+ source_artifacts_var "$repo_root" "$config_file" rules copilot "$IS_OUTPUT_REL/{}.instructions.md"
121
+ for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
122
+ if [ "${IS_SOURCE_SCOPED[i]}" = 1 ]; then
123
+ scoped+=("${IS_SOURCE_FILES[i]}")
124
+ scoped_names+=("${IS_SOURCE_NAMES[i]}")
125
+ elif [ "${IS_SOURCE_IN_AGENTS[i]}" = 0 ]; then
126
+ # An always-on rule AGENTS.md does not carry applies everywhere.
127
+ spec+="${IS_SOURCE_FILES[i]}"$'\x1f'"$instructions_dir/${IS_SOURCE_NAMES[i]}.instructions.md"$'\x1f'"fence_nofm"$'\x1f'"0"$'\x1f'"none"$'\x1f'"${LS}---${LS}applyTo: \"**\"${LS}---${LS}"$'\x1f\n'
128
+ report+=" rule: ${IS_SOURCE_NAMES[i]}.instructions.md (always-on)"$'\n'
129
+ always_count=$((always_count + 1))
130
+ fi
131
+ done
138
132
 
139
133
  if [ "${#scoped[@]}" -gt 0 ]; then
140
- local base paths_line spec="" report="" LS=$'\x1e'
134
+ local path base paths_line
135
+ i=0
141
136
  while IFS=$'\x1f' read -r path paths_line; do
142
- [ -n "$path" ] || continue
143
- base="${path##*/}"; base="${base%.md}"
137
+ base="${scoped_names[i]}"
138
+ i=$((i + 1))
144
139
 
145
140
  local header
146
141
  header="${LS}---"
@@ -152,12 +147,17 @@ sync_copilot_rules() {
152
147
  report+=" rule: $base.instructions.md (scoped)"$'\n'
153
148
  scoped_count=$((scoped_count + 1))
154
149
  done < <(copilot_paths_rows "${scoped[@]}")
155
-
150
+ fi
151
+ if [ -n "$spec" ]; then
156
152
  emit_wrapped_bodies "$spec"
157
153
  printf '%s' "$report"
158
154
  fi
159
155
 
160
- echo " rules: $scoped_count scoped"
156
+ if [ "$always_count" -gt 0 ]; then
157
+ echo " rules: $scoped_count scoped, $always_count always-on"
158
+ else
159
+ echo " rules: $scoped_count scoped"
160
+ fi
161
161
  }
162
162
 
163
163
  # Sync skills to Copilot format (SKILL.md in .github/skills/)
@@ -166,33 +166,23 @@ sync_copilot_skills() {
166
166
  local config_file="$2"
167
167
  local output_dir="$3"
168
168
 
169
- local src d skill_name
169
+ local i name
170
170
  local -a skill_dirs=()
171
- # The inventory decides which skills render, symlinked ones included
172
- # (decision 0017); the glob keeps this adapter's own listing order.
173
- skill_source_inventory "$repo_root" "$config_file"
174
- load_yaml_list "$config_file" "skills"
175
- local list="$IS_YAML_LIST"
176
- while IFS= read -r src; do
177
- [ -z "$src" ] && continue
178
- local dir="$repo_root/$src"
179
- [ -d "$dir" ] || continue
180
- for d in "$dir"/*/; do
181
- [ -d "$d" ] || continue
182
- [ -f "$d/SKILL.md" ] || continue
183
- skill_source_rendered "$d" || continue
184
- skill_dirs+=("$d")
185
- done
186
- done <<< "$list"
171
+ # The catalog's skills are the ones the inventory renders, symlinked ones
172
+ # included (decision 0017).
173
+ output_rel_var "$repo_root" "$output_dir"
174
+ source_artifacts_var "$repo_root" "$config_file" skills copilot "$IS_OUTPUT_REL/skills/{}"
175
+ for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
176
+ skill_dirs+=("${IS_SOURCE_FILES[i]%/SKILL.md}/")
177
+ done
187
178
 
188
179
  local count=0
189
180
  if [ "${#skill_dirs[@]}" -gt 0 ]; then
190
181
  # Copilot has no effort field: the copy carries no `effort:`.
191
- copy_skill_bundle_dirs_for copilot "$output_dir/skills" "${skill_dirs[@]}"
192
- for d in "${skill_dirs[@]}"; do
193
- skill_name="${d%/}"
182
+ copy_skill_bundle_dirs_named copilot "$output_dir/skills" "$IS_SOURCE_NAME_MAP" "${skill_dirs[@]}"
183
+ for name in "${IS_SOURCE_NAMES[@]}"; do
194
184
  count=$((count + 1))
195
- echo " skill: ${skill_name##*/}"
185
+ echo " skill: $name"
196
186
  done
197
187
  fi
198
188
 
@@ -205,19 +195,13 @@ sync_copilot_agents() {
205
195
  local config_file="$2"
206
196
  local output_dir="$3"
207
197
 
208
- local src f
209
- local -a files=()
210
- load_yaml_list "$config_file" "agents"
211
- local list="$IS_YAML_LIST"
212
- while IFS= read -r src; do
213
- [ -z "$src" ] && continue
214
- local dir="$repo_root/$src"
215
- [ -d "$dir" ] || continue
216
- for f in "$dir"/*.md; do
217
- [ -f "$f" ] || continue
218
- files+=("$f")
219
- done
220
- done <<< "$list"
198
+ local -a files=() names=()
199
+ output_rel_var "$repo_root" "$output_dir"
200
+ source_artifacts_var "$repo_root" "$config_file" agents copilot "$IS_OUTPUT_REL/agents/{}.agent.md"
201
+ if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
202
+ files=("${IS_SOURCE_FILES[@]}")
203
+ names=("${IS_SOURCE_NAMES[@]}")
204
+ fi
221
205
 
222
206
  local count=0
223
207
  if [ "${#files[@]}" -gt 0 ]; then
@@ -225,8 +209,7 @@ sync_copilot_agents() {
225
209
 
226
210
  local path tier access description name tools_line spec="" report="" LS=$'\x1e'
227
211
  while IFS=$'\x1f' read -r path tier access description; do
228
- [ -n "$path" ] || continue
229
- name="${path##*/}"; name="${name%.md}"
212
+ name="${names[count]}"
230
213
  resolve_model_var "$tier"
231
214
 
232
215
  # Build tools list based on access. Tool aliases per Copilot custom-agent