@oh-my-pi/pi-coding-agent 17.2.13 → 17.2.15

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 (239) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/{CHANGELOG-d8xh7keh.md → CHANGELOG-fk29zh06.md} +30 -0
  3. package/dist/cli.js +5114 -5417
  4. package/dist/types/cleanse/agent.d.ts +3 -0
  5. package/dist/types/cleanse/checkers.d.ts +21 -1
  6. package/dist/types/cleanse/index.d.ts +4 -0
  7. package/dist/types/cleanse/parsers.d.ts +3 -1
  8. package/dist/types/cli/args.d.ts +1 -0
  9. package/dist/types/cli/cleanse-picker.d.ts +17 -0
  10. package/dist/types/cli/command-help.d.ts +3 -0
  11. package/dist/types/cli/progress-reporter.d.ts +19 -0
  12. package/dist/types/commands/cleanse.d.ts +11 -0
  13. package/dist/types/commands/compress.d.ts +37 -0
  14. package/dist/types/commands/launch-help.d.ts +3 -0
  15. package/dist/types/commands/launch.d.ts +3 -0
  16. package/dist/types/compress/index.d.ts +27 -0
  17. package/dist/types/compress/protocol.d.ts +49 -0
  18. package/dist/types/compress/session.d.ts +15 -0
  19. package/dist/types/compress/types.d.ts +54 -0
  20. package/dist/types/config/settings-schema.d.ts +10 -0
  21. package/dist/types/internal-urls/local-protocol.d.ts +8 -0
  22. package/dist/types/mcp/transports/header-policy.d.ts +8 -0
  23. package/dist/types/mcp/transports/http.d.ts +2 -0
  24. package/dist/types/mcp/types.d.ts +19 -0
  25. package/dist/types/modes/components/late-diagnostics-message.d.ts +2 -0
  26. package/dist/types/modes/components/todo-reminder.d.ts +2 -0
  27. package/dist/types/modes/components/tool-activity.d.ts +17 -0
  28. package/dist/types/modes/components/transcript-container.d.ts +2 -0
  29. package/dist/types/modes/components/ttsr-notification.d.ts +2 -0
  30. package/dist/types/modes/interactive-mode.d.ts +3 -1
  31. package/dist/types/modes/types.d.ts +3 -1
  32. package/dist/types/modes/utils/transcript-render-helpers.d.ts +2 -1
  33. package/dist/types/modes/utils/ui-helpers.d.ts +3 -1
  34. package/dist/types/session/agent-session-types.d.ts +2 -0
  35. package/dist/types/session/agent-session.d.ts +2 -0
  36. package/dist/types/session/session-tools.d.ts +13 -0
  37. package/dist/types/tools/builtin-names.d.ts +1 -2
  38. package/dist/types/tools/index.d.ts +1 -0
  39. package/dist/types/tools/think.d.ts +41 -0
  40. package/dist/types/utils/zip.d.ts +19 -9
  41. package/package.json +13 -13
  42. package/src/cleanse/agent.ts +67 -2
  43. package/src/cleanse/checkers.ts +252 -21
  44. package/src/cleanse/index.ts +90 -36
  45. package/src/cleanse/parsers.ts +243 -20
  46. package/src/cleanse/prompts/discovery.md +74 -0
  47. package/src/cli/args.ts +3 -0
  48. package/src/cli/cleanse-picker.ts +86 -0
  49. package/src/cli/command-help.ts +4 -0
  50. package/src/cli/flag-tables.ts +1 -0
  51. package/src/cli/gallery-fixtures/agentic.ts +16 -0
  52. package/src/{cleanse/progress.ts → cli/progress-reporter.ts} +12 -7
  53. package/src/cli-commands.ts +5 -0
  54. package/src/commands/cleanse.ts +19 -4
  55. package/src/commands/compress.ts +45 -0
  56. package/src/commands/launch-help.ts +3 -0
  57. package/src/commit/agentic/prompts/analyze-file.md +6 -6
  58. package/src/commit/agentic/prompts/session-user.md +4 -4
  59. package/src/compress/index.ts +318 -0
  60. package/src/compress/prompts/request.md +11 -0
  61. package/src/compress/prompts/review.md +17 -0
  62. package/src/compress/prompts/system.md +81 -0
  63. package/src/compress/protocol.ts +210 -0
  64. package/src/compress/session.ts +72 -0
  65. package/src/compress/types.ts +59 -0
  66. package/src/config/settings-schema.ts +11 -0
  67. package/src/discovery/builtin-rules/go-add-cleanup.md +8 -8
  68. package/src/discovery/builtin-rules/go-exp-promoted.md +8 -8
  69. package/src/discovery/builtin-rules/go-ioutil.md +12 -12
  70. package/src/discovery/builtin-rules/go-new-expr.md +8 -8
  71. package/src/discovery/builtin-rules/go-range-int.md +5 -5
  72. package/src/discovery/builtin-rules/rs-box-leak.md +7 -7
  73. package/src/discovery/builtin-rules/rs-future-prelude.md +4 -4
  74. package/src/discovery/builtin-rules/rs-parking-lot.md +6 -6
  75. package/src/discovery/builtin-rules/ts-no-any.md +5 -5
  76. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +8 -8
  77. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +13 -14
  78. package/src/discovery/builtin-rules/ts-no-local-is-record.md +7 -7
  79. package/src/discovery/builtin-rules/ts-no-test-timers.md +3 -9
  80. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +7 -7
  81. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +2 -2
  82. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +3 -9
  83. package/src/discovery/builtin-rules/ts-set-map.md +2 -4
  84. package/src/internal-urls/local-protocol.ts +42 -0
  85. package/src/live/prompts/live-instructions.md +10 -10
  86. package/src/main.ts +4 -0
  87. package/src/mcp/client.ts +7 -3
  88. package/src/mcp/transports/header-policy.ts +28 -0
  89. package/src/mcp/transports/http.ts +200 -35
  90. package/src/mcp/types.ts +21 -0
  91. package/src/modes/components/chat-transcript-builder.ts +17 -5
  92. package/src/modes/components/late-diagnostics-message.ts +12 -1
  93. package/src/modes/components/todo-reminder.ts +12 -0
  94. package/src/modes/components/tool-activity.ts +45 -0
  95. package/src/modes/components/transcript-container.ts +16 -0
  96. package/src/modes/components/ttsr-notification.ts +12 -0
  97. package/src/modes/controllers/event-controller.ts +3 -6
  98. package/src/modes/controllers/input-controller.ts +6 -6
  99. package/src/modes/controllers/selector-controller.ts +8 -6
  100. package/src/modes/interactive-mode.ts +5 -40
  101. package/src/modes/print-mode.ts +13 -54
  102. package/src/modes/types.ts +1 -1
  103. package/src/modes/utils/transcript-render-helpers.ts +3 -2
  104. package/src/modes/utils/ui-helpers.ts +17 -7
  105. package/src/prompts/advisor/active-repo-watchdog.md +3 -4
  106. package/src/prompts/advisor/advise-tool.md +3 -3
  107. package/src/prompts/advisor/context-files.md +1 -1
  108. package/src/prompts/advisor/system.md +45 -65
  109. package/src/prompts/agents/designer.md +44 -44
  110. package/src/prompts/agents/init.md +18 -18
  111. package/src/prompts/agents/librarian.md +31 -31
  112. package/src/prompts/agents/reviewer.md +34 -39
  113. package/src/prompts/agents/security-reviewer.md +3 -5
  114. package/src/prompts/agents/task.md +11 -12
  115. package/src/prompts/bench.md +2 -5
  116. package/src/prompts/ci-green-request.md +13 -15
  117. package/src/prompts/dry-balance-bench.md +5 -5
  118. package/src/prompts/goals/goal-budget-limit.md +4 -5
  119. package/src/prompts/goals/goal-continuation.md +11 -11
  120. package/src/prompts/goals/goal-mode-active.md +7 -7
  121. package/src/prompts/goals/goal-todo-context.md +2 -2
  122. package/src/prompts/goals/guided-goal-interview.md +18 -24
  123. package/src/prompts/memories/read-path.md +9 -9
  124. package/src/prompts/memories/stage_one_system.md +9 -11
  125. package/src/prompts/review-custom-request.md +8 -11
  126. package/src/prompts/review-headless-request.md +3 -10
  127. package/src/prompts/security/scan-coordinator.md +6 -5
  128. package/src/prompts/security/validate-request.md +4 -7
  129. package/src/prompts/skills/user-invocation.md +2 -2
  130. package/src/prompts/steering/parent-irc.md +1 -1
  131. package/src/prompts/steering/user-interjection.md +1 -3
  132. package/src/prompts/system/active-repo-context.md +4 -2
  133. package/src/prompts/system/agent-creation-architect.md +22 -37
  134. package/src/prompts/system/agent-creation-user.md +3 -3
  135. package/src/prompts/system/auto-continue.md +1 -1
  136. package/src/prompts/system/auto-thinking-difficulty-local.md +5 -7
  137. package/src/prompts/system/auto-thinking-difficulty.md +8 -10
  138. package/src/prompts/system/autolearn-guidance-learn.md +2 -1
  139. package/src/prompts/system/autolearn-guidance.md +5 -4
  140. package/src/prompts/system/autolearn-nudge-autocontinue.md +3 -3
  141. package/src/prompts/system/background-tan-dispatch.md +3 -3
  142. package/src/prompts/system/btw-user.md +2 -2
  143. package/src/prompts/system/commit-message-system.md +8 -6
  144. package/src/prompts/system/eager-task.md +3 -3
  145. package/src/prompts/system/empty-stop-retry.md +1 -1
  146. package/src/prompts/system/gemini-tool-call-reminder.md +5 -5
  147. package/src/prompts/system/interrupted-thinking.md +3 -3
  148. package/src/prompts/system/irc-autoreply.md +1 -1
  149. package/src/prompts/system/irc-incoming.md +3 -3
  150. package/src/prompts/system/manual-continue.md +3 -3
  151. package/src/prompts/system/mcp-xdev-guidance.md +2 -2
  152. package/src/prompts/system/memory-consolidation-system.md +2 -2
  153. package/src/prompts/system/mid-run-todo-nudge.md +1 -1
  154. package/src/prompts/system/orchestrate-notice.md +25 -25
  155. package/src/prompts/system/personalities/default.md +9 -9
  156. package/src/prompts/system/personalities/friendly.md +11 -11
  157. package/src/prompts/system/personalities/pragmatic.md +8 -8
  158. package/src/prompts/system/plan-mode-active.md +61 -64
  159. package/src/prompts/system/plan-mode-approved.md +10 -11
  160. package/src/prompts/system/plan-mode-compact-instructions.md +11 -11
  161. package/src/prompts/system/plan-mode-reference.md +5 -5
  162. package/src/prompts/system/plan-yolo-handoff.md +2 -2
  163. package/src/prompts/system/prewalk-checklist.md +5 -5
  164. package/src/prompts/system/prewalk-continue.md +1 -1
  165. package/src/prompts/system/prewalk-plan.md +8 -9
  166. package/src/prompts/system/project-prompt.md +11 -12
  167. package/src/prompts/system/recap-user.md +1 -1
  168. package/src/prompts/system/resolve-device-reminder.md +1 -1
  169. package/src/prompts/system/rewind-report.md +3 -3
  170. package/src/prompts/system/side-channel-no-tools.md +3 -1
  171. package/src/prompts/system/snapcompact-context-stub.md +1 -1
  172. package/src/prompts/system/snapcompact-system-frames-note.md +1 -1
  173. package/src/prompts/system/snapcompact-system-stub.md +1 -1
  174. package/src/prompts/system/snapcompact-toolresult-note.md +1 -1
  175. package/src/prompts/system/speech-rewrite.md +11 -13
  176. package/src/prompts/system/subagent-async-pending.md +5 -5
  177. package/src/prompts/system/subagent-system-prompt.md +5 -15
  178. package/src/prompts/system/subagent-user-prompt.md +1 -1
  179. package/src/prompts/system/subagent-yield-reminder.md +12 -12
  180. package/src/prompts/system/system-prompt.md +110 -136
  181. package/src/prompts/system/tan-context-switch.md +8 -14
  182. package/src/prompts/system/task-label.md +3 -3
  183. package/src/prompts/system/thinking-loop-redirect.md +6 -6
  184. package/src/prompts/system/title-marker-instruction.md +2 -1
  185. package/src/prompts/system/title-system.md +3 -5
  186. package/src/prompts/system/ttsr-interrupt.md +3 -3
  187. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  188. package/src/prompts/system/ultrathink-notice.md +1 -1
  189. package/src/prompts/system/unexpected-stop-classifier.md +3 -3
  190. package/src/prompts/system/vibe-mode-active.md +15 -15
  191. package/src/prompts/system/web-search.md +16 -16
  192. package/src/prompts/system/workflow-notice.md +35 -33
  193. package/src/prompts/system/xdev-mount-notice.md +4 -4
  194. package/src/prompts/tools/apply-patch.md +22 -23
  195. package/src/prompts/tools/approve.md +5 -0
  196. package/src/prompts/tools/ask.md +10 -10
  197. package/src/prompts/tools/checkpoint.md +7 -7
  198. package/src/prompts/tools/computer.md +16 -16
  199. package/src/prompts/tools/github.md +12 -12
  200. package/src/prompts/tools/goal.md +8 -9
  201. package/src/prompts/tools/grep.md +5 -5
  202. package/src/prompts/tools/image-attachment-describe-system.md +6 -6
  203. package/src/prompts/tools/image-attachment-describe.md +3 -8
  204. package/src/prompts/tools/image-gen.md +4 -4
  205. package/src/prompts/tools/inspect-image-system.md +10 -10
  206. package/src/prompts/tools/inspect-image.md +10 -13
  207. package/src/prompts/tools/learn.md +4 -4
  208. package/src/prompts/tools/manage-skill.md +9 -6
  209. package/src/prompts/tools/memory-edit.md +8 -8
  210. package/src/prompts/tools/recall.md +4 -4
  211. package/src/prompts/tools/reflect.md +2 -2
  212. package/src/prompts/tools/replace.md +13 -11
  213. package/src/prompts/tools/retain.md +3 -4
  214. package/src/prompts/tools/rewind.md +7 -8
  215. package/src/prompts/tools/rewrite.md +12 -0
  216. package/src/prompts/tools/security-publish.md +5 -1
  217. package/src/prompts/tools/security-scan.md +10 -1
  218. package/src/prompts/tools/task-async-contract.md +7 -1
  219. package/src/prompts/tools/todo.md +27 -25
  220. package/src/prompts/tools/vibe-kill.md +2 -2
  221. package/src/prompts/tools/vibe-list.md +2 -2
  222. package/src/prompts/tools/vibe-send.md +6 -7
  223. package/src/prompts/tools/vibe-spawn.md +8 -6
  224. package/src/prompts/tools/web-search.md +5 -5
  225. package/src/sdk.ts +10 -1
  226. package/src/session/agent-session-types.ts +2 -0
  227. package/src/session/agent-session.ts +28 -0
  228. package/src/session/session-handoff.ts +21 -0
  229. package/src/session/session-tools.ts +47 -0
  230. package/src/tools/builtin-names.ts +1 -2
  231. package/src/tools/index.ts +10 -0
  232. package/src/tools/read-format.ts +8 -3
  233. package/src/tools/read.ts +18 -7
  234. package/src/tools/renderers.ts +2 -0
  235. package/src/tools/think.ts +84 -0
  236. package/src/tools/write.ts +1 -1
  237. package/src/utils/file-mentions.ts +8 -2
  238. package/src/utils/zip.ts +858 -94
  239. package/dist/types/cleanse/progress.d.ts +0 -14
