arkgate 4.8.24 → 4.8.26

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 (166) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +2 -2
  3. package/bin/ark-check-runtime.mjs +17 -14
  4. package/bin/ark-layer-match.mjs +115 -13
  5. package/bin/ark-mcp-runtime.mjs +36 -31
  6. package/bin/ark-shared.mjs +4 -23
  7. package/bin/lib/adoption-stance.mjs +2 -2
  8. package/bin/lib/adr-path.mjs +3 -3
  9. package/bin/lib/agent-gates.mjs +0 -61
  10. package/bin/lib/agent-homes.mjs +12 -21
  11. package/bin/lib/agent-projection-command.mjs +4 -4
  12. package/bin/lib/ambient-state.mjs +9 -30
  13. package/bin/lib/analysis-engine.mjs +14 -14
  14. package/bin/lib/architecture-scan.mjs +11 -9
  15. package/bin/lib/ark-order-doctor.mjs +3 -28
  16. package/bin/lib/ark-order-facts.mjs +6 -99
  17. package/bin/lib/ark-order-types.mjs +1 -2
  18. package/bin/lib/ark-run-doctor.mjs +3 -28
  19. package/bin/lib/ark-run-facts.mjs +30 -40
  20. package/bin/lib/ark-run-sensors.mjs +1 -1
  21. package/bin/lib/arkrule-file-hints.mjs +2 -47
  22. package/bin/lib/arkrules-contract.mjs +124 -133
  23. package/bin/lib/arkrules-sensors.mjs +7 -5
  24. package/bin/lib/check-args.mjs +63 -1
  25. package/bin/lib/ci-and-commands.mjs +8 -72
  26. package/bin/lib/ci-merge-boundary.mjs +1 -1
  27. package/bin/lib/class-source-scan.mjs +5 -5
  28. package/bin/lib/clone-detection.mjs +474 -0
  29. package/bin/lib/codex-home.mjs +1 -1
  30. package/bin/lib/config-contract-slices.mjs +44 -6
  31. package/bin/lib/config-contract.mjs +40 -117
  32. package/bin/lib/config-extras.mjs +16 -22
  33. package/bin/lib/contract-smells.mjs +33 -15
  34. package/bin/lib/core-ratchet.mjs +0 -2
  35. package/bin/lib/dashboard-cli.mjs +2 -2
  36. package/bin/lib/deep-module-coach.mjs +2 -4
  37. package/bin/lib/deploy-path.mjs +13 -38
  38. package/bin/lib/design-smells.mjs +2 -2
  39. package/bin/lib/diagnostic-catalog.mjs +8 -1
  40. package/bin/lib/doctor-advisories.mjs +55 -9
  41. package/bin/lib/doctor-green-cite.mjs +1 -1
  42. package/bin/lib/doctor-human.mjs +1 -1
  43. package/bin/lib/doctor-next-actions.mjs +2 -2
  44. package/bin/lib/doctor-plan.mjs +4 -6
  45. package/bin/lib/duplication-io.mjs +572 -0
  46. package/bin/lib/effective-contract-load.mjs +181 -44
  47. package/bin/lib/entry-points-io.mjs +424 -0
  48. package/bin/lib/extra-merge-teeth.mjs +36 -0
  49. package/bin/lib/field-install.mjs +38 -14
  50. package/bin/lib/first-run-help.mjs +7 -3
  51. package/bin/lib/flat-parent-importers.mjs +5 -3
  52. package/bin/lib/gate-files.mjs +30 -89
  53. package/bin/lib/github-enforcement.mjs +165 -28
  54. package/bin/lib/golden-pattern.mjs +1 -1
  55. package/bin/lib/hook-templates.mjs +16 -16
  56. package/bin/lib/html-report-advisories.mjs +4 -0
  57. package/bin/lib/html-report-depth.mjs +2 -2
  58. package/bin/lib/html-report.mjs +2 -2
  59. package/bin/lib/import-graph-projection.mjs +112 -0
  60. package/bin/lib/improvement-compass-doctor.mjs +1 -24
  61. package/bin/lib/invariant-coverage-io.mjs +4 -3
  62. package/bin/lib/invariant-coverage.mjs +26 -0
  63. package/bin/lib/invariant-probe-cli.mjs +347 -0
  64. package/bin/lib/invariant-probe-io.mjs +165 -0
  65. package/bin/lib/invariant-probe-runner.mjs +268 -0
  66. package/bin/lib/invariant-probe-sites.mjs +174 -0
  67. package/bin/lib/invariant-probe-workspace.mjs +183 -0
  68. package/bin/lib/invariant-probe.mjs +461 -0
  69. package/bin/lib/invariant-tests-path.mjs +16 -22
  70. package/bin/lib/literal-path-drift.mjs +4 -2
  71. package/bin/lib/managed-upgrade.mjs +2 -8
  72. package/bin/lib/mcp-adoption.mjs +4 -5
  73. package/bin/lib/mcp-hook-payload.mjs +4 -4
  74. package/bin/lib/orphan-modules-io.mjs +458 -0
  75. package/bin/lib/orphan-modules.mjs +251 -0
  76. package/bin/lib/outside-importers.mjs +197 -0
  77. package/bin/lib/package-manager.mjs +3 -3
  78. package/bin/lib/parse-health.mjs +1 -1
  79. package/bin/lib/persistence-write-hint.mjs +33 -8
  80. package/bin/lib/pin-dependent-gates.mjs +2 -2
  81. package/bin/lib/policy-delta-io.mjs +60 -12
  82. package/bin/lib/post-green-path.mjs +1 -1
  83. package/bin/lib/prepare-change.mjs +3 -9
  84. package/bin/lib/prepare-write.mjs +1 -2
  85. package/bin/lib/presets.mjs +56 -68
  86. package/bin/lib/product-copy.mjs +1 -6
  87. package/bin/lib/project-root.mjs +43 -85
  88. package/bin/lib/resolved-candidate-facts.mjs +90 -62
  89. package/bin/lib/rules-under-contract.mjs +72 -8
  90. package/bin/lib/scan-files.mjs +4 -4
  91. package/bin/lib/schema-validation.mjs +130 -0
  92. package/bin/lib/sensor-promote-cli.mjs +2 -11
  93. package/bin/lib/sensor-promote-io.mjs +33 -22
  94. package/bin/lib/sensor-promotion.mjs +1 -1
  95. package/bin/lib/skill-catalog-apply.mjs +1 -1
  96. package/bin/lib/skill-install.mjs +42 -38
  97. package/bin/lib/snippet-analysis.mjs +1 -1
  98. package/bin/lib/start-install-recovery.mjs +1 -1
  99. package/bin/lib/start-preview.mjs +1 -1
  100. package/bin/lib/states-transitions-presence.mjs +3 -3
  101. package/bin/lib/status-command.mjs +2 -18
  102. package/bin/lib/status-manifest.mjs +18 -26
  103. package/bin/lib/status-transition-catalog.mjs +0 -6
  104. package/bin/lib/suggestions.mjs +3 -3
  105. package/bin/lib/team-parliament-io.mjs +3 -17
  106. package/bin/lib/team-parliament.mjs +15 -9
  107. package/bin/lib/upgrade-behavior-changes.mjs +2 -2
  108. package/bin/lib/upgrade-command.mjs +2 -24
  109. package/bin/lib/upgrade-outcome.mjs +1 -1
  110. package/bin/lib/upstream-report.mjs +21 -10
  111. package/bin/lib/violations.mjs +10 -9
  112. package/bin/lib/weakest-link.mjs +13 -0
  113. package/bin/lib/write-path-capabilities.mjs +2 -2
  114. package/dist/chunk-G3JRBJGH.js +16 -0
  115. package/dist/chunk-R3AJ5RSZ.cjs +16 -0
  116. package/dist/{configTypes-BJbaeoj4.d.cts → configTypes-GPvvdleM.d.cts} +12 -1
  117. package/dist/{configTypes-BJbaeoj4.d.ts → configTypes-GPvvdleM.d.ts} +12 -1
  118. package/dist/eslint/index.cjs +11 -11
  119. package/dist/eslint/index.d.cts +10 -1
  120. package/dist/eslint/index.d.ts +10 -1
  121. package/dist/eslint/index.js +11 -11
  122. package/dist/index.cjs +34 -34
  123. package/dist/index.d.cts +587 -515
  124. package/dist/index.d.ts +587 -515
  125. package/dist/index.js +34 -34
  126. package/dist/nestjs/index.cjs +1 -1
  127. package/dist/nestjs/index.d.cts +1 -1
  128. package/dist/nestjs/index.d.ts +1 -1
  129. package/dist/nestjs/index.js +1 -1
  130. package/dist/order/index.cjs +1 -1
  131. package/dist/order/index.js +1 -1
  132. package/dist/runtime/index.cjs +12 -12
  133. package/dist/runtime/index.d.cts +174 -136
  134. package/dist/runtime/index.d.ts +174 -136
  135. package/dist/runtime/index.js +12 -12
  136. package/dist/{types-DRinG-vT.d.cts → types-CnauMQQL.d.cts} +33 -25
  137. package/dist/{types-DRinG-vT.d.ts → types-CnauMQQL.d.ts} +33 -25
  138. package/docs/README.md +3 -1
  139. package/docs/agent-guide.md +140 -6
  140. package/docs/ai-gates.md +15 -1
  141. package/docs/configuration.md +40 -11
  142. package/docs/diagnostics.md +119 -3
  143. package/docs/package-surface.md +4 -1
  144. package/docs/product-voice.md +7 -1
  145. package/package.json +11 -2
  146. package/schemas/ark.config.schema.json +31 -3
  147. package/schemas/ark.invariant-probe.schema.json +368 -0
  148. package/server.json +2 -2
  149. package/templates/agent-skills/README.md +1 -1
  150. package/templates/agent-skills/ark-adopt/SKILL.md +1 -0
  151. package/templates/agent-skills/ark-contract/SKILL.md +17 -0
  152. package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
  153. package/templates/agent-skills/ark-explore/SKILL.md +7 -0
  154. package/templates/agent-skills/ark-fix/SKILL.md +20 -0
  155. package/templates/agent-skills/ark-place/SKILL.md +8 -0
  156. package/templates/skills/ark-adopt.md +1 -0
  157. package/templates/skills/ark-contract.md +17 -0
  158. package/templates/skills/ark-coverage.md +2 -0
  159. package/templates/skills/ark-explore.md +7 -0
  160. package/templates/skills/ark-fix.md +20 -0
  161. package/templates/skills/ark-place.md +8 -0
  162. package/bin/lib/ark-order-error.mjs +0 -18
  163. package/bin/lib/ark-order-invariants.mjs +0 -388
  164. package/bin/lib/stable-hash.mjs +0 -90
  165. package/dist/chunk-MBXCFLO4.js +0 -16
  166. package/dist/chunk-RNQHRDCQ.cjs +0 -16
