@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
@@ -0,0 +1,225 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { queryCodeAuthority, CodeAuthorityFailure } from "../lib/code/authority-annotations.mjs";
3
+ import {
4
+ completedCodeAuthorityResponse,
5
+ MIN_CODE_AUTHORITY_BYTES,
6
+ refusedCodeAuthorityResponse,
7
+ renderCodeAuthorityResponse,
8
+ serializeCodeAuthorityResponse,
9
+ } from "../lib/code/authority-annotation-response.mjs";
10
+ import { contextForTypeScript, CodeContextFailure } from "../lib/code/context.mjs";
11
+ import {
12
+ MIN_CODE_CONTEXT_BYTES,
13
+ refusedCodeResponse,
14
+ renderContextResponse,
15
+ serializeCodeResponse,
16
+ } from "../lib/code/response.mjs";
17
+
18
+ export const CODE_CONTEXT_USAGE = "nimicoding code context <repo-relative-.ts-or-.tsx-path> --repo <exact-git-worktree-root> --symbol <top-level-identifier> --tsconfig <repo-relative-tsconfig> --max-bytes <integer>=512";
19
+ export const CODE_AUTHORITY_USAGE = "nimicoding code authority --repo <exact-git-worktree-root> (--authority <exact-id>|--source <repo-relative-.ts-.tsx-or-.go-path>|--audit) --max-files <integer> --max-bytes <integer>=512";
20
+ export const CODE_COMMAND_USAGE = [CODE_CONTEXT_USAGE, CODE_AUTHORITY_USAGE];
21
+
22
+ const GLOBAL_FLAG_OPTIONS = new Set(["--color", "--no-color"]);
23
+ const CODE_OPTIONS = new Set(["--repo", "--symbol", "--tsconfig", "--max-bytes"]);
24
+ const CODE_AUTHORITY_VALUE_OPTIONS = new Set(["--repo", "--authority", "--source", "--max-files", "--max-bytes"]);
25
+ const SUPPORTED_LANGUAGES = new Set(["en", "zh"]);
26
+
27
+ function writeResponse(response) {
28
+ process.stdout.write(serializeCodeResponse(response));
29
+ }
30
+
31
+ function writeRefusal(code, message, exitCode) {
32
+ writeResponse(refusedCodeResponse(code, message));
33
+ return exitCode;
34
+ }
35
+
36
+ function writeAuthorityResponse(response) {
37
+ process.stdout.write(serializeCodeAuthorityResponse(response));
38
+ }
39
+
40
+ function writeAuthorityRefusal(code, message, exitCode) {
41
+ writeAuthorityResponse(refusedCodeAuthorityResponse(code, message));
42
+ return exitCode;
43
+ }
44
+
45
+ export function classifyCodeInvocation(args) {
46
+ for (let index = 0; index < args.length; index += 1) {
47
+ const argument = args[index];
48
+ if (GLOBAL_FLAG_OPTIONS.has(argument) || argument.startsWith("--lang=")) continue;
49
+ if (argument === "--lang") {
50
+ const value = args[index + 1];
51
+ if (value !== undefined && !value.startsWith("--")) index += 1;
52
+ continue;
53
+ }
54
+ return argument === "code" ? index : null;
55
+ }
56
+ return null;
57
+ }
58
+
59
+ function parseLeadingGlobals(args, commandIndex) {
60
+ for (let index = 0; index < commandIndex; index += 1) {
61
+ const argument = args[index];
62
+ if (GLOBAL_FLAG_OPTIONS.has(argument)) continue;
63
+ if (argument.startsWith("--lang=")) {
64
+ if (!SUPPORTED_LANGUAGES.has(argument.slice("--lang=".length))) return false;
65
+ continue;
66
+ }
67
+ if (argument === "--lang") {
68
+ const value = args[index + 1];
69
+ if (!SUPPORTED_LANGUAGES.has(value)) return false;
70
+ index += 1;
71
+ continue;
72
+ }
73
+ return false;
74
+ }
75
+ return true;
76
+ }
77
+
78
+ function parseContextArguments(args) {
79
+ if (args[0] !== "context") return null;
80
+ const positional = [];
81
+ const options = new Map();
82
+ for (let index = 1; index < args.length; index += 1) {
83
+ const argument = args[index];
84
+ if (GLOBAL_FLAG_OPTIONS.has(argument)) continue;
85
+ if (argument.startsWith("--lang=")) {
86
+ if (!SUPPORTED_LANGUAGES.has(argument.slice("--lang=".length))) return null;
87
+ continue;
88
+ }
89
+ if (argument === "--lang") {
90
+ const value = args[index + 1];
91
+ if (!SUPPORTED_LANGUAGES.has(value)) return null;
92
+ index += 1;
93
+ continue;
94
+ }
95
+ if (argument.startsWith("--")) {
96
+ if (!CODE_OPTIONS.has(argument) || options.has(argument)) return null;
97
+ const value = args[index + 1];
98
+ if (value === undefined || value.startsWith("--")) return null;
99
+ options.set(argument, value);
100
+ index += 1;
101
+ continue;
102
+ }
103
+ positional.push(argument);
104
+ }
105
+ if (positional.length !== 1 || options.size !== CODE_OPTIONS.size) return null;
106
+ const maxBytesText = options.get("--max-bytes");
107
+ if (!/^(?:0|[1-9][0-9]*)$/u.test(maxBytesText)) return null;
108
+ const maxBytes = Number(maxBytesText);
109
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < MIN_CODE_CONTEXT_BYTES) return null;
110
+ const symbol = options.get("--symbol");
111
+ if (!/^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u.test(symbol)) return null;
112
+ return {
113
+ source: positional[0],
114
+ repository: options.get("--repo"),
115
+ symbol,
116
+ tsconfig: options.get("--tsconfig"),
117
+ maxBytes,
118
+ };
119
+ }
120
+
121
+ function parseAuthorityArguments(args) {
122
+ if (args[0] !== "authority") return null;
123
+ const options = new Map();
124
+ let audit = false;
125
+ for (let index = 1; index < args.length; index += 1) {
126
+ const argument = args[index];
127
+ if (GLOBAL_FLAG_OPTIONS.has(argument)) continue;
128
+ if (argument.startsWith("--lang=")) {
129
+ if (!SUPPORTED_LANGUAGES.has(argument.slice("--lang=".length))) return null;
130
+ continue;
131
+ }
132
+ if (argument === "--lang") {
133
+ const value = args[index + 1];
134
+ if (!SUPPORTED_LANGUAGES.has(value)) return null;
135
+ index += 1;
136
+ continue;
137
+ }
138
+ if (argument === "--audit") {
139
+ if (audit) return null;
140
+ audit = true;
141
+ continue;
142
+ }
143
+ if (!CODE_AUTHORITY_VALUE_OPTIONS.has(argument) || options.has(argument)) return null;
144
+ const value = args[index + 1];
145
+ if (value === undefined || value.startsWith("--")) return null;
146
+ options.set(argument, value);
147
+ index += 1;
148
+ }
149
+ if (!options.has("--repo") || !options.has("--max-files") || !options.has("--max-bytes")) return null;
150
+ const selectors = Number(options.has("--authority")) + Number(options.has("--source")) + Number(audit);
151
+ if (selectors !== 1) return null;
152
+ const maxFilesText = options.get("--max-files");
153
+ const maxBytesText = options.get("--max-bytes");
154
+ if (!/^[1-9][0-9]*$/u.test(maxFilesText) || !/^(?:0|[1-9][0-9]*)$/u.test(maxBytesText)) return null;
155
+ const maxFiles = Number(maxFilesText);
156
+ const maxBytes = Number(maxBytesText);
157
+ if (!Number.isSafeInteger(maxFiles) || !Number.isSafeInteger(maxBytes) || maxBytes < MIN_CODE_AUTHORITY_BYTES) return null;
158
+ const selector = options.has("--authority")
159
+ ? { kind: "authority", id: options.get("--authority") }
160
+ : options.has("--source")
161
+ ? { kind: "source", path: options.get("--source") }
162
+ : { kind: "audit" };
163
+ return { repository: options.get("--repo"), selector, maxFiles, maxBytes };
164
+ }
165
+
166
+ async function runAuthorityInvocation(args) {
167
+ const parsed = parseAuthorityArguments(args);
168
+ if (parsed === null) return writeAuthorityRefusal("CODE_USAGE", "invalid code authority command arguments", 2);
169
+ try {
170
+ const result = await queryCodeAuthority(parsed);
171
+ const response = completedCodeAuthorityResponse(result);
172
+ const rendered = renderCodeAuthorityResponse(response, parsed.maxBytes);
173
+ if (rendered === null) {
174
+ const refusal = serializeCodeAuthorityResponse(refusedCodeAuthorityResponse(
175
+ "CODE_AUTHORITY_OUTPUT_BUDGET",
176
+ "max-bytes cannot contain the complete code authority response",
177
+ ));
178
+ if (Buffer.byteLength(refusal, "utf8") > parsed.maxBytes) {
179
+ throw new Error("minimum code authority response budget cannot contain its refusal envelope");
180
+ }
181
+ process.stdout.write(refusal);
182
+ return 1;
183
+ }
184
+ process.stdout.write(rendered);
185
+ return response.status === "findings" ? 1 : 0;
186
+ } catch (error) {
187
+ if (error instanceof CodeAuthorityFailure) {
188
+ return writeAuthorityRefusal(error.code, error.message, error.exitCode);
189
+ }
190
+ return writeAuthorityRefusal("CODE_AUTHORITY_INTERNAL", "code authority query failed unexpectedly", 2);
191
+ }
192
+ }
193
+
194
+ export async function runCodeInvocation(args, commandIndex) {
195
+ const operation = args[commandIndex + 1];
196
+ if (!parseLeadingGlobals(args, commandIndex)) {
197
+ return operation === "authority"
198
+ ? writeAuthorityRefusal("CODE_USAGE", "invalid code authority command arguments", 2)
199
+ : writeRefusal("CODE_USAGE", "invalid code command arguments", 2);
200
+ }
201
+ if (operation === "authority") return runAuthorityInvocation(args.slice(commandIndex + 1));
202
+ const parsed = parseContextArguments(args.slice(commandIndex + 1));
203
+ if (parsed === null) return writeRefusal("CODE_USAGE", "invalid code command arguments", 2);
204
+ try {
205
+ const { maxBytes, ...request } = parsed;
206
+ const { scope, projection } = await contextForTypeScript(request);
207
+ const rendered = renderContextResponse({ scope, projection, maxBytes });
208
+ if (rendered === null) {
209
+ const response = serializeCodeResponse(refusedCodeResponse(
210
+ "CODE_OUTPUT_BUDGET",
211
+ "max-bytes cannot contain the mandatory code context response",
212
+ ));
213
+ if (Buffer.byteLength(response, "utf8") > maxBytes) {
214
+ throw new Error("minimum code response budget cannot contain its refusal envelope");
215
+ }
216
+ process.stdout.write(response);
217
+ return 1;
218
+ }
219
+ process.stdout.write(rendered.text);
220
+ return 0;
221
+ } catch (error) {
222
+ if (error instanceof CodeContextFailure) return writeRefusal(error.code, error.message, 2);
223
+ return writeRefusal("CODE_INTERNAL", "code context failed unexpectedly", 2);
224
+ }
225
+ }
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
 
