@aiwg/cli 2026.10.4 → 2026.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +3 -1
  6. package/dist/src/artifacts/browser-export.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +8 -1
  8. package/dist/src/artifacts/cli.js +72 -9
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  10. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  11. package/dist/src/artifacts/graph-query.js +18 -8
  12. package/dist/src/artifacts/index-adapters.js +339 -0
  13. package/dist/src/artifacts/index-builder.js +146 -16
  14. package/dist/src/artifacts/index-files.js +15 -4
  15. package/dist/src/artifacts/index-preparation.js +91 -26
  16. package/dist/src/artifacts/index-reader.js +44 -0
  17. package/dist/src/artifacts/index-refresh.js +130 -56
  18. package/dist/src/artifacts/index-status.js +20 -5
  19. package/dist/src/artifacts/index-worker.mjs +9 -7
  20. package/dist/src/artifacts/memory-maintenance.js +503 -0
  21. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  22. package/dist/src/artifacts/query-engine.js +263 -108
  23. package/dist/src/artifacts/relations-builder.js +2 -2
  24. package/dist/src/artifacts/source-graph.js +2 -2
  25. package/dist/src/artifacts/types.js +9 -1
  26. package/dist/src/cli/handlers/decision.js +19 -1
  27. package/dist/src/cli/handlers/guided-setup.js +470 -0
  28. package/dist/src/cli/handlers/help.js +2 -2
  29. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  30. package/dist/src/cli/handlers/index.js +4 -2
  31. package/dist/src/cli/handlers/init.js +41 -6
  32. package/dist/src/cli/handlers/local-executor.js +5 -3
  33. package/dist/src/cli/handlers/mc.js +509 -53
  34. package/dist/src/cli/handlers/models.js +32 -2
  35. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  36. package/dist/src/cli/handlers/rules.js +1 -1
  37. package/dist/src/cli/handlers/script-runner.js +73 -2
  38. package/dist/src/cli/handlers/serve.js +5 -3
  39. package/dist/src/cli/handlers/sessions.js +40 -6
  40. package/dist/src/cli/handlers/setup.js +428 -1
  41. package/dist/src/cli/handlers/skill-lint.js +1 -1
  42. package/dist/src/cli/handlers/subcommands.js +118 -8
  43. package/dist/src/cli/handlers/use.js +279 -93
  44. package/dist/src/cli/handlers/workspace.js +3 -1
  45. package/dist/src/cli/prompt-utils.js +8 -0
  46. package/dist/src/cli/router.js +16 -1
  47. package/dist/src/config/aiwg-config.js +42 -0
  48. package/dist/src/config/cli.js +7 -3
  49. package/dist/src/config/decision-config.js +14 -1
  50. package/dist/src/config/native-sandbox-warnings.js +25 -0
  51. package/dist/src/contributors/validation.js +6 -5
  52. package/dist/src/decision/admission.js +7 -3
  53. package/dist/src/decision/benchmark.js +469 -0
  54. package/dist/src/decision/ledger.js +22 -3
  55. package/dist/src/decision/pricing.js +147 -0
  56. package/dist/src/decision/qualification/gate-evidence.js +18 -15
  57. package/dist/src/decision/qualification/load-staged.js +539 -0
  58. package/dist/src/decision/qualification/load.js +109 -33
  59. package/dist/src/decision/qualification/runner.js +5 -2
  60. package/dist/src/decision/review/service.js +6 -4
  61. package/dist/src/decision/review/validate.js +23 -2
  62. package/dist/src/decision/savings.js +8 -3
  63. package/dist/src/discovery/index.js +492 -0
  64. package/dist/src/discovery/interview.js +233 -0
  65. package/dist/src/discovery/plan-apply.js +571 -0
  66. package/dist/src/discovery/plan-store.js +171 -0
  67. package/dist/src/discovery/plan-types.js +2 -0
  68. package/dist/src/discovery/plan-validation.js +139 -0
  69. package/dist/src/discovery/types.js +2 -0
  70. package/dist/src/extensions/commands/definitions.js +49 -9
  71. package/dist/src/extensions/manifest.js +71 -44
  72. package/dist/src/extensions/project-local-discovery.js +1 -1
  73. package/dist/src/extensions/project-quickref.js +18 -17
  74. package/dist/src/extensions/validation.js +32 -29
  75. package/dist/src/housekeeper/daemon-config.js +148 -0
  76. package/dist/src/housekeeper/ipc.js +84 -0
  77. package/dist/src/housekeeper/lifecycle.js +148 -0
  78. package/dist/src/housekeeper/platform.js +35 -0
  79. package/dist/src/housekeeper/status.js +70 -0
  80. package/dist/src/housekeeper/units.js +114 -0
  81. package/dist/src/jobs/flow.js +22 -21
  82. package/dist/src/lint/rule-activation.js +20 -5
  83. package/dist/src/lint/runner.js +1 -1
  84. package/dist/src/mcp/cli.mjs +5 -6
  85. package/dist/src/mcp/config-file.mjs +6 -2
  86. package/dist/src/mcp/config-layers.mjs +23 -7
  87. package/dist/src/mcp/profiles.js +18 -31
  88. package/dist/src/mcp/profiles.mjs +18 -32
  89. package/dist/src/mcp/registry.js +7 -27
  90. package/dist/src/mcp/registry.mjs +7 -28
  91. package/dist/src/mcp/server.mjs +34 -1
  92. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  93. package/dist/src/mcp/tools/command-run.mjs +3 -2
  94. package/dist/src/mcp/tools/decision.mjs +49 -48
  95. package/dist/src/mcp/tools/discovery.mjs +46 -21
  96. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  97. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  98. package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
  99. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  100. package/dist/src/memory/audit.js +719 -0
  101. package/dist/src/memory/canonical-context.js +392 -13
  102. package/dist/src/memory/canonical-maintenance.js +95 -0
  103. package/dist/src/memory/cli-result.js +22 -0
  104. package/dist/src/memory/cli.js +176 -0
  105. package/dist/src/memory/context-pack.js +57 -6
  106. package/dist/src/memory/cost.js +512 -0
  107. package/dist/src/memory/firewall-classification.d.mts +7 -0
  108. package/dist/src/memory/firewall-classification.mjs +32 -0
  109. package/dist/src/memory/index.js +8 -0
  110. package/dist/src/memory/line-maintenance.js +68 -0
  111. package/dist/src/memory/maintenance-cli.js +286 -0
  112. package/dist/src/memory/maintenance-contract.js +136 -0
  113. package/dist/src/memory/maintenance-index.js +18 -0
  114. package/dist/src/memory/maintenance-owner.js +2 -0
  115. package/dist/src/memory/maintenance-record.js +138 -0
  116. package/dist/src/memory/maintenance.js +254 -0
  117. package/dist/src/memory/provider-memory-adapter.js +154 -0
  118. package/dist/src/memory/recommend-cli.js +83 -0
  119. package/dist/src/memory/recommend.js +269 -0
  120. package/dist/src/memory/scheduled.js +81 -0
  121. package/dist/src/memory/taxonomy.js +44 -0
  122. package/dist/src/mission-protocol/codecs.js +19 -9
  123. package/dist/src/models/config-loader.js +11 -1
  124. package/dist/src/models/model-capabilities.v1.json +74 -50
  125. package/dist/src/models/provider-policy.js +125 -62
  126. package/dist/src/models/resolver.js +45 -8
  127. package/dist/src/models/worker-installation.d.mts +7 -0
  128. package/dist/src/models/worker-installation.mjs +169 -37
  129. package/dist/src/models/wrapper-route.js +1 -1
  130. package/dist/src/output-modes/registry.js +8 -7
  131. package/dist/src/plugin/metadata-validator.js +1 -1
  132. package/dist/src/providers/deploy-version.d.mts +1 -0
  133. package/dist/src/providers/deploy-version.mjs +17 -0
  134. package/dist/src/providers/provider-definitions.js +19 -16
  135. package/dist/src/providers/skill-ownership.d.mts +11 -2
  136. package/dist/src/providers/skill-ownership.mjs +35 -17
  137. package/dist/src/rules/activation.d.mts +11 -2
  138. package/dist/src/rules/activation.mjs +82 -6
  139. package/dist/src/rules/inventory.js +1 -1
  140. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  141. package/dist/src/schema/zod-validation.d.mts +6 -0
  142. package/dist/src/schema/zod-validation.mjs +50 -0
  143. package/dist/src/serve/executor-registry.js +297 -20
  144. package/dist/src/serve/executor-token.d.mts +1 -0
  145. package/dist/src/serve/executor-token.mjs +8 -0
  146. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  147. package/dist/src/serve/worker-invocation.js +114 -15
  148. package/dist/src/sessions/adapters/claude.js +19 -18
  149. package/dist/src/sessions/adapters/codex.js +33 -19
  150. package/dist/src/sessions/adapters/copilot.js +14 -13
  151. package/dist/src/sessions/adapters/cursor.js +19 -18
  152. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  153. package/dist/src/sessions/adapters/factory.js +11 -10
  154. package/dist/src/sessions/adapters/generic.js +32 -23
  155. package/dist/src/sessions/adapters/grokbot.js +35 -35
  156. package/dist/src/sessions/adapters/hermes.js +20 -19
  157. package/dist/src/sessions/adapters/muse.js +50 -49
  158. package/dist/src/sessions/adapters/omp.js +5 -4
  159. package/dist/src/sessions/adapters/openclaw.js +30 -29
  160. package/dist/src/sessions/adapters/opencode.js +26 -26
  161. package/dist/src/sessions/adapters/openhuman.js +24 -23
  162. package/dist/src/sessions/adapters/pi.js +5 -4
  163. package/dist/src/sessions/adapters/windsurf.js +8 -8
  164. package/dist/src/sessions/analytics.js +10 -9
  165. package/dist/src/sessions/batch-contracts.js +1 -1
  166. package/dist/src/sessions/batch-import.js +202 -2
  167. package/dist/src/sessions/candidates.js +7 -6
  168. package/dist/src/sessions/contracts.js +73 -54
  169. package/dist/src/sessions/coverage-preflight.js +26 -1
  170. package/dist/src/sessions/output-registration.js +11 -10
  171. package/dist/src/sessions/policy.js +18 -7
  172. package/dist/src/sessions/refresh.js +2 -3
  173. package/dist/src/sessions/repository.js +4 -2
  174. package/dist/src/sessions/workspace-discovery.js +92 -26
  175. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  176. package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
  177. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  178. package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
  179. package/dist/src/update/checker.d.mts +4 -0
  180. package/dist/src/update/checker.mjs +14 -0
  181. package/dist/src/writing/exemplar-selection.js +6 -6
  182. package/dist/src/writing/voice-evaluation.js +24 -23
  183. package/dist/src/writing/writer-migration.js +14 -13
  184. package/dist/src/writing/writer-profile.js +19 -18
  185. package/dist/src/writing/writing-brief.js +18 -17
  186. package/dist/src/writing/writing-receipt.js +47 -46
  187. package/package.json +5 -2
  188. package/schemas/decision/DecisionReview.schema.json +78 -1
  189. package/schemas/mission-v1.schema.json +3 -0
  190. package/tools/agents/deploy-agents.mjs +67 -39
  191. package/tools/agents/providers/antigravity.mjs +6 -1
  192. package/tools/agents/providers/base.mjs +19 -13
  193. package/tools/agents/providers/claude.mjs +1 -1
  194. package/tools/agents/providers/codex.mjs +50 -56
  195. package/tools/agents/providers/copilot.mjs +8 -2
  196. package/tools/agents/providers/cursor.mjs +1 -1
  197. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  198. package/tools/agents/providers/factory.mjs +7 -1
  199. package/tools/agents/providers/grok-build.mjs +18 -2
  200. package/tools/agents/providers/grokbot.mjs +1 -1
  201. package/tools/agents/providers/hermes.mjs +7 -13
  202. package/tools/agents/providers/muse-hooks.mjs +21 -14
  203. package/tools/agents/providers/muse.mjs +7 -2
  204. package/tools/agents/providers/omp.mjs +28 -4
  205. package/tools/agents/providers/opencode.mjs +14 -5
  206. package/tools/agents/providers/openhuman.mjs +1 -1
  207. package/tools/agents/providers/pi.mjs +1 -1
  208. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  209. package/tools/agents/providers/rule-delivery.mjs +263 -71
  210. package/tools/agents/providers/windsurf.mjs +7 -0
  211. package/tools/cli/workspace-status.mjs +5 -0
  212. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  213. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  214. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  215. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  216. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  217. package/tools/ralph-external/process-monitor.mjs +7 -4
  218. package/tools/ralph-external/session-launcher.mjs +280 -32
  219. package/tools/skills/deploy-skills-codex.mjs +11 -7
  220. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -87,9 +87,14 @@ export async function collectGraphIndexFiles(cwd, graph, options = {}) {
87
87
  throw new Error(`Unknown graph: ${graph}`);
88
88
  }
