@nimiplatform/nimi-coding 0.1.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 (186) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/adapters/README.md +25 -0
  4. package/adapters/claude/README.md +89 -0
  5. package/adapters/claude/profile.yaml +70 -0
  6. package/adapters/codex/README.md +53 -0
  7. package/adapters/codex/profile.yaml +78 -0
  8. package/adapters/oh-my-codex/README.md +185 -0
  9. package/adapters/oh-my-codex/profile.yaml +46 -0
  10. package/bin/nimicoding.mjs +6 -0
  11. package/cli/commands/admit-high-risk-decision.mjs +108 -0
  12. package/cli/commands/audit-sweep.mjs +341 -0
  13. package/cli/commands/blueprint-audit.mjs +91 -0
  14. package/cli/commands/clear.mjs +168 -0
  15. package/cli/commands/closeout.mjs +183 -0
  16. package/cli/commands/decide-high-risk-execution.mjs +124 -0
  17. package/cli/commands/doctor.mjs +53 -0
  18. package/cli/commands/generate-spec-derived-docs.mjs +131 -0
  19. package/cli/commands/handoff.mjs +123 -0
  20. package/cli/commands/ingest-high-risk-execution.mjs +95 -0
  21. package/cli/commands/review-high-risk-execution.mjs +95 -0
  22. package/cli/commands/start.mjs +717 -0
  23. package/cli/commands/topic-formatters.mjs +382 -0
  24. package/cli/commands/topic-goal.mjs +33 -0
  25. package/cli/commands/topic-options-shared.mjs +27 -0
  26. package/cli/commands/topic-options-workflow.mjs +767 -0
  27. package/cli/commands/topic-options.mjs +626 -0
  28. package/cli/commands/topic-runner.mjs +169 -0
  29. package/cli/commands/topic.mjs +795 -0
  30. package/cli/commands/validate-acceptance.mjs +5 -0
  31. package/cli/commands/validate-ai-governance.mjs +214 -0
  32. package/cli/commands/validate-execution-packet.mjs +5 -0
  33. package/cli/commands/validate-orchestration-state.mjs +5 -0
  34. package/cli/commands/validate-prompt.mjs +5 -0
  35. package/cli/commands/validate-spec-audit.mjs +27 -0
  36. package/cli/commands/validate-spec-governance.mjs +124 -0
  37. package/cli/commands/validate-spec-tree.mjs +27 -0
  38. package/cli/commands/validate-worker-output.mjs +5 -0
  39. package/cli/constants.mjs +489 -0
  40. package/cli/help.mjs +134 -0
  41. package/cli/index.mjs +103 -0
  42. package/cli/lib/adapter-profiles.mjs +403 -0
  43. package/cli/lib/audit-execution.mjs +52 -0
  44. package/cli/lib/audit-sweep-runtime/admissions.mjs +381 -0
  45. package/cli/lib/audit-sweep-runtime/audit-validity.mjs +333 -0
  46. package/cli/lib/audit-sweep-runtime/chunks.mjs +697 -0
  47. package/cli/lib/audit-sweep-runtime/closeout.mjs +144 -0
  48. package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +639 -0
  49. package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +515 -0
  50. package/cli/lib/audit-sweep-runtime/common.mjs +329 -0
  51. package/cli/lib/audit-sweep-runtime/coverage-quality.mjs +172 -0
  52. package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +152 -0
  53. package/cli/lib/audit-sweep-runtime/format.mjs +57 -0
  54. package/cli/lib/audit-sweep-runtime/ingest.mjs +486 -0
  55. package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +198 -0
  56. package/cli/lib/audit-sweep-runtime/inventory.mjs +728 -0
  57. package/cli/lib/audit-sweep-runtime/ledger.mjs +315 -0
  58. package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +101 -0
  59. package/cli/lib/audit-sweep-runtime/remediation.mjs +349 -0
  60. package/cli/lib/audit-sweep-runtime/rerun.mjs +129 -0
  61. package/cli/lib/audit-sweep-runtime/risk-budget.mjs +300 -0
  62. package/cli/lib/audit-sweep-runtime/status.mjs +62 -0
  63. package/cli/lib/audit-sweep-runtime/validators-ledger.mjs +215 -0
  64. package/cli/lib/audit-sweep-runtime/validators.mjs +758 -0
  65. package/cli/lib/audit-sweep.mjs +18 -0
  66. package/cli/lib/authority-convergence.mjs +309 -0
  67. package/cli/lib/blueprint-audit.mjs +370 -0
  68. package/cli/lib/bootstrap.mjs +228 -0
  69. package/cli/lib/closeout.mjs +623 -0
  70. package/cli/lib/codex-sdk-runner.mjs +76 -0
  71. package/cli/lib/contracts.mjs +180 -0
  72. package/cli/lib/doctor.mjs +18 -0
  73. package/cli/lib/entrypoints.mjs +274 -0
  74. package/cli/lib/external-execution.mjs +101 -0
  75. package/cli/lib/fs-helpers.mjs +33 -0
  76. package/cli/lib/handoff.mjs +785 -0
  77. package/cli/lib/high-risk-admission.mjs +442 -0
  78. package/cli/lib/high-risk-decision.mjs +324 -0
  79. package/cli/lib/high-risk-ingest.mjs +317 -0
  80. package/cli/lib/high-risk-review.mjs +263 -0
  81. package/cli/lib/internal/contracts-loaders.mjs +132 -0
  82. package/cli/lib/internal/contracts-parse-high-risk.mjs +131 -0
  83. package/cli/lib/internal/contracts-parse.mjs +457 -0
  84. package/cli/lib/internal/contracts-validators.mjs +398 -0
  85. package/cli/lib/internal/doctor-bootstrap-surface.mjs +359 -0
  86. package/cli/lib/internal/doctor-delegated-surface.mjs +256 -0
  87. package/cli/lib/internal/doctor-finalize.mjs +385 -0
  88. package/cli/lib/internal/doctor-format.mjs +286 -0
  89. package/cli/lib/internal/doctor-inspectors.mjs +294 -0
  90. package/cli/lib/internal/doctor-state.mjs +205 -0
  91. package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +315 -0
  92. package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +358 -0
  93. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +155 -0
  94. package/cli/lib/internal/governance/ai/check-high-risk-doc-metadata-core.mjs +173 -0
  95. package/cli/lib/internal/governance/config.mjs +150 -0
  96. package/cli/lib/internal/governance/runner.mjs +35 -0
  97. package/cli/lib/internal/governance/shared/read-yaml-with-fragments.mjs +49 -0
  98. package/cli/lib/internal/validators-artifacts.mjs +515 -0
  99. package/cli/lib/internal/validators-shared.mjs +28 -0
  100. package/cli/lib/internal/validators-spec-helpers.mjs +186 -0
  101. package/cli/lib/internal/validators-spec.mjs +410 -0
  102. package/cli/lib/shared.mjs +83 -0
  103. package/cli/lib/topic-draft-packets.mjs +48 -0
  104. package/cli/lib/topic-goal.mjs +361 -0
  105. package/cli/lib/topic-runner.mjs +772 -0
  106. package/cli/lib/topic.mjs +93 -0
  107. package/cli/lib/ui.mjs +178 -0
  108. package/cli/lib/validators.mjs +78 -0
  109. package/cli/lib/value-helpers.mjs +24 -0
  110. package/cli/lib/yaml-helpers.mjs +133 -0
  111. package/cli/nimicoding.mjs +1 -0
  112. package/cli/seeds/bootstrap.mjs +47 -0
  113. package/config/audit-execution-artifacts.yaml +20 -0
  114. package/config/bootstrap.yaml +6 -0
  115. package/config/external-execution-artifacts.yaml +16 -0
  116. package/config/host-adapter.yaml +30 -0
  117. package/config/host-profile.yaml +29 -0
  118. package/config/installer-evidence.yaml +31 -0
  119. package/config/skill-installer.yaml +23 -0
  120. package/config/skill-manifest.yaml +46 -0
  121. package/config/skills.yaml +30 -0
  122. package/config/spec-generation-inputs.yaml +25 -0
  123. package/contracts/acceptance.schema.yaml +16 -0
  124. package/contracts/admission-checklist.schema.yaml +15 -0
  125. package/contracts/audit-chunk.schema.yaml +110 -0
  126. package/contracts/audit-closeout.schema.yaml +51 -0
  127. package/contracts/audit-finding.schema.yaml +61 -0
  128. package/contracts/audit-ledger.schema.yaml +138 -0
  129. package/contracts/audit-plan.schema.yaml +123 -0
  130. package/contracts/audit-remediation-map.schema.yaml +51 -0
  131. package/contracts/audit-rerun.schema.yaml +31 -0
  132. package/contracts/audit-sweep-result.yaml +49 -0
  133. package/contracts/authority-convergence-audit.schema.yaml +19 -0
  134. package/contracts/closeout.schema.yaml +25 -0
  135. package/contracts/decision-review.schema.yaml +16 -0
  136. package/contracts/doc-spec-audit-result.yaml +19 -0
  137. package/contracts/execution-packet.schema.yaml +49 -0
  138. package/contracts/external-host-compatibility.yaml +22 -0
  139. package/contracts/forbidden-shortcuts.catalog.yaml +23 -0
  140. package/contracts/high-risk-admission.schema.yaml +23 -0
  141. package/contracts/high-risk-execution-result.yaml +20 -0
  142. package/contracts/orchestration-state.schema.yaml +41 -0
  143. package/contracts/overflow-continuation.schema.yaml +12 -0
  144. package/contracts/packet.schema.yaml +30 -0
  145. package/contracts/pending-note.schema.yaml +17 -0
  146. package/contracts/prompt.schema.yaml +12 -0
  147. package/contracts/remediation.schema.yaml +16 -0
  148. package/contracts/result.schema.yaml +24 -0
  149. package/contracts/spec-generation-audit.schema.yaml +31 -0
  150. package/contracts/spec-generation-inputs.schema.yaml +39 -0
  151. package/contracts/spec-reconstruction-result.yaml +37 -0
  152. package/contracts/topic-goal.schema.yaml +78 -0
  153. package/contracts/topic-run-ledger.schema.yaml +72 -0
  154. package/contracts/topic-step-decision.schema.yaml +45 -0
  155. package/contracts/topic.schema.yaml +65 -0
  156. package/contracts/true-close.schema.yaml +15 -0
  157. package/contracts/wave.schema.yaml +29 -0
  158. package/contracts/worker-output.schema.yaml +15 -0
  159. package/methodology/audit-sweep-p0p1-recall.yaml +45 -0
  160. package/methodology/authority-convergence-policy.yaml +42 -0
  161. package/methodology/core.yaml +25 -0
  162. package/methodology/four-closure-policy.yaml +28 -0
  163. package/methodology/overflow-continuation-policy.yaml +14 -0
  164. package/methodology/role-separation-policy.yaml +28 -0
  165. package/methodology/skill-exchange-projection.yaml +114 -0
  166. package/methodology/skill-handoff.yaml +34 -0
  167. package/methodology/skill-installer-result.yaml +27 -0
  168. package/methodology/skill-installer-summary-projection.yaml +181 -0
  169. package/methodology/skill-runtime.yaml +23 -0
  170. package/methodology/spec-reconstruction.yaml +63 -0
  171. package/methodology/spec-target-truth-profile.yaml +53 -0
  172. package/methodology/topic-lifecycle-report.yaml +144 -0
  173. package/methodology/topic-lifecycle.yaml +37 -0
  174. package/methodology/topic-naming-ontology.yaml +21 -0
  175. package/methodology/topic-ontology.yaml +38 -0
  176. package/methodology/topic-validation-policy.yaml +9 -0
  177. package/methodology/wave-dag-policy.yaml +14 -0
  178. package/package.json +50 -0
  179. package/spec/_meta/command-gating-matrix.yaml +110 -0
  180. package/spec/_meta/generate-drift-migration-checklist.yaml +155 -0
  181. package/spec/_meta/governance-routing-cutover-checklist.yaml +35 -0
  182. package/spec/_meta/phase2-impacted-surface-matrix.yaml +44 -0
  183. package/spec/_meta/spec-authority-cutover-readiness.yaml +104 -0
  184. package/spec/_meta/spec-tree-model.yaml +72 -0
  185. package/spec/bootstrap-state.yaml +99 -0
  186. package/spec/product-scope.yaml +56 -0
