@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.
Files changed (48) hide show
  1. package/cli/commands/init.sh +6 -1
  2. package/cli/commands/package.sh +8 -3
  3. package/cli/commands/registry.sh +1 -1
  4. package/cli/commands/source.sh +56 -17
  5. package/cli/commands/sync.sh +78 -6
  6. package/cli/intelligence +16 -3
  7. package/cli/internal/check.sh +64 -13
  8. package/cli/internal/convert-legacy.sh +1 -1
  9. package/cli/internal/package-add.sh +25 -4
  10. package/cli/internal/package-alias.sh +51 -0
  11. package/cli/internal/package-list.sh +16 -2
  12. package/cli/internal/package-remove.sh +3 -1
  13. package/cli/internal/package-search.sh +1 -1
  14. package/cli/internal/package-update.sh +14 -6
  15. package/cli/internal/restore.sh +3 -2
  16. package/cli/lib/adapter-lifecycle.sh +5 -2
  17. package/cli/lib/cli-common.sh +149 -34
  18. package/cli/lib/gitignore.sh +128 -34
  19. package/cli/lib/lockfile.sh +14 -4
  20. package/cli/lib/manifest.sh +315 -21
  21. package/cli/lib/qmap.awk +97 -32
  22. package/cli/lib/registry.sh +19 -14
  23. package/cli/lib/sync-cache.sh +596 -142
  24. package/cli/lib/sync-lock.sh +19 -2
  25. package/engine/ENGINE_SHA +1 -1
  26. package/engine/VERSION +1 -1
  27. package/engine/adapters/_template.sh +6 -2
  28. package/engine/adapters/agents.sh +8 -2
  29. package/engine/adapters/antigravity.sh +3 -1
  30. package/engine/adapters/claude.sh +29 -12
  31. package/engine/adapters/codex.sh +11 -13
  32. package/engine/adapters/copilot.sh +54 -4
  33. package/engine/adapters/cursor.sh +13 -2
  34. package/engine/adapters/opencode.sh +7 -1
  35. package/engine/adapters/pi.sh +3 -1
  36. package/engine/lib/adapter-contract.sh +51 -8
  37. package/engine/lib/common.sh +1072 -160
  38. package/engine/lib/contract.sh +5 -0
  39. package/engine/sync.sh +128 -28
  40. package/package.json +1 -1
  41. package/packages/sync/references/adapters.md +44 -9
  42. package/packages/sync/references/conventions.md +48 -8
  43. package/packages/sync/references/onboarding-migration.md +3 -3
  44. package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +3 -2
  45. package/packages/sync/skills/intelligence-review-context/SKILL.md +2 -1
  46. package/packages/sync/skills/intelligence-update-context/SKILL.md +4 -2
  47. package/packages/sync/skills/intelligence-update-context/references/agents.md +7 -3
  48. package/packages/sync/skills/intelligence-update-context/references/skills.md +3 -1
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", NR)
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, NR)
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", NR)
132
- } else if (empty) error_at("expected scalar for " key, NR)
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] = NR
190
+ row_line[row_count] = FNR
141
191
  if (strict) {
142
- if (key == "") error_at("empty package key", NR)
143
- if (package_seen[key]++) error_at("duplicate package " key, NR)
144
- if (!empty) error_at("expected field map for " key, NR)
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, NR)
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, NR)
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", NR)
210
+ if (strict) error_at("malformed lock structure: expected top-level scalar or quoted package field", FNR)
161
211
  }
162
212
 
163
- END {
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
- print source_file " has unsupported or missing lockfile_version '" (version == "" ? "<missing>" : version) "' — expected " expected_version > "/dev/stderr"
169
- exit 1
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
- print source_file ":" problem_line ": " problem > "/dev/stderr"
173
- exit 1
231
+ fail(source_file ":" problem_line ": " problem)
232
+ return
174
233
  }
175
234
  if (strict && !block_seen) {
176
- print source_file ": missing " block " block" > "/dev/stderr"
177
- exit 1
235
+ fail(source_file ": missing " block " block")
236
+ return
178
237
  }
179
- if (mode == "lock") print "V" sep top_value["lockfile_version"] sep top_value["engine_version"]
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
- print row
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") print name
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") print "R" sep row sep row_line[i]
197
- else print row
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) print tops[i] sep top_value[tops[i]]
205
- } else if (found) print result
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
  }
@@ -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 " WARN: registry unreachable or missing index.yaml, skipped: $reg_url" >&2
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")"
@@ -200,25 +200,30 @@ fetch_package() {
200
200
  printf '%s' "$sha"
201
201
  }
202
202
 
203
- # wire_package_sources <manifest> <@scope/name> <store-rel-dir>
203
+ # wire_package_sources <manifest> <@scope/name> <store-rel-dir> <root>
204
204
  # The provides convention: whichever of rules/agents/skills the installed
205
- # package has becomes a sources entry — inserted FIRST in its section, so
206
- # project-owned entries come later and win on same-named artifacts.
205
+ # package has becomes a sources entry — its store path, inserted FIRST in its
206
+ # section, so project-owned entries come later and win on same-named artifacts.
207
+ # A section that already names that directory, in any spelling, keeps the entry
208
+ # where it stands: the person who wrote `@scope/name/<dir>` or `<alias>:<dir>`
209
+ # chose both the spelling and the position. Call it once `packages:` declares
210
+ # the package and its alias, which is what resolves those spellings.
207
211
  wire_package_sources() {
208
- local manifest="$1" name="$2" rel="$3" root="$4"
212
+ local manifest="$1" rel="$3" root="$4"
209
213
  local section
210
214
  for section in rules agents skills; do
211
- if [ -d "$root/$rel/$section" ]; then
212
- sources_add_entry_first "$manifest" "$section" "$rel/$section"
213
- fi
215
+ [ -d "$root/$rel/$section" ] || continue
216
+ package_source_listed "$manifest" "$section" "$rel/$section" && continue
217
+ sources_add_entry_first "$manifest" "$section" "$rel/$section"
214
218
  done
215
219
  }
216
220
 
217
- # unwire_package_sources <manifest> <store-rel-dir>
221
+ # unwire_package_sources <manifest> <@scope/name> [section...]
222
+ # Drop the package's sources entries in every spelling (sources_remove_package):
223
+ # every one without sections, as `package remove` does; only the named
224
+ # sections' own directories with them, as `update` does for the directories a
225
+ # new version no longer has. Call it while `packages:` still declares the
226
+ # package and its alias.
218
227
  unwire_package_sources() {
219
- local manifest="$1" rel="$2"
220
- local section
221
- for section in rules agents skills; do
222
- sources_remove_entry "$manifest" "$section" "$rel/$section"
223
- done
228
+ sources_remove_package "$@"
224
229
  }