@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.
- package/CHANGELOG.md +16 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +92 -55
- package/README.zh-CN.md +93 -56
- package/cli/commands/authority.mjs +80 -125
- package/cli/commands/clear.mjs +3 -2
- package/cli/commands/code.mjs +225 -0
- package/cli/commands/start.mjs +3 -2
- package/cli/constants.mjs +2 -1
- package/cli/dev/check-authority-surface.mjs +17 -8
- package/cli/dev/check-product-conformance.mjs +4 -32
- package/cli/dev/run-cli-smoke.mjs +6 -0
- package/cli/help.mjs +8 -3
- package/cli/index.mjs +3 -2
- package/cli/lib/authority/audit.mjs +262 -185
- package/cli/lib/authority/change-candidates.mjs +807 -0
- package/cli/lib/authority/closed-sets.mjs +7 -8
- package/cli/lib/authority/compile.mjs +6 -24
- package/cli/lib/authority/compiled-snapshot.mjs +143 -0
- package/cli/lib/authority/consumers.mjs +69 -19
- package/cli/lib/authority/diagnostics.mjs +10 -2
- package/cli/lib/authority/diff.mjs +77 -54
- package/cli/lib/authority/format.mjs +4 -32
- package/cli/lib/authority/git-snapshot.mjs +34 -379
- package/cli/lib/authority/graph.mjs +10 -10
- package/cli/lib/authority/impact.mjs +17 -144
- package/cli/lib/authority/review.mjs +33 -43
- package/cli/lib/authority/sarif.mjs +1 -8
- package/cli/lib/authority/source-yaml.mjs +0 -4
- package/cli/lib/authority/surface.mjs +19 -1
- package/cli/lib/authority/terms.mjs +10 -9
- package/cli/lib/authority/validate.mjs +6 -14
- package/cli/lib/code/authority-annotation-response.mjs +43 -0
- package/cli/lib/code/authority-annotation-scan.mjs +162 -0
- package/cli/lib/code/authority-annotations.mjs +293 -0
- package/cli/lib/code/context.mjs +710 -0
- package/cli/lib/code/response.mjs +165 -0
- package/cli/lib/doctor.mjs +3 -2
- package/cli/lib/entrypoints.mjs +13 -5
- package/cli/seeds/seed-policy.yaml +2 -2
- package/contracts/authority-source.schema.yaml +0 -6
- package/contracts/authority-verifier-bindings.schema.yaml +21 -10
- package/methodology/authority-authoring.yaml +68 -70
- package/methodology/core.yaml +10 -6
- package/package.json +5 -3
- package/spec/authority-audit.yaml +56 -52
- package/spec/authority-authoring.yaml +3 -16
- package/spec/authority-change-candidates.yaml +146 -0
- package/spec/authority-consumers.yaml +7 -0
- package/spec/authority-discovery.yaml +2 -2
- package/spec/authority-graph.yaml +1 -1
- package/spec/authority-impact.yaml +16 -39
- package/spec/authority-review.yaml +29 -47
- package/spec/authority-terms.yaml +5 -1
- package/spec/code-authority-annotations.yaml +42 -0
- package/spec/code-context.yaml +38 -0
- package/spec/product-scope.yaml +15 -9
- package/cli/commands/validate-ai-governance.mjs +0 -335
- package/cli/lib/authority/evidence-snapshot.mjs +0 -1
- package/cli/lib/authority/evidence.mjs +0 -1432
- package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +0 -377
- package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +0 -376
- package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +0 -155
- package/cli/lib/internal/governance/ai/check-high-risk-doc-metadata-core.mjs +0 -173
- package/cli/lib/internal/governance/config.mjs +0 -114
- package/cli/lib/internal/governance/shared/read-yaml-with-fragments.mjs +0 -49
- package/contracts/authority-evidence-bindings.schema.yaml +0 -53
- package/contracts/authority-evidence-probe-results.schema.yaml +0 -38
- package/contracts/authority-impact-dispositions.schema.yaml +0 -13
- 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
|
+
}
|
package/cli/commands/start.mjs
CHANGED
|
@@ -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
|
-
|
|
69
|
-
|
|
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.
|
|
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 {
|
|
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
|
|
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 (!
|
|
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: ${
|
|
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,
|
|
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:
|
|
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 ===
|
|
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,
|
|
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
|
|
29
|
-
"Nimi Coding 提供 canonical authority
|
|
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);
|