lalph 0.3.12 → 0.3.14
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 +73 -9
- package/dist/cli.mjs +96 -37
- package/package.json +1 -1
- package/src/Linear.ts +67 -5
- package/src/commands/agents/add.ts +3 -1
- package/src/commands/agents/edit.ts +5 -3
- package/src/commands/agents/ls.ts +3 -1
- package/src/commands/agents/rm.ts +3 -1
- package/src/commands/agents.ts +6 -2
- package/src/commands/edit.ts +6 -2
- package/src/commands/issue.ts +4 -2
- package/src/commands/plan/tasks.ts +4 -2
- package/src/commands/plan.ts +60 -3
- package/src/commands/projects/add.ts +3 -1
- package/src/commands/projects/edit.ts +3 -1
- package/src/commands/projects/ls.ts +3 -1
- package/src/commands/projects/rm.ts +3 -1
- package/src/commands/projects/toggle.ts +3 -1
- package/src/commands/projects.ts +4 -2
- package/src/commands/root.ts +12 -5
- package/src/commands/sh.ts +3 -1
- package/src/commands/source.ts +3 -1
package/README.md
CHANGED
|
@@ -8,11 +8,12 @@ A LLM agent orchestrator driven by your chosen source of issues.
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
11
|
+
- Pull work from an issue source (GitHub Issues, Linear, etc.) and keep task state in sync
|
|
12
|
+
- Projects to group execution settings (enabled state, concurrency, target branch, git flow, review agent)
|
|
13
|
+
- Agent presets to control which CLI agent runs tasks, with optional label-based routing
|
|
14
|
+
- Plan mode to turn a high-level plan into a spec and generate PRD tasks
|
|
15
|
+
- Git worktrees to support multiple concurrent iterations
|
|
16
|
+
- Optional PR flow with auto-merge and support for issue dependencies
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
@@ -28,15 +29,78 @@ npx -y lalph@latest
|
|
|
28
29
|
|
|
29
30
|
## CLI usage
|
|
30
31
|
|
|
31
|
-
- Run the main loop: `lalph`
|
|
32
|
-
- Run
|
|
32
|
+
- Run the main loop across enabled projects: `lalph`
|
|
33
|
+
- Run a bounded set of iterations per enabled project: `lalph --iterations 1`
|
|
34
|
+
- Configure projects and per-project concurrency: `lalph projects add`
|
|
35
|
+
- Inspect and configure agent presets: `lalph agents ls`
|
|
33
36
|
- Start plan mode: `lalph plan`
|
|
34
|
-
-
|
|
35
|
-
- Choose your issue source: `lalph source`
|
|
37
|
+
- Create an issue from your editor: `lalph issue`
|
|
38
|
+
- Choose your issue source integration (applies to all projects): `lalph source`
|
|
36
39
|
|
|
37
40
|
It is recommended to add `.lalph/` to your `.gitignore` to avoid committing your
|
|
38
41
|
credentials.
|
|
39
42
|
|
|
43
|
+
## Agent presets
|
|
44
|
+
|
|
45
|
+
Agent presets define which CLI agent runs tasks (and with what arguments). Lalph
|
|
46
|
+
always needs a default preset and will prompt you to create one on first run if
|
|
47
|
+
it's missing.
|
|
48
|
+
|
|
49
|
+
Some issue sources support routing: you can associate a preset with a label, and
|
|
50
|
+
issues with that label will run with that preset; anything else uses the default.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
lalph agents ls
|
|
54
|
+
lalph agents add
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Projects
|
|
58
|
+
|
|
59
|
+
Projects bundle execution settings for the current repo: whether it is enabled
|
|
60
|
+
for runs, how many tasks can run concurrently, which branch to target, what git
|
|
61
|
+
flow to use, and whether review is enabled.
|
|
62
|
+
|
|
63
|
+
`lalph` runs across all enabled projects in parallel; for single-project
|
|
64
|
+
commands, you'll be prompted to choose an active project when needed.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
lalph projects add
|
|
68
|
+
lalph projects toggle
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Plan mode
|
|
72
|
+
|
|
73
|
+
Plan mode opens an editor so you can write a high-level plan. On save, lalph
|
|
74
|
+
generates a specification under `--specs` and then creates PRD tasks from it.
|
|
75
|
+
|
|
76
|
+
Use `--dangerous` to skip permission prompts during spec generation, and `--new`
|
|
77
|
+
to create a project before starting plan mode.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
lalph plan
|
|
81
|
+
lalph plan tasks .specs/my-spec.md
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Creating issues
|
|
85
|
+
|
|
86
|
+
`lalph issue` opens a new-issue template in your editor. When you save and close
|
|
87
|
+
the file, the issue is created in the current issue source.
|
|
88
|
+
|
|
89
|
+
Anything below the front matter is used as the issue description.
|
|
90
|
+
|
|
91
|
+
Front matter fields:
|
|
92
|
+
|
|
93
|
+
- `title`: short issue title
|
|
94
|
+
- `priority`: number (0 = none, 1 = urgent, 2 = high, 3 = normal, 4 = low)
|
|
95
|
+
- `estimate`: number of points, or `null`
|
|
96
|
+
- `blockedBy`: array of issue identifiers
|
|
97
|
+
- `autoMerge`: whether to mark this issue for auto-merge when applicable
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
lalph issue
|
|
101
|
+
lalph i
|
|
102
|
+
```
|
|
103
|
+
|
|
40
104
|
## Development
|
|
41
105
|
|
|
42
106
|
- Install dependencies: `pnpm install`
|
package/dist/cli.mjs
CHANGED
|
@@ -144155,16 +144155,49 @@ var LinearError = class extends ErrorClass("lalph/LinearError")({
|
|
|
144155
144155
|
cause: Defect
|
|
144156
144156
|
}) {};
|
|
144157
144157
|
const selectedProjectId = new ProjectSetting("linear.selectedProjectId", String$1);
|
|
144158
|
-
const
|
|
144158
|
+
const createLinearProject = gen(function* () {
|
|
144159
144159
|
const linear = yield* Linear;
|
|
144160
144160
|
const projects = yield* runCollect(linear.projects);
|
|
144161
|
-
const
|
|
144162
|
-
message: "
|
|
144163
|
-
|
|
144164
|
-
|
|
144165
|
-
|
|
144161
|
+
const projectName = yield* text$2({
|
|
144162
|
+
message: "Project name",
|
|
144163
|
+
validate(input) {
|
|
144164
|
+
const name = input.trim();
|
|
144165
|
+
if (name.length === 0) return fail$4("Project name cannot be empty");
|
|
144166
|
+
if (projects.some((project) => project.name.toLowerCase() === name.toLowerCase())) return fail$4("A project with this name already exists");
|
|
144167
|
+
return succeed$1(name);
|
|
144168
|
+
}
|
|
144169
|
+
});
|
|
144170
|
+
const teams = yield* linear.use((client) => client.teams()).pipe(map$8((teamConnection) => teamConnection.nodes));
|
|
144171
|
+
const teamId = yield* autoComplete({
|
|
144172
|
+
message: "Select a team for the new project",
|
|
144173
|
+
choices: teams.map((team) => ({
|
|
144174
|
+
title: team.name,
|
|
144175
|
+
value: team.id
|
|
144166
144176
|
}))
|
|
144167
144177
|
});
|
|
144178
|
+
const created = yield* linear.use((client) => client.createProject({
|
|
144179
|
+
name: projectName,
|
|
144180
|
+
teamIds: [teamId]
|
|
144181
|
+
}));
|
|
144182
|
+
return yield* linear.use(() => created.project);
|
|
144183
|
+
});
|
|
144184
|
+
const selectProject$1 = gen(function* () {
|
|
144185
|
+
const linear = yield* Linear;
|
|
144186
|
+
const choices = [{
|
|
144187
|
+
title: "Create new",
|
|
144188
|
+
value: { _tag: "create" }
|
|
144189
|
+
}, ...(yield* runCollect(linear.projects)).map((project) => ({
|
|
144190
|
+
title: project.name,
|
|
144191
|
+
value: {
|
|
144192
|
+
_tag: "existing",
|
|
144193
|
+
project
|
|
144194
|
+
}
|
|
144195
|
+
}))];
|
|
144196
|
+
const selected = yield* autoComplete({
|
|
144197
|
+
message: "Select a Linear project",
|
|
144198
|
+
choices
|
|
144199
|
+
});
|
|
144200
|
+
const project = selected._tag === "existing" ? selected.project : yield* createLinearProject;
|
|
144168
144201
|
yield* Settings.setProject(selectedProjectId, some$2(project.id));
|
|
144169
144202
|
return project;
|
|
144170
144203
|
});
|
|
@@ -152025,18 +152058,18 @@ const runProject = fnUntraced(function* (options) {
|
|
|
152025
152058
|
}
|
|
152026
152059
|
yield* awaitEmpty(fibers);
|
|
152027
152060
|
}, (effect, options) => annotateLogs(effect, { project: options.project.id }));
|
|
152028
|
-
const iterations = integer("iterations").pipe(withDescription$1("
|
|
152029
|
-
const maxIterationMinutes = integer("max-minutes").pipe(withDescription$1("
|
|
152030
|
-
const stallMinutes = integer("stall-minutes").pipe(withDescription$1("
|
|
152031
|
-
const specsDirectory = directory("specs").pipe(withDescription$1("Directory
|
|
152032
|
-
const verbose = boolean("verbose").pipe(withDescription$1("
|
|
152061
|
+
const iterations = integer("iterations").pipe(withDescription$1("Limit how many task iterations run per enabled project (default: unlimited). Use -i 1 to run a single iteration and exit."), withAlias("i"), withDefault(Number.POSITIVE_INFINITY));
|
|
152062
|
+
const maxIterationMinutes = integer("max-minutes").pipe(withDescription$1("Timeout an iteration if execution (and review, if enabled) exceeds this many minutes (default: LALPH_MAX_MINUTES or 90)."), withFallbackConfig(int("LALPH_MAX_MINUTES")), withDefault(90));
|
|
152063
|
+
const stallMinutes = integer("stall-minutes").pipe(withDescription$1("Fail an iteration if the agent stops responding for this many minutes (default: LALPH_STALL_MINUTES or 5)."), withFallbackConfig(int("LALPH_STALL_MINUTES")), withDefault(5));
|
|
152064
|
+
const specsDirectory = directory("specs").pipe(withDescription$1("Directory where plan specs are written and read (default: LALPH_SPECS or .specs)."), withAlias("s"), withFallbackConfig(string$1("LALPH_SPECS")), withDefault(".specs"));
|
|
152065
|
+
const verbose = boolean("verbose").pipe(withDescription$1("Increase log output for debugging. Use -v when you need detailed logs."), withAlias("v"));
|
|
152033
152066
|
const commandRoot = make$35("lalph", {
|
|
152034
152067
|
iterations,
|
|
152035
152068
|
maxIterationMinutes,
|
|
152036
152069
|
stallMinutes,
|
|
152037
152070
|
specsDirectory,
|
|
152038
152071
|
verbose
|
|
152039
|
-
}).pipe(withHandler(fnUntraced(function* ({ iterations, maxIterationMinutes, stallMinutes, specsDirectory }) {
|
|
152072
|
+
}).pipe(withDescription("Run the task loop across all enabled projects in parallel: pull issues from the current issue source and execute them with your configured agent preset(s). Use --iterations for a bounded run, and configure per-project concurrency via lalph projects edit."), withHandler(fnUntraced(function* ({ iterations, maxIterationMinutes, stallMinutes, specsDirectory }) {
|
|
152040
152073
|
yield* getDefaultCliAgentPreset;
|
|
152041
152074
|
let allProjects = yield* getAllProjects;
|
|
152042
152075
|
if (allProjects.length === 0) {
|
|
@@ -152115,8 +152148,8 @@ const agentTasker = fnUntraced(function* (options) {
|
|
|
152115
152148
|
const specificationPath = path("spec", {
|
|
152116
152149
|
pathType: "file",
|
|
152117
152150
|
mustExist: true
|
|
152118
|
-
}).pipe(withDescription$2("Path to
|
|
152119
|
-
const commandPlanTasks = make$35("tasks", { specificationPath }).pipe(withDescription("Convert
|
|
152151
|
+
}).pipe(withDescription$2("Required. Path to an existing specification file to convert into tasks"));
|
|
152152
|
+
const commandPlanTasks = make$35("tasks", { specificationPath }).pipe(withDescription("Convert an existing specification file into PRD tasks (without re-running plan mode)"), withHandler(fnUntraced(function* ({ specificationPath }) {
|
|
152120
152153
|
const { specsDirectory } = yield* commandRoot;
|
|
152121
152154
|
const fs = yield* FileSystem;
|
|
152122
152155
|
const pathService = yield* Path$1;
|
|
@@ -152176,12 +152209,12 @@ var Editor = class extends Service$1()("lalph/Editor", { make: gen(function* ()
|
|
|
152176
152209
|
|
|
152177
152210
|
//#endregion
|
|
152178
152211
|
//#region src/commands/plan.ts
|
|
152179
|
-
const dangerous = boolean("dangerous").pipe(withAlias("d"), withDescription$1("
|
|
152180
|
-
const withNewProject = boolean("new").pipe(withAlias("n"), withDescription$1("Create a new project before starting plan mode"));
|
|
152212
|
+
const dangerous = boolean("dangerous").pipe(withAlias("d"), withDescription$1("Skip permission prompts while generating the specification from your plan"));
|
|
152213
|
+
const withNewProject = boolean("new").pipe(withAlias("n"), withDescription$1("Create a new project (via prompts) before starting plan mode"));
|
|
152181
152214
|
const commandPlan = make$35("plan", {
|
|
152182
152215
|
dangerous,
|
|
152183
152216
|
withNewProject
|
|
152184
|
-
}).pipe(withDescription("
|
|
152217
|
+
}).pipe(withDescription("Open an editor to draft a plan; on save, generate a specification under --specs and then create PRD tasks from it. Use --new to create a project first; use --dangerous to skip permission prompts during spec generation."), withHandler(fnUntraced(function* ({ dangerous, withNewProject }) {
|
|
152185
152218
|
const thePlan = yield* (yield* Editor).editTemp({ suffix: ".md" });
|
|
152186
152219
|
if (isNone(thePlan)) return;
|
|
152187
152220
|
yield* gen(function* () {
|
|
@@ -152207,6 +152240,10 @@ const plan = fnUntraced(function* (options) {
|
|
|
152207
152240
|
preset
|
|
152208
152241
|
});
|
|
152209
152242
|
const planDetails = yield* pipe(fs.readFileString(pathService.join(worktree.directory, ".lalph", "plan.json")), flatMap$2(decodeEffect(PlanDetails)), mapError$2(() => new SpecNotFound()));
|
|
152243
|
+
if (isSome(options.targetBranch)) yield* commitAndPushSpecification({
|
|
152244
|
+
specsDirectory: options.specsDirectory,
|
|
152245
|
+
targetBranch: options.targetBranch.value
|
|
152246
|
+
});
|
|
152210
152247
|
yield* log$1("Converting specification into tasks");
|
|
152211
152248
|
yield* agentTasker({
|
|
152212
152249
|
specificationPath: planDetails.specification,
|
|
@@ -152224,6 +152261,28 @@ const plan = fnUntraced(function* (options) {
|
|
|
152224
152261
|
var SpecNotFound = class extends TaggedError("SpecNotFound") {
|
|
152225
152262
|
message = "The AI agent failed to produce a specification.";
|
|
152226
152263
|
};
|
|
152264
|
+
var SpecGitError = class extends TaggedError("SpecGitError") {};
|
|
152265
|
+
const commitAndPushSpecification = fnUntraced(function* (options) {
|
|
152266
|
+
const worktree = yield* Worktree;
|
|
152267
|
+
const absSpecsDirectory = (yield* Path$1).join(worktree.directory, options.specsDirectory);
|
|
152268
|
+
const git = (args) => make$23("git", [...args], {
|
|
152269
|
+
cwd: worktree.directory,
|
|
152270
|
+
stdout: "inherit",
|
|
152271
|
+
stderr: "inherit"
|
|
152272
|
+
}).pipe(exitCode);
|
|
152273
|
+
if ((yield* git(["add", absSpecsDirectory])) !== 0) return yield* new SpecGitError({ message: "Failed to stage specification changes." });
|
|
152274
|
+
if ((yield* git([
|
|
152275
|
+
"commit",
|
|
152276
|
+
"-m",
|
|
152277
|
+
"Update plan specification"
|
|
152278
|
+
])) !== 0) return yield* new SpecGitError({ message: "Failed to commit the generated specification changes." });
|
|
152279
|
+
const parsed = parseBranch(options.targetBranch);
|
|
152280
|
+
yield* git([
|
|
152281
|
+
"push",
|
|
152282
|
+
parsed.remote,
|
|
152283
|
+
`HEAD:${parsed.branch}`
|
|
152284
|
+
]);
|
|
152285
|
+
}, ignore({ log: "Warn" }));
|
|
152227
152286
|
const PlanDetails = fromJsonString(Struct({ specification: String$1 }));
|
|
152228
152287
|
|
|
152229
152288
|
//#endregion
|
|
@@ -152279,8 +152338,8 @@ const handler$1 = flow(withHandler(fnUntraced(function* () {
|
|
|
152279
152338
|
console.log(`URL: ${created.url}`);
|
|
152280
152339
|
}).pipe(provide$1([layerProjectIdPrompt, CurrentIssueSource.layer]));
|
|
152281
152340
|
})), provide(Editor.layer));
|
|
152282
|
-
const commandIssue = make$35("issue").pipe(withDescription("Create a new issue in
|
|
152283
|
-
const commandIssueAlias = make$35("i").pipe(withDescription("Alias for 'issue'
|
|
152341
|
+
const commandIssue = make$35("issue").pipe(withDescription("Create a new issue in your editor."), handler$1);
|
|
152342
|
+
const commandIssueAlias = make$35("i").pipe(withDescription("Alias for 'issue' (create a new issue in your editor)."), handler$1);
|
|
152284
152343
|
|
|
152285
152344
|
//#endregion
|
|
152286
152345
|
//#region src/commands/edit.ts
|
|
@@ -152288,20 +152347,20 @@ const handler = withHandler(fnUntraced(function* () {
|
|
|
152288
152347
|
const prd = yield* Prd;
|
|
152289
152348
|
yield* (yield* Editor).edit(prd.path);
|
|
152290
152349
|
}, provide$1([Prd.layerLocalProvided.pipe(provideMerge(layerProjectIdPrompt)), Editor.layer])));
|
|
152291
|
-
const commandEdit = make$35("edit").pipe(withDescription("Open the prd.yml
|
|
152292
|
-
const commandEditAlias = make$35("e").pipe(withDescription("Alias for 'edit'
|
|
152350
|
+
const commandEdit = make$35("edit").pipe(withDescription("Open the selected project's .lalph/prd.yml in your editor."), handler);
|
|
152351
|
+
const commandEditAlias = make$35("e").pipe(withDescription("Alias for 'edit' (open the selected project's .lalph/prd.yml in your editor)."), handler);
|
|
152293
152352
|
|
|
152294
152353
|
//#endregion
|
|
152295
152354
|
//#region src/commands/source.ts
|
|
152296
|
-
const commandSource = make$35("source").pipe(withDescription("Select the issue source to use"), withHandler(() => selectIssueSource), provide(Settings.layer));
|
|
152355
|
+
const commandSource = make$35("source").pipe(withDescription("Select the issue source to use (e.g. GitHub Issues or Linear). This applies to all projects."), withHandler(() => selectIssueSource), provide(Settings.layer));
|
|
152297
152356
|
|
|
152298
152357
|
//#endregion
|
|
152299
152358
|
//#region package.json
|
|
152300
|
-
var version = "0.3.
|
|
152359
|
+
var version = "0.3.14";
|
|
152301
152360
|
|
|
152302
152361
|
//#endregion
|
|
152303
152362
|
//#region src/commands/projects/ls.ts
|
|
152304
|
-
const commandProjectsLs = make$35("ls").pipe(withDescription("List
|
|
152363
|
+
const commandProjectsLs = make$35("ls").pipe(withDescription("List configured projects and how they run (enabled state, concurrency, branch, git flow, review agent)."), withHandler(fnUntraced(function* () {
|
|
152305
152364
|
const meta = yield* CurrentIssueSource;
|
|
152306
152365
|
const source = yield* IssueSource;
|
|
152307
152366
|
console.log("Issue source:", meta.name);
|
|
@@ -152325,11 +152384,11 @@ const commandProjectsLs = make$35("ls").pipe(withDescription("List all configure
|
|
|
152325
152384
|
|
|
152326
152385
|
//#endregion
|
|
152327
152386
|
//#region src/commands/projects/add.ts
|
|
152328
|
-
const commandProjectsAdd = make$35("add").pipe(withDescription("Add a
|
|
152387
|
+
const commandProjectsAdd = make$35("add").pipe(withDescription("Add a project and configure its execution settings (concurrency, target branch, git flow, review agent) and issue source settings."), withHandler(() => addOrUpdateProject()), provide(Settings.layer), provide(CurrentIssueSource.layer));
|
|
152329
152388
|
|
|
152330
152389
|
//#endregion
|
|
152331
152390
|
//#region src/commands/projects/rm.ts
|
|
152332
|
-
const commandProjectsRm = make$35("rm").pipe(withDescription("Remove a project"), withHandler(fnUntraced(function* () {
|
|
152391
|
+
const commandProjectsRm = make$35("rm").pipe(withDescription("Remove a project from the configured list and delete its stored state under .lalph/projects."), withHandler(fnUntraced(function* () {
|
|
152333
152392
|
const fs = yield* FileSystem;
|
|
152334
152393
|
const pathService = yield* Path$1;
|
|
152335
152394
|
const projects = yield* getAllProjects;
|
|
@@ -152343,14 +152402,14 @@ const commandProjectsRm = make$35("rm").pipe(withDescription("Remove a project")
|
|
|
152343
152402
|
|
|
152344
152403
|
//#endregion
|
|
152345
152404
|
//#region src/commands/projects/edit.ts
|
|
152346
|
-
const commandProjectsEdit = make$35("edit").pipe(withDescription("
|
|
152405
|
+
const commandProjectsEdit = make$35("edit").pipe(withDescription("Edit a project's execution settings (concurrency, target branch, git flow, review agent) and issue source settings."), withHandler(fnUntraced(function* () {
|
|
152347
152406
|
if ((yield* getAllProjects).length === 0) return yield* log$1("No projects available to edit.");
|
|
152348
152407
|
yield* addOrUpdateProject(yield* selectProject);
|
|
152349
152408
|
})), provide(Settings.layer), provide(CurrentIssueSource.layer));
|
|
152350
152409
|
|
|
152351
152410
|
//#endregion
|
|
152352
152411
|
//#region src/commands/projects/toggle.ts
|
|
152353
|
-
const commandProjectsToggle = make$35("toggle").pipe(withDescription("Enable or disable projects"), withHandler(fnUntraced(function* () {
|
|
152412
|
+
const commandProjectsToggle = make$35("toggle").pipe(withDescription("Enable or disable configured projects for lalph runs."), withHandler(fnUntraced(function* () {
|
|
152354
152413
|
const projects = yield* getAllProjects;
|
|
152355
152414
|
if (projects.length === 0) return yield* log$1("No projects available to toggle.");
|
|
152356
152415
|
const enabled = yield* multiSelect({
|
|
@@ -152376,12 +152435,12 @@ const subcommands$1 = withSubcommands([
|
|
|
152376
152435
|
commandProjectsToggle,
|
|
152377
152436
|
commandProjectsRm
|
|
152378
152437
|
]);
|
|
152379
|
-
const commandProjects = make$35("projects").pipe(withDescription("Manage projects"), subcommands$1);
|
|
152380
|
-
const commandProjectsAlias = make$35("p").pipe(withDescription("Alias for 'projects'
|
|
152438
|
+
const commandProjects = make$35("projects").pipe(withDescription("Manage projects and their execution settings (enabled state, concurrency, target branch, git flow, review agent). Use 'ls' to inspect and 'add', 'edit', or 'toggle' to configure."), subcommands$1);
|
|
152439
|
+
const commandProjectsAlias = make$35("p").pipe(withDescription("Alias for 'projects'."), subcommands$1);
|
|
152381
152440
|
|
|
152382
152441
|
//#endregion
|
|
152383
152442
|
//#region src/commands/sh.ts
|
|
152384
|
-
const commandSh = make$35("sh").pipe(withDescription("
|
|
152443
|
+
const commandSh = make$35("sh").pipe(withDescription("Launch an interactive shell in the active project's worktree."), withHandler(fnUntraced(function* () {
|
|
152385
152444
|
const worktree = yield* Worktree;
|
|
152386
152445
|
const fs = yield* FileSystem;
|
|
152387
152446
|
const pathService = yield* Path$1;
|
|
@@ -152397,7 +152456,7 @@ const commandSh = make$35("sh").pipe(withDescription("Enter an interactive shell
|
|
|
152397
152456
|
|
|
152398
152457
|
//#endregion
|
|
152399
152458
|
//#region src/commands/agents/ls.ts
|
|
152400
|
-
const commandAgentsLs = make$35("ls").pipe(withDescription("List
|
|
152459
|
+
const commandAgentsLs = make$35("ls").pipe(withDescription("List configured agent presets (preset ids, agent, arguments, and any issue-source options)."), withHandler(fnUntraced(function* () {
|
|
152401
152460
|
const meta = yield* CurrentIssueSource;
|
|
152402
152461
|
const source = yield* IssueSource;
|
|
152403
152462
|
console.log("Issue source:", meta.name);
|
|
@@ -152419,11 +152478,11 @@ const commandAgentsLs = make$35("ls").pipe(withDescription("List all configured
|
|
|
152419
152478
|
|
|
152420
152479
|
//#endregion
|
|
152421
152480
|
//#region src/commands/agents/add.ts
|
|
152422
|
-
const commandAgentsAdd = make$35("add").pipe(withDescription("Add a new CLI agent
|
|
152481
|
+
const commandAgentsAdd = make$35("add").pipe(withDescription("Add a new agent preset (interactive prompt for CLI agent, arguments, and any issue-source options)."), withHandler(() => addOrUpdatePreset()), provide(Settings.layer), provide(CurrentIssueSource.layer));
|
|
152423
152482
|
|
|
152424
152483
|
//#endregion
|
|
152425
152484
|
//#region src/commands/agents/rm.ts
|
|
152426
|
-
const commandAgentsRm = make$35("rm").pipe(withDescription("Remove a
|
|
152485
|
+
const commandAgentsRm = make$35("rm").pipe(withDescription("Remove an agent preset (select a preset to delete from your configuration)."), withHandler(fnUntraced(function* () {
|
|
152427
152486
|
const presets = yield* getAllCliAgentPresets;
|
|
152428
152487
|
if (presets.length === 0) return yield* log$1("There are no presets to remove.");
|
|
152429
152488
|
const preset = yield* selectCliAgentPreset;
|
|
@@ -152433,7 +152492,7 @@ const commandAgentsRm = make$35("rm").pipe(withDescription("Remove a CLI agent p
|
|
|
152433
152492
|
|
|
152434
152493
|
//#endregion
|
|
152435
152494
|
//#region src/commands/agents/edit.ts
|
|
152436
|
-
const commandAgentsEdit = make$35("edit").pipe(withDescription("
|
|
152495
|
+
const commandAgentsEdit = make$35("edit").pipe(withDescription("Edit an existing agent preset (interactive prompt to update agent, arguments, and any issue-source options)."), withHandler(fnUntraced(function* () {
|
|
152437
152496
|
if ((yield* getAllCliAgentPresets).length === 0) return yield* log$1("No presets available to edit.");
|
|
152438
152497
|
yield* addOrUpdatePreset({ existing: yield* selectCliAgentPreset });
|
|
152439
152498
|
})), provide(Settings.layer), provide(CurrentIssueSource.layer));
|
|
@@ -152446,8 +152505,8 @@ const subcommands = withSubcommands([
|
|
|
152446
152505
|
commandAgentsEdit,
|
|
152447
152506
|
commandAgentsRm
|
|
152448
152507
|
]);
|
|
152449
|
-
const commandAgents = make$35("agents").pipe(withDescription("Manage
|
|
152450
|
-
const commandAgentsAlias = make$35("a").pipe(withDescription("Alias for 'agents'
|
|
152508
|
+
const commandAgents = make$35("agents").pipe(withDescription("Manage agent presets used to run tasks. Use 'ls' to inspect presets and 'add'/'edit' to configure agents, arguments, and any issue-source options."), subcommands);
|
|
152509
|
+
const commandAgentsAlias = make$35("a").pipe(withDescription("Alias for 'agents' (manage agent presets used to run tasks)."), subcommands);
|
|
152451
152510
|
|
|
152452
152511
|
//#endregion
|
|
152453
152512
|
//#region src/cli.ts
|
package/package.json
CHANGED
package/src/Linear.ts
CHANGED
|
@@ -539,19 +539,81 @@ const selectedProjectId = new ProjectSetting(
|
|
|
539
539
|
"linear.selectedProjectId",
|
|
540
540
|
Schema.String,
|
|
541
541
|
)
|
|
542
|
+
type ProjectSelection =
|
|
543
|
+
| {
|
|
544
|
+
readonly _tag: "create"
|
|
545
|
+
}
|
|
546
|
+
| {
|
|
547
|
+
readonly _tag: "existing"
|
|
548
|
+
readonly project: LinearProject
|
|
549
|
+
}
|
|
550
|
+
const createLinearProject = Effect.gen(function* () {
|
|
551
|
+
const linear = yield* Linear
|
|
552
|
+
const projects = yield* Stream.runCollect(linear.projects)
|
|
553
|
+
const projectName = yield* Prompt.text({
|
|
554
|
+
message: "Project name",
|
|
555
|
+
validate(input) {
|
|
556
|
+
const name = input.trim()
|
|
557
|
+
if (name.length === 0) {
|
|
558
|
+
return Effect.fail("Project name cannot be empty")
|
|
559
|
+
}
|
|
560
|
+
if (
|
|
561
|
+
projects.some(
|
|
562
|
+
(project) => project.name.toLowerCase() === name.toLowerCase(),
|
|
563
|
+
)
|
|
564
|
+
) {
|
|
565
|
+
return Effect.fail("A project with this name already exists")
|
|
566
|
+
}
|
|
567
|
+
return Effect.succeed(name)
|
|
568
|
+
},
|
|
569
|
+
})
|
|
570
|
+
const teams = yield* linear
|
|
571
|
+
.use((client) => client.teams())
|
|
572
|
+
.pipe(Effect.map((teamConnection) => teamConnection.nodes))
|
|
573
|
+
const teamId = yield* Prompt.autoComplete({
|
|
574
|
+
message: "Select a team for the new project",
|
|
575
|
+
choices: teams.map((team) => ({
|
|
576
|
+
title: team.name,
|
|
577
|
+
value: team.id,
|
|
578
|
+
})),
|
|
579
|
+
})
|
|
580
|
+
const created = yield* linear.use((client) =>
|
|
581
|
+
client.createProject({
|
|
582
|
+
name: projectName,
|
|
583
|
+
teamIds: [teamId],
|
|
584
|
+
}),
|
|
585
|
+
)
|
|
586
|
+
return yield* linear.use(() => created.project!)
|
|
587
|
+
})
|
|
542
588
|
const selectProject = Effect.gen(function* () {
|
|
543
589
|
const linear = yield* Linear
|
|
544
590
|
|
|
545
591
|
const projects = yield* Stream.runCollect(linear.projects)
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
592
|
+
const choices: ReadonlyArray<{
|
|
593
|
+
readonly title: string
|
|
594
|
+
readonly value: ProjectSelection
|
|
595
|
+
}> = [
|
|
596
|
+
{
|
|
597
|
+
title: "Create new",
|
|
598
|
+
value: { _tag: "create" },
|
|
599
|
+
},
|
|
600
|
+
...projects.map((project) => ({
|
|
550
601
|
title: project.name,
|
|
551
|
-
value:
|
|
602
|
+
value: {
|
|
603
|
+
_tag: "existing" as const,
|
|
604
|
+
project,
|
|
605
|
+
},
|
|
552
606
|
})),
|
|
607
|
+
]
|
|
608
|
+
|
|
609
|
+
const selected = yield* Prompt.autoComplete({
|
|
610
|
+
message: "Select a Linear project",
|
|
611
|
+
choices,
|
|
553
612
|
})
|
|
554
613
|
|
|
614
|
+
const project =
|
|
615
|
+
selected._tag === "existing" ? selected.project : yield* createLinearProject
|
|
616
|
+
|
|
555
617
|
yield* Settings.setProject(selectedProjectId, Option.some(project.id))
|
|
556
618
|
|
|
557
619
|
return project
|
|
@@ -4,7 +4,9 @@ import { Settings } from "../../Settings.ts"
|
|
|
4
4
|
import { addOrUpdatePreset } from "../../Presets.ts"
|
|
5
5
|
|
|
6
6
|
export const commandAgentsAdd = Command.make("add").pipe(
|
|
7
|
-
Command.withDescription(
|
|
7
|
+
Command.withDescription(
|
|
8
|
+
"Add a new agent preset (interactive prompt for CLI agent, arguments, and any issue-source options).",
|
|
9
|
+
),
|
|
8
10
|
Command.withHandler(() => addOrUpdatePreset()),
|
|
9
11
|
Command.provide(Settings.layer),
|
|
10
12
|
Command.provide(CurrentIssueSource.layer),
|
|
@@ -9,11 +9,13 @@ import {
|
|
|
9
9
|
} from "../../Presets.ts"
|
|
10
10
|
|
|
11
11
|
export const commandAgentsEdit = Command.make("edit").pipe(
|
|
12
|
-
Command.withDescription(
|
|
12
|
+
Command.withDescription(
|
|
13
|
+
"Edit an existing agent preset (interactive prompt to update agent, arguments, and any issue-source options).",
|
|
14
|
+
),
|
|
13
15
|
Command.withHandler(
|
|
14
16
|
Effect.fnUntraced(function* () {
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
17
|
+
const presets = yield* getAllCliAgentPresets
|
|
18
|
+
if (presets.length === 0) {
|
|
17
19
|
return yield* Effect.log("No presets available to edit.")
|
|
18
20
|
}
|
|
19
21
|
const preset = yield* selectCliAgentPreset
|
|
@@ -6,7 +6,9 @@ import { Settings } from "../../Settings.ts"
|
|
|
6
6
|
import { getAllCliAgentPresets } from "../../Presets.ts"
|
|
7
7
|
|
|
8
8
|
export const commandAgentsLs = Command.make("ls").pipe(
|
|
9
|
-
Command.withDescription(
|
|
9
|
+
Command.withDescription(
|
|
10
|
+
"List configured agent presets (preset ids, agent, arguments, and any issue-source options).",
|
|
11
|
+
),
|
|
10
12
|
Command.withHandler(
|
|
11
13
|
Effect.fnUntraced(function* () {
|
|
12
14
|
const meta = yield* CurrentIssueSource
|
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
} from "../../Presets.ts"
|
|
10
10
|
|
|
11
11
|
export const commandAgentsRm = Command.make("rm").pipe(
|
|
12
|
-
Command.withDescription(
|
|
12
|
+
Command.withDescription(
|
|
13
|
+
"Remove an agent preset (select a preset to delete from your configuration).",
|
|
14
|
+
),
|
|
13
15
|
Command.withHandler(
|
|
14
16
|
Effect.fnUntraced(function* () {
|
|
15
17
|
const presets = yield* getAllCliAgentPresets
|
package/src/commands/agents.ts
CHANGED
|
@@ -12,11 +12,15 @@ const subcommands = Command.withSubcommands([
|
|
|
12
12
|
])
|
|
13
13
|
|
|
14
14
|
export const commandAgents = Command.make("agents").pipe(
|
|
15
|
-
Command.withDescription(
|
|
15
|
+
Command.withDescription(
|
|
16
|
+
"Manage agent presets used to run tasks. Use 'ls' to inspect presets and 'add'/'edit' to configure agents, arguments, and any issue-source options.",
|
|
17
|
+
),
|
|
16
18
|
subcommands,
|
|
17
19
|
)
|
|
18
20
|
|
|
19
21
|
export const commandAgentsAlias = Command.make("a").pipe(
|
|
20
|
-
Command.withDescription(
|
|
22
|
+
Command.withDescription(
|
|
23
|
+
"Alias for 'agents' (manage agent presets used to run tasks).",
|
|
24
|
+
),
|
|
21
25
|
subcommands,
|
|
22
26
|
)
|
package/src/commands/edit.ts
CHANGED
|
@@ -19,11 +19,15 @@ const handler = Command.withHandler(
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
export const commandEdit = Command.make("edit").pipe(
|
|
22
|
-
Command.withDescription(
|
|
22
|
+
Command.withDescription(
|
|
23
|
+
"Open the selected project's .lalph/prd.yml in your editor.",
|
|
24
|
+
),
|
|
23
25
|
handler,
|
|
24
26
|
)
|
|
25
27
|
|
|
26
28
|
export const commandEditAlias = Command.make("e").pipe(
|
|
27
|
-
Command.withDescription(
|
|
29
|
+
Command.withDescription(
|
|
30
|
+
"Alias for 'edit' (open the selected project's .lalph/prd.yml in your editor).",
|
|
31
|
+
),
|
|
28
32
|
handler,
|
|
29
33
|
)
|
package/src/commands/issue.ts
CHANGED
|
@@ -87,11 +87,13 @@ const handler = flow(
|
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
export const commandIssue = Command.make("issue").pipe(
|
|
90
|
-
Command.withDescription("Create a new issue in
|
|
90
|
+
Command.withDescription("Create a new issue in your editor."),
|
|
91
91
|
handler,
|
|
92
92
|
)
|
|
93
93
|
|
|
94
94
|
export const commandIssueAlias = Command.make("i").pipe(
|
|
95
|
-
Command.withDescription(
|
|
95
|
+
Command.withDescription(
|
|
96
|
+
"Alias for 'issue' (create a new issue in your editor).",
|
|
97
|
+
),
|
|
96
98
|
handler,
|
|
97
99
|
)
|
|
@@ -15,14 +15,16 @@ const specificationPath = Argument.path("spec", {
|
|
|
15
15
|
mustExist: true,
|
|
16
16
|
}).pipe(
|
|
17
17
|
Argument.withDescription(
|
|
18
|
-
"Path to
|
|
18
|
+
"Required. Path to an existing specification file to convert into tasks",
|
|
19
19
|
),
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
export const commandPlanTasks = Command.make("tasks", {
|
|
23
23
|
specificationPath,
|
|
24
24
|
}).pipe(
|
|
25
|
-
Command.withDescription(
|
|
25
|
+
Command.withDescription(
|
|
26
|
+
"Convert an existing specification file into PRD tasks (without re-running plan mode)",
|
|
27
|
+
),
|
|
26
28
|
Command.withHandler(
|
|
27
29
|
Effect.fnUntraced(
|
|
28
30
|
function* ({ specificationPath }) {
|
package/src/commands/plan.ts
CHANGED
|
@@ -12,24 +12,30 @@ import { agentTasker } from "../Agents/tasker.ts"
|
|
|
12
12
|
import { commandPlanTasks } from "./plan/tasks.ts"
|
|
13
13
|
import { Editor } from "../Editor.ts"
|
|
14
14
|
import { getDefaultCliAgentPreset } from "../Presets.ts"
|
|
15
|
+
import { ChildProcess } from "effect/unstable/process"
|
|
16
|
+
import { parseBranch } from "../shared/git.ts"
|
|
15
17
|
|
|
16
18
|
const dangerous = Flag.boolean("dangerous").pipe(
|
|
17
19
|
Flag.withAlias("d"),
|
|
18
20
|
Flag.withDescription(
|
|
19
|
-
"
|
|
21
|
+
"Skip permission prompts while generating the specification from your plan",
|
|
20
22
|
),
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
const withNewProject = Flag.boolean("new").pipe(
|
|
24
26
|
Flag.withAlias("n"),
|
|
25
|
-
Flag.withDescription(
|
|
27
|
+
Flag.withDescription(
|
|
28
|
+
"Create a new project (via prompts) before starting plan mode",
|
|
29
|
+
),
|
|
26
30
|
)
|
|
27
31
|
|
|
28
32
|
export const commandPlan = Command.make("plan", {
|
|
29
33
|
dangerous,
|
|
30
34
|
withNewProject,
|
|
31
35
|
}).pipe(
|
|
32
|
-
Command.withDescription(
|
|
36
|
+
Command.withDescription(
|
|
37
|
+
"Open an editor to draft a plan; on save, generate a specification under --specs and then create PRD tasks from it. Use --new to create a project first; use --dangerous to skip permission prompts during spec generation.",
|
|
38
|
+
),
|
|
33
39
|
Command.withHandler(
|
|
34
40
|
Effect.fnUntraced(function* ({ dangerous, withNewProject }) {
|
|
35
41
|
const editor = yield* Editor
|
|
@@ -86,6 +92,13 @@ const plan = Effect.fnUntraced(
|
|
|
86
92
|
Effect.mapError(() => new SpecNotFound()),
|
|
87
93
|
)
|
|
88
94
|
|
|
95
|
+
if (Option.isSome(options.targetBranch)) {
|
|
96
|
+
yield* commitAndPushSpecification({
|
|
97
|
+
specsDirectory: options.specsDirectory,
|
|
98
|
+
targetBranch: options.targetBranch.value,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
yield* Effect.log("Converting specification into tasks")
|
|
90
103
|
|
|
91
104
|
yield* agentTasker({
|
|
@@ -119,6 +132,50 @@ export class SpecNotFound extends Data.TaggedError("SpecNotFound") {
|
|
|
119
132
|
readonly message = "The AI agent failed to produce a specification."
|
|
120
133
|
}
|
|
121
134
|
|
|
135
|
+
export class SpecGitError extends Data.TaggedError("SpecGitError")<{
|
|
136
|
+
readonly message: string
|
|
137
|
+
}> {}
|
|
138
|
+
|
|
139
|
+
const commitAndPushSpecification = Effect.fnUntraced(
|
|
140
|
+
function* (options: {
|
|
141
|
+
readonly specsDirectory: string
|
|
142
|
+
readonly targetBranch: string
|
|
143
|
+
}) {
|
|
144
|
+
const worktree = yield* Worktree
|
|
145
|
+
const pathService = yield* Path.Path
|
|
146
|
+
|
|
147
|
+
const absSpecsDirectory = pathService.join(
|
|
148
|
+
worktree.directory,
|
|
149
|
+
options.specsDirectory,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const git = (args: ReadonlyArray<string>) =>
|
|
153
|
+
ChildProcess.make("git", [...args], {
|
|
154
|
+
cwd: worktree.directory,
|
|
155
|
+
stdout: "inherit",
|
|
156
|
+
stderr: "inherit",
|
|
157
|
+
}).pipe(ChildProcess.exitCode)
|
|
158
|
+
|
|
159
|
+
const addCode = yield* git(["add", absSpecsDirectory])
|
|
160
|
+
if (addCode !== 0) {
|
|
161
|
+
return yield* new SpecGitError({
|
|
162
|
+
message: "Failed to stage specification changes.",
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const commitCode = yield* git(["commit", "-m", "Update plan specification"])
|
|
167
|
+
if (commitCode !== 0) {
|
|
168
|
+
return yield* new SpecGitError({
|
|
169
|
+
message: "Failed to commit the generated specification changes.",
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const parsed = parseBranch(options.targetBranch)
|
|
174
|
+
yield* git(["push", parsed.remote, `HEAD:${parsed.branch}`])
|
|
175
|
+
},
|
|
176
|
+
Effect.ignore({ log: "Warn" }),
|
|
177
|
+
)
|
|
178
|
+
|
|
122
179
|
const PlanDetails = Schema.fromJsonString(
|
|
123
180
|
Schema.Struct({
|
|
124
181
|
specification: Schema.String,
|
|
@@ -4,7 +4,9 @@ import { CurrentIssueSource } from "../../CurrentIssueSource.ts"
|
|
|
4
4
|
import { Settings } from "../../Settings.ts"
|
|
5
5
|
|
|
6
6
|
export const commandProjectsAdd = Command.make("add").pipe(
|
|
7
|
-
Command.withDescription(
|
|
7
|
+
Command.withDescription(
|
|
8
|
+
"Add a project and configure its execution settings (concurrency, target branch, git flow, review agent) and issue source settings.",
|
|
9
|
+
),
|
|
8
10
|
Command.withHandler(() => addOrUpdateProject()),
|
|
9
11
|
Command.provide(Settings.layer),
|
|
10
12
|
Command.provide(CurrentIssueSource.layer),
|
|
@@ -9,7 +9,9 @@ import { Settings } from "../../Settings.ts"
|
|
|
9
9
|
import { CurrentIssueSource } from "../../CurrentIssueSource.ts"
|
|
10
10
|
|
|
11
11
|
export const commandProjectsEdit = Command.make("edit").pipe(
|
|
12
|
-
Command.withDescription(
|
|
12
|
+
Command.withDescription(
|
|
13
|
+
"Edit a project's execution settings (concurrency, target branch, git flow, review agent) and issue source settings.",
|
|
14
|
+
),
|
|
13
15
|
Command.withHandler(
|
|
14
16
|
Effect.fnUntraced(function* () {
|
|
15
17
|
const projects = yield* getAllProjects
|
|
@@ -6,7 +6,9 @@ import { getAllProjects } from "../../Projects.ts"
|
|
|
6
6
|
import { Settings } from "../../Settings.ts"
|
|
7
7
|
|
|
8
8
|
export const commandProjectsLs = Command.make("ls").pipe(
|
|
9
|
-
Command.withDescription(
|
|
9
|
+
Command.withDescription(
|
|
10
|
+
"List configured projects and how they run (enabled state, concurrency, branch, git flow, review agent).",
|
|
11
|
+
),
|
|
10
12
|
Command.withHandler(
|
|
11
13
|
Effect.fnUntraced(function* () {
|
|
12
14
|
const meta = yield* CurrentIssueSource
|
|
@@ -5,7 +5,9 @@ import { Settings } from "../../Settings.ts"
|
|
|
5
5
|
import { CurrentIssueSource } from "../../CurrentIssueSource.ts"
|
|
6
6
|
|
|
7
7
|
export const commandProjectsRm = Command.make("rm").pipe(
|
|
8
|
-
Command.withDescription(
|
|
8
|
+
Command.withDescription(
|
|
9
|
+
"Remove a project from the configured list and delete its stored state under .lalph/projects.",
|
|
10
|
+
),
|
|
9
11
|
Command.withHandler(
|
|
10
12
|
Effect.fnUntraced(function* () {
|
|
11
13
|
const fs = yield* FileSystem.FileSystem
|
|
@@ -5,7 +5,9 @@ import { Settings } from "../../Settings.ts"
|
|
|
5
5
|
import { Project } from "../../domain/Project.ts"
|
|
6
6
|
|
|
7
7
|
export const commandProjectsToggle = Command.make("toggle").pipe(
|
|
8
|
-
Command.withDescription(
|
|
8
|
+
Command.withDescription(
|
|
9
|
+
"Enable or disable configured projects for lalph runs.",
|
|
10
|
+
),
|
|
9
11
|
Command.withHandler(
|
|
10
12
|
Effect.fnUntraced(function* () {
|
|
11
13
|
const projects = yield* getAllProjects
|
package/src/commands/projects.ts
CHANGED
|
@@ -14,11 +14,13 @@ const subcommands = Command.withSubcommands([
|
|
|
14
14
|
])
|
|
15
15
|
|
|
16
16
|
export const commandProjects = Command.make("projects").pipe(
|
|
17
|
-
Command.withDescription(
|
|
17
|
+
Command.withDescription(
|
|
18
|
+
"Manage projects and their execution settings (enabled state, concurrency, target branch, git flow, review agent). Use 'ls' to inspect and 'add', 'edit', or 'toggle' to configure.",
|
|
19
|
+
),
|
|
18
20
|
subcommands,
|
|
19
21
|
)
|
|
20
22
|
|
|
21
23
|
export const commandProjectsAlias = Command.make("p").pipe(
|
|
22
|
-
Command.withDescription("Alias for 'projects'
|
|
24
|
+
Command.withDescription("Alias for 'projects'."),
|
|
23
25
|
subcommands,
|
|
24
26
|
)
|
package/src/commands/root.ts
CHANGED
|
@@ -323,14 +323,16 @@ const runProject = Effect.fnUntraced(
|
|
|
323
323
|
// Command
|
|
324
324
|
|
|
325
325
|
const iterations = Flag.integer("iterations").pipe(
|
|
326
|
-
Flag.withDescription(
|
|
326
|
+
Flag.withDescription(
|
|
327
|
+
"Limit how many task iterations run per enabled project (default: unlimited). Use -i 1 to run a single iteration and exit.",
|
|
328
|
+
),
|
|
327
329
|
Flag.withAlias("i"),
|
|
328
330
|
Flag.withDefault(Number.POSITIVE_INFINITY),
|
|
329
331
|
)
|
|
330
332
|
|
|
331
333
|
const maxIterationMinutes = Flag.integer("max-minutes").pipe(
|
|
332
334
|
Flag.withDescription(
|
|
333
|
-
"
|
|
335
|
+
"Timeout an iteration if execution (and review, if enabled) exceeds this many minutes (default: LALPH_MAX_MINUTES or 90).",
|
|
334
336
|
),
|
|
335
337
|
Flag.withFallbackConfig(Config.int("LALPH_MAX_MINUTES")),
|
|
336
338
|
Flag.withDefault(90),
|
|
@@ -338,7 +340,7 @@ const maxIterationMinutes = Flag.integer("max-minutes").pipe(
|
|
|
338
340
|
|
|
339
341
|
const stallMinutes = Flag.integer("stall-minutes").pipe(
|
|
340
342
|
Flag.withDescription(
|
|
341
|
-
"
|
|
343
|
+
"Fail an iteration if the agent stops responding for this many minutes (default: LALPH_STALL_MINUTES or 5).",
|
|
342
344
|
),
|
|
343
345
|
Flag.withFallbackConfig(Config.int("LALPH_STALL_MINUTES")),
|
|
344
346
|
Flag.withDefault(5),
|
|
@@ -346,7 +348,7 @@ const stallMinutes = Flag.integer("stall-minutes").pipe(
|
|
|
346
348
|
|
|
347
349
|
const specsDirectory = Flag.directory("specs").pipe(
|
|
348
350
|
Flag.withDescription(
|
|
349
|
-
"Directory
|
|
351
|
+
"Directory where plan specs are written and read (default: LALPH_SPECS or .specs).",
|
|
350
352
|
),
|
|
351
353
|
Flag.withAlias("s"),
|
|
352
354
|
Flag.withFallbackConfig(Config.string("LALPH_SPECS")),
|
|
@@ -354,7 +356,9 @@ const specsDirectory = Flag.directory("specs").pipe(
|
|
|
354
356
|
)
|
|
355
357
|
|
|
356
358
|
const verbose = Flag.boolean("verbose").pipe(
|
|
357
|
-
Flag.withDescription(
|
|
359
|
+
Flag.withDescription(
|
|
360
|
+
"Increase log output for debugging. Use -v when you need detailed logs.",
|
|
361
|
+
),
|
|
358
362
|
Flag.withAlias("v"),
|
|
359
363
|
)
|
|
360
364
|
|
|
@@ -365,6 +369,9 @@ export const commandRoot = Command.make("lalph", {
|
|
|
365
369
|
specsDirectory,
|
|
366
370
|
verbose,
|
|
367
371
|
}).pipe(
|
|
372
|
+
Command.withDescription(
|
|
373
|
+
"Run the task loop across all enabled projects in parallel: pull issues from the current issue source and execute them with your configured agent preset(s). Use --iterations for a bounded run, and configure per-project concurrency via lalph projects edit.",
|
|
374
|
+
),
|
|
368
375
|
Command.withHandler(
|
|
369
376
|
Effect.fnUntraced(
|
|
370
377
|
function* ({
|
package/src/commands/sh.ts
CHANGED
|
@@ -6,7 +6,9 @@ import { Worktree } from "../Worktree.ts"
|
|
|
6
6
|
import { layerProjectIdPrompt } from "../Projects.ts"
|
|
7
7
|
|
|
8
8
|
export const commandSh = Command.make("sh").pipe(
|
|
9
|
-
Command.withDescription(
|
|
9
|
+
Command.withDescription(
|
|
10
|
+
"Launch an interactive shell in the active project's worktree.",
|
|
11
|
+
),
|
|
10
12
|
Command.withHandler(
|
|
11
13
|
Effect.fnUntraced(
|
|
12
14
|
function* () {
|
package/src/commands/source.ts
CHANGED
|
@@ -3,7 +3,9 @@ import { selectIssueSource } from "../CurrentIssueSource.ts"
|
|
|
3
3
|
import { Settings } from "../Settings.ts"
|
|
4
4
|
|
|
5
5
|
export const commandSource = Command.make("source").pipe(
|
|
6
|
-
Command.withDescription(
|
|
6
|
+
Command.withDescription(
|
|
7
|
+
"Select the issue source to use (e.g. GitHub Issues or Linear). This applies to all projects.",
|
|
8
|
+
),
|
|
7
9
|
Command.withHandler(() => selectIssueSource),
|
|
8
10
|
Command.provide(Settings.layer),
|
|
9
11
|
)
|