@nimiplatform/nimi-coding 0.4.0 → 0.6.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 (70) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +92 -55
  4. package/README.zh-CN.md +93 -56
  5. package/cli/commands/authority.mjs +80 -125
  6. package/cli/commands/clear.mjs +3 -2
  7. package/cli/commands/code.mjs +225 -0
  8. package/cli/commands/start.mjs +3 -2
  9. package/cli/constants.mjs +2 -1
  10. package/cli/dev/check-authority-surface.mjs +17 -8
  11. package/cli/dev/check-product-conformance.mjs +4 -32
  12. package/cli/dev/run-cli-smoke.mjs +6 -0
  13. package/cli/help.mjs +8 -3
  14. package/cli/index.mjs +3 -2
  15. package/cli/lib/authority/audit.mjs +262 -185
  16. package/cli/lib/authority/change-candidates.mjs +807 -0
  17. package/cli/lib/authority/closed-sets.mjs +7 -8
  18. package/cli/lib/authority/compile.mjs +6 -24
  19. package/cli/lib/authority/compiled-snapshot.mjs +143 -0
  20. package/cli/lib/authority/consumers.mjs +69 -19
  21. package/cli/lib/authority/diagnostics.mjs +10 -2
  22. package/cli/lib/authority/diff.mjs +77 -54
  23. package/cli/lib/authority/format.mjs +4 -32
  24. package/cli/lib/authority/git-snapshot.mjs +34 -379
  25. package/cli/lib/authority/graph.mjs +10 -10
  26. package/cli/lib/authority/impact.mjs +17 -144
  27. package/cli/lib/authority/review.mjs +33 -43
  28. package/cli/lib/authority/sarif.mjs +1 -8
  29. package/cli/lib/authority/source-yaml.mjs +0 -4
  30. package/cli/lib/authority/surface.mjs +19 -1
  31. package/cli/lib/authority/terms.mjs +10 -9
  32. package/cli/lib/authority/validate.mjs +6 -14
  33. package/cli/lib/code/authority-annotation-response.mjs +43 -0
  34. package/cli/lib/code/authority-annotation-scan.mjs +162 -0
  35. package/cli/lib/code/authority-annotations.mjs +293 -0
  36. package/cli/lib/code/context.mjs +710 -0
  37. package/cli/lib/code/response.mjs +165 -0
  38. package/cli/lib/doctor.mjs +3 -2
  39. package/cli/lib/entrypoints.mjs +13 -5
  40. package/cli/seeds/seed-policy.yaml +2 -2
  41. package/contracts/authority-source.schema.yaml +0 -6
  42. package/contracts/authority-verifier-bindings.schema.yaml +21 -10
  43. package/methodology/authority-authoring.yaml +68 -70
  44. package/methodology/core.yaml +10 -6
  45. package/package.json +5 -3
  46. package/spec/authority-audit.yaml +56 -52
  47. package/spec/authority-authoring.yaml +3 -16
  48. package/spec/authority-change-candidates.yaml +146 -0
  49. package/spec/authority-consumers.yaml +7 -0
  50. package/spec/authority-discovery.yaml +2 -2
  51. package/spec/authority-graph.yaml +1 -1
  52. package/spec/authority-impact.yaml +16 -39
  53. package/spec/authority-review.yaml +29 -47
  54. package/spec/authority-terms.yaml +5 -1
  55. package/spec/code-authority-annotations.yaml +42 -0
  56. package/spec/code-context.yaml +38 -0
  57. package/spec/product-scope.yaml +15 -9
  58. package/cli/commands/validate-ai-governance.mjs +0 -335
  59. package/cli/lib/authority/evidence-snapshot.mjs +0 -1
  60. package/cli/lib/authority/evidence.mjs +0 -1432
  61. package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +0 -377
  62. package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +0 -376
  63. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +0 -155
  64. package/cli/lib/internal/governance/ai/check-high-risk-doc-metadata-core.mjs +0 -173
  65. package/cli/lib/internal/governance/config.mjs +0 -114
  66. package/cli/lib/internal/governance/shared/read-yaml-with-fragments.mjs +0 -49
  67. package/contracts/authority-evidence-bindings.schema.yaml +0 -53
  68. package/contracts/authority-evidence-probe-results.schema.yaml +0 -38
  69. package/contracts/authority-impact-dispositions.schema.yaml +0 -13
  70. package/spec/authority-evidence.yaml +0 -200
@@ -3,6 +3,7 @@ import { stat } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
 
