@polka-codes/cli 0.9.50 → 0.9.51

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.
Files changed (2) hide show
  1. package/dist/index.js +155 -95
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -35579,7 +35579,7 @@ var {
35579
35579
  Help
35580
35580
  } = import__.default;
35581
35581
  // package.json
35582
- var version = "0.9.50";
35582
+ var version = "0.9.51";
35583
35583
 
35584
35584
  // src/commands/code.ts
35585
35585
  import { readFile as readFile3 } from "node:fs/promises";
@@ -79038,27 +79038,78 @@ Branch names should:
79038
79038
 
79039
79039
  ## Response Format
79040
79040
 
79041
- ${createJsonResponseInstruction({
79042
- plan: "The generated or updated plan.",
79043
- branchName: "feat/new-feature-name",
79044
- question: {
79045
- question: "The clarifying question to ask the user.",
79046
- defaultAnswer: "The default answer to provide if the user does not provide an answer."
79047
- },
79048
- reason: "If no plan is needed, provide a reason here."
79049
- })}
79041
+ Respond with a JSON object in a markdown code block. The JSON object should have a "type" field that determines the structure of the rest of the object.
79042
+
79043
+ ### 1. When you generate a plan (type: 'plan-generated')
79044
+ - **type**: Must be "plan-generated".
79045
+ - **plan**: The generated or updated plan.
79046
+ - **branchName**: A suitable git branch name for the work.
79047
+
79048
+ Example:
79049
+ \`\`\`json
79050
+ {
79051
+ "type": "plan-generated",
79052
+ "plan": "1. Phase 1: Backend API Development\\n - [ ] Design and implement user authentication endpoints...",
79053
+ "branchName": "feat/user-authentication"
79054
+ }
79055
+ \`\`\`
79056
+
79057
+ ### 2. When you need to ask a question (type: 'question')
79058
+ - **type**: Must be "question".
79059
+ - **question**: An object containing the question and an optional default answer.
79060
+
79061
+ Example:
79062
+ \`\`\`json
79063
+ {
79064
+ "type": "question",
79065
+ "question": {
79066
+ "question": "What database are you using?",
79067
+ "defaultAnswer": "PostgreSQL"
79068
+ }
79069
+ }
79070
+ \`\`\`
79071
+
79072
+ ### 3. When no plan is needed or an error occurs (type: 'error')
79073
+ - **type**: Must be "error".
79074
+ - **reason**: A string explaining why no plan is generated (e.g., task already complete, unclear requirements after questioning).
79075
+
79076
+ Example:
79077
+ \`\`\`json
79078
+ {
79079
+ "type": "error",
79080
+ "reason": "The requested feature is already implemented in 'src/features/existing-feature.ts'."
79081
+ }
79082
+ \`\`\`
79050
79083
  `;
79051
79084
  var BRANCH_NAME_PATTERN = /^[a-zA-Z0-9/_-]+$/;
79052
79085
  var EpicPlanSchema = exports_external.object({
79086
+ type: exports_external.enum(["plan-generated", "question", "error"]),
79053
79087
  plan: exports_external.string().nullish(),
79054
79088
  branchName: exports_external.string().refine((name18) => name18.length >= 3, { message: "Branch name is too short (min 3 characters)." }).refine((name18) => name18.length <= 255, { message: "Branch name is too long (max 255 characters)." }).refine((name18) => BRANCH_NAME_PATTERN.test(name18), {
79055
79089
  message: "Invalid branch name format. Branch names should contain only letters, numbers, hyphens, underscores, and forward slashes."
79056
- }),
79090
+ }).nullish(),
79057
79091
  question: exports_external.object({
79058
79092
  question: exports_external.string(),
79059
79093
  defaultAnswer: exports_external.string().nullish()
79060
79094
  }).nullish(),
79061
79095
  reason: exports_external.string().nullish()
