@aiwg/cli 2026.10.5 → 2026.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +3 -1
  6. package/dist/src/artifacts/browser-export.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +8 -1
  8. package/dist/src/artifacts/cli.js +57 -6
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  10. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  11. package/dist/src/artifacts/graph-query.js +18 -8
  12. package/dist/src/artifacts/index-adapters.js +339 -0
  13. package/dist/src/artifacts/index-builder.js +143 -14
  14. package/dist/src/artifacts/index-files.js +15 -4
  15. package/dist/src/artifacts/index-reader.js +44 -0
  16. package/dist/src/artifacts/index-refresh.js +114 -55
  17. package/dist/src/artifacts/index-status.js +20 -5
  18. package/dist/src/artifacts/memory-maintenance.js +503 -0
  19. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  20. package/dist/src/artifacts/query-engine.js +263 -108
  21. package/dist/src/artifacts/relations-builder.js +2 -2
  22. package/dist/src/artifacts/source-graph.js +2 -2
  23. package/dist/src/artifacts/types.js +9 -1
  24. package/dist/src/cli/handlers/decision.js +19 -1
  25. package/dist/src/cli/handlers/guided-setup.js +470 -0
  26. package/dist/src/cli/handlers/help.js +2 -2
  27. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  28. package/dist/src/cli/handlers/index.js +4 -2
  29. package/dist/src/cli/handlers/init.js +41 -6
  30. package/dist/src/cli/handlers/local-executor.js +5 -3
  31. package/dist/src/cli/handlers/mc.js +509 -53
  32. package/dist/src/cli/handlers/models.js +32 -2
  33. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  34. package/dist/src/cli/handlers/rules.js +1 -1
  35. package/dist/src/cli/handlers/script-runner.js +73 -2
  36. package/dist/src/cli/handlers/serve.js +5 -3
  37. package/dist/src/cli/handlers/sessions.js +1 -0
  38. package/dist/src/cli/handlers/setup.js +428 -1
  39. package/dist/src/cli/handlers/skill-lint.js +1 -1
  40. package/dist/src/cli/handlers/subcommands.js +118 -8
  41. package/dist/src/cli/handlers/use.js +279 -93
  42. package/dist/src/cli/handlers/workspace.js +3 -1
  43. package/dist/src/cli/prompt-utils.js +8 -0
  44. package/dist/src/cli/router.js +16 -1
  45. package/dist/src/config/aiwg-config.js +42 -0
  46. package/dist/src/config/cli.js +7 -3
  47. package/dist/src/config/decision-config.js +14 -1
  48. package/dist/src/config/native-sandbox-warnings.js +25 -0
  49. package/dist/src/contributors/validation.js +6 -5
  50. package/dist/src/decision/admission.js +7 -3
  51. package/dist/src/decision/benchmark.js +469 -0
  52. package/dist/src/decision/ledger.js +22 -3
  53. package/dist/src/decision/pricing.js +147 -0
  54. package/dist/src/decision/qualification/gate-evidence.js +18 -15
  55. package/dist/src/decision/qualification/load-staged.js +539 -0
  56. package/dist/src/decision/qualification/load.js +109 -33
  57. package/dist/src/decision/qualification/runner.js +5 -2
  58. package/dist/src/decision/review/service.js +6 -4
  59. package/dist/src/decision/review/validate.js +23 -2
  60. package/dist/src/decision/savings.js +8 -3
  61. package/dist/src/discovery/index.js +492 -0
  62. package/dist/src/discovery/interview.js +233 -0
  63. package/dist/src/discovery/plan-apply.js +571 -0
  64. package/dist/src/discovery/plan-store.js +171 -0
  65. package/dist/src/discovery/plan-types.js +2 -0
  66. package/dist/src/discovery/plan-validation.js +139 -0
  67. package/dist/src/discovery/types.js +2 -0
  68. package/dist/src/extensions/commands/definitions.js +49 -9
  69. package/dist/src/extensions/manifest.js +71 -44
  70. package/dist/src/extensions/project-local-discovery.js +1 -1
  71. package/dist/src/extensions/project-quickref.js +18 -17
  72. package/dist/src/extensions/validation.js +32 -29
  73. package/dist/src/housekeeper/daemon-config.js +148 -0
  74. package/dist/src/housekeeper/ipc.js +84 -0
  75. package/dist/src/housekeeper/lifecycle.js +148 -0
  76. package/dist/src/housekeeper/platform.js +35 -0
  77. package/dist/src/housekeeper/status.js +70 -0
  78. package/dist/src/housekeeper/units.js +114 -0
  79. package/dist/src/jobs/flow.js +22 -21
  80. package/dist/src/lint/rule-activation.js +20 -5
  81. package/dist/src/lint/runner.js +1 -1
  82. package/dist/src/mcp/cli.mjs +5 -6
  83. package/dist/src/mcp/config-file.mjs +6 -2
  84. package/dist/src/mcp/config-layers.mjs +23 -7
  85. package/dist/src/mcp/profiles.js +18 -31
  86. package/dist/src/mcp/profiles.mjs +18 -32
  87. package/dist/src/mcp/registry.js +7 -27
  88. package/dist/src/mcp/registry.mjs +7 -28
  89. package/dist/src/mcp/server.mjs +34 -1
  90. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  91. package/dist/src/mcp/tools/command-run.mjs +3 -2
  92. package/dist/src/mcp/tools/decision.mjs +49 -48
  93. package/dist/src/mcp/tools/discovery.mjs +46 -21
  94. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  95. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  96. package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
  97. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  98. package/dist/src/memory/audit.js +719 -0
  99. package/dist/src/memory/canonical-context.js +392 -13
  100. package/dist/src/memory/canonical-maintenance.js +95 -0
  101. package/dist/src/memory/cli-result.js +22 -0
  102. package/dist/src/memory/cli.js +176 -0
  103. package/dist/src/memory/context-pack.js +57 -6
  104. package/dist/src/memory/cost.js +512 -0
  105. package/dist/src/memory/firewall-classification.d.mts +7 -0
  106. package/dist/src/memory/firewall-classification.mjs +32 -0
  107. package/dist/src/memory/index.js +8 -0
  108. package/dist/src/memory/line-maintenance.js +68 -0
  109. package/dist/src/memory/maintenance-cli.js +286 -0
  110. package/dist/src/memory/maintenance-contract.js +136 -0
  111. package/dist/src/memory/maintenance-index.js +18 -0
  112. package/dist/src/memory/maintenance-owner.js +2 -0
  113. package/dist/src/memory/maintenance-record.js +138 -0
  114. package/dist/src/memory/maintenance.js +254 -0
  115. package/dist/src/memory/provider-memory-adapter.js +154 -0
  116. package/dist/src/memory/recommend-cli.js +83 -0
  117. package/dist/src/memory/recommend.js +269 -0
  118. package/dist/src/memory/scheduled.js +81 -0
  119. package/dist/src/memory/taxonomy.js +44 -0
  120. package/dist/src/mission-protocol/codecs.js +19 -9
  121. package/dist/src/models/config-loader.js +11 -1
  122. package/dist/src/models/model-capabilities.v1.json +74 -50
  123. package/dist/src/models/provider-policy.js +125 -62
  124. package/dist/src/models/resolver.js +45 -8
  125. package/dist/src/models/worker-installation.d.mts +7 -0
  126. package/dist/src/models/worker-installation.mjs +169 -37
  127. package/dist/src/models/wrapper-route.js +1 -1
  128. package/dist/src/output-modes/registry.js +8 -7
  129. package/dist/src/plugin/metadata-validator.js +1 -1
  130. package/dist/src/providers/deploy-version.d.mts +1 -0
  131. package/dist/src/providers/deploy-version.mjs +17 -0
  132. package/dist/src/providers/provider-definitions.js +19 -16
  133. package/dist/src/providers/skill-ownership.d.mts +11 -2
  134. package/dist/src/providers/skill-ownership.mjs +35 -17
  135. package/dist/src/rules/activation.d.mts +11 -2
  136. package/dist/src/rules/activation.mjs +82 -6
  137. package/dist/src/rules/inventory.js +1 -1
  138. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  139. package/dist/src/schema/zod-validation.d.mts +6 -0
  140. package/dist/src/schema/zod-validation.mjs +50 -0
  141. package/dist/src/serve/executor-registry.js +297 -20
  142. package/dist/src/serve/executor-token.d.mts +1 -0
  143. package/dist/src/serve/executor-token.mjs +8 -0
  144. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  145. package/dist/src/serve/worker-invocation.js +114 -15
  146. package/dist/src/sessions/adapters/claude.js +19 -18
  147. package/dist/src/sessions/adapters/codex.js +10 -9
  148. package/dist/src/sessions/adapters/copilot.js +14 -13
  149. package/dist/src/sessions/adapters/cursor.js +19 -18
  150. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  151. package/dist/src/sessions/adapters/factory.js +11 -10
  152. package/dist/src/sessions/adapters/generic.js +32 -23
  153. package/dist/src/sessions/adapters/grokbot.js +35 -35
  154. package/dist/src/sessions/adapters/hermes.js +20 -19
  155. package/dist/src/sessions/adapters/muse.js +50 -49
  156. package/dist/src/sessions/adapters/omp.js +5 -4
  157. package/dist/src/sessions/adapters/openclaw.js +30 -29
  158. package/dist/src/sessions/adapters/opencode.js +26 -26
  159. package/dist/src/sessions/adapters/openhuman.js +24 -23
  160. package/dist/src/sessions/adapters/pi.js +5 -4
  161. package/dist/src/sessions/adapters/windsurf.js +8 -8
  162. package/dist/src/sessions/analytics.js +10 -9
  163. package/dist/src/sessions/candidates.js +7 -6
  164. package/dist/src/sessions/contracts.js +63 -53
  165. package/dist/src/sessions/output-registration.js +11 -10
  166. package/dist/src/sessions/workspace-discovery.js +3 -4
  167. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  168. package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
  169. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  170. package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
  171. package/dist/src/update/checker.d.mts +4 -0
  172. package/dist/src/update/checker.mjs +14 -0
  173. package/dist/src/writing/exemplar-selection.js +6 -6
  174. package/dist/src/writing/voice-evaluation.js +24 -23
  175. package/dist/src/writing/writer-migration.js +14 -13
  176. package/dist/src/writing/writer-profile.js +19 -18
  177. package/dist/src/writing/writing-brief.js +18 -17
  178. package/dist/src/writing/writing-receipt.js +47 -46
  179. package/package.json +5 -2
  180. package/schemas/decision/DecisionReview.schema.json +78 -1
  181. package/schemas/mission-v1.schema.json +3 -0
  182. package/tools/agents/deploy-agents.mjs +67 -39
  183. package/tools/agents/providers/antigravity.mjs +6 -1
  184. package/tools/agents/providers/base.mjs +19 -13
  185. package/tools/agents/providers/claude.mjs +1 -1
  186. package/tools/agents/providers/codex.mjs +50 -56
  187. package/tools/agents/providers/copilot.mjs +8 -2
  188. package/tools/agents/providers/cursor.mjs +1 -1
  189. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  190. package/tools/agents/providers/factory.mjs +7 -1
  191. package/tools/agents/providers/grok-build.mjs +18 -2
  192. package/tools/agents/providers/grokbot.mjs +1 -1
  193. package/tools/agents/providers/hermes.mjs +7 -13
  194. package/tools/agents/providers/muse-hooks.mjs +21 -14
  195. package/tools/agents/providers/muse.mjs +7 -2
  196. package/tools/agents/providers/omp.mjs +28 -4
  197. package/tools/agents/providers/opencode.mjs +14 -5
  198. package/tools/agents/providers/openhuman.mjs +1 -1
  199. package/tools/agents/providers/pi.mjs +1 -1
  200. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  201. package/tools/agents/providers/rule-delivery.mjs +263 -71
  202. package/tools/agents/providers/windsurf.mjs +7 -0
  203. package/tools/cli/workspace-status.mjs +5 -0
  204. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  205. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  206. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  207. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  208. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  209. package/tools/ralph-external/process-monitor.mjs +7 -4
  210. package/tools/ralph-external/session-launcher.mjs +280 -32
  211. package/tools/skills/deploy-skills-codex.mjs +11 -7
  212. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -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';
