supipowers 1.5.3 → 2.0.1

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 +131 -42
  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 +18 -8
  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 +135 -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 +268 -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 +221 -109
  211. package/src/review/fixer.ts +10 -6
  212. package/src/review/multi-agent-runner.ts +114 -13
  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 +1 -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 +1399 -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,355 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { ParsedSkill, PromptSection } from "./analyzer.js";
3
+ import type { TechStack } from "./optimizer.js";
4
+
5
+ export const STARTUP_OPTIMIZER_MANIFEST_VERSION = 1;
6
+ export const TARGET_STARTUP_PROMPT_BYTES = 8_000 * 4;
7
+ export const AGENTS_SPLIT_THRESHOLD_BYTES = TARGET_STARTUP_PROMPT_BYTES;
8
+
9
+ export type OptimizationSourceType = "skill" | "section";
10
+ export type RuleMode = "ttsr" | "rulebook";
11
+
12
+ export interface OptimizationSource {
13
+ sourceId: string;
14
+ sourceName: string;
15
+ sourceType: OptimizationSourceType;
16
+ sourceHash: string;
17
+ slug: string;
18
+ bytes: number;
19
+ tokens: number;
20
+ content: string;
21
+ }
22
+
23
+ export interface WriteRuleAction {
24
+ kind: "write-rule";
25
+ mode: RuleMode;
26
+ sourceId: string;
27
+ sourceName: string;
28
+ sourceHash: string;
29
+ slug: string;
30
+ targetPath: string;
31
+ sourceBytes: number;
32
+ estimatedSavedBytes: number;
33
+ sourceContent: string;
34
+ condition?: string;
35
+ description?: string;
36
+ }
37
+
38
+ export type ManualDisableReason = "source-still-loaded" | "tech-stack-irrelevant";
39
+
40
+ export interface ManualDisableAction {
41
+ kind: "manual-disable";
42
+ reason: ManualDisableReason;
43
+ sourceId: string;
44
+ sourceName: string;
45
+ sourceHash: string;
46
+ slug: string;
47
+ remediation: string;
48
+ }
49
+
50
+ export interface ManualAgentsSplitAction {
51
+ kind: "manual-agents-split";
52
+ sourceId: string;
53
+ sourceName: string;
54
+ sourceHash: string;
55
+ sourceBytes: number;
56
+ thresholdBytes: number;
57
+ remediation: string;
58
+ }
59
+
60
+ export type ManualOptimizationAction = ManualDisableAction | ManualAgentsSplitAction;
61
+ export type OptimizationAction = WriteRuleAction | ManualOptimizationAction;
62
+
63
+ export interface OptimizationWarning {
64
+ code: string;
65
+ message: string;
66
+ sourceId?: string;
67
+ }
68
+
69
+ export interface OptimizationPlan {
70
+ version: typeof STARTUP_OPTIMIZER_MANIFEST_VERSION;
71
+ targetBytes: number;
72
+ sourceSetHash: string;
73
+ beforeBytes: number;
74
+ estimatedAfterBytes: number;
75
+ estimatedSavedBytes: number;
76
+ sources: OptimizationSource[];
77
+ actions: OptimizationAction[];
78
+ warnings: OptimizationWarning[];
79
+ }
80
+
81
+ export interface BuildOptimizationPlanInput {
82
+ /**
83
+ * Original system prompt text. Used as the authoritative `beforeBytes` so the
84
+ * optimizer estimate matches the actual prompt size, even when `sections` and
85
+ * `skills` overlap (e.g. when `# Skills` content is also captured inside a
86
+ * containing section).
87
+ */
88
+ prompt: string;
89
+ sections: PromptSection[];
90
+ skills: ParsedSkill[];
91
+ techStack: TechStack;
92
+ }
93
+
94
+ interface BehaviorSkillSpec {
95
+ mode: "ttsr";
96
+ condition: string;
97
+ }
98
+
99
+ const BEHAVIOR_SKILLS: Record<string, BehaviorSkillSpec> = {
100
+ debugging: {
101
+ mode: "ttsr",
102
+ condition: String.raw`\b(?:debug(?:ging)?|root\s+cause|investigate|repro(?:duce|duction)?|failing\s+test)\b`,
103
+ },
104
+ tdd: {
105
+ mode: "ttsr",
106
+ condition: String.raw`\b(?:tdd|test\s+first|failing\s+test\s+first|red[-\s]+green[-\s]+refactor)\b`,
107
+ },
108
+ verification: {
109
+ mode: "ttsr",
110
+ condition: String.raw`\b(?:verify|verification|evidence|prove|proof|run\s+(?:the\s+)?(?:focused\s+)?tests?)\b`,
111
+ },
112
+ "receiving-code-review": {
113
+ mode: "ttsr",
114
+ condition: String.raw`\b(?:pr\s+feedback|code\s+review\s+comments?|reviewer\s+feedback|review\s+comments?)\b`,
115
+ },
116
+ };
117
+
118
+ const TECH_STACK_SKILLS: Record<string, { anyOf: Array<keyof TechStack>; values: string[] }> = {
119
+ "shadcn-ui": {
120
+ anyOf: ["frameworks", "tools"],
121
+ values: ["react", "next", "shadcn", "tailwind"],
122
+ },
123
+ "better-auth": {
124
+ anyOf: ["languages", "frameworks", "tools", "runtime"],
125
+ values: ["typescript", "javascript", "react", "next", "node", "bun"],
126
+ },
127
+ playwright: {
128
+ anyOf: ["tools"],
129
+ values: ["playwright"],
130
+ },
131
+ };
132
+
133
+ const ACTION_KIND_ORDER: Record<OptimizationAction["kind"], number> = {
134
+ "write-rule": 0,
135
+ "manual-disable": 1,
136
+ "manual-agents-split": 2,
137
+ };
138
+
139
+ export function hashOptimizationSource(content: string): string {
140
+ return createHash("sha256").update(content).digest("hex");
141
+ }
142
+
143
+ export function slugifyOptimizationSource(value: string): string {
144
+ const slug = value
145
+ .trim()
146
+ .toLowerCase()
147
+ .replace(/[^a-z0-9]+/g, "-")
148
+ .replace(/^-+|-+$/g, "");
149
+ return slug || "source";
150
+ }
151
+
152
+ export function sourceSetHash(sources: Pick<OptimizationSource, "sourceId" | "sourceHash">[]): string {
153
+ const body = [...sources]
154
+ .sort((a, b) => a.sourceId.localeCompare(b.sourceId))
155
+ .map((source) => `${source.sourceId}:${source.sourceHash}`)
156
+ .join("\n");
157
+ return hashOptimizationSource(body);
158
+ }
159
+
160
+ export function buildOptimizationPlan(input: BuildOptimizationPlanInput): OptimizationPlan {
161
+ const sources = buildSources(input.sections, input.skills);
162
+ const actions: OptimizationAction[] = [];
163
+ const warnings: OptimizationWarning[] = [];
164
+
165
+ for (const source of sources) {
166
+ if (source.sourceType === "skill") {
167
+ const canonicalName = source.sourceName.toLowerCase();
168
+ const behavior = BEHAVIOR_SKILLS[canonicalName];
169
+ if (behavior) {
170
+ actions.push(buildWriteRuleAction(source, behavior.mode, behavior.condition));
171
+ actions.push(buildManualDisableAction(source, "source-still-loaded"));
172
+ continue;
173
+ }
174
+
175
+ if (isTechStackIrrelevant(canonicalName, input.techStack)) {
176
+ actions.push(buildManualDisableAction(source, "tech-stack-irrelevant"));
177
+ continue;
178
+ }
179
+
180
+ actions.push(buildWriteRuleAction(source, "rulebook"));
181
+ actions.push(buildManualDisableAction(source, "source-still-loaded"));
182
+ continue;
183
+ }
184
+
185
+ if (source.sourceName === "AGENTS.md" && source.bytes > AGENTS_SPLIT_THRESHOLD_BYTES) {
186
+ actions.push({
187
+ kind: "manual-agents-split",
188
+ sourceId: source.sourceId,
189
+ sourceName: source.sourceName,
190
+ sourceHash: source.sourceHash,
191
+ sourceBytes: source.bytes,
192
+ thresholdBytes: AGENTS_SPLIT_THRESHOLD_BYTES,
193
+ remediation:
194
+ "Split oversized AGENTS.md guidance into smaller scoped files or managed rules so startup context stays under the L6 target.",
195
+ });
196
+ }
197
+ }
198
+
199
+ // beforeBytes comes from the original prompt, not from summing source bytes:
200
+ // sections and skills overlap in practice (e.g. `# Skills` content appears
201
+ // inside a containing section), so summing would double-count.
202
+ const beforeBytes = byteLength(input.prompt);
203
+
204
+ // Sources whose content the user is expected to actually remove from the
205
+ // startup prompt: write-rule companions and tech-stack manual-disable
206
+ // actions. Deduplicated by sourceId because a write-rule action is paired
207
+ // with a `source-still-loaded` manual-disable for the same source.
208
+ const removedSourceIds = new Set<string>();
209
+ for (const action of actions) {
210
+ if (action.kind === "write-rule" || action.kind === "manual-disable") {
211
+ removedSourceIds.add(action.sourceId);
212
+ }
213
+ }
214
+ const estimatedSavedBytes = sources
215
+ .filter((source) => removedSourceIds.has(source.sourceId))
216
+ .reduce((sum, source) => sum + source.bytes, 0);
217
+ const estimatedAfterBytes = Math.max(0, beforeBytes - estimatedSavedBytes);
218
+
219
+ return {
220
+ version: STARTUP_OPTIMIZER_MANIFEST_VERSION,
221
+ targetBytes: TARGET_STARTUP_PROMPT_BYTES,
222
+ sourceSetHash: sourceSetHash(sources),
223
+ beforeBytes,
224
+ estimatedAfterBytes,
225
+ estimatedSavedBytes,
226
+ sources,
227
+ actions: sortActions(actions),
228
+ warnings,
229
+ };
230
+ }
231
+
232
+ function byteLength(value: string): number {
233
+ return new TextEncoder().encode(value).length;
234
+ }
235
+
236
+ function buildSources(sections: PromptSection[], skills: ParsedSkill[]): OptimizationSource[] {
237
+ const sourceMap = new Map<string, OptimizationSource>();
238
+
239
+ for (const skill of skills) {
240
+ const sourceName = skill.name.trim();
241
+ const canonicalName = sourceName.toLowerCase();
242
+ const sourceId = `skill:${canonicalName}`;
243
+ const content = canonicalSourceContent(skill.content);
244
+ sourceMap.set(sourceId, {
245
+ sourceId,
246
+ sourceName: canonicalName,
247
+ sourceType: "skill",
248
+ sourceHash: hashOptimizationSource(content),
249
+ slug: slugifyOptimizationSource(sourceId),
250
+ bytes: byteLength(content),
251
+ tokens: Math.ceil(content.length / 4),
252
+ content,
253
+ });
254
+ }
255
+
256
+ for (const section of sections) {
257
+ const sourceName = section.label.trim();
258
+ const sourceId = `section:${sourceName}`;
259
+ const content = canonicalSourceContent(section.content);
260
+ sourceMap.set(sourceId, {
261
+ sourceId,
262
+ sourceName,
263
+ sourceType: "section",
264
+ sourceHash: hashOptimizationSource(content),
265
+ slug: slugifyOptimizationSource(sourceId),
266
+ bytes: byteLength(content),
267
+ tokens: Math.ceil(content.length / 4),
268
+ content,
269
+ });
270
+ }
271
+
272
+ return [...sourceMap.values()].sort((a, b) => a.sourceId.localeCompare(b.sourceId));
273
+ }
274
+
275
+ /**
276
+ * Canonicalize a source body for hashing/sizing: strip a single trailing newline.
277
+ *
278
+ * The renderer adds a trailing newline at file write time for POSIX-friendly EOF,
279
+ * and the parser strips it back. Hashing canonical (no-trailing-newline) form keeps
280
+ * the round trip stable regardless of whether the caller passed content with or
281
+ * without an EOF newline.
282
+ */
283
+ function canonicalSourceContent(content: string): string {
284
+ if (content.endsWith("\r\n")) return content.slice(0, -2);
285
+ if (content.endsWith("\n")) return content.slice(0, -1);
286
+ return content;
287
+ }
288
+
289
+ function buildWriteRuleAction(
290
+ source: OptimizationSource,
291
+ mode: RuleMode,
292
+ condition?: string,
293
+ ): WriteRuleAction {
294
+ return {
295
+ kind: "write-rule",
296
+ mode,
297
+ sourceId: source.sourceId,
298
+ sourceName: source.sourceName,
299
+ sourceHash: source.sourceHash,
300
+ slug: source.slug,
301
+ targetPath: `.omp/rules/${source.slug}.md`,
302
+ sourceBytes: source.bytes,
303
+ estimatedSavedBytes: source.bytes,
304
+ sourceContent: source.content,
305
+ ...(condition ? { condition } : {}),
306
+ };
307
+ }
308
+
309
+ function buildManualDisableAction(
310
+ source: OptimizationSource,
311
+ reason: ManualDisableReason,
312
+ ): ManualDisableAction {
313
+ const remediation = reason === "tech-stack-irrelevant"
314
+ ? `Disable ${source.sourceName}; it is not relevant to the detected project tech stack.`
315
+ : `Disable the original ${source.sourceName} skill once the managed rule is available so startup context is actually reduced.`;
316
+
317
+ return {
318
+ kind: "manual-disable",
319
+ reason,
320
+ sourceId: source.sourceId,
321
+ sourceName: source.sourceName,
322
+ sourceHash: source.sourceHash,
323
+ slug: source.slug,
324
+ remediation,
325
+ };
326
+ }
327
+
328
+ function isTechStackIrrelevant(skillName: string, techStack: TechStack): boolean {
329
+ const rule = TECH_STACK_SKILLS[skillName];
330
+ if (!rule) return false;
331
+
332
+ for (const key of rule.anyOf) {
333
+ const value = techStack[key];
334
+ if (Array.isArray(value)) {
335
+ if (value.some((entry) => rule.values.includes(entry.toLowerCase()))) return false;
336
+ } else if (typeof value === "string" && rule.values.includes(value.toLowerCase())) {
337
+ return false;
338
+ }
339
+ }
340
+
341
+ return true;
342
+ }
343
+
344
+ function sortActions(actions: OptimizationAction[]): OptimizationAction[] {
345
+ return [...actions].sort((a, b) => {
346
+ const kindDelta = ACTION_KIND_ORDER[a.kind] - ACTION_KIND_ORDER[b.kind];
347
+ if (kindDelta !== 0) return kindDelta;
348
+ const sourceDelta = a.sourceId.localeCompare(b.sourceId);
349
+ if (sourceDelta !== 0) return sourceDelta;
350
+ if (a.kind === "write-rule" && b.kind === "write-rule") {
351
+ return a.mode.localeCompare(b.mode);
352
+ }
353
+ return 0;
354
+ });
355
+ }
@@ -0,0 +1,146 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export const TOKENIGNORE_BEGIN_MARKER = "# BEGIN supipowers context optimizer managed block";
4
+ export const TOKENIGNORE_END_MARKER = "# END supipowers context optimizer managed block";
5
+
6
+ export const DEFAULT_TOKENIGNORE_ENTRIES = [
7
+ ".omp/supipowers/reviews/",
8
+ ".omp/supipowers/debug/",
9
+ ".omp/supipowers/reports/",
10
+ ".omp/supipowers/fix-pr-sessions/",
11
+ ".omp/supipowers/qa-sessions/",
12
+ ".omp/supipowers/ui-design/",
13
+ ".omp/supipowers/visual/",
14
+ ".omp/supipowers/sessions/*.db",
15
+ ".omp/supipowers/sessions/*.db-*",
16
+ "dist/",
17
+ "coverage/",
18
+ ];
19
+
20
+ export interface ManagedTokenignoreMergeResult {
21
+ content: string;
22
+ entries: string[];
23
+ hash: string;
24
+ }
25
+
26
+ export type ParsedManagedTokenignore =
27
+ | { status: "unmanaged"; managed: false }
28
+ | {
29
+ status: "managed";
30
+ managed: true;
31
+ entries: string[];
32
+ hash: string;
33
+ expectedHash: string;
34
+ hashMatches: boolean;
35
+ }
36
+ | { status: "malformed"; managed: true; error: string };
37
+
38
+ export function hashTokenignoreEntries(entries: string[]): string {
39
+ return createHash("sha256")
40
+ .update(normalizeEntries(entries).sort().join("\n"))
41
+ .digest("hex");
42
+ }
43
+
44
+ export function renderManagedTokenignoreBlock(entries: string[]): string {
45
+ const normalized = normalizeEntries(entries);
46
+ const hash = hashTokenignoreEntries(normalized);
47
+ return [
48
+ TOKENIGNORE_BEGIN_MARKER,
49
+ "# Managed by supipowers. Edit entries outside this block.",
50
+ `# hash: ${hash}`,
51
+ ...normalized,
52
+ TOKENIGNORE_END_MARKER,
53
+ ].join("\n");
54
+ }
55
+
56
+ export function mergeManagedTokenignore(
57
+ existing: string | null | undefined,
58
+ entries: string[],
59
+ ): ManagedTokenignoreMergeResult {
60
+ const normalized = normalizeEntries(entries);
61
+ const hash = hashTokenignoreEntries(normalized);
62
+ const block = renderManagedTokenignoreBlock(normalized);
63
+ const userContent = removeManagedTokenignoreBlocks(existing ?? "").trimEnd();
64
+ const content = userContent.length > 0
65
+ ? `${userContent}\n\n${block}\n`
66
+ : `${block}\n`;
67
+
68
+ return { content, entries: normalized, hash };
69
+ }
70
+
71
+ export function parseManagedTokenignore(text: string | null | undefined): ParsedManagedTokenignore {
72
+ const content = text ?? "";
73
+ const begin = content.indexOf(TOKENIGNORE_BEGIN_MARKER);
74
+ if (begin === -1) return { status: "unmanaged", managed: false };
75
+
76
+ const end = content.indexOf(TOKENIGNORE_END_MARKER, begin + TOKENIGNORE_BEGIN_MARKER.length);
77
+ if (end === -1) {
78
+ return { status: "malformed", managed: true, error: "managed tokenignore block is missing end marker" };
79
+ }
80
+
81
+ const block = content.slice(begin, end + TOKENIGNORE_END_MARKER.length);
82
+ const lines = block.split(/\r?\n/);
83
+ let hash: string | null = null;
84
+ const entries: string[] = [];
85
+
86
+ for (const rawLine of lines.slice(1, -1)) {
87
+ const line = rawLine.trim();
88
+ if (!line) continue;
89
+ if (line.startsWith("# hash:")) {
90
+ hash = line.slice("# hash:".length).trim();
91
+ continue;
92
+ }
93
+ if (line.startsWith("#")) continue;
94
+ entries.push(line);
95
+ }
96
+
97
+ if (!hash) {
98
+ return { status: "malformed", managed: true, error: "managed tokenignore block is missing hash" };
99
+ }
100
+
101
+ const normalized = normalizeEntries(entries);
102
+ const expectedHash = hashTokenignoreEntries(normalized);
103
+ return {
104
+ status: "managed",
105
+ managed: true,
106
+ entries: normalized,
107
+ hash,
108
+ expectedHash,
109
+ hashMatches: hash === expectedHash,
110
+ };
111
+ }
112
+
113
+ function normalizeEntries(entries: string[]): string[] {
114
+ const seen = new Set<string>();
115
+ const normalized: string[] = [];
116
+
117
+ for (const entry of entries) {
118
+ const trimmed = entry.trim();
119
+ if (!trimmed) continue;
120
+ if (seen.has(trimmed)) continue;
121
+ seen.add(trimmed);
122
+ normalized.push(trimmed);
123
+ }
124
+
125
+ return normalized;
126
+ }
127
+
128
+ function removeManagedTokenignoreBlocks(existing: string): string {
129
+ let output = existing;
130
+
131
+ while (true) {
132
+ const begin = output.indexOf(TOKENIGNORE_BEGIN_MARKER);
133
+ if (begin === -1) return output;
134
+ const end = output.indexOf(TOKENIGNORE_END_MARKER, begin + TOKENIGNORE_BEGIN_MARKER.length);
135
+ const removeEnd = end === -1
136
+ ? output.length
137
+ : end + TOKENIGNORE_END_MARKER.length + trailingNewlineLength(output, end + TOKENIGNORE_END_MARKER.length);
138
+ output = output.slice(0, begin) + output.slice(removeEnd);
139
+ }
140
+ }
141
+
142
+ function trailingNewlineLength(value: string, index: number): number {
143
+ if (value.slice(index, index + 2) === "\r\n") return 2;
144
+ if (value[index] === "\n") return 1;
145
+ return 0;
146
+ }
@@ -0,0 +1,49 @@
1
+ // src/context-mode/cache-handle.ts
2
+
3
+ const HANDLE_PREFIX = "cache://";
4
+ const SHA256_HEX_RE = /^[a-f0-9]{64}$/;
5
+ const RENDER_SHA256_RE = /^[a-fA-F0-9]{64}$/;
6
+ const MAX_DESCRIBED_VALUE_CHARS = 120;
7
+ const INVALID_HANDLE_REQUIREMENT =
8
+ "must be cache:// followed by 64 lowercase hexadecimal characters";
9
+
10
+ export type CacheHandleParseResult =
11
+ | { ok: true; handle: string; sha256: string }
12
+ | { ok: false; message: string };
13
+
14
+ export function renderCacheHandle(sha256: string): string {
15
+ const normalized = sha256.trim().toLowerCase();
16
+ if (!RENDER_SHA256_RE.test(normalized)) {
17
+ throw new Error(`invalid cache sha256: ${INVALID_HANDLE_REQUIREMENT}`);
18
+ }
19
+ return `${HANDLE_PREFIX}${normalized}`;
20
+ }
21
+
22
+ export function parseCacheHandle(value: string): CacheHandleParseResult {
23
+ const trimmed = value.trim();
24
+ if (!trimmed.startsWith(HANDLE_PREFIX)) {
25
+ return invalidCacheHandle(trimmed);
26
+ }
27
+
28
+ const sha256 = trimmed.slice(HANDLE_PREFIX.length);
29
+ if (!SHA256_HEX_RE.test(sha256)) {
30
+ return invalidCacheHandle(trimmed);
31
+ }
32
+
33
+ return { ok: true, handle: renderCacheHandle(sha256), sha256 };
34
+ }
35
+
36
+ export function describeInvalidCacheHandle(value: string): string {
37
+ const trimmed = value.trim();
38
+ if (trimmed.length > MAX_DESCRIBED_VALUE_CHARS) {
39
+ return `value with length ${trimmed.length}`;
40
+ }
41
+ return trimmed;
42
+ }
43
+
44
+ function invalidCacheHandle(value: string): CacheHandleParseResult {
45
+ return {
46
+ ok: false,
47
+ message: `invalid cache handle (${describeInvalidCacheHandle(value)}): ${INVALID_HANDLE_REQUIREMENT}`,
48
+ };
49
+ }
@@ -0,0 +1,71 @@
1
+ // src/context-mode/cache-preview.ts
2
+
3
+ export const DEFAULT_CACHE_PREVIEW_CHARS = 4 * 1024;
4
+ export const DEFAULT_CACHE_OPEN_CHARS = 64 * 1024;
5
+ export const HARD_CACHE_OPEN_CHARS = 100 * 1024;
6
+
7
+ const PREVIEW_OMISSION_MARKER = "\n… omitted …\n";
8
+
9
+ export function buildCachePreview(
10
+ text: string,
11
+ maxChars = DEFAULT_CACHE_PREVIEW_CHARS,
12
+ ): string {
13
+ const chars = Array.from(text);
14
+ const max = normalizeNonNegativeInteger(maxChars, DEFAULT_CACHE_PREVIEW_CHARS);
15
+ if (max === 0) return "";
16
+ if (chars.length <= max) return text;
17
+
18
+ const markerChars = Array.from(PREVIEW_OMISSION_MARKER);
19
+ if (max <= markerChars.length) {
20
+ return markerChars.slice(0, max).join("");
21
+ }
22
+
23
+ const contentBudget = max - markerChars.length;
24
+ const headCount = Math.ceil(contentBudget / 2);
25
+ const tailCount = Math.floor(contentBudget / 2);
26
+
27
+ return [
28
+ ...chars.slice(0, headCount),
29
+ ...markerChars,
30
+ ...chars.slice(chars.length - tailCount),
31
+ ].join("");
32
+ }
33
+
34
+ export function sliceCachedText(
35
+ text: string,
36
+ offset = 0,
37
+ limit = DEFAULT_CACHE_OPEN_CHARS,
38
+ ): {
39
+ text: string;
40
+ offset: number;
41
+ returnedChars: number;
42
+ totalChars: number;
43
+ nextOffset: number | null;
44
+ } {
45
+ const chars = Array.from(text);
46
+ const totalChars = chars.length;
47
+ const normalizedOffset = Math.min(
48
+ normalizeNonNegativeInteger(offset, 0),
49
+ totalChars,
50
+ );
51
+ const normalizedLimit = Math.min(
52
+ normalizeNonNegativeInteger(limit, DEFAULT_CACHE_OPEN_CHARS),
53
+ HARD_CACHE_OPEN_CHARS,
54
+ );
55
+
56
+ const end = Math.min(normalizedOffset + normalizedLimit, totalChars);
57
+ const sliced = chars.slice(normalizedOffset, end).join("");
58
+
59
+ return {
60
+ text: sliced,
61
+ offset: normalizedOffset,
62
+ returnedChars: end - normalizedOffset,
63
+ totalChars,
64
+ nextOffset: end < totalChars ? end : null,
65
+ };
66
+ }
67
+
68
+ function normalizeNonNegativeInteger(value: number, fallback: number): number {
69
+ if (!Number.isFinite(value)) return fallback;
70
+ return Math.max(0, Math.floor(value));
71
+ }