ai-foreman 1.7.5 → 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 +1 -1
- package/dist/branch/github.js +3 -2
- package/dist/branch/gitlab.js +3 -2
- package/dist/branch/planner.d.ts +5 -0
- package/dist/branch/planner.js +42 -3
- package/dist/buildRuns.d.ts +41 -17
- package/dist/buildRuns.js +243 -22
- package/dist/cli/start.js +104 -24
- package/dist/cli/tickets.d.ts +10 -1
- package/dist/cli/tickets.js +418 -79
- package/dist/foreman.d.ts +4 -4
- package/dist/foreman.js +35 -14
- package/dist/qaReview.d.ts +1 -0
- package/dist/qaReview.js +11 -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/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/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 +7 -3
package/dist/cli/start.js
CHANGED
|
@@ -17,21 +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
25
|
import { branchPlanLogMetadata, presentBranchPlan } from "../branch/presentation.js";
|
|
26
|
-
import { checkpointBuildRun, completeBuildRun, createBuildRun, heartbeatBuildRun, persistBuildSession, readBuildRuns, releaseBuildLease, resumeBuildRun } from "../buildRuns.js";
|
|
26
|
+
import { checkpointBuildRun, completeBuildRun, createBuildRun, heartbeatBuildRun, persistBuildSession, projectBuildRecovery, readBuildRuns, releaseBuildLease, resumeBuildRun } from "../buildRuns.js";
|
|
27
27
|
import { parse as parseYaml } from "yaml";
|
|
28
28
|
import { findResumableBranchSessions, formatBranchContinueCommand, formatBranchSummaryFollowupCommands, } from "../branch/resume.js";
|
|
29
29
|
import { detectGitProvider, loadTicketSetupConfig } from "../tickets/setupConfig.js";
|
|
30
30
|
import { loadDeliveryConfig, selectDeliveryUnitForRun, selectStacksForRun, updateStackDeliveryState } from "../tickets/delivery.js";
|
|
31
31
|
import { AgentStatusReporter } from "../statusReporter.js";
|
|
32
32
|
import { currentActivity, withActivityPhase } from "../activity.js";
|
|
33
|
-
import { WorkflowDb } from "../workflowDb.js";
|
|
33
|
+
import { WorkflowDb, readCurrentWorkflowLease } from "../workflowDb.js";
|
|
34
34
|
import { makeLogPath as makeRoleLogPath, readOnlyPermissionConfig, runRoleInstruction } from "../agentRun.js";
|
|
35
|
+
import { eligibleTickets, evaluateTicketEligibility } from "../tickets/eligibility.js";
|
|
35
36
|
function fail(message) {
|
|
36
37
|
console.error(`foreman: ${message}`);
|
|
37
38
|
process.exit(1);
|
|
@@ -284,13 +285,21 @@ export function buildStartCommand() {
|
|
|
284
285
|
.option("--no-auto-merge-wait", "do not wait for dependency PR/MRs before dependent tickets")
|
|
285
286
|
.option("--auto-merge-timeout-minutes <n>", "auto-merge dependency wait timeout in minutes (blank means no timeout)")
|
|
286
287
|
.option("--base <ref>", "base ref for root ticket branches (default: current branch or HEAD)")
|
|
287
|
-
.option("--branch-prefix <prefix>", "branch name prefix for ticket branches", "
|
|
288
|
+
.option("--branch-prefix <prefix>", "branch name prefix for ticket branches", "feature")
|
|
288
289
|
.option("--max-branch-depth <n>", "maximum selected branch stack depth", "5")
|
|
289
290
|
.option("--pr-ready", "create ready-for-review PRs instead of draft PRs")
|
|
290
291
|
.option("--keep-worktrees", "keep successful ticket worktrees for inspection")
|
|
291
|
-
.option("--ticket <id>", "
|
|
292
|
+
.option("--ticket <id>", "select one new ticket, or identify recovery tickets with --resume/--continue/--recover-run", collectTicket, [])
|
|
292
293
|
.option("--skip-delivery-unit <id>", "skip one unfinished delivery unit for this run", collectTicket, [])
|
|
293
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");
|
|
294
303
|
if (opts.steps !== undefined && opts.stacks !== undefined)
|
|
295
304
|
fail("--steps and --stacks are mutually exclusive");
|
|
296
305
|
if (opts.steps === undefined && opts.stacks === undefined) {
|
|
@@ -419,13 +428,14 @@ export function buildStartCommand() {
|
|
|
419
428
|
fail("choose either --resume <sessionId> or --continue, not both");
|
|
420
429
|
}
|
|
421
430
|
const branchMode = branchDefaults.branchMode;
|
|
422
|
-
const continueTickets =
|
|
431
|
+
const continueTickets = recoveryIntent ? requestedTicketIds : [];
|
|
432
|
+
let selectedNewTicket = !recoveryIntent ? requestedTicketIds[0] : undefined;
|
|
423
433
|
const maxBranchDepth = Number.parseInt(opts.maxBranchDepth, 10);
|
|
424
434
|
if (!Number.isInteger(maxBranchDepth) || maxBranchDepth < 1) {
|
|
425
435
|
fail("--max-branch-depth must be a positive integer");
|
|
426
436
|
}
|
|
427
|
-
if (!branchMode && continueTickets.length >
|
|
428
|
-
fail("
|
|
437
|
+
if (!branchMode && continueTickets.length > 1) {
|
|
438
|
+
fail("current-branch build recovery supports exactly one --ticket");
|
|
429
439
|
}
|
|
430
440
|
if (branchMode) {
|
|
431
441
|
if (opts.resume && continueTickets.length > 1) {
|
|
@@ -434,9 +444,6 @@ export function buildStartCommand() {
|
|
|
434
444
|
if ((opts.resume || opts.continue) && continueTickets.length === 0 && !recoveryRecord) {
|
|
435
445
|
fail(branchContinueTicketHelp(cwd, opts.resume ? "--resume" : "--continue"));
|
|
436
446
|
}
|
|
437
|
-
if (continueTickets.length > 0 && !(opts.resume || opts.continue)) {
|
|
438
|
-
fail("--ticket in branch mode requires --continue or --resume <sessionId>");
|
|
439
|
-
}
|
|
440
447
|
if (opts.tickets)
|
|
441
448
|
fail("--tickets is not supported with an isolated branch run; initialize and use .tickets/tickets.yaml");
|
|
442
449
|
if (!isTicketsInitialized(cwd))
|
|
@@ -450,6 +457,60 @@ export function buildStartCommand() {
|
|
|
450
457
|
if (!branchMode && opts.continue && !resumeSessionId) {
|
|
451
458
|
fail(`no previous session id found under ${join(cwd, ".foreman")}`);
|
|
452
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;
|
|
453
514
|
const config = loadConfig(join(cwd, "foreman.yaml"));
|
|
454
515
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
455
516
|
const logPath = join(cwd, ".foreman", `${stamp}.jsonl`);
|
|
@@ -541,9 +602,15 @@ export function buildStartCommand() {
|
|
|
541
602
|
const allowedBaseDirtyPaths = generatedTrackerDirtyPaths(ticketsConfig.paths);
|
|
542
603
|
const baseWorktreePolicy = recoveryRecord ? "skip" : "enforce";
|
|
543
604
|
if (baseWorktreePolicy === "skip") {
|
|
544
|
-
const
|
|
545
|
-
if (
|
|
546
|
-
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");
|
|
547
614
|
}
|
|
548
615
|
}
|
|
549
616
|
else {
|
|
@@ -613,11 +680,11 @@ export function buildStartCommand() {
|
|
|
613
680
|
else {
|
|
614
681
|
plan = buildBranchPlan(tickets, states, {
|
|
615
682
|
steps,
|
|
616
|
-
baseRef: opts.base ?? currentGitRef(cwd),
|
|
683
|
+
baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch ?? currentGitRef(cwd),
|
|
617
684
|
branchPrefix: opts.branchPrefix,
|
|
618
685
|
maxBranchDepth,
|
|
619
686
|
rootBaseBranches: branchDefaults.rootBaseBranches,
|
|
620
|
-
ticketIds: selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
687
|
+
ticketIds: selectedNewTicket ? [selectedNewTicket] : selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
621
688
|
});
|
|
622
689
|
let auditBuilder;
|
|
623
690
|
let auditViewer;
|
|
@@ -630,12 +697,12 @@ export function buildStartCommand() {
|
|
|
630
697
|
const auditDependencies = parseAuditDependencies(audit.text);
|
|
631
698
|
plan = buildBranchPlan(tickets, states, {
|
|
632
699
|
steps,
|
|
633
|
-
baseRef: opts.base ?? currentGitRef(cwd),
|
|
700
|
+
baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch ?? currentGitRef(cwd),
|
|
634
701
|
branchPrefix: opts.branchPrefix,
|
|
635
702
|
maxBranchDepth,
|
|
636
703
|
auditDependencies,
|
|
637
704
|
rootBaseBranches: branchDefaults.rootBaseBranches,
|
|
638
|
-
ticketIds: selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
705
|
+
ticketIds: selectedNewTicket ? [selectedNewTicket] : selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
639
706
|
});
|
|
640
707
|
auditDependencyCount = auditDependencies.length;
|
|
641
708
|
}
|
|
@@ -654,6 +721,11 @@ 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)
|
|
@@ -711,7 +783,7 @@ export function buildStartCommand() {
|
|
|
711
783
|
};
|
|
712
784
|
let masterRun = recoveryRecord ? resumeBuildRun(cwd, recoveryRecord.runId, { builder: capturedBranchBuilder, qa: capturedBranchQa, builderSessionId: opts.resume ? String(opts.resume) : null }) : createBuildRun({
|
|
713
785
|
tickets: plan.nodes.map((node) => node.ticket.id), deliveryUnit: selectedStacks.length ? selectedStacks.map((stack) => stack.id).join(",") : deliveryRun?.unit.id,
|
|
714
|
-
repositoryRoot: cwd, branchMode: branchPresentation.allocationMode, builder: capturedBranchBuilder, qa: capturedBranchQa,
|
|
786
|
+
repositoryRoot: cwd, branchMode: branchPresentation.allocationMode, baseRef: plan.baseRef, builder: capturedBranchBuilder, qa: capturedBranchQa,
|
|
715
787
|
});
|
|
716
788
|
const branchHeartbeat = setInterval(() => { masterRun = heartbeatBuildRun(cwd, masterRun); }, 10_000);
|
|
717
789
|
branchHeartbeat.unref();
|
|
@@ -817,7 +889,7 @@ export function buildStartCommand() {
|
|
|
817
889
|
};
|
|
818
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 };
|
|
819
891
|
let buildRun = recoveryRecord ? resumeBuildRun(cwd, recoveryRecord.runId, { builder: capturedBuilder, qa: capturedQa, builderSessionId: opts.resume ? String(opts.resume) : null }) : createBuildRun({
|
|
820
|
-
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,
|
|
821
893
|
});
|
|
822
894
|
const heartbeat = setInterval(() => { buildRun = heartbeatBuildRun(cwd, buildRun); }, 10_000);
|
|
823
895
|
heartbeat.unref();
|
|
@@ -866,7 +938,7 @@ export function buildStartCommand() {
|
|
|
866
938
|
try {
|
|
867
939
|
console.log("ai-foreman: asking builder to plan the next tickets or steps...\n");
|
|
868
940
|
buildRun = checkpointBuildRun(cwd, buildRun, "before-preflight");
|
|
869
|
-
await foreman.runPreflight(steps, ticketsContent);
|
|
941
|
+
await foreman.runPreflight(steps, ticketsContent, preferredTicket);
|
|
870
942
|
if (foreman.builderSessionId())
|
|
871
943
|
buildRun = persistBuildSession(cwd, buildRun, "builder", foreman.builderSessionId());
|
|
872
944
|
if (foreman.qaSessionId())
|
|
@@ -909,7 +981,15 @@ export function buildStartCommand() {
|
|
|
909
981
|
await foreman.sendPreflightFeedback(String(fb));
|
|
910
982
|
}
|
|
911
983
|
}
|
|
912
|
-
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);
|
|
913
993
|
if (foreman.qaSessionId())
|
|
914
994
|
buildRun = persistBuildSession(cwd, buildRun, "qa", foreman.qaSessionId());
|
|
915
995
|
buildRun = result.outcome === "all-done" || result.outcome === "plan-complete"
|
|
@@ -1058,7 +1138,7 @@ export function applyExplicitStackTopology(plan, stacks, delivery, branchPrefix)
|
|
|
1058
1138
|
const unit = unitById.get(unitId);
|
|
1059
1139
|
if (!unit)
|
|
1060
1140
|
throw new Error(`stack ${stack.id} references missing delivery unit ${unitId}`);
|
|
1061
|
-
const sharedBranch = unit.branch_mode === "shared" ? `${branchPrefix
|
|
1141
|
+
const sharedBranch = unit.branch_mode === "shared" ? `${branchPrefix || "feature"}/${slug(unitId)}` : undefined;
|
|
1062
1142
|
const unitBaseBranch = predecessorBranch;
|
|
1063
1143
|
for (let ticketIndex = 0; ticketIndex < unit.tickets.length; ticketIndex++) {
|
|
1064
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 {};
|