arkgate 4.8.23 → 4.8.25

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 (201) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +7 -5
  3. package/bin/ark-check-runtime.mjs +41 -91
  4. package/bin/ark-dashboard.mjs +56 -53
  5. package/bin/ark-layer-match.mjs +707 -121
  6. package/bin/ark-mcp-runtime.mjs +448 -169
  7. package/bin/ark-mcp.mjs +68 -4
  8. package/bin/ark-shared.mjs +42 -22
  9. package/bin/ark.mjs +65 -97
  10. package/bin/lib/adapter-contract-types.mjs +5 -0
  11. package/bin/lib/adapter-contract.mjs +15 -0
  12. package/bin/lib/adoption-stance.mjs +2 -2
  13. package/bin/lib/adr-path.mjs +3 -3
  14. package/bin/lib/agent-gates.mjs +0 -61
  15. package/bin/lib/agent-homes.mjs +12 -21
  16. package/bin/lib/agent-projection-command.mjs +9 -5
  17. package/bin/lib/ambient-state.mjs +9 -30
  18. package/bin/lib/analysis-engine.mjs +15 -11
  19. package/bin/lib/architecture-scan.mjs +61 -15
  20. package/bin/lib/ark-order-doctor.mjs +4 -29
  21. package/bin/lib/ark-order-facts.mjs +319 -17
  22. package/bin/lib/ark-order-sensors.mjs +28 -101
  23. package/bin/lib/ark-order-types.mjs +3 -2
  24. package/bin/lib/ark-run-doctor.mjs +17 -28
  25. package/bin/lib/ark-run-facts.mjs +461 -48
  26. package/bin/lib/ark-run-sensors.mjs +6 -4
  27. package/bin/lib/arkgate-pins.mjs +91 -0
  28. package/bin/lib/arkrule-file-hints.mjs +2 -47
  29. package/bin/lib/arkrules-contract.mjs +124 -133
  30. package/bin/lib/arkrules-sensors.mjs +55 -252
  31. package/bin/lib/check-args.mjs +85 -1
  32. package/bin/lib/ci-and-commands.mjs +24 -78
  33. package/bin/lib/ci-merge-boundary.mjs +1 -1
  34. package/bin/lib/class-source-scan.mjs +479 -0
  35. package/bin/lib/clone-detection.mjs +474 -0
  36. package/bin/lib/codex-home.mjs +5 -3
  37. package/bin/lib/config-contract-slices.mjs +739 -0
  38. package/bin/lib/config-contract.mjs +42 -607
  39. package/bin/lib/config-extras.mjs +16 -22
  40. package/bin/lib/config-warnings.mjs +32 -0
  41. package/bin/lib/contract-smells.mjs +33 -15
  42. package/bin/lib/core-ratchet.mjs +0 -2
  43. package/bin/lib/dashboard-cli.mjs +62 -0
  44. package/bin/lib/deep-module-coach.mjs +2 -4
  45. package/bin/lib/deploy-path.mjs +13 -38
  46. package/bin/lib/design-delta.mjs +1 -1
  47. package/bin/lib/design-smells.mjs +2 -2
  48. package/bin/lib/diagnostic-catalog.mjs +23 -9
  49. package/bin/lib/doctor-advisories.mjs +78 -8
  50. package/bin/lib/doctor-green-cite.mjs +1 -1
  51. package/bin/lib/doctor-human.mjs +1 -1
  52. package/bin/lib/doctor-next-actions.mjs +19 -4
  53. package/bin/lib/doctor-plan.mjs +9 -13
  54. package/bin/lib/duplication-io.mjs +572 -0
  55. package/bin/lib/effective-contract-load.mjs +282 -83
  56. package/bin/lib/enforcement-profiles.mjs +40 -4
  57. package/bin/lib/entry-points-io.mjs +424 -0
  58. package/bin/lib/extra-merge-teeth.mjs +36 -0
  59. package/bin/lib/field-install.mjs +38 -14
  60. package/bin/lib/first-run-help.mjs +10 -5
  61. package/bin/lib/flat-parent-importers.mjs +139 -0
  62. package/bin/lib/flat-parent-pilot.mjs +40 -100
  63. package/bin/lib/gate-files.mjs +58 -90
  64. package/bin/lib/git-change-scope.mjs +109 -0
  65. package/bin/lib/github-enforcement.mjs +166 -29
  66. package/bin/lib/golden-pattern.mjs +1 -1
  67. package/bin/lib/hook-templates.mjs +17 -15
  68. package/bin/lib/host-support-matrix.mjs +3 -1
  69. package/bin/lib/html-report-advisories.mjs +4 -0
  70. package/bin/lib/html-report-depth.mjs +2 -2
  71. package/bin/lib/html-report.mjs +2 -2
  72. package/bin/lib/import-graph-projection.mjs +112 -0
  73. package/bin/lib/import-resolve.mjs +13 -1
  74. package/bin/lib/improvement-compass-doctor.mjs +1 -24
  75. package/bin/lib/install-migrate.mjs +50 -73
  76. package/bin/lib/invariant-coverage-io.mjs +4 -3
  77. package/bin/lib/invariant-coverage.mjs +85 -11
  78. package/bin/lib/invariant-probe-cli.mjs +347 -0
  79. package/bin/lib/invariant-probe-io.mjs +165 -0
  80. package/bin/lib/invariant-probe-runner.mjs +268 -0
  81. package/bin/lib/invariant-probe-sites.mjs +174 -0
  82. package/bin/lib/invariant-probe-workspace.mjs +183 -0
  83. package/bin/lib/invariant-probe.mjs +461 -0
  84. package/bin/lib/invariant-tests-path.mjs +16 -22
  85. package/bin/lib/literal-path-drift.mjs +4 -2
  86. package/bin/lib/managed-upgrade.mjs +2 -8
  87. package/bin/lib/mcp-adoption.mjs +13 -6
  88. package/bin/lib/mcp-hook-payload.mjs +403 -63
  89. package/bin/lib/orphan-modules-io.mjs +458 -0
  90. package/bin/lib/orphan-modules.mjs +251 -0
  91. package/bin/lib/outside-importers.mjs +197 -0
  92. package/bin/lib/package-manager.mjs +60 -2
  93. package/bin/lib/parse-health.mjs +1 -1
  94. package/bin/lib/passthrough-commands.mjs +83 -0
  95. package/bin/lib/persistence-write-hint.mjs +106 -0
  96. package/bin/lib/pin-dependent-gates.mjs +117 -0
  97. package/bin/lib/policy-delta-io.mjs +219 -14
  98. package/bin/lib/post-green-path.mjs +1 -1
  99. package/bin/lib/prepare-change.mjs +115 -7
  100. package/bin/lib/prepare-write.mjs +2 -2
  101. package/bin/lib/presets.mjs +59 -73
  102. package/bin/lib/product-copy.mjs +1 -6
  103. package/bin/lib/project-root.mjs +31 -62
  104. package/bin/lib/remediation.mjs +23 -4
  105. package/bin/lib/resolved-candidate-facts.mjs +405 -117
  106. package/bin/lib/rules-inventory.mjs +22 -3
  107. package/bin/lib/rules-under-contract.mjs +247 -9
  108. package/bin/lib/scan-files.mjs +36 -4
  109. package/bin/lib/schema-validation.mjs +130 -0
  110. package/bin/lib/sensor-promote-cli.mjs +2 -11
  111. package/bin/lib/sensor-promote-io.mjs +33 -22
  112. package/bin/lib/sensor-promotion.mjs +1 -1
  113. package/bin/lib/skill-catalog-apply.mjs +1 -1
  114. package/bin/lib/skill-install.mjs +42 -38
  115. package/bin/lib/snippet-analysis.mjs +140 -6
  116. package/bin/lib/source-policy.mjs +34 -0
  117. package/bin/lib/start-install-recovery.mjs +72 -2
  118. package/bin/lib/start-preview.mjs +31 -1
  119. package/bin/lib/states-transitions-presence.mjs +3 -3
  120. package/bin/lib/status-command.mjs +35 -22
  121. package/bin/lib/status-manifest.mjs +50 -30
  122. package/bin/lib/status-transition-catalog.mjs +0 -6
  123. package/bin/lib/suggestions.mjs +3 -3
  124. package/bin/lib/team-parliament-io.mjs +76 -88
  125. package/bin/lib/team-parliament.mjs +21 -10
  126. package/bin/lib/upgrade-behavior-changes.mjs +2 -2
  127. package/bin/lib/upgrade-command.mjs +2 -24
  128. package/bin/lib/upgrade-outcome.mjs +1 -1
  129. package/bin/lib/upstream-report.mjs +21 -10
  130. package/bin/lib/violations.mjs +10 -9
  131. package/bin/lib/weakest-link.mjs +13 -0
  132. package/bin/lib/write-path-capabilities.mjs +6 -4
  133. package/dist/chunk-A5EIY3HI.cjs +5 -0
  134. package/dist/chunk-AHACEKSV.js +16 -0
  135. package/dist/chunk-SVSQVGFJ.js +5 -0
  136. package/dist/chunk-UN5EH4QV.cjs +16 -0
  137. package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
  138. package/dist/configTypes-GPvvdleM.d.ts +257 -0
  139. package/dist/eslint/index.cjs +18 -5
  140. package/dist/eslint/index.d.cts +368 -0
  141. package/dist/eslint/index.d.ts +139 -156
  142. package/dist/eslint/index.js +11 -5
  143. package/dist/index.cjs +36 -35
  144. package/dist/index.d.cts +5547 -0
  145. package/dist/index.d.ts +3283 -198
  146. package/dist/index.js +36 -35
  147. package/dist/inspectorListen-6DQVJ4DC.js +1 -0
  148. package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
  149. package/dist/nestjs/index.cjs +1 -18
  150. package/dist/nestjs/index.d.cts +22 -0
  151. package/dist/nestjs/index.d.ts +1 -3
  152. package/dist/nestjs/index.js +1 -18
  153. package/dist/order/index.cjs +1 -1
  154. package/dist/order/index.d.cts +155 -0
  155. package/dist/order/index.d.ts +15 -3
  156. package/dist/order/index.js +1 -1
  157. package/dist/runtime/index.cjs +13 -30
  158. package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
  159. package/dist/runtime/index.d.ts +2741 -133
  160. package/dist/runtime/index.js +13 -30
  161. package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
  162. package/dist/types-CnauMQQL.d.ts +1800 -0
  163. package/docs/README.md +3 -1
  164. package/docs/agent-guide.md +217 -16
  165. package/docs/ai-gates.md +174 -26
  166. package/docs/arkorder.md +32 -15
  167. package/docs/brownfield-adoption.md +5 -1
  168. package/docs/configuration.md +230 -31
  169. package/docs/develop.md +2 -1
  170. package/docs/diagnostics.md +212 -19
  171. package/docs/package-surface.md +35 -9
  172. package/docs/product-voice.md +7 -1
  173. package/package.json +69 -19
  174. package/schemas/ark.analysis-result.schema.json +15 -0
  175. package/schemas/ark.config.schema.json +77 -12
  176. package/schemas/ark.invariant-probe.schema.json +368 -0
  177. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  178. package/schemas/ark.status-manifest.schema.json +22 -0
  179. package/server.json +3 -9
  180. package/templates/agent-skills/README.md +1 -1
  181. package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
  182. package/templates/agent-skills/ark-contract/SKILL.md +17 -0
  183. package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
  184. package/templates/agent-skills/ark-explore/SKILL.md +7 -0
  185. package/templates/agent-skills/ark-fix/SKILL.md +20 -0
  186. package/templates/agent-skills/ark-order/SKILL.md +4 -2
  187. package/templates/agent-skills/ark-place/SKILL.md +8 -0
  188. package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
  189. package/templates/arkrules/PersistenceAdapters.json +1 -1
  190. package/templates/arkrules/PresentationAdapters.json +1 -1
  191. package/templates/skills/ark-adopt.md +5 -1
  192. package/templates/skills/ark-contract.md +17 -0
  193. package/templates/skills/ark-coverage.md +2 -0
  194. package/templates/skills/ark-explore.md +7 -0
  195. package/templates/skills/ark-fix.md +20 -0
  196. package/templates/skills/ark-order.md +4 -2
  197. package/templates/skills/ark-place.md +8 -0
  198. package/templates/skills/ark-runtime.md +6 -1
  199. package/bin/lib/ark-order-error.mjs +0 -18
  200. package/bin/lib/ark-order-invariants.mjs +0 -346
  201. package/dist/types-Ik_OakcI.d.ts +0 -183
