@aiwg/cli 0.0.0-bootstrap.0 → 2026.7.17

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 (466) hide show
  1. package/README.md +21 -1
  2. package/bin/aiwg.mjs +349 -0
  3. package/dist/src/a2a/agent-card.js +145 -0
  4. package/dist/src/a2a/client.js +411 -0
  5. package/dist/src/a2a/hitl-cli.js +171 -0
  6. package/dist/src/a2a/hitl-driver.js +370 -0
  7. package/dist/src/a2a/hitl.js +181 -0
  8. package/dist/src/a2a/http.js +193 -0
  9. package/dist/src/a2a/jcs.js +128 -0
  10. package/dist/src/a2a/jws.js +187 -0
  11. package/dist/src/a2a/types.js +24 -0
  12. package/dist/src/a2a/webhook.js +240 -0
  13. package/dist/src/activity-log/cli.js +348 -0
  14. package/dist/src/activity-log/parser.js +81 -0
  15. package/dist/src/activity-log/types.js +50 -0
  16. package/dist/src/agents/agent-deployer.js +367 -0
  17. package/dist/src/agents/agent-packager.js +271 -0
  18. package/dist/src/agents/agent-validator.js +271 -0
  19. package/dist/src/agents/grounding/knowledge-base.js +161 -0
  20. package/dist/src/agents/grounding/types.js +12 -0
  21. package/dist/src/agents/index.js +10 -0
  22. package/dist/src/agents/packaged-agent-inventory.js +75 -0
  23. package/dist/src/agents/types.js +7 -0
  24. package/dist/src/api/index.d.ts +10 -0
  25. package/dist/src/api/index.js +10 -0
  26. package/dist/src/artifacts/address-parser.js +73 -0
  27. package/dist/src/artifacts/audit/cli.js +183 -0
  28. package/dist/src/artifacts/audit/drift.js +122 -0
  29. package/dist/src/artifacts/audit/types.js +12 -0
  30. package/dist/src/artifacts/backends/graphology-backend.js +160 -0
  31. package/dist/src/artifacts/backends/json-backend.js +129 -0
  32. package/dist/src/artifacts/backends/sqlite-backend.js +199 -0
  33. package/dist/src/artifacts/browser-export.js +622 -0
  34. package/dist/src/artifacts/capability-resolver.js +94 -0
  35. package/dist/src/artifacts/checksum-manifest.js +91 -0
  36. package/dist/src/artifacts/citation-parser.js +162 -0
  37. package/dist/src/artifacts/cli.js +1462 -0
  38. package/dist/src/artifacts/corpus-tools/citation-densify.js +311 -0
  39. package/dist/src/artifacts/corpus-tools/cli.js +393 -0
  40. package/dist/src/artifacts/corpus-tools/corpus-graph.js +225 -0
  41. package/dist/src/artifacts/corpus-tools/curator.js +186 -0
  42. package/dist/src/artifacts/corpus-tools/discovery-log.js +70 -0
  43. package/dist/src/artifacts/corpus-tools/funder-network.js +141 -0
  44. package/dist/src/artifacts/corpus-tools/induction-audit.js +233 -0
  45. package/dist/src/artifacts/corpus-tools/integrity-scan.js +202 -0
  46. package/dist/src/artifacts/corpus-tools/profile-communities.js +166 -0
  47. package/dist/src/artifacts/corpus-tools/profile-edges.js +72 -0
  48. package/dist/src/artifacts/corpus-tools/profile-embed.js +113 -0
  49. package/dist/src/artifacts/corpus-tools/profile-generate-fm.js +167 -0
  50. package/dist/src/artifacts/corpus-tools/profile-generate.js +181 -0
  51. package/dist/src/artifacts/corpus-tools/profile-metrics.js +98 -0
  52. package/dist/src/artifacts/corpus-tools/profile-status.js +86 -0
  53. package/dist/src/artifacts/corpus-tools/profile-temporal.js +139 -0
  54. package/dist/src/artifacts/corpus-tools/radar-init.js +93 -0
  55. package/dist/src/artifacts/corpus-tools/radar-report.js +118 -0
  56. package/dist/src/artifacts/corpus-tools/radar-shared.js +189 -0
  57. package/dist/src/artifacts/corpus-tools/radar-status.js +68 -0
  58. package/dist/src/artifacts/corpus-tools/sidecar-lint.js +233 -0
  59. package/dist/src/artifacts/corpus-tools/sidecar-repair.js +277 -0
  60. package/dist/src/artifacts/corpus-tools/snapshot.js +591 -0
  61. package/dist/src/artifacts/corpus-tools/source-types.js +164 -0
  62. package/dist/src/artifacts/corpus-tools/vision-extract.js +243 -0
  63. package/dist/src/artifacts/corpus-views/build.js +126 -0
  64. package/dist/src/artifacts/corpus-views/corpus-config.js +124 -0
  65. package/dist/src/artifacts/corpus-views/ref-parser.js +437 -0
  66. package/dist/src/artifacts/corpus-views/renderers.js +353 -0
  67. package/dist/src/artifacts/corpus-views/taxonomies.js +131 -0
  68. package/dist/src/artifacts/dep-graph.js +173 -0
  69. package/dist/src/artifacts/discover-facets.js +375 -0
  70. package/dist/src/artifacts/embedding-index.js +354 -0
  71. package/dist/src/artifacts/enrichment/cli.js +240 -0
  72. package/dist/src/artifacts/enrichment/prompt.js +47 -0
  73. package/dist/src/artifacts/enrichment/store.js +90 -0
  74. package/dist/src/artifacts/enrichment/types.js +12 -0
  75. package/dist/src/artifacts/fortemi-core-query-adapter.js +475 -0
  76. package/dist/src/artifacts/fortemi-core-sync.js +251 -0
  77. package/dist/src/artifacts/fortemi-shard-export.js +44 -0
  78. package/dist/src/artifacts/fulltext.js +108 -0
  79. package/dist/src/artifacts/graph-backend.js +51 -0
  80. package/dist/src/artifacts/graph-query.js +247 -0
  81. package/dist/src/artifacts/hybrid-query.js +225 -0
  82. package/dist/src/artifacts/index-builder.js +1089 -0
  83. package/dist/src/artifacts/index-reader.js +112 -0
  84. package/dist/src/artifacts/index-status.js +193 -0
  85. package/dist/src/artifacts/legacy-index-migration.js +169 -0
  86. package/dist/src/artifacts/move.js +154 -0
  87. package/dist/src/artifacts/operational-state.js +239 -0
  88. package/dist/src/artifacts/prebuilt-build-lock.js +49 -0
  89. package/dist/src/artifacts/query-engine.js +1955 -0
  90. package/dist/src/artifacts/source-graph.js +529 -0
  91. package/dist/src/artifacts/stats.js +169 -0
  92. package/dist/src/artifacts/types.js +606 -0
  93. package/dist/src/artifacts/views/cli.js +293 -0
  94. package/dist/src/artifacts/views/definition.js +170 -0
  95. package/dist/src/artifacts/views/store.js +149 -0
  96. package/dist/src/artifacts/views/types.js +8 -0
  97. package/dist/src/artifacts/watcher.js +255 -0
  98. package/dist/src/catalog/builtin-models.json +636 -0
  99. package/dist/src/catalog/cli.js +228 -0
  100. package/dist/src/catalog/cli.mjs +289 -0
  101. package/dist/src/catalog/index.js +15 -0
  102. package/dist/src/catalog/loader.js +217 -0
  103. package/dist/src/catalog/loader.mjs +251 -0
  104. package/dist/src/catalog/sources.json +41 -0
  105. package/dist/src/catalog/types.js +8 -0
  106. package/dist/src/channel/manager.mjs +504 -0
  107. package/dist/src/cli/agent-spawn.js +178 -0
  108. package/dist/src/cli/cli-extension-loader.js +245 -0
  109. package/dist/src/cli/command-log.js +275 -0
  110. package/dist/src/cli/config-loader.js +232 -0
  111. package/dist/src/cli/env.js +82 -0
  112. package/dist/src/cli/errors.js +150 -0
  113. package/dist/src/cli/find-package-root.js +40 -0
  114. package/dist/src/cli/git-hooks.js +250 -0
  115. package/dist/src/cli/handlers/agentcard.js +206 -0
  116. package/dist/src/cli/handlers/artifacts.js +76 -0
  117. package/dist/src/cli/handlers/best-practices-audit.js +280 -0
  118. package/dist/src/cli/handlers/cockpit.js +137 -0
  119. package/dist/src/cli/handlers/command-log.js +37 -0
  120. package/dist/src/cli/handlers/daemon.js +59 -0
  121. package/dist/src/cli/handlers/diagnose.js +300 -0
  122. package/dist/src/cli/handlers/execution-mode.js +105 -0
  123. package/dist/src/cli/handlers/feedback.js +341 -0
  124. package/dist/src/cli/handlers/help.js +147 -0
  125. package/dist/src/cli/handlers/index.js +302 -0
  126. package/dist/src/cli/handlers/init.js +179 -0
  127. package/dist/src/cli/handlers/install.js +129 -0
  128. package/dist/src/cli/handlers/issues.js +46 -0
  129. package/dist/src/cli/handlers/lint.js +42 -0
  130. package/dist/src/cli/handlers/local-executor.js +315 -0
  131. package/dist/src/cli/handlers/marketplace.js +168 -0
  132. package/dist/src/cli/handlers/mc.js +1010 -0
  133. package/dist/src/cli/handlers/models.js +352 -0
  134. package/dist/src/cli/handlers/packages.js +155 -0
  135. package/dist/src/cli/handlers/ralph-launcher.js +717 -0
  136. package/dist/src/cli/handlers/ralph.js +575 -0
  137. package/dist/src/cli/handlers/refresh.js +432 -0
  138. package/dist/src/cli/handlers/regenerate.js +336 -0
  139. package/dist/src/cli/handlers/repo-access.js +109 -0
  140. package/dist/src/cli/handlers/run.js +165 -0
  141. package/dist/src/cli/handlers/runtime-info.js +301 -0
  142. package/dist/src/cli/handlers/sandbox.js +197 -0
  143. package/dist/src/cli/handlers/scaffolding.js +149 -0
  144. package/dist/src/cli/handlers/script-runner.js +121 -0
  145. package/dist/src/cli/handlers/sdlc-accelerate.js +148 -0
  146. package/dist/src/cli/handlers/serve.js +1835 -0
  147. package/dist/src/cli/handlers/session.js +348 -0
  148. package/dist/src/cli/handlers/setup.js +440 -0
  149. package/dist/src/cli/handlers/skill-lint.js +367 -0
  150. package/dist/src/cli/handlers/skill-usage.js +74 -0
  151. package/dist/src/cli/handlers/steward.js +567 -0
  152. package/dist/src/cli/handlers/subcommands.js +1702 -0
  153. package/dist/src/cli/handlers/team.js +337 -0
  154. package/dist/src/cli/handlers/types.js +11 -0
  155. package/dist/src/cli/handlers/use.js +2772 -0
  156. package/dist/src/cli/handlers/utilities.js +703 -0
  157. package/dist/src/cli/handlers/version.js +134 -0
  158. package/dist/src/cli/handlers/workspace-context.js +93 -0
  159. package/dist/src/cli/handlers/workspace.js +126 -0
  160. package/dist/src/cli/help-generator.js +178 -0
  161. package/dist/src/cli/hooks/builtin/activity-log-hook.js +126 -0
  162. package/dist/src/cli/hooks/executor.js +96 -0
  163. package/dist/src/cli/hooks/index.js +15 -0
  164. package/dist/src/cli/hooks/registry.js +143 -0
  165. package/dist/src/cli/hooks/types.js +13 -0
  166. package/dist/src/cli/log.js +492 -0
  167. package/dist/src/cli/project-isolation/detect.js +70 -0
  168. package/dist/src/cli/project-isolation/index.js +7 -0
  169. package/dist/src/cli/project-isolation/signals.js +17 -0
  170. package/dist/src/cli/project-isolation/warning.js +102 -0
  171. package/dist/src/cli/prompt-utils.js +155 -0
  172. package/dist/src/cli/provider-deployment-plan.js +126 -0
  173. package/dist/src/cli/provider-resolution.js +119 -0
  174. package/dist/src/cli/router.js +305 -0
  175. package/dist/src/cli/scope-resolver.js +392 -0
  176. package/dist/src/cli/skill-usage.js +616 -0
  177. package/dist/src/cli/ui.js +224 -0
  178. package/dist/src/cli/watch-service.js +203 -0
  179. package/dist/src/cli/workflow-orchestrator.js +478 -0
  180. package/dist/src/cli/workspace-signals.js +321 -0
  181. package/dist/src/community/footer.js +14 -0
  182. package/dist/src/community/links.js +123 -0
  183. package/dist/src/community/milestones.js +46 -0
  184. package/dist/src/community/nudge-policy.js +93 -0
  185. package/dist/src/config/aiwg-config.js +883 -0
  186. package/dist/src/config/cli.js +703 -0
  187. package/dist/src/config/gitignore.js +156 -0
  188. package/dist/src/config/project-artifacts.js +76 -0
  189. package/dist/src/config/user-config.js +428 -0
  190. package/dist/src/config/user-registry.js +127 -0
  191. package/dist/src/config/workspace.js +276 -0
  192. package/dist/src/contributors/detect.js +39 -0
  193. package/dist/src/contributors/discover.js +172 -0
  194. package/dist/src/contributors/heuristic.js +186 -0
  195. package/dist/src/contributors/index.js +11 -0
  196. package/dist/src/contributors/types.js +13 -0
  197. package/dist/src/contributors/validation.js +116 -0
  198. package/dist/src/data/community.schema.json +43 -0
  199. package/dist/src/data/community.yaml +16 -0
  200. package/dist/src/extensions/capability-index.js +270 -0
  201. package/dist/src/extensions/claude-hooks-installer.js +268 -0
  202. package/dist/src/extensions/commands/definitions.js +3485 -0
  203. package/dist/src/extensions/deployment-registration.js +463 -0
  204. package/dist/src/extensions/index.js +22 -0
  205. package/dist/src/extensions/loader.js +132 -0
  206. package/dist/src/extensions/managed-marker.js +100 -0
  207. package/dist/src/extensions/manifest.js +196 -0
  208. package/dist/src/extensions/project-local-activity.js +114 -0
  209. package/dist/src/extensions/project-local-discovery.js +261 -0
  210. package/dist/src/extensions/project-local-doctor.js +281 -0
  211. package/dist/src/extensions/project-local-gitignore.js +176 -0
  212. package/dist/src/extensions/project-local-paths.js +121 -0
  213. package/dist/src/extensions/project-local-promote.js +218 -0
  214. package/dist/src/extensions/project-local-remove.js +373 -0
  215. package/dist/src/extensions/project-local-scaffold.js +252 -0
  216. package/dist/src/extensions/project-quickref.js +291 -0
  217. package/dist/src/extensions/registry.js +368 -0
  218. package/dist/src/extensions/shadow-resolver.js +271 -0
  219. package/dist/src/extensions/types.js +86 -0
  220. package/dist/src/extensions/upstream-registry.js +195 -0
  221. package/dist/src/extensions/validation.js +631 -0
  222. package/dist/src/features/catalog.js +88 -0
  223. package/dist/src/features/cli.js +197 -0
  224. package/dist/src/features/installer.js +57 -0
  225. package/dist/src/features/paths.js +14 -0
  226. package/dist/src/features/runtime.js +21 -0
  227. package/dist/src/features/status.js +134 -0
  228. package/dist/src/hooks/laziness-detection.js +532 -0
  229. package/dist/src/intake/git-history-analyzer.js +486 -0
  230. package/dist/src/intake/index.js +9 -0
  231. package/dist/src/issues/cli.js +327 -0
  232. package/dist/src/issues/index.js +5 -0
  233. package/dist/src/issues/live.js +154 -0
  234. package/dist/src/issues/local.js +666 -0
  235. package/dist/src/issues/sync.js +143 -0
  236. package/dist/src/issues/types.js +2 -0
  237. package/dist/src/issues/workflows.js +164 -0
  238. package/dist/src/kb/cli.js +173 -0
  239. package/dist/src/lint/cli.js +171 -0
  240. package/dist/src/lint/loader.js +225 -0
  241. package/dist/src/lint/reporters.js +141 -0
  242. package/dist/src/lint/runner.js +364 -0
  243. package/dist/src/lint/types.js +10 -0
  244. package/dist/src/marketplace/cache.js +110 -0
  245. package/dist/src/marketplace/identifier.js +63 -0
  246. package/dist/src/marketplace/registry.js +36 -0
  247. package/dist/src/marketplace/sources/clawhub.js +53 -0
  248. package/dist/src/marketplace/sources/git.js +45 -0
  249. package/dist/src/marketplace/types.js +9 -0
  250. package/dist/src/mcp/adapters/codex-runtime.js +224 -0
  251. package/dist/src/mcp/cli.mjs +1197 -0
  252. package/dist/src/mcp/elicitation.mjs +149 -0
  253. package/dist/src/mcp/helpers.mjs +287 -0
  254. package/dist/src/mcp/index.mjs +11 -0
  255. package/dist/src/mcp/profiles.js +263 -0
  256. package/dist/src/mcp/profiles.mjs +360 -0
  257. package/dist/src/mcp/registry.js +376 -0
  258. package/dist/src/mcp/registry.mjs +387 -0
  259. package/dist/src/mcp/server.mjs +633 -0
  260. package/dist/src/mcp/test-simple.mjs +271 -0
  261. package/dist/src/mcp/tools/command-run.mjs +101 -0
  262. package/dist/src/mcp/tools/discovery.mjs +291 -0
  263. package/dist/src/mcp/tools/interaction.mjs +87 -0
  264. package/dist/src/mcp/tools/orchestration.mjs +320 -0
  265. package/dist/src/mcp/tools/subsystems.mjs +640 -0
  266. package/dist/src/memory/cli.js +166 -0
  267. package/dist/src/memory/project-registry.js +282 -0
  268. package/dist/src/metrics/artifact-metrics.js +184 -0
  269. package/dist/src/metrics/context-budget.js +174 -0
  270. package/dist/src/metrics/token-counter.js +133 -0
  271. package/dist/src/models/config-loader.js +228 -0
  272. package/dist/src/models/index.js +22 -0
  273. package/dist/src/models/model-capabilities.v1.json +120 -0
  274. package/dist/src/models/model-catalog.v1.json +96 -0
  275. package/dist/src/models/model-discovery.js +521 -0
  276. package/dist/src/models/provider-models.js +73 -0
  277. package/dist/src/models/provider-policy.js +273 -0
  278. package/dist/src/models/resolver.js +245 -0
  279. package/dist/src/models/router.js +70 -0
  280. package/dist/src/models/types.js +8 -0
  281. package/dist/src/models/wrapper-deployment.js +178 -0
  282. package/dist/src/models/wrapper-route.js +54 -0
  283. package/dist/src/ops/cli.js +268 -0
  284. package/dist/src/ops/registry.js +623 -0
  285. package/dist/src/packages/adapters/clawhub.js +113 -0
  286. package/dist/src/packages/adapters/git.js +153 -0
  287. package/dist/src/packages/adapters/gitea.js +93 -0
  288. package/dist/src/packages/adapters/github.js +69 -0
  289. package/dist/src/packages/adapters/local-cache.js +89 -0
  290. package/dist/src/packages/package-registry.js +143 -0
  291. package/dist/src/packages/registry.js +238 -0
  292. package/dist/src/packages/types.js +11 -0
  293. package/dist/src/plugin/cross-framework-ops.js +475 -0
  294. package/dist/src/plugin/framework-config-loader.js +235 -0
  295. package/dist/src/plugin/framework-detector.js +186 -0
  296. package/dist/src/plugin/framework-isolator.js +341 -0
  297. package/dist/src/plugin/framework-migration.js +371 -0
  298. package/dist/src/plugin/metadata-validator.js +906 -0
  299. package/dist/src/plugin/plugin-installer.js +436 -0
  300. package/dist/src/plugin/plugin-status.js +369 -0
  301. package/dist/src/plugin/plugin-uninstaller.js +473 -0
  302. package/dist/src/plugin/registry-validator.js +344 -0
  303. package/dist/src/plugin/skill-command-translator.js +415 -0
  304. package/dist/src/plugin/workspace-creator.js +189 -0
  305. package/dist/src/plugin/workspace-migrator.js +821 -0
  306. package/dist/src/policy/authorization.js +390 -0
  307. package/dist/src/policy/repo-access.js +215 -0
  308. package/dist/src/provenance/cli.js +16 -0
  309. package/dist/src/providers/capability-matrix.js +300 -0
  310. package/dist/src/providers/capability-matrix.yaml +514 -0
  311. package/dist/src/providers/provider-definitions.js +977 -0
  312. package/dist/src/providers/provider-definitions.mjs +159 -0
  313. package/dist/src/providers/provider-inventory.js +169 -0
  314. package/dist/src/quality/feedback-ab.js +174 -0
  315. package/dist/src/quality/feedback-collector.js +176 -0
  316. package/dist/src/quality/feedback-tracker.js +123 -0
  317. package/dist/src/quality/patterns/adr.json +24 -0
  318. package/dist/src/quality/patterns/sad.json +24 -0
  319. package/dist/src/quality/patterns/test-plan.json +24 -0
  320. package/dist/src/quality/patterns/use-case.json +24 -0
  321. package/dist/src/quality/scoring.js +157 -0
  322. package/dist/src/reflections/cli.js +15 -0
  323. package/dist/src/release/plan.js +139 -0
  324. package/dist/src/research/cache/manager.js +227 -0
  325. package/dist/src/research/clients/arxiv.js +156 -0
  326. package/dist/src/research/clients/base.js +197 -0
  327. package/dist/src/research/clients/crossref.js +112 -0
  328. package/dist/src/research/clients/semantic-scholar.js +126 -0
  329. package/dist/src/research/clients/unpaywall.js +87 -0
  330. package/dist/src/research/index.js +14 -0
  331. package/dist/src/research/query-cli.js +336 -0
  332. package/dist/src/research/services/acquisition.js +313 -0
  333. package/dist/src/research/services/archival.js +225 -0
  334. package/dist/src/research/services/citation.js +279 -0
  335. package/dist/src/research/services/discovery.js +243 -0
  336. package/dist/src/research/services/documentation.js +269 -0
  337. package/dist/src/research/services/index.js +14 -0
  338. package/dist/src/research/services/provenance.js +279 -0
  339. package/dist/src/research/services/quality.js +370 -0
  340. package/dist/src/research/services/types.js +7 -0
  341. package/dist/src/research/storage-cli.js +20 -0
  342. package/dist/src/research/types.js +47 -0
  343. package/dist/src/resources/index.d.ts +2 -0
  344. package/dist/src/resources/index.js +2 -0
  345. package/dist/src/resources/web-release.d.ts +89 -0
  346. package/dist/src/resources/web-release.js +882 -0
  347. package/dist/src/rlm/cache/cli.js +162 -0
  348. package/dist/src/rlm/cache/hash.js +35 -0
  349. package/dist/src/rlm/cache/store.js +170 -0
  350. package/dist/src/rlm/cache/types.js +8 -0
  351. package/dist/src/rlm/cli.js +650 -0
  352. package/dist/src/serve/a2a-terminal-observer.js +120 -0
  353. package/dist/src/serve/agent-router.js +205 -0
  354. package/dist/src/serve/dispatch-router.js +171 -0
  355. package/dist/src/serve/executor-registry.js +715 -0
  356. package/dist/src/serve/mission-conductor.js +177 -0
  357. package/dist/src/serve/orchestrator-adapter.js +113 -0
  358. package/dist/src/serve/orchestrator-override.js +94 -0
  359. package/dist/src/serve/orchestrator-pty.js +133 -0
  360. package/dist/src/serve/pty-bridge.js +799 -0
  361. package/dist/src/serve/sandbox-registry.js +832 -0
  362. package/dist/src/serve/screen-reader.js +228 -0
  363. package/dist/src/serve/stack-adapters.js +68 -0
  364. package/dist/src/serve/telemetry.js +143 -0
  365. package/dist/src/skills/adapters/clawhub.js +250 -0
  366. package/dist/src/skills/adapters/local.js +335 -0
  367. package/dist/src/skills/adapters/openclaw.js +316 -0
  368. package/dist/src/skills/cli.js +334 -0
  369. package/dist/src/skills/registry.js +117 -0
  370. package/dist/src/skills/run.js +259 -0
  371. package/dist/src/skills/runtime.js +94 -0
  372. package/dist/src/skills/types.js +10 -0
  373. package/dist/src/smiths/agentsmith/demo.js +116 -0
  374. package/dist/src/smiths/agentsmith/examples.js +300 -0
  375. package/dist/src/smiths/agentsmith/generator.js +503 -0
  376. package/dist/src/smiths/agentsmith/index.js +11 -0
  377. package/dist/src/smiths/agentsmith/types.js +8 -0
  378. package/dist/src/smiths/commandsmith/example.js +220 -0
  379. package/dist/src/smiths/commandsmith/generator.js +288 -0
  380. package/dist/src/smiths/commandsmith/index.js +41 -0
  381. package/dist/src/smiths/commandsmith/templates.js +296 -0
  382. package/dist/src/smiths/commandsmith/types.js +7 -0
  383. package/dist/src/smiths/context-pipeline/aiwg-md.js +104 -0
  384. package/dist/src/smiths/context-pipeline/allowlist.js +195 -0
  385. package/dist/src/smiths/context-pipeline/claude-hook.js +127 -0
  386. package/dist/src/smiths/context-pipeline/discovery.js +187 -0
  387. package/dist/src/smiths/context-pipeline/external-links-section.js +49 -0
  388. package/dist/src/smiths/context-pipeline/finalization.js +147 -0
  389. package/dist/src/smiths/context-pipeline/generator.js +477 -0
  390. package/dist/src/smiths/context-pipeline/index.js +30 -0
  391. package/dist/src/smiths/context-pipeline/legacy-inject.js +103 -0
  392. package/dist/src/smiths/context-pipeline/managed-hook.js +95 -0
  393. package/dist/src/smiths/context-pipeline/overflow.js +212 -0
  394. package/dist/src/smiths/context-pipeline/parallelism-section.js +94 -0
  395. package/dist/src/smiths/context-pipeline/provider-policy.js +64 -0
  396. package/dist/src/smiths/context-pipeline/sanitizer.js +93 -0
  397. package/dist/src/smiths/context-pipeline/types.js +23 -0
  398. package/dist/src/smiths/context-pipeline/workspace-context.js +988 -0
  399. package/dist/src/smiths/hook-bridge/codex-translator.js +129 -0
  400. package/dist/src/smiths/hook-bridge/copilot-translator.js +64 -0
  401. package/dist/src/smiths/hook-bridge/factory-translator.js +43 -0
  402. package/dist/src/smiths/hook-bridge/hermes-translator.js +88 -0
  403. package/dist/src/smiths/hook-bridge/index.js +79 -0
  404. package/dist/src/smiths/hook-bridge/loader.js +116 -0
  405. package/dist/src/smiths/hook-bridge/shim.js +89 -0
  406. package/dist/src/smiths/hook-bridge/types.js +31 -0
  407. package/dist/src/smiths/index.js +39 -0
  408. package/dist/src/smiths/mcpsmith/analyzers/cli-analyzer.js +390 -0
  409. package/dist/src/smiths/mcpsmith/example.js +119 -0
  410. package/dist/src/smiths/mcpsmith/generator.js +236 -0
  411. package/dist/src/smiths/mcpsmith/index.js +17 -0
  412. package/dist/src/smiths/mcpsmith/types.js +10 -0
  413. package/dist/src/smiths/platform-paths.js +101 -0
  414. package/dist/src/smiths/skillsmith/codex-sidecar.js +85 -0
  415. package/dist/src/smiths/skillsmith/collision-detector.js +249 -0
  416. package/dist/src/smiths/skillsmith/demo.js +83 -0
  417. package/dist/src/smiths/skillsmith/examples.js +161 -0
  418. package/dist/src/smiths/skillsmith/generator.js +316 -0
  419. package/dist/src/smiths/skillsmith/index.js +12 -0
  420. package/dist/src/smiths/skillsmith/namespace-adapter.js +180 -0
  421. package/dist/src/smiths/skillsmith/platform-resolver.js +157 -0
  422. package/dist/src/smiths/skillsmith/types.js +9 -0
  423. package/dist/src/smiths/toolsmith/index.js +10 -0
  424. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +710 -0
  425. package/dist/src/smiths/toolsmith/types.js +8 -0
  426. package/dist/src/storage/backends/fortemi.js +227 -0
  427. package/dist/src/storage/backends/fs.js +134 -0
  428. package/dist/src/storage/backends/logseq.js +309 -0
  429. package/dist/src/storage/backends/obsidian.js +233 -0
  430. package/dist/src/storage/cli.js +468 -0
  431. package/dist/src/storage/config.js +272 -0
  432. package/dist/src/storage/index.js +108 -0
  433. package/dist/src/storage/subsystem-cli.js +201 -0
  434. package/dist/src/storage/types.js +33 -0
  435. package/dist/src/testing/corpus/corpus-builder.js +383 -0
  436. package/dist/src/testing/corpus/ground-truth-manager.js +460 -0
  437. package/dist/src/testing/corpus/index.js +10 -0
  438. package/dist/src/testing/fixtures/index.js +9 -0
  439. package/dist/src/testing/fixtures/test-data-factory.js +472 -0
  440. package/dist/src/testing/generators/index.js +11 -0
  441. package/dist/src/testing/generators/test-case-generator.js +393 -0
  442. package/dist/src/testing/generators/test-code-generator.js +384 -0
  443. package/dist/src/testing/generators/use-case-parser.js +325 -0
  444. package/dist/src/testing/index.js +11 -0
  445. package/dist/src/tracker/capability-protocol.js +118 -0
  446. package/dist/src/update/checker.mjs +396 -0
  447. package/dist/src/update/notifier.mjs +235 -0
  448. package/dist/src/writing/content-diversifier.js +804 -0
  449. package/dist/src/writing/example-generator.js +959 -0
  450. package/dist/src/writing/example-templates.js +99 -0
  451. package/dist/src/writing/pattern-library.js +486 -0
  452. package/dist/src/writing/patterns/banned-phrases.json +4413 -0
  453. package/dist/src/writing/patterns/formulaic-structures.json +1300 -0
  454. package/dist/src/writing/patterns/generic-adjectives.json +1510 -0
  455. package/dist/src/writing/patterns/hedging-language.json +2096 -0
  456. package/dist/src/writing/patterns/transition-words.json +1285 -0
  457. package/dist/src/writing/patterns/weak-verbs.json +1112 -0
  458. package/dist/src/writing/prompt-optimizer.js +660 -0
  459. package/dist/src/writing/prompt-templates.js +583 -0
  460. package/dist/src/writing/scoring-config-loader.js +168 -0
  461. package/dist/src/writing/validation-engine.js +595 -0
  462. package/dist/src/writing/validation-rules.js +380 -0
  463. package/dist/src/writing/voice-analyzer.js +439 -0
  464. package/dist/src/writing/voice-calibration.js +661 -0
  465. package/dist/src/writing/voice-profiles.json +588 -0
  466. package/package.json +64 -9
