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
package/src/claude/prompts.js
CHANGED
|
@@ -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).
|
|
9
|
-
//
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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;
|