@@ -7,13 +7,14 @@ 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
9
  import { resolveIndexWorkers } from './index-preparation.js';
10
- import { collectGraphIndexFiles } from './index-files.js';
10
+ import { resolveFrameworkIndexRoot } from './framework-index-paths.js';
11
11
  import { collectIndexStatus } from './index-status.js';
12
+ import { graphIndexDigest, indexAdapterRegistry, selectIndexAdapters, indexFreshnessSnapshot, indexAdapterDigest, recordIndexVerification } from './index-adapters.js';
12
13
  import { getFortemiCoreSyncStatus, syncFortemiCoreIndex } from './fortemi-core-sync.js';
13
14
  import { acquireDirectoryLock, DirectoryLockContentionError } from './prebuilt-build-lock.js';
14
15
  import { resolveImportExclusions } from '../sessions/import-exclusions.js';
15
16
  import { resolveImportLimits } from '../sessions/import-limits.js';
16
- 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';
17
18
  function readJson(file, tolerateMalformed = true) {
18
19
  try {
19
20
  return JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -29,14 +30,6 @@ function writeJson(file, value) {
29
30
  fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
30
31
  fs.renameSync(temporary, file);
31
32
  }
32
- /** The registry is process-global; reload it per workspace to avoid cross-project leakage. */
33
- function loadWorkspaceGraphs(workspace) {
34
- for (const name of Object.keys(GRAPH_CONFIGS))
35
- delete GRAPH_CONFIGS[name];
36
- Object.assign(GRAPH_CONFIGS, structuredClone(BUILTIN_GRAPH_CONFIGS));
37
- loadUserGraphConfigs(workspace);
38
- loadGlobalGraphConfigs();
39
- }
40
33
  function workspacePaths(options) {
41
34
  const cwd = options.cwd ?? process.cwd();
42
35
  const found = new Map();
@@ -124,33 +117,38 @@ function planWorkspaceRefresh(options, workspaces) {
124
117
  loadWorkspaceGraphs(workspace);
125
118
  const graphs = collectIndexStatus(workspace).graphs
126
119
  .sort((a, b) => graphBuildOrder(a.name, GRAPH_CONFIGS[a.name]) - graphBuildOrder(b.name, GRAPH_CONFIGS[b.name]) || a.name.localeCompare(b.name));
127
- 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')) {
128
124
  for (const graph of graphs) {
129
- 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'))) {
130
128
  jobs.push({ kind: 'graph', workspace, graph: graph.name, status: 'planned', wallMs: 0 });
131
129
  }
132
130
  }
133
131
  }
134
- if (!options.only || options.only === 'caches') {
132
+ if (!options.only || options.only === 'all' || options.only === 'caches' || options.only?.startsWith('caches:')) {
135
133
  for (const graph of graphs) {
136
134
  // Refresh only local, opted-in caches; never create a new cache from a packaged prebuilt.
137
- if (fs.existsSync(getFortemiCoreSyncStatus(workspace, graph.name).location)) {
135
+ if (includes(`caches:${graph.name}`) && fs.existsSync(getFortemiCoreSyncStatus(workspace, graph.name).location)) {
138
136
  jobs.push({ kind: 'cache', workspace, graph: graph.name, status: 'planned', wallMs: 0 });
139
137
  }
140
138
  }
141
139
  }
142
- 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'))) {
143
141
  jobs.push({ kind: 'sessions', workspace, status: 'planned', wallMs: 0, noManifest: 0 });
144
142
  }
145
143
  for (const kind of ['runtime', 'issues']) {
146
- if (options.only && options.only !== kind)
144
+ if (!includes(kind))
147
145
  continue;
148
146
  if (kind === 'issues' && !fs.existsSync(projectAiwgPath(workspace, 'issues')))
149
147
  continue;
150
148
  jobs.push({ kind, workspace, status: 'planned', wallMs: 0 });
151
149
  }
152
150
  }
153
- if (!options.only || options.only === 'models') {
151
+ if (!options.only || options.only === 'all' || options.only === 'models') {
154
152
  jobs.push({ kind: 'models', scope: 'host', status: 'planned', wallMs: 0 });
155
153
  }
156
154
  return jobs;
@@ -177,23 +175,6 @@ export async function refreshResourceGate(workspace, live) {
177
175
  const result = preflight.evaluateDispatchGate(live ?? preflight.readLiveHostResources({ paths: [workspace] }), thresholds);
178
176
  return { ok: result.ok, reason: result.failures.map((failure) => failure.reason).join('; ') || undefined };
179
177
  }
180
- async function graphFingerprint(workspace, graph) {
181
- const files = await collectGraphIndexFiles(workspace, graph);
182
- // Source import resolution also depends on the compiler/package configuration.
183
- if (graph === 'source') {
184
- for (const file of ['tsconfig.json', 'package.json']) {
185
- if (fs.existsSync(path.join(workspace, file)))
186
- files.push(path.join(workspace, file));
187
- }
188
- }
189
- const stats = [...new Set(files)].sort().map(file => {
190
- const stat = fs.statSync(file);
191
- return [file, stat.mtimeMs, stat.ctimeMs, stat.size];
192
- });
193
- return createHash('sha256').update(JSON.stringify([
194
- INDEX_VERSION, INDEX_EXTRACTOR_VERSION, GRAPH_CONFIGS[graph], resolveGraphBackendType(graph), stats,
195
- ])).digest('hex');
196
- }
197
178
  async function refreshWorkspaceRuntime(workspace, full, runtime) {
198
179
  const directory = projectAiwgPath(workspace, 'smiths', 'toolsmith');
199
180
  const output = path.join(directory, 'runtime.json');
@@ -237,34 +218,54 @@ async function refreshWorkspaceRuntime(workspace, full, runtime) {
237
218
  async function executeJob(job, options, aliases, runtime) {
238
219
  const workspace = job.workspace ?? process.env.HOME ?? homedir();
239
220
  if (job.kind === 'graph') {
221
+ const graphStart = Date.now();
240
222
  const graph = job.graph;
241
223
  const config = GRAPH_CONFIGS[graph];
242
- 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)))) {
243
226
  job.status = 'skipped';
244
227
  job.reason = 'scan directories missing';
245
228
  return;
246
229
  }
247
230
  const directory = getGraphIndexDir(workspace, graph);
248
231
  const markerPath = path.join(directory, 'refresh-state.json');
232
+ const fingerprint = await graphIndexDigest(sourceWorkspace, graph);
249
233
  const marker = readJson(markerPath);
250
234
  const metadata = readJson(path.join(directory, 'metadata.json'));
251
- const fingerprint = await graphFingerprint(workspace, graph);
252
- const outputs = ['metadata.json', 'dependencies.json', 'tags.json', 'stats.json'];
253
- if (resolveGraphBackendType(graph) === 'sqlite')
254
- outputs.push('graph.db');
255
- if (!options.full && marker?.fingerprint === fingerprint && metadata?.builtAt === marker.builtAt
256
- && outputs.every(file => fs.existsSync(path.join(directory, file)))) {
257
- job.status = 'skipped';
258
- job.reason = 'unchanged';
259
- 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
+ }
260
253
  }
254
+ const beforeOutput = outputStamp(path.join(directory, 'metadata.json'));
261
255
  const { buildIndex } = await import('./index-builder.js');
262
- await buildIndex(workspace, { graph, force: Boolean(options.full), explicit: false, quiet: true, workers: options.workers,
256
+ await buildIndex(sourceWorkspace, { graph, force: Boolean(options.full), explicit: false, quiet: true, workers: options.workers, lockWaitMs: options.lockWaitMs ?? 0,
263
257
  onDiagnostic: message => (job.diagnostics ??= []).push(message) });
264
258
  const built = readJson(path.join(directory, 'metadata.json'));
265
259
  if (!built)
266
260
  throw new Error(`graph did not produce metadata: ${graph}`);
267
- 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
+ }
268
269
  }
269
270
  else if (job.kind === 'cache') {
270
271
  const status = getFortemiCoreSyncStatus(workspace, job.graph);
@@ -333,7 +334,8 @@ async function executeJob(job, options, aliases, runtime) {
333
334
  }
334
335
  }
335
336
  }
336
- job.status = 'ok';
337
+ if (job.status !== 'skipped')
338
+ job.status = 'ok';
337
339
  }