@@ -0,0 +1,2772 @@
1
+ /**
2
+ * Use Command Handler
3
+ *
4
+ * Deploys AIWG frameworks (SDLC, Marketing, Writing) to the current project.
5
+ * After deployment, registers deployed extensions in the extension registry.
6
+ *
7
+ * @implements @.aiwg/architecture/decisions/ADR-001-unified-extension-system.md
8
+ * @implements #56, #57
9
+ * @source @src/cli/router.ts
10
+ * @issue #33
11
+ */
12
+ import fs from 'fs/promises';
13
+ import path from 'path';
14
+ import os from 'os';
15
+ import YAML from 'yaml';
16
+ import { createScriptRunner } from './script-runner.js';
17
+ import { getFrameworkRoot, getVersionInfo } from '../../channel/manager.mjs';
18
+ import { getRegistry } from '../../extensions/registry.js';
19
+ import { registerDeployedExtensions } from '../../extensions/deployment-registration.js';
20
+ import { registerCliCommands, registerHooks } from '../cli-extension-loader.js';
21
+ import { translateSkillsToCommands, providerNeedsCommands } from '../../plugin/skill-command-translator.js';
22
+ import * as ui from '../ui.js';
23
+ import { readAiwgConfig, writeAiwgConfig, updateInstalled, hashManifest, emptyConfig, getProjectDir } from '../../config/aiwg-config.js';
24
+ import { getLogger } from '../log.js';
25
+ import { installCockpit } from './cockpit.js';
26
+ import { initHandler } from './init.js';
27
+ import { checkCollisions, formatCollisionReport, hasBlockingCollisions, } from '../../smiths/skillsmith/collision-detector.js';
28
+ import { discoverProjectLocalBundles, } from '../../extensions/project-local-discovery.js';
29
+ import { buildUpstreamRegistry } from '../../extensions/upstream-registry.js';
30
+ import { resolveShadows, formatShadowReport, } from '../../extensions/shadow-resolver.js';
31
+ import { appendProjectLocalActivity, emitDiscoverEventsDeduped, } from '../../extensions/project-local-activity.js';
32
+ import { hashBundleArtifacts } from '../../extensions/project-local-remove.js';
33
+ import { installAiwgHooks } from '../../extensions/claude-hooks-installer.js';
34
+ import { detectScope, mirrorToUserScope, rejectOpenClawProjectScope, USER_SCOPE_PATHS, } from '../scope-resolver.js';
35
+ import { maybeWarnProjectIsolation } from '../project-isolation/index.js';
36
+ import { formatWorkspaceSignalPlan, includedBundleIds, resolveWorkspaceSignalPlan, writeWorkspaceSignalPlan, } from '../workspace-signals.js';
37
+ import { getProviderArtifactPathStrings, getProviderKernelSkillPath, normalizeProviderDefinitionId, } from '../../providers/provider-definitions.js';
38
+ // Module-level guard so the iteration loops further down (which re-enter
39
+ // execute() per framework/provider) don't re-emit the warning each pass.
40
+ // Reset is not needed: a single CLI process is one user invocation.
41
+ let projectIsolationChecked = false;
42
+ // Context-pipeline: emits WORKSPACE.md + AIWG.md + provider adapters last.
43
+ // for non-Claude providers per ADR-1 (.aiwg/architecture/adr-agents-md-aggregation.md).
44
+ // Distinct from agentsmith (which creates subagent personas).
45
+ import { generate as generateContextFiles, discoverDeployedArtifacts, } from '../../smiths/context-pipeline/index.js';
46
+ import { verifyModelWrapperDeployment } from '../../models/wrapper-deployment.js';
47
+ /**
48
+ * Valid framework identifiers
49
+ */
50
+ const VALID_FRAMEWORKS = ['sdlc', 'marketing', 'media-curator', 'research', 'forensics', 'dfir', 'security-engineering', 'ops', 'validation', 'knowledge-base', 'writing', 'general', 'all'];
51
+ /**
52
+ * Framework name to deploy mode mapping.
53
+ * Mode is passed as `--mode <value>` to deploy-agents.mjs, which resolves
54
+ * to the actual framework via discoverFrameworks() + modeAliases.
55
+ */
56
+ const MODE_MAP = {
57
+ sdlc: 'sdlc',
58
+ marketing: 'marketing',
59
+ 'media-curator': 'media-curator',
60
+ research: 'research',
61
+ forensics: 'forensics',
62
+ dfir: 'dfir',
63
+ 'security-engineering': 'security-engineering',
64
+ ops: 'ops-complete', // ops-complete manifest id is 'ops-complete' (modeAlias: ops)
65
+ validation: 'validation-complete',
66
+ 'knowledge-base': 'knowledge-base',
67
+ writing: 'general',
68
+ general: 'general',
69
+ all: 'all',
70
+ };
71
+ const MODEL_DEPLOY_VALUE_FLAGS = new Set([
72
+ '--model', '--reasoning-model', '--coding-model', '--efficiency-model',
73
+ '--model-tier', '--filter', '--filter-role',
74
+ ]);
75
+ const MODEL_OVERRIDE_VALUE_FLAGS = new Set([
76
+ '--model', '--reasoning-model', '--coding-model', '--efficiency-model', '--model-tier',
77
+ ]);
78
+ const MODEL_DEPLOY_BOOLEAN_FLAGS = new Set(['--save', '--save-user']);
79
+ export function collectUseModelDeployArgs(args) {
80
+ const forwarded = [];
81
+ for (let i = 0; i < args.length; i++) {
82
+ if (MODEL_DEPLOY_BOOLEAN_FLAGS.has(args[i]))
83
+ forwarded.push(args[i]);
84
+ else if (MODEL_DEPLOY_VALUE_FLAGS.has(args[i]) && args[i + 1]) {
85
+ forwarded.push(args[i], args[++i]);
86
+ }
87
+ }
88
+ return forwarded;
89
+ }
90
+ export function collectModelOverrideDeployArgs(args) {
91
+ const forwarded = [];
92
+ for (let i = 0; i < args.length; i++) {
93
+ if (MODEL_OVERRIDE_VALUE_FLAGS.has(args[i]) && args[i + 1]) {
94
+ forwarded.push(args[i], args[++i]);
95
+ }
96
+ }
97
+ return forwarded;
98
+ }
99
+ async function loadDeployModelsConfig(frameworkRoot) {
100
+ const candidates = [
101
+ path.join(process.cwd(), 'models.json'),
102
+ path.join(os.homedir(), '.config', 'aiwg', 'models.json'),
103
+ path.join(frameworkRoot, 'agentic/code/frameworks/sdlc-complete/config/models.json'),
104
+ ];
105
+ for (const file of candidates) {
106
+ try {
107
+ return JSON.parse(await fs.readFile(file, 'utf8'));
108
+ }
109
+ catch { /* try the next deployment-precedence location */ }
110
+ }
111
+ return {
112
+ shorthand: {
113
+ opus: 'claude-opus-4-6',
114
+ sonnet: 'claude-sonnet-4-6',
115
+ haiku: 'claude-haiku-4-5-20251001',
116
+ inherit: 'inherit',
117
+ },
118
+ claude_shorthand: { opus: 'opus', sonnet: 'sonnet', haiku: 'haiku', inherit: 'inherit' },
119
+ };
120
+ }
121
+ function deployArgValue(args, flag) {
122
+ const index = args.indexOf(flag);
123
+ return index >= 0 ? args[index + 1] : undefined;
124
+ }
125
+ function resolveDeployModelAlias(value, provider, role, config) {
126
+ const clean = value.toLowerCase().replace(/['"]/g, '');
127
+ const shorthand = config[`${provider}_shorthand`] ?? config.shorthand ?? {};
128
+ if (typeof shorthand[clean] === 'string')
129
+ return shorthand[clean];
130
+ const tierModel = config[provider]?.[role]?.model;
131
+ if (clean === role && typeof tierModel === 'string')
132
+ return tierModel;
133
+ return value;
134
+ }
135
+ export function resolveUseWrapperModelExpectations(options) {
136
+ const roles = ['reasoning', 'coding', 'efficiency'];
137
+ let blanket = deployArgValue(options.modelDeployArgs, '--model');
138
+ const tier = deployArgValue(options.modelDeployArgs, '--model-tier');
139
+ if (tier) {
140
+ const tierRole = tier === 'economy' ? 'efficiency'
141
+ : tier === 'standard' ? 'coding'
142
+ : tier === 'premium' || tier === 'max-quality' ? 'reasoning' : null;
143
+ if (tierRole)
144
+ blanket = options.catalogModels[tierRole];
145
+ }
146
+ return Object.fromEntries(roles.map(role => {
147
+ const override = deployArgValue(options.modelDeployArgs, `--${role}-model`) ?? blanket;
148
+ return [role, override
149
+ ? resolveDeployModelAlias(override, options.provider, role, options.modelsConfig)
150
+ : options.catalogModels[role]];
151
+ }));
152
+ }
153
+ /**
154
+ * Framework name to actual directory name under agentic/code/frameworks/.
155
+ * Used for path construction in collision checks, CI hooks, and version tracking.
156
+ * Frameworks without a dedicated directory (writing, general) map to undefined —
157
+ * those code paths are skipped gracefully.
158
+ */
159
+ const FRAMEWORK_DIR_MAP = {
160
+ sdlc: 'sdlc-complete',
161
+ marketing: 'media-marketing-kit',
162
+ 'media-curator': 'media-curator',
163
+ research: 'research-complete',
164
+ forensics: 'forensics-complete',
165
+ dfir: 'forensics-complete',
166
+ 'security-engineering': 'security-engineering',
167
+ ops: 'ops-complete',
168
+ validation: 'validation-complete',
169
+ 'knowledge-base': 'knowledge-base',
170
+ // 'writing' and 'general' have no backing framework directory
171
+ // 'all' falls back to sdlc for manifest/CI purposes
172
+ all: 'sdlc-complete',
173
+ };
174
+ /** Resolve actual framework directory name for a given user-facing name. */
175
+ function resolveFrameworkDir(framework) {
176
+ return FRAMEWORK_DIR_MAP[framework];
177
+ }
178
+ /**
179
+ * Addons excluded from `aiwg use all`.
180
+ * aiwg-dev is contributor-only tooling — not for end users.
181
+ */
182
+ export const USE_ALL_DISALLOW = new Set(['aiwg-dev']);
183
+ /**
184
+ * Discover all addon names from the filesystem, minus the disallow list.
185
+ */
186
+ export async function getAllAddons(frameworkRoot) {
187
+ const addonsDir = path.join(frameworkRoot, 'agentic/code/addons');
188
+ const entries = await fs.readdir(addonsDir, { withFileTypes: true });
189
+ return entries
190
+ .filter(e => e.isDirectory() && !USE_ALL_DISALLOW.has(e.name))
191
+ .map(e => e.name);
192
+ }
193
+ /**
194
+ * Extensions excluded from `aiwg use all` deployment.
195
+ * `api-adapter` is an OpenAPI spec, not a deployable artifact bundle.
196
+ */
197
+ export const USE_ALL_EXTENSIONS_DISALLOW = new Set(['api-adapter']);
198
+ /**
199
+ * Discover all extension names from `agentic/code/extensions/*` (#1221).
200
+ *
201
+ * Extensions are addon-shaped bundles with their own `manifest.json`,
202
+ * `skills/`, `rules/`, and `templates/` directories. Only directories
203
+ * containing a `manifest.json` are considered deployable; bare directories
204
+ * (e.g. `api-adapter` which only ships an OpenAPI spec) are skipped.
205
+ */
206
+ export async function getAllExtensions(frameworkRoot) {
207
+ const extensionsDir = path.join(frameworkRoot, 'agentic/code/extensions');
208
+ let entries;
209
+ try {
210
+ entries = await fs.readdir(extensionsDir, { withFileTypes: true });
211
+ }
212
+ catch {
213
+ return [];
214
+ }
215
+ const result = [];
216
+ for (const entry of entries) {
217
+ if (!entry.isDirectory())
218
+ continue;
219
+ if (USE_ALL_EXTENSIONS_DISALLOW.has(entry.name))
220
+ continue;
221
+ const manifestPath = path.join(extensionsDir, entry.name, 'manifest.json');
222
+ try {
223
+ await fs.access(manifestPath);
224
+ result.push(entry.name);
225
+ }
226
+ catch {
227
+ // Directory without a manifest is not deployable as an extension.
228
+ continue;
229
+ }
230
+ }
231
+ return result;
232
+ }
233
+ /**
234
+ * Resolve extension source path from its name.
235
+ */
236
+ export function extensionPath(frameworkRoot, name) {
237
+ return path.join(frameworkRoot, 'agentic/code/extensions', name);
238
+ }
239
+ /**
240
+ * Check whether a given addon name exists on disk.
241
+ * The USE_ALL_DISALLOW list does NOT block explicit single-addon installs —
242
+ * contributors can still run `aiwg use aiwg-dev` directly.
243
+ */
244
+ /** Resolve canonical addon folder name from user-supplied alias. */
245
+ function resolveAddonFolderName(name) {
246
+ const ADDON_ALIASES = {
247
+ // ring-methodology has always been invokable as 'ring'
248
+ 'ring': 'ring-methodology',
249
+ // agent-loop addon — 'al' and 'ralph' are legacy aliases
250
+ 'al': 'agent-loop',
251
+ 'ralph': 'agent-loop',
252
+ };
253
+ return ADDON_ALIASES[name] ?? name;
254
+ }
255
+ export async function isValidAddon(frameworkRoot, name) {
256
+ try {
257
+ const folderName = resolveAddonFolderName(name);
258
+ const stat = await fs.stat(path.join(frameworkRoot, 'agentic/code/addons', folderName));
259
+ return stat.isDirectory();
260
+ }
261
+ catch {
262
+ return false;
263
+ }
264
+ }
265
+ /**
266
+ * Resolve addon source path from its name.
267
+ * Handles known aliases (ring, al, agent-loop).
268
+ */
269
+ export function addonPath(frameworkRoot, name) {
270
+ const folderName = resolveAddonFolderName(name);
271
+ return path.join(frameworkRoot, 'agentic/code/addons', folderName);
272
+ }
273
+ function getProviderPaths(provider) {
274
+ const paths = getProviderArtifactPathStrings(provider) ?? getProviderArtifactPathStrings('claude');
275
+ if (!paths)
276
+ throw new Error(`Missing provider paths for ${provider}`);
277
+ return paths;
278
+ }
279
+ function getProviderKernelSkillsPath(provider) {
280
+ return getProviderKernelSkillPath(provider) || getProviderKernelSkillPath('claude');
281
+ }
282
+ const MIRRORED_STANDARD_COMMAND_SKILLS = new Set([
283
+ 'aiwg-setup-project',
284
+ 'aiwg-update-claude',
285
+ 'aiwg-update-agents-md',
286
+ 'sdlc-accelerate',
287
+ 'project-status',
288
+ 'intake-wizard',
289
+ 'intake-from-codebase',
290
+ 'intake-start',
291
+ // Issue-workflow entry commands — invoked directly by users; mirror as
292
+ // commands so they reach .opencode/command/, .claude/commands/, etc. (#1549).
293
+ 'address-issues',
294
+ 'issue-audit',
295
+ ]);
296
+ const MIRRORED_KERNEL_COMMAND_SKILLS = new Set([
297
+ 'aiwg-refresh',
298
+ 'aiwg-doctor',
299
+ 'aiwg-status',
300
+ 'aiwg-help',
301
+ 'aiwg-regenerate',
302
+ 'aiwg-regenerate-claude',
303
+ 'aiwg-regenerate-codex',
304
+ 'aiwg-regenerate-opencode',
305
+ 'aiwg-regenerate-agents',
306
+ 'aiwg-issue',
307
+ 'aiwg-pr',
308
+ 'aiwg-delivery-pr',
309
+ 'aiwg-mission',
310
+ 'use',
311
+ 'steward',
312
+ ]);
313
+ function shouldMirrorStandardCommandSkill(skillName) {
314
+ return skillName.startsWith('flow-') || MIRRORED_STANDARD_COMMAND_SKILLS.has(skillName);
315
+ }
316
+ function shouldMirrorKernelCommandSkill(skillName) {
317
+ return MIRRORED_KERNEL_COMMAND_SKILLS.has(skillName);
318
+ }
319
+ function resolveProviderPath(target, providerPath) {
320
+ return path.isAbsolute(providerPath) ? providerPath : path.join(target, providerPath);
321
+ }
322
+ async function validateDeployedModelWrappers(options) {
323
+ const paths = getProviderPaths(options.provider);
324
+ const agentsPath = paths.agents ? resolveProviderPath(options.target, paths.agents) : null;
325
+ const { collectProviderInventory } = await import('../../providers/provider-inventory.js');
326
+ const { resolveDynamicModelCatalog } = await import('../../models/model-discovery.js');
327
+ const catalog = await resolveDynamicModelCatalog({
328
+ aiwgRoot: options.frameworkRoot,
329
+ inventory: await collectProviderInventory(options.target, { detectProcess: false }),
330
+ allowNetwork: false,
331
+ });
332
+ const catalogEntries = catalog.providers[options.provider]?.roles;
333
+ const catalogModels = catalogEntries
334
+ ? Object.fromEntries(Object.entries(catalogEntries).map(([role, entry]) => [role, entry.id]))
335
+ : undefined;
336
+ const expectedModels = catalogModels
337
+ ? resolveUseWrapperModelExpectations({
338
+ provider: options.provider,
339
+ modelDeployArgs: collectModelOverrideDeployArgs(options.modelDeployArgs),
340
+ catalogModels,
341
+ modelsConfig: await loadDeployModelsConfig(options.frameworkRoot),
342
+ })
343
+ : undefined;
344
+ const wrappers = await verifyModelWrapperDeployment(agentsPath, {
345
+ provider: options.provider,
346
+ ...(expectedModels ? {
347
+ models: expectedModels,
348
+ } : {}),
349
+ });
350
+ if (wrappers.supported && !wrappers.valid) {
351
+ const details = [
352
+ ...(wrappers.missing.length > 0 ? [`missing ${wrappers.missing.join(', ')}`] : []),
353
+ ...wrappers.mismatches.map(item => `${item.wrapper}.${item.field}: ${item.reason}`),
354
+ ];
355
+ const message = `Model wrapper deployment invalid for ${options.provider}: ${details.join('; ')}`;
356
+ if (!options.filtered)
357
+ return { exitCode: 1, message };
358
+ ui.warn(`${message} (filtered deployment)`);
359
+ }
360
+ else if (options.verbose && wrappers.supported) {
361
+ ui.dim(` Model wrappers verified: ${wrappers.found.join(', ')}`);
362
+ }
363
+ else if (options.verbose) {
364
+ ui.dim(` Model wrappers: ${options.provider} has no provider-native agent directory; model policy remains ${options.provider === 'hermes' || options.provider === 'openhuman' ? 'inherited/global' : 'informational'}.`);
365
+ }
366
+ return null;
367
+ }
368
+ /**
369
+ * List skill folder names from a source skills directory.
370
+ * Returns empty array if the directory doesn't exist.
371
+ */
372
+ async function listSourceSkillNames(skillsDir) {
373
+ try {
374
+ const entries = await fs.readdir(skillsDir, { withFileTypes: true });
375
+ return entries.filter(e => e.isDirectory()).map(e => e.name);
376
+ }
377
+ catch {
378
+ return [];
379
+ }
380
+ }
381
+ async function mirrorStandardCommandSkills(opts) {
382
+ const sourceDirs = [
383
+ opts.targetSkillsDir,
384
+ path.join(opts.frameworkRoot, 'agentic/code/frameworks/sdlc-complete/skills'),
385
+ ];
386
+ const seen = new Set();
387
+ let count = 0;
388
+ for (const sourceDir of sourceDirs) {
389
+ if (!sourceDir || seen.has(sourceDir))
390
+ continue;
391
+ seen.add(sourceDir);
392
+ const result = await translateSkillsToCommands(sourceDir, {
393
+ provider: opts.provider,
394
+ targetDir: opts.targetCommandsDir,
395
+ projectPath: opts.target,
396
+ dryRun: opts.dryRun,
397
+ verbose: opts.verbose,
398
+ nameFilter: shouldMirrorStandardCommandSkill,
399
+ });
400
+ count += result.translated.length;
401
+ }
402
+ return count;
403
+ }
404
+ /**
405
+ * Run pre-deployment collision check for a framework or addon.
406
+ * Emits warnings/errors to stderr. Returns false if deployment should be blocked.
407
+ */
408
+ async function runPreDeployCollisionCheck(opts) {
409
+ const { frameworkRoot, framework, target, provider, force, verbose = false } = opts;
410
+ // Resolve source skills dir for this framework
411
+ const frameworkDirName = resolveFrameworkDir(framework);
412
+ if (!frameworkDirName)
413
+ return true; // no backing directory — skip collision check
414
+ const sourceSkillsDir = path.join(frameworkRoot, 'agentic/code/frameworks', frameworkDirName, 'skills');
415
+ const skillNames = await listSourceSkillNames(sourceSkillsDir);
416
+ if (skillNames.length === 0)
417
+ return true; // nothing to check
418
+ const providerPaths = getProviderPaths(provider);
419
+ const skillsBaseDir = path.isAbsolute(providerPaths.skills)
420
+ ? providerPaths.skills
421
+ : path.join(target, providerPaths.skills);
422
+ const results = await checkCollisions({
423
+ platform: provider,
424
+ projectPath: target,
425
+ skillNames,
426
+ namespace: 'aiwg',
427
+ skillsBaseDir,
428
+ sourceSkillsDir,
429
+ });
430
+ const report = formatCollisionReport(results, { verbose });
431
+ if (report) {
432
+ process.stderr.write(report + '\n');
433
+ }
434
+ if (hasBlockingCollisions(results) && !force) {
435
+ process.stderr.write('\nDeployment blocked. Use --force to override.\n');
436
+ return false;
437
+ }
438
+ return true;
439
+ }
440
+ function agenticNextSteps(openStep) {
441
+ return [
442
+ openStep,
443
+ 'Ask the steward: "Check that AIWG is installed correctly and tell me what I can do here."',
444
+ 'Regenerate: Use aiwg-regenerate in-session when context files need rebuilding.',
445
+ 'Install runbook: docs/agentic-install-runbook.md',
446
+ 'Diagnostics: aiwg doctor',
447
+ ];
448
+ }
449
+ /**
450
+ * Framework-specific next steps guidance.
451
+ *
452
+ * Keep this handoff user-facing: `aiwg use` is the main human CLI entry point;
453
+ * discovery, capability lookup, and agent-loop commands are agent tools.
454
+ *
455
+ * Keyed as `<provider>/<framework>` with fallback to `<framework>`.
456
+ * The 'claude' provider is the default (shown for all unrecognized providers).
457
+ */
458
+ const NEXT_STEPS = {
459
+ 'sdlc': agenticNextSteps('Open platform: Open Claude Code, Codex, Cursor, Warp, or your chosen AI tool.'),
460
+ 'marketing': agenticNextSteps('Open platform: Open your chosen AI tool and ask for a campaign or marketing intake.'),
461
+ 'media-curator': agenticNextSteps('Open platform: Open your chosen AI tool and ask for a media collection next action.'),
462
+ 'research': agenticNextSteps('Open platform: Open your chosen AI tool and ask for a research workflow next action.'),
463
+ 'security-engineering': agenticNextSteps('Open platform: Open your chosen AI tool and ask for a security-engineering decision path.'),
464
+ 'all': agenticNextSteps('Open platform: Open Claude Code, Codex, Cursor, Warp, or your chosen AI tool.'),
465
+ 'hermes/sdlc': agenticNextSteps('Start Hermes: Open a Hermes chat attached to this project.'),
466
+ 'hermes/marketing': agenticNextSteps('Start Hermes: Open a Hermes chat attached to this project.'),
467
+ 'hermes/all': agenticNextSteps('Start Hermes: Open a Hermes chat attached to this project.'),
468
+ 'factory/sdlc': agenticNextSteps('Open Factory: Start Factory from this project root.'),
469
+ 'cursor/sdlc': agenticNextSteps('Open Cursor: Open this project in Cursor.'),
470
+ 'warp/sdlc': agenticNextSteps('Open Warp: Start a Warp session in this project root.'),
471
+ 'copilot/sdlc': agenticNextSteps('Open VS Code: Open this workspace and use Copilot Chat.'),
472
+ 'codex/sdlc': agenticNextSteps('Open Codex: Restart Codex in this project root.'),
473
+ 'windsurf/sdlc': agenticNextSteps('Open Windsurf: Open this project in Windsurf and ask Cascade for AIWG status.'),
474
+ 'openclaw/sdlc': agenticNextSteps('Start OpenClaw: Open OpenClaw with this project workspace.'),
475
+ 'openclaw/marketing': agenticNextSteps('Start OpenClaw: Open OpenClaw with this project workspace.'),
476
+ 'openclaw/all': agenticNextSteps('Start OpenClaw: Open OpenClaw with this project workspace.'),
477
+ 'openhuman/sdlc': agenticNextSteps('Open OpenHuman: Open OpenHuman and check the Skills view for AIWG kernel skills.'),
478
+ 'openhuman/marketing': agenticNextSteps('Open OpenHuman: Open OpenHuman and check the Skills view for AIWG kernel skills.'),
479
+ 'openhuman/all': agenticNextSteps('Open OpenHuman: Open OpenHuman and check the Skills view for AIWG kernel skills.'),
480
+ };
481
+ export function nextStepsFor(framework, provider = 'claude') {
482
+ const providerKey = `${provider}/${framework}`;
483
+ return NEXT_STEPS[providerKey] ?? NEXT_STEPS[framework] ?? NEXT_STEPS.sdlc;
484
+ }
485
+ function printNextSteps(framework, provider = 'claude') {
486
+ ui.section('Next steps:', nextStepsFor(framework, provider));
487
+ }
488
+ /**
489
+ * Per-provider session-reload requirement after `aiwg use`.
490
+ *
491
+ * Most agentic platforms read their `<provider>/agents/` directory at session
492
+ * start and cache the agent registry for the lifetime of the session. A
493
+ * deploy that lands new agent files is invisible to any session that was
494
+ * already running when the deploy completed — the Agent / Task tool will
495
+ * still report `Agent type 'foo' not found` until the session reloads.
496
+ *
497
+ * Issue #1240: surfacing this requirement in `aiwg use` output and in the
498
+ * Steward FAQ so operators stop hitting the "fallback to general-purpose"
499
+ * path silently.
500
+ */
501
+ const SESSION_RELOAD_NOTICE = {
502
+ claude: {
503
+ action: 'Restart your Claude Code session (close and reopen) to load the newly deployed agents.',
504
+ rationale: 'Claude Code reads .claude/agents/ at session start. A running session retains its old registry until reloaded.',
505
+ },
506
+ codex: {
507
+ action: 'Restart your Codex session to pick up newly deployed agents and home-dir skills.',
508
+ rationale: 'Codex caches its agent and skill registry per session. ~/.codex/skills/ and .codex/agents/ are scanned on startup.',
509
+ },
510
+ copilot: {
511
+ action: 'Reload the VS Code window (`Developer: Reload Window`) so Copilot picks up the new .github/agents/ entries.',
512
+ rationale: 'VS Code/Copilot caches workspace agent definitions until the window reloads.',
513
+ },
514
+ cursor: {
515
+ action: 'Restart Cursor (close and reopen the project) to load the newly deployed agents.',
516
+ rationale: 'Cursor reads .cursor/agents/ and .cursor/rules/ on workspace open.',
517
+ },
518
+ warp: {
519
+ action: 'Open a fresh Warp tab — WARP.md is re-read on tab start.',
520
+ rationale: 'Warp aggregates context from WARP.md when a new tab spawns; existing tabs keep the prior version.',
521
+ },
522
+ windsurf: {
523
+ action: 'Restart Windsurf or reload the workspace so the aggregated AGENTS.md is re-parsed.',
524
+ rationale: 'Windsurf reads AGENTS.md once per workspace session.',
525
+ },
526
+ factory: {
527
+ action: 'Restart your Factory droid runtime to pick up new entries in .factory/droids/.',
528
+ rationale: 'Factory caches the droid manifest at runtime start.',
529
+ },
530
+ opencode: {
531
+ action: 'Restart your OpenCode session — `.opencode/agent/` is scanned on startup.',
532
+ rationale: 'OpenCode loads agent files on session start and does not hot-reload.',
533
+ },
534
+ hermes: {
535
+ action: 'In an active Hermes session, run /reload-skills to pick up new skills in ~/.hermes/skills/ and /reload-mcp to pick up MCP server changes (~/.hermes/config.yaml) — both are in-session slash commands, no chat restart needed. Restart the chat only as a fallback if the slash commands are unavailable.',
536
+ rationale: 'Hermes loads skills and MCP config at session start (verified in hermes_cli/commands.py:178 and hermes_cli/config.py:1228). The /reload-skills and /reload-mcp slash commands re-scan in place; /reload-mcp prompts for confirmation by default.',
537
+ symptom: 'Until reloaded, newly deployed kernel skills are missing from `hermes skills list` and unreachable via natural-language invocation; new MCP servers (incl. AIWG) are missing from the tool surface.',
538
+ },
539
+ openclaw: {
540
+ action: 'Restart OpenClaw — ~/.openclaw/agents/ and ~/.openclaw/skills/ are loaded on startup.',
541
+ rationale: 'OpenClaw reads its home-dir registry once per process.',
542
+ },
543
+ };
544
+ function printSessionReloadNotice(provider) {
545
+ const notice = SESSION_RELOAD_NOTICE[provider];
546
+ if (!notice)
547
+ return;
548
+ const defaultSymptom = 'Until reloaded, the Agent/Task tool will report "Agent type not found" for the newly deployed agents.';
549
+ ui.section('Session reload required:', [
550
+ notice.action,
551
+ `Why: ${notice.rationale}`,
552
+ notice.symptom ?? defaultSymptom,
553
+ ]);
554
+ }
555
+ /**
556
+ * Count deployed artifacts in target directories
557
+ *
558
+ * @implements #609
559
+ */
560
+ async function countDeployedArtifacts(target, paths) {
561
+ const countMd = async (dir) => {
562
+ if (!dir)
563
+ return 0;
564
+ try {
565
+ // Support absolute paths (openclaw deploys to home dir)
566
+ const resolvedDir = path.isAbsolute(dir) ? dir : path.join(target, dir);
567
+ const entries = await fs.readdir(resolvedDir);
568
+ return entries.filter(f => f.endsWith('.md')).length;
569
+ }
570
+ catch {
571
+ return 0;
572
+ }
573
+ };
574
+ const countDirs = async (dir) => {
575
+ if (!dir)
576
+ return 0;
577
+ try {
578
+ const resolvedDir = path.isAbsolute(dir) ? dir : path.join(target, dir);
579
+ const entries = await fs.readdir(resolvedDir, { withFileTypes: true });
580
+ return entries.filter(e => e.isDirectory()).length;
581
+ }
582
+ catch {
583
+ return 0;
584
+ }
585
+ };
586
+ // Count rules by parsing declared counts from RULES-INDEX.md files rather
587
+ // than counting .md files on disk. When deployIndexOnly is true, only one
588
+ // RULES-INDEX.md is deployed but it declares the total count of rules across
589
+ // all installed components via section headers like "## Name (N rules — ...)".
590
+ const countRules = async (dir) => {
591
+ if (!dir)
592
+ return 0;
593
+ try {
594
+ const resolvedDir = path.isAbsolute(dir) ? dir : path.join(target, dir);
595
+ const entries = await fs.readdir(resolvedDir);
596
+ const indexFiles = entries.filter(f => f.endsWith('RULES-INDEX.md'));
597
+ if (indexFiles.length === 0) {
598
+ // No index files — fall back to counting individual rule .md files
599
+ return entries.filter(f => f.endsWith('.md')).length;
600
+ }
601
+ let total = 0;
602
+ for (const indexFile of indexFiles) {
603
+ const content = await fs.readFile(path.join(resolvedDir, indexFile), 'utf-8');
604
+ // Match section headers: "## Name (N rules — ..." or "— N rules*"
605
+ const matches = content.matchAll(/\((\d+) rules[^)]*\)/g);
606
+ for (const m of matches) {
607
+ total += parseInt(m[1], 10);
608
+ }
609
+ }
610
+ return total > 0 ? total : entries.filter(f => f.endsWith('.md')).length;
611
+ }
612
+ catch {
613
+ return 0;
614
+ }
615
+ };
616
+ // Kernel skills deploy to the platform-native skills dir (always-loaded
617
+ // set) while standard skills sequester under <provider>/.aiwg/skills (the
618
+ // index-driven discovery tier). Both contribute to the deployed surface,
619
+ // so both must be counted (#1228). Derive the kernel path by stripping
620
+ // the `.aiwg/` segment from the standard path.
621
+ const kernelSkillsPath = paths.skills
622
+ ? paths.skills.replace(/(^|\/)\.aiwg\/skills?$/, '$1skills')
623
+ : '';
624
+ return {
625
+ agents: await countMd(paths.agents),
626
+ commands: await countMd(paths.commands),
627
+ skills: (await countDirs(paths.skills)) +
628
+ (kernelSkillsPath && kernelSkillsPath !== paths.skills
629
+ ? await countDirs(kernelSkillsPath)
630
+ : 0),
631
+ rules: await countRules(paths.rules),
632
+ behaviors: await countDirs(paths.behaviors),
633
+ };
634
+ }
635
+ async function countDiscoverableSkills(aiwgRoot) {
636
+ try {
637
+ const { loadGraphIndexFile } = await import('../../artifacts/index-reader.js');
638
+ const index = loadGraphIndexFile(aiwgRoot, 'metadata.json', 'framework');
639
+ if (!index?.entries)
640
+ return null;
641
+ return Object.values(index.entries).filter(entry => entry.type === 'skill').length;
642
+ }
643
+ catch {
644
+ return null;
645
+ }
646
+ }
647
+ /**
648
+ * Detect forge targets from .git/config remote URLs.
649
+ * Returns a list of forge types found: 'github' | 'gitea'
650
+ *
651
+ * @implements #661
652
+ */
653
+ async function detectForges(projectDir) {
654
+ const forges = new Set();
655
+ try {
656
+ const gitConfig = await fs.readFile(path.join(projectDir, '.git', 'config'), 'utf-8');
657
+ if (/github\.com/i.test(gitConfig))
658
+ forges.add('github');
659
+ // Gitea: any non-github remote host (self-hosted instances)
660
+ const remoteUrls = [...gitConfig.matchAll(/url\s*=\s*(.+)/g)].map(m => m[1].trim());
661
+ for (const url of remoteUrls) {
662
+ if (!url.includes('github.com') && (url.includes('git.') || url.includes('.net') || url.includes('.io'))) {
663
+ forges.add('gitea');
664
+ }
665
+ }
666
+ }
667
+ catch {
668
+ // No .git/config — default to github only
669
+ forges.add('github');
670
+ }
671
+ return [...forges];
672
+ }
673
+ /**
674
+ * Deploy CI workflow files to .github/workflows/ and/or .gitea/workflows/
675
+ * when --ci-hooks-enabled is set.
676
+ *
677
+ * @implements #661
678
+ */
679
+ async function deployCiHooks(opts) {
680
+ const { frameworkRoot, framework, target, dryRun } = opts;
681
+ // Resolve framework source dir
682
+ const ciFrameworkDir = resolveFrameworkDir(framework);
683
+ if (!ciFrameworkDir)
684
+ return; // no backing directory — nothing to deploy
685
+ const frameworkDir = path.join(frameworkRoot, 'agentic/code/frameworks', ciFrameworkDir);
686
+ // Read CI manifest from framework manifest.json
687
+ let ciSpec = {};
688
+ try {
689
+ const manifestPath = path.join(frameworkDir, 'manifest.json');
690
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
691
+ const manifest = JSON.parse(manifestContent);
692
+ ciSpec = manifest.ci ?? {};
693
+ }
694
+ catch {
695
+ // No CI spec in manifest — nothing to deploy
696
+ return;
697
+ }
698
+ if (Object.keys(ciSpec).length === 0)
699
+ return;
700
+ const forges = await detectForges(target);
701
+ const ciSourceDir = path.join(frameworkDir, 'ci');
702
+ const forgeMap = [
703
+ { forge: 'github', targetDir: path.join(target, '.github', 'workflows'), files: ciSpec.github ?? [] },
704
+ { forge: 'gitea', targetDir: path.join(target, '.gitea', 'workflows'), files: ciSpec.gitea ?? [] },
705
+ ];
706
+ let deployed = 0;
707
+ for (const { forge, targetDir, files } of forgeMap) {
708
+ if (!forges.includes(forge) || files.length === 0)
709
+ continue;
710
+ if (!dryRun) {
711
+ await fs.mkdir(targetDir, { recursive: true });
712
+ }
713
+ for (const file of files) {
714
+ const src = path.join(ciSourceDir, forge, file);
715
+ const dest = path.join(targetDir, path.basename(file));
716
+ if (dryRun) {
717
+ console.log(` [dry-run] Would copy CI file: ${src} → ${dest}`);
718
+ }
719
+ else {
720
+ try {
721
+ await fs.copyFile(src, dest);
722
+ deployed++;
723
+ }
724
+ catch {
725
+ ui.warn(`Could not copy CI file: ${file} (source missing in framework — skipping)`);
726
+ }
727
+ }
728
+ }
729
+ }
730
+ if (!dryRun && deployed > 0) {
731
+ ui.blank();
732
+ ui.warn(`CI hooks installed (${deployed} file(s)). Review before committing — they affect your CI pipeline.`);
733
+ }
734
+ }
735
+ /**
736
+ * Count artifacts contributed by a single project-local bundle by reading the
737
+ * bundle's source directories. Approximates what deploy-agents.mjs writes to
738
+ * the provider deploy paths for this specific bundle (skills are subdirs;
739
+ * everything else is .md files).
740
+ *
741
+ * @implements #1035
742
+ */
743
+ async function countBundleSourceArtifacts(bundlePath) {
744
+ const countMd = async (dir) => {
745
+ try {
746
+ const entries = await fs.readdir(path.join(bundlePath, dir));
747
+ return entries.filter(f => f.endsWith('.md')).length;
748
+ }
749
+ catch {
750
+ return 0;
751
+ }
752
+ };
753
+ const countDirs = async (dir) => {
754
+ try {
755
+ const entries = await fs.readdir(path.join(bundlePath, dir), { withFileTypes: true });
756
+ return entries.filter(e => e.isDirectory()).length;
757
+ }
758
+ catch {
759
+ return 0;
760
+ }
761
+ };
762
+ return {
763
+ agents: await countMd('agents'),
764
+ commands: await countMd('commands'),
765
+ skills: await countDirs('skills'),
766
+ rules: await countMd('rules'),
767
+ };
768
+ }
769
+ /**
770
+ * Deploy a single project-local bundle to one provider via deploy-agents.mjs.
771
+ * Runs the same script and flags used for upstream addons, with the bundle
772
+ * directory as the `--source`. Idempotent — overwrites prior deploys.
773
+ *
774
+ * @implements #1035
775
+ */
776
+ async function deployOneProjectLocalBundle(opts) {
777
+ const { bundle, ctx, frameworkRoot, provider, target, dryRun, verbose, quiet, modelArgs } = opts;
778
+ const runner = createScriptRunner(frameworkRoot);
779
+ const args = [
780
+ '--source', bundle.bundlePath,
781
+ '--deploy-commands', '--deploy-skills', '--deploy-rules',
782
+ '--provider', provider,
783
+ '--target', target,
784
+ // Project-local skills MUST land in the per-project skills tier
785
+ // (#1228 follow-up). Default deploy mode after #1217 is no-copy +
786
+ // index-driven discovery, but that model assumes upstream skills at
787
+ // $AIWG_ROOT — project-local bundles live under the project's .aiwg/
788
+ // tree and aren't reachable via `aiwg discover` of the framework
789
+ // graph. Without --copy-all, the bundle's rules deploy but its skills
790
+ // never reach <provider>/.aiwg/skills/, leaving them invisible to
791
+ // both the platform and the index.
792
+ '--copy-all',
793
+ ...modelArgs,
794
+ ];
795
+ if (dryRun)
796
+ args.push('--dry-run');
797
+ if (verbose)
798
+ args.push('--verbose');
799
+ if (quiet && !verbose)
800
+ args.push('--quiet');
801
+ // Project-local bundles are addon-shaped — never trigger the legacy commands
802
+ // migration prompt (which is only relevant for full-framework deploys).
803
+ args.push('--skip-commands-migration');
804
+ const captureOpts = quiet && !verbose ? { capture: true } : {};
805
+ // Inject AIWG_ROOT so the deploy subprocess can resolve the upstream AIWG
806
+ // install root. The bundle's `--source` is its project-local path, so
807
+ // `computeAllKernelNames`/`computeAllArtifactBasenames` (which walk up from
808
+ // srcRoot looking for agentic/code/{frameworks,addons}) would otherwise fail
809
+ // and prune the provider's kernel skill directory with an empty desired set
810
+ // (#123). `frameworkRoot` is the AIWG install root that owns these trees.
811
+ const result = await runner.run('tools/agents/deploy-agents.mjs', args, {
812
+ ...captureOpts,
813
+ env: { AIWG_ROOT: frameworkRoot },
814
+ });
815
+ // Approximate counts from the bundle's source dirs (deploy-agents.mjs is
816
+ // idempotent and copies file-for-file from these dirs)
817
+ const counts = await countBundleSourceArtifacts(bundle.bundlePath);
818
+ void ctx;
819
+ return { exitCode: result.exitCode, counts };
820
+ }
821
+ /**
822
+ * Discover and deploy artifact-bearing project-local bundles from
823
+ * `.aiwg/{extensions,addons,frameworks,plugins,providers}/<id>/` for one
824
+ * provider. Provider bundles are metadata and are consumed by --provider
825
+ * resolution, not deployed as artifacts. Updates `aiwg.config.installed`
826
+ * with `source: 'project-local'` entries.
827
+ *
828
+ * Returns the number of bundles deployed and any deploy errors.
829
+ *
830
+ * @implements #1035
831
+ */
832
+ async function deployProjectLocalBundles(opts) {
833
+ const { ctx, frameworkRoot, projectDir, provider, target, dryRun, verbose, quiet, onlyBundleId, modelArgs = [], } = opts;
834
+ const discovery = await discoverProjectLocalBundles(projectDir);
835
+ if (discovery.errors.length > 0 && !quiet) {
836
+ ui.warn(`Project-local discovery surfaced ${discovery.errors.length} validation error(s) — run 'aiwg list --project-local' for details`);
837
+ }
838
+ const targetBundles = onlyBundleId
839
+ ? discovery.bundles.filter(b => b.id === onlyBundleId)
840
+ : discovery.bundles.filter(b => b.type !== 'provider');
841
+ if (targetBundles.length === 0) {
842
+ if (!onlyBundleId) {
843
+ const { loadProjectQuickref, deployProjectQuickref } = await import('../../extensions/project-quickref.js');
844
+ const quickref = await loadProjectQuickref(projectDir);
845
+ if (quickref.exists) {
846
+ try {
847
+ await deployProjectQuickref(projectDir, provider, { dryRun });
848
+ if (verbose || dryRun)
849
+ ui.dim(` + project quickref -> ${provider}`);
850
+ }
851
+ catch (error) {
852
+ ui.warn(`Project quickref deployment failed: ${error.message}`);
853
+ return { deployed: 0, failed: 1, bundles: [] };
854
+ }
855
+ }
856
+ }
857
+ return { deployed: 0, failed: 0, bundles: [] };
858
+ }
859
+ // #1037/#1049 — Activity log: emit `discover` for newly-seen bundles
860
+ // (deduped against recent log tail to avoid spam on repeated commands).
861
+ if (!dryRun) {
862
+ await emitDiscoverEventsDeduped(targetBundles.map(b => ({ id: b.id, type: b.type })));
863
+ }
864
+ // #1036 — Resolve shadows against the upstream registry before any deploy.
865
+ // Refuse to deploy bundles that contain a safety-critical shadow without an
866
+ // explicit `overrides:` declaration, or that share an artifact id with another
867
+ // project-local bundle, or that declare a phantom override.
868
+ const upstream = await buildUpstreamRegistry({ frameworkRoot });
869
+ const shadowResult = await resolveShadows(targetBundles, upstream);
870
+ const report = formatShadowReport(shadowResult);
871
+ if (report) {
872
+ process.stderr.write(report + '\n');
873
+ }
874
+ // #1037/#1049 — Activity log per shadow resolution
875
+ if (!dryRun) {
876
+ for (const r of shadowResult.resolutions) {
877
+ if (r.verdict === 'deploy')
878
+ continue; // no-collision case is silent
879
+ const bundle = targetBundles.find(b => b.id === r.bundleId);
880
+ if (!bundle)
881
+ continue;
882
+ const event = r.verdict === 'deploy-acknowledged'
883
+ ? 'shadow-acknowledged'
884
+ : r.verdict === 'refuse-unsafe' || r.verdict === 'refuse-phantom' || r.verdict === 'refuse-duplicate'
885
+ ? 'shadow-refused'
886
+ : 'conflict';
887
+ await appendProjectLocalActivity({
888
+ event,
889
+ name: bundle.id,
890
+ type: bundle.type,
891
+ summary: `${r.verdict}: ${r.artifactType}/${r.artifactId}${r.upstream ? ` overrides ${r.upstream.source}` : ''}`,
892
+ });
893
+ }
894
+ }
895
+ let deployed = 0;
896
+ let failed = 0;
897
+ for (const bundle of targetBundles) {
898
+ if (shadowResult.blockedBundleIds.has(bundle.id)) {
899
+ failed++;
900
+ ui.warn(`Refused to deploy project-local bundle '${bundle.id}' due to shadow-resolution policy (see ── above ──)`);
901
+ continue;
902
+ }
903
+ if (verbose || dryRun) {
904
+ const action = dryRun ? '[dry-run] Would deploy' : 'Deploying';
905
+ console.log(`${action} project-local ${bundle.type} '${bundle.id}' from ${bundle.localPath} → ${provider}`);
906
+ }
907
+ const result = await deployOneProjectLocalBundle({
908
+ bundle, ctx, frameworkRoot, provider, target, dryRun, verbose, quiet, modelArgs,
909
+ });
910
+ if (result.exitCode !== 0) {
911
+ failed++;
912
+ ui.warn(`Failed to deploy project-local bundle '${bundle.id}' (exit ${result.exitCode})`);
913
+ if (!dryRun) {
914
+ await appendProjectLocalActivity({
915
+ event: 'deploy-failed',
916
+ name: bundle.id,
917
+ type: bundle.type,
918
+ summary: `${provider}: exit ${result.exitCode}`,
919
+ });
920
+ }
921
+ continue;
922
+ }
923
+ deployed++;
924
+ if (!dryRun) {
925
+ const c = result.counts;
926
+ await appendProjectLocalActivity({
927
+ event: 'deploy',
928
+ name: bundle.id,
929
+ type: bundle.type,
930
+ summary: `${provider}: agents=${c.agents} commands=${c.commands} skills=${c.skills} rules=${c.rules}`,
931
+ });
932
+ }
933
+ // Persist registry entry (skip in dry-run — no side effects)
934
+ if (!dryRun) {
935
+ try {
936
+ const config = await readAiwgConfig(projectDir);
937
+ if (!config)
938
+ continue;
939
+ // Hash the bundle's manifest.json for stale detection
940
+ const manifestAbsPath = path.join(bundle.bundlePath, 'manifest.json');
941
+ const mHash = await hashManifest(manifestAbsPath);
942
+ // #1037 — record per-artifact source hashes so `aiwg remove` can
943
+ // detect pristine vs mutated vs replaced deployed files.
944
+ const artifactHashes = await hashBundleArtifacts(bundle.bundlePath);
945
+ const updated = updateInstalled(config, bundle.id, provider, result.counts, {
946
+ version: bundle.manifest.version,
947
+ source: 'project-local',
948
+ manifestHash: mHash,
949
+ localPath: bundle.localPath,
950
+ localType: bundle.type,
951
+ manifestVersion: bundle.manifest.manifestVersion,
952
+ artifactHashes,
953
+ });
954
+ await writeAiwgConfig(projectDir, updated);
955
+ }
956
+ catch (err) {
957
+ // Non-fatal: deploy already succeeded
958
+ ui.warn(`Project-local registry update failed for '${bundle.id}': ${err instanceof Error ? err.message : String(err)}`);
959
+ }
960
+ }
961
+ }
962
+ // A committed `.aiwg/quickref.json` is the canonical orientation source.
963
+ // Refresh its provider kernel copy whenever project-local bundles deploy so
964
+ // `aiwg use <bundle>` keeps the always-visible surface in sync.
965
+ const { loadProjectQuickref, deployProjectQuickref } = await import('../../extensions/project-quickref.js');
966
+ const quickref = await loadProjectQuickref(projectDir);
967
+ if (quickref.exists) {
968
+ try {
969
+ const quickrefResult = await deployProjectQuickref(projectDir, provider, { dryRun });
970
+ if (verbose || dryRun) {
971
+ ui.dim(` + project quickref -> ${quickrefResult.provider}${quickrefResult.emulated ? ' (emulated)' : ''}`);
972
+ }
973
+ }
974
+ catch (error) {
975
+ failed++;
976
+ ui.warn(`Project quickref deployment failed: ${error.message}`);
977
+ }
978
+ }
979
+ return { deployed, failed, bundles: targetBundles };
980
+ }
981
+ async function resolveProjectLocalProviderAdapter(projectDir, provider) {
982
+ const discovery = await discoverProjectLocalBundles(projectDir);
983
+ const bundle = discovery.bundles.find((candidate) => candidate.type === 'provider' && candidate.id === provider);
984
+ const extendsProvider = bundle?.manifest.providerConfig?.extends;
985
+ if (!bundle || !extendsProvider)
986
+ return { provider };
987
+ return { provider: extendsProvider, requestedProvider: provider, bundle };
988
+ }
989
+ function resolveBuiltInProviderForUse(provider) {
990
+ const normalized = normalizeProviderDefinitionId(provider);
991
+ if (normalized && normalized !== provider) {
992
+ return { provider: normalized, requestedProvider: provider };
993
+ }
994
+ return { provider };
995
+ }
996
+ function unsupportedProviderMessage(provider) {
997
+ const normalized = provider.trim().toLowerCase();
998
+ if (normalized === 'devin' || normalized === 'devin-cli') {
999
+ return [
1000
+ `Unsupported provider: ${provider}`,
1001
+ '',
1002
+ 'Devin Desktop is supported through the Windsurf compatibility adapter:',
1003
+ ' aiwg use sdlc --provider windsurf',
1004
+ ' aiwg use sdlc --provider devin-desktop',
1005
+ '',
1006
+ 'Devin CLI has distinct rules/skills surfaces and is recorded as future-provider metadata; AIWG does not emit .devin/ provider output yet.',
1007
+ ].join('\n');
1008
+ }
1009
+ return null;
1010
+ }
1011
+ const USE_FLAGS_WITH_VALUES = new Set([
1012
+ '--harness-agents',
1013
+ '--profile',
1014
+ '--provider',
1015
+ '--platform',
1016
+ '--providers',
1017
+ '--prefix',
1018
+ '--scope',
1019
+ '--target',
1020
+ ]);
1021
+ export const OPENHUMAN_DEFAULT_HARNESS_AGENTS = [
1022
+ 'architecture-designer',
1023
+ 'code-reviewer',
1024
+ 'project-manager',
1025
+ 'requirements-analyst',
1026
+ 'security-auditor',
1027
+ 'software-implementer',
1028
+ 'technical-writer',
1029
+ 'test-engineer',
1030
+ ];
1031
+ const OPENHUMAN_DEFAULT_HARNESS_PROFILE = {
1032
+ modelHint: 'agentic',
1033
+ temperature: 0.35,
1034
+ maxIterations: 10,
1035
+ iterationPolicy: 'strict',
1036
+ maxResultChars: 18000,
1037
+ maxTurnOutputTokens: 6000,
1038
+ timeoutSecs: 900,
1039
+ sandboxMode: 'none',
1040
+ tokenjuiceCompression: 'auto',
1041
+ };
1042
+ const OPENHUMAN_HARNESS_PROFILES = {
1043
+ 'aiwg-model-reasoning-worker': {
1044
+ modelHint: 'reasoning',
1045
+ },
1046
+ 'aiwg-model-coding-worker': {
1047
+ modelHint: 'coding',
1048
+ },
1049
+ 'aiwg-model-efficiency-worker': {
1050
+ modelHint: 'efficiency',
1051
+ },
1052
+ 'architecture-designer': {
1053
+ modelHint: 'reasoning',
1054
+ maxIterations: 14,
1055
+ iterationPolicy: 'extended',
1056
+ maxResultChars: 24000,
1057
+ maxTurnOutputTokens: 8000,
1058
+ timeoutSecs: 1200,
1059
+ },
1060
+ 'code-reviewer': {
1061
+ modelHint: 'coding',
1062
+ temperature: 0.25,
1063
+ maxIterations: 10,
1064
+ maxResultChars: 20000,
1065
+ tokenjuiceCompression: 'light',
1066
+ },
1067
+ 'project-manager': {
1068
+ modelHint: 'agentic',
1069
+ maxIterations: 8,
1070
+ maxResultChars: 14000,
1071
+ },
1072
+ 'requirements-analyst': {
1073
+ modelHint: 'reasoning',
1074
+ maxIterations: 12,
1075
+ iterationPolicy: 'extended',
1076
+ maxResultChars: 22000,
1077
+ },
1078
+ 'security-auditor': {
1079
+ modelHint: 'coding',
1080
+ temperature: 0.2,
1081
+ maxIterations: 12,
1082
+ iterationPolicy: 'extended',
1083
+ maxResultChars: 24000,
1084
+ maxTurnOutputTokens: 8000,
1085
+ timeoutSecs: 1200,
1086
+ tokenjuiceCompression: 'light',
1087
+ },
1088
+ 'software-implementer': {
1089
+ modelHint: 'coding',
1090
+ temperature: 0.25,
1091
+ maxIterations: 16,
1092
+ iterationPolicy: 'extended',
1093
+ maxResultChars: 26000,
1094
+ maxTurnOutputTokens: 9000,
1095
+ timeoutSecs: 1500,
1096
+ tokenjuiceCompression: 'light',
1097
+ },
1098
+ 'technical-writer': {
1099
+ modelHint: 'agentic',
1100
+ temperature: 0.45,
1101
+ maxIterations: 8,
1102
+ maxResultChars: 18000,
1103
+ },
1104
+ 'test-engineer': {
1105
+ modelHint: 'coding',
1106
+ temperature: 0.25,
1107
+ maxIterations: 14,
1108
+ iterationPolicy: 'extended',
1109
+ maxResultChars: 24000,
1110
+ maxTurnOutputTokens: 8000,
1111
+ timeoutSecs: 1200,
1112
+ tokenjuiceCompression: 'light',
1113
+ },
1114
+ };
1115
+ function readFlagValue(args, name) {
1116
+ for (let i = 0; i < args.length; i++) {
1117
+ const arg = args[i];
1118
+ if (arg === name)
1119
+ return args[i + 1];
1120
+ if (arg.startsWith(`${name}=`))
1121
+ return arg.slice(name.length + 1);
1122
+ }
1123
+ return undefined;
1124
+ }
1125
+ function removeFlagWithOptionalValue(args, name) {
1126
+ const result = [];
1127
+ for (let i = 0; i < args.length; i++) {
1128
+ const arg = args[i];
1129
+ if (arg === name) {
1130
+ i++;
1131
+ continue;
1132
+ }
1133
+ if (arg.startsWith(`${name}=`))
1134
+ continue;
1135
+ result.push(arg);
1136
+ }
1137
+ return result;
1138
+ }
1139
+ function withProviderOverride(args, provider) {
1140
+ const result = [];
1141
+ for (let i = 0; i < args.length; i++) {
1142
+ const arg = args[i];
1143
+ if ((arg === '--provider' || arg === '--platform') && i + 1 < args.length) {
1144
+ i++;
1145
+ continue;
1146
+ }
1147
+ result.push(arg);
1148
+ }
1149
+ result.push('--provider', provider);
1150
+ return result;
1151
+ }
1152
+ export function parseOpenHumanHarnessAgentSelector(args) {
1153
+ const value = readFlagValue(args, '--harness-agents');
1154
+ if (!value)
1155
+ return [];
1156
+ return Array.from(new Set(value
1157
+ .split(',')
1158
+ .map((entry) => slugifyAgentName(entry))
1159
+ .filter(Boolean)));
1160
+ }
1161
+ function hasFlag(args, name) {
1162
+ return args.some((arg) => arg === name || arg.startsWith(`${name}=`));
1163
+ }
1164
+ export function resolveOpenHumanHarnessAgentSelectors(args) {
1165
+ if (args.includes('--no-harness-agents'))
1166
+ return [];
1167
+ if (hasFlag(args, '--harness-agents'))
1168
+ return parseOpenHumanHarnessAgentSelector(args);
1169
+ return [];
1170
+ }
1171
+ function slugifyAgentName(value) {
1172
+ return value
1173
+ .trim()
1174
+ .replace(/\.md$/i, '')
1175
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
1176
+ .toLowerCase()
1177
+ .replace(/[^a-z0-9]+/g, '-')
1178
+ .replace(/^-+|-+$/g, '');
1179
+ }
1180
+ function snakeAgentId(slug) {
1181
+ return slug.replace(/-/g, '_');
1182
+ }
1183
+ function titleFromSlug(slug) {
1184
+ return slug
1185
+ .split('-')
1186
+ .filter(Boolean)
1187
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
1188
+ .join(' ');
1189
+ }
1190
+ function parseAgentMarkdown(slug, content) {
1191
+ if (!content.startsWith('---\n')) {
1192
+ return { slug, frontmatter: {}, body: content.trimStart() };
1193
+ }
1194
+ const end = content.indexOf('\n---', 4);
1195
+ if (end < 0)
1196
+ return { slug, frontmatter: {}, body: content.trimStart() };
1197
+ const rawFrontmatter = content.slice(4, end);
1198
+ const bodyStart = content.indexOf('\n', end + 4);
1199
+ const body = bodyStart >= 0 ? content.slice(bodyStart + 1) : '';
1200
+ const parsed = YAML.parse(rawFrontmatter);
1201
+ return {
1202
+ slug,
1203
+ frontmatter: parsed && typeof parsed === 'object' ? parsed : {},
1204
+ body: body.trimStart(),
1205
+ };
1206
+ }
1207
+ function frontmatterString(meta, key) {
1208
+ const value = meta[key];
1209
+ if (typeof value === 'string')
1210
+ return value;
1211
+ return undefined;
1212
+ }
1213
+ function normalizeDescription(value, slug) {
1214
+ const description = value?.replace(/\s+/g, ' ').trim();
1215
+ return description || `Use the ${titleFromSlug(slug)} AIWG specialist.`;
1216
+ }
1217
+ function escapeTomlBasicString(value) {
1218
+ return JSON.stringify(value);
1219
+ }
1220
+ function tomlMultilineLiteral(value) {
1221
+ // TOML literal strings cannot contain three consecutive apostrophes.
1222
+ return `'''${value.replace(/'''/g, "''\\'")}'''`;
1223
+ }
1224
+ function openHumanHarnessProfile(slug) {
1225
+ return {
1226
+ ...OPENHUMAN_DEFAULT_HARNESS_PROFILE,
1227
+ ...(OPENHUMAN_HARNESS_PROFILES[slug] ?? {}),
1228
+ };
1229
+ }
1230
+ function renderOpenHumanHarnessToml(agent, promptBody) {
1231
+ const id = snakeAgentId(agent.slug);
1232
+ const profile = openHumanHarnessProfile(agent.slug);
1233
+ return [
1234
+ '# AIWG-managed OpenHuman native harness agent; do not hand-edit.',
1235
+ '# Source template: agentic/code/frameworks/sdlc-complete/templates/openhuman/agent.toml.aiwg-template',
1236
+ `id = "aiwg_${id}"`,
1237
+ `when_to_use = ${escapeTomlBasicString(normalizeDescription(frontmatterString(agent.frontmatter, 'description'), agent.slug))}`,
1238
+ `display_name = ${escapeTomlBasicString(frontmatterString(agent.frontmatter, 'name') || titleFromSlug(agent.slug))}`,
1239
+ '',
1240
+ 'agent_tier = "worker"',
1241
+ `temperature = ${profile.temperature}`,
1242
+ `max_iterations = ${profile.maxIterations}`,
1243
+ `iteration_policy = "${profile.iterationPolicy}"`,
1244
+ `max_result_chars = ${profile.maxResultChars}`,
1245
+ `max_turn_output_tokens = ${profile.maxTurnOutputTokens}`,
1246
+ `timeout_secs = ${profile.timeoutSecs}`,
1247
+ `sandbox_mode = "${profile.sandboxMode}"`,
1248
+ `tokenjuice_compression = "${profile.tokenjuiceCompression}"`,
1249
+ '',
1250
+ 'omit_identity = true',
1251
+ 'omit_memory_context = true',
1252
+ 'omit_safety_preamble = true',
1253
+ 'omit_skills_catalog = false',
1254
+ 'omit_profile = true',
1255
+ 'omit_memory_md = false',
1256
+ 'background = false',
1257
+ 'trigger_memory_agent = "never"',
1258
+ '',
1259
+ '[system_prompt]',
1260
+ `inline = ${tomlMultilineLiteral(promptBody.trim())}`,
1261
+ '',
1262
+ '[model]',
1263
+ `hint = "${profile.modelHint}"`,
1264
+ '',
1265
+ ].join('\n');
1266
+ }
1267
+ function projectHarnessToml(agent) {
1268
+ const id = snakeAgentId(agent.slug);
1269
+ return [
1270
+ '# AIWG-managed OpenHuman harness definition; do not hand-edit.',
1271
+ `id = "aiwg_${id}"`,
1272
+ `when_to_use = ${escapeTomlBasicString(normalizeDescription(frontmatterString(agent.frontmatter, 'description'), agent.slug))}`,
1273
+ `display_name = ${escapeTomlBasicString(frontmatterString(agent.frontmatter, 'name') || titleFromSlug(agent.slug))}`,
1274
+ '',
1275
+ '[system_prompt]',
1276
+ `file = "aiwg/${id}.md"`,
1277
+ '',
1278
+ ].join('\n');
1279
+ }
1280
+ function userHarnessToml(agent) {
1281
+ return renderOpenHumanHarnessToml(agent, agent.body);
1282
+ }
1283
+ function assertNoSubagentsKey(toml) {
1284
+ if (/^\s*subagents\s*=/m.test(toml)) {
1285
+ throw new Error('OpenHuman AIWG harness definitions must not emit `subagents` for Worker-tier agents');
1286
+ }
1287
+ }
1288
+ function openHumanHomeDir() {
1289
+ return process.env.OPENHUMAN_HOME || path.join(os.homedir(), '.openhuman');
1290
+ }
1291
+ async function writeManagedFile(filePath, content, dryRun) {
1292
+ if (dryRun)
1293
+ return;
1294
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
1295
+ await fs.writeFile(filePath, content, 'utf-8');
1296
+ }
1297
+ async function readSourceAgent(frameworkRoot, slug) {
1298
+ const candidates = [
1299
+ path.join(frameworkRoot, 'agentic/code/frameworks/sdlc-complete/agents', `${slug}.md`),
1300
+ path.join(frameworkRoot, 'agentic/code/addons/aiwg-utils/agents', `${slug}.md`),
1301
+ path.join(frameworkRoot, 'agentic/code/agents/personas', `${slug}.md`),
1302
+ ];
1303
+ for (const candidate of candidates) {
1304
+ try {
1305
+ const content = await fs.readFile(candidate, 'utf-8');
1306
+ const parsed = parseAgentMarkdown(slug, content);
1307
+ if (!parsed.body.trim())
1308
+ throw new Error(`${candidate} has an empty prompt body after frontmatter stripping`);
1309
+ return parsed;
1310
+ }
1311
+ catch (error) {
1312
+ if (error.code !== 'ENOENT')
1313
+ throw error;
1314
+ }
1315
+ }
1316
+ throw new Error(`Unknown AIWG agent '${slug}' for --harness-agents`);
1317
+ }
1318
+ export async function deployOpenHumanHarnessAgents(opts) {
1319
+ const uniqueSelectors = Array.from(new Set(opts.selectors.map(slugifyAgentName).filter(Boolean)));
1320
+ if (uniqueSelectors.length === 0)
1321
+ return { emitted: 0, tomlPaths: [], promptPaths: [] };
1322
+ const tomlRoot = opts.scope === 'user'
1323
+ ? path.join(openHumanHomeDir(), 'agents')
1324
+ : path.join(opts.target, 'agents');
1325
+ const promptRoot = path.join(opts.target, 'agent', 'prompts', 'aiwg');
1326
+ const tomlPaths = [];
1327
+ const promptPaths = [];
1328
+ for (const slug of uniqueSelectors) {
1329
+ const agent = await readSourceAgent(opts.frameworkRoot, slug);
1330
+ const id = snakeAgentId(slug);
1331
+ const toml = opts.scope === 'user' ? userHarnessToml(agent) : projectHarnessToml(agent);
1332
+ assertNoSubagentsKey(toml);
1333
+ const tomlPath = path.join(tomlRoot, `aiwg_${id}.toml`);
1334
+ tomlPaths.push(tomlPath);
1335
+ await writeManagedFile(tomlPath, toml, !!opts.dryRun);
1336
+ if (opts.scope === 'project') {
1337
+ const promptPath = path.join(promptRoot, `${id}.md`);
1338
+ promptPaths.push(promptPath);
1339
+ await writeManagedFile(promptPath, agent.body.trimStart(), !!opts.dryRun);
1340
+ }
1341
+ }
1342
+ return { emitted: uniqueSelectors.length, tomlPaths, promptPaths };
1343
+ }
1344
+ function firstUsePositional(args) {
1345
+ for (let i = 0; i < args.length; i++) {
1346
+ const arg = args[i];
1347
+ if (USE_FLAGS_WITH_VALUES.has(arg)) {
1348
+ i++;
1349
+ continue;
1350
+ }
1351
+ if (!arg.startsWith('-'))
1352
+ return arg;
1353
+ }
1354
+ return undefined;
1355
+ }
1356
+ function removeFirstPositional(args) {
1357
+ let skipped = false;
1358
+ const result = [];
1359
+ for (let i = 0; i < args.length; i++) {
1360
+ const arg = args[i];
1361
+ if (USE_FLAGS_WITH_VALUES.has(arg)) {
1362
+ result.push(arg);
1363
+ if (i + 1 < args.length)
1364
+ result.push(args[++i]);
1365
+ continue;
1366
+ }
1367
+ if (!skipped && !arg.startsWith('-')) {
1368
+ skipped = true;
1369
+ continue;
1370
+ }
1371
+ result.push(arg);
1372
+ }
1373
+ return result;
1374
+ }
1375
+ function removeGlobalBootstrapFlags(args) {
1376
+ const result = [];
1377
+ const valueFlags = new Set(['--provider', '--platform', '--providers', '--scope', '--target', '--prefix']);
1378
+ const booleanFlags = new Set([
1379
+ '--global', '--user', '--ci-hooks-enabled', '--no-project-local',
1380
+ '--no-context-files', '--no-hooks', '--no-workspace-signals',
1381
+ ]);
1382
+ for (let i = 0; i < args.length; i += 1) {
1383
+ const arg = args[i];
1384
+ if (valueFlags.has(arg)) {
1385
+ i += 1;
1386
+ continue;
1387
+ }
1388
+ if (booleanFlags.has(arg))
1389
+ continue;
1390
+ result.push(arg);
1391
+ }
1392
+ return result;
1393
+ }
1394
+ function configuredGlobalProviders(args, config) {
1395
+ const providerIdx = args.findIndex((arg) => arg === '--provider' || arg === '--platform');
1396
+ if (providerIdx >= 0 && args[providerIdx + 1])
1397
+ return [args[providerIdx + 1]];
1398
+ const providersIdx = args.indexOf('--providers');
1399
+ if (providersIdx >= 0 && args[providersIdx + 1]) {
1400
+ const value = args[providersIdx + 1];
1401
+ return value === 'default'
1402
+ ? ['claude']
1403
+ : [...new Set(value.split(',').map((provider) => provider.trim()).filter(Boolean))];
1404
+ }
1405
+ return config?.providers?.length ? [...new Set(config.providers)] : ['claude'];
1406
+ }
1407
+ async function generateGlobalProjectContext(opts) {
1408
+ const userPaths = USER_SCOPE_PATHS[opts.provider];
1409
+ if (!userPaths)
1410
+ return;
1411
+ const skipContext = opts.args.includes('--no-context-files');
1412
+ const sections = await discoverDeployedArtifacts(opts.projectPath, {
1413
+ agents: userPaths.agents,
1414
+ rules: userPaths.rules,
1415
+ skills: userPaths.skills,
1416
+ behaviors: userPaths.behaviors,
1417
+ });
1418
+ await generateContextFiles({
1419
+ provider: opts.provider,
1420
+ projectPath: opts.projectPath,
1421
+ sections,
1422
+ detectExistingFiles: true,
1423
+ force: opts.args.includes('--force-context-files'),
1424
+ skip: {
1425
+ workspaceMd: skipContext || opts.args.includes('--no-workspace-md'),
1426
+ aiwgMd: skipContext || opts.args.includes('--no-aiwg-md'),
1427
+ agentsMd: skipContext || opts.args.includes('--no-agents-md'),
1428
+ },
1429
+ });
1430
+ }
1431
+ async function deploySourceDirectory(opts) {
1432
+ const args = [
1433
+ '--source', opts.source,
1434
+ '--deploy-commands',
1435
+ '--deploy-skills',
1436
+ '--deploy-rules',
1437
+ '--provider', opts.provider,
1438
+ '--target', opts.target,
1439
+ ...opts.modelArgs,
1440
+ ];
1441
+ if (opts.dryRun)
1442
+ args.push('--dry-run');
1443
+ if (opts.verbose)
1444
+ args.push('--verbose');
1445
+ if (opts.force)
1446
+ args.push('--force');
1447
+ if (opts.copyAll)
1448
+ args.push('--copy-all');
1449
+ if (opts.quiet)
1450
+ args.unshift('--quiet');
1451
+ const runner = createScriptRunner(opts.frameworkRoot);
1452
+ return runner.run('tools/agents/deploy-agents.mjs', args, opts.quiet ? { capture: true } : {});
1453
+ }
1454
+ /**
1455
+ * Use command handler
1456
+ *
1457
+ * Deploys framework agents, commands, and skills to the current project,
1458
+ * then registers them in the extension registry for discovery.
1459
+ */
1460
+ export class UseHandler {
1461
+ id = 'use';
1462
+ name = 'Use Framework';
1463
+ description = 'Deploy AIWG framework to project or user scope';
1464
+ category = 'framework';
1465
+ aliases = [];
1466
+ async execute(ctx) {
1467
+ const explicitTarget = firstUsePositional(ctx.args);
1468
+ if (ctx.args.includes('--workspace-signals')) {
1469
+ const signalArgs = ctx.args.filter((a) => a !== '--workspace-signals');
1470
+ const profileIdx = signalArgs.findIndex((a) => a === '--profile');
1471
+ const profile = profileIdx >= 0 && signalArgs[profileIdx + 1]
1472
+ ? signalArgs[profileIdx + 1]
1473
+ : undefined;
1474
+ const requestedTarget = firstUsePositional(signalArgs);
1475
+ const remainingSignalArgs = requestedTarget
1476
+ ? removeFirstPositional(signalArgs)
1477
+ : signalArgs;
1478
+ const projectDir = getProjectDir(ctx, remainingSignalArgs);
1479
+ const plan = await resolveWorkspaceSignalPlan(projectDir, { profile, requestedTarget });
1480
+ return {
1481
+ exitCode: 0,
1482
+ message: formatWorkspaceSignalPlan(plan),
1483
+ };
1484
+ }
1485
+ let framework = ctx.args[0];
1486
+ let remainingArgs = ctx.args.slice(1);
1487
+ if (framework === '--profile') {
1488
+ framework = 'all';
1489
+ remainingArgs = ctx.args;
1490
+ }
1491
+ const modelDeployArgs = collectUseModelDeployArgs(remainingArgs);
1492
+ if (framework === 'cockpit') {
1493
+ return installCockpit(ctx, remainingArgs);
1494
+ }
1495
+ // Structured logger for this invocation. Records go to both stderr (if
1496
+ // verbose level) and ~/.aiwg/logs/aiwg-YYYY-MM-DD.jsonl with full
1497
+ // provenance (invocation_id, aiwg_version, git_sha, etc.). #925.
1498
+ const log = getLogger('cli:use', { framework: framework ?? '<all>' });
1499
+ const span = log.span('use');
1500
+ // Resolve --prefix as alias for --target (#734)
1501
+ // --prefix is more intuitive for "deploy to a project directory" in cloud-init/CI
1502
+ const prefixIdx = remainingArgs.findIndex(a => a === '--prefix');
1503
+ if (prefixIdx >= 0 && remainingArgs[prefixIdx + 1]) {
1504
+ // Rewrite --prefix to --target for downstream compatibility
1505
+ remainingArgs[prefixIdx] = '--target';
1506
+ }
1507
+ // Global bootstrap deliberately avoids a persistent project artifact
1508
+ // deployment. Build the normal provider output in an isolated staging
1509
+ // directory, let the established user-scope mirror/registry path consume
1510
+ // it, then discard the stage and emit only lightweight project context.
1511
+ // `--scope user` remains additive for compatibility; `--global` is the
1512
+ // explicit no-project-deploy contract.
1513
+ if (remainingArgs.includes('--global')) {
1514
+ const scopeIdx = remainingArgs.indexOf('--scope');
1515
+ if (scopeIdx >= 0 && remainingArgs[scopeIdx + 1] === 'project') {
1516
+ return { exitCode: 1, message: 'Error: --global conflicts with --scope project' };
1517
+ }
1518
+ if (!framework || !VALID_FRAMEWORKS.includes(framework)) {
1519
+ return {
1520
+ exitCode: 1,
1521
+ message: 'Error: --global currently supports framework targets; addons and project-local bundles require project deployment',
1522
+ };
1523
+ }
1524
+ const contextTargetIdx = remainingArgs.indexOf('--target');
1525
+ const contextTarget = path.resolve(contextTargetIdx >= 0 && remainingArgs[contextTargetIdx + 1]
1526
+ ? remainingArgs[contextTargetIdx + 1]
1527
+ : (ctx.cwd || process.cwd()));
1528
+ const originalConfig = await readAiwgConfig(contextTarget);
1529
+ const providers = configuredGlobalProviders(remainingArgs, originalConfig);
1530
+ const dryRun = remainingArgs.includes('--dry-run');
1531
+ const stageRoot = dryRun
1532
+ ? path.join(os.tmpdir(), 'aiwg-global-bootstrap-dry-run')
1533
+ : await fs.mkdtemp(path.join(os.tmpdir(), 'aiwg-global-bootstrap-'));
1534
+ try {
1535
+ for (const provider of providers) {
1536
+ const innerArgs = [
1537
+ framework,
1538
+ ...removeGlobalBootstrapFlags(remainingArgs),
1539
+ '--provider', provider,
1540
+ '--scope', 'user',
1541
+ '--target', stageRoot,
1542
+ '--no-project-local',
1543
+ '--no-context-files',
1544
+ '--no-hooks',
1545
+ '--no-workspace-signals',
1546
+ ];
1547
+ const result = await this.execute({ ...ctx, cwd: stageRoot, args: innerArgs });
1548
+ if (result.exitCode !== 0)
1549
+ return result;
1550
+ if (!dryRun) {
1551
+ await fs.mkdir(contextTarget, { recursive: true });
1552
+ await generateGlobalProjectContext({
1553
+ provider: normalizeProviderDefinitionId(provider) ?? provider,
1554
+ projectPath: contextTarget,
1555
+ args: remainingArgs,
1556
+ });
1557
+ }
1558
+ }
1559
+ }
1560
+ finally {
1561
+ if (!dryRun)
1562
+ await fs.rm(stageRoot, { recursive: true, force: true });
1563
+ }
1564
+ return {
1565
+ exitCode: 0,
1566
+ message: dryRun
1567
+ ? `Global bootstrap preview complete; project context target: ${contextTarget}`
1568
+ : `Global bootstrap complete; user assets installed and lightweight project context generated at ${contextTarget}`,
1569
+ };
1570
+ }
1571
+ // Project-isolation warning (UC-NUA-002 / SAD §5.1). Fires once per CLI
1572
+ // process. Skipped when --target/--prefix is explicit (the user named a
1573
+ // destination) so the warning never fights with intentional out-of-cwd
1574
+ // deploys. Skipped on recursive iteration via the module-level guard.
1575
+ if (!projectIsolationChecked) {
1576
+ projectIsolationChecked = true;
1577
+ const userTargetedExplicitDir = remainingArgs.includes('--target');
1578
+ if (!userTargetedExplicitDir) {
1579
+ const isolationResult = await maybeWarnProjectIsolation({ cwd: ctx.cwd ?? process.cwd() });
1580
+ if (isolationResult.cancelled) {
1581
+ // User pressed Ctrl-C during the delay — exit cleanly with no
1582
+ // artifacts written (UC-NUA-002 Alt A2).
1583
+ return { exitCode: 130, message: 'Cancelled.' };
1584
+ }
1585
+ }
1586
+ }
1587
+ // Read project config for config-first resolution (#621).
1588
+ // projectDir resolution uses the shared helper so --target/--prefix,
1589
+ // ctx.cwd, and process.cwd() fallback are handled consistently across
1590
+ // handlers (#919 cleanup).
1591
+ const targetFlagIdx = remainingArgs.findIndex(a => a === '--target');
1592
+ const targetDir = targetFlagIdx >= 0 && remainingArgs[targetFlagIdx + 1]
1593
+ ? remainingArgs[targetFlagIdx + 1]
1594
+ : null;
1595
+ const projectDir = getProjectDir(ctx, remainingArgs);
1596
+ let config = await readAiwgConfig(projectDir);
1597
+ // Auto-init when no config found (#720)
1598
+ // Check early for --provider/--platform and --providers flags
1599
+ const _providerFlagIdx = remainingArgs.findIndex(a => a === '--provider' || a === '--platform');
1600
+ const _hasExplicitProvider = _providerFlagIdx >= 0 && !!remainingArgs[_providerFlagIdx + 1];
1601
+ const _providersFlagIdx = remainingArgs.findIndex(a => a === '--providers');
1602
+ const _providersValue = _providersFlagIdx >= 0 ? remainingArgs[_providersFlagIdx + 1] : null;
1603
+ const _isDryRun = remainingArgs.includes('--dry-run');
1604
+ // Bulk/automation intent: `aiwg use all` and `aiwg use --yes` skip the
1605
+ // init wizard and use sensible defaults so CLI calls never hang waiting
1606
+ // on a detached terminal. Users who want the wizard run `aiwg init`.
1607
+ const _isBulkIntent = framework === 'all'
1608
+ || remainingArgs.includes('--yes')
1609
+ || remainingArgs.includes('-y')
1610
+ || remainingArgs.includes('--non-interactive');
1611
+ if (!config) {
1612
+ if (_providersValue) {
1613
+ // --providers shorthand: write config without wizard
1614
+ const pList = _providersValue === 'default'
1615
+ ? ['claude']
1616
+ : _providersValue.split(',').map(s => s.trim()).filter(Boolean);
1617
+ config = emptyConfig(pList.length > 0 ? pList : ['claude']);
1618
+ if (!_isDryRun)
1619
+ await writeAiwgConfig(projectDir, config);
1620
+ }
1621
+ else if (_isBulkIntent || targetDir || _hasExplicitProvider || !process.stdin.isTTY) {
1622
+ // Non-interactive: auto-create minimal config with explicit provider or default (#734)
1623
+ // When --prefix/--target is set, or `use all`, or --yes is passed, we're in
1624
+ // automated mode — no wizard, no prompts, no way to hang on stdin.
1625
+ const autoProvider = _hasExplicitProvider ? remainingArgs[_providerFlagIdx + 1] : 'claude';
1626
+ config = emptyConfig([autoProvider]);
1627
+ if (!_isDryRun)
1628
+ await writeAiwgConfig(projectDir, config);
1629
+ if (!_isDryRun && _isBulkIntent && framework === 'all') {
1630
+ ui.dim(` No .aiwg/aiwg.config found — auto-created with provider '${autoProvider}'. Ask your AIWG agent to review repo/tracker/delivery policy.`);
1631
+ }
1632
+ }
1633
+ else if (process.stdin.isTTY) {
1634
+ // Interactive terminal with no config → run init wizard inline (#720)
1635
+ const initResult = await initHandler.execute({ ...ctx, args: [] });
1636
+ if (initResult.exitCode !== 0)
1637
+ return initResult;
1638
+ config = await readAiwgConfig(projectDir);
1639
+ }
1640
+ }
1641
+ // Zero-arg form: `aiwg use` with no framework → redeploy all installed to all providers
1642
+ if (!framework) {
1643
+ if (!config || Object.keys(config.installed).length === 0) {
1644
+ const advisory = !config
1645
+ ? "\n\nRun 'aiwg init', then ask your AIWG agent to establish providers, tracker, and delivery policy."
1646
+ : '';
1647
+ return {
1648
+ exitCode: 1,
1649
+ message: `Error: Framework, addon, or extension name required\nFrameworks: sdlc, marketing, media-curator, research, forensics, dfir, security-engineering, ops, validation, knowledge-base, all\nAddons: rlm, ring, daemon, aiwg-dev (full list: \`aiwg list\`)\nExtensions: sys, net, it, sec, stream, dev (full list: \`ls $AIWG_ROOT/agentic/code/extensions\`)\n'all' deploys every framework + every addon + every extension.${advisory}`,
1650
+ };
1651
+ }
1652
+ const installedNames = Object.keys(config.installed);
1653
+ const redeployProviders = config.providers.length > 0 ? config.providers : ['claude'];
1654
+ ui.blank();
1655
+ ui.header(` Redeploying ${installedNames.length} framework(s) to ${redeployProviders.join(', ')}...`);
1656
+ for (const name of installedNames) {
1657
+ for (const p of redeployProviders) {
1658
+ const result = await this.execute({ ...ctx, args: [name, '--provider', p] });
1659
+ if (result.exitCode !== 0)
1660
+ return result;
1661
+ }
1662
+ }
1663
+ return { exitCode: 0 };
1664
+ }
1665
+ const frameworkRoot = await getFrameworkRoot();
1666
+ if (framework === 'all' && explicitTarget !== 'all' && !remainingArgs.includes('--no-workspace-signals')) {
1667
+ const profileIdx = remainingArgs.findIndex((a) => a === '--profile');
1668
+ const profile = profileIdx >= 0 && remainingArgs[profileIdx + 1]
1669
+ ? remainingArgs[profileIdx + 1]
1670
+ : undefined;
1671
+ const plan = await resolveWorkspaceSignalPlan(projectDir, { profile, requestedTarget: framework });
1672
+ const selectedFrameworks = includedBundleIds(plan, 'framework');
1673
+ const selectedAddons = includedBundleIds(plan, 'addon');
1674
+ const selectedExtensions = includedBundleIds(plan, 'extension');
1675
+ const providerIdx = remainingArgs.findIndex(a => a === '--provider' || a === '--platform');
1676
+ const explicitProvider = providerIdx >= 0 && remainingArgs[providerIdx + 1] ? remainingArgs[providerIdx + 1] : null;
1677
+ let providersForFiltered;
1678
+ if (explicitProvider) {
1679
+ providersForFiltered = [explicitProvider];
1680
+ }
1681
+ else if (_providersValue) {
1682
+ providersForFiltered = _providersValue === 'default'
1683
+ ? ['claude']
1684
+ : _providersValue.split(',').map(s => s.trim()).filter(Boolean);
1685
+ }
1686
+ else if (config && config.providers.length > 0) {
1687
+ providersForFiltered = config.providers;
1688
+ }
1689
+ else {
1690
+ providersForFiltered = ['claude'];
1691
+ }
1692
+ const targetIdx = remainingArgs.findIndex(a => a === '--target');
1693
+ const target = targetIdx >= 0 && remainingArgs[targetIdx + 1] ? remainingArgs[targetIdx + 1] : process.cwd();
1694
+ const dryRun = remainingArgs.includes('--dry-run');
1695
+ const verbose = remainingArgs.includes('--verbose') || remainingArgs.includes('-v');
1696
+ const force = remainingArgs.includes('--force');
1697
+ const copyAll = remainingArgs.includes('--copy-all') || remainingArgs.includes('--copy-standard-skills');
1698
+ const quiet = !verbose && !dryRun;
1699
+ ui.blank();
1700
+ ui.header(` Workspace-aware deployment (${plan.profile})`);
1701
+ ui.dim(` Included frameworks: ${selectedFrameworks.join(', ') || '(none)'}`);
1702
+ ui.dim(` Included addons: ${selectedAddons.join(', ') || '(none)'}`);
1703
+ if (selectedExtensions.length > 0) {
1704
+ ui.dim(` Included extensions: ${selectedExtensions.join(', ')}`);
1705
+ }
1706
+ ui.dim(' Use `aiwg use all` for the full deployment.');
1707
+ for (const providerName of providersForFiltered) {
1708
+ for (const selected of selectedFrameworks) {
1709
+ const frameworkDir = resolveFrameworkDir(selected);
1710
+ if (!frameworkDir)
1711
+ continue;
1712
+ const result = await deploySourceDirectory({
1713
+ ctx,
1714
+ frameworkRoot,
1715
+ source: path.join(frameworkRoot, 'agentic/code/frameworks', frameworkDir),
1716
+ provider: providerName,
1717
+ target,
1718
+ dryRun,
1719
+ verbose,
1720
+ force,
1721
+ copyAll,
1722
+ quiet,
1723
+ modelArgs: modelDeployArgs,
1724
+ });
1725
+ if (result.exitCode !== 0)
1726
+ return result;
1727
+ }
1728
+ for (const selected of selectedAddons) {
1729
+ const result = await deploySourceDirectory({
1730
+ ctx,
1731
+ frameworkRoot,
1732
+ source: addonPath(frameworkRoot, selected),
1733
+ provider: providerName,
1734
+ target,
1735
+ dryRun,
1736
+ verbose,
1737
+ force,
1738
+ copyAll,
1739
+ quiet,
1740
+ modelArgs: modelDeployArgs,
1741
+ });
1742
+ if (result.exitCode !== 0)
1743
+ return result;
1744
+ }
1745
+ for (const selected of selectedExtensions) {
1746
+ const result = await deploySourceDirectory({
1747
+ ctx,
1748
+ frameworkRoot,
1749
+ source: extensionPath(frameworkRoot, selected),
1750
+ provider: providerName,
1751
+ target,
1752
+ dryRun,
1753
+ verbose,
1754
+ force,
1755
+ copyAll,
1756
+ quiet,
1757
+ modelArgs: modelDeployArgs,
1758
+ });
1759
+ if (result.exitCode !== 0)
1760
+ return result;
1761
+ }
1762
+ if (!remainingArgs.includes('--no-project-local')) {
1763
+ const plResult = await deployProjectLocalBundles({
1764
+ ctx,
1765
+ frameworkRoot,
1766
+ projectDir,
1767
+ provider: providerName,
1768
+ target,
1769
+ dryRun,
1770
+ verbose,
1771
+ quiet: !verbose && !dryRun,
1772
+ modelArgs: modelDeployArgs,
1773
+ });
1774
+ if (plResult.failed > 0) {
1775
+ ui.warn(`${plResult.failed} project-local bundle(s) failed to deploy`);
1776
+ }
1777
+ }
1778
+ if (!dryRun) {
1779
+ try {
1780
+ const registry = getRegistry();
1781
+ const paths = getProviderPaths(providerName);
1782
+ await registerDeployedExtensions(registry, {
1783
+ agentsPath: paths.agents,
1784
+ skillsPath: paths.skills,
1785
+ commandsPath: paths.commands,
1786
+ rulesPath: paths.rules,
1787
+ behaviorsPath: paths.behaviors,
1788
+ provider: providerName,
1789
+ cwd: target,
1790
+ });
1791
+ const counts = await countDeployedArtifacts(target, paths);
1792
+ if (quiet) {
1793
+ ui.blank();
1794
+ if (counts.agents > 0)
1795
+ ui.deployCount('Agents', counts.agents);
1796
+ if (counts.commands > 0)
1797
+ ui.deployCount('Commands', counts.commands);
1798
+ if (counts.skills > 0)
1799
+ ui.deployCount('Skills', counts.skills);
1800
+ if (counts.rules > 0)
1801
+ ui.deployCount('Rules', counts.rules);
1802
+ if (counts.behaviors > 0)
1803
+ ui.deployCount('Behaviors', counts.behaviors);
1804
+ ui.blank();
1805
+ printSessionReloadNotice(providerName);
1806
+ }
1807
+ }
1808
+ catch (error) {
1809
+ ui.warn(`Filtered deployment registration failed: ${error instanceof Error ? error.message : String(error)}`);
1810
+ }
1811
+ }
1812
+ }
1813
+ if (!remainingArgs.includes('--dry-run')) {
1814
+ await writeWorkspaceSignalPlan(projectDir, plan);
1815
+ }
1816
+ return { exitCode: 0 };
1817
+ }
1818
+ const isFramework = VALID_FRAMEWORKS.includes(framework);
1819
+ const isAddon = !isFramework && await isValidAddon(frameworkRoot, framework);
1820
+ // Extensions live in `agentic/code/extensions/<name>/` and are addon-shaped
1821
+ // bundles. We treat them as addons for deployment purposes (#1222) — when
1822
+ // the user runs `aiwg use sys` we resolve to the extension source dir and
1823
+ // deploy via the addon code path below by remapping `addonPath()` lookup.
1824
+ const isExtension = !isFramework && !isAddon
1825
+ && (await getAllExtensions(frameworkRoot)).includes(framework);
1826
+ // Project-local bundle resolution: when the name doesn't match an upstream
1827
+ // framework, addon, or extension, check `.aiwg/{extensions,addons,
1828
+ // frameworks,plugins,providers}/<id>/` for a matching bundle. (#1035)
1829
+ if (!isFramework && !isAddon && !isExtension) {
1830
+ const discovery = await discoverProjectLocalBundles(projectDir);
1831
+ const match = discovery.bundles.find(b => b.id === framework);
1832
+ if (match) {
1833
+ if (match.type === 'provider') {
1834
+ return {
1835
+ exitCode: 1,
1836
+ message: `Project-local provider '${match.id}' is selected with --provider.\n\nExample: aiwg use sdlc --provider ${match.id}`,
1837
+ };
1838
+ }
1839
+ const providerIdx = remainingArgs.findIndex(a => a === '--provider' || a === '--platform');
1840
+ const explicitProvider = providerIdx >= 0 && remainingArgs[providerIdx + 1] ? remainingArgs[providerIdx + 1] : null;
1841
+ const dryRunSingle = remainingArgs.includes('--dry-run');
1842
+ const verboseSingle = remainingArgs.includes('--verbose') || remainingArgs.includes('-v');
1843
+ const targetIdxSingle = remainingArgs.findIndex(a => a === '--target');
1844
+ const targetSingle = targetIdxSingle >= 0 && remainingArgs[targetIdxSingle + 1] ? remainingArgs[targetIdxSingle + 1] : process.cwd();
1845
+ // Multi-provider expansion mirrors the framework path
1846
+ let providersForSingle;
1847
+ if (explicitProvider)
1848
+ providersForSingle = [explicitProvider];
1849
+ else if (config && config.providers.length > 0)
1850
+ providersForSingle = config.providers;
1851
+ else
1852
+ providersForSingle = ['claude'];
1853
+ let totalDeployed = 0;
1854
+ let totalFailed = 0;
1855
+ for (const p of providersForSingle) {
1856
+ const r = await deployProjectLocalBundles({
1857
+ ctx, frameworkRoot, projectDir, provider: p, target: targetSingle,
1858
+ dryRun: dryRunSingle, verbose: verboseSingle, quiet: !verboseSingle && !dryRunSingle,
1859
+ onlyBundleId: framework,
1860
+ modelArgs: modelDeployArgs,
1861
+ });
1862
+ totalDeployed += r.deployed;
1863
+ totalFailed += r.failed;
1864
+ }
1865
+ if (!verboseSingle && !dryRunSingle) {
1866
+ ui.blank();
1867
+ ui.success(`project-local ${match.type} '${match.id}' deployed (${totalDeployed} provider(s))`);
1868
+ }
1869
+ return {
1870
+ exitCode: totalFailed > 0 ? 1 : 0,
1871
+ message: totalFailed > 0 ? `${totalFailed} project-local deploy(s) failed` : '',
1872
+ };
1873
+ }
1874
+ return {
1875
+ exitCode: 1,
1876
+ message: `Error: Unknown target '${framework}'\nFrameworks: ${VALID_FRAMEWORKS.join(', ')}\n\n'all' deploys every framework + every addon + every extension.\nFor a single addon, run 'aiwg list' to see available addons.\nFor extensions (sys, net, it, sec, stream, dev), see $AIWG_ROOT/agentic/code/extensions/.\nFor project-local artifacts, run 'aiwg list --project-local'.\nRun 'aiwg help' for usage information.`,
1877
+ };
1878
+ }
1879
+ // Handle addon-only or extension-only deployment.
1880
+ // Extensions are addon-shaped bundles — same code path, different source dir.
1881
+ if (isAddon || isExtension) {
1882
+ const providerIdx = remainingArgs.findIndex(a => a === '--provider' || a === '--platform');
1883
+ const explicitAddonProvider = providerIdx >= 0 && remainingArgs[providerIdx + 1] ? remainingArgs[providerIdx + 1] : null;
1884
+ const provider = explicitAddonProvider ?? (config?.providers?.[0] ?? 'claude');
1885
+ const targetIdx = remainingArgs.findIndex(a => a === '--target');
1886
+ const target = targetIdx >= 0 && remainingArgs[targetIdx + 1] ? remainingArgs[targetIdx + 1] : process.cwd();
1887
+ const runner = createScriptRunner(ctx.frameworkRoot);
1888
+ const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
1889
+ addonBaseArgs.push(...modelDeployArgs);
1890
+ if (provider)
1891
+ addonBaseArgs.push('--provider', provider);
1892
+ if (target)
1893
+ addonBaseArgs.push('--target', target);
1894
+ // Forward --copy-all (#1219) so addon-only deploys also honor it.
1895
+ if (remainingArgs.includes('--copy-all') || remainingArgs.includes('--copy-standard-skills')) {
1896
+ addonBaseArgs.push('--copy-all');
1897
+ }
1898
+ const kind = isExtension ? 'extension' : 'addon';
1899
+ ui.blank();
1900
+ ui.header(` Deploying ${framework} ${kind}...`);
1901
+ const addonSource = isExtension
1902
+ ? extensionPath(frameworkRoot, framework)
1903
+ : addonPath(frameworkRoot, framework);
1904
+ const addonResult = await runner.run('tools/agents/deploy-agents.mjs', [
1905
+ '--quiet', '--source', addonSource,
1906
+ ...addonBaseArgs,
1907
+ ], { capture: true });
1908
+ if (addonResult.exitCode !== 0) {
1909
+ return addonResult;
1910
+ }
1911
+ // Register deployed extensions
1912
+ try {
1913
+ const registry = getRegistry();
1914
+ const paths = getProviderPaths(provider);
1915
+ await registerDeployedExtensions(registry, {
1916
+ agentsPath: paths.agents,
1917
+ skillsPath: paths.skills,
1918
+ commandsPath: paths.commands,
1919
+ rulesPath: paths.rules,
1920
+ behaviorsPath: paths.behaviors,
1921
+ provider,
1922
+ cwd: target,
1923
+ });
1924
+ ui.success('Extension registration complete');
1925
+ }
1926
+ catch (error) {
1927
+ ui.warn(`Failed to register extensions: ${error instanceof Error ? error.message : String(error)}`);
1928
+ }
1929
+ // Register CLI commands if addon declares them
1930
+ try {
1931
+ const manifestPath = path.join(addonSource, 'manifest.json');
1932
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
1933
+ const manifest = JSON.parse(manifestContent);
1934
+ if (manifest.cli_commands?.namespace && manifest.cli_commands?.subcommands) {
1935
+ const cmds = manifest.cli_commands;
1936
+ const commandsSource = path.join(addonSource, cmds.entry || 'commands/');
1937
+ await registerCliCommands(target, cmds.namespace, cmds.description || `${framework} addon commands`, commandsSource, cmds.subcommands);
1938
+ ui.success(`CLI namespace '${cmds.namespace}' registered (${Object.keys(cmds.subcommands).length} subcommands)`);
1939
+ // Register Claude Code hooks for subcommands with hook_event
1940
+ if (provider === 'claude') {
1941
+ const registeredHooks = await registerHooks(target, cmds.namespace, cmds.subcommands);
1942
+ for (const hook of registeredHooks) {
1943
+ ui.success(`Hook registered: ${hook}`);
1944
+ }
1945
+ }
1946
+ }
1947
+ }
1948
+ catch (error) {
1949
+ ui.warn(`Failed to register CLI commands: ${error instanceof Error ? error.message : String(error)}`);
1950
+ }
1951
+ // Profile picker for addons with memory topology and multiple templates
1952
+ try {
1953
+ const profileManifestPath = path.join(addonSource, 'manifest.json');
1954
+ const profileManifestContent = await fs.readFile(profileManifestPath, 'utf-8');
1955
+ const profileManifest = JSON.parse(profileManifestContent);
1956
+ const topology = profileManifest.memory?.topology;
1957
+ const templates = profileManifest.templates;
1958
+ if (topology && templates && templates.length > 1) {
1959
+ const profileIdx = remainingArgs.findIndex(a => a === '--profile');
1960
+ let selectedProfile;
1961
+ if (profileIdx >= 0 && remainingArgs[profileIdx + 1]) {
1962
+ // Explicit --profile flag
1963
+ selectedProfile = remainingArgs[profileIdx + 1];
1964
+ const templateFile = templates.find((t) => t.replace('.md', '') === selectedProfile || t === selectedProfile);
1965
+ if (!templateFile) {
1966
+ ui.warn(`Unknown profile "${selectedProfile}". Available: ${templates.map((t) => t.replace('.md', '')).join(', ')}`);
1967
+ selectedProfile = undefined;
1968
+ }
1969
+ }
1970
+ else if (_isBulkIntent || !process.stdin.isTTY) {
1971
+ // Bulk/automation or non-TTY: silently pick 'generic' default.
1972
+ // The profile picker is annoying during `aiwg use all`.
1973
+ selectedProfile = 'generic';
1974
+ }
1975
+ else if (process.stdin.isTTY) {
1976
+ // Interactive profile selection via the shared `listSelect` helper
1977
+ // (POC for spike #926). One call renders the option list, handles
1978
+ // number-or-name matching, threads `ctx.signal` for Ctrl-C
1979
+ // cancellation, and resolves to the fallback on timeout or empty
1980
+ // input. The hand-rolled parse-and-branch that used to live here
1981
+ // is now a one-liner.
1982
+ const { createPromptInterface, listSelect } = await import('../prompt-utils.js');
1983
+ ui.blank();
1984
+ ui.header(' Select a topology profile:');
1985
+ const templateNames = templates.map((t) => t.replace('.md', ''));
1986
+ const options = templateNames.map((name) => ({
1987
+ label: name === 'generic' ? `${name} (default)` : name,
1988
+ value: name,
1989
+ }));
1990
+ const rl = createPromptInterface();
1991
+ try {
1992
+ selectedProfile = await listSelect(rl, ' Enter number or name [generic]: ', options, 'generic', ctx.signal);
1993
+ }
1994
+ finally {
1995
+ rl.close();
1996
+ }
1997
+ }
1998
+ // Write profile config to project namespace
1999
+ if (selectedProfile) {
2000
+ const namespace = topology.namespace || `.aiwg/${framework}`;
2001
+ const configDir = path.join(target, namespace);
2002
+ await fs.mkdir(configDir, { recursive: true });
2003
+ const profileConfig = {
2004
+ profile: selectedProfile,
2005
+ pageTemplate: `templates/${selectedProfile}.md`,
2006
+ selectedAt: new Date().toISOString(),
2007
+ };
2008
+ await fs.writeFile(path.join(configDir, 'config.json'), JSON.stringify(profileConfig, null, 2) + '\n');
2009
+ ui.success(`Profile "${selectedProfile}" selected → ${namespace}/config.json`);
2010
+ }
2011
+ }
2012
+ }
2013
+ catch {
2014
+ // Profile selection is optional — don't fail deployment
2015
+ }
2016
+ if (framework === 'aiwg-utils' && !remainingArgs.includes('--dry-run')) {
2017
+ const wrapperValidation = await validateDeployedModelWrappers({
2018
+ provider: normalizeProviderDefinitionId(provider) ?? provider,
2019
+ target,
2020
+ frameworkRoot,
2021
+ modelDeployArgs,
2022
+ filtered: remainingArgs.includes('--filter') || remainingArgs.includes('--filter-role'),
2023
+ verbose: remainingArgs.includes('--verbose') || remainingArgs.includes('-v'),
2024
+ });
2025
+ if (wrapperValidation)
2026
+ return wrapperValidation;
2027
+ }
2028
+ ui.blank();
2029
+ ui.success(`${framework} addon deployed`);
2030
+ return {
2031
+ exitCode: 0,
2032
+ };
2033
+ }
2034
+ // Map framework name to deploy mode
2035
+ const mode = MODE_MAP[framework];
2036
+ const deployArgs = ['--mode', mode, '--deploy-commands', '--deploy-skills', '--deploy-rules', ...remainingArgs];
2037
+ // Check flags
2038
+ const skipUtils = remainingArgs.includes('--no-utils');
2039
+ const skipProjectLocal = remainingArgs.includes('--no-project-local');
2040
+ const verbose = remainingArgs.includes('--verbose') || remainingArgs.includes('-v');
2041
+ const dryRun = remainingArgs.includes('--dry-run');
2042
+ const ciHooksEnabled = remainingArgs.includes('--ci-hooks-enabled');
2043
+ const force = remainingArgs.includes('--force');
2044
+ const skipConflicts = remainingArgs.includes('--skip-conflicts');
2045
+ const explicitHarnessAgentSelectors = parseOpenHumanHarnessAgentSelector(remainingArgs);
2046
+ // PUW-027 (#1128): --scope user|project per ADR-4. Default project.
2047
+ // #1156 Phase 1: --user is a shorthand for --scope user.
2048
+ let scope;
2049
+ try {
2050
+ scope = detectScope(remainingArgs);
2051
+ if (scope === 'project' && remainingArgs.includes('--user')) {
2052
+ scope = 'user';
2053
+ }
2054
+ }
2055
+ catch (err) {
2056
+ return {
2057
+ exitCode: 1,
2058
+ message: `Error: ${err instanceof Error ? err.message : String(err)}`,
2059
+ };
2060
+ }
2061
+ if (scope === 'user' && verbose) {
2062
+ ui.dim(` --scope user: deploy targets mirror to home-rooted paths per ADR-4 §2`);
2063
+ }
2064
+ const filteredArgs = deployArgs.filter(a => a !== '--no-utils' && a !== '--no-project-local' && a !== '--ci-hooks-enabled' && a !== '--force' && a !== '--skip-conflicts' && a !== '--no-harness-agents');
2065
+ const deployFilteredArgs = removeFlagWithOptionalValue(filteredArgs, '--harness-agents');
2066
+ // Pass --quiet to suppress deploy-agents.mjs header/footer in default mode (#460)
2067
+ // Dry-run must not capture output — its purpose is to show what would happen
2068
+ if (!verbose && !dryRun)
2069
+ deployFilteredArgs.push('--quiet');
2070
+ // Extract provider and target from remainingArgs to pass to addon deployments
2071
+ // Config-first resolution (#621): explicit --provider overrides config, config overrides default 'claude'
2072
+ const providerIdx = remainingArgs.findIndex(a => a === '--provider' || a === '--platform');
2073
+ const explicitProvider = providerIdx >= 0 && remainingArgs[providerIdx + 1] ? remainingArgs[providerIdx + 1] : null;
2074
+ // Determine providers list for multi-provider deployment
2075
+ let providers;
2076
+ if (explicitProvider) {
2077
+ providers = [explicitProvider];
2078
+ }
2079
+ else if (config && config.providers.length > 0) {
2080
+ providers = config.providers;
2081
+ }
2082
+ else {
2083
+ providers = ['claude'];
2084
+ if (!config) {
2085
+ ui.warn("No .aiwg/aiwg.config found. Run 'aiwg init', then ask your AIWG agent to configure this project properly.");
2086
+ }
2087
+ }
2088
+ // Multi-provider: loop over providers, deploying to each in sequence
2089
+ if (providers.length > 1) {
2090
+ for (const p of providers) {
2091
+ const result = await this.execute({ ...ctx, args: [framework, '--provider', p, ...remainingArgs] });
2092
+ if (result.exitCode !== 0)
2093
+ return result;
2094
+ }
2095
+ return { exitCode: 0 };
2096
+ }
2097
+ const requestedProvider = providers[0];
2098
+ const projectLocalProviderResolution = await resolveProjectLocalProviderAdapter(projectDir, requestedProvider);
2099
+ const builtInProviderResolution = projectLocalProviderResolution.requestedProvider
2100
+ ? { provider: projectLocalProviderResolution.provider, requestedProvider: projectLocalProviderResolution.requestedProvider }
2101
+ : resolveBuiltInProviderForUse(projectLocalProviderResolution.provider);
2102
+ const unsupportedMessage = projectLocalProviderResolution.requestedProvider
2103
+ ? null
2104
+ : unsupportedProviderMessage(requestedProvider);
2105
+ if (unsupportedMessage) {
2106
+ return { exitCode: 1, message: unsupportedMessage };
2107
+ }
2108
+ const provider = builtInProviderResolution.provider;
2109
+ const providerDeployArgs = builtInProviderResolution.requestedProvider
2110
+ ? withProviderOverride(deployFilteredArgs, provider)
2111
+ : deployFilteredArgs;
2112
+ const targetIdx = remainingArgs.findIndex(a => a === '--target');
2113
+ const target = targetIdx >= 0 && remainingArgs[targetIdx + 1] ? remainingArgs[targetIdx + 1] : process.cwd();
2114
+ if ((verbose || dryRun) && projectLocalProviderResolution.requestedProvider) {
2115
+ ui.dim(` project-local provider '${projectLocalProviderResolution.requestedProvider}' extends '${provider}'`);
2116
+ }
2117
+ else if ((verbose || dryRun) && builtInProviderResolution.requestedProvider) {
2118
+ ui.dim(` provider alias '${builtInProviderResolution.requestedProvider}' resolves to '${provider}'`);
2119
+ }
2120
+ if (explicitHarnessAgentSelectors.length > 0 && provider !== 'openhuman') {
2121
+ return {
2122
+ exitCode: 1,
2123
+ message: '--harness-agents is only supported with --provider openhuman',
2124
+ };
2125
+ }
2126
+ // #1526 / OpenHuman source alignment — OpenClaw and OpenHuman are
2127
+ // user-global app installs. An unflagged deploy should work; explicit
2128
+ // `--scope project` is rejected below.
2129
+ if ((provider === 'openclaw' || provider === 'openhuman') && scope === 'project' && !remainingArgs.includes('--scope')) {
2130
+ scope = 'user';
2131
+ }
2132
+ // #1156 Phase 1 — home-dir providers reject explicit --scope project.
2133
+ try {
2134
+ rejectOpenClawProjectScope(provider, scope);
2135
+ }
2136
+ catch (err) {
2137
+ return {
2138
+ exitCode: 1,
2139
+ message: err instanceof Error ? err.message : String(err),
2140
+ };
2141
+ }
2142
+ // #1156 Phase 1 — Reject --scope user for providers that don't have a
2143
+ // documented user-scope path map. Operators get a clear error rather than a
2144
+ // silent fall-through to project-only deployment.
2145
+ if (scope === 'user') {
2146
+ const { USER_SCOPE_PATHS } = await import('../scope-resolver.js');
2147
+ if (!USER_SCOPE_PATHS[provider]) {
2148
+ return {
2149
+ exitCode: 1,
2150
+ message: `--scope user not supported for provider '${provider}' — see docs/customization/user-scope-deployment.md for the supported list`,
2151
+ };
2152
+ }
2153
+ }
2154
+ // Pre-deployment collision check (skip in dry-run — nothing is written)
2155
+ if (!dryRun) {
2156
+ const canDeploy = await runPreDeployCollisionCheck({
2157
+ frameworkRoot,
2158
+ framework,
2159
+ target,
2160
+ provider,
2161
+ force,
2162
+ skipConflicts,
2163
+ verbose,
2164
+ });
2165
+ if (!canDeploy) {
2166
+ return { exitCode: 1, message: 'Deployment blocked due to name collisions. See above for details.' };
2167
+ }
2168
+ }
2169
+ // Deploy main framework
2170
+ const quiet = !verbose && !dryRun;
2171
+ const captureOpts = quiet ? { capture: true } : {};
2172
+ if (quiet) {
2173
+ const installLabel = framework === 'all'
2174
+ ? 'Installing complete AIWG surface'
2175
+ : `Installing ${framework} framework`;
2176
+ ui.blank();
2177
+ console.log(` ${ui.brandMark()} ${ui.bold(installLabel)} ${ui.dimText(`for ${provider === 'claude' ? 'Claude Code' : provider}`)}`);
2178
+ ui.blank();
2179
+ }
2180
+ const runner = createScriptRunner(ctx.frameworkRoot);
2181
+ const mainResult = await runner.run('tools/agents/deploy-agents.mjs', providerDeployArgs, captureOpts);
2182
+ if (mainResult.exitCode !== 0) {
2183
+ return mainResult;
2184
+ }
2185
+ const harnessAgentSelectors = provider === 'openhuman'
2186
+ ? resolveOpenHumanHarnessAgentSelectors(remainingArgs)
2187
+ : [];
2188
+ if (harnessAgentSelectors.length > 0) {
2189
+ try {
2190
+ const harness = await deployOpenHumanHarnessAgents({
2191
+ frameworkRoot,
2192
+ target,
2193
+ selectors: harnessAgentSelectors,
2194
+ scope: 'user',
2195
+ dryRun,
2196
+ });
2197
+ if (verbose || !quiet) {
2198
+ ui.dim(` OpenHuman native harness agents: ${harness.emitted}`);
2199
+ }
2200
+ }
2201
+ catch (error) {
2202
+ return {
2203
+ exitCode: 1,
2204
+ message: `OpenHuman harness agent deployment failed: ${error instanceof Error ? error.message : String(error)}`,
2205
+ };
2206
+ }
2207
+ }
2208
+ // Build common args for addon deployments (inherit provider and target)
2209
+ const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
2210
+ addonBaseArgs.push(...modelDeployArgs);
2211
+ if (provider)
2212
+ addonBaseArgs.push('--provider', provider);
2213
+ if (target)
2214
+ addonBaseArgs.push('--target', target);
2215
+ if (verbose)
2216
+ addonBaseArgs.push('--verbose');
2217
+ // Forward --copy-all to addon deploys so the legacy mirror behavior
2218
+ // is consistent across the framework + every addon (#1219).
2219
+ if (remainingArgs.includes('--copy-all') || remainingArgs.includes('--copy-standard-skills')) {
2220
+ addonBaseArgs.push('--copy-all');
2221
+ }
2222
+ // Deploy all addons (excluding disallow list) unless --no-utils
2223
+ if (!skipUtils) {
2224
+ const allAddons = await getAllAddons(frameworkRoot);
2225
+ for (const addon of allAddons) {
2226
+ if (verbose) {
2227
+ console.log('');
2228
+ console.log(`Deploying ${addon} addon...`);
2229
+ }
2230
+ const source = addonPath(frameworkRoot, addon);
2231
+ const addonArgs = quiet
2232
+ ? ['--quiet', '--source', source, ...addonBaseArgs]
2233
+ : ['--source', source, ...addonBaseArgs];
2234
+ const result = await runner.run('tools/agents/deploy-agents.mjs', addonArgs, captureOpts);
2235
+ if (result.exitCode !== 0) {
2236
+ return result;
2237
+ }
2238
+ }
2239
+ // Deploy all extensions from agentic/code/extensions/* (#1222).
2240
+ // Extensions are addon-shaped bundles (manifest type: "addon") that live
2241
+ // in a separate top-level dir to keep ops/sysops/itops/devops grouped.
2242
+ // `aiwg use all` was previously silent about them, leaving 6 extension
2243
+ // bundles undeployed even when the user explicitly asked for everything.
2244
+ const allExtensions = await getAllExtensions(frameworkRoot);
2245
+ for (const ext of allExtensions) {
2246
+ if (verbose) {
2247
+ console.log('');
2248
+ console.log(`Deploying ${ext} extension...`);
2249
+ }
2250
+ const source = extensionPath(frameworkRoot, ext);
2251
+ const extArgs = quiet
2252
+ ? ['--quiet', '--source', source, ...addonBaseArgs]
2253
+ : ['--source', source, ...addonBaseArgs];
2254
+ const result = await runner.run('tools/agents/deploy-agents.mjs', extArgs, captureOpts);
2255
+ if (result.exitCode !== 0) {
2256
+ return result;
2257
+ }
2258
+ }
2259
+ }
2260
+ // Deploy project-local bundles (#1035). Auto-runs after upstream addons unless
2261
+ // --no-project-local. Idempotent — overwrites prior deploys. Skipped under
2262
+ // --dry-run-disabled scenarios for safety; --dry-run is honored and logged.
2263
+ if (!skipProjectLocal) {
2264
+ const plResult = await deployProjectLocalBundles({
2265
+ ctx,
2266
+ frameworkRoot,
2267
+ projectDir,
2268
+ provider,
2269
+ target,
2270
+ dryRun,
2271
+ verbose,
2272
+ quiet,
2273
+ modelArgs: modelDeployArgs,
2274
+ });
2275
+ if (plResult.deployed > 0 && quiet) {
2276
+ ui.dim(` + ${plResult.deployed} project-local bundle(s)`);
2277
+ }
2278
+ if (plResult.failed > 0) {
2279
+ ui.warn(`${plResult.failed} project-local bundle(s) failed to deploy`);
2280
+ }
2281
+ }
2282
+ const paths = getProviderPaths(provider);
2283
+ if (!dryRun && !skipUtils) {
2284
+ const wrapperValidation = await validateDeployedModelWrappers({
2285
+ provider,
2286
+ target,
2287
+ frameworkRoot,
2288
+ modelDeployArgs,
2289
+ filtered: remainingArgs.includes('--filter') || remainingArgs.includes('--filter-role'),
2290
+ verbose,
2291
+ });
2292
+ if (wrapperValidation)
2293
+ return wrapperValidation;
2294
+ }
2295
+ const targetSkillsDir = resolveProviderPath(target, paths.skills);
2296
+ const targetCommandsDir = paths.commands ? resolveProviderPath(target, paths.commands) : '';
2297
+ const kernelSkillsPath = getProviderKernelSkillsPath(provider);
2298
+ const targetKernelSkillsDir = kernelSkillsPath ? resolveProviderPath(target, kernelSkillsPath) : '';
2299
+ // Translate deployed skills to commands for providers that require legacy command format.
2300
+ // (#550) Skills are canonical; commands are generated deployment artifacts.
2301
+ if (providerNeedsCommands(provider) && targetCommandsDir) {
2302
+ try {
2303
+ const translationResult = await translateSkillsToCommands(targetSkillsDir, {
2304
+ provider,
2305
+ targetDir: targetCommandsDir,
2306
+ projectPath: target,
2307
+ dryRun,
2308
+ verbose,
2309
+ });
2310
+ if (verbose && translationResult.translated.length > 0) {
2311
+ ui.success(`Translated ${translationResult.translated.length} skills → commands (${provider})`);
2312
+ }
2313
+ }
2314
+ catch (error) {
2315
+ ui.warn(`Skill→command translation failed: ${error instanceof Error ? error.message : String(error)}`);
2316
+ }
2317
+ }
2318
+ // Mirror deterministic operator workflows to each provider's native
2319
+ // command/prompt surface when one exists. This applies even when a
2320
+ // provider loads skills natively: users still expect setup, update,
2321
+ // status, intake, and flow workflows to show up in the provider's `/`
2322
+ // command picker where supported.
2323
+ if (targetCommandsDir) {
2324
+ try {
2325
+ const standardMirrored = await mirrorStandardCommandSkills({
2326
+ provider,
2327
+ target,
2328
+ targetCommandsDir,
2329
+ targetSkillsDir,
2330
+ frameworkRoot,
2331
+ dryRun,
2332
+ verbose,
2333
+ });
2334
+ if (verbose && standardMirrored > 0) {
2335
+ ui.success(`Mirrored ${standardMirrored} operator skills → commands (${provider})`);
2336
+ }
2337
+ // Mirror the kernel self-maintenance set (aiwg-regenerate, -doctor,
2338
+ // -refresh, -status, -help, -issue, -pr, -mission, use, steward) to
2339
+ // the provider's command surface so these bootstrap entry points are
2340
+ // *copied in* for direct `/`-access — not discovery-only. This matches
2341
+ // the standard operator mirror above (which already deploys /intake-*
2342
+ // and /flow-* on skills-native providers like Claude/Cursor), and makes
2343
+ // the wrapper's own callout true ("…directly invokable as slash
2344
+ // commands"). `aiwg discover` remains the backstop for the long tail.
2345
+ //
2346
+ // Supersedes the #1382 gate (`&& providerNeedsCommands(provider)`),
2347
+ // which suppressed these on Claude/Cursor to avoid a skill+command
2348
+ // duplicate `/` entry. The direct bootstrap entry point is worth that
2349
+ // redundancy — the same skill+command coexistence already shipped for
2350
+ // the standard operator set. Gated on `targetCommandsDir` only, so
2351
+ // providers without a command dir (Hermes/OpenHuman) still no-op.
2352
+ if (targetKernelSkillsDir) {
2353
+ const kernel = await translateSkillsToCommands(targetKernelSkillsDir, {
2354
+ provider,
2355
+ targetDir: targetCommandsDir,
2356
+ projectPath: target,
2357
+ dryRun,
2358
+ verbose,
2359
+ nameFilter: shouldMirrorKernelCommandSkill,
2360
+ });
2361
+ if (verbose && kernel.translated.length > 0) {
2362
+ ui.success(`Mirrored ${kernel.translated.length} kernel skills → commands (${provider})`);
2363
+ }
2364
+ }
2365
+ }
2366
+ catch (error) {
2367
+ ui.warn(`Skill→command mirror failed: ${error instanceof Error ? error.message : String(error)}`);
2368
+ }
2369
+ }
2370
+ // Register deployed extensions in the registry
2371
+ if (verbose) {
2372
+ console.log('');
2373
+ console.log('Registering deployed extensions...');
2374
+ }
2375
+ try {
2376
+ const registry = getRegistry();
2377
+ const paths = getProviderPaths(provider);
2378
+ await registerDeployedExtensions(registry, {
2379
+ agentsPath: paths.agents,
2380
+ skillsPath: paths.skills,
2381
+ commandsPath: paths.commands,
2382
+ rulesPath: paths.rules,
2383
+ behaviorsPath: paths.behaviors,
2384
+ provider,
2385
+ cwd: target,
2386
+ });
2387
+ if (verbose)
2388
+ console.log('Extension registration complete');
2389
+ }
2390
+ catch (error) {
2391
+ console.error('Warning: Failed to register extensions:', error instanceof Error ? error.message : String(error));
2392
+ // Don't fail the deployment if registration fails
2393
+ }
2394
+ // Rebuild the `framework` artifact index (#1212/#1214) so
2395
+ // `aiwg discover` queries return fresh capability data. This step
2396
+ // can take a few seconds on a full install (~2,000 artifacts) —
2397
+ // surface the work to the operator so the apparent stall during
2398
+ // `aiwg use` is legible. Best-effort — index rebuild failure must
2399
+ // not fail the deploy.
2400
+ //
2401
+ // Pre-flight: skip when the framework source dirs aren't present
2402
+ // (e.g., test fixtures, deploy from npm install rather than the
2403
+ // source repo). buildIndex() calls `process.exit(1)` on missing
2404
+ // scan dirs which would short-circuit our catch.
2405
+ let discoverableSkillCount = null;
2406
+ if (!dryRun) {
2407
+ // Build the framework graph against $AIWG_ROOT, not the project's
2408
+ // target dir (#1217). The framework source is user-global at
2409
+ // AIWG_ROOT — recording AIWG_ROOT-relative paths makes the index
2410
+ // resolvable from any project. Falls back to project target only
2411
+ // if AIWG_ROOT is unset or unreadable (rare).
2412
+ const aiwgRootForIndex = process.env.AIWG_ROOT || frameworkRoot || target;
2413
+ const fwSrcDir = path.join(aiwgRootForIndex, 'agentic', 'code', 'frameworks');
2414
+ const hasFrameworkSrc = await fs.access(fwSrcDir).then(() => true).catch(() => false);
2415
+ if (hasFrameworkSrc) {
2416
+ // Always announce the index build — this is the visible-to-user
2417
+ // expensive step on a full install (~2,000 artifacts indexed).
2418
+ // Without messaging, the operator sees an apparent stall after
2419
+ // the deploy summary. Verbose mode lets buildIndex's own
2420
+ // progress through; otherwise we show a single-line spinner-
2421
+ // style message and capture the noisy stat lines.
2422
+ ui.blank();
2423
+ ui.info('Building capability index…');
2424
+ ui.dim(' Indexing operational assets for agent-side lookup.');
2425
+ const indexStart = Date.now();
2426
+ // Capture buildIndex's own console.log noise unless verbose
2427
+ const origLog = console.log;
2428
+ if (!verbose)
2429
+ console.log = () => { };
2430
+ try {
2431
+ const { buildIndex } = await import('../../artifacts/index-builder.js');
2432
+ // Build against AIWG_ROOT so stored paths resolve from any
2433
+ // project (#1217). The output index location is XDG-shared
2434
+ // regardless of build cwd.
2435
+ await buildIndex(aiwgRootForIndex, { graph: 'framework', explicit: false });
2436
+ console.log = origLog;
2437
+ discoverableSkillCount = await countDiscoverableSkills(aiwgRootForIndex);
2438
+ const indexElapsedSec = ((Date.now() - indexStart) / 1000).toFixed(1);
2439
+ ui.success(`Capability index ready (${indexElapsedSec}s) — agents can search the installed capability set.`);
2440
+ }
2441
+ catch (error) {
2442
+ console.log = origLog;
2443
+ ui.warn(`Capability index build failed: ${error instanceof Error ? error.message : String(error)}`);
2444
+ ui.dim(' Deploy succeeded — skills are reachable, but agent-side capability search may be stale until the next rebuild.');
2445
+ }
2446
+ }
2447
+ else if (verbose) {
2448
+ console.log('Framework source not found; skipping capability index rebuild');
2449
+ }
2450
+ }
2451
+ // Show completion summary and next steps (default mode only)
2452
+ let counts = { agents: 0, commands: 0, skills: 0, rules: 0, behaviors: 0 };
2453
+ if (quiet) {
2454
+ // Count deployed artifacts
2455
+ const paths = getProviderPaths(provider);
2456
+ counts = await countDeployedArtifacts(target, paths);
2457
+ if (counts.agents > 0)
2458
+ ui.deployCount('Agents', counts.agents);
2459
+ if (counts.commands > 0)
2460
+ ui.deployCount('Commands', counts.commands);
2461
+ if (counts.skills > 0)
2462
+ ui.deployCount('Skills', counts.skills);
2463
+ if (discoverableSkillCount !== null)
2464
+ ui.deployCount('Discoverable skills', discoverableSkillCount);
2465
+ if (counts.rules > 0)
2466
+ ui.deployCount('Rules', counts.rules);
2467
+ if (counts.behaviors > 0)
2468
+ ui.deployCount('Behaviors', counts.behaviors);
2469
+ ui.blank();
2470
+ printNextSteps(framework, provider);
2471
+ // #1240: warn the operator that the running session can't see the newly
2472
+ // deployed agents until reloaded. Skipping this notice is what produced
2473
+ // the "Agent type 'software-implementer' not found" symptom on a stale
2474
+ // Claude Code session.
2475
+ ui.blank();
2476
+ printSessionReloadNotice(provider);
2477
+ // Append version confirmation line (#719)
2478
+ try {
2479
+ const versionInfo = await getVersionInfo();
2480
+ ui.blank();
2481
+ const repoStamp = versionInfo.repoUrl || 'aiwg.io';
2482
+ ui.dim(` AIWG v${versionInfo.version} — ${repoStamp}`);
2483
+ }
2484
+ catch {
2485
+ // Graceful fallback: omit version line if versionInfo unavailable
2486
+ }
2487
+ }
2488
+ // Deploy CI workflow files when --ci-hooks-enabled is set (#661)
2489
+ if (ciHooksEnabled) {
2490
+ await deployCiHooks({ frameworkRoot, framework, target, dryRun });
2491
+ }
2492
+ // PUW-027 (#1128), #1156 Phase 1 — --scope user: mirror the full
2493
+ // per-provider artifact set (agents/commands/skills/rules) to the
2494
+ // user-scope target per ADR-4 §2. The project-scope deploy stays in
2495
+ // place; user-scope copies are additive so the framework is available
2496
+ // across every project on the operator's machine. After a successful
2497
+ // mirror, record the deploy in the per-user registry at
2498
+ // ~/.aiwg/installed.json so `aiwg list --scope user` and `aiwg remove
2499
+ // --scope user` can find it from any cwd.
2500
+ if (scope === 'user' && provider !== 'openhuman' && !dryRun) {
2501
+ try {
2502
+ const paths = getProviderPaths(provider);
2503
+ const resolveProjectPath = (p) => !p ? '' : path.isAbsolute(p) ? p : path.join(target, p);
2504
+ const projectPaths = {
2505
+ agents: resolveProjectPath(paths.agents),
2506
+ skills: resolveProjectPath(paths.skills),
2507
+ kernelSkills: resolveProjectPath(getProviderKernelSkillsPath(provider)),
2508
+ commands: resolveProjectPath(paths.commands),
2509
+ rules: resolveProjectPath(paths.rules),
2510
+ behaviors: resolveProjectPath(paths.behaviors),
2511
+ };
2512
+ const r = await mirrorToUserScope(provider, projectPaths);
2513
+ const summary = [];
2514
+ if (r.agents.count > 0)
2515
+ summary.push(`${r.agents.count} agent(s)`);
2516
+ if (r.commands.count > 0)
2517
+ summary.push(`${r.commands.count} command(s)`);
2518
+ if (r.skills.count > 0)
2519
+ summary.push(`${r.skills.count} skill(s)`);
2520
+ if (r.rules.count > 0)
2521
+ summary.push(`${r.rules.count} rule(s)`);
2522
+ if (r.behaviors.count > 0)
2523
+ summary.push(`${r.behaviors.count} behavior(s)`);
2524
+ if (summary.length > 0) {
2525
+ // Show the per-type breakdown plus the primary user-scope target dir.
2526
+ // Prefer skills.targetDir as the surfaced location since most providers
2527
+ // share `~/.<provider>/` for the others.
2528
+ const headline = r.skills.targetDir || r.agents.targetDir || r.commands.targetDir || r.rules.targetDir;
2529
+ ui.dim(` --scope user: mirrored ${summary.join(', ')} to ${headline}`);
2530
+ // Record the deploy in the per-user registry. Counts come from the
2531
+ // mirror result so they reflect what actually landed at user scope,
2532
+ // not what was deployed at project scope (the two can diverge if
2533
+ // some artifact dirs were empty in the project tree). Entry names
2534
+ // are recorded so `aiwg remove --scope user` can revert precisely
2535
+ // (delete only this deploy's artifacts, not other frameworks').
2536
+ try {
2537
+ const { recordUserDeploy } = await import('../../config/user-registry.js');
2538
+ const versionInfo = await getVersionInfo().catch(() => ({ version: 'unknown' }));
2539
+ await recordUserDeploy({
2540
+ framework,
2541
+ provider,
2542
+ version: versionInfo.version,
2543
+ source: 'bundled',
2544
+ counts: {
2545
+ agents: r.agents.count,
2546
+ commands: r.commands.count,
2547
+ skills: r.skills.count,
2548
+ rules: r.rules.count,
2549
+ },
2550
+ entries: {
2551
+ agents: r.agents.entries,
2552
+ commands: r.commands.entries,
2553
+ skills: r.skills.entries,
2554
+ rules: r.rules.entries,
2555
+ behaviors: r.behaviors.entries,
2556
+ },
2557
+ });
2558
+ }
2559
+ catch (registryErr) {
2560
+ ui.warn(`user-scope registry update failed: ${registryErr instanceof Error ? registryErr.message : String(registryErr)}`);
2561
+ }
2562
+ }
2563
+ }
2564
+ catch (err) {
2565
+ ui.warn(`--scope user mirror failed: ${err instanceof Error ? err.message : String(err)}`);
2566
+ }
2567
+ }
2568
+ // PUW-010 (#1111) Claude Code aiwg-hooks autoInstall — wire the
2569
+ // addon's JS handler scripts into .claude/settings.json with backup-
2570
+ // and-rollback per ADR-3 §5. Default ON for Claude per ADR-3 §7;
2571
+ // operator opts out via --no-hooks.
2572
+ if (provider === 'claude' && !dryRun && !remainingArgs.includes('--no-hooks')) {
2573
+ try {
2574
+ const r = await installAiwgHooks({
2575
+ projectPath: target,
2576
+ frameworkRoot,
2577
+ dryRun,
2578
+ verbose,
2579
+ });
2580
+ if (r) {
2581
+ if (verbose && r.installedScripts.length > 0) {
2582
+ ui.dim(` aiwg-hooks: installed ${r.installedScripts.length} hook scripts to .claude/hooks/`);
2583
+ }
2584
+ if (verbose && r.registeredEvents.length > 0) {
2585
+ for (const event of r.registeredEvents) {
2586
+ ui.dim(` aiwg-hooks registered: ${event}`);
2587
+ }
2588
+ }
2589
+ if (r.backupPath) {
2590
+ ui.dim(` aiwg-hooks: backed up settings.json to ${r.backupPath}`);
2591
+ }
2592
+ for (const w of r.warnings) {
2593
+ ui.dim(` aiwg-hooks: ${w}`);
2594
+ }
2595
+ }
2596
+ }
2597
+ catch (err) {
2598
+ ui.warn(`aiwg-hooks installer: ${err instanceof Error ? err.message : String(err)}`);
2599
+ }
2600
+ }
2601
+ // PUW-018 (#1119) cross-provider hook bridge — opt-in via
2602
+ // --enable-cross-provider-hooks. When enabled and at least one canonical
2603
+ // hook source exists at agentic/code/addons/aiwg-hooks/canonical/*.yaml,
2604
+ // translate to provider-native artifacts (Codex TOML, Copilot JSON,
2605
+ // Factory shell, Hermes Python plugin). Per ADR-3 §7 autoInstall policy
2606
+ // this is opt-in.
2607
+ if (remainingArgs.includes('--enable-cross-provider-hooks') && !dryRun) {
2608
+ try {
2609
+ const { loadHookSources, bridgeAll } = await import('../../smiths/hook-bridge/index.js');
2610
+ const { sources, errors } = await loadHookSources(frameworkRoot);
2611
+ if (errors.length > 0) {
2612
+ for (const err of errors) {
2613
+ ui.warn(`hook-bridge load: ${err}`);
2614
+ }
2615
+ }
2616
+ if (sources.length === 0) {
2617
+ if (verbose) {
2618
+ ui.dim(' hook-bridge: no canonical hook sources found at agentic/code/addons/aiwg-hooks/canonical/ — flag is a no-op');
2619
+ }
2620
+ }
2621
+ else {
2622
+ // Cross-provider providers per ADR-3 §7 (no-op if their dir not present)
2623
+ const bridgeProviders = ['codex', 'copilot', 'factory', 'hermes'];
2624
+ const results = await bridgeAll(sources, bridgeProviders, {
2625
+ projectPath: target,
2626
+ dryRun,
2627
+ verbose,
2628
+ });
2629
+ let emittedCount = 0;
2630
+ for (const r of results) {
2631
+ if (r.skipped) {
2632
+ if (verbose)
2633
+ ui.dim(` hook-bridge skipped ${r.provider}: ${r.skipReason}`);
2634
+ continue;
2635
+ }
2636
+ emittedCount += r.emittedPaths.length;
2637
+ for (const w of r.warnings)
2638
+ ui.dim(` hook-bridge ${r.provider}: ${w}`);
2639
+ }
2640
+ if (emittedCount > 0) {
2641
+ ui.dim(` hook-bridge: emitted ${emittedCount} cross-provider hook artifact(s)`);
2642
+ }
2643
+ }
2644
+ }
2645
+ catch (err) {
2646
+ ui.warn(`hook-bridge: ${err instanceof Error ? err.message : String(err)}`);
2647
+ }
2648
+ }
2649
+ // Update installed section in config (#621)
2650
+ if (config !== null && !dryRun) {
2651
+ try {
2652
+ const versionInfo = await getVersionInfo();
2653
+ const versionDirName = resolveFrameworkDir(framework);
2654
+ const frameworkManifestPath = versionDirName
2655
+ ? path.join(frameworkRoot, 'agentic/code/frameworks', versionDirName, 'manifest.json')
2656
+ : null;
2657
+ if (!frameworkManifestPath)
2658
+ throw new Error('no manifest for general/writing mode');
2659
+ const mHash = await hashManifest(frameworkManifestPath);
2660
+ const updatedConfig = updateInstalled(config, framework, provider, {
2661
+ agents: counts.agents,
2662
+ commands: counts.commands,
2663
+ skills: counts.skills,
2664
+ rules: counts.rules,
2665
+ }, { version: versionInfo.version, source: 'bundled', manifestHash: mHash });
2666
+ await writeAiwgConfig(projectDir, updatedConfig);
2667
+ }
2668
+ catch {
2669
+ // Non-fatal: config tracking failure must not block deployment
2670
+ }
2671
+ }
2672
+ // Context-pipeline emission (ADR-1 §0 + §0.5 + §7).
2673
+ //
2674
+ // For AGENTS.md providers (codex/copilot/cursor/windsurf/hermes/warp/factory/
2675
+ // opencode), emit WORKSPACE.md + AIWG.md + provider adapters as the last filesystem
2676
+ // step before activity-log close. The generator-runs-after-deploy invariant
2677
+ // (ADR-1 §7) means the link index can only cite files we observe on disk:
2678
+ // failed deploys produce shorter indexes, never broken links.
2679
+ //
2680
+ // Operators opt out via --no-context-files / --no-aiwg-md / --no-agents-md.
2681
+ if (!dryRun) {
2682
+ const skipContext = remainingArgs.includes('--no-context-files');
2683
+ const skipWorkspaceMd = skipContext || remainingArgs.includes('--no-workspace-md');
2684
+ const skipAiwgMd = skipContext || remainingArgs.includes('--no-aiwg-md');
2685
+ const skipAgentsMd = skipContext || remainingArgs.includes('--no-agents-md');
2686
+ const forceContext = remainingArgs.includes('--force-context-files');
2687
+ try {
2688
+ const paths = getProviderPaths(provider);
2689
+ const sections = await discoverDeployedArtifacts(target, {
2690
+ agents: paths.agents,
2691
+ rules: paths.rules,
2692
+ skills: paths.skills,
2693
+ behaviors: paths.behaviors,
2694
+ });
2695
+ const ctxResult = await generateContextFiles({
2696
+ provider: provider,
2697
+ projectPath: target,
2698
+ sections,
2699
+ detectExistingFiles: true,
2700
+ force: forceContext,
2701
+ skip: { workspaceMd: skipWorkspaceMd, aiwgMd: skipAiwgMd, agentsMd: skipAgentsMd },
2702
+ });
2703
+ if (verbose && ctxResult.workspaceMdPath) {
2704
+ ui.dim(` ${ctxResult.workspaceMdAction === 'created' ? 'Created' : 'Refreshed'} WORKSPACE.md`);
2705
+ }
2706
+ if (verbose && ctxResult.agentsMdPath) {
2707
+ ui.dim(` Wrote AGENTS.md (${ctxResult.agentsMdBytes} bytes)`);
2708
+ }
2709
+ if (verbose && ctxResult.aiwgMdPath) {
2710
+ ui.dim(` Wrote AIWG.md`);
2711
+ }
2712
+ if (verbose && ctxResult.normalizedAiwgMdPath) {
2713
+ ui.dim(` Wrote .aiwg/AIWG.md`);
2714
+ }
2715
+ if (verbose && ctxResult.claudeMdHookPath && ctxResult.claudeMdHookAction && ctxResult.claudeMdHookAction !== 'unchanged') {
2716
+ // #1437: CLAUDE.md hook for claude provider; mirrors AGENTS.md for other providers.
2717
+ const verb = ctxResult.claudeMdHookAction === 'created' ? 'Created' :
2718
+ ctxResult.claudeMdHookAction === 'inserted' ? 'Inserted hook into' :
2719
+ ctxResult.claudeMdHookAction === 'updated' ? 'Updated hook in' :
2720
+ 'Touched';
2721
+ ui.dim(` ${verb} CLAUDE.md (@WORKSPACE.md then @AIWG.md block)`);
2722
+ }
2723
+ for (const w of ctxResult.warnings) {
2724
+ // #1579: loud warnings (non-managed twin/bridge left untouched) are
2725
+ // prefixed `WARNING:` and must render prominently, not dimmed, so the
2726
+ // consequence + remediation isn't buried in deploy output.
2727
+ if (w.startsWith('WARNING:')) {
2728
+ ui.warn(`context-pipeline: ${w.slice('WARNING:'.length).trim()}`);
2729
+ }
2730
+ else {
2731
+ ui.dim(` context-pipeline: ${w}`);
2732
+ }
2733
+ }
2734
+ for (const b of ctxResult.backupPaths) {
2735
+ ui.dim(` Backup created: ${b}`);
2736
+ }
2737
+ // PUW-029 size validation hook (#1130). Hard error at 32KB matches
2738
+ // Codex's config_toml.rs:68 cap. Soft warning at 30KB.
2739
+ if (provider === 'codex' && ctxResult.agentsMdBytes > 0) {
2740
+ if (ctxResult.agentsMdBytes >= 32 * 1024) {
2741
+ ui.dim(` WARNING: AGENTS.md (${ctxResult.agentsMdBytes} bytes) exceeds Codex 32KB cap. Auto-split lands in PUW-029 implementation; manual split needed for now.`);
2742
+ }
2743
+ else if (ctxResult.agentsMdBytes >= 30 * 1024) {
2744
+ ui.dim(` Note: AGENTS.md (${ctxResult.agentsMdBytes} bytes) approaches Codex 32KB cap (warn threshold 30KB).`);
2745
+ }
2746
+ }
2747
+ }
2748
+ catch (err) {
2749
+ // Non-fatal: context-pipeline emission must not block deployment.
2750
+ // Operator can re-run aiwg use to retry.
2751
+ const msg = err instanceof Error ? err.message : String(err);
2752
+ console.error(`Warning: context-pipeline emission failed: ${msg}`);
2753
+ }
2754
+ }
2755
+ span.end('use:complete', { framework });
2756
+ return {
2757
+ exitCode: 0,
2758
+ message: verbose ? `Successfully deployed ${framework} framework` : '',
2759
+ };
2760
+ }
2761
+ }
2762
+ /**
2763
+ * Create use handler instance
2764
+ */
2765
+ export function createUseHandler() {
2766
+ return new UseHandler();
2767
+ }
2768
+ /**
2769
+ * Singleton handler instance
2770
+ */
2771
+ export const useHandler = new UseHandler();
2772
+ //# sourceMappingURL=use.js.map