sticky-note-cli 2.6.9 → 2.6.10
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
|
@@ -54,6 +54,7 @@ const {
|
|
|
54
54
|
getResumeThreadId, findThreadById, getSessionId,
|
|
55
55
|
appendAuditLine, detectTool, getConfigPath,
|
|
56
56
|
isThreadInjected, markThreadInjected, normalizeSep,
|
|
57
|
+
writeOverlapSignal, isOverlapDismissed,
|
|
57
58
|
} = utils;
|
|
58
59
|
|
|
59
60
|
// ── Git helpers ───────────────────────────────────────────
|
|
@@ -528,6 +529,10 @@ function main() {
|
|
|
528
529
|
// Even with no scored threads, check for overlaps
|
|
529
530
|
const overlapWarning = detectAndFormatOverlaps(threads, currentUser);
|
|
530
531
|
if (overlapWarning) {
|
|
532
|
+
// Write signal file for preToolUse deny (user-visible in Copilot CLI)
|
|
533
|
+
if (_isCopilotCli() && !isOverlapDismissed()) {
|
|
534
|
+
writeOverlapSignal(overlapWarning);
|
|
535
|
+
}
|
|
531
536
|
_auditInject("no_scored_threads", 0);
|
|
532
537
|
_emit(overlapWarning);
|
|
533
538
|
return;
|
|
@@ -585,6 +590,10 @@ function main() {
|
|
|
585
590
|
// Prepend overlap warning if other users' threads touch the same files
|
|
586
591
|
const overlapWarning = detectAndFormatOverlaps(threads, currentUser);
|
|
587
592
|
if (overlapWarning) {
|
|
593
|
+
// Write signal file for preToolUse deny (user-visible in Copilot CLI)
|
|
594
|
+
if (_isCopilotCli() && !isOverlapDismissed()) {
|
|
595
|
+
writeOverlapSignal(overlapWarning);
|
|
596
|
+
}
|
|
588
597
|
output = overlapWarning + "\n" + output;
|
|
589
598
|
}
|
|
590
599
|
|
|
@@ -61,11 +61,8 @@ const {
|
|
|
61
61
|
appendAuditLine,
|
|
62
62
|
isThreadInjected,
|
|
63
63
|
markThreadInjected,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
getMemoryPath,
|
|
67
|
-
loadJson,
|
|
68
|
-
normalizeSep,
|
|
64
|
+
readAndDeleteOverlapSignal,
|
|
65
|
+
markOverlapDismissed,
|
|
69
66
|
} = utils;
|
|
70
67
|
|
|
71
68
|
// ── File path extraction from tool input ──────────────────
|
|
@@ -136,107 +133,49 @@ function formatThreadForInjection(threadData, file) {
|
|
|
136
133
|
return lines.join("\n");
|
|
137
134
|
}
|
|
138
135
|
|
|
139
|
-
// ── Overlap
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
// as the reason. The AI reports the denial to the user, then retries.
|
|
144
|
-
// Subsequent tool calls proceed normally.
|
|
145
|
-
|
|
146
|
-
const { execSync } = require("child_process");
|
|
147
|
-
|
|
148
|
-
function _getRecentlyModifiedFiles() {
|
|
149
|
-
const files = new Set();
|
|
150
|
-
for (const target of ["HEAD~5", "HEAD~1"]) {
|
|
151
|
-
try {
|
|
152
|
-
const result = execSync(`git diff --name-only ${target}`, {
|
|
153
|
-
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
154
|
-
});
|
|
155
|
-
for (const f of result.trim().split(/\r?\n/)) {
|
|
156
|
-
if (f.trim()) files.add(f.trim());
|
|
157
|
-
}
|
|
158
|
-
break;
|
|
159
|
-
} catch (_) { /* target doesn't exist */ }
|
|
160
|
-
}
|
|
161
|
-
try {
|
|
162
|
-
const result = execSync("git diff --name-only", {
|
|
163
|
-
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
164
|
-
});
|
|
165
|
-
for (const f of result.trim().split(/\r?\n/)) {
|
|
166
|
-
if (f.trim()) files.add(f.trim());
|
|
167
|
-
}
|
|
168
|
-
} catch (_) { /* ignore */ }
|
|
169
|
-
return files;
|
|
170
|
-
}
|
|
136
|
+
// ── Overlap deny via signal file (Copilot CLI only) ───────
|
|
137
|
+
// inject-context.js writes .overlap-pending with the warning text.
|
|
138
|
+
// We read+delete it atomically and return a deny. No shared state,
|
|
139
|
+
// no TTL, no session-ID issues.
|
|
171
140
|
|
|
172
|
-
function
|
|
141
|
+
function _checkOverlapSignal() {
|
|
173
142
|
if (!_isCopilotCli()) return null;
|
|
174
143
|
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const currentUser = getUser();
|
|
189
|
-
const modifiedFiles = _getRecentlyModifiedFiles();
|
|
190
|
-
if (modifiedFiles.size === 0) return null;
|
|
191
|
-
|
|
192
|
-
const activeThreads = threads.filter(
|
|
193
|
-
(t) => t.status === "open" || t.status === "stuck"
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
const warnings = [];
|
|
197
|
-
for (const thread of activeThreads) {
|
|
198
|
-
const threadUser = thread.user || thread.author || "";
|
|
199
|
-
if (threadUser === currentUser) continue;
|
|
200
|
-
const threadFiles = (thread.files_touched || []).map(normalizeSep);
|
|
201
|
-
const overlap = threadFiles.filter((f) => modifiedFiles.has(f));
|
|
202
|
-
if (overlap.length === 0) continue;
|
|
203
|
-
warnings.push({ thread, overlap });
|
|
144
|
+
const signalContent = readAndDeleteOverlapSignal();
|
|
145
|
+
if (!signalContent) return null;
|
|
146
|
+
|
|
147
|
+
// Extract a concise version for the deny reason
|
|
148
|
+
// The signal file contains the full markdown warning from detectAndFormatOverlaps
|
|
149
|
+
const lines = signalContent.split("\n").filter((l) => l.trim());
|
|
150
|
+
const denyLines = ["⚠️ OVERLAP DETECTED — someone else is working on files you're touching:"];
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
// Include lines that show user/file info (start with "- [")
|
|
153
|
+
if (line.trimStart().startsWith("- [")) {
|
|
154
|
+
denyLines.push(" " + line.trim().replace(/^- /, ""));
|
|
155
|
+
}
|
|
204
156
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// Build a concise deny reason
|
|
209
|
-
const lines = ["⚠️ OVERLAP DETECTED — someone else is working on files you're touching:"];
|
|
210
|
-
for (const { thread, overlap } of warnings) {
|
|
211
|
-
const user = thread.user || thread.author || "unknown";
|
|
212
|
-
const status = thread.status === "stuck" ? "STUCK" : "OPEN";
|
|
213
|
-
const narrative = thread.narrative || thread.last_note || "";
|
|
214
|
-
const snip = narrative.length > 80 ? narrative.substring(0, 80) + "…" : narrative;
|
|
215
|
-
lines.push(` [${status}] ${user}: ${overlap.join(", ")}${snip ? " — " + snip : ""}`);
|
|
157
|
+
if (denyLines.length === 1) {
|
|
158
|
+
// Fallback: just use first 300 chars of signal
|
|
159
|
+
denyLines.push(signalContent.substring(0, 300));
|
|
216
160
|
}
|
|
217
|
-
|
|
161
|
+
denyLines.push("Consider coordinating before proceeding. This tool call will be auto-retried.");
|
|
218
162
|
|
|
219
|
-
// Mark as
|
|
220
|
-
|
|
163
|
+
// Mark as dismissed so inject-context doesn't re-create signal for 5 minutes
|
|
164
|
+
markOverlapDismissed();
|
|
221
165
|
|
|
222
|
-
// Audit
|
|
166
|
+
// Audit
|
|
223
167
|
try {
|
|
224
168
|
appendAuditLine({
|
|
225
169
|
type: "overlap_deny",
|
|
226
|
-
user:
|
|
170
|
+
user: getUser(),
|
|
227
171
|
ts: new Date().toISOString(),
|
|
228
|
-
session_id:
|
|
229
|
-
warnings_count: warnings.length,
|
|
230
|
-
threads: warnings.map((w) => ({
|
|
231
|
-
user: w.thread.user || w.thread.author,
|
|
232
|
-
files: w.overlap,
|
|
233
|
-
})),
|
|
172
|
+
session_id: getSessionId({}),
|
|
234
173
|
});
|
|
235
174
|
} catch (_) { /* ignore */ }
|
|
236
175
|
|
|
237
176
|
return {
|
|
238
177
|
permissionDecision: "deny",
|
|
239
|
-
permissionDecisionReason:
|
|
178
|
+
permissionDecisionReason: denyLines.join("\n"),
|
|
240
179
|
};
|
|
241
180
|
}
|
|
242
181
|
|
|
@@ -256,8 +195,8 @@ function main() {
|
|
|
256
195
|
const sessionId = getSessionId(hookInput);
|
|
257
196
|
|
|
258
197
|
// ── Overlap deny gate (Copilot CLI only) ──
|
|
259
|
-
//
|
|
260
|
-
const denyResult =
|
|
198
|
+
// Check if inject-context.js wrote a pending overlap signal file.
|
|
199
|
+
const denyResult = _checkOverlapSignal();
|
|
261
200
|
if (denyResult) {
|
|
262
201
|
process.stdout.write(JSON.stringify(denyResult) + "\n");
|
|
263
202
|
return;
|
|
@@ -590,32 +590,50 @@ function markThreadInjected(threadId, sessionId) {
|
|
|
590
590
|
saveInjectedSet(data);
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
-
// ── Overlap
|
|
593
|
+
// ── Overlap signal file (V2.6.10) ─────────────────────────
|
|
594
|
+
// Signal file approach: inject-context.js writes .overlap-pending with the
|
|
595
|
+
// warning text. pre-tool-use.js reads+deletes it atomically and returns a
|
|
596
|
+
// deny with the content. This avoids shared-state / TTL / session-ID issues.
|
|
594
597
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
598
|
+
const _overlapPendingPath = () => path.join(_stickyDir(), ".overlap-pending");
|
|
599
|
+
const _overlapDismissedPath = () => path.join(_stickyDir(), ".overlap-dismissed");
|
|
600
|
+
|
|
601
|
+
function writeOverlapSignal(text) {
|
|
602
|
+
try {
|
|
603
|
+
fs.writeFileSync(_overlapPendingPath(), text, "utf-8");
|
|
604
|
+
} catch (_) { /* ignore */ }
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function readAndDeleteOverlapSignal() {
|
|
608
|
+
try {
|
|
609
|
+
const content = fs.readFileSync(_overlapPendingPath(), "utf-8");
|
|
610
|
+
fs.unlinkSync(_overlapPendingPath());
|
|
611
|
+
return content || null;
|
|
612
|
+
} catch (_) {
|
|
613
|
+
return null;
|
|
600
614
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function isOverlapDismissed(ttlMs) {
|
|
618
|
+
try {
|
|
619
|
+
const raw = fs.readFileSync(_overlapDismissedPath(), "utf-8").trim();
|
|
620
|
+
const elapsed = Date.now() - new Date(raw).getTime();
|
|
621
|
+
return elapsed < (ttlMs || 300000); // default 5 minutes
|
|
622
|
+
} catch (_) {
|
|
623
|
+
return false;
|
|
607
624
|
}
|
|
608
|
-
return !!data.overlap_warned;
|
|
609
625
|
}
|
|
610
626
|
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
data.overlap_warned_at = new Date().toISOString();
|
|
616
|
-
saveInjectedSet(data);
|
|
627
|
+
function markOverlapDismissed() {
|
|
628
|
+
try {
|
|
629
|
+
fs.writeFileSync(_overlapDismissedPath(), new Date().toISOString(), "utf-8");
|
|
630
|
+
} catch (_) { /* ignore */ }
|
|
617
631
|
}
|
|
618
632
|
|
|
633
|
+
// Legacy stubs — kept for API compat
|
|
634
|
+
function isOverlapWarned() { return false; }
|
|
635
|
+
function markOverlapWarned() { /* no-op */ }
|
|
636
|
+
|
|
619
637
|
// ── Active resumed thread tracking (V2.5) ─────────────────
|
|
620
638
|
|
|
621
639
|
const _activeResumePath = () => path.join(_stickyDir(), ".sticky-active-resume");
|
|
@@ -702,6 +720,11 @@ module.exports = {
|
|
|
702
720
|
// V2.6: overlap warning tracking
|
|
703
721
|
isOverlapWarned,
|
|
704
722
|
markOverlapWarned,
|
|
723
|
+
// V2.6.10: overlap signal file
|
|
724
|
+
writeOverlapSignal,
|
|
725
|
+
readAndDeleteOverlapSignal,
|
|
726
|
+
isOverlapDismissed,
|
|
727
|
+
markOverlapDismissed,
|
|
705
728
|
// V2.5: cross-platform path helper
|
|
706
729
|
normalizeSep,
|
|
707
730
|
};
|