@locusai/cli 0.17.5 → 0.17.7
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 +458 -97
- package/package.json +1 -1
package/bin/locus.js
CHANGED
|
@@ -1271,7 +1271,12 @@ function updateIssueLabels(number, addLabels, removeLabels, options = {}) {
|
|
|
1271
1271
|
gh(args, options);
|
|
1272
1272
|
}
|
|
1273
1273
|
function addIssueComment(number, body, options = {}) {
|
|
1274
|
-
|
|
1274
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1275
|
+
execFileSync("gh", ["issue", "comment", String(number), "--body", body], {
|
|
1276
|
+
cwd,
|
|
1277
|
+
encoding: "utf-8",
|
|
1278
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1279
|
+
});
|
|
1275
1280
|
}
|
|
1276
1281
|
function createMilestone(owner, repo, title, dueOn, description, options = {}) {
|
|
1277
1282
|
let args = `api repos/${owner}/${repo}/milestones -f title=${JSON.stringify(title)}`;
|
|
@@ -2800,11 +2805,17 @@ ${dim("Press Ctrl+C again to exit")}\r
|
|
|
2800
2805
|
render();
|
|
2801
2806
|
return;
|
|
2802
2807
|
case SEQ_WORD_LEFT:
|
|
2808
|
+
case SEQ_SHIFT_LEFT:
|
|
2809
|
+
case SEQ_META_LEFT:
|
|
2810
|
+
case SEQ_META_SHIFT_LEFT:
|
|
2803
2811
|
case "\x1Bb":
|
|
2804
2812
|
moveWordLeft();
|
|
2805
2813
|
render();
|
|
2806
2814
|
return;
|
|
2807
2815
|
case SEQ_WORD_RIGHT:
|
|
2816
|
+
case SEQ_SHIFT_RIGHT:
|
|
2817
|
+
case SEQ_META_RIGHT:
|
|
2818
|
+
case SEQ_META_SHIFT_RIGHT:
|
|
2808
2819
|
case "\x1Bf":
|
|
2809
2820
|
moveWordRight();
|
|
2810
2821
|
render();
|
|
@@ -2828,7 +2839,7 @@ ${dim("Press Ctrl+C again to exit")}\r
|
|
|
2828
2839
|
render();
|
|
2829
2840
|
return;
|
|
2830
2841
|
default:
|
|
2831
|
-
if (seq.charCodeAt(0) >= 32
|
|
2842
|
+
if (seq.charCodeAt(0) >= 32) {
|
|
2832
2843
|
insertText(seq);
|
|
2833
2844
|
render();
|
|
2834
2845
|
}
|
|
@@ -3131,7 +3142,7 @@ ${dim("Press")} ${yellow("ESC")} ${dim("again to force exit")}\r
|
|
|
3131
3142
|
}
|
|
3132
3143
|
var CSI = "\x1B[", SAVE_CURSOR = "\x1B7", RESTORE_CURSOR = "\x1B8", ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, ENABLE_KITTY_KEYBOARD = "\x1B[>1u", DISABLE_KITTY_KEYBOARD = "\x1B[<u", PASTE_START, PASTE_END, CTRL_A = "\x01", CTRL_C = "\x03", CTRL_D = "\x04", CTRL_E = "\x05", CTRL_K = "\v", CTRL_J = `
|
|
3133
3144
|
`, CTRL_U = "\x15", CTRL_W = "\x17", TAB = "\t", ENTER = "\r", ENTER_CRLF = `\r
|
|
3134
|
-
`, ESC = "\x1B", BACKSPACE = "", SEQ_LEFT, SEQ_RIGHT, SEQ_UP, SEQ_DOWN, SEQ_HOME, SEQ_END, SEQ_HOME_1, SEQ_END_4, SEQ_HOME_O = "\x1BOH", SEQ_END_O = "\x1BOF", SEQ_DELETE, SEQ_WORD_LEFT, SEQ_WORD_RIGHT, SEQ_SHIFT_ENTER_CSI_U, SEQ_SHIFT_ENTER_MODIFY, SEQ_SHIFT_ENTER_TILDE, SEQ_ALT_ENTER, CONTROL_SEQUENCES;
|
|
3145
|
+
`, ESC = "\x1B", BACKSPACE = "", SEQ_LEFT, SEQ_RIGHT, SEQ_UP, SEQ_DOWN, SEQ_HOME, SEQ_END, SEQ_HOME_1, SEQ_END_4, SEQ_HOME_O = "\x1BOH", SEQ_END_O = "\x1BOF", SEQ_DELETE, SEQ_WORD_LEFT, SEQ_WORD_RIGHT, SEQ_SHIFT_LEFT, SEQ_SHIFT_RIGHT, SEQ_META_LEFT, SEQ_META_RIGHT, SEQ_META_SHIFT_LEFT, SEQ_META_SHIFT_RIGHT, SEQ_SHIFT_ENTER_CSI_U, SEQ_SHIFT_ENTER_MODIFY, SEQ_SHIFT_ENTER_TILDE, SEQ_ALT_ENTER, CONTROL_SEQUENCES;
|
|
3135
3146
|
var init_input_handler = __esm(() => {
|
|
3136
3147
|
init_terminal();
|
|
3137
3148
|
init_image_detect();
|
|
@@ -3150,6 +3161,12 @@ var init_input_handler = __esm(() => {
|
|
|
3150
3161
|
SEQ_DELETE = `${CSI}3~`;
|
|
3151
3162
|
SEQ_WORD_LEFT = `${CSI}1;5D`;
|
|
3152
3163
|
SEQ_WORD_RIGHT = `${CSI}1;5C`;
|
|
3164
|
+
SEQ_SHIFT_LEFT = `${CSI}1;2D`;
|
|
3165
|
+
SEQ_SHIFT_RIGHT = `${CSI}1;2C`;
|
|
3166
|
+
SEQ_META_LEFT = `${CSI}1;9D`;
|
|
3167
|
+
SEQ_META_RIGHT = `${CSI}1;9C`;
|
|
3168
|
+
SEQ_META_SHIFT_LEFT = `${CSI}1;10D`;
|
|
3169
|
+
SEQ_META_SHIFT_RIGHT = `${CSI}1;10C`;
|
|
3153
3170
|
SEQ_SHIFT_ENTER_CSI_U = `${CSI}13;2u`;
|
|
3154
3171
|
SEQ_SHIFT_ENTER_MODIFY = `${CSI}27;2;13~`;
|
|
3155
3172
|
SEQ_SHIFT_ENTER_TILDE = `${CSI}13;2~`;
|
|
@@ -3163,6 +3180,12 @@ var init_input_handler = __esm(() => {
|
|
|
3163
3180
|
SEQ_ALT_ENTER,
|
|
3164
3181
|
SEQ_WORD_LEFT,
|
|
3165
3182
|
SEQ_WORD_RIGHT,
|
|
3183
|
+
SEQ_META_SHIFT_LEFT,
|
|
3184
|
+
SEQ_META_SHIFT_RIGHT,
|
|
3185
|
+
SEQ_META_LEFT,
|
|
3186
|
+
SEQ_META_RIGHT,
|
|
3187
|
+
SEQ_SHIFT_LEFT,
|
|
3188
|
+
SEQ_SHIFT_RIGHT,
|
|
3166
3189
|
SEQ_DELETE,
|
|
3167
3190
|
SEQ_HOME_1,
|
|
3168
3191
|
SEQ_END_4,
|
|
@@ -3449,6 +3472,10 @@ var init_runner = __esm(() => {
|
|
|
3449
3472
|
});
|
|
3450
3473
|
|
|
3451
3474
|
// src/ai/run-ai.ts
|
|
3475
|
+
var exports_run_ai = {};
|
|
3476
|
+
__export(exports_run_ai, {
|
|
3477
|
+
runAI: () => runAI
|
|
3478
|
+
});
|
|
3452
3479
|
async function runAI(options) {
|
|
3453
3480
|
const indicator = getStatusIndicator();
|
|
3454
3481
|
const renderer = options.silent ? null : new StreamRenderer;
|
|
@@ -7668,14 +7695,23 @@ __export(exports_plan, {
|
|
|
7668
7695
|
parsePlanOutput: () => parsePlanOutput,
|
|
7669
7696
|
parsePlanArgs: () => parsePlanArgs
|
|
7670
7697
|
});
|
|
7671
|
-
import {
|
|
7698
|
+
import {
|
|
7699
|
+
existsSync as existsSync13,
|
|
7700
|
+
mkdirSync as mkdirSync10,
|
|
7701
|
+
readdirSync as readdirSync7,
|
|
7702
|
+
readFileSync as readFileSync10,
|
|
7703
|
+
writeFileSync as writeFileSync8
|
|
7704
|
+
} from "node:fs";
|
|
7672
7705
|
import { join as join14 } from "node:path";
|
|
7673
7706
|
function printHelp() {
|
|
7674
7707
|
process.stderr.write(`
|
|
7675
7708
|
${bold("locus plan")} — AI-powered sprint planning
|
|
7676
7709
|
|
|
7677
7710
|
${bold("Usage:")}
|
|
7678
|
-
locus plan "<directive>" ${dim("# AI
|
|
7711
|
+
locus plan "<directive>" ${dim("# AI creates a plan file")}
|
|
7712
|
+
locus plan approve <id> ${dim("# Create GitHub issues from saved plan")}
|
|
7713
|
+
locus plan list ${dim("# List saved plans")}
|
|
7714
|
+
locus plan show <id> ${dim("# Show a saved plan")}
|
|
7679
7715
|
locus plan --from-issues --sprint <name> ${dim("# Organize existing issues")}
|
|
7680
7716
|
|
|
7681
7717
|
${bold("Options:")}
|
|
@@ -7686,6 +7722,8 @@ ${bold("Options:")}
|
|
|
7686
7722
|
${bold("Examples:")}
|
|
7687
7723
|
locus plan "Build user authentication with OAuth"
|
|
7688
7724
|
locus plan "Improve API performance" --sprint "Sprint 3"
|
|
7725
|
+
locus plan approve abc123
|
|
7726
|
+
locus plan list
|
|
7689
7727
|
locus plan --from-issues --sprint "Sprint 2"
|
|
7690
7728
|
|
|
7691
7729
|
`);
|
|
@@ -7693,11 +7731,48 @@ ${bold("Examples:")}
|
|
|
7693
7731
|
function normalizeSprintName(name) {
|
|
7694
7732
|
return name.trim().toLowerCase();
|
|
7695
7733
|
}
|
|
7734
|
+
function getPlansDir(projectRoot) {
|
|
7735
|
+
return join14(projectRoot, ".locus", "plans");
|
|
7736
|
+
}
|
|
7737
|
+
function ensurePlansDir(projectRoot) {
|
|
7738
|
+
const dir = getPlansDir(projectRoot);
|
|
7739
|
+
if (!existsSync13(dir)) {
|
|
7740
|
+
mkdirSync10(dir, { recursive: true });
|
|
7741
|
+
}
|
|
7742
|
+
return dir;
|
|
7743
|
+
}
|
|
7744
|
+
function generateId() {
|
|
7745
|
+
return `${Math.random().toString(36).slice(2, 8)}`;
|
|
7746
|
+
}
|
|
7747
|
+
function loadPlanFile(projectRoot, id) {
|
|
7748
|
+
const dir = getPlansDir(projectRoot);
|
|
7749
|
+
if (!existsSync13(dir))
|
|
7750
|
+
return null;
|
|
7751
|
+
const files = readdirSync7(dir).filter((f) => f.endsWith(".json"));
|
|
7752
|
+
const match = files.find((f) => f.startsWith(id));
|
|
7753
|
+
if (!match)
|
|
7754
|
+
return null;
|
|
7755
|
+
try {
|
|
7756
|
+
const content = readFileSync10(join14(dir, match), "utf-8");
|
|
7757
|
+
return JSON.parse(content);
|
|
7758
|
+
} catch {
|
|
7759
|
+
return null;
|
|
7760
|
+
}
|
|
7761
|
+
}
|
|
7696
7762
|
async function planCommand(projectRoot, args, flags = {}) {
|
|
7697
7763
|
if (args[0] === "help" || args.length === 0) {
|
|
7698
7764
|
printHelp();
|
|
7699
7765
|
return;
|
|
7700
7766
|
}
|
|
7767
|
+
if (args[0] === "list") {
|
|
7768
|
+
return handleListPlans(projectRoot);
|
|
7769
|
+
}
|
|
7770
|
+
if (args[0] === "show") {
|
|
7771
|
+
return handleShowPlan(projectRoot, args[1]);
|
|
7772
|
+
}
|
|
7773
|
+
if (args[0] === "approve") {
|
|
7774
|
+
return handleApprovePlan(projectRoot, args[1], flags);
|
|
7775
|
+
}
|
|
7701
7776
|
const parsedArgs = parsePlanArgs(args);
|
|
7702
7777
|
if (parsedArgs.error) {
|
|
7703
7778
|
process.stderr.write(`${red("✗")} ${parsedArgs.error}
|
|
@@ -7721,7 +7796,138 @@ async function planCommand(projectRoot, args, flags = {}) {
|
|
|
7721
7796
|
}
|
|
7722
7797
|
return handleAIPlan(projectRoot, config, directive, sprintName, flags);
|
|
7723
7798
|
}
|
|
7799
|
+
function handleListPlans(projectRoot) {
|
|
7800
|
+
const dir = getPlansDir(projectRoot);
|
|
7801
|
+
if (!existsSync13(dir)) {
|
|
7802
|
+
process.stderr.write(`${dim("No saved plans yet.")}
|
|
7803
|
+
`);
|
|
7804
|
+
return;
|
|
7805
|
+
}
|
|
7806
|
+
const files = readdirSync7(dir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
7807
|
+
if (files.length === 0) {
|
|
7808
|
+
process.stderr.write(`${dim("No saved plans yet.")}
|
|
7809
|
+
`);
|
|
7810
|
+
return;
|
|
7811
|
+
}
|
|
7812
|
+
process.stderr.write(`
|
|
7813
|
+
${bold("Saved Plans:")}
|
|
7814
|
+
|
|
7815
|
+
`);
|
|
7816
|
+
for (const file of files) {
|
|
7817
|
+
const id = file.replace(".json", "");
|
|
7818
|
+
try {
|
|
7819
|
+
const content = readFileSync10(join14(dir, file), "utf-8");
|
|
7820
|
+
const plan = JSON.parse(content);
|
|
7821
|
+
const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
|
|
7822
|
+
const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
|
|
7823
|
+
process.stderr.write(` ${cyan(id.slice(0, 12))} ${plan.directive.slice(0, 55)} ${dim(`${issueCount} issues`)} ${dim(date)}
|
|
7824
|
+
`);
|
|
7825
|
+
} catch {
|
|
7826
|
+
process.stderr.write(` ${cyan(id.slice(0, 12))} ${dim("(unreadable)")}
|
|
7827
|
+
`);
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
process.stderr.write(`
|
|
7831
|
+
`);
|
|
7832
|
+
process.stderr.write(` Approve a plan: ${bold("locus plan approve <id>")}
|
|
7833
|
+
|
|
7834
|
+
`);
|
|
7835
|
+
}
|
|
7836
|
+
function handleShowPlan(projectRoot, id) {
|
|
7837
|
+
if (!id) {
|
|
7838
|
+
process.stderr.write(`${red("✗")} Please provide a plan ID.
|
|
7839
|
+
`);
|
|
7840
|
+
process.stderr.write(` Usage: ${bold("locus plan show <id>")}
|
|
7841
|
+
`);
|
|
7842
|
+
return;
|
|
7843
|
+
}
|
|
7844
|
+
const plan = loadPlanFile(projectRoot, id);
|
|
7845
|
+
if (!plan) {
|
|
7846
|
+
process.stderr.write(`${red("✗")} Plan "${id}" not found.
|
|
7847
|
+
`);
|
|
7848
|
+
process.stderr.write(` List plans with: ${bold("locus plan list")}
|
|
7849
|
+
`);
|
|
7850
|
+
return;
|
|
7851
|
+
}
|
|
7852
|
+
process.stderr.write(`
|
|
7853
|
+
${bold("Plan:")} ${cyan(plan.directive)}
|
|
7854
|
+
`);
|
|
7855
|
+
process.stderr.write(` ${dim(`ID: ${plan.id}`)}
|
|
7856
|
+
`);
|
|
7857
|
+
if (plan.sprint) {
|
|
7858
|
+
process.stderr.write(` ${dim(`Sprint: ${plan.sprint}`)}
|
|
7859
|
+
`);
|
|
7860
|
+
}
|
|
7861
|
+
process.stderr.write(` ${dim(`Created: ${plan.createdAt.slice(0, 10)}`)}
|
|
7862
|
+
`);
|
|
7863
|
+
process.stderr.write(`
|
|
7864
|
+
`);
|
|
7865
|
+
process.stderr.write(` ${dim("Order")} ${dim("Title".padEnd(50))} ${dim("Priority")} ${dim("Type")}
|
|
7866
|
+
`);
|
|
7867
|
+
for (const issue of plan.issues) {
|
|
7868
|
+
process.stderr.write(` ${String(issue.order).padStart(5)} ${issue.title.padEnd(50).slice(0, 50)} ${issue.priority.padEnd(10)} ${issue.type}
|
|
7869
|
+
`);
|
|
7870
|
+
}
|
|
7871
|
+
process.stderr.write(`
|
|
7872
|
+
`);
|
|
7873
|
+
process.stderr.write(` Approve: ${bold(`locus plan approve ${plan.id.slice(0, 8)}`)}
|
|
7874
|
+
|
|
7875
|
+
`);
|
|
7876
|
+
}
|
|
7877
|
+
async function handleApprovePlan(projectRoot, id, flags) {
|
|
7878
|
+
if (!id) {
|
|
7879
|
+
process.stderr.write(`${red("✗")} Please provide a plan ID.
|
|
7880
|
+
`);
|
|
7881
|
+
process.stderr.write(` Usage: ${bold("locus plan approve <id>")}
|
|
7882
|
+
`);
|
|
7883
|
+
process.stderr.write(` List plans with: ${bold("locus plan list")}
|
|
7884
|
+
`);
|
|
7885
|
+
return;
|
|
7886
|
+
}
|
|
7887
|
+
const plan = loadPlanFile(projectRoot, id);
|
|
7888
|
+
if (!plan) {
|
|
7889
|
+
process.stderr.write(`${red("✗")} Plan "${id}" not found.
|
|
7890
|
+
`);
|
|
7891
|
+
process.stderr.write(` List plans with: ${bold("locus plan list")}
|
|
7892
|
+
`);
|
|
7893
|
+
return;
|
|
7894
|
+
}
|
|
7895
|
+
if (!Array.isArray(plan.issues) || plan.issues.length === 0) {
|
|
7896
|
+
process.stderr.write(`${red("✗")} Plan "${id}" has no issues.
|
|
7897
|
+
`);
|
|
7898
|
+
return;
|
|
7899
|
+
}
|
|
7900
|
+
const config = loadConfig(projectRoot);
|
|
7901
|
+
process.stderr.write(`
|
|
7902
|
+
${bold("Approving plan:")} ${cyan(plan.directive)}
|
|
7903
|
+
`);
|
|
7904
|
+
if (plan.sprint) {
|
|
7905
|
+
process.stderr.write(` ${dim(`Sprint: ${plan.sprint}`)}
|
|
7906
|
+
`);
|
|
7907
|
+
}
|
|
7908
|
+
process.stderr.write(`
|
|
7909
|
+
`);
|
|
7910
|
+
process.stderr.write(` ${dim("Order")} ${dim("Title".padEnd(50))} ${dim("Priority")} ${dim("Type")}
|
|
7911
|
+
`);
|
|
7912
|
+
for (const issue of plan.issues) {
|
|
7913
|
+
process.stderr.write(` ${String(issue.order).padStart(5)} ${issue.title.padEnd(50).slice(0, 50)} ${issue.priority.padEnd(10)} ${issue.type}
|
|
7914
|
+
`);
|
|
7915
|
+
}
|
|
7916
|
+
process.stderr.write(`
|
|
7917
|
+
`);
|
|
7918
|
+
if (flags.dryRun) {
|
|
7919
|
+
process.stderr.write(`${yellow("⚠")} ${bold("Dry run")} — no issues created.
|
|
7920
|
+
|
|
7921
|
+
`);
|
|
7922
|
+
return;
|
|
7923
|
+
}
|
|
7924
|
+
await createPlannedIssues(projectRoot, config, plan.issues, plan.sprint ?? undefined);
|
|
7925
|
+
}
|
|
7724
7926
|
async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
|
|
7927
|
+
const id = generateId();
|
|
7928
|
+
const plansDir = ensurePlansDir(projectRoot);
|
|
7929
|
+
const planPath = join14(plansDir, `${id}.json`);
|
|
7930
|
+
const planPathRelative = `.locus/plans/${id}.json`;
|
|
7725
7931
|
process.stderr.write(`
|
|
7726
7932
|
${bold("Planning:")} ${cyan(directive)}
|
|
7727
7933
|
`);
|
|
@@ -7731,55 +7937,65 @@ ${bold("Planning:")} ${cyan(directive)}
|
|
|
7731
7937
|
}
|
|
7732
7938
|
process.stderr.write(`
|
|
7733
7939
|
`);
|
|
7734
|
-
const
|
|
7735
|
-
const
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
model: flags.model ?? config.ai.model,
|
|
7739
|
-
cwd: projectRoot,
|
|
7740
|
-
activity: "sprint planning"
|
|
7741
|
-
});
|
|
7742
|
-
if (aiResult.interrupted) {
|
|
7940
|
+
const prompt = buildPlanningPrompt(projectRoot, config, directive, sprintName, id, planPathRelative);
|
|
7941
|
+
const { execCommand: execCommand2 } = await Promise.resolve().then(() => (init_exec(), exports_exec));
|
|
7942
|
+
await execCommand2(projectRoot, [prompt], {});
|
|
7943
|
+
if (!existsSync13(planPath)) {
|
|
7743
7944
|
process.stderr.write(`
|
|
7744
|
-
${yellow("
|
|
7945
|
+
${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
|
|
7946
|
+
`);
|
|
7947
|
+
process.stderr.write(` Try again or create issues manually with ${bold("locus issue create")}.
|
|
7745
7948
|
`);
|
|
7746
7949
|
return;
|
|
7747
7950
|
}
|
|
7748
|
-
|
|
7951
|
+
let plan;
|
|
7952
|
+
try {
|
|
7953
|
+
const content = readFileSync10(planPath, "utf-8");
|
|
7954
|
+
plan = JSON.parse(content);
|
|
7955
|
+
} catch {
|
|
7749
7956
|
process.stderr.write(`
|
|
7750
|
-
${red("✗")}
|
|
7957
|
+
${red("✗")} Plan file at ${bold(planPathRelative)} is not valid JSON.
|
|
7751
7958
|
`);
|
|
7752
7959
|
return;
|
|
7753
7960
|
}
|
|
7754
|
-
|
|
7755
|
-
const planned = parsePlanOutput(output);
|
|
7756
|
-
if (planned.length === 0) {
|
|
7961
|
+
if (!Array.isArray(plan.issues) || plan.issues.length === 0) {
|
|
7757
7962
|
process.stderr.write(`
|
|
7758
|
-
${yellow("⚠")}
|
|
7759
|
-
`);
|
|
7760
|
-
process.stderr.write(` The AI output is shown above. Create issues manually with ${bold("locus issue create")}.
|
|
7963
|
+
${yellow("⚠")} Plan file has no issues.
|
|
7761
7964
|
`);
|
|
7762
7965
|
return;
|
|
7763
7966
|
}
|
|
7967
|
+
if (!plan.id)
|
|
7968
|
+
plan.id = id;
|
|
7969
|
+
if (!plan.directive)
|
|
7970
|
+
plan.directive = directive;
|
|
7971
|
+
if (!plan.sprint && sprintName)
|
|
7972
|
+
plan.sprint = sprintName;
|
|
7973
|
+
if (!plan.createdAt)
|
|
7974
|
+
plan.createdAt = new Date().toISOString();
|
|
7975
|
+
writeFileSync8(planPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
7764
7976
|
process.stderr.write(`
|
|
7765
|
-
${bold("
|
|
7977
|
+
${bold("Plan saved:")} ${cyan(id)}
|
|
7766
7978
|
|
|
7767
7979
|
`);
|
|
7768
|
-
process.stderr.write(` ${dim("Order")} ${dim("Title".padEnd(
|
|
7980
|
+
process.stderr.write(` ${dim("Order")} ${dim("Title".padEnd(50))} ${dim("Priority")} ${dim("Type")}
|
|
7769
7981
|
`);
|
|
7770
|
-
for (const issue of
|
|
7771
|
-
process.stderr.write(` ${String(issue.order).padStart(5)} ${issue.title.padEnd(
|
|
7982
|
+
for (const issue of plan.issues) {
|
|
7983
|
+
process.stderr.write(` ${String(issue.order).padStart(5)} ${issue.title.padEnd(50).slice(0, 50)} ${(issue.priority ?? "medium").padEnd(10)} ${issue.type ?? "feature"}
|
|
7772
7984
|
`);
|
|
7773
7985
|
}
|
|
7774
7986
|
process.stderr.write(`
|
|
7775
7987
|
`);
|
|
7776
7988
|
if (flags.dryRun) {
|
|
7777
7989
|
process.stderr.write(`${yellow("⚠")} ${bold("Dry run")} — no issues created.
|
|
7990
|
+
`);
|
|
7991
|
+
process.stderr.write(` Approve later with: ${bold(`locus plan approve ${id.slice(0, 8)}`)}
|
|
7778
7992
|
|
|
7779
7993
|
`);
|
|
7780
7994
|
return;
|
|
7781
7995
|
}
|
|
7782
|
-
|
|
7996
|
+
process.stderr.write(` To create these issues: ${bold(`locus plan approve ${id.slice(0, 8)}`)}
|
|
7997
|
+
|
|
7998
|
+
`);
|
|
7783
7999
|
}
|
|
7784
8000
|
async function handleFromIssues(projectRoot, config, sprintName, flags) {
|
|
7785
8001
|
if (!sprintName) {
|
|
@@ -7801,6 +8017,7 @@ ${bold("Organizing issues for:")} ${cyan(sprintName)}
|
|
|
7801
8017
|
${i.body?.slice(0, 300) ?? ""}`).join(`
|
|
7802
8018
|
|
|
7803
8019
|
`);
|
|
8020
|
+
const { runAI: runAI2 } = await Promise.resolve().then(() => (init_run_ai(), exports_run_ai));
|
|
7804
8021
|
const prompt = `You are organizing GitHub issues for a sprint. Analyze these issues and suggest the optimal execution order.
|
|
7805
8022
|
|
|
7806
8023
|
Issues:
|
|
@@ -7811,7 +8028,7 @@ ORDER: #<number> <reason for this position>
|
|
|
7811
8028
|
|
|
7812
8029
|
Order them so that dependencies are respected (issues that produce code needed by later issues should come first).
|
|
7813
8030
|
Start with foundational/setup tasks, then core features, then integration/testing.`;
|
|
7814
|
-
const aiResult = await
|
|
8031
|
+
const aiResult = await runAI2({
|
|
7815
8032
|
prompt,
|
|
7816
8033
|
provider: config.ai.provider,
|
|
7817
8034
|
model: flags.model ?? config.ai.model,
|
|
@@ -7879,11 +8096,14 @@ ${bold("Suggested Order:")}
|
|
|
7879
8096
|
`);
|
|
7880
8097
|
}
|
|
7881
8098
|
}
|
|
7882
|
-
function
|
|
8099
|
+
function buildPlanningPrompt(projectRoot, config, directive, sprintName, id, planPathRelative) {
|
|
7883
8100
|
const parts = [];
|
|
7884
8101
|
parts.push(`You are a sprint planning assistant for the GitHub repository ${config.github.owner}/${config.github.repo}.`);
|
|
7885
8102
|
parts.push("");
|
|
7886
8103
|
parts.push(`DIRECTIVE: ${directive}`);
|
|
8104
|
+
if (sprintName) {
|
|
8105
|
+
parts.push(`SPRINT: ${sprintName}`);
|
|
8106
|
+
}
|
|
7887
8107
|
parts.push("");
|
|
7888
8108
|
const locusPath = join14(projectRoot, "LOCUS.md");
|
|
7889
8109
|
if (existsSync13(locusPath)) {
|
|
@@ -7899,28 +8119,37 @@ function buildPlanningContext(projectRoot, config, directive) {
|
|
|
7899
8119
|
parts.push(content.slice(0, 2000));
|
|
7900
8120
|
parts.push("");
|
|
7901
8121
|
}
|
|
7902
|
-
parts.push("
|
|
7903
|
-
parts.push(
|
|
7904
|
-
parts.push("Each issue should be independently executable by an AI agent.");
|
|
7905
|
-
parts.push("Order them so dependencies are respected (foundational tasks first).");
|
|
8122
|
+
parts.push("TASK:");
|
|
8123
|
+
parts.push(`Break down the directive into specific, actionable GitHub issues and write them to the file: ${planPathRelative}`);
|
|
7906
8124
|
parts.push("");
|
|
7907
|
-
parts.push(
|
|
8125
|
+
parts.push(`Write ONLY a valid JSON file to ${planPathRelative} with this exact structure:`);
|
|
7908
8126
|
parts.push("");
|
|
7909
|
-
parts.push("
|
|
7910
|
-
parts.push("
|
|
7911
|
-
parts.push("
|
|
7912
|
-
parts.push("
|
|
7913
|
-
parts.push("
|
|
7914
|
-
parts.push("
|
|
7915
|
-
parts.push("
|
|
7916
|
-
parts.push("
|
|
7917
|
-
parts.push("
|
|
8127
|
+
parts.push("```json");
|
|
8128
|
+
parts.push("{");
|
|
8129
|
+
parts.push(` "id": "${id}",`);
|
|
8130
|
+
parts.push(` "directive": ${JSON.stringify(directive)},`);
|
|
8131
|
+
parts.push(` "sprint": ${sprintName ? JSON.stringify(sprintName) : "null"},`);
|
|
8132
|
+
parts.push(` "createdAt": "${new Date().toISOString()}",`);
|
|
8133
|
+
parts.push(' "issues": [');
|
|
8134
|
+
parts.push(" {");
|
|
8135
|
+
parts.push(' "order": 1,');
|
|
8136
|
+
parts.push(' "title": "concise issue title",');
|
|
8137
|
+
parts.push(' "body": "detailed markdown body with acceptance criteria",');
|
|
8138
|
+
parts.push(' "priority": "critical|high|medium|low",');
|
|
8139
|
+
parts.push(' "type": "feature|bug|chore|refactor|docs",');
|
|
8140
|
+
parts.push(' "dependsOn": "none or comma-separated order numbers"');
|
|
8141
|
+
parts.push(" }");
|
|
8142
|
+
parts.push(" ]");
|
|
8143
|
+
parts.push("}");
|
|
8144
|
+
parts.push("```");
|
|
7918
8145
|
parts.push("");
|
|
7919
|
-
parts.push("
|
|
7920
|
-
parts.push("-
|
|
7921
|
-
parts.push("-
|
|
7922
|
-
parts.push("-
|
|
7923
|
-
parts.push("-
|
|
8146
|
+
parts.push("Requirements for the issues:");
|
|
8147
|
+
parts.push("- Break the directive into 3-10 specific, actionable issues");
|
|
8148
|
+
parts.push("- Each issue must be independently executable by an AI agent");
|
|
8149
|
+
parts.push("- Order them so dependencies are respected (foundational tasks first)");
|
|
8150
|
+
parts.push("- Write detailed issue bodies with clear acceptance criteria");
|
|
8151
|
+
parts.push("- Use valid GitHub Markdown only in issue bodies");
|
|
8152
|
+
parts.push("- Create the file using the Write tool — do not print the JSON to the terminal");
|
|
7924
8153
|
return parts.join(`
|
|
7925
8154
|
`);
|
|
7926
8155
|
}
|
|
@@ -8050,7 +8279,6 @@ ${green("✓")} Created ${planned.length} issues.${milestoneTitle ? ` Sprint: ${
|
|
|
8050
8279
|
`);
|
|
8051
8280
|
}
|
|
8052
8281
|
var init_plan = __esm(() => {
|
|
8053
|
-
init_run_ai();
|
|
8054
8282
|
init_config();
|
|
8055
8283
|
init_github();
|
|
8056
8284
|
init_terminal();
|
|
@@ -8525,11 +8753,11 @@ __export(exports_discuss, {
|
|
|
8525
8753
|
});
|
|
8526
8754
|
import {
|
|
8527
8755
|
existsSync as existsSync15,
|
|
8528
|
-
mkdirSync as
|
|
8529
|
-
readdirSync as
|
|
8756
|
+
mkdirSync as mkdirSync11,
|
|
8757
|
+
readdirSync as readdirSync8,
|
|
8530
8758
|
readFileSync as readFileSync12,
|
|
8531
8759
|
unlinkSync as unlinkSync5,
|
|
8532
|
-
writeFileSync as
|
|
8760
|
+
writeFileSync as writeFileSync9
|
|
8533
8761
|
} from "node:fs";
|
|
8534
8762
|
import { join as join16 } from "node:path";
|
|
8535
8763
|
import { createInterface as createInterface2 } from "node:readline";
|
|
@@ -8541,6 +8769,7 @@ ${bold("Usage:")}
|
|
|
8541
8769
|
locus discuss "<topic>" ${dim("# Start a new discussion")}
|
|
8542
8770
|
locus discuss list ${dim("# List all discussions")}
|
|
8543
8771
|
locus discuss show <id> ${dim("# Show a discussion")}
|
|
8772
|
+
locus discuss plan <id> ${dim("# Convert discussion to a plan")}
|
|
8544
8773
|
locus discuss delete <id> ${dim("# Delete a discussion")}
|
|
8545
8774
|
|
|
8546
8775
|
${bold("Examples:")}
|
|
@@ -8548,6 +8777,7 @@ ${bold("Examples:")}
|
|
|
8548
8777
|
locus discuss "Monorepo vs polyrepo for our microservices"
|
|
8549
8778
|
locus discuss list
|
|
8550
8779
|
locus discuss show abc123
|
|
8780
|
+
locus discuss plan abc123
|
|
8551
8781
|
|
|
8552
8782
|
`);
|
|
8553
8783
|
}
|
|
@@ -8557,11 +8787,11 @@ function getDiscussionsDir(projectRoot) {
|
|
|
8557
8787
|
function ensureDiscussionsDir(projectRoot) {
|
|
8558
8788
|
const dir = getDiscussionsDir(projectRoot);
|
|
8559
8789
|
if (!existsSync15(dir)) {
|
|
8560
|
-
|
|
8790
|
+
mkdirSync11(dir, { recursive: true });
|
|
8561
8791
|
}
|
|
8562
8792
|
return dir;
|
|
8563
8793
|
}
|
|
8564
|
-
function
|
|
8794
|
+
function generateId2() {
|
|
8565
8795
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8566
8796
|
}
|
|
8567
8797
|
async function discussCommand(projectRoot, args, flags = {}) {
|
|
@@ -8576,6 +8806,9 @@ async function discussCommand(projectRoot, args, flags = {}) {
|
|
|
8576
8806
|
if (subcommand === "show") {
|
|
8577
8807
|
return showDiscussion(projectRoot, args[1]);
|
|
8578
8808
|
}
|
|
8809
|
+
if (subcommand === "plan") {
|
|
8810
|
+
return convertDiscussionToPlan(projectRoot, args[1]);
|
|
8811
|
+
}
|
|
8579
8812
|
if (subcommand === "delete") {
|
|
8580
8813
|
return deleteDiscussion(projectRoot, args[1]);
|
|
8581
8814
|
}
|
|
@@ -8597,7 +8830,7 @@ function listDiscussions(projectRoot) {
|
|
|
8597
8830
|
`);
|
|
8598
8831
|
return;
|
|
8599
8832
|
}
|
|
8600
|
-
const files =
|
|
8833
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
8601
8834
|
if (files.length === 0) {
|
|
8602
8835
|
process.stderr.write(`${dim("No discussions yet.")}
|
|
8603
8836
|
`);
|
|
@@ -8632,7 +8865,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
8632
8865
|
`);
|
|
8633
8866
|
return;
|
|
8634
8867
|
}
|
|
8635
|
-
const files =
|
|
8868
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
|
|
8636
8869
|
const match = files.find((f) => f.startsWith(id));
|
|
8637
8870
|
if (!match) {
|
|
8638
8871
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -8655,7 +8888,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
8655
8888
|
`);
|
|
8656
8889
|
return;
|
|
8657
8890
|
}
|
|
8658
|
-
const files =
|
|
8891
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
|
|
8659
8892
|
const match = files.find((f) => f.startsWith(id));
|
|
8660
8893
|
if (!match) {
|
|
8661
8894
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -8666,6 +8899,38 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
8666
8899
|
process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
|
|
8667
8900
|
`);
|
|
8668
8901
|
}
|
|
8902
|
+
async function convertDiscussionToPlan(projectRoot, id) {
|
|
8903
|
+
if (!id) {
|
|
8904
|
+
process.stderr.write(`${red("✗")} Please provide a discussion ID.
|
|
8905
|
+
`);
|
|
8906
|
+
process.stderr.write(` Usage: ${bold("locus discuss plan <id>")}
|
|
8907
|
+
`);
|
|
8908
|
+
return;
|
|
8909
|
+
}
|
|
8910
|
+
const dir = getDiscussionsDir(projectRoot);
|
|
8911
|
+
if (!existsSync15(dir)) {
|
|
8912
|
+
process.stderr.write(`${red("✗")} No discussions found.
|
|
8913
|
+
`);
|
|
8914
|
+
return;
|
|
8915
|
+
}
|
|
8916
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
|
|
8917
|
+
const match = files.find((f) => f.startsWith(id));
|
|
8918
|
+
if (!match) {
|
|
8919
|
+
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
8920
|
+
`);
|
|
8921
|
+
return;
|
|
8922
|
+
}
|
|
8923
|
+
const content = readFileSync12(join16(dir, match), "utf-8");
|
|
8924
|
+
process.stderr.write(`
|
|
8925
|
+
${bold("Converting discussion to plan:")} ${cyan(id)}
|
|
8926
|
+
|
|
8927
|
+
`);
|
|
8928
|
+
await planCommand(projectRoot, [
|
|
8929
|
+
`Create a detailed, actionable implementation plan based on this discussion document:
|
|
8930
|
+
|
|
8931
|
+
${content.slice(0, 8000)}`
|
|
8932
|
+
], {});
|
|
8933
|
+
}
|
|
8669
8934
|
async function promptForTopic() {
|
|
8670
8935
|
return new Promise((resolve2) => {
|
|
8671
8936
|
const rl = createInterface2({
|
|
@@ -8681,58 +8946,131 @@ async function promptForTopic() {
|
|
|
8681
8946
|
rl.once("close", () => resolve2(""));
|
|
8682
8947
|
});
|
|
8683
8948
|
}
|
|
8949
|
+
async function promptForAnswers() {
|
|
8950
|
+
return new Promise((resolve2) => {
|
|
8951
|
+
const rl = createInterface2({
|
|
8952
|
+
input: process.stdin,
|
|
8953
|
+
output: process.stderr,
|
|
8954
|
+
terminal: true
|
|
8955
|
+
});
|
|
8956
|
+
const lines = [];
|
|
8957
|
+
rl.on("line", (line) => {
|
|
8958
|
+
if (line.trim() === "" && lines.length > 0) {
|
|
8959
|
+
rl.close();
|
|
8960
|
+
resolve2(lines.join(`
|
|
8961
|
+
`).trim());
|
|
8962
|
+
} else {
|
|
8963
|
+
lines.push(line);
|
|
8964
|
+
}
|
|
8965
|
+
});
|
|
8966
|
+
rl.once("close", () => resolve2(lines.join(`
|
|
8967
|
+
`).trim()));
|
|
8968
|
+
});
|
|
8969
|
+
}
|
|
8970
|
+
function isQuestionsResponse(output) {
|
|
8971
|
+
const trimmed = output.trimStart();
|
|
8972
|
+
if (trimmed.startsWith("#"))
|
|
8973
|
+
return false;
|
|
8974
|
+
const questionMarks = (trimmed.match(/\?/g) ?? []).length;
|
|
8975
|
+
return questionMarks >= 2;
|
|
8976
|
+
}
|
|
8684
8977
|
async function startDiscussion(projectRoot, topic, flags) {
|
|
8685
8978
|
const config = loadConfig(projectRoot);
|
|
8686
8979
|
const timer = createTimer();
|
|
8687
|
-
const id =
|
|
8980
|
+
const id = generateId2();
|
|
8688
8981
|
process.stderr.write(`
|
|
8689
8982
|
${bold("Discussion:")} ${cyan(topic)}
|
|
8690
8983
|
|
|
8691
8984
|
`);
|
|
8692
|
-
const
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8985
|
+
const conversation = [];
|
|
8986
|
+
let finalAnalysis = "";
|
|
8987
|
+
for (let round = 0;round < MAX_DISCUSSION_ROUNDS; round++) {
|
|
8988
|
+
const isFinalRound = round === MAX_DISCUSSION_ROUNDS - 1;
|
|
8989
|
+
const prompt = buildDiscussionPrompt(projectRoot, config, topic, conversation, isFinalRound);
|
|
8990
|
+
const aiResult = await runAI({
|
|
8991
|
+
prompt,
|
|
8992
|
+
provider: config.ai.provider,
|
|
8993
|
+
model: flags.model ?? config.ai.model,
|
|
8994
|
+
cwd: projectRoot,
|
|
8995
|
+
activity: "discussion"
|
|
8996
|
+
});
|
|
8997
|
+
if (aiResult.interrupted) {
|
|
8998
|
+
process.stderr.write(`
|
|
8702
8999
|
${yellow("⚡")} Discussion interrupted.
|
|
8703
9000
|
`);
|
|
8704
|
-
|
|
9001
|
+
if (!aiResult.output.trim())
|
|
9002
|
+
return;
|
|
9003
|
+
finalAnalysis = aiResult.output.trim();
|
|
9004
|
+
break;
|
|
9005
|
+
}
|
|
9006
|
+
if (!aiResult.success && !aiResult.interrupted) {
|
|
9007
|
+
process.stderr.write(`
|
|
9008
|
+
${red("✗")} Discussion failed: ${aiResult.error}
|
|
9009
|
+
`);
|
|
8705
9010
|
return;
|
|
8706
|
-
|
|
8707
|
-
|
|
9011
|
+
}
|
|
9012
|
+
const response = aiResult.output.trim();
|
|
9013
|
+
conversation.push({ role: "assistant", content: response });
|
|
9014
|
+
if (!isQuestionsResponse(response) || isFinalRound) {
|
|
9015
|
+
finalAnalysis = response;
|
|
9016
|
+
break;
|
|
9017
|
+
}
|
|
9018
|
+
process.stderr.write(`
|
|
9019
|
+
${dim("─".repeat(50))}
|
|
9020
|
+
${bold("Your answers:")} ${dim("(press Enter on an empty line when done)")}
|
|
9021
|
+
`);
|
|
9022
|
+
const answers = await promptForAnswers();
|
|
9023
|
+
if (!answers.trim()) {
|
|
9024
|
+
conversation.push({
|
|
9025
|
+
role: "user",
|
|
9026
|
+
content: "Please proceed with your analysis based on the information available."
|
|
9027
|
+
});
|
|
9028
|
+
} else {
|
|
9029
|
+
conversation.push({ role: "user", content: answers });
|
|
9030
|
+
}
|
|
8708
9031
|
process.stderr.write(`
|
|
8709
|
-
${red("✗")} Discussion failed: ${aiResult.error}
|
|
8710
9032
|
`);
|
|
8711
|
-
return;
|
|
8712
9033
|
}
|
|
8713
|
-
|
|
9034
|
+
if (!finalAnalysis)
|
|
9035
|
+
return;
|
|
8714
9036
|
const dir = ensureDiscussionsDir(projectRoot);
|
|
8715
9037
|
const date = new Date().toISOString().slice(0, 10);
|
|
8716
|
-
const
|
|
9038
|
+
const transcript = conversation.map((turn) => {
|
|
9039
|
+
const label = turn.role === "user" ? "You" : "AI";
|
|
9040
|
+
return `**${label}:**
|
|
8717
9041
|
|
|
8718
|
-
|
|
8719
|
-
|
|
9042
|
+
${turn.content}`;
|
|
9043
|
+
}).join(`
|
|
8720
9044
|
|
|
8721
9045
|
---
|
|
8722
9046
|
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
9047
|
+
`);
|
|
9048
|
+
const markdown = [
|
|
9049
|
+
`# ${topic}`,
|
|
9050
|
+
``,
|
|
9051
|
+
`**Date:** ${date}`,
|
|
9052
|
+
`**Provider:** ${config.ai.provider} / ${flags.model ?? config.ai.model}`,
|
|
9053
|
+
``,
|
|
9054
|
+
`---`,
|
|
9055
|
+
``,
|
|
9056
|
+
finalAnalysis,
|
|
9057
|
+
``,
|
|
9058
|
+
...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
|
|
9059
|
+
].join(`
|
|
9060
|
+
`);
|
|
9061
|
+
writeFileSync9(join16(dir, `${id}.md`), markdown, "utf-8");
|
|
8726
9062
|
process.stderr.write(`
|
|
8727
9063
|
${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
|
|
8728
9064
|
`);
|
|
8729
9065
|
process.stderr.write(` View with: ${bold(`locus discuss show ${id.slice(0, 8)}`)}
|
|
9066
|
+
`);
|
|
9067
|
+
process.stderr.write(` Plan with: ${bold(`locus discuss plan ${id.slice(0, 8)}`)}
|
|
8730
9068
|
|
|
8731
9069
|
`);
|
|
8732
9070
|
}
|
|
8733
|
-
function buildDiscussionPrompt(projectRoot, config, topic) {
|
|
9071
|
+
function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFinal) {
|
|
8734
9072
|
const parts = [];
|
|
8735
|
-
parts.push(`You are a senior software architect
|
|
9073
|
+
parts.push(`You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.`);
|
|
8736
9074
|
parts.push("");
|
|
8737
9075
|
const locusPath = join16(projectRoot, "LOCUS.md");
|
|
8738
9076
|
if (existsSync15(locusPath)) {
|
|
@@ -8748,26 +9086,48 @@ function buildDiscussionPrompt(projectRoot, config, topic) {
|
|
|
8748
9086
|
parts.push(content.slice(0, 2000));
|
|
8749
9087
|
parts.push("");
|
|
8750
9088
|
}
|
|
8751
|
-
parts.push(`TOPIC: ${topic}`);
|
|
9089
|
+
parts.push(`DISCUSSION TOPIC: ${topic}`);
|
|
8752
9090
|
parts.push("");
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
9091
|
+
if (conversation.length === 0) {
|
|
9092
|
+
parts.push("Before providing recommendations, you need to ask targeted clarifying questions.");
|
|
9093
|
+
parts.push("");
|
|
9094
|
+
parts.push("Ask 3-5 focused questions that will significantly improve the quality of your analysis.");
|
|
9095
|
+
parts.push("Format as a numbered list. Be specific and focused on the most important unknowns.");
|
|
9096
|
+
parts.push("Do NOT provide any analysis yet — questions only.");
|
|
9097
|
+
} else {
|
|
9098
|
+
parts.push("CONVERSATION SO FAR:");
|
|
9099
|
+
parts.push("");
|
|
9100
|
+
for (const turn of conversation) {
|
|
9101
|
+
if (turn.role === "user") {
|
|
9102
|
+
parts.push(`USER: ${turn.content}`);
|
|
9103
|
+
} else {
|
|
9104
|
+
parts.push(`ASSISTANT: ${turn.content}`);
|
|
9105
|
+
}
|
|
9106
|
+
parts.push("");
|
|
9107
|
+
}
|
|
9108
|
+
if (forceFinal) {
|
|
9109
|
+
parts.push("Based on everything discussed, provide your complete analysis and recommendations now.");
|
|
9110
|
+
parts.push("Format as a thorough markdown document with a clear title (# Heading), sections, trade-offs, and actionable recommendations.");
|
|
9111
|
+
} else {
|
|
9112
|
+
parts.push("Review the information gathered so far.");
|
|
9113
|
+
parts.push("");
|
|
9114
|
+
parts.push("If you have enough information to make a thorough recommendation:");
|
|
9115
|
+
parts.push(" → Provide a complete analysis as a markdown document with a title (# Heading), sections, trade-offs, and concrete recommendations.");
|
|
9116
|
+
parts.push("");
|
|
9117
|
+
parts.push("If you still need key information to give a good answer:");
|
|
9118
|
+
parts.push(" → Ask 2-3 more focused follow-up questions (numbered list only, no analysis yet).");
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
8763
9121
|
return parts.join(`
|
|
8764
9122
|
`);
|
|
8765
9123
|
}
|
|
9124
|
+
var MAX_DISCUSSION_ROUNDS = 5;
|
|
8766
9125
|
var init_discuss = __esm(() => {
|
|
8767
9126
|
init_run_ai();
|
|
8768
9127
|
init_config();
|
|
8769
9128
|
init_progress();
|
|
8770
9129
|
init_terminal();
|
|
9130
|
+
init_plan();
|
|
8771
9131
|
});
|
|
8772
9132
|
|
|
8773
9133
|
// src/commands/artifacts.ts
|
|
@@ -8779,7 +9139,7 @@ __export(exports_artifacts, {
|
|
|
8779
9139
|
formatDate: () => formatDate2,
|
|
8780
9140
|
artifactsCommand: () => artifactsCommand
|
|
8781
9141
|
});
|
|
8782
|
-
import { existsSync as existsSync16, readdirSync as
|
|
9142
|
+
import { existsSync as existsSync16, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
|
|
8783
9143
|
import { join as join17 } from "node:path";
|
|
8784
9144
|
function printHelp5() {
|
|
8785
9145
|
process.stderr.write(`
|
|
@@ -8806,7 +9166,7 @@ function listArtifacts(projectRoot) {
|
|
|
8806
9166
|
const dir = getArtifactsDir(projectRoot);
|
|
8807
9167
|
if (!existsSync16(dir))
|
|
8808
9168
|
return [];
|
|
8809
|
-
return
|
|
9169
|
+
return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
8810
9170
|
const filePath = join17(dir, fileName);
|
|
8811
9171
|
const stat = statSync4(filePath);
|
|
8812
9172
|
return {
|
|
@@ -9118,7 +9478,8 @@ ${bold("Examples:")}
|
|
|
9118
9478
|
locus init ${dim("# Set up Locus in this repo")}
|
|
9119
9479
|
locus exec ${dim("# Start interactive REPL")}
|
|
9120
9480
|
locus issue create "Fix login bug" ${dim("# Create a new issue")}
|
|
9121
|
-
locus plan "Build auth system" ${dim("# AI
|
|
9481
|
+
locus plan "Build auth system" ${dim("# AI creates a plan file")}
|
|
9482
|
+
locus plan approve <id> ${dim("# Create issues from saved plan")}
|
|
9122
9483
|
locus run ${dim("# Execute active sprint")}
|
|
9123
9484
|
locus run 42 43 ${dim("# Run issues in parallel")}
|
|
9124
9485
|
|