@nimiplatform/nimi-coding 0.2.1 → 0.2.3
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 +24 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/cli/commands/audit-sweep.mjs +25 -2
- package/cli/constants.mjs +1 -1
- package/cli/help.mjs +1 -0
- package/cli/lib/audit-sweep-runtime/audit-validity.mjs +18 -3
- package/cli/lib/audit-sweep-runtime/claude-auditor.mjs +647 -0
- package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +21 -6
- package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +3 -2
- package/cli/lib/audit-sweep-runtime/common.mjs +12 -0
- package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +106 -8
- package/cli/lib/audit-sweep-runtime/inventory.mjs +2 -4
- package/cli/lib/audit-sweep-runtime/validators.mjs +6 -1
- package/cli/lib/audit-sweep.mjs +1 -0
- package/cli/lib/handoff.mjs +12 -3
- package/cli/lib/internal/doctor-finalize.mjs +32 -11
- package/cli/lib/internal/doctor-format.mjs +3 -2
- package/config/bootstrap.yaml +1 -1
- package/package.json +2 -2
|
@@ -183,7 +183,7 @@ function isNonImplementationContextRef(ref) {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
function stripNonImplementationContextRefs(refs, evidenceInventorySet) {
|
|
186
|
-
return refs.filter((ref) =>
|
|
186
|
+
return refs.filter((ref) => !isNonImplementationContextRef(ref));
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function normalizeFindingEnvelope(finding, evidenceInventorySet, authorityRefSet = new Set()) {
|
|
@@ -412,6 +412,7 @@ function normalizeOutcome(rawOutcome, index, authorityRef, evidenceInventorySet)
|
|
|
412
412
|
...normalizeRefs(rawOutcome.inspected_implementation_refs),
|
|
413
413
|
...normalizeRefs(rawOutcome.implementation_evidence_refs),
|
|
414
414
|
]);
|
|
415
|
+
const contextOnlyRefs = inspectedImplementationRefs.filter((ref) => isNonImplementationContextRef(ref));
|
|
415
416
|
const implementationRefs = stripNonImplementationContextRefs(inspectedImplementationRefs, evidenceInventorySet);
|
|
416
417
|
const invalidImplementationRefs = refsOutsideSet(implementationRefs, evidenceInventorySet);
|
|
417
418
|
if (invalidImplementationRefs.length > 0) {
|
|
@@ -438,6 +439,9 @@ function normalizeOutcome(rawOutcome, index, authorityRef, evidenceInventorySet)
|
|
|
438
439
|
if (typeof rawOutcome.implementation_not_applicable_reason === "string" && rawOutcome.implementation_not_applicable_reason.trim()) {
|
|
439
440
|
normalized.implementation_not_applicable_reason = rawOutcome.implementation_not_applicable_reason.trim();
|
|
440
441
|
}
|
|
442
|
+
if (!normalized.implementation_not_applicable_reason && implementationRefs.length === 0 && contextOnlyRefs.length > 0) {
|
|
443
|
+
normalized.implementation_not_applicable_reason = `Only non-implementation context refs were cited: ${uniqueRefs(contextOnlyRefs).join(", ")}.`;
|
|
444
|
+
}
|
|
441
445
|
if (!normalized.reason && status === "not_applicable" && normalized.implementation_not_applicable_reason) {
|
|
442
446
|
normalized.reason = normalized.implementation_not_applicable_reason;
|
|
443
447
|
}
|
|
@@ -476,10 +480,17 @@ function normalizeRuleChecks(rawRuleChecks, evidenceInventorySet, authorityRefSe
|
|
|
476
480
|
if (typeof rawCheck.negative_reasoning !== "string" || !rawCheck.negative_reasoning.trim()) {
|
|
477
481
|
return { ok: false, error: `coverage.p0p1_rule_checks[${index}].negative_reasoning is required` };
|
|
478
482
|
}
|
|
479
|
-
const
|
|
483
|
+
const inputRefs = uniqueRefs(normalizeRefs(rawCheck.implementation_refs));
|
|
484
|
+
const rawRefs = stripNonImplementationContextRefs(inputRefs, evidenceInventorySet);
|
|
480
485
|
const refs = rawRefs.filter((ref) => evidenceInventorySet.has(ref));
|
|
481
486
|
const invalidRawRefs = rawRefs.filter((ref) => !evidenceInventorySet.has(ref) && !authorityRefSet.has(ref));
|
|
482
|
-
|
|
487
|
+
const status = rawCheck.status === "checked"
|
|
488
|
+
&& refs.length === 0
|
|
489
|
+
&& inputRefs.length > 0
|
|
490
|
+
&& inputRefs.every((ref) => isNonImplementationContextRef(ref))
|
|
491
|
+
? "not_applicable"
|
|
492
|
+
: rawCheck.status;
|
|
493
|
+
if (status === "checked" && refs.length === 0) {
|
|
483
494
|
return { ok: false, error: `coverage.p0p1_rule_checks[${index}].implementation_refs is required when status is checked` };
|
|
484
495
|
}
|
|
485
496
|
if (invalidRawRefs.length > 0) {
|
|
@@ -491,7 +502,7 @@ function normalizeRuleChecks(rawRuleChecks, evidenceInventorySet, authorityRefSe
|
|
|
491
502
|
implementationRefs.push(...refs);
|
|
492
503
|
ruleChecks.push({
|
|
493
504
|
id,
|
|
494
|
-
status
|
|
505
|
+
status,
|
|
495
506
|
implementation_refs: refs,
|
|
496
507
|
negative_reasoning: rawCheck.negative_reasoning.trim(),
|
|
497
508
|
});
|
|
@@ -524,6 +535,7 @@ function normalizeCodexSemanticOutput(rawOutput, chunk, options) {
|
|
|
524
535
|
}
|
|
525
536
|
|
|
526
537
|
const evidenceInventory = chunk.planning_basis === "spec_authority" ? (chunk.evidence_inventory ?? []) : (chunk.files ?? []);
|
|
538
|
+
const p0p1ImplementationInventory = evidenceInventory.filter((ref) => !isNonImplementationContextRef(ref));
|
|
527
539
|
const evidenceInventorySet = new Set(evidenceInventory);
|
|
528
540
|
const authorityRefSet = new Set(authorityRefs);
|
|
529
541
|
const outcomes = [];
|
|
@@ -561,7 +573,7 @@ function normalizeCodexSemanticOutput(rawOutput, chunk, options) {
|
|
|
561
573
|
chunk_id: chunk.chunk_id,
|
|
562
574
|
auditor: {
|
|
563
575
|
id: typeof rawOutput.auditor?.id === "string" && rawOutput.auditor.id.trim() ? rawOutput.auditor.id : options.auditorId,
|
|
564
|
-
mode: "codex_semantic_audit",
|
|
576
|
+
mode: options.auditorMode ?? "codex_semantic_audit",
|
|
565
577
|
methodology_ref: "package://@nimiplatform/nimi-coding/methodology/audit-sweep-p0p1-recall.yaml",
|
|
566
578
|
provenance: {
|
|
567
579
|
kind: "semantic_audit",
|
|
@@ -591,12 +603,14 @@ function normalizeCodexSemanticOutput(rawOutput, chunk, options) {
|
|
|
591
603
|
if (typeof rawOutput.coverage.p0p1_implementation_not_applicable_reason === "string" && rawOutput.coverage.p0p1_implementation_not_applicable_reason.trim()) {
|
|
592
604
|
evidence.coverage.p0p1_implementation_not_applicable_reason = rawOutput.coverage.p0p1_implementation_not_applicable_reason.trim();
|
|
593
605
|
}
|
|
594
|
-
if (!evidence.coverage.p0p1_implementation_not_applicable_reason &&
|
|
606
|
+
if (!evidence.coverage.p0p1_implementation_not_applicable_reason && p0p1ImplementationInventory.length === 0) {
|
|
595
607
|
const outcomeReasons = outcomes
|
|
596
608
|
.map((outcome) => outcome.implementation_not_applicable_reason)
|
|
597
609
|
.filter((reason) => typeof reason === "string" && reason.trim().length > 0);
|
|
598
610
|
if (outcomeReasons.length > 0) {
|
|
599
611
|
evidence.coverage.p0p1_implementation_not_applicable_reason = uniqueRefs(outcomeReasons).join(" ");
|
|
612
|
+
} else {
|
|
613
|
+
evidence.coverage.p0p1_implementation_not_applicable_reason = "The chunk has no in-scope implementation refs after excluding context/governance/authority documents.";
|
|
600
614
|
}
|
|
601
615
|
}
|
|
602
616
|
return { ok: true, evidence };
|
|
@@ -620,6 +634,7 @@ export async function extractCodexAuditorEvidenceFile(projectRoot, options) {
|
|
|
620
634
|
sessionRef: options.sessionRef,
|
|
621
635
|
transcriptRef: options.transcriptRef,
|
|
622
636
|
auditorId: options.auditorId,
|
|
637
|
+
auditorMode: options.auditorMode,
|
|
623
638
|
});
|
|
624
639
|
if (!normalized.ok) {
|
|
625
640
|
return normalized;
|
|
@@ -52,8 +52,9 @@ function codexPrompt({ packet, auditorPacketRef, rawRef, sessionRef }) {
|
|
|
52
52
|
"You only author semantic audit content: authority_outcomes reasoning/status, inspected_implementation_refs, P0/P1 rule checks, p0p1_negative_reasoning when applicable, and findings.",
|
|
53
53
|
"For each authority outcome, set authority_ref to the packet authority_ref and put inspected implementation refs in inspected_implementation_refs or implementation_evidence_refs.",
|
|
54
54
|
"Every implementation ref you cite must be an exact file ref from packet.selected_implementation_refs / packet.evidence_inventory.",
|
|
55
|
-
"Never put AGENTS.md, README.md, spec files, authority refs, methodology docs, or governance docs in inspected_implementation_refs, implementation_evidence_refs, coverage.p0p1_evidence_refs, findings[].implementation_refs, or coverage.p0p1_rule_checks[].implementation_refs
|
|
56
|
-
"If
|
|
55
|
+
"Never put AGENTS.md, README.md, spec files, authority refs, methodology docs, or governance docs in inspected_implementation_refs, implementation_evidence_refs, coverage.p0p1_evidence_refs, findings[].implementation_refs, or coverage.p0p1_rule_checks[].implementation_refs; even if packet.selected_implementation_refs includes them, treat them as context only.",
|
|
56
|
+
"If only context/governance/authority documents are available after that exclusion, use status=\"not_applicable\" for P0/P1 rule checks and explain the lack of implementation surface in negative_reasoning.",
|
|
57
|
+
"If a governance or authority document influenced reasoning, mention it only in negative_reasoning/description text, not in any implementation_refs array.",
|
|
57
58
|
"Use packet.audit_depth to size your inspection: deep means inspect the selected slice thoroughly, normal means focused semantic inspection, shallow means audit generated/table/index invariants from the selected slice without expanding the omitted inventory.",
|
|
58
59
|
"Return exactly one JSON object and nothing else. Do not wrap it in markdown.",
|
|
59
60
|
"The JSON object must have exactly these top-level fields: chunk_id, auditor, coverage, findings.",
|
|
@@ -20,6 +20,7 @@ export const AUDITABLE_EXTENSIONS = new Set([
|
|
|
20
20
|
".json",
|
|
21
21
|
".md",
|
|
22
22
|
".mjs",
|
|
23
|
+
".prisma",
|
|
23
24
|
".proto",
|
|
24
25
|
".py",
|
|
25
26
|
".rs",
|
|
@@ -30,13 +31,24 @@ export const AUDITABLE_EXTENSIONS = new Set([
|
|
|
30
31
|
]);
|
|
31
32
|
export const DEFAULT_EXCLUDE_PATTERNS = [
|
|
32
33
|
".git/",
|
|
34
|
+
".agents/",
|
|
35
|
+
".claude/",
|
|
36
|
+
".iterate/",
|
|
33
37
|
".next/",
|
|
34
38
|
".nimi/local/",
|
|
39
|
+
".openclaw/",
|
|
35
40
|
".turbo/",
|
|
41
|
+
"AGENTS.md",
|
|
36
42
|
"archive/",
|
|
37
43
|
"dist/",
|
|
44
|
+
"docs/_archive/",
|
|
38
45
|
"generated/",
|
|
39
46
|
"node_modules/",
|
|
47
|
+
"README.md",
|
|
48
|
+
"**/AGENTS.md",
|
|
49
|
+
"**/README.md",
|
|
50
|
+
"_archive/",
|
|
51
|
+
"**/_archive/**",
|
|
40
52
|
"pnpm-lock.yaml",
|
|
41
53
|
"package-lock.json",
|
|
42
54
|
"yarn.lock",
|
|
@@ -7,26 +7,112 @@ function evidenceRootsForSpecOwner(ownerDomain, targetRootRef) {
|
|
|
7
7
|
return [targetRootRef];
|
|
8
8
|
}
|
|
9
9
|
const owner = String(ownerDomain ?? "").trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
10
|
-
const repoWideEvidenceRoots = [".", ".github", "config", "scripts", "src", "lib", "packages", "apps", "tools", "services"];
|
|
11
10
|
if (!owner || owner === "spec-meta" || owner === "spec-root") {
|
|
12
|
-
return
|
|
11
|
+
return [];
|
|
13
12
|
}
|
|
14
13
|
if (owner === "project") {
|
|
15
|
-
return ["src", "lib", "packages", "apps", "tools", "services"
|
|
14
|
+
return ["src", "lib", "packages", "apps", "tools", "services"];
|
|
16
15
|
}
|
|
17
16
|
return [
|
|
18
17
|
owner,
|
|
18
|
+
`nimi-${owner}`,
|
|
19
19
|
`src/${owner}`,
|
|
20
20
|
`lib/${owner}`,
|
|
21
21
|
`packages/${owner}`,
|
|
22
|
+
`packages/nimi-${owner}`,
|
|
22
23
|
`apps/${owner}`,
|
|
23
24
|
`tools/${owner}`,
|
|
24
25
|
`services/${owner}`,
|
|
25
|
-
"scripts",
|
|
26
|
-
"config",
|
|
27
26
|
];
|
|
28
27
|
}
|
|
29
28
|
|
|
29
|
+
const DECLARED_EVIDENCE_REF_PATTERN = /(?:^|[\s"'`([{:;,])((?:\.\/)?(?:[A-Za-z0-9_.@+-]+\/)+[A-Za-z0-9_@+.-]+\.(?:cjs|css|go|js|jsx|json|md|mjs|prisma|proto|py|rs|ts|tsx|yaml|yml))(?:[#:)\\\],;."'`]|\s|$)/gu;
|
|
30
|
+
|
|
31
|
+
function looksLikeSpecAuthorityRelativeRef(normalized) {
|
|
32
|
+
const extension = path.posix.extname(normalized);
|
|
33
|
+
if (![".md", ".yaml", ".yml"].includes(extension)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const parts = normalized.split("/");
|
|
37
|
+
const firstSegment = parts[0];
|
|
38
|
+
if (["tables", "generated", "kernel"].includes(firstSegment)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (parts[1] === "kernel") {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const specDomainLike = /^(backend|dashboard|realm|runtime|v[0-9]+|vision|workers)$/u.test(firstSegment);
|
|
45
|
+
return specDomainLike && parts.length <= 2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeDeclaredEvidenceRef(value) {
|
|
49
|
+
const normalized = String(value ?? "")
|
|
50
|
+
.trim()
|
|
51
|
+
.replace(/\\/g, "/")
|
|
52
|
+
.replace(/^\.\//, "")
|
|
53
|
+
.replace(/[),.;:]+$/u, "");
|
|
54
|
+
if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("http:") || normalized.startsWith("https:")) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (!normalized.includes("/")) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const firstSegment = normalized.split("/")[0];
|
|
61
|
+
if (looksLikeSpecAuthorityRelativeRef(normalized)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (
|
|
65
|
+
normalized.startsWith(".nimi/spec/")
|
|
66
|
+
|| normalized.startsWith(".nimi/contracts/")
|
|
67
|
+
|| normalized.startsWith(".nimi/methodology/")
|
|
68
|
+
|| normalized.startsWith(".nimi/local/")
|
|
69
|
+
|| normalized.startsWith(".agents/")
|
|
70
|
+
|| normalized.startsWith(".claude/")
|
|
71
|
+
|| normalized.startsWith(".openclaw/")
|
|
72
|
+
|| normalized.includes("/.nimi/spec/")
|
|
73
|
+
|| normalized.includes("/.nimi/contracts/")
|
|
74
|
+
|| normalized.includes("/.nimi/methodology/")
|
|
75
|
+
) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const basename = path.posix.basename(normalized).toLowerCase();
|
|
79
|
+
if (basename === "agents.md" || basename === "readme.md") {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return normalized;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function candidateEvidenceRefsForDeclaredRef(declaredRef, evidenceRoots) {
|
|
86
|
+
const normalized = String(declaredRef ?? "").replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
87
|
+
if (!normalized) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const candidates = [normalized];
|
|
91
|
+
for (const rootRef of evidenceRoots ?? []) {
|
|
92
|
+
const root = String(rootRef ?? "").replace(/\\/g, "/").replace(/\/$/, "");
|
|
93
|
+
if (!root || root === "." || root.startsWith(".nimi/spec") || path.posix.extname(root)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (normalized === root || normalized.startsWith(`${root}/`)) {
|
|
97
|
+
candidates.push(normalized);
|
|
98
|
+
} else {
|
|
99
|
+
candidates.push(`${root}/${normalized}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return [...new Set(candidates)].sort();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractDeclaredEvidenceRefs(text) {
|
|
106
|
+
const refs = [];
|
|
107
|
+
for (const match of String(text ?? "").matchAll(DECLARED_EVIDENCE_REF_PATTERN)) {
|
|
108
|
+
const normalized = normalizeDeclaredEvidenceRef(match[1]);
|
|
109
|
+
if (normalized) {
|
|
110
|
+
refs.push(normalized);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return [...new Set(refs)].sort();
|
|
114
|
+
}
|
|
115
|
+
|
|
30
116
|
function slugPart(value) {
|
|
31
117
|
return String(value)
|
|
32
118
|
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
@@ -132,22 +218,34 @@ export function buildSpecChunks(includedInventory, options) {
|
|
|
132
218
|
const rootAdmissions = (options.auditEvidenceRootAdmissions ?? [])
|
|
133
219
|
.filter((admission) => admission.owner_domain === surface.ownerDomain && admission.authority_refs.includes(entry.file_ref));
|
|
134
220
|
const admittedEvidenceRoots = rootAdmissions.flatMap((admission) => admission.evidence_roots);
|
|
221
|
+
const authorityText = authorityRefs
|
|
222
|
+
.map((authorityRef) => options.authorityTextByRef?.get(authorityRef) ?? "")
|
|
223
|
+
.join("\n");
|
|
224
|
+
const declaredEvidenceRefs = packageAdmission || appAdmission
|
|
225
|
+
? []
|
|
226
|
+
: extractDeclaredEvidenceRefs(authorityText);
|
|
135
227
|
const evidenceRoots = packageAdmission
|
|
136
228
|
? packageAdmission.evidence_roots
|
|
137
229
|
: appAdmission
|
|
138
230
|
? appAdmission.evidence_roots
|
|
139
231
|
: [...new Set([
|
|
140
232
|
...evidenceRootsForSpecOwner(surface.ownerDomain, options.targetRootRef),
|
|
233
|
+
...declaredEvidenceRefs,
|
|
141
234
|
...admittedEvidenceRoots,
|
|
142
235
|
])].sort();
|
|
143
236
|
const moduleMapRefs = surface.surface === "domain-guides" || surface.surface === "app-domain-guides"
|
|
144
237
|
? extractModuleMapRefs(options.authorityTextByRef?.get(entry.file_ref) ?? "")
|
|
145
238
|
: [];
|
|
146
|
-
const declaredEvidenceTargets =
|
|
147
|
-
.map((moduleRef) => ({
|
|
239
|
+
const declaredEvidenceTargets = [
|
|
240
|
+
...moduleMapRefs.map((moduleRef) => ({
|
|
148
241
|
source_path: moduleRef,
|
|
149
242
|
candidates: candidateEvidenceRefsForModuleMapPath(moduleRef, evidenceRoots),
|
|
150
|
-
}))
|
|
243
|
+
})),
|
|
244
|
+
...declaredEvidenceRefs.map((evidenceRef) => ({
|
|
245
|
+
source_path: evidenceRef,
|
|
246
|
+
candidates: candidateEvidenceRefsForDeclaredRef(evidenceRef, evidenceRoots),
|
|
247
|
+
})),
|
|
248
|
+
]
|
|
151
249
|
.filter((target) => target.candidates.length > 0);
|
|
152
250
|
chunkIndex += 1;
|
|
153
251
|
const chunkId = [
|
|
@@ -111,7 +111,7 @@ async function listFallbackFiles(projectRoot, targetRootRef, excludePatterns) {
|
|
|
111
111
|
|
|
112
112
|
function classifyFile(fileRef) {
|
|
113
113
|
const extension = path.posix.extname(fileRef);
|
|
114
|
-
if ([".md", ".yaml", ".yml", ".json"].includes(extension)) {
|
|
114
|
+
if ([".md", ".yaml", ".yml", ".json", ".prisma"].includes(extension)) {
|
|
115
115
|
return "contract-or-doc";
|
|
116
116
|
}
|
|
117
117
|
if ([".test.ts", ".test.js", ".spec.ts", ".spec.js"].some((suffix) => fileRef.endsWith(suffix))) {
|
|
@@ -509,9 +509,7 @@ export async function createAuditSweepPlan(projectRoot, options) {
|
|
|
509
509
|
const authorityTextByRef = new Map();
|
|
510
510
|
if (chunkBasis.basis === "spec") {
|
|
511
511
|
for (const entry of includedInventory) {
|
|
512
|
-
|
|
513
|
-
authorityTextByRef.set(entry.file_ref, await readFile(artifactPath(projectRoot, entry.file_ref), "utf8"));
|
|
514
|
-
}
|
|
512
|
+
authorityTextByRef.set(entry.file_ref, await readFile(artifactPath(projectRoot, entry.file_ref), "utf8"));
|
|
515
513
|
}
|
|
516
514
|
}
|
|
517
515
|
let chunks = chunkBasis.basis === "spec"
|
|
@@ -43,6 +43,10 @@ const RUN_EVENT_TYPES = new Set([
|
|
|
43
43
|
"chunk_codex_audit_failed",
|
|
44
44
|
"chunk_codex_auditor_output_rejected",
|
|
45
45
|
"chunk_codex_auditor_output_accepted",
|
|
46
|
+
"chunk_claude_audit_prepared",
|
|
47
|
+
"chunk_claude_audit_failed",
|
|
48
|
+
"chunk_claude_auditor_output_rejected",
|
|
49
|
+
"chunk_claude_auditor_output_accepted",
|
|
46
50
|
"ledger_snapshot_created",
|
|
47
51
|
"remediation_map_created",
|
|
48
52
|
"remediation_map_admitted",
|
|
@@ -610,7 +614,8 @@ function validateRunLedgerReplay(events, plan, chunks, findings, latestLedger, c
|
|
|
610
614
|
check(checks, "run_replay_plan_created", eventsByType.get("plan_created")?.some((event) => event.plan_ref === planRefFromPlan(plan)) === true, "run ledger records plan_created for this plan");
|
|
611
615
|
for (const chunk of chunks) {
|
|
612
616
|
const dispatched = eventsByType.get("chunk_dispatched")?.some((event) => event.chunk_id === chunk.chunk_id) === true
|
|
613
|
-
|| eventsByType.get("chunk_codex_audit_prepared")?.some((event) => event.chunk_id === chunk.chunk_id) === true
|
|
617
|
+
|| eventsByType.get("chunk_codex_audit_prepared")?.some((event) => event.chunk_id === chunk.chunk_id) === true
|
|
618
|
+
|| eventsByType.get("chunk_claude_audit_prepared")?.some((event) => event.chunk_id === chunk.chunk_id) === true;
|
|
614
619
|
const ingested = eventsByType.get("chunk_ingested")?.some((event) => event.chunk_id === chunk.chunk_id && event.evidence_ref === chunk.evidence_ref) === true;
|
|
615
620
|
const frozen = eventsByType.get("chunk_frozen")?.some((event) => event.chunk_id === chunk.chunk_id) === true;
|
|
616
621
|
const failed = eventsByType.get("chunk_failed")?.some((event) => event.chunk_id === chunk.chunk_id) === true;
|
package/cli/lib/audit-sweep.mjs
CHANGED
|
@@ -6,6 +6,7 @@ export {
|
|
|
6
6
|
} from "./audit-sweep-runtime/chunks.mjs";
|
|
7
7
|
export { ingestAuditSweepChunk } from "./audit-sweep-runtime/ingest.mjs";
|
|
8
8
|
export { runCodexAuditSweepChunk } from "./audit-sweep-runtime/codex-auditor.mjs";
|
|
9
|
+
export { runClaudeAuditSweepChunk } from "./audit-sweep-runtime/claude-auditor.mjs";
|
|
9
10
|
export { buildAuditSweepLedger } from "./audit-sweep-runtime/ledger.mjs";
|
|
10
11
|
export {
|
|
11
12
|
admitAuditSweepRemediationMap,
|
package/cli/lib/handoff.mjs
CHANGED
|
@@ -72,13 +72,16 @@ function commandRuleAllowsCurrentState(rule, doctorResult) {
|
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
function evaluateSkillReadiness(skillId, doctorResult) {
|
|
75
|
-
|
|
75
|
+
const usesV2SurfaceModel = doctorResult.specGenerationInputs?.mode === "class_filtered";
|
|
76
|
+
const canBypassV2GlobalHandoffGate = usesV2SurfaceModel
|
|
77
|
+
&& ["spec_reconstruction", "doc_spec_audit"].includes(skillId);
|
|
78
|
+
|
|
79
|
+
if (!doctorResult.ok || (!doctorResult.handoffReadiness.ok && !canBypassV2GlobalHandoffGate)) {
|
|
76
80
|
return {
|
|
77
81
|
ok: false,
|
|
78
82
|
reason: "Bootstrap or handoff validation is failing; repair doctor errors before exporting handoff payloads",
|
|
79
83
|
};
|
|
80
84
|
}
|
|
81
|
-
const usesV2SurfaceModel = doctorResult.specGenerationInputs?.mode === "class_filtered";
|
|
82
85
|
if (usesV2SurfaceModel && (doctorResult.commandGating?.entries ?? []).length === 0) {
|
|
83
86
|
if (skillId === "spec_reconstruction") {
|
|
84
87
|
return {
|
|
@@ -86,6 +89,12 @@ function evaluateSkillReadiness(skillId, doctorResult) {
|
|
|
86
89
|
reason: "Projects may delegate spec reconstruction to an external AI host when canonical tree work is needed",
|
|
87
90
|
};
|
|
88
91
|
}
|
|
92
|
+
if (skillId === "doc_spec_audit" && doctorResult.canonicalTree?.requiredFilesValid === true) {
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
reason: "Skill prerequisites are satisfied by the current project-local truth",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
89
98
|
if (doctorResult.canonicalTree?.requiredFilesValid === true && doctorResult.specGenerationAudit?.ok === true) {
|
|
90
99
|
return {
|
|
91
100
|
ok: true,
|
|
@@ -338,7 +347,7 @@ export async function buildHandoffPayload(projectRoot, skillId) {
|
|
|
338
347
|
},
|
|
339
348
|
runtimeOwner: doctorResult.delegatedContracts.runtimeOwner,
|
|
340
349
|
triggerMode: doctorResult.delegatedContracts.triggerMode,
|
|
341
|
-
handoffReady:
|
|
350
|
+
handoffReady: readiness.ok,
|
|
342
351
|
skill: {
|
|
343
352
|
id: skillId,
|
|
344
353
|
required: expectedSkill.required === "true",
|
|
@@ -37,6 +37,24 @@ import {
|
|
|
37
37
|
|
|
38
38
|
const PACKAGE_REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
|
|
39
39
|
|
|
40
|
+
function deriveV2LifecycleState(bootstrapSurface) {
|
|
41
|
+
const treeReady = bootstrapSurface.canonicalTree.requiredFilesValid === true;
|
|
42
|
+
const benchmarkMode = bootstrapSurface.specGenerationInputs?.benchmarkMode
|
|
43
|
+
?? bootstrapSurface.blueprintReference?.mode
|
|
44
|
+
?? "none";
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
mode: "class_filtered",
|
|
48
|
+
treeState: treeReady ? "canonical_tree_ready" : "canonical_tree_in_progress",
|
|
49
|
+
authorityMode: "surface_class_validated",
|
|
50
|
+
blueprintMode: benchmarkMode,
|
|
51
|
+
reconstructionRequired: !treeReady,
|
|
52
|
+
readyForAiReconstruction: !treeReady,
|
|
53
|
+
cutoverReadiness: {},
|
|
54
|
+
activeAuthorityRoot: bootstrapSurface.specGenerationInputs?.canonicalTargetRoot ?? ".nimi/spec",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
40
58
|
export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegatedSurface) {
|
|
41
59
|
const checks = [...bootstrapSurface.checks, ...delegatedSurface.checks];
|
|
42
60
|
const usesV2SurfaceModel = bootstrapSurface.specGenerationInputs?.mode === "class_filtered";
|
|
@@ -264,7 +282,19 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
264
282
|
}
|
|
265
283
|
|
|
266
284
|
const hasErrors = checks.some((check) => check.severity === "error");
|
|
267
|
-
const
|
|
285
|
+
const lifecycleState = usesV2SurfaceModel
|
|
286
|
+
? deriveV2LifecycleState(bootstrapSurface)
|
|
287
|
+
: {
|
|
288
|
+
mode: bootstrapSurface.bootstrapStateContract.mode,
|
|
289
|
+
treeState: bootstrapSurface.bootstrapStateContract.treeState,
|
|
290
|
+
authorityMode: bootstrapSurface.bootstrapStateContract.authorityMode,
|
|
291
|
+
blueprintMode: bootstrapSurface.bootstrapStateContract.blueprintMode,
|
|
292
|
+
reconstructionRequired: bootstrapSurface.bootstrapStateContract.reconstructionRequired,
|
|
293
|
+
readyForAiReconstruction: bootstrapSurface.bootstrapStateContract.readyForAiReconstruction,
|
|
294
|
+
cutoverReadiness: bootstrapSurface.bootstrapStateContract.cutoverReadiness,
|
|
295
|
+
activeAuthorityRoot: bootstrapSurface.bootstrapStateContract.activeAuthorityRoot,
|
|
296
|
+
};
|
|
297
|
+
const reconstructionRequired = lifecycleState.reconstructionRequired === true;
|
|
268
298
|
|
|
269
299
|
const handoffReadiness = {
|
|
270
300
|
ok: delegatedSurface.handoffContextOk
|
|
@@ -355,16 +385,7 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
355
385
|
id: bootstrapSurface.bootstrapCompatibility.contractId,
|
|
356
386
|
version: bootstrapSurface.bootstrapCompatibility.contractVersion,
|
|
357
387
|
},
|
|
358
|
-
lifecycleState
|
|
359
|
-
mode: bootstrapSurface.bootstrapStateContract.mode,
|
|
360
|
-
treeState: bootstrapSurface.bootstrapStateContract.treeState,
|
|
361
|
-
authorityMode: bootstrapSurface.bootstrapStateContract.authorityMode,
|
|
362
|
-
blueprintMode: bootstrapSurface.bootstrapStateContract.blueprintMode,
|
|
363
|
-
reconstructionRequired: bootstrapSurface.bootstrapStateContract.reconstructionRequired,
|
|
364
|
-
readyForAiReconstruction: bootstrapSurface.bootstrapStateContract.readyForAiReconstruction,
|
|
365
|
-
cutoverReadiness: bootstrapSurface.bootstrapStateContract.cutoverReadiness,
|
|
366
|
-
activeAuthorityRoot: bootstrapSurface.bootstrapStateContract.activeAuthorityRoot,
|
|
367
|
-
},
|
|
388
|
+
lifecycleState,
|
|
368
389
|
specTreeModel: bootstrapSurface.specTreeModel,
|
|
369
390
|
specGenerationInputs: bootstrapSurface.specGenerationInputs,
|
|
370
391
|
canonicalTree: bootstrapSurface.canonicalTree,
|
|
@@ -64,7 +64,7 @@ const DOCTOR_NEXT_STEP_TRANSLATIONS = new Map([
|
|
|
64
64
|
["Repair the failing bootstrap checks, then rerun `nimicoding doctor`.", "修复失败的 bootstrap 检查项,然后重新运行 `nimicoding doctor`。"],
|
|
65
65
|
["Use an external AI host to reconstruct the declared canonical tree under `.nimi/spec`.", "使用外部 AI host 重建声明的 `.nimi/spec` canonical tree。"],
|
|
66
66
|
["Run `nimicoding blueprint-audit --write-local` after canonical tree generation when a benchmark blueprint is declared.", "当声明了 benchmark blueprint 且 canonical tree 生成完成后,运行 `nimicoding blueprint-audit --write-local`。"],
|
|
67
|
-
["Run `nimicoding validate-spec-audit` after generating
|
|
67
|
+
["Run `nimicoding validate-spec-audit` after generating the local spec generation audit for the canonical tree.", "为 canonical tree 生成本地 spec generation audit 后,运行 `nimicoding validate-spec-audit`。"],
|
|
68
68
|
["Run `nimicoding handoff --skill doc_spec_audit` and close out the result locally when the audit is complete.", "运行 `nimicoding handoff --skill doc_spec_audit`,并在审计完成后于本地 closeout 结果。"],
|
|
69
69
|
["Keep runtime ownership delegated; do not assume local skill installation or self-hosting.", "保持 runtime ownership 为 delegated;不要假设本地 skill 安装或 self-hosting。"],
|
|
70
70
|
["If you want a constrained external execution host, select one in `.nimi/config/host-adapter.yaml`.", "如果你希望使用受约束的外部执行 host,请在 `.nimi/config/host-adapter.yaml` 中选择一个。"],
|
|
@@ -86,6 +86,7 @@ function summarizeDoctorState(result) {
|
|
|
86
86
|
const blockingChecks = result.checks.filter((check) => check.severity === "error");
|
|
87
87
|
const warningChecks = result.checks.filter((check) => check.severity === "warn");
|
|
88
88
|
const importantInfoChecks = result.checks.filter((check) => check.severity === "info").slice(0, 2);
|
|
89
|
+
const usesV2SurfaceModel = result.specGenerationInputs?.mode === "class_filtered";
|
|
89
90
|
|
|
90
91
|
const bootstrapState = !result.bootstrapPresent
|
|
91
92
|
? localize("missing", "缺失")
|
|
@@ -93,7 +94,7 @@ function summarizeDoctorState(result) {
|
|
|
93
94
|
? localize("ready", "就绪")
|
|
94
95
|
: localize("needs attention", "需要关注");
|
|
95
96
|
|
|
96
|
-
const canonicalTreeState = !result.specTreeModel?.ok
|
|
97
|
+
const canonicalTreeState = !usesV2SurfaceModel && !result.specTreeModel?.ok
|
|
97
98
|
? localize("invalid", "无效")
|
|
98
99
|
: !result.canonicalTree.requiredFilesValid
|
|
99
100
|
? localize("incomplete", "未完成")
|
package/config/bootstrap.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimiplatform/nimi-coding",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "AI-native coding governance toolkit for bootstrapping .nimi/** into arbitrary projects.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"provenance": true
|
|
23
23
|
},
|
|
24
24
|
"bin": {
|
|
25
|
-
"nimicoding": "
|
|
25
|
+
"nimicoding": "bin/nimicoding.mjs"
|
|
26
26
|
},
|
|
27
27
|
"packageManager": "pnpm@10.32.1",
|
|
28
28
|
"engines": {
|