@normful/picadillo 2.0.3 → 2.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.
package/CHANGELOG.md CHANGED
@@ -1,8 +1,26 @@
1
+ ## [2.1.0] - 2026-02-17
2
+
3
+ ### 🚀 Features
4
+
5
+ - *(extension)* Add mulch extension for project learning management
6
+ - *(extension)* Add gastown extension for gt integration
7
+
8
+ ### 🚜 Refactor
9
+
10
+ - *(test)* Replace `vi.fn()` with `mock` from `bun:test` in parrot tests
11
+
12
+ ### 📚 Documentation
13
+
14
+ - Update README with reorganized skills and extensions sections
1
15
  ## [2.0.3] - 2026-02-16
2
16
 
3
17
  ### 📚 Documentation
4
18
 
5
19
  - *(README)* Add parrot extension demo screenshot to README
20
+
21
+ ### ⚙️ Miscellaneous Tasks
22
+
23
+ - Release 2.0.3
6
24
  ## [2.0.2] - 2026-02-16
7
25
 
8
26
  ### 🚀 Features
package/README.md CHANGED
@@ -27,7 +27,8 @@ repeatedly poke reality until it tastes better.”
27
27
  pi install https://github.com/normful/picadillo
28
28
  ```
29
29
 
30
- Configure what you don't want with `pi config`. It will modify `~/.pi/agent/settings.json`
30
+ Configure what you don't want with `pi config`. It will modify
31
+ `~/.pi/agent/settings.json`
31
32
 
32
33
  ## Dependencies
33
34
 
@@ -35,19 +36,38 @@ Configure what you don't want with `pi config`. It will modify `~/.pi/agent/sett
35
36
 
36
37
  ## Skills
37
38
 
38
- | Skill | Description |
39
- |-------|-------------|
40
- | [run-in-tmux](skills/run-in-tmux/) | Run commands in a new tmux session with split panes. Useful for dev environments, parallel processes, and persistent background tasks. |
39
+ ### run-in-tmux
41
40
 
