@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.
Files changed (2) hide show
  1. package/bin/locus.js +292 -208
  2. 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
- "You are a task planner for a software development team.",
5954
- "Given a user request, create a well-structured GitHub issue.",
5955
- "",
5956
- "Output ONLY a valid JSON object with exactly these fields:",
5957
- '- "title": A concise, actionable issue title (max 80 characters)',
5958
- '- "body": Detailed markdown description with context, acceptance criteria, and technical notes',
5959
- '- "priority": One of: critical, high, medium, low',
5960
- '- "type": One of: feature, bug, chore, refactor, docs',
5961
- "",
5962
- `User request: ${userRequest}`,
5963
- "",
5964
- "Output ONLY the JSON object. No explanations, no code execution, no other text."
5965
- ].join(`
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(`# Project Instructions
7053
-
7054
- ${locusmd}`);
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(`# Past Learnings
7059
-
7060
- ${learnings}`);
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(`# Previous Conversation
7066
-
7116
+ sections.push(`<previous-conversation>
7067
7117
  ${historyLines.join(`
7068
7118
 
7069
- `)}`);
7119
+ `)}
7120
+ </previous-conversation>`);
7070
7121
  }
7071
- sections.push(`# Current Request
7072
-
7073
- ${userMessage}`);
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 = ["# System Context"];
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(`## Project Instructions (LOCUS.md)
7085
-
7086
- ${locusmd}`);
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(`## Past Learnings
7091
-
7092
- ${learnings}`);
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
- parts.push(`## Discussion: ${file.replace(".md", "")}
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 parts.join(`
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
- `# Task Context`,
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
- parts.push(`
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
- ## Issue Comments
7128
- `);
7129
- for (const comment of comments) {
7130
- parts.push(comment);
7131
- }
7179
+ parts.push(`<issue-comments>
7180
+ ${comments.join(`
7181
+ `)}
7182
+ </issue-comments>`);
7132
7183
  }
7133
- return parts.join(`
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 = ["# Sprint Context"];
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
- **Important:** Build upon the changes from previous tasks. Do not revert or undo their work.`);
7156
- return parts.join(`
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 = ["# Repository Context"];
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(`## File Tree
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(`## Recent Commits
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 parts.join(`
7248
+ return `<repository-context>
7249
+ ${parts.join(`
7194
7250
 
7195
- `);
7251
+ `)}
7252
+ </repository-context>`;
7196
7253
  }
7197
7254
  function buildExecutionRules(config) {
7198
- return `# Execution Rules
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
- `# Current State — PR #${prNumber}`,
7216
- ``,
7217
- `## PR Diff`,
7218
- ``,
7219
- "```diff",
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
- ## Review Comments
7226
- `);
7227
- for (const comment of comments) {
7228
- parts.push(comment);
7229
- }
7279
+ parts.push(`<review-comments>
7280
+ ${comments.join(`
7281
+ `)}
7282
+ </review-comments>`);
7230
7283
  }
7231
- return parts.join(`
7232
- `);
7284
+ return `<pr-context number="${prNumber}">
7285
+ ${parts.join(`
7286
+
7287
+ `)}
7288
+ </pr-context>`;
7233
7289
  }
7234
7290
  function buildFeedbackInstructions() {
7235
- return `# Instructions
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 = `You are organizing GitHub issues for a sprint. Analyze these issues and suggest the optimal execution order.
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
- Issues:
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(`You are a sprint planning assistant for the GitHub repository ${config.github.owner}/${config.github.repo}.`);
10188
- parts.push("");
10189
- parts.push(`DIRECTIVE: ${directive}`);
10190
- if (sprintName) {
10191
- parts.push(`SPRINT: ${sprintName}`);
10192
- }
10193
- parts.push("");
10194
- const locusPath = join18(projectRoot, "LOCUS.md");
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("PROJECT CONTEXT (LOCUS.md):");
10198
- parts.push(content.slice(0, 3000));
10199
- parts.push("");
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("PAST LEARNINGS:");
10205
- parts.push(content.slice(0, 2000));
10206
- parts.push("");
10207
- }
10208
- parts.push("TASK:");
10209
- parts.push(`Break down the directive into specific, actionable GitHub issues and write them to the file: ${planPathRelative}`);
10210
- parts.push("");
10211
- parts.push(`Write ONLY a valid JSON file to ${planPathRelative} with this exact structure:`);
10212
- parts.push("");
10213
- parts.push("```json");
10214
- parts.push("{");
10215
- parts.push(` "id": "${id}",`);
10216
- parts.push(` "directive": ${JSON.stringify(directive)},`);
10217
- parts.push(` "sprint": ${sprintName ? JSON.stringify(sprintName) : "null"},`);
10218
- parts.push(` "createdAt": "${new Date().toISOString()}",`);
10219
- parts.push(' "issues": [');
10220
- parts.push(" {");
10221
- parts.push(' "order": 1,');
10222
- parts.push(' "title": "concise issue title",');
10223
- parts.push(' "body": "detailed markdown body with acceptance criteria",');
10224
- parts.push(' "priority": "critical|high|medium|low",');
10225
- parts.push(' "type": "feature|bug|chore|refactor|docs",');
10226
- parts.push(' "dependsOn": "none or comma-separated order numbers"');
10227
- parts.push(" }");
10228
- parts.push(" ]");
10229
- parts.push("}");
10230
- parts.push("```");
10231
- parts.push("");
10232
- parts.push("Requirements for the issues:");
10233
- parts.push("- Break the directive into 3-10 specific, actionable issues");
10234
- parts.push("- Each issue must be independently executable by an AI agent");
10235
- parts.push("- Order them so dependencies are respected (foundational tasks first)");
10236
- parts.push("- Write detailed issue bodies with clear acceptance criteria");
10237
- parts.push("- Use valid GitHub Markdown only in issue bodies");
10238
- parts.push("- Create the file using the Write tool — do not print the JSON to the terminal");
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(`You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.`);
10539
- parts.push("");
10540
- const locusPath = join19(projectRoot, "LOCUS.md");
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("PROJECT CONTEXT:");
10544
- parts.push(content.slice(0, 2000));
10545
- parts.push("");
10608
+ parts.push(`<project-context>
10609
+ ${content.slice(0, 2000)}
10610
+ </project-context>`);
10546
10611
  }
