@nimiplatform/nimi-coding 0.3.0 → 0.4.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 (118) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/CONTRIBUTING.md +3 -3
  3. package/README.md +285 -41
  4. package/README.zh-CN.md +294 -31
  5. package/bin/nimicoding.mjs +1 -1
  6. package/cli/commands/authority.mjs +849 -0
  7. package/cli/commands/clear.mjs +27 -7
  8. package/cli/commands/doctor.mjs +10 -1
  9. package/cli/commands/start.mjs +23 -27
  10. package/cli/commands/sync.mjs +21 -101
  11. package/cli/constants.mjs +7 -31
  12. package/cli/dev/check-authority-surface.mjs +173 -0
  13. package/cli/dev/check-product-conformance.mjs +917 -0
  14. package/cli/help.mjs +10 -17
  15. package/cli/index.mjs +6 -47
  16. package/cli/lib/authority/anchors.mjs +491 -0
  17. package/cli/lib/authority/audit.mjs +490 -0
  18. package/cli/lib/authority/closed-sets.mjs +646 -0
  19. package/cli/lib/authority/compile.mjs +110 -0
  20. package/cli/lib/authority/consumers.mjs +470 -0
  21. package/cli/lib/authority/diagnostics.mjs +87 -0
  22. package/cli/lib/authority/diff.mjs +287 -0
  23. package/cli/lib/authority/discover.mjs +447 -0
  24. package/cli/lib/authority/evidence-snapshot.mjs +1 -0
  25. package/cli/lib/authority/evidence.mjs +1432 -0
  26. package/cli/lib/authority/format.mjs +197 -0
  27. package/cli/lib/authority/git-snapshot.mjs +1065 -0
  28. package/cli/lib/authority/graph.mjs +351 -0
  29. package/cli/lib/authority/impact.mjs +252 -0
  30. package/cli/lib/authority/lexicon.mjs +55 -0
  31. package/cli/lib/authority/query.mjs +162 -0
  32. package/cli/lib/authority/repository-corpus.mjs +37 -0
  33. package/cli/lib/authority/repository-inventory.mjs +34 -0
  34. package/cli/lib/authority/review.mjs +222 -0
  35. package/cli/lib/authority/sarif.mjs +181 -0
  36. package/cli/lib/authority/scope-bindings.mjs +225 -0
  37. package/cli/lib/authority/source-map.mjs +105 -0
  38. package/cli/lib/authority/source-markdown.mjs +167 -0
  39. package/cli/lib/authority/source-yaml.mjs +312 -0
  40. package/cli/lib/authority/surface.mjs +79 -0
  41. package/cli/lib/authority/terms.mjs +421 -0
  42. package/cli/lib/authority/tracked-text.mjs +70 -0
  43. package/cli/lib/authority/validate.mjs +308 -0
  44. package/cli/lib/bootstrap.mjs +55 -71
  45. package/cli/lib/doctor.mjs +40 -123
  46. package/cli/lib/entrypoints.mjs +103 -105
  47. package/cli/lib/fs-helpers.mjs +139 -15
  48. package/cli/lib/internal/governance/config.mjs +0 -36
  49. package/cli/lib/shared.mjs +8 -21
  50. package/cli/lib/sync.mjs +81 -92
  51. package/cli/lib/value-helpers.mjs +0 -26
  52. package/cli/lib/yaml-helpers.mjs +1 -124
  53. package/cli/seeds/bootstrap.mjs +31 -87
  54. package/cli/seeds/seed-policy.yaml +9 -63
  55. package/contracts/authority-evidence-bindings.schema.yaml +53 -0
  56. package/contracts/authority-evidence-probe-results.schema.yaml +38 -0
  57. package/contracts/authority-impact-dispositions.schema.yaml +13 -0
  58. package/contracts/authority-scope-bindings.schema.yaml +15 -0
  59. package/contracts/authority-source.schema.yaml +64 -0
  60. package/contracts/authority-verifier-bindings.schema.yaml +20 -0
  61. package/methodology/authority-authoring.yaml +144 -0
  62. package/methodology/core.yaml +31 -26
  63. package/package.json +10 -6
  64. package/spec/authority-anchors.yaml +99 -0
  65. package/spec/authority-audit.yaml +85 -0
  66. package/spec/authority-authoring.yaml +150 -0
  67. package/spec/authority-closed-sets.yaml +65 -0
  68. package/spec/authority-consumers.yaml +70 -0
  69. package/spec/authority-context.yaml +50 -0
  70. package/spec/authority-discovery.yaml +119 -0
  71. package/spec/authority-evidence.yaml +200 -0
  72. package/spec/authority-graph.yaml +59 -0
  73. package/spec/authority-impact.yaml +84 -0
  74. package/spec/authority-review.yaml +89 -0
  75. package/spec/authority-terms.yaml +59 -0
  76. package/spec/product-scope.yaml +36 -23
  77. package/cli/commands/blueprint-audit.mjs +0 -91
  78. package/cli/commands/classify-spec-tree.mjs +0 -5
  79. package/cli/commands/generate-spec-derived-docs.mjs +0 -151
  80. package/cli/commands/generate-spec-migration-plan.mjs +0 -30
  81. package/cli/commands/surface-validator-command.mjs +0 -49
  82. package/cli/commands/validate-domain-admission.mjs +0 -5
  83. package/cli/commands/validate-guidance-bodies.mjs +0 -5
  84. package/cli/commands/validate-placement.mjs +0 -5
  85. package/cli/commands/validate-projection-edges.mjs +0 -5
  86. package/cli/commands/validate-spec-audit.mjs +0 -27
  87. package/cli/commands/validate-spec-governance.mjs +0 -124
  88. package/cli/commands/validate-spec-tree.mjs +0 -27
  89. package/cli/commands/validate-table-family.mjs +0 -5
  90. package/cli/commands/validate-tracked-output-admission.mjs +0 -5
  91. package/cli/lib/blueprint-audit.mjs +0 -363
  92. package/cli/lib/contracts.mjs +0 -21
  93. package/cli/lib/internal/contracts-loaders.mjs +0 -47
  94. package/cli/lib/internal/contracts-parse.mjs +0 -192
  95. package/cli/lib/internal/governance/runner.mjs +0 -35
  96. package/cli/lib/internal/surface-taxonomy-validators.mjs +0 -1147
  97. package/cli/lib/internal/validators-shared.mjs +0 -12
  98. package/cli/lib/internal/validators-spec-helpers.mjs +0 -52
  99. package/cli/lib/internal/validators-spec.mjs +0 -380
  100. package/cli/lib/validators.mjs +0 -30
  101. package/config/bootstrap.yaml +0 -6
  102. package/config/spec-generation-inputs.yaml +0 -41
  103. package/contracts/domain-admission.schema.yaml +0 -56
  104. package/contracts/migration-inventory.schema.yaml +0 -15
  105. package/contracts/negative-fixtures.yaml +0 -26
  106. package/contracts/placement-contract.schema.yaml +0 -127
  107. package/contracts/projection-edge.schema.yaml +0 -46
  108. package/contracts/shared-enums.yaml +0 -63
  109. package/contracts/spec-generation-audit.schema.yaml +0 -46
  110. package/contracts/spec-generation-inputs.schema.yaml +0 -43
  111. package/contracts/spec-layout.schema.yaml +0 -32
  112. package/contracts/surface-taxonomy.schema.yaml +0 -139
  113. package/contracts/table-family.schema.yaml +0 -133
  114. package/contracts/tracked-output-admission.schema.yaml +0 -17
  115. package/methodology/four-closure-policy.yaml +0 -20
  116. package/methodology/role-separation-policy.yaml +0 -22
  117. package/methodology/spec-reconstruction.yaml +0 -69
  118. package/spec/_meta/spec-tree-model.yaml +0 -44
