@aiwg/cli 2026.10.5 → 2026.10.7

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 (263) 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 +7 -3
  6. package/dist/src/a2a/agent-card.js +11 -0
  7. package/dist/src/api/index.d.ts +1 -0
  8. package/dist/src/api/index.js +1 -0
  9. package/dist/src/artifacts/browser-export.js +1 -0
  10. package/dist/src/artifacts/citation-parser.js +8 -1
  11. package/dist/src/artifacts/cli.js +57 -6
  12. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  13. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  14. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
  15. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
  16. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
  17. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
  18. package/dist/src/artifacts/fortemi-shard-export.js +284 -0
  19. package/dist/src/artifacts/graph-query.js +18 -8
  20. package/dist/src/artifacts/index-adapters.js +339 -0
  21. package/dist/src/artifacts/index-builder.js +143 -14
  22. package/dist/src/artifacts/index-files.js +15 -4
  23. package/dist/src/artifacts/index-preparation.js +2 -0
  24. package/dist/src/artifacts/index-reader.js +44 -0
  25. package/dist/src/artifacts/index-refresh.js +114 -55
  26. package/dist/src/artifacts/index-status.js +20 -5
  27. package/dist/src/artifacts/memory-maintenance.js +503 -0
  28. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  29. package/dist/src/artifacts/query-engine.js +263 -108
  30. package/dist/src/artifacts/relations-builder.js +2 -2
  31. package/dist/src/artifacts/source-graph.js +2 -2
  32. package/dist/src/artifacts/types.js +9 -1
  33. package/dist/src/auth/client.js +5 -1
  34. package/dist/src/auth/credential-store.js +5 -1
  35. package/dist/src/auth/sensitive-input.js +151 -0
  36. package/dist/src/channel/manager.mjs +3 -0
  37. package/dist/src/cli/handlers/agentcard.js +47 -25
  38. package/dist/src/cli/handlers/auth.js +22 -2
  39. package/dist/src/cli/handlers/decision.js +22 -3
  40. package/dist/src/cli/handlers/effect.js +4 -84
  41. package/dist/src/cli/handlers/guided-setup.js +470 -0
  42. package/dist/src/cli/handlers/help.js +2 -2
  43. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  44. package/dist/src/cli/handlers/index.js +4 -2
  45. package/dist/src/cli/handlers/init.js +41 -6
  46. package/dist/src/cli/handlers/install.js +6 -0
  47. package/dist/src/cli/handlers/local-executor.js +5 -3
  48. package/dist/src/cli/handlers/marketplace.js +15 -4
  49. package/dist/src/cli/handlers/mc.js +509 -53
  50. package/dist/src/cli/handlers/models.js +32 -2
  51. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  52. package/dist/src/cli/handlers/rules.js +1 -1
  53. package/dist/src/cli/handlers/script-runner.js +73 -2
  54. package/dist/src/cli/handlers/serve.js +5 -3
  55. package/dist/src/cli/handlers/sessions.js +1 -0
  56. package/dist/src/cli/handlers/setup.js +432 -1
  57. package/dist/src/cli/handlers/skill-lint.js +1 -1
  58. package/dist/src/cli/handlers/subcommands.js +118 -8
  59. package/dist/src/cli/handlers/use.js +335 -107
  60. package/dist/src/cli/handlers/workspace.js +3 -1
  61. package/dist/src/cli/prompt-utils.js +81 -0
  62. package/dist/src/cli/router.js +16 -1
  63. package/dist/src/config/aiwg-config.js +42 -0
  64. package/dist/src/config/cli.js +7 -3
  65. package/dist/src/config/decision-config.js +14 -1
  66. package/dist/src/config/gitignore.js +4 -0
  67. package/dist/src/config/native-sandbox-warnings.js +25 -0
  68. package/dist/src/config/workspace.js +2 -1
  69. package/dist/src/contributors/validation.js +6 -5
  70. package/dist/src/decision/adapters/jev.js +20 -31
  71. package/dist/src/decision/admission.js +7 -3
  72. package/dist/src/decision/ask.js +6 -1
  73. package/dist/src/decision/benchmark.js +482 -0
  74. package/dist/src/decision/ledger.js +22 -3
  75. package/dist/src/decision/pricing.js +147 -0
  76. package/dist/src/decision/qualification/gate-evidence.js +23 -18
  77. package/dist/src/decision/qualification/load-staged.js +576 -0
  78. package/dist/src/decision/qualification/load.js +109 -33
  79. package/dist/src/decision/qualification/runner.js +12 -5
  80. package/dist/src/decision/review/service.js +6 -4
  81. package/dist/src/decision/review/validate.js +23 -2
  82. package/dist/src/decision/savings.js +8 -3
  83. package/dist/src/discovery/index.js +492 -0
  84. package/dist/src/discovery/interview.js +238 -0
  85. package/dist/src/discovery/plan-apply.js +567 -0
  86. package/dist/src/discovery/plan-store.js +171 -0
  87. package/dist/src/discovery/plan-types.js +2 -0
  88. package/dist/src/discovery/plan-validation.js +139 -0
  89. package/dist/src/discovery/types.js +2 -0
  90. package/dist/src/effects/host.js +91 -0
  91. package/dist/src/extensions/commands/definitions.js +49 -9
  92. package/dist/src/extensions/manifest.js +71 -44
  93. package/dist/src/extensions/project-local-discovery.js +1 -1
  94. package/dist/src/extensions/project-local-gitignore.js +2 -0
  95. package/dist/src/extensions/project-quickref.js +18 -17
  96. package/dist/src/extensions/validation.js +32 -29
  97. package/dist/src/housekeeper/daemon-config.js +148 -0
  98. package/dist/src/housekeeper/ipc.js +84 -0
  99. package/dist/src/housekeeper/lifecycle.js +148 -0
  100. package/dist/src/housekeeper/platform.js +35 -0
  101. package/dist/src/housekeeper/status.js +70 -0
  102. package/dist/src/housekeeper/units.js +114 -0
  103. package/dist/src/issues/cli.js +16 -5
  104. package/dist/src/issues/live.js +44 -12
  105. package/dist/src/jobs/flow.js +22 -21
  106. package/dist/src/lint/rule-activation.js +20 -5
  107. package/dist/src/lint/runner.js +1 -1
  108. package/dist/src/marketplace/exchange.js +18 -6
  109. package/dist/src/marketplace/provenance.js +3 -2
  110. package/dist/src/mcp/adapters/codex-runtime.js +46 -76
  111. package/dist/src/mcp/cli.mjs +22 -96
  112. package/dist/src/mcp/config-file.mjs +6 -2
  113. package/dist/src/mcp/config-layers.mjs +23 -7
  114. package/dist/src/mcp/grok-build-config.mjs +2 -1
  115. package/dist/src/mcp/muse-native-config.mjs +2 -1
  116. package/dist/src/mcp/profiles.js +18 -31
  117. package/dist/src/mcp/profiles.mjs +18 -32
  118. package/dist/src/mcp/registry.js +7 -27
  119. package/dist/src/mcp/registry.mjs +7 -28
  120. package/dist/src/mcp/server.mjs +34 -1
  121. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  122. package/dist/src/mcp/tools/command-run.mjs +3 -2
  123. package/dist/src/mcp/tools/decision.mjs +49 -48
  124. package/dist/src/mcp/tools/discovery.mjs +46 -21
  125. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  126. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  127. package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
  128. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  129. package/dist/src/memory/audit.js +719 -0
  130. package/dist/src/memory/canonical-context.js +392 -13
  131. package/dist/src/memory/canonical-maintenance.js +95 -0
  132. package/dist/src/memory/cli-result.js +22 -0
  133. package/dist/src/memory/cli.js +176 -0
  134. package/dist/src/memory/context-pack.js +57 -6
  135. package/dist/src/memory/cost.js +512 -0
  136. package/dist/src/memory/firewall-classification.d.mts +7 -0
  137. package/dist/src/memory/firewall-classification.mjs +32 -0
  138. package/dist/src/memory/index.js +8 -0
  139. package/dist/src/memory/line-maintenance.js +68 -0
  140. package/dist/src/memory/maintenance-cli.js +286 -0
  141. package/dist/src/memory/maintenance-contract.js +136 -0
  142. package/dist/src/memory/maintenance-index.js +18 -0
  143. package/dist/src/memory/maintenance-owner.js +2 -0
  144. package/dist/src/memory/maintenance-record.js +138 -0
  145. package/dist/src/memory/maintenance.js +254 -0
  146. package/dist/src/memory/project-registry.js +2 -1
  147. package/dist/src/memory/provider-memory-adapter.js +154 -0
  148. package/dist/src/memory/recommend-cli.js +83 -0
  149. package/dist/src/memory/recommend.js +269 -0
  150. package/dist/src/memory/scheduled.js +81 -0
  151. package/dist/src/memory/taxonomy.js +44 -0
  152. package/dist/src/mission-protocol/codecs.js +19 -9
  153. package/dist/src/mission-protocol/worker-status.js +91 -1
  154. package/dist/src/models/config-loader.js +11 -1
  155. package/dist/src/models/model-capabilities.v1.json +74 -50
  156. package/dist/src/models/provider-policy.js +125 -62
  157. package/dist/src/models/resolver.js +45 -8
  158. package/dist/src/models/worker-installation.d.mts +8 -1
  159. package/dist/src/models/worker-installation.mjs +231 -38
  160. package/dist/src/models/wrapper-route.js +1 -1
  161. package/dist/src/output-modes/registry.js +8 -7
  162. package/dist/src/packages/adapters/git.js +99 -23
  163. package/dist/src/packages/adapters/gitea.js +8 -27
  164. package/dist/src/packages/adapters/github.js +7 -25
  165. package/dist/src/packages/registry.js +4 -4
  166. package/dist/src/plugin/metadata-validator.js +1 -1
  167. package/dist/src/process/cli-command.d.mts +5 -0
  168. package/dist/src/process/cli-command.mjs +42 -0
  169. package/dist/src/providers/deploy-version.d.mts +1 -0
  170. package/dist/src/providers/deploy-version.mjs +17 -0
  171. package/dist/src/providers/provider-definitions.js +19 -16
  172. package/dist/src/providers/skill-ownership.d.mts +11 -2
  173. package/dist/src/providers/skill-ownership.mjs +35 -17
  174. package/dist/src/rules/activation.d.mts +11 -2
  175. package/dist/src/rules/activation.mjs +82 -6
  176. package/dist/src/rules/inventory.js +1 -1
  177. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  178. package/dist/src/schema/zod-validation.d.mts +6 -0
  179. package/dist/src/schema/zod-validation.mjs +50 -0
  180. package/dist/src/serve/executor-registry.js +297 -20
  181. package/dist/src/serve/executor-token.d.mts +1 -0
  182. package/dist/src/serve/executor-token.mjs +8 -0
  183. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  184. package/dist/src/serve/worker-invocation.js +124 -15
  185. package/dist/src/sessions/adapters/claude.js +19 -18
  186. package/dist/src/sessions/adapters/codex.js +10 -9
  187. package/dist/src/sessions/adapters/copilot.js +14 -13
  188. package/dist/src/sessions/adapters/cursor.js +19 -18
  189. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  190. package/dist/src/sessions/adapters/factory.js +11 -10
  191. package/dist/src/sessions/adapters/generic.js +32 -23
  192. package/dist/src/sessions/adapters/grokbot.js +35 -35
  193. package/dist/src/sessions/adapters/hermes.js +20 -19
  194. package/dist/src/sessions/adapters/muse.js +50 -49
  195. package/dist/src/sessions/adapters/omp.js +5 -4
  196. package/dist/src/sessions/adapters/openclaw.js +30 -29
  197. package/dist/src/sessions/adapters/opencode.js +26 -26
  198. package/dist/src/sessions/adapters/openhuman.js +24 -23
  199. package/dist/src/sessions/adapters/pi.js +5 -4
  200. package/dist/src/sessions/adapters/windsurf.js +8 -8
  201. package/dist/src/sessions/analytics.js +10 -9
  202. package/dist/src/sessions/candidates.js +7 -6
  203. package/dist/src/sessions/contracts.js +63 -53
  204. package/dist/src/sessions/output-registration.js +11 -10
  205. package/dist/src/sessions/workspace-discovery.js +3 -4
  206. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  207. package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
  208. package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
  209. package/dist/src/smiths/context-pipeline/finalization.js +3 -2
  210. package/dist/src/smiths/context-pipeline/generator.js +7 -19
  211. package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
  212. package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
  213. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  214. package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
  215. package/dist/src/tracker/project-inputs.js +2 -0
  216. package/dist/src/update/checker.d.mts +4 -0
  217. package/dist/src/update/checker.mjs +14 -0
  218. package/dist/src/update/notifier.mjs +2 -4
  219. package/dist/src/writing/exemplar-selection.js +6 -6
  220. package/dist/src/writing/voice-evaluation.js +24 -23
  221. package/dist/src/writing/writer-migration.js +14 -13
  222. package/dist/src/writing/writer-profile.js +19 -18
  223. package/dist/src/writing/writing-brief.js +18 -17
  224. package/dist/src/writing/writing-receipt.js +47 -46
  225. package/package.json +5 -2
  226. package/schemas/decision/DecisionReview.schema.json +78 -1
  227. package/schemas/mission-v1.schema.json +3 -0
  228. package/tools/agents/deploy-agents.mjs +67 -39
  229. package/tools/agents/providers/antigravity.mjs +6 -1
  230. package/tools/agents/providers/base.mjs +24 -14
  231. package/tools/agents/providers/claude.mjs +6 -3
  232. package/tools/agents/providers/codex.mjs +54 -57
  233. package/tools/agents/providers/context-write.mjs +28 -0
  234. package/tools/agents/providers/copilot.mjs +13 -4
  235. package/tools/agents/providers/cursor.mjs +1 -1
  236. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  237. package/tools/agents/providers/factory.mjs +7 -1
  238. package/tools/agents/providers/grok-build.mjs +18 -2
  239. package/tools/agents/providers/grokbot.mjs +1 -1
  240. package/tools/agents/providers/hermes.mjs +12 -15
  241. package/tools/agents/providers/muse-hooks.mjs +21 -14
  242. package/tools/agents/providers/muse.mjs +7 -2
  243. package/tools/agents/providers/omp.mjs +31 -5
  244. package/tools/agents/providers/opencode.mjs +14 -5
  245. package/tools/agents/providers/openhuman.mjs +1 -1
  246. package/tools/agents/providers/pi.mjs +1 -1
  247. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  248. package/tools/agents/providers/rule-delivery.mjs +289 -80
  249. package/tools/agents/providers/warp.mjs +4 -1
  250. package/tools/agents/providers/windsurf.mjs +13 -3
  251. package/tools/cli/workspace-status.mjs +5 -0
  252. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  253. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  254. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
  255. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
  256. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  257. package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
  258. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  259. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  260. package/tools/ralph-external/process-monitor.mjs +49 -8
  261. package/tools/ralph-external/session-launcher.mjs +405 -35
  262. package/tools/skills/deploy-skills-codex.mjs +11 -7
  263. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -13,6 +13,7 @@ import { getFrameworkRoot } from '../../channel/manager.mjs';
