@idea1/cli 1.9.1 → 1.10.1
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 +123 -66
- package/dist/cli/named-lists.d.ts +1 -1
- package/dist/cli/named-lists.d.ts.map +1 -1
- package/dist/cli/named-lists.js +18 -2
- package/dist/cli/named-lists.js.map +1 -1
- package/dist/cli/parser.d.ts +3 -2
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +4 -3
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/resolver.d.ts +3 -0
- package/dist/cli/resolver.d.ts.map +1 -1
- package/dist/cli/resolver.js +41 -36
- package/dist/cli/resolver.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/approve-pr.d.ts.map +1 -1
- package/dist/commands/approve-pr.js +2 -54
- package/dist/commands/approve-pr.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +11 -6
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +5 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/join-project-item.d.ts.map +1 -1
- package/dist/commands/join-project-item.js +31 -12
- package/dist/commands/join-project-item.js.map +1 -1
- package/dist/commands/next-workflow-step.d.ts.map +1 -1
- package/dist/commands/next-workflow-step.js +1 -0
- package/dist/commands/next-workflow-step.js.map +1 -1
- package/dist/commands/ready-for-approval.d.ts.map +1 -1
- package/dist/commands/ready-for-approval.js +7 -67
- package/dist/commands/ready-for-approval.js.map +1 -1
- package/dist/commands/release.d.ts.map +1 -1
- package/dist/commands/release.js +7 -9
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/ship-local-changes.d.ts +2 -0
- package/dist/commands/ship-local-changes.d.ts.map +1 -1
- package/dist/commands/ship-local-changes.js +30 -5
- package/dist/commands/ship-local-changes.js.map +1 -1
- package/dist/commands/start-agent-implementation.d.ts.map +1 -1
- package/dist/commands/start-agent-implementation.js +4 -1
- package/dist/commands/start-agent-implementation.js.map +1 -1
- package/dist/commands/start-dev-implementation.d.ts.map +1 -1
- package/dist/commands/start-dev-implementation.js +1 -0
- package/dist/commands/start-dev-implementation.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +6 -3
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/url-handler.d.ts +5 -0
- package/dist/commands/url-handler.d.ts.map +1 -0
- package/dist/commands/url-handler.js +38 -0
- package/dist/commands/url-handler.js.map +1 -0
- package/dist/jobs/targets.d.ts +6 -1
- package/dist/jobs/targets.d.ts.map +1 -1
- package/dist/jobs/targets.js +12 -8
- package/dist/jobs/targets.js.map +1 -1
- package/dist/lib/claude-options.d.ts +47 -34
- package/dist/lib/claude-options.d.ts.map +1 -1
- package/dist/lib/claude-options.js +95 -82
- package/dist/lib/claude-options.js.map +1 -1
- package/dist/lib/claude-sessions.generated.d.ts +25 -0
- package/dist/lib/claude-sessions.generated.d.ts.map +1 -0
- package/dist/lib/claude-sessions.generated.js +16 -0
- package/dist/lib/claude-sessions.generated.js.map +1 -0
- package/dist/lib/settings.d.ts +10 -4
- package/dist/lib/settings.d.ts.map +1 -1
- package/dist/lib/settings.js +45 -15
- package/dist/lib/settings.js.map +1 -1
- package/dist/prompts/platform-prompts.d.ts +2 -0
- package/dist/prompts/platform-prompts.d.ts.map +1 -1
- package/dist/prompts/platform-prompts.js +9 -4
- package/dist/prompts/platform-prompts.js.map +1 -1
- package/dist/prompts/project-item-prompts.d.ts +3 -12
- package/dist/prompts/project-item-prompts.d.ts.map +1 -1
- package/dist/prompts/project-item-prompts.js +3 -18
- package/dist/prompts/project-item-prompts.js.map +1 -1
- package/dist/services/claude.d.ts +12 -7
- package/dist/services/claude.d.ts.map +1 -1
- package/dist/services/claude.js +7 -4
- package/dist/services/claude.js.map +1 -1
- package/dist/services/config.d.ts +5 -0
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +9 -0
- package/dist/services/config.js.map +1 -1
- package/dist/services/git.d.ts +5 -0
- package/dist/services/git.d.ts.map +1 -1
- package/dist/services/git.js +15 -3
- package/dist/services/git.js.map +1 -1
- package/dist/services/github.d.ts +25 -7
- package/dist/services/github.d.ts.map +1 -1
- package/dist/services/github.js +23 -9
- package/dist/services/github.js.map +1 -1
- package/dist/services/os-launcher.d.ts +29 -0
- package/dist/services/os-launcher.d.ts.map +1 -0
- package/dist/services/os-launcher.js +104 -0
- package/dist/services/os-launcher.js.map +1 -0
- package/dist/services/platform.d.ts +17 -3
- package/dist/services/platform.d.ts.map +1 -1
- package/dist/services/platform.js +79 -7
- package/dist/services/platform.js.map +1 -1
- package/dist/services/repository-registry.d.ts +11 -0
- package/dist/services/repository-registry.d.ts.map +1 -0
- package/dist/services/repository-registry.js +43 -0
- package/dist/services/repository-registry.js.map +1 -0
- package/dist/services/setup.d.ts.map +1 -1
- package/dist/services/setup.js +21 -1
- package/dist/services/setup.js.map +1 -1
- package/dist/services/url-handler.d.ts +23 -0
- package/dist/services/url-handler.d.ts.map +1 -0
- package/dist/services/url-handler.js +236 -0
- package/dist/services/url-handler.js.map +1 -0
- package/dist/services/worktree.d.ts +1 -19
- package/dist/services/worktree.d.ts.map +1 -1
- package/dist/services/worktree.js +0 -34
- package/dist/services/worktree.js.map +1 -1
- package/dist/types/index.d.ts +27 -25
- package/dist/types/index.d.ts.map +1 -1
- package/dist/workflow/data/project-item.d.ts +4 -0
- package/dist/workflow/data/project-item.d.ts.map +1 -1
- package/dist/workflow/data/team-member.d.ts +14 -0
- package/dist/workflow/data/team-member.d.ts.map +1 -0
- package/dist/workflow/data/team-member.js +2 -0
- package/dist/workflow/data/team-member.js.map +1 -0
- package/dist/workflow/data/workflow-context.d.ts +1 -1
- package/dist/workflow/data/workflow-states.d.ts +11 -2
- package/dist/workflow/data/workflow-states.d.ts.map +1 -1
- package/dist/workflow/data/workflow-states.generated.d.ts +17 -0
- package/dist/workflow/data/workflow-states.generated.d.ts.map +1 -1
- package/dist/workflow/data/workflow-states.generated.js +41 -0
- package/dist/workflow/data/workflow-states.generated.js.map +1 -1
- package/dist/workflow/data/workflow-states.js +21 -1
- package/dist/workflow/data/workflow-states.js.map +1 -1
- package/dist/workflow/engine/agent-session.d.ts +16 -0
- package/dist/workflow/engine/agent-session.d.ts.map +1 -0
- package/dist/workflow/engine/agent-session.js +109 -0
- package/dist/workflow/engine/agent-session.js.map +1 -0
- package/dist/workflow/engine/approval-flow.d.ts +39 -5
- package/dist/workflow/engine/approval-flow.d.ts.map +1 -1
- package/dist/workflow/engine/approval-flow.js +156 -2
- package/dist/workflow/engine/approval-flow.js.map +1 -1
- package/dist/workflow/engine/handlers/agent-implementation.d.ts +5 -4
- package/dist/workflow/engine/handlers/agent-implementation.d.ts.map +1 -1
- package/dist/workflow/engine/handlers/agent-implementation.js +13 -19
- package/dist/workflow/engine/handlers/agent-implementation.js.map +1 -1
- package/dist/workflow/engine/handlers/dev-implementation.js +1 -1
- package/dist/workflow/engine/handlers/dev-implementation.js.map +1 -1
- package/dist/workflow/engine/handlers/ready-for-approval.d.ts +7 -6
- package/dist/workflow/engine/handlers/ready-for-approval.d.ts.map +1 -1
- package/dist/workflow/engine/handlers/ready-for-approval.js +17 -19
- package/dist/workflow/engine/handlers/ready-for-approval.js.map +1 -1
- package/dist/workflow/engine/release-engine.d.ts +16 -5
- package/dist/workflow/engine/release-engine.d.ts.map +1 -1
- package/dist/workflow/engine/release-engine.js +81 -9
- package/dist/workflow/engine/release-engine.js.map +1 -1
- package/dist/workflow/engine/ship-flow.d.ts +15 -4
- package/dist/workflow/engine/ship-flow.d.ts.map +1 -1
- package/dist/workflow/engine/ship-flow.js +80 -21
- package/dist/workflow/engine/ship-flow.js.map +1 -1
- package/dist/workflow/engine/workflow-engine.d.ts +52 -16
- package/dist/workflow/engine/workflow-engine.d.ts.map +1 -1
- package/dist/workflow/engine/workflow-engine.js +146 -31
- package/dist/workflow/engine/workflow-engine.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { logger } from '../../lib/logger.js';
|
|
2
|
-
import {
|
|
2
|
+
import { CLAUDE_ENTRY_POINT_OPTION_NAME } from '../../lib/claude-options.js';
|
|
3
3
|
import { isolationModeOption, resolveIsolationMode } from '../../lib/isolation.js';
|
|
4
4
|
import * as git from '../../services/git.js';
|
|
5
5
|
import * as github from '../../services/github.js';
|
|
6
|
+
import * as platform from '../../services/platform.js';
|
|
6
7
|
import * as worktree from '../../services/worktree.js';
|
|
7
8
|
import { WorkflowBranchState } from '../data/workflow-context.js';
|
|
8
9
|
import { WorkflowStates } from '../data/workflow-states.js';
|
|
9
|
-
import { mergeConfirmationOption } from './approval-flow.js';
|
|
10
|
+
import { approvePrOnBranchWithNoItem, assigneeOption, mergeConfirmationOption, readyForApprovalOnBranchWithNoItem } from './approval-flow.js';
|
|
10
11
|
import { branchCreationOptions } from './branch-setup.js';
|
|
11
12
|
import { reviewersOption } from './handlers/ready-for-approval.js';
|
|
12
13
|
import { advanceAsNeededTo, requireLiveContext, startWorkflowManagementAtCommit } from './workflow-engine.js';
|
|
@@ -155,24 +156,81 @@ export async function carryLocalChanges(transition, changes) {
|
|
|
155
156
|
});
|
|
156
157
|
}
|
|
157
158
|
const { branchName } = requireLiveContext(transition);
|
|
159
|
+
transition.workingPath = await checkOutCarriedBranch(options, changes, branchName, commitSha, treeSha);
|
|
160
|
+
logger.success(`Carried the uncommitted changes onto ${branchName} (${commitSha.substring(0, 12)})`);
|
|
161
|
+
logger.hint(`Working directory: ${transition.workingPath}`);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Give a branch made from carried changes its local checkout per the isolation mode, and make the
|
|
165
|
+
* checkout the working directory; returns its path. In the main repository, the checkout that held
|
|
166
|
+
* the changes switches to the branch with its working tree as it is, which is the branch's tip.
|
|
167
|
+
* Otherwise the branch gets a worktree, and the checkout that held the changes is cleaned of them,
|
|
168
|
+
* unless they changed meanwhile.
|
|
169
|
+
*/
|
|
170
|
+
async function checkOutCarriedBranch(options, changes, branchName, commitSha, treeSha) {
|
|
171
|
+
const { sourceBranch, checkoutPath } = changes;
|
|
158
172
|
if (resolveIsolationMode(options) === 'main-repo') {
|
|
159
173
|
await git.switchToCommitKeepingWorkingTree(branchName, commitSha);
|
|
160
|
-
|
|
174
|
+
return checkoutPath;
|
|
175
|
+
}
|
|
176
|
+
const { workingPath } = await worktree.ensureBranchEnvironment('worktree', branchName);
|
|
177
|
+
if (await git.writeWorkingTreeTree() === treeSha) {
|
|
178
|
+
await git.discardWorkingTreeChanges();
|
|
179
|
+
logger.dim(`Removed the carried changes from ${checkoutPath}, which stays on ${sourceBranch}`);
|
|
161
180
|
}
|
|
162
181
|
else {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
182
|
+
logger.warning(`The changes in ${checkoutPath} changed while they were carried, so they were left there. Discard them once you have checked ${branchName}.`);
|
|
183
|
+
}
|
|
184
|
+
process.chdir(workingPath);
|
|
185
|
+
return workingPath;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Ship the uncommitted changes of the base branch checkout with no project item: they are committed
|
|
189
|
+
* onto a new branch on origin, which gets a PR into the base branch assigned to the person running
|
|
190
|
+
* the command, and the PR merges once the required checks pass - `ready-for-approval` and
|
|
191
|
+
* `approve-pr` on a branch with no item, with nothing asked on the way. No workflow status is
|
|
192
|
+
* recorded anywhere.
|
|
193
|
+
*
|
|
194
|
+
* Reads `commit-message`, `branch-name`, `base-branch` and `isolation` from the options.
|
|
195
|
+
*/
|
|
196
|
+
export async function shipWithoutItem(options) {
|
|
197
|
+
const changes = await findLocalChanges(options['base-branch'] || await worktree.resolveBaseBranch());
|
|
198
|
+
if (!changes) {
|
|
199
|
+
throw new Error('There are no uncommitted changes to ship.');
|
|
200
|
+
}
|
|
201
|
+
const { sourceBranch, checkoutPath } = changes;
|
|
202
|
+
process.chdir(checkoutPath);
|
|
203
|
+
logger.section(`Carry uncommitted changes from ${sourceBranch}`);
|
|
204
|
+
const self = await platform.resolveTeammate(await github.getCurrentLogin());
|
|
205
|
+
const message = options['commit-message'] || `Changes carried from ${sourceBranch}`;
|
|
206
|
+
const shortBranchName = options['branch-name'] || carriedBranchName(options['commit-message']);
|
|
207
|
+
const branchName = `${await worktree.resolveBranchPrefix()}/${shortBranchName}`;
|
|
208
|
+
if (await git.branchExists(branchName)) {
|
|
209
|
+
throw new Error(`Branch '${branchName}' already exists. Pass --branch-name to choose a different name.`);
|
|
173
210
|
}
|
|
211
|
+
const treeSha = await git.writeWorkingTreeTree();
|
|
212
|
+
const commitSha = await git.commitTree(treeSha, 'HEAD', message);
|
|
213
|
+
await git.pushCommitToBranch(branchName, commitSha, null);
|
|
214
|
+
await checkOutCarriedBranch(options, changes, branchName, commitSha, treeSha);
|
|
174
215
|
logger.success(`Carried the uncommitted changes onto ${branchName} (${commitSha.substring(0, 12)})`);
|
|
175
|
-
|
|
216
|
+
const branchOptions = {
|
|
217
|
+
...options,
|
|
218
|
+
[assigneeOption.longName]: self.email,
|
|
219
|
+
[reviewersOption.longName]: options[reviewersOption.longName] ?? '',
|
|
220
|
+
[mergeConfirmationOption.longName]: 'true',
|
|
221
|
+
'base-branch': sourceBranch,
|
|
222
|
+
title: message
|
|
223
|
+
};
|
|
224
|
+
await readyForApprovalOnBranchWithNoItem(branchName, branchOptions);
|
|
225
|
+
await approvePrOnBranchWithNoItem(branchName, true);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The name, under the branch prefix, of a branch made from carried changes with no item to name it:
|
|
229
|
+
* the commit message as a slug, else the time.
|
|
230
|
+
*/
|
|
231
|
+
function carriedBranchName(commitMessage) {
|
|
232
|
+
const slug = (commitMessage ?? '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 40).replace(/-+$/, '');
|
|
233
|
+
return slug || `changes-${new Date().toISOString().replace(/\D/g, '').substring(0, 12)}`;
|
|
176
234
|
}
|
|
177
235
|
/**
|
|
178
236
|
* The tip on origin of an item's live branch that changes can be carried onto: one cut from the
|
|
@@ -203,17 +261,18 @@ async function requireBranchWithNoCommitsOfItsOwn(context, sourceBranch) {
|
|
|
203
261
|
/**
|
|
204
262
|
* Advance the item through every workflow state up to Approved and Merged, asking nothing: the
|
|
205
263
|
* states on the way get the options a person would otherwise be asked for. No Claude session is
|
|
206
|
-
* started on the way through Agent Implementation, the PR
|
|
207
|
-
* command
|
|
208
|
-
*
|
|
209
|
-
*
|
|
264
|
+
* started on the way through Agent Implementation, the item and its PR are assigned to the person
|
|
265
|
+
* running the command - the team member whose linked GitHub account `gh` is signed in as - who
|
|
266
|
+
* merges it once the required checks pass (which brings the base branch's local checkout up to
|
|
267
|
+
* date with the merge). Each state runs its usual automation, so a run that stops - on failing
|
|
268
|
+
* checks, say - continues from where it stopped when run again.
|
|
210
269
|
*/
|
|
211
270
|
export async function approveAndMerge(transition) {
|
|
212
|
-
const
|
|
271
|
+
const self = await platform.resolveTeammate(await github.getCurrentLogin());
|
|
213
272
|
Object.assign(transition.options, {
|
|
214
273
|
[isolationModeOption.longName]: resolveIsolationMode(transition.options),
|
|
215
|
-
[
|
|
216
|
-
|
|
274
|
+
[CLAUDE_ENTRY_POINT_OPTION_NAME]: 'none',
|
|
275
|
+
[assigneeOption.longName]: self.email,
|
|
217
276
|
[reviewersOption.longName]: transition.options[reviewersOption.longName] ?? '',
|
|
218
277
|
[mergeConfirmationOption.longName]: 'true'
|
|
219
278
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ship-flow.js","sourceRoot":"","sources":["../../../src/workflow/engine/ship-flow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ship-flow.js","sourceRoot":"","sources":["../../../src/workflow/engine/ship-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,17 +24,32 @@ import { type WorkflowState } from '../data/workflow-states.js';
|
|
|
24
24
|
*
|
|
25
25
|
* A command resolves the transition it operates on and, when it changes the item, calls the
|
|
26
26
|
* operation that says so: `advanceAsNeededTo` to move the item on, `reenterState` to run the
|
|
27
|
-
* automation of the state it is already in.
|
|
27
|
+
* automation of the state it is already in, `advanceOrReenter` for either. They all put the item under workflow management on the way
|
|
28
28
|
* in - an item with no live branch gets one, created on origin and recorded on the platform as the
|
|
29
29
|
* item's - so a command never arranges that for itself and every handler runs on a branch.
|
|
30
30
|
*
|
|
31
31
|
* The workflow changes the item's context; the branch itself carries the work. A branch's PR is
|
|
32
32
|
* therefore gated on a CI/CD run of exactly what merges, and approving merges that run's head.
|
|
33
33
|
*
|
|
34
|
+
* Entering some states takes a teammate assignment for the item (`getAssignmentRequirement`):
|
|
35
|
+
* optional on the way into the early states, required on the way into Needs Approval for Release,
|
|
36
|
+
* where the assignee approves the pull request. The engine asks for the teammate once per
|
|
37
|
+
* transition, before any slow work, and records the assignment with the first such state it
|
|
38
|
+
* enters. An item out of scope (in no project) has no assignee.
|
|
39
|
+
*
|
|
34
40
|
* One thing lives locally, and only here: the item URL of each branch is remembered in the clone's
|
|
35
41
|
* git config, so "is this branch under workflow?" and "which item?" are answered instantly for
|
|
36
42
|
* display. The platform is the truth.
|
|
37
43
|
*/
|
|
44
|
+
/**
|
|
45
|
+
* An item's place in the project plan, as far as a transition needs it.
|
|
46
|
+
*/
|
|
47
|
+
export interface ProjectItemPlan {
|
|
48
|
+
/** The Project the item is planned in; null for an item out of scope, which has no assignee. */
|
|
49
|
+
projectId: string | null;
|
|
50
|
+
/** The team member the item is assigned to; null when it is unassigned. */
|
|
51
|
+
assignedToUserId: string | null;
|
|
52
|
+
}
|
|
38
53
|
/**
|
|
39
54
|
* One project item moving through the workflow, as the engine and the state handlers see it.
|
|
40
55
|
*/
|
|
@@ -44,11 +59,18 @@ export interface WorkflowTransition {
|
|
|
44
59
|
context: WorkflowContext | null;
|
|
45
60
|
/** The item's current state; null when it is outside workflow management. */
|
|
46
61
|
currentState: WorkflowState | null;
|
|
62
|
+
/** The item's place in the project plan, as read before the transition. */
|
|
63
|
+
plan: ProjectItemPlan;
|
|
47
64
|
/** Local checkout of the branch (worktree or main repo), or null when none exists. */
|
|
48
65
|
workingPath: string | null;
|
|
49
66
|
/** Resolved command options the handlers read (model, effort, assignee, ...). */
|
|
50
67
|
options: Record<string, string>;
|
|
51
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* What is known of a project item before a transition moves it: enough to ask for everything the
|
|
71
|
+
* transition will need.
|
|
72
|
+
*/
|
|
73
|
+
export type ItemBeforeTransition = Pick<WorkflowTransition, 'projectItemUrl' | 'currentState' | 'plan'>;
|
|
52
74
|
/**
|
|
53
75
|
* Side effects attached to entering a workflow state. `prepare` runs before the new state is
|
|
54
76
|
* recorded (it must succeed for the transition to happen); `complete` runs after the state has been
|
|
@@ -104,12 +126,10 @@ export interface TargetSummary {
|
|
|
104
126
|
target: string;
|
|
105
127
|
/** What progress output calls the target: `#<id> <title>` for an item, the branch name otherwise. */
|
|
106
128
|
label: string;
|
|
107
|
-
/** The item
|
|
108
|
-
|
|
129
|
+
/** The project item, with its state (null outside workflow management); null for a branch with no project item. */
|
|
130
|
+
item: ItemBeforeTransition | null;
|
|
109
131
|
/** The branch carrying the work; null for an item with no live branch in this repository. */
|
|
110
132
|
branchName: string | null;
|
|
111
|
-
/** The item's state; null for an item outside workflow management or a branch with no item. */
|
|
112
|
-
currentState: WorkflowState | null;
|
|
113
133
|
}
|
|
114
134
|
/**
|
|
115
135
|
* What a target is - its item, branch and state - read without moving into any checkout or
|
|
@@ -135,27 +155,43 @@ export declare function startWorkflowManagementAtCommit(transition: WorkflowTran
|
|
|
135
155
|
/**
|
|
136
156
|
* Move the item to `targetState` one state at a time: each state on the way is entered in order -
|
|
137
157
|
* its handler prepares, the state is recorded on the item, its handler completes - exactly as
|
|
138
|
-
* advancing to each state with its own command would. The options of every handler on the way
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
158
|
+
* advancing to each state with its own command would. The options of every handler on the way,
|
|
159
|
+
* and the teammate assignment when a state on the way takes one, are asked for before anything
|
|
160
|
+
* else happens. An item that is not under workflow management yet is put under it first, so
|
|
161
|
+
* advancing an item that has never been started to the first state is what starts it; an item
|
|
162
|
+
* already at or past `targetState` keeps its state and only gains its branch, so advancing again
|
|
163
|
+
* is a no-op and a run that stopped part way continues where it stopped.
|
|
164
|
+
*
|
|
165
|
+
* The assignment is recorded once, with the first state entered that takes one. Creating the
|
|
166
|
+
* branch of an item outside workflow management enters the first state by itself, so that state is
|
|
167
|
+
* recorded again with the assignment right after.
|
|
143
168
|
*/
|
|
144
169
|
export declare function advanceAsNeededTo(transition: WorkflowTransition, targetState: WorkflowState): Promise<void>;
|
|
145
170
|
/**
|
|
146
171
|
* Run the automation of the state the item is already in, without a transition: the state's
|
|
147
172
|
* handler prepares and completes again, so its working environment is set up and its session
|
|
148
173
|
* starts afresh. The item's status stays as it is; a pull request the handler found or opened for
|
|
149
|
-
* the branch is recorded on the item alongside it.
|
|
174
|
+
* the branch is recorded on the item alongside it. A state that requires a teammate assignment
|
|
175
|
+
* requires it again: the assignee is asked for, as entering the state does, and recorded.
|
|
150
176
|
*/
|
|
151
177
|
export declare function reenterState(transition: WorkflowTransition, state: WorkflowState): Promise<void>;
|
|
152
178
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
|
|
179
|
+
* Bring the item to `targetState` with that state's automation run: an item already in it re-enters
|
|
180
|
+
* it (`reenterState`), so the automation runs again - a pull request that needs its assignee gets
|
|
181
|
+
* one - and an item before it advances to it (`advanceAsNeededTo`). An item past it keeps its state.
|
|
182
|
+
*/
|
|
183
|
+
export declare function advanceOrReenter(transition: WorkflowTransition, targetState: WorkflowState): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Ask for everything that advancing the item to `targetState` needs and was not supplied on the
|
|
186
|
+
* command line: the options of every handler on the way, so a handler reached through `next` asks
|
|
187
|
+
* the same questions as its dedicated command would, and the teammate to assign the item to when a
|
|
188
|
+
* state on the way takes an assignment (an item already in a state that requires one is asked again,
|
|
189
|
+
* since entering that state again re-runs its automation). Every answer goes into `options`; the assignment is also
|
|
190
|
+
* returned, as the email of the teammate to record (null when there is none to record). A batch
|
|
191
|
+
* asks this for each of its items, up front, so its unattended runs are asked nothing; an option
|
|
192
|
+
* answered once is not asked again.
|
|
157
193
|
*/
|
|
158
|
-
export declare function resolveOptionsForAdvancing(
|
|
194
|
+
export declare function resolveOptionsForAdvancing(item: ItemBeforeTransition, targetState: WorkflowState, options: Record<string, string>): Promise<string | null>;
|
|
159
195
|
/**
|
|
160
196
|
* The summary of the project item a branch in this repository implements, with the item's workflow
|
|
161
197
|
* context. Null for a branch outside workflow management, which the base branch always is: no item
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-engine.d.ts","sourceRoot":"","sources":["../../../src/workflow/engine/workflow-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow-engine.d.ts","sourceRoot":"","sources":["../../../src/workflow/engine/workflow-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,14 @@ import { logger } from '../../lib/logger.js';
|
|
|
2
2
|
import { resolveOptions } from '../../cli/resolver.js';
|
|
3
3
|
import { resolveIsolationMode } from '../../lib/isolation.js';
|
|
4
4
|
import { isProjectItemUrl } from '../../lib/project-item-url.js';
|
|
5
|
+
import { promptSelect } from '../../lib/prompt.js';
|
|
5
6
|
import * as git from '../../services/git.js';
|
|
6
7
|
import * as github from '../../services/github.js';
|
|
7
8
|
import * as platform from '../../services/platform.js';
|
|
8
9
|
import * as worktree from '../../services/worktree.js';
|
|
9
10
|
import { WorkflowBranchState } from '../data/workflow-context.js';
|
|
10
|
-
import { WorkflowStates, getWorkflowStatesBetween, isStateAtOrAfter, normalizeWorkflowState } from '../data/workflow-states.js';
|
|
11
|
+
import { WorkflowStates, getAssignmentRequirement, getStateAssignmentRequirement, getWorkflowStatesBetween, isStateAtOrAfter, normalizeWorkflowState } from '../data/workflow-states.js';
|
|
12
|
+
import { approverGitHubLogin, assigneeOption } from './approval-flow.js';
|
|
11
13
|
import { collectBranchCreationChoices } from './branch-setup.js';
|
|
12
14
|
import { HANDLERS_BY_STATE, getHandler } from './handlers/index.js';
|
|
13
15
|
/**
|
|
@@ -63,7 +65,7 @@ export async function readTarget(target) {
|
|
|
63
65
|
const branchName = await branchNameForTarget(trimmed);
|
|
64
66
|
const branchItem = await getProjectItemSummaryForBranch(branchName);
|
|
65
67
|
if (!branchItem) {
|
|
66
|
-
return { target: trimmed, label: branchName,
|
|
68
|
+
return { target: trimmed, label: branchName, item: null, branchName };
|
|
67
69
|
}
|
|
68
70
|
item = branchItem;
|
|
69
71
|
}
|
|
@@ -71,11 +73,20 @@ export async function readTarget(target) {
|
|
|
71
73
|
return {
|
|
72
74
|
target: trimmed,
|
|
73
75
|
label: `#${item.externalId} ${item.title}`,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
item: {
|
|
77
|
+
projectItemUrl: item.url,
|
|
78
|
+
currentState: context ? normalizeWorkflowState(context.workflowStatus) : null,
|
|
79
|
+
plan: planOf(item)
|
|
80
|
+
},
|
|
81
|
+
branchName: await liveBranchHere(context) ? context.branchName : null
|
|
77
82
|
};
|
|
78
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* The item's place in the project plan, from its summary.
|
|
86
|
+
*/
|
|
87
|
+
function planOf(item) {
|
|
88
|
+
return { projectId: item.projectId, assignedToUserId: item.assignedToUserId };
|
|
89
|
+
}
|
|
79
90
|
/**
|
|
80
91
|
* The branch a branch-name or PR URL target names: the PR's head branch, or the name itself.
|
|
81
92
|
*/
|
|
@@ -112,6 +123,7 @@ async function transitionForItem(item, options) {
|
|
|
112
123
|
projectItemUrl: item.url,
|
|
113
124
|
context,
|
|
114
125
|
currentState: context ? normalizeWorkflowState(context.workflowStatus) : null,
|
|
126
|
+
plan: planOf(item),
|
|
115
127
|
workingPath: null,
|
|
116
128
|
options
|
|
117
129
|
};
|
|
@@ -132,6 +144,7 @@ async function transitionForManagedBranch(item, options) {
|
|
|
132
144
|
projectItemUrl: item.url,
|
|
133
145
|
context,
|
|
134
146
|
currentState: normalizeWorkflowState(context.workflowStatus),
|
|
147
|
+
plan: planOf(item),
|
|
135
148
|
workingPath: await enterCheckout(context.branchName),
|
|
136
149
|
options
|
|
137
150
|
};
|
|
@@ -302,31 +315,45 @@ async function createRecordedBranch(transition, record, startCommit) {
|
|
|
302
315
|
}
|
|
303
316
|
transition.context = item.workflowContext;
|
|
304
317
|
transition.currentState = item.workflowContext ? normalizeWorkflowState(item.workflowContext.workflowStatus) : null;
|
|
318
|
+
transition.plan = planOf(item);
|
|
305
319
|
transition.workingPath = null;
|
|
306
320
|
}
|
|
307
321
|
/**
|
|
308
322
|
* Move the item to `targetState` one state at a time: each state on the way is entered in order -
|
|
309
323
|
* its handler prepares, the state is recorded on the item, its handler completes - exactly as
|
|
310
|
-
* advancing to each state with its own command would. The options of every handler on the way
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
324
|
+
* advancing to each state with its own command would. The options of every handler on the way,
|
|
325
|
+
* and the teammate assignment when a state on the way takes one, are asked for before anything
|
|
326
|
+
* else happens. An item that is not under workflow management yet is put under it first, so
|
|
327
|
+
* advancing an item that has never been started to the first state is what starts it; an item
|
|
328
|
+
* already at or past `targetState` keeps its state and only gains its branch, so advancing again
|
|
329
|
+
* is a no-op and a run that stopped part way continues where it stopped.
|
|
330
|
+
*
|
|
331
|
+
* The assignment is recorded once, with the first state entered that takes one. Creating the
|
|
332
|
+
* branch of an item outside workflow management enters the first state by itself, so that state is
|
|
333
|
+
* recorded again with the assignment right after.
|
|
315
334
|
*/
|
|
316
335
|
export async function advanceAsNeededTo(transition, targetState) {
|
|
336
|
+
const wasUnmanaged = transition.currentState === null;
|
|
337
|
+
let assignee = await resolveOptionsForAdvancing(transition, targetState, transition.options);
|
|
317
338
|
await startWorkflowManagement(transition, targetState);
|
|
339
|
+
if (assignee !== null && wasUnmanaged && transition.currentState !== null && getStateAssignmentRequirement(transition.currentState) !== 'none') {
|
|
340
|
+
await persistState(transition, transition.currentState, assignee);
|
|
341
|
+
assignee = null;
|
|
342
|
+
}
|
|
318
343
|
if (transition.currentState !== null && isStateAtOrAfter(transition.currentState, targetState)) {
|
|
319
344
|
logger.info(`Already at "${transition.currentState}"; nothing to advance to reach "${targetState}".`);
|
|
320
345
|
return;
|
|
321
346
|
}
|
|
322
|
-
const
|
|
323
|
-
await resolveOptionsForAdvancing(transition.currentState, targetState, transition.options);
|
|
324
|
-
for (const state of steps) {
|
|
347
|
+
for (const state of getWorkflowStatesBetween(transition.currentState, targetState)) {
|
|
325
348
|
logger.status(`Workflow: ${transition.currentState ?? '(not started)'} → ${state}`);
|
|
326
349
|
const handler = HANDLERS_BY_STATE.get(state);
|
|
350
|
+
const assigneeForState = assignee !== null && getStateAssignmentRequirement(state) !== 'none' ? assignee : null;
|
|
327
351
|
await handler?.prepare?.(transition);
|
|
328
|
-
await persistState(transition, state);
|
|
352
|
+
await persistState(transition, state, assigneeForState);
|
|
329
353
|
await handler?.complete?.(transition);
|
|
354
|
+
if (assigneeForState !== null) {
|
|
355
|
+
assignee = null;
|
|
356
|
+
}
|
|
330
357
|
await refreshWorkingPath(transition);
|
|
331
358
|
}
|
|
332
359
|
}
|
|
@@ -348,31 +375,113 @@ async function refreshWorkingPath(transition) {
|
|
|
348
375
|
* Run the automation of the state the item is already in, without a transition: the state's
|
|
349
376
|
* handler prepares and completes again, so its working environment is set up and its session
|
|
350
377
|
* starts afresh. The item's status stays as it is; a pull request the handler found or opened for
|
|
351
|
-
* the branch is recorded on the item alongside it.
|
|
378
|
+
* the branch is recorded on the item alongside it. A state that requires a teammate assignment
|
|
379
|
+
* requires it again: the assignee is asked for, as entering the state does, and recorded.
|
|
352
380
|
*/
|
|
353
381
|
export async function reenterState(transition, state) {
|
|
354
382
|
await startWorkflowManagement(transition, state);
|
|
355
383
|
const handler = getHandler(state);
|
|
384
|
+
const assignee = await resolveAssignee(transition, state, reentryAssignmentRequirement(state), transition.options);
|
|
356
385
|
await resolveHandlerOptions(transition.options, [handler]);
|
|
357
386
|
const recordedPullRequestUrl = requireContext(transition).pullRequestUrl;
|
|
358
387
|
await handler.prepare?.(transition);
|
|
359
|
-
if (requireContext(transition).pullRequestUrl !== recordedPullRequestUrl) {
|
|
360
|
-
await persistState(transition, requireCurrentState(transition));
|
|
388
|
+
if (assignee !== null || requireContext(transition).pullRequestUrl !== recordedPullRequestUrl) {
|
|
389
|
+
await persistState(transition, requireCurrentState(transition), assignee?.email ?? null);
|
|
361
390
|
}
|
|
362
391
|
await handler.complete?.(transition);
|
|
363
392
|
}
|
|
393
|
+
/** Entering a state again takes the assignment its first entry does only when the state requires one. */
|
|
394
|
+
function reentryAssignmentRequirement(state) {
|
|
395
|
+
return getStateAssignmentRequirement(state) === 'required' ? 'required' : 'none';
|
|
396
|
+
}
|
|
364
397
|
/**
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
* its unattended runs are asked nothing; an option answered once is not asked again.
|
|
398
|
+
* Bring the item to `targetState` with that state's automation run: an item already in it re-enters
|
|
399
|
+
* it (`reenterState`), so the automation runs again - a pull request that needs its assignee gets
|
|
400
|
+
* one - and an item before it advances to it (`advanceAsNeededTo`). An item past it keeps its state.
|
|
369
401
|
*/
|
|
370
|
-
export async function
|
|
371
|
-
if (
|
|
402
|
+
export async function advanceOrReenter(transition, targetState) {
|
|
403
|
+
if (transition.currentState === targetState) {
|
|
404
|
+
await reenterState(transition, targetState);
|
|
372
405
|
return;
|
|
373
406
|
}
|
|
374
|
-
|
|
375
|
-
|
|
407
|
+
await advanceAsNeededTo(transition, targetState);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Ask for everything that advancing the item to `targetState` needs and was not supplied on the
|
|
411
|
+
* command line: the options of every handler on the way, so a handler reached through `next` asks
|
|
412
|
+
* the same questions as its dedicated command would, and the teammate to assign the item to when a
|
|
413
|
+
* state on the way takes an assignment (an item already in a state that requires one is asked again,
|
|
414
|
+
* since entering that state again re-runs its automation). Every answer goes into `options`; the assignment is also
|
|
415
|
+
* returned, as the email of the teammate to record (null when there is none to record). A batch
|
|
416
|
+
* asks this for each of its items, up front, so its unattended runs are asked nothing; an option
|
|
417
|
+
* answered once is not asked again.
|
|
418
|
+
*/
|
|
419
|
+
export async function resolveOptionsForAdvancing(item, targetState, options) {
|
|
420
|
+
const fromState = item.currentState;
|
|
421
|
+
const reenters = fromState === targetState && reentryAssignmentRequirement(targetState) === 'required';
|
|
422
|
+
if (fromState !== null && isStateAtOrAfter(fromState, targetState) && !reenters) {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const entered = reenters ? [targetState] : getWorkflowStatesBetween(fromState, targetState);
|
|
426
|
+
const requirement = reenters ? 'required' : getAssignmentRequirement(fromState, targetState);
|
|
427
|
+
const assignee = await resolveAssignee(item, targetState, requirement, options);
|
|
428
|
+
if (assignee !== null && entered.includes(WorkflowStates.NeedsApprovalForRelease)) {
|
|
429
|
+
approverGitHubLogin(assignee);
|
|
430
|
+
}
|
|
431
|
+
await resolveHandlerOptions(options, [...HANDLERS_BY_STATE.values()].filter(handler => entered.includes(handler.state)));
|
|
432
|
+
return assignee?.email ?? null;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* The teammate the item is assigned to on its way to `targetState`. Resolved from the `assignee`
|
|
436
|
+
* option when it was supplied (a teammate's email or GitHub login, or empty for none) and asked for
|
|
437
|
+
* otherwise - with "none", which keeps the current assignee, where the assignment is optional, and
|
|
438
|
+
* from the team's members alone, defaulting to the current assignee, where it is required. The
|
|
439
|
+
* option is left holding the teammate's email, or empty for none. An item out of scope has no
|
|
440
|
+
* assignee: an optional assignment is skipped, a required one refused. Returns the teammate to
|
|
441
|
+
* record, or null when there is nothing to record.
|
|
442
|
+
*/
|
|
443
|
+
async function resolveAssignee(item, targetState, requirement, options) {
|
|
444
|
+
if (requirement === 'none') {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
if (item.plan.projectId === null) {
|
|
448
|
+
if (requirement === 'required') {
|
|
449
|
+
throw new Error(`Assign ${item.projectItemUrl} to a project first: an item out of scope has no assignee, and "${targetState}" needs one.`);
|
|
450
|
+
}
|
|
451
|
+
logger.dim(`${item.projectItemUrl} is out of scope (in no project), so it has no assignee to set.`);
|
|
452
|
+
options[assigneeOption.longName] = '';
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
const members = await platform.listTeamMembers();
|
|
456
|
+
const current = members.find(member => member.userId === item.plan.assignedToUserId) ?? null;
|
|
457
|
+
const supplied = options[assigneeOption.longName];
|
|
458
|
+
const required = requirement === 'required';
|
|
459
|
+
const identity = supplied === undefined ? await promptForAssignee(members, current, required) : supplied.trim();
|
|
460
|
+
const assignee = identity.length > 0
|
|
461
|
+
? await platform.resolveTeammate(identity)
|
|
462
|
+
: keepCurrentAssignee(current, required, targetState);
|
|
463
|
+
options[assigneeOption.longName] = assignee?.email ?? '';
|
|
464
|
+
return assignee;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* No teammate named: none to record where the assignment is optional; where it is required, the
|
|
468
|
+
* current assignee, which then has to exist.
|
|
469
|
+
*/
|
|
470
|
+
function keepCurrentAssignee(current, required, targetState) {
|
|
471
|
+
if (!required) {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
if (!current) {
|
|
475
|
+
throw new Error(`"${targetState}" needs a teammate assignment: pass --assignee <email or GitHub login>, the assignee approves the pull request.`);
|
|
476
|
+
}
|
|
477
|
+
return current;
|
|
478
|
+
}
|
|
479
|
+
async function promptForAssignee(members, current, required) {
|
|
480
|
+
const memberChoices = members.map(member => ({ label: member.email, value: member.email }));
|
|
481
|
+
if (required) {
|
|
482
|
+
return promptSelect('Assign to a teammate', memberChoices, { default: current?.email });
|
|
483
|
+
}
|
|
484
|
+
return promptSelect('Assign to a teammate', [{ label: `None (keep ${current?.email ?? 'unassigned'})`, value: '' }, ...memberChoices], { default: '' });
|
|
376
485
|
}
|
|
377
486
|
async function resolveHandlerOptions(options, handlers) {
|
|
378
487
|
for (const handler of handlers) {
|
|
@@ -397,14 +506,20 @@ async function resolveHandlerOptions(options, handlers) {
|
|
|
397
506
|
}
|
|
398
507
|
/**
|
|
399
508
|
* Record the new state where it lives: the item's workflow context on idea1.ai, together with the
|
|
400
|
-
* pull request of the item's branch once a handler has found or opened it
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
|
|
404
|
-
|
|
509
|
+
* pull request of the item's branch once a handler has found or opened it, and the teammate the
|
|
510
|
+
* item is assigned to when the state takes an assignment (null leaves the assignment as it is).
|
|
511
|
+
* The item's status is the state of the work for every clone and for the web app alike, and its
|
|
512
|
+
* pull request link stays recorded once the pull request merges or closes. A state that could not
|
|
513
|
+
* be recorded is still the state the run reached; an assignment that could not be recorded stops
|
|
514
|
+
* the run, since the assignee is who the work goes to next.
|
|
515
|
+
*/
|
|
516
|
+
async function persistState(transition, targetState, assignedToEmail) {
|
|
405
517
|
const context = requireContext(transition);
|
|
406
518
|
const pullRequestUrl = context.pullRequestUrl.length > 0 ? context.pullRequestUrl : null;
|
|
407
|
-
await platform.setWorkflowStatus(transition.projectItemUrl, targetState, pullRequestUrl);
|
|
519
|
+
const recorded = await platform.setWorkflowStatus(transition.projectItemUrl, targetState, pullRequestUrl, assignedToEmail);
|
|
520
|
+
if (!recorded && assignedToEmail !== null) {
|
|
521
|
+
throw new Error(`Could not record "${targetState}" with the assignment to ${assignedToEmail} on ${transition.projectItemUrl}.`);
|
|
522
|
+
}
|
|
408
523
|
context.workflowStatus = targetState;
|
|
409
524
|
transition.currentState = targetState;
|
|
410
525
|
}
|