ai-foreman 1.7.4 → 1.7.6
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 +6 -1
- package/dist/activity.d.ts +63 -0
- package/dist/activity.js +244 -0
- package/dist/adapters/claude.d.ts +7 -0
- package/dist/adapters/claude.js +41 -1
- package/dist/adapters/codex.d.ts +1 -0
- package/dist/adapters/codex.js +65 -5
- package/dist/adapters/recovering.js +7 -1
- package/dist/adapters/types.d.ts +15 -0
- package/dist/agentRun.d.ts +5 -0
- package/dist/agentRun.js +5 -1
- package/dist/branch/github.js +5 -3
- package/dist/branch/gitlab.js +5 -3
- package/dist/branch/planner.d.ts +5 -0
- package/dist/branch/planner.js +42 -3
- package/dist/branch/presentation.d.ts +17 -0
- package/dist/branch/presentation.js +38 -0
- package/dist/branch/resume.d.ts +2 -0
- package/dist/branch/resume.js +2 -0
- package/dist/branch/runner.js +44 -25
- package/dist/buildRuns.d.ts +41 -17
- package/dist/buildRuns.js +243 -22
- package/dist/cli/events.js +24 -5
- package/dist/cli/runtimeAuthPrompt.js +5 -1
- package/dist/cli/start.js +138 -45
- package/dist/cli/tickets.d.ts +10 -1
- package/dist/cli/tickets.js +418 -79
- package/dist/foreman.d.ts +8 -5
- package/dist/foreman.js +38 -14
- package/dist/index.js +3 -1
- package/dist/providerQuestions.d.ts +23 -0
- package/dist/providerQuestions.js +24 -0
- package/dist/qaReview.d.ts +1 -0
- package/dist/qaReview.js +23 -15
- package/dist/qaSnapshot.d.ts +14 -0
- package/dist/qaSnapshot.js +117 -1
- package/dist/runtimeReadiness.js +5 -0
- package/dist/sources/sourceRegistry.js +13 -6
- package/dist/tickets/details.d.ts +17 -0
- package/dist/tickets/details.js +94 -0
- package/dist/tickets/eligibility.d.ts +28 -0
- package/dist/tickets/eligibility.js +71 -0
- package/dist/tickets/importer.js +13 -8
- package/dist/tickets/reset.d.ts +27 -0
- package/dist/tickets/reset.js +213 -0
- package/dist/tickets/reviewStandards.d.ts +7 -0
- package/dist/tickets/reviewStandards.js +53 -0
- package/dist/tickets/setupConfig.d.ts +22 -0
- package/dist/tickets/setupConfig.js +105 -1
- package/dist/tickets/sourceFetch.js +19 -0
- package/dist/tickets/stateDb.d.ts +8 -1
- package/dist/tickets/stateDb.js +54 -23
- package/dist/workflowDb.d.ts +3 -0
- package/dist/workflowDb.js +26 -0
- package/package.json +15 -3
package/dist/cli/start.js
CHANGED
|
@@ -17,19 +17,22 @@ import { resolveAgentForProject } from "./runtimeSelection.js";
|
|
|
17
17
|
import { isTicketsInitialized, loadTicketsConfig, resolveTicketPaths } from "../tickets/config.js";
|
|
18
18
|
import { loadTickets } from "../tickets/ticketLoader.js";
|
|
19
19
|
import { StateDb } from "../tickets/stateDb.js";
|
|
20
|
-
import { applySharedDeliveryBranch, buildBranchAuditInstruction, buildBranchPlan, parseAuditDependencies } from "../branch/planner.js";
|
|
21
|
-
import {
|
|
20
|
+
import { applySharedDeliveryBranch, applySizeBranchPolicy, buildBranchAuditInstruction, buildBranchPlan, parseAuditDependencies } from "../branch/planner.js";
|
|
21
|
+
import { currentGitRef, ensureCleanBaseWorktree, generatedTrackerDirtyPaths } from "../branch/git.js";
|
|
22
22
|
import { formatGitHubFailure, preflightGh } from "../branch/github.js";
|
|
23
23
|
import { preflightGlab } from "../branch/gitlab.js";
|
|
24
24
|
import { readDeliveryUnitSession, runBranchPlan } from "../branch/runner.js";
|
|
25
|
-
import {
|
|
25
|
+
import { branchPlanLogMetadata, presentBranchPlan } from "../branch/presentation.js";
|
|
26
|
+
import { checkpointBuildRun, completeBuildRun, createBuildRun, heartbeatBuildRun, persistBuildSession, projectBuildRecovery, readBuildRuns, releaseBuildLease, resumeBuildRun } from "../buildRuns.js";
|
|
26
27
|
import { parse as parseYaml } from "yaml";
|
|
27
28
|
import { findResumableBranchSessions, formatBranchContinueCommand, formatBranchSummaryFollowupCommands, } from "../branch/resume.js";
|
|
28
29
|
import { detectGitProvider, loadTicketSetupConfig } from "../tickets/setupConfig.js";
|
|
29
30
|
import { loadDeliveryConfig, selectDeliveryUnitForRun, selectStacksForRun, updateStackDeliveryState } from "../tickets/delivery.js";
|
|
30
31
|
import { AgentStatusReporter } from "../statusReporter.js";
|
|
31
|
-
import {
|
|
32
|
+
import { currentActivity, withActivityPhase } from "../activity.js";
|
|
33
|
+
import { WorkflowDb, readCurrentWorkflowLease } from "../workflowDb.js";
|
|
32
34
|
import { makeLogPath as makeRoleLogPath, readOnlyPermissionConfig, runRoleInstruction } from "../agentRun.js";
|
|
35
|
+
import { eligibleTickets, evaluateTicketEligibility } from "../tickets/eligibility.js";
|
|
33
36
|
function fail(message) {
|
|
34
37
|
console.error(`foreman: ${message}`);
|
|
35
38
|
process.exit(1);
|
|
@@ -185,7 +188,10 @@ function branchContinueTicketHelp(cwd, flag = "--continue") {
|
|
|
185
188
|
}
|
|
186
189
|
async function ensureGitHubReadyForCreatePr(cwd, log, yes) {
|
|
187
190
|
while (true) {
|
|
188
|
-
const result =
|
|
191
|
+
const result = await withActivityPhase("checking GitHub readiness", async () => {
|
|
192
|
+
currentActivity()?.update("checking GitHub readiness", "remote, authentication, and repository access");
|
|
193
|
+
return preflightGh(cwd);
|
|
194
|
+
});
|
|
189
195
|
if (result.ok)
|
|
190
196
|
return;
|
|
191
197
|
log.write("github-readiness-failed", {
|
|
@@ -211,6 +217,7 @@ async function ensureGitHubReadyForCreatePr(cwd, log, yes) {
|
|
|
211
217
|
console.log("ai-foreman: cancelled");
|
|
212
218
|
process.exit(0);
|
|
213
219
|
}
|
|
220
|
+
currentActivity()?.note("rafi: retrying GitHub readiness check");
|
|
214
221
|
}
|
|
215
222
|
}
|
|
216
223
|
async function ensureReviewProviderReady(cwd, provider, log, yes) {
|
|
@@ -219,7 +226,10 @@ async function ensureReviewProviderReady(cwd, provider, log, yes) {
|
|
|
219
226
|
return;
|
|
220
227
|
}
|
|
221
228
|
while (true) {
|
|
222
|
-
const result =
|
|
229
|
+
const result = await withActivityPhase("checking GitLab readiness", async () => {
|
|
230
|
+
currentActivity()?.update("checking GitLab readiness", "remote, authentication, and repository access");
|
|
231
|
+
return preflightGlab(cwd);
|
|
232
|
+
});
|
|
223
233
|
if (result.ok)
|
|
224
234
|
return;
|
|
225
235
|
log.write("gitlab-readiness-failed", {
|
|
@@ -245,6 +255,7 @@ async function ensureReviewProviderReady(cwd, provider, log, yes) {
|
|
|
245
255
|
console.log("ai-foreman: cancelled");
|
|
246
256
|
process.exit(0);
|
|
247
257
|
}
|
|
258
|
+
currentActivity()?.note("rafi: retrying GitLab readiness check");
|
|
248
259
|
}
|
|
249
260
|
}
|
|
250
261
|
export function buildStartCommand() {
|
|
@@ -274,13 +285,21 @@ export function buildStartCommand() {
|
|
|
274
285
|
.option("--no-auto-merge-wait", "do not wait for dependency PR/MRs before dependent tickets")
|
|
275
286
|
.option("--auto-merge-timeout-minutes <n>", "auto-merge dependency wait timeout in minutes (blank means no timeout)")
|
|
276
287
|
.option("--base <ref>", "base ref for root ticket branches (default: current branch or HEAD)")
|
|
277
|
-
.option("--branch-prefix <prefix>", "branch name prefix for ticket branches", "
|
|
288
|
+
.option("--branch-prefix <prefix>", "branch name prefix for ticket branches", "feature")
|
|
278
289
|
.option("--max-branch-depth <n>", "maximum selected branch stack depth", "5")
|
|
279
290
|
.option("--pr-ready", "create ready-for-review PRs instead of draft PRs")
|
|
280
291
|
.option("--keep-worktrees", "keep successful ticket worktrees for inspection")
|
|
281
|
-
.option("--ticket <id>", "
|
|
292
|
+
.option("--ticket <id>", "select one new ticket, or identify recovery tickets with --resume/--continue/--recover-run", collectTicket, [])
|
|
282
293
|
.option("--skip-delivery-unit <id>", "skip one unfinished delivery unit for this run", collectTicket, [])
|
|
283
294
|
.action(async (project, opts, command) => {
|
|
295
|
+
const requestedTicketIds = opts.ticket ?? [];
|
|
296
|
+
const recoveryIntent = Boolean(opts.resume || opts.continue || opts.recoverRun);
|
|
297
|
+
if (!recoveryIntent && requestedTicketIds.length > 1)
|
|
298
|
+
fail("new-ticket selection supports exactly one --ticket");
|
|
299
|
+
if (!recoveryIntent && requestedTicketIds.length === 1 && opts.steps === undefined && opts.stacks === undefined)
|
|
300
|
+
opts.steps = "1";
|
|
301
|
+
if (!recoveryIntent && requestedTicketIds.length && opts.stacks !== undefined)
|
|
302
|
+
fail("new-ticket selection cannot be combined with --stacks");
|
|
284
303
|
if (opts.steps !== undefined && opts.stacks !== undefined)
|
|
285
304
|
fail("--steps and --stacks are mutually exclusive");
|
|
286
305
|
if (opts.steps === undefined && opts.stacks === undefined) {
|
|
@@ -409,13 +428,14 @@ export function buildStartCommand() {
|
|
|
409
428
|
fail("choose either --resume <sessionId> or --continue, not both");
|
|
410
429
|
}
|
|
411
430
|
const branchMode = branchDefaults.branchMode;
|
|
412
|
-
const continueTickets =
|
|
431
|
+
const continueTickets = recoveryIntent ? requestedTicketIds : [];
|
|
432
|
+
let selectedNewTicket = !recoveryIntent ? requestedTicketIds[0] : undefined;
|
|
413
433
|
const maxBranchDepth = Number.parseInt(opts.maxBranchDepth, 10);
|
|
414
434
|
if (!Number.isInteger(maxBranchDepth) || maxBranchDepth < 1) {
|
|
415
435
|
fail("--max-branch-depth must be a positive integer");
|
|
416
436
|
}
|
|
417
|
-
if (!branchMode && continueTickets.length >
|
|
418
|
-
fail("
|
|
437
|
+
if (!branchMode && continueTickets.length > 1) {
|
|
438
|
+
fail("current-branch build recovery supports exactly one --ticket");
|
|
419
439
|
}
|
|
420
440
|
if (branchMode) {
|
|
421
441
|
if (opts.resume && continueTickets.length > 1) {
|
|
@@ -424,13 +444,10 @@ export function buildStartCommand() {
|
|
|
424
444
|
if ((opts.resume || opts.continue) && continueTickets.length === 0 && !recoveryRecord) {
|
|
425
445
|
fail(branchContinueTicketHelp(cwd, opts.resume ? "--resume" : "--continue"));
|
|
426
446
|
}
|
|
427
|
-
if (continueTickets.length > 0 && !(opts.resume || opts.continue)) {
|
|
428
|
-
fail("--ticket in branch mode requires --continue or --resume <sessionId>");
|
|
429
|
-
}
|
|
430
447
|
if (opts.tickets)
|
|
431
|
-
fail("--tickets is not supported with
|
|
448
|
+
fail("--tickets is not supported with an isolated branch run; initialize and use .tickets/tickets.yaml");
|
|
432
449
|
if (!isTicketsInitialized(cwd))
|
|
433
|
-
fail("
|
|
450
|
+
fail("isolated branch runs require initialized .tickets/ (run ai-foreman tickets init)");
|
|
434
451
|
if (branchDefaults.createReview && !branchDefaults.reviewProvider) {
|
|
435
452
|
fail("PR/MR completion is enabled but no GitHub or GitLab origin remote was detected; pass --provider github|gitlab or --completion none");
|
|
436
453
|
}
|
|
@@ -440,6 +457,60 @@ export function buildStartCommand() {
|
|
|
440
457
|
if (!branchMode && opts.continue && !resumeSessionId) {
|
|
441
458
|
fail(`no previous session id found under ${join(cwd, ".foreman")}`);
|
|
442
459
|
}
|
|
460
|
+
if (selectedNewTicket) {
|
|
461
|
+
if (!isTicketsInitialized(cwd))
|
|
462
|
+
fail("--ticket requires initialized .tickets/ (run `rafi tickets init`)");
|
|
463
|
+
const eligibilityConfig = loadTicketsConfig(cwd);
|
|
464
|
+
const eligibilityPaths = resolveTicketPaths(eligibilityConfig, cwd);
|
|
465
|
+
const eligibilityDefs = loadTickets(eligibilityPaths.tickets);
|
|
466
|
+
const eligibilityDb = new StateDb(eligibilityPaths.stateDb);
|
|
467
|
+
const eligibilityStates = eligibilityDb.getAllStates();
|
|
468
|
+
eligibilityDb.close();
|
|
469
|
+
const activeLease = readCurrentWorkflowLease(cwd);
|
|
470
|
+
let assessment = evaluateTicketEligibility({
|
|
471
|
+
tickets: eligibilityDefs,
|
|
472
|
+
states: eligibilityStates,
|
|
473
|
+
implementationLimit: eligibilityConfig.implementationLimit,
|
|
474
|
+
delivery: deliveryConfig,
|
|
475
|
+
activeLease: activeLease ? { runId: activeLease.runId } : undefined,
|
|
476
|
+
}, selectedNewTicket);
|
|
477
|
+
if (!assessment.eligible) {
|
|
478
|
+
console.error(`foreman: ${selectedNewTicket} is not eligible:`);
|
|
479
|
+
for (const blocker of assessment.blockers)
|
|
480
|
+
console.error(` - ${blocker.detail}`);
|
|
481
|
+
const dependency = assessment.eligibleDependencies[0];
|
|
482
|
+
if (!dependency || opts.yes || !process.stdin.isTTY || !process.stdout.isTTY)
|
|
483
|
+
fail(`cannot start ${selectedNewTicket}`);
|
|
484
|
+
const answer = await select({ message: `Start eligible dependency ${dependency.id}: ${dependency.title} instead?`, options: [
|
|
485
|
+
{ value: "dependency", label: `Start ${dependency.id} (Recommended)` },
|
|
486
|
+
{ value: "cancel", label: "Cancel" },
|
|
487
|
+
] });
|
|
488
|
+
if (isCancel(answer) || answer === "cancel") {
|
|
489
|
+
console.log("ai-foreman: cancelled; nothing changed");
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
selectedNewTicket = dependency.id;
|
|
493
|
+
assessment = evaluateTicketEligibility({ tickets: eligibilityDefs, states: eligibilityStates, implementationLimit: eligibilityConfig.implementationLimit, delivery: deliveryConfig }, selectedNewTicket);
|
|
494
|
+
}
|
|
495
|
+
const recommended = eligibleTickets({ tickets: eligibilityDefs, states: eligibilityStates, implementationLimit: eligibilityConfig.implementationLimit, delivery: deliveryConfig })[0];
|
|
496
|
+
if (assessment.eligible && recommended && recommended.id !== selectedNewTicket) {
|
|
497
|
+
console.log(`foreman: recommended ${recommended.id}: ${recommended.title}`);
|
|
498
|
+
console.log(`foreman: requested ${assessment.ticket.id}: ${assessment.ticket.title}`);
|
|
499
|
+
if (!opts.yes) {
|
|
500
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
501
|
+
fail(`starting non-recommended ${selectedNewTicket} requires --yes`);
|
|
502
|
+
const answer = await select({ message: `Start ${selectedNewTicket} anyway?`, options: [
|
|
503
|
+
{ value: "start", label: `Start ${selectedNewTicket}` }, { value: "cancel", label: "Cancel" },
|
|
504
|
+
] });
|
|
505
|
+
if (isCancel(answer) || answer === "cancel") {
|
|
506
|
+
console.log("ai-foreman: cancelled; nothing changed");
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const recoveryTicket = !branchMode && recoveryRecord ? continueTickets[0] : undefined;
|
|
513
|
+
const preferredTicket = selectedNewTicket ?? recoveryTicket;
|
|
443
514
|
const config = loadConfig(join(cwd, "foreman.yaml"));
|
|
444
515
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
445
516
|
const logPath = join(cwd, ".foreman", `${stamp}.jsonl`);
|
|
@@ -531,9 +602,15 @@ export function buildStartCommand() {
|
|
|
531
602
|
const allowedBaseDirtyPaths = generatedTrackerDirtyPaths(ticketsConfig.paths);
|
|
532
603
|
const baseWorktreePolicy = recoveryRecord ? "skip" : "enforce";
|
|
533
604
|
if (baseWorktreePolicy === "skip") {
|
|
534
|
-
const
|
|
535
|
-
if (
|
|
536
|
-
console.
|
|
605
|
+
const projection = recoveryRecord ? projectBuildRecovery(cwd, recoveryRecord) : undefined;
|
|
606
|
+
if (projection?.expectedChanges.length) {
|
|
607
|
+
console.log(`foreman: preserved expected in-progress work: ${projection.expectedChanges.join(", ")}`);
|
|
608
|
+
}
|
|
609
|
+
if (projection?.unexpectedChanges.length) {
|
|
610
|
+
console.warn("foreman: warning: unexpected changes may conflict with recovery; recovery is still allowed:");
|
|
611
|
+
for (const item of projection.unexpectedChanges)
|
|
612
|
+
console.warn(` ${item.path}: ${item.risk}`);
|
|
613
|
+
console.warn("foreman: recommended action: preserve or move overlapping changes before continuing");
|
|
537
614
|
}
|
|
538
615
|
}
|
|
539
616
|
else {
|
|
@@ -563,6 +640,7 @@ export function buildStartCommand() {
|
|
|
563
640
|
db.close();
|
|
564
641
|
const ticketById = new Map(tickets.map((ticket) => [ticket.id, ticket]));
|
|
565
642
|
const resumeSessionByTicket = new Map();
|
|
643
|
+
let auditDependencyCount;
|
|
566
644
|
let plan;
|
|
567
645
|
if (continueTickets.length > 0) {
|
|
568
646
|
const sessions = findResumableBranchSessions(join(cwd, ".foreman"));
|
|
@@ -589,6 +667,8 @@ export function buildStartCommand() {
|
|
|
589
667
|
dependencies: [],
|
|
590
668
|
depth: 1,
|
|
591
669
|
worktreePath: session.worktreePath,
|
|
670
|
+
deliveryUnitId: session.deliveryUnitId,
|
|
671
|
+
deliveryUnitFinal: session.deliveryUnitFinal,
|
|
592
672
|
});
|
|
593
673
|
}
|
|
594
674
|
plan = {
|
|
@@ -596,22 +676,15 @@ export function buildStartCommand() {
|
|
|
596
676
|
nodes,
|
|
597
677
|
issues: [],
|
|
598
678
|
};
|
|
599
|
-
log.write("branch-plan", {
|
|
600
|
-
baseRef: plan.baseRef,
|
|
601
|
-
tickets: plan.nodes.map((node) => node.ticket.id),
|
|
602
|
-
branches: plan.nodes.map((node) => ({ ticket: node.ticket.id, branch: node.branch, base: node.baseBranch })),
|
|
603
|
-
issues: plan.issues,
|
|
604
|
-
resume: true,
|
|
605
|
-
});
|
|
606
679
|
}
|
|
607
680
|
else {
|
|
608
681
|
plan = buildBranchPlan(tickets, states, {
|
|
609
682
|
steps,
|
|
610
|
-
baseRef: opts.base ?? currentGitRef(cwd),
|
|
683
|
+
baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch ?? currentGitRef(cwd),
|
|
611
684
|
branchPrefix: opts.branchPrefix,
|
|
612
685
|
maxBranchDepth,
|
|
613
686
|
rootBaseBranches: branchDefaults.rootBaseBranches,
|
|
614
|
-
ticketIds: selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
687
|
+
ticketIds: selectedNewTicket ? [selectedNewTicket] : selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
615
688
|
});
|
|
616
689
|
let auditBuilder;
|
|
617
690
|
let auditViewer;
|
|
@@ -624,20 +697,14 @@ export function buildStartCommand() {
|
|
|
624
697
|
const auditDependencies = parseAuditDependencies(audit.text);
|
|
625
698
|
plan = buildBranchPlan(tickets, states, {
|
|
626
699
|
steps,
|
|
627
|
-
baseRef: opts.base ?? currentGitRef(cwd),
|
|
700
|
+
baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch ?? currentGitRef(cwd),
|
|
628
701
|
branchPrefix: opts.branchPrefix,
|
|
629
702
|
maxBranchDepth,
|
|
630
703
|
auditDependencies,
|
|
631
704
|
rootBaseBranches: branchDefaults.rootBaseBranches,
|
|
632
|
-
ticketIds: selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
633
|
-
});
|
|
634
|
-
log.write("branch-plan", {
|
|
635
|
-
baseRef: plan.baseRef,
|
|
636
|
-
tickets: plan.nodes.map((node) => node.ticket.id),
|
|
637
|
-
branches: plan.nodes.map((node) => ({ ticket: node.ticket.id, branch: node.branch, base: node.baseBranch })),
|
|
638
|
-
issues: plan.issues,
|
|
639
|
-
auditDependencyCount: auditDependencies.length,
|
|
705
|
+
ticketIds: selectedNewTicket ? [selectedNewTicket] : selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
640
706
|
});
|
|
707
|
+
auditDependencyCount = auditDependencies.length;
|
|
641
708
|
}
|
|
642
709
|
finally {
|
|
643
710
|
await auditBuilder?.close().catch(() => { });
|
|
@@ -654,13 +721,27 @@ export function buildStartCommand() {
|
|
|
654
721
|
if (selectedStacks.length && deliveryConfig) {
|
|
655
722
|
plan = applyExplicitStackTopology(plan, selectedStacks, deliveryConfig, opts.branchPrefix);
|
|
656
723
|
}
|
|
724
|
+
else if (!deliveryRun) {
|
|
725
|
+
const branchPolicy = loadTicketSetupConfig(cwd)?.build.branch_policy;
|
|
726
|
+
if (branchPolicy)
|
|
727
|
+
plan = applySizeBranchPolicy(plan, branchPolicy, deliveryConfig, opts.branchPrefix);
|
|
728
|
+
}
|
|
657
729
|
if (recoveryRecord && opts.resume && plan.nodes[0]) {
|
|
658
730
|
const prior = findResumableBranchSessions(join(cwd, ".foreman")).find((session) => session.ticket === (recoveryRecord.currentTicket ?? plan.nodes[0].ticket.id));
|
|
659
731
|
if (prior)
|
|
660
732
|
resumeSessionByTicket.set(plan.nodes[0].ticket.id, { worktreePath: prior.worktreePath, sessionId: opts.resume });
|
|
661
733
|
}
|
|
662
734
|
}
|
|
663
|
-
|
|
735
|
+
const branchPresentation = presentBranchPlan(plan, {
|
|
736
|
+
stacked: selectedStacks.length > 0,
|
|
737
|
+
resumed: continueTickets.length > 0 || Boolean(recoveryRecord),
|
|
738
|
+
});
|
|
739
|
+
log.write("branch-plan", {
|
|
740
|
+
...branchPlanLogMetadata(plan, branchPresentation),
|
|
741
|
+
...(auditDependencyCount === undefined ? {} : { auditDependencyCount }),
|
|
742
|
+
...(continueTickets.length > 0 || recoveryRecord ? { resume: true } : {}),
|
|
743
|
+
});
|
|
744
|
+
console.log(branchPresentation.banner);
|
|
664
745
|
console.log(`foreman: project ${cwd}`);
|
|
665
746
|
console.log(`foreman: base ${plan.baseRef}`);
|
|
666
747
|
console.log(`foreman: log ${logPath}\n`);
|
|
@@ -680,7 +761,7 @@ export function buildStartCommand() {
|
|
|
680
761
|
console.log();
|
|
681
762
|
if (!opts.yes && plan.issues.every((issue) => !issue.blocking)) {
|
|
682
763
|
const action = await select({
|
|
683
|
-
message:
|
|
764
|
+
message: branchPresentation.prompt,
|
|
684
765
|
options: [
|
|
685
766
|
{ value: "proceed", label: "Proceed" },
|
|
686
767
|
{ value: "cancel", label: "Cancel" },
|
|
@@ -702,7 +783,7 @@ export function buildStartCommand() {
|
|
|
702
783
|
};
|
|
703
784
|
let masterRun = recoveryRecord ? resumeBuildRun(cwd, recoveryRecord.runId, { builder: capturedBranchBuilder, qa: capturedBranchQa, builderSessionId: opts.resume ? String(opts.resume) : null }) : createBuildRun({
|
|
704
785
|
tickets: plan.nodes.map((node) => node.ticket.id), deliveryUnit: selectedStacks.length ? selectedStacks.map((stack) => stack.id).join(",") : deliveryRun?.unit.id,
|
|
705
|
-
repositoryRoot: cwd, branchMode:
|
|
786
|
+
repositoryRoot: cwd, branchMode: branchPresentation.allocationMode, baseRef: plan.baseRef, builder: capturedBranchBuilder, qa: capturedBranchQa,
|
|
706
787
|
});
|
|
707
788
|
const branchHeartbeat = setInterval(() => { masterRun = heartbeatBuildRun(cwd, masterRun); }, 10_000);
|
|
708
789
|
branchHeartbeat.unref();
|
|
@@ -808,7 +889,7 @@ export function buildStartCommand() {
|
|
|
808
889
|
};
|
|
809
890
|
const capturedQa = { role: "qa", source: roleDefaults.qa ? "project" : "provider", make: qaAgent, model: qaModel ?? "default", reasoning: qaEffort ?? "default", fast: qaFast, session_strategy: roleDefaults.qa?.session_strategy ?? "compact", settings_revision: settingsRevision };
|
|
810
891
|
let buildRun = recoveryRecord ? resumeBuildRun(cwd, recoveryRecord.runId, { builder: capturedBuilder, qa: capturedQa, builderSessionId: opts.resume ? String(opts.resume) : null }) : createBuildRun({
|
|
811
|
-
tickets: [], repositoryRoot: cwd, branchMode: "current", builder: capturedBuilder, qa: capturedQa,
|
|
892
|
+
tickets: [], repositoryRoot: cwd, branchMode: "current", baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch, builder: capturedBuilder, qa: capturedQa,
|
|
812
893
|
});
|
|
813
894
|
const heartbeat = setInterval(() => { buildRun = heartbeatBuildRun(cwd, buildRun); }, 10_000);
|
|
814
895
|
heartbeat.unref();
|
|
@@ -827,7 +908,11 @@ export function buildStartCommand() {
|
|
|
827
908
|
reasoning: capturedBuilder.reasoning, fast: capturedBuilder.fast, step: 1, total: steps,
|
|
828
909
|
phase: "builder work session", sessionTransition: resumeSessionId ? "resumed exact session" : "initial session", adapter: () => foreman.builderAdapter(),
|
|
829
910
|
}, (line, snapshot) => {
|
|
830
|
-
|
|
911
|
+
const activity = currentActivity();
|
|
912
|
+
if (activity)
|
|
913
|
+
activity.writePersistent(line);
|
|
914
|
+
else
|
|
915
|
+
console.log(line);
|
|
831
916
|
log.write("agent-status", { ...snapshot });
|
|
832
917
|
const db = new WorkflowDb(cwd);
|
|
833
918
|
try {
|
|
@@ -853,7 +938,7 @@ export function buildStartCommand() {
|
|
|
853
938
|
try {
|
|
854
939
|
console.log("ai-foreman: asking builder to plan the next tickets or steps...\n");
|
|
855
940
|
buildRun = checkpointBuildRun(cwd, buildRun, "before-preflight");
|
|
856
|
-
await foreman.runPreflight(steps, ticketsContent);
|
|
941
|
+
await foreman.runPreflight(steps, ticketsContent, preferredTicket);
|
|
857
942
|
if (foreman.builderSessionId())
|
|
858
943
|
buildRun = persistBuildSession(cwd, buildRun, "builder", foreman.builderSessionId());
|
|
859
944
|
if (foreman.qaSessionId())
|
|
@@ -896,7 +981,15 @@ export function buildStartCommand() {
|
|
|
896
981
|
await foreman.sendPreflightFeedback(String(fb));
|
|
897
982
|
}
|
|
898
983
|
}
|
|
899
|
-
const result = await foreman.runBatch(steps, trackerRelPath)
|
|
984
|
+
const result = await foreman.runBatch(steps, trackerRelPath, (ticketId) => {
|
|
985
|
+
const tickets = buildRun.tickets.includes(ticketId)
|
|
986
|
+
? buildRun.tickets
|
|
987
|
+
: [...buildRun.tickets, ticketId];
|
|
988
|
+
buildRun = checkpointBuildRun(cwd, buildRun, "ticket-selected", {
|
|
989
|
+
tickets,
|
|
990
|
+
currentTicket: ticketId,
|
|
991
|
+
});
|
|
992
|
+
}, preferredTicket);
|
|
900
993
|
if (foreman.qaSessionId())
|
|
901
994
|
buildRun = persistBuildSession(cwd, buildRun, "qa", foreman.qaSessionId());
|
|
902
995
|
buildRun = result.outcome === "all-done" || result.outcome === "plan-complete"
|
|
@@ -1045,7 +1138,7 @@ export function applyExplicitStackTopology(plan, stacks, delivery, branchPrefix)
|
|
|
1045
1138
|
const unit = unitById.get(unitId);
|
|
1046
1139
|
if (!unit)
|
|
1047
1140
|
throw new Error(`stack ${stack.id} references missing delivery unit ${unitId}`);
|
|
1048
|
-
const sharedBranch = unit.branch_mode === "shared" ? `${branchPrefix
|
|
1141
|
+
const sharedBranch = unit.branch_mode === "shared" ? `${branchPrefix || "feature"}/${slug(unitId)}` : undefined;
|
|
1049
1142
|
const unitBaseBranch = predecessorBranch;
|
|
1050
1143
|
for (let ticketIndex = 0; ticketIndex < unit.tickets.length; ticketIndex++) {
|
|
1051
1144
|
const ticketId = unit.tickets[ticketIndex];
|
package/dist/cli/tickets.d.ts
CHANGED
|
@@ -24,6 +24,15 @@ interface PopulateCommandOptions {
|
|
|
24
24
|
yes?: boolean;
|
|
25
25
|
authorizeRetire?: string[];
|
|
26
26
|
}
|
|
27
|
+
export declare function combineRequirementSelections(selected: string[], additions: string): string[];
|
|
27
28
|
export declare function cmdPopulateCli(opts: PopulateCommandOptions): Promise<void>;
|
|
28
|
-
export
|
|
29
|
+
export interface TicketOwnershipReceipt {
|
|
30
|
+
path: string;
|
|
31
|
+
category: "tickets";
|
|
32
|
+
origin: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function buildTicketsCommand(options?: {
|
|
35
|
+
prepareOwnership?: (projectDir: string, receipts: TicketOwnershipReceipt[]) => void;
|
|
36
|
+
recordOwnership?: (projectDir: string, receipts: TicketOwnershipReceipt[]) => void;
|
|
37
|
+
}): Command;
|
|
29
38
|
export {};
|