@ainova-systems/intelligence 0.11.3 → 0.11.5

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.
@@ -57,9 +57,10 @@ if [ "$compact" -eq 0 ]; then
57
57
  exit $?
58
58
  fi
59
59
 
60
- # Compact mode is intentionally quiet only on success. Buffering the complete
61
- # combined stream means a failure still returns every diagnostic emitted by
62
- # lifecycle preflight, locked restore or the engine, together with its real rc.
60
+ # Compact mode keeps actionable one-line warnings on success. Buffering the
61
+ # complete combined stream means a failure still returns every diagnostic
62
+ # emitted by lifecycle preflight, locked restore or the engine, together with
63
+ # its real rc.
63
64
  compact_output="$(mktemp -t intelligence-sync-XXXXXX)"
64
65
  trap 'rm -f "$compact_output"' EXIT
65
66
  rc=0
@@ -76,5 +77,6 @@ if [ -z "$status_line" ] || [ -z "$done_line" ]; then
76
77
  echo "ERROR: sync succeeded without its final status contract" >&2
77
78
  exit 1
78
79
  fi
80
+ grep -E '^(WARNING:|CONTEXT:)' "$compact_output" || true
79
81
  echo "$status_line"
80
82
  echo "$done_line"
package/cli/intelligence CHANGED
@@ -47,7 +47,7 @@ Usage: intelligence <command> [args]
47
47
  --targets a,b --dir name --bare --no-sync New-project options
48
48
  --force Accept a dirty worktree during legacy project conversion
49
49
  sync [adapter] [--compact] Restore locked content, then render enabled adapters
50
- --compact On success show only final status; on failure show diagnostics
50
+ --compact On success show context, warnings and final status; on failure show diagnostics
51
51
  update [@scope/name] Show an update plan; ask before applying it
52
52
  --preview Show the plan without asking or writing
53
53
  --apply Apply the plan without asking
package/engine/ENGINE_SHA CHANGED
@@ -1 +1 @@
1
- 81c1719ad7eb845aa0e48ebae0fcf069afb105bd
1
+ bd688bcdb5443d808a72ed4e63805a65255a7d02
package/engine/VERSION CHANGED
@@ -1 +1 @@
1
- 0.11.3
1
+ 0.11.5
@@ -10,6 +10,9 @@
10
10
  # Unlike other adapters, the output is a single committed markdown file
11
11
  # meant to be read by both humans and LLMs. It must never be hand-edited —
12
12
  # every sync regenerates it from scratch.
13
+ #
14
+ # Every per-file loop batches its work into one awk process (see the batched
15
+ # helpers in lib/common.sh): process spawns dominate sync time on Windows.
13
16
 
14
17
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
15
18
 
