@nimiplatform/nimi-coding 0.2.7 → 0.3.0

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 (207) hide show
  1. package/CHANGELOG.md +8 -80
  2. package/CONTRIBUTING.md +5 -34
  3. package/README.md +47 -350
  4. package/README.zh-CN.md +33 -287
  5. package/cli/commands/start.mjs +48 -688
  6. package/cli/commands/validate-spec-audit.mjs +1 -5
  7. package/cli/constants.mjs +9 -417
  8. package/cli/dev/run-cli-smoke.mjs +24 -0
  9. package/cli/dev/run-suite.mjs +17 -0
  10. package/cli/help.mjs +28 -145
  11. package/cli/index.mjs +5 -33
  12. package/cli/lib/blueprint-audit.mjs +5 -12
  13. package/cli/lib/bootstrap.mjs +0 -9
  14. package/cli/lib/contracts.mjs +0 -163
  15. package/cli/lib/doctor.mjs +155 -10
  16. package/cli/lib/entrypoints.mjs +63 -167
  17. package/cli/lib/internal/contracts-loaders.mjs +0 -85
  18. package/cli/lib/internal/contracts-parse.mjs +84 -520
  19. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +13 -8
  20. package/cli/lib/internal/surface-taxonomy-validators.mjs +316 -66
  21. package/cli/lib/internal/validators-shared.mjs +0 -16
  22. package/cli/lib/internal/validators-spec-helpers.mjs +7 -140
  23. package/cli/lib/internal/validators-spec.mjs +103 -342
  24. package/cli/lib/shared.mjs +1 -42
  25. package/cli/lib/validators.mjs +0 -48
  26. package/cli/seeds/seed-policy.yaml +0 -3
  27. package/config/bootstrap.yaml +4 -4
  28. package/config/spec-generation-inputs.yaml +6 -12
  29. package/contracts/domain-admission.schema.yaml +3 -3
  30. package/contracts/migration-inventory.schema.yaml +12 -75
  31. package/contracts/negative-fixtures.yaml +19 -99
  32. package/contracts/placement-contract.schema.yaml +49 -78
  33. package/contracts/projection-edge.schema.yaml +37 -121
  34. package/contracts/shared-enums.yaml +4 -8
  35. package/contracts/spec-generation-inputs.schema.yaml +15 -112
  36. package/contracts/spec-layout.schema.yaml +32 -0
  37. package/contracts/surface-taxonomy.schema.yaml +43 -94
  38. package/contracts/table-family.schema.yaml +1 -1
  39. package/contracts/tracked-output-admission.schema.yaml +13 -66
  40. package/methodology/core.yaml +33 -25
  41. package/methodology/four-closure-policy.yaml +18 -26
  42. package/methodology/role-separation-policy.yaml +21 -27
  43. package/methodology/spec-reconstruction.yaml +17 -34
  44. package/package.json +4 -6
  45. package/spec/_meta/spec-tree-model.yaml +5 -101
  46. package/spec/product-scope.yaml +25 -57
  47. package/adapters/README.md +0 -25
  48. package/adapters/claude/README.md +0 -89
  49. package/adapters/claude/profile.yaml +0 -70
  50. package/adapters/codex/README.md +0 -53
  51. package/adapters/codex/profile.yaml +0 -78
  52. package/adapters/oh-my-codex/README.md +0 -184
  53. package/adapters/oh-my-codex/profile.yaml +0 -46
  54. package/cli/commands/admit-high-risk-decision.mjs +0 -108
  55. package/cli/commands/audit-sweep.mjs +0 -364
  56. package/cli/commands/closeout.mjs +0 -186
  57. package/cli/commands/decide-high-risk-execution.mjs +0 -124
  58. package/cli/commands/handoff.mjs +0 -123
  59. package/cli/commands/ingest-high-risk-execution.mjs +0 -95
  60. package/cli/commands/review-high-risk-execution.mjs +0 -95
  61. package/cli/commands/sweep-design.mjs +0 -295
  62. package/cli/commands/sweep.mjs +0 -22
  63. package/cli/commands/topic-formatters.mjs +0 -382
  64. package/cli/commands/topic-goal.mjs +0 -33
  65. package/cli/commands/topic-options-shared.mjs +0 -27
  66. package/cli/commands/topic-options-workflow.mjs +0 -767
  67. package/cli/commands/topic-options.mjs +0 -626
  68. package/cli/commands/topic-runner.mjs +0 -169
  69. package/cli/commands/topic.mjs +0 -795
  70. package/cli/commands/validate-acceptance.mjs +0 -5
  71. package/cli/commands/validate-execution-packet.mjs +0 -5
  72. package/cli/commands/validate-orchestration-state.mjs +0 -5
  73. package/cli/commands/validate-prompt.mjs +0 -5
  74. package/cli/commands/validate-worker-output.mjs +0 -5
  75. package/cli/lib/adapter-profiles.mjs +0 -403
  76. package/cli/lib/audit-execution.mjs +0 -52
  77. package/cli/lib/audit-sweep-runtime/admissions.mjs +0 -508
  78. package/cli/lib/audit-sweep-runtime/audit-validity.mjs +0 -356
  79. package/cli/lib/audit-sweep-runtime/chunks.mjs +0 -697
  80. package/cli/lib/audit-sweep-runtime/claude-auditor.mjs +0 -647
  81. package/cli/lib/audit-sweep-runtime/closeout.mjs +0 -144
  82. package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +0 -654
  83. package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +0 -516
  84. package/cli/lib/audit-sweep-runtime/common.mjs +0 -341
  85. package/cli/lib/audit-sweep-runtime/coverage-quality.mjs +0 -172
  86. package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +0 -155
  87. package/cli/lib/audit-sweep-runtime/format.mjs +0 -57
  88. package/cli/lib/audit-sweep-runtime/ingest.mjs +0 -486
  89. package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +0 -425
  90. package/cli/lib/audit-sweep-runtime/inventory.mjs +0 -798
  91. package/cli/lib/audit-sweep-runtime/ledger.mjs +0 -315
  92. package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +0 -101
  93. package/cli/lib/audit-sweep-runtime/remediation.mjs +0 -349
  94. package/cli/lib/audit-sweep-runtime/rerun.mjs +0 -129
  95. package/cli/lib/audit-sweep-runtime/risk-budget.mjs +0 -300
  96. package/cli/lib/audit-sweep-runtime/status.mjs +0 -62
  97. package/cli/lib/audit-sweep-runtime/validators-ledger.mjs +0 -215
  98. package/cli/lib/audit-sweep-runtime/validators.mjs +0 -797
  99. package/cli/lib/audit-sweep.mjs +0 -19
  100. package/cli/lib/authority-convergence.mjs +0 -704
  101. package/cli/lib/closeout.mjs +0 -732
  102. package/cli/lib/codex-sdk-runner.mjs +0 -76
  103. package/cli/lib/external-execution.mjs +0 -101
  104. package/cli/lib/handoff.mjs +0 -812
  105. package/cli/lib/high-risk-admission.mjs +0 -512
  106. package/cli/lib/high-risk-decision.mjs +0 -353
  107. package/cli/lib/high-risk-ingest.mjs +0 -321
  108. package/cli/lib/high-risk-review.mjs +0 -267
  109. package/cli/lib/internal/contracts-parse-high-risk.mjs +0 -131
  110. package/cli/lib/internal/contracts-validators.mjs +0 -399
  111. package/cli/lib/internal/doctor-bootstrap-surface.mjs +0 -406
  112. package/cli/lib/internal/doctor-delegated-surface.mjs +0 -256
  113. package/cli/lib/internal/doctor-finalize.mjs +0 -383
  114. package/cli/lib/internal/doctor-format.mjs +0 -286
  115. package/cli/lib/internal/doctor-inspectors.mjs +0 -327
  116. package/cli/lib/internal/doctor-state.mjs +0 -205
  117. package/cli/lib/internal/validators-artifacts.mjs +0 -515
  118. package/cli/lib/sweep-design-runtime/common.mjs +0 -246
  119. package/cli/lib/sweep-design-runtime/engine.mjs +0 -733
  120. package/cli/lib/sweep-design-runtime/fix-topic.mjs +0 -414
  121. package/cli/lib/sweep-design-runtime/lifecycle.mjs +0 -54
  122. package/cli/lib/sweep-design-runtime/results.mjs +0 -324
  123. package/cli/lib/sweep-design.mjs +0 -8
  124. package/cli/lib/topic-artifacts.mjs +0 -186
  125. package/cli/lib/topic-authority-coverage.mjs +0 -73
  126. package/cli/lib/topic-closeout.mjs +0 -560
  127. package/cli/lib/topic-common.mjs +0 -404
  128. package/cli/lib/topic-decisions.mjs +0 -332
  129. package/cli/lib/topic-draft-packets.mjs +0 -167
  130. package/cli/lib/topic-execution.mjs +0 -515
  131. package/cli/lib/topic-goal.mjs +0 -440
  132. package/cli/lib/topic-ledger.mjs +0 -281
  133. package/cli/lib/topic-lifecycle-artifacts.mjs +0 -173
  134. package/cli/lib/topic-root-validation.mjs +0 -288
  135. package/cli/lib/topic-runner-commands.mjs +0 -174
  136. package/cli/lib/topic-runner-deferral.mjs +0 -532
  137. package/cli/lib/topic-runner-stale-gates.mjs +0 -114
  138. package/cli/lib/topic-runner-validation.mjs +0 -138
  139. package/cli/lib/topic-runner.mjs +0 -727
  140. package/cli/lib/topic-scaffold.mjs +0 -252
  141. package/cli/lib/topic-waves.mjs +0 -403
  142. package/cli/lib/topic.mjs +0 -81
  143. package/config/audit-execution-artifacts.yaml +0 -20
  144. package/config/external-execution-artifacts.yaml +0 -16
  145. package/config/host-adapter.yaml +0 -30
  146. package/config/host-profile.yaml +0 -29
  147. package/config/installer-evidence.yaml +0 -31
  148. package/config/skill-installer.yaml +0 -23
  149. package/config/skill-manifest.yaml +0 -48
  150. package/config/skills.yaml +0 -30
  151. package/contracts/acceptance.schema.yaml +0 -16
  152. package/contracts/admission-checklist.schema.yaml +0 -15
  153. package/contracts/audit-chunk.schema.yaml +0 -123
  154. package/contracts/audit-closeout.schema.yaml +0 -51
  155. package/contracts/audit-finding.schema.yaml +0 -61
  156. package/contracts/audit-ledger.schema.yaml +0 -138
  157. package/contracts/audit-plan.schema.yaml +0 -138
  158. package/contracts/audit-remediation-map.schema.yaml +0 -52
  159. package/contracts/audit-rerun.schema.yaml +0 -31
  160. package/contracts/audit-sweep-result.yaml +0 -53
  161. package/contracts/authority-convergence-audit.schema.yaml +0 -19
  162. package/contracts/closeout.schema.yaml +0 -25
  163. package/contracts/decision-review.schema.yaml +0 -16
  164. package/contracts/doc-spec-audit-result.yaml +0 -19
  165. package/contracts/execution-packet.schema.yaml +0 -49
  166. package/contracts/external-host-compatibility.yaml +0 -22
  167. package/contracts/forbidden-shortcuts.catalog.yaml +0 -23
  168. package/contracts/high-risk-admission.schema.yaml +0 -23
  169. package/contracts/high-risk-execution-result.yaml +0 -20
  170. package/contracts/orchestration-state.schema.yaml +0 -41
  171. package/contracts/overflow-continuation.schema.yaml +0 -12
  172. package/contracts/packet.schema.yaml +0 -30
  173. package/contracts/pending-note.schema.yaml +0 -17
  174. package/contracts/prompt.schema.yaml +0 -12
  175. package/contracts/remediation.schema.yaml +0 -16
  176. package/contracts/result.schema.yaml +0 -24
  177. package/contracts/spec-reconstruction-result.yaml +0 -41
  178. package/contracts/sweep-design-result.yaml +0 -349
  179. package/contracts/topic-goal.schema.yaml +0 -87
  180. package/contracts/topic-run-ledger.schema.yaml +0 -72
  181. package/contracts/topic-step-decision.schema.yaml +0 -45
  182. package/contracts/topic.schema.yaml +0 -65
  183. package/contracts/true-close.schema.yaml +0 -15
  184. package/contracts/wave.schema.yaml +0 -29
  185. package/contracts/worker-output.schema.yaml +0 -15
  186. package/contracts/workflow-consumer.schema.yaml +0 -110
  187. package/methodology/audit-sweep-p0p1-recall.yaml +0 -45
  188. package/methodology/authority-convergence-policy.yaml +0 -42
  189. package/methodology/overflow-continuation-policy.yaml +0 -14
  190. package/methodology/skill-exchange-projection.yaml +0 -114
  191. package/methodology/skill-handoff.yaml +0 -34
  192. package/methodology/skill-installer-result.yaml +0 -27
  193. package/methodology/skill-installer-summary-projection.yaml +0 -181
  194. package/methodology/skill-runtime.yaml +0 -23
  195. package/methodology/spec-target-truth-profile.yaml +0 -46
  196. package/methodology/topic-lifecycle-report.yaml +0 -144
  197. package/methodology/topic-lifecycle.yaml +0 -37
  198. package/methodology/topic-naming-ontology.yaml +0 -21
  199. package/methodology/topic-ontology.yaml +0 -38
  200. package/methodology/topic-validation-policy.yaml +0 -9
  201. package/methodology/wave-dag-policy.yaml +0 -14
  202. package/spec/_meta/command-gating-matrix.yaml +0 -143
  203. package/spec/_meta/generate-drift-migration-checklist.yaml +0 -137
  204. package/spec/_meta/governance-routing-cutover-checklist.yaml +0 -35
  205. package/spec/_meta/phase2-impacted-surface-matrix.yaml +0 -44
  206. package/spec/_meta/spec-authority-cutover-readiness.yaml +0 -102
  207. package/spec/bootstrap-state.yaml +0 -99
