@nimiplatform/nimi-coding 0.1.0 → 0.2.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 (121) hide show
  1. package/README.md +19 -20
  2. package/adapters/oh-my-codex/README.md +8 -9
  3. package/cli/commands/audit-sweep.mjs +10 -10
  4. package/cli/commands/classify-spec-tree.mjs +5 -0
  5. package/cli/commands/closeout.mjs +3 -0
  6. package/cli/commands/generate-spec-derived-docs.mjs +20 -0
  7. package/cli/commands/generate-spec-migration-plan.mjs +30 -0
  8. package/cli/commands/start.mjs +5 -1
  9. package/cli/commands/surface-validator-command.mjs +49 -0
  10. package/cli/commands/sweep-design.mjs +295 -0
  11. package/cli/commands/sweep.mjs +22 -0
  12. package/cli/commands/sync.mjs +132 -0
  13. package/cli/commands/topic-formatters.mjs +8 -8
  14. package/cli/commands/validate-ai-governance.mjs +167 -46
  15. package/cli/commands/validate-domain-admission.mjs +5 -0
  16. package/cli/commands/validate-guidance-bodies.mjs +5 -0
  17. package/cli/commands/validate-placement.mjs +5 -0
  18. package/cli/commands/validate-projection-edges.mjs +5 -0
  19. package/cli/commands/validate-spec-audit.mjs +5 -1
  20. package/cli/commands/validate-table-family.mjs +5 -0
  21. package/cli/commands/validate-tracked-output-admission.mjs +5 -0
  22. package/cli/constants.mjs +5 -49
  23. package/cli/help.mjs +33 -11
  24. package/cli/index.mjs +20 -2
  25. package/cli/lib/audit-sweep-runtime/admissions.mjs +38 -29
  26. package/cli/lib/audit-sweep-runtime/audit-validity.mjs +8 -0
  27. package/cli/lib/audit-sweep-runtime/chunks.mjs +11 -11
  28. package/cli/lib/audit-sweep-runtime/closeout.mjs +8 -8
  29. package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +3 -3
  30. package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +10 -10
  31. package/cli/lib/audit-sweep-runtime/common.mjs +7 -7
  32. package/cli/lib/audit-sweep-runtime/format.mjs +3 -3
  33. package/cli/lib/audit-sweep-runtime/ingest.mjs +8 -8
  34. package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +24 -27
  35. package/cli/lib/audit-sweep-runtime/inventory.mjs +58 -18
  36. package/cli/lib/audit-sweep-runtime/ledger.mjs +1 -1
  37. package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +2 -2
  38. package/cli/lib/audit-sweep-runtime/remediation.mjs +6 -6
  39. package/cli/lib/audit-sweep-runtime/rerun.mjs +6 -6
  40. package/cli/lib/audit-sweep-runtime/status.mjs +1 -1
  41. package/cli/lib/audit-sweep-runtime/validators.mjs +2 -2
  42. package/cli/lib/authority-convergence.mjs +397 -2
  43. package/cli/lib/blueprint-audit.mjs +5 -5
  44. package/cli/lib/closeout.mjs +126 -3
  45. package/cli/lib/contracts.mjs +21 -17
  46. package/cli/lib/handoff.mjs +29 -11
  47. package/cli/lib/high-risk-admission.mjs +60 -11
  48. package/cli/lib/high-risk-decision.mjs +31 -2
  49. package/cli/lib/high-risk-ingest.mjs +5 -1
  50. package/cli/lib/high-risk-review.mjs +5 -1
  51. package/cli/lib/internal/contracts-parse.mjs +195 -24
  52. package/cli/lib/internal/contracts-validators.mjs +3 -2
  53. package/cli/lib/internal/doctor-bootstrap-surface.mjs +82 -35
  54. package/cli/lib/internal/doctor-delegated-surface.mjs +1 -1
  55. package/cli/lib/internal/doctor-finalize.mjs +12 -8
  56. package/cli/lib/internal/doctor-inspectors.mjs +34 -1
  57. package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +74 -12
  58. package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +24 -6
  59. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +18 -23
  60. package/cli/lib/internal/surface-taxonomy-validators.mjs +931 -0
  61. package/cli/lib/internal/validators-spec.mjs +229 -20
  62. package/cli/lib/sweep-design-runtime/common.mjs +246 -0
  63. package/cli/lib/sweep-design-runtime/engine.mjs +733 -0
  64. package/cli/lib/sweep-design-runtime/fix-topic.mjs +414 -0
  65. package/cli/lib/sweep-design-runtime/lifecycle.mjs +54 -0
  66. package/cli/lib/sweep-design-runtime/results.mjs +324 -0
  67. package/cli/lib/sweep-design.mjs +8 -0
  68. package/cli/lib/sync.mjs +143 -0
  69. package/cli/lib/topic-artifacts.mjs +186 -0
  70. package/cli/lib/topic-authority-coverage.mjs +73 -0
  71. package/cli/lib/topic-closeout.mjs +560 -0
  72. package/cli/lib/topic-common.mjs +404 -0
  73. package/cli/lib/topic-decisions.mjs +332 -0
  74. package/cli/lib/topic-draft-packets.mjs +126 -7
  75. package/cli/lib/topic-execution.mjs +515 -0
  76. package/cli/lib/topic-goal.mjs +112 -33
  77. package/cli/lib/topic-ledger.mjs +281 -0
  78. package/cli/lib/topic-lifecycle-artifacts.mjs +173 -0
  79. package/cli/lib/topic-root-validation.mjs +288 -0
  80. package/cli/lib/topic-runner-commands.mjs +174 -0
  81. package/cli/lib/topic-runner-deferral.mjs +532 -0
  82. package/cli/lib/topic-runner-stale-gates.mjs +114 -0
  83. package/cli/lib/topic-runner-validation.mjs +138 -0
  84. package/cli/lib/topic-runner.mjs +109 -154
  85. package/cli/lib/topic-scaffold.mjs +252 -0
  86. package/cli/lib/topic-waves.mjs +403 -0
  87. package/cli/lib/topic.mjs +81 -93
  88. package/cli/lib/value-helpers.mjs +6 -1
  89. package/cli/seeds/bootstrap.mjs +96 -20
  90. package/cli/seeds/seed-policy.yaml +67 -0
  91. package/config/bootstrap.yaml +1 -1
  92. package/config/skill-manifest.yaml +4 -2
  93. package/config/spec-generation-inputs.yaml +41 -19
  94. package/contracts/audit-remediation-map.schema.yaml +1 -0
  95. package/contracts/audit-sweep-result.yaml +4 -0
  96. package/contracts/domain-admission.schema.yaml +56 -0
  97. package/contracts/migration-inventory.schema.yaml +80 -0
  98. package/contracts/negative-fixtures.yaml +91 -0
  99. package/contracts/placement-contract.schema.yaml +163 -0
  100. package/contracts/projection-edge.schema.yaml +130 -0
  101. package/contracts/shared-enums.yaml +68 -0
  102. package/contracts/spec-generation-audit.schema.yaml +19 -4
  103. package/contracts/spec-generation-inputs.schema.yaml +130 -29
  104. package/contracts/spec-reconstruction-result.yaml +9 -5
  105. package/contracts/surface-taxonomy.schema.yaml +201 -0
  106. package/contracts/sweep-design-result.yaml +349 -0
  107. package/contracts/table-family.schema.yaml +114 -0
  108. package/contracts/topic-goal.schema.yaml +10 -1
  109. package/contracts/tracked-output-admission.schema.yaml +70 -0
  110. package/contracts/workflow-consumer.schema.yaml +112 -0
  111. package/methodology/audit-sweep-p0p1-recall.yaml +1 -1
  112. package/methodology/spec-reconstruction.yaml +53 -30
  113. package/package.json +5 -4
  114. package/spec/_meta/command-gating-matrix.yaml +33 -0
  115. package/spec/_meta/generate-drift-migration-checklist.yaml +44 -62
  116. package/spec/_meta/governance-routing-cutover-checklist.yaml +3 -3
  117. package/spec/_meta/phase2-impacted-surface-matrix.yaml +14 -14
  118. package/spec/_meta/spec-authority-cutover-readiness.yaml +3 -5
  119. package/spec/_meta/spec-tree-model.yaml +104 -36
  120. package/spec/bootstrap-state.yaml +36 -36
  121. package/spec/product-scope.yaml +13 -10