89
89
  const artifactRoot = resolveProjectAiwgDir(cwd);
90
- const excludedDirs = [path.join(cwd, DEFAULT_PROJECT_AIWG_DIR), artifactRoot]
91
- .flatMap(root => INDEX_SCRATCH_DIRS.map(name => path.resolve(root, name)));
92
- const excludedPaths = new Set(excludedDirs);
90
+ const artifactRoots = [path.join(cwd, DEFAULT_PROJECT_AIWG_DIR), artifactRoot];
91
+ // Aggregate owner state contains history, tombstones and transaction payloads.
92
+ // It is not an ordinary artifact: only each owner's public projection is a
93
+ // retrieval surface, never the backing store or maintenance receipts.
94
+ const privateDirs = ['context/compound-memory', 'memory/transactions', 'memory/maintenance'];
95
+ const privateFiles = ['memory/line-memory.meta.json', 'memory/line-memory.config.json', 'memory/line-memory.lock'];
96
+ const excludedDirs = artifactRoots.flatMap(root => [...INDEX_SCRATCH_DIRS, ...privateDirs].map(name => path.resolve(root, name)));
97
+ const excludedPaths = new Set([...excludedDirs, ...artifactRoots.flatMap(root => privateFiles.map(name => path.resolve(root, name)))]);
93
98
  // A directory's ancestors are shared by thousands of sibling artifacts.
