open-multi-agent-kit 0.78.1 → 0.78.3

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 (102) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/MATURITY.md +4 -0
  3. package/README.md +70 -1
  4. package/dist/benchmark/contracts.d.ts +116 -0
  5. package/dist/benchmark/contracts.js +6 -0
  6. package/dist/benchmark/fixtures.d.ts +11 -0
  7. package/dist/benchmark/fixtures.js +121 -0
  8. package/dist/benchmark/harness.d.ts +13 -0
  9. package/dist/benchmark/harness.js +191 -0
  10. package/dist/benchmark/shadow-mode.d.ts +17 -0
  11. package/dist/benchmark/shadow-mode.js +96 -0
  12. package/dist/cli/register-spec-agent-goal-commands.js +45 -0
  13. package/dist/cli/release-promotion-gate.d.ts +14 -0
  14. package/dist/cli/release-promotion-gate.js +71 -0
  15. package/dist/cli/v2/release-commands.d.ts +29 -0
  16. package/dist/cli/v2/release-commands.js +95 -0
  17. package/dist/commands/chat/native-root-loop.js +14 -1
  18. package/dist/commands/chat/slash/commands/session.js +19 -1
  19. package/dist/commands/goal-interview.d.ts +18 -0
  20. package/dist/commands/goal-interview.js +396 -0
  21. package/dist/commands/merge.js +102 -56
  22. package/dist/contracts/interview.d.ts +106 -0
  23. package/dist/contracts/interview.js +9 -0
  24. package/dist/contracts/provider-health.d.ts +37 -0
  25. package/dist/contracts/provider-health.js +49 -1
  26. package/dist/evidence/evidence-trust-score.d.ts +101 -0
  27. package/dist/evidence/evidence-trust-score.js +408 -0
  28. package/dist/evidence/index.d.ts +6 -0
  29. package/dist/evidence/index.js +3 -0
  30. package/dist/evidence/proof-trust-cli.d.ts +8 -0
  31. package/dist/evidence/proof-trust-cli.js +27 -0
  32. package/dist/evidence/proof-trust.d.ts +14 -0
  33. package/dist/evidence/proof-trust.js +381 -0
  34. package/dist/evidence/regression-proof-matrix.d.ts +42 -0
  35. package/dist/evidence/regression-proof-matrix.js +72 -0
  36. package/dist/goal/intent-frame.d.ts +6 -0
  37. package/dist/goal/intent-frame.js +21 -9
  38. package/dist/goal/interview-assimilation.d.ts +13 -0
  39. package/dist/goal/interview-assimilation.js +383 -0
  40. package/dist/goal/interview-question-bank.d.ts +11 -0
  41. package/dist/goal/interview-question-bank.js +225 -0
  42. package/dist/goal/interview-scoring.d.ts +31 -0
  43. package/dist/goal/interview-scoring.js +187 -0
  44. package/dist/goal/interview-session.d.ts +25 -0
  45. package/dist/goal/interview-session.js +116 -0
  46. package/dist/input/input-envelope.d.ts +22 -0
  47. package/dist/input/input-envelope.js +1 -0
  48. package/dist/orchestration/merge-arbiter.d.ts +91 -0
  49. package/dist/orchestration/merge-arbiter.js +376 -0
  50. package/dist/providers/health.d.ts +3 -0
  51. package/dist/providers/health.js +46 -0
  52. package/dist/providers/index.d.ts +1 -0
  53. package/dist/providers/index.js +1 -0
  54. package/dist/providers/provider-health.d.ts +8 -1
  55. package/dist/providers/provider-health.js +39 -0
  56. package/dist/providers/provider-task-runner.js +31 -0
  57. package/dist/providers/provider.d.ts +2 -0
  58. package/dist/providers/router.js +87 -3
  59. package/dist/providers/types.d.ts +4 -0
  60. package/dist/runtime/advanced-control-loop.d.ts +60 -0
  61. package/dist/runtime/advanced-control-loop.js +136 -0
  62. package/dist/runtime/agent-runtime.d.ts +10 -0
  63. package/dist/runtime/blast-radius.d.ts +10 -0
  64. package/dist/runtime/blast-radius.js +14 -0
  65. package/dist/runtime/contracts/evidence.d.ts +87 -0
  66. package/dist/runtime/contracts/evidence.js +7 -0
  67. package/dist/runtime/contracts/router-v2.d.ts +44 -0
  68. package/dist/runtime/contracts/router-v2.js +4 -0
  69. package/dist/runtime/contracts/weakness-remediation.d.ts +67 -0
  70. package/dist/runtime/contracts/weakness-remediation.js +36 -0
  71. package/dist/runtime/kimi-api-runtime.js +59 -1
  72. package/dist/runtime/proof-bundle-trust.d.ts +74 -0
  73. package/dist/runtime/proof-bundle-trust.js +100 -0
  74. package/dist/runtime/provider-maturity-gate.d.ts +43 -0
  75. package/dist/runtime/provider-maturity-gate.js +129 -0
  76. package/dist/runtime/public-surface.d.ts +93 -0
  77. package/dist/runtime/public-surface.js +146 -0
  78. package/dist/runtime/router-v2-scoring.d.ts +11 -0
  79. package/dist/runtime/router-v2-scoring.js +151 -0
  80. package/dist/runtime/tool-dispatch-contracts.d.ts +24 -3
  81. package/dist/runtime/tool-dispatch-contracts.js +42 -2
  82. package/dist/runtime/weakness-remediation-index.d.ts +27 -0
  83. package/dist/runtime/weakness-remediation-index.js +37 -0
  84. package/dist/safety/enforcement-engine.d.ts +89 -0
  85. package/dist/safety/enforcement-engine.js +279 -0
  86. package/dist/safety/tool-authority-gate.d.ts +40 -0
  87. package/dist/safety/tool-authority-gate.js +92 -0
  88. package/dist/schema/evidence.schema.d.ts +2 -2
  89. package/dist/schema/proof-bundle.schema.d.ts +28 -28
  90. package/dist/util/clipboard-image.d.ts +49 -0
  91. package/dist/util/clipboard-image.js +263 -0
  92. package/docs/2026-06-09/critical-issues.md +20 -0
  93. package/docs/2026-06-09/improvements.md +14 -0
  94. package/docs/2026-06-09/init-checklist.md +25 -0
  95. package/docs/2026-06-09/plan.md +20 -0
  96. package/docs/benchmark-design.md +122 -0
  97. package/docs/github-organic-promotion.md +127 -0
  98. package/docs/native-root-runtime-algorithms.md +301 -0
  99. package/package.json +8 -4
  100. package/readmeasset/ASSET_INDEX.md +1 -0
  101. package/templates/skills/agents/omk-agent-reach-websearch/SKILL.md +55 -0
  102. package/templates/skills/kimi/omk-agent-reach-websearch/SKILL.md +55 -0
