@nimiplatform/nimi-coding 0.1.0 → 0.2.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 (121) hide show
  1. package/README.md +19 -20
  2. package/adapters/oh-my-codex/README.md +8 -9
  3. package/cli/commands/audit-sweep.mjs +10 -10
  4. package/cli/commands/classify-spec-tree.mjs +5 -0
  5. package/cli/commands/closeout.mjs +3 -0
  6. package/cli/commands/generate-spec-derived-docs.mjs +20 -0
  7. package/cli/commands/generate-spec-migration-plan.mjs +30 -0
  8. package/cli/commands/start.mjs +5 -1
  9. package/cli/commands/surface-validator-command.mjs +49 -0
  10. package/cli/commands/sweep-design.mjs +295 -0
  11. package/cli/commands/sweep.mjs +22 -0
  12. package/cli/commands/sync.mjs +132 -0
  13. package/cli/commands/topic-formatters.mjs +8 -8
  14. package/cli/commands/validate-ai-governance.mjs +167 -46
  15. package/cli/commands/validate-domain-admission.mjs +5 -0
  16. package/cli/commands/validate-guidance-bodies.mjs +5 -0
  17. package/cli/commands/validate-placement.mjs +5 -0
  18. package/cli/commands/validate-projection-edges.mjs +5 -0
  19. package/cli/commands/validate-spec-audit.mjs +5 -1
  20. package/cli/commands/validate-table-family.mjs +5 -0
  21. package/cli/commands/validate-tracked-output-admission.mjs +5 -0
  22. package/cli/constants.mjs +5 -49
  23. package/cli/help.mjs +33 -11
  24. package/cli/index.mjs +20 -2
  25. package/cli/lib/audit-sweep-runtime/admissions.mjs +38 -29
  26. package/cli/lib/audit-sweep-runtime/audit-validity.mjs +8 -0
  27. package/cli/lib/audit-sweep-runtime/chunks.mjs +11 -11
  28. package/cli/lib/audit-sweep-runtime/closeout.mjs +8 -8
  29. package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +3 -3
  30. package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +10 -10
  31. package/cli/lib/audit-sweep-runtime/common.mjs +7 -7
  32. package/cli/lib/audit-sweep-runtime/format.mjs +3 -3
  33. package/cli/lib/audit-sweep-runtime/ingest.mjs +8 -8
  34. package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +24 -27
  35. package/cli/lib/audit-sweep-runtime/inventory.mjs +58 -18
  36. package/cli/lib/audit-sweep-runtime/ledger.mjs +1 -1
  37. package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +2 -2
  38. package/cli/lib/audit-sweep-runtime/remediation.mjs +6 -6
  39. package/cli/lib/audit-sweep-runtime/rerun.mjs +6 -6
  40. package/cli/lib/audit-sweep-runtime/status.mjs +1 -1
  41. package/cli/lib/audit-sweep-runtime/validators.mjs +2 -2
  42. package/cli/lib/authority-convergence.mjs +397 -2
  43. package/cli/lib/blueprint-audit.mjs +5 -5
  44. package/cli/lib/closeout.mjs +126 -3
  45. package/cli/lib/contracts.mjs +21 -17
  46. package/cli/lib/handoff.mjs +29 -11
  47. package/cli/lib/high-risk-admission.mjs +60 -11
  48. package/cli/lib/high-risk-decision.mjs +31 -2
  49. package/cli/lib/high-risk-ingest.mjs +5 -1
  50. package/cli/lib/high-risk-review.mjs +5 -1
  51. package/cli/lib/internal/contracts-parse.mjs +195 -24
  52. package/cli/lib/internal/contracts-validators.mjs +3 -2
  53. package/cli/lib/internal/doctor-bootstrap-surface.mjs +82 -35
  54. package/cli/lib/internal/doctor-delegated-surface.mjs +1 -1
  55. package/cli/lib/internal/doctor-finalize.mjs +12 -8
  56. package/cli/lib/internal/doctor-inspectors.mjs +34 -1
  57. package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +74 -12
  58. package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +24 -6
  59. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +18 -23
  60. package/cli/lib/internal/surface-taxonomy-validators.mjs +931 -0
  61. package/cli/lib/internal/validators-spec.mjs +229 -20
  62. package/cli/lib/sweep-design-runtime/common.mjs +246 -0
  63. package/cli/lib/sweep-design-runtime/engine.mjs +733 -0
  64. package/cli/lib/sweep-design-runtime/fix-topic.mjs +414 -0
  65. package/cli/lib/sweep-design-runtime/lifecycle.mjs +54 -0
  66. package/cli/lib/sweep-design-runtime/results.mjs +324 -0
  67. package/cli/lib/sweep-design.mjs +8 -0
  68. package/cli/lib/sync.mjs +143 -0
  69. package/cli/lib/topic-artifacts.mjs +186 -0
  70. package/cli/lib/topic-authority-coverage.mjs +73 -0
  71. package/cli/lib/topic-closeout.mjs +560 -0
  72. package/cli/lib/topic-common.mjs +404 -0
  73. package/cli/lib/topic-decisions.mjs +332 -0
  74. package/cli/lib/topic-draft-packets.mjs +126 -7
  75. package/cli/lib/topic-execution.mjs +515 -0
  76. package/cli/lib/topic-goal.mjs +112 -33
  77. package/cli/lib/topic-ledger.mjs +281 -0
  78. package/cli/lib/topic-lifecycle-artifacts.mjs +173 -0
  79. package/cli/lib/topic-root-validation.mjs +288 -0
  80. package/cli/lib/topic-runner-commands.mjs +174 -0
  81. package/cli/lib/topic-runner-deferral.mjs +532 -0
  82. package/cli/lib/topic-runner-stale-gates.mjs +114 -0
  83. package/cli/lib/topic-runner-validation.mjs +138 -0
  84. package/cli/lib/topic-runner.mjs +109 -154
  85. package/cli/lib/topic-scaffold.mjs +252 -0
  86. package/cli/lib/topic-waves.mjs +403 -0
  87. package/cli/lib/topic.mjs +81 -93
  88. package/cli/lib/value-helpers.mjs +6 -1
  89. package/cli/seeds/bootstrap.mjs +96 -20
  90. package/cli/seeds/seed-policy.yaml +67 -0
  91. package/config/bootstrap.yaml +1 -1
  92. package/config/skill-manifest.yaml +4 -2
  93. package/config/spec-generation-inputs.yaml +41 -19
  94. package/contracts/audit-remediation-map.schema.yaml +1 -0
  95. package/contracts/audit-sweep-result.yaml +4 -0
  96. package/contracts/domain-admission.schema.yaml +56 -0
  97. package/contracts/migration-inventory.schema.yaml +80 -0
  98. package/contracts/negative-fixtures.yaml +91 -0
  99. package/contracts/placement-contract.schema.yaml +163 -0
  100. package/contracts/projection-edge.schema.yaml +130 -0
  101. package/contracts/shared-enums.yaml +68 -0
  102. package/contracts/spec-generation-audit.schema.yaml +19 -4
  103. package/contracts/spec-generation-inputs.schema.yaml +130 -29
  104. package/contracts/spec-reconstruction-result.yaml +9 -5
  105. package/contracts/surface-taxonomy.schema.yaml +201 -0
  106. package/contracts/sweep-design-result.yaml +349 -0
  107. package/contracts/table-family.schema.yaml +114 -0
  108. package/contracts/topic-goal.schema.yaml +10 -1
  109. package/contracts/tracked-output-admission.schema.yaml +70 -0
  110. package/contracts/workflow-consumer.schema.yaml +112 -0
  111. package/methodology/audit-sweep-p0p1-recall.yaml +1 -1
  112. package/methodology/spec-reconstruction.yaml +53 -30
  113. package/package.json +5 -4
  114. package/spec/_meta/command-gating-matrix.yaml +33 -0
  115. package/spec/_meta/generate-drift-migration-checklist.yaml +44 -62
  116. package/spec/_meta/governance-routing-cutover-checklist.yaml +3 -3
  117. package/spec/_meta/phase2-impacted-surface-matrix.yaml +14 -14
  118. package/spec/_meta/spec-authority-cutover-readiness.yaml +3 -5
  119. package/spec/_meta/spec-tree-model.yaml +104 -36
  120. package/spec/bootstrap-state.yaml +36 -36
  121. package/spec/product-scope.yaml +13 -10
