@mneme-ai/core 2.21.6 → 2.21.7

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 (47) hide show
  1. package/dist/agent_manifest.d.ts +1 -1
  2. package/dist/agent_manifest.d.ts.map +1 -1
  3. package/dist/agent_manifest.js +12 -1
  4. package/dist/agent_manifest.js.map +1 -1
  5. package/dist/ai_soul.d.ts +8 -1
  6. package/dist/ai_soul.d.ts.map +1 -1
  7. package/dist/ai_soul.js +9 -3
  8. package/dist/ai_soul.js.map +1 -1
  9. package/dist/consent_fabric/consent_fabric.test.js +9 -2
  10. package/dist/consent_fabric/consent_fabric.test.js.map +1 -1
  11. package/dist/consent_fabric/rights.d.ts.map +1 -1
  12. package/dist/consent_fabric/rights.js +11 -8
  13. package/dist/consent_fabric/rights.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +10 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/pulse.d.ts.map +1 -1
  19. package/dist/pulse.js +18 -8
  20. package/dist/pulse.js.map +1 -1
  21. package/dist/pulse_neutralization.test.d.ts +2 -0
  22. package/dist/pulse_neutralization.test.d.ts.map +1 -0
  23. package/dist/pulse_neutralization.test.js +71 -0
  24. package/dist/pulse_neutralization.test.js.map +1 -0
  25. package/dist/upgrade_visibility/exit_log.d.ts +53 -0
  26. package/dist/upgrade_visibility/exit_log.d.ts.map +1 -0
  27. package/dist/upgrade_visibility/exit_log.js +128 -0
  28. package/dist/upgrade_visibility/exit_log.js.map +1 -0
  29. package/dist/upgrade_visibility/index.d.ts +45 -0
  30. package/dist/upgrade_visibility/index.d.ts.map +1 -0
  31. package/dist/upgrade_visibility/index.js +83 -0
  32. package/dist/upgrade_visibility/index.js.map +1 -0
  33. package/dist/upgrade_visibility/mutex.d.ts +44 -0
  34. package/dist/upgrade_visibility/mutex.d.ts.map +1 -0
  35. package/dist/upgrade_visibility/mutex.js +114 -0
  36. package/dist/upgrade_visibility/mutex.js.map +1 -0
  37. package/dist/upgrade_visibility/npm_detector.d.ts +40 -0
  38. package/dist/upgrade_visibility/npm_detector.d.ts.map +1 -0
  39. package/dist/upgrade_visibility/npm_detector.js +118 -0
  40. package/dist/upgrade_visibility/npm_detector.js.map +1 -0
  41. package/dist/upgrade_visibility/upgrade_visibility.test.d.ts +2 -0
  42. package/dist/upgrade_visibility/upgrade_visibility.test.d.ts.map +1 -0
  43. package/dist/upgrade_visibility/upgrade_visibility.test.js +161 -0
  44. package/dist/upgrade_visibility/upgrade_visibility.test.js.map +1 -0
  45. package/dist/version_check.js +1 -1
  46. package/dist/version_check.js.map +1 -1
  47. package/package.json +1 -1
