open-multi-agent-kit 0.78.1 → 0.78.2
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/CHANGELOG.md +12 -0
- package/MATURITY.md +4 -0
- package/README.md +70 -1
- package/dist/cli/register-spec-agent-goal-commands.js +45 -0
- package/dist/cli/release-promotion-gate.d.ts +14 -0
- package/dist/cli/release-promotion-gate.js +71 -0
- package/dist/cli/v2/release-commands.d.ts +29 -0
- package/dist/cli/v2/release-commands.js +95 -0
- package/dist/commands/chat/native-root-loop.js +14 -1
- package/dist/commands/chat/slash/commands/session.js +19 -1
- package/dist/commands/goal-interview.d.ts +18 -0
- package/dist/commands/goal-interview.js +396 -0
- package/dist/contracts/interview.d.ts +106 -0
- package/dist/contracts/interview.js +9 -0
- package/dist/evidence/index.d.ts +4 -0
- package/dist/evidence/index.js +2 -0
- package/dist/evidence/proof-trust-cli.d.ts +8 -0
- package/dist/evidence/proof-trust-cli.js +27 -0
- package/dist/evidence/proof-trust.d.ts +14 -0
- package/dist/evidence/proof-trust.js +381 -0
- package/dist/evidence/regression-proof-matrix.d.ts +42 -0
- package/dist/evidence/regression-proof-matrix.js +72 -0
- package/dist/goal/intent-frame.d.ts +6 -0
- package/dist/goal/intent-frame.js +21 -9
- package/dist/goal/interview-assimilation.d.ts +13 -0
- package/dist/goal/interview-assimilation.js +383 -0
- package/dist/goal/interview-question-bank.d.ts +11 -0
- package/dist/goal/interview-question-bank.js +225 -0
- package/dist/goal/interview-scoring.d.ts +31 -0
- package/dist/goal/interview-scoring.js +187 -0
- package/dist/goal/interview-session.d.ts +25 -0
- package/dist/goal/interview-session.js +116 -0
- package/dist/input/input-envelope.d.ts +22 -0
- package/dist/input/input-envelope.js +1 -0
- package/dist/runtime/advanced-control-loop.d.ts +60 -0
- package/dist/runtime/advanced-control-loop.js +136 -0
- package/dist/runtime/agent-runtime.d.ts +10 -0
- package/dist/runtime/blast-radius.d.ts +10 -0
- package/dist/runtime/blast-radius.js +14 -0
- package/dist/runtime/contracts/evidence.d.ts +87 -0
- package/dist/runtime/contracts/evidence.js +7 -0
- package/dist/runtime/contracts/router-v2.d.ts +44 -0
- package/dist/runtime/contracts/router-v2.js +4 -0
- package/dist/runtime/contracts/weakness-remediation.d.ts +67 -0
- package/dist/runtime/contracts/weakness-remediation.js +36 -0
- package/dist/runtime/kimi-api-runtime.js +59 -1
- package/dist/runtime/proof-bundle-trust.d.ts +74 -0
- package/dist/runtime/proof-bundle-trust.js +100 -0
- package/dist/runtime/provider-maturity-gate.d.ts +41 -0
- package/dist/runtime/provider-maturity-gate.js +101 -0
- package/dist/runtime/public-surface.d.ts +93 -0
- package/dist/runtime/public-surface.js +146 -0
- package/dist/runtime/router-v2-scoring.d.ts +11 -0
- package/dist/runtime/router-v2-scoring.js +151 -0
- package/dist/runtime/weakness-remediation-index.d.ts +27 -0
- package/dist/runtime/weakness-remediation-index.js +37 -0
- package/dist/schema/proof-bundle.schema.d.ts +26 -26
- package/dist/util/clipboard-image.d.ts +49 -0
- package/dist/util/clipboard-image.js +263 -0
- package/docs/2026-06-09/critical-issues.md +20 -0
- package/docs/2026-06-09/improvements.md +14 -0
- package/docs/2026-06-09/init-checklist.md +25 -0
- package/docs/2026-06-09/plan.md +20 -0
- package/docs/github-organic-promotion.md +127 -0
- package/docs/native-root-runtime-algorithms.md +301 -0
- package/package.json +4 -3
- package/readmeasset/ASSET_INDEX.md +1 -0
- package/templates/skills/agents/omk-agent-reach-websearch/SKILL.md +55 -0
- package/templates/skills/kimi/omk-agent-reach-websearch/SKILL.md +55 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { mkdir, writeFile, readFile, readdir } from "fs/promises";
|
|
2
|
+
import { basename, join } from "path";
|
|
3
|
+
import { getProjectRoot } from "../util/fs.js";
|
|
4
|
+
import { style, header, status, label } from "../util/theme.js";
|
|
5
|
+
import { NotFoundError, UsageError, emitError } from "../util/cli-contract.js";
|
|
6
|
+
import { createGoalPersister } from "../goal/persistence.js";
|
|
7
|
+
import { createGoalSpec, updateGoalStatus } from "../goal/intake.js";
|
|
8
|
+
import { buildInterviewSession, ingestAnswers } from "../goal/interview-session.js";
|
|
9
|
+
import { applyInterviewDelta } from "../goal/interview-assimilation.js";
|
|
10
|
+
import { redactSecretText } from "../goal/intent-frame.js";
|
|
11
|
+
import { readImageFile } from "../util/clipboard-image.js";
|
|
12
|
+
const IMAGE_ATTACHMENT_QUESTION_ID = "q-image-attachment";
|
|
13
|
+
function getGoalBasePath() {
|
|
14
|
+
return join(getProjectRoot(), ".omk", "goals");
|
|
15
|
+
}
|
|
16
|
+
function printAlphaWarning(json) {
|
|
17
|
+
if (json)
|
|
18
|
+
return;
|
|
19
|
+
if (!process.env.OMK_GOAL_ALPHA) {
|
|
20
|
+
console.log(style.orange("⚠️ Goal interview is alpha. Set OMK_GOAL_ALPHA=1 to suppress this warning."));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function parseDepth(value) {
|
|
24
|
+
if (!value)
|
|
25
|
+
return undefined;
|
|
26
|
+
if (value === "light" || value === "standard" || value === "deep")
|
|
27
|
+
return value;
|
|
28
|
+
throw new UsageError(`Invalid depth: ${value} (expected light | standard | deep)`);
|
|
29
|
+
}
|
|
30
|
+
function parseMode(value) {
|
|
31
|
+
if (!value || value === "create")
|
|
32
|
+
return "create";
|
|
33
|
+
if (value === "refine")
|
|
34
|
+
return "refine";
|
|
35
|
+
throw new UsageError(`Invalid mode: ${value} (expected create | refine)`);
|
|
36
|
+
}
|
|
37
|
+
async function loadAnswersFile(root, filePath) {
|
|
38
|
+
const abs = join(root, filePath);
|
|
39
|
+
let content;
|
|
40
|
+
try {
|
|
41
|
+
content = await readFile(abs, "utf-8");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new NotFoundError(`Answers file not found: ${filePath}`);
|
|
45
|
+
}
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(content);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new UsageError(`Answers file is not valid JSON: ${filePath}`);
|
|
52
|
+
}
|
|
53
|
+
const rows = Array.isArray(parsed)
|
|
54
|
+
? parsed
|
|
55
|
+
: (parsed && typeof parsed === "object" && Array.isArray(parsed.answers)
|
|
56
|
+
? parsed.answers
|
|
57
|
+
: null);
|
|
58
|
+
if (!rows) {
|
|
59
|
+
throw new UsageError(`Answers file must be an array or { "answers": [...] }: ${filePath}`);
|
|
60
|
+
}
|
|
61
|
+
const now = new Date().toISOString();
|
|
62
|
+
const answers = [];
|
|
63
|
+
for (const row of rows) {
|
|
64
|
+
if (!row || typeof row !== "object")
|
|
65
|
+
continue;
|
|
66
|
+
const r = row;
|
|
67
|
+
if (typeof r.questionId !== "string")
|
|
68
|
+
continue;
|
|
69
|
+
answers.push({
|
|
70
|
+
questionId: r.questionId,
|
|
71
|
+
answer: typeof r.answer === "string" ? r.answer : "",
|
|
72
|
+
answeredAt: typeof r.answeredAt === "string" ? r.answeredAt : now,
|
|
73
|
+
skipped: r.skipped === true ? true : undefined,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return answers;
|
|
77
|
+
}
|
|
78
|
+
function buildImageAttachment(imagePath) {
|
|
79
|
+
const img = readImageFile(imagePath);
|
|
80
|
+
if (!img.ok) {
|
|
81
|
+
throw new UsageError(img.error ?? `Unable to read image file: ${imagePath}`);
|
|
82
|
+
}
|
|
83
|
+
if (typeof img.dataUri !== "string" || typeof img.ext !== "string") {
|
|
84
|
+
throw new UsageError(`Image file could not be converted to a data URI: ${imagePath}`);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
name: basename(imagePath),
|
|
88
|
+
path: imagePath,
|
|
89
|
+
mimeType: `image/${img.ext}`,
|
|
90
|
+
dataUri: img.dataUri,
|
|
91
|
+
ext: img.ext,
|
|
92
|
+
source: "file",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function attachImageToSession(session, imagePath) {
|
|
96
|
+
const attachment = buildImageAttachment(imagePath);
|
|
97
|
+
const answeredAt = new Date().toISOString();
|
|
98
|
+
return {
|
|
99
|
+
...session,
|
|
100
|
+
updatedAt: answeredAt,
|
|
101
|
+
answers: [
|
|
102
|
+
...session.answers.filter((answer) => answer.questionId !== IMAGE_ATTACHMENT_QUESTION_ID),
|
|
103
|
+
{
|
|
104
|
+
questionId: IMAGE_ATTACHMENT_QUESTION_ID,
|
|
105
|
+
answer: `Image file: ${imagePath}`,
|
|
106
|
+
answeredAt,
|
|
107
|
+
skipped: false,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
findings: [
|
|
111
|
+
...session.findings.filter((finding) => finding.sourceQuestionId !== IMAGE_ATTACHMENT_QUESTION_ID),
|
|
112
|
+
{
|
|
113
|
+
field: "attachments",
|
|
114
|
+
value: attachment,
|
|
115
|
+
sourceQuestionId: IMAGE_ATTACHMENT_QUESTION_ID,
|
|
116
|
+
confidence: 1,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function renderQuestionsMarkdown(session) {
|
|
122
|
+
const lines = [
|
|
123
|
+
`# Interview Questions — ${session.sessionId}`,
|
|
124
|
+
"",
|
|
125
|
+
`**Mode:** ${session.mode} | **Depth:** ${session.depth} | **Ambiguity:** ${session.ambiguity}`,
|
|
126
|
+
"",
|
|
127
|
+
"| # | Score | Req | Target | Kind | Prompt |",
|
|
128
|
+
"|---|-------|-----|--------|------|--------|",
|
|
129
|
+
];
|
|
130
|
+
session.questions.forEach((q, i) => {
|
|
131
|
+
const req = q.required ? "✓" : "";
|
|
132
|
+
lines.push(`| ${i + 1} | ${q.score} | ${req} | ${q.targetField} | ${q.kind} | ${q.prompt.replace(/\|/g, "\\|")} |`);
|
|
133
|
+
});
|
|
134
|
+
lines.push("");
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
function renderReportMarkdown(session) {
|
|
138
|
+
const c = session.completeness;
|
|
139
|
+
const lines = [
|
|
140
|
+
`# Interview Report — ${session.sessionId}`,
|
|
141
|
+
"",
|
|
142
|
+
`**Status:** ${session.status} | **Mode:** ${session.mode} | **Depth:** ${session.depth}`,
|
|
143
|
+
`**Ambiguity:** ${session.ambiguity} | **Completeness:** ${c.overall}`,
|
|
144
|
+
"",
|
|
145
|
+
"## Completeness by axis",
|
|
146
|
+
"",
|
|
147
|
+
`- objective: ${c.objective}`,
|
|
148
|
+
`- successCriteria: ${c.successCriteria}`,
|
|
149
|
+
`- evidence: ${c.evidence}`,
|
|
150
|
+
`- artifacts: ${c.artifacts}`,
|
|
151
|
+
`- constraints: ${c.constraints}`,
|
|
152
|
+
`- risks: ${c.risks}`,
|
|
153
|
+
`- authority: ${c.authority}`,
|
|
154
|
+
"",
|
|
155
|
+
];
|
|
156
|
+
if (c.criticalMissing.length > 0) {
|
|
157
|
+
lines.push("## Critical missing fields", "", ...c.criticalMissing.map((m) => `- ${m}`), "");
|
|
158
|
+
}
|
|
159
|
+
if (c.contradictions.length > 0) {
|
|
160
|
+
lines.push("## Unresolved contradictions", "", ...c.contradictions.map((m) => `- ${m}`), "");
|
|
161
|
+
}
|
|
162
|
+
lines.push("## Answers", "");
|
|
163
|
+
if (session.answers.length === 0) {
|
|
164
|
+
lines.push("_No answers recorded yet._", "");
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
for (const a of session.answers) {
|
|
168
|
+
if (a.skipped) {
|
|
169
|
+
lines.push(`- **${a.questionId}**: _(skipped)_`);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
lines.push(`- **${a.questionId}**: ${a.answer}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
lines.push("");
|
|
176
|
+
}
|
|
177
|
+
lines.push("## Spec delta", "");
|
|
178
|
+
if (session.specDelta.changes.length === 0) {
|
|
179
|
+
lines.push("_No structured changes derived._", "");
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
for (const ch of session.specDelta.changes) {
|
|
183
|
+
lines.push(`- \`${ch.op}\` **${String(ch.field)}** (conf ${ch.confidence}): ${JSON.stringify(ch.value)}`);
|
|
184
|
+
}
|
|
185
|
+
lines.push("");
|
|
186
|
+
}
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
async function writeSessionArtifacts(dir, session) {
|
|
190
|
+
await mkdir(dir, { recursive: true });
|
|
191
|
+
await writeFile(join(dir, "interview.json"), `${JSON.stringify(session, null, 2)}\n`);
|
|
192
|
+
await writeFile(join(dir, "spec-delta.json"), `${JSON.stringify(session.specDelta, null, 2)}\n`);
|
|
193
|
+
await writeFile(join(dir, "questions.md"), renderQuestionsMarkdown(session));
|
|
194
|
+
await writeFile(join(dir, "interview-report.md"), renderReportMarkdown(session));
|
|
195
|
+
const answersJsonl = session.answers.map((a) => JSON.stringify(a)).join("\n");
|
|
196
|
+
await writeFile(join(dir, "answers.jsonl"), answersJsonl.length > 0 ? `${answersJsonl}\n` : "");
|
|
197
|
+
}
|
|
198
|
+
function printSessionSummary(session, sessionDir) {
|
|
199
|
+
console.log(header("Goal Interview"));
|
|
200
|
+
console.log(label("Session", session.sessionId));
|
|
201
|
+
console.log(label("Mode", session.mode));
|
|
202
|
+
console.log(label("Depth", session.depth));
|
|
203
|
+
console.log(label("Ambiguity", String(session.ambiguity)));
|
|
204
|
+
console.log(label("Completeness", String(session.completeness.overall)));
|
|
205
|
+
console.log(label("Status", session.status));
|
|
206
|
+
console.log(label("Questions", String(session.questions.length)));
|
|
207
|
+
console.log(label("Answers", String(session.answers.length)));
|
|
208
|
+
if (session.completeness.criticalMissing.length > 0) {
|
|
209
|
+
console.log(label("Missing", session.completeness.criticalMissing.join(", ")));
|
|
210
|
+
}
|
|
211
|
+
if (session.completeness.contradictions.length > 0) {
|
|
212
|
+
console.log(style.pink(`Contradictions: ${session.completeness.contradictions.join("; ")}`));
|
|
213
|
+
}
|
|
214
|
+
console.log("");
|
|
215
|
+
if (session.answers.length === 0) {
|
|
216
|
+
console.log(style.purpleBold("Top questions"));
|
|
217
|
+
for (const q of session.questions.slice(0, 6)) {
|
|
218
|
+
const req = q.required ? style.pink("[required]") : style.gray("[optional]");
|
|
219
|
+
console.log(` ${req} (${q.score}) ${q.prompt}`);
|
|
220
|
+
}
|
|
221
|
+
console.log("");
|
|
222
|
+
console.log(style.gray("Answer via: omk goal interview \"<prompt>\" --answers answers.json --write-spec"));
|
|
223
|
+
}
|
|
224
|
+
console.log(status.success(`interview.json → ${join(sessionDir, "interview.json")}`));
|
|
225
|
+
}
|
|
226
|
+
export async function goalInterviewCommand(input, options) {
|
|
227
|
+
printAlphaWarning(options.json);
|
|
228
|
+
const root = getProjectRoot();
|
|
229
|
+
const persister = createGoalPersister(getGoalBasePath());
|
|
230
|
+
const mode = parseMode(options.mode);
|
|
231
|
+
const depth = parseDepth(options.depth);
|
|
232
|
+
const maxQuestions = options.maxQuestions ? Number.parseInt(options.maxQuestions, 10) : undefined;
|
|
233
|
+
if (maxQuestions !== undefined && (!Number.isFinite(maxQuestions) || maxQuestions <= 0)) {
|
|
234
|
+
throw new UsageError(`Invalid --max-questions: ${options.maxQuestions}`);
|
|
235
|
+
}
|
|
236
|
+
// Resolve existing goal for refine mode or when --goal-id / id positional is given.
|
|
237
|
+
const goalIdHint = options.goalId ?? (mode === "refine" ? input : undefined);
|
|
238
|
+
let existingGoal = null;
|
|
239
|
+
if (goalIdHint) {
|
|
240
|
+
existingGoal = await persister.load(goalIdHint);
|
|
241
|
+
if (!existingGoal) {
|
|
242
|
+
const msg = `Goal not found: ${goalIdHint}`;
|
|
243
|
+
emitError(msg, Boolean(options.json));
|
|
244
|
+
throw new NotFoundError(msg);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const rawPrompt = existingGoal
|
|
248
|
+
? (existingGoal.rawPrompt || existingGoal.objective || existingGoal.title)
|
|
249
|
+
: (input ?? "");
|
|
250
|
+
if (!rawPrompt.trim()) {
|
|
251
|
+
throw new UsageError("A raw goal prompt or an existing --goal-id is required.");
|
|
252
|
+
}
|
|
253
|
+
// Redact secrets from the prompt before it reaches the session, the GoalSpec,
|
|
254
|
+
// persisted artifacts, or --json output.
|
|
255
|
+
const safePrompt = redactSecretText(rawPrompt);
|
|
256
|
+
const seed = {
|
|
257
|
+
rawPrompt: safePrompt,
|
|
258
|
+
riskLevel: existingGoal?.riskLevel,
|
|
259
|
+
goal: existingGoal ?? undefined,
|
|
260
|
+
};
|
|
261
|
+
let session = buildInterviewSession({
|
|
262
|
+
seed,
|
|
263
|
+
mode: existingGoal ? "refine" : mode,
|
|
264
|
+
depth,
|
|
265
|
+
maxQuestions,
|
|
266
|
+
goalId: existingGoal?.goalId,
|
|
267
|
+
});
|
|
268
|
+
if (options.answers) {
|
|
269
|
+
const answers = await loadAnswersFile(root, options.answers);
|
|
270
|
+
session = ingestAnswers(session, seed, answers);
|
|
271
|
+
}
|
|
272
|
+
if (options.image) {
|
|
273
|
+
session = attachImageToSession(session, options.image);
|
|
274
|
+
}
|
|
275
|
+
// Optionally apply the delta to a GoalSpec.
|
|
276
|
+
let savedGoalId = existingGoal?.goalId;
|
|
277
|
+
if (options.writeSpec) {
|
|
278
|
+
let goal;
|
|
279
|
+
if (existingGoal) {
|
|
280
|
+
const result = applyInterviewDelta(existingGoal, session.specDelta);
|
|
281
|
+
goal = updateGoalStatus(result.goal, result.goal.status, { planRevision: result.goal.planRevision + 1 });
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
const base = createGoalSpec(safePrompt);
|
|
285
|
+
goal = applyInterviewDelta(base, session.specDelta).goal;
|
|
286
|
+
}
|
|
287
|
+
await persister.save(goal);
|
|
288
|
+
await persister.appendHistory(goal.goalId, {
|
|
289
|
+
at: new Date().toISOString(),
|
|
290
|
+
action: existingGoal ? "interview-refine" : "interview-create",
|
|
291
|
+
detail: {
|
|
292
|
+
sessionId: session.sessionId,
|
|
293
|
+
ambiguity: session.ambiguity,
|
|
294
|
+
completeness: session.completeness.overall,
|
|
295
|
+
changes: session.specDelta.changes.length,
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
savedGoalId = goal.goalId;
|
|
299
|
+
session.goalId = goal.goalId;
|
|
300
|
+
session.specDelta.goalId = goal.goalId;
|
|
301
|
+
}
|
|
302
|
+
// Persist interview artifacts. With a goal id, store under the goal dir.
|
|
303
|
+
const sessionDir = savedGoalId
|
|
304
|
+
? join(getGoalBasePath(), savedGoalId, "interviews", session.sessionId)
|
|
305
|
+
: join(root, ".omk", "interviews", session.sessionId);
|
|
306
|
+
await writeSessionArtifacts(sessionDir, session);
|
|
307
|
+
if (options.json) {
|
|
308
|
+
console.log(JSON.stringify(session, null, 2));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
printSessionSummary(session, sessionDir);
|
|
312
|
+
if (savedGoalId) {
|
|
313
|
+
console.log(status.success(`goal spec written → ${savedGoalId}`));
|
|
314
|
+
console.log(style.gray(`Next: omk goal plan ${savedGoalId}`));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function findLatestInterviewSessionId(goalDir) {
|
|
318
|
+
const interviewsDir = join(goalDir, "interviews");
|
|
319
|
+
try {
|
|
320
|
+
const entries = await readdir(interviewsDir, { withFileTypes: true });
|
|
321
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
322
|
+
return dirs.length > 0 ? dirs[dirs.length - 1] : null;
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
export async function goalRefineCommand(goalId, options) {
|
|
329
|
+
printAlphaWarning(options.json);
|
|
330
|
+
const persister = createGoalPersister(getGoalBasePath());
|
|
331
|
+
const goal = await persister.load(goalId);
|
|
332
|
+
if (!goal) {
|
|
333
|
+
const msg = `Goal not found: ${goalId}`;
|
|
334
|
+
emitError(msg, Boolean(options.json));
|
|
335
|
+
throw new NotFoundError(msg);
|
|
336
|
+
}
|
|
337
|
+
const goalDir = join(getGoalBasePath(), goalId);
|
|
338
|
+
const from = options.fromInterview ?? "latest";
|
|
339
|
+
const sessionId = from === "latest" ? await findLatestInterviewSessionId(goalDir) : from;
|
|
340
|
+
if (!sessionId) {
|
|
341
|
+
const msg = `No interview session found for goal: ${goalId}. Run: omk goal interview ${goalId} --mode refine --answers answers.json`;
|
|
342
|
+
emitError(msg, Boolean(options.json));
|
|
343
|
+
throw new NotFoundError(msg);
|
|
344
|
+
}
|
|
345
|
+
const deltaPath = join(goalDir, "interviews", sessionId, "spec-delta.json");
|
|
346
|
+
let delta;
|
|
347
|
+
try {
|
|
348
|
+
delta = JSON.parse(await readFile(deltaPath, "utf-8"));
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
const msg = `Interview spec-delta not found: ${deltaPath}`;
|
|
352
|
+
emitError(msg, Boolean(options.json));
|
|
353
|
+
throw new NotFoundError(msg);
|
|
354
|
+
}
|
|
355
|
+
const result = applyInterviewDelta(goal, delta);
|
|
356
|
+
const refined = updateGoalStatus(result.goal, result.goal.status, { planRevision: result.goal.planRevision + 1 });
|
|
357
|
+
await persister.save(refined);
|
|
358
|
+
await persister.appendHistory(goalId, {
|
|
359
|
+
at: new Date().toISOString(),
|
|
360
|
+
action: "refine",
|
|
361
|
+
detail: {
|
|
362
|
+
sessionId,
|
|
363
|
+
applied: result.appliedChanges.length,
|
|
364
|
+
skipped: result.skippedChanges.length,
|
|
365
|
+
contradictions: result.contradictions.length,
|
|
366
|
+
planRevision: refined.planRevision,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
if (options.plan) {
|
|
370
|
+
const { goalPlanCommand } = await import("./goal.js");
|
|
371
|
+
await goalPlanCommand(goalId, { json: options.json });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (options.json) {
|
|
375
|
+
console.log(JSON.stringify({
|
|
376
|
+
goalId,
|
|
377
|
+
sessionId,
|
|
378
|
+
applied: result.appliedChanges.length,
|
|
379
|
+
skipped: result.skippedChanges.length,
|
|
380
|
+
contradictions: result.contradictions,
|
|
381
|
+
planRevision: refined.planRevision,
|
|
382
|
+
status: refined.status,
|
|
383
|
+
}, null, 2));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
console.log(header("Goal Refined"));
|
|
387
|
+
console.log(label("ID", goalId));
|
|
388
|
+
console.log(label("From interview", sessionId));
|
|
389
|
+
console.log(label("Applied changes", String(result.appliedChanges.length)));
|
|
390
|
+
console.log(label("Skipped changes", String(result.skippedChanges.length)));
|
|
391
|
+
console.log(label("Plan revision", String(refined.planRevision)));
|
|
392
|
+
if (result.contradictions.length > 0) {
|
|
393
|
+
console.log(style.pink(`Contradictions: ${result.contradictions.join("; ")}`));
|
|
394
|
+
}
|
|
395
|
+
console.log(style.gray(`Next: omk goal plan ${goalId}`));
|
|
396
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { GoalSpec, RiskLevel } from "./goal.js";
|
|
2
|
+
export declare const INTERVIEW_SCHEMA_VERSION: "omk.interview.v1";
|
|
3
|
+
export declare const INTERVIEW_DELTA_SCHEMA_VERSION: "omk.interview-delta.v1";
|
|
4
|
+
export type InterviewDepth = "light" | "standard" | "deep";
|
|
5
|
+
export type InterviewMode = "create" | "refine";
|
|
6
|
+
export type InterviewQuestionKind = "objective" | "success-criteria" | "artifact" | "constraint" | "non-goal" | "risk" | "authority" | "evidence" | "dependency" | "rollback";
|
|
7
|
+
/**
|
|
8
|
+
* Field on GoalSpec (or a derived structure) that an answer is meant to fill.
|
|
9
|
+
* A question without a targetField is low quality and must be dropped.
|
|
10
|
+
*/
|
|
11
|
+
export type InterviewTargetField = "objective" | "successCriteria" | "expectedArtifacts" | "constraints" | "nonGoals" | "risks" | "riskLevel" | "intentFrame" | "actionAtoms";
|
|
12
|
+
export interface InterviewQuestion {
|
|
13
|
+
id: string;
|
|
14
|
+
kind: InterviewQuestionKind;
|
|
15
|
+
prompt: string;
|
|
16
|
+
required: boolean;
|
|
17
|
+
targetField: InterviewTargetField;
|
|
18
|
+
/** 0..1 — how much answering reduces uncertainty about the goal. */
|
|
19
|
+
informationGain: number;
|
|
20
|
+
/** 0..1 — how much answering reduces execution risk. */
|
|
21
|
+
riskReduction: number;
|
|
22
|
+
/** 0..1 — how strongly the answer reshapes the execution DAG. */
|
|
23
|
+
dagImpact: number;
|
|
24
|
+
/** 0..1 — how strongly the answer strengthens evidence gates. */
|
|
25
|
+
evidenceImpact: number;
|
|
26
|
+
/** 0..1 — burden on the user (penalty). */
|
|
27
|
+
userCost: number;
|
|
28
|
+
/** Derived ranking score, 0..1, rounded to 2 decimals. */
|
|
29
|
+
score: number;
|
|
30
|
+
}
|
|
31
|
+
export interface InterviewAnswer {
|
|
32
|
+
questionId: string;
|
|
33
|
+
answer: string;
|
|
34
|
+
answeredAt: string;
|
|
35
|
+
skipped?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface InterviewFinding {
|
|
38
|
+
field: InterviewTargetField | string;
|
|
39
|
+
value: unknown;
|
|
40
|
+
sourceQuestionId: string;
|
|
41
|
+
/** 0..1 confidence that this finding is correct and explicit. */
|
|
42
|
+
confidence: number;
|
|
43
|
+
/** True when this finding contradicts an existing explicit value. */
|
|
44
|
+
conflict?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface InterviewCompleteness {
|
|
47
|
+
/** Weighted overall completeness, 0..1. */
|
|
48
|
+
overall: number;
|
|
49
|
+
objective: number;
|
|
50
|
+
successCriteria: number;
|
|
51
|
+
evidence: number;
|
|
52
|
+
artifacts: number;
|
|
53
|
+
constraints: number;
|
|
54
|
+
risks: number;
|
|
55
|
+
authority: number;
|
|
56
|
+
/** Required fields with no usable value yet. */
|
|
57
|
+
criticalMissing: string[];
|
|
58
|
+
/** Unresolved contradictions between answers and existing values. */
|
|
59
|
+
contradictions: string[];
|
|
60
|
+
}
|
|
61
|
+
export type InterviewDeltaField = keyof GoalSpec | "successCriteria" | "expectedArtifacts" | "riskLevel";
|
|
62
|
+
export interface InterviewSpecChange {
|
|
63
|
+
field: InterviewDeltaField;
|
|
64
|
+
op: "add" | "replace" | "remove";
|
|
65
|
+
value: unknown;
|
|
66
|
+
sourceQuestionId: string;
|
|
67
|
+
/** 0..1 confidence used by the conflict resolver. */
|
|
68
|
+
confidence: number;
|
|
69
|
+
}
|
|
70
|
+
export interface InterviewSpecDelta {
|
|
71
|
+
schemaVersion: typeof INTERVIEW_DELTA_SCHEMA_VERSION;
|
|
72
|
+
goalId?: string;
|
|
73
|
+
changes: InterviewSpecChange[];
|
|
74
|
+
}
|
|
75
|
+
export type InterviewStatus = "open" | "complete" | "blocked";
|
|
76
|
+
export interface InterviewSession {
|
|
77
|
+
schemaVersion: typeof INTERVIEW_SCHEMA_VERSION;
|
|
78
|
+
sessionId: string;
|
|
79
|
+
goalId?: string;
|
|
80
|
+
mode: InterviewMode;
|
|
81
|
+
depth: InterviewDepth;
|
|
82
|
+
createdAt: string;
|
|
83
|
+
updatedAt: string;
|
|
84
|
+
rawPrompt?: string;
|
|
85
|
+
/** Ambiguity score 0..1; higher means more interview is needed. */
|
|
86
|
+
ambiguity: number;
|
|
87
|
+
questions: InterviewQuestion[];
|
|
88
|
+
answers: InterviewAnswer[];
|
|
89
|
+
findings: InterviewFinding[];
|
|
90
|
+
completeness: InterviewCompleteness;
|
|
91
|
+
specDelta: InterviewSpecDelta;
|
|
92
|
+
status: InterviewStatus;
|
|
93
|
+
}
|
|
94
|
+
/** Inputs the question bank needs to derive candidate questions. */
|
|
95
|
+
export interface InterviewSeed {
|
|
96
|
+
rawPrompt: string;
|
|
97
|
+
riskLevel?: RiskLevel;
|
|
98
|
+
goal?: GoalSpec;
|
|
99
|
+
}
|
|
100
|
+
/** Result of applying a spec delta to a GoalSpec. */
|
|
101
|
+
export interface InterviewApplyResult {
|
|
102
|
+
goal: GoalSpec;
|
|
103
|
+
appliedChanges: InterviewSpecChange[];
|
|
104
|
+
skippedChanges: InterviewSpecChange[];
|
|
105
|
+
contradictions: string[];
|
|
106
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Contract: src/contracts/interview.ts
|
|
2
|
+
// Owner: Contract Worker (Deep Interview phase 0)
|
|
3
|
+
// OMK Deep Interview — uncertainty reducer for goal-driven agent runs.
|
|
4
|
+
//
|
|
5
|
+
// Read-only for downstream interview modules. These types are the shared
|
|
6
|
+
// language for question banking, scoring, assimilation, session building,
|
|
7
|
+
// and the `omk goal interview` / `omk goal refine` CLI commands.
|
|
8
|
+
export const INTERVIEW_SCHEMA_VERSION = "omk.interview.v1";
|
|
9
|
+
export const INTERVIEW_DELTA_SCHEMA_VERSION = "omk.interview-delta.v1";
|
package/dist/evidence/index.d.ts
CHANGED
|
@@ -13,3 +13,7 @@ export { decideRepair } from "../orchestration/repair-policy.js";
|
|
|
13
13
|
export type { RepairContext } from "../orchestration/repair-policy.js";
|
|
14
14
|
export type { DecisionTraceStore } from "./decision-trace.js";
|
|
15
15
|
export { createDecisionTraceStore } from "./decision-trace.js";
|
|
16
|
+
export type { ProofTrustMvpEngine, ProofTrustResult } from "./proof-trust.js";
|
|
17
|
+
export { createProofTrustMvpEngine } from "./proof-trust.js";
|
|
18
|
+
export type { AlgorithmSpec, ReleaseCandidate, RegressionProofMatrixResult, RegressionProofMatrixEngine, RegressionProofMatrixOptions, } from "./regression-proof-matrix.js";
|
|
19
|
+
export { createRegressionProofMatrixEngine } from "./regression-proof-matrix.js";
|
package/dist/evidence/index.js
CHANGED
|
@@ -5,3 +5,5 @@ export { createDiagnosisEngine } from "./diagnosis.js";
|
|
|
5
5
|
export { createRunTraceStore } from "./run-trace.js";
|
|
6
6
|
export { decideRepair } from "../orchestration/repair-policy.js";
|
|
7
7
|
export { createDecisionTraceStore } from "./decision-trace.js";
|
|
8
|
+
export { createProofTrustMvpEngine } from "./proof-trust.js";
|
|
9
|
+
export { createRegressionProofMatrixEngine } from "./regression-proof-matrix.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Proof Trust CLI — thin wrapper around ProofTrustMvpEngine.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node dist/evidence/proof-trust-cli.js <runDir> <bundlePath>
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { createProofTrustMvpEngine } from "./proof-trust.js";
|
|
10
|
+
async function main() {
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const runDir = args[0] ?? ".omk/runs";
|
|
13
|
+
const bundlePath = args[1];
|
|
14
|
+
if (!bundlePath) {
|
|
15
|
+
console.error("Usage: proof-trust-cli <runDir> <bundlePath>");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const bundle = JSON.parse(await readFile(bundlePath, "utf8"));
|
|
19
|
+
const engine = createProofTrustMvpEngine();
|
|
20
|
+
const result = await engine.evaluate(runDir, bundle);
|
|
21
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22
|
+
process.exit(result.missingFields.length > 0 ? 1 : 0);
|
|
23
|
+
}
|
|
24
|
+
main().catch((err) => {
|
|
25
|
+
console.error(err);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof Trust MVP — Algorithm 3
|
|
3
|
+
*
|
|
4
|
+
* Evaluates a proof bundle against a run directory, computing a trust score
|
|
5
|
+
* based on weighted field presence and validity.
|
|
6
|
+
*/
|
|
7
|
+
export interface ProofTrustMvpEngine {
|
|
8
|
+
evaluate(runDir: string, bundle: unknown): Promise<ProofTrustResult>;
|
|
9
|
+
}
|
|
10
|
+
export interface ProofTrustResult {
|
|
11
|
+
readonly trustScore: number;
|
|
12
|
+
readonly missingFields: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function createProofTrustMvpEngine(schemas?: Readonly<Record<string, unknown>>): ProofTrustMvpEngine;
|