@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
@@ -18,7 +18,7 @@ import { parseDecisionJson, parseDecisionYaml } from '../decision/entry.js';
18
18
  import { validateGateDocument } from '../gates/schema.js';
19
19
  import { INDEX_EXTRACTOR_VERSION, INDEX_VERSION, INDEX_DIR, OPERATIONAL_DISCOVERY_TYPES, PHASE_DIRECTORIES, GRAPH_CONFIGS, TEMPLATE_INDEX_EXTENSIONS, resolveGraphScanDir, loadUserGraphConfigs, loadGlobalGraphConfigs, resolveGraphBackendType, } from './types.js';
20
20
  import { parseCitationSidecar, citationResultToEdges, buildRefToPathMap } from './citation-parser.js';
21
- import { writeIndexFile, resolveIndexDir, loadGraphIndexFile } from './index-reader.js';
21
+ import { writeIndexFile, resolveIndexDir, loadGraphIndexFile, publishIndexFiles } from './index-reader.js';
22
22
  import { loadManifest, writeManifest, statMatches, makeEntry } from './checksum-manifest.js';
23
23
  import { FRAMEWORK_RELATIONS_REVISION } from './framework-source-digest.js';
24
24
  import { normalizeOperationalState } from './operational-state.js';
@@ -26,6 +26,9 @@ import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/proj
26
26
  import { normalizeStateTransferProjection } from './state-transfer.js';
27
27
  import { resolveIndexWorkers, runIndexPreparation } from './index-preparation.js';
28
28
  import { collectGraphIndexFiles, findArtifactFiles, indexPathFor } from './index-files.js';
