killeros 2.1.22 → 2.1.24

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.
@@ -6,11 +6,9 @@ import { BoundedText } from "./bounded-text.ts";
6
6
  import { formatTime, formatTokens } from "./display.ts";
7
7
  import { reportError } from "./errors.ts";
8
8
  import { parseGoalCommand } from "./goal-command.ts";
9
- import { formatGoalHistory } from "./goal-history.ts";
10
- import { GOAL_ENTRY_TYPE, GOAL_UPDATE_TOOL, goalBranchEntries, isGoalModeSupported, isSavedSession, pauseGoalAfterFailure, persistGoalState, scheduleGoalContinuation, stopGoalRun, sumGoalTokens, syncGoalUpdateTool, transitionGoal, type GoalEntryData } from "./goal-runtime.ts";
11
- import { checkpointPausedGoalState, createNewGoalState, DEFAULT_GOAL_MAX_TURNS, editGoalState, goalElapsedMilliseconds, GOAL_VERSION, inferGoalVerification, parseGoalState, pauseGoalState, recordGoalBlockerAudit, updateGoalControlsState, validateGoalObjective, verifyGoalDeliverable } from "./goal-state.ts";
12
- import { listGoalCompletionChecks, resolveGoalCompletionCheck, runGoalCompletionCheck } from "./hooks.ts";
13
- import type { GoalCompletionCheck, GoalRuntime, GoalState, GoalStatus, InitRuntime } from "./runtime.ts";
9
+ import { GOAL_ENTRY_TYPE, GOAL_UPDATE_TOOL, isGoalModeSupported, isSavedSession, pauseGoalAfterFailure, persistGoalState, scheduleGoalContinuation, stopGoalRun, sumGoalTokens, syncGoalUpdateTool, transitionGoal, type GoalEntryData } from "./goal-runtime.ts";
10
+ import { checkpointPausedGoalState, createNewGoalState, DEFAULT_GOAL_MAX_TURNS, GOAL_MAX_TURNS, goalElapsedMilliseconds, GOAL_VERSION, inferGoalVerification, parseGoalState, recordGoalBlockerAudit, transitionGoalState, verifyGoalDeliverable } from "./goal-state.ts";
11
+ import type { GoalRuntime, GoalState, GoalStatus, InitRuntime } from "./runtime.ts";
14
12
  import { safeTerminalText } from "./safe-terminal-text.ts";
15
13
 
