@aiwg/cli 0.0.0-bootstrap.0 → 2026.7.18

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 (467) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -1
  3. package/bin/aiwg.mjs +349 -0
  4. package/dist/src/a2a/agent-card.js +145 -0
  5. package/dist/src/a2a/client.js +411 -0
  6. package/dist/src/a2a/hitl-cli.js +171 -0
  7. package/dist/src/a2a/hitl-driver.js +370 -0
  8. package/dist/src/a2a/hitl.js +181 -0
  9. package/dist/src/a2a/http.js +193 -0
  10. package/dist/src/a2a/jcs.js +128 -0
  11. package/dist/src/a2a/jws.js +187 -0
  12. package/dist/src/a2a/types.js +24 -0
  13. package/dist/src/a2a/webhook.js +240 -0
  14. package/dist/src/activity-log/cli.js +348 -0
  15. package/dist/src/activity-log/parser.js +81 -0
  16. package/dist/src/activity-log/types.js +50 -0
  17. package/dist/src/agents/agent-deployer.js +367 -0
  18. package/dist/src/agents/agent-packager.js +271 -0
  19. package/dist/src/agents/agent-validator.js +271 -0
  20. package/dist/src/agents/grounding/knowledge-base.js +161 -0
  21. package/dist/src/agents/grounding/types.js +12 -0
  22. package/dist/src/agents/index.js +10 -0
  23. package/dist/src/agents/packaged-agent-inventory.js +75 -0
  24. package/dist/src/agents/types.js +7 -0
  25. package/dist/src/api/index.d.ts +10 -0
  26. package/dist/src/api/index.js +10 -0
  27. package/dist/src/artifacts/address-parser.js +73 -0
  28. package/dist/src/artifacts/audit/cli.js +183 -0
  29. package/dist/src/artifacts/audit/drift.js +122 -0
  30. package/dist/src/artifacts/audit/types.js +12 -0
  31. package/dist/src/artifacts/backends/graphology-backend.js +160 -0
  32. package/dist/src/artifacts/backends/json-backend.js +129 -0
  33. package/dist/src/artifacts/backends/sqlite-backend.js +199 -0
  34. package/dist/src/artifacts/browser-export.js +622 -0
  35. package/dist/src/artifacts/capability-resolver.js +94 -0
  36. package/dist/src/artifacts/checksum-manifest.js +91 -0
  37. package/dist/src/artifacts/citation-parser.js +162 -0
  38. package/dist/src/artifacts/cli.js +1486 -0
  39. package/dist/src/artifacts/corpus-tools/citation-densify.js +311 -0
  40. package/dist/src/artifacts/corpus-tools/cli.js +393 -0
  41. package/dist/src/artifacts/corpus-tools/corpus-graph.js +225 -0
  42. package/dist/src/artifacts/corpus-tools/curator.js +186 -0
  43. package/dist/src/artifacts/corpus-tools/discovery-log.js +70 -0
  44. package/dist/src/artifacts/corpus-tools/funder-network.js +141 -0
  45. package/dist/src/artifacts/corpus-tools/induction-audit.js +233 -0
  46. package/dist/src/artifacts/corpus-tools/integrity-scan.js +202 -0
  47. package/dist/src/artifacts/corpus-tools/profile-communities.js +166 -0
  48. package/dist/src/artifacts/corpus-tools/profile-edges.js +72 -0
  49. package/dist/src/artifacts/corpus-tools/profile-embed.js +113 -0
  50. package/dist/src/artifacts/corpus-tools/profile-generate-fm.js +167 -0
  51. package/dist/src/artifacts/corpus-tools/profile-generate.js +181 -0
  52. package/dist/src/artifacts/corpus-tools/profile-metrics.js +98 -0
  53. package/dist/src/artifacts/corpus-tools/profile-status.js +86 -0
  54. package/dist/src/artifacts/corpus-tools/profile-temporal.js +139 -0
  55. package/dist/src/artifacts/corpus-tools/radar-init.js +93 -0
  56. package/dist/src/artifacts/corpus-tools/radar-report.js +118 -0
  57. package/dist/src/artifacts/corpus-tools/radar-shared.js +189 -0
  58. package/dist/src/artifacts/corpus-tools/radar-status.js +68 -0
  59. package/dist/src/artifacts/corpus-tools/sidecar-lint.js +233 -0
  60. package/dist/src/artifacts/corpus-tools/sidecar-repair.js +277 -0
  61. package/dist/src/artifacts/corpus-tools/snapshot.js +591 -0
  62. package/dist/src/artifacts/corpus-tools/source-types.js +164 -0
  63. package/dist/src/artifacts/corpus-tools/vision-extract.js +243 -0
  64. package/dist/src/artifacts/corpus-views/build.js +126 -0
  65. package/dist/src/artifacts/corpus-views/corpus-config.js +124 -0
  66. package/dist/src/artifacts/corpus-views/ref-parser.js +437 -0
  67. package/dist/src/artifacts/corpus-views/renderers.js +353 -0
  68. package/dist/src/artifacts/corpus-views/taxonomies.js +131 -0
  69. package/dist/src/artifacts/dep-graph.js +173 -0
  70. package/dist/src/artifacts/discover-facets.js +375 -0
  71. package/dist/src/artifacts/embedding-index.js +354 -0
  72. package/dist/src/artifacts/enrichment/cli.js +240 -0
  73. package/dist/src/artifacts/enrichment/prompt.js +47 -0
  74. package/dist/src/artifacts/enrichment/store.js +90 -0
  75. package/dist/src/artifacts/enrichment/types.js +12 -0
  76. package/dist/src/artifacts/fortemi-core-query-adapter.js +475 -0
  77. package/dist/src/artifacts/fortemi-core-sync.js +251 -0
  78. package/dist/src/artifacts/fortemi-shard-export.js +44 -0
  79. package/dist/src/artifacts/fulltext.js +108 -0
  80. package/dist/src/artifacts/graph-backend.js +51 -0
  81. package/dist/src/artifacts/graph-query.js +247 -0
  82. package/dist/src/artifacts/hybrid-query.js +225 -0
  83. package/dist/src/artifacts/index-builder.js +1089 -0
  84. package/dist/src/artifacts/index-reader.js +112 -0
  85. package/dist/src/artifacts/index-status.js +193 -0
  86. package/dist/src/artifacts/legacy-index-migration.js +169 -0
  87. package/dist/src/artifacts/move.js +154 -0
  88. package/dist/src/artifacts/operational-state.js +239 -0
  89. package/dist/src/artifacts/prebuilt-build-lock.js +49 -0
  90. package/dist/src/artifacts/query-engine.js +1955 -0
  91. package/dist/src/artifacts/source-graph.js +529 -0
  92. package/dist/src/artifacts/stats.js +169 -0
  93. package/dist/src/artifacts/types.js +606 -0
  94. package/dist/src/artifacts/views/cli.js +293 -0
  95. package/dist/src/artifacts/views/definition.js +170 -0
  96. package/dist/src/artifacts/views/store.js +149 -0
  97. package/dist/src/artifacts/views/types.js +8 -0
  98. package/dist/src/artifacts/watcher.js +255 -0
  99. package/dist/src/catalog/builtin-models.json +636 -0
  100. package/dist/src/catalog/cli.js +228 -0
  101. package/dist/src/catalog/cli.mjs +289 -0
  102. package/dist/src/catalog/index.js +15 -0
  103. package/dist/src/catalog/loader.js +217 -0
  104. package/dist/src/catalog/loader.mjs +251 -0
  105. package/dist/src/catalog/sources.json +41 -0
  106. package/dist/src/catalog/types.js +8 -0
  107. package/dist/src/channel/manager.mjs +504 -0
  108. package/dist/src/cli/agent-spawn.js +178 -0
  109. package/dist/src/cli/cli-extension-loader.js +245 -0
  110. package/dist/src/cli/command-log.js +275 -0
  111. package/dist/src/cli/config-loader.js +232 -0
  112. package/dist/src/cli/env.js +82 -0
  113. package/dist/src/cli/errors.js +150 -0
  114. package/dist/src/cli/find-package-root.js +40 -0
  115. package/dist/src/cli/git-hooks.js +250 -0
  116. package/dist/src/cli/handlers/agentcard.js +206 -0
  117. package/dist/src/cli/handlers/artifacts.js +76 -0
  118. package/dist/src/cli/handlers/best-practices-audit.js +280 -0
  119. package/dist/src/cli/handlers/cockpit.js +137 -0
  120. package/dist/src/cli/handlers/command-log.js +37 -0
  121. package/dist/src/cli/handlers/daemon.js +59 -0
  122. package/dist/src/cli/handlers/diagnose.js +300 -0
  123. package/dist/src/cli/handlers/execution-mode.js +105 -0
  124. package/dist/src/cli/handlers/feedback.js +341 -0
  125. package/dist/src/cli/handlers/help.js +147 -0
  126. package/dist/src/cli/handlers/index.js +302 -0
  127. package/dist/src/cli/handlers/init.js +179 -0
  128. package/dist/src/cli/handlers/install.js +129 -0
  129. package/dist/src/cli/handlers/issues.js +46 -0
  130. package/dist/src/cli/handlers/lint.js +42 -0
  131. package/dist/src/cli/handlers/local-executor.js +315 -0
  132. package/dist/src/cli/handlers/marketplace.js +168 -0
  133. package/dist/src/cli/handlers/mc.js +1010 -0
  134. package/dist/src/cli/handlers/models.js +352 -0
  135. package/dist/src/cli/handlers/packages.js +155 -0
  136. package/dist/src/cli/handlers/ralph-launcher.js +717 -0
  137. package/dist/src/cli/handlers/ralph.js +575 -0
  138. package/dist/src/cli/handlers/refresh.js +432 -0
  139. package/dist/src/cli/handlers/regenerate.js +336 -0
  140. package/dist/src/cli/handlers/repo-access.js +109 -0
  141. package/dist/src/cli/handlers/run.js +165 -0
  142. package/dist/src/cli/handlers/runtime-info.js +301 -0
  143. package/dist/src/cli/handlers/sandbox.js +197 -0
  144. package/dist/src/cli/handlers/scaffolding.js +149 -0
  145. package/dist/src/cli/handlers/script-runner.js +121 -0
  146. package/dist/src/cli/handlers/sdlc-accelerate.js +148 -0
  147. package/dist/src/cli/handlers/serve.js +1835 -0
  148. package/dist/src/cli/handlers/session.js +348 -0
  149. package/dist/src/cli/handlers/setup.js +440 -0
  150. package/dist/src/cli/handlers/skill-lint.js +367 -0
  151. package/dist/src/cli/handlers/skill-usage.js +74 -0
  152. package/dist/src/cli/handlers/steward.js +567 -0
  153. package/dist/src/cli/handlers/subcommands.js +1702 -0
  154. package/dist/src/cli/handlers/team.js +337 -0
  155. package/dist/src/cli/handlers/types.js +11 -0
  156. package/dist/src/cli/handlers/use.js +2772 -0
  157. package/dist/src/cli/handlers/utilities.js +703 -0
  158. package/dist/src/cli/handlers/version.js +134 -0
  159. package/dist/src/cli/handlers/workspace-context.js +93 -0
  160. package/dist/src/cli/handlers/workspace.js +126 -0
  161. package/dist/src/cli/help-generator.js +178 -0
  162. package/dist/src/cli/hooks/builtin/activity-log-hook.js +126 -0
  163. package/dist/src/cli/hooks/executor.js +96 -0
  164. package/dist/src/cli/hooks/index.js +15 -0
  165. package/dist/src/cli/hooks/registry.js +143 -0
  166. package/dist/src/cli/hooks/types.js +13 -0
  167. package/dist/src/cli/log.js +492 -0
  168. package/dist/src/cli/project-isolation/detect.js +70 -0
  169. package/dist/src/cli/project-isolation/index.js +7 -0
  170. package/dist/src/cli/project-isolation/signals.js +17 -0
  171. package/dist/src/cli/project-isolation/warning.js +102 -0
  172. package/dist/src/cli/prompt-utils.js +155 -0
  173. package/dist/src/cli/provider-deployment-plan.js +126 -0
  174. package/dist/src/cli/provider-resolution.js +119 -0
  175. package/dist/src/cli/router.js +305 -0
  176. package/dist/src/cli/scope-resolver.js +392 -0
  177. package/dist/src/cli/skill-usage.js +616 -0
  178. package/dist/src/cli/ui.js +224 -0
  179. package/dist/src/cli/watch-service.js +203 -0
  180. package/dist/src/cli/workflow-orchestrator.js +478 -0
  181. package/dist/src/cli/workspace-signals.js +321 -0
  182. package/dist/src/community/footer.js +14 -0
  183. package/dist/src/community/links.js +123 -0
  184. package/dist/src/community/milestones.js +46 -0
  185. package/dist/src/community/nudge-policy.js +93 -0
  186. package/dist/src/config/aiwg-config.js +883 -0
  187. package/dist/src/config/cli.js +703 -0
  188. package/dist/src/config/gitignore.js +156 -0
  189. package/dist/src/config/project-artifacts.js +76 -0
  190. package/dist/src/config/user-config.js +428 -0
  191. package/dist/src/config/user-registry.js +127 -0
  192. package/dist/src/config/workspace.js +276 -0
  193. package/dist/src/contributors/detect.js +39 -0
  194. package/dist/src/contributors/discover.js +172 -0
  195. package/dist/src/contributors/heuristic.js +186 -0
  196. package/dist/src/contributors/index.js +11 -0
  197. package/dist/src/contributors/types.js +13 -0
  198. package/dist/src/contributors/validation.js +116 -0
  199. package/dist/src/data/community.schema.json +43 -0
  200. package/dist/src/data/community.yaml +16 -0
  201. package/dist/src/extensions/capability-index.js +270 -0
  202. package/dist/src/extensions/claude-hooks-installer.js +268 -0
  203. package/dist/src/extensions/commands/definitions.js +3485 -0
  204. package/dist/src/extensions/deployment-registration.js +463 -0
  205. package/dist/src/extensions/index.js +22 -0
  206. package/dist/src/extensions/loader.js +132 -0
  207. package/dist/src/extensions/managed-marker.js +100 -0
  208. package/dist/src/extensions/manifest.js +196 -0
  209. package/dist/src/extensions/project-local-activity.js +114 -0
  210. package/dist/src/extensions/project-local-discovery.js +261 -0
  211. package/dist/src/extensions/project-local-doctor.js +281 -0
  212. package/dist/src/extensions/project-local-gitignore.js +176 -0
  213. package/dist/src/extensions/project-local-paths.js +121 -0
  214. package/dist/src/extensions/project-local-promote.js +218 -0
  215. package/dist/src/extensions/project-local-remove.js +373 -0
  216. package/dist/src/extensions/project-local-scaffold.js +252 -0
  217. package/dist/src/extensions/project-quickref.js +291 -0
  218. package/dist/src/extensions/registry.js +368 -0
  219. package/dist/src/extensions/shadow-resolver.js +271 -0
  220. package/dist/src/extensions/types.js +86 -0
  221. package/dist/src/extensions/upstream-registry.js +195 -0
  222. package/dist/src/extensions/validation.js +631 -0
  223. package/dist/src/features/catalog.js +88 -0
  224. package/dist/src/features/cli.js +197 -0
  225. package/dist/src/features/installer.js +57 -0
  226. package/dist/src/features/paths.js +14 -0
  227. package/dist/src/features/runtime.js +21 -0
  228. package/dist/src/features/status.js +134 -0
  229. package/dist/src/hooks/laziness-detection.js +532 -0
  230. package/dist/src/intake/git-history-analyzer.js +486 -0
  231. package/dist/src/intake/index.js +9 -0
  232. package/dist/src/issues/cli.js +327 -0
  233. package/dist/src/issues/index.js +5 -0
  234. package/dist/src/issues/live.js +154 -0
  235. package/dist/src/issues/local.js +666 -0
  236. package/dist/src/issues/sync.js +143 -0
  237. package/dist/src/issues/types.js +2 -0
  238. package/dist/src/issues/workflows.js +164 -0
  239. package/dist/src/kb/cli.js +173 -0
  240. package/dist/src/lint/cli.js +171 -0
  241. package/dist/src/lint/loader.js +225 -0
  242. package/dist/src/lint/reporters.js +141 -0
  243. package/dist/src/lint/runner.js +364 -0
  244. package/dist/src/lint/types.js +10 -0
  245. package/dist/src/marketplace/cache.js +110 -0
  246. package/dist/src/marketplace/identifier.js +63 -0
  247. package/dist/src/marketplace/registry.js +36 -0
  248. package/dist/src/marketplace/sources/clawhub.js +53 -0
  249. package/dist/src/marketplace/sources/git.js +45 -0
  250. package/dist/src/marketplace/types.js +9 -0
  251. package/dist/src/mcp/adapters/codex-runtime.js +224 -0
  252. package/dist/src/mcp/cli.mjs +1197 -0
  253. package/dist/src/mcp/elicitation.mjs +149 -0
  254. package/dist/src/mcp/helpers.mjs +287 -0
  255. package/dist/src/mcp/index.mjs +11 -0
  256. package/dist/src/mcp/profiles.js +263 -0
  257. package/dist/src/mcp/profiles.mjs +360 -0
  258. package/dist/src/mcp/registry.js +376 -0
  259. package/dist/src/mcp/registry.mjs +387 -0
  260. package/dist/src/mcp/server.mjs +633 -0
  261. package/dist/src/mcp/test-simple.mjs +271 -0
  262. package/dist/src/mcp/tools/command-run.mjs +101 -0
  263. package/dist/src/mcp/tools/discovery.mjs +291 -0
  264. package/dist/src/mcp/tools/interaction.mjs +87 -0
  265. package/dist/src/mcp/tools/orchestration.mjs +320 -0
  266. package/dist/src/mcp/tools/subsystems.mjs +640 -0
  267. package/dist/src/memory/cli.js +166 -0
  268. package/dist/src/memory/project-registry.js +282 -0
  269. package/dist/src/metrics/artifact-metrics.js +184 -0
  270. package/dist/src/metrics/context-budget.js +174 -0
  271. package/dist/src/metrics/token-counter.js +133 -0
  272. package/dist/src/models/config-loader.js +228 -0
  273. package/dist/src/models/index.js +22 -0
  274. package/dist/src/models/model-capabilities.v1.json +120 -0
  275. package/dist/src/models/model-catalog.v1.json +96 -0
  276. package/dist/src/models/model-discovery.js +521 -0
  277. package/dist/src/models/provider-models.js +73 -0
  278. package/dist/src/models/provider-policy.js +273 -0
  279. package/dist/src/models/resolver.js +245 -0
  280. package/dist/src/models/router.js +70 -0
  281. package/dist/src/models/types.js +8 -0
  282. package/dist/src/models/wrapper-deployment.js +178 -0
  283. package/dist/src/models/wrapper-route.js +54 -0
  284. package/dist/src/ops/cli.js +268 -0
  285. package/dist/src/ops/registry.js +623 -0
  286. package/dist/src/packages/adapters/clawhub.js +113 -0
  287. package/dist/src/packages/adapters/git.js +153 -0
  288. package/dist/src/packages/adapters/gitea.js +93 -0
  289. package/dist/src/packages/adapters/github.js +69 -0
  290. package/dist/src/packages/adapters/local-cache.js +89 -0
  291. package/dist/src/packages/package-registry.js +143 -0
  292. package/dist/src/packages/registry.js +238 -0
  293. package/dist/src/packages/types.js +11 -0
  294. package/dist/src/plugin/cross-framework-ops.js +475 -0
  295. package/dist/src/plugin/framework-config-loader.js +235 -0
  296. package/dist/src/plugin/framework-detector.js +186 -0
  297. package/dist/src/plugin/framework-isolator.js +341 -0
  298. package/dist/src/plugin/framework-migration.js +371 -0
  299. package/dist/src/plugin/metadata-validator.js +906 -0
  300. package/dist/src/plugin/plugin-installer.js +436 -0
  301. package/dist/src/plugin/plugin-status.js +369 -0
  302. package/dist/src/plugin/plugin-uninstaller.js +473 -0
  303. package/dist/src/plugin/registry-validator.js +344 -0
  304. package/dist/src/plugin/skill-command-translator.js +415 -0
  305. package/dist/src/plugin/workspace-creator.js +189 -0
  306. package/dist/src/plugin/workspace-migrator.js +821 -0
  307. package/dist/src/policy/authorization.js +390 -0
  308. package/dist/src/policy/repo-access.js +215 -0
  309. package/dist/src/provenance/cli.js +16 -0
  310. package/dist/src/providers/capability-matrix.js +300 -0
  311. package/dist/src/providers/capability-matrix.yaml +514 -0
  312. package/dist/src/providers/provider-definitions.js +977 -0
  313. package/dist/src/providers/provider-definitions.mjs +159 -0
  314. package/dist/src/providers/provider-inventory.js +169 -0
  315. package/dist/src/quality/feedback-ab.js +174 -0
  316. package/dist/src/quality/feedback-collector.js +176 -0
  317. package/dist/src/quality/feedback-tracker.js +123 -0
  318. package/dist/src/quality/patterns/adr.json +24 -0
  319. package/dist/src/quality/patterns/sad.json +24 -0
  320. package/dist/src/quality/patterns/test-plan.json +24 -0
  321. package/dist/src/quality/patterns/use-case.json +24 -0
  322. package/dist/src/quality/scoring.js +157 -0
  323. package/dist/src/reflections/cli.js +15 -0
  324. package/dist/src/release/plan.js +139 -0
  325. package/dist/src/research/cache/manager.js +227 -0
  326. package/dist/src/research/clients/arxiv.js +156 -0
  327. package/dist/src/research/clients/base.js +197 -0
  328. package/dist/src/research/clients/crossref.js +112 -0
  329. package/dist/src/research/clients/semantic-scholar.js +126 -0
  330. package/dist/src/research/clients/unpaywall.js +87 -0
  331. package/dist/src/research/index.js +14 -0
  332. package/dist/src/research/query-cli.js +336 -0
  333. package/dist/src/research/services/acquisition.js +313 -0
  334. package/dist/src/research/services/archival.js +225 -0
  335. package/dist/src/research/services/citation.js +279 -0
  336. package/dist/src/research/services/discovery.js +243 -0
  337. package/dist/src/research/services/documentation.js +269 -0
  338. package/dist/src/research/services/index.js +14 -0
  339. package/dist/src/research/services/provenance.js +279 -0
  340. package/dist/src/research/services/quality.js +370 -0
  341. package/dist/src/research/services/types.js +7 -0
  342. package/dist/src/research/storage-cli.js +20 -0
  343. package/dist/src/research/types.js +47 -0
  344. package/dist/src/resources/index.d.ts +2 -0
  345. package/dist/src/resources/index.js +2 -0
  346. package/dist/src/resources/web-release.d.ts +89 -0
  347. package/dist/src/resources/web-release.js +886 -0
  348. package/dist/src/rlm/cache/cli.js +162 -0
  349. package/dist/src/rlm/cache/hash.js +35 -0
  350. package/dist/src/rlm/cache/store.js +170 -0
  351. package/dist/src/rlm/cache/types.js +8 -0
  352. package/dist/src/rlm/cli.js +650 -0
  353. package/dist/src/serve/a2a-terminal-observer.js +120 -0
  354. package/dist/src/serve/agent-router.js +205 -0
  355. package/dist/src/serve/dispatch-router.js +171 -0
  356. package/dist/src/serve/executor-registry.js +715 -0
  357. package/dist/src/serve/mission-conductor.js +177 -0
  358. package/dist/src/serve/orchestrator-adapter.js +113 -0
  359. package/dist/src/serve/orchestrator-override.js +94 -0
  360. package/dist/src/serve/orchestrator-pty.js +133 -0
  361. package/dist/src/serve/pty-bridge.js +799 -0
  362. package/dist/src/serve/sandbox-registry.js +832 -0
  363. package/dist/src/serve/screen-reader.js +228 -0
  364. package/dist/src/serve/stack-adapters.js +68 -0
  365. package/dist/src/serve/telemetry.js +143 -0
  366. package/dist/src/skills/adapters/clawhub.js +250 -0
  367. package/dist/src/skills/adapters/local.js +335 -0
  368. package/dist/src/skills/adapters/openclaw.js +316 -0
  369. package/dist/src/skills/cli.js +334 -0
  370. package/dist/src/skills/registry.js +117 -0
  371. package/dist/src/skills/run.js +259 -0
  372. package/dist/src/skills/runtime.js +94 -0
  373. package/dist/src/skills/types.js +10 -0
  374. package/dist/src/smiths/agentsmith/demo.js +116 -0
  375. package/dist/src/smiths/agentsmith/examples.js +300 -0
  376. package/dist/src/smiths/agentsmith/generator.js +503 -0
  377. package/dist/src/smiths/agentsmith/index.js +11 -0
  378. package/dist/src/smiths/agentsmith/types.js +8 -0
  379. package/dist/src/smiths/commandsmith/example.js +220 -0
  380. package/dist/src/smiths/commandsmith/generator.js +288 -0
  381. package/dist/src/smiths/commandsmith/index.js +41 -0
  382. package/dist/src/smiths/commandsmith/templates.js +296 -0
  383. package/dist/src/smiths/commandsmith/types.js +7 -0
  384. package/dist/src/smiths/context-pipeline/aiwg-md.js +104 -0
  385. package/dist/src/smiths/context-pipeline/allowlist.js +195 -0
  386. package/dist/src/smiths/context-pipeline/claude-hook.js +127 -0
  387. package/dist/src/smiths/context-pipeline/discovery.js +187 -0
  388. package/dist/src/smiths/context-pipeline/external-links-section.js +49 -0
  389. package/dist/src/smiths/context-pipeline/finalization.js +147 -0
  390. package/dist/src/smiths/context-pipeline/generator.js +477 -0
  391. package/dist/src/smiths/context-pipeline/index.js +30 -0
  392. package/dist/src/smiths/context-pipeline/legacy-inject.js +103 -0
  393. package/dist/src/smiths/context-pipeline/managed-hook.js +95 -0
  394. package/dist/src/smiths/context-pipeline/overflow.js +212 -0
  395. package/dist/src/smiths/context-pipeline/parallelism-section.js +94 -0
  396. package/dist/src/smiths/context-pipeline/provider-policy.js +64 -0
  397. package/dist/src/smiths/context-pipeline/sanitizer.js +93 -0
  398. package/dist/src/smiths/context-pipeline/types.js +23 -0
  399. package/dist/src/smiths/context-pipeline/workspace-context.js +988 -0
  400. package/dist/src/smiths/hook-bridge/codex-translator.js +129 -0
  401. package/dist/src/smiths/hook-bridge/copilot-translator.js +64 -0
  402. package/dist/src/smiths/hook-bridge/factory-translator.js +43 -0
  403. package/dist/src/smiths/hook-bridge/hermes-translator.js +88 -0
  404. package/dist/src/smiths/hook-bridge/index.js +79 -0
  405. package/dist/src/smiths/hook-bridge/loader.js +116 -0
  406. package/dist/src/smiths/hook-bridge/shim.js +89 -0
  407. package/dist/src/smiths/hook-bridge/types.js +31 -0
  408. package/dist/src/smiths/index.js +39 -0
  409. package/dist/src/smiths/mcpsmith/analyzers/cli-analyzer.js +390 -0
  410. package/dist/src/smiths/mcpsmith/example.js +119 -0
  411. package/dist/src/smiths/mcpsmith/generator.js +236 -0
  412. package/dist/src/smiths/mcpsmith/index.js +17 -0
  413. package/dist/src/smiths/mcpsmith/types.js +10 -0
  414. package/dist/src/smiths/platform-paths.js +101 -0
  415. package/dist/src/smiths/skillsmith/codex-sidecar.js +85 -0
  416. package/dist/src/smiths/skillsmith/collision-detector.js +249 -0
  417. package/dist/src/smiths/skillsmith/demo.js +83 -0
  418. package/dist/src/smiths/skillsmith/examples.js +161 -0
  419. package/dist/src/smiths/skillsmith/generator.js +316 -0
  420. package/dist/src/smiths/skillsmith/index.js +12 -0
  421. package/dist/src/smiths/skillsmith/namespace-adapter.js +180 -0
  422. package/dist/src/smiths/skillsmith/platform-resolver.js +157 -0
  423. package/dist/src/smiths/skillsmith/types.js +9 -0
  424. package/dist/src/smiths/toolsmith/index.js +10 -0
  425. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +710 -0
  426. package/dist/src/smiths/toolsmith/types.js +8 -0
  427. package/dist/src/storage/backends/fortemi.js +227 -0
  428. package/dist/src/storage/backends/fs.js +134 -0
  429. package/dist/src/storage/backends/logseq.js +309 -0
  430. package/dist/src/storage/backends/obsidian.js +233 -0
  431. package/dist/src/storage/cli.js +468 -0
  432. package/dist/src/storage/config.js +272 -0
  433. package/dist/src/storage/index.js +108 -0
  434. package/dist/src/storage/subsystem-cli.js +201 -0
  435. package/dist/src/storage/types.js +33 -0
  436. package/dist/src/testing/corpus/corpus-builder.js +383 -0
  437. package/dist/src/testing/corpus/ground-truth-manager.js +460 -0
  438. package/dist/src/testing/corpus/index.js +10 -0
  439. package/dist/src/testing/fixtures/index.js +9 -0
  440. package/dist/src/testing/fixtures/test-data-factory.js +472 -0
  441. package/dist/src/testing/generators/index.js +11 -0
  442. package/dist/src/testing/generators/test-case-generator.js +393 -0
  443. package/dist/src/testing/generators/test-code-generator.js +384 -0
  444. package/dist/src/testing/generators/use-case-parser.js +325 -0
  445. package/dist/src/testing/index.js +11 -0
  446. package/dist/src/tracker/capability-protocol.js +118 -0
  447. package/dist/src/update/checker.mjs +396 -0
  448. package/dist/src/update/notifier.mjs +235 -0
  449. package/dist/src/writing/content-diversifier.js +804 -0
  450. package/dist/src/writing/example-generator.js +959 -0
  451. package/dist/src/writing/example-templates.js +99 -0
  452. package/dist/src/writing/pattern-library.js +486 -0
  453. package/dist/src/writing/patterns/banned-phrases.json +4413 -0
  454. package/dist/src/writing/patterns/formulaic-structures.json +1300 -0
  455. package/dist/src/writing/patterns/generic-adjectives.json +1510 -0
  456. package/dist/src/writing/patterns/hedging-language.json +2096 -0
  457. package/dist/src/writing/patterns/transition-words.json +1285 -0
  458. package/dist/src/writing/patterns/weak-verbs.json +1112 -0
  459. package/dist/src/writing/prompt-optimizer.js +660 -0
  460. package/dist/src/writing/prompt-templates.js +583 -0
  461. package/dist/src/writing/scoring-config-loader.js +168 -0
  462. package/dist/src/writing/validation-engine.js +595 -0
  463. package/dist/src/writing/validation-rules.js +380 -0
  464. package/dist/src/writing/voice-analyzer.js +439 -0
  465. package/dist/src/writing/voice-calibration.js +661 -0
  466. package/dist/src/writing/voice-profiles.json +588 -0
  467. package/package.json +65 -9
