audit-tools 0.30.3 → 0.30.4

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 (90) hide show
  1. package/dist/audit/cli/auditStep.d.ts.map +1 -1
  2. package/dist/audit/cli/auditStep.js +24 -13
  3. package/dist/audit/cli/auditStep.js.map +1 -1
  4. package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
  5. package/dist/audit/cli/nextStepHelpers.js +11 -6
  6. package/dist/audit/cli/nextStepHelpers.js.map +1 -1
  7. package/dist/audit/cli/reviewRun.d.ts.map +1 -1
  8. package/dist/audit/cli/reviewRun.js +9 -4
  9. package/dist/audit/cli/reviewRun.js.map +1 -1
  10. package/dist/audit/contracts/workerSchemas.d.ts +22 -4
  11. package/dist/audit/contracts/workerSchemas.d.ts.map +1 -1
  12. package/dist/audit/contracts/wrapperResponse.d.ts +12 -12
  13. package/dist/audit/orchestrator/artifactFreshness.d.ts +2 -1
  14. package/dist/audit/orchestrator/artifactFreshness.d.ts.map +1 -1
  15. package/dist/audit/orchestrator/artifactFreshness.js +5 -15
  16. package/dist/audit/orchestrator/artifactFreshness.js.map +1 -1
  17. package/dist/audit/orchestrator/executorRunners.d.ts.map +1 -1
  18. package/dist/audit/orchestrator/executorRunners.js +10 -9
  19. package/dist/audit/orchestrator/executorRunners.js.map +1 -1
  20. package/dist/audit/orchestrator/ingestionExecutors.d.ts.map +1 -1
  21. package/dist/audit/orchestrator/ingestionExecutors.js +5 -1
  22. package/dist/audit/orchestrator/ingestionExecutors.js.map +1 -1
  23. package/dist/audit/orchestrator/intentCheckpointGate.d.ts +129 -0
  24. package/dist/audit/orchestrator/intentCheckpointGate.d.ts.map +1 -0
  25. package/dist/audit/orchestrator/intentCheckpointGate.js +193 -0
  26. package/dist/audit/orchestrator/intentCheckpointGate.js.map +1 -0
  27. package/dist/audit/orchestrator/ledger.d.ts +30 -0
  28. package/dist/audit/orchestrator/ledger.d.ts.map +1 -0
  29. package/dist/audit/orchestrator/ledger.js +126 -0
  30. package/dist/audit/orchestrator/ledger.js.map +1 -0
  31. package/dist/audit/orchestrator/nextStep.d.ts +11 -29
  32. package/dist/audit/orchestrator/nextStep.d.ts.map +1 -1
  33. package/dist/audit/orchestrator/nextStep.js +11 -29
  34. package/dist/audit/orchestrator/nextStep.js.map +1 -1
  35. package/dist/audit/orchestrator/resultBaseline.d.ts +82 -0
  36. package/dist/audit/orchestrator/resultBaseline.d.ts.map +1 -0
  37. package/dist/audit/orchestrator/resultBaseline.js +79 -0
  38. package/dist/audit/orchestrator/resultBaseline.js.map +1 -0
  39. package/dist/audit/quota/index.d.ts +12 -12
  40. package/dist/audit/types/artifactMetadata.d.ts +9 -0
  41. package/dist/audit/types/artifactMetadata.d.ts.map +1 -1
  42. package/dist/audit/types.d.ts +13 -4
  43. package/dist/audit/types.d.ts.map +1 -1
  44. package/dist/audit/types.js +8 -0
  45. package/dist/audit/types.js.map +1 -1
  46. package/dist/remediate/steps/nextStep.d.ts +11 -23
  47. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  48. package/dist/remediate/steps/nextStep.js +45 -43
  49. package/dist/remediate/steps/nextStep.js.map +1 -1
  50. package/dist/shared/contentKey.d.ts +83 -0
  51. package/dist/shared/contentKey.d.ts.map +1 -0
  52. package/dist/shared/contentKey.js +163 -0
  53. package/dist/shared/contentKey.js.map +1 -0
  54. package/dist/shared/friction/captureFrictionEvent.d.ts +50 -0
  55. package/dist/shared/friction/captureFrictionEvent.d.ts.map +1 -0
  56. package/dist/shared/friction/captureFrictionEvent.js +34 -0
  57. package/dist/shared/friction/captureFrictionEvent.js.map +1 -0
  58. package/dist/shared/friction/triage.d.ts +126 -0
  59. package/dist/shared/friction/triage.d.ts.map +1 -0
  60. package/dist/shared/friction/triage.js +149 -0
  61. package/dist/shared/friction/triage.js.map +1 -0
  62. package/dist/shared/index.d.ts +11 -2
  63. package/dist/shared/index.d.ts.map +1 -1
  64. package/dist/shared/index.js +8 -2
  65. package/dist/shared/index.js.map +1 -1
  66. package/dist/shared/io/auditToolsPaths.d.ts +9 -0
  67. package/dist/shared/io/auditToolsPaths.d.ts.map +1 -1
  68. package/dist/shared/io/auditToolsPaths.js +11 -0
  69. package/dist/shared/io/auditToolsPaths.js.map +1 -1
  70. package/dist/shared/io/frictionCapture.d.ts +2 -0
  71. package/dist/shared/io/frictionCapture.d.ts.map +1 -1
  72. package/dist/shared/io/frictionCapture.js +1 -1
  73. package/dist/shared/io/frictionCapture.js.map +1 -1
  74. package/dist/shared/quota/capacity.d.ts +4 -4
  75. package/dist/shared/quota/quotaSource.d.ts +2 -2
  76. package/dist/shared/repair/emitValidateRepair.d.ts +124 -0
  77. package/dist/shared/repair/emitValidateRepair.d.ts.map +1 -0
  78. package/dist/shared/repair/emitValidateRepair.js +179 -0
  79. package/dist/shared/repair/emitValidateRepair.js.map +1 -0
  80. package/dist/shared/repair/index.d.ts +8 -0
  81. package/dist/shared/repair/index.d.ts.map +1 -0
  82. package/dist/shared/repair/index.js +2 -0
  83. package/dist/shared/repair/index.js.map +1 -0
  84. package/dist/shared/stableStringify.d.ts +14 -0
  85. package/dist/shared/stableStringify.d.ts.map +1 -0
  86. package/dist/shared/stableStringify.js +28 -0
  87. package/dist/shared/stableStringify.js.map +1 -0
  88. package/package.json +1 -1
  89. package/schemas/audit_result.schema.json +9 -0
  90. package/schemas/audit_results.schema.json +9 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The shared content-key seam (O2 ↔ F1) — the SOLE definition of the workflow's
