@ainova-systems/intelligence 0.18.1 → 0.20.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 +6 -1
- package/cli/commands/package.sh +8 -3
- package/cli/commands/registry.sh +1 -1
- package/cli/commands/source.sh +56 -17
- package/cli/commands/sync.sh +78 -6
- package/cli/intelligence +16 -3
- package/cli/internal/check.sh +64 -13
- package/cli/internal/convert-legacy.sh +1 -1
- package/cli/internal/package-add.sh +25 -4
- package/cli/internal/package-alias.sh +51 -0
- package/cli/internal/package-list.sh +16 -2
- package/cli/internal/package-remove.sh +3 -1
- package/cli/internal/package-search.sh +1 -1
- package/cli/internal/package-update.sh +14 -6
- package/cli/internal/restore.sh +3 -2
- package/cli/lib/adapter-lifecycle.sh +5 -2
- package/cli/lib/cli-common.sh +149 -34
- package/cli/lib/gitignore.sh +128 -34
- package/cli/lib/lockfile.sh +14 -4
- package/cli/lib/manifest.sh +315 -21
- package/cli/lib/qmap.awk +97 -32
- package/cli/lib/registry.sh +19 -14
- 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 +6 -2
- package/engine/adapters/agents.sh +8 -2
- package/engine/adapters/antigravity.sh +3 -1
- package/engine/adapters/claude.sh +29 -12
- package/engine/adapters/codex.sh +11 -13
- package/engine/adapters/copilot.sh +54 -4
- package/engine/adapters/cursor.sh +13 -2
- package/engine/adapters/opencode.sh +7 -1
- package/engine/adapters/pi.sh +3 -1
- package/engine/lib/adapter-contract.sh +51 -8
- package/engine/lib/common.sh +1072 -160
- package/engine/lib/contract.sh +5 -0
- package/engine/sync.sh +128 -28
- package/package.json +1 -1
- package/packages/sync/references/adapters.md +44 -9
- package/packages/sync/references/conventions.md +48 -8
- package/packages/sync/references/onboarding-migration.md +3 -3
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +3 -2
- package/packages/sync/skills/intelligence-review-context/SKILL.md +2 -1
- package/packages/sync/skills/intelligence-update-context/SKILL.md +4 -2
- package/packages/sync/skills/intelligence-update-context/references/agents.md +7 -3
- package/packages/sync/skills/intelligence-update-context/references/skills.md +3 -1
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
|
+
c8058f91133b86479a03d7ae950191e2d21b68a6
|
package/engine/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.20.0
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
# has_paths(file) — check if file has paths: field
|
|
28
28
|
# get_model(config, ide, tier) — resolve model from config or default
|
|
29
29
|
# get_model_default(ide, tier) — hardcoded default for ide:tier
|
|
30
|
+
# map_effort(tool, effort) — the tool's level for a neutral effort; "" when none
|
|
30
31
|
# map_access_to_claude_tools(access) — full->"" (no tools list; inherits all), readonly->restricted
|
|
31
32
|
# map_access_to_claude_disallowed(access) — readonly->"Write, Edit", full->""
|
|
32
|
-
# read_yaml_list(manifest, section) — read a source list from intelligence.yaml
|
|
33
|
+
# read_yaml_list(manifest, section) — read a source list from intelligence.yaml,
|
|
34
|
+
# package references as their store paths
|
|
33
35
|
# get_target_field(config, target, field) — read a target's config field
|
|
34
36
|
|
|
35
37
|
# Project adapters are sourced by engine/sync.sh after the shared library is
|
|
@@ -40,7 +42,9 @@
|
|
|
40
42
|
# configured target output passed as $1. `owned` paths are exclusively managed;
|
|
41
43
|
# `managed` paths share a directory with another adapter or hand-authored files.
|
|
42
44
|
# Add `legacy` inputs to the initial backup, `preserve` for settings that must
|
|
43
|
-
# never be replaced, and explicit ignore/include records for Git policy.
|
|
45
|
+
# never be replaced, and explicit ignore/include records for Git policy. $2, when
|
|
46
|
+
# given, is the manifest: read it only to choose Git policy (ignore versus
|
|
47
|
+
# unignore), never ownership — callers that read ownership alone omit it.
|
|
44
48
|
adapter_contract_<name>() {
|
|
45
49
|
local output="${1%/}"
|
|
46
50
|
adapter_contract_version 1
|
|
@@ -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
|
|
@@ -212,10 +214,14 @@ agents_md_append_rules_list() {
|
|
|
212
214
|
# Main entry point for AGENTS.md adapter
|
|
213
215
|
adapter_contract_agents() {
|
|
214
216
|
local output
|
|
215
|
-
|
|
217
|
+
agents_output_path_var "$1"
|
|
218
|
+
output="$IS_AGENTS_OUTPUT_PATH"
|
|
216
219
|
adapter_contract_version 1
|
|
217
220
|
adapter_contract_owned "$output"
|
|
218
221
|
adapter_contract_legacy "AGENTS.md"
|
|
222
|
+
# No ignore record, deliberately: AGENTS.md is the tool-neutral fallback
|
|
223
|
+
# every tool reads, and a fresh clone has it only if it is committed —
|
|
224
|
+
# before anyone has run a sync.
|
|
219
225
|
}
|
|
220
226
|
|
|
221
227
|
sync_to_agents() {
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
43
43
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
46
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
47
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
46
48
|
|
|
47
49
|
adapter_contract_antigravity() {
|
|
48
50
|
local output="${1%/}"
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
# Transforms source prompts to .claude/ format
|
|
4
4
|
#
|
|
5
5
|
# Rules: copy as-is (paths: frontmatter preserved)
|
|
6
|
-
# Skills: copy skill directories in full (SKILL.md + bundled resources)
|
|
7
|
-
#
|
|
6
|
+
# Skills: copy skill directories in full (SKILL.md + bundled resources),
|
|
7
|
+
# effort -> Claude's effort level
|
|
8
|
+
# Agents: tier -> model, effort -> effort, access -> tools/disallowedTools
|
|
8
9
|
#
|
|
9
10
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
10
11
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
11
12
|
|
|
12
|
-
|
|
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"
|
|
13
16
|
|
|
14
17
|
adapter_contract_claude() {
|
|
15
18
|
local output="${1%/}"
|
|
@@ -60,6 +63,9 @@ sync_claude_skills() {
|
|
|
60
63
|
|
|
61
64
|
local src d skill_name
|
|
62
65
|
local -a skill_dirs=()
|
|
66
|
+
# The inventory decides which skills render, symlinked ones included
|
|
67
|
+
# (decision 0017); the glob keeps this adapter's own listing order.
|
|
68
|
+
skill_source_inventory "$repo_root" "$config_file"
|
|
63
69
|
load_yaml_list "$config_file" "skills"
|
|
64
70
|
local list="$IS_YAML_LIST"
|
|
65
71
|
while IFS= read -r src; do
|
|
@@ -69,11 +75,12 @@ sync_claude_skills() {
|
|
|
69
75
|
for d in "$dir"/*/; do
|
|
70
76
|
[ -d "$d" ] || continue
|
|
71
77
|
[ -f "$d/SKILL.md" ] || continue
|
|
78
|
+
skill_source_rendered "$d" || continue
|
|
72
79
|
skill_dirs+=("$d")
|
|
73
80
|
done
|
|
74
81
|
done <<< "$list"
|
|
75
82
|
[ "${#skill_dirs[@]}" -gt 0 ] || return 0
|
|
76
|
-
|
|
83
|
+
copy_skill_bundle_dirs_for claude "$output_dir/skills" "${skill_dirs[@]}"
|
|
77
84
|
for d in "${skill_dirs[@]}"; do
|
|
78
85
|
skill_name="${d%/}"
|
|
79
86
|
echo " skill: ${skill_name##*/}"
|
|
@@ -81,7 +88,7 @@ sync_claude_skills() {
|
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
# Sync all agents to Claude format: one frontmatter pass resolves
|
|
84
|
-
# tier/access for every file, one transform pass writes every output.
|
|
91
|
+
# tier/effort/access for every file, one transform pass writes every output.
|
|
85
92
|
sync_claude_agents() {
|
|
86
93
|
local repo_root="$1"
|
|
87
94
|
local config_file="$2"
|
|
@@ -104,19 +111,22 @@ sync_claude_agents() {
|
|
|
104
111
|
|
|
105
112
|
load_model_tiers "$config_file" "claude"
|
|
106
113
|
|
|
107
|
-
local path tier access spec="" report=""
|
|
108
|
-
while IFS=$'\x1f' read -r path tier access; do
|
|
114
|
+
local path tier access effort spec="" report=""
|
|
115
|
+
while IFS=$'\x1f' read -r path tier access effort; do
|
|
109
116
|
[ -n "$path" ] || continue
|
|
110
117
|
resolve_model_var "$tier"
|
|
118
|
+
map_effort_var claude "$effort"
|
|
111
119
|
map_access_to_claude_tools_var "$access"
|
|
112
120
|
map_access_to_claude_disallowed_var "$access"
|
|
113
|
-
spec+="$path"$'\x1f'"$IS_MODEL"$'\x1f'"$IS_CLAUDE_TOOLS"$'\x1f'"$IS_CLAUDE_DISALLOWED"$'\n'
|
|
121
|
+
spec+="$path"$'\x1f'"$IS_MODEL"$'\x1f'"$IS_CLAUDE_TOOLS"$'\x1f'"$IS_CLAUDE_DISALLOWED"$'\x1f'"$IS_EFFORT"$'\n'
|
|
114
122
|
report+=" agent: ${path##*/} (tier=$tier -> model=$IS_MODEL, access=$access)"$'\n'
|
|
115
|
-
done < <(frontmatter_index "tier,access" "${files[@]}")
|
|
123
|
+
done < <(frontmatter_index "tier,access,effort" "${files[@]}")
|
|
116
124
|
|
|
117
125
|
# The transform used to run per file with the values injected via -v;
|
|
118
126
|
# the batched form carries them per file through the environment (awk -v
|
|
119
|
-
# would reprocess backslash escapes).
|
|
127
|
+
# would reprocess backslash escapes). The first frontmatter `effort:` line
|
|
128
|
+
# becomes Claude's level in place, or goes when there is none; a later
|
|
129
|
+
# one goes too, since frontmatter_index read only the first.
|
|
120
130
|
is_fin_awk_vars
|
|
121
131
|
IS_MAP_SPEC="$spec" awk "${IS_FIN_V[@]}" -v dst="$output_dir/agents" "$IS_AWK_LIB"'
|
|
122
132
|
BEGIN {
|
|
@@ -125,18 +135,25 @@ sync_claude_agents() {
|
|
|
125
135
|
for (i = 1; i <= n; i++) {
|
|
126
136
|
if (recs[i] == "") continue
|
|
127
137
|
split(recs[i], f, US)
|
|
128
|
-
MODEL[f[1]] = f[2]; TOOLS[f[1]] = f[3]; EXTRA[f[1]] = f[4]
|
|
138
|
+
MODEL[f[1]] = f[2]; TOOLS[f[1]] = f[3]; EXTRA[f[1]] = f[4]; EFFORT[f[1]] = f[5]
|
|
129
139
|
}
|
|
130
140
|
}
|
|
131
141
|
FNR == 1 {
|
|
132
142
|
if (out != "") close(out)
|
|
133
143
|
out = dst "/" base_name(FILENAME)
|
|
134
144
|
count = 0
|
|
145
|
+
fm = 0
|
|
146
|
+
effort_seen = 0
|
|
135
147
|
}
|
|
136
148
|
/^tier:/ { next }
|
|
137
149
|
/^access:/ { next }
|
|
138
150
|
{ sub(/\r$/, "") }
|
|
151
|
+
FNR == 1 && $0 == "---" { fm = 1 }
|
|
139
152
|
/^---$/ { count++ }
|
|
153
|
+
fm && count == 1 && substr($0, 1, 7) == "effort:" {
|
|
154
|
+
if (!effort_seen++ && EFFORT[FILENAME] != "") print fin_line("effort: " EFFORT[FILENAME]) > out
|
|
155
|
+
next
|
|
156
|
+
}
|
|
140
157
|
count == 2 && /^---$/ {
|
|
141
158
|
if (TOOLS[FILENAME] != "") print fin_line("tools: " TOOLS[FILENAME]) > out
|
|
142
159
|
if (EXTRA[FILENAME] != "") print fin_line("disallowedTools: " EXTRA[FILENAME]) > out
|
|
@@ -165,7 +182,7 @@ sync_to_claude() {
|
|
|
165
182
|
# commands/, statusline.sh, etc.).
|
|
166
183
|
rm -rf "$output_dir/rules" "$output_dir/agents"
|
|
167
184
|
if [ -d "$output_dir/skills" ]; then
|
|
168
|
-
find "$output_dir/skills" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} +
|
|
185
|
+
find "$output_dir/skills" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -exec rm -rf {} +
|
|
169
186
|
fi
|
|
170
187
|
mkdir -p "$output_dir/rules" "$output_dir/skills" "$output_dir/agents"
|
|
171
188
|
|
package/engine/adapters/codex.sh
CHANGED
|
@@ -9,12 +9,16 @@
|
|
|
9
9
|
# Skills: copy skill directories in full to .agents/skills/{name}/ (Codex
|
|
10
10
|
# reads from $REPO_ROOT/.agents/skills exclusively per official docs)
|
|
11
11
|
# Agents: -> .codex/agents/{name}.toml (name, description, model,
|
|
12
|
-
# model_reasoning_effort, sandbox_mode, developer_instructions)
|
|
12
|
+
# model_reasoning_effort, sandbox_mode, developer_instructions).
|
|
13
|
+
# model_reasoning_effort comes from `effort:` alone — never from the tier —
|
|
14
|
+
# and is omitted without one, so Codex's own setting applies.
|
|
13
15
|
#
|
|
14
16
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
15
17
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
20
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
21
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
18
22
|
|
|
19
23
|
adapter_contract_codex() {
|
|
20
24
|
local output="${1%/}"
|
|
@@ -97,18 +101,12 @@ sync_codex_agents() {
|
|
|
97
101
|
if [ "${#files[@]}" -gt 0 ]; then
|
|
98
102
|
load_model_tiers "$config_file" "codex"
|
|
99
103
|
|
|
100
|
-
local path tier access description name
|
|
101
|
-
while IFS=$'\x1f' read -r path tier access description; do
|
|
104
|
+
local path tier access description effort name sandbox spec="" report="" LS=$'\x1e'
|
|
105
|
+
while IFS=$'\x1f' read -r path tier access description effort; do
|
|
102
106
|
[ -n "$path" ] || continue
|
|
103
107
|
name="${path##*/}"; name="${name%.md}"
|
|
104
108
|
resolve_model_var "$tier"
|
|
105
|
-
|
|
106
|
-
frontier) effort="xhigh" ;;
|
|
107
|
-
heavy|"") effort="high" ;;
|
|
108
|
-
standard) effort="medium" ;;
|
|
109
|
-
light) effort="low" ;;
|
|
110
|
-
*) effort="medium" ;;
|
|
111
|
-
esac
|
|
109
|
+
map_effort_var codex "$effort"
|
|
112
110
|
case "$access" in
|
|
113
111
|
readonly) sandbox="read-only" ;;
|
|
114
112
|
*) sandbox="workspace-write" ;;
|
|
@@ -121,7 +119,7 @@ sync_codex_agents() {
|
|
|
121
119
|
header="${LS}name = \"$name_escaped\""
|
|
122
120
|
header+="${LS}description = \"$description_escaped\""
|
|
123
121
|
header+="${LS}model = \"$IS_MODEL\""
|
|
124
|
-
header+="${LS}model_reasoning_effort = \"$
|
|
122
|
+
[ -z "$IS_EFFORT" ] || header+="${LS}model_reasoning_effort = \"$IS_EFFORT\""
|
|
125
123
|
header+="${LS}sandbox_mode = \"$sandbox\""
|
|
126
124
|
header+="${LS}"
|
|
127
125
|
header+="${LS}developer_instructions = \"\"\""
|
|
@@ -129,7 +127,7 @@ sync_codex_agents() {
|
|
|
129
127
|
spec+="$path"$'\x1f'"$output_dir/$name.toml"$'\x1f'"fence"$'\x1f'"1"$'\x1f'"toml"$'\x1f'"$header"$'\x1f'"${LS}\"\"\""$'\n'
|
|
130
128
|
report+=" agent: $name.toml"$'\n'
|
|
131
129
|
count=$((count + 1))
|
|
132
|
-
done < <(frontmatter_index "tier,access,description" "${files[@]}")
|
|
130
|
+
done < <(frontmatter_index "tier,access,description,effort" "${files[@]}")
|
|
133
131
|
|
|
134
132
|
emit_wrapped_bodies "$spec"
|
|
135
133
|
printf '%s' "$report"
|
|
@@ -12,13 +12,22 @@
|
|
|
12
12
|
# Skills: copy skill directories in full to .github/skills/{name}/
|
|
13
13
|
# Agents: -> .github/agents/{name}.agent.md (description, tools, model)
|
|
14
14
|
#
|
|
15
|
+
# Git policy (decision 0020): generated output is ignored by default, as it is
|
|
16
|
+
# for Cursor and Claude Code — Copilot in the editor reads it from disk after
|
|
17
|
+
# sync. GitHub's cloud Copilot (code review, the coding agent) reads the
|
|
18
|
+
# repository instead, so `targets.copilot.commit_output: true` keeps the output
|
|
19
|
+
# tracked. Only the generated subdirectories are named, never `.github/`
|
|
20
|
+
# itself: workflows, templates and hand-written files there stay tracked.
|
|
21
|
+
#
|
|
15
22
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
16
23
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
26
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
27
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
19
28
|
|
|
20
29
|
adapter_contract_copilot() {
|
|
21
|
-
local output="${1%/}"
|
|
30
|
+
local output="${1%/}" record=adapter_contract_ignore sub
|
|
22
31
|
adapter_contract_version 1
|
|
23
32
|
adapter_contract_requires agents
|
|
24
33
|
adapter_contract_owned "$output/instructions"
|
|
@@ -26,6 +35,37 @@ adapter_contract_copilot() {
|
|
|
26
35
|
adapter_contract_owned "$output/agents"
|
|
27
36
|
adapter_contract_owned "$output/skills"
|
|
28
37
|
adapter_contract_legacy "$output/copilot-instructions.md"
|
|
38
|
+
# A malformed value declares the default here; sync refuses it with the
|
|
39
|
+
# reason, so the policy never rests on a guess.
|
|
40
|
+
if copilot_commit_output_var "${2:-}" && [ "$IS_COPILOT_COMMIT_OUTPUT" = 1 ]; then
|
|
41
|
+
record=adapter_contract_unignore
|
|
42
|
+
fi
|
|
43
|
+
# The same four paths the contract owns: every sync rewrites them, so the
|
|
44
|
+
# ignore covers generated output only.
|
|
45
|
+
for sub in instructions prompts agents skills; do
|
|
46
|
+
"$record" "$output/$sub/"
|
|
47
|
+
done
|
|
48
|
+
# The root instructions file onboarding quarantines, like CLAUDE.md and
|
|
49
|
+
# .cursorrules: its content migrates into project rules, and a copy left
|
|
50
|
+
# behind makes Copilot ignore AGENTS.md (copilot-cli#489).
|
|
51
|
+
"$record" "$output/copilot-instructions.md"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# copilot_commit_output_var <manifest> — the one reader of
|
|
55
|
+
# targets.copilot.commit_output. Sets IS_COPILOT_COMMIT_OUTPUT to 1 when the
|
|
56
|
+
# project commits generated output and 0 for the default; returns 1 for any
|
|
57
|
+
# value other than true or false. No manifest — a caller reading ownership
|
|
58
|
+
# only — is the default.
|
|
59
|
+
copilot_commit_output_var() {
|
|
60
|
+
local manifest="$1" setting
|
|
61
|
+
IS_COPILOT_COMMIT_OUTPUT=0
|
|
62
|
+
[ -n "$manifest" ] && [ -f "$manifest" ] || return 0
|
|
63
|
+
setting="$(get_target_field "$manifest" "copilot" "commit_output")"
|
|
64
|
+
case "$setting" in
|
|
65
|
+
''|false) ;;
|
|
66
|
+
true) IS_COPILOT_COMMIT_OUTPUT=1 ;;
|
|
67
|
+
*) return 1 ;;
|
|
68
|
+
esac
|
|
29
69
|
}
|
|
30
70
|
|
|
31
71
|
# copilot_paths_rows <file>... — batch form of the old per-file `paths:`
|
|
@@ -128,6 +168,9 @@ sync_copilot_skills() {
|
|
|
128
168
|
|
|
129
169
|
local src d skill_name
|
|
130
170
|
local -a skill_dirs=()
|
|
171
|
+
# The inventory decides which skills render, symlinked ones included
|
|
172
|
+
# (decision 0017); the glob keeps this adapter's own listing order.
|
|
173
|
+
skill_source_inventory "$repo_root" "$config_file"
|
|
131
174
|
load_yaml_list "$config_file" "skills"
|
|
132
175
|
local list="$IS_YAML_LIST"
|
|
133
176
|
while IFS= read -r src; do
|
|
@@ -137,13 +180,15 @@ sync_copilot_skills() {
|
|
|
137
180
|
for d in "$dir"/*/; do
|
|
138
181
|
[ -d "$d" ] || continue
|
|
139
182
|
[ -f "$d/SKILL.md" ] || continue
|
|
183
|
+
skill_source_rendered "$d" || continue
|
|
140
184
|
skill_dirs+=("$d")
|
|
141
185
|
done
|
|
142
186
|
done <<< "$list"
|
|
143
187
|
|
|
144
188
|
local count=0
|
|
145
189
|
if [ "${#skill_dirs[@]}" -gt 0 ]; then
|
|
146
|
-
|
|
190
|
+
# Copilot has no effort field: the copy carries no `effort:`.
|
|
191
|
+
copy_skill_bundle_dirs_for copilot "$output_dir/skills" "${skill_dirs[@]}"
|
|
147
192
|
for d in "${skill_dirs[@]}"; do
|
|
148
193
|
skill_name="${d%/}"
|
|
149
194
|
count=$((count + 1))
|
|
@@ -221,10 +266,15 @@ sync_to_copilot() {
|
|
|
221
266
|
|
|
222
267
|
echo "=== GitHub Copilot ==="
|
|
223
268
|
|
|
269
|
+
copilot_commit_output_var "$config_file" || {
|
|
270
|
+
echo "ERROR: targets.copilot.commit_output must be true or false: true keeps generated Copilot output tracked, false or omission ignores it." >&2
|
|
271
|
+
return 1
|
|
272
|
+
}
|
|
273
|
+
|
|
224
274
|
# Clean generated content (preserve workflows, etc.)
|
|
225
275
|
rm -rf "$output_dir/instructions" "$output_dir/prompts"
|
|
226
276
|
if [ -d "$output_dir/skills" ]; then
|
|
227
|
-
find "$output_dir/skills" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} +
|
|
277
|
+
find "$output_dir/skills" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) -exec rm -rf {} +
|
|
228
278
|
fi
|
|
229
279
|
rm -rf "$output_dir/agents"
|
|
230
280
|
mkdir -p "$output_dir" "$output_dir/skills" "$output_dir/agents"
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
# same rules would cause double-loading and burn the context window.
|
|
11
11
|
# Skills: copy skill directories in full (SKILL.md + bundled resources)
|
|
12
12
|
# Agents: strip tier/access/tools/disallowedTools, add model + readonly:true
|
|
13
|
+
# Cursor has no per-agent or per-skill effort field (its effort is part of a
|
|
14
|
+
# model ID), so `effort:` is stripped from both.
|
|
13
15
|
#
|
|
14
16
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
15
17
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
20
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
21
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
18
22
|
|
|
19
23
|
adapter_contract_cursor() {
|
|
20
24
|
local output="${1%/}"
|
|
@@ -100,6 +104,9 @@ sync_cursor_skills() {
|
|
|
100
104
|
|
|
101
105
|
local src d skill_name
|
|
102
106
|
local -a skill_dirs=()
|
|
107
|
+
# The inventory decides which skills render, symlinked ones included
|
|
108
|
+
# (decision 0017); the glob keeps this adapter's own listing order.
|
|
109
|
+
skill_source_inventory "$repo_root" "$config_file"
|
|
103
110
|
load_yaml_list "$config_file" "skills"
|
|
104
111
|
local list="$IS_YAML_LIST"
|
|
105
112
|
while IFS= read -r src; do
|
|
@@ -109,11 +116,12 @@ sync_cursor_skills() {
|
|
|
109
116
|
for d in "$dir"/*/; do
|
|
110
117
|
[ -d "$d" ] || continue
|
|
111
118
|
[ -f "$d/SKILL.md" ] || continue
|
|
119
|
+
skill_source_rendered "$d" || continue
|
|
112
120
|
skill_dirs+=("$d")
|
|
113
121
|
done
|
|
114
122
|
done <<< "$list"
|
|
115
123
|
[ "${#skill_dirs[@]}" -gt 0 ] || return 0
|
|
116
|
-
|
|
124
|
+
copy_skill_bundle_dirs_for cursor "$output_dir/skills" "${skill_dirs[@]}"
|
|
117
125
|
for d in "${skill_dirs[@]}"; do
|
|
118
126
|
skill_name="${d%/}"
|
|
119
127
|
echo " skill: ${skill_name##*/}"
|
|
@@ -170,13 +178,16 @@ sync_cursor_agents() {
|
|
|
170
178
|
if (out != "") close(out)
|
|
171
179
|
out = dst "/" base_name(FILENAME)
|
|
172
180
|
count = 0
|
|
181
|
+
fm = 0
|
|
173
182
|
}
|
|
174
183
|
/^tier:/ { next }
|
|
175
184
|
/^access:/ { next }
|
|
176
185
|
/^tools:/ { next }
|
|
177
186
|
/^disallowedTools:/ { next }
|
|
178
187
|
{ sub(/\r$/, "") }
|
|
188
|
+
FNR == 1 && $0 == "---" { fm = 1 }
|
|
179
189
|
/^---$/ { count++ }
|
|
190
|
+
fm && count == 1 && substr($0, 1, 7) == "effort:" { next }
|
|
180
191
|
count == 2 && /^---$/ {
|
|
181
192
|
print fin_line("model: " MODEL[FILENAME]) > out
|
|
182
193
|
if (RO[FILENAME] != "") print fin_line(RO[FILENAME]) > out
|
|
@@ -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%/}"
|
|
@@ -88,6 +90,9 @@ sync_opencode_commands() {
|
|
|
88
90
|
|
|
89
91
|
local src d skill_name
|
|
90
92
|
local -a skill_mds=()
|
|
93
|
+
# The inventory decides which skills render, symlinked ones included
|
|
94
|
+
# (decision 0017); the glob keeps this adapter's own listing order.
|
|
95
|
+
skill_source_inventory "$repo_root" "$config_file"
|
|
91
96
|
load_yaml_list "$config_file" "skills"
|
|
92
97
|
local list="$IS_YAML_LIST"
|
|
93
98
|
while IFS= read -r src; do
|
|
@@ -97,6 +102,7 @@ sync_opencode_commands() {
|
|
|
97
102
|
for d in "$dir"/*/; do
|
|
98
103
|
[ -d "$d" ] || continue
|
|
99
104
|
[ -f "$d/SKILL.md" ] || continue
|
|
105
|
+
skill_source_rendered "$d" || continue
|
|
100
106
|
skill_mds+=("${d%/}/SKILL.md")
|
|
101
107
|
done
|
|
102
108
|
done <<< "$list"
|
package/engine/adapters/pi.sh
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
# Every per-file loop batches its work into one awk process (see the batched
|
|
17
17
|
# helpers in lib/common.sh): process spawns dominate sync time on Windows.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# The engine and the CLI load the shared library before any adapter. Only a
|
|
20
|
+
# standalone source of this file loads it here: that costs a dirname process.
|
|
21
|
+
declare -F finalize_output_file >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
20
22
|
|
|
21
23
|
adapter_contract_pi() {
|
|
22
24
|
local output="${1%/}"
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Declarative adapter ownership contract shared by the engine and CLI.
|
|
3
3
|
#
|
|
4
|
-
# Every adapter exposes adapter_contract_<name> <configured-output
|
|
5
|
-
# function emits tab-separated records through the helpers below. Keeping the
|
|
4
|
+
# Every adapter exposes adapter_contract_<name> <configured-output> [manifest].
|
|
5
|
+
# The function emits tab-separated records through the helpers below. Keeping the
|
|
6
6
|
# declaration beside sync_to_<name>() makes backup, rollback, git policy and
|
|
7
7
|
# lifecycle checks consume the same ownership model as the writer itself.
|
|
8
|
+
#
|
|
9
|
+
# The manifest argument exists for Git policy alone: a target field may choose
|
|
10
|
+
# whether generated output is ignored (targets.copilot.commit_output). Ownership
|
|
11
|
+
# never depends on it, so the engine and the sync cache, which read ownership
|
|
12
|
+
# only, pass none and spare every sync the extra read; a contract given none
|
|
13
|
+
# declares its default policy. Every caller that applies or checks Git policy
|
|
14
|
+
# passes the manifest.
|
|
8
15
|
|
|
9
16
|
adapter_contract_version() { printf 'version\t%s\n' "$1"; }
|
|
10
17
|
adapter_contract_requires() { printf 'requires\t%s\n' "$1"; }
|
|
@@ -14,6 +21,10 @@ adapter_contract_legacy() { printf 'legacy\t%s\n' "$1"; }
|
|
|
14
21
|
adapter_contract_preserve() { printf 'preserve\t%s\n' "$1"; }
|
|
15
22
|
adapter_contract_ignore() { printf 'ignore\t%s\n' "$1"; }
|
|
16
23
|
adapter_contract_include() { printf 'include\t%s\n' "$1"; }
|
|
24
|
+
# An ignore line the policy must not hold: the writer takes it back out of the
|
|
25
|
+
# region it manages. It is how a configuration that keeps output tracked undoes
|
|
26
|
+
# the default ignore an earlier alignment already wrote.
|
|
27
|
+
adapter_contract_unignore() { printf 'unignore\t%s\n' "$1"; }
|
|
17
28
|
|
|
18
29
|
# --- cross-adapter ownership -------------------------------------------------
|
|
19
30
|
#
|
|
@@ -106,12 +117,12 @@ adapter_contract_safe_concrete_path() {
|
|
|
106
117
|
esac
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
# adapter_contract_records <adapter-name> <adapter-file> <configured-output>
|
|
120
|
+
# adapter_contract_records <adapter-name> <adapter-file> <configured-output> [manifest]
|
|
110
121
|
# Source and query in a subshell so a project adapter cannot leak shell state
|
|
111
122
|
# into the caller. Project adapters are trusted executable code during sync;
|
|
112
123
|
# the isolation here is for correctness, not a security boundary.
|
|
113
124
|
adapter_contract_records() (
|
|
114
|
-
local name="$1" file="$2" output="$3" fn
|
|
125
|
+
local name="$1" file="$2" output="$3" manifest="${4:-}" fn
|
|
115
126
|
# shellcheck source=/dev/null
|
|
116
127
|
source "$file"
|
|
117
128
|
printf -v fn 'adapter_contract_%s' "$name"
|
|
@@ -119,6 +130,38 @@ adapter_contract_records() (
|
|
|
119
130
|
echo "ERROR: adapter '$name' has no $fn contract" >&2
|
|
120
131
|
return 1
|
|
121
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
|
|
122
165
|
while IFS= read -r line; do
|
|
123
166
|
[ -n "$line" ] || continue
|
|
124
167
|
kind="${line%%$'\t'*}"
|
|
@@ -149,7 +192,7 @@ adapter_contract_records() (
|
|
|
149
192
|
return 1
|
|
150
193
|
}
|
|
151
194
|
;;
|
|
152
|
-
ignore|include)
|
|
195
|
+
ignore|include|unignore)
|
|
153
196
|
adapter_contract_safe_path "$value" || {
|
|
154
197
|
echo "ERROR: adapter '$name' declares unsafe $kind path '$value'" >&2
|
|
155
198
|
return 1
|
|
@@ -160,10 +203,10 @@ adapter_contract_records() (
|
|
|
160
203
|
return 1
|
|
161
204
|
;;
|
|
162
205
|
esac
|
|
163
|
-
printf '%s\n' "$line"
|
|
164
|
-
done
|
|
206
|
+
printf '%s%s\n' "$prefix" "$line"
|
|
207
|
+
done
|
|
165
208
|
[ "$saw_version" -eq 1 ] || {
|
|
166
209
|
echo "ERROR: adapter '$name' contract did not declare version 1" >&2
|
|
167
210
|
return 1
|
|
168
211
|
}
|
|
169
|
-
|
|
212
|
+
}
|