@nimiplatform/nimi-coding 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -92
- 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/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
|
@@ -28,13 +28,18 @@ const GENERIC_PNPM_COMMANDS = new Set([
|
|
|
28
28
|
"run",
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
function collectKnownPnpmScripts(projectRoot, failures) {
|
|
32
|
+
const packagePath = path.join(projectRoot, "package.json");
|
|
33
|
+
if (!fs.existsSync(packagePath)) {
|
|
34
|
+
return new Set();
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const rootPkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
38
|
+
return new Set(Object.keys(rootPkg?.scripts || {}));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
failures.push(`package.json: invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
41
|
+
return new Set();
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
function validatePnpmCommand(command, knownScripts, failures, relPath) {
|
|
@@ -91,7 +96,7 @@ export function evaluateAgentsFreshnessCheck(options = {}) {
|
|
|
91
96
|
: DEFAULT_STALE_TOKENS;
|
|
92
97
|
|
|
93
98
|
const failures = [];
|
|
94
|
-
const knownScripts = collectKnownPnpmScripts(projectRoot);
|
|
99
|
+
const knownScripts = collectKnownPnpmScripts(projectRoot, failures);
|
|
95
100
|
|
|
96
101
|
for (const target of targets) {
|
|
97
102
|
const abs = path.join(projectRoot, target.rel);
|
|
@@ -6,7 +6,7 @@ import { pathExists } from "../fs-helpers.mjs";
|
|
|
6
6
|
import { parseYamlText } from "../yaml-helpers.mjs";
|
|
7
7
|
import { isPlainObject } from "../value-helpers.mjs";
|
|
8
8
|
|
|
9
|
-
const SURFACE_RESULT_CONTRACT = "nimicoding.surface-validator-result.
|
|
9
|
+
const SURFACE_RESULT_CONTRACT = "nimicoding.surface-validator-result.v2";
|
|
10
10
|
|
|
11
11
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
|
|
12
12
|
|
|
@@ -18,12 +18,12 @@ const CONTRACT_REFS = {
|
|
|
18
18
|
tableFamily: "contracts/table-family.schema.yaml",
|
|
19
19
|
domainAdmission: "contracts/domain-admission.schema.yaml",
|
|
20
20
|
trackedOutputAdmission: "contracts/tracked-output-admission.schema.yaml",
|
|
21
|
-
|
|
21
|
+
specLayout: "contracts/spec-layout.schema.yaml",
|
|
22
22
|
negativeFixtures: "contracts/negative-fixtures.yaml",
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
const TEXT_RULE_BODY_PATTERN = /\bMUST(?:\s+NOT)?\b|\bmust\s+not\b|必须|不得|fail(?:s|ed)?\s+closed/i;
|
|
26
|
-
const
|
|
26
|
+
const PRODUCT_RULE_DEFINITION_PATTERN = /^\s*(?:[-*]\s*)?(?:rule_id|rule id)\s*[:|]/im;
|
|
27
27
|
const GENERATED_REF_PATTERN = /\.nimi\/spec\/[^)\s]+\/kernel\/generated\/|\.nimi\/spec\/generated\/|kernel\/generated\//;
|
|
28
28
|
|
|
29
29
|
function toPosix(value) {
|
|
@@ -65,9 +65,227 @@ async function loadSurfaceContracts(projectRoot) {
|
|
|
65
65
|
if (hostDomainAdmission) {
|
|
66
66
|
contracts.domainAdmission = hostDomainAdmission;
|
|
67
67
|
}
|
|
68
|
+
const hostSpecLayout = await readHostYamlAt(projectRoot, ".nimi/config/spec-layout.yaml");
|
|
69
|
+
contracts.hostSpecLayout = parseHostSpecLayout(
|
|
70
|
+
hostSpecLayout,
|
|
71
|
+
contracts.specLayout,
|
|
72
|
+
contracts.tableFamily,
|
|
73
|
+
);
|
|
68
74
|
return contracts;
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
function isPortableRef(ref) {
|
|
78
|
+
return typeof ref === "string"
|
|
79
|
+
&& ref.length > 0
|
|
80
|
+
&& !path.posix.isAbsolute(ref)
|
|
81
|
+
&& !ref.startsWith("./")
|
|
82
|
+
&& !ref.split("/").includes("..")
|
|
83
|
+
&& path.posix.normalize(ref) === ref;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isRefWithin(ref, root) {
|
|
87
|
+
return ref === root || ref.startsWith(`${root}/`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hasExactFields(value, requiredFields) {
|
|
91
|
+
if (!isPlainObject(value)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const actual = Object.keys(value).sort();
|
|
95
|
+
const expected = [...requiredFields].sort();
|
|
96
|
+
return actual.length === expected.length
|
|
97
|
+
&& actual.every((field, index) => field === expected[index]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isLiteralPortableRef(ref) {
|
|
101
|
+
return isPortableRef(ref) && !/[\*{}]/.test(ref);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isGeneratedProjectionRoot(ref, canonicalRoot) {
|
|
105
|
+
return ref === `${canonicalRoot}/generated`
|
|
106
|
+
|| ref.startsWith(`${canonicalRoot}/generated/`)
|
|
107
|
+
|| /^\.nimi\/spec\/[^/]+\/kernel\/generated(?:\/.*)?$/.test(ref);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseHostSpecLayout(hostConfig, schemaContract, tableFamilyContract) {
|
|
111
|
+
if (!hostConfig) {
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
present: false,
|
|
115
|
+
errors: [],
|
|
116
|
+
canonicalRoot: ".nimi/spec",
|
|
117
|
+
hostInstructionPaths: [],
|
|
118
|
+
trackedDerivedProjections: [],
|
|
119
|
+
tableFamilyExtensions: [],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const errors = [];
|
|
124
|
+
const parsed = hostConfig.data;
|
|
125
|
+
const layout = parsed?.spec_layout;
|
|
126
|
+
const requiredDocumentFields = Array.isArray(schemaContract.data?.required_top_level_fields)
|
|
127
|
+
? schemaContract.data.required_top_level_fields.map(String)
|
|
128
|
+
: [];
|
|
129
|
+
const requiredLayoutFields = Array.isArray(schemaContract.data?.layout_required_fields)
|
|
130
|
+
? schemaContract.data.layout_required_fields.map(String)
|
|
131
|
+
: [];
|
|
132
|
+
const requiredProjectionFields = Array.isArray(schemaContract.data?.tracked_derived_projection_required_fields)
|
|
133
|
+
? schemaContract.data.tracked_derived_projection_required_fields.map(String)
|
|
134
|
+
: [];
|
|
135
|
+
const requiredFamilyFields = Array.isArray(schemaContract.data?.table_family_extension_required_fields)
|
|
136
|
+
? schemaContract.data.table_family_extension_required_fields.map(String)
|
|
137
|
+
: [];
|
|
138
|
+
const packageFamilyIds = new Set(
|
|
139
|
+
(tableFamilyContract.data?.table_families ?? []).map((entry) => String(entry.table_family)),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (parsed?.version !== 1 || parsed?.contract_ref !== ".nimi/contracts/spec-layout.schema.yaml" || !isPlainObject(layout)) {
|
|
143
|
+
errors.push("invalid_spec_layout_config: .nimi/config/spec-layout.yaml");
|
|
144
|
+
}
|
|
145
|
+
if (!hasExactFields(parsed, requiredDocumentFields)) {
|
|
146
|
+
errors.push("invalid_spec_layout_document_fields: .nimi/config/spec-layout.yaml");
|
|
147
|
+
}
|
|
148
|
+
if (!hasExactFields(layout, requiredLayoutFields)) {
|
|
149
|
+
errors.push("invalid_spec_layout_fields: .nimi/config/spec-layout.yaml");
|
|
150
|
+
}
|
|
151
|
+
for (const field of requiredLayoutFields) {
|
|
152
|
+
if (!(field in (layout ?? {}))) {
|
|
153
|
+
errors.push(`missing_spec_layout_field: .nimi/config/spec-layout.yaml: ${field}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const canonicalRoot = typeof layout?.canonical_root === "string" ? layout.canonical_root : ".nimi/spec";
|
|
158
|
+
if (!isPortableRef(canonicalRoot) || canonicalRoot !== ".nimi/spec") {
|
|
159
|
+
errors.push(`invalid_spec_layout_canonical_root: ${canonicalRoot}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!Array.isArray(layout?.host_instruction_paths)) {
|
|
163
|
+
errors.push("invalid_host_instruction_paths: .nimi/config/spec-layout.yaml");
|
|
164
|
+
}
|
|
165
|
+
const hostInstructionPaths = Array.isArray(layout?.host_instruction_paths)
|
|
166
|
+
? layout.host_instruction_paths.map(String)
|
|
167
|
+
: [];
|
|
168
|
+
const seenInstructionPaths = new Set();
|
|
169
|
+
for (const ref of hostInstructionPaths) {
|
|
170
|
+
if (!isPortableRef(ref)
|
|
171
|
+
|| !ref.startsWith(`${canonicalRoot}/`)
|
|
172
|
+
|| ref === `${canonicalRoot}/INDEX.md`
|
|
173
|
+
|| ref.includes("/kernel/")
|
|
174
|
+
|| !ref.endsWith(".md")
|
|
175
|
+
|| /[\*{}]/.test(path.posix.basename(ref))) {
|
|
176
|
+
errors.push(`invalid_host_instruction_path: ${ref}`);
|
|
177
|
+
}
|
|
178
|
+
if (seenInstructionPaths.has(ref)) {
|
|
179
|
+
errors.push(`duplicate_host_instruction_path: ${ref}`);
|
|
180
|
+
}
|
|
181
|
+
seenInstructionPaths.add(ref);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!Array.isArray(layout?.tracked_derived_projections)) {
|
|
185
|
+
errors.push("invalid_tracked_derived_projections: .nimi/config/spec-layout.yaml");
|
|
186
|
+
}
|
|
187
|
+
const trackedDerivedProjections = Array.isArray(layout?.tracked_derived_projections)
|
|
188
|
+
? layout.tracked_derived_projections
|
|
189
|
+
: [];
|
|
190
|
+
const seenProjectionIds = new Set();
|
|
191
|
+
const seenProjectionRoots = new Set();
|
|
192
|
+
for (const projection of trackedDerivedProjections) {
|
|
193
|
+
if (!isPlainObject(projection)) {
|
|
194
|
+
errors.push("invalid_tracked_derived_projection: non-object entry");
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!hasExactFields(projection, requiredProjectionFields)) {
|
|
198
|
+
errors.push(`invalid_tracked_derived_projection_fields: ${String(projection.projection_id ?? "unknown")}`);
|
|
199
|
+
}
|
|
200
|
+
for (const field of requiredProjectionFields) {
|
|
201
|
+
if (!(field in projection)) {
|
|
202
|
+
errors.push(`missing_tracked_derived_projection_field: ${String(projection.projection_id ?? "unknown")}: ${field}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const projectionId = String(projection.projection_id ?? "");
|
|
206
|
+
const root = String(projection.root ?? "");
|
|
207
|
+
const sourceRoots = Array.isArray(projection.source_roots) ? projection.source_roots.map(String) : [];
|
|
208
|
+
if (!projectionId || seenProjectionIds.has(projectionId)) {
|
|
209
|
+
errors.push(`invalid_or_duplicate_projection_id: ${projectionId || "empty"}`);
|
|
210
|
+
}
|
|
211
|
+
const overlapsExistingRoot = [...seenProjectionRoots].some((existingRoot) => (
|
|
212
|
+
isRefWithin(root, existingRoot) || isRefWithin(existingRoot, root)
|
|
213
|
+
));
|
|
214
|
+
if (!isLiteralPortableRef(root)
|
|
215
|
+
|| !isGeneratedProjectionRoot(root, canonicalRoot)
|
|
216
|
+
|| overlapsExistingRoot) {
|
|
217
|
+
errors.push(`invalid_or_duplicate_projection_root: ${root || "empty"}`);
|
|
218
|
+
}
|
|
219
|
+
if (sourceRoots.length === 0 || sourceRoots.some((ref) => (
|
|
220
|
+
!isLiteralPortableRef(ref)
|
|
221
|
+
|| !ref.startsWith(`${canonicalRoot}/`)
|
|
222
|
+
|| isRefWithin(ref, root)
|
|
223
|
+
))) {
|
|
224
|
+
errors.push(`invalid_projection_source_roots: ${projectionId || "unknown"}`);
|
|
225
|
+
}
|
|
226
|
+
for (const field of ["generate_command", "drift_check_command", "required_marker"]) {
|
|
227
|
+
if (typeof projection[field] !== "string" || projection[field].trim().length === 0) {
|
|
228
|
+
errors.push(`invalid_tracked_derived_projection_field: ${projectionId || "unknown"}: ${field}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
seenProjectionIds.add(projectionId);
|
|
232
|
+
seenProjectionRoots.add(root);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!Array.isArray(layout?.table_family_extensions)) {
|
|
236
|
+
errors.push("invalid_table_family_extensions: .nimi/config/spec-layout.yaml");
|
|
237
|
+
}
|
|
238
|
+
const tableFamilyExtensions = Array.isArray(layout?.table_family_extensions)
|
|
239
|
+
? layout.table_family_extensions
|
|
240
|
+
: [];
|
|
241
|
+
const seenFamilies = new Set();
|
|
242
|
+
for (const family of tableFamilyExtensions) {
|
|
243
|
+
if (!isPlainObject(family)) {
|
|
244
|
+
errors.push("invalid_table_family_extension: non-object entry");
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (!hasExactFields(family, requiredFamilyFields)) {
|
|
248
|
+
errors.push(`invalid_table_family_extension_fields: ${String(family.table_family ?? "unknown")}`);
|
|
249
|
+
}
|
|
250
|
+
for (const field of requiredFamilyFields) {
|
|
251
|
+
if (!(field in family)) {
|
|
252
|
+
errors.push(`missing_table_family_extension_field: ${String(family.table_family ?? "unknown")}: ${field}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const familyId = String(family.table_family ?? "");
|
|
256
|
+
if (!/^[a-z][a-z0-9_]*$/.test(familyId) || seenFamilies.has(familyId)) {
|
|
257
|
+
errors.push(`invalid_or_duplicate_table_family_extension: ${familyId || "empty"}`);
|
|
258
|
+
}
|
|
259
|
+
if (packageFamilyIds.has(familyId)) {
|
|
260
|
+
errors.push(`table_family_extension_shadows_package_family: ${familyId}`);
|
|
261
|
+
}
|
|
262
|
+
if (family.authority_class !== "product_authority_table"
|
|
263
|
+
|| !Array.isArray(family.required_fields)
|
|
264
|
+
|| family.required_fields.length === 0
|
|
265
|
+
|| family.required_fields.some((field) => typeof field !== "string" || field.length === 0)
|
|
266
|
+
|| !family.required_fields.includes("table_family")
|
|
267
|
+
|| !family.required_fields.includes("owner")
|
|
268
|
+
|| new Set(family.required_fields).size !== family.required_fields.length
|
|
269
|
+
|| !Array.isArray(family.forbidden_fields)
|
|
270
|
+
|| family.forbidden_fields.some((field) => typeof field !== "string" || field.length === 0)
|
|
271
|
+
|| new Set(family.forbidden_fields).size !== family.forbidden_fields.length
|
|
272
|
+
|| family.required_fields.some((field) => family.forbidden_fields.includes(field))) {
|
|
273
|
+
errors.push(`invalid_table_family_extension_shape: ${familyId || "unknown"}`);
|
|
274
|
+
}
|
|
275
|
+
seenFamilies.add(familyId);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
ok: errors.length === 0,
|
|
280
|
+
present: true,
|
|
281
|
+
errors,
|
|
282
|
+
canonicalRoot,
|
|
283
|
+
hostInstructionPaths,
|
|
284
|
+
trackedDerivedProjections,
|
|
285
|
+
tableFamilyExtensions,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
71
289
|
async function collectFilesUnder(rootPath) {
|
|
72
290
|
const exists = await pathExists(rootPath);
|
|
73
291
|
if (!exists || !exists.isDirectory()) {
|
|
@@ -100,7 +318,6 @@ async function collectCandidateFiles(projectRoot, rootRef = ".nimi/spec") {
|
|
|
100
318
|
".nimi/derived",
|
|
101
319
|
".nimi/state",
|
|
102
320
|
".nimi/audit",
|
|
103
|
-
".nimi/roadmap",
|
|
104
321
|
];
|
|
105
322
|
const uniqueRoots = [...new Set(roots)];
|
|
106
323
|
const files = [];
|
|
@@ -191,7 +408,10 @@ function tableFamilyContractMap(contracts) {
|
|
|
191
408
|
const families = Array.isArray(contracts.tableFamily.data?.table_families)
|
|
192
409
|
? contracts.tableFamily.data.table_families
|
|
193
410
|
: [];
|
|
194
|
-
|
|
411
|
+
const extensions = contracts.hostSpecLayout?.ok
|
|
412
|
+
? contracts.hostSpecLayout.tableFamilyExtensions
|
|
413
|
+
: [];
|
|
414
|
+
return new Map([...families, ...extensions].map((entry) => [String(entry.table_family), entry]));
|
|
195
415
|
}
|
|
196
416
|
|
|
197
417
|
function domainAdmissionMap(contracts) {
|
|
@@ -211,17 +431,34 @@ function trackedAdmissionRoots(contracts) {
|
|
|
211
431
|
function isTrackedNonProductRef(ref) {
|
|
212
432
|
return ref.startsWith(".nimi/derived/")
|
|
213
433
|
|| ref.startsWith(".nimi/state/")
|
|
214
|
-
|| ref.startsWith(".nimi/audit/")
|
|
215
|
-
|| ref.startsWith(".nimi/roadmap/");
|
|
434
|
+
|| ref.startsWith(".nimi/audit/");
|
|
216
435
|
}
|
|
217
436
|
|
|
218
437
|
function isAdmittedTrackedOutput(ref, admittedRoots) {
|
|
219
438
|
return admittedRoots.some((root) => pathMatchesGlob(ref, root));
|
|
220
439
|
}
|
|
221
440
|
|
|
222
|
-
function
|
|
441
|
+
function projectionForRef(ref, contracts) {
|
|
442
|
+
if (!contracts.hostSpecLayout?.ok) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
return contracts.hostSpecLayout.trackedDerivedProjections.find((entry) => isRefWithin(ref, String(entry.root))) ?? null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function isHostInstructionRef(ref, contracts) {
|
|
449
|
+
return contracts.hostSpecLayout?.ok
|
|
450
|
+
&& contracts.hostSpecLayout.hostInstructionPaths.some((pattern) => pathMatchesGlob(ref, pattern));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function classifyRef(ref, text, parsedYaml, contracts) {
|
|
223
454
|
if (ref.startsWith(".nimi/spec/future/")) {
|
|
224
|
-
return "
|
|
455
|
+
return "unclassified";
|
|
456
|
+
}
|
|
457
|
+
if (isHostInstructionRef(ref, contracts)) {
|
|
458
|
+
return "host_instruction";
|
|
459
|
+
}
|
|
460
|
+
if (projectionForRef(ref, contracts)) {
|
|
461
|
+
return "tracked_derived_projection";
|
|
225
462
|
}
|
|
226
463
|
if (ref.startsWith(".nimi/spec/generated/") || ref.includes("/kernel/generated/")) {
|
|
227
464
|
return "derived_view";
|
|
@@ -233,14 +470,8 @@ function classifyRef(ref, text, parsedYaml) {
|
|
|
233
470
|
if (ref.endsWith("-admission-anchor.yaml")) {
|
|
234
471
|
return "host_projection_anchor";
|
|
235
472
|
}
|
|
236
|
-
if (/cutover|checklist|matrix|readiness|migration/i.test(ref)) {
|
|
237
|
-
return "lifecycle_progress_state";
|
|
238
|
-
}
|
|
239
473
|
return "methodology_authority";
|
|
240
474
|
}
|
|
241
|
-
if (ref === ".nimi/spec/bootstrap-state.yaml") {
|
|
242
|
-
return "lifecycle_progress_state";
|
|
243
|
-
}
|
|
244
475
|
if (ref === ".nimi/spec/product-scope.yaml") {
|
|
245
476
|
return "methodology_authority";
|
|
246
477
|
}
|
|
@@ -278,9 +509,6 @@ function classifyRef(ref, text, parsedYaml) {
|
|
|
278
509
|
if (ref.startsWith(".nimi/local/") || ref.startsWith(".local/")) {
|
|
279
510
|
return "operational_local_artifact";
|
|
280
511
|
}
|
|
281
|
-
if (ref.startsWith(".nimi/topics/")) {
|
|
282
|
-
return "lifecycle_progress_state";
|
|
283
|
-
}
|
|
284
512
|
if (ref.startsWith(".nimi/contracts/") || ref.startsWith(".nimi/methodology/")) {
|
|
285
513
|
return "nimicoding_managed_projection";
|
|
286
514
|
}
|
|
@@ -294,7 +522,6 @@ function classifyRef(ref, text, parsedYaml) {
|
|
|
294
522
|
if (ref.startsWith(".nimi/derived/")) return "derived_view";
|
|
295
523
|
if (ref.startsWith(".nimi/state/")) return "spec_generation_state";
|
|
296
524
|
if (ref.startsWith(".nimi/audit/")) return "audit_evidence_state";
|
|
297
|
-
return "candidate_roadmap";
|
|
298
525
|
}
|
|
299
526
|
return "unclassified";
|
|
300
527
|
}
|
|
@@ -312,7 +539,10 @@ function dispositionFor(ref, surfaceClass, errors) {
|
|
|
312
539
|
if (surfaceClass === "derived_view") {
|
|
313
540
|
return "delete";
|
|
314
541
|
}
|
|
315
|
-
if (
|
|
542
|
+
if (surfaceClass === "host_instruction" || surfaceClass === "tracked_derived_projection") {
|
|
543
|
+
return errors.length > 0 ? "block" : "keep";
|
|
544
|
+
}
|
|
545
|
+
if (["spec_generation_state", "audit_evidence_state", "operational_local_artifact"].includes(surfaceClass)) {
|
|
316
546
|
return "move_local";
|
|
317
547
|
}
|
|
318
548
|
if (errors.length > 0) {
|
|
@@ -331,15 +561,15 @@ function targetRootFor(surfaceClass, ref) {
|
|
|
331
561
|
if (surfaceClass === "derived_view") {
|
|
332
562
|
return "stdout_view";
|
|
333
563
|
}
|
|
334
|
-
if (surfaceClass === "
|
|
564
|
+
if (surfaceClass === "host_instruction" || surfaceClass === "tracked_derived_projection") {
|
|
565
|
+
return ".nimi/spec";
|
|
566
|
+
}
|
|
567
|
+
if (surfaceClass === "spec_generation_state" || surfaceClass === "operational_local_artifact") {
|
|
335
568
|
return ".nimi/local/state";
|
|
336
569
|
}
|
|
337
570
|
if (surfaceClass === "audit_evidence_state") {
|
|
338
571
|
return ".nimi/local/audit";
|
|
339
572
|
}
|
|
340
|
-
if (surfaceClass === "candidate_roadmap") {
|
|
341
|
-
return ".nimi/topics";
|
|
342
|
-
}
|
|
343
573
|
if (surfaceClass === "unclassified") {
|
|
344
574
|
return null;
|
|
345
575
|
}
|
|
@@ -356,17 +586,20 @@ function ownerFor(surfaceClass, ref) {
|
|
|
356
586
|
if (surfaceClass === "derived_view") {
|
|
357
587
|
return "generator";
|
|
358
588
|
}
|
|
589
|
+
if (surfaceClass === "tracked_derived_projection") {
|
|
590
|
+
return "generator";
|
|
591
|
+
}
|
|
592
|
+
if (surfaceClass === "host_instruction") {
|
|
593
|
+
return "product_host";
|
|
594
|
+
}
|
|
359
595
|
if (surfaceClass === "spec_generation_state") {
|
|
360
596
|
return "spec_generator";
|
|
361
597
|
}
|
|
362
598
|
if (surfaceClass === "audit_evidence_state") {
|
|
363
|
-
return "
|
|
599
|
+
return "verification_process";
|
|
364
600
|
}
|
|
365
|
-
if (surfaceClass === "
|
|
366
|
-
return "
|
|
367
|
-
}
|
|
368
|
-
if (surfaceClass === "lifecycle_progress_state") {
|
|
369
|
-
return "topic_or_execution_workflow";
|
|
601
|
+
if (surfaceClass === "operational_local_artifact") {
|
|
602
|
+
return "external_tooling";
|
|
370
603
|
}
|
|
371
604
|
return domainForSpecRef(ref) ?? "product_domain";
|
|
372
605
|
}
|
|
@@ -404,9 +637,10 @@ function validationCommandsFor(entryClass) {
|
|
|
404
637
|
product_authority_table: ["pnpm exec nimicoding validate-table-family --profile nimi --root .nimi/spec"],
|
|
405
638
|
support_registry: ["pnpm exec nimicoding validate-table-family --profile nimi --root .nimi/spec"],
|
|
406
639
|
thin_guidance: ["pnpm exec nimicoding validate-guidance-bodies --profile nimi --root .nimi/spec"],
|
|
640
|
+
host_instruction: ["pnpm exec nimicoding validate-placement --profile nimi --root .nimi/spec"],
|
|
641
|
+
tracked_derived_projection: ["pnpm exec nimicoding validate-placement --profile nimi --root .nimi/spec"],
|
|
407
642
|
host_projection_anchor: ["pnpm exec nimicoding validate-projection-edges --profile nimi --root .nimi/spec"],
|
|
408
643
|
nimicoding_managed_projection: ["pnpm exec nimicoding validate-placement --profile nimi --root .nimi/spec"],
|
|
409
|
-
candidate_roadmap: ["pnpm exec nimicoding validate-domain-admission --profile nimi --root .nimi/spec"],
|
|
410
644
|
};
|
|
411
645
|
return commands[entryClass] ?? ["pnpm exec nimicoding validate-placement --profile nimi --root .nimi/spec"];
|
|
412
646
|
}
|
|
@@ -424,6 +658,17 @@ function validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts) {
|
|
|
424
658
|
if (surfaceClass === "derived_view" && ref.startsWith(".nimi/spec/")) {
|
|
425
659
|
addError(errors, "derived_view_under_product_authority_root", ref);
|
|
426
660
|
}
|
|
661
|
+
if (surfaceClass === "tracked_derived_projection") {
|
|
662
|
+
const projection = projectionForRef(ref, contracts);
|
|
663
|
+
if (!projection) {
|
|
664
|
+
addError(errors, "tracked_derived_projection_without_layout_admission", ref);
|
|
665
|
+
} else if (!text.includes(String(projection.required_marker))) {
|
|
666
|
+
addError(errors, "tracked_derived_projection_missing_generated_marker", ref, String(projection.required_marker));
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (surfaceClass === "host_instruction" && PRODUCT_RULE_DEFINITION_PATTERN.test(text)) {
|
|
670
|
+
addError(errors, "host_instruction_defines_product_rule", ref);
|
|
671
|
+
}
|
|
427
672
|
if (surfaceClass === "derived_view" && ref.startsWith(".nimi/local/derived/")) {
|
|
428
673
|
addError(errors, "derived_view_written_to_local_derived", ref);
|
|
429
674
|
}
|
|
@@ -433,15 +678,9 @@ function validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts) {
|
|
|
433
678
|
if (surfaceClass === "audit_evidence_state" && ref.startsWith(".nimi/spec/")) {
|
|
434
679
|
addError(errors, "audit_evidence_state_under_spec", ref);
|
|
435
680
|
}
|
|
436
|
-
if (surfaceClass === "lifecycle_progress_state" && ref.startsWith(".nimi/spec/")) {
|
|
437
|
-
addError(errors, "lifecycle_progress_state_under_spec", ref);
|
|
438
|
-
}
|
|
439
681
|
if (surfaceClass === "methodology_authority" && ref.startsWith(".nimi/spec/")) {
|
|
440
682
|
addError(errors, "package_methodology_under_host_spec", ref);
|
|
441
683
|
}
|
|
442
|
-
if (surfaceClass === "candidate_roadmap" && ref.startsWith(".nimi/spec/")) {
|
|
443
|
-
addError(errors, "candidate_roadmap_under_spec", ref);
|
|
444
|
-
}
|
|
445
684
|
if (isTrackedNonProductRef(ref) && !isAdmittedTrackedOutput(ref, trackedAdmissionRoots(contracts))) {
|
|
446
685
|
addError(errors, "tracked_non_product_without_admission", ref);
|
|
447
686
|
}
|
|
@@ -449,8 +688,8 @@ function validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts) {
|
|
|
449
688
|
if (TEXT_RULE_BODY_PATTERN.test(text)) {
|
|
450
689
|
addError(errors, "guidance_defines_rule_body", ref);
|
|
451
690
|
}
|
|
452
|
-
if (
|
|
453
|
-
addError(errors, "
|
|
691
|
+
if (PRODUCT_RULE_DEFINITION_PATTERN.test(text)) {
|
|
692
|
+
addError(errors, "guidance_defines_product_rule", ref);
|
|
454
693
|
}
|
|
455
694
|
}
|
|
456
695
|
if (surfaceClass === "product_authority" && isMarkdownRef(ref) && GENERATED_REF_PATTERN.test(text)) {
|
|
@@ -483,6 +722,9 @@ function validateTableRef(ref, parsedYaml, contracts) {
|
|
|
483
722
|
addError(errors, "missing_table_family_required_field", ref, String(field));
|
|
484
723
|
}
|
|
485
724
|
}
|
|
725
|
+
if (containsAnyKey(parsedYaml, familyContract.forbidden_fields ?? [])) {
|
|
726
|
+
addError(errors, "table_contains_family_forbidden_field", ref, family);
|
|
727
|
+
}
|
|
486
728
|
}
|
|
487
729
|
|
|
488
730
|
const forbiddenFields = contracts.tableFamily.data?.forbidden_fields_by_authority_class?.product_authority_table ?? [];
|
|
@@ -519,18 +761,20 @@ async function buildInventory(projectRoot, options = {}) {
|
|
|
519
761
|
const rootRef = options.rootRef ?? ".nimi/spec";
|
|
520
762
|
const absoluteFiles = await collectCandidateFiles(projectRoot, rootRef);
|
|
521
763
|
const entries = [];
|
|
522
|
-
const
|
|
764
|
+
const layoutErrors = [...(contracts.hostSpecLayout?.errors ?? [])];
|
|
765
|
+
const errors = [...layoutErrors];
|
|
523
766
|
|
|
524
767
|
for (const absolutePath of absoluteFiles) {
|
|
525
768
|
const ref = relativeRef(projectRoot, absolutePath);
|
|
526
769
|
const text = await readFile(absolutePath, "utf8");
|
|
527
770
|
const parsedYaml = isYamlRef(ref) ? yamlForText(text) : null;
|
|
528
|
-
const surfaceClass = classifyRef(ref, text, parsedYaml);
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
771
|
+
const surfaceClass = classifyRef(ref, text, parsedYaml, contracts);
|
|
772
|
+
const validationErrors = {
|
|
773
|
+
placement: validateRefPlacement(ref, surfaceClass, parsedYaml, text, contracts),
|
|
774
|
+
table_family: validateTableRef(ref, parsedYaml, contracts),
|
|
775
|
+
domain_admission: validateDomainRef(ref, contracts),
|
|
776
|
+
};
|
|
777
|
+
const entryErrors = Object.values(validationErrors).flat();
|
|
534
778
|
errors.push(...entryErrors);
|
|
535
779
|
const requiredConfirmation = confirmationFor(entryErrors);
|
|
536
780
|
entries.push({
|
|
@@ -544,6 +788,7 @@ async function buildInventory(projectRoot, options = {}) {
|
|
|
544
788
|
ambiguity: ambiguityFor(requiredConfirmation, surfaceClass),
|
|
545
789
|
evidence: entryErrors.length > 0 ? entryErrors : [`classified_as:${surfaceClass}`],
|
|
546
790
|
validation_commands: validationCommandsFor(surfaceClass),
|
|
791
|
+
validation_errors: validationErrors,
|
|
547
792
|
errors: entryErrors,
|
|
548
793
|
});
|
|
549
794
|
}
|
|
@@ -551,6 +796,7 @@ async function buildInventory(projectRoot, options = {}) {
|
|
|
551
796
|
return {
|
|
552
797
|
contracts,
|
|
553
798
|
entries,
|
|
799
|
+
layoutErrors,
|
|
554
800
|
errors,
|
|
555
801
|
};
|
|
556
802
|
}
|
|
@@ -602,39 +848,39 @@ function groupInventoryByDisposition(entries) {
|
|
|
602
848
|
return Object.fromEntries(Object.entries(groups).map(([key, value]) => [key, value.sort()]));
|
|
603
849
|
}
|
|
604
850
|
|
|
605
|
-
function
|
|
606
|
-
const
|
|
851
|
+
function buildMigrationGroups(entries) {
|
|
852
|
+
const groupKinds = [
|
|
607
853
|
{
|
|
608
|
-
|
|
854
|
+
group_id: "move-package-methodology-copied-under-spec",
|
|
609
855
|
dispositions: ["move_package"],
|
|
610
856
|
requires_confirmation: false,
|
|
611
857
|
},
|
|
612
858
|
{
|
|
613
|
-
|
|
859
|
+
group_id: "move-local-derived-and-audit-state",
|
|
614
860
|
dispositions: ["move_local"],
|
|
615
861
|
requires_confirmation: false,
|
|
616
862
|
},
|
|
617
863
|
{
|
|
618
|
-
|
|
864
|
+
group_id: "rewrite-product-authority-tables-and-guidance",
|
|
619
865
|
dispositions: ["rewrite"],
|
|
620
866
|
requires_confirmation: true,
|
|
621
867
|
},
|
|
622
868
|
{
|
|
623
|
-
|
|
869
|
+
group_id: "resolve-blocked-semantic-forks",
|
|
624
870
|
dispositions: ["block"],
|
|
625
871
|
requires_confirmation: true,
|
|
626
872
|
},
|
|
627
873
|
];
|
|
628
|
-
return
|
|
629
|
-
.map((
|
|
630
|
-
const matching = entries.filter((entry) =>
|
|
874
|
+
return groupKinds
|
|
875
|
+
.map((group) => {
|
|
876
|
+
const matching = entries.filter((entry) => group.dispositions.includes(entry.disposition));
|
|
631
877
|
return {
|
|
632
|
-
...
|
|
878
|
+
...group,
|
|
633
879
|
entry_count: matching.length,
|
|
634
880
|
source_paths: matching.map((entry) => entry.source_path).sort(),
|
|
635
881
|
};
|
|
636
882
|
})
|
|
637
|
-
.filter((
|
|
883
|
+
.filter((group) => group.entry_count > 0);
|
|
638
884
|
}
|
|
639
885
|
|
|
640
886
|
function enumValidation(entries, inventory) {
|
|
@@ -655,7 +901,7 @@ function migrationPlanForInventory(rootRef, inventory) {
|
|
|
655
901
|
const enumStatus = enumValidation(entries, inventory);
|
|
656
902
|
const requiredConfirmationEntries = entries.filter((entry) => entry.required_confirmation !== "none");
|
|
657
903
|
return {
|
|
658
|
-
contract: "nimicoding.spec-migration-plan.
|
|
904
|
+
contract: "nimicoding.spec-migration-plan.v2",
|
|
659
905
|
ok: enumStatus.unknown_target_classes.length === 0 && enumStatus.unknown_dispositions.length === 0,
|
|
660
906
|
version: inventory.version,
|
|
661
907
|
root: rootRef,
|
|
@@ -687,7 +933,7 @@ function migrationPlanForInventory(rootRef, inventory) {
|
|
|
687
933
|
: "resolve_owner_or_package_boundary_before_migration",
|
|
688
934
|
})),
|
|
689
935
|
groups: groupInventoryByDisposition(entries),
|
|
690
|
-
|
|
936
|
+
migration_groups: buildMigrationGroups(entries),
|
|
691
937
|
mutation_policy: {
|
|
692
938
|
mutates_source_tree: false,
|
|
693
939
|
allowed_output_roots: [".nimi/local/state/spec-surface"],
|
|
@@ -700,13 +946,15 @@ export async function classifySpecSurface(projectRoot, options = {}) {
|
|
|
700
946
|
const { entries, errors } = await buildInventory(projectRoot, options);
|
|
701
947
|
return reportFor("classify-spec-tree", errors.length === 0, errors, [], entries, {
|
|
702
948
|
inventory: {
|
|
703
|
-
version:
|
|
949
|
+
version: 2,
|
|
704
950
|
inventory: entries,
|
|
705
951
|
disposition_enum: ["keep", "move_package", "move_local", "split", "rewrite", "delete", "block"],
|
|
706
952
|
target_class_enum: [
|
|
707
953
|
"product_authority",
|
|
708
954
|
"product_authority_table",
|
|
709
955
|
"thin_guidance",
|
|
956
|
+
"host_instruction",
|
|
957
|
+
"tracked_derived_projection",
|
|
710
958
|
"derived_view",
|
|
711
959
|
"spec_generation_state",
|
|
712
960
|
"audit_evidence_state",
|
|
@@ -714,13 +962,11 @@ export async function classifySpecSurface(projectRoot, options = {}) {
|
|
|
714
962
|
"methodology_authority",
|
|
715
963
|
"nimicoding_managed_projection",
|
|
716
964
|
"host_projection_anchor",
|
|
717
|
-
"candidate_roadmap",
|
|
718
965
|
"support_registry",
|
|
719
|
-
"lifecycle_progress_state",
|
|
720
966
|
],
|
|
721
967
|
semantic_constraints: [
|
|
722
968
|
"inventory_must_not_modify_files",
|
|
723
|
-
"
|
|
969
|
+
"future_under_spec_must_fail_classification",
|
|
724
970
|
"nimicoding_managed_projection_must_not_be_promoted_to_product_authority",
|
|
725
971
|
],
|
|
726
972
|
},
|
|
@@ -753,7 +999,11 @@ export function isProductAuthoritySurfaceClass(surfaceClass) {
|
|
|
753
999
|
}
|
|
754
1000
|
|
|
755
1001
|
export async function validatePlacement(projectRoot, options = {}) {
|
|
756
|
-
const { entries,
|
|
1002
|
+
const { entries, layoutErrors } = await buildInventory(projectRoot, options);
|
|
1003
|
+
const errors = [
|
|
1004
|
+
...layoutErrors,
|
|
1005
|
+
...entries.flatMap((entry) => entry.validation_errors?.placement ?? []),
|
|
1006
|
+
];
|
|
757
1007
|
return reportFor("validate-placement", errors.length === 0, errors, [], entries);
|
|
758
1008
|
}
|
|
759
1009
|
|
|
@@ -804,8 +1054,8 @@ export async function validateGuidanceBodies(projectRoot, options = {}) {
|
|
|
804
1054
|
if (TEXT_RULE_BODY_PATTERN.test(text)) {
|
|
805
1055
|
addError(errors, "guidance_defines_rule_body", entry.source_path);
|
|
806
1056
|
}
|
|
807
|
-
if (
|
|
808
|
-
addError(errors, "
|
|
1057
|
+
if (PRODUCT_RULE_DEFINITION_PATTERN.test(text)) {
|
|
1058
|
+
addError(errors, "guidance_defines_product_rule", entry.source_path);
|
|
809
1059
|
}
|
|
810
1060
|
}
|
|
811
1061
|
return reportFor("validate-guidance-bodies", errors.length === 0, errors, [], entries);
|