@massu/core 1.13.0 → 1.14.0

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.
@@ -201,17 +201,17 @@ var init_manifest_registry = __esm({
201
201
  });
202
202
 
203
203
  // src/detect/package-detector.ts
204
- import { readFileSync as readFileSync2, existsSync as existsSync3, statSync, lstatSync, readdirSync } from "fs";
205
- import { join, relative } from "path";
204
+ import { readFileSync as readFileSync3, existsSync as existsSync4, statSync, lstatSync, readdirSync } from "fs";
205
+ import { join as join2, relative } from "path";
206
206
  import { parse as parseToml } from "smol-toml";
207
207
  function safeRead(path) {
208
208
  try {
209
- if (!existsSync3(path)) return null;
209
+ if (!existsSync4(path)) return null;
210
210
  const ls = lstatSync(path);
211
211
  if (ls.isSymbolicLink()) return null;
212
212
  const st = statSync(path);
213
213
  if (!st.isFile()) return null;
214
- return readFileSync2(path, "utf-8");
214
+ return readFileSync3(path, "utf-8");
215
215
  } catch {
216
216
  return null;
217
217
  }
@@ -242,7 +242,7 @@ function parsePackageJson(path, directory, root, warnings) {
242
242
  const peer = Object.keys(
243
243
  pkg.peerDependencies ?? {}
244
244
  );
245
- const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync3(join(directory, "tsconfig.json"));
245
+ const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync4(join2(directory, "tsconfig.json"));
246
246
  const language = hasTs ? "typescript" : "javascript";
247
247
  const scripts = Object.keys(
248
248
  pkg.scripts ?? {}
@@ -668,8 +668,8 @@ function detectManifestsInDir(dir, root, warnings) {
668
668
  let dirEntries = null;
669
669
  for (const entry of getManifestRegistry2()) {
670
670
  if (!entry.pattern.startsWith("*")) {
671
- const path = join(dir, entry.pattern);
672
- if (!existsSync3(path)) continue;
671
+ const path = join2(dir, entry.pattern);
672
+ if (!existsSync4(path)) continue;
673
673
  const m = entry.parse(path, dir, root, warnings);
674
674
  if (m !== null) out.push(m);
675
675
  } else {
@@ -682,8 +682,8 @@ function detectManifestsInDir(dir, root, warnings) {
682
682
  }
683
683
  for (const fname of dirEntries) {
684
684
  if (!matchManifestPattern2(fname, entry.pattern)) continue;
685
- const path = join(dir, fname);
686
- if (!existsSync3(path)) continue;
685
+ const path = join2(dir, fname);
686
+ if (!existsSync4(path)) continue;
687
687
  const m = entry.parse(path, dir, root, warnings);
688
688
  if (m !== null) out.push(m);
689
689
  }
@@ -693,7 +693,7 @@ function detectManifestsInDir(dir, root, warnings) {
693
693
  }
694
694
  function listSubdirs(dir) {
695
695
  try {
696
- return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join(dir, e.name));
696
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join2(dir, e.name));
697
697
  } catch {
698
698
  return [];
699
699
  }
@@ -703,8 +703,8 @@ function detectPackageManifests(projectRoot) {
703
703
  const manifests = [];
704
704
  manifests.push(...detectManifestsInDir(projectRoot, projectRoot, warnings));
705
705
  for (const ws of WORKSPACE_DIRS) {
706
- const wsRoot = join(projectRoot, ws);
707
- if (!existsSync3(wsRoot)) continue;
706
+ const wsRoot = join2(projectRoot, ws);
707
+ if (!existsSync4(wsRoot)) continue;
708
708
  for (const sub of listSubdirs(wsRoot)) {
709
709
  manifests.push(...detectManifestsInDir(sub, projectRoot, warnings));
710
710
  for (const sub2 of listSubdirs(sub)) {
@@ -6890,38 +6890,38 @@ var init_source_dir_detector = __esm({
6890
6890
  });
6891
6891
 
6892
6892
  // src/detect/monorepo-detector.ts
6893
- import { readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync2, lstatSync as lstatSync2, readdirSync as readdirSync2 } from "fs";
6894
- import { join as join2, relative as relative2 } from "path";
6893
+ import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync2, lstatSync as lstatSync2, readdirSync as readdirSync2 } from "fs";
6894
+ import { join as join3, relative as relative2 } from "path";
6895
6895
  import { parse as parseYaml2 } from "yaml";
6896
6896
  import { parse as parseToml2 } from "smol-toml";
6897
6897
  function safeReadText(path) {
6898
6898
  try {
6899
- if (!existsSync4(path)) return null;
6899
+ if (!existsSync5(path)) return null;
6900
6900
  const ls = lstatSync2(path);
6901
6901
  if (ls.isSymbolicLink()) return null;
6902
6902
  const st = statSync2(path);
6903
6903
  if (!st.isFile()) return null;
6904
- return readFileSync3(path, "utf-8");
6904
+ return readFileSync4(path, "utf-8");
6905
6905
  } catch {
6906
6906
  return null;
6907
6907
  }
6908
6908
  }
6909
6909
  function firstManifestIn(dir) {
6910
6910
  for (const m of MANIFEST_PRIORITY) {
6911
- if (existsSync4(join2(dir, m))) return m;
6911
+ if (existsSync5(join3(dir, m))) return m;
6912
6912
  }
6913
6913
  return null;
6914
6914
  }
6915
6915
  function manifestName(dir, manifest) {
6916
6916
  try {
6917
6917
  if (manifest === "package.json") {
6918
- const raw = safeReadText(join2(dir, "package.json"));
6918
+ const raw = safeReadText(join3(dir, "package.json"));
6919
6919
  if (!raw) return null;
6920
6920
  const pkg = JSON.parse(raw);
6921
6921
  return typeof pkg.name === "string" ? pkg.name : null;
6922
6922
  }
6923
6923
  if (manifest === "pyproject.toml") {
6924
- const raw = safeReadText(join2(dir, "pyproject.toml"));
6924
+ const raw = safeReadText(join3(dir, "pyproject.toml"));
6925
6925
  if (!raw) return null;
6926
6926
  const toml = parseToml2(raw);
6927
6927
  const project = toml.project;
@@ -6932,7 +6932,7 @@ function manifestName(dir, manifest) {
6932
6932
  return null;
6933
6933
  }
6934
6934
  if (manifest === "Cargo.toml") {
6935
- const raw = safeReadText(join2(dir, "Cargo.toml"));
6935
+ const raw = safeReadText(join3(dir, "Cargo.toml"));
6936
6936
  if (!raw) return null;
6937
6937
  const toml = parseToml2(raw);
6938
6938
  const pkg = toml.package;
@@ -6940,7 +6940,7 @@ function manifestName(dir, manifest) {
6940
6940
  return null;
6941
6941
  }
6942
6942
  if (manifest === "go.mod") {
6943
- const raw = safeReadText(join2(dir, "go.mod"));
6943
+ const raw = safeReadText(join3(dir, "go.mod"));
6944
6944
  if (!raw) return null;
6945
6945
  for (const line of raw.split(/\r?\n/)) {
6946
6946
  const trimmed = line.trim();
@@ -6964,7 +6964,7 @@ function pkgFromDir(root, dir) {
6964
6964
  }
6965
6965
  function listSubdirs2(dir) {
6966
6966
  try {
6967
- return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join2(dir, e.name));
6967
+ return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join3(dir, e.name));
6968
6968
  } catch {
6969
6969
  return [];
6970
6970
  }
@@ -6972,8 +6972,8 @@ function listSubdirs2(dir) {
6972
6972
  function genericWorkspaces(root) {
6973
6973
  const out = [];
6974
6974
  for (const parent of CONVENTIONAL_WORKSPACE_PARENTS) {
6975
- const p = join2(root, parent);
6976
- if (!existsSync4(p)) continue;
6975
+ const p = join3(root, parent);
6976
+ if (!existsSync5(p)) continue;
6977
6977
  for (const sub of listSubdirs2(p)) {
6978
6978
  const pkg = pkgFromDir(root, sub);
6979
6979
  if (pkg) out.push(pkg);
@@ -6982,7 +6982,7 @@ function genericWorkspaces(root) {
6982
6982
  return out;
6983
6983
  }
6984
6984
  function detectYarnWorkspaces(root) {
6985
- const raw = safeReadText(join2(root, "package.json"));
6985
+ const raw = safeReadText(join3(root, "package.json"));
6986
6986
  if (!raw) return null;
6987
6987
  let pkg;
6988
6988
  try {
@@ -6997,7 +6997,7 @@ function detectYarnWorkspaces(root) {
6997
6997
  return expandWorkspaceGlobs(root, globs);
6998
6998
  }
6999
6999
  function detectPnpmWorkspaces(root) {
7000
- const raw = safeReadText(join2(root, "pnpm-workspace.yaml"));
7000
+ const raw = safeReadText(join3(root, "pnpm-workspace.yaml"));
7001
7001
  if (!raw) return null;
7002
7002
  try {
7003
7003
  const parsed = parseYaml2(raw);
@@ -7013,8 +7013,8 @@ function expandWorkspaceGlobs(root, globs) {
7013
7013
  for (const pattern of globs) {
7014
7014
  const parts = pattern.split("/");
7015
7015
  if (parts.length === 2 && (parts[1] === "*" || parts[1] === "**")) {
7016
- const parent = join2(root, parts[0]);
7017
- if (!existsSync4(parent)) continue;
7016
+ const parent = join3(root, parts[0]);
7017
+ if (!existsSync5(parent)) continue;
7018
7018
  for (const sub of listSubdirs2(parent)) {
7019
7019
  const pkg = pkgFromDir(root, sub);
7020
7020
  if (pkg && !seen.has(pkg.path)) {
@@ -7024,8 +7024,8 @@ function expandWorkspaceGlobs(root, globs) {
7024
7024
  }
7025
7025
  continue;
7026
7026
  }
7027
- const direct = join2(root, pattern);
7028
- if (existsSync4(direct)) {
7027
+ const direct = join3(root, pattern);
7028
+ if (existsSync5(direct)) {
7029
7029
  const pkg = pkgFromDir(root, direct);
7030
7030
  if (pkg && !seen.has(pkg.path)) {
7031
7031
  seen.add(pkg.path);
@@ -7036,16 +7036,16 @@ function expandWorkspaceGlobs(root, globs) {
7036
7036
  return out;
7037
7037
  }
7038
7038
  function hasTurbo(root) {
7039
- return existsSync4(join2(root, "turbo.json"));
7039
+ return existsSync5(join3(root, "turbo.json"));
7040
7040
  }
7041
7041
  function hasNx(root) {
7042
- return existsSync4(join2(root, "nx.json"));
7042
+ return existsSync5(join3(root, "nx.json"));
7043
7043
  }
7044
7044
  function hasLerna(root) {
7045
- return existsSync4(join2(root, "lerna.json"));
7045
+ return existsSync5(join3(root, "lerna.json"));
7046
7046
  }
7047
7047
  function hasBazel(root) {
7048
- return existsSync4(join2(root, "WORKSPACE")) || existsSync4(join2(root, "WORKSPACE.bazel")) || existsSync4(join2(root, "MODULE.bazel"));
7048
+ return existsSync5(join3(root, "WORKSPACE")) || existsSync5(join3(root, "WORKSPACE.bazel")) || existsSync5(join3(root, "MODULE.bazel"));
7049
7049
  }
7050
7050
  function detectMonorepo(projectRoot) {
7051
7051
  const nested = [];
@@ -7236,8 +7236,8 @@ var init_vr_command_map = __esm({
7236
7236
  });
7237
7237
 
7238
7238
  // src/detect/domain-inferrer.ts
7239
- import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
7240
- import { join as join3 } from "path";
7239
+ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
7240
+ import { join as join4 } from "path";
7241
7241
  function titleCase(s) {
7242
7242
  if (!s) return s;
7243
7243
  return s.split(/[-_\s]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
@@ -7257,8 +7257,8 @@ function topLevelSrcSubdirs(root, sourceDirs) {
7257
7257
  const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
7258
7258
  const seen = /* @__PURE__ */ new Set();
7259
7259
  for (const rel of effective) {
7260
- const abs = join3(root, rel);
7261
- if (!existsSync5(abs)) continue;
7260
+ const abs = join4(root, rel);
7261
+ if (!existsSync6(abs)) continue;
7262
7262
  try {
7263
7263
  for (const e of readdirSync3(abs, { withFileTypes: true })) {
7264
7264
  if (!e.isDirectory()) continue;
@@ -7345,8 +7345,8 @@ var init_domain_inferrer = __esm({
7345
7345
  });
7346
7346
 
7347
7347
  // src/detect/regex-fallback.ts
7348
- import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
7349
- import { resolve as resolve4, join as join4, basename as basename2 } from "path";
7348
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
7349
+ import { resolve as resolve4, join as join5, basename as basename2 } from "path";
7350
7350
  function introspectPython(detection, projectRoot) {
7351
7351
  const sourceDir = resolveSourceDir(detection, "python", projectRoot);
7352
7352
  if (!sourceDir) return null;
@@ -7513,9 +7513,9 @@ function resolveSourceDir(detection, lang, projectRoot) {
7513
7513
  if (list.length > 0) {
7514
7514
  const first = list[0];
7515
7515
  const abs = resolve4(projectRoot, first);
7516
- return existsSync6(abs) ? abs : null;
7516
+ return existsSync7(abs) ? abs : null;
7517
7517
  }
7518
- return existsSync6(projectRoot) ? projectRoot : null;
7518
+ return existsSync7(projectRoot) ? projectRoot : null;
7519
7519
  }
7520
7520
  function sampleFiles(dir, nameRegex, pathFilter) {
7521
7521
  const out = [];
@@ -7535,7 +7535,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
7535
7535
  if (entry === "__pycache__") continue;
7536
7536
  if (entry === "venv" || entry === ".venv") continue;
7537
7537
  if (entry === "dist" || entry === "build") continue;
7538
- const child = join4(path, entry);
7538
+ const child = join5(path, entry);
7539
7539
  let st;
7540
7540
  try {
7541
7541
  st = statSync3(child);
@@ -7560,7 +7560,7 @@ function readSafe(path) {
7560
7560
  try {
7561
7561
  const st = statSync3(path);
7562
7562
  if (st.size > MAX_FILE_BYTES) return null;
7563
- return readFileSync4(path, "utf-8");
7563
+ return readFileSync5(path, "utf-8");
7564
7564
  } catch {
7565
7565
  return null;
7566
7566
  }
@@ -7809,7 +7809,7 @@ __export(drift_exports, {
7809
7809
  computeFingerprint: () => computeFingerprint,
7810
7810
  detectDrift: () => detectDrift
7811
7811
  });
7812
- import { createHash } from "crypto";
7812
+ import { createHash as createHash2 } from "crypto";
7813
7813
  function summarizeDetection(det) {
7814
7814
  const languages = Array.from(new Set(det.manifests.map((m) => m.language))).sort();
7815
7815
  const frameworks = {};
@@ -7840,7 +7840,7 @@ function summarizeDetection(det) {
7840
7840
  function computeFingerprint(det) {
7841
7841
  const data = summarizeDetection(det);
7842
7842
  const stable = JSON.stringify(data, Object.keys(data).sort());
7843
- return createHash("sha256").update(stable).digest("hex");
7843
+ return createHash2("sha256").update(stable).digest("hex");
7844
7844
  }
7845
7845
  function stringOf(v) {
7846
7846
  if (typeof v === "string") return v;
@@ -8988,6 +8988,45 @@ function initMemorySchema(db) {
8988
8988
  CREATE INDEX IF NOT EXISTS idx_so_file ON shared_observations(file_path);
8989
8989
  CREATE INDEX IF NOT EXISTS idx_so_module ON shared_observations(module);
8990
8990
  `);
8991
+ db.exec(`
8992
+ CREATE TABLE IF NOT EXISTS team_promotion_outbound (
8993
+ prompt_hash TEXT PRIMARY KEY,
8994
+ destination TEXT NOT NULL,
8995
+ draft_text TEXT NOT NULL,
8996
+ score REAL,
8997
+ signals_json TEXT NOT NULL DEFAULT '[]',
8998
+ content_hash TEXT NOT NULL,
8999
+ -- PA3-004 (Phase 3 Stream A): hardened-destination publish carries the
9000
+ -- publisher's review attestation so the server CHECK (hardened rows need a
9001
+ -- review_attestation) is satisfiable. hardened=0 for the Phase-2 rows.
9002
+ hardened INTEGER NOT NULL DEFAULT 0,
9003
+ review_attestation_json TEXT,
9004
+ created_at TEXT DEFAULT (datetime('now'))
9005
+ );
9006
+ CREATE TABLE IF NOT EXISTS team_revocation_outbound (
9007
+ prompt_hash TEXT PRIMARY KEY,
9008
+ created_at TEXT DEFAULT (datetime('now'))
9009
+ );
9010
+ `);
9011
+ db.exec(`
9012
+ CREATE TABLE IF NOT EXISTS rule_promotion_events_outbound (
9013
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9014
+ prompt_hash TEXT NOT NULL,
9015
+ event_type TEXT NOT NULL,
9016
+ metadata_json TEXT NOT NULL DEFAULT '{}',
9017
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
9018
+ );
9019
+ CREATE INDEX IF NOT EXISTS idx_rpe_outbound_created ON rule_promotion_events_outbound(created_at);
9020
+ `);
9021
+ db.exec(`
9022
+ CREATE TABLE IF NOT EXISTS analytics_events (
9023
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9024
+ event_type TEXT NOT NULL,
9025
+ event_data TEXT NOT NULL DEFAULT '{}',
9026
+ created_at TEXT DEFAULT (datetime('now'))
9027
+ );
9028
+ CREATE INDEX IF NOT EXISTS idx_analytics_events_type ON analytics_events(event_type);
9029
+ `);
8991
9030
  db.exec(`
8992
9031
  CREATE TABLE IF NOT EXISTS knowledge_conflicts (
8993
9032
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -9068,6 +9107,13 @@ function initMemorySchema(db) {
9068
9107
  `ALTER TABLE license_cache ADD COLUMN signed_payload_json TEXT NOT NULL DEFAULT ''`
9069
9108
  );
9070
9109
  }
9110
+ const outboundCols = db.prepare(`PRAGMA table_info(team_promotion_outbound)`).all();
9111
+ if (!outboundCols.some((c) => c.name === "hardened")) {
9112
+ db.exec(`ALTER TABLE team_promotion_outbound ADD COLUMN hardened INTEGER NOT NULL DEFAULT 0`);
9113
+ }
9114
+ if (!outboundCols.some((c) => c.name === "review_attestation_json")) {
9115
+ db.exec(`ALTER TABLE team_promotion_outbound ADD COLUMN review_attestation_json TEXT`);
9116
+ }
9071
9117
  db.exec(`
9072
9118
  CREATE TABLE IF NOT EXISTS failure_classes (
9073
9119
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -9088,6 +9134,22 @@ function initMemorySchema(db) {
9088
9134
  CREATE INDEX IF NOT EXISTS idx_fc_needs_review ON failure_classes(needs_review);
9089
9135
  `);
9090
9136
  }
9137
+ function getMemoryMeta(db, key) {
9138
+ const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
9139
+ return row ? row.value : null;
9140
+ }
9141
+ function setMemoryMeta(db, key, value) {
9142
+ db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES (?, ?)").run(key, value);
9143
+ }
9144
+ function recordTelemetry(db, eventType, data) {
9145
+ try {
9146
+ db.prepare(`
9147
+ INSERT INTO analytics_events (event_type, event_data, created_at)
9148
+ VALUES (?, ?, datetime('now'))
9149
+ `).run(eventType, JSON.stringify(data));
9150
+ } catch {
9151
+ }
9152
+ }
9091
9153
  function autoDetectTaskId(planFile) {
9092
9154
  if (!planFile) return null;
9093
9155
  const base = basename(planFile);
@@ -9165,9 +9227,499 @@ function linkSessionToTask(db, sessionId, taskId) {
9165
9227
  db.prepare("UPDATE sessions SET task_id = ? WHERE session_id = ?").run(taskId, sessionId);
9166
9228
  }
9167
9229
 
9230
+ // src/team-rule-sync.ts
9231
+ import { existsSync as existsSync3, writeFileSync, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
9232
+ import { join, dirname as dirname3 } from "path";
9233
+
9234
+ // src/license.ts
9235
+ import { createHash } from "crypto";
9236
+
9237
+ // src/security/ed25519-envelope-verifier.ts
9238
+ import { createPublicKey, verify as cryptoVerify } from "crypto";
9239
+ var SPKI_ED25519_PREFIX = Buffer.from([
9240
+ 48,
9241
+ 42,
9242
+ 48,
9243
+ 5,
9244
+ 6,
9245
+ 3,
9246
+ 43,
9247
+ 101,
9248
+ 112,
9249
+ 3,
9250
+ 33,
9251
+ 0
9252
+ ]);
9253
+ function verifyEd25519SignedEnvelope(key, payload) {
9254
+ if (!key.knownFingerprints.has(key.fingerprintHex)) {
9255
+ return {
9256
+ kind: "error",
9257
+ reason: `Bundled ${key.keyLabel} pubkey fingerprint ${key.fingerprintHex} is not in the trusted allowlist. Possible build-time tamper.`
9258
+ };
9259
+ }
9260
+ const sig = payload._signature;
9261
+ const alg = payload._signature_alg;
9262
+ const payloadKeys = payload._signature_payload_keys;
9263
+ const sigPubkey = payload._signature_pubkey_fingerprint;
9264
+ if (typeof sig !== "string" || sig.length === 0) {
9265
+ return { kind: "missing_signature" };
9266
+ }
9267
+ if (alg !== "ed25519") {
9268
+ return { kind: "error", reason: `Unsupported signature algorithm: ${alg}` };
9269
+ }
9270
+ if (!Array.isArray(payloadKeys) || payloadKeys.length === 0) {
9271
+ return { kind: "error", reason: "Missing _signature_payload_keys" };
9272
+ }
9273
+ if (typeof sigPubkey === "string" && sigPubkey !== key.fingerprintHex) {
9274
+ return { kind: "unknown_pubkey", got: sigPubkey };
9275
+ }
9276
+ const canonicalObj = {};
9277
+ for (const k of payloadKeys) {
9278
+ if (typeof k !== "string") continue;
9279
+ canonicalObj[k] = payload[k];
9280
+ }
9281
+ const canonical = JSON.stringify(canonicalObj, [...payloadKeys].sort());
9282
+ try {
9283
+ const der = Buffer.concat([SPKI_ED25519_PREFIX, Buffer.from(key.pubkeyBytes)]);
9284
+ const pubkey = createPublicKey({ key: der, format: "der", type: "spki" });
9285
+ const ok = cryptoVerify(
9286
+ null,
9287
+ Buffer.from(canonical, "utf-8"),
9288
+ pubkey,
9289
+ Buffer.from(sig, "base64")
9290
+ );
9291
+ return ok ? { kind: "valid" } : { kind: "bad_signature" };
9292
+ } catch (err) {
9293
+ return {
9294
+ kind: "error",
9295
+ reason: `Signature verification threw: ${err instanceof Error ? err.message : String(err)}`
9296
+ };
9297
+ }
9298
+ }
9299
+
9300
+ // src/security/license-pubkey.generated.ts
9301
+ var LICENSE_PUBKEY_ED25519 = new Uint8Array([39, 136, 108, 146, 85, 233, 119, 252, 223, 226, 123, 155, 234, 168, 200, 150, 36, 249, 174, 6, 130, 146, 125, 196, 136, 224, 202, 150, 53, 228, 114, 15]);
9302
+ var LICENSE_PUBKEY_FINGERPRINT_HEX = "18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475";
9303
+ var KNOWN_LICENSE_PUBKEY_FINGERPRINTS = /* @__PURE__ */ new Set([
9304
+ "18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475"
9305
+ ]);
9306
+
9307
+ // src/security/license-response-verifier.ts
9308
+ function verifyLicenseResponse(payload) {
9309
+ return verifyEd25519SignedEnvelope(
9310
+ {
9311
+ pubkeyBytes: LICENSE_PUBKEY_ED25519,
9312
+ fingerprintHex: LICENSE_PUBKEY_FINGERPRINT_HEX,
9313
+ knownFingerprints: KNOWN_LICENSE_PUBKEY_FINGERPRINTS,
9314
+ keyLabel: "license"
9315
+ },
9316
+ payload
9317
+ );
9318
+ }
9319
+ function isLicenseSignatureRequired() {
9320
+ return process.env.MASSU_REQUIRE_SIGNED_LICENSE === "true";
9321
+ }
9322
+
9323
+ // src/license.ts
9324
+ var _warnedLicenseSig = false;
9325
+ function warnLicenseSigOnce(reason) {
9326
+ if (_warnedLicenseSig) return;
9327
+ _warnedLicenseSig = true;
9328
+ process.stderr.write(
9329
+ `[massu] WARNING: license-validate response is unsigned or signature invalid (${reason}). Acceptance permitted under transition mode. Operator: provision Supabase Edge Function LICENSE_RESPONSE_SIGNING_PRIVATE_KEY_B64 then set MASSU_REQUIRE_SIGNED_LICENSE=true to enforce strict mode.
9330
+ `
9331
+ );
9332
+ }
9333
+ var TIER_LEVELS = {
9334
+ free: 0,
9335
+ pro: 1,
9336
+ team: 2,
9337
+ enterprise: 3
9338
+ };
9339
+ function tierLevel(tier) {
9340
+ return TIER_LEVELS[tier] ?? 0;
9341
+ }
9342
+ var PLAN_TO_TIER_MAP = {
9343
+ free: "free",
9344
+ cloud_pro: "pro",
9345
+ cloud_team: "team",
9346
+ cloud_enterprise: "enterprise"
9347
+ };
9348
+ var IN_MEMORY_CACHE_TTL_MS = 15 * 60 * 1e3;
9349
+ var GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
9350
+ function readTrustedCache(cached) {
9351
+ if (!cached.signed_payload_json) {
9352
+ if (isLicenseSignatureRequired()) return null;
9353
+ warnLicenseSigOnce("cache_unsigned_transition");
9354
+ return {
9355
+ tier: cached.tier,
9356
+ validUntil: cached.valid_until,
9357
+ features: JSON.parse(cached.features || "[]")
9358
+ };
9359
+ }
9360
+ let parsed;
9361
+ try {
9362
+ parsed = JSON.parse(cached.signed_payload_json);
9363
+ } catch {
9364
+ return null;
9365
+ }
9366
+ const result = verifyLicenseResponse(parsed);
9367
+ if (result.kind !== "valid") return null;
9368
+ const verifiedPlan = typeof parsed.plan === "string" ? parsed.plan : null;
9369
+ const verifiedTierField = typeof parsed.tier === "string" ? parsed.tier : null;
9370
+ const tier = verifiedPlan ? PLAN_TO_TIER_MAP[verifiedPlan] ?? "free" : verifiedTierField ?? "free";
9371
+ const validUntil = typeof parsed.validUntil === "string" ? parsed.validUntil : "";
9372
+ const features = Array.isArray(parsed.features) ? parsed.features : [];
9373
+ const orgId = typeof parsed.orgId === "string" && parsed.orgId.length > 0 ? parsed.orgId : void 0;
9374
+ return { tier, validUntil, features, orgId };
9375
+ }
9376
+ function getCachedTierReadOnly(memDb) {
9377
+ const config = getConfig();
9378
+ const apiKey = config.cloud?.apiKey;
9379
+ if (!apiKey) return "free";
9380
+ const ownsDb = !memDb;
9381
+ const db = memDb ?? getMemoryDb();
9382
+ try {
9383
+ const keyHash = createHash("sha256").update(apiKey).digest("hex");
9384
+ const cached = db.prepare(
9385
+ "SELECT tier, valid_until, last_validated, features, signed_payload_json FROM license_cache WHERE api_key_hash = ?"
9386
+ ).get(keyHash);
9387
+ if (!cached) return "free";
9388
+ const trusted = readTrustedCache(cached);
9389
+ if (!trusted) return "free";
9390
+ const lastValidated = new Date(cached.last_validated);
9391
+ const sevenDaysAgo = new Date(Date.now() - GRACE_PERIOD_MS);
9392
+ if (!(lastValidated > sevenDaysAgo)) return "free";
9393
+ return trusted.tier;
9394
+ } catch {
9395
+ return "free";
9396
+ } finally {
9397
+ if (ownsDb) {
9398
+ try {
9399
+ db.close();
9400
+ } catch {
9401
+ }
9402
+ }
9403
+ }
9404
+ }
9405
+ function getCachedOrgId(memDb) {
9406
+ const config = getConfig();
9407
+ const apiKey = config.cloud?.apiKey;
9408
+ if (!apiKey) return null;
9409
+ const ownsDb = !memDb;
9410
+ const db = memDb ?? getMemoryDb();
9411
+ try {
9412
+ const keyHash = createHash("sha256").update(apiKey).digest("hex");
9413
+ const cached = db.prepare(
9414
+ "SELECT tier, valid_until, last_validated, features, signed_payload_json FROM license_cache WHERE api_key_hash = ?"
9415
+ ).get(keyHash);
9416
+ if (!cached) return null;
9417
+ const trusted = readTrustedCache(cached);
9418
+ if (!trusted) return null;
9419
+ const lastValidated = new Date(cached.last_validated);
9420
+ const sevenDaysAgo = new Date(Date.now() - GRACE_PERIOD_MS);
9421
+ if (!(lastValidated > sevenDaysAgo)) return null;
9422
+ return trusted.orgId ?? null;
9423
+ } catch {
9424
+ return null;
9425
+ } finally {
9426
+ if (ownsDb) {
9427
+ try {
9428
+ db.close();
9429
+ } catch {
9430
+ }
9431
+ }
9432
+ }
9433
+ }
9434
+
9435
+ // src/auto-learning-entitlement.ts
9436
+ var TEAM_SHARED_PROMOTION_MIN_TIER = "team";
9437
+ function entitledForTeamSharedPromotion(tier) {
9438
+ return tierLevel(tier) >= tierLevel(TEAM_SHARED_PROMOTION_MIN_TIER);
9439
+ }
9440
+
9441
+ // src/security/promotion-pubkey.generated.ts
9442
+ var PROMOTION_PUBKEY_ED25519 = new Uint8Array([107, 161, 33, 17, 189, 44, 193, 128, 252, 155, 188, 236, 100, 163, 23, 146, 219, 155, 216, 139, 134, 72, 211, 182, 151, 122, 209, 151, 135, 65, 167, 26]);
9443
+ var PROMOTION_PUBKEY_FINGERPRINT_HEX = "b14e2a73e23c02891e976ec161d339da6c930266c0202828d3187a3bd6e5d83f";
9444
+ var KNOWN_PROMOTION_PUBKEY_FINGERPRINTS = /* @__PURE__ */ new Set([
9445
+ "b14e2a73e23c02891e976ec161d339da6c930266c0202828d3187a3bd6e5d83f"
9446
+ ]);
9447
+
9448
+ // src/security/promotion-envelope-verifier.ts
9449
+ function verifyPromotionEnvelope(payload) {
9450
+ return verifyEd25519SignedEnvelope(
9451
+ {
9452
+ pubkeyBytes: PROMOTION_PUBKEY_ED25519,
9453
+ fingerprintHex: PROMOTION_PUBKEY_FINGERPRINT_HEX,
9454
+ knownFingerprints: KNOWN_PROMOTION_PUBKEY_FINGERPRINTS,
9455
+ keyLabel: "promotion"
9456
+ },
9457
+ payload
9458
+ );
9459
+ }
9460
+
9461
+ // src/rule-candidate-hardened.ts
9462
+ var TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
9463
+ "pattern-scanner",
9464
+ "custom-destination"
9465
+ ];
9466
+ function isHardenedShareableDestination(destination) {
9467
+ return TEAM_HARDENED_SHAREABLE_DESTINATIONS.includes(destination);
9468
+ }
9469
+
9470
+ // src/rule-candidate-applier.ts
9471
+ var TEAM_SHAREABLE_DESTINATIONS = [
9472
+ "corrections-md",
9473
+ "claude-md-cr"
9474
+ ];
9475
+ function isTeamShareableDestination(destination) {
9476
+ return TEAM_SHAREABLE_DESTINATIONS.includes(destination);
9477
+ }
9478
+
9479
+ // src/team-knowledge.ts
9480
+ function shareObservation(db, developerId, project, observationType, summary, opts) {
9481
+ const result = db.prepare(`
9482
+ INSERT INTO shared_observations
9483
+ (original_id, developer_id, project, observation_type, summary, file_path, module, severity, is_shared, shared_at)
9484
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, TRUE, datetime('now'))
9485
+ `).run(
9486
+ opts?.originalId ?? null,
9487
+ developerId,
9488
+ project,
9489
+ observationType,
9490
+ summary,
9491
+ opts?.filePath ?? null,
9492
+ opts?.module ?? null,
9493
+ opts?.severity ?? 3
9494
+ );
9495
+ return Number(result.lastInsertRowid);
9496
+ }
9497
+
9498
+ // src/team-rule-sync.ts
9499
+ var CURSOR_KEY = "team_promotions_cursor";
9500
+ var DEFAULT_TIMEOUT_MS = 2e3;
9501
+ var PROMPT_HASH_RE = /^[0-9a-f]{16}$/;
9502
+ var ZERO = {
9503
+ pulled: 0,
9504
+ materialized: 0,
9505
+ skipped: 0,
9506
+ dropped_unverified: 0,
9507
+ dropped_nonshareable: 0,
9508
+ revoked_handled: 0
9509
+ };
9510
+ async function pullTeamPromotions(db, opts = {}) {
9511
+ const config = getConfig();
9512
+ const cloud = config.cloud;
9513
+ const projectRoot = opts.projectRoot ?? getProjectRoot();
9514
+ const tier = opts.tier ?? getCachedTierReadOnly(db);
9515
+ if (!entitledForTeamSharedPromotion(tier)) return { ...ZERO };
9516
+ const endpoint = opts.endpoint ?? cloud?.endpoint;
9517
+ const apiKey = opts.apiKey ?? cloud?.apiKey;
9518
+ if (!endpoint || !apiKey) return { ...ZERO };
9519
+ const ownOrgId = opts.orgId !== void 0 ? opts.orgId : getCachedOrgId(db);
9520
+ const since = parseCursor(getMemoryMeta(db, CURSOR_KEY));
9521
+ const fetchImpl = opts.fetchImpl ?? fetch;
9522
+ const timeoutMs = opts.timeoutMs ?? cloud?.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
9523
+ let envelope;
9524
+ try {
9525
+ const res = await fetchImpl(`${endpoint}/promoted-rules?since=${since}`, {
9526
+ method: "GET",
9527
+ headers: { Authorization: `Bearer ${apiKey}` },
9528
+ signal: AbortSignal.timeout(timeoutMs)
9529
+ });
9530
+ if (!res.ok) return { ...ZERO };
9531
+ envelope = await res.json();
9532
+ } catch {
9533
+ return { ...ZERO };
9534
+ }
9535
+ const verdict = verifyPromotionEnvelope(envelope);
9536
+ if (verdict.kind !== "valid") {
9537
+ const result2 = { ...ZERO, dropped_unverified: countUntrusted(envelope) };
9538
+ emitDropTelemetry(db, "team_promotion_envelope_dropped", { reason: verdict.kind });
9539
+ return result2;
9540
+ }
9541
+ const signedKeys = Array.isArray(envelope._signature_payload_keys) ? envelope._signature_payload_keys : [];
9542
+ if (!signedKeys.includes("orgId") || !signedKeys.includes("promotions_json")) {
9543
+ const result2 = { ...ZERO, dropped_unverified: countUntrusted(envelope) };
9544
+ emitDropTelemetry(db, "team_promotion_unsigned_field", {
9545
+ orgId_signed: signedKeys.includes("orgId"),
9546
+ promotions_json_signed: signedKeys.includes("promotions_json")
9547
+ });
9548
+ return result2;
9549
+ }
9550
+ const signedOrgId = typeof envelope.orgId === "string" ? envelope.orgId : null;
9551
+ if (!signedOrgId || !ownOrgId || signedOrgId !== ownOrgId) {
9552
+ const result2 = { ...ZERO, dropped_unverified: countUntrusted(envelope) };
9553
+ emitDropTelemetry(db, "team_promotion_org_mismatch", {
9554
+ signed_org_present: !!signedOrgId,
9555
+ own_org_present: !!ownOrgId
9556
+ });
9557
+ return result2;
9558
+ }
9559
+ const promotions = parsePromotions(envelope.promotions_json);
9560
+ const result = { ...ZERO };
9561
+ let maxSeq = since;
9562
+ for (const p of promotions) {
9563
+ if (!isValidWirePromotion(p)) continue;
9564
+ result.pulled += 1;
9565
+ if (typeof p.seq === "number" && p.seq > maxSeq) maxSeq = p.seq;
9566
+ const hardenedMaterialize = isHardenedShareableDestination(p.destination) && p.hardened === true;
9567
+ if (!isTeamShareableDestination(p.destination) && !hardenedMaterialize) {
9568
+ result.dropped_nonshareable += 1;
9569
+ continue;
9570
+ }
9571
+ const candidatePath = sidecarPath(projectRoot, p.prompt_hash);
9572
+ if (p.revoked_at) {
9573
+ handleRevocation(db, projectRoot, candidatePath, p.prompt_hash);
9574
+ result.revoked_handled += 1;
9575
+ continue;
9576
+ }
9577
+ if (existsSync3(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
9578
+ result.skipped += 1;
9579
+ continue;
9580
+ }
9581
+ materializeCandidate(db, projectRoot, candidatePath, p, signedOrgId);
9582
+ result.materialized += 1;
9583
+ }
9584
+ const serverCursor = typeof envelope.cursor === "number" ? envelope.cursor : 0;
9585
+ const nextCursor = Math.max(since, maxSeq, serverCursor);
9586
+ if (nextCursor > since) setMemoryMeta(db, CURSOR_KEY, String(nextCursor));
9587
+ return result;
9588
+ }
9589
+ function parseCursor(raw) {
9590
+ if (raw === null) return 0;
9591
+ const n = Number(raw);
9592
+ return Number.isInteger(n) && n >= 0 ? n : 0;
9593
+ }
9594
+ function parsePromotions(json) {
9595
+ if (typeof json !== "string") return [];
9596
+ try {
9597
+ const v = JSON.parse(json);
9598
+ return Array.isArray(v) ? v : [];
9599
+ } catch {
9600
+ return [];
9601
+ }
9602
+ }
9603
+ function countUntrusted(envelope) {
9604
+ const arr = parsePromotions(envelope.promotions_json);
9605
+ return arr.length > 0 ? arr.length : 1;
9606
+ }
9607
+ function isValidWirePromotion(p) {
9608
+ if (!p || typeof p !== "object") return false;
9609
+ const r = p;
9610
+ return typeof r.prompt_hash === "string" && PROMPT_HASH_RE.test(r.prompt_hash) && typeof r.destination === "string" && typeof r.draft_text === "string" && typeof r.promoted_by === "string" && typeof r.promoted_at === "string";
9611
+ }
9612
+ function sidecarPath(projectRoot, promptHash) {
9613
+ return join(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
9614
+ }
9615
+ function alreadyApplied(db, promptHash) {
9616
+ try {
9617
+ const row = db.prepare(
9618
+ `SELECT 1 FROM audit_log WHERE event_type = 'rule_promoted'
9619
+ AND json_extract(metadata, '$.prompt_hash') = ? LIMIT 1`
9620
+ ).get(promptHash);
9621
+ return !!row;
9622
+ } catch {
9623
+ return false;
9624
+ }
9625
+ }
9626
+ function handleRevocation(db, projectRoot, candidatePath, promptHash) {
9627
+ if (existsSync3(candidatePath)) {
9628
+ try {
9629
+ unlinkSync(candidatePath);
9630
+ } catch {
9631
+ }
9632
+ return;
9633
+ }
9634
+ if (alreadyApplied(db, promptHash)) {
9635
+ process.stderr.write(
9636
+ `[massu] team rule ${promptHash} was revoked by your org \u2014 consider reverting it.
9637
+ `
9638
+ );
9639
+ }
9640
+ }
9641
+ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
9642
+ const promptText = p.draft_text.replace(/\n+/g, " ").slice(0, 200) || `team rule ${p.prompt_hash}`;
9643
+ const sidecar = {
9644
+ // Standard RuleCandidatePayload fields (so `/massu-rule approve` → readCandidate
9645
+ // → validateCandidatePayload passes), synthesized from the promotion.
9646
+ prompt: promptText,
9647
+ prompt_hash: p.prompt_hash,
9648
+ score: clampScore(p.score),
9649
+ signals: sanitizeSignals(p.signals),
9650
+ prior_turn_files: [],
9651
+ timestamp: p.promoted_at,
9652
+ session_id: `team:${p.promoted_by}`,
9653
+ // Provenance (PB-004): the applier's team-origin gate keys on this. PA3-005:
9654
+ // a hardened materialization sets `hardened: true` so the applier's hardened
9655
+ // apply-gate (PA3-004) engages. `review_attestation` is intentionally NOT
9656
+ // copied from the publisher here — the RECEIVER's `/massu-rule review` records
9657
+ // ITS OWN two-operator + render-only ack into provenance.review_attestation
9658
+ // before apply; until then the gate refuses (hardened-PENDING).
9659
+ provenance: {
9660
+ origin: "team",
9661
+ org_id: orgId,
9662
+ promoted_by: p.promoted_by,
9663
+ promoted_at: p.promoted_at,
9664
+ signature_verified: true,
9665
+ ...p.hardened === true ? { hardened: true } : {}
9666
+ },
9667
+ // Extra fields the `/massu-rule approve` flow reads to drive the apply (the
9668
+ // publisher already decided destination + body). validateCandidatePayload
9669
+ // ignores unknown keys. `publisher_review_attestation` is retained for the
9670
+ // receiver's review UI (display only — never the apply-gate authority).
9671
+ destination: p.destination,
9672
+ draft_text: p.draft_text,
9673
+ ...p.review_attestation !== void 0 ? { publisher_review_attestation: p.review_attestation } : {}
9674
+ };
9675
+ const dir = dirname3(candidatePath);
9676
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
9677
+ writeFileSync(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
9678
+ try {
9679
+ shareObservation(db, p.promoted_by, getProjectName(), "rule_promotion", promptText, {
9680
+ filePath: void 0,
9681
+ module: p.destination
9682
+ });
9683
+ } catch {
9684
+ }
9685
+ }
9686
+ function clampScore(score) {
9687
+ if (typeof score !== "number" || !Number.isFinite(score)) return 0;
9688
+ return Math.max(-200, Math.min(200, score));
9689
+ }
9690
+ function sanitizeSignals(signals) {
9691
+ if (!Array.isArray(signals)) return [];
9692
+ const out = [];
9693
+ for (const s of signals) {
9694
+ if (!s || typeof s !== "object") continue;
9695
+ const sig = s;
9696
+ out.push({
9697
+ name: typeof sig.name === "string" ? sig.name : "unknown",
9698
+ baseWeight: typeof sig.baseWeight === "number" ? sig.baseWeight : 0,
9699
+ applied: typeof sig.applied === "number" ? sig.applied : 0,
9700
+ ...typeof sig.evidence === "string" ? { evidence: sig.evidence } : {}
9701
+ });
9702
+ }
9703
+ return out;
9704
+ }
9705
+ function getProjectName() {
9706
+ try {
9707
+ return getConfig().project?.name ?? "massu";
9708
+ } catch {
9709
+ return "massu";
9710
+ }
9711
+ }
9712
+ function emitDropTelemetry(db, eventType, data) {
9713
+ recordTelemetry(db, eventType, data);
9714
+ process.stderr.write(
9715
+ `[massu] team-shared promotion pull: dropped envelope (${eventType}). A signed/org-matched response is required \u2014 see massu.ai for details.
9716
+ `
9717
+ );
9718
+ }
9719
+
9168
9720
  // src/hooks/session-start.ts
9169
- import { readFileSync as readFileSync5, existsSync as existsSync7 } from "fs";
9170
- import { join as join5, resolve as resolve5 } from "path";
9721
+ import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
9722
+ import { join as join6, resolve as resolve5 } from "path";
9171
9723
  import { parse as parseYaml3 } from "yaml";
9172
9724
 
9173
9725
  // src/lib/pidLiveness.ts
@@ -9246,6 +9798,10 @@ Session memory, code intelligence, and governance are now active.
9246
9798
  const watcherBanner = buildWatcherBanner();
9247
9799
  if (watcherBanner) process.stdout.write(watcherBanner);
9248
9800
  }
9801
+ try {
9802
+ await pullTeamPromotions(db);
9803
+ } catch (_pullErr) {
9804
+ }
9249
9805
  } finally {
9250
9806
  db.close();
9251
9807
  }
@@ -9335,7 +9891,7 @@ async function buildContext(db, sessionId, source, tokenBudget, taskId) {
9335
9891
  }
9336
9892
  try {
9337
9893
  const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
9338
- if (existsSync7(knowledgeDbPath)) {
9894
+ if (existsSync8(knowledgeDbPath)) {
9339
9895
  const Database2 = (await import("better-sqlite3")).default;
9340
9896
  const kdb = new Database2(knowledgeDbPath, { readonly: true });
9341
9897
  try {
@@ -9426,8 +9982,8 @@ async function buildDriftBanner() {
9426
9982
  if (process.env.MASSU_DRIFT_QUIET === "1") return "";
9427
9983
  if (watcherIsLiveAndFresh()) return "";
9428
9984
  const configPath = resolve5(process.cwd(), "massu.config.yaml");
9429
- if (!existsSync7(configPath)) return "";
9430
- const content = readFileSync5(configPath, "utf-8");
9985
+ if (!existsSync8(configPath)) return "";
9986
+ const content = readFileSync6(configPath, "utf-8");
9431
9987
  const parsed = parseYaml3(content);
9432
9988
  if (!parsed || typeof parsed !== "object") return "";
9433
9989
  const det = parsed.detection;
@@ -9467,9 +10023,9 @@ function loadCorrectionsPreventionRules() {
9467
10023
  const config = getConfig();
9468
10024
  const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
9469
10025
  const projectDirName = cwd.replace(/[/\\]/g, "-").replace(/^-/, "");
9470
- const correctionsPath = join5(homeDir, claudeDirName, "projects", projectDirName, "memory", "corrections.md");
9471
- if (!existsSync7(correctionsPath)) return [];
9472
- const content = readFileSync5(correctionsPath, "utf-8");
10026
+ const correctionsPath = join6(homeDir, claudeDirName, "projects", projectDirName, "memory", "corrections.md");
10027
+ if (!existsSync8(correctionsPath)) return [];
10028
+ const content = readFileSync6(correctionsPath, "utf-8");
9473
10029
  const lines = content.split("\n");
9474
10030
  const rules = [];
9475
10031
  for (const line of lines) {
@@ -9491,8 +10047,8 @@ function loadCorrectionsPreventionRules() {
9491
10047
  function readWatchStateRaw(cwd) {
9492
10048
  try {
9493
10049
  const path = resolve5(cwd, ".massu", "watch-state.json");
9494
- if (!existsSync7(path)) return null;
9495
- const obj = JSON.parse(readFileSync5(path, "utf-8"));
10050
+ if (!existsSync8(path)) return null;
10051
+ const obj = JSON.parse(readFileSync6(path, "utf-8"));
9496
10052
  if (!obj || typeof obj !== "object") return null;
9497
10053
  return obj;
9498
10054
  } catch {