claude-teammate 0.1.311 → 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.311",
3
+ "version": "0.1.313",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,14 @@ const LANGUAGE_PRESERVATION_RULES = [
32
32
  "When writing Vietnamese, always use proper Vietnamese diacritics (e.g. 'Ho so nang luc' is wrong; 'Hồ sơ năng lực' is correct)."
33
33
  ].join(" ");
34
34
 
35
+ // Only the 4 typographic characters below may be ASCII-folded. This rule must
36
+ // never be generalized into "non-ASCII not allowed in code" — it does not touch
37
+ // letters with diacritics (Vietnamese accents, đ, etc.), which stay exactly as written.
38
+ const SMART_PUNCTUATION_RULE =
39
+ "When editing code or markup, only these typographic characters are disallowed and must be replaced: " +
40
+ 'smart quotes -> ", em/en dashes -> -. ' +
41
+ "This applies ONLY to those characters. Never alter letters with diacritics or any other natural-language text; keep it exactly as written.";
42
+
35
43
  export function buildRepoSelectionSystemPrompt() {
36
44
  return [
37
45
  "You are selecting the single repository that a Jira implementation task should use.",
@@ -338,8 +346,8 @@ export function buildGitHubPRImplementationSystemPrompt() {
338
346
  "Implement the plan from the pull request description.",
339
347
  "Read the epic memory snapshot before making changes so you can reuse known repo-specific build or verification knowledge and avoid repeating past mistakes.",
340
348
  "Run only the commands needed to inspect, edit, and test.",
341
- 'When editing code or markup, smart quotes and em/en dashes are not allowed; replace them if encountered: smart quotes -> ", em/en dashes -> -.',
342
349
  LANGUAGE_PRESERVATION_RULES,
350
+ SMART_PUNCTUATION_RULE,
343
351
  "Only perform frontend/browser verification when the task explicitly asks for UI validation, when the latest human feedback asks for visual confirmation, or when the affected change is clearly user-facing and the verification path is obvious from the repository or epic memory.",
344
352
  "When frontend/browser verification is needed, prefer the narrowest useful check, such as an existing targeted test or a short focused Playwright flow, instead of broad exploratory verification.",
345
353
  "Do not spend significant time inferring how to start the app or configuring Playwright unless that verification is necessary to confirm the requested change.",
@@ -396,7 +404,7 @@ ${formatPriorWorkContext(input)}
396
404
  Instructions:
397
405
  - Checkout the branch above in this repository.
398
406
  - Implement the plan described in the pull request body.
399
- - Smart quotes and em/en dashes are not allowed. Use: smart quotes -> ", em/en dashes -> -.
407
+ - Only these typographic characters are disallowed in code/markup and must be replaced: smart quotes -> ", em/en dashes -> -. This does not apply to diacritics or other natural-language text — keep those exactly as written.
400
408
  - If the latest human PR comment requests a change or reports a problem, address that feedback as part of this implementation run.
401
409
  - Only use frontend/browser verification when it is needed to confirm the requested change. If verification is needed, prefer existing targeted checks or the most direct focused Playwright flow you can determine from the repository or epic memory.
402
410
  - Do not spend significant time inferring app startup or Playwright setup unless visual confirmation is actually necessary for this PR.
@@ -414,6 +422,8 @@ export function buildGitHubPRPlanBreakdownSystemPrompt() {
414
422
  "Each step must be self-contained: a single developer can implement it in isolation by reading only that step description.",
415
423
  "Steps must be ordered by dependency — steps that others depend on come first.",
416
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.",
417
427
  "Aim for 3-8 steps. Do not produce trivial steps or steps that only say 'test' or 'verify'.",
418
428
  LANGUAGE_PRESERVATION_RULES,
419
429
  "Return only structured output matching the provided schema.",
@@ -436,9 +446,10 @@ ${JSON.stringify(input.memory?.epic || {}, null, 2)}
436
446
  Instructions:
437
447
  - Produce an ordered list of atomic implementation steps.
438
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.
439
450
  - Steps must be ordered by dependency (earlier steps must not depend on later ones).
440
451
  - Aim for 3-8 steps covering the full scope of the plan.
441
- - 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.`;
442
453
  }
443
454
 
444
455
  export function buildGitHubPRStepBreakdownSystemPrompt() {
@@ -447,6 +458,8 @@ export function buildGitHubPRStepBreakdownSystemPrompt() {
447
458
  "Each sub-step must be self-contained and implementable by reading only that sub-step description.",
448
459
  "Sub-steps must be ordered by dependency.",
449
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.",
450
463
  "Do not produce trivial sub-steps.",
451
464
  LANGUAGE_PRESERVATION_RULES,
452
465
  "Return only structured output matching the provided schema."
@@ -465,8 +478,9 @@ ${input.step}
465
478
  Instructions:
466
479
  - Produce 2-4 ordered atomic sub-steps that together complete this step.
467
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.
468
482
  - Sub-steps must be ordered by dependency.
469
- - 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.`;
470
484
  }
471
485
 
472
486
  export function buildGitHubPRImplementationStepSystemPrompt() {
@@ -478,8 +492,8 @@ export function buildGitHubPRImplementationStepSystemPrompt() {
478
492
  "Implement only the specific step described — do not implement other parts of the larger plan.",
479
493
  "Read the epic memory snapshot before making changes so you can reuse known repo-specific knowledge and avoid repeating past mistakes.",
480
494
  "Run only the commands needed to inspect, edit, and test this specific step.",
481
- "When editing code or markup, smart quotes and em/en dashes are not allowed; replace them if encountered.",
482
495
  LANGUAGE_PRESERVATION_RULES,
496
+ SMART_PUNCTUATION_RULE,
483
497
  "Do not run git add, git commit, or git push. The outer worker will handle all git operations.",
484
498
  "If you are blocked on this step, return result=stuck with a concise reason.",
485
499
  "Return only structured output matching the provided schema.",
@@ -512,7 +526,7 @@ ${formatPriorWorkContext(input)}
512
526
  Instructions:
513
527
  - Checkout the branch above in this repository.
514
528
  - Implement only the step described above. Do not implement other parts of the plan.
515
- - Smart quotes and em/en dashes are not allowed. Use: smart quotes -> ", em/en dashes -> -.
529
+ - Only these typographic characters are disallowed in code/markup and must be replaced: smart quotes -> ", em/en dashes -> -. This does not apply to diacritics or other natural-language text — keep those exactly as written.
516
530
  - Do not commit or push; leave modified files in the working tree.
517
531
  - Return summary as 1-2 sentences describing what changed in this step.
518
532
  - If successful, return result=implemented.
@@ -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;