@promptowl/contextnest-community 1.16.1 → 1.17.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.
@@ -993,7 +993,8 @@ import { v4 as uuid } from "uuid";
993
993
  import {
994
994
  NestStorage as NestStorage2,
995
995
  GraphQueryEngine,
996
- VersionManager
996
+ VersionManager,
997
+ DocumentNotFoundError
997
998
  } from "@promptowl/contextnest-engine";
998
999
  import { createEngineApi } from "@promptowl/contextnest-engine/api";
999
1000
 
@@ -1060,39 +1061,88 @@ var FlatNestStorage = class extends NestStorage {
1060
1061
 
1061
1062
  // src/nodes/engine.ts
1062
1063
  var DISCOVERY_TTL_MS = 3e4;
1063
- var DOCUMENT_WRITERS = [
1064
- "writeDocument",
1065
- "deleteDocument",
1066
- "writeVaultFile",
1067
- "init"
1068
- ];
1064
+ var isLive = (node) => node.frontmatter.status !== "rejected";
1065
+ var DERIVED_VAULT_FILES = /* @__PURE__ */ new Set([
1066
+ "INDEX.md",
1067
+ "CONTEXT.md",
1068
+ "CLAUDE.md",
1069
+ "GEMINI.md",
1070
+ "AGENTS.md",
1071
+ "README.md"
1072
+ ]);
1069
1073
  function withDiscoveryCache(storage) {
1070
1074
  const entries = /* @__PURE__ */ new Map();
1071
1075
  const discover = storage.discoverDocuments.bind(storage);
1076
+ const readDoc = storage.readDocument.bind(storage);
1077
+ const filtersRetired = !(storage instanceof FlatNestStorage);
1072
1078
  let generation = 0;
1073
1079
  storage.discoverDocuments = async (options = {}) => {
1074
1080
  const key = options.includeRetired || options.includeSuperseded ? "all" : "live";
1075
1081
  const hit = entries.get(key);
1076
1082
  if (hit && Date.now() - hit.ts < DISCOVERY_TTL_MS) return hit.docs;
1083
+ const all = entries.get("all");
1084
+ if (all && Date.now() - all.ts < DISCOVERY_TTL_MS) {
1085
+ const docs2 = filtersRetired ? all.docs.filter(isLive) : all.docs;
1086
+ entries.set("live", { docs: docs2, ts: all.ts });
1087
+ return docs2;
1088
+ }
1077
1089
  const startedAt = generation;
1078
- const docs = await discover(options);
1079
- if (generation === startedAt) entries.set(key, { docs, ts: Date.now() });
1090
+ const crawled = await discover({ ...options, includeRetired: true });
1091
+ const docs = key === "all" || !filtersRetired ? crawled : crawled.filter(isLive);
1092
+ if (generation === startedAt) {
1093
+ const ts = Date.now();
1094
+ entries.set("all", { docs: crawled, ts });
1095
+ entries.set(key, { docs, ts });
1096
+ }
1080
1097
  return docs;
1081
1098
  };
1082
1099
  const invalidate = () => {
1083
1100
  generation++;
1084
1101
  entries.clear();
1085
1102
  };
1086
- for (const name of DOCUMENT_WRITERS) {
1103
+ const patch = async (id) => {
1104
+ generation++;
1105
+ let node = null;
1106
+ try {
1107
+ node = await readDoc(id);
1108
+ } catch (err) {
1109
+ if (!(err instanceof DocumentNotFoundError)) {
1110
+ invalidate();
1111
+ return;
1112
+ }
1113
+ node = null;
1114
+ }
1115
+ for (const [key, entry] of entries) {
1116
+ const docs = entry.docs.filter((d) => d.id !== id);
1117
+ if (node && (key === "all" || isLive(node))) {
1118
+ const at = docs.findIndex((d) => d.id > id);
1119
+ docs.splice(at === -1 ? docs.length : at, 0, node);
1120
+ }
1121
+ entry.docs = docs;
1122
+ }
1123
+ };
1124
+ const wrap = (name, after) => {
1087
1125
  const write = storage[name].bind(storage);
1088
1126
  storage[name] = async (...args) => {
1089
1127
  try {
1090
1128
  return await write(...args);
1091
1129
  } finally {
1092
- invalidate();
1130
+ try {
1131
+ await after(args);
1132
+ } catch {
1133
+ invalidate();
1134
+ }
1093
1135
  }
1094
1136
  };
1095
- }
1137
+ };
1138
+ wrap("writeDocument", (args) => patch(String(args[0])));
1139
+ wrap("deleteDocument", (args) => patch(String(args[0])));
1140
+ wrap("init", () => invalidate());
1141
+ wrap("writeVaultFile", (args) => {
1142
+ const rel = String(args[0] ?? "");
1143
+ const base = rel.split(/[/\\]/).pop() || "";
1144
+ if (rel.endsWith(".md") && !DERIVED_VAULT_FILES.has(base)) invalidate();
1145
+ });
1096
1146
  return { storage, invalidate };
1097
1147
  }
1098
1148
  var NestEngineCache = class {
@@ -1606,14 +1656,18 @@ async function removeTeamRefs(tx, teamId) {
1606
1656
  }
1607
1657
  async function resolveTeamRolesForUser(nestId, userId) {
1608
1658
  const refs = await listNestTeams(nestId).catch(() => []);
1659
+ return resolveTeamRolesFromShared(refs, userId);
1660
+ }
1661
+ async function resolveTeamRolesFromShared(refs, userId) {
1609
1662
  if (refs.length === 0) return [];
1610
- const db = getDb();
1663
+ const ids = [...new Set(refs.map((r) => r.teamId).filter(Boolean))];
1664
+ if (ids.length === 0) return [];
1665
+ const rows = await getDb().all(
1666
+ `SELECT members FROM teams WHERE id IN (${ids.map(() => "?").join(",")})`,
1667
+ ids
1668
+ );
1611
1669
  const roles = /* @__PURE__ */ new Set();
1612
- for (const ref of refs) {
1613
- const row = await db.get("SELECT members FROM teams WHERE id = ?", [
1614
- ref.teamId
1615
- ]);
1616
- if (!row) continue;
1670
+ for (const row of rows) {
1617
1671
  const member = parseMembers(row.members).find((m) => m.userId === userId);
1618
1672
  if (member) roles.add(member.role);
1619
1673
  }
@@ -1679,71 +1733,91 @@ var PERMISSION_LEVELS = {
1679
1733
  admin: 3,
1680
1734
  owner: 4
1681
1735
  };
1682
- async function resolveNestPermission(nestId, userId) {
1683
- const db = getDb();
1684
- const nest = await db.get("SELECT user_id, visibility FROM nests WHERE id = ?", [nestId]);
1685
- if (!nest) return "none";
1686
- if (nest.user_id === userId) return "owner";
1687
- if (nest.user_id === ANON_USER_ID && await isLicenseAdminUserId(userId)) {
1688
- return "owner";
1689
- }
1690
- if (await isLicenseAdminUserId(userId)) return "admin";
1691
- const caller = await db.get("SELECT email FROM users WHERE id = ?", [userId]);
1692
- if (caller?.email && isSuperAdmin(caller.email)) {
1693
- return "admin";
1694
- }
1695
- const higher = (a, b) => b && PERMISSION_LEVELS[b] > PERMISSION_LEVELS[a] ? b : a;
1696
- let best = "none";
1697
- const directGrant = await db.get(
1698
- "SELECT permission FROM nest_collaborators WHERE nest_id = ? AND user_id = ?",
1699
- [nestId, userId]
1700
- );
1701
- if (directGrant) best = higher(best, directGrant.permission);
1702
- if (best === "admin") return best;
1703
- for (const role of await resolveTeamRolesForUser(nestId, userId)) {
1704
- best = higher(best, teamRoleToPermission(role));
1705
- }
1706
- if (best === "admin") return best;
1707
- const stewardGrant = await db.get(
1708
- `SELECT 1 FROM stewards s
1709
- JOIN users u ON u.id = ?
1710
- WHERE s.nest_id = ? AND s.is_active = 1
1711
- AND LOWER(s.user_email) = LOWER(u.email)
1712
- LIMIT 1`,
1713
- [userId, nestId]
1736
+ var higher = (a, b) => b && PERMISSION_LEVELS[b] > PERMISSION_LEVELS[a] ? b : a;
1737
+ async function resolveNestAccess(nestId, userId) {
1738
+ const row = await getDb().get(
1739
+ `SELECT n.user_id AS owner_id,
1740
+ n.visibility AS visibility,
1741
+ n.shared_teams AS shared_teams,
1742
+ u.email AS caller_email,
1743
+ nc.permission AS collab_permission,
1744
+ (SELECT 1 FROM stewards s
1745
+ WHERE s.nest_id = n.id AND s.is_active = 1
1746
+ AND LOWER(s.user_email) = LOWER(u.email)
1747
+ LIMIT 1) AS steward_hit,
1748
+ (SELECT 1 FROM grants g
1749
+ WHERE g.nest_id = n.id AND g.user_id = ?
1750
+ LIMIT 1) AS grant_hit
1751
+ FROM nests n
1752
+ LEFT JOIN users u ON u.id = ?
1753
+ LEFT JOIN nest_collaborators nc
1754
+ ON nc.nest_id = n.id AND nc.user_id = ?
1755
+ WHERE n.id = ?`,
1756
+ [userId, userId, userId, nestId]
1714
1757
  );
1715
- if (stewardGrant) best = higher(best, "read");
1716
- if (nest.visibility === "public") best = higher(best, "read");
1717
- return best;
1758
+ const absent = {
1759
+ exists: false,
1760
+ ownerId: null,
1761
+ visibility: null,
1762
+ callerEmail: null,
1763
+ collaboratorPermission: null,
1764
+ isSteward: false,
1765
+ hasGrant: false,
1766
+ permission: "none",
1767
+ isPublicReader: false
1768
+ };
1769
+ if (!row) return absent;
1770
+ const base = {
1771
+ exists: true,
1772
+ ownerId: row.owner_id,
1773
+ visibility: row.visibility,
1774
+ callerEmail: row.caller_email ?? null,
1775
+ collaboratorPermission: row.collab_permission ?? null,
1776
+ // Postgres returns integer 1, SQLite returns 1 — both truthy, neither a
1777
+ // boolean, so normalize rather than leaking the driver's shape.
1778
+ isSteward: !!row.steward_hit,
1779
+ hasGrant: !!row.grant_hit
1780
+ };
1781
+ if (row.owner_id === userId) {
1782
+ return { ...base, permission: "owner", isPublicReader: false };
1783
+ }
1784
+ const email = row.caller_email ?? null;
1785
+ const licenseAdmin = isLicenseAdminEmail(email);
1786
+ if (row.owner_id === ANON_USER_ID && licenseAdmin) {
1787
+ return { ...base, permission: "owner", isPublicReader: false };
1788
+ }
1789
+ if (licenseAdmin || email && isSuperAdmin(email)) {
1790
+ return { ...base, permission: "admin", isPublicReader: false };
1791
+ }
1792
+ let best = higher("none", row.collab_permission);
1793
+ if (best !== "admin") {
1794
+ const teamRoles = await resolveTeamRolesFromShared(
1795
+ parseSharedTeams(row.shared_teams),
1796
+ userId
1797
+ );
1798
+ for (const role of teamRoles) {
1799
+ best = higher(best, teamRoleToPermission(role));
1800
+ }
1801
+ if (teamRoles.length > 0) {
1802
+ return { ...base, permission: best, isPublicReader: false };
1803
+ }
1804
+ }
1805
+ if (best === "admin") {
1806
+ return { ...base, permission: best, isPublicReader: false };
1807
+ }
1808
+ if (base.isSteward) best = higher(best, "read");
1809
+ if (row.visibility === "public") best = higher(best, "read");
1810
+ const isPublicReader2 = row.visibility === "public" && !row.collab_permission && !base.isSteward;
1811
+ return { ...base, permission: best, isPublicReader: isPublicReader2 };
1812
+ }
1813
+ async function resolveNestPermission(nestId, userId) {
1814
+ return (await resolveNestAccess(nestId, userId)).permission;
1718
1815
  }
1719
1816
  function permissionLevel(p) {
1720
1817
  return PERMISSION_LEVELS[p] ?? 0;
1721
1818
  }
1722
1819
  async function isPublicReader(nestId, userId) {
1723
- const db = getDb();
1724
- const nest = await db.get("SELECT user_id, visibility FROM nests WHERE id = ?", [nestId]);
1725
- if (!nest) return false;
1726
- if (nest.visibility !== "public") return false;
1727
- if (nest.user_id === userId) return false;
1728
- const directGrant = await db.get(
1729
- "SELECT 1 FROM nest_collaborators WHERE nest_id = ? AND user_id = ?",
1730
- [nestId, userId]
1731
- );
1732
- if (directGrant) return false;
1733
- if (await isLicenseAdminUserId(userId)) return false;
1734
- const caller = await db.get("SELECT email FROM users WHERE id = ?", [userId]);
1735
- if (caller?.email && isSuperAdmin(caller.email)) return false;
1736
- const stewardGrant = await db.get(
1737
- `SELECT 1 FROM stewards s
1738
- JOIN users u ON u.id = ?
1739
- WHERE s.nest_id = ? AND s.is_active = 1
1740
- AND LOWER(s.user_email) = LOWER(u.email)
1741
- LIMIT 1`,
1742
- [userId, nestId]
1743
- );
1744
- if (stewardGrant) return false;
1745
- if ((await resolveTeamRolesForUser(nestId, userId)).length > 0) return false;
1746
- return true;
1820
+ return (await resolveNestAccess(nestId, userId)).isPublicReader;
1747
1821
  }
1748
1822
 
1749
1823
  // src/governance/tag-index-service.ts
@@ -2040,10 +2114,12 @@ async function listSharedNests(userId) {
2040
2114
  LEFT JOIN stewards s
2041
2115
  ON s.nest_id = n.id AND s.is_active = 1
2042
2116
  AND LOWER(s.user_email) = LOWER(u.email)
2117
+ LEFT JOIN grants g
2118
+ ON g.nest_id = n.id AND g.user_id = ?
2043
2119
  WHERE n.user_id != ?
2044
- AND (nc.user_id IS NOT NULL OR s.id IS NOT NULL)
2120
+ AND (nc.user_id IS NOT NULL OR s.id IS NOT NULL OR g.id IS NOT NULL)
2045
2121
  ORDER BY n.created_at DESC`,
2046
- [userId, userId, userId]
2122
+ [userId, userId, userId, userId]
2047
2123
  );
2048
2124
  const seen = new Set(direct.map((n) => n.id));
2049
2125
  for (const n of await nestsSharedViaTeams(userId)) {
@@ -2069,10 +2145,16 @@ async function listSharedNests(userId) {
2069
2145
  );
2070
2146
  return direct;
2071
2147
  }
2072
- async function listVisibleNests(userId) {
2148
+ async function listVisibleNests(userId, opts = {}) {
2073
2149
  const db = getDb();
2074
2150
  const includeAnon = config.AUTH_MODE === "open" || await isServerAdminUserId(userId);
2075
- const params = includeAnon ? [userId, userId, userId, ANON_USER_ID] : [userId, userId, userId];
2151
+ const params = [
2152
+ userId,
2153
+ userId,
2154
+ ...opts.readable ? [userId] : [],
2155
+ userId,
2156
+ ...includeAnon ? [ANON_USER_ID] : []
2157
+ ];
2076
2158
  const rows = await db.all(
2077
2159
  `SELECT DISTINCT n.* FROM nests n
2078
2160
  LEFT JOIN nest_collaborators nc
@@ -2081,9 +2163,11 @@ async function listVisibleNests(userId) {
2081
2163
  LEFT JOIN stewards s
2082
2164
  ON s.nest_id = n.id AND s.is_active = 1
2083
2165
  AND LOWER(s.user_email) = LOWER(u.email)
2166
+ ${opts.readable ? "LEFT JOIN grants g ON g.nest_id = n.id AND g.user_id = ?" : ""}
2084
2167
  WHERE n.user_id = ?
2085
2168
  OR nc.user_id IS NOT NULL
2086
2169
  OR s.id IS NOT NULL
2170
+ ${opts.readable ? "OR g.id IS NOT NULL OR n.visibility = 'public'" : ""}
2087
2171
  ${includeAnon ? "OR n.user_id = ?" : ""}
2088
2172
  ORDER BY n.created_at DESC`,
2089
2173
  params
@@ -2220,6 +2304,7 @@ export {
2220
2304
  canApproveWith,
2221
2305
  primaryRole,
2222
2306
  isServerAdminUserId,
2307
+ resolveNestAccess,
2223
2308
  resolveNestPermission,
2224
2309
  permissionLevel,
2225
2310
  isPublicReader
@@ -2,7 +2,7 @@ import {
2
2
  buildTitleMap,
3
3
  insertOrReplace,
4
4
  nowExpr
5
- } from "./chunk-L7UCMGWZ.js";
5
+ } from "./chunk-SNFGU5Q7.js";
6
6
  import {
7
7
  getDb
8
8
  } from "./chunk-JMJLNEXE.js";
@@ -11,7 +11,7 @@ import {
11
11
  resolveNestPermission,
12
12
  resolveTeamRolesForUser,
13
13
  sendEmailToRecipient
14
- } from "./chunk-L7UCMGWZ.js";
14
+ } from "./chunk-SNFGU5Q7.js";
15
15
  import {
16
16
  ConflictError,
17
17
  ValidationError
@@ -2,11 +2,11 @@ import {
2
2
  createGrant,
3
3
  deleteGrant,
4
4
  grantCoversNode,
5
- hasAnyGrant,
5
+ grantRoleForNode,
6
6
  listGrants,
7
7
  listUserGrants,
8
8
  resolveNodeGrant
9
- } from "./chunk-OTGZQZKK.js";
9
+ } from "./chunk-HWDPCPYT.js";
10
10
  import "./chunk-GUNJTORH.js";
11
11
  import "./chunk-JMJLNEXE.js";
12
12
  import "./chunk-SLTQACJW.js";
@@ -14,7 +14,7 @@ export {
14
14
  createGrant,
15
15
  deleteGrant,
16
16
  grantCoversNode,
17
- hasAnyGrant,
17
+ grantRoleForNode,
18
18
  listGrants,
19
19
  listUserGrants,
20
20
  resolveNodeGrant