claude-teammate 0.1.145 → 0.1.147

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.145",
3
+ "version": "0.1.147",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -191,7 +191,8 @@ export async function runWorkerCommand({ projectRoot }) {
191
191
  lastReviewSuccessAt: null,
192
192
  lastReviewError: null,
193
193
  reviewPrCount: 0,
194
- reviewPrs: []
194
+ reviewPrs: [],
195
+ pollerBusy: { jira: false, github: false, draftPr: false, reviewPr: false }
195
196
  };
196
197
 
197
198
  const updatePrSubtaskState = async (key, updates) => {
@@ -250,6 +251,8 @@ export async function runWorkerCommand({ projectRoot }) {
250
251
  return;
251
252
  }
252
253
  jiraFlag.value = true;
254
+ state.pollerBusy.jira = true;
255
+ await writeState(runtimePaths.stateFile, state);
253
256
  await runPoll("", "Jira", state, async () => {
254
257
  const result = await jira.fetchAssignedIssues();
255
258
  const processedIssues = [];
@@ -272,6 +275,8 @@ export async function runWorkerCommand({ projectRoot }) {
272
275
  logInfo: { issues: result.issues.length, assigned: result.total }
273
276
  };
274
277
  }, { stateFile: runtimePaths.stateFile, logger, flagRef: jiraFlag });
278
+ state.pollerBusy.jira = false;
279
+ await writeState(runtimePaths.stateFile, state);
275
280
  };
276
281
 
277
282
  const pollGitHub = async () => {
@@ -279,6 +284,8 @@ export async function runWorkerCommand({ projectRoot }) {
279
284
  return;
280
285
  }
281
286
  githubFlag.value = true;
287
+ state.pollerBusy.github = true;
288
+ await writeState(runtimePaths.stateFile, state);
282
289
  await runPoll("GitHub", "GitHub", state, async () => {
283
290
  const repos = filterReposForActiveForge(await listKnownRepos(projectRoot), forgeRegistry);
284
291
  const reposByUrl = new Map(repos.map((repo) => [repo.url, repo]));
@@ -396,6 +403,8 @@ export async function runWorkerCommand({ projectRoot }) {
396
403
  logInfo: { tracked: trackedIssueCount }
397
404
  };
398
405
  }, { stateFile: runtimePaths.stateFile, logger, flagRef: githubFlag });
406
+ state.pollerBusy.github = false;
407
+ await writeState(runtimePaths.stateFile, state);
399
408
  };
400
409
 
401
410
  const pollDraftPrs = async () => {
@@ -403,6 +412,8 @@ export async function runWorkerCommand({ projectRoot }) {
403
412
  return;
404
413
  }
405
414
  prFlag.value = true;
415
+ state.pollerBusy.draftPr = true;
416
+ await writeState(runtimePaths.stateFile, state);
406
417
  const prJira = createJiraClient(values);
407
418
  await runPoll("Pr", "Pull request", state, async () => {
408
419
  const repos = filterReposForActiveForge(await listKnownRepos(projectRoot), forgeRegistry);
@@ -529,6 +540,8 @@ export async function runWorkerCommand({ projectRoot }) {
529
540
  logInfo: { tracked: trackedPrCount }
530
541
  };
531
542
  }, { stateFile: runtimePaths.stateFile, logger, flagRef: prFlag });
543
+ state.pollerBusy.draftPr = false;
544
+ await writeState(runtimePaths.stateFile, state);
532
545
  };
533
546
 
534
547
  const pollReviewPrs = async () => {
@@ -536,6 +549,8 @@ export async function runWorkerCommand({ projectRoot }) {
536
549
  return;
537
550
  }
538
551
  reviewFlag.value = true;
552
+ state.pollerBusy.reviewPr = true;
553
+ await writeState(runtimePaths.stateFile, state);
539
554
  await runPoll("Review", "PR review", state, async () => {
540
555
  const processedPrs = [];
541
556
  const repos = filterReposForActiveForge(await listKnownRepos(projectRoot), forgeRegistry);
@@ -594,6 +609,8 @@ export async function runWorkerCommand({ projectRoot }) {
594
609
  logInfo: { reviewed: reviewedPrCount }
595
610
  };
596
611
  }, { stateFile: runtimePaths.stateFile, logger, flagRef: reviewFlag });
612
+ state.pollerBusy.reviewPr = false;
613
+ await writeState(runtimePaths.stateFile, state);
597
614
  };