@@ -55,30 +58,26 @@ agents_md_append_agents_table() {
55
58
  local rows=""
56
59
  local count=0
57
60
 
58
- while IFS= read -r src; do
59
- [ -z "$src" ] && continue
60
- local dir
61
- dir="$(resolve_source_dir "$repo_root" "$src")"
62
- [ -d "$dir" ] || continue
63
- # Byte-order (LC_ALL=C) sort so generated output is identical across
64
- # platforms — bash glob order follows LC_COLLATE, which differs between
65
- # Linux CI (UTF-8, ignores `-`) and Git Bash (C, byte order).
66
- while IFS= read -r f; do
67
- [ -f "$f" ] || continue
68
- local name rel tier access desc
69
- name="$(basename "$f" .md)"
70
- rel="$(repo_rel_link "$repo_root" "$f")"
71
- tier=$(get_frontmatter_value "tier" "$f")
72
- access=$(get_frontmatter_value "access" "$f")
73
- desc=$(get_frontmatter_value "description" "$f")
74
- if [ -n "$rel" ]; then
75
- rows+="| [$name]($rel) | ${tier:--} | ${access:--} | ${desc:--} |"$'\n'
61
+ local f
62
+ local -a files=()
63
+ while IFS= read -r f; do
64
+ [ -n "$f" ] && files+=("$f")
65
+ done < <(source_artifact_files "$repo_root" "$config_file" "agents")
66
+
67
+ if [ "${#files[@]}" -gt 0 ]; then
68
+ local path tier access desc name
69
+ while IFS=$'\x1f' read -r path tier access desc; do
70
+ [ -n "$path" ] || continue
71
+ name="${path##*/}"; name="${name%.md}"
72
+ repo_rel_link_var "$repo_root" "$path"
73
+ if [ -n "$IS_REPO_REL" ]; then
74
+ rows+="| [$name]($IS_REPO_REL) | ${tier:--} | ${access:--} | ${desc:--} |"$'\n'
76
75
  else
77
76
  rows+="| $name | ${tier:--} | ${access:--} | ${desc:--} |"$'\n'
78
77
  fi
79
78
  count=$((count + 1))
80
- done < <(find "$dir" -maxdepth 1 -type f -name '*.md' -print | LC_ALL=C sort)
81
- done < <(read_yaml_list "$config_file" "agents")
79
+ done < <(frontmatter_index "tier,access,description" "${files[@]}")
80
+ fi
82
81
 
83
82
  [ "$count" -eq 0 ] && return 0
84
83
 
@@ -102,31 +101,26 @@ agents_md_append_skills_table() {
102
101
  local rows=""
103
102
  local count=0
104
103
 
105
- while IFS= read -r src; do
106
- [ -z "$src" ] && continue
107
- local dir
108
- dir="$(resolve_source_dir "$repo_root" "$src")"
109
- [ -d "$dir" ] || continue
110
- # Byte-order (LC_ALL=C) sort so generated output is identical across
111
- # platforms — see the note in agents_md_append_agents_table.
112
- while IFS= read -r skill_dir; do
113
- [ -d "$skill_dir" ] || continue
114
- local dirname
115
- dirname="$(basename "$skill_dir")"
116
- case "$dirname" in _*) continue ;; esac
117
- local skill_file="${skill_dir%/}/SKILL.md"
118
- [ -f "$skill_file" ] || continue
119
- local rel desc
120
- rel="$(repo_rel_link "$repo_root" "$skill_file")"
121
- desc=$(get_frontmatter_value "description" "$skill_file")
122
- if [ -n "$rel" ]; then
123
- rows+="| [$dirname]($rel) | ${desc:--} |"$'\n'
104
+ local f dirname
105
+ local -a skill_files=()
106
+ while IFS= read -r f; do
107
+ [ -n "$f" ] && skill_files+=("$f")
108
+ done < <(source_artifact_files "$repo_root" "$config_file" "skills")
109
+
110
+ if [ "${#skill_files[@]}" -gt 0 ]; then
111
+ local path desc
112
+ while IFS=$'\x1f' read -r path desc; do
113
+ [ -n "$path" ] || continue
114
+ dirname="${path%/SKILL.md}"; dirname="${dirname##*/}"
115
+ repo_rel_link_var "$repo_root" "$path"
116
+ if [ -n "$IS_REPO_REL" ]; then
117
+ rows+="| [$dirname]($IS_REPO_REL) | ${desc:--} |"$'\n'
124
118
  else
125
119
  rows+="| $dirname | ${desc:--} |"$'\n'
126
120
  fi
127
121
  count=$((count + 1))
128
- done < <(find "$dir" -mindepth 1 -maxdepth 1 -type d -print | LC_ALL=C sort)
129
- done < <(read_yaml_list "$config_file" "skills")
122
+ done < <(frontmatter_index "description" "${skill_files[@]}")
123
+ fi
130
124
 
131
125
  [ "$count" -eq 0 ] && return 0
132
126
 
@@ -151,33 +145,32 @@ agents_md_append_rules_list() {
151
145
  local count=0
152
146
  local global_rule_files=()
153
147
 
154
- while IFS= read -r src; do
155
- [ -z "$src" ] && continue
156
- local dir
157
- dir="$(resolve_source_dir "$repo_root" "$src")"
158
- [ -d "$dir" ] || continue
159
- # Byte-order (LC_ALL=C) sort so generated output — and the inline order
160
- # of always-on rules below — is identical across platforms. See the
161
- # note in agents_md_append_agents_table.
162
- while IFS= read -r f; do
163
- [ -f "$f" ] || continue
164
- local name rel scope
165
- name="$(basename "$f" .md)"
166
- rel="$(repo_rel_link "$repo_root" "$f")"
148
+ local f
149
+ local -a files=()
150
+ while IFS= read -r f; do
151
+ [ -n "$f" ] && files+=("$f")
152
+ done < <(source_artifact_files "$repo_root" "$config_file" "rules")
153
+
154
+ if [ "${#files[@]}" -gt 0 ]; then
155
+ local path hp name scope
156
+ while IFS=$'\x1f' read -r path hp; do
157
+ [ -n "$path" ] || continue
158
+ name="${path##*/}"; name="${name%.md}"
167
159
  scope="global"
168
- if [ "$(has_paths "$f")" != "0" ]; then
160
+ if [ "$hp" != "0" ]; then
169
161
  scope="scoped"
170
162
  else
171
- global_rule_files+=("$f")
163
+ global_rule_files+=("$path")
172
164
  fi
173
- if [ -n "$rel" ]; then
174
- lines+="- [$name]($rel) ($scope)"$'\n'
165
+ repo_rel_link_var "$repo_root" "$path"
166
+ if [ -n "$IS_REPO_REL" ]; then
167
+ lines+="- [$name]($IS_REPO_REL) ($scope)"$'\n'
175
168
  else
176
169
  lines+="- $name ($scope)"$'\n'
177
170
  fi
178
171
  count=$((count + 1))
179
- done < <(find "$dir" -maxdepth 1 -type f -name '*.md' -print | LC_ALL=C sort)
180
- done < <(read_yaml_list "$config_file" "rules")
172
+ done < <(frontmatter_index "paths#" "${files[@]}")
173
+ fi
181
174
 
182
175
  [ "$count" -eq 0 ] && return 0
183
176
 
@@ -206,28 +199,25 @@ agents_md_append_rules_list() {
206
199
  echo "<!-- Inlined from always-on rules in $content_rel/rules/ -->"
207
200
  echo ""
208
201
  } >> "$output"
209
- local rf
210
- for rf in "${global_rule_files[@]}"; do
211
- awk '
212
- BEGIN { in_fm=0; past_fm=0 }
213
- { sub(/\r$/, "") }
214
- /^---$/ {
215
- if (!past_fm) { in_fm = !in_fm; if (!in_fm) { past_fm=1 }; next }
216
- }
217
- past_fm || !in_fm { print }
218
- ' "$rf" >> "$output"
219
- echo "" >> "$output"
220
- done
202
+ # One awk inlines every global rule body, with a blank line after
203
+ # each — the same output the per-file passes produced.
204
+ awk '
205
+ FNR == 1 { if (NR != 1) print ""; in_fm = 0; past_fm = 0 }
206
+ { sub(/\r$/, "") }
207
+ /^---$/ {
208
+ if (!past_fm) { in_fm = !in_fm; if (!in_fm) { past_fm = 1 }; next }
209
+ }
210
+ past_fm || !in_fm { print }
211
+ END { print "" }
212
+ ' "${global_rule_files[@]}" >> "$output"
221
213
  echo " rules: ${#global_rule_files[@]} global rule(s) inlined"
222
214
  fi
223
215
  }
224
216
 
225
217
  # Main entry point for AGENTS.md adapter
226
218
  adapter_contract_agents() {
227
- local output="$1"
228
- if [[ "$output" == */ ]] || [[ "$output" != *.md ]]; then
229
- output="${output%/}/AGENTS.md"
230
- fi
219
+ local output
220
+ output="$(agents_output_path "$1")"
231
221
  adapter_contract_version 1
232
222
  adapter_contract_owned "$output"
233
223
  adapter_contract_legacy "AGENTS.md"
@@ -244,10 +234,8 @@ sync_to_agents() {
244
234
  # If it looks like a directory lexically (trailing slash or no .md
245
235
  # extension), append the default filename. The ownership contract uses the
246
236
  # same rule, so filesystem state cannot make its write-set ambiguous.
247
- local output_file="$output_dir"
248
- if [[ "$output_file" == */ ]] || [[ "$output_file" != *.md ]]; then
249
- output_file="${output_file%/}/AGENTS.md"
250
- fi
237
+ local output_file
238
+ output_file="$(agents_output_path "$output_dir")"
251
239
 
252
240
  mkdir -p "$(dirname "$output_file")"
253
241
 
@@ -5,6 +5,9 @@
5
5
  # Rules: copy as-is (paths: frontmatter preserved)
6
6
  # Skills: copy skill directories in full (SKILL.md + bundled resources)
7
7
  # Agents: tier -> model, access -> tools/disallowedTools
8
+ #
9
+ # Every per-file loop batches its work into one awk process (see the batched
10
+ # helpers in lib/common.sh): process spawns dominate sync time on Windows.
8
11
 
9
12
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
10
13
 
@@ -29,18 +32,24 @@ sync_claude_rules() {
29
32
  local config_file="$2"
30
33
  local output_dir="$3"
31
34
 
35
+ local src f
36
+ local -a files=()
37
+ load_yaml_list "$config_file" "rules"
38
+ local list="$IS_YAML_LIST"
32
39
  while IFS= read -r src; do
33
40
  [ -z "$src" ] && continue
34
- local dir
35
- dir="$(resolve_source_dir "$repo_root" "$src")"
41
+ local dir="$repo_root/$src"
36
42
  [ -d "$dir" ] || continue
37
43
  for f in "$dir"/*.md; do
38
44
  [ -f "$f" ] || continue
39
- cp "$f" "$output_dir/rules/"
40
- finalize_output_file "$output_dir/rules/$(basename "$f")"
41
- echo " rule: $(basename "$f")"
45
+ files+=("$f")
42
46
  done
43
- done < <(read_yaml_list "$config_file" "rules")
47
+ done <<< "$list"
48
+ [ "${#files[@]}" -gt 0 ] || return 0
49
+ finalize_copy_files "$output_dir/rules" "${files[@]}"
50
+ for f in "${files[@]}"; do
51
+ echo " rule: ${f##*/}"
52
+ done
44
53
  }
45
54
 
46
55
  # Sync skills to Claude format (copy skill directories in full)
@@ -49,75 +58,98 @@ sync_claude_skills() {
49
58
  local config_file="$2"
50
59
  local output_dir="$3"
51
60
 
61
+ local src d skill_name
62
+ local -a skill_dirs=()
63
+ load_yaml_list "$config_file" "skills"
64
+ local list="$IS_YAML_LIST"
52
65
  while IFS= read -r src; do
53
66
  [ -z "$src" ] && continue
54
- local dir
55
- dir="$(resolve_source_dir "$repo_root" "$src")"
67
+ local dir="$repo_root/$src"
56
68
  [ -d "$dir" ] || continue
57
69
  for d in "$dir"/*/; do
58
70
  [ -d "$d" ] || continue
59
- local skill_name
60
- skill_name="$(basename "$d")"
61
71
  [ -f "$d/SKILL.md" ] || continue
62
- copy_skill_bundle "$d" "$output_dir/skills/$skill_name"
63
- echo " skill: $skill_name"
72
+ skill_dirs+=("$d")
64
73
  done
65
- done < <(read_yaml_list "$config_file" "skills")
66
- }
67
-
68
- # Sync a single agent file to Claude format
69
- sync_claude_agent() {
70
- local src="$1"
71
- local config_file="$2"
72
- local output_dir="$3"
73
- local name
74
- name="$(basename "$src")"
75
-
76
- local tier access
77
- tier=$(get_frontmatter_value "tier" "$src")
78
- access=$(get_frontmatter_value "access" "$src")
79
-
80
- local model tools extra
81
- model=$(get_model "$config_file" "claude" "$tier")
82
- tools=$(map_access_to_claude_tools "$access")
83
- extra=$(map_access_to_claude_disallowed "$access")
84
-
85
- # Transform: remove tier/access, inject tools/model/disallowedTools before closing ---
86
- awk -v model="$model" -v tools="$tools" -v extra="$extra" '
87
- BEGIN { count=0 }
88
- /^tier:/ { next }
89
- /^access:/ { next }
90
- {
91
- sub(/\r$/, "")
92
- }
93
- /^---$/ { count++ }
94
- count==2 && /^---$/ {
95
- if (tools != "") print "tools: " tools
96
- if (extra != "") print "disallowedTools: " extra
97
- print "model: " model
98
- }
99
- { print }
100
- ' "$src" > "$output_dir/agents/$name"
101
- finalize_output_file "$output_dir/agents/$name"
102
- echo " agent: $name (tier=$tier -> model=$model, access=$access)"
74
+ done <<< "$list"
75
+ [ "${#skill_dirs[@]}" -gt 0 ] || return 0
76
+ copy_skill_bundle_dirs "$output_dir/skills" "${skill_dirs[@]}"
77
+ for d in "${skill_dirs[@]}"; do
78
+ skill_name="${d%/}"
79
+ echo " skill: ${skill_name##*/}"
80
+ done
103
81
  }
104
82
 
105
- # Sync all agents to Claude format
83
+ # Sync all agents to Claude format: one frontmatter pass resolves
84
+ # tier/access for every file, one transform pass writes every output.
106
85
  sync_claude_agents() {
107
86
  local repo_root="$1"
108
87
  local config_file="$2"
109
88
  local output_dir="$3"
110
89
 
90
+ local src f
91
+ local -a files=()
92
+ load_yaml_list "$config_file" "agents"
93
+ local list="$IS_YAML_LIST"
111
94
  while IFS= read -r src; do
112
95
  [ -z "$src" ] && continue
113
- local dir
114
- dir="$(resolve_source_dir "$repo_root" "$src")"
96
+ local dir="$repo_root/$src"
115
97
  [ -d "$dir" ] || continue
116
98
  for f in "$dir"/*.md; do
117
99
  [ -f "$f" ] || continue
118
- sync_claude_agent "$f" "$config_file" "$output_dir"
100
+ files+=("$f")
119
101
  done
120
- done < <(read_yaml_list "$config_file" "agents")
102
+ done <<< "$list"
103
+ [ "${#files[@]}" -gt 0 ] || return 0
104
+
105
+ load_model_tiers "$config_file" "claude"
106
+
107
+ local path tier access spec="" report=""
108
+ while IFS=$'\x1f' read -r path tier access; do
109
+ [ -n "$path" ] || continue
110
+ resolve_model_var "$tier"
111
+ map_access_to_claude_tools_var "$access"
112
+ map_access_to_claude_disallowed_var "$access"
113
+ spec+="$path"$'\x1f'"$IS_MODEL"$'\x1f'"$IS_CLAUDE_TOOLS"$'\x1f'"$IS_CLAUDE_DISALLOWED"$'\n'
114
+ report+=" agent: ${path##*/} (tier=$tier -> model=$IS_MODEL, access=$access)"$'\n'
115
+ done < <(frontmatter_index "tier,access" "${files[@]}")
116
+
117
+ # The transform used to run per file with the values injected via -v;
118
+ # the batched form carries them per file through the environment (awk -v
119
+ # would reprocess backslash escapes).
120
+ is_fin_awk_vars
121
+ IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
122
+ BEGIN {
123
+ US = sprintf("%c", 31)
124
+ n = split(ENVIRON["IS_MAP_SPEC"], recs, "\n")
125
+ for (i = 1; i <= n; i++) {
126
+ if (recs[i] == "") continue
127
+ split(recs[i], f, US)
128
+ MODEL[f[1]] = f[2]; TOOLS[f[1]] = f[3]; EXTRA[f[1]] = f[4]
129
+ }
130
+ }
131
+ FNR == 1 {
132
+ if (out != "") close(out)
133
+ out = dst "/" base_name(FILENAME)
134
+ count = 0
135
+ }
136
+ /^tier:/ { next }
137
+ /^access:/ { next }
138
+ { sub(/\r$/, "") }
139
+ /^---$/ { count++ }
140
+ count == 2 && /^---$/ {
141
+ if (TOOLS[FILENAME] != "") print fin_line("tools: " TOOLS[FILENAME]) > out
142
+ if (EXTRA[FILENAME] != "") print fin_line("disallowedTools: " EXTRA[FILENAME]) > out
143
+ print fin_line("model: " MODEL[FILENAME]) > out
144
+ }
145
+ { print fin_line($0) > out }
146
+ ' "${files[@]}"
147
+ # awk never reads a record from an empty source; the old per-file shell
148
+ # redirect still created an empty output for it.
149
+ for f in "${files[@]}"; do
150
+ [ -s "$f" ] || : > "$output_dir/agents/${f##*/}"
151
+ done
152
+ printf '%s' "$report"
121
153
  }
