@nimiplatform/nimi-coding 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/CONTRIBUTING.md +3 -3
- package/README.md +285 -41
- package/README.zh-CN.md +294 -31
- package/bin/nimicoding.mjs +1 -1
- package/cli/commands/authority.mjs +849 -0
- package/cli/commands/clear.mjs +27 -7
- package/cli/commands/doctor.mjs +10 -1
- package/cli/commands/start.mjs +23 -27
- package/cli/commands/sync.mjs +21 -101
- package/cli/constants.mjs +7 -31
- package/cli/dev/check-authority-surface.mjs +173 -0
- package/cli/dev/check-product-conformance.mjs +917 -0
- package/cli/help.mjs +10 -17
- package/cli/index.mjs +6 -47
- package/cli/lib/authority/anchors.mjs +491 -0
- package/cli/lib/authority/audit.mjs +490 -0
- package/cli/lib/authority/closed-sets.mjs +646 -0
- package/cli/lib/authority/compile.mjs +110 -0
- package/cli/lib/authority/consumers.mjs +470 -0
- package/cli/lib/authority/diagnostics.mjs +87 -0
- package/cli/lib/authority/diff.mjs +287 -0
- package/cli/lib/authority/discover.mjs +447 -0
- package/cli/lib/authority/evidence-snapshot.mjs +1 -0
- package/cli/lib/authority/evidence.mjs +1432 -0
- package/cli/lib/authority/format.mjs +197 -0
- package/cli/lib/authority/git-snapshot.mjs +1065 -0
- package/cli/lib/authority/graph.mjs +351 -0
- package/cli/lib/authority/impact.mjs +252 -0
- package/cli/lib/authority/lexicon.mjs +55 -0
- package/cli/lib/authority/query.mjs +162 -0
- package/cli/lib/authority/repository-corpus.mjs +37 -0
- package/cli/lib/authority/repository-inventory.mjs +34 -0
- package/cli/lib/authority/review.mjs +222 -0
- package/cli/lib/authority/sarif.mjs +181 -0
- package/cli/lib/authority/scope-bindings.mjs +225 -0
- package/cli/lib/authority/source-map.mjs +105 -0
- package/cli/lib/authority/source-markdown.mjs +167 -0
- package/cli/lib/authority/source-yaml.mjs +312 -0
- package/cli/lib/authority/surface.mjs +79 -0
- package/cli/lib/authority/terms.mjs +421 -0
- package/cli/lib/authority/tracked-text.mjs +70 -0
- package/cli/lib/authority/validate.mjs +308 -0
- package/cli/lib/bootstrap.mjs +55 -71
- package/cli/lib/doctor.mjs +40 -123
- package/cli/lib/entrypoints.mjs +103 -105
- package/cli/lib/fs-helpers.mjs +139 -15
- package/cli/lib/internal/governance/config.mjs +0 -36
- package/cli/lib/shared.mjs +8 -21
- package/cli/lib/sync.mjs +81 -92
- package/cli/lib/value-helpers.mjs +0 -26
- package/cli/lib/yaml-helpers.mjs +1 -124
- package/cli/seeds/bootstrap.mjs +31 -87
- package/cli/seeds/seed-policy.yaml +9 -63
- package/contracts/authority-evidence-bindings.schema.yaml +53 -0
- package/contracts/authority-evidence-probe-results.schema.yaml +38 -0
- package/contracts/authority-impact-dispositions.schema.yaml +13 -0
- package/contracts/authority-scope-bindings.schema.yaml +15 -0
- package/contracts/authority-source.schema.yaml +64 -0
- package/contracts/authority-verifier-bindings.schema.yaml +20 -0
- package/methodology/authority-authoring.yaml +144 -0
- package/methodology/core.yaml +31 -26
- package/package.json +10 -6
- package/spec/authority-anchors.yaml +99 -0
- package/spec/authority-audit.yaml +85 -0
- package/spec/authority-authoring.yaml +150 -0
- package/spec/authority-closed-sets.yaml +65 -0
- package/spec/authority-consumers.yaml +70 -0
- package/spec/authority-context.yaml +50 -0
- package/spec/authority-discovery.yaml +119 -0
- package/spec/authority-evidence.yaml +200 -0
- package/spec/authority-graph.yaml +59 -0
- package/spec/authority-impact.yaml +84 -0
- package/spec/authority-review.yaml +89 -0
- package/spec/authority-terms.yaml +59 -0
- package/spec/product-scope.yaml +36 -23
- package/cli/commands/blueprint-audit.mjs +0 -91
- package/cli/commands/classify-spec-tree.mjs +0 -5
- package/cli/commands/generate-spec-derived-docs.mjs +0 -151
- package/cli/commands/generate-spec-migration-plan.mjs +0 -30
- package/cli/commands/surface-validator-command.mjs +0 -49
- package/cli/commands/validate-domain-admission.mjs +0 -5
- package/cli/commands/validate-guidance-bodies.mjs +0 -5
- package/cli/commands/validate-placement.mjs +0 -5
- package/cli/commands/validate-projection-edges.mjs +0 -5
- package/cli/commands/validate-spec-audit.mjs +0 -27
- package/cli/commands/validate-spec-governance.mjs +0 -124
- package/cli/commands/validate-spec-tree.mjs +0 -27
- package/cli/commands/validate-table-family.mjs +0 -5
- package/cli/commands/validate-tracked-output-admission.mjs +0 -5
- package/cli/lib/blueprint-audit.mjs +0 -363
- package/cli/lib/contracts.mjs +0 -21
- package/cli/lib/internal/contracts-loaders.mjs +0 -47
- package/cli/lib/internal/contracts-parse.mjs +0 -192
- package/cli/lib/internal/governance/runner.mjs +0 -35
- package/cli/lib/internal/surface-taxonomy-validators.mjs +0 -1147
- package/cli/lib/internal/validators-shared.mjs +0 -12
- package/cli/lib/internal/validators-spec-helpers.mjs +0 -52
- package/cli/lib/internal/validators-spec.mjs +0 -380
- package/cli/lib/validators.mjs +0 -30
- package/config/bootstrap.yaml +0 -6
- package/config/spec-generation-inputs.yaml +0 -41
- package/contracts/domain-admission.schema.yaml +0 -56
- package/contracts/migration-inventory.schema.yaml +0 -15
- package/contracts/negative-fixtures.yaml +0 -26
- package/contracts/placement-contract.schema.yaml +0 -127
- package/contracts/projection-edge.schema.yaml +0 -46
- package/contracts/shared-enums.yaml +0 -63
- package/contracts/spec-generation-audit.schema.yaml +0 -46
- package/contracts/spec-generation-inputs.schema.yaml +0 -43
- package/contracts/spec-layout.schema.yaml +0 -32
- package/contracts/surface-taxonomy.schema.yaml +0 -139
- package/contracts/table-family.schema.yaml +0 -133
- package/contracts/tracked-output-admission.schema.yaml +0 -17
- package/methodology/four-closure-policy.yaml +0 -20
- package/methodology/role-separation-policy.yaml +0 -22
- package/methodology/spec-reconstruction.yaml +0 -69
- package/spec/_meta/spec-tree-model.yaml +0 -44
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { lstat, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { compareText, createLocator, makeDiagnostic, portablePath, REPAIRS, sortDiagnostics } from "./diagnostics.mjs";
|
|
5
|
+
import { parseMarkdownAuthority, stringifyCanonicalMarkdown } from "./source-markdown.mjs";
|
|
6
|
+
import { AUTHORITY_FORMAT_V2, loadAuthorityContract, parseYamlAuthority, stringifyCanonicalYaml } from "./source-yaml.mjs";
|
|
7
|
+
import { validateFormatStructure } from "./validate.mjs";
|
|
8
|
+
|
|
9
|
+
export class AuthorityInputError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "AuthorityInputError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function profileFor(filePath) {
|
|
17
|
+
if (filePath.endsWith(".authority.yaml")) return "yaml";
|
|
18
|
+
if (filePath.endsWith(".authority.md")) return "markdown";
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function collectDirectory(current, files) {
|
|
23
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
24
|
+
entries.sort((left, right) => compareText(left.name, right.name));
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const absolutePath = path.join(current, entry.name);
|
|
27
|
+
if (entry.isSymbolicLink()) throw new AuthorityInputError(`authority input refuses symbolic link: ${absolutePath}`);
|
|
28
|
+
if (entry.isDirectory()) await collectDirectory(absolutePath, files);
|
|
29
|
+
else if (entry.isFile()) {
|
|
30
|
+
if (!profileFor(absolutePath)) throw new AuthorityInputError(`authority input contains unsupported file: ${absolutePath}`);
|
|
31
|
+
files.push(absolutePath);
|
|
32
|
+
} else throw new AuthorityInputError(`authority input contains unsupported filesystem entry: ${absolutePath}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function collectAuthorityFiles(inputPath, options = {}) {
|
|
37
|
+
const absolutePath = path.resolve(process.cwd(), inputPath);
|
|
38
|
+
let info;
|
|
39
|
+
try {
|
|
40
|
+
info = await lstat(absolutePath);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new AuthorityInputError(`authority input is not readable: ${absolutePath}: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
if (info.isSymbolicLink()) throw new AuthorityInputError(`authority input refuses symbolic link: ${absolutePath}`);
|
|
45
|
+
if (options.singleFile && !info.isFile()) throw new AuthorityInputError("authority fmt requires one regular canonical source file");
|
|
46
|
+
const files = [];
|
|
47
|
+
if (info.isFile()) {
|
|
48
|
+
if (!profileFor(absolutePath)) throw new AuthorityInputError(`unsupported authority source suffix: ${absolutePath}`);
|
|
49
|
+
files.push(absolutePath);
|
|
50
|
+
} else if (info.isDirectory()) await collectDirectory(absolutePath, files);
|
|
51
|
+
else throw new AuthorityInputError(`unsupported authority input type: ${absolutePath}`);
|
|
52
|
+
if (files.length === 0) throw new AuthorityInputError(`authority input contains no canonical source files: ${absolutePath}`);
|
|
53
|
+
return files;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function parseAuthorityFile(absolutePath) {
|
|
57
|
+
const bytes = await readFile(absolutePath);
|
|
58
|
+
const profile = profileFor(absolutePath);
|
|
59
|
+
let text;
|
|
60
|
+
try {
|
|
61
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
62
|
+
} catch {
|
|
63
|
+
const file = portablePath(absolutePath);
|
|
64
|
+
const locator = createLocator("");
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
file,
|
|
68
|
+
locator,
|
|
69
|
+
diagnostics: [makeDiagnostic({
|
|
70
|
+
code: "AUTH_SYNTAX_UNSUPPORTED",
|
|
71
|
+
file,
|
|
72
|
+
range: locator.range(0),
|
|
73
|
+
reason: "authority source bytes are not valid UTF-8",
|
|
74
|
+
repair: "rewrite the file as valid UTF-8 without changing product semantics",
|
|
75
|
+
})],
|
|
76
|
+
locations: new Map(),
|
|
77
|
+
keyLocations: new Map(),
|
|
78
|
+
insertionPoints: new Map(),
|
|
79
|
+
data: null,
|
|
80
|
+
profile,
|
|
81
|
+
isDocument: true,
|
|
82
|
+
units: [],
|
|
83
|
+
sourceText: "",
|
|
84
|
+
inputBytes: bytes.length,
|
|
85
|
+
absolutePath,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const source = profile === "yaml"
|
|
89
|
+
? parseYamlAuthority(text, absolutePath)
|
|
90
|
+
: parseMarkdownAuthority(text, absolutePath);
|
|
91
|
+
const document = profile === "yaml"
|
|
92
|
+
? source
|
|
93
|
+
: { ...source, isDocument: true, units: source.data ? [source] : [] };
|
|
94
|
+
return { ...document, inputBytes: bytes.length, absolutePath };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function parseAuthorityPath(inputPath, options = {}) {
|
|
98
|
+
const contract = await loadAuthorityContract();
|
|
99
|
+
const files = await collectAuthorityFiles(inputPath, options);
|
|
100
|
+
const documents = [];
|
|
101
|
+
const sources = [];
|
|
102
|
+
for (const file of files) {
|
|
103
|
+
const document = await parseAuthorityFile(file);
|
|
104
|
+
documents.push(document);
|
|
105
|
+
sources.push(...document.units.map((source) => ({ ...source, absolutePath: document.absolutePath })));
|
|
106
|
+
}
|
|
107
|
+
return { contract, files, documents, sources };
|
|
108
|
+
}
|
|
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
|
+
}
|
|
139
|
+
return document.profile === "yaml"
|
|
140
|
+
? stringifyCanonicalYaml(document.data, { container: true })
|
|
141
|
+
: stringifyCanonicalMarkdown(document.data);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function formattingDiagnostics(documents, contract) {
|
|
145
|
+
const diagnostics = [];
|
|
146
|
+
for (const document of documents) {
|
|
147
|
+
const structure = validateFormatStructure(document, contract);
|
|
148
|
+
if (structure.length > 0) {
|
|
149
|
+
diagnostics.push(...structure);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const canonical = canonicalText(document);
|
|
153
|
+
if (canonical !== document.sourceText) {
|
|
154
|
+
diagnostics.push(makeDiagnostic({
|
|
155
|
+
code: "AUTH_FORMAT_DRIFT",
|
|
156
|
+
file: document.file,
|
|
157
|
+
range: document.locations.get("") ?? document.locator.range(0, document.sourceText.length),
|
|
158
|
+
reason: "authority source is not byte-canonical",
|
|
159
|
+
repair: REPAIRS.format,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return sortDiagnostics(diagnostics);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function atomicWrite(filePath, content) {
|
|
167
|
+
const temporary = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
168
|
+
try {
|
|
169
|
+
await writeFile(temporary, content, "utf8");
|
|
170
|
+
await rename(temporary, filePath);
|
|
171
|
+
} finally {
|
|
172
|
+
await rm(temporary, { force: true });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function formatAuthorityFile(inputPath, options = {}) {
|
|
177
|
+
const { contract, documents } = await parseAuthorityPath(inputPath, { singleFile: true });
|
|
178
|
+
const document = documents[0];
|
|
179
|
+
const diagnostics = validateFormatStructure(document, contract);
|
|
180
|
+
if (diagnostics.length > 0) return { ok: false, changed: false, diagnostics, fileCount: 1 };
|
|
181
|
+
const canonical = canonicalText(document, { upgrade: options.upgrade === true, contract });
|
|
182
|
+
const changed = canonical !== document.sourceText;
|
|
183
|
+
if (options.check && changed) return {
|
|
184
|
+
ok: false,
|
|
185
|
+
changed: true,
|
|
186
|
+
diagnostics: [makeDiagnostic({
|
|
187
|
+
code: "AUTH_FORMAT_DRIFT",
|
|
188
|
+
file: document.file,
|
|
189
|
+
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",
|
|
191
|
+
repair: REPAIRS.format,
|
|
192
|
+
})],
|
|
193
|
+
fileCount: 1,
|
|
194
|
+
};
|
|
195
|
+
if (!options.check && changed) await atomicWrite(document.absolutePath ?? path.resolve(process.cwd(), inputPath), canonical);
|
|
196
|
+
return { ok: true, changed, diagnostics: [], fileCount: 1 };
|
|
197
|
+
}
|