79096
+ }).superRefine((data, ctx) => {
79097
+ if (data.type === "plan-generated") {
79098
+ if (!data.plan || data.plan.trim() === "") {
79099
+ ctx.addIssue({
79100
+ code: "custom",
79101
+ message: 'Plan is required when type is "plan-generated".',
79102
+ path: ["plan"]
79103
+ });
79104
+ }
79105
+ if (!data.branchName || data.branchName.trim() === "") {
79106
+ ctx.addIssue({
79107
+ code: "custom",
79108
+ message: 'Branch name is required when type is "plan-generated".',
79109
+ path: ["branchName"]
79110
+ });
79111
+ }
79112
+ }
79062
79113
  });
79063
79114
  function getPlanPrompt(task, planContent) {
79064
79115
  const planSection = planContent ? `
@@ -79832,8 +79883,8 @@ Files:`);
79832
79883
 
79833
79884
  // src/workflows/code.workflow.ts
79834
79885
  var ImplementOutputSchema = exports_external.object({
79835
- summary: exports_external.string().nullish(),
79836
- bailReason: exports_external.string().nullish()
79886
+ summary: exports_external.string().nullish().describe("Short summary of the changes made"),
79887
+ bailReason: exports_external.string().nullish().describe("Reason for bailing out of the implementation loop")
79837
79888
  }).refine((data) => data.summary != null !== (data.bailReason != null), {
79838
79889
  message: "Either summary or bailReason must be provided, but not both"
79839
79890
  });
@@ -80383,6 +80434,7 @@ var EpicContextSchema = exports_external.object({
80383
80434
  task: exports_external.string().nullish(),
80384
80435
  plan: exports_external.string().nullish(),
80385
80436
  branchName: exports_external.string().nullish(),
80437
+ baseBranch: exports_external.string().nullish(),
80386
80438
  todos: exports_external.array(TodoItemSchema).nullish(),
80387
80439
  memory: exports_external.record(exports_external.string(), exports_external.string()).nullish()
80388
80440
  });
@@ -80482,48 +80534,57 @@ ${feedback}`
80482
80534
  outputSchema: EpicPlanSchema
80483
80535
  }, context);
80484
80536
  if (planResult.type === "Exit" /* Exit */) {
80485
- return { ...planResult.object, type: planResult.type };
80537
+ return planResult.object;
80486
80538
  }
80487
- return { plan: "", reason: "Usage limit exceeded.", type: "Exit" /* Exit */, branchName: "" };
80539
+ return { type: "error", reason: "Usage limit exceeded." };
80488
80540
  }
80489
80541
  async function createAndApprovePlan(task, context) {
80490
80542
  const { logger, step, tools: tools2 } = context;
80491
80543
  logger.info(`Phase 2: Creating high-level plan...
80492
80544
  `);
80493
80545
  let feedback;
80494
- let highLevelPlan;
80495
- let branchName;
80496
80546
  let planAttempt = 1;
80497
80547
  try {
80498
80548
  while (true) {
80499
80549
  const result = await step(`plan-${planAttempt}`, () => createPlan2({ task, feedback }, context));
80500
80550
  planAttempt++;
80501
- if (result.question) {
80502
- const answer = await tools2.input({
80503
- message: result.question.question,
80504
- default: result.question.defaultAnswer || undefined
80505
- });
80506
- feedback = `The user answered the question "${result.question.question}" with: "${answer}"`;
80507
- continue;
80508
- }
80509
- if (!result.plan) {
80510
- if (result.reason) {
80511
- logger.info(`No plan created. Reason: ${result.reason}`);
80512
- } else {
80513
- logger.info("No plan created.");
80514
- }
80515
- return null;
80516
- }
80517
- logger.info(`Plan:
80551
+ switch (result.type) {
80552
+ case "plan-generated": {
80553
+ if (!result.plan || !result.branchName) {
80554
+ logger.error("Invalid plan-generated response. Missing plan or branchName.");
80555
+ return null;
80556
+ }
80557
+ logger.info(`Plan:
80518
80558
  ${result.plan}`);
80519
- if (result.branchName) {
80520
- logger.info(`Suggested branch name: ${result.branchName}`);
80521
- }
80522
- feedback = await tools2.input({ message: "Press Enter to approve the plan, or provide feedback to refine it." });
80523
- if (feedback.trim() === "") {
80524
- highLevelPlan = result.plan;
80525
- branchName = result.branchName;
80526
- break;
80559
+ logger.info(`Suggested branch name: ${result.branchName}`);
80560
+ feedback = await tools2.input({ message: "Press Enter to approve the plan, or provide feedback to refine it." });
80561
+ if (feedback.trim() === "") {
80562
+ logger.info(`High-level plan approved.
80563
+ `);
80564
+ return { plan: result.plan, branchName: result.branchName };
80565
+ }
80566
+ break;
80567
+ }
80568
+ case "question": {
80569
+ if (!result.question) {
80570
+ logger.error("Invalid question response. Missing question object.");
80571
+ return null;
80572
+ }
80573
+ const answer = await tools2.input({
80574
+ message: result.question.question,
80575
+ default: result.question.defaultAnswer || undefined
80576
+ });
80577
+ feedback = `The user answered the question "${result.question.question}" with: "${answer}"`;
80578
+ break;
80579
+ }
80580
+ case "error": {
80581
+ logger.info(`Plan creation failed. Reason: ${result.reason || "Unknown error"}`);
80582
+ return null;
80583
+ }
80584
+ default: {
80585
+ logger.error(`Unknown response type from planner: ${result.type}`);
80586
+ return null;
80587
+ }
80527
80588
  }
80528
80589
  }