3
+ * derived keys (INV-CK-1). O2's append-only results ledger (re-association +
4
+ * append-time idempotency) and F1's element-staleness gate both import their keys
5
+ * from HERE, so the two cannot drift.
6
+ *
7
+ * THREE derived keys, one canonical-input derivation chain (OBL-content-key-seam-contract):
8
+ *
9
+ * - `identityKey` (INV-CK-3 / INV-CK-4): sha256 over the canonical
10
+ * {unit_id, lens, pass_id} tuple. EXCLUDES task_id and every volatile/provenance
11
+ * field (INV-CK-3). It is a ONE-TO-MANY GROUPING key (INV-CK-4 / CE-001) used to
12
+ * re-associate ledger records — NEVER a primary key. `{unit_id, lens, pass_id}` is
13
+ * not unique within a pass (base vs. deepening/steward, or an O3 re-dispatch), so
14
+ * multiple distinct results legitimately share one identityKey.
15
+ *
16
+ * - `idempotencyKey` (INV-CK-7, signature-STABLE): sha256 over
17
+ * {identity_key, result_content_discriminator}. It is the anchor O2 ingests on:
18
+ * a replay of the *same* logical result is a no-op (CE-001b / fail-4), because a
19
+ * benign edit to the task content (which only moves the task_content_signature)
20
+ * does NOT move the idempotencyKey. Two genuinely-different same-coordinate
21
+ * results carry distinct discriminators → distinct idempotencyKeys → both persist
22
+ * (CE-001). This is signature-STABLE by construction: task_content_signature is
23
+ * not one of its inputs.
24
+ *
25
+ * - `contentKey` (INV-CK-5, signature-SENSITIVE): sha256 over
26
+ * {idempotency_key, task_content_signature}. It BUMPS when the idempotencyKey OR
27
+ * the task_content_signature changes, so it is the correct driver for staleness:
28
+ * a benign content edit (idempotencyKey fixed, signature C1→C2) bumps ONLY the
29
+ * contentKey (fail-4), letting idempotent re-ingest stay a no-op while staleness
30
+ * still fires.
31
+ *
32
+ * - `newInstanceId`: a per-record unique id (INV-CK / CE-001). The ledger is keyed
33
+ * by this — every appended record is distinct — so identityKey/idempotencyKey are
34
+ * grouping/idempotency keys, never primary keys (fail-2). This is the ONLY
35
+ * non-pure helper here (it mints randomness); the three key derivations above are
36
+ * pure and deterministic (INV-CK-2).
37
+ *
38
+ * Tool-owned inputs (never caller discretion):
39
+ * - `buildTaskContentSignature` (FC-002 / fail-1): the signature recipe lives HERE.
40
+ * Derived ONLY from task-defining content; `task_id` and timestamp/provenance
41
+ * fields are STRIPPED — a caller-built signature is a false-fresh latent bug.
42
+ * - `buildResultContentDiscriminator` (C-002 / fail-3): the discriminator recipe
43
+ * lives HERE too, keyed off the emit-source enum (base | deepening | steward |
44
+ * redispatch). Because the discriminator feeds the signature-STABLE
45
+ * idempotencyKey, a caller-chosen discriminator is a correctness hazard (a wrongly
46
+ * identical one collapses two distinct results; a wrongly distinct one on a replay
47
+ * mints a duplicate). The recipe is owned, never operator-chosen.
48
+ *
49
+ * Relating invariant (INV-CK-6, OBL-content-key-seam-inv-7/inv-8):
50
+ * equal contentKey ⟹ equal idempotencyKey ⟹ equal identityKey
51
+ * (NOT the converses), and `idempotencyKey` is invariant under a change to
52
+ * `task_content_signature` alone. The chain holds because each key nests the prior
53
+ * one as a signed input: contentKey signs idempotency_key, which signs identity_key.
54
+ * One canonical input thus yields all three keys with this documented relation —
55
+ * re-association stability (idempotencyKey) and staleness-on-change (contentKey) are
56
+ * reconciled, not in tension.
57
+ */
58
+ import { createHash, randomUUID } from "node:crypto";
59
+ import { stableStringify } from "./stableStringify.js";
60
+ import { normalizeForMetadataHash } from "../audit/orchestrator/artifactFreshness.js";
61
+ function sha256(value) {
62
+ return createHash('sha256').update(value, 'utf8').digest('hex');
63
+ }
64
+ // Provenance / non-content fields stripped before signing. Renumbering a task,
65
+ // or restamping it, must not change the task-content signature (FC-002). The
66
+ // artifact name routes through the shared normalizeForMetadataHash so the same
67
+ // non-semantic-stripping discipline is reused (INV-CK-2), never re-implemented.
68
+ const TASK_SIGNATURE_ARTIFACT = 'task_content_signature';
69
+ const NON_CONTENT_SIGNATURE_FIELDS = new Set([
70
+ 'task_id',
71
+ 'generated_at',
72
+ 'created_at',
73
+ 'updated_at',
74
+ 'timestamp',
75
+ 'provenance',
76
+ ]);
77
+ /**
78
+ * TOOL-OWNED task-content signature (FC-002 / fail-1). Derives a stable signature
79
+ * from the task-defining content ONLY — `task_id` and timestamp/provenance fields
80
+ * are stripped, then the remaining fields are run through the shared
81
+ * `normalizeForMetadataHash` + canonical `stableStringify` (the single serializer,
82
+ * INV-CK-2), so reordered keys produce an identical signature.
83
+ */
84
+ export function buildTaskContentSignature(input) {
85
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
86
+ throw new Error('buildTaskContentSignature: input must be a plain object of task-defining content');
87
+ }
88
+ const content = Object.fromEntries(Object.entries(input).filter(([key]) => !NON_CONTENT_SIGNATURE_FIELDS.has(key)));
89
+ return sha256(stableStringify(normalizeForMetadataHash(TASK_SIGNATURE_ARTIFACT, content)));
90
+ }
91
+ /**
92
+ * TOOL-OWNED result-content discriminator (C-002 / fail-3). The discriminator
93
+ * string is derived from the emit-source enum (plus the attempt counter for a
94
+ * re-dispatch), never chosen by a caller — because it feeds the signature-STABLE
95
+ * idempotencyKey, an operator-chosen value would be a correctness hazard.
96
+ */
97
+ export function buildResultContentDiscriminator(input) {
98
+ const source = input?.source;
99
+ if (source === 'base' || source === 'deepening' || source === 'steward') {
100
+ return source;
101
+ }
102
+ if (source === 'redispatch') {
103
+ const attempt = input.attempt;
104
+ if (!Number.isInteger(attempt) || attempt < 1) {
105
+ // Without a distinct attempt counter two re-dispatches would collide
106
+ // (fail-3) — refuse rather than mint a colliding discriminator.
107
+ throw new Error("buildResultContentDiscriminator: source 'redispatch' requires an integer attempt >= 1");
108
+ }
109
+ return `redispatch-attempt-${attempt}`;
110
+ }
111
+ throw new Error(`buildResultContentDiscriminator: unknown emit source ${JSON.stringify(source)}`);
112
+ }
113
+ function requireField(value, name) {
114
+ if (typeof value !== 'string' || value.length === 0) {
115
+ // No silent colliding key (fail-3): a missing component would otherwise hash
116
+ // to the same key for distinct coordinates.
117
+ throw new Error(`contentKey: ${name} is required and must be a non-empty string`);
118
+ }
119
+ return value;
120
+ }
121
+ /**
122
+ * sha256 over the canonical {unit_id, lens, pass_id} tuple (INV-CK-3). EXCLUDES
123
+ * task_id and all volatile fields. ONE-TO-MANY grouping key (INV-CK-4) — never a
124
+ * primary key. Throws if any component is missing (fail-3).
125
+ */
126
+ export function identityKey(input) {
127
+ const unit_id = requireField(input?.unit_id, 'unit_id');
128
+ const lens = requireField(input?.lens, 'lens');
129
+ const pass_id = requireField(input?.pass_id, 'pass_id');
130
+ return sha256(stableStringify({ unit_id, lens, pass_id }));
131
+ }
132
+ /**
133
+ * sha256 over {identity_key, result_content_discriminator} (INV-CK-7). SIGNATURE-
134
+ * STABLE: the task_content_signature is deliberately NOT an input, so a benign
135
+ * content edit does not move it (fail-4). This is the key O2 ingests on. equal
136
+ * idempotencyKey ⟹ equal identityKey, since identity_key is a signed input.
137
+ */
138
+ export function idempotencyKey(input) {
139
+ const identity_key = identityKey(input);
140
+ const result_content_discriminator = requireField(input?.result_content_discriminator, 'result_content_discriminator');
141
+ return sha256(stableStringify({ identity_key, result_content_discriminator }));
142
+ }
143
+ /**
144
+ * sha256 over {idempotency_key, task_content_signature} (INV-CK-5). SIGNATURE-
145
+ * SENSITIVE: bumps when the idempotencyKey OR the task_content_signature changes,
146
+ * so it drives staleness (CE-001 / inv-5). equal contentKey ⟹ equal idempotencyKey
147
+ * ⟹ equal identityKey, since each is a signed input of the next.
148
+ */
149
+ export function contentKey(input) {
150
+ const idempotency_key = idempotencyKey(input);
151
+ const task_content_signature = requireField(input?.task_content_signature, 'task_content_signature');
152
+ return sha256(stableStringify({ idempotency_key, task_content_signature }));
153
+ }
154
+ /**
155
+ * Mint a fresh per-record instance id (CE-001 / fail-2). The ledger keys on this,
156
+ * so every appended record is distinct and identityKey/idempotencyKey are never
157
+ * primary keys. This is the ONLY non-deterministic helper in the seam — the three
158
+ * key derivations above are pure.
159
+ */
160
+ export function newInstanceId() {
161
+ return randomUUID();
162
+ }
163
+ //# sourceMappingURL=contentKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contentKey.js","sourceRoot":"","sources":["../../src/shared/contentKey.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { type FrictionCaptureArtifact, type FrictionItem } from '../io/frictionCapture.js';
2
+ /**
3
+ * The single mechanical-friction sink (FC-005). Mechanical seams (O3
4
+ * validation/coercion/repair warnings; O2 recovery / ingest-merge-retry) call
5
+ * `captureFrictionEvent(artifactsDir, runId, event)` with a stable distinct
6
+ * event id; the call accretes the event onto the per-run friction record that
7
+ * `src/shared/io/frictionCapture.ts` owns.
8
+ *
9
+ * This module is landed FIRST, before O1's full triage UI, as a callable,
10
+ * inert-no-op-safe API so O2/O3 can wire it at their seams immediately.
11
+ *
12
+ * Guaranteed properties (INV-O1-5 / INV-O1-6 / INV-O1-7, FC-005):
13
+ * - BEST-EFFORT / NON-FATAL: every read/write is swallowed. A failed or
14
+ * contended capture write NEVER throws into the in-flight obligation — the
15
+ * sink resolves regardless. A caller may `await` it or fire-and-forget.
16
+ * - NO-OP-SAFE: callable before any triage UI exists; with nothing wired it is
17
+ * a pure append to an otherwise-unread per-run record, and a missing/locked
18
+ * artifacts dir simply degrades to a swallowed write.
19
+ * - IDEMPOTENT / PER-EVENT DE-DUP: an event whose `id` already appears in the
20
+ * run record is dropped, so re-entrant passes (re-dispatch, retry) never
21
+ * double-record the same logical event (INV-O1-6, FAIL-O1-5).
22
+ * - OS/PATH-AGNOSTIC: the record path comes only from the substrate's
23
+ * `frictionCapturePath` (node:path joins off `artifactsDir` + `sanitizeRunId`);
24
+ * no platform-baked literal, never coupled to any repo doc (INV-O1-7).
25
+ *
26
+ * This is the substrate-wrapping sink only: live triage dispositions over the
27
+ * accreted events are owned by O1's triage module, the locked append ordering by
28
+ * O2 — this layer just appends one de-duped event, best-effort.
29
+ */
30
+ /** A single mechanical friction event recorded through the sink. */
31
+ export interface FrictionEvent extends FrictionItem {
32
+ /**
33
+ * Stable distinct id for this logical event. Re-recording an event with an id
34
+ * already present in the run record is a no-op (per-event de-dup, INV-O1-6).
35
+ */
36
+ id: string;
37
+ }
38
+ /** A friction record item carrying its mechanical-event de-dup id. */
39
+ export interface CapturedFrictionItem extends FrictionItem {
40
+ id: string;
41
+ }
42
+ /**
43
+ * Append one mechanical friction event to the per-run record, best-effort.
44
+ *
45
+ * Never throws: any failure (missing/locked dir, malformed existing record,
46
+ * write contention) is swallowed so the calling obligation is never broken
47
+ * (INV-O1-5, FAIL-O1-3). De-duped on `event.id` (INV-O1-6, FAIL-O1-5).
48
+ */
49
+ export declare function captureFrictionEvent(artifactsDir: string, runId: string, event: FrictionEvent, tool?: FrictionCaptureArtifact['tool']): Promise<void>;
50
+ //# sourceMappingURL=captureFrictionEvent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captureFrictionEvent.d.ts","sourceRoot":"","sources":["../../../src/shared/friction/captureFrictionEvent.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EAIlB,MAAM,0BAA0B,CAAC;AAGlC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,oEAAoE;AACpE,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,sEAAsE;AACtE,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,EAAE,EAAE,MAAM,CAAC;CACZ;AAOD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,aAAa,EACpB,IAAI,GAAE,uBAAuB,CAAC,MAAM,CAAoB,GACvD,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
@@ -0,0 +1,34 @@
1
+ import { FRICTION_CAPTURE_SCHEMA_VERSION, frictionCapturePath, sanitizeRunId, } from '../io/frictionCapture.js';
2
+ import { readOptionalJsonFile, writeJsonFile } from '../io/json.js';
3
+ /**
4
+ * Append one mechanical friction event to the per-run record, best-effort.
5
+ *
6
+ * Never throws: any failure (missing/locked dir, malformed existing record,
7
+ * write contention) is swallowed so the calling obligation is never broken
8
+ * (INV-O1-5, FAIL-O1-3). De-duped on `event.id` (INV-O1-6, FAIL-O1-5).
9
+ */
10
+ export async function captureFrictionEvent(artifactsDir, runId, event, tool = 'remediate-code') {
11
+ try {
12
+ const path = frictionCapturePath(artifactsDir, sanitizeRunId(runId));
13
+ const existing = await readOptionalJsonFile(path);
14
+ const frictions = Array.isArray(existing?.frictions)
15
+ ? existing.frictions
16
+ : [];
17
+ // Per-event de-dup: drop an event whose id is already recorded.
18
+ if (frictions.some((item) => item.id === event.id)) {
19
+ return;
20
+ }
21
+ const artifact = {
22
+ schema_version: FRICTION_CAPTURE_SCHEMA_VERSION,
23
+ tool: existing?.tool ?? tool,
24
+ run_id: runId,
25
+ captured_at: new Date().toISOString(),
26
+ frictions: [...frictions, { ...event }],
27
+ };
28
+ await writeJsonFile(path, artifact);
29
+ }
30
+ catch {
31
+ // Best-effort: swallow every failure so capture never breaks the obligation.
32
+ }
33
+ }
34
+ //# sourceMappingURL=captureFrictionEvent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captureFrictionEvent.js","sourceRoot":"","sources":["../../../src/shared/friction/captureFrictionEvent.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,+BAA+B,EAC/B,mBAAmB,EACnB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAkDpE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,YAAoB,EACpB,KAAa,EACb,KAAoB,EACpB,OAAwC,gBAAgB;IAExD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,mBAAmB,CAAC,YAAY,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAA2B,IAAI,CAAC,CAAC;QAE5E,MAAM,SAAS,GAA2B,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC;YAC1E,CAAC,CAAC,QAAS,CAAC,SAAS;YACrB,CAAC,CAAC,EAAE,CAAC;QAEP,gEAAgE;QAChE,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;YACnD,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAA6B;YACzC,cAAc,EAAE,+BAA+B;YAC/C,IAAI,EAAE,QAAQ,EAAE,IAAI,IAAI,IAAI;YAC5B,MAAM,EAAE,KAAK;YACb,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACrC,SAAS,EAAE,CAAC,GAAG,SAAS,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC;SACxC,CAAC;QAEF,MAAM,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;IAC/E,CAAC;AACH,CAAC"}
@@ -0,0 +1,126 @@
1
+ import { type FrictionCaptureArtifact } from '../io/frictionCapture.js';
2
+ import { type AgentReflection } from '../agentReflections.js';
3
+ import type { CapturedFrictionItem } from './captureFrictionEvent.js';
4
+ /**
5
+ * O1 end-of-run friction TRIAGE — single-sourced for BOTH orchestrators so the
6
+ * triage step shape, the disposition vocabulary, the blocking semantics, and the
7
+ * close-out deciders cannot drift between the two halves of the pipeline. This is
8
+ * the analog (and now the single source) of the former per-orchestrator
9
+ * `decideAuditFrictionCloseout` / `decideRemediateFrictionCloseout` deciders,
10
+ * collapsed here as one parameterized decider.
11
+ *
12
+ * What the triage gates:
13
+ * - The MECHANICAL friction events the O3/O2 seams accreted through the sink
14
+ * (`captureFrictionEvent` → the per-run `friction/<run_id>.json` record).
15
+ * - The OPT-IN agent-feedback reflections workers appended to
16
+ * `agent-feedback.jsonl` during the run.
17
+ *
18
+ * The satisfaction predicate set is the UNION of those two sources. The triage is
19
+ * a MANDATORY, BLOCKING end-of-run step: it stays unsatisfied until every captured
20
+ * event AND every surfaced reflection carries a recorded disposition. This is
21
+ * DISTINCT from the optional, untouched mid-run worker reflection channel — a
22
+ * worker MAY append a reflection line, but the blocking triage is the host's
23
+ * end-of-run obligation to dispose of what was captured.
24
+ *
25
+ * False-green is dropped: an empty up-front zero-friction record NO LONGER
26
+ * satisfies the close-out. Satisfaction requires either:
27
+ * - EMPTY SET: zero events AND zero reflections → trivially disposed (the
28
+ * close-out fires once, persists a disposed record, and never re-loops); or
29
+ * - DISPOSED: every event id and every reflection key has a `keep|discard|
30
+ * annotate` disposition recorded against it.
31
+ *
32
+ * Friction appends ride O2's `withFileLock` (keyed off the friction record path)
33
+ * so a friction append never races the locked critical section.
34
+ */
35
+ /** The triage disposition vocabulary — the host's verdict per captured item. */
36
+ export type FrictionDisposition = 'keep' | 'discard' | 'annotate';
37
+ /** The set of valid dispositions, for validation at the contract boundary. */
38
+ export declare const FRICTION_DISPOSITIONS: readonly FrictionDisposition[];
39
+ /** Whether a value is a valid disposition (mechanical contract check). */
40
+ export declare function isFrictionDisposition(value: unknown): value is FrictionDisposition;
41
+ /**
42
+ * One triage record: a host disposition against a captured item key. `target_id`
43
+ * is the friction-event id (mechanical sink) or the reflection key (a stable key
44
+ * derived from a surfaced agent-feedback reflection). `annotation` is required
45
+ * only for the `annotate` disposition.
46
+ */
47
+ export interface FrictionDispositionRecord {
48
+ target_id: string;
49
+ disposition: FrictionDisposition;
50
+ annotation?: string;
51
+ }
52
+ /** A captured item the host must dispose of, normalized across both sources. */
53
+ export interface TriageSubject {
54
+ /** Stable key the disposition is recorded against. */
55
+ id: string;
56
+ /** Which source surfaced it — a mechanical event or an agent reflection. */
57
+ source: 'event' | 'reflection';
58
+ /** Human-readable summary for the triage prompt. */
59
+ note: string;
60
+ }
61
+ /**
62
+ * The mandatory blocking triage decision. `pending` means the close-out is NOT
63
+ * satisfied — the host must record a disposition for every subject in `pending`
64
+ * before the run can present complete. `action`:
65
+ * - "dispose" → there are pending subjects; the run is BLOCKED on triage.
66
+ * - "disposed" → nothing pending (empty set, or all subjects disposed); the
67
+ * close-out is satisfied and the run proceeds. Fires at most once.
68
+ */
69
+ export interface FrictionTriageDecision {
70
+ action: 'dispose' | 'disposed';
71
+ /** Subjects still awaiting a disposition (empty when `disposed`). */
72
+ pending: TriageSubject[];
73
+ /** The run_id-keyed friction record path (always set, for the handoff). */
74
+ recordPath: string;
75
+ }
76
+ /** A stable key for a surfaced reflection (task_id + ordinal within the run). */
77
+ export declare function reflectionKey(reflection: AgentReflection, ordinal: number): string;
78
+ /**
79
+ * The per-run friction record the triage reads/writes. Extends the captured
80
+ * artifact (mechanical events) with the host's `dispositions[]` — the tool owns
81
+ * every field except the host-recorded disposition content.
82
+ */
83
+ export interface TriagedFrictionArtifact extends Omit<FrictionCaptureArtifact, 'frictions'> {
84
+ frictions: CapturedFrictionItem[];
85
+ dispositions?: FrictionDispositionRecord[];
86
+ }
87
+ /** Where the friction append/triage lock lives (rides O2's withFileLock). */
88
+ export declare function frictionLockPath(artifactsDir: string, runId: string): string;
89
+ /**
90
+ * Build the union triage-subject set for a run: every captured mechanical event
91
+ * UNION every surfaced agent-feedback reflection. Deterministic — pure read off
92
+ * the on-disk record + the reflections file, never host discretion.
93
+ */
94
+ export declare function collectTriageSubjects(artifactsDir: string, runId: string): Promise<TriageSubject[]>;
95
+ /**
96
+ * The MANDATORY BLOCKING end-of-run triage close-out, single-sourced for both
97
+ * orchestrators (caller passes its `tool`). Drops the former false-green: an
98
+ * empty up-front record never satisfies — satisfaction is decided off the union
99
+ * subject set vs. the recorded dispositions:
100
+ *
101
+ * - empty set (zero events AND zero reflections) → "disposed" trivially, and a
102
+ * disposed record is persisted once so it never re-loops;
103
+ * - every subject disposed → "disposed";
104
+ * - any subject undisposed → "dispose" (the run is BLOCKED) with the pending
105
+ * subjects surfaced for the host to dispose of.
106
+ *
107
+ * Deterministic — keyed only off the on-disk record + reflections at
108
+ * `(artifactsDir, runId)`. Never coupled to any repo's backlog doc. The persist
109
+ * on the trivial-empty path rides O2's `withFileLock` so it cannot race a
110
+ * concurrent friction append into the same record.
111
+ */
112
+ export declare function decideFrictionTriage(artifactsDir: string, runId: string, tool: FrictionCaptureArtifact['tool']): Promise<FrictionTriageDecision>;
113
+ /**
114
+ * Mutate the per-run friction record under O2's `withFileLock` so a friction
115
+ * append (mechanical event OR host disposition) never races the locked critical
116
+ * section. The mutator receives the current record (a fresh degrade-clean shell
117
+ * when none exists) and returns the next record to persist atomically.
118
+ */
119
+ export declare function appendFrictionUnderLock(artifactsDir: string, runId: string, mutate: (record: TriagedFrictionArtifact) => Promise<TriagedFrictionArtifact> | TriagedFrictionArtifact, tool?: FrictionCaptureArtifact['tool']): Promise<TriagedFrictionArtifact>;
120
+ /**
121
+ * Record one host disposition against a captured subject, under the shared lock.
122
+ * Re-disposing the same `target_id` overwrites the prior verdict (idempotent on
123
+ * the key). The `annotate` disposition carries the host annotation.
124
+ */
125
+ export declare function recordFrictionDisposition(artifactsDir: string, runId: string, disposition: FrictionDispositionRecord, tool?: FrictionCaptureArtifact['tool']): Promise<TriagedFrictionArtifact>;
126
+ //# sourceMappingURL=triage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"triage.d.ts","sourceRoot":"","sources":["../../../src/shared/friction/triage.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,uBAAuB,EAI7B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,eAAe,EAGrB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,EAAE,SAAS,mBAAmB,EAI/D,CAAC;AAEF,0EAA0E;AAC1E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAKlF;AAED;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,mBAAmB,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,MAAM,EAAE,OAAO,GAAG,YAAY,CAAC;IAC/B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC;IAC/B,qEAAqE;IACrE,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAkBD;;;;GAIG;AACH,MAAM,WAAW,uBACf,SAAQ,IAAI,CAAC,uBAAuB,EAAE,WAAW,CAAC;IAClD,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC,YAAY,CAAC,EAAE,yBAAyB,EAAE,CAAC;CAC5C;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5E;AAkBD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,EAAE,CAAC,CAkB1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,uBAAuB,CAAC,MAAM,CAAC,GACpC,OAAO,CAAC,sBAAsB,CAAC,CAiCjC;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,OAAO,CAAC,uBAAuB,CAAC,GAAG,uBAAuB,EACvG,IAAI,GAAE,uBAAuB,CAAC,MAAM,CAAoB,GACvD,OAAO,CAAC,uBAAuB,CAAC,CAoBlC;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,yBAAyB,EACtC,IAAI,GAAE,uBAAuB,CAAC,MAAM,CAAoB,GACvD,OAAO,CAAC,uBAAuB,CAAC,CAalC"}
@@ -0,0 +1,149 @@
1
+ import { FRICTION_CAPTURE_SCHEMA_VERSION, frictionCapturePath, sanitizeRunId, } from '../io/frictionCapture.js';
2
+ import { AGENT_FEEDBACK_FILENAME, parseReflectionsNdjson, } from '../agentReflections.js';
3
+ import { readOptionalJsonFile, readOptionalTextFile, writeJsonFile } from '../io/json.js';
4
+ import { withFileLock } from '../quota/fileLock.js';
5
+ /** The set of valid dispositions, for validation at the contract boundary. */
6
+ export const FRICTION_DISPOSITIONS = [
7
+ 'keep',
8
+ 'discard',
9
+ 'annotate',
10
+ ];
11
+ /** Whether a value is a valid disposition (mechanical contract check). */
12
+ export function isFrictionDisposition(value) {
13
+ return (typeof value === 'string' &&
14
+ FRICTION_DISPOSITIONS.includes(value));
15
+ }
16
+ /** A stable key for a surfaced reflection (task_id + ordinal within the run). */
17
+ export function reflectionKey(reflection, ordinal) {
18
+ return `reflection:${reflection.task_id}:${ordinal}`;
19
+ }
20
+ function summarizeReflection(reflection) {
21
+ const parts = [`[${reflection.severity}] ${reflection.task_id}`];
22
+ const detail = [
23
+ ...(reflection.ambiguities ?? []),
24
+ ...(reflection.tool_friction ?? []),
25
+ ...(reflection.suggestions ?? []),
26
+ ].filter((item) => item.trim().length > 0);
27
+ if (detail.length > 0)
28
+ parts.push(detail.join('; '));
29
+ return parts.join(' — ');
30
+ }
31
+ function summarizeEvent(item) {
32
+ const sev = item.severity ? `[${item.severity}] ` : '';
33
+ return `${sev}${item.note}`;
34
+ }
35
+ /** Where the friction append/triage lock lives (rides O2's withFileLock). */
36
+ export function frictionLockPath(artifactsDir, runId) {
37
+ return `${frictionCapturePath(artifactsDir, sanitizeRunId(runId))}.lock`;
38
+ }
39
+ async function readReflections(artifactsDir) {
40
+ const text = await readOptionalTextFile(`${artifactsDir}/${AGENT_FEEDBACK_FILENAME}`);
41
+ return text ? parseReflectionsNdjson(text) : [];
42
+ }
43
+ async function readRecord(artifactsDir, runId) {
44
+ return readOptionalJsonFile(frictionCapturePath(artifactsDir, sanitizeRunId(runId)));
45
+ }
46
+ /**
47
+ * Build the union triage-subject set for a run: every captured mechanical event
48
+ * UNION every surfaced agent-feedback reflection. Deterministic — pure read off
49
+ * the on-disk record + the reflections file, never host discretion.
50
+ */
51
+ export async function collectTriageSubjects(artifactsDir, runId) {
52
+ const [record, reflections] = await Promise.all([
53
+ readRecord(artifactsDir, runId),
54
+ readReflections(artifactsDir),
55
+ ]);
56
+ const subjects = [];
57
+ for (const item of record?.frictions ?? []) {
58
+ subjects.push({ id: item.id, source: 'event', note: summarizeEvent(item) });
59
+ }
60
+ reflections.forEach((reflection, index) => {
61
+ subjects.push({
62
+ id: reflectionKey(reflection, index),
63
+ source: 'reflection',
64
+ note: summarizeReflection(reflection),
65
+ });
66
+ });
67
+ return subjects;
68
+ }
69
+ /**
70
+ * The MANDATORY BLOCKING end-of-run triage close-out, single-sourced for both
71
+ * orchestrators (caller passes its `tool`). Drops the former false-green: an
72
+ * empty up-front record never satisfies — satisfaction is decided off the union
73
+ * subject set vs. the recorded dispositions:
74
+ *
75
+ * - empty set (zero events AND zero reflections) → "disposed" trivially, and a
76
+ * disposed record is persisted once so it never re-loops;
77
+ * - every subject disposed → "disposed";
78
+ * - any subject undisposed → "dispose" (the run is BLOCKED) with the pending
79
+ * subjects surfaced for the host to dispose of.
80
+ *
81
+ * Deterministic — keyed only off the on-disk record + reflections at
82
+ * `(artifactsDir, runId)`. Never coupled to any repo's backlog doc. The persist
83
+ * on the trivial-empty path rides O2's `withFileLock` so it cannot race a
84
+ * concurrent friction append into the same record.
85
+ */
86
+ export async function decideFrictionTriage(artifactsDir, runId, tool) {
87
+ const recordPath = frictionCapturePath(artifactsDir, sanitizeRunId(runId));
88
+ const subjects = await collectTriageSubjects(artifactsDir, runId);
89
+ if (subjects.length === 0) {
90
+ // Empty set: trivially disposed. Persist a disposed record once (under the
91
+ // shared lock) so the close-out fires at most once and never re-loops.
92
+ await appendFrictionUnderLock(artifactsDir, runId, (record) => ({
93
+ ...record,
94
+ tool: record.tool ?? tool,
95
+ dispositions: record.dispositions ?? [],
96
+ }), tool);
97
+ return { action: 'disposed', pending: [], recordPath };
98
+ }
99
+ const record = await readRecord(artifactsDir, runId);
100
+ const disposed = new Set((record?.dispositions ?? [])
101
+ .filter((d) => isFrictionDisposition(d.disposition))
102
+ .map((d) => d.target_id));
103
+ const pending = subjects.filter((subject) => !disposed.has(subject.id));
104
+ return {
105
+ action: pending.length === 0 ? 'disposed' : 'dispose',
106
+ pending,
107
+ recordPath,
108
+ };
109
+ }
110
+ /**
111
+ * Mutate the per-run friction record under O2's `withFileLock` so a friction
112
+ * append (mechanical event OR host disposition) never races the locked critical
113
+ * section. The mutator receives the current record (a fresh degrade-clean shell
114
+ * when none exists) and returns the next record to persist atomically.
115
+ */
116
+ export async function appendFrictionUnderLock(artifactsDir, runId, mutate, tool = 'remediate-code') {
117
+ const recordPath = frictionCapturePath(artifactsDir, sanitizeRunId(runId));
118
+ return withFileLock(frictionLockPath(artifactsDir, runId), async () => {
119
+ const existing = await readOptionalJsonFile(recordPath);
120
+ const base = existing ?? {
121
+ schema_version: FRICTION_CAPTURE_SCHEMA_VERSION,
122
+ tool,
123
+ run_id: runId,
124
+ captured_at: new Date().toISOString(),
125
+ frictions: [],
126
+ dispositions: [],
127
+ };
128
+ const next = await mutate(base);
129
+ const persisted = {
130
+ ...next,
131
+ captured_at: new Date().toISOString(),
132
+ };
133
+ await writeJsonFile(recordPath, persisted);
134
+ return persisted;
135
+ });
136
+ }
137
+ /**
138
+ * Record one host disposition against a captured subject, under the shared lock.
139
+ * Re-disposing the same `target_id` overwrites the prior verdict (idempotent on
140
+ * the key). The `annotate` disposition carries the host annotation.
141
+ */
142
+ export async function recordFrictionDisposition(artifactsDir, runId, disposition, tool = 'remediate-code') {
143
+ return appendFrictionUnderLock(artifactsDir, runId, (record) => {
144
+ const dispositions = (record.dispositions ?? []).filter((d) => d.target_id !== disposition.target_id);
145
+ dispositions.push(disposition);
146
+ return { ...record, dispositions };
147
+ }, tool);
148
+ }
149
+ //# sourceMappingURL=triage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"triage.js","sourceRoot":"","sources":["../../../src/shared/friction/triage.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,9 @@ export type { FindingIdentityFields } from "./findingIdentitySignature.js";
46
46
  export { normalizeAnchorPath, normalizeTitle, findingIdentitySignature, findingIdentityFields, findingIdentityKey, } from "./findingIdentitySignature.js";