122
154
 
123
155
  # Main entry point for Claude adapter
@@ -133,9 +165,7 @@ sync_to_claude() {
133
165
  # commands/, statusline.sh, etc.).
134
166
  rm -rf "$output_dir/rules" "$output_dir/agents"
135
167
  if [ -d "$output_dir/skills" ]; then
136
- find "$output_dir/skills" -mindepth 1 -maxdepth 1 -type d | while read -r d; do
137
- rm -rf "$d"
138
- done
168
+ find "$output_dir/skills" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} +
139
169
  fi
140
170
  mkdir -p "$output_dir/rules" "$output_dir/skills" "$output_dir/agents"
141
171
 
@@ -10,6 +10,9 @@
10
10
  # reads from $REPO_ROOT/.agents/skills exclusively per official docs)
11
11
  # Agents: -> .codex/agents/{name}.toml (name, description, model,
12
12
  # model_reasoning_effort, sandbox_mode, developer_instructions)
13
+ #
14
+ # Every per-file loop batches its work into one awk process (see the batched
15
+ # helpers in lib/common.sh): process spawns dominate sync time on Windows.
13
16
 
14
17
  source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
15
18
 
@@ -23,6 +26,44 @@ adapter_contract_codex() {
23
26
  adapter_contract_ignore "$output/agents/"
24
27
  }
