mindweave 2.5.0 → 3.0.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.
Files changed (255) hide show
  1. package/README.md +81 -145
  2. package/dist/cli/App.js +579 -304
  3. package/dist/cli/App.js.map +1 -1
  4. package/dist/cli/analytics.js +27 -18
  5. package/dist/cli/analytics.js.map +1 -1
  6. package/dist/cli/attachments.js +34 -0
  7. package/dist/cli/attachments.js.map +1 -1
  8. package/dist/cli/bannerFit.js +70 -0
  9. package/dist/cli/bannerFit.js.map +1 -0
  10. package/dist/cli/bootstrap.js +41 -3
  11. package/dist/cli/bootstrap.js.map +1 -1
  12. package/dist/cli/commands.js +4 -1
  13. package/dist/cli/commands.js.map +1 -1
  14. package/dist/cli/components/ApprovalBox.js +3 -2
  15. package/dist/cli/components/ApprovalBox.js.map +1 -1
  16. package/dist/cli/components/BlockView.js +27 -6
  17. package/dist/cli/components/BlockView.js.map +1 -1
  18. package/dist/cli/components/FirstRunFrame.js +2 -1
  19. package/dist/cli/components/FirstRunFrame.js.map +1 -1
  20. package/dist/cli/components/KeyManager.js +5 -4
  21. package/dist/cli/components/KeyManager.js.map +1 -1
  22. package/dist/cli/components/KeySetup.js +29 -13
  23. package/dist/cli/components/KeySetup.js.map +1 -1
  24. package/dist/cli/components/MarathonBox.js +64 -0
  25. package/dist/cli/components/MarathonBox.js.map +1 -0
  26. package/dist/cli/components/McpMinitabs.js +8 -7
  27. package/dist/cli/components/McpMinitabs.js.map +1 -1
  28. package/dist/cli/components/Picker.js +4 -3
  29. package/dist/cli/components/Picker.js.map +1 -1
  30. package/dist/cli/components/PromptInput.js +47 -8
  31. package/dist/cli/components/PromptInput.js.map +1 -1
  32. package/dist/cli/components/PulseDot.js +42 -0
  33. package/dist/cli/components/PulseDot.js.map +1 -0
  34. package/dist/cli/components/SubagentView.js +9 -6
  35. package/dist/cli/components/SubagentView.js.map +1 -1
  36. package/dist/cli/components/TipLine.js +2 -1
  37. package/dist/cli/components/TipLine.js.map +1 -1
  38. package/dist/cli/components/ToolGroup.js +65 -49
  39. package/dist/cli/components/ToolGroup.js.map +1 -1
  40. package/dist/cli/components/ToolLine.js +20 -19
  41. package/dist/cli/components/ToolLine.js.map +1 -1
  42. package/dist/cli/components/TrustGate.js +2 -1
  43. package/dist/cli/components/TrustGate.js.map +1 -1
  44. package/dist/cli/components/minitabs.js +3 -2
  45. package/dist/cli/components/minitabs.js.map +1 -1
  46. package/dist/cli/desktopApp.js +45 -0
  47. package/dist/cli/desktopApp.js.map +1 -0
  48. package/dist/cli/help.js +1 -0
  49. package/dist/cli/help.js.map +1 -1
  50. package/dist/cli/inkOutputPatch.js +285 -0
  51. package/dist/cli/inkOutputPatch.js.map +1 -0
  52. package/dist/cli/inkSpeed.js +29 -0
  53. package/dist/cli/inkSpeed.js.map +1 -0
  54. package/dist/cli/inkSpeedLoader.js +47 -0
  55. package/dist/cli/inkSpeedLoader.js.map +1 -0
  56. package/dist/cli/inputText.js +30 -0
  57. package/dist/cli/inputText.js.map +1 -0
  58. package/dist/cli/keyManager.js +2 -1
  59. package/dist/cli/keyManager.js.map +1 -1
  60. package/dist/cli/keyPrefs.js +90 -0
  61. package/dist/cli/keyPrefs.js.map +1 -0
  62. package/dist/cli/keySetup.js +5 -2
  63. package/dist/cli/keySetup.js.map +1 -1
  64. package/dist/cli/marathonUi.js +113 -0
  65. package/dist/cli/marathonUi.js.map +1 -0
  66. package/dist/cli/markdown.js +63 -11
  67. package/dist/cli/markdown.js.map +1 -1
  68. package/dist/cli/modes.js +3 -3
  69. package/dist/cli/modes.js.map +1 -1
  70. package/dist/cli/prodEnv.js +43 -0
  71. package/dist/cli/prodEnv.js.map +1 -0
  72. package/dist/cli/revealPace.js +5 -5
  73. package/dist/cli/revealPace.js.map +1 -1
  74. package/dist/cli/revealQueue.js +120 -0
  75. package/dist/cli/revealQueue.js.map +1 -0
  76. package/dist/cli/theme.js +47 -0
  77. package/dist/cli/theme.js.map +1 -0
  78. package/dist/cli/toolDisplay.js +112 -18
  79. package/dist/cli/toolDisplay.js.map +1 -1
  80. package/dist/cli/transcript.js +1 -1
  81. package/dist/cli/transcript.js.map +1 -1
  82. package/dist/cli/virtualWindow.js +14 -2
  83. package/dist/cli/virtualWindow.js.map +1 -1
  84. package/dist/cli/wheelAccel.js +73 -0
  85. package/dist/cli/wheelAccel.js.map +1 -0
  86. package/dist/cli/widestLinePatch.js +48 -0
  87. package/dist/cli/widestLinePatch.js.map +1 -0
  88. package/dist/cli/workingVerb.js +88 -28
  89. package/dist/cli/workingVerb.js.map +1 -1
  90. package/dist/cli/wrap.js +1 -1
  91. package/dist/cli/wrap.js.map +1 -1
  92. package/dist/core/contextSettings.js +84 -0
  93. package/dist/core/contextSettings.js.map +1 -0
  94. package/dist/core/limitsView.js +40 -0
  95. package/dist/core/limitsView.js.map +1 -0
  96. package/dist/core/mcpServers.js +164 -0
  97. package/dist/core/mcpServers.js.map +1 -0
  98. package/dist/core/permissions.js +209 -0
  99. package/dist/core/permissions.js.map +1 -0
  100. package/dist/core/providerKeys.js +187 -0
  101. package/dist/core/providerKeys.js.map +1 -0
  102. package/dist/core/rulesSkills.js +142 -0
  103. package/dist/core/rulesSkills.js.map +1 -0
  104. package/dist/core/spendView.js +90 -0
  105. package/dist/core/spendView.js.map +1 -0
  106. package/dist/core/turnRunner.js +759 -0
  107. package/dist/core/turnRunner.js.map +1 -0
  108. package/dist/drivers/anthropic/client.js +101 -26
  109. package/dist/drivers/anthropic/client.js.map +1 -1
  110. package/dist/drivers/anthropic/manifest.js +43 -13
  111. package/dist/drivers/anthropic/manifest.js.map +1 -1
  112. package/dist/drivers/cerebras/manifest.js +13 -3
  113. package/dist/drivers/cerebras/manifest.js.map +1 -1
  114. package/dist/drivers/deepseek/manifest.js +24 -34
  115. package/dist/drivers/deepseek/manifest.js.map +1 -1
  116. package/dist/drivers/glm/manifest.js +21 -6
  117. package/dist/drivers/glm/manifest.js.map +1 -1
  118. package/dist/drivers/groq/manifest.js +5 -2
  119. package/dist/drivers/groq/manifest.js.map +1 -1
  120. package/dist/drivers/kimi/manifest.js +13 -10
  121. package/dist/drivers/kimi/manifest.js.map +1 -1
  122. package/dist/drivers/meta/manifest.js +21 -16
  123. package/dist/drivers/meta/manifest.js.map +1 -1
  124. package/dist/drivers/minimax/manifest.js +15 -12
  125. package/dist/drivers/minimax/manifest.js.map +1 -1
  126. package/dist/drivers/ollama/catalog.js +71 -0
  127. package/dist/drivers/ollama/catalog.js.map +1 -0
  128. package/dist/drivers/ollama/client.js +164 -0
  129. package/dist/drivers/ollama/client.js.map +1 -0
  130. package/dist/drivers/ollama/endpoint.js +42 -0
  131. package/dist/drivers/ollama/endpoint.js.map +1 -0
  132. package/dist/drivers/ollama/index.js +5 -0
  133. package/dist/drivers/ollama/index.js.map +1 -0
  134. package/dist/drivers/ollama/manifest.js +64 -0
  135. package/dist/drivers/ollama/manifest.js.map +1 -0
  136. package/dist/drivers/openai/client.js +10 -4
  137. package/dist/drivers/openai/client.js.map +1 -1
  138. package/dist/drivers/openai/manifest.js +69 -18
  139. package/dist/drivers/openai/manifest.js.map +1 -1
  140. package/dist/drivers/qwen/manifest.js +19 -6
  141. package/dist/drivers/qwen/manifest.js.map +1 -1
  142. package/dist/drivers/registry.js +11 -0
  143. package/dist/drivers/registry.js.map +1 -1
  144. package/dist/drivers/retryPolicy.js +26 -1
  145. package/dist/drivers/retryPolicy.js.map +1 -1
  146. package/dist/drivers/xai/client.js +12 -10
  147. package/dist/drivers/xai/client.js.map +1 -1
  148. package/dist/drivers/xai/manifest.js +62 -25
  149. package/dist/drivers/xai/manifest.js.map +1 -1
  150. package/dist/dynamo/auxModel.js +54 -0
  151. package/dist/dynamo/auxModel.js.map +1 -0
  152. package/dist/dynamo/engine.js +407 -99
  153. package/dist/dynamo/engine.js.map +1 -1
  154. package/dist/dynamo/guard.js +10 -0
  155. package/dist/dynamo/guard.js.map +1 -1
  156. package/dist/dynamo/liveMeter.js +25 -6
  157. package/dist/dynamo/liveMeter.js.map +1 -1
  158. package/dist/dynamo/marathon.js +409 -0
  159. package/dist/dynamo/marathon.js.map +1 -0
  160. package/dist/dynamo/marathonPrompt.js +45 -0
  161. package/dist/dynamo/marathonPrompt.js.map +1 -0
  162. package/dist/dynamo/model.js +6 -2
  163. package/dist/dynamo/model.js.map +1 -1
  164. package/dist/dynamo/toolBatches.js +58 -0
  165. package/dist/dynamo/toolBatches.js.map +1 -0
  166. package/dist/dynamo/usageLimits.js +446 -0
  167. package/dist/dynamo/usageLimits.js.map +1 -0
  168. package/dist/governor/freshness.js +10 -7
  169. package/dist/governor/freshness.js.map +1 -1
  170. package/dist/governor/index.js +76 -9
  171. package/dist/governor/index.js.map +1 -1
  172. package/dist/governor/write.js +43 -14
  173. package/dist/governor/write.js.map +1 -1
  174. package/dist/index.js +18 -151
  175. package/dist/index.js.map +1 -1
  176. package/dist/main.js +162 -0
  177. package/dist/main.js.map +1 -0
  178. package/dist/mcp/manager.js +18 -0
  179. package/dist/mcp/manager.js.map +1 -1
  180. package/dist/memory/allMetas.js +69 -0
  181. package/dist/memory/allMetas.js.map +1 -0
  182. package/dist/memory/compaction.js +70 -15
  183. package/dist/memory/compaction.js.map +1 -1
  184. package/dist/memory/earlier.js +15 -0
  185. package/dist/memory/earlier.js.map +1 -0
  186. package/dist/memory/profile.js +93 -0
  187. package/dist/memory/profile.js.map +1 -0
  188. package/dist/memory/rewind.js +262 -0
  189. package/dist/memory/rewind.js.map +1 -0
  190. package/dist/memory/session.js +42 -10
  191. package/dist/memory/session.js.map +1 -1
  192. package/dist/memory/sessionMemory.js +48 -13
  193. package/dist/memory/sessionMemory.js.map +1 -1
  194. package/dist/memory/sessionMemoryCompact.js +1 -1
  195. package/dist/memory/sessionMemoryCompact.js.map +1 -1
  196. package/dist/memory/sessionTitle.js +50 -0
  197. package/dist/memory/sessionTitle.js.map +1 -0
  198. package/dist/memory/store.js +42 -1
  199. package/dist/memory/store.js.map +1 -1
  200. package/dist/memory/types.js.map +1 -1
  201. package/dist/tools/askUser.js +14 -0
  202. package/dist/tools/askUser.js.map +1 -1
  203. package/dist/tools/backgroundShells.js +208 -5
  204. package/dist/tools/backgroundShells.js.map +1 -1
  205. package/dist/tools/browser.js +176 -0
  206. package/dist/tools/browser.js.map +1 -0
  207. package/dist/tools/cdp.js +165 -0
  208. package/dist/tools/cdp.js.map +1 -0
  209. package/dist/tools/checkpoints.js +208 -5
  210. package/dist/tools/checkpoints.js.map +1 -1
  211. package/dist/tools/deferredNative.js +2 -0
  212. package/dist/tools/deferredNative.js.map +1 -1
  213. package/dist/tools/detail.js +9 -6
  214. package/dist/tools/detail.js.map +1 -1
  215. package/dist/tools/edit.js +2 -1
  216. package/dist/tools/edit.js.map +1 -1
  217. package/dist/tools/exitPlan.js +4 -0
  218. package/dist/tools/exitPlan.js.map +1 -1
  219. package/dist/tools/hiddenDesktop.js +273 -0
  220. package/dist/tools/hiddenDesktop.js.map +1 -0
  221. package/dist/tools/listeningPorts.js +134 -0
  222. package/dist/tools/listeningPorts.js.map +1 -0
  223. package/dist/tools/mcpSearch.js +8 -0
  224. package/dist/tools/mcpSearch.js.map +1 -1
  225. package/dist/tools/mindweaveStatus.js +1 -1
  226. package/dist/tools/mindweaveStatus.js.map +1 -1
  227. package/dist/tools/registry.js +3 -0
  228. package/dist/tools/registry.js.map +1 -1
  229. package/dist/tools/replaceSymbol.js +2 -1
  230. package/dist/tools/replaceSymbol.js.map +1 -1
  231. package/dist/tools/runCommand.js +113 -9
  232. package/dist/tools/runCommand.js.map +1 -1
  233. package/dist/tools/screenshot.js +4 -6
  234. package/dist/tools/screenshot.js.map +1 -1
  235. package/dist/tools/screenshotWin.js +26 -9
  236. package/dist/tools/screenshotWin.js.map +1 -1
  237. package/dist/tools/stateCheckpoint.js +128 -0
  238. package/dist/tools/stateCheckpoint.js.map +1 -0
  239. package/dist/tools/todo.js +6 -0
  240. package/dist/tools/todo.js.map +1 -1
  241. package/dist/tools/ui.js +1295 -0
  242. package/dist/tools/ui.js.map +1 -0
  243. package/dist/tools/uiHide.js +356 -0
  244. package/dist/tools/uiHide.js.map +1 -0
  245. package/dist/tools/uiPage.js +861 -0
  246. package/dist/tools/uiPage.js.map +1 -0
  247. package/dist/tools/uiWin.js +506 -0
  248. package/dist/tools/uiWin.js.map +1 -0
  249. package/dist/tools/webFetch.js +32 -25
  250. package/dist/tools/webFetch.js.map +1 -1
  251. package/dist/tools/webSearch.js +12 -0
  252. package/dist/tools/webSearch.js.map +1 -1
  253. package/dist/tools/writeFile.js +2 -1
  254. package/dist/tools/writeFile.js.map +1 -1
  255. package/package.json +78 -75
