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 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. On save, lalph
74
- generates a specification under `--specs` and then creates PRD tasks from it.
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: toArray(labelFilter)
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 (options.autoMerge) update.labels.push(...toArray(autoMergeLabelName));
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
- }).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 }) {
155778
- const thePlan = yield* (yield* Editor).editTemp({ suffix: ".md" });
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.24";
155983
+ var version = "0.3.26";
155920
155984
 
155921
155985
  //#endregion
155922
155986
  //#region src/commands/projects/ls.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.3.24",
4
+ "version": "0.3.26",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
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: Option.toArray(labelFilter),
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 (options.autoMerge !== undefined) {
381
+ if (
382
+ options.autoMerge !== undefined &&
383
+ Option.isSome(autoMergeLabelName)
384
+ ) {
359
385
  if (options.autoMerge) {
360
- update.labels.push(...Option.toArray(autoMergeLabelName))
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
 
@@ -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
- "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.",
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* editor.editTemp({
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
@@ -84,6 +84,7 @@ const claude = new CliAgent({
84
84
  "--disallowed-tools",
85
85
  "AskUserQuestion",
86
86
  ...extraArgs,
87
+ "--",
87
88
  `@${prdFilePath}
88
89
 
89
90
  ${prompt}`,