ralphctl 0.10.0 → 0.11.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.
package/dist/cli.mjs
CHANGED
|
@@ -399,7 +399,10 @@ import { z } from "zod";
|
|
|
399
399
|
var CLAUDE_MODELS = [
|
|
400
400
|
"claude-haiku-4-5",
|
|
401
401
|
"claude-sonnet-4-6",
|
|
402
|
-
"claude-opus-4-8"
|
|
402
|
+
"claude-opus-4-8",
|
|
403
|
+
"claude-opus-4-8[1m]",
|
|
404
|
+
"claude-fable-5",
|
|
405
|
+
"claude-fable-5[1m]"
|
|
403
406
|
];
|
|
404
407
|
var isClaudeModel = (s) => CLAUDE_MODELS.includes(s);
|
|
405
408
|
|
|
@@ -408,6 +411,7 @@ var CODEX_MODELS = [
|
|
|
408
411
|
"gpt-5.5",
|
|
409
412
|
"gpt-5.4",
|
|
410
413
|
"gpt-5.4-mini",
|
|
414
|
+
"gpt-5.3-codex-spark",
|
|
411
415
|
"gpt-5.3-codex",
|
|
412
416
|
"gpt-5.2",
|
|
413
417
|
"codex-auto-review"
|
|
@@ -420,6 +424,7 @@ var COPILOT_MODELS = [
|
|
|
420
424
|
"gpt-5.4-mini",
|
|
421
425
|
"gpt-5.1",
|
|
422
426
|
"gpt-5.4",
|
|
427
|
+
"gpt-5.5",
|
|
423
428
|
"gpt-5.3-codex",
|
|
424
429
|
"gpt-5.2-codex",
|
|
425
430
|
"gpt-5.2",
|
|
@@ -431,10 +436,18 @@ var COPILOT_MODELS = [
|
|
|
431
436
|
"claude-sonnet-4.6",
|
|
432
437
|
"claude-sonnet-4.5",
|
|
433
438
|
"claude-sonnet-4",
|
|
439
|
+
"claude-opus-4.8",
|
|
440
|
+
"claude-opus-4.7",
|
|
434
441
|
"claude-opus-4.6",
|
|
435
442
|
"claude-opus-4.6-fast",
|
|
436
443
|
"claude-opus-4.5",
|
|
437
|
-
"gemini-3-pro-preview"
|
|
444
|
+
"gemini-3.1-pro-preview",
|
|
445
|
+
"gemini-3-pro-preview",
|
|
446
|
+
"gemini-3-flash-preview",
|
|
447
|
+
"gemini-3.5-flash",
|
|
448
|
+
"gemini-2.5-pro",
|
|
449
|
+
"mai-code-1-flash",
|
|
450
|
+
"raptor-mini-preview"
|
|
438
451
|
];
|
|
439
452
|
var isCopilotModel = (s) => COPILOT_MODELS.includes(s);
|
|
440
453
|
|
|
@@ -522,6 +535,18 @@ var migrateRetiredClaudeOpus = (ai) => {
|
|
|
522
535
|
return next;
|
|
523
536
|
};
|
|
524
537
|
var promoteLegacyAiRows = (ai) => migrateRetiredClaudeOpus(seedLegacyCreatePrRow(promoteLegacyImplementRow(ai)));
|
|
538
|
+
var healHarnessCrossKnobs = (harness) => {
|
|
539
|
+
if (typeof harness !== "object" || harness === null) return harness;
|
|
540
|
+
const h = harness;
|
|
541
|
+
const turns = h["maxTurns"];
|
|
542
|
+
if (typeof turns !== "number" || !Number.isInteger(turns)) return harness;
|
|
543
|
+
const rawThreshold = h["plateauThreshold"];
|
|
544
|
+
const threshold = typeof rawThreshold === "number" && Number.isInteger(rawThreshold) ? rawThreshold : 3;
|
|
545
|
+
if (turns >= threshold) return harness;
|
|
546
|
+
const healedThreshold = Math.max(2, Math.min(threshold, turns));
|
|
547
|
+
const healedTurns = Math.max(turns, healedThreshold);
|
|
548
|
+
return { ...h, maxTurns: healedTurns, plateauThreshold: healedThreshold };
|
|
549
|
+
};
|
|
525
550
|
var AiSettingsSchema = z.preprocess(
|
|
526
551
|
promoteLegacyAiRows,
|
|
527
552
|
z.object({
|
|
@@ -542,40 +567,64 @@ var SettingsSchema = z.object({
|
|
|
542
567
|
*/
|
|
543
568
|
schemaVersion: z.literal(CURRENT_SCHEMA_VERSION).default(CURRENT_SCHEMA_VERSION),
|
|
544
569
|
ai: AiSettingsSchema,
|
|
545
|
-
harness: z.
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
570
|
+
harness: z.preprocess(
|
|
571
|
+
healHarnessCrossKnobs,
|
|
572
|
+
z.object({
|
|
573
|
+
/** Generator–evaluator turns budgeted per `Attempt` (1–10). */
|
|
574
|
+
maxTurns: z.number().int().min(1).max(10),
|
|
575
|
+
/** Default cap on attempts per task before transitioning to `blocked` (1–10). */
|
|
576
|
+
maxAttempts: z.number().int().min(1).max(10),
|
|
577
|
+
/** Adapter-side retries on `RateLimitError` before surfacing the failure (0–10). */
|
|
578
|
+
rateLimitRetries: z.number().int().min(0).max(10),
|
|
579
|
+
/**
|
|
580
|
+
* Consecutive evaluator turns flagging the same failed-dimension set before the loop
|
|
581
|
+
* exits with a `plateau` warning (2–5, default 3 — patient: the graduated remedy ladder
|
|
582
|
+
* climbs cheapest-first across plateaus, so a slightly higher threshold avoids spending an
|
|
583
|
+
* escalation rung on a stall the generator would have broken on its own). See
|
|
584
|
+
* `business/task/plateau-detection.ts` for the exemption rules (score improvement /
|
|
585
|
+
* commit-message change / critique-prose shift) that can soften or skip the plateau even
|
|
586
|
+
* when the threshold is met.
|
|
587
|
+
*/
|
|
588
|
+
plateauThreshold: z.number().int().min(2).max(5).default(3),
|
|
589
|
+
/**
|
|
590
|
+
* Master switch for failure-driven generator-model escalation. The flag name is retained for
|
|
591
|
+
* backward compatibility, but it now gates ALL failure-driven escalation — not only plateau:
|
|
592
|
+
* `plateau` AND `budget-exhausted` (the turn budget ran out without a terminal verdict) exits
|
|
593
|
+
* both climb the ladder, and `malformed` exits (the evaluator emitted no parseable verdict) get
|
|
594
|
+
* a plain same-model fresh-attempt retry — instead of settling immediately.
|
|
595
|
+
*
|
|
596
|
+
* On an escalatable exit the generator climbs one rung up the merged ladder ({@link
|
|
597
|
+
* escalationMap} over the built-in `DEFAULT_ESCALATION_MAP`) across successive failures, bounded
|
|
598
|
+
* by `maxAttempts`; each climb hands the targeted prior critique to the stronger model. The
|
|
599
|
+
* "change your approach" directive is NOT injected on a model bump — it fires only once the
|
|
600
|
+
* generator reaches the top of the ladder, as a same-model nudge (one more attempt on the same
|
|
601
|
+
* model, where no fresh capability remains so a change of approach is the only lever). A
|
|
602
|
+
* malformed exit never burns a ladder rung (it is the evaluator's failure, not the generator's)
|
|
603
|
+
* — it retries on the same model while budget remains.
|
|
604
|
+
*
|
|
605
|
+
* A failure-driven retry never blocks: after the ladder tops out (a further failure on the
|
|
606
|
+
* nudged top-tier model) or the attempt budget is exhausted, the work is preserved
|
|
607
|
+
* (done-with-warning). Defaults `true`.
|
|
608
|
+
*/
|
|
609
|
+
escalateOnPlateau: z.boolean().default(true),
|
|
610
|
+
/**
|
|
611
|
+
* User overrides for the built-in `DEFAULT_ESCALATION_MAP` (in
|
|
612
|
+
* `business/task/escalation-map.ts`). Keys are the current model id, values the model id
|
|
613
|
+
* to escalate to. Empty by default; merged at read time with user keys winning on
|
|
614
|
+
* conflict and extending the default ladder. Non-string entries fail schema validation
|
|
615
|
+
* with a typed Zod error naming the offending field.
|
|
616
|
+
*/
|
|
617
|
+
escalationMap: z.record(z.string(), z.string()).default({})
|
|
618
|
+
}).superRefine((harness, ctx) => {
|
|
619
|
+
if (harness.maxTurns < harness.plateauThreshold) {
|
|
620
|
+
ctx.addIssue({
|
|
621
|
+
code: z.ZodIssueCode.custom,
|
|
622
|
+
path: ["maxTurns"],
|
|
623
|
+
message: `maxTurns (${String(harness.maxTurns)}) must be \u2265 plateauThreshold (${String(harness.plateauThreshold)}); when maxTurns < plateauThreshold the plateau window can never fill within a single attempt and escalation becomes unreachable. Increase maxTurns or lower plateauThreshold.`
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
})
|
|
627
|
+
),
|
|
579
628
|
logging: z.object({
|
|
580
629
|
level: LogLevelSchema
|
|
581
630
|
}),
|
|
@@ -674,7 +723,10 @@ var DEFAULT_MODELS_BY_PROVIDER = {
|
|
|
674
723
|
"openai-codex": {
|
|
675
724
|
refine: "gpt-5.4-mini",
|
|
676
725
|
plan: "gpt-5.5",
|
|
677
|
-
|
|
726
|
+
// `gpt-5.3-codex` is deprecated for ChatGPT sign-in — the "reset implement to Codex" path
|
|
727
|
+
// rides the frontier default `gpt-5.5` instead, matching the CODEX_ONLY preset decision so
|
|
728
|
+
// the everyday autonomous loop works under ChatGPT auth and sits at the top of the ladder.
|
|
729
|
+
implement: "gpt-5.5",
|
|
678
730
|
readiness: "gpt-5.4-mini",
|
|
679
731
|
ideate: "gpt-5.5",
|
|
680
732
|
createPr: "gpt-5.4-mini"
|
|
@@ -705,7 +757,7 @@ var DEFAULT_SETTINGS = {
|
|
|
705
757
|
maxTurns: 5,
|
|
706
758
|
maxAttempts: 3,
|
|
707
759
|
rateLimitRetries: 3,
|
|
708
|
-
plateauThreshold:
|
|
760
|
+
plateauThreshold: 3,
|
|
709
761
|
escalateOnPlateau: true,
|
|
710
762
|
escalationMap: {}
|
|
711
763
|
},
|
|
@@ -902,13 +954,24 @@ var createGitRunner = (deps = {}) => {
|
|
|
902
954
|
);
|
|
903
955
|
return;
|
|
904
956
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
957
|
+
try {
|
|
958
|
+
finish(
|
|
959
|
+
Result.ok({
|
|
960
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
961
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
962
|
+
exitCode: code ?? -1
|
|
963
|
+
})
|
|
964
|
+
);
|
|
965
|
+
} catch (cause) {
|
|
966
|
+
finish(
|
|
967
|
+
Result.error(
|
|
968
|
+
new StorageError({
|
|
969
|
+
subCode: "io",
|
|
970
|
+
message: `git output too large to materialize: git ${args.join(" ")} \u2014 ${stringifyError(cause)}`
|
|
971
|
+
})
|
|
972
|
+
)
|
|
973
|
+
);
|
|
974
|
+
}
|
|
912
975
|
});
|
|
913
976
|
});
|
|
914
977
|
return { run };
|
|
@@ -1892,10 +1955,18 @@ var markTaskBlocked = (task, reason, blockKind) => {
|
|
|
1892
1955
|
var unblockTask = (task) => {
|
|
1893
1956
|
const guard2 = requireStatus("task", task, ["blocked"], "unblock");
|
|
1894
1957
|
if (!guard2.ok) return Result.error(guard2.error);
|
|
1895
|
-
const {
|
|
1958
|
+
const {
|
|
1959
|
+
blockedReason: _reason,
|
|
1960
|
+
blockKind: _kind,
|
|
1961
|
+
escalatedFromModel: _from,
|
|
1962
|
+
escalatedToModel: _to,
|
|
1963
|
+
...rest
|
|
1964
|
+
} = guard2.value;
|
|
1896
1965
|
void _reason;
|
|
1897
1966
|
void _kind;
|
|
1898
|
-
|
|
1967
|
+
void _from;
|
|
1968
|
+
void _to;
|
|
1969
|
+
return Result.ok({ ...rest, status: "todo", attempts: [] });
|
|
1899
1970
|
};
|
|
1900
1971
|
var resetTaskToTodo = (task) => {
|
|
1901
1972
|
if (task.status === "todo") return Result.ok(task);
|
|
@@ -2661,7 +2732,10 @@ var CONTEXT_WINDOW = {
|
|
|
2661
2732
|
// Claude (claude-code adapter — direct from Anthropic)
|
|
2662
2733
|
"claude-haiku-4-5": 2e5,
|
|
2663
2734
|
"claude-sonnet-4-6": 2e5,
|
|
2664
|
-
"claude-opus-4-8": 2e5
|
|
2735
|
+
"claude-opus-4-8": 2e5,
|
|
2736
|
+
// `[1m]` is Claude Code's 1M-token long-context selector — the window is part of the id.
|
|
2737
|
+
"claude-opus-4-8[1m]": 1e6,
|
|
2738
|
+
"claude-fable-5[1m]": 1e6
|
|
2665
2739
|
};
|
|
2666
2740
|
var contextWindowFor = (model) => {
|
|
2667
2741
|
if (model === void 0) return void 0;
|
|
@@ -4305,22 +4379,24 @@ var parseIssueUrl = (url) => {
|
|
|
4305
4379
|
const repo = segments[1];
|
|
4306
4380
|
const num = Number(segments[3]);
|
|
4307
4381
|
if (owner && repo && Number.isInteger(num) && num > 0) {
|
|
4308
|
-
return { host: "github", owner, repo, number: num };
|
|
4382
|
+
return { host: "github", hostname: parsed.hostname, owner, repo, number: num };
|
|
4309
4383
|
}
|
|
4310
4384
|
}
|
|
4311
4385
|
return null;
|
|
4312
4386
|
}
|
|
4313
4387
|
const dashIdx = segments.indexOf("-");
|
|
4314
|
-
|
|
4388
|
+
const kind = dashIdx >= 0 ? segments[dashIdx + 1] : void 0;
|
|
4389
|
+
if (dashIdx >= 2 && (kind === "issues" || kind === "work_items")) {
|
|
4315
4390
|
const num = Number(segments[dashIdx + 2]);
|
|
4316
4391
|
const repo = segments[dashIdx - 1];
|
|
4317
4392
|
if (repo && Number.isInteger(num) && num > 0) {
|
|
4318
4393
|
const owner = segments.slice(0, dashIdx - 1).join("/");
|
|
4319
|
-
return { host: "gitlab", owner, repo, number: num };
|
|
4394
|
+
return { host: "gitlab", hostname: parsed.hostname, owner, repo, number: num };
|
|
4320
4395
|
}
|
|
4321
4396
|
}
|
|
4322
4397
|
return null;
|
|
4323
4398
|
};
|
|
4399
|
+
var glabRepoArg = (parsed) => `${parsed.hostname}/${parsed.owner}/${parsed.repo}`;
|
|
4324
4400
|
var looksLikeNotFound = (stderr) => {
|
|
4325
4401
|
const s = stderr.toLowerCase();
|
|
4326
4402
|
return s.includes("not found") || s.includes("404") || s.includes("could not resolve") || s.includes("does not exist");
|
|
@@ -4378,7 +4454,7 @@ var fetchGitLabNotes = async (spawn3, parsed) => {
|
|
|
4378
4454
|
const result = await runCli(
|
|
4379
4455
|
spawn3,
|
|
4380
4456
|
"glab",
|
|
4381
|
-
["issue", "note", "list", String(parsed.number), "--repo",
|
|
4457
|
+
["issue", "note", "list", String(parsed.number), "--repo", glabRepoArg(parsed), "--output", "json"],
|
|
4382
4458
|
{ timeoutMs: CLI_TIMEOUT_MS }
|
|
4383
4459
|
);
|
|
4384
4460
|
if (!result.ok) return { comments: [], failure: result.error.message };
|
|
@@ -4409,7 +4485,7 @@ var fetchGitLab = async (spawn3, parsed, url, logger) => {
|
|
|
4409
4485
|
const result = await runCli(
|
|
4410
4486
|
spawn3,
|
|
4411
4487
|
"glab",
|
|
4412
|
-
["issue", "view", String(parsed.number), "--repo",
|
|
4488
|
+
["issue", "view", String(parsed.number), "--repo", glabRepoArg(parsed), "--output", "json"],
|
|
4413
4489
|
{ timeoutMs: CLI_TIMEOUT_MS }
|
|
4414
4490
|
);
|
|
4415
4491
|
if (!result.ok) return Result.error(result.error);
|
|
@@ -4477,7 +4553,15 @@ var commentGitLab = async (spawn3, url, body, parsed) => {
|
|
|
4477
4553
|
const r = await runCli(
|
|
4478
4554
|
spawn3,
|
|
4479
4555
|
"glab",
|
|
4480
|
-
[
|
|
4556
|
+
[
|
|
4557
|
+
"issue",
|
|
4558
|
+
"comment",
|
|
4559
|
+
String(parsed.number),
|
|
4560
|
+
"--repo",
|
|
4561
|
+
`${parsed.hostname}/${parsed.owner}/${parsed.repo}`,
|
|
4562
|
+
"--body",
|
|
4563
|
+
body
|
|
4564
|
+
],
|
|
4481
4565
|
{ timeoutMs: CLI_TIMEOUT_MS2 }
|
|
4482
4566
|
);
|
|
4483
4567
|
if (!r.ok) return Result.error(r.error);
|
|
@@ -5075,7 +5159,7 @@ var createNpmVersionChecker = (deps) => {
|
|
|
5075
5159
|
// package.json
|
|
5076
5160
|
var package_default = {
|
|
5077
5161
|
name: "ralphctl",
|
|
5078
|
-
version: "0.
|
|
5162
|
+
version: "0.11.0",
|
|
5079
5163
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code, GitHub Copilot, and OpenAI Codex across repositories",
|
|
5080
5164
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
5081
5165
|
type: "module",
|
|
@@ -5185,12 +5269,17 @@ var CLI_METADATA = {
|
|
|
5185
5269
|
|
|
5186
5270
|
// src/business/task/escalation-map.ts
|
|
5187
5271
|
var DEFAULT_ESCALATION_MAP = {
|
|
5188
|
-
// Claude
|
|
5272
|
+
// Claude (Claude-Code / Codex dash-form) — Haiku → Sonnet → Opus.
|
|
5189
5273
|
"claude-haiku-4-5": "claude-sonnet-4-6",
|
|
5190
5274
|
"claude-sonnet-4-6": "claude-opus-4-8",
|
|
5191
|
-
// Copilot
|
|
5275
|
+
// Claude (Copilot dot-form) — Haiku → Sonnet → Opus.
|
|
5276
|
+
"claude-haiku-4.5": "claude-sonnet-4.6",
|
|
5277
|
+
"claude-sonnet-4.6": "claude-opus-4.8",
|
|
5278
|
+
// Copilot/Codex GPT — mini variants step up to their full-tier frontier, and the
|
|
5279
|
+
// economic full tier (`gpt-5.4`) climbs to the flagship.
|
|
5192
5280
|
"gpt-5-mini": "gpt-5.5",
|
|
5193
|
-
"gpt-5.4-mini": "gpt-5.5"
|
|
5281
|
+
"gpt-5.4-mini": "gpt-5.5",
|
|
5282
|
+
"gpt-5.4": "gpt-5.5"
|
|
5194
5283
|
};
|
|
5195
5284
|
var mergeEscalationMap = (user) => ({
|
|
5196
5285
|
...DEFAULT_ESCALATION_MAP,
|
|
@@ -5203,6 +5292,16 @@ var warnEscalationMapSelfLoops = (escalationMap, logger) => {
|
|
|
5203
5292
|
}
|
|
5204
5293
|
}
|
|
5205
5294
|
};
|
|
5295
|
+
var escalationLadderCyclicFrom = (map, start) => {
|
|
5296
|
+
const seen = /* @__PURE__ */ new Set([start]);
|
|
5297
|
+
let cur = map[start];
|
|
5298
|
+
while (cur !== void 0) {
|
|
5299
|
+
if (seen.has(cur)) return true;
|
|
5300
|
+
seen.add(cur);
|
|
5301
|
+
cur = map[cur];
|
|
5302
|
+
}
|
|
5303
|
+
return false;
|
|
5304
|
+
};
|
|
5206
5305
|
|
|
5207
5306
|
// src/integration/ai/skills/_engine/filesystem-skills-adapter.ts
|
|
5208
5307
|
import { existsSync } from "fs";
|
|
@@ -10212,7 +10311,8 @@ var TaskBlock = ({
|
|
|
10212
10311
|
cardExpanded,
|
|
10213
10312
|
cardFocused,
|
|
10214
10313
|
nowMs,
|
|
10215
|
-
blockedReason
|
|
10314
|
+
blockedReason,
|
|
10315
|
+
warningSummary
|
|
10216
10316
|
}) => {
|
|
10217
10317
|
const presentation = STATUS_PRESENTATION[task.status];
|
|
10218
10318
|
const isSpinning = task.status === "running";
|
|
@@ -10224,6 +10324,7 @@ var TaskBlock = ({
|
|
|
10224
10324
|
const evalElided = task.evaluations.length - evalRows.length;
|
|
10225
10325
|
const criteriaBullets = taskCriteria;
|
|
10226
10326
|
const blockedReasonText = blockedReason?.trim() ?? "";
|
|
10327
|
+
const warningSummaryText = task.status === "completed" ? warningSummary?.trim() ?? "" : "";
|
|
10227
10328
|
const latestCommitSha = useMemo8(() => {
|
|
10228
10329
|
const sha = taskProjection?.lastAttempt?.commitSha;
|
|
10229
10330
|
return sha !== void 0 ? String(sha).slice(0, 7) : void 0;
|
|
@@ -10313,6 +10414,14 @@ var TaskBlock = ({
|
|
|
10313
10414
|
" ",
|
|
10314
10415
|
collapseWhitespace(blockedReasonText)
|
|
10315
10416
|
] }) }) }),
|
|
10417
|
+
warningSummaryText.length > 0 && // A done card that carries a final-attempt warning — surfaced collapsed OR expanded so the
|
|
10418
|
+
// operator never reads a flagged completion as a clean pass. Same warning glyph as the
|
|
10419
|
+
// attempt-card / blocked line; the tasks list previously showed it only for blockedReason.
|
|
10420
|
+
/* @__PURE__ */ jsx33(Box22, { paddingLeft: 2, children: /* @__PURE__ */ jsx33(Box22, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsxs23(Text23, { color: inkColors.warning, wrap: "truncate-end", children: [
|
|
10421
|
+
glyphs.warningGlyph,
|
|
10422
|
+
" ",
|
|
10423
|
+
collapseWhitespace(warningSummaryText)
|
|
10424
|
+
] }) }) }),
|
|
10316
10425
|
cardExpanded && idleSnippets.length > 0 && /* @__PURE__ */ jsx33(Box22, { paddingLeft: 2, children: /* @__PURE__ */ jsx33(Box22, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsxs23(Text23, { dimColor: true, wrap: "truncate-end", children: [
|
|
10317
10426
|
glyphs.activityArrow,
|
|
10318
10427
|
" ",
|
|
@@ -10531,6 +10640,7 @@ var TasksPanel = ({
|
|
|
10531
10640
|
inputActive = false,
|
|
10532
10641
|
taskCriteriaById,
|
|
10533
10642
|
blockedReasonById,
|
|
10643
|
+
warningSummaryById,
|
|
10534
10644
|
showEvaluatorFailureUI = false,
|
|
10535
10645
|
sprintState,
|
|
10536
10646
|
nowMs
|
|
@@ -10634,6 +10744,7 @@ var TasksPanel = ({
|
|
|
10634
10744
|
const taskCriteria = criteriaBullets !== void 0 && criteriaBullets.length > 0 ? criteriaBullets : void 0;
|
|
10635
10745
|
const taskProjection = sprintState?.tasks.find((t) => t.id === task.id);
|
|
10636
10746
|
const blockedReason = blockedReasonById?.get(task.id);
|
|
10747
|
+
const warningSummary = warningSummaryById?.get(task.id);
|
|
10637
10748
|
const isActive = idx === activeTaskIdx;
|
|
10638
10749
|
return /* @__PURE__ */ jsx34(
|
|
10639
10750
|
TaskBlock,
|
|
@@ -10658,7 +10769,8 @@ var TasksPanel = ({
|
|
|
10658
10769
|
...recovering !== void 0 ? { recovering } : {},
|
|
10659
10770
|
...taskCriteria !== void 0 ? { taskCriteria } : {},
|
|
10660
10771
|
...taskProjection !== void 0 ? { taskProjection } : {},
|
|
10661
|
-
...blockedReason !== void 0 ? { blockedReason } : {}
|
|
10772
|
+
...blockedReason !== void 0 ? { blockedReason } : {},
|
|
10773
|
+
...warningSummary !== void 0 ? { warningSummary } : {}
|
|
10662
10774
|
},
|
|
10663
10775
|
task.id
|
|
10664
10776
|
);
|
|
@@ -11670,11 +11782,13 @@ var formatDuration = (ms) => {
|
|
|
11670
11782
|
return minutes > 0 ? `${String(hours)}h ${String(minutes)}m` : `${String(hours)}h`;
|
|
11671
11783
|
};
|
|
11672
11784
|
var SHA_DISPLAY_LENGTH = 7;
|
|
11785
|
+
var sanitizeInline = (text) => text.replace(/[\r\n\t\v\f]+/g, " ").trim();
|
|
11786
|
+
var neutralizeProseHeadings = (text) => text.split("\n").map((line) => line.startsWith("#") ? ` ${line}` : line).join("\n");
|
|
11673
11787
|
var appendSubsection = (lines, heading, entries) => {
|
|
11674
11788
|
if (entries.length === 0) return;
|
|
11675
11789
|
lines.push(`### ${heading}`);
|
|
11676
11790
|
for (const entry of entries) {
|
|
11677
|
-
lines.push(`- ${entry}`);
|
|
11791
|
+
lines.push(`- ${entry.split("\n").join("\n ")}`);
|
|
11678
11792
|
}
|
|
11679
11793
|
lines.push("");
|
|
11680
11794
|
};
|
|
@@ -11682,22 +11796,64 @@ var appendLearningsSubsection = (lines, entries) => {
|
|
|
11682
11796
|
if (entries.length === 0) return;
|
|
11683
11797
|
lines.push("### Learnings");
|
|
11684
11798
|
for (const entry of entries) {
|
|
11685
|
-
lines.push(`- **${entry.text}**`);
|
|
11799
|
+
lines.push(`- **${entry.text.split("\n").join("\n ")}**`);
|
|
11686
11800
|
if (entry.context !== void 0 && entry.context.trim().length > 0) {
|
|
11687
|
-
lines.push(` - Context: ${entry.context.trim()}`);
|
|
11801
|
+
lines.push(` - Context: ${entry.context.trim().split("\n").join("\n ")}`);
|
|
11688
11802
|
}
|
|
11689
11803
|
if (entry.appliesTo !== void 0 && entry.appliesTo.trim().length > 0) {
|
|
11690
|
-
lines.push(` - Applies to: ${entry.appliesTo.trim()}`);
|
|
11804
|
+
lines.push(` - Applies to: ${entry.appliesTo.trim().split("\n").join("\n ")}`);
|
|
11805
|
+
}
|
|
11806
|
+
}
|
|
11807
|
+
lines.push("");
|
|
11808
|
+
};
|
|
11809
|
+
var warningSentence = (warning) => {
|
|
11810
|
+
switch (warning.kind) {
|
|
11811
|
+
case "budget-exhausted": {
|
|
11812
|
+
const turns = warning.turnsUsed !== void 0 && warning.turnBudget !== void 0 ? ` after exhausting the turn budget (${String(warning.turnsUsed)} of ${String(warning.turnBudget)} turns used)` : " after exhausting the turn budget";
|
|
11813
|
+
return `The evaluator did not pass${turns}.`;
|
|
11814
|
+
}
|
|
11815
|
+
case "plateau": {
|
|
11816
|
+
const dims = warning.dimensions !== void 0 && warning.dimensions.length > 0 ? ` on the same failed dimension${warning.dimensions.length === 1 ? "" : "s"}: ${warning.dimensions.join(", ")}` : "";
|
|
11817
|
+
return `The evaluator plateaued \u2014 two consecutive evaluations flagged the identical failure${dims}.`;
|
|
11691
11818
|
}
|
|
11819
|
+
case "malformed": {
|
|
11820
|
+
const detail = warning.detail !== void 0 && warning.detail.trim().length > 0 ? ` (${warning.detail.trim()})` : "";
|
|
11821
|
+
return `The evaluator output could not be parsed${detail}.`;
|
|
11822
|
+
}
|
|
11823
|
+
case "verify-failed": {
|
|
11824
|
+
const detail = warning.detail !== void 0 && warning.detail.trim().length > 0 ? `: ${warning.detail.trim()}` : "";
|
|
11825
|
+
return `The post-task verify script ran red after the commit${detail}.`;
|
|
11826
|
+
}
|
|
11827
|
+
}
|
|
11828
|
+
};
|
|
11829
|
+
var remedySentence = (verdict, escalation, warning) => {
|
|
11830
|
+
if (escalation !== void 0) {
|
|
11831
|
+
return escalation.from === escalation.to ? `Remedy: retried the same model (${escalation.to}) \u2014 already at the top of the escalation ladder.` : `Remedy: escalated the generator model from ${escalation.from} to ${escalation.to}.`;
|
|
11832
|
+
}
|
|
11833
|
+
if (verdict === "escalated" && warning?.kind === "malformed") {
|
|
11834
|
+
return "Remedy: retried on the same model \u2014 the evaluator failed to produce a verdict, so no escalation rung was spent.";
|
|
11692
11835
|
}
|
|
11836
|
+
if (verdict === "pass-with-warning") {
|
|
11837
|
+
return "Remedy: kept the attempt with the warning attached for operator review.";
|
|
11838
|
+
}
|
|
11839
|
+
return void 0;
|
|
11840
|
+
};
|
|
11841
|
+
var appendOutcomeDetail = (lines, input) => {
|
|
11842
|
+
const bullets = [];
|
|
11843
|
+
if (input.warning !== void 0) bullets.push(warningSentence(input.warning));
|
|
11844
|
+
const remedy = remedySentence(input.verdict, input.escalation, input.warning);
|
|
11845
|
+
if (remedy !== void 0) bullets.push(remedy);
|
|
11846
|
+
if (bullets.length === 0) return;
|
|
11847
|
+
lines.push("### Outcome detail");
|
|
11848
|
+
for (const b of bullets) lines.push(`- ${b}`);
|
|
11693
11849
|
lines.push("");
|
|
11694
11850
|
};
|
|
11695
11851
|
var renderJournalEntry = (input) => {
|
|
11696
11852
|
const sha = input.commitSha !== void 0 ? input.commitSha.slice(0, SHA_DISPLAY_LENGTH) : EM_DASH;
|
|
11697
|
-
const outcome = input.outcome.trim().length > 0 ? input.outcome.trim() : "_(no outcome paragraph)_";
|
|
11853
|
+
const outcome = input.outcome.trim().length > 0 ? neutralizeProseHeadings(input.outcome.trim()) : "_(no outcome paragraph)_";
|
|
11698
11854
|
const lines = [];
|
|
11699
11855
|
lines.push("");
|
|
11700
|
-
lines.push(`## Task: ${input.taskName} \u2014 Attempt ${String(input.attemptN)}`);
|
|
11856
|
+
lines.push(`## Task: ${sanitizeInline(input.taskName)} \u2014 Attempt ${String(input.attemptN)}`);
|
|
11701
11857
|
lines.push("");
|
|
11702
11858
|
lines.push(`_${String(input.timestamp)}_`);
|
|
11703
11859
|
lines.push("");
|
|
@@ -11708,6 +11864,7 @@ var renderJournalEntry = (input) => {
|
|
|
11708
11864
|
lines.push(`- Duration: ${formatDuration(input.durationMs)}`);
|
|
11709
11865
|
lines.push(`- Commit: ${sha}`);
|
|
11710
11866
|
lines.push("");
|
|
11867
|
+
appendOutcomeDetail(lines, input);
|
|
11711
11868
|
appendSubsection(lines, "Changes", input.changes);
|
|
11712
11869
|
appendSubsection(lines, "Decisions", input.decisions);
|
|
11713
11870
|
appendLearningsSubsection(lines, input.learnings);
|
|
@@ -12756,6 +12913,22 @@ var refineTicketInteractiveLeaf = (deps, ticket) => leaf(`refine-ticket-${String
|
|
|
12756
12913
|
}
|
|
12757
12914
|
});
|
|
12758
12915
|
|
|
12916
|
+
// src/integration/ai/prompts/_engine/extract-placeholders.ts
|
|
12917
|
+
var extractPlaceholders = (template) => {
|
|
12918
|
+
const PATTERN = /\{\{([A-Z][A-Z0-9_]*)\}\}/g;
|
|
12919
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12920
|
+
const ordered = [];
|
|
12921
|
+
let m;
|
|
12922
|
+
while ((m = PATTERN.exec(template)) !== null) {
|
|
12923
|
+
const name = m[1];
|
|
12924
|
+
if (name !== void 0 && !seen.has(name)) {
|
|
12925
|
+
seen.add(name);
|
|
12926
|
+
ordered.push(name);
|
|
12927
|
+
}
|
|
12928
|
+
}
|
|
12929
|
+
return ordered;
|
|
12930
|
+
};
|
|
12931
|
+
|
|
12759
12932
|
// src/integration/ai/prompts/_engine/substitute.ts
|
|
12760
12933
|
var PLACEHOLDER_PATTERN = /\{\{([A-Z][A-Z0-9_]*)\}\}/g;
|
|
12761
12934
|
var substitute = (template, values) => template.replace(PLACEHOLDER_PATTERN, (match, key) => {
|
|
@@ -12763,14 +12936,17 @@ var substitute = (template, values) => template.replace(PLACEHOLDER_PATTERN, (ma
|
|
|
12763
12936
|
const value = values[key];
|
|
12764
12937
|
return value !== void 0 ? value : match;
|
|
12765
12938
|
});
|
|
12766
|
-
var
|
|
12767
|
-
const
|
|
12768
|
-
|
|
12769
|
-
|
|
12939
|
+
var assertTemplateKeysFilled = (rendered, template, partialBodies, values, where) => {
|
|
12940
|
+
const required = new Set(extractPlaceholders(template));
|
|
12941
|
+
for (const body of partialBodies) {
|
|
12942
|
+
for (const key of extractPlaceholders(body)) required.add(key);
|
|
12943
|
+
}
|
|
12944
|
+
const missing = Array.from(required).filter((key) => values[key] === void 0);
|
|
12945
|
+
if (missing.length === 0) return Result.ok(rendered);
|
|
12770
12946
|
return Result.error(
|
|
12771
12947
|
new ParseError({
|
|
12772
12948
|
subCode: "schema-mismatch",
|
|
12773
|
-
message: `${where}:
|
|
12949
|
+
message: `${where}: template declares ${String(missing.length)} unfilled placeholder(s): ${missing.map((k) => `{{${k}}}`).join(", ")}. Either fill the slot in the builder's substitution map or remove the placeholder from the template.`
|
|
12774
12950
|
})
|
|
12775
12951
|
);
|
|
12776
12952
|
};
|
|
@@ -12780,11 +12956,13 @@ var buildPrompt = async (loader, def, input) => {
|
|
|
12780
12956
|
const template = await loader.load(def.templateName);
|
|
12781
12957
|
if (!template.ok) return Result.error(template.error);
|
|
12782
12958
|
const values = {};
|
|
12959
|
+
const partialBodies = [];
|
|
12783
12960
|
if (def.partials !== void 0) {
|
|
12784
12961
|
for (const [placeholder, name] of Object.entries(def.partials)) {
|
|
12785
12962
|
const partial = await loader.load(name);
|
|
12786
12963
|
if (!partial.ok) return Result.error(partial.error);
|
|
12787
12964
|
values[placeholder] = partial.value.trim();
|
|
12965
|
+
partialBodies.push(partial.value);
|
|
12788
12966
|
}
|
|
12789
12967
|
}
|
|
12790
12968
|
for (const [field, rawSpec] of Object.entries(def.parameters)) {
|
|
@@ -12808,7 +12986,7 @@ var buildPrompt = async (loader, def, input) => {
|
|
|
12808
12986
|
values[spec.placeholder] = String(validated.value);
|
|
12809
12987
|
}
|
|
12810
12988
|
const rendered = substitute(template.value, values);
|
|
12811
|
-
return
|
|
12989
|
+
return assertTemplateKeysFilled(rendered, template.value, partialBodies, values, `buildPrompt(${def.templateName})`);
|
|
12812
12990
|
};
|
|
12813
12991
|
|
|
12814
12992
|
// src/integration/ai/prompts/refine/definition.ts
|
|
@@ -13883,7 +14061,10 @@ var parseTaskList = (rawTasks, input) => {
|
|
|
13883
14061
|
const normalisedExtras = t.extraDimensions !== void 0 ? t.extraDimensions.map((d) => d.trim().toLowerCase()).filter((d) => d.length > 0) : void 0;
|
|
13884
14062
|
const created = createTask({
|
|
13885
14063
|
...mappedId !== void 0 ? { id: mappedId } : {},
|
|
13886
|
-
|
|
14064
|
+
// Single-line names everywhere: the name is interpolated into the journal's structural
|
|
14065
|
+
// `## Task: <name> — Attempt <N>` header line that cap-progress splits and attributes on —
|
|
14066
|
+
// a planner-emitted newline would break the task's own section boundary.
|
|
14067
|
+
name: t.name.replace(/[\r\n\t\v\f]+/g, " ").trim(),
|
|
13887
14068
|
...t.description !== void 0 && t.description.trim().length > 0 ? { description: t.description } : {},
|
|
13888
14069
|
steps: t.steps,
|
|
13889
14070
|
verificationCriteria: t.verificationCriteria.map((c) => ({
|
|
@@ -13897,7 +14078,8 @@ var parseTaskList = (rawTasks, input) => {
|
|
|
13897
14078
|
repositoryId: repoId,
|
|
13898
14079
|
...dependsOn.length > 0 ? { dependsOn } : {},
|
|
13899
14080
|
...normalisedExtras !== void 0 && normalisedExtras.length > 0 ? { extraDimensions: normalisedExtras } : {},
|
|
13900
|
-
...externalRefs !== void 0 ? { externalRefs } : {}
|
|
14081
|
+
...externalRefs !== void 0 ? { externalRefs } : {},
|
|
14082
|
+
...input.defaultMaxAttempts !== void 0 ? { maxAttempts: input.defaultMaxAttempts } : {}
|
|
13901
14083
|
});
|
|
13902
14084
|
if (!created.ok) {
|
|
13903
14085
|
return Result.error(
|
|
@@ -14022,7 +14204,8 @@ var parsePlanOutput = (raw, ctx) => {
|
|
|
14022
14204
|
return parseTaskList(json, {
|
|
14023
14205
|
project: ctx.project,
|
|
14024
14206
|
mode: { kind: "lookup", tickets: approvedTickets },
|
|
14025
|
-
...ctx.logger !== void 0 ? { logger: ctx.logger } : {}
|
|
14207
|
+
...ctx.logger !== void 0 ? { logger: ctx.logger } : {},
|
|
14208
|
+
...ctx.defaultMaxAttempts !== void 0 ? { defaultMaxAttempts: ctx.defaultMaxAttempts } : {}
|
|
14026
14209
|
});
|
|
14027
14210
|
};
|
|
14028
14211
|
|
|
@@ -14068,7 +14251,8 @@ var callPlannerInteractiveLeaf = (deps) => leaf("call-planner-interactive", {
|
|
|
14068
14251
|
const parsed = parsePlanOutput(planSignal.tasksJson, {
|
|
14069
14252
|
project: input.project,
|
|
14070
14253
|
sprint: input.sprint,
|
|
14071
|
-
logger: deps.logger
|
|
14254
|
+
logger: deps.logger,
|
|
14255
|
+
defaultMaxAttempts: deps.maxAttempts
|
|
14072
14256
|
});
|
|
14073
14257
|
if (!parsed.ok) return Result.error(parsed.error);
|
|
14074
14258
|
return planSprintUseCase({
|
|
@@ -14246,6 +14430,7 @@ var createPlanFlow = (deps, opts) => {
|
|
|
14246
14430
|
eventBus: deps.eventBus,
|
|
14247
14431
|
clock: deps.clock,
|
|
14248
14432
|
model: opts.model,
|
|
14433
|
+
maxAttempts: opts.maxAttempts,
|
|
14249
14434
|
...opts.effort !== void 0 ? { effort: opts.effort } : {},
|
|
14250
14435
|
...opts.additionalRoots !== void 0 && opts.additionalRoots.length > 0 ? { additionalRoots: opts.additionalRoots } : {},
|
|
14251
14436
|
...deps.reviewBeforeApprove !== void 0 ? { reviewBeforeApprove: deps.reviewBeforeApprove } : {}
|
|
@@ -14327,6 +14512,7 @@ var launchPlan = async (ctx) => {
|
|
|
14327
14512
|
additionalRoots: snapshot.project.repositories.map((r) => r.path),
|
|
14328
14513
|
providerId: settings.ai.plan.provider,
|
|
14329
14514
|
model: settings.ai.plan.model,
|
|
14515
|
+
maxAttempts: settings.harness.maxAttempts,
|
|
14330
14516
|
...effort !== void 0 ? { effort } : {},
|
|
14331
14517
|
planRoot: planRoot.value
|
|
14332
14518
|
}
|
|
@@ -15495,6 +15681,15 @@ var recordRunningAttemptWarning = (task, warning) => {
|
|
|
15495
15681
|
if (!guard2.ok) return Result.error(guard2.error);
|
|
15496
15682
|
return Result.ok(replaceLastAttempt(task, recordAttemptWarning(guard2.value.running, warning)));
|
|
15497
15683
|
};
|
|
15684
|
+
var clearRunningAttemptPlateauWarning = (task) => {
|
|
15685
|
+
const guard2 = requireRunningAttempt(task);
|
|
15686
|
+
if (!guard2.ok) return task;
|
|
15687
|
+
const running = guard2.value.running;
|
|
15688
|
+
if (running.warning?.kind !== "plateau") return task;
|
|
15689
|
+
const { warning: _cleared, ...rest } = running;
|
|
15690
|
+
void _cleared;
|
|
15691
|
+
return replaceLastAttempt(task, rest);
|
|
15692
|
+
};
|
|
15498
15693
|
var appendAttemptVerifyRun = (task, run) => {
|
|
15499
15694
|
const guard2 = requireRunningAttempt(task);
|
|
15500
15695
|
if (!guard2.ok) return Result.error(guard2.error);
|
|
@@ -15743,17 +15938,6 @@ var failCurrentAttempt = (task, now, reason, abortMeta) => {
|
|
|
15743
15938
|
return Result.ok(inProgressNext);
|
|
15744
15939
|
};
|
|
15745
15940
|
var recordTaskEscalation = (task, fromModel, toModel) => {
|
|
15746
|
-
if (task.escalatedFromModel !== void 0 || task.escalatedToModel !== void 0) {
|
|
15747
|
-
return Result.error(
|
|
15748
|
-
new InvalidStateError({
|
|
15749
|
-
entity: "task",
|
|
15750
|
-
currentState: "in_progress",
|
|
15751
|
-
attemptedAction: "record-escalation",
|
|
15752
|
-
message: `task '${task.id}' already escalated (${String(task.escalatedFromModel)} \u2192 ${String(task.escalatedToModel)})`,
|
|
15753
|
-
hint: "The once-per-task cap blocks a second escalation; transition to blocked instead."
|
|
15754
|
-
})
|
|
15755
|
-
);
|
|
15756
|
-
}
|
|
15757
15941
|
const from = parseRequiredString("task.escalatedFromModel", fromModel);
|
|
15758
15942
|
if (!from.ok) return Result.error(from.error);
|
|
15759
15943
|
const to = parseRequiredString("task.escalatedToModel", toModel);
|
|
@@ -15762,35 +15946,33 @@ var recordTaskEscalation = (task, fromModel, toModel) => {
|
|
|
15762
15946
|
};
|
|
15763
15947
|
|
|
15764
15948
|
// src/business/task/escalation-policy.ts
|
|
15949
|
+
var triggerLabel = (trigger) => trigger === "plateau" ? "plateau" : "turn budget exhausted";
|
|
15765
15950
|
var decideEscalation = (props) => {
|
|
15766
15951
|
if (!props.flagOn) return { kind: "flag-off" };
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
kind: "already-escalated",
|
|
15770
|
-
from: props.task.escalatedFromModel,
|
|
15771
|
-
to: props.task.escalatedToModel
|
|
15772
|
-
};
|
|
15773
|
-
}
|
|
15774
|
-
if (props.task.maxAttempts !== void 0 && props.task.attempts.length >= props.task.maxAttempts) {
|
|
15952
|
+
const effectiveMaxAttempts = props.task.maxAttempts ?? props.fallbackMaxAttempts;
|
|
15953
|
+
if (props.task.attempts.length >= effectiveMaxAttempts) {
|
|
15775
15954
|
return {
|
|
15776
15955
|
kind: "budget-exhausted",
|
|
15777
15956
|
attemptsUsed: props.task.attempts.length,
|
|
15778
|
-
maxAttempts:
|
|
15957
|
+
maxAttempts: effectiveMaxAttempts
|
|
15779
15958
|
};
|
|
15780
15959
|
}
|
|
15781
|
-
const
|
|
15782
|
-
const
|
|
15783
|
-
if (
|
|
15784
|
-
return { kind: "
|
|
15960
|
+
const merged = mergeEscalationMap(props.userMap);
|
|
15961
|
+
const next = merged[props.generatorModel];
|
|
15962
|
+
if (next !== void 0 && next !== props.generatorModel && !escalationLadderCyclicFrom(merged, props.generatorModel)) {
|
|
15963
|
+
return { kind: "escalate", from: props.generatorModel, to: next };
|
|
15785
15964
|
}
|
|
15786
|
-
|
|
15965
|
+
const nudgedAtTop = props.task.escalatedFromModel !== void 0 && props.task.escalatedFromModel === props.task.escalatedToModel && props.task.escalatedToModel === props.generatorModel;
|
|
15966
|
+
if (nudgedAtTop) return { kind: "topped-out", model: props.generatorModel };
|
|
15967
|
+
return { kind: "nudge", currentModel: props.generatorModel };
|
|
15787
15968
|
};
|
|
15788
15969
|
var escalationBannerId = (taskId) => `model-escalation-${taskId}`;
|
|
15789
15970
|
var applyEscalation = (props) => {
|
|
15790
|
-
const { task, decision, eventBus, clock } = props;
|
|
15971
|
+
const { task, decision, trigger, eventBus, clock } = props;
|
|
15791
15972
|
const log = props.logger.named("task.escalation-policy");
|
|
15792
15973
|
const bannerId = escalationBannerId(String(task.id));
|
|
15793
15974
|
const now = clock();
|
|
15975
|
+
const cause = triggerLabel(trigger);
|
|
15794
15976
|
switch (decision.kind) {
|
|
15795
15977
|
case "flag-off":
|
|
15796
15978
|
return Result.ok({ task });
|
|
@@ -15803,7 +15985,7 @@ var applyEscalation = (props) => {
|
|
|
15803
15985
|
attemptN: task.attempts.length,
|
|
15804
15986
|
from: decision.from,
|
|
15805
15987
|
to: decision.to,
|
|
15806
|
-
reason:
|
|
15988
|
+
reason: trigger,
|
|
15807
15989
|
at: now
|
|
15808
15990
|
});
|
|
15809
15991
|
eventBus.publish({
|
|
@@ -15811,7 +15993,7 @@ var applyEscalation = (props) => {
|
|
|
15811
15993
|
id: bannerId,
|
|
15812
15994
|
tier: "info",
|
|
15813
15995
|
message: `escalated generator model: ${decision.from} \u2192 ${decision.to}`,
|
|
15814
|
-
cause
|
|
15996
|
+
cause,
|
|
15815
15997
|
at: now
|
|
15816
15998
|
});
|
|
15817
15999
|
log.info(`escalating generator model: ${decision.from} \u2192 ${decision.to}`, {
|
|
@@ -15819,7 +16001,7 @@ var applyEscalation = (props) => {
|
|
|
15819
16001
|
attemptN: task.attempts.length,
|
|
15820
16002
|
from: decision.from,
|
|
15821
16003
|
to: decision.to,
|
|
15822
|
-
reason:
|
|
16004
|
+
reason: trigger
|
|
15823
16005
|
});
|
|
15824
16006
|
return Result.ok({ task: stamped.value });
|
|
15825
16007
|
}
|
|
@@ -15830,46 +16012,49 @@ var applyEscalation = (props) => {
|
|
|
15830
16012
|
type: "banner-show",
|
|
15831
16013
|
id: bannerId,
|
|
15832
16014
|
tier: "info",
|
|
15833
|
-
message:
|
|
15834
|
-
cause
|
|
16015
|
+
message: `${cause} on '${decision.currentModel}' (top of ladder) \u2014 retrying with a change-of-approach directive`,
|
|
16016
|
+
cause,
|
|
15835
16017
|
at: now
|
|
15836
16018
|
});
|
|
15837
|
-
log.info("
|
|
16019
|
+
log.info("top-of-ladder nudge: retrying on the same model with a change-of-approach directive", {
|
|
15838
16020
|
taskId: String(task.id),
|
|
15839
|
-
currentModel: decision.currentModel
|
|
16021
|
+
currentModel: decision.currentModel,
|
|
16022
|
+
reason: trigger
|
|
15840
16023
|
});
|
|
15841
16024
|
return Result.ok({ task: stamped.value });
|
|
15842
16025
|
}
|
|
15843
|
-
case "
|
|
15844
|
-
const message = `
|
|
16026
|
+
case "topped-out": {
|
|
16027
|
+
const message = `ladder exhausted on '${decision.model}' (${cause}); keeping the work`;
|
|
15845
16028
|
eventBus.publish({
|
|
15846
16029
|
type: "banner-show",
|
|
15847
16030
|
id: bannerId,
|
|
15848
16031
|
tier: "warn",
|
|
15849
|
-
message: "
|
|
16032
|
+
message: "ladder exhausted \u2014 keeping the work",
|
|
16033
|
+
cause,
|
|
15850
16034
|
at: now
|
|
15851
16035
|
});
|
|
15852
16036
|
log.warn(message, {
|
|
15853
16037
|
taskId: String(task.id),
|
|
15854
|
-
|
|
15855
|
-
|
|
16038
|
+
model: decision.model,
|
|
16039
|
+
reason: trigger
|
|
15856
16040
|
});
|
|
15857
16041
|
return Result.ok({ task });
|
|
15858
16042
|
}
|
|
15859
16043
|
case "budget-exhausted": {
|
|
15860
|
-
const message =
|
|
16044
|
+
const message = `${cause} with attempt budget exhausted (attempts=${String(decision.attemptsUsed)}, maxAttempts=${String(decision.maxAttempts)}); keeping the work`;
|
|
15861
16045
|
eventBus.publish({
|
|
15862
16046
|
type: "banner-show",
|
|
15863
16047
|
id: bannerId,
|
|
15864
16048
|
tier: "warn",
|
|
15865
|
-
message:
|
|
16049
|
+
message: `${cause}, attempt budget exhausted \u2014 keeping the work`,
|
|
15866
16050
|
cause: `attempts=${String(decision.attemptsUsed)}/${String(decision.maxAttempts)}`,
|
|
15867
16051
|
at: now
|
|
15868
16052
|
});
|
|
15869
16053
|
log.warn(message, {
|
|
15870
16054
|
taskId: String(task.id),
|
|
15871
16055
|
attemptsUsed: decision.attemptsUsed,
|
|
15872
|
-
maxAttempts: decision.maxAttempts
|
|
16056
|
+
maxAttempts: decision.maxAttempts,
|
|
16057
|
+
reason: trigger
|
|
15873
16058
|
});
|
|
15874
16059
|
return Result.ok({ task });
|
|
15875
16060
|
}
|
|
@@ -15886,7 +16071,7 @@ var mapExit = (exit) => {
|
|
|
15886
16071
|
case "malformed":
|
|
15887
16072
|
return { verdict: "malformed", warning: { kind: "malformed", detail: exit.detail } };
|
|
15888
16073
|
case "plateau":
|
|
15889
|
-
return { verdict: "failed"
|
|
16074
|
+
return { verdict: "failed" };
|
|
15890
16075
|
case "budget-exhausted":
|
|
15891
16076
|
return {
|
|
15892
16077
|
verdict: "failed",
|
|
@@ -15894,6 +16079,47 @@ var mapExit = (exit) => {
|
|
|
15894
16079
|
};
|
|
15895
16080
|
}
|
|
15896
16081
|
};
|
|
16082
|
+
var isEscalatableExit = (exit) => exit.kind === "plateau" || exit.kind === "budget-exhausted";
|
|
16083
|
+
var resolveEscalatableRemedy = (exit, props, cfg) => {
|
|
16084
|
+
const trigger = exit.kind;
|
|
16085
|
+
const decision = decideEscalation({
|
|
16086
|
+
task: props.task,
|
|
16087
|
+
generatorModel: props.generatorModel,
|
|
16088
|
+
flagOn: cfg.escalateOnPlateau,
|
|
16089
|
+
userMap: cfg.escalationMap,
|
|
16090
|
+
fallbackMaxAttempts: cfg.maxAttempts
|
|
16091
|
+
});
|
|
16092
|
+
const applied = applyEscalation({
|
|
16093
|
+
task: props.task,
|
|
16094
|
+
decision,
|
|
16095
|
+
trigger,
|
|
16096
|
+
eventBus: props.eventBus,
|
|
16097
|
+
logger: props.logger,
|
|
16098
|
+
clock: props.clock
|
|
16099
|
+
});
|
|
16100
|
+
if (!applied.ok) return Result.error(applied.error);
|
|
16101
|
+
const shouldFailAttempt = decision.kind === "escalate" || decision.kind === "nudge";
|
|
16102
|
+
return Result.ok({
|
|
16103
|
+
task: applied.value.task,
|
|
16104
|
+
...applied.value.blockedReason !== void 0 ? { blockedReason: applied.value.blockedReason } : {},
|
|
16105
|
+
shouldFailAttempt
|
|
16106
|
+
});
|
|
16107
|
+
};
|
|
16108
|
+
var resolveMalformedRemedy = (props, cfg, log) => {
|
|
16109
|
+
const effectiveMaxAttempts = props.task.maxAttempts ?? cfg.maxAttempts;
|
|
16110
|
+
const budgetRemains = props.task.attempts.length < effectiveMaxAttempts;
|
|
16111
|
+
const shouldFailAttempt = cfg.escalateOnPlateau && budgetRemains;
|
|
16112
|
+
log.info(
|
|
16113
|
+
shouldFailAttempt ? "malformed exit: retrying on the same model (no escalation)" : "malformed exit: keeping the work (done-with-warning)",
|
|
16114
|
+
{
|
|
16115
|
+
taskId: props.task.id,
|
|
16116
|
+
attemptsUsed: props.task.attempts.length,
|
|
16117
|
+
maxAttempts: effectiveMaxAttempts,
|
|
16118
|
+
flagOn: cfg.escalateOnPlateau
|
|
16119
|
+
}
|
|
16120
|
+
);
|
|
16121
|
+
return { task: props.task, shouldFailAttempt };
|
|
16122
|
+
};
|
|
15897
16123
|
var finalizeGenEvalUseCase = async (props) => {
|
|
15898
16124
|
const log = props.logger.named("task.finalize-gen-eval");
|
|
15899
16125
|
const cfg = await props.readConfig();
|
|
@@ -15905,28 +16131,21 @@ var finalizeGenEvalUseCase = async (props) => {
|
|
|
15905
16131
|
}
|
|
15906
16132
|
log.debug(`finalizing gen-eval (${exit.kind})`, { taskId: props.task.id, exitKind: exit.kind });
|
|
15907
16133
|
const mapped = mapExit(exit);
|
|
15908
|
-
let
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
15923
|
-
clock: props.clock
|
|
15924
|
-
});
|
|
15925
|
-
if (!applied.ok) return Result.error(applied.error);
|
|
15926
|
-
taskForPersist = applied.value.task;
|
|
15927
|
-
if (applied.value.blockedReason !== void 0) blockedReason = applied.value.blockedReason;
|
|
15928
|
-
if (decision.kind === "escalate" || decision.kind === "nudge") shouldFailAttempt = true;
|
|
15929
|
-
}
|
|
16134
|
+
let remedy = {
|
|
16135
|
+
task: exit.kind === "passed" ? clearRunningAttemptPlateauWarning(props.task) : props.task,
|
|
16136
|
+
...mapped.blockedReason !== void 0 ? { blockedReason: mapped.blockedReason } : {},
|
|
16137
|
+
shouldFailAttempt: false
|
|
16138
|
+
};
|
|
16139
|
+
if (isEscalatableExit(exit)) {
|
|
16140
|
+
const resolved = resolveEscalatableRemedy(exit, props, cfg);
|
|
16141
|
+
if (!resolved.ok) return Result.error(resolved.error);
|
|
16142
|
+
remedy = resolved.value;
|
|
16143
|
+
} else if (exit.kind === "malformed") {
|
|
16144
|
+
remedy = resolveMalformedRemedy(props, cfg, log);
|
|
16145
|
+
}
|
|
16146
|
+
const taskForPersist = remedy.task;
|
|
16147
|
+
const blockedReason = remedy.blockedReason ?? mapped.blockedReason;
|
|
16148
|
+
const shouldFailAttempt = remedy.shouldFailAttempt;
|
|
15930
16149
|
const persisted = await props.taskRepo.update(props.sprintId, taskForPersist);
|
|
15931
16150
|
if (!persisted.ok) {
|
|
15932
16151
|
log.error("persist failed", { taskId: taskForPersist.id, error: persisted.error.message });
|
|
@@ -16020,14 +16239,6 @@ var failedDimensions = (signal) => {
|
|
|
16020
16239
|
}
|
|
16021
16240
|
return names;
|
|
16022
16241
|
};
|
|
16023
|
-
var setsEqual = (a, b) => {
|
|
16024
|
-
if (a.size === 0 || b.size === 0) return false;
|
|
16025
|
-
if (a.size !== b.size) return false;
|
|
16026
|
-
for (const name of a) {
|
|
16027
|
-
if (!b.has(name)) return false;
|
|
16028
|
-
}
|
|
16029
|
-
return true;
|
|
16030
|
-
};
|
|
16031
16242
|
var trigrams = (text) => {
|
|
16032
16243
|
const normalised = text.trim().toLowerCase().replace(/\s+/g, " ");
|
|
16033
16244
|
if (normalised.length < 3) return /* @__PURE__ */ new Set([normalised]);
|
|
@@ -16050,27 +16261,71 @@ var trigramJaccard = (a, b) => {
|
|
|
16050
16261
|
return intersection / union;
|
|
16051
16262
|
};
|
|
16052
16263
|
var CRITIQUE_SHIFT_SIMILARITY = 0.5;
|
|
16264
|
+
var WARNING_SOFTEN_CAP = 2;
|
|
16265
|
+
var countNeverDecreased = (turns) => {
|
|
16266
|
+
let prev = -1;
|
|
16267
|
+
for (const t of turns) {
|
|
16268
|
+
const n = failedDimensions(t.evaluation).size;
|
|
16269
|
+
if (prev !== -1 && n < prev) return false;
|
|
16270
|
+
prev = n;
|
|
16271
|
+
}
|
|
16272
|
+
return true;
|
|
16273
|
+
};
|
|
16274
|
+
var trailingWarningStreak = (priorTurns) => {
|
|
16275
|
+
let streak = 0;
|
|
16276
|
+
for (let i = priorTurns.length - 1; i >= 0; i--) {
|
|
16277
|
+
if (priorTurns[i]?.verdict === "warning") streak += 1;
|
|
16278
|
+
else break;
|
|
16279
|
+
}
|
|
16280
|
+
return streak;
|
|
16281
|
+
};
|
|
16282
|
+
var critiqueShiftedFromAll = (window, current) => {
|
|
16283
|
+
const currentCritique = current.critique;
|
|
16284
|
+
if (currentCritique === void 0 || currentCritique.trim().length === 0) return false;
|
|
16285
|
+
let maxSimilarity = 0;
|
|
16286
|
+
let comparedAny = false;
|
|
16287
|
+
for (const prior of window) {
|
|
16288
|
+
const priorCritique = prior.critique;
|
|
16289
|
+
if (priorCritique === void 0 || priorCritique.trim().length === 0) continue;
|
|
16290
|
+
comparedAny = true;
|
|
16291
|
+
const sim = trigramJaccard(priorCritique, currentCritique);
|
|
16292
|
+
if (sim > maxSimilarity) maxSimilarity = sim;
|
|
16293
|
+
}
|
|
16294
|
+
return comparedAny && maxSimilarity < CRITIQUE_SHIFT_SIMILARITY;
|
|
16295
|
+
};
|
|
16296
|
+
var hasHash = (r) => r.changedFilesHash !== void 0 && r.changedFilesHash.length > 0;
|
|
16297
|
+
var workProductChanged = (window, current) => {
|
|
16298
|
+
const anyPriorHash = window.some(hasHash);
|
|
16299
|
+
if (hasHash(current)) {
|
|
16300
|
+
let comparedAny = false;
|
|
16301
|
+
for (const prior of window) {
|
|
16302
|
+
if (!hasHash(prior)) continue;
|
|
16303
|
+
comparedAny = true;
|
|
16304
|
+
if (prior.changedFilesHash === current.changedFilesHash) return false;
|
|
16305
|
+
}
|
|
16306
|
+
return comparedAny;
|
|
16307
|
+
}
|
|
16308
|
+
if (anyPriorHash) {
|
|
16309
|
+
return false;
|
|
16310
|
+
}
|
|
16311
|
+
const lastPrior = window[window.length - 1];
|
|
16312
|
+
const priorSubject = lastPrior?.commitSubject;
|
|
16313
|
+
const currentSubject = current.commitSubject;
|
|
16314
|
+
return priorSubject !== void 0 && priorSubject.trim().length > 0 && currentSubject !== void 0 && currentSubject.trim().length > 0 && priorSubject.trim() !== currentSubject.trim();
|
|
16315
|
+
};
|
|
16053
16316
|
var computePlateauVerdict = (priorTurns, current, options) => {
|
|
16054
16317
|
const threshold = Math.max(2, Math.min(5, Math.trunc(options.threshold)));
|
|
16055
16318
|
if (priorTurns.length < threshold - 1) return { kind: "none" };
|
|
16056
16319
|
const window = priorTurns.slice(-(threshold - 1));
|
|
16057
16320
|
const currentFailed = failedDimensions(current.evaluation);
|
|
16058
16321
|
if (currentFailed.size === 0) return { kind: "none" };
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
const lastPrior = window[window.length - 1];
|
|
16063
|
-
if (lastPrior === void 0) return { kind: "none" };
|
|
16064
|
-
const priorCritique = lastPrior.critique;
|
|
16065
|
-
const currentCritique = current.critique;
|
|
16066
|
-
if (priorCritique !== void 0 && priorCritique.trim().length > 0 && currentCritique !== void 0 && currentCritique.trim().length > 0 && trigramJaccard(priorCritique, currentCritique) < CRITIQUE_SHIFT_SIMILARITY) {
|
|
16322
|
+
if (!countNeverDecreased([...window, current])) return { kind: "none" };
|
|
16323
|
+
const dimensions = [...currentFailed];
|
|
16324
|
+
if (critiqueShiftedFromAll(window, current)) {
|
|
16067
16325
|
return { kind: "progress", reason: "critique-shifted" };
|
|
16068
16326
|
}
|
|
16069
|
-
|
|
16070
|
-
|
|
16071
|
-
const currentSubject = current.commitSubject;
|
|
16072
|
-
if (priorSubject !== void 0 && priorSubject.trim().length > 0 && currentSubject !== void 0 && currentSubject.trim().length > 0 && priorSubject.trim() !== currentSubject.trim()) {
|
|
16073
|
-
return { kind: "warning", dimensions, reason: "commit-progress" };
|
|
16327
|
+
if (workProductChanged(window, current) && trailingWarningStreak(priorTurns) < WARNING_SOFTEN_CAP) {
|
|
16328
|
+
return { kind: "warning", dimensions, reason: "work-product-changed" };
|
|
16074
16329
|
}
|
|
16075
16330
|
return { kind: "plateau", dimensions };
|
|
16076
16331
|
};
|
|
@@ -16080,6 +16335,12 @@ var isRecoverableTurnError = (err) => err.code !== ErrorCode.Aborted && err.code
|
|
|
16080
16335
|
|
|
16081
16336
|
// src/business/task/run-evaluator-turn.ts
|
|
16082
16337
|
var findEvaluation = (signals) => signals.find((s) => s.type === "evaluation");
|
|
16338
|
+
var resolveCritique = (evaluation) => {
|
|
16339
|
+
const explicit = evaluation.critique;
|
|
16340
|
+
if (explicit !== void 0 && explicit.trim().length > 0) return explicit;
|
|
16341
|
+
const synthesized = evaluation.dimensions.filter((d) => !d.passed && d.finding.trim().length > 0).map((d) => `[${d.dimension}] ${d.finding.trim()}`).join("\n");
|
|
16342
|
+
return synthesized.length > 0 ? synthesized : void 0;
|
|
16343
|
+
};
|
|
16083
16344
|
var runEvaluatorTurnUseCase = async (props) => {
|
|
16084
16345
|
const log = props.logger.named("task.evaluator-turn");
|
|
16085
16346
|
log.debug("running evaluator turn", { taskId: props.task.id });
|
|
@@ -16130,17 +16391,19 @@ var runEvaluatorTurnUseCase = async (props) => {
|
|
|
16130
16391
|
exit: { kind: "malformed", detail: "evaluator emitted dimension scores but no terminal verdict" }
|
|
16131
16392
|
});
|
|
16132
16393
|
}
|
|
16133
|
-
const critique = evaluation
|
|
16134
|
-
const
|
|
16394
|
+
const critique = resolveCritique(evaluation);
|
|
16395
|
+
const baseRecord = {
|
|
16135
16396
|
evaluation,
|
|
16136
16397
|
...critique !== void 0 && critique.trim().length > 0 ? { critique } : {},
|
|
16137
|
-
...props.currentCommitSubject !== void 0 && props.currentCommitSubject.trim().length > 0 ? { commitSubject: props.currentCommitSubject } : {}
|
|
16398
|
+
...props.currentCommitSubject !== void 0 && props.currentCommitSubject.trim().length > 0 ? { commitSubject: props.currentCommitSubject } : {},
|
|
16399
|
+
...props.changedFilesHash !== void 0 && props.changedFilesHash.length > 0 ? { changedFilesHash: props.changedFilesHash } : {}
|
|
16138
16400
|
};
|
|
16139
|
-
const verdict = computePlateauVerdict(props.priorTurns ?? [],
|
|
16401
|
+
const verdict = computePlateauVerdict(props.priorTurns ?? [], baseRecord, {
|
|
16140
16402
|
threshold: props.plateauThreshold
|
|
16141
16403
|
});
|
|
16404
|
+
const currentRecord = { ...baseRecord, verdict: verdict.kind };
|
|
16142
16405
|
if (verdict.kind === "plateau") {
|
|
16143
|
-
log.warn("evaluator plateaued
|
|
16406
|
+
log.warn("evaluator plateaued \u2014 no net progress across the window", {
|
|
16144
16407
|
taskId: recorded.value.id,
|
|
16145
16408
|
dimensions: verdict.dimensions,
|
|
16146
16409
|
threshold: props.plateauThreshold
|
|
@@ -16164,7 +16427,7 @@ var runEvaluatorTurnUseCase = async (props) => {
|
|
|
16164
16427
|
});
|
|
16165
16428
|
return Result.error(warned.error);
|
|
16166
16429
|
}
|
|
16167
|
-
log.info("plateau softened to warning \u2014
|
|
16430
|
+
log.info("plateau softened to warning \u2014 work product changed; continuing", {
|
|
16168
16431
|
taskId: warned.value.id,
|
|
16169
16432
|
dimensions: verdict.dimensions,
|
|
16170
16433
|
reason: verdict.reason
|
|
@@ -16288,6 +16551,150 @@ var buildEvaluatePrompt = async (deps, input) => buildPrompt(deps, evaluatePromp
|
|
|
16288
16551
|
priorProgress: input.priorProgress ?? ""
|
|
16289
16552
|
});
|
|
16290
16553
|
|
|
16554
|
+
// src/integration/ai/prompts/evaluate-continuation/definition.ts
|
|
16555
|
+
var requireNonEmpty3 = (field, message) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message })) : Result.ok(v);
|
|
16556
|
+
var evaluateContinuationPromptDef = {
|
|
16557
|
+
templateName: "evaluate-continuation",
|
|
16558
|
+
description: "Resumed evaluator turn (round 2+ of a session thread). Carries only the per-round delta \u2014 round number, recent journal \u2014 because the specification and rubric are already in the conversation.",
|
|
16559
|
+
parameters: {
|
|
16560
|
+
roundNumber: {
|
|
16561
|
+
placeholder: "ROUND_NUMBER",
|
|
16562
|
+
description: "Current gen-eval round number rendered as a string.",
|
|
16563
|
+
validate: requireNonEmpty3("roundNumber", "round number must not be empty")
|
|
16564
|
+
},
|
|
16565
|
+
contractPath: {
|
|
16566
|
+
placeholder: "CONTRACT_PATH",
|
|
16567
|
+
description: "Absolute path to the per-task contract.md sidecar \u2014 re-read hedge for a context-free resume.",
|
|
16568
|
+
validate: requireNonEmpty3("contractPath", "contract path must not be empty")
|
|
16569
|
+
},
|
|
16570
|
+
progressFile: {
|
|
16571
|
+
placeholder: "PROGRESS_FILE",
|
|
16572
|
+
description: "Absolute path to the sprint progress.md journal \u2014 full history when the inline excerpt is short.",
|
|
16573
|
+
validate: requireNonEmpty3("progressFile", "progress file path must not be empty")
|
|
16574
|
+
},
|
|
16575
|
+
priorProgress: {
|
|
16576
|
+
placeholder: "PRIOR_PROGRESS",
|
|
16577
|
+
description: "Capped recent slice of progress.md inlined into the prior-progress block \u2014 empty when none yet."
|
|
16578
|
+
},
|
|
16579
|
+
outputContractSection: {
|
|
16580
|
+
placeholder: "OUTPUT_CONTRACT_SECTION",
|
|
16581
|
+
description: "Audit-[09] output contract block rendered for THIS round's evaluator output directory \u2014 names the current signals.json path.",
|
|
16582
|
+
validate: requireNonEmpty3(
|
|
16583
|
+
"outputContractSection",
|
|
16584
|
+
"output-contract section must not be empty (renderContractSectionFor always emits a body)"
|
|
16585
|
+
)
|
|
16586
|
+
}
|
|
16587
|
+
},
|
|
16588
|
+
partials: {
|
|
16589
|
+
HARNESS_CONTEXT: "harness-context"
|
|
16590
|
+
},
|
|
16591
|
+
// Same single-signal contract as the full evaluate prompt — a continuation turn still emits
|
|
16592
|
+
// exactly one `evaluation` verdict.
|
|
16593
|
+
expectedSignals: ["evaluation"]
|
|
16594
|
+
};
|
|
16595
|
+
var buildEvaluateContinuationPrompt = async (deps, input) => buildPrompt(deps, evaluateContinuationPromptDef, {
|
|
16596
|
+
roundNumber: String(input.roundNumber),
|
|
16597
|
+
contractPath: input.contractPath,
|
|
16598
|
+
progressFile: input.progressFile,
|
|
16599
|
+
priorProgress: input.priorProgress,
|
|
16600
|
+
outputContractSection: input.outputContractSection
|
|
16601
|
+
});
|
|
16602
|
+
|
|
16603
|
+
// src/integration/ai/contract/_engine/corrective-retry.ts
|
|
16604
|
+
import { ZodError } from "zod";
|
|
16605
|
+
var isCorrectableContractError = (err) => err.code !== ErrorCode.Aborted && err.code !== ErrorCode.RateLimit && err.code !== ErrorCode.MigrationGap;
|
|
16606
|
+
var MAX_ENUMERATED_ISSUES = 10;
|
|
16607
|
+
var correctiveBody = (err, signalsPath, selfContainedContext) => {
|
|
16608
|
+
const lines = [];
|
|
16609
|
+
lines.push("Your previous `signals.json` did not satisfy the output contract, so the harness could");
|
|
16610
|
+
lines.push("not read a verdict. Fix it now in ONE follow-up \u2014 re-read the output-contract section");
|
|
16611
|
+
lines.push("below and re-write the file. Do not change anything else.");
|
|
16612
|
+
lines.push("");
|
|
16613
|
+
if (err instanceof ParseError && err.subCode === "schema-mismatch" && err.cause instanceof ZodError) {
|
|
16614
|
+
lines.push("The shape failed schema validation. Each line below is one problem \u2014 fix every one:");
|
|
16615
|
+
lines.push("");
|
|
16616
|
+
const issues = err.cause.issues;
|
|
16617
|
+
for (const issue of issues.slice(0, MAX_ENUMERATED_ISSUES)) {
|
|
16618
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
16619
|
+
lines.push(`- at \`${path}\`: ${issue.message}`);
|
|
16620
|
+
}
|
|
16621
|
+
if (issues.length > MAX_ENUMERATED_ISSUES) {
|
|
16622
|
+
lines.push(
|
|
16623
|
+
`- \u2026plus ${String(issues.length - MAX_ENUMERATED_ISSUES)} more of the same kinds \u2014 fix the pattern, not just the listed lines.`
|
|
16624
|
+
);
|
|
16625
|
+
}
|
|
16626
|
+
lines.push("");
|
|
16627
|
+
lines.push("Common cause: a terminal `evaluation` verdict (`passed` / `failed`) MUST grade all four");
|
|
16628
|
+
lines.push("floor dimensions \u2014 correctness, completeness, safety, consistency \u2014 each with a finding.");
|
|
16629
|
+
} else if (err instanceof ParseError && err.subCode === "invalid-json") {
|
|
16630
|
+
lines.push(`The file at \`${signalsPath}\` existed but was not valid JSON. Re-write it as a single`);
|
|
16631
|
+
lines.push("valid JSON object matching the `{ schemaVersion, signals }` shape. Check for trailing");
|
|
16632
|
+
lines.push("commas, unescaped quotes inside string fields, and truncated output.");
|
|
16633
|
+
} else {
|
|
16634
|
+
lines.push(`You did not write \`${signalsPath}\`. Write that exact absolute path now with your`);
|
|
16635
|
+
lines.push("`Write` tool, matching the `{ schemaVersion, signals }` shape from the contract above.");
|
|
16636
|
+
}
|
|
16637
|
+
lines.push("");
|
|
16638
|
+
lines.push(`Write the corrected file to the exact absolute path \`${signalsPath}\` and stop.`);
|
|
16639
|
+
lines.push("");
|
|
16640
|
+
lines.push("If this session does not already hold the task context (fresh session), re-read the");
|
|
16641
|
+
lines.push("referenced on-disk files before acting \u2014 do NOT invent a verdict or content from this");
|
|
16642
|
+
lines.push("message alone.");
|
|
16643
|
+
lines.push("");
|
|
16644
|
+
lines.push(selfContainedContext);
|
|
16645
|
+
return lines.join("\n");
|
|
16646
|
+
};
|
|
16647
|
+
var validateSignalsFileWithCorrectiveRetry = async (deps, contract) => {
|
|
16648
|
+
const first = await validateSignalsFile(deps.outputDir, contract);
|
|
16649
|
+
if (first.ok) return first;
|
|
16650
|
+
const err = first.error;
|
|
16651
|
+
if (!isCorrectableContractError(err)) return Result.error(err);
|
|
16652
|
+
const log = deps.logger.named("ai.contract.corrective-retry");
|
|
16653
|
+
const signalsPath = `${String(deps.outputDir)}/signals.json`;
|
|
16654
|
+
log.warn("signals.json failed the contract \u2014 issuing one corrective retry", {
|
|
16655
|
+
outputDir: String(deps.outputDir),
|
|
16656
|
+
error: err.message
|
|
16657
|
+
});
|
|
16658
|
+
const corrective = correctiveBody(err, signalsPath, deps.selfContainedContext);
|
|
16659
|
+
const respawn = await deps.reinvoke(corrective);
|
|
16660
|
+
if (!respawn.ok) {
|
|
16661
|
+
log.warn("corrective retry spawn failed \u2014 self-blocking on the spawn error", {
|
|
16662
|
+
outputDir: String(deps.outputDir),
|
|
16663
|
+
error: respawn.error.message
|
|
16664
|
+
});
|
|
16665
|
+
return Result.error(respawn.error);
|
|
16666
|
+
}
|
|
16667
|
+
const second = await validateSignalsFile(deps.outputDir, contract);
|
|
16668
|
+
if (second.ok) {
|
|
16669
|
+
log.info("corrective retry produced a valid signals.json", { outputDir: String(deps.outputDir) });
|
|
16670
|
+
return second;
|
|
16671
|
+
}
|
|
16672
|
+
log.warn("corrective retry still failed the contract \u2014 self-blocking", {
|
|
16673
|
+
outputDir: String(deps.outputDir),
|
|
16674
|
+
error: second.error.message
|
|
16675
|
+
});
|
|
16676
|
+
return Result.error(second.error);
|
|
16677
|
+
};
|
|
16678
|
+
|
|
16679
|
+
// src/application/flows/implement/leaves/work-product-fingerprint.ts
|
|
16680
|
+
import { createHash as createHash2 } from "crypto";
|
|
16681
|
+
var computeWorkProductFingerprint = async (gitRunner, cwd) => {
|
|
16682
|
+
const status = await gitRunner.run(cwd, ["status", "--porcelain"]);
|
|
16683
|
+
if (!status.ok || status.value.exitCode !== 0) return void 0;
|
|
16684
|
+
const diff = await gitRunner.run(cwd, ["diff", "HEAD"]);
|
|
16685
|
+
if (!diff.ok || diff.value.exitCode !== 0) return void 0;
|
|
16686
|
+
const hash = createHash2("sha1").update(status.value.stdout).update("\0").update(diff.value.stdout);
|
|
16687
|
+
const untracked = await gitRunner.run(cwd, ["ls-files", "--others", "--exclude-standard"]);
|
|
16688
|
+
if (!untracked.ok || untracked.value.exitCode !== 0) return void 0;
|
|
16689
|
+
const paths = untracked.value.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
16690
|
+
if (paths.length > 0) {
|
|
16691
|
+
const blobs = await gitRunner.run(cwd, ["hash-object", "--", ...paths]);
|
|
16692
|
+
if (!blobs.ok || blobs.value.exitCode !== 0) return void 0;
|
|
16693
|
+
hash.update("\0").update(blobs.value.stdout);
|
|
16694
|
+
}
|
|
16695
|
+
return hash.digest("hex");
|
|
16696
|
+
};
|
|
16697
|
+
|
|
16291
16698
|
// src/application/flows/implement/leaves/implement-session.ts
|
|
16292
16699
|
import { dirname as dirname14 } from "path";
|
|
16293
16700
|
|
|
@@ -16336,6 +16743,11 @@ import { z as z33 } from "zod";
|
|
|
16336
16743
|
|
|
16337
16744
|
// src/integration/ai/contract/_engine/signals/evaluation/schema.ts
|
|
16338
16745
|
import { z as z32 } from "zod";
|
|
16746
|
+
|
|
16747
|
+
// src/domain/signal.ts
|
|
16748
|
+
var FLOOR_DIMENSIONS = ["correctness", "completeness", "safety", "consistency"];
|
|
16749
|
+
|
|
16750
|
+
// src/integration/ai/contract/_engine/signals/evaluation/schema.ts
|
|
16339
16751
|
var dimensionScoreSchema = z32.object({
|
|
16340
16752
|
dimension: z32.string(),
|
|
16341
16753
|
passed: z32.boolean(),
|
|
@@ -16357,6 +16769,16 @@ var evaluationSignalSchema = z32.object({
|
|
|
16357
16769
|
critique: z32.string().optional(),
|
|
16358
16770
|
timestamp: IsoTimestampSchema
|
|
16359
16771
|
}).superRefine((s, ctx) => {
|
|
16772
|
+
if (s.status === "malformed") return;
|
|
16773
|
+
const present = new Set(s.dimensions.map((d) => d.dimension.trim().toLowerCase()));
|
|
16774
|
+
const missing = FLOOR_DIMENSIONS.filter((name) => !present.has(name));
|
|
16775
|
+
if (missing.length > 0) {
|
|
16776
|
+
ctx.addIssue({
|
|
16777
|
+
code: "custom",
|
|
16778
|
+
message: `status is "${s.status}" but the required floor dimension(s) ${missing.join(", ")} are missing \u2014 every terminal verdict must grade correctness, completeness, safety, and consistency`,
|
|
16779
|
+
path: ["dimensions"]
|
|
16780
|
+
});
|
|
16781
|
+
}
|
|
16360
16782
|
if (s.status === "passed") {
|
|
16361
16783
|
const failedIndex = s.dimensions.findIndex((d) => !d.passed);
|
|
16362
16784
|
if (failedIndex !== -1) {
|
|
@@ -16366,7 +16788,7 @@ var evaluationSignalSchema = z32.object({
|
|
|
16366
16788
|
path: ["dimensions", failedIndex, "passed"]
|
|
16367
16789
|
});
|
|
16368
16790
|
}
|
|
16369
|
-
} else
|
|
16791
|
+
} else {
|
|
16370
16792
|
const anyFailed = s.dimensions.some((d) => !d.passed);
|
|
16371
16793
|
if (!anyFailed) {
|
|
16372
16794
|
ctx.addIssue({
|
|
@@ -16502,14 +16924,68 @@ var writeRoundPrompt = async (workspaceRoot, round, role, prompt, logger) => {
|
|
|
16502
16924
|
}
|
|
16503
16925
|
};
|
|
16504
16926
|
|
|
16927
|
+
// src/application/flows/implement/leaves/_shared/cap-progress.ts
|
|
16928
|
+
var RECENT_ATTEMPT_SECTIONS = 3;
|
|
16929
|
+
var ATTEMPT_DELIMITER = /^(?=## Task: )/m;
|
|
16930
|
+
var elisionNote = (droppedCount) => `_${String(droppedCount)} earlier attempt section${droppedCount === 1 ? "" : "s"} omitted from this inline excerpt \u2014 read the full \`progress.md\` on disk for the complete history._
|
|
16931
|
+
|
|
16932
|
+
`;
|
|
16933
|
+
var capProgressBody = (body, recentSections = RECENT_ATTEMPT_SECTIONS, currentTaskName) => {
|
|
16934
|
+
if (body.trim().length === 0) return "";
|
|
16935
|
+
const pieces = body.split(ATTEMPT_DELIMITER);
|
|
16936
|
+
if (pieces.length <= 1) return body;
|
|
16937
|
+
const [header, ...sections] = pieces;
|
|
16938
|
+
const isCurrentTask = (section) => currentTaskName !== void 0 && section.startsWith(`## Task: ${currentTaskName} \u2014 Attempt`);
|
|
16939
|
+
const otherIndexes = sections.flatMap((s, i) => isCurrentTask(s) ? [] : [i]);
|
|
16940
|
+
const keptOtherIndexes = new Set(otherIndexes.slice(Math.max(0, otherIndexes.length - recentSections)));
|
|
16941
|
+
const keep = (i) => isCurrentTask(sections[i] ?? "") || keptOtherIndexes.has(i);
|
|
16942
|
+
if (sections.every((_s, i) => keep(i))) return body;
|
|
16943
|
+
const parts = [header ?? ""];
|
|
16944
|
+
let droppedRun = 0;
|
|
16945
|
+
for (let i = 0; i < sections.length; i += 1) {
|
|
16946
|
+
if (keep(i)) {
|
|
16947
|
+
if (droppedRun > 0) {
|
|
16948
|
+
parts.push(elisionNote(droppedRun));
|
|
16949
|
+
droppedRun = 0;
|
|
16950
|
+
}
|
|
16951
|
+
parts.push(sections[i] ?? "");
|
|
16952
|
+
} else {
|
|
16953
|
+
droppedRun += 1;
|
|
16954
|
+
}
|
|
16955
|
+
}
|
|
16956
|
+
if (droppedRun > 0) parts.push(elisionNote(droppedRun));
|
|
16957
|
+
return parts.join("");
|
|
16958
|
+
};
|
|
16959
|
+
|
|
16505
16960
|
// src/application/flows/implement/leaves/evaluator.ts
|
|
16506
|
-
var
|
|
16961
|
+
var readCappedProgress = async (path, currentTaskName) => {
|
|
16507
16962
|
try {
|
|
16508
|
-
return await fs22.readFile(path, "utf8");
|
|
16963
|
+
return capProgressBody(await fs22.readFile(path, "utf8"), RECENT_ATTEMPT_SECTIONS, currentTaskName);
|
|
16509
16964
|
} catch {
|
|
16510
16965
|
return "";
|
|
16511
16966
|
}
|
|
16512
16967
|
};
|
|
16968
|
+
var buildEvaluatorPrompt = async (deps, args) => {
|
|
16969
|
+
const priorProgress = await readCappedProgress(String(deps.progressFile), args.task.name);
|
|
16970
|
+
const contractPath = join30(String(args.workspaceRoot), "contract.md");
|
|
16971
|
+
if (args.priorEvaluatorSessionId === void 0) {
|
|
16972
|
+
return buildEvaluatePrompt(deps.templateLoader, {
|
|
16973
|
+
task: args.task,
|
|
16974
|
+
projectPath: String(deps.cwd),
|
|
16975
|
+
contractPath,
|
|
16976
|
+
outputContractSection: args.outputContractSection,
|
|
16977
|
+
priorProgress,
|
|
16978
|
+
...deps.verifyScript !== void 0 ? { verifyScript: deps.verifyScript } : {}
|
|
16979
|
+
});
|
|
16980
|
+
}
|
|
16981
|
+
return buildEvaluateContinuationPrompt(deps.templateLoader, {
|
|
16982
|
+
roundNumber: args.roundNum,
|
|
16983
|
+
contractPath,
|
|
16984
|
+
progressFile: String(deps.progressFile),
|
|
16985
|
+
priorProgress,
|
|
16986
|
+
outputContractSection: args.outputContractSection
|
|
16987
|
+
});
|
|
16988
|
+
};
|
|
16513
16989
|
var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
|
|
16514
16990
|
useCase: {
|
|
16515
16991
|
execute: async (input, signal) => {
|
|
@@ -16520,14 +16996,13 @@ var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
|
|
|
16520
16996
|
if (!outputDirPath.ok) return Result.error(outputDirPath.error);
|
|
16521
16997
|
const outputDir = outputDirPath.value;
|
|
16522
16998
|
const callEvaluate = async (task) => {
|
|
16523
|
-
const
|
|
16524
|
-
const prompt = await
|
|
16999
|
+
const outputContractSection = renderContractSectionFor(evaluatorOutputContract, outputDir);
|
|
17000
|
+
const prompt = await buildEvaluatorPrompt(deps, {
|
|
16525
17001
|
task,
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
outputContractSection
|
|
16529
|
-
|
|
16530
|
-
...deps.verifyScript !== void 0 ? { verifyScript: deps.verifyScript } : {}
|
|
17002
|
+
workspaceRoot: input.workspaceRoot,
|
|
17003
|
+
roundNum: input.roundNum,
|
|
17004
|
+
outputContractSection,
|
|
17005
|
+
priorEvaluatorSessionId: input.priorEvaluatorSessionId
|
|
16531
17006
|
});
|
|
16532
17007
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
16533
17008
|
await writeRoundPrompt(input.workspaceRoot, input.roundNum, "evaluator", String(prompt.value), deps.logger);
|
|
@@ -16546,7 +17021,43 @@ var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
|
|
|
16546
17021
|
)
|
|
16547
17022
|
);
|
|
16548
17023
|
if (!spawn3.ok) return Result.error(spawn3.error);
|
|
16549
|
-
const validated = await
|
|
17024
|
+
const validated = await validateSignalsFileWithCorrectiveRetry(
|
|
17025
|
+
{
|
|
17026
|
+
outputDir,
|
|
17027
|
+
logger: deps.logger,
|
|
17028
|
+
// Self-containment for a COLD corrective spawn (no resumable id / codex stale-resume
|
|
17029
|
+
// fallback): the per-round output contract plus the reviewer's grounding — without
|
|
17030
|
+
// this, a context-free retry's whole prompt is the error text, which is exactly
|
|
17031
|
+
// enough scaffolding to fabricate a schema-valid verdict for unseen work.
|
|
17032
|
+
selfContainedContext: [
|
|
17033
|
+
`Task spec (read it): \`${join30(String(input.workspaceRoot), "contract.md")}\``,
|
|
17034
|
+
"Your PRIMARY INPUT is the uncommitted working-tree diff \u2014 inspect it via shell",
|
|
17035
|
+
"(`git status` / `git diff HEAD`) before grading. A verdict must reflect the actual",
|
|
17036
|
+
"work, never this message.",
|
|
17037
|
+
"",
|
|
17038
|
+
outputContractSection
|
|
17039
|
+
].join("\n"),
|
|
17040
|
+
reinvoke: async (corrective) => {
|
|
17041
|
+
const resume = await readRoundSessionId(input.workspaceRoot, input.roundNum, "evaluator") ?? input.priorEvaluatorSessionId;
|
|
17042
|
+
const respawn = await deps.provider.generate(
|
|
17043
|
+
implementSession(
|
|
17044
|
+
input.workspaceRoot,
|
|
17045
|
+
deps.cwd,
|
|
17046
|
+
deps.sprintDir,
|
|
17047
|
+
corrective,
|
|
17048
|
+
deps.model,
|
|
17049
|
+
signalsFile,
|
|
17050
|
+
"evaluator",
|
|
17051
|
+
resume,
|
|
17052
|
+
deps.effort,
|
|
17053
|
+
signal
|
|
17054
|
+
)
|
|
17055
|
+
);
|
|
17056
|
+
return respawn.ok ? Result.ok(void 0) : Result.error(respawn.error);
|
|
17057
|
+
}
|
|
17058
|
+
},
|
|
17059
|
+
evaluatorOutputContract
|
|
17060
|
+
);
|
|
16550
17061
|
if (!validated.ok) return Result.error(validated.error);
|
|
16551
17062
|
const signals = validated.value;
|
|
16552
17063
|
for (const sig of signals) {
|
|
@@ -16556,11 +17067,13 @@ var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
|
|
|
16556
17067
|
await renderSidecars(deps.writeFile, outputDir, signals, evaluatorOutputContract.sidecars, deps.logger);
|
|
16557
17068
|
return Result.ok(signals);
|
|
16558
17069
|
};
|
|
17070
|
+
const changedFilesHash = await computeWorkProductFingerprint(deps.gitRunner, deps.cwd);
|
|
16559
17071
|
const result = await runEvaluatorTurnUseCase({
|
|
16560
17072
|
task: input.task,
|
|
16561
17073
|
priorTurns: input.priorTurns,
|
|
16562
17074
|
plateauThreshold: deps.plateauThreshold,
|
|
16563
17075
|
...input.currentCommitSubject !== void 0 ? { currentCommitSubject: input.currentCommitSubject } : {},
|
|
17076
|
+
...changedFilesHash !== void 0 ? { changedFilesHash } : {},
|
|
16564
17077
|
callEvaluate,
|
|
16565
17078
|
evaluationFile: roundEvaluationRelativePath(input.roundNum),
|
|
16566
17079
|
logger: deps.logger
|
|
@@ -16685,14 +17198,112 @@ var runGeneratorTurnUseCase = async (props) => {
|
|
|
16685
17198
|
});
|
|
16686
17199
|
};
|
|
16687
17200
|
|
|
17201
|
+
// src/integration/ai/prompts/implement-continuation/definition.ts
|
|
17202
|
+
var requireNonEmpty4 = (field, message) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message })) : Result.ok(v);
|
|
17203
|
+
var implementContinuationPromptDef = {
|
|
17204
|
+
templateName: "implement-continuation",
|
|
17205
|
+
description: "Resumed generator turn (round 2+ of a session thread). Carries only the per-round delta \u2014 critique, round number, plateau directive \u2014 because the full brief is already in the conversation.",
|
|
17206
|
+
parameters: {
|
|
17207
|
+
roundNumber: {
|
|
17208
|
+
placeholder: "ROUND_NUMBER",
|
|
17209
|
+
description: "Current gen-eval round number rendered as a string.",
|
|
17210
|
+
validate: requireNonEmpty4("roundNumber", "round number must not be empty")
|
|
17211
|
+
},
|
|
17212
|
+
contractPath: {
|
|
17213
|
+
placeholder: "CONTRACT_PATH",
|
|
17214
|
+
description: "Absolute path to the per-task contract.md sidecar \u2014 re-read hedge for a context-free resume.",
|
|
17215
|
+
validate: requireNonEmpty4("contractPath", "contract path must not be empty")
|
|
17216
|
+
},
|
|
17217
|
+
progressFile: {
|
|
17218
|
+
placeholder: "PROGRESS_FILE",
|
|
17219
|
+
description: "Absolute path to the sprint progress.md journal \u2014 full history when the inline excerpt is short.",
|
|
17220
|
+
validate: requireNonEmpty4("progressFile", "progress file path must not be empty")
|
|
17221
|
+
},
|
|
17222
|
+
priorProgress: {
|
|
17223
|
+
placeholder: "PRIOR_PROGRESS",
|
|
17224
|
+
description: "Capped recent slice of progress.md inlined into the prior-progress block \u2014 empty when none yet."
|
|
17225
|
+
},
|
|
17226
|
+
priorCritiqueSection: {
|
|
17227
|
+
placeholder: "PRIOR_CRITIQUE_SECTION",
|
|
17228
|
+
description: `"## Prior Critique" markdown block \u2014 the prior round's failed evaluator critique, verbatim.`
|
|
17229
|
+
},
|
|
17230
|
+
plateauDirectiveSection: {
|
|
17231
|
+
placeholder: "PLATEAU_DIRECTIVE_SECTION",
|
|
17232
|
+
description: '"## \u26A0 You have plateaued \u2014 change your approach" block \u2014 empty unless this round is a plateau-break attempt.'
|
|
17233
|
+
},
|
|
17234
|
+
outputContractSection: {
|
|
17235
|
+
placeholder: "OUTPUT_CONTRACT_SECTION",
|
|
17236
|
+
description: "Audit-[09] output contract block rendered for THIS round's output directory \u2014 names the current signals.json path.",
|
|
17237
|
+
validate: requireNonEmpty4(
|
|
17238
|
+
"outputContractSection",
|
|
17239
|
+
"output-contract section must not be empty (renderContractSectionFor always emits a body)"
|
|
17240
|
+
)
|
|
17241
|
+
}
|
|
17242
|
+
},
|
|
17243
|
+
partials: {
|
|
17244
|
+
HARNESS_CONTEXT: "harness-context"
|
|
17245
|
+
},
|
|
17246
|
+
// Same accepted signal union as the full implement prompt — a continuation turn is still a
|
|
17247
|
+
// generator turn and may emit the full narrative + lifecycle set.
|
|
17248
|
+
expectedSignals: [
|
|
17249
|
+
"change",
|
|
17250
|
+
"decision",
|
|
17251
|
+
"learning",
|
|
17252
|
+
"note",
|
|
17253
|
+
"task-verified",
|
|
17254
|
+
"task-complete",
|
|
17255
|
+
"task-blocked",
|
|
17256
|
+
"commit-message"
|
|
17257
|
+
]
|
|
17258
|
+
};
|
|
17259
|
+
var buildImplementContinuationPrompt = async (deps, input) => buildPrompt(deps, implementContinuationPromptDef, {
|
|
17260
|
+
roundNumber: String(input.roundNumber),
|
|
17261
|
+
contractPath: input.contractPath,
|
|
17262
|
+
progressFile: input.progressFile,
|
|
17263
|
+
priorProgress: input.priorProgress,
|
|
17264
|
+
priorCritiqueSection: renderPriorCritiqueSection(input.priorCritique),
|
|
17265
|
+
plateauDirectiveSection: renderPlateauDirectiveSection(input.plateauBreak ?? false),
|
|
17266
|
+
outputContractSection: input.outputContractSection
|
|
17267
|
+
});
|
|
17268
|
+
|
|
16688
17269
|
// src/application/flows/implement/leaves/generator.ts
|
|
16689
|
-
var
|
|
17270
|
+
var readCappedProgress2 = async (path, currentTaskName) => {
|
|
16690
17271
|
try {
|
|
16691
|
-
return await fs23.readFile(path, "utf8");
|
|
17272
|
+
return capProgressBody(await fs23.readFile(path, "utf8"), RECENT_ATTEMPT_SECTIONS, currentTaskName);
|
|
16692
17273
|
} catch {
|
|
16693
17274
|
return "";
|
|
16694
17275
|
}
|
|
16695
17276
|
};
|
|
17277
|
+
var buildGeneratorPrompt = async (deps, args) => {
|
|
17278
|
+
const priorCritique = latestCritique(args.task);
|
|
17279
|
+
const priorProgress = await readCappedProgress2(String(deps.progressFile), args.task.name);
|
|
17280
|
+
const contractPath = join31(String(args.workspaceRoot), "contract.md");
|
|
17281
|
+
const lastSettled = [...args.task.attempts].reverse().find((a) => a.status !== "running");
|
|
17282
|
+
const stallDriven = lastSettled?.warning?.kind === "plateau" || lastSettled?.warning?.kind === "budget-exhausted";
|
|
17283
|
+
const plateauBreak = args.task.escalatedFromModel !== void 0 && args.task.escalatedFromModel === args.task.escalatedToModel && stallDriven;
|
|
17284
|
+
if (args.priorGeneratorSessionId === void 0) {
|
|
17285
|
+
return buildImplementPrompt(deps.templateLoader, {
|
|
17286
|
+
task: args.task,
|
|
17287
|
+
projectPath: String(deps.cwd),
|
|
17288
|
+
contractPath,
|
|
17289
|
+
progressFile: String(deps.progressFile),
|
|
17290
|
+
priorProgress,
|
|
17291
|
+
outputContractSection: args.outputContractSection,
|
|
17292
|
+
...deps.verifyScript !== void 0 ? { verifyScript: deps.verifyScript } : {},
|
|
17293
|
+
...priorCritique !== void 0 ? { priorCritique } : {},
|
|
17294
|
+
...plateauBreak ? { plateauBreak: true } : {}
|
|
17295
|
+
});
|
|
17296
|
+
}
|
|
17297
|
+
return buildImplementContinuationPrompt(deps.templateLoader, {
|
|
17298
|
+
roundNumber: args.roundNum,
|
|
17299
|
+
contractPath,
|
|
17300
|
+
progressFile: String(deps.progressFile),
|
|
17301
|
+
priorProgress,
|
|
17302
|
+
outputContractSection: args.outputContractSection,
|
|
17303
|
+
...priorCritique !== void 0 ? { priorCritique } : {},
|
|
17304
|
+
...plateauBreak ? { plateauBreak: true } : {}
|
|
17305
|
+
});
|
|
17306
|
+
};
|
|
16696
17307
|
var generatorLeaf = (deps, taskId) => leaf(`generator-${String(taskId)}`, {
|
|
16697
17308
|
useCase: {
|
|
16698
17309
|
execute: async (input, signal) => {
|
|
@@ -16727,21 +17338,13 @@ var generatorLeaf = (deps, taskId) => leaf(`generator-${String(taskId)}`, {
|
|
|
16727
17338
|
const learningsEmitted = [];
|
|
16728
17339
|
const notesEmitted = [];
|
|
16729
17340
|
const callImplement = async (task) => {
|
|
16730
|
-
const
|
|
16731
|
-
const
|
|
16732
|
-
const prompt = await buildImplementPrompt(deps.templateLoader, {
|
|
17341
|
+
const outputContractSection = renderContractSectionFor(generatorOutputContract, outputDir);
|
|
17342
|
+
const prompt = await buildGeneratorPrompt(deps, {
|
|
16733
17343
|
task,
|
|
16734
|
-
|
|
16735
|
-
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
outputContractSection: renderContractSectionFor(generatorOutputContract, outputDir),
|
|
16739
|
-
...deps.verifyScript !== void 0 ? { verifyScript: deps.verifyScript } : {},
|
|
16740
|
-
...priorCritique !== void 0 ? { priorCritique } : {},
|
|
16741
|
-
// Plateau-break attempt: the escalation policy stamped the task (model bump and/or a
|
|
16742
|
-
// change-of-approach nudge) after the gen-eval loop stalled. Surface the "change your
|
|
16743
|
-
// approach" directive so the generator abandons the non-converging path.
|
|
16744
|
-
...task.escalatedFromModel !== void 0 ? { plateauBreak: true } : {}
|
|
17344
|
+
workspaceRoot: input.workspaceRoot,
|
|
17345
|
+
roundNum,
|
|
17346
|
+
outputContractSection,
|
|
17347
|
+
priorGeneratorSessionId: input.priorGeneratorSessionId
|
|
16745
17348
|
});
|
|
16746
17349
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
16747
17350
|
await writeRoundPrompt(input.workspaceRoot, roundNum, "generator", String(prompt.value), deps.logger);
|
|
@@ -16761,7 +17364,39 @@ var generatorLeaf = (deps, taskId) => leaf(`generator-${String(taskId)}`, {
|
|
|
16761
17364
|
)
|
|
16762
17365
|
);
|
|
16763
17366
|
if (!spawn3.ok) return Result.error(spawn3.error);
|
|
16764
|
-
const validated = await
|
|
17367
|
+
const validated = await validateSignalsFileWithCorrectiveRetry(
|
|
17368
|
+
{
|
|
17369
|
+
outputDir,
|
|
17370
|
+
logger: deps.logger,
|
|
17371
|
+
// Self-containment for a COLD corrective spawn (no resumable id / codex stale-resume
|
|
17372
|
+
// fallback): the per-round output contract + the on-disk task spec, so a fresh
|
|
17373
|
+
// session re-reads its grounding instead of emitting signals from the error text.
|
|
17374
|
+
selfContainedContext: [
|
|
17375
|
+
`Task spec (read it): \`${join31(String(input.workspaceRoot), "contract.md")}\``,
|
|
17376
|
+
"",
|
|
17377
|
+
outputContractSection
|
|
17378
|
+
].join("\n"),
|
|
17379
|
+
reinvoke: async (corrective) => {
|
|
17380
|
+
const resume = await readRoundSessionId(input.workspaceRoot, roundNum, "generator") ?? input.priorGeneratorSessionId;
|
|
17381
|
+
const respawn = await deps.provider.generate(
|
|
17382
|
+
implementSession(
|
|
17383
|
+
input.workspaceRoot,
|
|
17384
|
+
deps.cwd,
|
|
17385
|
+
deps.sprintDir,
|
|
17386
|
+
corrective,
|
|
17387
|
+
effectiveModel,
|
|
17388
|
+
signalsFile,
|
|
17389
|
+
"generator",
|
|
17390
|
+
resume,
|
|
17391
|
+
deps.effort,
|
|
17392
|
+
signal
|
|
17393
|
+
)
|
|
17394
|
+
);
|
|
17395
|
+
return respawn.ok ? Result.ok(void 0) : Result.error(respawn.error);
|
|
17396
|
+
}
|
|
17397
|
+
},
|
|
17398
|
+
generatorOutputContract
|
|
17399
|
+
);
|
|
16765
17400
|
if (!validated.ok) return Result.error(validated.error);
|
|
16766
17401
|
const signals = validated.value;
|
|
16767
17402
|
for (const sig of signals) {
|
|
@@ -17076,6 +17711,9 @@ var createGenEvalLoop = (deps, opts, taskId) => {
|
|
|
17076
17711
|
};
|
|
17077
17712
|
const evaluatorLeafDeps = {
|
|
17078
17713
|
...sharedLeafDeps,
|
|
17714
|
+
// Evaluator-only: the work-product fingerprint for the plateau predicate. The generator
|
|
17715
|
+
// leaf neither needs nor accepts the git runner.
|
|
17716
|
+
gitRunner: deps.gitRunner,
|
|
17079
17717
|
provider: deps.evaluatorProvider,
|
|
17080
17718
|
model: opts.evaluator.model,
|
|
17081
17719
|
...opts.evaluator.effort !== void 0 ? { effort: opts.evaluator.effort } : {}
|
|
@@ -17130,9 +17768,16 @@ var createGenEvalLoop = (deps, opts, taskId) => {
|
|
|
17130
17768
|
)
|
|
17131
17769
|
]),
|
|
17132
17770
|
{
|
|
17133
|
-
|
|
17771
|
+
// Loop-entry guard. Refuse to enter a turn when a terminal exit is ALREADY on ctx — the
|
|
17772
|
+
// only way `lastExit` is set at loop entry is a pre-task-verify block/skip (start-attempt
|
|
17773
|
+
// runs before pre-verify and settle-attempt clears `lastExit` at the end of every attempt,
|
|
17774
|
+
// so no stale exit can leak across attempts/tasks; the parallel merge-wave classifies
|
|
17775
|
+
// `lastExit` PER_TASK). Without this check a pre-blocked task would still claim a
|
|
17776
|
+
// `rounds/<N>/` dir, stamp two meta sidecars, and spawn one full generator session on the
|
|
17777
|
+
// exact broken tree the gate refused — the most expensive unit in the system.
|
|
17778
|
+
shouldContinue: async (ctx, i) => {
|
|
17134
17779
|
const cfg = await deps.readConfig();
|
|
17135
|
-
return i <= Math.max(1, cfg.maxTurns);
|
|
17780
|
+
return ctx.lastExit === void 0 && i <= Math.max(1, cfg.maxTurns);
|
|
17136
17781
|
},
|
|
17137
17782
|
shouldStop: (ctx) => ctx.lastExit !== void 0
|
|
17138
17783
|
}
|
|
@@ -17484,6 +18129,17 @@ var legacyVerifyResult = (run, rawOutput, spawnErrorMessage) => {
|
|
|
17484
18129
|
var postTaskVerifyLeaf = (deps, opts, taskId) => leaf(`post-task-verify-${String(taskId)}`, {
|
|
17485
18130
|
useCase: {
|
|
17486
18131
|
execute: async (input, signal) => {
|
|
18132
|
+
if (input.preVerifyBlockedZeroTurn) {
|
|
18133
|
+
const skipped = {
|
|
18134
|
+
phase: "post",
|
|
18135
|
+
ranAt: deps.clock(),
|
|
18136
|
+
command: "",
|
|
18137
|
+
exitCode: 0,
|
|
18138
|
+
durationMs: 0,
|
|
18139
|
+
outcome: "skipped"
|
|
18140
|
+
};
|
|
18141
|
+
return Result.ok({ task: input.task, run: skipped, rawOutput: "" });
|
|
18142
|
+
}
|
|
17487
18143
|
const { run, rawOutput, spawnErrorMessage } = await runVerifyScriptUseCase({
|
|
17488
18144
|
cwd: opts.cwd,
|
|
17489
18145
|
phase: "post",
|
|
@@ -17599,9 +18255,11 @@ var postTaskVerifyLeaf = (deps, opts, taskId) => leaf(`post-task-verify-${String
|
|
|
17599
18255
|
message: `post-task-verify-${String(taskId)}: expected in_progress task \u2014 got '${ctx.currentTask.status}'`
|
|
17600
18256
|
});
|
|
17601
18257
|
}
|
|
18258
|
+
const preVerifyBlockedZeroTurn = ctx.lastBlockReason !== void 0 && ctx.genEvalTurn === void 0;
|
|
17602
18259
|
return {
|
|
17603
18260
|
task: ctx.currentTask,
|
|
17604
18261
|
sprintId: ctx.sprintId,
|
|
18262
|
+
preVerifyBlockedZeroTurn,
|
|
17605
18263
|
...ctx.lastPreVerifyOutcome !== void 0 ? { preOutcome: ctx.lastPreVerifyOutcome } : {}
|
|
17606
18264
|
};
|
|
17607
18265
|
},
|
|
@@ -17627,6 +18285,148 @@ var postTaskVerifyLeaf = (deps, opts, taskId) => leaf(`post-task-verify-${String
|
|
|
17627
18285
|
}
|
|
17628
18286
|
});
|
|
17629
18287
|
|
|
18288
|
+
// src/business/task/record-quarantine.ts
|
|
18289
|
+
var QUARANTINE_LINE_PREFIX = "Rejected diff quarantined to git stash";
|
|
18290
|
+
var buildReason = (current, stashMessage) => {
|
|
18291
|
+
const line = `${QUARANTINE_LINE_PREFIX} (recover via \`git stash list\`): ${stashMessage}`;
|
|
18292
|
+
if (current.includes(line)) return current;
|
|
18293
|
+
return `${current}
|
|
18294
|
+
${line}`;
|
|
18295
|
+
};
|
|
18296
|
+
var recordQuarantineUseCase = async (props) => {
|
|
18297
|
+
const log = props.logger.named("task.record-quarantine");
|
|
18298
|
+
if (props.task.status !== "blocked") {
|
|
18299
|
+
return Result.error(
|
|
18300
|
+
new InvalidStateError({
|
|
18301
|
+
entity: "task",
|
|
18302
|
+
currentState: props.task.status,
|
|
18303
|
+
attemptedAction: "record-quarantine",
|
|
18304
|
+
message: `record-quarantine: expected a blocked task \u2014 got '${props.task.status}'`
|
|
18305
|
+
})
|
|
18306
|
+
);
|
|
18307
|
+
}
|
|
18308
|
+
const nextReason = buildReason(props.task.blockedReason, props.stashMessage);
|
|
18309
|
+
if (nextReason === props.task.blockedReason) {
|
|
18310
|
+
log.debug("quarantine pointer already recorded; skipping re-write", {
|
|
18311
|
+
taskId: props.task.id,
|
|
18312
|
+
sprintId: props.sprintId
|
|
18313
|
+
});
|
|
18314
|
+
return Result.ok(props.task);
|
|
18315
|
+
}
|
|
18316
|
+
const updated = { ...props.task, blockedReason: nextReason };
|
|
18317
|
+
const persisted = await props.taskRepo.update(props.sprintId, updated);
|
|
18318
|
+
if (!persisted.ok) {
|
|
18319
|
+
log.error("persist failed", { taskId: updated.id, error: persisted.error.message });
|
|
18320
|
+
return Result.error(persisted.error);
|
|
18321
|
+
}
|
|
18322
|
+
log.info("recorded quarantine pointer on blocked task", {
|
|
18323
|
+
taskId: updated.id,
|
|
18324
|
+
sprintId: props.sprintId,
|
|
18325
|
+
stashMessage: props.stashMessage
|
|
18326
|
+
});
|
|
18327
|
+
return Result.ok(updated);
|
|
18328
|
+
};
|
|
18329
|
+
|
|
18330
|
+
// src/application/flows/implement/leaves/quarantine-blocked-diff.ts
|
|
18331
|
+
var quarantineStashMessage = (sprintId, taskId) => `ralphctl/${String(sprintId)}/${String(taskId)}/blocked-diff`;
|
|
18332
|
+
var quarantineBlockedDiffLeaf = (deps, opts, taskId) => leaf(`quarantine-blocked-diff-${String(taskId)}`, {
|
|
18333
|
+
useCase: {
|
|
18334
|
+
execute: async (input) => {
|
|
18335
|
+
const log = deps.logger.named("task.quarantine-blocked-diff");
|
|
18336
|
+
const message = quarantineStashMessage(input.sprintId, taskId);
|
|
18337
|
+
const stashed = await gitStashPush(deps.gitRunner, opts.cwd, message);
|
|
18338
|
+
if (!stashed.ok) {
|
|
18339
|
+
log.warn("quarantine stash failed \u2014 proceeding without cleaning the tree", {
|
|
18340
|
+
taskId: String(taskId),
|
|
18341
|
+
cwd: String(opts.cwd),
|
|
18342
|
+
error: stashed.error.message
|
|
18343
|
+
});
|
|
18344
|
+
return Result.ok(void 0);
|
|
18345
|
+
}
|
|
18346
|
+
if (!stashed.value.stashed) return Result.ok(void 0);
|
|
18347
|
+
const recorded = await recordQuarantineUseCase({
|
|
18348
|
+
task: input.task,
|
|
18349
|
+
sprintId: input.sprintId,
|
|
18350
|
+
stashMessage: message,
|
|
18351
|
+
taskRepo: deps.taskRepo,
|
|
18352
|
+
logger: deps.logger
|
|
18353
|
+
});
|
|
18354
|
+
if (!recorded.ok) {
|
|
18355
|
+
log.warn("quarantine stash succeeded but recording the pointer failed", {
|
|
18356
|
+
taskId: String(taskId),
|
|
18357
|
+
stashMessage: message,
|
|
18358
|
+
error: recorded.error.message
|
|
18359
|
+
});
|
|
18360
|
+
return Result.ok(void 0);
|
|
18361
|
+
}
|
|
18362
|
+
const journalLine = `
|
|
18363
|
+
_Task ${recorded.value.name}: rejected diff quarantined to git stash \u2014 recover via \`git stash list\` (message: \`${message}\`)._
|
|
18364
|
+
`;
|
|
18365
|
+
const appended = await deps.appendFile(opts.progressFile, journalLine);
|
|
18366
|
+
if (!appended.ok) {
|
|
18367
|
+
log.warn("quarantine pointer journal append failed", {
|
|
18368
|
+
taskId: String(taskId),
|
|
18369
|
+
error: appended.error.message
|
|
18370
|
+
});
|
|
18371
|
+
}
|
|
18372
|
+
return Result.ok(recorded.value);
|
|
18373
|
+
}
|
|
18374
|
+
},
|
|
18375
|
+
input: (ctx) => {
|
|
18376
|
+
const task = ctx.tasks?.find((t) => t.id === taskId);
|
|
18377
|
+
if (task === void 0 || task.status !== "blocked") {
|
|
18378
|
+
throw new InvalidStateError({
|
|
18379
|
+
entity: "chain",
|
|
18380
|
+
currentState: task === void 0 ? "missing" : task.status,
|
|
18381
|
+
attemptedAction: `quarantine-blocked-diff-${String(taskId)}`,
|
|
18382
|
+
message: `quarantine-blocked-diff-${String(taskId)}: expected a settled blocked task on ctx.tasks`
|
|
18383
|
+
});
|
|
18384
|
+
}
|
|
18385
|
+
return { task, sprintId: ctx.sprintId };
|
|
18386
|
+
},
|
|
18387
|
+
output: (ctx, out) => {
|
|
18388
|
+
if (out === void 0) return ctx;
|
|
18389
|
+
return {
|
|
18390
|
+
...ctx,
|
|
18391
|
+
tasks: (ctx.tasks ?? []).map((t) => t.id === out.id ? out : t)
|
|
18392
|
+
};
|
|
18393
|
+
}
|
|
18394
|
+
});
|
|
18395
|
+
var isSettledBlocked = (ctx, taskId) => ctx.tasks?.find((t) => t.id === taskId)?.status === "blocked" && ctx.genEvalTurn !== void 0;
|
|
18396
|
+
|
|
18397
|
+
// src/application/flows/implement/leaves/quarantine-retry-diff.ts
|
|
18398
|
+
var retryStashMessage = (sprintId, taskId, attemptN) => `ralphctl/${String(sprintId)}/${String(taskId)}/attempt-${String(attemptN)}-rejected-diff`;
|
|
18399
|
+
var quarantineRetryDiffLeaf = (deps, opts, taskId) => leaf(`quarantine-retry-diff-${String(taskId)}`, {
|
|
18400
|
+
useCase: {
|
|
18401
|
+
execute: async (input) => {
|
|
18402
|
+
const log = deps.logger.named("task.quarantine-retry-diff");
|
|
18403
|
+
const message = retryStashMessage(input.sprintId, taskId, input.attemptN);
|
|
18404
|
+
const stashed = await gitStashPush(deps.gitRunner, opts.cwd, message);
|
|
18405
|
+
if (!stashed.ok) {
|
|
18406
|
+
log.warn("retry-diff stash failed \u2014 the retried attempt will see the red tree", {
|
|
18407
|
+
taskId: String(taskId),
|
|
18408
|
+
cwd: String(opts.cwd),
|
|
18409
|
+
error: stashed.error.message
|
|
18410
|
+
});
|
|
18411
|
+
return Result.ok(void 0);
|
|
18412
|
+
}
|
|
18413
|
+
if (stashed.value.stashed) {
|
|
18414
|
+
log.info("rejected red diff quarantined before retry", {
|
|
18415
|
+
taskId: String(taskId),
|
|
18416
|
+
stashMessage: message
|
|
18417
|
+
});
|
|
18418
|
+
}
|
|
18419
|
+
return Result.ok(void 0);
|
|
18420
|
+
}
|
|
18421
|
+
},
|
|
18422
|
+
input: (ctx) => ({
|
|
18423
|
+
sprintId: ctx.sprintId,
|
|
18424
|
+
attemptN: ctx.currentTask?.attempts.length ?? 0
|
|
18425
|
+
}),
|
|
18426
|
+
output: (ctx) => ctx
|
|
18427
|
+
});
|
|
18428
|
+
var isRedVerifyRetry = (ctx) => ctx.lastShouldFailAttempt === true && ctx.lastBlockReason !== void 0;
|
|
18429
|
+
|
|
17630
18430
|
// src/application/flows/implement/leaves/progress-journal.ts
|
|
17631
18431
|
var dedupeTexts = (texts) => {
|
|
17632
18432
|
if (texts === void 0 || texts.length === 0) return [];
|
|
@@ -17646,14 +18446,43 @@ var renderOutcomeParagraph = (task, attempt) => {
|
|
|
17646
18446
|
return task.blockedReason.trim().length > 0 ? `Blocked: ${task.blockedReason.trim()}` : "Blocked \u2014 no reason recorded.";
|
|
17647
18447
|
}
|
|
17648
18448
|
if (task.status === "done") {
|
|
18449
|
+
if (attempt?.evaluation?.status === "passed") {
|
|
18450
|
+
return attempt.warning !== void 0 ? "Task settled as done with a warning attached." : "Task completed successfully.";
|
|
18451
|
+
}
|
|
17649
18452
|
const critique = attempt?.critique?.trim();
|
|
17650
18453
|
if (critique !== void 0 && critique.length > 0) {
|
|
17651
18454
|
return critique;
|
|
17652
18455
|
}
|
|
17653
|
-
return "Task completed successfully.";
|
|
18456
|
+
return attempt?.warning !== void 0 ? "Task settled as done with a warning attached." : "Task completed successfully.";
|
|
18457
|
+
}
|
|
18458
|
+
if (task.status === "in_progress") {
|
|
18459
|
+
const critique = attempt?.critique?.trim();
|
|
18460
|
+
return critique !== void 0 && critique.length > 0 ? `Attempt did not pass; the harness is retrying. ${critique}` : "Attempt did not pass; the harness is retrying.";
|
|
17654
18461
|
}
|
|
17655
18462
|
return `Settled with task status \`${task.status}\`.`;
|
|
17656
18463
|
};
|
|
18464
|
+
var deriveVerdict = (task, attempt) => {
|
|
18465
|
+
if (task.status === "blocked") return "blocked";
|
|
18466
|
+
if (task.status === "in_progress") return "escalated";
|
|
18467
|
+
if (task.status === "done" && attempt?.warning !== void 0) return "pass-with-warning";
|
|
18468
|
+
return "pass";
|
|
18469
|
+
};
|
|
18470
|
+
var toJournalWarning = (warning) => {
|
|
18471
|
+
switch (warning.kind) {
|
|
18472
|
+
case "budget-exhausted":
|
|
18473
|
+
return { kind: "budget-exhausted", turnsUsed: warning.turnsUsed, turnBudget: warning.turnBudget };
|
|
18474
|
+
case "plateau":
|
|
18475
|
+
return { kind: "plateau", dimensions: warning.dimensions };
|
|
18476
|
+
case "malformed":
|
|
18477
|
+
return { kind: "malformed", detail: warning.detail };
|
|
18478
|
+
case "verify-failed": {
|
|
18479
|
+
const exit = warning.exitCode !== null ? `exit ${String(warning.exitCode)}` : "no exit code";
|
|
18480
|
+
const detail = warning.stderr.trim().length > 0 ? `${exit} \u2014 ${warning.stderr.trim()}` : exit;
|
|
18481
|
+
return { kind: "verify-failed", detail };
|
|
18482
|
+
}
|
|
18483
|
+
}
|
|
18484
|
+
};
|
|
18485
|
+
var toJournalEscalation = (task) => task.escalatedFromModel !== void 0 && task.escalatedToModel !== void 0 ? { from: task.escalatedFromModel, to: task.escalatedToModel } : void 0;
|
|
17657
18486
|
var latestAttempt = (task) => task.attempts[task.attempts.length - 1];
|
|
17658
18487
|
var attemptDurationMs = (attempt) => {
|
|
17659
18488
|
if (attempt === void 0 || attempt.status === "running") return void 0;
|
|
@@ -17663,7 +18492,9 @@ var progressJournalLeaf = (deps, opts, taskId) => leaf(`progress-journal-${Strin
|
|
|
17663
18492
|
useCase: {
|
|
17664
18493
|
execute: async (input) => {
|
|
17665
18494
|
const attempt = latestAttempt(input.task);
|
|
17666
|
-
const verdict = input.task
|
|
18495
|
+
const verdict = deriveVerdict(input.task, attempt);
|
|
18496
|
+
const warning = attempt?.warning !== void 0 ? toJournalWarning(attempt.warning) : void 0;
|
|
18497
|
+
const escalation = (verdict === "escalated" || verdict === "pass-with-warning") && attempt?.warning?.kind !== "malformed" ? toJournalEscalation(input.task) : void 0;
|
|
17667
18498
|
const text = renderJournalEntry({
|
|
17668
18499
|
taskName: input.task.name,
|
|
17669
18500
|
attemptN: input.task.attempts.length,
|
|
@@ -17672,6 +18503,8 @@ var progressJournalLeaf = (deps, opts, taskId) => leaf(`progress-journal-${Strin
|
|
|
17672
18503
|
roundN: input.roundN,
|
|
17673
18504
|
totalRounds: opts.totalRounds,
|
|
17674
18505
|
...attemptDurationMs(attempt) !== void 0 ? { durationMs: attemptDurationMs(attempt) } : {},
|
|
18506
|
+
...warning !== void 0 ? { warning } : {},
|
|
18507
|
+
...escalation !== void 0 ? { escalation } : {},
|
|
17675
18508
|
changes: input.changes,
|
|
17676
18509
|
decisions: input.decisions,
|
|
17677
18510
|
learnings: input.learnings,
|
|
@@ -17732,6 +18565,9 @@ var settleTask = (props, now) => {
|
|
|
17732
18565
|
if (!stamped.ok) return Result.error(stamped.error);
|
|
17733
18566
|
task = stamped.value;
|
|
17734
18567
|
}
|
|
18568
|
+
if (props.shouldFailAttempt === true) {
|
|
18569
|
+
return failCurrentAttempt(task, now, props.verdict === "malformed" ? "malformed" : "failed");
|
|
18570
|
+
}
|
|
17735
18571
|
if (props.blockedReason !== void 0) {
|
|
17736
18572
|
const aborted = failCurrentAttempt(task, now, "aborted");
|
|
17737
18573
|
if (!aborted.ok) return Result.error(aborted.error);
|
|
@@ -17740,9 +18576,6 @@ var settleTask = (props, now) => {
|
|
|
17740
18576
|
}
|
|
17741
18577
|
return markTaskBlocked(aborted.value, props.blockedReason, "own");
|
|
17742
18578
|
}
|
|
17743
|
-
if (props.shouldFailAttempt === true) {
|
|
17744
|
-
return failCurrentAttempt(task, now, "failed");
|
|
17745
|
-
}
|
|
17746
18579
|
return markTaskDone(task, now);
|
|
17747
18580
|
};
|
|
17748
18581
|
var settleAttemptUseCase = async (props) => {
|
|
@@ -18262,6 +19095,7 @@ var createPerTaskSubchain = (deps, opts, task, repo, readConfig) => {
|
|
|
18262
19095
|
templateLoader: deps.templateLoader,
|
|
18263
19096
|
signals: deps.signals,
|
|
18264
19097
|
writeFile: deps.writeFile,
|
|
19098
|
+
gitRunner: deps.gitRunner,
|
|
18265
19099
|
clock: deps.clock,
|
|
18266
19100
|
logger: deps.logger,
|
|
18267
19101
|
eventBus: deps.eventBus,
|
|
@@ -18325,6 +19159,17 @@ var createPerTaskSubchain = (deps, opts, task, repo, readConfig) => {
|
|
|
18325
19159
|
taskId
|
|
18326
19160
|
)
|
|
18327
19161
|
),
|
|
19162
|
+
// Composed-case cleanup: a granted retry (escalate / nudge / malformed) whose work ALSO
|
|
19163
|
+
// failed post-verify red. The commit guard above skipped the red work; this stashes it
|
|
19164
|
+
// so the RETRIED attempt's pre-verify starts from the last good commit instead of
|
|
19165
|
+
// hard-blocking on its own predecessor's rejected diff. Must run BEFORE settle-attempt —
|
|
19166
|
+
// settle's output projection clears both flags the guard reads. Green-verify retries
|
|
19167
|
+
// commit normally and the stash no-ops on their clean tree.
|
|
19168
|
+
guard(
|
|
19169
|
+
`quarantine-retry-diff-guard-${String(taskId)}`,
|
|
19170
|
+
isRedVerifyRetry,
|
|
19171
|
+
quarantineRetryDiffLeaf({ gitRunner: deps.gitRunner, logger: deps.logger }, { cwd: repo.path }, taskId)
|
|
19172
|
+
),
|
|
18328
19173
|
settleAttemptLeaf(
|
|
18329
19174
|
{ taskRepo: deps.taskRepo, clock: deps.clock, logger: deps.logger, gitRunner: deps.gitRunner },
|
|
18330
19175
|
{ cwd: repo.path },
|
|
@@ -18349,15 +19194,46 @@ var createPerTaskSubchain = (deps, opts, task, repo, readConfig) => {
|
|
|
18349
19194
|
)
|
|
18350
19195
|
]),
|
|
18351
19196
|
{
|
|
18352
|
-
// The attempt count is bounded by the task's own `maxAttempts` (validated 1–10)
|
|
18353
|
-
//
|
|
18354
|
-
//
|
|
18355
|
-
//
|
|
18356
|
-
//
|
|
18357
|
-
|
|
19197
|
+
// The attempt count is bounded by the task's own `maxAttempts` (validated 1–10), or the
|
|
19198
|
+
// configured `settings.harness.maxAttempts` fallback for legacy tasks planned before the
|
|
19199
|
+
// field existed (mirrors the budget fallback in `finalize-gen-eval`/`decideEscalation`,
|
|
19200
|
+
// so a legacy task's loop cap and its escalation budget agree). The domain's
|
|
19201
|
+
// `failCurrentAttempt` still transitions the task to `blocked` once attempts hit the
|
|
19202
|
+
// cap, so a budget-exhausted task is never silently dropped — `shouldStop` just
|
|
19203
|
+
// recognises that terminal status and exits.
|
|
19204
|
+
maxIterations: task.maxAttempts ?? deps.config.harness.maxAttempts,
|
|
18358
19205
|
shouldStop: (ctx) => terminalTaskStatus(ctx, taskId)
|
|
18359
19206
|
}
|
|
18360
19207
|
),
|
|
19208
|
+
// SERIAL-PATH ONLY. A task that settled `blocked` (self-block / budget-exhausted) leaves its
|
|
19209
|
+
// rejected diff in the SHARED worktree — `settle-attempt`'s dirty-tree guardrail exempts the
|
|
19210
|
+
// block path so the operator can inspect it. On the serial path that contaminates the next
|
|
19211
|
+
// task (its `git add -A` sweeps the leftovers into its commit; the dirt flips its pre-verify
|
|
19212
|
+
// red and the red post-verify is mis-attributed `baseline-broken`, landing a corrupt commit).
|
|
19213
|
+
// This guarded leaf stashes the rejected diff so the tree is clean again before the next task
|
|
19214
|
+
// runs, restoring the invariant the prologue's one-shot preflight assumes. The guard is
|
|
19215
|
+
// synchronous (status === 'blocked' read from the settled `ctx.tasks` copy — `settle-attempt`
|
|
19216
|
+
// cleared `ctx.currentTask`); the splice itself is gated on the serial-path proxy
|
|
19217
|
+
// `includeBranchPreflight` so the parallel launcher (per-task worktrees, already isolated)
|
|
19218
|
+
// never includes it. Stays INSIDE the body guard + AFTER the loop so it runs once per task,
|
|
19219
|
+
// and BEFORE `uninstall-skills` so that leaf remains the subchain's terminal element (the
|
|
19220
|
+
// TUI's task-completion detector keys on it).
|
|
19221
|
+
...includeBranchPreflight ? [
|
|
19222
|
+
guard(
|
|
19223
|
+
`quarantine-blocked-diff-guard-${String(taskId)}`,
|
|
19224
|
+
(ctx) => isSettledBlocked(ctx, taskId),
|
|
19225
|
+
quarantineBlockedDiffLeaf(
|
|
19226
|
+
{
|
|
19227
|
+
gitRunner: deps.gitRunner,
|
|
19228
|
+
taskRepo: deps.taskRepo,
|
|
19229
|
+
appendFile: deps.appendFile,
|
|
19230
|
+
logger: deps.logger
|
|
19231
|
+
},
|
|
19232
|
+
{ cwd: repo.path, progressFile: opts.progressFile },
|
|
19233
|
+
taskId
|
|
19234
|
+
)
|
|
19235
|
+
)
|
|
19236
|
+
] : [],
|
|
18361
19237
|
uninstallSkillsLeaf(
|
|
18362
19238
|
{ skillsAdapter: deps.skillsAdapter },
|
|
18363
19239
|
{ name: `${opts.terminalLeafName}-${String(taskId)}`, cwdPicker: repoCwdPicker(repo.path) }
|
|
@@ -19060,10 +19936,10 @@ var transitionSprintToReviewLeaf = (deps) => leaf("transition-sprint-to-review",
|
|
|
19060
19936
|
var combineAbortSignals = (host, secondary) => host === void 0 ? secondary : AbortSignal.any([host, secondary]);
|
|
19061
19937
|
|
|
19062
19938
|
// src/integration/io/lock-paths.ts
|
|
19063
|
-
import { createHash as
|
|
19939
|
+
import { createHash as createHash3 } from "crypto";
|
|
19064
19940
|
import { join as join37 } from "path";
|
|
19065
19941
|
var repoLockFile = (locksRoot, worktreePath) => {
|
|
19066
|
-
const hash =
|
|
19942
|
+
const hash = createHash3("sha1").update(String(worktreePath)).digest("hex").slice(0, 16);
|
|
19067
19943
|
return AbsolutePath.parse(join37(String(locksRoot), `repo-${hash}.lock`));
|
|
19068
19944
|
};
|
|
19069
19945
|
|
|
@@ -19218,7 +20094,8 @@ var createImplementFlow = (deps, opts) => {
|
|
|
19218
20094
|
const readConfig = () => Promise.resolve({
|
|
19219
20095
|
maxTurns: deps.config.harness.maxTurns,
|
|
19220
20096
|
escalateOnPlateau: deps.config.harness.escalateOnPlateau,
|
|
19221
|
-
escalationMap: deps.config.harness.escalationMap
|
|
20097
|
+
escalationMap: deps.config.harness.escalationMap,
|
|
20098
|
+
maxAttempts: deps.config.harness.maxAttempts
|
|
19222
20099
|
});
|
|
19223
20100
|
const perTaskChains = opts.todoTasks.map(
|
|
19224
20101
|
(task) => createPerTaskSubchain(
|
|
@@ -19372,7 +20249,11 @@ var runWorktreeSetupScript = async (deps, worktreePath, setupScript, taskId, ctx
|
|
|
19372
20249
|
const name = `worktree-setup-script-${String(taskId)}`;
|
|
19373
20250
|
if (signal?.aborted) return abortedStep(name, 0, onTrace);
|
|
19374
20251
|
const start = performance.now();
|
|
19375
|
-
const ran = await deps.implement.shellScriptRunner.run(
|
|
20252
|
+
const ran = await deps.implement.shellScriptRunner.run(
|
|
20253
|
+
worktreePath,
|
|
20254
|
+
setupScript,
|
|
20255
|
+
signal !== void 0 ? { signal } : {}
|
|
20256
|
+
);
|
|
19376
20257
|
const durationMs = performance.now() - start;
|
|
19377
20258
|
if (ran.ok && ran.value.passed) {
|
|
19378
20259
|
onTrace?.({ elementName: name, status: "completed", durationMs });
|
|
@@ -19811,7 +20692,8 @@ var buildParallelElement = (implementDeps, implementOpts, appSignals, maxParalle
|
|
|
19811
20692
|
const readConfig = () => Promise.resolve({
|
|
19812
20693
|
maxTurns: implementDeps.config.harness.maxTurns,
|
|
19813
20694
|
escalateOnPlateau: implementDeps.config.harness.escalateOnPlateau,
|
|
19814
|
-
escalationMap: implementDeps.config.harness.escalationMap
|
|
20695
|
+
escalationMap: implementDeps.config.harness.escalationMap,
|
|
20696
|
+
maxAttempts: implementDeps.config.harness.maxAttempts
|
|
19815
20697
|
});
|
|
19816
20698
|
const parallelDeps = { ...implementDeps, appendFile: serializeAppendFile(implementDeps.appendFile) };
|
|
19817
20699
|
const branchDeps = {
|
|
@@ -20179,7 +21061,7 @@ var runReviewRoundUseCase = async (props) => {
|
|
|
20179
21061
|
};
|
|
20180
21062
|
|
|
20181
21063
|
// src/integration/ai/prompts/apply-feedback/definition.ts
|
|
20182
|
-
var
|
|
21064
|
+
var requireNonEmpty5 = (field, message) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message })) : Result.ok(v);
|
|
20183
21065
|
var applyFeedbackPromptDef = {
|
|
20184
21066
|
templateName: "apply-feedback",
|
|
20185
21067
|
description: "Apply one round of human feedback to an already-implemented sprint. Review-time work, not initial implementation.",
|
|
@@ -20187,12 +21069,12 @@ var applyFeedbackPromptDef = {
|
|
|
20187
21069
|
repositories: {
|
|
20188
21070
|
placeholder: "REPOSITORIES",
|
|
20189
21071
|
description: "Markdown list of every sprint-affected repository (absolute path + display name). The AI picks which to touch based on the latest round.",
|
|
20190
|
-
validate:
|
|
21072
|
+
validate: requireNonEmpty5("repositories", "repositories block must not be empty")
|
|
20191
21073
|
},
|
|
20192
21074
|
sprintContext: {
|
|
20193
21075
|
placeholder: "SPRINT_CONTEXT",
|
|
20194
21076
|
description: "Sprint metadata block (slug, name, ticket count).",
|
|
20195
|
-
validate:
|
|
21077
|
+
validate: requireNonEmpty5("sprintContext", "sprint context must not be empty")
|
|
20196
21078
|
},
|
|
20197
21079
|
feedbackLog: {
|
|
20198
21080
|
placeholder: "FEEDBACK_LOG",
|
|
@@ -20201,7 +21083,7 @@ var applyFeedbackPromptDef = {
|
|
|
20201
21083
|
latestRound: {
|
|
20202
21084
|
placeholder: "LATEST_ROUND",
|
|
20203
21085
|
description: "The round body the AI must act on now.",
|
|
20204
|
-
validate:
|
|
21086
|
+
validate: requireNonEmpty5("latestRound", "latest round body must not be empty")
|
|
20205
21087
|
},
|
|
20206
21088
|
progress: {
|
|
20207
21089
|
placeholder: "PROGRESS",
|
|
@@ -20210,7 +21092,7 @@ var applyFeedbackPromptDef = {
|
|
|
20210
21092
|
outputContractSection: {
|
|
20211
21093
|
placeholder: "OUTPUT_CONTRACT_SECTION",
|
|
20212
21094
|
description: "Audit-[09] output contract block rendered from the review-round contract \u2014 instructs the AI to write `signals.json` directly with exactly one terminal signal.",
|
|
20213
|
-
validate:
|
|
21095
|
+
validate: requireNonEmpty5("outputContractSection", "output-contract section must not be empty")
|
|
20214
21096
|
}
|
|
20215
21097
|
},
|
|
20216
21098
|
partials: {
|
|
@@ -20623,7 +21505,7 @@ import { promises as fs28 } from "fs";
|
|
|
20623
21505
|
import { join as join42 } from "path";
|
|
20624
21506
|
|
|
20625
21507
|
// src/integration/ai/prompts/distill-learnings/definition.ts
|
|
20626
|
-
var
|
|
21508
|
+
var requireNonEmpty6 = (field, message) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message })) : Result.ok(v);
|
|
20627
21509
|
var distillLearningsPromptDef = {
|
|
20628
21510
|
templateName: "distill-learnings",
|
|
20629
21511
|
description: "One-shot documentation edit that folds curated learnings into an existing project context file\u2019s own idempotent `## Learnings (ralphctl)` section.",
|
|
@@ -20635,12 +21517,12 @@ var distillLearningsPromptDef = {
|
|
|
20635
21517
|
candidateLearnings: {
|
|
20636
21518
|
placeholder: "CANDIDATE_LEARNINGS",
|
|
20637
21519
|
description: "Markdown list of the curated learnings to fold into the context file.",
|
|
20638
|
-
validate:
|
|
21520
|
+
validate: requireNonEmpty6("candidateLearnings", "candidate learnings must not be empty")
|
|
20639
21521
|
},
|
|
20640
21522
|
targetFilename: {
|
|
20641
21523
|
placeholder: "TARGET_FILENAME",
|
|
20642
21524
|
description: "Native context-file name for the target provider (CLAUDE.md / .github/copilot-instructions.md / AGENTS.md).",
|
|
20643
|
-
validate:
|
|
21525
|
+
validate: requireNonEmpty6("targetFilename", "target filename must not be empty")
|
|
20644
21526
|
},
|
|
20645
21527
|
projectTooling: {
|
|
20646
21528
|
placeholder: "PROJECT_TOOLING",
|
|
@@ -22554,7 +23436,7 @@ var launchReadiness = async (ctx) => {
|
|
|
22554
23436
|
import { join as join48 } from "path";
|
|
22555
23437
|
|
|
22556
23438
|
// src/integration/ai/prompts/detect-skills/definition.ts
|
|
22557
|
-
var
|
|
23439
|
+
var requireNonEmpty7 = (field, message) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message })) : Result.ok(v);
|
|
22558
23440
|
var detectSkillsPromptDef = {
|
|
22559
23441
|
templateName: "detect-skills",
|
|
22560
23442
|
description: "Read-only repo inventory that authors two short coding-agent skills: setup (sprint-start prep) and verify (post-task gate interpretation).",
|
|
@@ -22562,17 +23444,17 @@ var detectSkillsPromptDef = {
|
|
|
22562
23444
|
repositoryPath: {
|
|
22563
23445
|
placeholder: "REPOSITORY_PATH",
|
|
22564
23446
|
description: "Absolute path to the repository the AI is inventorying.",
|
|
22565
|
-
validate:
|
|
23447
|
+
validate: requireNonEmpty7("repositoryPath", "repository path must not be empty")
|
|
22566
23448
|
},
|
|
22567
23449
|
skillsConvention: {
|
|
22568
23450
|
placeholder: "SKILLS_CONVENTION",
|
|
22569
23451
|
description: "Provider-specific guidance on where to find existing skills in this repository.",
|
|
22570
|
-
validate:
|
|
23452
|
+
validate: requireNonEmpty7("skillsConvention", "skills convention snippet must not be empty")
|
|
22571
23453
|
},
|
|
22572
23454
|
outputContractSection: {
|
|
22573
23455
|
placeholder: "OUTPUT_CONTRACT_SECTION",
|
|
22574
23456
|
description: "Audit-[09] output contract block rendered from the detect-skills contract \u2014 instructs the AI to write `signals.json` directly.",
|
|
22575
|
-
validate:
|
|
23457
|
+
validate: requireNonEmpty7("outputContractSection", "output-contract section must not be empty")
|
|
22576
23458
|
}
|
|
22577
23459
|
},
|
|
22578
23460
|
partials: {
|
|
@@ -23007,7 +23889,7 @@ var launchDetectSkills = (ctx) => {
|
|
|
23007
23889
|
import { join as join49 } from "path";
|
|
23008
23890
|
|
|
23009
23891
|
// src/integration/ai/prompts/detect-scripts/definition.ts
|
|
23010
|
-
var
|
|
23892
|
+
var requireNonEmpty8 = (field, message) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message })) : Result.ok(v);
|
|
23011
23893
|
var detectScriptsPromptDef = {
|
|
23012
23894
|
templateName: "detect-scripts",
|
|
23013
23895
|
description: "Read-only repo inventory that proposes a single-line setup script and a single-line verify script.",
|
|
@@ -23015,12 +23897,12 @@ var detectScriptsPromptDef = {
|
|
|
23015
23897
|
repositoryPath: {
|
|
23016
23898
|
placeholder: "REPOSITORY_PATH",
|
|
23017
23899
|
description: "Absolute path to the repository the AI is inventorying.",
|
|
23018
|
-
validate:
|
|
23900
|
+
validate: requireNonEmpty8("repositoryPath", "repository path must not be empty")
|
|
23019
23901
|
},
|
|
23020
23902
|
outputContractSection: {
|
|
23021
23903
|
placeholder: "OUTPUT_CONTRACT_SECTION",
|
|
23022
23904
|
description: "Audit-[09] output contract block rendered from the detect-scripts contract \u2014 instructs the AI to write `signals.json` directly.",
|
|
23023
|
-
validate:
|
|
23905
|
+
validate: requireNonEmpty8("outputContractSection", "output-contract section must not be empty")
|
|
23024
23906
|
}
|
|
23025
23907
|
},
|
|
23026
23908
|
partials: {
|
|
@@ -23652,7 +24534,8 @@ var parseIdeateOutput = (raw, ctx) => {
|
|
|
23652
24534
|
const tasks = parseTaskList(parsed.data.tasks, {
|
|
23653
24535
|
project: ctx.project,
|
|
23654
24536
|
mode: { kind: "fixed", ticketId: ctx.ticketId, ...ctx.ticket !== void 0 ? { ticket: ctx.ticket } : {} },
|
|
23655
|
-
...ctx.logger !== void 0 ? { logger: ctx.logger } : {}
|
|
24537
|
+
...ctx.logger !== void 0 ? { logger: ctx.logger } : {},
|
|
24538
|
+
...ctx.defaultMaxAttempts !== void 0 ? { defaultMaxAttempts: ctx.defaultMaxAttempts } : {}
|
|
23656
24539
|
});
|
|
23657
24540
|
if (!tasks.ok) return Result.error(tasks.error);
|
|
23658
24541
|
void ctx.sprintId;
|
|
@@ -23707,7 +24590,8 @@ var ideateAndPlanLeaf = (deps) => leaf("ideate-and-plan", {
|
|
|
23707
24590
|
// Pass the freshly minted ticket so any `externalRef` on it (currently unused by the
|
|
23708
24591
|
// ideate input surface, but ready when added) propagates to every generated task.
|
|
23709
24592
|
ticket: pending.value,
|
|
23710
|
-
logger: deps.logger
|
|
24593
|
+
logger: deps.logger,
|
|
24594
|
+
defaultMaxAttempts: deps.maxAttempts
|
|
23711
24595
|
});
|
|
23712
24596
|
if (!parsed.ok) return Result.error(parsed.error);
|
|
23713
24597
|
const added = addApprovedTicketUseCase({
|
|
@@ -23874,6 +24758,7 @@ var createIdeateFlow = (deps, opts) => {
|
|
|
23874
24758
|
writeFile: deps.writeFile,
|
|
23875
24759
|
eventBus: deps.eventBus,
|
|
23876
24760
|
model: opts.model,
|
|
24761
|
+
maxAttempts: opts.maxAttempts,
|
|
23877
24762
|
...opts.effort !== void 0 ? { effort: opts.effort } : {}
|
|
23878
24763
|
}),
|
|
23879
24764
|
uninstallSkillsLeaf({ skillsAdapter: deps.skillsAdapter }, { cwdPicker: () => opts.cwd }),
|
|
@@ -23922,6 +24807,7 @@ var launchIdeate = async (ctx) => {
|
|
|
23922
24807
|
cwd,
|
|
23923
24808
|
providerId: settings.ai.ideate.provider,
|
|
23924
24809
|
model: settings.ai.ideate.model,
|
|
24810
|
+
maxAttempts: settings.harness.maxAttempts,
|
|
23925
24811
|
...effort !== void 0 ? { effort } : {},
|
|
23926
24812
|
ideateRoot: ideateRoot.value
|
|
23927
24813
|
}
|
|
@@ -24811,18 +25697,9 @@ var settingsManifest = {
|
|
|
24811
25697
|
triggers: {}
|
|
24812
25698
|
};
|
|
24813
25699
|
|
|
24814
|
-
// src/application/flows/ticket
|
|
24815
|
-
var ticketAddManifest = {
|
|
24816
|
-
id: "ticket-add",
|
|
24817
|
-
title: "Add ticket",
|
|
24818
|
-
description: "Append a pending ticket to a draft sprint.",
|
|
24819
|
-
canBackground: false,
|
|
24820
|
-
triggers: { currentSprintStatus: ["draft"] }
|
|
24821
|
-
};
|
|
24822
|
-
|
|
24823
|
-
// src/application/flows/ticket-remove/manifest.ts
|
|
25700
|
+
// src/application/flows/remove-ticket/manifest.ts
|
|
24824
25701
|
var ticketRemoveManifest = {
|
|
24825
|
-
id: "ticket
|
|
25702
|
+
id: "remove-ticket",
|
|
24826
25703
|
title: "Remove ticket",
|
|
24827
25704
|
description: "Drop a ticket from a draft sprint.",
|
|
24828
25705
|
canBackground: false,
|
|
@@ -24847,7 +25724,6 @@ var flowRegistry = [
|
|
|
24847
25724
|
{ manifest: createPrManifest },
|
|
24848
25725
|
{ manifest: doctorManifest },
|
|
24849
25726
|
{ manifest: settingsManifest },
|
|
24850
|
-
{ manifest: ticketAddManifest },
|
|
24851
25727
|
{ manifest: ticketRemoveManifest }
|
|
24852
25728
|
];
|
|
24853
25729
|
|
|
@@ -25085,8 +25961,7 @@ var SPRINT_SCOPED_FLOW_IDS = [
|
|
|
25085
25961
|
"review",
|
|
25086
25962
|
"close-sprint",
|
|
25087
25963
|
"create-pr",
|
|
25088
|
-
"ticket
|
|
25089
|
-
"ticket-remove"
|
|
25964
|
+
"remove-ticket"
|
|
25090
25965
|
];
|
|
25091
25966
|
var PROJECT_SCOPED_FLOW_IDS = [
|
|
25092
25967
|
"create-sprint",
|
|
@@ -25101,9 +25976,9 @@ var SPRINT_SCOPED_SET = new Set(SPRINT_SCOPED_FLOW_IDS);
|
|
|
25101
25976
|
var PROJECT_SCOPED_SET = new Set(PROJECT_SCOPED_FLOW_IDS);
|
|
25102
25977
|
var HIDDEN_SET = new Set(HIDDEN_BY_DEFAULT_FLOW_IDS);
|
|
25103
25978
|
var ALLOWED_BY_STATUS = {
|
|
25104
|
-
draft: /* @__PURE__ */ new Set(["refine", "add-tickets", "plan", "
|
|
25105
|
-
planned: /* @__PURE__ */ new Set(["implement", "
|
|
25106
|
-
active: /* @__PURE__ */ new Set(["implement"
|
|
25979
|
+
draft: /* @__PURE__ */ new Set(["refine", "add-tickets", "plan", "remove-ticket"]),
|
|
25980
|
+
planned: /* @__PURE__ */ new Set(["implement", "remove-ticket"]),
|
|
25981
|
+
active: /* @__PURE__ */ new Set(["implement"]),
|
|
25107
25982
|
review: /* @__PURE__ */ new Set(["review", "close-sprint"]),
|
|
25108
25983
|
done: /* @__PURE__ */ new Set(["create-pr"])
|
|
25109
25984
|
};
|
|
@@ -25143,8 +26018,7 @@ var viewRouteFor = (flowId, snapshot) => {
|
|
|
25143
26018
|
return { id: "doctor" };
|
|
25144
26019
|
case "settings":
|
|
25145
26020
|
return { id: "settings" };
|
|
25146
|
-
case "ticket
|
|
25147
|
-
case "ticket-remove":
|
|
26021
|
+
case "remove-ticket":
|
|
25148
26022
|
return snapshot.sprint ? { id: "sprint-detail", props: { sprintId: snapshot.sprint.id } } : void 0;
|
|
25149
26023
|
case "export-context":
|
|
25150
26024
|
return { id: "export-context" };
|
|
@@ -26380,8 +27254,8 @@ var SprintsView = () => {
|
|
|
26380
27254
|
import { useEffect as useEffect22, useMemo as useMemo14, useState as useState31 } from "react";
|
|
26381
27255
|
import { Box as Box44, Text as Text45 } from "ink";
|
|
26382
27256
|
|
|
26383
|
-
// src/application/flows/ticket
|
|
26384
|
-
var createTicketRemoveFlow = (deps) => leaf("ticket
|
|
27257
|
+
// src/application/flows/remove-ticket/flow.ts
|
|
27258
|
+
var createTicketRemoveFlow = (deps) => leaf("remove-ticket", {
|
|
26385
27259
|
useCase: {
|
|
26386
27260
|
async execute(input) {
|
|
26387
27261
|
const sprint = await deps.sprintRepo.findById(input.sprintId);
|
|
@@ -26943,8 +27817,8 @@ var TaskCard = ({
|
|
|
26943
27817
|
expanded,
|
|
26944
27818
|
index
|
|
26945
27819
|
}) => {
|
|
26946
|
-
const
|
|
26947
|
-
const lastAttemptElapsed =
|
|
27820
|
+
const lastAttempt3 = task.attempts[task.attempts.length - 1];
|
|
27821
|
+
const lastAttemptElapsed = lastAttempt3 !== void 0 ? attemptElapsedMs(lastAttempt3) : void 0;
|
|
26948
27822
|
const { atLeast } = useBreakpoint();
|
|
26949
27823
|
const singleLineMetadata = atLeast("md");
|
|
26950
27824
|
const metadataParts = buildTaskMetadataParts({
|
|
@@ -28717,6 +29591,18 @@ var ExecuteBody = ({
|
|
|
28717
29591
|
// src/application/ui/tui/views/execute-view-internals/tasks-panel-host.tsx
|
|
28718
29592
|
import { useMemo as useMemo17 } from "react";
|
|
28719
29593
|
import { jsx as jsx71 } from "react/jsx-runtime";
|
|
29594
|
+
var warningSummaryFor = (w) => {
|
|
29595
|
+
switch (w.kind) {
|
|
29596
|
+
case "budget-exhausted":
|
|
29597
|
+
return `done with warning: turn budget exhausted (${String(w.turnsUsed)}/${String(w.turnBudget)} turns)`;
|
|
29598
|
+
case "plateau":
|
|
29599
|
+
return w.dimensions.length > 0 ? `done with warning: evaluator plateaued on ${w.dimensions.join(", ")}` : "done with warning: evaluator plateaued";
|
|
29600
|
+
case "malformed":
|
|
29601
|
+
return "done with warning: evaluator output malformed";
|
|
29602
|
+
case "verify-failed":
|
|
29603
|
+
return `done with warning: post-task verify red (${w.exitCode !== null ? `exit ${String(w.exitCode)}` : "no exit code"})`;
|
|
29604
|
+
}
|
|
29605
|
+
};
|
|
28720
29606
|
var TasksPanelHost = ({
|
|
28721
29607
|
bucketed,
|
|
28722
29608
|
descriptor,
|
|
@@ -28746,6 +29632,16 @@ var TasksPanelHost = ({
|
|
|
28746
29632
|
}
|
|
28747
29633
|
return m.size > 0 ? m : void 0;
|
|
28748
29634
|
}, [taskState]);
|
|
29635
|
+
const warningSummaryById = useMemo17(() => {
|
|
29636
|
+
if (taskState === void 0) return void 0;
|
|
29637
|
+
const m = /* @__PURE__ */ new Map();
|
|
29638
|
+
for (const t of taskState) {
|
|
29639
|
+
if (t.status !== "done") continue;
|
|
29640
|
+
const warning = t.attempts[t.attempts.length - 1]?.warning;
|
|
29641
|
+
if (warning !== void 0) m.set(String(t.id), warningSummaryFor(warning));
|
|
29642
|
+
}
|
|
29643
|
+
return m.size > 0 ? m : void 0;
|
|
29644
|
+
}, [taskState]);
|
|
28749
29645
|
if (bucketed === void 0) return null;
|
|
28750
29646
|
return /* @__PURE__ */ jsx71(
|
|
28751
29647
|
TasksPanel,
|
|
@@ -28759,7 +29655,8 @@ var TasksPanelHost = ({
|
|
|
28759
29655
|
...descriptor.taskNames !== void 0 ? { nameById: descriptor.taskNames } : {},
|
|
28760
29656
|
...descriptor.taskRecovering !== void 0 ? { recoveringByTaskId: descriptor.taskRecovering } : {},
|
|
28761
29657
|
...taskCriteriaById !== void 0 ? { taskCriteriaById } : {},
|
|
28762
|
-
...blockedReasonById !== void 0 ? { blockedReasonById } : {}
|
|
29658
|
+
...blockedReasonById !== void 0 ? { blockedReasonById } : {},
|
|
29659
|
+
...warningSummaryById !== void 0 ? { warningSummaryById } : {}
|
|
28763
29660
|
}
|
|
28764
29661
|
);
|
|
28765
29662
|
};
|
|
@@ -29184,9 +30081,9 @@ var flowIdToTitle = (flowId) => {
|
|
|
29184
30081
|
return "Close Sprint";
|
|
29185
30082
|
case "add-tickets":
|
|
29186
30083
|
return "Add Tickets";
|
|
29187
|
-
case "ticket
|
|
30084
|
+
case "add-ticket":
|
|
29188
30085
|
return "Add Ticket";
|
|
29189
|
-
case "ticket
|
|
30086
|
+
case "remove-ticket":
|
|
29190
30087
|
return "Remove Ticket";
|
|
29191
30088
|
case "export-context":
|
|
29192
30089
|
return "Export Context";
|
|
@@ -29496,7 +30393,16 @@ var createSettingsShowFlow = (deps) => leaf("settings-show", {
|
|
|
29496
30393
|
import "react";
|
|
29497
30394
|
|
|
29498
30395
|
// src/business/settings/presets.ts
|
|
29499
|
-
var PRESET_NAMES = [
|
|
30396
|
+
var PRESET_NAMES = [
|
|
30397
|
+
"mixed",
|
|
30398
|
+
"claude-only",
|
|
30399
|
+
"copilot-only",
|
|
30400
|
+
"codex-only",
|
|
30401
|
+
"mixed-economic",
|
|
30402
|
+
"claude-economic",
|
|
30403
|
+
"copilot-economic",
|
|
30404
|
+
"codex-economic"
|
|
30405
|
+
];
|
|
29500
30406
|
var isPresetName = (raw) => PRESET_NAMES.includes(raw);
|
|
29501
30407
|
var MIXED = {
|
|
29502
30408
|
effort: "high",
|
|
@@ -29527,13 +30433,13 @@ var CLAUDE_ONLY = {
|
|
|
29527
30433
|
var COPILOT_ONLY = {
|
|
29528
30434
|
effort: "high",
|
|
29529
30435
|
refine: { provider: "github-copilot", model: "claude-sonnet-4.6" },
|
|
29530
|
-
plan: { provider: "github-copilot", model: "claude-opus-4.
|
|
30436
|
+
plan: { provider: "github-copilot", model: "claude-opus-4.8", effort: "xhigh" },
|
|
29531
30437
|
implement: {
|
|
29532
|
-
generator: { provider: "github-copilot", model: "claude-opus-4.
|
|
29533
|
-
evaluator: { provider: "github-copilot", model: "claude-opus-4.
|
|
30438
|
+
generator: { provider: "github-copilot", model: "claude-opus-4.8", effort: "xhigh" },
|
|
30439
|
+
evaluator: { provider: "github-copilot", model: "claude-opus-4.8", effort: "xhigh" }
|
|
29534
30440
|
},
|
|
29535
30441
|
readiness: { provider: "github-copilot", model: "gpt-5-mini", effort: "medium" },
|
|
29536
|
-
ideate: { provider: "github-copilot", model: "claude-opus-4.
|
|
30442
|
+
ideate: { provider: "github-copilot", model: "claude-opus-4.8" },
|
|
29537
30443
|
createPr: { provider: "github-copilot", model: "gpt-5-mini" }
|
|
29538
30444
|
};
|
|
29539
30445
|
var CODEX_ONLY = {
|
|
@@ -29541,8 +30447,58 @@ var CODEX_ONLY = {
|
|
|
29541
30447
|
refine: { provider: "openai-codex", model: "gpt-5.4" },
|
|
29542
30448
|
plan: { provider: "openai-codex", model: "gpt-5.5", effort: "high" },
|
|
29543
30449
|
implement: {
|
|
29544
|
-
|
|
29545
|
-
|
|
30450
|
+
// gpt-5.3-codex is deprecated for ChatGPT sign-in — implement now rides the frontier
|
|
30451
|
+
// default so the everyday autonomous loop keeps working under ChatGPT auth.
|
|
30452
|
+
generator: { provider: "openai-codex", model: "gpt-5.5", effort: "high" },
|
|
30453
|
+
evaluator: { provider: "openai-codex", model: "gpt-5.5", effort: "high" }
|
|
30454
|
+
},
|
|
30455
|
+
readiness: { provider: "openai-codex", model: "gpt-5.4-mini", effort: "medium" },
|
|
30456
|
+
ideate: { provider: "openai-codex", model: "gpt-5.5" },
|
|
30457
|
+
createPr: { provider: "openai-codex", model: "gpt-5.4-mini" }
|
|
30458
|
+
};
|
|
30459
|
+
var MIXED_ECONOMIC = {
|
|
30460
|
+
effort: "high",
|
|
30461
|
+
refine: { provider: "openai-codex", model: "gpt-5.4-mini" },
|
|
30462
|
+
plan: { provider: "github-copilot", model: "claude-sonnet-4.6", effort: "high" },
|
|
30463
|
+
implement: {
|
|
30464
|
+
generator: { provider: "claude-code", model: "claude-sonnet-4-6", effort: "high" },
|
|
30465
|
+
evaluator: { provider: "claude-code", model: "claude-sonnet-4-6", effort: "high" }
|
|
30466
|
+
},
|
|
30467
|
+
readiness: { provider: "github-copilot", model: "gpt-5-mini", effort: "medium" },
|
|
30468
|
+
ideate: { provider: "claude-code", model: "claude-sonnet-4-6" },
|
|
30469
|
+
createPr: { provider: "openai-codex", model: "gpt-5.4-mini" }
|
|
30470
|
+
};
|
|
30471
|
+
var CLAUDE_ECONOMIC = {
|
|
30472
|
+
effort: "high",
|
|
30473
|
+
refine: { provider: "claude-code", model: "claude-haiku-4-5" },
|
|
30474
|
+
plan: { provider: "claude-code", model: "claude-sonnet-4-6", effort: "high" },
|
|
30475
|
+
implement: {
|
|
30476
|
+
generator: { provider: "claude-code", model: "claude-sonnet-4-6", effort: "high" },
|
|
30477
|
+
evaluator: { provider: "claude-code", model: "claude-sonnet-4-6", effort: "high" }
|
|
30478
|
+
},
|
|
30479
|
+
readiness: { provider: "claude-code", model: "claude-haiku-4-5", effort: "medium" },
|
|
30480
|
+
ideate: { provider: "claude-code", model: "claude-sonnet-4-6" },
|
|
30481
|
+
createPr: { provider: "claude-code", model: "claude-haiku-4-5" }
|
|
30482
|
+
};
|
|
30483
|
+
var COPILOT_ECONOMIC = {
|
|
30484
|
+
effort: "high",
|
|
30485
|
+
refine: { provider: "github-copilot", model: "gpt-5.4-mini" },
|
|
30486
|
+
plan: { provider: "github-copilot", model: "claude-sonnet-4.6", effort: "high" },
|
|
30487
|
+
implement: {
|
|
30488
|
+
generator: { provider: "github-copilot", model: "claude-sonnet-4.6", effort: "high" },
|
|
30489
|
+
evaluator: { provider: "github-copilot", model: "claude-sonnet-4.6", effort: "high" }
|
|
30490
|
+
},
|
|
30491
|
+
readiness: { provider: "github-copilot", model: "gpt-5-mini", effort: "medium" },
|
|
30492
|
+
ideate: { provider: "github-copilot", model: "claude-sonnet-4.6" },
|
|
30493
|
+
createPr: { provider: "github-copilot", model: "gpt-5-mini" }
|
|
30494
|
+
};
|
|
30495
|
+
var CODEX_ECONOMIC = {
|
|
30496
|
+
effort: "high",
|
|
30497
|
+
refine: { provider: "openai-codex", model: "gpt-5.4-mini" },
|
|
30498
|
+
plan: { provider: "openai-codex", model: "gpt-5.4", effort: "high" },
|
|
30499
|
+
implement: {
|
|
30500
|
+
generator: { provider: "openai-codex", model: "gpt-5.4", effort: "high" },
|
|
30501
|
+
evaluator: { provider: "openai-codex", model: "gpt-5.4", effort: "high" }
|
|
29546
30502
|
},
|
|
29547
30503
|
readiness: { provider: "openai-codex", model: "gpt-5.4-mini", effort: "medium" },
|
|
29548
30504
|
ideate: { provider: "openai-codex", model: "gpt-5.5" },
|
|
@@ -29552,7 +30508,11 @@ var PRESETS = {
|
|
|
29552
30508
|
mixed: MIXED,
|
|
29553
30509
|
"claude-only": CLAUDE_ONLY,
|
|
29554
30510
|
"copilot-only": COPILOT_ONLY,
|
|
29555
|
-
"codex-only": CODEX_ONLY
|
|
30511
|
+
"codex-only": CODEX_ONLY,
|
|
30512
|
+
"mixed-economic": MIXED_ECONOMIC,
|
|
30513
|
+
"claude-economic": CLAUDE_ECONOMIC,
|
|
30514
|
+
"copilot-economic": COPILOT_ECONOMIC,
|
|
30515
|
+
"codex-economic": CODEX_ECONOMIC
|
|
29556
30516
|
};
|
|
29557
30517
|
var applyPreset = (name, current) => ({
|
|
29558
30518
|
...current,
|
|
@@ -29565,7 +30525,11 @@ var PRESET_LABEL = {
|
|
|
29565
30525
|
mixed: "Apply: Mixed",
|
|
29566
30526
|
"claude-only": "Apply: Claude only",
|
|
29567
30527
|
"copilot-only": "Apply: Copilot only",
|
|
29568
|
-
"codex-only": "Apply: Codex only"
|
|
30528
|
+
"codex-only": "Apply: Codex only",
|
|
30529
|
+
"mixed-economic": "Apply: Mixed (economic)",
|
|
30530
|
+
"claude-economic": "Apply: Claude (economic)",
|
|
30531
|
+
"copilot-economic": "Apply: Copilot (economic)",
|
|
30532
|
+
"codex-economic": "Apply: Codex (economic)"
|
|
29569
30533
|
};
|
|
29570
30534
|
var LOG_LEVELS = ["silent", "debug", "info", "warn", "error"];
|
|
29571
30535
|
var DEFAULT_TOKEN = "Default";
|
|
@@ -30846,6 +31810,28 @@ var createPushBranchLeaf = (deps) => leaf(
|
|
|
30846
31810
|
);
|
|
30847
31811
|
|
|
30848
31812
|
// src/business/sprint/views/pr-content.ts
|
|
31813
|
+
var warningDetail = (w) => {
|
|
31814
|
+
switch (w.kind) {
|
|
31815
|
+
case "budget-exhausted":
|
|
31816
|
+
return `turn budget exhausted (${String(w.turnsUsed)}/${String(w.turnBudget)} turns)`;
|
|
31817
|
+
case "plateau":
|
|
31818
|
+
return w.dimensions.length > 0 ? `evaluator plateaued on: ${w.dimensions.join(", ")}` : "evaluator plateaued";
|
|
31819
|
+
case "malformed":
|
|
31820
|
+
return w.detail.trim().length > 0 ? `evaluator output malformed (${w.detail.trim()})` : "evaluator output malformed";
|
|
31821
|
+
case "verify-failed": {
|
|
31822
|
+
const exit = w.exitCode !== null ? `exit ${String(w.exitCode)}` : "no exit code";
|
|
31823
|
+
return w.stderr.trim().length > 0 ? `post-task verify red (${exit}: ${w.stderr.trim()})` : `post-task verify red (${exit})`;
|
|
31824
|
+
}
|
|
31825
|
+
}
|
|
31826
|
+
};
|
|
31827
|
+
var lastAttempt2 = (task) => task.attempts[task.attempts.length - 1];
|
|
31828
|
+
var renderWarningsSection = (tasks) => {
|
|
31829
|
+
const flagged = tasks.map((t) => ({ name: t.name, warning: t.status === "done" ? lastAttempt2(t)?.warning : void 0 })).filter((e) => e.warning !== void 0);
|
|
31830
|
+
if (flagged.length === 0) return "";
|
|
31831
|
+
const lines = flagged.map((e) => `- ${e.name} \u2014 ${warningDetail(e.warning)}`).join("\n");
|
|
31832
|
+
return `## Completed with warnings
|
|
31833
|
+
${lines}`;
|
|
31834
|
+
};
|
|
30849
31835
|
var derivePrContent = (sprint, tasks) => {
|
|
30850
31836
|
const sections = [`# ${sprint.name}`];
|
|
30851
31837
|
if (sprint.tickets.length > 0) {
|
|
@@ -30859,6 +31845,8 @@ ${ticketLines}`);
|
|
|
30859
31845
|
sections.push(`## Tasks
|
|
30860
31846
|
${taskLines}`);
|
|
30861
31847
|
}
|
|
31848
|
+
const warnings = renderWarningsSection(tasks);
|
|
31849
|
+
if (warnings.length > 0) sections.push(warnings);
|
|
30862
31850
|
const orderedRefs = normalizeRefs([
|
|
30863
31851
|
...sprint.tickets.map((t) => t.externalRef ?? ""),
|
|
30864
31852
|
...tasks.flatMap((t) => t.externalRefs ?? [])
|
|
@@ -30909,7 +31897,12 @@ var createCreatePrLeaf = (deps) => leaf("create-pr", {
|
|
|
30909
31897
|
}
|
|
30910
31898
|
const derived = derivePrContent(sprint.value, tasks);
|
|
30911
31899
|
const title = input.title ?? input.aiContent?.title ?? derived.title;
|
|
30912
|
-
const
|
|
31900
|
+
const resolvedBody = input.body ?? input.aiContent?.body ?? derived.body;
|
|
31901
|
+
const usingDerivedBody = input.body === void 0 && input.aiContent?.body === void 0;
|
|
31902
|
+
const warningsSection = usingDerivedBody ? "" : renderWarningsSection(tasks);
|
|
31903
|
+
const body = warningsSection.length > 0 ? `${resolvedBody}
|
|
31904
|
+
|
|
31905
|
+
${warningsSection}` : resolvedBody;
|
|
30913
31906
|
deps.eventBus.publish({
|
|
30914
31907
|
type: "log",
|
|
30915
31908
|
level: "info",
|
|
@@ -31463,7 +32456,11 @@ var PRESET_LABEL2 = {
|
|
|
31463
32456
|
mixed: "mixed",
|
|
31464
32457
|
"claude-only": "claude-only",
|
|
31465
32458
|
"copilot-only": "copilot-only",
|
|
31466
|
-
"codex-only": "codex-only"
|
|
32459
|
+
"codex-only": "codex-only",
|
|
32460
|
+
"mixed-economic": "mixed-economic",
|
|
32461
|
+
"claude-economic": "claude-economic",
|
|
32462
|
+
"copilot-economic": "copilot-economic",
|
|
32463
|
+
"codex-economic": "codex-economic"
|
|
31467
32464
|
};
|
|
31468
32465
|
var pickPresetForDetected = (installed) => {
|
|
31469
32466
|
if (installed.size === 1) {
|
|
@@ -32085,8 +33082,8 @@ var StepView2 = ({ step, onChange, onCancel, onSubmit }) => {
|
|
|
32085
33082
|
import { useEffect as useEffect40, useState as useState46 } from "react";
|
|
32086
33083
|
import { Box as Box75 } from "ink";
|
|
32087
33084
|
|
|
32088
|
-
// src/application/flows/ticket
|
|
32089
|
-
var createTicketAddFlow = (deps) => leaf("ticket
|
|
33085
|
+
// src/application/flows/add-ticket/flow.ts
|
|
33086
|
+
var createTicketAddFlow = (deps) => leaf("add-ticket", {
|
|
32090
33087
|
useCase: {
|
|
32091
33088
|
async execute(input) {
|
|
32092
33089
|
const sprint = await deps.sprintRepo.findById(input.sprintId);
|