@pi-unipi/unipi 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -0
- package/package.json +70 -0
- package/packages/btw/README.md +95 -0
- package/packages/btw/extensions/btw.ts +1972 -0
- package/packages/btw/skills/btw/SKILL.md +149 -0
- package/packages/core/README.md +74 -0
- package/packages/core/index.ts +10 -0
- package/packages/info-screen/README.md +115 -0
- package/packages/info-screen/index.ts +165 -0
- package/packages/memory/README.md +94 -0
- package/packages/memory/index.ts +209 -0
- package/packages/memory/skills/memory/SKILL.md +151 -0
- package/packages/ralph/README.md +101 -0
- package/packages/ralph/index.ts +548 -0
- package/packages/subagents/README.md +114 -0
- package/packages/unipi/index.ts +28 -0
- package/packages/workflow/README.md +129 -0
- package/packages/workflow/index.ts +155 -0
- package/packages/workflow/skills/brainstorm/SKILL.md +202 -0
- package/packages/workflow/skills/consolidate/SKILL.md +142 -0
- package/packages/workflow/skills/consultant/SKILL.md +97 -0
- package/packages/workflow/skills/document/SKILL.md +120 -0
- package/packages/workflow/skills/gather-context/SKILL.md +122 -0
- package/packages/workflow/skills/plan/SKILL.md +169 -0
- package/packages/workflow/skills/quick-work/SKILL.md +110 -0
- package/packages/workflow/skills/review-work/SKILL.md +131 -0
- package/packages/workflow/skills/scan-issues/SKILL.md +183 -0
- package/packages/workflow/skills/work/SKILL.md +144 -0
- package/packages/workflow/skills/worktree-create/SKILL.md +69 -0
- package/packages/workflow/skills/worktree-list/SKILL.md +67 -0
- package/packages/workflow/skills/worktree-merge/SKILL.md +79 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @unipi/memory — Extension entry
|
|
3
|
+
*
|
|
4
|
+
* Persistent cross-session memory with vector search.
|
|
5
|
+
* All storage is project-scoped. "Global" tools search across all projects.
|
|
6
|
+
* Injects memory titles at session start.
|
|
7
|
+
* Auto-consolidates on compaction.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import {
|
|
12
|
+
UNIPI_EVENTS,
|
|
13
|
+
MODULES,
|
|
14
|
+
emitEvent,
|
|
15
|
+
getPackageVersion,
|
|
16
|
+
} from "@pi-unipi/core";
|
|
17
|
+
|
|
18
|
+
// Get info registry from global (avoids direct import issues with pi's extension loading)
|
|
19
|
+
function getInfoRegistry() {
|
|
20
|
+
const g = globalThis as any;
|
|
21
|
+
return g.__unipi_info_registry;
|
|
22
|
+
}
|
|
23
|
+
import {
|
|
24
|
+
MemoryStorage,
|
|
25
|
+
getProjectName,
|
|
26
|
+
searchAllProjects,
|
|
27
|
+
listAllProjects,
|
|
28
|
+
} from "./storage.js";
|
|
29
|
+
import { registerMemoryTools, MEMORY_TOOLS } from "./tools.js";
|
|
30
|
+
import { registerMemoryCommands } from "./commands.js";
|
|
31
|
+
|
|
32
|
+
/** Package version */
|
|
33
|
+
const VERSION = getPackageVersion(new URL(".", import.meta.url).pathname);
|
|
34
|
+
|
|
35
|
+
/** Storage instance for current project */
|
|
36
|
+
let projectStorage: MemoryStorage | null = null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get storage for the current project.
|
|
40
|
+
*/
|
|
41
|
+
function getStorage(): MemoryStorage {
|
|
42
|
+
if (!projectStorage) {
|
|
43
|
+
// Fallback: create new instance (shouldn't happen after session_start)
|
|
44
|
+
return new MemoryStorage("unknown");
|
|
45
|
+
}
|
|
46
|
+
return projectStorage;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default function (pi: ExtensionAPI) {
|
|
50
|
+
// Register skills directory
|
|
51
|
+
const skillsDir = new URL("./skills", import.meta.url).pathname;
|
|
52
|
+
pi.on("resources_discover", async (_event, _ctx) => {
|
|
53
|
+
return {
|
|
54
|
+
skillPaths: [skillsDir],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Register tools and commands
|
|
59
|
+
registerMemoryTools(pi, getStorage);
|
|
60
|
+
registerMemoryCommands(pi, getStorage);
|
|
61
|
+
|
|
62
|
+
// Session lifecycle
|
|
63
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
64
|
+
// Initialize project storage
|
|
65
|
+
const projectName = getProjectName(ctx.cwd);
|
|
66
|
+
projectStorage = new MemoryStorage(projectName);
|
|
67
|
+
projectStorage.init();
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
// Announce module
|
|
71
|
+
emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
|
|
72
|
+
name: MODULES.MEMORY,
|
|
73
|
+
version: VERSION,
|
|
74
|
+
commands: [
|
|
75
|
+
"unipi:memory-process",
|
|
76
|
+
"unipi:memory-search",
|
|
77
|
+
"unipi:memory-consolidate",
|
|
78
|
+
"unipi:memory-forget",
|
|
79
|
+
"unipi:global-memory-search",
|
|
80
|
+
"unipi:global-memory-list",
|
|
81
|
+
],
|
|
82
|
+
tools: [
|
|
83
|
+
MEMORY_TOOLS.STORE,
|
|
84
|
+
MEMORY_TOOLS.SEARCH,
|
|
85
|
+
MEMORY_TOOLS.DELETE,
|
|
86
|
+
MEMORY_TOOLS.LIST,
|
|
87
|
+
MEMORY_TOOLS.GLOBAL_SEARCH,
|
|
88
|
+
MEMORY_TOOLS.GLOBAL_LIST,
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Register info group
|
|
93
|
+
const registry = getInfoRegistry();
|
|
94
|
+
if (registry) {
|
|
95
|
+
console.debug("[memory] Registering info group");
|
|
96
|
+
registry.registerGroup({
|
|
97
|
+
id: "memory",
|
|
98
|
+
name: "Memory",
|
|
99
|
+
icon: "🧠",
|
|
100
|
+
priority: 60,
|
|
101
|
+
config: {
|
|
102
|
+
showByDefault: true,
|
|
103
|
+
stats: [
|
|
104
|
+
{ id: "projectCount", label: "Project Memories", show: true },
|
|
105
|
+
{ id: "totalCount", label: "Total Memories", show: true },
|
|
106
|
+
{ id: "projects", label: "Projects", show: true },
|
|
107
|
+
{ id: "recent", label: "Recent Memories", show: true },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
dataProvider: async () => {
|
|
111
|
+
if (!projectStorage) {
|
|
112
|
+
return {
|
|
113
|
+
projectCount: { value: "0" },
|
|
114
|
+
totalCount: { value: "0" },
|
|
115
|
+
projects: { value: "none" },
|
|
116
|
+
recent: { value: "none" },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const projectMemories = projectStorage.listAll();
|
|
121
|
+
const allMemories = listAllProjects();
|
|
122
|
+
const uniqueProjects = [...new Set(allMemories.map((m) => m.project))];
|
|
123
|
+
|
|
124
|
+
// Get 3 most recent memories (sorted by updated DESC in listAll)
|
|
125
|
+
const recentMemories = projectMemories.slice(0, 3);
|
|
126
|
+
const recentList = recentMemories.map(m => m.title).join("\n");
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
projectCount: { value: String(projectMemories.length) },
|
|
130
|
+
totalCount: { value: String(allMemories.length) },
|
|
131
|
+
projects: {
|
|
132
|
+
value: uniqueProjects.length.toString(),
|
|
133
|
+
detail: uniqueProjects.slice(0, 5).join(", ") + (uniqueProjects.length > 5 ? ` +${uniqueProjects.length - 5} more` : ""),
|
|
134
|
+
},
|
|
135
|
+
recent: {
|
|
136
|
+
value: recentMemories.length > 0 ? recentMemories[0].title : "none",
|
|
137
|
+
detail: recentMemories.length > 1 ? recentMemories.slice(1).map(m => m.title).join("\n") : undefined,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Show memory status in UI
|
|
145
|
+
if (ctx.hasUI) {
|
|
146
|
+
const projectCount = projectStorage.listAll().length;
|
|
147
|
+
const allMemories = listAllProjects();
|
|
148
|
+
const projectCountAll = allMemories.length;
|
|
149
|
+
ctx.ui.setStatus(
|
|
150
|
+
"unipi-memory",
|
|
151
|
+
`🧠 memory ${projectCount}p/${projectCountAll}all`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Inject memory titles at session start
|
|
157
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
158
|
+
if (!projectStorage) return;
|
|
159
|
+
|
|
160
|
+
const projectName = getProjectName(ctx.cwd);
|
|
161
|
+
const projectMemories = projectStorage.listAll();
|
|
162
|
+
|
|
163
|
+
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`;
|
|
173
|
+
}
|
|
174
|
+
|
|
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>";
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
systemPrompt: event.systemPrompt + injection,
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
|
|
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);
|
|
199
|
+
|
|
200
|
+
// Don't modify the compaction summary - return unchanged
|
|
201
|
+
return {};
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Cleanup on shutdown
|
|
205
|
+
pi.on("session_shutdown", async () => {
|
|
206
|
+
projectStorage?.close();
|
|
207
|
+
projectStorage = null;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory
|
|
3
|
+
description: >
|
|
4
|
+
Persistent cross-session memory management. Store and retrieve user preferences,
|
|
5
|
+
project decisions, code patterns, and conversation summaries across sessions.
|
|
6
|
+
Use when you need to remember something important or recall past context.
|
|
7
|
+
allowed-tools:
|
|
8
|
+
- memory_store
|
|
9
|
+
- memory_search
|
|
10
|
+
- memory_delete
|
|
11
|
+
- memory_list
|
|
12
|
+
- global_memory_store
|
|
13
|
+
- global_memory_search
|
|
14
|
+
- global_memory_list
|
|
15
|
+
- read
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Memory
|
|
19
|
+
|
|
20
|
+
Persistent cross-session memory for Pi coding agent. Memories survive session restarts, compaction, and context resets.
|
|
21
|
+
|
|
22
|
+
## When to Store Memory
|
|
23
|
+
|
|
24
|
+
Store memory when you encounter:
|
|
25
|
+
|
|
26
|
+
| Type | Examples | Title Format |
|
|
27
|
+
|------|----------|--------------|
|
|
28
|
+
| **Preference** | User likes tabs, prefers functional style | `style_typescript_prefer_tabs` |
|
|
29
|
+
| **Decision** | Chose PostgreSQL over MySQL, JWT over sessions | `db_postgres_chosen_over_mysql` |
|
|
30
|
+
| **Pattern** | How auth is structured, API naming conventions | `api_rest_versioning_v2` |
|
|
31
|
+
| **Summary** | Key findings from debugging, research results | `perf_slow_query_root_cause` |
|
|
32
|
+
|
|
33
|
+
## Naming Convention
|
|
34
|
+
|
|
35
|
+
**Format:** `<most_important>_<less_important>_<lesser>`
|
|
36
|
+
|
|
37
|
+
**Rules:**
|
|
38
|
+
- Use underscores, not hyphens
|
|
39
|
+
- Start with category (style, db, auth, api, arch, etc.)
|
|
40
|
+
- Be specific: `auth_jwt_prefer_refresh_tokens` not `auth_tokens`
|
|
41
|
+
- Keep under 60 characters
|
|
42
|
+
|
|
43
|
+
**Good titles:**
|
|
44
|
+
- `style_typescript_strict_mode_always`
|
|
45
|
+
- `db_postgres_use_connection_pooling`
|
|
46
|
+
- `arch_api_versioning_v2_breaking`
|
|
47
|
+
- `perf_cache_redis_for_sessions`
|
|
48
|
+
|
|
49
|
+
**Bad titles:**
|
|
50
|
+
- `auth` (too vague)
|
|
51
|
+
- `User prefers tabs` (not snake_case)
|
|
52
|
+
- `auth-jwt-refresh` (hyphens, not underscores)
|
|
53
|
+
|
|
54
|
+
## When to Search Memory
|
|
55
|
+
|
|
56
|
+
Search memory when:
|
|
57
|
+
|
|
58
|
+
1. **User references past work:** "Remember when we fixed the auth bug?"
|
|
59
|
+
2. **Making similar decisions:** "What did we decide about database choice?"
|
|
60
|
+
3. **Setting up new features:** "What's the user's coding style?"
|
|
61
|
+
4. **Debugging recurring issues:** "Have we seen this error before?"
|
|
62
|
+
|
|
63
|
+
## How to Use Tools
|
|
64
|
+
|
|
65
|
+
### Store a memory:
|
|
66
|
+
```
|
|
67
|
+
memory_store(
|
|
68
|
+
title: "auth_jwt_prefer_refresh_tokens",
|
|
69
|
+
content: "User prefers short-lived access tokens (15min) with long-lived refresh tokens (30d). Always implement token rotation on refresh.",
|
|
70
|
+
tags: ["auth", "jwt", "preferences"],
|
|
71
|
+
type: "preference"
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Search memories:
|
|
76
|
+
```
|
|
77
|
+
memory_search(query: "auth tokens")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### List all project memories:
|
|
81
|
+
```
|
|
82
|
+
memory_list()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Delete a memory:
|
|
86
|
+
```
|
|
87
|
+
memory_delete(title: "auth_jwt_prefer_refresh_tokens")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Project vs Cross-Project Search
|
|
91
|
+
|
|
92
|
+
| Action | Scope | Tools |
|
|
93
|
+
|--------|-------|-------|
|
|
94
|
+
| **Store** | Always project-scoped | `memory_store` |
|
|
95
|
+
| **Search this project** | Current project only | `memory_search` |
|
|
96
|
+
| **Search all projects** | Cross-project | `global_memory_search` |
|
|
97
|
+
| **List all** | Cross-project | `global_memory_list` |
|
|
98
|
+
|
|
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
|
+
|
|
101
|
+
## Update-First Principle
|
|
102
|
+
|
|
103
|
+
**Always check before creating.** Before storing a new memory:
|
|
104
|
+
|
|
105
|
+
1. Search for similar memories
|
|
106
|
+
2. If found and relevant → UPDATE the existing memory
|
|
107
|
+
3. If not found → CREATE new memory
|
|
108
|
+
|
|
109
|
+
This prevents memory duplication and keeps memory clean.
|
|
110
|
+
|
|
111
|
+
## Consolidation
|
|
112
|
+
|
|
113
|
+
When the user runs `/unipi:memory-consolidate` or during compaction:
|
|
114
|
+
|
|
115
|
+
1. Review the session for memory-worthy items
|
|
116
|
+
2. For each item:
|
|
117
|
+
- Search for existing similar memory
|
|
118
|
+
- Update if found, create if not
|
|
119
|
+
3. Report what was stored/updated
|
|
120
|
+
|
|
121
|
+
## Reading Memory Files
|
|
122
|
+
|
|
123
|
+
Memory files are stored in `~/.unipi/memory/` as markdown with YAML frontmatter:
|
|
124
|
+
|
|
125
|
+
```markdown
|
|
126
|
+
---
|
|
127
|
+
title: auth_jwt_prefer_refresh_tokens
|
|
128
|
+
tags: [auth, jwt, preferences]
|
|
129
|
+
project: my-app
|
|
130
|
+
created: 2026-04-26T10:00:00Z
|
|
131
|
+
updated: 2026-04-26T15:30:00Z
|
|
132
|
+
type: preference
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
# Auth: Prefer Refresh Tokens
|
|
136
|
+
|
|
137
|
+
User prefers short-lived access tokens (15min) with long-lived refresh tokens (30d).
|
|
138
|
+
Always implement token rotation on refresh.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
You can read these files directly with the `read` tool for full context.
|
|
142
|
+
|
|
143
|
+
## Anti-Patterns
|
|
144
|
+
|
|
145
|
+
| Don't | Do Instead |
|
|
146
|
+
|-------|------------|
|
|
147
|
+
| Store everything | Only store decisions, preferences, patterns, summaries |
|
|
148
|
+
| Create duplicate memories | Search first, update existing |
|
|
149
|
+
| Use vague titles | Use specific `<category>_<detail>` format |
|
|
150
|
+
| Store in wrong scope | Project-specific = project scope, universal = global |
|
|
151
|
+
| Forget to update | When context changes, update the memory |
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# @pi-unipi/ralph
|
|
2
|
+
|
|
3
|
+
Long-running iterative development loops for [Pi coding agent](https://github.com/badlogic/pi-mono).
|
|
4
|
+
|
|
5
|
+
Start a loop, the agent works through iterations, reflects periodically, and completes when done. State persists across sessions.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pi install npm:@pi-unipi/ralph
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or as part of the full suite:
|
|
14
|
+
```bash
|
|
15
|
+
pi install npm:unipi
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
| Command | Description |
|
|
21
|
+
|---------|-------------|
|
|
22
|
+
| `/unipi:ralph start <name\|path>` | Start a new loop |
|
|
23
|
+
| `/unipi:ralph stop` | Pause current loop |
|
|
24
|
+
| `/unipi:ralph resume <name>` | Resume a paused loop |
|
|
25
|
+
| `/unipi:ralph status` | Show all loops |
|
|
26
|
+
| `/unipi:ralph cancel <name>` | Delete loop state |
|
|
27
|
+
| `/unipi:ralph archive <name>` | Move loop to archive |
|
|
28
|
+
| `/unipi:ralph clean [--all]` | Clean completed loops |
|
|
29
|
+
| `/unipi:ralph list --archived` | Show archived loops |
|
|
30
|
+
| `/unipi:ralph nuke [--yes]` | Delete all ralph data |
|
|
31
|
+
|
|
32
|
+
### Options
|
|
33
|
+
|
|
34
|
+
| Flag | Description |
|
|
35
|
+
|------|-------------|
|
|
36
|
+
| `--max-iterations N` | Stop after N iterations (default: 50) |
|
|
37
|
+
| `--items-per-iteration N` | Suggest N items per turn (prompt hint) |
|
|
38
|
+
| `--reflect-every N` | Reflect every N iterations |
|
|
39
|
+
|
|
40
|
+
## Tools
|
|
41
|
+
|
|
42
|
+
| Tool | Description |
|
|
43
|
+
|------|-------------|
|
|
44
|
+
| `ralph_start` | Start a ralph loop (called by agent) |
|
|
45
|
+
| `ralph_done` | Signal iteration complete (called by agent) |
|
|
46
|
+
|
|
47
|
+
## How It Works
|
|
48
|
+
|
|
49
|
+
1. **Start** — creates a task file in `.unipi/ralph/` and begins iteration loop
|
|
50
|
+
2. **Iterate** — agent works on task, updates progress, calls `ralph_done`
|
|
51
|
+
3. **Reflect** — optional reflection prompts at configurable intervals
|
|
52
|
+
4. **Complete** — agent emits `COMPLETE` marker or max iterations reached
|
|
53
|
+
5. **Persist** — state saved to disk, survives session restarts
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
start → iterate → done → iterate → ... → complete
|
|
57
|
+
↑ │
|
|
58
|
+
└──── resume ────────────────────────────┘
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Task File Format
|
|
62
|
+
|
|
63
|
+
Loops operate on markdown task files:
|
|
64
|
+
|
|
65
|
+
```markdown
|
|
66
|
+
# Task
|
|
67
|
+
|
|
68
|
+
Redesign the authentication system.
|
|
69
|
+
|
|
70
|
+
## Goals
|
|
71
|
+
- Migrate to JWT refresh tokens
|
|
72
|
+
- Add role-based access control
|
|
73
|
+
|
|
74
|
+
## Checklist
|
|
75
|
+
- [x] Design token rotation
|
|
76
|
+
- [ ] Implement refresh endpoint
|
|
77
|
+
- [ ] Add RBAC middleware
|
|
78
|
+
- [ ] Write tests
|
|
79
|
+
|
|
80
|
+
## Notes
|
|
81
|
+
Session 1: Completed token rotation design.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## State Storage
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
.unipi/ralph/
|
|
88
|
+
├── loop-name.state.json ← active/paused state
|
|
89
|
+
├── loop-name.md ← task file
|
|
90
|
+
└── archive/
|
|
91
|
+
└── loop-name.* ← archived loops
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Integration
|
|
95
|
+
|
|
96
|
+
- **@pi-unipi/core** — event types, constants, utilities
|
|
97
|
+
- **@pi-unipi/info-screen** — registers info group showing loop status
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|