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
@@ -1,16 +1,21 @@
1
+ import { isDeepStrictEqual } from "node:util";
1
2
  import type { Platform, PlatformContext } from "../platform/types.js";
2
3
  import { DEFAULT_CONFIG } from "../config/defaults.js";
3
- import { formatConfigErrors, inspectConfig, updateConfig } from "../config/loader.js";
4
+ import { formatConfigErrors, inspectConfig, inspectConfigAtScope, updateConfig } from "../config/loader.js";
4
5
  import type { InspectionLoadResult } from "../config/schema.js";
5
- import type { SupipowersConfig } from "../types.js";
6
+ import { validateQualityGates } from "../config/schema.js";
6
7
  import { createWorkflowProgress } from "../platform/progress.js";
7
8
  import {
8
- interactivelySaveGateSetup,
9
+ formatGateProposal,
9
10
  setupGates,
10
11
  summarizeEnabledGates,
11
12
  type GateSetupMode,
12
13
  type SetupGatesProgressEvent,
13
14
  } from "../quality/setup.js";
15
+ import type { ConfigScope, QualityGatesConfig, SetupProposal, SupipowersConfig } from "../types.js";
16
+ import { resolvePackageManager } from "../workspace/package-manager.js";
17
+ import { resolveRepoRoot } from "../workspace/repo-root.js";
18
+ import { discoverWorkspaceTargets } from "../workspace/targets.js";
14
19
 
