@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
|
@@ -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
|
|
@@ -41,45 +44,55 @@ sync_cursor_rules() {
|
|
|
41
44
|
local config_file="$2"
|
|
42
45
|
local output_dir="$3"
|
|
43
46
|
|
|
44
|
-
local
|
|
45
|
-
local -a files=()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
57
66
|
[ "${#files[@]}" -gt 0 ] || return 0
|
|
58
67
|
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
scoped+=("$path")
|
|
66
|
-
done < <(frontmatter_index "paths#" "${files[@]}")
|
|
67
|
-
[ "${#scoped[@]}" -gt 0 ] || return 0
|
|
68
|
-
|
|
69
|
-
# Path-scoped rule -> Auto Attached (paths -> globs). The rewrite is
|
|
70
|
-
# anchored inside the frontmatter block: a body line may legitimately start
|
|
71
|
-
# with `paths:` (a rule documenting rule syntax does exactly that), and
|
|
72
|
-
# 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.
|
|
73
74
|
is_fin_awk_vars
|
|
74
|
-
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
|
+
}
|
|
75
80
|
FNR == 1 {
|
|
76
81
|
if (out != "") close(out)
|
|
77
|
-
|
|
78
|
-
sub(/\.md$/, ".mdc", nm)
|
|
79
|
-
out = dst "/" nm
|
|
82
|
+
out = dst "/" art_name(FILENAME) ".mdc"
|
|
80
83
|
fm = 0
|
|
84
|
+
tgt_reset()
|
|
81
85
|
}
|
|
82
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
|
+
}
|
|
83
96
|
FNR == 1 {
|
|
84
97
|
if ($0 == "---") fm = 1
|
|
85
98
|
print fin_line($0) > out
|
|
@@ -87,12 +100,16 @@ sync_cursor_rules() {
|
|
|
87
100
|
next
|
|
88
101
|
}
|
|
89
102
|
fm == 1 && $0 == "---" { fm = 2; print fin_line($0) > out; next }
|
|
103
|
+
fm == 1 && tgt_drop($0) { next }
|
|
90
104
|
fm == 1 { sub(/^paths:/, "globs:") }
|
|
91
105
|
{ print fin_line($0) > out }
|
|
92
|
-
' "${
|
|
93
|
-
for
|
|
94
|
-
|
|
95
|
-
|
|
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]})"
|
|
96
113
|
done
|
|
97
114
|
}
|
|
98
115
|
|
|
@@ -102,29 +119,19 @@ sync_cursor_skills() {
|
|
|
102
119
|
local config_file="$2"
|
|
103
120
|
local output_dir="$3"
|
|
104
121
|
|
|
105
|
-
local
|
|
122
|
+
local i name
|
|
106
123
|
local -a skill_dirs=()
|
|
107
|
-
# The
|
|
108
|
-
# (decision 0017)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
[ -f "$d/SKILL.md" ] || continue
|
|
119
|
-
skill_source_rendered "$d" || continue
|
|
120
|
-
skill_dirs+=("$d")
|
|
121
|
-
done
|
|
122
|
-
done <<< "$list"
|
|
123
|
-
[ "${#skill_dirs[@]}" -gt 0 ] || return 0
|
|
124
|
-
copy_skill_bundle_dirs_for cursor "$output_dir/skills" "${skill_dirs[@]}"
|
|
125
|
-
for d in "${skill_dirs[@]}"; do
|
|
126
|
-
skill_name="${d%/}"
|
|
127
|
-
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"
|
|
128
135
|
done
|
|
129
136
|
}
|
|
130
137
|
|
|
@@ -134,37 +141,32 @@ sync_cursor_agents() {
|
|
|
134
141
|
local config_file="$2"
|
|
135
142
|
local output_dir="$3"
|
|
136
143
|
|
|
137
|
-
local
|
|
138
|
-
local -a files=()
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
for f in "$dir"/*.md; do
|
|
146
|
-
[ -f "$f" ] || continue
|
|
147
|
-
files+=("$f")
|
|
148
|
-
done
|
|
149
|
-
done <<< "$list"
|
|
150
|
-
[ "${#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"
|
|
151
152
|
|
|
152
153
|
load_model_tiers "$config_file" "cursor"
|
|
153
154
|
|
|
154
155
|
local path tier access spec="" report="" cursor_readonly
|
|
156
|
+
i=0
|
|
155
157
|
while IFS=$'\x1f' read -r path tier access; do
|
|
156
|
-
[ -n "$path" ] || continue
|
|
157
158
|
resolve_model_var "$tier"
|
|
158
159
|
cursor_readonly=""
|
|
159
160
|
[ "$access" = "readonly" ] && cursor_readonly="readonly: true"
|
|
160
161
|
spec+="$path"$'\x1f'"$IS_MODEL"$'\x1f'"$cursor_readonly"$'\n'
|
|
161
|
-
report+=" agent: ${
|
|
162
|
+
report+=" agent: ${names[i]}.md (tier=$tier -> cursor)"$'\n'
|
|
163
|
+
i=$((i + 1))
|
|
162
164
|
done < <(frontmatter_index "tier,access" "${files[@]}")
|
|
163
165
|
|
|
164
166
|
# Always emit model: line (even when value matches Cursor's default)
|
|
165
167
|
# so config is explicit and grep-able.
|
|
166
168
|
is_fin_awk_vars
|
|
167
|
-
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"'
|
|
168
170
|
BEGIN {
|
|
169
171
|
US = sprintf("%c", 31)
|
|
170
172
|
n = split(ENVIRON["IS_MAP_SPEC"], recs, "\n")
|
|
@@ -176,9 +178,10 @@ sync_cursor_agents() {
|
|
|
176
178
|
}
|
|
177
179
|
FNR == 1 {
|
|
178
180
|
if (out != "") close(out)
|
|
179
|
-
out = dst "/"
|
|
181
|
+
out = dst "/" art_file(FILENAME, ".md")
|
|
180
182
|
count = 0
|
|
181
183
|
fm = 0
|
|
184
|
+
tgt_reset()
|
|
182
185
|
}
|
|
183
186
|
/^tier:/ { next }
|
|
184
187
|
/^access:/ { next }
|
|
@@ -187,6 +190,7 @@ sync_cursor_agents() {
|
|
|
187
190
|
{ sub(/\r$/, "") }
|
|
188
191
|
FNR == 1 && $0 == "---" { fm = 1 }
|
|
189
192
|
/^---$/ { count++ }
|
|
193
|
+
fm && count == 1 && !/^---$/ && tgt_drop($0) { next }
|
|
190
194
|
fm && count == 1 && substr($0, 1, 7) == "effort:" { next }
|
|
191
195
|
count == 2 && /^---$/ {
|
|
192
196
|
print fin_line("model: " MODEL[FILENAME]) > out
|
|
@@ -194,8 +198,8 @@ sync_cursor_agents() {
|
|
|
194
198
|
}
|
|
195
199
|
{ print fin_line($0) > out }
|
|
196
200
|
' "${files[@]}"
|
|
197
|
-
for
|
|
198
|
-
[ -s "$
|
|
201
|
+
for ((i = 0; i < ${#files[@]}; i++)); do
|
|
202
|
+
[ -s "${files[i]}" ] || : > "$output_dir/agents/${names[i]}.md"
|
|
199
203
|
done
|
|
200
204
|
printf '%s' "$report"
|
|
201
205
|
}
|
|
@@ -88,32 +88,24 @@ sync_opencode_commands() {
|
|
|
88
88
|
[ "${#generated[@]}" -gt 0 ] && rm -f "${generated[@]}"
|
|
89
89
|
fi
|
|
90
90
|
|
|
91
|
-
local
|
|
92
|
-
local -a skill_mds=()
|
|
93
|
-
# The
|
|
94
|
-
# (decision 0017)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
for d in "$dir"/*/; do
|
|
103
|
-
[ -d "$d" ] || continue
|
|
104
|
-
[ -f "$d/SKILL.md" ] || continue
|
|
105
|
-
skill_source_rendered "$d" || continue
|
|
106
|
-
skill_mds+=("${d%/}/SKILL.md")
|
|
107
|
-
done
|
|
108
|
-
done <<< "$list"
|
|
91
|
+
local skill_name
|
|
92
|
+
local -a skill_mds=() names=()
|
|
93
|
+
# The catalog's skills are the ones the inventory renders, symlinked ones
|
|
94
|
+
# included (decision 0017). The commands directory is tracked, so a local
|
|
95
|
+
# skill gets no command.
|
|
96
|
+
output_rel_var "$repo_root" "$commands_dir"
|
|
97
|
+
source_artifacts_var "$repo_root" "$config_file" skills opencode "$IS_OUTPUT_REL/{}.md"
|
|
98
|
+
if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
|
|
99
|
+
skill_mds=("${IS_SOURCE_FILES[@]}")
|
|
100
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
101
|
+
fi
|
|
109
102
|
|
|
110
103
|
local count=0
|
|
111
104
|
if [ "${#skill_mds[@]}" -gt 0 ]; then
|
|
112
105
|
local path description argument_hint out_file
|
|
113
106
|
local -a out_files=()
|
|
114
107
|
while IFS=$'\x1f' read -r path description argument_hint; do
|
|
115
|
-
|
|
116
|
-
skill_name="${path%/SKILL.md}"; skill_name="${skill_name##*/}"
|
|
108
|
+
skill_name="${names[count]}"
|
|
117
109
|
out_file="$commands_dir/$skill_name.md"
|
|
118
110
|
yaml_dq_escape_var "$description"
|
|
119
111
|
{
|
|
@@ -151,19 +143,13 @@ sync_opencode_agents() {
|
|
|
151
143
|
local agents_dir="$output_dir/agents"
|
|
152
144
|
mkdir -p "$agents_dir"
|
|
153
145
|
|
|
154
|
-
local
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
[ -d "$dir" ] || continue
|
|
162
|
-
for f in "$dir"/*.md; do
|
|
163
|
-
[ -f "$f" ] || continue
|
|
164
|
-
files+=("$f")
|
|
165
|
-
done
|
|
166
|
-
done <<< "$list"
|
|
146
|
+
local -a files=() names=()
|
|
147
|
+
output_rel_var "$repo_root" "$agents_dir"
|
|
148
|
+
source_artifacts_var "$repo_root" "$config_file" agents opencode "$IS_OUTPUT_REL/{}.md"
|
|
149
|
+
if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
|
|
150
|
+
files=("${IS_SOURCE_FILES[@]}")
|
|
151
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
152
|
+
fi
|
|
167
153
|
|
|
168
154
|
local count=0
|
|
169
155
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
@@ -171,8 +157,7 @@ sync_opencode_agents() {
|
|
|
171
157
|
|
|
172
158
|
local path tier access description name edit_perm bash_perm spec="" report="" LS=$'\x1e'
|
|
173
159
|
while IFS=$'\x1f' read -r path tier access description; do
|
|
174
|
-
|
|
175
|
-
name="${path##*/}"; name="${name%.md}"
|
|
160
|
+
name="${names[count]}"
|
|
176
161
|
resolve_model_var "$tier"
|
|
177
162
|
|
|
178
163
|
# Map source `access:` to opencode permission keys.
|
package/engine/adapters/pi.sh
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Rules:
|
|
6
6
|
# - Always-on (no `paths:`) -> SKIPPED here. Pi reads AGENTS.md natively.
|
|
7
|
+
# An always-on rule AGENTS.md does not carry — a local one, or one whose
|
|
8
|
+
# targets leave `agents` out — goes into the generated extension, which
|
|
9
|
+
# adds its body to the system prompt (decision 0021).
|
|
7
10
|
# - Path-scoped (with `paths:`) -> copied to .pi/intelligence-sync/rules/
|
|
8
11
|
# and surfaced via a generated Pi extension that lists them in the system
|
|
9
12
|
# prompt so the model can `read` the relevant rule on demand.
|
|
@@ -85,6 +88,39 @@ pi_rule_paths_rows() {
|
|
|
85
88
|
' "$@"
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
# pi_rule_body_rows <file>... — one line per file in argument order: its body,
|
|
92
|
+
# without the frontmatter and leading or trailing blank lines, escaped as the
|
|
93
|
+
# contents of a TypeScript double-quoted string (`\`, `"`, tabs and line breaks
|
|
94
|
+
# escaped).
|
|
95
|
+
pi_rule_body_rows() {
|
|
96
|
+
[ "$#" -gt 0 ] || return 0
|
|
97
|
+
awk "$IS_AWK_LIB"'
|
|
98
|
+
function store( j, first, last, s) {
|
|
99
|
+
if (cur == "") return
|
|
100
|
+
first = 0; last = 0
|
|
101
|
+
for (j = 1; j <= n; j++) if (B[j] != "") { if (!first) first = j; last = j }
|
|
102
|
+
s = ""
|
|
103
|
+
for (j = first; first && j <= last; j++) s = s (j > first ? "\\n" : "") B[j]
|
|
104
|
+
R[cur] = s
|
|
105
|
+
}
|
|
106
|
+
FNR == 1 { store(); cur = FILENAME; n = 0; in_fm = 0; past_fm = 0 }
|
|
107
|
+
{ sub(/\r$/, "") }
|
|
108
|
+
FNR == 1 && $0 != "---" { past_fm = 1 }
|
|
109
|
+
/^---$/ && !past_fm { in_fm = !in_fm; if (!in_fm) past_fm = 1; next }
|
|
110
|
+
!past_fm { next }
|
|
111
|
+
{
|
|
112
|
+
line = is_repl($0, "\\", "\\\\")
|
|
113
|
+
line = is_repl(line, "\"", "\\\"")
|
|
114
|
+
line = is_repl(line, "\t", "\\t")
|
|
115
|
+
B[++n] = line
|
|
116
|
+
}
|
|
117
|
+
END {
|
|
118
|
+
store()
|
|
119
|
+
for (i = 1; i < ARGC; i++) print (ARGV[i] in R) ? R[ARGV[i]] : ""
|
|
120
|
+
}
|
|
121
|
+
' "$@"
|
|
122
|
+
}
|
|
123
|
+
|
|
88
124
|
sync_pi_rules() {
|
|
89
125
|
local repo_root="$1"
|
|
90
126
|
local config_file="$2"
|
|
@@ -98,38 +134,44 @@ sync_pi_rules() {
|
|
|
98
134
|
|
|
99
135
|
mkdir -p "$rules_root" "$(dirname "$extension_file")"
|
|
100
136
|
|
|
101
|
-
local
|
|
102
|
-
local
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
while
|
|
106
|
-
[
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
local -a scoped=() scoped_names=() always=() always_names=()
|
|
138
|
+
local k=0 always_rules=""
|
|
139
|
+
output_rel_var "$repo_root" "$rules_root"
|
|
140
|
+
source_artifacts_var "$repo_root" "$config_file" rules pi "$IS_OUTPUT_REL/{}.md"
|
|
141
|
+
while [ "$k" -lt "${#IS_SOURCE_FILES[@]}" ]; do
|
|
142
|
+
if [ "${IS_SOURCE_SCOPED[k]}" = 1 ]; then
|
|
143
|
+
scoped+=("${IS_SOURCE_FILES[k]}")
|
|
144
|
+
scoped_names+=("${IS_SOURCE_NAMES[k]}")
|
|
145
|
+
elif [ "${IS_SOURCE_IN_AGENTS[k]}" = 0 ]; then
|
|
146
|
+
# An always-on rule AGENTS.md does not carry: the extension adds
|
|
147
|
+
# its body to the system prompt (decision 0021).
|
|
148
|
+
always+=("${IS_SOURCE_FILES[k]}")
|
|
149
|
+
always_names+=("${IS_SOURCE_NAMES[k]}")
|
|
150
|
+
fi
|
|
151
|
+
k=$((k + 1))
|
|
152
|
+
done
|
|
153
|
+
|
|
154
|
+
if [ "${#always[@]}" -gt 0 ]; then
|
|
155
|
+
local body
|
|
156
|
+
k=0
|
|
157
|
+
while IFS= read -r body; do
|
|
158
|
+
pi_ts_escape_var "${always_names[k]}"
|
|
159
|
+
always_rules+=" { name: \"$IS_PI_TS_ESCAPED\", body: \"$body\" },"$'\n'
|
|
160
|
+
echo " rule: ${always_names[k]} (always-on, in the extension)"
|
|
161
|
+
k=$((k + 1))
|
|
162
|
+
done < <(pi_rule_body_rows "${always[@]}")
|
|
123
163
|
fi
|
|
124
164
|
|
|
125
165
|
if [ "${#scoped[@]}" -gt 0 ]; then
|
|
126
|
-
|
|
166
|
+
finalize_copy_named "$rules_root" "$IS_SOURCE_NAME_MAP" "${scoped[@]}"
|
|
127
167
|
|
|
128
168
|
local -a row_arr
|
|
129
169
|
local base rel_rule_path patterns_js display sep display_sep pat i
|
|
170
|
+
k=0
|
|
130
171
|
while IFS=$'\x1f' read -r -a row_arr; do
|
|
131
172
|
[ "${#row_arr[@]}" -gt 0 ] || continue
|
|
132
|
-
base="${
|
|
173
|
+
base="${scoped_names[k]}.md"
|
|
174
|
+
k=$((k + 1))
|
|
133
175
|
rel_rule_path="$output_rel/intelligence-sync/rules/$base"
|
|
134
176
|
patterns_js=""
|
|
135
177
|
display=""
|
|
@@ -153,7 +195,64 @@ sync_pi_rules() {
|
|
|
153
195
|
done < <(pi_rule_paths_rows "${scoped[@]}")
|
|
154
196
|
fi
|
|
155
197
|
|
|
156
|
-
if [ "$
|
|
198
|
+
if [ "${#always[@]}" -gt 0 ]; then
|
|
199
|
+
{
|
|
200
|
+
cat <<'EOF'
|
|
201
|
+
/**
|
|
202
|
+
* Generated by intelligence-sync. Do not edit manually.
|
|
203
|
+
* Adds the always-on project rules AGENTS.md does not carry to the system
|
|
204
|
+
* prompt, and lists path-scoped project rules generated from intelligence/rules/.
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
208
|
+
|
|
209
|
+
const projectRules: Array<{ name: string; body: string }> = [
|
|
210
|
+
EOF
|
|
211
|
+
printf '%s' "$always_rules"
|
|
212
|
+
cat <<'EOF'
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const scopedRules: Array<{ path: string; patterns: string[] }> = [
|
|
216
|
+
EOF
|
|
217
|
+
printf '%s' "$manifest"
|
|
218
|
+
cat <<'EOF'
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
export default function intelligenceSyncRulesExtension(pi: ExtensionAPI) {
|
|
222
|
+
pi.on("before_agent_start", async (event) => {
|
|
223
|
+
let systemPrompt = event.systemPrompt;
|
|
224
|
+
|
|
225
|
+
if (projectRules.length > 0) {
|
|
226
|
+
systemPrompt += `
|
|
227
|
+
|
|
228
|
+
## Project Rules
|
|
229
|
+
|
|
230
|
+
${projectRules.map((rule) => rule.body).join("\n\n")}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (scopedRules.length > 0) {
|
|
234
|
+
const rulesList = scopedRules
|
|
235
|
+
.map((rule) => `- ${rule.path} — paths: ${rule.patterns.join(", ")}`)
|
|
236
|
+
.join("\n");
|
|
237
|
+
|
|
238
|
+
systemPrompt += `
|
|
239
|
+
|
|
240
|
+
## Scoped Project Rules
|
|
241
|
+
|
|
242
|
+
AGENTS.md already carries the always-on project rules. Additional path-scoped rules are available on demand:
|
|
243
|
+
|
|
244
|
+
${rulesList}
|
|
245
|
+
|
|
246
|
+
When the task touches files that match one of these path patterns, use the read tool to load the relevant rule file before making changes.`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { systemPrompt };
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
EOF
|
|
253
|
+
} > "$extension_file"
|
|
254
|
+
finalize_output_file "$extension_file"
|
|
255
|
+
elif [ "$count" -gt 0 ]; then
|
|
157
256
|
{
|
|
158
257
|
cat <<'EOF'
|
|
159
258
|
/**
|
|
@@ -199,7 +298,11 @@ EOF
|
|
|
199
298
|
finalize_output_file "$extension_file"
|
|
200
299
|
fi
|
|
201
300
|
|
|
202
|
-
|
|
301
|
+
if [ "${#always[@]}" -gt 0 ]; then
|
|
302
|
+
echo " -> Rules: $count scoped, ${#always[@]} always-on"
|
|
303
|
+
else
|
|
304
|
+
echo " -> Rules: $count scoped"
|
|
305
|
+
fi
|
|
203
306
|
}
|
|
204
307
|
|
|
205
308
|
sync_pi_skills() {
|
|
@@ -223,25 +326,18 @@ sync_pi_agents() {
|
|
|
223
326
|
|
|
224
327
|
mkdir -p "$prompts_dir"
|
|
225
328
|
|
|
226
|
-
local
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
[ -d "$dir" ] || continue
|
|
234
|
-
for f in "$dir"/*.md; do
|
|
235
|
-
[ -f "$f" ] || continue
|
|
236
|
-
files+=("$f")
|
|
237
|
-
done
|
|
238
|
-
done <<< "$list"
|
|
329
|
+
local -a files=() names=()
|
|
330
|
+
output_rel_var "$repo_root" "$prompts_dir"
|
|
331
|
+
source_artifacts_var "$repo_root" "$config_file" agents pi "$IS_OUTPUT_REL/intelligence-agent-{}.md"
|
|
332
|
+
if [ "${#IS_SOURCE_FILES[@]}" -gt 0 ]; then
|
|
333
|
+
files=("${IS_SOURCE_FILES[@]}")
|
|
334
|
+
names=("${IS_SOURCE_NAMES[@]}")
|
|
335
|
+
fi
|
|
239
336
|
|
|
240
337
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
241
338
|
local path desc access name template_desc spec="" report="" LS=$'\x1e'
|
|
242
339
|
while IFS=$'\x1f' read -r path desc access; do
|
|
243
|
-
|
|
244
|
-
name="${path##*/}"; name="${name%.md}"
|
|
340
|
+
name="${names[count]}"
|
|
245
341
|
repo_rel_link_var "$repo_root" "$path"
|
|
246
342
|
|
|
247
343
|
template_desc="Use $name persona"
|