@@ -0,0 +1,446 @@
1
+ /**
2
+ * usageLimits.ts — a cap you set on your own use, in windows that open and close.
3
+ *
4
+ * Shaped like the plans people already know: a short window (5 hours) that closes when its
5
+ * amount is used and reopens when it ends; a longer one (a week) that does the same; and the
6
+ * month's own budget above both. You set the month, "Analyze" works out the other two from how
7
+ * you actually use the tool, and the numbers stay editable.
8
+ *
9
+ * THE UNIT IS YOURS: tokens (fresh input plus output, the same "billed" figure the Spend tab
10
+ * shows) or money (those tokens at each model's own rates). Both come from the per-call log
11
+ * every session already keeps, so nothing new is recorded and nothing leaves the machine.
12
+ *
13
+ * WINDOWS ARE DERIVED, NOT STORED. A window opens at the first use after the previous one
14
+ * ended and lasts its span, so where one starts is a fact about the usage history and can be
15
+ * recomputed from it. That keeps the app and the terminal agreeing without sharing any state
16
+ * beyond the history they both already write.
17
+ *
18
+ * A LIMIT IS CHECKED BETWEEN STEPS, never inside one. A step that begins under the limit
19
+ * finishes, so a window can be passed by what that one step cost (a few thousand tokens) and
20
+ * never by more. The next step is what stops, and it stops cleanly: nothing is lost, and the
21
+ * work can carry on once the window reopens.
22
+ */
23
+ import { promises as fs } from "node:fs";
24
+ import { join } from "node:path";
25
+ import { callRecords, everyMeta } from "../memory/allMetas.js";
26
+ import { stateRoot } from "../memory/store.js";
27
+ import { priceFor } from "./pricing.js";
28
+ export const DEFAULT_LIMITS = {
29
+ enabled: false,
30
+ unit: "tokens",
31
+ monthly: 0,
32
+ monthStartDay: 1,
33
+ fiveHour: 0,
34
+ weekly: 0,
35
+ enforce: true,
36
+ };
37
+ export const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
38
+ export const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
39
+ /** How much history the windows are worked out from. Long enough for a week's chain to settle. */
40
+ const HORIZON_MS = 62 * 24 * 60 * 60 * 1000;
41
+ /** Warnings, lowest first. Each fires once per window. */
42
+ const WARN_AT = [0.8, 0.95];
43
+ /** The disk is re-read at most this often; live use is added in between. */
44
+ const REFRESH_MS = 60_000;
45
+ // ── config ───────────────────────────────────────────────────────────────────
46
+ export function limitsPath() {
47
+ return join(stateRoot(), "limits.json");
48
+ }
49
+ function num(v, fallback, max = Number.MAX_SAFE_INTEGER) {
50
+ const n = typeof v === "number" ? v : Number(v);
51
+ return Number.isFinite(n) && n >= 0 ? Math.min(n, max) : fallback;
52
+ }
53
+ /** Whatever was written, made safe: a hand-edited or half-written file never breaks the app. */
54
+ export function sanitizeLimits(raw) {
55
+ const r = (raw && typeof raw === "object" ? raw : {});
56
+ const unit = r["unit"] === "usd" ? "usd" : "tokens";
57
+ const money = unit === "usd";
58
+ const round = (n) => (money ? Math.round(n * 100) / 100 : Math.round(n));
59
+ const cfg = {
60
+ enabled: r["enabled"] === true,
61
+ unit,
62
+ monthly: round(num(r["monthly"], 0)),
63
+ monthStartDay: Math.min(28, Math.max(1, Math.round(num(r["monthStartDay"], 1)))),
64
+ fiveHour: round(num(r["fiveHour"], 0)),
65
+ weekly: round(num(r["weekly"], 0)),
66
+ enforce: r["enforce"] !== false,
67
+ };
68
+ const at = num(r["analyzedAt"], 0);
69
+ if (at > 0)
70
+ cfg.analyzedAt = at;
71
+ const reset = num(r["weeklyResetAt"], 0);
72
+ if (reset > 0)
73
+ cfg.weeklyResetAt = reset;
74
+ return cfg;
75
+ }
76
+ export async function readLimitsConfig() {
77
+ try {
78
+ return sanitizeLimits(JSON.parse(await fs.readFile(limitsPath(), "utf8")));
79
+ }
80
+ catch {
81
+ return { ...DEFAULT_LIMITS };
82
+ }
83
+ }
84
+ async function writeLimitsFile(cfg) {
85
+ const file = limitsPath();
86
+ await fs.mkdir(stateRoot(), { recursive: true });
87
+ const tmp = `${file}.${process.pid}.tmp`;
88
+ await fs.writeFile(tmp, JSON.stringify(cfg, null, 2), "utf8");
89
+ await fs.rename(tmp, file); // whole file or nothing: a crash mid-write cannot leave half of one
90
+ }
91
+ let config = { ...DEFAULT_LIMITS };
92
+ let ledger = [];
93
+ let loadedAt = 0;
94
+ const told = new Set();
95
+ /** Fresh input plus output, and what those cost at the model's own rates. */
96
+ export function callAmount(model, hit, miss, out) {
97
+ const price = priceFor(model);
98
+ return { tokens: miss + out, usd: (hit * price.cacheHit + miss * price.cacheMiss + out * price.output) / 1_000_000 };
99
+ }
100
+ async function loadLedger(now) {
101
+ const since = now - HORIZON_MS;
102
+ const recs = [];
103
+ for (const meta of await everyMeta()) {
104
+ for (const c of callRecords(meta)) {
105
+ if (c.at >= since)
106
+ recs.push({ at: c.at, ...callAmount(c.model, c.hit, c.miss, c.out) });
107
+ }
108
+ }
109
+ return recs.sort((a, b) => a.at - b.at);
110
+ }
111
+ // ── the 5-hour session's start, remembered ──────────────────────────────────
112
+ function statePath() {
113
+ return join(stateRoot(), "limits-state.json");
114
+ }
115
+ /**
116
+ * Where the current 5-hour session began. A session opens at the first use after the last one
117
+ * ended, so its start is a fact of the history; it is kept so that it stays put, rather than
118
+ * being worked out again from however much history happens to be in view (nonstop use would
119
+ * otherwise slide its boundaries as old records age out).
120
+ */
121
+ let fiveSeed = null;
122
+ async function readFiveSeed() {
123
+ try {
124
+ const v = JSON.parse(await fs.readFile(statePath(), "utf8")).fiveHourStart;
125
+ return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : null;
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ }
131
+ async function writeFiveSeed() {
132
+ try {
133
+ await fs.mkdir(stateRoot(), { recursive: true });
134
+ const tmp = `${statePath()}.${process.pid}.tmp`;
135
+ await fs.writeFile(tmp, JSON.stringify({ fiveHourStart: fiveSeed }), "utf8");
136
+ await fs.rename(tmp, statePath());
137
+ }
138
+ catch {
139
+ /* the start is re-derived from the history next time: losing this only costs exactness */
140
+ }
141
+ }
142
+ /** Where a session that began at `seed` stands once a use at `at` happens. */
143
+ function advanceSeed(seed, at) {
144
+ return seed === null || at >= seed + FIVE_HOURS_MS ? at : seed;
145
+ }
146
+ /**
147
+ * Bring the settings and the history up to date. Cheap when limits are off (one small file
148
+ * read), and when on the disk is scanned at most once a minute, with live use added between.
149
+ * `force` reads the history even while limits are off: Analyze needs it before they are on.
150
+ */
151
+ export async function refreshUsageLimits(opts = {}) {
152
+ const now = opts.now ?? Date.now();
153
+ config = await readLimitsConfig();
154
+ if (!config.enabled && !opts.force)
155
+ return config;
156
+ if (opts.force || loadedAt === 0 || now - loadedAt >= REFRESH_MS)
157
+ await adoptLedger(await loadLedger(now), now);
158
+ // A limit set before the week had a fixed reset: keep the week it was already in.
159
+ if (config.enabled && config.weekly > 0 && !config.weeklyResetAt) {
160
+ config = { ...config, weeklyResetAt: latestWindow(ledger, WEEK_MS, "tokens")?.start ?? now };
161
+ await writeLimitsFile(config);
162
+ }
163
+ return config;
164
+ }
165
+ /**
166
+ * Take a freshly read history as the truth, together with where the 5-hour session began: the
167
+ * remembered start carried forward through whatever has happened since. The two always move
168
+ * together, because a history without its session start would let the next call open a new
169
+ * session and ignore the hours already used.
170
+ */
171
+ async function adoptLedger(recs, now) {
172
+ ledger = recs;
173
+ loadedAt = now;
174
+ const before = await readFiveSeed();
175
+ fiveSeed = latestWindow(ledger, FIVE_HOURS_MS, "tokens", before)?.start ?? null;
176
+ if (fiveSeed !== before)
177
+ await writeFiveSeed();
178
+ }
179
+ /** A model call finished: count it. Ignored while limits are off. */
180
+ export function noteUsage(model, u, at = Date.now()) {
181
+ if (!config.enabled)
182
+ return;
183
+ // Exactly what the per-call log records for this call (fresh input plus output), so the figure
184
+ // does not change when the history is read back from disk, and matches the Spend tab.
185
+ ledger.push({ at, ...callAmount(model, u.cacheHitTokens, u.cacheMissTokens, u.completionTokens) });
186
+ const next = advanceSeed(fiveSeed, at);
187
+ if (next !== fiveSeed) {
188
+ fiveSeed = next;
189
+ void writeFiveSeed();
190
+ }
191
+ }
192
+ /**
193
+ * The latest window in a history: opens at the first use after the last one ended. With a
194
+ * `seed` (where a window is known to have begun) it carries on from there and ignores anything
195
+ * older, so the answer does not depend on how far back the history happens to reach.
196
+ */
197
+ function latestWindow(recs, span, field, seed = null) {
198
+ let cur = seed !== null ? { start: seed, end: seed + span, used: 0 } : null;
199
+ for (const r of recs) {
200
+ if (seed !== null && r.at < seed)
201
+ continue;
202
+ if (!cur || r.at >= cur.end)
203
+ cur = { start: r.at, end: r.at + span, used: 0 };
204
+ cur.used += r[field];
205
+ }
206
+ return cur;
207
+ }
208
+ function daysIn(year, month) {
209
+ return new Date(year, month + 1, 0).getDate();
210
+ }
211
+ function billingStart(year, month, day) {
212
+ return new Date(year, month, Math.min(day, daysIn(year, month)), 0, 0, 0, 0).getTime();
213
+ }
214
+ /** The billing month containing `now`: [start, end) in local time. */
215
+ export function billingMonth(now, day) {
216
+ const d = new Date(now);
217
+ let y = d.getFullYear();
218
+ let m = d.getMonth();
219
+ if (billingStart(y, m, day) > now) {
220
+ m -= 1;
221
+ if (m < 0) {
222
+ m = 11;
223
+ y -= 1;
224
+ }
225
+ }
226
+ const start = billingStart(y, m, day);
227
+ const ny = m === 11 ? y + 1 : y;
228
+ const nm = m === 11 ? 0 : m + 1;
229
+ return { start, end: billingStart(ny, nm, day) };
230
+ }
231
+ function stateOf(fraction, now, end) {
232
+ if (end === null)
233
+ return "idle";
234
+ if (now >= end)
235
+ return "idle";
236
+ if (fraction >= 1)
237
+ return "blocked";
238
+ if (fraction >= WARN_AT[0])
239
+ return "warn";
240
+ return "ok";
241
+ }
242
+ function windowStatus(id, label, limit, chain, now) {
243
+ const open = chain !== null && now < chain.end;
244
+ const used = open ? chain.used : 0;
245
+ const fraction = limit > 0 ? used / limit : 0;
246
+ return {
247
+ id,
248
+ label,
249
+ limit,
250
+ used,
251
+ fraction,
252
+ start: open ? chain.start : null,
253
+ end: open ? chain.end : null,
254
+ state: stateOf(fraction, now, open ? chain.end : null),
255
+ };
256
+ }
257
+ /** Where every window stands. Pure: hand it a config and a history. */
258
+ export function computeStatus(cfg, recs, now, fiveStart = null) {
259
+ const field = cfg.unit === "usd" ? "usd" : "tokens";
260
+ const inHorizon = recs.filter((r) => r.at <= now).sort((a, b) => a.at - b.at); // windows chain in time order
261
+ const windows = [];
262
+ if (cfg.fiveHour > 0)
263
+ windows.push(windowStatus("fiveHour", "5-hour", cfg.fiveHour, latestWindow(inHorizon, FIVE_HOURS_MS, field, fiveStart), now));
264
+ if (cfg.weekly > 0) {
265
+ // A fixed weekly reset: the same day and time every week, whenever you use the tool. Weeks
266
+ // are counted from the reset moment, so they never depend on the history at all.
267
+ const anchor = cfg.weeklyResetAt ?? now;
268
+ const start = anchor + Math.floor((now - anchor) / WEEK_MS) * WEEK_MS;
269
+ const used = inHorizon.filter((r) => r.at >= start && r.at < start + WEEK_MS).reduce((s, r) => s + r[field], 0);
270
+ windows.push(windowStatus("weekly", "weekly", cfg.weekly, { start, end: start + WEEK_MS, used }, now));
271
+ }
272
+ if (cfg.monthly > 0) {
273
+ const m = billingMonth(now, cfg.monthStartDay);
274
+ const used = inHorizon.filter((r) => r.at >= m.start && r.at < m.end).reduce((s, r) => s + r[field], 0);
275
+ windows.push(windowStatus("monthly", "monthly", cfg.monthly, { start: m.start, end: m.end, used }, now));
276
+ }
277
+ const blocking = windows.filter((w) => w.state === "blocked");
278
+ const blocked = blocking.length > 0 ? blocking.reduce((a, b) => ((b.end ?? 0) > (a.end ?? 0) ? b : a)) : null;
279
+ return { unit: cfg.unit, enabled: cfg.enabled, enforce: cfg.enforce, windows, blocked, now };
280
+ }
281
+ /** Where the windows stand right now, from the live history. */
282
+ export function limitStatus(now = Date.now()) {
283
+ return computeStatus(config, ledger, now, fiveSeed);
284
+ }
285
+ /** The settings as last read. */
286
+ export function currentLimits() {
287
+ return config;
288
+ }
289
+ // ── wording ──────────────────────────────────────────────────────────────────
290
+ export function formatAmount(unit, n) {
291
+ if (unit === "usd")
292
+ return n >= 100 ? `$${Math.round(n).toLocaleString("en-US")}` : `$${n.toFixed(2)}`;
293
+ if (n >= 1_000_000)
294
+ return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}M tokens`;
295
+ if (n >= 10_000)
296
+ return `${Math.round(n / 1000)}K tokens`;
297
+ if (n >= 1000)
298
+ return `${(n / 1000).toFixed(1)}K tokens`;
299
+ return `${Math.round(n)} tokens`;
300
+ }
301
+ /** "40m", "2h 10m", "3d 4h": how long until something happens. */
302
+ export function formatWait(ms) {
303
+ const min = Math.max(1, Math.round(ms / 60_000));
304
+ if (min < 60)
305
+ return `${min}m`;
306
+ const h = Math.floor(min / 60);
307
+ if (h < 24)
308
+ return `${h}h${min % 60 ? ` ${min % 60}m` : ""}`;
309
+ const d = Math.floor(h / 24);
310
+ return `${d}d${h % 24 ? ` ${h % 24}h` : ""}`;
311
+ }
312
+ /** "7:40 PM" today, "Thu 9:15 AM" within the week, "Oct 1" beyond. */
313
+ export function formatWhen(at, now) {
314
+ const d = new Date(at);
315
+ const time = d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
316
+ const sameDay = new Date(now).toDateString() === d.toDateString();
317
+ if (sameDay)
318
+ return time;
319
+ if (at - now < WEEK_MS)
320
+ return `${d.toLocaleDateString([], { weekday: "short" })} ${time}`;
321
+ return d.toLocaleDateString([], { month: "short", day: "numeric" });
322
+ }
323
+ function windowName(w) {
324
+ return w.id === "fiveHour" ? "5-hour" : w.id === "weekly" ? "weekly" : "monthly";
325
+ }
326
+ /** Why work is being held, in one sentence, or null when nothing is. */
327
+ export function blockReason(status) {
328
+ const w = status.blocked;
329
+ if (!w || w.end === null)
330
+ return null;
331
+ return (`your ${windowName(w)} limit is used up (${formatAmount(status.unit, w.used)} of ${formatAmount(status.unit, w.limit)}). ` +
332
+ `It opens again ${formatWhen(w.end, status.now)} (in ${formatWait(w.end - status.now)})`);
333
+ }
334
+ /** What the engine asks before each step: a reason to stop, or null to carry on. */
335
+ export function limitGateReason(now = Date.now()) {
336
+ if (!config.enabled || !config.enforce)
337
+ return null;
338
+ return blockReason(limitStatus(now));
339
+ }
340
+ /**
341
+ * Heads-up lines for windows that just crossed 80% or 95%. Each threshold speaks once per window,
342
+ * and if several were crossed at once only the highest is said.
343
+ */
344
+ export function takeLimitWarnings(now = Date.now()) {
345
+ if (!config.enabled)
346
+ return [];
347
+ const status = limitStatus(now);
348
+ const out = [];
349
+ for (const w of status.windows) {
350
+ if (w.state === "idle" || w.end === null || w.start === null)
351
+ continue;
352
+ let hit = null;
353
+ for (const t of WARN_AT)
354
+ if (w.fraction >= t)
355
+ hit = t;
356
+ if (hit === null || w.fraction >= 1)
357
+ continue; // at 100% the gate speaks instead
358
+ if (told.has(`${w.id}:${w.start}:${hit}`))
359
+ continue;
360
+ // Anything lower counts as told too, so an 80% heads-up never follows a 95% one.
361
+ for (const t of WARN_AT)
362
+ if (t <= hit)
363
+ told.add(`${w.id}:${w.start}:${t}`);
364
+ out.push(`${Math.round(hit * 100)}% of your ${windowName(w)} limit is used (${formatAmount(status.unit, w.used)} of ${formatAmount(status.unit, w.limit)}). ` +
365
+ `It resets ${formatWhen(w.end, now)} (in ${formatWait(w.end - now)}).`);
366
+ }
367
+ return out;
368
+ }
369
+ const DEFAULT_WINDOWS_PER_WEEK = 8;
370
+ /**
371
+ * Turn a month's budget into the windows that fit it.
372
+ *
373
+ * The week gets its share of the month (7 days out of however many this month has), so weeks
374
+ * spent in full add up to the month and nothing more. The 5-hour window comes from how you
375
+ * work: if you typically open about eight windows a week, one window's fair share is an eighth
376
+ * of the week; it is given half again as much so a long session is not cut short, but never
377
+ * more than half the week, so one heavy afternoon cannot spend the whole of it.
378
+ */
379
+ export function analyzeLimits(cfg, recs, now) {
380
+ const money = cfg.unit === "usd";
381
+ const month = billingMonth(now, cfg.monthStartDay);
382
+ const days = Math.round((month.end - month.start) / 86_400_000);
383
+ const weekly = (cfg.monthly * 7) / days;
384
+ const since = now - 28 * 86_400_000;
385
+ const recent = recs.filter((r) => r.at >= since && r.at <= now).sort((a, b) => a.at - b.at);
386
+ let perWeek = DEFAULT_WINDOWS_PER_WEEK;
387
+ let basedOnDays = 0;
388
+ if (recent.length > 0) {
389
+ let windows = 0;
390
+ let end = 0;
391
+ for (const r of recent) {
392
+ if (r.at >= end) {
393
+ windows++;
394
+ end = r.at + FIVE_HOURS_MS;
395
+ }
396
+ }
397
+ basedOnDays = Math.max(1, Math.min(28, Math.ceil((now - recent[0].at) / 86_400_000)));
398
+ const weeks = Math.max(1, basedOnDays / 7);
399
+ perWeek = Math.min(14, Math.max(4, Math.round(windows / weeks)));
400
+ }
401
+ const fiveHourRaw = Math.min(weekly * 0.5, (weekly / perWeek) * 1.5);
402
+ const tidy = (n) => (money ? Math.max(0.01, Math.round(n * 100) / 100) : Math.max(1000, Math.round(n / 1000) * 1000));
403
+ const note = (basedOnDays > 0
404
+ ? `Based on your last ${basedOnDays} day${basedOnDays === 1 ? "" : "s"}: about ${perWeek} separate 5-hour windows a week. `
405
+ : `No history yet, so it assumes about ${perWeek} separate 5-hour windows a week. `) +
406
+ `The week gets 7 of this month's ${days} days of the budget, and a 5-hour window up to 1.5 times its fair share so a long session is not cut short.`;
407
+ return { fiveHour: tidy(fiveHourRaw), weekly: tidy(weekly), windowsPerWeek: perWeek, basedOnDays, note };
408
+ }
409
+ /** Analyze against the live history. */
410
+ export function analyzeCurrent(cfg, now = Date.now()) {
411
+ return analyzeLimits(cfg, ledger, now);
412
+ }
413
+ // ── writing ──────────────────────────────────────────────────────────────────
414
+ /** Save new settings (merged over the current ones), and start using them at once. */
415
+ export async function saveLimits(patch) {
416
+ const next = sanitizeLimits({ ...(await readLimitsConfig()), ...patch });
417
+ // The week's fixed reset is the moment limits with a weekly amount are first turned on.
418
+ if (next.enabled && next.weekly > 0 && !next.weeklyResetAt)
419
+ next.weeklyResetAt = Date.now();
420
+ // Tracking with nothing to track is just "off", so the panel and the engine agree.
421
+ if (next.monthly <= 0 && next.fiveHour <= 0 && next.weekly <= 0)
422
+ next.enabled = false;
423
+ await writeLimitsFile(next);
424
+ const wasEnabled = config.enabled;
425
+ config = next;
426
+ if (next.enabled && (!wasEnabled || ledger.length === 0))
427
+ await adoptLedger(await loadLedger(Date.now()), Date.now());
428
+ told.clear(); // new numbers: earlier heads-ups no longer describe them
429
+ return next;
430
+ }
431
+ /** For tests: forget everything held in memory. */
432
+ export function resetUsageLimitsForTests() {
433
+ config = { ...DEFAULT_LIMITS };
434
+ ledger = [];
435
+ loadedAt = 0;
436
+ fiveSeed = null;
437
+ told.clear();
438
+ }
439
+ /** For tests: put a history in place. */
440
+ export function setLedgerForTests(recs, cfg) {
441
+ ledger = [...recs].sort((a, b) => a.at - b.at);
442
+ config = cfg;
443
+ loadedAt = Date.now();
444
+ fiveSeed = latestWindow(ledger, FIVE_HOURS_MS, "tokens")?.start ?? null;
445
+ }
446
+ //# sourceMappingURL=usageLimits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usageLimits.js","sourceRoot":"","sources":["../../src/dynamo/usageLimits.ts"],"names":[],"mappings":"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"}
@@ -27,9 +27,9 @@
27
27
  */