@@ -3,14 +3,12 @@ import path from "node:path";
3
3
  import {
4
4
  SPEC_GENERATION_AUDIT_CONTRACT_REF,
5
5
  SPEC_GENERATION_AUDIT_COVERAGE_STATUS_ENUM,
6
- SPEC_GENERATION_AUDIT_FILE_REQUIRED_FIELDS,
7
6
  SPEC_GENERATION_AUDIT_SOURCE_BASIS_ENUM,
8
7
  } from "../../constants.mjs";
9
8
  import {
10
9
  loadBlueprintReference,
11
10
  loadSpecGenerationAuditContract,
12
11
  loadSpecGenerationInputsConfig,
13
- loadSpecTreeModelContract,
14
12
  } from "../contracts.mjs";
15
13
  import { readTextIfFile } from "../fs-helpers.mjs";
16
14
  import { isPlainObject } from "../value-helpers.mjs";
@@ -21,161 +19,59 @@ import {
21
19
  } from "./validators-shared.mjs";
22
20
  import {
23
21
  validateDomainAdmission,
22
+ validateGuidanceBodies,
24
23
  validatePlacement,
24
+ validateProjectionEdges,
25
25
  validateTableFamily,
26
26
  } from "./surface-taxonomy-validators.mjs";
27
27
  import {
28
- classifyAuditCoveredFiles,
29
- classifySpecTreeFiles,
30
28
  collectTreeFiles,
31
29
  isDeclaredInputsCompatibleWithConfig,
32
30
  isSourceRefWithinDeclaredRoots,
33
31
  } from "./validators-spec-helpers.mjs";
