@oscharko-dev/keiko-contracts 0.2.8 → 0.2.9

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 (106) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/bff-wire.d.ts +33 -0
  3. package/dist/bff-wire.d.ts.map +1 -1
  4. package/dist/command-runner.d.ts +81 -0
  5. package/dist/command-runner.d.ts.map +1 -0
  6. package/dist/command-runner.js +209 -0
  7. package/dist/container-runtime.d.ts +125 -0
  8. package/dist/container-runtime.d.ts.map +1 -0
  9. package/dist/container-runtime.js +287 -0
  10. package/dist/context-engineering.js +2 -2
  11. package/dist/discussion-intelligence.d.ts +70 -0
  12. package/dist/discussion-intelligence.d.ts.map +1 -0
  13. package/dist/discussion-intelligence.js +440 -0
  14. package/dist/editor-agent-governance.d.ts +65 -0
  15. package/dist/editor-agent-governance.d.ts.map +1 -0
  16. package/dist/editor-agent-governance.js +180 -0
  17. package/dist/editor-agent.d.ts +29 -1
  18. package/dist/editor-agent.d.ts.map +1 -1
  19. package/dist/editor-agent.js +183 -6
  20. package/dist/editor-builtin-capabilities.d.ts +13 -0
  21. package/dist/editor-builtin-capabilities.d.ts.map +1 -0
  22. package/dist/editor-builtin-capabilities.js +135 -0
  23. package/dist/editor-language-mode-map.d.ts +11 -0
  24. package/dist/editor-language-mode-map.d.ts.map +1 -0
  25. package/dist/editor-language-mode-map.js +89 -0
  26. package/dist/editor-layout.d.ts +37 -0
  27. package/dist/editor-layout.d.ts.map +1 -1
  28. package/dist/editor-layout.js +125 -8
  29. package/dist/editor-workspace-path.d.ts +20 -0
  30. package/dist/editor-workspace-path.d.ts.map +1 -0
  31. package/dist/editor-workspace-path.js +133 -0
  32. package/dist/evidence.d.ts +3 -1
  33. package/dist/evidence.d.ts.map +1 -1
  34. package/dist/gateway.d.ts +72 -3
  35. package/dist/gateway.d.ts.map +1 -1
  36. package/dist/gateway.js +73 -3
  37. package/dist/git-commit-intent.d.ts +28 -0
  38. package/dist/git-commit-intent.d.ts.map +1 -0
  39. package/dist/git-commit-intent.js +155 -0
  40. package/dist/git-commit-policy.d.ts +29 -0
  41. package/dist/git-commit-policy.d.ts.map +1 -0
  42. package/dist/git-commit-policy.js +173 -0
  43. package/dist/git-delivery-action-sheet.d.ts +157 -0
  44. package/dist/git-delivery-action-sheet.d.ts.map +1 -0
  45. package/dist/git-delivery-action-sheet.js +430 -0
  46. package/dist/git-delivery-evidence.d.ts +92 -0
  47. package/dist/git-delivery-evidence.d.ts.map +1 -0
  48. package/dist/git-delivery-evidence.js +272 -0
  49. package/dist/git-delivery-policy.d.ts +40 -0
  50. package/dist/git-delivery-policy.d.ts.map +1 -0
  51. package/dist/git-delivery-policy.js +183 -0
  52. package/dist/git-delivery-provider.d.ts +53 -0
  53. package/dist/git-delivery-provider.d.ts.map +1 -0
  54. package/dist/git-delivery-provider.js +96 -0
  55. package/dist/git-delivery.d.ts +202 -0
  56. package/dist/git-delivery.d.ts.map +1 -0
  57. package/dist/git-delivery.js +410 -0
  58. package/dist/git-history.d.ts +27 -0
  59. package/dist/git-history.d.ts.map +1 -0
  60. package/dist/git-history.js +73 -0
  61. package/dist/git-merge.d.ts +67 -0
  62. package/dist/git-merge.d.ts.map +1 -0
  63. package/dist/git-merge.js +323 -0
  64. package/dist/git-pull-request.d.ts +112 -0
  65. package/dist/git-pull-request.d.ts.map +1 -0
  66. package/dist/git-pull-request.js +351 -0
  67. package/dist/git-repository-agent.d.ts +46 -0
  68. package/dist/git-repository-agent.d.ts.map +1 -0
  69. package/dist/git-repository-agent.js +198 -0
  70. package/dist/git-repository-summary.d.ts +54 -0
  71. package/dist/git-repository-summary.d.ts.map +1 -0
  72. package/dist/git-repository-summary.js +105 -0
  73. package/dist/git-repository.d.ts +60 -0
  74. package/dist/git-repository.d.ts.map +1 -0
  75. package/dist/git-repository.js +106 -0
  76. package/dist/git-sync.d.ts +49 -0
  77. package/dist/git-sync.d.ts.map +1 -0
  78. package/dist/git-sync.js +110 -0
  79. package/dist/index.d.ts +63 -9
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +33 -6
  82. package/dist/lsp-process.d.ts +38 -0
  83. package/dist/lsp-process.d.ts.map +1 -0
  84. package/dist/lsp-process.js +103 -0
  85. package/dist/runtime-capabilities.d.ts +44 -0
  86. package/dist/runtime-capabilities.d.ts.map +1 -0
  87. package/dist/runtime-capabilities.js +141 -0
  88. package/dist/task-workspace.d.ts +302 -0
  89. package/dist/task-workspace.d.ts.map +1 -0
  90. package/dist/task-workspace.js +1236 -0
  91. package/dist/voice-action-intent.d.ts +86 -0
  92. package/dist/voice-action-intent.d.ts.map +1 -0
  93. package/dist/voice-action-intent.js +387 -0
  94. package/dist/voice-playback.d.ts +50 -0
  95. package/dist/voice-playback.d.ts.map +1 -0
  96. package/dist/voice-playback.js +240 -0
  97. package/dist/voice-protocol.d.ts +165 -0
  98. package/dist/voice-protocol.d.ts.map +1 -0
  99. package/dist/voice-protocol.js +312 -0
  100. package/dist/voice-transcript.d.ts +57 -0
  101. package/dist/voice-transcript.d.ts.map +1 -0
  102. package/dist/voice-transcript.js +221 -0
  103. package/package.json +5 -1
  104. package/dist/conversation-budget.d.ts +0 -37
  105. package/dist/conversation-budget.d.ts.map +0 -1
  106. package/dist/conversation-budget.js +0 -97
