@aiwg/cli 2026.10.3 → 2026.10.5

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 (279) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/model-capabilities.v1.json +96 -4
  3. package/bin/aiwg.mjs +50 -27
  4. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  5. package/dist/src/artifacts/browser-export.js +6 -0
  6. package/dist/src/artifacts/checksum-manifest.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +173 -92
  8. package/dist/src/artifacts/citation-table-schema.js +59 -0
  9. package/dist/src/artifacts/cli.js +69 -18
  10. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  11. package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
  12. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  13. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  14. package/dist/src/artifacts/discovery-eval.js +6 -4
  15. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  16. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  17. package/dist/src/artifacts/framework-capabilities.js +171 -0
  18. package/dist/src/artifacts/framework-index-paths.js +73 -0
  19. package/dist/src/artifacts/framework-source-digest.js +75 -0
  20. package/dist/src/artifacts/index-builder.js +286 -131
  21. package/dist/src/artifacts/index-files.js +132 -18
  22. package/dist/src/artifacts/index-preparation.js +130 -0
  23. package/dist/src/artifacts/index-refresh.js +591 -0
  24. package/dist/src/artifacts/index-worker.mjs +11 -0
  25. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  26. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  27. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  28. package/dist/src/artifacts/query-engine.js +410 -88
  29. package/dist/src/artifacts/relations-builder.js +168 -0
  30. package/dist/src/artifacts/relations.js +148 -0
  31. package/dist/src/artifacts/stats.js +5 -2
  32. package/dist/src/artifacts/types.js +30 -10
  33. package/dist/src/artifacts/watcher.js +1 -1
  34. package/dist/src/auth/credential-store.js +2 -1
  35. package/dist/src/catalog/cli.d.mts +5 -0
  36. package/dist/src/channel/manager.d.mts +20 -0
  37. package/dist/src/channel/manager.mjs +19 -2
  38. package/dist/src/cli/command-log.js +2 -3
  39. package/dist/src/cli/handlers/artifacts.js +3 -0
  40. package/dist/src/cli/handlers/auth.js +3 -0
  41. package/dist/src/cli/handlers/decision.js +19 -12
  42. package/dist/src/cli/handlers/diagnose.js +4 -3
  43. package/dist/src/cli/handlers/effect.js +4 -2
  44. package/dist/src/cli/handlers/evidence.js +3 -0
  45. package/dist/src/cli/handlers/help.js +1 -0
  46. package/dist/src/cli/handlers/index.js +3 -1
  47. package/dist/src/cli/handlers/init.js +4 -1
  48. package/dist/src/cli/handlers/lint.js +5 -0
  49. package/dist/src/cli/handlers/models.js +104 -8
  50. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  51. package/dist/src/cli/handlers/refresh.js +12 -0
  52. package/dist/src/cli/handlers/resource-versions.js +3 -0
  53. package/dist/src/cli/handlers/rules.js +195 -0
  54. package/dist/src/cli/handlers/run.js +4 -0
  55. package/dist/src/cli/handlers/runtime-info.js +1 -2
  56. package/dist/src/cli/handlers/script-runner.js +3 -2
  57. package/dist/src/cli/handlers/session.js +19 -9
  58. package/dist/src/cli/handlers/sessions.js +137 -43
  59. package/dist/src/cli/handlers/setup-manifest.js +39 -44
  60. package/dist/src/cli/handlers/setup.js +41 -0
  61. package/dist/src/cli/handlers/steward.js +11 -0
  62. package/dist/src/cli/handlers/subcommands.js +57 -2
  63. package/dist/src/cli/handlers/uhp.js +3 -0
  64. package/dist/src/cli/handlers/use.js +294 -55
  65. package/dist/src/cli/handlers/utilities.js +26 -10
  66. package/dist/src/cli/handlers/version.js +8 -0
  67. package/dist/src/cli/router.js +15 -1
  68. package/dist/src/cli/services/deployment-verification.js +19 -9
  69. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  70. package/dist/src/cli/skill-usage.js +2 -3
  71. package/dist/src/config/aiwg-config.js +87 -37
  72. package/dist/src/config/cli.js +111 -5
  73. package/dist/src/config/decision-config.js +106 -0
  74. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  75. package/dist/src/config/gitignore.js +50 -0
  76. package/dist/src/config/index-graph-overrides.js +36 -0
  77. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  78. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  79. package/dist/src/config/user-config-dir.d.mts +9 -0
  80. package/dist/src/config/user-config.js +2 -1
  81. package/dist/src/config/user-registry.js +23 -15
  82. package/dist/src/config/user-state.js +7 -0
  83. package/dist/src/decision/ask.js +203 -43
  84. package/dist/src/decision/context-live-qualification.js +2 -2
  85. package/dist/src/decision/driver.js +13 -8
  86. package/dist/src/decision/ensemble/aggregate.js +23 -10
  87. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  88. package/dist/src/decision/entry.js +2 -15
  89. package/dist/src/decision/graph-live-qualification.js +1 -1
  90. package/dist/src/decision/graph-run.js +9 -0
  91. package/dist/src/decision/heldout/collector.js +5 -13
  92. package/dist/src/decision/heldout/contract.js +30 -7
  93. package/dist/src/decision/jev-credentials.js +15 -1
  94. package/dist/src/decision/ledger.js +131 -28
  95. package/dist/src/decision/offload-activation.js +137 -0
  96. package/dist/src/decision/offload.js +550 -0
  97. package/dist/src/decision/qualification/load.js +4 -6
  98. package/dist/src/decision/savings.js +28 -0
  99. package/dist/src/effects/types.js +5 -1
  100. package/dist/src/effects/verifiers/git.js +2 -1
  101. package/dist/src/effects/verifiers/tracker.js +26 -14
  102. package/dist/src/extensions/commands/definitions.js +19 -0
  103. package/dist/src/extensions/project-local-remove.js +36 -1
  104. package/dist/src/gates/providers/loader.js +61 -17
  105. package/dist/src/installation/manager-command.d.mts +18 -0
  106. package/dist/src/installation/manager-command.mjs +14 -2
  107. package/dist/src/installation/manager.d.mts +49 -0
  108. package/dist/src/installation/manager.mjs +78 -3
  109. package/dist/src/issues/cli.js +4 -4
  110. package/dist/src/issues/local.js +24 -5
  111. package/dist/src/jobs/runner.js +1 -1
  112. package/dist/src/lint/cli.js +27 -1
  113. package/dist/src/lint/loader.js +2 -1
  114. package/dist/src/lint/reporters.js +25 -1
  115. package/dist/src/lint/rule-activation.js +43 -0
  116. package/dist/src/lint/runner.js +137 -62
  117. package/dist/src/mcp/adapters/codex-runtime.js +7 -1
  118. package/dist/src/mcp/claude-install.d.mts +4 -0
  119. package/dist/src/mcp/claude-install.mjs +41 -0
  120. package/dist/src/mcp/cli.d.mts +1 -0
  121. package/dist/src/mcp/cli.mjs +36 -11
  122. package/dist/src/mcp/config-file.d.mts +28 -0
  123. package/dist/src/mcp/config-layers.d.mts +36 -0
  124. package/dist/src/mcp/credentials.d.mts +51 -0
  125. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  126. package/dist/src/mcp/grok-build-config.mjs +1 -1
  127. package/dist/src/mcp/helpers.mjs +3 -2
  128. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  129. package/dist/src/mcp/muse-native-config.mjs +1 -0
  130. package/dist/src/mcp/omp-config.d.mts +4 -0
  131. package/dist/src/mcp/registry.js +3 -93
  132. package/dist/src/mcp/registry.mjs +3 -87
  133. package/dist/src/mcp/server-config.d.mts +3 -0
  134. package/dist/src/mcp/server-config.mjs +89 -0
  135. package/dist/src/mcp/server.mjs +37 -375
  136. package/dist/src/mcp/test-simple.mjs +2 -1
  137. package/dist/src/mcp/toml-editor.d.mts +4 -0
  138. package/dist/src/mcp/toml-string.d.mts +1 -0
  139. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  140. package/dist/src/mcp/tool-filters.d.mts +46 -0
  141. package/dist/src/mcp/tools/decision.mjs +12 -11
  142. package/dist/src/mcp/tools/discovery.mjs +62 -55
  143. package/dist/src/mcp/tools/orchestration.mjs +12 -2
  144. package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
  145. package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
  146. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
  147. package/dist/src/mission-protocol/codecs.js +8 -0
  148. package/dist/src/models/config-loader.js +7 -2
  149. package/dist/src/models/model-capabilities.v1.json +96 -4
  150. package/dist/src/models/model-discovery.js +76 -11
  151. package/dist/src/models/provider-policy.js +101 -7
  152. package/dist/src/models/qualification-digest.d.mts +2 -0
  153. package/dist/src/models/resolver.js +12 -10
  154. package/dist/src/models/worker-direct-readiness.js +65 -44
  155. package/dist/src/models/worker-dispatch-offer.js +3 -3
  156. package/dist/src/models/worker-health-readiness.js +16 -3
  157. package/dist/src/models/worker-installation.d.mts +39 -0
  158. package/dist/src/models/worker-installation.mjs +135 -28
  159. package/dist/src/process/bounded-child-process.d.mts +84 -0
  160. package/dist/src/process/bounded-child-process.mjs +43 -0
  161. package/dist/src/process/cli-command.d.mts +16 -0
  162. package/dist/src/process/signal-child.d.mts +6 -0
  163. package/dist/src/process/signal-child.mjs +7 -0
  164. package/dist/src/providers/deployed-skill-roots.js +20 -0
  165. package/dist/src/providers/grok-build-acp.js +3 -2
  166. package/dist/src/providers/grok-build-headless.js +12 -4
  167. package/dist/src/providers/grok-build-inspect.js +1 -0
  168. package/dist/src/providers/omp-agent.d.mts +8 -0
  169. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  170. package/dist/src/providers/omp-paths.d.mts +13 -0
  171. package/dist/src/providers/skill-ownership.d.mts +9 -0
  172. package/dist/src/providers/skill-ownership.mjs +88 -0
  173. package/dist/src/rules/activation.d.mts +21 -0
  174. package/dist/src/rules/activation.mjs +62 -0
  175. package/dist/src/rules/inline-budget.d.mts +26 -0
  176. package/dist/src/rules/inline-budget.mjs +66 -0
  177. package/dist/src/rules/inventory.js +129 -0
  178. package/dist/src/rules/pin-state.js +73 -0
  179. package/dist/src/rules/pins.d.mts +14 -0
  180. package/dist/src/rules/pins.mjs +112 -0
  181. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  182. package/dist/src/serve/mission-conductor.js +38 -12
  183. package/dist/src/serve/worker-invocation.js +30 -10
  184. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  185. package/dist/src/sessions/adapter-registry.js +23 -19
  186. package/dist/src/sessions/adapters/claude.js +39 -54
  187. package/dist/src/sessions/adapters/codex.js +95 -67
  188. package/dist/src/sessions/adapters/copilot.js +4 -4
  189. package/dist/src/sessions/adapters/cursor.js +40 -57
  190. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  191. package/dist/src/sessions/adapters/factory.js +31 -27
  192. package/dist/src/sessions/adapters/generic.js +10 -10
  193. package/dist/src/sessions/adapters/grok-build.js +11 -11
  194. package/dist/src/sessions/adapters/grokbot.js +24 -13
  195. package/dist/src/sessions/adapters/hermes.js +27 -29
  196. package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
  197. package/dist/src/sessions/adapters/muse.js +150 -93
  198. package/dist/src/sessions/adapters/omp.js +28 -14
  199. package/dist/src/sessions/adapters/openclaw.js +89 -79
  200. package/dist/src/sessions/adapters/opencode.js +27 -24
  201. package/dist/src/sessions/adapters/openhuman.js +18 -60
  202. package/dist/src/sessions/adapters/pi.js +29 -14
  203. package/dist/src/sessions/adapters/warp.js +3 -3
  204. package/dist/src/sessions/adapters/windsurf.js +19 -48
  205. package/dist/src/sessions/batch-contracts.js +16 -8
  206. package/dist/src/sessions/batch-import.js +252 -12
  207. package/dist/src/sessions/contracts.js +50 -3
  208. package/dist/src/sessions/coverage-preflight.js +27 -2
  209. package/dist/src/sessions/export-plan.js +4 -3
  210. package/dist/src/sessions/import-exclusions.js +72 -0
  211. package/dist/src/sessions/import-lease.js +34 -18
  212. package/dist/src/sessions/import-limits.js +37 -0
  213. package/dist/src/sessions/importer.js +60 -38
  214. package/dist/src/sessions/lifecycle.js +45 -0
  215. package/dist/src/sessions/policy.js +145 -26
  216. package/dist/src/sessions/readers.js +106 -53
  217. package/dist/src/sessions/refresh.js +105 -0
  218. package/dist/src/sessions/repository.js +151 -54
  219. package/dist/src/sessions/workspace-discovery.js +292 -60
  220. package/dist/src/sessions/workspace-matching.js +107 -0
  221. package/dist/src/skills/importer.js +1 -0
  222. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  223. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  224. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  225. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  226. package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
  227. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  228. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  229. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
  230. package/dist/src/storage/backends/fs.js +18 -76
  231. package/dist/src/storage/backends/obsidian.js +1 -0
  232. package/dist/src/tracker/forge-http.js +2 -1
  233. package/dist/src/update/checker.d.mts +2 -0
  234. package/dist/src/update/service.d.mts +22 -0
  235. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  236. package/dist/src/utils/contained-filesystem.mjs +683 -0
  237. package/dist/src/utils/temporary-directory.js +36 -0
  238. package/dist/src/writing/example-generator.js +1 -1
  239. package/package.json +15 -2
  240. package/schemas/models/model-catalog.v1.schema.json +1 -1
  241. package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
  242. package/tools/_resolve-impl.mjs +13 -15
  243. package/tools/agents/deploy-agents.mjs +49 -21
  244. package/tools/agents/providers/base.mjs +104 -134
  245. package/tools/agents/providers/claude.mjs +8 -24
  246. package/tools/agents/providers/codex.mjs +18 -11
  247. package/tools/agents/providers/cursor.mjs +19 -18
  248. package/tools/agents/providers/grok-build.mjs +3 -1
  249. package/tools/agents/providers/hermes.mjs +57 -47
  250. package/tools/agents/providers/openclaw.mjs +9 -4
  251. package/tools/agents/providers/pi.mjs +5 -0
  252. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  253. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  254. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  255. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  256. package/tools/agents/providers/warp.mjs +20 -26
  257. package/tools/cli/decision-offload-report.mjs +40 -0
  258. package/tools/cli/workspace-status.mjs +862 -0
  259. package/tools/plugin/package-plugins.mjs +186 -8
  260. package/tools/plugin/plugin-links.mjs +114 -0
  261. package/tools/providers/antigravity-transport.mjs +3 -1
  262. package/tools/providers/deepseek-harness-transport.mjs +6 -4
  263. package/tools/providers/omp-transport.mjs +4 -2
  264. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  265. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  266. package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
  267. package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
  268. package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
  269. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  270. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  271. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  272. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  273. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  274. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  275. package/tools/ralph-external/process-monitor.mjs +1017 -0
  276. package/tools/ralph-external/session-launcher.mjs +5057 -0
  277. package/tools/scripts/resource-preflight.mjs +541 -0
  278. package/tools/skills/deploy-skills-codex.mjs +2 -1
  279. package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