25
28
 
29
+ # Codex-specific guard for its documented project-instruction byte budget.
30
+ # A personal/project Codex config may already raise the actual limit, so the
31
+ # manifest exposes one warning setting: false disables it, while a positive
32
+ # byte count matches an effective limit the adapter cannot discover itself.
33
+ codex_warn_project_doc_size() {
34
+ local repo_root="$1"
35
+ local config_file="$2"
36
+ local warning_setting warning_limit=32768
37
+ warning_setting="$(get_target_field "$config_file" "codex" "warn_project_doc_limit")"
38
+ case "$warning_setting" in
39
+ ''|true) ;;
40
+ false) return 0 ;;
41
+ *[!0-9]*|0|0*)
42
+ echo "ERROR: targets.codex.warn_project_doc_limit must be true, false, or a positive byte count without leading zeros." >&2
43
+ return 1
44
+ ;;
45
+ *) warning_limit="$warning_setting" ;;
46
+ esac
47
+
48
+ target_output_var "$config_file" "agents"
49
+ local output
50
+ output="$(agents_output_path "${IS_TGT_OUTPUT:-.agents}")"
51
+
52
+ local output_file="$repo_root/$output"
53
+ [ -f "$output_file" ] || return 0
54
+
55
+ local bytes
56
+ bytes="$(context_files_bytes "$output_file")"
57
+ [ "$bytes" -gt "$warning_limit" ] || return 0
58
+
59
+ local recommended="$warning_limit"
60
+ while [ "$recommended" -lt "$bytes" ]; do
61
+ recommended=$((recommended * 2))
62
+ done
63
+
64
+ echo "WARNING: Codex may truncate $output: $bytes bytes exceeds the $warning_limit-byte Intelligence warning threshold; set project_doc_max_bytes = $recommended in Codex config.toml and restart Codex, reduce/split the instructions, set targets.codex.warn_project_doc_limit to the effective byte limit, or set it to false to disable this warning."
65
+ }
66
+
26
67
  # Sync skills to Codex format (.agents/skills/{name}/SKILL.md)
