@aiwg/cli 2026.10.3 → 2026.10.4
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 +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +98 -37
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +51 -11
- package/dist/src/sessions/contracts.js +40 -2
- package/dist/src/sessions/coverage-preflight.js +1 -1
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +147 -52
- package/dist/src/sessions/workspace-discovery.js +221 -56
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
* @source @src/artifacts/types.ts
|
|
9
9
|
* @tests @test/unit/artifacts/query-engine.test.ts
|
|
10
10
|
*/
|
|
11
|
+
import { refreshFrameworkCapabilities, resolveFrameworkCorpusRoot, isCorpusBundleInstalled } from './framework-capabilities.js';
|
|
12
|
+
import { resolveFrameworkIndexRoot } from './framework-index-paths.js';
|
|
13
|
+
import { ensureRelations, selectPrebuiltRelations, emptyRuleStats } from './relations.js';
|
|
11
14
|
import { minimatch } from 'minimatch';
|
|
15
|
+
import { inferRuleActivation } from '../rules/activation.mjs';
|
|
12
16
|
import * as fs from 'node:fs';
|
|
13
17
|
import * as path from 'node:path';
|
|
14
|
-
import { OPERATIONAL_DISCOVERY_TYPES, OPERATIONAL_SHOW_TYPES, isOperationalShowType, loadGlobalGraphConfigs, } from './types.js';
|
|
18
|
+
import { OPERATIONAL_DISCOVERY_TYPES, OPERATIONAL_SHOW_TYPES, isOperationalShowType, loadGlobalGraphConfigs, getGraphIndexDir, } from './types.js';
|
|
15
19
|
import { loadMetadataIndex, loadGraphIndexFile } from './index-reader.js';
|
|
16
20
|
import { bm25Rank } from './fulltext.js';
|
|
17
21
|
import { parseFrontmatter } from './index-builder.js';
|
|
@@ -859,21 +863,6 @@ function showHintForDiscoverResult(entry, id) {
|
|
|
859
863
|
}
|
|
860
864
|
return `aiwg show ${id}`;
|
|
861
865
|
}
|
|
862
|
-
async function getAiwgRootForDiscover() {
|
|
863
|
-
if (process.env.AIWG_ROOT)
|
|
864
|
-
return process.env.AIWG_ROOT;
|
|
865
|
-
try {
|
|
866
|
-
const mod = await import('../channel/manager.mjs');
|
|
867
|
-
if (typeof mod.getFrameworkRoot === 'function') {
|
|
868
|
-
const r = await mod.getFrameworkRoot();
|
|
869
|
-
return r || null;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
catch {
|
|
873
|
-
// channel manager unavailable — fall through
|
|
874
|
-
}
|
|
875
|
-
return null;
|
|
876
|
-
}
|
|
877
866
|
export async function discoverCapability(cwd, params) {
|
|
878
867
|
loadGlobalGraphConfigs();
|
|
879
868
|
const startTime = Date.now();
|
|
@@ -888,7 +877,7 @@ export async function discoverCapability(cwd, params) {
|
|
|
888
877
|
const limit = params.limit ?? 5;
|
|
889
878
|
// For framework-graph entries, anchor returned paths to AIWG_ROOT so
|
|
890
879
|
// they resolve from any project working directory (#1217).
|
|
891
|
-
const aiwgRoot =
|
|
880
|
+
const aiwgRoot = resolveFrameworkIndexRoot(cwd);
|
|
892
881
|
// Source: prefer `framework` graph (built post-deploy), fall back to
|
|
893
882
|
// project / codebase / legacy depending on what's available.
|
|
894
883
|
let entries = [];
|
|
@@ -897,10 +886,24 @@ export async function discoverCapability(cwd, params) {
|
|
|
897
886
|
let resourceSource = params.resourceSource ?? 'local';
|
|
898
887
|
let resolvedWebRelease;
|
|
899
888
|
let verifiedSourcePaths;
|
|
889
|
+
const prebuiltRelations = resourceSource !== 'web' && (!params.graph || params.graph === 'framework')
|
|
890
|
+
? selectPrebuiltRelations(cwd).reader : null;
|
|
891
|
+
if ((!prebuiltRelations || backend === 'local') && resourceSource !== 'web' && (!params.graph || params.graph === 'framework')) {
|
|
892
|
+
const root = resolveFrameworkCorpusRoot(cwd) ?? aiwgRoot;
|
|
893
|
+
if (root) {
|
|
894
|
+
try {
|
|
895
|
+
await refreshFrameworkCapabilities(root, backend === 'fortemi-core');
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
console.error(`Warning: capability index refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
900
902
|
if (resourceSource === 'auto') {
|
|
901
903
|
const { loadFortemiCoreMetadataEntries } = await import('./fortemi-core-query-adapter.js');
|
|
902
904
|
const localGraphs = params.graph ? [params.graph] : defaultCapabilityGraphs(cwd);
|
|
903
|
-
const localAvailable = localGraphs.some((graph) => loadFortemiCoreMetadataEntries(cwd, graph).entries.length > 0
|
|
905
|
+
const localAvailable = localGraphs.some((graph) => loadFortemiCoreMetadataEntries(cwd, graph).entries.length > 0
|
|
906
|
+
|| Object.keys(loadGraphIndexFile(cwd, 'metadata.json', graph)?.entries ?? {}).length > 0);
|
|
904
907
|
resourceSource = localAvailable ? 'local' : 'web';
|
|
905
908
|
}
|
|
906
909
|
if (resourceSource === 'web') {
|
|
@@ -949,6 +952,16 @@ export async function discoverCapability(cwd, params) {
|
|
|
949
952
|
continue;
|
|
950
953
|
}
|
|
951
954
|
}
|
|
955
|
+
if (loaded.sourceIndexNewer && resourceSource === 'local') {
|
|
956
|
+
// Do not silently omit a graph whose derived cache is stale. Rank its
|
|
957
|
+
// current source metadata, without consulting scores from the old cache.
|
|
958
|
+
const index = loadGraphIndexFile(cwd, 'metadata.json', graph);
|
|
959
|
+
if (index) {
|
|
960
|
+
console.error(`Warning: ${graph} capability cache is unavailable or stale; using the local ${graph} index. ${loaded.reason}`);
|
|
961
|
+
entries.push(...Object.values(index.entries).map((entry) => withIndexProvenance(entry, graph)));
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
952
965
|
if (loaded.reason) {
|
|
953
966
|
unavailableReason ??= loaded.reason;
|
|
954
967
|
if (graph === 'framework' && !params.graph) {
|
|
@@ -992,6 +1005,7 @@ export async function discoverCapability(cwd, params) {
|
|
|
992
1005
|
: {}),
|
|
993
1006
|
},
|
|
994
1007
|
results: [],
|
|
1008
|
+
rule_stats: emptyRuleStats(),
|
|
995
1009
|
total: 0,
|
|
996
1010
|
hint: unavailableReason,
|
|
997
1011
|
}, null, 2));
|
|
@@ -1011,47 +1025,10 @@ export async function discoverCapability(cwd, params) {
|
|
|
1011
1025
|
// discover is EXCLUSIVELY the agentic-capability surface (#1545): source
|
|
1012
1026
|
// the `framework` graph (deployed AIWG capabilities) + project-local
|
|
1013
1027
|
// capability artifacts. NEVER `codebase` — source code is not a capability
|
|
1014
|
-
// surface.
|
|
1015
|
-
// index is
|
|
1016
|
-
//
|
|
1017
|
-
|
|
1018
|
-
if (!fwIdx) {
|
|
1019
|
-
// The framework graph is shared (XDG) and `defaultBuild: false`, so a bare
|
|
1020
|
-
// `aiwg index build` never (re)builds it — only `aiwg use` or an explicit
|
|
1021
|
-
// `--graph framework` does. When it's absent, build it from wherever the
|
|
1022
|
-
// AIWG source lives: `cwd/agentic/code` when inside the AIWG repo, else
|
|
1023
|
-
// `$AIWG_ROOT/agentic/code` for a USER project (#1541 — opencode users sit
|
|
1024
|
-
// in their own project, not the AIWG repo, so a cwd-only check left
|
|
1025
|
-
// discover unable to find deployed commands like address-issues). buildIndex
|
|
1026
|
-
// scans <root>/agentic/code and writes to the cwd-independent XDG dir, so
|
|
1027
|
-
// reading back via cwd still resolves the freshly-built shared index.
|
|
1028
|
-
const buildRoot = fs.existsSync(path.join(cwd, 'agentic', 'code'))
|
|
1029
|
-
? cwd
|
|
1030
|
-
: aiwgRoot && fs.existsSync(path.join(aiwgRoot, 'agentic', 'code'))
|
|
1031
|
-
? aiwgRoot
|
|
1032
|
-
: null;
|
|
1033
|
-
if (buildRoot) {
|
|
1034
|
-
const { buildIndex } = await import('./index-builder.js');
|
|
1035
|
-
if (!params.json)
|
|
1036
|
-
console.error('Building capability index (first run)…');
|
|
1037
|
-
// buildIndex writes progress to stdout. In --json mode that would corrupt
|
|
1038
|
-
// the JSON envelope the caller parses, so silence stdout for the duration
|
|
1039
|
-
// of the auto-build (the build is incidental to the query).
|
|
1040
|
-
const origLog = console.log;
|
|
1041
|
-
if (params.json)
|
|
1042
|
-
console.log = () => { };
|
|
1043
|
-
try {
|
|
1044
|
-
await buildIndex(buildRoot, { graph: 'framework', explicit: false });
|
|
1045
|
-
}
|
|
1046
|
-
catch {
|
|
1047
|
-
/* non-fatal — fall through to whatever index is available */
|
|
1048
|
-
}
|
|
1049
|
-
finally {
|
|
1050
|
-
console.log = origLog;
|
|
1051
|
-
}
|
|
1052
|
-
fwIdx = loadGraphIndexFile(cwd, 'metadata.json', 'framework');
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1028
|
+
// surface. The framework
|
|
1029
|
+
// index is reconciled with its source above, including existing indexes
|
|
1030
|
+
// left behind by an older runtime or a different checkout.
|
|
1031
|
+
const fwIdx = loadGraphIndexFile(cwd, 'metadata.json', 'framework');
|
|
1055
1032
|
const includeUser = projectAllowsUserIndices(cwd);
|
|
1056
1033
|
// Project-local operational artifacts authored in this project. The
|
|
1057
1034
|
// DEFAULT_DISCOVER_TYPES filter keeps non-capability project artifacts
|
|
@@ -1082,6 +1059,7 @@ export async function discoverCapability(cwd, params) {
|
|
|
1082
1059
|
console.log(JSON.stringify({
|
|
1083
1060
|
query: { phrase: params.phrase, types },
|
|
1084
1061
|
results: [],
|
|
1062
|
+
rule_stats: emptyRuleStats(),
|
|
1085
1063
|
total: 0,
|
|
1086
1064
|
hint,
|
|
1087
1065
|
}, null, 2));
|
|
@@ -1191,6 +1169,16 @@ export async function discoverCapability(cwd, params) {
|
|
|
1191
1169
|
? `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 ${params.graph ? `--graph ${params.graph}` : '--all'}\`, or pass \`--backend local\` to use the legacy local index.`
|
|
1192
1170
|
: `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\`).`
|
|
1193
1171
|
: null;
|
|
1172
|
+
const relations = resourceSource === 'local' && (!params.graph || params.graph === 'framework')
|
|
1173
|
+
? prebuiltRelations ?? await ensureRelations(cwd) : undefined;
|
|
1174
|
+
const governing = scored.map(result => compoundGoverningRules(entries, result.entry, relations ?? undefined, params.phrase));
|
|
1175
|
+
const responseRules = new Map();
|
|
1176
|
+
for (const item of governing.flat()) {
|
|
1177
|
+
const key = `${item.rule.type}:${item.rule.path}`, prior = responseRules.get(key);
|
|
1178
|
+
responseRules.set(key, { rule: item.rule, applicability: [...new Set([...(prior?.applicability ?? []), ...item.applicability])] });
|
|
1179
|
+
}
|
|
1180
|
+
const responseFields = governingRuleFields([...responseRules.values()]);
|
|
1181
|
+
const responseRuleStats = governingRuleStats([...responseRules.values()]);
|
|
1194
1182
|
const includePaths = params.includePaths ?? true;
|
|
1195
1183
|
const facetActivations = diagnoseFacetActivations(params.phrase);
|
|
1196
1184
|
const roundDiagnostic = (value) => Math.round(value * 1_000_000) / 1_000_000;
|
|
@@ -1228,6 +1216,8 @@ export async function discoverCapability(cwd, params) {
|
|
|
1228
1216
|
},
|
|
1229
1217
|
};
|
|
1230
1218
|
};
|
|
1219
|
+
const { loadRelations } = await import('./relations.js');
|
|
1220
|
+
const ruleRelations = resourceSource === 'local' ? loadRelations(cwd) ?? (aiwgRoot ? loadRelations(aiwgRoot, cwd) : null) : null;
|
|
1231
1221
|
if (params.json) {
|
|
1232
1222
|
const jsonIndent = params.jsonPretty === false ? undefined : 2;
|
|
1233
1223
|
console.log(JSON.stringify({
|
|
@@ -1250,7 +1240,7 @@ export async function discoverCapability(cwd, params) {
|
|
|
1250
1240
|
}
|
|
1251
1241
|
: {}),
|
|
1252
1242
|
},
|
|
1253
|
-
results: scored.map(r => ({
|
|
1243
|
+
results: scored.map((r, resultIndex) => ({
|
|
1254
1244
|
id: discoveryIdForEntry(r.entry),
|
|
1255
1245
|
...(includePaths ? { path: resolvePath(r.entry) } : {}),
|
|
1256
1246
|
type: r.entry.type,
|
|
@@ -1258,9 +1248,14 @@ export async function discoverCapability(cwd, params) {
|
|
|
1258
1248
|
title: r.entry.title,
|
|
1259
1249
|
score: Math.round(r.score * 100) / 100,
|
|
1260
1250
|
ranking: rankingDiagnostic(r.entry, r.score, r.facetDiagnostics),
|
|
1251
|
+
...governingRuleFields(governing[resultIndex]),
|
|
1261
1252
|
triggers: r.entry.triggers ?? [],
|
|
1262
1253
|
capability: r.entry.capability ?? r.entry.summary,
|
|
1263
1254
|
kernel: r.entry.kernel ?? false,
|
|
1255
|
+
...(['skill', 'agent', 'flow', 'command'].includes(r.entry.type) && ruleRelations
|
|
1256
|
+
&& ruleRelations.rulesForTarget(r.entry.type, discoveryIdForEntry(r.entry)).length ? {
|
|
1257
|
+
startup_rule_stats: ruleRelations.ruleStats(r.entry.type, discoveryIdForEntry(r.entry)),
|
|
1258
|
+
} : {}),
|
|
1264
1259
|
provenance: {
|
|
1265
1260
|
graph: r.entry.indexGraph ?? null,
|
|
1266
1261
|
scope: r.entry.indexScope ?? null,
|
|
@@ -1275,6 +1270,8 @@ export async function discoverCapability(cwd, params) {
|
|
|
1275
1270
|
}
|
|
1276
1271
|
: {}),
|
|
1277
1272
|
})),
|
|
1273
|
+
rule_stats: responseRuleStats,
|
|
1274
|
+
...(responseFields.required_rule_checks ? { required_rule_checks: responseFields.required_rule_checks, rule_check_obligation: RULE_CHECK_OBLIGATION } : {}),
|
|
1278
1275
|
total: scored.length,
|
|
1279
1276
|
query_time_ms: queryTimeMs,
|
|
1280
1277
|
diagnostics: {
|
|
@@ -1296,6 +1293,7 @@ export async function discoverCapability(cwd, params) {
|
|
|
1296
1293
|
}
|
|
1297
1294
|
const relaxedNote = relaxed ? ' — relaxed match (verbose query)' : '';
|
|
1298
1295
|
console.log(`Discovery results for "${params.phrase}" (${scored.length} matches, ${queryTimeMs}ms)${relaxedNote}:`);
|
|
1296
|
+
console.log(statsText(responseRuleStats));
|
|
1299
1297
|
console.log('');
|
|
1300
1298
|
for (const diagnostic of facetActivations) {
|
|
1301
1299
|
if (diagnostic.status === 'suppressed' && diagnostic.reason) {
|
|
@@ -1339,6 +1337,12 @@ export async function discoverCapability(cwd, params) {
|
|
|
1339
1337
|
`scope ${ranking.tie_breakers.scope}`,
|
|
1340
1338
|
].filter((part) => Boolean(part));
|
|
1341
1339
|
console.log(` ranking: ${rankingParts.join('; ')}`);
|
|
1340
|
+
if (governing[i].length) {
|
|
1341
|
+
console.log(` ${statsText(governingRuleStats(governing[i]))}`);
|
|
1342
|
+
console.log(` ${RULE_CHECK_OBLIGATION}`);
|
|
1343
|
+
for (const { rule, applicability } of governing[i])
|
|
1344
|
+
console.log(` - ${rule.name ?? rule.path}: ${rule.ruleSummary ?? ''} (${applicability.join('; ')}) — \`${boundRuleDescriptor(rule).show_command}\``);
|
|
1345
|
+
}
|
|
1342
1346
|
if (r.entry.script) {
|
|
1343
1347
|
console.log(` run: ${buildRunHint(r.entry)}`);
|
|
1344
1348
|
}
|
|
@@ -1364,11 +1368,37 @@ function resolveMetadataPath(cwd, aiwgRoot, entry) {
|
|
|
1364
1368
|
}
|
|
1365
1369
|
return path.join(cwd, normalizedPath);
|
|
1366
1370
|
}
|
|
1367
|
-
async function loadShowEntries(cwd, params) {
|
|
1368
|
-
const aiwgRoot =
|
|
1371
|
+
async function loadShowEntries(cwd, params, compact = false) {
|
|
1372
|
+
const aiwgRoot = resolveFrameworkIndexRoot(cwd);
|
|
1369
1373
|
const backend = params.backend ?? DEFAULT_ARTIFACT_SEARCH_BACKEND;
|
|
1370
1374
|
let resourceSource = params.resourceSource ?? 'local';
|
|
1371
1375
|
let entries = [];
|
|
1376
|
+
let relations;
|
|
1377
|
+
if (compact && resourceSource !== 'web' && (!params.graph || params.graph === 'framework')) {
|
|
1378
|
+
relations = await ensureRelations(cwd) ?? undefined;
|
|
1379
|
+
if (relations?.index.artifacts) {
|
|
1380
|
+
const frameworkEntries = Object.values(relations.index.artifacts).map(entry => withIndexProvenance({
|
|
1381
|
+
phase: '', tags: [], created: '', updated: '', checksum: '', summary: '', dependencies: [], dependents: [], ...entry,
|
|
1382
|
+
}, 'framework'));
|
|
1383
|
+
const localEntries = params.graph ? [] : defaultCapabilityGraphs(cwd).filter(graph => graph !== 'framework')
|
|
1384
|
+
.flatMap(graph => loadLocalCapabilityEntries(cwd, graph));
|
|
1385
|
+
const compactEntries = [...localEntries, ...frameworkEntries];
|
|
1386
|
+
if (findShowMatches(compactEntries, params.typeFilter ?? [...OPERATIONAL_SHOW_TYPES], params.name.trim()).length) {
|
|
1387
|
+
return { entries: compactEntries, aiwgRoot, resourceSource: 'local', relations };
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
if (resourceSource !== 'web' && (!params.graph || params.graph === 'framework')) {
|
|
1392
|
+
const root = resolveFrameworkCorpusRoot(cwd) ?? aiwgRoot;
|
|
1393
|
+
if (root) {
|
|
1394
|
+
try {
|
|
1395
|
+
await refreshFrameworkCapabilities(root, backend === 'fortemi-core' && params.backend !== undefined);
|
|
1396
|
+
}
|
|
1397
|
+
catch (error) {
|
|
1398
|
+
console.error(`Warning: capability index refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1372
1402
|
if (resourceSource === 'auto' && backend === 'fortemi-core') {
|
|
1373
1403
|
const { loadFortemiCoreMetadataEntries } = await import('./fortemi-core-query-adapter.js');
|
|
1374
1404
|
const localGraphs = params.graph ? [params.graph] : defaultCapabilityGraphs(cwd);
|
|
@@ -1451,24 +1481,59 @@ async function loadShowEntries(cwd, params) {
|
|
|
1451
1481
|
process.exit(1);
|
|
1452
1482
|
}
|
|
1453
1483
|
}
|
|
1454
|
-
else if (params.graph) {
|
|
1455
|
-
const idx = loadGraphIndexFile(cwd, 'metadata.json', params.graph);
|
|
1456
|
-
if (idx)
|
|
1457
|
-
entries = Object.values(idx.entries).map((entry) => withIndexProvenance(entry, params.graph));
|
|
1458
|
-
}
|
|
1459
1484
|
else {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1485
|
+
entries = loadLocalCapabilityEntries(cwd, params.graph);
|
|
1486
|
+
}
|
|
1487
|
+
return { entries, aiwgRoot, resourceSource: 'local', relations };
|
|
1488
|
+
}
|
|
1489
|
+
/** Local index inputs shared by show and exact rule-binding validation (#2938). */
|
|
1490
|
+
export function loadLocalCapabilityEntries(cwd, graph, snapshot) {
|
|
1491
|
+
const entries = [];
|
|
1492
|
+
for (const selectedGraph of graph ? [graph] : defaultCapabilityGraphs(cwd)) {
|
|
1493
|
+
const key = snapshot && `${selectedGraph}:${getGraphIndexDir(cwd, selectedGraph)}`;
|
|
1494
|
+
let selected = key ? snapshot.get(key) : undefined;
|
|
1495
|
+
if (!selected) {
|
|
1496
|
+
const index = loadGraphIndexFile(cwd, 'metadata.json', selectedGraph);
|
|
1497
|
+
selected = index ? Object.values(index.entries).map(entry => withIndexProvenance(entry, selectedGraph)) : [];
|
|
1498
|
+
if (key)
|
|
1499
|
+
snapshot.set(key, selected);
|
|
1500
|
+
}
|
|
1501
|
+
entries.push(...selected);
|
|
1502
|
+
}
|
|
1503
|
+
if (!graph && entries.length === 0) {
|
|
1504
|
+
const legacy = loadMetadataIndex(cwd);
|
|
1505
|
+
if (legacy)
|
|
1506
|
+
entries.push(...Object.values(legacy.entries));
|
|
1507
|
+
}
|
|
1508
|
+
return entries;
|
|
1509
|
+
}
|
|
1510
|
+
/** Project/user inputs only; package artifacts come from a validated compact table. */
|
|
1511
|
+
export function loadLocalOverrideCapabilityEntries(cwd) {
|
|
1512
|
+
const local = defaultCapabilityGraphs(cwd).filter(graph => graph !== 'framework')
|
|
1513
|
+
.flatMap(graph => loadLocalCapabilityEntries(cwd, graph));
|
|
1514
|
+
if (local.length)
|
|
1515
|
+
return local;
|
|
1516
|
+
const canonical = (directory) => {
|
|
1517
|
+
try {
|
|
1518
|
+
return fs.realpathSync(directory);
|
|
1464
1519
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
if (legacy)
|
|
1468
|
-
entries.push(...Object.values(legacy.entries));
|
|
1520
|
+
catch {
|
|
1521
|
+
return path.resolve(directory);
|
|
1469
1522
|
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1523
|
+
};
|
|
1524
|
+
const packageRoot = canonical(resolveFrameworkIndexRoot(cwd));
|
|
1525
|
+
// Retain the legacy PROJECT index, never use the package's full index as a fallback.
|
|
1526
|
+
if (canonical(cwd) === packageRoot)
|
|
1527
|
+
return [];
|
|
1528
|
+
const legacy = loadMetadataIndex(cwd);
|
|
1529
|
+
return Object.values(legacy?.entries ?? {}).filter(entry => {
|
|
1530
|
+
const provenance = entry;
|
|
1531
|
+
if (provenance.indexGraph === 'framework' || provenance.indexScope === 'packaged'
|
|
1532
|
+
|| normalizeIndexedPath(entry.path).startsWith('agentic/code/'))
|
|
1533
|
+
return false;
|
|
1534
|
+
const relative = path.relative(packageRoot, path.resolve(cwd, entry.path));
|
|
1535
|
+
return relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative);
|
|
1536
|
+
}).map(entry => withIndexProvenance(entry, 'project'));
|
|
1472
1537
|
}
|
|
1473
1538
|
function findShowMatches(entries, types, needle) {
|
|
1474
1539
|
const needleLower = needle.toLowerCase();
|
|
@@ -1538,6 +1603,57 @@ function findShowMatches(entries, types, needle) {
|
|
|
1538
1603
|
}
|
|
1539
1604
|
return matches;
|
|
1540
1605
|
}
|
|
1606
|
+
/** Preselect exact candidates once; the canonical resolver still owns precedence and ambiguity. */
|
|
1607
|
+
export function createRuleBindingTargetResolver(entries) {
|
|
1608
|
+
const candidates = new Map();
|
|
1609
|
+
for (const entry of entries) {
|
|
1610
|
+
if (!['skill', 'agent', 'flow'].includes(entry.type))
|
|
1611
|
+
continue;
|
|
1612
|
+
const normalized = normalizeIndexedPath(entry.path);
|
|
1613
|
+
const basename = path.posix.basename(normalized);
|
|
1614
|
+
const slug = /^(?:SKILL|AGENT|FLOW)\.md$/i.test(basename)
|
|
1615
|
+
? path.posix.basename(path.posix.dirname(normalized))
|
|
1616
|
+
: basename.replace(/\.[^.]+$/, '');
|
|
1617
|
+
for (const id of new Set([entry.name, discoveryIdForEntry(entry), slug])) {
|
|
1618
|
+
if (typeof id !== 'string')
|
|
1619
|
+
continue;
|
|
1620
|
+
const key = `${entry.type}\0${id}`;
|
|
1621
|
+
const selected = candidates.get(key) ?? [];
|
|
1622
|
+
selected.push(entry);
|
|
1623
|
+
candidates.set(key, selected);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return (type, id) => resolveRuleBindingTarget(candidates.get(`${type}\0${id}`) ?? [], type, id);
|
|
1627
|
+
}
|
|
1628
|
+
/** Resolve exact rule binding IDs using the same precedence and ambiguity as show.
|
|
1629
|
+
* Canonical layout slugs remain valid when frontmatter names are display names.
|
|
1630
|
+
* Titles and filesystem paths are deliberately excluded (#2938).
|
|
1631
|
+
*/
|
|
1632
|
+
export function resolveRuleBindingTarget(entries, type, id) {
|
|
1633
|
+
const exact = entries.filter(entry => {
|
|
1634
|
+
if (entry.type !== type)
|
|
1635
|
+
return false;
|
|
1636
|
+
const normalized = normalizeIndexedPath(entry.path);
|
|
1637
|
+
const basename = path.posix.basename(normalized);
|
|
1638
|
+
const slug = /^(?:SKILL|AGENT|FLOW)\.md$/i.test(basename)
|
|
1639
|
+
? path.posix.basename(path.posix.dirname(normalized))
|
|
1640
|
+
: basename.replace(/\.[^.]+$/, '');
|
|
1641
|
+
return entry.name === id || discoveryIdForEntry(entry) === id || slug === id;
|
|
1642
|
+
});
|
|
1643
|
+
let matches = findShowMatches(exact, [type], id);
|
|
1644
|
+
// Generated plugin copies (agentic/code/plugins/*) mirror canonical framework
|
|
1645
|
+
// and addon artifacts. Prefer the canonical source when it is unambiguous.
|
|
1646
|
+
if (matches.length > 1) {
|
|
1647
|
+
const canonical = matches.filter(entry => !/(^|\/)agentic\/code\/plugins\//.test(normalizeIndexedPath(entry.path)));
|
|
1648
|
+
if (canonical.length === 1)
|
|
1649
|
+
matches = canonical;
|
|
1650
|
+
}
|
|
1651
|
+
if (matches.length === 0)
|
|
1652
|
+
throw new Error(`Missing ${type} binding target: ${id}`);
|
|
1653
|
+
if (matches.length > 1)
|
|
1654
|
+
throw new Error(`Ambiguous ${type} binding target: ${id}: ${matches.map(entry => entry.path).join(', ')}`);
|
|
1655
|
+
return matches[0];
|
|
1656
|
+
}
|
|
1541
1657
|
async function fortemiRecordForEntry(cwd, entry, sourcePaths) {
|
|
1542
1658
|
const graph = (entry.indexGraph ?? 'project');
|
|
1543
1659
|
const { loadFortemiCoreExport } = await import('./fortemi-core-query-adapter.js');
|
|
@@ -1762,6 +1878,178 @@ async function findAgentInCorpus(agentsRoot, name, depth = 0) {
|
|
|
1762
1878
|
}
|
|
1763
1879
|
return null;
|
|
1764
1880
|
}
|
|
1881
|
+
/** Build an ephemeral reverse lookup from the already-loaded rule metadata. */
|
|
1882
|
+
export function rulesBoundTo(entries, target) {
|
|
1883
|
+
const key = { skill: 'skills', agent: 'agents', flow: 'flows' }[target.type];
|
|
1884
|
+
if (!key)
|
|
1885
|
+
return [];
|
|
1886
|
+
const file = path.basename(normalizeIndexedPath(target.path));
|
|
1887
|
+
const aliases = [target.name, target.title, target.path, discoveryIdForEntry(target),
|
|
1888
|
+
file === 'SKILL.md' ? path.basename(path.dirname(normalizeIndexedPath(target.path))) : file.replace(/\.[^.]+$/, '')]
|
|
1889
|
+
.filter((alias) => !!alias);
|
|
1890
|
+
const seen = new Set();
|
|
1891
|
+
return entries.filter(rule => {
|
|
1892
|
+
if (rule.type !== 'rule' && rule.type !== 'behavior')
|
|
1893
|
+
return false;
|
|
1894
|
+
const patterns = rule.bound_to?.[key];
|
|
1895
|
+
if (!Array.isArray(patterns) || !patterns.some(pattern => typeof pattern === 'string'
|
|
1896
|
+
&& aliases.some(alias => alias === pattern || minimatch(alias, pattern, { nonegate: true, nocomment: true }))))
|
|
1897
|
+
return false;
|
|
1898
|
+
const identity = `${rule.type}:${rule.path}`;
|
|
1899
|
+
if (seen.has(identity))
|
|
1900
|
+
return false;
|
|
1901
|
+
seen.add(identity);
|
|
1902
|
+
return true;
|
|
1903
|
+
}).sort((a, b) => (a.name ?? a.path).localeCompare(b.name ?? b.path) || a.path.localeCompare(b.path));
|
|
1904
|
+
}
|
|
1905
|
+
/** Relationship metadata for shipped targets, merged with project/user contracts. */
|
|
1906
|
+
export function relationshipRules(entries, target, relations) {
|
|
1907
|
+
const local = rulesBoundTo(relations ? entries.filter(entry => !normalizeIndexedPath(entry.path).startsWith('agentic/code/')) : entries, target);
|
|
1908
|
+
if (!relations || !['skill', 'agent', 'flow', 'command'].includes(target.type))
|
|
1909
|
+
return local;
|
|
1910
|
+
const asMetadata = (id) => {
|
|
1911
|
+
const rule = relations.index.rules[id];
|
|
1912
|
+
return { path: rule.path, type: rule.type, name: rule.name, activation: rule.activation ?? undefined,
|
|
1913
|
+
bound_to: rule.bound_to, ruleSummary: rule.summary, ruleEnforcement: rule.enforcement ?? undefined, rulePaths: rule.paths, triggers: rule.triggers };
|
|
1914
|
+
};
|
|
1915
|
+
const key = `${target.type}:${discoveryIdForEntry(target)}`;
|
|
1916
|
+
const shipped = Object.hasOwn(relations.index.targets, key)
|
|
1917
|
+
? relations.rulesForTarget(target.type, discoveryIdForEntry(target)).map(asMetadata)
|
|
1918
|
+
// Project/user overrides keep the old alias/glob behavior using only small records.
|
|
1919
|
+
: rulesBoundTo(Object.keys(relations.index.rules).map(asMetadata), target);
|
|
1920
|
+
const merged = new Map([...local, ...shipped].map(rule => [`${rule.type}:${rule.path}`, rule]));
|
|
1921
|
+
return [...merged.values()].sort((a, b) => (a.name ?? a.path).localeCompare(b.name ?? b.path) || a.path.localeCompare(b.path));
|
|
1922
|
+
}
|
|
1923
|
+
export const RULE_CHECK_OBLIGATION = 'Before governed work, fetch and follow every required rule below; a discovery summary is not the full rule.';
|
|
1924
|
+
function relationMetadata(relations) {
|
|
1925
|
+
return Object.values(relations.index.rules).map(rule => ({ path: rule.path, type: rule.type, name: rule.name,
|
|
1926
|
+
activation: rule.activation ?? undefined, ruleSummary: rule.summary, ruleEnforcement: rule.enforcement ?? undefined,
|
|
1927
|
+
bound_to: rule.bound_to, rulePaths: rule.paths, triggers: rule.triggers }));
|
|
1928
|
+
}
|
|
1929
|
+
/** Post-ranking enrichment: relationship lookup never loads the full metadata index. */
|
|
1930
|
+
export function compoundGoverningRules(entries, target, relations, phrase = '', includeMatches = true, excludeKernel = true) {
|
|
1931
|
+
if (!['skill', 'agent', 'flow', 'command'].includes(target.type))
|
|
1932
|
+
return [];
|
|
1933
|
+
const found = new Map();
|
|
1934
|
+
const add = (rule, why) => {
|
|
1935
|
+
const activation = rule.activation ?? (rule.type === 'behavior' ? 'kernel' : inferRuleActivation(rule.ruleEnforcement));
|
|
1936
|
+
if (excludeKernel && (activation === 'kernel' || rule.kernel || relations?.pinnedRuleIds.has(discoveryIdForEntry(rule))))
|
|
1937
|
+
return;
|
|
1938
|
+
const key = `${rule.type}:${rule.path}`, item = found.get(key) ?? { rule, applicability: [] };
|
|
1939
|
+
if (!item.applicability.includes(why))
|
|
1940
|
+
item.applicability.push(why);
|
|
1941
|
+
found.set(key, item);
|
|
1942
|
+
};
|
|
1943
|
+
for (const rule of relationshipRules(entries, target, relations))
|
|
1944
|
+
add(rule, `bound:${target.type}:${discoveryIdForEntry(target)}`);
|
|
1945
|
+
if (relations && target.type === 'skill' && target.name?.startsWith('projected:')) {
|
|
1946
|
+
for (const id of relations.rulesForTarget('skill', target.name)) {
|
|
1947
|
+
const rule = relationMetadata(relations).find(rule => discoveryIdForEntry(rule) === id);
|
|
1948
|
+
if (rule)
|
|
1949
|
+
add(rule, `projected-flow:${target.name.slice('projected:'.length)}`);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
if (includeMatches) {
|
|
1953
|
+
const query = phrase.toLowerCase();
|
|
1954
|
+
const queryPaths = (phrase.replace(/\\/g, '/').match(/[\w@./*?-]+/g) ?? []).map(value => value.replace(/[.,;:!?]+$/, ''));
|
|
1955
|
+
const corpus = relations ? relationMetadata(relations) : [];
|
|
1956
|
+
const rules = new Map([...corpus, ...entries.filter(entry => ['rule', 'behavior'].includes(entry.type))]
|
|
1957
|
+
.map(rule => [`${rule.type}:${rule.path}`, rule]));
|
|
1958
|
+
for (const rule of rules.values()) {
|
|
1959
|
+
for (const glob of rule.rulePaths ?? [])
|
|
1960
|
+
for (const candidate of [...queryPaths.filter(value => /[./*?]/.test(value) || value === glob), ...(target.governedPaths ?? [])]) {
|
|
1961
|
+
if (minimatch(candidate, glob, { dot: true, nonegate: true, nocomment: true })
|
|
1962
|
+
|| ((target.governedPaths ?? []).includes(candidate) && minimatch(glob, candidate, { dot: true, nonegate: true, nocomment: true })))
|
|
1963
|
+
add(rule, `path:${candidate}`);
|
|
1964
|
+
}
|
|
1965
|
+
for (const trigger of rule.triggers ?? []) {
|
|
1966
|
+
const normalized = trigger.trim().toLowerCase();
|
|
1967
|
+
let offset = normalized ? query.indexOf(normalized) : -1;
|
|
1968
|
+
while (offset >= 0) {
|
|
1969
|
+
if ((offset === 0 || !/\w/.test(query[offset - 1]))
|
|
1970
|
+
&& (offset + normalized.length === query.length || !/\w/.test(query[offset + normalized.length]))) {
|
|
1971
|
+
add(rule, `trigger:${trigger}`);
|
|
1972
|
+
break;
|
|
1973
|
+
}
|
|
1974
|
+
offset = query.indexOf(normalized, offset + 1);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return [...found.values()].sort((a, b) => (a.rule.name ?? a.rule.path).localeCompare(b.rule.name ?? b.rule.path) || a.rule.path.localeCompare(b.rule.path));
|
|
1980
|
+
}
|
|
1981
|
+
function boundRuleDescriptor(rule) {
|
|
1982
|
+
return { name: rule.name ?? path.basename(rule.path, '.md'), activation: rule.activation ?? null,
|
|
1983
|
+
summary: rule.ruleSummary ?? '', show_command: `aiwg show ${rule.type} ${rule.name ?? path.basename(rule.path, '.md')}` };
|
|
1984
|
+
}
|
|
1985
|
+
/** Counts unique attached contracts; every emitted check is required irrespective of severity. */
|
|
1986
|
+
export function governingRuleStats(governing) {
|
|
1987
|
+
const stats = emptyRuleStats();
|
|
1988
|
+
for (const { rule } of new Map(governing.map(item => [`${item.rule.type}:${item.rule.path}`, item])).values()) {
|
|
1989
|
+
const enforcement = rule.ruleEnforcement?.toLowerCase() ?? 'unknown', activation = rule.activation ?? 'unknown';
|
|
1990
|
+
stats.by_enforcement[enforcement] = (stats.by_enforcement[enforcement] ?? 0) + 1;
|
|
1991
|
+
stats.by_activation[activation] = (stats.by_activation[activation] ?? 0) + 1;
|
|
1992
|
+
stats.required_checks++;
|
|
1993
|
+
}
|
|
1994
|
+
return stats;
|
|
1995
|
+
}
|
|
1996
|
+
export function governingRuleFields(governing) {
|
|
1997
|
+
if (!governing.length)
|
|
1998
|
+
return {};
|
|
1999
|
+
return { bound_rules: governing.map(({ rule }) => boundRuleDescriptor(rule)), rule_stats: governingRuleStats(governing),
|
|
2000
|
+
required_rule_checks: governing.map(({ rule, applicability }) => ({ rule_id: discoveryIdForEntry(rule),
|
|
2001
|
+
applicability, fetch_command: `aiwg show ${rule.type} ${discoveryIdForEntry(rule)}` })), rule_check_obligation: RULE_CHECK_OBLIGATION };
|
|
2002
|
+
}
|
|
2003
|
+
function statsText(stats) {
|
|
2004
|
+
return `rules: ${Object.entries(stats.by_enforcement).map(([level, count]) => `${count} ${level}`).join(', ') || '0'} · activation: ${Object.entries(stats.by_activation).map(([mode, count]) => `${count} ${mode}`).join(', ') || 'none'} · ${stats.required_checks} required checks`;
|
|
2005
|
+
}
|
|
2006
|
+
export function relationshipRuleStats(entries, target, relations) {
|
|
2007
|
+
const shipped = Object.hasOwn(relations.index.targets, `${target.type}:${discoveryIdForEntry(target)}`);
|
|
2008
|
+
const stats = shipped ? relations.ruleStats(target.type, discoveryIdForEntry(target)) : emptyRuleStats();
|
|
2009
|
+
// Shipped counts are precomputed; project/user contracts and overrides are overlays.
|
|
2010
|
+
const overlay = shipped ? rulesBoundTo(entries.filter(entry => !normalizeIndexedPath(entry.path).startsWith('agentic/code/')), target)
|
|
2011
|
+
: relationshipRules(entries, target, relations);
|
|
2012
|
+
for (const rule of overlay) {
|
|
2013
|
+
const enforcement = rule.ruleEnforcement ?? 'unknown';
|
|
2014
|
+
const activation = rule.activation ?? 'unknown';
|
|
2015
|
+
stats.by_enforcement[enforcement] = (stats.by_enforcement[enforcement] ?? 0) + 1;
|
|
2016
|
+
stats.by_activation[activation] = (stats.by_activation[activation] ?? 0) + 1;
|
|
2017
|
+
if (enforcement === 'critical' || enforcement === 'high')
|
|
2018
|
+
stats.required_checks++;
|
|
2019
|
+
}
|
|
2020
|
+
return stats;
|
|
2021
|
+
}
|
|
2022
|
+
async function showBindings(entries, target, content, params, read, relations) {
|
|
2023
|
+
const governing = compoundGoverningRules(entries, target, relations, '', false, false);
|
|
2024
|
+
const rules = governing.map(item => item.rule);
|
|
2025
|
+
const boundRules = await Promise.all(rules.map(async (rule) => ({
|
|
2026
|
+
...boundRuleDescriptor(rule),
|
|
2027
|
+
...(params.boundRuleBodies ? { content: await read(rule) } : {}),
|
|
2028
|
+
})));
|
|
2029
|
+
// Parse the selected rule for reverse lookup, including corpus-only rules.
|
|
2030
|
+
const targets = target.type === 'rule' || target.type === 'behavior'
|
|
2031
|
+
? parseFrontmatter(content).data.bound_to : undefined;
|
|
2032
|
+
const boundTo = targets && typeof targets === 'object' && !Array.isArray(targets)
|
|
2033
|
+
? Object.fromEntries(['skills', 'agents', 'flows'].flatMap(key => {
|
|
2034
|
+
const values = targets[key];
|
|
2035
|
+
const names = Array.isArray(values) ? [...new Set(values.filter((value) => typeof value === 'string' && !!value.trim()))] : [];
|
|
2036
|
+
return names.length ? [[key, names]] : [];
|
|
2037
|
+
})) : {};
|
|
2038
|
+
let text = '';
|
|
2039
|
+
if (boundRules.length) {
|
|
2040
|
+
text += `\n## Bound rules\n\n${RULE_CHECK_OBLIGATION}\n\n${statsText(governingRuleStats(governing))}\n\n` + boundRules.map(rule => `- **${rule.name}**: ${rule.summary} (\`${rule.show_command}\`)`
|
|
2041
|
+
+ ('content' in rule ? `\n\n${rule.content}` : '')).join('\n') + '\n';
|
|
2042
|
+
}
|
|
2043
|
+
if (Object.keys(boundTo).length) {
|
|
2044
|
+
text += '\n## Bound targets\n\n' + Object.entries(boundTo)
|
|
2045
|
+
.map(([key, names]) => `- ${key}: ${names.map(name => `\`${name}\``).join(', ')}`).join('\n') + '\n';
|
|
2046
|
+
}
|
|
2047
|
+
return { fields: {
|
|
2048
|
+
...governingRuleFields(governing),
|
|
2049
|
+
...(boundRules.length ? { bound_rules: boundRules } : {}),
|
|
2050
|
+
...(Object.keys(boundTo).length ? { bound_to: boundTo } : {}),
|
|
2051
|
+
}, text };
|
|
2052
|
+
}
|
|
1765
2053
|
/**
|
|
1766
2054
|
* Read and emit the full text of a specific artifact (typically a
|
|
1767
2055
|
* SKILL.md). Consumers don't need to know where AIWG stores skills —
|
|
@@ -1785,7 +2073,7 @@ export async function showArtifact(cwd, params) {
|
|
|
1785
2073
|
const types = params.typeFilter && params.typeFilter.length > 0
|
|
1786
2074
|
? params.typeFilter
|
|
1787
2075
|
: [...OPERATIONAL_SHOW_TYPES];
|
|
1788
|
-
const { entries, aiwgRoot, resourceSource, webRelease, } = await loadShowEntries(cwd, params);
|
|
2076
|
+
const { entries, aiwgRoot, resourceSource, webRelease, relations, } = await loadShowEntries(cwd, params, true);
|
|
1789
2077
|
if (entries.length === 0 && params.backend !== 'fortemi-core') {
|
|
1790
2078
|
console.error('Error: No artifact index found.');
|
|
1791
2079
|
console.error('Run `aiwg index build --graph framework` (or `aiwg use <framework>`) first.');
|
|
@@ -1796,7 +2084,7 @@ export async function showArtifact(cwd, params) {
|
|
|
1796
2084
|
if (matches.length === 0) {
|
|
1797
2085
|
// Corpus fallback (#1221): when the artifact isn't in any indexed graph,
|
|
1798
2086
|
// scan the AIWG_ROOT corpus for a likely match and render it with an
|
|
1799
|
-
//
|
|
2087
|
+
// installation-aware banner. This keeps `aiwg show` useful in workspaces
|
|
1800
2088
|
// where the operator hasn't run `aiwg use` yet, or where the framework
|
|
1801
2089
|
// index is stale, rather than exiting with a misleading "not found".
|
|
1802
2090
|
if (resourceSource === 'local' && aiwgRoot && params.backend !== 'fortemi-core') {
|
|
@@ -1811,34 +2099,49 @@ export async function showArtifact(cwd, params) {
|
|
|
1811
2099
|
console.error(`Error reading ${corpusMatch.path}: ${e.message ?? String(err)}`);
|
|
1812
2100
|
process.exit(1);
|
|
1813
2101
|
}
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
|
|
2102
|
+
const localPin = corpusMatch.type === 'rule' ? (await import('../rules/pin-state.js')).rulePinState(cwd, aiwgRoot).rules.find(rule => rule.path === corpusMatch.path) : undefined;
|
|
2103
|
+
const installed = await isCorpusBundleInstalled(cwd, aiwgRoot, corpusMatch.bundleKind, corpusMatch.bundleId);
|
|
2104
|
+
const installHint = installed
|
|
2105
|
+
? 'The installed bundle is missing from this index. Run `aiwg index build --graph framework` to refresh it.'
|
|
2106
|
+
: corpusMatch.bundleKind && corpusMatch.bundleId
|
|
2107
|
+
? `Run \`aiwg use ${corpusMatch.bundleId}\` to install this ${corpusMatch.bundleKind} into the workspace.`
|
|
2108
|
+
: 'Run `aiwg index build --graph framework` to refresh the artifact index.';
|
|
2109
|
+
const bindings = await showBindings(entries, {
|
|
2110
|
+
path: corpusMatch.path, type: corpusMatch.type, name: needle, title: needle,
|
|
2111
|
+
}, content, params, rule => fs.readFile(resolveMetadataPath(cwd, aiwgRoot, rule), 'utf8'), relations);
|
|
1817
2112
|
if (params.json) {
|
|
1818
2113
|
console.log(JSON.stringify({
|
|
2114
|
+
...bindings.fields,
|
|
1819
2115
|
path: corpusMatch.path,
|
|
1820
2116
|
type: corpusMatch.type,
|
|
1821
2117
|
title: needle,
|
|
1822
2118
|
kernel: false,
|
|
1823
|
-
uninstalled:
|
|
2119
|
+
uninstalled: !installed,
|
|
2120
|
+
...(localPin ? { local_pin: localPin } : {}),
|
|
1824
2121
|
hint: installHint,
|
|
1825
2122
|
content,
|
|
1826
2123
|
}, null, 2));
|
|
1827
2124
|
return;
|
|
1828
2125
|
}
|
|
1829
|
-
// Plain text mode:
|
|
1830
|
-
//
|
|
2126
|
+
// Plain text mode: distinguish a missing index entry from an
|
|
2127
|
+
// uninstalled bundle, then stream the body.
|
|
1831
2128
|
const banner = [
|
|
1832
|
-
|
|
2129
|
+
installed
|
|
2130
|
+
? '<!-- AIWG: installed bundle artifact — not in the selected index.'
|
|
2131
|
+
: '<!-- AIWG: corpus-only artifact — not in the indexed workspace.',
|
|
1833
2132
|
` ${installHint}`,
|
|
1834
2133
|
' Origin: ' + corpusMatch.path,
|
|
1835
2134
|
'-->',
|
|
1836
2135
|
'',
|
|
1837
2136
|
].join('\n');
|
|
1838
2137
|
process.stdout.write(banner);
|
|
2138
|
+
if (localPin)
|
|
2139
|
+
process.stdout.write(`Pinned locally: kernel (canonical ${localPin.canonical_activation}; ${localPin.matches.map(match => match.source).join(', ')}).\n`);
|
|
1839
2140
|
process.stdout.write(content);
|
|
1840
2141
|
if (!content.endsWith('\n'))
|
|
1841
2142
|
process.stdout.write('\n');
|
|
2143
|
+
if (bindings.text)
|
|
2144
|
+
process.stdout.write(bindings.text);
|
|
1842
2145
|
return;
|
|
1843
2146
|
}
|
|
1844
2147
|
}
|
|
@@ -1925,6 +2228,20 @@ export async function showArtifact(cwd, params) {
|
|
|
1925
2228
|
process.exit(1);
|
|
1926
2229
|
}
|
|
1927
2230
|
}
|
|
2231
|
+
const localPin = entry.type === 'rule' ? (await import('../rules/pin-state.js')).rulePinState(cwd, aiwgRoot ?? cwd).rules.find(rule => rule.path === filePath) : undefined;
|
|
2232
|
+
const bindings = await showBindings(entries, entry, content, params, async (rule) => {
|
|
2233
|
+
if (resourceSource === 'local')
|
|
2234
|
+
return fs.readFile(resolvePath(rule), 'utf8');
|
|
2235
|
+
const { logicalIdFromFirstPartyPath, resolveAiwgResourceBytes } = await import('../resources/resolver.js');
|
|
2236
|
+
const logicalId = logicalIdFromFirstPartyPath(rule.path);
|
|
2237
|
+
if (!logicalId || !webRelease)
|
|
2238
|
+
throw new Error(`Cannot resolve bound rule: ${rule.path}`);
|
|
2239
|
+
const resolved = await resolveAiwgResourceBytes(logicalId, {
|
|
2240
|
+
source: 'web', frameworkRoot: aiwgRoot ?? cwd, webRelease,
|
|
2241
|
+
webReleaseOptions: params.webReleaseOptions, offline: params.offline,
|
|
2242
|
+
});
|
|
2243
|
+
return resolved.bytes.toString('utf8');
|
|
2244
|
+
}, relations);
|
|
1928
2245
|
if (params.json) {
|
|
1929
2246
|
const providerModels = resourceSource === 'local'
|
|
1930
2247
|
? await loadProviderModelMetadata(cwd, aiwgRoot)
|
|
@@ -1956,21 +2273,26 @@ export async function showArtifact(cwd, params) {
|
|
|
1956
2273
|
? { executable: true, run_hint: buildRunHint(entry) }
|
|
1957
2274
|
: {}),
|
|
1958
2275
|
...(providerModels ? { providerModels } : {}),
|
|
2276
|
+
...bindings.fields,
|
|
2277
|
+
...(localPin ? { local_pin: localPin } : {}),
|
|
1959
2278
|
content,
|
|
1960
2279
|
}, null, 2));
|
|
1961
2280
|
return;
|
|
1962
2281
|
}
|
|
1963
|
-
//
|
|
1964
|
-
// (agent or operator) sees exactly what the source authored.
|
|
2282
|
+
// Stream the authored content first; append binding guidance only when present.
|
|
1965
2283
|
// #1227 — for script-bearing skills, prepend a one-line banner so
|
|
1966
2284
|
// agents reading the body immediately see the skill is executable.
|
|
1967
2285
|
if (entry.script) {
|
|
1968
2286
|
const hint = buildRunHint(entry);
|
|
1969
2287
|
process.stdout.write(`<!-- AIWG: executable skill — run via: ${hint} -->\n`);
|
|
1970
2288
|
}
|
|
2289
|
+
if (localPin)
|
|
2290
|
+
process.stdout.write(`Pinned locally: kernel (canonical ${localPin.canonical_activation}; ${localPin.matches.map(match => match.source).join(', ')}).\n`);
|
|
1971
2291
|
process.stdout.write(content);
|
|
1972
2292
|
if (!content.endsWith('\n'))
|
|
1973
2293
|
process.stdout.write('\n');
|
|
2294
|
+
if (bindings.text)
|
|
2295
|
+
process.stdout.write(bindings.text);
|
|
1974
2296
|
}
|
|
1975
2297
|
export async function showMetadata(cwd, params) {
|
|
1976
2298
|
loadGlobalGraphConfigs();
|