claude-teammate 0.1.312 → 0.1.313

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-teammate",
3
- "version": "0.1.312",
3
+ "version": "0.1.313",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -422,6 +422,8 @@ export function buildGitHubPRPlanBreakdownSystemPrompt() {
422
422
  "Each step must be self-contained: a single developer can implement it in isolation by reading only that step description.",
423
423
  "Steps must be ordered by dependency — steps that others depend on come first.",
424
424
  "Each step description must be specific enough to act on without reading the full PR body: name the exact files, classes, functions, or fields to change and what the change is.",
425
+ "Each step must describe a product code, configuration, schema, or test-code change in the target repository.",
426
+ "Do not create process-only steps such as opening a terminal, changing directories, locating files, reading files, running grep/ls/find, or using an editor.",
425
427
  "Aim for 3-8 steps. Do not produce trivial steps or steps that only say 'test' or 'verify'.",
426
428
  LANGUAGE_PRESERVATION_RULES,
427
429
  "Return only structured output matching the provided schema.",
@@ -444,9 +446,10 @@ ${JSON.stringify(input.memory?.epic || {}, null, 2)}
444
446
  Instructions:
445
447
  - Produce an ordered list of atomic implementation steps.
446
448
  - Each step must be specific enough to implement without reading this full description again: name the exact files, classes, methods, or enum values involved.
449
+ - Each step must describe a code/config/schema/test-code change, not an execution tactic or repository exploration action.
447
450
  - Steps must be ordered by dependency (earlier steps must not depend on later ones).
448
451
  - Aim for 3-8 steps covering the full scope of the plan.
449
- - Do not include steps for committing, pushing, or testing unless the test is part of the plan.`;
452
+ - Do not include steps for committing, pushing, opening a terminal, changing directories, locating files, reading files, running grep/ls/find, using an editor, or testing unless the test is part of the plan.`;
450
453
  }
451
454
 