@@ -0,0 +1,440 @@
1
+ // Discussion Intelligence contract for colleague-like discussion behavior (Epic #491, Issue #502,
2
+ // ADR-0065). This leaf module DEFINES the provider-neutral, text-first semantics that let Keiko run the
3
+ // five discussion modes (Challenge / Review / Decide / Brainstorm / Evidence-check), disagree
4
+ // professionally grounded in evidence + assumptions + uncertainty, surface confidence, and recover an
5
+ // interrupted spoken discussion turn without losing the active question or decision context.
6
+ //
7
+ // It is pure data + pure functions only — nothing performs IO, crypto, clock reads, randomness, or audio
8
+ // processing. No raw user / assistant text, transcript excerpt, credential, provider URL, or tool grant
9
+ // is ever a field on these types: every value that crosses a boundary is a closed enum, a bool, a bounded
10
+ // 0–1 float, an int, or a fixed content-free template string (the server renders these templates verbatim
11
+ // as an ADDITIVE block; `CONVERSATION_SYSTEM_PROMPT` stays immutable).
12
+ //
13
+ // Reuse, not a parallel stack (Issue #502 AC2): the citation / contradiction / grounding vocabulary is
14
+ // imported from `./prompt-enhancer.js`, and voice gating is derived from `./voice-transcript.js`'s
15
+ // `voiceTranscriptCaptureAllowed` rather than re-encoding the voice profile table. Discussion
16
+ // intelligence ITSELF is always available (text-first, AC1); the voice predicate only governs whether
17
+ // SPOKEN turns may drive it.
18
+ //
19
+ // `DISCUSSION_INTELLIGENCE_SCHEMA_VERSION` follows the same evolution rule as the sibling schema
20
+ // versions (ADR-0010 D2): a breaking change introduces a NEW literal rather than mutating "1", and it is
21
+ // INDEPENDENT of every other schema version. Leaf-package rule (ADR-0019 direction 1): no
22
+ // `@oscharko-dev/keiko-*` import may appear here; siblings are reached by relative path.
23
+ import { voiceTranscriptCaptureAllowed } from "./voice-transcript.js";
24
+ // ─── Schema version ───────────────────────────────────────────────────────────
25
+ export const DISCUSSION_INTELLIGENCE_SCHEMA_VERSION = "1";
26
+ export function isDiscussionIntelligenceSchemaVersionSupported(version) {
27
+ return version === DISCUSSION_INTELLIGENCE_SCHEMA_VERSION;
28
+ }
29
+ export const DISCUSSION_MODES = [
30
+ "challenge",
31
+ "review",
32
+ "decide",
33
+ "brainstorm",
34
+ "evidence-check",
35
+ ];
36
+ export function isDiscussionMode(value) {
37
+ return typeof value === "string" && DISCUSSION_MODES.includes(value);
38
+ }
39
+ export function assertNeverDiscussionMode(value) {
40
+ throw new TypeError(`Unhandled DiscussionMode: ${JSON.stringify(value)}`);
41
+ }
42
+ export const DISCUSSION_CONFIDENCE_LEVELS = [
43
+ "high",
44
+ "medium",
45
+ "low",
46
+ ];
47
+ // Cut points (documented + tested): a non-finite or out-of-[0,1] score is guarded to `low` (the most
48
+ // cautious level — never overstate confidence). Within range: `< 1/3 → low`, `< 2/3 → medium`, else
49
+ // `high`. The boundaries 1/3 and 2/3 themselves round UP (1/3 is medium, 2/3 is high), matching the
50
+ // segment-fill thresholds.
51
+ const CONFIDENCE_LOW_CEILING = 1 / 3;
52
+ const CONFIDENCE_MEDIUM_CEILING = 2 / 3;
53
+ export function confidenceLevelFromScore(score) {
54
+ if (!Number.isFinite(score) || score < 0 || score > 1) {
55
+ return "low";
56
+ }
57
+ if (score < CONFIDENCE_LOW_CEILING) {
58
+ return "low";
59
+ }
60
+ if (score < CONFIDENCE_MEDIUM_CEILING) {
61
+ return "medium";
62
+ }
63
+ return "high";
64
+ }
65
+ export const DISAGREEMENT_FACETS = [
66
+ "evidence",
67
+ "assumptions",
68
+ "uncertainty",
69
+ ];
70
+ export const DISCUSSION_DIRECTIVES = [
71
+ "state-position-then-evidence",
72
+ "challenge-stated-assumptions",
73
+ "surface-counter-evidence",
74
+ "list-explicit-assumptions",
75
+ "disclose-uncertainty-and-confidence",
76
+ "cite-evidence-or-state-none",
77
+ "offer-decision-with-tradeoffs",
78
+ "expand-option-space-before-converging",
79
+ "verify-claims-against-evidence",
80
+ "defer-to-user-on-unresolved-contradiction",
81
+ ];
82
+ export function assertNeverDiscussionDirective(value) {
83
+ throw new TypeError(`Unhandled DiscussionDirective: ${JSON.stringify(value)}`);
84
+ }
85
+ // Fixed, content-free instruction templates. Bounded length; no raw input echo, no credential, provider
86
+ // URL, system prompt, or tool/secret/egress authority encodable here. Keyed by directive for totality.
87
+ export const DISCUSSION_DIRECTIVE_TEMPLATES = {
88
+ "state-position-then-evidence": "State your position first, then the evidence that supports it.",
89
+ "challenge-stated-assumptions": "Identify and challenge the assumptions the stated position depends on.",
90
+ "surface-counter-evidence": "Surface evidence that weighs against the position, not only evidence for it.",
91
+ "list-explicit-assumptions": "List the explicit assumptions you are making before drawing a conclusion.",
92
+ "disclose-uncertainty-and-confidence": "Disclose your remaining uncertainty and your confidence level for each claim.",
93
+ "cite-evidence-or-state-none": "Cite the evidence for each claim, or state plainly that no evidence is available.",
94
+ "offer-decision-with-tradeoffs": "Offer a recommended next action and lay out the trade-offs of each option.",
95
+ "expand-option-space-before-converging": "Expand the range of options before converging on a single recommendation.",
96
+ "verify-claims-against-evidence": "Verify each claim against the available evidence before accepting it.",
97
+ "defer-to-user-on-unresolved-contradiction": "When a contradiction cannot be resolved from evidence, defer the decision to the user.",
98
+ };
99
+ // Maps each directive to the disagreement facet(s) its template addresses (AC3). Keyed by directive for
100
+ // totality (a new directive without an entry is a compile error). A directive whose template is about
101
+ // the decision shape or deferral rather than a disagreement facet maps to the empty array. This is the
102
+ // machine-checkable bridge between a mode's rendered directives and the facets it mandates: the regression
103
+ // guard `discussionDirectivesCoverFacets` proves every mode's directives actually instruct the model on
104
+ // the facets the mode mandates (so e.g. an `evidence`-mandating mode cannot silently drop evidence from
105
+ // its rendered prompt).
106
+ export const DISCUSSION_DIRECTIVE_FACETS = {
107
+ "state-position-then-evidence": ["evidence"],
108
+ "challenge-stated-assumptions": ["assumptions"],
109
+ "surface-counter-evidence": ["evidence"],
110
+ "list-explicit-assumptions": ["assumptions"],
111
+ "disclose-uncertainty-and-confidence": ["uncertainty"],
112
+ "cite-evidence-or-state-none": ["evidence"],
113
+ "offer-decision-with-tradeoffs": [],
114
+ "expand-option-space-before-converging": [],
115
+ "verify-claims-against-evidence": ["evidence"],
116
+ "defer-to-user-on-unresolved-contradiction": [],
117
+ };
118
+ // Frozen TOTAL table keyed by mode (a new mode without a plan is a compile error). Invariant enforced in
119
+ // tests: every disagreement-capable mode (challenge/review/decide/evidence-check) mandates ALL THREE
120
+ // `DISAGREEMENT_FACETS`; `brainstorm` relaxes uncertainty (it expands options rather than disagreeing).
121
+ export const DISCUSSION_MODE_PLANS = {
122
+ challenge: {
123
+ mode: "challenge",
124
+ challengesAssumptions: true,
125
+ requiresExplicitAssumptions: true,
126
+ requiresUncertaintyDisclosure: true,
127
+ citationDiscipline: "require-citations-or-state-no-evidence",
128
+ contradictionPolicy: "disclose-and-defer",
129
+ groundingDirectives: [
130
+ "stay-within-evidence",
131
+ "disclose-uncertainty",
132
+ "separate-known-from-retrieved",
133
+ ],
134
+ producesDecisionRecommendation: false,
135
+ mandatedFacets: ["evidence", "assumptions", "uncertainty"],
136
+ directives: [
137
+ "state-position-then-evidence",
138
+ "challenge-stated-assumptions",
139
+ "surface-counter-evidence",
140
+ "list-explicit-assumptions",
141
+ "disclose-uncertainty-and-confidence",
142
+ ],
143
+ },
144
+ review: {
145
+ mode: "review",
146
+ challengesAssumptions: true,
147
+ requiresExplicitAssumptions: true,
148
+ requiresUncertaintyDisclosure: true,
149
+ citationDiscipline: "require-citations-or-state-no-evidence",
150
+ contradictionPolicy: "disclose-and-defer",
151
+ groundingDirectives: [
152
+ "attribute-claims-to-sources",
153
+ "stay-within-evidence",
154
+ "disclose-uncertainty",
155
+ ],
156
+ producesDecisionRecommendation: false,
157
+ mandatedFacets: ["evidence", "assumptions", "uncertainty"],
158
+ directives: [
159
+ "verify-claims-against-evidence",
160
+ "surface-counter-evidence",
161
+ "list-explicit-assumptions",
162
+ "disclose-uncertainty-and-confidence",
163
+ ],
164
+ },
165
+ decide: {
166
+ mode: "decide",
167
+ challengesAssumptions: true,
168
+ requiresExplicitAssumptions: true,
169
+ requiresUncertaintyDisclosure: true,
170
+ citationDiscipline: "require-citations-or-state-no-evidence",
171
+ contradictionPolicy: "disclose-and-defer",
172
+ groundingDirectives: ["stay-within-evidence", "disclose-uncertainty"],
173
+ producesDecisionRecommendation: true,
174
+ mandatedFacets: ["evidence", "assumptions", "uncertainty"],
175
+ directives: [
176
+ "cite-evidence-or-state-none",
177
+ "list-explicit-assumptions",
178
+ "offer-decision-with-tradeoffs",
179
+ "disclose-uncertainty-and-confidence",
180
+ "defer-to-user-on-unresolved-contradiction",
181
+ ],
182
+ },
183
+ brainstorm: {
184
+ mode: "brainstorm",
185
+ challengesAssumptions: false,
186
+ requiresExplicitAssumptions: true,
187
+ requiresUncertaintyDisclosure: false,
188
+ citationDiscipline: "best-effort",
189
+ contradictionPolicy: "synthesize-with-caveats",
190
+ groundingDirectives: ["separate-known-from-retrieved"],
191
+ producesDecisionRecommendation: false,
192
+ mandatedFacets: ["evidence", "assumptions"],
193
+ directives: [
194
+ "expand-option-space-before-converging",
195
+ "state-position-then-evidence",
196
+ "list-explicit-assumptions",
197
+ ],
198
+ },
199
+ "evidence-check": {
200
+ mode: "evidence-check",
201
+ challengesAssumptions: true,
202
+ requiresExplicitAssumptions: true,
203
+ requiresUncertaintyDisclosure: true,
204
+ citationDiscipline: "require-citations",
205
+ contradictionPolicy: "disclose-and-defer",
206
+ groundingDirectives: [
207
+ "attribute-claims-to-sources",
208
+ "do-not-fabricate-sources",
209
+ "stay-within-evidence",
210
+ "disclose-uncertainty",
211
+ ],
212
+ producesDecisionRecommendation: false,
213
+ mandatedFacets: ["evidence", "assumptions", "uncertainty"],
214
+ directives: [
215
+ "verify-claims-against-evidence",
216
+ "cite-evidence-or-state-none",
217
+ "list-explicit-assumptions",
218
+ "disclose-uncertainty-and-confidence",
219
+ ],
220
+ },
221
+ };
222
+ export function discussionModePlan(mode) {
223
+ return DISCUSSION_MODE_PLANS[mode];
224
+ }
225
+ // AC3 regression guard: true iff the union of the disagreement facets addressed by the plan's rendered
226
+ // directives (via `DISCUSSION_DIRECTIVE_FACETS`) is a SUPERSET of the facets the plan mandates. In other
227
+ // words, every facet the mode promises to cover has at least one directive in the rendered prompt that
228
+ // instructs the model on it. A mandated facet with no covering directive — e.g. a `decide` plan that
229
+ // mandates `evidence` but renders no evidence directive — makes this return false.
230
+ export function discussionDirectivesCoverFacets(plan) {
231
+ const covered = new Set();
232
+ for (const directive of plan.directives) {
233
+ for (const facet of DISCUSSION_DIRECTIVE_FACETS[directive]) {
234
+ covered.add(facet);
235
+ }
236
+ }
237
+ return plan.mandatedFacets.every((facet) => covered.has(facet));
238
+ }
239
+ // ─── Capability gating (text-first + voice reuse, AC1/AC2) ───────────────────────
240
+ // Discussion intelligence is ALWAYS available as text; this predicate only governs whether SPOKEN turns
241
+ // may drive it. It reuses `voiceTranscriptCaptureAllowed` (the committed-transcript capability) so the
242
+ // gating cannot drift from the voice contract: true for `speech-to-text`/`full-realtime`, false for
243
+ // `none`/`speech-output` (playback-only). `none` keeps Keiko fully usable text-first (AC1).
244
+ export function voiceCanDriveDiscussion(profile) {
245
+ return voiceTranscriptCaptureAllowed(profile);
246
+ }
247
+ export const DISCUSSION_TURN_STATUSES = [
248
+ "active",
249
+ "interrupted",
250
+ "recovered",
251
+ "resolved",
252
+ ];
253
+ // Legal state-changing transitions, keyed by status for totality. `active` may be interrupted or
254
+ // resolved directly; an `interrupted` turn recovers or resolves; a `recovered` turn may be interrupted
255
+ // again (repeated barge-in) or resolved; `resolved` is terminal.
256
+ export const DISCUSSION_TURN_STATUS_TRANSITIONS = {
257
+ active: ["interrupted", "resolved"],
258
+ interrupted: ["recovered", "resolved"],
259
+ recovered: ["interrupted", "resolved"],
260
+ resolved: [],
261
+ };
262
+ export function isDiscussionTurnStatus(value) {
263
+ return (typeof value === "string" && DISCUSSION_TURN_STATUSES.includes(value));
264
+ }
265
+ export function canTransitionDiscussionTurnStatus(from, to) {
266
+ return DISCUSSION_TURN_STATUS_TRANSITIONS[from].includes(to);
267
+ }
268
+ export function assertNeverDiscussionTurnStatus(value) {
269
+ throw new TypeError(`Unhandled DiscussionTurnStatus: ${JSON.stringify(value)}`);
270
+ }
271
+ // ─── topicId validation (opaque bounded safe-text id) ────────────────────────────
272
+ // `topicId` is the open-question / decision identity — NEVER raw text. It is validated like the
273
+ // prompt-enhancer branded ids (non-empty after trim, no surrounding whitespace, NFKC-normalised, no
274
+ // control characters, ≤256, no path-traversal fragment) with a small LOCAL validator (the leaf rule
275
+ // permits importing the prompt-enhancer one, but a local validator keeps this module self-contained and
276
+ // avoids coupling the discussion identity to the enhancer's id brand).
277
+ const DISCUSSION_TOPIC_ID_MAX_LENGTH = 256;
278
+ const DISCUSSION_TOPIC_ID_FORBIDDEN_FRAGMENTS = ["..", "/", "\\"];
279
+ function topicIdHasControlCharacter(value) {
280
+ for (let index = 0; index < value.length; index += 1) {
281
+ const code = value.charCodeAt(index);
282
+ // C0 controls 0x00–0x1F, DEL 0x7F, C1 controls 0x80–0x9F.
283
+ if (code <= 0x1f || code === 0x7f || (code >= 0x80 && code <= 0x9f)) {
284
+ return true;
285
+ }
286
+ }
287
+ return false;
288
+ }
289
+ // Collects ALL reasons a candidate topicId is invalid (accumulating, never throws). Empty array = valid.
290
+ export function discussionTopicIdReasons(value) {
291
+ const reasons = [];
292
+ if (typeof value !== "string") {
293
+ return ["topicId: must be a string"];
294
+ }
295
+ if (value.length === 0 || value.trim().length === 0) {
296
+ reasons.push("topicId: must not be empty or whitespace-only");
297
+ }
298
+ if (value.length > 0 && value !== value.trim()) {
299
+ reasons.push("topicId: must not have surrounding whitespace");
300
+ }
301
+ if (value.length > DISCUSSION_TOPIC_ID_MAX_LENGTH) {
302
+ reasons.push("topicId: exceeds max length 256");
303
+ }
304
+ if (value.normalize("NFKC") !== value) {
305
+ reasons.push("topicId: must be NFKC-normalised");
306
+ }
307
+ if (topicIdHasControlCharacter(value)) {
308
+ reasons.push("topicId: contains control characters");
309
+ }
310
+ if (DISCUSSION_TOPIC_ID_FORBIDDEN_FRAGMENTS.some((fragment) => value.includes(fragment))) {
311
+ reasons.push("topicId: contains a forbidden path fragment");
312
+ }
313
+ return reasons;
314
+ }
315
+ export function isValidDiscussionTopicId(value) {
316
+ return discussionTopicIdReasons(value).length === 0;
317
+ }
318
+ // Pure constructor. Throws on an invalid topicId or a non-integer/negative turnIndex (boundary input),
319
+ // keeping every constructed context well-formed; the transition helpers below never throw.
320
+ export function beginDiscussionTurn(mode, topicId, turnIndex) {
321
+ const reasons = discussionTopicIdReasons(topicId);
322
+ if (reasons.length > 0) {
323
+ throw new TypeError(`Invalid discussion topicId: ${reasons.join("; ")}`);
324
+ }
325
+ if (!Number.isInteger(turnIndex) || turnIndex < 0) {
326
+ throw new TypeError(`Invalid discussion turnIndex: ${JSON.stringify(turnIndex)}`);
327
+ }
328
+ return {
329
+ schemaVersion: DISCUSSION_INTELLIGENCE_SCHEMA_VERSION,
330
+ mode,
331
+ topicId,
332
+ turnIndex,
333
+ status: "active",
334
+ };
335
+ }
336
+ // Returns a NEW context at the target status, preserving mode/topicId/turnIndex, when the transition is
337
+ // legal; otherwise returns the input unchanged (the voice-transcript "snapshot, never throw" posture).
338
+ function transitionDiscussionTurn(ctx, to) {
339
+ if (!canTransitionDiscussionTurnStatus(ctx.status, to)) {
340
+ return ctx;
341
+ }
342
+ return { ...ctx, status: to };
343
+ }
344
+ // AC4 load-bearing helpers. interrupt: active→interrupted; recover: interrupted→recovered with
345
+ // mode/topicId/turnIndex UNCHANGED (the no-context-loss proof); resolve: →resolved.
346
+ export function applyDiscussionInterruption(ctx) {
347
+ return transitionDiscussionTurn(ctx, "interrupted");
348
+ }
349
+ export function applyDiscussionRecovery(ctx) {
350
+ return transitionDiscussionTurn(ctx, "recovered");
351
+ }
352
+ export function resolveDiscussionTurn(ctx) {
353
+ return transitionDiscussionTurn(ctx, "resolved");
354
+ }
355
+ // Counts + enums only; never any topicId or text. An optional confidence score maps to a level via
356
+ // `confidenceLevelFromScore`; when omitted the summary carries no confidence.
357
+ export function summarizeDiscussionTurn(ctx, confidenceScore) {
358
+ const base = {
359
+ mode: ctx.mode,
360
+ status: ctx.status,
361
+ turnIndex: ctx.turnIndex,
362
+ mandatedFacetCount: DISCUSSION_MODE_PLANS[ctx.mode].mandatedFacets.length,
363
+ };
364
+ if (confidenceScore === undefined) {
365
+ return base;
366
+ }
367
+ return { ...base, confidence: confidenceLevelFromScore(confidenceScore) };
368
+ }
369
+ function buildDiscussionResult(reasons) {
370
+ return reasons.length === 0 ? { ok: true } : { ok: false, reasons };
371
+ }
372
+ function isRecord(value) {
373
+ return typeof value === "object" && value !== null;
374
+ }
375
+ export function validateDiscussionTurnContext(value) {
376
+ if (!isRecord(value)) {
377
+ return { ok: false, reasons: ["context: must be an object"] };
378
+ }
379
+ const reasons = [];
380
+ if (!isDiscussionIntelligenceSchemaVersionSupported(value.schemaVersion)) {
381
+ reasons.push("schemaVersion: unsupported");
382
+ }
383
+ if (!isDiscussionMode(value.mode)) {
384
+ reasons.push("mode: unknown discussion mode");
385
+ }
386
+ for (const reason of discussionTopicIdReasons(value.topicId)) {
387
+ reasons.push(reason);
388
+ }
389
+ const turnIndex = value.turnIndex;
390
+ if (typeof turnIndex !== "number" || !Number.isInteger(turnIndex) || turnIndex < 0) {
391
+ reasons.push("turnIndex: must be a non-negative integer");
392
+ }
393
+ if (!isDiscussionTurnStatus(value.status)) {
394
+ reasons.push("status: unknown turn status");
395
+ }
396
+ return buildDiscussionResult(reasons);
397
+ }
398
+ function readStringArray(value) {
399
+ return Array.isArray(value)
400
+ ? value.filter((entry) => typeof entry === "string")
401
+ : [];
402
+ }
403
+ function validateModePlanFacets(mode, value, reasons) {
404
+ const disagreementCapable = mode !== "brainstorm";
405
+ const mandated = readStringArray(value);
406
+ for (const facet of DISAGREEMENT_FACETS) {
407
+ if (disagreementCapable && !mandated.includes(facet)) {
408
+ reasons.push(`mandatedFacets: ${mode} must mandate ${facet}`);
409
+ }
410
+ }
411
+ }
412
+ function validateModePlanDirectives(value, reasons) {
413
+ if (!Array.isArray(value) || value.length === 0) {
414
+ reasons.push("directives: must not be empty");
415
+ return;
416
+ }
417
+ for (const directive of value) {
418
+ if (typeof directive !== "string" ||
419
+ !DISCUSSION_DIRECTIVES.includes(directive)) {
420
+ reasons.push(`directives: unknown directive ${JSON.stringify(directive)}`);
421
+ }
422
+ }
423
+ }
424
+ export function validateDiscussionModePlan(value) {
425
+ if (!isRecord(value)) {
426
+ return { ok: false, reasons: ["plan: must be an object"] };
427
+ }
428
+ const mode = value.mode;
429
+ if (!isDiscussionMode(mode)) {
430
+ return { ok: false, reasons: ["mode: unknown discussion mode"] };
431
+ }
432
+ const reasons = [];
433
+ const canonical = DISCUSSION_MODE_PLANS[mode];
434
+ validateModePlanFacets(mode, value.mandatedFacets, reasons);
435
+ validateModePlanDirectives(value.directives, reasons);
436
+ if (value.producesDecisionRecommendation !== canonical.producesDecisionRecommendation) {
437
+ reasons.push("producesDecisionRecommendation: disagrees with canonical plan");
438
+ }
439
+ return buildDiscussionResult(reasons);
440
+ }
@@ -0,0 +1,65 @@
1
+ import { type EditorAgentActionStatus, type EditorAgentActionType, type EditorAgentConflictCode, type EditorAgentFailureCode } from "./editor-agent.js";
2
+ export declare const EDITOR_AGENT_AUDIT_SCHEMA_VERSION: "1";
3
+ export declare const EDITOR_AGENT_AUDIT_SUMMARY_MAX_CHARS = 200;
4
+ export type EditorAgentActionEffectClass = "navigation" | "layout" | "content-mutation" | "external-effect";
5
+ export declare const EDITOR_AGENT_ACTION_EFFECT_CLASS: Readonly<Record<EditorAgentActionType, EditorAgentActionEffectClass>>;
6
+ export declare function isMutatingEditorAgentAction(type: EditorAgentActionType): boolean;
7
+ export type EditorAgentActionDisposition = "allowed" | "review-required" | "denied";
8
+ export declare const EDITOR_AGENT_ACTION_DISPOSITIONS: readonly EditorAgentActionDisposition[];
9
+ export type EditorAgentActionDenyReason = "workspace-boundary-escape" | "denied-sensitive-path";
10
+ export declare const EDITOR_AGENT_ACTION_DENY_REASONS: readonly EditorAgentActionDenyReason[];
11
+ export type EditorAgentActionReviewReason = "content-mutation-requires-review" | "external-effect-requires-review";
12
+ export declare const EDITOR_AGENT_ACTION_REVIEW_REASONS: readonly EditorAgentActionReviewReason[];
13
+ export interface EditorAgentActionPolicyDecision {
14
+ readonly disposition: EditorAgentActionDisposition;
15
+ readonly effectClass: EditorAgentActionEffectClass;
16
+ readonly denyReason?: EditorAgentActionDenyReason | undefined;
17
+ readonly reviewReason?: EditorAgentActionReviewReason | undefined;
18
+ }
19
+ export interface EditorAgentActionPolicyContext {
20
+ readonly targetPath: string | null;
21
+ readonly targetSensitive: boolean;
22
+ }
23
+ export declare function classifyEditorAgentAction(type: EditorAgentActionType, context: EditorAgentActionPolicyContext): EditorAgentActionPolicyDecision;
24
+ export interface EditorAgentActionAuditRecord {
25
+ readonly schemaVersion: typeof EDITOR_AGENT_AUDIT_SCHEMA_VERSION;
26
+ readonly auditId: string;
27
+ readonly occurredAt: number;
28
+ readonly sessionId: string;
29
+ readonly actionId: string;
30
+ readonly actionType: EditorAgentActionType;
31
+ readonly effectClass: EditorAgentActionEffectClass;
32
+ readonly mutating: boolean;
33
+ readonly disposition: EditorAgentActionDisposition;
34
+ readonly denyReason?: EditorAgentActionDenyReason | undefined;
35
+ readonly reviewReason?: EditorAgentActionReviewReason | undefined;
36
+ readonly outcome: EditorAgentActionStatus;
37
+ readonly conflictCode?: EditorAgentConflictCode | undefined;
38
+ readonly failureCode?: EditorAgentFailureCode | undefined;
39
+ readonly targetPath?: string | undefined;
40
+ readonly editCount?: number | undefined;
41
+ readonly patchByteLength?: number | undefined;
42
+ readonly summary: string;
43
+ }
44
+ export interface EditorAgentActionAuditInput {
45
+ readonly auditId: string;
46
+ readonly occurredAt: number;
47
+ readonly sessionId: string;
48
+ readonly actionId: string;
49
+ readonly actionType: EditorAgentActionType;
50
+ readonly decision: EditorAgentActionPolicyDecision;
51
+ readonly outcome: EditorAgentActionStatus;
52
+ readonly conflictCode?: EditorAgentConflictCode | undefined;
53
+ readonly failureCode?: EditorAgentFailureCode | undefined;
54
+ readonly targetPath?: string | null | undefined;
55
+ readonly editCount?: number | undefined;
56
+ readonly patchByteLength?: number | undefined;
57
+ }
58
+ export declare function buildEditorAgentActionAuditRecord(input: EditorAgentActionAuditInput): EditorAgentActionAuditRecord;
59
+ export interface EditorAgentAuditResponse {
60
+ readonly records: readonly EditorAgentActionAuditRecord[];
61
+ }
62
+ export declare function isEditorAgentActionDisposition(value: unknown): value is EditorAgentActionDisposition;
63
+ export declare function isEditorAgentActionEffectClass(value: unknown): value is EditorAgentActionEffectClass;
64
+ export declare function isEditorAgentActionAuditRecord(value: unknown): value is EditorAgentActionAuditRecord;
65
+ //# sourceMappingURL=editor-agent-governance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-agent-governance.d.ts","sourceRoot":"","sources":["../src/editor-agent-governance.ts"],"names":[],"mappings":"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"}