supipowers 1.5.2 → 2.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 (340) hide show
  1. package/README.md +14 -8
  2. package/bin/install.mjs +20 -5
  3. package/bin/install.ts +95 -0
  4. package/package.json +8 -4
  5. package/skills/context-mode/SKILL.md +17 -10
  6. package/skills/harness/SKILL.md +94 -0
  7. package/skills/ui-design/SKILL.md +63 -0
  8. package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
  9. package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
  10. package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
  11. package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
  12. package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
  13. package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
  14. package/skills/ultraplan-discover/SKILL.md +96 -0
  15. package/skills/ultraplan-intake/SKILL.md +89 -0
  16. package/skills/ultraplan-research/SKILL.md +129 -0
  17. package/skills/ultraplan-review/SKILL.md +86 -0
  18. package/skills/ultraplan-review-scope/SKILL.md +111 -0
  19. package/skills/ultraplan-review-structure/SKILL.md +120 -0
  20. package/skills/ultraplan-review-tdd/SKILL.md +142 -0
  21. package/skills/ultraplan-scout/SKILL.md +110 -0
  22. package/skills/ultraplan-synthesize/SKILL.md +124 -0
  23. package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
  24. package/src/ai/schema-text.ts +129 -0
  25. package/src/ai/structured-output.ts +274 -0
  26. package/src/ai/template.ts +27 -0
  27. package/src/bootstrap.ts +63 -28
  28. package/src/commands/agents.ts +149 -45
  29. package/src/commands/ai-review.ts +251 -30
  30. package/src/commands/clear.ts +434 -0
  31. package/src/commands/commit.ts +1 -0
  32. package/src/commands/config.ts +242 -44
  33. package/src/commands/context.ts +55 -28
  34. package/src/commands/doctor.ts +234 -6
  35. package/src/commands/fix-pr.ts +306 -131
  36. package/src/commands/generate.ts +111 -21
  37. package/src/commands/memory.ts +192 -0
  38. package/src/commands/model-picker.ts +28 -21
  39. package/src/commands/model.ts +19 -9
  40. package/src/commands/optimize-context.ts +408 -29
  41. package/src/commands/plan.ts +2 -0
  42. package/src/commands/qa.ts +312 -137
  43. package/src/commands/release.ts +259 -76
  44. package/src/commands/review.ts +293 -59
  45. package/src/commands/status.ts +200 -13
  46. package/src/commands/supi.ts +3 -35
  47. package/src/commands/ui-design.ts +394 -0
  48. package/src/commands/ultraplan.ts +1518 -0
  49. package/src/commands/update.ts +86 -0
  50. package/src/config/defaults.ts +62 -0
  51. package/src/config/loader.ts +448 -60
  52. package/src/config/schema.ts +108 -2
  53. package/src/context/optimizer.ts +25 -33
  54. package/src/context/rule-renderer.ts +223 -0
  55. package/src/context/savings.ts +258 -0
  56. package/src/context/startup-check.ts +380 -0
  57. package/src/context/startup-optimizer.ts +355 -0
  58. package/src/context/tokenignore.ts +146 -0
  59. package/src/context-mode/cache-handle.ts +49 -0
  60. package/src/context-mode/cache-preview.ts +71 -0
  61. package/src/context-mode/cache-store.ts +738 -0
  62. package/src/context-mode/compressor.ts +131 -26
  63. package/src/context-mode/dedup.ts +108 -0
  64. package/src/context-mode/detector.ts +35 -4
  65. package/src/context-mode/event-extractor.ts +14 -12
  66. package/src/context-mode/event-store.ts +91 -36
  67. package/src/context-mode/hooks.ts +798 -56
  68. package/src/context-mode/knowledge/store.ts +255 -11
  69. package/src/context-mode/memory-store.ts +325 -0
  70. package/src/context-mode/metrics-recorder.ts +158 -0
  71. package/src/context-mode/metrics-store.ts +765 -0
  72. package/src/context-mode/model.ts +24 -0
  73. package/src/context-mode/processor-keys.ts +29 -0
  74. package/src/context-mode/processors/build.ts +66 -0
  75. package/src/context-mode/processors/docker.ts +57 -0
  76. package/src/context-mode/processors/git.ts +111 -0
  77. package/src/context-mode/processors/json.ts +112 -0
  78. package/src/context-mode/processors/k8s.ts +67 -0
  79. package/src/context-mode/processors/lint.ts +67 -0
  80. package/src/context-mode/processors/log.ts +86 -0
  81. package/src/context-mode/processors/registry.ts +116 -0
  82. package/src/context-mode/processors/test-runner.ts +102 -0
  83. package/src/context-mode/processors/types.ts +20 -0
  84. package/src/context-mode/repomap.ts +400 -0
  85. package/src/context-mode/routing.ts +97 -24
  86. package/src/context-mode/sandbox/runners.ts +5 -1
  87. package/src/context-mode/snapshot-builder.ts +106 -11
  88. package/src/context-mode/source-hash.ts +173 -0
  89. package/src/context-mode/tool-name.ts +11 -0
  90. package/src/context-mode/tools.ts +654 -22
  91. package/src/context-mode/web/fetcher.ts +31 -12
  92. package/src/debug/logger.ts +2 -1
  93. package/src/deps/registry.ts +1 -1
  94. package/src/discipline/failure-summarizer.ts +170 -0
  95. package/src/discipline/failure-taxonomy.ts +131 -0
  96. package/src/discipline/workflow-invariants.ts +125 -0
  97. package/src/discovery/index.ts +31 -0
  98. package/src/discovery/lsp.ts +87 -0
  99. package/src/discovery/rank.ts +144 -0
  100. package/src/discovery/sources.ts +89 -0
  101. package/src/discovery/workflow.ts +87 -0
  102. package/src/docs/contracts.ts +39 -0
  103. package/src/docs/drift.ts +117 -87
  104. package/src/fix-pr/assessment.ts +200 -0
  105. package/src/fix-pr/contracts.ts +47 -0
  106. package/src/fix-pr/fetch-comments.ts +80 -0
  107. package/src/fix-pr/prompt-builder.ts +58 -40
  108. package/src/fix-pr/scripts/exec.ts +34 -0
  109. package/src/fix-pr/scripts/trigger-review.ts +106 -0
  110. package/src/fix-pr/scripts/wait-and-check.ts +108 -0
  111. package/src/fix-pr/types.ts +4 -0
  112. package/src/git/branch-finish.ts +5 -0
  113. package/src/git/commit-contract.ts +83 -0
  114. package/src/git/commit.ts +121 -184
  115. package/src/git/status.ts +62 -8
  116. package/src/harness/anti_slop/architecture-parser.ts +210 -0
  117. package/src/harness/anti_slop/backend-factory.ts +30 -0
  118. package/src/harness/anti_slop/backend.ts +140 -0
  119. package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
  120. package/src/harness/anti_slop/fallow-adapter.ts +305 -0
  121. package/src/harness/anti_slop/installer.ts +227 -0
  122. package/src/harness/anti_slop/queue.ts +216 -0
  123. package/src/harness/anti_slop/recommend.ts +84 -0
  124. package/src/harness/anti_slop/score.ts +180 -0
  125. package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
  126. package/src/harness/artifacts/agents-md.ts +88 -0
  127. package/src/harness/artifacts/checks-wiring.ts +57 -0
  128. package/src/harness/artifacts/docs-tree.ts +79 -0
  129. package/src/harness/artifacts/lint-configs.ts +136 -0
  130. package/src/harness/artifacts/review-agents.ts +67 -0
  131. package/src/harness/bare-entry.ts +108 -0
  132. package/src/harness/command.ts +1010 -0
  133. package/src/harness/default-agents/design.md +23 -0
  134. package/src/harness/default-agents/discover.md +18 -0
  135. package/src/harness/default-agents/implement.md +24 -0
  136. package/src/harness/default-agents/plan.md +19 -0
  137. package/src/harness/default-agents/research.md +21 -0
  138. package/src/harness/default-agents/validate.md +22 -0
  139. package/src/harness/gc/reporter.ts +28 -0
  140. package/src/harness/gc/runner.ts +136 -0
  141. package/src/harness/hooks/layer-context-inject.ts +155 -0
  142. package/src/harness/hooks/post-session-sweep.ts +130 -0
  143. package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
  144. package/src/harness/hooks/register.ts +118 -0
  145. package/src/harness/model.ts +117 -0
  146. package/src/harness/pipeline.ts +348 -0
  147. package/src/harness/project-paths.ts +235 -0
  148. package/src/harness/stage-runner.ts +107 -0
  149. package/src/harness/stages/design.ts +386 -0
  150. package/src/harness/stages/discover.ts +454 -0
  151. package/src/harness/stages/implement.ts +162 -0
  152. package/src/harness/stages/plan.ts +335 -0
  153. package/src/harness/stages/research.ts +263 -0
  154. package/src/harness/stages/validate.ts +684 -0
  155. package/src/harness/storage.ts +467 -0
  156. package/src/harness/tools.ts +426 -0
  157. package/src/lsp/bridge.ts +56 -95
  158. package/src/lsp/capabilities.ts +108 -0
  159. package/src/lsp/contracts.ts +35 -0
  160. package/src/lsp/detector.ts +8 -12
  161. package/src/markdown-frontmatter.ts +68 -0
  162. package/src/mempalace/bridge.ts +129 -0
  163. package/src/mempalace/config.ts +75 -0
  164. package/src/mempalace/format.ts +163 -0
  165. package/src/mempalace/hooks.ts +370 -0
  166. package/src/mempalace/installer-helper.ts +194 -0
  167. package/src/mempalace/python/mempalace_bridge.py +440 -0
  168. package/src/mempalace/runtime.ts +565 -0
  169. package/src/mempalace/schema.ts +264 -0
  170. package/src/mempalace/session-summary.ts +198 -0
  171. package/src/mempalace/tool.ts +186 -0
  172. package/src/mempalace/uv.ts +256 -0
  173. package/src/migrate/runner.ts +354 -0
  174. package/src/planning/approval-flow.ts +206 -9
  175. package/src/planning/plan-writer-prompt.ts +4 -3
  176. package/src/planning/planning-ask-tool.ts +39 -0
  177. package/src/planning/render-markdown.ts +74 -0
  178. package/src/planning/spec.ts +42 -0
  179. package/src/planning/system-prompt.ts +11 -8
  180. package/src/planning/validate.ts +84 -0
  181. package/src/platform/omp.ts +15 -2
  182. package/src/platform/system-prompt.ts +37 -0
  183. package/src/platform/test-utils.ts +3 -0
  184. package/src/platform/types.ts +6 -1
  185. package/src/qa/config.ts +12 -6
  186. package/src/qa/detect-app-type.ts +13 -6
  187. package/src/qa/matrix.ts +12 -6
  188. package/src/qa/prompt-builder.ts +28 -30
  189. package/src/qa/scripts/dev-server-utils.ts +72 -0
  190. package/src/qa/scripts/run-e2e-tests.ts +226 -0
  191. package/src/qa/scripts/start-dev-server.ts +138 -0
  192. package/src/qa/scripts/stop-dev-server.ts +77 -0
  193. package/src/qa/session.ts +13 -7
  194. package/src/quality/ai-setup.ts +27 -25
  195. package/src/quality/contracts.ts +34 -0
  196. package/src/quality/gates/ai-review.ts +20 -58
  197. package/src/quality/gates/command.ts +249 -46
  198. package/src/quality/review-gates.ts +18 -2
  199. package/src/quality/runner.ts +63 -22
  200. package/src/quality/schemas.ts +37 -2
  201. package/src/quality/setup.ts +96 -16
  202. package/src/release/changelog.ts +1 -1
  203. package/src/release/channels/custom.ts +13 -3
  204. package/src/release/channels/types.ts +5 -0
  205. package/src/release/contracts.ts +90 -0
  206. package/src/release/executor.ts +122 -45
  207. package/src/release/prompt.ts +18 -2
  208. package/src/release/targets.ts +86 -0
  209. package/src/release/version.ts +96 -71
  210. package/src/review/agent-loader.ts +298 -127
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +115 -14
  213. package/src/review/output.ts +12 -139
  214. package/src/review/runner.ts +12 -6
  215. package/src/review/scope.ts +144 -24
  216. package/src/review/types.ts +11 -20
  217. package/src/review/validator.ts +12 -6
  218. package/src/storage/fix-pr-sessions.ts +21 -14
  219. package/src/storage/plans.ts +14 -5
  220. package/src/storage/qa-sessions.ts +25 -19
  221. package/src/storage/reliability-metrics.ts +180 -0
  222. package/src/storage/reports.ts +8 -7
  223. package/src/storage/review-sessions.ts +55 -20
  224. package/src/tool-catalog/active-tool-controller.ts +164 -0
  225. package/src/tool-catalog/active-tool-planner.ts +212 -0
  226. package/src/tool-catalog/tool-groups.ts +102 -0
  227. package/src/types.ts +1401 -5
  228. package/src/ui-design/backend-adapter.ts +78 -0
  229. package/src/ui-design/backends/local-html.ts +82 -0
  230. package/src/ui-design/backends/pencil-mcp.ts +111 -0
  231. package/src/ui-design/components-scanner.ts +124 -0
  232. package/src/ui-design/config.ts +55 -0
  233. package/src/ui-design/pen-scanner.ts +95 -0
  234. package/src/ui-design/pen-selector.ts +72 -0
  235. package/src/ui-design/prompt-builder.ts +73 -0
  236. package/src/ui-design/scanner.ts +136 -0
  237. package/src/ui-design/session.ts +974 -0
  238. package/src/ui-design/system-prompt.ts +312 -0
  239. package/src/ui-design/tokens-scanner.ts +181 -0
  240. package/src/ui-design/types.ts +96 -0
  241. package/src/ultraplan/agent-catalog.ts +522 -0
  242. package/src/ultraplan/authoring/agent-catalog.ts +310 -0
  243. package/src/ultraplan/authoring/authoring-tools.ts +552 -0
  244. package/src/ultraplan/authoring/command-handlers.ts +339 -0
  245. package/src/ultraplan/authoring/markdown.ts +510 -0
  246. package/src/ultraplan/authoring/model.ts +162 -0
  247. package/src/ultraplan/authoring/pipeline.ts +319 -0
  248. package/src/ultraplan/authoring/stage-runner.ts +141 -0
  249. package/src/ultraplan/authoring/stages/approve.ts +249 -0
  250. package/src/ultraplan/authoring/stages/discover.ts +289 -0
  251. package/src/ultraplan/authoring/stages/intake.ts +203 -0
  252. package/src/ultraplan/authoring/stages/research.ts +399 -0
  253. package/src/ultraplan/authoring/stages/review.ts +333 -0
  254. package/src/ultraplan/authoring/stages/scout.ts +188 -0
  255. package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
  256. package/src/ultraplan/authoring/storage.ts +594 -0
  257. package/src/ultraplan/authoring/synth-gate.ts +165 -0
  258. package/src/ultraplan/authoring-draft.ts +653 -0
  259. package/src/ultraplan/authoring-persist.ts +180 -0
  260. package/src/ultraplan/authoring-tool.ts +608 -0
  261. package/src/ultraplan/authoring-wizard.ts +587 -0
  262. package/src/ultraplan/batch/merge.ts +98 -0
  263. package/src/ultraplan/batch/planner.ts +150 -0
  264. package/src/ultraplan/batch/presenter.ts +97 -0
  265. package/src/ultraplan/batch/storage.ts +420 -0
  266. package/src/ultraplan/batch/supervisor.ts +317 -0
  267. package/src/ultraplan/batch/worker.ts +26 -0
  268. package/src/ultraplan/batch/worktree.ts +110 -0
  269. package/src/ultraplan/contracts.ts +1593 -0
  270. package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
  271. package/src/ultraplan/default-agents/authoring/intake.md +12 -0
  272. package/src/ultraplan/default-agents/authoring/planner.md +12 -0
  273. package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
  274. package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
  275. package/src/ultraplan/default-agents/authoring/scout.md +12 -0
  276. package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
  277. package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
  278. package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
  279. package/src/ultraplan/default-agents/backend-executor.md +10 -0
  280. package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
  281. package/src/ultraplan/default-agents/backend-tester.md +10 -0
  282. package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
  283. package/src/ultraplan/default-agents/frontend-executor.md +10 -0
  284. package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
  285. package/src/ultraplan/default-agents/frontend-tester.md +10 -0
  286. package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
  287. package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
  288. package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
  289. package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
  290. package/src/ultraplan/execution/contract.ts +71 -0
  291. package/src/ultraplan/execution/policy.ts +217 -0
  292. package/src/ultraplan/execution/runtime-tools.ts +107 -0
  293. package/src/ultraplan/execution/session-runner.ts +281 -0
  294. package/src/ultraplan/next-router.ts +85 -0
  295. package/src/ultraplan/presenter.ts +359 -0
  296. package/src/ultraplan/project-paths.ts +342 -0
  297. package/src/ultraplan/runtime/active-execution.ts +72 -0
  298. package/src/ultraplan/runtime/apply-mutation.ts +416 -0
  299. package/src/ultraplan/runtime/blockers.ts +243 -0
  300. package/src/ultraplan/runtime/hook-bridge.ts +486 -0
  301. package/src/ultraplan/runtime/launch-context.ts +207 -0
  302. package/src/ultraplan/runtime/migration.ts +524 -0
  303. package/src/ultraplan/runtime/normalize.ts +281 -0
  304. package/src/ultraplan/runtime/proof.ts +260 -0
  305. package/src/ultraplan/runtime/reducer.ts +416 -0
  306. package/src/ultraplan/runtime/repair.ts +251 -0
  307. package/src/ultraplan/runtime/tracker-storage.ts +368 -0
  308. package/src/ultraplan/session-selection.ts +291 -0
  309. package/src/ultraplan/storage.ts +374 -0
  310. package/src/utils/editor.ts +38 -0
  311. package/src/utils/executable.ts +80 -0
  312. package/src/utils/paths.ts +1 -20
  313. package/src/utils/shell.ts +31 -0
  314. package/src/visual/companion.ts +2 -1
  315. package/src/visual/scripts/frame-template.html +60 -0
  316. package/src/visual/scripts/index.js +59 -13
  317. package/src/visual/scripts/package.json +3 -0
  318. package/src/visual/start-server.ts +2 -1
  319. package/src/workspace/git-scope.ts +64 -0
  320. package/src/workspace/locks.ts +23 -0
  321. package/src/workspace/package-manager.ts +117 -0
  322. package/src/workspace/path-mapping.ts +75 -0
  323. package/src/workspace/project-slug.ts +92 -0
  324. package/src/workspace/repo-root.ts +137 -0
  325. package/src/workspace/selector.ts +115 -0
  326. package/src/workspace/state-paths.ts +118 -0
  327. package/src/workspace/targets.ts +313 -0
  328. package/src/fix-pr/scripts/diff-comments.sh +0 -33
  329. package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
  330. package/src/fix-pr/scripts/trigger-review.sh +0 -36
  331. package/src/fix-pr/scripts/wait-and-check.sh +0 -37
  332. package/src/qa/scripts/detect-app-type.sh +0 -68
  333. package/src/qa/scripts/discover-routes.sh +0 -143
  334. package/src/qa/scripts/run-e2e-tests.sh +0 -131
  335. package/src/qa/scripts/start-dev-server.sh +0 -46
  336. package/src/qa/scripts/stop-dev-server.sh +0 -36
  337. package/src/review/prompts/fix-output-schema.md +0 -18
  338. package/src/review/prompts/review-output-schema.md +0 -38
  339. package/src/review/template.ts +0 -15
  340. /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