@@ -1,10 +1,11 @@
1
1
  import { getProjectDir, sanitizeText } from "@oh-my-pi/pi-utils";
2
+ import { pickCleanseTarget, promptCleanseRequest } from "../cli/cleanse-picker";
3
+ import { createProgressReporter } from "../cli/progress-reporter";
2
4
  import { shortenPath } from "../tools/render-utils";
3
- import { type CleanseAgentRuntime, createCleanseAgentRuntime } from "./agent";
5
+ import { type CleanseAgentHooks, type CleanseAgentRuntime, createCleanseAgentRuntime } from "./agent";
4
6
  import { groupDiagnosticsByFile } from "./balance";
5
- import { discoverCleanseDiagnosticSuite } from "./checkers";
7
+ import { buildCustomCleanseSuite, type CleanseDiagnosticSuite, discoverCleanseDiagnosticSuite } from "./checkers";
6
8
  import { runCleanseLoop } from "./loop";
7
- import { createCleanseProgressReporter } from "./progress";
8
9
  import type { CleanseAgentOutcome, CleanseAssignment, CleanseDiagnosticReport, CleanseLoopResult } from "./types";
9
10
 
10
11
  const DEFAULT_MODEL = "@smol";
@@ -15,6 +16,10 @@ export interface CleanseCommandOptions {
15
16
  maxAgents?: number;
16
17
  model?: string;
17
18
  includeTests?: boolean;
19
+ /** Free-form description handed to a discovery agent instead of built-in checker discovery. */
20
+ request?: string;
21
+ /** Run every discovered checker without the interactive picker. */
22
+ all?: boolean;
18
23
  }