94
99
  // Cache only within this scan so the next build still notices new symlinks.
95
100
  const safePaths = new Map();
@@ -109,7 +114,7 @@ export async function collectGraphIndexFiles(cwd, graph, options = {}) {
109
114
  }
110
115
  const inputPath = file;
111
116
  file = path.resolve(file);
112
- if (excludedDirs.some(dir => process.platform === 'win32'
117
+ if (excludedPaths.has(file) || excludedDirs.some(dir => process.platform === 'win32'
113
118
  ? pathContains(dir, file) : file === dir || file.startsWith(dir + path.sep)))
114
119
  return false;
115
120
  if (knownStat && !knownStat.isSymbolicLink() && safePaths.get(path.dirname(file)) === true) {
@@ -190,6 +195,10 @@ export async function collectGraphIndexFiles(cwd, graph, options = {}) {
190
195
  }
191
196
  }
192
197
  let sources = [...files];
198
+ const candidates = [...sources].sort();
199
+ if (options.selection?.files && JSON.stringify(candidates) === JSON.stringify(options.selection.candidates)) {
200
+ return [...options.selection.files];
201
+ }
193
202
  if (!options.includeIgnored) {
194
203
  // Many projects ignore the whole artifact root (`.aiwg/`). Honoring that
195
204
  // would empty the index, so ignore rules apply only beneath a scan root
@@ -225,6 +234,8 @@ export async function collectGraphIndexFiles(cwd, graph, options = {}) {
225
234
  if (!selected.has(file))
226
235
  options.fileStats.delete(file);
227
236
  }
237
+ if (options.selection)
238
+ Object.assign(options.selection, { candidates, files: [...sources] });
228
239
  return sources;
229
240
  }
230
241
  //# sourceMappingURL=index-files.js.map
@@ -1,65 +1,130 @@
1
1
  /** Bounded artifact-local preparation; ordered graph reduction stays in index-builder. */
2
2
  import os from 'node:os';
3
- import { Worker } from 'node:worker_threads';
3
+ import { fork } from 'node:child_process';
4
+ import { signalChild } from '../process/signal-child.mjs';
5
+ const MIB = 1024 ** 2;
6
+ const WORKER_HEAP_MB = 256;
7
+ // Leave room for V8's young generation, executable code, IPC and native allocations.
8
+ const WORKER_BUDGET_BYTES = 512 * MIB;
9
+ // Parsing and metadata extraction amplify input size; do large files only after the pool exits.
10
+ const MAX_WORKER_ARTIFACT_BYTES = MIB;
11
+ export function resolveIndexWorkers(requested, env = process.env) {
12
+ const value = requested ?? env.AIWG_INDEX_WORKERS;
13
+ if (requested === undefined && (value === undefined || value === ''))
14
+ return undefined;
15
+ if ((typeof value === 'string' && !/^\d+$/.test(value))
16
+ || !Number.isSafeInteger(Number(value)) || Number(value) < 0) {
17
+ throw new Error(`${requested === undefined ? 'AIWG_INDEX_WORKERS' : '--index-workers'} must be a nonnegative safe integer (0 = serial)`);
18
+ }
19
+ return Number(value);
20
+ }
4
21
  export function indexPreparationWorkers(jobs, requested, cores = os.availableParallelism(), memory = Math.min(os.freemem(), process.availableMemory?.() ?? Infinity), totalBytes = 0) {
5
- if (requested !== undefined && (!Number.isInteger(requested) || requested < 0))
6
- throw new Error('Index workers must be a nonnegative integer');
22
+ requested = resolveIndexWorkers(requested);
7
23
  if (jobs < 64 || cores < 3 || requested === 0 || requested === 1)
8
24
  return 0;
9
- // Thread startup costs dominate lightweight scans even at thousands of files.
10
- // Explicit thread requests bypass only this conservative automatic workload gate.
11
- if (requested === undefined && (jobs < 1000 || !Number.isFinite(totalBytes) || totalBytes < 16 * 1024 ** 2))
25
+ // Process startup costs dominate lightweight scans even at thousands of files.
26
+ // Explicit requests bypass only this conservative automatic workload gate.
27
+ if (requested === undefined && (jobs < 1000 || !Number.isFinite(totalBytes) || totalBytes < 16 * MIB))
12
28
  return 0;
13
- const byMemory = Math.floor((memory - 512 * 1024 ** 2) / (256 * 1024 ** 2));
29
+ const byMemory = Math.floor((memory - 512 * MIB) / WORKER_BUDGET_BYTES);
14
30
  const bounded = Math.max(0, Math.min(requested ?? 4, 4, cores - 1, byMemory, jobs));
15
31
  return bounded >= 2 ? bounded : 0;
16
32
  }
17
- export async function runIndexPreparation(jobs, serial, moduleUrl, requested) {
18
- const workerCount = indexPreparationWorkers(jobs.length, requested, undefined, undefined, jobs.reduce((total, job) => total + job.sizeBytes, 0));
33
+ export async function runIndexPreparation(jobs, serial, moduleUrl, requested, onDiagnostic) {
34
+ const diagnostics = [];
35
+ const diagnose = (message) => { diagnostics.push(message); onDiagnostic?.(message); };
36
+ const eligible = jobs.map((job, index) => ({ job, index })).filter(({ job }) => Number.isFinite(job.sizeBytes) && job.sizeBytes >= 0 && job.sizeBytes <= MAX_WORKER_ARTIFACT_BYTES);
37
+ const workerCount = indexPreparationWorkers(eligible.length, requested, undefined, undefined, eligible.reduce((total, { job }) => total + job.sizeBytes, 0));
19
38
  if (!workerCount)
20
- return { results: jobs.map(serial), workerCount: 0 };
39
+ return { results: jobs.map(serial), workerCount: 0, diagnostics };
21
40
  const results = new Array(jobs.length), workers = [];
41
+ const closed = [];
22
42
  let next = 0, completed = 0, stopped = false;
23
43
  try {
24
44
  await new Promise((resolve, reject) => {
25
- const fail = (error) => { stopped = true; reject(error); };
45
+ // Stop dispatch on a process failure. Reap the entire pool before retrying
46
+ // unfinished work in the main isolate, avoiding overlapping memory peaks.
47
+ const recover = (message) => {
48
+ if (stopped)
49
+ return;
50
+ stopped = true;
51
+ diagnose(`${message}; retrying unfinished preparation serially`);
52
+ resolve();
53
+ };
26
54
  const dispatch = (worker) => {
27
- if (!stopped && next < jobs.length) {
28
- const index = next++;
29
- worker.postMessage({ index, job: jobs[index] });
55
+ if (stopped || next >= eligible.length)
56
+ return;
57
+ const { index, job } = eligible[next++];
58
+ try {
59
+ worker.send({ index, job }, error => { if (error)
60
+ recover(`Index preparation IPC failed for ${job.relativePath}: ${error.message}`); });
61
+ }
62
+ catch (error) {
63
+ recover(`Index preparation IPC failed for ${job.relativePath}: ${error instanceof Error ? error.message : String(error)}`);
30
64
  }
31
65
  };
32
- for (let index = 0; index < workerCount; index++) {
33
- const worker = new Worker(new URL('./index-worker.mjs', import.meta.url), {
34
- workerData: { moduleUrl }, execArgv: [], resourceLimits: { maxOldGenerationSizeMb: 256 },
66
+ for (let index = 0; index < workerCount && !stopped; index++) {
67
+ // A worker_threads resourceLimit is not a process boundary: V8 can abort
68
+ // Node during allocation before its isolate-limit error is delivered.
69
+ // A child heap fatal/SIGABRT is instead an ordinary, recoverable exit.
70
+ const worker = fork(new URL('./index-worker.mjs', import.meta.url), [moduleUrl], {
71
+ execArgv: [`--max-old-space-size=${WORKER_HEAP_MB}`, '--max-semi-space-size=16'],
72
+ stdio: ['ignore', 'ignore', 'pipe', 'ipc'], serialization: 'advanced',
35
73
  });
36
74
  workers.push(worker);
37
- worker.on('error', fail);
38
- worker.on('exit', code => { if (!stopped && completed < jobs.length)
39
- fail(new Error(`Index preparation worker exited early (${code})`)); });
40
- worker.on('message', message => {
75
+ closed.push(new Promise(done => worker.once('close', () => done())));
76
+ let stderr = '';
77
+ let activePath = '<startup>';
78
+ worker.stderr?.on('data', chunk => { stderr = (stderr + String(chunk)).slice(-8192); });
79
+ worker.on('error', error => recover(`Index preparation worker failed for ${activePath}: ${error.message}`));
80
+ worker.on('exit', (code, signal) => {
81
+ const oom = /heap out of memory|ERR_WORKER_OUT_OF_MEMORY/i.test(stderr) ? ' (heap out of memory)' : '';
82
+ recover(`Index preparation worker exited early for ${activePath} (${signal ?? code})${oom}`);
83
+ });
84
+ worker.on('message', (message) => {
41
85
  if (stopped)
42
86
  return;
43
87
  if (message.error) {
44
- fail(new Error(message.error));
88
+ stopped = true;
89
+ reject(new Error(message.error));
45
90
  return;
46
91
  }
47
92
  results[message.index] = message.result;
48
93
  completed++;
49
- if (completed === jobs.length) {
94
+ if (completed === eligible.length) {
50
95
  stopped = true;
51
96
  resolve();
52
97
  }
53
- else
98
+ else {
99
+ activePath = eligible[next]?.job.relativePath ?? '<idle>';
54
100
  dispatch(worker);
101
+ }
55
102
  });
103
+ activePath = eligible[next]?.job.relativePath ?? '<idle>';
56
104
  dispatch(worker);
57
105
  }
58
106
  });
59
- return { results, workerCount };
60
107
  }
61
108
  finally {
62
- await Promise.all(workers.map(worker => worker.terminate()));
109
+ stopped = true;
110
+ for (const worker of workers)
111
+ if (worker.exitCode === null && worker.signalCode === null)
112
+ signalChild(worker, 'SIGKILL');
113
+ await Promise.all(closed);
114
+ }
115
+ const largeCount = jobs.length - eligible.length;
116
+ if (largeCount)
117
+ diagnose(`${largeCount} artifact(s) above the worker size threshold prepared serially`);
118
+ for (let index = 0; index < jobs.length; index++) {
119
+ if (!results[index]) {
120
+ try {
121
+ results[index] = serial(jobs[index]);
122
+ }
123
+ catch (error) {
124
+ throw new Error(`${jobs[index].relativePath}: ${error instanceof Error ? error.message : String(error)}`);
125
+ }
126
+ }
63
127
  }
128
+ return { results, workerCount, diagnostics };
64
129
  }
65
130
  //# sourceMappingURL=index-preparation.js.map
@@ -11,6 +11,7 @@
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
13
  import { getGraphIndexDir, getProjectIndexRoot } from './types.js';
14
+ import { loadManifest, writeManifest } from './checksum-manifest.js';
14
15
  /**
15
16
  * Load a JSON file from the index directory
16
17
  *
@@ -109,4 +110,47 @@ export function loadGraphIndexFile(cwd, filename, graph) {
109
110
  return null;
110
111
  }
111
112
  }
113
+ /** Publish a prepared snapshot with the existing atomic file writes under the owner lock. */
114
+ export function publishIndexFiles(staging, directory) {
115
+ const files = fs.readdirSync(staging).filter(file => fs.statSync(path.join(staging, file)).isFile())
116
+ .sort((a, b) => Number(a === 'metadata.json') - Number(b === 'metadata.json'));
117
+ const backups = path.join(staging, '.previous');
118
+ fs.mkdirSync(backups);
119
+ for (const file of files) {
120
+ const target = path.join(directory, file);
121
+ if (fs.existsSync(target))
122
+ fs.copyFileSync(target, path.join(backups, file));
123
+ }
124
+ const published = [];
125
+ try {
126
+ for (const file of files) {
127
+ fs.renameSync(path.join(staging, file), path.join(directory, file));
128
+ published.push(file);
129
+ }
130
+ // Receipts bind to final inode signatures, after the atomic renames.
131
+ if (files.includes('checksum-manifest.json')) {
132
+ const manifest = loadManifest(directory);
133
+ const signature = (file) => {
134
+ const stat = fs.statSync(path.join(directory, file), { bigint: true });
135
+ return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(':');
136
+ };
137
+ if (manifest.receipt) {
138
+ manifest.receipt.metadataSignature = signature('metadata.json');
139
+ if (manifest.receipt.relations)
140
+ manifest.receipt.relations.signature = signature('relations.json');
141
+ writeManifest(directory, manifest);
142
+ }
143
+ }
144
+ }
145
+ catch (error) {
146
+ for (const file of published.reverse()) {
147
+ const backup = path.join(backups, file);
148
+ if (fs.existsSync(backup))
149
+ fs.renameSync(backup, path.join(directory, file));
150
+ else
151
+ fs.rmSync(path.join(directory, file), { force: true });
152
+ }
153
+ throw error;
154
+ }
155
+ }
112
156
  //# sourceMappingURL=index-reader.js.map
@@ -1,4 +1,4 @@
1
- import { createHash, randomUUID } from 'node:crypto';
1
+ import { randomUUID } from 'node:crypto';
2
2
  import * as fs from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import path from 'node:path';
@@ -6,13 +6,15 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
6
6
  import { findPackageRoot } from '../cli/find-package-root.js';
7
7
  import { projectAiwgPath, projectControlPath } from '../config/project-artifacts.js';
8
8
  import { getUserStateDir } from '../config/user-state.js';
9
- import { collectGraphIndexFiles } from './index-files.js';
9
+ import { resolveIndexWorkers } from './index-preparation.js';
10
+ import { resolveFrameworkIndexRoot } from './framework-index-paths.js';
10
11
  import { collectIndexStatus } from './index-status.js';
12
+ import { graphIndexDigest, indexAdapterRegistry, selectIndexAdapters, indexFreshnessSnapshot, indexAdapterDigest, recordIndexVerification } from './index-adapters.js';
11
13
  import { getFortemiCoreSyncStatus, syncFortemiCoreIndex } from './fortemi-core-sync.js';
12
14
  import { acquireDirectoryLock, DirectoryLockContentionError } from './prebuilt-build-lock.js';
13
15
  import { resolveImportExclusions } from '../sessions/import-exclusions.js';
14
16
  import { resolveImportLimits } from '../sessions/import-limits.js';
15
- import { BUILTIN_GRAPH_CONFIGS, GRAPH_CONFIGS, INDEX_EXTRACTOR_VERSION, INDEX_VERSION, getGraphIndexDir, graphBuildOrder, loadGlobalGraphConfigs, loadUserGraphConfigs, resolveGraphBackendType, resolveGraphScanDir, } from './types.js';
17
+ import { GRAPH_CONFIGS, getGraphIndexDir, graphBuildOrder, loadWorkspaceGraphs, resolveGraphScanDir, } from './types.js';
16
18
  function readJson(file, tolerateMalformed = true) {
17
19
  try {
18
20
  return JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -28,14 +30,6 @@ function writeJson(file, value) {
28
30
  fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
29
31
  fs.renameSync(temporary, file);
30
32
  }
31
- /** The registry is process-global; reload it per workspace to avoid cross-project leakage. */
32
- function loadWorkspaceGraphs(workspace) {
33
- for (const name of Object.keys(GRAPH_CONFIGS))
34
- delete GRAPH_CONFIGS[name];
35
- Object.assign(GRAPH_CONFIGS, structuredClone(BUILTIN_GRAPH_CONFIGS));
36
- loadUserGraphConfigs(workspace);
37
- loadGlobalGraphConfigs();
38
- }
39
33
  function workspacePaths(options) {
40
34
  const cwd = options.cwd ?? process.cwd();
41
35
  const found = new Map();
@@ -123,33 +117,38 @@ function planWorkspaceRefresh(options, workspaces) {
123
117
  loadWorkspaceGraphs(workspace);
124
118
  const graphs = collectIndexStatus(workspace).graphs
125
119
  .sort((a, b) => graphBuildOrder(a.name, GRAPH_CONFIGS[a.name]) - graphBuildOrder(b.name, GRAPH_CONFIGS[b.name]) || a.name.localeCompare(b.name));
126
- if (!options.only || options.only === 'graphs') {
120
+ const adapters = indexAdapterRegistry(workspace);
121
+ const selected = selectIndexAdapters(adapters, options.only);
122
+ const includes = (name) => selected.some(adapter => adapter.name === name);
123
+ if (!options.only || options.only === 'all' || options.only === 'graphs' || selected.some(adapter => adapter.group === 'graphs')) {
127
124
  for (const graph of graphs) {
128
- if (graph.built || graph.defaultBuild || graph.origin === 'registered') {
125
+ if (selected.some(adapter => adapter.group === 'graphs' && adapter.graph === graph.name)
126
+ && (graph.built || graph.defaultBuild || graph.origin === 'registered'
127
+ || (options.only && options.only !== 'all' && options.only !== 'graphs'))) {
129
128
  jobs.push({ kind: 'graph', workspace, graph: graph.name, status: 'planned', wallMs: 0 });
130
129
  }
131
130
  }
132
131
  }
133
- if (!options.only || options.only === 'caches') {
132
+ if (!options.only || options.only === 'all' || options.only === 'caches' || options.only?.startsWith('caches:')) {
134
133
  for (const graph of graphs) {
135
134
  // Refresh only local, opted-in caches; never create a new cache from a packaged prebuilt.
136
- if (fs.existsSync(getFortemiCoreSyncStatus(workspace, graph.name).location)) {
135
+ if (includes(`caches:${graph.name}`) && fs.existsSync(getFortemiCoreSyncStatus(workspace, graph.name).location)) {
137
136
  jobs.push({ kind: 'cache', workspace, graph: graph.name, status: 'planned', wallMs: 0 });
138
137
  }
139
138
  }
140
139
  }
141
- if ((!options.only || options.only === 'sessions') && fs.existsSync(path.join(workspace, '.aiwg/sessions/catalog.sqlite'))) {
140
+ if (includes('sessions') && fs.existsSync(path.join(workspace, '.aiwg/sessions/catalog.sqlite'))) {
142
141
  jobs.push({ kind: 'sessions', workspace, status: 'planned', wallMs: 0, noManifest: 0 });
143
142
  }
144
143
  for (const kind of ['runtime', 'issues']) {
145
- if (options.only && options.only !== kind)
144
+ if (!includes(kind))
146
145
  continue;
147
146
  if (kind === 'issues' && !fs.existsSync(projectAiwgPath(workspace, 'issues')))
148
147
  continue;
149
148
  jobs.push({ kind, workspace, status: 'planned', wallMs: 0 });
150
149
  }
151
150
  }
152
- if (!options.only || options.only === 'models') {
151
+ if (!options.only || options.only === 'all' || options.only === 'models') {
153
152
  jobs.push({ kind: 'models', scope: 'host', status: 'planned', wallMs: 0 });
154
153
  }
155
154
  return jobs;
@@ -176,23 +175,6 @@ export async function refreshResourceGate(workspace, live) {
176
175
  const result = preflight.evaluateDispatchGate(live ?? preflight.readLiveHostResources({ paths: [workspace] }), thresholds);
177
176
  return { ok: result.ok, reason: result.failures.map((failure) => failure.reason).join('; ') || undefined };
178
177
  }
179
- async function graphFingerprint(workspace, graph) {
180
- const files = await collectGraphIndexFiles(workspace, graph);
181
- // Source import resolution also depends on the compiler/package configuration.
182
- if (graph === 'source') {
183
- for (const file of ['tsconfig.json', 'package.json']) {
184
- if (fs.existsSync(path.join(workspace, file)))
185
- files.push(path.join(workspace, file));
186
- }
187
- }
188
- const stats = [...new Set(files)].sort().map(file => {
189
- const stat = fs.statSync(file);
190
- return [file, stat.mtimeMs, stat.ctimeMs, stat.size];
191
- });
192
- return createHash('sha256').update(JSON.stringify([
193
- INDEX_VERSION, INDEX_EXTRACTOR_VERSION, GRAPH_CONFIGS[graph], resolveGraphBackendType(graph), stats,
194
- ])).digest('hex');
195
- }
196
178
  async function refreshWorkspaceRuntime(workspace, full, runtime) {
197
179
  const directory = projectAiwgPath(workspace, 'smiths', 'toolsmith');
198
180
  const output = path.join(directory, 'runtime.json');
@@ -236,33 +218,54 @@ async function refreshWorkspaceRuntime(workspace, full, runtime) {
236
218
  async function executeJob(job, options, aliases, runtime) {
237
219
  const workspace = job.workspace ?? process.env.HOME ?? homedir();
238
220
  if (job.kind === 'graph') {
221
+ const graphStart = Date.now();
239
222
  const graph = job.graph;
240
223
  const config = GRAPH_CONFIGS[graph];
241
- if (!config.scanDirs.some(dir => fs.existsSync(resolveGraphScanDir(workspace, dir)))) {
224
+ const sourceWorkspace = graph === 'framework' ? resolveFrameworkIndexRoot(workspace) : workspace;
225
+ if (!config.scanDirs.some(dir => fs.existsSync(resolveGraphScanDir(sourceWorkspace, dir)))) {
242
226
  job.status = 'skipped';
243
227
  job.reason = 'scan directories missing';
244
228
  return;
245
229
  }
246
230
  const directory = getGraphIndexDir(workspace, graph);
247
231
  const markerPath = path.join(directory, 'refresh-state.json');
232
+ const fingerprint = await graphIndexDigest(sourceWorkspace, graph);
248
233
  const marker = readJson(markerPath);
249
234
  const metadata = readJson(path.join(directory, 'metadata.json'));
250
- const fingerprint = await graphFingerprint(workspace, graph);
251
- const outputs = ['metadata.json', 'dependencies.json', 'tags.json', 'stats.json'];
252
- if (resolveGraphBackendType(graph) === 'sqlite')
253
- outputs.push('graph.db');
254
- if (!options.full && marker?.fingerprint === fingerprint && metadata?.builtAt === marker.builtAt
255
- && outputs.every(file => fs.existsSync(path.join(directory, file)))) {
256
- job.status = 'skipped';
257
- job.reason = 'unchanged';
258
- return;
235
+ const adapter = indexAdapterRegistry(workspace).find(item => item.name === graph);
236
+ if (!options.full && marker?.fingerprint === fingerprint && marker.builtAt === metadata?.builtAt
237
+ && indexFreshnessSnapshot(adapter, workspace).pendingWork === 0) {
238
+ const release = await acquireDirectoryLock(`${directory}.build.lock`, {
239
+ timeoutMs: options.lockWaitMs ?? 0, reclaimDeadOwner: true,
240
+ });
241
+ try {
242
+ const current = await graphIndexDigest(sourceWorkspace, graph);
243
+ if (current === fingerprint && indexFreshnessSnapshot(adapter, workspace).pendingWork === 0) {
244
+ recordIndexVerification(adapter, workspace, { before: fingerprint, after: current, wallMs: Date.now() - graphStart });
245
+ job.status = 'skipped';
246
+ job.reason = 'unchanged';
247
+ return;
248
+ }
249
+ }
250
+ finally {
251
+ await release();
252
+ }
259
253
  }
254
+ const beforeOutput = outputStamp(path.join(directory, 'metadata.json'));
260
255
  const { buildIndex } = await import('./index-builder.js');
261
- await buildIndex(workspace, { graph, force: Boolean(options.full), explicit: false, quiet: true });
256
+ await buildIndex(sourceWorkspace, { graph, force: Boolean(options.full), explicit: false, quiet: true, workers: options.workers, lockWaitMs: options.lockWaitMs ?? 0,
257
+ onDiagnostic: message => (job.diagnostics ??= []).push(message) });
262
258
  const built = readJson(path.join(directory, 'metadata.json'));
263
259
  if (!built)
264
260
  throw new Error(`graph did not produce metadata: ${graph}`);
265
- writeJson(markerPath, { fingerprint, builtAt: built.builtAt });
261
+ const verification = readJson(path.join(directory, 'freshness.json'));
262
+ if (!options.full && beforeOutput && beforeOutput === outputStamp(path.join(directory, 'metadata.json'))) {
263
+ job.status = 'skipped';
264
+ job.reason = 'unchanged';
265
+ }
266
+ if (!verification?.pendingWork && verification?.lastVerifiedDigest === fingerprint) {
267
+ writeJson(markerPath, { fingerprint, builtAt: built.builtAt });
268
+ }
266
269
  }
267
270
  else if (job.kind === 'cache') {
268
271
  const status = getFortemiCoreSyncStatus(workspace, job.graph);
@@ -331,7 +334,8 @@ async function executeJob(job, options, aliases, runtime) {
331
334
  }
332
335
  }
333
336
  }
334
- job.status = 'ok';
337
+ if (job.status !== 'skipped')
338
+ job.status = 'ok';
335
339
  }
336
340
  function outputStamp(file) {
337
341
  try {
@@ -351,6 +355,7 @@ function jobKey(job, aliases) {
351
355
  ...(job.kind === 'sessions' && aliases.length > 1 ? [aliases] : [])]);
352
356
  }
353
357
  export async function refreshIndexes(options = {}, dependencies = {}) {
358
+ options = { ...options, workers: resolveIndexWorkers(options.workers) };
354
359
  const start = Date.now();
355
360
  const workspaces = workspacePaths(options);
356
361
  const jobs = planWorkspaceRefresh(options, workspaces);
@@ -360,6 +365,13 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
360
365
  totals: { jobs: jobs.length, ok: 0, skipped: 0, deferred: 0, errors: 0, noManifest: 0, excluded: 0, wallMs: 0 },
361
366
  };
362
367
  const finish = () => {
368
+ for (const job of jobs) {
369
+ for (const receipt of job.sessionImports ?? (job.sessionImport ? [job.sessionImport] : [])) {
370
+ for (const warning of receipt.run.preflight?.scopeWarnings ?? []) {
371
+ report.messages.push(`WARNING ${job.workspace}: ${warning.message}`);
372
+ }
373
+ }
374
+ }
363
375
  for (const job of jobs.filter(job => job.noManifest)) {
364
376
  const command = `aiwg sessions discover --workspace '${job.workspace.replace(/'/g, "'\\''")}'`;
365
377
  report.messages.push(`Session refresh skipped for ${job.workspace}: no authorized discovery manifest. Run \`${command}\` or configure sessions.discovery_manifests in .aiwg/aiwg.config.`);
@@ -375,6 +387,17 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
375
387
  .reduce((total, receipt) => total + (receipt.totals.excluded ?? 0), 0), 0),
376
388
  wallMs: Date.now() - start,
377
389
  };
390
+ report.indices = [];
391
+ for (const workspace of workspaces.keys()) {
392
+ loadWorkspaceGraphs(workspace);
393
+ for (const adapter of selectIndexAdapters(indexAdapterRegistry(workspace), options.only)) {
394
+ report.indices.push({ ...indexFreshnessSnapshot(adapter, workspace), workspace });
395
+ }
396
+ }
397
+ if (!jobs.length && options.only && report.indices.length && report.indices.every(index => index.status === 'unsupported')) {
398
+ report.status = 'deferred';
399
+ report.reason = 'unsupported';
400
+ }
378
401
  return report;
379
402
  };
380
403
  if (options.dryRun)
@@ -425,6 +448,8 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
425
448
  continue;
426
449
  }
427
450
  const jobStart = Date.now();
451
+ let adapter;
452
+ let beforeDigest;
428
453
  try {
429
454
  const gateWorkspace = job.workspace ?? workspaces.keys().next().value ?? options.cwd ?? process.cwd();
430
455
  const gate = await (dependencies.gate ?? refreshResourceGate)(gateWorkspace);
@@ -437,6 +462,15 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
437
462
  }
438
463
  if (job.workspace)
439
464
  loadWorkspaceGraphs(job.workspace);
465
+ const workspace = job.workspace ?? options.cwd ?? process.cwd();
466
+ adapter = indexAdapterRegistry(workspace).find(item => item.name === (job.kind === 'graph' ? job.graph
467
+ : job.kind === 'cache' ? `caches:${job.graph}` : job.kind));
468
+ if (adapter?.supported && job.kind !== 'graph') {
469
+ try {
470
+ beforeDigest = await indexAdapterDigest(adapter, workspace);
471
+ }
472
+ catch { /* Owner reports unavailable dependencies. */ }
473
+ }
440
474
  if (job.kind === 'cache' && jobs.some(other => other.kind === 'graph'
441
475
  && other.workspace === job.workspace && other.graph === job.graph && other.status === 'error')) {
442
476
  job.status = 'skipped';
@@ -461,13 +495,52 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
461
495
  await executeJob(job, options, aliases, discoverRuntime);
462
496
  }
463
497
  catch (error) {
464
- job.status = 'error';
465
- job.error = error instanceof Error ? error.message : String(error);
498
+ if (error instanceof DirectoryLockContentionError) {
499
+ job.status = 'deferred';
500
+ job.reason = 'lock-held';
501
+ report.status = 'deferred';
502
+ }
503
+ else {
504
+ job.status = 'error';
505
+ job.error = error instanceof Error ? error.message : String(error);
506
+ }
466
507
  }
467
508
  finally {
468
509
  job.wallMs = Date.now() - jobStart;
510
+ if (adapter) {
511
+ const workspace = job.workspace ?? options.cwd ?? process.cwd();
512
+ if (adapter.supported && job.kind !== 'graph' && job.status !== 'deferred') {
513
+ try {
514
+ let after;
515
+ let probeError;
516
+ try {
517
+ after = await indexAdapterDigest(adapter, workspace);
518
+ }
519
+ catch (error) {
520
+ probeError = String(error);
521
+ }
522
+ if (job.kind === 'runtime' && job.status === 'ok')
523
+ beforeDigest = after;
524
+ const partial = job.reason && job.reason !== 'unchanged';
525
+ job.freshness = recordIndexVerification(adapter, workspace, { before: beforeDigest, after,
526
+ error: job.error ?? probeError, pendingWork: partial ? 1 : 0, wallMs: job.wallMs });
527
+ }
528
+ catch (error) {
529
+ job.freshness = { ...indexFreshnessSnapshot(adapter, workspace), status: 'degraded',
530
+ pendingWork: 1, lastError: String(error) };
531
+ }
532
+ }
533
+ else
534
+ job.freshness = indexFreshnessSnapshot(adapter, workspace);
535
+ if (job.freshness.pendingWork && !job.error && job.status !== 'deferred' && job.kind === 'graph'
536
+ && job.reason !== 'scan directories missing') {
537
+ job.status = 'deferred';
538
+ job.reason = 'inputs changed during build';
539
+ report.status = 'deferred';
540
+ }
541
+ }
469
542
  }
470
- if (job.status !== 'error' && job.reason !== 'graph failed'
543
+ if (job.status !== 'error' && job.status !== 'deferred' && job.reason !== 'graph failed'
471
544
  && job.reason !== 'no authorized discovery manifest')
472
545
  checkpoint.completed.push(key);
473
546
  writeJson(checkpointPath, checkpoint);
@@ -507,7 +580,7 @@ export async function runIndexRefresh(args, cwd = process.cwd(), signal) {
507
580
  options.dryRun = true;
508
581
  else if (flag === '--json')
509
582
  json = true;
510
- else if (['--workspace', '--root', '--only', '--lock-wait-ms', '--max-record-bytes', '--max-source-bytes', '--exclude'].includes(flag)) {
583
+ else if (['--workspace', '--root', '--only', '--lock-wait-ms', '--max-record-bytes', '--max-source-bytes', '--exclude', '--index-workers'].includes(flag)) {
511
584
  const value = args[++i];
512
585
  if (!value || value.startsWith('--'))
513
586
  throw new Error(`${flag} requires a value`);
@@ -519,13 +592,11 @@ export async function runIndexRefresh(args, cwd = process.cwd(), signal) {
519
592
  options.maxRecordBytes = value;
520
593
  else if (flag === '--max-source-bytes')
521
594
  options.maxSourceBytes = value;
595
+ else if (flag === '--index-workers')
596
+ options.workers = resolveIndexWorkers(value);
522
597
  else if (flag === '--exclude')
523
598
  (options.exclude ??= []).push(value);
524
599
  else if (flag === '--only') {
525
- if (value !== 'graphs' && value !== 'sessions' && value !== 'caches'
526
- && value !== 'runtime' && value !== 'models' && value !== 'issues') {
527
- throw new Error('--only must be graphs, sessions, caches, runtime, models, or issues');
528
- }
529
600
  options.only = value;
530
601
  }
531
602
  else {
@@ -537,6 +608,7 @@ export async function runIndexRefresh(args, cwd = process.cwd(), signal) {
537
608
  else
538
609
  throw new Error(`unknown refresh option: ${flag}`);
539
610
  }
611
+ options.workers = resolveIndexWorkers(options.workers);
540
612
  resolveImportLimits(undefined, { maxRecordBytes: options.maxRecordBytes, maxSourceBytes: options.maxSourceBytes });
541
613
  resolveImportExclusions(undefined, options.exclude);
542
614
  const report = await refreshIndexes(options);
@@ -547,6 +619,8 @@ export async function runIndexRefresh(args, cwd = process.cwd(), signal) {
547
619
  for (const job of report.jobs)
548
620
  console.log(` ${job.status}: ${job.kind} ${job.scope === 'host' ? '[host]' : job.workspace}${job.graph ? ` [${job.graph}]` : ''}${job.error || job.reason ? `: ${job.error ?? job.reason}` : ''}`);
549
621
  for (const job of report.jobs) {
622
+ for (const diagnostic of job.diagnostics ?? [])
623
+ console.log(` preparation: ${diagnostic}`);
550
624
  for (const receipt of job.sessionImports ?? (job.sessionImport ? [job.sessionImport] : [])) {
551
625
  for (const source of receipt.run.sources) {
552
626
  if (source.status === 'excluded')