@@ -0,0 +1,1955 @@
1
+ /**
2
+ * Artifact Query Engine
3
+ *
4
+ * Searches the artifact index by keyword, type, phase, tags, and path pattern.
5
+ * Returns ranked results in human-readable or JSON format.
6
+ *
7
+ * @implements #416
8
+ * @source @src/artifacts/types.ts
9
+ * @tests @test/unit/artifacts/query-engine.test.ts
10
+ */
11
+ import { minimatch } from 'minimatch';
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import { OPERATIONAL_DISCOVERY_TYPES, OPERATIONAL_SHOW_TYPES, isOperationalShowType, loadGlobalGraphConfigs, } from './types.js';
15
+ import { loadMetadataIndex, loadGraphIndexFile } from './index-reader.js';
16
+ import { bm25Rank } from './fulltext.js';
17
+ import { parseFrontmatter } from './index-builder.js';
18
+ import { applyFacetFusion, diagnoseFacetActivations, } from './discover-facets.js';
19
+ import { recordTypeForEntry, stableRecordId, } from './browser-export.js';
20
+ import { loadProviderModelMetadata } from '../models/provider-models.js';
21
+ import { operationalStateQueryProjection, } from './operational-state.js';
22
+ import { projectAiwgPath } from '../config/project-artifacts.js';
23
+ function normalizeIndexedPath(entryPath) {
24
+ return entryPath.replace(/\\/g, '/');
25
+ }
26
+ /**
27
+ * Resolve an index entry's source file path and read its body (frontmatter
28
+ * stripped). Entry paths are normally relative to the project; framework-graph
29
+ * entries may be absolute or anchored to $AIWG_ROOT. Returns null when the
30
+ * file can't be read (stale index entry) so full-text ranking skips it.
31
+ */
32
+ function readEntryBody(cwd, entryPath) {
33
+ const candidates = [];
34
+ if (path.isAbsolute(entryPath))
35
+ candidates.push(entryPath);
36
+ else {
37
+ const normalizedPath = normalizeIndexedPath(entryPath);
38
+ if (normalizedPath.startsWith('.aiwg/')) {
39
+ candidates.push(projectAiwgPath(cwd, normalizedPath.slice('.aiwg/'.length)));
40
+ }
41
+ candidates.push(path.resolve(cwd, normalizedPath));
42
+ if (process.env.AIWG_ROOT)
43
+ candidates.push(path.resolve(process.env.AIWG_ROOT, normalizedPath));
44
+ }
45
+ for (const p of candidates) {
46
+ try {
47
+ const raw = fs.readFileSync(p, 'utf8');
48
+ return parseFrontmatter(raw).body;
49
+ }
50
+ catch {
51
+ /* try next candidate */
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+ const DEFAULT_ARTIFACT_SEARCH_BACKEND = 'fortemi-core';
57
+ const DISCOVER_TYPE_ORDER = new Map([...OPERATIONAL_DISCOVERY_TYPES, 'hook', 'doc'].map((type, index) => [type, index]));
58
+ function canonicalLocalityRank(entryPath) {
59
+ const normalized = entryPath.replace(/\\/g, '/');
60
+ if (normalized.startsWith('.aiwg/') || normalized.includes('/.aiwg/'))
61
+ return 0;
62
+ if (normalized.includes('/plugins/') || normalized.startsWith('agentic/code/plugins/'))
63
+ return 3;
64
+ if (normalized.includes('/frameworks/') ||
65
+ normalized.includes('/addons/') ||
66
+ normalized.includes('/extensions/') ||
67
+ normalized.startsWith('agentic/code/frameworks/') ||
68
+ normalized.startsWith('agentic/code/addons/') ||
69
+ normalized.startsWith('agentic/code/extensions/')) {
70
+ return 2;
71
+ }
72
+ return 1;
73
+ }
74
+ function graphScope(graph) {
75
+ if (graph === 'project')
76
+ return 'project';
77
+ if (graph === 'user' || graph.startsWith('user-'))
78
+ return 'user';
79
+ if (graph === 'framework')
80
+ return 'packaged';
81
+ if (graph === 'codebase' || graph === 'source')
82
+ return 'codebase';
83
+ return 'custom';
84
+ }
85
+ function withIndexProvenance(entry, graph) {
86
+ return { ...entry, indexGraph: graph, indexScope: graphScope(graph) };
87
+ }
88
+ function discoveryIdForEntry(entry) {
89
+ return stableRecordId(recordTypeForEntry(entry, 'v2'), entry.path);
90
+ }
91
+ function logicalDiscoverKey(entry) {
92
+ const identity = (entry.name ?? entry.title ?? path.basename(entry.path))
93
+ .toLowerCase()
94
+ .replace(/[-_\s]+/g, ' ')
95
+ .trim();
96
+ return `${entry.type}:${identity}`;
97
+ }
98
+ function dedupeDiscoverResults(results) {
99
+ const bestByIdentity = new Map();
100
+ for (const result of results) {
101
+ const key = logicalDiscoverKey(result.entry);
102
+ const existing = bestByIdentity.get(key);
103
+ if (!existing || compareDiscoverResults(result, existing) < 0) {
104
+ bestByIdentity.set(key, result);
105
+ }
106
+ }
107
+ return Array.from(bestByIdentity.values()).sort(compareDiscoverResults);
108
+ }
109
+ function scopeRank(entry) {
110
+ const scope = entry.indexScope;
111
+ if (scope === 'project')
112
+ return 0;
113
+ if (scope === 'user')
114
+ return 1;
115
+ if (scope === 'custom')
116
+ return 2;
117
+ if (scope === 'packaged')
118
+ return 3;
119
+ if (scope === 'codebase')
120
+ return 4;
121
+ return canonicalLocalityRank(entry.path);
122
+ }
123
+ function compareText(left, right) {
124
+ return (left ?? '').localeCompare(right ?? '');
125
+ }
126
+ export function compareDiscoverResults(left, right) {
127
+ const scoreCmp = right.score - left.score;
128
+ if (scoreCmp !== 0)
129
+ return scoreCmp;
130
+ const localityCmp = scopeRank(left.entry) - scopeRank(right.entry);
131
+ if (localityCmp !== 0)
132
+ return localityCmp;
133
+ const typeCmp = (DISCOVER_TYPE_ORDER.get(left.entry.type) ?? 99) -
134
+ (DISCOVER_TYPE_ORDER.get(right.entry.type) ?? 99);
135
+ if (typeCmp !== 0)
136
+ return typeCmp;
137
+ const nameCmp = compareText(left.entry.name ?? left.entry.title, right.entry.name ?? right.entry.title);
138
+ if (nameCmp !== 0)
139
+ return nameCmp;
140
+ return left.entry.path.localeCompare(right.entry.path);
141
+ }
142
+ /**
143
+ * Stop-words to drop when tokenizing a discovery phrase. Keep short —
144
+ * we want the user's verbs and nouns to dominate scoring.
145
+ */
146
+ // Stopwords stripped from a discover QUERY before scoring (#1581). Two classes:
147
+ // 1. Grammatical filler (articles, prepositions, pronouns, question words,
148
+ // asking verbs) — a natural-language intent like "Find an AIWG skill that
149
+ // handles intake forms" carries the same meaning as "intake forms", but the
150
+ // filler tokens inflate the token count: the strict overlap gate is
151
+ // ceil(n/2), so a verbose query raises the bar above what the genuinely
152
+ // relevant short-match artifact can clear, and the (hits/n) term dilutes
153
+ // every score. Dropping filler collapses the query to its content tokens.
154
+ // 2. AIWG meta-type nouns ('aiwg', 'skill', 'agent', 'command', 'rule',
155
+ // 'flow', 'workflow') — in a discover query these carry zero discriminating
156
+ // signal (every result IS an aiwg skill/agent/…; the TYPE is filtered via
157
+ // --type). The content word after them is what matters
158
+ // ("…skill that handles >intake forms<"). Keeping them surfaces incidental
159
+ // junk matches (any artifact mentioning "skill"/"aiwg") above the real one.
160
+ // Only the query is tokenized here — entry fields are substring-matched — so this
161
+ // never changes what text an artifact exposes, only how the query is reduced.
162
+ const SCORE_STOPWORDS = new Set([
163
+ // articles / conjunctions / prepositions / copula
164
+ 'the', 'a', 'an', 'and', 'or', 'of', 'for', 'to', 'in', 'on',
165
+ 'with', 'into', 'from', 'is', 'are', 'be', 'i', 'we', 'my',
166
+ // pronouns / determiners / fillers
167
+ 'it', 'you', 'me', 'us', 'your', 'our', 'this', 'that', 'these', 'those',
168
+ 'there', 'here', 'some', 'any', 'all', 'also', 'please', 'about',
169
+ // question words
170
+ 'how', 'what', 'which', 'where', 'when', 'who', 'why',
171
+ // asking / request verbs ("find a skill that handles …")
172
+ 'find', 'give', 'show', 'need', 'want', 'looking', 'look', 'help',
173
+ 'do', 'does', 'did', 'can', 'could', 'should', 'would', 'will',
174
+ 'handle', 'handles', 'handling',
175
+ // AIWG meta-type nouns — zero discriminating signal in a discover query
176
+ 'aiwg', 'skill', 'skills', 'agent', 'agents', 'command', 'commands',
177
+ 'rule', 'rules', 'flow', 'flows', 'workflow', 'workflows',
178
+ ]);
179
+ /**
180
+ * Tokenize a query phrase into lowercased keywords for multi-word
181
+ * scoring. Splits on whitespace and punctuation; drops stopwords and
182
+ * single-character tokens.
183
+ */
184
+ function tokenize(text) {
185
+ return text
186
+ .toLowerCase()
187
+ .split(/[^a-z0-9-]+/)
188
+ .filter(t => t.length > 1 && !SCORE_STOPWORDS.has(t));
189
+ }
190
+ /**
191
+ * Score a metadata entry against a keyword query.
192
+ *
193
+ * Multi-word queries are tokenized; each token contributes its weighted
194
+ * match across the entry's searchable fields. The full phrase still
195
+ * earns bonus weight when it appears as a contiguous substring or as
196
+ * an exact trigger phrase (the most reliable signal).
197
+ *
198
+ * For AIWG artifact kinds (skills/agents/commands/rules) the entry
199
+ * carries `triggers` (declared activation phrases) and `capability`
200
+ * (one-line description). These get the highest weights so capability
201
+ * search via `aiwg index discover` ranks the right skill on top
202
+ * instead of bottoming out on a path-substring match (#1214).
203
+ */
204
+ /**
205
+ * Normalize a string for exact-name comparison (#1233) — lowercase and
206
+ * collapse `-` / `_` / whitespace runs so hyphenated kernel-skill names
207
+ * like `aiwg-doctor` match queries with spaces ("aiwg doctor") and the
208
+ * rendered title ("AIWG Doctor") matches the slug query.
209
+ */
210
+ function normalizeName(s) {
211
+ return s.toLowerCase().replace(/[-_\s]+/g, ' ').trim();
212
+ }
213
+ function damerauLevenshteinAtMostOne(a, b) {
214
+ if (a === b)
215
+ return true;
216
+ if (Math.abs(a.length - b.length) > 1)
217
+ return false;
218
+ if (a.length === b.length) {
219
+ let firstDiff = -1;
220
+ let diffCount = 0;
221
+ for (let i = 0; i < a.length; i++) {
222
+ if (a[i] !== b[i]) {
223
+ if (firstDiff < 0)
224
+ firstDiff = i;
225
+ diffCount++;
226
+ }
227
+ }
228
+ if (diffCount === 1)
229
+ return true;
230
+ return diffCount === 2
231
+ && firstDiff + 1 < a.length
232
+ && a[firstDiff] === b[firstDiff + 1]
233
+ && a[firstDiff + 1] === b[firstDiff];
234
+ }
235
+ const shorter = a.length < b.length ? a : b;
236
+ const longer = a.length < b.length ? b : a;
237
+ let i = 0;
238
+ let j = 0;
239
+ let edits = 0;
240
+ while (i < shorter.length && j < longer.length) {
241
+ if (shorter[i] === longer[j]) {
242
+ i++;
243
+ j++;
244
+ }
245
+ else {
246
+ edits++;
247
+ if (edits > 1)
248
+ return false;
249
+ j++;
250
+ }
251
+ }
252
+ return true;
253
+ }
254
+ function nearNameMatch(query, name) {
255
+ const queryParts = normalizeName(query).split(/\s+/).filter(Boolean);
256
+ const nameParts = normalizeName(name).split(/\s+/).filter(Boolean);
257
+ if (queryParts.length !== nameParts.length)
258
+ return false;
259
+ return queryParts.every((part, i) => {
260
+ const target = nameParts[i];
261
+ if (part === target)
262
+ return true;
263
+ if (part.length < 5 || target.length < 5)
264
+ return false;
265
+ return damerauLevenshteinAtMostOne(part, target);
266
+ });
267
+ }
268
+ function scoreEntryDetailed(entry, text, opts = {}) {
269
+ const tokens = tokenize(text);
270
+ // Field substring matching uses the stopword-stripped CONTENT phrase, not the
271
+ // raw query (#1581). Without this, a query that reduces to a single content
272
+ // token — e.g. "intake skill" or "find an intake skill" → ["intake"] — sets
273
+ // useMultiToken=false (1 token) and then fails the full-string `.includes()`
274
+ // checks because the raw phrase ("intake skill") isn't a substring of any
275
+ // field, while the one meaningful token is never matched. Joining the content
276
+ // tokens restores the invariant that a single content token IS the phrase, and
277
+ // for multi-token queries gives a tighter (content-only) phrase to match.
278
+ // Pure-stopword queries fall back to the raw text.
279
+ const lower = tokens.length > 0 ? tokens.join(' ') : text.toLowerCase().trim();
280
+ const rawLower = text.toLowerCase().trim();
281
+ const personaIdentitySuppressed = diagnoseFacetActivations(text).some((activation) => activation.facet === 'persona-identity' && activation.status === 'suppressed');
282
+ let score = 0;
283
+ const matches = [];
284
+ const finish = (uncappedScore = score, cap = 1) => ({
285
+ score: Math.min(uncappedScore, cap),
286
+ diagnostic: {
287
+ content_tokens: tokens,
288
+ raw_score: uncappedScore,
289
+ score: Math.min(uncappedScore, cap),
290
+ score_cap_applied: uncappedScore > cap,
291
+ matches,
292
+ },
293
+ });
294
+ const addMatch = (contribution, match) => {
295
+ score += contribution;
296
+ matches.push({ ...match, contribution });
297
+ };
298
+ // Exact-name floor (#1233) — if the query (normalized) exactly matches
299
+ // the entry's canonical name, this is the artifact the user is asking
300
+ // for and it must surface at the top regardless of how cluttered the
301
+ // rest of the corpus scoring gets. Hyphens, underscores, and whitespace
302
+ // are interchangeable in the comparison so `aiwg-doctor`, `aiwg doctor`,
303
+ // and `aiwg_doctor` all match a kernel skill with `name: aiwg-doctor`.
304
+ //
305
+ // Returns 1.001 (not 1.0) so an exact-name hit sorts above the many
306
+ // capped-at-1.0 substring matches that share generic words like "use".
307
+ // Display rounding (Math.round(score * 100) / 100) hides the offset, so
308
+ // the user still sees `score: 1.0` while sort order is preserved.
309
+ if (entry.name) {
310
+ const queryNorm = normalizeName(text);
311
+ const nameNorm = normalizeName(entry.name);
312
+ if (queryNorm === nameNorm) {
313
+ matches.push({
314
+ field: 'name',
315
+ match: 'exact',
316
+ value: entry.name,
317
+ contribution: 1.001,
318
+ matched_tokens: tokens,
319
+ query_token_coverage: 1,
320
+ });
321
+ return finish(1.001, 1.001);
322
+ }
323
+ if (nearNameMatch(text, entry.name)) {
324
+ matches.push({
325
+ field: 'name',
326
+ match: 'near-name',
327
+ value: entry.name,
328
+ contribution: 0.951,
329
+ matched_tokens: tokens,
330
+ query_token_coverage: 1,
331
+ });
332
+ return finish(0.951);
333
+ }
334
+ }
335
+ // Searchable text — joined once so per-token includes() is cheap
336
+ const titleLower = entry.title.toLowerCase();
337
+ const summaryLower = entry.summary.toLowerCase();
338
+ const pathLower = entry.path.toLowerCase();
339
+ const typeLower = entry.type.toLowerCase();
340
+ const capabilityLower = entry.capability ? entry.capability.toLowerCase() : '';
341
+ const tagsLower = entry.tags.map(t => t.toLowerCase());
342
+ const triggersLower = (entry.triggers ?? []).map(trigger => trigger.toLowerCase());
343
+ const searchTermsLower = (entry.searchTerms ?? []).join(' ').toLowerCase();
344
+ const kindLower = entry.kind?.toLowerCase() ?? '';
345
+ const sourceTypeLower = entry.sourceType?.toLowerCase() ?? '';
346
+ // For multi-token queries, require ≥50% token overlap to count
347
+ // partial matches. This keeps gibberish queries (e.g.,
348
+ // `xyzzy_zzqwkjhg_42` after splitting on `_`) from surfacing
349
+ // incidental single-token hits.
350
+ const useMultiToken = tokens.length > 1;
351
+ // #1561 — verbose natural-language queries (e.g. "skill that handles intake
352
+ // forms") dilute the token hit ratio below the ceil(n/2) overlap gate and
353
+ // dead-end to zero results. When the strict pass finds nothing, callers
354
+ // re-score with `relaxOverlap` so a single meaningful token hit (intake /
355
+ // forms) still surfaces ranked candidates instead of an empty set.
356
+ const minHits = useMultiToken
357
+ ? (opts.relaxOverlap ? 1 : Math.ceil(tokens.length / 2))
358
+ : 1;
359
+ const overlapOK = (hits) => useMultiToken && hits >= minHits;
360
+ // Trigger phrase match — highest weight (4x). Exact match on the full
361
+ // phrase wins big; substring or token-overlap is still strong.
362
+ if (triggersLower.length > 0) {
363
+ for (const trigger of triggersLower) {
364
+ const triggerTokens = tokenize(trigger);
365
+ const matchedTokens = tokens.filter((token) => triggerTokens.includes(token));
366
+ const queryCoverage = tokens.length > 0 ? matchedTokens.length / tokens.length : 0;
367
+ if (trigger === lower || trigger === rawLower) {
368
+ matches.push({
369
+ field: 'trigger',
370
+ match: 'exact',
371
+ value: trigger,
372
+ contribution: 1.0008,
373
+ matched_tokens: matchedTokens,
374
+ query_token_coverage: 1,
375
+ });
376
+ return finish(1.0008, 1.0008);
377
+ }
378
+ else if (trigger.includes(lower) ||
379
+ lower.includes(trigger) ||
380
+ trigger.includes(rawLower) ||
381
+ rawLower.includes(trigger)) {
382
+ const triggerInsideQuery = lower.includes(trigger) || rawLower.includes(trigger);
383
+ const containedCoverage = triggerInsideQuery
384
+ ? queryCoverage
385
+ : triggerTokens.length > 0
386
+ ? matchedTokens.length / triggerTokens.length
387
+ : 0;
388
+ // A complete multi-word trigger inside a noisier query is a strong
389
+ // metadata signal. The ambiguous one-word `persona` trigger is
390
+ // downweighted only when the facet classifier has identified buyer /
391
+ // audience marketing context; distinctive one-word triggers such as
392
+ // GRADE retain their established exact-trigger behavior (#1828).
393
+ const suppressedGenericPersona = personaIdentitySuppressed &&
394
+ triggerTokens.length === 1 &&
395
+ triggerTokens[0] === 'persona' &&
396
+ tokens.length > 1;
397
+ const contribution = suppressedGenericPersona
398
+ ? 0.24 * queryCoverage
399
+ : triggerTokens.length > 1
400
+ ? Math.min(1, 0.75 + 0.25 * containedCoverage)
401
+ : 1;
402
+ addMatch(contribution, {
403
+ field: 'trigger',
404
+ match: 'contained-phrase',
405
+ value: trigger,
406
+ matched_tokens: matchedTokens,
407
+ query_token_coverage: queryCoverage,
408
+ });
409
+ }
410
+ else if (useMultiToken) {
411
+ const hits = tokens.filter(t => trigger.includes(t));
412
+ if (overlapOK(hits.length)) {
413
+ addMatch(0.06 * 4 * (hits.length / tokens.length), {
414
+ field: 'trigger',
415
+ match: 'token-overlap',
416
+ value: trigger,
417
+ matched_tokens: hits,
418
+ query_token_coverage: hits.length / tokens.length,
419
+ });
420
+ }
421
+ }
422
+ }
423
+ }
424
+ // Capability description (2x weight) — full phrase first, then tokens
425
+ if (capabilityLower) {
426
+ if (capabilityLower.includes(lower)) {
427
+ addMatch(0.2 * 2, {
428
+ field: 'capability',
429
+ match: 'contained-phrase',
430
+ value: entry.capability,
431
+ });
432
+ }
433
+ else if (useMultiToken) {
434
+ const hits = tokens.filter(t => capabilityLower.includes(t));
435
+ if (overlapOK(hits.length)) {
436
+ addMatch(0.1 * 2 * (hits.length / tokens.length), {
437
+ field: 'capability',
438
+ match: 'token-overlap',
439
+ value: entry.capability,
440
+ matched_tokens: hits,
441
+ query_token_coverage: hits.length / tokens.length,
442
+ });
443
+ }
444
+ }
445
+ }
446
+ // Title (3x weight)
447
+ if (titleLower.includes(lower)) {
448
+ addMatch(0.3 * 3, {
449
+ field: 'title',
450
+ match: titleLower === lower ? 'exact' : 'contained-phrase',
451
+ value: entry.title,
452
+ });
453
+ if (titleLower === lower) {
454
+ addMatch(0.2, { field: 'title', match: 'exact', value: entry.title });
455
+ }
456
+ }
457
+ else if (useMultiToken) {
458
+ const hits = tokens.filter(t => titleLower.includes(t));
459
+ if (overlapOK(hits.length)) {
460
+ addMatch(0.08 * 3 * (hits.length / tokens.length), {
461
+ field: 'title',
462
+ match: 'token-overlap',
463
+ value: entry.title,
464
+ matched_tokens: hits,
465
+ query_token_coverage: hits.length / tokens.length,
466
+ });
467
+ }
468
+ }
469
+ // Tags (2x weight)
470
+ for (const tag of tagsLower) {
471
+ if (tag.includes(lower)) {
472
+ addMatch(0.2 * 2, { field: 'tag', match: 'contained-phrase', value: tag });
473
+ }
474
+ else if (useMultiToken) {
475
+ const hits = tokens.filter(t => tag.includes(t));
476
+ if (overlapOK(hits.length)) {
477
+ addMatch(0.05 * 2 * (hits.length / tokens.length), {
478
+ field: 'tag',
479
+ match: 'token-overlap',
480
+ value: tag,
481
+ matched_tokens: hits,
482
+ query_token_coverage: hits.length / tokens.length,
483
+ });
484
+ }
485
+ }
486
+ }
487
+ // Structure-aware language terms (1.5x weight). These are deliberately
488
+ // below declared triggers/capabilities but above generic body summaries.
489
+ if (searchTermsLower.includes(lower)) {
490
+ addMatch(0.18 * 1.5, { field: 'search_terms', match: 'contained-phrase' });
491
+ }
492
+ else if (useMultiToken) {
493
+ const hits = tokens.filter(t => searchTermsLower.includes(t));
494
+ if (overlapOK(hits.length)) {
495
+ addMatch(0.06 * 1.5 * (hits.length / tokens.length), {
496
+ field: 'search_terms',
497
+ match: 'token-overlap',
498
+ matched_tokens: hits,
499
+ query_token_coverage: hits.length / tokens.length,
500
+ });
501
+ }
502
+ }
503
+ // Exact declarative kind and physical source classification are compact,
504
+ // useful routing signals (e.g. FlowPlaybook vs OpsInventory; runbook that
505
+ // originated under templates/).
506
+ if (kindLower.includes(lower)) {
507
+ addMatch(0.15, { field: 'kind', match: 'contained-phrase', value: entry.kind });
508
+ }
509
+ if (sourceTypeLower.includes(lower)) {
510
+ addMatch(0.08, { field: 'source_type', match: 'contained-phrase', value: entry.sourceType });
511
+ }
512
+ // Summary (1x weight)
513
+ if (summaryLower.includes(lower)) {
514
+ addMatch(0.15, { field: 'summary', match: 'contained-phrase' });
515
+ }
516
+ else if (useMultiToken) {
517
+ const hits = tokens.filter(t => summaryLower.includes(t));
518
+ if (overlapOK(hits.length)) {
519
+ addMatch(0.04 * (hits.length / tokens.length), {
520
+ field: 'summary',
521
+ match: 'token-overlap',
522
+ matched_tokens: hits,
523
+ query_token_coverage: hits.length / tokens.length,
524
+ });
525
+ }
526
+ }
527
+ // Path (0.5x weight)
528
+ if (pathLower.includes(lower)) {
529
+ addMatch(0.1, { field: 'path', match: 'contained-phrase', value: entry.path });
530
+ }
531
+ else if (useMultiToken) {
532
+ const hits = tokens.filter(t => pathLower.includes(t));
533
+ if (overlapOK(hits.length)) {
534
+ addMatch(0.03 * (hits.length / tokens.length), {
535
+ field: 'path',
536
+ match: 'token-overlap',
537
+ value: entry.path,
538
+ matched_tokens: hits,
539
+ query_token_coverage: hits.length / tokens.length,
540
+ });
541
+ }
542
+ }
543
+ // Type (0.5x weight)
544
+ if (typeLower.includes(lower)) {
545
+ addMatch(0.1, { field: 'type', match: 'contained-phrase', value: entry.type });
546
+ }
547
+ return finish();
548
+ }
549
+ function scoreEntry(entry, text, opts = {}) {
550
+ return scoreEntryDetailed(entry, text, opts).score;
551
+ }
552
+ /**
553
+ * Query the artifact index
554
+ */
555
+ export async function queryIndex(cwd, params, options = {}) {
556
+ const { graph } = options;
557
+ const backend = options.backend ?? DEFAULT_ARTIFACT_SEARCH_BACKEND;
558
+ const startTime = Date.now();
559
+ let candidates;
560
+ if (backend === 'fortemi-core') {
561
+ const { loadFortemiCoreMetadataEntries } = await import('./fortemi-core-query-adapter.js');
562
+ const loaded = loadFortemiCoreMetadataEntries(cwd, graph ?? 'project');
563
+ if (loaded.reason) {
564
+ if (options.json) {
565
+ console.log(JSON.stringify({
566
+ query: { text: params.text, backend: 'fortemi-core', graph: graph ?? 'project' },
567
+ results: [],
568
+ total: 0,
569
+ hint: loaded.reason ?? 'Fortemi Core static index is unavailable.',
570
+ }, null, 2));
571
+ }
572
+ else {
573
+ console.error('Error: ' + (loaded.reason ?? 'Fortemi Core static index is unavailable.'));
574
+ }
575
+ process.exit(1);
576
+ }
577
+ candidates = loaded.entries;
578
+ }
579
+ else if (graph) {
580
+ // Single graph mode
581
+ const index = loadGraphIndexFile(cwd, 'metadata.json', graph);
582
+ if (!index) {
583
+ console.error(`Error: No artifact index found for graph '${graph}'.`);
584
+ console.log("Run 'aiwg index build' first to create the index.");
585
+ process.exit(1);
586
+ }
587
+ candidates = Object.values(index.entries);
588
+ }
589
+ else {
590
+ // No graph specified: search across all project-local graphs
591
+ const graphTypes = ['project', 'codebase'];
592
+ const allEntries = [];
593
+ for (const g of graphTypes) {
594
+ const idx = loadGraphIndexFile(cwd, 'metadata.json', g);
595
+ if (idx)
596
+ allEntries.push(...Object.values(idx.entries));
597
+ }
598
+ // Fall back to legacy root index
599
+ if (allEntries.length === 0) {
600
+ const legacy = loadMetadataIndex(cwd);
601
+ if (!legacy) {
602
+ console.error('Error: No artifact index found.');
603
+ console.log("Run 'aiwg index build' first to create the index.");
604
+ process.exit(1);
605
+ }
606
+ allEntries.push(...Object.values(legacy.entries));
607
+ }
608
+ candidates = allEntries;
609
+ }
610
+ // Apply filters
611
+ if (params.type) {
612
+ candidates = candidates.filter(e => e.type === params.type);
613
+ }
614
+ if (params.phase) {
615
+ candidates = candidates.filter(e => e.phase === params.phase);
616
+ }
617
+ if (params.tags && params.tags.length > 0) {
618
+ candidates = candidates.filter(e => params.tags.every(tag => e.tags.includes(tag)));
619
+ }
620
+ if (params.path) {
621
+ candidates = candidates.filter(e => minimatch(e.path, params.path));
622
+ }
623
+ if (params.updatedAfter) {
624
+ const cutoff = new Date(params.updatedAfter).getTime();
625
+ candidates = candidates.filter(e => new Date(e.updated).getTime() >= cutoff);
626
+ }
627
+ // Score and rank
628
+ let results;
629
+ const matchedById = new Map();
630
+ const operationalStateById = new Map();
631
+ if (params.text && params.fulltext && backend === 'fortemi-core') {
632
+ const { queryFortemiCoreStaticFulltextIndex } = await import('./fortemi-core-query-adapter.js');
633
+ const queried = queryFortemiCoreStaticFulltextIndex(cwd, {
634
+ graph: graph ?? 'project',
635
+ text: params.text,
636
+ limit: params.limit ?? 20,
637
+ path: params.path,
638
+ type: params.type,
639
+ phase: params.phase,
640
+ tags: params.tags,
641
+ });
642
+ if (queried.reason) {
643
+ if (options.json) {
644
+ console.log(JSON.stringify({
645
+ query: { text: params.text, backend: 'fortemi-core', graph: graph ?? 'project' },
646
+ mode: 'fulltext',
647
+ results: [],
648
+ total: 0,
649
+ hint: queried.reason,
650
+ }, null, 2));
651
+ }
652
+ else {
653
+ console.error('Error: ' + queried.reason);
654
+ }
655
+ process.exit(1);
656
+ }
657
+ results = queried.results.map(result => {
658
+ const entry = {
659
+ path: result.path,
660
+ type: result.type,
661
+ phase: result.phase,
662
+ title: result.title,
663
+ tags: result.tags,
664
+ created: '',
665
+ updated: '',
666
+ checksum: '',
667
+ summary: result.summary,
668
+ dependencies: [],
669
+ dependents: [],
670
+ };
671
+ matchedById.set(result.path, result.matched);
672
+ if (result.operational_state) {
673
+ operationalStateById.set(result.path, result.operational_state);
674
+ }
675
+ return { entry, score: result.score };
676
+ });
677
+ }
678
+ else if (params.text && params.fulltext) {
679
+ // Lexical full-text (#1494): read candidate bodies and BM25-rank. The
680
+ // index is the candidate set; ranking is over actual body content, not
681
+ // the metadata/summary the default path scores.
682
+ const byPath = new Map();
683
+ const docs = [];
684
+ for (const entry of candidates) {
685
+ const body = readEntryBody(cwd, entry.path);
686
+ if (body === null)
687
+ continue;
688
+ byPath.set(entry.path, entry);
689
+ docs.push({ id: entry.path, text: body });
690
+ }
691
+ const hits = bm25Rank(docs, params.text);
692
+ results = hits
693
+ .map(h => {
694
+ matchedById.set(h.id, h.matchedTerms);
695
+ return { entry: byPath.get(h.id), score: h.score };
696
+ })
697
+ .filter(r => r.entry);
698
+ }
699
+ else if (params.text) {
700
+ results = candidates
701
+ .map(entry => ({ entry, score: scoreEntry(entry, params.text) }))
702
+ .filter(r => r.score > 0)
703
+ .sort(compareDiscoverResults);
704
+ }
705
+ else {
706
+ // No keyword — return all filtered results with score 1.0
707
+ results = candidates.map(entry => ({ entry, score: 1.0 }));
708
+ }
709
+ // Apply limit
710
+ const limit = params.limit ?? 20;
711
+ results = results.slice(0, limit);
712
+ const queryTimeMs = Date.now() - startTime;
713
+ // Output
714
+ if (options.json) {
715
+ console.log(JSON.stringify({
716
+ query: { text: params.text, filters: { type: params.type, phase: params.phase, tags: params.tags, path: params.path }, backend },
717
+ mode: params.fulltext ? 'fulltext' : 'metadata',
718
+ results: results.map(r => ({
719
+ path: r.entry.path,
720
+ type: r.entry.type,
721
+ phase: r.entry.phase,
722
+ title: r.entry.title,
723
+ score: Math.round(r.score * 100) / 100,
724
+ summary: r.entry.summary,
725
+ ...(operationalStateById.has(r.entry.path)
726
+ ? { operational_state: operationalStateById.get(r.entry.path) }
727
+ : r.entry.operationalState
728
+ ? { operational_state: operationalStateQueryProjection(r.entry.operationalState) }
729
+ : {}),
730
+ ...(params.fulltext ? { matched: matchedById.get(r.entry.path) ?? [] } : {}),
731
+ })),
732
+ total: results.length,
733
+ query_time_ms: queryTimeMs,
734
+ }, null, 2));
735
+ }
736
+ else {
737
+ const queryDesc = params.text ? `"${params.text}"` : 'all';
738
+ console.log(`Results for ${queryDesc} (${results.length} matches, ${queryTimeMs}ms):`);
739
+ console.log('');
740
+ console.log(' # Score Type Phase Path');
741
+ for (let i = 0; i < results.length; i++) {
742
+ const r = results[i];
743
+ const num = String(i + 1).padStart(3);
744
+ const score = r.score.toFixed(2).padStart(4);
745
+ const type = r.entry.type.padEnd(12).slice(0, 12);
746
+ const phase = r.entry.phase.padEnd(14).slice(0, 14);
747
+ console.log(` ${num} ${score} ${type} ${phase} ${r.entry.path}`);
748
+ }
749
+ if (results.length === 0) {
750
+ console.log(' No results found.');
751
+ }
752
+ console.log('');
753
+ }
754
+ }
755
+ const DEFAULT_DISCOVER_TYPES = [...OPERATIONAL_DISCOVERY_TYPES];
756
+ const DEFAULT_CAPABILITY_GRAPHS = ['project', 'user', 'framework'];
757
+ function projectAllowsUserIndices(cwd) {
758
+ try {
759
+ const configPath = projectAiwgPath(cwd, 'aiwg.config');
760
+ if (!fs.existsSync(configPath))
761
+ return true;
762
+ const parsed = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
763
+ const index = parsed.index;
764
+ const userIndices = index?.userIndices;
765
+ if (userIndices?.enabled === false)
766
+ return false;
767
+ const indices = parsed.indices;
768
+ const user = indices?.user;
769
+ if (user?.enabled === false)
770
+ return false;
771
+ }
772
+ catch {
773
+ return true;
774
+ }
775
+ return true;
776
+ }
777
+ function defaultCapabilityGraphs(cwd) {
778
+ if (projectAllowsUserIndices(cwd))
779
+ return DEFAULT_CAPABILITY_GRAPHS;
780
+ return DEFAULT_CAPABILITY_GRAPHS.filter((graph) => graph !== 'user' && !graph.startsWith('user-'));
781
+ }
782
+ /**
783
+ * Resolve the AIWG installation root for path-anchoring discover output.
784
+ * Discover returns paths anchored to AIWG_ROOT so the agent's `Read`
785
+ * tool can resolve them from any project working directory (#1217).
786
+ */
787
+ /**
788
+ * Build the `run_hint` string for a script-bearing skill entry (#1227).
789
+ *
790
+ * Format: `aiwg run skill <name> [-- <argsHint>]`. The skill name is the
791
+ * basename of its source directory (skills/<name>/SKILL.md), which is
792
+ * what `aiwg run skill` accepts.
793
+ */
794
+ function buildRunHint(entry) {
795
+ // Use the directory basename for slug-layout skills, filename stem otherwise.
796
+ const lastSlash = entry.path.lastIndexOf('/');
797
+ const tail = lastSlash >= 0 ? entry.path.slice(lastSlash + 1) : entry.path;
798
+ let name;
799
+ if (tail === 'SKILL.md' && lastSlash >= 0) {
800
+ const dir = entry.path.slice(0, lastSlash);
801
+ const dirSlash = dir.lastIndexOf('/');
802
+ name = dirSlash >= 0 ? dir.slice(dirSlash + 1) : dir;
803
+ }
804
+ else {
805
+ name = tail.replace(/\.[^.]+$/, '');
806
+ }
807
+ const argsHint = entry.script?.argsHint ? ` -- ${entry.script.argsHint}` : '';
808
+ return `aiwg run skill ${name}${argsHint}`;
809
+ }
810
+ function showHintForDiscoverResult(entry, id) {
811
+ if (isOperationalShowType(entry.type)) {
812
+ return `aiwg show ${entry.type} ${id}`;
813
+ }
814
+ return `aiwg show ${id}`;
815
+ }
816
+ async function getAiwgRootForDiscover() {
817
+ if (process.env.AIWG_ROOT)
818
+ return process.env.AIWG_ROOT;
819
+ try {
820
+ const mod = await import('../channel/manager.mjs');
821
+ if (typeof mod.getFrameworkRoot === 'function') {
822
+ const r = await mod.getFrameworkRoot();
823
+ return r || null;
824
+ }
825
+ }
826
+ catch {
827
+ // channel manager unavailable — fall through
828
+ }
829
+ return null;
830
+ }
831
+ export async function discoverCapability(cwd, params) {
832
+ loadGlobalGraphConfigs();
833
+ const startTime = Date.now();
834
+ const types = params.typeFilter && params.typeFilter.length > 0
835
+ ? params.typeFilter
836
+ : DEFAULT_DISCOVER_TYPES;
837
+ // Default top-K = 5 (Wave A from #1218): peer-reviewed work on tool
838
+ // retrieval (Semantic Tool Discovery for MCP, arXiv:2603.20313) reports
839
+ // 97.1% hit@K=3 at scale; K=5 keeps a buffer above K=3 while halving
840
+ // the prior K=10 default. Operators wanting more breadth can pass
841
+ // `--limit N` explicitly.
842
+ const limit = params.limit ?? 5;
843
+ // For framework-graph entries, anchor returned paths to AIWG_ROOT so
844
+ // they resolve from any project working directory (#1217).
845
+ const aiwgRoot = await getAiwgRootForDiscover();
846
+ // Source: prefer `framework` graph (built post-deploy), fall back to
847
+ // project / codebase / legacy depending on what's available.
848
+ let entries = [];
849
+ const backend = params.backend ?? DEFAULT_ARTIFACT_SEARCH_BACKEND;
850
+ let fortemiCoreDiscoveryScores = null;
851
+ let resourceSource = params.resourceSource ?? 'local';
852
+ let resolvedWebRelease;
853
+ let verifiedSourcePaths;
854
+ if (resourceSource === 'auto') {
855
+ const { loadFortemiCoreMetadataEntries } = await import('./fortemi-core-query-adapter.js');
856
+ const localGraphs = params.graph ? [params.graph] : defaultCapabilityGraphs(cwd);
857
+ const localAvailable = localGraphs.some((graph) => loadFortemiCoreMetadataEntries(cwd, graph).entries.length > 0);
858
+ resourceSource = localAvailable ? 'local' : 'web';
859
+ }
860
+ if (resourceSource === 'web') {
861
+ if (backend !== 'fortemi-core') {
862
+ console.error('Error: --resource-source web requires the Fortemi Core query backend; --backend local remains available with local resources.');
863
+ process.exit(1);
864
+ }
865
+ if (params.graph && params.graph !== 'framework') {
866
+ console.error("Error: web-backed capability discovery currently supports only the 'framework' graph.");
867
+ process.exit(1);
868
+ }
869
+ try {
870
+ const { resolveWebRelease } = await import('../resources/web-release.js');
871
+ const release = await resolveWebRelease({
872
+ ...params.webReleaseOptions,
873
+ selector: params.aiwgVersion ?? 'stable',
874
+ offline: params.offline,
875
+ });
876
+ resolvedWebRelease = release;
877
+ verifiedSourcePaths = {
878
+ manifestPath: release.fortemiManifestPath,
879
+ exportPath: release.fortemiExportPath,
880
+ manifestSha256: release.fortemiManifestSha256,
881
+ manifestSize: release.fortemiManifestSize,
882
+ exportSha256: release.fortemiExportSha256,
883
+ exportSize: release.fortemiExportSize,
884
+ };
885
+ }
886
+ catch (error) {
887
+ console.error(`Error: AIWG web resource resolution failed: ${error instanceof Error ? error.message : String(error)}`);
888
+ process.exit(1);
889
+ }
890
+ }
891
+ if (backend === 'fortemi-core') {
892
+ const graphs = resourceSource === 'web'
893
+ ? ['framework']
894
+ : params.graph ? [params.graph] : defaultCapabilityGraphs(cwd);
895
+ const { loadFortemiCoreMetadataEntries, queryFortemiCoreAiwgDiscovery, } = await import('./fortemi-core-query-adapter.js');
896
+ let unavailableReason;
897
+ for (const graph of graphs) {
898
+ const loaded = loadFortemiCoreMetadataEntries(cwd, graph, verifiedSourcePaths);
899
+ if (loaded.reason)
900
+ unavailableReason ??= loaded.reason;
901
+ entries.push(...loaded.entries.map((entry) => withIndexProvenance(entry, graph)));
902
+ const discovered = await queryFortemiCoreAiwgDiscovery(cwd, {
903
+ graph,
904
+ text: params.phrase,
905
+ limit: Math.max(limit * 10, 50),
906
+ types,
907
+ sourcePaths: verifiedSourcePaths,
908
+ });
909
+ if (!discovered.reason) {
910
+ fortemiCoreDiscoveryScores ??= new Map();
911
+ for (const result of discovered.results) {
912
+ if (result.score <= 0)
913
+ continue;
914
+ fortemiCoreDiscoveryScores.set(result.entry.path, Math.max(fortemiCoreDiscoveryScores.get(result.entry.path) ?? 0, result.score));
915
+ }
916
+ }
917
+ }
918
+ if (entries.length === 0 && unavailableReason) {
919
+ if (params.json) {
920
+ console.log(JSON.stringify({
921
+ query: {
922
+ phrase: params.phrase,
923
+ types,
924
+ limit,
925
+ backend: 'fortemi-core',
926
+ graph: resourceSource === 'web' ? 'framework' : params.graph ?? 'capability-default',
927
+ ...(resourceSource === 'web'
928
+ ? {
929
+ resource_source: 'web',
930
+ aiwg_selector: resolvedWebRelease?.selector,
931
+ aiwg_version: resolvedWebRelease?.version,
932
+ manifest_sha256: resolvedWebRelease?.manifestDigest,
933
+ manifest_url: resolvedWebRelease?.manifestUrl,
934
+ }
935
+ : {}),
936
+ },
937
+ results: [],
938
+ total: 0,
939
+ hint: unavailableReason,
940
+ }, null, 2));
941
+ }
942
+ else {
943
+ console.error('Error: ' + unavailableReason);
944
+ }
945
+ process.exit(1);
946
+ }
947
+ }
948
+ else if (params.graph) {
949
+ const idx = loadGraphIndexFile(cwd, 'metadata.json', params.graph);
950
+ if (idx)
951
+ entries = Object.values(idx.entries).map((entry) => withIndexProvenance(entry, params.graph));
952
+ }
953
+ else {
954
+ // discover is EXCLUSIVELY the agentic-capability surface (#1545): source
955
+ // the `framework` graph (deployed AIWG capabilities) + project-local
956
+ // capability artifacts. NEVER `codebase` — source code is not a capability
957
+ // surface. discover leverages indexing transparently: if the framework
958
+ // index is missing but its source is present, build it once (incremental
959
+ // thereafter) so discover Just Works without the operator picking a graph.
960
+ let fwIdx = loadGraphIndexFile(cwd, 'metadata.json', 'framework');
961
+ if (!fwIdx) {
962
+ // The framework graph is shared (XDG) and `defaultBuild: false`, so a bare
963
+ // `aiwg index build` never (re)builds it — only `aiwg use` or an explicit
964
+ // `--graph framework` does. When it's absent, build it from wherever the
965
+ // AIWG source lives: `cwd/agentic/code` when inside the AIWG repo, else
966
+ // `$AIWG_ROOT/agentic/code` for a USER project (#1541 — opencode users sit
967
+ // in their own project, not the AIWG repo, so a cwd-only check left
968
+ // discover unable to find deployed commands like address-issues). buildIndex
969
+ // scans <root>/agentic/code and writes to the cwd-independent XDG dir, so
970
+ // reading back via cwd still resolves the freshly-built shared index.
971
+ const buildRoot = fs.existsSync(path.join(cwd, 'agentic', 'code'))
972
+ ? cwd
973
+ : aiwgRoot && fs.existsSync(path.join(aiwgRoot, 'agentic', 'code'))
974
+ ? aiwgRoot
975
+ : null;
976
+ if (buildRoot) {
977
+ const { buildIndex } = await import('./index-builder.js');
978
+ if (!params.json)
979
+ console.error('Building capability index (first run)…');
980
+ // buildIndex writes progress to stdout. In --json mode that would corrupt
981
+ // the JSON envelope the caller parses, so silence stdout for the duration
982
+ // of the auto-build (the build is incidental to the query).
983
+ const origLog = console.log;
984
+ if (params.json)
985
+ console.log = () => { };
986
+ try {
987
+ await buildIndex(buildRoot, { graph: 'framework', explicit: false });
988
+ }
989
+ catch {
990
+ /* non-fatal — fall through to whatever index is available */
991
+ }
992
+ finally {
993
+ console.log = origLog;
994
+ }
995
+ fwIdx = loadGraphIndexFile(cwd, 'metadata.json', 'framework');
996
+ }
997
+ }
998
+ const includeUser = projectAllowsUserIndices(cwd);
999
+ // Project-local operational artifacts authored in this project. The
1000
+ // DEFAULT_DISCOVER_TYPES filter keeps non-capability project artifacts
1001
+ // (requirements, ADRs, …) out of results.
1002
+ const projIdx = loadGraphIndexFile(cwd, 'metadata.json', 'project');
1003
+ if (projIdx)
1004
+ entries.push(...Object.values(projIdx.entries).map((entry) => withIndexProvenance(entry, 'project')));
1005
+ if (includeUser) {
1006
+ const userIdx = loadGraphIndexFile(cwd, 'metadata.json', 'user');
1007
+ if (userIdx)
1008
+ entries.push(...Object.values(userIdx.entries).map((entry) => withIndexProvenance(entry, 'user')));
1009
+ }
1010
+ if (fwIdx)
1011
+ entries.push(...Object.values(fwIdx.entries).map((entry) => withIndexProvenance(entry, 'framework')));
1012
+ if (entries.length === 0) {
1013
+ const legacy = loadMetadataIndex(cwd);
1014
+ if (legacy)
1015
+ entries.push(...Object.values(legacy.entries));
1016
+ }
1017
+ }
1018
+ if (entries.length === 0 && backend !== 'fortemi-core') {
1019
+ // Empty-index case (#1221). Surface a hint that explains the gap rather
1020
+ // than returning a bare zero-result envelope — the latter trains agents
1021
+ // to conclude "AIWG doesn't have a skill for that" when in fact the
1022
+ // index simply hasn't been built in this workspace yet.
1023
+ const hint = 'No artifact index found. Run `aiwg index build --graph framework` (or `aiwg use <framework>`) first.';
1024
+ if (params.json) {
1025
+ console.log(JSON.stringify({
1026
+ query: { phrase: params.phrase, types },
1027
+ results: [],
1028
+ total: 0,
1029
+ hint,
1030
+ }, null, 2));
1031
+ }
1032
+ else {
1033
+ console.error('Error: No artifact index found.');
1034
+ console.log('Run `aiwg index build --graph framework` (or `aiwg use <framework>`) first.');
1035
+ }
1036
+ process.exit(1);
1037
+ }
1038
+ // Filter by type
1039
+ const candidates = entries.filter(e => types.includes(e.type));
1040
+ // Score (strict overlap gate). Keep the full sorted list so the facet
1041
+ // fusion below can inject/lift curated capabilities into the top-K before
1042
+ // truncation — a capability that the lexical pass ranked outside `limit`
1043
+ // (or missed entirely) still surfaces when the query activates its facet.
1044
+ const lexicalDiagnostics = new Map();
1045
+ const legacyStrictScored = candidates
1046
+ .map(entry => {
1047
+ const detailed = scoreEntryDetailed(entry, params.phrase);
1048
+ const legacyScore = detailed.score;
1049
+ const fortemiScore = fortemiCoreDiscoveryScores?.get(entry.path) ?? 0;
1050
+ lexicalDiagnostics.set(entry.path, detailed.diagnostic);
1051
+ return {
1052
+ entry,
1053
+ score: legacyScore > 0 ? legacyScore : fortemiScore * 0.2,
1054
+ legacyScore,
1055
+ };
1056
+ })
1057
+ .filter(r => r.score > 0)
1058
+ .sort(compareDiscoverResults);
1059
+ const strictScored = (legacyStrictScored.some(result => result.legacyScore > 0)
1060
+ ? legacyStrictScored.filter(result => result.legacyScore > 0)
1061
+ : legacyStrictScored).map(({ entry, score }) => ({ entry, score }));
1062
+ // Single-pass facet fusion (#1623 U3): fuse the curated feature→capability
1063
+ // facets (expansion / persona / project / provider-capability) into the
1064
+ // lexical ranking so canonical domain phrases rank their owning capability
1065
+ // top-K instead of being out-scored by artifacts that merely mention the
1066
+ // word. Facet activation can also rescue an otherwise-empty strict pass.
1067
+ let scored = dedupeDiscoverResults(await applyFacetFusion(strictScored, candidates, params.phrase)).slice(0, limit);
1068
+ // #1561 — verbose-query fallback. A wordy full-sentence query
1069
+ // ("find me a skill that handles intake forms") dilutes the token hit ratio
1070
+ // below the strict ceil(n/2) overlap gate and returns nothing, training
1071
+ // agents to conclude "no skill exists" — the exact decline-without-search
1072
+ // failure the skill-discovery rule guards against. When the strict pass
1073
+ // dead-ends, re-score with a relaxed (single-hit) overlap so the meaningful
1074
+ // tokens still surface ranked candidates rather than an empty set.
1075
+ let relaxed = false;
1076
+ if (scored.length === 0) {
1077
+ // Floor the relaxed pass so a single incidental path/summary token hit
1078
+ // (~0.006–0.008) doesn't surface as noise. Capability/title/trigger field
1079
+ // hits land ~0.04+, so this keeps meaningful matches while dropping junk —
1080
+ // if nothing clears the floor, we fall through to the no-match hint, which
1081
+ // is more honest than surfacing a 0.01 path match.
1082
+ const RELAXED_MIN_SCORE = 0.02;
1083
+ const relaxedFull = candidates
1084
+ .map(entry => {
1085
+ const detailed = scoreEntryDetailed(entry, params.phrase, { relaxOverlap: true });
1086
+ lexicalDiagnostics.set(entry.path, detailed.diagnostic);
1087
+ return { entry, score: detailed.score };
1088
+ })
1089
+ .filter(r => r.score >= RELAXED_MIN_SCORE)
1090
+ .sort(compareDiscoverResults);
1091
+ const relaxedScored = dedupeDiscoverResults(await applyFacetFusion(relaxedFull, candidates, params.phrase)).slice(0, limit);
1092
+ if (relaxedScored.length > 0) {
1093
+ scored = relaxedScored;
1094
+ relaxed = true;
1095
+ }
1096
+ }
1097
+ const queryTimeMs = Date.now() - startTime;
1098
+ /**
1099
+ * Resolve a stored framework-graph path to an absolute AIWG_ROOT
1100
+ * path (#1217). Framework-graph entries are stored as paths relative
1101
+ * to the AIWG repo root (e.g. `agentic/code/frameworks/.../SKILL.md`);
1102
+ * anchoring them to `AIWG_ROOT` makes them readable from any project
1103
+ * working directory.
1104
+ *
1105
+ * Kernel skills are anchored the same way (#1230) — `aiwg show` reads
1106
+ * the source corpus, not platform deploy mirrors, so kernel entries
1107
+ * need the same `AIWG_ROOT` anchoring as non-kernel framework entries.
1108
+ * Non-framework entries (project / codebase graphs) keep their stored
1109
+ * paths unchanged.
1110
+ */
1111
+ function resolvePath(entry) {
1112
+ const normalizedPath = normalizeIndexedPath(entry.path);
1113
+ if (!aiwgRoot)
1114
+ return normalizedPath;
1115
+ if (path.isAbsolute(entry.path))
1116
+ return entry.path;
1117
+ if (normalizedPath.startsWith('agentic/code/'))
1118
+ return path.join(aiwgRoot, normalizedPath);
1119
+ return normalizedPath;
1120
+ }
1121
+ // Build a hint string when the index has entries but no scored matches —
1122
+ // this is the second silent-failure mode #1221 calls out. When the workspace
1123
+ // has project-local content (.aiwg/{extensions,addons,frameworks,plugins,providers}/)
1124
+ // surface a project-graph rebuild hint (#1235).
1125
+ // discover-appropriate hint (#1545): never push index/graph mechanics. If
1126
+ // nothing scored, the phrase didn't match an indexed capability — suggest a
1127
+ // broader phrase / type filter, and a capability refresh via `aiwg use`.
1128
+ const emptyResultHint = scored.length === 0
1129
+ ? backend === 'fortemi-core'
1130
+ ? `No capability matched "${params.phrase}" among ${entries.length} Fortemi Core static-cache capabilities. Try a broader phrase or a \`--type\` filter. If the local index changed, refresh the cache with \`aiwg index sync\`, or pass \`--backend local\` to use the legacy local index.`
1131
+ : `No capability matched "${params.phrase}" among ${entries.length} indexed capabilities. Try a broader phrase or a \`--type\` filter. If you just installed or authored capabilities, refresh discovery with \`aiwg use <framework>\` (or \`aiwg index build\`).`
1132
+ : null;
1133
+ const includePaths = params.includePaths ?? true;
1134
+ const facetActivations = diagnoseFacetActivations(params.phrase);
1135
+ const roundDiagnostic = (value) => Math.round(value * 1_000_000) / 1_000_000;
1136
+ const rankingDiagnostic = (entry, finalScore, facetDiagnostics) => {
1137
+ const lexical = lexicalDiagnostics.get(entry.path) ?? {
1138
+ content_tokens: tokenize(params.phrase),
1139
+ raw_score: 0,
1140
+ score: 0,
1141
+ score_cap_applied: false,
1142
+ matches: [],
1143
+ };
1144
+ const scope = entry.indexScope ?? 'unknown';
1145
+ return {
1146
+ lexical_score: roundDiagnostic(lexical.score),
1147
+ lexical_raw_score: roundDiagnostic(lexical.raw_score),
1148
+ final_score: roundDiagnostic(finalScore),
1149
+ score_cap_applied: lexical.score_cap_applied,
1150
+ matches: lexical.matches.map((match) => ({
1151
+ ...match,
1152
+ contribution: roundDiagnostic(match.contribution),
1153
+ ...(match.query_token_coverage === undefined
1154
+ ? {}
1155
+ : { query_token_coverage: roundDiagnostic(match.query_token_coverage) }),
1156
+ })),
1157
+ facets: (facetDiagnostics ?? []).map((facet) => ({
1158
+ ...facet,
1159
+ activation_floor: roundDiagnostic(facet.activation_floor),
1160
+ base_score: roundDiagnostic(facet.base_score),
1161
+ rrf_tiebreak: roundDiagnostic(facet.rrf_tiebreak),
1162
+ })),
1163
+ tie_breakers: {
1164
+ scope,
1165
+ scope_rank: scopeRank(entry),
1166
+ type_rank: DISCOVER_TYPE_ORDER.get(entry.type) ?? 99,
1167
+ },
1168
+ };
1169
+ };
1170
+ if (params.json) {
1171
+ const jsonIndent = params.jsonPretty === false ? undefined : 2;
1172
+ console.log(JSON.stringify({
1173
+ query: {
1174
+ phrase: params.phrase,
1175
+ types,
1176
+ limit,
1177
+ aiwg_root: aiwgRoot ?? null,
1178
+ backend,
1179
+ graph: backend === 'fortemi-core'
1180
+ ? resourceSource === 'web' ? 'framework' : params.graph ?? 'capability-default'
1181
+ : params.graph,
1182
+ ...(resourceSource === 'web'
1183
+ ? {
1184
+ resource_source: 'web',
1185
+ aiwg_selector: resolvedWebRelease?.selector,
1186
+ aiwg_version: resolvedWebRelease?.version,
1187
+ manifest_sha256: resolvedWebRelease?.manifestDigest,
1188
+ manifest_url: resolvedWebRelease?.manifestUrl,
1189
+ }
1190
+ : {}),
1191
+ },
1192
+ results: scored.map(r => ({
1193
+ id: discoveryIdForEntry(r.entry),
1194
+ ...(includePaths ? { path: resolvePath(r.entry) } : {}),
1195
+ type: r.entry.type,
1196
+ name: r.entry.name,
1197
+ title: r.entry.title,
1198
+ score: Math.round(r.score * 100) / 100,
1199
+ ranking: rankingDiagnostic(r.entry, r.score, r.facetDiagnostics),
1200
+ triggers: r.entry.triggers ?? [],
1201
+ capability: r.entry.capability ?? r.entry.summary,
1202
+ kernel: r.entry.kernel ?? false,
1203
+ provenance: {
1204
+ graph: r.entry.indexGraph ?? null,
1205
+ scope: r.entry.indexScope ?? null,
1206
+ },
1207
+ // #1227 — surface script-bearing skills so agents know to use
1208
+ // `aiwg run skill <name>` instead of executing instructions
1209
+ // themselves.
1210
+ ...(r.entry.script
1211
+ ? {
1212
+ executable: true,
1213
+ run_hint: buildRunHint(r.entry),
1214
+ }
1215
+ : {}),
1216
+ })),
1217
+ total: scored.length,
1218
+ query_time_ms: queryTimeMs,
1219
+ diagnostics: {
1220
+ content_tokens: tokenize(params.phrase),
1221
+ facet_activations: facetActivations,
1222
+ score_tie_break_order: ['score', 'scope', 'type', 'name', 'path'],
1223
+ },
1224
+ ...(relaxed ? { relaxed_overlap: true } : {}),
1225
+ ...(emptyResultHint ? { hint: emptyResultHint } : {}),
1226
+ }, null, jsonIndent));
1227
+ return;
1228
+ }
1229
+ if (scored.length === 0) {
1230
+ console.log(`No discovery matches for "${params.phrase}" in types: ${types.join(',')}.`);
1231
+ console.log('Try a broader phrase or a `--type` filter. If capabilities were just added, refresh with `aiwg use <framework>` (or `aiwg index build`).');
1232
+ return;
1233
+ }
1234
+ const relaxedNote = relaxed ? ' — relaxed match (verbose query)' : '';
1235
+ console.log(`Discovery results for "${params.phrase}" (${scored.length} matches, ${queryTimeMs}ms)${relaxedNote}:`);
1236
+ console.log('');
1237
+ for (const diagnostic of facetActivations) {
1238
+ if (diagnostic.status === 'suppressed' && diagnostic.reason) {
1239
+ console.log(`Routing diagnostic: ${diagnostic.reason}.`);
1240
+ console.log('');
1241
+ }
1242
+ }
1243
+ for (let i = 0; i < scored.length; i++) {
1244
+ const r = scored[i];
1245
+ const ranking = rankingDiagnostic(r.entry, r.score, r.facetDiagnostics);
1246
+ const id = discoveryIdForEntry(r.entry);
1247
+ const score = r.score.toFixed(2);
1248
+ const flags = [
1249
+ r.entry.kernel ? 'kernel' : null,
1250
+ r.entry.script ? 'exec' : null,
1251
+ ].filter(Boolean).join(', ');
1252
+ const locator = includePaths ? resolvePath(r.entry) : id;
1253
+ const topTriggerMatch = ranking.matches
1254
+ .filter((match) => match.field === 'trigger')
1255
+ .reduce((best, match) => !best || match.contribution > best.contribution ? match : best, undefined);
1256
+ const topTrigger = topTriggerMatch?.value ??
1257
+ (r.entry.triggers && r.entry.triggers.length > 0 ? r.entry.triggers[0] : '');
1258
+ console.log(`${i + 1}. ${r.entry.title}`);
1259
+ console.log(` type: ${r.entry.type} score: ${score}${flags ? ` flags: ${flags}` : ''}`);
1260
+ console.log(` ${includePaths ? 'path' : 'id'}: ${locator}`);
1261
+ if (r.entry.name) {
1262
+ console.log(` name: ${r.entry.name}`);
1263
+ }
1264
+ if (r.entry.capability) {
1265
+ console.log(` capability: ${r.entry.capability}`);
1266
+ }
1267
+ if (topTrigger) {
1268
+ console.log(` trigger: "${topTrigger}"`);
1269
+ }
1270
+ const triggerCoverage = topTriggerMatch?.query_token_coverage;
1271
+ const rankingParts = [
1272
+ `lexical ${ranking.lexical_score.toFixed(4)} -> final ${ranking.final_score.toFixed(4)}`,
1273
+ triggerCoverage === undefined
1274
+ ? null
1275
+ : `trigger covers ${Math.round(triggerCoverage * 100)}% of query tokens`,
1276
+ `scope ${ranking.tie_breakers.scope}`,
1277
+ ].filter((part) => Boolean(part));
1278
+ console.log(` ranking: ${rankingParts.join('; ')}`);
1279
+ if (r.entry.script) {
1280
+ console.log(` run: ${buildRunHint(r.entry)}`);
1281
+ }
1282
+ else if (!includePaths) {
1283
+ console.log(` show: ${showHintForDiscoverResult(r.entry, id)}`);
1284
+ }
1285
+ console.log('');
1286
+ }
1287
+ console.log('Use `--format json` for machine-readable output. Use `aiwg show metadata <id>` for paths and full metadata.');
1288
+ }
1289
+ function resolveMetadataPath(cwd, aiwgRoot, entry) {
1290
+ if (path.isAbsolute(entry.path))
1291
+ return entry.path;
1292
+ const normalizedPath = normalizeIndexedPath(entry.path);
1293
+ if (aiwgRoot && normalizedPath.startsWith('agentic/code/')) {
1294
+ return path.join(aiwgRoot, normalizedPath);
1295
+ }
1296
+ if (entry.indexScope === 'user' && normalizedPath.startsWith('~/.aiwg/')) {
1297
+ return path.join(process.env.HOME ?? '', normalizedPath.slice(2));
1298
+ }
1299
+ if (normalizedPath.startsWith('.aiwg/')) {
1300
+ return projectAiwgPath(cwd, normalizedPath.slice('.aiwg/'.length));
1301
+ }
1302
+ return path.join(cwd, normalizedPath);
1303
+ }
1304
+ async function loadShowEntries(cwd, params) {
1305
+ const aiwgRoot = await getAiwgRootForDiscover();
1306
+ const backend = params.backend ?? DEFAULT_ARTIFACT_SEARCH_BACKEND;
1307
+ let resourceSource = params.resourceSource ?? 'local';
1308
+ let entries = [];
1309
+ if (resourceSource === 'auto' && backend === 'fortemi-core') {
1310
+ const { loadFortemiCoreMetadataEntries } = await import('./fortemi-core-query-adapter.js');
1311
+ const localGraphs = params.graph ? [params.graph] : defaultCapabilityGraphs(cwd);
1312
+ resourceSource = localGraphs.some((graph) => loadFortemiCoreMetadataEntries(cwd, graph).entries.length > 0) ? 'local' : 'web';
1313
+ }
1314
+ else if (resourceSource === 'auto') {
1315
+ resourceSource = 'local';
1316
+ }
1317
+ if (resourceSource === 'web') {
1318
+ if (backend !== 'fortemi-core') {
1319
+ console.error('Error: --resource-source web requires the Fortemi Core query backend; --backend local remains available with local resources.');
1320
+ process.exit(1);
1321
+ }
1322
+ if (params.graph && params.graph !== 'framework') {
1323
+ console.error("Error: web-backed show currently supports only the 'framework' graph.");
1324
+ process.exit(1);
1325
+ }
1326
+ try {
1327
+ const { resolveWebRelease } = await import('../resources/web-release.js');
1328
+ const webRelease = await resolveWebRelease({
1329
+ ...params.webReleaseOptions,
1330
+ selector: params.aiwgVersion ?? 'stable',
1331
+ offline: params.offline,
1332
+ });
1333
+ const sourcePaths = {
1334
+ manifestPath: webRelease.fortemiManifestPath,
1335
+ exportPath: webRelease.fortemiExportPath,
1336
+ manifestSha256: webRelease.fortemiManifestSha256,
1337
+ manifestSize: webRelease.fortemiManifestSize,
1338
+ exportSha256: webRelease.fortemiExportSha256,
1339
+ exportSize: webRelease.fortemiExportSize,
1340
+ };
1341
+ const { loadFortemiCoreMetadataEntries } = await import('./fortemi-core-query-adapter.js');
1342
+ const loaded = loadFortemiCoreMetadataEntries(cwd, 'framework', sourcePaths);
1343
+ if (loaded.reason)
1344
+ throw new Error(loaded.reason);
1345
+ entries = loaded.entries.map((entry) => withIndexProvenance(entry, 'framework'));
1346
+ return { entries, aiwgRoot, resourceSource: 'web', webRelease, sourcePaths };
1347
+ }
1348
+ catch (error) {
1349
+ console.error(`Error: AIWG web resource resolution failed: ${error instanceof Error ? error.message : String(error)}`);
1350
+ process.exit(1);
1351
+ }
1352
+ }
1353
+ // Preserve the direct API's historical local-index behavior when callers
1354
+ // omit `backend`. The CLI always passes its explicit Fortemi default, while
1355
+ // an explicit `auto` source also opts into Fortemi-backed availability.
1356
+ if (params.backend === 'fortemi-core' || params.resourceSource === 'auto') {
1357
+ const { loadFortemiCoreMetadataEntries } = await import('./fortemi-core-query-adapter.js');
1358
+ const graphs = params.graph ? [params.graph] : defaultCapabilityGraphs(cwd);
1359
+ let unavailableReason;
1360
+ for (const graph of graphs) {
1361
+ const loaded = loadFortemiCoreMetadataEntries(cwd, graph);
1362
+ if (loaded.reason)
1363
+ unavailableReason ??= loaded.reason;
1364
+ entries.push(...loaded.entries.map((entry) => withIndexProvenance(entry, graph)));
1365
+ }
1366
+ if (entries.length === 0 && unavailableReason) {
1367
+ if (params.json) {
1368
+ console.log(JSON.stringify({
1369
+ backend: 'fortemi-core',
1370
+ name: params.name,
1371
+ error: unavailableReason,
1372
+ }, null, 2));
1373
+ }
1374
+ else {
1375
+ console.error('Error: ' + unavailableReason);
1376
+ }
1377
+ process.exit(1);
1378
+ }
1379
+ }
1380
+ else if (params.graph) {
1381
+ const idx = loadGraphIndexFile(cwd, 'metadata.json', params.graph);
1382
+ if (idx)
1383
+ entries = Object.values(idx.entries).map((entry) => withIndexProvenance(entry, params.graph));
1384
+ }
1385
+ else {
1386
+ for (const g of defaultCapabilityGraphs(cwd)) {
1387
+ const idx = loadGraphIndexFile(cwd, 'metadata.json', g);
1388
+ if (idx)
1389
+ entries.push(...Object.values(idx.entries).map((entry) => withIndexProvenance(entry, g)));
1390
+ }
1391
+ if (entries.length === 0) {
1392
+ const legacy = loadMetadataIndex(cwd);
1393
+ if (legacy)
1394
+ entries.push(...Object.values(legacy.entries));
1395
+ }
1396
+ }
1397
+ return { entries, aiwgRoot, resourceSource: 'local' };
1398
+ }
1399
+ function findShowMatches(entries, types, needle) {
1400
+ const needleLower = needle.toLowerCase();
1401
+ const normalizedNeedle = normalizeIndexedPath(needle);
1402
+ const candidates = entries.filter(e => types.includes(e.type));
1403
+ let matches = candidates.filter(e => discoveryIdForEntry(e) === needle);
1404
+ if (matches.length === 0) {
1405
+ matches = candidates.filter(e => e.path === needle || normalizeIndexedPath(e.path) === normalizedNeedle);
1406
+ }
1407
+ if (matches.length === 0) {
1408
+ matches = candidates.filter(e => {
1409
+ const normalizedPath = normalizeIndexedPath(e.path);
1410
+ const dirStem = path.basename(path.dirname(normalizedPath));
1411
+ const basename = path.basename(normalizedPath);
1412
+ const fileStem = basename.replace(/\.[^.]+$/, '');
1413
+ const slugLayoutMatch = ((basename === 'SKILL.md' && e.type === 'skill') ||
1414
+ (basename === 'BEHAVIOR.md' && e.type === 'behavior')) &&
1415
+ dirStem === needle;
1416
+ return slugLayoutMatch || fileStem === needle || e.name === needle;
1417
+ });
1418
+ }
1419
+ if (matches.length === 0) {
1420
+ matches = candidates.filter(e => typeof e.title === 'string' && e.title.toLowerCase() === needleLower);
1421
+ }
1422
+ matches = uniqueEntriesByPath(matches);
1423
+ matches = matches.sort((a, b) => scopeRank(a) - scopeRank(b) || a.path.localeCompare(b.path));
1424
+ if (matches.length > 1) {
1425
+ const sameIdentity = matches.every((entry) => entry.type === matches[0].type &&
1426
+ (entry.name ?? '').toLowerCase() === (matches[0].name ?? '').toLowerCase());
1427
+ if (sameIdentity && scopeRank(matches[0]) < scopeRank(matches[1])) {
1428
+ matches = [matches[0]];
1429
+ }
1430
+ }
1431
+ if (matches.length > 1 && types.includes('skill')) {
1432
+ const frameworkSkillMatches = matches.filter(e => isFrameworkSourceSkillPath(e.path));
1433
+ if (frameworkSkillMatches.length === 1)
1434
+ matches = frameworkSkillMatches;
1435
+ }
1436
+ if (matches.length > 1 && types.includes('skill')) {
1437
+ const sourceSkillMatches = matches.filter(e => isCanonicalSourceSkillPath(e.path));
1438
+ if (sourceSkillMatches.length === 1)
1439
+ matches = sourceSkillMatches;
1440
+ }
1441
+ if (matches.length > 1 && types.includes('agent')) {
1442
+ const canonicalSourceMatches = matches.filter(e => isCanonicalSourceAgentPath(e.path));
1443
+ if (canonicalSourceMatches.length === 1)
1444
+ matches = canonicalSourceMatches;
1445
+ }
1446
+ if (matches.length > 1 && types.includes('agent')) {
1447
+ const canonicalBundleMatches = matches.filter(e => isCanonicalBundleAgentPath(e.path));
1448
+ const canonicalBundlePaths = uniqueEntriesByPath(canonicalBundleMatches);
1449
+ if (canonicalBundlePaths.length === 1)
1450
+ matches = canonicalBundlePaths;
1451
+ }
1452
+ if (matches.length > 1 && types.includes('agent')) {
1453
+ const nonPersonaMatches = matches.filter(e => !isTopLevelPersonaAgentPath(e.path));
1454
+ if (nonPersonaMatches.length === 1)
1455
+ matches = nonPersonaMatches;
1456
+ }
1457
+ return matches;
1458
+ }
1459
+ async function fortemiRecordForEntry(cwd, entry, sourcePaths) {
1460
+ const graph = (entry.indexGraph ?? 'project');
1461
+ const { loadFortemiCoreExport } = await import('./fortemi-core-query-adapter.js');
1462
+ const loaded = loadFortemiCoreExport(cwd, graph, sourcePaths);
1463
+ if (!loaded.exported)
1464
+ return null;
1465
+ const id = discoveryIdForEntry(entry);
1466
+ return loaded.exported.items.find((record) => record.id === id || record.source.path === entry.path) ?? null;
1467
+ }
1468
+ /**
1469
+ * Scan the AIWG_ROOT corpus for an artifact matching `name` (#1221).
1470
+ *
1471
+ * Walks the well-known artifact layouts under
1472
+ * `agentic/code/{frameworks,addons,extensions,plugins}/<bundle>/{skills,agents,commands,rules,templates,behaviors,flows}/`
1473
+ * and returns the first match. Used as a fallback in `aiwg show` when an
1474
+ * artifact isn't in any built index — either because the workspace hasn't
1475
+ * been deployed to yet, or because the bundle hasn't been installed.
1476
+ *
1477
+ * Returns the resolved absolute path plus enough metadata for the caller
1478
+ * to surface a useful install hint. Returns null if nothing matches.
1479
+ */
1480
+ async function findCorpusArtifact(aiwgRoot, name, typeFilter) {
1481
+ const fs = await import('node:fs');
1482
+ const path = await import('node:path');
1483
+ const fsp = fs.promises;
1484
+ const groups = [
1485
+ { kind: 'framework', dir: path.join(aiwgRoot, 'agentic/code/frameworks') },
1486
+ { kind: 'addon', dir: path.join(aiwgRoot, 'agentic/code/addons') },
1487
+ { kind: 'extension', dir: path.join(aiwgRoot, 'agentic/code/extensions') },
1488
+ { kind: 'plugin', dir: path.join(aiwgRoot, 'agentic/code/plugins') },
1489
+ ];
1490
+ // (subdir, type, layout) — 'flat' = `<name>.md`, 'slug' = `<name>/SKILL.md`
1491
+ const tries = [
1492
+ { sub: 'skills', type: 'skill', layout: 'slug' },
1493
+ { sub: 'skills', type: 'skill', layout: 'flat' },
1494
+ { sub: 'agents', type: 'agent', layout: 'flat' },
1495
+ { sub: 'commands', type: 'command', layout: 'flat' },
1496
+ { sub: 'rules', type: 'rule', layout: 'flat' },
1497
+ { sub: 'templates', type: 'template', layout: 'flat' },
1498
+ { sub: 'behaviors', type: 'behavior', layout: 'flat' },
1499
+ { sub: 'flows', type: 'flow', layout: 'flat' },
1500
+ { sub: 'runbooks', type: 'runbook', layout: 'flat' },
1501
+ ];
1502
+ for (const group of groups) {
1503
+ let bundles;
1504
+ try {
1505
+ bundles = (await fsp.readdir(group.dir, { withFileTypes: true }))
1506
+ .filter(d => d.isDirectory())
1507
+ .map(d => d.name);
1508
+ }
1509
+ catch {
1510
+ continue;
1511
+ }
1512
+ for (const bundle of bundles) {
1513
+ for (const t of tries) {
1514
+ if (typeFilter.length > 0 && !typeFilter.includes(t.type))
1515
+ continue;
1516
+ const candidate = t.layout === 'slug'
1517
+ ? path.join(group.dir, bundle, t.sub, name, 'SKILL.md')
1518
+ : path.join(group.dir, bundle, t.sub, `${name}.md`);
1519
+ try {
1520
+ const stat = await fsp.stat(candidate);
1521
+ if (stat.isFile()) {
1522
+ return { path: candidate, type: t.type, bundleKind: group.kind, bundleId: bundle };
1523
+ }
1524
+ }
1525
+ catch {
1526
+ // not present — continue
1527
+ }
1528
+ }
1529
+ }
1530
+ }
1531
+ // Top-level agents live at `agentic/code/agents/<category>/<name>.md`
1532
+ // (e.g. `personas/aiwg-writer.md`) — a flat-under-category layout the
1533
+ // bundle groups above do not cover. Scan it directly so persona/agent
1534
+ // `show` survives un-indexed (or stale-framework-index) workspaces, where
1535
+ // the corpus fallback is the only resolution path. (#1623 U5)
1536
+ if (typeFilter.length === 0 || typeFilter.includes('agent')) {
1537
+ const agentMatch = await findAgentInCorpus(path.join(aiwgRoot, 'agentic/code/agents'), name);
1538
+ if (agentMatch) {
1539
+ return { path: agentMatch, type: 'agent', bundleKind: null, bundleId: null };
1540
+ }
1541
+ }
1542
+ // Behaviors commonly use a slug layout: `behaviors/<name>/BEHAVIOR.md`.
1543
+ if (typeFilter.length === 0 || typeFilter.includes('behavior')) {
1544
+ for (const group of groups) {
1545
+ let bundles;
1546
+ try {
1547
+ bundles = (await fsp.readdir(group.dir, { withFileTypes: true }))
1548
+ .filter(d => d.isDirectory())
1549
+ .map(d => d.name);
1550
+ }
1551
+ catch {
1552
+ continue;
1553
+ }
1554
+ for (const bundle of bundles) {
1555
+ const candidate = path.join(group.dir, bundle, 'behaviors', name, 'BEHAVIOR.md');
1556
+ try {
1557
+ const stat = await fsp.stat(candidate);
1558
+ if (stat.isFile()) {
1559
+ return { path: candidate, type: 'behavior', bundleKind: group.kind, bundleId: bundle };
1560
+ }
1561
+ }
1562
+ catch {
1563
+ // not present — continue
1564
+ }
1565
+ }
1566
+ }
1567
+ }
1568
+ return null;
1569
+ }
1570
+ /**
1571
+ * Recursively scan `agentic/code/agents` for a flat `<name>.md` agent file.
1572
+ *
1573
+ * Agents use an `agents/<category>/<name>.md` layout (e.g. `personas/`) that
1574
+ * differs from the bundle `<dir>/<bundle>/<sub>/<name>.md` layout the generic
1575
+ * corpus scan walks, so persona agents are otherwise missed. Bounded depth —
1576
+ * the agents tree is shallow. Returns the first matching absolute path, or
1577
+ * null. Direct files at each level take priority over subdirectories. (#1623)
1578
+ */
1579
+ async function findAgentInCorpus(agentsRoot, name, depth = 0) {
1580
+ if (depth > 3)
1581
+ return null;
1582
+ const fs = await import('node:fs');
1583
+ const path = await import('node:path');
1584
+ const fsp = fs.promises;
1585
+ let entries;
1586
+ try {
1587
+ entries = await fsp.readdir(agentsRoot, { withFileTypes: true });
1588
+ }
1589
+ catch {
1590
+ return null;
1591
+ }
1592
+ // Prefer a direct `<name>.md` at this level before descending.
1593
+ for (const entry of entries) {
1594
+ if (entry.isFile() && entry.name === `${name}.md`) {
1595
+ return path.join(agentsRoot, entry.name);
1596
+ }
1597
+ }
1598
+ // Descend into category subdirectories (e.g. personas/).
1599
+ for (const entry of entries) {
1600
+ if (entry.isDirectory()) {
1601
+ const found = await findAgentInCorpus(path.join(agentsRoot, entry.name), name, depth + 1);
1602
+ if (found)
1603
+ return found;
1604
+ }
1605
+ }
1606
+ return null;
1607
+ }
1608
+ /**
1609
+ * Read and emit the full text of a specific artifact (typically a
1610
+ * SKILL.md). Consumers don't need to know where AIWG stores skills —
1611
+ * they pass the skill name and the CLI reads the file from the indexed
1612
+ * location.
1613
+ *
1614
+ * Lookup order:
1615
+ * 1. Stable discover/Fortemi id
1616
+ * 2. Exact path match against any indexed entry's stored path
1617
+ * 3. Basename match (e.g. `intake-wizard` matches an entry whose
1618
+ * directory basename is `intake-wizard`)
1619
+ * 4. Title match (case-insensitive)
1620
+ * 5. Corpus fallback under AIWG_ROOT (#1221)
1621
+ *
1622
+ * On ambiguity, lists all matches and exits with code 2 unless
1623
+ * `--first` is supplied.
1624
+ */
1625
+ export async function showArtifact(cwd, params) {
1626
+ loadGlobalGraphConfigs();
1627
+ const { promises: fs } = await import('node:fs');
1628
+ const types = params.typeFilter && params.typeFilter.length > 0
1629
+ ? params.typeFilter
1630
+ : [...OPERATIONAL_SHOW_TYPES];
1631
+ const { entries, aiwgRoot, resourceSource, webRelease, } = await loadShowEntries(cwd, params);
1632
+ if (entries.length === 0 && params.backend !== 'fortemi-core') {
1633
+ console.error('Error: No artifact index found.');
1634
+ console.error('Run `aiwg index build --graph framework` (or `aiwg use <framework>`) first.');
1635
+ process.exit(1);
1636
+ }
1637
+ const needle = params.name.trim();
1638
+ let matches = findShowMatches(entries, types, needle);
1639
+ if (matches.length === 0) {
1640
+ // Corpus fallback (#1221): when the artifact isn't in any indexed graph,
1641
+ // scan the AIWG_ROOT corpus for a likely match and render it with an
1642
+ // "(uninstalled)" banner. This keeps `aiwg show` useful in workspaces
1643
+ // where the operator hasn't run `aiwg use` yet, or where the framework
1644
+ // index is stale, rather than exiting with a misleading "not found".
1645
+ if (resourceSource === 'local' && aiwgRoot && params.backend !== 'fortemi-core') {
1646
+ const corpusMatch = await findCorpusArtifact(aiwgRoot, needle, types);
1647
+ if (corpusMatch) {
1648
+ let content;
1649
+ try {
1650
+ content = await fs.readFile(corpusMatch.path, 'utf8');
1651
+ }
1652
+ catch (err) {
1653
+ const e = err;
1654
+ console.error(`Error reading ${corpusMatch.path}: ${e.message ?? String(err)}`);
1655
+ process.exit(1);
1656
+ }
1657
+ const installHint = corpusMatch.bundleKind && corpusMatch.bundleId
1658
+ ? `Run \`aiwg use ${corpusMatch.bundleId}\` to install this ${corpusMatch.bundleKind} into the workspace.`
1659
+ : 'Run `aiwg index build --graph framework` to refresh the artifact index.';
1660
+ if (params.json) {
1661
+ console.log(JSON.stringify({
1662
+ path: corpusMatch.path,
1663
+ type: corpusMatch.type,
1664
+ title: needle,
1665
+ kernel: false,
1666
+ uninstalled: true,
1667
+ hint: installHint,
1668
+ content,
1669
+ }, null, 2));
1670
+ return;
1671
+ }
1672
+ // Plain text mode: prepend a banner so consumers see the artifact is
1673
+ // corpus-only, then stream the body.
1674
+ const banner = [
1675
+ '<!-- AIWG: corpus-only artifact — not in the indexed workspace.',
1676
+ ` ${installHint}`,
1677
+ ' Origin: ' + corpusMatch.path,
1678
+ '-->',
1679
+ '',
1680
+ ].join('\n');
1681
+ process.stdout.write(banner);
1682
+ process.stdout.write(content);
1683
+ if (!content.endsWith('\n'))
1684
+ process.stdout.write('\n');
1685
+ return;
1686
+ }
1687
+ }
1688
+ console.error(`Error: no artifact found matching "${needle}".`);
1689
+ console.error('Try `aiwg discover "<phrase>"` to find the right name.');
1690
+ process.exit(1);
1691
+ }
1692
+ // Resolve relative framework-graph paths to absolute paths.
1693
+ // Kernel entries are anchored the same way as non-kernel framework entries
1694
+ // (#1230) — show reads the source corpus, not platform deploy mirrors.
1695
+ const resolvePath = (entry) => resourceSource === 'web'
1696
+ ? `raw/${normalizeIndexedPath(entry.path)}`
1697
+ : resolveMetadataPath(cwd, aiwgRoot, entry);
1698
+ if (matches.length > 1 && !params.first) {
1699
+ if (params.json) {
1700
+ console.log(JSON.stringify({
1701
+ ambiguous: true,
1702
+ name: needle,
1703
+ matches: matches.map(e => ({
1704
+ id: discoveryIdForEntry(e),
1705
+ path: resolvePath(e),
1706
+ type: e.type,
1707
+ name: e.name,
1708
+ title: e.title,
1709
+ kernel: e.kernel ?? false,
1710
+ provenance: {
1711
+ graph: e.indexGraph ?? null,
1712
+ scope: e.indexScope ?? null,
1713
+ ...(webRelease
1714
+ ? {
1715
+ resource_source: 'web',
1716
+ aiwg_selector: webRelease.selector,
1717
+ aiwg_version: webRelease.version,
1718
+ manifest_sha256: webRelease.manifestDigest,
1719
+ manifest_url: webRelease.manifestUrl,
1720
+ }
1721
+ : {}),
1722
+ },
1723
+ })),
1724
+ }, null, 2));
1725
+ }
1726
+ else {
1727
+ console.error(`Ambiguous: "${needle}" matches ${matches.length} artifacts. Disambiguate with --type or pass the stable id:`);
1728
+ for (const e of matches) {
1729
+ console.error(` ${e.type.padEnd(7)} ${discoveryIdForEntry(e)} ${e.title}`);
1730
+ }
1731
+ console.error('');
1732
+ console.error('Re-run with `--first` to pick the top match, `--type skill` to filter, or `aiwg show metadata <id>` for paths.');
1733
+ }
1734
+ process.exit(2);
1735
+ }
1736
+ const entry = matches[0];
1737
+ const filePath = resolvePath(entry);
1738
+ let content;
1739
+ if (resourceSource === 'web') {
1740
+ try {
1741
+ if (!webRelease)
1742
+ throw new Error('verified web release context is unavailable');
1743
+ const { fetchVerifiedRawResource } = await import('../resources/web-release.js');
1744
+ const bytes = await fetchVerifiedRawResource(webRelease, filePath, {
1745
+ baseUrl: params.webReleaseOptions?.baseUrl,
1746
+ fetcher: params.webReleaseOptions?.fetcher,
1747
+ allowInsecureLoopbackHttp: params.webReleaseOptions?.allowInsecureLoopbackHttp,
1748
+ offline: params.offline,
1749
+ });
1750
+ content = bytes.toString('utf8');
1751
+ }
1752
+ catch (error) {
1753
+ console.error(`Error: AIWG web resource show failed: ${error instanceof Error ? error.message : String(error)}`);
1754
+ process.exit(1);
1755
+ }
1756
+ }
1757
+ else {
1758
+ try {
1759
+ content = await fs.readFile(filePath, 'utf8');
1760
+ }
1761
+ catch (err) {
1762
+ const e = err;
1763
+ console.error(`Error reading ${filePath}: ${e.message ?? String(err)}`);
1764
+ process.exit(1);
1765
+ }
1766
+ }
1767
+ if (params.json) {
1768
+ const providerModels = resourceSource === 'local'
1769
+ ? await loadProviderModelMetadata(cwd, aiwgRoot)
1770
+ : null;
1771
+ console.log(JSON.stringify({
1772
+ id: discoveryIdForEntry(entry),
1773
+ path: filePath,
1774
+ type: entry.type,
1775
+ name: entry.name,
1776
+ title: entry.title,
1777
+ kernel: entry.kernel ?? false,
1778
+ provenance: {
1779
+ graph: entry.indexGraph ?? null,
1780
+ scope: entry.indexScope ?? null,
1781
+ ...(webRelease
1782
+ ? {
1783
+ resource_source: 'web',
1784
+ aiwg_selector: webRelease.selector,
1785
+ aiwg_version: webRelease.version,
1786
+ manifest_sha256: webRelease.manifestDigest,
1787
+ manifest_url: webRelease.manifestUrl,
1788
+ }
1789
+ : {}),
1790
+ },
1791
+ // #1227 — surface script-bearing skills so callers can route to
1792
+ // `aiwg run skill <name>` instead of treating SKILL.md as
1793
+ // instructions for the agent to execute itself.
1794
+ ...(entry.script
1795
+ ? { executable: true, run_hint: buildRunHint(entry) }
1796
+ : {}),
1797
+ ...(providerModels ? { providerModels } : {}),
1798
+ content,
1799
+ }, null, 2));
1800
+ return;
1801
+ }
1802
+ // Plain mode: stream the file content unmodified so the consumer
1803
+ // (agent or operator) sees exactly what the source authored.
1804
+ // #1227 — for script-bearing skills, prepend a one-line banner so
1805
+ // agents reading the body immediately see the skill is executable.
1806
+ if (entry.script) {
1807
+ const hint = buildRunHint(entry);
1808
+ process.stdout.write(`<!-- AIWG: executable skill — run via: ${hint} -->\n`);
1809
+ }
1810
+ process.stdout.write(content);
1811
+ if (!content.endsWith('\n'))
1812
+ process.stdout.write('\n');
1813
+ }
1814
+ export async function showMetadata(cwd, params) {
1815
+ loadGlobalGraphConfigs();
1816
+ const types = params.typeFilter && params.typeFilter.length > 0
1817
+ ? params.typeFilter
1818
+ : [...OPERATIONAL_SHOW_TYPES];
1819
+ const { entries, aiwgRoot, resourceSource, webRelease, sourcePaths, } = await loadShowEntries(cwd, params);
1820
+ if (entries.length === 0 && params.backend !== 'fortemi-core') {
1821
+ console.error('Error: No artifact index found.');
1822
+ console.error('Run `aiwg index build --graph framework` (or `aiwg use <framework>`) first.');
1823
+ process.exit(1);
1824
+ }
1825
+ const needle = params.name.trim();
1826
+ const matches = findShowMatches(entries, types, needle);
1827
+ if (matches.length === 0) {
1828
+ console.error(`Error: no artifact metadata found matching "${needle}".`);
1829
+ console.error('Try `aiwg discover "<phrase>" --json` to find the stable id.');
1830
+ process.exit(1);
1831
+ }
1832
+ const resolvePath = (entry) => resourceSource === 'web'
1833
+ ? `raw/${normalizeIndexedPath(entry.path)}`
1834
+ : resolveMetadataPath(cwd, aiwgRoot, entry);
1835
+ if (matches.length > 1 && !params.first) {
1836
+ if (params.json) {
1837
+ console.log(JSON.stringify({
1838
+ ambiguous: true,
1839
+ name: needle,
1840
+ matches: matches.map((entry) => ({
1841
+ id: discoveryIdForEntry(entry),
1842
+ type: entry.type,
1843
+ name: entry.name,
1844
+ title: entry.title,
1845
+ provenance: {
1846
+ graph: entry.indexGraph ?? null,
1847
+ scope: entry.indexScope ?? null,
1848
+ ...(webRelease
1849
+ ? {
1850
+ resource_source: 'web',
1851
+ aiwg_selector: webRelease.selector,
1852
+ aiwg_version: webRelease.version,
1853
+ manifest_sha256: webRelease.manifestDigest,
1854
+ manifest_url: webRelease.manifestUrl,
1855
+ }
1856
+ : {}),
1857
+ },
1858
+ })),
1859
+ }, null, 2));
1860
+ }
1861
+ else {
1862
+ console.error(`Ambiguous: "${needle}" matches ${matches.length} artifacts. Disambiguate with the stable id:`);
1863
+ for (const entry of matches) {
1864
+ console.error(` ${entry.type.padEnd(7)} ${discoveryIdForEntry(entry)} ${entry.title}`);
1865
+ }
1866
+ }
1867
+ process.exit(2);
1868
+ }
1869
+ const entry = matches[0];
1870
+ const absolutePath = resolvePath(entry);
1871
+ const backend = params.backend ?? DEFAULT_ARTIFACT_SEARCH_BACKEND;
1872
+ const record = backend === 'fortemi-core'
1873
+ ? await fortemiRecordForEntry(cwd, entry, sourcePaths)
1874
+ : null;
1875
+ const payload = {
1876
+ id: discoveryIdForEntry(entry),
1877
+ backend,
1878
+ type: entry.type,
1879
+ name: entry.name,
1880
+ title: entry.title,
1881
+ paths: {
1882
+ absolute: absolutePath,
1883
+ indexed: entry.path,
1884
+ repo_relative: record?.source.repo_relative_path ?? entry.path,
1885
+ locator: record?.source.locator ?? entry.name ?? entry.title,
1886
+ },
1887
+ provenance: {
1888
+ graph: entry.indexGraph ?? null,
1889
+ scope: entry.indexScope ?? null,
1890
+ ...(webRelease
1891
+ ? {
1892
+ resource_source: 'web',
1893
+ aiwg_selector: webRelease.selector,
1894
+ aiwg_version: webRelease.version,
1895
+ manifest_sha256: webRelease.manifestDigest,
1896
+ manifest_url: webRelease.manifestUrl,
1897
+ }
1898
+ : {}),
1899
+ },
1900
+ metadata: record ?? entry,
1901
+ providerModels: resourceSource === 'local'
1902
+ ? await loadProviderModelMetadata(cwd, aiwgRoot)
1903
+ : null,
1904
+ };
1905
+ if (params.json) {
1906
+ console.log(JSON.stringify(payload, null, 2));
1907
+ return;
1908
+ }
1909
+ console.log(`${payload.type} ${payload.title}`);
1910
+ console.log(` id: ${payload.id}`);
1911
+ if (payload.name)
1912
+ console.log(` name: ${payload.name}`);
1913
+ console.log(` graph: ${payload.provenance.graph ?? 'unknown'}`);
1914
+ console.log(` scope: ${payload.provenance.scope ?? 'unknown'}`);
1915
+ console.log(` path: ${payload.paths.absolute}`);
1916
+ console.log(` indexed_path: ${payload.paths.indexed}`);
1917
+ if (payload.providerModels) {
1918
+ console.log(' provider_models:');
1919
+ for (const [provider, hints] of Object.entries(payload.providerModels.providers)) {
1920
+ console.log(` ${provider}:`);
1921
+ for (const hint of hints) {
1922
+ console.log(` ${hint.role}: ${hint.model} (wrapper: ${hint.wrapper}, status: ${hint.status})`);
1923
+ }
1924
+ }
1925
+ }
1926
+ console.log('');
1927
+ console.log(JSON.stringify(payload.metadata, null, 2));
1928
+ }
1929
+ function isTopLevelPersonaAgentPath(entryPath) {
1930
+ return /(^|[/\\])agentic[/\\]code[/\\]agents[/\\]personas[/\\][^/\\]+\.md$/.test(entryPath);
1931
+ }
1932
+ function isCanonicalBundleAgentPath(entryPath) {
1933
+ return /(^|[/\\])agentic[/\\]code[/\\](?:frameworks|addons|extensions|plugins)[/\\][^/\\]+[/\\]agents[/\\][^/\\]+\.md$/.test(entryPath);
1934
+ }
1935
+ function isCanonicalSourceAgentPath(entryPath) {
1936
+ return /(^|[/\\])agentic[/\\]code[/\\](?:frameworks|addons|extensions)[/\\][^/\\]+[/\\]agents[/\\][^/\\]+\.md$/.test(entryPath);
1937
+ }
1938
+ function isFrameworkSourceSkillPath(entryPath) {
1939
+ return /(^|[/\\])agentic[/\\]code[/\\]frameworks[/\\][^/\\]+[/\\]skills[/\\][^/\\]+[/\\]SKILL\.md$/.test(entryPath);
1940
+ }
1941
+ function isCanonicalSourceSkillPath(entryPath) {
1942
+ return /(^|[/\\])agentic[/\\]code[/\\](?:frameworks|addons|extensions)[/\\][^/\\]+[/\\]skills[/\\][^/\\]+[/\\]SKILL\.md$/.test(entryPath);
1943
+ }
1944
+ function uniqueEntriesByPath(entries) {
1945
+ const seen = new Set();
1946
+ const unique = [];
1947
+ for (const entry of entries) {
1948
+ if (seen.has(entry.path))
1949
+ continue;
1950
+ seen.add(entry.path);
1951
+ unique.push(entry);
1952
+ }
1953
+ return unique;
1954
+ }
1955
+ //# sourceMappingURL=query-engine.js.map