@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.
- package/README.md +21 -1
- package/bin/aiwg.mjs +349 -0
- package/dist/src/a2a/agent-card.js +145 -0
- package/dist/src/a2a/client.js +411 -0
- package/dist/src/a2a/hitl-cli.js +171 -0
- package/dist/src/a2a/hitl-driver.js +370 -0
- package/dist/src/a2a/hitl.js +181 -0
- package/dist/src/a2a/http.js +193 -0
- package/dist/src/a2a/jcs.js +128 -0
- package/dist/src/a2a/jws.js +187 -0
- package/dist/src/a2a/types.js +24 -0
- package/dist/src/a2a/webhook.js +240 -0
- package/dist/src/activity-log/cli.js +348 -0
- package/dist/src/activity-log/parser.js +81 -0
- package/dist/src/activity-log/types.js +50 -0
- package/dist/src/agents/agent-deployer.js +367 -0
- package/dist/src/agents/agent-packager.js +271 -0
- package/dist/src/agents/agent-validator.js +271 -0
- package/dist/src/agents/grounding/knowledge-base.js +161 -0
- package/dist/src/agents/grounding/types.js +12 -0
- package/dist/src/agents/index.js +10 -0
- package/dist/src/agents/packaged-agent-inventory.js +75 -0
- package/dist/src/agents/types.js +7 -0
- package/dist/src/api/index.d.ts +10 -0
- package/dist/src/api/index.js +10 -0
- package/dist/src/artifacts/address-parser.js +73 -0
- package/dist/src/artifacts/audit/cli.js +183 -0
- package/dist/src/artifacts/audit/drift.js +122 -0
- package/dist/src/artifacts/audit/types.js +12 -0
- package/dist/src/artifacts/backends/graphology-backend.js +160 -0
- package/dist/src/artifacts/backends/json-backend.js +129 -0
- package/dist/src/artifacts/backends/sqlite-backend.js +199 -0
- package/dist/src/artifacts/browser-export.js +622 -0
- package/dist/src/artifacts/capability-resolver.js +94 -0
- package/dist/src/artifacts/checksum-manifest.js +91 -0
- package/dist/src/artifacts/citation-parser.js +162 -0
- package/dist/src/artifacts/cli.js +1462 -0
- package/dist/src/artifacts/corpus-tools/citation-densify.js +311 -0
- package/dist/src/artifacts/corpus-tools/cli.js +393 -0
- package/dist/src/artifacts/corpus-tools/corpus-graph.js +225 -0
- package/dist/src/artifacts/corpus-tools/curator.js +186 -0
- package/dist/src/artifacts/corpus-tools/discovery-log.js +70 -0
- package/dist/src/artifacts/corpus-tools/funder-network.js +141 -0
- package/dist/src/artifacts/corpus-tools/induction-audit.js +233 -0
- package/dist/src/artifacts/corpus-tools/integrity-scan.js +202 -0
- package/dist/src/artifacts/corpus-tools/profile-communities.js +166 -0
- package/dist/src/artifacts/corpus-tools/profile-edges.js +72 -0
- package/dist/src/artifacts/corpus-tools/profile-embed.js +113 -0
- package/dist/src/artifacts/corpus-tools/profile-generate-fm.js +167 -0
- package/dist/src/artifacts/corpus-tools/profile-generate.js +181 -0
- package/dist/src/artifacts/corpus-tools/profile-metrics.js +98 -0
- package/dist/src/artifacts/corpus-tools/profile-status.js +86 -0
- package/dist/src/artifacts/corpus-tools/profile-temporal.js +139 -0
- package/dist/src/artifacts/corpus-tools/radar-init.js +93 -0
- package/dist/src/artifacts/corpus-tools/radar-report.js +118 -0
- package/dist/src/artifacts/corpus-tools/radar-shared.js +189 -0
- package/dist/src/artifacts/corpus-tools/radar-status.js +68 -0
- package/dist/src/artifacts/corpus-tools/sidecar-lint.js +233 -0
- package/dist/src/artifacts/corpus-tools/sidecar-repair.js +277 -0
- package/dist/src/artifacts/corpus-tools/snapshot.js +591 -0
- package/dist/src/artifacts/corpus-tools/source-types.js +164 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +243 -0
- package/dist/src/artifacts/corpus-views/build.js +126 -0
- package/dist/src/artifacts/corpus-views/corpus-config.js +124 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +437 -0
- package/dist/src/artifacts/corpus-views/renderers.js +353 -0
- package/dist/src/artifacts/corpus-views/taxonomies.js +131 -0
- package/dist/src/artifacts/dep-graph.js +173 -0
- package/dist/src/artifacts/discover-facets.js +375 -0
- package/dist/src/artifacts/embedding-index.js +354 -0
- package/dist/src/artifacts/enrichment/cli.js +240 -0
- package/dist/src/artifacts/enrichment/prompt.js +47 -0
- package/dist/src/artifacts/enrichment/store.js +90 -0
- package/dist/src/artifacts/enrichment/types.js +12 -0
- package/dist/src/artifacts/fortemi-core-query-adapter.js +475 -0
- package/dist/src/artifacts/fortemi-core-sync.js +251 -0
- package/dist/src/artifacts/fortemi-shard-export.js +44 -0
- package/dist/src/artifacts/fulltext.js +108 -0
- package/dist/src/artifacts/graph-backend.js +51 -0
- package/dist/src/artifacts/graph-query.js +247 -0
- package/dist/src/artifacts/hybrid-query.js +225 -0
- package/dist/src/artifacts/index-builder.js +1089 -0
- package/dist/src/artifacts/index-reader.js +112 -0
- package/dist/src/artifacts/index-status.js +193 -0
- package/dist/src/artifacts/legacy-index-migration.js +169 -0
- package/dist/src/artifacts/move.js +154 -0
- package/dist/src/artifacts/operational-state.js +239 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +49 -0
- package/dist/src/artifacts/query-engine.js +1955 -0
- package/dist/src/artifacts/source-graph.js +529 -0
- package/dist/src/artifacts/stats.js +169 -0
- package/dist/src/artifacts/types.js +606 -0
- package/dist/src/artifacts/views/cli.js +293 -0
- package/dist/src/artifacts/views/definition.js +170 -0
- package/dist/src/artifacts/views/store.js +149 -0
- package/dist/src/artifacts/views/types.js +8 -0
- package/dist/src/artifacts/watcher.js +255 -0
- package/dist/src/catalog/builtin-models.json +636 -0
- package/dist/src/catalog/cli.js +228 -0
- package/dist/src/catalog/cli.mjs +289 -0
- package/dist/src/catalog/index.js +15 -0
- package/dist/src/catalog/loader.js +217 -0
- package/dist/src/catalog/loader.mjs +251 -0
- package/dist/src/catalog/sources.json +41 -0
- package/dist/src/catalog/types.js +8 -0
- package/dist/src/channel/manager.mjs +504 -0
- package/dist/src/cli/agent-spawn.js +178 -0
- package/dist/src/cli/cli-extension-loader.js +245 -0
- package/dist/src/cli/command-log.js +275 -0
- package/dist/src/cli/config-loader.js +232 -0
- package/dist/src/cli/env.js +82 -0
- package/dist/src/cli/errors.js +150 -0
- package/dist/src/cli/find-package-root.js +40 -0
- package/dist/src/cli/git-hooks.js +250 -0
- package/dist/src/cli/handlers/agentcard.js +206 -0
- package/dist/src/cli/handlers/artifacts.js +76 -0
- package/dist/src/cli/handlers/best-practices-audit.js +280 -0
- package/dist/src/cli/handlers/cockpit.js +137 -0
- package/dist/src/cli/handlers/command-log.js +37 -0
- package/dist/src/cli/handlers/daemon.js +59 -0
- package/dist/src/cli/handlers/diagnose.js +300 -0
- package/dist/src/cli/handlers/execution-mode.js +105 -0
- package/dist/src/cli/handlers/feedback.js +341 -0
- package/dist/src/cli/handlers/help.js +147 -0
- package/dist/src/cli/handlers/index.js +302 -0
- package/dist/src/cli/handlers/init.js +179 -0
- package/dist/src/cli/handlers/install.js +129 -0
- package/dist/src/cli/handlers/issues.js +46 -0
- package/dist/src/cli/handlers/lint.js +42 -0
- package/dist/src/cli/handlers/local-executor.js +315 -0
- package/dist/src/cli/handlers/marketplace.js +168 -0
- package/dist/src/cli/handlers/mc.js +1010 -0
- package/dist/src/cli/handlers/models.js +352 -0
- package/dist/src/cli/handlers/packages.js +155 -0
- package/dist/src/cli/handlers/ralph-launcher.js +717 -0
- package/dist/src/cli/handlers/ralph.js +575 -0
- package/dist/src/cli/handlers/refresh.js +432 -0
- package/dist/src/cli/handlers/regenerate.js +336 -0
- package/dist/src/cli/handlers/repo-access.js +109 -0
- package/dist/src/cli/handlers/run.js +165 -0
- package/dist/src/cli/handlers/runtime-info.js +301 -0
- package/dist/src/cli/handlers/sandbox.js +197 -0
- package/dist/src/cli/handlers/scaffolding.js +149 -0
- package/dist/src/cli/handlers/script-runner.js +121 -0
- package/dist/src/cli/handlers/sdlc-accelerate.js +148 -0
- package/dist/src/cli/handlers/serve.js +1835 -0
- package/dist/src/cli/handlers/session.js +348 -0
- package/dist/src/cli/handlers/setup.js +440 -0
- package/dist/src/cli/handlers/skill-lint.js +367 -0
- package/dist/src/cli/handlers/skill-usage.js +74 -0
- package/dist/src/cli/handlers/steward.js +567 -0
- package/dist/src/cli/handlers/subcommands.js +1702 -0
- package/dist/src/cli/handlers/team.js +337 -0
- package/dist/src/cli/handlers/types.js +11 -0
- package/dist/src/cli/handlers/use.js +2772 -0
- package/dist/src/cli/handlers/utilities.js +703 -0
- package/dist/src/cli/handlers/version.js +134 -0
- package/dist/src/cli/handlers/workspace-context.js +93 -0
- package/dist/src/cli/handlers/workspace.js +126 -0
- package/dist/src/cli/help-generator.js +178 -0
- package/dist/src/cli/hooks/builtin/activity-log-hook.js +126 -0
- package/dist/src/cli/hooks/executor.js +96 -0
- package/dist/src/cli/hooks/index.js +15 -0
- package/dist/src/cli/hooks/registry.js +143 -0
- package/dist/src/cli/hooks/types.js +13 -0
- package/dist/src/cli/log.js +492 -0
- package/dist/src/cli/project-isolation/detect.js +70 -0
- package/dist/src/cli/project-isolation/index.js +7 -0
- package/dist/src/cli/project-isolation/signals.js +17 -0
- package/dist/src/cli/project-isolation/warning.js +102 -0
- package/dist/src/cli/prompt-utils.js +155 -0
- package/dist/src/cli/provider-deployment-plan.js +126 -0
- package/dist/src/cli/provider-resolution.js +119 -0
- package/dist/src/cli/router.js +305 -0
- package/dist/src/cli/scope-resolver.js +392 -0
- package/dist/src/cli/skill-usage.js +616 -0
- package/dist/src/cli/ui.js +224 -0
- package/dist/src/cli/watch-service.js +203 -0
- package/dist/src/cli/workflow-orchestrator.js +478 -0
- package/dist/src/cli/workspace-signals.js +321 -0
- package/dist/src/community/footer.js +14 -0
- package/dist/src/community/links.js +123 -0
- package/dist/src/community/milestones.js +46 -0
- package/dist/src/community/nudge-policy.js +93 -0
- package/dist/src/config/aiwg-config.js +883 -0
- package/dist/src/config/cli.js +703 -0
- package/dist/src/config/gitignore.js +156 -0
- package/dist/src/config/project-artifacts.js +76 -0
- package/dist/src/config/user-config.js +428 -0
- package/dist/src/config/user-registry.js +127 -0
- package/dist/src/config/workspace.js +276 -0
- package/dist/src/contributors/detect.js +39 -0
- package/dist/src/contributors/discover.js +172 -0
- package/dist/src/contributors/heuristic.js +186 -0
- package/dist/src/contributors/index.js +11 -0
- package/dist/src/contributors/types.js +13 -0
- package/dist/src/contributors/validation.js +116 -0
- package/dist/src/data/community.schema.json +43 -0
- package/dist/src/data/community.yaml +16 -0
- package/dist/src/extensions/capability-index.js +270 -0
- package/dist/src/extensions/claude-hooks-installer.js +268 -0
- package/dist/src/extensions/commands/definitions.js +3485 -0
- package/dist/src/extensions/deployment-registration.js +463 -0
- package/dist/src/extensions/index.js +22 -0
- package/dist/src/extensions/loader.js +132 -0
- package/dist/src/extensions/managed-marker.js +100 -0
- package/dist/src/extensions/manifest.js +196 -0
- package/dist/src/extensions/project-local-activity.js +114 -0
- package/dist/src/extensions/project-local-discovery.js +261 -0
- package/dist/src/extensions/project-local-doctor.js +281 -0
- package/dist/src/extensions/project-local-gitignore.js +176 -0
- package/dist/src/extensions/project-local-paths.js +121 -0
- package/dist/src/extensions/project-local-promote.js +218 -0
- package/dist/src/extensions/project-local-remove.js +373 -0
- package/dist/src/extensions/project-local-scaffold.js +252 -0
- package/dist/src/extensions/project-quickref.js +291 -0
- package/dist/src/extensions/registry.js +368 -0
- package/dist/src/extensions/shadow-resolver.js +271 -0
- package/dist/src/extensions/types.js +86 -0
- package/dist/src/extensions/upstream-registry.js +195 -0
- package/dist/src/extensions/validation.js +631 -0
- package/dist/src/features/catalog.js +88 -0
- package/dist/src/features/cli.js +197 -0
- package/dist/src/features/installer.js +57 -0
- package/dist/src/features/paths.js +14 -0
- package/dist/src/features/runtime.js +21 -0
- package/dist/src/features/status.js +134 -0
- package/dist/src/hooks/laziness-detection.js +532 -0
- package/dist/src/intake/git-history-analyzer.js +486 -0
- package/dist/src/intake/index.js +9 -0
- package/dist/src/issues/cli.js +327 -0
- package/dist/src/issues/index.js +5 -0
- package/dist/src/issues/live.js +154 -0
- package/dist/src/issues/local.js +666 -0
- package/dist/src/issues/sync.js +143 -0
- package/dist/src/issues/types.js +2 -0
- package/dist/src/issues/workflows.js +164 -0
- package/dist/src/kb/cli.js +173 -0
- package/dist/src/lint/cli.js +171 -0
- package/dist/src/lint/loader.js +225 -0
- package/dist/src/lint/reporters.js +141 -0
- package/dist/src/lint/runner.js +364 -0
- package/dist/src/lint/types.js +10 -0
- package/dist/src/marketplace/cache.js +110 -0
- package/dist/src/marketplace/identifier.js +63 -0
- package/dist/src/marketplace/registry.js +36 -0
- package/dist/src/marketplace/sources/clawhub.js +53 -0
- package/dist/src/marketplace/sources/git.js +45 -0
- package/dist/src/marketplace/types.js +9 -0
- package/dist/src/mcp/adapters/codex-runtime.js +224 -0
- package/dist/src/mcp/cli.mjs +1197 -0
- package/dist/src/mcp/elicitation.mjs +149 -0
- package/dist/src/mcp/helpers.mjs +287 -0
- package/dist/src/mcp/index.mjs +11 -0
- package/dist/src/mcp/profiles.js +263 -0
- package/dist/src/mcp/profiles.mjs +360 -0
- package/dist/src/mcp/registry.js +376 -0
- package/dist/src/mcp/registry.mjs +387 -0
- package/dist/src/mcp/server.mjs +633 -0
- package/dist/src/mcp/test-simple.mjs +271 -0
- package/dist/src/mcp/tools/command-run.mjs +101 -0
- package/dist/src/mcp/tools/discovery.mjs +291 -0
- package/dist/src/mcp/tools/interaction.mjs +87 -0
- package/dist/src/mcp/tools/orchestration.mjs +320 -0
- package/dist/src/mcp/tools/subsystems.mjs +640 -0
- package/dist/src/memory/cli.js +166 -0
- package/dist/src/memory/project-registry.js +282 -0
- package/dist/src/metrics/artifact-metrics.js +184 -0
- package/dist/src/metrics/context-budget.js +174 -0
- package/dist/src/metrics/token-counter.js +133 -0
- package/dist/src/models/config-loader.js +228 -0
- package/dist/src/models/index.js +22 -0
- package/dist/src/models/model-capabilities.v1.json +120 -0
- package/dist/src/models/model-catalog.v1.json +96 -0
- package/dist/src/models/model-discovery.js +521 -0
- package/dist/src/models/provider-models.js +73 -0
- package/dist/src/models/provider-policy.js +273 -0
- package/dist/src/models/resolver.js +245 -0
- package/dist/src/models/router.js +70 -0
- package/dist/src/models/types.js +8 -0
- package/dist/src/models/wrapper-deployment.js +178 -0
- package/dist/src/models/wrapper-route.js +54 -0
- package/dist/src/ops/cli.js +268 -0
- package/dist/src/ops/registry.js +623 -0
- package/dist/src/packages/adapters/clawhub.js +113 -0
- package/dist/src/packages/adapters/git.js +153 -0
- package/dist/src/packages/adapters/gitea.js +93 -0
- package/dist/src/packages/adapters/github.js +69 -0
- package/dist/src/packages/adapters/local-cache.js +89 -0
- package/dist/src/packages/package-registry.js +143 -0
- package/dist/src/packages/registry.js +238 -0
- package/dist/src/packages/types.js +11 -0
- package/dist/src/plugin/cross-framework-ops.js +475 -0
- package/dist/src/plugin/framework-config-loader.js +235 -0
- package/dist/src/plugin/framework-detector.js +186 -0
- package/dist/src/plugin/framework-isolator.js +341 -0
- package/dist/src/plugin/framework-migration.js +371 -0
- package/dist/src/plugin/metadata-validator.js +906 -0
- package/dist/src/plugin/plugin-installer.js +436 -0
- package/dist/src/plugin/plugin-status.js +369 -0
- package/dist/src/plugin/plugin-uninstaller.js +473 -0
- package/dist/src/plugin/registry-validator.js +344 -0
- package/dist/src/plugin/skill-command-translator.js +415 -0
- package/dist/src/plugin/workspace-creator.js +189 -0
- package/dist/src/plugin/workspace-migrator.js +821 -0
- package/dist/src/policy/authorization.js +390 -0
- package/dist/src/policy/repo-access.js +215 -0
- package/dist/src/provenance/cli.js +16 -0
- package/dist/src/providers/capability-matrix.js +300 -0
- package/dist/src/providers/capability-matrix.yaml +514 -0
- package/dist/src/providers/provider-definitions.js +977 -0
- package/dist/src/providers/provider-definitions.mjs +159 -0
- package/dist/src/providers/provider-inventory.js +169 -0
- package/dist/src/quality/feedback-ab.js +174 -0
- package/dist/src/quality/feedback-collector.js +176 -0
- package/dist/src/quality/feedback-tracker.js +123 -0
- package/dist/src/quality/patterns/adr.json +24 -0
- package/dist/src/quality/patterns/sad.json +24 -0
- package/dist/src/quality/patterns/test-plan.json +24 -0
- package/dist/src/quality/patterns/use-case.json +24 -0
- package/dist/src/quality/scoring.js +157 -0
- package/dist/src/reflections/cli.js +15 -0
- package/dist/src/release/plan.js +139 -0
- package/dist/src/research/cache/manager.js +227 -0
- package/dist/src/research/clients/arxiv.js +156 -0
- package/dist/src/research/clients/base.js +197 -0
- package/dist/src/research/clients/crossref.js +112 -0
- package/dist/src/research/clients/semantic-scholar.js +126 -0
- package/dist/src/research/clients/unpaywall.js +87 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/query-cli.js +336 -0
- package/dist/src/research/services/acquisition.js +313 -0
- package/dist/src/research/services/archival.js +225 -0
- package/dist/src/research/services/citation.js +279 -0
- package/dist/src/research/services/discovery.js +243 -0
- package/dist/src/research/services/documentation.js +269 -0
- package/dist/src/research/services/index.js +14 -0
- package/dist/src/research/services/provenance.js +279 -0
- package/dist/src/research/services/quality.js +370 -0
- package/dist/src/research/services/types.js +7 -0
- package/dist/src/research/storage-cli.js +20 -0
- package/dist/src/research/types.js +47 -0
- package/dist/src/resources/index.d.ts +2 -0
- package/dist/src/resources/index.js +2 -0
- package/dist/src/resources/web-release.d.ts +89 -0
- package/dist/src/resources/web-release.js +882 -0
- package/dist/src/rlm/cache/cli.js +162 -0
- package/dist/src/rlm/cache/hash.js +35 -0
- package/dist/src/rlm/cache/store.js +170 -0
- package/dist/src/rlm/cache/types.js +8 -0
- package/dist/src/rlm/cli.js +650 -0
- package/dist/src/serve/a2a-terminal-observer.js +120 -0
- package/dist/src/serve/agent-router.js +205 -0
- package/dist/src/serve/dispatch-router.js +171 -0
- package/dist/src/serve/executor-registry.js +715 -0
- package/dist/src/serve/mission-conductor.js +177 -0
- package/dist/src/serve/orchestrator-adapter.js +113 -0
- package/dist/src/serve/orchestrator-override.js +94 -0
- package/dist/src/serve/orchestrator-pty.js +133 -0
- package/dist/src/serve/pty-bridge.js +799 -0
- package/dist/src/serve/sandbox-registry.js +832 -0
- package/dist/src/serve/screen-reader.js +228 -0
- package/dist/src/serve/stack-adapters.js +68 -0
- package/dist/src/serve/telemetry.js +143 -0
- package/dist/src/skills/adapters/clawhub.js +250 -0
- package/dist/src/skills/adapters/local.js +335 -0
- package/dist/src/skills/adapters/openclaw.js +316 -0
- package/dist/src/skills/cli.js +334 -0
- package/dist/src/skills/registry.js +117 -0
- package/dist/src/skills/run.js +259 -0
- package/dist/src/skills/runtime.js +94 -0
- package/dist/src/skills/types.js +10 -0
- package/dist/src/smiths/agentsmith/demo.js +116 -0
- package/dist/src/smiths/agentsmith/examples.js +300 -0
- package/dist/src/smiths/agentsmith/generator.js +503 -0
- package/dist/src/smiths/agentsmith/index.js +11 -0
- package/dist/src/smiths/agentsmith/types.js +8 -0
- package/dist/src/smiths/commandsmith/example.js +220 -0
- package/dist/src/smiths/commandsmith/generator.js +288 -0
- package/dist/src/smiths/commandsmith/index.js +41 -0
- package/dist/src/smiths/commandsmith/templates.js +296 -0
- package/dist/src/smiths/commandsmith/types.js +7 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +104 -0
- package/dist/src/smiths/context-pipeline/allowlist.js +195 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +127 -0
- package/dist/src/smiths/context-pipeline/discovery.js +187 -0
- package/dist/src/smiths/context-pipeline/external-links-section.js +49 -0
- package/dist/src/smiths/context-pipeline/finalization.js +147 -0
- package/dist/src/smiths/context-pipeline/generator.js +477 -0
- package/dist/src/smiths/context-pipeline/index.js +30 -0
- package/dist/src/smiths/context-pipeline/legacy-inject.js +103 -0
- package/dist/src/smiths/context-pipeline/managed-hook.js +95 -0
- package/dist/src/smiths/context-pipeline/overflow.js +212 -0
- package/dist/src/smiths/context-pipeline/parallelism-section.js +94 -0
- package/dist/src/smiths/context-pipeline/provider-policy.js +64 -0
- package/dist/src/smiths/context-pipeline/sanitizer.js +93 -0
- package/dist/src/smiths/context-pipeline/types.js +23 -0
- package/dist/src/smiths/context-pipeline/workspace-context.js +988 -0
- package/dist/src/smiths/hook-bridge/codex-translator.js +129 -0
- package/dist/src/smiths/hook-bridge/copilot-translator.js +64 -0
- package/dist/src/smiths/hook-bridge/factory-translator.js +43 -0
- package/dist/src/smiths/hook-bridge/hermes-translator.js +88 -0
- package/dist/src/smiths/hook-bridge/index.js +79 -0
- package/dist/src/smiths/hook-bridge/loader.js +116 -0
- package/dist/src/smiths/hook-bridge/shim.js +89 -0
- package/dist/src/smiths/hook-bridge/types.js +31 -0
- package/dist/src/smiths/index.js +39 -0
- package/dist/src/smiths/mcpsmith/analyzers/cli-analyzer.js +390 -0
- package/dist/src/smiths/mcpsmith/example.js +119 -0
- package/dist/src/smiths/mcpsmith/generator.js +236 -0
- package/dist/src/smiths/mcpsmith/index.js +17 -0
- package/dist/src/smiths/mcpsmith/types.js +10 -0
- package/dist/src/smiths/platform-paths.js +101 -0
- package/dist/src/smiths/skillsmith/codex-sidecar.js +85 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +249 -0
- package/dist/src/smiths/skillsmith/demo.js +83 -0
- package/dist/src/smiths/skillsmith/examples.js +161 -0
- package/dist/src/smiths/skillsmith/generator.js +316 -0
- package/dist/src/smiths/skillsmith/index.js +12 -0
- package/dist/src/smiths/skillsmith/namespace-adapter.js +180 -0
- package/dist/src/smiths/skillsmith/platform-resolver.js +157 -0
- package/dist/src/smiths/skillsmith/types.js +9 -0
- package/dist/src/smiths/toolsmith/index.js +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +710 -0
- package/dist/src/smiths/toolsmith/types.js +8 -0
- package/dist/src/storage/backends/fortemi.js +227 -0
- package/dist/src/storage/backends/fs.js +134 -0
- package/dist/src/storage/backends/logseq.js +309 -0
- package/dist/src/storage/backends/obsidian.js +233 -0
- package/dist/src/storage/cli.js +468 -0
- package/dist/src/storage/config.js +272 -0
- package/dist/src/storage/index.js +108 -0
- package/dist/src/storage/subsystem-cli.js +201 -0
- package/dist/src/storage/types.js +33 -0
- package/dist/src/testing/corpus/corpus-builder.js +383 -0
- package/dist/src/testing/corpus/ground-truth-manager.js +460 -0
- package/dist/src/testing/corpus/index.js +10 -0
- package/dist/src/testing/fixtures/index.js +9 -0
- package/dist/src/testing/fixtures/test-data-factory.js +472 -0
- package/dist/src/testing/generators/index.js +11 -0
- package/dist/src/testing/generators/test-case-generator.js +393 -0
- package/dist/src/testing/generators/test-code-generator.js +384 -0
- package/dist/src/testing/generators/use-case-parser.js +325 -0
- package/dist/src/testing/index.js +11 -0
- package/dist/src/tracker/capability-protocol.js +118 -0
- package/dist/src/update/checker.mjs +396 -0
- package/dist/src/update/notifier.mjs +235 -0
- package/dist/src/writing/content-diversifier.js +804 -0
- package/dist/src/writing/example-generator.js +959 -0
- package/dist/src/writing/example-templates.js +99 -0
- package/dist/src/writing/pattern-library.js +486 -0
- package/dist/src/writing/patterns/banned-phrases.json +4413 -0
- package/dist/src/writing/patterns/formulaic-structures.json +1300 -0
- package/dist/src/writing/patterns/generic-adjectives.json +1510 -0
- package/dist/src/writing/patterns/hedging-language.json +2096 -0
- package/dist/src/writing/patterns/transition-words.json +1285 -0
- package/dist/src/writing/patterns/weak-verbs.json +1112 -0
- package/dist/src/writing/prompt-optimizer.js +660 -0
- package/dist/src/writing/prompt-templates.js +583 -0
- package/dist/src/writing/scoring-config-loader.js +168 -0
- package/dist/src/writing/validation-engine.js +595 -0
- package/dist/src/writing/validation-rules.js +380 -0
- package/dist/src/writing/voice-analyzer.js +439 -0
- package/dist/src/writing/voice-calibration.js +661 -0
- package/dist/src/writing/voice-profiles.json +588 -0
- 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
|