@@ -260,26 +260,49 @@ export async function assertHeldoutRequestBounds(corpus, plan, approval) {
260
260
  }
261
261
  }
262
262
  }
263
- export async function planHeldoutCollection(bundle, digest) {
263
+ /** Prior observations are reusable only within the same study, corpus and preregistration. */
264
+ export function heldoutResumeAttempts(bundle, prior) {
265
+ const a = bundle.approval;
266
+ const old = prior.filter(attempt => attempt.study === a.study && attempt.corpusDigest === a.corpusDigest);
267
+ if (old.some(attempt => attempt.preregistrationDigest !== a.preregistrationDigest))
268
+ throw new HeldoutError('changed-preregistration');
269
+ const latest = new Map();
270
+ for (const attempt of old) {
271
+ const key = `${attempt.rowId}/${attempt.requestId}`, previous = latest.get(key);
272
+ if (previous?.result?.disposition !== 'success' && (attempt.result?.disposition === 'success'
273
+ || attempt.ordinal >= (previous?.ordinal ?? 0)))
274
+ latest.set(key, attempt);
275
+ }
276
+ return { old, latest };
277
+ }
278
+ /** The collector and dry-run reserve only unfinished requests, with their unspent retry allowance. */
279
+ export function heldoutPendingRequests(row, plan, latest) {
280
+ return row.requests.flatMap(request => {
281
+ const previous = latest.get(`${row.id}/${request.id}`);
282
+ const first = (previous?.ordinal ?? 0) + 1, attempts = 2 + plan.providerFailurePolicy.maxRetries - first;
283
+ return previous?.result?.disposition === 'success' || attempts <= 0 ? [] : [{ request, first, attempts }];
284
+ });
285
+ }
286
+ export async function planHeldoutCollection(bundle, digest, prior = []) {
264
287
  validateHeldoutBundle(bundle, digest);
265
288
  await assertHeldoutRequestBounds(bundle.corpus, bundle.preregistration, bundle.approval);
266
- let maximumRequestEstimateTokens = 0, tokens = 0, usdMicros = 0;
289
+ const { latest } = heldoutResumeAttempts(bundle, prior);
290
+ let maximumRequestEstimateTokens = 0, tokens = 0, usdMicros = 0, maximumAttempts = 0;
267
291
  const rows = heldoutRowsInScope(bundle);
268
292
  for (const row of rows)
269
- for (const request of row.requests) {
293
+ for (const { request, attempts } of heldoutPendingRequests(row, bundle.preregistration, latest)) {
270
294
  const planned = await heldoutRequest(bundle.corpus, bundle.preregistration, bundle.approval, row, request);
271
295
  maximumRequestEstimateTokens = Math.max(maximumRequestEstimateTokens, planned.estimatedTokens);
272
296
  const reservation = heldoutReservationMicros(bundle.approval, planned.estimatedTokens);
273
297
  if (reservation > Math.floor(bundle.approval.budget.usd * 1_000_000))
274
298
  throw new HeldoutError('approval-call-budget');
275
- const attempts = 1 + bundle.preregistration.providerFailurePolicy.maxRetries;
299
+ maximumAttempts += attempts;
276
300
  tokens += attempts * heldoutReservationTokens(bundle.preregistration, planned.estimatedTokens);
277
301
  usdMicros += attempts * reservation;
278
302
  }
279
- const attempts = rows.reduce((n, r) => n + r.requests.length, 0) * (1 + bundle.preregistration.providerFailurePolicy.maxRetries);
280
303
  const a = bundle.approval;
281
- return { calibration: structuredClone(a.calibration), rowsInScope: rows.map(row => row.id), providerCalls: 0, maximumAttempts: attempts, maximumRequestEstimateTokens, reservedTokens: tokens, reservedUsdMicros: usdMicros,
282
- fitsBeforeStop: attempts <= Math.floor(a.budget.calls * 0.8) && tokens <= Math.floor(a.budget.tokens * 0.8)
304
+ return { calibration: structuredClone(a.calibration), rowsInScope: rows.map(row => row.id), providerCalls: 0, maximumAttempts, maximumRequestEstimateTokens, reservedTokens: tokens, reservedUsdMicros: usdMicros,
305
+ fitsBeforeStop: maximumAttempts <= Math.floor(a.budget.calls * 0.8) && tokens <= Math.floor(a.budget.tokens * 0.8)
283
306
  && usdMicros <= Math.floor(Math.min(a.budget.usd, HELDOUT_CAP_USD[a.study] - a.priorStudySpendUsd, HELDOUT_PORTFOLIO_CAP_USD - a.priorPortfolioSpendUsd) * 800_000) };
284
307
  }
285
308
  /** An intentionally unapproved, unpriced form. Unknown attestations stay null, never fixture approvals. */
@@ -1,3 +1,4 @@
1
+ import { resolveDecisionEnablement } from '../config/decision-config.js';
1
2
  import { constants } from 'node:fs';
2
3
  import { access, chmod, mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
3
4
  import os from 'node:os';
@@ -64,7 +65,8 @@ export async function lookupJevCredential(options = {}) {
64
65
  const forced = environment.AIWG_DECISION_ENABLED;
65
66
  const envToken = environment.JEV_API_KEY?.trim() || environment.AIWG_DECISION_JEV_API_KEY?.trim();
66
67
  const fileRecord = envToken && (forced === '0' || forced === '1') ? null : await readCredentialFile(options);
67
- const enabled = forced === '0' ? false : forced === '1' ? true : fileRecord?.enabled === true;
68
+ const enablement = resolveDecisionEnablement(options);
69
+ const enabled = enablement.source === 'default' ? fileRecord?.enabled === true : enablement.enabled;
68
70
  if (envToken)
69
71
  return {
70
72
  source: 'env',
@@ -78,6 +80,18 @@ export async function lookupJevCredential(options = {}) {
78
80
  return null;
79
81
  return { source: 'file', token: fileRecord.token, region: fileRecord.region, ...(fileRecord.endpoint ? { endpoint: fileRecord.endpoint } : {}), enabled };
80
82
  }
83
+ /** Offline metadata-only inspection. Never probes Jev and never returns credential bytes. */
84
+ export async function inspectJevCredential(options = {}) {
85
+ try {
86
+ const credential = await lookupJevCredential(options);
87
+ if (!credential)
88
+ return { configured: false, enabled: false, source: 'none' };
89
+ return { configured: true, enabled: credential.enabled, source: credential.source };
90
+ }
91
+ catch {
92
+ return { configured: null, enabled: null, source: 'unknown' };
93
+ }
94
+ }
81
95
  export async function resolveJevCredentialBytes(logicalRef, options = {}) {
82
96
  if (logicalRef !== JEV_CREDENTIAL_REF)
83
97
  throw new JevCredentialError('configuration');
@@ -62,14 +62,17 @@ import { projectAiwgPath, projectAiwgWritePath } from '../config/project-artifac
62
62
  import { redactText } from '../governance/redaction.js';
63
63
  import { CanonicalJsonByteEstimator } from './context-plan.js';
64
64
  import { resolveActiveProvider } from '../cli/provider-resolution.js';
65
- export const DECISION_LEDGER_SCHEMA = 'aiwg-decision-ledger-record/v1';
65
+ import { readAiwgConfig } from '../config/aiwg-config.js';
66
+ import { decisionSavingsWeights, estimateDecisionSavings } from './savings.js';
67
+ export const DECISION_LEDGER_SCHEMA = 'aiwg-decision-ledger-record/v2';
68
+ export const DECISION_LEDGER_SCHEMA_V1 = 'aiwg-decision-ledger-record/v1';
66
69
  const DECISION_LEDGER_DIR_NAME = 'decision';
67
70
  const DECISION_LEDGER_FILE_NAME = 'ledger.jsonl';
68
71
  const DECISION_LEDGER_KINDS = ['yes-no', 'choices', 'scale'];
69
72
  const DECISION_LEDGER_STATUSES = ['answered', 'fallback'];
70
73
  /**
71
- * Per-decision-kind reasoning/output token allowance folded into the
72
- * "tokens avoided" estimate for answered calls. Named and recorded with
74
+ * Legacy v1 per-kind allowance, retained only for comparison and replay.
75
+ * Named and recorded with
73
76
  * every estimate (not just referenced) so the figure can be recomputed
74
77
  * later per the issue's proposal item 3.
75
78
  */
@@ -175,7 +178,7 @@ function answerIndexFor(mode, rawAnswer) {
175
178
  function contextMessageForEstimate(input) {
176
179
  return input.context === undefined || input.context === '' ? input.question : `${input.question}\n\nContext:\n${input.context}`;
177
180
  }
178
- function estimateSavings(input, result) {
181
+ function estimateSavingsV1(input, result) {
179
182
  if (result.status === 'answered') {
180
183
  const inputTokens = new CanonicalJsonByteEstimator().estimate(contextMessageForEstimate(input)).tokens;
181
184
  const allowance = DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS[input.mode.kind];
@@ -185,22 +188,24 @@ function estimateSavings(input, result) {
185
188
  // Avoid signed zero (`-0`): a fallback that never called Jev (e.g. not-configured) costs nothing.
186
189
  return { estimatedLlmTokensAvoided: jevCost === 0 ? 0 : -jevCost, estimationMethod: DECISION_SAVINGS_ESTIMATION_METHOD };
187
190
  }
188
- /**
189
- * `--session`, then `AIWG_SESSION_ID`, else the literal "unknown" (no
190
- * harness-specific session env helper exists in this repo yet). Either
191
- * source is rejected down to "unknown" if it does not match the
192
- * attribution identifier shape — this is the safety net for the
193
- * environment-derived value, which (unlike `--session`) has no CLI exit
194
- * code to reject through.
195
- */
196
- export function resolveDecisionSessionId(explicitSessionId, env) {
191
+ /** Ordered harness session markers; never substitute provider/process ids. */
192
+ export const DECISION_SESSION_ENV_VARS = [
193
+ 'AIWG_SESSION_ID', 'CLAUDE_CODE_SESSION_ID', 'CODEX_THREAD_ID', 'CODEX_SESSION_ID',
194
+ 'HERMES_SESSION_ID', 'OPENCODE_SESSION_ID', 'MUSE_SESSION_ID', 'GROK_SESSION_ID', 'WARP_SESSION_ID',
195
+ ];
196
+ export function resolveDecisionSession(explicitSessionId, env) {
197
197
  const explicit = explicitSessionId?.trim();
198
198
  if (explicit && ATTRIBUTION_FIELD_PATTERN.test(explicit))
199
- return explicit;
200
- const fromEnv = env.AIWG_SESSION_ID?.trim();
201
- if (fromEnv && ATTRIBUTION_FIELD_PATTERN.test(fromEnv))
202
- return fromEnv;
203
- return 'unknown';
199
+ return { sessionId: explicit, sessionSource: '--session' };
200
+ for (const key of DECISION_SESSION_ENV_VARS) {
201
+ const value = env[key]?.trim();
202
+ if (value && ATTRIBUTION_FIELD_PATTERN.test(value))
203
+ return { sessionId: value, sessionSource: key };
204
+ }
205
+ return { sessionId: 'unknown', sessionSource: 'unknown' };
206
+ }
207
+ export function resolveDecisionSessionId(explicitSessionId, env) {
208
+ return resolveDecisionSession(explicitSessionId, env).sessionId;
204
209
  }
205
210
  function withinRecordBudget(record) {
206
211
  return Buffer.byteLength(JSON.stringify(record), 'utf8') <= MAX_LEDGER_RECORD_BYTES;
@@ -224,7 +229,9 @@ function enforceRecordBudget(record) {
224
229
  /** Pure, synchronous and side-effect free: builds the record this call would append, fully sanitized. */
225
230
  export function buildDecisionLedgerRecord(input, result, context) {
226
231
  const timestamp = new Date((context.now ?? Date.now)()).toISOString();
227
- const { estimatedLlmTokensAvoided, estimationMethod } = estimateSavings(input, result);
232
+ const legacy = estimateSavingsV1(input, result);
233
+ const inputTokens = new CanonicalJsonByteEstimator().estimate(contextMessageForEstimate(input)).tokens;
234
+ const savings = estimateDecisionSavings(inputTokens, result.status === 'answered', (result.usage?.inputTokens ?? 0) + (result.usage?.outputTokens ?? 0), context.savingsWeights ?? decisionSavingsWeights());
228
235
  const caller = context.caller && ATTRIBUTION_FIELD_PATTERN.test(context.caller) ? context.caller : null;
229
236
  const rawAnswer = result.answer;
230
237
  const answer = typeof rawAnswer === 'string' ? sanitizeField(rawAnswer) : rawAnswer;
@@ -239,6 +246,7 @@ export function buildDecisionLedgerRecord(input, result, context) {
239
246
  transport: context.transport,
240
247
  provider: sanitizeField(context.provider) ?? 'unknown',
241
248
  sessionId: sanitizeField(context.sessionId) ?? 'unknown',
249
+ sessionSource: sanitizeField(context.sessionSource) ?? 'unknown',
242
250
  projectRoot: sanitizeField(context.projectRoot) ?? '',
243
251
  decisionKind: input.mode.kind,
244
252
  choiceIds,
@@ -250,13 +258,22 @@ export function buildDecisionLedgerRecord(input, result, context) {
250
258
  status: result.status,
251
259
  answer,
252
260
  confidence: result.confidence,
261
+ ...(result.answeredBy === undefined ? {} : { answeredBy: result.answeredBy }),
253
262
  threshold: input.threshold ?? 0.8,
254
263
  fallbackReason: result.status === 'fallback' ? result.reason : null,
255
264
  model: typeof result.model === 'string' ? sanitizeField(result.model) : result.model,
256
265
  usage: result.usage,
257
266
  latencyMs: result.latencyMs,
258
- estimatedLlmTokensAvoided,
259
- estimationMethod,
267
+ ...(result.aggregate ? {
268
+ passesRequested: result.aggregate.passesRequested, escalationPasses: result.aggregate.escalationPasses,
269
+ passesRun: result.aggregate.passesRun, escalated: result.aggregate.escalated,
270
+ earlyStop: result.aggregate.earlyStop, agreementRatio: result.aggregate.agreementRatio,
271
+ minAgreement: result.aggregate.minAgreement, agreementConfidenceFloor: result.aggregate.agreementConfidenceFloor,
272
+ passConfidenceSummary: result.aggregate.confidenceSummary,
273
+ } : {}),
274
+ estimatedLlmTokensAvoided: legacy.estimatedLlmTokensAvoided,
275
+ estimationMethodV1: legacy.estimationMethod,
276
+ ...savings,
260
277
  caller: sanitizeField(caller, MAX_METADATA_FIELD_BYTES),
261
278
  };
262
279
  if (context.recordText) {
@@ -480,11 +497,13 @@ async function performRecord(input, result, options, warn, flag) {
480
497
  const provider = await resolveProviderSafely(options);
481
498
  if (flag.cancelled)
482
499
  return null;
483
- const sessionId = resolveDecisionSessionId(options.explicitSessionId, options.env ?? process.env);
500
+ const session = resolveDecisionSession(options.explicitSessionId, options.env ?? process.env);
501
+ const config = await readAiwgConfig(options.projectRoot);
484
502
  const record = buildDecisionLedgerRecord(input, result, {
485
503
  transport: options.transport,
486
504
  provider,
487
- sessionId,
505
+ ...session,
506
+ savingsWeights: decisionSavingsWeights(config?.decision?.ask),
488
507
  projectRoot: options.projectRoot,
489
508
  caller: options.caller,
490
509
  recordText: options.recordText,
@@ -588,7 +607,7 @@ function isDecisionLedgerRecord(value) {
588
607
  if (!value || typeof value !== 'object')
589
608
  return false;
590
609
  const record = value;
591
- if (record.schema !== DECISION_LEDGER_SCHEMA)
610
+ if (record.schema !== DECISION_LEDGER_SCHEMA && record.schema !== DECISION_LEDGER_SCHEMA_V1)
592
611
  return false;
593
612
  if (typeof record.id !== 'string' || record.id.length === 0)
594
613
  return false;
@@ -622,6 +641,8 @@ function isDecisionLedgerRecord(value) {
622
641
  return false;
623
642
  if (!isUnitIntervalOrNull(record.confidence))
624
643
  return false;
644
+ if (record.answeredBy !== undefined && record.answeredBy !== null && record.answeredBy !== 'confidence' && record.answeredBy !== 'agreement')
645
+ return false;
625
646
  if (!isUnitIntervalOrNull(record.threshold) || record.threshold === null)
626
647
  return false;
627
648
  if (record.fallbackReason !== null && typeof record.fallbackReason !== 'string')
@@ -634,10 +655,54 @@ function isDecisionLedgerRecord(value) {
634
655
  return false;
635
656
  if (!isFiniteNumber(record.estimatedLlmTokensAvoided))
636
657
  return false; // may be negative
637
- if (typeof record.estimationMethod !== 'string')
638
- return false;
658
+ if (record.schema === DECISION_LEDGER_SCHEMA_V1) {
659
+ if (typeof record.estimationMethod !== 'string')
660
+ return false;
661
+ }
662
+ else {
663
+ const method = record.estimationMethod;
664
+ if (!method || typeof method !== 'object' || method.version !== 'aiwg-decision-savings/v2')
665
+ return false;
666
+ if (!['standard', 'reasoning'].includes(method.caller_model_class))
667
+ return false;
668
+ for (const key of ['input_tokens', 'reasoning_tokens', 'output_tokens', 'llm_per_mtok', 'jev_per_mtok']) {
669
+ if (!isFiniteNumber(method[key]) || method[key] < 0 || method[key] > 1_000_000)
670
+ return false;
671
+ }
672
+ if (typeof record.sessionSource !== 'string')
673
+ return false;
674
+ for (const key of ['llm_tokens_avoided', 'jev_tokens']) {
675
+ if (!isFiniteNumber(record[key]) || record[key] < 0)
676
+ return false;
677
+ }
678
+ if (!isFiniteNumber(record.cost_weighted_net_usd))
679
+ return false;
680
+ }
639
681
  if (record.caller !== null && typeof record.caller !== 'string')
640
682
  return false;
683
+ for (const key of ['passesRequested', 'escalationPasses', 'passesRun']) {
684
+ if (record[key] !== undefined && (!Number.isSafeInteger(record[key]) || record[key] < (key === 'passesRun' ? 0 : 1)))
685
+ return false;
686
+ }
687
+ if (record.escalated !== undefined && typeof record.escalated !== 'boolean')
688
+ return false;
689
+ if (record.agreementRatio !== undefined && (record.agreementRatio === null || !isUnitIntervalOrNull(record.agreementRatio)))
690
+ return false;
691
+ if (record.minAgreement !== undefined && !isUnitIntervalOrNull(record.minAgreement))
692
+ return false;
693
+ if (record.agreementConfidenceFloor !== undefined && (record.agreementConfidenceFloor === null || !isUnitIntervalOrNull(record.agreementConfidenceFloor)))
694
+ return false;
695
+ if (record.earlyStop !== undefined && record.earlyStop !== null && record.earlyStop !== 'certain-failure' && record.earlyStop !== 'timeout')
696
+ return false;
697
+ if (record.passConfidenceSummary !== undefined) {
698
+ const summary = record.passConfidenceSummary;
699
+ if (!summary || typeof summary !== 'object' || Array.isArray(summary))
700
+ return false;
701
+ if (![summary.count, summary.missing].every(value => Number.isSafeInteger(value) && value >= 0))
702
+ return false;
703
+ if (![summary.mean, summary.min, summary.max].every(isUnitIntervalOrNull))
704
+ return false;
705
+ }
641
706
  return true;
642
707
  }
643
708
  async function* streamDecisionLedgerLines(path) {
@@ -714,20 +779,47 @@ function groupKeyFor(record, groupBy) {
714
779
  return record.sessionId;
715
780
  if (groupBy === 'kind')
716
781
  return record.decisionKind;
782
+ if (groupBy === 'passes')
783
+ return `${record.passesRequested ?? 1}/${record.escalationPasses ?? record.passesRequested ?? 1}`;
784
+ if (groupBy === 'escalation')
785
+ return String(record.escalated ?? false);
717
786
  return record.caller; // may be null; kept distinct from the literal string "unattributed"
718
787
  }
719
788
  function newAccumulator() {
720
789
  return {
790
+ llmTokensAvoided: 0, costWeightedNetUsd: 0, legacyReestimatedCalls: 0,
721
791
  totalCalls: 0, answered: 0, fallback: 0, fallbackCountByReason: {},
792
+ agreementCount: 0, agreementSum: 0, escalatedCalls: 0, passesRun: 0,
722
793
  confidenceCount: 0, confidenceSum: 0, confidenceMin: null, confidenceMax: null,
723
794
  jevInputTokens: 0, jevOutputTokens: 0, answeredJevInputTokens: 0, answeredJevOutputTokens: 0,
724
795
  estimatedLlmTokensAvoidedTotal: 0,
725
796
  };
726
797
  }
727
- function accumulate(acc, record) {
798
+ function accumulate(acc, record, weights) {
728
799
  acc.totalCalls += 1;
800
+ if (record.agreementRatio !== undefined) {
801
+ acc.agreementCount += 1;
802
+ acc.agreementSum += record.agreementRatio;
803
+ }
804
+ if (record.escalated)
805
+ acc.escalatedCalls += 1;
806
+ acc.passesRun += record.passesRun ?? (record.fallbackReason === 'not-configured' ? 0 : 1);
729
807
  const inputTokens = record.usage?.inputTokens ?? 0;
730
808
  const outputTokens = record.usage?.outputTokens ?? 0;
809
+ if (record.schema === DECISION_LEDGER_SCHEMA) {
810
+ acc.llmTokensAvoided += record.llm_tokens_avoided;
811
+ acc.costWeightedNetUsd += record.cost_weighted_net_usd;
812
+ }
813
+ else {
814
+ // v1 answered credit contains the original input estimate plus the known
815
+ // per-kind allowance. Digests alone cannot reconstruct caller text.
816
+ const recoveredInput = record.status === 'answered'
817
+ ? Math.max(0, record.estimatedLlmTokensAvoided - DECISION_SAVINGS_REASONING_ALLOWANCE_TOKENS[record.decisionKind]) : 0;
818
+ const estimate = estimateDecisionSavings(recoveredInput, record.status === 'answered', inputTokens + outputTokens, weights);
819
+ acc.llmTokensAvoided += estimate.llm_tokens_avoided;
820
+ acc.costWeightedNetUsd += estimate.cost_weighted_net_usd;
821
+ acc.legacyReestimatedCalls += 1;
822
+ }
731
823
  if (record.status === 'answered') {
732
824
  acc.answered += 1;
733
825
  if (record.confidence !== null) {
@@ -755,10 +847,17 @@ function finalizeGroup(key, acc) {
755
847
  }
756
848
  return {
757
849
  key,
850
+ llm_tokens_avoided: acc.llmTokensAvoided,
851
+ jev_tokens: acc.jevInputTokens + acc.jevOutputTokens,
852
+ cost_weighted_net_usd: acc.costWeightedNetUsd,
853
+ legacyReestimatedCalls: acc.legacyReestimatedCalls,
758
854
  totalCalls: acc.totalCalls,
759
855
  answered: acc.answered,
760
856
  fallback: acc.fallback,
761
857
  fallbackRate: acc.totalCalls ? acc.fallback / acc.totalCalls : 0,
858
+ hitRate: acc.totalCalls ? acc.answered / acc.totalCalls : 0,
859
+ agreement: { count: acc.agreementCount, mean: acc.agreementCount ? acc.agreementSum / acc.agreementCount : null },
860
+ escalatedCalls: acc.escalatedCalls, passesRun: acc.passesRun,
762
861
  fallbackCountByReason: acc.fallbackCountByReason,
763
862
  fallbackRateByReason,
764
863
  confidence: {
@@ -768,12 +867,15 @@ function finalizeGroup(key, acc) {
768
867
  max: acc.confidenceMax,
769
868
  },
770
869
  jevUsage: { inputTokens: acc.jevInputTokens, outputTokens: acc.jevOutputTokens },
870
+ tokensPerAnsweredDecision: acc.answered ? (acc.jevInputTokens + acc.jevOutputTokens) / acc.answered : null,
771
871
  estimatedLlmTokensAvoidedTotal: acc.estimatedLlmTokensAvoidedTotal,
772
872
  netSavingsTokens: acc.estimatedLlmTokensAvoidedTotal - (acc.answeredJevInputTokens + acc.answeredJevOutputTokens),
773
873
  };
774
874
  }
775
875
  /** Reads, validates, filters and aggregates the ledger offline, streaming line-by-line with incremental min/max; no network, no Jev call. Tolerates a missing ledger file (empty result). */
776
876
  export async function decisionLedgerStats(projectRoot, filters = {}) {
877
+ const config = await readAiwgConfig(projectRoot);
878
+ const weights = decisionSavingsWeights(config?.decision?.ask);
777
879
  const groups = new Map();
778
880
  let skipped = 0;
779
881
  for await (const record of streamDecisionLedgerRecords(projectRoot)) {
@@ -789,7 +891,7 @@ export async function decisionLedgerStats(projectRoot, filters = {}) {
789
891
  acc = newAccumulator();
790
892
  groups.set(key, acc);
791
893
  }
792
- accumulate(acc, record);
894
+ accumulate(acc, record, weights);
793
895
  }
794
896
  const sortedKeys = [...groups.keys()].sort((a, b) => (a ?? '').localeCompare(b ?? ''));
795
897
  return {
@@ -798,6 +900,7 @@ export async function decisionLedgerStats(projectRoot, filters = {}) {
798
900
  skipped,
799
901
  groups: sortedKeys.map(key => finalizeGroup(key, groups.get(key))),
800
902
  filters: { ...filters },
903
+ legacyEstimationWeights: weights,
801
904
  };
802
905
  }
803
906
  // ---------------------------------------------------------------------------
@@ -0,0 +1,137 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { getConfigPath, readAiwgConfigCandidate } from '../config/aiwg-config.js';
4
+ import { inspectUserRegistry } from '../config/user-registry.js';
5
+ import { resolveDecisionEnablement } from '../config/decision-config.js';
6
+ import { projectAiwgPath } from '../config/project-artifacts.js';
7
+ import * as credentials from './jev-credentials.js';
8
+ export const DECISION_COMPACT_ANCHOR = 'Decision offload: before the first eligible bounded choice, including ordinary triage or worker routing, read `aiwg show rule decision-offload` and use the CLI or MCP ask capability. On fallback decide normally. Answers are advisory and do not authorize actions.';
9
+ function isInstalledEntry(value) {
10
+ if (!value || typeof value !== 'object' || Array.isArray(value))
11
+ return false;
12
+ const entry = value;
13
+ return typeof entry.version === 'string' && entry.version.trim().length > 0
14
+ && typeof entry.source === 'string' && entry.source.trim().length > 0
15
+ // Older registry/config entries can predate installedAt, so its absence is
16
+ // compatible; a present non-string timestamp is corrupt evidence.
17
+ && (entry.installedAt === undefined || typeof entry.installedAt === 'string')
18
+ && Boolean(entry.deployedTo) && typeof entry.deployedTo === 'object' && !Array.isArray(entry.deployedTo);
19
+ }
20
+ async function inspectProjectInstall(cwd) {
21
+ // A positive marker is sufficient installation evidence even when an
22
+ // unrelated user-scope registry or config section is unreadable. Negative
23
+ // evidence still goes through the full candidate validator below.
24
+ const local = getConfigPath(cwd);
25
+ const candidates = [local];
26
+ for (const file of candidates) {
27
+ try {
28
+ const parsed = JSON.parse(await readFile(file, 'utf8'));
29
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
30
+ const installed = parsed.installed;
31
+ if (installed && typeof installed === 'object' && !Array.isArray(installed)
32
+ && Object.hasOwn(installed, 'decision-engine')) {
33
+ return isInstalledEntry(installed['decision-engine']) ? true : null;
34
+ }
35
+ }
36
+ // A valid local config wins over compatibility/artifact copies.
37
+ if (file === local)
38
+ break;
39
+ }
40
+ catch (error) {
41
+ if (error.code === 'ENOENT') {
42
+ if (file === local) {
43
+ try {
44
+ const artifact = projectAiwgPath(cwd, 'aiwg.config');
45
+ if (!candidates.includes(artifact))
46
+ candidates.push(artifact);
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ continue;
53
+ }
54
+ return null;
55
+ }
56
+ }
57
+ try {
58
+ // Installation metadata is independent of worker-model defaults. Reading
59
+ // the validated candidate avoids turning an unrelated missing defaults
60
+ // bundle into unknown addon-installation evidence.
61
+ const config = await readAiwgConfigCandidate(cwd);
62
+ const installed = config?.installed ?? {};
63
+ return Object.hasOwn(installed, 'decision-engine')
64
+ ? isInstalledEntry(installed['decision-engine']) ? true : null
65
+ : false;
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ async function inspectUserInstall() {
72
+ const inspected = await inspectUserRegistry();
73
+ if (Object.hasOwn(inspected.registry.installed, 'decision-engine')) {
74
+ return isInstalledEntry(inspected.registry.installed['decision-engine']) ? true : null;
75
+ }
76
+ return inspected.status === 'unreadable' ? null : false;
77
+ }
78
+ function combineInstallEvidence(project, user) {
79
+ if (project === true || user === true)
80
+ return true;
81
+ if (project === null || user === null)
82
+ return null;
83
+ return false;
84
+ }
85
+ async function inspectReadableFile(file) {
86
+ try {
87
+ await readFile(file);
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ return error.code === 'ENOENT' ? false : null;
92
+ }
93
+ }
94
+ async function inspectAnchor(file) {
95
+ try {
96
+ return (await readFile(file, 'utf8')).includes(DECISION_COMPACT_ANCHOR);
97
+ }
98
+ catch (error) {
99
+ return error.code === 'ENOENT' ? false : null;
100
+ }
101
+ }
102
+ /** Read configuration evidence only; native session loading is not observable here. */
103
+ export async function inspectDecisionOffloadActivation(options) {
104
+ const [projectInstalled, userInstalled] = await Promise.all([
105
+ inspectProjectInstall(options.cwd),
106
+ inspectUserInstall(),
107
+ ]);
108
+ const installed = combineInstallEvidence(projectInstalled, userInstalled);
109
+ let evaluateEnabled = null;
110
+ try {
111
+ evaluateEnabled = resolveDecisionEnablement({ cwd: options.cwd, env: options.env }).enabled;
112
+ }
113
+ catch { /* invalid settings are unknown */ }
114
+ // The inspector is supplied by the shared decision runtime. The compatibility
115
+ // branch preserves older installed runtimes and discards credential material.
116
+ const runtime = credentials;
117
+ let askEnabled = installed === null ? null : false;
118
+ if (installed === true) {
119
+ try {
120
+ const ask = runtime.inspectJevCredential
121
+ ? await runtime.inspectJevCredential({ cwd: options.cwd, env: options.env })
122
+ : await credentials.lookupJevCredential({ cwd: options.cwd, env: options.env }).then(value => ({ configured: Boolean(value), enabled: value?.enabled ?? false }));
123
+ askEnabled = ask.configured === null || ask.enabled === null ? null : ask.configured && ask.enabled;
124
+ }
125
+ catch {
126
+ askEnabled = null;
127
+ }
128
+ }
129
+ const rulePath = options.frameworkRoot && path.join(options.frameworkRoot, 'agentic/code/addons/decision-engine/rules/decision-offload.md');
130
+ const ruleSourcePresent = rulePath ? await inspectReadableFile(rulePath) : null;
131
+ const startupFile = path.join(options.cwd, 'AIWG.md');
132
+ const startupAnchorPresent = await inspectAnchor(startupFile);
133
+ return { installed, askEnabled, evaluateEnabled,
134
+ shouldAnchor: installed === true && askEnabled === true, ruleSourcePresent, ruleDelivery: 'unknown',
135
+ startupAnchorPresent, nativeActivation: 'not-claimed' };
136
+ }
137
+ //# sourceMappingURL=offload-activation.js.map