@@ -0,0 +1,733 @@
1
+ import {
2
+ assertDesignArtifact,
3
+ AUDITOR_FAMILIES,
4
+ AUDITOR_MODES,
5
+ AUDITOR_RESULT_ORIGINS,
6
+ deriveRunId,
7
+ designRef,
8
+ FINAL_OUTCOME_STATES,
9
+ findingAuthorityRef,
10
+ findingCodeRefs,
11
+ inputError,
12
+ loadAuditFindings,
13
+ loadYamlRef,
14
+ LLM_AUDITOR_RESULT_ORIGINS,
15
+ normalizeFindingForDesign,
16
+ nowIso,
17
+ PRIOR_DESIGN_STATE_MARKERS,
18
+ requireRunId,
19
+ REVISION_TYPES,
20
+ safeDesignId,
21
+ sha256Object,
22
+ TRANSIENT_STATES,
23
+ writeYamlRef,
24
+ } from "./common.mjs";
25
+ import { sweepDesignWaveAuthorityRefs } from "../topic-authority-coverage.mjs";
26
+
27
+ const ZERO_HASH = "0".repeat(64);
28
+
29
+ function inventoryRef(runId) {
30
+ return designRef(runId, "inventory.yaml");
31
+ }
32
+
33
+ export function packetRef(runId, packetId) {
34
+ return designRef(runId, "design-auditor-packets", `${packetId}.yaml`);
35
+ }
36
+
37
+ export function resultRef(runId, resultId) {
38
+ return designRef(runId, "design-auditor-results", `${resultId}.yaml`);
39
+ }
40
+
41
+ function auditorPromptRef(runId, packetId) {
42
+ return designRef(runId, "auditor-prompts", `${packetId}.yaml`);
43
+ }
44
+
45
+ function batchManifestRef(runId, manifestId) {
46
+ return designRef(runId, "batch-manifests", `${manifestId}.yaml`);
47
+ }
48
+
49
+ export function ledgerRef(runId) {
50
+ return designRef(runId, "revision-ledger.yaml");
51
+ }
52
+
53
+ export function finalStateReportRef(runId) {
54
+ return designRef(runId, "final-state-report.yaml");
55
+ }
56
+
57
+ export function wavePlanRef(runId) {
58
+ return designRef(runId, "wave-plan.yaml");
59
+ }
60
+
61
+ export function decisionQueueRef(runId) {
62
+ return designRef(runId, "decision-queue.yaml");
63
+ }
64
+
65
+ export async function loadInventory(projectRoot, runId) {
66
+ return assertDesignArtifact(projectRoot, inventoryRef(runId), "sweep-design-inventory", "inventory");
67
+ }
68
+
69
+ function splitList(value) {
70
+ return String(value ?? "")
71
+ .split(",")
72
+ .map((item) => item.trim())
73
+ .filter(Boolean);
74
+ }
75
+
76
+ function normalizeIdList(value) {
77
+ return [...new Set(splitList(value))].sort();
78
+ }
79
+
80
+ function parsePositiveInteger(value, label) {
81
+ const parsed = Number.parseInt(String(value ?? ""), 10);
82
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
83
+ return { ok: false, error: `nimicoding sweep design refused: ${label} must be a positive integer.\n` };
84
+ }
85
+ return { ok: true, value: parsed };
86
+ }
87
+
88
+ function chunkArray(values, size) {
89
+ const chunks = [];
90
+ for (let index = 0; index < values.length; index += size) {
91
+ chunks.push(values.slice(index, index + size));
92
+ }
93
+ return chunks;
94
+ }
95
+
96
+ function requireArray(value, label, errors) {
97
+ if (!Array.isArray(value)) {
98
+ errors.push(`${label} must be an array`);
99
+ return [];
100
+ }
101
+ return value;
102
+ }
103
+
104
+ export function requireNonEmptyArray(value, label, errors) {
105
+ const array = requireArray(value, label, errors);
106
+ if (array.length === 0) errors.push(`${label} must not be empty`);
107
+ return array;
108
+ }
109
+
110
+ function requireString(value, label, errors) {
111
+ if (typeof value !== "string" || value.length === 0) {
112
+ errors.push(`${label} must be a non-empty string`);
113
+ return "";
114
+ }
115
+ return value;
116
+ }
117
+
118
+ function validateRequiredFields(value, fields, label, errors) {
119
+ for (const field of fields) {
120
+ if (value[field] === undefined || value[field] === null) {
121
+ errors.push(`${label}.${field} is required`);
122
+ }
123
+ }
124
+ }
125
+
126
+ function findInventoryFindings(inventory, findingIds) {
127
+ const byId = new Map(inventory.findings.map((finding) => [finding.finding_id, finding]));
128
+ return findingIds.map((findingId) => byId.get(findingId)).filter(Boolean);
129
+ }
130
+
131
+ function relatedFindingCandidates(inventory, selectedIds) {
132
+ const selected = findInventoryFindings(inventory, selectedIds);
133
+ const selectedOwners = new Set(selected.map((finding) => finding.owner_domain).filter(Boolean));
134
+ const selectedCategories = new Set(selected.map((finding) => finding.category).filter(Boolean));
135
+ return inventory.findings
136
+ .filter((finding) => !selectedIds.includes(finding.finding_id))
137
+ .filter((finding) => selectedOwners.has(finding.owner_domain) || selectedCategories.has(finding.category))
138
+ .map((finding) => finding.source_finding_ref)
139
+ .slice(0, 12);
140
+ }
141
+
142
+ function initialLedger(runId, timestamp) {
143
+ const ledger = {
144
+ version: 1,
145
+ kind: "sweep-design-revision-ledger",
146
+ run_id: runId,
147
+ ledger_id: `${runId}-revision-ledger`,
148
+ append_only: true,
149
+ entries: [],
150
+ previous_ledger_snapshot_hash: null,
151
+ entries_root_hash: ZERO_HASH,
152
+ ledger_snapshot_hash: null,
153
+ created_at: timestamp,
154
+ updated_at: timestamp,
155
+ };
156
+ ledger.ledger_snapshot_hash = computeLedgerSnapshotHash(ledger);
157
+ return ledger;
158
+ }
159
+
160
+ function entryHashPayload(entry) {
161
+ const clone = { ...entry };
162
+ delete clone.entry_hash;
163
+ return clone;
164
+ }
165
+
166
+ function computeEntryHash(entry) {
167
+ return sha256Object(entryHashPayload(entry));
168
+ }
169
+
170
+ function computeEntriesRootHash(entries) {
171
+ return sha256Object(entries.map((entry) => entry.entry_hash));
172
+ }
173
+
174
+ function computeLedgerSnapshotHash(ledger) {
175
+ return sha256Object({
176
+ run_id: ledger.run_id,
177
+ ledger_id: ledger.ledger_id,
178
+ append_only: ledger.append_only,
179
+ entries_root_hash: ledger.entries_root_hash,
180
+ entry_count: ledger.entries.length,
181
+ previous_ledger_snapshot_hash: ledger.previous_ledger_snapshot_hash ?? null,
182
+ });
183
+ }
184
+
185
+ export function validateLedger(ledger) {
186
+ const errors = [];
187
+ if (!ledger || ledger.kind !== "sweep-design-revision-ledger") {
188
+ return ["revision ledger is missing or malformed"];
189
+ }
190
+ if (ledger.append_only !== true) errors.push("revision ledger append_only must be true");
191
+ if (!Array.isArray(ledger.entries)) errors.push("revision ledger entries must be an array");
192
+ const entries = Array.isArray(ledger.entries) ? ledger.entries : [];
193
+ let previous = ZERO_HASH;
194
+ entries.forEach((entry, index) => {
195
+ if (entry.entry_index !== index + 1) errors.push(`revision entry ${index + 1} has non-monotonic entry_index`);
196
+ if (entry.previous_entry_hash !== previous) errors.push(`revision entry ${entry.revision_entry_id ?? index + 1} has invalid previous_entry_hash`);
197
+ const actual = computeEntryHash(entry);
198
+ if (entry.entry_hash !== actual) errors.push(`revision entry ${entry.revision_entry_id ?? index + 1} has invalid entry_hash`);
199
+ previous = entry.entry_hash;
200
+ });
201
+ if (entries.length > 0 && ledger.entries_root_hash !== computeEntriesRootHash(entries)) {
202
+ errors.push("revision ledger entries_root_hash is invalid");
203
+ }
204
+ if (entries.length === 0 && ledger.entries_root_hash !== ZERO_HASH) {
205
+ errors.push("empty revision ledger entries_root_hash must be zero hash");
206
+ }
207
+ if (ledger.ledger_snapshot_hash !== computeLedgerSnapshotHash(ledger)) {
208
+ errors.push("revision ledger ledger_snapshot_hash is invalid");
209
+ }
210
+ return errors;
211
+ }
212
+
213
+ export async function loadOrCreateLedger(projectRoot, runId, timestamp) {
214
+ const existing = await loadYamlRef(projectRoot, ledgerRef(runId));
215
+ if (!existing) {
216
+ const ledger = initialLedger(runId, timestamp);
217
+ const ref = await writeYamlRef(projectRoot, ledgerRef(runId), ledger);
218
+ return { ok: true, ref, value: ledger };
219
+ }
220
+ const errors = validateLedger(existing);
221
+ if (errors.length > 0) {
222
+ return inputError(`nimicoding sweep design refused: ${errors.join("; ")}.\n`);
223
+ }
224
+ return { ok: true, ref: ledgerRef(runId), value: existing };
225
+ }
226
+
227
+ export function normalizeRevisionEntry(entry, context) {
228
+ const nextIndex = context.ledger.entries.length + context.normalized.length + 1;
229
+ const previousHash = context.normalized.length > 0
230
+ ? context.normalized[context.normalized.length - 1].entry_hash
231
+ : context.ledger.entries.at(-1)?.entry_hash ?? ZERO_HASH;
232
+ const revisionType = requireString(entry.revision_type, "revision_entries[].revision_type", context.errors);
233
+ if (revisionType && !REVISION_TYPES.has(revisionType)) {
234
+ context.errors.push(`unsupported revision_type ${revisionType}`);
235
+ }
236
+ const replacementArtifactRefs = Array.isArray(entry.replacement_artifact_refs) ? entry.replacement_artifact_refs : [];
237
+ if (!replacementArtifactRefs.includes(context.resultArtifactRef)) {
238
+ replacementArtifactRefs.push(context.resultArtifactRef);
239
+ }
240
+ const normalized = {
241
+ version: 1,
242
+ kind: "sweep-design-revision-entry",
243
+ revision_entry_id: entry.revision_entry_id ?? `${context.resultId}-revision-${String(nextIndex).padStart(4, "0")}`,
244
+ entry_index: nextIndex,
245
+ revision_type: revisionType,
246
+ created_at: entry.created_at ?? context.timestamp,
247
+ previous_entry_hash: previousHash,
248
+ previous_artifact_refs: Array.isArray(entry.previous_artifact_refs) ? entry.previous_artifact_refs : [],
249
+ replacement_artifact_refs: replacementArtifactRefs,
250
+ affected_finding_ids: Array.isArray(entry.affected_finding_ids) ? entry.affected_finding_ids : [],
251
+ affected_cluster_ids: Array.isArray(entry.affected_cluster_ids) ? entry.affected_cluster_ids : [],
252
+ affected_wave_ids: Array.isArray(entry.affected_wave_ids) ? entry.affected_wave_ids : [],
253
+ reason_code: requireString(entry.reason_code, "revision_entries[].reason_code", context.errors),
254
+ evidence_refs: Array.isArray(entry.evidence_refs) ? entry.evidence_refs : [],
255
+ auditor_provenance: entry.auditor_provenance ?? context.auditorProvenance,
256
+ human_gate_status: entry.human_gate_status ?? "not_required",
257
+ projection_refs_changed: Array.isArray(entry.projection_refs_changed) ? entry.projection_refs_changed : [],
258
+ };
259
+ normalized.entry_hash = computeEntryHash(normalized);
260
+ return normalized;
261
+ }
262
+
263
+ export function updateLedgerHashes(ledger, previousSnapshotHash, timestamp) {
264
+ ledger.previous_ledger_snapshot_hash = previousSnapshotHash ?? ledger.previous_ledger_snapshot_hash ?? null;
265
+ ledger.entries_root_hash = ledger.entries.length > 0 ? computeEntriesRootHash(ledger.entries) : ZERO_HASH;
266
+ ledger.updated_at = timestamp;
267
+ ledger.ledger_snapshot_hash = computeLedgerSnapshotHash(ledger);
268
+ return ledger;
269
+ }
270
+
271
+ export function finalOutcomeState(outcome) {
272
+ return outcome.final_outcome ?? outcome.state ?? outcome.outcome ?? null;
273
+ }
274
+
275
+ export function validateOutcome(outcome, context) {
276
+ const errors = context.errors;
277
+ const state = finalOutcomeState(outcome);
278
+ requireString(outcome.finding_id, "finding_outcomes[].finding_id", errors);
279
+ if (!FINAL_OUTCOME_STATES.has(state)) {
280
+ errors.push(`finding_outcomes[] has unsupported final outcome ${state}`);
281
+ return;
282
+ }
283
+ for (const field of [
284
+ "design_auditor_packet_ref",
285
+ "design_auditor_result_ref",
286
+ "revision_ledger_entry_refs",
287
+ "related_finding_ids_considered",
288
+ "code_refs_considered",
289
+ "authority_refs_considered",
290
+ ]) {
291
+ if (outcome[field] === undefined || outcome[field] === null) errors.push(`finding_outcomes[].${field} is required for ${state}`);
292
+ }
293
+ requireNonEmptyArray(outcome.revision_ledger_entry_refs, "finding_outcomes[].revision_ledger_entry_refs", errors);
294
+ requireArray(outcome.related_finding_ids_considered, "finding_outcomes[].related_finding_ids_considered", errors);
295
+ requireArray(outcome.code_refs_considered, "finding_outcomes[].code_refs_considered", errors);
296
+ requireArray(outcome.authority_refs_considered, "finding_outcomes[].authority_refs_considered", errors);
297
+ if (state === "duplicate" && !outcome.canonical_finding_or_cluster_ref) errors.push("duplicate outcome requires canonical_finding_or_cluster_ref");
298
+ if (state === "superseded" && !outcome.superseding_finding_or_cluster_ref) errors.push("superseded outcome requires superseding_finding_or_cluster_ref");
299
+ if (state === "false_positive" && !outcome.human_gate_ref) errors.push("false_positive outcome requires human_gate_ref");
300
+ if (state === "ready_for_implementation_wave") {
301
+ for (const field of ["wave_id_ref", "preflight_ref", "validation_command_refs", "closeout_criteria_ref"]) {
302
+ if (outcome[field] === undefined || outcome[field] === null) errors.push(`ready_for_implementation_wave outcome requires ${field}`);
303
+ }
304
+ requireNonEmptyArray(outcome.validation_command_refs, "ready_for_implementation_wave.validation_command_refs", errors);
305
+ }
306
+ if (state === "needs_user_decision") {
307
+ for (const field of ["decision_queue_item_ref", "decision_packet_ref", "recommended_decision", "queue_status", "blocked_downstream_wave_refs"]) {
308
+ if (outcome[field] === undefined || outcome[field] === null) errors.push(`needs_user_decision outcome requires ${field}`);
309
+ }
310
+ requireNonEmptyArray(outcome.blocked_downstream_wave_refs, "needs_user_decision.blocked_downstream_wave_refs", errors);
311
+ if (["accepted", "closed"].includes(outcome.queue_status) && !outcome.human_gate_decision_ref) {
312
+ errors.push("accepted or closed needs_user_decision requires human_gate_decision_ref");
313
+ }
314
+ }
315
+ if (state === "needs_more_audit" && !outcome.extra_audit_request_ref) errors.push("needs_more_audit outcome requires extra_audit_request_ref");
316
+ if (state === "needs_authority_alignment" && !outcome.authority_convergence_ref) errors.push("needs_authority_alignment outcome requires authority_convergence_ref");
317
+ if (state === "blocked") requireNonEmptyArray(outcome.blocking_cause_refs, "blocked.blocking_cause_refs", errors);
318
+ }
319
+
320
+ export function validateAuditorResult(result) {
321
+ const errors = [];
322
+ if (!result || result.kind !== "sweep-design-design-auditor-result") {
323
+ errors.push("result kind must be sweep-design-design-auditor-result");
324
+ return errors;
325
+ }
326
+ validateRequiredFields(result, [
327
+ "run_id",
328
+ "packet_id",
329
+ "result_id",
330
+ "auditor",
331
+ "auditor_family",
332
+ "auditor_mode",
333
+ "auditor_result_origin",
334
+ "methodology_ref",
335
+ "packet_ref",
336
+ "session_ref",
337
+ "transcript_ref",
338
+ "result_schema_version",
339
+ "provenance",
340
+ "evidence_read",
341
+ "finding_outcomes",
342
+ "cluster_changes",
343
+ "wave_changes",
344
+ "revision_entries",
345
+ "human_decision_requests",
346
+ "extra_audit_requests",
347
+ "validation_recommendations",
348
+ "closeout_recommendations",
349
+ "rejection_status",
350
+ ], "result", errors);
351
+ if (!AUDITOR_FAMILIES.has(result.auditor_family)) errors.push(`unsupported auditor_family ${result.auditor_family}`);
352
+ if (!AUDITOR_MODES.has(result.auditor_mode)) errors.push(`unsupported auditor_mode ${result.auditor_mode}`);
353
+ if (!AUDITOR_RESULT_ORIGINS.has(result.auditor_result_origin)) errors.push(`unsupported auditor_result_origin ${result.auditor_result_origin}`);
354
+ if (LLM_AUDITOR_RESULT_ORIGINS.has(result.auditor_result_origin)) {
355
+ for (const field of ["llm_session_ref", "llm_transcript_ref", "llm_prompt_ref"]) {
356
+ requireString(result[field], `result.${field}`, errors);
357
+ }
358
+ }
359
+ for (const field of [
360
+ "evidence_read",
361
+ "finding_outcomes",
362
+ "cluster_changes",
363
+ "wave_changes",
364
+ "revision_entries",
365
+ "human_decision_requests",
366
+ "extra_audit_requests",
367
+ "validation_recommendations",
368
+ "closeout_recommendations",
369
+ ]) {
370
+ requireArray(result[field], `result.${field}`, errors);
371
+ }
372
+ return errors;
373
+ }
374
+
375
+ export function isLlmAuditorResult(result) {
376
+ return LLM_AUDITOR_RESULT_ORIGINS.has(result?.auditor_result_origin);
377
+ }
378
+
379
+ export function isSyntheticAuditorResult(result) {
380
+ return result?.auditor_result_origin === "synthetic_trial";
381
+ }
382
+
383
+ export function validatePacket(packet) {
384
+ const errors = [];
385
+ validateRequiredFields(packet, [
386
+ "run_id",
387
+ "packet_id",
388
+ "source_audit_sweep_id",
389
+ "included_finding_ids",
390
+ "source_finding_refs",
391
+ "related_finding_refs",
392
+ "related_code_refs",
393
+ "authority_refs",
394
+ "prior_design_state_refs",
395
+ "prior_design_state_marker",
396
+ "revision_ledger_refs",
397
+ "current_cluster_refs",
398
+ "current_wave_refs",
399
+ "explicit_questions",
400
+ "expected_result_shape_ref",
401
+ "evidence_gap_policy",
402
+ "stop_conditions",
403
+ ], "packet", errors);
404
+ requireNonEmptyArray(packet.included_finding_ids, "packet.included_finding_ids", errors);
405
+ requireNonEmptyArray(packet.source_finding_refs, "packet.source_finding_refs", errors);
406
+ if (!packet.authority_only_packet) requireNonEmptyArray(packet.related_code_refs, "packet.related_code_refs", errors);
407
+ requireArray(packet.authority_refs, "packet.authority_refs", errors);
408
+ requireArray(packet.related_finding_refs, "packet.related_finding_refs", errors);
409
+ if (!PRIOR_DESIGN_STATE_MARKERS.has(packet.prior_design_state_marker)) {
410
+ errors.push(`unsupported prior_design_state_marker ${packet.prior_design_state_marker}`);
411
+ }
412
+ if (packet.prior_design_state_marker !== "empty" && (!Array.isArray(packet.prior_design_state_refs) || packet.prior_design_state_refs.length === 0)) {
413
+ errors.push("non-empty prior_design_state_marker requires prior_design_state_refs");
414
+ }
415
+ if (packet.prior_design_state_marker === "empty" && Array.isArray(packet.prior_design_state_refs) && packet.prior_design_state_refs.length > 0) {
416
+ errors.push("prior_design_state_marker empty requires no prior_design_state_refs");
417
+ }
418
+ if (Array.isArray(packet.evidence_gaps) && packet.evidence_gaps.length > 0 && !packet.evidence_gap_result) {
419
+ errors.push("material evidence gaps require evidence_gap_result");
420
+ }
421
+ return errors;
422
+ }
423
+
424
+ async function ensureRefAbsent(projectRoot, ref, label) {
425
+ const existing = await loadYamlRef(projectRoot, ref);
426
+ if (existing) {
427
+ return inputError(`nimicoding sweep design refused: ${label} already exists at ${ref}.\n`);
428
+ }
429
+ return { ok: true };
430
+ }
431
+
432
+ function buildPacketValue(inventory, ledger, runId, packetId, includedFindingIds, options, timestamp) {
433
+ const selected = findInventoryFindings(inventory, includedFindingIds);
434
+ if (selected.length !== includedFindingIds.length) {
435
+ const found = new Set(selected.map((finding) => finding.finding_id));
436
+ const missing = includedFindingIds.filter((findingId) => !found.has(findingId));
437
+ return { ok: false, error: `nimicoding sweep design refused: finding not found: ${missing.join(", ")}.\n` };
438
+ }
439
+ const relatedCodeRefs = [...new Set(selected.flatMap((finding) => findingCodeRefs({
440
+ ...finding,
441
+ implementation_refs: finding.evidence_refs,
442
+ })))].sort();
443
+ const authorityRefs = [...new Set(selected.map((finding) => finding.authority_ref).filter(Boolean))].sort();
444
+ const priorDesignStateRefs = splitList(options.priorDesignStateRefs);
445
+ const priorDesignStateMarker = options.priorDesignStateMarker ?? (priorDesignStateRefs.length > 0 ? "present" : "empty");
446
+ const evidenceGaps = [];
447
+ if (!options.authorityOnly && relatedCodeRefs.length === 0) evidenceGaps.push("related_code_refs_missing");
448
+ const packet = {
449
+ version: 2,
450
+ kind: "sweep-design-design-auditor-packet",
451
+ run_id: runId,
452
+ packet_id: packetId,
453
+ source_audit_sweep_id: inventory.source_audit_sweep_id,
454
+ included_finding_ids: includedFindingIds,
455
+ source_finding_refs: selected.map((finding) => finding.source_finding_ref),
456
+ related_finding_refs: relatedFindingCandidates(inventory, includedFindingIds),
457
+ related_code_refs: relatedCodeRefs,
458
+ authority_refs: authorityRefs,
459
+ authority_only_packet: Boolean(options.authorityOnly),
460
+ prior_design_state_refs: priorDesignStateRefs,
461
+ prior_design_state_marker: priorDesignStateMarker,
462
+ revision_ledger_refs: [ledger.ref],
463
+ current_cluster_refs: splitList(options.currentClusterRefs),
464
+ current_wave_refs: splitList(options.currentWaveRefs),
465
+ explicit_questions: splitList(options.explicitQuestions || options.explicitQuestion),
466
+ expected_result_shape_ref: ".nimi/contracts/sweep-design-result.yaml#artifact_kinds.design_auditor_result",
467
+ evidence_gap_policy: "missing_material_input_requires_explicit_gap_result_or_refusal",
468
+ evidence_gaps: evidenceGaps,
469
+ evidence_gap_result: evidenceGaps.length > 0 ? "blocked_until_material_input_supplied" : null,
470
+ stop_conditions: [
471
+ "product_fork",
472
+ "authority_fork",
473
+ "semantic_fork",
474
+ "missing_evidence",
475
+ "extra_audit_required",
476
+ "authority_alignment_required",
477
+ "human_decision_required",
478
+ ],
479
+ created_at: timestamp,
480
+ };
481
+ const errors = validatePacket(packet);
482
+ if (errors.length > 0) return { ok: false, error: `nimicoding sweep design refused: ${errors.join("; ")}.\n` };
483
+ return { ok: true, packet, selected };
484
+ }
485
+
486
+ function buildAuditorPromptValue(runId, packetId, packetRefValue, packet, timestamp) {
487
+ return {
488
+ version: 2,
489
+ kind: "sweep-design-auditor-prompt",
490
+ run_id: runId,
491
+ packet_id: packetId,
492
+ packet_ref: packetRefValue,
493
+ expected_result_shape_ref: ".nimi/contracts/sweep-design-result.yaml#artifact_kinds.design_auditor_result",
494
+ required_result_origin: "external_llm_session",
495
+ synthetic_result_policy: "synthetic_trial_results_are_load_tests_only_and_do_not_satisfy_true_llm_closeout",
496
+ required_llm_provenance_fields: [
497
+ "auditor_result_origin",
498
+ "llm_session_ref",
499
+ "llm_transcript_ref",
500
+ "llm_prompt_ref",
501
+ ],
502
+ task: [
503
+ "Read every included source finding, related code ref, authority ref, related finding candidate, prior design state ref, current cluster ref, and current wave ref in the packet.",
504
+ "Return a sweep-design-design-auditor-result with final outcomes for every included finding.",
505
+ "Queue product, semantic, or authority forks instead of accepting them.",
506
+ "Use needs_authority_alignment when implementation cannot safely proceed until authority ownership is aligned.",
507
+ "Emit implementation-ready waves only when scope, authority owner, validation commands, negative checks, closeout criteria, and provenance are concrete.",
508
+ "Do not mutate source audit findings.",
509
+ ],
510
+ packet_summary: {
511
+ included_finding_count: packet.included_finding_ids.length,
512
+ source_finding_refs: packet.source_finding_refs,
513
+ related_code_refs: packet.related_code_refs,
514
+ authority_refs: packet.authority_refs,
515
+ related_finding_refs: packet.related_finding_refs,
516
+ prior_design_state_refs: packet.prior_design_state_refs,
517
+ current_cluster_refs: packet.current_cluster_refs,
518
+ current_wave_refs: packet.current_wave_refs,
519
+ explicit_questions: packet.explicit_questions,
520
+ },
521
+ created_at: timestamp,
522
+ };
523
+ }
524
+
525
+ function waveIncludedCount(wave) {
526
+ const counts = [
527
+ Array.isArray(wave.finding_ids) ? wave.finding_ids.length : 0,
528
+ Array.isArray(wave.merged_cluster_ids) ? wave.merged_cluster_ids.length : 0,
529
+ ];
530
+ return Math.max(...counts);
531
+ }
532
+
533
+ export function validateWave(wave, errors) {
534
+ for (const field of [
535
+ "wave_id",
536
+ "scope",
537
+ "owner_domain",
538
+ "authority_owner",
539
+ "dependencies",
540
+ "preflight_ref",
541
+ "non_goals",
542
+ "validation_commands",
543
+ "negative_checks",
544
+ "drift_resistance_checks",
545
+ "closeout_criteria",
546
+ "source_design_packet_refs",
547
+ "design_auditor_result_refs",
548
+ "revision_ledger_entry_refs",
549
+ "blocked_gate_refs",
550
+ "merged_cluster_ids",
551
+ "merged_root_cause_keys",
552
+ ]) {
553
+ if (wave[field] === undefined || wave[field] === null) errors.push(`wave.${field} is required`);
554
+ }
555
+ for (const field of ["validation_commands", "negative_checks", "drift_resistance_checks", "closeout_criteria", "source_design_packet_refs", "design_auditor_result_refs", "revision_ledger_entry_refs"]) {
556
+ requireNonEmptyArray(wave[field], `wave.${field}`, errors);
557
+ }
558
+ const count = waveIncludedCount(wave);
559
+ if (count > 1 && !wave.consolidation_rationale) errors.push("multi-finding or multi-cluster wave requires consolidation_rationale");
560
+ if (count <= 1 && !wave.isolation_justification) errors.push("single-finding or single-cluster wave requires isolation_justification");
561
+ const requiredAuthorityRefs = sweepDesignWaveAuthorityRefs(wave);
562
+ const declaredAuthorityOwners = new Set(typeof wave.authority_owner === "string"
563
+ ? [wave.authority_owner]
564
+ : Array.isArray(wave.authority_owner)
565
+ ? wave.authority_owner
566
+ : []);
567
+ const missingAuthorityRefs = requiredAuthorityRefs.filter((ref) => !declaredAuthorityOwners.has(ref));
568
+ if (missingAuthorityRefs.length > 0) {
569
+ errors.push(`wave.authority_owner must cover source authority refs: ${missingAuthorityRefs.join(", ")}`);
570
+ }
571
+ }
572
+
573
+ export function stopClassForResult(result) {
574
+ const outcomes = result.finding_outcomes ?? [];
575
+ if (outcomes.some((outcome) => finalOutcomeState(outcome) === "needs_user_decision") || (result.human_decision_requests ?? []).length > 0) {
576
+ return "require_human_confirmation";
577
+ }
578
+ if (outcomes.some((outcome) => finalOutcomeState(outcome) === "needs_authority_alignment")) {
579
+ return "authority_alignment_required";
580
+ }
581
+ if (outcomes.some((outcome) => finalOutcomeState(outcome) === "needs_more_audit") || (result.extra_audit_requests ?? []).length > 0) {
582
+ return "extra_audit_required";
583
+ }
584
+ if (outcomes.some((outcome) => finalOutcomeState(outcome) === "blocked")) {
585
+ return "blocked";
586
+ }
587
+ return null;
588
+ }
589
+
590
+ export async function runIntake(projectRoot, options) {
591
+ const sweepId = safeDesignId(options.sweepId);
592
+ if (!sweepId) return inputError("nimicoding sweep design refused: --sweep-id must be a stable id.\n");
593
+ const audit = await loadAuditFindings(projectRoot, sweepId);
594
+ if (!audit.ok) return audit;
595
+ const runId = options.runId ? safeDesignId(options.runId) : deriveRunId(sweepId);
596
+ if (!runId) return inputError("nimicoding sweep design refused: --run-id must be a stable id.\n");
597
+ const timestamp = options.verifiedAt ?? nowIso();
598
+ const findings = audit.store.findings.map((finding) => normalizeFindingForDesign(finding, audit.ref));
599
+ const inventory = {
600
+ version: 2,
601
+ kind: "sweep-design-inventory",
602
+ run_id: runId,
603
+ artifact_role: "forked_design_workset",
604
+ source_audit_sweep_id: sweepId,
605
+ source_findings_ref: audit.ref,
606
+ source_findings_sha256: audit.sourceSha256,
607
+ source_findings_mutation_policy: "read_only_never_update_from_sweep_design",
608
+ design_judgement_policy: "llm_auditor_result_required_for_final_outcomes",
609
+ finding_count: findings.length,
610
+ findings,
611
+ created_at: timestamp,
612
+ updated_at: timestamp,
613
+ };
614
+ const ref = await writeYamlRef(projectRoot, inventoryRef(runId), inventory);
615
+ await loadOrCreateLedger(projectRoot, runId, timestamp);
616
+ return { ok: true, exitCode: 0, runId, sourceAuditSweepId: sweepId, inventoryRef: ref, ledgerRef: ledgerRef(runId), findingCount: findings.length };
617
+ }
618
+
619
+ export async function runPacketBuild(projectRoot, options) {
620
+ const run = requireRunId(options);
621
+ if (!run.ok) return run;
622
+ const packetId = safeDesignId(options.packetId);
623
+ if (!packetId) return inputError("nimicoding sweep design refused: --packet-id must be a stable id.\n");
624
+ const includedFindingIds = normalizeIdList(options.findingIds || options.findingId);
625
+ if (includedFindingIds.length === 0) {
626
+ return inputError("nimicoding sweep design refused: packet-build requires --finding-id or --finding-ids.\n");
627
+ }
628
+ const inventory = await loadInventory(projectRoot, run.runId);
629
+ if (!inventory.ok) return inventory;
630
+ const ledger = await loadOrCreateLedger(projectRoot, run.runId, options.verifiedAt ?? nowIso());
631
+ if (!ledger.ok) return ledger;
632
+ const built = buildPacketValue(inventory.value, ledger, run.runId, packetId, includedFindingIds, options, options.verifiedAt ?? nowIso());
633
+ if (!built.ok) return inputError(built.error);
634
+ const { packet } = built;
635
+ const ref = await writeYamlRef(projectRoot, packetRef(run.runId, packetId), packet);
636
+ return { ok: true, exitCode: 0, runId: run.runId, packetRef: ref, packetId, findingCount: includedFindingIds.length };
637
+ }
638
+
639
+ export async function runPacketBuildBatch(projectRoot, options) {
640
+ const run = requireRunId(options);
641
+ if (!run.ok) return run;
642
+ const batchSize = parsePositiveInteger(options.batchSize, "--batch-size");
643
+ if (!batchSize.ok) return inputError(batchSize.error);
644
+ const inventory = await loadInventory(projectRoot, run.runId);
645
+ if (!inventory.ok) return inventory;
646
+ const timestamp = options.verifiedAt ?? nowIso();
647
+ const ledger = await loadOrCreateLedger(projectRoot, run.runId, timestamp);
648
+ if (!ledger.ok) return ledger;
649
+ const selectedFindingIds = normalizeIdList(options.findingIds)
650
+ || inventory.value.findings.map((finding) => finding.finding_id).sort();
651
+ const ids = selectedFindingIds.length > 0 ? selectedFindingIds : inventory.value.findings.map((finding) => finding.finding_id).sort();
652
+ const found = new Set(inventory.value.findings.map((finding) => finding.finding_id));
653
+ const missing = ids.filter((findingId) => !found.has(findingId));
654
+ if (missing.length > 0) return inputError(`nimicoding sweep design refused: finding not found: ${missing.join(", ")}.\n`);
655
+ const packetPrefix = options.packetPrefix || "packet";
656
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{1,80}$/.test(packetPrefix)) {
657
+ return inputError("nimicoding sweep design refused: --packet-prefix must be a stable id prefix.\n");
658
+ }
659
+ const manifestId = options.manifestId || `${packetPrefix}-manifest`;
660
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{2,120}$/.test(manifestId)) {
661
+ return inputError("nimicoding sweep design refused: --manifest-id must be a stable artifact id.\n");
662
+ }
663
+ const manifestRef = batchManifestRef(run.runId, manifestId);
664
+ const manifestAbsent = await ensureRefAbsent(projectRoot, manifestRef, "batch manifest");
665
+ if (!manifestAbsent.ok) return manifestAbsent;
666
+ const batches = chunkArray(ids, batchSize.value);
667
+ const packetEntries = [];
668
+ for (const [index, findingIds] of batches.entries()) {
669
+ const packetId = `${packetPrefix}-${String(index + 1).padStart(4, "0")}`;
670
+ const packetArtifactRef = packetRef(run.runId, packetId);
671
+ const promptArtifactRef = auditorPromptRef(run.runId, packetId);
672
+ for (const [ref, label] of [[packetArtifactRef, "design auditor packet"], [promptArtifactRef, "auditor prompt"]]) {
673
+ const absent = await ensureRefAbsent(projectRoot, ref, label);
674
+ if (!absent.ok) return absent;
675
+ }
676
+ const built = buildPacketValue(inventory.value, ledger, run.runId, packetId, findingIds, options, timestamp);
677
+ if (!built.ok) return inputError(built.error);
678
+ await writeYamlRef(projectRoot, packetArtifactRef, built.packet);
679
+ await writeYamlRef(projectRoot, promptArtifactRef, buildAuditorPromptValue(run.runId, packetId, packetArtifactRef, built.packet, timestamp));
680
+ packetEntries.push({
681
+ packet_id: packetId,
682
+ packet_ref: packetArtifactRef,
683
+ prompt_ref: promptArtifactRef,
684
+ finding_ids: findingIds,
685
+ source_finding_refs: built.packet.source_finding_refs,
686
+ });
687
+ }
688
+ const manifest = {
689
+ version: 2,
690
+ kind: "sweep-design-batch-manifest",
691
+ run_id: run.runId,
692
+ manifest_id: manifestId,
693
+ source_inventory_ref: inventory.ref,
694
+ source_audit_sweep_id: inventory.value.source_audit_sweep_id,
695
+ source_findings_ref: inventory.value.source_findings_ref,
696
+ source_findings_sha256: inventory.value.source_findings_sha256,
697
+ source_findings_mutation_policy: "read_only_never_update_from_sweep_design",
698
+ batch_policy: "deterministic_inventory_order_chunking",
699
+ batch_size: batchSize.value,
700
+ packet_prefix: packetPrefix,
701
+ selected_finding_count: ids.length,
702
+ packet_count: packetEntries.length,
703
+ packets: packetEntries,
704
+ generated_artifact_policy: "packets_and_prompts_only_no_auditor_results",
705
+ true_llm_closeout_policy: "requires_external_llm_session_or_llm_session_results_ingested_later",
706
+ created_at: timestamp,
707
+ };
708
+ const ref = await writeYamlRef(projectRoot, manifestRef, manifest);
709
+ return {
710
+ ok: true,
711
+ exitCode: 0,
712
+ runId: run.runId,
713
+ manifestRef: ref,
714
+ packetCount: packetEntries.length,
715
+ promptCount: packetEntries.length,
716
+ findingCount: ids.length,
717
+ sourceFindingsSha256: inventory.value.source_findings_sha256,
718
+ };
719
+ }
720
+
721
+ export async function runAuditorPrompt(projectRoot, options) {
722
+ const run = requireRunId(options);
723
+ if (!run.ok) return run;
724
+ const packetId = safeDesignId(options.packetId);
725
+ if (!packetId) return inputError("nimicoding sweep design refused: --packet-id must be a stable id.\n");
726
+ const packet = await assertDesignArtifact(projectRoot, packetRef(run.runId, packetId), "sweep-design-design-auditor-packet", "design auditor packet");
727
+ if (!packet.ok) return packet;
728
+ const packetErrors = validatePacket(packet.value);
729
+ if (packetErrors.length > 0) return inputError(`nimicoding sweep design refused: ${packetErrors.join("; ")}.\n`);
730
+ const prompt = buildAuditorPromptValue(run.runId, packetId, packet.ref, packet.value, options.verifiedAt ?? nowIso());
731
+ const ref = await writeYamlRef(projectRoot, auditorPromptRef(run.runId, packetId), prompt);
732
+ return { ok: true, exitCode: 0, runId: run.runId, packetId, promptRef: ref, findingCount: packet.value.included_finding_ids.length };
733
+ }