@ainova-systems/intelligence 0.19.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.
- package/cli/commands/init.sh +2 -1
- package/cli/commands/registry.sh +1 -1
- package/cli/commands/source.sh +1 -1
- package/cli/commands/sync.sh +78 -6
- package/cli/intelligence +15 -2
- package/cli/internal/check.sh +39 -0
- package/cli/internal/convert-legacy.sh +1 -1
- package/cli/internal/package-search.sh +1 -1
- package/cli/internal/package-update.sh +2 -2
- package/cli/internal/restore.sh +3 -2
- package/cli/lib/cli-common.sh +145 -33
- package/cli/lib/gitignore.sh +19 -2
- package/cli/lib/lockfile.sh +14 -4
- package/cli/lib/manifest.sh +147 -1
- package/cli/lib/qmap.awk +97 -32
- package/cli/lib/registry.sh +1 -1
- package/cli/lib/sync-cache.sh +596 -142
- package/cli/lib/sync-lock.sh +19 -2
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/engine/adapters/_template.sh +32 -8
- package/engine/adapters/agents.sh +47 -52
- package/engine/adapters/antigravity.sh +71 -53
- package/engine/adapters/claude.sh +42 -57
- package/engine/adapters/codex.sh +77 -16
- package/engine/adapters/copilot.sh +54 -65
- package/engine/adapters/cursor.sh +88 -78
- package/engine/adapters/opencode.sh +23 -32
- package/engine/adapters/pi.sh +140 -42
- package/engine/lib/adapter-contract.sh +36 -4
- package/engine/lib/common.sh +1388 -165
- package/engine/lib/contract.sh +5 -0
- package/engine/sync.sh +145 -38
- package/package.json +1 -1
- package/packages/sync/references/adapters.md +48 -38
- package/packages/sync/references/conventions.md +61 -12
- 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
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,
|
|
@@ -16,7 +23,9 @@
|
|
|
16
23
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
17
24
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
27
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
28
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
20
29
|
|
|
21
30
|
adapter_contract_codex() {
|
|
22
31
|
local output="${1%/}"
|
|
@@ -24,8 +33,61 @@ adapter_contract_codex() {
|
|
|
24
33
|
adapter_contract_requires agents
|
|
25
34
|
adapter_contract_managed ".agents/skills"
|
|
26
35
|
adapter_contract_owned "$output/agents"
|
|
36
|
+
# Marker-managed: a hand-written override is left alone.
|
|
37
|
+
adapter_contract_managed "AGENTS.override.md"
|
|
27
38
|
adapter_contract_ignore ".agents/skills/"
|
|
28
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"
|
|
29
91
|
}
|
|
30
92
|
|
|
31
93
|
# Codex-specific guard for its documented project-instruction byte budget.
|
|
@@ -50,6 +112,8 @@ codex_warn_project_doc_size() {
|
|
|
50
112
|
target_output_var "$config_file" "agents"
|
|
51
113
|
local output
|
|
52
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"
|
|
53
117
|
|
|
54
118
|
local output_file="$repo_root/$output"
|
|
55
119
|
[ -f "$output_file" ] || return 0
|
|
@@ -81,19 +145,13 @@ sync_codex_agents() {
|
|
|
81
145
|
local config_file="$2"
|
|
82
146
|
local output_dir="$3"
|
|
83
147
|
|
|
84
|
-
local
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
[ -d "$dir" ] || continue
|
|
92
|
-
for f in "$dir"/*.md; do
|
|
93
|
-
[ -f "$f" ] || continue
|
|
94
|
-
files+=("$f")
|
|
95
|
-
done
|
|
96
|
-
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
|
|
97
155
|
|
|
98
156
|
local count=0
|
|
99
157
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
@@ -101,8 +159,7 @@ sync_codex_agents() {
|
|
|
101
159
|
|
|
102
160
|
local path tier access description effort name sandbox spec="" report="" LS=$'\x1e'
|
|
103
161
|
while IFS=$'\x1f' read -r path tier access description effort; do
|
|
104
|
-
|
|
105
|
-
name="${path##*/}"; name="${name%.md}"
|
|
162
|
+
name="${names[count]}"
|
|
106
163
|
resolve_model_var "$tier"
|
|
107
164
|
map_effort_var codex "$effort"
|
|
108
165
|
case "$access" in
|
|
@@ -153,5 +210,9 @@ sync_to_codex() {
|
|
|
153
210
|
mkdir -p "$agents_dir"
|
|
154
211
|
sync_codex_agents "$repo_root" "$config_file" "$agents_dir"
|
|
155
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
|
+
|
|
156
217
|
codex_warn_project_doc_size "$repo_root" "$config_file"
|
|
157
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
|
#
|
|
@@ -22,7 +24,9 @@
|
|
|
22
24
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
23
25
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
28
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
29
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
26
30
|
|
|
27
31
|
adapter_contract_copilot() {
|
|
28
32
|
local output="${1%/}" record=adapter_contract_ignore sub
|
|
@@ -98,8 +102,8 @@ copilot_paths_rows() {
|
|
|
98
102
|
' "$@"
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
# Sync rules to Copilot format.
|
|
102
|
-
#
|
|
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.
|
|
103
107
|
sync_copilot_rules() {
|
|
104
108
|
local repo_root="$1"
|
|
105
109
|
local config_file="$2"
|
|
@@ -110,35 +114,28 @@ sync_copilot_rules() {
|
|
|
110
114
|
|
|
111
115
|
local scoped_count=0
|
|
112
116
|
|
|
113
|
-
local
|
|
114
|
-
local -a
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
[
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if [ "${#files[@]}" -gt 0 ]; then
|
|
129
|
-
local path hp
|
|
130
|
-
while IFS=$'\x1f' read -r path hp; do
|
|
131
|
-
[ -n "$path" ] || continue
|
|
132
|
-
[ "$hp" -eq 0 ] && continue
|
|
133
|
-
scoped+=("$path")
|
|
134
|
-
done < <(frontmatter_index "paths#" "${files[@]}")
|
|
135
|
-
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
|
|
136
132
|
|
|
137
133
|
if [ "${#scoped[@]}" -gt 0 ]; then
|
|
138
|
-
local base paths_line
|
|
134
|
+
local path base paths_line
|
|
135
|
+
i=0
|
|
139
136
|
while IFS=$'\x1f' read -r path paths_line; do
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
base="${scoped_names[i]}"
|
|
138
|
+
i=$((i + 1))
|
|
142
139
|
|
|
143
140
|
local header
|
|
144
141
|
header="${LS}---"
|
|
@@ -150,12 +147,17 @@ sync_copilot_rules() {
|
|
|
150
147
|
report+=" rule: $base.instructions.md (scoped)"$'\n'
|
|
151
148
|
scoped_count=$((scoped_count + 1))
|
|
152
149
|
done < <(copilot_paths_rows "${scoped[@]}")
|
|
153
|
-
|
|
150
|
+
fi
|
|
151
|
+
if [ -n "$spec" ]; then
|
|
154
152
|
emit_wrapped_bodies "$spec"
|
|
155
153
|
printf '%s' "$report"
|
|
156
154
|
fi
|
|
157
155
|
|
|
158
|
-
|
|
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
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
# Sync skills to Copilot format (SKILL.md in .github/skills/)
|
|
@@ -164,29 +166,23 @@ sync_copilot_skills() {
|
|
|
164
166
|
local config_file="$2"
|
|
165
167
|
local output_dir="$3"
|
|
166
168
|
|
|
167
|
-
local
|
|
169
|
+
local i name
|
|
168
170
|
local -a skill_dirs=()
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
[ -d "$d" ] || continue
|
|
177
|
-
[ -f "$d/SKILL.md" ] || continue
|
|
178
|
-
skill_dirs+=("$d")
|
|
179
|
-
done
|
|
180
|
-
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
|
|
181
178
|
|
|
182
179
|
local count=0
|
|
183
180
|
if [ "${#skill_dirs[@]}" -gt 0 ]; then
|
|
184
181
|
# Copilot has no effort field: the copy carries no `effort:`.
|
|
185
|
-
|
|
186
|
-
for
|
|
187
|
-
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
|
|
188
184
|
count=$((count + 1))
|
|
189
|
-
echo " skill: $
|
|
185
|
+
echo " skill: $name"
|
|
190
186
|
done
|
|
191
187
|
fi
|
|
192
188
|
|
|
@@ -199,19 +195,13 @@ sync_copilot_agents() {
|
|
|
199
195
|
local config_file="$2"
|
|
200
196
|
local output_dir="$3"
|
|
201
197
|
|
|
202
|
-
local
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
[ -d "$dir" ] || continue
|
|
210
|
-
for f in "$dir"/*.md; do
|
|
211
|
-
[ -f "$f" ] || continue
|
|
212
|
-
files+=("$f")
|
|
213
|
-
done
|
|
214
|
-
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
|
|
215
205
|
|
|
216
206
|
local count=0
|
|
217
207
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
@@ -219,8 +209,7 @@ sync_copilot_agents() {
|
|
|
219
209
|
|
|
220
210
|
local path tier access description name tools_line spec="" report="" LS=$'\x1e'
|
|
221
211
|
while IFS=$'\x1f' read -r path tier access description; do
|
|
222
|
-
|
|
223
|
-
name="${path##*/}"; name="${name%.md}"
|
|
212
|
+
name="${names[count]}"
|
|
224
213
|
resolve_model_var "$tier"
|
|
225
214
|
|
|
226
215
|
# Build tools list based on access. Tool aliases per Copilot custom-agent
|
|
@@ -268,7 +257,7 @@ sync_to_copilot() {
|
|
|
268
257
|
# Clean generated content (preserve workflows, etc.)
|
|
269
258
|
rm -rf "$output_dir/instructions" "$output_dir/prompts"
|
|
270
259
|
if [ -d "$output_dir/skills" ]; then
|
|
271
|
-
find "$output_dir/skills" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} +
|
|
260
|
+
find "$output_dir/skills" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -exec rm -rf {} +
|
|
272
261
|
fi
|
|
273
262
|
rm -rf "$output_dir/agents"
|
|
274
263
|
mkdir -p "$output_dir" "$output_dir/skills" "$output_dir/agents"
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Rules:
|
|
6
6
|
# - Path-scoped (with `paths:`) -> .cursor/rules/<name>.mdc with `globs:`
|
|
7
|
-
# - Always-on (no `paths:`) -> SKIPPED here
|
|
8
|
-
# natively for project-level context, and the
|
|
9
|
-
# always-on rule content into AGENTS.md.
|
|
10
|
-
# same rules would cause double-loading and
|
|
7
|
+
# - Always-on (no `paths:`) -> SKIPPED here while AGENTS.md carries it.
|
|
8
|
+
# Cursor reads AGENTS.md natively for project-level context, and the
|
|
9
|
+
# `agents` adapter inlines always-on rule content into AGENTS.md.
|
|
10
|
+
# Generating .mdc copies of the same rules would cause double-loading and
|
|
11
|
+
# burn the context window. An always-on rule AGENTS.md does not carry — a
|
|
12
|
+
# local one, or one whose targets leave `agents` out — becomes
|
|
13
|
+
# .cursor/rules/<name>.mdc with `alwaysApply: true` (decision 0021).
|
|
11
14
|
# Skills: copy skill directories in full (SKILL.md + bundled resources)
|
|
12
15
|
# Agents: strip tier/access/tools/disallowedTools, add model + readonly:true
|
|
13
16
|
# Cursor has no per-agent or per-skill effort field (its effort is part of a
|
|
@@ -16,7 +19,9 @@
|
|
|
16
19
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
17
20
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
23
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
24
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
20
25
|
|
|
21
26
|
adapter_contract_cursor() {
|
|
22
27
|
local output="${1%/}"
|
|
@@ -39,45 +44,55 @@ sync_cursor_rules() {
|
|
|
39
44
|
local config_file="$2"
|
|
40
45
|
local output_dir="$3"
|
|
41
46
|
|
|
42
|
-
local
|
|
43
|
-
local -a files=()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
local i always=""
|
|
48
|
+
local -a files=() names=() kinds=()
|
|
49
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
50
|
+
source_artifacts_var "$repo_root" "$config_file" rules cursor "$IS_OUTPUT_REL/rules/{}.mdc"
|
|
51
|
+
# A scoped rule takes Cursor's own channel. An always-on rule does only
|
|
52
|
+
# when AGENTS.md, which Cursor reads, does not carry it: a local rule, or
|
|
53
|
+
# one whose targets leave `agents` out (decision 0021).
|
|
54
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
55
|
+
if [ "${IS_SOURCE_SCOPED[i]}" = 1 ]; then
|
|
56
|
+
kinds+=(scoped)
|
|
57
|
+
elif [ "${IS_SOURCE_IN_AGENTS[i]}" = 0 ]; then
|
|
58
|
+
kinds+=(always-on)
|
|
59
|
+
always="$always${IS_SOURCE_FILES[i]}"$'\n'
|
|
60
|
+
else
|
|
61
|
+
continue
|
|
62
|
+
fi
|
|
63
|
+
files+=("${IS_SOURCE_FILES[i]}")
|
|
64
|
+
names+=("${IS_SOURCE_NAMES[i]}")
|
|
65
|
+
done
|
|
55
66
|
[ "${#files[@]}" -gt 0 ] || return 0
|
|
56
67
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
scoped+=("$path")
|
|
64
|
-
done < <(frontmatter_index "paths#" "${files[@]}")
|
|
65
|
-
[ "${#scoped[@]}" -gt 0 ] || return 0
|
|
66
|
-
|
|
67
|
-
# Path-scoped rule -> Auto Attached (paths -> globs). The rewrite is
|
|
68
|
-
# anchored inside the frontmatter block: a body line may legitimately start
|
|
69
|
-
# with `paths:` (a rule documenting rule syntax does exactly that), and
|
|
70
|
-
# rewriting it would corrupt the text it teaches.
|
|
68
|
+
# Path-scoped rule -> Auto Attached (paths -> globs); always-on rule ->
|
|
69
|
+
# Always (`alwaysApply: true`), with a frontmatter block of its own when the
|
|
70
|
+
# source has none. The rewrite is anchored inside the frontmatter block: a
|
|
71
|
+
# body line may legitimately start with `paths:` (a rule documenting rule
|
|
72
|
+
# syntax does exactly that), and rewriting it would corrupt the text it
|
|
73
|
+
# teaches.
|
|
71
74
|
is_fin_awk_vars
|
|
72
|
-
awk "${IS_FIN_V[@]}" -v dst="$output_dir/rules" "$IS_AWK_LIB"'
|
|
75
|
+
IS_MDC_ALWAYS="$always" IS_NAME_MAP="$IS_SOURCE_NAME_MAP" awk "${IS_FIN_V[@]}" -v dst="$output_dir/rules" "$IS_AWK_LIB"'
|
|
76
|
+
BEGIN {
|
|
77
|
+
n = split(ENVIRON["IS_MDC_ALWAYS"], al, "\n")
|
|
78
|
+
for (k = 1; k <= n; k++) if (al[k] != "") ALWAYS[al[k]] = 1
|
|
79
|
+
}
|
|
73
80
|
FNR == 1 {
|
|
74
81
|
if (out != "") close(out)
|
|
75
|
-
|
|
76
|
-
sub(/\.md$/, ".mdc", nm)
|
|
77
|
-
out = dst "/" nm
|
|
82
|
+
out = dst "/" art_name(FILENAME) ".mdc"
|
|
78
83
|
fm = 0
|
|
84
|
+
tgt_reset()
|
|
79
85
|
}
|
|
80
86
|
{ sub(/\r$/, "") }
|
|
87
|
+
FNR == 1 && (FILENAME in ALWAYS) {
|
|
88
|
+
print fin_line("---") > out
|
|
89
|
+
print fin_line("alwaysApply: true") > out
|
|
90
|
+
if ($0 == "---") { fm = 1; next }
|
|
91
|
+
print fin_line("---") > out
|
|
92
|
+
fm = 2
|
|
93
|
+
print fin_line($0) > out
|
|
94
|
+
next
|
|
95
|
+
}
|
|
81
96
|
FNR == 1 {
|
|
82
97
|
if ($0 == "---") fm = 1
|
|
83
98
|
print fin_line($0) > out
|
|
@@ -85,12 +100,16 @@ sync_cursor_rules() {
|
|
|
85
100
|
next
|
|
86
101
|
}
|
|
87
102
|
fm == 1 && $0 == "---" { fm = 2; print fin_line($0) > out; next }
|
|
103
|
+
fm == 1 && tgt_drop($0) { next }
|
|
88
104
|
fm == 1 { sub(/^paths:/, "globs:") }
|
|
89
105
|
{ print fin_line($0) > out }
|
|
90
|
-
' "${
|
|
91
|
-
for
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
' "${files[@]}"
|
|
107
|
+
for ((i = 0; i < ${#files[@]}; i++)); do
|
|
108
|
+
# awk never reads a record from an empty source.
|
|
109
|
+
if [ "${kinds[i]}" = always-on ] && [ ! -s "${files[i]}" ]; then
|
|
110
|
+
printf -- '---\nalwaysApply: true\n---\n' > "$output_dir/rules/${names[i]}.mdc"
|
|
111
|
+
fi
|
|
112
|
+
echo " rule: ${names[i]}.mdc (${kinds[i]})"
|
|
94
113
|
done
|
|
95
114
|
}
|
|
96
115
|
|
|
@@ -100,25 +119,19 @@ sync_cursor_skills() {
|
|
|
100
119
|
local config_file="$2"
|
|
101
120
|
local output_dir="$3"
|
|
102
121
|
|
|
103
|
-
local
|
|
122
|
+
local i name
|
|
104
123
|
local -a skill_dirs=()
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
done <<< "$list"
|
|
117
|
-
[ "${#skill_dirs[@]}" -gt 0 ] || return 0
|
|
118
|
-
copy_skill_bundle_dirs_for cursor "$output_dir/skills" "${skill_dirs[@]}"
|
|
119
|
-
for d in "${skill_dirs[@]}"; do
|
|
120
|
-
skill_name="${d%/}"
|
|
121
|
-
echo " skill: ${skill_name##*/}"
|
|
124
|
+
# The catalog's skills are the ones the inventory renders, symlinked ones
|
|
125
|
+
# included (decision 0017).
|
|
126
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
127
|
+
source_artifacts_var "$repo_root" "$config_file" skills cursor "$IS_OUTPUT_REL/skills/{}"
|
|
128
|
+
[ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
|
|
129
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
130
|
+
skill_dirs+=("${IS_SOURCE_FILES[i]%/SKILL.md}/")
|
|
131
|
+
done
|
|
132
|
+
copy_skill_bundle_dirs_named cursor "$output_dir/skills" "$IS_SOURCE_NAME_MAP" "${skill_dirs[@]}"
|
|
133
|
+
for name in "${IS_SOURCE_NAMES[@]}"; do
|
|
134
|
+
echo " skill: $name"
|
|
122
135
|
done
|
|
123
136
|
}
|
|
124
137
|
|
|
@@ -128,37 +141,32 @@ sync_cursor_agents() {
|
|
|
128
141
|
local config_file="$2"
|
|
129
142
|
local output_dir="$3"
|
|
130
143
|
|
|
131
|
-
local
|
|
132
|
-
local -a files=()
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
for f in "$dir"/*.md; do
|
|
140
|
-
[ -f "$f" ] || continue
|
|
141
|
-
files+=("$f")
|
|
142
|
-
done
|
|
143
|
-
done <<< "$list"
|
|
144
|
-
[ "${#files[@]}" -gt 0 ] || return 0
|
|
144
|
+
local i
|
|
145
|
+
local -a files=() names=()
|
|
146
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
147
|
+
source_artifacts_var "$repo_root" "$config_file" agents cursor "$IS_OUTPUT_REL/agents/{}.md"
|
|
148
|
+
[ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
|
|
149
|
+
files=("${IS_SOURCE_FILES[@]}")
|
|
150
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
151
|
+
local name_map="$IS_SOURCE_NAME_MAP"
|
|
145
152
|
|
|
146
153
|
load_model_tiers "$config_file" "cursor"
|
|
147
154
|
|
|
148
155
|
local path tier access spec="" report="" cursor_readonly
|
|
156
|
+
i=0
|
|
149
157
|
while IFS=$'\x1f' read -r path tier access; do
|
|
150
|
-
[ -n "$path" ] || continue
|
|
151
158
|
resolve_model_var "$tier"
|
|
152
159
|
cursor_readonly=""
|
|
153
160
|
[ "$access" = "readonly" ] && cursor_readonly="readonly: true"
|
|
154
161
|
spec+="$path"$'\x1f'"$IS_MODEL"$'\x1f'"$cursor_readonly"$'\n'
|
|
155
|
-
report+=" agent: ${
|
|
162
|
+
report+=" agent: ${names[i]}.md (tier=$tier -> cursor)"$'\n'
|
|
163
|
+
i=$((i + 1))
|
|
156
164
|
done < <(frontmatter_index "tier,access" "${files[@]}")
|
|
157
165
|
|
|
158
166
|
# Always emit model: line (even when value matches Cursor's default)
|
|
159
167
|
# so config is explicit and grep-able.
|
|
160
168
|
is_fin_awk_vars
|
|
161
|
-
IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
|
|
169
|
+
IS_NAME_MAP="$name_map" IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
|
|
162
170
|
BEGIN {
|
|
163
171
|
US = sprintf("%c", 31)
|
|
164
172
|
n = split(ENVIRON["IS_MAP_SPEC"], recs, "\n")
|
|
@@ -170,9 +178,10 @@ sync_cursor_agents() {
|
|
|
170
178
|
}
|
|
171
179
|
FNR == 1 {
|
|
172
180
|
if (out != "") close(out)
|
|
173
|
-
out = dst "/"
|
|
181
|
+
out = dst "/" art_file(FILENAME, ".md")
|
|
174
182
|
count = 0
|
|
175
183
|
fm = 0
|
|
184
|
+
tgt_reset()
|
|
176
185
|
}
|
|
177
186
|
/^tier:/ { next }
|
|
178
187
|
/^access:/ { next }
|
|
@@ -181,6 +190,7 @@ sync_cursor_agents() {
|
|
|
181
190
|
{ sub(/\r$/, "") }
|
|
182
191
|
FNR == 1 && $0 == "---" { fm = 1 }
|
|
183
192
|
/^---$/ { count++ }
|
|
193
|
+
fm && count == 1 && !/^---$/ && tgt_drop($0) { next }
|
|
184
194
|
fm && count == 1 && substr($0, 1, 7) == "effort:" { next }
|
|
185
195
|
count == 2 && /^---$/ {
|
|
186
196
|
print fin_line("model: " MODEL[FILENAME]) > out
|
|
@@ -188,8 +198,8 @@ sync_cursor_agents() {
|
|
|
188
198
|
}
|
|
189
199
|
{ print fin_line($0) > out }
|
|
190
200
|
' "${files[@]}"
|
|
191
|
-
for
|
|
192
|
-
[ -s "$
|
|
201
|
+
for ((i = 0; i < ${#files[@]}; i++)); do
|
|
202
|
+
[ -s "${files[i]}" ] || : > "$output_dir/agents/${names[i]}.md"
|
|
193
203
|
done
|
|
194
204
|
printf '%s' "$report"
|
|
195
205
|
}
|