@aiwg/cli 2026.10.5 → 2026.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +3 -1
  6. package/dist/src/artifacts/browser-export.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +8 -1
  8. package/dist/src/artifacts/cli.js +57 -6
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  10. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  11. package/dist/src/artifacts/graph-query.js +18 -8
  12. package/dist/src/artifacts/index-adapters.js +339 -0
  13. package/dist/src/artifacts/index-builder.js +143 -14
  14. package/dist/src/artifacts/index-files.js +15 -4
  15. package/dist/src/artifacts/index-reader.js +44 -0
  16. package/dist/src/artifacts/index-refresh.js +114 -55
  17. package/dist/src/artifacts/index-status.js +20 -5
  18. package/dist/src/artifacts/memory-maintenance.js +503 -0
  19. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  20. package/dist/src/artifacts/query-engine.js +263 -108
  21. package/dist/src/artifacts/relations-builder.js +2 -2
  22. package/dist/src/artifacts/source-graph.js +2 -2
  23. package/dist/src/artifacts/types.js +9 -1
  24. package/dist/src/cli/handlers/decision.js +19 -1
  25. package/dist/src/cli/handlers/guided-setup.js +470 -0
  26. package/dist/src/cli/handlers/help.js +2 -2
  27. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  28. package/dist/src/cli/handlers/index.js +4 -2
  29. package/dist/src/cli/handlers/init.js +41 -6
  30. package/dist/src/cli/handlers/local-executor.js +5 -3
  31. package/dist/src/cli/handlers/mc.js +509 -53
  32. package/dist/src/cli/handlers/models.js +32 -2
  33. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  34. package/dist/src/cli/handlers/rules.js +1 -1
  35. package/dist/src/cli/handlers/script-runner.js +73 -2
  36. package/dist/src/cli/handlers/serve.js +5 -3
  37. package/dist/src/cli/handlers/sessions.js +1 -0
  38. package/dist/src/cli/handlers/setup.js +428 -1
  39. package/dist/src/cli/handlers/skill-lint.js +1 -1
  40. package/dist/src/cli/handlers/subcommands.js +118 -8
  41. package/dist/src/cli/handlers/use.js +279 -93
  42. package/dist/src/cli/handlers/workspace.js +3 -1
  43. package/dist/src/cli/prompt-utils.js +8 -0
  44. package/dist/src/cli/router.js +16 -1
  45. package/dist/src/config/aiwg-config.js +42 -0
  46. package/dist/src/config/cli.js +7 -3
  47. package/dist/src/config/decision-config.js +14 -1
  48. package/dist/src/config/native-sandbox-warnings.js +25 -0
  49. package/dist/src/contributors/validation.js +6 -5
  50. package/dist/src/decision/admission.js +7 -3
  51. package/dist/src/decision/benchmark.js +469 -0
  52. package/dist/src/decision/ledger.js +22 -3
  53. package/dist/src/decision/pricing.js +147 -0
  54. package/dist/src/decision/qualification/gate-evidence.js +18 -15
  55. package/dist/src/decision/qualification/load-staged.js +539 -0
  56. package/dist/src/decision/qualification/load.js +109 -33
  57. package/dist/src/decision/qualification/runner.js +5 -2
  58. package/dist/src/decision/review/service.js +6 -4
  59. package/dist/src/decision/review/validate.js +23 -2
  60. package/dist/src/decision/savings.js +8 -3
  61. package/dist/src/discovery/index.js +492 -0
  62. package/dist/src/discovery/interview.js +233 -0
  63. package/dist/src/discovery/plan-apply.js +571 -0
  64. package/dist/src/discovery/plan-store.js +171 -0
  65. package/dist/src/discovery/plan-types.js +2 -0
  66. package/dist/src/discovery/plan-validation.js +139 -0
  67. package/dist/src/discovery/types.js +2 -0
  68. package/dist/src/extensions/commands/definitions.js +49 -9
  69. package/dist/src/extensions/manifest.js +71 -44
  70. package/dist/src/extensions/project-local-discovery.js +1 -1
  71. package/dist/src/extensions/project-quickref.js +18 -17
  72. package/dist/src/extensions/validation.js +32 -29
  73. package/dist/src/housekeeper/daemon-config.js +148 -0
  74. package/dist/src/housekeeper/ipc.js +84 -0
  75. package/dist/src/housekeeper/lifecycle.js +148 -0
  76. package/dist/src/housekeeper/platform.js +35 -0
  77. package/dist/src/housekeeper/status.js +70 -0
  78. package/dist/src/housekeeper/units.js +114 -0
  79. package/dist/src/jobs/flow.js +22 -21
  80. package/dist/src/lint/rule-activation.js +20 -5
  81. package/dist/src/lint/runner.js +1 -1
  82. package/dist/src/mcp/cli.mjs +5 -6
  83. package/dist/src/mcp/config-file.mjs +6 -2
  84. package/dist/src/mcp/config-layers.mjs +23 -7
  85. package/dist/src/mcp/profiles.js +18 -31
  86. package/dist/src/mcp/profiles.mjs +18 -32
  87. package/dist/src/mcp/registry.js +7 -27
  88. package/dist/src/mcp/registry.mjs +7 -28
  89. package/dist/src/mcp/server.mjs +34 -1
  90. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  91. package/dist/src/mcp/tools/command-run.mjs +3 -2
  92. package/dist/src/mcp/tools/decision.mjs +49 -48
  93. package/dist/src/mcp/tools/discovery.mjs +46 -21
  94. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  95. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  96. package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
  97. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  98. package/dist/src/memory/audit.js +719 -0
  99. package/dist/src/memory/canonical-context.js +392 -13
  100. package/dist/src/memory/canonical-maintenance.js +95 -0
  101. package/dist/src/memory/cli-result.js +22 -0
  102. package/dist/src/memory/cli.js +176 -0
  103. package/dist/src/memory/context-pack.js +57 -6
  104. package/dist/src/memory/cost.js +512 -0
  105. package/dist/src/memory/firewall-classification.d.mts +7 -0
  106. package/dist/src/memory/firewall-classification.mjs +32 -0
  107. package/dist/src/memory/index.js +8 -0
  108. package/dist/src/memory/line-maintenance.js +68 -0
  109. package/dist/src/memory/maintenance-cli.js +286 -0
  110. package/dist/src/memory/maintenance-contract.js +136 -0
  111. package/dist/src/memory/maintenance-index.js +18 -0
  112. package/dist/src/memory/maintenance-owner.js +2 -0
  113. package/dist/src/memory/maintenance-record.js +138 -0
  114. package/dist/src/memory/maintenance.js +254 -0
  115. package/dist/src/memory/provider-memory-adapter.js +154 -0
  116. package/dist/src/memory/recommend-cli.js +83 -0
  117. package/dist/src/memory/recommend.js +269 -0
  118. package/dist/src/memory/scheduled.js +81 -0
  119. package/dist/src/memory/taxonomy.js +44 -0
  120. package/dist/src/mission-protocol/codecs.js +19 -9
  121. package/dist/src/models/config-loader.js +11 -1
  122. package/dist/src/models/model-capabilities.v1.json +74 -50
  123. package/dist/src/models/provider-policy.js +125 -62
  124. package/dist/src/models/resolver.js +45 -8
  125. package/dist/src/models/worker-installation.d.mts +7 -0
  126. package/dist/src/models/worker-installation.mjs +169 -37
  127. package/dist/src/models/wrapper-route.js +1 -1
  128. package/dist/src/output-modes/registry.js +8 -7
  129. package/dist/src/plugin/metadata-validator.js +1 -1
  130. package/dist/src/providers/deploy-version.d.mts +1 -0
  131. package/dist/src/providers/deploy-version.mjs +17 -0
  132. package/dist/src/providers/provider-definitions.js +19 -16
  133. package/dist/src/providers/skill-ownership.d.mts +11 -2
  134. package/dist/src/providers/skill-ownership.mjs +35 -17
  135. package/dist/src/rules/activation.d.mts +11 -2
  136. package/dist/src/rules/activation.mjs +82 -6
  137. package/dist/src/rules/inventory.js +1 -1
  138. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  139. package/dist/src/schema/zod-validation.d.mts +6 -0
  140. package/dist/src/schema/zod-validation.mjs +50 -0
  141. package/dist/src/serve/executor-registry.js +297 -20
  142. package/dist/src/serve/executor-token.d.mts +1 -0
  143. package/dist/src/serve/executor-token.mjs +8 -0
  144. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  145. package/dist/src/serve/worker-invocation.js +114 -15
  146. package/dist/src/sessions/adapters/claude.js +19 -18
  147. package/dist/src/sessions/adapters/codex.js +10 -9
  148. package/dist/src/sessions/adapters/copilot.js +14 -13
  149. package/dist/src/sessions/adapters/cursor.js +19 -18
  150. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  151. package/dist/src/sessions/adapters/factory.js +11 -10
  152. package/dist/src/sessions/adapters/generic.js +32 -23
  153. package/dist/src/sessions/adapters/grokbot.js +35 -35
  154. package/dist/src/sessions/adapters/hermes.js +20 -19
  155. package/dist/src/sessions/adapters/muse.js +50 -49
  156. package/dist/src/sessions/adapters/omp.js +5 -4
  157. package/dist/src/sessions/adapters/openclaw.js +30 -29
  158. package/dist/src/sessions/adapters/opencode.js +26 -26
  159. package/dist/src/sessions/adapters/openhuman.js +24 -23
  160. package/dist/src/sessions/adapters/pi.js +5 -4
  161. package/dist/src/sessions/adapters/windsurf.js +8 -8
  162. package/dist/src/sessions/analytics.js +10 -9
  163. package/dist/src/sessions/candidates.js +7 -6
  164. package/dist/src/sessions/contracts.js +63 -53
  165. package/dist/src/sessions/output-registration.js +11 -10
  166. package/dist/src/sessions/workspace-discovery.js +3 -4
  167. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  168. package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
  169. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  170. package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
  171. package/dist/src/update/checker.d.mts +4 -0
  172. package/dist/src/update/checker.mjs +14 -0
  173. package/dist/src/writing/exemplar-selection.js +6 -6
  174. package/dist/src/writing/voice-evaluation.js +24 -23
  175. package/dist/src/writing/writer-migration.js +14 -13
  176. package/dist/src/writing/writer-profile.js +19 -18
  177. package/dist/src/writing/writing-brief.js +18 -17
  178. package/dist/src/writing/writing-receipt.js +47 -46
  179. package/package.json +5 -2
  180. package/schemas/decision/DecisionReview.schema.json +78 -1
  181. package/schemas/mission-v1.schema.json +3 -0
  182. package/tools/agents/deploy-agents.mjs +67 -39
  183. package/tools/agents/providers/antigravity.mjs +6 -1
  184. package/tools/agents/providers/base.mjs +19 -13
  185. package/tools/agents/providers/claude.mjs +1 -1
  186. package/tools/agents/providers/codex.mjs +50 -56
  187. package/tools/agents/providers/copilot.mjs +8 -2
  188. package/tools/agents/providers/cursor.mjs +1 -1
  189. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  190. package/tools/agents/providers/factory.mjs +7 -1
  191. package/tools/agents/providers/grok-build.mjs +18 -2
  192. package/tools/agents/providers/grokbot.mjs +1 -1
  193. package/tools/agents/providers/hermes.mjs +7 -13
  194. package/tools/agents/providers/muse-hooks.mjs +21 -14
  195. package/tools/agents/providers/muse.mjs +7 -2
  196. package/tools/agents/providers/omp.mjs +28 -4
  197. package/tools/agents/providers/opencode.mjs +14 -5
  198. package/tools/agents/providers/openhuman.mjs +1 -1
  199. package/tools/agents/providers/pi.mjs +1 -1
  200. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  201. package/tools/agents/providers/rule-delivery.mjs +263 -71
  202. package/tools/agents/providers/windsurf.mjs +7 -0
  203. package/tools/cli/workspace-status.mjs +5 -0
  204. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  205. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  206. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  207. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  208. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  209. package/tools/ralph-external/process-monitor.mjs +7 -4
  210. package/tools/ralph-external/session-launcher.mjs +280 -32
  211. package/tools/skills/deploy-skills-codex.mjs +11 -7
  212. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -18,7 +18,7 @@ import * as path from 'node:path';
