@ainova-systems/intelligence 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/init.sh +2 -1
- package/cli/commands/registry.sh +1 -1
- package/cli/commands/source.sh +1 -1
- package/cli/commands/sync.sh +78 -6
- package/cli/intelligence +15 -2
- package/cli/internal/check.sh +39 -0
- package/cli/internal/convert-legacy.sh +1 -1
- package/cli/internal/package-search.sh +1 -1
- package/cli/internal/package-update.sh +2 -2
- package/cli/internal/restore.sh +3 -2
- package/cli/lib/cli-common.sh +145 -33
- package/cli/lib/gitignore.sh +19 -2
- package/cli/lib/lockfile.sh +14 -4
- package/cli/lib/manifest.sh +147 -1
- package/cli/lib/qmap.awk +97 -32
- package/cli/lib/registry.sh +1 -1
- package/cli/lib/sync-cache.sh +596 -142
- package/cli/lib/sync-lock.sh +19 -2
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/engine/adapters/_template.sh +32 -8
- package/engine/adapters/agents.sh +47 -52
- package/engine/adapters/antigravity.sh +71 -53
- package/engine/adapters/claude.sh +42 -57
- package/engine/adapters/codex.sh +77 -16
- package/engine/adapters/copilot.sh +54 -65
- package/engine/adapters/cursor.sh +88 -78
- package/engine/adapters/opencode.sh +23 -32
- package/engine/adapters/pi.sh +140 -42
- package/engine/lib/adapter-contract.sh +36 -4
- package/engine/lib/common.sh +1388 -165
- package/engine/lib/contract.sh +5 -0
- package/engine/sync.sh +145 -38
- package/package.json +1 -1
- package/packages/sync/references/adapters.md +48 -38
- package/packages/sync/references/conventions.md +61 -12
- package/packages/sync/references/onboarding-migration.md +4 -4
- package/packages/sync/rules/intelligence-authoring.md +2 -2
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +7 -5
- package/packages/sync/skills/intelligence-review-context/references/audit-checks.md +1 -1
- package/packages/sync/skills/intelligence-update-context/references/rules.md +5 -0
package/cli/lib/gitignore.sh
CHANGED
|
@@ -312,10 +312,26 @@ ensure_gitignore_header() {
|
|
|
312
312
|
fi
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
# local_artifact_ignore_patterns <content-dir> — the patterns that make a rule,
|
|
316
|
+
# agent or skill local (decision 0021): a file or skill directory with one of
|
|
317
|
+
# these names never reaches committed output, and Git never commits it. The
|
|
318
|
+
# engine recognizes the same names, so the two cannot disagree; `init` writes
|
|
319
|
+
# them and `status --check` reports one missing.
|
|
320
|
+
local_artifact_ignore_patterns() {
|
|
321
|
+
local dir="${1#./}"
|
|
322
|
+
while [ "${dir%/}" != "$dir" ]; do dir="${dir%/}"; done
|
|
323
|
+
printf '%s\n' "$dir/rules/*.local.md" "$dir/agents/*.local.md" "$dir/skills/*.local/"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# ensure_base_gitignore <root> <content-dir> — the package store and the local
|
|
327
|
+
# artifact patterns of the project's content directory.
|
|
315
328
|
ensure_base_gitignore() {
|
|
316
|
-
local root="$1"
|
|
329
|
+
local root="$1" content_dir="$2" line
|
|
317
330
|
ensure_gitignore_header "$root"
|
|
318
331
|
gitignore_add_line "$root" '.intelligence/'
|
|
332
|
+
while IFS= read -r line; do
|
|
333
|
+
gitignore_add_line "$root" "$line"
|
|
334
|
+
done < <(local_artifact_ignore_patterns "$content_dir")
|
|
319
335
|
}
|
|
320
336
|
|
|
321
337
|
ensure_target_gitignore() {
|
|
@@ -343,7 +359,7 @@ ensure_target_gitignore() {
|
|
|
343
359
|
|
|
344
360
|
ensure_manifest_gitignore() {
|
|
345
361
|
local root="$1" manifest="$2" target
|
|
346
|
-
ensure_base_gitignore "$root"
|
|
362
|
+
ensure_base_gitignore "$root" "$(manifest_intelligence_dir "$manifest")"
|
|
347
363
|
while IFS= read -r target; do
|
|
348
364
|
[ -n "$target" ] || continue
|
|
349
365
|
if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
|
|
@@ -420,6 +436,7 @@ managed_gitignore_patterns() {
|
|
|
420
436
|
local root="$1" manifest="$2" content_dir target output records kind value
|
|
421
437
|
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
422
438
|
printf '%s\n' '.intelligence/' "$content_dir/_backup/"
|
|
439
|
+
local_artifact_ignore_patterns "$content_dir"
|
|
423
440
|
while IFS= read -r target; do
|
|
424
441
|
[ -n "$target" ] || continue
|
|
425
442
|
[ "$(is_target_enabled "$manifest" "$target")" = "1" ] || continue
|
package/cli/lib/lockfile.sh
CHANGED
|
@@ -13,6 +13,12 @@ LOCK_SEP=$'\x1f'
|
|
|
13
13
|
# lock_validate <lock> [--metadata|--restore] [manifest] — read-only preflight. A subshell
|
|
14
14
|
# contains the fail-fast input guards so status can report a failed verdict too.
|
|
15
15
|
lock_validate() (
|
|
16
|
+
lock_validate_checks "$@"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# lock_validate_checks — lock_validate without its subshell, for a caller that
|
|
20
|
+
# already runs it in one (a capturing `$(...)`): the guards exit through `die`.
|
|
21
|
+
lock_validate_checks() {
|
|
16
22
|
local lock="$1" mode="${2---metadata}" manifest="${3:-}" count=0
|
|
17
23
|
local kind name requested url path resolved sha line rows current
|
|
18
24
|
local -x CLI_INPUT_CONTEXT=""
|
|
@@ -23,7 +29,8 @@ lock_validate() (
|
|
|
23
29
|
esac
|
|
24
30
|
# The same tokenizer supplies ordinary lock rows and strict metadata. Buffer
|
|
25
31
|
# before consumption: a failed parser must never yield a partial package set.
|
|
26
|
-
|
|
32
|
+
_qmap_read_var lock "$lock" packages '' '' "$LOCKFILE_VERSION" || return $?
|
|
33
|
+
rows="$IS_QMAP_OUT"
|
|
27
34
|
bundled_engine_version_var
|
|
28
35
|
current="$IS_BUNDLED_ENGINE_VERSION"
|
|
29
36
|
while IFS="$LOCK_SEP" read -r kind name requested url path resolved sha line; do
|
|
@@ -66,10 +73,12 @@ lock_validate() (
|
|
|
66
73
|
[[ "$sha" =~ ^[0-9a-f]{40}$ || "$sha" =~ ^[0-9a-f]{64}$ ]] \
|
|
67
74
|
|| die "locked restore: $name has a missing or invalid commit SHA — restore a valid intelligence.lock"
|
|
68
75
|
done <<< "$rows"
|
|
69
|
-
if [ "$count" -eq 0 ] && [ -n "$manifest" ]
|
|
70
|
-
|
|
76
|
+
if [ "$count" -eq 0 ] && [ -n "$manifest" ]; then
|
|
77
|
+
_qmap_read_var keys "$manifest" packages
|
|
78
|
+
[ -z "$IS_QMAP_OUT" ] \
|
|
79
|
+
|| die "$lock: packages is empty but the manifest declares packages — restore their recorded identities"
|
|
71
80
|
fi
|
|
72
|
-
|
|
81
|
+
}
|
|
73
82
|
|
|
74
83
|
# top_scalar <file> <key> — one top-level scalar through the shared tokenizer.
|
|
75
84
|
top_scalar() {
|
|
@@ -86,6 +95,7 @@ lock_to_tsv() {
|
|
|
86
95
|
lock_write_from_tsv() {
|
|
87
96
|
local lock="$1" tsv="$2"
|
|
88
97
|
local tmp="$lock.cli.tmp"
|
|
98
|
+
qmap_memo_reset
|
|
89
99
|
{
|
|
90
100
|
echo "# Generated by the intelligence CLI. Do not edit."
|
|
91
101
|
echo "lockfile_version: $LOCKFILE_VERSION"
|
package/cli/lib/manifest.sh
CHANGED
|
@@ -156,10 +156,20 @@ target_set_enabled() {
|
|
|
156
156
|
'
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
_QMAP_AWK="${BASH_SOURCE[0]%/*}/qmap.awk"
|
|
160
|
+
|
|
159
161
|
# _qmap_read <mode> <file> <block> [key] [field] [expected-lock-version]
|
|
160
162
|
# All scalar reads and strict lock records share one tokenizer. Environment
|
|
161
163
|
# transport preserves literal backslashes that awk -v would interpret again.
|
|
162
164
|
_qmap_read() {
|
|
165
|
+
if _qmap_memo_find "$@"; then
|
|
166
|
+
_qmap_memo_replay
|
|
167
|
+
return
|
|
168
|
+
fi
|
|
169
|
+
_qmap_read_file "$@"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_qmap_read_file() {
|
|
163
173
|
local mode="$1" file="$2"
|
|
164
174
|
if [ ! -f "$file" ] || [ ! -r "$file" ]; then
|
|
165
175
|
case "$mode" in
|
|
@@ -169,7 +179,140 @@ _qmap_read() {
|
|
|
169
179
|
fi
|
|
170
180
|
QMAP_MODE="$mode" QMAP_FILE="$file" QMAP_BLOCK="${3:-}" \
|
|
171
181
|
QMAP_KEY="${4:-}" QMAP_FIELD="${5:-}" QMAP_EXPECTED_VERSION="${6:-}" \
|
|
172
|
-
LC_ALL=C awk -f "$
|
|
182
|
+
LC_ALL=C awk -f "$_QMAP_AWK" < "$file"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# _qmap_read_var <mode> <file> <block> [key] [field] [expected-lock-version]
|
|
186
|
+
# — _qmap_read into IS_QMAP_OUT, as `$(_qmap_read ...)` would capture it, and
|
|
187
|
+
# with its status. A preloaded answer costs no process: on Git Bash every
|
|
188
|
+
# `$(...)` forks, which is what made a lifecycle preflight take half a second.
|
|
189
|
+
# shellcheck disable=SC2034 # IS_QMAP_OUT is the return channel
|
|
190
|
+
_qmap_read_var() {
|
|
191
|
+
if _qmap_memo_find "$@"; then
|
|
192
|
+
IS_QMAP_OUT="${QMAP_MEMO_OUTS[$IS_QMAP_MEMO_AT]}"
|
|
193
|
+
while [[ "$IS_QMAP_OUT" == *$'\n' ]]; do IS_QMAP_OUT="${IS_QMAP_OUT%$'\n'}"; done
|
|
194
|
+
if [ "${QMAP_MEMO_RCS[$IS_QMAP_MEMO_AT]}" -ne 0 ]; then
|
|
195
|
+
printf '%s' "${QMAP_MEMO_OUTS[$IS_QMAP_MEMO_AT]}" >&2
|
|
196
|
+
IS_QMAP_OUT=""
|
|
197
|
+
fi
|
|
198
|
+
return "${QMAP_MEMO_RCS[$IS_QMAP_MEMO_AT]}"
|
|
199
|
+
fi
|
|
200
|
+
IS_QMAP_OUT="$(_qmap_read_file "$@")"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# --- Reads answered once per process ------------------------------------
|
|
204
|
+
# qmap_preload runs one awk over several requests and remembers each answer —
|
|
205
|
+
# output or strict error, and status — under the exact request it answers.
|
|
206
|
+
# Only a command that holds the project lock may preload, and anything that
|
|
207
|
+
# writes a qmap document forgets every answer (qmap_memo_reset): a remembered
|
|
208
|
+
# read must never outlive the bytes it was read from.
|
|
209
|
+
QMAP_MEMO_KEYS=()
|
|
210
|
+
QMAP_MEMO_RCS=()
|
|
211
|
+
QMAP_MEMO_OUTS=()
|
|
212
|
+
|
|
213
|
+
qmap_memo_reset() {
|
|
214
|
+
QMAP_MEMO_KEYS=()
|
|
215
|
+
QMAP_MEMO_RCS=()
|
|
216
|
+
QMAP_MEMO_OUTS=()
|
|
217
|
+
if declare -F manifest_view_reset >/dev/null; then manifest_view_reset; fi
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# _qmap_memo_find <request...> — set IS_QMAP_MEMO_AT to the remembered answer.
|
|
221
|
+
_qmap_memo_find() {
|
|
222
|
+
local request="${1:-}"$'\037'"${2:-}"$'\037'"${3:-}"$'\037'"${4:-}"$'\037'"${5:-}"$'\037'"${6:-}" i=0
|
|
223
|
+
while [ "$i" -lt "${#QMAP_MEMO_KEYS[@]}" ]; do
|
|
224
|
+
if [ "${QMAP_MEMO_KEYS[$i]}" = "$request" ]; then
|
|
225
|
+
IS_QMAP_MEMO_AT="$i"
|
|
226
|
+
return 0
|
|
227
|
+
fi
|
|
228
|
+
i=$((i + 1))
|
|
229
|
+
done
|
|
230
|
+
return 1
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Print a remembered answer exactly as the awk reader printed it.
|
|
234
|
+
_qmap_memo_replay() {
|
|
235
|
+
if [ "${QMAP_MEMO_RCS[$IS_QMAP_MEMO_AT]}" -ne 0 ]; then
|
|
236
|
+
printf '%s' "${QMAP_MEMO_OUTS[$IS_QMAP_MEMO_AT]}" >&2
|
|
237
|
+
return "${QMAP_MEMO_RCS[$IS_QMAP_MEMO_AT]}"
|
|
238
|
+
fi
|
|
239
|
+
printf '%s' "${QMAP_MEMO_OUTS[$IS_QMAP_MEMO_AT]}"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# qmap_preload <request>... — each request is one string of _qmap_read's six
|
|
243
|
+
# arguments, unit-separated. Requests for absent or empty documents are left
|
|
244
|
+
# to the ordinary reader, which reports them exactly as before. A preload that
|
|
245
|
+
# cannot run leaves nothing remembered; reads then take the ordinary path.
|
|
246
|
+
# A caller that wants the awk pass to overlap other work uses the three steps
|
|
247
|
+
# itself: qmap_preload_plan, qmap_preload_run (prints), qmap_preload_store.
|
|
248
|
+
qmap_preload() {
|
|
249
|
+
local out
|
|
250
|
+
qmap_preload_plan "$@" || return 0
|
|
251
|
+
out="$(qmap_preload_run)" || return 0
|
|
252
|
+
qmap_preload_store "$out"
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# qmap_preload_plan <request>... — QMAP_PLAN_REQUESTS, _FILES and _JOBS for the
|
|
256
|
+
# requests one awk pass can answer; returns 1 when there are none.
|
|
257
|
+
qmap_preload_plan() {
|
|
258
|
+
local request saved_ifs="$IFS"
|
|
259
|
+
local -a fields=()
|
|
260
|
+
QMAP_PLAN_REQUESTS=() QMAP_PLAN_FILES=() QMAP_PLAN_JOBS=""
|
|
261
|
+
for request in "$@"; do
|
|
262
|
+
IFS=$'\037'
|
|
263
|
+
set -f
|
|
264
|
+
# shellcheck disable=SC2206 # splitting on the unit separator is the point
|
|
265
|
+
fields=($request)
|
|
266
|
+
set +f
|
|
267
|
+
IFS="$saved_ifs"
|
|
268
|
+
case "${fields[1]:-}" in /*) ;; *) continue ;; esac
|
|
269
|
+
[ -f "${fields[1]}" ] && [ -r "${fields[1]}" ] && [ -s "${fields[1]}" ] || continue
|
|
270
|
+
QMAP_PLAN_FILES+=("${fields[1]}")
|
|
271
|
+
QMAP_PLAN_REQUESTS+=("$request")
|
|
272
|
+
QMAP_PLAN_JOBS="$QMAP_PLAN_JOBS${fields[0]:-}"$'\037'"${fields[2]:-}"$'\037'"${fields[3]:-}"$'\037'"${fields[4]:-}"$'\037'"${fields[5]:-}"$'\n'
|
|
273
|
+
done
|
|
274
|
+
[ "${#QMAP_PLAN_FILES[@]}" -gt 0 ]
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# qmap_preload_run — the planned pass; a trailing `end` proves it completed.
|
|
278
|
+
qmap_preload_run() {
|
|
279
|
+
QMAP_JOBS="${QMAP_PLAN_JOBS%$'\n'}" LC_ALL=C awk -f "$_QMAP_AWK" "${QMAP_PLAN_FILES[@]}" && echo end
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# qmap_preload_store <output> — remember every planned answer, or nothing when
|
|
283
|
+
# the output is not exactly one complete answer per request.
|
|
284
|
+
qmap_preload_store() {
|
|
285
|
+
local header line status count raw i=0 saved_ifs="$IFS"
|
|
286
|
+
local -a lines=() rcs=() outs=()
|
|
287
|
+
IFS=$'\n'
|
|
288
|
+
set -f
|
|
289
|
+
# shellcheck disable=SC2206 # no line is empty: rows carry a ">" prefix
|
|
290
|
+
lines=($1)
|
|
291
|
+
set +f
|
|
292
|
+
IFS="$saved_ifs"
|
|
293
|
+
while [ "$i" -lt "${#lines[@]}" ]; do
|
|
294
|
+
header="${lines[i]}"
|
|
295
|
+
i=$((i + 1))
|
|
296
|
+
if [ "$header" = end ]; then
|
|
297
|
+
[ "$i" -eq "${#lines[@]}" ] && [ "${#rcs[@]}" -eq "${#QMAP_PLAN_REQUESTS[@]}" ] || return 0
|
|
298
|
+
QMAP_MEMO_KEYS+=("${QMAP_PLAN_REQUESTS[@]}")
|
|
299
|
+
QMAP_MEMO_RCS+=("${rcs[@]}")
|
|
300
|
+
QMAP_MEMO_OUTS+=("${outs[@]}")
|
|
301
|
+
return 0
|
|
302
|
+
fi
|
|
303
|
+
case "$header" in $'\036'[01]' '[0-9]*) ;; *) return 0 ;; esac
|
|
304
|
+
status="${header:1:1}" count="${header#* }"
|
|
305
|
+
raw=""
|
|
306
|
+
while [ "$count" -gt 0 ]; do
|
|
307
|
+
line="${lines[i]-}"
|
|
308
|
+
case "$line" in '>'*) ;; *) return 0 ;; esac
|
|
309
|
+
raw="$raw${line#>}"$'\n'
|
|
310
|
+
i=$((i + 1))
|
|
311
|
+
count=$((count - 1))
|
|
312
|
+
done
|
|
313
|
+
rcs+=("$status")
|
|
314
|
+
outs+=("$raw")
|
|
315
|
+
done
|
|
173
316
|
}
|
|
174
317
|
|
|
175
318
|
# qmap_validate_document <file> <block> — structural validation only.
|
|
@@ -191,6 +334,7 @@ qmap_value() { _qmap_read value "$1" "$2" "$3"; }
|
|
|
191
334
|
_qmap_stage() {
|
|
192
335
|
local file="$1"; shift
|
|
193
336
|
local tmp="$file.cli.tmp" rc=0
|
|
337
|
+
qmap_memo_reset
|
|
194
338
|
awk "$@" "$file" > "$tmp" || rc=$?
|
|
195
339
|
[ "$rc" -eq 0 ] || { rm -f "$tmp"; die "internal: manifest edit refused (awk exit $rc) for $file"; }
|
|
196
340
|
[ -s "$tmp" ] || { rm -f "$tmp"; die "internal: manifest edit produced an empty file for $file"; }
|
|
@@ -643,6 +787,8 @@ sources_remove_package() {
|
|
|
643
787
|
shift 2
|
|
644
788
|
[ -f "$file" ] || return 0
|
|
645
789
|
tmp="$file.cli.tmp"
|
|
790
|
+
# A write to a qmap document forgets every remembered read, as _qmap_stage does.
|
|
791
|
+
qmap_memo_reset
|
|
646
792
|
awk -v name="$name" -v only=" $* " "$IS_PKG_REF_AWK$IS_YAML_ITEM_AWK"'
|
|
647
793
|
BEGIN { store = ".intelligence/packages/" name }
|
|
648
794
|
FNR == 1 { pass++ }
|
package/cli/lib/qmap.awk
CHANGED
|
@@ -1,16 +1,58 @@
|
|
|
1
1
|
# Shared tokenizer for the CLI-owned quoted-key map and scalar document shape.
|
|
2
2
|
# Reader modes ignore unrelated YAML. Lock mode validates the whole document
|
|
3
3
|
# before publishing rows, so a bad later entry never yields a partial result.
|
|
4
|
+
#
|
|
5
|
+
# One read per process: QMAP_MODE and friends describe it, the document
|
|
6
|
+
# arrives on stdin, and a strict failure exits 1 with its reason on stderr.
|
|
7
|
+
# Several reads per process: QMAP_JOBS holds one job per line (mode, block,
|
|
8
|
+
# key, field and expected lock version, unit-separated) and each job names its
|
|
9
|
+
# document as the matching file operand, so a command that needs the manifest,
|
|
10
|
+
# the lock and the CLI's package descriptor parses them in a single awk start.
|
|
11
|
+
# Every job then prints a header, "\036<status> <line count>", followed by that
|
|
12
|
+
# many lines, each behind a ">" so none is empty: its rows, or its error. Empty
|
|
13
|
+
# documents cannot be job operands (they yield no first line to start the job);
|
|
14
|
+
# the caller reads those alone.
|
|
4
15
|
BEGIN {
|
|
5
|
-
mode = ENVIRON["QMAP_MODE"]
|
|
6
|
-
block = ENVIRON["QMAP_BLOCK"]
|
|
7
|
-
wanted_key = ENVIRON["QMAP_KEY"]
|
|
8
|
-
wanted_field = ENVIRON["QMAP_FIELD"]
|
|
9
|
-
expected_version = ENVIRON["QMAP_EXPECTED_VERSION"]
|
|
10
|
-
source_file = ENVIRON["QMAP_FILE"]
|
|
11
|
-
strict = (mode == "lock" || mode == "validate")
|
|
12
16
|
sep = sprintf("%c", 31)
|
|
13
17
|
single_quote = sprintf("%c", 39)
|
|
18
|
+
multi = (ENVIRON["QMAP_JOBS"] != "")
|
|
19
|
+
if (multi) job_count = split(ENVIRON["QMAP_JOBS"], job_spec, "\n")
|
|
20
|
+
else configure(ENVIRON["QMAP_MODE"], ENVIRON["QMAP_BLOCK"], ENVIRON["QMAP_KEY"], ENVIRON["QMAP_FIELD"], ENVIRON["QMAP_EXPECTED_VERSION"], ENVIRON["QMAP_FILE"])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function configure(m, b, k, f, v, file) {
|
|
24
|
+
mode = m
|
|
25
|
+
block = b
|
|
26
|
+
wanted_key = k
|
|
27
|
+
wanted_field = f
|
|
28
|
+
expected_version = v
|
|
29
|
+
source_file = file
|
|
30
|
+
strict = (mode == "lock" || mode == "validate")
|
|
31
|
+
problem = ""; problem_line = 0
|
|
32
|
+
in_block = 0; package = ""
|
|
33
|
+
row_count = 0; found = 0; result = ""; block_seen = 0
|
|
34
|
+
split("", row_name); split("", row_line)
|
|
35
|
+
split("", package_seen); split("", field_seen); split("", field_value)
|
|
36
|
+
split("", top_seen); split("", top_value)
|
|
37
|
+
out_count = 0; failed = 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# emit/fail keep the single-document output stream and exit status unchanged;
|
|
41
|
+
# a job buffers both so its header can state them first.
|
|
42
|
+
function emit(line) {
|
|
43
|
+
if (multi) out[++out_count] = line
|
|
44
|
+
else print line
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fail(message) {
|
|
48
|
+
if (multi) {
|
|
49
|
+
failed = 1
|
|
50
|
+
out_count = 0
|
|
51
|
+
out[++out_count] = message
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
print message > "/dev/stderr"
|
|
55
|
+
exit 1
|
|
14
56
|
}
|
|
15
57
|
|
|
16
58
|
function error_at(reason, line) {
|
|
@@ -104,6 +146,14 @@ function token(line, text, colon, tail) {
|
|
|
104
146
|
return 1
|
|
105
147
|
}
|
|
106
148
|
|
|
149
|
+
# A job starts at its document's first line and ends where the next begins.
|
|
150
|
+
multi && FNR == 1 {
|
|
151
|
+
if (job) finish()
|
|
152
|
+
job++
|
|
153
|
+
split(job_spec[job], spec, sep)
|
|
154
|
+
configure(spec[1], spec[2], spec[3], spec[4], spec[5], FILENAME)
|
|
155
|
+
}
|
|
156
|
+
|
|
107
157
|
{
|
|
108
158
|
sub(/\r$/, "")
|
|
109
159
|
if ($0 ~ /^[ \t]*(#.*)?$/) next
|
|
@@ -114,7 +164,7 @@ function token(line, text, colon, tail) {
|
|
|
114
164
|
if (kind == "field" && package != "") context = "package " package ", field " key ": "
|
|
115
165
|
else if (kind == "top") context = "field " key ": "
|
|
116
166
|
}
|
|
117
|
-
error_at(context "malformed lock structure or unsupported scalar",
|
|
167
|
+
error_at(context "malformed lock structure or unsupported scalar", FNR)
|
|
118
168
|
}
|
|
119
169
|
if ($0 ~ /^[^ #\t]/) { in_block = 0; package = "" }
|
|
120
170
|
next
|
|
@@ -122,14 +172,14 @@ function token(line, text, colon, tail) {
|
|
|
122
172
|
if (kind == "top") {
|
|
123
173
|
in_block = (key == block && empty)
|
|
124
174
|
package = ""
|
|
125
|
-
if (strict && top_seen[key]++) error_at("duplicate top-level field " key,
|
|
175
|
+
if (strict && top_seen[key]++) error_at("duplicate top-level field " key, FNR)
|
|
126
176
|
if (!(key in top_value)) top_value[key] = value
|
|
127
177
|
if (mode == "top" && key == wanted_key && !found) { result = value; found = 1 }
|
|
128
178
|
if (strict) {
|
|
129
179
|
if (key == block) {
|
|
130
180
|
block_seen = 1
|
|
131
|
-
if (!empty) error_at(block " must be a block map",
|
|
132
|
-
} else if (empty) error_at("expected scalar for " key,
|
|
181
|
+
if (!empty) error_at(block " must be a block map", FNR)
|
|
182
|
+
} else if (empty) error_at("expected scalar for " key, FNR)
|
|
133
183
|
}
|
|
134
184
|
next
|
|
135
185
|
}
|
|
@@ -137,46 +187,55 @@ function token(line, text, colon, tail) {
|
|
|
137
187
|
package = key
|
|
138
188
|
row_count++
|
|
139
189
|
row_name[row_count] = key
|
|
140
|
-
row_line[row_count] =
|
|
190
|
+
row_line[row_count] = FNR
|
|
141
191
|
if (strict) {
|
|
142
|
-
if (key == "") error_at("empty package key",
|
|
143
|
-
if (package_seen[key]++) error_at("duplicate package " key,
|
|
144
|
-
if (!empty) error_at("expected field map for " key,
|
|
192
|
+
if (key == "") error_at("empty package key", FNR)
|
|
193
|
+
if (package_seen[key]++) error_at("duplicate package " key, FNR)
|
|
194
|
+
if (!empty) error_at("expected field map for " key, FNR)
|
|
145
195
|
}
|
|
146
196
|
if (mode == "value" && key == wanted_key && !found) { result = value; found = 1 }
|
|
147
197
|
next
|
|
148
198
|
}
|
|
149
199
|
if (kind == "field" && in_block && package != "") {
|
|
150
200
|
identity = package SUBSEP key
|
|
151
|
-
if (strict && field_seen[identity]++) error_at("duplicate field " package "." key,
|
|
201
|
+
if (strict && field_seen[identity]++) error_at("duplicate field " package "." key, FNR)
|
|
152
202
|
if (!(identity in field_value)) field_value[identity] = value
|
|
153
|
-
if (strict && empty) error_at("expected scalar for " package "." key,
|
|
203
|
+
if (strict && empty) error_at("expected scalar for " package "." key, FNR)
|
|
154
204
|
if (mode == "field" && package == wanted_key && key == wanted_field && !found) {
|
|
155
205
|
result = value
|
|
156
206
|
found = 1
|
|
157
207
|
}
|
|
158
208
|
next
|
|
159
209
|
}
|
|
160
|
-
if (strict) error_at("malformed lock structure: expected top-level scalar or quoted package field",
|
|
210
|
+
if (strict) error_at("malformed lock structure: expected top-level scalar or quoted package field", FNR)
|
|
161
211
|
}
|
|
162
212
|
|
|
163
|
-
|
|
213
|
+
# The end of one document: its rows or its error, through emit/fail.
|
|
214
|
+
function finish( i) {
|
|
215
|
+
finish_rows()
|
|
216
|
+
if (multi) {
|
|
217
|
+
printf "%c%d %d\n", 30, failed, out_count
|
|
218
|
+
for (i = 1; i <= out_count; i++) print ">" out[i]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function finish_rows( version, count, fields, tops, i, f, name, row) {
|
|
164
223
|
# Read the format header before interpreting a future body's shape. Errors
|
|
165
224
|
# were retained during the single pass so header placement does not matter.
|
|
166
225
|
if (mode == "lock" && top_value["lockfile_version"] != expected_version) {
|
|
167
226
|
version = top_value["lockfile_version"]
|
|
168
|
-
|
|
169
|
-
|
|
227
|
+
fail(source_file " has unsupported or missing lockfile_version '" (version == "" ? "<missing>" : version) "' — expected " expected_version)
|
|
228
|
+
return
|
|
170
229
|
}
|
|
171
230
|
if (strict && problem != "") {
|
|
172
|
-
|
|
173
|
-
|
|
231
|
+
fail(source_file ":" problem_line ": " problem)
|
|
232
|
+
return
|
|
174
233
|
}
|
|
175
234
|
if (strict && !block_seen) {
|
|
176
|
-
|
|
177
|
-
|
|
235
|
+
fail(source_file ": missing " block " block")
|
|
236
|
+
return
|
|
178
237
|
}
|
|
179
|
-
if (mode == "lock")
|
|
238
|
+
if (mode == "lock") emit("V" sep top_value["lockfile_version"] sep top_value["engine_version"])
|
|
180
239
|
if (mode == "fieldrows") {
|
|
181
240
|
# One row per package key, as `keys` lists them: the name, then each
|
|
182
241
|
# field QMAP_FIELD names (space separated), as qmap_field decodes it.
|
|
@@ -185,22 +244,28 @@ END {
|
|
|
185
244
|
name = row_name[i]
|
|
186
245
|
row = name
|
|
187
246
|
for (f = 1; f <= count; f++) row = row sep field_value[name SUBSEP fields[f]]
|
|
188
|
-
|
|
247
|
+
emit(row)
|
|
189
248
|
}
|
|
190
249
|
} else if (mode == "rows" || mode == "lock" || mode == "keys") {
|
|
191
250
|
for (i = 1; i <= row_count; i++) {
|
|
192
251
|
name = row_name[i]
|
|
193
|
-
if (mode == "keys")
|
|
252
|
+
if (mode == "keys") emit(name)
|
|
194
253
|
else {
|
|
195
254
|
row = name sep field_value[name SUBSEP "requested"] sep field_value[name SUBSEP "url"] sep field_value[name SUBSEP "path"] sep field_value[name SUBSEP "resolved"] sep field_value[name SUBSEP "sha"]
|
|
196
|
-
if (mode == "lock")
|
|
197
|
-
else
|
|
255
|
+
if (mode == "lock") emit("R" sep row sep row_line[i])
|
|
256
|
+
else emit(row)
|
|
198
257
|
}
|
|
199
258
|
}
|
|
200
259
|
} else if (mode == "tops") {
|
|
201
260
|
# Several top-level scalars in one pass: QMAP_KEY lists them, space
|
|
202
261
|
# separated; each present one prints as key<US>value.
|
|
203
262
|
count = split(wanted_key, tops, " ")
|
|
204
|
-
for (i = 1; i <= count; i++) if (tops[i] in top_value)
|
|
205
|
-
} else if (found)
|
|
263
|
+
for (i = 1; i <= count; i++) if (tops[i] in top_value) emit(tops[i] sep top_value[tops[i]])
|
|
264
|
+
} else if (found) emit(result)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
END {
|
|
268
|
+
if (multi) {
|
|
269
|
+
if (job) finish()
|
|
270
|
+
} else finish()
|
|
206
271
|
}
|
package/cli/lib/registry.sh
CHANGED
|
@@ -61,7 +61,7 @@ resolve_package_source() {
|
|
|
61
61
|
[ -n "$reg_url" ] || continue
|
|
62
62
|
index="$(_fetch_index "${reg_url#git+}")"
|
|
63
63
|
if [ -z "$index" ]; then
|
|
64
|
-
echo "
|
|
64
|
+
echo "WARNING: registry unreachable or missing index.yaml, skipped: $reg_url" >&2
|
|
65
65
|
continue
|
|
66
66
|
fi
|
|
67
67
|
RES_URL="$(qmap_field "$index" "packages" "$name" "url")"
|