@@ -27,22 +27,24 @@ export {
27
27
  parseHighRiskSchemaContract,
28
28
  } from "./contracts-parse-high-risk.mjs";
29
29
 
30
- const SPEC_TREE_PROFILE_ENUM = ["minimal", "standard", "mature"];
30
+ const SPEC_TREE_PROFILE_ENUM = ["minimal", "standard", "mature", "surface_taxonomy_v1"];
31
31
  const AUTHORITY_MODE_ENUM = [
32
32
  "external_authority_active",
33
33
  "external_blueprint_active",
34
34
  "canonical_cutover_ready",
35
35
  "canonical_active",
36
+ "surface_class_validated",
36
37
  ];
37
38
  const BLUEPRINT_MODE_ENUM = [
38
39
  "none",
39
40
  "repo_spec_blueprint",
40
41
  "custom_blueprint",
41
42
  ];
42
- const SPEC_GENERATION_MODE_ENUM = ["mixed"];
43
+ const SPEC_GENERATION_MODE_ENUM = ["mixed", "class_filtered"];
43
44
  const SPEC_GENERATION_ACCEPTANCE_MODE_ENUM = [
44
45
  "canonical_tree_validity_without_blueprint",
45
46
  "semantic_and_structural_parity_when_blueprint_exists",
47
+ "placement_validity_before_generation",
46
48
  ];
