@ainova-systems/intelligence 0.11.0-rc.7 → 0.11.0-rc.9
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/README.md +19 -16
- package/cli/commands/adapter.sh +153 -0
- package/cli/commands/init.sh +113 -22
- package/cli/commands/package.sh +30 -0
- package/cli/commands/registry.sh +4 -2
- package/cli/commands/status.sh +12 -4
- package/cli/commands/sync.sh +7 -20
- package/cli/commands/update.sh +76 -70
- package/cli/engine-package.yaml +2 -2
- package/cli/intelligence +10 -12
- package/cli/{commands/doctor.sh → internal/check.sh} +30 -23
- package/cli/{commands/migrate.sh → internal/migrate-v1.sh} +117 -30
- package/cli/{commands/add.sh → internal/package-add.sh} +11 -16
- package/cli/{commands/list.sh → internal/package-list.sh} +9 -4
- package/cli/{commands/remove.sh → internal/package-remove.sh} +3 -3
- package/cli/{commands/search.sh → internal/package-search.sh} +4 -4
- package/cli/internal/package-update.sh +103 -0
- package/cli/{commands/install.sh → internal/restore.sh} +7 -18
- package/cli/internal/target-state.sh +57 -0
- package/cli/internal/upgrade-v2.sh +133 -0
- package/cli/lib/cli-common.sh +142 -12
- package/cli/lib/lockfile.sh +1 -1
- package/cli/lib/manifest.sh +109 -0
- package/cli/lib/registry.sh +4 -4
- package/engine/ENGINE_SHA +1 -1
- package/engine/adapters/_template.sh +10 -9
- package/engine/adapters/agents.sh +11 -11
- package/engine/adapters/opencode.sh +1 -1
- package/engine/lib/common.sh +14 -22
- package/engine/lib/contract.sh +14 -14
- package/engine/sync.sh +25 -20
- package/package.json +1 -1
- package/packages/sync/agents/intelligence-architect.md +5 -3
- package/packages/sync/agents/intelligence-operator.md +10 -13
- package/packages/sync/references/adapters.md +252 -0
- package/packages/sync/references/conventions.md +385 -0
- package/packages/sync/rules/intelligence-authoring.md +6 -6
- package/packages/sync/skills/intelligence-add-agent/SKILL.md +5 -5
- package/packages/sync/skills/intelligence-add-rule/SKILL.md +3 -3
- package/packages/sync/skills/intelligence-add-skill/SKILL.md +3 -3
- package/packages/sync/skills/intelligence-extract-skill/SKILL.md +2 -2
- package/packages/sync/skills/intelligence-install-adapter/SKILL.md +29 -22
- package/packages/sync/skills/intelligence-learn-from-context/SKILL.md +3 -3
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +55 -0
- package/packages/sync/skills/intelligence-review-skills/SKILL.md +6 -6
- package/packages/sync/skills/intelligence-sync/SKILL.md +13 -9
- package/packages/sync/skills/intelligence-uninstall-adapter/SKILL.md +19 -37
- package/packages/sync/skills/intelligence-update/SKILL.md +34 -156
- package/cli/commands/upgrade.sh +0 -68
- package/packages/sync/docs/ADAPTERS.md +0 -214
- package/packages/sync/docs/CONVENTIONS.md +0 -456
package/cli/lib/cli-common.sh
CHANGED
|
@@ -54,8 +54,8 @@ source "$CLI_DIR/lib/lockfile.sh"
|
|
|
54
54
|
# The engine-content package: OPTIONAL but auto-selected at init. Package by
|
|
55
55
|
# UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
|
|
56
56
|
# at the version the CLI ships, it materializes from the npm bundle without
|
|
57
|
-
# network; only a cross-version
|
|
58
|
-
# at the bundled engine version and moved
|
|
57
|
+
# network; only a cross-version acquisition reaches git. The pin is held
|
|
58
|
+
# exactly at the bundled engine version and moved by lifecycle alignment.
|
|
59
59
|
#
|
|
60
60
|
# Its identity is DATA shipped with the distribution (cli/engine-package.yaml),
|
|
61
61
|
# never a name compiled into cli code — a fork edits the file.
|
|
@@ -113,7 +113,7 @@ require_v2() {
|
|
|
113
113
|
detect_project
|
|
114
114
|
case "$IP_MODE" in
|
|
115
115
|
v2) ;;
|
|
116
|
-
legacy) die "this is a vendored (v1) setup — run 'intelligence
|
|
116
|
+
legacy) die "this is a vendored (v1) setup — run 'intelligence init' to migrate it, or keep using its own flow" ;;
|
|
117
117
|
*) die "no intelligence project found here — run 'intelligence init'" ;;
|
|
118
118
|
esac
|
|
119
119
|
}
|
|
@@ -126,19 +126,25 @@ manifest_intelligence_dir() {
|
|
|
126
126
|
printf '%s' "${v:-intelligence}"
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
default_target_output() {
|
|
130
|
+
case "$1" in
|
|
131
|
+
agents) printf '%s' "AGENTS.md" ;;
|
|
132
|
+
copilot) printf '%s' ".github" ;;
|
|
133
|
+
*) printf '.%s' "$1" ;;
|
|
134
|
+
esac
|
|
135
|
+
}
|
|
136
|
+
|
|
129
137
|
bundled_engine_version() {
|
|
130
138
|
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/VERSION"
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
# --- The sync package's manifest/lock plumbing ---------------------------
|
|
134
|
-
# sync_pkg_entry <manifest> — write/refresh the
|
|
135
|
-
#
|
|
136
|
-
#
|
|
142
|
+
# sync_pkg_entry <manifest> — write/refresh only the requested exact pin.
|
|
143
|
+
# The built-in source lives in engine-package.yaml and resolved source state
|
|
144
|
+
# belongs exclusively to intelligence.lock.
|
|
137
145
|
sync_pkg_entry() {
|
|
138
146
|
local manifest="$1"
|
|
139
147
|
qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "version" "$(bundled_engine_version)"
|
|
140
|
-
qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "url" "$SYNC_PKG_URL"
|
|
141
|
-
qmap_set "$manifest" "packages" "$SYNC_PKG_NAME" "path" "$SYNC_PKG_PATH"
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
# sync_pkg_install <root> — materialize the package into the store and lock
|
|
@@ -156,14 +162,138 @@ sync_pkg_install() {
|
|
|
156
162
|
# Everything the IS_CLI mode of sync.sh needs, derived from the manifest.
|
|
157
163
|
export_engine_env() {
|
|
158
164
|
local root="$1"
|
|
159
|
-
local
|
|
160
|
-
|
|
165
|
+
local content_rel
|
|
166
|
+
content_rel="$(manifest_intelligence_dir "$root/intelligence.yaml")"
|
|
161
167
|
export IS_CLI=1
|
|
162
168
|
export CONFIG_FILE="$root/intelligence.yaml"
|
|
163
169
|
export REPO_ROOT="$root"
|
|
164
|
-
export
|
|
170
|
+
export IS_CONTENT_REL="$content_rel"
|
|
165
171
|
export IS_MODULE_REL="$SYNC_PKG_STORE"
|
|
166
172
|
export IS_SYNC_CMD="intelligence sync"
|
|
167
173
|
export IS_MANIFEST_NAME="intelligence.yaml"
|
|
168
|
-
export IS_PROTECTED_DIRS="$
|
|
174
|
+
export IS_PROTECTED_DIRS="$content_rel:.intelligence"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# --- Project lifecycle preflight -----------------------------------------
|
|
178
|
+
# Public commands are intentionally few. They share this state gate so a CLI
|
|
179
|
+
# installed at a newer engine version brings the current v2 project forward
|
|
180
|
+
# before a mutating operation. The npm install itself cannot do that: it runs
|
|
181
|
+
# outside any project and does not know which repositories the user owns.
|
|
182
|
+
|
|
183
|
+
is_ci_environment() {
|
|
184
|
+
case "${CI:-}" in
|
|
185
|
+
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
|
186
|
+
*) return 1 ;;
|
|
187
|
+
esac
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Validate a repository-relative project content directory, including every
|
|
191
|
+
# existing symlinked path component. Call before writing a new manifest.
|
|
192
|
+
assert_safe_content_dir() {
|
|
193
|
+
local root="$1" content_dir="$2" repo_phys probe old_ifs part probe_phys
|
|
194
|
+
local -a parts
|
|
195
|
+
case "$content_dir" in
|
|
196
|
+
""|/*|.|..|../*|*/../*|*/..|.git|.git/*|.intelligence|.intelligence/*|*\\*|[A-Za-z]:*)
|
|
197
|
+
die "unsafe content directory '$content_dir'"
|
|
198
|
+
;;
|
|
199
|
+
esac
|
|
200
|
+
repo_phys="$(cd "$root" && pwd -P)"
|
|
201
|
+
probe="$root"
|
|
202
|
+
old_ifs="$IFS"; IFS='/'; read -r -a parts <<< "$content_dir"; IFS="$old_ifs"
|
|
203
|
+
for part in "${parts[@]}"; do
|
|
204
|
+
[ -n "$part" ] || continue
|
|
205
|
+
probe="$probe/$part"
|
|
206
|
+
if [ -e "$probe" ] || [ -L "$probe" ]; then
|
|
207
|
+
probe_phys="$(cd "$probe" 2>/dev/null && pwd -P)" \
|
|
208
|
+
|| die "cannot resolve project content path '$content_dir'"
|
|
209
|
+
case "$probe_phys" in
|
|
210
|
+
"$repo_phys"|"$repo_phys"/*) ;;
|
|
211
|
+
*) die "project content directory resolves outside the repository: '$content_dir'" ;;
|
|
212
|
+
esac
|
|
213
|
+
fi
|
|
214
|
+
done
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
project_needs_upgrade() {
|
|
218
|
+
local root="$1" manifest="$1/intelligence.yaml" stamp eng pinned locked name
|
|
219
|
+
[ -f "$manifest" ] || return 1
|
|
220
|
+
stamp="$(read_schema_version "$manifest")"
|
|
221
|
+
eng="$(bundled_engine_version)"
|
|
222
|
+
[ -z "$stamp" ] && return 0
|
|
223
|
+
[ -n "$(top_scalar "$manifest" "sync_version")" ] && return 0
|
|
224
|
+
_ver_gt "$eng" "$stamp" && return 0
|
|
225
|
+
[ -d "$root/.intelligence/engine" ] && return 0
|
|
226
|
+
|
|
227
|
+
while IFS= read -r name; do
|
|
228
|
+
[ -n "$name" ] || continue
|
|
229
|
+
# Early RC manifests mixed requested intent with resolved source
|
|
230
|
+
# details. Source URL/path now live only in the required lockfile.
|
|
231
|
+
[ -n "$(qmap_field "$manifest" "packages" "$name" "url")" ] && return 0
|
|
232
|
+
[ -n "$(qmap_field "$manifest" "packages" "$name" "path")" ] && return 0
|
|
233
|
+
if [ "$name" = "$SYNC_PKG_NAME" ]; then
|
|
234
|
+
pinned="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
235
|
+
locked="$(qmap_field "$root/intelligence.lock" "packages" "$name" "resolved")"
|
|
236
|
+
[ "$pinned" = "$eng" ] || return 0
|
|
237
|
+
if [ -f "$root/intelligence.lock" ]; then
|
|
238
|
+
[ -n "$locked" ] || return 0
|
|
239
|
+
[ "${locked#v}" = "$eng" ] || return 0
|
|
240
|
+
fi
|
|
241
|
+
fi
|
|
242
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
243
|
+
return 1
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# A manifest with packages but no lock has no trustworthy resolved state from
|
|
247
|
+
# which lifecycle alignment can proceed. Never manufacture a partial lock.
|
|
248
|
+
project_has_packages() {
|
|
249
|
+
local root="$1" name found=1
|
|
250
|
+
while IFS= read -r name; do
|
|
251
|
+
[ -n "$name" ] || continue
|
|
252
|
+
assert_valid_pkg_name "$name"
|
|
253
|
+
found=0
|
|
254
|
+
done < <(qmap_keys "$root/intelligence.yaml" "packages")
|
|
255
|
+
return "$found"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
ensure_project_current() {
|
|
259
|
+
local root="$1" explicit="${2:-}" manifest="$1/intelligence.yaml" stamp eng
|
|
260
|
+
check_version_compat "$manifest" || return $?
|
|
261
|
+
if project_has_packages "$root" && [ ! -f "$root/intelligence.lock" ]; then
|
|
262
|
+
die "manifest declares packages but intelligence.lock is absent — restore the committed lock before running project lifecycle commands"
|
|
263
|
+
fi
|
|
264
|
+
project_needs_upgrade "$root" || return 0
|
|
265
|
+
stamp="$(read_schema_version "$manifest")"
|
|
266
|
+
eng="$(bundled_engine_version)"
|
|
267
|
+
if is_ci_environment && [ "$explicit" != "--explicit" ]; then
|
|
268
|
+
die "project lifecycle requires alignment (stamp ${stamp:-unstamped}, engine $eng) — run 'intelligence init --apply' locally, review and commit the diff"
|
|
269
|
+
fi
|
|
270
|
+
echo " project alignment: stamp ${stamp:-unstamped}, engine $eng"
|
|
271
|
+
bash "$CLI_DIR/internal/upgrade-v2.sh" --no-sync
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
project_store_missing() {
|
|
275
|
+
local root="$1" manifest="$1/intelligence.yaml" name src
|
|
276
|
+
while IFS= read -r name; do
|
|
277
|
+
[ -n "$name" ] || continue
|
|
278
|
+
assert_valid_pkg_name "$name"
|
|
279
|
+
[ -d "$root/.intelligence/packages/$name" ] || return 0
|
|
280
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
281
|
+
for section in rules agents skills; do
|
|
282
|
+
while IFS= read -r src; do
|
|
283
|
+
case "$src" in
|
|
284
|
+
.intelligence/packages/*)
|
|
285
|
+
[ -d "$root/$src" ] || return 0
|
|
286
|
+
;;
|
|
287
|
+
esac
|
|
288
|
+
done < <(read_yaml_list "$manifest" "$section")
|
|
289
|
+
done
|
|
290
|
+
return 1
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
restore_project_store_if_missing() {
|
|
294
|
+
local root="$1"
|
|
295
|
+
project_store_missing "$root" || return 0
|
|
296
|
+
[ -f "$root/intelligence.lock" ] || die "package store is missing and intelligence.lock is absent — run 'intelligence init'"
|
|
297
|
+
echo " restoring package store from intelligence.lock"
|
|
298
|
+
bash "$CLI_DIR/internal/restore.sh" --frozen --no-sync
|
|
169
299
|
}
|
package/cli/lib/lockfile.sh
CHANGED
|
@@ -12,7 +12,7 @@ LOCK_SEP=$'\x1f'
|
|
|
12
12
|
|
|
13
13
|
# top_scalar <file> <key> — one top-level scalar (`key: "value"`), quotes and
|
|
14
14
|
# trailing comment stripped. The declared exception to "no new parsing": the
|
|
15
|
-
# engine's
|
|
15
|
+
# engine's read_schema_version is hardwired to its own key.
|
|
16
16
|
top_scalar() {
|
|
17
17
|
[ -f "$1" ] || return 0
|
|
18
18
|
awk -v key="$2" '
|
package/cli/lib/manifest.sh
CHANGED
|
@@ -36,6 +36,92 @@ assert_valid_pkg_name() {
|
|
|
36
36
|
esac
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
# Target names become adapter filenames and shell function suffixes, so keep
|
|
40
|
+
# them inside the portable shell-identifier subset used by built-in adapters.
|
|
41
|
+
assert_valid_target_name() {
|
|
42
|
+
case "$1" in
|
|
43
|
+
""|[!abcdefghijklmnopqrstuvwxyz]*|*[!abcdefghijklmnopqrstuvwxyz0123456789_]*)
|
|
44
|
+
die "invalid target name '$1' — expected [a-z][a-z0-9_]*"
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# target_exists <manifest> <name> — distinguish a disabled target from one
|
|
50
|
+
# that is not declared at all. Both inline and block target shapes count.
|
|
51
|
+
target_exists() {
|
|
52
|
+
local file="$1" target="$2"
|
|
53
|
+
[ -f "$file" ] || return 1
|
|
54
|
+
awk -v target="$target" '
|
|
55
|
+
{ sub(/\r$/, "") }
|
|
56
|
+
/^targets:[[:space:]]*$/ { in_targets = 1; next }
|
|
57
|
+
/^[A-Za-z]/ { in_targets = 0 }
|
|
58
|
+
in_targets && $0 ~ "^ " target ":[[:space:]]*" { found = 1; exit }
|
|
59
|
+
END { exit(found ? 0 : 1) }
|
|
60
|
+
' "$file"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# target_set_enabled <manifest> <name> <true|false> <default-output>
|
|
64
|
+
# Transactionally update only `enabled`, preserving every other target field
|
|
65
|
+
# and comment. A missing target is added in the compact form used by init.
|
|
66
|
+
target_set_enabled() {
|
|
67
|
+
local file="$1" target="$2" enabled="$3" output="$4"
|
|
68
|
+
[ -f "$file" ] || die "no such file: $file"
|
|
69
|
+
case "$enabled" in true|false) ;; *) die "internal: invalid target state '$enabled'" ;; esac
|
|
70
|
+
output="$(_yq_esc "$output")"
|
|
71
|
+
_qmap_stage "$file" -v target="$target" -v enabled="$enabled" -v output="$output" '
|
|
72
|
+
function entry() { return " " target ": { enabled: " enabled ", output: \"" output "\" }" }
|
|
73
|
+
function add_enabled_to_inline(line, p) {
|
|
74
|
+
p = index(line, "{")
|
|
75
|
+
if (p > 0) return substr(line, 1, p) " enabled: " enabled "," substr(line, p + 1)
|
|
76
|
+
return line
|
|
77
|
+
}
|
|
78
|
+
function flush_target() {
|
|
79
|
+
if (in_target && !enabled_seen) print " enabled: " enabled
|
|
80
|
+
in_target = 0
|
|
81
|
+
}
|
|
82
|
+
function flush_targets() {
|
|
83
|
+
flush_target()
|
|
84
|
+
if (in_targets && !target_seen) { print entry(); target_seen = 1 }
|
|
85
|
+
in_targets = 0
|
|
86
|
+
}
|
|
87
|
+
{ sub(/\r$/, "") }
|
|
88
|
+
/^targets:[[:space:]]*$/ { targets_seen = 1; in_targets = 1; print; next }
|
|
89
|
+
in_targets && /^[A-Za-z]/ { flush_targets() }
|
|
90
|
+
in_targets && /^ [A-Za-z]/ {
|
|
91
|
+
flush_target()
|
|
92
|
+
if ($0 ~ "^ " target ":[[:space:]]*") {
|
|
93
|
+
target_seen = 1
|
|
94
|
+
if (match($0, /enabled:[[:space:]]*(true|false)/)) {
|
|
95
|
+
print substr($0, 1, RSTART - 1) "enabled: " enabled substr($0, RSTART + RLENGTH)
|
|
96
|
+
enabled_seen = 1
|
|
97
|
+
} else if ($0 ~ /\{/) {
|
|
98
|
+
print add_enabled_to_inline($0)
|
|
99
|
+
enabled_seen = 1
|
|
100
|
+
} else {
|
|
101
|
+
print
|
|
102
|
+
in_target = 1
|
|
103
|
+
enabled_seen = 0
|
|
104
|
+
}
|
|
105
|
+
next
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
in_target && /^ enabled:[[:space:]]*/ {
|
|
109
|
+
print " enabled: " enabled
|
|
110
|
+
enabled_seen = 1
|
|
111
|
+
next
|
|
112
|
+
}
|
|
113
|
+
{ last = $0; print }
|
|
114
|
+
END {
|
|
115
|
+
flush_targets()
|
|
116
|
+
if (!targets_seen) {
|
|
117
|
+
if (last != "") print ""
|
|
118
|
+
print "targets:"
|
|
119
|
+
print entry()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
'
|
|
123
|
+
}
|
|
124
|
+
|
|
39
125
|
# qmap_keys <file> <block> — quoted keys, one per line.
|
|
40
126
|
qmap_keys() {
|
|
41
127
|
[ -f "$1" ] || return 0
|
|
@@ -215,6 +301,29 @@ qmap_delete_key() {
|
|
|
215
301
|
'
|
|
216
302
|
}
|
|
217
303
|
|
|
304
|
+
# qmap_delete_field <file> <block> <key> <field> — drop one field from a
|
|
305
|
+
# quoted-key entry without disturbing its other fields or surrounding YAML.
|
|
306
|
+
qmap_delete_field() {
|
|
307
|
+
local file="$1" block="$2" key="$3" field="$4"
|
|
308
|
+
[ -f "$file" ] || return 0
|
|
309
|
+
_qmap_stage "$file" -v block="$block" -v key="$key" -v field="$field" '
|
|
310
|
+
{ sub(/\r$/, "") }
|
|
311
|
+
$0 ~ "^" block ":[ \t]*$" { inb = 1; print; next }
|
|
312
|
+
inb && /^[^ #]/ { inb = 0; ink = 0 }
|
|
313
|
+
inb && /^ "/ {
|
|
314
|
+
s = substr($0, 4); q = index(s, "\"")
|
|
315
|
+
ink = (q > 0 && substr(s, 1, q - 1) == key)
|
|
316
|
+
print; next
|
|
317
|
+
}
|
|
318
|
+
inb && ink && /^ [A-Za-z_]/ {
|
|
319
|
+
line = $0; sub(/^ /, "", line)
|
|
320
|
+
c = index(line, ":")
|
|
321
|
+
if (c > 0 && substr(line, 1, c - 1) == field) next
|
|
322
|
+
}
|
|
323
|
+
{ print }
|
|
324
|
+
'
|
|
325
|
+
}
|
|
326
|
+
|
|
218
327
|
# --- registries: a trust LIST, not a scope map -----------------------------
|
|
219
328
|
# The block holds registry repo URLs in trust order. Two shapes are read so
|
|
220
329
|
# early manifests keep working: the list form (`- "url"`) and the retired
|
package/cli/lib/registry.sh
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# visible, reviewable, deletable.
|
|
12
12
|
#
|
|
13
13
|
# The NAME is the trust anchor a developer reasons with; source integrity is
|
|
14
|
-
# a separate mechanism: the lock pins url+sha, and
|
|
14
|
+
# a separate mechanism: the lock pins url+sha, and `status --check` flags
|
|
15
15
|
# resolution/lock url drift.
|
|
16
16
|
#
|
|
17
17
|
# An index is itself fetched with git (never curl): auth, proxies and private
|
|
@@ -93,7 +93,7 @@ suggest_similar() {
|
|
|
93
93
|
cshort="${cand#*/}"; cshort="${cshort%s}"
|
|
94
94
|
if [ "$cshort" = "$want" ] && [ "$cand" != "$name" ]; then
|
|
95
95
|
seen="$seen$cand "
|
|
96
|
-
echo " Did you mean: intelligence add $cand" >&2
|
|
96
|
+
echo " Did you mean: intelligence package add $cand" >&2
|
|
97
97
|
fi
|
|
98
98
|
done < <(qmap_keys "$idx" "packages")
|
|
99
99
|
}
|
|
@@ -116,8 +116,8 @@ fetch_package() {
|
|
|
116
116
|
assert_safe_source_url "$url"
|
|
117
117
|
assert_safe_ref "$ref"
|
|
118
118
|
# Bundle seed: the engine-content package at the CLI's own version copies
|
|
119
|
-
# from the npm bundle — no network, which keeps init
|
|
120
|
-
#
|
|
119
|
+
# from the npm bundle — no network, which keeps init, sync restoration and
|
|
120
|
+
# archived-project conversion offline. Keyed on the
|
|
121
121
|
# full (url, path, ref) triple; any other version or source falls through
|
|
122
122
|
# to the normal clone.
|
|
123
123
|
if [ "$url" = "$SYNC_PKG_URL" ] && [ "$subpath" = "$SYNC_PKG_PATH" ] \
|
package/engine/ENGINE_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
af238e67e8a9469edf6d2058e169de52cbeb93af
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
2
|
+
# Intelligence CLI: project adapter template
|
|
3
|
+
# Scaffold this file with: intelligence adapter create <name>
|
|
4
4
|
#
|
|
5
5
|
# This file is NOT executable as-is — `<name>` placeholders below would be
|
|
6
6
|
# parsed by bash as input redirection (`<` operator). Replace every
|
|
7
7
|
# occurrence with your adapter name before sourcing.
|
|
8
8
|
#
|
|
9
9
|
# Required:
|
|
10
|
-
# 1.
|
|
11
|
-
# 2.
|
|
12
|
-
#
|
|
13
|
-
# 4. Add target to config.yaml:
|
|
10
|
+
# 1. Implement sync_to_<name>() and its three content transforms
|
|
11
|
+
# 2. Enable it after implementation: intelligence adapter enable <name>
|
|
12
|
+
# This adds the target to intelligence.yaml when it is absent:
|
|
14
13
|
# targets:
|
|
15
14
|
# <name>: { enabled: true, output: ".<name>" }
|
|
16
15
|
#
|
|
17
16
|
# The sync_to_<name>() function receives:
|
|
18
17
|
# $1 = repo_root — absolute path to the project root
|
|
19
|
-
# $2 = config_file — absolute path to
|
|
18
|
+
# $2 = config_file — absolute path to intelligence.yaml
|
|
20
19
|
# $3 = output_dir — absolute path to the output directory (e.g., .myide/)
|
|
21
20
|
#
|
|
22
21
|
# Available library functions (from lib/common.sh):
|
|
@@ -29,10 +28,12 @@
|
|
|
29
28
|
# get_model_default(ide, tier) — hardcoded default for ide:tier
|
|
30
29
|
# map_access_to_claude_tools(access) — full->"" (no tools list; inherits all), readonly->restricted
|
|
31
30
|
# map_access_to_claude_disallowed(access) — readonly->"Write, Edit", full->""
|
|
32
|
-
# read_yaml_list(
|
|
31
|
+
# read_yaml_list(manifest, section) — read a source list from intelligence.yaml
|
|
33
32
|
# get_target_field(config, target, field) — read a target's config field
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
# Project adapters are sourced by engine/sync.sh after the shared library is
|
|
35
|
+
# loaded. Keep this template position-independent: after scaffolding it lives
|
|
36
|
+
# under the project's content directory, not beside engine/lib/.
|
|
36
37
|
|
|
37
38
|
# Sync rules for <agent-name>
|
|
38
39
|
# Typical transformations:
|
|
@@ -21,8 +21,8 @@ source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
|
21
21
|
# one document Cursor, Copilot and Codex all read as canonical.
|
|
22
22
|
# The defaults only cover an adapter driven directly (tests); sync.sh always
|
|
23
23
|
# exports both.
|
|
24
|
-
|
|
25
|
-
printf '%s' "${
|
|
24
|
+
agents_md_content_rel() {
|
|
25
|
+
printf '%s' "${IS_CONTENT_REL:-intelligence}"
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
agents_md_module_rel() {
|
|
@@ -196,14 +196,14 @@ agents_md_append_rules_list() {
|
|
|
196
196
|
# Path-scoped rules stay in tool-specific channels (.cursor/rules/,
|
|
197
197
|
# .github/instructions/) so monorepo scoping is preserved.
|
|
198
198
|
if [ "${#global_rule_files[@]}" -gt 0 ]; then
|
|
199
|
-
local
|
|
200
|
-
|
|
199
|
+
local content_rel
|
|
200
|
+
content_rel="$(agents_md_content_rel "$repo_root")"
|
|
201
201
|
{
|
|
202
202
|
echo "---"
|
|
203
203
|
echo ""
|
|
204
204
|
echo "## Project Context"
|
|
205
205
|
echo ""
|
|
206
|
-
echo "<!-- Inlined from always-on rules in $
|
|
206
|
+
echo "<!-- Inlined from always-on rules in $content_rel/rules/ -->"
|
|
207
207
|
echo ""
|
|
208
208
|
} >> "$output"
|
|
209
209
|
local rf
|
|
@@ -240,17 +240,17 @@ sync_to_agents() {
|
|
|
240
240
|
|
|
241
241
|
mkdir -p "$(dirname "$output_file")"
|
|
242
242
|
|
|
243
|
-
local
|
|
244
|
-
|
|
243
|
+
local content_rel module_rel
|
|
244
|
+
content_rel="$(agents_md_content_rel "$repo_root")"
|
|
245
245
|
module_rel="$(agents_md_module_rel "$repo_root")"
|
|
246
246
|
|
|
247
247
|
# These two strings are written into a COMMITTED file. An absolute path here
|
|
248
248
|
# means the derivation failed, and a machine-specific path in AGENTS.md is
|
|
249
249
|
# worse than a failed sync — fail loudly instead.
|
|
250
|
-
case "$
|
|
250
|
+
case "$content_rel$module_rel" in
|
|
251
251
|
/*|*:[\\/]*)
|
|
252
252
|
echo " ERROR: AGENTS.md paths did not resolve relative to the repo root:" >&2
|
|
253
|
-
echo "
|
|
253
|
+
echo " content='$content_rel' module='$module_rel' repo_root='$repo_root'" >&2
|
|
254
254
|
return 1
|
|
255
255
|
;;
|
|
256
256
|
esac
|
|
@@ -261,7 +261,7 @@ sync_to_agents() {
|
|
|
261
261
|
|
|
262
262
|
{
|
|
263
263
|
echo "<!-- Generated by intelligence-sync. Do not edit manually. -->"
|
|
264
|
-
echo "<!-- Source: $
|
|
264
|
+
echo "<!-- Source: $content_rel/ | Sync: $sync_cmd -->"
|
|
265
265
|
echo ""
|
|
266
266
|
} > "$output_file"
|
|
267
267
|
|
|
@@ -270,7 +270,7 @@ sync_to_agents() {
|
|
|
270
270
|
{
|
|
271
271
|
echo "## Intelligence"
|
|
272
272
|
echo ""
|
|
273
|
-
echo "Source of truth: \`$
|
|
273
|
+
echo "Source of truth: \`$content_rel/\` | Sync: \`$sync_cmd\`"
|
|
274
274
|
echo ""
|
|
275
275
|
} >> "$output_file"
|
|
276
276
|
|
|
@@ -29,7 +29,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
|
29
29
|
# Strip frontmatter, return body only.
|
|
30
30
|
# Direct call to the shared `strip_frontmatter` helper in lib/common.sh —
|
|
31
31
|
# duplicating the awk block here would violate the "all parsing in common.sh"
|
|
32
|
-
# convention (see CLAUDE.md /
|
|
32
|
+
# convention (see CLAUDE.md / packages/sync/references/conventions.md).
|
|
33
33
|
|
|
34
34
|
sync_opencode_skills() {
|
|
35
35
|
local repo_root="$1"
|
package/engine/lib/common.sh
CHANGED
|
@@ -16,16 +16,14 @@ normalize_file_to_lf() {
|
|
|
16
16
|
|
|
17
17
|
# --- Layout tokens -----------------------------------------------------------
|
|
18
18
|
#
|
|
19
|
-
# The
|
|
20
|
-
# a codename)
|
|
21
|
-
#
|
|
22
|
-
# say "the umbrella", and an agent body needs to name the sync command. Both are
|
|
23
|
-
# spelled with tokens, expanded here at output time:
|
|
19
|
+
# The content directory is named by the project (`intelligence/`,
|
|
20
|
+
# `Intelligence/`, a codename), so package-shipped artifacts cannot hardcode
|
|
21
|
+
# it. Layout-sensitive paths and commands use tokens expanded at output time:
|
|
24
22
|
#
|
|
25
|
-
# <
|
|
26
|
-
# <module>
|
|
23
|
+
# <content-dir> -> the repo-relative project content dir (e.g. `Intelligence`)
|
|
24
|
+
# <module> -> the repo-relative sync package store path
|
|
27
25
|
#
|
|
28
|
-
# Values come from
|
|
26
|
+
# Values come from IS_CONTENT_REL / IS_MODULE_REL, which the CLI derives from
|
|
29
27
|
# the detected layout (never hardcoded) and exports before any adapter runs.
|
|
30
28
|
# Expansion happens in EVERY generated file, frontmatter and body alike, so a
|
|
31
29
|
# scoped rule reaches Claude's `paths:`, Cursor's `globs:` and Copilot's
|
|
@@ -34,24 +32,18 @@ normalize_file_to_lf() {
|
|
|
34
32
|
# finalize_output_file <file>
|
|
35
33
|
# The single exit gate for every file an adapter writes: expand layout tokens,
|
|
36
34
|
# then normalize CRLF -> LF. Adapters MUST call this (not normalize_file_to_lf)
|
|
37
|
-
# on each output — a missed call ships a literal `<
|
|
35
|
+
# on each output — a missed call ships a literal `<content-dir>` into an IDE.
|
|
38
36
|
finalize_output_file() {
|
|
39
37
|
local target="$1"
|
|
40
|
-
local
|
|
41
|
-
local mod="${IS_MODULE_REL
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# exact pre-token string), the CLI overrides it via IS_SYNC_CMD
|
|
45
|
-
# (`intelligence sync`). `<manifest>` names the project's config file the
|
|
46
|
-
# same way: `config.yaml` vendored, `intelligence.yaml` under the CLI
|
|
47
|
-
# (IS_MANIFEST_NAME).
|
|
48
|
-
local sc="${IS_SYNC_CMD:-bash $mod/scripts/sync.sh}"
|
|
49
|
-
local mf="${IS_MANIFEST_NAME:-config.yaml}"
|
|
38
|
+
local content="${IS_CONTENT_REL:-intelligence}"
|
|
39
|
+
local mod="${IS_MODULE_REL:-.intelligence/packages/@ainova-systems/sync}"
|
|
40
|
+
local sc="${IS_SYNC_CMD:-intelligence sync}"
|
|
41
|
+
local mf="${IS_MANIFEST_NAME:-intelligence.yaml}"
|
|
50
42
|
local tmp_file="$target.tmp"
|
|
51
43
|
# Literal (index-based) substitution, not gsub: a regex replacement would
|
|
52
44
|
# give `&` in a path its special meaning, and POSIX awk has no way to pass a
|
|
53
45
|
# replacement string verbatim.
|
|
54
|
-
awk -v
|
|
46
|
+
awk -v content="$content" -v mod="$mod" -v sc="$sc" -v mf="$mf" '
|
|
55
47
|
function repl(s, from, to, out, i) {
|
|
56
48
|
out = ""
|
|
57
49
|
while ((i = index(s, from)) > 0) {
|
|
@@ -65,7 +57,7 @@ finalize_output_file() {
|
|
|
65
57
|
$0 = repl($0, "<sync-cmd>", sc)
|
|
66
58
|
$0 = repl($0, "<manifest>", mf)
|
|
67
59
|
$0 = repl($0, "<module>", mod)
|
|
68
|
-
$0 = repl($0, "<
|
|
60
|
+
$0 = repl($0, "<content-dir>", content)
|
|
69
61
|
print
|
|
70
62
|
}
|
|
71
63
|
' "$target" > "$tmp_file"
|
|
@@ -798,7 +790,7 @@ warn_unsynced() {
|
|
|
798
790
|
# from its location — it comes from the env contract the CLI exports.
|
|
799
791
|
local intel_basename
|
|
800
792
|
if [ "${IS_CLI:-0}" = "1" ]; then
|
|
801
|
-
intel_basename="$(basename "${
|
|
793
|
+
intel_basename="$(basename "${IS_CONTENT_REL:-intelligence}")"
|
|
802
794
|
else
|
|
803
795
|
intel_basename="$(basename "$(dirname "$config_file")")"
|
|
804
796
|
fi
|
package/engine/lib/contract.sh
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# shellcheck disable=SC2034 # IS_RC_*/
|
|
2
|
+
# shellcheck disable=SC2034 # IS_RC_*/IS_SCHEMA_VERSION_KEY are the public bash<->CLI contract, consumed by the scripts that source this lib
|
|
3
3
|
# intelligence-sync: the version and status contract.
|
|
4
4
|
# Source this file — never execute directly.
|
|
5
5
|
#
|
|
6
6
|
# Two things live here, and both are contracts other programs depend on:
|
|
7
|
-
# * the applied-schema stamp (`
|
|
7
|
+
# * the applied-schema stamp (`schema_version` in the manifest) with its
|
|
8
8
|
# read / write / compare helpers, and
|
|
9
9
|
# * the IS_STATUS / IS_RC_* codes every engine flow reports.
|
|
10
10
|
#
|
|
11
|
-
# Schema migrations themselves are NOT here: the CLI owns them
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# Schema migrations themselves are NOT here: the CLI owns them behind
|
|
12
|
+
# `intelligence init`, and a v1 project is brought forward by the archived v1
|
|
13
|
+
# engine before `intelligence init` converts it.
|
|
14
14
|
|
|
15
15
|
# The applied-schema version is a managed key in the manifest.
|
|
16
16
|
#
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
# Anything else in the manifest may be reshaped; the name, location and shape of
|
|
19
19
|
# this key never are - so any engine, however old or new, can always read "what
|
|
20
20
|
# schema is this?" before parsing the rest.
|
|
21
|
-
|
|
21
|
+
IS_SCHEMA_VERSION_KEY="schema_version"
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
|
|
23
|
+
# read_schema_version <config_file> → applied version, or "" if absent.
|
|
24
|
+
read_schema_version() {
|
|
25
25
|
local cf="$1"
|
|
26
26
|
[ -f "$cf" ] || return 0
|
|
27
|
-
awk -v k="$
|
|
27
|
+
awk -v k="$IS_SCHEMA_VERSION_KEY" '
|
|
28
28
|
{ sub(/\r$/, "") }
|
|
29
29
|
$0 ~ "^" k ":" {
|
|
30
30
|
v = $0; sub(/^[^:]*:[[:space:]]*/, "", v)
|
|
@@ -35,14 +35,14 @@ read_engine_stamp() {
|
|
|
35
35
|
' "$cf"
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
#
|
|
38
|
+
# stamp_schema_version <config_file> <version> — idempotent, transactional upsert of
|
|
39
39
|
# the contract key (replace in place if present, else append at top level).
|
|
40
40
|
# No-op if config.yaml does not exist yet (pre-bootstrap).
|
|
41
|
-
|
|
41
|
+
stamp_schema_version() {
|
|
42
42
|
local cf="$1" ver="$2"
|
|
43
43
|
[ -f "$cf" ] || return 0
|
|
44
44
|
local tmp="$cf.ver.tmp"
|
|
45
|
-
awk -v k="$
|
|
45
|
+
awk -v k="$IS_SCHEMA_VERSION_KEY" -v val="$ver" '
|
|
46
46
|
{ sub(/\r$/, "") }
|
|
47
47
|
$0 ~ "^" k ":" { print k ": \"" val "\""; found=1; next }
|
|
48
48
|
{ print }
|
|
@@ -62,7 +62,7 @@ IS_RC_CONFIG_MISSING=2 # no manifest found
|
|
|
62
62
|
IS_RC_AMBIGUOUS=3 # conflicting state; agent/human-only — bash never emits this itself
|
|
63
63
|
IS_RC_AHEAD=4 # project stamped newer than this engine understands
|
|
64
64
|
IS_RC_ABORTED_INCOMPLETE=5 # staged state incomplete; the project was left untouched
|
|
65
|
-
IS_RC_NEEDS_UPDATE=6 # pending schema changes (stamp < engine) — run `intelligence
|
|
65
|
+
IS_RC_NEEDS_UPDATE=6 # pending schema changes (stamp < engine) — run `intelligence init --apply`
|
|
66
66
|
|
|
67
67
|
# is_status <code-name> [detail] — emit one parseable line for the skill.
|
|
68
68
|
is_status() {
|
|
@@ -106,7 +106,7 @@ _ver_gt() {
|
|
|
106
106
|
# conflict, else 0.
|
|
107
107
|
check_version_compat() {
|
|
108
108
|
local cf="$1" stamp eng
|
|
109
|
-
stamp="$(
|
|
109
|
+
stamp="$(read_schema_version "$cf")"
|
|
110
110
|
[ -n "$stamp" ] || return 0
|
|
111
111
|
eng="$(engine_version)"
|
|
112
112
|
[ -n "$eng" ] || return 0
|