29
+ import { acquireDirectoryLock } from './prebuilt-build-lock.js';
30
+ import { graphIndexDigest, indexAdapterRegistry, recordIndexVerification } from './index-adapters.js';
31
+ import { loadMemoryRecord } from '../memory/maintenance-record.js';
29
32
  function absoluteEntryPath(cwd, entryPath, graph) {
30
33
  if ((!graph || graph === 'project') && entryPath.startsWith(`${DEFAULT_PROJECT_AIWG_DIR}/`)) {
31
34
  return path.join(resolveProjectAiwgDir(cwd), entryPath.slice(DEFAULT_PROJECT_AIWG_DIR.length + 1));
@@ -369,6 +372,9 @@ function inferType(data, filePath) {
369
372
  // Rules/RULES-INDEX.md is a curated index file, not a rule.
370
373
  if (basename === 'rules-index' || basename === 'index')
371
374
  break;
375
+ // <rule>.core.md is a delivery body (#3450), not a separate rule.
376
+ if (/\.core\.md$/i.test(filePath))
377
+ break;
372
378
  if (isMarkdown)
373
379
  return 'rule';
374
380
  break;
@@ -836,27 +842,73 @@ function resolveInstallRootHint() {
836
842
  catch { /* fall through */ }
837
843
  return '<aiwg install root>';
838
844
  }
845
+ /** Current source identities for rule bindings; no search index or graph is written. */
846
+ export async function collectRuleBindingEntries(cwd, scope) {
847
+ const fileStats = new Map();
848
+ const files = await collectGraphIndexFiles(cwd, undefined, { scope, fileStats });
849
+ return files.flatMap(fullPath => {
850
+ const stat = fileStats.get(fullPath) ?? fs.statSync(fullPath);
851
+ const entry = extractIndexArtifact({ fullPath, relativePath: indexPathFor(cwd, fullPath),
852
+ mtimeMs: stat.mtimeMs, birthtimeMs: stat.birthtimeMs, sizeBytes: stat.size }, true).entry;
853
+ return entry && ['skill', 'agent', 'flow'].includes(entry.type) ? [entry] : [];
854
+ });
855
+ }
839
856
  /** Shared serial/worker read, checksum and extraction. No index output or global reduction. */
840
857
  export function prepareIndexArtifact(job) {
858
+ const warnings = [];
859
+ const result = extractIndexArtifact(job, false, warnings);
860
+ return { ...result, ...(warnings.length ? { warnings } : {}) };
861
+ }
862
+ function extractIndexArtifact(job, bindingOnly, warnings = []) {
841
863
  const { fullPath, relativePath } = job;
842
864
  const content = fs.readFileSync(fullPath, 'utf8');
843
865
  const checksum = computeChecksum(content);
844
866
  if (job.existingChecksum === checksum)
845
867
  return { checksum };
846
868
  const { data, body } = parseFrontmatter(content);
869
+ let memoryRecord;
870
+ if (data.memoryRecord !== undefined) {
871
+ try {
872
+ memoryRecord = loadMemoryRecord(data.memoryRecord);
873
+ }
874
+ catch (error) {
875
+ warnings.push(`${relativePath}: invalid memoryRecord metadata quarantined: ${error instanceof Error ? error.message : String(error)}`);
876
+ }
877
+ }
878
+ const inferredType = inferType(data, relativePath);
879
+ const bindingType = ['skill', 'agent', 'flow'].includes(inferredType);
880
+ // Only YAML flows can promote an otherwise unrelated document to a binding target.
881
+ if (bindingOnly && !bindingType && !/\.ya?ml$/i.test(relativePath))
882
+ return { checksum };
847
883
  // YAML Flow documents (workflow.aiwg.io/v1) are pure YAML, not
848
884
  // markdown+frontmatter — detect them up front so they classify and
849
885
  // become discoverable (#1540).
850
886
  const flow = parseFlowDoc(content, relativePath);
887
+ if (bindingOnly && !flow && !bindingType)
888
+ return { checksum };
851
889
  const decision = flow ? null : parseDecisionDoc(content, relativePath);
852
890
  const gatePack = flow || decision ? null : parseGatePackDoc(content, relativePath);
853
- const inferredType = inferType(data, relativePath);
891
+ if (bindingOnly && (decision || gatePack))
892
+ return { checksum };
854
893
  const physicalType = inferType({ ...data, type: undefined }, relativePath);
855
894
  const runbook = flow || decision || gatePack ? null : parseRunbookDoc(data, body, relativePath);
856
895
  const schemaDoc = inferredType === 'schema' ? parseSchemaDoc(content, relativePath) : null;
857
896
  const title = flow?.name ?? decision?.name ?? gatePack?.name ?? schemaDoc?.title ?? extractTitle(data, body);
858
897
  const phase = typeof data.phase === 'string' ? data.phase : inferPhase(relativePath);
859
898
  const type = flow?.type ?? decision?.type ?? gatePack?.type ?? (runbook ? 'runbook' : inferredType);
899
+ // Binding resolution consumes only identity and title. Keep the same canonical
900
+ // classification (including declarative flows and runbooks), without extracting
901
+ // search terms, mentions, links, or constructing a persistent graph.
902
+ if (bindingOnly)
903
+ return { checksum, entry: {
904
+ path: relativePath, type, title, phase,
905
+ ...(memoryRecord ? { memoryRecord } : {}),
906
+ name: flow?.name ?? decision?.name ?? gatePack?.name ?? schemaDoc?.name
907
+ ?? (OPERATIONAL_DISCOVERY_TYPES.includes(type)
908
+ ? extractCanonicalName(data, relativePath) : undefined),
909
+ tags: [], dependencies: [], dependents: [], checksum,
910
+ created: '', updated: '', summary: '',
911
+ } };
860
912
  const tags = flow?.tags ?? decision?.tags ?? gatePack?.tags ?? (Array.isArray(data.tags) ? data.tags.map(String) : []);
861
913
  const summary = flow?.description ?? decision?.description ?? gatePack?.description ?? schemaDoc?.capability ?? runbook?.capability ?? extractSummary(data, body);
862
914
  const dependencies = extractMentions(content);
@@ -882,6 +934,8 @@ export function prepareIndexArtifact(job) {
882
934
  // remain searchable even when the rendered title strips the hyphen.
883
935
  const name = flow?.name ?? decision?.name ?? gatePack?.name ?? schemaDoc?.name ?? (isDiscoverable ? extractCanonicalName(data, relativePath) : undefined);
884
936
  const entry = {
937
+ ...(memoryRecord ? { memoryRecord } : {}),
938
+ ...(typeof data.memoryCategory === 'string' ? { memoryCategory: data.memoryCategory } : {}),
885
939
  path: relativePath,
886
940
  type,
887
941
  ...(kind ? { kind } : {}),
@@ -910,6 +964,67 @@ export function prepareIndexArtifact(job) {
910
964
  return { checksum, entry };
911
965
  }
912
966
  export async function buildIndex(cwd, options = {}) {
967
+ loadUserGraphConfigs(cwd);
968
+ loadGlobalGraphConfigs();
969
+ const scanDirs = options.scope ? [path.join(cwd, options.scope)] : options.graph
970
+ ? GRAPH_CONFIGS[options.graph]?.scanDirs.map(dir => resolveGraphScanDir(cwd, dir)) ?? []
971
+ : [resolveProjectAiwgDir(cwd)];
972
+ if (!scanDirs.some(dir => fs.existsSync(dir))) {
973
+ // Keep the owner's missing-root diagnostic/exit path before creating output directories.
974
+ await buildIndexSnapshot(cwd, options, () => '');
975
+ return;
976
+ }
977
+ const directory = options.outputDir
978
+ ? path.join(options.outputDir, options.graph ? `.aiwg/.index/${options.graph}` : INDEX_DIR)
979
+ : resolveIndexDir(cwd, options.graph);
980
+ // Manual and background builds acquire the same per-output owner lock.
981
+ const release = await acquireDirectoryLock(`${directory}.build.lock`, {
982
+ timeoutMs: options.lockWaitMs ?? 180_000, reclaimDeadOwner: true,
983
+ });
984
+ let staging;
985
+ const start = Date.now();
986
+ let owned;
987
+ try {
988
+ const adapter = !options.scope && !options.includeIgnored
989
+ ? indexAdapterRegistry(cwd).find(item => item.name === (options.graph ?? 'project')) : undefined;
990
+ owned = adapter && directory === path.dirname(adapter.statePath) ? adapter : undefined;
991
+ const observation = { selection: {} };
992
+ const preparePublication = () => {
993
+ fs.mkdirSync(directory, { recursive: true });
994
+ return staging ??= fs.mkdtempSync(path.join(directory, '.build-'));
995
+ };
996
+ await buildIndexSnapshot(cwd, options, preparePublication, owned ? observation : undefined);
997
+ // A receipt hit verifies the existing snapshot directly against the scan's
998
+ // stats. There is no extraction interval to recheck. Real builds still probe
999
+ // again after preparation so concurrent source changes remain pending.
1000
+ const after = owned ? observation.unchanged ? observation.before
1001
+ : await graphIndexDigest(cwd, options.graph, { selection: observation.selection }) : undefined;
1002
+ // All extraction and backend work has succeeded before any live file moves.
1003
+ if (owned)
1004
+ recordIndexVerification(owned, cwd, { before: observation.before, after, wallMs: Date.now() - start }, staging);
1005
+ if (staging)
1006
+ publishIndexFiles(staging, directory);
1007
+ }
1008
+ catch (error) {
1009
+ if (owned) {
1010
+ try {
1011
+ recordIndexVerification(owned, cwd, { error: error instanceof Error ? error.message : String(error), wallMs: Date.now() - start });
1012
+ }
1013
+ catch { /* Preserve the original build failure if recording is unavailable. */ }
1014
+ }
1015
+ throw error;
1016
+ }
1017
+ finally {
1018
+ try {
1019
+ if (staging)
1020
+ fs.rmSync(staging, { recursive: true, force: true });
1021
+ }
1022
+ finally {
1023
+ await release();
1024
+ }
1025
+ }
1026
+ }
1027
+ async function buildIndexSnapshot(cwd, options, preparePublication, observation) {
913
1028
  const { force = false, verbose = false, scope, includeIgnored = false, outputDir, graph, explicit = true } = options;
914
1029
  const workers = resolveIndexWorkers(options.workers);
915
1030
  const startTime = Date.now();
@@ -979,10 +1094,12 @@ export async function buildIndex(cwd, options = {}) {
979
1094
  // effectiveOutputCwd is used for backward-compat loadMetadataIndex calls
980
1095
  const effectiveOutputCwd = outputDir ?? cwd;
981
1096
  if (graph === 'source') {
1097
+ if (observation)
1098
+ observation.before = await graphIndexDigest(cwd, graph, { selection: observation.selection });
982
1099
  const { buildSourceGraphIndex } = await import('./source-graph.js');
983
1100
  await buildSourceGraphIndex({
984
1101
  cwd,
985
- outputDir: indexOutputDir,
1102
+ outputDir: preparePublication(),
986
1103
  effectiveOutputCwd,
987
1104
  verbose,
988
1105
  });
@@ -1001,7 +1118,7 @@ export async function buildIndex(cwd, options = {}) {
1001
1118
  : loadManifest(indexOutputDir);
1002
1119
  const useFilenameMetadata = graphConfig?.nodeStrategy === 'filename-metadata';
1003
1120
  const reusable = !useFilenameMetadata && !graphConfig?.metadataSupplements?.length && !graphConfig?.edgeExtraction;
1004
- const fileStats = !force && reusable ? new Map() : undefined;
1121
+ const fileStats = observation || (!force && reusable) ? new Map() : undefined;
1005
1122
  const nextManifestEntries = {};
1006
1123
  const manifestStats = {
1007
1124
  checked: 0,
@@ -1011,7 +1128,9 @@ export async function buildIndex(cwd, options = {}) {
1011
1128
  pruned: 0,
1012
1129
  };
1013
1130
  // Collect files from all scan directories
1014
- const files = await collectGraphIndexFiles(cwd, graph, { scope, includeIgnored, fileStats });
1131
+ const files = await collectGraphIndexFiles(cwd, graph, { scope, includeIgnored, fileStats, selection: observation?.selection });
1132
+ if (observation)
1133
+ observation.before = await graphIndexDigest(cwd, graph, { files, fileStats });
1015
1134
  const entries = {};
1016
1135
  const tagIndex = {};
1017
1136
  const depGraph = {};
@@ -1052,7 +1171,7 @@ export async function buildIndex(cwd, options = {}) {
1052
1171
  };
1053
1172
  const entriesDigest = (values) => createHash('sha256').update(JSON.stringify(values)).digest('hex');
1054
1173
  const writeBuildManifest = (values) => {
1055
- writeManifest(indexOutputDir, { version: 1, generated: new Date().toISOString(), entries: values,
1174
+ writeManifest(publicationDir, { version: 1, generated: new Date().toISOString(), entries: values,
1056
1175
  ...(reusable ? { receipt: {
1057
1176
  indexVersion: INDEX_VERSION, extractorVersion: INDEX_EXTRACTOR_VERSION, buildConfigHash,
1058
1177
  sourceRoot: path.resolve(cwd), metadataSignature: fileSignature('metadata.json'), entriesDigest: entriesDigest(values),
@@ -1086,11 +1205,16 @@ export async function buildIndex(cwd, options = {}) {
1086
1205
  manifestStats.checked = files.length;
1087
1206
  manifestStats.statSkipped = files.length;
1088
1207
  const buildTimeMs = Date.now() - startTime;
1208
+ // Only stats changes on a receipt hit; its existing atomic writer is
1209
+ // sufficient under the owner lock, without staging or backup copies.
1089
1210
  writeIndexFile(effectiveOutputCwd, 'stats.json', { ...stats, builtAt: new Date().toISOString(), buildTimeMs }, indexOutputDir);
1211
+ if (observation)
1212
+ observation.unchanged = true;
1090
1213
  reportBuild(buildTimeMs, files.length);
1091
1214
  return;
1092
1215
  }
1093
1216
  }
1217
+ const publicationDir = preparePublication();
1094
1218
  const existingIndex = force ? null : outputDir
1095
1219
  ? loadIndexFromDir(indexOutputDir, 'metadata.json')
1096
1220
  : loadGraphIndexFile(effectiveOutputCwd, 'metadata.json', graph);
@@ -1121,6 +1245,10 @@ export async function buildIndex(cwd, options = {}) {
1121
1245
  ...(!force && existingEntries[relativePath] ? { existingChecksum: existingEntries[relativePath].checksum } : {}) });
1122
1246
  }
1123
1247
  const preparation = await runIndexPreparation(jobs, prepareIndexArtifact, import.meta.url, workers, options.onDiagnostic ?? (message => console.warn(`Index preparation: ${message}`)));
1248
+ for (const result of preparation.results)
1249
+ for (const warning of result.warnings ?? []) {
1250
+ (options.onDiagnostic ?? (message => console.warn(`Index preparation: ${message}`)))(warning);
1251
+ }
1124
1252
  const preparedArtifacts = new Map(jobs.map((job, index) => [job.fullPath, preparation.results[index]]));
1125
1253
  for (const fullPath of files) {
1126
1254
  const relativePath = indexPathFor(cwd, fullPath, graph);
@@ -1221,14 +1349,15 @@ export async function buildIndex(cwd, options = {}) {
1221
1349
  const projectionNeedsRepair = graph === 'framework' && !relationsReceiptMatches();
1222
1350
  if (graph === 'framework') {
1223
1351
  const { writeRelations } = await import('./relations-builder.js');
1224
- await writeRelations(entries, effectiveOutputCwd, indexOutputDir, force || !relationsReceiptMatches());
1352
+ if (force || !relationsReceiptMatches())
1353
+ await writeRelations(entries, effectiveOutputCwd, publicationDir, true);
1225
1354
  }
1226
1355
  const buildTimeMs = Date.now() - startTime;
1227
1356
  writeIndexFile(effectiveOutputCwd, 'stats.json', {
1228
1357
  ...stats,
1229
1358
  builtAt: new Date().toISOString(),
1230
1359
  buildTimeMs,
1231
- }, indexOutputDir);
1360
+ }, publicationDir);
1232
1361
  // Preserve no-op manifest bytes after output-only timestamp drift.
1233
1362
  // Renew receipts only for changed source stats or repaired projections.
1234
1363
  if (manifestStats.checksumSkipped > 0 || projectionNeedsRepair)
@@ -1354,7 +1483,7 @@ export async function buildIndex(cwd, options = {}) {
1354
1483
  }
1355
1484
  if (graph === 'framework') {
1356
1485
  const { writeRelations } = await import('./relations-builder.js');
1357
- await writeRelations(entries, effectiveOutputCwd, indexOutputDir, force || !relationsReceiptMatches());
1486
+ await writeRelations(entries, effectiveOutputCwd, publicationDir, force || !relationsReceiptMatches(), indexOutputDir);
1358
1487
  }
1359
1488
  const buildTimeMs = Date.now() - startTime;
1360
1489
  // Write index files
@@ -1366,13 +1495,13 @@ export async function buildIndex(cwd, options = {}) {
1366
1495
  buildTimeMs,
1367
1496
  entries,
1368
1497
  };
1369
- writeIndexFile(effectiveOutputCwd, 'metadata.json', index, indexOutputDir);
1370
- writeIndexFile(effectiveOutputCwd, 'tags.json', tagIndex, indexOutputDir);
1371
- writeIndexFile(effectiveOutputCwd, 'dependencies.json', depGraph, indexOutputDir);
1498
+ writeIndexFile(effectiveOutputCwd, 'metadata.json', index, publicationDir);
1499
+ writeIndexFile(effectiveOutputCwd, 'tags.json', tagIndex, publicationDir);
1500
+ writeIndexFile(effectiveOutputCwd, 'dependencies.json', depGraph, publicationDir);
1372
1501
  // Materialize the configured backend and always retain dependencies.json as
1373
1502
  // the stable compatibility/export contract.
1374
1503
  const { createGraphBackend } = await import('./graph-backend.js');
1375
- const persistentPath = backendType === 'sqlite' ? path.join(indexOutputDir, 'graph.db') : undefined;
1504
+ const persistentPath = backendType === 'sqlite' ? path.join(publicationDir, 'graph.db') : undefined;
1376
1505
  let selectedBackend;
1377
1506
  try {
1378
1507
  selectedBackend = await createGraphBackend(backendType, persistentPath);
@@ -1430,7 +1559,7 @@ export async function buildIndex(cwd, options = {}) {
1430
1559
  orphanedArtifacts: orphaned,
1431
1560
  mostReferenced,
1432
1561
  },
1433
- }, indexOutputDir);
1562
+ }, publicationDir);
1434
1563
  writeBuildManifest(nextManifestEntries);
1435
1564
  // Report
1436
1565
  reportBuild(buildTimeMs, total);
@@ -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
@@ -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