@@ -3,62 +3,24 @@
3
3
  * Implementation lives in focused modules under bin/lib/.
4
4
  */
5
5
  export {
6
- assessCodexHomeMcp,
7
- codexArkBlockHasPreferredBin,
8
- codexArkBlockNeedsRewrite,
9
- codexConfigPath,
10
- codexPrimaryTable,
11
- codexProjectMcpIsValid,
12
- codexProjectSlug,
13
- codexPromptsDir,
14
- codexSkillsDir,
15
- codexScopedTableForRoot,
16
- extractCodexArkRootFromToml,
17
- extractCodexRootFromBlock,
18
6
  isTempOrUpgradeRoot,
19
- listCodexArkServerTables,
20
- upsertCodexMcpTable,
21
7
  wireCodexMcp,
22
8
  } from './codex-home.mjs';
23
9
 
24
10
  export {
25
- PREFERRED_MCP_BIN,
26
- antigravityHooks,
27
11
  claudeSettings,
28
- codexProjectConfig,
29
- cursorHooks,
30
12
  grokHooks,
31
- grokProjectConfig,
32
- mergeAntigravityArkHook,
33
- mergeCursorArkHook,
34
- mergeOpencodeArkMcp,
35
- opencodeProjectConfig,
36
13
  } from './hook-templates.mjs';
