cursordoctrine 0.5.2 → 0.5.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.
Files changed (2) hide show
  1. package/bin/cli.mjs +38 -7
  2. package/package.json +1 -1
package/bin/cli.mjs CHANGED
@@ -62,6 +62,29 @@ function keyOf(command, keys) {
62
62
  return keys.find((k) => command.includes(k));
63
63
  }
64
64
 
65
+ // Detect a STALE entry from a prior install: its command names a hook script
66
+ // (ps1/sh, not an .md prompt) under .agents/hooks OR names inject-doctrine,
67
+ // but that filename is NOT in the current payload. These are NOT foreign
68
+ // entries the user added — they are leftovers from an older version of THIS
69
+ // pack (e.g. anchor-set-nudge / minimal-edit-audit, deleted in 0.5.0). The
70
+ // old merge preserved them as "foreign", so deleted hooks kept running on
71
+ // every edit silently. Returning true here makes install reap them.
72
+ const HOOK_FILENAME_RE = /([\w.\-]+)\.(ps1|sh)\b/;
73
+ const INJECT_RE = /\binject-doctrine\.(ps1|sh)\b/;
74
+ function isStaleOurs(command) {
75
+ if (typeof command !== 'string') return false;
76
+ const hookMatch = command.match(HOOK_FILENAME_RE);
77
+ if (hookMatch) {
78
+ const fname = `${hookMatch[1]}.${hookMatch[2]}`;
79
+ // If the script still ships, it's current ours (handled elsewhere).
80
+ return !payloadHookFiles().includes(fname);
81
+ }
82
+ if (INJECT_RE.test(command)) {
83
+ return !doctrineFiles.includes(injectName) ? true : false;
84
+ }
85
+ return false;
86
+ }
87
+
65
88
  function mergeHooks(existing, incoming, keys) {
66
89
  const out = structuredClone(existing);
67
90
  if (out.version === undefined) out.version = incoming.version;
@@ -75,22 +98,25 @@ function mergeHooks(existing, incoming, keys) {
75
98
  if (i >= 0) cur[i] = entry;
76
99
  else cur.push(entry);
77
100
  }
78
- // Re-order our entries to match the shipped hooks.json (merge used to leave
79
- // stale order e.g. post-tool-use before intent-anchor breaking same-tool
80
- // delivery of the anchor message).
81
- const foreign = cur.filter((x) => x && !isOurs(x.command, keys));
101
+ // Reap stale ours entries from prior installs BEFORE treating anything as
102
+ // foreign. isStaleOurs catches commands referencing scripts this pack no
103
+ // longer ships (e.g. anchor-set-nudge / minimal-edit-audit, deleted in
104
+ // 0.5.0). Without this, deleted hooks kept running silently on every edit
105
+ // because the merge mistook them for user-added foreign entries.
106
+ const live = cur.filter((x) => x && !isStaleOurs(x.command));
107
+ const foreign = live.filter((x) => x && !isOurs(x.command, keys));
82
108
  const reordered = [];
83
109
  const used = new Set();
84
110
  for (const entry of entries) {
85
111
  const k = keyOf(entry.command, keys);
86
112
  if (!k || !isOurs(entry.command, keys)) continue;
87
- const found = cur.find((x) => x && keyOf(x.command, keys) === k);
113
+ const found = live.find((x) => x && keyOf(x.command, keys) === k);
88
114
  if (found) {
89
115
  reordered.push(found);
90
116
  used.add(k);
91
117
  }
92
118
  }
93
- for (const x of cur) {
119
+ for (const x of live) {
94
120
  const k = keyOf(x?.command, keys);
95
121
  if (isOurs(x?.command, keys) && k && !used.has(k)) reordered.push(x);
96
122
  }
@@ -429,7 +455,12 @@ function uninstall() {
429
455
  let foreign = 0;
430
456
  for (const [event, entries] of Object.entries(existing.hooks || {})) {
431
457
  if (!Array.isArray(entries)) continue;
432
- existing.hooks[event] = entries.filter((x) => !isOurs(x?.command, keys));
458
+ // Remove both current ours AND stale ours (commands referencing scripts
459
+ // this pack no longer ships - leftovers from prior versions). Pure
460
+ // foreign entries are kept, same as before.
461
+ existing.hooks[event] = entries.filter(
462
+ (x) => !x || (!isOurs(x.command, keys) && !isStaleOurs(x.command))
463
+ );
433
464
  foreign += existing.hooks[event].length;
434
465
  if (existing.hooks[event].length === 0) delete existing.hooks[event];
435
466
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursordoctrine",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Thin self-review hooks for Cursor — the model is the auditor. Pruned + deduplicated: intent-anchor (auto-scaffolded .scope.json per prompt + per-turn re-injection against Salience Dilution), intent-trace final review, unified anti-slop checklist as single source of truth.",
5
5
  "bin": {
6
6
  "cursordoctrine": "bin/cli.mjs"