sticky-note-cli 2.6.11 → 2.6.13
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/README.md +16 -3
- package/bin/cli.js +67 -1
- package/package.json +1 -1
- package/templates/CLAUDE.md +10 -3
- package/templates/copilot-instructions.md +11 -3
- package/templates/hooks/inject-context.js +3 -2
- package/templates/hooks/pre-tool-use.js +8 -3
package/README.md
CHANGED
|
@@ -42,7 +42,14 @@ their next session by relevance.
|
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
-
## What's New in V2.
|
|
45
|
+
## What's New in V2.6
|
|
46
|
+
|
|
47
|
+
- **Overlap detection**: Warns when you're working on files another teammate has open/stuck threads on. Three delivery channels: injected context, stderr banner, and preToolUse deny (Copilot CLI)
|
|
48
|
+
- **File claiming**: `npx sticky-note-cli claim src/auth.ts "refactoring auth flow"` — declare intent to work on files
|
|
49
|
+
- **Auto-close Copilot CLI threads**: Threads from Copilot CLI sessions auto-close after configurable inactivity (default 24h), since Copilot CLI has no session-end signal
|
|
50
|
+
- **PID-keyed session isolation**: Concurrent Copilot CLI sessions get independent overlap dedup via `COPILOT_LOADER_PID`
|
|
51
|
+
|
|
52
|
+
### What's New in V2.5
|
|
46
53
|
|
|
47
54
|
- **Smart injection (two-tier)**: Stuck threads injected eagerly at session start; all other threads injected lazily when you first touch a file they authored — via built-in git blame attribution
|
|
48
55
|
- **Thread resume (local)**: `npx sticky-note-cli resume-thread --query "auth fix" --user alice` — natural language thread discovery with text similarity + file attribution ranking
|
|
@@ -148,7 +155,7 @@ injects the top 3–5 (under token budget) as additional context.
|
|
|
148
155
|
|
|
149
156
|
| Signal | Weight | Description |
|
|
150
157
|
|--------|--------|-------------|
|
|
151
|
-
| File overlap | 3 | Thread files match your recent git changes |
|
|
158
|
+
| File overlap | 3 per file (max 5) | Thread files match your recent git changes |
|
|
152
159
|
| Branch match | 2 | Thread is on your current branch |
|
|
153
160
|
| Recency | 2 | Decays 0.2 per day from last activity |
|
|
154
161
|
| Stuck status | +2 | Boost for threads marked stuck |
|
|
@@ -321,6 +328,8 @@ npx sticky-note-cli resume --clear # Cancel active resume
|
|
|
321
328
|
npx sticky-note-cli resume-thread # Smart resume: --query, --user, --file (V2.5)
|
|
322
329
|
npx sticky-note-cli audit # Query merged audit trail (all users)
|
|
323
330
|
npx sticky-note-cli who # Show active and recent team members
|
|
331
|
+
npx sticky-note-cli overlap # Detect file overlaps with teammates (V2.6)
|
|
332
|
+
npx sticky-note-cli claim <files> # Declare intent to work on files (V2.6)
|
|
324
333
|
npx sticky-note-cli switch <branch> # Safe branch switch (auto-stashes data)
|
|
325
334
|
npx sticky-note-cli gc # Tombstone expired threads
|
|
326
335
|
npx sticky-note-cli reset # Wipe all threads (--force, --keep-audit)
|
|
@@ -349,16 +358,20 @@ Edit `.sticky-note/sticky-note-config.json`:
|
|
|
349
358
|
```json
|
|
350
359
|
{
|
|
351
360
|
"stale_days": 14,
|
|
361
|
+
"copilot_cli_auto_close_hours": 24,
|
|
362
|
+
"inject_token_budget": 1000,
|
|
352
363
|
"mcp_servers": [],
|
|
353
364
|
"skills": [],
|
|
354
365
|
"conventions": ["Use TypeScript strict mode", "Test before commit"],
|
|
355
|
-
"hook_version": "2.
|
|
366
|
+
"hook_version": "2.6.12"
|
|
356
367
|
}
|
|
357
368
|
```
|
|
358
369
|
|
|
359
370
|
| Key | Description | Default |
|
|
360
371
|
|----------------|------------------------------------------|---------|
|
|
361
372
|
| `stale_days` | Days before threads expire + gc cleanup | `14` |
|
|
373
|
+
| `copilot_cli_auto_close_hours` | Hours before idle Copilot CLI threads auto-close | `24` |
|
|
374
|
+
| `inject_token_budget` | Max tokens for per-prompt thread injection | `1000` |
|
|
362
375
|
| `mcp_servers` | Shared MCP server references | `[]` |
|
|
363
376
|
| `skills` | Team skill definitions | `[]` |
|
|
364
377
|
| `conventions` | Team coding conventions (injected) | `[]` |
|
package/bin/cli.js
CHANGED
|
@@ -526,12 +526,29 @@ async function cmdInit() {
|
|
|
526
526
|
gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
+
// Fix overly broad .claude/ ignore that blocks hook sharing
|
|
530
|
+
let gitignoreChanged = false;
|
|
531
|
+
if (gitignoreContent) {
|
|
532
|
+
const lines = gitignoreContent.split(/\r?\n/);
|
|
533
|
+
const filtered = lines.filter((line) => {
|
|
534
|
+
const trimmed = line.trim();
|
|
535
|
+
// Remove lines that ignore all of .claude/ (but keep targeted entries)
|
|
536
|
+
return trimmed !== ".claude/" && trimmed !== ".claude";
|
|
537
|
+
});
|
|
538
|
+
if (filtered.length !== lines.length) {
|
|
539
|
+
gitignoreContent = filtered.join("\n");
|
|
540
|
+
fs.writeFileSync(gitignorePath, gitignoreContent);
|
|
541
|
+
gitignoreChanged = true;
|
|
542
|
+
print(" [OK] .gitignore: removed broad .claude/ ignore (hooks will be shared via git)");
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
529
546
|
const missingEntries = ignoreAdditions.filter((e) => !gitignoreContent.includes(e.trim()));
|
|
530
547
|
if (missingEntries.length > 0) {
|
|
531
548
|
const block = "\n# Sticky Note\n" + missingEntries.join("\n") + "\n";
|
|
532
549
|
fs.appendFileSync(gitignorePath, block);
|
|
533
550
|
print(" [OK] .gitignore updated");
|
|
534
|
-
} else {
|
|
551
|
+
} else if (!gitignoreChanged) {
|
|
535
552
|
print(" ⏭️ .gitignore already configured");
|
|
536
553
|
}
|
|
537
554
|
|
|
@@ -623,6 +640,17 @@ async function cmdInit() {
|
|
|
623
640
|
print(" [OK] .git/hooks/post-rewrite (attribution survives rebase)");
|
|
624
641
|
}
|
|
625
642
|
|
|
643
|
+
// Force-add .claude/hooks/ and .claude/settings.json so git tracks them
|
|
644
|
+
// (needed if .claude/ was previously in .gitignore)
|
|
645
|
+
try {
|
|
646
|
+
execFileSync("git", ["add", "--force", ".claude/hooks/", ".claude/settings.json"], {
|
|
647
|
+
cwd: process.cwd(), stdio: ["pipe", "pipe", "pipe"],
|
|
648
|
+
});
|
|
649
|
+
print(" [OK] .claude/hooks/ and .claude/settings.json staged for git");
|
|
650
|
+
} catch (err) {
|
|
651
|
+
debugLog("git add --force .claude/ failed: " + (err.message || err));
|
|
652
|
+
}
|
|
653
|
+
|
|
626
654
|
// Done!
|
|
627
655
|
print("\n ✨ Sticky Note V2 initialized!\n");
|
|
628
656
|
print(" Next steps:");
|
|
@@ -699,6 +727,44 @@ function cmdUpdate() {
|
|
|
699
727
|
|
|
700
728
|
// Upgrade .gitattributes from merge=union to custom driver
|
|
701
729
|
const gitattrsPath = path.join(process.cwd(), ".gitattributes");
|
|
730
|
+
|
|
731
|
+
// Fix overly broad .claude/ gitignore that blocks hook sharing
|
|
732
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
733
|
+
if (fs.existsSync(gitignorePath)) {
|
|
734
|
+
let gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
735
|
+
const lines = gitignoreContent.split(/\r?\n/);
|
|
736
|
+
const filtered = lines.filter((line) => {
|
|
737
|
+
const trimmed = line.trim();
|
|
738
|
+
return trimmed !== ".claude/" && trimmed !== ".claude";
|
|
739
|
+
});
|
|
740
|
+
if (filtered.length !== lines.length) {
|
|
741
|
+
fs.writeFileSync(gitignorePath, filtered.join("\n"));
|
|
742
|
+
print(" [OK] .gitignore: removed broad .claude/ ignore (hooks will be shared via git)");
|
|
743
|
+
|
|
744
|
+
// Ensure targeted ignores are present
|
|
745
|
+
const ignoreAdditions = fs
|
|
746
|
+
.readFileSync(path.join(TEMPLATES_DIR, "gitignore-additions.txt"), "utf-8")
|
|
747
|
+
.trim()
|
|
748
|
+
.split(/\r?\n/)
|
|
749
|
+
.filter((l) => l.trim() && !l.startsWith("#"));
|
|
750
|
+
const updatedContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
751
|
+
const missing = ignoreAdditions.filter((e) => !updatedContent.includes(e.trim()));
|
|
752
|
+
if (missing.length > 0) {
|
|
753
|
+
fs.appendFileSync(gitignorePath, "\n# Sticky Note\n" + missing.join("\n") + "\n");
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Force-add previously ignored hook files so git starts tracking them
|
|
757
|
+
try {
|
|
758
|
+
execFileSync("git", ["add", "--force", ".claude/hooks/", ".claude/settings.json"], {
|
|
759
|
+
cwd: process.cwd(), stdio: ["pipe", "pipe", "pipe"],
|
|
760
|
+
});
|
|
761
|
+
print(" [OK] git add --force .claude/hooks/ .claude/settings.json");
|
|
762
|
+
} catch (err) {
|
|
763
|
+
debugLog("git add --force .claude/ failed: " + (err.message || err));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
702
768
|
if (fs.existsSync(gitattrsPath)) {
|
|
703
769
|
let gitattrsContent = fs.readFileSync(gitattrsPath, "utf-8");
|
|
704
770
|
const oldRule = ".sticky-note/sticky-note.json merge=union";
|
package/package.json
CHANGED
package/templates/CLAUDE.md
CHANGED
|
@@ -189,9 +189,15 @@ if set up by `npx sticky-note init`.
|
|
|
189
189
|
### ⚠️ Overlap warnings (CRITICAL — always surface these)
|
|
190
190
|
|
|
191
191
|
Sticky-note hooks detect when your files overlap with other teammates'
|
|
192
|
-
open or stuck threads.
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
open or stuck threads. Overlap warnings arrive through TWO channels:
|
|
193
|
+
|
|
194
|
+
1. **Injected context** — `[STICKY-NOTE] ⚠️ OVERLAP DETECTED` appears in
|
|
195
|
+
your hook output. This fires on every user prompt.
|
|
196
|
+
2. **Tool call deny** — Your first tool call may be denied with a reason
|
|
197
|
+
containing "CRITICAL INSTRUCTION" and the overlap details.
|
|
198
|
+
|
|
199
|
+
When you receive an overlap warning through EITHER channel, you **MUST**
|
|
200
|
+
start your response with this exact banner format before doing ANYTHING else:
|
|
195
201
|
|
|
196
202
|
```
|
|
197
203
|
⚠️ Overlap detected: [user] is working on [files] — [narrative summary]
|
|
@@ -202,6 +208,7 @@ Rules:
|
|
|
202
208
|
- This banner MUST be the **very first thing** in your response
|
|
203
209
|
- Do NOT bury it at the end or in a footnote
|
|
204
210
|
- Do NOT rephrase it as "Also noted..." or "By the way..."
|
|
211
|
+
- If a tool call was denied with overlap info, display the warning then proceed
|
|
205
212
|
- Then continue with the user's actual request normally
|
|
206
213
|
|
|
207
214
|
This pattern exists to prevent two teammates from wasting hours on the
|
|
@@ -167,9 +167,16 @@ if set up by `npx sticky-note init`.
|
|
|
167
167
|
### ⚠️ Overlap warnings (CRITICAL — always surface these)
|
|
168
168
|
|
|
169
169
|
Sticky-note hooks detect when your files overlap with other teammates'
|
|
170
|
-
open or stuck threads.
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
open or stuck threads. Overlap warnings arrive through TWO channels:
|
|
171
|
+
|
|
172
|
+
1. **Injected context** — `[STICKY-NOTE] ⚠️ OVERLAP DETECTED` appears in
|
|
173
|
+
your `additionalContext`. This fires on every user prompt.
|
|
174
|
+
2. **Tool call deny** — Your first tool call may be denied with a
|
|
175
|
+
`permissionDecisionReason` containing "CRITICAL INSTRUCTION" and the
|
|
176
|
+
overlap details. After the deny, the tool is auto-retried.
|
|
177
|
+
|
|
178
|
+
When you receive an overlap warning through EITHER channel, you **MUST**
|
|
179
|
+
start your response with this exact banner format before doing ANYTHING else:
|
|
173
180
|
|
|
174
181
|
```
|
|
175
182
|
⚠️ Overlap detected: [user] is working on [files] — [narrative summary]
|
|
@@ -187,6 +194,7 @@ Rules:
|
|
|
187
194
|
- Show it BEFORE your thinking indicators (◐, ●, etc.)
|
|
188
195
|
- Do NOT bury it at the end or in a footnote
|
|
189
196
|
- Do NOT rephrase it as "Also noted..." or "By the way..."
|
|
197
|
+
- If a tool call was denied with overlap info, display the warning then proceed
|
|
190
198
|
- Then continue with the user's actual request normally
|
|
191
199
|
|
|
192
200
|
This pattern exists to prevent two teammates from wasting hours on the
|
|
@@ -150,8 +150,9 @@ function detectAndFormatOverlaps(threads, currentUser) {
|
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
const lines = [
|
|
153
|
-
"## [STICKY-NOTE] ⚠️ OVERLAP DETECTED
|
|
154
|
-
"**
|
|
153
|
+
"## [STICKY-NOTE] ⚠️ OVERLAP DETECTED\n",
|
|
154
|
+
"**CRITICAL INSTRUCTION: Display the overlap warning below as the FIRST line of your response.",
|
|
155
|
+
"Do NOT skip this. Do NOT rephrase it. Do NOT bury it.**\n",
|
|
155
156
|
"Someone else is working on files you're touching:\n",
|
|
156
157
|
];
|
|
157
158
|
for (const { thread, overlap } of warnings) {
|
|
@@ -195,14 +195,19 @@ function _checkOverlapsAndDeny() {
|
|
|
195
195
|
|
|
196
196
|
if (warnings.length === 0) return null;
|
|
197
197
|
|
|
198
|
-
const lines = [
|
|
198
|
+
const lines = [
|
|
199
|
+
"CRITICAL INSTRUCTION: Before doing anything else, display the following overlap warning to the user.",
|
|
200
|
+
"This is NOT optional. Show it as the FIRST line of your response.\n",
|
|
201
|
+
];
|
|
199
202
|
for (const { thread, overlap } of warnings) {
|
|
200
203
|
const user = thread.user || thread.author || "unknown";
|
|
201
204
|
const status = thread.status === "stuck" ? "STUCK" : "OPEN";
|
|
202
205
|
const snip = (thread.narrative || thread.last_note || "").substring(0, 80);
|
|
203
|
-
|
|
206
|
+
const threadId = (thread.id || "").substring(0, 8);
|
|
207
|
+
lines.push(`⚠️ Overlap detected: ${user} is working on ${overlap.join(", ")}${snip ? " — " + snip : ""}`);
|
|
208
|
+
lines.push(` Status: ${status} | Resume: npx sticky-note resume ${threadId}`);
|
|
204
209
|
}
|
|
205
|
-
lines.push("
|
|
210
|
+
lines.push("\nAfter displaying the warning, continue with the user's request. This tool call will be auto-retried.");
|
|
206
211
|
|
|
207
212
|
markOverlapWarned();
|
|
208
213
|
|