@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.
- package/cli/commands/init.sh +2 -1
- package/cli/internal/check.sh +39 -0
- package/cli/lib/gitignore.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 +42 -50
- package/engine/adapters/antigravity.sh +68 -52
- package/engine/adapters/claude.sh +38 -59
- package/engine/adapters/codex.sh +74 -15
- package/engine/adapters/copilot.sh +50 -67
- 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 +735 -56
- package/engine/sync.sh +49 -33
- package/package.json +1 -1
- package/packages/sync/references/adapters.md +44 -39
- 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
package/cli/commands/init.sh
CHANGED
|
@@ -356,7 +356,8 @@ manifest="$root/intelligence.yaml"
|
|
|
356
356
|
} > "$manifest"
|
|
357
357
|
|
|
358
358
|
# The package store is restorable state, never committed — the npm model.
|
|
359
|
-
|
|
359
|
+
# Local rules, agents and skills stay on this machine (decision 0021).
|
|
360
|
+
ensure_base_gitignore "$root" "$content_dir"
|
|
360
361
|
for target in $targets; do
|
|
361
362
|
ensure_target_gitignore "$root" "$manifest" "$target"
|
|
362
363
|
done
|
package/cli/internal/check.sh
CHANGED
|
@@ -198,6 +198,14 @@ while IFS= read -r target; do
|
|
|
198
198
|
ok "adapter $target contract v1"
|
|
199
199
|
done < <(target_names "$manifest")
|
|
200
200
|
|
|
201
|
+
# A local rule, agent or skill is one these patterns name (decision 0021): the
|
|
202
|
+
# engine keeps it out of every committed output, and without the pattern Git
|
|
203
|
+
# would still offer to commit the file itself.
|
|
204
|
+
while IFS= read -r pattern; do
|
|
205
|
+
ignore_file_has_line "$IP_ROOT/.gitignore" "$pattern" \
|
|
206
|
+
|| warn "local artifact Git policy is missing '$pattern' — run 'intelligence init'"
|
|
207
|
+
done < <(local_artifact_ignore_patterns "$content_dir")
|
|
208
|
+
|
|
201
209
|
# Lock orphans.
|
|
202
210
|
if [ "$lock_valid" -eq 1 ] && [ -f "$lock" ]; then
|
|
203
211
|
while IFS= read -r name; do
|
|
@@ -214,6 +222,7 @@ fi
|
|
|
214
222
|
# is exactly why doctor names it). Each entry is judged by the directory the
|
|
215
223
|
# engine renders it as, read through the same parser: a package reference by
|
|
216
224
|
# its store path, one that resolves to nothing by why.
|
|
225
|
+
local_specs=()
|
|
217
226
|
for section in rules agents skills; do
|
|
218
227
|
seen=$'\n'
|
|
219
228
|
while IFS=$'\037' read -r src state dir alias holders; do
|
|
@@ -269,10 +278,40 @@ for section in rules agents skills; do
|
|
|
269
278
|
# authoring later needs no config edit.
|
|
270
279
|
*) ok "sources.$section '$src' — not created yet (optional)" ;;
|
|
271
280
|
esac
|
|
281
|
+
continue
|
|
272
282
|
fi
|
|
283
|
+
# A project-owned source is where a tracked file can carry a local
|
|
284
|
+
# name; store content is restorable state Git never tracks.
|
|
285
|
+
case "$dir" in
|
|
286
|
+
.intelligence/*) ;;
|
|
287
|
+
*)
|
|
288
|
+
if [ "$section" = skills ]; then
|
|
289
|
+
local_specs+=(":(glob)${dir%/}/*.local/SKILL.md")
|
|
290
|
+
else
|
|
291
|
+
local_specs+=(":(glob)${dir%/}/*.local.md")
|
|
292
|
+
fi
|
|
293
|
+
;;
|
|
294
|
+
esac
|
|
273
295
|
done < <(read_source_entries "$manifest" "$section")
|
|
274
296
|
done
|
|
275
297
|
|
|
298
|
+
# A name makes an artifact local (decision 0021), and .gitignore never applies
|
|
299
|
+
# to a file Git already tracks: one committed before its name meant anything,
|
|
300
|
+
# or added by hand, renders only into ignored output while every clone holds
|
|
301
|
+
# it. Git and the engine would disagree about it, so it is named here.
|
|
302
|
+
if [ "${#local_specs[@]}" -gt 0 ] \
|
|
303
|
+
&& git -C "$IP_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
304
|
+
if tracked_local="$(git -C "$IP_ROOT" -c core.quotePath=false ls-files -- "${local_specs[@]}")"; then
|
|
305
|
+
while IFS= read -r path; do
|
|
306
|
+
[ -n "$path" ] || continue
|
|
307
|
+
case "$path" in */SKILL.md) path="${path%/SKILL.md}/" ;; esac
|
|
308
|
+
warn "$path is tracked by Git, but its name makes it local: sync renders it only into ignored output — rename it to share it, or 'git rm --cached' it to keep it on this machine"
|
|
309
|
+
done <<< "$tracked_local"
|
|
310
|
+
else
|
|
311
|
+
warn "tracked local artifacts could not be listed — 'git ls-files' failed"
|
|
312
|
+
fi
|
|
313
|
+
fi
|
|
314
|
+
|
|
276
315
|
if [ "$problems" -eq 0 ]; then
|
|
277
316
|
echo "all good."
|
|
278
317
|
else
|
package/cli/lib/gitignore.sh
CHANGED
|
@@ -312,10 +312,26 @@ ensure_gitignore_header() {
|
|
|
312
312
|
fi
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
# local_artifact_ignore_patterns <content-dir> — the patterns that make a rule,
|
|
316
|
+
# agent or skill local (decision 0021): a file or skill directory with one of
|
|
317
|
+
# these names never reaches committed output, and Git never commits it. The
|
|
318
|
+
# engine recognizes the same names, so the two cannot disagree; `init` writes
|
|
319
|
+
# them and `status --check` reports one missing.
|
|
320
|
+
local_artifact_ignore_patterns() {
|
|
321
|
+
local dir="${1#./}"
|
|
322
|
+
while [ "${dir%/}" != "$dir" ]; do dir="${dir%/}"; done
|
|
323
|
+
printf '%s\n' "$dir/rules/*.local.md" "$dir/agents/*.local.md" "$dir/skills/*.local/"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# ensure_base_gitignore <root> <content-dir> — the package store and the local
|
|
327
|
+
# artifact patterns of the project's content directory.
|
|
315
328
|
ensure_base_gitignore() {
|
|
316
|
-
local root="$1"
|
|
329
|
+
local root="$1" content_dir="$2" line
|
|
317
330
|
ensure_gitignore_header "$root"
|
|
318
331
|
gitignore_add_line "$root" '.intelligence/'
|
|
332
|
+
while IFS= read -r line; do
|
|
333
|
+
gitignore_add_line "$root" "$line"
|
|
334
|
+
done < <(local_artifact_ignore_patterns "$content_dir")
|
|
319
335
|
}
|
|
320
336
|
|
|
321
337
|
ensure_target_gitignore() {
|
|
@@ -343,7 +359,7 @@ ensure_target_gitignore() {
|
|
|
343
359
|
|
|
344
360
|
ensure_manifest_gitignore() {
|
|
345
361
|
local root="$1" manifest="$2" target
|
|
346
|
-
ensure_base_gitignore "$root"
|
|
362
|
+
ensure_base_gitignore "$root" "$(manifest_intelligence_dir "$manifest")"
|
|
347
363
|
while IFS= read -r target; do
|
|
348
364
|
[ -n "$target" ] || continue
|
|
349
365
|
if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
|
|
@@ -420,6 +436,7 @@ managed_gitignore_patterns() {
|
|
|
420
436
|
local root="$1" manifest="$2" content_dir target output records kind value
|
|
421
437
|
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
422
438
|
printf '%s\n' '.intelligence/' "$content_dir/_backup/"
|
|
439
|
+
local_artifact_ignore_patterns "$content_dir"
|
|
423
440
|
while IFS= read -r target; do
|
|
424
441
|
[ -n "$target" ] || continue
|
|
425
442
|
[ "$(is_target_enabled "$manifest" "$target")" = "1" ] || continue
|
package/engine/ENGINE_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
af0a0fdbc3f505e4f063ae516c9243c92db6e376
|
package/engine/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.21.0
|
|
@@ -30,9 +30,17 @@
|
|
|
30
30
|
# map_effort(tool, effort) — the tool's level for a neutral effort; "" when none
|
|
31
31
|
# map_access_to_claude_tools(access) — full->"" (no tools list; inherits all), readonly->restricted
|
|
32
32
|
# map_access_to_claude_disallowed(access) — readonly->"Write, Edit", full->""
|
|
33
|
-
#
|
|
34
|
-
#
|
|
33
|
+
# source_artifacts_var(root, config, section, adapter, output)
|
|
34
|
+
# — the sources this adapter renders (below)
|
|
35
|
+
# finalize_copy_named(dir, map, file...) — copy and finalize files under their rendered names
|
|
36
|
+
# copy_skill_bundle_dirs_named(tool, dir, map, skill_dir...) — copy skills under their rendered names
|
|
35
37
|
# get_target_field(config, target, field) — read a target's config field
|
|
38
|
+
#
|
|
39
|
+
# Read sources through source_artifacts_var, never with a glob of your own: it
|
|
40
|
+
# names each artifact (`.draft.md` is `draft`, `me.local.md` is `me`), renders
|
|
41
|
+
# only the later of two same-named artifacts, and leaves a local artifact out of
|
|
42
|
+
# every output path Git tracks (decision 0021). An adapter that globs sources
|
|
43
|
+
# itself can write a teammate's local file into a committed output.
|
|
36
44
|
|
|
37
45
|
# Project adapters are sourced by engine/sync.sh after the shared library is
|
|
38
46
|
# loaded. Keep this template position-independent: after scaffolding it lives
|
|
@@ -61,13 +69,24 @@ adapter_contract_<name>() {
|
|
|
61
69
|
# - Copy as-is (like Claude)
|
|
62
70
|
# - Convert paths: to globs: (like Cursor)
|
|
63
71
|
# - Merge into single file (like Copilot)
|
|
72
|
+
#
|
|
73
|
+
# source_artifacts_var takes the repo-relative path an artifact lands at, `{}`
|
|
74
|
+
# standing for its name, and fills IS_SOURCE_FILES (the source file, or a
|
|
75
|
+
# skill's SKILL.md), IS_SOURCE_NAMES, IS_SOURCE_SCOPED (1 for a rule with
|
|
76
|
+
# `paths:`), IS_SOURCE_IN_AGENTS (1 when AGENTS.md carries the artifact too)
|
|
77
|
+
# and IS_SOURCE_NAME_MAP, the names finalize_copy_named and
|
|
78
|
+
# copy_skill_bundle_dirs_named write under. It lists only the artifacts whose
|
|
79
|
+
# `targets:` / `exclude_targets:` select this adapter; the shared helpers drop
|
|
80
|
+
# both fields from every copy, and a frontmatter copied by hand must too.
|
|
64
81
|
sync_<name>_rules() {
|
|
65
82
|
local repo_root="$1"
|
|
66
83
|
local config_file="$2"
|
|
67
84
|
local output_dir="$3"
|
|
68
85
|
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
87
|
+
source_artifacts_var "$repo_root" "$config_file" rules <name> "$IS_OUTPUT_REL/rules/{}.md"
|
|
88
|
+
# TODO: Render each of IS_SOURCE_FILES into $output_dir/rules/ under its IS_SOURCE_NAMES entry
|
|
89
|
+
echo " rules: ${#IS_SOURCE_FILES[@]} found, not rendered"
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
# Sync skills for <agent-name>
|
|
@@ -76,8 +95,11 @@ sync_<name>_skills() {
|
|
|
76
95
|
local config_file="$2"
|
|
77
96
|
local output_dir="$3"
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
99
|
+
source_artifacts_var "$repo_root" "$config_file" skills <name> "$IS_OUTPUT_REL/skills/{}"
|
|
100
|
+
# TODO: Copy each skill directory ("${IS_SOURCE_FILES[i]%/SKILL.md}") with
|
|
101
|
+
# copy_skill_bundle_dirs_named <name> "$output_dir/skills" "$IS_SOURCE_NAME_MAP" <dir>...
|
|
102
|
+
echo " skills: ${#IS_SOURCE_FILES[@]} found, not rendered"
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
# Sync agents for <agent-name>
|
|
@@ -86,8 +108,10 @@ sync_<name>_agents() {
|
|
|
86
108
|
local config_file="$2"
|
|
87
109
|
local output_dir="$3"
|
|
88
110
|
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
112
|
+
source_artifacts_var "$repo_root" "$config_file" agents <name> "$IS_OUTPUT_REL/agents/{}.md"
|
|
113
|
+
# TODO: Render each of IS_SOURCE_FILES into $output_dir/agents/ under its IS_SOURCE_NAMES entry
|
|
114
|
+
echo " agents: ${#IS_SOURCE_FILES[@]} found, not rendered"
|
|
91
115
|
}
|
|
92
116
|
|
|
93
117
|
# Main entry point
|
|
@@ -60,15 +60,19 @@ agents_md_append_agents_table() {
|
|
|
60
60
|
local rows=""
|
|
61
61
|
local count=0
|
|
62
62
|
|
|
63
|
-
local -a files=()
|
|
64
|
-
|
|
65
|
-
[ "${#IS_SOURCE_FILES[@]}" -
|
|
63
|
+
local -a files=() names=()
|
|
64
|
+
source_artifacts_var "$repo_root" "$config_file" agents agents "${output#"$repo_root"/}"
|
|
65
|
+
if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
|
|
66
|
+
files=("${IS_SOURCE_FILES[@]}")
|
|
67
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
68
|
+
fi
|
|
66
69
|
|
|
67
70
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
68
|
-
local path tier access desc name
|
|
71
|
+
local path tier access desc name i=0
|
|
72
|
+
# frontmatter_index prints one row per file, in argument order.
|
|
69
73
|
while IFS=$'\x1f' read -r path tier access desc; do
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
name="${names[i]}"
|
|
75
|
+
i=$((i + 1))
|
|
72
76
|
repo_rel_link_var "$repo_root" "$path"
|
|
73
77
|
if [ -n "$IS_REPO_REL" ]; then
|
|
74
78
|
rows+="| [$name]($IS_REPO_REL) | ${tier:--} | ${access:--} | ${desc:--} |"$'\n'
|
|
@@ -101,21 +105,24 @@ agents_md_append_skills_table() {
|
|
|
101
105
|
local rows=""
|
|
102
106
|
local count=0
|
|
103
107
|
|
|
104
|
-
local
|
|
105
|
-
local -a skill_files=()
|
|
106
|
-
|
|
107
|
-
[ "${#IS_SOURCE_FILES[@]}" -
|
|
108
|
+
local name
|
|
109
|
+
local -a skill_files=() names=()
|
|
110
|
+
source_artifacts_var "$repo_root" "$config_file" skills agents "${output#"$repo_root"/}"
|
|
111
|
+
if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
|
|
112
|
+
skill_files=("${IS_SOURCE_FILES[@]}")
|
|
113
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
114
|
+
fi
|
|
108
115
|
|
|
109
116
|
if [ "${#skill_files[@]}" -gt 0 ]; then
|
|
110
|
-
local path desc
|
|
117
|
+
local path desc i=0
|
|
111
118
|
while IFS=$'\x1f' read -r path desc; do
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
name="${names[i]}"
|
|
120
|
+
i=$((i + 1))
|
|
114
121
|
repo_rel_link_var "$repo_root" "$path"
|
|
115
122
|
if [ -n "$IS_REPO_REL" ]; then
|
|
116
|
-
rows+="| [$
|
|
123
|
+
rows+="| [$name]($IS_REPO_REL) | ${desc:--} |"$'\n'
|
|
117
124
|
else
|
|
118
|
-
rows+="| $
|
|
125
|
+
rows+="| $name | ${desc:--} |"$'\n'
|
|
119
126
|
fi
|
|
120
127
|
count=$((count + 1))
|
|
121
128
|
done < <(frontmatter_index "description" "${skill_files[@]}")
|
|
@@ -144,30 +151,25 @@ agents_md_append_rules_list() {
|
|
|
144
151
|
local count=0
|
|
145
152
|
local global_rule_files=()
|
|
146
153
|
|
|
147
|
-
local
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
lines+="- $name ($scope)"$'\n'
|
|
167
|
-
fi
|
|
168
|
-
count=$((count + 1))
|
|
169
|
-
done < <(frontmatter_index "paths#" "${files[@]}")
|
|
170
|
-
fi
|
|
154
|
+
local i path name scope
|
|
155
|
+
source_artifacts_var "$repo_root" "$config_file" rules agents "${output#"$repo_root"/}"
|
|
156
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
157
|
+
path="${IS_SOURCE_FILES[i]}"
|
|
158
|
+
name="${IS_SOURCE_NAMES[i]}"
|
|
159
|
+
scope="global"
|
|
160
|
+
if [ "${IS_SOURCE_SCOPED[i]}" = 1 ]; then
|
|
161
|
+
scope="scoped"
|
|
162
|
+
else
|
|
163
|
+
global_rule_files+=("$path")
|
|
164
|
+
fi
|
|
165
|
+
repo_rel_link_var "$repo_root" "$path"
|
|
166
|
+
if [ -n "$IS_REPO_REL" ]; then
|
|
167
|
+
lines+="- [$name]($IS_REPO_REL) ($scope)"$'\n'
|
|
168
|
+
else
|
|
169
|
+
lines+="- $name ($scope)"$'\n'
|
|
170
|
+
fi
|
|
171
|
+
count=$((count + 1))
|
|
172
|
+
done
|
|
171
173
|
|
|
172
174
|
[ "$count" -eq 0 ] && return 0
|
|
173
175
|
|
|
@@ -196,17 +198,7 @@ agents_md_append_rules_list() {
|
|
|
196
198
|
echo "<!-- Inlined from always-on rules in $content_rel/rules/ -->"
|
|
197
199
|
echo ""
|
|
198
200
|
} >> "$output"
|
|
199
|
-
|
|
200
|
-
# each — the same output the per-file passes produced.
|
|
201
|
-
awk '
|
|
202
|
-
FNR == 1 { if (NR != 1) print ""; in_fm = 0; past_fm = 0 }
|
|
203
|
-
{ sub(/\r$/, "") }
|
|
204
|
-
/^---$/ {
|
|
205
|
-
if (!past_fm) { in_fm = !in_fm; if (!in_fm) { past_fm = 1 }; next }
|
|
206
|
-
}
|
|
207
|
-
past_fm || !in_fm { print }
|
|
208
|
-
END { print "" }
|
|
209
|
-
' "${global_rule_files[@]}" >> "$output"
|
|
201
|
+
emit_rule_bodies "${global_rule_files[@]}" >> "$output"
|
|
210
202
|
echo " rules: ${#global_rule_files[@]} global rule(s) inlined"
|
|
211
203
|
fi
|
|
212
204
|
}
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
# - Always-on (no `paths:`) -> SKIPPED here. The CLI docs tell a project to
|
|
12
12
|
# "Create a GEMINI.md or AGENTS.md file at your workspace root", and the
|
|
13
13
|
# `agents` adapter inlines always-on rule content into AGENTS.md. Emitting
|
|
14
|
-
# copies under .agents/rules/ would double-load the same text.
|
|
14
|
+
# copies under .agents/rules/ would double-load the same text. An always-on
|
|
15
|
+
# rule AGENTS.md does not carry — a local one, or one whose targets leave
|
|
16
|
+
# `agents` out — becomes .agents/rules/<name>.md with `trigger: always_on`,
|
|
17
|
+
# the same key family as `trigger: glob` and just as undocumented
|
|
18
|
+
# (decision 0021).
|
|
15
19
|
# - Path-scoped (with `paths:`) -> .agents/rules/<name>.md carrying
|
|
16
20
|
# `trigger: glob` and `globs:`. NOT vendor-documented: the rules page names
|
|
17
21
|
# four activation modes in prose (manual, always on, model decision, glob
|
|
@@ -95,45 +99,46 @@ sync_antigravity_rules() {
|
|
|
95
99
|
antigravity_rule_limit "$config_file" || return 1
|
|
96
100
|
rule_limit="$IS_AG_RULE_LIMIT"
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
local
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
[
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
[ "$hp" -eq 0 ] && continue
|
|
119
|
-
scoped+=("$path")
|
|
120
|
-
done < <(frontmatter_index "paths#" "${files[@]}")
|
|
121
|
-
fi
|
|
122
|
-
if [ "${#scoped[@]}" -eq 0 ]; then
|
|
102
|
+
# Skip always-on rules AGENTS.md (canonical) carries; one it does not
|
|
103
|
+
# carry takes the always_on trigger instead (decision 0021).
|
|
104
|
+
local i always="" always_count=0
|
|
105
|
+
local -a files=() names=() kinds=()
|
|
106
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
107
|
+
source_artifacts_var "$repo_root" "$config_file" rules antigravity "$IS_OUTPUT_REL/rules/{}.md"
|
|
108
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
109
|
+
if [ "${IS_SOURCE_SCOPED[i]}" = 1 ]; then
|
|
110
|
+
kinds+=(scoped)
|
|
111
|
+
elif [ "${IS_SOURCE_IN_AGENTS[i]}" = 0 ]; then
|
|
112
|
+
kinds+=(always-on)
|
|
113
|
+
always="$always${IS_SOURCE_FILES[i]}"$'\n'
|
|
114
|
+
always_count=$((always_count + 1))
|
|
115
|
+
else
|
|
116
|
+
continue
|
|
117
|
+
fi
|
|
118
|
+
files+=("${IS_SOURCE_FILES[i]}")
|
|
119
|
+
names+=("${IS_SOURCE_NAMES[i]}")
|
|
120
|
+
done
|
|
121
|
+
if [ "${#files[@]}" -eq 0 ]; then
|
|
123
122
|
echo " -> Rules: 0 scoped (AGENTS.md carries always-on)"
|
|
124
123
|
return 0
|
|
125
124
|
fi
|
|
126
125
|
|
|
127
|
-
# Path-scoped rule -> Glob activation (paths -> globs)
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
126
|
+
# Path-scoped rule -> Glob activation (paths -> globs); always-on rule ->
|
|
127
|
+
# `trigger: always_on`, with a frontmatter block of its own when the source
|
|
128
|
+
# has none. The rewrite is anchored inside the frontmatter block: a body
|
|
129
|
+
# line may legitimately start with `paths:` (a rule documenting rule
|
|
130
|
+
# syntax does exactly that), and rewriting it would corrupt the text it
|
|
131
|
+
# teaches.
|
|
131
132
|
#
|
|
132
133
|
# stdout carries nothing but the character-count overflow report, so the
|
|
133
134
|
# limit check costs no extra process.
|
|
134
135
|
local overflow
|
|
135
136
|
is_fin_awk_vars
|
|
136
|
-
overflow="$(awk "${IS_FIN_V[@]}" -v dst="$output_dir/rules" -v limit="$rule_limit" "$IS_AWK_LIB"'
|
|
137
|
+
overflow="$(IS_AG_ALWAYS="$always" IS_NAME_MAP="$IS_SOURCE_NAME_MAP" awk "${IS_FIN_V[@]}" -v dst="$output_dir/rules" -v limit="$rule_limit" "$IS_AWK_LIB"'
|
|
138
|
+
BEGIN {
|
|
139
|
+
n = split(ENVIRON["IS_AG_ALWAYS"], al, "\n")
|
|
140
|
+
for (k = 1; k <= n; k++) if (al[k] != "") ALWAYS[al[k]] = 1
|
|
141
|
+
}
|
|
137
142
|
function emit(line, rendered) {
|
|
138
143
|
rendered = fin_line(line)
|
|
139
144
|
print rendered > out
|
|
@@ -141,11 +146,21 @@ sync_antigravity_rules() {
|
|
|
141
146
|
}
|
|
142
147
|
FNR == 1 {
|
|
143
148
|
if (out != "") close(out)
|
|
144
|
-
out = dst "/"
|
|
149
|
+
out = dst "/" art_file(FILENAME, ".md")
|
|
145
150
|
chars[out] = 0
|
|
146
151
|
fm = 0
|
|
152
|
+
tgt_reset()
|
|
147
153
|
}
|
|
148
154
|
{ sub(/\r$/, "") }
|
|
155
|
+
FNR == 1 && (FILENAME in ALWAYS) {
|
|
156
|
+
emit("---")
|
|
157
|
+
emit("trigger: always_on")
|
|
158
|
+
if ($0 == "---") { fm = 1; next }
|
|
159
|
+
emit("---")
|
|
160
|
+
fm = 2
|
|
161
|
+
emit($0)
|
|
162
|
+
next
|
|
163
|
+
}
|
|
149
164
|
FNR == 1 {
|
|
150
165
|
if ($0 == "---") fm = 1
|
|
151
166
|
emit($0)
|
|
@@ -153,6 +168,7 @@ sync_antigravity_rules() {
|
|
|
153
168
|
next
|
|
154
169
|
}
|
|
155
170
|
fm == 1 && $0 == "---" { fm = 2; emit($0); next }
|
|
171
|
+
fm == 1 && tgt_drop($0) { next }
|
|
156
172
|
fm == 1 { sub(/^paths:/, "globs:") }
|
|
157
173
|
{ emit($0) }
|
|
158
174
|
END {
|
|
@@ -161,7 +177,7 @@ sync_antigravity_rules() {
|
|
|
161
177
|
for (f in chars)
|
|
162
178
|
if (chars[f] > limit) printf "%s\t%d\n", f, chars[f]
|
|
163
179
|
}
|
|
164
|
-
' "${
|
|
180
|
+
' "${files[@]}" | sort)"
|
|
165
181
|
|
|
166
182
|
local over_file over_chars
|
|
167
183
|
while IFS=$'\t' read -r over_file over_chars; do
|
|
@@ -169,11 +185,18 @@ sync_antigravity_rules() {
|
|
|
169
185
|
echo "WARNING: Antigravity limits a rule file to $rule_limit characters: ${over_file#"$repo_root/"} renders $over_chars; split the rule, shorten it, set targets.antigravity.warn_rule_limit to the effective limit, or set it to false to disable this warning." >&2
|
|
170
186
|
done <<< "$overflow"
|
|
171
187
|
|
|
172
|
-
for
|
|
173
|
-
|
|
174
|
-
|
|
188
|
+
for ((i = 0; i < ${#files[@]}; i++)); do
|
|
189
|
+
# awk never reads a record from an empty source.
|
|
190
|
+
if [ "${kinds[i]}" = always-on ] && [ ! -s "${files[i]}" ]; then
|
|
191
|
+
printf -- '---\ntrigger: always_on\n---\n' > "$output_dir/rules/${names[i]}.md"
|
|
192
|
+
fi
|
|
193
|
+
echo " rule: ${names[i]}.md (${kinds[i]})"
|
|
175
194
|
done
|
|
176
|
-
|
|
195
|
+
if [ "$always_count" -gt 0 ]; then
|
|
196
|
+
echo " -> Rules: $(( ${#files[@]} - always_count )) scoped, $always_count always-on"
|
|
197
|
+
else
|
|
198
|
+
echo " -> Rules: ${#files[@]} scoped"
|
|
199
|
+
fi
|
|
177
200
|
}
|
|
178
201
|
|
|
179
202
|
sync_antigravity_agents() {
|
|
@@ -181,19 +204,13 @@ sync_antigravity_agents() {
|
|
|
181
204
|
local config_file="$2"
|
|
182
205
|
local agents_dir="$3"
|
|
183
206
|
|
|
184
|
-
local
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
[ -d "$dir" ] || continue
|
|
192
|
-
for f in "$dir"/*.md; do
|
|
193
|
-
[ -f "$f" ] || continue
|
|
194
|
-
files+=("$f")
|
|
195
|
-
done
|
|
196
|
-
done <<< "$list"
|
|
207
|
+
local -a files=() names=()
|
|
208
|
+
output_rel_var "$repo_root" "$agents_dir"
|
|
209
|
+
source_artifacts_var "$repo_root" "$config_file" agents antigravity "$IS_OUTPUT_REL/{}.md"
|
|
210
|
+
if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
|
|
211
|
+
files=("${IS_SOURCE_FILES[@]}")
|
|
212
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
213
|
+
fi
|
|
197
214
|
|
|
198
215
|
local count=0
|
|
199
216
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
@@ -201,8 +218,7 @@ sync_antigravity_agents() {
|
|
|
201
218
|
|
|
202
219
|
local path tier access description name spec="" report="" LS=$'\x1e'
|
|
203
220
|
while IFS=$'\x1f' read -r path tier access description; do
|
|
204
|
-
|
|
205
|
-
name="${path##*/}"; name="${name%.md}"
|
|
221
|
+
name="${names[count]}"
|
|
206
222
|
resolve_model_var "$tier"
|
|
207
223
|
|
|
208
224
|
local header
|