5
5
  import { compileAuthorityPath } from "./compile.mjs";
6
+ import { createCompiledAuthoritySnapshot } from "./compiled-snapshot.mjs";
6
7
  import { compareText, makeDiagnostic, portablePath, sortDiagnostics } from "./diagnostics.mjs";
7
8
 
8
9
  const DIFF_FORMAT = "nimicoding.authority-diff/v1";
@@ -46,14 +47,14 @@ function portableMapping(mapping, basePath) {
46
47
  return { ...mapping, file: portablePath(mapping.file, basePath) };
47
48
  }
48
49
 
49
- function mappedAt(compiled, unitIndex, pointer, basePath) {
50
- const exact = compiled.sourceMap.fields[`/units/${unitIndex}${pointer}`];
51
- if (exact) return portableMapping(exact, basePath);
50
+ function mappedAt(snapshot, unitIndex, pointer) {
51
+ const exact = snapshot.compiled.sourceMap.fields[`/units/${unitIndex}${pointer}`];
52
+ if (exact) return portableMapping(exact, snapshot.inputRoot);
52
53
  const prefix = `/units/${unitIndex}${pointer}/`;
53
- const nested = Object.entries(compiled.sourceMap.fields)
54
+ const nested = Object.entries(snapshot.compiled.sourceMap.fields)
54
55
  .filter(([candidate]) => candidate.startsWith(prefix))
55
56
  .sort(([left], [right]) => compareText(left, right))[0]?.[1];
56
- return portableMapping(nested, basePath);
57
+ return portableMapping(nested, snapshot.inputRoot);
57
58
  }
58
59
 
59
60
  function addChange(changes, { unitId, operation = "modified", pointer, before, after, beforeSource = null, afterSource = null }) {
@@ -68,7 +69,7 @@ function addChange(changes, { unitId, operation = "modified", pointer, before, a
68
69
  });
69
70
  }
70
71
 
71
- function compareScalar(changes, beforeEntry, afterEntry, pointer, beforeBase, afterBase) {
72
+ function compareScalar(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry, pointer) {
72
73
  const before = beforeEntry.unit;
73
74
  const after = afterEntry.unit;
74
75
  const read = (value, parts) => parts.reduce((current, part) => current?.[part], value);
@@ -81,21 +82,21 @@ function compareScalar(changes, beforeEntry, afterEntry, pointer, beforeBase, af
81
82
  pointer,
82
83
  before: left,
83
84
  after: right,
84
- beforeSource: mappedAt(beforeEntry.compiled, beforeEntry.index, pointer, beforeBase),
85
- afterSource: mappedAt(afterEntry.compiled, afterEntry.index, pointer, afterBase),
85
+ beforeSource: mappedAt(beforeSnapshot, beforeEntry.unitIndex, pointer),
86
+ afterSource: mappedAt(afterSnapshot, afterEntry.unitIndex, pointer),
86
87
  });
87
88
  }
88
89
 
89
- function compareValueSet(changes, beforeEntry, afterEntry, pointer, beforeBase, afterBase) {
90
+ function compareValueSet(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry, pointer) {
90
91
  const parts = pointer.slice(1).split("/");
91
92
  const read = (unit) => parts.reduce((current, part) => current?.[part], unit) ?? [];
92
93
  const beforeValues = read(beforeEntry.unit);
93
94
  const afterValues = read(afterEntry.unit);
94
95
  const beforeSet = new Set(beforeValues.map((value) => JSON.stringify(value)));
95
96
  const afterSet = new Set(afterValues.map((value) => JSON.stringify(value)));
96
- for (const [operation, values, otherSet, entry, basePath, sourceSide] of [
97
- ["removed", beforeValues, afterSet, beforeEntry, beforeBase, "beforeSource"],
98
- ["added", afterValues, beforeSet, afterEntry, afterBase, "afterSource"],
97
+ for (const [operation, values, otherSet, snapshot, entry, sourceSide] of [
98
+ ["removed", beforeValues, afterSet, beforeSnapshot, beforeEntry, "beforeSource"],
99
+ ["added", afterValues, beforeSet, afterSnapshot, afterEntry, "afterSource"],
99
100
  ]) {
100
101
  values.forEach((value, index) => {
101
102
  if (otherSet.has(JSON.stringify(value))) return;
@@ -105,20 +106,20 @@ function compareValueSet(changes, beforeEntry, afterEntry, pointer, beforeBase,
105
106
  pointer,
106
107
  before: operation === "removed" ? value : null,
107
108
  after: operation === "added" ? value : null,
108
- [sourceSide]: mappedAt(entry.compiled, entry.index, `${pointer}/${index}`, basePath),
109
+ [sourceSide]: mappedAt(snapshot, entry.unitIndex, `${pointer}/${index}`),
109
110
  });
110
111
  });
111
112
  }
112
113
  }
113
114
 
114
- function compareRelations(changes, beforeEntry, afterEntry, beforeBase, afterBase) {
115
+ function compareRelations(changes, beforeSnapshot, afterSnapshot, beforeEntry, afterEntry) {
115
116
  const beforeRelations = beforeEntry.unit.relations;
116
117
  const afterRelations = afterEntry.unit.relations;
117
118
  const beforeSet = new Set(beforeRelations.map((edge) => `${edge.type}\0${edge.target}`));
118
119
  const afterSet = new Set(afterRelations.map((edge) => `${edge.type}\0${edge.target}`));
119
- for (const [operation, edges, otherSet, entry, basePath, sourceSide] of [
120
- ["removed", beforeRelations, afterSet, beforeEntry, beforeBase, "beforeSource"],
121
- ["added", afterRelations, beforeSet, afterEntry, afterBase, "afterSource"],
120
+ for (const [operation, edges, otherSet, snapshot, entry, sourceSide] of [
121
+ ["removed", beforeRelations, afterSet, beforeSnapshot, beforeEntry, "beforeSource"],
122
+ ["added", afterRelations, beforeSet, afterSnapshot, afterEntry, "afterSource"],
122
123
  ]) {
123
124
  edges.forEach((edge, index) => {
124
125
  if (otherSet.has(`${edge.type}\0${edge.target}`)) return;
@@ -128,23 +129,23 @@ function compareRelations(changes, beforeEntry, afterEntry, beforeBase, afterBas
128
129
  pointer: "/relations",
129
130
  before: operation === "removed" ? edge : null,
130
131
  after: operation === "added" ? edge : null,
131
- [sourceSide]: mappedAt(entry.compiled, entry.index, `/relations/${index}`, basePath),
132
+ [sourceSide]: mappedAt(snapshot, entry.unitIndex, `/relations/${index}`),
132
133
  });
133
134
  });
134
135
  }
135
136
  }
136
137
 
137
- function sideMappedAt(compiled, unitIndex, pointer, basePath, side) {
138
- const mapped = mappedAt(compiled, unitIndex, pointer, basePath);
138
+ function sideMappedAt(snapshot, unitIndex, pointer, side) {
139
+ const mapped = mappedAt(snapshot, unitIndex, pointer);
139
140
  return { ...mapped, file: path.posix.join(side, mapped.file) };
140
141
  }
141
142
 
142
- function transitionDiagnostics(before, after, beforeBase, afterBase) {
143
+ function transitionDiagnostics(before, after) {
143
144
  const diagnostics = [];
144
- const afterById = new Map(after.ir.units.map((unit) => [unit.id, unit]));
145
- before.ir.units.forEach((unit, index) => {
146
- if (afterById.has(unit.id)) return;
147
- const mapped = sideMappedAt(before, index, "/id", beforeBase, "before");
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");
148
149
  diagnostics.push(makeDiagnostic({
149
150
  code: "AUTH_DIFF_TRANSITION_INVALID",
150
151
  file: mapped.file,
@@ -155,12 +156,12 @@ function transitionDiagnostics(before, after, beforeBase, afterBase) {
155
156
  ? "retain the removed tombstone; permanent reserved-ID storage is not admitted"
156
157
  : "replace the active unit with a valid removed tombstone carrying an explicit reason",
157
158
  }));
158
- });
159
- const beforeById = new Map(before.ir.units.map((unit) => [unit.id, unit]));
160
- after.ir.units.forEach((unit, index) => {
161
- const previous = beforeById.get(unit.id);
162
- if (!previous) return;
163
- const mapped = sideMappedAt(after, index, "/id", afterBase, "after");
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");
164
165
  if (previous.lifecycle === "removed" && unit.lifecycle !== "removed") diagnostics.push(makeDiagnostic({
165
166
  code: "AUTH_DIFF_TRANSITION_INVALID",
166
167
  file: mapped.file,
@@ -177,18 +178,16 @@ function transitionDiagnostics(before, after, beforeBase, afterBase) {
177
178
  reason: `authority identity cannot change kind from ${previous.kind} to ${unit.kind}: ${unit.id}`,
178
179
  repair: "retain the original identity and declare a distinct ID for the new kind",
179
180
  }));
180
- });
181
+ }
181
182
  return sortDiagnostics(diagnostics);
182
183
  }
183
184
 
184
- function compareUnits(before, after, beforeBase, afterBase) {
185
+ function compareUnits(before, after) {
185
186
  const changes = [];
186
- const beforeById = new Map(before.ir.units.map((unit, index) => [unit.id, { unit, index, compiled: before }]));
187
- const afterById = new Map(after.ir.units.map((unit, index) => [unit.id, { unit, index, compiled: after }]));
188
- const ids = [...new Set([...beforeById.keys(), ...afterById.keys()])].sort(compareText);
187
+ const ids = [...new Set([...before.byId.keys(), ...after.byId.keys()])].sort(compareText);
189
188
  for (const id of ids) {
190
- const left = beforeById.get(id);
191
- const right = afterById.get(id);
189
+ const left = before.byId.get(id);
190
+ const right = after.byId.get(id);
192
191
  if (!left) {
193
192
  addChange(changes, {
194
193
  unitId: id,
@@ -196,16 +195,16 @@ function compareUnits(before, after, beforeBase, afterBase) {
196
195
  pointer: "",
197
196
  before: null,
198
197
  after: right.unit,
199
- afterSource: mappedAt(after, right.index, "/id", afterBase),
198
+ afterSource: mappedAt(after, right.unitIndex, "/id"),
200
199
  });
201
200
  continue;
202
201
  }
203
202
  if (!right) continue;
204
203
  for (const pointer of ["/kind", "/owner", "/lifecycle", "/metadata/title", "/metadata/reason", "/semantic/meaning", "/semantic/modality", "/semantic/statement", "/semantic/condition", "/semantic/failure"]) {
205
- compareScalar(changes, left, right, pointer, beforeBase, afterBase);
204
+ compareScalar(changes, before, after, left, right, pointer);
206
205
  }
207
- compareValueSet(changes, left, right, "/semantic/scope", beforeBase, afterBase);
208
- compareRelations(changes, left, right, beforeBase, afterBase);
206
+ compareValueSet(changes, before, after, left, right, "/semantic/scope");
207
+ compareRelations(changes, before, after, left, right);
209
208
  }
210
209
  return changes.sort((left, right) => (
211
210
  compareText(left.unitId, right.unitId)
@@ -231,7 +230,7 @@ function budgetDiagnostic(requiredBytes, maxBytes) {
231
230
  });
232
231
  }
233
232
 
234
- export async function diffAuthorityPaths(beforePath, afterPath, { maxBytes = null } = {}) {
233
+ export async function diffAuthorityPathsWithSnapshots(beforePath, afterPath, { maxBytes = null } = {}) {
235
234
  const [before, after, beforeBase, afterBase] = await Promise.all([
236
235
  compileAuthorityPath(beforePath),
237
236
  compileAuthorityPath(afterPath),
@@ -239,24 +238,46 @@ export async function diffAuthorityPaths(beforePath, afterPath, { maxBytes = nul
239
238
  sourceRoot(afterPath),
240
239
  ]);
241
240
  if (!before.ok || !after.ok) return {
242
- ok: false,
243
- diagnostics: sortDiagnostics([
244
- ...sideDiagnostics(before.diagnostics, beforeBase, "before"),
245
- ...sideDiagnostics(after.diagnostics, afterBase, "after"),
246
- ]),
247
- fileCount: before.fileCount + after.fileCount,
248
- unitCount: 0,
249
- diff: null,
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 }),
250
261
  };
251
- const invalidTransitions = transitionDiagnostics(before, after, beforeBase, afterBase);
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);
252
272
  if (invalidTransitions.length > 0) return {
253
273
  ok: false,
254
274
  diagnostics: invalidTransitions,
255
275
  fileCount: before.fileCount + after.fileCount,
256
276
  unitCount: 0,
257
277
  diff: null,
278
+ partial: false,
258
279
  };
259
- const changes = compareUnits(before, after, beforeBase, afterBase);
280
+ const changes = compareUnits(beforeSnapshot, afterSnapshot);
260
281
  const diff = {
261
282
  format: DIFF_FORMAT,
262
283
  changes,
@@ -273,15 +294,17 @@ export async function diffAuthorityPaths(beforePath, afterPath, { maxBytes = nul
273
294
  unitCount: 0,
274
295
  payloadBytes,
275
296
  diff: null,
297
+ partial: false,
276
298
  };
277
299
  return {
278
300
  ok: true,
279
301
  diagnostics: [],
280
302
  fileCount: before.fileCount + after.fileCount,
281
- unitCount: new Set([...before.ir.units, ...after.ir.units].map((unit) => unit.id)).size,
303
+ unitCount: new Set([...beforeSnapshot.byId.keys(), ...afterSnapshot.byId.keys()]).size,
282
304
  payloadBytes,
283
305
  diff,
284
306
  before,
285
307
  after,
308
+ partial: false,
286
309
  };
287
310
  }
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
 
4
4
  import { compareText, createLocator, makeDiagnostic, portablePath, REPAIRS, sortDiagnostics } from "./diagnostics.mjs";
5
5
  import { parseMarkdownAuthority, stringifyCanonicalMarkdown } from "./source-markdown.mjs";
6
- import { AUTHORITY_FORMAT_V2, loadAuthorityContract, parseYamlAuthority, stringifyCanonicalYaml } from "./source-yaml.mjs";
6
+ import { loadAuthorityContract, parseYamlAuthority, stringifyCanonicalYaml } from "./source-yaml.mjs";
7
7
  import { validateFormatStructure } from "./validate.mjs";
8
8
 
9
9
  export class AuthorityInputError extends Error {
@@ -107,35 +107,7 @@ export async function parseAuthorityPath(inputPath, options = {}) {
107
107
  return { contract, files, documents, sources };
108
108
  }
109
109
 
110
- function uniformValue(values) {
111
- return values.length > 0 && values.every((value) => value === values[0]) ? values[0] : null;
112
- }
113
-
114
- function upgradedYamlData(document, contract) {
115
- const units = document.units.map((source) => structuredClone(source.data));
116
- const identifier = new RegExp(contract.identifier_pattern);
117
- const uniformOwner = uniformValue(units.map((unit) => unit.owner));
118
- const activeRules = units.filter((unit) => unit.kind === "rule" && unit.lifecycle === "active");
119
- const uniformScope = uniformValue(activeRules.map((unit) => (
120
- Array.isArray(unit.scope) && unit.scope.length === 1 ? unit.scope[0] : null
121
- )));
122
- return {
123
- format: AUTHORITY_FORMAT_V2,
124
- ...((typeof uniformOwner === "string" && identifier.test(uniformOwner))
125
- ? { owner: uniformOwner }
126
- : document.data.format === AUTHORITY_FORMAT_V2 && Object.hasOwn(document.data, "owner") ? { owner: document.data.owner } : {}),
127
- ...((typeof uniformScope === "string" && identifier.test(uniformScope))
128
- ? { scope: uniformScope }
129
- : document.data.format === AUTHORITY_FORMAT_V2 && Object.hasOwn(document.data, "scope") ? { scope: document.data.scope } : {}),
130
- units,
131
- };
132
- }
133
-
134
- export function canonicalText(document, options = {}) {
135
- if (options.upgrade) {
136
- if (document.profile !== "yaml") throw new AuthorityInputError("authority fmt --upgrade requires one YAML authority container");
137
- return stringifyCanonicalYaml(upgradedYamlData(document, options.contract), { container: true });
138
- }
110
+ export function canonicalText(document) {
139
111
  return document.profile === "yaml"
140
112
  ? stringifyCanonicalYaml(document.data, { container: true })
141
113
  : stringifyCanonicalMarkdown(document.data);
@@ -178,7 +150,7 @@ export async function formatAuthorityFile(inputPath, options = {}) {
178
150
  const document = documents[0];
179
151
  const diagnostics = validateFormatStructure(document, contract);
180
152
  if (diagnostics.length > 0) return { ok: false, changed: false, diagnostics, fileCount: 1 };
181
- const canonical = canonicalText(document, { upgrade: options.upgrade === true, contract });
153
+ const canonical = canonicalText(document);
182
154
  const changed = canonical !== document.sourceText;
183
155
  if (options.check && changed) return {
184
156
  ok: false,
@@ -187,7 +159,7 @@ export async function formatAuthorityFile(inputPath, options = {}) {
187
159
  code: "AUTH_FORMAT_DRIFT",
188
160
  file: document.file,
189
161
  range: document.locations.get("") ?? document.locator.range(0, document.sourceText.length),
190
- reason: options.upgrade ? "authority source is not byte-canonical v2" : "authority source is not byte-canonical",
162
+ reason: "authority source is not byte-canonical",
191
163
  repair: REPAIRS.format,
192
164
  })],
193
165
  fileCount: 1,