338
340
  function outputStamp(file) {
339
341
  try {
@@ -385,6 +387,17 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
385
387
  .reduce((total, receipt) => total + (receipt.totals.excluded ?? 0), 0), 0),
386
388
  wallMs: Date.now() - start,
387
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
+ }
388
401
  return report;
389
402
  };
390
403
  if (options.dryRun)
@@ -435,6 +448,8 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
435
448
  continue;
436
449
  }
437
450
  const jobStart = Date.now();
451
+ let adapter;
452
+ let beforeDigest;
438
453
  try {
439
454
  const gateWorkspace = job.workspace ?? workspaces.keys().next().value ?? options.cwd ?? process.cwd();
440
455
  const gate = await (dependencies.gate ?? refreshResourceGate)(gateWorkspace);
@@ -447,6 +462,15 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
447
462
  }
448
463
  if (job.workspace)
449
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
+ }
450
474
  if (job.kind === 'cache' && jobs.some(other => other.kind === 'graph'
451
475
  && other.workspace === job.workspace && other.graph === job.graph && other.status === 'error')) {
452
476
  job.status = 'skipped';
@@ -471,13 +495,52 @@ export async function refreshIndexes(options = {}, dependencies = {}) {
471
495
  await executeJob(job, options, aliases, discoverRuntime);
472
496
  }
473
497
  catch (error) {
474
- job.status = 'error';
475
- 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
+ }
476
507
  }