598
615
 
599
616
  await pollJira();
@@ -2447,15 +2464,21 @@ export function getLatestClarificationInput(detail, botUser, jira) {
2447
2464
  }
2448
2465
 
2449
2466
  export function hasNewHumanReplyWhileWaiting(detail, botUser, jira) {
2450
- const latestNonProgressComment = [...detail.comments]
2467
+ const nonProgressComments = [...detail.comments]
2451
2468
  .filter((comment) => !String(comment?.bodyText || "").startsWith(PROGRESS_COMMENT_PREFIX))
2452
- .sort(compareCommentsNewestFirst)[0];
2469
+ .sort(compareCommentsNewestFirst);
2453
2470
 
2454
- if (!latestNonProgressComment) {
2471
+ const latestHumanComment = nonProgressComments.find((comment) => !jira.isBotAuthor(comment.author, botUser));
2472
+ if (!latestHumanComment) {
2455
2473
  return false;
2456
2474
  }
2457
2475
 
2458
- return !jira.isBotAuthor(latestNonProgressComment.author, botUser);
2476
+ const latestBotComment = nonProgressComments.find((comment) => jira.isBotAuthor(comment.author, botUser));
2477
+ if (!latestBotComment) {
2478
+ return true;
2479
+ }
2480
+
2481
+ return getCommentTimestamp(latestHumanComment) > getCommentTimestamp(latestBotComment);
2459
2482
  }
2460
2483
 
2461
2484
  export function shouldReuseNoCodeDecision({ issueMemory, latestInputId }) {
@@ -2467,11 +2490,15 @@ export function shouldReuseNoCodeDecision({ issueMemory, latestInputId }) {
2467
2490
  }
2468
2491
 
2469
2492
  function compareCommentsNewestFirst(left, right) {
2470
- const leftTime = Date.parse(left?.updated || left?.created || "") || 0;
2471
- const rightTime = Date.parse(right?.updated || right?.created || "") || 0;
2493
+ const leftTime = getCommentTimestamp(left);
2494
+ const rightTime = getCommentTimestamp(right);
2472
2495
  return rightTime - leftTime;
2473
2496
  }
2474
2497
 
2498
+ function getCommentTimestamp(comment) {
2499
+ return Date.parse(comment?.updated || comment?.created || "") || 0;
2500
+ }
2501
+
2475
2502
  export function getLatestBlockedTaskRestartComment(detail, issueMemory, botUser, jira) {
2476
2503
  if (!isBlockedOrFailedIssueState(detail, issueMemory)) {
2477
2504
  return null;
@@ -624,8 +624,8 @@ tr:hover td { background:var(--bg); }
624
624
  margin-top:5px;
625
625
  }
626
626
 
627
- /* ── Implementation Tracker ── */
628
- .impl-tracker {
627
+ /* ── Heartbeat Queues ── */
628
+ .heartbeat-queues {
629
629
  background:var(--bg-card);
630
630
  border:1px solid var(--border);
631
631
  border-radius:var(--r-lg);
@@ -634,104 +634,90 @@ tr:hover td { background:var(--bg); }
634
634
  overflow:hidden;
635
635
  }
636
636
 
637
- .impl-header {
637
+ .hbq-header {
638
638
  display:flex;
639
639
  align-items:center;
640
640
  gap:10px;
641
- padding:14px 18px;
641
+ padding:12px 18px;
642
642
  border-bottom:1px solid var(--border);
643
- background: linear-gradient(135deg, var(--teal-bg), var(--sky-bg));
643
+ background:linear-gradient(135deg, var(--teal-bg), var(--sky-bg));
644
644
  }
645
645
 
646
- .impl-icon {
647
- width:32px; height:32px;
648
- background:var(--teal);
649
- border-radius:8px;
650
- display:flex;
651
- align-items:center;
652
- justify-content:center;
653
- color:white;
646
+ .hbq-title {
654
647
  font-size:.85rem;
655
- flex-shrink:0;
656
- }
657
-
658
- .impl-title {
659
- font-size:.9rem;
660
- font-weight:600;
648
+ font-weight:700;
661
649
  color:var(--ink-1);
662
650
  }
663
651
 
664
- .impl-sub {
665
- font-size:.72rem;
666
- color:var(--ink-3);
667
- margin-top:1px;
668
- }
669
-
670
- .impl-body { padding:16px 18px; }
652
+ .hbq-body { padding:14px 18px; display:flex; flex-direction:column; gap:10px; }
671
653
 
672
- .impl-phase-row {
654
+ .hbq-row {
673
655
  display:flex;
674
656
  align-items:center;
675
657
  gap:10px;
676
- margin-bottom:12px;
677
658
  }
678
659
 
679
- .impl-phase-label {
680
- font-size:.72rem;
681
- font-weight:600;
660
+ .hbq-label {
661
+ font-size:.68rem;
662
+ font-weight:700;
682
663
  text-transform:uppercase;
683
664
  letter-spacing:.5px;
684
- color:var(--ink-2);
685
- width:140px;
665
+ color:var(--ink-3);
666
+ width:80px;
686
667
  flex-shrink:0;
687
668
  }
688
669
 
689
- .impl-progress-bar {
670
+ .hbq-cards {
671
+ display:flex;
672
+ flex-direction:row;
673
+ flex-wrap:nowrap;
674
+ gap:6px;
675
+ overflow-x:auto;
690
676
  flex:1;
691
- height:8px;
692
- background:var(--border);
693
- border-radius:4px;
694
- overflow:hidden;
677
+ scrollbar-width:none;
678
+ padding-bottom:2px;
695
679
  }
696
680
 
697
- .impl-progress-fill {
698
- height:100%;
699
- border-radius:4px;
700
- width:0;
701
- transition:width 1.5s cubic-bezier(.4,0,.2,1) .2s;
702
- background:linear-gradient(90deg,var(--teal),var(--sky));
703
- position:relative;
704
- overflow:hidden;
681
+ .hbq-cards::-webkit-scrollbar { display:none; }
682
+
683
+ .hbq-card {
684
+ font-family:var(--f-mono);
685
+ font-size:.72rem;
686
+ font-weight:600;
687
+ padding:4px 9px;
688
+ border-radius:5px;
689
+ border:1px solid var(--border);
690
+ background:var(--bg);
691
+ color:var(--ink-2);
692
+ white-space:nowrap;
693
+ cursor:pointer;
694
+ flex-shrink:0;
695
+ transition:border-color .15s, color .15s;
705
696
  }
706
697
 
707
- .impl-progress-fill::after {
708
- content:"";
709
- position:absolute;
710
- top:0; left:0; bottom:0;
711
- width:60px;
712
- background:linear-gradient(90deg,transparent,rgba(255,255,255,.4),transparent);
713
- animation:shimmer 2s infinite;
698
+ .hbq-card:hover { border-color:var(--accent); color:var(--accent); }
699
+
700
+ .hbq-card.active {
701
+ border-color:var(--teal);
702
+ color:var(--teal);
703
+ background:var(--teal-bg);
704
+ animation:hbq-pulse 1.4s ease-in-out infinite;
714
705
  }
715
706
 
716
- @keyframes shimmer {
717
- from { transform:translateX(-60px); }
718
- to { transform:translateX(calc(100% + 60px)); }
707
+ @keyframes hbq-pulse {
708
+ 0%, 100% { opacity:1; }
709
+ 50% { opacity:.45; }
719
710
  }
720
711
 
721
- .impl-phase-status {
722
- font-family:var(--f-mono);
723
- font-size:.68rem;
724
- font-weight:600;
725
- width:80px;
726
- text-align:right;
727
- flex-shrink:0;
712
+ @media (prefers-reduced-motion: reduce) {
713
+ .hbq-card.active { animation:none; }
728
714
  }
729
715
 
730
- .impl-dur {
731
- font-family:var(--f-mono);
732
- font-size:.72rem;
716
+ .hbq-idle {
717
+ font-size:.68rem;
733
718
  color:var(--ink-3);
734
- text-align:right;
719
+ font-style:italic;
720
+ padding:4px 2px;
735
721
  }
736
722
 
737
723
  /* ── Log ── */
@@ -1571,8 +1557,8 @@ tr.clickable:hover td { background:var(--sky-bg) !important; }
1571
1557
  </div>
1572
1558
  </div>
1573
1559
 
1574
- <!-- Active implementation tracker -->
1575
- <div id="impl-tracker-wrap"></div>
1560
+ <!-- Heartbeat queue view -->
1561
+ <div id="heartbeat-queues-wrap"></div>
1576
1562
 
1577
1563
  <!-- Worker info -->
1578
1564
  <div class="card" id="worker-card">
@@ -1729,7 +1715,6 @@ let cachedStatus = null;
1729
1715
  let cachedMemFiles = [];
1730
1716
  let activeBoardFilter = null;
1731
1717
  let currentMemPath = null;
1732
- let lastImplTrackerKey = null;
1733
1718
  let currentDrawerKey = null;
1734
1719
  let currentLogFilter = "all";
1735
1720
 
@@ -1798,7 +1783,7 @@ async function loadStatus() {
1798
1783
  renderStats(data);
1799
1784
  renderWorkerDetails(data);
1800
1785
  renderIssues(data);
1801
- renderImplTracker(data);
1786
+ renderHeartbeatQueues(data);
1802
1787
  renderBoardFilters(data);
1803
1788
  renderPipeline(data);
1804
1789
  renderAwaiting(data);
@@ -1958,86 +1943,52 @@ function renderIssues(data) {
1958
1943
  }).join("");
1959
1944
  }
1960
1945
 
1961
- function renderImplTracker(data) {
1962
- const wrap = document.getElementById("impl-tracker-wrap");
1946
+ function renderHeartbeatQueues(data) {
1947
+ const wrap = document.getElementById("heartbeat-queues-wrap");
1963
1948
  const s = data.state || {};
1964
- const impl = s.prImplementation;
1965
- const review = s.prCommentReview;
1966
-
1967
- if (!impl && !review) {
1968
- if (lastImplTrackerKey !== null) { wrap.innerHTML = ""; lastImplTrackerKey = null; }
1969
- return;
1970
- }
1971
-
1972
- const trackerKey = [
1973
- impl?.branch || "", impl?.status || "", impl?.startedAt || "", impl?.finishedAt || "",
1974
- review?.branch || "", review?.status || "", review?.startedAt || "", review?.finishedAt || ""
1975
- ].join("|");
1976
- if (trackerKey === lastImplTrackerKey) return;
1977
- lastImplTrackerKey = trackerKey;
1978
-
1979
- const phases = [];
1980
-
1981
- if (review) phases.push({
1982
- label: "Comment Review",
1983
- status: review.status,
1984
- started: review.startedAt,
1985
- finished: review.finishedAt,
1986
- pr: review.pullRequestUrl,
1987
- branch: review.branchName
1988
- });
1989
-
1990
- if (impl) phases.push({
1991
- label: "Implementation",
1992
- status: impl.status,
1993
- started: impl.startedAt,
1994
- finished: impl.finishedAt,
1995
- pr: impl.pullRequestUrl,
1996
- branch: impl.branchName
1997
- });
1998
-
1999
- const mainPhase = impl || review;
2000
- const dur = mainPhase.finishedAt && mainPhase.startedAt
2001
- ? formatDuration(new Date(mainPhase.finishedAt) - new Date(mainPhase.startedAt))
2002
- : mainPhase.startedAt ? "In progress · " + formatDuration(Date.now() - new Date(mainPhase.startedAt)) : "—";
1949
+ const busy = s.pollerBusy || {};
1950
+
1951
+ const rows = [
1952
+ { label: "Jira", items: s.issues || [], busyKey: "jira", type: "jira" },
1953
+ { label: "GitHub", items: s.githubIssues || [], busyKey: "github", type: "jira" },
1954
+ { label: "Draft PRs", items: s.draftPrs || [], busyKey: "draftPr", type: "pr" },
1955
+ { label: "Review PRs", items: s.reviewPrs || [], busyKey: "reviewPr", type: "pr" }
1956
+ ];
1957
+
1958
+ const renderCard = (item, isActive, type, idx) => {
1959
+ const activeClass = isActive && idx === 0 ? " active" : "";
1960
+ if (type === "jira") {
1961
+ const key = esc(item.key || "");
1962
+ const onclick = item.key
1963
+ ? `onclick='openDrawer(${JSON.stringify(item)}, cachedStatus)'`
1964
+ : "";
1965
+ return `<div class="hbq-card${activeClass}" ${onclick}>${key}</div>`;
1966
+ }
1967
+ const label = item.pullRequestNumber ? esc("#" + item.pullRequestNumber) : "PR";
1968
+ const href = item.pullRequestUrl ? esc(item.pullRequestUrl) : "#";
1969
+ return `<a class="hbq-card${activeClass}" href="${href}" target="_blank" style="text-decoration:none">${label}</a>`;
1970
+ };
2003
1971
 
2004
1972
  wrap.innerHTML = `
2005
- <div class="impl-tracker">
2006
- <div class="impl-header">
2007
- <div class="impl-icon">⚡</div>
2008
- <div>
2009
- <div class="impl-title">${esc(mainPhase.branch || "Implementation Tracker")}</div>
2010
- <div class="impl-sub">
2011
- ${mainPhase.pr ? `<a href="${esc(mainPhase.pr)}" target="_blank">View PR</a> · ` : ""}
2012
- Duration: ${dur}
2013
- </div>
2014
- </div>
2015
- <span class="tag ${mainPhase.status}" style="margin-left:auto">${esc(mainPhase.status)}</span>
1973
+ <div class="heartbeat-queues">
1974
+ <div class="hbq-header">
1975
+ <span class="hbq-title">Heartbeat Queues</span>
2016
1976
  </div>
2017
- <div class="impl-body">
2018
- ${phases.map((ph, i) => {
2019
- const pct = ph.status === "finished" ? 100 : ph.status === "running" ? 60 : 0;
2020
- const color = ph.status === "finished" ? "var(--green)" : "var(--teal)";
2021
- const animDelay = i * 0.3;
1977
+ <div class="hbq-body">
1978
+ ${rows.map(row => {
1979
+ const isActive = !!busy[row.busyKey];
1980
+ const cards = row.items.length > 0
1981
+ ? row.items.map((item, idx) => renderCard(item, isActive, row.type, idx)).join("")
1982
+ : `<span class="hbq-idle">idle</span>`;
2022
1983
  return `
2023
- <div class="impl-phase-row">
2024
- <div class="impl-phase-label">${esc(ph.label)}</div>
2025
- <div class="impl-progress-bar">
2026
- <div class="impl-progress-fill" data-pct="${pct}" style="background:${color};transition-delay:${animDelay}s"></div>
2027
- </div>
2028
- <div class="impl-phase-status" style="color:${pct===100?'var(--green)':'var(--teal)'}">${esc(ph.status)}</div>
2029
- </div>`;
1984
+ <div class="hbq-row">
1985
+ <div class="hbq-label">${esc(row.label)}</div>
1986
+ <div class="hbq-cards">${cards}</div>
1987
+ </div>`;
2030
1988
  }).join("")}
2031
1989
  </div>
2032
1990
  </div>
2033
1991
  `;
2034
-
2035
- // animate bars
2036
- setTimeout(() => {
2037
- wrap.querySelectorAll(".impl-progress-fill[data-pct]").forEach(el => {
2038
- el.style.width = el.dataset.pct + "%";
2039
- });
2040
- }, 100);
2041
1992
  }
2042
1993
 
2043
1994
  /* ── Pipeline ── */