claude-teammate 0.1.63 → 0.1.65
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 +1 -1
- package/src/claude.js +217 -0
- package/src/commands/worker.js +79 -0
- package/src/dashboard/ui.html +11 -3
package/package.json
CHANGED
package/src/claude.js
CHANGED
|
@@ -192,6 +192,45 @@ const EPIC_MEMORY_CLEANUP_SCHEMA = {
|
|
|
192
192
|
required: ["facts", "guardrails"]
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
+
const CODE_CHANGE_DECISION_SCHEMA = {
|
|
196
|
+
type: "object",
|
|
197
|
+
additionalProperties: false,
|
|
198
|
+
properties: {
|
|
199
|
+
verdict: {
|
|
200
|
+
type: "string",
|
|
201
|
+
enum: ["needs_code", "no_code"]
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
required: ["verdict"]
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const NO_CODE_TASK_SCHEMA = {
|
|
208
|
+
type: "object",
|
|
209
|
+
additionalProperties: false,
|
|
210
|
+
properties: {
|
|
211
|
+
result: {
|
|
212
|
+
type: "string",
|
|
213
|
+
enum: ["completed", "failed"]
|
|
214
|
+
},
|
|
215
|
+
summary: {
|
|
216
|
+
type: "string"
|
|
217
|
+
},
|
|
218
|
+
epic_facts: {
|
|
219
|
+
type: "array",
|
|
220
|
+
items: {
|
|
221
|
+
type: "string"
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
epic_guardrails: {
|
|
225
|
+
type: "array",
|
|
226
|
+
items: {
|
|
227
|
+
type: "string"
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
required: ["result", "summary", "epic_facts", "epic_guardrails"]
|
|
232
|
+
};
|
|
233
|
+
|
|
195
234
|
export async function runClaudeClarification(input) {
|
|
196
235
|
if (!Array.isArray(input.repoPaths) || input.repoPaths.length === 0) {
|
|
197
236
|
throw new Error("Claude clarification requires at least one local repository path.");
|
|
@@ -451,6 +490,84 @@ export async function runClaudeEpicMemorySummarize(input) {
|
|
|
451
490
|
return validateEpicMemoryCleanupResult(parseClaudeOutput(stdout));
|
|
452
491
|
}
|
|
453
492
|
|
|
493
|
+
export async function runClaudeCodeChangeDecision(input) {
|
|
494
|
+
if (!Array.isArray(input.repoPaths) || input.repoPaths.length === 0) {
|
|
495
|
+
throw new Error("Claude code change decision requires at least one local repository path.");
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const args = [
|
|
499
|
+
"--print",
|
|
500
|
+
"--model",
|
|
501
|
+
input.model || DEFAULT_MODEL,
|
|
502
|
+
"--permission-mode",
|
|
503
|
+
resolveClaudePermissionMode(input.permissionMode),
|
|
504
|
+
"--effort",
|
|
505
|
+
"low",
|
|
506
|
+
"--output-format",
|
|
507
|
+
"json",
|
|
508
|
+
"--json-schema",
|
|
509
|
+
JSON.stringify(CODE_CHANGE_DECISION_SCHEMA),
|
|
510
|
+
"--append-system-prompt",
|
|
511
|
+
buildCodeChangeDecisionSystemPrompt(),
|
|
512
|
+
buildCodeChangeDecisionUserPrompt(input)
|
|
513
|
+
];
|
|
514
|
+
|
|
515
|
+
let stdout;
|
|
516
|
+
const workingDirectory = getClaudeWorkspaceRoot(input.repoPaths);
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
({ stdout } = await runClaudeCommand("claude", args, {
|
|
520
|
+
cwd: workingDirectory,
|
|
521
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
522
|
+
timeout: input.timeoutMs || DEFAULT_TIMEOUT_MS
|
|
523
|
+
}));
|
|
524
|
+
} catch (error) {
|
|
525
|
+
throw new Error(formatClaudeInvocationError(error, input.timeoutMs || DEFAULT_TIMEOUT_MS));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return validateCodeChangeDecisionResult(parseClaudeOutput(stdout));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export async function runClaudeNoCodeTask(input) {
|
|
532
|
+
if (!Array.isArray(input.repoPaths) || input.repoPaths.length === 0) {
|
|
533
|
+
throw new Error("Claude no-code task requires at least one local repository path.");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const repoPaths = [...new Set(input.repoPaths.map((repoPath) => path.resolve(repoPath)))];
|
|
537
|
+
|
|
538
|
+
const args = [
|
|
539
|
+
"--print",
|
|
540
|
+
"--model",
|
|
541
|
+
input.model || DEFAULT_MODEL,
|
|
542
|
+
"--permission-mode",
|
|
543
|
+
resolveClaudePermissionMode(input.permissionMode),
|
|
544
|
+
"--effort",
|
|
545
|
+
"medium",
|
|
546
|
+
"--output-format",
|
|
547
|
+
"json",
|
|
548
|
+
"--json-schema",
|
|
549
|
+
JSON.stringify(NO_CODE_TASK_SCHEMA),
|
|
550
|
+
"--append-system-prompt",
|
|
551
|
+
buildNoCodeTaskSystemPrompt(),
|
|
552
|
+
buildNoCodeTaskUserPrompt(input)
|
|
553
|
+
];
|
|
554
|
+
|
|
555
|
+
let stdout;
|
|
556
|
+
|
|
557
|
+
try {
|
|
558
|
+
({ stdout } = await runClaudeCommand("claude", args, {
|
|
559
|
+
cwd: getClaudeWorkspaceRoot(repoPaths),
|
|
560
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
561
|
+
timeout: input.timeoutMs || 30 * 60 * 1000,
|
|
562
|
+
onSpawn: input.onSpawn
|
|
563
|
+
}));
|
|
564
|
+
} catch (error) {
|
|
565
|
+
throw new Error(formatClaudeInvocationError(error, input.timeoutMs || 30 * 60 * 1000));
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return validateNoCodeTaskResult(parseClaudeOutput(stdout));
|
|
569
|
+
}
|
|
570
|
+
|
|
454
571
|
export async function runClaudeImplementation(input) {
|
|
455
572
|
if (!input.repoPath) {
|
|
456
573
|
throw new Error("Claude implementation requires a local repository path.");
|
|
@@ -605,6 +722,14 @@ export function parseClaudeOutput(output) {
|
|
|
605
722
|
return direct;
|
|
606
723
|
}
|
|
607
724
|
|
|
725
|
+
if ("verdict" in direct) {
|
|
726
|
+
return direct;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if ("result" in direct && "summary" in direct) {
|
|
730
|
+
return direct;
|
|
731
|
+
}
|
|
732
|
+
|
|
608
733
|
if ("result" in direct && typeof direct.result === "string") {
|
|
609
734
|
return JSON.parse(direct.result);
|
|
610
735
|
}
|
|
@@ -753,6 +878,35 @@ function validateEpicMemoryCleanupResult(result) {
|
|
|
753
878
|
};
|
|
754
879
|
}
|
|
755
880
|
|
|
881
|
+
function validateCodeChangeDecisionResult(result) {
|
|
882
|
+
if (!result || typeof result !== "object" || !["needs_code", "no_code"].includes(result.verdict)) {
|
|
883
|
+
throw new Error("Claude CLI returned an invalid code change decision payload.");
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return { verdict: result.verdict };
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function validateNoCodeTaskResult(result) {
|
|
890
|
+
if (!result || typeof result !== "object") {
|
|
891
|
+
throw new Error("Claude CLI returned an invalid no-code task payload.");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (!["completed", "failed"].includes(result.result)) {
|
|
895
|
+
throw new Error("Claude CLI returned an unsupported no-code task result.");
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
result: result.result,
|
|
900
|
+
summary: String(result.summary ?? "").trim(),
|
|
901
|
+
epic_facts: Array.isArray(result.epic_facts)
|
|
902
|
+
? result.epic_facts.map((item) => String(item).trim()).filter(Boolean)
|
|
903
|
+
: [],
|
|
904
|
+
epic_guardrails: Array.isArray(result.epic_guardrails)
|
|
905
|
+
? result.epic_guardrails.map((item) => String(item).trim()).filter(Boolean)
|
|
906
|
+
: []
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
|
|
756
910
|
function formatClaudeInvocationError(error, timeoutMs) {
|
|
757
911
|
const stderr = error instanceof Error && "stderr" in error ? String(error.stderr || "") : "";
|
|
758
912
|
const output = error instanceof Error && "stdout" in error ? String(error.stdout || "") : "";
|
|
@@ -895,6 +1049,69 @@ function terminateChildProcess(child) {
|
|
|
895
1049
|
});
|
|
896
1050
|
}
|
|
897
1051
|
|
|
1052
|
+
function buildCodeChangeDecisionSystemPrompt() {
|
|
1053
|
+
return [
|
|
1054
|
+
"You are deciding whether a Jira task requires code changes.",
|
|
1055
|
+
"Read the issue description and memory snapshot carefully.",
|
|
1056
|
+
"Output verdict=needs_code if the task involves modifying, adding, or removing code in any repository.",
|
|
1057
|
+
"Output verdict=no_code if the task can be completed without any code changes (e.g. documentation lookup, research, answering questions, configuration through UI, or purely informational tasks).",
|
|
1058
|
+
"The decision must be deterministic based on the information provided.",
|
|
1059
|
+
"Return only structured output matching the provided schema."
|
|
1060
|
+
].join(" ");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function buildCodeChangeDecisionUserPrompt(input) {
|
|
1064
|
+
return `Decide whether this Jira task requires code changes.
|
|
1065
|
+
|
|
1066
|
+
Issue key: ${input.issue.key}
|
|
1067
|
+
Issue URL: ${input.issue.url}
|
|
1068
|
+
Summary: ${input.issue.summary}
|
|
1069
|
+
Status: ${input.issue.status}
|
|
1070
|
+
Description:
|
|
1071
|
+
${input.issue.descriptionText || "(none)"}
|
|
1072
|
+
|
|
1073
|
+
Memory snapshot:
|
|
1074
|
+
${JSON.stringify(input.memory, null, 2)}
|
|
1075
|
+
|
|
1076
|
+
Output verdict=needs_code if code changes are required, or verdict=no_code if the task can be completed without any code changes.`;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function buildNoCodeTaskSystemPrompt() {
|
|
1080
|
+
return [
|
|
1081
|
+
"You are completing a Jira task that does not require code changes.",
|
|
1082
|
+
"Read the task description and memory snapshot carefully.",
|
|
1083
|
+
"You may inspect the provided repository using read-only tools if that helps complete the task.",
|
|
1084
|
+
"Do not edit files, do not run mutating commands, and do not make code changes.",
|
|
1085
|
+
"Read the epic memory snapshot before starting so you can apply known repo-specific knowledge.",
|
|
1086
|
+
"When writing repo-specific epic memory, include a short repo label in the text itself using the shortest unambiguous name or abbreviation you can derive from the repo URL or local path, for example `landing:` or `claude-teammate:`.",
|
|
1087
|
+
"Do not add a repo label for epic-wide product, business, or workflow knowledge that is not tied to one codebase.",
|
|
1088
|
+
"Do not restate Jira URLs, repo URLs, repo paths, or other epic facts that are already stored separately from the memory bullets.",
|
|
1089
|
+
"Do not store obvious facts, generic advice, or one-off ticket details in epic memory.",
|
|
1090
|
+
"Return result=completed with a summary of what was accomplished, or result=failed with a summary of why it could not be completed.",
|
|
1091
|
+
"Return epic_facts and epic_guardrails only for reusable non-obvious repo or epic knowledge discovered during this task.",
|
|
1092
|
+
"Return only structured output matching the provided schema."
|
|
1093
|
+
].join(" ");
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
function buildNoCodeTaskUserPrompt(input) {
|
|
1097
|
+
return `Complete this Jira task.
|
|
1098
|
+
|
|
1099
|
+
Issue key: ${input.issue.key}
|
|
1100
|
+
Issue URL: ${input.issue.url}
|
|
1101
|
+
Summary: ${input.issue.summary}
|
|
1102
|
+
Status: ${input.issue.status}
|
|
1103
|
+
Description:
|
|
1104
|
+
${input.issue.descriptionText || "(none)"}
|
|
1105
|
+
|
|
1106
|
+
Memory snapshot:
|
|
1107
|
+
${JSON.stringify(input.memory, null, 2)}
|
|
1108
|
+
|
|
1109
|
+
Repository paths:
|
|
1110
|
+
${input.repoPaths.join("\n")}
|
|
1111
|
+
|
|
1112
|
+
Complete the task and return result=completed with a summary, or result=failed with an explanation.`;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
898
1115
|
function buildJiraClarificationSystemPrompt() {
|
|
899
1116
|
return [
|
|
900
1117
|
"You are clarifying a Jira ticket for an implementation planning workflow.",
|
package/src/commands/worker.js
CHANGED
|
@@ -5,9 +5,11 @@ import process from "node:process";
|
|
|
5
5
|
import {
|
|
6
6
|
cleanupClaudeProcesses,
|
|
7
7
|
runClaudeClarification,
|
|
8
|
+
runClaudeCodeChangeDecision,
|
|
8
9
|
runClaudeEpicMemoryCleanup,
|
|
9
10
|
runClaudeEpicMemorySummarize,
|
|
10
11
|
runClaudeImplementation,
|
|
12
|
+
runClaudeNoCodeTask,
|
|
11
13
|
runClaudePrReview,
|
|
12
14
|
runClaudePullRequestCommentReview,
|
|
13
15
|
runClaudeGitHubIssueReview
|
|
@@ -695,6 +697,83 @@ async function processJiraIssue({ issue, jira, github, botUser, config, projectR
|
|
|
695
697
|
issue: detail.key,
|
|
696
698
|
repos: epicMemory.repos.length
|
|
697
699
|
});
|
|
700
|
+
|
|
701
|
+
if (!issueMemory.code_change_verdict) {
|
|
702
|
+
let decisionResult;
|
|
703
|
+
try {
|
|
704
|
+
decisionResult = await runClaudeCodeChangeDecision({
|
|
705
|
+
issue: detail,
|
|
706
|
+
memory: { epic: buildEpicMemoryPromptSnapshot(epicMemory) },
|
|
707
|
+
repoPaths: epicMemory.repos.map((repo) => repo.local_path).filter(Boolean),
|
|
708
|
+
permissionMode: config.CLAUDE_PERMISSION_MODE,
|
|
709
|
+
timeoutMs: parseOptionalInt(config.CLAUDE_TIMEOUT_MS)
|
|
710
|
+
});
|
|
711
|
+
} catch (error) {
|
|
712
|
+
issueMemory.last_error = error instanceof Error ? error.message : String(error);
|
|
713
|
+
issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
714
|
+
await logger.error("Claude code change decision failed", { issue: detail.key, error });
|
|
715
|
+
return buildIssueState(detail, epicMemory, issueMemory, null, issueMemory.last_error);
|
|
716
|
+
}
|
|
717
|
+
issueMemory.code_change_verdict = decisionResult.verdict;
|
|
718
|
+
issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
719
|
+
await logger.info("Code change decision made", {
|
|
720
|
+
issue: detail.key,
|
|
721
|
+
verdict: decisionResult.verdict
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (issueMemory.code_change_verdict === "no_code") {
|
|
726
|
+
if (!issueMemory.no_code_result) {
|
|
727
|
+
let taskResult = null;
|
|
728
|
+
try {
|
|
729
|
+
taskResult = await runClaudeNoCodeTask({
|
|
730
|
+
issue: detail,
|
|
731
|
+
memory: { epic: buildEpicMemoryPromptSnapshot(epicMemory) },
|
|
732
|
+
repoPaths: epicMemory.repos.map((repo) => repo.local_path).filter(Boolean),
|
|
733
|
+
permissionMode: config.CLAUDE_PERMISSION_MODE,
|
|
734
|
+
timeoutMs: parseOptionalInt(config.CLAUDE_TIMEOUT_MS)
|
|
735
|
+
});
|
|
736
|
+
} catch (error) {
|
|
737
|
+
issueMemory.no_code_result = "failed";
|
|
738
|
+
issueMemory.no_code_summary = error instanceof Error ? error.message : String(error);
|
|
739
|
+
issueMemory.workflow_state = "failed";
|
|
740
|
+
issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
741
|
+
await logger.error("Claude no-code task failed", { issue: detail.key, error });
|
|
742
|
+
}
|
|
743
|
+
if (taskResult) {
|
|
744
|
+
epicMemory = await saveMaintainedEpicMemory({
|
|
745
|
+
filePath: epicMemoryRecord.filePath,
|
|
746
|
+
issue: detail,
|
|
747
|
+
epicMemory,
|
|
748
|
+
projectRoot,
|
|
749
|
+
permissionMode: config.CLAUDE_PERMISSION_MODE,
|
|
750
|
+
timeoutMs: parseOptionalInt(config.CLAUDE_TIMEOUT_MS),
|
|
751
|
+
reason: "Updated epic memory from no-code task execution.",
|
|
752
|
+
candidateUpdates: {
|
|
753
|
+
epic_facts: taskResult.epic_facts || [],
|
|
754
|
+
epic_guardrails: taskResult.epic_guardrails || []
|
|
755
|
+
},
|
|
756
|
+
logger
|
|
757
|
+
});
|
|
758
|
+
issueMemory.no_code_result = taskResult.result;
|
|
759
|
+
issueMemory.no_code_summary = taskResult.summary;
|
|
760
|
+
issueMemory.workflow_state = taskResult.result === "completed" ? "completed" : "failed";
|
|
761
|
+
issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
762
|
+
await logger.info("No-code task finished", {
|
|
763
|
+
issue: detail.key,
|
|
764
|
+
result: taskResult.result
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const resultComment = issueMemory.no_code_result === "completed"
|
|
770
|
+
? `Completed: ${issueMemory.no_code_summary}`
|
|
771
|
+
: `Failed: ${issueMemory.no_code_summary}`;
|
|
772
|
+
await ensureJiraComment(detail, jira, botUser, resultComment);
|
|
773
|
+
issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
|
|
774
|
+
return buildIssueState(detail, epicMemory, issueMemory, "no_code");
|
|
775
|
+
}
|
|
776
|
+
|
|
698
777
|
issueMemory = await continueGitHubIssueCreation({
|
|
699
778
|
detail,
|
|
700
779
|
jira,
|
package/src/dashboard/ui.html
CHANGED
|
@@ -503,9 +503,15 @@ tr:last-child td { border-bottom:none; }
|
|
|
503
503
|
tr:hover td { background:var(--bg); }
|
|
504
504
|
|
|
505
505
|
/* ── Pipeline ── */
|
|
506
|
+
.pipeline-scroll {
|
|
507
|
+
overflow-x: auto;
|
|
508
|
+
}
|
|
509
|
+
|
|
506
510
|
.pipeline {
|
|
507
511
|
display:grid;
|
|
508
|
-
grid-
|
|
512
|
+
grid-auto-flow: column;
|
|
513
|
+
grid-auto-columns: minmax(200px, 1fr);
|
|
514
|
+
min-width: max-content;
|
|
509
515
|
gap:14px;
|
|
510
516
|
margin-bottom:4px;
|
|
511
517
|
}
|
|
@@ -1370,8 +1376,10 @@ tr.clickable:hover td { background:var(--sky-bg) !important; }
|
|
|
1370
1376
|
</div>
|
|
1371
1377
|
<button class="btn" onclick="refreshAll()">↻ Refresh</button>
|
|
1372
1378
|
</div>
|
|
1373
|
-
<div class="pipeline
|
|
1374
|
-
<div class="
|
|
1379
|
+
<div class="pipeline-scroll">
|
|
1380
|
+
<div class="pipeline" id="pipeline-view">
|
|
1381
|
+
<div class="empty"><div class="empty-icon">▸</div><div class="empty-text">Loading…</div></div>
|
|
1382
|
+
</div>
|
|
1375
1383
|
</div>
|
|
1376
1384
|
</div>
|
|
1377
1385
|
|