@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.
@@ -183,7 +183,7 @@ function isNonImplementationContextRef(ref) {
183
183
  }
184
184
 
185
185
  function stripNonImplementationContextRefs(refs, evidenceInventorySet) {
186
- return refs.filter((ref) => evidenceInventorySet.has(ref) || !isNonImplementationContextRef(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 rawRefs = stripNonImplementationContextRefs(uniqueRefs(normalizeRefs(rawCheck.implementation_refs)), evidenceInventorySet);
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
- if (rawCheck.status === "checked" && refs.length === 0) {
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: rawCheck.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 && evidenceInventory.length === 0) {
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 unless that exact file appears in packet.selected_implementation_refs.",
56
- "If a governance or authority document influenced reasoning but is not in packet.selected_implementation_refs, mention it only in negative_reasoning/description text, not in any implementation_refs array.",
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 repoWideEvidenceRoots;
11
+ return [];
13
12
  }
14
13
  if (owner === "project") {
15
- return ["src", "lib", "packages", "apps", "tools", "services", "scripts", "config"];
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 = moduleMapRefs
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
- if ([".md", ".markdown"].includes(entry.extension)) {
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;
@@ -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,
@@ -72,13 +72,16 @@ function commandRuleAllowsCurrentState(rule, doctorResult) {
72
72
  };
73
73
  }
74
74
  function evaluateSkillReadiness(skillId, doctorResult) {
75
- if (!doctorResult.ok || !doctorResult.handoffReadiness.ok) {
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: doctorResult.handoffReadiness.ok,
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 reconstructionRequired = bootstrapSurface.bootstrapStateContract.reconstructionRequired === true;
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 `.nimi/spec/_meta/spec-generation-audit.yaml` for the canonical tree.", "在为 canonical tree 生成 `.nimi/spec/_meta/spec-generation-audit.yaml` 后,运行 `nimicoding validate-spec-audit`。"],
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", "未完成")
@@ -1,6 +1,6 @@
1
1
  version: 1
2
2
  initialized_by: "@nimiplatform/nimi-coding"
3
- cli_version: "0.2.1"
3
+ cli_version: "0.2.2"
4
4
  bootstrap_contract: "nimicoding.bootstrap"
5
5
  bootstrap_contract_version: 1
6
6
  profile: default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimiplatform/nimi-coding",
3
- "version": "0.2.1",
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": "./bin/nimicoding.mjs"
25
+ "nimicoding": "bin/nimicoding.mjs"
26
26
  },
27
27
  "packageManager": "pnpm@10.32.1",
28
28
  "engines": {