ai-foreman 1.7.6 → 1.7.8
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/README.md +26 -1
- package/dist/activity.d.ts +3 -0
- package/dist/activity.js +31 -6
- package/dist/adapters/claude.d.ts +61 -6
- package/dist/adapters/claude.js +544 -36
- package/dist/adapters/codex.d.ts +28 -1
- package/dist/adapters/codex.js +398 -27
- package/dist/adapters/recovering.d.ts +13 -2
- package/dist/adapters/recovering.js +32 -4
- package/dist/adapters/sessionFailure.d.ts +18 -0
- package/dist/adapters/sessionFailure.js +51 -0
- package/dist/adapters/types.d.ts +117 -9
- package/dist/agentRun.d.ts +4 -1
- package/dist/agentRun.js +96 -9
- package/dist/branch/currentGuard.d.ts +38 -0
- package/dist/branch/currentGuard.js +71 -0
- package/dist/branch/planner.d.ts +1 -1
- package/dist/branch/planner.js +2 -7
- package/dist/branch/prefix.d.ts +8 -0
- package/dist/branch/prefix.js +26 -0
- package/dist/branch/resume.d.ts +2 -0
- package/dist/branch/resume.js +8 -0
- package/dist/branch/runner.d.ts +14 -2
- package/dist/branch/runner.js +77 -17
- package/dist/buildRuns.d.ts +13 -4
- package/dist/buildRuns.js +85 -19
- package/dist/cli/handoffs.d.ts +2 -0
- package/dist/cli/handoffs.js +74 -0
- package/dist/cli/start.js +1079 -43
- package/dist/cli/tickets.js +199 -3
- package/dist/continuity.d.ts +92 -0
- package/dist/continuity.js +415 -0
- package/dist/foreman.d.ts +5 -1
- package/dist/foreman.js +41 -6
- package/dist/handoffs.d.ts +69 -0
- package/dist/handoffs.js +546 -0
- package/dist/log.d.ts +1 -1
- package/dist/permissions/policy.d.ts +5 -1
- package/dist/permissions/policy.js +48 -1
- package/dist/qaReview.d.ts +6 -6
- package/dist/qaReview.js +24 -14
- package/dist/sessionAvailability.d.ts +15 -0
- package/dist/sessionAvailability.js +57 -0
- package/dist/sessionIdentity.d.ts +33 -0
- package/dist/sessionIdentity.js +132 -0
- package/dist/sessionLifecycle.d.ts +198 -5
- package/dist/sessionLifecycle.js +1373 -20
- package/dist/statusReporter.d.ts +84 -8
- package/dist/statusReporter.js +270 -14
- package/dist/ticketPlanning.js +8 -0
- package/dist/ticketPopulation.js +88 -12
- package/dist/tickets/commands.js +20 -12
- package/dist/tickets/groupReset.d.ts +25 -0
- package/dist/tickets/groupReset.js +279 -0
- package/dist/tickets/groups.d.ts +36 -0
- package/dist/tickets/groups.js +68 -0
- package/dist/tickets/importer.js +27 -3
- package/dist/tickets/recommendations.js +31 -7
- package/dist/tickets/reset.d.ts +1 -0
- package/dist/tickets/reset.js +1 -0
- package/dist/tickets/setupConfig.d.ts +1 -0
- package/dist/tickets/setupConfig.js +10 -2
- package/dist/tickets/stateDb.d.ts +34 -0
- package/dist/tickets/stateDb.js +207 -0
- package/dist/tickets/ticketLoader.js +5 -2
- package/dist/tickets/validate.js +4 -0
- package/dist/workflowDb.d.ts +145 -3
- package/dist/workflowDb.js +571 -10
- package/package.json +32 -4
package/dist/cli/tickets.js
CHANGED
|
@@ -17,11 +17,13 @@ import { assertEffortLevel, createRoleBuilder, makeLogPath, readOnlyPermissionCo
|
|
|
17
17
|
import { DEFAULT_TICKET_SETUP, configuredPlanningSources, configuredAppName, defaultAppName, detectDefaultBranch, detectPackageName, ensureRafiConfigForTicketSetup, externalSources, hasTicketSetupConfig, loadTicketSetupConfig, loadTicketSetupConfigWithDefaults, localSourcePaths, mergeTicketSetup, recommendedBuildDefaults, saveTicketSetupConfig, syncTicketLimits, urlSources, } from "../tickets/setupConfig.js";
|
|
18
18
|
import { fetchAndSnapshotUrl, snapshotExternalLocalFile } from "../tickets/sourceFetch.js";
|
|
19
19
|
import { checkpointInterview, completeInterview, createInterviewRecord, failInterview, } from "../interviews.js";
|
|
20
|
-
import { applyTicketPopulation, authorizeTicketRetirements, extractTicketPopulationProposal, materializeTicketPopulation, TICKET_POPULATION_PROPOSAL_END, TICKET_POPULATION_PROPOSAL_START } from "../ticketPopulation.js";
|
|
20
|
+
import { applyTicketPopulation, authorizeTicketRetirements, extractTicketPopulationProposal, materializeTicketPopulation, recoverTicketPublications, TICKET_POPULATION_PROPOSAL_END, TICKET_POPULATION_PROPOSAL_START } from "../ticketPopulation.js";
|
|
21
21
|
import { loadTickets } from "../tickets/ticketLoader.js";
|
|
22
22
|
import { resolveTicketPaths } from "../tickets/config.js";
|
|
23
23
|
import { formatTicketDetails, getTicketDetails } from "../tickets/details.js";
|
|
24
|
-
import { previewTicketReset, recoverTicketResetPublications, resetTickets } from "../tickets/reset.js";
|
|
24
|
+
import { applyResolvedTicketReset, previewTicketReset, recoverTicketResetPublications, resetTickets, resolveTicketResetSelection, TicketResetDependencyConflictError } from "../tickets/reset.js";
|
|
25
|
+
import { validateBranchPrefix } from "../branch/prefix.js";
|
|
26
|
+
import { listTicketGroups, previewTicketGroupRepair, repairTicketGroups } from "../tickets/groups.js";
|
|
25
27
|
let nestedTicketCommandDepth = 0;
|
|
26
28
|
class TicketInterviewCancelled extends Error {
|
|
27
29
|
}
|
|
@@ -321,6 +323,8 @@ async function collectTicketSetupPatch(dir, opts, current) {
|
|
|
321
323
|
populatePatch.agent_preference = parseAgentPreference(opts.agentPreference);
|
|
322
324
|
if (opts.branchStrategy)
|
|
323
325
|
buildPatch.branch_strategy = parseBranchStrategy(opts.branchStrategy);
|
|
326
|
+
if (opts.branchPrefix)
|
|
327
|
+
buildPatch.branch_prefix = validateBranchPrefix(opts.branchPrefix);
|
|
324
328
|
if (opts.completion)
|
|
325
329
|
buildPatch.completion = parseCompletionMode(opts.completion);
|
|
326
330
|
if (opts.provider)
|
|
@@ -420,6 +424,18 @@ async function collectTicketSetupPatch(dir, opts, current) {
|
|
|
420
424
|
if (isCancel(base))
|
|
421
425
|
cancelTicketInterview();
|
|
422
426
|
buildPatch.base_branch = String(base).trim();
|
|
427
|
+
const prefix = await text({ message: "Branch prefix:", initialValue: current.build.branch_prefix, defaultValue: current.build.branch_prefix, validate: (value) => {
|
|
428
|
+
try {
|
|
429
|
+
validateBranchPrefix(String(value));
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
return error instanceof Error ? error.message : String(error);
|
|
434
|
+
}
|
|
435
|
+
} });
|
|
436
|
+
if (isCancel(prefix))
|
|
437
|
+
cancelTicketInterview();
|
|
438
|
+
buildPatch.branch_prefix = validateBranchPrefix(String(prefix));
|
|
423
439
|
const policyMode = await select({ message: "Use one global branch strategy or choose by inferred ticket size?", initialValue: current.build.branch_policy.mode, options: [
|
|
424
440
|
{ value: "size", label: "Size-based: XS/S shared, M/L/XL per ticket (Recommended)" },
|
|
425
441
|
{ value: "global", label: "One global strategy" },
|
|
@@ -772,6 +788,22 @@ function parseResetScope(value) {
|
|
|
772
788
|
return value;
|
|
773
789
|
throw new Error("--scope must be all, completed-and-unfinished, or unfinished");
|
|
774
790
|
}
|
|
791
|
+
function parsePositiveInteger(value, label) {
|
|
792
|
+
const parsed = Number.parseInt(value, 10);
|
|
793
|
+
if (Number.isInteger(parsed) && parsed > 0 && String(parsed) === value.trim())
|
|
794
|
+
return parsed;
|
|
795
|
+
throw new Error(`${label} must be a positive integer`);
|
|
796
|
+
}
|
|
797
|
+
function parseTicketGroupId(value) {
|
|
798
|
+
if (!/^TG-[1-9]\d*$/.test(value))
|
|
799
|
+
throw new Error("--group must use a stable ID such as TG-1");
|
|
800
|
+
return value;
|
|
801
|
+
}
|
|
802
|
+
function parseDeletedTicketPolicy(value) {
|
|
803
|
+
if (value === "ignore" || value === "restore")
|
|
804
|
+
return value;
|
|
805
|
+
throw new Error("--deleted-tickets must be ignore or restore");
|
|
806
|
+
}
|
|
775
807
|
function parseOptionalPositiveInteger(value, label) {
|
|
776
808
|
if (!value.trim())
|
|
777
809
|
return null;
|
|
@@ -1091,11 +1123,100 @@ function ticketOwnershipReceipts(projectDir, commandName, _afterWrite) {
|
|
|
1091
1123
|
}
|
|
1092
1124
|
return unique(candidates).filter((path) => existsSync(join(projectDir, path))).map((path) => ({ path, category: "tickets", origin: `tickets:${commandName}` }));
|
|
1093
1125
|
}
|
|
1126
|
+
async function runGroupResetCli(projectDir, opts) {
|
|
1127
|
+
const requested = opts.allGroups
|
|
1128
|
+
? { kind: "all-groups" }
|
|
1129
|
+
: opts.recentGroups !== undefined
|
|
1130
|
+
? { kind: "recent-groups", count: parsePositiveInteger(String(opts.recentGroups), "--recent-groups") }
|
|
1131
|
+
: opts.group !== undefined
|
|
1132
|
+
? { kind: "group", groupId: parseTicketGroupId(String(opts.group)) }
|
|
1133
|
+
: { kind: "group-index", position: parsePositiveInteger(String(opts.groupIndex), "--group-index") };
|
|
1134
|
+
const explicitPolicy = opts.deletedTickets === undefined ? undefined : parseDeletedTicketPolicy(String(opts.deletedTickets));
|
|
1135
|
+
const initial = resolveTicketResetSelection(projectDir, requested, { deletedTickets: "ignore" });
|
|
1136
|
+
const missing = initial.previewRows.filter((row) => row.definitionMissing);
|
|
1137
|
+
if (missing.length && !explicitPolicy && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
1138
|
+
throw new Error("group reset includes deleted ticket definitions; automation must pass --deleted-tickets ignore|restore");
|
|
1139
|
+
}
|
|
1140
|
+
let policy = explicitPolicy;
|
|
1141
|
+
const perTicket = {};
|
|
1142
|
+
const restoreDependencies = [];
|
|
1143
|
+
if (missing.length && !policy) {
|
|
1144
|
+
const answer = await select({ message: `${missing.length} group member definition(s) are missing. What should reset do?`, options: [
|
|
1145
|
+
{ value: "ignore", label: "Ignore all missing definitions (Recommended)" },
|
|
1146
|
+
{ value: "restore", label: "Restore all from Rafi snapshots" },
|
|
1147
|
+
{ value: "each", label: "Decide each one" },
|
|
1148
|
+
] });
|
|
1149
|
+
if (isCancel(answer)) {
|
|
1150
|
+
console.log("foreman tickets: reset cancelled; nothing changed");
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
if (answer === "each") {
|
|
1154
|
+
for (const row of missing) {
|
|
1155
|
+
const decision = await select({ message: `${row.ticketId}: ${row.title}`, options: [
|
|
1156
|
+
{ value: "ignore", label: "Ignore missing definition (Recommended)" }, { value: "restore", label: "Restore definition" },
|
|
1157
|
+
] });
|
|
1158
|
+
if (isCancel(decision)) {
|
|
1159
|
+
console.log("foreman tickets: reset cancelled; nothing changed");
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
perTicket[row.ticketId] = decision;
|
|
1163
|
+
}
|
|
1164
|
+
policy = "ignore";
|
|
1165
|
+
}
|
|
1166
|
+
else
|
|
1167
|
+
policy = answer;
|
|
1168
|
+
}
|
|
1169
|
+
let selection;
|
|
1170
|
+
while (true) {
|
|
1171
|
+
try {
|
|
1172
|
+
selection = resolveTicketResetSelection(projectDir, requested, { deletedTickets: policy ?? "ignore", perTicket, restoreDependencies });
|
|
1173
|
+
break;
|
|
1174
|
+
}
|
|
1175
|
+
catch (error) {
|
|
1176
|
+
if (!(error instanceof TicketResetDependencyConflictError) || !process.stdin.isTTY || !process.stdout.isTTY)
|
|
1177
|
+
throw error;
|
|
1178
|
+
for (const conflict of error.conflicts) {
|
|
1179
|
+
const decision = await select({ message: `${conflict.ticketId} depends on deleted definition ${conflict.dependencyId}.`, options: [
|
|
1180
|
+
{ value: "dependency", label: `Restore ${conflict.dependencyId} without resetting it (Recommended)` },
|
|
1181
|
+
{ value: "skip", label: `Skip restoring ${conflict.ticketId}` },
|
|
1182
|
+
{ value: "cancel", label: "Cancel reset" },
|
|
1183
|
+
] });
|
|
1184
|
+
if (isCancel(decision) || decision === "cancel") {
|
|
1185
|
+
console.log("foreman tickets: reset cancelled; nothing changed");
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (decision === "dependency")
|
|
1189
|
+
restoreDependencies.push(conflict.dependencyId);
|
|
1190
|
+
else
|
|
1191
|
+
perTicket[conflict.ticketId] = "ignore";
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
console.log(`foreman tickets reset preview (${selection.groups.map((group) => group.id).join(", ") || requested.kind}):`);
|
|
1196
|
+
for (const row of selection.previewRows)
|
|
1197
|
+
console.log(` ${row.ticketId}: ${row.title} [${row.status}]${row.definitionMissing ? row.restoreDefinition ? " — restore definition and reset" : " — missing definition, skip" : " — reset active state"}`);
|
|
1198
|
+
for (const dependency of selection.definitionRestorations.filter((item) => item.dependencyOnly))
|
|
1199
|
+
console.log(` dependency ${dependency.ticketId}: restore definition only (do not reset)`);
|
|
1200
|
+
if (!selection.previewRows.length)
|
|
1201
|
+
return;
|
|
1202
|
+
if (!opts.yes) {
|
|
1203
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
1204
|
+
throw new Error("confirmation requires a TTY or --yes");
|
|
1205
|
+
const approved = await confirm({ message: `Apply this frozen ${selection.previewRows.length}-member reset selection?`, initialValue: false });
|
|
1206
|
+
if (isCancel(approved) || !approved) {
|
|
1207
|
+
console.log("foreman tickets: reset cancelled; nothing changed");
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
const result = applyResolvedTicketReset(projectDir, selection, String(opts.actor ?? "user"));
|
|
1212
|
+
console.log(`foreman tickets: reset ${result.tickets.length} ticket(s), restored ${result.restored.length}, ignored ${result.ignoredMissing.length} missing (audit ${result.resetId})`);
|
|
1213
|
+
}
|
|
1094
1214
|
export function buildTicketsCommand(options = {}) {
|
|
1095
1215
|
const tickets = new Command("tickets").description("Manage the structured ticket tracker for a project.");
|
|
1096
1216
|
tickets.hook("preAction", (_thisCommand, actionCommand) => {
|
|
1097
1217
|
const commandOptions = actionCommand.opts();
|
|
1098
1218
|
const projectDir = cwd(commandOptions);
|
|
1219
|
+
recoverTicketPublications(projectDir);
|
|
1099
1220
|
recoverTicketResetPublications(projectDir);
|
|
1100
1221
|
if (options.prepareOwnership && !["show", "validate", "queue"].includes(actionCommand.name())) {
|
|
1101
1222
|
options.prepareOwnership(projectDir, ticketOwnershipReceipts(projectDir, actionCommand.name(), false));
|
|
@@ -1127,6 +1248,7 @@ export function buildTicketsCommand(options = {}) {
|
|
|
1127
1248
|
.option("--url-source <urls...>", "add public HTTP(S) source URLs")
|
|
1128
1249
|
.option("--agent-preference <agent>", "populate runtime preference (configured | claude | codex)")
|
|
1129
1250
|
.option("--branch-strategy <strategy>", "build branch strategy default (current | batch | branch-per-ticket)")
|
|
1251
|
+
.option("--branch-prefix <prefix>", "prefix for Rafi-generated branches (for example team/feature)")
|
|
1130
1252
|
.option("--completion <mode>", "build completion default (pr | auto-merge | direct-merge | none)")
|
|
1131
1253
|
.option("--provider <provider>", "PR/MR provider default (auto | github | gitlab | local)")
|
|
1132
1254
|
.option("--auto-merge-wait", "wait for dependency PR/MRs to merge before starting dependent tickets")
|
|
@@ -1162,6 +1284,7 @@ export function buildTicketsCommand(options = {}) {
|
|
|
1162
1284
|
.option("--url-source <urls...>", "replace saved sources with public HTTP(S) URLs")
|
|
1163
1285
|
.option("--agent-preference <agent>", "populate runtime preference (configured | claude | codex)")
|
|
1164
1286
|
.option("--branch-strategy <strategy>", "build branch strategy default (current | batch | branch-per-ticket)")
|
|
1287
|
+
.option("--branch-prefix <prefix>", "prefix for Rafi-generated branches (for example team/feature)")
|
|
1165
1288
|
.option("--completion <mode>", "build completion default (pr | auto-merge | direct-merge | none)")
|
|
1166
1289
|
.option("--provider <provider>", "PR/MR provider default (auto | github | gitlab | local)")
|
|
1167
1290
|
.option("--auto-merge-wait", "wait for dependency PR/MRs to merge before starting dependent tickets")
|
|
@@ -1269,20 +1392,93 @@ export function buildTicketsCommand(options = {}) {
|
|
|
1269
1392
|
fail(String(err instanceof Error ? err.message : err));
|
|
1270
1393
|
}
|
|
1271
1394
|
});
|
|
1395
|
+
const groups = tickets.command("groups").description("List and repair durable ticket creation groups.");
|
|
1396
|
+
groups
|
|
1397
|
+
.command("list")
|
|
1398
|
+
.description("List immutable ticket creation groups newest first.")
|
|
1399
|
+
.option("-p, --project <dir>", "project directory (default: cwd)")
|
|
1400
|
+
.option("--json", "write the versioned stable JSON envelope")
|
|
1401
|
+
.action((opts) => {
|
|
1402
|
+
try {
|
|
1403
|
+
const envelope = listTicketGroups(cwd(opts));
|
|
1404
|
+
if (opts.json) {
|
|
1405
|
+
process.stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (!envelope.groups.length) {
|
|
1409
|
+
console.log("foreman tickets groups: no creation groups");
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
for (const group of envelope.groups) {
|
|
1413
|
+
const totals = Object.entries(group.statusTotals).map(([status, count]) => `${status}=${count}`).join(", ");
|
|
1414
|
+
console.log(`${group.id} index=${group.recencyPosition} origin=${group.origin} created=${group.createdAt}${group.legacy ? " legacy" : ""} ${totals}`);
|
|
1415
|
+
for (const member of group.members)
|
|
1416
|
+
console.log(` ${member.position}. ${member.ticketId} [${member.status}]${member.definitionMissing ? " — definition missing (recoverable)" : ""}`);
|
|
1417
|
+
for (const run of group.recoverableRuns)
|
|
1418
|
+
console.log(` recoverable run ${run.runId} [${run.status}] ${run.tickets.join(", ")}`);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
catch (error) {
|
|
1422
|
+
fail(String(error instanceof Error ? error.message : error));
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
groups
|
|
1426
|
+
.command("repair")
|
|
1427
|
+
.description("Preview and group tickets created outside Rafi after the legacy migration.")
|
|
1428
|
+
.option("-p, --project <dir>", "project directory (default: cwd)")
|
|
1429
|
+
.option("-y, --yes", "approve the exact preview")
|
|
1430
|
+
.action(async (opts) => {
|
|
1431
|
+
try {
|
|
1432
|
+
const dir = cwd(opts);
|
|
1433
|
+
const preview = previewTicketGroupRepair(dir);
|
|
1434
|
+
console.log("foreman tickets groups repair preview:");
|
|
1435
|
+
console.log(preview.ticketIds.length ? ` new repair group: ${preview.ticketIds.join(", ")}` : " no ungrouped tickets");
|
|
1436
|
+
if (!preview.ticketIds.length)
|
|
1437
|
+
return;
|
|
1438
|
+
if (!opts.yes) {
|
|
1439
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
1440
|
+
throw new Error("repair approval requires a TTY or --yes");
|
|
1441
|
+
const approved = await confirm({ message: `Create one repair group for exactly ${preview.ticketIds.length} ticket(s)?`, initialValue: false });
|
|
1442
|
+
if (isCancel(approved) || !approved) {
|
|
1443
|
+
console.log("foreman tickets groups: repair cancelled; nothing changed");
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
const group = repairTicketGroups(dir, preview.inputFingerprint);
|
|
1448
|
+
console.log(group ? `foreman tickets groups: created ${group.id} for ${group.members.map((member) => member.ticketId).join(", ")}` : "foreman tickets groups: nothing to repair");
|
|
1449
|
+
}
|
|
1450
|
+
catch (error) {
|
|
1451
|
+
fail(String(error instanceof Error ? error.message : error));
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1272
1454
|
tickets
|
|
1273
1455
|
.command("reset [ticketId]")
|
|
1274
1456
|
.description("Reset one ticket or an explicit ticket scope to pristine active state while retaining history.")
|
|
1275
1457
|
.option("-p, --project <dir>", "project directory (default: cwd)")
|
|
1276
1458
|
.option("--all", "open the interactive bulk reset scope chooser")
|
|
1277
1459
|
.option("--scope <scope>", "bulk scope (all | completed-and-unfinished | unfinished)")
|
|
1460
|
+
.option("--all-groups", "reset the exact membership of every durable creation group")
|
|
1461
|
+
.option("--recent-groups <count>", "reset the exact membership of the newest N creation groups")
|
|
1462
|
+
.option("--group <TG-N>", "reset one durable creation group by stable ID")
|
|
1463
|
+
.option("--group-index <position>", "reset one creation group by current newest-first position")
|
|
1464
|
+
.option("--deleted-tickets <policy>", "missing-definition policy for automation (ignore | restore)")
|
|
1278
1465
|
.option("--actor <actor>", "who requested the reset", "user")
|
|
1279
1466
|
.option("-y, --yes", "confirm an explicit ticket or --scope without prompting")
|
|
1280
1467
|
.action(async (ticketId, opts) => {
|
|
1281
1468
|
try {
|
|
1469
|
+
const groupTargetCount = [opts.allGroups, opts.recentGroups, opts.group, opts.groupIndex].filter((value) => value !== undefined && value !== false).length;
|
|
1470
|
+
if (groupTargetCount > 0) {
|
|
1471
|
+
if (groupTargetCount !== 1)
|
|
1472
|
+
throw new Error("choose exactly one group reset target");
|
|
1473
|
+
if (ticketId || opts.all || opts.scope)
|
|
1474
|
+
throw new Error("group reset targets cannot be combined with a ticket or scope target");
|
|
1475
|
+
await runGroupResetCli(cwd(opts), opts);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1282
1478
|
if (ticketId && (opts.all || opts.scope))
|
|
1283
1479
|
throw new Error("provide a ticket ID or a bulk scope, not both");
|
|
1284
1480
|
if (!ticketId && !opts.all && !opts.scope)
|
|
1285
|
-
throw new Error("provide <ticketId>, --all,
|
|
1481
|
+
throw new Error("provide <ticketId>, --all, --scope <scope>, or one group target");
|
|
1286
1482
|
let target = ticketId;
|
|
1287
1483
|
if (opts.scope)
|
|
1288
1484
|
target = parseResetScope(String(opts.scope));
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ContinuityDelta, ResolvedAgentSettings } from "rafi-spec";
|
|
2
|
+
import type { BuilderAdapter, CompactResult, ContextManagementPolicy, ContextUsage, InterruptResult, ManagedTurnDispatcher, PreparedContextManagement, ProviderSessionUsage, ProviderSettingSwitch, RuntimeFailure, TurnResult } from "./adapters/types.js";
|
|
3
|
+
export declare const CONTINUITY_MARKER = "RAFI_CONTINUITY_DELTA:";
|
|
4
|
+
export declare class ContinuityValidationError extends Error {
|
|
5
|
+
readonly problems: string[];
|
|
6
|
+
constructor(problems: string[]);
|
|
7
|
+
}
|
|
8
|
+
export declare class ContinuityRecoveryRequiredError extends Error {
|
|
9
|
+
readonly runId: string;
|
|
10
|
+
readonly role: "builder" | "qa";
|
|
11
|
+
constructor(runId: string, role: "builder" | "qa", message: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Exact-session loss is a host recovery boundary, never a continuity-repair
|
|
15
|
+
* prompt. `guidedRecovery` is true when the provider may already have received
|
|
16
|
+
* the frozen instruction, so a fresh successor must reconcile receipts first.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SessionUnavailableContinuityError extends Error {
|
|
19
|
+
readonly runId: string;
|
|
20
|
+
readonly role: "builder" | "qa";
|
|
21
|
+
readonly failure: RuntimeFailure;
|
|
22
|
+
readonly guidedRecovery: boolean;
|
|
23
|
+
constructor(runId: string, role: "builder" | "qa", failure: RuntimeFailure);
|
|
24
|
+
}
|
|
25
|
+
export declare function validateContinuityDelta(value: unknown): ContinuityDelta;
|
|
26
|
+
export declare function parseContinuityDelta(text: string): {
|
|
27
|
+
delta?: ContinuityDelta;
|
|
28
|
+
cleanText: string;
|
|
29
|
+
error?: ContinuityValidationError;
|
|
30
|
+
};
|
|
31
|
+
export declare function mergeContinuityDeltas(deltas: readonly ContinuityDelta[]): ContinuityDelta;
|
|
32
|
+
export declare function baselineContinuityDelta(nextAction?: string): ContinuityDelta;
|
|
33
|
+
export declare function continuityInstruction(): string;
|
|
34
|
+
export interface ContinuityAdapterOptions {
|
|
35
|
+
adapter: BuilderAdapter;
|
|
36
|
+
projectDir: string;
|
|
37
|
+
runId: string;
|
|
38
|
+
role: "builder" | "qa";
|
|
39
|
+
settings: ResolvedAgentSettings;
|
|
40
|
+
authoritativeStateRevision?: () => number;
|
|
41
|
+
/** @deprecated Use recoverWithHandoff so successor acceptance is lifecycle-managed and durable. */
|
|
42
|
+
createSuccessor?: (handoff: string) => Promise<BuilderAdapter>;
|
|
43
|
+
/**
|
|
44
|
+
* Production recovery path for a double-invalid delta. The callback must
|
|
45
|
+
* publish a versioned handoff, validate a genuinely fresh successor, and
|
|
46
|
+
* move the role lease before returning it.
|
|
47
|
+
*/
|
|
48
|
+
recoverWithHandoff?: (input: {
|
|
49
|
+
reason: string;
|
|
50
|
+
reconstruction: string;
|
|
51
|
+
predecessor: BuilderAdapter;
|
|
52
|
+
}) => Promise<BuilderAdapter>;
|
|
53
|
+
/** Explicit compatibility recovery may replace a dead predecessor lease only after its first valid checkpoint. */
|
|
54
|
+
replaceRecoveryLeaseAfterCheckpoint?: boolean;
|
|
55
|
+
/** Host-owned validated Builder handoff request processing at a safe turn boundary. */
|
|
56
|
+
handleHandoffRequest?: (text: string, predecessor: BuilderAdapter, frozenAction: string) => Promise<BuilderAdapter | undefined>;
|
|
57
|
+
}
|
|
58
|
+
/** Enforces a durable role checkpoint after every completed provider turn. */
|
|
59
|
+
export declare class ContinuityAdapter implements BuilderAdapter {
|
|
60
|
+
private readonly options;
|
|
61
|
+
private adapter;
|
|
62
|
+
private readonly queue;
|
|
63
|
+
private sourcePump?;
|
|
64
|
+
private recoveryLeasePending;
|
|
65
|
+
private turnDispatcher?;
|
|
66
|
+
constructor(options: ContinuityAdapterOptions);
|
|
67
|
+
get agent(): "claude" | "codex";
|
|
68
|
+
sendTurn(instruction: string): Promise<TurnResult>;
|
|
69
|
+
sessionId(): string | undefined;
|
|
70
|
+
sessionRef(): import("rafi-spec").ProviderSessionRefV1 | undefined;
|
|
71
|
+
adoptSessionRef(ref: import("rafi-spec").ProviderSessionRefV1): void;
|
|
72
|
+
validateSession(): Promise<import("rafi-spec").SessionAvailabilityV1>;
|
|
73
|
+
compact(): Promise<CompactResult>;
|
|
74
|
+
prepareContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
|
|
75
|
+
updateContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
|
|
76
|
+
interruptTurnAtCompactionBoundary(providerEventId?: string): Promise<InterruptResult>;
|
|
77
|
+
installManagedTurnDispatcher(dispatcher: ManagedTurnDispatcher): void;
|
|
78
|
+
contextUsage(): Promise<ContextUsage | undefined>;
|
|
79
|
+
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
80
|
+
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
81
|
+
events(): AsyncIterable<import("./adapters/types.js").BuilderEvent>;
|
|
82
|
+
close(): Promise<void>;
|
|
83
|
+
/** Keep the observable wrapper and event stream stable while moving to an accepted successor. */
|
|
84
|
+
adoptValidatedSuccessor(successor: BuilderAdapter): Promise<void>;
|
|
85
|
+
private providerTurn;
|
|
86
|
+
private publish;
|
|
87
|
+
private moveRecoveryLeaseAfterCheckpoint;
|
|
88
|
+
private recordSessionUnavailable;
|
|
89
|
+
private reconstructionHandoff;
|
|
90
|
+
private revision;
|
|
91
|
+
private pumpEvents;
|
|
92
|
+
}
|