@aiwg/cli 2026.9.24 → 2026.10.1

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 (209) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +2 -1
  2. package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
  3. package/dist/src/artifacts/discovery-shadow.js +195 -0
  4. package/dist/src/artifacts/index-builder.js +47 -9
  5. package/dist/src/artifacts/query-engine.js +94 -1
  6. package/dist/src/artifacts/types.js +1 -0
  7. package/dist/src/cli/handlers/decision.js +177 -0
  8. package/dist/src/cli/handlers/gates.js +154 -0
  9. package/dist/src/cli/handlers/index.js +5 -1
  10. package/dist/src/cli/handlers/sessions.js +10 -7
  11. package/dist/src/cli/handlers/utilities.js +20 -0
  12. package/dist/src/config/aiwg-config.js +15 -2
  13. package/dist/src/decision/acceptance.js +25 -0
  14. package/dist/src/decision/adapters/jev.js +29 -3
  15. package/dist/src/decision/ask.js +352 -0
  16. package/dist/src/decision/calibration/registry.js +9 -0
  17. package/dist/src/decision/compose.js +74 -15
  18. package/dist/src/decision/context-live-promotion.js +344 -0
  19. package/dist/src/decision/context-live-qualification.js +591 -0
  20. package/dist/src/decision/context-pruning.js +847 -0
  21. package/dist/src/decision/driver.js +432 -0
  22. package/dist/src/decision/egress-live-qualification.js +874 -0
  23. package/dist/src/decision/ensemble/contract.js +64 -1
  24. package/dist/src/decision/ensemble/index.js +1 -0
  25. package/dist/src/decision/ensemble/report.js +8 -42
  26. package/dist/src/decision/ensemble/runtime.js +571 -0
  27. package/dist/src/decision/ensemble-study/artifacts.js +85 -0
  28. package/dist/src/decision/ensemble-study/calibration.js +443 -0
  29. package/dist/src/decision/ensemble-study/corpus.js +229 -0
  30. package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
  31. package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
  32. package/dist/src/decision/ensemble-study/multifact.js +241 -0
  33. package/dist/src/decision/ensemble-study/protocol.js +77 -0
  34. package/dist/src/decision/ensemble-study/score.js +462 -0
  35. package/dist/src/decision/ensemble-study/staged.js +87 -0
  36. package/dist/src/decision/ensemble-study/statistics.js +84 -0
  37. package/dist/src/decision/evaluate.js +227 -14
  38. package/dist/src/decision/export.js +4 -0
  39. package/dist/src/decision/feature-export.js +1074 -0
  40. package/dist/src/decision/graph-index.js +16 -0
  41. package/dist/src/decision/graph-live-qualification.js +1041 -0
  42. package/dist/src/decision/graph-live-workload.js +245 -0
  43. package/dist/src/decision/graph-skill-bridge.js +21 -3
  44. package/dist/src/decision/heldout/calibration.js +140 -0
  45. package/dist/src/decision/heldout/collector.js +430 -0
  46. package/dist/src/decision/heldout/contract.js +309 -0
  47. package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
  48. package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
  49. package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
  50. package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
  51. package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
  52. package/dist/src/decision/heldout/d29-pools.js +288 -0
  53. package/dist/src/decision/heldout/d29-v6.js +169 -0
  54. package/dist/src/decision/heldout/d29-v7.js +202 -0
  55. package/dist/src/decision/heldout/d29-v8.js +586 -0
  56. package/dist/src/decision/heldout/generator-registry.js +67 -0
  57. package/dist/src/decision/heldout/generators.js +696 -0
  58. package/dist/src/decision/heldout/index.js +8 -0
  59. package/dist/src/decision/heldout/journal.js +346 -0
  60. package/dist/src/decision/heldout/study-generators.js +64 -0
  61. package/dist/src/decision/heldout/types.js +2 -0
  62. package/dist/src/decision/host-policy.js +61 -0
  63. package/dist/src/decision/index.js +13 -0
  64. package/dist/src/decision/interop-profile.v1.json +112 -0
  65. package/dist/src/decision/interop.js +653 -0
  66. package/dist/src/decision/issue-triage/index.js +3 -0
  67. package/dist/src/decision/issue-triage/runtime.js +1069 -0
  68. package/dist/src/decision/issue-triage/types.js +2 -0
  69. package/dist/src/decision/jev-credentials.js +121 -0
  70. package/dist/src/decision/patterns/catalog.js +28 -8
  71. package/dist/src/decision/patterns/playground.js +28 -11
  72. package/dist/src/decision/patterns/specs.js +19 -10
  73. package/dist/src/decision/preprocessed-evidence.js +724 -0
  74. package/dist/src/decision/qualification/capture.js +8 -2
  75. package/dist/src/decision/qualification/gate-evidence.js +19 -4
  76. package/dist/src/decision/qualification/quality.js +57 -49
  77. package/dist/src/decision/qualification/release.js +76 -3
  78. package/dist/src/decision/qualification/runner.js +5 -2
  79. package/dist/src/decision/routing/contract.js +175 -0
  80. package/dist/src/decision/routing/index.js +5 -0
  81. package/dist/src/decision/routing/report.js +216 -0
  82. package/dist/src/decision/routing/runtime.js +685 -0
  83. package/dist/src/decision/routing/types.js +2 -0
  84. package/dist/src/decision/sdlc-screening.js +1157 -0
  85. package/dist/src/decision/sensitivity/comparative.js +154 -0
  86. package/dist/src/decision/sensitivity/contract.js +218 -0
  87. package/dist/src/decision/sensitivity/index.js +5 -0
  88. package/dist/src/decision/sensitivity/runtime.js +912 -0
  89. package/dist/src/decision/sensitivity/types.js +2 -0
  90. package/dist/src/decision/validate.js +26 -0
  91. package/dist/src/extensions/commands/definitions.js +38 -0
  92. package/dist/src/extensions/manifest.js +44 -0
  93. package/dist/src/gates/discovery.js +315 -0
  94. package/dist/src/gates/driver.js +388 -0
  95. package/dist/src/gates/evaluate.js +719 -0
  96. package/dist/src/gates/floors.js +255 -0
  97. package/dist/src/gates/index.js +17 -0
  98. package/dist/src/gates/providers/evidence.js +38 -0
  99. package/dist/src/gates/providers/index.js +21 -0
  100. package/dist/src/gates/providers/isolated-child-source.js +446 -0
  101. package/dist/src/gates/providers/loader.js +1069 -0
  102. package/dist/src/gates/providers/paired.js +52 -0
  103. package/dist/src/gates/providers/proportion.js +60 -0
  104. package/dist/src/gates/providers/registry.js +40 -0
  105. package/dist/src/gates/providers/scalar.js +41 -0
  106. package/dist/src/gates/providers/screening.js +53 -0
  107. package/dist/src/gates/providers/types.js +25 -0
  108. package/dist/src/gates/registry.js +1003 -0
  109. package/dist/src/gates/report.js +102 -0
  110. package/dist/src/gates/schema.js +56 -0
  111. package/dist/src/gates/stats/binomial.js +26 -0
  112. package/dist/src/gates/stats/bootstrap.js +62 -0
  113. package/dist/src/gates/stats/clopper-pearson.js +95 -0
  114. package/dist/src/gates/stats/digest.js +26 -0
  115. package/dist/src/gates/stats/error.js +5 -0
  116. package/dist/src/gates/stats/index.js +9 -0
  117. package/dist/src/gates/stats/normal.js +35 -0
  118. package/dist/src/gates/stats/paired.js +120 -0
  119. package/dist/src/gates/stats/splits.js +30 -0
  120. package/dist/src/gates/types.js +8 -0
  121. package/dist/src/governance/redaction.js +14 -4
  122. package/dist/src/mcp/muse-native-config.mjs +123 -0
  123. package/dist/src/mcp/tools/decision.mjs +228 -0
  124. package/dist/src/mcp/tools/subsystems.mjs +2 -0
  125. package/dist/src/providers/capability-matrix.yaml +2 -1
  126. package/dist/src/providers/provider-definitions.js +2 -1
  127. package/dist/src/sessions/adapters/muse.js +336 -39
  128. package/dist/src/sessions/batch-import.js +7 -0
  129. package/dist/src/sessions/workspace-discovery.js +13 -2
  130. package/package.json +1 -1
  131. package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
  132. package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
  133. package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
  134. package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
  135. package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
  136. package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
  137. package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
  138. package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
  139. package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
  140. package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
  141. package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
  142. package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
  143. package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
  144. package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
  145. package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
  146. package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
  147. package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
  148. package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
  149. package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
  150. package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
  151. package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
  152. package/schemas/decision/D29Study.v1.schema.json +1903 -0
  153. package/schemas/decision/D29Study.v2.schema.json +1344 -0
  154. package/schemas/decision/D29Study.v3.schema.json +609 -0
  155. package/schemas/decision/D29Study.v4.schema.json +1133 -0
  156. package/schemas/decision/D29Study.v6.schema.json +1205 -0
  157. package/schemas/decision/D29Study.v7.schema.json +1471 -0
  158. package/schemas/decision/D29Study.v8.schema.json +671 -0
  159. package/schemas/decision/D29Study.v9.schema.json +778 -0
  160. package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
  161. package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
  162. package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
  163. package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
  164. package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
  165. package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
  166. package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
  167. package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
  168. package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
  169. package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
  170. package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
  171. package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
  172. package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
  173. package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
  174. package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
  175. package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
  176. package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
  177. package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
  178. package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
  179. package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
  180. package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
  181. package/schemas/decision/DecisionRuleset.schema.json +8 -2
  182. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
  183. package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
  184. package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
  185. package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
  186. package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
  187. package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
  188. package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
  189. package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
  190. package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
  191. package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
  192. package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
  193. package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
  194. package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
  195. package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
  196. package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
  197. package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
  198. package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
  199. package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
  200. package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
  201. package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
  202. package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
  203. package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
  204. package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
  205. package/tools/agents/deploy-agents.mjs +1 -1
  206. package/tools/agents/providers/base.mjs +40 -18
  207. package/tools/agents/providers/factory.mjs +10 -2
  208. package/tools/agents/providers/muse-hooks.mjs +1 -29
  209. package/tools/agents/providers/muse.mjs +6 -3
