one-code-extension 0.1.4 → 0.3.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 (300) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +222 -177
  3. package/agents/explore.md +9 -6
  4. package/agents/general-purpose.md +1 -1
  5. package/agents/plan.md +6 -4
  6. package/docs/guide/README.md +52 -0
  7. package/docs/guide/bring-your-claude-code-setup.md +128 -0
  8. package/docs/guide/configuration.md +173 -0
  9. package/docs/guide/differences-from-claude-code.md +152 -0
  10. package/docs/guide/doctor.md +88 -0
  11. package/docs/guide/hooks.md +111 -0
  12. package/docs/guide/installation.md +126 -0
  13. package/docs/guide/permissions-modes-and-auto-mode.md +452 -0
  14. package/docs/guide/providers-and-models.md +196 -0
  15. package/docs/guide/reference.md +226 -0
  16. package/docs/guide/sessions-and-context.md +192 -0
  17. package/docs/guide/skills-plugins-and-mcp.md +178 -0
  18. package/docs/guide/subagents-and-workflows.md +195 -0
  19. package/docs/guide/tasks-and-background-work.md +113 -0
  20. package/docs/guide/terminal-interface.md +157 -0
  21. package/docs/guide/tools.md +177 -0
  22. package/docs/guide/troubleshooting.md +177 -0
  23. package/extensions/ask-user/index.ts +65 -71
  24. package/extensions/ask-user/questions.ts +15 -28
  25. package/extensions/ask-user/widget.ts +533 -0
  26. package/extensions/auto-mode/actions.ts +9 -26
  27. package/extensions/auto-mode/classifier-prompt.ts +127 -0
  28. package/extensions/auto-mode/classifier.ts +305 -227
  29. package/extensions/auto-mode/config.ts +279 -77
  30. package/extensions/auto-mode/defaults.ts +17 -60
  31. package/extensions/auto-mode/instructions.ts +2 -1
  32. package/extensions/auto-mode/model-picker.ts +53 -31
  33. package/extensions/auto-mode/model-select.ts +153 -82
  34. package/extensions/auto-mode/paths.ts +13 -1
  35. package/extensions/auto-mode/pause.ts +8 -0
  36. package/extensions/auto-mode/prompt.ts +205 -238
  37. package/extensions/auto-mode/recoverability.ts +229 -0
  38. package/extensions/auto-mode/rules.ts +92 -75
  39. package/extensions/auto-mode/safety-floor.ts +43 -12
  40. package/extensions/auto-mode/sensitive.ts +11 -0
  41. package/extensions/auto-mode/setup-run.ts +245 -0
  42. package/extensions/auto-mode/setup.ts +235 -0
  43. package/extensions/auto-mode/shell-analysis.ts +332 -26
  44. package/extensions/auto-mode/transcript.ts +102 -0
  45. package/extensions/background/index.ts +256 -43
  46. package/extensions/background/monitor-batch.ts +58 -0
  47. package/extensions/background/registry.ts +9 -0
  48. package/extensions/background/wakeup.ts +45 -0
  49. package/extensions/bash/background.ts +47 -22
  50. package/extensions/bash/guards.ts +229 -0
  51. package/extensions/bash/index.ts +81 -19
  52. package/extensions/branding/index.ts +44 -18
  53. package/extensions/branding/startup.ts +23 -8
  54. package/extensions/claude-compat/index.ts +17 -2
  55. package/extensions/claude-context/index.ts +41 -12
  56. package/extensions/clear/index.ts +27 -0
  57. package/extensions/compaction/fit.ts +121 -0
  58. package/extensions/compaction/index.ts +176 -71
  59. package/extensions/compaction/prompt.ts +21 -1
  60. package/extensions/context-budget/budget.ts +61 -0
  61. package/extensions/context-budget/index.ts +66 -0
  62. package/extensions/context-management/index.ts +40 -16
  63. package/extensions/doctor/build.ts +226 -0
  64. package/extensions/doctor/cli.ts +99 -0
  65. package/extensions/doctor/compat.ts +532 -0
  66. package/extensions/doctor/dependencies.ts +186 -0
  67. package/extensions/doctor/fix-prompt.ts +125 -0
  68. package/extensions/doctor/index.ts +308 -0
  69. package/extensions/doctor/models.ts +231 -0
  70. package/extensions/doctor/presets.ts +208 -0
  71. package/extensions/doctor/report.ts +189 -0
  72. package/extensions/doctor/update-lookup.ts +46 -0
  73. package/extensions/doctor/viewer.ts +124 -0
  74. package/extensions/effort/index.ts +41 -4
  75. package/extensions/effort/slider.ts +33 -2
  76. package/extensions/file-tracker/index.ts +143 -20
  77. package/extensions/file-tracker/replay.ts +46 -0
  78. package/extensions/file-tracker/tracker.ts +51 -4
  79. package/extensions/footer/footer-line.ts +207 -0
  80. package/extensions/footer/index.ts +152 -0
  81. package/extensions/footer/pr.ts +53 -0
  82. package/extensions/hooks/executor.ts +57 -20
  83. package/extensions/hooks/formatter-notice.ts +76 -0
  84. package/extensions/hooks/index.ts +314 -73
  85. package/extensions/hooks/matcher.ts +35 -1
  86. package/extensions/hooks/plugin-hooks.ts +3 -3
  87. package/extensions/hooks/protocol.ts +58 -0
  88. package/extensions/hooks/subagent-bridge.ts +77 -0
  89. package/extensions/hooks/trust.ts +15 -4
  90. package/extensions/init/index.ts +26 -0
  91. package/extensions/init/prompt.ts +40 -0
  92. package/extensions/interrupted/index.ts +35 -0
  93. package/extensions/interrupted/line.ts +9 -0
  94. package/extensions/lib/abort.ts +15 -0
  95. package/extensions/lib/agent-loader.ts +183 -0
  96. package/extensions/lib/anthropic-payload.ts +15 -0
  97. package/extensions/lib/atomic-write.ts +56 -0
  98. package/extensions/lib/bridge-watch.ts +20 -0
  99. package/extensions/lib/cache-retention.ts +60 -0
  100. package/extensions/lib/capability-index.ts +466 -0
  101. package/extensions/lib/claude-context.ts +318 -27
  102. package/extensions/lib/claude-settings.ts +64 -0
  103. package/extensions/lib/consent-preview.ts +30 -0
  104. package/extensions/lib/deferred.ts +144 -13
  105. package/extensions/lib/favorites.ts +43 -0
  106. package/extensions/lib/fetch-timeout.ts +15 -0
  107. package/extensions/lib/frontmatter.ts +31 -0
  108. package/extensions/lib/git.ts +44 -2
  109. package/extensions/lib/hook-gate.ts +90 -0
  110. package/extensions/lib/interrupt.ts +94 -0
  111. package/extensions/lib/mcp-overrides.ts +61 -0
  112. package/extensions/lib/mcp-share.ts +25 -0
  113. package/extensions/lib/mcp-status.ts +34 -0
  114. package/extensions/lib/memory.ts +41 -6
  115. package/extensions/lib/model-facts.generated.json +2480 -0
  116. package/extensions/lib/model-facts.ts +162 -0
  117. package/extensions/lib/model-policy.ts +145 -189
  118. package/extensions/lib/model-tier.ts +480 -64
  119. package/extensions/lib/model-unusable.ts +37 -0
  120. package/extensions/lib/notifications.ts +320 -1
  121. package/extensions/lib/one-code-settings.ts +63 -0
  122. package/extensions/lib/original-command.ts +71 -0
  123. package/extensions/lib/package-version.ts +31 -0
  124. package/extensions/lib/paths.ts +39 -4
  125. package/extensions/lib/permission-gate.ts +192 -0
  126. package/extensions/lib/persisted-output.ts +18 -0
  127. package/extensions/lib/pi-ai-estimate.ts +160 -0
  128. package/extensions/lib/pi-version.ts +1 -1
  129. package/extensions/lib/plan-mode-channels.ts +11 -0
  130. package/extensions/lib/plugin-overrides.ts +24 -0
  131. package/extensions/lib/plugin-root.ts +44 -0
  132. package/extensions/lib/plugins.ts +233 -54
  133. package/extensions/lib/prefix-warm-gate.ts +157 -0
  134. package/extensions/lib/process-tree.ts +56 -0
  135. package/extensions/lib/reminders.ts +321 -68
  136. package/extensions/lib/scratchpad.ts +11 -8
  137. package/extensions/lib/session-lifecycle.ts +28 -0
  138. package/extensions/lib/settings-channels.ts +14 -0
  139. package/extensions/lib/shell-tasks.ts +54 -0
  140. package/extensions/lib/skill-overrides.ts +86 -0
  141. package/extensions/lib/skill-scan.ts +110 -0
  142. package/extensions/lib/text-width.ts +267 -0
  143. package/extensions/lib/tui-render.ts +281 -54
  144. package/extensions/lib/usage-bus.ts +75 -0
  145. package/extensions/lib/usage-tracker.ts +67 -0
  146. package/extensions/lib/worktree-isolation.ts +170 -0
  147. package/extensions/lsp/client.ts +69 -12
  148. package/extensions/lsp/format.ts +12 -2
  149. package/extensions/lsp/index.ts +273 -84
  150. package/extensions/lsp/install-hints.ts +43 -0
  151. package/extensions/lsp/keep-alive.ts +19 -0
  152. package/extensions/lsp/plugin-servers.ts +331 -0
  153. package/extensions/lsp/watcher.ts +157 -0
  154. package/extensions/mcp/announce.ts +99 -0
  155. package/extensions/mcp/client.ts +181 -22
  156. package/extensions/mcp/config.ts +55 -24
  157. package/extensions/mcp/index.ts +586 -86
  158. package/extensions/mcp/oauth/browser.ts +26 -0
  159. package/extensions/mcp/oauth/callback.ts +104 -0
  160. package/extensions/mcp/oauth/flow.ts +68 -0
  161. package/extensions/mcp/oauth/provider.ts +147 -0
  162. package/extensions/mcp/oauth/store.ts +86 -0
  163. package/extensions/mcp/panel/keys.ts +34 -0
  164. package/extensions/mcp/panel/model.ts +62 -0
  165. package/extensions/mcp/panel/render.ts +141 -0
  166. package/extensions/mcp/panel/state.ts +108 -0
  167. package/extensions/mcp/schema.ts +83 -6
  168. package/extensions/mcp/trust.ts +340 -0
  169. package/extensions/memory/entries.ts +163 -0
  170. package/extensions/memory/index.ts +119 -2
  171. package/extensions/memory/open-external.ts +97 -0
  172. package/extensions/memory/panel.ts +124 -0
  173. package/extensions/model-default/index.ts +74 -0
  174. package/extensions/notebook/index.ts +36 -18
  175. package/extensions/notebook/notebook.ts +110 -17
  176. package/extensions/permissions/index.ts +1012 -247
  177. package/extensions/permissions/matcher.ts +488 -57
  178. package/extensions/permissions/modes.ts +55 -7
  179. package/extensions/permissions/project-trust.ts +81 -0
  180. package/extensions/permissions/protected-paths.ts +16 -3
  181. package/extensions/permissions/rule-prose.ts +79 -0
  182. package/extensions/permissions/session-grant.ts +91 -0
  183. package/extensions/permissions/settings.ts +112 -33
  184. package/extensions/permissions/subagent-gate.ts +57 -0
  185. package/extensions/plan-mode/index.ts +143 -33
  186. package/extensions/plan-mode/reminder.ts +16 -19
  187. package/extensions/plan-mode/viewer.ts +40 -31
  188. package/extensions/plugins/counts.ts +68 -0
  189. package/extensions/plugins/index.ts +496 -32
  190. package/extensions/plugins/install/install.ts +167 -0
  191. package/extensions/plugins/install/paths.ts +23 -0
  192. package/extensions/plugins/install/registry.ts +72 -0
  193. package/extensions/plugins/marketplace/git.ts +103 -0
  194. package/extensions/plugins/marketplace/official.ts +36 -0
  195. package/extensions/plugins/marketplace/parse.ts +94 -0
  196. package/extensions/plugins/marketplace/registry.ts +60 -0
  197. package/extensions/plugins/marketplace/sync.ts +146 -0
  198. package/extensions/plugins/marketplace/types.ts +128 -0
  199. package/extensions/plugins/panel/keys.ts +52 -0
  200. package/extensions/plugins/panel/render.ts +315 -0
  201. package/extensions/plugins/panel/rows.ts +230 -0
  202. package/extensions/plugins/panel/state.ts +333 -0
  203. package/extensions/recap/index.ts +215 -0
  204. package/extensions/recap/prompt.ts +44 -0
  205. package/extensions/recap/scheduler.ts +87 -0
  206. package/extensions/search-tools/index.ts +0 -0
  207. package/extensions/search-tools/tools.ts +7 -5
  208. package/extensions/skill/index.ts +360 -34
  209. package/extensions/skill/invoke.ts +185 -0
  210. package/extensions/skill/listing.ts +21 -0
  211. package/extensions/skill/panel/keys.ts +41 -0
  212. package/extensions/skill/panel/render.ts +91 -0
  213. package/extensions/skill/panel/state.ts +115 -0
  214. package/extensions/spinner/index.ts +110 -0
  215. package/extensions/spinner/line.ts +51 -0
  216. package/extensions/spinner/verbs.ts +201 -0
  217. package/extensions/subagents/agents.ts +88 -44
  218. package/extensions/subagents/default-model.ts +62 -32
  219. package/extensions/subagents/delegation-steer.ts +23 -0
  220. package/extensions/subagents/hand-back-review.ts +67 -0
  221. package/extensions/subagents/index.ts +1545 -346
  222. package/extensions/subagents/live-runs.ts +293 -0
  223. package/extensions/subagents/model-select.ts +252 -82
  224. package/extensions/subagents/outcome.ts +90 -0
  225. package/extensions/subagents/panel-keys.ts +41 -0
  226. package/extensions/subagents/panel-render.ts +357 -0
  227. package/extensions/subagents/panel-widget.ts +330 -0
  228. package/extensions/subagents/pending-claim.ts +45 -0
  229. package/extensions/subagents/prose.ts +105 -0
  230. package/extensions/subagents/runner.ts +674 -0
  231. package/extensions/subagents/runs.ts +68 -1
  232. package/extensions/subagents/send-to-main-tool.ts +55 -0
  233. package/extensions/subagents/session-turns.ts +138 -0
  234. package/extensions/subagents/shell-panel.ts +198 -0
  235. package/extensions/subagents/usage.ts +16 -4
  236. package/extensions/subagents/worktree.ts +13 -0
  237. package/extensions/system-prompt/environment.ts +5 -3
  238. package/extensions/system-prompt/git-status.ts +93 -0
  239. package/extensions/system-prompt/index.ts +23 -1
  240. package/extensions/system-prompt/template.ts +34 -4
  241. package/extensions/system-prompt/tiers/common.ts +31 -6
  242. package/extensions/system-prompt/tiers/low.ts +13 -3
  243. package/extensions/system-prompt/tiers/mid.ts +21 -8
  244. package/extensions/system-reminder/index.ts +53 -9
  245. package/extensions/tasks/index.ts +65 -16
  246. package/extensions/tasks/store.ts +76 -3
  247. package/extensions/tool-search/announce.ts +42 -0
  248. package/extensions/tool-search/index.ts +136 -13
  249. package/extensions/turn-duration/index.ts +60 -0
  250. package/extensions/turn-duration/line.ts +22 -0
  251. package/extensions/turn-duration/span.ts +41 -0
  252. package/extensions/turn-duration/verbs.ts +24 -0
  253. package/extensions/web/backends.ts +421 -0
  254. package/extensions/web/index.ts +178 -21
  255. package/extensions/web-fetch/index.ts +74 -18
  256. package/extensions/web-fetch/summarize.ts +1 -1
  257. package/extensions/workflow/agent-session.ts +182 -112
  258. package/extensions/workflow/globals.ts +129 -106
  259. package/extensions/workflow/index.ts +343 -28
  260. package/extensions/workflow/records.ts +118 -0
  261. package/extensions/workflow/run-manager.ts +80 -31
  262. package/extensions/workflow/saved-workflows.ts +2 -1
  263. package/extensions/workflow/script-worker.mjs +110 -0
  264. package/extensions/workflow/types.ts +74 -8
  265. package/extensions/workflow/viewer.ts +562 -0
  266. package/extensions/workflow/vm-runtime.ts +118 -22
  267. package/extensions/workflow/widget.ts +126 -18
  268. package/extensions/worktree/guards.ts +240 -0
  269. package/extensions/worktree/index.ts +30 -5
  270. package/extensions/worktree/rewrite.ts +32 -16
  271. package/package.json +20 -10
  272. package/skills/code-review/SKILL.md +165 -0
  273. package/skills/fewer-permission-prompts/SKILL.md +61 -0
  274. package/skills/security-review/SKILL.md +178 -0
  275. package/skills/simplify/SKILL.md +70 -0
  276. package/themes/{one-code-light.json → onecode-light.json} +6 -1
  277. package/themes/{one-code.json → onecode.json} +6 -1
  278. package/types/app.d.ts +8 -1
  279. package/types/community-packages.d.ts +45 -2
  280. package/docs/decisions/auto-mode.md +0 -526
  281. package/docs/decisions/branding.md +0 -127
  282. package/docs/decisions/code-review-remediation.md +0 -106
  283. package/docs/decisions/compaction.md +0 -140
  284. package/docs/decisions/distribution.md +0 -168
  285. package/docs/decisions/hooks.md +0 -64
  286. package/docs/decisions/lsp.md +0 -58
  287. package/docs/decisions/mcp.md +0 -104
  288. package/docs/decisions/memory-state.md +0 -170
  289. package/docs/decisions/model-policy.md +0 -126
  290. package/docs/decisions/modes.md +0 -178
  291. package/docs/decisions/skills-plugins.md +0 -56
  292. package/docs/decisions/subagents-workflows.md +0 -225
  293. package/docs/decisions/system-prompt.md +0 -98
  294. package/docs/decisions/tools.md +0 -407
  295. package/docs/decisions/tui.md +0 -217
  296. package/docs/decisions.md +0 -148
  297. package/extensions/subagents/child.ts +0 -414
  298. package/extensions/subagents/rpc-turns.ts +0 -123
  299. package/extensions/todo/index.ts +0 -133
  300. package/extensions/workflow/permission-gate.ts +0 -71
