@locusai/cli 0.17.6 → 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 +452 -96
- 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,11 +8753,11 @@ __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
8763
|
import { createInterface as createInterface2 } from "node:readline";
|
|
@@ -8546,6 +8769,7 @@ ${bold("Usage:")}
|
|
|
8546
8769
|
locus discuss "<topic>" ${dim("# Start a new discussion")}
|
|
8547
8770
|
locus discuss list ${dim("# List all discussions")}
|
|
8548
8771
|
locus discuss show <id> ${dim("# Show a discussion")}
|
|
8772
|
+
locus discuss plan <id> ${dim("# Convert discussion to a plan")}
|
|
8549
8773
|
locus discuss delete <id> ${dim("# Delete a discussion")}
|
|
8550
8774
|
|
|
8551
8775
|
${bold("Examples:")}
|
|
@@ -8553,6 +8777,7 @@ ${bold("Examples:")}
|
|
|
8553
8777
|
locus discuss "Monorepo vs polyrepo for our microservices"
|
|
8554
8778
|
locus discuss list
|
|
8555
8779
|
locus discuss show abc123
|
|
8780
|
+
locus discuss plan abc123
|
|
8556
8781
|
|
|
8557
8782
|
`);
|
|
8558
8783
|
}
|
|
@@ -8562,11 +8787,11 @@ function getDiscussionsDir(projectRoot) {
|
|
|
8562
8787
|
function ensureDiscussionsDir(projectRoot) {
|
|
8563
8788
|
const dir = getDiscussionsDir(projectRoot);
|
|
8564
8789
|
if (!existsSync15(dir)) {
|
|
8565
|
-
|
|
8790
|
+
mkdirSync11(dir, { recursive: true });
|
|
8566
8791
|
}
|
|
8567
8792
|
return dir;
|
|
8568
8793
|
}
|
|
8569
|
-
function
|
|
8794
|
+
function generateId2() {
|
|
8570
8795
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8571
8796
|
}
|
|
8572
8797
|
async function discussCommand(projectRoot, args, flags = {}) {
|
|
@@ -8581,6 +8806,9 @@ async function discussCommand(projectRoot, args, flags = {}) {
|
|
|
8581
8806
|
if (subcommand === "show") {
|
|
8582
8807
|
return showDiscussion(projectRoot, args[1]);
|
|
8583
8808
|
}
|
|
8809
|
+
if (subcommand === "plan") {
|
|
8810
|
+
return convertDiscussionToPlan(projectRoot, args[1]);
|
|
8811
|
+
}
|
|
8584
8812
|
if (subcommand === "delete") {
|
|
8585
8813
|
return deleteDiscussion(projectRoot, args[1]);
|
|
8586
8814
|
}
|
|
@@ -8602,7 +8830,7 @@ function listDiscussions(projectRoot) {
|
|
|
8602
8830
|
`);
|
|
8603
8831
|
return;
|
|
8604
8832
|
}
|
|
8605
|
-
const files =
|
|
8833
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
8606
8834
|
if (files.length === 0) {
|
|
8607
8835
|
process.stderr.write(`${dim("No discussions yet.")}
|
|
8608
8836
|
`);
|
|
@@ -8637,7 +8865,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
8637
8865
|
`);
|
|
8638
8866
|
return;
|
|
8639
8867
|
}
|
|
8640
|
-
const files =
|
|
8868
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
|
|
8641
8869
|
const match = files.find((f) => f.startsWith(id));
|
|
8642
8870
|
if (!match) {
|
|
8643
8871
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -8660,7 +8888,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
8660
8888
|
`);
|
|
8661
8889
|
return;
|
|
8662
8890
|
}
|
|
8663
|
-
const files =
|
|
8891
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
|
|
8664
8892
|
const match = files.find((f) => f.startsWith(id));
|
|
8665
8893
|
if (!match) {
|
|
8666
8894
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -8671,6 +8899,38 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
8671
8899
|
process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
|
|
8672
8900
|
`);
|
|
8673
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
|
+
}
|
|
8674
8934
|
async function promptForTopic() {
|
|
8675
8935
|
return new Promise((resolve2) => {
|
|
8676
8936
|
const rl = createInterface2({
|
|
@@ -8686,58 +8946,131 @@ async function promptForTopic() {
|
|
|
8686
8946
|
rl.once("close", () => resolve2(""));
|
|
8687
8947
|
});
|
|
8688
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
|
+
}
|
|
8689
8977
|
async function startDiscussion(projectRoot, topic, flags) {
|
|
8690
8978
|
const config = loadConfig(projectRoot);
|
|
8691
8979
|
const timer = createTimer();
|
|
8692
|
-
const id =
|
|
8980
|
+
const id = generateId2();
|
|
8693
8981
|
process.stderr.write(`
|
|
8694
8982
|
${bold("Discussion:")} ${cyan(topic)}
|
|
8695
8983
|
|
|
8696
8984
|
`);
|
|
8697
|
-
const
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
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(`
|
|
8707
8999
|
${yellow("⚡")} Discussion interrupted.
|
|
8708
9000
|
`);
|
|
8709
|
-
|
|
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
|
+
`);
|
|
8710
9010
|
return;
|
|
8711
|
-
|
|
8712
|
-
|
|
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
|
+
}
|
|
8713
9031
|
process.stderr.write(`
|
|
8714
|
-
${red("✗")} Discussion failed: ${aiResult.error}
|
|
8715
9032
|
`);
|
|
8716
|
-
return;
|
|
8717
9033
|
}
|
|
8718
|
-
|
|
9034
|
+
if (!finalAnalysis)
|
|
9035
|
+
return;
|
|
8719
9036
|
const dir = ensureDiscussionsDir(projectRoot);
|
|
8720
9037
|
const date = new Date().toISOString().slice(0, 10);
|
|
8721
|
-
const
|
|
9038
|
+
const transcript = conversation.map((turn) => {
|
|
9039
|
+
const label = turn.role === "user" ? "You" : "AI";
|
|
9040
|
+
return `**${label}:**
|
|
8722
9041
|
|
|
8723
|
-
|
|
8724
|
-
|
|
9042
|
+
${turn.content}`;
|
|
9043
|
+
}).join(`
|
|
8725
9044
|
|
|
8726
9045
|
---
|
|
8727
9046
|
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
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");
|
|
8731
9062
|
process.stderr.write(`
|
|
8732
9063
|
${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
|
|
8733
9064
|
`);
|
|
8734
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)}`)}
|
|
8735
9068
|
|
|
8736
9069
|
`);
|
|
8737
9070
|
}
|
|
8738
|
-
function buildDiscussionPrompt(projectRoot, config, topic) {
|
|
9071
|
+
function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFinal) {
|
|
8739
9072
|
const parts = [];
|
|
8740
|
-
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.`);
|
|
8741
9074
|
parts.push("");
|
|
8742
9075
|
const locusPath = join16(projectRoot, "LOCUS.md");
|
|
8743
9076
|
if (existsSync15(locusPath)) {
|
|
@@ -8753,26 +9086,48 @@ function buildDiscussionPrompt(projectRoot, config, topic) {
|
|
|
8753
9086
|
parts.push(content.slice(0, 2000));
|
|
8754
9087
|
parts.push("");
|
|
8755
9088
|
}
|
|
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");
|
|
9089
|
+
parts.push(`DISCUSSION TOPIC: ${topic}`);
|
|
8765
9090
|
parts.push("");
|
|
8766
|
-
|
|
8767
|
-
|
|
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
|
+
}
|
|
8768
9121
|
return parts.join(`
|
|
8769
9122
|
`);
|
|
8770
9123
|
}
|
|
9124
|
+
var MAX_DISCUSSION_ROUNDS = 5;
|
|
8771
9125
|
var init_discuss = __esm(() => {
|
|
8772
9126
|
init_run_ai();
|
|
8773
9127
|
init_config();
|
|
8774
9128
|
init_progress();
|
|
8775
9129
|
init_terminal();
|
|
9130
|
+
init_plan();
|
|
8776
9131
|
});
|
|
8777
9132
|
|
|
8778
9133
|
// src/commands/artifacts.ts
|
|
@@ -8784,7 +9139,7 @@ __export(exports_artifacts, {
|
|
|
8784
9139
|
formatDate: () => formatDate2,
|
|
8785
9140
|
artifactsCommand: () => artifactsCommand
|
|
8786
9141
|
});
|
|
8787
|
-
import { existsSync as existsSync16, readdirSync as
|
|
9142
|
+
import { existsSync as existsSync16, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync4 } from "node:fs";
|
|
8788
9143
|
import { join as join17 } from "node:path";
|
|
8789
9144
|
function printHelp5() {
|
|
8790
9145
|
process.stderr.write(`
|
|
@@ -8811,7 +9166,7 @@ function listArtifacts(projectRoot) {
|
|
|
8811
9166
|
const dir = getArtifactsDir(projectRoot);
|
|
8812
9167
|
if (!existsSync16(dir))
|
|
8813
9168
|
return [];
|
|
8814
|
-
return
|
|
9169
|
+
return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
8815
9170
|
const filePath = join17(dir, fileName);
|
|
8816
9171
|
const stat = statSync4(filePath);
|
|
8817
9172
|
return {
|
|
@@ -9123,7 +9478,8 @@ ${bold("Examples:")}
|
|
|
9123
9478
|
locus init ${dim("# Set up Locus in this repo")}
|
|
9124
9479
|
locus exec ${dim("# Start interactive REPL")}
|
|
9125
9480
|
locus issue create "Fix login bug" ${dim("# Create a new issue")}
|
|
9126
|
-
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")}
|
|
9127
9483
|
locus run ${dim("# Execute active sprint")}
|
|
9128
9484
|
locus run 42 43 ${dim("# Run issues in parallel")}
|
|
9129
9485
|
|