18
18
  import { OPERATIONAL_DISCOVERY_TYPES, OPERATIONAL_SHOW_TYPES, isOperationalShowType, loadGlobalGraphConfigs, getGraphIndexDir, } from './types.js';
19
19
  import { loadMetadataIndex, loadGraphIndexFile } from './index-reader.js';
20
20
  import { bm25Rank } from './fulltext.js';
21
- import { parseFrontmatter } from './index-builder.js';
21
+ import { parseFrontmatter, parseFlowDoc, extractMarkdownLinks } from './index-builder.js';
22
22
  import { applyFacetFusion, diagnoseFacetActivations, } from './discover-facets.js';
23
23
  import { recordTypeForEntry, stableRecordId, } from './browser-export.js';
24
24
  import { loadProviderModelMetadata } from '../models/provider-models.js';
@@ -27,6 +27,13 @@ import { operationalStateQueryProjection, } from './operational-state.js';
27
27
  function normalizeIndexedPath(entryPath) {
28
28
  return entryPath.replace(/\\/g, '/');
29
29
  }
30
+ /** Historical records stay in the index for explicit show/history lookup. */
31
+ export function isOrdinaryMemoryEntry(entry, _cwd) {
32
+ // Maintenance owners publish dispositions into the index before returning.
33
+ // Retrieval must not read sources: ordinary legacy entries need no memory
34
+ // stamp, and temporary source I/O failures cannot hide active metadata.
35
+ return !entry.memoryRecord || (entry.memoryRecord.lifecycle === 'active' && entry.memoryRecord.placement !== 'archived');
36
+ }
30
37
  /**
31
38
  * Resolve an index entry's source file path and read its body (frontmatter
32
39
  * stripped). Entry paths are normally relative to the project; framework-graph
@@ -657,6 +664,8 @@ export async function queryIndex(cwd, params, options = {}) {
657
664
  }
658
665
  candidates = allEntries;
659
666
  }
667
+ // Retained tombstones are history, never ordinary retrieval results.
668
+ candidates = candidates.filter(entry => isOrdinaryMemoryEntry(entry, cwd));
660
669
  // Apply filters
661
670
  if (params.type) {
662
671
  candidates = candidates.filter(e => e.type === params.type);
@@ -704,8 +713,10 @@ export async function queryIndex(cwd, params, options = {}) {
704
713
  }
705
714
  process.exit(1);
706
715
  }
707
- results = queried.results.map(result => {
716
+ const candidateByPath = new Map(candidates.map(entry => [entry.path, entry]));
717
+ results = queried.results.filter(result => candidateByPath.has(result.path)).map(result => {
708
718
  const entry = {
719
+ ...(candidateByPath.get(result.path)?.memoryRecord ? { memoryRecord: candidateByPath.get(result.path).memoryRecord } : {}),
709
720
  path: result.path,
710
721
  type: result.type,
711
722
  phase: result.phase,
@@ -758,7 +769,7 @@ export async function queryIndex(cwd, params, options = {}) {
758
769
  }
759
770
  // Apply limit
760
771
  const limit = params.limit ?? 20;
761
- results = results.slice(0, limit);
772
+ results = results.filter(result => isOrdinaryMemoryEntry(result.entry, cwd)).slice(0, limit);
762
773
  const queryTimeMs = Date.now() - startTime;
763
774
  // Output
764
775
  if (options.json) {
@@ -1071,6 +1082,7 @@ export async function discoverCapability(cwd, params) {
1071
1082
  process.exit(1);
1072
1083
  }
1073
1084
  // Filter by type
1085
+ entries = entries.filter(entry => isOrdinaryMemoryEntry(entry, cwd));
1074
1086
  const candidates = entries.filter(e => types.includes(e.type));
1075
1087
  // Score (strict overlap gate). Keep the full sorted list so the facet
1076
1088
  // fusion below can inject/lift curated capabilities into the top-K before
@@ -1377,13 +1389,21 @@ async function loadShowEntries(cwd, params, compact = false) {
1377
1389
  if (compact && resourceSource !== 'web' && (!params.graph || params.graph === 'framework')) {
1378
1390
  relations = await ensureRelations(cwd) ?? undefined;
1379
1391
  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'));
1392
+ const reader = relations;
1393
+ const matched = findShowMatches(Object.values(reader.index.artifacts), params.typeFilter ?? [...OPERATIONAL_SHOW_TYPES], params.name.trim());
1394
+ const frameworkEntries = matched.map(entry => {
1395
+ const id = discoveryIdForEntry(entry);
1396
+ const rule = reader.index.rules[id];
1397
+ return withIndexProvenance({ ...entry, phase: '', tags: [], created: '', updated: '', checksum: '', summary: '', dependencies: [], dependents: [],
1398
+ ...(rule ? { activation: reader.pinnedRuleIds.has(id) ? 'kernel' : rule.activation ?? undefined,
1399
+ ruleSummary: rule.summary, ruleEnforcement: rule.enforcement ?? undefined, bound_to: rule.bound_to } : {}),
1400
+ }, 'framework');
1401
+ });
1383
1402
  const localEntries = params.graph ? [] : defaultCapabilityGraphs(cwd).filter(graph => graph !== 'framework')
1384
- .flatMap(graph => loadLocalCapabilityEntries(cwd, graph));
1403
+ .flatMap(graph => loadLocalCapabilityEntries(cwd, graph, undefined, true));
1385
1404
  const compactEntries = [...localEntries, ...frameworkEntries];
1386
- if (findShowMatches(compactEntries, params.typeFilter ?? [...OPERATIONAL_SHOW_TYPES], params.name.trim()).length) {
1405
+ if ((params.typeFilter?.every(type => ['skill', 'agent', 'flow', 'command', 'rule', 'behavior'].includes(type)))
1406
+ || findShowMatches(compactEntries, params.typeFilter ?? [...OPERATIONAL_SHOW_TYPES], params.name.trim()).length) {
1387
1407
  return { entries: compactEntries, aiwgRoot, resourceSource: 'local', relations };
1388
1408
  }
1389
1409
  }
@@ -1482,12 +1502,12 @@ async function loadShowEntries(cwd, params, compact = false) {
1482
1502
  }
1483
1503
  }
1484
1504
  else {
1485
- entries = loadLocalCapabilityEntries(cwd, params.graph);
1505
+ entries = loadLocalCapabilityEntries(cwd, params.graph, undefined, true);
1486
1506
  }
1487
1507
  return { entries, aiwgRoot, resourceSource: 'local', relations };
1488
1508
  }
1489
1509
  /** Local index inputs shared by show and exact rule-binding validation (#2938). */