13
13
  import { maybePrintCommunityFooter } from '../../community/footer.js';
14
14
  import { buildDeploymentStatusProbe } from '../services/deployment-verification.js';
15
15
  import { detectScope } from '../scope-resolver.js';
16
+ import { readNativeSandboxWarnings } from '../../config/native-sandbox-warnings.js';
16
17
  function statusProjectRoot(args, fallback) {
17
18
  const valueFlags = new Set(['--scope', '--provider', '--bundle']);
18
19
  for (let index = 0; index < args.length; index += 1) {
@@ -57,9 +58,10 @@ export const statusHandler = {
57
58
  });
58
59
  const { inspectGrokBuildNative } = await import('../../mcp/grok-build-config.mjs');
59
60
  const grokBuildNative = await inspectGrokBuildNative({ projectDir: projectRoot });
61
+ const nativeSandboxWarnings = await readNativeSandboxWarnings(projectRoot);
60
62
  return {
61
63
  exitCode: probe.status === 'needs-repair' ? 1 : 0,
62
- message: JSON.stringify({ ...probe, grokBuildNative }, null, 2),
64
+ message: JSON.stringify({ ...probe, grokBuildNative, nativeSandboxWarnings }, null, 2),
63
65
  rawOutput: true,
64
66
  };
65
67
  }