@@ -0,0 +1,383 @@
1
+ // Module: src/goal/interview-assimilation.ts
2
+ // Owner: Deep Interview phase 2 (answer assimilation + conflict resolution + spec-delta apply)
3
+ //
4
+ // Deterministic, offline, NO LLM, NO network. Pure logic over the interview /
5
+ // goal contracts. The only side-effecting call is `new Date().toISOString()`
6
+ // inside `applyInterviewDelta` (the contract's single allowed timestamp site).
7
+ //
8
+ // Conflict resolver priority (highest wins):
9
+ // 1. explicit answer (a non-skipped, non-empty interview answer)
10
+ // 2. existing explicit value (e.g. SuccessCriterion.inferred === false,
11
+ // or a non-empty objective NOT derived from
12
+ // the inferred IntentFrame)
13
+ // 3. inferred field (values produced by intent inference)
14
+ // 4. heuristic default (scaffolding defaults)
15
+ //
16
+ // `add` ops always append and therefore never contradict. `replace` ops on
17
+ // `objective` / `riskLevel` are the only ones that can collide: when the goal
18
+ // already holds an explicit value and the new answer differs materially, we
19
+ // record a human-readable contradiction (and lower confidence to 0.5) instead
20
+ // of silently overwriting. The downstream `applyInterviewDelta` re-checks the
21
+ // goal state so it stays self-consistent even with externally-built deltas.
22
+ import { INTERVIEW_DELTA_SCHEMA_VERSION } from "../contracts/interview.js";
23
+ const EXPLICIT_CONFIDENCE = 0.9;
24
+ const CONFLICTED_CONFIDENCE = 0.5;
25
+ const HIGH_RISK_RE = /production|prod\b|migrat|database|deploy|보안|배포|권한|마이그/i;
26
+ const LOW_RISK_RE = /docs only|문서만|read[- ]?only|읽기 전용/i;
27
+ const COMMAND_RE = /\b(test|tests|spec|specs|command|cmd|run|build|lint|typecheck|pytest|jest|vitest|npm|yarn|pnpm|make|check|ci)\b/i;
28
+ const PATH_RE = /[\w./-]+\.[a-zA-Z0-9]+/;
29
+ // ---------------------------------------------------------------------------
30
+ // assimilateAnswers
31
+ // ---------------------------------------------------------------------------
32
+ export function assimilateAnswers(input) {
33
+ const { questions, answers, goal } = input;
34
+ const questionById = new Map(questions.map((q) => [q.id, q]));
35
+ const findings = [];
36
+ const changes = [];
37
+ const contradictions = [];
38
+ const record = (question, field, op, value, confidence, conflict) => {
39
+ changes.push({ field, op, value, sourceQuestionId: question.id, confidence });
40
+ findings.push({ field: question.targetField, value, sourceQuestionId: question.id, confidence, conflict });
41
+ };
42
+ for (const answer of answers) {
43
+ if (answer.skipped)
44
+ continue;
45
+ const text = answer.answer.trim();
46
+ if (text.length === 0)
47
+ continue;
48
+ const question = questionById.get(answer.questionId);
49
+ if (!question)
50
+ continue;
51
+ switch (question.targetField) {
52
+ case "objective": {
53
+ let confidence = EXPLICIT_CONFIDENCE;
54
+ let conflict = false;
55
+ if (isObjectiveExplicit(goal) && differsMaterially(goal?.objective ?? "", text)) {
56
+ conflict = true;
57
+ confidence = CONFLICTED_CONFIDENCE;
58
+ contradictions.push(`objective: answer ${question.id} conflicts with existing explicit objective ` +
59
+ `("${truncate(goal?.objective ?? "")}" vs "${truncate(text)}")`);
60
+ }
61
+ record(question, "objective", "replace", text, confidence, conflict);
62
+ break;
63
+ }
64
+ case "successCriteria": {
65
+ for (const item of splitIntoItems(text, 3)) {
66
+ record(question, "successCriteria", "add", item, EXPLICIT_CONFIDENCE, false);
67
+ }
68
+ break;
69
+ }
70
+ case "expectedArtifacts": {
71
+ const path = extractPath(text);
72
+ const gate = COMMAND_RE.test(text) ? "command-pass" : "file-exists";
73
+ const name = path ? basename(path) : truncate(firstNonEmptyLine(text), 80);
74
+ const value = path !== undefined ? { name, path, gate } : { name, gate };
75
+ record(question, "expectedArtifacts", "add", value, EXPLICIT_CONFIDENCE, false);
76
+ break;
77
+ }
78
+ case "constraints": {
79
+ record(question, "constraints", "add", text, EXPLICIT_CONFIDENCE, false);
80
+ break;
81
+ }
82
+ case "nonGoals": {
83
+ record(question, "nonGoals", "add", text, EXPLICIT_CONFIDENCE, false);
84
+ break;
85
+ }
86
+ case "risks": {
87
+ const level = computeRiskLevel(text);
88
+ record(question, "risks", "add", { description: text, level }, EXPLICIT_CONFIDENCE, false);
89
+ // Escalate the goal-level riskLevel when a risk answer is high.
90
+ // Never downgrade here (applyInterviewDelta enforces escalate-only).
91
+ if (level === "high") {
92
+ record(question, "riskLevel", "replace", "high", EXPLICIT_CONFIDENCE, false);
93
+ }
94
+ break;
95
+ }
96
+ case "riskLevel": {
97
+ const newLevel = computeRiskLevel(text);
98
+ let confidence = EXPLICIT_CONFIDENCE;
99
+ let conflict = false;
100
+ if (goal && goal.riskLevel === "high" && riskRank(newLevel) < riskRank("high")) {
101
+ conflict = true;
102
+ confidence = CONFLICTED_CONFIDENCE;
103
+ contradictions.push(`riskLevel: answer ${question.id} would downgrade explicit "high" risk to "${newLevel}"`);
104
+ }
105
+ record(question, "riskLevel", "replace", newLevel, confidence, conflict);
106
+ break;
107
+ }
108
+ // intentFrame / actionAtoms are not assimilated by this module.
109
+ default:
110
+ break;
111
+ }
112
+ }
113
+ const specDelta = {
114
+ schemaVersion: INTERVIEW_DELTA_SCHEMA_VERSION,
115
+ goalId: goal?.goalId,
116
+ changes,
117
+ };
118
+ return { findings, specDelta, contradictions };
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // applyInterviewDelta
122
+ // ---------------------------------------------------------------------------
123
+ export function applyInterviewDelta(goal, delta) {
124
+ // Clone: new top-level object + fresh copies of every collection we mutate so
125
+ // the caller's GoalSpec is never touched. Existing nested entries are reused
126
+ // by reference but never mutated (we only push new entries / reassign scalars).
127
+ const next = {
128
+ ...goal,
129
+ successCriteria: [...goal.successCriteria],
130
+ constraints: [...goal.constraints],
131
+ nonGoals: [...goal.nonGoals],
132
+ risks: [...goal.risks],
133
+ expectedArtifacts: [...goal.expectedArtifacts],
134
+ runIds: [...goal.runIds],
135
+ };
136
+ const appliedChanges = [];
137
+ const skippedChanges = [];
138
+ const contradictions = [];
139
+ for (const change of delta.changes) {
140
+ if (change.op === "add") {
141
+ switch (change.field) {
142
+ case "successCriteria": {
143
+ const description = asString(change.value);
144
+ if (!description) {
145
+ skippedChanges.push(change);
146
+ break;
147
+ }
148
+ const requirement = next.successCriteria.length === 0 ? "required" : "optional";
149
+ const criterion = {
150
+ id: `criterion-${next.successCriteria.length + 1}`,
151
+ description,
152
+ requirement,
153
+ weight: requirement === "required" ? 1.0 : 0.5,
154
+ inferred: false,
155
+ };
156
+ next.successCriteria.push(criterion);
157
+ appliedChanges.push(change);
158
+ break;
159
+ }
160
+ case "expectedArtifacts": {
161
+ const artifact = asArtifactValue(change.value);
162
+ if (!artifact) {
163
+ skippedChanges.push(change);
164
+ break;
165
+ }
166
+ const entry = { name: artifact.name };
167
+ if (artifact.path !== undefined)
168
+ entry.path = artifact.path;
169
+ if (artifact.gate !== undefined)
170
+ entry.gate = artifact.gate;
171
+ next.expectedArtifacts.push(entry);
172
+ appliedChanges.push(change);
173
+ break;
174
+ }
175
+ case "constraints": {
176
+ const description = asString(change.value);
177
+ if (!description) {
178
+ skippedChanges.push(change);
179
+ break;
180
+ }
181
+ const constraint = { id: `constraint-${next.constraints.length + 1}`, description };
182
+ next.constraints.push(constraint);
183
+ appliedChanges.push(change);
184
+ break;
185
+ }
186
+ case "nonGoals": {
187
+ const value = asString(change.value);
188
+ if (!value) {
189
+ skippedChanges.push(change);
190
+ break;
191
+ }
192
+ next.nonGoals.push(value);
193
+ appliedChanges.push(change);
194
+ break;
195
+ }
196
+ case "risks": {
197
+ const risk = asRiskValue(change.value);
198
+ if (!risk) {
199
+ skippedChanges.push(change);
200
+ break;
201
+ }
202
+ const entry = { id: `risk-${next.risks.length + 1}`, description: risk.description, level: risk.level };
203
+ next.risks.push(entry);
204
+ appliedChanges.push(change);
205
+ break;
206
+ }
207
+ default:
208
+ skippedChanges.push(change);
209
+ break;
210
+ }
211
+ continue;
212
+ }
213
+ if (change.op === "replace") {
214
+ switch (change.field) {
215
+ case "riskLevel": {
216
+ const newLevel = asRiskLevel(change.value);
217
+ if (newLevel === null || change.confidence < CONFLICTED_CONFIDENCE) {
218
+ skippedChanges.push(change);
219
+ break;
220
+ }
221
+ const current = next.riskLevel;
222
+ const downgradeFromHigh = current === "high" && riskRank(newLevel) < riskRank(current);
223
+ if (downgradeFromHigh) {
224
+ contradictions.push(`riskLevel: skipped downgrade of explicit "high" risk to "${newLevel}" (from ${change.sourceQuestionId})`);
225
+ skippedChanges.push(change);
226
+ }
227
+ else if (newLevel !== current) {
228
+ // higher-or-different
229
+ next.riskLevel = newLevel;
230
+ appliedChanges.push(change);
231
+ }
232
+ else {
233
+ skippedChanges.push(change);
234
+ }
235
+ break;
236
+ }
237
+ case "objective": {
238
+ const newObjective = asString(change.value);
239
+ if (!newObjective) {
240
+ skippedChanges.push(change);
241
+ break;
242
+ }
243
+ if (isObjectiveExplicit(next) &&
244
+ change.confidence < EXPLICIT_CONFIDENCE &&
245
+ differsMaterially(next.objective, newObjective)) {
246
+ contradictions.push(`objective: skipped overwrite of explicit objective with low-confidence answer (from ${change.sourceQuestionId})`);
247
+ skippedChanges.push(change);
248
+ }
249
+ else {
250
+ next.objective = newObjective;
251
+ appliedChanges.push(change);
252
+ }
253
+ break;
254
+ }
255
+ default:
256
+ skippedChanges.push(change);
257
+ break;
258
+ }
259
+ continue;
260
+ }
261
+ // "remove" and any unknown op are not supported by this module.
262
+ skippedChanges.push(change);
263
+ }
264
+ // The single allowed timestamp site for this module.
265
+ next.updatedAt = new Date().toISOString();
266
+ return { goal: next, appliedChanges, skippedChanges, contradictions };
267
+ }
268
+ // ---------------------------------------------------------------------------
269
+ // Internal helpers (pure)
270
+ // ---------------------------------------------------------------------------
271
+ /**
272
+ * A goal's objective is "explicit" when it is non-empty and was not merely
273
+ * lifted from the inferred IntentFrame (problem / desiredOutcome). Without a
274
+ * dedicated flag on GoalSpec.objective this is the most reliable deterministic
275
+ * signal that the value came from a user/prompt source rather than inference.
276
+ */
277
+ function isObjectiveExplicit(goal) {
278
+ if (!goal)
279
+ return false;
280
+ const objective = goal.objective?.trim() ?? "";
281
+ if (objective.length === 0)
282
+ return false;
283
+ const frame = goal.intentFrame;
284
+ if (frame) {
285
+ const desired = frame.desiredOutcome?.trim() ?? "";
286
+ const problem = frame.problem?.trim() ?? "";
287
+ if (objective === desired || objective === problem)
288
+ return false;
289
+ }
290
+ return true;
291
+ }
292
+ function computeRiskLevel(text) {
293
+ if (HIGH_RISK_RE.test(text))
294
+ return "high";
295
+ if (LOW_RISK_RE.test(text))
296
+ return "low";
297
+ return "medium";
298
+ }
299
+ function riskRank(level) {
300
+ return level === "high" ? 2 : level === "medium" ? 1 : 0;
301
+ }
302
+ function differsMaterially(a, b) {
303
+ return normalize(a) !== normalize(b);
304
+ }
305
+ function normalize(value) {
306
+ return value.trim().toLowerCase().replace(/\s+/g, " ");
307
+ }
308
+ /** Split answer text into list items on newlines or inline numbered markers. */
309
+ function splitIntoItems(text, max) {
310
+ const byLine = text
311
+ .split(/\r?\n/)
312
+ .map((line) => line.trim())
313
+ .filter((line) => line.length > 0);
314
+ let parts;
315
+ if (byLine.length > 1) {
316
+ parts = byLine;
317
+ }
318
+ else {
319
+ const single = byLine[0] ?? text.trim();
320
+ const inline = single
321
+ .split(/(?=\b\d+[.)]\s)/)
322
+ .map((segment) => segment.trim())
323
+ .filter((segment) => segment.length > 0);
324
+ parts = inline.length > 1 ? inline : [single];
325
+ }
326
+ return parts
327
+ .map(stripListMarker)
328
+ .map((item) => item.trim())
329
+ .filter((item) => item.length > 0)
330
+ .slice(0, max);
331
+ }
332
+ function stripListMarker(value) {
333
+ return value.replace(/^\s*(?:\d+[.)]|[-*•])\s+/, "").trim();
334
+ }
335
+ function extractPath(text) {
336
+ const match = text.match(PATH_RE);
337
+ return match ? match[0] : undefined;
338
+ }
339
+ function basename(path) {
340
+ const segments = path.split("/");
341
+ return segments[segments.length - 1] || path;
342
+ }
343
+ function firstNonEmptyLine(text) {
344
+ for (const line of text.split(/\r?\n/)) {
345
+ const trimmed = line.trim();
346
+ if (trimmed.length > 0)
347
+ return trimmed;
348
+ }
349
+ return text.trim();
350
+ }
351
+ function truncate(value, max = 60) {
352
+ const trimmed = value.trim();
353
+ return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max - 1)}…`;
354
+ }
355
+ function asString(value) {
356
+ return typeof value === "string" ? value.trim() : "";
357
+ }
358
+ function asRiskLevel(value) {
359
+ return value === "low" || value === "medium" || value === "high" ? value : null;
360
+ }
361
+ function asArtifactValue(value) {
362
+ if (typeof value !== "object" || value === null)
363
+ return null;
364
+ const record = value;
365
+ const name = typeof record.name === "string" ? record.name.trim() : "";
366
+ if (name.length === 0)
367
+ return null;
368
+ const path = typeof record.path === "string" && record.path.length > 0 ? record.path : undefined;
369
+ const gate = record.gate === "file-exists" || record.gate === "command-pass" || record.gate === "summary"
370
+ ? record.gate
371
+ : undefined;
372
+ return { name, path, gate };
373
+ }
374
+ function asRiskValue(value) {
375
+ if (typeof value !== "object" || value === null)
376
+ return null;
377
+ const record = value;
378
+ const description = typeof record.description === "string" ? record.description.trim() : "";
379
+ const level = asRiskLevel(record.level);
380
+ if (description.length === 0 || level === null)
381
+ return null;
382
+ return { description, level };
383
+ }
@@ -0,0 +1,11 @@
1
+ import type { InterviewQuestion, InterviewSeed } from "../contracts/interview.js";
2
+ /**
3
+ * Build the deterministic candidate-question bank for a seed.
4
+ *
5
+ * The result always contains the 10 axes in a stable order. Signals are
6
+ * adjusted deterministically: risk/authority/rollback axes are boosted on
7
+ * high-risk seeds, and any axis already satisfied by `seed.goal` has its
8
+ * informationGain reduced so the caller's ranker downranks it. `score` is
9
+ * intentionally omitted — the caller assigns it.
10
+ */
11
+ export declare function buildInterviewQuestionBank(seed: InterviewSeed): Array<Omit<InterviewQuestion, "score">>;
@@ -0,0 +1,225 @@
1
+ // Module: src/goal/interview-question-bank.ts
2
+ // Owner: Interview Question Bank Worker (Deep Interview phase)
3
+ //
4
+ // Deterministic, offline candidate-question bank for the OMK Deep Interview.
5
+ // NO LLM calls, NO network, NO Date, NO randomness: the same `InterviewSeed`
6
+ // always yields the same candidate set. The caller is responsible for scoring
7
+ // (the returned candidates intentionally omit `score`).
8
+ /**
9
+ * Seeds matching this pattern (or a `high` riskLevel) boost the risk/authority/
10
+ * rollback axes so the ranker surfaces safety questions earlier.
11
+ */
12
+ const HIGH_RISK_PATTERN = /production|deploy|migrat|database|보안|배포|마이그/i;
13
+ /** Axes that defend against execution risk and benefit from high-risk boosts. */
14
+ const RISK_AXIS_IDS = new Set(["q-risk", "q-authority", "q-rollback"]);
15
+ /** Multiplier applied to informationGain when the goal already fills the axis. */
16
+ const POPULATED_DOWNRANK = 0.4;
17
+ /**
18
+ * The 10 deep-interview axes. Base signals follow the deterministic guidance:
19
+ * - required-true axes start with informationGain >= 0.8;
20
+ * - artifact/verification/success-criteria carry evidenceImpact >= 0.7 and
21
+ * dagImpact >= 0.6;
22
+ * - scope/non-goal carry moderate dagImpact (~0.5);
23
+ * - risk/authority/rollback carry riskReduction >= 0.7;
24
+ * - userCost stays in the ~0.2-0.4 band.
25
+ */
26
+ const QUESTION_SPECS = [
27
+ {
28
+ id: "q-objective",
29
+ kind: "objective",
30
+ targetField: "objective",
31
+ required: true,
32
+ prompt: "이 작업이 최종적으로 무엇이 되면 성공인지 한 문장으로 정의해줘.",
33
+ informationGain: 0.9,
34
+ riskReduction: 0.3,
35
+ dagImpact: 0.7,
36
+ evidenceImpact: 0.5,
37
+ userCost: 0.2,
38
+ },
39
+ {
40
+ id: "q-success-criteria",
41
+ kind: "success-criteria",
42
+ targetField: "successCriteria",
43
+ required: true,
44
+ prompt: "완료를 판단할 필수 성공 기준을 1-3개로 써줘.",
45
+ informationGain: 0.86,
46
+ riskReduction: 0.4,
47
+ dagImpact: 0.65,
48
+ evidenceImpact: 0.8,
49
+ userCost: 0.3,
50
+ },
51
+ {
52
+ id: "q-artifact",
53
+ kind: "artifact",
54
+ targetField: "expectedArtifacts",
55
+ required: true,
56
+ prompt: "반드시 생성/수정되어야 하는 산출물을 경로까지 적어줘. 예: src/commands/goal-interview.ts",
57
+ informationGain: 0.83,
58
+ riskReduction: 0.35,
59
+ dagImpact: 0.7,
60
+ evidenceImpact: 0.85,
61
+ userCost: 0.3,
62
+ },
63
+ {
64
+ id: "q-scope",
65
+ kind: "constraint",
66
+ targetField: "constraints",
67
+ required: false,
68
+ prompt: "수정 가능한 범위를 구체적으로 지정해줘. 예: src/ 만, docs 만, 전체 repo.",
69
+ informationGain: 0.6,
70
+ riskReduction: 0.5,
71
+ dagImpact: 0.5,
72
+ evidenceImpact: 0.4,
73
+ userCost: 0.3,
74
+ },
75
+ {
76
+ id: "q-non-goal",
77
+ kind: "non-goal",
78
+ targetField: "nonGoals",
79
+ required: false,
80
+ prompt: "절대 하지 말아야 할 행동이 있어? 예: npm publish 금지, git push 금지, production config 수정 금지.",
81
+ informationGain: 0.55,
82
+ riskReduction: 0.5,
83
+ dagImpact: 0.5,
84
+ evidenceImpact: 0.35,
85
+ userCost: 0.3,
86
+ },
87
+ {
88
+ id: "q-verification",
89
+ kind: "evidence",
90
+ targetField: "successCriteria",
91
+ required: true,
92
+ prompt: "검증 명령/테스트가 있으면 적어줘. 없으면 OMK가 추론해도 되는지 답해줘.",
93
+ informationGain: 0.8,
94
+ riskReduction: 0.5,
95
+ dagImpact: 0.6,
96
+ evidenceImpact: 0.85,
97
+ userCost: 0.35,
98
+ },
99
+ {
100
+ id: "q-risk",
101
+ kind: "risk",
102
+ targetField: "risks",
103
+ required: false,
104
+ prompt: "데이터/보안/배포/권한 측면의 위험이 있어?",
105
+ informationGain: 0.6,
106
+ riskReduction: 0.75,
107
+ dagImpact: 0.55,
108
+ evidenceImpact: 0.5,
109
+ userCost: 0.35,
110
+ },
111
+ {
112
+ id: "q-authority",
113
+ kind: "authority",
114
+ targetField: "riskLevel",
115
+ required: false,
116
+ prompt: "허용 권한 수준을 알려줘. write/shell/merge 중 어디까지 허용해?",
117
+ informationGain: 0.55,
118
+ riskReduction: 0.7,
119
+ dagImpact: 0.55,
120
+ evidenceImpact: 0.45,
121
+ userCost: 0.25,
122
+ },
123
+ {
124
+ id: "q-dependency",
125
+ kind: "dependency",
126
+ targetField: "intentFrame",
127
+ required: false,
128
+ prompt: "참고해야 할 파일/문서/외부 시스템이 있어?",
129
+ informationGain: 0.55,
130
+ riskReduction: 0.4,
131
+ dagImpact: 0.5,
132
+ evidenceImpact: 0.45,
133
+ userCost: 0.35,
134
+ },
135
+ {
136
+ id: "q-rollback",
137
+ kind: "rollback",
138
+ targetField: "riskLevel",
139
+ required: false,
140
+ prompt: "실패 시 rollback 또는 중단 조건이 있어?",
141
+ informationGain: 0.5,
142
+ riskReduction: 0.7,
143
+ dagImpact: 0.55,
144
+ evidenceImpact: 0.5,
145
+ userCost: 0.3,
146
+ },
147
+ ];
148
+ /** Clamp a signal into [0,1] and round to 2 decimals for stable output. */
149
+ function clamp01(value) {
150
+ const bounded = value < 0 ? 0 : value > 1 ? 1 : value;
151
+ return Math.round(bounded * 100) / 100;
152
+ }
153
+ /** True when the seed describes risk-sensitive work (prompt match or high risk). */
154
+ function isHighRiskSeed(seed) {
155
+ if (seed.riskLevel === "high") {
156
+ return true;
157
+ }
158
+ return HIGH_RISK_PATTERN.test(seed.rawPrompt);
159
+ }
160
+ /**
161
+ * True when the goal already carries a usable value for the axis, so the axis
162
+ * question can be downranked. `riskLevel` always has a default and does not
163
+ * capture authority/rollback intent, so those axes are never auto-populated.
164
+ */
165
+ function isAxisPopulated(goal, id) {
166
+ if (!goal) {
167
+ return false;
168
+ }
169
+ switch (id) {
170
+ case "q-objective":
171
+ return goal.objective.trim().length > 0;
172
+ case "q-success-criteria":
173
+ case "q-verification":
174
+ return goal.successCriteria.length > 0;
175
+ case "q-artifact":
176
+ return goal.expectedArtifacts.length > 0;
177
+ case "q-scope":
178
+ return goal.constraints.length > 0;
179
+ case "q-non-goal":
180
+ return goal.nonGoals.length > 0;
181
+ case "q-risk":
182
+ return goal.risks.length > 0;
183
+ case "q-dependency":
184
+ return (goal.intentFrame?.entities.length ?? 0) > 0;
185
+ default:
186
+ return false;
187
+ }
188
+ }
189
+ /**
190
+ * Build the deterministic candidate-question bank for a seed.
191
+ *
192
+ * The result always contains the 10 axes in a stable order. Signals are
193
+ * adjusted deterministically: risk/authority/rollback axes are boosted on
194
+ * high-risk seeds, and any axis already satisfied by `seed.goal` has its
195
+ * informationGain reduced so the caller's ranker downranks it. `score` is
196
+ * intentionally omitted — the caller assigns it.
197
+ */
198
+ export function buildInterviewQuestionBank(seed) {
199
+ const highRisk = isHighRiskSeed(seed);
200
+ return QUESTION_SPECS.map((spec) => {
201
+ let informationGain = spec.informationGain;
202
+ let riskReduction = spec.riskReduction;
203
+ let dagImpact = spec.dagImpact;
204
+ if (highRisk && RISK_AXIS_IDS.has(spec.id)) {
205
+ informationGain += 0.1;
206
+ riskReduction = Math.max(riskReduction + 0.15, 0.85);
207
+ dagImpact = Math.max(dagImpact + 0.2, 0.7);
208
+ }
209
+ if (isAxisPopulated(seed.goal, spec.id)) {
210
+ informationGain *= POPULATED_DOWNRANK;
211
+ }
212
+ return {
213
+ id: spec.id,
214
+ kind: spec.kind,
215
+ prompt: spec.prompt,
216
+ required: spec.required,
217
+ targetField: spec.targetField,
218
+ informationGain: clamp01(informationGain),
219
+ riskReduction: clamp01(riskReduction),
220
+ dagImpact: clamp01(dagImpact),
221
+ evidenceImpact: clamp01(spec.evidenceImpact),
222
+ userCost: clamp01(spec.userCost),
223
+ };
224
+ });
225
+ }
@@ -0,0 +1,31 @@
1
+ import type { GoalSpec } from "../contracts/goal.js";
2
+ import type { InterviewCompleteness, InterviewDepth, InterviewFinding, InterviewQuestion, InterviewSeed } from "../contracts/interview.js";
3
+ /** Max questions surfaced per interview depth. */
4
+ export declare const DEPTH_LIMITS: Record<InterviewDepth, number>;
5
+ /**
6
+ * Derive the ranking score for a candidate question.
7
+ * score = informationGain*0.35 + riskReduction*0.25 + dagImpact*0.20
8
+ * + evidenceImpact*0.15 - userCost*0.05, clamped to [0,1].
9
+ */
10
+ export declare function scoreInterviewQuestion(input: Omit<InterviewQuestion, "score">): InterviewQuestion;
11
+ /**
12
+ * Select the highest-value questions for a depth, dropping questions whose
13
+ * target field is already answered by the goal (unless marked required).
14
+ */
15
+ export declare function selectInterviewQuestions(goal: GoalSpec | undefined, candidates: InterviewQuestion[], depth: InterviewDepth, maxQuestions?: number): InterviewQuestion[];
16
+ /**
17
+ * Ambiguity 0..1 — higher means more interview is needed. Computed as the
18
+ * weighted sum of axes that are neither described in the prompt nor present on
19
+ * the goal. Axis weights sum to 1.
20
+ */
21
+ export declare function computeAmbiguity(seed: InterviewSeed): number;
22
+ /**
23
+ * Map an ambiguity score to a recommended depth.
24
+ * <0.25 light (caller may skip) · <0.50 light · <0.75 standard · else deep.
25
+ */
26
+ export declare function recommendDepth(ambiguity: number): InterviewDepth;
27
+ /**
28
+ * Completeness across required and supporting axes, blending populated goal
29
+ * fields with assimilated interview findings.
30
+ */
31
+ export declare function computeCompleteness(goal: GoalSpec | undefined, findings: InterviewFinding[]): InterviewCompleteness;