@locusai/cli 0.18.1 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/locus.js +292 -208
- package/package.json +1 -1
package/bin/locus.js
CHANGED
|
@@ -1440,6 +1440,9 @@ function updateIssueLabels(number, addLabels, removeLabels, options = {}) {
|
|
|
1440
1440
|
}
|
|
1441
1441
|
gh(args, options);
|
|
1442
1442
|
}
|
|
1443
|
+
function deleteIssue(number, options = {}) {
|
|
1444
|
+
gh(`issue delete ${number} --yes`, options);
|
|
1445
|
+
}
|
|
1443
1446
|
function addIssueComment(number, body, options = {}) {
|
|
1444
1447
|
const cwd = options.cwd ?? process.cwd();
|
|
1445
1448
|
execFileSync("gh", ["issue", "comment", String(number), "--body", body], {
|
|
@@ -5822,6 +5825,10 @@ async function issueCommand(projectRoot, args) {
|
|
|
5822
5825
|
case "close":
|
|
5823
5826
|
await issueClose(projectRoot, parsed);
|
|
5824
5827
|
break;
|
|
5828
|
+
case "delete":
|
|
5829
|
+
case "rm":
|
|
5830
|
+
await issueDelete(projectRoot, parsed);
|
|
5831
|
+
break;
|
|
5825
5832
|
default:
|
|
5826
5833
|
if (/^\d+$/.test(parsed.subcommand)) {
|
|
5827
5834
|
parsed.positional.unshift(parsed.subcommand);
|
|
@@ -5949,21 +5956,26 @@ ${dim("────────────────────────
|
|
|
5949
5956
|
}
|
|
5950
5957
|
}
|
|
5951
5958
|
function buildIssueCreationPrompt(userRequest) {
|
|
5952
|
-
return
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5959
|
+
return `<role>
|
|
5960
|
+
You are a task planner for a software development team.
|
|
5961
|
+
Given a user request, create a well-structured GitHub issue.
|
|
5962
|
+
</role>
|
|
5963
|
+
|
|
5964
|
+
<output-format>
|
|
5965
|
+
Output ONLY a valid JSON object with exactly these fields:
|
|
5966
|
+
- "title": A concise, actionable issue title (max 80 characters)
|
|
5967
|
+
- "body": Detailed markdown description with context, acceptance criteria, and technical notes
|
|
5968
|
+
- "priority": One of: critical, high, medium, low
|
|
5969
|
+
- "type": One of: feature, bug, chore, refactor, docs
|
|
5970
|
+
</output-format>
|
|
5971
|
+
|
|
5972
|
+
<user-request>
|
|
5973
|
+
${userRequest}
|
|
5974
|
+
</user-request>
|
|
5975
|
+
|
|
5976
|
+
<constraints>
|
|
5977
|
+
Output ONLY the JSON object. No explanations, no code execution, no other text.
|
|
5978
|
+
</constraints>`;
|
|
5967
5979
|
}
|
|
5968
5980
|
function extractJSON(text) {
|
|
5969
5981
|
const codeBlock = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
@@ -6227,6 +6239,43 @@ async function issueClose(projectRoot, parsed) {
|
|
|
6227
6239
|
process.exit(1);
|
|
6228
6240
|
}
|
|
6229
6241
|
}
|
|
6242
|
+
async function issueDelete(projectRoot, parsed) {
|
|
6243
|
+
const issueNumbers = parsed.positional.filter((a) => /^\d+$/.test(a)).map(Number);
|
|
6244
|
+
if (issueNumbers.length === 0) {
|
|
6245
|
+
process.stderr.write(`${red("✗")} No issue numbers provided.
|
|
6246
|
+
`);
|
|
6247
|
+
process.stderr.write(` Usage: ${bold("locus issue delete <number...>")}
|
|
6248
|
+
`);
|
|
6249
|
+
process.exit(1);
|
|
6250
|
+
}
|
|
6251
|
+
const label = issueNumbers.length === 1 ? `issue #${issueNumbers[0]}` : `${issueNumbers.length} issues (#${issueNumbers.join(", #")})`;
|
|
6252
|
+
process.stderr.write(`${yellow("⚠")} This will ${bold("permanently delete")} ${label}.
|
|
6253
|
+
`);
|
|
6254
|
+
const answer = await askQuestion(`${cyan("?")} Are you sure? ${dim("[y/N]")} `);
|
|
6255
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
6256
|
+
process.stderr.write(`${yellow("○")} Cancelled.
|
|
6257
|
+
`);
|
|
6258
|
+
return;
|
|
6259
|
+
}
|
|
6260
|
+
let failed = 0;
|
|
6261
|
+
for (const num of issueNumbers) {
|
|
6262
|
+
process.stderr.write(`${cyan("●")} Deleting issue #${num}...`);
|
|
6263
|
+
try {
|
|
6264
|
+
deleteIssue(num, { cwd: projectRoot });
|
|
6265
|
+
process.stderr.write(`\r${green("✓")} Deleted issue #${num}
|
|
6266
|
+
`);
|
|
6267
|
+
} catch (e) {
|
|
6268
|
+
process.stderr.write(`\r${red("✗")} #${num}: ${e.message}
|
|
6269
|
+
`);
|
|
6270
|
+
failed++;
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
if (issueNumbers.length > 1 && failed === 0) {
|
|
6274
|
+
process.stderr.write(`
|
|
6275
|
+
${green("✓")} All ${issueNumbers.length} issues deleted.
|
|
6276
|
+
`);
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6230
6279
|
function formatPriority(labels) {
|
|
6231
6280
|
for (const label of labels) {
|
|
6232
6281
|
if (label === "p:critical")
|
|
@@ -6329,6 +6378,7 @@ ${bold("Subcommands:")}
|
|
|
6329
6378
|
${cyan("show")} Show issue details
|
|
6330
6379
|
${cyan("label")} Bulk-update labels / sprint assignment
|
|
6331
6380
|
${cyan("close")} Close an issue
|
|
6381
|
+
${cyan("delete")} ${dim("(rm)")} Permanently delete issues (bulk)
|
|
6332
6382
|
|
|
6333
6383
|
${bold("Create options:")}
|
|
6334
6384
|
${dim("--sprint, -s")} Assign to sprint (milestone)
|
|
@@ -6350,6 +6400,7 @@ ${bold("Examples:")}
|
|
|
6350
6400
|
locus issue show 42
|
|
6351
6401
|
locus issue label 42 43 --sprint "Sprint 2"
|
|
6352
6402
|
locus issue close 42
|
|
6403
|
+
locus issue delete 42 43 44
|
|
6353
6404
|
|
|
6354
6405
|
`);
|
|
6355
6406
|
}
|
|
@@ -7047,30 +7098,30 @@ function buildFeedbackPrompt(ctx) {
|
|
|
7047
7098
|
}
|
|
7048
7099
|
function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
|
|
7049
7100
|
const sections = [];
|
|
7050
|
-
const locusmd = readFileSafe(join13(projectRoot, "LOCUS.md"));
|
|
7101
|
+
const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
|
|
7051
7102
|
if (locusmd) {
|
|
7052
|
-
sections.push(
|
|
7053
|
-
|
|
7054
|
-
|
|
7103
|
+
sections.push(`<project-instructions>
|
|
7104
|
+
${locusmd}
|
|
7105
|
+
</project-instructions>`);
|
|
7055
7106
|
}
|
|
7056
7107
|
const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
|
|
7057
7108
|
if (learnings) {
|
|
7058
|
-
sections.push(
|
|
7059
|
-
|
|
7060
|
-
|
|
7109
|
+
sections.push(`<past-learnings>
|
|
7110
|
+
${learnings}
|
|
7111
|
+
</past-learnings>`);
|
|
7061
7112
|
}
|
|
7062
7113
|
if (previousMessages && previousMessages.length > 0) {
|
|
7063
7114
|
const recent = previousMessages.slice(-10);
|
|
7064
7115
|
const historyLines = recent.map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`);
|
|
7065
|
-
sections.push(
|
|
7066
|
-
|
|
7116
|
+
sections.push(`<previous-conversation>
|
|
7067
7117
|
${historyLines.join(`
|
|
7068
7118
|
|
|
7069
|
-
`)}
|
|
7119
|
+
`)}
|
|
7120
|
+
</previous-conversation>`);
|
|
7070
7121
|
}
|
|
7071
|
-
sections.push(
|
|
7072
|
-
|
|
7073
|
-
|
|
7122
|
+
sections.push(`<current-request>
|
|
7123
|
+
${userMessage}
|
|
7124
|
+
</current-request>`);
|
|
7074
7125
|
return sections.join(`
|
|
7075
7126
|
|
|
7076
7127
|
---
|
|
@@ -7078,18 +7129,18 @@ ${userMessage}`);
|
|
|
7078
7129
|
`);
|
|
7079
7130
|
}
|
|
7080
7131
|
function buildSystemContext(projectRoot) {
|
|
7081
|
-
const parts = [
|
|
7082
|
-
const locusmd = readFileSafe(join13(projectRoot, "LOCUS.md"));
|
|
7132
|
+
const parts = [];
|
|
7133
|
+
const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
|
|
7083
7134
|
if (locusmd) {
|
|
7084
|
-
parts.push(
|
|
7085
|
-
|
|
7086
|
-
|
|
7135
|
+
parts.push(`<project-instructions>
|
|
7136
|
+
${locusmd}
|
|
7137
|
+
</project-instructions>`);
|
|
7087
7138
|
}
|
|
7088
7139
|
const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
|
|
7089
7140
|
if (learnings) {
|
|
7090
|
-
parts.push(
|
|
7091
|
-
|
|
7092
|
-
|
|
7141
|
+
parts.push(`<past-learnings>
|
|
7142
|
+
${learnings}
|
|
7143
|
+
</past-learnings>`);
|
|
7093
7144
|
}
|
|
7094
7145
|
const discussionsDir = join13(projectRoot, ".locus", "discussions");
|
|
7095
7146
|
if (existsSync14(discussionsDir)) {
|
|
@@ -7098,43 +7149,46 @@ ${learnings}`);
|
|
|
7098
7149
|
for (const file of files) {
|
|
7099
7150
|
const content = readFileSafe(join13(discussionsDir, file));
|
|
7100
7151
|
if (content) {
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
${content.slice(0, 2000)}
|
|
7152
|
+
const name = file.replace(".md", "");
|
|
7153
|
+
parts.push(`<discussion name="${name}">
|
|
7154
|
+
${content.slice(0, 2000)}
|
|
7155
|
+
</discussion>`);
|
|
7104
7156
|
}
|
|
7105
7157
|
}
|
|
7106
7158
|
} catch {}
|
|
7107
7159
|
}
|
|
7108
|
-
return
|
|
7160
|
+
return `<system-context>
|
|
7161
|
+
${parts.join(`
|
|
7109
7162
|
|
|
7110
|
-
`)
|
|
7163
|
+
`)}
|
|
7164
|
+
</system-context>`;
|
|
7111
7165
|
}
|
|
7112
7166
|
function buildTaskContext(issue, comments) {
|
|
7113
|
-
const parts = [
|
|
7114
|
-
|
|
7115
|
-
``,
|
|
7116
|
-
`## Issue #${issue.number}: ${issue.title}`,
|
|
7117
|
-
``,
|
|
7118
|
-
issue.body || "_No description provided._"
|
|
7119
|
-
];
|
|
7167
|
+
const parts = [];
|
|
7168
|
+
const issueParts = [issue.body || "_No description provided._"];
|
|
7120
7169
|
const labels = issue.labels.filter((l) => l.startsWith("p:") || l.startsWith("type:"));
|
|
7121
7170
|
if (labels.length > 0) {
|
|
7122
|
-
|
|
7123
|
-
**Labels:** ${labels.join(", ")}`);
|
|
7171
|
+
issueParts.push(`**Labels:** ${labels.join(", ")}`);
|
|
7124
7172
|
}
|
|
7173
|
+
parts.push(`<issue number="${issue.number}" title="${issue.title}">
|
|
7174
|
+
${issueParts.join(`
|
|
7175
|
+
|
|
7176
|
+
`)}
|
|
7177
|
+
</issue>`);
|
|
7125
7178
|
if (comments && comments.length > 0) {
|
|
7126
|
-
parts.push(
|
|
7127
|
-
|
|
7128
|
-
`)
|
|
7129
|
-
|
|
7130
|
-
parts.push(comment);
|
|
7131
|
-
}
|
|
7179
|
+
parts.push(`<issue-comments>
|
|
7180
|
+
${comments.join(`
|
|
7181
|
+
`)}
|
|
7182
|
+
</issue-comments>`);
|
|
7132
7183
|
}
|
|
7133
|
-
return
|
|
7134
|
-
`
|
|
7184
|
+
return `<task-context>
|
|
7185
|
+
${parts.join(`
|
|
7186
|
+
|
|
7187
|
+
`)}
|
|
7188
|
+
</task-context>`;
|
|
7135
7189
|
}
|
|
7136
7190
|
function buildSprintContext(sprintName, position, diffSummary) {
|
|
7137
|
-
const parts = [
|
|
7191
|
+
const parts = [];
|
|
7138
7192
|
if (sprintName) {
|
|
7139
7193
|
parts.push(`**Sprint:** ${sprintName}`);
|
|
7140
7194
|
}
|
|
@@ -7142,30 +7196,31 @@ function buildSprintContext(sprintName, position, diffSummary) {
|
|
|
7142
7196
|
parts.push(`**Position:** Task ${position}`);
|
|
7143
7197
|
}
|
|
7144
7198
|
if (diffSummary) {
|
|
7145
|
-
parts.push(
|
|
7146
|
-
## Changes from Previous Tasks
|
|
7147
|
-
|
|
7199
|
+
parts.push(`<previous-changes>
|
|
7148
7200
|
The following changes have already been made by earlier tasks in this sprint:
|
|
7149
7201
|
|
|
7150
7202
|
\`\`\`diff
|
|
7151
7203
|
${diffSummary}
|
|
7152
|
-
|
|
7204
|
+
\`\`\`
|
|
7205
|
+
</previous-changes>`);
|
|
7153
7206
|
}
|
|
7154
|
-
parts.push(
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7207
|
+
parts.push(`**Important:** Build upon the changes from previous tasks. Do not revert or undo their work.`);
|
|
7208
|
+
return `<sprint-context>
|
|
7209
|
+
${parts.join(`
|
|
7210
|
+
|
|
7211
|
+
`)}
|
|
7212
|
+
</sprint-context>`;
|
|
7158
7213
|
}
|
|
7159
7214
|
function buildRepoContext(projectRoot) {
|
|
7160
|
-
const parts = [
|
|
7215
|
+
const parts = [];
|
|
7161
7216
|
try {
|
|
7162
7217
|
const tree = execSync10("find . -maxdepth 2 -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/.locus/*' -not -path '*/dist/*' -not -path '*/build/*' | head -80", { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
7163
7218
|
if (tree) {
|
|
7164
|
-
parts.push(
|
|
7165
|
-
|
|
7219
|
+
parts.push(`<file-tree>
|
|
7166
7220
|
\`\`\`
|
|
7167
7221
|
${tree}
|
|
7168
|
-
|
|
7222
|
+
\`\`\`
|
|
7223
|
+
</file-tree>`);
|
|
7169
7224
|
}
|
|
7170
7225
|
} catch {}
|
|
7171
7226
|
try {
|
|
@@ -7175,11 +7230,11 @@ ${tree}
|
|
|
7175
7230
|
stdio: ["pipe", "pipe", "pipe"]
|
|
7176
7231
|
}).trim();
|
|
7177
7232
|
if (gitLog) {
|
|
7178
|
-
parts.push(
|
|
7179
|
-
|
|
7233
|
+
parts.push(`<recent-commits>
|
|
7180
7234
|
\`\`\`
|
|
7181
7235
|
${gitLog}
|
|
7182
|
-
|
|
7236
|
+
\`\`\`
|
|
7237
|
+
</recent-commits>`);
|
|
7183
7238
|
}
|
|
7184
7239
|
} catch {}
|
|
7185
7240
|
try {
|
|
@@ -7190,13 +7245,14 @@ ${gitLog}
|
|
|
7190
7245
|
}).trim();
|
|
7191
7246
|
parts.push(`**Current branch:** ${branch}`);
|
|
7192
7247
|
} catch {}
|
|
7193
|
-
return
|
|
7248
|
+
return `<repository-context>
|
|
7249
|
+
${parts.join(`
|
|
7194
7250
|
|
|
7195
|
-
`)
|
|
7251
|
+
`)}
|
|
7252
|
+
</repository-context>`;
|
|
7196
7253
|
}
|
|
7197
7254
|
function buildExecutionRules(config) {
|
|
7198
|
-
return
|
|
7199
|
-
|
|
7255
|
+
return `<execution-rules>
|
|
7200
7256
|
1. **Commit format:** Use conventional commits: \`feat: <title> (#<issue>)\`, \`fix: ...\`, \`chore: ...\`. Every commit message MUST be multi-line: the first line is the title, then a blank line, then \`Co-Authored-By: LocusAgent <agent@locusai.team>\` as a Git trailer. Use \`git commit -m "<title>" -m "Co-Authored-By: LocusAgent <agent@locusai.team>"\` (two separate -m flags) to ensure the trailer is on its own line.
|
|
7201
7257
|
2. **Code quality:** Follow existing code style. Run linters/formatters if available.
|
|
7202
7258
|
3. **Testing:** If test files exist for modified code, update them accordingly.
|
|
@@ -7208,37 +7264,37 @@ function buildExecutionRules(config) {
|
|
|
7208
7264
|
5. **Base branch:** ${config.agent.baseBranch}
|
|
7209
7265
|
6. **Provider:** ${config.ai.provider} / ${config.ai.model}
|
|
7210
7266
|
|
|
7211
|
-
When you are done, provide a brief summary of what you changed and why
|
|
7267
|
+
When you are done, provide a brief summary of what you changed and why.
|
|
7268
|
+
</execution-rules>`;
|
|
7212
7269
|
}
|
|
7213
7270
|
function buildPRContext(prNumber, diff, comments) {
|
|
7214
7271
|
const parts = [
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
diff.slice(0, 1e4),
|
|
7221
|
-
"```"
|
|
7272
|
+
`<pr-diff>
|
|
7273
|
+
\`\`\`diff
|
|
7274
|
+
${diff.slice(0, 1e4)}
|
|
7275
|
+
\`\`\`
|
|
7276
|
+
</pr-diff>`
|
|
7222
7277
|
];
|
|
7223
7278
|
if (comments.length > 0) {
|
|
7224
|
-
parts.push(
|
|
7225
|
-
|
|
7226
|
-
`)
|
|
7227
|
-
|
|
7228
|
-
parts.push(comment);
|
|
7229
|
-
}
|
|
7279
|
+
parts.push(`<review-comments>
|
|
7280
|
+
${comments.join(`
|
|
7281
|
+
`)}
|
|
7282
|
+
</review-comments>`);
|
|
7230
7283
|
}
|
|
7231
|
-
return
|
|
7232
|
-
`
|
|
7284
|
+
return `<pr-context number="${prNumber}">
|
|
7285
|
+
${parts.join(`
|
|
7286
|
+
|
|
7287
|
+
`)}
|
|
7288
|
+
</pr-context>`;
|
|
7233
7289
|
}
|
|
7234
7290
|
function buildFeedbackInstructions() {
|
|
7235
|
-
return
|
|
7236
|
-
|
|
7291
|
+
return `<instructions>
|
|
7237
7292
|
1. Address ALL review feedback from the comments above.
|
|
7238
7293
|
2. Make targeted changes — do NOT rewrite code from scratch.
|
|
7239
7294
|
3. If a reviewer comment is unclear, make your best judgment and note your interpretation.
|
|
7240
7295
|
4. Push changes to the same branch — do NOT create a new PR.
|
|
7241
|
-
5. When done, summarize what you changed in response to each comment
|
|
7296
|
+
5. When done, summarize what you changed in response to each comment.
|
|
7297
|
+
</instructions>`;
|
|
7242
7298
|
}
|
|
7243
7299
|
function readFileSafe(path) {
|
|
7244
7300
|
try {
|
|
@@ -10102,16 +10158,21 @@ ${i.body?.slice(0, 300) ?? ""}`).join(`
|
|
|
10102
10158
|
|
|
10103
10159
|
`);
|
|
10104
10160
|
const { runAI: runAI2 } = await Promise.resolve().then(() => (init_run_ai(), exports_run_ai));
|
|
10105
|
-
const prompt =
|
|
10161
|
+
const prompt = `<role>
|
|
10162
|
+
You are organizing GitHub issues for a sprint. Analyze these issues and suggest the optimal execution order.
|
|
10163
|
+
</role>
|
|
10106
10164
|
|
|
10107
|
-
|
|
10165
|
+
<issues>
|
|
10108
10166
|
${issueDescriptions}
|
|
10167
|
+
</issues>
|
|
10109
10168
|
|
|
10169
|
+
<instructions>
|
|
10110
10170
|
For each issue, output a line in this format:
|
|
10111
10171
|
ORDER: #<number> <reason for this position>
|
|
10112
10172
|
|
|
10113
10173
|
Order them so that dependencies are respected (issues that produce code needed by later issues should come first).
|
|
10114
|
-
Start with foundational/setup tasks, then core features, then integration/testing
|
|
10174
|
+
Start with foundational/setup tasks, then core features, then integration/testing.
|
|
10175
|
+
</instructions>`;
|
|
10115
10176
|
const aiResult = await runAI2({
|
|
10116
10177
|
prompt,
|
|
10117
10178
|
provider: config.ai.provider,
|
|
@@ -10184,59 +10245,62 @@ ${bold("Suggested Order:")}
|
|
|
10184
10245
|
}
|
|
10185
10246
|
function buildPlanningPrompt(projectRoot, config, directive, sprintName, id, planPathRelative) {
|
|
10186
10247
|
const parts = [];
|
|
10187
|
-
parts.push(
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10248
|
+
parts.push(`<role>
|
|
10249
|
+
You are a sprint planning assistant for the GitHub repository ${config.github.owner}/${config.github.repo}.
|
|
10250
|
+
</role>`);
|
|
10251
|
+
parts.push(`<directive>
|
|
10252
|
+
${directive}${sprintName ? `
|
|
10253
|
+
|
|
10254
|
+
**Sprint:** ${sprintName}` : ""}
|
|
10255
|
+
</directive>`);
|
|
10256
|
+
const locusPath = join18(projectRoot, ".locus", "LOCUS.md");
|
|
10195
10257
|
if (existsSync18(locusPath)) {
|
|
10196
10258
|
const content = readFileSync13(locusPath, "utf-8");
|
|
10197
|
-
parts.push(
|
|
10198
|
-
|
|
10199
|
-
|
|
10259
|
+
parts.push(`<project-context>
|
|
10260
|
+
${content.slice(0, 3000)}
|
|
10261
|
+
</project-context>`);
|
|
10200
10262
|
}
|
|
10201
10263
|
const learningsPath = join18(projectRoot, ".locus", "LEARNINGS.md");
|
|
10202
10264
|
if (existsSync18(learningsPath)) {
|
|
10203
10265
|
const content = readFileSync13(learningsPath, "utf-8");
|
|
10204
|
-
parts.push(
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
}
|
|
10208
|
-
parts.push(
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
parts.push(
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10266
|
+
parts.push(`<past-learnings>
|
|
10267
|
+
${content.slice(0, 2000)}
|
|
10268
|
+
</past-learnings>`);
|
|
10269
|
+
}
|
|
10270
|
+
parts.push(`<task>
|
|
10271
|
+
Break down the directive into specific, actionable GitHub issues and write them to the file: ${planPathRelative}
|
|
10272
|
+
|
|
10273
|
+
Write ONLY a valid JSON file to ${planPathRelative} with this exact structure:
|
|
10274
|
+
|
|
10275
|
+
\`\`\`json
|
|
10276
|
+
{
|
|
10277
|
+
"id": "${id}",
|
|
10278
|
+
"directive": ${JSON.stringify(directive)},
|
|
10279
|
+
"sprint": ${sprintName ? JSON.stringify(sprintName) : "null"},
|
|
10280
|
+
"createdAt": "${new Date().toISOString()}",
|
|
10281
|
+
"issues": [
|
|
10282
|
+
{
|
|
10283
|
+
"order": 1,
|
|
10284
|
+
"title": "concise issue title",
|
|
10285
|
+
"body": "detailed markdown body with acceptance criteria",
|
|
10286
|
+
"priority": "critical|high|medium|low",
|
|
10287
|
+
"type": "feature|bug|chore|refactor|docs",
|
|
10288
|
+
"dependsOn": "none or comma-separated order numbers"
|
|
10289
|
+
}
|
|
10290
|
+
]
|
|
10291
|
+
}
|
|
10292
|
+
\`\`\`
|
|
10293
|
+
</task>`);
|
|
10294
|
+
parts.push(`<requirements>
|
|
10295
|
+
- Break the directive into 3-10 specific, actionable issues
|
|
10296
|
+
- Each issue must be independently executable by an AI agent
|
|
10297
|
+
- Order them so dependencies are respected (foundational tasks first)
|
|
10298
|
+
- Write detailed issue bodies with clear acceptance criteria
|
|
10299
|
+
- Use valid GitHub Markdown only in issue bodies
|
|
10300
|
+
- Create the file using the Write tool — do not print the JSON to the terminal
|
|
10301
|
+
</requirements>`);
|
|
10239
10302
|
return parts.join(`
|
|
10303
|
+
|
|
10240
10304
|
`);
|
|
10241
10305
|
}
|
|
10242
10306
|
function sanitizePlanOutput(output) {
|
|
@@ -10535,47 +10599,55 @@ _Reviewed by Locus AI (${config.ai.provider}/${flags.model ?? config.ai.model})_
|
|
|
10535
10599
|
}
|
|
10536
10600
|
function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
|
|
10537
10601
|
const parts = [];
|
|
10538
|
-
parts.push(
|
|
10539
|
-
|
|
10540
|
-
|
|
10602
|
+
parts.push(`<role>
|
|
10603
|
+
You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.
|
|
10604
|
+
</role>`);
|
|
10605
|
+
const locusPath = join19(projectRoot, ".locus", "LOCUS.md");
|
|
10541
10606
|
if (existsSync19(locusPath)) {
|
|
10542
10607
|
const content = readFileSync14(locusPath, "utf-8");
|
|
10543
|
-
parts.push(
|
|
10544
|
-
|
|
10545
|
-
|
|
10608
|
+
parts.push(`<project-context>
|
|
10609
|
+
${content.slice(0, 2000)}
|
|
10610
|
+
</project-context>`);
|
|
10546
10611
|
}
|
|
10547
|
-
|
|
10548
|
-
parts.push(`Branch: ${pr.head} → ${pr.base}`);
|
|
10612
|
+
const prMeta = [`Branch: ${pr.head} → ${pr.base}`];
|
|
10549
10613
|
if (pr.body) {
|
|
10550
|
-
|
|
10614
|
+
prMeta.push(`Description:
|
|
10551
10615
|
${pr.body.slice(0, 1000)}`);
|
|
10552
10616
|
}
|
|
10553
|
-
parts.push(""
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
parts.push(
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10617
|
+
parts.push(`<pull-request number="${pr.number}" title="${pr.title}">
|
|
10618
|
+
${prMeta.join(`
|
|
10619
|
+
`)}
|
|
10620
|
+
</pull-request>`);
|
|
10621
|
+
parts.push(`<diff>
|
|
10622
|
+
${diff.slice(0, 50000)}
|
|
10623
|
+
</diff>`);
|
|
10624
|
+
let instructions = `Provide a thorough code review. For each issue found, describe:
|
|
10625
|
+
1. The file and approximate location
|
|
10626
|
+
2. What the issue is
|
|
10627
|
+
3. Why it matters
|
|
10628
|
+
4. How to fix it
|
|
10629
|
+
|
|
10630
|
+
Categories to check:
|
|
10631
|
+
- Correctness: bugs, logic errors, edge cases
|
|
10632
|
+
- Security: injection, XSS, auth issues, secret exposure
|
|
10633
|
+
- Performance: N+1 queries, unnecessary allocations, missing caching
|
|
10634
|
+
- Maintainability: naming, complexity, code organization
|
|
10635
|
+
- Testing: missing tests, inadequate coverage`;
|
|
10570
10636
|
if (focus) {
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10637
|
+
instructions += `
|
|
10638
|
+
|
|
10639
|
+
**Focus areas:** ${focus}
|
|
10640
|
+
Pay special attention to the above areas.`;
|
|
10574
10641
|
}
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10642
|
+
instructions += `
|
|
10643
|
+
|
|
10644
|
+
End with an overall assessment: APPROVE, REQUEST_CHANGES, or COMMENT.
|
|
10645
|
+
Be constructive and specific. Praise good patterns too.`;
|
|
10646
|
+
parts.push(`<review-instructions>
|
|
10647
|
+
${instructions}
|
|
10648
|
+
</review-instructions>`);
|
|
10578
10649
|
return parts.join(`
|
|
10650
|
+
|
|
10579
10651
|
`);
|
|
10580
10652
|
}
|
|
10581
10653
|
var init_review = __esm(() => {
|
|
@@ -11130,55 +11202,67 @@ ${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
|
|
|
11130
11202
|
}
|
|
11131
11203
|
function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFinal) {
|
|
11132
11204
|
const parts = [];
|
|
11133
|
-
parts.push(
|
|
11134
|
-
|
|
11135
|
-
|
|
11205
|
+
parts.push(`<role>
|
|
11206
|
+
You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.
|
|
11207
|
+
</role>`);
|
|
11208
|
+
const locusPath = join20(projectRoot, ".locus", "LOCUS.md");
|
|
11136
11209
|
if (existsSync20(locusPath)) {
|
|
11137
11210
|
const content = readFileSync15(locusPath, "utf-8");
|
|
11138
|
-
parts.push(
|
|
11139
|
-
|
|
11140
|
-
|
|
11211
|
+
parts.push(`<project-context>
|
|
11212
|
+
${content.slice(0, 3000)}
|
|
11213
|
+
</project-context>`);
|
|
11141
11214
|
}
|
|
11142
11215
|
const learningsPath = join20(projectRoot, ".locus", "LEARNINGS.md");
|
|
11143
11216
|
if (existsSync20(learningsPath)) {
|
|
11144
11217
|
const content = readFileSync15(learningsPath, "utf-8");
|
|
11145
|
-
parts.push(
|
|
11146
|
-
|
|
11147
|
-
|
|
11218
|
+
parts.push(`<past-learnings>
|
|
11219
|
+
${content.slice(0, 2000)}
|
|
11220
|
+
</past-learnings>`);
|
|
11148
11221
|
}
|
|
11149
|
-
parts.push(
|
|
11150
|
-
|
|
11222
|
+
parts.push(`<discussion-topic>
|
|
11223
|
+
${topic}
|
|
11224
|
+
</discussion-topic>`);
|
|
11151
11225
|
if (conversation.length === 0) {
|
|
11152
|
-
parts.push(
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11226
|
+
parts.push(`<instructions>
|
|
11227
|
+
Before providing recommendations, you need to ask targeted clarifying questions.
|
|
11228
|
+
|
|
11229
|
+
Ask 3-5 focused questions that will significantly improve the quality of your analysis.
|
|
11230
|
+
Format as a numbered list. Be specific and focused on the most important unknowns.
|
|
11231
|
+
Do NOT provide any analysis yet — questions only.
|
|
11232
|
+
</instructions>`);
|
|
11157
11233
|
} else {
|
|
11158
|
-
|
|
11159
|
-
parts.push("");
|
|
11234
|
+
const historyLines = [];
|
|
11160
11235
|
for (const turn of conversation) {
|
|
11161
11236
|
if (turn.role === "user") {
|
|
11162
|
-
|
|
11237
|
+
historyLines.push(`USER: ${turn.content}`);
|
|
11163
11238
|
} else {
|
|
11164
|
-
|
|
11239
|
+
historyLines.push(`ASSISTANT: ${turn.content}`);
|
|
11165
11240
|
}
|
|
11166
|
-
parts.push("");
|
|
11167
11241
|
}
|
|
11242
|
+
parts.push(`<conversation-history>
|
|
11243
|
+
${historyLines.join(`
|
|
11244
|
+
|
|
11245
|
+
`)}
|
|
11246
|
+
</conversation-history>`);
|
|
11168
11247
|
if (forceFinal) {
|
|
11169
|
-
parts.push(
|
|
11170
|
-
|
|
11248
|
+
parts.push(`<instructions>
|
|
11249
|
+
Based on everything discussed, provide your complete analysis and recommendations now.
|
|
11250
|
+
Format as a thorough markdown document with a clear title (# Heading), sections, trade-offs, and actionable recommendations.
|
|
11251
|
+
</instructions>`);
|
|
11171
11252
|
} else {
|
|
11172
|
-
parts.push(
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11253
|
+
parts.push(`<instructions>
|
|
11254
|
+
Review the information gathered so far.
|
|
11255
|
+
|
|
11256
|
+
If you have enough information to make a thorough recommendation:
|
|
11257
|
+
→ Provide a complete analysis as a markdown document with a title (# Heading), sections, trade-offs, and concrete recommendations.
|
|
11258
|
+
|
|
11259
|
+
If you still need key information to give a good answer:
|
|
11260
|
+
→ Ask 2-3 more focused follow-up questions (numbered list only, no analysis yet).
|
|
11261
|
+
</instructions>`);
|
|
11179
11262
|
}
|
|
11180
11263
|
}
|
|
11181
11264
|
return parts.join(`
|
|
11265
|
+
|
|
11182
11266
|
`);
|
|
11183
11267
|
}
|
|
11184
11268
|
var MAX_DISCUSSION_ROUNDS = 5;
|