@nimiplatform/nimi-coding 0.1.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.
Files changed (186) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/adapters/README.md +25 -0
  4. package/adapters/claude/README.md +89 -0
  5. package/adapters/claude/profile.yaml +70 -0
  6. package/adapters/codex/README.md +53 -0
  7. package/adapters/codex/profile.yaml +78 -0
  8. package/adapters/oh-my-codex/README.md +185 -0
  9. package/adapters/oh-my-codex/profile.yaml +46 -0
  10. package/bin/nimicoding.mjs +6 -0
  11. package/cli/commands/admit-high-risk-decision.mjs +108 -0
  12. package/cli/commands/audit-sweep.mjs +341 -0
  13. package/cli/commands/blueprint-audit.mjs +91 -0
  14. package/cli/commands/clear.mjs +168 -0
  15. package/cli/commands/closeout.mjs +183 -0
  16. package/cli/commands/decide-high-risk-execution.mjs +124 -0
  17. package/cli/commands/doctor.mjs +53 -0
  18. package/cli/commands/generate-spec-derived-docs.mjs +131 -0
  19. package/cli/commands/handoff.mjs +123 -0
  20. package/cli/commands/ingest-high-risk-execution.mjs +95 -0
  21. package/cli/commands/review-high-risk-execution.mjs +95 -0
  22. package/cli/commands/start.mjs +717 -0
  23. package/cli/commands/topic-formatters.mjs +382 -0
  24. package/cli/commands/topic-goal.mjs +33 -0
  25. package/cli/commands/topic-options-shared.mjs +27 -0
  26. package/cli/commands/topic-options-workflow.mjs +767 -0
  27. package/cli/commands/topic-options.mjs +626 -0
  28. package/cli/commands/topic-runner.mjs +169 -0
  29. package/cli/commands/topic.mjs +795 -0
  30. package/cli/commands/validate-acceptance.mjs +5 -0
  31. package/cli/commands/validate-ai-governance.mjs +214 -0
  32. package/cli/commands/validate-execution-packet.mjs +5 -0
  33. package/cli/commands/validate-orchestration-state.mjs +5 -0
  34. package/cli/commands/validate-prompt.mjs +5 -0
  35. package/cli/commands/validate-spec-audit.mjs +27 -0
  36. package/cli/commands/validate-spec-governance.mjs +124 -0
  37. package/cli/commands/validate-spec-tree.mjs +27 -0
  38. package/cli/commands/validate-worker-output.mjs +5 -0
  39. package/cli/constants.mjs +489 -0
  40. package/cli/help.mjs +134 -0
  41. package/cli/index.mjs +103 -0
  42. package/cli/lib/adapter-profiles.mjs +403 -0
  43. package/cli/lib/audit-execution.mjs +52 -0
  44. package/cli/lib/audit-sweep-runtime/admissions.mjs +381 -0
  45. package/cli/lib/audit-sweep-runtime/audit-validity.mjs +333 -0
  46. package/cli/lib/audit-sweep-runtime/chunks.mjs +697 -0
  47. package/cli/lib/audit-sweep-runtime/closeout.mjs +144 -0
  48. package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +639 -0
  49. package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +515 -0
  50. package/cli/lib/audit-sweep-runtime/common.mjs +329 -0
  51. package/cli/lib/audit-sweep-runtime/coverage-quality.mjs +172 -0
  52. package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +152 -0
  53. package/cli/lib/audit-sweep-runtime/format.mjs +57 -0
  54. package/cli/lib/audit-sweep-runtime/ingest.mjs +486 -0
  55. package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +198 -0
  56. package/cli/lib/audit-sweep-runtime/inventory.mjs +728 -0
  57. package/cli/lib/audit-sweep-runtime/ledger.mjs +315 -0
  58. package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +101 -0
  59. package/cli/lib/audit-sweep-runtime/remediation.mjs +349 -0
  60. package/cli/lib/audit-sweep-runtime/rerun.mjs +129 -0
  61. package/cli/lib/audit-sweep-runtime/risk-budget.mjs +300 -0
  62. package/cli/lib/audit-sweep-runtime/status.mjs +62 -0
  63. package/cli/lib/audit-sweep-runtime/validators-ledger.mjs +215 -0
  64. package/cli/lib/audit-sweep-runtime/validators.mjs +758 -0
  65. package/cli/lib/audit-sweep.mjs +18 -0
  66. package/cli/lib/authority-convergence.mjs +309 -0
  67. package/cli/lib/blueprint-audit.mjs +370 -0
  68. package/cli/lib/bootstrap.mjs +228 -0
  69. package/cli/lib/closeout.mjs +623 -0
  70. package/cli/lib/codex-sdk-runner.mjs +76 -0
  71. package/cli/lib/contracts.mjs +180 -0
  72. package/cli/lib/doctor.mjs +18 -0
  73. package/cli/lib/entrypoints.mjs +274 -0
  74. package/cli/lib/external-execution.mjs +101 -0
  75. package/cli/lib/fs-helpers.mjs +33 -0
  76. package/cli/lib/handoff.mjs +785 -0
  77. package/cli/lib/high-risk-admission.mjs +442 -0
  78. package/cli/lib/high-risk-decision.mjs +324 -0
  79. package/cli/lib/high-risk-ingest.mjs +317 -0
  80. package/cli/lib/high-risk-review.mjs +263 -0
  81. package/cli/lib/internal/contracts-loaders.mjs +132 -0
  82. package/cli/lib/internal/contracts-parse-high-risk.mjs +131 -0
  83. package/cli/lib/internal/contracts-parse.mjs +457 -0
  84. package/cli/lib/internal/contracts-validators.mjs +398 -0
  85. package/cli/lib/internal/doctor-bootstrap-surface.mjs +359 -0
  86. package/cli/lib/internal/doctor-delegated-surface.mjs +256 -0
  87. package/cli/lib/internal/doctor-finalize.mjs +385 -0
  88. package/cli/lib/internal/doctor-format.mjs +286 -0
  89. package/cli/lib/internal/doctor-inspectors.mjs +294 -0
  90. package/cli/lib/internal/doctor-state.mjs +205 -0
  91. package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +315 -0
  92. package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +358 -0
  93. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +155 -0
  94. package/cli/lib/internal/governance/ai/check-high-risk-doc-metadata-core.mjs +173 -0
  95. package/cli/lib/internal/governance/config.mjs +150 -0
  96. package/cli/lib/internal/governance/runner.mjs +35 -0
  97. package/cli/lib/internal/governance/shared/read-yaml-with-fragments.mjs +49 -0
  98. package/cli/lib/internal/validators-artifacts.mjs +515 -0
  99. package/cli/lib/internal/validators-shared.mjs +28 -0
  100. package/cli/lib/internal/validators-spec-helpers.mjs +186 -0
  101. package/cli/lib/internal/validators-spec.mjs +410 -0
  102. package/cli/lib/shared.mjs +83 -0
  103. package/cli/lib/topic-draft-packets.mjs +48 -0
  104. package/cli/lib/topic-goal.mjs +361 -0
  105. package/cli/lib/topic-runner.mjs +772 -0
  106. package/cli/lib/topic.mjs +93 -0
  107. package/cli/lib/ui.mjs +178 -0
  108. package/cli/lib/validators.mjs +78 -0
  109. package/cli/lib/value-helpers.mjs +24 -0
  110. package/cli/lib/yaml-helpers.mjs +133 -0
  111. package/cli/nimicoding.mjs +1 -0
  112. package/cli/seeds/bootstrap.mjs +47 -0
  113. package/config/audit-execution-artifacts.yaml +20 -0
  114. package/config/bootstrap.yaml +6 -0
  115. package/config/external-execution-artifacts.yaml +16 -0
  116. package/config/host-adapter.yaml +30 -0
  117. package/config/host-profile.yaml +29 -0
  118. package/config/installer-evidence.yaml +31 -0
  119. package/config/skill-installer.yaml +23 -0
  120. package/config/skill-manifest.yaml +46 -0
  121. package/config/skills.yaml +30 -0
  122. package/config/spec-generation-inputs.yaml +25 -0
  123. package/contracts/acceptance.schema.yaml +16 -0
  124. package/contracts/admission-checklist.schema.yaml +15 -0
  125. package/contracts/audit-chunk.schema.yaml +110 -0
  126. package/contracts/audit-closeout.schema.yaml +51 -0
  127. package/contracts/audit-finding.schema.yaml +61 -0
  128. package/contracts/audit-ledger.schema.yaml +138 -0
  129. package/contracts/audit-plan.schema.yaml +123 -0
  130. package/contracts/audit-remediation-map.schema.yaml +51 -0
  131. package/contracts/audit-rerun.schema.yaml +31 -0
  132. package/contracts/audit-sweep-result.yaml +49 -0
  133. package/contracts/authority-convergence-audit.schema.yaml +19 -0
  134. package/contracts/closeout.schema.yaml +25 -0
  135. package/contracts/decision-review.schema.yaml +16 -0
  136. package/contracts/doc-spec-audit-result.yaml +19 -0
  137. package/contracts/execution-packet.schema.yaml +49 -0
  138. package/contracts/external-host-compatibility.yaml +22 -0
  139. package/contracts/forbidden-shortcuts.catalog.yaml +23 -0
  140. package/contracts/high-risk-admission.schema.yaml +23 -0
  141. package/contracts/high-risk-execution-result.yaml +20 -0
  142. package/contracts/orchestration-state.schema.yaml +41 -0
  143. package/contracts/overflow-continuation.schema.yaml +12 -0
  144. package/contracts/packet.schema.yaml +30 -0
  145. package/contracts/pending-note.schema.yaml +17 -0
  146. package/contracts/prompt.schema.yaml +12 -0
  147. package/contracts/remediation.schema.yaml +16 -0
  148. package/contracts/result.schema.yaml +24 -0
  149. package/contracts/spec-generation-audit.schema.yaml +31 -0
  150. package/contracts/spec-generation-inputs.schema.yaml +39 -0
  151. package/contracts/spec-reconstruction-result.yaml +37 -0
  152. package/contracts/topic-goal.schema.yaml +78 -0
  153. package/contracts/topic-run-ledger.schema.yaml +72 -0
  154. package/contracts/topic-step-decision.schema.yaml +45 -0
  155. package/contracts/topic.schema.yaml +65 -0
  156. package/contracts/true-close.schema.yaml +15 -0
  157. package/contracts/wave.schema.yaml +29 -0
  158. package/contracts/worker-output.schema.yaml +15 -0
  159. package/methodology/audit-sweep-p0p1-recall.yaml +45 -0
  160. package/methodology/authority-convergence-policy.yaml +42 -0
  161. package/methodology/core.yaml +25 -0
  162. package/methodology/four-closure-policy.yaml +28 -0
  163. package/methodology/overflow-continuation-policy.yaml +14 -0
  164. package/methodology/role-separation-policy.yaml +28 -0
  165. package/methodology/skill-exchange-projection.yaml +114 -0
  166. package/methodology/skill-handoff.yaml +34 -0
  167. package/methodology/skill-installer-result.yaml +27 -0
  168. package/methodology/skill-installer-summary-projection.yaml +181 -0
  169. package/methodology/skill-runtime.yaml +23 -0
  170. package/methodology/spec-reconstruction.yaml +63 -0
  171. package/methodology/spec-target-truth-profile.yaml +53 -0
  172. package/methodology/topic-lifecycle-report.yaml +144 -0
  173. package/methodology/topic-lifecycle.yaml +37 -0
  174. package/methodology/topic-naming-ontology.yaml +21 -0
  175. package/methodology/topic-ontology.yaml +38 -0
  176. package/methodology/topic-validation-policy.yaml +9 -0
  177. package/methodology/wave-dag-policy.yaml +14 -0
  178. package/package.json +50 -0
  179. package/spec/_meta/command-gating-matrix.yaml +110 -0
  180. package/spec/_meta/generate-drift-migration-checklist.yaml +155 -0
  181. package/spec/_meta/governance-routing-cutover-checklist.yaml +35 -0
  182. package/spec/_meta/phase2-impacted-surface-matrix.yaml +44 -0
  183. package/spec/_meta/spec-authority-cutover-readiness.yaml +104 -0
  184. package/spec/_meta/spec-tree-model.yaml +72 -0
  185. package/spec/bootstrap-state.yaml +99 -0
  186. package/spec/product-scope.yaml +56 -0