@@ -1,363 +0,0 @@
1
- import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import YAML from "yaml";
4
-
5
- import { loadBlueprintReference, loadSpecGenerationInputsConfig } from "./contracts.mjs";
6
- import { pathExists } from "./fs-helpers.mjs";
7
- import {
8
- localize,
9
- styleHeading,
10
- styleLabel,
11
- styleStatus,
12
- } from "./ui.mjs";
13
-
14
- const BLUEPRINT_AUDIT_CONTRACT_VERSION = "nimicoding.blueprint-audit.v2";
15
- const DEFAULT_REPORT_PATH = ".nimi/local/state/spec-generation/blueprint-equivalence-audit.json";
16
-
17
- async function collectFiles(rootPath, relativePrefix = "") {
18
- const info = await pathExists(rootPath);
19
- if (!info || !info.isDirectory()) {
20
- return [];
21
- }
22
-
23
- const entries = await readdir(rootPath, { withFileTypes: true });
24
- const collected = [];
25
- for (const entry of entries) {
26
- const absoluteChildPath = path.join(rootPath, entry.name);
27
- const relativeChildPath = relativePrefix ? path.posix.join(relativePrefix, entry.name) : entry.name;
28
- if (entry.isDirectory()) {
29
- collected.push(...await collectFiles(absoluteChildPath, relativeChildPath));
30
- } else if (entry.isFile()) {
31
- collected.push(relativeChildPath.split(path.sep).join(path.posix.sep));
32
- }
33
- }
34
-
35
- return collected.sort();
36
- }
37
-
38
- async function collectDomainInventory(rootPath) {
39
- const info = await pathExists(rootPath);
40
- if (!info || !info.isDirectory()) {
41
- return {
42
- domains: [],
43
- hasIndex: false,
44
- files: [],
45
- };
46
- }
47
-
48
- const entries = await readdir(rootPath, { withFileTypes: true });
49
- const domains = [];
50
- for (const entry of entries) {
51
- if (!entry.isDirectory()) {
52
- continue;
53
- }
54
-
55
- const kernelPath = path.join(rootPath, entry.name, "kernel");
56
- const kernelInfo = await pathExists(kernelPath);
57
- if (kernelInfo?.isDirectory()) {
58
- domains.push(entry.name);
59
- }
60
- }
61
-
62
- const indexInfo = await pathExists(path.join(rootPath, "INDEX.md"));
63
- const files = await collectFiles(rootPath);
64
-
65
- return {
66
- domains: domains.sort(),
67
- hasIndex: Boolean(indexInfo?.isFile()),
68
- files,
69
- };
70
- }
71
-
72
- function categorizeBlueprintFiles(files) {
73
- const kernelMarkdown = [];
74
- const kernelTables = [];
75
- const kernelGenerated = [];
76
- const domainGuides = [];
77
-
78
- for (const relativePath of files) {
79
- if (/^([^/]+)\/kernel\/[^/]+\.md$/.test(relativePath)) {
80
- kernelMarkdown.push(relativePath);
81
- continue;
82
- }
83
-
84
- if (/^([^/]+)\/kernel\/tables\/.+\.(ya?ml)$/.test(relativePath)) {
85
- kernelTables.push(relativePath);
86
- continue;
87
- }
88
-
89
- if (/^([^/]+)\/kernel\/generated\/.+\.md$/.test(relativePath)) {
90
- kernelGenerated.push(relativePath);
91
- continue;
92
- }
93
-
94
- if (/^[^/]+\/[^/]+\.md$/.test(relativePath)) {
95
- domainGuides.push(relativePath);
96
- }
97
- }
98
-
99
- return {
100
- kernelMarkdown,
101
- kernelTables,
102
- kernelGenerated,
103
- domainGuides,
104
- };
105
- }
106
-
107
- function compareFileSets(blueprintFiles, canonicalFiles) {
108
- const canonicalSet = new Set(canonicalFiles);
109
- const blueprintSet = new Set(blueprintFiles);
110
-
111
- return {
112
- present: blueprintFiles.filter((filePath) => canonicalSet.has(filePath)),
113
- missing: blueprintFiles.filter((filePath) => !canonicalSet.has(filePath)),
114
- extra: canonicalFiles.filter((filePath) => !blueprintSet.has(filePath)),
115
- };
116
- }
117
-
118
- function buildSummarySection(compareResult) {
119
- return {
120
- present: compareResult.present.length,
121
- missing: compareResult.missing.length,
122
- extra: compareResult.extra.length,
123
- };
124
- }
125
-
126
- function collectIds(value, ids = []) {
127
- if (Array.isArray(value)) {
128
- for (const entry of value) {
129
- collectIds(entry, ids);
130
- }
131
- return ids;
132
- }
133
-
134
- if (value && typeof value === "object") {
135
- if (typeof value.id === "string") {
136
- ids.push(value.id);
137
- }
138
- for (const nestedValue of Object.values(value)) {
139
- collectIds(nestedValue, ids);
140
- }
141
- }
142
-
143
- return ids;
144
- }
145
-
146
- async function compareRuleIds(blueprintAbsoluteRoot, canonicalAbsoluteRoot, presentTablePaths) {
147
- const files = [];
148
- const aggregateMissing = new Set();
149
- const aggregateExtra = new Set();
150
- const parseErrors = [];
151
-
152
- for (const relativePath of presentTablePaths) {
153
- try {
154
- const blueprintText = await readFile(path.join(blueprintAbsoluteRoot, relativePath), "utf8");
155
- const canonicalText = await readFile(path.join(canonicalAbsoluteRoot, relativePath), "utf8");
156
- const blueprintIds = Array.from(new Set(collectIds(YAML.parse(blueprintText)))).sort();
157
- const canonicalIds = Array.from(new Set(collectIds(YAML.parse(canonicalText)))).sort();
158
- const missingIds = blueprintIds.filter((entry) => !canonicalIds.includes(entry));
159
- const extraIds = canonicalIds.filter((entry) => !blueprintIds.includes(entry));
160
-
161
- missingIds.forEach((entry) => aggregateMissing.add(entry));
162
- extraIds.forEach((entry) => aggregateExtra.add(entry));
163
-
164
- files.push({
165
- path: relativePath,
166
- blueprintIds: blueprintIds.length,
167
- canonicalIds: canonicalIds.length,
168
- missingIds,
169
- extraIds,
170
- });
171
- } catch (error) {
172
- parseErrors.push({
173
- path: relativePath,
174
- error: error instanceof Error ? error.message : String(error),
175
- });
176
- }
177
- }
178
-
179
- return {
180
- comparedFiles: files.length,
181
- files,
182
- parseErrors,
183
- missingRuleIds: Array.from(aggregateMissing).sort(),
184
- extraRuleIds: Array.from(aggregateExtra).sort(),
185
- };
186
- }
187
-
188
- export async function buildBlueprintAuditPayload(projectRoot, options = {}) {
189
- const blueprintReference = await loadBlueprintReference(projectRoot);
190
- const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
191
-
192
- const canonicalRoot = options.canonicalRoot
193
- ?? specGenerationInputs.canonicalTargetRoot
194
- ?? ".nimi/spec";
195
- const blueprintRoot = options.blueprintRoot
196
- ?? blueprintReference.root
197
- ?? specGenerationInputs.benchmarkBlueprintRoot
198
- ?? null;
199
-
200
- if (!blueprintRoot) {
201
- return {
202
- ok: false,
203
- exitCode: 2,
204
- inputError: true,
205
- error: `${localize(
206
- "nimicoding blueprint-audit refused: no blueprint root is declared; pass --blueprint-root or add project-local blueprint reference metadata.",
207
- "nimicoding blueprint-audit 已拒绝:没有声明 blueprint root;请传入 --blueprint-root,或补充项目本地 blueprint reference 元数据。",
208
- )}\n`,
209
- };
210
- }
211
-
212
- const blueprintAbsoluteRoot = path.resolve(projectRoot, blueprintRoot);
213
- const canonicalAbsoluteRoot = path.resolve(projectRoot, canonicalRoot);
214
- const blueprintInventory = await collectDomainInventory(blueprintAbsoluteRoot);
215
- const canonicalInventory = await collectDomainInventory(canonicalAbsoluteRoot);
216
-
217
- const missingDomains = blueprintInventory.domains.filter((domainId) => !canonicalInventory.domains.includes(domainId));
218
- const extraDomains = canonicalInventory.domains.filter((domainId) => !blueprintInventory.domains.includes(domainId));
219
- const blueprintFiles = categorizeBlueprintFiles(blueprintInventory.files);
220
- const canonicalFiles = categorizeBlueprintFiles(canonicalInventory.files);
221
-
222
- const kernelMarkdown = compareFileSets(blueprintFiles.kernelMarkdown, canonicalFiles.kernelMarkdown);
223
- const kernelTables = compareFileSets(blueprintFiles.kernelTables, canonicalFiles.kernelTables);
224
- const kernelGenerated = {
225
- present: [],
226
- missing: [],
227
- extra: [],
228
- };
229
- const domainGuides = compareFileSets(blueprintFiles.domainGuides, canonicalFiles.domainGuides);
230
- const ruleIdPreservation = await compareRuleIds(
231
- blueprintAbsoluteRoot,
232
- canonicalAbsoluteRoot,
233
- kernelTables.present,
234
- );
235
-
236
- const indexPresent = blueprintInventory.hasIndex ? canonicalInventory.hasIndex : true;
237
- const ok = missingDomains.length === 0
238
- && kernelMarkdown.missing.length === 0
239
- && kernelTables.missing.length === 0
240
- && domainGuides.missing.length === 0
241
- && ruleIdPreservation.missingRuleIds.length === 0
242
- && ruleIdPreservation.parseErrors.length === 0
243
- && indexPresent;
244
-
245
- const reportPath = path.join(projectRoot, DEFAULT_REPORT_PATH);
246
- const nextSteps = [];
247
- if (missingDomains.length > 0 || kernelMarkdown.missing.length > 0 || kernelTables.missing.length > 0 || !indexPresent) {
248
- nextSteps.push("Resolve the missing blueprint structure under `/.nimi/spec/**` before claiming equivalence.");
249
- }
250
- if (domainGuides.missing.length > 0) {
251
- nextSteps.push("Thin and map domain guides only after kernel coverage is in place.");
252
- }
253
- if (ruleIdPreservation.missingRuleIds.length > 0 || ruleIdPreservation.parseErrors.length > 0) {
254
- nextSteps.push("Preserve benchmark rule IDs in canonical kernel tables before treating the canonical tree as benchmark-equivalent.");
255
- }
256
-
257
- return {
258
- contractVersion: BLUEPRINT_AUDIT_CONTRACT_VERSION,
259
- ok,
260
- exitCode: ok ? 0 : 1,
261
- projectRoot,
262
- blueprintRoot,
263
- canonicalRoot,
264
- blueprintReference,
265
- specGenerationInputs: {
266
- ok: specGenerationInputs.ok,
267
- mode: specGenerationInputs.mode,
268
- acceptanceMode: specGenerationInputs.acceptanceMode,
269
- benchmarkBlueprintRoot: specGenerationInputs.benchmarkBlueprintRoot,
270
- },
271
- inventory: {
272
- blueprintDomains: blueprintInventory.domains,
273
- canonicalDomains: canonicalInventory.domains,
274
- missingDomains,
275
- extraDomains,
276
- indexPresent,
277
- },
278
- comparison: {
279
- kernelMarkdown: {
280
- ...buildSummarySection(kernelMarkdown),
281
- missingPaths: kernelMarkdown.missing,
282
- },
283
- kernelTables: {
284
- ...buildSummarySection(kernelTables),
285
- missingPaths: kernelTables.missing,
286
- },
287
- kernelGenerated: {
288
- ...buildSummarySection(kernelGenerated),
289
- missingPaths: kernelGenerated.missing,
290
- },
291
- domainGuides: {
292
- ...buildSummarySection(domainGuides),
293
- missingPaths: domainGuides.missing,
294
- },
295
- },
296
- structuralGaps: {
297
- missingDomains,
298
- missingKernelMarkdown: kernelMarkdown.missing,
299
- missingKernelTables: kernelTables.missing,
300
- indexMissing: !indexPresent,
301
- },
302
- semanticMappingGaps: {
303
- ruleIdPreservation,
304
- },
305
- derivedViewGaps: {
306
- missingKernelGenerated: kernelGenerated.missing,
307
- missingDomainGuides: domainGuides.missing,
308
- },
309
- artifactPath: reportPath,
310
- nextSteps,
311
- };
312
- }
313
-
314
- export function formatBlueprintAuditPayload(payload) {
315
- const lines = [
316
- styleHeading(`nimicoding blueprint-audit: ${payload.projectRoot}`),
317
- "",
318
- styleLabel(localize("Summary:", "摘要:")),
319
- ` - ${localize("status", "状态")}: ${styleStatus(payload.ok ? "ok" : "needs_attention")}`,
320
- ` - ${localize("blueprint root", "blueprint root")}: ${payload.blueprintRoot}`,
321
- ` - ${localize("canonical root", "canonical root")}: ${payload.canonicalRoot}`,
322
- ` - ${localize("missing domains", "缺失域")}: ${payload.inventory.missingDomains.length}`,
323
- ` - ${localize("kernel markdown missing", "缺失 kernel markdown")}: ${payload.comparison.kernelMarkdown.missing}`,
324
- ` - ${localize("kernel tables missing", "缺失 kernel tables")}: ${payload.comparison.kernelTables.missing}`,
325
- ` - ${localize("generated views missing", "缺失 generated 视图")}: ${payload.comparison.kernelGenerated.missing}`,
326
- ` - ${localize("guide mappings missing", "缺失 guide 映射")}: ${payload.comparison.domainGuides.missing}`,
327
- ` - ${localize("rule ids missing", "缺失 rule id")}: ${payload.semanticMappingGaps.ruleIdPreservation.missingRuleIds.length}`,
328
- "",
329
- styleLabel(localize("Coverage:", "覆盖情况:")),
330
- ` - ${localize("blueprint domains", "blueprint 域")}: ${payload.inventory.blueprintDomains.length}`,
331
- ` - ${localize("canonical domains", "canonical 域")}: ${payload.inventory.canonicalDomains.length}`,
332
- ` - ${localize("INDEX present", "INDEX 已存在")}: ${payload.inventory.indexPresent ? "true" : "false"}`,
333
- ];
334
-
335
- if (payload.inventory.missingDomains.length > 0) {
336
- lines.push(` - ${localize("missing domain ids", "缺失域 ID")}: ${payload.inventory.missingDomains.join(", ")}`);
337
- }
338
-
339
- if (payload.semanticMappingGaps.ruleIdPreservation.missingRuleIds.length > 0) {
340
- lines.push(` - ${localize("missing rule ids", "缺失 rule id")}: ${payload.semanticMappingGaps.ruleIdPreservation.missingRuleIds.join(", ")}`);
341
- }
342
-
343
- if (payload.semanticMappingGaps.ruleIdPreservation.parseErrors.length > 0) {
344
- lines.push(` - ${localize("rule-id parse errors", "rule-id 解析错误")}: ${payload.semanticMappingGaps.ruleIdPreservation.parseErrors.length}`);
345
- }
346
-
347
- if (payload.nextSteps.length > 0) {
348
- lines.push("", styleLabel(localize("Next:", "下一步:")));
349
- for (const step of payload.nextSteps) {
350
- lines.push(` - ${localize(step, step)}`);
351
- }
352
- }
353
-
354
- return `${lines.join("\n")}\n`;
355
- }
356
-
357
- export async function writeBlueprintAuditArtifact(projectRoot, payload) {
358
- await mkdir(path.dirname(payload.artifactPath), { recursive: true });
359
- await writeFile(payload.artifactPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
360
- return {
361
- jsonRef: path.relative(projectRoot, payload.artifactPath).split(path.sep).join(path.posix.sep),
362
- };
363
- }
@@ -1,21 +0,0 @@
1
- import {
2
- loadBlueprintReference as loadBlueprintReferenceInternal,
3
- loadSpecGenerationAuditContract as loadSpecGenerationAuditContractInternal,
4
- loadSpecGenerationInputsConfig as loadSpecGenerationInputsConfigInternal,
5
- loadSpecGenerationInputsContract as loadSpecGenerationInputsContractInternal,
6
- } from "./internal/contracts-loaders.mjs";
7
-
8
- export function loadSpecGenerationInputsContract(projectRoot) {
9
- return loadSpecGenerationInputsContractInternal(projectRoot);
10
- }
11
- export function loadSpecGenerationAuditContract(projectRoot) {
12
- return loadSpecGenerationAuditContractInternal(projectRoot);
13
- }
14
-
15
- export function loadSpecGenerationInputsConfig(projectRoot) {
16
- return loadSpecGenerationInputsConfigInternal(projectRoot);
17
- }
18
-
19
- export function loadBlueprintReference(projectRoot) {
20
- return loadBlueprintReferenceInternal(projectRoot);
21
- }
@@ -1,47 +0,0 @@
1
- import path from "node:path";
2
-
3
- import {
4
- BLUEPRINT_REFERENCE_REF,
5
- SPEC_GENERATION_AUDIT_CONTRACT_REF,
6
- SPEC_GENERATION_INPUTS_CONTRACT_REF,
7
- SPEC_GENERATION_INPUTS_REF,
8
- } from "../../constants.mjs";
9
- import { readTextIfFile } from "../fs-helpers.mjs";
10
- import {
11
- parseBlueprintReference,
12
- parseSpecGenerationAuditContract,
13
- parseSpecGenerationInputsConfig,
14
- parseSpecGenerationInputsContract,
15
- } from "./contracts-parse.mjs";
16
-
17
- async function loadParsedYaml(projectRoot, relativePath, parse) {
18
- const text = await readTextIfFile(path.join(projectRoot, relativePath));
19
- return {
20
- path: relativePath,
21
- text,
22
- ...parse(text),
23
- };
24
- }
25
- export function loadSpecGenerationInputsContract(projectRoot) {
26
- return loadParsedYaml(
27
- projectRoot,
28
- SPEC_GENERATION_INPUTS_CONTRACT_REF,
29
- parseSpecGenerationInputsContract,
30
- );
31
- }
32
-
33
- export function loadSpecGenerationAuditContract(projectRoot) {
34
- return loadParsedYaml(
35
- projectRoot,
36
- SPEC_GENERATION_AUDIT_CONTRACT_REF,
37
- parseSpecGenerationAuditContract,
38
- );
39
- }
40
-
41
- export function loadSpecGenerationInputsConfig(projectRoot) {
42
- return loadParsedYaml(projectRoot, SPEC_GENERATION_INPUTS_REF, parseSpecGenerationInputsConfig);
43
- }
44
-
45
- export function loadBlueprintReference(projectRoot) {
46
- return loadParsedYaml(projectRoot, BLUEPRINT_REFERENCE_REF, parseBlueprintReference);
47
- }
@@ -1,192 +0,0 @@
1
- import {
2
- SPEC_GENERATION_AUDIT_COVERAGE_STATUS_ENUM,
3
- SPEC_GENERATION_AUDIT_REQUIRED_TOP_LEVEL_FIELDS,
4
- SPEC_GENERATION_AUDIT_SOURCE_BASIS_ENUM,
5
- } from "../../constants.mjs";
6
- import { arraysEqual, isPlainObject, toStringArray } from "../value-helpers.mjs";
7
- import { parseYamlText } from "../yaml-helpers.mjs";
8
-
9
- const ACCEPTANCE_MODES = [
10
- "semantic_and_structural_parity_when_blueprint_exists",
11
- "placement_validity_before_generation",
12
- ];
13
- const SPEC_GENERATION_INPUT_FIELDS = [
14
- "mode",
15
- "canonical_target_root",
16
- "code_inputs",
17
- "docs_inputs",
18
- "structure_inputs",
19
- "local_inputs",
20
- "forbidden_source_classes",
21
- "generation_order",
22
- "inference_rules",
23
- "acceptance_mode",
24
- ];
25
- const SPEC_GENERATION_INPUT_DOCUMENT_FIELDS = [
26
- "version",
27
- "contract_ref",
28
- "spec_generation_inputs",
29
- ];
30
-
31
- function toStringOrNull(value) {
32
- return typeof value === "string" ? value : null;
33
- }
34
-
35
- function hasExactFields(value, expectedFields) {
36
- return isPlainObject(value)
37
- && arraysEqual(Object.keys(value).sort(), [...expectedFields].sort());
38
- }
39
-
40
- function isAllowedAuthorityRoot(root) {
41
- return typeof root === "string"
42
- && (root === ".nimi/spec" || root.startsWith(".nimi/spec/"))
43
- && !root.includes("..");
44
- }
45
-
46
- export function parseSpecGenerationAuditContract(text) {
47
- const parsed = parseYamlText(text);
48
- const requiredTopLevelFields = toStringArray(parsed?.required_top_level_fields);
49
- const requiredFileEntryFields = toStringArray(parsed?.required_file_entry_fields);
50
- const sourceBasisEnum = toStringArray(parsed?.source_basis_enum);
51
- const coverageStatusEnum = toStringArray(parsed?.coverage_status_enum);
52
- const hardConstraints = toStringArray(parsed?.hard_constraints);
53
- const expectedTopLevelFields = [
54
- ...SPEC_GENERATION_AUDIT_REQUIRED_TOP_LEVEL_FIELDS.slice(0, 4),
55
- "placement_report_ref",
56
- ...SPEC_GENERATION_AUDIT_REQUIRED_TOP_LEVEL_FIELDS.slice(4),
57
- ];
58
- const expectedFileEntryFields = [
59
- "canonical_path",
60
- "surface_class",
61
- "source_refs",
62
- "source_basis",
63
- "coverage_status",
64
- "unresolved_items",
65
- ];
66
-
67
- return {
68
- ok: parsed?.version === 2
69
- && String(parsed?.audit_contract?.id ?? "") === "canonical_spec_generation_audit"
70
- && String(parsed?.audit_contract?.target_ref ?? "") === ".nimi/local/state/spec-generation/spec-generation-audit.yaml"
71
- && arraysEqual(requiredTopLevelFields, expectedTopLevelFields)
72
- && arraysEqual(requiredFileEntryFields, expectedFileEntryFields)
73
- && arraysEqual(sourceBasisEnum, SPEC_GENERATION_AUDIT_SOURCE_BASIS_ENUM)
74
- && arraysEqual(coverageStatusEnum, SPEC_GENERATION_AUDIT_COVERAGE_STATUS_ENUM)
75
- && hardConstraints.includes("every_generated_canonical_file_requires_a_matching_audit_entry")
76
- && hardConstraints.includes("required_canonical_files_must_not_be_placeholder_not_allowed")
77
- && hardConstraints.includes("unresolved_or_inferred_content_must_be_explicit")
78
- && hardConstraints.includes("source_refs_must_stay_within_class_filtered_declared_inputs")
79
- && hardConstraints.includes("no_empty_success_looking_audit_entries"),
80
- requiredTopLevelFields,
81
- requiredFileEntryFields,
82
- sourceBasisEnum,
83
- coverageStatusEnum,
84
- hardConstraints,
85
- };
86
- }
87
-
88
- export function parseSpecGenerationInputsContract(text) {
89
- const parsed = parseYamlText(text);
90
- const contract = parsed?.input_contract;
91
- const requiredFields = toStringArray(parsed?.required_fields);
92
- const generationOrderEnum = toStringArray(parsed?.generation_order_enum);
93
- const hardConstraints = toStringArray(parsed?.hard_constraints);
94
-
95
- return {
96
- ok: parsed?.version === 3
97
- && String(contract?.id ?? "") === "canonical_spec_generation_inputs"
98
- && String(contract?.owner ?? "") === "nimi-coding"
99
- && arraysEqual(toStringArray(parsed?.mode_enum), ["class_filtered"])
100
- && arraysEqual(requiredFields, SPEC_GENERATION_INPUT_FIELDS)
101
- && generationOrderEnum.includes("classify_inputs")
102
- && generationOrderEnum.includes("validate_placement")
103
- && hardConstraints.includes("docs_roots_blanket_ingestion_is_forbidden")
104
- && hardConstraints.includes("each_input_file_must_have_an_accepted_surface_class_before_render"),
105
- requiredFields,
106
- generationOrderEnum,
107
- hardConstraints,
108
- };
109
- }
110
-
111
- export function parseSpecGenerationInputsConfig(text) {
112
- const parsed = parseYamlText(text);
113
- const config = parsed?.spec_generation_inputs;
114
- const codeInputs = Array.isArray(config?.code_inputs) ? config.code_inputs : [];
115
- const docsInputs = Array.isArray(config?.docs_inputs) ? config.docs_inputs : [];
116
- const structureInputs = Array.isArray(config?.structure_inputs) ? config.structure_inputs : [];
117
- const localInputs = Array.isArray(config?.local_inputs) ? config.local_inputs : [];
118
- const forbiddenSourceClasses = toStringArray(config?.forbidden_source_classes);
119
- const generationOrder = toStringArray(config?.generation_order);
120
- const inferenceRules = toStringArray(config?.inference_rules);
121
- const acceptanceMode = toStringOrNull(config?.acceptance_mode);
122
-
123
- return {
124
- ok: hasExactFields(parsed, SPEC_GENERATION_INPUT_DOCUMENT_FIELDS)
125
- && hasExactFields(config, SPEC_GENERATION_INPUT_FIELDS)
126
- && parsed.version === 3
127
- && String(parsed?.contract_ref ?? "") === ".nimi/contracts/spec-generation-inputs.schema.yaml"
128
- && config?.mode === "class_filtered"
129
- && config?.canonical_target_root === ".nimi/spec"
130
- && docsInputs.length > 0
131
- && docsInputs.every((entry) => isPlainObject(entry)
132
- && isAllowedAuthorityRoot(entry.root)
133
- && Array.isArray(entry.allowed_surface_classes)
134
- && Array.isArray(entry.forbidden_surface_classes)
135
- && entry.use_as_authority === true)
136
- && Array.isArray(codeInputs)
137
- && Array.isArray(structureInputs)
138
- && Array.isArray(localInputs)
139
- && forbiddenSourceClasses.length > 0
140
- && generationOrder.includes("classify_inputs")
141
- && generationOrder.includes("validate_placement")
142
- && inferenceRules.includes("no_blanket_docs_roots")
143
- && ACCEPTANCE_MODES.includes(acceptanceMode),
144
- mode: toStringOrNull(config?.mode),
145
- canonicalTargetRoot: toStringOrNull(config?.canonical_target_root),
146
- codeRoots: codeInputs.map((entry) => String(entry?.root ?? "")).filter(Boolean),
147
- docsRoots: docsInputs.map((entry) => String(entry?.root ?? "")).filter(Boolean),
148
- structureRoots: structureInputs.map((entry) => String(entry?.root ?? "")).filter(Boolean),
149
- localRoots: localInputs.map((entry) => String(entry?.root ?? "")).filter(Boolean),
150
- humanNotePaths: [],
151
- benchmarkBlueprintRoot: null,
152
- benchmarkMode: "none",
153
- acceptanceMode,
154
- generationOrder,
155
- inferenceRules,
156
- };
157
- }
158
-
159
- export function parseBlueprintReference(text) {
160
- if (!text) {
161
- return {
162
- ok: true,
163
- present: false,
164
- mode: null,
165
- root: null,
166
- canonicalTargetRoot: null,
167
- equivalenceContractRef: null,
168
- };
169
- }
170
-
171
- const parsed = parseYamlText(text);
172
- const reference = parsed?.blueprint_reference;
173
- const mode = toStringOrNull(reference?.mode);
174
- const root = toStringOrNull(reference?.root);
175
- const canonicalTargetRoot = toStringOrNull(reference?.canonical_target_root);
176
- const equivalenceContractRef = toStringOrNull(reference?.equivalence_contract_ref);
177
- const localRefValid = equivalenceContractRef === null
178
- || (equivalenceContractRef.startsWith(".nimi/local/") && !equivalenceContractRef.includes(".."));
179
-
180
- return {
181
- ok: parsed?.version === 2
182
- && ["repo_spec_blueprint", "custom_blueprint"].includes(mode)
183
- && typeof root === "string"
184
- && canonicalTargetRoot === ".nimi/spec"
185
- && localRefValid,
186
- present: true,
187
- mode,
188
- root,
189
- canonicalTargetRoot,
190
- equivalenceContractRef,
191
- };
192
- }
@@ -1,35 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
-
3
- export function runNodeScript(scriptPath, args = [], options = {}) {
4
- const result = spawnSync(process.execPath, [scriptPath, ...args], {
5
- cwd: options.cwd ?? process.cwd(),
6
- env: { ...process.env, ...(options.env ?? {}) },
7
- stdio: "pipe",
8
- encoding: "utf8",
9
- shell: false,
10
- });
11
-
12
- return {
13
- ok: result.status === 0,
14
- exitCode: result.status ?? 1,
15
- stdout: result.stdout || "",
16
- stderr: result.stderr || "",
17
- };
18
- }
19
-
20
- export function runCommand(command, options = {}) {
21
- const result = spawnSync(command, {
22
- cwd: options.cwd ?? process.cwd(),
23
- env: { ...process.env, ...(options.env ?? {}) },
24
- stdio: "pipe",
25
- encoding: "utf8",
26
- shell: true,
27
- });
28
-
29
- return {
30
- ok: result.status === 0,
31
- exitCode: result.status ?? 1,
32
- stdout: result.stdout || "",
33
- stderr: result.stderr || "",
34
- };
35
- }