37
14
 
38
15
  export { detectWritePathCapabilities } from './write-path-detect.mjs';
39
16
 
40
- export {
41
- ensureBaselineFlagInCheckCommand,
42
- syncBaselineIntoCheckSurfaces,
43
- pinArkgateDevDependency,
44
- IO_DIR_SEGMENTS,
45
- detectContractFalseGreenRisk,
46
- FALSE_GREEN_GAP_ID,
47
- falseGreenAdoptionGap,
48
- } from './field-install.mjs';
49
-
50
17
  export {
51
18
  readJson,
52
- readPackageJson,
53
19
  hasCheckArchitectureScript,
54
- packageScriptsHaveTypecheck,
55
- treeHasTypecheckScript,
56
20
  ensureTypecheckScript,
57
21
  compactRouterHost,
58
22
  REQUIRED_GATE_FILES,
59
- hasArkWorkflow,
60
23
  missingGates,
61
- ensureDirForFile,
62
24
  isArkAgentsContent,
63
25
  isSelfHostedLibraryAgents,
64
26
  writeTemplate,
@@ -67,39 +29,23 @@ export {
67
29
  export { loadTypeScript } from './typescript-host.mjs';
68
30
 
69
31
  export {
70
- checkArgsForRoot,
71
- packageManager,
72
32
  arkCheckCommand,
73
33
  checkArchitectureScriptSnippet,
74
- ensureCheckArchitectureScript,
75
- layerPlacementTable,
76
- loadConfigLayersForAgents,
77
34
  agentInstructions,
78
- mcpJson,
79
- codexTomlSnippet,
80
- instructionRule,
81
- cursorRule,
82
- detectNodeMajorFromWorkflows,
83
- detectCiNode,
84
35
  githubWorkflow,
85
36
  } from './ci-and-commands.mjs';
86
37
 
87
38
  export {
88
39
  normalizeToolsList,
89
40
  resolveTools,
90
- KNOWN_TOOLS,
91
41
  SKILL_TOOL_TARGETS,
92
42
  detectActiveAgentHost,
93
43
  codexConcernIsActive,
94
44
  arkPackageVersion,
95
45
  stampSkill,
96
46
  installedSkillVersion,
97
- isVersionOlder,
98
- skillContentIdentity,
99
- skillContentMatchesTemplate,
100
47
  skillTemplates,
101
48
  skillTemplateNames,
102
- skillTemplateBodies,
103
49
  detectCodexHomeGap,
104
50
  detectCodexRepoSkillGap,
105
51
  assessCodexSkillParity,
@@ -107,7 +53,6 @@ export {
107
53
  detectSkillGaps,
108
54
  skillGapsForActiveHost,
109
55
  skillGapToolLabel,
110
- SKILL_CANONICAL_TOOL,
111
56
  agentsMdSkillRefs,
112
57
  verifyHostSkillCatalog,
113
58
  printSkillAndCodexGapHints,
@@ -123,18 +68,12 @@ export {
123
68
  } from './mcp-adoption.mjs';
124
69
 
125
70
  export {
126
- detectPreCommitArk,
127
71
  detectCiEnforcement,
128
72
  classifyArkCheckFlags,
129
- detectConfigGateDrift,
130
- jobIdsThatRunArkCheck,
131
- isArkRequiredStatusCheck,
132
- reportGithubBranchProtection,
133
73
  collectWeakestLinkGaps,
134
74
  } from './weakest-link.mjs';
135
75
 
136
76
  export {
137
- buildManagedAssetCatalog,
138
77
  staleRunnerGateFiles,
139
78
  warnLockfileConflict,
140
79
  runMigrateCommands,
@@ -13,6 +13,7 @@ import {
13
13
  arkPackageVersion,
14
14
  assessSkillCatalogParity,
15
15
  detectActiveAgentHost,
16
+ homeCatalogFloorState,
16
17
  isValidSemver,
17
18
  isVersionOlder,
18
19
  skillTemplateNames,
@@ -52,43 +53,43 @@ const HOSTS = {
52
53
  },
53
54
  };
54
55
 
55
- export function agentHomeHostIds() {
56
+ function agentHomeHostIds() {
56
57
  return Object.keys(HOSTS);
57
58
  }
58
59
 
59
- export function claudeHomeDir(env = process.env, homeDir = os.homedir()) {
60
+ function claudeHomeDir(env = process.env, homeDir = os.homedir()) {
60
61
  return resolveHomeDir(HOSTS.claude, env, homeDir);
61
62
  }
62
63
 
63
- export function grokHomeDir(env = process.env, homeDir = os.homedir()) {
64
+ function grokHomeDir(env = process.env, homeDir = os.homedir()) {
64
65
  return resolveHomeDir(HOSTS.grok, env, homeDir);
65
66
  }
66
67
 
67
- export function claudeSkillsDir(env = process.env, homeDir = os.homedir()) {
68
+ function claudeSkillsDir(env = process.env, homeDir = os.homedir()) {
68
69
  return path.join(claudeHomeDir(env, homeDir), 'skills');
69
70
  }
70
71
 
71
- export function grokSkillsDir(env = process.env, homeDir = os.homedir()) {
72
+ function grokSkillsDir(env = process.env, homeDir = os.homedir()) {
72
73
  return path.join(grokHomeDir(env, homeDir), 'skills');
73
74
  }
74
75
 
75
- export function antigravityHomeDir(env = process.env, homeDir = os.homedir()) {
76
+ function antigravityHomeDir(env = process.env, homeDir = os.homedir()) {
76
77
  return resolveHomeDir(HOSTS.antigravity, env, homeDir);
77
78
  }
78
79
 
79
- export function antigravitySkillsDir(env = process.env, homeDir = os.homedir()) {
80
+ function antigravitySkillsDir(env = process.env, homeDir = os.homedir()) {
80
81
  return path.join(antigravityHomeDir(env, homeDir), 'skills');
81
82
  }
82
83
 
83
- export function usesDefaultAntigravityHome(env = process.env, homeDir = os.homedir()) {
84
+ function usesDefaultAntigravityHome(env = process.env, homeDir = os.homedir()) {
84
85
  return usesDefaultHome(HOSTS.antigravity, env, homeDir);
85
86
  }
86
87
 
87
- export function usesDefaultClaudeHome(env = process.env, homeDir = os.homedir()) {
88
+ function usesDefaultClaudeHome(env = process.env, homeDir = os.homedir()) {
88
89
  return usesDefaultHome(HOSTS.claude, env, homeDir);
89
90
  }
90
91
 
91
- export function usesDefaultGrokHome(env = process.env, homeDir = os.homedir()) {
92
+ function usesDefaultGrokHome(env = process.env, homeDir = os.homedir()) {
92
93
  return usesDefaultHome(HOSTS.grok, env, homeDir);
93
94
  }
94
95
 
@@ -129,17 +130,7 @@ function readHomeCatalogFloor(skillsDir) {
129
130
  };
130
131
  const catalog = readOne(path.join(skillsDir, HOME_SKILL_CATALOG));
131
132
  const pending = readOne(path.join(skillsDir, HOME_SKILL_PENDING_CATALOG));
132
- let floorVersion = catalog.version;
133
- if (pending.version && (!floorVersion || isVersionOlder(floorVersion, pending.version))) {
134
- floorVersion = pending.version;
135
- }
136
- return {
137
- floorVersion,
138
- pendingVersion: pending.version,
139
- hasMetadata: catalog.exists || pending.exists,
140
- metadataInvalid:
141
- (catalog.exists && !catalog.valid) || (pending.exists && !pending.valid),
142
- };
133
+ return homeCatalogFloorState(catalog, pending);
143
134
  }
144
135
 
145
136
  function homeInPlay(parity, catalogState) {
@@ -43,7 +43,7 @@ function packageVersionFallback() {
43
43
  * Resolve catalog short-list entries (ruleId + title) from the public catalog.
44
44
  * @param {readonly string[]|null|undefined} [ruleIds]
45
45
  */
46
- export function resolveProjectionCatalogShortList(ruleIds) {
46
+ function resolveProjectionCatalogShortList(ruleIds) {
47
47
  const ids =
48
48
  Array.isArray(ruleIds) && ruleIds.length > 0 ? ruleIds : DEFAULT_AGENT_PROJECTION_RULE_IDS;
49
49
  return ids.map((ruleId) => {
@@ -67,19 +67,22 @@ export function resolveProjectionCatalogShortList(ruleIds) {
67
67
  * checkCommand?: string|null,
68
68
  * }} [options]
69
69
  */
70
- export function collectAgentProjectionFacts(options = {}) {
70
+ function collectAgentProjectionFacts(options = {}) {
71
71
  const startRoot = path.resolve(options.root || process.cwd());
72
72
  const configName = options.config || 'ark.config.json';
73
73
  let resolvedRoot = startRoot;
74
+ let configPath = null;
74
75
  try {
75
76
  const resolved = resolveEffectiveProjectRoot(startRoot, {
76
77
  configName,
77
78
  writeMode: false,
78
79
  });
79
80
  resolvedRoot = path.resolve(resolved.root || startRoot);
81
+ if (resolved.configFound && resolved.configPath) configPath = path.resolve(resolved.configPath);
80
82
  } catch {
81
83
  resolvedRoot = startRoot;
82
84
  }
85
+ if (!configPath) configPath = path.resolve(resolvedRoot, configName);
83
86
 
84
87
  const version =
85
88
  (typeof options.arkgateVersion === 'string' && options.arkgateVersion.trim()) ||
@@ -88,7 +91,8 @@ export function collectAgentProjectionFacts(options = {}) {
88
91
 
89
92
  let layers = options.layers;
90
93
  if (layers === undefined) {
91
- layers = loadConfigLayersForAgents(resolvedRoot);
94
+ // Read the contract the caller named (--config), not a hardcoded ark.config.json.
95
+ layers = loadConfigLayersForAgents(resolvedRoot, configPath);
92
96
  }
93
97
 
94
98
  const layerSummaries = Array.isArray(layers)
@@ -130,7 +134,7 @@ export function collectAgentProjectionFacts(options = {}) {
130
134
  * Build projection block + meta for a project (no write).
131
135
  * @param {Parameters<typeof collectAgentProjectionFacts>[0]} [options]
132
136
  */
133
- export function buildProjectAgentProjection(options = {}) {
137
+ function buildProjectAgentProjection(options = {}) {
134
138
  const facts = collectAgentProjectionFacts(options);
135
139
  const block = buildAgentProjectionBlock(facts);
136
140
  const meta = buildAgentProjectionMeta(facts);
@@ -207,7 +211,7 @@ export function planAgentProjectionRefresh(options = {}) {
207
211
  * @param {ReturnType<typeof planAgentProjectionRefresh>} plan
208
212
  * @param {{ write?: boolean }} [opts]
209
213
  */
210
- export function applyAgentProjectionRefresh(plan, opts = {}) {
214
+ function applyAgentProjectionRefresh(plan, opts = {}) {
211
215
  const shouldWrite = opts.write !== false;
212
216
  if (!plan.wouldWrite) {
213
217
  return { wrote: false, action: plan.action, path: plan.path };
@@ -15,49 +15,28 @@
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
17
17
  import { layerForFile } from '../ark-shared.mjs';
18
+ import { loadAckSidecar } from './contract-smells.mjs';
18
19
 
19
20
  export const AMBIENT_STATE_ACKS_PATH = '.ark/ambient-state-acks.json';
20
21
 
21
- const MAX_ACK_BYTES = 64 * 1024;
22
- const MAX_ACK_ENTRIES = 200;
23
22
  const MAX_FILE_BYTES = 256 * 1024;
24
23
  const MAX_FINDINGS = 50;
25
24
 
26
25
  /** Bounded, fail-loud sidecar loader (same discipline as contract-smell acks). */
27
26
  export function loadAmbientStateAcks(root) {
28
- const relPath = AMBIENT_STATE_ACKS_PATH;
29
- const abs = path.join(root, relPath);
30
- let stats;
31
- try {
32
- stats = fs.statSync(abs);
33
- } catch {
34
- return { path: relPath, exists: false, acks: [] };
35
- }
36
- const invalid = (error) => ({ path: relPath, exists: true, invalid: true, error, acks: [] });
37
- if (!stats.isFile()) return invalid('not a regular file');
38
- if (stats.size > MAX_ACK_BYTES) return invalid(`larger than ${MAX_ACK_BYTES} bytes`);
39
- let parsed;
40
- try {
41
- parsed = JSON.parse(fs.readFileSync(abs, 'utf8'));
42
- } catch (error) {
43
- return invalid(error instanceof Error ? error.message : 'unreadable JSON');
44
- }
45
- const acks = Array.isArray(parsed?.acks) ? parsed.acks : null;
46
- if (!acks) return invalid('expected { acks: [{ file, name, reason? }] }');
47
- if (acks.length > MAX_ACK_ENTRIES) return invalid(`more than ${MAX_ACK_ENTRIES} entries`);
48
- const wellFormed = acks.every(
49
- (a) =>
27
+ return loadAckSidecar(root, AMBIENT_STATE_ACKS_PATH, {
28
+ shape: '{ acks: [{ file, name, reason? }] }',
29
+ isWellFormed: (a) =>
50
30
  a !== null &&
51
31
  typeof a === 'object' &&
52
32
  typeof a.file === 'string' &&
53
33
  a.file.length > 0 &&
54
34
  typeof a.name === 'string' &&
55
- a.name.length > 0
56
- );
57
- if (!wellFormed) return invalid('every ack needs string file and name');
58
- // Normalize separators so a Windows-authored ack file still matches.
59
- const normalized = acks.map((a) => ({ ...a, file: a.file.replace(/\\/g, '/') }));
60
- return { path: relPath, exists: true, acks: normalized };
35
+ a.name.length > 0,
36
+ malformed: 'every ack needs string file and name',
37
+ // Normalize separators so a Windows-authored ack file still matches.
38
+ normalize: (acks) => acks.map((a) => ({ ...a, file: a.file.replace(/\\/g, '/') })),
39
+ });
61
40
  }
62
41
 
63
42
  function isAcknowledged(ackState, file, name) {