jeopi-cli 16.2.30 → 16.4.0

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 (44) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/cli.js +2824 -2736
  3. package/dist/types/config/settings-schema.d.ts +32 -2
  4. package/dist/types/goals/runtime.d.ts +16 -0
  5. package/dist/types/goals/verifier.d.ts +9 -0
  6. package/dist/types/memories/index.d.ts +9 -0
  7. package/dist/types/memory-backend/types.d.ts +11 -0
  8. package/dist/types/session/agent-session.d.ts +22 -7
  9. package/dist/types/slash-commands/helpers/skill-verify.d.ts +5 -0
  10. package/dist/types/task/model-tier-classifier.d.ts +8 -0
  11. package/dist/types/tools/builtin-names.d.ts +1 -1
  12. package/dist/types/tools/index.d.ts +1 -0
  13. package/dist/types/tools/learn.d.ts +4 -0
  14. package/dist/types/tools/path-write-lock.d.ts +10 -0
  15. package/dist/types/tools/vision-verify.d.ts +34 -0
  16. package/package.json +12 -12
  17. package/src/autolearn/controller.ts +21 -5
  18. package/src/autoresearch/index.ts +60 -6
  19. package/src/config/settings-schema.ts +38 -2
  20. package/src/edit/index.ts +2 -1
  21. package/src/goals/runtime.ts +51 -0
  22. package/src/goals/verifier.ts +100 -0
  23. package/src/internal-urls/docs-index.generated.txt +2 -2
  24. package/src/memories/index.ts +10 -2
  25. package/src/memory-backend/local-backend.ts +34 -0
  26. package/src/memory-backend/types.ts +11 -0
  27. package/src/prompts/agents/designer.md +1 -0
  28. package/src/prompts/agents/goal-verifier.md +51 -0
  29. package/src/prompts/tools/learn.md +2 -0
  30. package/src/prompts/tools/vision-verify-system.md +16 -0
  31. package/src/prompts/tools/vision-verify.md +20 -0
  32. package/src/sdk.ts +2 -0
  33. package/src/session/agent-session.ts +51 -8
  34. package/src/slash-commands/builtin-registry.ts +9 -0
  35. package/src/slash-commands/helpers/skill-verify.ts +171 -0
  36. package/src/task/agents.ts +2 -0
  37. package/src/task/index.ts +26 -1
  38. package/src/task/model-tier-classifier.ts +43 -0
  39. package/src/tools/builtin-names.ts +1 -0
  40. package/src/tools/index.ts +4 -0
  41. package/src/tools/learn.ts +16 -1
  42. package/src/tools/path-write-lock.ts +44 -0
  43. package/src/tools/vision-verify.ts +347 -0
  44. package/src/tools/write.ts +12 -9
@@ -6,6 +6,13 @@ import { assertSubstantiveCompletionEvidence } from "./completion-evidence";
6
6
  import { GoalEvidenceTracker, isVerificationSignal, MUTATION_TOOL_NAMES } from "./evidence-gate";
7
7
  import type { Goal, GoalBudgetSteering, GoalModeState, GoalRuntimeEvent, GoalTokenUsage } from "./state";
8
8
 
9
+ export interface GoalCompletionVerdict {
10
+ verdict: "okay" | "iterate" | "reject";
11
+ justification: string;
12
+ summary?: string;
13
+ requiredFixes: string[];
14
+ }
15
+
9
16
  export interface GoalRuntimeHost {
10
17
  getState(): GoalModeState | undefined;
11
18
  setState(state: GoalModeState | undefined): void;
@@ -17,6 +24,16 @@ export interface GoalRuntimeHost {
17
24
  content: string;
18
25
  deliverAs?: "steer" | "followUp" | "nextTurn";
19
26
  }): Promise<void>;
27
+ /**
28
+ * Spawn an independent grader sub-agent to verdict a completion claim against
29
+ * the objective and the actual repo diff (article gap: "the agent that wrote
30
+ * the code is not the agent that grades it"). The sub-agent sees only the
31
+ * objective + evidence text passed here plus its own repo inspection — never
32
+ * this session's transcript/reasoning. Optional: hosts that don't wire this
33
+ * (e.g. test harnesses, `goal.verifyCompletion` disabled) skip independent
34
+ * grading and fall back to the deterministic evidence gate alone.
35
+ */
36
+ verifyCompletion?(objective: string, evidence: string): Promise<GoalCompletionVerdict>;
20
37
  now?(): number;
21
38
  }
22
39
 