477
508
  finally {
478
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
+ }
479
542
  }
480
- if (job.status !== 'error' && job.reason !== 'graph failed'
543
+ if (job.status !== 'error' && job.status !== 'deferred' && job.reason !== 'graph failed'
481
544
  && job.reason !== 'no authorized discovery manifest')
482
545
  checkpoint.completed.push(key);
483
546
  writeJson(checkpointPath, checkpoint);
@@ -534,10 +597,6 @@ export async function runIndexRefresh(args, cwd = process.cwd(), signal) {
534
597
  else if (flag === '--exclude')
535
598
  (options.exclude ??= []).push(value);
536
599
  else if (flag === '--only') {
537
- if (value !== 'graphs' && value !== 'sessions' && value !== 'caches'
538
- && value !== 'runtime' && value !== 'models' && value !== 'issues') {
539
- throw new Error('--only must be graphs, sessions, caches, runtime, models, or issues');
540
- }
541
600
  options.only = value;
542
601
  }
543
602
  else {
@@ -18,8 +18,9 @@
18
18
  */
19
19
  import * as fs from 'node:fs';
20
20
  import * as path from 'node:path';
21
- import { GRAPH_CONFIGS, BUILTIN_GRAPH_CONFIGS, getGraphIndexDir, getProjectIndexRoot, loadGlobalGraphConfigs, loadUserGraphConfigs, resolveGraphBackendType, } from './types.js';
21
+ import { GRAPH_CONFIGS, BUILTIN_GRAPH_CONFIGS, getGraphIndexDir, getProjectIndexRoot, loadWorkspaceGraphs, resolveGraphBackendType, } from './types.js';
22
22
  import { getFortemiCoreSyncStatus, } from './fortemi-core-sync.js';
23
+ import { indexAdapterRegistry, indexFreshnessSnapshot, selectIndexAdapters } from './index-adapters.js';
23
24
  function readBuiltMeta(indexDir) {
24
25
  try {
25
26
  const metaPath = path.join(indexDir, 'metadata.json');
@@ -42,12 +43,11 @@ function readBuiltMeta(indexDir) {
42
43
  *
43
44
  * @param nowMs - injectable clock for deterministic tests (defaults to Date.now()).
44
45
  */
45
- export function collectIndexStatus(cwd, nowMs) {
46
+ export function collectIndexStatus(cwd, nowMs, only) {
46
47
  // Populate the registry from module + operator config, collecting warnings
47
48
  // instead of letting malformed defs vanish (#1624).
48
49
  const warnings = [];
49
- loadUserGraphConfigs(cwd, warnings);
50
- loadGlobalGraphConfigs(warnings);
50
+ loadWorkspaceGraphs(cwd, warnings);
51
51
  const now = nowMs ?? Date.now();
52
52
  const graphs = [];
53
53
  for (const [name, config] of Object.entries(GRAPH_CONFIGS)) {
@@ -99,6 +99,10 @@ export function collectIndexStatus(cwd, nowMs) {
99
99
  const built = graphs.filter((g) => g.built).length;
100
100
  return {
101
101
  graphs,
102
+ indices: selectIndexAdapters(indexAdapterRegistry(cwd), only).map(adapter => ({
103
+ ...indexFreshnessSnapshot(adapter, cwd, now), identity: adapter.identity, backend: adapter.backend,
104
+ operation: adapter.operation, roots: adapter.roots(cwd), exclusions: adapter.exclusions, outputPaths: adapter.outputPaths,
105
+ })),
102
106
  orphanIndexDirs,
103
107
  warnings,
104
108
  fortemiCore: getFortemiCoreSyncStatus(cwd),
@@ -111,9 +115,17 @@ export function collectIndexStatus(cwd, nowMs) {
111
115
  },
112
116
  };
113
117
  }
118
+ /** Probe inputs through their owners; the legacy synchronous reader stays available. */
119
+ export async function probeIndexStatus(cwd, only, nowMs = Date.now()) {
120
+ const report = collectIndexStatus(cwd, nowMs, only);
121
+ const adapters = new Map(indexAdapterRegistry(cwd).map(adapter => [adapter.name, adapter]));
122
+ report.indices = await Promise.all(report.indices.map(async (index) => ({ ...index,
123
+ ...await adapters.get(index.name).probe(cwd, nowMs) })));
124
+ return report;
125
+ }
114
126
  /** Render `aiwg index status` (human table or JSON). */
115
127
  export async function showIndexStatus(cwd, opts = {}) {
116
- const report = collectIndexStatus(cwd);
128
+ const report = await probeIndexStatus(cwd, opts.only);
117
129
  if (opts.json) {
118
130
  console.log(JSON.stringify(report, null, 2));
119
131
  return;
@@ -182,6 +194,9 @@ export async function showIndexStatus(cwd, opts = {}) {
182
194
  console.log(`⚠ ${missing} registered durable index(es) are not built. Run \`aiwg index build --all\`.`);
183
195
  }
184
196
  console.log('');
197
+ for (const index of report.indices) {
198
+ console.log(` ${index.name}: ${index.status} (target ${index.policy.targetStaleAfterMs}ms, pending ${index.pendingWork})${index.lastError ? `: ${index.lastError}` : ''}`);
199
+ }
185
200
  console.log(`${report.summary.total} graph(s): ${report.summary.built} built, ${missing} missing, ` +
186
201
  `${report.summary.orphans} orphan(s), ${report.summary.warnings} warning(s).`);
187
202
  }