@@ -5,8 +5,8 @@
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
7
 
8
- export const ADOPTION_STANCE_REL = '.ark/adoption-stance.json';
9
- export const ADOPTION_STANCE_VALUE = 'advisory-only';
8
+ const ADOPTION_STANCE_REL = '.ark/adoption-stance.json';
9
+ const ADOPTION_STANCE_VALUE = 'advisory-only';
10
10
  export const ADOPTED_REQUIRED_MERGE = 'required-merge';
11
11
  export const ADOPTED_ADVISORY_ACKED = 'advisory-only-acked';
12
12
  export const ADOPTED_NOT = 'not-adopted';
@@ -12,10 +12,10 @@ export const ADR_PATH_ASK =
12
12
  export const ADR_PATH_NEXT =
13
13
  'Add a short note under docs/adr/ (or docs/decisions/) and put that file path in --policy-ack as adrPath.';
14
14
 
15
- export const ADR_PATH_MISSING_FILE_ASK =
15
+ const ADR_PATH_MISSING_FILE_ASK =
16
16
  'The acknowledgement names a decision note that is missing or empty.';
17
17
 
18
- export const ADR_PATH_MISSING_FILE_NEXT =
18
+ const ADR_PATH_MISSING_FILE_NEXT =
19
19
  'Write that note (or fix adrPath) under docs/adr/ or docs/decisions/, then run ArkGate again.';