41
+ Run commands in a new tmux session with split panes. Useful for dev
42
+ environments, parallel processes, and persistent background tasks.
43
+
44
+ ### mulch
45
+
46
+ Usage examples showing how to use [mulch](https://github.com/jayminwest/mulch)
47
+ to record and retrieve structured project
48
+ learnings.
42
49
 
43
50
  ## Extensions
44
51
 
45
- | Extension | Description |
46
- |-----------|-------------|
47
- | [parrot](extensions/parrot.ts) | Opens the last AI response in an external text editor (respects `$VISUAL` or `$EDITOR`), then sends your edited content back to the chat. Useful for editing AI responses before re-sending, copying output to a full-featured editor, or iterating with custom edits. Usage: `/parrot` or `Alt+R` |
52
+ ### parrot
53
+
54
+ Opens the last AI response in an external text editor (respects `$VISUAL` or
55
+ `$EDITOR`), then sends your edited content back to the chat. Useful for editing
56
+ AI responses before re-sending, copying output to a full-featured editor, or
57
+ iterating with custom edits.
58
+
59
+ Usage: `/parrot` or `Alt+R`
60
+
61
+ ### gastown
62
+
63
+ Hooks to automatically run `gt prime` and `gt mail` from [gastown](https://github.com/steveyegge/gastown)
64
+
65
+ ### mulch
48
66
 
67
+ Hooks to automatically run [mulch](https://github.com/jayminwest/mulch) for
68
+ recording and retrieving structured project learnings.
49
69
 
50
- ### Parrot Extension Demo
70
+ ## Parrot Extension Demo
51
71
 
52
72
  [![asciicast](https://asciinema.org/a/788693.svg)](https://asciinema.org/a/788693)
53
73
 
@@ -0,0 +1,160 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExecResult } from "@mariozechner/pi-coding-agent";
3
+
4
+ export type { ExecResult };
5
+ export const GASTOWN_MESSAGE_TYPE = "gastown";
6
+ export const AUTONOMOUS_ROLES = new Set(["polecat", "witness", "refinery", "deacon"]);
7
+
8
+ export interface GastownConfig {
9
+ role?: string;
10
+ }
11
+
12
+ /**
13
+ * Run `gt prime --hook` to get prime text.
14
+ */
15
+ export async function gastownPrime(execFn: ExtensionAPI["exec"]): Promise<string> {
16
+ let primeText = "";
17
+ try {
18
+ const { stdout } = await execFn("gt", ["prime", "--hook"]);
19
+ primeText = stdout;
20
+ } catch (e) {
21
+ console.error("[gastown] gt prime failed:", e);
22
+ }
23
+ return primeText;
24
+ }
25
+
26
+ /**
27
+ * Run `gt mail check --inject` to get mail text.
28
+ */
29
+ export async function gastownMailCheck(execFn: ExtensionAPI["exec"]): Promise<string> {
30
+ let mailText = "";
31
+ try {
32
+ const { stdout } = await execFn("gt", ["mail", "check", "--inject"]);
33
+ mailText = stdout;
34
+ } catch (e) {
35
+ console.error("[gastown] gt mail check --inject failed:", e);
36
+ }
37
+ return mailText;
38
+ }
39
+
40
+ /**
41
+ * Record session costs using `gt costs record --session <sessionId>`.
42
+ */
43
+ export async function recordSessionCosts(
44
+ execFn: ExtensionAPI["exec"],
45
+ sessionId: string,
46
+ ): Promise<void> {
47
+ try {
48
+ await execFn("gt", ["costs", "record", "--session", sessionId]);
49
+ } catch (e) {
50
+ console.error("[gastown] gt costs record failed:", e);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Check if the given role is an autonomous role.
56
+ */
57
+ export function isAutonomousRole(role: string): boolean {
58
+ return AUTONOMOUS_ROLES.has(role.toLowerCase());
59
+ }
60
+
61
+ /**
62
+ * Handle session_start event.
63
+ * Fetches prime text and mail, sends combined message.
64
+ */
65
+ export async function handleSessionStart(
66
+ execFn: ExtensionAPI["exec"],
67
+ sendMessage: ExtensionAPI["sendMessage"],
68
+ ): Promise<void> {
69
+ const primeText = await gastownPrime(execFn);
70
+ if (!primeText) return;
71
+
72
+ const mailText = await gastownMailCheck(execFn);
73
+ sendMessage(
74
+ {
75
+ customType: GASTOWN_MESSAGE_TYPE,
76
+ content: primeText + "\n\n" + mailText,
77
+ display: true,
78
+ },
79
+ {
80
+ deliverAs: "steer",
81
+ triggerTurn: true,
82
+ },
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Handle before_agent_start event.
88
+ * Sends mail text only for autonomous roles.
89
+ */
90
+ export function handleBeforeAgentStart(
91
+ role: string,
92
+ mailText: string,
93
+ ): { message: { customType: string; content: string; display: boolean } } | undefined {
94
+ if (!mailText || !isAutonomousRole(role)) return undefined;
95
+
96
+ return {
97
+ message: {
98
+ customType: GASTOWN_MESSAGE_TYPE,
99
+ content: mailText,
100
+ display: true,
101
+ },
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Handle session_compact event.
107
+ * Fetches prime text and sends as follow-up.
108
+ */
109
+ export async function handleSessionCompact(
110
+ execFn: ExtensionAPI["exec"],
111
+ sendMessage: ExtensionAPI["sendMessage"],
112
+ ): Promise<void> {
113
+ const primeText = await gastownPrime(execFn);
114
+ if (!primeText) return;
115
+
116
+ sendMessage(
117
+ {
118
+ customType: GASTOWN_MESSAGE_TYPE,
119
+ content: primeText,
120
+ display: true,
121
+ },
122
+ {
123
+ deliverAs: "followUp",
124
+ triggerTurn: true,
125
+ },
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Handle session_shutdown event.
131
+ * Records session costs.
132
+ */
133
+ export async function handleSessionShutdown(
134
+ execFn: ExtensionAPI["exec"],
135
+ sessionId: string,
136
+ ): Promise<void> {
137
+ await recordSessionCosts(execFn, sessionId);
138
+ }
139
+
140
+ export default function (pi: ExtensionAPI) {
141
+ const role = (process.env.GT_ROLE || "").toLowerCase();
142
+
143
+ pi.on("session_start", async (_event, _ctx) => {
144
+ await handleSessionStart(pi.exec, pi.sendMessage);
145
+ });
146
+
147
+ pi.on("before_agent_start", async (_event, _ctx) => {
148
+ const mailText = await gastownMailCheck(pi.exec);
149
+ return handleBeforeAgentStart(role, mailText);
150
+ });
151
+
152
+ pi.on("session_compact", async (_event, _ctx) => {
153
+ await handleSessionCompact(pi.exec, pi.sendMessage);
154
+ });
155
+
156
+ pi.on("session_shutdown", async (_event, ctx) => {
157
+ const sessionId = ctx.sessionManager.getSessionId();
158
+ await handleSessionShutdown(pi.exec, sessionId);
159
+ });
160
+ }
@@ -0,0 +1,75 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExecResult } from "@mariozechner/pi-coding-agent";
3
+
4
+ export type { ExecResult };
5
+ export const MULCH_MESSAGE_TYPE = "mulch";
6
+
7
+ const APPEND_TO_MULCH_PRIME = `
8
+ ---
9
+
10
+ But you should NOT run any above mulch commands right now.
11
+ I am just letting you know the session close protocol, so that you remember to do it later.
12
+ `;
13
+
14
+ export async function mulchPrime(
15
+ execFn: ExtensionAPI["exec"],
16
+ ): Promise<string> {
17
+ let primeText = "";
18
+ try {
19
+ const { stdout } = await execFn("mulch", ["prime"]);
20
+ primeText = stdout;
21
+ } catch (e) {
22
+ console.error("mulch prime failed:", e);
23
+ }
24
+ return primeText;
25
+ }
26
+
27
+ export async function handleSessionStart(
28
+ execFn: ExtensionAPI["exec"],
29
+ sendMessage: ExtensionAPI["sendMessage"],
30
+ ): Promise<void> {
31
+ const primeText = await mulchPrime(execFn);
32
+ if (!primeText) return;
33
+
34
+ sendMessage(
35
+ {
36
+ customType: MULCH_MESSAGE_TYPE,
37
+ content: primeText + APPEND_TO_MULCH_PRIME,
38
+ display: true,
39
+ },
40
+ {
41
+ deliverAs: "steer",
42
+ triggerTurn: true,
43
+ },
44
+ );
45
+ }
46
+
47
+ export async function handleSessionCompact(
48
+ execFn: ExtensionAPI["exec"],
49
+ sendMessage: ExtensionAPI["sendMessage"],
50
+ ): Promise<void> {
51
+ const primeText = await mulchPrime(execFn);
52
+ if (!primeText) return;
53
+
54
+ sendMessage(
55
+ {
56
+ customType: MULCH_MESSAGE_TYPE,
57
+ content: primeText + APPEND_TO_MULCH_PRIME,
58
+ display: true,
59
+ },
60
+ {
61
+ deliverAs: "followUp",
62
+ triggerTurn: true,
63
+ },
64
+ );
65
+ }
66
+
67
+ export default function (pi: ExtensionAPI) {
68
+ pi.on("session_start", async (_event, _ctx) => {
69
+ await handleSessionStart(pi.exec, pi.sendMessage);
70
+ });
71
+
72
+ pi.on("session_compact", async (_event, _ctx) => {
73
+ await handleSessionCompact(pi.exec, pi.sendMessage);
74
+ });
75
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@normful/picadillo",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "description": "pi agent skills/extensions: run-in-tmux, parrot (send AI message with edited version of AI message)",
6
6
  "keywords": [
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: mulch
3
+ description: Record and retrieve structured project learnings using `mulch` CLI. Use when working on code and wanting to capture patterns, failures, decisions, conventions, references, or guides for future sessions.
4
+ ---
5
+
6
+ # Mulch Usage
7
+
8
+ Mulch captures project learnings as structured "records" organized by **domain**. Each domain represents a category of knowledge:
9
+
10
+ | Domain | Description |
11
+ |--------|-------------|
12
+ | `purpose` | Why the project exists |
13
+ | `identity` | Brand, naming, tone |
14
+ | `public-interface` | APIs, CLI, UI surfaces |
15
+ | `internal-structure` | Code organization |
16
+ | `dependencies` | External libraries |
17
+ | `data` | Database, schemas, models |
18
+ | `configuration` | Env vars, settings |
19
+ | `observability` | Logging, metrics, tracing |
20
+ | `glossary` | Domain-specific terminology |
21
+
22
+ ---
23
+
24
+ # When to Run Commands
25
+
26
+ ## 1. At Session Start
27
+
28
+ Load all project expertise into your context:
29
+
30
+ ```bash
31
+ mulch prime # load all project learnings
32
+ mulch prime --files src/foo.ts # load only records relevant to specific files
33
+ ```
34
+
35
+ This gives you awareness of existing patterns, decisions, and conventions before you start working.
36
+
37
+ ## 2. While Working
38
+
39
+ Record insights as you discover them:
40
+
41
+ ```bash
42
+ # Create a new domain if needed
43
+ mulch add purpose
44
+
45
+ # Record different types of learnings
46
+ mulch record <domain> --type pattern --name "Auth Pattern" --description "Use middleware for auth checks before route handlers"
47
+ mulch record <domain> --type failure --name "Missing Validation" --description "Form field not validated on client" --resolution "Add Zod schema validation"
48
+ mulch record <domain> --type decision --title "Chose PostgreSQL" --rationale "Needed relational queries and ACID compliance"
49
+ mulch record <domain> --type convention --content "API routes go in src/routes/"
50
+ mulch record <domain> --type reference --name "Error Handling" --description "See docs/error patterns"
51
+ mulch record <domain> --type guide --name "Setup Guide" --description "Run npm install then npm run dev"
52
+
53
+ # Add evidence, files, tags
54
+ mulch record <domain> --type pattern --name "..." --description "..." --evidence-commit abc123
55
+ mulch record <domain> --type pattern --name "..." --description "..." --evidence-bead fix-42 # link to bd issue
56
+ mulch record <domain> --type pattern --name "..." --description "..." --files "src/auth.ts,src/middleware.ts"
57
+ mulch record <domain> --type pattern --name "..." --description "..." --tags "security,auth"
58
+
59
+ # Link related records
60
+ mulch record <domain> --type pattern --name "..." --description "..." --relates-to mx-abc123
61
+ mulch record <domain> --type pattern --name "..." --description "..." --supersedes mx-old123
62
+
63
+ # Batch record from JSON file
64
+ mulch record <domain> --batch records.json
65
+ mulch record <domain> --batch records.json --dry-run # preview first
66
+ ```
67
+
68
+ ## 3. Before Finishing Your Task
69
+
70
+ After all file editing is done (but before committing), run `mulch learn` to see what changed and get recording suggestions:
71
+
72
+ ```bash
73
+ mulch learn
74
+ mulch learn --since HEAD~3 # diff against specific commit
75
+ ```
76
+
77
+ This shows which files were modified and can suggest what to record based on your changes.
78
+
79
+ Then record any learnings and sync to git:
80
+
81
+ ```bash
82
+ mulch record <domain> ... # record learnings from this session
83
+ mulch sync # validates, stages, and commits .mulch/ changes
84
+ ```
85
+
86
+ **Note:** `mulch sync` runs `mulch validate` automatically before committing, so you don't need a separate validate step.
87
+
88
+ ---
89
+
90
+ # Querying & Searching
91
+
92
+ **Tip:** `--json` is a global flag that works with all commands below.
93
+
94
+ ```bash
95
+ # Check domain status
96
+ mulch status --json
97
+ mulch status --json | jq '.'
98
+
99
+ # Query records in a domain
100
+ mulch query <domain> --json
101
+ mulch query <domain> --type failure --json # filter by type
102
+
103
+ # Search across all domains
104
+ mulch search "auth" --json
105
+ mulch search "auth" --type pattern --json # filter by type
106
+ mulch search "auth" --tag security --json # filter by tag
107
+ mulch search "auth" --domain <domain> --json # limit to domain
108
+
109
+ # Show recent additions
110
+ mulch ready --json
111
+
112
+ # Show changes since git ref
113
+ mulch diff --json
114
+ mulch diff --since v1.0.0 --json
115
+ ```
116
+
117
+ ## Other Useful Commands
118
+
119
+ ```bash
120
+ # Edit a record
121
+ mulch edit <domain> mx-abc123 --description "Updated description"
122
+
123
+ # Delete a record
124
+ mulch delete <domain> mx-abc123
125
+
126
+ # Compact similar records
127
+ mulch compact <domain> --analyze
128
+ mulch compact <domain> --apply --type pattern --name "Auth Patterns" --description "Combined from 5 records"
129
+
130
+ # Generate onboarding snippet for AGENTS.md
131
+ mulch onboard
132
+ mulch onboard --check # verify it's installed
133
+
134
+ # Health check
135
+ mulch doctor # check for issues with --fix option
136
+
137
+ # Check for updates
138
+ mulch update
139
+ ```
140
+
141
+ ## JSON Output
142
+
143
+ All commands support `--json` for scripting:
144
+ ```bash
145
+ mulch status --json | jq '.'
146
+ mulch query <domain> --json | jq '.[] | select(.type == "failure")'
147
+ mulch search "auth" --json | jq '.[] | .name'
148
+ ```