claude-teammate 0.1.113 → 0.1.115

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.113",
3
+ "version": "0.1.115",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -68,6 +68,10 @@ function markClaudeUsageLimit() {
68
68
  function isClaudeUsageLimited() {
69
69
  return Date.now() < claudeUsageLimitUntil;
70
70
  }
71
+
72
+ function isSubtask(issueType) {
73
+ return ["subtask", "sub-task"].includes(issueType.toLowerCase());
74
+ }
71
75
  const REPO_REQUEST_COMMENT = "Please provide repo url";
72
76
  const EPIC_MEMORY_MAX_CHARS = 3_500;
73
77
  const ISSUE_SETTLE_TIME_MS = 2 * 60 * 1000;
@@ -642,6 +646,18 @@ async function processJiraIssue({ issue, jira, forgeRegistry, botUser, config, p
642
646
 
643
647
  const issueMemoryRecord = await loadIssueMemory(projectRoot, config.JIRA_BASE_URL, detail);
644
648
 
649
+ if (!detail.epicId && !detail.epicKey) {
650
+ if (isSubtask(detail.issueType) && detail.parentKey) {
651
+ await logger.info("Subtask has no direct epic, checking parent for epic", { issue: detail.key, parent: detail.parentKey });
652
+ const parentDetail = await jira.fetchIssueDetails(detail.parentKey);
653
+ if (parentDetail.epicId || parentDetail.epicKey) {
654
+ detail.epicId = parentDetail.epicId;
655
+ detail.epicKey = parentDetail.epicKey;
656
+ detail.epicUrl = parentDetail.epicUrl;
657
+ }
658
+ }
659
+ }
660
+
645
661
  if (!detail.epicId && !detail.epicKey) {
646
662
  await logger.info("Issue has no epic, notifying user", { issue: detail.key });
647
663
  await ensureJiraComment(detail, jira, botUser, "An epic is missing from this ticket, please add one and tell me when you are done.");
@@ -661,6 +677,11 @@ async function processJiraIssue({ issue, jira, forgeRegistry, botUser, config, p
661
677
  );
662
678
  };
663
679
 
680
+ const TERMINAL_WORKFLOW_STATES = new Set(["completed", "failed", "skipped_no_epic"]);
681
+ const wasReopened =
682
+ TERMINAL_WORKFLOW_STATES.has(issueMemory.workflow_state) &&
683
+ (isToDoStatus(detail.status) || isInProgressStatus(detail.status));
684
+
664
685
  try {
665
686
  issueMemory.last_error = "";
666
687
 
@@ -674,38 +695,59 @@ async function processJiraIssue({ issue, jira, forgeRegistry, botUser, config, p
674
695
 
675
696
  if (transition.transitioned) {
676
697
  detail.status = "In Progress";
677
- const taskAlreadyProducedResult = issueMemory.no_code_result === "completed" || issueMemory.no_code_result === "failed";
678
- if (taskAlreadyProducedResult) {
679
- // Jira transition failed previously; just retry without re-running the task.
680
- issueMemory.workflow_state = issueMemory.no_code_result === "completed" ? "completed" : "failed";
681
- } else {
682
- // Fresh pickup or re-run: clear previous-run fields so task executes from scratch.
698
+ if (wasReopened) {
699
+ // Issue was re-opened from a terminal state; clear all task run state and restart from scratch.
683
700
  issueMemory.workflow_state = "in_progress";
684
701
  issueMemory.code_change_verdict = null;
685
702
  issueMemory.no_code_result = null;
686
703
  issueMemory.no_code_summary = null;
687
704
  issueMemory.progress_comment_id = null;
688
- await saveProgress("Task picked up. Setting up workspace...");
705
+ issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
706
+ await ensureJiraComment(detail, jira, botUser, "Issue re-opened. Clearing previous run state and restarting from scratch.");
707
+ await saveProgress("Task restarted from scratch. Setting up workspace...");
708
+ } else {
709
+ const taskAlreadyProducedResult = issueMemory.no_code_result === "completed" || issueMemory.no_code_result === "failed";
710
+ if (taskAlreadyProducedResult) {
711
+ // Jira transition failed previously; just retry without re-running the task.
712
+ issueMemory.workflow_state = issueMemory.no_code_result === "completed" ? "completed" : "failed";
713
+ } else {
714
+ // Fresh pickup or re-run: clear previous-run fields so task executes from scratch.
715
+ issueMemory.workflow_state = "in_progress";
716
+ issueMemory.code_change_verdict = null;
717
+ issueMemory.no_code_result = null;
718
+ issueMemory.no_code_summary = null;
719
+ issueMemory.progress_comment_id = null;
720
+ await saveProgress("Task picked up. Setting up workspace...");
721
+ }
722
+ issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
689
723
  }
690
- issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
691
724
  }
692
725
  }
693
726
 
694
- const TERMINAL_WORKFLOW_STATES = new Set(["completed", "failed", "skipped_no_epic"]);
695
727
  if (isInProgressStatus(detail.status) && TERMINAL_WORKFLOW_STATES.has(issueMemory.workflow_state)) {
696
- // If the task already produced a successful result, the Jira transition to "In Review" likely
697
- // failed (no transition available). Don't re-run the task; just let the no_code path retry
698
- // the transition on this poll.
699
- const taskAlreadyProducedResult = issueMemory.no_code_result === "completed" || issueMemory.no_code_result === "failed";
700
- if (!taskAlreadyProducedResult) {
728
+ if (wasReopened) {
729
+ // Issue was re-opened from terminal state directly to In Progress; resume from prior context.
701
730
  issueMemory.workflow_state = "in_progress";
702
- // Clear previous-run fields so clarification and code-change decision re-run with the new user comment.
703
- issueMemory.code_change_verdict = null;
704
731
  issueMemory.no_code_result = null;
705
- issueMemory.no_code_summary = null;
732
+ issueMemory.code_change_verdict = null;
706
733
  issueMemory.progress_comment_id = null;
707
734
  issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
708
- await saveProgress("Task restarted. Setting up workspace...");
735
+ await ensureJiraComment(detail, jira, botUser, "Issue re-opened. Resuming previous session.");
736
+ } else {
737
+ // If the task already produced a successful result, the Jira transition to "In Review" likely
738
+ // failed (no transition available). Don't re-run the task; just let the no_code path retry
739
+ // the transition on this poll.
740
+ const taskAlreadyProducedResult = issueMemory.no_code_result === "completed" || issueMemory.no_code_result === "failed";
741
+ if (!taskAlreadyProducedResult) {
742
+ issueMemory.workflow_state = "in_progress";
743
+ // Clear previous-run fields so clarification and code-change decision re-run with the new user comment.
744
+ issueMemory.code_change_verdict = null;
745
+ issueMemory.no_code_result = null;
746
+ issueMemory.no_code_summary = null;
747
+ issueMemory.progress_comment_id = null;
748
+ issueMemory = await saveIssueMemory(issueMemoryRecord.filePath, detail, issueMemory);
749
+ await saveProgress("Task restarted. Setting up workspace...");
750
+ }
709
751
  }
710
752
  }
711
753
 
@@ -1020,7 +1062,7 @@ async function processJiraIssue({ issue, jira, forgeRegistry, botUser, config, p
1020
1062
  ? `Completed: ${issueMemory.no_code_summary}`
1021
1063
  : `Failed: ${issueMemory.no_code_summary}`;
1022
1064
  await ensureJiraComment(detail, jira, botUser, resultComment);
1023
- if (issueMemory.no_code_result === "completed") {
1065
+ if (issueMemory.no_code_result === "completed" && !wasReopened) {
1024
1066
  let transition = await jira.transitionIssueToStatus(detail.key, "In Review");
1025
1067
  if (!transition.transitioned) {
1026
1068
  transition = await jira.transitionIssueToStatus(detail.key, "Done");
package/src/jira.js CHANGED
@@ -204,6 +204,7 @@ function mapIssue(issue, baseUrl) {
204
204
  epicId: epic?.id ?? null,
205
205
  epicKey: epic?.key ?? null,
206
206
  epicUrl: epic?.url ?? null,
207
+ parentKey: issue.fields?.parent?.key ?? null,
207
208
  created: issue.fields?.created ?? null,
208
209
  updated: issue.fields?.updated ?? null
209
210
  };