20
20
 
21
21
  /**
@@ -24,7 +24,7 @@ export const ADR_PATH_MISSING_FILE_NEXT =
24
24
  * @param {unknown} value
25
25
  * @returns {string | null}
26
26
  */
27
- export function canonicalizeAdrPath(value) {
27
+ function canonicalizeAdrPath(value) {
28
28
  if (typeof value !== 'string') return null;
29
29
  const rel = value.trim().replace(/\\/g, '/');
30
30
  if (!rel || rel.startsWith('/') || /^[A-Za-z]:\//.test(rel) || rel.includes('\0')) return null;
@@ -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,7 +67,7 @@ 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;
@@ -134,7 +134,7 @@ export function collectAgentProjectionFacts(options = {}) {
134
134
  * Build projection block + meta for a project (no write).
135
135
  * @param {Parameters<typeof collectAgentProjectionFacts>[0]} [options]
136
136
  */
137
- export function buildProjectAgentProjection(options = {}) {
137
+ function buildProjectAgentProjection(options = {}) {
138
138
  const facts = collectAgentProjectionFacts(options);
139
139
  const block = buildAgentProjectionBlock(facts);
140
140
  const meta = buildAgentProjectionMeta(facts);
@@ -211,7 +211,7 @@ export function planAgentProjectionRefresh(options = {}) {
211
211
  * @param {ReturnType<typeof planAgentProjectionRefresh>} plan
212
212
  * @param {{ write?: boolean }} [opts]
213
213
  */
214
- export function applyAgentProjectionRefresh(plan, opts = {}) {
214
+ function applyAgentProjectionRefresh(plan, opts = {}) {
215
215
  const shouldWrite = opts.write !== false;
216
216
  if (!plan.wouldWrite) {
217
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) {