16
14
  const GoalUpdateParams = Type.Object({
@@ -33,7 +31,7 @@ const GoalUpdateParams = Type.Object({
33
31
  interface GoalUpdateDetails {
34
32
  status: "complete" | "blocked" | "blocker-audit";
35
33
  evidence: string;
36
- verification?: "file" | "check" | "file-and-check" | "model-reported";
34
+ verification?: "file" | "model-reported";
37
35
  blockerKey?: string;
38
36
  streak?: number;
39
37
  }
@@ -42,17 +40,12 @@ function goalStatusLabel(status: GoalStatus): string {
42
40
  return `${status.charAt(0).toUpperCase()}${status.slice(1)}`;
43
41
  }
44
42
 
45
- function goalPanelActions(status: GoalStatus): Array<{ label: string; control: "checks" | "pause" | "resume" | "edit" | "clear" }> {
46
- const terminal = [
47
- { label: "List completion checks", control: "checks" as const },
48
- { label: "Edit objective", control: "edit" as const },
49
- { label: "Clear goal", control: "clear" as const },
50
- ];
51
- if (status === "active") return [{ label: "Pause automatic continuation", control: "pause" }, ...terminal];
43
+ function goalPanelActions(status: GoalStatus): Array<{ label: string; control: "pause" | "resume" | "clear" }> {
44
+ if (status === "active") return [{ label: "Pause automatic continuation", control: "pause" }, { label: "Clear goal", control: "clear" }];
52
45
  if (status === "paused" || status === "blocked") {
53
- return [{ label: "Resume automatic continuation", control: "resume" }, ...terminal];
46
+ return [{ label: "Resume automatic continuation", control: "resume" }, { label: "Clear goal", control: "clear" }];
54
47
  }
55
- return terminal;
48
+ return [{ label: "Clear goal", control: "clear" }];
56
49
  }
57
50
 
58
51
  function goalStatusSummary(state: GoalState, ctx: ExtensionContext): string {
@@ -62,7 +55,7 @@ function goalStatusSummary(state: GoalState, ctx: ExtensionContext): string {
62
55
  : `${state.turns}/${state.maxTurns} turns`;
63
56
  const lines = [
64
57
  `Goal ${goalStatusLabel(state.status).toLowerCase()} · ${turns} · ${formatTime(goalElapsedMilliseconds(state, Date.now()))} · ${formatTokens(usedTokens)} tokens`,
65
- ...(state.completionCheck === undefined ? [] : [`Check: ${state.completionCheck.name}`]),
58
+ ...(state.verification === undefined ? [] : [`Deliverable: ${state.verification.path}`]),
66
59
  state.objective,
67
60
  ];
68
61
  if (state.result) lines.push(state.result);
@@ -105,21 +98,13 @@ export function registerGoalInterface(
105
98
  if (!evidence) throw new Error("Goal evidence must not be empty");
106
99
  if (params.status === "complete") {
107
100
  if (state.verification) await verifyGoalDeliverable(state.verification);
108
- if (state.completionCheck) await runGoalCompletionCheck(ctx, state.completionCheck, signal);
109
- if (state.verification && state.completionCheck) await verifyGoalDeliverable(state.verification);
110
101
  if (runtime.state !== state) throw new Error("Goal changed while completion was being verified");
111
- const verification = state.verification && state.completionCheck
112
- ? "file-and-check"
113
- : state.verification ? "file" : state.completionCheck ? "check" : "model-reported";
102
+ const verification = state.verification ? "file" : "model-reported";
114
103
  transitionGoal(pi, runtime, "complete", "complete", evidence, { resetBlockedAudit: true });
115
104
  const safeEvidence = safeTerminalText(evidence);
116
- const text = state.verification && state.completionCheck
117
- ? `Goal verified complete by file and ${state.completionCheck.name}: ${safeEvidence}`
118
- : state.completionCheck
119
- ? `Goal verified complete by ${state.completionCheck.name}: ${safeEvidence}`
120
- : state.verification
121
- ? `Goal verified complete at ${safeTerminalText(state.verification.path)}: ${safeEvidence}`
122
- : `Goal marked complete (model-reported): ${safeEvidence}`;
105
+ const text = state.verification
106
+ ? `Goal verified complete at ${safeTerminalText(state.verification.path)}: ${safeEvidence}`
107
+ : `Goal marked complete (model-reported): ${safeEvidence}`;
123
108
  return {
124
109
  content: [{ type: "text", text }],
125
110
  details: { status: "complete", evidence, verification },
@@ -198,99 +183,6 @@ export function registerGoalInterface(
198
183
  return;
199
184
  }
200
185
 
201
- if (command.kind === "history") {
202
- const history = formatGoalHistory(goalBranchEntries(ctx), command.count);
203
- ctx.ui.notify(history ?? "No goal history on the current branch.", "info");
204
- return;
205
- }
206
-
207
- if (command.kind === "checks") {
208
- try {
209
- const checks = listGoalCompletionChecks(ctx);
210
- ctx.ui.notify(checks.length
211
- ? `Goal completion checks: ${checks.join(", ")}`
212
- : "No goal completion checks are configured.", "info");
213
- } catch (error) {
214
- reportError(ctx, "Goal completion checks could not be listed", error);
215
- }
216
- return;
217
- }
218
-
219
- if (command.kind === "check" || command.kind === "limit") {
220
- if (initState.active) {
221
- ctx.ui.notify(`Wait for /init to finish before changing goal ${command.kind}`, "error");
222
- return;
223
- }
224
- const maxTurns = command.kind === "limit" && command.value.kind === "count" ? command.value.count : undefined;
225
- const current = runtime.state;
226
- if (!current) {
227
- ctx.ui.notify("No goal is set", "info");
228
- return;
229
- }
230
- if (current.status === "complete") {
231
- ctx.ui.notify("The goal is complete. Set a new objective or use /goal edit.", "info");
232
- return;
233
- }
234
- let completionCheck: GoalCompletionCheck | undefined = current.completionCheck;
235
- if (command.kind === "check") {
236
- try {
237
- completionCheck = command.value.kind === "clear" ? undefined : resolveGoalCompletionCheck(ctx, command.value.name);
238
- } catch (error) {
239
- reportError(ctx, "Goal completion check could not be set", error);
240
- return;
241
- }
242
- }
243
- runtime.continuationHeld = true;
244
- try {
245
- await ctx.waitForIdle();
246
- } catch (error) {
247
- runtime.continuationHeld = false;
248
- reportError(ctx, "Goal could not wait for the active turn", error);
249
- scheduleGoalContinuation(pi, runtime, initState, ctx);
250
- return;
251
- }
252
- runtime.continuationHeld = false;
253
- const latest = runtime.state;
254
- if (!latest || latest.status === "complete") {
255
- ctx.ui.notify(latest?.status === "complete" ? "The goal completed before its controls changed." : "No goal is set", "info");
256
- return;
257
- }
258
- if (command.kind === "check" && command.value.kind === "named") {
259
- try {
260
- completionCheck = resolveGoalCompletionCheck(ctx, command.value.name);
261
- } catch (error) {
262
- reportError(ctx, "Goal completion check could not be set", error);
263
- scheduleGoalContinuation(pi, runtime, initState, ctx);
264
- return;
265
- }
266
- } else if (command.kind === "check") {
267
- completionCheck = undefined;
268
- } else {
269
- completionCheck = latest.completionCheck;
270
- }
271
- const nextLimit = command.kind === "limit" ? maxTurns : latest.maxTurns;
272
- let next = updateGoalControlsState(latest, { completionCheck, ...(nextLimit === undefined ? {} : { maxTurns: nextLimit }) }, Date.now());
273
- const exhausted = next.status === "active" && next.maxTurns !== undefined && next.turns >= next.maxTurns;
274
- if (exhausted) next = pauseGoalState(next, `Turn limit reached (${next.turns}/${next.maxTurns}).`, Date.now());
275
- try {
276
- persistGoalState(pi, runtime, command.kind, next);
277
- runtime.continuationScheduled = false;
278
- if (exhausted) {
279
- ctx.ui.notify(`Goal paused: turn limit reached (${next.turns}/${next.maxTurns})`, "warning");
280
- } else {
281
- const message = command.kind === "check"
282
- ? completionCheck ? `Goal completion check set to ${completionCheck.name}` : "Goal completion check cleared"
283
- : next.maxTurns === undefined ? "Goal turn limit cleared" : `Goal turn limit set to ${next.maxTurns}`;
284
- ctx.ui.notify(message, "info");
285
- scheduleGoalContinuation(pi, runtime, initState, ctx);
286
- }
287
- } catch (error) {
288
- reportError(ctx, `Goal ${command.kind} could not be changed`, error);
289
- scheduleGoalContinuation(pi, runtime, initState, ctx);
290
- }
291
- return;
292
- }
293
-
294
186
  if (command.kind === "clear") {
295
187
  if (!runtime.state) {
296
188
  ctx.ui.notify("No goal is set", "info");
@@ -401,17 +293,30 @@ export function registerGoalInterface(
401
293
  return;
402
294
  }
403
295
  if (runtime.state.status === "complete") {
404
- ctx.ui.notify("The goal is complete. Set a new objective or use /goal edit.", "info");
405
- return;
406
- }
407
- if (runtime.state.maxTurns !== undefined && runtime.state.turns >= runtime.state.maxTurns) {
408
- ctx.ui.notify(`Goal turn limit reached (${runtime.state.turns}/${runtime.state.maxTurns}). Raise or clear it before resuming.`, "warning");
296
+ ctx.ui.notify("The goal is complete. Set a new objective.", "info");
409
297
  return;
410
298
  }
411
299
  if (runtime.state.status === "active") {
412
300
  ctx.ui.notify("Goal is already active", "info");
413
301
  return;
414
302
  }
303
+ const currentMax = runtime.state.maxTurns;
304
+ if (currentMax !== undefined && runtime.state.turns >= currentMax) {
305
+ if (runtime.state.turns >= GOAL_MAX_TURNS) {
306
+ ctx.ui.notify(`Goal reached the lifetime limit (${runtime.state.turns}/${GOAL_MAX_TURNS}). Set a new objective.`, "warning");
307
+ return;
308
+ }
309
+ const renewed = Math.min(Math.max(currentMax, runtime.state.turns) + DEFAULT_GOAL_MAX_TURNS, GOAL_MAX_TURNS);
310
+ try {
311
+ const base = transitionGoalState(runtime.state, "active", undefined, { resetBlockedAudit: true }, Date.now());
312
+ persistGoalState(pi, runtime, "resume", { ...base, maxTurns: renewed });
313
+ runtime.continuationScheduled = false;
314
+ if (scheduleGoalContinuation(pi, runtime, initState, ctx)) ctx.ui.notify("Goal resumed", "info");
315
+ } catch (error) {
316
+ reportError(ctx, "Goal could not be resumed", error);
317
+ }
318
+ return;
319
+ }
415
320
  try {
416
321
  transitionGoal(pi, runtime, "resume", "active", undefined, { resetBlockedAudit: true });
417
322
  runtime.continuationScheduled = false;
@@ -422,80 +327,12 @@ export function registerGoalInterface(
422
327
  return;
423
328
  }
424
329
 
425
- if (command.kind === "edit") {
426
- if (initState.active) {
427
- ctx.ui.notify("Wait for /init to finish before editing a goal", "error");
428
- return;
429
- }
430
- if (!runtime.state) {
431
- ctx.ui.notify("No goal is set", "info");
432
- return;
433
- }
434
- if (ctx.mode !== "tui") {
435
- ctx.ui.notify("/goal edit requires interactive TUI mode", "error");
436
- return;
437
- }
438
- runtime.continuationHeld = true;
439
- let waitError: unknown;
440
- try {
441
- await ctx.waitForIdle();
442
- } catch (error) {
443
- waitError = error;
444
- } finally {
445
- runtime.continuationHeld = false;
446
- }
447
- if (waitError) {
448
- reportError(ctx, "Goal could not wait for the active turn", waitError);
449
- scheduleGoalContinuation(pi, runtime, initState, ctx);
450
- return;
451
- }
452
- const edited = await ctx.ui.editor("Edit long-running goal", runtime.state.objective);
453
- if (edited === undefined) {
454
- scheduleGoalContinuation(pi, runtime, initState, ctx);
455
- return;
456
- }
457
- const objective = validateGoalObjective(edited);
458
- if (!objective) {
459
- ctx.ui.notify(edited.trim() ? "A goal objective may not exceed 4,000 characters" : "A goal objective may not be empty", "error");
460
- scheduleGoalContinuation(pi, runtime, initState, ctx);
461
- return;
462
- }
463
- let verification: Awaited<ReturnType<typeof inferGoalVerification>>;
464
- try {
465
- verification = await inferGoalVerification(objective);
466
- } catch (error) {
467
- reportError(ctx, "Goal verification could not be inferred", error);
468
- scheduleGoalContinuation(pi, runtime, initState, ctx);
469
- return;
470
- }
471
- const next = editGoalState(runtime.state, objective, verification, Date.now());
472
- try {
473
- persistGoalState(pi, runtime, "edit", next);
474
- runtime.continuationScheduled = false;
475
- if (scheduleGoalContinuation(pi, runtime, initState, ctx)) ctx.ui.notify("Goal updated and active", "info");
476
- } catch (error) {
477
- if (runtime.state?.status === "active") {
478
- pauseGoalAfterFailure(
479
- pi,
480
- runtime,
481
- ctx,
482
- `Goal could not be edited: ${error instanceof Error ? error.message : String(error)}`,
483
- "Automatic continuation is stopped. Retry /goal edit after session storage recovers.",
484
- );
485
- } else {
486
- reportError(ctx, "Goal could not be edited", error);
487
- }
488
- }
489
- return;
490
- }
491
-
492
330
  if (initState.active) {
493
331
  ctx.ui.notify("Wait for /init to finish before starting a goal", "error");
494
332
  return;
495
333
  }
496
334
  switch (command.kind) {
497
335
  case "objective":
498
- case "start":
499
336
  break;
500
337
  default: {
501
338
  const unhandled: never = command;
@@ -503,17 +340,6 @@ export function registerGoalInterface(
503
340
  }
504
341
  }
505
342
  const objective = command.objective;
506
- const controlledStart = command.kind === "start" ? command : undefined;
507
-
508
- let completionCheck: GoalCompletionCheck | undefined;
509
- if (controlledStart?.completionCheckName) {
510
- try {
511
- completionCheck = resolveGoalCompletionCheck(ctx, controlledStart.completionCheckName);
512
- } catch (error) {
513
- reportError(ctx, "Goal completion check could not be resolved", error);
514
- return;
515
- }
516
- }
517
343
 
518
344
  const unfinished = runtime.state && runtime.state.status !== "complete";
519
345
  if (unfinished) {
@@ -539,14 +365,21 @@ export function registerGoalInterface(
539
365
  scheduleGoalContinuation(pi, runtime, initState, ctx);
540
366
  return;
541
367
  }
368
+ let verification: Awaited<ReturnType<typeof inferGoalVerification>>;
542
369
  try {
543
- if (controlledStart?.completionCheckName) {
544
- completionCheck = resolveGoalCompletionCheck(ctx, controlledStart.completionCheckName);
370
+ verification = await inferGoalVerification(objective, ctx.cwd);
371
+ } catch (error) {
372
+ if (!unfinished) {
373
+ reportError(ctx, "Goal could not be started", error);
374
+ } else {
375
+ reportError(ctx, "Goal could not be replaced", error);
376
+ scheduleGoalContinuation(pi, runtime, initState, ctx);
545
377
  }
546
- const verification = await inferGoalVerification(objective);
378
+ return;
379
+ }
380
+ try {
547
381
  const state = createNewGoalState(objective, sumGoalTokens(ctx), verification, Date.now(), {
548
- ...(completionCheck === undefined ? {} : { completionCheck }),
549
- maxTurns: controlledStart?.maxTurns ?? DEFAULT_GOAL_MAX_TURNS,
382
+ maxTurns: DEFAULT_GOAL_MAX_TURNS,
550
383
  });
551
384
  persistGoalState(pi, runtime, unfinished ? "replace" : "set", state);
552
385
  if (scheduleGoalContinuation(pi, runtime, initState, ctx)) {
@@ -573,23 +406,11 @@ export function registerGoalInterface(
573
406
  description: "Set a non-command objective or view the current goal",
574
407
  getArgumentCompletions: (prefix) => {
575
408
  const normalized = prefix.trimStart().toLowerCase();
576
- const actions = normalized.startsWith("start ")
577
- ? [
578
- { value: "start --check ", description: "Bind a named completion check" },
579
- { value: "start --turns ", description: "Set a goal turn limit" },
580
- { value: "start -- ", description: "Start a goal with strict syntax" },
581
- ]
582
- : [
583
- { value: "clear", description: "Remove the current goal" },
584
- { value: "edit", description: "Edit and reactivate the current goal" },
585
- { value: "pause", description: "Stop automatic continuation" },
586
- { value: "resume", description: "Resume automatic continuation" },
587
- { value: "start", description: "Start with optional controls" },
588
- { value: "check", description: "Set or clear a completion check" },
589
- { value: "checks", description: "List completion checks" },
590
- { value: "limit", description: "Set or clear a turn limit" },
591
- { value: "history", description: "Show goal history" },
592
- ];
409
+ const actions = [
410
+ { value: "clear", description: "Remove the current goal" },
411
+ { value: "pause", description: "Stop automatic continuation" },
412
+ { value: "resume", description: "Resume automatic continuation" },
413
+ ];
593
414
  return actions
594
415
  .filter((action) => action.value.startsWith(normalized))
595
416
  .map((action) => ({ ...action, label: action.value.trimEnd() }));
@@ -9,7 +9,7 @@ export const GOAL_ENTRY_TYPE = "killeros-goal";
9
9
  const GOAL_CONTINUATION_TYPE = "killeros-goal-continuation";
10
10
  export const GOAL_UPDATE_TOOL = "killeros_goal_update";
11
11
 
12
- export type GoalEntryEvent = "set" | "replace" | "edit" | "check" | "limit" | "turn" | "pause" | "resume" | "blocked" | "complete" | "error" | "clear" | "checkpoint" | "blocker-audit";
12
+ export type GoalEntryEvent = "set" | "replace" | "limit" | "turn" | "pause" | "resume" | "blocked" | "complete" | "error" | "clear" | "checkpoint" | "blocker-audit";
13
13
  export interface GoalEntryData {
14
14
  version: 1;
15
15
  event: GoalEntryEvent;
@@ -125,14 +125,12 @@ export function pauseGoalAtTurnLimit(
125
125
  pi: ExtensionAPI,
126
126
  runtime: GoalRuntime,
127
127
  ctx: ExtensionContext,
128
- notify = true,
129
128
  ): boolean {
130
129
  const state = runtime.state;
131
130
  if (state?.status !== "active" || state.maxTurns === undefined || state.turns < state.maxTurns) return false;
132
131
  const result = `Turn limit reached (${state.turns}/${state.maxTurns}).`;
133
132
  try {
134
133
  transitionGoal(pi, runtime, "limit", "paused", result);
135
- if (notify) ctx.ui.notify(`Goal paused: turn limit reached (${state.turns}/${state.maxTurns})`, "warning");
136
134
  } catch (error) {
137
135
  pauseGoalAfterFailure(pi, runtime, ctx, `turn limit pause could not be saved: ${error instanceof Error ? error.message : String(error)}`);
138
136
  }
@@ -2,12 +2,11 @@ import { createHash } from "node:crypto";
2
2
  import type { Stats } from "node:fs";
3
3
  import { lstat, open, type FileHandle } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import type { GoalBlockerAudit, GoalCompletionCheck, GoalFileBaseline, GoalFileVerification, GoalState, GoalStateCommon, GoalStatus } from "./runtime.ts";
5
+ import type { GoalBlockerAudit, GoalFileBaseline, GoalFileVerification, GoalState, GoalStateCommon, GoalStatus } from "./runtime.ts";
6
6
 
7
7
  export const DEFAULT_GOAL_MAX_TURNS = 20;
8
8
  export const GOAL_OBJECTIVE_LIMIT = 4_000;
9
9
  export const GOAL_MAX_TURNS = 10_000;
10
- export const GOAL_CHECK_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
11
10
  export const GOAL_VERSION = 1;
12
11
  const FILE_HASH_CHUNK_SIZE = 64 * 1024;
13
12
  export const FILE_HASH_LIMIT = 64 * 1024 * 1024;
@@ -64,6 +63,14 @@ function isAbsoluteFilePath(value: string): boolean {
64
63
  return path.isAbsolute(value) || path.win32.isAbsolute(value);
65
64
  }
66
65
 
66
+ function stripUnquotedPathPunctuation(value: string): string {
67
+ const pathWithoutMarks = value.replace(/[.!?]+$/u, "");
68
+ const trailingClosers = pathWithoutMarks.match(/\)+$/u)?.[0].length ?? 0;
69
+ const unmatchedClosers = Math.max(0, pathWithoutMarks.split(")").length - pathWithoutMarks.split("(").length);
70
+ const punctuationLength = Math.min(trailingClosers, unmatchedClosers);
71
+ return pathWithoutMarks.slice(0, punctuationLength ? -punctuationLength : undefined);
72
+ }
73
+
67
74
  function isGoalFileVerification(value: unknown): value is GoalFileVerification {
68
75
  return isUnknownRecord(value)
69
76
  && value.kind === "file"
@@ -73,15 +80,6 @@ function isGoalFileVerification(value: unknown): value is GoalFileVerification {
73
80
  && isGoalFileBaseline(value.baseline);
74
81
  }
75
82
 
76
- function isGoalCompletionCheck(value: unknown): value is GoalCompletionCheck {
77
- return isUnknownRecord(value)
78
- && value.kind === "named-command"
79
- && typeof value.name === "string"
80
- && GOAL_CHECK_NAME_PATTERN.test(value.name)
81
- && typeof value.configHash === "string"
82
- && /^[a-f0-9]{64}$/u.test(value.configHash);
83
- }
84
-
85
83
  function isMaxTurns(value: unknown): value is number {
86
84
  return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= GOAL_MAX_TURNS;
87
85
  }
@@ -118,7 +116,6 @@ export function parseGoalState(value: unknown): GoalState | undefined {
118
116
  resumeAfterManualCompaction,
119
117
  blockerAudit,
120
118
  verification,
121
- completionCheck,
122
119
  maxTurns,
123
120
  } = value;
124
121
  if (version !== GOAL_VERSION
@@ -134,7 +131,6 @@ export function parseGoalState(value: unknown): GoalState | undefined {
134
131
  || !safeNonNegativeInteger(baselineTokens)
135
132
  || result !== undefined && typeof result !== "string"
136
133
  || verification !== undefined && !isGoalFileVerification(verification)
137
- || completionCheck !== undefined && !isGoalCompletionCheck(completionCheck)
138
134
  || maxTurns !== undefined && !isMaxTurns(maxTurns)
139
135
  || resumeAfterManualCompaction !== undefined && resumeAfterManualCompaction !== true
140
136
  || blockerAudit !== undefined && !isGoalBlockerAudit(blockerAudit, turns, status)) {
@@ -152,7 +148,6 @@ export function parseGoalState(value: unknown): GoalState | undefined {
152
148
  blockedAuditStartTurn: blockedAuditStartTurn ?? 0,
153
149
  baselineTokens,
154
150
  ...(verification === undefined ? {} : { verification }),
155
- ...(completionCheck === undefined ? {} : { completionCheck }),
156
151
  ...(maxTurns === undefined ? {} : { maxTurns }),
157
152
  };
158
153
  switch (status) {
@@ -238,13 +233,26 @@ export async function captureGoalFileBaseline(
238
233
  }
239
234
  }
240
235
 
241
- /** Captures one explicit absolute output path so goal completion can verify its creation or modification. */
242
- export async function inferGoalVerification(objective: string): Promise<GoalFileVerification | undefined> {
236
+ /** Captures one explicit output path so goal completion can verify its creation or modification. */
237
+ export async function inferGoalVerification(objective: string, cwd: string): Promise<GoalFileVerification | undefined> {
238
+ const candidates: string[] = [];
243
239
  const destination = /\b(?:create|write|save|generate)\b[^\r\n]{0,160}?\b(?:file|document|markdown|report|spreadsheet|presentation|image)\b\s+(?:to|at|as|destination(?:\s+is)?|output(?:\s+(?:to|at))?)\b\s*(?:`([^`\r\n]+)`|"([^"\r\n]+)"|'([^'\r\n]+)'|([A-Za-z]:\\[^\s,;]+|\/[^\s,;]+))/giu;
244
- const paths = [...objective.matchAll(destination)]
245
- .map((match) => (match[1] ?? match[2] ?? match[3] ?? match[4] ?? "").trim())
246
- .filter(isAbsoluteFilePath);
247
- const unique = [...new Set(paths)];
240
+ for (const match of objective.matchAll(destination)) {
241
+ const quoted = match[1] ?? match[2] ?? match[3];
242
+ candidates.push(quoted !== undefined ? quoted.trim() : stripUnquotedPathPunctuation((match[4] ?? "").trim()));
243
+ }
244
+ const direct = /\b(?:update|edit|fix|refactor|migrate)\s+(`([^`\r\n]+)`|"([^"\r\n]+)"|'([^'\r\n]+)')/giu;
245
+ for (const match of objective.matchAll(direct)) {
246
+ const quoted = match[2] ?? match[3] ?? match[4];
247
+ if (quoted !== undefined) candidates.push(quoted.trim());
248
+ }
249
+ const resolved: string[] = [];
250
+ for (const raw of candidates) {
251
+ if (!raw || /^(?:https?|file):\/\//iu.test(raw) || /[\\\/]$/u.test(raw)) continue;
252
+ const absolute = path.isAbsolute(raw) || path.win32.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
253
+ if (isAbsoluteFilePath(absolute)) resolved.push(absolute);
254
+ }
255
+ const unique = [...new Set(resolved)];
248
256
  const filePath = unique.length === 1 ? unique[0] : undefined;
249
257
  return filePath ? { kind: "file", path: filePath, baseline: await captureGoalFileBaseline(filePath) } : undefined;
250
258
  }
@@ -302,7 +310,6 @@ export function commonGoalState(state: GoalState): GoalStateCommon {
302
310
  blockedAuditStartTurn: state.blockedAuditStartTurn,
303
311
  baselineTokens: state.baselineTokens,
304
312
  ...(state.verification === undefined ? {} : { verification: state.verification }),
305
- ...(state.completionCheck === undefined ? {} : { completionCheck: state.completionCheck }),
306
313
  ...(state.maxTurns === undefined ? {} : { maxTurns: state.maxTurns }),
307
314
  };
308
315
  }
@@ -319,7 +326,7 @@ export function createNewGoalState(
319
326
  baselineTokens: number,
320
327
  verification: GoalFileVerification | undefined,
321
328
  now: number,
322
- controls: { completionCheck?: GoalCompletionCheck; maxTurns?: number } = {},
329
+ controls: { maxTurns?: number } = {},
323
330
  ): GoalState {
324
331
  return {
325
332
  version: GOAL_VERSION,
@@ -334,71 +341,10 @@ export function createNewGoalState(
334
341
  blockedAuditStartTurn: 0,
335
342
  baselineTokens,
336
343
  ...(verification === undefined ? {} : { verification }),
337
- ...(controls.completionCheck === undefined ? {} : { completionCheck: controls.completionCheck }),
338
344
  ...(controls.maxTurns === undefined ? {} : { maxTurns: controls.maxTurns }),
339
345
  };
340
346
  }
341
347
 
342
- export function editGoalState(
343
- state: GoalState,
344
- objective: string,
345
- verification: GoalFileVerification | undefined,
346
- now: number,
347
- ): GoalState {
348
- const current = stopGoalClock(state, now);
349
- const { verification: _previousVerification, ...common } = current;
350
- return {
351
- ...common,
352
- revision: current.revision + 1,
353
- objective,
354
- status: "active",
355
- updatedAt: now,
356
- activeStartedAt: now,
357
- blockedAuditStartTurn: current.turns,
358
- ...(verification === undefined ? {} : { verification }),
359
- };
360
- }
361
-
362
- export function updateGoalControlsState(
363
- state: Exclude<GoalState, { status: "complete" }>,
364
- controls: { completionCheck?: GoalCompletionCheck; maxTurns?: number },
365
- now: number,
366
- ): GoalState {
367
- const stopped = stopGoalClock(state, now);
368
- const { completionCheck: _completionCheck, maxTurns: _maxTurns, ...common } = stopped;
369
- const nextCommon: GoalStateCommon = {
370
- ...common,
371
- revision: common.revision + 1,
372
- updatedAt: now,
373
- ...(controls.completionCheck === undefined ? {} : { completionCheck: controls.completionCheck }),
374
- ...(controls.maxTurns === undefined ? {} : { maxTurns: controls.maxTurns }),
375
- };
376
- if (state.status === "active") {
377
- return {
378
- ...nextCommon,
379
- status: "active",
380
- activeStartedAt: now,
381
- ...(state.result === undefined ? {} : { result: state.result }),
382
- ...(state.blockerAudit === undefined ? {} : { blockerAudit: state.blockerAudit }),
383
- };
384
- }
385
- if (state.status === "paused") {
386
- return {
387
- ...nextCommon,
388
- status: "paused",
389
- ...(state.result === undefined ? {} : { result: state.result }),
390
- ...(state.blockerAudit === undefined ? {} : { blockerAudit: state.blockerAudit }),
391
- ...(state.resumeAfterManualCompaction === undefined ? {} : { resumeAfterManualCompaction: true }),
392
- };
393
- }
394
- return {
395
- ...nextCommon,
396
- status: "blocked",
397
- result: state.result,
398
- ...(state.blockerAudit === undefined ? {} : { blockerAudit: state.blockerAudit }),
399
- };
400
- }
401
-
402
348
  export function beginGoalTurnState(
403
349
  current: Extract<GoalState, { status: "active" }>,
404
350
  now: number,