@locusai/cli 0.17.6 → 0.17.8
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 +443 -116
- package/package.json +1 -1
package/bin/locus.js
CHANGED
|
@@ -2805,11 +2805,17 @@ ${dim("Press Ctrl+C again to exit")}\r
|
|
|
2805
2805
|
render();
|
|
2806
2806
|
return;
|
|
2807
2807
|
case SEQ_WORD_LEFT:
|
|
2808
|
+
case SEQ_SHIFT_LEFT:
|
|
2809
|
+
case SEQ_META_LEFT:
|
|
2810
|
+
case SEQ_META_SHIFT_LEFT:
|
|
2808
2811
|
case "\x1Bb":
|
|
2809
2812
|
moveWordLeft();
|
|
2810
2813
|
render();
|
|
2811
2814
|
return;
|
|
2812
2815
|
case SEQ_WORD_RIGHT:
|
|
2816
|
+
case SEQ_SHIFT_RIGHT:
|
|
2817
|
+
case SEQ_META_RIGHT:
|
|
2818
|
+
case SEQ_META_SHIFT_RIGHT:
|
|
2813
2819
|
case "\x1Bf":
|
|
2814
2820
|
moveWordRight();
|
|
2815
2821
|
render();
|
|
@@ -2833,7 +2839,7 @@ ${dim("Press Ctrl+C again to exit")}\r
|
|
|
2833
2839
|
render();
|
|
2834
2840
|
return;
|
|
2835
2841
|
default:
|
|
2836
|
-
if (seq.charCodeAt(0) >= 32
|
|
2842
|
+
if (seq.charCodeAt(0) >= 32) {
|
|
2837
2843
|
insertText(seq);
|
|
2838
2844
|
render();
|
|
2839
2845
|
}
|
|
@@ -3136,7 +3142,7 @@ ${dim("Press")} ${yellow("ESC")} ${dim("again to force exit")}\r
|
|
|
3136
3142
|
}
|
|
3137
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 = `
|
|
3138
3144
|
`, CTRL_U = "\x15", CTRL_W = "\x17", TAB = "\t", ENTER = "\r", ENTER_CRLF = `\r
|
|
3139
|
-
`, 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;
|
|
3140
3146
|
var init_input_handler = __esm(() => {
|
|
3141
3147
|
init_terminal();
|
|
3142
3148
|
init_image_detect();
|
|
@@ -3155,6 +3161,12 @@ var init_input_handler = __esm(() => {
|
|
|
3155
3161
|
SEQ_DELETE = `${CSI}3~`;
|
|
3156
3162
|
SEQ_WORD_LEFT = `${CSI}1;5D`;
|
|
3157
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`;
|
|
3158
3170
|
SEQ_SHIFT_ENTER_CSI_U = `${CSI}13;2u`;
|
|
3159
3171
|
SEQ_SHIFT_ENTER_MODIFY = `${CSI}27;2;13~`;
|
|
3160
3172
|
SEQ_SHIFT_ENTER_TILDE = `${CSI}13;2~`;
|
|
@@ -3168,6 +3180,12 @@ var init_input_handler = __esm(() => {
|
|
|
3168
3180
|
SEQ_ALT_ENTER,
|
|
3169
3181
|
SEQ_WORD_LEFT,
|
|
3170
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,
|
|
3171
3189
|
SEQ_DELETE,
|
|
3172
3190
|
SEQ_HOME_1,
|
|
3173
3191
|
SEQ_END_4,
|
|
@@ -3454,6 +3472,10 @@ var init_runner = __esm(() => {
|
|
|
3454
3472
|
});
|
|
3455
3473
|
|
|
3456
3474
|
// src/ai/run-ai.ts
|
|
3475
|
+
var exports_run_ai = {};
|
|
3476
|
+
__export(exports_run_ai, {
|
|
3477
|
+
runAI: () => runAI
|
|
3478
|
+
});
|
|
3457
3479
|
async function runAI(options) {
|
|
3458
3480
|
const indicator = getStatusIndicator();
|
|
3459
3481
|
const renderer = options.silent ? null : new StreamRenderer;
|
|
@@ -7673,14 +7695,23 @@ __export(exports_plan, {
|
|
|
7673
7695
|
parsePlanOutput: () => parsePlanOutput,
|
|
7674
7696
|
parsePlanArgs: () => parsePlanArgs
|
|
7675
7697
|
});
|
|
7676
|
-
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";
|
|
7677
7705
|
import { join as join14 } from "node:path";
|
|
7678
7706
|
function printHelp() {
|
|
7679
7707
|
process.stderr.write(`
|
|
7680
7708
|
${bold("locus plan")} — AI-powered sprint planning
|
|
7681
7709
|
|
|
7682
7710
|
${bold("Usage:")}
|
|
7683
|
-
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")}
|
|
7684
7715
|
locus plan --from-issues --sprint <name> ${dim("# Organize existing issues")}
|
|
7685
7716
|
|
|
7686
7717
|
${bold("Options:")}
|
|
@@ -7691,6 +7722,8 @@ ${bold("Options:")}
|
|
|
7691
7722
|
${bold("Examples:")}
|
|
7692
7723
|
locus plan "Build user authentication with OAuth"
|
|
7693
7724
|
locus plan "Improve API performance" --sprint "Sprint 3"
|
|
7725
|
+
locus plan approve abc123
|
|
7726
|
+
locus plan list
|
|
7694
7727
|
locus plan --from-issues --sprint "Sprint 2"
|
|
7695
7728
|
|
|
7696
7729
|
`);
|
|
@@ -7698,11 +7731,48 @@ ${bold("Examples:")}
|
|
|
7698
7731
|
function normalizeSprintName(name) {
|
|
7699
7732
|
return name.trim().toLowerCase();
|
|
7700
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
|
+
}
|
|
7701
7762
|
async function planCommand(projectRoot, args, flags = {}) {
|
|
7702
7763
|
if (args[0] === "help" || args.length === 0) {
|
|
7703
7764
|
printHelp();
|
|
7704
7765
|
return;
|
|
7705
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
|
+
}
|
|
7706
7776
|
const parsedArgs = parsePlanArgs(args);
|
|
7707
7777
|
if (parsedArgs.error) {
|
|
7708
7778
|
process.stderr.write(`${red("✗")} ${parsedArgs.error}
|
|
@@ -7726,7 +7796,138 @@ async function planCommand(projectRoot, args, flags = {}) {
|
|
|
7726
7796
|
}
|
|
7727
7797
|
return handleAIPlan(projectRoot, config, directive, sprintName, flags);
|
|
7728
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
|
+
}
|
|
7729
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`;
|
|
7730
7931
|
process.stderr.write(`
|
|
7731
7932
|
${bold("Planning:")} ${cyan(directive)}
|
|
7732
7933
|
`);
|
|
@@ -7736,55 +7937,65 @@ ${bold("Planning:")} ${cyan(directive)}
|
|
|
7736
7937
|
}
|
|
7737
7938
|
process.stderr.write(`
|
|
7738
7939
|
`);
|
|
7739
|
-
const
|
|
7740
|
-
const
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
model: flags.model ?? config.ai.model,
|
|
7744
|
-
cwd: projectRoot,
|
|
7745
|
-
activity: "sprint planning"
|
|
7746
|
-
});
|
|
7747
|
-
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)) {
|
|
7748
7944
|
process.stderr.write(`
|
|
7749
|
-
${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")}.
|
|
7750
7948
|
`);
|
|
7751
7949
|
return;
|
|
7752
7950
|
}
|
|
7753
|
-
|
|
7951
|
+
let plan;
|
|
7952
|
+
try {
|
|
7953
|
+
const content = readFileSync10(planPath, "utf-8");
|
|
7954
|
+
plan = JSON.parse(content);
|
|
7955
|
+
} catch {
|
|
7754
7956
|
process.stderr.write(`
|
|
7755
|
-
${red("✗")}
|
|
7957
|
+
${red("✗")} Plan file at ${bold(planPathRelative)} is not valid JSON.
|
|
7756
7958
|
`);
|
|
7757
7959
|
return;
|
|
7758
7960
|
}
|
|
7759
|
-
|
|
7760
|
-
const planned = parsePlanOutput(output);
|
|
7761
|
-
if (planned.length === 0) {
|
|
7961
|
+
if (!Array.isArray(plan.issues) || plan.issues.length === 0) {
|
|
7762
7962
|
process.stderr.write(`
|
|
7763
|
-
${yellow("⚠")}
|
|
7764
|
-
`);
|
|
7765
|
-
process.stderr.write(` The AI output is shown above. Create issues manually with ${bold("locus issue create")}.
|
|
7963
|
+
${yellow("⚠")} Plan file has no issues.
|
|
7766
7964
|
`);
|
|
7767
7965
|
return;
|
|
7768
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");
|
|
7769
7976
|
process.stderr.write(`
|
|
7770
|
-
${bold("
|
|
7977
|
+
${bold("Plan saved:")} ${cyan(id)}
|
|
7771
7978
|
|
|
7772
7979
|
`);
|
|
7773
|
-
process.stderr.write(` ${dim("Order")} ${dim("Title".padEnd(
|
|
7980
|
+
process.stderr.write(` ${dim("Order")} ${dim("Title".padEnd(50))} ${dim("Priority")} ${dim("Type")}
|
|
7774
7981
|
`);
|
|
7775
|
-
for (const issue of
|
|
7776
|
-
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"}
|
|
7777
7984
|
`);
|
|
7778
7985
|
}
|
|
7779
7986
|
process.stderr.write(`
|
|
7780
7987
|
`);
|
|
7781
7988
|
if (flags.dryRun) {
|
|
7782
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)}`)}
|
|
7783
7992
|
|
|
7784
7993
|
`);
|
|
7785
7994
|
return;
|
|
7786
7995
|
}
|
|
7787
|
-
|
|
7996
|
+
process.stderr.write(` To create these issues: ${bold(`locus plan approve ${id.slice(0, 8)}`)}
|
|
7997
|
+
|
|
7998
|
+
`);
|
|
7788
7999
|
}
|
|
7789
8000
|
async function handleFromIssues(projectRoot, config, sprintName, flags) {
|
|
7790
8001
|
if (!sprintName) {
|
|
@@ -7806,6 +8017,7 @@ ${bold("Organizing issues for:")} ${cyan(sprintName)}
|
|
|
7806
8017
|
${i.body?.slice(0, 300) ?? ""}`).join(`
|
|
7807
8018
|
|
|
7808
8019
|
`);
|
|
8020
|
+
const { runAI: runAI2 } = await Promise.resolve().then(() => (init_run_ai(), exports_run_ai));
|
|
7809
8021
|
const prompt = `You are organizing GitHub issues for a sprint. Analyze these issues and suggest the optimal execution order.
|
|
7810
8022
|
|
|
7811
8023
|
Issues:
|
|
@@ -7816,7 +8028,7 @@ ORDER: #<number> <reason for this position>
|
|
|
7816
8028
|
|
|
7817
8029
|
Order them so that dependencies are respected (issues that produce code needed by later issues should come first).
|
|
7818
8030
|
Start with foundational/setup tasks, then core features, then integration/testing.`;
|
|
7819
|
-
const aiResult = await
|
|
8031
|
+
const aiResult = await runAI2({
|
|
7820
8032
|
prompt,
|
|
7821
8033
|
provider: config.ai.provider,
|
|
7822
8034
|
model: flags.model ?? config.ai.model,
|
|
@@ -7884,11 +8096,14 @@ ${bold("Suggested Order:")}
|
|
|
7884
8096
|
`);
|
|
7885
8097
|
}
|
|
7886
8098
|
}
|
|
7887
|
-
function
|
|
8099
|
+
function buildPlanningPrompt(projectRoot, config, directive, sprintName, id, planPathRelative) {
|
|
7888
8100
|
const parts = [];
|
|
7889
8101
|
parts.push(`You are a sprint planning assistant for the GitHub repository ${config.github.owner}/${config.github.repo}.`);
|
|
7890
8102
|
parts.push("");
|
|
7891
8103
|
parts.push(`DIRECTIVE: ${directive}`);
|
|
8104
|
+
if (sprintName) {
|
|
8105
|
+
parts.push(`SPRINT: ${sprintName}`);
|
|
8106
|
+
}
|
|
7892
8107
|
parts.push("");
|
|
7893
8108
|
const locusPath = join14(projectRoot, "LOCUS.md");
|
|
7894
8109
|
if (existsSync13(locusPath)) {
|
|
@@ -7904,28 +8119,37 @@ function buildPlanningContext(projectRoot, config, directive) {
|
|
|
7904
8119
|
parts.push(content.slice(0, 2000));
|
|
7905
8120
|
parts.push("");
|
|
7906
8121
|
}
|
|
7907
|
-
parts.push("
|
|
7908
|
-
parts.push(
|
|
7909
|
-
parts.push("Each issue should be independently executable by an AI agent.");
|
|
7910
|
-
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}`);
|
|
7911
8124
|
parts.push("");
|
|
7912
|
-
parts.push(
|
|
8125
|
+
parts.push(`Write ONLY a valid JSON file to ${planPathRelative} with this exact structure:`);
|
|
7913
8126
|
parts.push("");
|
|
7914
|
-
parts.push("
|
|
7915
|
-
parts.push("
|
|
7916
|
-
parts.push("
|
|
7917
|
-
parts.push("
|
|
7918
|
-
parts.push("
|
|
7919
|
-
parts.push("
|
|
7920
|
-
parts.push("
|
|
7921
|
-
parts.push("
|
|
7922
|
-
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("```");
|
|
7923
8145
|
parts.push("");
|
|
7924
|
-
parts.push("
|
|
7925
|
-
parts.push("-
|
|
7926
|
-
parts.push("-
|
|
7927
|
-
parts.push("-
|
|
7928
|
-
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");
|
|
7929
8153
|
return parts.join(`
|
|
7930
8154
|
`);
|
|
7931
8155
|
}
|
|
@@ -8055,7 +8279,6 @@ ${green("✓")} Created ${planned.length} issues.${milestoneTitle ? ` Sprint: ${
|
|
|
8055
8279
|
`);
|
|
8056
8280
|
}
|
|
8057
8281
|
var init_plan = __esm(() => {
|
|
8058
|
-
init_run_ai();
|
|
8059
8282
|
init_config();
|
|
8060
8283
|
init_github();
|
|
8061
8284
|
init_terminal();
|
|
@@ -8530,14 +8753,13 @@ __export(exports_discuss, {
|
|
|
8530
8753
|
});
|
|
8531
8754
|
import {
|
|
8532
8755
|
existsSync as existsSync15,
|
|
8533
|
-
mkdirSync as
|
|
8534
|
-
readdirSync as
|
|
8756
|
+
mkdirSync as mkdirSync11,
|
|
8757
|
+
readdirSync as readdirSync8,
|
|
8535
8758
|
readFileSync as readFileSync12,
|
|
8536
8759
|
unlinkSync as unlinkSync5,
|
|
8537
|
-
writeFileSync as
|
|
8760
|
+
writeFileSync as writeFileSync9
|
|
8538
8761
|
} from "node:fs";
|
|
8539
8762
|
import { join as join16 } from "node:path";
|
|
8540
|
-
import { createInterface as createInterface2 } from "node:readline";
|
|
8541
8763
|
function printHelp4() {
|
|
8542
8764
|
process.stderr.write(`
|
|
8543
8765
|
${bold("locus discuss")} — AI-powered architectural discussions
|
|
@@ -8546,6 +8768,7 @@ ${bold("Usage:")}
|
|
|
8546
8768
|
locus discuss "<topic>" ${dim("# Start a new discussion")}
|
|
8547
8769
|
locus discuss list ${dim("# List all discussions")}
|
|
8548
8770
|
locus discuss show <id> ${dim("# Show a discussion")}
|
|
8771
|
+
locus discuss plan <id> ${dim("# Convert discussion to a plan")}
|
|
8549
8772
|
locus discuss delete <id> ${dim("# Delete a discussion")}
|
|
8550
8773
|
|
|
8551
8774
|
${bold("Examples:")}
|
|
@@ -8553,6 +8776,7 @@ ${bold("Examples:")}
|
|
|
8553
8776
|
locus discuss "Monorepo vs polyrepo for our microservices"
|
|
8554
8777
|
locus discuss list
|
|
8555
8778
|
locus discuss show abc123
|
|
8779
|
+
locus discuss plan abc123
|
|
8556
8780
|
|
|
8557
8781
|
`);
|
|
8558
8782
|
}
|
|
@@ -8562,11 +8786,11 @@ function getDiscussionsDir(projectRoot) {
|
|
|
8562
8786
|
function ensureDiscussionsDir(projectRoot) {
|
|
8563
8787
|
const dir = getDiscussionsDir(projectRoot);
|
|
8564
8788
|
if (!existsSync15(dir)) {
|
|
8565
|
-
|
|
8789
|
+
mkdirSync11(dir, { recursive: true });
|
|
8566
8790
|
}
|
|
8567
8791
|
return dir;
|
|
8568
8792
|
}
|
|
8569
|
-
function
|
|
8793
|
+
function generateId2() {
|
|
8570
8794
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8571
8795
|
}
|
|
8572
8796
|
async function discussCommand(projectRoot, args, flags = {}) {
|
|
@@ -8581,16 +8805,15 @@ async function discussCommand(projectRoot, args, flags = {}) {
|
|
|
8581
8805
|
if (subcommand === "show") {
|
|
8582
8806
|
return showDiscussion(projectRoot, args[1]);
|
|
8583
8807
|
}
|
|
8808
|
+
if (subcommand === "plan") {
|
|
8809
|
+
return convertDiscussionToPlan(projectRoot, args[1]);
|
|
8810
|
+
}
|
|
8584
8811
|
if (subcommand === "delete") {
|
|
8585
8812
|
return deleteDiscussion(projectRoot, args[1]);
|
|
8586
8813
|
}
|
|
8587
8814
|
if (args.length === 0) {
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
printHelp4();
|
|
8591
|
-
return;
|
|
8592
|
-
}
|
|
8593
|
-
return startDiscussion(projectRoot, topic2, flags);
|
|
8815
|
+
printHelp4();
|
|
8816
|
+
return;
|
|
8594
8817
|
}
|
|
8595
8818
|
const topic = args.join(" ").trim();
|
|
8596
8819
|
return startDiscussion(projectRoot, topic, flags);
|
|
@@ -8602,7 +8825,7 @@ function listDiscussions(projectRoot) {
|
|
|
8602
8825
|
`);
|
|
8603
8826
|
return;
|
|
8604
8827
|
}
|
|
8605
|
-
const files =
|
|
8828
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
8606
8829
|
if (files.length === 0) {
|
|
8607
8830
|
process.stderr.write(`${dim("No discussions yet.")}
|
|
8608
8831
|
`);
|
|
@@ -8637,7 +8860,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
8637
8860
|
`);
|
|
8638
8861
|
return;
|
|
8639
8862
|
}
|
|
8640
|
-
const files =
|
|
8863
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
|
|
8641
8864
|
const match = files.find((f) => f.startsWith(id));
|
|
8642
8865
|
if (!match) {
|
|
8643
8866
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -8660,7 +8883,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
8660
8883
|
`);
|
|
8661
8884
|
return;
|
|
8662
8885
|
}
|
|
8663
|
-
const files =
|
|
8886
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
|
|
8664
8887
|
const match = files.find((f) => f.startsWith(id));
|
|
8665
8888
|
if (!match) {
|
|
8666
8889
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -8671,73 +8894,153 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
8671
8894
|
process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
|
|
8672
8895
|
`);
|
|
8673
8896
|
}
|
|
8674
|
-
async function
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8897
|
+
async function convertDiscussionToPlan(projectRoot, id) {
|
|
8898
|
+
if (!id) {
|
|
8899
|
+
process.stderr.write(`${red("✗")} Please provide a discussion ID.
|
|
8900
|
+
`);
|
|
8901
|
+
process.stderr.write(` Usage: ${bold("locus discuss plan <id>")}
|
|
8902
|
+
`);
|
|
8903
|
+
return;
|
|
8904
|
+
}
|
|
8905
|
+
const dir = getDiscussionsDir(projectRoot);
|
|
8906
|
+
if (!existsSync15(dir)) {
|
|
8907
|
+
process.stderr.write(`${red("✗")} No discussions found.
|
|
8908
|
+
`);
|
|
8909
|
+
return;
|
|
8910
|
+
}
|
|
8911
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
|
|
8912
|
+
const match = files.find((f) => f.startsWith(id));
|
|
8913
|
+
if (!match) {
|
|
8914
|
+
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
8915
|
+
`);
|
|
8916
|
+
return;
|
|
8917
|
+
}
|
|
8918
|
+
const content = readFileSync12(join16(dir, match), "utf-8");
|
|
8919
|
+
process.stderr.write(`
|
|
8920
|
+
${bold("Converting discussion to plan:")} ${cyan(id)}
|
|
8921
|
+
|
|
8922
|
+
`);
|
|
8923
|
+
await planCommand(projectRoot, [
|
|
8924
|
+
`Create a detailed, actionable implementation plan based on this discussion document:
|
|
8925
|
+
|
|
8926
|
+
${content.slice(0, 8000)}`
|
|
8927
|
+
], {});
|
|
8928
|
+
}
|
|
8929
|
+
async function promptForAnswers() {
|
|
8930
|
+
const input = new InputHandler({
|
|
8931
|
+
prompt: `${cyan("you")} ${dim(">")} `
|
|
8687
8932
|
});
|
|
8933
|
+
const result = await input.readline();
|
|
8934
|
+
if (result.type === "submit") {
|
|
8935
|
+
return result.text.trim();
|
|
8936
|
+
}
|
|
8937
|
+
return "";
|
|
8938
|
+
}
|
|
8939
|
+
function isQuestionsResponse(output) {
|
|
8940
|
+
const trimmed = output.trimStart();
|
|
8941
|
+
if (trimmed.startsWith("#"))
|
|
8942
|
+
return false;
|
|
8943
|
+
const questionMarks = (trimmed.match(/\?/g) ?? []).length;
|
|
8944
|
+
return questionMarks >= 2;
|
|
8688
8945
|
}
|
|
8689
8946
|
async function startDiscussion(projectRoot, topic, flags) {
|
|
8690
8947
|
const config = loadConfig(projectRoot);
|
|
8691
8948
|
const timer = createTimer();
|
|
8692
|
-
const id =
|
|
8949
|
+
const id = generateId2();
|
|
8693
8950
|
process.stderr.write(`
|
|
8694
8951
|
${bold("Discussion:")} ${cyan(topic)}
|
|
8695
8952
|
|
|
8696
8953
|
`);
|
|
8697
|
-
const
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8954
|
+
const conversation = [];
|
|
8955
|
+
let finalAnalysis = "";
|
|
8956
|
+
for (let round = 0;round < MAX_DISCUSSION_ROUNDS; round++) {
|
|
8957
|
+
const isFinalRound = round === MAX_DISCUSSION_ROUNDS - 1;
|
|
8958
|
+
const prompt = buildDiscussionPrompt(projectRoot, config, topic, conversation, isFinalRound);
|
|
8959
|
+
const aiResult = await runAI({
|
|
8960
|
+
prompt,
|
|
8961
|
+
provider: config.ai.provider,
|
|
8962
|
+
model: flags.model ?? config.ai.model,
|
|
8963
|
+
cwd: projectRoot,
|
|
8964
|
+
activity: "discussion"
|
|
8965
|
+
});
|
|
8966
|
+
if (aiResult.interrupted) {
|
|
8967
|
+
process.stderr.write(`
|
|
8707
8968
|
${yellow("⚡")} Discussion interrupted.
|
|
8708
8969
|
`);
|
|
8709
|
-
|
|
8970
|
+
if (!aiResult.output.trim())
|
|
8971
|
+
return;
|
|
8972
|
+
finalAnalysis = aiResult.output.trim();
|
|
8973
|
+
break;
|
|
8974
|
+
}
|
|
8975
|
+
if (!aiResult.success && !aiResult.interrupted) {
|
|
8976
|
+
process.stderr.write(`
|
|
8977
|
+
${red("✗")} Discussion failed: ${aiResult.error}
|
|
8978
|
+
`);
|
|
8710
8979
|
return;
|
|
8711
|
-
|
|
8712
|
-
|
|
8980
|
+
}
|
|
8981
|
+
const response = aiResult.output.trim();
|
|
8982
|
+
conversation.push({ role: "assistant", content: response });
|
|
8983
|
+
if (!isQuestionsResponse(response) || isFinalRound) {
|
|
8984
|
+
finalAnalysis = response;
|
|
8985
|
+
break;
|
|
8986
|
+
}
|
|
8987
|
+
process.stderr.write(`
|
|
8988
|
+
${dim("─".repeat(50))}
|
|
8989
|
+
${bold("Your answers:")} ${dim("(Shift+Enter for newlines, Enter to submit)")}
|
|
8990
|
+
|
|
8991
|
+
`);
|
|
8992
|
+
const answers = await promptForAnswers();
|
|
8993
|
+
if (!answers.trim()) {
|
|
8994
|
+
conversation.push({
|
|
8995
|
+
role: "user",
|
|
8996
|
+
content: "Please proceed with your analysis based on the information available."
|
|
8997
|
+
});
|
|
8998
|
+
} else {
|
|
8999
|
+
conversation.push({ role: "user", content: answers });
|
|
9000
|
+
}
|
|
8713
9001
|
process.stderr.write(`
|
|
8714
|
-
${red("✗")} Discussion failed: ${aiResult.error}
|
|
8715
9002
|
`);
|
|
8716
|
-
return;
|
|
8717
9003
|
}
|
|
8718
|
-
|
|
9004
|
+
if (!finalAnalysis)
|
|
9005
|
+
return;
|
|
8719
9006
|
const dir = ensureDiscussionsDir(projectRoot);
|
|
8720
9007
|
const date = new Date().toISOString().slice(0, 10);
|
|
8721
|
-
const
|
|
9008
|
+
const transcript = conversation.map((turn) => {
|
|
9009
|
+
const label = turn.role === "user" ? "You" : "AI";
|
|
9010
|
+
return `**${label}:**
|
|
8722
9011
|
|
|
8723
|
-
|
|
8724
|
-
|
|
9012
|
+
${turn.content}`;
|
|
9013
|
+
}).join(`
|
|
8725
9014
|
|
|
8726
9015
|
---
|
|
8727
9016
|
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
9017
|
+
`);
|
|
9018
|
+
const markdown = [
|
|
9019
|
+
`# ${topic}`,
|
|
9020
|
+
``,
|
|
9021
|
+
`**Date:** ${date}`,
|
|
9022
|
+
`**Provider:** ${config.ai.provider} / ${flags.model ?? config.ai.model}`,
|
|
9023
|
+
``,
|
|
9024
|
+
`---`,
|
|
9025
|
+
``,
|
|
9026
|
+
finalAnalysis,
|
|
9027
|
+
``,
|
|
9028
|
+
...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
|
|
9029
|
+
].join(`
|
|
9030
|
+
`);
|
|
9031
|
+
writeFileSync9(join16(dir, `${id}.md`), markdown, "utf-8");
|
|
8731
9032
|
process.stderr.write(`
|
|
8732
9033
|
${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
|
|
8733
9034
|
`);
|
|
8734
9035
|
process.stderr.write(` View with: ${bold(`locus discuss show ${id.slice(0, 8)}`)}
|
|
9036
|
+
`);
|
|
9037
|
+
process.stderr.write(` Plan with: ${bold(`locus discuss plan ${id.slice(0, 8)}`)}
|
|
8735
9038
|
|
|
8736
9039
|
`);
|
|
8737
9040
|
}
|
|
8738
|
-
function buildDiscussionPrompt(projectRoot, config, topic) {
|
|
9041
|
+
function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFinal) {
|
|
8739
9042
|
const parts = [];
|
|
8740
|
-
parts.push(`You are a senior software architect
|
|
9043
|
+
parts.push(`You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.`);
|
|
8741
9044
|
parts.push("");
|
|
8742
9045
|
const locusPath = join16(projectRoot, "LOCUS.md");
|
|
8743
9046
|
if (existsSync15(locusPath)) {
|
|
@@ -8753,26 +9056,49 @@ function buildDiscussionPrompt(projectRoot, config, topic) {
|
|
|
8753
9056
|
parts.push(content.slice(0, 2000));
|
|
8754
9057
|
parts.push("");
|
|
8755
9058
|
}
|
|
8756
|
-
parts.push(`TOPIC: ${topic}`);
|
|
8757
|
-
parts.push("");
|
|
8758
|
-
parts.push("Please provide a thorough analysis covering:");
|
|
8759
|
-
parts.push("1. **Context**: Restate the problem/question and why it matters");
|
|
8760
|
-
parts.push("2. **Options**: List all viable approaches with pros/cons");
|
|
8761
|
-
parts.push("3. **Recommendation**: Your recommended approach with reasoning");
|
|
8762
|
-
parts.push("4. **Trade-offs**: What we gain and what we sacrifice");
|
|
8763
|
-
parts.push("5. **Implementation Notes**: Key technical considerations");
|
|
8764
|
-
parts.push("6. **Decision**: A clear, actionable conclusion");
|
|
9059
|
+
parts.push(`DISCUSSION TOPIC: ${topic}`);
|
|
8765
9060
|
parts.push("");
|
|
8766
|
-
|
|
8767
|
-
|
|
9061
|
+
if (conversation.length === 0) {
|
|
9062
|
+
parts.push("Before providing recommendations, you need to ask targeted clarifying questions.");
|
|
9063
|
+
parts.push("");
|
|
9064
|
+
parts.push("Ask 3-5 focused questions that will significantly improve the quality of your analysis.");
|
|
9065
|
+
parts.push("Format as a numbered list. Be specific and focused on the most important unknowns.");
|
|
9066
|
+
parts.push("Do NOT provide any analysis yet — questions only.");
|
|
9067
|
+
} else {
|
|
9068
|
+
parts.push("CONVERSATION SO FAR:");
|
|
9069
|
+
parts.push("");
|
|
9070
|
+
for (const turn of conversation) {
|
|
9071
|
+
if (turn.role === "user") {
|
|
9072
|
+
parts.push(`USER: ${turn.content}`);
|
|
9073
|
+
} else {
|
|
9074
|
+
parts.push(`ASSISTANT: ${turn.content}`);
|
|
9075
|
+
}
|
|
9076
|
+
parts.push("");
|
|
9077
|
+
}
|
|
9078
|
+
if (forceFinal) {
|
|
9079
|
+
parts.push("Based on everything discussed, provide your complete analysis and recommendations now.");
|
|
9080
|
+
parts.push("Format as a thorough markdown document with a clear title (# Heading), sections, trade-offs, and actionable recommendations.");
|
|
9081
|
+
} else {
|
|
9082
|
+
parts.push("Review the information gathered so far.");
|
|
9083
|
+
parts.push("");
|
|
9084
|
+
parts.push("If you have enough information to make a thorough recommendation:");
|
|
9085
|
+
parts.push(" → Provide a complete analysis as a markdown document with a title (# Heading), sections, trade-offs, and concrete recommendations.");
|
|
9086
|
+
parts.push("");
|
|
9087
|
+
parts.push("If you still need key information to give a good answer:");
|
|
9088
|
+
parts.push(" → Ask 2-3 more focused follow-up questions (numbered list only, no analysis yet).");
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
8768
9091
|
return parts.join(`
|
|
8769
9092
|
`);
|
|
8770
9093
|
}
|
|
9094
|
+
var MAX_DISCUSSION_ROUNDS = 5;
|
|
8771
9095
|
var init_discuss = __esm(() => {
|
|
8772
9096
|
init_run_ai();
|
|
8773
9097
|
init_config();
|
|
8774
9098
|
init_progress();
|
|
8775
9099
|
init_terminal();
|
|
9100
|
+
init_input_handler();
|
|
9101
|
+
init_plan();
|
|
8776
9102
|
});
|
|
8777
9103
|
|
|
8778
9104
|
// src/commands/artifacts.ts
|
|
@@ -8784,7 +9110,7 @@ __export(exports_artifacts, {
|
|
|
8784
9110
|
formatDate: () => formatDate2,
|
|
8785
9111
|
artifactsCommand: () => artifactsCommand
|
|
8786
9112
|
});
|
|
8787
|
-
import { existsSync as existsSync16, readdirSync as
|
|
9113
|
+
import { existsSync as existsSync16, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
|
|
8788
9114
|
import { join as join17 } from "node:path";
|
|
8789
9115
|
function printHelp5() {
|
|
8790
9116
|
process.stderr.write(`
|
|
@@ -8811,7 +9137,7 @@ function listArtifacts(projectRoot) {
|
|
|
8811
9137
|
const dir = getArtifactsDir(projectRoot);
|
|
8812
9138
|
if (!existsSync16(dir))
|
|
8813
9139
|
return [];
|
|
8814
|
-
return
|
|
9140
|
+
return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
8815
9141
|
const filePath = join17(dir, fileName);
|
|
8816
9142
|
const stat = statSync4(filePath);
|
|
8817
9143
|
return {
|
|
@@ -9123,7 +9449,8 @@ ${bold("Examples:")}
|
|
|
9123
9449
|
locus init ${dim("# Set up Locus in this repo")}
|
|
9124
9450
|
locus exec ${dim("# Start interactive REPL")}
|
|
9125
9451
|
locus issue create "Fix login bug" ${dim("# Create a new issue")}
|
|
9126
|
-
locus plan "Build auth system" ${dim("# AI
|
|
9452
|
+
locus plan "Build auth system" ${dim("# AI creates a plan file")}
|
|
9453
|
+
locus plan approve <id> ${dim("# Create issues from saved plan")}
|
|
9127
9454
|
locus run ${dim("# Execute active sprint")}
|
|
9128
9455
|
locus run 42 43 ${dim("# Run issues in parallel")}
|
|
9129
9456
|
|