28
28
  import { promises as fs } from "node:fs";
29
29
  import { join } from "node:path";
30
- import { projectDir } from "../memory/store.js";
30
+ import { projectDir, stateRoot } from "../memory/store.js";
31
31
  /** The files and directories a project's governance is read from. */
32
- const FORBIDDEN_FILES = ["forbidden.md", "forbidden-commands.md", "forbidden-mcp-tools.md"];
32
+ const FORBIDDEN_FILES = ["forbidden.md", "forbidden-commands.md", "forbidden-mcp-tools.md", "sentinel-allow.md", "context.json"];
33
33
  /** One directory's entries as `name:mtime:size`, sorted so the stamp is order-stable. */
34
34
  async function dirStamp(dir, depth) {
35
35
  let names;
@@ -65,19 +65,22 @@ async function dirStamp(dir, depth) {
65
65
  */
66
66
  export async function governanceStamp(cwd) {
67
67
  const stateDir = projectDir(cwd);
68
- const [rules, skills, ...files] = await Promise.all([
68
+ const [rules, skills, globalRules, globalSkills, ...files] = await Promise.all([
69
69
  dirStamp(join(stateDir, "rules"), 0),
70
70
  dirStamp(join(stateDir, "skills"), 1),
71
- ...FORBIDDEN_FILES.map(async (name) => {
71
+ dirStamp(join(stateRoot(), "rules"), 0),
72
+ dirStamp(join(stateRoot(), "skills"), 1),
73
+ // Both scopes: an edit to a universal list must reach sessions already running.
74
+ ...[stateDir, stateRoot()].flatMap((dir) => FORBIDDEN_FILES.map(async (name) => {
72
75
  try {
73
- const st = await fs.stat(join(stateDir, name));
76
+ const st = await fs.stat(join(dir, name));
74
77
  return `${name}:${st.mtimeMs}:${st.size}`;
75
78
  }
76
79
  catch {
77
80
  return `${name}:-`;
78
81
  }
79
- }),
82
+ })),
80
83
  ]);
81
- return `r[${rules}] s[${skills}] f[${files.join("|")}]`;
84
+ return `r[${rules}] s[${skills}] gr[${globalRules}] gs[${globalSkills}] f[${files.join("|")}]`;
82
85
  }
83
86
  //# sourceMappingURL=freshness.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"freshness.js","sourceRoot":"","sources":["../../src/governor/freshness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,qEAAqE;AACrE,MAAM,eAAe,GAAG,CAAC,cAAc,EAAE,uBAAuB,EAAE,wBAAwB,CAAC,CAAC;AAE5F,yFAAyF;AACzF,KAAK,UAAU,QAAQ,CAAC,GAAW,EAAE,KAAa;IAChD,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,GAAG,IAAI,CAAC,CAAC,gEAAgE;IACrF,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/B,6EAA6E;YAC7E,kEAAkE;YAClE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,KAAK,GAAG,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;;gBACzF,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAClD,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrC,GAAG,eAAe,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YACpC,IAAI,CAAC;gBACH,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;gBAC/C,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;YAC5C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,GAAG,IAAI,IAAI,CAAC;YACrB,CAAC;QACH,CAAC,CAAC;KACH,CAAC,CAAC;IACH,OAAO,KAAK,KAAK,OAAO,MAAM,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAC1D,CAAC"}
1
+ {"version":3,"file":"freshness.js","sourceRoot":"","sources":["../../src/governor/freshness.ts"],"names":[],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * mechanical enforcement; the rules and skill catalog are rendered into the
15
15
  * system prompt by the engine.
16
16
  */
17
- import { projectDir } from "../memory/store.js";
17
+ import { projectDir, stateRoot } from "../memory/store.js";
18
18
  import { promises as fs } from "node:fs";
19
19
  import { join } from "node:path";
20
20
  import { loadRules } from "./rules.js";
@@ -29,25 +29,92 @@ async function readStateFile(stateDir, name) {
29
29
  return "";
30
30
  }
31
31
  }
32
- /** Load all governance for the project rooted at `cwd`. Always succeeds (empties). */
32
+ /**
33
+ * Where one scope's permission files live: the project's state dir, or the universal
34
+ * one (`~/.mindweave`) whose lists apply in every project.
35
+ */
36
+ export function governanceDir(cwd, scope) {
37
+ return scope === "global" ? stateRoot() : projectDir(cwd);
38
+ }
39
+ /** The deny-lists and Sentinel allowances of ONE scope, unmerged, for a settings screen. */
40
+ export async function loadPermissionLists(cwd, scope) {
41
+ const dir = governanceDir(cwd, scope);
42
+ const [paths, commands, mcpTools, sentinel] = await Promise.all([
43
+ readStateFile(dir, "forbidden.md"),
44
+ readStateFile(dir, "forbidden-commands.md"),
45
+ readStateFile(dir, "forbidden-mcp-tools.md"),
46
+ readStateFile(dir, "sentinel-allow.md"),
47
+ ]);
48
+ return {
49
+ paths: parseForbidden(paths),
50
+ commands: parseForbiddenCommands(commands),
51
+ mcpTools: parseForbiddenMcpTools(mcpTools),
52
+ sentinelAllow: parseForbiddenCommands(sentinel),
53
+ };
54
+ }
55
+ const unique = (list) => [...new Set(list)];
56
+ const CONTEXT_FILE = "context.json";
57
+ /** One scope's auto-compaction override, or null if unset/unreadable/invalid. */
58
+ export async function loadContextOverride(cwd, scope) {
59
+ try {
60
+ const raw = JSON.parse(await fs.readFile(join(governanceDir(cwd, scope), CONTEXT_FILE), "utf8"));
61
+ const n = raw.autoCompactTokens;
62
+ return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.round(n) : null;
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ /** Set or clear one scope's auto-compaction override. `tokens: null` clears it. */
69
+ export async function saveContextOverride(cwd, scope, tokens) {
70
+ const dir = governanceDir(cwd, scope);
71
+ const file = join(dir, CONTEXT_FILE);
72
+ if (tokens === null) {
73
+ await fs.rm(file, { force: true });
74
+ return;
75
+ }
76
+ await fs.mkdir(dir, { recursive: true });
77
+ await fs.writeFile(file, JSON.stringify({ autoCompactTokens: Math.round(tokens) }, null, 2), "utf8");
78
+ }
79
+ /**
80
+ * Load all governance for the project rooted at `cwd`. Always succeeds (empties).
81
+ *
82
+ * The deny-lists and Sentinel allowances are the universal layer plus the project's,
83
+ * merged: something forbidden everywhere is forbidden here too, and a project can only
84
+ * add to it. Rules and skills stay per project.
85
+ */
33
86
  export async function loadGovernance(cwd) {
34
87
  const stateDir = projectDir(cwd);
35
- const [rules, skills, forbiddenText, forbiddenCmdText, forbiddenMcpText] = await Promise.all([
88
+ const [projectRules, projectSkills, globalRules, globalSkills, global, project, globalContext, projectContext] = await Promise.all([
36
89
  loadRules(stateDir),
37
90
  loadSkillCatalog(stateDir),
38
- readStateFile(stateDir, "forbidden.md"),
39
- readStateFile(stateDir, "forbidden-commands.md"),
40
- readStateFile(stateDir, "forbidden-mcp-tools.md"),
91
+ loadRules(stateRoot()),
92
+ loadSkillCatalog(stateRoot()),
93
+ loadPermissionLists(cwd, "global"),
94
+ loadPermissionLists(cwd, "project"),
95
+ loadContextOverride(cwd, "global"),
96
+ loadContextOverride(cwd, "project"),
41
97
  ]);
98
+ // Rules and skills for every project come first; a project's own of the same name
99
+ // replaces the universal one, so a project can override a general habit.
100
+ const byName = (general, own) => {
101
+ const ownNames = new Set(own.map((x) => x.name));
102
+ return [...general.filter((x) => !ownNames.has(x.name)), ...own];
103
+ };
104
+ const rules = byName(globalRules, projectRules);
105
+ const skills = byName(globalSkills, projectSkills);
106
+ const sentinelAllow = unique([...global.sentinelAllow, ...project.sentinelAllow]);
42
107
  return {
43
108
  rules,
44
109
  skills,
45
110
  forbidden: {
46
- patterns: parseForbidden(forbiddenText),
47
- commands: parseForbiddenCommands(forbiddenCmdText),
48
- mcpTools: parseForbiddenMcpTools(forbiddenMcpText),
111
+ patterns: unique([...global.paths, ...project.paths]),
112
+ commands: unique([...global.commands, ...project.commands]),
113
+ mcpTools: unique([...global.mcpTools, ...project.mcpTools]),
49
114
  root: cwd,
50
115
  },
116
+ ...(sentinelAllow.length ? { sentinelAllow } : {}),
117
+ ...(projectContext !== null ? { contextAutoCompactTokens: projectContext } : globalContext !== null ? { contextAutoCompactTokens: globalContext } : {}),
51
118
  };
52
119
  }
53
120
  export { renderRules } from "./rules.js";