@@ -0,0 +1,370 @@
1
+ import type { Platform } from "../platform/types.js";
2
+ import { normalizeSystemPromptBlocks } from "../platform/system-prompt.js";
3
+ import type { SupipowersConfig } from "../types.js";
4
+ import { createMempalaceBridge, type MempalaceBridgeFacade } from "./bridge.js";
5
+ import { resolveDefaultWing, resolveMempalaceConfig, type ResolvedMempalaceConfig } from "./config.js";
6
+ import { getEventStore as getContextEventStore, getSessionId as getContextSessionId } from "../context-mode/hooks.js";
7
+ import { buildCompactionCheckpoint, buildShutdownDiary } from "./session-summary.js";
8
+
9
+ export interface MempalaceHooksDeps {
10
+ createBridge?: (config: ResolvedMempalaceConfig, cwd: string) => MempalaceBridgeFacade;
11
+ getEventStore?: () => Parameters<typeof buildCompactionCheckpoint>[0]["eventStore"];
12
+ getSessionId?: () => string;
13
+ now?: () => string;
14
+ }
15
+
16
+ const wakeUpCache = new Map<string, string>();
17
+
18
+ /**
19
+ * Per-session turn counter for wake-up cadence gating. The full wake-up block
20
+ * is injected on turn 1 and every Nth turn thereafter (where N is
21
+ * `mempalace.budgets.wakeUpInjectionEvery`); other turns get a one-line
22
+ * refresher. Cleared on session_start / session_switch.
23
+ */
24
+ const turnCounters = new Map<string, number>();
25
+
26
+ /** Test-only: reset cadence state between cases. */
27
+ export function _resetMempalaceHookState(): void {
28
+ wakeUpCache.clear();
29
+ turnCounters.clear();
30
+ }
31
+
32
+ function contextCwd(ctx: unknown): string {
33
+ return typeof ctx === "object" && ctx !== null && typeof (ctx as { cwd?: unknown }).cwd === "string"
34
+ ? (ctx as { cwd: string }).cwd
35
+ : process.cwd();
36
+ }
37
+
38
+ function sessionIdFrom(event: unknown, ctx: unknown): string {
39
+ for (const source of [event, ctx]) {
40
+ if (typeof source === "object" && source !== null) {
41
+ const value = (source as { sessionId?: unknown }).sessionId;
42
+ if (typeof value === "string" && value.length > 0) return value;
43
+ }
44
+ }
45
+ return "default-session";
46
+ }
47
+
48
+ function currentSystemPromptBlocks(event: unknown, ctx: unknown): string[] {
49
+ if (typeof ctx === "object" && ctx !== null && typeof (ctx as { getSystemPrompt?: unknown }).getSystemPrompt === "function") {
50
+ try {
51
+ const prompt = (ctx as { getSystemPrompt: () => unknown }).getSystemPrompt();
52
+ if (prompt != null) return normalizeSystemPromptBlocks(prompt);
53
+ } catch {
54
+ // best effort: fall back to event value
55
+ }
56
+ }
57
+ return normalizeSystemPromptBlocks(
58
+ typeof event === "object" && event !== null
59
+ ? (event as { systemPrompt?: unknown }).systemPrompt
60
+ : undefined,
61
+ );
62
+ }
63
+
64
+ function appendPrompt(base: unknown, block: string): { systemPrompt: string[] } {
65
+ return { systemPrompt: [...normalizeSystemPromptBlocks(base), block] };
66
+ }
67
+
68
+ function truncateByTokenBudget(text: string, tokens: number): string {
69
+ const maxChars = Math.max(200, tokens * 4);
70
+ if (text.length <= maxChars) return text;
71
+ return `${text.slice(0, maxChars - 1).trimEnd()}…`;
72
+ }
73
+
74
+ function wakeText(result: unknown): string {
75
+ if (typeof result === "string") return result;
76
+ if (typeof result === "object" && result !== null) {
77
+ const record = result as Record<string, unknown>;
78
+ for (const key of ["text", "summary", "content", "wake_up"]) {
79
+ if (typeof record[key] === "string") return record[key] as string;
80
+ }
81
+ }
82
+ return JSON.stringify(result ?? {});
83
+ }
84
+
85
+ function setupGuidanceBlock(resolved: ResolvedMempalaceConfig, wing: string): string {
86
+ return [
87
+ "# MemPalace memory",
88
+ `- palace: ${resolved.palacePath}`,
89
+ `- default wing: ${wing}`,
90
+ "- MemPalace runtime is not ready. Run `/supi:memory setup` (or call the `mempalace(action=\"setup\")` tool), then `mempalace(action=\"init\")` and `mempalace(action=\"mine\")` when appropriate.",
91
+ ].join("\n");
92
+ }
93
+
94
+ function wakeUpBlock(resolved: ResolvedMempalaceConfig, wing: string, text: string): string {
95
+ const excerpt = truncateByTokenBudget(text, resolved.budgets.wakeUpTokens);
96
+ const lines = [
97
+ "# MemPalace memory",
98
+ `- palace: ${resolved.palacePath}`,
99
+ `- default wing: ${wing}`,
100
+ ];
101
+ if (resolved.hooks.searchGuidance) {
102
+ lines.push(
103
+ "- You **MUST** call `mempalace(action=\"search\", query=...)` before answering questions about prior decisions, people, projects, or past events. Skip only when the answer is fully derivable from the current turn or the active codebase.",
104
+ );
105
+ }
106
+ if (excerpt.trim()) {
107
+ lines.push("", "## Wake-up excerpt", excerpt.trim());
108
+ }
109
+ return lines.join("\n");
110
+ }
111
+
112
+ /**
113
+ * Compact one-line refresher injected on turns where we skip the full
114
+ * wake-up dump. Keeps the model oriented (palace/wing) and re-asserts the
115
+ * RFC-2119 search nudge in ~140 chars instead of ~870 tokens.
116
+ */
117
+ function wakeUpRefresher(resolved: ResolvedMempalaceConfig, wing: string): string {
118
+ const lines = [
119
+ `# MemPalace memory: wing=${wing}`,
120
+ ];
121
+ if (resolved.hooks.searchGuidance) {
122
+ lines.push(
123
+ "- You **MUST** call `mempalace(action=\"search\", query=...)` before answering past-fact questions; per-turn search results appear below when relevant.",
124
+ );
125
+ }
126
+ return lines.join("\n");
127
+ }
128
+
129
+ /**
130
+ * Pull the user's incoming prompt out of the before_agent_start event.
131
+ * OMP exposes it as `event.prompt`; on agent-driven turns it may be empty.
132
+ */
133
+ function extractUserPrompt(event: unknown): string {
134
+ if (typeof event !== "object" || event === null) return "";
135
+ const value = (event as { prompt?: unknown }).prompt;
136
+ return typeof value === "string" ? value.trim() : "";
137
+ }
138
+
139
+ /** Minimum prompt length below which we skip auto-search (saves a bridge call). */
140
+ const AUTO_SEARCH_MIN_PROMPT_CHARS = 15;
141
+
142
+ /** Cap on the search query length. Long prompts are truncated to the first N chars. */
143
+ const AUTO_SEARCH_QUERY_MAX_CHARS = 500;
144
+
145
+ /**
146
+ * Returns `true` when `prompt` is a trivial acknowledgement that does not warrant
147
+ * a memory search (saves the bridge round-trip for "yes", "ok", "thanks", etc.).
148
+ */
149
+ function isTrivialPrompt(prompt: string): boolean {
150
+ const normalized = prompt.toLowerCase().replace(/[^\p{L}\p{N}\s]+/gu, " ").replace(/\s+/g, " ").trim();
151
+ if (normalized.length < AUTO_SEARCH_MIN_PROMPT_CHARS) return true;
152
+ // Conservative wordlist: only strip obvious filler.
153
+ const TRIVIAL = new Set(["yes", "no", "ok", "okay", "thanks", "thank you", "great", "cool", "go", "continue", "proceed"]);
154
+ return TRIVIAL.has(normalized);
155
+ }
156
+
157
+ interface SearchHit {
158
+ text?: unknown;
159
+ source_file?: unknown;
160
+ room?: unknown;
161
+ bm25_score?: unknown;
162
+ similarity?: unknown;
163
+ }
164
+
165
+ function pickHits(result: unknown): SearchHit[] {
166
+ if (typeof result !== "object" || result === null) return [];
167
+ const raw = (result as { results?: unknown }).results;
168
+ return Array.isArray(raw) ? raw.filter((hit): hit is SearchHit => typeof hit === "object" && hit !== null) : [];
169
+ }
170
+
171
+ /** Score-gated relevance check so we don't inject low-quality matches as noise. */
172
+ function isRelevantHit(hit: SearchHit): boolean {
173
+ const sim = typeof hit.similarity === "number" ? hit.similarity : null;
174
+ const bm25 = typeof hit.bm25_score === "number" ? hit.bm25_score : null;
175
+ // Either signal must clear a low bar. Mempalace's `similarity` is ~1.0 for
176
+ // perfect, ~0.5 for "kinda related"; bm25 is unbounded but >0.3 is meaningful.
177
+ if (sim !== null && sim >= 0.55) return true;
178
+ if (bm25 !== null && bm25 >= 0.3) return true;
179
+ return false;
180
+ }
181
+
182
+ /**
183
+ * Format relevant search hits as a compact bulleted section. Snippet length,
184
+ * per-hit cost, and total budget are all bounded so a single auto-search
185
+ * cannot blow the per-turn allowance.
186
+ */
187
+ function autoSearchBlock(hits: SearchHit[], budgetTokens: number): string | null {
188
+ if (hits.length === 0) return null;
189
+ const maxChars = Math.max(200, budgetTokens * 4);
190
+ const lines = ["", "## Relevant memories"];
191
+ let used = lines.reduce((acc, line) => acc + line.length + 1, 0);
192
+ for (const hit of hits) {
193
+ const source = typeof hit.source_file === "string" ? hit.source_file : "?";
194
+ const room = typeof hit.room === "string" ? hit.room : "?";
195
+ const text = typeof hit.text === "string" ? hit.text : "";
196
+ const snippet = text.replace(/\s+/g, " ").trim().slice(0, 120);
197
+ const entry = `- [${room}/${source}] ${snippet}${text.length > 120 ? "…" : ""}`;
198
+ if (used + entry.length + 1 > maxChars) break;
199
+ lines.push(entry);
200
+ used += entry.length + 1;
201
+ }
202
+ if (lines.length === 2) return null; // header only
203
+ return lines.join("\n");
204
+ }
205
+
206
+ export function registerMempalaceHooks(
207
+ platform: Platform,
208
+ config: SupipowersConfig,
209
+ deps: MempalaceHooksDeps = {},
210
+ ): void {
211
+ if (!config.mempalace.enabled) return;
212
+
213
+ const clearAll = () => {
214
+ wakeUpCache.clear();
215
+ turnCounters.clear();
216
+ };
217
+ platform.on("session_start", clearAll);
218
+ platform.on("session_switch", clearAll);
219
+
220
+ platform.on("before_agent_start", async (event: unknown, ctx: unknown) => {
221
+ const wakeUpEnabled = config.mempalace.hooks.wakeUp;
222
+ const guidanceEnabled = config.mempalace.hooks.searchGuidance;
223
+ const autoSearchEnabled = config.mempalace.hooks.autoSearchOnPrompt;
224
+ if (!wakeUpEnabled && !guidanceEnabled && !autoSearchEnabled) return undefined;
225
+
226
+ const cwd = contextCwd(ctx);
227
+ const resolved = resolveMempalaceConfig(config, cwd, platform.paths);
228
+ let wing: string;
229
+ try {
230
+ wing = resolveDefaultWing(resolved, cwd, platform.paths);
231
+ } catch {
232
+ wing = "project";
233
+ }
234
+ const sessionId = sessionIdFrom(event, ctx);
235
+ const cacheKey = `${sessionId}|${wing}|${resolved.palacePath}`;
236
+ const basePrompt = currentSystemPromptBlocks(event, ctx);
237
+ const userPrompt = extractUserPrompt(event);
238
+
239
+ const bridge = deps.createBridge
240
+ ? deps.createBridge(resolved, cwd)
241
+ : createMempalaceBridge({ cwd, config: resolved });
242
+
243
+ // Cadence gating: wake-up dump on turn 1 and every Nth turn; refresher
244
+ // otherwise. Saves ~750 tokens/turn average for a default cadence of 10.
245
+ const cadence = Math.max(1, Math.floor(resolved.budgets.wakeUpInjectionEvery));
246
+ const turnKey = `${sessionId}|${wing}|${resolved.palacePath}`;
247
+ const turnCount = (turnCounters.get(turnKey) ?? 0) + 1;
248
+ turnCounters.set(turnKey, turnCount);
249
+ const isFullInjectionTurn = turnCount === 1 || turnCount % cadence === 0;
250
+
251
+ // Run the cached generic wake_up and the per-prompt search in parallel.
252
+ // wake_up is cached for the session; auto-search is fresh every turn.
253
+ const wakePromise = (async (): Promise<string> => {
254
+ if (!isFullInjectionTurn) return wakeUpRefresher(resolved, wing);
255
+ const cached = wakeUpCache.get(cacheKey);
256
+ if (cached) return cached;
257
+ const wake = await bridge.execute({ action: "wake_up", wing, timeout: resolved.timeouts.hookMs });
258
+ const block = wake.ok
259
+ ? wakeUpBlock(resolved, wing, wakeText(wake.result))
260
+ : setupGuidanceBlock(resolved, wing);
261
+ wakeUpCache.set(cacheKey, block);
262
+ return block;
263
+ })();
264
+
265
+ const searchPromise = (async (): Promise<string | null> => {
266
+ if (!autoSearchEnabled) return null;
267
+ if (!userPrompt || isTrivialPrompt(userPrompt)) return null;
268
+ const query = userPrompt.slice(0, AUTO_SEARCH_QUERY_MAX_CHARS);
269
+ try {
270
+ const result = await bridge.execute({
271
+ action: "search",
272
+ query,
273
+ wing,
274
+ limit: 3,
275
+ timeout: resolved.timeouts.hookMs,
276
+ });
277
+ if (!result.ok) return null;
278
+ const hits = pickHits(result.result).filter(isRelevantHit);
279
+ return autoSearchBlock(hits, resolved.budgets.autoSearchTokens);
280
+ } catch {
281
+ // Auto-search is best-effort. A failure here must never block the turn.
282
+ return null;
283
+ }
284
+ })();
285
+
286
+ const [wakeBlock, searchBlock] = await Promise.all([wakePromise, searchPromise]);
287
+ const combined = searchBlock ? `${wakeBlock}\n${searchBlock}` : wakeBlock;
288
+ return appendPrompt(basePrompt, combined);
289
+ });
290
+
291
+ if (config.mempalace.hooks.compactionCheckpoint) {
292
+ platform.on("session_before_compact", async (_event: unknown, ctx: unknown) => {
293
+ try {
294
+ const cwd = contextCwd(ctx);
295
+ const resolved = resolveMempalaceConfig(config, cwd, platform.paths);
296
+ let wing: string;
297
+ try {
298
+ wing = resolveDefaultWing(resolved, cwd, platform.paths);
299
+ } catch {
300
+ wing = "project";
301
+ }
302
+ const sessionId = deps.getSessionId?.() ?? getContextSessionId();
303
+ const checkpoint = buildCompactionCheckpoint({
304
+ cwd,
305
+ sessionId,
306
+ wing,
307
+ defaultAgentName: resolved.defaultAgentName,
308
+ now: deps.now?.(),
309
+ eventStore: deps.getEventStore?.() ?? getContextEventStore(),
310
+ maxChars: resolved.budgets.diaryChars,
311
+ });
312
+ const bridge = deps.createBridge
313
+ ? deps.createBridge(resolved, cwd)
314
+ : createMempalaceBridge({ cwd, config: resolved });
315
+ await bridge.execute({
316
+ action: "add_drawer",
317
+ wing: checkpoint.metadata.wing,
318
+ room: checkpoint.metadata.room,
319
+ content: checkpoint.content,
320
+ added_by: checkpoint.metadata.added_by,
321
+ source_file: checkpoint.metadata.source_file,
322
+ timeout: resolved.timeouts.hookMs,
323
+ });
324
+ } catch {
325
+ // Compaction must never be cancelled by MemPalace checkpoint failures.
326
+ }
327
+ return undefined;
328
+ });
329
+ }
330
+
331
+ if (config.mempalace.hooks.shutdownDiary) {
332
+ platform.on("session_shutdown", async (_event: unknown, ctx: unknown) => {
333
+ try {
334
+ const cwd = contextCwd(ctx);
335
+ const resolved = resolveMempalaceConfig(config, cwd, platform.paths);
336
+ let wing: string;
337
+ try {
338
+ wing = resolveDefaultWing(resolved, cwd, platform.paths);
339
+ } catch {
340
+ wing = "project";
341
+ }
342
+ const sessionId = deps.getSessionId?.() ?? getContextSessionId();
343
+ const diary = buildShutdownDiary({
344
+ cwd,
345
+ sessionId,
346
+ wing,
347
+ defaultAgentName: resolved.defaultAgentName,
348
+ now: deps.now?.(),
349
+ eventStore: deps.getEventStore?.() ?? getContextEventStore(),
350
+ maxChars: resolved.budgets.diaryChars,
351
+ });
352
+ const bridge = deps.createBridge
353
+ ? deps.createBridge(resolved, cwd)
354
+ : createMempalaceBridge({ cwd, config: resolved });
355
+ await bridge.execute({
356
+ action: "diary_write",
357
+ agent_name: diary.metadata.agent_name,
358
+ wing: diary.metadata.wing,
359
+ topic: diary.metadata.topic,
360
+ entry: diary.entry,
361
+ source_file: diary.metadata.source_file,
362
+ timeout: resolved.timeouts.hookMs,
363
+ });
364
+ } catch {
365
+ // Shutdown must never be delayed or failed by MemPalace diary writes.
366
+ }
367
+ return undefined;
368
+ });
369
+ }
370
+ }
@@ -0,0 +1,194 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { DEFAULT_CONFIG } from "../config/defaults.js";
4
+ import type { Platform, PlatformPaths } from "../platform/types.js";
5
+ import type { SupipowersConfig } from "../types.js";
6
+ import { createMempalaceBridge, type MempalaceBridgeFacade } from "./bridge.js";
7
+ import { resolveDefaultWing, resolveMempalaceConfig } from "./config.js";
8
+ import {
9
+ resolveBridgeScriptPath,
10
+ resolveManagedVenvPaths,
11
+ setupMempalaceRuntime,
12
+ type ProcessRunner,
13
+ type SetupMempalaceRuntimeResult,
14
+ } from "./runtime.js";
15
+
16
+ export interface MempalaceInstallSnapshot {
17
+ enabled: boolean;
18
+ packageVersion: string;
19
+ managedBinDir: string;
20
+ uvPath: string;
21
+ uvInstalled: boolean;
22
+ venvPath: string;
23
+ venvPython: string;
24
+ venvInstalled: boolean;
25
+ bridgeOk: boolean;
26
+ bridgePath: string;
27
+ /** True when uv binary, managed venv, and bridge script are all present. */
28
+ ready: boolean;
29
+ }
30
+
31
+ /**
32
+ * Inspect the filesystem for the artifacts MemPalace setup writes.
33
+ *
34
+ * Cheap (no subprocesses, no network); safe to call from interactive prompts to
35
+ * decide whether to phrase the question as "Install" vs "Update".
36
+ */
37
+ export function snapshotMempalaceInstall(
38
+ paths: PlatformPaths,
39
+ cwd: string,
40
+ config: SupipowersConfig = DEFAULT_CONFIG,
41
+ ): MempalaceInstallSnapshot {
42
+ const resolved = resolveMempalaceConfig(config, cwd, paths);
43
+ const venv = resolveManagedVenvPaths(resolved.managedVenvPath);
44
+ const managedBinDir = paths.global("bin");
45
+ const uvBinary = process.platform === "win32" ? "uv.exe" : "uv";
46
+ const uvPath = path.join(managedBinDir, uvBinary);
47
+ const bridge = resolveBridgeScriptPath();
48
+
49
+ const uvInstalled = existsSync(uvPath);
50
+ const venvInstalled = existsSync(venv.python);
51
+
52
+ return {
53
+ enabled: resolved.enabled,
54
+ packageVersion: resolved.packageVersion,
55
+ managedBinDir,
56
+ uvPath,
57
+ uvInstalled,
58
+ venvPath: venv.root,
59
+ venvPython: venv.python,
60
+ venvInstalled,
61
+ bridgeOk: bridge.ok,
62
+ bridgePath: bridge.path,
63
+ ready: uvInstalled && venvInstalled && bridge.ok,
64
+ };
65
+ }
66
+
67
+ export interface RunMempalaceSetupOptions {
68
+ paths: PlatformPaths;
69
+ cwd: string;
70
+ config?: SupipowersConfig;
71
+ runner: ProcessRunner;
72
+ onProgress?: (message: string) => void;
73
+ }
74
+
75
+ /**
76
+ * Drive the MemPalace install/update pipeline using the same managed paths every
77
+ * caller (the `/supi:memory setup` command, the `bunx supipowers` installer, and
78
+ * the `/supi:update` command) should agree on.
79
+ *
80
+ * Returns the structured result from `setupMempalaceRuntime`. Callers decide how
81
+ * to surface progress and errors (spinner vs. notify vs. raw stdout).
82
+ */
83
+ export async function runMempalaceSetup(
84
+ options: RunMempalaceSetupOptions,
85
+ ): Promise<SetupMempalaceRuntimeResult> {
86
+ const config = options.config ?? DEFAULT_CONFIG;
87
+ const resolved = resolveMempalaceConfig(config, options.cwd, options.paths);
88
+ const bridge = resolveBridgeScriptPath();
89
+ if (!bridge.ok) {
90
+ return { ok: false, error: bridge.error };
91
+ }
92
+ return await setupMempalaceRuntime({
93
+ cwd: options.cwd,
94
+ config: resolved,
95
+ bridgeScriptPath: bridge.path,
96
+ managedBinDir: options.paths.global("bin"),
97
+ runner: options.runner,
98
+ onProgress: options.onProgress,
99
+ });
100
+ }
101
+
102
+ export interface MempalaceInitState {
103
+ wing: string;
104
+ initialized: boolean;
105
+ /** Set when the bridge call failed (palace missing, timeout, etc.); treat as not-initialized. */
106
+ bridgeError?: { code: string; message: string };
107
+ }
108
+
109
+ function isWingPresent(result: unknown, wing: string): boolean {
110
+ if (!result || typeof result !== "object") return false;
111
+ const record = result as Record<string, unknown>;
112
+ const candidates = [record.wings, record.items, record.results];
113
+ for (const list of candidates) {
114
+ if (!Array.isArray(list)) continue;
115
+ for (const entry of list) {
116
+ if (typeof entry === "string" && entry === wing) return true;
117
+ if (entry && typeof entry === "object") {
118
+ const e = entry as Record<string, unknown>;
119
+ if (e.name === wing || e.wing === wing || e.id === wing) return true;
120
+ }
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Determine whether the current project's wing has been registered in the palace.
128
+ *
129
+ * Calls `list_wings` through the bridge facade and looks for the resolved default
130
+ * wing in the response. On any failure (palace missing, timeout, malformed
131
+ * response) the wing is treated as not initialized — the steered init step is
132
+ * idempotent on already-initialized wings, so a false negative is safe.
133
+ */
134
+ export async function checkMempalaceProjectInitialized(options: {
135
+ paths: PlatformPaths;
136
+ cwd: string;
137
+ config?: SupipowersConfig;
138
+ bridge?: MempalaceBridgeFacade;
139
+ }): Promise<MempalaceInitState> {
140
+ const config = options.config ?? DEFAULT_CONFIG;
141
+ const resolved = resolveMempalaceConfig(config, options.cwd, options.paths);
142
+ let wing: string;
143
+ try {
144
+ wing = resolveDefaultWing(resolved, options.cwd, options.paths);
145
+ } catch {
146
+ wing = "project";
147
+ }
148
+ const bridge = options.bridge ?? createMempalaceBridge({ cwd: options.cwd, config: resolved });
149
+ const result = await bridge.execute({ action: "list_wings" });
150
+ if (!result.ok) {
151
+ return {
152
+ wing,
153
+ initialized: false,
154
+ bridgeError: { code: result.error.code, message: result.error.message },
155
+ };
156
+ }
157
+ return { wing, initialized: isWingPresent(result.result, wing) };
158
+ }
159
+
160
+ /**
161
+ * Steer the active model toward initializing the project's MemPalace wing.
162
+ *
163
+ * Caller should only invoke this after `setupMempalaceRuntime` succeeded and
164
+ * `checkMempalaceProjectInitialized` returned `initialized: false`. Returns
165
+ * `false` when steering is unavailable (no `sendMessage` on the platform).
166
+ */
167
+ export function steerMempalaceInitialization(
168
+ platform: Platform,
169
+ state: { wing: string; cwd: string },
170
+ ): boolean {
171
+ if (typeof platform.sendMessage !== "function") return false;
172
+ const text = [
173
+ "# MemPalace memory: initialize project wing",
174
+ "",
175
+ `MemPalace setup just completed. The current project's wing (\`${state.wing}\`) is not yet initialized in the palace.`,
176
+ "",
177
+ "Please initialize and seed memory for this project by running these tool calls in order:",
178
+ "",
179
+ `1. \`mempalace(action="init", dir=".", yes=true)\` — register this project's wing in the palace.`,
180
+ `2. \`mempalace(action="mine", dir=".", limit=20)\` — seed initial drawers from project files.`,
181
+ "",
182
+ "Step 2 is recommended but optional; skip it if the user prefers an empty wing or is mid-task. After running, summarize what was indexed.",
183
+ ].join("\n");
184
+
185
+ platform.sendMessage(
186
+ {
187
+ customType: "supi-mempalace-init",
188
+ content: [{ type: "text", text }],
189
+ display: "none",
190
+ },
191
+ { deliverAs: "steer", triggerTurn: true },
192
+ );
193
+ return true;
194
+ }