@nimiplatform/nimi-coding 0.3.1 → 0.5.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 (115) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +3 -3
  3. package/README.md +297 -41
  4. package/README.zh-CN.md +306 -31
  5. package/bin/nimicoding.mjs +1 -1
  6. package/cli/commands/authority.mjs +804 -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 +182 -0
  13. package/cli/dev/check-product-conformance.mjs +889 -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 +567 -0
  18. package/cli/lib/authority/change-candidates.mjs +807 -0
  19. package/cli/lib/authority/closed-sets.mjs +645 -0
  20. package/cli/lib/authority/compile.mjs +92 -0
  21. package/cli/lib/authority/compiled-snapshot.mjs +143 -0
  22. package/cli/lib/authority/consumers.mjs +520 -0
  23. package/cli/lib/authority/diagnostics.mjs +93 -0
  24. package/cli/lib/authority/diff.mjs +310 -0
  25. package/cli/lib/authority/discover.mjs +447 -0
  26. package/cli/lib/authority/format.mjs +169 -0
  27. package/cli/lib/authority/git-snapshot.mjs +720 -0
  28. package/cli/lib/authority/graph.mjs +351 -0
  29. package/cli/lib/authority/impact.mjs +125 -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 +212 -0
  35. package/cli/lib/authority/sarif.mjs +174 -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 +308 -0
  40. package/cli/lib/authority/surface.mjs +97 -0
  41. package/cli/lib/authority/terms.mjs +422 -0
  42. package/cli/lib/authority/tracked-text.mjs +70 -0
  43. package/cli/lib/authority/validate.mjs +300 -0
  44. package/cli/lib/bootstrap.mjs +55 -71
  45. package/cli/lib/doctor.mjs +40 -123
  46. package/cli/lib/entrypoints.mjs +104 -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-scope-bindings.schema.yaml +15 -0
  56. package/contracts/authority-source.schema.yaml +58 -0
  57. package/contracts/authority-verifier-bindings.schema.yaml +31 -0
  58. package/methodology/authority-authoring.yaml +127 -0
  59. package/methodology/core.yaml +34 -27
  60. package/package.json +10 -6
  61. package/spec/authority-anchors.yaml +99 -0
  62. package/spec/authority-audit.yaml +89 -0
  63. package/spec/authority-authoring.yaml +137 -0
  64. package/spec/authority-change-candidates.yaml +146 -0
  65. package/spec/authority-closed-sets.yaml +65 -0
  66. package/spec/authority-consumers.yaml +77 -0
  67. package/spec/authority-context.yaml +50 -0
  68. package/spec/authority-discovery.yaml +119 -0
  69. package/spec/authority-graph.yaml +59 -0
  70. package/spec/authority-impact.yaml +61 -0
  71. package/spec/authority-review.yaml +71 -0
  72. package/spec/authority-terms.yaml +63 -0
  73. package/spec/product-scope.yaml +37 -23
  74. package/cli/commands/blueprint-audit.mjs +0 -91
  75. package/cli/commands/classify-spec-tree.mjs +0 -5
  76. package/cli/commands/generate-spec-derived-docs.mjs +0 -151
  77. package/cli/commands/generate-spec-migration-plan.mjs +0 -30
  78. package/cli/commands/surface-validator-command.mjs +0 -49
  79. package/cli/commands/validate-domain-admission.mjs +0 -5
  80. package/cli/commands/validate-guidance-bodies.mjs +0 -5
  81. package/cli/commands/validate-placement.mjs +0 -5
  82. package/cli/commands/validate-projection-edges.mjs +0 -5
  83. package/cli/commands/validate-spec-audit.mjs +0 -27
  84. package/cli/commands/validate-spec-governance.mjs +0 -144
  85. package/cli/commands/validate-spec-tree.mjs +0 -27
  86. package/cli/commands/validate-table-family.mjs +0 -5
  87. package/cli/commands/validate-tracked-output-admission.mjs +0 -5
  88. package/cli/lib/blueprint-audit.mjs +0 -363
  89. package/cli/lib/contracts.mjs +0 -21
  90. package/cli/lib/internal/contracts-loaders.mjs +0 -47
  91. package/cli/lib/internal/contracts-parse.mjs +0 -192
  92. package/cli/lib/internal/governance/runner.mjs +0 -35
  93. package/cli/lib/internal/surface-taxonomy-validators.mjs +0 -1147
  94. package/cli/lib/internal/validators-shared.mjs +0 -12
  95. package/cli/lib/internal/validators-spec-helpers.mjs +0 -52
  96. package/cli/lib/internal/validators-spec.mjs +0 -380
  97. package/cli/lib/validators.mjs +0 -30
  98. package/config/bootstrap.yaml +0 -6
  99. package/config/spec-generation-inputs.yaml +0 -41
  100. package/contracts/domain-admission.schema.yaml +0 -56
  101. package/contracts/migration-inventory.schema.yaml +0 -15
  102. package/contracts/negative-fixtures.yaml +0 -26
  103. package/contracts/placement-contract.schema.yaml +0 -127
  104. package/contracts/projection-edge.schema.yaml +0 -46
  105. package/contracts/shared-enums.yaml +0 -63
  106. package/contracts/spec-generation-audit.schema.yaml +0 -46
  107. package/contracts/spec-generation-inputs.schema.yaml +0 -43
  108. package/contracts/spec-layout.schema.yaml +0 -32
  109. package/contracts/surface-taxonomy.schema.yaml +0 -139
  110. package/contracts/table-family.schema.yaml +0 -133
  111. package/contracts/tracked-output-admission.schema.yaml +0 -17
  112. package/methodology/four-closure-policy.yaml +0 -20
  113. package/methodology/role-separation-policy.yaml +0 -22
  114. package/methodology/spec-reconstruction.yaml +0 -69
  115. package/spec/_meta/spec-tree-model.yaml +0 -44
