@nimiplatform/nimi-coding 0.2.8 → 0.3.1
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.
- package/CHANGELOG.md +10 -90
- package/CONTRIBUTING.md +5 -34
- package/README.md +47 -350
- package/README.zh-CN.md +33 -287
- package/cli/commands/start.mjs +48 -688
- package/cli/commands/validate-spec-audit.mjs +1 -5
- package/cli/commands/validate-spec-governance.mjs +20 -0
- package/cli/constants.mjs +9 -417
- package/cli/help.mjs +28 -145
- package/cli/index.mjs +5 -33
- package/cli/lib/blueprint-audit.mjs +5 -12
- package/cli/lib/bootstrap.mjs +0 -9
- package/cli/lib/contracts.mjs +0 -163
- package/cli/lib/doctor.mjs +155 -10
- package/cli/lib/entrypoints.mjs +63 -167
- package/cli/lib/internal/contracts-loaders.mjs +0 -85
- package/cli/lib/internal/contracts-parse.mjs +84 -520
- package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +13 -8
- package/cli/lib/internal/surface-taxonomy-validators.mjs +316 -66
- package/cli/lib/internal/validators-shared.mjs +0 -16
- package/cli/lib/internal/validators-spec-helpers.mjs +7 -140
- package/cli/lib/internal/validators-spec.mjs +103 -342
- package/cli/lib/shared.mjs +1 -42
- package/cli/lib/validators.mjs +0 -48
- package/cli/seeds/seed-policy.yaml +0 -3
- package/config/bootstrap.yaml +4 -4
- package/config/spec-generation-inputs.yaml +6 -12
- package/contracts/domain-admission.schema.yaml +3 -3
- package/contracts/migration-inventory.schema.yaml +12 -75
- package/contracts/negative-fixtures.yaml +19 -99
- package/contracts/placement-contract.schema.yaml +49 -78
- package/contracts/projection-edge.schema.yaml +37 -121
- package/contracts/shared-enums.yaml +4 -8
- package/contracts/spec-generation-inputs.schema.yaml +15 -112
- package/contracts/spec-layout.schema.yaml +32 -0
- package/contracts/surface-taxonomy.schema.yaml +43 -94
- package/contracts/table-family.schema.yaml +1 -1
- package/contracts/tracked-output-admission.schema.yaml +13 -66
- package/methodology/core.yaml +33 -25
- package/methodology/four-closure-policy.yaml +18 -26
- package/methodology/role-separation-policy.yaml +21 -27
- package/methodology/spec-reconstruction.yaml +17 -34
- package/package.json +2 -4
- package/spec/_meta/spec-tree-model.yaml +5 -101
- package/spec/product-scope.yaml +25 -57
- package/adapters/README.md +0 -25
- package/adapters/claude/README.md +0 -89
- package/adapters/claude/profile.yaml +0 -70
- package/adapters/codex/README.md +0 -53
- package/adapters/codex/profile.yaml +0 -78
- package/adapters/oh-my-codex/README.md +0 -184
- package/adapters/oh-my-codex/profile.yaml +0 -46
- package/cli/commands/admit-high-risk-decision.mjs +0 -108
- package/cli/commands/audit-sweep.mjs +0 -364
- package/cli/commands/closeout.mjs +0 -186
- package/cli/commands/decide-high-risk-execution.mjs +0 -124
- package/cli/commands/handoff.mjs +0 -123
- package/cli/commands/ingest-high-risk-execution.mjs +0 -95
- package/cli/commands/review-high-risk-execution.mjs +0 -95
- package/cli/commands/sweep-design.mjs +0 -295
- package/cli/commands/sweep.mjs +0 -22
- package/cli/commands/topic-formatters.mjs +0 -382
- package/cli/commands/topic-goal.mjs +0 -33
- package/cli/commands/topic-options-shared.mjs +0 -27
- package/cli/commands/topic-options-workflow.mjs +0 -767
- package/cli/commands/topic-options.mjs +0 -626
- package/cli/commands/topic-runner.mjs +0 -169
- package/cli/commands/topic.mjs +0 -795
- package/cli/commands/validate-acceptance.mjs +0 -5
- package/cli/commands/validate-execution-packet.mjs +0 -5
- package/cli/commands/validate-orchestration-state.mjs +0 -5
- package/cli/commands/validate-prompt.mjs +0 -5
- package/cli/commands/validate-worker-output.mjs +0 -5
- package/cli/lib/adapter-profiles.mjs +0 -403
- package/cli/lib/audit-execution.mjs +0 -52
- package/cli/lib/audit-sweep-runtime/admissions.mjs +0 -508
- package/cli/lib/audit-sweep-runtime/audit-validity.mjs +0 -356
- package/cli/lib/audit-sweep-runtime/chunks.mjs +0 -697
- package/cli/lib/audit-sweep-runtime/claude-auditor.mjs +0 -658
- package/cli/lib/audit-sweep-runtime/closeout.mjs +0 -144
- package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +0 -654
- package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +0 -527
- package/cli/lib/audit-sweep-runtime/common.mjs +0 -341
- package/cli/lib/audit-sweep-runtime/coverage-quality.mjs +0 -172
- package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +0 -155
- package/cli/lib/audit-sweep-runtime/format.mjs +0 -57
- package/cli/lib/audit-sweep-runtime/ingest.mjs +0 -486
- package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +0 -425
- package/cli/lib/audit-sweep-runtime/inventory.mjs +0 -798
- package/cli/lib/audit-sweep-runtime/ledger.mjs +0 -315
- package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +0 -101
- package/cli/lib/audit-sweep-runtime/remediation.mjs +0 -349
- package/cli/lib/audit-sweep-runtime/rerun.mjs +0 -129
- package/cli/lib/audit-sweep-runtime/risk-budget.mjs +0 -300
- package/cli/lib/audit-sweep-runtime/status.mjs +0 -62
- package/cli/lib/audit-sweep-runtime/validators-ledger.mjs +0 -215
- package/cli/lib/audit-sweep-runtime/validators.mjs +0 -797
- package/cli/lib/audit-sweep.mjs +0 -19
- package/cli/lib/authority-convergence.mjs +0 -720
- package/cli/lib/closeout.mjs +0 -732
- package/cli/lib/codex-sdk-runner.mjs +0 -76
- package/cli/lib/external-execution.mjs +0 -101
- package/cli/lib/handoff.mjs +0 -812
- package/cli/lib/high-risk-admission.mjs +0 -512
- package/cli/lib/high-risk-decision.mjs +0 -353
- package/cli/lib/high-risk-ingest.mjs +0 -321
- package/cli/lib/high-risk-review.mjs +0 -267
- package/cli/lib/internal/contracts-parse-high-risk.mjs +0 -131
- package/cli/lib/internal/contracts-validators.mjs +0 -399
- package/cli/lib/internal/doctor-bootstrap-surface.mjs +0 -406
- package/cli/lib/internal/doctor-delegated-surface.mjs +0 -256
- package/cli/lib/internal/doctor-finalize.mjs +0 -383
- package/cli/lib/internal/doctor-format.mjs +0 -286
- package/cli/lib/internal/doctor-inspectors.mjs +0 -327
- package/cli/lib/internal/doctor-state.mjs +0 -205
- package/cli/lib/internal/validators-artifacts.mjs +0 -515
- package/cli/lib/sweep-design-runtime/common.mjs +0 -246
- package/cli/lib/sweep-design-runtime/engine.mjs +0 -733
- package/cli/lib/sweep-design-runtime/fix-topic.mjs +0 -414
- package/cli/lib/sweep-design-runtime/lifecycle.mjs +0 -54
- package/cli/lib/sweep-design-runtime/results.mjs +0 -324
- package/cli/lib/sweep-design.mjs +0 -8
- package/cli/lib/topic-artifacts.mjs +0 -186
- package/cli/lib/topic-authority-coverage.mjs +0 -73
- package/cli/lib/topic-closeout.mjs +0 -560
- package/cli/lib/topic-common.mjs +0 -404
- package/cli/lib/topic-decisions.mjs +0 -332
- package/cli/lib/topic-draft-packets.mjs +0 -167
- package/cli/lib/topic-execution.mjs +0 -533
- package/cli/lib/topic-goal.mjs +0 -440
- package/cli/lib/topic-ledger.mjs +0 -281
- package/cli/lib/topic-lifecycle-artifacts.mjs +0 -173
- package/cli/lib/topic-root-validation.mjs +0 -288
- package/cli/lib/topic-runner-commands.mjs +0 -174
- package/cli/lib/topic-runner-deferral.mjs +0 -532
- package/cli/lib/topic-runner-stale-gates.mjs +0 -114
- package/cli/lib/topic-runner-validation.mjs +0 -138
- package/cli/lib/topic-runner.mjs +0 -727
- package/cli/lib/topic-scaffold.mjs +0 -252
- package/cli/lib/topic-waves.mjs +0 -403
- package/cli/lib/topic.mjs +0 -81
- package/config/audit-execution-artifacts.yaml +0 -20
- package/config/external-execution-artifacts.yaml +0 -16
- package/config/host-adapter.yaml +0 -30
- package/config/host-profile.yaml +0 -29
- package/config/installer-evidence.yaml +0 -31
- package/config/skill-installer.yaml +0 -23
- package/config/skill-manifest.yaml +0 -48
- package/config/skills.yaml +0 -30
- package/contracts/acceptance.schema.yaml +0 -16
- package/contracts/admission-checklist.schema.yaml +0 -15
- package/contracts/audit-chunk.schema.yaml +0 -123
- package/contracts/audit-closeout.schema.yaml +0 -51
- package/contracts/audit-finding.schema.yaml +0 -61
- package/contracts/audit-ledger.schema.yaml +0 -138
- package/contracts/audit-plan.schema.yaml +0 -138
- package/contracts/audit-remediation-map.schema.yaml +0 -52
- package/contracts/audit-rerun.schema.yaml +0 -31
- package/contracts/audit-sweep-result.yaml +0 -53
- package/contracts/authority-convergence-audit.schema.yaml +0 -19
- package/contracts/closeout.schema.yaml +0 -25
- package/contracts/decision-review.schema.yaml +0 -16
- package/contracts/doc-spec-audit-result.yaml +0 -19
- package/contracts/execution-packet.schema.yaml +0 -49
- package/contracts/external-host-compatibility.yaml +0 -22
- package/contracts/forbidden-shortcuts.catalog.yaml +0 -23
- package/contracts/high-risk-admission.schema.yaml +0 -23
- package/contracts/high-risk-execution-result.yaml +0 -20
- package/contracts/orchestration-state.schema.yaml +0 -41
- package/contracts/overflow-continuation.schema.yaml +0 -12
- package/contracts/packet.schema.yaml +0 -30
- package/contracts/pending-note.schema.yaml +0 -17
- package/contracts/prompt.schema.yaml +0 -12
- package/contracts/remediation.schema.yaml +0 -16
- package/contracts/result.schema.yaml +0 -24
- package/contracts/spec-reconstruction-result.yaml +0 -41
- package/contracts/sweep-design-result.yaml +0 -349
- package/contracts/topic-goal.schema.yaml +0 -87
- package/contracts/topic-run-ledger.schema.yaml +0 -72
- package/contracts/topic-step-decision.schema.yaml +0 -45
- package/contracts/topic.schema.yaml +0 -65
- package/contracts/true-close.schema.yaml +0 -15
- package/contracts/wave.schema.yaml +0 -29
- package/contracts/worker-output.schema.yaml +0 -15
- package/contracts/workflow-consumer.schema.yaml +0 -110
- package/methodology/audit-sweep-p0p1-recall.yaml +0 -45
- package/methodology/authority-convergence-policy.yaml +0 -42
- package/methodology/overflow-continuation-policy.yaml +0 -14
- package/methodology/skill-exchange-projection.yaml +0 -114
- package/methodology/skill-handoff.yaml +0 -34
- package/methodology/skill-installer-result.yaml +0 -27
- package/methodology/skill-installer-summary-projection.yaml +0 -181
- package/methodology/skill-runtime.yaml +0 -23
- package/methodology/spec-target-truth-profile.yaml +0 -46
- package/methodology/topic-lifecycle-report.yaml +0 -144
- package/methodology/topic-lifecycle.yaml +0 -37
- package/methodology/topic-naming-ontology.yaml +0 -21
- package/methodology/topic-ontology.yaml +0 -38
- package/methodology/topic-validation-policy.yaml +0 -9
- package/methodology/wave-dag-policy.yaml +0 -14
- package/spec/_meta/command-gating-matrix.yaml +0 -143
- package/spec/_meta/generate-drift-migration-checklist.yaml +0 -137
- package/spec/_meta/governance-routing-cutover-checklist.yaml +0 -35
- package/spec/_meta/phase2-impacted-surface-matrix.yaml +0 -44
- package/spec/_meta/spec-authority-cutover-readiness.yaml +0 -102
- 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
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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:
|
|
52
|
+
ok: false,
|
|
87
53
|
errors,
|
|
88
54
|
warnings,
|
|
89
|
-
refusal:
|
|
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
|
|
157
|
-
const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
|
|
61
|
+
const generationInputs = await loadSpecGenerationInputsConfig(projectRoot);
|
|
158
62
|
const errors = [];
|
|
159
63
|
const warnings = [];
|
|
160
64
|
|
|
161
|
-
if (!
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
|
92
|
+
const canonicalRoot = generationInputs.canonicalTargetRoot;
|
|
93
|
+
const requiredFiles = await loadRequiredFiles(projectRoot);
|
|
197
94
|
const missingRequired = requiredFiles
|
|
198
|
-
.map((entry) => path.posix.relative(
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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:
|
|
253
|
-
canonicalRoot
|
|
129
|
+
profile: "surface_taxonomy_v2",
|
|
130
|
+
canonicalRoot,
|
|
254
131
|
totalFiles: files.length,
|
|
255
132
|
requiredFiles: requiredFiles.length,
|
|
256
133
|
missingRequired,
|
|
257
|
-
classifiedFiles:
|
|
258
|
-
unexpectedFiles:
|
|
259
|
-
conflictingFiles:
|
|
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
|
|
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 (!
|
|
149
|
+
if (!generationInputs.ok || generationInputs.mode !== "class_filtered") {
|
|
275
150
|
return {
|
|
276
151
|
ok: false,
|
|
277
|
-
errors: [`invalid spec
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
364
|
-
|
|
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 ?? "") !==
|
|
377
|
-
errors.push(
|
|
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,
|
|
380
|
-
errors.push("spec generation audit input_roots must
|
|
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
|
|
384
|
-
const treeFiles = await collectTreeFiles(canonicalRootPath);
|
|
239
|
+
const treeFiles = await collectTreeFiles(path.resolve(projectRoot, canonicalRoot));
|
|
385
240
|
if (treeFiles.length === 0) {
|
|
386
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
433
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
477
|
-
|
|
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}/`)
|
|
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
|
|
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
|
|
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
|
|
503
|
-
errors.push(`spec generation audit source_refs must be
|
|
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
|
|
326
|
+
errors.push(`spec generation audit unresolved_items must be strings for ${canonicalPath}`);
|
|
521
327
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
errors.push(`
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
|
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
|
|
565
|
-
if (!
|
|
566
|
-
|
|
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
|
-
|
|
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:
|
|
369
|
+
declaredProfile: "surface_taxonomy_v2",
|
|
370
|
+
auditedFiles: values.length,
|
|
610
371
|
requiredAuditedFiles: requiredFiles.length,
|
|
611
372
|
missingAuditEntries,
|
|
612
|
-
completeFiles:
|
|
613
|
-
partialFiles:
|
|
614
|
-
placeholderFiles:
|
|
615
|
-
unresolvedFiles:
|
|
616
|
-
inferredFiles:
|
|
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
|
}
|