@locusai/cli 0.24.1 → 0.24.2
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/bin/locus.js +165 -0
- package/package.json +2 -2
package/bin/locus.js
CHANGED
|
@@ -11637,6 +11637,7 @@ ${bold2("locus plan")} — AI-powered sprint planning
|
|
|
11637
11637
|
${bold2("Usage:")}
|
|
11638
11638
|
locus plan "<directive>" ${dim2("# AI creates a plan file")}
|
|
11639
11639
|
locus plan approve <id> <sprintname> ${dim2("# Create GitHub issues from saved plan")}
|
|
11640
|
+
locus plan refine <id> "<feedback>" ${dim2("# Refine a plan with feedback")}
|
|
11640
11641
|
locus plan list ${dim2("# List saved plans")}
|
|
11641
11642
|
locus plan show <id> ${dim2("# Show a saved plan")}
|
|
11642
11643
|
locus plan --from-issues --sprint <name> ${dim2("# Organize existing issues")}
|
|
@@ -11650,6 +11651,7 @@ ${bold2("Examples:")}
|
|
|
11650
11651
|
locus plan "Build user authentication with OAuth"
|
|
11651
11652
|
locus plan "Improve API performance" --sprint "Sprint 3"
|
|
11652
11653
|
locus plan approve abc123 "Sprint 3"
|
|
11654
|
+
locus plan refine abc123 "Split the auth issue into login and signup"
|
|
11653
11655
|
locus plan list
|
|
11654
11656
|
locus plan --from-issues --sprint "Sprint 2"
|
|
11655
11657
|
|
|
@@ -11697,6 +11699,19 @@ async function planCommand(projectRoot, args, flags = {}) {
|
|
|
11697
11699
|
if (args[0] === "show") {
|
|
11698
11700
|
return handleShowPlan(projectRoot, args[1]);
|
|
11699
11701
|
}
|
|
11702
|
+
if (args[0] === "refine") {
|
|
11703
|
+
const refineId = args[1];
|
|
11704
|
+
const refineArgs = parsePlanArgs(args.slice(2));
|
|
11705
|
+
const feedback = refineArgs.directive;
|
|
11706
|
+
if (!refineId || !feedback) {
|
|
11707
|
+
process.stderr.write(`${red2("✗")} Please provide a plan ID and feedback.
|
|
11708
|
+
`);
|
|
11709
|
+
process.stderr.write(` Usage: ${bold2('locus plan refine <id> "your feedback"')}
|
|
11710
|
+
`);
|
|
11711
|
+
return;
|
|
11712
|
+
}
|
|
11713
|
+
return handleRefinePlan(projectRoot, refineId, feedback, flags);
|
|
11714
|
+
}
|
|
11700
11715
|
if (args[0] === "approve") {
|
|
11701
11716
|
const approveArgs = parsePlanArgs(args.slice(2));
|
|
11702
11717
|
const sprintName2 = approveArgs.sprintName ?? (approveArgs.directive || undefined);
|
|
@@ -11808,6 +11823,100 @@ ${bold2("Plan:")} ${cyan2(plan.directive)}
|
|
|
11808
11823
|
`);
|
|
11809
11824
|
process.stderr.write(` Approve: ${bold2(`locus plan approve ${plan.id.slice(0, 8)} <sprintname>`)}
|
|
11810
11825
|
|
|
11826
|
+
`);
|
|
11827
|
+
}
|
|
11828
|
+
async function handleRefinePlan(projectRoot, id, feedback, flags) {
|
|
11829
|
+
const plan = loadPlanFile(projectRoot, id);
|
|
11830
|
+
if (!plan) {
|
|
11831
|
+
process.stderr.write(`${red2("✗")} Plan "${id}" not found.
|
|
11832
|
+
`);
|
|
11833
|
+
process.stderr.write(` List plans with: ${bold2("locus plan list")}
|
|
11834
|
+
`);
|
|
11835
|
+
return;
|
|
11836
|
+
}
|
|
11837
|
+
const config = loadConfig(projectRoot);
|
|
11838
|
+
const planPath = join23(getPlansDir(projectRoot), `${plan.id}.json`);
|
|
11839
|
+
const planPathRelative = `.locus/plans/${plan.id}.json`;
|
|
11840
|
+
process.stderr.write(`
|
|
11841
|
+
${bold2("Refining plan:")} ${cyan2(plan.directive)}
|
|
11842
|
+
`);
|
|
11843
|
+
process.stderr.write(` ${dim2(`Feedback: ${feedback}`)}
|
|
11844
|
+
|
|
11845
|
+
`);
|
|
11846
|
+
if (config.sandbox.enabled) {
|
|
11847
|
+
const mismatch = checkProviderSandboxMismatch(config.sandbox, flags.model ?? config.ai.model, config.ai.provider);
|
|
11848
|
+
if (mismatch) {
|
|
11849
|
+
process.stderr.write(`${red2("✗")} ${mismatch}
|
|
11850
|
+
`);
|
|
11851
|
+
return;
|
|
11852
|
+
}
|
|
11853
|
+
}
|
|
11854
|
+
const prompt = buildRefinePrompt(projectRoot, config, plan, feedback, planPathRelative);
|
|
11855
|
+
const aiResult = await runAI({
|
|
11856
|
+
prompt,
|
|
11857
|
+
provider: config.ai.provider,
|
|
11858
|
+
model: flags.model ?? config.ai.model,
|
|
11859
|
+
cwd: projectRoot,
|
|
11860
|
+
activity: "refining plan",
|
|
11861
|
+
sandboxed: config.sandbox.enabled,
|
|
11862
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
11863
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
11864
|
+
});
|
|
11865
|
+
if (aiResult.interrupted) {
|
|
11866
|
+
process.stderr.write(`
|
|
11867
|
+
${yellow2("⚡")} Refinement interrupted.
|
|
11868
|
+
`);
|
|
11869
|
+
return;
|
|
11870
|
+
}
|
|
11871
|
+
if (!aiResult.success) {
|
|
11872
|
+
process.stderr.write(`
|
|
11873
|
+
${red2("✗")} Refinement failed: ${aiResult.error}
|
|
11874
|
+
`);
|
|
11875
|
+
return;
|
|
11876
|
+
}
|
|
11877
|
+
if (!existsSync23(planPath)) {
|
|
11878
|
+
process.stderr.write(`
|
|
11879
|
+
${yellow2("⚠")} Plan file was not found at ${bold2(planPathRelative)}.
|
|
11880
|
+
`);
|
|
11881
|
+
return;
|
|
11882
|
+
}
|
|
11883
|
+
let updatedPlan;
|
|
11884
|
+
try {
|
|
11885
|
+
const content = readFileSync13(planPath, "utf-8");
|
|
11886
|
+
updatedPlan = JSON.parse(content);
|
|
11887
|
+
} catch {
|
|
11888
|
+
process.stderr.write(`
|
|
11889
|
+
${red2("✗")} Updated plan file at ${bold2(planPathRelative)} is not valid JSON.
|
|
11890
|
+
`);
|
|
11891
|
+
return;
|
|
11892
|
+
}
|
|
11893
|
+
if (!Array.isArray(updatedPlan.issues) || updatedPlan.issues.length === 0) {
|
|
11894
|
+
process.stderr.write(`
|
|
11895
|
+
${yellow2("⚠")} Refined plan has no issues.
|
|
11896
|
+
`);
|
|
11897
|
+
return;
|
|
11898
|
+
}
|
|
11899
|
+
updatedPlan.id = plan.id;
|
|
11900
|
+
updatedPlan.directive = plan.directive;
|
|
11901
|
+
updatedPlan.sprint = updatedPlan.sprint ?? plan.sprint;
|
|
11902
|
+
updatedPlan.createdAt = plan.createdAt;
|
|
11903
|
+
writeFileSync10(planPath, JSON.stringify(updatedPlan, null, 2), "utf-8");
|
|
11904
|
+
process.stderr.write(`
|
|
11905
|
+
${bold2("Plan refined:")} ${cyan2(plan.id)}
|
|
11906
|
+
|
|
11907
|
+
`);
|
|
11908
|
+
process.stderr.write(` ${dim2("Order")} ${dim2("Title".padEnd(50))} ${dim2("Priority")} ${dim2("Type")}
|
|
11909
|
+
`);
|
|
11910
|
+
for (const issue of updatedPlan.issues) {
|
|
11911
|
+
process.stderr.write(` ${String(issue.order).padStart(5)} ${issue.title.padEnd(50).slice(0, 50)} ${(issue.priority ?? "medium").padEnd(10)} ${issue.type ?? "feature"}
|
|
11912
|
+
`);
|
|
11913
|
+
}
|
|
11914
|
+
process.stderr.write(`
|
|
11915
|
+
`);
|
|
11916
|
+
process.stderr.write(` To approve: ${bold2(`locus plan approve ${plan.id.slice(0, 8)} <sprintname>`)}
|
|
11917
|
+
`);
|
|
11918
|
+
process.stderr.write(` To refine again: ${bold2(`locus plan refine ${plan.id.slice(0, 8)} "more feedback"`)}
|
|
11919
|
+
|
|
11811
11920
|
`);
|
|
11812
11921
|
}
|
|
11813
11922
|
async function handleApprovePlan(projectRoot, id, flags, sprintOverride) {
|
|
@@ -12136,6 +12245,62 @@ Write ONLY a valid JSON file to ${planPathRelative} with this exact structure:
|
|
|
12136
12245
|
</requirements>`);
|
|
12137
12246
|
return parts.join(`
|
|
12138
12247
|
|
|
12248
|
+
`);
|
|
12249
|
+
}
|
|
12250
|
+
function buildRefinePrompt(projectRoot, config, plan, feedback, planPathRelative) {
|
|
12251
|
+
const parts = [];
|
|
12252
|
+
parts.push(`<role>
|
|
12253
|
+
You are a sprint planning assistant for the GitHub repository ${config.github.owner}/${config.github.repo}.
|
|
12254
|
+
</role>`);
|
|
12255
|
+
parts.push(`<existing-plan>
|
|
12256
|
+
${JSON.stringify(plan, null, 2)}
|
|
12257
|
+
</existing-plan>`);
|
|
12258
|
+
parts.push(`<feedback>
|
|
12259
|
+
${feedback}
|
|
12260
|
+
</feedback>`);
|
|
12261
|
+
const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
|
|
12262
|
+
if (existsSync23(locusPath)) {
|
|
12263
|
+
const content = readFileSync13(locusPath, "utf-8");
|
|
12264
|
+
parts.push(`<project-context>
|
|
12265
|
+
${content.slice(0, 3000)}
|
|
12266
|
+
</project-context>`);
|
|
12267
|
+
}
|
|
12268
|
+
parts.push(`<task>
|
|
12269
|
+
You have been given an existing plan and user feedback about it. Modify the plan according to the feedback.
|
|
12270
|
+
|
|
12271
|
+
Write the updated plan as valid JSON to ${planPathRelative}, keeping the same structure:
|
|
12272
|
+
|
|
12273
|
+
\`\`\`json
|
|
12274
|
+
{
|
|
12275
|
+
"id": "${plan.id}",
|
|
12276
|
+
"directive": ${JSON.stringify(plan.directive)},
|
|
12277
|
+
"sprint": ${plan.sprint ? JSON.stringify(plan.sprint) : "null"},
|
|
12278
|
+
"createdAt": "${plan.createdAt}",
|
|
12279
|
+
"issues": [
|
|
12280
|
+
{
|
|
12281
|
+
"order": 1,
|
|
12282
|
+
"title": "concise issue title",
|
|
12283
|
+
"body": "detailed markdown body with acceptance criteria",
|
|
12284
|
+
"priority": "critical|high|medium|low",
|
|
12285
|
+
"type": "feature|bug|chore|refactor|docs",
|
|
12286
|
+
"dependsOn": "none or comma-separated order numbers"
|
|
12287
|
+
}
|
|
12288
|
+
]
|
|
12289
|
+
}
|
|
12290
|
+
\`\`\`
|
|
12291
|
+
</task>`);
|
|
12292
|
+
parts.push(`<requirements>
|
|
12293
|
+
- Apply the user's feedback to modify the existing plan
|
|
12294
|
+
- You may add, remove, reorder, or modify issues based on the feedback
|
|
12295
|
+
- Keep issues that the feedback does not mention unchanged
|
|
12296
|
+
- Ensure each issue is independently executable by an AI agent
|
|
12297
|
+
- Order them so dependencies are respected (foundational tasks first)
|
|
12298
|
+
- Write detailed issue bodies with clear acceptance criteria
|
|
12299
|
+
- Use valid GitHub Markdown only in issue bodies
|
|
12300
|
+
- Create the file using the Write tool — do not print the JSON to the terminal
|
|
12301
|
+
</requirements>`);
|
|
12302
|
+
return parts.join(`
|
|
12303
|
+
|
|
12139
12304
|
`);
|
|
12140
12305
|
}
|
|
12141
12306
|
function sanitizePlanOutput(output) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@locusai/cli",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.2",
|
|
4
4
|
"description": "GitHub-native AI engineering assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"dependencies": {},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@locusai/sdk": "^0.24.
|
|
39
|
+
"@locusai/sdk": "^0.24.2",
|
|
40
40
|
"@types/bun": "latest",
|
|
41
41
|
"typescript": "^5.8.3"
|
|
42
42
|
},
|