lalph 0.3.24 → 0.3.26
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 +5 -2
- package/dist/cli.mjs +72 -8
- package/package.json +1 -1
- package/src/Github.ts +35 -3
- package/src/commands/plan.ts +16 -4
- package/src/domain/CliAgent.ts +1 -0
package/README.md
CHANGED
|
@@ -70,14 +70,17 @@ lalph projects toggle
|
|
|
70
70
|
|
|
71
71
|
## Plan mode
|
|
72
72
|
|
|
73
|
-
Plan mode opens an editor so you can write a high-level plan.
|
|
74
|
-
|
|
73
|
+
Plan mode opens an editor so you can write a high-level plan. You can also pass
|
|
74
|
+
`--file` / `-f` with a markdown file path to skip the editor. On save (or file
|
|
75
|
+
read), lalph generates a specification under `--specs` and then creates PRD
|
|
76
|
+
tasks from it.
|
|
75
77
|
|
|
76
78
|
Use `--dangerous` to skip permission prompts during spec generation, and `--new`
|
|
77
79
|
to create a project before starting plan mode.
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
82
|
lalph plan
|
|
83
|
+
lalph plan --file ./my-plan.md
|
|
81
84
|
lalph plan tasks .specs/my-spec.md
|
|
82
85
|
```
|
|
83
86
|
|
package/dist/cli.mjs
CHANGED
|
@@ -49627,6 +49627,35 @@ const directory$1 = (kind, name, options) => path$1(kind, name, {
|
|
|
49627
49627
|
mustExist: options?.mustExist
|
|
49628
49628
|
});
|
|
49629
49629
|
/**
|
|
49630
|
+
* Creates a file path parameter.
|
|
49631
|
+
*
|
|
49632
|
+
* This is a convenience function that creates a path parameter with a
|
|
49633
|
+
* `pathType` set to `"file"` and a default type name of `"file"`.
|
|
49634
|
+
*
|
|
49635
|
+
* @example
|
|
49636
|
+
* ```ts
|
|
49637
|
+
* import * as Param from "effect/unstable/cli/Param"
|
|
49638
|
+
*
|
|
49639
|
+
* // @internal - this module is not exported publicly
|
|
49640
|
+
*
|
|
49641
|
+
* // Basic file parameter
|
|
49642
|
+
* const outputFile = Param.file(Param.flagKind, "output")
|
|
49643
|
+
*
|
|
49644
|
+
* // File that must exist
|
|
49645
|
+
* const inputFile = Param.file(Param.flagKind, "input", { mustExist: true })
|
|
49646
|
+
*
|
|
49647
|
+
* // Usage: --output result.txt --input existing-file.txt
|
|
49648
|
+
* ```
|
|
49649
|
+
*
|
|
49650
|
+
* @since 4.0.0
|
|
49651
|
+
* @category constructors
|
|
49652
|
+
*/
|
|
49653
|
+
const file$2 = (kind, name, options) => path$1(kind, name, {
|
|
49654
|
+
pathType: "file",
|
|
49655
|
+
typeName: "file",
|
|
49656
|
+
mustExist: options?.mustExist
|
|
49657
|
+
});
|
|
49658
|
+
/**
|
|
49630
49659
|
* Creates an empty sentinel parameter that always fails to parse.
|
|
49631
49660
|
*
|
|
49632
49661
|
* This is useful for creating placeholder parameters or for combinators.
|
|
@@ -51323,6 +51352,26 @@ const choiceWithValue = (name, choices) => choiceWithValue$1(flagKind, name, cho
|
|
|
51323
51352
|
*/
|
|
51324
51353
|
const choice = (name, choices) => choice$1(flagKind, name, choices);
|
|
51325
51354
|
/**
|
|
51355
|
+
* Creates a file path flag that accepts file paths with optional existence validation.
|
|
51356
|
+
*
|
|
51357
|
+
* @example
|
|
51358
|
+
* ```ts
|
|
51359
|
+
* import { Flag } from "effect/unstable/cli"
|
|
51360
|
+
*
|
|
51361
|
+
* // Basic file flag
|
|
51362
|
+
* const inputFlag = Flag.file("input")
|
|
51363
|
+
* // Usage: --input ./data.json
|
|
51364
|
+
*
|
|
51365
|
+
* // File that must exist
|
|
51366
|
+
* const configFlag = Flag.file("config", { mustExist: true })
|
|
51367
|
+
* // Usage: --config ./config.yaml (file must exist)
|
|
51368
|
+
* ```
|
|
51369
|
+
*
|
|
51370
|
+
* @since 4.0.0
|
|
51371
|
+
* @category constructors
|
|
51372
|
+
*/
|
|
51373
|
+
const file$1 = (name, options) => file$2(flagKind, name, options);
|
|
51374
|
+
/**
|
|
51326
51375
|
* Creates a directory path flag that accepts directory paths with optional existence validation.
|
|
51327
51376
|
*
|
|
51328
51377
|
* @example
|
|
@@ -68792,6 +68841,7 @@ const claude = new CliAgent({
|
|
|
68792
68841
|
"--disallowed-tools",
|
|
68793
68842
|
"AskUserQuestion",
|
|
68794
68843
|
...extraArgs,
|
|
68844
|
+
"--",
|
|
68795
68845
|
`@${prdFilePath}
|
|
68796
68846
|
|
|
68797
68847
|
${prompt}`
|
|
@@ -154268,22 +154318,29 @@ const GithubIssueSource = effect$1(IssueSource, gen(function* () {
|
|
|
154268
154318
|
updateIssue: fnUntraced(function* (options) {
|
|
154269
154319
|
const { labelFilter, autoMergeLabelName } = yield* get$7(projectSettings, options.projectId);
|
|
154270
154320
|
const issueNumber = Number(options.issueId.slice(1));
|
|
154321
|
+
const currentIssue = yield* github.request((rest) => rest.issues.get({
|
|
154322
|
+
owner: cli.owner,
|
|
154323
|
+
repo: cli.repo,
|
|
154324
|
+
issue_number: issueNumber
|
|
154325
|
+
}));
|
|
154326
|
+
const labels = Array.from(new Set([...currentIssue.data.labels.flatMap((label) => typeof label === "string" ? [label] : label.name ? [label.name] : []), ...toArray(labelFilter)]));
|
|
154271
154327
|
const update = {
|
|
154272
154328
|
owner: cli.owner,
|
|
154273
154329
|
repo: cli.repo,
|
|
154274
154330
|
issue_number: issueNumber,
|
|
154275
|
-
labels
|
|
154331
|
+
labels
|
|
154276
154332
|
};
|
|
154277
154333
|
if (options.title) update.title = options.title;
|
|
154278
154334
|
if (options.description) update.body = options.description;
|
|
154279
154335
|
if (options.state) {
|
|
154280
154336
|
update.state = options.state === "done" ? "closed" : "open";
|
|
154337
|
+
update.labels = update.labels.filter((label) => label !== "in-review" && label !== "in-progress");
|
|
154281
154338
|
if (options.state === "in-review") update.labels.push("in-review");
|
|
154282
154339
|
else if (options.state === "in-progress") update.labels.push("in-progress");
|
|
154283
154340
|
}
|
|
154284
|
-
if (options.autoMerge !== void 0) {
|
|
154285
|
-
if (
|
|
154286
|
-
}
|
|
154341
|
+
if (options.autoMerge !== void 0 && isSome(autoMergeLabelName)) if (options.autoMerge) {
|
|
154342
|
+
if (!update.labels.includes(autoMergeLabelName.value)) update.labels.push(autoMergeLabelName.value);
|
|
154343
|
+
} else update.labels = update.labels.filter((label) => label !== autoMergeLabelName.value);
|
|
154287
154344
|
yield* updateIssue(update);
|
|
154288
154345
|
if (options.blockedBy !== void 0) {
|
|
154289
154346
|
const desiredBlockedBy = options.blockedBy;
|
|
@@ -155771,11 +155828,18 @@ var Editor = class extends Service$1()("lalph/Editor", { make: gen(function* ()
|
|
|
155771
155828
|
//#region src/commands/plan.ts
|
|
155772
155829
|
const dangerous = boolean("dangerous").pipe(withAlias("d"), withDescription$1("Skip permission prompts while generating the specification from your plan"));
|
|
155773
155830
|
const withNewProject = boolean("new").pipe(withAlias("n"), withDescription$1("Create a new project (via prompts) before starting plan mode"));
|
|
155831
|
+
const file = file$1("file", { mustExist: true }).pipe(withAlias("f"), withDescription$1("Read the plan from a markdown file instead of opening an editor"), optional);
|
|
155774
155832
|
const commandPlan = make$36("plan", {
|
|
155775
155833
|
dangerous,
|
|
155776
|
-
withNewProject
|
|
155777
|
-
|
|
155778
|
-
|
|
155834
|
+
withNewProject,
|
|
155835
|
+
file
|
|
155836
|
+
}).pipe(withDescription("Draft a plan in your editor (or use --file); then generate a specification under --specs and create PRD tasks from it. Use --new to create a project first, and --dangerous to skip permission prompts during spec generation."), withHandler(fnUntraced(function* ({ dangerous, withNewProject, file }) {
|
|
155837
|
+
const editor = yield* Editor;
|
|
155838
|
+
const fs = yield* FileSystem;
|
|
155839
|
+
const thePlan = yield* matchEffect(file.asEffect(), {
|
|
155840
|
+
onFailure: () => editor.editTemp({ suffix: ".md" }),
|
|
155841
|
+
onSuccess: (path) => fs.readFileString(path).pipe(asSome)
|
|
155842
|
+
});
|
|
155779
155843
|
if (isNone(thePlan)) return;
|
|
155780
155844
|
yield* gen(function* () {
|
|
155781
155845
|
const project = withNewProject ? yield* addOrUpdateProject() : yield* selectProject;
|
|
@@ -155916,7 +155980,7 @@ const commandSource = make$36("source").pipe(withDescription("Select the issue s
|
|
|
155916
155980
|
|
|
155917
155981
|
//#endregion
|
|
155918
155982
|
//#region package.json
|
|
155919
|
-
var version = "0.3.
|
|
155983
|
+
var version = "0.3.26";
|
|
155920
155984
|
|
|
155921
155985
|
//#endregion
|
|
155922
155986
|
//#region src/commands/projects/ls.ts
|
package/package.json
CHANGED
package/src/Github.ts
CHANGED
|
@@ -325,6 +325,25 @@ export const GithubIssueSource = Layer.effect(
|
|
|
325
325
|
options.projectId,
|
|
326
326
|
)
|
|
327
327
|
const issueNumber = Number(options.issueId.slice(1))
|
|
328
|
+
const currentIssue = yield* github.request((rest) =>
|
|
329
|
+
rest.issues.get({
|
|
330
|
+
owner: cli.owner,
|
|
331
|
+
repo: cli.repo,
|
|
332
|
+
issue_number: issueNumber,
|
|
333
|
+
}),
|
|
334
|
+
)
|
|
335
|
+
const labels = Array.from(
|
|
336
|
+
new Set([
|
|
337
|
+
...currentIssue.data.labels.flatMap((label) =>
|
|
338
|
+
typeof label === "string"
|
|
339
|
+
? [label]
|
|
340
|
+
: label.name
|
|
341
|
+
? [label.name]
|
|
342
|
+
: [],
|
|
343
|
+
),
|
|
344
|
+
...Option.toArray(labelFilter),
|
|
345
|
+
]),
|
|
346
|
+
)
|
|
328
347
|
const update: {
|
|
329
348
|
owner: string
|
|
330
349
|
repo: string
|
|
@@ -337,7 +356,7 @@ export const GithubIssueSource = Layer.effect(
|
|
|
337
356
|
owner: cli.owner,
|
|
338
357
|
repo: cli.repo,
|
|
339
358
|
issue_number: issueNumber,
|
|
340
|
-
labels
|
|
359
|
+
labels,
|
|
341
360
|
}
|
|
342
361
|
|
|
343
362
|
if (options.title) {
|
|
@@ -349,15 +368,28 @@ export const GithubIssueSource = Layer.effect(
|
|
|
349
368
|
if (options.state) {
|
|
350
369
|
update.state = options.state === "done" ? "closed" : "open"
|
|
351
370
|
|
|
371
|
+
update.labels = update.labels.filter(
|
|
372
|
+
(label) => label !== "in-review" && label !== "in-progress",
|
|
373
|
+
)
|
|
374
|
+
|
|
352
375
|
if (options.state === "in-review") {
|
|
353
376
|
update.labels.push("in-review")
|
|
354
377
|
} else if (options.state === "in-progress") {
|
|
355
378
|
update.labels.push("in-progress")
|
|
356
379
|
}
|
|
357
380
|
}
|
|
358
|
-
if (
|
|
381
|
+
if (
|
|
382
|
+
options.autoMerge !== undefined &&
|
|
383
|
+
Option.isSome(autoMergeLabelName)
|
|
384
|
+
) {
|
|
359
385
|
if (options.autoMerge) {
|
|
360
|
-
update.labels.
|
|
386
|
+
if (!update.labels.includes(autoMergeLabelName.value)) {
|
|
387
|
+
update.labels.push(autoMergeLabelName.value)
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
update.labels = update.labels.filter(
|
|
391
|
+
(label) => label !== autoMergeLabelName.value,
|
|
392
|
+
)
|
|
361
393
|
}
|
|
362
394
|
}
|
|
363
395
|
|
package/src/commands/plan.ts
CHANGED
|
@@ -29,20 +29,32 @@ const withNewProject = Flag.boolean("new").pipe(
|
|
|
29
29
|
),
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
+
const file = Flag.file("file", { mustExist: true }).pipe(
|
|
33
|
+
Flag.withAlias("f"),
|
|
34
|
+
Flag.withDescription(
|
|
35
|
+
"Read the plan from a markdown file instead of opening an editor",
|
|
36
|
+
),
|
|
37
|
+
Flag.optional,
|
|
38
|
+
)
|
|
39
|
+
|
|
32
40
|
export const commandPlan = Command.make("plan", {
|
|
33
41
|
dangerous,
|
|
34
42
|
withNewProject,
|
|
43
|
+
file,
|
|
35
44
|
}).pipe(
|
|
36
45
|
Command.withDescription(
|
|
37
|
-
"
|
|
46
|
+
"Draft a plan in your editor (or use --file); then generate a specification under --specs and create PRD tasks from it. Use --new to create a project first, and --dangerous to skip permission prompts during spec generation.",
|
|
38
47
|
),
|
|
39
48
|
Command.withHandler(
|
|
40
|
-
Effect.fnUntraced(function* ({ dangerous, withNewProject }) {
|
|
49
|
+
Effect.fnUntraced(function* ({ dangerous, withNewProject, file }) {
|
|
41
50
|
const editor = yield* Editor
|
|
51
|
+
const fs = yield* FileSystem.FileSystem
|
|
42
52
|
|
|
43
|
-
const thePlan = yield*
|
|
44
|
-
suffix: ".md",
|
|
53
|
+
const thePlan = yield* Effect.matchEffect(file.asEffect(), {
|
|
54
|
+
onFailure: () => editor.editTemp({ suffix: ".md" }),
|
|
55
|
+
onSuccess: (path) => fs.readFileString(path).pipe(Effect.asSome),
|
|
45
56
|
})
|
|
57
|
+
|
|
46
58
|
if (Option.isNone(thePlan)) return
|
|
47
59
|
|
|
48
60
|
// We nest this effect, so we can launch the editor first as fast as
|