1490
- export function loadLocalCapabilityEntries(cwd, graph, snapshot) {
1510
+ export function loadLocalCapabilityEntries(cwd, graph, snapshot, includeHistory = false) {
1491
1511
  const entries = [];
1492
1512
  for (const selectedGraph of graph ? [graph] : defaultCapabilityGraphs(cwd)) {
1493
1513
  const key = snapshot && `${selectedGraph}:${getGraphIndexDir(cwd, selectedGraph)}`;
@@ -1505,7 +1525,7 @@ export function loadLocalCapabilityEntries(cwd, graph, snapshot) {
1505
1525
  if (legacy)
1506
1526
  entries.push(...Object.values(legacy.entries));
1507
1527
  }
1508
- return entries;
1528
+ return includeHistory ? entries : entries.filter(entry => isOrdinaryMemoryEntry(entry, cwd));
1509
1529
  }
1510
1530
  /** Project/user inputs only; package artifacts come from a validated compact table. */
1511
1531
  export function loadLocalOverrideCapabilityEntries(cwd) {
@@ -1539,7 +1559,7 @@ function findShowMatches(entries, types, needle) {
1539
1559
  const needleLower = needle.toLowerCase();
1540
1560
  const normalizedNeedle = normalizeIndexedPath(needle);
1541
1561
  const candidates = entries.filter(e => types.includes(e.type));
1542
- let matches = candidates.filter(e => discoveryIdForEntry(e) === needle);
1562
+ let matches = needle.startsWith('aiwg:') ? candidates.filter(e => discoveryIdForEntry(e) === needle) : [];
1543
1563
  if (matches.length === 0) {
1544
1564
  matches = candidates.filter(e => e.path === needle || normalizeIndexedPath(e.path) === normalizedNeedle);
1545
1565
  }
@@ -1933,7 +1953,7 @@ export function compoundGoverningRules(entries, target, relations, phrase = '',
1933
1953
  const found = new Map();
1934
1954
  const add = (rule, why) => {
1935
1955
  const activation = rule.activation ?? (rule.type === 'behavior' ? 'kernel' : inferRuleActivation(rule.ruleEnforcement));
1936
- if (excludeKernel && (activation === 'kernel' || rule.kernel || relations?.pinnedRuleIds.has(discoveryIdForEntry(rule))))
1956
+ if (excludeKernel && (activation === 'kernel' || activation === 'core' || rule.kernel || relations?.pinnedRuleIds.has(discoveryIdForEntry(rule))))
1937
1957
  return;
1938
1958
  const key = `${rule.type}:${rule.path}`, item = found.get(key) ?? { rule, applicability: [] };
1939
1959
  if (!item.applicability.includes(why))
@@ -1979,8 +1999,8 @@ export function compoundGoverningRules(entries, target, relations, phrase = '',
1979
1999
  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
2000
  }
1981
2001
  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')}` };
2002
+ return { name: rule.name ?? path.basename(rule.path, '.md'), activation: rule.activation ?? null, enforcement: rule.ruleEnforcement ?? null,
2003
+ summary: (rule.ruleSummary ?? '').replace(/\s+/g, ' ').trim(), show_command: `aiwg show ${rule.type} ${rule.name ?? path.basename(rule.path, '.md')} --body` };
1984
2004
  }
1985
2005
  /** Counts unique attached contracts; every emitted check is required irrespective of severity. */
1986
2006
  export function governingRuleStats(governing) {
@@ -1998,7 +2018,7 @@ export function governingRuleFields(governing) {
1998
2018
  return {};
1999
2019
  return { bound_rules: governing.map(({ rule }) => boundRuleDescriptor(rule)), rule_stats: governingRuleStats(governing),
2000
2020
  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 };
2021
+ applicability, fetch_command: `aiwg show ${rule.type} ${discoveryIdForEntry(rule)} --body` })), rule_check_obligation: RULE_CHECK_OBLIGATION };
2002
2022
  }
2003
2023
  function statsText(stats) {
2004
2024
  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`;
@@ -2021,10 +2041,13 @@ export function relationshipRuleStats(entries, target, relations) {
2021
2041
  }
2022
2042
  async function showBindings(entries, target, content, params, read, relations) {
2023
2043
  const governing = compoundGoverningRules(entries, target, relations, '', false, false);
2044
+ for (const { rule } of governing)
2045
+ if (relations?.pinnedRuleIds.has(discoveryIdForEntry(rule)))
2046
+ rule.activation = 'kernel';
2024
2047
  const rules = governing.map(item => item.rule);
2025
2048
  const boundRules = await Promise.all(rules.map(async (rule) => ({
2026
2049
  ...boundRuleDescriptor(rule),
2027
- ...(params.boundRuleBodies ? { content: await read(rule) } : {}),
2050
+ ...((params.boundRuleBodies || params.withRuleBodies) ? { content: await read(rule) } : {}),
2028
2051
  })));
2029
2052
  // Parse the selected rule for reverse lookup, including corpus-only rules.
2030
2053
  const targets = target.type === 'rule' || target.type === 'behavior'
@@ -2035,24 +2058,227 @@ async function showBindings(entries, target, content, params, read, relations) {
2035
2058
  const names = Array.isArray(values) ? [...new Set(values.filter((value) => typeof value === 'string' && !!value.trim()))] : [];
2036
2059
  return names.length ? [[key, names]] : [];
2037
2060
  })) : {};
2061
+ const boundTargets = new Map();
2062
+ if (target.type === 'rule' || target.type === 'behavior') {
2063
+ for (const edge of relations?.targetsForRule(discoveryIdForEntry(target)) ?? []) {
2064
+ boundTargets.set(`${edge.type}:${edge.id}`, { ...edge, name: relations?.index.artifacts?.[edge.id]?.name
2065
+ ?? relations?.index.targets[`${edge.type}:${edge.id}`]?.name });
2066
+ }
2067
+ const selectedRule = { ...target, bound_to: boundTo };
2068
+ for (const candidate of entries)
2069
+ if (rulesBoundTo([selectedRule], candidate).length) {
2070
+ const id = discoveryIdForEntry(candidate);
2071
+ boundTargets.set(`${candidate.type}:${id}`, { type: candidate.type, id, name: candidate.name });
2072
+ }
2073
+ }
2038
2074
  let text = '';
2039
2075
  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}\`)`
2076
+ // Cards already print these stats in the header; body/section output has no header.
2077
+ const stats = params.body || params.sections?.length ? `${statsText(governingRuleStats(governing))}\n\n` : '';
2078
+ text += `\n## Bound rules\n\n${RULE_CHECK_OBLIGATION}\n\n${stats}` + boundRules.map(rule => `- **${rule.name}** [${rule.activation ?? 'unknown'} / ${rule.enforcement ?? 'unknown'}]: ${rule.summary} (\`${rule.show_command}\`)`
2041
2079
  + ('content' in rule ? `\n\n${rule.content}` : '')).join('\n') + '\n';
2042
2080
  }
2043
2081
  if (Object.keys(boundTo).length) {
2044
2082
  text += '\n## Bound targets\n\n' + Object.entries(boundTo)
2045
2083
  .map(([key, names]) => `- ${key}: ${names.map(name => `\`${name}\``).join(', ')}`).join('\n') + '\n';
2046
2084
  }
2085
+ if (boundTargets.size)
2086
+ text += '\nResolved targets: ' + [...boundTargets.values()].map(target => `${target.type} ${target.name ?? target.id}`).join(', ') + '\n';
2047
2087
  return { fields: {
2048
2088
  ...governingRuleFields(governing),
2089
+ ...(['rule', 'behavior'].includes(target.type) ? { bound_to: boundTo, bound_targets: [...boundTargets.values()] } : {}),
2049
2090
  ...(boundRules.length ? { bound_rules: boundRules } : {}),
2050
2091
  ...(Object.keys(boundTo).length ? { bound_to: boundTo } : {}),
2051
2092
  }, text };
2052
2093
  }
2094
+ /** Sections include their nested headings, stopping at the next peer/ancestor. */
2095
+ function showSections(content) {
2096
+ const body = parseFrontmatter(content).body;
2097
+ const headings = [];
2098
+ let offset = 0;
2099
+ let fence = '';
2100
+ const lines = body.match(/[^\n]*\n|[^\n]+$/g) ?? [];
2101
+ for (let i = 0; i < lines.length; i++) {
2102
+ const line = lines[i];
2103
+ const marker = line.match(/^ {0,3}(`{3,}|~{3,})/);
2104
+ if (marker) {
2105
+ if (!fence)
2106
+ fence = marker[1];
2107
+ else if (marker[1][0] === fence[0] && marker[1].length >= fence.length)
2108
+ fence = '';
2109
+ }
2110
+ else if (!fence) {
2111
+ const atx = line.match(/^ {0,3}(#{1,6})\s+(.+?)(?:\s+#+)?\s*$/);
2112
+ const setext = i + 1 < lines.length && lines[i + 1].match(/^ {0,3}(=+|-+)\s*$/);
2113
+ if (atx)
2114
+ headings.push({ heading: atx[2], level: atx[1].length, start: offset });
2115
+ else if (line.trim() && setext)
2116
+ headings.push({ heading: line.trim(), level: setext[1][0] === '=' ? 1 : 2, start: offset });
2117
+ }
2118
+ offset += line.length;
2119
+ }
2120
+ return headings.map((heading, i) => {
2121
+ const end = headings.slice(i + 1).find(next => next.level <= heading.level)?.start ?? body.length;
2122
+ const text = body.slice(heading.start, end);
2123
+ return { heading: heading.heading, level: heading.level, bytes: Buffer.byteLength(text),
2124
+ lines: text.split('\n').length - (text.endsWith('\n') ? 1 : 0), text };
2125
+ });
2126
+ }
2127
+ /** List companion directories and explicitly linked resources within the owning bundle.
2128
+ * Resolve paths against that boundary and exclude symlink components. */
2129
+ function showResources(filePath, source, release) {
2130
+ if (release) {
2131
+ const base = path.posix.dirname(filePath);
2132
+ const linked = new Set(extractMarkdownLinks(source).map(link => path.posix.normalize(path.posix.join(base, link))));
2133
+ const boundary = filePath.match(/^(raw\/agentic\/code\/(?:frameworks|addons|extensions)\/[^/]+)\//)?.[1];
2134
+ return [...release.descriptors.values()].filter(descriptor => {
2135
+ const relative = path.posix.relative(base, descriptor.path);
2136
+ const category = /(?:^|\/)(?:templates|scripts|references|resources|assets)\//.test(relative);
2137
+ const inside = relative !== '..' && !relative.startsWith('../');
2138
+ return (inside && category || linked.has(descriptor.path) && (inside || category))
2139
+ && (!boundary || descriptor.path.startsWith(`${boundary}/`));
2140
+ }).map(descriptor => ({ path: path.posix.relative(base, descriptor.path), bytes: descriptor.size }))
2141
+ .sort((a, b) => a.path.localeCompare(b.path));
2142
+ }
2143
+ const base = path.dirname(filePath);
2144
+ const normalizedBase = normalizeIndexedPath(base);
2145
+ const boundary = normalizedBase.match(/^(.*\/(?:frameworks|addons|extensions)\/[^/]+)(?:\/|$)/)?.[1]
2146
+ ?? normalizedBase.match(/^(.*\/\.aiwg)(?:\/|$)/)?.[1] ?? base;
2147
+ const resources = new Map();
2148
+ const walk = (relative) => {
2149
+ const absolute = path.resolve(base, relative);
2150
+ const contained = path.relative(boundary, absolute);
2151
+ if (contained === '..' || contained.startsWith(`..${path.sep}`) || path.isAbsolute(contained))
2152
+ return;
2153
+ let cursor = boundary;
2154
+ let stat;
2155
+ try {
2156
+ for (const component of contained.split(path.sep)) {
2157
+ cursor = path.join(cursor, component);
2158
+ stat = fs.lstatSync(cursor);
2159
+ if (stat.isSymbolicLink())
2160
+ return;
2161
+ }
2162
+ }
2163
+ catch {
2164
+ return;
2165
+ }
2166
+ if (stat?.isDirectory()) {
2167
+ for (const name of fs.readdirSync(absolute).sort())
2168
+ walk(path.join(relative, name));
2169
+ }
2170
+ else if (stat?.isFile() && absolute !== filePath) {
2171
+ const key = normalizeIndexedPath(path.relative(base, absolute));
2172
+ resources.set(key, { path: key, bytes: stat.size });
2173
+ }
2174
+ };
2175
+ for (const directory of ['templates', 'scripts', 'references', 'resources', 'assets'])
2176
+ walk(directory);
2177
+ const { data } = parseFrontmatter(source);
2178
+ const declared = Array.isArray(data.resources) ? data.resources.filter((value) => typeof value === 'string') : [];
2179
+ for (const link of [...extractMarkdownLinks(source), ...declared]) {
2180
+ if (path.isAbsolute(link) || /^[a-z]+:/i.test(link))
2181
+ continue;
2182
+ const relative = path.relative(base, path.resolve(base, link));
2183
+ // Outside the artifact directory, only declared companion categories qualify.
2184
+ if ((relative === '..' || relative.startsWith(`..${path.sep}`)) && !/(?:^|\/)(?:templates|scripts|references|resources|assets)\//.test(normalizeIndexedPath(relative)))
2185
+ continue;
2186
+ walk(relative);
2187
+ }
2188
+ return [...resources.values()].sort((a, b) => a.path.localeCompare(b.path));
2189
+ }
2190
+ async function emitShowCard(cwd, aiwgRoot, entry, filePath, source, params, bindings, extra, local, release) {
2191
+ const { data } = parseFrontmatter(source);
2192
+ const flow = entry.type === 'flow' || entry.type === 'runbook' ? parseFlowDoc(source, entry.path) : null;
2193
+ const sections = showSections(source);
2194
+ const selected = (params.sections ?? []).map(heading => {
2195
+ const exact = sections.filter(section => section.heading === heading);
2196
+ const matches = exact.length ? exact : sections.filter(section => section.heading.toLowerCase() === heading.toLowerCase());
2197
+ if (matches.length !== 1)
2198
+ throw new Error(`${matches.length ? 'Ambiguous' : 'Unknown'} heading "${heading}". Headings: ${sections.map(section => section.heading).join(', ') || '(none)'}`);
2199
+ return matches[0].text;
2200
+ });
2201
+ const owner = normalizeIndexedPath(entry.path).match(/(?:^|\/)(?:agentic\/code|\.aiwg)\/(frameworks|addons|extensions)\/([^/]+)\//);
2202
+ const kind = owner ? { frameworks: 'framework', addons: 'addon', extensions: 'extension' }[owner[1]] : null;
2203
+ const installed = local && (owner && aiwgRoot
2204
+ ? await isCorpusBundleInstalled(cwd, aiwgRoot, kind, owner[2]) : true);
2205
+ const resources = local || release ? showResources(filePath, source, release) : [];
2206
+ const resource = params.resource ? resources.find(resource => resource.path === params.resource) : undefined;
2207
+ if (params.resource && !resource)
2208
+ throw new Error(`Unknown resource "${params.resource}". Resources: ${resources.map(resource => resource.path).join(', ') || '(none)'}`);
2209
+ const quote = (value) => '"' + value.replace(/["\\$`]/g, '\\$&') + '"';
2210
+ const command = `aiwg show ${entry.type} ${extra.hint ? params.name : discoveryIdForEntry(entry)}`
2211
+ + (params.graph ? ` --graph ${quote(params.graph)}` : '')
2212
+ + (params.backend ? ` --backend ${params.backend}` : '')
2213
+ + (params.resourceSource ? ` --resource-source ${params.resourceSource}` : '')
2214
+ + (params.aiwgVersion ? ` --aiwg-version ${quote(params.aiwgVersion)}` : '')
2215
+ + (params.offline ? ' --offline' : '');
2216
+ let resourceBody;
2217
+ if (resource) {
2218
+ if (local)
2219
+ resourceBody = fs.readFileSync(path.join(path.dirname(filePath), resource.path), 'utf8');
2220
+ else if (release) {
2221
+ const { fetchVerifiedRawResource } = await import('../resources/web-release.js');
2222
+ resourceBody = (await fetchVerifiedRawResource(release, path.posix.normalize(path.posix.join(path.posix.dirname(filePath), resource.path)), { ...params.webReleaseOptions, offline: params.offline })).toString('utf8');
2223
+ }
2224
+ }
2225
+ const localPin = extra.local_pin;
2226
+ const pinNote = localPin ? `Pinned locally: kernel (canonical ${localPin.canonical_activation}; ${localPin.matches.map(match => match.source).join(', ')}).` : '';
2227
+ const contentNotice = 'This is a metadata card. The component body has not been fetched. Before deciding or applying its workflow, read the needed sections or --body using the drill-in commands below. Read full governing rules before governed work; --rules alone lists checks.';
2228
+ const card = {
2229
+ id: discoveryIdForEntry(entry), type: entry.type, name: entry.name ?? String(data.name ?? entry.title),
2230
+ title: entry.title, path: filePath, description: String(data.description ?? data.summary ?? flow?.description ?? entry.capability ?? entry.ruleSummary ?? entry.summary ?? ''),
2231
+ capability: owner ? { kind, name: owner[2] } : null, installed, kernel: entry.kernel ?? false,
2232
+ ...(['rule', 'behavior'].includes(entry.type) ? {
2233
+ activation: localPin ? 'kernel' : entry.activation ?? data.activation ?? null, enforcement: data.enforcement ?? entry.ruleEnforcement ?? null,
2234
+ bound_to: data.bound_to ?? entry.bound_to ?? {},
2235
+ } : {}),
2236
+ bound_rules: [], rule_stats: emptyRuleStats(), required_rule_checks: [], rule_check_obligation: RULE_CHECK_OBLIGATION,
2237
+ ...bindings.fields,
2238
+ outline: sections.map(({ text: _text, ...metadata }) => metadata), resources,
2239
+ ...(!params.body && !selected.length ? { content_notice: contentNotice } : {}),
2240
+ drill_in: {
2241
+ sections: sections.map(section => `${command} --section ${quote(section.heading)}`),
2242
+ rules: `${command} --rules`, rule_bodies: `${command} --rules --with-rule-bodies`,
2243
+ resources: `${command} --resources`, resource: resources.map(resource => `${command} --resource ${quote(resource.path)}`),
2244
+ body: `${command} --body`,
2245
+ },
2246
+ ...extra,
2247
+ ...(params.body || selected.length ? { content: params.body ? source : selected.join('\n') } : {}),
2248
+ ...(resource ? { resource: { ...resource, body: resourceBody } } : {}),
2249
+ };
2250
+ if (params.json) {
2251
+ console.log(JSON.stringify(card, null, 2));
2252
+ return;
2253
+ }
2254
+ if ('content' in card) {
2255
+ if (extra.hint)
2256
+ process.stdout.write(`<!-- AIWG: ${card.installed ? 'installed bundle artifact — not in the selected index.' : 'corpus-only artifact — not in the indexed workspace.'}\n ${extra.hint}\n Origin: ${filePath}\n-->\n\n`);
2257
+ if (entry.script)
2258
+ process.stdout.write(`<!-- AIWG: executable skill — run via: ${buildRunHint(entry)} -->\n`);
2259
+ if (pinNote)
2260
+ process.stdout.write(`${pinNote}\n`);
2261
+ process.stdout.write(card.content);
2262
+ if (!card.content.endsWith('\n'))
2263
+ process.stdout.write('\n');
2264
+ if (bindings.text)
2265
+ process.stdout.write(bindings.text);
2266
+ return;
2267
+ }
2268
+ const lines = [`# ${card.type}: ${card.name}`, `ID: ${card.id}`, card.description,
2269
+ `Owner: ${card.capability ? `${card.capability.kind}:${card.capability.name}` : 'project/unowned'} | installed: ${card.installed}`, statsText(card.rule_stats)];
2270
+ if (pinNote)
2271
+ lines.push(pinNote);
2272
+ if ('activation' in card)
2273
+ lines.push(`Activation: ${card.activation} | enforcement: ${card.enforcement}`, `Bound targets: ${JSON.stringify(card.bound_to)}`);
2274
+ lines.push(contentNotice, bindings.text || RULE_CHECK_OBLIGATION, '\n## Outline', ...card.outline.map(section => `- ${section.heading} (${section.bytes} bytes, ${section.lines} lines)`), '\n## Resources', ...resources.map(resource => `- ${resource.path} (${resource.bytes} bytes)`), '\n## Drill in', ...Object.values(card.drill_in).flat().map(command => `- ${command}`));
2275
+ if (card.resource)
2276
+ lines.push(`\n## Resource: ${card.resource.path}`, card.resource.body);
2277
+ console.log(lines.join('\n'));
2278
+ }
2053
2279
  /**
2054
- * Read and emit the full text of a specific artifact (typically a
2055
- * SKILL.md). Consumers don't need to know where AIWG stores skills —
2280
+ * Emit a metadata card, with opt-in body/section drill-ins for a
2281
+ * SKILL.md. Consumers don't need to know where AIWG stores skills —
2056
2282
  * they pass the skill name and the CLI reads the file from the indexed
2057
2283
  * location.
2058
2284
  *
@@ -2074,7 +2300,7 @@ export async function showArtifact(cwd, params) {
2074
2300
  ? params.typeFilter
2075
2301
  : [...OPERATIONAL_SHOW_TYPES];
2076
2302
  const { entries, aiwgRoot, resourceSource, webRelease, relations, } = await loadShowEntries(cwd, params, true);
2077
- if (entries.length === 0 && params.backend !== 'fortemi-core') {
2303
+ if (entries.length === 0 && !relations && params.backend !== 'fortemi-core') {
2078
2304
  console.error('Error: No artifact index found.');
2079
2305
  console.error('Run `aiwg index build --graph framework` (or `aiwg use <framework>`) first.');
2080
2306
  process.exit(1);
@@ -2106,42 +2332,10 @@ export async function showArtifact(cwd, params) {
2106
2332
  : corpusMatch.bundleKind && corpusMatch.bundleId
2107
2333
  ? `Run \`aiwg use ${corpusMatch.bundleId}\` to install this ${corpusMatch.bundleKind} into the workspace.`
2108
2334
  : '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);
2112
- if (params.json) {
2113
- console.log(JSON.stringify({
2114
- ...bindings.fields,
2115
- path: corpusMatch.path,
2116
- type: corpusMatch.type,
2117
- title: needle,
2118
- kernel: false,
2119
- uninstalled: !installed,
2120
- ...(localPin ? { local_pin: localPin } : {}),
2121
- hint: installHint,
2122
- content,
2123
- }, null, 2));
2124
- return;
2125
- }
2126
- // Plain text mode: distinguish a missing index entry from an
2127
- // uninstalled bundle, then stream the body.
2128
- const banner = [
2129
- installed
2130
- ? '<!-- AIWG: installed bundle artifact — not in the selected index.'
2131
- : '<!-- AIWG: corpus-only artifact — not in the indexed workspace.',
2132
- ` ${installHint}`,
2133
- ' Origin: ' + corpusMatch.path,
2134
- '-->',
2135
- '',
2136
- ].join('\n');
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`);
2140
- process.stdout.write(content);
2141
- if (!content.endsWith('\n'))
2142
- process.stdout.write('\n');
2143
- if (bindings.text)
2144
- process.stdout.write(bindings.text);
2335
+ const fallbackEntry = { path: path.relative(aiwgRoot, corpusMatch.path), type: corpusMatch.type, name: needle, title: needle };
2336
+ const bindings = await showBindings(entries, fallbackEntry, content, params, rule => fs.readFile(resolveMetadataPath(cwd, aiwgRoot, rule), 'utf8'), relations);
2337
+ await emitShowCard(cwd, aiwgRoot, fallbackEntry, corpusMatch.path, content, params, bindings, { uninstalled: !installed, installed, hint: installHint,
2338
+ ...(localPin ? { local_pin: localPin } : {}) }, true);
2145
2339
  return;
2146
2340
  }
2147
2341
  }
@@ -2242,57 +2436,18 @@ export async function showArtifact(cwd, params) {
2242
2436
  });
2243
2437
  return resolved.bytes.toString('utf8');
2244
2438
  }, relations);
2245
- if (params.json) {
2246
- const providerModels = resourceSource === 'local'
2247
- ? await loadProviderModelMetadata(cwd, aiwgRoot)
2248
- : null;
2249
- console.log(JSON.stringify({
2250
- id: discoveryIdForEntry(entry),
2251
- path: filePath,
2252
- type: entry.type,
2253
- name: entry.name,
2254
- title: entry.title,
2255
- kernel: entry.kernel ?? false,
2256
- provenance: {
2257
- graph: entry.indexGraph ?? null,
2258
- scope: entry.indexScope ?? null,
2259
- ...(webRelease
2260
- ? {
2261
- resource_source: 'web',
2262
- aiwg_selector: webRelease.selector,
2263
- aiwg_version: webRelease.version,
2264
- manifest_sha256: webRelease.manifestDigest,
2265
- manifest_url: webRelease.manifestUrl,
2266
- }
2267
- : {}),
2268
- },
2269
- // #1227 — surface script-bearing skills so callers can route to
2270
- // `aiwg run skill <name>` instead of treating SKILL.md as
2271
- // instructions for the agent to execute itself.
2272
- ...(entry.script
2273
- ? { executable: true, run_hint: buildRunHint(entry) }
2274
- : {}),
2275
- ...(providerModels ? { providerModels } : {}),
2276
- ...bindings.fields,
2277
- ...(localPin ? { local_pin: localPin } : {}),
2278
- content,
2279
- }, null, 2));
2280
- return;
2281
- }
2282
- // Stream the authored content first; append binding guidance only when present.
2283
- // #1227 — for script-bearing skills, prepend a one-line banner so
2284
- // agents reading the body immediately see the skill is executable.
2285
- if (entry.script) {
2286
- const hint = buildRunHint(entry);
2287
- process.stdout.write(`<!-- AIWG: executable skill — run via: ${hint} -->\n`);
2288
- }
2289
- if (localPin)
2290
- process.stdout.write(`Pinned locally: kernel (canonical ${localPin.canonical_activation}; ${localPin.matches.map(match => match.source).join(', ')}).\n`);
2291
- process.stdout.write(content);
2292
- if (!content.endsWith('\n'))
2293
- process.stdout.write('\n');
2294
- if (bindings.text)
2295
- process.stdout.write(bindings.text);
2439
+ const providerModels = resourceSource === 'local' ? await loadProviderModelMetadata(cwd, aiwgRoot) : null;
2440
+ await emitShowCard(cwd, aiwgRoot, entry, filePath, content, params, bindings, {
2441
+ provenance: {
2442
+ graph: entry.indexGraph ?? null,
2443
+ scope: entry.indexScope ?? null,
2444
+ ...(webRelease ? { resource_source: 'web', aiwg_selector: webRelease.selector, aiwg_version: webRelease.version,
2445
+ manifest_sha256: webRelease.manifestDigest, manifest_url: webRelease.manifestUrl } : {}),
2446
+ },
2447
+ ...(entry.script ? { executable: true, run_hint: buildRunHint(entry) } : {}),
2448
+ ...(providerModels ? { providerModels } : {}),
2449
+ ...(localPin ? { local_pin: localPin } : {}),
2450
+ }, resourceSource === 'local', webRelease);
2296
2451
  }
2297
2452
  export async function showMetadata(cwd, params) {
2298
2453
  loadGlobalGraphConfigs();
@@ -152,11 +152,11 @@ export function buildRelations(entries) {
152
152
  }
153
153
  return index;
154
154
  }
155
- export async function writeRelations(entries, cwd, indexDir, force = false) {
155
+ export async function writeRelations(entries, cwd, indexDir, force = false, existingDir = indexDir) {
156
156
  const values = Object.values(entries);
157
157
  const fingerprint = relationsFingerprint(values);
158
158
  try {
159
- const existing = JSON.parse(fs.readFileSync(path.join(indexDir, 'relations.json'), 'utf8'));
159
+ const existing = JSON.parse(fs.readFileSync(path.join(existingDir, 'relations.json'), 'utf8'));
160
160
  if (!force && existing.version === RELATIONS_VERSION && existing.$schema === RELATIONS_SCHEMA
161
161
  && existing.fingerprint.version === fingerprint.version && existing.fingerprint.sources === fingerprint.sources
162
162
  && existing.artifacts && existing.rules && existing.targets && existing.capabilities && existing.aliases?.targets && existing.aliases.rules)
@@ -80,7 +80,7 @@ function shouldSkipDir(dirName, fullPath) {
80
80
  return true;
81
81
  return fullPath.split(path.sep).includes('.aiwg') && fullPath.split(path.sep).includes('.index');
82
82
  }
83
- function collectSourceFiles(cwd) {
83
+ export function collectSourceFiles(cwd, includeAssets = false) {
84
84
  const results = [];
85
85
  const visit = (dir) => {
86
86
  if (!fs.existsSync(dir))
@@ -91,7 +91,7 @@ function collectSourceFiles(cwd) {
91
91
  if (!shouldSkipDir(entry.name, fullPath))
92
92
  visit(fullPath);
93
93
  }
94
- else if (entry.isFile() && CODE_EXTENSIONS.has(path.extname(entry.name))) {
94
+ else if (entry.isFile() && (CODE_EXTENSIONS.has(path.extname(entry.name)) || (includeAssets && ASSET_EXTENSIONS.has(path.extname(entry.name))))) {
95
95
  results.push(fullPath);
96
96
  }
97
97
  }
@@ -110,7 +110,7 @@ export const INDEX_VERSION = '1.0.0';
110
110
  * making the serialized index schema incompatible; a mismatch simply forces a
111
111
  * one-time content re-extraction during the next incremental build.
112
112
  */
113
- export const INDEX_EXTRACTOR_VERSION = '2026.10.19.1';
113
+ export const INDEX_EXTRACTOR_VERSION = '2026.10.24.1';
114
114
  /**
115
115
  * Built-in graph definitions
116
116
  */
@@ -672,6 +672,14 @@ export function loadGlobalGraphConfigs(diagnostics) {
672
672
  }
673
673
  /** Module-scoped guard so the config.yaml deprecation note prints at most once per process. */
674
674
  let yamlIndexDeprecationWarned = false;
675
+ /** Reload one workspace's registrations, including removals between daemon runs. */
676
+ export function loadWorkspaceGraphs(workspace, diagnostics) {
677
+ for (const name of Object.keys(GRAPH_CONFIGS))
678
+ delete GRAPH_CONFIGS[name];
679
+ Object.assign(GRAPH_CONFIGS, structuredClone(BUILTIN_GRAPH_CONFIGS));
680
+ loadUserGraphConfigs(workspace, diagnostics);
681
+ loadGlobalGraphConfigs(diagnostics);
682
+ }
675
683
  /**
676
684
  * Get the index output directory for a given graph type
677
685
  *
@@ -1,10 +1,11 @@
1
+ import { decisionPricing } from '../../decision/pricing.js';
1
2
  import { ATTRIBUTION_FIELD_PATTERN, DecisionAskUsageError, decisionCapabilities, DecisionLedgerSinceError, decisionLedgerHistory, decisionLedgerStats, evaluateRequestPath, listPatterns, livePlan, MAX_ATTRIBUTION_FIELD_LENGTH, materializeSyntheticClassificationSetup, parseDecisionLedgerSince, readDecisionContextFile, runDecisionOffloadCheckpoint, runOfflinePattern, setupJev, showPattern, syntheticClassificationSetup, validateDecisionInput, } from '../../decision/driver.js';
2
3
  function jsonResult(value, exitCode = 0) {
3
4
  return { exitCode, message: JSON.stringify(value, null, 2), rawOutput: true };
4
5
  }
5
6
  function usage() {
6
7
  return [
7
- 'Usage: aiwg decision <capabilities|status|patterns|validate|evaluate|setup|history|stats> [options]',
8
+ 'Usage: aiwg decision <capabilities|status|patterns|validate|evaluate|setup|history|stats|pricing> [options]',
8
9
  ' aiwg decision ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--context <text> | --context-file <path> | --context-stdin] [--threshold 0.8] [--timeout-ms 15000] [--passes N] [--escalation-passes N] [--provider <id>] [--session <id>] [--caller <tag>] [--record-text] [--json]',
9
10
  '',
10
11
  'Commands:',
@@ -19,6 +20,7 @@ function usage() {
19
20
  ' setup jev [--token-stdin] [--region <r>] [--endpoint <url>] [--verify] [--remove]',
20
21
  ' ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--provider <id>] [--session <id>] [--caller <tag>] [--record-text]',
21
22
  ' history [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--json]',
23
+ ' pricing [--refresh --model jev-X.Y.Z] [--dry-run] [--offline | --allow-snapshot] [--json]',
22
24
  ' stats [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--group-by provider|session|kind|caller|passes|escalation] [--json]',
23
25
  '',
24
26
  'Notes:',
@@ -314,6 +316,7 @@ async function decisionStats(ctx) {
314
316
  ` jev usage: input=${group.jevUsage.inputTokens} output=${group.jevUsage.outputTokens}`,
315
317
  ` tokens per answered decision=${group.tokensPerAnsweredDecision?.toFixed(2) ?? '-'}`,
316
318
  ` llm_tokens_avoided=${group.llm_tokens_avoided} jev_tokens=${group.jev_tokens} cost_weighted_net_usd=${group.cost_weighted_net_usd.toFixed(6)}`,
319
+ ...group.ratesUsed.map(rate => ` rates used (${rate.calls} calls): ${JSON.stringify(rate.method)} source=${rate.method.source ?? 'manual'} checked_at=${rate.method.checked_at ?? 'unknown'}`),
317
320
  ` v1 comparison only: estimated LLM tokens avoided=${group.estimatedLlmTokensAvoidedTotal} net savings=${group.netSavingsTokens}`,
318
321
  ...(group.legacyReestimatedCalls ? [` v1 records re-estimated=${group.legacyReestimatedCalls} weights=${JSON.stringify(result.legacyEstimationWeights)}`] : []),
319
322
  ]);
@@ -331,6 +334,21 @@ async function executeDecision(ctx) {
331
334
  return await decisionAsk(ctx);
332
335
  if (command === 'history')
333
336
  return await decisionHistory(ctx);
337
+ if (command === 'pricing') {
338
+ const args = ctx.args.slice(1);
339
+ assertLedgerArgs(args, ['--model'], ['--refresh', '--dry-run', '--offline', '--allow-snapshot', '--json']);
340
+ const result = await decisionPricing({ cwd: ctx.cwd, refresh: args.includes('--refresh'), dryRun: args.includes('--dry-run'),
341
+ offline: args.includes('--offline'), allowSnapshot: args.includes('--allow-snapshot'), model: args.includes('--model') ? takeRequiredOption(args, '--model') : undefined });
342
+ if (args.includes('--json'))
343
+ return jsonResult(result);
344
+ return { exitCode: 0, rawOutput: true, message: [
345
+ `decision.ask.cost: ${JSON.stringify(result.current)}`,
346
+ `Source URL: ${result.sourceUrl}`,
347
+ ...('snapshotDate' in result && result.snapshotDate ? [`Bundled snapshot dated ${result.snapshotDate}`] : []),
348
+ ...('warning' in result && result.warning ? [`Warning: ${result.warning}`] : []),
349
+ ...('diff' in result ? [`${result.written ? 'Written' : 'Dry run; config unchanged'}: ${JSON.stringify(result.diff)}`] : []),
350
+ ].join('\n') };
351
+ }
334
352
  if (command === 'stats')
335
353
  return await decisionStats(ctx);
336
354
  if (command === 'patterns') {