one-code-extension 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +202 -0
  3. package/agents/explore.md +24 -0
  4. package/agents/general-purpose.md +21 -0
  5. package/agents/plan.md +22 -0
  6. package/docs/decisions/auto-mode.md +526 -0
  7. package/docs/decisions/branding.md +127 -0
  8. package/docs/decisions/code-review-remediation.md +106 -0
  9. package/docs/decisions/compaction.md +140 -0
  10. package/docs/decisions/distribution.md +133 -0
  11. package/docs/decisions/hooks.md +64 -0
  12. package/docs/decisions/lsp.md +58 -0
  13. package/docs/decisions/mcp.md +104 -0
  14. package/docs/decisions/memory-state.md +170 -0
  15. package/docs/decisions/model-policy.md +126 -0
  16. package/docs/decisions/modes.md +178 -0
  17. package/docs/decisions/skills-plugins.md +56 -0
  18. package/docs/decisions/subagents-workflows.md +225 -0
  19. package/docs/decisions/system-prompt.md +98 -0
  20. package/docs/decisions/tools.md +407 -0
  21. package/docs/decisions/tui.md +202 -0
  22. package/docs/decisions.md +148 -0
  23. package/extensions/ask-user/index.ts +124 -0
  24. package/extensions/ask-user/questions.ts +60 -0
  25. package/extensions/auto-mode/actions.ts +78 -0
  26. package/extensions/auto-mode/classifier.ts +388 -0
  27. package/extensions/auto-mode/config.ts +237 -0
  28. package/extensions/auto-mode/decision-log.ts +60 -0
  29. package/extensions/auto-mode/defaults.ts +65 -0
  30. package/extensions/auto-mode/instructions.ts +74 -0
  31. package/extensions/auto-mode/model-picker.ts +225 -0
  32. package/extensions/auto-mode/model-select.ts +178 -0
  33. package/extensions/auto-mode/paths.ts +109 -0
  34. package/extensions/auto-mode/pause.ts +96 -0
  35. package/extensions/auto-mode/prompt.ts +307 -0
  36. package/extensions/auto-mode/rules.ts +96 -0
  37. package/extensions/auto-mode/safety-floor.ts +100 -0
  38. package/extensions/auto-mode/sensitive.ts +137 -0
  39. package/extensions/auto-mode/shell-analysis.ts +702 -0
  40. package/extensions/background/index.ts +405 -0
  41. package/extensions/background/registry.ts +81 -0
  42. package/extensions/background/wakeup.ts +41 -0
  43. package/extensions/bash/background.ts +136 -0
  44. package/extensions/bash/index.ts +149 -0
  45. package/extensions/branding/assistant-marker.ts +53 -0
  46. package/extensions/branding/index.ts +366 -0
  47. package/extensions/branding/prompt-editor.ts +42 -0
  48. package/extensions/branding/prompt-marker.ts +29 -0
  49. package/extensions/branding/startup.ts +152 -0
  50. package/extensions/claude-compat/index.ts +50 -0
  51. package/extensions/claude-context/index.ts +79 -0
  52. package/extensions/compaction/index.ts +231 -0
  53. package/extensions/compaction/prompt.ts +191 -0
  54. package/extensions/context-management/index.ts +147 -0
  55. package/extensions/effort/index.ts +216 -0
  56. package/extensions/effort/slider.ts +253 -0
  57. package/extensions/exit/index.ts +28 -0
  58. package/extensions/file-tracker/index.ts +103 -0
  59. package/extensions/file-tracker/tracker.ts +145 -0
  60. package/extensions/hooks/debug.ts +53 -0
  61. package/extensions/hooks/executor.ts +132 -0
  62. package/extensions/hooks/index.ts +310 -0
  63. package/extensions/hooks/matcher.ts +72 -0
  64. package/extensions/hooks/plugin-hooks.ts +79 -0
  65. package/extensions/hooks/protocol.ts +151 -0
  66. package/extensions/hooks/settings.ts +163 -0
  67. package/extensions/hooks/trust.ts +151 -0
  68. package/extensions/lib/branch-restore.ts +35 -0
  69. package/extensions/lib/claude-context.ts +148 -0
  70. package/extensions/lib/deferred.ts +119 -0
  71. package/extensions/lib/git.ts +14 -0
  72. package/extensions/lib/memory.ts +219 -0
  73. package/extensions/lib/model-policy.ts +436 -0
  74. package/extensions/lib/model-tier.ts +99 -0
  75. package/extensions/lib/notifications.ts +18 -0
  76. package/extensions/lib/paths.ts +26 -0
  77. package/extensions/lib/per-cwd.ts +21 -0
  78. package/extensions/lib/persisted-output.ts +70 -0
  79. package/extensions/lib/pi-version.ts +54 -0
  80. package/extensions/lib/plugins.ts +227 -0
  81. package/extensions/lib/reminders.ts +212 -0
  82. package/extensions/lib/scratchpad.ts +68 -0
  83. package/extensions/lib/tui-render.ts +522 -0
  84. package/extensions/lsp/client.ts +278 -0
  85. package/extensions/lsp/format.ts +46 -0
  86. package/extensions/lsp/index.ts +193 -0
  87. package/extensions/lsp/protocol.ts +64 -0
  88. package/extensions/lsp/servers.ts +79 -0
  89. package/extensions/mcp/client.ts +205 -0
  90. package/extensions/mcp/config.ts +199 -0
  91. package/extensions/mcp/index.ts +373 -0
  92. package/extensions/mcp/schema.ts +161 -0
  93. package/extensions/memory/index.ts +90 -0
  94. package/extensions/notebook/index.ts +77 -0
  95. package/extensions/notebook/notebook.ts +108 -0
  96. package/extensions/permissions/index.ts +876 -0
  97. package/extensions/permissions/matcher.ts +455 -0
  98. package/extensions/permissions/modes.ts +107 -0
  99. package/extensions/permissions/protected-paths.ts +125 -0
  100. package/extensions/permissions/settings.ts +98 -0
  101. package/extensions/plan-mode/index.ts +209 -0
  102. package/extensions/plan-mode/reminder.ts +33 -0
  103. package/extensions/plan-mode/slug.ts +28 -0
  104. package/extensions/plan-mode/viewer.ts +124 -0
  105. package/extensions/plugins/index.ts +115 -0
  106. package/extensions/plugins/template.ts +42 -0
  107. package/extensions/search-tools/index.ts +0 -0
  108. package/extensions/search-tools/tools.ts +24 -0
  109. package/extensions/skill/index.ts +170 -0
  110. package/extensions/subagents/agents.ts +126 -0
  111. package/extensions/subagents/child.ts +414 -0
  112. package/extensions/subagents/default-model.ts +122 -0
  113. package/extensions/subagents/index.ts +1119 -0
  114. package/extensions/subagents/model-select.ts +346 -0
  115. package/extensions/subagents/rpc-turns.ts +123 -0
  116. package/extensions/subagents/runs.ts +93 -0
  117. package/extensions/subagents/usage.ts +53 -0
  118. package/extensions/subagents/worktree.ts +69 -0
  119. package/extensions/system-prompt/environment.ts +35 -0
  120. package/extensions/system-prompt/index.ts +54 -0
  121. package/extensions/system-prompt/template.ts +103 -0
  122. package/extensions/system-prompt/tiers/common.ts +63 -0
  123. package/extensions/system-prompt/tiers/frontier.ts +24 -0
  124. package/extensions/system-prompt/tiers/low.ts +71 -0
  125. package/extensions/system-prompt/tiers/mid.ts +56 -0
  126. package/extensions/system-reminder/index.ts +37 -0
  127. package/extensions/tasks/index.ts +140 -0
  128. package/extensions/tasks/store.ts +174 -0
  129. package/extensions/todo/index.ts +133 -0
  130. package/extensions/tool-search/index.ts +168 -0
  131. package/extensions/tool-style/index.ts +84 -0
  132. package/extensions/web/index.ts +43 -0
  133. package/extensions/web-fetch/extract.ts +139 -0
  134. package/extensions/web-fetch/index.ts +291 -0
  135. package/extensions/web-fetch/summarize.ts +78 -0
  136. package/extensions/workflow/agent-session.ts +368 -0
  137. package/extensions/workflow/globals.ts +251 -0
  138. package/extensions/workflow/index.ts +364 -0
  139. package/extensions/workflow/journal.ts +75 -0
  140. package/extensions/workflow/permission-gate.ts +71 -0
  141. package/extensions/workflow/run-manager.ts +306 -0
  142. package/extensions/workflow/saved-workflows.ts +45 -0
  143. package/extensions/workflow/script-source.ts +201 -0
  144. package/extensions/workflow/types.ts +99 -0
  145. package/extensions/workflow/vm-runtime.ts +31 -0
  146. package/extensions/workflow/widget.ts +56 -0
  147. package/extensions/worktree/index.ts +266 -0
  148. package/extensions/worktree/rewrite.ts +59 -0
  149. package/package.json +101 -0
  150. package/themes/one-code-light.json +76 -0
  151. package/themes/one-code.json +76 -0
  152. package/types/app.d.ts +5 -0
  153. package/types/community-packages.d.ts +12 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Environment facts for the system prompt's # Environment section.
