@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,883 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-Level AIWG Config
|
|
3
|
+
*
|
|
4
|
+
* Manages `.aiwg/aiwg.config` — the project-level record of:
|
|
5
|
+
* - Which AI provider toolchains this project targets
|
|
6
|
+
* - Which frameworks/addons are deployed (with uninstall metadata)
|
|
7
|
+
* - User-defined scripts callable via `aiwg run`
|
|
8
|
+
*
|
|
9
|
+
* @implements #621
|
|
10
|
+
*/
|
|
11
|
+
import { readFile, writeFile, mkdir, access, readdir, rename, unlink } from 'fs/promises';
|
|
12
|
+
import { createHash, randomBytes } from 'crypto';
|
|
13
|
+
import { resolve, join, isAbsolute } from 'path';
|
|
14
|
+
import { normalizeNamedCaptures } from '../artifacts/index-builder.js';
|
|
15
|
+
import { getProviderDefinition, PROVIDER_IDS, resolveProviderPathValue, } from '../providers/provider-definitions.js';
|
|
16
|
+
import { validateAuthorization, } from '../policy/authorization.js';
|
|
17
|
+
import { projectAiwgPath, resolveProjectAiwgDir } from './project-artifacts.js';
|
|
18
|
+
const CONFIG_FILENAME = 'aiwg.config';
|
|
19
|
+
/**
|
|
20
|
+
* Operations that a workspace may authorize for one member repository.
|
|
21
|
+
* Filesystem/tool capability never implies authorization; unlisted members
|
|
22
|
+
* and actions are denied by the workspace resolver.
|
|
23
|
+
*
|
|
24
|
+
* @implements #1764
|
|
25
|
+
*/
|
|
26
|
+
export const WORKSPACE_REPO_ACTIONS = [
|
|
27
|
+
'read',
|
|
28
|
+
'write',
|
|
29
|
+
'commit',
|
|
30
|
+
'push',
|
|
31
|
+
'issue-comment',
|
|
32
|
+
'service-action',
|
|
33
|
+
'destructive',
|
|
34
|
+
];
|
|
35
|
+
const DEFAULT_BRANCH_NAMING = {
|
|
36
|
+
prefix_by_type: {
|
|
37
|
+
feat: 'feat/{issue}-{slug}',
|
|
38
|
+
fix: 'fix/{issue}-{slug}',
|
|
39
|
+
docs: 'docs/{slug}',
|
|
40
|
+
chore: 'chore/{slug}',
|
|
41
|
+
refactor: 'refactor/{slug}',
|
|
42
|
+
test: 'test/{slug}',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the delivery policy with defaults applied.
|
|
47
|
+
*
|
|
48
|
+
* Defaults are intentionally conservative — they match what AIWG agents
|
|
49
|
+
* naturally do today (PR-required, rebase-merge, no force pushes, post issue
|
|
50
|
+
* comments) so that adding the schema doesn't shift behavior for existing
|
|
51
|
+
* projects.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveDelivery(delivery) {
|
|
54
|
+
return {
|
|
55
|
+
mode: delivery?.mode ?? 'pr-required',
|
|
56
|
+
default_branch: delivery?.default_branch ?? 'main',
|
|
57
|
+
branch_naming: {
|
|
58
|
+
prefix_by_type: {
|
|
59
|
+
...DEFAULT_BRANCH_NAMING.prefix_by_type,
|
|
60
|
+
...(delivery?.branch_naming?.prefix_by_type ?? {}),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
merge_style: delivery?.merge_style ?? 'rebase-merge',
|
|
64
|
+
delete_branch_on_merge: delivery?.delete_branch_on_merge ?? true,
|
|
65
|
+
require_ci_green: delivery?.require_ci_green ?? true,
|
|
66
|
+
require_signed_commits: delivery?.require_signed_commits ?? false,
|
|
67
|
+
committer: delivery?.committer,
|
|
68
|
+
signing: delivery?.signing,
|
|
69
|
+
release_signing: delivery?.release_signing,
|
|
70
|
+
force_push_policy: delivery?.force_push_policy ?? 'never',
|
|
71
|
+
auto_close_issues: delivery?.auto_close_issues ?? true,
|
|
72
|
+
issue_comment_on_cycle: delivery?.issue_comment_on_cycle ?? true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Per-provider parallelism defaults. Conservative numbers for Anthropic-backed
|
|
77
|
+
* providers reflect Pro/Team-plan rate limits — operators on Enterprise tiers
|
|
78
|
+
* should bump via `aiwg config set --project parallelism.max_parallel_subagents N`.
|
|
79
|
+
*
|
|
80
|
+
* Sources for the numbers:
|
|
81
|
+
* - claude / claude-code: Anthropic per-key throttling at higher concurrency
|
|
82
|
+
* - codex / copilot / etc.: OpenAI / GitHub quotas are generally per-org and
|
|
83
|
+
* less aggressive at small fan-outs (10 is a safe middle ground)
|
|
84
|
+
* - hermes: MCP sidecar; rate-limit depends on upstream provider, operator
|
|
85
|
+
* should tune. Conservative 10 default.
|
|
86
|
+
* - unknown: conservative 4 default.
|
|
87
|
+
*/
|
|
88
|
+
export const PROVIDER_PARALLELISM_DEFAULTS = {
|
|
89
|
+
claude: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
90
|
+
codex: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
91
|
+
copilot: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
92
|
+
cursor: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
93
|
+
factory: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
94
|
+
opencode: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
95
|
+
warp: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
96
|
+
windsurf: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
97
|
+
openclaw: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
98
|
+
hermes: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
99
|
+
};
|
|
100
|
+
const UNKNOWN_PROVIDER_PARALLELISM = {
|
|
101
|
+
max_parallel_subagents: 4,
|
|
102
|
+
max_parallel_ralph_loops: 2,
|
|
103
|
+
max_parallel_mc_missions: 4,
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Return the provider's parallelism defaults, or the conservative fallback
|
|
107
|
+
* when the provider is unknown.
|
|
108
|
+
*/
|
|
109
|
+
export function getProviderParallelismDefaults(provider) {
|
|
110
|
+
if (!provider)
|
|
111
|
+
return UNKNOWN_PROVIDER_PARALLELISM;
|
|
112
|
+
return PROVIDER_PARALLELISM_DEFAULTS[provider] ?? UNKNOWN_PROVIDER_PARALLELISM;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the parallelism caps with provider-aware defaults applied. The
|
|
116
|
+
* primary provider drives the default — typically the first entry in the
|
|
117
|
+
* project's `providers` array.
|
|
118
|
+
*
|
|
119
|
+
* When `parallelism` has explicit values, they override the provider default
|
|
120
|
+
* field-by-field. When no provider is supplied (or it's not in the defaults
|
|
121
|
+
* map), the conservative 4-subagent fallback applies.
|
|
122
|
+
*
|
|
123
|
+
* @implements #1359
|
|
124
|
+
*/
|
|
125
|
+
export function resolveParallelism(parallelism, primaryProvider) {
|
|
126
|
+
const defaults = getProviderParallelismDefaults(primaryProvider);
|
|
127
|
+
const resolved = {
|
|
128
|
+
max_parallel_subagents: parallelism?.max_parallel_subagents ?? defaults.max_parallel_subagents,
|
|
129
|
+
max_parallel_ralph_loops: parallelism?.max_parallel_ralph_loops ?? defaults.max_parallel_ralph_loops,
|
|
130
|
+
max_parallel_mc_missions: parallelism?.max_parallel_mc_missions ?? defaults.max_parallel_mc_missions,
|
|
131
|
+
};
|
|
132
|
+
if (parallelism?.rationale !== undefined)
|
|
133
|
+
resolved.rationale = parallelism.rationale;
|
|
134
|
+
return resolved;
|
|
135
|
+
}
|
|
136
|
+
const GRAPH_BACKENDS = ['json', 'graphology', 'sqlite'];
|
|
137
|
+
const NODE_STRATEGIES = ['default', 'filename-metadata'];
|
|
138
|
+
const BUILD_TIERS = ['lightweight', 'standard', 'heavy'];
|
|
139
|
+
/**
|
|
140
|
+
* Validate an `index` config block, returning a list of human-readable error
|
|
141
|
+
* messages (empty = valid). Hand-rolled rather than ajv because aiwg.config has
|
|
142
|
+
* no runtime schema-validation pipeline and the codebase deliberately avoids
|
|
143
|
+
* pulling a validator dependency for config parsing (#1491).
|
|
144
|
+
*
|
|
145
|
+
* Catches the failure modes #1491 calls out: unknown graph type, missing
|
|
146
|
+
* scanDirs, bad regex, typo'd keys, malformed manifest. `aiwg index build` and
|
|
147
|
+
* `aiwg doctor` surface these at validate time instead of at runtime.
|
|
148
|
+
*
|
|
149
|
+
* @param index The value of `aiwg.config.index` (untrusted shape).
|
|
150
|
+
* @returns Array of error strings; empty when the block is valid or absent.
|
|
151
|
+
*/
|
|
152
|
+
export function validateIndexConfig(index) {
|
|
153
|
+
const errors = [];
|
|
154
|
+
if (index === undefined || index === null)
|
|
155
|
+
return errors;
|
|
156
|
+
if (typeof index !== 'object' || Array.isArray(index)) {
|
|
157
|
+
return ['index: must be an object'];
|
|
158
|
+
}
|
|
159
|
+
const graphs = index.graphs;
|
|
160
|
+
if (graphs === undefined)
|
|
161
|
+
return errors; // index with no graphs is permissible
|
|
162
|
+
if (typeof graphs !== 'object' || graphs === null || Array.isArray(graphs)) {
|
|
163
|
+
return ['index.graphs: must be an object mapping graph names to definitions'];
|
|
164
|
+
}
|
|
165
|
+
const isStringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === 'string');
|
|
166
|
+
for (const [name, rawDef] of Object.entries(graphs)) {
|
|
167
|
+
const where = `index.graphs.${name}`;
|
|
168
|
+
if (typeof rawDef !== 'object' || rawDef === null || Array.isArray(rawDef)) {
|
|
169
|
+
errors.push(`${where}: must be an object`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const def = rawDef;
|
|
173
|
+
if (name === 'indices') {
|
|
174
|
+
// Markdown-view manifest (build.py).
|
|
175
|
+
const manifest = def.manifest;
|
|
176
|
+
if (!Array.isArray(manifest)) {
|
|
177
|
+
errors.push(`${where}.manifest: must be an array`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
manifest.forEach((entry, i) => {
|
|
181
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
182
|
+
errors.push(`${where}.manifest[${i}]: must be an object`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const e = entry;
|
|
186
|
+
if (typeof e.name !== 'string' || e.name.trim() === '') {
|
|
187
|
+
errors.push(`${where}.manifest[${i}]: 'name' is required and must be a non-empty string (it selects the renderer)`);
|
|
188
|
+
}
|
|
189
|
+
if (e.output !== undefined && typeof e.output !== 'string') {
|
|
190
|
+
errors.push(`${where}.manifest[${i}].output: must be a string`);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// JSON node/edge graph def.
|
|
196
|
+
if (!isStringArray(def.scanDirs) || def.scanDirs.length === 0) {
|
|
197
|
+
errors.push(`${where}.scanDirs: required, must be a non-empty array of strings`);
|
|
198
|
+
}
|
|
199
|
+
if (def.extensions !== undefined && !isStringArray(def.extensions)) {
|
|
200
|
+
errors.push(`${where}.extensions: must be an array of strings`);
|
|
201
|
+
}
|
|
202
|
+
if (def.nodeStrategy !== undefined && !NODE_STRATEGIES.includes(def.nodeStrategy)) {
|
|
203
|
+
errors.push(`${where}.nodeStrategy: must be one of ${NODE_STRATEGIES.join(' | ')}`);
|
|
204
|
+
}
|
|
205
|
+
if (def.graphBackend !== undefined && !GRAPH_BACKENDS.includes(def.graphBackend)) {
|
|
206
|
+
errors.push(`${where}.graphBackend: must be one of ${GRAPH_BACKENDS.join(' | ')}`);
|
|
207
|
+
}
|
|
208
|
+
if (def.buildTier !== undefined && !BUILD_TIERS.includes(def.buildTier)) {
|
|
209
|
+
errors.push(`${where}.buildTier: must be one of ${BUILD_TIERS.join(' | ')}`);
|
|
210
|
+
}
|
|
211
|
+
if (def.buildOrder !== undefined && (typeof def.buildOrder !== 'number' || !Number.isFinite(def.buildOrder))) {
|
|
212
|
+
errors.push(`${where}.buildOrder: must be a finite number`);
|
|
213
|
+
}
|
|
214
|
+
if (def.nodeStrategy === 'filename-metadata') {
|
|
215
|
+
if (typeof def.filenamePattern !== 'string' || def.filenamePattern.trim() === '') {
|
|
216
|
+
errors.push(`${where}.filenamePattern: required when nodeStrategy is 'filename-metadata'`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (def.filenamePattern !== undefined && typeof def.filenamePattern === 'string') {
|
|
220
|
+
try {
|
|
221
|
+
// Apply the same Python→JS named-capture normalization the builder uses
|
|
222
|
+
// (index-builder.ts buildFilenameMetadataEntry) so the validator doesn't
|
|
223
|
+
// reject patterns the builder would happily compile. Fixes #1514 —
|
|
224
|
+
// `aiwg` workspace refresh emits Python-style `(?P<name>)` patterns
|
|
225
|
+
// (port residue from the Python build.py); the builder is lenient, the
|
|
226
|
+
// validator wasn't, so legit configs failed up front.
|
|
227
|
+
// eslint-disable-next-line no-new
|
|
228
|
+
new RegExp(normalizeNamedCaptures(def.filenamePattern));
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
errors.push(`${where}.filenamePattern: not a valid regular expression (${err.message})`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (def.edgeExtraction !== undefined) {
|
|
235
|
+
const ee = def.edgeExtraction;
|
|
236
|
+
if (typeof ee !== 'object' || ee === null || Array.isArray(ee)) {
|
|
237
|
+
errors.push(`${where}.edgeExtraction: must be an object`);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
const eo = ee;
|
|
241
|
+
if (eo.parser !== 'citation-sidecar') {
|
|
242
|
+
errors.push(`${where}.edgeExtraction.parser: must be 'citation-sidecar'`);
|
|
243
|
+
}
|
|
244
|
+
if (!Array.isArray(eo.edges)) {
|
|
245
|
+
errors.push(`${where}.edgeExtraction.edges: must be an array`);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
eo.edges.forEach((edge, i) => {
|
|
249
|
+
if (typeof edge !== 'object' || edge === null) {
|
|
250
|
+
errors.push(`${where}.edgeExtraction.edges[${i}]: must be an object`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const ed = edge;
|
|
254
|
+
for (const k of ['type', 'source', 'target']) {
|
|
255
|
+
if (typeof ed[k] !== 'string' || ed[k].trim() === '') {
|
|
256
|
+
errors.push(`${where}.edgeExtraction.edges[${i}].${k}: required, must be a non-empty string`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return errors;
|
|
265
|
+
}
|
|
266
|
+
const EXTERNAL_LINK_KEY_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
267
|
+
/**
|
|
268
|
+
* Validate project-defined external links without performing network access.
|
|
269
|
+
*/
|
|
270
|
+
export function validateExternalLinks(externalLinks) {
|
|
271
|
+
if (externalLinks === undefined || externalLinks === null)
|
|
272
|
+
return [];
|
|
273
|
+
if (typeof externalLinks !== 'object' || Array.isArray(externalLinks)) {
|
|
274
|
+
return ['externalLinks: must be an object mapping stable identifiers to link definitions'];
|
|
275
|
+
}
|
|
276
|
+
const errors = [];
|
|
277
|
+
for (const [key, rawLink] of Object.entries(externalLinks)) {
|
|
278
|
+
const where = `externalLinks.${key}`;
|
|
279
|
+
if (!EXTERNAL_LINK_KEY_PATTERN.test(key)) {
|
|
280
|
+
errors.push(`${where}: key must start with a lowercase letter and contain only lowercase letters, numbers, underscores, or hyphens`);
|
|
281
|
+
}
|
|
282
|
+
if (!rawLink || typeof rawLink !== 'object' || Array.isArray(rawLink)) {
|
|
283
|
+
errors.push(`${where}: must be an object`);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const link = rawLink;
|
|
287
|
+
const allowedFields = new Set(['label', 'url', 'description', 'category', 'audience']);
|
|
288
|
+
for (const field of Object.keys(link)) {
|
|
289
|
+
if (!allowedFields.has(field)) {
|
|
290
|
+
errors.push(`${where}.${field}: unknown field`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (typeof link.label !== 'string' || link.label.trim() === '') {
|
|
294
|
+
errors.push(`${where}.label: required, must be a non-empty string`);
|
|
295
|
+
}
|
|
296
|
+
else if (link.label.length > 200) {
|
|
297
|
+
errors.push(`${where}.label: must be at most 200 characters`);
|
|
298
|
+
}
|
|
299
|
+
if (typeof link.url !== 'string' || link.url.trim() === '') {
|
|
300
|
+
errors.push(`${where}.url: required, must be an absolute HTTP(S) URL`);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
try {
|
|
304
|
+
const parsed = new URL(link.url);
|
|
305
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
306
|
+
errors.push(`${where}.url: protocol must be http or https`);
|
|
307
|
+
}
|
|
308
|
+
if (parsed.username || parsed.password) {
|
|
309
|
+
errors.push(`${where}.url: embedded credentials are not allowed`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
errors.push(`${where}.url: must be a valid absolute URL`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
for (const field of ['description', 'category', 'audience']) {
|
|
317
|
+
const value = link[field];
|
|
318
|
+
if (value !== undefined && (typeof value !== 'string' || value.trim() === '')) {
|
|
319
|
+
errors.push(`${where}.${field}: must be a non-empty string when provided`);
|
|
320
|
+
}
|
|
321
|
+
else if (typeof value === 'string' && value.length > 500) {
|
|
322
|
+
errors.push(`${where}.${field}: must be at most 500 characters`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return errors;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Validate the general workspace-of-repositories blocks.
|
|
330
|
+
*
|
|
331
|
+
* This is intentionally runtime validation as well as schema documentation:
|
|
332
|
+
* CLI callers may not have editor/schema support, and authorization data must
|
|
333
|
+
* fail closed when malformed.
|
|
334
|
+
*
|
|
335
|
+
* @implements #1764
|
|
336
|
+
*/
|
|
337
|
+
export function validateWorkspaceConfig(workspace, repos) {
|
|
338
|
+
const errors = [];
|
|
339
|
+
if (workspace === undefined && repos === undefined)
|
|
340
|
+
return errors;
|
|
341
|
+
if (!workspace || typeof workspace !== 'object' || Array.isArray(workspace)) {
|
|
342
|
+
return ['workspace: must be an object when workspace or repos is configured'];
|
|
343
|
+
}
|
|
344
|
+
const metadata = workspace;
|
|
345
|
+
for (const field of ['name', 'root', 'member_of']) {
|
|
346
|
+
if (metadata[field] !== undefined && (typeof metadata[field] !== 'string' || !metadata[field].trim())) {
|
|
347
|
+
errors.push(`workspace.${field}: must be a non-empty string`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (metadata.member_of !== undefined && repos !== undefined) {
|
|
351
|
+
errors.push('workspace.member_of: member back-references must not also declare repos');
|
|
352
|
+
}
|
|
353
|
+
if (repos === undefined)
|
|
354
|
+
return errors;
|
|
355
|
+
if (typeof metadata.name !== 'string' || !metadata.name.trim()) {
|
|
356
|
+
errors.push('workspace.name: required when repos is configured');
|
|
357
|
+
}
|
|
358
|
+
if (!Array.isArray(repos) || repos.length === 0) {
|
|
359
|
+
errors.push('repos: must be a non-empty array');
|
|
360
|
+
return errors;
|
|
361
|
+
}
|
|
362
|
+
const names = new Set();
|
|
363
|
+
const paths = new Set();
|
|
364
|
+
const allowedActions = new Set(WORKSPACE_REPO_ACTIONS);
|
|
365
|
+
repos.forEach((raw, index) => {
|
|
366
|
+
const where = `repos[${index}]`;
|
|
367
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
368
|
+
errors.push(`${where}: must be an object`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const repo = raw;
|
|
372
|
+
if (typeof repo.name !== 'string' || !repo.name.trim()) {
|
|
373
|
+
errors.push(`${where}.name: required and must be a non-empty string`);
|
|
374
|
+
}
|
|
375
|
+
else if (names.has(repo.name.trim())) {
|
|
376
|
+
errors.push(`${where}.name: duplicate member name '${repo.name.trim()}'`);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
names.add(repo.name.trim());
|
|
380
|
+
}
|
|
381
|
+
if (typeof repo.path !== 'string' || !repo.path.trim()) {
|
|
382
|
+
errors.push(`${where}.path: required and must be a non-empty string`);
|
|
383
|
+
}
|
|
384
|
+
else if (paths.has(repo.path.trim())) {
|
|
385
|
+
errors.push(`${where}.path: duplicate member path '${repo.path.trim()}'`);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
paths.add(repo.path.trim());
|
|
389
|
+
}
|
|
390
|
+
if (!Array.isArray(repo.allowed) || repo.allowed.length === 0) {
|
|
391
|
+
errors.push(`${where}.allowed: must be a non-empty array`);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
for (const action of repo.allowed) {
|
|
395
|
+
if (typeof action !== 'string' || !allowedActions.has(action)) {
|
|
396
|
+
errors.push(`${where}.allowed: invalid operation '${String(action)}'`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (repo.provider !== undefined && !['gitea', 'github', 'gitlab'].includes(String(repo.provider))) {
|
|
401
|
+
errors.push(`${where}.provider: must be gitea, github, or gitlab`);
|
|
402
|
+
}
|
|
403
|
+
if (repo.notes !== undefined && typeof repo.notes !== 'string') {
|
|
404
|
+
errors.push(`${where}.notes: must be a string`);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
return errors;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Read the `index` block, preferring `.aiwg/aiwg.config` (the consolidated
|
|
411
|
+
* home, #1491) and falling back to the legacy `.aiwg/config.yaml` `index:`
|
|
412
|
+
* block. Returns `{ index, source }` where source is 'aiwg.config',
|
|
413
|
+
* 'config.yaml' (deprecated), or 'none'.
|
|
414
|
+
*
|
|
415
|
+
* The fallback keeps un-migrated corpora working; callers should surface a
|
|
416
|
+
* deprecation notice when `source === 'config.yaml'`.
|
|
417
|
+
*/
|
|
418
|
+
export async function readIndexConfig(projectDir) {
|
|
419
|
+
const cfg = await readAiwgConfig(projectDir);
|
|
420
|
+
if (cfg?.index && typeof cfg.index === 'object') {
|
|
421
|
+
return { index: cfg.index, source: 'aiwg.config' };
|
|
422
|
+
}
|
|
423
|
+
// Fallback: legacy config.yaml in the resolved AIWG artifact directory (deprecated).
|
|
424
|
+
const yamlPath = projectAiwgPath(projectDir, 'config.yaml');
|
|
425
|
+
try {
|
|
426
|
+
await access(yamlPath);
|
|
427
|
+
const { load: loadYaml } = await import('js-yaml');
|
|
428
|
+
const raw = loadYaml(await readFile(yamlPath, 'utf-8'));
|
|
429
|
+
const idx = raw?.index;
|
|
430
|
+
if (idx && typeof idx === 'object') {
|
|
431
|
+
return { index: idx, source: 'config.yaml' };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
/* no config.yaml or unreadable */
|
|
436
|
+
}
|
|
437
|
+
return { index: undefined, source: 'none' };
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Provider tag for a given remote URL. Used by skills (issue-create,
|
|
441
|
+
* pr-review, commit-and-push) to pick the right CLI / MCP client when
|
|
442
|
+
* the operator didn't pass `--provider` explicitly.
|
|
443
|
+
*
|
|
444
|
+
* Recognized hosts:
|
|
445
|
+
* - github.com → 'github'
|
|
446
|
+
* - gitlab.com / gitlab.* → 'gitlab'
|
|
447
|
+
* - any host containing 'gitea' (or matching the typical Gitea path shape) → 'gitea'
|
|
448
|
+
*
|
|
449
|
+
* Returns 'unknown' for self-hosted instances we can't classify by host alone —
|
|
450
|
+
* callers should then prompt the operator or fall back to the configured
|
|
451
|
+
* AIWG provider list.
|
|
452
|
+
*
|
|
453
|
+
* @implements #997
|
|
454
|
+
*/
|
|
455
|
+
export function resolveRemoteProvider(remoteUrl) {
|
|
456
|
+
if (!remoteUrl)
|
|
457
|
+
return 'unknown';
|
|
458
|
+
const lower = remoteUrl.toLowerCase();
|
|
459
|
+
// github.com (or git@github.com:owner/repo.git form)
|
|
460
|
+
if (/(^|[/@])github\.com[:/]/.test(lower))
|
|
461
|
+
return 'github';
|
|
462
|
+
// gitlab.com or self-hosted gitlab
|
|
463
|
+
if (/(^|[/@])gitlab\./.test(lower) || lower.includes('/gitlab/'))
|
|
464
|
+
return 'gitlab';
|
|
465
|
+
// gitea — identified by hostname token. Self-hosted Gitea instances often
|
|
466
|
+
// don't include 'gitea' in their hostname (e.g. corporate git servers), so
|
|
467
|
+
// 'unknown' is the honest answer there — callers should consult the
|
|
468
|
+
// configured AIWG provider list rather than guess.
|
|
469
|
+
if (lower.includes('gitea'))
|
|
470
|
+
return 'gitea';
|
|
471
|
+
return 'unknown';
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Resolve the repo remote topology with defaults applied.
|
|
475
|
+
*
|
|
476
|
+
* Defaults:
|
|
477
|
+
* - `primary` defaults to "origin"
|
|
478
|
+
* - `issue_tracker` defaults to `primary`
|
|
479
|
+
* - `ci` defaults to `primary`
|
|
480
|
+
* - `secondary` defaults to `[]`
|
|
481
|
+
*
|
|
482
|
+
* Pass an absent or partial `remotes` block — every field comes back populated.
|
|
483
|
+
*/
|
|
484
|
+
export function resolveRemotes(remotes) {
|
|
485
|
+
const primary = remotes?.primary ?? 'origin';
|
|
486
|
+
return {
|
|
487
|
+
primary,
|
|
488
|
+
issue_tracker: remotes?.issue_tracker ?? primary,
|
|
489
|
+
ci: remotes?.ci ?? primary,
|
|
490
|
+
tracker_actor: remotes?.tracker_actor,
|
|
491
|
+
transport: remotes?.transport,
|
|
492
|
+
secondary: remotes?.secondary ?? [],
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Resolve stable semantic label roles to provider-native label strings.
|
|
497
|
+
* The same role therefore drives Gitea, GitHub, and local issue-store flows.
|
|
498
|
+
*/
|
|
499
|
+
export function resolveIssueLabels(issues, provider) {
|
|
500
|
+
const diagnostics = validateIssueLabels(issues, { provider });
|
|
501
|
+
if (!issues?.labels) {
|
|
502
|
+
return {
|
|
503
|
+
labels: {},
|
|
504
|
+
diagnostics: [{
|
|
505
|
+
severity: 'warning',
|
|
506
|
+
code: 'fallback',
|
|
507
|
+
message: 'issues.labels is not configured; issue workflows retain legacy label-name behavior and must warn before guessing or provisioning labels.',
|
|
508
|
+
}],
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
const labels = Object.fromEntries(Object.entries(issues.labels).map(([role, definition]) => [
|
|
512
|
+
role,
|
|
513
|
+
{
|
|
514
|
+
...definition,
|
|
515
|
+
role,
|
|
516
|
+
provider,
|
|
517
|
+
resolved_name: definition.provider_names?.[provider] ?? definition.name,
|
|
518
|
+
},
|
|
519
|
+
]));
|
|
520
|
+
return { labels, diagnostics };
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Validate taxonomy structure and, when supplied, tracker availability.
|
|
524
|
+
* This function is read-only: ordinary issue processing never provisions
|
|
525
|
+
* missing labels implicitly.
|
|
526
|
+
*/
|
|
527
|
+
export function validateIssueLabels(issues, options = {}) {
|
|
528
|
+
if (!issues?.labels)
|
|
529
|
+
return [];
|
|
530
|
+
const diagnostics = [];
|
|
531
|
+
const seenNames = new Map();
|
|
532
|
+
const roles = new Set(Object.keys(issues.labels));
|
|
533
|
+
const available = options.availableLabels ? new Set(options.availableLabels) : undefined;
|
|
534
|
+
const categories = new Set([
|
|
535
|
+
'type', 'area', 'priority', 'lifecycle', 'blocked-reason',
|
|
536
|
+
'review-approval', 'ownership', 'automation-eligibility', 'human-interaction',
|
|
537
|
+
]);
|
|
538
|
+
for (const [role, definition] of Object.entries(issues.labels)) {
|
|
539
|
+
const where = `issues.labels.${role}`;
|
|
540
|
+
if (!definition.name?.trim() || !definition.description?.trim() || !definition.category) {
|
|
541
|
+
diagnostics.push({
|
|
542
|
+
severity: 'error',
|
|
543
|
+
code: 'missing',
|
|
544
|
+
role,
|
|
545
|
+
message: `${where} must define non-empty name, description, and category fields.`,
|
|
546
|
+
});
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (!categories.has(definition.category)) {
|
|
550
|
+
diagnostics.push({
|
|
551
|
+
severity: 'error',
|
|
552
|
+
code: 'conflict',
|
|
553
|
+
role,
|
|
554
|
+
message: `${where}.category '${definition.category}' is not a supported semantic category.`,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
if (typeof definition.requires_human !== 'boolean' || typeof definition.blocks_automation !== 'boolean') {
|
|
558
|
+
diagnostics.push({
|
|
559
|
+
severity: 'error',
|
|
560
|
+
code: 'missing',
|
|
561
|
+
role,
|
|
562
|
+
message: `${where} must explicitly define requires_human and blocks_automation booleans.`,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
if (definition.blocks_automation && !definition.requires_human && !definition.resume_when) {
|
|
566
|
+
diagnostics.push({
|
|
567
|
+
severity: 'error',
|
|
568
|
+
code: 'conflict',
|
|
569
|
+
role,
|
|
570
|
+
message: `${where} blocks automation but declares neither human action nor a resume condition.`,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if (definition.blocks_automation && !definition.resume_when?.trim()) {
|
|
574
|
+
diagnostics.push({
|
|
575
|
+
severity: 'error',
|
|
576
|
+
code: 'missing',
|
|
577
|
+
role,
|
|
578
|
+
message: `${where}.resume_when is required when blocks_automation is true.`,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
if (definition.transition_to && !roles.has(definition.transition_to)) {
|
|
582
|
+
diagnostics.push({
|
|
583
|
+
severity: 'error',
|
|
584
|
+
code: 'conflict',
|
|
585
|
+
role,
|
|
586
|
+
message: `${where}.transition_to references unknown semantic role '${definition.transition_to}'.`,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
const resolvedName = options.provider
|
|
590
|
+
? definition.provider_names?.[options.provider] ?? definition.name
|
|
591
|
+
: definition.name;
|
|
592
|
+
const normalized = resolvedName.trim().toLocaleLowerCase();
|
|
593
|
+
const previousRole = seenNames.get(normalized);
|
|
594
|
+
if (previousRole) {
|
|
595
|
+
diagnostics.push({
|
|
596
|
+
severity: 'error',
|
|
597
|
+
code: 'duplicate',
|
|
598
|
+
role,
|
|
599
|
+
message: `${where} resolves to '${resolvedName}', already used by role '${previousRole}'.`,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
seenNames.set(normalized, role);
|
|
604
|
+
}
|
|
605
|
+
if (available && !available.has(resolvedName)) {
|
|
606
|
+
diagnostics.push({
|
|
607
|
+
severity: 'error',
|
|
608
|
+
code: 'unavailable',
|
|
609
|
+
role,
|
|
610
|
+
message: `${where} resolves to unavailable tracker label '${resolvedName}'; provision it explicitly before issue processing.`,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return diagnostics;
|
|
615
|
+
}
|
|
616
|
+
export const VALID_PROVIDERS = PROVIDER_IDS.filter((provider) => provider !== 'generic');
|
|
617
|
+
/**
|
|
618
|
+
* Empty config template.
|
|
619
|
+
*
|
|
620
|
+
* Includes an explicit `delivery` block defaulting to `pr-required`. The
|
|
621
|
+
* runtime default in {@link resolveDelivery} is the same, so this is purely
|
|
622
|
+
* for visibility — new projects ship with the policy written down so users
|
|
623
|
+
* can see what their agents will do, and switch via `aiwg config set` or
|
|
624
|
+
* the AIWG Steward agent without first having to discover the field exists.
|
|
625
|
+
*/
|
|
626
|
+
export function emptyConfig(providers = ['claude']) {
|
|
627
|
+
const primaryProvider = providers[0];
|
|
628
|
+
const parDefaults = getProviderParallelismDefaults(primaryProvider);
|
|
629
|
+
const knownProvider = primaryProvider && primaryProvider in PROVIDER_PARALLELISM_DEFAULTS;
|
|
630
|
+
return {
|
|
631
|
+
$schema: 'https://aiwg.io/schemas/aiwg.config.v1.json',
|
|
632
|
+
version: '1',
|
|
633
|
+
providers,
|
|
634
|
+
installed: {},
|
|
635
|
+
scripts: {},
|
|
636
|
+
delivery: {
|
|
637
|
+
mode: 'pr-required',
|
|
638
|
+
default_branch: 'main',
|
|
639
|
+
require_ci_green: true,
|
|
640
|
+
auto_close_issues: true,
|
|
641
|
+
issue_comment_on_cycle: true,
|
|
642
|
+
force_push_policy: 'never',
|
|
643
|
+
},
|
|
644
|
+
parallelism: {
|
|
645
|
+
max_parallel_subagents: parDefaults.max_parallel_subagents,
|
|
646
|
+
max_parallel_ralph_loops: parDefaults.max_parallel_ralph_loops,
|
|
647
|
+
max_parallel_mc_missions: parDefaults.max_parallel_mc_missions,
|
|
648
|
+
rationale: knownProvider
|
|
649
|
+
? `Provider default for ${primaryProvider} — adjust via 'aiwg config set --project parallelism.max_parallel_subagents N'`
|
|
650
|
+
: `Conservative default (unknown provider) — adjust via 'aiwg config set --project parallelism.max_parallel_subagents N'`,
|
|
651
|
+
},
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Resolve path to the project-level AIWG config.
|
|
656
|
+
*
|
|
657
|
+
* Defaults to `<project>/.aiwg/aiwg.config`; honors `AIWG_ARTIFACTS_PATH` so
|
|
658
|
+
* projects can rename or relocate the AIWG artifact directory.
|
|
659
|
+
*/
|
|
660
|
+
export function getConfigPath(projectDir) {
|
|
661
|
+
return projectAiwgPath(projectDir, CONFIG_FILENAME);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Resolve the project directory for a handler invocation.
|
|
665
|
+
*
|
|
666
|
+
* Precedence:
|
|
667
|
+
* 1. `--target <path>` or `--prefix <path>` flag in args
|
|
668
|
+
* 2. The HandlerContext `cwd`, if provided
|
|
669
|
+
* 3. `process.cwd()`
|
|
670
|
+
*
|
|
671
|
+
* All three variants existed scattered across handlers (#919 cleanup).
|
|
672
|
+
* Use this helper so we have one authoritative resolution.
|
|
673
|
+
*/
|
|
674
|
+
export function getProjectDir(ctx, args = []) {
|
|
675
|
+
const targetIdx = args.findIndex(a => a === '--target' || a === '--prefix');
|
|
676
|
+
const targetValue = targetIdx >= 0 ? args[targetIdx + 1] : undefined;
|
|
677
|
+
return targetValue || ctx?.cwd || process.cwd();
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Read .aiwg/aiwg.config.
|
|
681
|
+
* Returns null if the file does not exist.
|
|
682
|
+
*/
|
|
683
|
+
export async function readAiwgConfig(projectDir) {
|
|
684
|
+
const filePath = getConfigPath(projectDir);
|
|
685
|
+
try {
|
|
686
|
+
await access(filePath);
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
const content = await readFile(filePath, 'utf-8');
|
|
692
|
+
const parsed = JSON.parse(content);
|
|
693
|
+
// Ensure required fields exist (forward-compat)
|
|
694
|
+
if (!parsed.providers)
|
|
695
|
+
parsed.providers = ['claude'];
|
|
696
|
+
if (!parsed.installed)
|
|
697
|
+
parsed.installed = {};
|
|
698
|
+
if (!parsed.scripts)
|
|
699
|
+
parsed.scripts = {};
|
|
700
|
+
const externalLinkErrors = validateExternalLinks(parsed.externalLinks);
|
|
701
|
+
if (externalLinkErrors.length > 0) {
|
|
702
|
+
throw new Error(`Invalid .aiwg/aiwg.config:\n${externalLinkErrors.join('\n')}`);
|
|
703
|
+
}
|
|
704
|
+
const workspaceErrors = validateWorkspaceConfig(parsed.workspace, parsed.repos);
|
|
705
|
+
if (workspaceErrors.length > 0) {
|
|
706
|
+
throw new Error(`Invalid .aiwg/aiwg.config:\n${workspaceErrors.join('\n')}`);
|
|
707
|
+
}
|
|
708
|
+
const authorizationErrors = parsed.authorization
|
|
709
|
+
? validateAuthorization(parsed.authorization).filter(item => item.severity === 'error')
|
|
710
|
+
: [];
|
|
711
|
+
if (authorizationErrors.length > 0) {
|
|
712
|
+
throw new Error(`Invalid .aiwg/aiwg.config:\n${authorizationErrors.map(item => item.message).join('\n')}`);
|
|
713
|
+
}
|
|
714
|
+
return parsed;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Write aiwg.config, creating the resolved AIWG artifact directory if needed.
|
|
718
|
+
*/
|
|
719
|
+
export async function writeAiwgConfig(projectDir, config) {
|
|
720
|
+
const dir = resolveProjectAiwgDir(projectDir);
|
|
721
|
+
await mkdir(dir, { recursive: true });
|
|
722
|
+
const filePath = join(dir, CONFIG_FILENAME);
|
|
723
|
+
// Atomic write: emit to a temp sibling, fsync-ish via rename. Prevents a
|
|
724
|
+
// crash or kill mid-write from corrupting the config file. Rename is
|
|
725
|
+
// atomic on POSIX and on NTFS when both paths are on the same volume.
|
|
726
|
+
// The random suffix avoids collisions if two concurrent writers run.
|
|
727
|
+
const tmpPath = `${filePath}.${randomBytes(6).toString('hex')}.tmp`;
|
|
728
|
+
try {
|
|
729
|
+
await writeFile(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
730
|
+
await rename(tmpPath, filePath);
|
|
731
|
+
}
|
|
732
|
+
catch (err) {
|
|
733
|
+
// Best-effort cleanup of the temp file on failure, ignoring ENOENT.
|
|
734
|
+
try {
|
|
735
|
+
await unlink(tmpPath);
|
|
736
|
+
}
|
|
737
|
+
catch {
|
|
738
|
+
/* ignore */
|
|
739
|
+
}
|
|
740
|
+
throw err;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Update the `installed` record for a framework after a successful deployment.
|
|
745
|
+
* Returns the updated config (does not write to disk — caller must call writeAiwgConfig).
|
|
746
|
+
*/
|
|
747
|
+
export function updateInstalled(config, name, provider, counts, opts) {
|
|
748
|
+
// Project-local invariant: `source: 'project-local'` requires localPath + localType
|
|
749
|
+
// (per ADR adr-unified-registry-shape §6 risk mitigation).
|
|
750
|
+
if (opts.source === 'project-local') {
|
|
751
|
+
if (!opts.localPath || !opts.localType) {
|
|
752
|
+
throw new Error(`updateInstalled: source 'project-local' requires localPath and localType (got ${JSON.stringify({ localPath: opts.localPath, localType: opts.localType })})`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
const existing = config.installed[name] ?? {
|
|
756
|
+
version: opts.version,
|
|
757
|
+
source: opts.source,
|
|
758
|
+
installedAt: new Date().toISOString(),
|
|
759
|
+
deployedTo: {},
|
|
760
|
+
manifestHash: opts.manifestHash,
|
|
761
|
+
};
|
|
762
|
+
existing.version = opts.version;
|
|
763
|
+
existing.source = opts.source;
|
|
764
|
+
existing.installedAt = new Date().toISOString();
|
|
765
|
+
existing.deployedTo[provider] = counts;
|
|
766
|
+
if (opts.manifestHash)
|
|
767
|
+
existing.manifestHash = opts.manifestHash;
|
|
768
|
+
if (opts.source === 'project-local') {
|
|
769
|
+
existing.localPath = opts.localPath;
|
|
770
|
+
existing.localType = opts.localType;
|
|
771
|
+
if (opts.manifestVersion)
|
|
772
|
+
existing.manifestVersion = opts.manifestVersion;
|
|
773
|
+
if (opts.artifactHashes)
|
|
774
|
+
existing.artifactHashes = opts.artifactHashes;
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
// Clear stale project-local fields if a previously project-local entry is
|
|
778
|
+
// being overwritten by a non-project-local source.
|
|
779
|
+
delete existing.localPath;
|
|
780
|
+
delete existing.localType;
|
|
781
|
+
delete existing.manifestVersion;
|
|
782
|
+
delete existing.artifactHashes;
|
|
783
|
+
}
|
|
784
|
+
config.installed[name] = existing;
|
|
785
|
+
return config;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Aggregate deployment counts across all installed frameworks for a given provider.
|
|
789
|
+
* Returns the totals for agents, commands, skills, and rules.
|
|
790
|
+
* If no provider is specified, uses the first configured provider.
|
|
791
|
+
*/
|
|
792
|
+
export function getDeploymentSummary(config, provider) {
|
|
793
|
+
const targetProvider = provider ?? config.providers[0] ?? 'claude';
|
|
794
|
+
const totals = { agents: 0, commands: 0, skills: 0, rules: 0 };
|
|
795
|
+
for (const entry of Object.values(config.installed)) {
|
|
796
|
+
const counts = entry.deployedTo[targetProvider];
|
|
797
|
+
if (!counts)
|
|
798
|
+
continue;
|
|
799
|
+
totals.agents += counts.agents;
|
|
800
|
+
totals.commands += counts.commands;
|
|
801
|
+
totals.skills += counts.skills;
|
|
802
|
+
totals.rules += counts.rules;
|
|
803
|
+
}
|
|
804
|
+
return totals;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Compute SHA-256 hash of a manifest.json file.
|
|
808
|
+
* Returns undefined if the file cannot be read.
|
|
809
|
+
*/
|
|
810
|
+
export async function hashManifest(manifestPath) {
|
|
811
|
+
try {
|
|
812
|
+
const content = await readFile(manifestPath, 'utf-8');
|
|
813
|
+
return 'sha256:' + createHash('sha256').update(content).digest('hex');
|
|
814
|
+
}
|
|
815
|
+
catch {
|
|
816
|
+
return undefined;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function getProviderDeployDirs(provider, projectDir) {
|
|
820
|
+
const artifacts = getProviderDefinition(provider)?.paths.artifacts;
|
|
821
|
+
if (!artifacts)
|
|
822
|
+
return null;
|
|
823
|
+
return {
|
|
824
|
+
agents: resolveProviderPathValue(artifacts.agents, projectDir),
|
|
825
|
+
skills: resolveProviderPathValue(artifacts.skills, projectDir),
|
|
826
|
+
commands: resolveProviderPathValue(artifacts.commands, projectDir),
|
|
827
|
+
rules: resolveProviderPathValue(artifacts.rules, projectDir),
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Count .md files or subdirectories in a deployment directory.
|
|
832
|
+
* Returns 0 if the directory does not exist.
|
|
833
|
+
*/
|
|
834
|
+
async function countDeployedInDir(projectDir, relOrAbsDir, mode) {
|
|
835
|
+
if (!relOrAbsDir)
|
|
836
|
+
return 0;
|
|
837
|
+
const dir = isAbsolute(relOrAbsDir) ? relOrAbsDir : resolve(projectDir, relOrAbsDir);
|
|
838
|
+
try {
|
|
839
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
840
|
+
if (mode === 'md')
|
|
841
|
+
return entries.filter(e => e.isFile() && e.name.endsWith('.md')).length;
|
|
842
|
+
return entries.filter(e => e.isDirectory()).length;
|
|
843
|
+
}
|
|
844
|
+
catch {
|
|
845
|
+
return 0;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Scan actual deployment directories and populate `deployedTo` for any
|
|
850
|
+
* `installed` entries that have an empty `deployedTo` map.
|
|
851
|
+
*
|
|
852
|
+
* Called by `aiwg init` when migrating a project that already has frameworks
|
|
853
|
+
* deployed but whose config was created before deployment-tracking was added.
|
|
854
|
+
*
|
|
855
|
+
* @implements #721
|
|
856
|
+
*/
|
|
857
|
+
export async function populateDeployedTo(config, projectDir) {
|
|
858
|
+
const entriesNeedingPopulation = Object.entries(config.installed).filter(([, entry]) => Object.keys(entry.deployedTo).length === 0);
|
|
859
|
+
if (entriesNeedingPopulation.length === 0)
|
|
860
|
+
return config;
|
|
861
|
+
for (const provider of config.providers) {
|
|
862
|
+
const dirs = getProviderDeployDirs(provider, projectDir);
|
|
863
|
+
if (!dirs)
|
|
864
|
+
continue;
|
|
865
|
+
const counts = {
|
|
866
|
+
agents: await countDeployedInDir(projectDir, dirs.agents, 'md'),
|
|
867
|
+
commands: await countDeployedInDir(projectDir, dirs.commands, 'md'),
|
|
868
|
+
skills: await countDeployedInDir(projectDir, dirs.skills, 'dirs'),
|
|
869
|
+
rules: await countDeployedInDir(projectDir, dirs.rules, 'md'),
|
|
870
|
+
};
|
|
871
|
+
// Only populate if at least one artifact type is present
|
|
872
|
+
if (counts.agents + counts.commands + counts.skills + counts.rules === 0)
|
|
873
|
+
continue;
|
|
874
|
+
for (const [name, entry] of entriesNeedingPopulation) {
|
|
875
|
+
if (Object.keys(entry.deployedTo).length === 0) {
|
|
876
|
+
entry.deployedTo[provider] = counts;
|
|
877
|
+
config.installed[name] = entry;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return config;
|
|
882
|
+
}
|
|
883
|
+
//# sourceMappingURL=aiwg-config.js.map
|