@@ -16,6 +16,7 @@
16
16
  * migration mechanical.
17
17
  */
18
18
  import readline from 'readline';
19
+ import { Writable } from 'node:stream';
19
20
  import * as ui from './ui.js';
20
21
  /**
21
22
  * Resolve the prompt timeout from env. Falsy / non-numeric / non-positive
@@ -36,6 +37,13 @@ export function createPromptInterface() {
36
37
  output: process.stdout,
37
38
  });
38
39
  }
40
+ /** Readline still owns timeout/cancellation; discard terminal echo for secret input. */
41
+ export function createSecretPromptInterface() {
42
+ const output = new Writable({ write(_chunk, _encoding, done) { done(); } });
43
+ const rl = readline.createInterface({ input: process.stdin, output, terminal: true });
44
+ rl.once('close', () => output.destroy());
45
+ return rl;
46
+ }
39
47
  /**
40
48
  * Ask a single question with a hard timeout. On timeout, resolves to `fallback`
41
49
  * and emits a warn-level UI message describing what default was used.
@@ -80,7 +88,16 @@ export function askWithTimeout(rl, prompt, parse, fallback, fallbackLabel, timeo
80
88
  const cleanup = () => {
81
89
  clearTimeout(timer);
82
90
  signal?.removeEventListener('abort', onAbort);
91
+ rl.removeListener('close', onClose);
83
92
  };
93
+ const onClose = () => {
94
+ if (settled)
95
+ return;
96
+ settled = true;
97
+ cleanup();
98
+ resolve(fallback);
99
+ };
100
+ rl.on('close', onClose);
84
101
  if (signal?.aborted) {
85
102
  // Fire synchronously so the caller's try/catch sees the reject without
86
103
  // awaiting a tick.
@@ -104,6 +121,70 @@ export function askWithTimeout(rl, prompt, parse, fallback, fallbackLabel, timeo
104
121
  export async function askString(rl, prompt, fallback = '', signal) {
105
122
  return askWithTimeout(rl, prompt, (a) => a.trim(), fallback, fallback || '(empty)', promptTimeoutMs(), signal);
106
123
  }
124
+ /** Collect a hidden paste through an empty line, with one overall prompt deadline. */
125
+ export function askMultilineSecret(rl, signal) {
126
+ return new Promise((resolve, reject) => {
127
+ const lines = [];
128
+ let length = 0;
129
+ let settled = false;
130
+ const cleanup = () => {
131
+ clearTimeout(timer);
132
+ signal?.removeEventListener('abort', onAbort);
133
+ rl.removeListener('line', onLine);
134
+ rl.removeListener('close', onClose);
135
+ lines.length = 0;
136
+ };
137
+ const finish = (value) => {
138
+ if (settled)
139
+ return;
140
+ settled = true;
141
+ cleanup();
142
+ resolve(value);
143
+ };
144
+ const onLine = (line) => {
145
+ if (!line) {
146
+ finish(lines.join('\n'));
147
+ return;
148
+ }
149
+ length += line.length + (lines.length ? 1 : 0);
150
+ if (length > 16384) {
151
+ settled = true;
152
+ cleanup();
153
+ reject(new Error('Sensitive input exceeds 16384 characters.'));
154
+ return;
155
+ }
156
+ lines.push(line);
157
+ };
158
+ // A closed terminal or timeout discards incomplete input, never echoes it.
159
+ const onClose = () => finish('');
160
+ const onAbort = () => {
161
+ if (settled)
162
+ return;
163
+ settled = true;
164
+ cleanup();
165
+ try {
166
+ rl.close();
167
+ }
168
+ catch { /* already closed */ }
169
+ reject(signal.reason ?? new Error('Aborted'));
170
+ };
171
+ const timeoutMs = promptTimeoutMs();
172
+ const timer = setTimeout(() => {
173
+ if (settled)
174
+ return;
175
+ ui.warn(` No input completed within ${Math.round(timeoutMs / 1000)}s — discarding incomplete sensitive input`);
176
+ finish('');
177
+ }, timeoutMs);
178
+ timer.unref?.();
179
+ rl.on('line', onLine);
180
+ rl.on('close', onClose);
181
+ if (signal?.aborted) {
182
+ onAbort();
183
+ return;
184
+ }
185
+ signal?.addEventListener('abort', onAbort, { once: true });
186
+ });
187
+ }
107
188
  /**
108
189
  * Prompt for a yes/no answer with timeout and default. Answers starting with
109
190
  * 'y' (case-insensitive) are considered yes; everything else is no. On
@@ -176,6 +176,20 @@ export async function run(args, options = {}) {
176
176
  }
177
177
  // Build context for handler and hooks
178
178
  const ctx = await buildContext(commandArgs, args, options);
179
+ // Read-only memory views (audit, cost, recommend, plan, verify, --preview) cannot invoke mutation-capable hooks or append command telemetry.
180
+ // Keep dispatch through the existing memory handler and strict parser.
181
+ if (commandId === 'memory' && (['audit', 'cost', 'recommend', 'plan', 'verify'].includes(commandArgs[0]) || commandArgs.includes('--preview'))) {
182
+ const result = await handler.execute(ctx);
183
+ if (result.message) {
184
+ if (result.exitCode !== 0)
185
+ ui.error(result.message);
186
+ else
187
+ ui.info(result.message);
188
+ }
189
+ if (result.exitCode !== 0)
190
+ process.exit(result.exitCode);
191
+ return;
192
+ }
179
193
  // Build hook context
180
194
  const hookCtx = {
181
195
  event: 'pre-command',
@@ -332,8 +346,9 @@ async function buildContext(args, rawArgs, options) {
332
346
  const readOnlyRuleInventory = rawArgs[0] === 'rules' && ['inventory', 'list'].includes(args[0]);
333
347
  const readOnlyOffloadReport = (['doctor', '-doctor', '--doctor'].includes(rawArgs[0] ?? '') && args.includes('--decision-offload'))
334
348
  || (rawArgs[0] === 'steward' && args[0] === 'decision-offload');
349
+ const readOnlyMemoryAudit = rawArgs[0] === 'memory' && (['audit', 'cost', 'recommend', 'plan', 'verify'].includes(args[0]) || args.includes('--preview') || args.includes('--help') || args.includes('-h'));
335
350
  const frameworkRoot = await getFrameworkRoot({
336
- createIfMissing: !args.includes('--dry-run') && !readOnlyRuleInventory && !readOnlyOffloadReport,
351
+ createIfMissing: !args.includes('--dry-run') && !readOnlyRuleInventory && !readOnlyOffloadReport && !readOnlyMemoryAudit,
337
352
  });
338
353
  const ctx = {
339
354
  args,
@@ -201,6 +201,17 @@ export function validateParallelismConfig(value) {
201
201
  error(`clis/${id}/adapter`, 'Unknown provider binding');
202
202
  }
203
203
  for (const [id, group] of Object.entries(policy.groups ?? {})) {
204
+ if (group.execution?.launchMode === 'native-sandbox'
205
+ && (!['read-only', 'workspace-write'].includes(group.execution.sandbox ?? '')
206
+ || (group.execution.transport ?? 'shell') !== 'shell')) {
207
+ error(`groups/${id}/execution/launchMode`, 'native-sandbox requires an enforcing read-only or workspace-write shell sandbox');
208
+ }
209
+ if (group.execution?.launchMode === 'native-sandbox') {
210
+ if (group.execution.approval === 'on-request')
211
+ error(`groups/${id}/execution/approval`, 'native-sandbox requires approval="never"');
212
+ if (group.execution.network === 'allowlisted')
213
+ error(`groups/${id}/execution/network`, 'native-sandbox requires network="deny"');
214
+ }
204
215
  const ids = new Set();
205
216
  for (const member of group.members) {
206
217
  if (ids.has(member.id))
@@ -208,6 +219,10 @@ export function validateParallelismConfig(value) {
208
219
  ids.add(member.id);
209
220
  if (!policy.clis?.[member.cli] && !builtinIds.has(member.cli))
210
221
  error(`groups/${id}/members/${member.id}/cli`, 'Unknown installation');
222
+ if (group.execution?.launchMode === 'native-sandbox'
223
+ && (policy.clis?.[member.cli]?.adapter ?? `providers:${member.cli}`) !== 'providers:codex') {
224
+ error(`groups/${id}/members/${member.id}/cli`, 'native-sandbox requires a codex adapter');
225
+ }
211
226
  }
212
227
  for (const member of group.fallback ?? [])
213
228
  if (!ids.has(member))
@@ -461,6 +476,33 @@ export function validateIndexConfig(index) {
461
476
  if (indexObject.graphBackend !== undefined && !GRAPH_BACKENDS.includes(indexObject.graphBackend)) {
462
477
  errors.push(`index.graphBackend: must be one of ${GRAPH_BACKENDS.join(' | ')}`);
463
478
  }
479
+ if (indexObject.freshness !== undefined) {
480
+ const policies = indexObject.freshness;
481
+ if (!policies || typeof policies !== 'object' || Array.isArray(policies)) {
482
+ errors.push('index.freshness: must map adapter names to policies');
483
+ }
484
+ else
485
+ for (const [name, policy] of Object.entries(policies)) {
486
+ const where = `index.freshness.${name}`;
487
+ if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
488
+ errors.push(`${where}: must be an object`);
489
+ continue;
490
+ }
491
+ for (const [key, value] of Object.entries(policy)) {
492
+ if (key === 'measureConvergence') {
493
+ if (typeof value !== 'boolean')
494
+ errors.push(`${where}.${key}: must be a boolean`);
495
+ }
496
+ else if (key === 'targetStaleAfterMs' || key === 'maxRetriesPerDay') {
497
+ if (!Number.isSafeInteger(value) || value < (key === 'targetStaleAfterMs' ? 1 : 0)) {
498
+ errors.push(`${where}.${key}: must be a ${key === 'targetStaleAfterMs' ? 'positive' : 'non-negative'} integer`);
499
+ }
500
+ }
501
+ else
502
+ errors.push(`${where}.${key}: unknown policy key`);
503
+ }
504
+ }
505
+ }
464
506
  const isStringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === 'string');
465
507
  errors.push(...validateIndexGraphOverrides(indexObject.graphOverrides));
466
508
  const graphs = indexObject.graphs;
@@ -24,6 +24,7 @@ import { normalizeProviderDefinitionId } from '../providers/provider-definitions
24
24
  import { UserConfig } from './user-config.js';
25
25
  import { AiwgError, EXIT_CODES } from '../cli/errors.js';
26
26
  import { isInteractive } from '../cli/env.js';
27
+ import { splitParams } from '../cli/agent-spawn.js';
27
28
  import { exitCodeForSpawnResult } from '../cli/child-process-result.js';
28
29
  import { projectControlPath, resolveProjectAiwgDir } from './project-artifacts.js';
29
30
  const _scriptDir = path.dirname(fileURLToPath(import.meta.url));
@@ -400,7 +401,7 @@ async function projectConfigSet(key, raw, args, quiet = false) {
400
401
  }
401
402
  value = n;
402
403
  }
403
- if (key === 'decision.ask.cost.llm_per_mtok' || key === 'decision.ask.cost.jev_per_mtok') {
404
+ if (['decision.ask.cost.llm_per_mtok', 'decision.ask.cost.jev_per_mtok', 'decision.ask.cost.jev_input_per_mtok', 'decision.ask.cost.jev_output_per_mtok'].includes(key)) {
404
405
  const n = Number(raw);
405
406
  if (!raw.trim() || !Number.isFinite(n) || n < 0 || n > 1_000_000) {
406
407
  throw new AiwgError({ code: 'ERR_INVALID_VALUE', message: `${key} must be a non-negative number <= 1000000`, exitCode: EXIT_CODES.USAGE });
@@ -733,14 +734,17 @@ async function handleEdit(config, args = []) {
733
734
  const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
734
735
  // Ensure the config file exists before opening
735
736
  await config.ensureDir();
736
- const { execSync } = await import('child_process');
737
+ const { execFileSync } = await import('child_process');
737
738
  try {
739
+ const [executable, ...editorArgs] = splitParams(editor);
740
+ if (!executable)
741
+ throw new Error('Editor command is empty');
738
742
  // sync-spawn: interactive editor-launch — launches $EDITOR/$VISUAL on the
739
743
  // config file; the user interacts with it directly (see tools/testing/
740
744
  // sync-spawn-allowlist.json). Reached only when isInteractive() is true
741
745
  // (#3062) — the no-terminal path above refuses before spawning anything.
742
746
  // Not a candidate for a bounded timeout.
743
- execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
747
+ execFileSync(executable, [...editorArgs, configPath], { stdio: 'inherit', shell: false });
744
748
  }
745
749
  catch (err) {
746
750
  throw new AiwgError({
@@ -57,10 +57,23 @@ export function validateDecisionConfig(value) {
57
57
  sampling.push(`decision.ask.${section} must be an object`);
58
58
  continue;
59
59
  }
60
- const allowed = section === 'cost' ? ['llm_per_mtok', 'jev_per_mtok'] : ['caller_model_class', 'reasoning_tokens', 'output_tokens'];
60
+ const allowed = section === 'cost' ? ['llm_per_mtok', 'jev_per_mtok', 'jev_input_per_mtok', 'jev_output_per_mtok', 'source', 'checked_at', 'jev_model'] : ['caller_model_class', 'reasoning_tokens', 'output_tokens'];
61
61
  for (const [key, value] of Object.entries(settings)) {
62
62
  if (!allowed.includes(key))
63
63
  sampling.push(`unknown decision.ask.${section}.${key}`);
64
+ else if (key === 'source') {
65
+ if (!['typesafe-docs', 'manual', 'typesafe-snapshot'].includes(value))
66
+ sampling.push('decision.ask.cost.source must be typesafe-docs, manual or typesafe-snapshot');
67
+ }
68
+ else if (key === 'checked_at') {
69
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)
70
+ || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value)
71
+ sampling.push('decision.ask.cost.checked_at must be an ISO date (YYYY-MM-DD)');
72
+ }
73
+ else if (key === 'jev_model') {
74
+ if (typeof value !== 'string' || !/^jev-\d+\.\d+\.\d+$/.test(value))
75
+ sampling.push('decision.ask.cost.jev_model must be a versioned Jev model (jev-X.Y.Z)');
76
+ }
64
77
  else if (key === 'caller_model_class') {
65
78
  if (value !== 'standard' && value !== 'reasoning')
66
79
  sampling.push('decision.ask.savings.caller_model_class must be standard or reasoning');
@@ -13,6 +13,7 @@ import * as path from 'path';
13
13
  import { execFile } from 'child_process';
14
14
  import { promisify } from 'util';
15
15
  import { boundedSpawnSync } from '../process/bounded-child-process.mjs';
16
+ import { helperEnvironment, HELPER_ENV_ALLOWLIST } from '../process/cli-command.mjs';
16
17
  const execFileAsync = promisify(execFile);
17
18
  const GITIGNORE_PROBE_BASENAME = '.aiwg-ignore-probe';
18
19
  // ===========================
@@ -94,6 +95,7 @@ async function isGitWorktree(projectRoot) {
94
95
  try {
95
96
  const { stdout } = await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], {
96
97
  cwd: projectRoot,
98
+ env: helperEnvironment(process.env, [...HELPER_ENV_ALLOWLIST, 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR']),
97
99
  windowsHide: true,
98
100
  });
99
101
  return stdout.trim() === 'true';
@@ -117,6 +119,7 @@ async function resolveCoveredPatterns(projectRoot, patterns, lines) {
117
119
  try {
118
120
  await execFileAsync('git', ['check-ignore', '--quiet', '--', probePath(pattern)], {
119
121
  cwd: projectRoot,
122
+ env: helperEnvironment(process.env, [...HELPER_ENV_ALLOWLIST, 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR']),
120
123
  windowsHide: true,
121
124
  });
122
125
  return true;
@@ -239,6 +242,7 @@ export function dropGitignored(targetDir, files) {
239
242
  }
240
243
  const result = boundedSpawnSync('git', ['check-ignore', '-z', '--stdin'], 'quick', {
241
244
  cwd: targetDir,
245
+ env: helperEnvironment(process.env, [...HELPER_ENV_ALLOWLIST, 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR']),
242
246
  input: `${files.join('\0')}\0`,
243
247
  encoding: 'utf8',
244
248
  });
@@ -0,0 +1,25 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { getConfigPath, readAiwgConfig } from './aiwg-config.js';
4
+ import { projectAiwgPath } from './project-artifacts.js';
5
+ /** Advisory only: native-sandbox bypasses the systemd launcher boundary. */
6
+ export function nativeSandboxWarnings(config, projectDir) {
7
+ const groups = Object.entries(config?.parallelism?.groups ?? {})
8
+ .filter(([, group]) => group.execution?.launchMode === 'native-sandbox')
9
+ .map(([id]) => id);
10
+ if (groups.length === 0)
11
+ return [];
12
+ // Match readAiwgConfig's repository-control-plane-first compatibility lookup.
13
+ const localPath = getConfigPath(projectDir);
14
+ const configPath = resolve(existsSync(localPath) ? localPath : projectAiwgPath(projectDir, 'aiwg.config'));
15
+ return groups.map(group => ({
16
+ code: 'NATIVE_SANDBOX_ISOLATION',
17
+ group,
18
+ configPath,
19
+ message: `Group "${group}" selects launchMode: native-sandbox in ${configPath}; systemd-unit isolation and its memory limit do not apply; only the worker CLI's own sandbox applies.`,
20
+ }));
21
+ }
22
+ export async function readNativeSandboxWarnings(projectDir) {
23
+ return nativeSandboxWarnings(await readAiwgConfig(projectDir), projectDir);
24
+ }
25
+ //# sourceMappingURL=native-sandbox-warnings.js.map
@@ -13,6 +13,7 @@ import { existsSync, statSync } from 'fs';
13
13
  import { homedir } from 'os';
