sticky-note-cli 2.6.1 → 2.6.3

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": "sticky-note-cli",
3
- "version": "2.6.1",
3
+ "version": "2.6.3",
4
4
  "description": "Human-to-human handoff for AI coding assistants. Git-backed shared memory for Claude Code and Copilot CLI.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -120,6 +120,69 @@ function extractKeywords(prompt) {
120
120
  return keywords;
121
121
  }
122
122
 
123
+ // ── Overlap detection (for Copilot CLI where session-start output is ignored) ──
124
+
125
+ function detectAndFormatOverlaps(threads, currentUser) {
126
+ const modifiedFiles = getRecentlyModifiedFiles();
127
+ if (modifiedFiles.size === 0) return "";
128
+
129
+ const activeThreads = (threads || []).filter(
130
+ (t) => t.status === "open" || t.status === "stuck"
131
+ );
132
+
133
+ const warnings = [];
134
+ for (const thread of activeThreads) {
135
+ const threadUser = thread.user || thread.author || "";
136
+ if (threadUser === currentUser) continue;
137
+ const threadFiles = (thread.files_touched || []).map(normalizeSep);
138
+ const overlap = threadFiles.filter((f) => modifiedFiles.has(f));
139
+ if (overlap.length === 0) continue;
140
+ warnings.push({ thread, overlap });
141
+ }
142
+
143
+ if (warnings.length === 0) return "";
144
+
145
+ warnings.sort((a, b) => {
146
+ if (a.thread.status !== b.thread.status) {
147
+ return a.thread.status === "stuck" ? -1 : 1;
148
+ }
149
+ return b.overlap.length - a.overlap.length;
150
+ });
151
+
152
+ const lines = [
153
+ "## [STICKY-NOTE] ⚠️ OVERLAP DETECTED\n",
154
+ "Someone else is working on files you're touching:\n",
155
+ ];
156
+ for (const { thread, overlap } of warnings) {
157
+ const user = thread.user || thread.author || "unknown";
158
+ const status = thread.status === "stuck" ? "[STUCK]" : "[OPEN]";
159
+ const narrative = thread.narrative || thread.last_note || "";
160
+ const narrativeSnip = narrative.length > 100
161
+ ? narrative.substring(0, 100) + "…"
162
+ : narrative;
163
+ const branch = thread.branch ? ` on \`${thread.branch}\`` : "";
164
+ const threadId = (thread.id || "").substring(0, 8);
165
+
166
+ lines.push(`- ${status} **${user}**${branch}: ${overlap.join(", ")}`);
167
+ if (narrativeSnip) lines.push(` _${narrativeSnip}_`);
168
+ const failed = thread.failed_approaches || [];
169
+ if (failed.length > 0) {
170
+ lines.push(` ⚠️ ${failed.length} failed approach(es):`);
171
+ for (const fa of failed.slice(0, 2)) {
172
+ lines.push(` - ${(fa.description || "").substring(0, 80)}`);
173
+ }
174
+ }
175
+ if (threadId) {
176
+ lines.push(` → Resume: \`npx sticky-note resume ${threadId}\``);
177
+ }
178
+ }
179
+
180
+ lines.push(
181
+ "\n**Consider coordinating with these teammates before starting work.**\n"
182
+ );
183
+ return lines.join("\n");
184
+ }
185
+
123
186
  // ── Scoring ───────────────────────────────────────────────
124
187
 
125
188
  function scoreThread(thread, recentlyModified, currentBranch, currentUser, promptKeywords) {
@@ -442,6 +505,13 @@ function main() {
442
505
  const scoringBlock = debugLines.join("\n");
443
506
 
444
507
  if (scored.length === 0) {
508
+ // Even with no scored threads, check for overlaps
509
+ const overlapWarning = detectAndFormatOverlaps(threads, currentUser);
510
+ if (overlapWarning) {
511
+ _auditInject("no_scored_threads", 0);
512
+ _emit(overlapWarning);
513
+ return;
514
+ }
445
515
  _auditInject("no_scored_threads", 0);
446
516
  _emit("");
447
517
  return;
@@ -492,6 +562,12 @@ function main() {
492
562
  let output = outputLines.join("\n").trim();
493
563
  output += scoringBlock;
494
564
 
565
+ // Prepend overlap warning if other users' threads touch the same files
566
+ const overlapWarning = detectAndFormatOverlaps(threads, currentUser);
567
+ if (overlapWarning) {
568
+ output = overlapWarning + "\n" + output;
569
+ }
570
+
495
571
  _auditInject(
496
572
  "injected",
497
573
  scored.length,
@@ -484,9 +484,13 @@ function main() {
484
484
  memory.threads || [], getUser(), memory
485
485
  );
486
486
 
487
- // V2.5: Mark eagerly-injected stuck threads so PreToolUse won't re-inject
488
- for (const threadId of threadResult.threadIds) {
489
- markThreadInjected(threadId, sessionId);
487
+ // V2.5: Mark eagerly-injected stuck threads so PreToolUse won't re-inject.
488
+ // Skip for Copilot CLI — its sessionStart output is not surfaced to the AI,
489
+ // so marking threads as injected would prevent inject-context from delivering them.
490
+ if (!_isCopilotCli()) {
491
+ for (const threadId of threadResult.threadIds) {
492
+ markThreadInjected(threadId, sessionId);
493
+ }
490
494
  }
491
495
 
492
496
  appendAuditLine({