34
32
 
35
- async function loadV2RequiredFiles(projectRoot) {
33
+ async function loadRequiredFiles(projectRoot) {
36
34
  const text = await readTextIfFile(path.join(projectRoot, ".nimi", "methodology", "spec-reconstruction.yaml"));
37
35
  const parsed = parseYamlText(text);
38
36
  return Array.isArray(parsed?.reconstruction?.target_tree_shape?.minimal_required_outputs)
39
37
  ? parsed.reconstruction.target_tree_shape.minimal_required_outputs.map(String)
40
38
  : [];
41
39
  }
42
-
43
- function isV2SpecGenerationInputs(specGenerationInputs) {
44
- return specGenerationInputs.ok && specGenerationInputs.mode === "class_filtered";
40
+ function normalizeAuditFileClass(entry) {
41
+ return typeof entry.surface_class === "string"
42
+ ? entry.surface_class
43
+ : String(entry.file_class ?? "");
45
44
  }
46
45
 
47
- async function validateSpecTreeV2(rootPath, projectRoot, specGenerationInputs) {
48
- const errors = [];
49
- const warnings = [];
50
- const expectedRoot = path.resolve(projectRoot, specGenerationInputs.canonicalTargetRoot ?? ".nimi/spec");
51
- const targetRoot = path.resolve(rootPath);
52
-
53
- if (targetRoot !== expectedRoot) {
54
- errors.push(`spec tree root mismatch: expected ${expectedRoot} but received ${targetRoot}`);
55
- }
56
-
57
- const files = await collectTreeFiles(targetRoot);
58
- if (files.length === 0) {
59
- return {
60
- ok: false,
61
- errors: errors.length > 0 ? errors : [`missing spec tree root: ${targetRoot}`],
62
- warnings,
63
- refusal: makeValidatorRefusal(
64
- VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_TREE_MISSING,
65
- "spec tree root is missing or empty",
66
- ),
67
- };
68
- }
69
-
70
- const requiredFiles = await loadV2RequiredFiles(projectRoot);
71
- const canonicalRoot = specGenerationInputs.canonicalTargetRoot ?? ".nimi/spec";
72
- const missingRequired = requiredFiles
73
- .map((entry) => path.posix.relative(canonicalRoot, entry))
74
- .filter((entry) => !files.includes(entry));
75
- if (missingRequired.length > 0) {
76
- errors.push(`missing required canonical files: ${missingRequired.join(", ")}`);
77
- }
78
-
79
- const rootRef = path.relative(projectRoot, targetRoot).split(path.sep).join(path.posix.sep) || ".";
80
- const placement = await validatePlacement(projectRoot, { rootRef });
81
- const domainAdmission = await validateDomainAdmission(projectRoot, { rootRef });
82
- const tableFamily = await validateTableFamily(projectRoot, { rootRef });
83
- errors.push(...(placement.errors ?? []), ...(domainAdmission.errors ?? []), ...(tableFamily.errors ?? []));
46
+ function toPortableProjectPath(projectRoot, absolutePath) {
47
+ return path.relative(projectRoot, absolutePath).split(path.sep).join(path.posix.sep);
48
+ }
84
49
 
50
+ function invalidTree(errors, warnings, message) {
85
51
  return {
86
- ok: errors.length === 0,
52
+ ok: false,
87
53
  errors,
88
54
  warnings,
89
- refusal: errors.length === 0
90
- ? null
91
- : makeValidatorRefusal(
92
- VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_TREE_INVALID,
93
- `spec tree is invalid: ${path.basename(targetRoot)}`,
94
- ),
95
- summary: {
96
- profile: "surface_taxonomy_v1",
97
- canonicalRoot,
98
- totalFiles: files.length,
99
- requiredFiles: requiredFiles.length,
100
- missingRequired,
101
- classifiedFiles: placement.summary?.total ?? files.length,
102
- unexpectedFiles: [],
103
- conflictingFiles: [],
104
- },
55
+ refusal: makeValidatorRefusal(VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_TREE_INVALID, message),
105
56
  };
106
57
  }
107
58
 
108
- function toPortableProjectPath(projectRoot, absolutePath) {
109
- return path.relative(projectRoot, absolutePath).split(path.sep).join(path.posix.sep);
110
- }
111
-
112
- function pathStartsWithRoot(candidate, root) {
113
- return candidate === root || candidate.startsWith(`${root}/`);
114
- }
115
-
116
- function generatedOutputNormativeRootOverlaps(specTreeModel) {
117
- const normativeRoots = specTreeModel.domains.map((domain) => domain.normativeRoot).filter(Boolean);
118
- const overlaps = [];
119
- for (const pipeline of specTreeModel.generatedPipelines) {
120
- for (const outputRoot of pipeline.outputRoots) {
121
- for (const normativeRoot of normativeRoots) {
122
- if (pathStartsWithRoot(outputRoot, normativeRoot) || pathStartsWithRoot(normativeRoot, outputRoot)) {
123
- overlaps.push({
124
- pipelineId: pipeline.id,
125
- outputRoot,
126
- normativeRoot,
127
- });
128
- }
129
- }
130
- }
131
- }
132
- return overlaps;
133
- }
134
-
135
- function normalizeAuditFileClass(entry) {
136
- const explicitSurfaceClass = typeof entry.surface_class === "string" ? entry.surface_class : null;
137
- if (explicitSurfaceClass) {
138
- return explicitSurfaceClass;
139
- }
140
-
141
- switch (String(entry.file_class ?? "")) {
142
- case "kernel_markdown":
143
- return "product_authority";
144
- case "kernel_tables":
145
- return "product_authority_table";
146
- case "domain_guides":
147
- case "kernel_generated":
148
- return "thin_guidance";
149
- default:
150
- return String(entry.file_class ?? "");
151
- }
152
- }
153
-
154
59
  export async function validateSpecTree(rootPath, options = {}) {
155
60
  const projectRoot = options.projectRoot ?? process.cwd();
156
- const specTreeModel = await loadSpecTreeModelContract(projectRoot);
157
- const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
61
+ const generationInputs = await loadSpecGenerationInputsConfig(projectRoot);
158
62
  const errors = [];
159
63
  const warnings = [];
160
64
 
161
- if (!specTreeModel.ok) {
162
- if (isV2SpecGenerationInputs(specGenerationInputs)) {
163
- return validateSpecTreeV2(rootPath, projectRoot, specGenerationInputs);
164
- }
165
- return {
166
- ok: false,
167
- errors: [`invalid spec tree model contract: ${specTreeModel.path}`],
65
+ if (!generationInputs.ok || generationInputs.mode !== "class_filtered") {
66
+ return invalidTree(
67
+ [`invalid spec generation inputs config: ${generationInputs.path}`],
168
68
  warnings,
169
- refusal: makeValidatorRefusal(
170
- VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_TREE_INVALID,
171
- "spec tree validation requires a valid spec-tree-model contract",
172
- ),
173
- };
69
+ "spec tree validation requires class-filtered generation inputs",
70
+ );
174
71
  }
175
72
 
176
- const expectedRoot = path.resolve(projectRoot, specTreeModel.canonicalRoot);
177
73
  const targetRoot = path.resolve(rootPath);
178
-
74
+ const expectedRoot = path.resolve(projectRoot, generationInputs.canonicalTargetRoot);
179
75
  if (targetRoot !== expectedRoot) {
180
76
  errors.push(`spec tree root mismatch: expected ${expectedRoot} but received ${targetRoot}`);
181
77
  }
@@ -193,51 +89,32 @@ export async function validateSpecTree(rootPath, options = {}) {
193
89
  };
194
90
  }
195
91
 
196
- const requiredFiles = specTreeModel.requiredFilesByProfile[specTreeModel.profile] ?? [];
92
+ const canonicalRoot = generationInputs.canonicalTargetRoot;
93
+ const requiredFiles = await loadRequiredFiles(projectRoot);
197
94
  const missingRequired = requiredFiles
198
- .map((entry) => path.posix.relative(specTreeModel.canonicalRoot, entry))
95
+ .map((entry) => path.posix.relative(canonicalRoot, entry))
199
96
  .filter((entry) => !files.includes(entry));
200
-
201
97
  if (missingRequired.length > 0) {
202
98
  errors.push(`missing required canonical files: ${missingRequired.join(", ")}`);
203
99
  }
204
100
 
205
- const generatedOutputOverlaps = generatedOutputNormativeRootOverlaps(specTreeModel);
206
- if (generatedOutputOverlaps.length > 0) {
207
- errors.push(
208
- `generated output roots overlap normative roots: ${generatedOutputOverlaps.map((entry) => `${entry.pipelineId}:${entry.outputRoot}->${entry.normativeRoot}`).join(", ")}`,
209
- );
210
- }
211
-
212
- for (const domain of specTreeModel.domains) {
213
- const domainRoot = path.posix.relative(specTreeModel.canonicalRoot, domain.root);
214
- const normativeRoot = path.posix.relative(specTreeModel.canonicalRoot, domain.normativeRoot);
215
- const tablesRoot = path.posix.relative(specTreeModel.canonicalRoot, domain.tablesRoot);
216
- const domainHasFiles = files.some((entry) => entry.startsWith(`${domainRoot}/`));
217
- const normativeHasFiles = files.some((entry) => entry.startsWith(`${normativeRoot}/`));
218
- const tablesHasFiles = files.some((entry) => entry.startsWith(`${tablesRoot}/`));
219
-
220
- if (!domainHasFiles) {
221
- errors.push(`declared domain root has no files: ${domainRoot}`);
222
- }
223
- if (!normativeHasFiles) {
224
- errors.push(`declared normative root has no files: ${normativeRoot}`);
101
+ const rootRef = path.relative(projectRoot, targetRoot).split(path.sep).join(path.posix.sep) || ".";
102
+ const reports = await Promise.all([
103
+ validatePlacement(projectRoot, { rootRef }),
104
+ validateDomainAdmission(projectRoot, { rootRef }),
105
+ validateTableFamily(projectRoot, { rootRef }),
106
+ validateProjectionEdges(projectRoot, { rootRef }),
107
+ validateGuidanceBodies(projectRoot, { rootRef }),
108
+ ]);
109
+ for (const report of reports) {
110
+ for (const error of report.errors ?? []) {
111
+ if (!errors.includes(error)) errors.push(error);
225
112
  }
226
- if (!tablesHasFiles) {
227
- errors.push(`declared tables root has no files: ${tablesRoot}`);
113
+ for (const warning of report.warnings ?? []) {
114
+ if (!warnings.includes(warning)) warnings.push(warning);
228
115
  }
229
116
  }
230
117
 
231
- const classification = classifySpecTreeFiles(specTreeModel.canonicalRoot, files, specTreeModel);
232
- if (classification.unexpected.length > 0) {
233
- errors.push(`unexpected files outside declared spec classes: ${classification.unexpected.join(", ")}`);
234
- }
235
- if (classification.conflicts.length > 0) {
236
- errors.push(
237
- `files matched multiple spec classes: ${classification.conflicts.map((entry) => `${entry.path} -> ${entry.classes.join("|")}`).join(", ")}`,
238
- );
239
- }
240
-
241
118
  return {
242
119
  ok: errors.length === 0,
243
120
  errors,
@@ -249,52 +126,37 @@ export async function validateSpecTree(rootPath, options = {}) {
249
126
  `spec tree is invalid: ${path.basename(targetRoot)}`,
250
127
  ),
251
128
  summary: {
252
- profile: specTreeModel.profile,
253
- canonicalRoot: specTreeModel.canonicalRoot,
129
+ profile: "surface_taxonomy_v2",
130
+ canonicalRoot,
254
131
  totalFiles: files.length,
255
132
  requiredFiles: requiredFiles.length,
256
133
  missingRequired,
257
- classifiedFiles: classification.classifications.length,
258
- unexpectedFiles: classification.unexpected,
259
- conflictingFiles: classification.conflicts,
134
+ classifiedFiles: reports[0]?.summary?.total ?? files.length,
135
+ unexpectedFiles: [],
136
+ conflictingFiles: [],
260
137
  },
261
138
  };
262
139
  }
263
140
 
264
141
  export async function validateSpecAudit(auditPath, options = {}) {
265
142
  const projectRoot = options.projectRoot ?? process.cwd();
266
- const specTreeModel = await loadSpecTreeModelContract(projectRoot);
267
- const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
143
+ const generationInputs = await loadSpecGenerationInputsConfig(projectRoot);
268
144
  const blueprintReference = await loadBlueprintReference(projectRoot);
269
145
  const auditContract = await loadSpecGenerationAuditContract(projectRoot);
270
- const usesV2SurfaceModel = isV2SpecGenerationInputs(specGenerationInputs);
271
146
  const errors = [];
272
147
  const warnings = [];
273
148
 
274
- if (!specTreeModel.ok && !usesV2SurfaceModel) {
149
+ if (!generationInputs.ok || generationInputs.mode !== "class_filtered") {
275
150
  return {
276
151
  ok: false,
277
- errors: [`invalid spec tree model contract: ${specTreeModel.path}`],
152
+ errors: [`invalid spec generation inputs config: ${generationInputs.path}`],
278
153
  warnings,
279
154
  refusal: makeValidatorRefusal(
280
155
  VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
281
- "spec audit validation requires a valid spec-tree-model contract",
156
+ "spec audit validation requires class-filtered generation inputs",
282
157
  ),
283
158
  };
284
159
  }
285
-
286
- if (!specGenerationInputs.ok) {
287
- return {
288
- ok: false,
289
- errors: [`invalid spec generation inputs config: ${specGenerationInputs.path}`],
290
- warnings,
291
- refusal: makeValidatorRefusal(
292
- VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
293
- "spec audit validation requires a valid spec-generation-inputs config",
294
- ),
295
- };
296
- }
297
-
298
160
  if (!auditContract.ok) {
299
161
  return {
300
162
  ok: false,
@@ -302,10 +164,13 @@ export async function validateSpecAudit(auditPath, options = {}) {
302
164
  warnings,
303
165
  refusal: makeValidatorRefusal(
304
166
  VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
305
- "spec audit validation requires a valid spec-generation-audit contract",
167
+ "spec audit validation requires a valid audit contract",
306
168
  ),
307
169
  };
308
170
  }
171
+ if (!blueprintReference.ok) {
172
+ errors.push(`invalid blueprint reference: ${blueprintReference.path}`);
173
+ }
309
174
 
310
175
  const absoluteAuditPath = path.resolve(auditPath);
311
176
  const auditText = await readTextIfFile(absoluteAuditPath);
@@ -335,10 +200,7 @@ export async function validateSpecAudit(auditPath, options = {}) {
335
200
  };
336
201
  }
337
202
 
338
- if (![1, 2].includes(parsed.version)) {
339
- errors.push("spec generation audit version must be 1 or 2");
340
- }
341
-
203
+ if (parsed.version !== 2) errors.push("spec generation audit version must be 2");
342
204
  if (String(parsed.contract_ref ?? "") !== SPEC_GENERATION_AUDIT_CONTRACT_REF) {
343
205
  errors.push(`spec generation audit contract_ref must be ${SPEC_GENERATION_AUDIT_CONTRACT_REF}`);
344
206
  }
@@ -360,59 +222,28 @@ export async function validateSpecAudit(auditPath, options = {}) {
360
222
  : null,
361
223
  };
362
224
 
363
- if (usesV2SurfaceModel) {
364
- if (!["mixed", "class_filtered"].includes(String(audit.generation_mode ?? ""))) {
365
- errors.push("spec generation audit generation_mode must be `mixed` or `class_filtered`");
366
- }
367
- } else if (String(audit.generation_mode ?? "") !== "mixed") {
368
- errors.push("spec generation audit generation_mode must be `mixed`");
225
+ if (String(audit.generation_mode ?? "") !== "class_filtered") {
226
+ errors.push("spec generation audit generation_mode must be class_filtered");
369
227
  }
370
-
371
- const canonicalRoot = specTreeModel.ok ? specTreeModel.canonicalRoot : specGenerationInputs.canonicalTargetRoot ?? ".nimi/spec";
372
- const declaredProfile = specTreeModel.ok ? specTreeModel.profile : "surface_taxonomy_v1";
228
+ const canonicalRoot = generationInputs.canonicalTargetRoot;
373
229
  if (String(audit.canonical_target_root ?? "") !== canonicalRoot) {
374
230
  errors.push(`spec generation audit canonical_target_root must be ${canonicalRoot}`);
375
231
  }
376
- if (String(audit.declared_profile ?? "") !== declaredProfile) {
377
- errors.push(`spec generation audit declared_profile must be ${declaredProfile}`);
232
+ if (String(audit.declared_profile ?? "") !== "surface_taxonomy_v2") {
233
+ errors.push("spec generation audit declared_profile must be surface_taxonomy_v2");
378
234
  }
379
- if (!isDeclaredInputsCompatibleWithConfig(declaredInputs, specGenerationInputs, blueprintReference)) {
380
- errors.push("spec generation audit input_roots must stay within the declared generation inputs and optional benchmark root");
235
+ if (!isDeclaredInputsCompatibleWithConfig(declaredInputs, generationInputs, blueprintReference)) {
236
+ errors.push("spec generation audit input_roots must match declared generation inputs and optional blueprint root");
381
237
  }
382
238
 
383
- const canonicalRootPath = path.resolve(projectRoot, canonicalRoot);
384
- const treeFiles = await collectTreeFiles(canonicalRootPath);
239
+ const treeFiles = await collectTreeFiles(path.resolve(projectRoot, canonicalRoot));
385
240
  if (treeFiles.length === 0) {
386
- return {
387
- ok: false,
388
- errors: [`missing spec tree root: ${canonicalRootPath}`],
389
- warnings,
390
- refusal: makeValidatorRefusal(
391
- VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
392
- "spec generation audit requires a present canonical spec tree",
393
- ),
394
- };
395
- }
396
-
397
- const { auditedFiles, classifications } = specTreeModel.ok
398
- ? classifyAuditCoveredFiles(treeFiles, specTreeModel)
399
- : {
400
- auditedFiles: treeFiles
401
- .filter((entry) => !entry.startsWith("_meta/"))
402
- .map((entry) => ({ path: entry, category: "surface_taxonomy_v1" })),
403
- classifications: { unexpected: [], conflicts: [] },
404
- };
405
- if (classifications.unexpected.length > 0) {
406
- errors.push(`spec tree contains unexpected files outside declared spec classes: ${classifications.unexpected.join(", ")}`);
407
- }
408
- if (classifications.conflicts.length > 0) {
409
- errors.push(`spec tree contains files matched to multiple classes: ${classifications.conflicts.map((entry) => `${entry.path} -> ${entry.classes.join("|")}`).join(", ")}`);
241
+ errors.push(`missing spec tree root: ${path.resolve(projectRoot, canonicalRoot)}`);
410
242
  }
411
-
243
+ const auditedFiles = treeFiles.filter((entry) => !entry.startsWith("_meta/"));
412
244
  const fileEntries = Array.isArray(audit.files) ? audit.files : [];
413
- if (!Array.isArray(audit.files)) {
414
- errors.push("spec generation audit files must be an array");
415
- }
245
+ if (!Array.isArray(audit.files)) errors.push("spec generation audit files must be an array");
246
+
416
247
  const fileEntryRefs = audit.file_entry_refs === undefined
417
248
  ? []
418
249
  : Array.isArray(audit.file_entry_refs)
@@ -424,39 +255,25 @@ export async function validateSpecAudit(auditPath, options = {}) {
424
255
 
425
256
  const referencedFileEntries = [];
426
257
  for (const entryRef of fileEntryRefs ?? []) {
427
- if (entryRef.length === 0 || path.isAbsolute(entryRef)) {
428
- errors.push(`spec generation audit file_entry_refs must be non-empty repository-relative paths: ${entryRef}`);
429
- continue;
430
- }
258
+ const expectedPrefix = ".nimi/local/state/spec-generation/spec-generation-audit/";
431
259
  const absoluteEntryRef = path.resolve(projectRoot, entryRef);
432
- const relativeEntryRef = path.relative(projectRoot, absoluteEntryRef).split(path.sep).join(path.posix.sep);
433
- const expectedPrefix = usesV2SurfaceModel
434
- ? ".nimi/local/state/spec-generation/spec-generation-audit/"
435
- : `${specTreeModel.canonicalRoot}/_meta/spec-generation-audit/`;
436
- if (relativeEntryRef !== entryRef || !relativeEntryRef.startsWith(expectedPrefix)) {
260
+ const relativeEntryRef = toPortableProjectPath(projectRoot, absoluteEntryRef);
261
+ if (!entryRef || path.isAbsolute(entryRef) || relativeEntryRef !== entryRef || !entryRef.startsWith(expectedPrefix)) {
437
262
  errors.push(`spec generation audit file_entry_ref must stay under ${expectedPrefix}: ${entryRef}`);
438
263
  continue;
439
264
  }
440
265
  const entryText = await readTextIfFile(absoluteEntryRef);
441
- if (entryText === null) {
442
- errors.push(`spec generation audit file_entry_ref is missing: ${entryRef}`);
443
- continue;
444
- }
445
266
  const entryParsed = parseYamlText(entryText);
446
267
  const entryPayload = entryParsed?.spec_generation_audit_file_entries;
447
- if (!isPlainObject(entryParsed) || !isPlainObject(entryPayload)) {
448
- errors.push(`spec generation audit file_entry_ref is not a valid entry shard: ${entryRef}`);
268
+ if (!isPlainObject(entryParsed) || !isPlainObject(entryPayload) || entryParsed.version !== 2) {
269
+ errors.push(`spec generation audit file_entry_ref is invalid: ${entryRef}`);
449
270
  continue;
450
271
  }
451
- const expectedShardVersion = usesV2SurfaceModel ? 2 : 1;
452
- if (entryParsed.version !== expectedShardVersion) {
453
- errors.push(`spec generation audit file_entry_ref version must be ${expectedShardVersion}: ${entryRef}`);
454
- }
455
272
  if (String(entryParsed.contract_ref ?? "") !== SPEC_GENERATION_AUDIT_CONTRACT_REF) {
456
- errors.push(`spec generation audit file_entry_ref contract_ref must be ${SPEC_GENERATION_AUDIT_CONTRACT_REF}: ${entryRef}`);
273
+ errors.push(`spec generation audit file_entry_ref contract_ref is invalid: ${entryRef}`);
457
274
  }
458
275
  if (String(entryPayload.parent_ref ?? "") !== toPortableProjectPath(projectRoot, absoluteAuditPath)) {
459
- errors.push(`spec generation audit file_entry_ref parent_ref must point to ${toPortableProjectPath(projectRoot, absoluteAuditPath)}: ${entryRef}`);
276
+ errors.push(`spec generation audit file_entry_ref parent_ref is invalid: ${entryRef}`);
460
277
  }
461
278
  if (!Array.isArray(entryPayload.files)) {
462
279
  errors.push(`spec generation audit file_entry_ref files must be an array: ${entryRef}`);
@@ -464,135 +281,79 @@ export async function validateSpecAudit(auditPath, options = {}) {
464
281
  }
465
282
  referencedFileEntries.push(...entryPayload.files);
466
283
  }
467
- const allFileEntries = [...fileEntries, ...referencedFileEntries];
468
284
 
469
285
  const auditEntryByRelativePath = new Map();
470
- for (const entry of allFileEntries) {
286
+ for (const entry of [...fileEntries, ...referencedFileEntries]) {
471
287
  if (!isPlainObject(entry)) {
472
288
  errors.push("spec generation audit file entries must be mappings");
473
289
  continue;
474
290
  }
475
-
476
- const requiredEntryFields = auditContract.requiredFileEntryFields?.length > 0
477
- ? auditContract.requiredFileEntryFields
478
- : SPEC_GENERATION_AUDIT_FILE_REQUIRED_FIELDS;
479
- const missingEntryFields = requiredEntryFields.filter((field) => !(field in entry));
480
- if (missingEntryFields.length > 0) {
481
- errors.push(`spec generation audit file entry is missing required fields: ${missingEntryFields.join(", ")}`);
291
+ const missingFields = auditContract.requiredFileEntryFields.filter((field) => !(field in entry));
292
+ if (missingFields.length > 0) {
293
+ errors.push(`spec generation audit file entry is missing required fields: ${missingFields.join(", ")}`);
482
294
  continue;
483
295
  }
484
-
485
296
  const canonicalPath = String(entry.canonical_path ?? "");
486
- if (!canonicalPath.startsWith(`${canonicalRoot}/`) && canonicalPath !== `${canonicalRoot}/INDEX.md`) {
297
+ if (!canonicalPath.startsWith(`${canonicalRoot}/`)) {
487
298
  errors.push(`spec generation audit canonical_path must stay under ${canonicalRoot}: ${canonicalPath}`);
488
299
  continue;
489
300
  }
490
-
491
301
  const relativePath = path.posix.relative(canonicalRoot, canonicalPath);
492
302
  if (relativePath.startsWith("_meta/")) {
493
- errors.push(`spec generation audit must not record _meta files as generated canonical files: ${canonicalPath}`);
303
+ errors.push(`spec generation audit must not record _meta files: ${canonicalPath}`);
494
304
  continue;
495
305
  }
496
-
497
306
  if (auditEntryByRelativePath.has(relativePath)) {
498
- errors.push(`duplicate spec generation audit entry for canonical path: ${canonicalPath}`);
307
+ errors.push(`duplicate spec generation audit entry: ${canonicalPath}`);
499
308
  continue;
500
309
  }
501
310
 
502
- if (!Array.isArray(entry.source_refs) || entry.source_refs.length === 0 || entry.source_refs.some((ref) => typeof ref !== "string" || ref.length === 0)) {
503
- errors.push(`spec generation audit source_refs must be a non-empty array for ${canonicalPath}`);
311
+ if (!Array.isArray(entry.source_refs) || entry.source_refs.length === 0) {
312
+ errors.push(`spec generation audit source_refs must be non-empty for ${canonicalPath}`);
504
313
  } else {
505
314
  const invalidRefs = entry.source_refs.filter((ref) => !isSourceRefWithinDeclaredRoots(ref, declaredInputs));
506
315
  if (invalidRefs.length > 0) {
507
316
  errors.push(`spec generation audit source_refs escape declared inputs for ${canonicalPath}: ${invalidRefs.join(", ")}`);
508
317
  }
509
318
  }
510
-
511
319
  if (!SPEC_GENERATION_AUDIT_SOURCE_BASIS_ENUM.includes(String(entry.source_basis ?? ""))) {
512
320
  errors.push(`spec generation audit source_basis is invalid for ${canonicalPath}`);
513
321
  }
514
-
515
322
  if (!SPEC_GENERATION_AUDIT_COVERAGE_STATUS_ENUM.includes(String(entry.coverage_status ?? ""))) {
516
323
  errors.push(`spec generation audit coverage_status is invalid for ${canonicalPath}`);
517
324
  }
518
-
519
325
  if (!Array.isArray(entry.unresolved_items) || entry.unresolved_items.some((item) => typeof item !== "string")) {
520
- errors.push(`spec generation audit unresolved_items must be an array of strings for ${canonicalPath}`);
326
+ errors.push(`spec generation audit unresolved_items must be strings for ${canonicalPath}`);
521
327
  }
522
-
523
- if (entry.notes !== undefined && (!Array.isArray(entry.notes) || entry.notes.some((item) => typeof item !== "string"))) {
524
- errors.push(`spec generation audit notes must be an array of strings for ${canonicalPath}`);
328
+ if ((entry.source_basis !== "grounded" || entry.coverage_status !== "complete")
329
+ && (!Array.isArray(entry.unresolved_items) || entry.unresolved_items.length === 0)) {
330
+ errors.push(`inferred or partial files must declare unresolved_items for ${canonicalPath}`);
525
331
  }
332
+ auditEntryByRelativePath.set(relativePath, entry);
333
+ }
526
334
 
527
- const requiresExplicitUnresolved = entry.source_basis !== "grounded" || entry.coverage_status !== "complete";
528
- if (requiresExplicitUnresolved && (!Array.isArray(entry.unresolved_items) || entry.unresolved_items.length === 0)) {
529
- errors.push(`spec generation audit inferred or partial files must declare unresolved_items for ${canonicalPath}`);
335
+ const missingAuditEntries = auditedFiles.filter((entry) => !auditEntryByRelativePath.has(entry));
336
+ if (missingAuditEntries.length > 0) {
337
+ errors.push(`spec generation audit is missing canonical files: ${missingAuditEntries.join(", ")}`);
338
+ }
339
+ for (const relativePath of auditEntryByRelativePath.keys()) {
340
+ if (!auditedFiles.includes(relativePath)) {
341
+ errors.push(`spec generation audit entry points to a non-existent canonical file: ${relativePath}`);
530
342
  }
531
-
532
- auditEntryByRelativePath.set(relativePath, {
533
- ...entry,
534
- relativePath,
535
- });
536
343
  }
537
344
 
538
- const missingAuditEntries = [];
539
- const requiredFileRefs = specTreeModel.ok
540
- ? (specTreeModel.requiredFilesByProfile[specTreeModel.profile] ?? [])
541
- : await loadV2RequiredFiles(projectRoot);
542
- const requiredFiles = requiredFileRefs
345
+ const requiredFiles = (await loadRequiredFiles(projectRoot))
543
346
  .map((entry) => path.posix.relative(canonicalRoot, entry))
544
347
  .filter((entry) => !entry.startsWith("_meta/"));
545
-
546
- for (const classifiedFile of auditedFiles) {
547
- const auditEntry = auditEntryByRelativePath.get(classifiedFile.path);
548
- if (!auditEntry) {
549
- missingAuditEntries.push(classifiedFile.path);
550
- continue;
551
- }
552
-
553
- const auditFileClass = normalizeAuditFileClass(auditEntry);
554
- if (classifiedFile.classId && auditFileClass !== classifiedFile.classId && !(classifiedFile.path === "INDEX.md" && auditFileClass === "index")) {
555
- errors.push(`spec generation audit file_class does not match canonical tree classification for ${classifiedFile.path}: expected ${classifiedFile.classId}`);
556
- }
557
- }
558
-
559
- if (missingAuditEntries.length > 0) {
560
- errors.push(`spec generation audit is missing file entries for canonical files: ${missingAuditEntries.join(", ")}`);
561
- }
562
-
563
348
  for (const requiredFile of requiredFiles) {
564
- const auditEntry = auditEntryByRelativePath.get(requiredFile);
565
- if (!auditEntry) {
566
- errors.push(`required canonical file is missing an audit entry: ${requiredFile}`);
567
- continue;
568
- }
569
- if (auditEntry.coverage_status === "placeholder_not_allowed") {
349
+ const entry = auditEntryByRelativePath.get(requiredFile);
350
+ if (!entry) errors.push(`required canonical file is missing an audit entry: ${requiredFile}`);
351
+ else if (entry.coverage_status === "placeholder_not_allowed") {
570
352
  errors.push(`required canonical file must not be placeholder_not_allowed: ${requiredFile}`);
571
353
  }
572
354
  }
573
355
 
574
- for (const [relativePath] of auditEntryByRelativePath) {
575
- if (!auditedFiles.some((entry) => entry.path === relativePath)) {
576
- errors.push(`spec generation audit entry points to a non-existent canonical file: ${relativePath}`);
577
- }
578
- }
579
-
580
- const unresolvedCount = Array.from(auditEntryByRelativePath.values())
581
- .filter((entry) => Array.isArray(entry.unresolved_items) && entry.unresolved_items.length > 0)
582
- .length;
583
- const inferredCount = Array.from(auditEntryByRelativePath.values())
584
- .filter((entry) => entry.source_basis === "inferred" || entry.source_basis === "mixed_grounded_and_inferred")
585
- .length;
586
- const partialCount = Array.from(auditEntryByRelativePath.values())
587
- .filter((entry) => entry.coverage_status === "partial")
588
- .length;
589
- const placeholderCount = Array.from(auditEntryByRelativePath.values())
590
- .filter((entry) => entry.coverage_status === "placeholder_not_allowed")
591
- .length;
592
- const completeCount = Array.from(auditEntryByRelativePath.values())
593
- .filter((entry) => entry.coverage_status === "complete")
594
- .length;
595
-
356
+ const values = [...auditEntryByRelativePath.values()];
596
357
  return {
597
358
  ok: errors.length === 0,
598
359
  errors,
@@ -605,15 +366,15 @@ export async function validateSpecAudit(auditPath, options = {}) {
605
366
  ),
606
367
  summary: {
607
368
  canonicalRoot,
608
- declaredProfile,
609
- auditedFiles: auditEntryByRelativePath.size,
369
+ declaredProfile: "surface_taxonomy_v2",
370
+ auditedFiles: values.length,
610
371
  requiredAuditedFiles: requiredFiles.length,
611
372
  missingAuditEntries,
612
- completeFiles: completeCount,
613
- partialFiles: partialCount,
614
- placeholderFiles: placeholderCount,
615
- unresolvedFiles: unresolvedCount,
616
- inferredFiles: inferredCount,
373
+ completeFiles: values.filter((entry) => entry.coverage_status === "complete").length,
374
+ partialFiles: values.filter((entry) => entry.coverage_status === "partial").length,
375
+ placeholderFiles: values.filter((entry) => entry.coverage_status === "placeholder_not_allowed").length,
376
+ unresolvedFiles: values.filter((entry) => entry.unresolved_items?.length > 0).length,
377
+ inferredFiles: values.filter((entry) => entry.source_basis !== "grounded").length,
617
378
  },
618
379
  };
619
380
  }