@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
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
28
28
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
31
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
32
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
31
33
|
|
|
32
34
|
adapter_contract_opencode() {
|
|
33
35
|
local output="${1%/}"
|
|
@@ -86,28 +88,24 @@ sync_opencode_commands() {
|
|
|
86
88
|
[ "${#generated[@]}" -gt 0 ] && rm -f "${generated[@]}"
|
|
87
89
|
fi
|
|
88
90
|
|
|
89
|
-
local
|
|
90
|
-
local -a skill_mds=()
|
|
91
|
-
|
|
92
|
-
local
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
skill_mds+=("${d%/}/SKILL.md")
|
|
101
|
-
done
|
|
102
|
-
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
|
|
103
102
|
|
|
104
103
|
local count=0
|
|
105
104
|
if [ "${#skill_mds[@]}" -gt 0 ]; then
|
|
106
105
|
local path description argument_hint out_file
|
|
107
106
|
local -a out_files=()
|
|
108
107
|
while IFS=$'\x1f' read -r path description argument_hint; do
|
|
109
|
-
|
|
110
|
-
skill_name="${path%/SKILL.md}"; skill_name="${skill_name##*/}"
|
|
108
|
+
skill_name="${names[count]}"
|
|
111
109
|
out_file="$commands_dir/$skill_name.md"
|
|
112
110
|
yaml_dq_escape_var "$description"
|
|
113
111
|
{
|
|
@@ -145,19 +143,13 @@ sync_opencode_agents() {
|
|
|
145
143
|
local agents_dir="$output_dir/agents"
|
|
146
144
|
mkdir -p "$agents_dir"
|
|
147
145
|
|
|
148
|
-
local
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
[ -d "$dir" ] || continue
|
|
156
|
-
for f in "$dir"/*.md; do
|
|
157
|
-
[ -f "$f" ] || continue
|
|
158
|
-
files+=("$f")
|
|
159
|
-
done
|
|
160
|
-
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
|
|
161
153
|
|
|
162
154
|
local count=0
|
|
163
155
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
@@ -165,8 +157,7 @@ sync_opencode_agents() {
|
|
|
165
157
|
|
|
166
158
|
local path tier access description name edit_perm bash_perm spec="" report="" LS=$'\x1e'
|
|
167
159
|
while IFS=$'\x1f' read -r path tier access description; do
|
|
168
|
-
|
|
169
|
-
name="${path##*/}"; name="${name%.md}"
|
|
160
|
+
name="${names[count]}"
|
|
170
161
|
resolve_model_var "$tier"
|
|
171
162
|
|
|
172
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.
|
|
@@ -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_pi() {
|
|
22
27
|
local output="${1%/}"
|
|
@@ -83,6 +88,39 @@ pi_rule_paths_rows() {
|
|
|
83
88
|
' "$@"
|
|
84
89
|
}
|
|
85
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
|
+
|
|
86
124
|
sync_pi_rules() {
|
|
87
125
|
local repo_root="$1"
|
|
88
126
|
local config_file="$2"
|
|
@@ -96,38 +134,44 @@ sync_pi_rules() {
|
|
|
96
134
|
|
|
97
135
|
mkdir -p "$rules_root" "$(dirname "$extension_file")"
|
|
98
136
|
|
|
99
|
-
local
|
|
100
|
-
local
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
while
|
|
104
|
-
[
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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[@]}")
|
|
121
163
|
fi
|
|
122
164
|
|
|
123
165
|
if [ "${#scoped[@]}" -gt 0 ]; then
|
|
124
|
-
|
|
166
|
+
finalize_copy_named "$rules_root" "$IS_SOURCE_NAME_MAP" "${scoped[@]}"
|
|
125
167
|
|
|
126
168
|
local -a row_arr
|
|
127
169
|
local base rel_rule_path patterns_js display sep display_sep pat i
|
|
170
|
+
k=0
|
|
128
171
|
while IFS=$'\x1f' read -r -a row_arr; do
|
|
129
172
|
[ "${#row_arr[@]}" -gt 0 ] || continue
|
|
130
|
-
base="${
|
|
173
|
+
base="${scoped_names[k]}.md"
|
|
174
|
+
k=$((k + 1))
|
|
131
175
|
rel_rule_path="$output_rel/intelligence-sync/rules/$base"
|
|
132
176
|
patterns_js=""
|
|
133
177
|
display=""
|
|
@@ -151,7 +195,64 @@ sync_pi_rules() {
|
|
|
151
195
|
done < <(pi_rule_paths_rows "${scoped[@]}")
|
|
152
196
|
fi
|
|
153
197
|
|
|
154
|
-
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
|
|
155
256
|
{
|
|
156
257
|
cat <<'EOF'
|
|
157
258
|
/**
|
|
@@ -197,7 +298,11 @@ EOF
|
|
|
197
298
|
finalize_output_file "$extension_file"
|
|
198
299
|
fi
|
|
199
300
|
|
|
200
|
-
|
|
301
|
+
if [ "${#always[@]}" -gt 0 ]; then
|
|
302
|
+
echo " -> Rules: $count scoped, ${#always[@]} always-on"
|
|
303
|
+
else
|
|
304
|
+
echo " -> Rules: $count scoped"
|
|
305
|
+
fi
|
|
201
306
|
}
|
|
202
307
|
|
|
203
308
|
sync_pi_skills() {
|
|
@@ -221,25 +326,18 @@ sync_pi_agents() {
|
|
|
221
326
|
|
|
222
327
|
mkdir -p "$prompts_dir"
|
|
223
328
|
|
|
224
|
-
local
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
[ -d "$dir" ] || continue
|
|
232
|
-
for f in "$dir"/*.md; do
|
|
233
|
-
[ -f "$f" ] || continue
|
|
234
|
-
files+=("$f")
|
|
235
|
-
done
|
|
236
|
-
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
|
|
237
336
|
|
|
238
337
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
239
338
|
local path desc access name template_desc spec="" report="" LS=$'\x1e'
|
|
240
339
|
while IFS=$'\x1f' read -r path desc access; do
|
|
241
|
-
|
|
242
|
-
name="${path##*/}"; name="${name%.md}"
|
|
340
|
+
name="${names[count]}"
|
|
243
341
|
repo_rel_link_var "$repo_root" "$path"
|
|
244
342
|
|
|
245
343
|
template_desc="Use $name persona"
|
|
@@ -122,7 +122,7 @@ adapter_contract_safe_concrete_path() {
|
|
|
122
122
|
# into the caller. Project adapters are trusted executable code during sync;
|
|
123
123
|
# the isolation here is for correctness, not a security boundary.
|
|
124
124
|
adapter_contract_records() (
|
|
125
|
-
local name="$1" file="$2" output="$3" manifest="${4:-}" fn
|
|
125
|
+
local name="$1" file="$2" output="$3" manifest="${4:-}" fn
|
|
126
126
|
# shellcheck source=/dev/null
|
|
127
127
|
source "$file"
|
|
128
128
|
printf -v fn 'adapter_contract_%s' "$name"
|
|
@@ -130,6 +130,38 @@ adapter_contract_records() (
|
|
|
130
130
|
echo "ERROR: adapter '$name' has no $fn contract" >&2
|
|
131
131
|
return 1
|
|
132
132
|
}
|
|
133
|
+
_adapter_contract_emit "$name" '' < <("$fn" "$output" "$manifest")
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# adapter_contract_records_batch <scratch-file> <name> <file> <output>... —
|
|
137
|
+
# adapter_contract_records for several built-in adapters in one subshell, each
|
|
138
|
+
# record prefixed with "<name><TAB>". One process instead of two per adapter:
|
|
139
|
+
# on Git Bash every fork costs tens of milliseconds. <scratch-file> receives
|
|
140
|
+
# each contract's raw lines before they are checked.
|
|
141
|
+
adapter_contract_records_batch() (
|
|
142
|
+
local scratch="$1" name file output fn
|
|
143
|
+
shift
|
|
144
|
+
while [ "$#" -ge 3 ]; do
|
|
145
|
+
name="$1" file="$2" output="$3"
|
|
146
|
+
shift 3
|
|
147
|
+
# shellcheck source=/dev/null
|
|
148
|
+
source "$file"
|
|
149
|
+
printf -v fn 'adapter_contract_%s' "$name"
|
|
150
|
+
declare -F "$fn" >/dev/null 2>&1 || {
|
|
151
|
+
echo "ERROR: adapter '$name' has no $fn contract" >&2
|
|
152
|
+
return 1
|
|
153
|
+
}
|
|
154
|
+
# Like the process substitution above, a contract's own status is not
|
|
155
|
+
# its verdict: the records it printed are.
|
|
156
|
+
"$fn" "$output" > "$scratch" || true
|
|
157
|
+
_adapter_contract_emit "$name" "$name"$'\t' < "$scratch" || return 1
|
|
158
|
+
done
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# _adapter_contract_emit <adapter-name> <prefix> — check the contract records
|
|
162
|
+
# on stdin and print each one after <prefix>; return 1 on the first bad one.
|
|
163
|
+
_adapter_contract_emit() {
|
|
164
|
+
local name="$1" prefix="$2" line kind value saw_version=0
|
|
133
165
|
while IFS= read -r line; do
|
|
134
166
|
[ -n "$line" ] || continue
|
|
135
167
|
kind="${line%%$'\t'*}"
|
|
@@ -171,10 +203,10 @@ adapter_contract_records() (
|
|
|
171
203
|
return 1
|
|
172
204
|
;;
|
|
173
205
|
esac
|
|
174
|
-
printf '%s\n' "$line"
|
|
175
|
-
done
|
|
206
|
+
printf '%s%s\n' "$prefix" "$line"
|
|
207
|
+
done
|
|
176
208
|
[ "$saw_version" -eq 1 ] || {
|
|
177
209
|
echo "ERROR: adapter '$name' contract did not declare version 1" >&2
|
|
178
210
|
return 1
|
|
179
211
|
}
|
|
180
|
-
|
|
212
|
+
}
|