claude-teammate 0.1.307 → 0.1.308
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/prompts.js +16 -0
- package/src/jira.js +19 -1
- package/src/memory.js +1 -0
- package/src/worker/jira-helpers.js +41 -1
- package/src/worker/jira-issue-workflow.js +13 -1
package/package.json
CHANGED
package/src/claude/prompts.js
CHANGED
|
@@ -179,6 +179,7 @@ export function buildJiraClarificationUserPrompt(input) {
|
|
|
179
179
|
referenceRepos: input.referenceRepos,
|
|
180
180
|
repoPaths: input.repoPaths
|
|
181
181
|
});
|
|
182
|
+
const attachmentsSection = formatIssueAttachments(input.issue.attachments);
|
|
182
183
|
|
|
183
184
|
return `Clarify this Jira issue.
|
|
184
185
|
|
|
@@ -189,6 +190,7 @@ Status: ${input.issue.status}
|
|
|
189
190
|
Description:
|
|
190
191
|
${input.issue.descriptionText || "(none)"}
|
|
191
192
|
|
|
193
|
+
${attachmentsSection}
|
|
192
194
|
Memory snapshot:
|
|
193
195
|
${JSON.stringify(input.memory, null, 2)}
|
|
194
196
|
|
|
@@ -202,6 +204,20 @@ ${reopenContext}
|
|
|
202
204
|
Decide whether the requirements are clear enough to plan implementation and whether the task needs code changes. Read code only if it helps.`;
|
|
203
205
|
}
|
|
204
206
|
|
|
207
|
+
// List attachments already present on the Jira issue so the agent reads
|
|
208
|
+
// material the user attached instead of asking for it. These are downloadable
|
|
209
|
+
// with jira_download_attachments by filename.
|
|
210
|
+
function formatIssueAttachments(attachments) {
|
|
211
|
+
const list = (Array.isArray(attachments) ? attachments : []).filter((item) => item && item.filename);
|
|
212
|
+
if (list.length === 0) {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
const lines = list.map((item) => `- ${item.filename}${item.mimeType ? ` (${item.mimeType})` : ""}`).join("\n");
|
|
216
|
+
return `Issue attachments (download with jira_download_attachments before planning; read every one relevant to the task):
|
|
217
|
+
${lines}
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
220
|
+
|
|
205
221
|
function formatClarificationRepoScope({ targetRepo, referenceRepos, repoPaths }) {
|
|
206
222
|
const repoPathLines = (Array.isArray(repoPaths) ? repoPaths : []).filter(Boolean);
|
|
207
223
|
|
package/src/jira.js
CHANGED
|
@@ -306,10 +306,28 @@ function mapIssueDetail(issue, baseUrl) {
|
|
|
306
306
|
? issue.fields.comment.comments.map(mapComment).sort(compareCommentsByCreated)
|
|
307
307
|
: [];
|
|
308
308
|
|
|
309
|
+
// The detail fetch uses fields=*all, so existing attachments are already in
|
|
310
|
+
// the payload — surface them so the intake/planning step can download and
|
|
311
|
+
// read documents that users attach to the issue (a frequent "tài liệu đã
|
|
312
|
+
// đính kèm" complaint where the bot ignored material already on the task).
|
|
313
|
+
const attachments = Array.isArray(issue.fields?.attachment) ? issue.fields.attachment.map(mapAttachment) : [];
|
|
314
|
+
|
|
309
315
|
return {
|
|
310
316
|
...mapIssue(issue, baseUrl),
|
|
311
317
|
descriptionText: adfToText(issue.fields?.description).trim(),
|
|
312
|
-
comments
|
|
318
|
+
comments,
|
|
319
|
+
attachments
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function mapAttachment(attachment) {
|
|
324
|
+
return {
|
|
325
|
+
id: attachment?.id ?? null,
|
|
326
|
+
filename: attachment?.filename ?? "",
|
|
327
|
+
mimeType: attachment?.mimeType ?? "",
|
|
328
|
+
size: attachment?.size ?? null,
|
|
329
|
+
url: attachment?.content ?? null,
|
|
330
|
+
created: attachment?.created ?? null
|
|
313
331
|
};
|
|
314
332
|
}
|
|
315
333
|
|
package/src/memory.js
CHANGED
|
@@ -263,6 +263,7 @@ function normalizeIssueMemoryData(data) {
|
|
|
263
263
|
normalized.last_jira_memory_comment_id = String(normalized.last_jira_memory_comment_id ?? "").trim();
|
|
264
264
|
normalized.code_change_verdict = String(normalized.code_change_verdict ?? "").trim();
|
|
265
265
|
normalized.code_change_input_id = String(normalized.code_change_input_id ?? "").trim();
|
|
266
|
+
normalized.no_code_locked = Boolean(normalized.no_code_locked);
|
|
266
267
|
delete normalized.github_issue_url;
|
|
267
268
|
delete normalized.github_issue_number;
|
|
268
269
|
delete normalized.latest_processed_comment_id;
|
|
@@ -185,11 +185,51 @@ export function shouldDisplayIssueInState(issue) {
|
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// Matches an explicit "no code" declaration in any of: "no code", "no_code",
|
|
189
|
+
// "no-code", "nocode" (case-insensitive). Users add this to tell the bot a task
|
|
190
|
+
// is documentation/estimate only and must NOT create a repo issue or push code.
|
|
191
|
+
const NO_CODE_MARKER = /\bno[\s_-]?code\b/iu;
|
|
192
|
+
|
|
193
|
+
// Deterministic no-code detection from explicit user signals, so the verdict no
|
|
194
|
+
// longer depends on the LLM guessing (which flipped to "code" and made users
|
|
195
|
+
// repeat "task no code" on every comment). Checks an explicit Jira label, the
|
|
196
|
+
// description, and the latest human comment.
|
|
197
|
+
export function detectNoCodeMarker(detail, botUser, jira) {
|
|
198
|
+
if (!detail) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const labels = Array.isArray(detail.labels) ? detail.labels : [];
|
|
203
|
+
if (labels.some((label) => /^no[\s_-]?code$/iu.test(String(label || "").trim()))) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (NO_CODE_MARKER.test(String(detail.descriptionText || ""))) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const latestHumanComment = [...(Array.isArray(detail.comments) ? detail.comments : [])]
|
|
212
|
+
.filter((comment) => !String(comment?.bodyText || "").startsWith(PROGRESS_COMMENT_PREFIX))
|
|
213
|
+
.sort(compareCommentsNewestFirst)
|
|
214
|
+
.find((comment) => (botUser ? !jira.isBotAuthor(comment.author, botUser) : true));
|
|
215
|
+
|
|
216
|
+
return Boolean(latestHumanComment && NO_CODE_MARKER.test(String(latestHumanComment.bodyText || "")));
|
|
217
|
+
}
|
|
218
|
+
|
|
188
219
|
export function shouldReuseNoCodeDecision({ issueMemory, latestInputId }) {
|
|
220
|
+
const noGithubIssues = !Array.isArray(issueMemory?.github_issues) || issueMemory.github_issues.length === 0;
|
|
221
|
+
|
|
222
|
+
// Once an explicit no-code marker has locked the issue, keep the no-code
|
|
223
|
+
// verdict sticky across subsequent comments (new input ids) so a follow-up
|
|
224
|
+
// that does not repeat "no code" is not re-evaluated back into a code task.
|
|
225
|
+
if (issueMemory?.no_code_locked && noGithubIssues) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
189
229
|
return (
|
|
190
230
|
issueMemory?.code_change_verdict === "no_code" &&
|
|
191
231
|
issueMemory?.code_change_input_id === latestInputId &&
|
|
192
|
-
|
|
232
|
+
noGithubIssues
|
|
193
233
|
);
|
|
194
234
|
}
|
|
195
235
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
cleanupJiraProgressComments,
|
|
23
23
|
deriveRawTaskRepos,
|
|
24
24
|
deriveTaskRepos,
|
|
25
|
+
detectNoCodeMarker,
|
|
25
26
|
ensureJiraComment,
|
|
26
27
|
getLatestBlockedTaskRestartComment,
|
|
27
28
|
getLatestClarificationInput,
|
|
@@ -632,7 +633,18 @@ export async function processJiraIssue({
|
|
|
632
633
|
issueMemory,
|
|
633
634
|
latestInputId: latestInput.id
|
|
634
635
|
});
|
|
635
|
-
if (
|
|
636
|
+
if (detectNoCodeMarker(detail, botUser, jira)) {
|
|
637
|
+
// Explicit user signal ("task no code" / no-code label) overrides the
|
|
638
|
+
// LLM verdict and locks the issue so later comments stay no-code. This
|
|
639
|
+
// stops the bot from re-deciding "code" and creating a repo issue, which
|
|
640
|
+
// forced users to repeat "task no code" on every comment.
|
|
641
|
+
decisionResult = { verdict: "no_code" };
|
|
642
|
+
issueMemory.no_code_locked = true;
|
|
643
|
+
await logger.info("Explicit no-code marker detected; forcing no_code verdict", {
|
|
644
|
+
issue: detail.key,
|
|
645
|
+
input: latestInput.id
|
|
646
|
+
});
|
|
647
|
+
} else if (shouldReusePreservedNoCodeDecision) {
|
|
636
648
|
decisionResult = { verdict: "no_code" };
|
|
637
649
|
await logger.info("Reusing preserved no-code decision", {
|
|
638
650
|
issue: detail.key,
|