@pi-unipi/unipi 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,6 +24,7 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "typecheck": "npx tsc --noEmit --skipLibCheck",
27
+ "test": "npm test --workspaces",
27
28
  "publish:all": "npm publish --workspaces --access public"
28
29
  },
29
30
  "files": [
@@ -39,7 +40,8 @@
39
40
  "node_modules/@pi-unipi/memory/index.ts",
40
41
  "node_modules/@pi-unipi/info-screen/index.ts",
41
42
  "node_modules/@pi-unipi/subagents/src/index.ts",
42
- "node_modules/@pi-unipi/btw/extensions/btw.ts"
43
+ "node_modules/@pi-unipi/btw/extensions/btw.ts",
44
+ "node_modules/@pi-unipi/web-api/src/index.ts"
43
45
  ],
44
46
  "skills": [
45
47
  "node_modules/@pi-unipi/workflow/skills",
@@ -61,7 +63,8 @@
61
63
  "@pi-unipi/memory": "*",
62
64
  "@pi-unipi/info-screen": "*",
63
65
  "@pi-unipi/subagents": "*",
64
- "@pi-unipi/btw": "*"
66
+ "@pi-unipi/btw": "*",
67
+ "@pi-unipi/web-api": "*"
65
68
  },
66
69
  "devDependencies": {
67
70
  "@types/node": "^25.6.0",
@@ -26,8 +26,9 @@ import {
26
26
  searchAllProjects,
27
27
  listAllProjects,
28
28
  } from "./storage.js";
29
- import { registerMemoryTools, MEMORY_TOOLS } from "./tools.js";
29
+ import { registerMemoryTools, MEMORY_TOOLS, GLOBAL_SEARCH_ALIAS } from "./tools.js";
30
30
  import { registerMemoryCommands } from "./commands.js";
31
+ import { isEmbeddingReady, hasModelChanged } from "./settings.js";
31
32
 
32
33
  /** Package version */
33
34
  const VERSION = getPackageVersion(new URL(".", import.meta.url).pathname);
@@ -47,6 +48,10 @@ function getStorage(): MemoryStorage {
47
48
  }
48
49
 
49
50
  export default function (pi: ExtensionAPI) {
51
+ // Lifecycle state — tracks whether recall/store have happened this session
52
+ let recallDone = false;
53
+ let storeDone = false;
54
+
50
55
  // Register skills directory
51
56
  const skillsDir = new URL("./skills", import.meta.url).pathname;
52
57
  pi.on("resources_discover", async (_event, _ctx) => {
@@ -56,11 +61,15 @@ export default function (pi: ExtensionAPI) {
56
61
  });
57
62
 
58
63
  // Register tools and commands
59
- registerMemoryTools(pi, getStorage);
64
+ registerMemoryTools(pi, getStorage, () => { recallDone = true; storeDone = true; });
60
65
  registerMemoryCommands(pi, getStorage);
61
66
 
62
67
  // Session lifecycle
63
68
  pi.on("session_start", async (_event, ctx) => {
69
+ // Reset lifecycle flags
70
+ recallDone = false;
71
+ storeDone = false;
72
+
64
73
  // Initialize project storage
65
74
  const projectName = getProjectName(ctx.cwd);
66
75
  projectStorage = new MemoryStorage(projectName);
@@ -78,13 +87,14 @@ export default function (pi: ExtensionAPI) {
78
87
  "unipi:memory-forget",
79
88
  "unipi:global-memory-search",
80
89
  "unipi:global-memory-list",
90
+ "unipi:memory-settings",
81
91
  ],
82
92
  tools: [
83
93
  MEMORY_TOOLS.STORE,
84
94
  MEMORY_TOOLS.SEARCH,
85
95
  MEMORY_TOOLS.DELETE,
86
96
  MEMORY_TOOLS.LIST,
87
- MEMORY_TOOLS.GLOBAL_SEARCH,
97
+ GLOBAL_SEARCH_ALIAS,
88
98
  MEMORY_TOOLS.GLOBAL_LIST,
89
99
  ],
90
100
  });
@@ -146,59 +156,76 @@ export default function (pi: ExtensionAPI) {
146
156
  const projectCount = projectStorage.listAll().length;
147
157
  const allMemories = listAllProjects();
148
158
  const projectCountAll = allMemories.length;
159
+ const vecReady = isEmbeddingReady();
160
+ const vecIcon = vecReady ? "⚡" : "📝";
149
161
  ctx.ui.setStatus(
150
162
  "unipi-memory",
151
- `🧠 memory ${projectCount}p/${projectCountAll}all`
163
+ `${vecIcon} memory ${projectCount}p/${projectCountAll}all${hasModelChanged() ? " ⚠" : ""}`
152
164
  );
153
165
  }
154
166
  });
155
167
 
156
- // Inject memory titles at session start
168
+ // Inject memory recall reminder at agent start (hidden message, not system prompt)
157
169
  pi.on("before_agent_start", async (event, ctx) => {
170
+ if (recallDone) return;
158
171
  if (!projectStorage) return;
159
172
 
160
173
  const projectName = getProjectName(ctx.cwd);
161
174
  const projectMemories = projectStorage.listAll();
162
175
 
163
176
  if (projectMemories.length === 0) {
164
- return; // No memories to inject
165
- }
166
-
167
- let injection = "\n\n<memory>\n";
168
- injection += `Available memories for project "${projectName}":\n\n`;
169
-
170
- // Project memories
171
- for (const m of projectMemories) {
172
- injection += `- ${m.title}\n`;
177
+ recallDone = true; // Nothing to recall, skip
178
+ return;
173
179
  }
174
180
 
175
- injection += "\nUse memory_search to retrieve full content. Use memory_store to save new memories.\n";
176
- injection += "Use global_memory_search to search across ALL projects.\n";
177
- injection += "</memory>";
181
+ const titleList = projectMemories.slice(0, 20).map(m => `- ${m.title}`).join("\n");
182
+ const extra = projectMemories.length > 20 ? `\n... and ${projectMemories.length - 20} more` : "";
178
183
 
179
184
  return {
180
- systemPrompt: event.systemPrompt + injection,
185
+ message: {
186
+ customType: "unipi-memory-recall-reminder",
187
+ content: [
188
+ "## 🧠 Memory System Active",
189
+ "",
190
+ `You have ${projectMemories.length} memories stored for project "${projectName}".`,
191
+ "**BEFORE starting work**, call `memory_search` with relevant keywords to check for existing context.",
192
+ "",
193
+ "Available memories:",
194
+ titleList + extra,
195
+ "",
196
+ "**AFTER completing the task**, if you learned something non-obvious,",
197
+ "call `memory_store` to save it for future sessions.",
198
+ "",
199
+ "Guardrails: read max 10 memory results per search. Update existing memories instead of creating duplicates.",
200
+ ].join("\n"),
201
+ display: false,
202
+ },
181
203
  };
182
204
  });
183
205
 
184
- // Auto-consolidation on compaction
185
- pi.on("session_before_compact", async (event, ctx) => {
186
- const { preparation } = event;
187
-
188
- // Extract summary text
189
- const summary = preparation.previousSummary || "";
190
-
191
- if (!summary || summary.length < 100) {
192
- // Summary too short to extract memories from
193
- return;
194
- }
195
-
196
- // For now, just log that consolidation would happen
197
- // Future: Use LLM to extract memories
198
- console.log("[unipi/memory] Auto-consolidation triggered, summary length:", summary.length);
206
+ // After each agent response, remind LLM to save if it hasn't yet
207
+ pi.on("agent_end", async (_event, _ctx) => {
208
+ if (storeDone || !recallDone) return;
209
+
210
+ pi.sendMessage(
211
+ {
212
+ customType: "unipi-memory-retro-reminder",
213
+ content: [
214
+ "**🧠 Memory reminder:** If you learned something non-obvious in this task,",
215
+ "call `memory_store` to save it as a memory for future sessions.",
216
+ "Update existing memories instead of creating duplicates.",
217
+ ].join(" "),
218
+ display: false,
219
+ },
220
+ {
221
+ deliverAs: "nextTurn",
222
+ },
223
+ );
224
+ });
199
225
 
200
- // Don't modify the compaction summary - return unchanged
201
- return {};
226
+ // After compaction, reset recall state so reminder re-injects
227
+ pi.on("session_compact", async (_event, _ctx) => {
228
+ recallDone = false;
202
229
  });
203
230
 
204
231
  // Cleanup on shutdown
@@ -9,7 +9,6 @@ allowed-tools:
9
9
  - memory_search
10
10
  - memory_delete
11
11
  - memory_list
12
- - global_memory_store
13
12
  - global_memory_search
14
13
  - global_memory_list
15
14
  - read
@@ -87,16 +86,18 @@ memory_list()
87
86
  memory_delete(title: "auth_jwt_prefer_refresh_tokens")
88
87
  ```
89
88
 
90
- ## Project vs Cross-Project Search
89
+ ## Search Scope
91
90
 
92
- | Action | Scope | Tools |
93
- |--------|-------|-------|
91
+ `memory_search` searches ALL projects by default. Use `scope` param to narrow:
92
+
93
+ | Action | Scope | Tool |
94
+ |--------|-------|------|
94
95
  | **Store** | Always project-scoped | `memory_store` |
95
- | **Search this project** | Current project only | `memory_search` |
96
- | **Search all projects** | Cross-project | `global_memory_search` |
96
+ | **Search all projects** | Cross-project (default) | `memory_search(query)` or `memory_search(query, scope="all")` |
97
+ | **Search this project** | Current project only | `memory_search(query, scope="project")` |
97
98
  | **List all** | Cross-project | `global_memory_list` |
98
99
 
99
- **All memories are project-scoped.** When you store a memory, it belongs to the current project. Use `global_memory_search` to search across ALL projects when looking for past work or user preferences.
100
+ **All memories are project-scoped.** When you store a memory, it belongs to the current project. `memory_search` searches everything by default no need to call a separate global search.
100
101
 
101
102
  ## Update-First Principle
102
103
 
@@ -108,7 +109,27 @@ memory_delete(title: "auth_jwt_prefer_refresh_tokens")
108
109
 
109
110
  This prevents memory duplication and keeps memory clean.
110
111
 
111
- ## Consolidation
112
+ ## Vector Search (Embeddings)
113
+
114
+ Memory supports vector similarity search via OpenRouter API.
115
+
116
+ ### Setup
117
+ 1. Run `/unipi:memory-settings`
118
+ 2. Add your OpenRouter API key
119
+ 3. Select embedding model (default: `openai/text-embedding-3-small`)
120
+
121
+ ### How it works
122
+ - Embeddings are generated when storing/searching memories
123
+ - Search combines **vector similarity** + **fuzzy text matching** for best results
124
+ - Vector search finds semantically similar memories even without exact keyword matches
125
+
126
+ ### Model compatibility
127
+ ⚠ **Different embedding models produce incompatible vectors.**
128
+ If you switch models, existing embeddings won't match new searches.
129
+ Use `/unipi:memory-settings` → "Re-embed All Memories" to fix.
130
+
131
+ ### No API key?
132
+ Falls back to fuzzy text-only search. Still works, just less semantic.
112
133
 
113
134
  When the user runs `/unipi:memory-consolidate` or during compaction:
114
135
 
@@ -149,3 +170,4 @@ You can read these files directly with the `read` tool for full context.
149
170
  | Use vague titles | Use specific `<category>_<detail>` format |
150
171
  | Store in wrong scope | Project-specific = project scope, universal = global |
151
172
  | Forget to update | When context changes, update the memory |
173
+ | Switch embedding models without re-embedding | Re-embed or accept fuzzy-only fallback |
@@ -0,0 +1,179 @@
1
+ # @pi-unipi/web-api
2
+
3
+ Web search, read, and summarize tools with provider-based backend selection for Pi coding agent.
4
+
5
+ ## Overview
6
+
7
+ `@pi-unipi/web-api` provides agent tools for web access:
8
+
9
+ - **web_search** — Search the web using various providers
10
+ - **web_read** — Extract content from URLs
11
+ - **web_llm_summarize** — Summarize web content using LLM
12
+
13
+ Providers are ranked by capability and cost, allowing smart auto-selection.
14
+
15
+ ## Features
16
+
17
+ - **Provider-based architecture** — Multiple search/read providers with unified interface
18
+ - **Smart selection** — Auto-select cheapest available provider
19
+ - **API key management** — Interactive TUI for key configuration
20
+ - **Caching** — Web content cached with configurable TTL
21
+ - **Subagent integration** — Tools automatically available to spawned subagents
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @pi-unipi/web-api
27
+ ```
28
+
29
+ Add to your pi configuration:
30
+
31
+ ```json
32
+ {
33
+ "pi": {
34
+ "extensions": [
35
+ "node_modules/@pi-unipi/web-api/src/index.ts"
36
+ ]
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Providers
42
+
43
+ ### Search Providers
44
+
45
+ | Provider | Rank | Cost | API Key |
46
+ |----------|------|------|---------|
47
+ | DuckDuckGo | 1 | Free | No |
48
+ | Jina AI Search | 2 | Freemium | Optional |
49
+ | SerpAPI | 3 | Paid | Required |
50
+ | Tavily | 4 | Paid | Required |
51
+ | Perplexity | 5 | Paid | Required |
52
+
53
+ ### Read Providers
54
+
55
+ | Provider | Rank | Cost | API Key |
56
+ |----------|------|------|---------|
57
+ | Jina AI Reader | 1 | Freemium | Optional |
58
+ | Firecrawl | 2 | Paid | Required |
59
+ | Perplexity | 3 | Paid | Required |
60
+
61
+ ### Summarize Providers
62
+
63
+ | Provider | Rank | Cost | API Key |
64
+ |----------|------|------|---------|
65
+ | Perplexity | 1 | Paid | Required |
66
+ | LLM Summarize | 2 | LLM tokens | No |
67
+
68
+ ## Configuration
69
+
70
+ ### API Keys
71
+
72
+ Configure API keys via the interactive settings command:
73
+
74
+ ```
75
+ /unipi:web-settings
76
+ ```
77
+
78
+ Or set environment variables:
79
+
80
+ ```bash
81
+ export SERPAPI_KEY="your-key"
82
+ export TAVILY_API_KEY="your-key"
83
+ export FIRECRAWL_API_KEY="your-key"
84
+ export PERPLEXITY_API_KEY="your-key"
85
+ export JINA_API_KEY="your-key"
86
+ ```
87
+
88
+ ### Settings Files
89
+
90
+ - **Auth:** `~/.unipi/config/web-api/auth.json` (API keys, gitignored)
91
+ - **Config:** `~/.unipi/config/web-api/config.json` (provider settings)
92
+
93
+ ## Usage
94
+
95
+ ### Web Search
96
+
97
+ ```
98
+ # Auto-select cheapest provider
99
+ web_search(query: "TypeScript generics")
100
+
101
+ # Use specific provider
102
+ web_search(query: "latest AI research", source: 4) # Tavily
103
+ ```
104
+
105
+ ### Web Read
106
+
107
+ ```
108
+ # Auto-select provider
109
+ web_read(url: "https://example.com/article")
110
+
111
+ # Use specific provider
112
+ web_read(url: "https://example.com/spa", source: 2) # Firecrawl
113
+ ```
114
+
115
+ ### Web Summarize
116
+
117
+ ```
118
+ # Auto-summarize
119
+ web_llm_summarize(url: "https://example.com/long-article")
120
+
121
+ # Custom prompt
122
+ web_llm_summarize(url: "https://example.com/research", prompt: "Extract key findings")
123
+ ```
124
+
125
+ ## Commands
126
+
127
+ ### /unipi:web-settings
128
+
129
+ Interactive settings dialog for managing providers and API keys.
130
+
131
+ ### /unipi:web-cache-clear
132
+
133
+ Clear all cached web content.
134
+
135
+ ## Cache
136
+
137
+ - Default TTL: 1 hour
138
+ - Cache location: `~/.unipi/config/web-api/cache/`
139
+ - Automatic for web_read operations
140
+
141
+ ## Troubleshooting
142
+
143
+ ### No provider available
144
+
145
+ If you see "No search provider available":
146
+
147
+ 1. Run `/unipi:web-settings`
148
+ 2. Enable at least one provider
149
+ 3. Add API keys for paid providers
150
+
151
+ ### API key invalid
152
+
153
+ If API key validation fails:
154
+
155
+ 1. Check the key is correct
156
+ 2. Verify the key has sufficient permissions
157
+ 3. Check provider status at their website
158
+
159
+ ### Rate limiting
160
+
161
+ If you hit rate limits:
162
+
163
+ 1. Add an API key for higher limits
164
+ 2. Use a different provider
165
+ 3. Wait and retry
166
+
167
+ ## Development
168
+
169
+ ```bash
170
+ # Type check
171
+ npm run typecheck
172
+
173
+ # Build
174
+ npm run build
175
+ ```
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: web
3
+ description: "Web search, read, and summarize tools with provider-based backend"
4
+ ---
5
+
6
+ # Web Tools
7
+
8
+ Use these tools to access web content. Providers are ranked by capability and cost.
9
+
10
+ ## web_search
11
+
12
+ Search the web for information. Lower `source` = simpler/cheaper providers.
13
+
14
+ - **Quick facts:** source 1-2 (DuckDuckGo, Jina Search)
15
+ - **Research:** source 3-5 (SerpAPI, Tavily, Perplexity)
16
+
17
+ **Parameters:**
18
+ - `query` (required): Search query string
19
+ - `source` (optional): Provider selection (1-5)
20
+
21
+ **Examples:**
22
+ ```
23
+ web_search(query: "TypeScript generics tutorial")
24
+ web_search(query: "latest AI research", source: 4) # Use Tavily
25
+ ```
26
+
27
+ ## web_read
28
+
29
+ Read URL content. Lower `source` = simpler providers.
30
+
31
+ - **Basic extraction:** source 1 (Jina Reader)
32
+ - **Advanced crawling:** source 2 (Firecrawl)
33
+
34
+ **Parameters:**
35
+ - `url` (required): URL to read
36
+ - `source` (optional): Provider selection (1-3)
37
+
38
+ **Examples:**
39
+ ```
40
+ web_read(url: "https://example.com/article")
41
+ web_read(url: "https://example.com/spa", source: 2) # Use Firecrawl
42
+ ```
43
+
44
+ ## web_llm_summarize
45
+
46
+ Summarize URL with LLM. Higher cost (LLM tokens + provider).
47
+
48
+ - Use for complex content that needs analysis
49
+ - Custom prompts supported for targeted summaries
50
+
51
+ **Parameters:**
52
+ - `url` (required): URL to summarize
53
+ - `prompt` (optional): Custom summarization prompt
54
+ - `source` (optional): Provider selection for content fetch (1-3)
55
+
56
+ **Examples:**
57
+ ```
58
+ web_llm_summarize(url: "https://example.com/long-article")
59
+ web_llm_summarize(url: "https://example.com/research", prompt: "Extract key findings")
60
+ ```
61
+
62
+ ## Provider Selection
63
+
64
+ - Omit `source` for auto-selection (cheapest available)
65
+ - Specify `source` number for specific provider
66
+ - If provider unavailable, tool throws descriptive error
67
+
68
+ ### Provider Rankings
69
+
70
+ **Search providers:**
71
+ 1. DuckDuckGo (free)
72
+ 2. Jina AI Search (freemium)
73
+ 3. SerpAPI (paid)
74
+ 4. Tavily (paid)
75
+ 5. Perplexity (paid)
76
+
77
+ **Read providers:**
78
+ 1. Jina AI Reader (freemium)
79
+ 2. Firecrawl (paid)
80
+ 3. Perplexity (paid)
81
+
82
+ **Summarize providers:**
83
+ 1. Perplexity (paid)
84
+ 2. LLM Summarize (uses pi's LLM)
85
+
86
+ ## Cost Awareness
87
+
88
+ - **DuckDuckGo:** Free (search only)
89
+ - **Jina:** Freemium (search + read)
90
+ - **SerpAPI/Tavily:** Paid (search)
91
+ - **Firecrawl:** Paid (read)
92
+ - **Perplexity:** Paid (search + summarize)
93
+ - **LLM Summarize:** LLM token cost
94
+
95
+ ## Configuration
96
+
97
+ Configure providers via `/unipi:web-settings` command.
98
+
99
+ - Add/remove API keys
100
+ - Enable/disable providers
101
+ - View provider status
102
+
103
+ ## Cache
104
+
105
+ Web content is cached for 1 hour by default.
106
+
107
+ - Clear cache: `/unipi:web-cache-clear`
108
+ - Cache is automatic for web_read operations
@@ -86,7 +86,16 @@ export default function (pi: ExtensionAPI) {
86
86
  systemPrompt:
87
87
  event.systemPrompt +
88
88
  base +
89
- `\nThe write tool is available but restricted to .unipi/docs/specs/ only.\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
89
+ `\nThe write tool is available but restricted to .unipi/docs/specs/ only.\nbash is available ONLY for specific setup use case (e.g., git init, mkdir). Do NOT use bash for reading files or listing directories — use grep, find, ls instead.\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
90
+ };
91
+ }
92
+
93
+ if (currentSandboxLevel === "write_unipi") {
94
+ return {
95
+ systemPrompt:
96
+ event.systemPrompt +
97
+ base +
98
+ `\nWrite tool is restricted to .unipi/docs/ only (specs and plans).\nUse grep, find, ls for file discovery — do NOT guess filenames.\nbash is blocked — use read, write, edit, grep, find, ls only.\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
90
99
  };
91
100
  }
92
101
 
@@ -94,7 +103,7 @@ export default function (pi: ExtensionAPI) {
94
103
  systemPrompt:
95
104
  event.systemPrompt +
96
105
  base +
97
- `\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requests an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
106
+ `\nDo NOT attempt to call blocked tools. Do NOT output tool call XML for them.\nIf the user requires an action that requires a blocked tool, respond that you do not have access.\n</sandbox>`,
98
107
  };
99
108
  });
100
109
 
@@ -145,6 +145,7 @@ The `## Implementation Checklist` section uses `- [ ]` markdown checkboxes. Thes
145
145
 
146
146
  - **`[ ]` = unplanned** — not yet covered by a plan
147
147
  - **`[x]` = planned** — marked when `/unipi:plan` covers this item
148
+ - **`[x]` does NOT mean done** — implementation progress is tracked in the plan file (`unstarted:` / `in-progress:` / `completed:`)
148
149
  - Agent MUST fill in checklist items based on the design
149
150
  - Each item should be a discrete, implementable task
150
151
  - Items should be ordered by dependency (earlier items don't depend on later ones)
@@ -70,6 +70,7 @@ Structure the plan with heart of gold style:
70
70
  title: "{Topic} — Implementation Plan"
71
71
  type: plan
72
72
  date: YYYY-MM-DD
73
+ workbranch: {branch-name or empty if on main}
73
74
  specs:
74
75
  - path/to/spec.md
75
76
  ---
@@ -81,16 +82,19 @@ specs:
81
82
 
82
83
  ## Tasks
83
84
 
84
- ### Task 1: {Task Name}
85
- **Description:** {What needs to be done}
86
- **Dependencies:** {None, or list of tasks}
87
- **Acceptance Criteria:** {How to verify done}
88
- **Steps:**
89
- 1. {Concrete step}
90
- 2. {Concrete step}
85
+ - unstarted: Task 1 {Task Name}
86
+ - Description: {What needs to be done}
87
+ - Dependencies: {None, or list of tasks}
88
+ - Acceptance Criteria: {How to verify done}
89
+ - Steps:
90
+ 1. {Concrete step}
91
+ 2. {Concrete step}
91
92
 
92
- ### Task 2: {Task Name}
93
- ...
93
+ - unstarted: Task 2 {Task Name}
94
+ - Description: ...
95
+ - Dependencies: ...
96
+ - Acceptance Criteria: ...
97
+ - Steps: ...
94
98
 
95
99
  ## Sequencing
96
100
  {Order of execution, dependency graph if complex}
@@ -107,6 +111,20 @@ specs:
107
111
  - **Steps** are bite-sized — agent can follow without guessing
108
112
  - Order tasks by dependency (foundational work first)
109
113
 
114
+ ### Task Status Lifecycle
115
+
116
+ Tasks use prefixes to track progress:
117
+
118
+ | Status | Meaning |
119
+ |--------|----------|
120
+ | `unstarted:` | Not started |
121
+ | `in-progress:` | Being worked on |
122
+ | `completed:` | Done and verified |
123
+ | `failed:` | Attempted but failed, needs investigation |
124
+ | `awaiting_user:` | Needs user action (test, approve, provide input) |
125
+ | `blocked:` | Waiting on dependency or external factor |
126
+ | `skipped:` | Intentionally not doing (deferred, out of scope) |
127
+
110
128
  ---
111
129
 
112
130
  ## Phase 4: Update Brainstorm Checkboxes
@@ -129,6 +147,8 @@ After plan is complete:
129
147
 
130
148
  This creates the link between brainstorm and plan — plan covers some items, others remain for future plans.
131
149
 
150
+ **Semantics:** Spec `[x]` means "planned" (covered by a plan). It does NOT mean "done". Implementation progress is tracked in the plan file, not the spec.
151
+
132
152
  ---
133
153
 
134
154
  ## Phase 5: Review Plan
@@ -138,10 +158,11 @@ Self-check before presenting:
138
158
  - [ ] Every task has clear acceptance criteria
139
159
  - [ ] Dependencies are correct (no circular, no missing)
140
160
  - [ ] Steps are bite-sized (agent can follow without guessing)
141
- - [ ] Brainstorm checkboxes updated correctly
142
161
  - [ ] String greedy scope respected (if provided)
143
162
  - [ ] Plan is focused enough for single `/unipi:work` session
144
163
 
164
+ Do NOT re-read or re-edit the spec checkboxes — Phase 4 already wrote them.
165
+
145
166
  ---
146
167
 
147
168
  ## Phase 6: Present & Handoff
@@ -164,6 +185,5 @@ Recommend starting a **new session** for work — it will switch pi's internal w
164
185
  ## Resumability
165
186
 
166
187
  If user runs `/unipi:plan` on an existing plan:
167
- 1. Read the plan
168
- 2. Check what's already marked done
169
- 3. Ask user if they want to add tasks, modify existing, or plan new scope
188
+ 1. Read the plan — look for `completed:` tasks
189
+ 2. Ask user if they want to add tasks, modify existing, or plan new scope
@@ -25,13 +25,17 @@ Review what was built, verify task completion, run codebase checks, add reviewer
25
25
 
26
26
  ---
27
27
 
28
- ## Phase 1: Load Plan
28
+ ## Phase 1: Load Plan & Switch Branch
29
29
 
30
30
  1. If `plan:` arg provided, read that plan
31
31
  2. If not, list plans in `.unipi/docs/plans/` and ask user
32
32
  3. Read plan fully — understand tasks, acceptance criteria, current status
33
+ 4. **Read `workbranch:` from plan frontmatter:**
34
+ - If `workbranch:` exists and is not empty → switch to that branch/worktree
35
+ - If `workbranch:` missing or empty → review on current branch (main)
36
+ - To switch: `git checkout {workbranch}` or use worktree path
33
37
 
34
- **Exit:** Plan loaded. Know what to review.
38
+ **Exit:** On correct branch. Plan loaded.
35
39
 
36
40
  ---
37
41
 
@@ -42,9 +46,13 @@ For each task in plan:
42
46
  1. Read acceptance criteria
43
47
  2. Verify against actual implementation
44
48
  3. Determine status:
45
- - **Done** — all criteria met
49
+ - **Done** — all criteria met → `completed:`
46
50
  - **Partially Done X/Y** — some steps complete, others not
47
- - **Unstarted** — nothing done
51
+ - **Unstarted** — nothing done → `unstarted:`
52
+ - **Failed** — attempted but broken → `failed:`
53
+ - **Awaiting User** — needs user action → `awaiting_user:`
54
+ - **Blocked** — waiting on dependency → `blocked:`
55
+ - **Skipped** — intentionally not done → `skipped:`
48
56
 
49
57
  If `string(greedy)` scope provided, only check matching tasks.
50
58
 
@@ -39,6 +39,7 @@ If args not provided, ask user interactively:
39
39
  - "Do you want to work on current branch or create a worktree?"
40
40
  - If worktree: "What branch name?" (suggest based on spec topic)
41
41
  - Create worktree if not exists
42
+ - **After creating/confirming worktree:** write `workbranch: {branch-name}` to the plan file frontmatter
42
43
 
43
44
  2. **Specs:**
44
45
  - List available plans in `.unipi/docs/plans/`
@@ -58,7 +59,7 @@ If args not provided, ask user interactively:
58
59
  1. Read plan file(s)
59
60
  2. Review critically — identify questions or concerns
60
61
  3. If concerns: raise with user before starting
61
- 4. Identify which tasks are already `[x]` (done) vs `[ ]` (pending)
62
+ 4. Identify task status: `unstarted:` (pending), `in-progress:` (started), `completed:` (done), `failed:` (needs investigation), `awaiting_user:` (needs user action), `blocked:` (waiting on dependency), `skipped:` (deferred)
62
63
 
63
64
  **Exit:** Plan reviewed, ready to execute.
64
65
 
@@ -66,14 +67,36 @@ If args not provided, ask user interactively:
66
67
 
67
68
  ## Phase 3: Execute Tasks
68
69
 
69
- For each task in order:
70
+ For each task in order, skip `completed:`, `skipped:`, and `awaiting_user:` tasks:
70
71
 
71
- 1. Mark task as `in_progress` in plan
72
+ ### If `unstarted:`
73
+ 1. Change `unstarted:` to `in-progress:` in plan
72
74
  2. Follow each step exactly (plan has bite-sized steps)
73
75
  3. Run verifications as specified in acceptance criteria
74
- 4. Mark as `[x]` when complete
76
+ 4. Change `in-progress:` to `completed:` when complete
75
77
  5. Update plan file with progress
76
78
 
79
+ ### If `in-progress:`
80
+ 1. Continue from where it left off
81
+ 2. Follow remaining steps
82
+ 3. Change to `completed:` when done
83
+
84
+ ### If `failed:`
85
+ 1. Read failure notes
86
+ 2. Investigate root cause
87
+ 3. Fix and re-verify
88
+ 4. Change to `completed:` when fixed, or keep `failed:` if still broken
89
+
90
+ ### If `awaiting_user:`
91
+ 1. Remind user what's needed
92
+ 2. Wait for user response
93
+ 3. Resume when user provides input
94
+
95
+ ### If `blocked:`
96
+ 1. Check if blocker is resolved
97
+ 2. If resolved → change to `unstarted:` and continue
98
+ 3. If still blocked → skip and move to next task
99
+
77
100
  ### When to Stop and Ask
78
101
 
79
102
  **STOP immediately when:**
@@ -107,12 +130,11 @@ Don't wait until end to commit — incremental commits are safer.
107
130
 
108
131
  ## Phase 5: Complete
109
132
 
110
- When all tasks marked `[x]`:
133
+ When all tasks are `completed:`:
111
134
 
112
135
  1. Run final verification (tests, lint, build)
113
136
  2. Commit all remaining changes
114
- 3. Update plan with completion status
115
- 4. Inform user:
137
+ 3. Inform user:
116
138
 
117
139
  > "All tasks complete. Worktree: `feat/<branch>`. Recommend reviewing before merge."
118
140
 
@@ -127,9 +149,9 @@ Suggest next step:
127
149
 
128
150
  ## Resumability
129
151
 
130
- If user runs `/unipi:work` and plan has partial `[x]` marks:
152
+ If user runs `/unipi:work` and plan has `completed:` or `in-progress:` tasks:
131
153
  1. Read plan
132
- 2. Identify first `[ ]` task
154
+ 2. Identify first `unstarted:` task
133
155
  3. Ask user: "Resume from Task N: {name}?"
134
156
  4. Continue from there
135
157
 
@@ -9,8 +9,8 @@ List all git worktrees created by unipi.
9
9
 
10
10
  ## Boundaries
11
11
 
12
- **This skill MAY:** run git worktree commands, read filesystem.
13
- **This skill MAY NOT:** edit anything, create worktrees, merge branches.
12
+ **This skill MAY:** read filesystem (ls, read), grep for git info.
13
+ **This skill MAY NOT:** edit anything, create worktrees, merge branches, run bash.
14
14
 
15
15
  ## Command Format
16
16
 
@@ -24,17 +24,27 @@ No arguments. Lists all unipi worktrees.
24
24
 
25
25
  ## Process
26
26
 
27
- ### Phase 1: Gather Worktrees
27
+ ### Phase 1: Discover Worktrees
28
28
 
29
- 1. Run `git worktree list --porcelain`
30
- 2. Filter for worktrees under `.unipi/worktrees/`
31
- 3. For each worktree, collect:
32
- - Branch name
33
- - Path
34
- - HEAD commit
35
- - Whether it has uncommitted changes
29
+ 1. `ls .unipi/worktrees/` list worktree directories
30
+ 2. For each directory:
31
+ - Read `.unipi/worktrees/<branch>/.git` file to get gitdir path
32
+ - Check for uncommitted changes: `ls .unipi/worktrees/<branch>/` and read key files
33
+ 3. If `.unipi/worktrees/` doesn't exist or is empty → no worktrees
36
34
 
37
- ### Phase 2: Present
35
+ **Exit:** List of worktrees discovered.
36
+
37
+ ### Phase 2: Determine Status
38
+
39
+ For each worktree directory:
40
+
41
+ 1. Check if `HEAD` file exists in gitdir
42
+ 2. Check for modified files by reading directory listing
43
+ 3. Determine status:
44
+ - **clean** — no uncommitted changes
45
+ - **dirty** — has uncommitted changes
46
+
47
+ ### Phase 3: Present
38
48
 
39
49
  Display in table format:
40
50
 
@@ -51,7 +61,7 @@ Worktrees:
51
61
  If no worktrees:
52
62
  > "No unipi worktrees. Create one with `/unipi:worktree-create`"
53
63
 
54
- ### Phase 3: Suggest Actions
64
+ ### Phase 4: Suggest Actions
55
65
 
56
66
  Based on state:
57
67
  - If dirty worktrees exist → suggest reviewing or committing
@@ -65,3 +75,4 @@ Based on state:
65
75
  - Only shows worktrees under `.unipi/worktrees/`
66
76
  - Ignores worktrees in other locations
67
77
  - Status: `clean` = no uncommitted changes, `dirty` = has changes
78
+ - No bash required — uses ls, read, grep for all discovery
@@ -46,8 +46,16 @@ For each source branch (in dependency order if specs indicate ordering):
46
46
  3. **If conflict:**
47
47
  - Report which files conflict
48
48
  - Reference the spec/plan context: "This branch was working on: <description>"
49
- - Ask user how to resolve (do NOT force merge)
50
- - Skip to next branch if user says so
49
+ - **Structured conflict resolution:**
50
+ 1. **Check finalized plan** — read the plan for this branch, understand intended changes
51
+ 2. **Brainstorm resolution** — reason about which changes align with the plan's intent
52
+ 3. **Read codebase** — read the conflicting files to understand current state
53
+ 4. **Ask user** — present options and ask for final decision (always ask, never force)
54
+ - Options to present:
55
+ - Accept incoming (their changes)
56
+ - Accept current (main branch)
57
+ - Manual merge (describe what each side does, ask user to decide)
58
+ - Skip this branch
51
59
  4. **If success:**
52
60
  - Remove worktree: `git worktree remove .unipi/worktrees/<branch-name>`
53
61
  - Delete branch: `git branch -d <branch>`