@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/bff-wire.d.ts +33 -0
- package/dist/bff-wire.d.ts.map +1 -1
- package/dist/command-runner.d.ts +81 -0
- package/dist/command-runner.d.ts.map +1 -0
- package/dist/command-runner.js +209 -0
- package/dist/container-runtime.d.ts +125 -0
- package/dist/container-runtime.d.ts.map +1 -0
- package/dist/container-runtime.js +287 -0
- package/dist/context-engineering.js +2 -2
- package/dist/discussion-intelligence.d.ts +70 -0
- package/dist/discussion-intelligence.d.ts.map +1 -0
- package/dist/discussion-intelligence.js +440 -0
- package/dist/editor-agent-governance.d.ts +65 -0
- package/dist/editor-agent-governance.d.ts.map +1 -0
- package/dist/editor-agent-governance.js +180 -0
- package/dist/editor-agent.d.ts +29 -1
- package/dist/editor-agent.d.ts.map +1 -1
- package/dist/editor-agent.js +183 -6
- package/dist/editor-builtin-capabilities.d.ts +13 -0
- package/dist/editor-builtin-capabilities.d.ts.map +1 -0
- package/dist/editor-builtin-capabilities.js +135 -0
- package/dist/editor-language-mode-map.d.ts +11 -0
- package/dist/editor-language-mode-map.d.ts.map +1 -0
- package/dist/editor-language-mode-map.js +89 -0
- package/dist/editor-layout.d.ts +37 -0
- package/dist/editor-layout.d.ts.map +1 -1
- package/dist/editor-layout.js +125 -8
- package/dist/editor-workspace-path.d.ts +20 -0
- package/dist/editor-workspace-path.d.ts.map +1 -0
- package/dist/editor-workspace-path.js +133 -0
- package/dist/evidence.d.ts +3 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/gateway.d.ts +72 -3
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +73 -3
- package/dist/git-commit-intent.d.ts +28 -0
- package/dist/git-commit-intent.d.ts.map +1 -0
- package/dist/git-commit-intent.js +155 -0
- package/dist/git-commit-policy.d.ts +29 -0
- package/dist/git-commit-policy.d.ts.map +1 -0
- package/dist/git-commit-policy.js +173 -0
- package/dist/git-delivery-action-sheet.d.ts +157 -0
- package/dist/git-delivery-action-sheet.d.ts.map +1 -0
- package/dist/git-delivery-action-sheet.js +430 -0
- package/dist/git-delivery-evidence.d.ts +92 -0
- package/dist/git-delivery-evidence.d.ts.map +1 -0
- package/dist/git-delivery-evidence.js +272 -0
- package/dist/git-delivery-policy.d.ts +40 -0
- package/dist/git-delivery-policy.d.ts.map +1 -0
- package/dist/git-delivery-policy.js +183 -0
- package/dist/git-delivery-provider.d.ts +53 -0
- package/dist/git-delivery-provider.d.ts.map +1 -0
- package/dist/git-delivery-provider.js +96 -0
- package/dist/git-delivery.d.ts +202 -0
- package/dist/git-delivery.d.ts.map +1 -0
- package/dist/git-delivery.js +410 -0
- package/dist/git-history.d.ts +27 -0
- package/dist/git-history.d.ts.map +1 -0
- package/dist/git-history.js +73 -0
- package/dist/git-merge.d.ts +67 -0
- package/dist/git-merge.d.ts.map +1 -0
- package/dist/git-merge.js +323 -0
- package/dist/git-pull-request.d.ts +112 -0
- package/dist/git-pull-request.d.ts.map +1 -0
- package/dist/git-pull-request.js +351 -0
- package/dist/git-repository-agent.d.ts +46 -0
- package/dist/git-repository-agent.d.ts.map +1 -0
- package/dist/git-repository-agent.js +198 -0
- package/dist/git-repository-summary.d.ts +54 -0
- package/dist/git-repository-summary.d.ts.map +1 -0
- package/dist/git-repository-summary.js +105 -0
- package/dist/git-repository.d.ts +60 -0
- package/dist/git-repository.d.ts.map +1 -0
- package/dist/git-repository.js +106 -0
- package/dist/git-sync.d.ts +49 -0
- package/dist/git-sync.d.ts.map +1 -0
- package/dist/git-sync.js +110 -0
- package/dist/index.d.ts +63 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -6
- package/dist/lsp-process.d.ts +38 -0
- package/dist/lsp-process.d.ts.map +1 -0
- package/dist/lsp-process.js +103 -0
- package/dist/runtime-capabilities.d.ts +44 -0
- package/dist/runtime-capabilities.d.ts.map +1 -0
- package/dist/runtime-capabilities.js +141 -0
- package/dist/task-workspace.d.ts +302 -0
- package/dist/task-workspace.d.ts.map +1 -0
- package/dist/task-workspace.js +1236 -0
- package/dist/voice-action-intent.d.ts +86 -0
- package/dist/voice-action-intent.d.ts.map +1 -0
- package/dist/voice-action-intent.js +387 -0
- package/dist/voice-playback.d.ts +50 -0
- package/dist/voice-playback.d.ts.map +1 -0
- package/dist/voice-playback.js +240 -0
- package/dist/voice-protocol.d.ts +165 -0
- package/dist/voice-protocol.d.ts.map +1 -0
- package/dist/voice-protocol.js +312 -0
- package/dist/voice-transcript.d.ts +57 -0
- package/dist/voice-transcript.d.ts.map +1 -0
- package/dist/voice-transcript.js +221 -0
- package/package.json +5 -1
- package/dist/conversation-budget.d.ts +0 -37
- package/dist/conversation-budget.d.ts.map +0 -1
- 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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"}
|