27
68
  sync_codex_skills() {
28
69
  local repo_root="$1"
@@ -38,24 +79,29 @@ sync_codex_agents() {
38
79
  local config_file="$2"
39
80
  local output_dir="$3"
40
81
 
41
- local count=0
82
+ local src f
83
+ local -a files=()
84
+ load_yaml_list "$config_file" "agents"
85
+ local list="$IS_YAML_LIST"
42
86
  while IFS= read -r src; do
43
87
  [ -z "$src" ] && continue
44
- local dir
45
- dir="$(resolve_source_dir "$repo_root" "$src")"
88
+ local dir="$repo_root/$src"
46
89
  [ -d "$dir" ] || continue
47
90
  for f in "$dir"/*.md; do
48
91
  [ -f "$f" ] || continue
49
- local name
50
- name="$(basename "$f" .md)"
51
-
52
- local tier access description
53
- tier=$(get_frontmatter_value "tier" "$f")
54
- access=$(get_frontmatter_value "access" "$f")
55
- description=$(get_frontmatter_value "description" "$f")
92
+ files+=("$f")
93
+ done
94
+ done <<< "$list"
56
95
 
57
- local model effort sandbox
58
- model=$(get_model "$config_file" "codex" "$tier")
96
+ local count=0
97
+ if [ "${#files[@]}" -gt 0 ]; then
98
+ load_model_tiers "$config_file" "codex"
99
+
100
+ local path tier access description name effort sandbox spec="" report="" LS=$'\x1e'
101
+ while IFS=$'\x1f' read -r path tier access description; do
102
+ [ -n "$path" ] || continue
103
+ name="${path##*/}"; name="${name%.md}"
104
+ resolve_model_var "$tier"
59
105
  case "$tier" in
60
106
  heavy) effort="high" ;;