3
+ * Collected once per (cwd, model) and cached so the prompt stays byte-stable
4
+ * across turns — required for provider prompt caching to pay off.
5
+ */
6
+
7
+ import os from "node:os";
8
+ import { findGitRoot } from "../lib/git.ts";
9
+ import { projectMemoryDir } from "../lib/memory.ts";
10
+
11
+ export interface EnvironmentInfo {
12
+ cwd: string;
13
+ isGitRepo: boolean;
14
+ platform: string;
15
+ osVersion: string;
16
+ shell: string;
17
+ date: string;
18
+ modelLine: string;
19
+ /** Per-project auto-memory directory; the memory extension guarantees it exists. */
20
+ memoryDir: string;
21
+ }
22
+
23
+ export function collectEnvironment(cwd: string, modelLine: string): EnvironmentInfo {
24
+ const gitRoot = findGitRoot(cwd);
25
+ return {
26
+ cwd,
27
+ isGitRepo: gitRoot !== undefined,
28
+ platform: process.platform,
29
+ osVersion: `${os.type()} ${os.release()}`,
30
+ shell: process.env.SHELL ? (process.env.SHELL.split("/").pop() ?? "unknown") : "unknown",
31
+ date: new Date().toISOString().slice(0, 10),
32
+ modelLine,
33
+ memoryDir: projectMemoryDir(cwd, os.homedir()),
34
+ };
35
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * system-prompt extension — replaces pi's default system prompt with the
3
+ * adapted Claude Code prompt on every turn via before_agent_start.
4
+ *
5
+ * The environment block is cached per (cwd, model) so the generated prompt is
6
+ * byte-stable across turns and provider prompt caching stays effective. The
7
+ * scratchpad path embeds the session id, so it lives outside that cache —
8
+ * derived at session_start, constant within the session.
9
+ */
10
+
11
+ import { mkdirSync } from "node:fs";
12
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import { resolveModelTier } from "../lib/model-tier.ts";
14
+ import { sessionScratchpadDir } from "../lib/scratchpad.ts";
15
+ import { collectEnvironment, type EnvironmentInfo } from "./environment.ts";
16
+ import { buildClaudeCodeSystemPrompt } from "./template.ts";
17
+
18
+ export default function systemPromptExtension(pi: ExtensionAPI) {
19
+ let cachedEnv: EnvironmentInfo | undefined;
20
+ let cachedKey = "";
21
+ let scratchpad: string | undefined;
22
+
23
+ pi.on("session_start", (_event, ctx) => {
24
+ // The prompt section promises a usable directory, so the extension that
25
+ // makes the promise creates it. Failure (unwritable /tmp) drops the
26
+ // section rather than promising a directory writes will error on.
27
+ const candidate = sessionScratchpadDir(ctx.cwd, ctx.sessionManager.getSessionId());
28
+ try {
29
+ mkdirSync(candidate, { recursive: true });
30
+ scratchpad = candidate;
31
+ } catch {
32
+ scratchpad = undefined;
33
+ }
34
+ });
35
+
36
+ pi.on("before_agent_start", (event, ctx) => {
37
+ // A named agent (or a `--system-prompt` launch) supplies its own prompt via
38
+ // customPrompt. Return nothing so pi's own builder uses it verbatim, rather
39
+ // than clobbering it with the tiered One Code prompt.
40
+ if (event.systemPromptOptions.customPrompt) return;
41
+
42
+ const model = ctx.model;
43
+ const modelLine = model ? `${model.id} (${model.provider})` : "unknown";
44
+ // Re-resolved every turn: the model (and so the tier) can change mid-session.
45
+ const tier = resolveModelTier(model);
46
+ const key = `${ctx.cwd}|${modelLine}|${tier}`;
47
+ if (!cachedEnv || cachedKey !== key) {
48
+ cachedEnv = collectEnvironment(ctx.cwd, modelLine);
49
+ cachedKey = key;
50
+ }
51
+
52
+ return { systemPrompt: buildClaudeCodeSystemPrompt(event.systemPromptOptions, cachedEnv, tier, scratchpad) };
53
+ });
54
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Claude Code system prompt, adapted for One Code, selected by model tier.
3
+ *
4
+ * The tier-specific section text lives in `tiers/` (one bundle per tier);
5
+ * this module is the tier-agnostic composer — it places the dynamic blocks
6
+ * (tools, memory, environment, scratchpad, project context, skills, cwd trailer)
7
+ * around the bundle's `lead`/`tail` sections. Sections tied to Anthropic-hosted
8
+ * features are dropped, and the environment block is generated dynamically.
9
+ *
10
+ * For a fixed tier this function must be pure and deterministic: same inputs,
11
+ * byte-identical output (prompt-cache stability). The frontier bundle reproduces
12
+ * the pre-tiering prompt exactly.
13
+ */
14
+
15
+ import type { BuildSystemPromptOptions } from "@earendil-works/pi-coding-agent";
16
+ import { memoryPromptSection } from "../lib/memory.ts";
17
+ import type { PromptTier } from "../lib/model-tier.ts";
18
+ import { scratchpadPromptSection } from "../lib/scratchpad.ts";
19
+ import type { EnvironmentInfo } from "./environment.ts";
20
+ import type { PromptBundle } from "./tiers/common.ts";
21
+ import { frontierBundle } from "./tiers/frontier.ts";
22
+ import { lowBundle } from "./tiers/low.ts";
23
+ import { midBundle } from "./tiers/mid.ts";
24
+
25
+ const BUNDLES: Record<PromptTier, PromptBundle> = {
26
+ frontier: frontierBundle,
27
+ mid: midBundle,
28
+ low: lowBundle,
29
+ };
30
+
31
+ function buildToolsSection(options: BuildSystemPromptOptions): string {
32
+ const tools = options.selectedTools ?? ["read", "bash", "edit", "write"];
33
+ const visible = tools.filter((name) => !!options.toolSnippets?.[name]);
34
+ const toolsList =
35
+ visible.length > 0 ? visible.map((name) => `- ${name}: ${options.toolSnippets![name]}`).join("\n") : "(none)";
36
+
37
+ const guidelines: string[] = [];
38
+ const seen = new Set<string>();
39
+ for (const g of options.promptGuidelines ?? []) {
40
+ const trimmed = g.trim();
41
+ if (trimmed && !seen.has(trimmed)) {
42
+ seen.add(trimmed);
43
+ guidelines.push(trimmed);
44
+ }
45
+ }
46
+ const guidelinesBlock = guidelines.length > 0 ? `\n\nGuidelines:\n${guidelines.map((g) => `- ${g}`).join("\n")}` : "";
47
+
48
+ return `# Available tools\n${toolsList}${guidelinesBlock}`;
49
+ }
50
+
51
+ function buildEnvironmentSection(env: EnvironmentInfo): string {
52
+ return `# Environment
53
+ - Working directory: ${env.cwd}
54
+ - Is a git repository: ${env.isGitRepo ? "yes" : "no"}
55
+ - Platform: ${env.platform}
56
+ - OS Version: ${env.osVersion}
57
+ - Shell: ${env.shell}
58
+ - Today's date: ${env.date}
59
+ - Model: ${env.modelLine}`;
60
+ }
61
+
62
+ export function buildClaudeCodeSystemPrompt(
63
+ options: BuildSystemPromptOptions,
64
+ env: EnvironmentInfo,
65
+ tier: PromptTier,
66
+ /**
67
+ * Per-session (it embeds the session id), so it rides outside the
68
+ * (cwd, model, tier)-cached EnvironmentInfo — constant within a session, which
69
+ * is all provider prompt caching needs.
70
+ */
71
+ scratchpadDir?: string,
72
+ ): string {
73
+ const bundle = BUNDLES[tier];
74
+ const sections = [
75
+ ...bundle.lead,
76
+ buildToolsSection(options),
77
+ // Claude Code orders Memory just before Environment; mid/low use the long spec.
78
+ memoryPromptSection(env.memoryDir, bundle.verboseMemory),
79
+ buildEnvironmentSection(env),
80
+ // Claude Code orders Scratchpad between Environment and the tail sections.
81
+ ...(scratchpadDir ? [scratchpadPromptSection(scratchpadDir)] : []),
82
+ ...bundle.tail,
83
+ ];
84
+
85
+ let prompt = sections.join("\n\n");
86
+
87
+ // Mirror pi's own custom-prompt assembly: append text, skills, and the
88
+ // trailing cwd line. CLAUDE.md / AGENTS.md context files are NOT put here —
89
+ // Claude Code injects them as the `# claudeMd` <system-reminder> on the first
90
+ // user message (extensions/claude-context), not in the system prompt.
91
+ if (options.appendSystemPrompt) {
92
+ prompt += `\n\n${options.appendSystemPrompt}`;
93
+ }
94
+
95
+ // Skills are NOT listed here — the skill extension emits them as the
96
+ // "available for use with the Skill tool" <system-reminder> on the first user
97
+ // message (Claude Code's block 3), framed for the `skill` tool rather than pi's
98
+ // read-the-file convention.
99
+
100
+ prompt += `\nCurrent working directory: ${env.cwd.replace(/\\/g, "/")}`;
101
+
102
+ return prompt;
103
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Prompt sections shared across tiers, kept verbatim from the pre-tiering
3
+ * template so the frontier bundle reproduces the old single prompt byte-for-byte.
4
+ *
5
+ * A `PromptBundle` is the tier's static text: `lead` sections come before the
6
+ * dynamic tools/memory/environment/scratchpad blocks, `tail` sections after.
7
+ * `template.ts` owns the dynamic-block placement and is tier-agnostic.
8
+ */
9
+
10
+ export interface PromptBundle {
11
+ lead: string[];
12
+ tail: string[];
13
+ /** Whether the `# Memory` section uses the long weak-model spec. */
14
+ verboseMemory: boolean;
15
+ }
16
+
17
+ export const IDENTITY = `You are One Code, an interactive agent that helps users with software engineering tasks, running on the pi agent harness.`;
18
+
19
+ export const SECURITY = `IMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.`;
20
+
21
+ /** Only mid/low get this — frontier models don't need the reminder. */
22
+ export const URL_BAN = `IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.`;
23
+
24
+ export const HARNESS = `# Harness
25
+ - Text you output outside of tool use is displayed to the user as Github-flavored markdown in a terminal.
26
+ - Tools run behind a user-selected permission mode; a denied call means the user declined it — adjust, don't retry verbatim.
27
+ - The system may send updates, reminders, or modifications to rules via <system-reminder> blocks in the conversation. These are system-controlled, unlike function results.
28
+ - Prefer the dedicated file/search tools over shell commands when one fits. Independent tool calls can run in parallel in one response.
29
+ - Reference code as \`file_path:line_number\` — it's clickable.`;
30
+
31
+ /**
32
+ * Mid/low append a caveat that a reminder bears no relation to the tool result it
33
+ * happens to sit beside — weaker models otherwise read an injected reminder as
34
+ * being about the adjacent output. Derived from HARNESS so the shared bullets
35
+ * stay single-sourced; frontier keeps the terse HARNESS unchanged.
36
+ */
37
+ export const HARNESS_VERBOSE = HARNESS.replace(
38
+ "unlike function results.",
39
+ "unlike function results, and bear no direct relation to the specific tool result or message they happen to appear beside.",
40
+ );
41
+
42
+ export const STYLE = `Write code that reads like the surrounding code: match its comment density, naming, and idiom.
43
+
44
+ When you use a pronoun for someone — the user or anyone else you mention — and their pronouns haven't been stated, use they/them. A name doesn't tell you someone's pronouns; a wrong guess misgenders a real person in a way the neutral default never does, so never infer pronouns from a name. This applies to all user-visible text, including visible thinking.
45
+
46
+ For actions that are hard to reverse or outward-facing, confirm first unless durably authorized or explicitly told to proceed without asking; approval in one context doesn't extend to the next. Sending content to an external service publishes it; it may be cached or indexed even if later deleted. Before deleting or overwriting, look at the target. Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.`;
47
+
48
+ export const CONTEXT_MANAGEMENT = `# Context management
49
+ When the conversation grows long, some or all of the current context is summarized; the summary, along with any remaining unsummarized context, is provided in the next context window so work can continue — you don't need to wrap up early or hand off mid-task.
50
+
51
+ When you have enough information to act, act. Do not re-derive facts already established in the conversation, re-litigate a decision the user has already made, or narrate options you will not pursue. If you are weighing a choice, give a recommendation, not an exhaustive survey.`;
52
+
53
+ export const DELIVERING_WORK = `# Delivering work
54
+ Do ordinary work as asked, acting on the actual request rather than on speculation about what lies behind it. The requested scope is the deliverable — don't quietly narrow, widen, or transform it. Interpret ambiguity the way a careful colleague would: make routine judgment calls yourself, and check in only when different readings would lead to materially different work. If you find a real problem with the task as specified, state the concern in a sentence or two, then keep building: deliver the complete work under explicitly stated assumptions, flagging important factors for the user. Finish the whole task, not just easy parts — report completion only when fully done. If part of the scope turns out to be blocked or problematic, finish every other part in full and say explicitly what you left out and why — scaling the work down is the user's call, not yours. Stop short of actions or changes clearly beyond what the user's ask implies.
55
+
56
+ If you find an uncertainty mid-task, first do everything that doesn't depend on the answer; for what does, state your assumption or ask your question to the user at the right time. Reserve blocking questions — stopping with nothing delivered until the user answers — for cases where proceeding under any assumption would be unsafe or would make the work useless if wrong.
57
+
58
+ If you raise a concern about a request and the user repeats or reaffirms it, treat that as their decision, communicate this, and proceed with the full request. Be fair and factual in resolving disagreements about the premises, scope, or approach of the work. Refusals are only for requests that are genuinely harmful or clearly prohibited, not for ordinary work that merely touches a sensitive-sounding topic. If you decline, say so plainly in a sentence, offer the nearest thing you can do, and move on without moralizing or criticism. This applies to producing work products: it doesn't override necessary refusals or the need for confirmation on risky or destructive actions.`;
59
+
60
+ export const CORRECTIONS = `# Corrections
61
+ Avoid unnecessary or excessive self-correction. Only correct an earlier statement in your user-facing text when the error would change the user's code, conclusions, or decisions. State corrections plainly and concisely, and continue the task; combine multiple corrections rather than enumerating them all. For slips that change nothing for the user, simply make the correction and move on - no need to note it explicitly. Don't add apologies or preambles, don't be overly self-critical, and don't ruminate or give a detailed account of the mistake or tally past errors. Sometimes, other agents will report incorrect or misleading results - don't always take them at face value immediately. If other agents correct your statements and they are right, then simply update your approach without narrating too much about the correction to the user.
62
+
63
+ A follow-up question about your earlier work is not, by itself, a signal that you got something wrong — answer what was asked. A statement that was accurate needs no correction: don't re-audit how you phrased it, how you verified it, or limits you already stated. When the user does point to a real error, correct it plainly as above.`;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Frontier tier — Anthropic Opus/Fable ≥ 4.8 and Sonnet ≥ 5. The lean register:
3
+ * the same sections One Code shipped before tiering, so the built prompt is
4
+ * byte-identical to the pre-tiering output for these models (regression guard in
5
+ * `test/unit/template.test.ts`). Splitting `IDENTITY`/`SECURITY` into two
6
+ * sections joins back to the old combined constant since sections join on "\n\n".
7
+ */
8
+
9
+ import {
10
+ CONTEXT_MANAGEMENT,
11
+ CORRECTIONS,
12
+ DELIVERING_WORK,
13
+ HARNESS,
14
+ IDENTITY,
15
+ type PromptBundle,
16
+ SECURITY,
17
+ STYLE,
18
+ } from "./common.ts";
19
+
20
+ export const frontierBundle: PromptBundle = {
21
+ lead: [IDENTITY, SECURITY, HARNESS, STYLE],
22
+ tail: [CONTEXT_MANAGEMENT, DELIVERING_WORK, CORRECTIONS],
23
+ verboseMemory: false,
24
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Low tier — identified small/cheap/local models. The most explicit register,
3
+ * bespoke for weak models: it layers the scaffolding that opencode's small-model
4
+ * prompts use (text≠action, an act-vs-answer rule, per-task playbooks, symmetric
5
+ * anti-over/under-action closers) onto Claude Code v2.1.81's tool discipline and
6
+ * an explicit skill nudge — the "small models miss skills" concern that motivated
7
+ * tiering, since the skill tool's own description is not tiered. Uses the long
8
+ * memory spec. Shared sections come from common.ts; verbose ones are reused from
9
+ * the mid tier.
10
+ */
11
+
12
+ import {
13
+ CONTEXT_MANAGEMENT,
14
+ CORRECTIONS,
15
+ DELIVERING_WORK,
16
+ HARNESS_VERBOSE,
17
+ IDENTITY,
18
+ type PromptBundle,
19
+ SECURITY,
20
+ STYLE,
21
+ URL_BAN,
22
+ } from "./common.ts";
23
+ import { DOING_TASKS, EXECUTING_CARE, TEXT_OUTPUT, TONE_STYLE } from "./mid.ts";
24
+
25
+ export const MAKE_CHANGES_WITH_TOOLS = `# Make changes with tools, not prose
26
+ Code, edits, or commands that appear only in your text reply are NOT applied — they are not saved to the filesystem and do not run. Never treat showing code as a substitute for making the change. To change a file, call the edit or write tool; to run something, call the shell tool. If a request needs a change to the workspace, your turn is not done until you have made it with a tool.`;
27
+
28
+ export const ANSWER_OR_ACT = `# Answer or act
29
+ Decide up front which the request needs:
30
+ - A simple question or greeting that doesn't touch the workspace → answer directly, no tools.
31
+ - Anything that inspects, changes, or runs code in the project → take action with tools; don't just describe what you would do.
32
+ - When it could be read either way → treat it as a task and act.
33
+ Do the work rather than asking permission to start; ask the user only when you genuinely cannot proceed without an answer.`;
34
+
35
+ export const USING_TOOLS = `# Using your tools
36
+ - Prefer the dedicated tools over the shell: use read to read files (not cat/head/tail/sed), edit to change them (not sed/awk), write to create them (not echo redirection), and the search tools to find files or content (not find/grep/ls). Reserve the shell for commands that genuinely need it.
37
+ - Break multi-step work down with the todo tool and keep it updated as you go.
38
+ - When a skill fits the task, use it — invoke it with the skill tool instead of redoing the same work by hand. Skills are set up on purpose; reach for the matching one rather than improvising.
39
+ - For broad, open-ended exploration of an unfamiliar codebase, delegate to a subagent; for a directed lookup you already know how to run, use the search tools directly.
40
+ - You can call multiple independent tools in one response — do so when the calls don't depend on each other.`;
41
+
42
+ export const PLAYBOOKS = `# Playbooks
43
+ - New code from scratch: understand the requirement, sketch the smallest design that meets it, write it with the edit/write tools, then run it or its tests with the shell.
44
+ - Bug fix: reproduce or locate the failure (an error, a failing test), read the surrounding code to find the root cause, make the minimal fix, then re-run to confirm it passes.
45
+ - New feature in existing code: read the neighbouring code first, follow its patterns and libraries, add the feature with minimal intrusion, and add or update tests if the project has them.
46
+ - Refactor: keep behaviour identical; update every caller when an interface changes; don't alter unrelated logic.`;
47
+
48
+ export const STAYING_ON_TRACK = `# Staying on track
49
+ Avoid failing in either direction:
50
+ - Don't stop early. If tests fail or the task isn't fully done, keep going until it works or you hit a genuine blocker you must raise with the user.
51
+ - Don't over-reach. Keep it simple, do only what was asked, and never hand the user more than they wanted — no extra features, refactors, or files they didn't ask for.
52
+ Think about the best approach, then act decisively; verify what you build by running it, not by assuming it works.`;
53
+
54
+ export const lowBundle: PromptBundle = {
55
+ lead: [
56
+ IDENTITY,
57
+ SECURITY,
58
+ URL_BAN,
59
+ HARNESS_VERBOSE,
60
+ MAKE_CHANGES_WITH_TOOLS,
61
+ ANSWER_OR_ACT,
62
+ USING_TOOLS,
63
+ DOING_TASKS,
64
+ PLAYBOOKS,
65
+ EXECUTING_CARE,
66
+ TEXT_OUTPUT,
67
+ TONE_STYLE,
68
+ ],
69
+ tail: [STAYING_ON_TRACK, STYLE, CONTEXT_MANAGEMENT, DELIVERING_WORK, CORRECTIONS],
70
+ verboseMemory: true,
71
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Mid tier — other Anthropic models (Haiku, Sonnet < 5, Opus 4.1–4.7) and
3
+ * non-Anthropic capable-but-not-small models. The verbose register, adapted from
4
+ * Claude Code's Haiku prompt: the behavioural guidance that frontier models infer
5
+ * is spelled out explicitly. One Code-branded; Anthropic-hosted specifics (/help,
6
+ * feedback URL, artifacts) dropped. Uses the long memory spec.
7
+ */
8
+
9
+ import {
10
+ CONTEXT_MANAGEMENT,
11
+ CORRECTIONS,
12
+ DELIVERING_WORK,
13
+ HARNESS_VERBOSE,
14
+ IDENTITY,
15
+ type PromptBundle,
16
+ SECURITY,
17
+ STYLE,
18
+ URL_BAN,
19
+ } from "./common.ts";
20
+
21
+ export const DOING_TASKS = `# Doing tasks
22
+ - When given an unclear or generic instruction, interpret it in the context of software-engineering work and the current directory. If asked to change "methodName" to snake case, find the method in the code and edit it — don't just reply with "method_name".
23
+ - Do not propose changes to code you haven't read. If the user asks about or wants you to modify a file, read it first.
24
+ - Prefer editing an existing file to creating a new one; don't create files unless they're necessary for the goal.
25
+ - Don't add features, refactor, or introduce abstractions beyond what the task requires. A bug fix doesn't need surrounding cleanup; a one-shot operation doesn't need a helper. Three similar lines beat a premature abstraction.
26
+ - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees; validate only at system boundaries (user input, external APIs).
27
+ - Be careful not to introduce security vulnerabilities (command injection, XSS, SQL injection, and the rest of the OWASP top 10). If you notice you wrote insecure code, fix it immediately.
28
+ - Default to writing no comments. Add one only when the WHY is non-obvious — a hidden constraint, a subtle invariant, a workaround. Don't explain WHAT the code does; well-named identifiers already do that.
29
+ - Prefer the dedicated tools over the shell: read/edit/write instead of cat/sed/echo, the search tools instead of find/grep. Reserve the shell for operations that genuinely need it.
30
+ - Use the todo tool to plan and track multi-step work; mark each item done as soon as it's done, rather than batching.`;
31
+
32
+ export const EXECUTING_CARE = `# Executing actions with care
33
+ Consider the reversibility and blast radius of each action. Local, reversible actions (editing files, running tests) you can take freely. For actions that are hard to reverse, affect shared systems beyond your machine, or are otherwise risky, transparently say what you're about to do and confirm first — the cost of pausing is low, the cost of an unwanted action (lost work, an unintended message, a deleted branch) is high. Approving an action once does not approve it in every later context; authorization holds for the scope specified, not beyond.
34
+
35
+ Examples that warrant confirmation:
36
+ - Destructive: deleting files or branches, dropping tables, killing processes, rm -rf, overwriting uncommitted changes.
37
+ - Hard to reverse: force-pushing, git reset --hard, amending published commits, removing or downgrading dependencies, changing CI/CD.
38
+ - Visible to others or shared state: pushing, opening or commenting on PRs and issues, sending messages, posting to external services.
39
+
40
+ Don't reach for a destructive shortcut to clear an obstacle — find the root cause instead of bypassing a safety check (e.g. --no-verify). If you find unexpected state (unfamiliar files, branches, config), investigate before deleting or overwriting; it may be the user's in-progress work. When unsure whether something should be kept, prefer a reversible step (move or stash it) over deletion.`;
41
+
42
+ export const TEXT_OUTPUT = `# Text output (does not apply to tool calls)
43
+ Assume the user sees only your text output, not your tool calls or thinking. Before your first tool call, say in one sentence what you're about to do. While working, give short updates at key moments — when you find something, change direction, or hit a blocker. Brief is good; silent is not. One sentence per update is usually enough.
44
+
45
+ Don't narrate your internal deliberation. Keep user-facing text to relevant updates and state results and decisions directly. Write so a reader can pick up cold — complete sentences, no unexplained shorthand — but keep it tight. End the turn with a one- or two-sentence summary: what changed and what's next, nothing more. Match the response to the task: a simple question gets a direct answer, not headers and sections.`;
46
+
47
+ export const TONE_STYLE = `# Tone and style
48
+ Be concise and direct; your output is read in a terminal. Lead with the answer or the action, not the reasoning. Skip preamble and postamble — don't restate the request or over-explain what you did. If it fits in one sentence, don't write three. This does not apply to code or tool calls.
49
+
50
+ Only use emojis if the user explicitly asks. When referencing code, use the \`file_path:line_number\` pattern so the user can jump to it. Do not put a colon before a tool call — since the call itself may not be shown, "Let me read the file:" followed by a read should just be "Let me read the file." with a period.`;
51
+
52
+ export const midBundle: PromptBundle = {
53
+ lead: [IDENTITY, SECURITY, URL_BAN, HARNESS_VERBOSE, STYLE, DOING_TASKS, EXECUTING_CARE, TEXT_OUTPUT, TONE_STYLE],
54
+ tail: [CONTEXT_MANAGEMENT, DELIVERING_WORK, CORRECTIONS],
55
+ verboseMemory: true,
56
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * system-reminder extension — injects queued <system-reminder> blocks into the
3
+ * outgoing LLM request (pi `context` event). Transient by design: the session
4
+ * file never contains the reminders, matching Claude Code behavior.
5
+ */
6
+
7
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
8
+ import { injectReminders, REMINDER_CHANNEL, reminderQueue, type ReminderPayload } from "../lib/reminders.ts";
9
+
10
+ export default function systemReminderExtension(pi: ExtensionAPI) {
11
+ pi.events.on(REMINDER_CHANNEL, (data) => {
12
+ const payload = data as ReminderPayload;
13
+ if (!payload) return;
14
+ if (payload.remove && payload.key) {
15
+ reminderQueue.remove(payload.key);
16
+ } else if (typeof payload.text === "string") {
17
+ reminderQueue.enqueue(payload.text, {
18
+ scope: payload.scope,
19
+ key: payload.key,
20
+ placement: payload.placement,
21
+ order: payload.order,
22
+ suffix: payload.suffix,
23
+ });
24
+ }
25
+ });
26
+
27
+ pi.on("context", (event) => {
28
+ if (reminderQueue.size === 0) return;
29
+ // injectReminders is a no-op when there is no user message to attach to, and
30
+ // drain() would clear the next-turn queue as a side effect — dropping those
31
+ // reminders for good. Only consume the queue once there is somewhere to put
32
+ // them, so they survive to the next eligible turn.
33
+ if (!event.messages.some((m) => m.role === "user")) return;
34
+ const reminders = reminderQueue.drain();
35
+ return { messages: injectReminders(event.messages, reminders) };
36
+ });
37
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * tasks extension — Claude Code's stateful Task list:
3
+ * task_create / task_get / task_list / task_update.
4
+ *
5
+ * Complements todo_write (the stateless whole-list variant, which stays):
6
+ * tasks are addressable by id and carry owners, metadata, and dependencies.
7
+ * State rides in tool-result details (same branch-safe pattern as todo), so
8
+ * resuming or branching a session restores the list current at that point.
9
+ * All four tools are deferred — discovered via tool_search.
10
+ */
11
+
12
+ import { StringEnum } from "@earendil-works/pi-ai";
13
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
14
+ import { Type } from "typebox";
15
+ import { restoreLatestDetails } from "../lib/branch-restore.ts";
16
+ import { DEFER_CHANNEL } from "../lib/deferred.ts";
17
+ import { ccToolRenderers } from "../lib/tui-render.ts";
18
+ import { formatTaskDetails, formatTaskLine, formatTaskList, type TaskSnapshot, TaskStore } from "./store.ts";
19
+
20
+ interface TaskDetails {
21
+ taskSnapshot: TaskSnapshot;
22
+ }
23
+
24
+ const TASK_TOOLS = new Set(["task_create", "task_get", "task_list", "task_update"]);
25
+
26
+ export default function tasksExtension(pi: ExtensionAPI) {
27
+ const store = new TaskStore();
28
+
29
+ const updateWidget = (ctx: ExtensionContext) => {
30
+ if (!ctx.hasUI) return;
31
+ const tasks = store.list();
32
+ if (tasks.length === 0) {
33
+ ctx.ui.setWidget("cc-tasks", undefined);
34
+ return;
35
+ }
36
+ const done = tasks.filter((t) => t.status === "completed").length;
37
+ const active = tasks.find((t) => t.status === "in_progress");
38
+ ctx.ui.setWidget("cc-tasks", [` tasks ${done}/${tasks.length}${active ? ` · ${active.activeForm ?? active.subject}` : ""}`]);
39
+ };
40
+
41
+ const reconstructState = (ctx: ExtensionContext) => {
42
+ const details = restoreLatestDetails<TaskDetails>(ctx.sessionManager.getBranch(), TASK_TOOLS, (d) => Boolean(d?.taskSnapshot));
43
+ store.restore(details?.taskSnapshot);
44
+ updateWidget(ctx);
45
+ };
46
+
47
+ pi.on("session_start", (_event, ctx) => reconstructState(ctx));
48
+ pi.on("session_tree", (_event, ctx) => reconstructState(ctx));
49
+
50
+ const result = (text: string, ctx: ExtensionContext, isError = false) => {
51
+ updateWidget(ctx);
52
+ return {
53
+ content: [{ type: "text" as const, text }],
54
+ details: { taskSnapshot: store.snapshot() } satisfies TaskDetails,
55
+ isError,
56
+ };
57
+ };
58
+
59
+ pi.registerTool({
60
+ name: "task_create",
61
+ label: "Create Task",
62
+ ...ccToolRenderers("Create Task"),
63
+ description:
64
+ "Add a task to the session's structured task list. Unlike todo_write (which replaces the whole list), tasks are addressable by id and can carry owners, metadata, and dependencies — use task_update to change status or link tasks, task_list/task_get to read them. Create tasks for multi-step work so progress is visible.",
65
+ parameters: Type.Object({
66
+ subject: Type.String({ description: "A brief title for the task" }),
67
+ description: Type.String({ description: "What needs to be done" }),
68
+ activeForm: Type.Optional(Type.String({ description: 'Present continuous form shown while in_progress (e.g. "Running tests")' })),
69
+ metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Arbitrary metadata to attach to the task" })),
70
+ }),
71
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
72
+ const task = store.create(params);
73
+ return result(`Created task #${task.id}: ${task.subject}`, ctx);
74
+ },
75
+ });
76
+
77
+ pi.registerTool({
78
+ name: "task_get",
79
+ label: "Get Task",
80
+ ...ccToolRenderers("Get Task"),
81
+ description: "Retrieve one task by id: full description, status, owner, and dependency links. Verify blockedBy is empty before starting work on it.",
82
+ parameters: Type.Object({
83
+ taskId: Type.String({ description: "The id of the task to retrieve" }),
84
+ }),
85
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
86
+ const task = store.get(params.taskId);
87
+ if (!task) return result(`No task with id "${params.taskId}". Use task_list to see ids.`, ctx, true);
88
+ return result(formatTaskDetails(store, task), ctx);
89
+ },
90
+ });
91
+
92
+ pi.registerTool({
93
+ name: "task_list",
94
+ label: "List Tasks",
95
+ ...ccToolRenderers("List Tasks", { maxCollapsedLines: 12 }),
96
+ description: "List all tasks: id, subject, status, owner, and open blockers. Prefer working on unblocked pending tasks in id order.",
97
+ parameters: Type.Object({}),
98
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
99
+ return result(formatTaskList(store), ctx);
100
+ },
101
+ });
102
+
103
+ pi.registerTool({
104
+ name: "task_update",
105
+ label: "Update Task",
106
+ ...ccToolRenderers("Update Task"),
107
+ description:
108
+ "Update a task: status (pending/in_progress/completed, or deleted to remove it), subject, description, owner, metadata (merge; null deletes a key), and dependencies via addBlocks/addBlockedBy. Mark a task in_progress before starting it and completed only when fully done.",
109
+ parameters: Type.Object({
110
+ taskId: Type.String({ description: "The id of the task to update" }),
111
+ status: Type.Optional(StringEnum(["pending", "in_progress", "completed", "deleted"] as const)),
112
+ subject: Type.Optional(Type.String()),
113
+ description: Type.Optional(Type.String()),
114
+ activeForm: Type.Optional(Type.String()),
115
+ owner: Type.Optional(Type.String({ description: "New owner for the task" })),
116
+ metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Metadata keys to merge; a null value deletes the key" })),
117
+ addBlocks: Type.Optional(Type.Array(Type.String(), { description: "Task ids that cannot start until this one completes" })),
118
+ addBlockedBy: Type.Optional(Type.Array(Type.String(), { description: "Task ids that must complete before this one" })),
119
+ }),
120
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
121
+ const { taskId, ...input } = params;
122
+ const outcome = store.update(taskId, input as Parameters<TaskStore["update"]>[1]);
123
+ if (outcome.deleted) return result(`Deleted task #${taskId}.`, ctx);
124
+ if (outcome.error && !outcome.task) return result(outcome.error, ctx, true);
125
+ const line = formatTaskLine(store, outcome.task!);
126
+ return result(outcome.error ? `${line}\n${outcome.error}` : line, ctx, Boolean(outcome.error));
127
+ },
128
+ });
129
+
130
+ for (const name of TASK_TOOLS) {
131
+ pi.events.emit(DEFER_CHANNEL, { name, keywords: ["task", "todo", "plan", "progress", "dependencies", "tracking"] });
132
+ }
133
+
134
+ pi.registerCommand("tasks", {
135
+ description: "Show the structured task list",
136
+ handler: async (_args, ctx) => {
137
+ ctx.ui.notify(formatTaskList(store), "info");
138
+ },
139
+ });
140
+ }