47
47
  export type { HashContentOptions } from "./hash.js";
48
48
  export { hashContent } from "./hash.js";
49
+ export { stableStringify } from "./stableStringify.js";
50
+ export type { IdentityKeyInput, TaskContentSignatureInput, ResultEmitSource, ResultContentDiscriminatorInput, IdempotencyKeyInput, ContentKeyInput, } from "./contentKey.js";
51
+ export { buildTaskContentSignature, buildResultContentDiscriminator, identityKey, idempotencyKey, contentKey, newInstanceId, } from "./contentKey.js";
49
52
  export type { ProjectionDiffEntry, ReReviewSectionInput, } from "./reReview/projectionDiff.js";
50
53
  export { stableStringifyProjection, diffProjections, renderDiffReReviewSection, } from "./reReview/projectionDiff.js";
51
54
  export type { RunTrackedOptions, RunTrackedResult } from "./tooling/exec.js";
@@ -60,9 +63,15 @@ export { isGitRepo, gitRefExists, changedFiles, fileCommits, stagedAndUntracked,
60
63
  export type { RunLogEvent, RunLoggerOptions } from "./observability/runLog.js";
61
64
  export { RunLogger } from "./observability/runLog.js";
62
65
  export { isFileMissingError, isTransientFsError, withFsRetry, readJsonFile, writeJsonFile, readJsonStringScalar, readJsonStringScalarChunks, appendNdjsonFile, readNdjsonFile, readOptionalJsonFile, readOptionalNdjsonFile, writeNdjsonFile, readOptionalTextFile, writeTextFile, } from "./io/json.js";
63
- export { auditToolsDir, auditArtifactsDir, remediationArtifactsDir, stepsDir, incomingDir, outputDirFor, auditReportPath, auditFindingsPath, promotedAuditReportPath, promotedAuditFindingsPath, AUDIT_REPORT_FILENAME, AUDIT_FINDINGS_FILENAME, REMEDIATION_REPORT_FILENAME, REMEDIATION_OUTCOMES_FILENAME, } from "./io/auditToolsPaths.js";
66
+ export { auditToolsDir, auditArtifactsDir, remediationArtifactsDir, stepsDir, artifactTreeLockPath, incomingDir, outputDirFor, auditReportPath, auditFindingsPath, promotedAuditReportPath, promotedAuditFindingsPath, AUDIT_REPORT_FILENAME, AUDIT_FINDINGS_FILENAME, REMEDIATION_REPORT_FILENAME, REMEDIATION_OUTCOMES_FILENAME, } from "./io/auditToolsPaths.js";
64
67
  export type { FrictionItem, FrictionCaptureArtifact, } from "./io/frictionCapture.js";
65
- export { FRICTION_CAPTURE_SCHEMA_VERSION, FRICTION_CAPTURE_DIRNAME, frictionCaptureDir, frictionCapturePath, frictionCaptured, persistFrictionCapture, } from "./io/frictionCapture.js";
68
+ export { FRICTION_CAPTURE_SCHEMA_VERSION, FRICTION_CAPTURE_DIRNAME, frictionCaptureDir, frictionCapturePath, frictionCaptured, persistFrictionCapture, sanitizeRunId, } from "./io/frictionCapture.js";
69
+ export type { FrictionEvent, CapturedFrictionItem, } from "./friction/captureFrictionEvent.js";
70
+ export { captureFrictionEvent } from "./friction/captureFrictionEvent.js";
71
+ export type { FrictionDisposition, FrictionDispositionRecord, TriageSubject, FrictionTriageDecision, TriagedFrictionArtifact, } from "./friction/triage.js";
72
+ export { FRICTION_DISPOSITIONS, isFrictionDisposition, reflectionKey, frictionLockPath, collectTriageSubjects, decideFrictionTriage, appendFrictionUnderLock, recordFrictionDisposition, } from "./friction/triage.js";
73
+ export type { RepairValidationError, RepairValidationResult, RepairCoercion, RepairCoercionResult, RepairPatcher, RepairContract, RepairStatus, RepairStage, RepairRedispatch, RepairOutcome, RunEmitValidateRepairOptions, } from "./repair/index.js";
74
+ export { runEmitValidateRepair } from "./repair/index.js";
66
75
  export type { RepoVisibility } from "./io/gitignoreArtifacts.js";
67
76
  export { ALWAYS_IGNORE_PATTERNS, VISIBILITY_CONDITIONAL_PATTERNS, GITIGNORE_BLOCK_BEGIN, GITIGNORE_BLOCK_END, renderGitignoreBlock, mergeGitignoreBlock, detectRepoVisibility, ensureArtifactGitignore, } from "./io/gitignoreArtifacts.js";
68
77
  export type { BaseStepContract, WriteStepContractInput, } from "./io/stepContractWriter.js";