15
20
  const FRAMEWORK_OPTIONS = [
16
21
  { value: "", label: "not set — auto-detect on first /supi:qa run" },
@@ -38,18 +43,29 @@ export interface SettingDef {
38
43
  set: (cwd: string, value: unknown) => Promise<string | null>;
39
44
  }
40
45
 
46
+ export interface ConfigScopeSelection {
47
+ scope: ConfigScope;
48
+ repoRoot: string;
49
+ isMonorepo: boolean;
50
+ }
51
+
52
+ export interface ConfigScopeView {
53
+ selection: ConfigScopeSelection;
54
+ inspection: InspectionLoadResult;
55
+ globalInspection: InspectionLoadResult;
56
+ rootInspection: InspectionLoadResult;
57
+ }
58
+
41
59
  export interface ConfigCommandDependencies {
42
60
  inspectConfig: typeof inspectConfig;
43
61
  updateConfig: typeof updateConfig;
44
62
  setupGates: typeof setupGates;
45
- interactivelySaveGateSetup: typeof interactivelySaveGateSetup;
46
63
  }
47
64
 
48
65
  const CONFIG_COMMAND_DEPENDENCIES: ConfigCommandDependencies = {
49
66
  inspectConfig,
50
67
  updateConfig,
51
68
  setupGates,
52
- interactivelySaveGateSetup,
53
69
  };
54
70
 
55
71
  function currentConfig(inspection: InspectionLoadResult): SupipowersConfig {
@@ -64,6 +80,159 @@ function describeInspection(inspection: InspectionLoadResult, config: Supipowers
64
80
  return summarizeEnabledGates(config.quality.gates);
65
81
  }
66
82
 
83
+ function getConfigResolutionOptions(selection: ConfigScopeSelection): {
84
+ repoRoot: string;
85
+ } {
86
+ return { repoRoot: selection.repoRoot };
87
+ }
88
+
89
+ function getConfigMutationOptions(selection: ConfigScopeSelection): {
90
+ scope: ConfigScope;
91
+ repoRoot: string;
92
+ } {
93
+ return {
94
+ scope: selection.scope,
95
+ ...getConfigResolutionOptions(selection),
96
+ };
97
+ }
98
+
99
+ function getSelectionCwd(selection: ConfigScopeSelection): string {
100
+ return selection.repoRoot;
101
+ }
102
+
103
+ function configPathForSelection(selection: ConfigScopeSelection): string {
104
+ switch (selection.scope) {
105
+ case "global":
106
+ return "~/.omp/supipowers/config.json";
107
+ case "root":
108
+ return ".omp/supipowers/config.json";
109
+ }
110
+ }
111
+
112
+ function repositoryScopeLabel(selection: ConfigScopeSelection): string {
113
+ return selection.isMonorepo ? "monorepo repository" : "repository";
114
+ }
115
+
116
+ function selectionSummary(selection: ConfigScopeSelection): string {
117
+ return `${selection.scope === "global" ? "global" : repositoryScopeLabel(selection)} — ${configPathForSelection(selection)}`;
118
+ }
119
+
120
+ function getNestedValue(value: unknown, key: string): unknown {
121
+ return key
122
+ .split(".")
123
+ .reduce<unknown>((current, segment) => {
124
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
125
+ return undefined;
126
+ }
127
+ return (current as Record<string, unknown>)[segment];
128
+ }, value);
129
+ }
130
+
131
+ function getSelectedConfig(view: ConfigScopeView): SupipowersConfig {
132
+ return currentConfig(view.inspection);
133
+ }
134
+
135
+ function describeSettingProvenance(view: ConfigScopeView, key: string): string {
136
+ const defaultValue = getNestedValue(DEFAULT_CONFIG, key);
137
+ const globalValue = getNestedValue(currentConfig(view.globalInspection), key);
138
+ const rootValue = getNestedValue(currentConfig(view.rootInspection), key);
139
+
140
+ switch (view.selection.scope) {
141
+ case "global":
142
+ return isDeepStrictEqual(globalValue, defaultValue) ? "default" : "overridden in global";
143
+ case "root":
144
+ if (!isDeepStrictEqual(rootValue, globalValue)) {
145
+ return "overridden in repository";
146
+ }
147
+ if (!isDeepStrictEqual(globalValue, defaultValue)) {
148
+ return "inherited from global";
149
+ }
150
+ return "default";
151
+ }
152
+ }
153
+
154
+ function describeSettingValue(display: string, view: ConfigScopeView, key: string): string {
155
+ return `${display} — ${describeSettingProvenance(view, key)}`;
156
+ }
157
+
158
+ function parseRevisedQualityGates(raw: string): QualityGatesConfig {
159
+ const parsed = JSON.parse(raw) as unknown;
160
+ const validation = validateQualityGates(parsed);
161
+ if (!validation.valid) {
162
+ throw new Error(validation.errors.join("\n"));
163
+ }
164
+
165
+ return parsed as QualityGatesConfig;
166
+ }
167
+
168
+ function buildQualityGateSetupHelpText(
169
+ proposal: SetupProposal,
170
+ scopeSelection: ConfigScopeSelection,
171
+ mode: GateSetupMode,
172
+ ): string {
173
+ const intro = mode === "ai-assisted"
174
+ ? "AI analyzed your project and suggested these gates."
175
+ : "Detected project checks and suggested these gates.";
176
+
177
+ return [
178
+ intro,
179
+ "",
180
+ formatGateProposal(proposal),
181
+ "",
182
+ `Will write to ${configPathForSelection(scopeSelection)}`,
183
+ ].join("\n");
184
+ }
185
+
186
+ async function saveQualityGateProposal(
187
+ ctx: PlatformContext,
188
+ paths: Platform["paths"],
189
+ deps: ConfigCommandDependencies,
190
+ selection: ConfigScopeSelection,
191
+ proposal: SetupProposal,
192
+ mode: GateSetupMode,
193
+ ): Promise<"saved" | "cancelled"> {
194
+ let nextProposal = proposal;
195
+ const selectionCwd = getSelectionCwd(selection);
196
+ const mutationOptions = getConfigMutationOptions(selection);
197
+
198
+ while (true) {
199
+ const choice = await ctx.ui.select(
200
+ mode === "ai-assisted" ? "AI-assisted quality gate proposal" : "Quality gate setup",
201
+ ["Accept", "Revise", "Cancel"],
202
+ {
203
+ helpText: buildQualityGateSetupHelpText(nextProposal, selection, mode),
204
+ },
205
+ );
206
+
207
+ if (!choice || choice === "Cancel") {
208
+ return "cancelled";
209
+ }
210
+
211
+ if (choice === "Revise") {
212
+ const revised = await ctx.ui.input(
213
+ "Edit quality.gates JSON",
214
+ { value: JSON.stringify(nextProposal.gates, null, 2) },
215
+ );
216
+ if (!revised) {
217
+ continue;
218
+ }
219
+
220
+ try {
221
+ nextProposal = {
222
+ ...nextProposal,
223
+ gates: parseRevisedQualityGates(revised),
224
+ };
225
+ } catch (error) {
226
+ ctx.ui.notify((error as Error).message, "error");
227
+ }
228
+ continue;
229
+ }
230
+
231
+ deps.updateConfig(paths, selectionCwd, { quality: { gates: nextProposal.gates } }, mutationOptions);
232
+ return "saved";
233
+ }
234
+ }
235
+
67
236
  function createQualityGateSetupProgress(ctx: PlatformContext, mode: GateSetupMode) {
68
237
  if (mode !== "ai-assisted") {
69
238
  return null;
@@ -109,40 +278,51 @@ function createQualityGateSetupProgress(ctx: PlatformContext, mode: GateSetupMod
109
278
  };
110
279
  }
111
280
 
112
- function buildGateSetupDialogOptions(mode: GateSetupMode) {
113
- return mode === "ai-assisted"
114
- ? {
115
- title: "AI-assisted quality gate proposal",
116
- intro: "AI analyzed your project and suggested these gates.",
117
- }
118
- : {
119
- intro: "Detected project checks and suggested these gates.",
120
- };
281
+ export function buildConfigScopeView(
282
+ platform: Platform,
283
+ cwd: string,
284
+ selection: ConfigScopeSelection,
285
+ ): ConfigScopeView {
286
+ const resolutionOptions = getConfigResolutionOptions(selection);
287
+ const globalInspection = inspectConfigAtScope(platform.paths, cwd, "global", resolutionOptions);
288
+ const rootInspection = inspectConfigAtScope(platform.paths, cwd, "root", resolutionOptions);
289
+ const inspection = selection.scope === "global" ? globalInspection : rootInspection;
290
+
291
+ return {
292
+ selection,
293
+ inspection,
294
+ globalInspection,
295
+ rootInspection,
296
+ };
121
297
  }
122
298
 
123
299
  export function buildSettings(
124
300
  platform: Platform,
125
301
  ctx: PlatformContext,
126
- inspection: InspectionLoadResult,
302
+ view: ConfigScopeView,
127
303
  deps: ConfigCommandDependencies = CONFIG_COMMAND_DEPENDENCIES,
128
304
  ): SettingDef[] {
129
305
  const { paths } = platform;
130
- const config = currentConfig(inspection);
306
+ const config = getSelectedConfig(view);
307
+ const selectionCwd = getSelectionCwd(view.selection);
308
+ const resolutionOptions = getConfigResolutionOptions(view.selection);
309
+ const mutationOptions = getConfigMutationOptions(view.selection);
131
310
 
132
311
  return [
133
312
  {
134
313
  label: "Setup quality gates",
135
314
  key: "quality.gates",
136
- helpText: "Inspect the project and configure review gates",
315
+ helpText: `Inspect the project and configure review gates. Changes write to ${configPathForSelection(view.selection)}.`,
137
316
  type: "select",
138
317
  options: ["Run deterministic setup", "Run AI-assisted setup"],
139
- get: () => describeInspection(inspection, config),
140
- set: async (cwd, value) => {
318
+ get: () => describeSettingValue(describeInspection(view.inspection, config), view, "quality.gates"),
319
+ set: async (_cwd, value) => {
141
320
  const mode: GateSetupMode = String(value).includes("AI-assisted") ? "ai-assisted" : "deterministic";
142
321
  const progress = createQualityGateSetupProgress(ctx, mode);
143
322
 
144
323
  try {
145
- const result = await deps.setupGates(platform, cwd, deps.inspectConfig(platform.paths, cwd), {
324
+ const selectedInspection = deps.inspectConfig(paths, selectionCwd, resolutionOptions);
325
+ const result = await deps.setupGates(platform, selectionCwd, selectedInspection, {
146
326
  mode,
147
327
  onProgress: (event) => progress?.handle(event),
148
328
  });
@@ -157,12 +337,13 @@ export function buildSettings(
157
337
  }
158
338
 
159
339
  progress?.dispose();
160
- const saveResult = await deps.interactivelySaveGateSetup(
340
+ const saveResult = await saveQualityGateProposal(
161
341
  ctx,
162
342
  paths,
163
- cwd,
343
+ deps,
344
+ view.selection,
164
345
  result.proposal,
165
- buildGateSetupDialogOptions(mode),
346
+ mode,
166
347
  );
167
348
  return saveResult === "saved" ? "saved" : null;
168
349
  } catch (error) {
@@ -176,52 +357,57 @@ export function buildSettings(
176
357
  {
177
358
  label: "LSP setup guide",
178
359
  key: "lsp.setupGuide",
179
- helpText: "Show LSP setup tips when no language server is active",
360
+ helpText: `Show LSP setup tips when no language server is active. Changes write to ${configPathForSelection(view.selection)}.`,
180
361
  type: "toggle",
181
- get: () => (config.lsp.setupGuide ? "on" : "off"),
182
- set: async (cwd, value) => {
183
- deps.updateConfig(paths, cwd, { lsp: { setupGuide: value === "on" } });
362
+ get: () => describeSettingValue(config.lsp.setupGuide ? "on" : "off", view, "lsp.setupGuide"),
363
+ set: async (_cwd, value) => {
364
+ deps.updateConfig(paths, selectionCwd, { lsp: { setupGuide: value === "on" } }, mutationOptions);
184
365
  return String(value);
185
366
  },
186
367
  },
187
368
  {
188
369
  label: "QA framework",
189
370
  key: "qa.framework",
190
- helpText: "Test runner used by /supi:qa",
371
+ helpText: `Test runner used by /supi:qa. Changes write to ${configPathForSelection(view.selection)}.`,
191
372
  type: "select",
192
373
  options: FRAMEWORK_OPTIONS.map((framework) => framework.label),
193
- get: () => config.qa.framework ?? "not set",
194
- set: async (cwd, value) => {
374
+ get: () => describeSettingValue(config.qa.framework ?? "not set", view, "qa.framework"),
375
+ set: async (_cwd, value) => {
195
376
  const chosen = FRAMEWORK_OPTIONS.find((framework) => framework.label === value);
196
377
  if (!chosen) {
197
378
  return null;
198
379
  }
199
380
 
200
- deps.updateConfig(paths, cwd, { qa: { framework: chosen.value || null } });
381
+ deps.updateConfig(paths, selectionCwd, { qa: { framework: chosen.value || null } }, mutationOptions);
201
382
  return chosen.label.split(" — ")[0];
202
383
  },
203
384
  },
204
385
  {
205
386
  label: "Release channels",
206
387
  key: "release.channels",
207
- helpText: "Where /supi:release publishes your project",
388
+ helpText: `Where /supi:release publishes your project. Changes write to ${configPathForSelection(view.selection)}.`,
208
389
  type: "select",
209
390
  options: CHANNEL_OPTIONS.map((channel) => channel.label),
210
- get: () =>
391
+ get: () => describeSettingValue(
211
392
  config.release.channels.length > 0 ? config.release.channels.join(", ") : "not set",
212
- set: async (cwd, value) => {
393
+ view,
394
+ "release.channels",
395
+ ),
396
+ set: async (_cwd, value) => {
213
397
  const chosen = CHANNEL_OPTIONS.find((channel) => channel.label === value);
214
398
  if (!chosen) {
215
399
  return null;
216
400
  }
217
401
 
218
- deps.updateConfig(paths, cwd, { release: { channels: chosen.value } });
402
+ deps.updateConfig(paths, selectionCwd, { release: { channels: chosen.value } }, mutationOptions);
219
403
  return chosen.label.split(" — ")[0];
220
404
  },
221
405
  },
222
406
  ];
223
407
  }
224
408
 
409
+
410
+
225
411
  export async function runConfigMenu(
226
412
  platform: Platform,
227
413
  ctx: PlatformContext,
@@ -232,16 +418,28 @@ export async function runConfigMenu(
232
418
  return;
233
419
  }
234
420
 
421
+ const repoRoot = await resolveRepoRoot(platform, ctx.cwd);
422
+ const isMonorepo = discoverWorkspaceTargets(repoRoot, resolvePackageManager(repoRoot).id)
423
+ .some((target) => target.kind === "workspace");
424
+ let selection: ConfigScopeSelection = {
425
+ scope: "root",
426
+ repoRoot,
427
+ isMonorepo,
428
+ };
429
+
235
430
  while (true) {
236
- const inspection = deps.inspectConfig(platform.paths, ctx.cwd);
237
- const settings = buildSettings(platform, ctx, inspection, deps);
238
- const options = settings.map((setting) => `${setting.label}: ${setting.get()}`);
239
- options.push("Done");
431
+ const view = buildConfigScopeView(platform, ctx.cwd, selection);
432
+ const settings = buildSettings(platform, ctx, view, deps);
433
+ const options = [...settings.map((setting) => `${setting.label}: ${setting.get()}`), "Done"];
240
434
 
241
435
  const choice = await ctx.ui.select(
242
436
  "Supipowers Settings",
243
437
  options,
244
- { helpText: "Select a setting to change · Esc to close" },
438
+ {
439
+ helpText: selection.scope === "root" && selection.isMonorepo
440
+ ? `Editing ${selectionSummary(selection)}. Changes apply to every workspace in this monorepo. Esc to close.`
441
+ : `Editing ${selectionSummary(selection)}. Changes write to ${configPathForSelection(selection)}. Esc to close.`,
442
+ },
245
443
  );
246
444
 
247
445
  if (choice === undefined || choice === null || choice === "Done") {
@@ -257,7 +455,7 @@ export async function runConfigMenu(
257
455
  try {
258
456
  if (setting.type === "select" && setting.options) {
259
457
  const currentValue = setting.get();
260
- const currentIndex = setting.options.findIndex((option) => option.startsWith(currentValue));
458
+ const currentIndex = setting.options.findIndex((option) => currentValue.startsWith(option.split(" — ")[0]));
261
459
  const value = await ctx.ui.select(
262
460
  setting.label,
263
461
  setting.options,
@@ -268,15 +466,15 @@ export async function runConfigMenu(
268
466
  );
269
467
 
270
468
  if (value !== undefined && value !== null) {
271
- const display = await setting.set(ctx.cwd, value);
469
+ const display = await setting.set(getSelectionCwd(selection), value);
272
470
  if (display) {
273
471
  ctx.ui.notify(`${setting.label} → ${display}`, "info");
274
472
  }
275
473
  }
276
474
  } else if (setting.type === "toggle") {
277
475
  const current = setting.get();
278
- const newValue = current === "on" ? "off" : "on";
279
- const display = await setting.set(ctx.cwd, newValue);
476
+ const newValue = current.startsWith("on") ? "off" : "on";
477
+ const display = await setting.set(getSelectionCwd(selection), newValue);
280
478
  if (display) {
281
479
  ctx.ui.notify(`${setting.label} → ${display}`, "info");
282
480
  }
@@ -1,7 +1,8 @@
1
1
  import { writeFileSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { basename, join } from "node:path";
3
3
  import { tmpdir } from "node:os";
4
4
  import type { Platform, PlatformContext } from "../platform/types.js";
5
+ import { systemPromptText } from "../platform/system-prompt.js";
5
6
  import {
6
7
  parseSystemPrompt,
7
8
  buildBreakdownItems,
@@ -9,6 +10,14 @@ import {
9
10
  formatToolsReport,
10
11
  } from "../context/analyzer.js";
11
12
  import type { ContextUsage } from "../context/analyzer.js";
13
+ import {
14
+ buildSavingsLinesFromStore,
15
+ formatSavingsReportFromStore,
16
+ getFirstRunNotice,
17
+ } from "../context/savings.js";
18
+ import { getMetricsStore, getSessionId } from "../context-mode/hooks.js";
19
+ import { getProjectStateDir, getProjectStatePath } from "../workspace/state-paths.js";
20
+ import { openInEditor } from "../utils/editor.js";
12
21
 
13
22
  const REPORT_FILE = ".omp-context-breakdown.md";
14
23
 
@@ -33,13 +42,26 @@ export function handleContext(platform: Platform, ctx: PlatformContext): void {
33
42
 
34
43
  let systemPrompt = "";
35
44
  try {
36
- systemPrompt = (ctx as any).getSystemPrompt?.() ?? "";
45
+ systemPrompt = systemPromptText((ctx as any).getSystemPrompt?.());
37
46
  } catch {
38
47
  // getSystemPrompt not available — continue without
39
48
  }
40
49
 
41
- // If we have nothing to show, notify and bail
42
- if (!usage && !systemPrompt) {
50
+ // L1 metrics surfaces
51
+ const store = getMetricsStore();
52
+ const sessionId = getSessionId();
53
+ const projectSlug = basename(getProjectStateDir(platform.paths, ctx.cwd));
54
+ const dbAbsPath = getProjectStatePath(platform.paths, ctx.cwd, "sessions", "metrics.db");
55
+ const sessionStartedAtMs = (() => {
56
+ try {
57
+ return store?.getSessionMeta(sessionId)?.started_at ?? null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ })();
62
+
63
+ // Bail only when *nothing* is available: no usage, no system prompt, no metrics store.
64
+ if (!usage && !systemPrompt && !store) {
43
65
  ctx.ui.notify("Context data unavailable", "warning");
44
66
  return;
45
67
  }
@@ -47,7 +69,25 @@ export function handleContext(platform: Platform, ctx: PlatformContext): void {
47
69
  // Parse system prompt (may be empty)
48
70
  const sections = systemPrompt ? parseSystemPrompt(systemPrompt) : [];
49
71
  const activeTools = platform.getActiveTools();
50
- const items = buildBreakdownItems(usage, sections, activeTools, !systemPrompt);
72
+ const baseItems = buildBreakdownItems(usage, sections, activeTools, !systemPrompt);
73
+
74
+ // Build the savings panel + first-run notice + footer
75
+ const noticeLine = getFirstRunNotice(store, projectSlug, dbAbsPath);
76
+ const savingsLines = buildSavingsLinesFromStore(
77
+ store,
78
+ sessionId,
79
+ sessionStartedAtMs,
80
+ dbAbsPath,
81
+ );
82
+ const footerLine = `Metrics DB: ${dbAbsPath}`;
83
+ const drillableSavings = new Set(savingsLines);
84
+
85
+ const items: typeof baseItems = [];
86
+ if (noticeLine) items.push({ line: noticeLine });
87
+ for (const line of savingsLines) items.push({ line });
88
+ items.push({ line: footerLine });
89
+ items.push(...baseItems);
90
+
51
91
  const lines = items.map(i => i.line);
52
92
 
53
93
  while (true) {
@@ -56,14 +96,17 @@ export function handleContext(platform: Platform, ctx: PlatformContext): void {
56
96
  });
57
97
  if (!choice || choice.trim() === "Close") break;
58
98
 
59
- const item = items.find(i => i.line === choice);
60
- if (!item || (!item.section && !item.toolNames)) continue;
61
-
62
99
  let report: string | null = null;
63
- if (item.section) {
64
- report = formatSectionReport(item.section);
65
- } else if (item.toolNames) {
66
- report = formatToolsReport(item.toolNames);
100
+ if (drillableSavings.has(choice)) {
101
+ report = formatSavingsReportFromStore(store, sessionId, sessionStartedAtMs);
102
+ } else {
103
+ const item = items.find(i => i.line === choice);
104
+ if (!item || (!item.section && !item.toolNames)) continue;
105
+ if (item.section) {
106
+ report = formatSectionReport(item.section);
107
+ } else if (item.toolNames) {
108
+ report = formatToolsReport(item.toolNames);
109
+ }
67
110
  }
68
111
 
69
112
  if (report) {
@@ -98,20 +141,4 @@ function writeReport(cwd: string, content: string): string {
98
141
  writeFileSync(fallback, content, "utf-8");
99
142
  return fallback;
100
143
  }
101
- }
102
-
103
- /** Open a file in the user's preferred editor */
104
- async function openInEditor(platform: Platform, filePath: string): Promise<void> {
105
- const editor = process.env.VISUAL || process.env.EDITOR;
106
- try {
107
- if (editor) {
108
- await platform.exec(editor, [filePath]);
109
- } else {
110
- const cmd = process.platform === "darwin" ? "open"
111
- : process.platform === "win32" ? "start" : "xdg-open";
112
- await platform.exec(cmd, [filePath]);
113
- }
114
- } catch {
115
- // Editor open failed — non-fatal, file was still written
116
- }
117
144
  }