61
107
  standard) effort="medium" ;;
@@ -66,43 +112,27 @@ sync_codex_agents() {
66
112
  readonly) sandbox="read-only" ;;
67
113
  *) sandbox="workspace-write" ;;
68
114
  esac
69
-
70
- local body
71
- body=$(awk '
72
- BEGIN { in_fm=0; past_fm=0 }
73
- { sub(/\r$/, "") }
74
- /^---$/ {
75
- if (!past_fm) { in_fm = !in_fm; if (!in_fm) { past_fm=1 }; next }
76
- }
77
- past_fm { print }
78
- ' "$f")
79
-
80
- # Escape backslashes used in Markdown paths, then neutralize any
81
- # closing TOML triple-quote sequence in the agent body.
82
- local body_safe
83
- body_safe="${body//\\/\\\\}"
84
- body_safe="${body_safe//\"\"\"/\"\"\\\"}"
85
-
86
115
  local name_escaped description_escaped
87
- name_escaped=$(toml_escape "$name")
88
- description_escaped=$(toml_escape "$description")
89
-
90
- {
91
- echo "name = \"$name_escaped\""
92
- echo "description = \"$description_escaped\""
93
- echo "model = \"$model\""
94
- echo "model_reasoning_effort = \"$effort\""
95
- echo "sandbox_mode = \"$sandbox\""
96
- echo ""
97
- echo "developer_instructions = \"\"\""
98
- echo "$body_safe"
99
- echo "\"\"\""
100
- } > "$output_dir/$name.toml"
101
- finalize_output_file "$output_dir/$name.toml"
116
+ toml_escape_var "$name"; name_escaped="$IS_TOML_ESCAPED"
117
+ toml_escape_var "$description"; description_escaped="$IS_TOML_ESCAPED"
118
+
119
+ local header
120
+ header="${LS}name = \"$name_escaped\""
121
+ header+="${LS}description = \"$description_escaped\""
122
+ header+="${LS}model = \"$IS_MODEL\""
123
+ header+="${LS}model_reasoning_effort = \"$effort\""
124
+ header+="${LS}sandbox_mode = \"$sandbox\""
125
+ header+="${LS}"
126
+ header+="${LS}developer_instructions = \"\"\""
127
+
128
+ spec+="$path"$'\x1f'"$output_dir/$name.toml"$'\x1f'"fence"$'\x1f'"1"$'\x1f'"toml"$'\x1f'"$header"$'\x1f'"${LS}\"\"\""$'\n'
129
+ report+=" agent: $name.toml"$'\n'
102
130
  count=$((count + 1))
103
- echo " agent: $name.toml"
104
- done
105
- done < <(read_yaml_list "$config_file" "agents")
131
+ done < <(frontmatter_index "tier,access,description" "${files[@]}")
132
+
133
+ emit_wrapped_bodies "$spec"
134
+ printf '%s' "$report"
135
+ fi
106
136
 
107
137
  echo " -> Agents: $count"
108
138
  }
@@ -125,4 +155,6 @@ sync_to_codex() {
125
155
  rm -rf "$agents_dir"
126
156
  mkdir -p "$agents_dir"
127
157
  sync_codex_agents "$repo_root" "$config_file" "$agents_dir"
158
+
159
+ codex_warn_project_doc_size "$repo_root" "$config_file"
128
160
  }