452
455
  export function buildGitHubPRStepBreakdownSystemPrompt() {
@@ -455,6 +458,8 @@ export function buildGitHubPRStepBreakdownSystemPrompt() {
455
458
  "Each sub-step must be self-contained and implementable by reading only that sub-step description.",
456
459
  "Sub-steps must be ordered by dependency.",
457
460
  "Each sub-step must be specific: name the exact files, classes, functions, or fields to change.",
461
+ "Each sub-step must describe a code/config/schema/test-code change, not an execution tactic or repository exploration action.",
462
+ "Do not create process-only sub-steps such as opening a terminal, changing directories, locating files, reading files, running grep/ls/find, or using an editor.",
458
463
  "Do not produce trivial sub-steps.",
459
464
  LANGUAGE_PRESERVATION_RULES,
460
465
  "Return only structured output matching the provided schema."
@@ -473,8 +478,9 @@ ${input.step}
473
478
  Instructions:
474
479
  - Produce 2-4 ordered atomic sub-steps that together complete this step.
475
480
  - Each sub-step must be specific: name the exact files, classes, methods, or values to change.
481
+ - Each sub-step must describe a code/config/schema/test-code change, not an execution tactic or repository exploration action.
476
482
  - Sub-steps must be ordered by dependency.
477
- - Do not include steps for committing, pushing, or testing.`;
483
+ - Do not include steps for committing, pushing, opening a terminal, changing directories, locating files, reading files, running grep/ls/find, using an editor, or testing.`;
478
484
  }
479
485
 
480
486
  export function buildGitHubPRImplementationStepSystemPrompt() {
@@ -2,11 +2,12 @@ import { mkdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { atomicWriteFile } from "../fs-atomic.js";
4
4
 
5
- // Durable map of Claude session ids keyed by repo + PR branch, so a PR comment
5
+ // Durable map of Claude session ids keyed by repo + PR branch + PR scope, so a PR comment
6
6
  // or reopen can resume the prior conversation instead of rebuilding context
7
7
  // from memory (which previously dropped diffs / decisions and caused the model
8
- // to "forget" earlier work). Stored under the same memory dir as issue memory
9
- // so it survives worker restarts.
8
+ // to "forget" earlier work). The PR scope prevents separate PRs that reuse a
9
+ // generic branch name from inheriting each other's Claude conversation.
10
+ // Stored under the same memory dir as issue memory so it survives worker restarts.
10
11
  const SESSIONS_FILE = "pr-sessions.json";
11
12
  const MAX_ENTRIES = 1000;
12
13
 
@@ -14,8 +15,13 @@ function sessionStorePath(projectRoot) {
14
15
  return path.join(projectRoot, "memory", SESSIONS_FILE);
15
16
  }
16
17
 
17
- function sessionKey(repoUrl, branchName) {
18
- return `${String(repoUrl || "").trim()}#${String(branchName || "").trim()}`;
18
+ function sessionKey(repoUrl, branchName, scope = "") {
19
+ const parts = [String(repoUrl || "").trim(), String(branchName || "").trim()];
20
+ const normalizedScope = String(scope || "").trim();
21
+ if (normalizedScope) {
22
+ parts.push(normalizedScope);
23
+ }
24
+ return parts.join("#");
19
25
  }
20
26
 
21
27
  async function readStore(projectRoot) {
@@ -29,12 +35,12 @@ async function readStore(projectRoot) {
29
35
  }
30
36
  }
31
37
 
32
- export async function loadPrSessionId(projectRoot, repoUrl, branchName) {
38
+ export async function loadPrSessionId(projectRoot, repoUrl, branchName, scope = "") {
33
39
  if (!projectRoot || !branchName) {
34
40
  return "";
35
41
  }
36
42
  const store = await readStore(projectRoot);
37
- const entry = store[sessionKey(repoUrl, branchName)];
43
+ const entry = store[sessionKey(repoUrl, branchName, scope)];
38
44
  return entry && typeof entry.session_id === "string" ? entry.session_id : "";
39
45
  }
40
46
 
@@ -42,14 +48,17 @@ export async function loadPrSessionId(projectRoot, repoUrl, branchName) {
42
48
  // never lose each other's entries.
43
49
  let writeChain = Promise.resolve();
44
50
 
45
- export async function savePrSessionId(projectRoot, repoUrl, branchName, sessionId) {
51
+ export async function savePrSessionId(projectRoot, repoUrl, branchName, scopeOrSessionId, maybeSessionId) {
52
+ const hasScope = maybeSessionId !== undefined;
53
+ const scope = hasScope ? scopeOrSessionId : "";
54
+ const sessionId = hasScope ? maybeSessionId : scopeOrSessionId;
46
55
  if (!projectRoot || !branchName || !sessionId) {
47
56
  return;
48
57
  }
49
58
  writeChain = writeChain
50
59
  .then(async () => {
51
60
  const store = await readStore(projectRoot);
52
- store[sessionKey(repoUrl, branchName)] = {
61
+ store[sessionKey(repoUrl, branchName, scope)] = {
53
62
  session_id: String(sessionId).trim(),
54
63
  updated_at: new Date().toISOString()
55
64
  };
@@ -2,8 +2,7 @@ import {
2
2
  isClaudeCliError,
3
3
  runClaudeImplementation,
4
4
  runClaudeImplementationPlanBreakdown,
5
- runClaudeImplementationStep,
6
- runClaudeStepBreakdown
5
+ runClaudeImplementationStep
7
6
  } from "../claude.js";
8
7
  import {
9
8
  branchHasChangesAgainstMain,
@@ -30,13 +29,23 @@ import {
30
29
  formatPullRequestOutcomeComment,
31
30
  getNextPlanStep,
32
31
  hasImplementationPlan,
33
- replaceStepWithSubSteps,
34
32
  setImplementationPlan,
35
33
  setPullRequestStatus,
34
+ stripImplementationPlan,
36
35
  tickPlanStep
37
36
  } from "./pull-request.js";
38
37
  import { extractJiraKey, parseOptionalInt, summarizeForLog, toErrorMessage } from "./utils.js";
39
38
 
39
+ const STEP_BRANCH_DIFF_MAX_CHARS = 3000;
40
+
41
+ function truncateForPrompt(value, maxChars, label) {
42
+ const text = String(value || "");
43
+ if (text.length <= maxChars) {
44
+ return text;
45
+ }
46
+ return `${text.slice(0, maxChars)}\n\n[${label}: ${text.length - maxChars} chars omitted]`;
47
+ }
48
+
40
49
  async function commitPartialPullRequestChanges(repo, detail, logger) {
41
50
  try {
42
51
  const commitResult = await commitAndPushRepoChanges(
@@ -81,7 +90,6 @@ export function createPullRequestImplementationServices(overrides = {}) {
81
90
  runClaudeImplementation,
82
91
  runClaudeImplementationPlanBreakdown,
83
92
  runClaudeImplementationStep,
84
- runClaudeStepBreakdown,
85
93
  commitAndPushRepoChanges,
86
94
  prepareRepoForBranch,
87
95
  persistEpicImplementationKnowledge,
@@ -186,9 +194,13 @@ export async function processPullRequestImplementation({
186
194
  // it from memory (which dropped diffs/decisions and caused re-work). The
187
195
  // branchDiff + latestComment below remain as the fallback when no session
188
196
  // id is stored or the stored id is stale.
189
- const resumeSessionId = await loadPrSessionId(projectRoot, repo.url, detail.headRef);
197
+ const prSessionScope =
198
+ [githubIssueMemory?.jira_key, detail.number, detail.url].filter(Boolean).map(String).join("#") ||
199
+ detail.headRef ||
200
+ issueKey;
201
+ const resumeSessionId = await loadPrSessionId(projectRoot, repo.url, detail.headRef, prSessionScope);
190
202
  const onSessionId = (sessionId) => {
191
- void savePrSessionId(projectRoot, repo.url, detail.headRef, sessionId);
203
+ void savePrSessionId(projectRoot, repo.url, detail.headRef, prSessionScope, sessionId);
192
204
  };
193
205
 
194
206
  // Re-ground the run on any work earlier runs already committed to this
@@ -198,12 +210,17 @@ export async function processPullRequestImplementation({
198
210
  typeof services.getBranchDiffAgainstBase === "function"
199
211
  ? await services.getBranchDiffAgainstBase(repoPath, detail.baseRef)
200
212
  : "";
213
+ const stepBranchDiff = truncateForPrompt(
214
+ branchDiff,
215
+ STEP_BRANCH_DIFF_MAX_CHARS,
216
+ "branch diff truncated for step prompt"
217
+ );
201
218
 
202
219
  // Proactively break down large PRs before attempting full implementation
203
220
  if (!inStepMode && nextBody.length > 5000) {
204
221
  try {
205
222
  const proactiveBreakdown = await services.runClaudeImplementationPlanBreakdown({
206
- pullRequest: { ...detail, body: nextBody.slice(0, 8000) },
223
+ pullRequest: { ...detail, body: stripImplementationPlan(nextBody).slice(0, 8000) },
207
224
  memory: { epic: epicSnapshot },
208
225
  issueKey,
209
226
  logger
@@ -269,7 +286,7 @@ export async function processPullRequestImplementation({
269
286
  step: stepText,
270
287
  memory: { epic: epicSnapshot },
271
288
  epicContext: epicMemory,
272
- branchDiff,
289
+ branchDiff: stepBranchDiff,
273
290
  repoPath,
274
291
  repoPaths: repoAccess.repoPaths,
275
292
  branchName: detail.headRef,
@@ -504,7 +521,7 @@ export async function processPullRequestImplementation({
504
521
  const issueKey = githubIssueMemory?.jira_key || `PR-${detail.number}`;
505
522
  const epicSnapshot = buildEpicMemoryPromptSnapshot(epicMemory);
506
523
  const breakdown = await services.runClaudeImplementationPlanBreakdown({
507
- pullRequest: { ...detail, body: nextBody.slice(0, 8000) },
524
+ pullRequest: { ...detail, body: stripImplementationPlan(nextBody).slice(0, 8000) },
508
525
  memory: { epic: epicSnapshot },
509
526
  issueKey,
510
527
  logger
@@ -527,38 +544,15 @@ export async function processPullRequestImplementation({
527
544
  });
528
545
  }
529
546
  }
530
- // Try to split the failing step into sub-steps before giving up permanently.
531
547
  if (inStepMode && stepText) {
532
- try {
533
- const issueKey = githubIssueMemory?.jira_key || `PR-${detail.number}`;
534
- const subBreakdown = await services.runClaudeStepBreakdown({
535
- pullRequest: { ...detail },
536
- step: stepText,
537
- issueKey,
538
- logger
539
- });
540
- if (subBreakdown?.steps?.length > 1) {
541
- const splitBody = setPullRequestStatus(
542
- replaceStepWithSubSteps(nextBody, stepText, subBreakdown.steps),
543
- "IMPLEMENTING"
544
- );
545
- await github.updatePullRequest(repo.url, detail.number, { title: detail.title, body: splitBody });
546
- await logger.info("Split too-large step into sub-steps", {
547
- repo: repo.url,
548
- pr: detail.number,
549
- subSteps: subBreakdown.steps.length
550
- });
551
- return buildDraftPrState({ ...detail, body: splitBody }, "IMPLEMENTING", "implementing");
552
- }
553
- } catch (stepBreakdownError) {
554
- await logger.error("Failed to split stuck step into sub-steps", {
555
- repo: repo.url,
556
- pr: detail.number,
557
- error: stepBreakdownError
558
- });
559
- }
548
+ await logger.error("Pull request implementation step is too large; stopping without recursive breakdown", {
549
+ repo: repo.url,
550
+ pr: detail.number,
551
+ step: summarizeForLog(stepText)
552
+ });
560
553
  }
561
- // Step could not be subdivided further permanent stuck.
554
+ // A planned step that still exceeds the prompt budget should stop here
555
+ // instead of recursively expanding the PR body with more generated steps.
562
556
  // Otherwise (no plan yet, breakdown failed) → allow auto-retry by preserving STUCK_AT.
563
557
  const promptTooLongComment = inStepMode
564
558
  ? "Even a single implementation step is too large for Claude to process. Please split this PR into smaller, more focused changes."
@@ -132,15 +132,30 @@ const PLAN_START = "<!-- tm8:steps -->";
132
132
  const PLAN_END = "<!-- /tm8:steps -->";
133
133
  const UNCHECKED_STEP_RE = /^- \[ \] (.+)$/mu;
134
134
  const CHECKED_STEP_RE = /^- \[x\] /mu;
135
+ const MAX_IMPLEMENTATION_PLAN_STEPS = 20;
136
+ const MAX_STEP_BREAKDOWN_STEPS = 4;
137
+ const MAX_IMPLEMENTATION_STEP_CHARS = 1000;
135
138
 
136
139
  export function hasImplementationPlan(body) {
137
140
  const s = String(body || "");
138
141
  return s.includes(PLAN_START) && s.includes(PLAN_END);
139
142
  }
140
143
 
144
+ export function stripImplementationPlan(body) {
145
+ const normalized = String(body || "");
146
+ if (!hasImplementationPlan(normalized)) {
147
+ return normalized;
148
+ }
149
+ return normalized
150
+ .replace(new RegExp(`\\n*${escapeRegExpStr(PLAN_START)}[\\s\\S]*?${escapeRegExpStr(PLAN_END)}`, "u"), "")
151
+ .trimEnd();
152
+ }
153
+
141
154
  export function setImplementationPlan(body, steps) {
142
155
  const normalized = String(body || "");
143
- const planLines = steps.map((step) => `- [ ] ${step}`).join("\n");
156
+ const planLines = normalizeImplementationSteps(steps, MAX_IMPLEMENTATION_PLAN_STEPS)
157
+ .map((step) => `- [ ] ${step}`)
158
+ .join("\n");
144
159
  const planBlock = `${PLAN_START}\n${planLines}\n${PLAN_END}`;
145
160
  if (hasImplementationPlan(normalized)) {
146
161
  return normalized.replace(
@@ -173,7 +188,9 @@ export function replaceStepWithSubSteps(body, stepText, subSteps) {
173
188
  return body;
174
189
  }
175
190
  const escaped = stepText.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
176
- const subLines = subSteps.map((s) => `- [ ] ${s}`).join("\n");
191
+ const subLines = normalizeImplementationSteps(subSteps, MAX_STEP_BREAKDOWN_STEPS)
192
+ .map((s) => `- [ ] ${s}`)
193
+ .join("\n");
177
194
  return String(body || "").replace(new RegExp(`^- \\[ \\] ${escaped}$`, "mu"), subLines);
178
195
  }
179
196
 
@@ -192,6 +209,25 @@ function escapeRegExpStr(value) {
192
209
  return String(value).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
193
210
  }
194
211
 
212
+ function normalizeImplementationSteps(steps, maxSteps) {
213
+ if (!Array.isArray(steps)) {
214
+ return [];
215
+ }
216
+ return steps
217
+ .map((step) =>
218
+ String(step || "")
219
+ .replace(/\s+/gu, " ")
220
+ .trim()
221
+ )
222
+ .filter(Boolean)
223
+ .slice(0, maxSteps)
224
+ .map((step) =>
225
+ step.length > MAX_IMPLEMENTATION_STEP_CHARS
226
+ ? `${step.slice(0, MAX_IMPLEMENTATION_STEP_CHARS - 3).trimEnd()}...`
227
+ : step
228
+ );
229
+ }
230
+
195
231
  export function formatPullRequestOutcomeComment(summary, fallback) {
196
232
  const normalized = String(summary || "").trim();
197
233
  return normalized || fallback;