14
14
  import { dirname, isAbsolute, relative, resolve } from 'path';
15
15
  import { promisify } from 'util';
16
+ import { helperEnvironment } from '../process/cli-command.mjs';
16
17
  import { getConfigPath, readAiwgConfig, resolveDelivery, resolveRemoteProvider, resolveRemotes, } from './aiwg-config.js';
17
18
  const execFileAsync = promisify(execFile);
18
19
  function expandHome(value) {
@@ -96,7 +97,7 @@ async function readRemoteUrl(repoPath, remote) {
96
97
  if (remote.includes('://') || /^[^@/\s]+@[^:/\s]+:/.test(remote))
97
98
  return remote;
98
99
  try {
99
- const { stdout } = await execFileAsync('git', ['-C', repoPath, 'remote', 'get-url', remote], { encoding: 'utf8' });
100
+ const { stdout } = await execFileAsync('git', ['-C', repoPath, 'remote', 'get-url', remote], { encoding: 'utf8', env: helperEnvironment() });
100
101
  return stdout.trim() || undefined;
101
102
  }
102
103
  catch {
@@ -9,13 +9,14 @@
9
9
  * @issue #938
10
10
  */
11
11
  import { z } from 'zod';
12
+ import { integer } from '../schema/zod-validation.mjs';
12
13
  /**
13
14
  * Detection spec — declarative globs + counts. Common to every kind.
14
15
  */
15
16
  const DetectionSpecSchema = z.object({
16
17
  glob: z.array(z.string()).min(1, 'detect.glob must have at least one pattern'),
17
- minCount: z.number().int().positive().optional(),
18
- conditions: z.record(z.string()).optional(),
18
+ minCount: integer().positive().optional(),
19
+ conditions: z.record(z.string(), z.string()).optional(),
19
20
  });
20
21
  /**
21
22
  * Base frontmatter shared by every kind. Per-kind schemas extend this.
@@ -39,7 +40,7 @@ export const StatusContributorSchema = ContributorBaseSchema.extend({
39
40
  * code-driven; the aggregator reads these to populate the report block.
40
41
  */
41
42
  fields: z
42
- .record(z.object({
43
+ .record(z.string(), z.object({
43
44
  type: z.enum(['string', 'number', 'date']),
44
45
  source: z.string(),
45
46
  regex: z.string().optional(),
@@ -60,7 +61,7 @@ export const ResearchContributorSchema = ContributorBaseSchema.extend({
60
61
  })
61
62
  .optional(),
62
63
  focus_areas: z.array(z.string()).min(1, 'research contributors must declare at least one focus area'),
63
- recency_default_months: z.number().int().positive().optional(),
64
+ recency_default_months: integer().positive().optional(),
64
65
  });
65
66
  /**
66
67
  * Schema registry. Adding a new kind means adding its schema here — no other
@@ -108,7 +109,7 @@ export function validateContributor(data) {
108
109
  if (!result.success) {
109
110
  return {
110
111
  ok: false,
111
- errors: result.error.errors.map(e => `${e.path.join('.') || '<root>'}: ${e.message}`),
112
+ errors: result.error.issues.map(e => `${e.path.join('.') || '<root>'}: ${e.message}`),
112
113
  };
113
114
  }
114
115
  return { ok: true, data: result.data };
@@ -44,22 +44,15 @@ export class JevDecisionAdapter {
44
44
  this.region = options.region?.trim() ? options.region : null;
45
45
  this.observeResponseBody = options.observeResponseBody;
46
46
  }
47
- /** Used only for non-2xx error bodies, where there is no separate successful parse to stay consistent with. */
48
- observeResponse(body) {
49
- if (!this.observeResponseBody)
50
- return;
51
- let parsed = {};
52
- try {
53
- parsed = asRecord(parseBoundedJson(body));
54
- }
55
- catch { /* Invalid answers still have bounded body evidence. */ }
56
- let usage = emptyUsage();
47
+ /** An error response may still report billed usage and correlation metadata. */
48
+ parseErrorResponse(body) {
49
+ let parsed;
57
50
  try {
58
- usage = normalizeUsage(parsed.usage);
51
+ parsed = parseBoundedJson(body);
59
52
  }
60
- catch { /* Missing usage is unknown, including on auth errors. */ }
61
- this.observeResponseBody(body, { actualModel: typeof parsed.model === 'string' && parsed.model ? parsed.model : null,
62
- usage });
53
+ catch { /* Error classification does not require valid JSON. */ }
54
+ this.observeParsed(body, parsed);
55
+ return parsed;
63
56
  }
64
57
  /**
65
58
  * Derives the observer's model/usage from a value `parseBoundedJson` has already produced (or `undefined`
@@ -81,6 +74,15 @@ export class JevDecisionAdapter {
81
74
  catch { /* Missing usage is unknown, including on auth errors. */ }
82
75
  return { actualModel: typeof record.model === 'string' && record.model ? record.model : null, usage };
83
76
  }
77
+ responseMetadata(parsed, metadata) {
78
+ let body = {};
79
+ try {
80
+ body = asRecord(parsed);
81
+ }
82
+ catch { /* No body correlation metadata. */ }
83
+ const id = metadata.requestId ? null : safeRequestId(body.request_id ?? body.requestId);
84
+ return { ...metadata, ...this.observationMetadataOf(parsed), ...(id ? { requestId: id, requestIdSource: 'body' } : {}) };
85
+ }
84
86
  /** Observes exactly once, from the single parse attempt `parsed` already represents (successful or not). */
85
87
  observeParsed(body, parsed) {
86
88
  this.observeResponseBody?.(body, this.observationMetadataOf(parsed));
@@ -209,22 +211,7 @@ export class JevDecisionAdapter {
209
211
  if (!response.ok) {
210
212
  try {
211
213
  const errorBody = await readBoundedBody(response, signal);
212
- this.observeResponse(errorBody);
213
- if (!metadata.requestId && response.headers.get('content-type')?.includes('json')) {
214
- let parsedError;
215
- try {
216
- parsedError = parseBoundedJson(errorBody);
217
- }
218
- catch {
219
- parsedError = null;
220
- }
221
- if (parsedError && typeof parsedError === 'object' && !Array.isArray(parsedError)) {
222
- const field = parsedError.request_id ?? parsedError.requestId;
223
- const safe = safeRequestId(field);
224
- if (safe)
225
- metadata = { ...metadata, requestId: safe, requestIdSource: 'body' };
226
- }
227
- }
214
+ metadata = this.responseMetadata(this.parseErrorResponse(errorBody), metadata);
228
215
  }
229
216
  catch {
230
217
  if (request.signal.aborted)
@@ -248,6 +235,7 @@ export class JevDecisionAdapter {
248
235
  finally {
249
236
  this.observeParsed(encoded, parsed);
250
237
  }
238
+ metadata = this.responseMetadata(parsed, metadata);
251
239
  if (request.signal.aborted)
252
240
  return externalInterruption(request, 'terminal-response', metadata);
253
241
  if (deadline.aborted)
@@ -353,7 +341,7 @@ export class JevDecisionAdapter {
353
341
  }
354
342
  if (!response.ok) {
355
343
  try {
356
- this.observeResponse(await readBoundedBody(response, signal));
344
+ metadata = this.responseMetadata(this.parseErrorResponse(await readBoundedBody(response, signal)), metadata);
357
345
  }
358
346
  catch {
359
347
  return batchFailure(requests, failure('invalid-output', metadata));
@@ -371,6 +359,7 @@ export class JevDecisionAdapter {
371
359
  finally {
372
360
  this.observeParsed(encoded, parsed);
373
361
  }
362
+ metadata = this.responseMetadata(parsed, metadata);
374
363
  const normalized = normalizeBatchResponse(requests, parsed).map(answer => ({
375
364
  questionId: answer.questionId,
376
365
  observation: { ...answer.observation, ...metadata },
@@ -43,9 +43,13 @@ export class DecisionAdmissionController {
43
43
  this.now = now;
44
44
  this.options = options;
45
45
  }
46
- /** Aggregate counts only; suitable for load harness sampling. */
47
- snapshot() {
48
- return { active: this.active, queued: this.queuedCount() };
46
+ /** Host-only counts for load sampling; omitting a scope returns aggregate counts. */
47
+ snapshot(scope) {
48
+ const principal = scope?.principalId;
49
+ const provider = scope?.providerId;
50
+ const active = principal && provider ? this.activePrincipalProvider.get(`${provider}\u0000${principal}`) ?? 0
51
+ : principal ? this.activePrincipal.get(principal) ?? 0 : provider ? this.activeProvider.get(provider) ?? 0 : this.active;
52
+ return { active, queued: this.queuedCount(request => (!principal || request.principalId === principal) && (!provider || request.providerId === provider)) };
49
53
  }
50
54
  /** Bounded, metadata-only breaker transition history, oldest first. */
51
55
  breakerTransitions() {
@@ -495,6 +495,11 @@ export async function setupJev(input = {}, options = {}) {
495
495
  next: ['Run aiwg decision ask --question "<q>" --yes-no --json.'],
496
496
  };
497
497
  if (input.verify) {
498
+ // Verify the credential just saved, even when the shell still names an
499
+ // older token. Ordinary ask calls retain their environment precedence.
500
+ const verificationEnv = { ...environment };
501
+ delete verificationEnv.JEV_API_KEY;
502
+ delete verificationEnv.AIWG_DECISION_JEV_API_KEY;
498
503
  const verified = await askDecision({
499
504
  question: 'Is this Jev decision setup verification request well formed?',
500
505
  mode: { kind: 'yes-no' },
@@ -502,7 +507,7 @@ export async function setupJev(input = {}, options = {}) {
502
507
  timeoutMs: 15_000,
503
508
  passes: 1,
504
509
  escalationPasses: 1,
505
- }, options);
510
+ }, { ...options, env: verificationEnv });
506
511
  result.verified = verified.status === 'answered';
507
512
  result.model = verified.model;
508
513
  result.usage = verified.usage;