19
24
 
20
25
  /** Observable completion state returned to the CLI adapter. */
@@ -27,7 +32,7 @@ export interface CleanseCommandResult {
27
32
 
28
33
  /** Detect project diagnostics, dispatch one bounded repair batch, and verify it. */
29
34
  export async function runCleanseCommand(options: CleanseCommandOptions = {}): Promise<CleanseCommandResult> {
30
- const maxAgents = options.maxAgents ?? 8;
35
+ const maxAgents = options.maxAgents ?? 32;
31
36
  if (!Number.isInteger(maxAgents) || maxAgents <= 0) throw new Error("--agents must be a positive integer");
32
37
  const model = options.model?.trim() || DEFAULT_MODEL;
33
38
  const cwd = getProjectDir();
@@ -37,7 +42,7 @@ export async function runCleanseCommand(options: CleanseCommandOptions = {}): Pr
37
42
  process.once("SIGTERM", abort);
38
43
  let runtime: CleanseAgentRuntime | undefined;
39
44
  let loopResult: CleanseLoopResult | undefined;
40
- const progress = createCleanseProgressReporter();
45
+ const progress = createProgressReporter("Repairing");
41
46
  const interactiveFailures: CleanseAgentOutcome[] = [];
42
47
  let interactiveFailuresPrinted = false;
43
48
  const printInteractiveFailures = (): void => {
@@ -45,15 +50,85 @@ export async function runCleanseCommand(options: CleanseCommandOptions = {}): Pr
45
50
  interactiveFailuresPrinted = true;
46
51
  for (const outcome of interactiveFailures) printAgentOutcome(outcome);
47
52
  };
53
+ const hooks: CleanseAgentHooks = {
54
+ onStart(name, assignment) {
55
+ if (progress.interactive) return;
56
+ process.stdout.write(`[start] ${name}: ${formatAssignmentFiles(assignment)} (weight ${assignment.weight})\n`);
57
+ },
58
+ onFinish(outcome) {
59
+ progress.complete();
60
+ if (progress.interactive) {
61
+ if (!outcome.success) interactiveFailures.push(outcome);
62
+ return;
63
+ }
64
+ printAgentOutcome(outcome);
65
+ },
66
+ };
67
+ const ensureRuntime = async (): Promise<CleanseAgentRuntime> => {
68
+ if (runtime) return runtime;
69
+ process.stdout.write(`Resolving model ${model}...\n`);
70
+ runtime = await createCleanseAgentRuntime({ cwd, model, hooks });
71
+ process.stdout.write(`Model: ${runtime.model}\nSession: ${shortenPath(runtime.sessionFile)}\n`);
72
+ return runtime;
73
+ };
48
74
 
49
75
  try {
50
- process.stdout.write("Detecting configured project checkers...\n");
51
- const suite = await discoverCleanseDiagnosticSuite(cwd, { includeTests: options.includeTests });
52
- if (suite.checkCount === 0) {
53
- const report: CleanseDiagnosticReport = { checks: [], diagnostics: [], skipped: [...suite.skipped] };
76
+ let request = options.request?.trim() || undefined;
77
+ let suite: CleanseDiagnosticSuite | undefined;
78
+ if (!request) {
79
+ process.stdout.write("Detecting configured project checkers...\n");
80
+ suite = await discoverCleanseDiagnosticSuite(cwd, { includeTests: options.includeTests });
81
+ const interactive = options.all !== true && process.stdin.isTTY === true && process.stdout.isTTY === true;
82
+ if (interactive) {
83
+ if (suite.checkers.length > 0) {
84
+ const choice = await pickCleanseTarget(suite.checkers);
85
+ if (choice.kind === "cancel") {
86
+ process.stderr.write("Cleanse cancelled.\n");
87
+ return {
88
+ exitCode: 130,
89
+ status: "cancelled",
90
+ report: { checks: [], diagnostics: [], skipped: [...suite.skipped] },
91
+ };
92
+ }
93
+ if (choice.kind === "checker") suite.select([choice.id]);
94
+ if (choice.kind === "request") {
95
+ request = choice.request;
96
+ suite = undefined;
97
+ }
98
+ } else {
99
+ printSkippedChecks({ checks: [], diagnostics: [], skipped: [...suite.skipped] });
100
+ process.stdout.write("No supported checker with an available executable was found.\n");
101
+ const answer = await promptCleanseRequest();
102
+ if (answer === null) {
103
+ return {
104
+ exitCode: 1,
105
+ status: "unsupported",
106
+ report: { checks: [], diagnostics: [], skipped: [...suite.skipped] },
107
+ };
108
+ }
109
+ request = answer;
110
+ suite = undefined;
111
+ }
112
+ }
113
+ }
114
+ if (request) {
115
+ const activeRuntime = await ensureRuntime();
116
+ process.stdout.write(`Discovering checkers for "${request}"...\n`);
117
+ const specs = await activeRuntime.discoverCheckers(request, abortController.signal);
118
+ suite = await buildCustomCleanseSuite(cwd, specs);
119
+ for (const checker of suite.checkers) {
120
+ process.stdout.write(`[checker] ${checker.label}: ${checker.command}\n`);
121
+ }
122
+ }
123
+ if (!suite || suite.checkers.length === 0) {
124
+ const report: CleanseDiagnosticReport = { checks: [], diagnostics: [], skipped: [...(suite?.skipped ?? [])] };
54
125
  printSkippedChecks(report);
55
- process.stderr.write("No supported checker with an available executable was found.\n");
56
- return { exitCode: 1, status: "unsupported", report };
126
+ process.stderr.write(
127
+ request
128
+ ? "Checker discovery produced no runnable command.\n"
129
+ : "No supported checker with an available executable was found.\n",
130
+ );
131
+ return { exitCode: 1, status: "unsupported", report, sessionFile: runtime?.sessionFile };
57
132
  }
58
133
  const initialReport = await suite.run(abortController.signal);
59
134
  printCheckReport(initialReport);
@@ -61,7 +136,7 @@ export async function runCleanseCommand(options: CleanseCommandOptions = {}): Pr
61
136
  process.stdout.write(
62
137
  `Clean: ${initialReport.checks.length} checker${initialReport.checks.length === 1 ? "" : "s"} passed.\n`,
63
138
  );
64
- return { exitCode: 0, status: "clean", report: initialReport };
139
+ return { exitCode: 0, status: "clean", report: initialReport, sessionFile: runtime?.sessionFile };
65
140
  }
66
141
 
67
142
  const assignments = groupDiagnosticsByFile(initialReport.diagnostics);
@@ -70,33 +145,12 @@ export async function runCleanseCommand(options: CleanseCommandOptions = {}): Pr
70
145
  process.stdout.write(
71
146
  `Found ${initialReport.diagnostics.length} diagnostic${initialReport.diagnostics.length === 1 ? "" : "s"} across ${fileCount} file${fileCount === 1 ? "" : "s"}; launching ${agentCount} subagent${agentCount === 1 ? "" : "s"}.\n`,
72
147
  );
73
- process.stdout.write(`Resolving model ${model}...\n`);
74
- const activeRuntime = await createCleanseAgentRuntime({
75
- cwd,
76
- model,
77
- hooks: {
78
- onStart(name, assignment) {
79
- if (progress.interactive) return;
80
- process.stdout.write(
81
- `[start] ${name}: ${formatAssignmentFiles(assignment)} (weight ${assignment.weight})\n`,
82
- );
83
- },
84
- onFinish(outcome) {
85
- progress.complete();
86
- if (progress.interactive) {
87
- if (!outcome.success) interactiveFailures.push(outcome);
88
- return;
89
- }
90
- printAgentOutcome(outcome);
91
- },
92
- },
93
- });
94
- runtime = activeRuntime;
95
- process.stdout.write(`Model: ${activeRuntime.model}\nSession: ${shortenPath(activeRuntime.sessionFile)}\n`);
148
+ const activeRuntime = await ensureRuntime();
149
+ const activeSuite = suite;
96
150
  loopResult = await runCleanseLoop(
97
151
  { maxAgents, initialReport, signal: abortController.signal },
98
152
  {
99
- collect: signal => suite.run(signal),
153
+ collect: signal => activeSuite.run(signal),
100
154
  dispatch: (batch, wave, report, signal) => activeRuntime.dispatch(batch, wave, report, signal),
101
155
  onWave(_wave, batch) {
102
156
  process.stdout.write(
@@ -3,26 +3,40 @@ import { isRecord, sanitizeText } from "@oh-my-pi/pi-utils";
3
3
  import type { CleanseDiagnostic, CleanseSeverity } from "./types";
4
4
 
5
5
  /** Machine and fallback output formats understood by cleanse. */
6
- export type CleanseParserKind =
7
- | "rust"
8
- | "rust-test"
9
- | "go"
10
- | "go-test"
11
- | "ruff"
12
- | "pyright"
13
- | "eslint"
14
- | "biome"
15
- | "rubocop"
16
- | "phpstan"
17
- | "psalm"
18
- | "swiftlint"
19
- | "dart"
20
- | "credo"
21
- | "shellcheck"
22
- | "hlint"
23
- | "terraform"
24
- | "tflint"
25
- | "generic";
6
+ export const CLEANSE_PARSER_KINDS = [
7
+ "rust",
8
+ "rust-test",
9
+ "go",
10
+ "go-test",
11
+ "staticcheck",
12
+ "golangci",
13
+ "ruff",
14
+ "pyright",
15
+ "mypy",
16
+ "pylint",
17
+ "flake8",
18
+ "ty",
19
+ "eslint",
20
+ "biome",
21
+ "oxlint",
22
+ "deno-lint",
23
+ "stylelint",
24
+ "rubocop",
25
+ "phpstan",
26
+ "psalm",
27
+ "swiftlint",
28
+ "dart",
29
+ "credo",
30
+ "shellcheck",
31
+ "hlint",
32
+ "terraform",
33
+ "tflint",
34
+ "actionlint",
35
+ "generic",
36
+ ] as const;
37
+
38
+ /** One machine or fallback output format understood by cleanse. */
39
+ export type CleanseParserKind = (typeof CLEANSE_PARSER_KINDS)[number];
26
40
 
27
41
  /** Captured checker process output passed to a format parser. */
28
42
  export interface CleanseParserInput {
@@ -63,14 +77,32 @@ export function parseCleanseDiagnostics(kind: CleanseParserKind, input: CleanseP
63
77
  return parseGo(input);
64
78
  case "go-test":
65
79
  return parseGoTest(input);
80
+ case "staticcheck":
81
+ return parseStaticcheck(input);
82
+ case "golangci":
83
+ return parseGolangci(input);
66
84
  case "ruff":
67
85
  return parseRuff(input);
68
86
  case "pyright":
69
87
  return parsePyright(input);
88
+ case "mypy":
89
+ return parseGeneric(input);
90
+ case "pylint":
91
+ return parsePylint(input);
92
+ case "flake8":
93
+ return parseFlake8(input);
94
+ case "ty":
95
+ return parseTy(input);
70
96
  case "eslint":
71
97
  return parseEslint(input);
72
98
  case "biome":
73
99
  return parseBiome(input);
100
+ case "oxlint":
101
+ return parseUnixFormat(input);
102
+ case "deno-lint":
103
+ return parseDenoLint(input);
104
+ case "stylelint":
105
+ return parseStylelint(input);
74
106
  case "rubocop":
75
107
  return parseRubocop(input);
76
108
  case "phpstan":
@@ -91,6 +123,8 @@ export function parseCleanseDiagnostics(kind: CleanseParserKind, input: CleanseP
91
123
  return parseTerraform(input);
92
124
  case "tflint":
93
125
  return parseTflint(input);
126
+ case "actionlint":
127
+ return parseActionlint(input);
94
128
  case "generic":
95
129
  return parseGeneric(input);
96
130
  }
@@ -326,6 +360,44 @@ function parseGoTest(input: CleanseParserInput): CleanseDiagnostic[] {
326
360
  return diagnostics.length > 0 ? diagnostics : parseGeneric(input);
327
361
  }
328
362
 
363
+ function parseStaticcheck(input: CleanseParserInput): CleanseDiagnostic[] {
364
+ const diagnostics: CleanseDiagnostic[] = [];
365
+ for (const value of allJsonValues(input)) {
366
+ const record = toRecord(value);
367
+ const location = nestedRecord(record, "location");
368
+ const end = nestedRecord(record, "end");
369
+ addDiagnostic(diagnostics, input, {
370
+ file: stringField(location, "file"),
371
+ line: numberField(location, "line"),
372
+ column: numberField(location, "column"),
373
+ endLine: numberField(end, "line"),
374
+ endColumn: numberField(end, "column"),
375
+ code: stringField(record, "code"),
376
+ severity: stringField(record, "severity") ?? "warning",
377
+ message: stringField(record, "message"),
378
+ });
379
+ }
380
+ return diagnostics;
381
+ }
382
+
383
+ function parseGolangci(input: CleanseParserInput): CleanseDiagnostic[] {
384
+ const diagnostics: CleanseDiagnostic[] = [];
385
+ const text = sanitizeText(`${input.stdout}\n${input.stderr}`);
386
+ for (const line of text.split("\n")) {
387
+ const match = /^(.+?):(\d+):(\d+):\s+(.*?)\s+\(([A-Za-z0-9_-]+)\)$/.exec(line.trim());
388
+ if (!match) continue;
389
+ addDiagnostic(diagnostics, input, {
390
+ file: match[1],
391
+ line: Number.parseInt(match[2], 10),
392
+ column: Number.parseInt(match[3], 10),
393
+ code: match[5],
394
+ severity: "warning",
395
+ message: match[4],
396
+ });
397
+ }
398
+ return diagnostics;
399
+ }
400
+
329
401
  function parseRuff(input: CleanseParserInput): CleanseDiagnostic[] {
330
402
  const diagnostics: CleanseDiagnostic[] = [];
331
403
  for (const root of allJsonValues(input)) {
@@ -374,6 +446,62 @@ function parsePyright(input: CleanseParserInput): CleanseDiagnostic[] {
374
446
  return diagnostics;
375
447
  }
376
448
 
449
+ function parsePylint(input: CleanseParserInput): CleanseDiagnostic[] {
450
+ const diagnostics: CleanseDiagnostic[] = [];
451
+ for (const root of allJsonValues(input)) {
452
+ for (const value of toArray(root)) {
453
+ const record = toRecord(value);
454
+ addDiagnostic(diagnostics, input, {
455
+ file: stringField(record, "path"),
456
+ line: numberField(record, "line"),
457
+ column: zeroBased(numberField(record, "column")),
458
+ endLine: numberField(record, "endLine"),
459
+ endColumn: zeroBased(numberField(record, "endColumn")),
460
+ code: stringField(record, "symbol") ?? stringField(record, "message-id"),
461
+ severity: stringField(record, "type"),
462
+ message: stringField(record, "message"),
463
+ });
464
+ }
465
+ }
466
+ return diagnostics;
467
+ }
468
+
469
+ function parseFlake8(input: CleanseParserInput): CleanseDiagnostic[] {
470
+ const diagnostics: CleanseDiagnostic[] = [];
471
+ const text = sanitizeText(`${input.stdout}\n${input.stderr}`);
472
+ for (const line of text.split("\n")) {
473
+ const match = /^(.+?):(\d+):(\d+):\s+([A-Z]+\d+)\s+(.*)$/.exec(line.trim());
474
+ if (!match) continue;
475
+ addDiagnostic(diagnostics, input, {
476
+ file: match[1],
477
+ line: Number.parseInt(match[2], 10),
478
+ column: Number.parseInt(match[3], 10),
479
+ code: match[4],
480
+ severity: match[4].startsWith("F") || match[4].startsWith("E9") ? "error" : "warning",
481
+ message: match[5],
482
+ });
483
+ }
484
+ return diagnostics;
485
+ }
486
+
487
+ function parseTy(input: CleanseParserInput): CleanseDiagnostic[] {
488
+ const diagnostics: CleanseDiagnostic[] = [];
489
+ const text = sanitizeText(`${input.stdout}\n${input.stderr}`);
490
+ for (const line of text.split("\n")) {
491
+ const match = /^(.+?):(\d+):(\d+):\s+(error|warning|info)\[([^\]]+)\]\s+(.*)$/.exec(line.trim());
492
+ if (!match) continue;
493
+ addDiagnostic(diagnostics, input, {
494
+ file: match[1],
495
+ line: Number.parseInt(match[2], 10),
496
+ column: Number.parseInt(match[3], 10),
497
+ code: match[5],
498
+ severity: match[4],
499
+ message: match[6],
500
+ });
501
+ }
502
+ return diagnostics;
503
+ }
504
+
377
505
  function zeroBased(value: number | undefined): number | undefined {
378
506
  return value === undefined ? undefined : value + 1;
379
507
  }
@@ -424,6 +552,83 @@ function parseBiome(input: CleanseParserInput): CleanseDiagnostic[] {
424
552
  return diagnostics;
425
553
  }
426
554
 
555
+ function parseUnixFormat(input: CleanseParserInput): CleanseDiagnostic[] {
556
+ const diagnostics: CleanseDiagnostic[] = [];
557
+ const text = sanitizeText(`${input.stdout}\n${input.stderr}`);
558
+ for (const line of text.split("\n")) {
559
+ const match = /^(.+?):(\d+):(\d+):\s+(.*?)\s+\[(Error|Warning)\/([^\]]+)\]$/i.exec(line.trim());
560
+ if (!match) continue;
561
+ addDiagnostic(diagnostics, input, {
562
+ file: match[1],
563
+ line: Number.parseInt(match[2], 10),
564
+ column: Number.parseInt(match[3], 10),
565
+ code: match[6],
566
+ severity: match[5],
567
+ message: match[4],
568
+ });
569
+ }
570
+ return diagnostics;
571
+ }
572
+
573
+ function parseDenoLint(input: CleanseParserInput): CleanseDiagnostic[] {
574
+ const diagnostics: CleanseDiagnostic[] = [];
575
+ for (const root of allJsonValues(input)) {
576
+ const record = toRecord(root);
577
+ if (!record) continue;
578
+ for (const value of toArray(record.diagnostics)) {
579
+ const entry = toRecord(value);
580
+ const range = nestedRecord(entry, "range");
581
+ const start = nestedRecord(range, "start");
582
+ const end = nestedRecord(range, "end");
583
+ addDiagnostic(diagnostics, input, {
584
+ file: stringField(entry, "filename"),
585
+ line: numberField(start, "line"),
586
+ column: zeroBased(numberField(start, "col")),
587
+ endLine: numberField(end, "line"),
588
+ endColumn: zeroBased(numberField(end, "col")),
589
+ code: stringField(entry, "code"),
590
+ severity: "warning",
591
+ message: stringField(entry, "message"),
592
+ suggestion: stringField(entry, "hint"),
593
+ });
594
+ }
595
+ for (const value of toArray(record.errors)) {
596
+ const entry = toRecord(value);
597
+ addDiagnostic(diagnostics, input, {
598
+ file: stringField(entry, "file_path"),
599
+ severity: "error",
600
+ message: stringField(entry, "message"),
601
+ });
602
+ }
603
+ }
604
+ return diagnostics;
605
+ }
606
+
607
+ function parseStylelint(input: CleanseParserInput): CleanseDiagnostic[] {
608
+ const diagnostics: CleanseDiagnostic[] = [];
609
+ for (const root of allJsonValues(input)) {
610
+ for (const value of toArray(root)) {
611
+ const record = toRecord(value);
612
+ const source = stringField(record, "source");
613
+ if (!source) continue;
614
+ for (const warning of toArray(record?.warnings)) {
615
+ const entry = toRecord(warning);
616
+ addDiagnostic(diagnostics, input, {
617
+ file: source,
618
+ line: numberField(entry, "line"),
619
+ column: numberField(entry, "column"),
620
+ endLine: numberField(entry, "endLine"),
621
+ endColumn: numberField(entry, "endColumn"),
622
+ code: stringField(entry, "rule"),
623
+ severity: stringField(entry, "severity"),
624
+ message: stringField(entry, "text"),
625
+ });
626
+ }
627
+ }
628
+ }
629
+ return diagnostics;
630
+ }
631
+
427
632
  function parseRubocop(input: CleanseParserInput): CleanseDiagnostic[] {
428
633
  const diagnostics: CleanseDiagnostic[] = [];
429
634
  for (const rootValue of allJsonValues(input)) {
@@ -654,6 +859,24 @@ function parseTflint(input: CleanseParserInput): CleanseDiagnostic[] {
654
859
  return diagnostics;
655
860
  }
656
861
 
862
+ function parseActionlint(input: CleanseParserInput): CleanseDiagnostic[] {
863
+ const diagnostics: CleanseDiagnostic[] = [];
864
+ for (const root of allJsonValues(input)) {
865
+ for (const value of toArray(root)) {
866
+ const record = toRecord(value);
867
+ addDiagnostic(diagnostics, input, {
868
+ file: stringField(record, "filepath"),
869
+ line: numberField(record, "line"),
870
+ column: numberField(record, "column"),
871
+ code: stringField(record, "kind"),
872
+ severity: "error",
873
+ message: stringField(record, "message"),
874
+ });
875
+ }
876
+ }
877
+ return diagnostics;
878
+ }
879
+
657
880
  function parseGeneric(input: CleanseParserInput): CleanseDiagnostic[] {
658
881
  const diagnostics: CleanseDiagnostic[] = [];
659
882
  const text = sanitizeText(`${input.stdout}\n${input.stderr}`);
@@ -0,0 +1,74 @@
1
+ <critical>
2
+ - NEVER edit project files — read-and-verify discovery only.
3
+ - MUST run each proposed command once.
4
+ - Final: exactly one JSON object matching the schema below; no prose or code fences.
5
+ </critical>
6
+
7
+ # Checker Discovery
8
+
9
+ User requested `omp cleanse` detect and repair: **{{request}}**
10
+
11
+ Identify project command(s) surfacing exactly these diagnostics for orchestrator execution, output parsing, and repair-agent dispatch.
12
+
13
+ <workflow>
14
+ 1. Inspect manifests, configs, lockfiles, and scripts for relevant tooling.
15
+ 2. Determine exact command and working directory. Prefer project-local binaries (`node_modules/.bin`, `.venv/bin`, wrappers like `gradlew`) over global tools.
16
+ 3. Prefer machine-readable output matching a known parser below. Otherwise use gcc-style `file:line:col: severity: message` output and parser `generic`.
17
+ 4. Run once: verify execution and output matches the chosen parser. Non-zero exit with parseable diagnostics: fine; crash or usage error: not.
18
+ 5. If multiple commands cover the request, return one entry per command.
19
+ </workflow>
20
+
21
+ ## Known parsers
22
+
23
+ |id|expected output|
24
+ |---|---|
25
+ |`rust`|`cargo … --message-format=json`|
26
+ |`rust-test`|`cargo test … --message-format=json`|
27
+ |`go`|`go vet -json`|
28
+ |`go-test`|`go test -json`|
29
+ |`staticcheck`|`staticcheck -f json`|
30
+ |`golangci`|golangci-lint default text output|
31
+ |`ruff`|`ruff check --output-format=json`|
32
+ |`pyright`|`pyright`/`basedpyright` `--outputjson`|
33
+ |`mypy`|mypy default text output|
34
+ |`pylint`|`pylint --output-format=json`|
35
+ |`flake8`|flake8 default text output|
36
+ |`ty`|`ty check --output-format concise`|
37
+ |`eslint`|`eslint --format=json`|
38
+ |`biome`|`biome check --reporter=json`|
39
+ |`oxlint`|unix-format lines `file:line:col: message [Error/rule]` (`--format=unix`)|
40
+ |`deno-lint`|`deno lint --json`|
41
+ |`stylelint`|`stylelint --formatter json`|
42
+ |`rubocop`|`rubocop --format json`|
43
+ |`phpstan`|`phpstan analyse --error-format=json`|
44
+ |`psalm`|`psalm --output-format=json`|
45
+ |`swiftlint`|`swiftlint lint --reporter json`|
46
+ |`dart`|`dart analyze --format machine`|
47
+ |`credo`|`mix credo --format=json`|
48
+ |`shellcheck`|`shellcheck --format=json1`|
49
+ |`hlint`|`hlint --json`|
50
+ |`terraform`|`terraform validate -json`|
51
+ |`tflint`|`tflint --format=json`|
52
+ |`actionlint`|actionlint with its JSON `-format` template|
53
+ |`generic`|gcc-style `file:line:col: severity: message` lines (tsc/tsgo `--pretty false`, mypy, clang, zig, MSVC-style)|
54
+
55
+ ## Output schema
56
+
57
+ ```json
58
+ {
59
+ "checkers": [
60
+ {
61
+ "label": "tsc (packages/app)",
62
+ "language": "TypeScript",
63
+ "cwd": "packages/app",
64
+ "command": ["tsc", "--noEmit", "--pretty", "false"],
65
+ "parser": "generic"
66
+ }
67
+ ]
68
+ }
69
+ ```
70
+
71
+ - `command`: argv array; first element: binary name or project-relative path. NEVER shell-wrap.
72
+ - `cwd`: project-relative working directory; omit for project root.
73
+ - `parser`: known parser id; omit for `generic`.
74
+ - Return `"checkers": []` only if nothing in this project produces the requested diagnostics.
package/src/cli/args.ts CHANGED
@@ -48,6 +48,7 @@ export interface Args {
48
48
  serviceTier?: ServiceTierOpenAISettingValue;
49
49
  hideThinking?: boolean;
50
50
  advisor?: boolean;
51
+ externalThinking?: boolean;
51
52
  continue?: boolean;
52
53
  resume?: string | true;
53
54
  fromClaude?: boolean;
@@ -255,6 +256,8 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
255
256
  result.hideThinking = true;
256
257
  } else if (arg === "--advisor") {
257
258
  result.advisor = true;
259
+ } else if (arg === "--external-thinking") {
260
+ result.externalThinking = true;
258
261
  } else if (arg === "--prewalk") {
259
262
  result.prewalk = true;
260
263
  } else if (arg === "--no-prewalk") {
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Standalone TUI pickers for `omp cleanse`.
3
+ *
4
+ * Mirrors {@link ./setup-model-picker.ts}: one-shot {@link TUI} instances over a
5
+ * {@link SelectList} or {@link Input}, resolved on select/submit/cancel and torn
6
+ * down immediately so the command can keep writing plain stdout afterwards.
7
+ */
8
+ import { Input, ProcessTerminal, type SelectItem, SelectList, TUI } from "@oh-my-pi/pi-tui";
9
+ import type { CleanseCheckerDescriptor } from "../cleanse/checkers";
10
+ import { getSelectListTheme } from "../modes/theme/theme";
11
+
12
+ /** Outcome of the interactive cleanse target picker. */
13
+ export type CleanseTargetChoice =
14
+ | { kind: "all" }
15
+ | { kind: "checker"; id: string }
16
+ | { kind: "request"; request: string }
17
+ | { kind: "cancel" };
18
+
19
+ /** Pick between running every discovered checker, one specific checker, or a free-form request. */
20
+ export async function pickCleanseTarget(checkers: readonly CleanseCheckerDescriptor[]): Promise<CleanseTargetChoice> {
21
+ const items: SelectItem[] = [
22
+ {
23
+ value: "all",
24
+ label: `Run all ${checkers.length} discovered checker${checkers.length === 1 ? "" : "s"}`,
25
+ },
26
+ ...checkers.map(checker => ({
27
+ value: `checker:${checker.id}`,
28
+ label: checker.label,
29
+ description: `${checker.language} — ${checker.command}`,
30
+ })),
31
+ {
32
+ value: "request",
33
+ label: "Describe what to fix…",
34
+ description: "A discovery agent figures out the command to run",
35
+ },
36
+ ];
37
+ const selection = await selectOne("Select what to cleanse:", items);
38
+ if (selection === null) return { kind: "cancel" };
39
+ if (selection === "all") return { kind: "all" };
40
+ if (selection === "request") {
41
+ const request = await promptCleanseRequest();
42
+ return request === null ? { kind: "cancel" } : { kind: "request", request };
43
+ }
44
+ return { kind: "checker", id: selection.slice("checker:".length) };
45
+ }
46
+
47
+ /** One-shot text prompt for a free-form cleanse request; `null` when cancelled or left empty. */
48
+ export async function promptCleanseRequest(): Promise<string | null> {
49
+ const { promise, resolve } = Promise.withResolvers<string | null>();
50
+ const ui = new TUI(new ProcessTerminal());
51
+ let resolved = false;
52
+ const finish = (value: string | null): void => {
53
+ if (resolved) return;
54
+ resolved = true;
55
+ ui.stop();
56
+ resolve(value);
57
+ };
58
+ const input = new Input();
59
+ input.onSubmit = value => finish(value.trim() || null);
60
+ input.onEscape = () => finish(null);
61
+ process.stdout.write('Describe what to detect and fix (e.g. "ts errors"):\n');
62
+ ui.addChild(input);
63
+ ui.setFocus(input);
64
+ ui.start();
65
+ return promise;
66
+ }
67
+
68
+ async function selectOne(title: string, items: SelectItem[]): Promise<string | null> {
69
+ const { promise, resolve } = Promise.withResolvers<string | null>();
70
+ const ui = new TUI(new ProcessTerminal());
71
+ let resolved = false;
72
+ const finish = (value: string | null): void => {
73
+ if (resolved) return;
74
+ resolved = true;
75
+ ui.stop();
76
+ resolve(value);
77
+ };
78
+ const list = new SelectList(items, Math.min(items.length, 12), getSelectListTheme());
79
+ list.onSelect = item => finish(item.value);
80
+ list.onCancel = () => finish(null);
81
+ process.stdout.write(`${title}\n`);
82
+ ui.addChild(list);
83
+ ui.setFocus(list);
84
+ ui.start();
85
+ return promise;
86
+ }
@@ -34,6 +34,10 @@ export const completionsHelp = {
34
34
 
35
35
  export const completeHelp = { hidden: true } satisfies CommandMetadata;
36
36
 
37
+ export const compressHelp = {
38
+ description: "Rewrite a text file into the dense prompt register, reporting what it drops",
39
+ } satisfies CommandMetadata;
40
+
37
41
  export const configHelp = { description: "Manage configuration settings" } satisfies CommandMetadata;
38
42
 
39
43
  export const dryBalanceHelp = {