80529
80590
  } catch (e) {
@@ -80533,19 +80594,8 @@ ${result.plan}`);
80533
80594
  }
80534
80595
  throw e;
80535
80596
  }
80536
- if (!highLevelPlan) {
80537
- logger.info("Plan not approved. Exiting.");
80538
- return null;
80539
- }
80540
- if (!branchName) {
80541
- logger.error("Error: No branch name was generated from the plan. Exiting.");
80542
- return null;
80543
- }
80544
- logger.info(`High-level plan approved.
80545
- `);
80546
- return { plan: highLevelPlan, branchName };
80547
80597
  }
80548
- async function createFeatureBranch(branchName, context) {
80598
+ async function createFeatureBranch(branchName, baseBranch, context) {
80549
80599
  const { logger, step, tools: tools2 } = context;
80550
80600
  logger.info(`Phase 3: Creating/switching to feature branch...
80551
80601
  `);
@@ -80565,7 +80615,10 @@ async function createFeatureBranch(branchName, context) {
80565
80615
  }
80566
80616
  } else {
80567
80617
  logger.info(`Creating new branch '${branchName}'...`);
80568
- const createBranchResult = await step("createBranch", async () => await tools2.executeCommand({ command: "git", args: ["checkout", "-b", branchName] }));
80618
+ const createBranchResult = await step("createBranch", async () => await tools2.executeCommand({
80619
+ command: "git",
80620
+ args: baseBranch ? ["checkout", "-b", branchName, baseBranch] : ["checkout", "-b", branchName]
80621
+ }));
80569
80622
  if (createBranchResult.exitCode !== 0) {
80570
80623
  logger.error(`Error: Failed to create branch '${branchName}'. Git command failed.`);
80571
80624
  return { success: false, branchName: null };
@@ -80604,12 +80657,13 @@ async function runPreflightChecks(epicContext, context) {
80604
80657
  }
80605
80658
  if (epicContext.plan) {
80606
80659
  logger.info("Found an existing `.epic.yml` file.");
80607
- if (epicContext.branchName) {
80608
- const currentBranchResult = await step("getCurrentBranch", async () => tools2.executeCommand({ command: "git", args: ["rev-parse", "--abbrev-ref", "HEAD"] }));
80609
- const currentBranch = currentBranchResult.stdout.trim();
80610
- if (currentBranch !== epicContext.branchName) {
80611
- throw new Error(`You are on branch '${currentBranch}' but the epic was started on branch '${epicContext.branchName}'. Please switch to the correct branch to resume.`);
80612
- }
80660
+ if (!epicContext.branchName) {
80661
+ throw new Error("Invalid epic context loaded from .epic.yml: branchName is required.");
80662
+ }
80663
+ const currentBranchResult = await step("getCurrentBranch", async () => tools2.executeCommand({ command: "git", args: ["rev-parse", "--abbrev-ref", "HEAD"] }));
80664
+ const currentBranch = currentBranchResult.stdout.trim();
80665
+ if (currentBranch !== epicContext.branchName) {
80666
+ throw new Error(`You are on branch '${currentBranch}' but the epic was started on branch '${epicContext.branchName}'. Please switch to the correct branch to resume.`);
80613
80667
  }
80614
80668
  logger.info("Resuming previous epic session.");
80615
80669
  return true;
@@ -80620,6 +80674,11 @@ async function runPreflightChecks(epicContext, context) {
80620
80674
  logger.info("Suggestion: Run `git add .` and `git commit` to clean your working directory, or `git stash` to temporarily save changes.\n");
80621
80675
  return false;
80622
80676
  }
80677
+ const baseBranchResult = await step("getBaseBranch", async () => tools2.executeCommand({ command: "git", args: ["rev-parse", "--abbrev-ref", "HEAD"] }));
80678
+ if (baseBranchResult.exitCode === 0 && baseBranchResult.stdout.trim()) {
80679
+ epicContext.baseBranch = baseBranchResult.stdout.trim();
80680
+ logger.info(`Using current branch '${epicContext.baseBranch}' as the base for this epic.`);
80681
+ }
80623
80682
  logger.info(`Pre-flight checks passed.
80624
80683
  `);
80625
80684
  return true;
@@ -80812,28 +80871,22 @@ Progress: ${progressMessage}`);
80812
80871
  }
