@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
@@ -5,9 +5,9 @@ import { fileURLToPath } from "node:url";
5
5
  import { parse as parseYaml } from "yaml";
6
6
  import { GRAPH_CONFIGS, getProjectIndexRoot, loadGlobalGraphConfigs } from "./types.js";
7
7
  import { buildAiwgFortemiIndexExport, } from "./browser-export.js";
8
- import { loadGraphIndexFile } from "./index-reader.js";
8
+ import { loadGraphIndexFile, publishIndexFiles } from "./index-reader.js";
9
9
  import { getFrameworkIndexBaseDir } from './framework-index-paths.js';
10
- function syncDir(cwd, graph) {
10
+ export function getFortemiCoreIndexDir(cwd, graph) {
11
11
  loadGlobalGraphConfigs();
12
12
  if (graph === 'framework')
13
13
  return path.join(getFrameworkIndexBaseDir(cwd), 'fortemi-core', graph);
@@ -180,14 +180,15 @@ function writeExport(file, exported, maxChars) {
180
180
  }
181
181
  export function syncFortemiCoreIndex(cwd, options = {}) {
182
182
  const graph = options.graph ?? "project";
183
- const dir = syncDir(cwd, graph);
183
+ const dir = getFortemiCoreIndexDir(cwd, graph);
184
184
  const exportPath = path.join(dir, "aiwg-fortemi-index-v2.json");
185
185
  const manifestPath = path.join(dir, "manifest.json");
186
186
  const maxChars = options.maxChunkChars ?? 64 * 1024;
187
187
  if (!Number.isSafeInteger(maxChars) || maxChars < 64)
188
188
  throw new Error("maxChunkChars must be an integer of at least 64");
189
189
  fs.mkdirSync(dir, { recursive: true });
190
- const temporaryPath = path.join(dir, `.export-${randomUUID()}.tmp`);
190
+ const staging = fs.mkdtempSync(path.join(dir, ".sync-"));
191
+ const temporaryPath = path.join(staging, `.export-${randomUUID()}.tmp`);
191
192
  const previous = readManifest(manifestPath);
192
193
  const sourceBuiltAt = sourceIndexBuiltAt(cwd, graph);
193
194
  const buildExport = (generatedAt) => {
@@ -200,10 +201,25 @@ export function syncFortemiCoreIndex(cwd, options = {}) {
200
201
  });
201
202
  const checksum = writeExport(temporaryPath, exported, maxChars);
202
203
  const recordsWithConcepts = exported.items.filter((item) => (item.skos_concepts?.length ?? 0) > 0).length;
204
+ const shortTextThreshold = 200;
205
+ let shortTextCount = 0;
206
+ let filenameTitleCount = 0;
207
+ for (const item of exported.items) {
208
+ if (item.text.trim().length < shortTextThreshold)
209
+ shortTextCount++;
210
+ if (item.title.trim() === path.posix.basename(item.source.path))
211
+ filenameTitleCount++;
212
+ }
203
213
  return {
204
214
  generatedAt,
205
215
  checksum,
206
216
  itemCount: exported.items.length,
217
+ textQuality: {
218
+ short_text_threshold_chars: shortTextThreshold,
219
+ short_text_count: shortTextCount,
220
+ filename_title_count: filenameTitleCount,
221
+ degenerate: exported.items.length === 0 || shortTextCount / exported.items.length >= 0.8,
222
+ },
207
223
  skosCoverage: {
208
224
  records_with_concepts: recordsWithConcepts,
209
225
  total_records: exported.items.length,
@@ -228,7 +244,7 @@ export function syncFortemiCoreIndex(cwd, options = {}) {
228
244
  }
229
245
  }
230
246
  }
231
- fs.renameSync(temporaryPath, exportPath);
247
+ fs.renameSync(temporaryPath, path.join(staging, path.basename(exportPath)));
232
248
  const manifest = {
233
249
  schema_version: "aiwg.fortemi.core-sync.v1",
234
250
  backend: "fortemi-core",
@@ -238,19 +254,21 @@ export function syncFortemiCoreIndex(cwd, options = {}) {
238
254
  export_schema_version: "aiwg.fortemi.index.export.v2",
239
255
  export_checksum: built.checksum,
240
256
  item_count: built.itemCount,
257
+ text_quality: built.textQuality,
241
258
  skos_coverage: built.skosCoverage,
242
259
  status,
243
260
  source_index_built_at: sourceBuiltAt,
244
261
  };
245
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
262
+ fs.writeFileSync(path.join(staging, path.basename(manifestPath)), JSON.stringify(manifest, null, 2) + "\n", "utf-8");
263
+ publishIndexFiles(staging, dir);
246
264
  return manifest;
247
265
  }
248
266
  finally {
249
- fs.rmSync(temporaryPath, { force: true });
267
+ fs.rmSync(staging, { recursive: true, force: true });
250
268
  }
251
269
  }
252
270
  export function getFortemiCoreSyncStatus(cwd, graph = "project") {
253
- const dir = syncDir(cwd, graph);
271
+ const dir = getFortemiCoreIndexDir(cwd, graph);
254
272
  const manifestPath = path.join(dir, "manifest.json");
255
273
  const exportPath = path.join(dir, "aiwg-fortemi-index-v2.json");
256
274
  const { manifest, reason: manifestReadReason } = readManifestResult(manifestPath);
@@ -9,8 +9,17 @@
9
9
  * @tests @test/unit/artifacts/graph-query.test.ts
10
10
  */
11
11
  import { normalizeEdges } from './types.js';
12
- import { buildFortemiCoreDependencyGraph } from './fortemi-core-query-adapter.js';
12
+ import { buildFortemiCoreDependencyGraph, loadFortemiCoreMetadataEntries } from './fortemi-core-query-adapter.js';
13
13
  import { closeGraphBackend, openGraphBackend } from './backend-runtime.js';
14
+ import { loadGraphIndexFile } from './index-reader.js';
15
+ import { isOrdinaryMemoryEntry } from './query-engine.js';
16
+ function graphMemoryMetadata(cwd, graph, backend) {
17
+ const local = loadGraphIndexFile(cwd, 'metadata.json', graph)?.entries ?? {};
18
+ if (backend === 'fortemi-core') {
19
+ return { ...Object.fromEntries(loadFortemiCoreMetadataEntries(cwd, graph).entries.map(entry => [entry.path, entry])), ...local };
20
+ }
21
+ return local;
22
+ }
14
23
  /**
15
24
  * Get neighbors of a node in a dependency graph.
16
25
  *
@@ -20,15 +29,15 @@ import { closeGraphBackend, openGraphBackend } from './backend-runtime.js';
20
29
  * @param edgeType - Optional filter by edge type
21
30
  * @returns Array of neighbor paths
22
31
  */
23
- export function getNeighbors(graph, node, direction = 'both', edgeType) {
32
+ export function getNeighbors(graph, node, direction = 'both', edgeType, metadata = {}) {
24
33
  const entry = graph[node];
25
- if (!entry)
34
+ if (!entry || (metadata[node] && !isOrdinaryMemoryEntry(metadata[node])))
26
35
  return [];
27
36
  const results = new Set();
28
37
  if (direction === 'in' || direction === 'both') {
29
38
  const edges = normalizeEdges(entry.upstream);
30
39
  for (const edge of edges) {
31
- if (!edgeType || edge.type === edgeType) {
40
+ if ((!edgeType || edge.type === edgeType) && (!metadata[edge.path] || isOrdinaryMemoryEntry(metadata[edge.path]))) {
32
41
  results.add(edge.path);
33
42
  }
34
43
  }
@@ -36,7 +45,7 @@ export function getNeighbors(graph, node, direction = 'both', edgeType) {
36
45
  if (direction === 'out' || direction === 'both') {
37
46
  const edges = normalizeEdges(entry.downstream);
38
47
  for (const edge of edges) {
39
- if (!edgeType || edge.type === edgeType) {
48
+ if ((!edgeType || edge.type === edgeType) && (!metadata[edge.path] || isOrdinaryMemoryEntry(metadata[edge.path]))) {
40
49
  results.add(edge.path);
41
50
  }
42
51
  }
@@ -134,7 +143,7 @@ export async function showNeighbors(cwd, options) {
134
143
  }
135
144
  process.exit(1);
136
145
  }
137
- const neighbors = getNeighbors(graph, resolved, direction, edgeType);
146
+ const neighbors = getNeighbors(graph, resolved, direction, edgeType, graphMemoryMetadata(cwd, graphType, backend));
138
147
  if (json) {
139
148
  console.log(JSON.stringify({
140
149
  graph: graphType,
@@ -208,8 +217,9 @@ export async function executeSetQuery(cwd, options) {
208
217
  console.error(`Error: Node '${nodeB}' not found in graph '${graphType}'.`);
209
218
  process.exit(1);
210
219
  }
211
- const neighborsA = getNeighbors(graph, resolvedA, direction, edgeType);
212
- const neighborsB = getNeighbors(graph, resolvedB, direction, edgeType);
220
+ const metadata = graphMemoryMetadata(cwd, graphType, backend);
221
+ const neighborsA = getNeighbors(graph, resolvedA, direction, edgeType, metadata);
222
+ const neighborsB = getNeighbors(graph, resolvedB, direction, edgeType, metadata);
213
223
  let result;
214
224
  switch (op) {
215
225
  case 'intersection':
@@ -0,0 +1,339 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getUserStateDir } from '../config/user-state.js';
5
+ import { projectAiwgPath, projectControlPath } from '../config/project-artifacts.js';
6
+ import { validateIndexConfig } from '../config/aiwg-config.js';
7
+ import { collectGraphIndexFiles, findArtifactFiles, INDEX_SCRATCH_DIRS } from './index-files.js';
8
+ import { writeIndexFile } from './index-reader.js';
9
+ import { GRAPH_CONFIGS, INDEX_VERSION, INDEX_EXTRACTOR_VERSION, getGraphIndexDir, resolveGraphBackendType, resolveGraphScanDir } from './types.js';
10
+ import { acquireDirectoryLock, DirectoryLockContentionError } from './prebuilt-build-lock.js';
11
+ import { resolveFrameworkIndexRoot } from './framework-index-paths.js';
12
+ import { resolveProjectLocalSearchRoots } from '../extensions/project-local-paths.js';
13
+ import { getFortemiCoreIndexDir } from './fortemi-core-sync.js';
14
+ function json(file) {
15
+ try {
16
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
17
+ }
18
+ catch (error) {
19
+ if (error.code === 'ENOENT')
20
+ return undefined;
21
+ throw error;
22
+ }
23
+ }
24
+ function canonical(root) {
25
+ try {
26
+ return fs.realpathSync(root);
27
+ }
28
+ catch {
29
+ return path.resolve(root);
30
+ }
31
+ }
32
+ function hashFiles(files, context) {
33
+ const hash = createHash('sha256').update(JSON.stringify(context));
34
+ for (const file of [...new Set(files)].sort()) {
35
+ hash.update('\0').update(file).update('\0').update(fs.readFileSync(file));
36
+ }
37
+ return hash.digest('hex');
38
+ }
39
+ /** Same authorized source set as the builder, including links and bundle roots. */
40
+ export async function graphIndexDigest(cwd, graph, observation = {}) {
41
+ const fileStats = observation.fileStats ?? new Map();
42
+ const files = observation.files ? [...observation.files]
43
+ : graph === 'source' ? (await import('./source-graph.js')).collectSourceFiles(cwd, true)
44
+ : await collectGraphIndexFiles(cwd, graph, { selection: observation.selection, fileStats });
45
+ const ignoreRoots = graph && GRAPH_CONFIGS[graph]?.shared && graph !== 'framework' ? [] : [cwd];
46
+ if (graph)
47
+ ignoreRoots.push(...GRAPH_CONFIGS[graph].scanDirs.map(dir => resolveGraphScanDir(cwd, dir)));
48
+ for (const root of ignoreRoots) {
49
+ const ignoreFile = path.join(root, '.gitignore');
50
+ if (fs.existsSync(ignoreFile))
51
+ files.push(ignoreFile);
52
+ }
53
+ if (graph === 'source')
54
+ for (const file of ['tsconfig.json', 'package.json']) {
55
+ if (fs.existsSync(path.join(cwd, file)))
56
+ files.push(path.join(cwd, file));
57
+ }
58
+ // Supplements affect graph entries even though they are outside scanDirs.
59
+ for (const supplement of (graph ? GRAPH_CONFIGS[graph]?.metadataSupplements : undefined) ?? []) {
60
+ files.push(...findArtifactFiles(resolveGraphScanDir(cwd, supplement.scanDir)));
61
+ }
62
+ const inputs = [...new Set(files)].sort().map(file => {
63
+ const stat = fileStats.get(file) ?? fs.statSync(file);
64
+ return [file, stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs];
65
+ });
66
+ return createHash('sha256').update(JSON.stringify([graph && GRAPH_CONFIGS[graph]?.shared ? null : canonical(cwd), INDEX_VERSION, INDEX_EXTRACTOR_VERSION,
67
+ graph ? GRAPH_CONFIGS[graph] : null, resolveGraphBackendType(graph), inputs])).digest('hex');
68
+ }
69
+ export function resolveFreshnessPolicy(cwd, adapter) {
70
+ const config = json(projectControlPath(cwd, 'aiwg.config'))?.index;
71
+ const errors = validateIndexConfig(config && { freshness: config.freshness });
72
+ if (errors.length)
73
+ throw new Error(errors.join('; '));
74
+ const defaults = {
75
+ graphs: 3_600_000, sessions: 900_000, caches: 86_400_000, runtime: 21_600_000,
76
+ models: 86_400_000, issues: 300_000, datasets: 86_400_000,
77
+ };
78
+ return { index: adapter.name, targetStaleAfterMs: defaults[adapter.group], maxRetriesPerDay: 3,
79
+ measureConvergence: true, ...config?.freshness?.[adapter.group], ...config?.freshness?.[adapter.name] };
80
+ }
81
+ /** Registry is bounded by graph registrations and existing owner scopes. No host discovery. */
82
+ export function indexAdapterRegistry(cwd) {
83
+ const adapters = [];
84
+ const add = (definition) => {
85
+ const adapter = { ...definition,
86
+ probe: (workspace, nowMs) => probeIndexAdapter(adapter, workspace, nowMs),
87
+ refresh: async (opts) => {
88
+ const { refreshIndexes } = await import('./index-refresh.js');
89
+ return refreshIndexes({ cwd: opts.cwd, only: adapter.name, full: opts.full,
90
+ dryRun: opts.dryRun, signal: opts.signal });
91
+ },
92
+ convergence: last => {
93
+ const jobs = last.jobs.filter(job => definition.group === 'graphs' ? job.kind === 'graph' && job.graph === definition.graph
94
+ : definition.group === 'caches' ? job.kind === 'cache' && job.graph === definition.graph : job.kind === definition.name);
95
+ return { converged: jobs.length > 0 && jobs.every(job => job.freshness?.status === 'fresh'
96
+ && job.freshness.convergence?.converged !== false), wallMs: last.totals.wallMs,
97
+ residualJobs: jobs.filter(job => job.freshness?.status !== 'fresh').length };
98
+ },
99
+ };
100
+ adapters.push(adapter);
101
+ };
102
+ for (const [graph, config] of Object.entries(GRAPH_CONFIGS)) {
103
+ const directory = getGraphIndexDir(cwd, graph);
104
+ const outputs = ['metadata.json', 'dependencies.json', 'tags.json', 'stats.json'];
105
+ if (resolveGraphBackendType(graph) === 'sqlite')
106
+ outputs.push('graph.db');
107
+ if (graph === 'framework')
108
+ outputs.push('relations.json');
109
+ add({ name: graph, graph, group: 'graphs', identity: canonical(directory),
110
+ backend: resolveGraphBackendType(graph), operation: 'buildIndex', supported: true,
111
+ exclusions: [...INDEX_SCRATCH_DIRS, 'symlinks', 'gitignored', 'hidden directories'],
112
+ roots: workspace => [...new Set([
113
+ ...config.scanDirs.map(dir => canonical(resolveGraphScanDir(graph === 'framework' ? resolveFrameworkIndexRoot(workspace) : workspace, dir))),
114
+ ...(graph === 'project' ? [canonical(workspace), ...resolveProjectLocalSearchRoots(workspace).map(root => canonical(root.rootPath))] : []),
115
+ ])],
116
+ outputPaths: outputs.map(file => path.join(directory, file)), statePath: path.join(directory, 'freshness.json') });
117
+ if (graph === 'framework') {
118
+ const owner = adapters[adapters.length - 1];
119
+ add({ ...owner, name: 'relationships', operation: 'buildIndex(framework):relations',
120
+ reason: 'Relationship projection shares the framework snapshot and owner lock' });
121
+ }
122
+ const cacheDirectory = getFortemiCoreIndexDir(cwd, graph);
123
+ if (fs.existsSync(cacheDirectory))
124
+ add({ name: `caches:${graph}`, graph, group: 'caches',
125
+ identity: canonical(cacheDirectory), backend: 'fortemi-core', operation: 'syncFortemiCoreIndex',
126
+ supported: true, exclusions: ['remote mutation'], roots: () => [canonical(directory)],
127
+ outputPaths: ['manifest.json', 'aiwg-fortemi-index-v2.json'].map(file => path.join(cacheDirectory, file)),
128
+ statePath: path.join(cacheDirectory, 'freshness.json') });
129
+ }
130
+ const scopes = [
131
+ { name: 'sessions', directory: path.join(cwd, '.aiwg/sessions'), backend: 'sqlite',
132
+ operation: 'refreshSessionCatalog', outputs: ['catalog.sqlite'] },
133
+ { name: 'runtime', directory: projectAiwgPath(cwd, 'smiths/toolsmith'), backend: 'json',
134
+ operation: 'RuntimeDiscovery', outputs: ['runtime.json', 'runtime-info.md'] },
135
+ { name: 'issues', directory: projectAiwgPath(cwd, 'issues'), backend: 'json',
136
+ operation: 'LocalIssueProviderCore.rebuildIssueIndex', outputs: ['index/issues.index.json'] },
137
+ ];
138
+ for (const scope of scopes) {
139
+ // Absent opt-in scopes are visible as unavailable; planning still respects opt-in.
140
+ add({ name: scope.name, group: scope.name, identity: canonical(scope.directory), backend: scope.backend,
141
+ operation: scope.operation, supported: true, exclusions: ['undeclared provider roots', 'auth and token files'],
142
+ roots: workspace => {
143
+ if (scope.name !== 'sessions')
144
+ return [canonical(scope.directory)];
145
+ let config;
146
+ try {
147
+ config = json(projectControlPath(workspace, 'aiwg.config'));
148
+ }
149
+ catch {
150
+ return [];
151
+ }
152
+ const manifests = [path.join(workspace, '.aiwg/sessions/discovery-manifest.json'),
153
+ ...(config?.sessions?.discovery_manifests ?? []).map(file => path.resolve(workspace, file))];
154
+ return [...new Set(manifests.flatMap(file => {
155
+ try {
156
+ const manifest = json(file);
157
+ return Object.values(manifest?.authorizedRoots ?? {}).flat().map(canonical);
158
+ }
159
+ catch {
160
+ return [];
161
+ }
162
+ }))];
163
+ }, outputPaths: scope.outputs.map(file => path.join(scope.directory, file)),
164
+ statePath: path.join(scope.directory, '.index', 'freshness.json') });
165
+ }
166
+ for (const name of ['models', 'datasets'])
167
+ add({ name, group: name,
168
+ identity: `${name}:${canonical(cwd)}`, backend: 'remote', operation: name === 'models'
169
+ ? 'resolveDynamicModelCatalog' : 'dataset owner', supported: false,
170
+ reason: `${name} has no local deterministic freshness probe; use its owning operation`,
171
+ roots: () => [], outputPaths: [], exclusions: ['remote-only owners'],
172
+ statePath: projectAiwgPath(cwd, '.index', `${name}-freshness.json`) });
173
+ return adapters;
174
+ }
175
+ export function selectIndexAdapters(adapters, only = 'all') {
176
+ if (only === 'all')
177
+ return adapters;
178
+ const selected = adapters.filter(adapter => adapter.name === only || adapter.group === only);
179
+ if (!selected.length && !['sessions', 'issues', 'caches'].includes(only))
180
+ throw new Error(`Unknown index: ${only}`);
181
+ return selected;
182
+ }
183
+ async function adapterDigest(adapter, cwd) {
184
+ if (adapter.group === 'graphs')
185
+ return graphIndexDigest(adapter.graph === 'framework' ? resolveFrameworkIndexRoot(cwd) : cwd, adapter.graph);
186
+ if (adapter.group === 'caches')
187
+ return hashFiles(['metadata.json', 'dependencies.json'].map(file => path.join(getGraphIndexDir(cwd, adapter.graph), file)), adapter.identity);
188
+ if (adapter.group === 'issues')
189
+ return hashFiles(['items', 'events', 'comments'].flatMap(dir => findArtifactFiles(projectAiwgPath(cwd, 'issues', dir), ['.md', '.json', '.jsonl'])), adapter.identity);
190
+ if (adapter.group === 'sessions') {
191
+ const { readDiscoveryManifest, discoverWorkspaceHistories } = await import('../sessions/workspace-discovery.js');
192
+ const config = json(projectControlPath(cwd, 'aiwg.config'));
193
+ const manifests = [path.join(cwd, '.aiwg/sessions/discovery-manifest.json'),
194
+ ...(config?.sessions?.discovery_manifests ?? []).map(file => path.resolve(cwd, file))];
195
+ const inputs = [];
196
+ for (const file of [...new Set(manifests)])
197
+ if (fs.existsSync(file)) {
198
+ const previous = await readDiscoveryManifest(file);
199
+ if (previous.workspaceId !== canonical(cwd))
200
+ throw new Error('discovery manifest workspace mismatch');
201
+ const authorizedRoots = previous.authorizedRoots ?? {};
202
+ if (!previous.authorizedRoots)
203
+ for (const source of previous.sources) {
204
+ const roots = authorizedRoots[source.provider] ??= [];
205
+ if (!roots.includes(source.authorizedRoot))
206
+ roots.push(source.authorizedRoot);
207
+ }
208
+ const current = await discoverWorkspaceHistories({ workspace: cwd,
209
+ refreshFrom: { ...previous, authorizedRoots }, knownSources: previous.sources,
210
+ exclude: config?.sessions?.import?.exclude });
211
+ inputs.push(current.manifestId);
212
+ }
213
+ if (!inputs.length)
214
+ throw new Error('no authorized discovery manifest');
215
+ return createHash('sha256').update(JSON.stringify([adapter.identity, inputs])).digest('hex');
216
+ }
217
+ // RuntimeDiscovery owns environmental discovery; its input receipt bounds this probe.
218
+ const runtime = json(adapter.outputPaths[0]);
219
+ if (!runtime?.inputFingerprint)
220
+ throw new Error('runtime input fingerprint unavailable');
221
+ return hashFiles([projectControlPath(cwd, 'aiwg.config')].filter(file => fs.existsSync(file)), runtime.inputFingerprint);
222
+ }
223
+ function outputDigest(adapter) {
224
+ if (!adapter.outputPaths.every(file => fs.existsSync(file)))
225
+ return undefined;
226
+ // The owner may renew stats on an unchanged build. Snapshot identity follows primary output.
227
+ const stat = fs.statSync(adapter.outputPaths[0], { bigint: true });
228
+ // Rename changes ctime during publication; inode, mtime and size survive it.
229
+ return createHash('sha256').update(JSON.stringify([adapter.identity, String(stat.dev), String(stat.ino),
230
+ String(stat.size), String(stat.mtimeNs)])).digest('hex');
231
+ }
232
+ /** Synchronous persisted view for existing reader callers; CLI/daemon also probe inputs. */
233
+ export function indexFreshnessSnapshot(adapter, cwd, nowMs = Date.now(), digest) {
234
+ const fallback = { index: adapter.name, targetStaleAfterMs: 3_600_000,
235
+ maxRetriesPerDay: 3, measureConvergence: true };
236
+ let policy = fallback;
237
+ try {
238
+ policy = resolveFreshnessPolicy(cwd, adapter);
239
+ if (!adapter.supported)
240
+ return { name: adapter.name, status: 'unsupported', policy,
241
+ lastVerifiedDigest: null, lastVerifiedAt: null, dirtySince: null, dirtyAgeMs: null,
242
+ pendingWork: 0, lastError: null, reason: adapter.reason };
243
+ const state = json(adapter.statePath);
244
+ const outputs = outputDigest(adapter);
245
+ const age = state?.lastVerifiedAt ? Math.max(0, nowMs - Date.parse(state.lastVerifiedAt)) : Infinity;
246
+ const changed = !state || !outputs || outputs !== state.outputDigest || (digest !== undefined && state.lastVerifiedDigest !== digest);
247
+ const dirtySince = state?.dirtySince ?? (changed ? new Date(nowMs).toISOString()
248
+ : age > policy.targetStaleAfterMs ? new Date(Date.parse(state.lastVerifiedAt) + policy.targetStaleAfterMs).toISOString() : null);
249
+ return { name: adapter.name, policy,
250
+ status: !outputs ? 'unavailable' : state?.lastError ? 'degraded'
251
+ : changed || state?.pendingWork || age > policy.targetStaleAfterMs ? 'stale' : 'fresh',
252
+ lastVerifiedDigest: state?.lastVerifiedDigest ?? null, lastVerifiedAt: state?.lastVerifiedAt ?? null,
253
+ dirtySince, dirtyAgeMs: dirtySince ? Math.max(0, nowMs - Date.parse(dirtySince)) : null,
254
+ pendingWork: changed ? Math.max(1, state?.pendingWork ?? 0) : state?.pendingWork ?? 0,
255
+ lastError: state?.lastError ?? null, convergence: policy.measureConvergence && state?.convergence
256
+ ? { ...state.convergence, converged: state.convergence.residualJobs === 0
257
+ && state.convergence.wallMs <= policy.targetStaleAfterMs } : undefined };
258
+ }
259
+ catch (error) {
260
+ return { name: adapter.name, status: 'degraded', policy, lastVerifiedDigest: null, lastVerifiedAt: null,
261
+ dirtySince: null, dirtyAgeMs: null, pendingWork: 1, lastError: String(error) };
262
+ }
263
+ }
264
+ export async function probeIndexAdapter(adapter, cwd, nowMs = Date.now()) {
265
+ const persisted = indexFreshnessSnapshot(adapter, cwd, nowMs);
266
+ if (!adapter.supported || persisted.status === 'unavailable')
267
+ return persisted;
268
+ try {
269
+ if (adapter.group === 'sessions') {
270
+ const { sessionRefreshAvailable } = await import('../sessions/refresh.js');
271
+ if (!sessionRefreshAvailable())
272
+ return { ...persisted, status: 'unavailable', pendingWork: 1,
273
+ reason: 'sqlite unavailable' };
274
+ }
275
+ if (adapter.group === 'caches') {
276
+ const { getFortemiCoreSyncStatus } = await import('./fortemi-core-sync.js');
277
+ const cache = getFortemiCoreSyncStatus(cwd, adapter.graph);
278
+ if (cache.stale && !cache.sourceIndexNewer)
279
+ return { ...persisted, status: 'degraded',
280
+ pendingWork: 1, lastError: cache.reason };
281
+ }
282
+ const digest = await adapterDigest(adapter, cwd);
283
+ let snapshot = indexFreshnessSnapshot(adapter, cwd, nowMs, digest);
284
+ // Preserve the first measured dirty observation without racing a publisher.
285
+ // Probe never builds or creates an absent index.
286
+ if (fs.existsSync(adapter.statePath)
287
+ && snapshot.status === 'stale' && snapshot.lastVerifiedDigest !== digest) {
288
+ let release;
289
+ try {
290
+ release = await acquireDirectoryLock(adapter.group === 'graphs'
291
+ ? `${path.dirname(adapter.statePath)}.build.lock` : path.join(getUserStateDir(), 'index-refresh.lock'), { timeoutMs: 0 });
292
+ const state = json(adapter.statePath);
293
+ if (state && !state.dirtySince && state.lastVerifiedDigest !== digest) {
294
+ writeIndexFile(cwd, path.basename(adapter.statePath), { ...state,
295
+ dirtySince: new Date(nowMs).toISOString(), pendingWork: 1 }, path.dirname(adapter.statePath));
296
+ }
297
+ snapshot = indexFreshnessSnapshot(adapter, cwd, nowMs, digest);
298
+ }
299
+ catch (error) {
300
+ if (!(error instanceof DirectoryLockContentionError))
301
+ throw error;
302
+ }
303
+ finally {
304
+ await release?.();
305
+ }
306
+ }
307
+ return snapshot;
308
+ }
309
+ catch (error) {
310
+ return { ...indexFreshnessSnapshot(adapter, cwd, nowMs), status: 'degraded',
311
+ pendingWork: 1, lastError: error instanceof Error ? error.message : String(error) };
312
+ }
313
+ }
314
+ export async function indexAdapterDigest(adapter, cwd) {
315
+ if (!adapter.supported)
316
+ return undefined;
317
+ return adapterDigest(adapter, cwd);
318
+ }
319
+ /** Call under the existing owner lock; failure never advances verification. */
320
+ export function recordIndexVerification(adapter, cwd, opts, publicationDir) {
321
+ const now = opts.nowMs ?? Date.now();
322
+ let prior;
323
+ try {
324
+ prior = json(adapter.statePath);
325
+ }
326
+ catch { /* Replace corrupt receipt, never the snapshot. */ }
327
+ const complete = !opts.error && !!opts.before && opts.before === opts.after && !opts.pendingWork;
328
+ const policy = resolveFreshnessPolicy(cwd, adapter);
329
+ const state = { lastVerifiedDigest: complete ? opts.after : prior?.lastVerifiedDigest ?? null,
330
+ lastVerifiedAt: complete ? new Date(now).toISOString() : prior?.lastVerifiedAt ?? null,
331
+ dirtySince: complete ? null : prior?.dirtySince ?? new Date(now).toISOString(),
332
+ pendingWork: complete ? 0 : Math.max(1, opts.pendingWork ?? 0), lastError: opts.error ?? null,
333
+ outputDigest: complete ? outputDigest(publicationDir ? { ...adapter, outputPaths: adapter.outputPaths.map(file => fs.existsSync(path.join(publicationDir, path.basename(file))) ? path.join(publicationDir, path.basename(file)) : file) } : adapter) : prior?.outputDigest,
334
+ ...(policy.measureConvergence ? { convergence: { converged: complete && opts.wallMs <= policy.targetStaleAfterMs,
335
+ wallMs: opts.wallMs, residualJobs: complete ? 0 : Math.max(1, opts.pendingWork ?? 0) } } : {}) };
336
+ writeIndexFile(cwd, path.basename(adapter.statePath), state, publicationDir ?? path.dirname(adapter.statePath));
337
+ return indexFreshnessSnapshot(adapter, cwd, now, opts.after);
338
+ }
339
+ //# sourceMappingURL=index-adapters.js.map