@herbertgao/pi-extensions 2026.8.1 → 2026.8.2
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.
- package/README.md +5 -2
- package/node_modules/@czottmann/pi-automode/README.md +16 -2
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +35 -0
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +18 -9
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +6 -2
- package/node_modules/@czottmann/pi-automode/examples/automode.local.json +6 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +11 -7
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +100 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +32 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +78 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +16 -4
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +23 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +22 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +18 -2
- package/node_modules/@czottmann/pi-automode/package.json +4 -4
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +23 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +99 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +30 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +16 -0
- package/node_modules/@dietrichgebert/ponytail/AGENTS.md +32 -0
- package/node_modules/@dietrichgebert/ponytail/LICENSE +21 -0
- package/node_modules/@dietrichgebert/ponytail/README.es.md +298 -0
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +315 -0
- package/node_modules/@dietrichgebert/ponytail/README.md +351 -0
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +21 -0
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +62 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +115 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +27 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +27 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +44 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +21 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +96 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +169 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +98 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +130 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +85 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.ps1 +24 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.sh +18 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +77 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +26 -0
- package/node_modules/@dietrichgebert/ponytail/package.json +47 -0
- package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +211 -0
- package/node_modules/@dietrichgebert/ponytail/pi-extension/package.json +8 -0
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +60 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +120 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +41 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +44 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +50 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +71 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +57 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +10 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary.ts +6 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -83
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/markdown-enhance.ts +119 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +73 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-hover.ts +130 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-interaction.ts +65 -542
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-layout.ts +160 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-packets.ts +143 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-scroll.ts +184 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/diff-renderer.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-grouping.ts +9 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +91 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/agent-summary.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -0
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +2 -2
- package/node_modules/@herbertgao/pi-stash/README.md +9 -9
- package/node_modules/@herbertgao/pi-stash/package.json +1 -1
- package/node_modules/@herbertgao/pi-stash/src/index.ts +2 -1
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +14 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +6 -2
- package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +85 -11
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +28 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +12 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +2 -0
- package/node_modules/@narumitw/pi-btw/LICENSE +21 -0
- package/node_modules/@narumitw/pi-btw/README.md +210 -0
- package/node_modules/@narumitw/pi-btw/package.json +53 -0
- package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +632 -0
- package/node_modules/@narumitw/pi-btw/src/btw.ts +996 -0
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +339 -0
- package/node_modules/@narumitw/pi-btw/src/index.ts +1 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +296 -0
- package/node_modules/@narumitw/pi-btw/src/settings.ts +237 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +238 -0
- package/node_modules/@narumitw/pi-btw/src/text.ts +10 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +620 -0
- package/node_modules/@pi-plugins/fast-mode/LICENSE +21 -0
- package/node_modules/@pi-plugins/fast-mode/README.md +92 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts +6 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +2026 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -0
- package/node_modules/@pi-plugins/fast-mode/package.json +73 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +59 -0
- package/node_modules/pi-mcp-adapter/README.md +62 -13
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +377 -0
- package/node_modules/pi-mcp-adapter/commands.ts +9 -7
- package/node_modules/pi-mcp-adapter/config.ts +110 -49
- package/node_modules/pi-mcp-adapter/direct-tools.ts +34 -19
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +37 -36
- package/node_modules/pi-mcp-adapter/errors.ts +21 -17
- package/node_modules/pi-mcp-adapter/host-html-template.ts +13 -7
- package/node_modules/pi-mcp-adapter/index.ts +37 -14
- package/node_modules/pi-mcp-adapter/init.ts +9 -7
- package/node_modules/pi-mcp-adapter/json-schema-validator.ts +2 -2
- package/node_modules/pi-mcp-adapter/lifecycle.ts +8 -8
- package/node_modules/pi-mcp-adapter/logger.ts +1 -1
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +36 -22
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +120 -2
- package/node_modules/pi-mcp-adapter/mcp-code.ts +13 -11
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +19 -13
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +15 -11
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +34 -15
- package/node_modules/pi-mcp-adapter/mcp-probe.ts +131 -34
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +2 -2
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +7 -4
- package/node_modules/pi-mcp-adapter/mcp-trace.ts +59 -64
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +34 -26
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +6 -4
- package/node_modules/pi-mcp-adapter/oauth-handler.ts +1 -1
- package/node_modules/pi-mcp-adapter/onboarding-state.ts +19 -11
- package/node_modules/pi-mcp-adapter/package.json +13 -8
- package/node_modules/pi-mcp-adapter/panel-keys.ts +20 -4
- package/node_modules/pi-mcp-adapter/prompts.ts +2 -2
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +48 -11
- package/node_modules/pi-mcp-adapter/sampling-handler.ts +22 -15
- package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +165 -113
- package/node_modules/pi-mcp-adapter/session-recovery.ts +11 -12
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +3 -3
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +67 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -5
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +14 -2
- package/node_modules/pi-mcp-adapter/ts-shape.ts +9 -2
- package/node_modules/pi-mcp-adapter/types.ts +109 -22
- package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +40 -0
- package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +28 -19
- package/node_modules/pi-mcp-adapter/ui-server.ts +90 -58
- package/node_modules/pi-mcp-adapter/ui-session.ts +16 -14
- package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +4 -4
- package/node_modules/pi-mcp-adapter/utils.ts +23 -9
- package/package.json +22 -7
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
relative,
|
|
8
8
|
resolve,
|
|
9
9
|
} from "node:path";
|
|
10
|
-
import { HOME, PROFILE_FILES } from "./constants.ts";
|
|
10
|
+
import { HOME, PATH_BEARING_TOOLS, PROFILE_FILES } from "./constants.ts";
|
|
11
11
|
|
|
12
12
|
function stripLeadingAt(value: string): string {
|
|
13
13
|
return value.startsWith("@") ? value.slice(1) : value;
|
|
@@ -22,6 +22,28 @@ export function resolveInputPath(
|
|
|
22
22
|
return isAbsolute(raw) ? resolve(raw) : resolve(cwd, raw);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** The target path a file tool operates on, from `input.path` (or undefined). */
|
|
26
|
+
export function extractInputPath(
|
|
27
|
+
toolName: string,
|
|
28
|
+
input: Record<string, unknown>,
|
|
29
|
+
): string | undefined {
|
|
30
|
+
if (!PATH_BEARING_TOOLS.has(toolName)) return undefined;
|
|
31
|
+
const value = input.path;
|
|
32
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Expand a leading `~`, `$HOME`, or `${HOME}` in a path-denial pattern. */
|
|
36
|
+
export function expandHomePattern(pattern: string): string {
|
|
37
|
+
const home = HOME.replace(/\\/g, "/");
|
|
38
|
+
if (pattern === "~" || pattern === "$HOME" || pattern === "${HOME}") {
|
|
39
|
+
return home;
|
|
40
|
+
}
|
|
41
|
+
if (pattern.startsWith("~/")) return `${home}/${pattern.slice(2)}`;
|
|
42
|
+
if (pattern.startsWith("$HOME/")) return `${home}/${pattern.slice(6)}`;
|
|
43
|
+
if (pattern.startsWith("${HOME}/")) return `${home}/${pattern.slice(8)}`;
|
|
44
|
+
return pattern;
|
|
45
|
+
}
|
|
46
|
+
|
|
25
47
|
export function normalizePathForMatch(path: string, cwd: string): string {
|
|
26
48
|
const normalized = normalize(path);
|
|
27
49
|
const rel = relative(cwd, normalized);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { ToolPattern } from "./types.ts";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
expandHomePattern,
|
|
4
|
+
normalizePathForMatch,
|
|
5
|
+
resolveInputPath,
|
|
6
|
+
} from "./paths.ts";
|
|
3
7
|
|
|
4
8
|
function normalizeToolName(name: string): string {
|
|
5
9
|
const lower = name.trim().replace(/^@/, "").toLowerCase();
|
|
@@ -75,6 +79,23 @@ function getPrimaryArgument(
|
|
|
75
79
|
return JSON.stringify(input);
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Whether a resolved absolute path matches a configured path-denial pattern.
|
|
84
|
+
* Patterns support `~`/`$HOME` expansion and `*` globs, where `*` matches any
|
|
85
|
+
* characters, including `/`. Matching is case-insensitive and
|
|
86
|
+
* conservative-safe: over-matching only blocks more.
|
|
87
|
+
*/
|
|
88
|
+
export function matchesDeniedPath(
|
|
89
|
+
resolvedPath: string,
|
|
90
|
+
deniedPaths: string[],
|
|
91
|
+
): boolean {
|
|
92
|
+
const normalized = resolvedPath.replace(/\\/g, "/");
|
|
93
|
+
return deniedPaths.some((pattern) => {
|
|
94
|
+
const expanded = expandHomePattern(pattern).replace(/\\/g, "/");
|
|
95
|
+
return wildcardToRegExp(expanded).test(normalized);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
78
99
|
/** Match a scoped permission rule against a concrete tool call. */
|
|
79
100
|
export function matchesToolPattern(
|
|
80
101
|
pattern: ToolPattern,
|
|
@@ -40,6 +40,14 @@ export type AutoModeSettings = {
|
|
|
40
40
|
enabled?: boolean;
|
|
41
41
|
classifierModel?: string;
|
|
42
42
|
classifierReasoningLevel?: ClassifierReasoningLevel;
|
|
43
|
+
/** When true, read-only tools (read/grep/find/ls) are classified instead of auto-allowed. */
|
|
44
|
+
classifyReadOnlyTools?: boolean;
|
|
45
|
+
/** Override the fast-stage completion token budget (default 512). */
|
|
46
|
+
fastClassifierMaxTokens?: number;
|
|
47
|
+
/** When true, file tools whose resolved path is inside the working directory are allowed deterministically (no classifier), and outside-CWD file access is classified. */
|
|
48
|
+
allowInsideWorkingDirectory?: boolean;
|
|
49
|
+
/** Path glob patterns (file tools) that are always denied before the classifier. Supports `~` and `*` (matches any characters, including `/`). */
|
|
50
|
+
deniedPaths?: unknown;
|
|
43
51
|
maxUserTranscriptTokens?: number;
|
|
44
52
|
maxToolTranscriptTokens?: number;
|
|
45
53
|
environment?: unknown;
|
|
@@ -76,6 +84,10 @@ export type EffectiveConfig = {
|
|
|
76
84
|
enabled: boolean;
|
|
77
85
|
classifierModel?: string;
|
|
78
86
|
classifierReasoningLevel?: ClassifierReasoningLevel;
|
|
87
|
+
classifyReadOnlyTools: boolean;
|
|
88
|
+
fastClassifierMaxTokens: number;
|
|
89
|
+
allowInsideWorkingDirectory: boolean;
|
|
90
|
+
deniedPaths: string[];
|
|
79
91
|
maxUserTranscriptTokens: number;
|
|
80
92
|
maxToolTranscriptTokens: number;
|
|
81
93
|
environment: string[];
|
|
@@ -108,12 +120,16 @@ export type DenialRecord = {
|
|
|
108
120
|
| "permissions.deny"
|
|
109
121
|
| "permissions.ask"
|
|
110
122
|
| "deterministic-hard-deny"
|
|
123
|
+
| "deterministic-path-deny"
|
|
111
124
|
| "classifier"
|
|
112
125
|
| "setup";
|
|
113
126
|
};
|
|
114
127
|
|
|
115
|
-
/** Denial kind plus the
|
|
116
|
-
export type DecisionKind =
|
|
128
|
+
/** Denial kind plus the deterministic allow fast paths, used for decision log entries. */
|
|
129
|
+
export type DecisionKind =
|
|
130
|
+
| DenialRecord["kind"]
|
|
131
|
+
| "read-only"
|
|
132
|
+
| "inside-working-directory";
|
|
117
133
|
|
|
118
134
|
export type ClassificationDecision = {
|
|
119
135
|
decision: "allow" | "block";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@czottmann/pi-automode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Claude Code-style auto mode guardrail for pi.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/czottmann/pi-automode"
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"@earendil-works/pi-tui": "*"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@earendil-works/pi-ai": "^0.
|
|
45
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
46
|
-
"@earendil-works/pi-tui": "^0.
|
|
44
|
+
"@earendil-works/pi-ai": "^0.84.1",
|
|
45
|
+
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
46
|
+
"@earendil-works/pi-tui": "^0.84.1",
|
|
47
47
|
"@types/node": "^24.0.0",
|
|
48
48
|
"tsx": "^4.22.4",
|
|
49
49
|
"typescript": "^5.8.0"
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Audit the whole repo for over-engineering, what can be deleted
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Audit the entire repository for over-engineering only, not correctness. Scan the whole tree, not a diff. One line per finding, ranked biggest cut first: <tag> <what to cut>. <replacement>. [path]. Tags: delete (dead code/speculative feature), stdlib (reinvented standard library), native (dependency doing what the platform does), yagni (abstraction with one implementation), shrink (same logic, fewer lines). End with the net lines and dependencies removable. If nothing to cut: 'Lean already. Ship.'
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Harvest ponytail: comments into a tracked debt ledger"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Harvest every `ponytail:` comment in this repository into a debt ledger so deferrals do not rot into 'later means never'. Grep the whole tree for comment markers (grep -rnE '(#|//) ?ponytail:' ., skipping node_modules/.git/build output). One row per marker, grouped by file: <file>:<line>, <what was simplified>. ceiling: <the limit named in the comment>. upgrade: <the trigger to revisit>. Tag any marker that names no upgrade path or trigger as no-trigger, those rot silently. End with the count of markers and how many lack a trigger. If none: 'No ponytail: debt. Clean ledger.' Report only, change nothing.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show ponytail's measured impact scoreboard (less code, cost, time)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Show the ponytail gain scoreboard. One shot, change nothing: do not switch mode, write flag files, or persist anything. Render the published benchmark medians (5 everyday tasks; models Haiku, Sonnet, Opus; source benchmarks/ and the README) as plain ASCII bars: Lines of code, no-skill 100% vs ponytail 6-20% (down 80-94%); Cost, no-skill 100% vs ponytail 23-53% (down 47-77%); Speed, ponytail 3-6x faster. The bar length shows the measured range, the label carries the exact figure. These are benchmark medians, not this repo. NEVER print a per-repo savings number: the unbuilt version was never written, so there is no real baseline to subtract from in a live repo. For real per-repo figures, point to /ponytail-debt (the counted shortcut ledger) and /ponytail-audit (what is still cuttable). Report only.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Quick reference for ponytail levels, skills, and commands
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Show the ponytail quick reference. One shot, change nothing: do not switch mode, write flag files, or persist anything. Levels: /ponytail lite (build what's asked, name the lazier alternative in one line), /ponytail (full, the default ladder: YAGNI then stdlib then native then one line then minimum), /ponytail ultra (deletion before addition, challenges the requirement before building). Commands: /ponytail-review (over-engineering review of the current changes), /ponytail-audit (whole-repo over-engineering audit), /ponytail-debt (harvest ponytail: comments into a tracked ledger), /ponytail-gain (measured-impact scoreboard from the benchmark), /ponytail-help (this card). Deactivate with 'stop ponytail', 'normal mode', or /ponytail off; resume anytime with /ponytail. Default mode is full; change it with the PONYTAIL_DEFAULT_MODE environment variable (off|lite|full|ultra) or a config file at ~/.config/ponytail/config.json (Windows: %APPDATA%\ponytail\config.json) with {"defaultMode": "lite"}. Resolution order: env var, then config file, then full.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Review changes for over-engineering, what can be deleted
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Review the current code changes for over-engineering only, not correctness. One line per finding: L<line>: <tag> <what to cut>. <replacement>. Tags: delete (dead code/speculative feature), stdlib (reinvented standard library), native (dependency doing what the platform does), yagni (abstraction with one implementation), shrink (same logic, fewer lines). End with the net lines removable. If nothing to cut: 'Lean already. Ship.'
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Switch ponytail intensity level (lite/full/ultra/off)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Switch to ponytail $ARGUMENTS mode. If no level specified, use full. Lazy senior dev mode, before any code: does it need to exist at all (YAGNI)? Does the standard library do it? A native platform feature? Can it be one line? Build the minimum that works. No unrequested abstractions, no avoidable dependencies, no boilerplate. Mark deliberate simplifications that cut a real corner with a known ceiling using a ponytail: comment that names the ceiling and upgrade path.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ponytail command-file frontmatter parser.
|
|
4
|
+
//
|
|
5
|
+
// Pulled out of ponytail.mjs so the plugin module's only top-level export is
|
|
6
|
+
// the plugin function itself. OpenCode's legacy plugin loader (the one that
|
|
7
|
+
// runs before v1 plugins are detected) treats every function exported from a
|
|
8
|
+
// plugin module as a plugin; calling the frontmatter parser as one threw
|
|
9
|
+
// "path must be a string or a file descriptor" because it got the plugin
|
|
10
|
+
// context object as its first argument. Keeping the parser in its own module
|
|
11
|
+
// leaves exactly one plugin-shaped export on ponytail.mjs.
|
|
12
|
+
|
|
13
|
+
function parseCommandFile(filePath) {
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
16
|
+
// Tolerate CRLF: a Windows checkout (autocrlf) delivers \r\n, npm ships \n.
|
|
17
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
const description = match[1].match(/description:\s*(.+)/)?.[1]?.trim();
|
|
20
|
+
return { description, template: match[2].trim() };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { parseCommandFile };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// ponytail — OpenCode plugin.
|
|
2
|
+
//
|
|
3
|
+
// Injects the ponytail ruleset into every chat's system prompt at the active
|
|
4
|
+
// intensity, persists /ponytail mode switches, and registers slash commands so
|
|
5
|
+
// they work when the package is installed from npm. Reuses the shared
|
|
6
|
+
// instruction builder so Claude Code, Codex, pi, and OpenCode all read one
|
|
7
|
+
// source of truth.
|
|
8
|
+
//
|
|
9
|
+
// OpenCode loads this as a server plugin — add it to your opencode.json:
|
|
10
|
+
// { "plugin": ["@dietrichgebert/ponytail"] }
|
|
11
|
+
|
|
12
|
+
import { createRequire } from 'module';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
|
|
20
|
+
// The shared instruction builder is CommonJS; bridge to it from this ES module.
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const { getPonytailInstructions } = require('../../hooks/ponytail-instructions');
|
|
23
|
+
const { getDefaultMode, normalizePersistedMode } = require('../../hooks/ponytail-config');
|
|
24
|
+
const { parseCommandFile } = require('./ponytail-frontmatter.cjs');
|
|
25
|
+
|
|
26
|
+
// OpenCode has no flag-file convention of its own; keep mode beside its config.
|
|
27
|
+
const statePath = path.join(
|
|
28
|
+
process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'),
|
|
29
|
+
'opencode',
|
|
30
|
+
'.ponytail-active',
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
function readMode() {
|
|
34
|
+
try {
|
|
35
|
+
return normalizePersistedMode(fs.readFileSync(statePath, 'utf8').trim()) || getDefaultMode();
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return getDefaultMode();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeMode(mode) {
|
|
42
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
43
|
+
fs.writeFileSync(statePath, mode);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default async ({ client } = {}) => {
|
|
47
|
+
const log = (level, message) => {
|
|
48
|
+
try { client && client.app && client.app.log({ body: { service: 'ponytail', level, message } }); } catch (e) {}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const ponytailSkillsDir = path.resolve(__dirname, '../../skills');
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
// Register slash commands + skills directory.
|
|
55
|
+
config: async (config) => {
|
|
56
|
+
if (!config.command) config.command = {};
|
|
57
|
+
const commandDir = path.join(__dirname, '..', 'command');
|
|
58
|
+
try {
|
|
59
|
+
for (const file of fs.readdirSync(commandDir).filter((f) => f.endsWith('.md'))) {
|
|
60
|
+
const name = path.basename(file, '.md');
|
|
61
|
+
const parsed = parseCommandFile(path.join(commandDir, file));
|
|
62
|
+
if (parsed) config.command[name] = parsed;
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
|
|
66
|
+
config.skills = config.skills || {};
|
|
67
|
+
config.skills.paths = config.skills.paths || [];
|
|
68
|
+
if (!config.skills.paths.includes(ponytailSkillsDir)) {
|
|
69
|
+
config.skills.paths.push(ponytailSkillsDir);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Append the ruleset to the system prompt every turn.
|
|
74
|
+
'experimental.chat.system.transform': async (_input, output) => {
|
|
75
|
+
const mode = readMode();
|
|
76
|
+
if (mode === 'off') return;
|
|
77
|
+
const instructions = getPonytailInstructions(mode);
|
|
78
|
+
if (output.system.length > 0) {
|
|
79
|
+
output.system[output.system.length - 1] += '\n\n' + instructions;
|
|
80
|
+
} else {
|
|
81
|
+
output.system.push(instructions);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Persist `/ponytail <level>` so the next turn's injection follows it.
|
|
86
|
+
// ponytail: mode applies from the next message, not the current one — the
|
|
87
|
+
// transform reads the flag the command writes. Good enough; switch to a
|
|
88
|
+
// synchronous store if same-turn switching ever matters.
|
|
89
|
+
'command.execute.before': async (input) => {
|
|
90
|
+
if (!input || input.command !== 'ponytail') return;
|
|
91
|
+
// `off` is persisted like any mode; the transform reads it and stays silent.
|
|
92
|
+
const args = String(input.arguments || '').trim();
|
|
93
|
+
const mode = args ? normalizePersistedMode(args) : getDefaultMode();
|
|
94
|
+
if (!mode) return;
|
|
95
|
+
writeMode(mode);
|
|
96
|
+
log('info', 'ponytail ' + mode);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Ponytail, lazy senior dev mode
|
|
2
|
+
|
|
3
|
+
You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
|
|
4
|
+
|
|
5
|
+
Before writing any code, stop at the first rung that holds:
|
|
6
|
+
|
|
7
|
+
1. Does this need to be built at all? (YAGNI)
|
|
8
|
+
2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
|
|
9
|
+
3. Does the standard library already do this? Use it.
|
|
10
|
+
4. Does a native platform feature cover it? Use it.
|
|
11
|
+
5. Does an already-installed dependency solve it? Use it.
|
|
12
|
+
6. Can this be one line? Make it one line.
|
|
13
|
+
7. Only then: write the minimum code that works.
|
|
14
|
+
|
|
15
|
+
The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
|
|
16
|
+
|
|
17
|
+
Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once — one guard there is a smaller diff than one per caller, and patching only the path the ticket names leaves a sibling caller still broken.
|
|
18
|
+
|
|
19
|
+
Rules:
|
|
20
|
+
|
|
21
|
+
- No abstractions that weren't explicitly requested.
|
|
22
|
+
- No new dependency if it can be avoided.
|
|
23
|
+
- No boilerplate nobody asked for.
|
|
24
|
+
- Deletion over addition. Boring over clever. Fewest files possible.
|
|
25
|
+
- Shortest working diff wins, but only once you understand the problem. The smallest change in the wrong place isn't lazy, it's a second bug.
|
|
26
|
+
- Question complex requests: "Do you actually need X, or does Y cover it?"
|
|
27
|
+
- Pick the edge-case-correct option when two stdlib approaches are the same size, lazy means less code, not the flimsier algorithm.
|
|
28
|
+
- Mark deliberate simplifications that cut a real corner with a known ceiling (global lock, O(n²) scan, naive heuristic) with a `ponytail:` comment naming the ceiling and upgrade path.
|
|
29
|
+
|
|
30
|
+
Not lazy about: understanding the problem (read it fully and trace the real flow before picking a rung, a small diff you don't understand is just laziness dressed up as efficiency), input validation at trust boundaries, error handling that prevents data loss, security, accessibility, the calibration real hardware needs (the platform is never the spec ideal, a clock drifts, a sensor reads off), anything explicitly requested. Lazy code without its check is unfinished: non-trivial logic leaves ONE runnable check behind, the smallest thing that fails if the logic breaks (an assert-based demo/self-check or one small test file; no frameworks, no fixtures). Trivial one-liners need no test.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ponytail",
|
|
3
|
+
"version": "4.9.0",
|
|
4
|
+
"description": "Lazy senior dev mode. Forces the simplest, shortest solution that actually works: YAGNI, stdlib first, no unrequested abstractions.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Dietrich Gebert",
|
|
7
|
+
"url": "https://github.com/DietrichGebert"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/DietrichGebert/ponytail",
|
|
10
|
+
"repository": "https://github.com/DietrichGebert/ponytail",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["yagni", "minimalism", "code-review", "productivity"],
|
|
13
|
+
"skills": "./skills/",
|
|
14
|
+
"rules": "./.qoder/rules/",
|
|
15
|
+
"hooks": "./hooks/qoder-hooks.json"
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Ponytail, lazy senior dev mode
|
|
2
|
+
|
|
3
|
+
You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
|
|
4
|
+
|
|
5
|
+
Before writing any code, stop at the first rung that holds:
|
|
6
|
+
|
|
7
|
+
1. Does this need to be built at all? (YAGNI)
|
|
8
|
+
2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
|
|
9
|
+
3. Does the standard library already do this? Use it.
|
|
10
|
+
4. Does a native platform feature cover it? Use it.
|
|
11
|
+
5. Does an already-installed dependency solve it? Use it.
|
|
12
|
+
6. Can this be one line? Make it one line.
|
|
13
|
+
7. Only then: write the minimum code that works.
|
|
14
|
+
|
|
15
|
+
The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
|
|
16
|
+
|
|
17
|
+
Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once — one guard there is a smaller diff than one per caller, and patching only the path the ticket names leaves a sibling caller still broken.
|
|
18
|
+
|
|
19
|
+
Rules:
|
|
20
|
+
|
|
21
|
+
- No abstractions that weren't explicitly requested.
|
|
22
|
+
- No new dependency if it can be avoided.
|
|
23
|
+
- No boilerplate nobody asked for.
|
|
24
|
+
- Deletion over addition. Boring over clever. Fewest files possible.
|
|
25
|
+
- Shortest working diff wins, but only once you understand the problem. The smallest change in the wrong place isn't lazy, it's a second bug.
|
|
26
|
+
- Question complex requests: "Do you actually need X, or does Y cover it?"
|
|
27
|
+
- Pick the edge-case-correct option when two stdlib approaches are the same size, lazy means less code, not the flimsier algorithm.
|
|
28
|
+
- Mark deliberate simplifications that cut a real corner with a known ceiling (global lock, O(n²) scan, naive heuristic) with a `ponytail:` comment naming the ceiling and upgrade path.
|
|
29
|
+
|
|
30
|
+
Not lazy about: understanding the problem (read it fully and trace the real flow before picking a rung, a small diff you don't understand is just laziness dressed up as efficiency), input validation at trust boundaries, error handling that prevents data loss, security, accessibility, the calibration real hardware needs (the platform is never the spec ideal, a clock drifts, a sensor reads off), anything explicitly requested. Lazy code without its check is unfinished: non-trivial logic leaves ONE runnable check behind, the smallest thing that fails if the logic breaks (an assert-based demo/self-check or one small test file; no frameworks, no fixtures). Trivial one-liners need no test.
|
|
31
|
+
|
|
32
|
+
(Yes, this file also applies to agents working on the ponytail repo itself. Especially to them.)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DietrichGebert
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|