3
+ import { PROJECT_LOCAL_CLI_INVOCATION } from "../constants.mjs";
3
4
  import {
4
5
  integrateEntrypoints,
5
6
  pathExists,
@@ -65,8 +66,8 @@ export async function runStart(args) {
65
66
  entrypoints,
66
67
  next: [
67
68
  "Author project-owned canonical authority under .nimi/spec using only *.authority.yaml or *.authority.md.",
68
- "Run nimicoding authority fmt on each changed authority file.",
69
- "Run nimicoding authority check .nimi/spec on the complete canonical root.",
69
+ `Run ${PROJECT_LOCAL_CLI_INVOCATION} authority fmt on each changed authority file.`,
70
+ `Run ${PROJECT_LOCAL_CLI_INVOCATION} authority check .nimi/spec on the complete canonical root.`,
70
71
  ],
71
72
  };
72
73
 
package/cli/constants.mjs CHANGED
@@ -1,5 +1,6 @@
1
- export const VERSION = "0.4.0";
1
+ export const VERSION = "0.6.0";
2
2
  export const PACKAGE_NAME = "@nimiplatform/nimi-coding";
3
+ export const PROJECT_LOCAL_CLI_INVOCATION = "pnpm exec nimicoding";
3
4
  export const LOCAL_GITIGNORE_ENTRIES = [".nimi/local/"];
4
5
  export const AUTHORITY_GITATTRIBUTES_ENTRIES = [
5
6
  "*.authority.yaml text eol=lf",
@@ -12,12 +12,18 @@ import { fileURLToPath } from "node:url";
12
12
 
13
13
  import YAML from "yaml";
14
14
 
15
+ import { AUTHORITY_COMMAND_USAGE } from "../commands/authority.mjs";
15
16
  import { PNPM_BUILTIN_SUBCOMMANDS } from "../lib/authority/anchors.mjs";
16
- import { AUTHORING_SURFACE, AUTHORITY_SURFACE, REPOSITORY_BOUND_COMMANDS } from "../lib/authority/surface.mjs";
17
+ import {
18
+ AUTHORING_SURFACE,
19
+ AUTHORITY_SURFACE,
20
+ ENFORCED_AUTHORITY_COMMANDS,
21
+ } from "../lib/authority/surface.mjs";
17
22
 
18
23
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
19
24
 
20
25
  const CONTRACT_FILES = {
26
+ audit: "spec/authority-audit.yaml",
21
27
  anchors: "spec/authority-anchors.yaml",
22
28
  consumers: "spec/authority-consumers.yaml",
23
29
  terms: "spec/authority-terms.yaml",
@@ -42,7 +48,7 @@ export function checkAuthoritySurface(root = packageRoot) {
42
48
  const failures = [];
43
49
  const contractIds = new Map();
44
50
 
45
- for (const command of REPOSITORY_BOUND_COMMANDS) {
51
+ for (const command of ENFORCED_AUTHORITY_COMMANDS) {
46
52
  const relative = CONTRACT_FILES[command];
47
53
  if (relative === undefined) {
48
54
  failures.push(`command ${command} is in the enforced surface but has no product contract file`);
@@ -109,6 +115,13 @@ export function checkAuthoritySurface(root = packageRoot) {
109
115
  `${command} command line flags ${JSON.stringify(commandFlags)} != declared surface ${JSON.stringify(surfaceFlags)}`,
110
116
  );
111
117
  }
118
+ const usagePrefix = `nimicoding authority ${command} `;
119
+ const helpUsages = AUTHORITY_COMMAND_USAGE.filter((usage) => usage.startsWith(usagePrefix));
120
+ if (helpUsages.length !== 1 || helpUsages[0] !== contract.command) {
121
+ failures.push(
122
+ `${command} help usage ${JSON.stringify(helpUsages)} != contract command ${JSON.stringify(contract.command)}`,
123
+ );
124
+ }
112
125
  }
113
126
 
114
127
  // fmt and the source schema: the authoring contract has to describe the
@@ -135,10 +148,6 @@ export function checkAuthoritySurface(root = packageRoot) {
135
148
  if (authoring.fields?.format !== source.format) {
136
149
  failures.push(`authoring format ${authoring.fields?.format} != source schema format ${source.format}`);
137
150
  }
138
- const authoringMigration = authoring.fields?.migration_inputs?.format;
139
- if (authoringMigration !== source.migration_inputs?.format) {
140
- failures.push(`migration input format: authoring ${authoringMigration} != source schema ${source.migration_inputs?.format}`);
141
- }
142
151
  if (!sameSet(authoring.profiles?.yaml?.container_fields ?? [], source.yaml_document?.allowed_fields ?? [])) {
143
152
  failures.push(
144
153
  `container fields: authoring ${JSON.stringify(sorted(authoring.profiles?.yaml?.container_fields ?? []))} `
@@ -152,7 +161,7 @@ export function checkAuthoritySurface(root = packageRoot) {
152
161
 
153
162
  // The other direction: a contract that no enforced command claims.
154
163
  for (const [command, relative] of Object.entries(CONTRACT_FILES)) {
155
- if (!REPOSITORY_BOUND_COMMANDS.includes(command)) {
164
+ if (!ENFORCED_AUTHORITY_COMMANDS.includes(command)) {
156
165
  failures.push(`${relative} declares command ${command}, which the enforced surface does not admit`);
157
166
  }
158
167
  }
@@ -168,6 +177,6 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
168
177
  for (const failure of failures) process.stderr.write(`authority-surface: ${failure}\n`);
169
178
  if (failures.length > 0) process.exit(1);
170
179
  process.stdout.write(
171
- `authority-surface: ${REPOSITORY_BOUND_COMMANDS.length} repository-bound commands match their product contracts\n`,
180
+ `authority-surface: ${ENFORCED_AUTHORITY_COMMANDS.length} public command surfaces match their product contracts\n`,
172
181
  );
173
182
  }
@@ -335,18 +335,6 @@ function createClassification(contracts) {
335
335
  return leaves;
336
336
  }
337
337
 
338
- function addAuthoringClassification(leaves, authoring) {
339
- const values = authoring?.commands?.check?.require_format?.admitted_values;
340
- if (!Array.isArray(values) || values.length === 0) throw new Error("authoring contract declares no admitted check formats");
341
- values.forEach((value, index) => leaves.set(`authoring:commands.check.require_format.admitted_values[${index}]`, {
342
- contract: "authoring",
343
- path: `commands.check.require_format.admitted_values[${index}]`,
344
- value,
345
- status: "unmapped",
346
- details: [],
347
- }));
348
- }
349
-
350
338
  function classify(leaves, contract, leafPath, matches, detail) {
351
339
  const leaf = leaves.get(`${contract}:${leafPath}`);
352
340
  if (!leaf) throw new Error(`behavioural check names no classified leaf: ${contract}:${leafPath}`);
@@ -431,7 +419,7 @@ async function exerciseParserSurface(cliPath, fixture, contracts, leaves) {
431
419
  return validReports;
432
420
  }
433
421
 
434
- async function exerciseRequiredCombinations(cliPath, fixture, contracts, authoring, leaves) {
422
+ async function exerciseRequiredCombinations(cliPath, fixture, contracts, leaves) {
435
423
  const codeWithoutTerm = await runCli(cliPath, fixture.root, [
436
424
  ...requiredArgs("terms", fixture).filter((entry) => entry !== "--json"),
437
425
  "--code", "--json",
@@ -442,20 +430,6 @@ async function exerciseRequiredCombinations(cliPath, fixture, contracts, authori
442
430
  classify(leaves, "terms", leafPath, refusedCode, `required --code/--term combination exited ${codeWithoutTerm.code}`);
443
431
  }
444
432
 
445
- const admitted = authoring.commands.check.require_format.admitted_values;
446
- const invalidFormat = await runCli(cliPath, fixture.root, [
447
- "authority", "check", fixture.spec, "--require-format", "nimicoding.authority/invalid", "--json",
448
- ]);
449
- const formatRefusal = invalidFormat.code === 2
450
- && invalidFormat.stdout === ""
451
- && admitted.every((format) => invalidFormat.stderr.includes(format));
452
- admitted.forEach((_, index) => classify(
453
- leaves,
454
- "authoring",
455
- `commands.check.require_format.admitted_values[${index}]`,
456
- formatRefusal,
457
- `invalid --require-format exited ${invalidFormat.code} and named admitted formats`,
458
- ));
459
433
  }
460
434
 
461
435
  async function independentFixtureOracle(fixture) {
@@ -479,7 +453,7 @@ async function independentFixtureOracle(fixture) {
479
453
  units: 3,
480
454
  files: 6,
481
455
  refs: 2,
482
- terms: 2,
456
+ terms: 5,
483
457
  sets: 2,
484
458
  consumersInputBytes: nonCorpusBytes,
485
459
  termsInputBytes: authorityBytes + nonCorpusBytes,
@@ -682,7 +656,7 @@ async function exerciseCoreClosure(cliPath, fixture, contracts, leaves, validRep
682
656
  consumers: validReports.consumers.report?.summary?.filesScanned === 6
683
657
  && validReports.consumers.report?.summary?.references === 2,
684
658
  terms: validReports.terms.report?.summary?.units === 3
685
- && validReports.terms.report?.summary?.distinctTerms === 2,
659
+ && validReports.terms.report?.summary?.distinctTerms === 5,
686
660
  "closed-sets": validReports["closed-sets"].report?.summary?.sets?.total === 2,
687
661
  };
688
662
  for (const command of Object.keys(contracts)) {
@@ -841,13 +815,11 @@ async function runConformanceLane({ contractDirectory, cliPath, lane }) {
841
815
  for (const [command, file] of Object.entries(PRODUCT_CONTRACTS)) {
842
816
  contracts[command] = YAML.parse(await readFile(path.join(contractDirectory, file), "utf8"));
843
817
  }
844
- const authoring = YAML.parse(await readFile(path.join(contractDirectory, "authority-authoring.yaml"), "utf8"));
845
818
  const leaves = createClassification(contracts);
846
- addAuthoringClassification(leaves, authoring);
847
819
  const fixture = await createRepositoryFixture();
848
820
  try {
849
821
  const validReports = await exerciseParserSurface(cliPath, fixture, contracts, leaves);
850
- await exerciseRequiredCombinations(cliPath, fixture, contracts, authoring, leaves);
822
+ await exerciseRequiredCombinations(cliPath, fixture, contracts, leaves);
851
823
  await exerciseCompleteAndBudgets(cliPath, fixture, contracts, leaves, validReports);
852
824
  await exerciseGrammar(cliPath, fixture, leaves, validReports);
853
825
  await addAnchorFinding(fixture);
@@ -1,9 +1,11 @@
1
1
  import { spawnSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
2
3
  import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
 
5
6
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
6
7
  const cliPath = path.join(repoRoot, "bin", "nimicoding.mjs");
8
+ const packageVersion = JSON.parse(readFileSync(path.join(repoRoot, "package.json"), "utf8")).version;
7
9
 
8
10
  for (const args of [["--help"], ["--version"]]) {
9
11
  const result = spawnSync(process.execPath, [cliPath, ...args], {
@@ -21,4 +23,8 @@ for (const args of [["--help"], ["--version"]]) {
21
23
  process.stderr.write(`${args.join(" ")} smoke exited with ${result.status ?? "unknown"}\n`);
22
24
  process.exit(result.status ?? 1);
23
25
  }
26
+ if (args[0] === "--version" && result.stdout !== `${packageVersion}\n`) {
27
+ process.stderr.write(`--version returned ${JSON.stringify(result.stdout)}; expected ${JSON.stringify(`${packageVersion}\n`)}\n`);
28
+ process.exit(1);
29
+ }
24
30
  }
package/cli/help.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { VERSION } from "./constants.mjs";
2
2
  import { AUTHORITY_ANCHOR_GRAMMAR_HELP } from "./lib/authority/anchors.mjs";
3
3
  import { AUTHORITY_COMMAND_USAGE } from "./commands/authority.mjs";
4
+ import { CODE_COMMAND_USAGE } from "./commands/code.mjs";
4
5
  import { localize, styleCommand, styleHeading, styleMuted } from "./lib/ui.mjs";
5
6
 
6
7
  export function helpText() {
@@ -8,11 +9,11 @@ export function helpText() {
8
9
  // The authority list comes from the command module itself so help and the
9
10
  // parser cannot describe different surfaces.
10
11
  ...AUTHORITY_COMMAND_USAGE,
12
+ ...CODE_COMMAND_USAGE,
11
13
  "nimicoding start [--yes]",
12
14
  "nimicoding sync [--apply|--check|--dry-run] [--json]",
13
15
  "nimicoding clear [--yes]",
14
16
  "nimicoding doctor [--verbose|--json]",
15
- "nimicoding validate-ai-governance --profile <profile-id> --scope <all|agents-freshness|context-budget|structure-budget|high-risk-doc-metadata> # optional L3 repository governance",
16
17
  ];
17
18
  return [
18
19
  styleHeading(`nimicoding ${VERSION}`),
@@ -25,8 +26,12 @@ export function helpText() {
25
26
  AUTHORITY_ANCHOR_GRAMMAR_HELP,
26
27
  "",
27
28
  styleMuted(localize(
28
- "Nimi Coding provides canonical authority methodology, formatting, compiler primitives, and deterministic gates. The optional L3 repository-governance command is not authority admission. AI task planning and execution belong to the host.",
29
- "Nimi Coding 提供 canonical authority 方法论、格式化、编译器原语与确定性 gate。可选 L3 repository-governance 命令不属于 authority admission;AI 任务规划与执行由宿主负责。",
29
+ "Nimi Coding provides canonical authority products, bounded read-only TypeScript context, and explicit code-authority marker lookup. AI task planning, editing, verification, and execution belong to the host.",
30
+ "Nimi Coding 提供 canonical authority 产品、有界只读的 TypeScript context,以及显式 code-authority marker 查询。AI 任务规划、编辑、验证与执行由宿主负责。",
31
+ )),
32
+ styleMuted(localize(
33
+ "Code queries read tracked current-filesystem source; unsaved editor buffers, conformance claims, and unannotated implementation semantics are outside v1.",
34
+ "Code 查询读取 Git tracked 的当前文件系统源码;未保存的编辑器 buffer、conformance 声明和未标注实现语义不属于 v1。",
30
35
  )),
31
36
  "",
32
37
  ].join("\n");
package/cli/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { runAuthority } from "./commands/authority.mjs";
2
2
  import { runClear } from "./commands/clear.mjs";
3
+ import { classifyCodeInvocation, runCodeInvocation } from "./commands/code.mjs";
3
4
  import { runDoctor } from "./commands/doctor.mjs";
4
5
  import { runStart } from "./commands/start.mjs";
5
6
  import { runSync } from "./commands/sync.mjs";
6
- import { runValidateAiGovernance } from "./commands/validate-ai-governance.mjs";
7
7
  import { helpText } from "./help.mjs";
8
8
  import { VERSION } from "./constants.mjs";
9
9
  import { configureCliUi, localize, parseGlobalUiOptions } from "./lib/ui.mjs";
@@ -14,10 +14,11 @@ const COMMANDS = {
14
14
  sync: runSync,
15
15
  clear: runClear,
16
16
  doctor: runDoctor,
17
- "validate-ai-governance": runValidateAiGovernance,
18
17
  };
19
18
 
20
19
  export async function runCli(args) {
20
+ const codeCommandIndex = classifyCodeInvocation(args);
21
+ if (codeCommandIndex !== null) return runCodeInvocation(args, codeCommandIndex);
21
22
  const parsedUi = parseGlobalUiOptions(args);
22
23
  if (!parsedUi.ok) {
23
24
  process.stderr.write(parsedUi.error);