@@ -0,0 +1,388 @@
1
+ import { lstatSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { findPackageRoot } from '../cli/find-package-root.js';
7
+ import { readAiwgConfig } from '../config/aiwg-config.js';
8
+ import { parseDecisionJson, parseDecisionYaml } from '../decision/entry.js';
9
+ import { artifactDigest } from '../decision/validate.js';
10
+ import { discoverShippedGatePacks, loadGatePackFile, originForPackDir, registerBundleGatePacks } from './discovery.js';
11
+ import { evaluateGates } from './evaluate.js';
12
+ import { resolveProjectFloors, validateGatesConfig } from './floors.js';
13
+ import { createCoreProviderRegistry } from './providers/index.js';
14
+ import { GateRegistry, splitPackId, validateResolvedPack } from './registry.js';
15
+ import { validateGateDocument } from './schema.js';
16
+ function baseDir(options = {}) {
17
+ return path.resolve(options.cwd ?? process.cwd());
18
+ }
19
+ /**
20
+ * The installed package root: the only tree allowed to contribute `aiwg:`
21
+ * packs. Resolved from the installed package location, never from the cwd,
22
+ * so an arbitrary checkout cannot impersonate the shipped namespace.
23
+ */
24
+ export function installedAiwgRoot() {
25
+ const root = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
26
+ if (root === null)
27
+ throw new Error('installed aiwg package root is unavailable');
28
+ return root;
29
+ }
30
+ function rootDir(options = {}) {
31
+ return path.resolve(options.aiwgRoot ?? installedAiwgRoot());
32
+ }
33
+ function resolvePath(candidate, base) {
34
+ if (!candidate)
35
+ throw new Error('Path is required');
36
+ return path.resolve(base, candidate);
37
+ }
38
+ async function readDocument(filePath) {
39
+ const content = await readFile(filePath, 'utf8');
40
+ return filePath.toLowerCase().endsWith('.json') ? parseDecisionJson(content) : parseDecisionYaml(content);
41
+ }
42
+ function isRecord(value) {
43
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
44
+ }
45
+ /**
46
+ * Build an offline registry from shipped packs plus explicit pack dirs/files.
47
+ * Trust rules: `aiwg:` is reserved for the installed tree (an `--aiwg-root`
48
+ * override is marked `untrusted-override` and can never feed evaluation);
49
+ * single files loaded via `packDirs` are always `project:` packs regardless
50
+ * of the id they declare; `--pack-dir` bundle directories are always
51
+ * project-scoped too (`originForPackDir` refuses manifests claiming a
52
+ * shipped or installed bundle id, and symlinked bundle dirs refuse), so
53
+ * `addon:`/`framework:`/`extension:` packs never enter through an unverified
54
+ * path (never path sniffing). Read-only inspection (`list`/`show`/`validate`)
55
+ * works on any root; `evaluateGatesFromFiles` refuses untrusted roots.
56
+ */
57
+ export function buildGatesRegistry(options = {}) {
58
+ const registry = new GateRegistry(createCoreProviderRegistry());
59
+ const loaded = [];
60
+ const installed = installedAiwgRoot();
61
+ const root = rootDir(options);
62
+ const shippedTrust = options.aiwgRoot !== undefined
63
+ && path.resolve(options.aiwgRoot) !== path.resolve(installed)
64
+ ? 'untrusted-override' : 'shipped';
65
+ try {
66
+ loaded.push(...discoverShippedGatePacks(root, registry));
67
+ }
68
+ catch (error) {
69
+ throw new Error(`shipped gate packs rejected: ${error instanceof Error ? error.message : String(error)}`);
70
+ }
71
+ for (const dir of options.packDirs ?? []) {
72
+ const resolved = resolvePath(dir, baseDir(options));
73
+ // A symlinked --pack-dir bundle directory refuses: it could point at the
74
+ // installed tree (or anywhere else) while claiming to be project-local.
75
+ try {
76
+ const status = lstatSync(resolved);
77
+ if (status.isSymbolicLink())
78
+ throw new Error(`pack-dir bundle must not be a symlink: ${resolved}`);
79
+ if (!status.isDirectory() && !status.isFile()) {
80
+ throw new Error(`pack-dir path is not a file or directory: ${resolved}`);
81
+ }
82
+ }
83
+ catch (error) {
84
+ if (error instanceof Error && /symlink|not a file or directory/.test(error.message))
85
+ throw error;
86
+ throw new Error(`pack-dir path is unavailable: ${resolved}`);
87
+ }
88
+ const lower = resolved.toLowerCase();
89
+ if (lower.endsWith('.gatepack.yaml') || lower.endsWith('.gatepack.yml') || lower.endsWith('.gatepack.json')
90
+ || lower.endsWith('.json') || lower.endsWith('.yaml') || lower.endsWith('.yml')) {
91
+ const pack = loadGatePackFile(resolved);
92
+ const { namespace } = splitPackId(pack.metadata.id);
93
+ if (namespace !== 'project') {
94
+ throw new Error(`gate pack rejected: ${resolved}: pack ${pack.metadata.id} from --pack-dir`
95
+ + ` must use the project: namespace (aiwg: is reserved for shipped packs;`
96
+ + ` addon:/framework:/extension: packs load only from manifest-declared bundles)`);
97
+ }
98
+ try {
99
+ registry.registerPack(pack, { namespace: 'project' });
100
+ }
101
+ catch (error) {
102
+ throw new Error(`gate pack rejected: ${resolved}: ${error instanceof Error ? error.message : String(error)}`);
103
+ }
104
+ loaded.push(pack.metadata.id);
105
+ continue;
106
+ }
107
+ loaded.push(...registerBundleGatePacks(registry, resolved, originForPackDir(resolved, { cwd: baseDir(options), aiwgRoot: root })));
108
+ }
109
+ return { registry, loaded, shippedTrust };
110
+ }
111
+ /** Offline validation for one pack/binding/report file. Never throws for invalid content. */
112
+ export async function validateGateFile(kind, filePath, options = {}) {
113
+ const resolved = resolvePath(filePath, baseDir(options));
114
+ let document;
115
+ try {
116
+ document = await readDocument(resolved);
117
+ }
118
+ catch (error) {
119
+ return {
120
+ schema: 'aiwg-gates-validation/v1', kind: kind === 'auto' ? 'pack' : kind, source: 'path',
121
+ valid: false, summary: { id: null, version: null, apiVersion: null },
122
+ errors: [error instanceof Error ? error.message : 'unreadable-file'],
123
+ };
124
+ }
125
+ const detected = kind !== 'auto'
126
+ ? kind
127
+ : isRecord(document) && document.kind === 'GateBinding' ? 'binding'
128
+ : isRecord(document) && document.kind === 'GateReport' ? 'report' : 'pack';
129
+ const summary = {
130
+ id: isRecord(document) && isRecord(document.metadata) && typeof document.metadata.id === 'string' ? document.metadata.id : null,
131
+ version: isRecord(document) && isRecord(document.metadata) && typeof document.metadata.version === 'string' ? document.metadata.version : null,
132
+ apiVersion: isRecord(document) && typeof document.apiVersion === 'string' ? document.apiVersion : null,
133
+ };
134
+ const errors = [];
135
+ try {
136
+ if (detected === 'pack') {
137
+ const pack = validateGateDocument(document);
138
+ if (pack.kind !== 'GatePack')
139
+ throw new Error(`expected kind GatePack, got '${String(document.kind)}'`);
140
+ const { registry } = buildGatesRegistry(options);
141
+ const providers = registry.providers
142
+ ?? createCoreProviderRegistry();
143
+ validateResolvedPack(pack, providers);
144
+ }
145
+ else if (detected === 'binding') {
146
+ const { registry } = buildGatesRegistry(options);
147
+ // Binding validation resolves with the config floors, exactly like
148
+ // evaluation (R3): a binding that loosens project policy is invalid.
149
+ // Floors load failures are validation errors, never throws.
150
+ let floors;
151
+ try {
152
+ floors = await loadProjectFloorsForEvaluate(baseDir(options));
153
+ }
154
+ catch (error) {
155
+ throw new Error(`invalid gates section in aiwg.config: ${error instanceof Error ? error.message : String(error)}`);
156
+ }
157
+ registry.resolveBinding(document, floors);
158
+ }
159
+ else {
160
+ // Full report validation needs the trusted evaluation context
161
+ // (`validateGateReport` re-derives the report byte-identically).
162
+ // `validate` checks the closed schema, the kind and the self-digest,
163
+ // so rewritten evidence with a stale digest still fails here.
164
+ const report = validateGateDocument(document);
165
+ if (!isRecord(document) || document.kind !== 'GateReport')
166
+ throw new Error('expected kind GateReport');
167
+ const { digest, ...fields } = report;
168
+ if (typeof digest !== 'string' || artifactDigest(fields) !== digest) {
169
+ throw new Error('report digest does not match its content');
170
+ }
171
+ }
172
+ }
173
+ catch (error) {
174
+ errors.push(error instanceof Error ? error.message : 'validation-failed');
175
+ }
176
+ return { schema: 'aiwg-gates-validation/v1', kind: detected, source: 'path', valid: errors.length === 0, summary, errors };
177
+ }
178
+ /**
179
+ * Load a sealed holdout record from disk. The record must already carry its
180
+ * digest; the seal is re-derived and a missing digest is refused (never
181
+ * auto-sealed: sealing is a trust assertion the file alone cannot make).
182
+ */
183
+ export async function loadHoldoutFile(filePath, cwd) {
184
+ const resolved = resolvePath(filePath, cwd);
185
+ const document = await readDocument(resolved);
186
+ if (!isRecord(document))
187
+ throw new Error(`holdout file must be an object: ${resolved}`);
188
+ const { frozenDigest, firstAccessedAt, digest } = document;
189
+ if (typeof frozenDigest !== 'string' || !(firstAccessedAt === null || typeof firstAccessedAt === 'string')) {
190
+ throw new Error(`holdout file must carry {frozenDigest, firstAccessedAt}: ${resolved}`);
191
+ }
192
+ if (typeof digest !== 'string') {
193
+ throw new Error(`holdout file must already carry its sealed digest (refusing to auto-seal): ${resolved}`);
194
+ }
195
+ const expected = artifactDigest({ frozenDigest, firstAccessedAt });
196
+ if (expected !== digest)
197
+ throw new Error(`holdout record seal does not match its content: ${resolved}`);
198
+ return { frozenDigest: frozenDigest, firstAccessedAt: firstAccessedAt, digest: digest };
199
+ }
200
+ /**
201
+ * Load a sealed upstream record from disk. The file must be a sealed
202
+ * `{metadata, digest}` record whose digest is re-derived; raw upstream
203
+ * metadata is refused (never auto-sealed).
204
+ */
205
+ export async function loadUpstreamFile(filePath, cwd) {
206
+ const resolved = resolvePath(filePath, cwd);
207
+ const document = await readDocument(resolved);
208
+ if (!isRecord(document))
209
+ throw new Error(`upstream file must be an object: ${resolved}`);
210
+ if (!isRecord(document.metadata) || typeof document.digest !== 'string') {
211
+ throw new Error(`upstream file must be a sealed record {metadata, digest} (refusing to auto-seal): ${resolved}`);
212
+ }
213
+ const expected = artifactDigest(document.metadata);
214
+ if (expected !== document.digest)
215
+ throw new Error(`upstream integrity digest does not match its report: ${resolved}`);
216
+ return { metadata: document.metadata, digest: document.digest };
217
+ }
218
+ /**
219
+ * Load the trusted project floors for one CLI evaluation from
220
+ * `<cwd>/.aiwg/aiwg.config`. A missing config resolves to the operator
221
+ * defaults; an unreadable file or an invalid `gates` section refuses
222
+ * evaluation (fail closed). `readAiwgConfig` itself only warns on gates
223
+ * errors (so unrelated commands keep working); this path re-validates
224
+ * strictly because it feeds the evaluator.
225
+ */
226
+ export async function loadProjectFloorsForEvaluate(cwd) {
227
+ const config = await readAiwgConfig(cwd);
228
+ const errors = validateGatesConfig(config?.gates);
229
+ if (errors.length > 0) {
230
+ throw new Error(`refusing evaluation: invalid gates section in aiwg.config:\n${errors.join('\n')}`);
231
+ }
232
+ return resolveProjectFloors(config?.gates);
233
+ }
234
+ /**
235
+ * Offline evaluation from files. Packs resolve through the offline registry;
236
+ * digests, pins and seals are re-derived inside the evaluator. The report is
237
+ * labeled `attestation: 'offline-cli'` so CLI-produced reports are
238
+ * distinguishable from reports built over a verified trust ceremony.
239
+ * Returns the report object (the CLI prints it as JSON).
240
+ */
241
+ export async function evaluateGatesFromFiles(files, options = {}) {
242
+ const cwd = baseDir(options);
243
+ const { registry, shippedTrust } = buildGatesRegistry(options);
244
+ if (shippedTrust !== 'shipped') {
245
+ throw new Error('refusing evaluation: packs from an --aiwg-root override are untrusted'
246
+ + ' and cannot be used by evaluate (list/show/validate remain available for inspection)');
247
+ }
248
+ const floors = await loadProjectFloorsForEvaluate(cwd);
249
+ const binding = await readDocument(resolvePath(files.bindingPath, cwd));
250
+ const metrics = await readDocument(resolvePath(files.metricsPath, cwd));
251
+ const holdout = await loadHoldoutFile(files.holdoutPath, cwd);
252
+ const upstream = files.upstreamPath ? await loadUpstreamFile(files.upstreamPath, cwd) : null;
253
+ return evaluateGates({
254
+ binding, registry, trustedBindingDigest: files.trustedBindingDigest,
255
+ holdout, metrics, upstream, now: files.now, floors, attestation: 'offline-cli',
256
+ });
257
+ }
258
+ /** List registered packs, optionally filtered by namespace. */
259
+ export function listGatePacks(options = {}) {
260
+ const { registry } = buildGatesRegistry(options);
261
+ const entries = [...registry.packs.entries()];
262
+ const summaries = entries.map(([id, entry]) => {
263
+ let resolvedDigest = entry.digest;
264
+ try {
265
+ resolvedDigest = String(artifactDigest(registry.resolvePack(id).resolved));
266
+ }
267
+ catch {
268
+ resolvedDigest = entry.digest;
269
+ }
270
+ return {
271
+ id, version: entry.authored.metadata.version, description: entry.authored.metadata.description,
272
+ digest: entry.digest, resolvedDigest, namespace: entry.namespace, gates: entry.authored.spec.gates.length,
273
+ };
274
+ }).sort((a, b) => a.id.localeCompare(b.id));
275
+ if (options.namespace)
276
+ return summaries.filter(entry => entry.namespace === options.namespace);
277
+ return summaries;
278
+ }
279
+ /**
280
+ * Packs referenced by one rule's `enforcedBy` frontmatter (#2830 stub; full
281
+ * coverage is #2839). Scans bundled and project-local `rules/*.md` for an
282
+ * `enforcedBy: [aiwg:<bundle>/<pack>#<gate>]` list naming the rule. Returns
283
+ * the referenced pack ids (without gate fragments). Empty when no rule
284
+ * declares `enforcedBy` — rules remain guidance until they name a check.
285
+ */
286
+ export async function packsReferencedByRule(ruleId, options = {}) {
287
+ const { readdir, readFile: readRuleFile, stat } = await import('node:fs/promises');
288
+ const roots = [];
289
+ const root = rootDir(options);
290
+ for (const sub of ['frameworks', 'addons', 'extensions']) {
291
+ roots.push(path.join(root, 'agentic/code', sub));
292
+ }
293
+ roots.push(path.join(baseDir(options), '.aiwg'));
294
+ const ruleFiles = [];
295
+ const walk = async (dir, depth) => {
296
+ if (depth > 5 || ruleFiles.length > 500)
297
+ return;
298
+ let entries;
299
+ try {
300
+ entries = await readdir(dir, { withFileTypes: true });
301
+ }
302
+ catch {
303
+ return;
304
+ }
305
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
306
+ if (ruleFiles.length > 500)
307
+ return;
308
+ const full = path.join(dir, entry.name);
309
+ if (entry.isDirectory()) {
310
+ if (entry.name === 'node_modules' || entry.name.startsWith('.git'))
311
+ continue;
312
+ await walk(full, depth + 1);
313
+ }
314
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
315
+ if (!full.replace(/\\/g, '/').includes('/rules/'))
316
+ continue;
317
+ ruleFiles.push(full);
318
+ }
319
+ }
320
+ };
321
+ for (const dir of roots)
322
+ await walk(dir, 0);
323
+ const referenced = new Set();
324
+ for (const file of ruleFiles) {
325
+ let content;
326
+ try {
327
+ const st = await stat(file);
328
+ if (st.size > 65_536)
329
+ continue;
330
+ content = await readRuleFile(file, 'utf8');
331
+ }
332
+ catch {
333
+ continue;
334
+ }
335
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
336
+ if (!match)
337
+ continue;
338
+ const frontmatter = match[1];
339
+ if (!frontmatter.includes('enforcedBy'))
340
+ continue;
341
+ const ruleName = path.basename(file, '.md');
342
+ const dirName = path.basename(path.dirname(file));
343
+ const names = new Set([ruleName, dirName === 'rules' ? null : `${dirName}/${ruleName}`, file]);
344
+ if (!names.has(ruleId) && ![...names].filter(Boolean).some(name => ruleId.endsWith(String(name))))
345
+ continue;
346
+ for (const line of frontmatter.split('\n')) {
347
+ const trimmed = line.trim().replace(/^-\s*/, '');
348
+ const id = trimmed.match(/(aiwg|framework|addon|extension|project):[^\s,'"\]]+/)?.[1]
349
+ ? trimmed.match(/((?:aiwg|framework|addon|extension|project):[^\s,'"\]]+)/)?.[1]
350
+ : null;
351
+ if (id)
352
+ referenced.add(id.split('#')[0]);
353
+ }
354
+ }
355
+ return [...referenced].sort();
356
+ }
357
+ /** Show one pack by full id, rest-path or short pack name. */
358
+ export function showGatePack(id, options = {}) {
359
+ const { registry } = buildGatesRegistry(options);
360
+ let resolved;
361
+ try {
362
+ resolved = registry.resolvePack(id);
363
+ }
364
+ catch (error) {
365
+ // Rest-path lookup needs the full `<bundle>/<name>` rest; a short pack
366
+ // name (`integrity-ceiling`) resolves when exactly one pack owns it.
367
+ const needle = id.includes(':') ? id.slice(id.indexOf(':') + 1) : id;
368
+ if (needle.includes('/') || /\s/.test(needle))
369
+ throw error;
370
+ const entries = [...registry.packs.keys()].filter(key => {
371
+ const rest = key.includes(':') ? key.slice(key.indexOf(':') + 1) : key;
372
+ return rest === needle || rest.endsWith(`/${needle}`);
373
+ }).sort();
374
+ if (entries.length !== 1)
375
+ throw error;
376
+ resolved = registry.resolvePack(entries[0]);
377
+ }
378
+ return {
379
+ schema: 'aiwg-gates-show/v1',
380
+ id: resolved.authored.metadata.id,
381
+ version: resolved.authored.metadata.version,
382
+ description: resolved.authored.metadata.description,
383
+ digest: resolved.digest,
384
+ resolvedDigest: resolved.resolvedDigest,
385
+ pack: resolved.resolved,
386
+ };
387
+ }
388
+ //# sourceMappingURL=driver.js.map