@@ -0,0 +1,186 @@
1
+ import path from "node:path";
2
+ import { readdir } from "node:fs/promises";
3
+
4
+ import { readTextIfFile } from "../fs-helpers.mjs";
5
+
6
+ function posixRelative(targetRoot, absolutePath) {
7
+ return path.relative(targetRoot, absolutePath).split(path.sep).join(path.posix.sep);
8
+ }
9
+
10
+ export async function collectTreeFiles(rootPath) {
11
+ const text = await readTextIfFile(path.join(rootPath, "INDEX.md"));
12
+ if (text === null) {
13
+ const info = await readTextIfFile(rootPath);
14
+ if (info !== null) {
15
+ return [];
16
+ }
17
+ }
18
+
19
+ async function walk(currentPath) {
20
+ let entries;
21
+ try {
22
+ entries = await readdir(currentPath, { withFileTypes: true });
23
+ } catch {
24
+ return [];
25
+ }
26
+
27
+ const files = [];
28
+ for (const entry of entries) {
29
+ const childPath = path.join(currentPath, entry.name);
30
+ if (entry.isDirectory()) {
31
+ files.push(...await walk(childPath));
32
+ } else if (entry.isFile()) {
33
+ files.push(posixRelative(rootPath, childPath));
34
+ }
35
+ }
36
+ return files.sort();
37
+ }
38
+
39
+ return walk(rootPath);
40
+ }
41
+
42
+ function escapeRegexLiteral(value) {
43
+ return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
44
+ }
45
+
46
+ function globToRegex(pattern) {
47
+ const DOUBLE_STAR_SLASH = "__DOUBLE_STAR_SLASH__";
48
+ const DOUBLE_STAR = "__DOUBLE_STAR__";
49
+ const SINGLE_STAR = "__SINGLE_STAR__";
50
+
51
+ let source = pattern
52
+ .replaceAll("**/", DOUBLE_STAR_SLASH)
53
+ .replaceAll("**", DOUBLE_STAR)
54
+ .replaceAll("*", SINGLE_STAR);
55
+
56
+ source = escapeRegexLiteral(source)
57
+ .replaceAll(DOUBLE_STAR_SLASH, "(?:.*/)?")
58
+ .replaceAll(DOUBLE_STAR, ".*")
59
+ .replaceAll(SINGLE_STAR, "[^/]*");
60
+
61
+ return new RegExp(`^${source}$`);
62
+ }
63
+
64
+ function compilePathClassMatchers(specTreeModel) {
65
+ const classes = [
66
+ ...specTreeModel.normativeClasses.map((entry) => ({ ...entry, category: "normative" })),
67
+ ...specTreeModel.derivedClasses.map((entry) => ({ ...entry, category: "derived" })),
68
+ ...specTreeModel.guidanceClasses.map((entry) => ({ ...entry, category: "guidance" })),
69
+ ];
70
+
71
+ return classes.map((entry) => ({
72
+ ...entry,
73
+ includeMatchers: entry.pathPatterns.map(globToRegex),
74
+ excludeMatchers: (entry.excludedPathPatterns ?? []).map(globToRegex),
75
+ }));
76
+ }
77
+
78
+ function isAllowedTopLevelSupportFile(relativePath) {
79
+ return [
80
+ "INDEX.md",
81
+ "bootstrap-state.yaml",
82
+ "product-scope.yaml",
83
+ "high-risk-admissions.yaml",
84
+ ].includes(relativePath);
85
+ }
86
+
87
+ export function classifySpecTreeFiles(canonicalRoot, files, specTreeModel) {
88
+ const matchers = compilePathClassMatchers(specTreeModel);
89
+ const classifications = [];
90
+ const unexpected = [];
91
+ const conflicts = [];
92
+
93
+ for (const relativePath of files) {
94
+ const canonicalRelativePath = path.posix.join(canonicalRoot, relativePath);
95
+
96
+ if (relativePath.startsWith("_meta/")) {
97
+ classifications.push({ path: relativePath, classId: "_meta", category: "meta" });
98
+ continue;
99
+ }
100
+
101
+ if (isAllowedTopLevelSupportFile(relativePath)) {
102
+ classifications.push({ path: relativePath, classId: "support", category: "support" });
103
+ continue;
104
+ }
105
+
106
+ const matched = matchers.filter((matcher) => (
107
+ matcher.includeMatchers.some((regex) => regex.test(canonicalRelativePath))
108
+ && !matcher.excludeMatchers.some((regex) => regex.test(canonicalRelativePath))
109
+ ));
110
+
111
+ if (matched.length === 0) {
112
+ unexpected.push(relativePath);
113
+ continue;
114
+ }
115
+
116
+ if (matched.length > 1) {
117
+ conflicts.push({
118
+ path: relativePath,
119
+ classes: matched.map((entry) => entry.id),
120
+ });
121
+ continue;
122
+ }
123
+
124
+ classifications.push({
125
+ path: relativePath,
126
+ classId: matched[0].id,
127
+ category: matched[0].category,
128
+ });
129
+ }
130
+
131
+ return {
132
+ classifications,
133
+ unexpected,
134
+ conflicts,
135
+ };
136
+ }
137
+
138
+ export function classifyAuditCoveredFiles(files, specTreeModel) {
139
+ const classifications = classifySpecTreeFiles(specTreeModel.canonicalRoot, files, specTreeModel);
140
+ const auditedFiles = classifications.classifications.filter((entry) => (
141
+ entry.category !== "meta"
142
+ && (
143
+ entry.category !== "support"
144
+ || entry.path === "INDEX.md"
145
+ )
146
+ ));
147
+ return {
148
+ classifications,
149
+ auditedFiles,
150
+ };
151
+ }
152
+
153
+ export function isSourceRefWithinDeclaredRoots(sourceRef, declaredInputs) {
154
+ const roots = [
155
+ ...declaredInputs.code_roots,
156
+ ...declaredInputs.docs_roots,
157
+ ...declaredInputs.structure_roots,
158
+ ...(declaredInputs.benchmark_blueprint_root ? [declaredInputs.benchmark_blueprint_root] : []),
159
+ ];
160
+
161
+ if (declaredInputs.human_note_paths.includes(sourceRef)) {
162
+ return true;
163
+ }
164
+
165
+ return roots.some((root) => (
166
+ root === "."
167
+ ? !path.posix.isAbsolute(sourceRef)
168
+ : sourceRef === root || sourceRef.startsWith(`${root}/`)
169
+ ));
170
+ }
171
+
172
+ export function isDeclaredInputsCompatibleWithConfig(declaredInputs, generationInputs, blueprintReference) {
173
+ const benchmarkRoot = generationInputs.benchmarkBlueprintRoot ?? blueprintReference.root ?? null;
174
+
175
+ const rootsAlign = (declaredRoots, configuredRoots) => declaredRoots.every((entry) => configuredRoots.includes(entry));
176
+
177
+ return rootsAlign(declaredInputs.code_roots, generationInputs.codeRoots ?? [])
178
+ && rootsAlign(declaredInputs.docs_roots, generationInputs.docsRoots ?? [])
179
+ && rootsAlign(declaredInputs.structure_roots, generationInputs.structureRoots ?? [])
180
+ && rootsAlign(declaredInputs.human_note_paths, generationInputs.humanNotePaths ?? [])
181
+ && (
182
+ declaredInputs.benchmark_blueprint_root === null
183
+ ? benchmarkRoot === null
184
+ : declaredInputs.benchmark_blueprint_root === benchmarkRoot
185
+ );
186
+ }
@@ -0,0 +1,410 @@
1
+ import path from "node:path";
2
+
3
+ import {
4
+ SPEC_GENERATION_AUDIT_CONTRACT_REF,
5
+ SPEC_GENERATION_AUDIT_COVERAGE_STATUS_ENUM,
6
+ SPEC_GENERATION_AUDIT_FILE_REQUIRED_FIELDS,
7
+ SPEC_GENERATION_AUDIT_SOURCE_BASIS_ENUM,
8
+ } from "../../constants.mjs";
9
+ import {
10
+ loadBlueprintReference,
11
+ loadSpecGenerationAuditContract,
12
+ loadSpecGenerationInputsConfig,
13
+ loadSpecTreeModelContract,
14
+ } from "../contracts.mjs";
15
+ import { readTextIfFile } from "../fs-helpers.mjs";
16
+ import { isPlainObject } from "../value-helpers.mjs";
17
+ import { parseYamlText } from "../yaml-helpers.mjs";
18
+ import {
19
+ makeValidatorRefusal,
20
+ VALIDATOR_NATIVE_REFUSAL_CODES,
21
+ } from "./validators-shared.mjs";
22
+ import {
23
+ classifyAuditCoveredFiles,
24
+ classifySpecTreeFiles,
25
+ collectTreeFiles,
26
+ isDeclaredInputsCompatibleWithConfig,
27
+ isSourceRefWithinDeclaredRoots,
28
+ } from "./validators-spec-helpers.mjs";
29
+
30
+ export async function validateSpecTree(rootPath, options = {}) {
31
+ const projectRoot = options.projectRoot ?? process.cwd();
32
+ const specTreeModel = await loadSpecTreeModelContract(projectRoot);
33
+ const errors = [];
34
+ const warnings = [];
35
+
36
+ if (!specTreeModel.ok) {
37
+ return {
38
+ ok: false,
39
+ errors: [`invalid spec tree model contract: ${specTreeModel.path}`],
40
+ warnings,
41
+ refusal: makeValidatorRefusal(
42
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_TREE_INVALID,
43
+ "spec tree validation requires a valid spec-tree-model contract",
44
+ ),
45
+ };
46
+ }
47
+
48
+ const expectedRoot = path.resolve(projectRoot, specTreeModel.canonicalRoot);
49
+ const targetRoot = path.resolve(rootPath);
50
+
51
+ if (targetRoot !== expectedRoot) {
52
+ errors.push(`spec tree root mismatch: expected ${expectedRoot} but received ${targetRoot}`);
53
+ }
54
+
55
+ const files = await collectTreeFiles(targetRoot);
56
+ if (files.length === 0) {
57
+ return {
58
+ ok: false,
59
+ errors: errors.length > 0 ? errors : [`missing spec tree root: ${targetRoot}`],
60
+ warnings,
61
+ refusal: makeValidatorRefusal(
62
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_TREE_MISSING,
63
+ "spec tree root is missing or empty",
64
+ ),
65
+ };
66
+ }
67
+
68
+ const requiredFiles = specTreeModel.requiredFilesByProfile[specTreeModel.profile] ?? [];
69
+ const missingRequired = requiredFiles
70
+ .map((entry) => path.posix.relative(specTreeModel.canonicalRoot, entry))
71
+ .filter((entry) => !files.includes(entry));
72
+
73
+ if (missingRequired.length > 0) {
74
+ errors.push(`missing required canonical files: ${missingRequired.join(", ")}`);
75
+ }
76
+
77
+ for (const domain of specTreeModel.domains) {
78
+ const domainRoot = path.posix.relative(specTreeModel.canonicalRoot, domain.root);
79
+ const normativeRoot = path.posix.relative(specTreeModel.canonicalRoot, domain.normativeRoot);
80
+ const tablesRoot = path.posix.relative(specTreeModel.canonicalRoot, domain.tablesRoot);
81
+ const domainHasFiles = files.some((entry) => entry.startsWith(`${domainRoot}/`));
82
+ const normativeHasFiles = files.some((entry) => entry.startsWith(`${normativeRoot}/`));
83
+ const tablesHasFiles = files.some((entry) => entry.startsWith(`${tablesRoot}/`));
84
+
85
+ if (!domainHasFiles) {
86
+ errors.push(`declared domain root has no files: ${domainRoot}`);
87
+ }
88
+ if (!normativeHasFiles) {
89
+ errors.push(`declared normative root has no files: ${normativeRoot}`);
90
+ }
91
+ if (!tablesHasFiles) {
92
+ errors.push(`declared tables root has no files: ${tablesRoot}`);
93
+ }
94
+ }
95
+
96
+ const classification = classifySpecTreeFiles(specTreeModel.canonicalRoot, files, specTreeModel);
97
+ if (classification.unexpected.length > 0) {
98
+ errors.push(`unexpected files outside declared spec classes: ${classification.unexpected.join(", ")}`);
99
+ }
100
+ if (classification.conflicts.length > 0) {
101
+ errors.push(
102
+ `files matched multiple spec classes: ${classification.conflicts.map((entry) => `${entry.path} -> ${entry.classes.join("|")}`).join(", ")}`,
103
+ );
104
+ }
105
+
106
+ return {
107
+ ok: errors.length === 0,
108
+ errors,
109
+ warnings,
110
+ refusal: errors.length === 0
111
+ ? null
112
+ : makeValidatorRefusal(
113
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_TREE_INVALID,
114
+ `spec tree is invalid: ${path.basename(targetRoot)}`,
115
+ ),
116
+ summary: {
117
+ profile: specTreeModel.profile,
118
+ canonicalRoot: specTreeModel.canonicalRoot,
119
+ totalFiles: files.length,
120
+ requiredFiles: requiredFiles.length,
121
+ missingRequired,
122
+ classifiedFiles: classification.classifications.length,
123
+ unexpectedFiles: classification.unexpected,
124
+ conflictingFiles: classification.conflicts,
125
+ },
126
+ };
127
+ }
128
+
129
+ export async function validateSpecAudit(auditPath, options = {}) {
130
+ const projectRoot = options.projectRoot ?? process.cwd();
131
+ const specTreeModel = await loadSpecTreeModelContract(projectRoot);
132
+ const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
133
+ const blueprintReference = await loadBlueprintReference(projectRoot);
134
+ const auditContract = await loadSpecGenerationAuditContract(projectRoot);
135
+ const errors = [];
136
+ const warnings = [];
137
+
138
+ if (!specTreeModel.ok) {
139
+ return {
140
+ ok: false,
141
+ errors: [`invalid spec tree model contract: ${specTreeModel.path}`],
142
+ warnings,
143
+ refusal: makeValidatorRefusal(
144
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
145
+ "spec audit validation requires a valid spec-tree-model contract",
146
+ ),
147
+ };
148
+ }
149
+
150
+ if (!specGenerationInputs.ok) {
151
+ return {
152
+ ok: false,
153
+ errors: [`invalid spec generation inputs config: ${specGenerationInputs.path}`],
154
+ warnings,
155
+ refusal: makeValidatorRefusal(
156
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
157
+ "spec audit validation requires a valid spec-generation-inputs config",
158
+ ),
159
+ };
160
+ }
161
+
162
+ if (!auditContract.ok) {
163
+ return {
164
+ ok: false,
165
+ errors: [`invalid spec generation audit contract: ${auditContract.path}`],
166
+ warnings,
167
+ refusal: makeValidatorRefusal(
168
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
169
+ "spec audit validation requires a valid spec-generation-audit contract",
170
+ ),
171
+ };
172
+ }
173
+
174
+ const absoluteAuditPath = path.resolve(auditPath);
175
+ const auditText = await readTextIfFile(absoluteAuditPath);
176
+ if (auditText === null) {
177
+ return {
178
+ ok: false,
179
+ errors: [`missing file: ${absoluteAuditPath}`],
180
+ warnings,
181
+ refusal: makeValidatorRefusal(
182
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_MISSING,
183
+ "spec generation audit artifact is missing",
184
+ ),
185
+ };
186
+ }
187
+
188
+ const parsed = parseYamlText(auditText);
189
+ const audit = parsed?.spec_generation_audit;
190
+ if (!isPlainObject(parsed) || !isPlainObject(audit)) {
191
+ return {
192
+ ok: false,
193
+ errors: [`invalid YAML document: ${absoluteAuditPath}`],
194
+ warnings,
195
+ refusal: makeValidatorRefusal(
196
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
197
+ "spec generation audit artifact is not valid YAML",
198
+ ),
199
+ };
200
+ }
201
+
202
+ if (parsed.version !== 1) {
203
+ errors.push("spec generation audit version must be 1");
204
+ }
205
+
206
+ if (String(parsed.contract_ref ?? "") !== SPEC_GENERATION_AUDIT_CONTRACT_REF) {
207
+ errors.push(`spec generation audit contract_ref must be ${SPEC_GENERATION_AUDIT_CONTRACT_REF}`);
208
+ }
209
+
210
+ const missingTopLevelFields = auditContract.requiredTopLevelFields.filter((field) => !(field in audit));
211
+ if (missingTopLevelFields.length > 0) {
212
+ errors.push(`spec generation audit is missing required fields: ${missingTopLevelFields.join(", ")}`);
213
+ }
214
+
215
+ const declaredInputs = {
216
+ code_roots: Array.isArray(audit?.input_roots?.code_roots) ? audit.input_roots.code_roots.map(String) : [],
217
+ docs_roots: Array.isArray(audit?.input_roots?.docs_roots) ? audit.input_roots.docs_roots.map(String) : [],
218
+ structure_roots: Array.isArray(audit?.input_roots?.structure_roots) ? audit.input_roots.structure_roots.map(String) : [],
219
+ human_note_paths: Array.isArray(audit?.input_roots?.human_note_paths) ? audit.input_roots.human_note_paths.map(String) : [],
220
+ benchmark_blueprint_root: audit?.input_roots?.benchmark_blueprint_root === null
221
+ ? null
222
+ : typeof audit?.input_roots?.benchmark_blueprint_root === "string"
223
+ ? audit.input_roots.benchmark_blueprint_root
224
+ : null,
225
+ };
226
+
227
+ if (String(audit.generation_mode ?? "") !== "mixed") {
228
+ errors.push("spec generation audit generation_mode must be `mixed`");
229
+ }
230
+ if (String(audit.canonical_target_root ?? "") !== specTreeModel.canonicalRoot) {
231
+ errors.push(`spec generation audit canonical_target_root must be ${specTreeModel.canonicalRoot}`);
232
+ }
233
+ if (String(audit.declared_profile ?? "") !== specTreeModel.profile) {
234
+ errors.push(`spec generation audit declared_profile must be ${specTreeModel.profile}`);
235
+ }
236
+ if (!isDeclaredInputsCompatibleWithConfig(declaredInputs, specGenerationInputs, blueprintReference)) {
237
+ errors.push("spec generation audit input_roots must stay within the declared generation inputs and optional benchmark root");
238
+ }
239
+
240
+ const canonicalRootPath = path.resolve(projectRoot, specTreeModel.canonicalRoot);
241
+ const treeFiles = await collectTreeFiles(canonicalRootPath);
242
+ if (treeFiles.length === 0) {
243
+ return {
244
+ ok: false,
245
+ errors: [`missing spec tree root: ${canonicalRootPath}`],
246
+ warnings,
247
+ refusal: makeValidatorRefusal(
248
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
249
+ "spec generation audit requires a present canonical spec tree",
250
+ ),
251
+ };
252
+ }
253
+
254
+ const { auditedFiles, classifications } = classifyAuditCoveredFiles(treeFiles, specTreeModel);
255
+ if (classifications.unexpected.length > 0) {
256
+ errors.push(`spec tree contains unexpected files outside declared spec classes: ${classifications.unexpected.join(", ")}`);
257
+ }
258
+ if (classifications.conflicts.length > 0) {
259
+ errors.push(`spec tree contains files matched to multiple classes: ${classifications.conflicts.map((entry) => `${entry.path} -> ${entry.classes.join("|")}`).join(", ")}`);
260
+ }
261
+
262
+ const fileEntries = Array.isArray(audit.files) ? audit.files : [];
263
+ if (!Array.isArray(audit.files)) {
264
+ errors.push("spec generation audit files must be an array");
265
+ }
266
+
267
+ const auditEntryByRelativePath = new Map();
268
+ for (const entry of fileEntries) {
269
+ if (!isPlainObject(entry)) {
270
+ errors.push("spec generation audit file entries must be mappings");
271
+ continue;
272
+ }
273
+
274
+ const missingEntryFields = SPEC_GENERATION_AUDIT_FILE_REQUIRED_FIELDS.filter((field) => !(field in entry));
275
+ if (missingEntryFields.length > 0) {
276
+ errors.push(`spec generation audit file entry is missing required fields: ${missingEntryFields.join(", ")}`);
277
+ continue;
278
+ }
279
+
280
+ const canonicalPath = String(entry.canonical_path ?? "");
281
+ if (!canonicalPath.startsWith(`${specTreeModel.canonicalRoot}/`) && canonicalPath !== `${specTreeModel.canonicalRoot}/INDEX.md`) {
282
+ errors.push(`spec generation audit canonical_path must stay under ${specTreeModel.canonicalRoot}: ${canonicalPath}`);
283
+ continue;
284
+ }
285
+
286
+ const relativePath = path.posix.relative(specTreeModel.canonicalRoot, canonicalPath);
287
+ if (relativePath.startsWith("_meta/")) {
288
+ errors.push(`spec generation audit must not record _meta files as generated canonical files: ${canonicalPath}`);
289
+ continue;
290
+ }
291
+
292
+ if (auditEntryByRelativePath.has(relativePath)) {
293
+ errors.push(`duplicate spec generation audit entry for canonical path: ${canonicalPath}`);
294
+ continue;
295
+ }
296
+
297
+ if (!Array.isArray(entry.source_refs) || entry.source_refs.length === 0 || entry.source_refs.some((ref) => typeof ref !== "string" || ref.length === 0)) {
298
+ errors.push(`spec generation audit source_refs must be a non-empty array for ${canonicalPath}`);
299
+ } else {
300
+ const invalidRefs = entry.source_refs.filter((ref) => !isSourceRefWithinDeclaredRoots(ref, declaredInputs));
301
+ if (invalidRefs.length > 0) {
302
+ errors.push(`spec generation audit source_refs escape declared inputs for ${canonicalPath}: ${invalidRefs.join(", ")}`);
303
+ }
304
+ }
305
+
306
+ if (!SPEC_GENERATION_AUDIT_SOURCE_BASIS_ENUM.includes(String(entry.source_basis ?? ""))) {
307
+ errors.push(`spec generation audit source_basis is invalid for ${canonicalPath}`);
308
+ }
309
+
310
+ if (!SPEC_GENERATION_AUDIT_COVERAGE_STATUS_ENUM.includes(String(entry.coverage_status ?? ""))) {
311
+ errors.push(`spec generation audit coverage_status is invalid for ${canonicalPath}`);
312
+ }
313
+
314
+ if (!Array.isArray(entry.unresolved_items) || entry.unresolved_items.some((item) => typeof item !== "string")) {
315
+ errors.push(`spec generation audit unresolved_items must be an array of strings for ${canonicalPath}`);
316
+ }
317
+
318
+ if (entry.notes !== undefined && (!Array.isArray(entry.notes) || entry.notes.some((item) => typeof item !== "string"))) {
319
+ errors.push(`spec generation audit notes must be an array of strings for ${canonicalPath}`);
320
+ }
321
+
322
+ const requiresExplicitUnresolved = entry.source_basis !== "grounded" || entry.coverage_status !== "complete";
323
+ if (requiresExplicitUnresolved && (!Array.isArray(entry.unresolved_items) || entry.unresolved_items.length === 0)) {
324
+ errors.push(`spec generation audit inferred or partial files must declare unresolved_items for ${canonicalPath}`);
325
+ }
326
+
327
+ auditEntryByRelativePath.set(relativePath, {
328
+ ...entry,
329
+ relativePath,
330
+ });
331
+ }
332
+
333
+ const missingAuditEntries = [];
334
+ const requiredFiles = (specTreeModel.requiredFilesByProfile[specTreeModel.profile] ?? [])
335
+ .map((entry) => path.posix.relative(specTreeModel.canonicalRoot, entry))
336
+ .filter((entry) => !entry.startsWith("_meta/"));
337
+
338
+ for (const classifiedFile of auditedFiles) {
339
+ const auditEntry = auditEntryByRelativePath.get(classifiedFile.path);
340
+ if (!auditEntry) {
341
+ missingAuditEntries.push(classifiedFile.path);
342
+ continue;
343
+ }
344
+
345
+ if (auditEntry.file_class !== classifiedFile.classId && !(classifiedFile.path === "INDEX.md" && auditEntry.file_class === "index")) {
346
+ errors.push(`spec generation audit file_class does not match canonical tree classification for ${classifiedFile.path}: expected ${classifiedFile.classId}`);
347
+ }
348
+ }
349
+
350
+ if (missingAuditEntries.length > 0) {
351
+ errors.push(`spec generation audit is missing file entries for canonical files: ${missingAuditEntries.join(", ")}`);
352
+ }
353
+
354
+ for (const requiredFile of requiredFiles) {
355
+ const auditEntry = auditEntryByRelativePath.get(requiredFile);
356
+ if (!auditEntry) {
357
+ errors.push(`required canonical file is missing an audit entry: ${requiredFile}`);
358
+ continue;
359
+ }
360
+ if (auditEntry.coverage_status === "placeholder_not_allowed") {
361
+ errors.push(`required canonical file must not be placeholder_not_allowed: ${requiredFile}`);
362
+ }
363
+ }
364
+
365
+ for (const [relativePath] of auditEntryByRelativePath) {
366
+ if (!auditedFiles.some((entry) => entry.path === relativePath)) {
367
+ errors.push(`spec generation audit entry points to a non-existent canonical file: ${relativePath}`);
368
+ }
369
+ }
370
+
371
+ const unresolvedCount = Array.from(auditEntryByRelativePath.values())
372
+ .filter((entry) => Array.isArray(entry.unresolved_items) && entry.unresolved_items.length > 0)
373
+ .length;
374
+ const inferredCount = Array.from(auditEntryByRelativePath.values())
375
+ .filter((entry) => entry.source_basis === "inferred" || entry.source_basis === "mixed_grounded_and_inferred")
376
+ .length;
377
+ const partialCount = Array.from(auditEntryByRelativePath.values())
378
+ .filter((entry) => entry.coverage_status === "partial")
379
+ .length;
380
+ const placeholderCount = Array.from(auditEntryByRelativePath.values())
381
+ .filter((entry) => entry.coverage_status === "placeholder_not_allowed")
382
+ .length;
383
+ const completeCount = Array.from(auditEntryByRelativePath.values())
384
+ .filter((entry) => entry.coverage_status === "complete")
385
+ .length;
386
+
387
+ return {
388
+ ok: errors.length === 0,
389
+ errors,
390
+ warnings,
391
+ refusal: errors.length === 0
392
+ ? null
393
+ : makeValidatorRefusal(
394
+ VALIDATOR_NATIVE_REFUSAL_CODES.SPEC_AUDIT_INVALID,
395
+ `spec generation audit is invalid: ${path.basename(absoluteAuditPath)}`,
396
+ ),
397
+ summary: {
398
+ canonicalRoot: specTreeModel.canonicalRoot,
399
+ declaredProfile: specTreeModel.profile,
400
+ auditedFiles: auditEntryByRelativePath.size,
401
+ requiredAuditedFiles: requiredFiles.length,
402
+ missingAuditEntries,
403
+ completeFiles: completeCount,
404
+ partialFiles: partialCount,
405
+ placeholderFiles: placeholderCount,
406
+ unresolvedFiles: unresolvedCount,
407
+ inferredFiles: inferredCount,
408
+ },
409
+ };
410
+ }
@@ -0,0 +1,83 @@
1
+ export {
2
+ buildBlueprintAuditPayload,
3
+ formatBlueprintAuditPayload,
4
+ writeBlueprintAuditArtifact,
5
+ } from "./blueprint-audit.mjs";
6
+ export {
7
+ loadAdmittedAdapterProfiles,
8
+ loadAdapterProfile,
9
+ selectAdapterProfile,
10
+ } from "./adapter-profiles.mjs";
11
+ export {
12
+ appendGitignoreEntries,
13
+ pathExists,
14
+ readTextIfFile,
15
+ } from "./fs-helpers.mjs";
16
+ export { loadExternalExecutionArtifactsConfig, validateHighRiskExecutionArtifactRefs } from "./external-execution.mjs";
17
+ export {
18
+ mergeOrderedPaths,
19
+ parsePathRequirements,
20
+ parseSkillSection,
21
+ parseYamlText,
22
+ readTopLevelKeys,
23
+ readYamlList,
24
+ readYamlScalar,
25
+ } from "./yaml-helpers.mjs";
26
+ export {
27
+ integrateEntrypoints,
28
+ previewEntrypointIntegration,
29
+ previewEntrypointRemoval,
30
+ removeManagedEntrypoints,
31
+ } from "./entrypoints.mjs";
32
+ export {
33
+ inspectBootstrapCompatibility,
34
+ previewBootstrapRemoval,
35
+ previewBootstrapWrites,
36
+ removeManagedBootstrapFiles,
37
+ writeMissingBootstrapFiles,
38
+ } from "./bootstrap.mjs";
39
+ export {
40
+ findCommandGatingRule,
41
+ loadBlueprintReference,
42
+ loadCommandGatingMatrix,
43
+ loadDocSpecAuditContract,
44
+ loadExternalHostCompatibilityContract,
45
+ loadHighRiskAdmissionContract,
46
+ loadHighRiskExecutionContract,
47
+ loadHighRiskSchemaContracts,
48
+ loadSpecGenerationInputsConfig,
49
+ loadSpecGenerationInputsContract,
50
+ loadSpecTreeModelContract,
51
+ loadSpecReconstructionContract,
52
+ validateDocSpecAuditSummary,
53
+ validateHighRiskAdmissionsSpec,
54
+ validateHighRiskAdmissionRecord,
55
+ validateHighRiskExecutionSummary,
56
+ validateSpecReconstructionSummary,
57
+ } from "./contracts.mjs";
58
+ export {
59
+ formatDoctorResult,
60
+ inspectDoctorState,
61
+ } from "./doctor.mjs";
62
+ export {
63
+ buildHighRiskAdmissionPayload,
64
+ formatHighRiskAdmissionPayload,
65
+ writeHighRiskAdmission,
66
+ } from "./high-risk-admission.mjs";
67
+ export {
68
+ buildHandoffPayload,
69
+ formatHandoffPayload,
70
+ formatHandoffPrompt,
71
+ formatStartPastePrompt,
72
+ getStartHostOption,
73
+ resolveStartHostChoice,
74
+ START_HOST_OPTIONS,
75
+ writeHandoffJsonArtifact,
76
+ writeHandoffPromptArtifacts,
77
+ } from "./handoff.mjs";
78
+ export {
79
+ buildCloseoutPayload,
80
+ formatCloseoutPayload,
81
+ loadImportedCloseoutOptions,
82
+ validateImportedCloseoutShape,
83
+ } from "./closeout.mjs";