@@ -0,0 +1,18 @@
1
+ export { createAuditSweepPlan } from "./audit-sweep-runtime/inventory.mjs";
2
+ export {
3
+ dispatchAuditSweepChunk,
4
+ reviewAuditSweepChunk,
5
+ skipAuditSweepChunk,
6
+ } from "./audit-sweep-runtime/chunks.mjs";
7
+ export { ingestAuditSweepChunk } from "./audit-sweep-runtime/ingest.mjs";
8
+ export { runCodexAuditSweepChunk } from "./audit-sweep-runtime/codex-auditor.mjs";
9
+ export { buildAuditSweepLedger } from "./audit-sweep-runtime/ledger.mjs";
10
+ export {
11
+ admitAuditSweepRemediationMap,
12
+ buildAuditSweepRemediationMap,
13
+ } from "./audit-sweep-runtime/remediation.mjs";
14
+ export { resolveAuditSweepFinding } from "./audit-sweep-runtime/rerun.mjs";
15
+ export { buildAuditSweepCloseoutImport } from "./audit-sweep-runtime/closeout.mjs";
16
+ export { getAuditSweepStatus } from "./audit-sweep-runtime/status.mjs";
17
+ export { validateAuditSweepArtifacts } from "./audit-sweep-runtime/validators.mjs";
18
+ export { formatAuditSweepPayload } from "./audit-sweep-runtime/format.mjs";
@@ -0,0 +1,309 @@
1
+ import { loadTopicRuntimeContracts } from "./contracts.mjs";
2
+
3
+ function stringList(value) {
4
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
5
+ }
6
+
7
+ function normalizeAuthorityConvergencePolicy(parsed) {
8
+ const policy = parsed?.authority_convergence_policy ?? {};
9
+ const postUpdateReview = policy.post_update_review ?? {};
10
+ return {
11
+ triggerPacketKinds: stringList(policy.trigger_packet_kinds),
12
+ triggerRefPrefixes: stringList(policy.trigger_ref_prefixes),
13
+ triggerWorkTypes: stringList(policy.trigger_topic_fields?.work_type),
14
+ requiredResultKind: typeof policy.required_result?.result_kind === "string"
15
+ ? policy.required_result.result_kind
16
+ : "audit",
17
+ passVerdict: typeof policy.required_result?.pass_verdict === "string"
18
+ ? policy.required_result.pass_verdict
19
+ : "PASS",
20
+ blockedVerdicts: stringList(policy.blocked_verdicts),
21
+ postUpdateReview: {
22
+ triggerPacketKinds: stringList(postUpdateReview.trigger_packet_kinds),
23
+ triggerRefPrefixes: stringList(postUpdateReview.trigger_ref_prefixes),
24
+ requiredResultKind: typeof postUpdateReview.required_result?.result_kind === "string"
25
+ ? postUpdateReview.required_result.result_kind
26
+ : "judgement",
27
+ passVerdict: typeof postUpdateReview.required_result?.pass_verdict === "string"
28
+ ? postUpdateReview.required_result.pass_verdict
29
+ : "PASS",
30
+ },
31
+ };
32
+ }
33
+
34
+ export async function loadAuthorityConvergencePolicy(projectRoot) {
35
+ const loaded = await loadTopicRuntimeContracts(projectRoot);
36
+ return normalizeAuthorityConvergencePolicy(loaded.authorityConvergencePolicy.data);
37
+ }
38
+
39
+ export function needsAuthorityConvergenceAudit(topic, packet, policy) {
40
+ if (policy.triggerPacketKinds.includes(String(packet.packet_kind ?? ""))) return true;
41
+ if (policy.triggerWorkTypes.includes(String(topic.work_type ?? ""))) return true;
42
+ const refs = [
43
+ ...stringList(packet.authority_owner),
44
+ ...stringList(packet.canonical_seams),
45
+ ];
46
+ return refs.some((ref) => policy.triggerRefPrefixes.some((prefix) => (
47
+ ref === prefix.slice(0, -1) || ref.startsWith(prefix) || ref.includes(prefix)
48
+ )));
49
+ }
50
+
51
+ export function needsPostUpdateReview(packet, policy) {
52
+ const reviewPolicy = policy.postUpdateReview ?? {};
53
+ if (reviewPolicy.triggerPacketKinds?.includes(String(packet.packet_kind ?? ""))) return true;
54
+ const refs = [
55
+ ...stringList(packet.authority_owner),
56
+ ...stringList(packet.canonical_seams),
57
+ ];
58
+ return refs.some((ref) => reviewPolicy.triggerRefPrefixes?.some((prefix) => (
59
+ ref === prefix.slice(0, -1) || ref.startsWith(prefix) || ref.includes(prefix)
60
+ )));
61
+ }
62
+
63
+ export function latestResultOfKind(results, kind) {
64
+ return [...results].reverse().find((entry) => entry.result?.result_kind === kind) ?? null;
65
+ }
66
+
67
+ function verifiedAtMs(resultEntry) {
68
+ const value = resultEntry?.result?.verified_at;
69
+ if (typeof value !== "string" || value.length === 0) return Number.NaN;
70
+ return Date.parse(value);
71
+ }
72
+
73
+ export function hasFreshPassingPostUpdateReview(results, implementationResult, policy) {
74
+ const reviewPolicy = policy.postUpdateReview ?? {};
75
+ const implementationVerifiedAt = verifiedAtMs(implementationResult);
76
+ if (!Number.isFinite(implementationVerifiedAt)) return false;
77
+ return [...results].reverse().some((entry) => (
78
+ entry.result?.result_kind === reviewPolicy.requiredResultKind
79
+ && entry.result?.verdict === reviewPolicy.passVerdict
80
+ && verifiedAtMs(entry) >= implementationVerifiedAt
81
+ ));
82
+ }
83
+
84
+ export function buildPostUpdateReviewDecision({ topicId, wave, packets, results, policy, commandRef }) {
85
+ const specUpdatingPacket = packets.find((entry) => needsPostUpdateReview(entry.packet, policy));
86
+ const implementationResult = latestResultOfKind(results, "implementation");
87
+ if (
88
+ !specUpdatingPacket
89
+ || implementationResult?.result?.verdict !== "PASS"
90
+ || hasFreshPassingPostUpdateReview(results, implementationResult, policy)
91
+ ) {
92
+ return null;
93
+ }
94
+ const reviewPolicy = policy.postUpdateReview ?? {};
95
+ return {
96
+ stopClass: "require_human_confirmation",
97
+ recommendedAction: "record_result",
98
+ reasonCode: "spec_update_review_required",
99
+ recommendedDecision: "record_post_spec_update_judgement_before_wave_closeout",
100
+ recommendationRationale: "This wave updated spec/authority truth; manager judgement is required before automatic wave closeout.",
101
+ expectedArtifacts: [`result-${wave.wave_id}-${reviewPolicy.requiredResultKind}.md`],
102
+ nextCommandRef: commandRef([
103
+ "result",
104
+ "record",
105
+ topicId,
106
+ "--kind",
107
+ reviewPolicy.requiredResultKind,
108
+ "--verdict",
109
+ "<verdict>",
110
+ "--from",
111
+ "<path>",
112
+ "--verified-at",
113
+ "<utc>",
114
+ ]),
115
+ };
116
+ }
117
+
118
+ export function authorityConvergenceAuditInstructions(role) {
119
+ return role === "audit"
120
+ ? `
121
+ Authority Convergence Audit:
122
+ - Check implementation readiness, owner split, parallel truth, canonical vocabulary, and blocking deferred scope.
123
+ - Do not implement code, edit spec, or decide semantic acceptance.
124
+ - Return PASS, NEEDS_REVISION, or FAIL with blocking_findings, concerns, deferred_non_blockers, authority_refs, and ready_for_implementation.
125
+ `
126
+ : "";
127
+ }
128
+
129
+ export function buildAuthorityConvergenceDecision({ topicId, wave, packet, auditResult, policy, commandRef }) {
130
+ if (packet.status !== "dispatched") {
131
+ return {
132
+ stopClass: "continue",
133
+ recommendedAction: "dispatch_audit",
134
+ reasonCode: "authority_convergence_audit_required",
135
+ recommendedDecision: "dispatch_authority_convergence_auditor",
136
+ recommendationRationale: "This packet changes or anchors authority/spec truth.",
137
+ expectedArtifacts: [`prompt-${packet.packet_id}-audit.md`],
138
+ nextCommandRef: commandRef(["audit", "dispatch", topicId, "--packet", packet.packet_id]),
139
+ };
140
+ }
141
+ if (auditResult?.result?.verdict === policy.passVerdict) {
142
+ return {
143
+ stopClass: "continue",
144
+ recommendedAction: "dispatch_worker",
145
+ reasonCode: "authority_convergence_audit_passed",
146
+ recommendedDecision: "dispatch_the_selected_packet_to_the_worker",
147
+ recommendationRationale: "The authority convergence audit passed.",
148
+ expectedArtifacts: [`prompt-${packet.packet_id}-worker.md`],
149
+ nextCommandRef: commandRef(["worker", "dispatch", topicId, "--packet", packet.packet_id]),
150
+ };
151
+ }
152
+ if (auditResult && policy.blockedVerdicts.includes(auditResult.result?.verdict)) {
153
+ return {
154
+ stopClass: "blocked",
155
+ recommendedAction: "open_remediation",
156
+ reasonCode: "authority_convergence_audit_failed",
157
+ recommendedDecision: "revise_authority_packet_before_implementation_dispatch",
158
+ recommendationRationale: "The latest authority convergence audit result blocks implementation dispatch.",
159
+ blockingChecks: [{
160
+ id: "authority_convergence_audit_verdict",
161
+ ok: false,
162
+ reason: `audit verdict is ${auditResult.result?.verdict}`,
163
+ }],
164
+ nextCommandRef: commandRef([
165
+ "remediation",
166
+ "open",
167
+ topicId,
168
+ "--kind",
169
+ "a",
170
+ "--reason",
171
+ "authority-convergence",
172
+ ]),
173
+ };
174
+ }
175
+ return {
176
+ stopClass: "await_external_evidence",
177
+ recommendedAction: "record_result",
178
+ reasonCode: "awaiting_authority_convergence_audit_result",
179
+ recommendedDecision: "record_the_authority_convergence_audit_result_when_available",
180
+ recommendationRationale: "The authority convergence audit must be recorded before implementation dispatch.",
181
+ expectedArtifacts: [`result-${wave.wave_id}-${policy.requiredResultKind}.md`],
182
+ nextCommandRef: commandRef([
183
+ "result",
184
+ "record",
185
+ topicId,
186
+ "--kind",
187
+ policy.requiredResultKind,
188
+ "--verdict",
189
+ "<verdict>",
190
+ "--from",
191
+ "<path>",
192
+ "--verified-at",
193
+ "<utc>",
194
+ ]),
195
+ };
196
+ }
197
+
198
+ function dispatchWorkerDecision(topicId, packet) {
199
+ return {
200
+ stopClass: "continue",
201
+ recommendedAction: "dispatch_worker",
202
+ reasonCode: "dispatchable_packet_available",
203
+ recommendedDecision: "dispatch_the_selected_packet_to_the_worker",
204
+ recommendationRationale: "A dispatchable packet exists for the admitted wave, so the next operational step is mechanical.",
205
+ expectedArtifacts: [`prompt-${packet.packet_id}-worker.md`],
206
+ nextCommandRef: null,
207
+ };
208
+ }
209
+
210
+ function dispatchablePacketRank(packet) {
211
+ const ranks = {
212
+ candidate: 0,
213
+ admitted: 1,
214
+ preflight: 2,
215
+ dispatched: 3,
216
+ };
217
+ return ranks[packet.status] ?? 99;
218
+ }
219
+
220
+ export async function buildPreImplementationDecision({
221
+ projectRoot,
222
+ loaded,
223
+ wave,
224
+ commandRef,
225
+ listWavePackets,
226
+ listWaveResults,
227
+ findUniqueFreezableDraftPacket,
228
+ loadTopicRuntimeAuthority,
229
+ }) {
230
+ const packets = await listWavePackets(loaded.topicDir, wave.wave_id);
231
+ const dispatchable = packets
232
+ .filter((entry) => ["candidate", "admitted", "preflight", "dispatched"].includes(entry.packet.status))
233
+ .sort((left, right) => (
234
+ dispatchablePacketRank(left.packet) - dispatchablePacketRank(right.packet)
235
+ || left.packetRefName.localeCompare(right.packetRefName)
236
+ ))[0];
237
+ if (dispatchable) {
238
+ const policy = await loadAuthorityConvergencePolicy(projectRoot);
239
+ if (wave.state === "preflight_admitted" && needsAuthorityConvergenceAudit(loaded.topic, dispatchable.packet, policy)) {
240
+ const auditResult = latestResultOfKind(await listWaveResults(loaded.topicDir, wave.wave_id), policy.requiredResultKind);
241
+ return buildAuthorityConvergenceDecision({
242
+ topicId: loaded.topicId,
243
+ wave,
244
+ packet: dispatchable.packet,
245
+ auditResult,
246
+ policy,
247
+ commandRef,
248
+ });
249
+ }
250
+ const decision = dispatchWorkerDecision(loaded.topicId, dispatchable.packet);
251
+ decision.nextCommandRef = commandRef(["worker", "dispatch", loaded.topicId, "--packet", dispatchable.packet.packet_id]);
252
+ return decision;
253
+ }
254
+
255
+ const autoDraft = await findUniqueFreezableDraftPacket(
256
+ projectRoot,
257
+ loaded,
258
+ wave,
259
+ await loadTopicRuntimeAuthority(projectRoot),
260
+ );
261
+ return autoDraft.ok
262
+ ? {
263
+ stopClass: "continue",
264
+ recommendedAction: "freeze_packet",
265
+ reasonCode: "draft_packet_ready",
266
+ recommendedDecision: "freeze_packet",
267
+ recommendationRationale: "One draft is freezeable.",
268
+ expectedArtifacts: [`packet-${autoDraft.packet.packet_id}.md`],
269
+ nextCommandRef: commandRef(["packet", "freeze", loaded.topicId, "--from", autoDraft.draftRef]),
270
+ }
271
+ : {
272
+ stopClass: "require_human_confirmation",
273
+ recommendedAction: "freeze_packet",
274
+ reasonCode: autoDraft.reasonCode,
275
+ recommendedDecision: "select_or_create_draft",
276
+ recommendationRationale: "Draft packet is missing or ambiguous.",
277
+ expectedArtifacts: ["packet-<packet-id>.md"],
278
+ nextCommandRef: commandRef(["packet", "freeze", loaded.topicId, "--from", "<draft-packet>"]),
279
+ };
280
+ }
281
+
282
+ export function buildDispatchPrompt(packet, topicId, role) {
283
+ const auditInstructions = authorityConvergenceAuditInstructions(role);
284
+ return `# ${role === "worker" ? "Worker" : "Audit"} Dispatch
285
+ Topic: \`${topicId}\`
286
+ Packet: \`${packet.packet_id}\`
287
+ Wave: \`${packet.wave_id}\`
288
+ Packet Kind: \`${packet.packet_kind}\`
289
+ Role: \`${role}\`
290
+ Authority Owner:
291
+ ${(Array.isArray(packet.authority_owner) ? packet.authority_owner : []).map((entry) => `- ${entry}`).join(`
292
+ `)}
293
+ Canonical Seams:
294
+ ${(Array.isArray(packet.canonical_seams) ? packet.canonical_seams : []).map((entry) => `- ${entry}`).join(`
295
+ `)}
296
+ Forbidden Shortcuts:
297
+ ${(Array.isArray(packet.forbidden_shortcuts) ? packet.forbidden_shortcuts : []).map((entry) => `- ${entry}`).join(`
298
+ `)}
299
+ Acceptance Invariants:
300
+ ${(Array.isArray(packet.acceptance_invariants) ? packet.acceptance_invariants : []).map((entry) => `- ${entry}`).join(`
301
+ `)}
302
+ Negative Tests:
303
+ ${(Array.isArray(packet.negative_tests) ? packet.negative_tests : []).map((entry) => `- ${entry}`).join(`
304
+ `)}
305
+ Reopen Conditions:
306
+ ${(Array.isArray(packet.reopen_conditions) ? packet.reopen_conditions : []).map((entry) => `- ${entry}`).join(`
307
+ `)}
308
+ ${auditInstructions}`;
309
+ }
@@ -0,0 +1,370 @@
1
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import YAML from "yaml";
4
+
5
+ import { loadBlueprintReference, loadSpecGenerationInputsConfig, loadSpecTreeModelContract } from "./contracts.mjs";
6
+ import { pathExists } from "./fs-helpers.mjs";
7
+ import {
8
+ localize,
9
+ styleHeading,
10
+ styleLabel,
11
+ styleStatus,
12
+ } from "./ui.mjs";
13
+
14
+ const BLUEPRINT_AUDIT_CONTRACT_VERSION = "nimicoding.blueprint-audit.v1";
15
+ const DEFAULT_REPORT_PATH = ".nimi/local/report/blueprint-equivalence-audit.json";
16
+
17
+ async function collectFiles(rootPath, relativePrefix = "") {
18
+ const info = await pathExists(rootPath);
19
+ if (!info || !info.isDirectory()) {
20
+ return [];
21
+ }
22
+
23
+ const entries = await readdir(rootPath, { withFileTypes: true });
24
+ const collected = [];
25
+ for (const entry of entries) {
26
+ const absoluteChildPath = path.join(rootPath, entry.name);
27
+ const relativeChildPath = relativePrefix ? path.posix.join(relativePrefix, entry.name) : entry.name;
28
+ if (entry.isDirectory()) {
29
+ collected.push(...await collectFiles(absoluteChildPath, relativeChildPath));
30
+ } else if (entry.isFile()) {
31
+ collected.push(relativeChildPath.split(path.sep).join(path.posix.sep));
32
+ }
33
+ }
34
+
35
+ return collected.sort();
36
+ }
37
+
38
+ async function collectDomainInventory(rootPath) {
39
+ const info = await pathExists(rootPath);
40
+ if (!info || !info.isDirectory()) {
41
+ return {
42
+ domains: [],
43
+ hasIndex: false,
44
+ files: [],
45
+ };
46
+ }
47
+
48
+ const entries = await readdir(rootPath, { withFileTypes: true });
49
+ const domains = [];
50
+ for (const entry of entries) {
51
+ if (!entry.isDirectory()) {
52
+ continue;
53
+ }
54
+
55
+ const kernelPath = path.join(rootPath, entry.name, "kernel");
56
+ const kernelInfo = await pathExists(kernelPath);
57
+ if (kernelInfo?.isDirectory()) {
58
+ domains.push(entry.name);
59
+ }
60
+ }
61
+
62
+ const indexInfo = await pathExists(path.join(rootPath, "INDEX.md"));
63
+ const files = await collectFiles(rootPath);
64
+
65
+ return {
66
+ domains: domains.sort(),
67
+ hasIndex: Boolean(indexInfo?.isFile()),
68
+ files,
69
+ };
70
+ }
71
+
72
+ function categorizeBlueprintFiles(files) {
73
+ const kernelMarkdown = [];
74
+ const kernelTables = [];
75
+ const kernelGenerated = [];
76
+ const domainGuides = [];
77
+
78
+ for (const relativePath of files) {
79
+ if (/^([^/]+)\/kernel\/[^/]+\.md$/.test(relativePath)) {
80
+ kernelMarkdown.push(relativePath);
81
+ continue;
82
+ }
83
+
84
+ if (/^([^/]+)\/kernel\/tables\/.+\.(ya?ml)$/.test(relativePath)) {
85
+ kernelTables.push(relativePath);
86
+ continue;
87
+ }
88
+
89
+ if (/^([^/]+)\/kernel\/generated\/.+\.md$/.test(relativePath)) {
90
+ kernelGenerated.push(relativePath);
91
+ continue;
92
+ }
93
+
94
+ if (/^[^/]+\/[^/]+\.md$/.test(relativePath)) {
95
+ domainGuides.push(relativePath);
96
+ }
97
+ }
98
+
99
+ return {
100
+ kernelMarkdown,
101
+ kernelTables,
102
+ kernelGenerated,
103
+ domainGuides,
104
+ };
105
+ }
106
+
107
+ function compareFileSets(blueprintFiles, canonicalFiles) {
108
+ const canonicalSet = new Set(canonicalFiles);
109
+ const blueprintSet = new Set(blueprintFiles);
110
+
111
+ return {
112
+ present: blueprintFiles.filter((filePath) => canonicalSet.has(filePath)),
113
+ missing: blueprintFiles.filter((filePath) => !canonicalSet.has(filePath)),
114
+ extra: canonicalFiles.filter((filePath) => !blueprintSet.has(filePath)),
115
+ };
116
+ }
117
+
118
+ function buildSummarySection(compareResult) {
119
+ return {
120
+ present: compareResult.present.length,
121
+ missing: compareResult.missing.length,
122
+ extra: compareResult.extra.length,
123
+ };
124
+ }
125
+
126
+ function collectIds(value, ids = []) {
127
+ if (Array.isArray(value)) {
128
+ for (const entry of value) {
129
+ collectIds(entry, ids);
130
+ }
131
+ return ids;
132
+ }
133
+
134
+ if (value && typeof value === "object") {
135
+ if (typeof value.id === "string") {
136
+ ids.push(value.id);
137
+ }
138
+ for (const nestedValue of Object.values(value)) {
139
+ collectIds(nestedValue, ids);
140
+ }
141
+ }
142
+
143
+ return ids;
144
+ }
145
+
146
+ async function compareRuleIds(blueprintAbsoluteRoot, canonicalAbsoluteRoot, presentTablePaths) {
147
+ const files = [];
148
+ const aggregateMissing = new Set();
149
+ const aggregateExtra = new Set();
150
+ const parseErrors = [];
151
+
152
+ for (const relativePath of presentTablePaths) {
153
+ try {
154
+ const blueprintText = await readFile(path.join(blueprintAbsoluteRoot, relativePath), "utf8");
155
+ const canonicalText = await readFile(path.join(canonicalAbsoluteRoot, relativePath), "utf8");
156
+ const blueprintIds = Array.from(new Set(collectIds(YAML.parse(blueprintText)))).sort();
157
+ const canonicalIds = Array.from(new Set(collectIds(YAML.parse(canonicalText)))).sort();
158
+ const missingIds = blueprintIds.filter((entry) => !canonicalIds.includes(entry));
159
+ const extraIds = canonicalIds.filter((entry) => !blueprintIds.includes(entry));
160
+
161
+ missingIds.forEach((entry) => aggregateMissing.add(entry));
162
+ extraIds.forEach((entry) => aggregateExtra.add(entry));
163
+
164
+ files.push({
165
+ path: relativePath,
166
+ blueprintIds: blueprintIds.length,
167
+ canonicalIds: canonicalIds.length,
168
+ missingIds,
169
+ extraIds,
170
+ });
171
+ } catch (error) {
172
+ parseErrors.push({
173
+ path: relativePath,
174
+ error: error instanceof Error ? error.message : String(error),
175
+ });
176
+ }
177
+ }
178
+
179
+ return {
180
+ comparedFiles: files.length,
181
+ files,
182
+ parseErrors,
183
+ missingRuleIds: Array.from(aggregateMissing).sort(),
184
+ extraRuleIds: Array.from(aggregateExtra).sort(),
185
+ };
186
+ }
187
+
188
+ export async function buildBlueprintAuditPayload(projectRoot, options = {}) {
189
+ const specTreeModel = await loadSpecTreeModelContract(projectRoot);
190
+ const blueprintReference = await loadBlueprintReference(projectRoot);
191
+ const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
192
+
193
+ const canonicalRoot = options.canonicalRoot
194
+ ?? specTreeModel.canonicalRoot
195
+ ?? ".nimi/spec";
196
+ const blueprintRoot = options.blueprintRoot
197
+ ?? blueprintReference.root
198
+ ?? specGenerationInputs.benchmarkBlueprintRoot
199
+ ?? null;
200
+
201
+ if (!blueprintRoot) {
202
+ return {
203
+ ok: false,
204
+ exitCode: 2,
205
+ inputError: true,
206
+ error: `${localize(
207
+ "nimicoding blueprint-audit refused: no blueprint root is declared; pass --blueprint-root or add project-local blueprint reference metadata.",
208
+ "nimicoding blueprint-audit 已拒绝:没有声明 blueprint root;请传入 --blueprint-root,或补充项目本地 blueprint reference 元数据。",
209
+ )}\n`,
210
+ };
211
+ }
212
+
213
+ const blueprintAbsoluteRoot = path.resolve(projectRoot, blueprintRoot);
214
+ const canonicalAbsoluteRoot = path.resolve(projectRoot, canonicalRoot);
215
+ const blueprintInventory = await collectDomainInventory(blueprintAbsoluteRoot);
216
+ const canonicalInventory = await collectDomainInventory(canonicalAbsoluteRoot);
217
+
218
+ const missingDomains = blueprintInventory.domains.filter((domainId) => !canonicalInventory.domains.includes(domainId));
219
+ const extraDomains = canonicalInventory.domains.filter((domainId) => !blueprintInventory.domains.includes(domainId));
220
+ const blueprintFiles = categorizeBlueprintFiles(blueprintInventory.files);
221
+ const canonicalFiles = categorizeBlueprintFiles(canonicalInventory.files);
222
+
223
+ const kernelMarkdown = compareFileSets(blueprintFiles.kernelMarkdown, canonicalFiles.kernelMarkdown);
224
+ const kernelTables = compareFileSets(blueprintFiles.kernelTables, canonicalFiles.kernelTables);
225
+ const kernelGenerated = compareFileSets(blueprintFiles.kernelGenerated, canonicalFiles.kernelGenerated);
226
+ const domainGuides = compareFileSets(blueprintFiles.domainGuides, canonicalFiles.domainGuides);
227
+ const ruleIdPreservation = await compareRuleIds(
228
+ blueprintAbsoluteRoot,
229
+ canonicalAbsoluteRoot,
230
+ kernelTables.present,
231
+ );
232
+
233
+ const indexPresent = blueprintInventory.hasIndex ? canonicalInventory.hasIndex : true;
234
+ const ok = missingDomains.length === 0
235
+ && kernelMarkdown.missing.length === 0
236
+ && kernelTables.missing.length === 0
237
+ && kernelGenerated.missing.length === 0
238
+ && domainGuides.missing.length === 0
239
+ && ruleIdPreservation.missingRuleIds.length === 0
240
+ && ruleIdPreservation.parseErrors.length === 0
241
+ && indexPresent;
242
+
243
+ const reportPath = path.join(projectRoot, DEFAULT_REPORT_PATH);
244
+ const nextSteps = [];
245
+ if (missingDomains.length > 0 || kernelMarkdown.missing.length > 0 || kernelTables.missing.length > 0 || !indexPresent) {
246
+ nextSteps.push("Copy the missing blueprint structure into `/.nimi/spec/**` before attempting authority cutover.");
247
+ }
248
+ if (kernelGenerated.missing.length > 0) {
249
+ nextSteps.push("Regenerate derived kernel docs after canonical blueprint content is built out.");
250
+ }
251
+ if (domainGuides.missing.length > 0) {
252
+ nextSteps.push("Thin and map domain guides only after kernel coverage is in place.");
253
+ }
254
+ if (ruleIdPreservation.missingRuleIds.length > 0 || ruleIdPreservation.parseErrors.length > 0) {
255
+ nextSteps.push("Preserve benchmark rule IDs in canonical kernel tables before treating the canonical tree as benchmark-equivalent.");
256
+ }
257
+
258
+ return {
259
+ contractVersion: BLUEPRINT_AUDIT_CONTRACT_VERSION,
260
+ ok,
261
+ exitCode: ok ? 0 : 1,
262
+ projectRoot,
263
+ blueprintRoot,
264
+ canonicalRoot,
265
+ blueprintReference,
266
+ specTreeModel: {
267
+ ok: specTreeModel.ok,
268
+ profile: specTreeModel.profile,
269
+ canonicalRoot: specTreeModel.canonicalRoot,
270
+ authorityMode: specTreeModel.authorityMode,
271
+ },
272
+ specGenerationInputs: {
273
+ ok: specGenerationInputs.ok,
274
+ mode: specGenerationInputs.mode,
275
+ acceptanceMode: specGenerationInputs.acceptanceMode,
276
+ benchmarkBlueprintRoot: specGenerationInputs.benchmarkBlueprintRoot,
277
+ },
278
+ inventory: {
279
+ blueprintDomains: blueprintInventory.domains,
280
+ canonicalDomains: canonicalInventory.domains,
281
+ missingDomains,
282
+ extraDomains,
283
+ indexPresent,
284
+ },
285
+ comparison: {
286
+ kernelMarkdown: {
287
+ ...buildSummarySection(kernelMarkdown),
288
+ missingPaths: kernelMarkdown.missing,
289
+ },
290
+ kernelTables: {
291
+ ...buildSummarySection(kernelTables),
292
+ missingPaths: kernelTables.missing,
293
+ },
294
+ kernelGenerated: {
295
+ ...buildSummarySection(kernelGenerated),
296
+ missingPaths: kernelGenerated.missing,
297
+ },
298
+ domainGuides: {
299
+ ...buildSummarySection(domainGuides),
300
+ missingPaths: domainGuides.missing,
301
+ },
302
+ },
303
+ structuralGaps: {
304
+ missingDomains,
305
+ missingKernelMarkdown: kernelMarkdown.missing,
306
+ missingKernelTables: kernelTables.missing,
307
+ indexMissing: !indexPresent,
308
+ },
309
+ semanticMappingGaps: {
310
+ ruleIdPreservation,
311
+ },
312
+ derivedViewGaps: {
313
+ missingKernelGenerated: kernelGenerated.missing,
314
+ missingDomainGuides: domainGuides.missing,
315
+ },
316
+ artifactPath: reportPath,
317
+ nextSteps,
318
+ };
319
+ }
320
+
321
+ export function formatBlueprintAuditPayload(payload) {
322
+ const lines = [
323
+ styleHeading(`nimicoding blueprint-audit: ${payload.projectRoot}`),
324
+ "",
325
+ styleLabel(localize("Summary:", "摘要:")),
326
+ ` - ${localize("status", "状态")}: ${styleStatus(payload.ok ? "ok" : "needs_attention")}`,
327
+ ` - ${localize("blueprint root", "blueprint root")}: ${payload.blueprintRoot}`,
328
+ ` - ${localize("canonical root", "canonical root")}: ${payload.canonicalRoot}`,
329
+ ` - ${localize("missing domains", "缺失域")}: ${payload.inventory.missingDomains.length}`,
330
+ ` - ${localize("kernel markdown missing", "缺失 kernel markdown")}: ${payload.comparison.kernelMarkdown.missing}`,
331
+ ` - ${localize("kernel tables missing", "缺失 kernel tables")}: ${payload.comparison.kernelTables.missing}`,
332
+ ` - ${localize("generated views missing", "缺失 generated 视图")}: ${payload.comparison.kernelGenerated.missing}`,
333
+ ` - ${localize("guide mappings missing", "缺失 guide 映射")}: ${payload.comparison.domainGuides.missing}`,
334
+ ` - ${localize("rule ids missing", "缺失 rule id")}: ${payload.semanticMappingGaps.ruleIdPreservation.missingRuleIds.length}`,
335
+ "",
336
+ styleLabel(localize("Coverage:", "覆盖情况:")),
337
+ ` - ${localize("blueprint domains", "blueprint 域")}: ${payload.inventory.blueprintDomains.length}`,
338
+ ` - ${localize("canonical domains", "canonical 域")}: ${payload.inventory.canonicalDomains.length}`,
339
+ ` - ${localize("INDEX present", "INDEX 已存在")}: ${payload.inventory.indexPresent ? "true" : "false"}`,
340
+ ];
341
+
342
+ if (payload.inventory.missingDomains.length > 0) {
343
+ lines.push(` - ${localize("missing domain ids", "缺失域 ID")}: ${payload.inventory.missingDomains.join(", ")}`);
344
+ }
345
+
346
+ if (payload.semanticMappingGaps.ruleIdPreservation.missingRuleIds.length > 0) {
347
+ lines.push(` - ${localize("missing rule ids", "缺失 rule id")}: ${payload.semanticMappingGaps.ruleIdPreservation.missingRuleIds.join(", ")}`);
348
+ }
349
+
350
+ if (payload.semanticMappingGaps.ruleIdPreservation.parseErrors.length > 0) {
351
+ lines.push(` - ${localize("rule-id parse errors", "rule-id 解析错误")}: ${payload.semanticMappingGaps.ruleIdPreservation.parseErrors.length}`);
352
+ }
353
+
354
+ if (payload.nextSteps.length > 0) {
355
+ lines.push("", styleLabel(localize("Next:", "下一步:")));
356
+ for (const step of payload.nextSteps) {
357
+ lines.push(` - ${localize(step, step)}`);
358
+ }
359
+ }
360
+
361
+ return `${lines.join("\n")}\n`;
362
+ }
363
+
364
+ export async function writeBlueprintAuditArtifact(projectRoot, payload) {
365
+ await mkdir(path.dirname(payload.artifactPath), { recursive: true });
366
+ await writeFile(payload.artifactPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
367
+ return {
368
+ jsonRef: path.relative(projectRoot, payload.artifactPath).split(path.sep).join(path.posix.sep),
369
+ };
370
+ }