@ainova-systems/intelligence 0.20.0 → 0.22.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.
- package/cli/commands/init.sh +2 -1
- package/cli/commands/sync.sh +9 -0
- package/cli/internal/check.sh +39 -0
- package/cli/lib/gitignore.sh +19 -2
- package/cli/lib/sync-lock.sh +103 -21
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/engine/adapters/_template.sh +32 -8
- package/engine/adapters/agents.sh +42 -50
- package/engine/adapters/antigravity.sh +68 -52
- package/engine/adapters/claude-plugin/.claude-plugin/plugin.json +6 -0
- package/engine/adapters/claude-plugin/README.md +14 -0
- package/engine/adapters/claude-plugin/hooks/hooks.json +3 -0
- package/engine/adapters/claude-plugin/hooks/register.js +324 -0
- package/engine/adapters/claude.sh +129 -59
- package/engine/adapters/codex.sh +74 -15
- package/engine/adapters/copilot.sh +56 -79
- package/engine/adapters/cursor.sh +85 -81
- package/engine/adapters/opencode.sh +20 -35
- package/engine/adapters/pi.sh +137 -41
- package/engine/lib/common.sh +753 -56
- package/engine/lib/contract.sh +15 -6
- package/engine/sync.sh +49 -33
- package/package.json +1 -1
- package/packages/sync/references/adapters.md +56 -40
- package/packages/sync/references/conventions.md +57 -11
- package/packages/sync/references/onboarding-migration.md +4 -4
- package/packages/sync/rules/intelligence-authoring.md +2 -2
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +7 -5
- package/packages/sync/skills/intelligence-review-context/references/audit-checks.md +1 -1
- package/packages/sync/skills/intelligence-update-context/references/rules.md +5 -0
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
# Skills: copy skill directories in full (SKILL.md + bundled resources),
|
|
7
7
|
# effort -> Claude's effort level
|
|
8
8
|
# Agents: tier -> model, effort -> effort, access -> tools/disallowedTools
|
|
9
|
+
# Plugin: with `targets.claude.plugin: true`, the Claude Code mod in
|
|
10
|
+
# adapters/claude-plugin/ is rendered into <output>/skills/intelligence/
|
|
11
|
+
# (decision 0022). Claude Code loads a plugin from a project's skills
|
|
12
|
+
# directory, so the plugin lives inside a path this contract already owns:
|
|
13
|
+
# ownership, Git policy, snapshots and `sync --check` need no new record.
|
|
9
14
|
#
|
|
10
15
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
11
16
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
@@ -29,29 +34,67 @@ adapter_contract_claude() {
|
|
|
29
34
|
adapter_contract_include "$output/settings.json"
|
|
30
35
|
}
|
|
31
36
|
|
|
37
|
+
# claude_plugin_var <manifest> — the one reader of targets.claude.plugin. Sets
|
|
38
|
+
# IS_CLAUDE_PLUGIN to 1 when the project renders the Claude Code plugin and 0
|
|
39
|
+
# for the default; returns 1 for a value target_bool_var refuses.
|
|
40
|
+
claude_plugin_var() {
|
|
41
|
+
local rc=0
|
|
42
|
+
target_bool_var "$1" claude plugin || rc=1
|
|
43
|
+
IS_CLAUDE_PLUGIN="$IS_TARGET_BOOL"
|
|
44
|
+
return "$rc"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# The directory name the plugin takes under <output>/skills: a source skill
|
|
48
|
+
# rendered under this name would be overwritten, so sync refuses the pair.
|
|
49
|
+
IS_CLAUDE_PLUGIN_NAME=intelligence
|
|
50
|
+
|
|
51
|
+
# render_claude_plugin <output_dir> — copy the plugin template byte for byte,
|
|
52
|
+
# stamp the engine version into its manifest and normalize line endings. The
|
|
53
|
+
# template is in the engine's adapter directory, never in the content package:
|
|
54
|
+
# it is executable tooling, and the bundle copies all of engine/. It is found
|
|
55
|
+
# through the engine root, so a project's own claude.sh renders it too.
|
|
56
|
+
render_claude_plugin() {
|
|
57
|
+
local output_dir="$1" tpl
|
|
58
|
+
local dst="$output_dir/skills/$IS_CLAUDE_PLUGIN_NAME"
|
|
59
|
+
# Rendered beside the destination, under a name no skill can take, and
|
|
60
|
+
# swapped in only when it differs: a running session reloads the mod on
|
|
61
|
+
# every file change, and announces each reload in its transcript.
|
|
62
|
+
local tmp="$output_dir/skills/.$IS_CLAUDE_PLUGIN_NAME.render.$$"
|
|
63
|
+
engine_root_var
|
|
64
|
+
tpl="$IS_ENGINE_ROOT/adapters/claude-plugin"
|
|
65
|
+
engine_version_var
|
|
66
|
+
rm -rf "$tmp"
|
|
67
|
+
mkdir -p "$tmp/.claude-plugin" "$tmp/hooks"
|
|
68
|
+
cp "$tpl/hooks/hooks.json" "$tpl/hooks/register.js" "$tmp/hooks/"
|
|
69
|
+
cp "$tpl/README.md" "$tmp/README.md"
|
|
70
|
+
awk -v v="$IS_ENGINE_VERSION" '{ sub(/__INTELLIGENCE_VERSION__/, v); print }' \
|
|
71
|
+
"$tpl/.claude-plugin/plugin.json" > "$tmp/.claude-plugin/plugin.json"
|
|
72
|
+
finalize_output_files "$tmp/.claude-plugin/plugin.json" "$tmp/hooks/hooks.json" \
|
|
73
|
+
"$tmp/hooks/register.js" "$tmp/README.md"
|
|
74
|
+
if [ -d "$dst" ] && [ ! -L "$dst" ] && diff -r -q "$tmp" "$dst" >/dev/null 2>&1; then
|
|
75
|
+
rm -rf "$tmp"
|
|
76
|
+
else
|
|
77
|
+
rm -rf "$dst"
|
|
78
|
+
mv "$tmp" "$dst"
|
|
79
|
+
fi
|
|
80
|
+
echo " plugin: $IS_CLAUDE_PLUGIN_NAME (Claude Code mod $IS_ENGINE_VERSION)"
|
|
81
|
+
}
|
|
82
|
+
|
|
32
83
|
# Sync rules to Claude format (copy as-is, normalize LF)
|
|
33
84
|
sync_claude_rules() {
|
|
34
85
|
local repo_root="$1"
|
|
35
86
|
local config_file="$2"
|
|
36
87
|
local output_dir="$3"
|
|
37
88
|
|
|
38
|
-
local
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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##*/}"
|
|
89
|
+
local name
|
|
90
|
+
# Every source reaches this adapter through the source catalog (decision
|
|
91
|
+
# 0021): rendered names, overrides and local artifacts are settled there.
|
|
92
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
93
|
+
source_artifacts_var "$repo_root" "$config_file" rules claude "$IS_OUTPUT_REL/rules/{}.md"
|
|
94
|
+
[ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
|
|
95
|
+
finalize_copy_named "$output_dir/rules" "$IS_SOURCE_NAME_MAP" "${IS_SOURCE_FILES[@]}"
|
|
96
|
+
for name in "${IS_SOURCE_NAMES[@]}"; do
|
|
97
|
+
echo " rule: $name.md"
|
|
55
98
|
done
|
|
56
99
|
}
|
|
57
100
|
|
|
@@ -61,29 +104,38 @@ sync_claude_skills() {
|
|
|
61
104
|
local config_file="$2"
|
|
62
105
|
local output_dir="$3"
|
|
63
106
|
|
|
64
|
-
local
|
|
107
|
+
local i name clash="" nocase=0
|
|
65
108
|
local -a skill_dirs=()
|
|
66
|
-
# The
|
|
67
|
-
# (decision 0017)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
109
|
+
# The catalog's skills are the ones the inventory renders, symlinked ones
|
|
110
|
+
# included (decision 0017).
|
|
111
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
112
|
+
source_artifacts_var "$repo_root" "$config_file" skills claude "$IS_OUTPUT_REL/skills/{}"
|
|
113
|
+
[ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
|
|
114
|
+
# The plugin takes one skill directory name; a skill rendered under it and
|
|
115
|
+
# the plugin would each erase the other, so neither is written. Names
|
|
116
|
+
# compare without case: on Windows and macOS `Intelligence/` is the same
|
|
117
|
+
# directory. (nocasematch, not ${name,,}: macOS ships bash 3.2.)
|
|
118
|
+
if [ "${IS_CLAUDE_PLUGIN:-0}" = 1 ]; then
|
|
119
|
+
shopt -q nocasematch && nocase=1
|
|
120
|
+
shopt -s nocasematch
|
|
121
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
122
|
+
if [[ "${IS_SOURCE_NAMES[i]}" == "$IS_CLAUDE_PLUGIN_NAME" ]]; then
|
|
123
|
+
clash="${IS_SOURCE_FILES[i]#"$repo_root/"}"
|
|
124
|
+
break
|
|
125
|
+
fi
|
|
80
126
|
done
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
127
|
+
[ "$nocase" = 1 ] || shopt -u nocasematch
|
|
128
|
+
if [ -n "$clash" ]; then
|
|
129
|
+
echo "ERROR: skill $clash renders as $IS_OUTPUT_REL/skills/$IS_CLAUDE_PLUGIN_NAME, the directory targets.claude.plugin: true reserves for the Claude Code plugin. Rename the skill or set targets.claude.plugin to false." >&2
|
|
130
|
+
return 1
|
|
131
|
+
fi
|
|
132
|
+
fi
|
|
133
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
134
|
+
skill_dirs+=("${IS_SOURCE_FILES[i]%/SKILL.md}/")
|
|
135
|
+
done
|
|
136
|
+
copy_skill_bundle_dirs_named claude "$output_dir/skills" "$IS_SOURCE_NAME_MAP" "${skill_dirs[@]}"
|
|
137
|
+
for name in "${IS_SOURCE_NAMES[@]}"; do
|
|
138
|
+
echo " skill: $name"
|
|
87
139
|
done
|
|
88
140
|
}
|
|
89
141
|
|
|
@@ -94,32 +146,27 @@ sync_claude_agents() {
|
|
|
94
146
|
local config_file="$2"
|
|
95
147
|
local output_dir="$3"
|
|
96
148
|
|
|
97
|
-
local
|
|
98
|
-
local -a files=()
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
149
|
+
local i
|
|
150
|
+
local -a files=() names=()
|
|
151
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
152
|
+
source_artifacts_var "$repo_root" "$config_file" agents claude "$IS_OUTPUT_REL/agents/{}.md"
|
|
153
|
+
[ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
|
|
154
|
+
files=("${IS_SOURCE_FILES[@]}")
|
|
155
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
156
|
+
local name_map="$IS_SOURCE_NAME_MAP"
|
|
111
157
|
|
|
112
158
|
load_model_tiers "$config_file" "claude"
|
|
113
159
|
|
|
114
160
|
local path tier access effort spec="" report=""
|
|
161
|
+
i=0
|
|
115
162
|
while IFS=$'\x1f' read -r path tier access effort; do
|
|
116
|
-
[ -n "$path" ] || continue
|
|
117
163
|
resolve_model_var "$tier"
|
|
118
164
|
map_effort_var claude "$effort"
|
|
119
165
|
map_access_to_claude_tools_var "$access"
|
|
120
166
|
map_access_to_claude_disallowed_var "$access"
|
|
121
167
|
spec+="$path"$'\x1f'"$IS_MODEL"$'\x1f'"$IS_CLAUDE_TOOLS"$'\x1f'"$IS_CLAUDE_DISALLOWED"$'\x1f'"$IS_EFFORT"$'\n'
|
|
122
|
-
report+=" agent: ${
|
|
168
|
+
report+=" agent: ${names[i]}.md (tier=$tier -> model=$IS_MODEL, access=$access)"$'\n'
|
|
169
|
+
i=$((i + 1))
|
|
123
170
|
done < <(frontmatter_index "tier,access,effort" "${files[@]}")
|
|
124
171
|
|
|
125
172
|
# The transform used to run per file with the values injected via -v;
|
|
@@ -128,7 +175,7 @@ sync_claude_agents() {
|
|
|
128
175
|
# becomes Claude's level in place, or goes when there is none; a later
|
|
129
176
|
# one goes too, since frontmatter_index read only the first.
|
|
130
177
|
is_fin_awk_vars
|
|
131
|
-
IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
|
|
178
|
+
IS_NAME_MAP="$name_map" IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
|
|
132
179
|
BEGIN {
|
|
133
180
|
US = sprintf("%c", 31)
|
|
134
181
|
n = split(ENVIRON["IS_MAP_SPEC"], recs, "\n")
|
|
@@ -140,16 +187,18 @@ sync_claude_agents() {
|
|
|
140
187
|
}
|
|
141
188
|
FNR == 1 {
|
|
142
189
|
if (out != "") close(out)
|
|
143
|
-
out = dst "/"
|
|
190
|
+
out = dst "/" art_file(FILENAME, ".md")
|
|
144
191
|
count = 0
|
|
145
192
|
fm = 0
|
|
146
193
|
effort_seen = 0
|
|
194
|
+
tgt_reset()
|
|
147
195
|
}
|
|
148
196
|
/^tier:/ { next }
|
|
149
197
|
/^access:/ { next }
|
|
150
198
|
{ sub(/\r$/, "") }
|
|
151
199
|
FNR == 1 && $0 == "---" { fm = 1 }
|
|
152
200
|
/^---$/ { count++ }
|
|
201
|
+
fm && count == 1 && !/^---$/ && tgt_drop($0) { next }
|
|
153
202
|
fm && count == 1 && substr($0, 1, 7) == "effort:" {
|
|
154
203
|
if (!effort_seen++ && EFFORT[FILENAME] != "") print fin_line("effort: " EFFORT[FILENAME]) > out
|
|
155
204
|
next
|
|
@@ -163,8 +212,8 @@ sync_claude_agents() {
|
|
|
163
212
|
' "${files[@]}"
|
|
164
213
|
# awk never reads a record from an empty source; the old per-file shell
|
|
165
214
|
# redirect still created an empty output for it.
|
|
166
|
-
for
|
|
167
|
-
[ -s "$
|
|
215
|
+
for ((i = 0; i < ${#files[@]}; i++)); do
|
|
216
|
+
[ -s "${files[i]}" ] || : > "$output_dir/agents/${names[i]}.md"
|
|
168
217
|
done
|
|
169
218
|
printf '%s' "$report"
|
|
170
219
|
}
|
|
@@ -177,18 +226,39 @@ sync_to_claude() {
|
|
|
177
226
|
|
|
178
227
|
echo "=== Claude Code ==="
|
|
179
228
|
|
|
229
|
+
# The flag is read before anything is removed: a malformed value refuses
|
|
230
|
+
# the sync with the reason, and the output stays exactly as it was.
|
|
231
|
+
claude_plugin_var "$config_file" || {
|
|
232
|
+
echo "ERROR: targets.claude.plugin must be true or false: true renders the Claude Code plugin into ${output_dir#"$repo_root/"}/skills/$IS_CLAUDE_PLUGIN_NAME, false or omission does not." >&2
|
|
233
|
+
return 1
|
|
234
|
+
}
|
|
235
|
+
|
|
180
236
|
# Clean generated content only — preserve any project-level files
|
|
181
237
|
# the user manages directly (settings.json, settings.local.json,
|
|
182
238
|
# commands/, statusline.sh, etc.).
|
|
183
239
|
rm -rf "$output_dir/rules" "$output_dir/agents"
|
|
184
240
|
if [ -d "$output_dir/skills" ]; then
|
|
185
|
-
|
|
241
|
+
# The plugin directory stays while the flag holds: Claude Code reloads
|
|
242
|
+
# a mod whose files change, so render_claude_plugin rewrites it only
|
|
243
|
+
# when its content differs. A skill of that name was refused above.
|
|
244
|
+
if [ "$IS_CLAUDE_PLUGIN" = 1 ]; then
|
|
245
|
+
find "$output_dir/skills" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) \
|
|
246
|
+
! -name "$IS_CLAUDE_PLUGIN_NAME" -exec rm -rf {} +
|
|
247
|
+
else
|
|
248
|
+
find "$output_dir/skills" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -exec rm -rf {} +
|
|
249
|
+
fi
|
|
186
250
|
fi
|
|
187
251
|
mkdir -p "$output_dir/rules" "$output_dir/skills" "$output_dir/agents"
|
|
188
252
|
|
|
189
253
|
sync_claude_rules "$repo_root" "$config_file" "$output_dir"
|
|
254
|
+
# Called plainly, never on the left of `||`: that would turn errexit off
|
|
255
|
+
# for every copy inside, and a failed one would no longer fail the sync.
|
|
190
256
|
sync_claude_skills "$repo_root" "$config_file" "$output_dir"
|
|
191
257
|
sync_claude_agents "$repo_root" "$config_file" "$output_dir"
|
|
258
|
+
# The skills renderer has refused a skill of the plugin's name by now, so
|
|
259
|
+
# the plugin never lands on top of one. Cleared above with every other
|
|
260
|
+
# skill directory, it exists only while the flag says so.
|
|
261
|
+
[ "$IS_CLAUDE_PLUGIN" = 0 ] || render_claude_plugin "$output_dir"
|
|
192
262
|
|
|
193
263
|
count_matching_files "$output_dir/rules" "*.md" \
|
|
194
264
|
"$output_dir/skills" "SKILL.md" "$output_dir/agents" "*.md"
|
package/engine/adapters/codex.sh
CHANGED
|
@@ -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
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
#
|
|
@@ -53,19 +55,13 @@ adapter_contract_copilot() {
|
|
|
53
55
|
|
|
54
56
|
# copilot_commit_output_var <manifest> — the one reader of
|
|
55
57
|
# targets.copilot.commit_output. Sets IS_COPILOT_COMMIT_OUTPUT to 1 when the
|
|
56
|
-
# project commits generated output and 0 for the default; returns 1 for
|
|
57
|
-
#
|
|
58
|
-
# only — is the default.
|
|
58
|
+
# project commits generated output and 0 for the default; returns 1 for a value
|
|
59
|
+
# target_bool_var refuses.
|
|
59
60
|
copilot_commit_output_var() {
|
|
60
|
-
local
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
case "$setting" in
|
|
65
|
-
''|false) ;;
|
|
66
|
-
true) IS_COPILOT_COMMIT_OUTPUT=1 ;;
|
|
67
|
-
*) return 1 ;;
|
|
68
|
-
esac
|
|
61
|
+
local rc=0
|
|
62
|
+
target_bool_var "$1" copilot commit_output || rc=1
|
|
63
|
+
IS_COPILOT_COMMIT_OUTPUT="$IS_TARGET_BOOL"
|
|
64
|
+
return "$rc"
|
|
69
65
|
}
|
|
70
66
|
|
|
71
67
|
# copilot_paths_rows <file>... — batch form of the old per-file `paths:`
|
|
@@ -100,8 +96,8 @@ copilot_paths_rows() {
|
|
|
100
96
|
' "$@"
|
|
101
97
|
}
|
|
102
98
|
|
|
103
|
-
# Sync rules to Copilot format.
|
|
104
|
-
#
|
|
99
|
+
# Sync rules to Copilot format. Path-scoped rules are emitted, and an always-on
|
|
100
|
+
# rule only when AGENTS.md (read by Copilot natively) does not carry it.
|
|
105
101
|
sync_copilot_rules() {
|
|
106
102
|
local repo_root="$1"
|
|
107
103
|
local config_file="$2"
|
|
@@ -112,35 +108,28 @@ sync_copilot_rules() {
|
|
|
112
108
|
|
|
113
109
|
local scoped_count=0
|
|
114
110
|
|
|
115
|
-
local
|
|
116
|
-
local -a
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
[
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
111
|
+
local i always_count=0 spec="" report="" LS=$'\x1e'
|
|
112
|
+
local -a scoped=() scoped_names=()
|
|
113
|
+
output_rel_var "$repo_root" "$instructions_dir"
|
|
114
|
+
source_artifacts_var "$repo_root" "$config_file" rules copilot "$IS_OUTPUT_REL/{}.instructions.md"
|
|
115
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
116
|
+
if [ "${IS_SOURCE_SCOPED[i]}" = 1 ]; then
|
|
117
|
+
scoped+=("${IS_SOURCE_FILES[i]}")
|
|
118
|
+
scoped_names+=("${IS_SOURCE_NAMES[i]}")
|
|
119
|
+
elif [ "${IS_SOURCE_IN_AGENTS[i]}" = 0 ]; then
|
|
120
|
+
# An always-on rule AGENTS.md does not carry applies everywhere.
|
|
121
|
+
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'
|
|
122
|
+
report+=" rule: ${IS_SOURCE_NAMES[i]}.instructions.md (always-on)"$'\n'
|
|
123
|
+
always_count=$((always_count + 1))
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
138
126
|
|
|
139
127
|
if [ "${#scoped[@]}" -gt 0 ]; then
|
|
140
|
-
local base paths_line
|
|
128
|
+
local path base paths_line
|
|
129
|
+
i=0
|
|
141
130
|
while IFS=$'\x1f' read -r path paths_line; do
|
|
142
|
-
|
|
143
|
-
|
|
131
|
+
base="${scoped_names[i]}"
|
|
132
|
+
i=$((i + 1))
|
|
144
133
|
|
|
145
134
|
local header
|
|
146
135
|
header="${LS}---"
|
|
@@ -152,12 +141,17 @@ sync_copilot_rules() {
|
|
|
152
141
|
report+=" rule: $base.instructions.md (scoped)"$'\n'
|
|
153
142
|
scoped_count=$((scoped_count + 1))
|
|
154
143
|
done < <(copilot_paths_rows "${scoped[@]}")
|
|
155
|
-
|
|
144
|
+
fi
|
|
145
|
+
if [ -n "$spec" ]; then
|
|
156
146
|
emit_wrapped_bodies "$spec"
|
|
157
147
|
printf '%s' "$report"
|
|
158
148
|
fi
|
|
159
149
|
|
|
160
|
-
|
|
150
|
+
if [ "$always_count" -gt 0 ]; then
|
|
151
|
+
echo " rules: $scoped_count scoped, $always_count always-on"
|
|
152
|
+
else
|
|
153
|
+
echo " rules: $scoped_count scoped"
|
|
154
|
+
fi
|
|
161
155
|
}
|
|
162
156
|
|
|
163
157
|
# Sync skills to Copilot format (SKILL.md in .github/skills/)
|
|
@@ -166,33 +160,23 @@ sync_copilot_skills() {
|
|
|
166
160
|
local config_file="$2"
|
|
167
161
|
local output_dir="$3"
|
|
168
162
|
|
|
169
|
-
local
|
|
163
|
+
local i name
|
|
170
164
|
local -a skill_dirs=()
|
|
171
|
-
# The
|
|
172
|
-
# (decision 0017)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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"
|
|
165
|
+
# The catalog's skills are the ones the inventory renders, symlinked ones
|
|
166
|
+
# included (decision 0017).
|
|
167
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
168
|
+
source_artifacts_var "$repo_root" "$config_file" skills copilot "$IS_OUTPUT_REL/skills/{}"
|
|
169
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
170
|
+
skill_dirs+=("${IS_SOURCE_FILES[i]%/SKILL.md}/")
|
|
171
|
+
done
|
|
187
172
|
|
|
188
173
|
local count=0
|
|
189
174
|
if [ "${#skill_dirs[@]}" -gt 0 ]; then
|
|
190
175
|
# Copilot has no effort field: the copy carries no `effort:`.
|
|
191
|
-
|
|
192
|
-
for
|
|
193
|
-
skill_name="${d%/}"
|
|
176
|
+
copy_skill_bundle_dirs_named copilot "$output_dir/skills" "$IS_SOURCE_NAME_MAP" "${skill_dirs[@]}"
|
|
177
|
+
for name in "${IS_SOURCE_NAMES[@]}"; do
|
|
194
178
|
count=$((count + 1))
|
|
195
|
-
echo " skill: $
|
|
179
|
+
echo " skill: $name"
|
|
196
180
|
done
|
|
197
181
|
fi
|
|
198
182
|
|
|
@@ -205,19 +189,13 @@ sync_copilot_agents() {
|
|
|
205
189
|
local config_file="$2"
|
|
206
190
|
local output_dir="$3"
|
|
207
191
|
|
|
208
|
-
local
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
[ -d "$dir" ] || continue
|
|
216
|
-
for f in "$dir"/*.md; do
|
|
217
|
-
[ -f "$f" ] || continue
|
|
218
|
-
files+=("$f")
|
|
219
|
-
done
|
|
220
|
-
done <<< "$list"
|
|
192
|
+
local -a files=() names=()
|
|
193
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
194
|
+
source_artifacts_var "$repo_root" "$config_file" agents copilot "$IS_OUTPUT_REL/agents/{}.agent.md"
|
|
195
|
+
if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
|
|
196
|
+
files=("${IS_SOURCE_FILES[@]}")
|
|
197
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
198
|
+
fi
|
|
221
199
|
|
|
222
200
|
local count=0
|
|
223
201
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
@@ -225,8 +203,7 @@ sync_copilot_agents() {
|
|
|
225
203
|
|
|
226
204
|
local path tier access description name tools_line spec="" report="" LS=$'\x1e'
|
|
227
205
|
while IFS=$'\x1f' read -r path tier access description; do
|
|
228
|
-
|
|
229
|
-
name="${path##*/}"; name="${name%.md}"
|
|
206
|
+
name="${names[count]}"
|
|
230
207
|
resolve_model_var "$tier"
|
|
231
208
|
|
|
232
209
|
# Build tools list based on access. Tool aliases per Copilot custom-agent
|