10547
- parts.push(`PULL REQUEST #${pr.number}: ${pr.title}`);
10548
- parts.push(`Branch: ${pr.head} → ${pr.base}`);
10612
+ const prMeta = [`Branch: ${pr.head} ${pr.base}`];
10549
10613
  if (pr.body) {
10550
- parts.push(`Description:
10614
+ prMeta.push(`Description:
10551
10615
  ${pr.body.slice(0, 1000)}`);
10552
10616
  }
10553
- parts.push("");
10554
- parts.push("DIFF:");
10555
- parts.push(diff.slice(0, 50000));
10556
- parts.push("");
10557
- parts.push("REVIEW INSTRUCTIONS:");
10558
- parts.push("Provide a thorough code review. For each issue found, describe:");
10559
- parts.push("1. The file and approximate location");
10560
- parts.push("2. What the issue is");
10561
- parts.push("3. Why it matters");
10562
- parts.push("4. How to fix it");
10563
- parts.push("");
10564
- parts.push("Categories to check:");
10565
- parts.push("- Correctness: bugs, logic errors, edge cases");
10566
- parts.push("- Security: injection, XSS, auth issues, secret exposure");
10567
- parts.push("- Performance: N+1 queries, unnecessary allocations, missing caching");
10568
- parts.push("- Maintainability: naming, complexity, code organization");
10569
- parts.push("- Testing: missing tests, inadequate coverage");
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
- parts.push("");
10572
- parts.push(`FOCUS AREAS: ${focus}`);
10573
- parts.push("Pay special attention to the above areas.");
10637
+ instructions += `
10638
+
10639
+ **Focus areas:** ${focus}
10640
+ Pay special attention to the above areas.`;
10574
10641
  }
10575
- parts.push("");
10576
- parts.push("End with an overall assessment: APPROVE, REQUEST_CHANGES, or COMMENT.");
10577
- parts.push("Be constructive and specific. Praise good patterns too.");
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(`You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.`);
11134
- parts.push("");
11135
- const locusPath = join20(projectRoot, "LOCUS.md");
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("PROJECT CONTEXT:");
11139
- parts.push(content.slice(0, 3000));
11140
- parts.push("");
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("PAST LEARNINGS:");
11146
- parts.push(content.slice(0, 2000));
11147
- parts.push("");
11218
+ parts.push(`<past-learnings>
11219
+ ${content.slice(0, 2000)}
11220
+ </past-learnings>`);
11148
11221
  }
11149
- parts.push(`DISCUSSION TOPIC: ${topic}`);
11150
- parts.push("");
11222
+ parts.push(`<discussion-topic>
11223
+ ${topic}
11224
+ </discussion-topic>`);
11151
11225
  if (conversation.length === 0) {
11152
- parts.push("Before providing recommendations, you need to ask targeted clarifying questions.");
11153
- parts.push("");
11154
- parts.push("Ask 3-5 focused questions that will significantly improve the quality of your analysis.");
11155
- parts.push("Format as a numbered list. Be specific and focused on the most important unknowns.");
11156
- parts.push("Do NOT provide any analysis yet questions only.");
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
- parts.push("CONVERSATION SO FAR:");
11159
- parts.push("");
11234
+ const historyLines = [];
11160
11235
  for (const turn of conversation) {
11161
11236
  if (turn.role === "user") {
11162
- parts.push(`USER: ${turn.content}`);
11237
+ historyLines.push(`USER: ${turn.content}`);
11163
11238
  } else {
11164
- parts.push(`ASSISTANT: ${turn.content}`);
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("Based on everything discussed, provide your complete analysis and recommendations now.");
11170
- parts.push("Format as a thorough markdown document with a clear title (# Heading), sections, trade-offs, and actionable recommendations.");
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("Review the information gathered so far.");
11173
- parts.push("");
11174
- parts.push("If you have enough information to make a thorough recommendation:");
11175
- parts.push(" → Provide a complete analysis as a markdown document with a title (# Heading), sections, trade-offs, and concrete recommendations.");
11176
- parts.push("");
11177
- parts.push("If you still need key information to give a good answer:");
11178
- parts.push(" → Ask 2-3 more focused follow-up questions (numbered list only, no analysis yet).");
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "GitHub-native AI engineering assistant",
5
5
  "type": "module",
6
6
  "bin": {