@@ -122,6 +139,8 @@ export class GoalRuntime {
122
139
  #turnSnapshot: GoalTurnSnapshot | undefined;
123
140
  #wallClock: GoalWallClockSnapshot;
124
141
  #budgetReportedFor: string | undefined;
142
+ /** Set when an independent-verifier non-okay verdict was bounced once; cleared by any new mutation/verification or a fresh goal. Mirrors GoalEvidenceTracker's bypass latch, kept separate because it gates a different (probabilistic) signal. */
143
+ #verifierBypassArmed = false;
125
144
  #accountingTail: Promise<void> = Promise.resolve();
126
145
 
127
146
  constructor(host: GoalRuntimeHost) {
@@ -219,10 +238,12 @@ export class GoalRuntime {
219
238
  if (!this.#hasAccountingState()) return;
220
239
  if (MUTATION_TOOL_NAMES[toolName]) {
221
240
  this.#evidence.recordMutation();
241
+ this.#verifierBypassArmed = false;
222
242
  return;
223
243
  }
224
244
  if (detail && isVerificationSignal(detail.command ?? "", detail.outputHead ?? "")) {
225
245
  this.#evidence.recordVerification();
246
+ this.#verifierBypassArmed = false;
226
247
  }
227
248
  }
228
249
 
@@ -418,6 +439,7 @@ export class GoalRuntime {
418
439
  const state = this.#createGoalState(objective, input.tokenBudget);
419
440
  this.#budgetReportedFor = undefined;
420
441
  this.#evidence.reset();
442
+ this.#verifierBypassArmed = false;
421
443
  this.#markActiveAccounting(state.goal);
422
444
  await this.#commitState(state, { persist: "goal" });
423
445
  return state;
@@ -437,6 +459,7 @@ export class GoalRuntime {
437
459
  const state = this.#createGoalState(objective, input.tokenBudget);
438
460
  this.#budgetReportedFor = undefined;
439
461
  this.#evidence.reset();
462
+ this.#verifierBypassArmed = false;
440
463
  this.#markActiveAccounting(state.goal);
441
464
  await this.#commitState(state, { persist: "goal" });
442
465
  return state;
@@ -522,6 +545,34 @@ export class GoalRuntime {
522
545
  if (gate.block) {
523
546
  throw new Error(gate.message);
524
547
  }
548
+ // Independent grader (goal-verifier sub-agent, see GoalRuntimeHost.verifyCompletion):
549
+ // maker != grader, per the compounding-loop primitive — the same agent that did
550
+ // the work does not get to be the sole judge of whether it's done. Same bounce-once
551
+ // shape as the deterministic gate above, kept as a separate latch since this signal
552
+ // is probabilistic (an LLM verdict), not mechanical.
553
+ if (this.#host.verifyCompletion) {
554
+ const verdict = await this.#host.verifyCompletion(state.goal.objective, verifiedEvidence);
555
+ if (verdict.verdict !== "okay") {
556
+ if (this.#verifierBypassArmed) {
557
+ // Second attempt with no new mutation/verification since the bounce:
558
+ // escape hatch for a verifier false-positive-reject/iterate.
559
+ this.#verifierBypassArmed = false;
560
+ } else {
561
+ this.#verifierBypassArmed = true;
562
+ const fixes =
563
+ verdict.requiredFixes.length > 0
564
+ ? `\nRequired fixes:\n${verdict.requiredFixes.map(fix => `- ${fix}`).join("\n")}`
565
+ : "";
566
+ throw new Error(
567
+ `Completion blocked: independent verifier verdict was "${verdict.verdict}", not "okay". ` +
568
+ `${verdict.justification}${fixes}\nAddress the gaps above, then call complete again. ` +
569
+ "If the verifier is wrong, call complete again unchanged to override.",
570
+ );
571
+ }
572
+ } else {
573
+ this.#verifierBypassArmed = false;
574
+ }
575
+ }
525
576
  state.enabled = false;
526
577
  state.goal.status = "complete";
527
578
  state.goal.completionEvidence = verifiedEvidence;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Independent-verifier bridge for goal-mode completion (see GoalRuntimeHost.verifyCompletion
3
+ * in runtime.ts). Spawns the bundled `goal-verifier` sub-agent — a fresh session with its own
4
+ * kernel, its own read-only tool set, and no exposure to the maker's transcript/reasoning —
5
+ * to grade a completion claim against the objective and the actual repo diff. Reuses the eval
6
+ * tool's `agent()` bridge as a plain async call (no eval kernel required outside eval cells;
7
+ * `withBridgeTimeoutPause` in agent-bridge.ts is a no-op when no `emitStatus` sink is wired).
8
+ */
9
+ import { runEvalAgent } from "../eval/agent-bridge";
10
+ import { getBundledAgent } from "../task/agents";
11
+ import type { ToolSession } from "../tools";
12
+ import type { GoalCompletionVerdict } from "./runtime";
13
+
14
+ const VERIFIER_AGENT_NAME = "goal-verifier";
15
+
16
+ /** Cap the evidence claim embedded in the verifier prompt; the verifier re-derives ground truth from the repo, not from a long claim. */
17
+ const MAX_EVIDENCE_CHARS = 4000;
18
+
19
+ function parseVerdict(output: string): GoalCompletionVerdict {
20
+ let data: unknown;
21
+ try {
22
+ data = JSON.parse(output);
23
+ } catch {
24
+ // Verifier didn't return a structured payload (refused, crashed, plain-text
25
+ // reply). Fail open to "iterate" rather than silently trusting completion —
26
+ // a broken verifier bounces once instead of being bypassed unnoticed.
27
+ return {
28
+ verdict: "iterate",
29
+ justification: `goal-verifier did not return a structured verdict. Raw output: ${output.slice(0, 500)}`,
30
+ requiredFixes: [],
31
+ };
32
+ }
33
+ if (data === null || typeof data !== "object") {
34
+ return {
35
+ verdict: "iterate",
36
+ justification: "goal-verifier returned a non-object payload.",
37
+ requiredFixes: [],
38
+ };
39
+ }
40
+ const record = data as Record<string, unknown>;
41
+ const verdict = record.verdict;
42
+ if (verdict !== "okay" && verdict !== "iterate" && verdict !== "reject") {
43
+ return {
44
+ verdict: "iterate",
45
+ justification: "goal-verifier returned an unrecognized verdict value.",
46
+ requiredFixes: [],
47
+ };
48
+ }
49
+ const rawFixes = record.required_fixes;
50
+ const requiredFixes = Array.isArray(rawFixes)
51
+ ? rawFixes.filter((fix): fix is string => typeof fix === "string")
52
+ : [];
53
+ const justification = typeof record.justification === "string" ? record.justification : "";
54
+ const summary = typeof record.summary === "string" ? record.summary : undefined;
55
+ return { verdict, justification, summary, requiredFixes };
56
+ }
57
+
58
+ /**
59
+ * Grade a goal-completion claim with the independent `goal-verifier` sub-agent.
60
+ * Never throws: a spawn failure (depth limit, disabled agent, no model) fails
61
+ * open to `iterate` so it costs one corrective bounce, the same as a genuine
62
+ * gap — never a silent skip of independent grading, never a permanent block.
63
+ */
64
+ export async function verifyGoalCompletion(
65
+ session: ToolSession,
66
+ objective: string,
67
+ evidence: string,
68
+ ): Promise<GoalCompletionVerdict> {
69
+ const truncatedEvidence =
70
+ evidence.length > MAX_EVIDENCE_CHARS ? `${evidence.slice(0, MAX_EVIDENCE_CHARS)}\n[truncated]` : evidence;
71
+ const verifierPrompt =
72
+ `<objective>\n${objective}\n</objective>\n\n` +
73
+ `<evidence>\n${truncatedEvidence}\n</evidence>\n\n` +
74
+ "Verdict whether the objective above is genuinely satisfied by the repo's current state. " +
75
+ "The evidence is the maker's claim, not a fact — verify it independently.";
76
+ try {
77
+ // Explicit schema, not the agent's own frontmatter default: runEvalAgent (the
78
+ // eval `agent()` bridge) only applies `outputSchema` when the caller supplies
79
+ // one — unlike the `task` tool, it never falls back to `effectiveAgent.output`
80
+ // (task/index.ts). Read the bundled definition's own `output` so this can't
81
+ // drift from goal-verifier.md's declared shape. Omit the key entirely when
82
+ // undefined: `Object.hasOwn` (agent-bridge.ts's `structured` check) is true
83
+ // for an explicit `schema: undefined`, which would wrongly disable the
84
+ // agent's frontmatter-schema fallback in the bridge.
85
+ const schema = getBundledAgent(VERIFIER_AGENT_NAME)?.output;
86
+ const args =
87
+ schema === undefined
88
+ ? { prompt: verifierPrompt, agent: VERIFIER_AGENT_NAME }
89
+ : { prompt: verifierPrompt, agent: VERIFIER_AGENT_NAME, schema };
90
+ const result = await runEvalAgent(args, { session });
91
+ return parseVerdict(result.text);
92
+ } catch (err) {
93
+ const message = err instanceof Error ? err.message : String(err);
94
+ return {
95
+ verdict: "iterate",
96
+ justification: `Independent verifier could not run: ${message}`,
97
+ requiredFixes: [],
98
+ };
99
+ }
100
+ }