@@ -0,0 +1,128 @@
1
+ /**
2
+ * v2.21.7 — UPGRADE VISIBILITY · EXIT LOG.
3
+ *
4
+ * HMAC-chained log of every upgrade attempt + its exit code. Closes
5
+ * the "silent upgrade fail" concern from the v2.21.6 AI-agent audit:
6
+ *
7
+ * "exit-4294963214 = mneme upgrade ตัวเองพังเงียบๆ แล้ว pulse ก็ยังบอกว่า
8
+ * auto-upgrade is one tool call away."
9
+ *
10
+ * - Append-only log at `.mneme/upgrade/log.jsonl`.
11
+ * - Every entry: ts + version-before/after + npm exit code + reason +
12
+ * prev sig (chain-linked, tamper-evident).
13
+ * - `verifyChain()` audits integrity.
14
+ * - `lastFailure()` surfaces the most recent non-zero exit so the
15
+ * pulse can mention "previous upgrade attempt failed (exit X)" —
16
+ * no more silent fails.
17
+ */
18
+ import { existsSync, readFileSync, appendFileSync, mkdirSync, writeFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { createHmac, randomBytes } from "node:crypto";
21
+ const DIR = ".mneme/upgrade";
22
+ const LOG = "log.jsonl";
23
+ const KEY = "upgrade.key";
24
+ function dir(repoRoot) {
25
+ const d = join(repoRoot, DIR);
26
+ if (!existsSync(d))
27
+ mkdirSync(d, { recursive: true });
28
+ return d;
29
+ }
30
+ function key(repoRoot) {
31
+ const p = join(dir(repoRoot), KEY);
32
+ if (existsSync(p))
33
+ return readFileSync(p, "utf8").trim();
34
+ const k = randomBytes(32).toString("base64url");
35
+ writeFileSync(p, k, "utf8");
36
+ return k;
37
+ }
38
+ function sign(payload, k) {
39
+ return createHmac("sha256", k).update(payload).digest("base64url").slice(0, 22);
40
+ }
41
+ function logPath(repoRoot) { return join(dir(repoRoot), LOG); }
42
+ export function recordUpgrade(repoRoot, opts) {
43
+ const k = key(repoRoot);
44
+ const ts = new Date().toISOString();
45
+ const id = "up_" + randomBytes(4).toString("hex");
46
+ const existing = listUpgrades(repoRoot);
47
+ const prev = existing.length > 0 ? existing[existing.length - 1].sig : "genesis";
48
+ const canonical = `${ts}|${opts.versionBefore}|${opts.versionAfter ?? ""}|${opts.exitCode}|${opts.reason}|${prev}`;
49
+ const sig = sign(canonical, k);
50
+ const entry = {
51
+ v: 1, id, ts,
52
+ versionBefore: opts.versionBefore,
53
+ versionAfter: opts.versionAfter,
54
+ exitCode: opts.exitCode,
55
+ reason: opts.reason,
56
+ prev, sig,
57
+ ...(opts.command ? { command: opts.command } : {}),
58
+ ...(opts.stderrTail ? { stderrTail: opts.stderrTail } : {}),
59
+ };
60
+ appendFileSync(logPath(repoRoot), JSON.stringify(entry) + "\n", "utf8");
61
+ return entry;
62
+ }
63
+ export function listUpgrades(repoRoot) {
64
+ const p = logPath(repoRoot);
65
+ if (!existsSync(p))
66
+ return [];
67
+ try {
68
+ return readFileSync(p, "utf8").trim().split("\n").map((l) => { try {
69
+ return JSON.parse(l);
70
+ }
71
+ catch {
72
+ return null;
73
+ } }).filter((r) => !!r);
74
+ }
75
+ catch {
76
+ return [];
77
+ }
78
+ }
79
+ export function lastFailure(repoRoot) {
80
+ const all = listUpgrades(repoRoot);
81
+ for (let i = all.length - 1; i >= 0; i--) {
82
+ if (all[i].exitCode !== 0)
83
+ return all[i];
84
+ }
85
+ return null;
86
+ }
87
+ export function lastSuccess(repoRoot) {
88
+ const all = listUpgrades(repoRoot);
89
+ for (let i = all.length - 1; i >= 0; i--) {
90
+ if (all[i].exitCode === 0)
91
+ return all[i];
92
+ }
93
+ return null;
94
+ }
95
+ export function verifyChain(repoRoot) {
96
+ const all = listUpgrades(repoRoot);
97
+ if (all.length === 0)
98
+ return { ok: true };
99
+ const k = key(repoRoot);
100
+ let lastSig = "genesis";
101
+ for (let i = 0; i < all.length; i++) {
102
+ const e = all[i];
103
+ if (e.prev !== lastSig)
104
+ return { ok: false, brokenAt: i, reason: `entry ${i} prev=${e.prev.slice(0, 8)} expected ${lastSig.slice(0, 8)}` };
105
+ const canonical = `${e.ts}|${e.versionBefore}|${e.versionAfter ?? ""}|${e.exitCode}|${e.reason}|${e.prev}`;
106
+ if (sign(canonical, k) !== e.sig)
107
+ return { ok: false, brokenAt: i, reason: `entry ${i} signature mismatch` };
108
+ lastSig = e.sig;
109
+ }
110
+ return { ok: true };
111
+ }
112
+ export function formatUpgradeLog(entries) {
113
+ if (entries.length === 0)
114
+ return "📜 UPGRADE LOG — empty";
115
+ const lines = [`📜 UPGRADE LOG — ${entries.length} entries`, ""];
116
+ for (const e of entries.slice(-20)) {
117
+ const badge = e.exitCode === 0 ? "✓" : "✗";
118
+ lines.push(` ${badge} ${e.ts} v${e.versionBefore} → ${e.versionAfter ?? "(failed)"} exit=${e.exitCode} ${e.reason}`);
119
+ if (e.stderrTail && e.exitCode !== 0) {
120
+ const tail = e.stderrTail.split("\n").slice(-2).join(" / ").slice(0, 200);
121
+ lines.push(` stderr: ${tail}`);
122
+ }
123
+ }
124
+ if (entries.length > 20)
125
+ lines.push(` (showing last 20 of ${entries.length})`);
126
+ return lines.join("\n");
127
+ }
128
+ //# sourceMappingURL=exit_log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exit_log.js","sourceRoot":"","sources":["../../src/upgrade_visibility/exit_log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * v2.21.7 — UPGRADE VISIBILITY.
3
+ *
4
+ * Closes the two deferred concerns from the v2.21.6 AI-agent audit:
5
+ *
6
+ * 5. "Silent upgrade fail (exit 4294963214) swallowed."
7
+ * → exit_log.ts: HMAC-chained record of every attempt + exit
8
+ * code; `lastFailure()` surfaces the most recent non-zero
9
+ * exit. No more silent fails.
10
+ *
11
+ * 6. "Auto-upgrade race during user's npm install."
12
+ * → npm_detector.ts + mutex.ts: ancestor-chain detection blocks
13
+ * self-upgrade when an `npm install` is alive in the parent
14
+ * tree; file-lock mutex prevents two Mneme processes from
15
+ * running `npm install` at the same time.
16
+ *
17
+ * Composes:
18
+ *
19
+ * const safety = isUpgradeSafeRightNow();
20
+ * if (!safety.safe) return recordUpgrade(repoRoot, {
21
+ * versionBefore: cur, versionAfter: null, exitCode: -1,
22
+ * reason: `aborted: ${safety.reason}`,
23
+ * });
24
+ * const lock = acquireLock(repoRoot, { reason: "auto-upgrade" });
25
+ * if (!lock.ok) return recordUpgrade(repoRoot, {...exitCode: -2, reason: lock.reason!});
26
+ * // ... run `npm install -g mneme-ai` ...
27
+ * recordUpgrade(repoRoot, { versionBefore, versionAfter, exitCode, ... });
28
+ * releaseLock(repoRoot);
29
+ */
30
+ export { acquireLock, releaseLock, readLock, isLocked, formatLock, type LockState, type AcquireResult, type AcquireOptions, } from "./mutex.js";
31
+ export { recordUpgrade, listUpgrades, lastFailure, lastSuccess, verifyChain, formatUpgradeLog, type UpgradeEntry, type RecordUpgradeOptions, } from "./exit_log.js";
32
+ export { isInsideNpmInstall, isUpgradeSafeRightNow, formatDetection, type DetectionResult, } from "./npm_detector.js";
33
+ import { lastFailure, lastSuccess } from "./exit_log.js";
34
+ /** One-shot doctor: is it safe to attempt an upgrade right now? Pure
35
+ * read-only — never mutates state. */
36
+ export interface UpgradeDoctorReport {
37
+ ready: boolean;
38
+ reasons: string[];
39
+ recentFailure: ReturnType<typeof lastFailure>;
40
+ recentSuccess: ReturnType<typeof lastSuccess>;
41
+ attempts: number;
42
+ }
43
+ export declare function upgradeDoctor(repoRoot: string): UpgradeDoctorReport;
44
+ export declare function formatDoctor(r: UpgradeDoctorReport): string;
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/upgrade_visibility/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EACL,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EACxD,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,GACxD,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EACpF,KAAK,YAAY,EAAE,KAAK,oBAAoB,GAC7C,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,EAC1D,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,WAAW,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AAEvE;uCACuC;AACvC,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IAC9C,aAAa,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAiBnE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAsB3D"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * v2.21.7 — UPGRADE VISIBILITY.
3
+ *
4
+ * Closes the two deferred concerns from the v2.21.6 AI-agent audit:
5
+ *
6
+ * 5. "Silent upgrade fail (exit 4294963214) swallowed."
7
+ * → exit_log.ts: HMAC-chained record of every attempt + exit
8
+ * code; `lastFailure()` surfaces the most recent non-zero
9
+ * exit. No more silent fails.
10
+ *
11
+ * 6. "Auto-upgrade race during user's npm install."
12
+ * → npm_detector.ts + mutex.ts: ancestor-chain detection blocks
13
+ * self-upgrade when an `npm install` is alive in the parent
14
+ * tree; file-lock mutex prevents two Mneme processes from
15
+ * running `npm install` at the same time.
16
+ *
17
+ * Composes:
18
+ *
19
+ * const safety = isUpgradeSafeRightNow();
20
+ * if (!safety.safe) return recordUpgrade(repoRoot, {
21
+ * versionBefore: cur, versionAfter: null, exitCode: -1,
22
+ * reason: `aborted: ${safety.reason}`,
23
+ * });
24
+ * const lock = acquireLock(repoRoot, { reason: "auto-upgrade" });
25
+ * if (!lock.ok) return recordUpgrade(repoRoot, {...exitCode: -2, reason: lock.reason!});
26
+ * // ... run `npm install -g mneme-ai` ...
27
+ * recordUpgrade(repoRoot, { versionBefore, versionAfter, exitCode, ... });
28
+ * releaseLock(repoRoot);
29
+ */
30
+ export { acquireLock, releaseLock, readLock, isLocked, formatLock, } from "./mutex.js";
31
+ export { recordUpgrade, listUpgrades, lastFailure, lastSuccess, verifyChain, formatUpgradeLog, } from "./exit_log.js";
32
+ export { isInsideNpmInstall, isUpgradeSafeRightNow, formatDetection, } from "./npm_detector.js";
33
+ import { isUpgradeSafeRightNow } from "./npm_detector.js";
34
+ import { isLocked, readLock } from "./mutex.js";
35
+ import { lastFailure, lastSuccess, listUpgrades } from "./exit_log.js";
36
+ export function upgradeDoctor(repoRoot) {
37
+ const reasons = [];
38
+ let ready = true;
39
+ const safety = isUpgradeSafeRightNow();
40
+ if (!safety.safe) {
41
+ ready = false;
42
+ reasons.push(safety.reason);
43
+ }
44
+ if (isLocked(repoRoot)) {
45
+ ready = false;
46
+ const lk = readLock(repoRoot);
47
+ reasons.push(`upgrade lock active: pid=${lk?.pid} reason="${lk?.reason}"`);
48
+ }
49
+ return {
50
+ ready,
51
+ reasons,
52
+ recentFailure: lastFailure(repoRoot),
53
+ recentSuccess: lastSuccess(repoRoot),
54
+ attempts: listUpgrades(repoRoot).length,
55
+ };
56
+ }
57
+ export function formatDoctor(r) {
58
+ const lines = [];
59
+ lines.push(`🩺 UPGRADE DOCTOR — ${r.ready ? "✓ ready" : "✗ blocked"}`);
60
+ lines.push("");
61
+ if (r.reasons.length > 0) {
62
+ lines.push(" Blockers:");
63
+ for (const reason of r.reasons)
64
+ lines.push(` - ${reason}`);
65
+ lines.push("");
66
+ }
67
+ lines.push(` Attempts logged: ${r.attempts}`);
68
+ if (r.recentFailure) {
69
+ lines.push(` Last failure: ${r.recentFailure.ts} exit=${r.recentFailure.exitCode}`);
70
+ lines.push(` reason: ${r.recentFailure.reason}`);
71
+ }
72
+ else {
73
+ lines.push(" Last failure: (none recorded)");
74
+ }
75
+ if (r.recentSuccess) {
76
+ lines.push(` Last success: ${r.recentSuccess.ts} v${r.recentSuccess.versionBefore} → v${r.recentSuccess.versionAfter}`);
77
+ }
78
+ else {
79
+ lines.push(" Last success: (none recorded)");
80
+ }
81
+ return lines.join("\n");
82
+ }
83
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/upgrade_visibility/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * v2.21.7 — UPGRADE VISIBILITY · MUTEX.
3
+ *
4
+ * File-lock-based mutex that prevents two Mneme operations from
5
+ * running `npm install` concurrently. Closes the race condition
6
+ * surfaced in the v2.21.6 AI-agent audit:
7
+ *
8
+ * "auto-upgrade triggers in parallel with the user's npm install
9
+ * on session start; npm install fails because the daemon holds
10
+ * DLL handles."
11
+ *
12
+ * - Lock file at `.mneme/upgrade/upgrade.lock` (per-repo).
13
+ * - Lock contains owner PID + ts + reason; stale-lock detection
14
+ * when owner PID is no longer alive (process check) OR lock is
15
+ * older than maxAgeMs (default 10 min — generous for slow CI).
16
+ * - acquire() returns ok/false; never blocks.
17
+ */
18
+ export interface LockState {
19
+ pid: number;
20
+ ts: string;
21
+ reason: string;
22
+ /** Optional: hostname for cross-machine awareness. */
23
+ host?: string;
24
+ }
25
+ export interface AcquireResult {
26
+ ok: boolean;
27
+ /** When ok=false, the existing lock that blocks acquisition. */
28
+ heldBy?: LockState;
29
+ reason?: string;
30
+ }
31
+ export interface AcquireOptions {
32
+ reason: string;
33
+ /** Stale-lock threshold; default 10 minutes. */
34
+ maxAgeMs?: number;
35
+ }
36
+ export declare function acquireLock(repoRoot: string, opts: AcquireOptions): AcquireResult;
37
+ export declare function releaseLock(repoRoot: string): {
38
+ ok: boolean;
39
+ reason?: string;
40
+ };
41
+ export declare function readLock(repoRoot: string): LockState | null;
42
+ export declare function isLocked(repoRoot: string, maxAgeMs?: number): boolean;
43
+ export declare function formatLock(state: LockState | null): string;
44
+ //# sourceMappingURL=mutex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutex.d.ts","sourceRoot":"","sources":["../../src/upgrade_visibility/mutex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAqBD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,gEAAgE;IAChE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,aAAa,CAuBjF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAa9E;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAI3D;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAqB,GAAG,OAAO,CAMjF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,MAAM,CAG1D"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * v2.21.7 — UPGRADE VISIBILITY · MUTEX.
3
+ *
4
+ * File-lock-based mutex that prevents two Mneme operations from
5
+ * running `npm install` concurrently. Closes the race condition
6
+ * surfaced in the v2.21.6 AI-agent audit:
7
+ *
8
+ * "auto-upgrade triggers in parallel with the user's npm install
9
+ * on session start; npm install fails because the daemon holds
10
+ * DLL handles."
11
+ *
12
+ * - Lock file at `.mneme/upgrade/upgrade.lock` (per-repo).
13
+ * - Lock contains owner PID + ts + reason; stale-lock detection
14
+ * when owner PID is no longer alive (process check) OR lock is
15
+ * older than maxAgeMs (default 10 min — generous for slow CI).
16
+ * - acquire() returns ok/false; never blocks.
17
+ */
18
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ const DIR = ".mneme/upgrade";
21
+ const LOCK = "upgrade.lock";
22
+ const DEFAULT_MAX_AGE_MS = 10 * 60 * 1000;
23
+ function dir(repoRoot) {
24
+ const d = join(repoRoot, DIR);
25
+ if (!existsSync(d))
26
+ mkdirSync(d, { recursive: true });
27
+ return d;
28
+ }
29
+ function lockPath(repoRoot) { return join(dir(repoRoot), LOCK); }
30
+ function pidAlive(pid) {
31
+ if (pid <= 0)
32
+ return false;
33
+ try {
34
+ // signal 0 = liveness probe; throws on dead process.
35
+ process.kill(pid, 0);
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ export function acquireLock(repoRoot, opts) {
43
+ const p = lockPath(repoRoot);
44
+ if (existsSync(p)) {
45
+ let existing = null;
46
+ try {
47
+ existing = JSON.parse(readFileSync(p, "utf8"));
48
+ }
49
+ catch { /* corrupt */ }
50
+ if (existing) {
51
+ const alive = pidAlive(existing.pid);
52
+ const age = Date.now() - Date.parse(existing.ts);
53
+ const stale = !alive || age > (opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS);
54
+ if (!stale) {
55
+ return { ok: false, heldBy: existing, reason: `lock held by pid=${existing.pid} since ${existing.ts}: ${existing.reason}` };
56
+ }
57
+ // stale → take it
58
+ }
59
+ }
60
+ const state = {
61
+ pid: process.pid,
62
+ ts: new Date().toISOString(),
63
+ reason: opts.reason,
64
+ host: (() => { try {
65
+ return require("node:os").hostname();
66
+ }
67
+ catch {
68
+ return undefined;
69
+ } })(),
70
+ };
71
+ writeFileSync(p, JSON.stringify(state, null, 2), "utf8");
72
+ return { ok: true };
73
+ }
74
+ export function releaseLock(repoRoot) {
75
+ const p = lockPath(repoRoot);
76
+ if (!existsSync(p))
77
+ return { ok: true };
78
+ try {
79
+ const cur = JSON.parse(readFileSync(p, "utf8"));
80
+ if (cur.pid !== process.pid) {
81
+ return { ok: false, reason: `cannot release lock held by another pid=${cur.pid}` };
82
+ }
83
+ unlinkSync(p);
84
+ return { ok: true };
85
+ }
86
+ catch (e) {
87
+ return { ok: false, reason: `failed to release: ${e.message}` };
88
+ }
89
+ }
90
+ export function readLock(repoRoot) {
91
+ const p = lockPath(repoRoot);
92
+ if (!existsSync(p))
93
+ return null;
94
+ try {
95
+ return JSON.parse(readFileSync(p, "utf8"));
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ export function isLocked(repoRoot, maxAgeMs = DEFAULT_MAX_AGE_MS) {
102
+ const cur = readLock(repoRoot);
103
+ if (!cur)
104
+ return false;
105
+ const alive = pidAlive(cur.pid);
106
+ const age = Date.now() - Date.parse(cur.ts);
107
+ return alive && age <= maxAgeMs;
108
+ }
109
+ export function formatLock(state) {
110
+ if (!state)
111
+ return "✓ no upgrade in progress";
112
+ return `🔒 upgrade in progress\n pid: ${state.pid}\n host: ${state.host ?? "(unknown)"}\n since: ${state.ts}\n reason: ${state.reason}`;
113
+ }
114
+ //# sourceMappingURL=mutex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../src/upgrade_visibility/mutex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * v2.21.7 — UPGRADE VISIBILITY · NPM INSTALL DETECTOR.
3
+ *
4
+ * Detects whether the current process tree is inside / parented by a
5
+ * live `npm install` (or similar package-manager) operation. Used as
6
+ * a SAFETY GATE before auto-upgrade fires — closes the race condition
7
+ * the v2.21.6 AI-agent audit flagged:
8
+ *
9
+ * "Pulse hook trigger upgrade ขนานกับที่ผมรัน npm install"
10
+ *
11
+ * The detection is heuristic + platform-aware:
12
+ *
13
+ * - Windows: check parent process via `wmic` / `tasklist` lookup
14
+ * for npm.exe / yarn.exe / pnpm.exe in the ancestor chain.
15
+ * - POSIX: walk `/proc/<ppid>/comm` upward looking for the same.
16
+ *
17
+ * On EITHER platform, if detection cannot complete (e.g. wmic
18
+ * missing), we fail OPEN — caller should treat unknown as "do not
19
+ * auto-upgrade right now". Better to skip a self-upgrade than to
20
+ * collide with the user's package install.
21
+ */
22
+ export interface DetectionResult {
23
+ /** True iff we believe an `npm install` is active in the ancestor chain. */
24
+ detected: boolean;
25
+ /** The detected command name, when available. */
26
+ evidence?: string;
27
+ /** When detection couldn't be performed, this surfaces the reason. */
28
+ unknownReason?: string;
29
+ }
30
+ /** Whether an npm-like operation owns the ancestor chain. Caller
31
+ * should treat any positive result as "do NOT auto-upgrade". */
32
+ export declare function isInsideNpmInstall(): DetectionResult;
33
+ /** Safety predicate used by callers that want to gate an auto-upgrade.
34
+ * Pass a hint string for the log. */
35
+ export declare function isUpgradeSafeRightNow(): {
36
+ safe: boolean;
37
+ reason: string;
38
+ };
39
+ export declare function formatDetection(r: DetectionResult): string;
40
+ //# sourceMappingURL=npm_detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm_detector.d.ts","sourceRoot":"","sources":["../../src/upgrade_visibility/npm_detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA6DD;iEACiE;AACjE,wBAAgB,kBAAkB,IAAI,eAAe,CAGpD;AAED;sCACsC;AACtC,wBAAgB,qBAAqB,IAAI;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CASzE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,eAAe,GAAG,MAAM,CAI1D"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * v2.21.7 — UPGRADE VISIBILITY · NPM INSTALL DETECTOR.
3
+ *
4
+ * Detects whether the current process tree is inside / parented by a
5
+ * live `npm install` (or similar package-manager) operation. Used as
6
+ * a SAFETY GATE before auto-upgrade fires — closes the race condition
7
+ * the v2.21.6 AI-agent audit flagged:
8
+ *
9
+ * "Pulse hook trigger upgrade ขนานกับที่ผมรัน npm install"
10
+ *
11
+ * The detection is heuristic + platform-aware:
12
+ *
13
+ * - Windows: check parent process via `wmic` / `tasklist` lookup
14
+ * for npm.exe / yarn.exe / pnpm.exe in the ancestor chain.
15
+ * - POSIX: walk `/proc/<ppid>/comm` upward looking for the same.
16
+ *
17
+ * On EITHER platform, if detection cannot complete (e.g. wmic
18
+ * missing), we fail OPEN — caller should treat unknown as "do not
19
+ * auto-upgrade right now". Better to skip a self-upgrade than to
20
+ * collide with the user's package install.
21
+ */
22
+ import { execSync } from "node:child_process";
23
+ const NPM_LIKE = new Set(["npm", "npm-cli.js", "yarn", "pnpm", "bun", "npx"]);
24
+ function detectWindows() {
25
+ // wmic is being deprecated on newer Win11 builds; fall back to PowerShell Get-CimInstance.
26
+ // Both ways: walk parent chain up to 8 levels looking for an npm-like command.
27
+ try {
28
+ const out = execSync('wmic process get ProcessId,ParentProcessId,Name /FORMAT:CSV', { encoding: "utf8", timeout: 3000 }).toString();
29
+ const rows = out.split(/\r?\n/).slice(1)
30
+ .map((l) => l.split(",").map((s) => s.trim()))
31
+ .filter((r) => r.length >= 4);
32
+ const byPid = new Map();
33
+ for (const r of rows) {
34
+ const [, name, ppid, pid] = r;
35
+ const pidN = parseInt(pid, 10);
36
+ const ppidN = parseInt(ppid, 10);
37
+ if (!Number.isFinite(pidN))
38
+ continue;
39
+ byPid.set(pidN, { name: (name ?? "").toLowerCase(), ppid: ppidN });
40
+ }
41
+ let cur = process.ppid;
42
+ for (let i = 0; i < 8 && byPid.has(cur); i++) {
43
+ const node = byPid.get(cur);
44
+ const stem = node.name.replace(/\.exe$/i, "");
45
+ if (NPM_LIKE.has(stem))
46
+ return { detected: true, evidence: node.name };
47
+ cur = node.ppid;
48
+ }
49
+ return { detected: false };
50
+ }
51
+ catch (e) {
52
+ return { detected: false, unknownReason: `wmic probe failed: ${e.message.slice(0, 80)}` };
53
+ }
54
+ }
55
+ function detectPosix() {
56
+ try {
57
+ let cur = process.ppid;
58
+ for (let i = 0; i < 8 && cur > 0; i++) {
59
+ const commPath = `/proc/${cur}/comm`;
60
+ let comm;
61
+ try {
62
+ comm = require("node:fs").readFileSync(commPath, "utf8").trim();
63
+ }
64
+ catch {
65
+ return { detected: false };
66
+ }
67
+ const stem = comm.toLowerCase();
68
+ if (NPM_LIKE.has(stem))
69
+ return { detected: true, evidence: comm };
70
+ const statPath = `/proc/${cur}/stat`;
71
+ let parent;
72
+ try {
73
+ const stat = require("node:fs").readFileSync(statPath, "utf8");
74
+ // /proc/PID/stat format: pid (comm) state ppid ...
75
+ // comm can contain spaces; split from the LAST ')' to be safe.
76
+ const after = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/);
77
+ parent = parseInt(after[1], 10);
78
+ }
79
+ catch {
80
+ return { detected: false };
81
+ }
82
+ if (!Number.isFinite(parent) || parent <= 1)
83
+ break;
84
+ cur = parent;
85
+ }
86
+ return { detected: false };
87
+ }
88
+ catch (e) {
89
+ return { detected: false, unknownReason: `proc probe failed: ${e.message.slice(0, 80)}` };
90
+ }
91
+ }
92
+ /** Whether an npm-like operation owns the ancestor chain. Caller
93
+ * should treat any positive result as "do NOT auto-upgrade". */
94
+ export function isInsideNpmInstall() {
95
+ if (process.platform === "win32")
96
+ return detectWindows();
97
+ return detectPosix();
98
+ }
99
+ /** Safety predicate used by callers that want to gate an auto-upgrade.
100
+ * Pass a hint string for the log. */
101
+ export function isUpgradeSafeRightNow() {
102
+ const r = isInsideNpmInstall();
103
+ if (r.detected) {
104
+ return { safe: false, reason: `package manager active in ancestor chain (${r.evidence}); skipping to avoid race` };
105
+ }
106
+ if (r.unknownReason) {
107
+ return { safe: false, reason: `cannot verify package-manager state; skipping out of caution (${r.unknownReason})` };
108
+ }
109
+ return { safe: true, reason: "no package manager detected in ancestor chain" };
110
+ }
111
+ export function formatDetection(r) {
112
+ if (r.detected)
113
+ return `⚠ npm-like process detected in ancestor chain: ${r.evidence}`;
114
+ if (r.unknownReason)
115
+ return `· detection inconclusive: ${r.unknownReason}`;
116
+ return "✓ no package manager active in ancestor chain";
117
+ }
118
+ //# sourceMappingURL=npm_detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm_detector.js","sourceRoot":"","sources":["../../src/upgrade_visibility/npm_detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAW9C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAE9E,SAAS,aAAa;IACpB,2FAA2F;IAC3F,+EAA+E;IAC/E,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,6DAA6D,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QACpI,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;aACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;aAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0C,CAAC;QAChE,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAI,EAAE,EAAE,CAAC,CAAC;YAChC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAK,EAAE,EAAE,CAAC,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,SAAS;YACrC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;QACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;YAC9C,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;YACvE,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC;QAClB,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,sBAAuB,CAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;IACvG,CAAC;AACH,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;QACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;YACrC,IAAI,IAAY,CAAC;YACjB,IAAI,CAAC;gBAAC,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAAC,CAAC;YACxE,MAAM,CAAC;gBAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YAAC,CAAC;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAChC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAClE,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;YACrC,IAAI,MAAc,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAW,CAAC;gBACzE,mDAAmD;gBACnD,+DAA+D;gBAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACxE,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,EAAE,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YAAC,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;gBAAE,MAAM;YACnD,GAAG,GAAG,MAAM,CAAC;QACf,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,sBAAuB,CAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;IACvG,CAAC;AACH,CAAC;AAED;iEACiE;AACjE,MAAM,UAAU,kBAAkB;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,aAAa,EAAE,CAAC;IACzD,OAAO,WAAW,EAAE,CAAC;AACvB,CAAC;AAED;sCACsC;AACtC,MAAM,UAAU,qBAAqB;IACnC,MAAM,CAAC,GAAG,kBAAkB,EAAE,CAAC;IAC/B,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,6CAA6C,CAAC,CAAC,QAAQ,2BAA2B,EAAE,CAAC;IACrH,CAAC;IACD,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,iEAAiE,CAAC,CAAC,aAAa,GAAG,EAAE,CAAC;IACtH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,+CAA+C,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,CAAkB;IAChD,IAAI,CAAC,CAAC,QAAQ;QAAE,OAAO,kDAAkD,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtF,IAAI,CAAC,CAAC,aAAa;QAAE,OAAO,6BAA6B,CAAC,CAAC,aAAa,EAAE,CAAC;IAC3E,OAAO,+CAA+C,CAAC;AACzD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=upgrade_visibility.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade_visibility.test.d.ts","sourceRoot":"","sources":["../../src/upgrade_visibility/upgrade_visibility.test.ts"],"names":[],"mappings":""}