@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/cli/lib/sync-lock.sh
CHANGED
|
@@ -31,7 +31,11 @@ sync_lock_pgid() {
|
|
|
31
31
|
local stat
|
|
32
32
|
local -a fields
|
|
33
33
|
IS_PGID=""
|
|
34
|
-
|
|
34
|
+
# Cygwin and MSYS publish the group on its own; composing their stat file
|
|
35
|
+
# takes tens of milliseconds.
|
|
36
|
+
if [ -r "/proc/$$/pgid" ] && IFS= read -r IS_PGID < "/proc/$$/pgid"; then
|
|
37
|
+
:
|
|
38
|
+
elif [ -r "/proc/$$/stat" ] && IFS= read -r stat < "/proc/$$/stat"; then
|
|
35
39
|
# pid (comm) state ppid pgrp …: comm may hold spaces, so cut after it.
|
|
36
40
|
read -r -a fields <<< "${stat##*) }"
|
|
37
41
|
IS_PGID="${fields[2]:-}"
|
|
@@ -87,7 +91,8 @@ sync_lock_is_stale() {
|
|
|
87
91
|
# sync_lock_acquire <root> — take <root>/.intelligence/sync.lock or refuse.
|
|
88
92
|
sync_lock_acquire() {
|
|
89
93
|
local root="$1" lock="$1/.intelligence/sync.lock" took=0 age=""
|
|
90
|
-
|
|
94
|
+
# A process start costs tens of milliseconds on Git Bash: skip the usual case.
|
|
95
|
+
[ -d "$root/.intelligence" ] || mkdir -p "$root/.intelligence" || die "cannot create $root/.intelligence"
|
|
91
96
|
if mkdir "$lock" 2>/dev/null; then
|
|
92
97
|
took=1
|
|
93
98
|
elif mkdir "$lock.takeover" 2>/dev/null; then
|
|
@@ -146,3 +151,15 @@ sync_lock_release() {
|
|
|
146
151
|
fi
|
|
147
152
|
SYNC_LOCK_HELD=""
|
|
148
153
|
}
|
|
154
|
+
|
|
155
|
+
# project_lock_scratch <root> — set IS_LOCK_SCRATCH to a path prefix inside the
|
|
156
|
+
# lock this process holds or joined, for files that live only as long as the
|
|
157
|
+
# run: releasing the lock removes them with it, so a hot path needs no process
|
|
158
|
+
# to create or delete a temporary directory. Names carry the pid, because a
|
|
159
|
+
# joined child shares the directory with its holder.
|
|
160
|
+
# shellcheck disable=SC2034 # IS_LOCK_SCRATCH is the return channel
|
|
161
|
+
project_lock_scratch() {
|
|
162
|
+
local lock="$1/.intelligence/sync.lock"
|
|
163
|
+
[ -d "$lock" ] && [ ! -L "$lock" ] || return 1
|
|
164
|
+
IS_LOCK_SCRATCH="$lock/scratch.$$"
|
|
165
|
+
}
|
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
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
15
15
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
18
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
19
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
18
20
|
|
|
19
21
|
# Repo-relative paths to the project's content dir and to the installed sync
|
|
20
22
|
# package, taken from the env contract — never hardcoded. The content dir is
|
|
@@ -58,15 +60,19 @@ agents_md_append_agents_table() {
|
|
|
58
60
|
local rows=""
|
|
59
61
|
local count=0
|
|
60
62
|
|
|
61
|
-
local -a files=()
|
|
62
|
-
|
|
63
|
-
[ "${#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
|
|
64
69
|
|
|
65
70
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
66
|
-
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.
|
|
67
73
|
while IFS=$'\x1f' read -r path tier access desc; do
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
name="${names[i]}"
|
|
75
|
+
i=$((i + 1))
|
|
70
76
|
repo_rel_link_var "$repo_root" "$path"
|
|
71
77
|
if [ -n "$IS_REPO_REL" ]; then
|
|
72
78
|
rows+="| [$name]($IS_REPO_REL) | ${tier:--} | ${access:--} | ${desc:--} |"$'\n'
|
|
@@ -99,21 +105,24 @@ agents_md_append_skills_table() {
|
|
|
99
105
|
local rows=""
|
|
100
106
|
local count=0
|
|
101
107
|
|
|
102
|
-
local
|
|
103
|
-
local -a skill_files=()
|
|
104
|
-
|
|
105
|
-
[ "${#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
|
|
106
115
|
|
|
107
116
|
if [ "${#skill_files[@]}" -gt 0 ]; then
|
|
108
|
-
local path desc
|
|
117
|
+
local path desc i=0
|
|
109
118
|
while IFS=$'\x1f' read -r path desc; do
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
name="${names[i]}"
|
|
120
|
+
i=$((i + 1))
|
|
112
121
|
repo_rel_link_var "$repo_root" "$path"
|
|
113
122
|
if [ -n "$IS_REPO_REL" ]; then
|
|
114
|
-
rows+="| [$
|
|
123
|
+
rows+="| [$name]($IS_REPO_REL) | ${desc:--} |"$'\n'
|
|
115
124
|
else
|
|
116
|
-
rows+="| $
|
|
125
|
+
rows+="| $name | ${desc:--} |"$'\n'
|
|
117
126
|
fi
|
|
118
127
|
count=$((count + 1))
|
|
119
128
|
done < <(frontmatter_index "description" "${skill_files[@]}")
|
|
@@ -142,30 +151,25 @@ agents_md_append_rules_list() {
|
|
|
142
151
|
local count=0
|
|
143
152
|
local global_rule_files=()
|
|
144
153
|
|
|
145
|
-
local
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
lines+="- $name ($scope)"$'\n'
|
|
165
|
-
fi
|
|
166
|
-
count=$((count + 1))
|
|
167
|
-
done < <(frontmatter_index "paths#" "${files[@]}")
|
|
168
|
-
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
|
|
169
173
|
|
|
170
174
|
[ "$count" -eq 0 ] && return 0
|
|
171
175
|
|
|
@@ -194,17 +198,7 @@ agents_md_append_rules_list() {
|
|
|
194
198
|
echo "<!-- Inlined from always-on rules in $content_rel/rules/ -->"
|
|
195
199
|
echo ""
|
|
196
200
|
} >> "$output"
|
|
197
|
-
|
|
198
|
-
# each — the same output the per-file passes produced.
|
|
199
|
-
awk '
|
|
200
|
-
FNR == 1 { if (NR != 1) print ""; in_fm = 0; past_fm = 0 }
|
|
201
|
-
{ sub(/\r$/, "") }
|
|
202
|
-
/^---$/ {
|
|
203
|
-
if (!past_fm) { in_fm = !in_fm; if (!in_fm) { past_fm = 1 }; next }
|
|
204
|
-
}
|
|
205
|
-
past_fm || !in_fm { print }
|
|
206
|
-
END { print "" }
|
|
207
|
-
' "${global_rule_files[@]}" >> "$output"
|
|
201
|
+
emit_rule_bodies "${global_rule_files[@]}" >> "$output"
|
|
208
202
|
echo " rules: ${#global_rule_files[@]} global rule(s) inlined"
|
|
209
203
|
fi
|
|
210
204
|
}
|
|
@@ -212,7 +206,8 @@ agents_md_append_rules_list() {
|
|
|
212
206
|
# Main entry point for AGENTS.md adapter
|
|
213
207
|
adapter_contract_agents() {
|
|
214
208
|
local output
|
|
215
|
-
|
|
209
|
+
agents_output_path_var "$1"
|
|
210
|
+
output="$IS_AGENTS_OUTPUT_PATH"
|
|
216
211
|
adapter_contract_version 1
|
|
217
212
|
adapter_contract_owned "$output"
|
|
218
213
|
adapter_contract_legacy "AGENTS.md"
|
|
@@ -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
|
|
@@ -42,7 +46,9 @@
|
|
|
42
46
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
43
47
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
50
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
51
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
46
52
|
|
|
47
53
|
adapter_contract_antigravity() {
|
|
48
54
|
local output="${1%/}"
|
|
@@ -93,45 +99,46 @@ sync_antigravity_rules() {
|
|
|
93
99
|
antigravity_rule_limit "$config_file" || return 1
|
|
94
100
|
rule_limit="$IS_AG_RULE_LIMIT"
|
|
95
101
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
local
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
[
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
[ "$hp" -eq 0 ] && continue
|
|
117
|
-
scoped+=("$path")
|
|
118
|
-
done < <(frontmatter_index "paths#" "${files[@]}")
|
|
119
|
-
fi
|
|
120
|
-
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
|
|
121
122
|
echo " -> Rules: 0 scoped (AGENTS.md carries always-on)"
|
|
122
123
|
return 0
|
|
123
124
|
fi
|
|
124
125
|
|
|
125
|
-
# Path-scoped rule -> Glob activation (paths -> globs)
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
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.
|
|
129
132
|
#
|
|
130
133
|
# stdout carries nothing but the character-count overflow report, so the
|
|
131
134
|
# limit check costs no extra process.
|
|
132
135
|
local overflow
|
|
133
136
|
is_fin_awk_vars
|
|
134
|
-
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
|
+
}
|
|
135
142
|
function emit(line, rendered) {
|
|
136
143
|
rendered = fin_line(line)
|
|
137
144
|
print rendered > out
|
|
@@ -139,11 +146,21 @@ sync_antigravity_rules() {
|
|
|
139
146
|
}
|
|
140
147
|
FNR == 1 {
|
|
141
148
|
if (out != "") close(out)
|
|
142
|
-
out = dst "/"
|
|
149
|
+
out = dst "/" art_file(FILENAME, ".md")
|
|
143
150
|
chars[out] = 0
|
|
144
151
|
fm = 0
|
|
152
|
+
tgt_reset()
|
|
145
153
|
}
|
|
146
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
|
+
}
|
|
147
164
|
FNR == 1 {
|
|
148
165
|
if ($0 == "---") fm = 1
|
|
149
166
|
emit($0)
|
|
@@ -151,6 +168,7 @@ sync_antigravity_rules() {
|
|
|
151
168
|
next
|
|
152
169
|
}
|
|
153
170
|
fm == 1 && $0 == "---" { fm = 2; emit($0); next }
|
|
171
|
+
fm == 1 && tgt_drop($0) { next }
|
|
154
172
|
fm == 1 { sub(/^paths:/, "globs:") }
|
|
155
173
|
{ emit($0) }
|
|
156
174
|
END {
|
|
@@ -159,7 +177,7 @@ sync_antigravity_rules() {
|
|
|
159
177
|
for (f in chars)
|
|
160
178
|
if (chars[f] > limit) printf "%s\t%d\n", f, chars[f]
|
|
161
179
|
}
|
|
162
|
-
' "${
|
|
180
|
+
' "${files[@]}" | sort)"
|
|
163
181
|
|
|
164
182
|
local over_file over_chars
|
|
165
183
|
while IFS=$'\t' read -r over_file over_chars; do
|
|
@@ -167,11 +185,18 @@ sync_antigravity_rules() {
|
|
|
167
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
|
|
168
186
|
done <<< "$overflow"
|
|
169
187
|
|
|
170
|
-
for
|
|
171
|
-
|
|
172
|
-
|
|
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]})"
|
|
173
194
|
done
|
|
174
|
-
|
|
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
|
|
175
200
|
}
|
|
176
201
|
|
|
177
202
|
sync_antigravity_agents() {
|
|
@@ -179,19 +204,13 @@ sync_antigravity_agents() {
|
|
|
179
204
|
local config_file="$2"
|
|
180
205
|
local agents_dir="$3"
|
|
181
206
|
|
|
182
|
-
local
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
[ -d "$dir" ] || continue
|
|
190
|
-
for f in "$dir"/*.md; do
|
|
191
|
-
[ -f "$f" ] || continue
|
|
192
|
-
files+=("$f")
|
|
193
|
-
done
|
|
194
|
-
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
|
|
195
214
|
|
|
196
215
|
local count=0
|
|
197
216
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
@@ -199,8 +218,7 @@ sync_antigravity_agents() {
|
|
|
199
218
|
|
|
200
219
|
local path tier access description name spec="" report="" LS=$'\x1e'
|
|
201
220
|
while IFS=$'\x1f' read -r path tier access description; do
|
|
202
|
-
|
|
203
|
-
name="${path##*/}"; name="${name%.md}"
|
|
221
|
+
name="${names[count]}"
|
|
204
222
|
resolve_model_var "$tier"
|
|
205
223
|
|
|
206
224
|
local header
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
11
11
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
14
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
15
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
14
16
|
|
|
15
17
|
adapter_contract_claude() {
|
|
16
18
|
local output="${1%/}"
|
|
@@ -33,23 +35,15 @@ sync_claude_rules() {
|
|
|
33
35
|
local config_file="$2"
|
|
34
36
|
local output_dir="$3"
|
|
35
37
|
|
|
36
|
-
local
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
[ -f "$f" ] || continue
|
|
46
|
-
files+=("$f")
|
|
47
|
-
done
|
|
48
|
-
done <<< "$list"
|
|
49
|
-
[ "${#files[@]}" -gt 0 ] || return 0
|
|
50
|
-
finalize_copy_files "$output_dir/rules" "${files[@]}"
|
|
51
|
-
for f in "${files[@]}"; do
|
|
52
|
-
echo " rule: ${f##*/}"
|
|
38
|
+
local name
|
|
39
|
+
# Every source reaches this adapter through the source catalog (decision
|
|
40
|
+
# 0021): rendered names, overrides and local artifacts are settled there.
|
|
41
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
42
|
+
source_artifacts_var "$repo_root" "$config_file" rules claude "$IS_OUTPUT_REL/rules/{}.md"
|
|
43
|
+
[ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
|
|
44
|
+
finalize_copy_named "$output_dir/rules" "$IS_SOURCE_NAME_MAP" "${IS_SOURCE_FILES[@]}"
|
|
45
|
+
for name in "${IS_SOURCE_NAMES[@]}"; do
|
|
46
|
+
echo " rule: $name.md"
|
|
53
47
|
done
|
|
54
48
|
}
|
|
55
49
|
|
|
@@ -59,25 +53,19 @@ sync_claude_skills() {
|
|
|
59
53
|
local config_file="$2"
|
|
60
54
|
local output_dir="$3"
|
|
61
55
|
|
|
62
|
-
local
|
|
56
|
+
local i name
|
|
63
57
|
local -a skill_dirs=()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
done <<< "$list"
|
|
76
|
-
[ "${#skill_dirs[@]}" -gt 0 ] || return 0
|
|
77
|
-
copy_skill_bundle_dirs_for claude "$output_dir/skills" "${skill_dirs[@]}"
|
|
78
|
-
for d in "${skill_dirs[@]}"; do
|
|
79
|
-
skill_name="${d%/}"
|
|
80
|
-
echo " skill: ${skill_name##*/}"
|
|
58
|
+
# The catalog's skills are the ones the inventory renders, symlinked ones
|
|
59
|
+
# included (decision 0017).
|
|
60
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
61
|
+
source_artifacts_var "$repo_root" "$config_file" skills claude "$IS_OUTPUT_REL/skills/{}"
|
|
62
|
+
[ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
|
|
63
|
+
for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
|
|
64
|
+
skill_dirs+=("${IS_SOURCE_FILES[i]%/SKILL.md}/")
|
|
65
|
+
done
|
|
66
|
+
copy_skill_bundle_dirs_named claude "$output_dir/skills" "$IS_SOURCE_NAME_MAP" "${skill_dirs[@]}"
|
|
67
|
+
for name in "${IS_SOURCE_NAMES[@]}"; do
|
|
68
|
+
echo " skill: $name"
|
|
81
69
|
done
|
|
82
70
|
}
|
|
83
71
|
|
|
@@ -88,32 +76,27 @@ sync_claude_agents() {
|
|
|
88
76
|
local config_file="$2"
|
|
89
77
|
local output_dir="$3"
|
|
90
78
|
|
|
91
|
-
local
|
|
92
|
-
local -a files=()
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
for f in "$dir"/*.md; do
|
|
100
|
-
[ -f "$f" ] || continue
|
|
101
|
-
files+=("$f")
|
|
102
|
-
done
|
|
103
|
-
done <<< "$list"
|
|
104
|
-
[ "${#files[@]}" -gt 0 ] || return 0
|
|
79
|
+
local i
|
|
80
|
+
local -a files=() names=()
|
|
81
|
+
output_rel_var "$repo_root" "$output_dir"
|
|
82
|
+
source_artifacts_var "$repo_root" "$config_file" agents claude "$IS_OUTPUT_REL/agents/{}.md"
|
|
83
|
+
[ "${#IS_SOURCE_FILES[@]}" -gt 0 ] || return 0
|
|
84
|
+
files=("${IS_SOURCE_FILES[@]}")
|
|
85
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
86
|
+
local name_map="$IS_SOURCE_NAME_MAP"
|
|
105
87
|
|
|
106
88
|
load_model_tiers "$config_file" "claude"
|
|
107
89
|
|
|
108
90
|
local path tier access effort spec="" report=""
|
|
91
|
+
i=0
|
|
109
92
|
while IFS=$'\x1f' read -r path tier access effort; do
|
|
110
|
-
[ -n "$path" ] || continue
|
|
111
93
|
resolve_model_var "$tier"
|
|
112
94
|
map_effort_var claude "$effort"
|
|
113
95
|
map_access_to_claude_tools_var "$access"
|
|
114
96
|
map_access_to_claude_disallowed_var "$access"
|
|
115
97
|
spec+="$path"$'\x1f'"$IS_MODEL"$'\x1f'"$IS_CLAUDE_TOOLS"$'\x1f'"$IS_CLAUDE_DISALLOWED"$'\x1f'"$IS_EFFORT"$'\n'
|
|
116
|
-
report+=" agent: ${
|
|
98
|
+
report+=" agent: ${names[i]}.md (tier=$tier -> model=$IS_MODEL, access=$access)"$'\n'
|
|
99
|
+
i=$((i + 1))
|
|
117
100
|
done < <(frontmatter_index "tier,access,effort" "${files[@]}")
|
|
118
101
|
|
|
119
102
|
# The transform used to run per file with the values injected via -v;
|
|
@@ -122,7 +105,7 @@ sync_claude_agents() {
|
|
|
122
105
|
# becomes Claude's level in place, or goes when there is none; a later
|
|
123
106
|
# one goes too, since frontmatter_index read only the first.
|
|
124
107
|
is_fin_awk_vars
|
|
125
|
-
IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
|
|
108
|
+
IS_NAME_MAP="$name_map" IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
|
|
126
109
|
BEGIN {
|
|
127
110
|
US = sprintf("%c", 31)
|
|
128
111
|
n = split(ENVIRON["IS_MAP_SPEC"], recs, "\n")
|
|
@@ -134,16 +117,18 @@ sync_claude_agents() {
|
|
|
134
117
|
}
|
|
135
118
|
FNR == 1 {
|
|
136
119
|
if (out != "") close(out)
|
|
137
|
-
out = dst "/"
|
|
120
|
+
out = dst "/" art_file(FILENAME, ".md")
|
|
138
121
|
count = 0
|
|
139
122
|
fm = 0
|
|
140
123
|
effort_seen = 0
|
|
124
|
+
tgt_reset()
|
|
141
125
|
}
|
|
142
126
|
/^tier:/ { next }
|
|
143
127
|
/^access:/ { next }
|
|
144
128
|
{ sub(/\r$/, "") }
|
|
145
129
|
FNR == 1 && $0 == "---" { fm = 1 }
|
|
146
130
|
/^---$/ { count++ }
|
|
131
|
+
fm && count == 1 && !/^---$/ && tgt_drop($0) { next }
|
|
147
132
|
fm && count == 1 && substr($0, 1, 7) == "effort:" {
|
|
148
133
|
if (!effort_seen++ && EFFORT[FILENAME] != "") print fin_line("effort: " EFFORT[FILENAME]) > out
|
|
149
134
|
next
|
|
@@ -157,8 +142,8 @@ sync_claude_agents() {
|
|
|
157
142
|
' "${files[@]}"
|
|
158
143
|
# awk never reads a record from an empty source; the old per-file shell
|
|
159
144
|
# redirect still created an empty output for it.
|
|
160
|
-
for
|
|
161
|
-
[ -s "$
|
|
145
|
+
for ((i = 0; i < ${#files[@]}; i++)); do
|
|
146
|
+
[ -s "${files[i]}" ] || : > "$output_dir/agents/${names[i]}.md"
|
|
162
147
|
done
|
|
163
148
|
printf '%s' "$report"
|
|
164
149
|
}
|
|
@@ -176,7 +161,7 @@ sync_to_claude() {
|
|
|
176
161
|
# commands/, statusline.sh, etc.).
|
|
177
162
|
rm -rf "$output_dir/rules" "$output_dir/agents"
|
|
178
163
|
if [ -d "$output_dir/skills" ]; then
|
|
179
|
-
find "$output_dir/skills" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} +
|
|
164
|
+
find "$output_dir/skills" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -exec rm -rf {} +
|
|
180
165
|
fi
|
|
181
166
|
mkdir -p "$output_dir/rules" "$output_dir/skills" "$output_dir/agents"
|
|
182
167
|
|