@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.
- package/dist/index.js +155 -95
- 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.
|
|
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
|
-
|
|
79042
|
-
|
|
79043
|
-
|
|
79044
|
-
|
|
79045
|
-
|
|
79046
|
-
|
|
79047
|
-
|
|
79048
|
-
|
|
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
|
|
80537
|
+
return planResult.object;
|
|
80486
80538
|
}
|
|
80487
|
-
return {
|
|
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
|
-
|
|
80502
|
-
|
|
80503
|
-
|
|
80504
|
-
|
|
80505
|
-
|
|
80506
|
-
|
|
80507
|
-
|
|
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
|
-
|
|
80520
|
-
|
|
80521
|
-
|
|
80522
|
-
|
|
80523
|
-
|
|
80524
|
-
|
|
80525
|
-
|
|
80526
|
-
|
|
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({
|
|
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
|
-
|
|
80609
|
-
|
|
80610
|
-
|
|
80611
|
-
|
|
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
|
-
|
|
80821
|
-
|
|
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 ===
|
|
80833
|
-
logger.info(`You are on the
|
|
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 = `${
|
|
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 (
|
|
80969
|
+
var epicWorkflow = async (epicContext, context) => {
|
|
80917
80970
|
const { logger, tools: tools2 } = context;
|
|
80918
|
-
const { task
|
|
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
|
-
|
|
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({
|
|
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);
|