47
49
  const SPEC_GENERATION_ORDER_ENUM = [
48
50
  "index",
@@ -50,6 +52,12 @@ const SPEC_GENERATION_ORDER_ENUM = [
50
52
  "kernel_tables",
51
53
  "generated_views",
52
54
  "thin_guides",
55
+ "classify_inputs",
56
+ "validate_placement",
57
+ "product_authority",
58
+ "product_authority_tables",
59
+ "derived_views",
60
+ "generation_audit",
53
61
  ];
54
62
  const TOPIC_LIFECYCLE_REPORT_MARKDOWN = /^\.nimi\/topics\/(?:proposal|ongoing|pending|closed)\/\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*\/[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*\.md$/;
55
63
 
@@ -61,6 +69,13 @@ function toStringOrNull(value) {
61
69
  return typeof value === "string" ? value : null;
62
70
  }
63
71
 
72
+ function toStringList(value) {
73
+ if (typeof value === "string") {
74
+ return [value];
75
+ }
76
+ return toStringArray(value);
77
+ }
78
+
64
79
  function normalizePathClass(entry) {
65
80
  if (!isPlainObject(entry)) {
66
81
  return null;
@@ -100,13 +115,48 @@ function normalizeGeneratedPipeline(entry) {
100
115
  return {
101
116
  id: toStringOrNull(entry.id),
102
117
  ownerSurface: toStringOrNull(entry.owner_surface),
103
- inputRoots: toStringArray(entry.input_roots),
118
+ inputRoots: toStringArray(entry.input_roots).length > 0
119
+ ? toStringArray(entry.input_roots)
120
+ : [
121
+ ...toStringList(entry.input_contract_ref),
122
+ ...toStringList(entry.input_config_ref),
123
+ ],
104
124
  outputRoots: toStringArray(entry.output_roots),
105
125
  generateCommand: toStringOrNull(entry.generate_command),
106
126
  driftCheckCommand: toStringOrNull(entry.drift_check_command),
107
127
  };
108
128
  }
109
129
 
130
+ function normalizeV2SpecDomain(entry) {
131
+ if (!isPlainObject(entry)) {
132
+ return null;
133
+ }
134
+ const root = toStringOrNull(entry.root);
135
+ return {
136
+ id: toStringOrNull(entry.id),
137
+ root,
138
+ normativeRoot: root ? `${root}/kernel` : null,
139
+ tablesRoot: root ? `${root}/kernel/tables` : null,
140
+ generatedRoot: null,
141
+ guidePaths: [],
142
+ };
143
+ }
144
+
145
+ function normalizeV2PathClass(entry) {
146
+ if (!isPlainObject(entry)) {
147
+ return null;
148
+ }
149
+ return {
150
+ id: toStringOrNull(entry.id),
151
+ pathPatterns: toStringArray(entry.path_patterns),
152
+ excludedPathPatterns: toStringArray(entry.excluded_path_patterns),
153
+ allowedExtensions: toStringArray(entry.allowed_extensions),
154
+ generatorRefs: [],
155
+ mustReferenceNormativeIds: entry.must_reference_product_authority_ids === true,
156
+ normative: ["product_authority", "product_authority_table"].includes(entry.id),
157
+ };
158
+ }
159
+
110
160
  function pathStartsWithRoot(targetPath, root) {
111
161
  if (typeof targetPath !== "string" || typeof root !== "string") {
112
162
  return false;
@@ -140,8 +190,19 @@ function topicLifecycleMarkdownPathsAreSortable(paths) {
140
190
  });
141
191
  }
142
192
 
193
+ function isAllowedV2AuthorityDocsRoot(root) {
194
+ if (typeof root !== "string" || root.length === 0) {
195
+ return false;
196
+ }
197
+ if (root === "." || root === "./" || root === "/" || root === "**" || root.includes("..")) {
198
+ return false;
199
+ }
200
+ return root === ".nimi/spec" || root.startsWith(".nimi/spec/");
201
+ }
202
+
143
203
  export function parseSpecReconstructionContract(text) {
144
204
  const parsed = parseYamlText(text);
205
+ const version = parsed?.version ?? null;
145
206
  const summaryRequiredFields = toStringArray(parsed?.summary_required_fields);
146
207
  const summaryStatusEnum = toStringArray(parsed?.summary_status_enum);
147
208
  const completionRequirements = toStringArray(parsed?.completion_requirements);
@@ -156,20 +217,35 @@ export function parseSpecReconstructionContract(text) {
156
217
  }
157
218
  : null;
158
219
 
220
+ const expectedSummaryFields = version === 2
221
+ ? [...SPEC_RECONSTRUCTION_SUMMARY_REQUIRED_FIELDS.slice(0, 2), "placement_report_ref", ...SPEC_RECONSTRUCTION_SUMMARY_REQUIRED_FIELDS.slice(2)]
222
+ : SPEC_RECONSTRUCTION_SUMMARY_REQUIRED_FIELDS;
223
+ const expectedProfileRef = version === 2 ? ".nimi/contracts/surface-taxonomy.schema.yaml" : SPEC_TREE_MODEL_REF;
224
+ const expectedInputsRef = version === 2 ? ".nimi/config/spec-generation-inputs.yaml" : SPEC_GENERATION_INPUTS_REF;
225
+ const expectedAuditContractRef = version === 2 ? ".nimi/contracts/spec-generation-audit.schema.yaml" : SPEC_GENERATION_AUDIT_CONTRACT_REF;
226
+ const expectedAuditRef = version === 2 ? ".nimi/local/state/spec-generation/spec-generation-audit.yaml" : SPEC_GENERATION_AUDIT_REF;
227
+ const fileClassRequirement = version === 2
228
+ ? "declared_surface_class_constraints_valid"
229
+ : "declared_file_class_constraints_valid";
230
+ const auditRequirement = version === 2
231
+ ? "spec_generation_audit_present_and_valid_as_local_state"
232
+ : "spec_generation_audit_present_and_valid";
233
+
159
234
  return {
160
- ok: arraysEqual(summaryRequiredFields, SPEC_RECONSTRUCTION_SUMMARY_REQUIRED_FIELDS)
235
+ ok: [1, 2].includes(version)
236
+ && arraysEqual(summaryRequiredFields, expectedSummaryFields)
161
237
  && arraysEqual(summaryStatusEnum, SPEC_RECONSTRUCTION_SUMMARY_STATUS)
162
238
  && completionRequirements.includes("canonical_tree_ready")
163
239
  && completionRequirements.includes("declared_profile_required_files_valid")
164
- && completionRequirements.includes("declared_file_class_constraints_valid")
165
- && completionRequirements.includes("spec_generation_audit_present_and_valid")
240
+ && completionRequirements.includes(fileClassRequirement)
241
+ && completionRequirements.includes(auditRequirement)
166
242
  && completionRequirements.includes("required_canonical_files_have_matching_audit_entries")
167
243
  && completionRequirements.includes("unresolved_gaps_must_remain_explicit")
168
244
  && completionRequirements.includes("semantic_and_structural_parity_when_blueprint_exists")
169
- && canonicalTreeCompletion?.profileRef === SPEC_TREE_MODEL_REF
170
- && canonicalTreeCompletion?.generationInputsRef === SPEC_GENERATION_INPUTS_REF
171
- && canonicalTreeCompletion?.auditContractRef === SPEC_GENERATION_AUDIT_CONTRACT_REF
172
- && canonicalTreeCompletion?.auditRef === SPEC_GENERATION_AUDIT_REF
245
+ && canonicalTreeCompletion?.profileRef === expectedProfileRef
246
+ && canonicalTreeCompletion?.generationInputsRef === expectedInputsRef
247
+ && canonicalTreeCompletion?.auditContractRef === expectedAuditContractRef
248
+ && canonicalTreeCompletion?.auditRef === expectedAuditRef
173
249
  && canonicalTreeCompletion?.requiredTreeState === "canonical_tree_ready"
174
250
  && canonicalTreeCompletion?.requiredFilesValid === true,
175
251
  canonicalTreeCompletion,
@@ -181,24 +257,36 @@ export function parseSpecReconstructionContract(text) {
181
257
 
182
258
  export function parseSpecGenerationAuditContract(text) {
183
259
  const parsed = parseYamlText(text);
260
+ const version = parsed?.version ?? null;
184
261
  const requiredTopLevelFields = toStringArray(parsed?.required_top_level_fields);
185
262
  const requiredFileEntryFields = toStringArray(parsed?.required_file_entry_fields);
186
263
  const sourceBasisEnum = toStringArray(parsed?.source_basis_enum);
187
264
  const coverageStatusEnum = toStringArray(parsed?.coverage_status_enum);
188
265
  const hardConstraints = toStringArray(parsed?.hard_constraints);
189
266
 
267
+ const expectedTopLevelFields = version === 2
268
+ ? [...SPEC_GENERATION_AUDIT_REQUIRED_TOP_LEVEL_FIELDS.slice(0, 4), "placement_report_ref", ...SPEC_GENERATION_AUDIT_REQUIRED_TOP_LEVEL_FIELDS.slice(4)]
269
+ : SPEC_GENERATION_AUDIT_REQUIRED_TOP_LEVEL_FIELDS;
270
+ const expectedFileEntryFields = version === 2
271
+ ? ["canonical_path", "surface_class", "source_refs", "source_basis", "coverage_status", "unresolved_items"]
272
+ : SPEC_GENERATION_AUDIT_FILE_REQUIRED_FIELDS;
273
+ const expectedTargetRef = version === 2 ? ".nimi/local/state/spec-generation/spec-generation-audit.yaml" : SPEC_GENERATION_AUDIT_REF;
274
+ const sourceConstraint = version === 2
275
+ ? "source_refs_must_stay_within_class_filtered_declared_inputs"
276
+ : "source_refs_must_stay_within_declared_inputs_or_optional_benchmark_root";
277
+
190
278
  return {
191
- ok: parsed?.version === 1
279
+ ok: [1, 2].includes(version)
192
280
  && String(parsed?.audit_contract?.id ?? "") === "canonical_spec_generation_audit"
193
- && String(parsed?.audit_contract?.target_ref ?? "") === SPEC_GENERATION_AUDIT_REF
194
- && arraysEqual(requiredTopLevelFields, SPEC_GENERATION_AUDIT_REQUIRED_TOP_LEVEL_FIELDS)
195
- && arraysEqual(requiredFileEntryFields, SPEC_GENERATION_AUDIT_FILE_REQUIRED_FIELDS)
281
+ && String(parsed?.audit_contract?.target_ref ?? "") === expectedTargetRef
282
+ && arraysEqual(requiredTopLevelFields, expectedTopLevelFields)
283
+ && arraysEqual(requiredFileEntryFields, expectedFileEntryFields)
196
284
  && arraysEqual(sourceBasisEnum, SPEC_GENERATION_AUDIT_SOURCE_BASIS_ENUM)
197
285
  && arraysEqual(coverageStatusEnum, SPEC_GENERATION_AUDIT_COVERAGE_STATUS_ENUM)
198
286
  && hardConstraints.includes("every_generated_canonical_file_requires_a_matching_audit_entry")
199
287
  && hardConstraints.includes("required_canonical_files_must_not_be_placeholder_not_allowed")
200
288
  && hardConstraints.includes("unresolved_or_inferred_content_must_be_explicit")
201
- && hardConstraints.includes("source_refs_must_stay_within_declared_inputs_or_optional_benchmark_root")
289
+ && hardConstraints.includes(sourceConstraint)
202
290
  && hardConstraints.includes("no_empty_success_looking_audit_entries"),
203
291
  requiredTopLevelFields,
204
292
  requiredFileEntryFields,
@@ -215,6 +303,26 @@ export function parseSpecGenerationInputsContract(text) {
215
303
  const generationOrderEnum = toStringArray(parsed?.generation_order_enum);
216
304
  const hardConstraints = toStringArray(parsed?.hard_constraints);
217
305
 
306
+ if (parsed?.version === 2) {
307
+ return {
308
+ ok: String(contract?.id ?? "") === "canonical_spec_generation_inputs"
309
+ && String(contract?.owner ?? "") === "nimi-coding"
310
+ && requiredFields.includes("mode")
311
+ && requiredFields.includes("canonical_target_root")
312
+ && requiredFields.includes("docs_inputs")
313
+ && requiredFields.includes("forbidden_source_classes")
314
+ && arraysEqual(toStringArray(parsed?.mode_enum), ["class_filtered"])
315
+ && SPEC_GENERATION_ACCEPTANCE_MODE_ENUM.includes(toStringArray(parsed?.acceptance_mode_enum)[0])
316
+ && generationOrderEnum.includes("classify_inputs")
317
+ && generationOrderEnum.includes("validate_placement")
318
+ && hardConstraints.includes("docs_roots_blanket_ingestion_is_forbidden")
319
+ && hardConstraints.includes("each_input_file_must_have_an_accepted_surface_class_before_render"),
320
+ requiredFields,
321
+ generationOrderEnum,
322
+ hardConstraints,
323
+ };
324
+ }
325
+
218
326
  return {
219
327
  ok: parsed?.version === 1
220
328
  && String(contract?.id ?? "") === "canonical_spec_generation_inputs"
@@ -242,6 +350,12 @@ export function parseSpecGenerationInputsConfig(text) {
242
350
  const codeRoots = toStringArray(config?.code_roots);
243
351
  const docsRoots = toStringArray(config?.docs_roots);
244
352
  const structureRoots = toStringArray(config?.structure_roots);
353
+ const codeInputs = Array.isArray(config?.code_inputs) ? config.code_inputs : [];
354
+ const docsInputs = Array.isArray(config?.docs_inputs) ? config.docs_inputs : [];
355
+ const structureInputs = Array.isArray(config?.structure_inputs) ? config.structure_inputs : [];
356
+ const topicInputs = Array.isArray(config?.topic_inputs) ? config.topic_inputs : [];
357
+ const localInputs = Array.isArray(config?.local_inputs) ? config.local_inputs : [];
358
+ const forbiddenSourceClasses = toStringArray(config?.forbidden_source_classes);
245
359
  const humanNotePaths = toStringArray(config?.human_note_paths);
246
360
  const benchmarkBlueprintRoot = typeof config?.benchmark_blueprint_root === "string"
247
361
  ? config.benchmark_blueprint_root
@@ -251,6 +365,46 @@ export function parseSpecGenerationInputsConfig(text) {
251
365
  const generationOrder = toStringArray(config?.generation_order);
252
366
  const inferenceRules = toStringArray(config?.inference_rules);
253
367
 
368
+ if (parsed?.version === 2) {
369
+ const legacyRootsAbsent = !("code_roots" in config)
370
+ && !("docs_roots" in config)
371
+ && !("structure_roots" in config);
372
+ return {
373
+ ok: String(parsed?.contract_ref ?? "") === ".nimi/contracts/spec-generation-inputs.schema.yaml"
374
+ && mode === "class_filtered"
375
+ && canonicalTargetRoot === ".nimi/spec"
376
+ && legacyRootsAbsent
377
+ && Array.isArray(codeInputs)
378
+ && docsInputs.length > 0
379
+ && docsInputs.every((entry) => isPlainObject(entry)
380
+ && typeof entry.root === "string"
381
+ && isAllowedV2AuthorityDocsRoot(entry.root)
382
+ && Array.isArray(entry.allowed_surface_classes)
383
+ && Array.isArray(entry.forbidden_surface_classes)
384
+ && entry.use_as_authority === true)
385
+ && Array.isArray(structureInputs)
386
+ && Array.isArray(topicInputs)
387
+ && Array.isArray(localInputs)
388
+ && forbiddenSourceClasses.length > 0
389
+ && generationOrder.includes("classify_inputs")
390
+ && generationOrder.includes("validate_placement")
391
+ && inferenceRules.includes("no_blanket_docs_roots")
392
+ && SPEC_GENERATION_ACCEPTANCE_MODE_ENUM.includes(acceptanceMode)
393
+ && topicLifecycleMarkdownPathsAreSortable(humanNotePaths),
394
+ mode,
395
+ canonicalTargetRoot,
396
+ codeRoots: codeInputs.map((entry) => String(entry.root ?? "")).filter(Boolean),
397
+ docsRoots: docsInputs.map((entry) => String(entry.root ?? "")).filter(Boolean),
398
+ structureRoots: structureInputs.map((entry) => String(entry.root ?? "")).filter(Boolean),
399
+ humanNotePaths,
400
+ benchmarkBlueprintRoot,
401
+ benchmarkMode: benchmarkMode ?? "none",
402
+ acceptanceMode,
403
+ generationOrder,
404
+ inferenceRules,
405
+ };
406
+ }
407
+
254
408
  return {
255
409
  ok: parsed?.version === 1
256
410
  && String(parsed?.contract_ref ?? "") === SPEC_GENERATION_INPUTS_CONTRACT_REF
@@ -289,24 +443,40 @@ export function parseSpecTreeModel(text) {
289
443
  const parsed = parseYamlText(text);
290
444
  const model = parsed?.spec_tree_model;
291
445
  const profile = toStringOrNull(model?.profile);
292
- const canonicalRoot = toStringOrNull(model?.canonical_root);
446
+ const canonicalRoot = toStringOrNull(model?.canonical_root) ?? toStringOrNull(model?.product_authority_root);
293
447
  const authorityMode = normalizeAuthorityModeValue(toStringOrNull(model?.authority_mode));
448
+ const isV2 = parsed?.version === 2;
294
449
  const domains = Array.isArray(model?.domains)
295
- ? model.domains.map(normalizeSpecDomain).filter(Boolean)
450
+ ? model.domains.map(isV2 ? normalizeV2SpecDomain : normalizeSpecDomain).filter(Boolean)
296
451
  : [];
297
- const normativeClasses = Array.isArray(model?.normative_classes)
298
- ? model.normative_classes.map(normalizePathClass).filter(Boolean)
452
+ const normativeClassSource = isV2 ? model?.product_authority_classes : model?.normative_classes;
453
+ const derivedClassSource = isV2
454
+ ? (Array.isArray(model?.non_authority_classes) ? model.non_authority_classes.filter((entry) => entry?.id === "derived_view") : [])
455
+ : model?.derived_classes;
456
+ const normativeClasses = Array.isArray(normativeClassSource)
457
+ ? normativeClassSource.map(isV2 ? normalizeV2PathClass : normalizePathClass).filter(Boolean)
299
458
  : [];
300
- const derivedClasses = Array.isArray(model?.derived_classes)
301
- ? model.derived_classes.map(normalizePathClass).filter(Boolean)
459
+ const derivedClasses = Array.isArray(derivedClassSource)
460
+ ? derivedClassSource.map(isV2 ? (entry) => ({
461
+ id: toStringOrNull(entry.id),
462
+ pathPatterns: toStringArray(entry.allowed_roots),
463
+ excludedPathPatterns: [],
464
+ allowedExtensions: [],
465
+ generatorRefs: [],
466
+ mustReferenceNormativeIds: false,
467
+ normative: false,
468
+ }) : normalizePathClass).filter(Boolean)
302
469
  : [];
303
470
  const guidanceClasses = Array.isArray(model?.guidance_classes)
304
- ? model.guidance_classes.map(normalizePathClass).filter(Boolean)
471
+ ? model.guidance_classes.map(isV2 ? normalizeV2PathClass : normalizePathClass).filter(Boolean)
305
472
  : [];
306
473
  const requiredFilesByProfile = SPEC_TREE_PROFILE_ENUM.reduce((acc, currentProfile) => {
307
474
  acc[currentProfile] = toStringArray(model?.required_files?.[currentProfile]);
308
475
  return acc;
309
476
  }, {});
477
+ if (isV2 && requiredFilesByProfile.surface_taxonomy_v1.length === 0) {
478
+ requiredFilesByProfile.surface_taxonomy_v1 = requiredFilesByProfile.minimal;
479
+ }
310
480
  const generatedPipelines = Array.isArray(model?.generated_pipelines)
311
481
  ? model.generated_pipelines.map(normalizeGeneratedPipeline).filter(Boolean)
312
482
  : [];
@@ -333,7 +503,8 @@ export function parseSpecTreeModel(text) {
333
503
  && (!domain.generatedRoot || pathStartsWithRoot(domain.generatedRoot, domain.normativeRoot))
334
504
  && domain.guidePaths.every((guidePath) => pathStartsWithRoot(guidePath, domain.root))
335
505
  ));
336
- const requiredFilesValid = SPEC_TREE_PROFILE_ENUM.every((currentProfile) => (
506
+ const profilesToCheck = isV2 ? [profile] : SPEC_TREE_PROFILE_ENUM.filter((entry) => entry !== "surface_taxonomy_v1");
507
+ const requiredFilesValid = profilesToCheck.every((currentProfile) => (
337
508
  requiredFilesByProfile[currentProfile].length > 0
338
509
  && requiredFilesByProfile[currentProfile].every((requiredPath) => pathStartsWithRoot(requiredPath, canonicalRoot))
339
510
  ));
@@ -356,7 +527,7 @@ export function parseSpecTreeModel(text) {
356
527
  );
357
528
 
358
529
  return {
359
- ok: parsed?.version === 1
530
+ ok: [1, 2].includes(parsed?.version)
360
531
  && profileValid
361
532
  && canonicalRootValid
362
533
  && authorityModeValid
@@ -91,10 +91,11 @@ export function validateSpecReconstructionSummary(summary, contract, verifiedAt)
91
91
  };
92
92
  }
93
93
 
94
- if (typeof summary.audit_ref !== "string" || summary.audit_ref !== SPEC_GENERATION_AUDIT_REF) {
94
+ const expectedAuditRef = contract.canonicalTreeCompletion?.auditRef ?? SPEC_GENERATION_AUDIT_REF;
95
+ if (typeof summary.audit_ref !== "string" || summary.audit_ref !== expectedAuditRef) {
95
96
  return {
96
97
  ok: false,
97
- reason: `spec_reconstruction summary.audit_ref must be \`${SPEC_GENERATION_AUDIT_REF}\``,
98
+ reason: `spec_reconstruction summary.audit_ref must be \`${expectedAuditRef}\``,
98
99
  };
99
100
  }
100
101
 
@@ -5,7 +5,6 @@ import {
5
5
  COMMAND_GATING_MATRIX_REF,
6
6
  LOCAL_GITIGNORE_ENTRIES,
7
7
  PACKAGE_NAME,
8
- REQUIRED_BOOTSTRAP_FILES,
9
8
  REQUIRED_LOCAL_DIRS,
10
9
  SPEC_GENERATION_AUDIT_CONTRACT_REF,
11
10
  SPEC_GENERATION_AUDIT_REF,
@@ -13,6 +12,7 @@ import {
13
12
  SPEC_GENERATION_INPUTS_REF,
14
13
  SPEC_TREE_MODEL_REF,
15
14
  } from "../../constants.mjs";
15
+ import { getBootstrapSeedEntries } from "../../seeds/bootstrap.mjs";
16
16
  import { inspectBootstrapCompatibility } from "../bootstrap.mjs";
17
17
  import {
18
18
  findCommandGatingRule,
@@ -68,11 +68,22 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
68
68
 
69
69
  checks.push(buildCheck("nimi_root", true, ".nimi directory exists"));
70
70
 
71
+ const seedEntries = await getBootstrapSeedEntries();
71
72
  const missingBootstrapFiles = [];
72
- for (const relativePath of REQUIRED_BOOTSTRAP_FILES) {
73
- const info = await pathExists(path.join(projectRoot, relativePath));
73
+ const driftedPackageCanonicalFiles = [];
74
+ for (const entry of seedEntries) {
75
+ const absolutePath = path.join(projectRoot, entry.outputRelativePath);
76
+ const info = await pathExists(absolutePath);
74
77
  if (!info || !info.isFile()) {
75
- missingBootstrapFiles.push(relativePath);
78
+ missingBootstrapFiles.push(entry.outputRelativePath);
79
+ continue;
80
+ }
81
+ if (entry.ownership !== "package_canonical") {
82
+ continue;
83
+ }
84
+ const actual = await readTextIfFile(absolutePath);
85
+ if (actual !== entry.content) {
86
+ driftedPackageCanonicalFiles.push(entry.outputRelativePath);
76
87
  }
77
88
  }
78
89
  checks.push(
@@ -80,10 +91,18 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
80
91
  "bootstrap_seed_files",
81
92
  missingBootstrapFiles.length === 0,
82
93
  missingBootstrapFiles.length === 0
83
- ? `All required bootstrap seed files are present (${REQUIRED_BOOTSTRAP_FILES.length}/${REQUIRED_BOOTSTRAP_FILES.length})`
84
- : `Missing required bootstrap seed files: ${missingBootstrapFiles.join(", ")}`,
94
+ ? `All bootstrap seed files declared by the seed projection policy are present (${seedEntries.length}/${seedEntries.length})`
95
+ : `Missing bootstrap seed files declared by the seed projection policy: ${missingBootstrapFiles.join(", ")}`,
85
96
  ),
86
97
  );
98
+ checks.push({
99
+ id: "bootstrap_seed_package_canonical_in_sync",
100
+ ok: true,
101
+ severity: driftedPackageCanonicalFiles.length === 0 ? "ok" : "warn",
102
+ detail: driftedPackageCanonicalFiles.length === 0
103
+ ? "All package_canonical seed files match package source byte-for-byte"
104
+ : `package_canonical seed files diverge from package source; run \`nimicoding sync --apply\` to refresh, or \`nimicoding sync --check\` for the authoritative gate: ${driftedPackageCanonicalFiles.join(", ")}`,
105
+ });
87
106
 
88
107
  const missingLocalDirs = [];
89
108
  for (const relativePath of REQUIRED_LOCAL_DIRS) {
@@ -149,15 +168,23 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
149
168
  : "bootstrap.yaml declares an unsupported bootstrap contract id or version",
150
169
  });
151
170
 
171
+ const specGenerationInputsContract = await loadSpecGenerationInputsContract(projectRoot);
172
+ const specGenerationAuditContract = await loadSpecGenerationAuditContract(projectRoot);
173
+ const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
174
+ const usesV2SurfaceModel = specGenerationInputs.ok && specGenerationInputs.mode === "class_filtered";
175
+
152
176
  const bootstrapStateText = await readTextIfFile(path.join(projectRoot, ".nimi", "spec", "bootstrap-state.yaml"));
153
177
  const bootstrapStateContract = inspectBootstrapStateContract(bootstrapStateText);
154
178
  checks.push(
155
179
  buildCheck(
156
180
  "bootstrap_state_contract",
157
- bootstrapStateContract.ok,
158
- bootstrapStateContract.ok
181
+ usesV2SurfaceModel || bootstrapStateContract.ok,
182
+ usesV2SurfaceModel
183
+ ? "v2 host-local surface model does not require .nimi/spec/bootstrap-state.yaml"
184
+ : bootstrapStateContract.ok
159
185
  ? `bootstrap-state.yaml matches the ${bootstrapStateContract.treeState} tree-state contract`
160
186
  : "bootstrap-state.yaml is missing required lifecycle fields or declares an unsupported lifecycle state",
187
+ usesV2SurfaceModel ? "info" : undefined,
161
188
  ),
162
189
  );
163
190
 
@@ -166,10 +193,13 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
166
193
  checks.push(
167
194
  buildCheck(
168
195
  "standalone_completion_truth",
169
- completionTruth.ok,
170
- completionTruth.ok
196
+ usesV2SurfaceModel || completionTruth.ok,
197
+ usesV2SurfaceModel
198
+ ? "v2 host-local surface model does not require .nimi/spec/product-scope.yaml"
199
+ : completionTruth.ok
171
200
  ? `Product scope declares standalone completion profile ${completionTruth.completionProfile}`
172
201
  : "product-scope.yaml is missing or drifted from the package-owned standalone completion truth",
202
+ usesV2SurfaceModel ? "info" : undefined,
173
203
  ),
174
204
  );
175
205
 
@@ -177,14 +207,16 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
177
207
  checks.push(
178
208
  buildCheck(
179
209
  "spec_tree_model_contract",
180
- specTreeModel.ok,
181
- specTreeModel.ok
210
+ usesV2SurfaceModel || specTreeModel.ok,
211
+ usesV2SurfaceModel
212
+ ? ".nimi/contracts/surface-taxonomy.schema.yaml supplies the v2 spec surface profile"
213
+ : specTreeModel.ok
182
214
  ? `${SPEC_TREE_MODEL_REF} declares canonical root ${specTreeModel.canonicalRoot} with profile ${specTreeModel.profile}`
183
215
  : `${SPEC_TREE_MODEL_REF} is missing or malformed`,
216
+ usesV2SurfaceModel ? "ok" : undefined,
184
217
  ),
185
218
  );
186
219
 
187
- const specGenerationInputsContract = await loadSpecGenerationInputsContract(projectRoot);
188
220
  checks.push(
189
221
  buildCheck(
190
222
  "spec_generation_inputs_contract",
@@ -195,7 +227,6 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
195
227
  ),
196
228
  );
197
229
 
198
- const specGenerationAuditContract = await loadSpecGenerationAuditContract(projectRoot);
199
230
  checks.push(
200
231
  buildCheck(
201
232
  "spec_generation_audit_contract",
@@ -206,13 +237,12 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
206
237
  ),
207
238
  );
208
239
 
209
- const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
210
240
  checks.push(
211
241
  buildCheck(
212
242
  "spec_generation_inputs_config",
213
243
  specGenerationInputs.ok,
214
244
  specGenerationInputs.ok
215
- ? `${SPEC_GENERATION_INPUTS_REF} declares mixed canonical spec generation inputs`
245
+ ? `${SPEC_GENERATION_INPUTS_REF} declares ${specGenerationInputs.mode} canonical spec generation inputs`
216
246
  : `${SPEC_GENERATION_INPUTS_REF} is missing or malformed`,
217
247
  ),
218
248
  );
@@ -221,21 +251,29 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
221
251
  checks.push(
222
252
  buildCheck(
223
253
  "command_gating_matrix_contract",
224
- commandGatingMatrix.ok,
225
- commandGatingMatrix.ok
254
+ usesV2SurfaceModel || commandGatingMatrix.ok,
255
+ usesV2SurfaceModel
256
+ ? "v2 workflow gates are provided by project-local .nimi/contracts and .nimi/methodology"
257
+ : commandGatingMatrix.ok
226
258
  ? `${COMMAND_GATING_MATRIX_REF} declares ${commandGatingMatrix.entries.length} command gating rules`
227
259
  : `${COMMAND_GATING_MATRIX_REF} is missing or malformed`,
260
+ usesV2SurfaceModel ? "ok" : undefined,
228
261
  ),
229
262
  );
230
263
 
231
264
  const blueprintReference = await loadBlueprintReference(projectRoot);
232
- const blueprintReferenceExpected = bootstrapStateContract.blueprintMode !== "none";
265
+ const blueprintReferenceExpected = usesV2SurfaceModel
266
+ ? specGenerationInputs.benchmarkMode !== "none" || typeof specGenerationInputs.benchmarkBlueprintRoot === "string"
267
+ : bootstrapStateContract.blueprintMode !== "none";
268
+ const expectedBlueprintCanonicalRoot = usesV2SurfaceModel
269
+ ? specGenerationInputs.canonicalTargetRoot
270
+ : specTreeModel.canonicalRoot;
233
271
  const blueprintReferenceAligned = !blueprintReferenceExpected
234
272
  ? !blueprintReference.present
235
273
  : blueprintReference.present
236
274
  && blueprintReference.ok
237
- && blueprintReference.mode === bootstrapStateContract.blueprintMode
238
- && blueprintReference.canonicalTargetRoot === specTreeModel.canonicalRoot;
275
+ && (usesV2SurfaceModel || blueprintReference.mode === bootstrapStateContract.blueprintMode)
276
+ && blueprintReference.canonicalTargetRoot === expectedBlueprintCanonicalRoot;
239
277
  checks.push(
240
278
  buildCheck(
241
279
  "blueprint_reference_contract",
@@ -257,7 +295,7 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
257
295
  available: benchmarkAvailable,
258
296
  ready: benchmarkAvailable && Boolean(
259
297
  specGenerationInputs.ok
260
- && specTreeModel.ok
298
+ && (usesV2SurfaceModel || specTreeModel.ok)
261
299
  && (
262
300
  specGenerationInputs.benchmarkMode === "none"
263
301
  ? !blueprintReference.present
@@ -295,32 +333,38 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
295
333
  : `Canonical tree required files are still missing: ${canonicalTree.missing.join(", ")}`,
296
334
  });
297
335
 
298
- const specGenerationAuditReport = await validateSpecAudit(
299
- path.join(projectRoot, SPEC_GENERATION_AUDIT_REF),
300
- { projectRoot },
301
- );
302
- const specGenerationAudit = specGenerationAuditReport.refusal?.code === "SPEC_AUDIT_MISSING"
303
- ? emptySpecGenerationAudit()
304
- : {
336
+ const specGenerationAuditRef = usesV2SurfaceModel
337
+ ? ".nimi/local/state/spec-generation/spec-generation-audit.yaml"
338
+ : SPEC_GENERATION_AUDIT_REF;
339
+ const specGenerationAuditInfo = await pathExists(path.join(projectRoot, specGenerationAuditRef));
340
+ let specGenerationAudit = emptySpecGenerationAudit();
341
+ specGenerationAudit.auditPath = specGenerationAuditRef;
342
+ if (specGenerationAuditInfo?.isFile()) {
343
+ const specGenerationAuditReport = await validateSpecAudit(
344
+ path.join(projectRoot, specGenerationAuditRef),
345
+ { projectRoot },
346
+ );
347
+ specGenerationAudit = {
305
348
  present: true,
306
349
  ok: specGenerationAuditReport.ok,
307
- auditPath: SPEC_GENERATION_AUDIT_REF,
350
+ auditPath: specGenerationAuditRef,
308
351
  validator: "validate-spec-audit",
309
352
  summary: specGenerationAuditReport.summary ?? null,
310
353
  reason: specGenerationAuditReport.ok
311
354
  ? "Spec generation audit is present and structurally valid"
312
355
  : specGenerationAuditReport.errors.join("; "),
313
356
  };
357
+ }
314
358
  const specGenerationAuditCheckSeverity = !specGenerationAudit.present
315
- ? canonicalTree.requiredFilesValid ? "error" : "info"
359
+ ? canonicalTree.requiredFilesValid ? "warn" : "info"
316
360
  : specGenerationAudit.ok ? "ok" : canonicalTree.requiredFilesValid ? "error" : "warn";
317
361
  checks.push({
318
362
  id: "spec_generation_audit",
319
- ok: !specGenerationAudit.present ? !canonicalTree.requiredFilesValid : specGenerationAudit.ok,
363
+ ok: !specGenerationAudit.present ? true : specGenerationAudit.ok,
320
364
  severity: specGenerationAuditCheckSeverity,
321
365
  detail: !specGenerationAudit.present
322
366
  ? canonicalTree.requiredFilesValid
323
- ? "Canonical tree is ready but spec generation audit is still missing or invalid"
367
+ ? "Canonical tree is ready and spec generation audit is missing; run the audit workflow when file-level generation evidence is required"
324
368
  : specGenerationAudit.reason
325
369
  : specGenerationAudit.ok
326
370
  ? specGenerationAudit.reason
@@ -331,10 +375,13 @@ export async function inspectDoctorBootstrapSurface(projectRoot) {
331
375
  checks.push(
332
376
  buildCheck(
333
377
  "high_risk_closeout_gate",
334
- Boolean(highRiskCloseoutGate?.completedRequires?.tree_state === "canonical_tree_ready"),
335
- highRiskCloseoutGate
378
+ usesV2SurfaceModel || Boolean(highRiskCloseoutGate?.completedRequires?.tree_state === "canonical_tree_ready"),
379
+ usesV2SurfaceModel
380
+ ? "v2 high-risk closeout gates are contract-driven by project-local .nimi/contracts"
381
+ : highRiskCloseoutGate
336
382
  ? "Command gating matrix includes high_risk_execution closeout readiness"
337
383
  : "command gating matrix is missing closeout gating for high_risk_execution",
384
+ usesV2SurfaceModel ? "ok" : undefined,
338
385
  ),
339
386
  );
340
387
 
@@ -147,7 +147,7 @@ export async function inspectDoctorDelegatedSurface(projectRoot) {
147
147
  const missingHandoffPaths = [];
148
148
  for (const relativePath of handoffRequiredContext) {
149
149
  const info = await pathExists(path.join(projectRoot, relativePath));
150
- if (!info) {
150
+ if (!info && relativePath !== ".nimi/spec") {
151
151
  missingHandoffPaths.push(relativePath);
152
152
  }
153
153
  }