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