@nimiplatform/nimi-coding 0.3.1 → 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 +23 -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 -144
- 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
package/cli/commands/clear.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import process from "node:process";
|
|
|
4
4
|
import {
|
|
5
5
|
previewBootstrapRemoval,
|
|
6
6
|
previewEntrypointRemoval,
|
|
7
|
+
preflightManagedProjectPaths,
|
|
7
8
|
removeManagedBootstrapFiles,
|
|
8
9
|
removeManagedEntrypoints,
|
|
9
10
|
} from "../lib/shared.mjs";
|
|
@@ -70,8 +71,19 @@ export async function runClear(args) {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const projectRoot = process.cwd();
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
let entrypointPreview;
|
|
75
|
+
let bootstrapPreview;
|
|
76
|
+
try {
|
|
77
|
+
await preflightManagedProjectPaths(projectRoot);
|
|
78
|
+
entrypointPreview = await previewEntrypointRemoval(projectRoot);
|
|
79
|
+
bootstrapPreview = await previewBootstrapRemoval(projectRoot);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
process.stderr.write(localize(
|
|
82
|
+
`nimicoding clear refused: ${error.message}.\n`,
|
|
83
|
+
`nimicoding clear 已拒绝:${error.message}。\n`,
|
|
84
|
+
));
|
|
85
|
+
return 2;
|
|
86
|
+
}
|
|
75
87
|
|
|
76
88
|
if (canPromptInteractively() && !parsed.options.yes) {
|
|
77
89
|
process.stdout.write(`${styleHeading(localize(`nimicoding clear: ${projectRoot}`, `nimicoding clear:${projectRoot}`))}
|
|
@@ -88,7 +100,6 @@ ${formatList(
|
|
|
88
100
|
${styleLabel(localize("What I will keep:", "我会保留的内容:"))}
|
|
89
101
|
- .nimi/spec/**
|
|
90
102
|
- .nimi/local/**
|
|
91
|
-
- .nimi/cache/**
|
|
92
103
|
${bootstrapPreview.preservedModifiedFiles.length > 0
|
|
93
104
|
? `\n${styleMuted(localize("I also found package-owned bootstrap files that were changed locally, so I will leave them in place:", "我还发现了一些包拥有的 bootstrap 文件已被本地修改,因此我会保留它们:"))}\n${bootstrapPreview.preservedModifiedFiles.map((filePath) => ` - ${filePath}`).join("\n")}`
|
|
94
105
|
: ""}
|
|
@@ -106,7 +117,6 @@ ${localize(" - nothing needed clearing", " - 没有需要清理的内容")}
|
|
|
106
117
|
${styleLabel(localize("What I kept:", "我保留的内容:"))}
|
|
107
118
|
- .nimi/spec/**
|
|
108
119
|
- .nimi/local/**
|
|
109
|
-
- .nimi/cache/**
|
|
110
120
|
|
|
111
121
|
${styleMuted(localize("No nimicoding-managed bootstrap files or managed AI blocks were found.", "没有检测到 nimicoding 托管的 bootstrap 文件或托管 AI 区块。"))}
|
|
112
122
|
`);
|
|
@@ -127,8 +137,19 @@ ${styleMuted(localize("No files were changed.", "没有修改任何文件。"))}
|
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
139
|
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
let removedEntrypoints;
|
|
141
|
+
let removedBootstrap;
|
|
142
|
+
try {
|
|
143
|
+
await preflightManagedProjectPaths(projectRoot);
|
|
144
|
+
removedEntrypoints = await removeManagedEntrypoints(projectRoot);
|
|
145
|
+
removedBootstrap = await removeManagedBootstrapFiles(projectRoot);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
process.stderr.write(localize(
|
|
148
|
+
`nimicoding clear refused: ${error.message}.\n`,
|
|
149
|
+
`nimicoding clear 已拒绝:${error.message}。\n`,
|
|
150
|
+
));
|
|
151
|
+
return 2;
|
|
152
|
+
}
|
|
132
153
|
|
|
133
154
|
process.stdout.write(`${styleHeading(localize(`nimicoding clear: ${projectRoot}`, `nimicoding clear:${projectRoot}`))}
|
|
134
155
|
|
|
@@ -146,7 +167,6 @@ ${formatList(
|
|
|
146
167
|
${styleLabel(localize("What I kept:", "我保留的内容:"))}
|
|
147
168
|
- .nimi/spec/**
|
|
148
169
|
- .nimi/local/**
|
|
149
|
-
- .nimi/cache/**
|
|
150
170
|
${removedBootstrap.preservedModifiedFiles.length > 0
|
|
151
171
|
? `\n${removedBootstrap.preservedModifiedFiles.map((filePath) => ` - ${localize("kept because it was modified", "已保留,因为它已被修改")}: ${filePath}`).join("\n")}`
|
|
152
172
|
: ""}
|
package/cli/commands/doctor.mjs
CHANGED
|
@@ -40,7 +40,16 @@ export async function runDoctor(args) {
|
|
|
40
40
|
return 2;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
let result;
|
|
44
|
+
try {
|
|
45
|
+
result = await inspectDoctorState(process.cwd());
|
|
46
|
+
} catch (error) {
|
|
47
|
+
process.stderr.write(localize(
|
|
48
|
+
`nimicoding doctor refused: ${error.message}.`,
|
|
49
|
+
`nimicoding doctor 已拒绝:${error.message}。`,
|
|
50
|
+
) + "\n");
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
44
53
|
if (parsed.options.json) {
|
|
45
54
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
46
55
|
} else {
|
package/cli/commands/start.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
inspectBootstrapCompatibility,
|
|
5
4
|
integrateEntrypoints,
|
|
6
5
|
pathExists,
|
|
6
|
+
preflightManagedProjectPaths,
|
|
7
7
|
previewBootstrapWrites,
|
|
8
8
|
previewEntrypointIntegration,
|
|
9
9
|
writeMissingBootstrapFiles,
|
|
@@ -34,43 +34,39 @@ export async function runStart(args) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const projectRoot = process.cwd();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
));
|
|
44
|
-
return 2;
|
|
45
|
-
}
|
|
37
|
+
let bootstrap;
|
|
38
|
+
let entrypoints;
|
|
39
|
+
try {
|
|
40
|
+
await preflightManagedProjectPaths(projectRoot);
|
|
41
|
+
const nimiRoot = path.join(projectRoot, ".nimi");
|
|
42
|
+
const nimiInfo = await pathExists(nimiRoot);
|
|
43
|
+
if (nimiInfo && !nimiInfo.isDirectory()) throw new Error(`${nimiRoot} exists and is not a directory`);
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
const bootstrapPreview = await previewBootstrapWrites(projectRoot);
|
|
46
|
+
const entrypointPreview = await previewEntrypointIntegration(projectRoot);
|
|
47
|
+
bootstrap = bootstrapPreview.hasWork
|
|
48
|
+
? await writeMissingBootstrapFiles(projectRoot)
|
|
49
|
+
: { createdFiles: [], updatedFiles: [], createdDirs: [], gitignoreUpdated: false, gitattributesUpdated: false };
|
|
50
|
+
entrypoints = entrypointPreview.length > 0
|
|
51
|
+
? await integrateEntrypoints(projectRoot)
|
|
52
|
+
: [];
|
|
53
|
+
} catch (error) {
|
|
49
54
|
process.stderr.write(localize(
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
`nimicoding start refused: ${error.message}.\n`,
|
|
56
|
+
`nimicoding start 已拒绝:${error.message}。\n`,
|
|
52
57
|
));
|
|
53
|
-
return
|
|
58
|
+
return 2;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
const bootstrapPreview = await previewBootstrapWrites(projectRoot);
|
|
57
|
-
const entrypointPreview = await previewEntrypointIntegration(projectRoot);
|
|
58
|
-
const bootstrap = bootstrapPreview.hasWork
|
|
59
|
-
? await writeMissingBootstrapFiles(projectRoot)
|
|
60
|
-
: { createdFiles: [], createdDirs: [], gitignoreUpdated: false };
|
|
61
|
-
const entrypoints = entrypointPreview.length > 0
|
|
62
|
-
? await integrateEntrypoints(projectRoot)
|
|
63
|
-
: [];
|
|
64
|
-
|
|
65
61
|
const output = {
|
|
66
62
|
ok: true,
|
|
67
63
|
projectRoot,
|
|
68
64
|
bootstrap,
|
|
69
65
|
entrypoints,
|
|
70
66
|
next: [
|
|
71
|
-
"
|
|
72
|
-
"Run nimicoding
|
|
73
|
-
"Run nimicoding
|
|
67
|
+
"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.",
|
|
74
70
|
],
|
|
75
71
|
};
|
|
76
72
|
|
package/cli/commands/sync.mjs
CHANGED
|
@@ -2,112 +2,30 @@ import { runSeedSync, SYNC_MODE, SYNC_RESULT_STATUS } from "../lib/sync.mjs";
|
|
|
2
2
|
import { localize } from "../lib/ui.mjs";
|
|
3
3
|
|
|
4
4
|
function parseSyncOptions(args) {
|
|
5
|
-
const options = {
|
|
6
|
-
mode: SYNC_MODE.DRY_RUN,
|
|
7
|
-
json: false,
|
|
8
|
-
};
|
|
5
|
+
const options = { mode: SYNC_MODE.DRY_RUN, json: false };
|
|
9
6
|
let modeSet = false;
|
|
10
|
-
|
|
11
7
|
for (const arg of args) {
|
|
12
|
-
if (
|
|
13
|
-
if (modeSet) {
|
|
14
|
-
|
|
15
|
-
ok: false,
|
|
16
|
-
error: localize(
|
|
17
|
-
"nimicoding sync refused: --apply, --check, and --dry-run are mutually exclusive.\n",
|
|
18
|
-
"nimicoding sync 拒绝执行:--apply、--check、--dry-run 互斥。\n",
|
|
19
|
-
),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
options.mode = SYNC_MODE.APPLY;
|
|
23
|
-
modeSet = true;
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
if (arg === "--check") {
|
|
27
|
-
if (modeSet) {
|
|
28
|
-
return {
|
|
29
|
-
ok: false,
|
|
30
|
-
error: localize(
|
|
31
|
-
"nimicoding sync refused: --apply, --check, and --dry-run are mutually exclusive.\n",
|
|
32
|
-
"nimicoding sync 拒绝执行:--apply、--check、--dry-run 互斥。\n",
|
|
33
|
-
),
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
options.mode = SYNC_MODE.CHECK;
|
|
8
|
+
if (["--apply", "--check", "--dry-run"].includes(arg)) {
|
|
9
|
+
if (modeSet) return { ok: false, error: localize("nimicoding sync refused: --apply, --check, and --dry-run are mutually exclusive.\n", "nimicoding sync 拒绝执行:--apply、--check、--dry-run 互斥。\n") };
|
|
10
|
+
options.mode = arg === "--apply" ? SYNC_MODE.APPLY : arg === "--check" ? SYNC_MODE.CHECK : SYNC_MODE.DRY_RUN;
|
|
37
11
|
modeSet = true;
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
if (arg === "--dry-run") {
|
|
41
|
-
if (modeSet) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
error: localize(
|
|
45
|
-
"nimicoding sync refused: --apply, --check, and --dry-run are mutually exclusive.\n",
|
|
46
|
-
"nimicoding sync 拒绝执行:--apply、--check、--dry-run 互斥。\n",
|
|
47
|
-
),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
options.mode = SYNC_MODE.DRY_RUN;
|
|
51
|
-
modeSet = true;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (arg === "--json") {
|
|
55
|
-
options.json = true;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
ok: false,
|
|
60
|
-
error: localize(
|
|
61
|
-
`nimicoding sync refused: unknown option ${arg}.\n`,
|
|
62
|
-
`nimicoding sync 拒绝执行:未知选项 ${arg}。\n`,
|
|
63
|
-
),
|
|
64
|
-
};
|
|
12
|
+
} else if (arg === "--json") options.json = true;
|
|
13
|
+
else return { ok: false, error: localize(`nimicoding sync refused: unknown option ${arg}.\n`, `nimicoding sync 拒绝执行:未知选项 ${arg}。\n`) };
|
|
65
14
|
}
|
|
66
|
-
|
|
67
15
|
return { ok: true, options };
|
|
68
16
|
}
|
|
69
17
|
|
|
70
18
|
function formatHumanReport(result) {
|
|
71
|
-
const lines = [];
|
|
72
|
-
lines.push(
|
|
73
|
-
`nimicoding sync (${result.mode})`,
|
|
74
|
-
`nimicoding sync (${result.mode})`,
|
|
75
|
-
));
|
|
76
|
-
lines.push("");
|
|
77
|
-
lines.push(localize("Summary:", "概览:"));
|
|
78
|
-
lines.push(` total: ${result.summary.total}`);
|
|
79
|
-
lines.push(` in_sync: ${result.summary.in_sync}`);
|
|
80
|
-
if (result.mode === SYNC_MODE.APPLY) {
|
|
81
|
-
lines.push(` created: ${result.summary.created}`);
|
|
82
|
-
lines.push(` updated: ${result.summary.updated}`);
|
|
83
|
-
} else {
|
|
84
|
-
lines.push(` would_create: ${result.summary.would_create}`);
|
|
85
|
-
lines.push(` would_update: ${result.summary.would_update}`);
|
|
86
|
-
}
|
|
87
|
-
lines.push(` drifted_preserved (host-owned seed): ${result.summary.drifted_preserved}`);
|
|
88
|
-
if (result.mode === SYNC_MODE.CHECK) {
|
|
89
|
-
lines.push(` missing_package_canonical: ${result.summary.missing_package_canonical}`);
|
|
90
|
-
lines.push(` missing_host_state_seed: ${result.summary.missing_host_state_seed}`);
|
|
91
|
-
lines.push(` drifted_package_canonical: ${result.summary.drifted_package_canonical}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
19
|
+
const lines = [`nimicoding sync (${result.mode})`, "", localize("Summary:", "概览:")];
|
|
20
|
+
for (const [key, value] of Object.entries(result.summary)) lines.push(` ${key}: ${value}`);
|
|
94
21
|
const noteworthy = result.results.filter((entry) => entry.status !== SYNC_RESULT_STATUS.IN_SYNC);
|
|
95
22
|
if (noteworthy.length > 0) {
|
|
96
|
-
lines.push("");
|
|
97
|
-
lines.push(
|
|
98
|
-
for (const entry of noteworthy) {
|
|
99
|
-
lines.push(` [${entry.status}] (${entry.ownership}) ${entry.outputRelativePath}`);
|
|
100
|
-
}
|
|
23
|
+
lines.push("", localize("Per-surface status:", "逐表面状态:"));
|
|
24
|
+
for (const entry of noteworthy) lines.push(` [${entry.status}] (${entry.ownership}) ${entry.outputRelativePath}`);
|
|
101
25
|
}
|
|
102
|
-
|
|
103
26
|
if (result.mode === SYNC_MODE.CHECK && !result.ok) {
|
|
104
|
-
lines.push("");
|
|
105
|
-
lines.push(localize(
|
|
106
|
-
"FAIL: package_canonical drift or missing seed detected; run `nimicoding sync --apply` to refresh.",
|
|
107
|
-
"FAIL:检测到 package_canonical drift 或缺失 seed;执行 `nimicoding sync --apply` 以刷新。",
|
|
108
|
-
));
|
|
27
|
+
lines.push("", localize("FAIL: an exact package projection or managed block is missing or drifted, or an exact deprecated package projection path remains.", "FAIL:精确 package 投影或 managed block 缺失/漂移,或精确 deprecated package 投影路径仍然存在。"));
|
|
109
28
|
}
|
|
110
|
-
|
|
111
29
|
return `${lines.join("\n")}\n`;
|
|
112
30
|
}
|
|
113
31
|
|
|
@@ -117,15 +35,17 @@ export async function runSync(args) {
|
|
|
117
35
|
process.stderr.write(parsed.error);
|
|
118
36
|
return 2;
|
|
119
37
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
process.
|
|
125
|
-
|
|
126
|
-
|
|
38
|
+
let result;
|
|
39
|
+
try {
|
|
40
|
+
result = await runSeedSync(process.cwd(), parsed.options.mode);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
process.stderr.write(localize(
|
|
43
|
+
`nimicoding sync refused: ${error.message}.\n`,
|
|
44
|
+
`nimicoding sync 已拒绝:${error.message}。\n`,
|
|
45
|
+
));
|
|
46
|
+
return 2;
|
|
127
47
|
}
|
|
128
|
-
|
|
48
|
+
process.stdout.write(parsed.options.json ? `${JSON.stringify(result, null, 2)}\n` : formatHumanReport(result));
|
|
129
49
|
return result.ok ? 0 : 1;
|
|
130
50
|
}
|
|
131
51
|
|
package/cli/constants.mjs
CHANGED
|
@@ -1,37 +1,13 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.4.0";
|
|
2
2
|
export const PACKAGE_NAME = "@nimiplatform/nimi-coding";
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
export const LOCAL_GITIGNORE_ENTRIES = [".nimi/local/"];
|
|
4
|
+
export const AUTHORITY_GITATTRIBUTES_ENTRIES = [
|
|
5
|
+
"*.authority.yaml text eol=lf",
|
|
6
|
+
"*.authority.md text eol=lf",
|
|
7
|
+
];
|
|
8
|
+
export const REQUIRED_LOCAL_DIRS = [".nimi/local"];
|
|
8
9
|
|
|
9
10
|
export const AGENTS_BEGIN = "<!-- nimicoding:managed:agents:start -->";
|
|
10
11
|
export const AGENTS_END = "<!-- nimicoding:managed:agents:end -->";
|
|
11
12
|
export const CLAUDE_BEGIN = "<!-- nimicoding:managed:claude:start -->";
|
|
12
13
|
export const CLAUDE_END = "<!-- nimicoding:managed:claude:end -->";
|
|
13
|
-
|
|
14
|
-
export const SPEC_GENERATION_AUDIT_REF = ".nimi/local/state/spec-generation/spec-generation-audit.yaml";
|
|
15
|
-
export const SPEC_GENERATION_AUDIT_CONTRACT_REF = ".nimi/contracts/spec-generation-audit.schema.yaml";
|
|
16
|
-
export const SPEC_GENERATION_INPUTS_REF = ".nimi/config/spec-generation-inputs.yaml";
|
|
17
|
-
export const SPEC_GENERATION_INPUTS_CONTRACT_REF = ".nimi/contracts/spec-generation-inputs.schema.yaml";
|
|
18
|
-
export const BLUEPRINT_REFERENCE_REF = ".nimi/local/state/spec-generation/blueprint-reference.yaml";
|
|
19
|
-
|
|
20
|
-
export const SPEC_GENERATION_AUDIT_REQUIRED_TOP_LEVEL_FIELDS = [
|
|
21
|
-
"generation_mode",
|
|
22
|
-
"canonical_target_root",
|
|
23
|
-
"declared_profile",
|
|
24
|
-
"input_roots",
|
|
25
|
-
"files",
|
|
26
|
-
];
|
|
27
|
-
export const SPEC_GENERATION_AUDIT_SOURCE_BASIS_ENUM = [
|
|
28
|
-
"grounded",
|
|
29
|
-
"mixed_grounded_and_inferred",
|
|
30
|
-
"inferred",
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
export const SPEC_GENERATION_AUDIT_COVERAGE_STATUS_ENUM = [
|
|
34
|
-
"complete",
|
|
35
|
-
"partial",
|
|
36
|
-
"placeholder_not_allowed",
|
|
37
|
-
];
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Compares the command surface the parser enforces against the product
|
|
3
|
+
// contracts that admit it, in both directions.
|
|
4
|
+
//
|
|
5
|
+
// The comparison is over parsed structure -- command tokens, flag names,
|
|
6
|
+
// report format strings -- not over prose. A gate that greps a contract for a
|
|
7
|
+
// sentence proves only that the sentence is still there.
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
import YAML from "yaml";
|
|
14
|
+
|
|
15
|
+
import { PNPM_BUILTIN_SUBCOMMANDS } from "../lib/authority/anchors.mjs";
|
|
16
|
+
import { AUTHORING_SURFACE, AUTHORITY_SURFACE, REPOSITORY_BOUND_COMMANDS } from "../lib/authority/surface.mjs";
|
|
17
|
+
|
|
18
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
19
|
+
|
|
20
|
+
const CONTRACT_FILES = {
|
|
21
|
+
anchors: "spec/authority-anchors.yaml",
|
|
22
|
+
consumers: "spec/authority-consumers.yaml",
|
|
23
|
+
terms: "spec/authority-terms.yaml",
|
|
24
|
+
"closed-sets": "spec/authority-closed-sets.yaml",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function sorted(values) {
|
|
28
|
+
return [...values].sort();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sameSet(left, right) {
|
|
32
|
+
return JSON.stringify(sorted(left)) === JSON.stringify(sorted(right));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Flags as the contract's own command line spells them, so the contract cannot
|
|
36
|
+
// claim a surface its command string does not show.
|
|
37
|
+
function flagsInCommand(command) {
|
|
38
|
+
return sorted(new Set([...command.matchAll(/--([a-z][a-z0-9-]*)/gu)].map((match) => match[1])));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function checkAuthoritySurface(root = packageRoot) {
|
|
42
|
+
const failures = [];
|
|
43
|
+
const contractIds = new Map();
|
|
44
|
+
|
|
45
|
+
for (const command of REPOSITORY_BOUND_COMMANDS) {
|
|
46
|
+
const relative = CONTRACT_FILES[command];
|
|
47
|
+
if (relative === undefined) {
|
|
48
|
+
failures.push(`command ${command} is in the enforced surface but has no product contract file`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
let contract;
|
|
52
|
+
try {
|
|
53
|
+
contract = YAML.parse(readFileSync(path.join(root, relative), "utf8"));
|
|
54
|
+
} catch (error) {
|
|
55
|
+
failures.push(`${relative} is not readable structured YAML: ${error.message}`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const declared = contract?.surface;
|
|
59
|
+
if (declared === undefined) {
|
|
60
|
+
failures.push(`${relative} declares no surface block`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
contractIds.set(contract?.spec?.id, command);
|
|
64
|
+
|
|
65
|
+
const enforced = AUTHORITY_SURFACE[command];
|
|
66
|
+
if (command === "anchors" && !sameSet(
|
|
67
|
+
contract?.lexical_grammar?.class_B?.builtin_subcommands_excluded ?? [],
|
|
68
|
+
PNPM_BUILTIN_SUBCOMMANDS,
|
|
69
|
+
)) {
|
|
70
|
+
failures.push(
|
|
71
|
+
`anchors builtin_subcommands_excluded: contract ${JSON.stringify(sorted(contract?.lexical_grammar?.class_B?.builtin_subcommands_excluded ?? []))} `
|
|
72
|
+
+ `!= enforced ${JSON.stringify(sorted(PNPM_BUILTIN_SUBCOMMANDS))}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
for (const [field, contractKey] of [
|
|
76
|
+
["booleanFlags", "boolean_flags"],
|
|
77
|
+
["valueFlags", "value_flags"],
|
|
78
|
+
["integerFlags", "integer_flags"],
|
|
79
|
+
["repeatableFlags", "repeatable_flags"],
|
|
80
|
+
]) {
|
|
81
|
+
if (!Object.hasOwn(declared, contractKey)) {
|
|
82
|
+
failures.push(`${command} ${contractKey}: contract field is required even when its array is empty`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(declared[contractKey])) {
|
|
86
|
+
failures.push(`${command} ${contractKey}: contract field must be an array`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!sameSet(enforced[field], declared[contractKey])) {
|
|
90
|
+
failures.push(
|
|
91
|
+
`${command} ${contractKey}: contract ${JSON.stringify(sorted(declared[contractKey]))} `
|
|
92
|
+
+ `!= enforced ${JSON.stringify(sorted(enforced[field]))}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (declared.report_format !== enforced.reportFormat) {
|
|
97
|
+
failures.push(`${command} report_format: contract ${declared.report_format} != enforced ${enforced.reportFormat}`);
|
|
98
|
+
}
|
|
99
|
+
if (contract?.spec?.id !== enforced.contract) {
|
|
100
|
+
failures.push(`${command} contract id: ${contract?.spec?.id} != enforced ${enforced.contract}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const commandFlags = flagsInCommand(String(contract.command ?? ""));
|
|
104
|
+
const surfaceFlags = sorted(new Set([
|
|
105
|
+
...enforced.booleanFlags, ...enforced.valueFlags, ...enforced.integerFlags, ...enforced.repeatableFlags,
|
|
106
|
+
]));
|
|
107
|
+
if (!sameSet(commandFlags, surfaceFlags)) {
|
|
108
|
+
failures.push(
|
|
109
|
+
`${command} command line flags ${JSON.stringify(commandFlags)} != declared surface ${JSON.stringify(surfaceFlags)}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// fmt and the source schema: the authoring contract has to describe the
|
|
115
|
+
// format the validator admits and the container fields it accepts, so a
|
|
116
|
+
// contract edit that widens either one fails here instead of passing
|
|
117
|
+
// silently because the constant lived in the validator.
|
|
118
|
+
let authoring;
|
|
119
|
+
let source;
|
|
120
|
+
try {
|
|
121
|
+
authoring = YAML.parse(readFileSync(path.join(root, "spec/authority-authoring.yaml"), "utf8"));
|
|
122
|
+
source = YAML.parse(readFileSync(path.join(root, "contracts/authority-source.schema.yaml"), "utf8"));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
failures.push(`the authoring contract or source schema is not readable structured YAML: ${error.message}`);
|
|
125
|
+
}
|
|
126
|
+
if (authoring !== undefined && source !== undefined) {
|
|
127
|
+
const fmtFlags = sorted(new Set([...AUTHORING_SURFACE.fmt.booleanFlags]));
|
|
128
|
+
const declaredFmt = flagsInCommand(String(authoring.commands?.fmt?.syntax ?? ""));
|
|
129
|
+
if (!sameSet(declaredFmt, fmtFlags)) {
|
|
130
|
+
failures.push(`fmt flags: contract ${JSON.stringify(declaredFmt)} != enforced ${JSON.stringify(fmtFlags)}`);
|
|
131
|
+
}
|
|
132
|
+
if (authoring.spec?.id !== AUTHORING_SURFACE.fmt.contract) {
|
|
133
|
+
failures.push(`authoring contract id: ${authoring.spec?.id} != enforced ${AUTHORING_SURFACE.fmt.contract}`);
|
|
134
|
+
}
|
|
135
|
+
if (authoring.fields?.format !== source.format) {
|
|
136
|
+
failures.push(`authoring format ${authoring.fields?.format} != source schema format ${source.format}`);
|
|
137
|
+
}
|
|
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
|
+
if (!sameSet(authoring.profiles?.yaml?.container_fields ?? [], source.yaml_document?.allowed_fields ?? [])) {
|
|
143
|
+
failures.push(
|
|
144
|
+
`container fields: authoring ${JSON.stringify(sorted(authoring.profiles?.yaml?.container_fields ?? []))} `
|
|
145
|
+
+ `!= source schema ${JSON.stringify(sorted(source.yaml_document?.allowed_fields ?? []))}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (!sameSet(source.fields?.yaml_document ?? [], source.yaml_document?.allowed_fields ?? [])) {
|
|
149
|
+
failures.push("the source schema states two different admitted container field sets");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// The other direction: a contract that no enforced command claims.
|
|
154
|
+
for (const [command, relative] of Object.entries(CONTRACT_FILES)) {
|
|
155
|
+
if (!REPOSITORY_BOUND_COMMANDS.includes(command)) {
|
|
156
|
+
failures.push(`${relative} declares command ${command}, which the enforced surface does not admit`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const [id, command] of contractIds) {
|
|
160
|
+
if (id === undefined) failures.push(`the contract for ${command} declares no spec.id`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return failures;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("check-authority-surface.mjs")) {
|
|
167
|
+
const failures = checkAuthoritySurface();
|
|
168
|
+
for (const failure of failures) process.stderr.write(`authority-surface: ${failure}\n`);
|
|
169
|
+
if (failures.length > 0) process.exit(1);
|
|
170
|
+
process.stdout.write(
|
|
171
|
+
`authority-surface: ${REPOSITORY_BOUND_COMMANDS.length} repository-bound commands match their product contracts\n`,
|
|
172
|
+
);
|
|
173
|
+
}
|