80813
80872
  return commitMessages;
80814
80873
  }
80815
- async function performFinalReviewAndFix(context, highLevelPlan) {
80874
+ async function performFinalReviewAndFix(context, highLevelPlan, baseBranch) {
80816
80875
  const { logger, step, tools: tools2 } = context;
80817
80876
  logger.info(`
80818
80877
  Phase 6: Final Review and Fixup...
80819
80878
  `);
80820
- const ghCheckResult = await tools2.executeCommand({ command: "gh", args: ["--version"] });
80821
- if (ghCheckResult.exitCode !== 0) {
80822
- logger.warn("Warning: GitHub CLI (gh) is not installed. Skipping final review step. Please install it from https://cli.github.com/ to enable final reviews.");
80879
+ if (!baseBranch) {
80880
+ logger.warn("Warning: Base branch is not defined. Skipping final review step.");
80823
80881
  return { passed: true };
80824
80882
  }
80825
- const defaultBranchResult = await tools2.executeCommand({
80826
- command: "gh",
80827
- args: ["repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"]
80828
- });
80829
- const defaultBranch = defaultBranchResult.stdout.trim();
80830
80883
  const currentBranchResult = await tools2.executeCommand({ command: "git", args: ["rev-parse", "--abbrev-ref", "HEAD"] });
80831
80884
  const currentBranch = currentBranchResult.stdout.trim();
80832
- if (currentBranch === defaultBranch) {
80833
- logger.info(`You are on the default branch ('${defaultBranch}'). No final review needed.`);
80885
+ if (currentBranch === baseBranch) {
80886
+ logger.info(`You are on the base branch ('${baseBranch}'). No final review needed.`);
80834
80887
  return { passed: true };
80835
80888
  }
80836
- const commitRange = `${defaultBranch}...${currentBranch}`;
80889
+ const commitRange = `${baseBranch}...${currentBranch}`;
80837
80890
  for (let i = 0;i < MAX_REVIEW_RETRIES; i++) {
80838
80891
  const diffResult = await tools2.executeCommand({ command: "git", args: ["diff", "--name-status", commitRange] });
80839
80892
  const changedFiles = parseGitDiffNameStatus(diffResult.stdout);
@@ -80913,9 +80966,9 @@ Max retries (${MAX_REVIEW_RETRIES}) reached for final review. Issues might remai
80913
80966
  }
80914
80967
  return { passed: false };
80915
80968
  }
80916
- var epicWorkflow = async (input2, context) => {
80969
+ var epicWorkflow = async (epicContext, context) => {
80917
80970
  const { logger, tools: tools2 } = context;
80918
- const { task, epicContext = {} } = input2;
80971
+ const { task } = epicContext;
80919
80972
  const workflowStartTime = Date.now();
80920
80973
  if (!task || task.trim() === "") {
80921
80974
  logger.error("Error: Task cannot be empty. Please provide a valid task description.");
@@ -80949,7 +81002,7 @@ var epicWorkflow = async (input2, context) => {
80949
81002
  logger.error("Error: Branch name is missing after planning phase. Exiting.");
80950
81003
  return;
80951
81004
  }
80952
- const branchResult = await createFeatureBranch(epicContext.branchName, context);
81005
+ const branchResult = await createFeatureBranch(epicContext.branchName, epicContext.baseBranch ?? undefined, context);
80953
81006
  if (!branchResult.success || !branchResult.branchName)
80954
81007
  return;
80955
81008
  if (epicContext.branchName !== branchResult.branchName) {
@@ -80961,7 +81014,7 @@ var epicWorkflow = async (input2, context) => {
80961
81014
  await addTodoItemsFromPlan(epicContext.plan, context);
80962
81015
  }
80963
81016
  const commitMessages = await runImplementationLoop(context, epicContext.plan);
80964
- await performFinalReviewAndFix(context, epicContext.plan);
81017
+ await performFinalReviewAndFix(context, epicContext.plan, epicContext.baseBranch ?? undefined);
80965
81018
  await tools2.executeCommand({ command: "git", args: ["rm", "-f", ".epic.yml"] });
80966
81019
  const statusResult = await tools2.executeCommand({
80967
81020
  command: "git",
@@ -81284,33 +81337,40 @@ var commitCommand = new Command("commit").description("Create a commit with AI-g
81284
81337
 
81285
81338
  // src/commands/epic.ts
81286
81339
  async function runEpic(task2, _options, command) {
81287
- let taskInput = task2;
81288
- if (!taskInput) {
81289
- taskInput = await getUserInput("What epic or large feature do you want to implement?");
81290
- if (!taskInput) {
81291
- return;
81292
- }
81293
- }
81294
- if (!taskInput) {
81295
- console.error("No task provided. Aborting.");
81296
- return;
81297
- }
81298
- const ctx = await loadEpicContext();
81299
- const workflowInput = {
81300
- task: taskInput,
81301
- epicContext: ctx
81302
- };
81303
81340
  const globalOpts = (command.parent ?? command).opts();
81304
81341
  const { verbose, yes } = globalOpts;
81305
81342
  const logger = createLogger({
81306
81343
  verbose
81307
81344
  });
81308
- await runWorkflow(epicWorkflow, workflowInput, {
81345
+ let taskInput = task2;
81346
+ const epicContext = await loadEpicContext();
81347
+ if (epicContext.task) {
81348
+ if (taskInput) {
81349
+ logger.error("Error: Existing epic context found, but task was provided via CLI args. Exiting.");
81350
+ return;
81351
+ }
81352
+ logger.info("Resuming existing epic session. Task:");
81353
+ logger.info(` ${epicContext.task}`);
81354
+ } else {
81355
+ if (!taskInput) {
81356
+ taskInput = await getUserInput("What feature do you want to implement?");
81357
+ if (!taskInput) {
81358
+ logger.info("No task provided. Exiting.");
81359
+ return;
81360
+ }
81361
+ }
81362
+ epicContext.task = taskInput;
81363
+ }
81364
+ await runWorkflow(epicWorkflow, epicContext, {
81309
81365
  commandName: "epic",
81310
81366
  command,
81311
81367
  logger,
81312
81368
  yes,
81313
- getProvider: (opt) => getProvider({ ...opt, todoItemStore: new EpicTodoItemStore(ctx), memoryStore: new EpicMemoryStore(ctx) })
81369
+ getProvider: (opt) => getProvider({
81370
+ ...opt,
81371
+ todoItemStore: new EpicTodoItemStore(epicContext),
81372
+ memoryStore: new EpicMemoryStore(epicContext)
81373
+ })
81314
81374
  });
81315
81375
  }
81316
81376
  var epicCommand = new Command("epic").description("Orchestrates a large feature or epic, breaking it down into smaller tasks.").argument("[task]", "The epic to plan and implement.").action(runEpic);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polka-codes/cli",
3
- "version": "0.9.50",
3
+ "version": "0.9.51",
4
4
  "license": "AGPL-3.0",
5
5
  "author": "github@polka.codes",
6
6
  "type": "module",