@@ -0,0 +1,310 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import { compileAuthorityPath } from "./compile.mjs";
6
+ import { createCompiledAuthoritySnapshot } from "./compiled-snapshot.mjs";
7
+ import { compareText, makeDiagnostic, portablePath, sortDiagnostics } from "./diagnostics.mjs";
8
+
9
+ const DIFF_FORMAT = "nimicoding.authority-diff/v1";
10
+
11
+ async function sourceRoot(inputPath) {
12
+ const absolute = path.resolve(inputPath);
13
+ try {
14
+ const info = await stat(absolute);
15
+ return info.isDirectory() ? absolute : path.dirname(absolute);
16
+ } catch {
17
+ return path.dirname(absolute);
18
+ }
19
+ }
20
+
21
+ function sidePath(file, basePath, side) {
22
+ const relative = portablePath(file, basePath);
23
+ const safe = path.isAbsolute(relative) ? path.basename(relative) : relative;
24
+ return path.posix.join(side, safe || ".");
25
+ }
26
+
27
+ function compareRelated(left, right) {
28
+ return compareText(left.path, right.path)
29
+ || left.range.start.line - right.range.start.line
30
+ || left.range.start.column - right.range.start.column
31
+ || compareText(left.pointer, right.pointer)
32
+ || compareText(left.role, right.role);
33
+ }
34
+
35
+ function sideDiagnostics(diagnostics, basePath, side) {
36
+ return diagnostics.map((diagnostic) => ({
37
+ ...diagnostic,
38
+ path: sidePath(diagnostic.path, basePath, side),
39
+ related: diagnostic.related
40
+ .map((related) => ({ ...related, path: sidePath(related.path, basePath, side) }))
41
+ .sort(compareRelated),
42
+ }));
43
+ }
44
+
45
+ function portableMapping(mapping, basePath) {
46
+ if (!mapping) return null;
47
+ return { ...mapping, file: portablePath(mapping.file, basePath) };
48
+ }
49
+
50
+ function mappedAt(snapshot, unitIndex, pointer) {
51
+ const exact = snapshot.compiled.sourceMap.fields[`/units/${unitIndex}${pointer}`];
52
+ if (exact) return portableMapping(exact, snapshot.inputRoot);
53
+ const prefix = `/units/${unitIndex}${pointer}/`;
54
+ const nested = Object.entries(snapshot.compiled.sourceMap.fields)
55
+ .filter(([candidate]) => candidate.startsWith(prefix))
56
+ .sort(([left], [right]) => compareText(left, right))[0]?.[1];
57
+ return portableMapping(nested, snapshot.inputRoot);
58
+ }
59
+
60
+ function addChange(changes, { unitId, operation = "modified", pointer, before, after, beforeSource = null, afterSource = null }) {
61
+ changes.push({
62
+ unitId,
63
+ operation,
64
+ pointer,
65
+ before,
66
+ after,
67
+ ...(beforeSource ? { beforeSource } : {}),
68
+ ...(afterSource ? { afterSource } : {}),
69
+ });
70
+ }
71
+
72
+ function compareScalar(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry, pointer) {
73
+ const before = beforeEntry.unit;
74
+ const after = afterEntry.unit;
75
+ const read = (value, parts) => parts.reduce((current, part) => current?.[part], value);
76
+ const parts = pointer.slice(1).split("/");
77
+ const left = read(before, parts) ?? null;
78
+ const right = read(after, parts) ?? null;
79
+ if (JSON.stringify(left) === JSON.stringify(right)) return;
80
+ addChange(changes, {
81
+ unitId: after.id,
82
+ pointer,
83
+ before: left,
84
+ after: right,
85
+ beforeSource: mappedAt(beforeSnapshot, beforeEntry.unitIndex, pointer),
86
+ afterSource: mappedAt(afterSnapshot, afterEntry.unitIndex, pointer),
87
+ });
88
+ }
89
+
90
+ function compareValueSet(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry, pointer) {
91
+ const parts = pointer.slice(1).split("/");
92
+ const read = (unit) => parts.reduce((current, part) => current?.[part], unit) ?? [];
93
+ const beforeValues = read(beforeEntry.unit);
94
+ const afterValues = read(afterEntry.unit);
95
+ const beforeSet = new Set(beforeValues.map((value) => JSON.stringify(value)));
96
+ const afterSet = new Set(afterValues.map((value) => JSON.stringify(value)));
97
+ for (const [operation, values, otherSet, snapshot, entry, sourceSide] of [
98
+ ["removed", beforeValues, afterSet, beforeSnapshot, beforeEntry, "beforeSource"],
99
+ ["added", afterValues, beforeSet, afterSnapshot, afterEntry, "afterSource"],
100
+ ]) {
101
+ values.forEach((value, index) => {
102
+ if (otherSet.has(JSON.stringify(value))) return;
103
+ addChange(changes, {
104
+ unitId: afterEntry.unit.id,
105
+ operation,
106
+ pointer,
107
+ before: operation === "removed" ? value : null,
108
+ after: operation === "added" ? value : null,
109
+ [sourceSide]: mappedAt(snapshot, entry.unitIndex, `${pointer}/${index}`),
110
+ });
111
+ });
112
+ }
113
+ }
114
+
115
+ function compareRelations(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry) {
116
+ const beforeRelations = beforeEntry.unit.relations;
117
+ const afterRelations = afterEntry.unit.relations;
118
+ const beforeSet = new Set(beforeRelations.map((edge) => `${edge.type}\0${edge.target}`));
119
+ const afterSet = new Set(afterRelations.map((edge) => `${edge.type}\0${edge.target}`));
120
+ for (const [operation, edges, otherSet, snapshot, entry, sourceSide] of [
121
+ ["removed", beforeRelations, afterSet, beforeSnapshot, beforeEntry, "beforeSource"],
122
+ ["added", afterRelations, beforeSet, afterSnapshot, afterEntry, "afterSource"],
123
+ ]) {
124
+ edges.forEach((edge, index) => {
125
+ if (otherSet.has(`${edge.type}\0${edge.target}`)) return;
126
+ addChange(changes, {
127
+ unitId: afterEntry.unit.id,
128
+ operation,
129
+ pointer: "/relations",
130
+ before: operation === "removed" ? edge : null,
131
+ after: operation === "added" ? edge : null,
132
+ [sourceSide]: mappedAt(snapshot, entry.unitIndex, `/relations/${index}`),
133
+ });
134
+ });
135
+ }
136
+ }
137
+
138
+ function sideMappedAt(snapshot, unitIndex, pointer, side) {
139
+ const mapped = mappedAt(snapshot, unitIndex, pointer);
140
+ return { ...mapped, file: path.posix.join(side, mapped.file) };
141
+ }
142
+
143
+ function transitionDiagnostics(before, after) {
144
+ const diagnostics = [];
145
+ for (const entry of before.byId.values()) {
146
+ const { unit, unitIndex } = entry;
147
+ if (after.byId.has(unit.id)) continue;
148
+ const mapped = sideMappedAt(before, unitIndex, "/id", "before");
149
+ diagnostics.push(makeDiagnostic({
150
+ code: "AUTH_DIFF_TRANSITION_INVALID",
151
+ file: mapped.file,
152
+ range: mapped.range,
153
+ pointer: mapped.sourcePointer,
154
+ reason: `authority identity cannot physically disappear: ${unit.id}`,
155
+ repair: unit.lifecycle === "removed"
156
+ ? "retain the removed tombstone; permanent reserved-ID storage is not admitted"
157
+ : "replace the active unit with a valid removed tombstone carrying an explicit reason",
158
+ }));
159
+ }
160
+ for (const entry of after.byId.values()) {
161
+ const { unit, unitIndex } = entry;
162
+ const previous = before.byId.get(unit.id)?.unit;
163
+ if (!previous) continue;
164
+ const mapped = sideMappedAt(after, unitIndex, "/id", "after");
165
+ if (previous.lifecycle === "removed" && unit.lifecycle !== "removed") diagnostics.push(makeDiagnostic({
166
+ code: "AUTH_DIFF_TRANSITION_INVALID",
167
+ file: mapped.file,
168
+ range: mapped.range,
169
+ pointer: mapped.sourcePointer,
170
+ reason: `removed identity cannot return to ${unit.lifecycle}: ${unit.id}`,
171
+ repair: "retain the removed identity and declare a new product-authorized ID",
172
+ }));
173
+ if (previous.kind !== unit.kind) diagnostics.push(makeDiagnostic({
174
+ code: "AUTH_DIFF_TRANSITION_INVALID",
175
+ file: mapped.file,
176
+ range: mapped.range,
177
+ pointer: mapped.sourcePointer,
178
+ reason: `authority identity cannot change kind from ${previous.kind} to ${unit.kind}: ${unit.id}`,
179
+ repair: "retain the original identity and declare a distinct ID for the new kind",
180
+ }));
181
+ }
182
+ return sortDiagnostics(diagnostics);
183
+ }
184
+
185
+ function compareUnits(before, after) {
186
+ const changes = [];
187
+ const ids = [...new Set([...before.byId.keys(), ...after.byId.keys()])].sort(compareText);
188
+ for (const id of ids) {
189
+ const left = before.byId.get(id);
190
+ const right = after.byId.get(id);
191
+ if (!left) {
192
+ addChange(changes, {
193
+ unitId: id,
194
+ operation: "unit_added",
195
+ pointer: "",
196
+ before: null,
197
+ after: right.unit,
198
+ afterSource: mappedAt(after, right.unitIndex, "/id"),
199
+ });
200
+ continue;
201
+ }
202
+ if (!right) continue;
203
+ for (const pointer of ["/kind", "/owner", "/lifecycle", "/metadata/title", "/metadata/reason", "/semantic/meaning", "/semantic/modality", "/semantic/statement", "/semantic/condition", "/semantic/failure"]) {
204
+ compareScalar(changes, before, after, left, right, pointer);
205
+ }
206
+ compareValueSet(changes, before, after, left, right, "/semantic/scope");
207
+ compareRelations(changes, before, after, left, right);
208
+ }
209
+ return changes.sort((left, right) => (
210
+ compareText(left.unitId, right.unitId)
211
+ || compareText(left.pointer, right.pointer)
212
+ || compareText(left.operation, right.operation)
213
+ || compareText(JSON.stringify(left.before), JSON.stringify(right.before))
214
+ || compareText(JSON.stringify(left.after), JSON.stringify(right.after))
215
+ ));
216
+ }
217
+
218
+ export function canonicalDiffBytes(diff) {
219
+ return Buffer.byteLength(JSON.stringify(diff), "utf8");
220
+ }
221
+
222
+ function budgetDiagnostic(requiredBytes, maxBytes) {
223
+ return makeDiagnostic({
224
+ code: "AUTH_DIFF_BUDGET",
225
+ file: ".",
226
+ range: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } },
227
+ pointer: "",
228
+ reason: `complete semantic diff requires ${requiredBytes} UTF-8 bytes but max-bytes is ${maxBytes}`,
229
+ repair: "increase the explicit byte budget; partial semantic diff is forbidden",
230
+ });
231
+ }
232
+
233
+ export async function diffAuthorityPathsWithSnapshots(beforePath, afterPath, { maxBytes = null } = {}) {
234
+ const [before, after, beforeBase, afterBase] = await Promise.all([
235
+ compileAuthorityPath(beforePath),
236
+ compileAuthorityPath(afterPath),
237
+ sourceRoot(beforePath),
238
+ sourceRoot(afterPath),
239
+ ]);
240
+ if (!before.ok || !after.ok) return {
241
+ beforeSnapshot: null,
242
+ afterSnapshot: null,
243
+ result: {
244
+ ok: false,
245
+ diagnostics: sortDiagnostics([
246
+ ...sideDiagnostics(before.diagnostics, beforeBase, "before"),
247
+ ...sideDiagnostics(after.diagnostics, afterBase, "after"),
248
+ ]),
249
+ fileCount: before.fileCount + after.fileCount,
250
+ unitCount: 0,
251
+ diff: null,
252
+ partial: false,
253
+ },
254
+ };
255
+ const beforeSnapshot = createCompiledAuthoritySnapshot(before, beforeBase);
256
+ const afterSnapshot = createCompiledAuthoritySnapshot(after, afterBase);
257
+ return {
258
+ beforeSnapshot,
259
+ afterSnapshot,
260
+ result: diffAuthoritySnapshots(beforeSnapshot, afterSnapshot, { maxBytes }),
261
+ };
262
+ }
263
+
264
+ export async function diffAuthorityPaths(beforePath, afterPath, options = {}) {
265
+ return (await diffAuthorityPathsWithSnapshots(beforePath, afterPath, options)).result;
266
+ }
267
+
268
+ export function diffAuthoritySnapshots(beforeSnapshot, afterSnapshot, { maxBytes = null } = {}) {
269
+ const before = beforeSnapshot.compiled;
270
+ const after = afterSnapshot.compiled;
271
+ const invalidTransitions = transitionDiagnostics(beforeSnapshot, afterSnapshot);
272
+ if (invalidTransitions.length > 0) return {
273
+ ok: false,
274
+ diagnostics: invalidTransitions,
275
+ fileCount: before.fileCount + after.fileCount,
276
+ unitCount: 0,
277
+ diff: null,
278
+ partial: false,
279
+ };
280
+ const changes = compareUnits(beforeSnapshot, afterSnapshot);
281
+ const diff = {
282
+ format: DIFF_FORMAT,
283
+ changes,
284
+ summary: {
285
+ units: new Set(changes.map((change) => change.unitId)).size,
286
+ changes: changes.length,
287
+ },
288
+ };
289
+ const payloadBytes = canonicalDiffBytes(diff);
290
+ if (maxBytes !== null && payloadBytes > maxBytes) return {
291
+ ok: false,
292
+ diagnostics: [budgetDiagnostic(payloadBytes, maxBytes)],
293
+ fileCount: before.fileCount + after.fileCount,
294
+ unitCount: 0,
295
+ payloadBytes,
296
+ diff: null,
297
+ partial: false,
298
+ };
299
+ return {
300
+ ok: true,
301
+ diagnostics: [],
302
+ fileCount: before.fileCount + after.fileCount,
303
+ unitCount: new Set([...beforeSnapshot.byId.keys(), ...afterSnapshot.byId.keys()]).size,
304
+ payloadBytes,
305
+ diff,
306
+ before,
307
+ after,
308
+ partial: false,
309
+ };
310
+ }