@@ -15,22 +15,37 @@
15
15
  import { existsSync, readdirSync, readFileSync } from "node:fs";
16
16
  import { basename, dirname, join, relative } from "node:path";
17
17
  import { findGitRoot } from "../lib/git.ts";
18
- import { discoverPlugins } from "../lib/plugins.ts";
18
+ import { defaultDiscoverRoots, discoverPlugins } from "../lib/plugins.ts";
19
+ import { claudeUserDir } from "../lib/paths.ts";
19
20
 
20
21
  export interface StartupSection {
21
22
  label: string;
22
23
  items: string[];
23
24
  }
24
25
 
25
- /** CLAUDE.md / CLAUDE.local.md / AGENTS.md from cwd up to the git root (or just cwd outside a repo). */
26
+ /**
27
+ * The project context files One Code actually gathers, from cwd up to the git
28
+ * root (or just cwd outside a repo), for the startup banner. Mirrors what the
29
+ * blocks send: a directory's CLAUDE.md, or its AGENTS.md when it has no CLAUDE.md
30
+ * (CLAUDE.md > AGENTS.md), plus CLAUDE.local.md and ONECODE.md when present — so
31
+ * AGENTS.md is listed only when it's really in play, not whenever it exists.
32
+ */
26
33
  export function contextFileNames(cwd: string): string[] {
27
34
  const stop = findGitRoot(cwd) ?? cwd;
28
35
  const found: string[] = [];
29
36
  let dir = cwd;
30
37
  while (true) {
31
- for (const name of ["CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"]) {
32
- const path = join(dir, name);
33
- if (existsSync(path)) found.push(relative(cwd, path) || name);
38
+ const rel = (name: string) => relative(cwd, join(dir, name)) || name;
39
+ const present = (name: string) => existsSync(join(dir, name));
40
+ // CLAUDE.md is primary; AGENTS.md stands in only when there is no CLAUDE.md.
41
+ if (present("CLAUDE.md")) found.push(rel("CLAUDE.md"));
42
+ else if (present("AGENTS.md")) found.push(rel("AGENTS.md"));
43
+ if (present("CLAUDE.local.md")) found.push(rel("CLAUDE.local.md"));
44
+ for (const name of ["ONECODE.md", "onecode.md", "One Code.md"]) {
45
+ if (present(name)) {
46
+ found.push(rel(name));
47
+ break;
48
+ }
34
49
  }
35
50
  if (dir === stop) break;
36
51
  const parent = dirname(dir);
@@ -49,7 +64,7 @@ export function contextFileNames(cwd: string): string[] {
49
64
  export function skillNames(cwd: string, home: string, agentDir: string): string[] {
50
65
  const dirs = [
51
66
  join(cwd, ".claude", "skills"),
52
- join(home, ".claude", "skills"),
67
+ join(claudeUserDir(home), "skills"),
53
68
  join(agentDir, "skills"),
54
69
  ];
55
70
  const names = new Set<string>();
@@ -63,7 +78,7 @@ export function skillNames(cwd: string, home: string, agentDir: string): string[
63
78
  // Unreadable dir: skip, same as pi would.
64
79
  }
65
80
  }
66
- for (const skill of discoverPlugins(join(home, ".claude")).skills) {
81
+ for (const skill of discoverPlugins(defaultDiscoverRoots(agentDir, cwd, home)).skills) {
67
82
  names.add(skill.name);
68
83
  }
69
84
  return [...names].sort((a, b) => a.localeCompare(b));
@@ -72,7 +87,7 @@ export function skillNames(cwd: string, home: string, agentDir: string): string[
72
87
  /** Saved workflow names from the Claude Code layout dirs (project shadows user). */
73
88
  export function workflowNames(cwd: string, home: string): string[] {
74
89
  const names = new Set<string>();
75
- for (const dir of [join(cwd, ".claude", "workflows"), join(home, ".claude", "workflows")]) {
90
+ for (const dir of [join(cwd, ".claude", "workflows"), join(claudeUserDir(home), "workflows")]) {
76
91
  if (!existsSync(dir)) continue;
77
92
  try {
78
93
  for (const entry of readdirSync(dir)) {
@@ -8,6 +8,12 @@
8
8
  * ~/.claude/commands, <project>/.claude/commands → slash commands (prompt templates;
9
9
  * Claude Code's $ARGUMENTS works as-is)
10
10
  *
11
+ * It also points discovery at the catalog of skills bundled with this package
12
+ * (`<package>/skills`) — Claude Code's self-contained built-in skills, which
13
+ * ship inside its binary rather than on disk, so nothing else would surface
14
+ * them. See docs/decisions/skills-plugins.md and docs/findings (the extraction
15
+ * process is recorded in .claude/skills/extract-cc-skills/).
16
+ *
11
17
  * CLAUDE.md needs no handling — pi discovers it natively alongside AGENTS.md.
12
18
  */
13
19
 
@@ -16,6 +22,9 @@ import os from "node:os";
16
22
  import { join } from "node:path";
17
23
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
24
  import { claudeConfigDir } from "../lib/paths.ts";
25
+ import { claudeUserDir } from "../lib/paths.ts";
26
+
27
+ import { BUNDLED_SKILLS_DIR } from "../lib/skill-scan.ts";
19
28
 
20
29
  /**
21
30
  * `claudeDir` is Claude Code's config dir (honours CLAUDE_CONFIG_DIR), defaulting
@@ -26,7 +35,8 @@ import { claudeConfigDir } from "../lib/paths.ts";
26
35
  export function claudeResourcePaths(
27
36
  cwd: string,
28
37
  home: string,
29
- claudeDir: string = join(home, ".claude"),
38
+ claudeDir: string = claudeUserDir(home),
39
+ bundledSkillsDir?: string,
30
40
  ): { skillPaths: string[]; promptPaths: string[] } {
31
41
  const candidates = {
32
42
  skillPaths: [
@@ -34,6 +44,11 @@ export function claudeResourcePaths(
34
44
  join(home, ".agents", "skills"),
35
45
  join(cwd, ".claude", "skills"),
36
46
  join(cwd, ".agents", "skills"),
47
+ // The bundled catalog is listed LAST so it loses a name collision:
48
+ // pi keeps the first-loaded skill for a given name, so a user or
49
+ // project skill of the same name wins and the bundled one is the
50
+ // fallback (see docs/decisions/skills-plugins.md).
51
+ ...(bundledSkillsDir ? [bundledSkillsDir] : []),
37
52
  ],
38
53
  promptPaths: [join(claudeDir, "commands"), join(cwd, ".claude", "commands")],
39
54
  };
@@ -45,6 +60,6 @@ export function claudeResourcePaths(
45
60
 
46
61
  export default function claudeCompatExtension(pi: ExtensionAPI) {
47
62
  pi.on("resources_discover", (event) => {
48
- return claudeResourcePaths(event.cwd, os.homedir(), claudeConfigDir());
63
+ return claudeResourcePaths(event.cwd, os.homedir(), claudeConfigDir(), BUNDLED_SKILLS_DIR);
49
64
  });
50
65
  }
@@ -10,6 +10,11 @@
10
10
  * pi's `context` event), so it never persists to the session and never doubles up
11
11
  * on resume.
12
12
  *
13
+ * When ONECODE.md files exist, this extension also emits a separate `# oneCodeMd`
14
+ * block at CONTEXT_ORDER.oneCodeMd — after # claudeMd, so One Code-specific
15
+ * instructions take precedence over CLAUDE.md. Keeping them out of # claudeMd
16
+ * leaves that block byte-exact with Claude Code.
17
+ *
13
18
  * Paths are re-derived from home/cwd here rather than shared with the memory
14
19
  * extension (jiti gives each extension its own module instance). CLAUDE.md is no
15
20
  * longer put in the system prompt, and MEMORY.md is no longer a separate reminder
@@ -21,12 +26,13 @@ import { readFileSync } from "node:fs";
21
26
  import os from "node:os";
22
27
  import { join } from "node:path";
23
28
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
24
- import { buildClaudeMdBlock, discoverContextFiles } from "../lib/claude-context.ts";
29
+ import { buildClaudeMdBlock, buildOneCodeBlock, discoverContextFiles, discoverOneCodeFiles } from "../lib/claude-context.ts";
25
30
  import { projectMemoryDir, truncateIndex } from "../lib/memory.ts";
26
- import { claudeConfigDir } from "../lib/paths.ts";
31
+ import { claudeConfigDir, oneCodeStateDir } from "../lib/paths.ts";
27
32
  import { CONTEXT_ORDER, REMINDER_CHANNEL } from "../lib/reminders.ts";
28
33
 
29
34
  const REMINDER_KEY = "claude-context";
35
+ const ONECODE_REMINDER_KEY = "one-code-context";
30
36
 
31
37
  /** The account email Claude Code stamps as `# userEmail`. git config is our proxy. */
32
38
  function resolveEmail(cwd: string): string | null {
@@ -53,10 +59,17 @@ function readMemoryIndex(cwd: string): { path: string; content: string } | null
53
59
 
54
60
  export default function claudeContextExtension(pi: ExtensionAPI) {
55
61
  pi.on("session_start", (_event, ctx) => {
62
+ // The # claudeMd block carries the CLAUDE.md family, falling back to a
63
+ // directory's AGENTS.md when it has no CLAUDE.md (CLAUDE.md > AGENTS.md). It
64
+ // stays byte-exact with Claude Code wherever CLAUDE.md is present, since
65
+ // AGENTS.md only fills in for a missing one. ONECODE.md rides its own
66
+ // higher-precedence block below.
56
67
  const inner = buildClaudeMdBlock({
57
68
  contextFiles: discoverContextFiles({
58
69
  cwd: ctx.cwd,
59
70
  homeClaudeDir: claudeConfigDir(),
71
+ agentsFallback: true,
72
+ home: os.homedir(),
60
73
  }),
61
74
  memoryIndex: readMemoryIndex(ctx.cwd),
62
75
  email: resolveEmail(ctx.cwd),
@@ -64,16 +77,32 @@ export default function claudeContextExtension(pi: ExtensionAPI) {
64
77
  // block changes only across days too.
65
78
  date: new Date().toISOString().slice(0, 10),
66
79
  });
67
- if (!inner) return;
80
+ if (inner) {
81
+ pi.events.emit(REMINDER_CHANNEL, {
82
+ text: inner,
83
+ scope: "every-turn",
84
+ key: REMINDER_KEY,
85
+ placement: "first-prepend",
86
+ order: CONTEXT_ORDER.claudeMd,
87
+ // Claude Code's claudeMd block ends `</system-reminder>\n\n` on the wire.
88
+ suffix: "\n\n",
89
+ });
90
+ }
68
91
 
69
- pi.events.emit(REMINDER_CHANNEL, {
70
- text: inner,
71
- scope: "every-turn",
72
- key: REMINDER_KEY,
73
- placement: "first-prepend",
74
- order: CONTEXT_ORDER.claudeMd,
75
- // Claude Code's claudeMd block ends `</system-reminder>\n\n` on the wire.
76
- suffix: "\n\n",
77
- });
92
+ // One Code's own instructions ride in a separate block AFTER # claudeMd, so
93
+ // they take precedence over CLAUDE.md (higher order = closer to the user text).
94
+ const oneCode = buildOneCodeBlock(
95
+ discoverOneCodeFiles({ cwd: ctx.cwd, homeOneCodeDir: oneCodeStateDir(), home: os.homedir() }),
96
+ );
97
+ if (oneCode) {
98
+ pi.events.emit(REMINDER_CHANNEL, {
99
+ text: oneCode,
100
+ scope: "every-turn",
101
+ key: ONECODE_REMINDER_KEY,
102
+ placement: "first-prepend",
103
+ order: CONTEXT_ORDER.oneCodeMd,
104
+ suffix: "\n\n",
105
+ });
106
+ }
78
107
  });
79
108
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * clear extension — Claude Code's `/clear`.
3
+ *
4
+ * Claude Code's `/clear` wipes the conversation and starts fresh in the same
5
+ * working directory. pi's nearest primitive is `newSession()` (its built-in
6
+ * command is `/new`). A user coming from Claude Code types `/clear` out of
7
+ * habit; without this it isn't a command, so the word goes to the model as a
8
+ * message instead of resetting context. This registers `/clear` alongside pi's
9
+ * `/new`, routed through the same `newSession()` call.
10
+ *
11
+ * Nuance vs. Claude Code: CC clears history in place; pi starts a new session
12
+ * file (same cwd, extensions, config). The user-facing effect is identical —
13
+ * a clean context — which is what a `/clear` habit wants. Like `/exit`→`/quit`,
14
+ * we cannot fold this into pi's `/new` palette entry (pi has no command
15
+ * aliases), so `/clear` and `/new` are two entries that both reset.
16
+ */
17
+
18
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
19
+
20
+ export default function clearExtension(pi: ExtensionAPI) {
21
+ pi.registerCommand("clear", {
22
+ description: "Clear the conversation and start fresh (same as /new)",
23
+ handler: async (_args, ctx) => {
24
+ await ctx.newSession();
25
+ },
26
+ });
27
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Which request shape a compaction takes, and how a standalone one is made to
3
+ * fit the window. Pure: no pi extension API, only pi-ai's own estimator.
4
+ *
5
+ * Two shapes exist:
6
+ *
7
+ * - **Replay**: the session's last real request (captured from the `context`
8
+ * event) plus the instruction. Cache-aligned, so the call is mostly cache
9
+ * reads — the reason the extension compacts on the session model at all.
10
+ * It only works while the request leaves room for the summary: pi clamps
11
+ * `max_tokens` to what the window has left (`clampMaxTokensToContext`), so a
12
+ * request at or over the window is sent with `max_tokens` 1, or the provider
13
+ * rejects it outright. Both return nothing usable. An overflow-triggered
14
+ * compaction is *by definition* in that state (review H2: every overflow
15
+ * fell through to pi's summary).
16
+ *
17
+ * - **Standalone**: the doomed span alone (what pi is about to discard,
18
+ * reconstructed from `preparation`), no tools, a one-line system prompt, and
19
+ * the instruction. It cannot hit the cache, which is moot on an overflowing
20
+ * request anyway; what it keeps is One Code's summary: the nine sections,
21
+ * the continuation preamble, and the transcript pointer. Even the doomed
22
+ * span may not fit — pi keeps only ~20k tokens after the cut, so on an
23
+ * overflow the span is nearly the whole window — so `fitToBudget` clears
24
+ * the largest tool results first until the request has room. Their full
25
+ * text is in the session transcript the summary points at (Claude Code's
26
+ * own microcompact clears old tool results the same way).
27
+ *
28
+ * Both decisions ask pi-ai's `clampMaxTokensToContext` — the very function
29
+ * `completeSimple` applies to the request — rather than re-deriving its
30
+ * arithmetic, so they can never disagree with the clamp they predict. One
31
+ * trap in that estimator: it anchors on the last assistant message's
32
+ * `usage` total when there is one (`withoutUsage`).
33
+ */
34
+
35
+ import { type Api, type Context, contentText, type Message, type Model } from "@earendil-works/pi-ai";
36
+ import { PREVIEW_BYTES } from "../lib/persisted-output.ts";
37
+ // Vendored from pi-ai: importing its deep subpaths directly breaks under the
38
+ // bundled app's library loader (distribution review 2026-09-09, H1).
39
+ import { clampMaxTokensToContext, estimateMessageTokens } from "../lib/pi-ai-estimate.ts";
40
+
41
+ /**
42
+ * The least output room a replay must leave for the summary. Below it the
43
+ * clamp truncates the reply (or the request is rejected) and pi's summary
44
+ * would serve instead. With pi's default `reserveTokens` (16 384) a threshold
45
+ * compaction leaves ~12k, comfortably above; an overflow leaves nothing.
46
+ */
47
+ export const MIN_REPLAY_SUMMARY_TOKENS = 4096;
48
+
49
+ /** The output room a standalone request is trimmed to leave for the summary. */
50
+ export const STANDALONE_SUMMARY_TOKENS = 8192;
51
+
52
+ /** How much of a cleared tool result's text is kept as a landmark (persisted-output's preview size). */
53
+ export const CLEARED_RESULT_HEAD_CHARS = PREVIEW_BYTES;
54
+
55
+ /**
56
+ * True when replaying the captured request would leave room for the summary:
57
+ * the `max_tokens` pi will actually send, given this request, is at least the
58
+ * floor. An overflow never replays — the request that just failed *is* the
59
+ * capture, whatever the estimate says.
60
+ */
61
+ export function replayFits(reason: "manual" | "threshold" | "overflow", model: Model<Api>, request: Context, maxTokens: number): boolean {
62
+ if (reason === "overflow") return false;
63
+ return clampMaxTokensToContext(model, request, maxTokens) >= MIN_REPLAY_SUMMARY_TOKENS;
64
+ }
65
+
66
+ /**
67
+ * Clears the largest tool results (oldest first on a tie) until pi's clamp
68
+ * would leave `STANDALONE_SUMMARY_TOKENS` of output room, or none are left to
69
+ * clear (then the provider's verdict decides, and pi's summary serves on
70
+ * failure). Nothing but tool results is touched. Returns a new context with
71
+ * new messages — the inputs are never mutated.
72
+ */
73
+ export function fitToBudget(model: Model<Api>, request: Context, maxTokens: number): { request: Context; cleared: number } {
74
+ const messages = [...request.messages];
75
+ const fitted: Context = { ...request, messages };
76
+ const candidates = messages
77
+ .map((message, index) => ({ index, tokens: message.role === "toolResult" ? estimateMessageTokens(message) : -1 }))
78
+ .filter(({ tokens }) => tokens >= 0)
79
+ .sort((a, b) => b.tokens - a.tokens || a.index - b.index);
80
+
81
+ let cleared = 0;
82
+ for (const { index } of candidates) {
83
+ if (clampMaxTokensToContext(model, fitted, maxTokens) >= STANDALONE_SUMMARY_TOKENS) break;
84
+ messages[index] = clearToolResult(messages[index]);
85
+ cleared++;
86
+ }
87
+ return { request: fitted, cleared };
88
+ }
89
+
90
+ /**
91
+ * The messages with every assistant `usage` zeroed. pi-ai's estimator anchors
92
+ * on the *last assistant message's usage total* plus the messages after it
93
+ * (`estimateContextTokens`), and a usage total describes the request it came
94
+ * from — the session's, with its ~20k of system prompt and tools, the
95
+ * messages before the span, and on an overflow a total already past the
96
+ * window. Anchored on it, a standalone request that fit by content was still
97
+ * clamped to `max_tokens` 1 (measured). Zero usage is skipped by the
98
+ * estimator (it needs a positive total), so the clamp counts what is actually
99
+ * sent. The replay path keeps its usage: there the numbers describe the very
100
+ * prefix being replayed. Copies; the inputs are untouched.
101
+ */
102
+ export function withoutUsage(messages: readonly Message[]): Message[] {
103
+ return messages.map((message) =>
104
+ message.role === "assistant" && message.usage
105
+ ? { ...message, usage: { ...message.usage, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 } }
106
+ : message,
107
+ );
108
+ }
109
+
110
+ /** A tool result reduced to its opening text and a note; images are dropped. */
111
+ export function clearToolResult(message: Message): Message {
112
+ if (message.role !== "toolResult") return message;
113
+ const text = contentText(message.content);
114
+ const head = text.slice(0, CLEARED_RESULT_HEAD_CHARS);
115
+ const omitted = text.length - head.length;
116
+ const images = message.content.filter((block) => block.type !== "text").length;
117
+ const note = `[Tool result cleared to fit the compaction request: ${omitted} more characters${
118
+ images > 0 ? ` and ${images} image${images === 1 ? "" : "s"}` : ""
119
+ } omitted. The full result is in the session transcript.]`;
120
+ return { ...message, content: [{ type: "text", text: head ? `${head}\n${note}` : note }] };
121
+ }