omni-pi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CREDITS.md +28 -0
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/agents/brain.md +24 -0
- package/agents/expert.md +21 -0
- package/agents/planner.md +22 -0
- package/agents/worker.md +21 -0
- package/bin/omni.js +79 -0
- package/extensions/omni-core/index.ts +22 -0
- package/extensions/omni-memory/index.ts +72 -0
- package/extensions/omni-skills/index.ts +11 -0
- package/extensions/omni-status/index.ts +11 -0
- package/package.json +75 -0
- package/prompts/brainstorm.md +15 -0
- package/prompts/spec-template.md +14 -0
- package/prompts/task-template.md +16 -0
- package/skills/omni-escalation/SKILL.md +17 -0
- package/skills/omni-execution/SKILL.md +18 -0
- package/skills/omni-init/SKILL.md +19 -0
- package/skills/omni-planning/SKILL.md +19 -0
- package/skills/omni-verification/SKILL.md +18 -0
- package/src/commands.ts +521 -0
- package/src/config.ts +154 -0
- package/src/context.ts +165 -0
- package/src/contracts.ts +183 -0
- package/src/doctor.ts +225 -0
- package/src/git.ts +135 -0
- package/src/memory.ts +25 -0
- package/src/pi.ts +240 -0
- package/src/planning.ts +303 -0
- package/src/plans.ts +247 -0
- package/src/repo.ts +210 -0
- package/src/skills.ts +308 -0
- package/src/status.ts +105 -0
- package/src/subagents.ts +1031 -0
- package/src/sync.ts +70 -0
- package/src/tasks.ts +141 -0
- package/src/templates.ts +261 -0
- package/src/work.ts +345 -0
- package/src/workflow.ts +375 -0
- package/templates/omni/DECISIONS.md +10 -0
- package/templates/omni/IDEAS.md +13 -0
- package/templates/omni/PROJECT.md +19 -0
- package/templates/omni/SESSION-SUMMARY.md +13 -0
- package/templates/omni/SKILLS.md +21 -0
- package/templates/omni/SPEC.md +11 -0
- package/templates/omni/STATE.md +7 -0
- package/templates/omni/TASKS.md +6 -0
- package/templates/omni/TESTS.md +17 -0
- package/templates/omni/research/README.md +3 -0
- package/templates/omni/specs/README.md +3 -0
- package/templates/omni/tasks/README.md +3 -0
- package/templates/pi/agents/omni-expert.md +13 -0
- package/templates/pi/agents/omni-worker.md +13 -0
package/src/sync.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface SyncRequest {
|
|
5
|
+
summary: string;
|
|
6
|
+
decisions?: string[];
|
|
7
|
+
nextHandoffNotes?: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function appendBullets(
|
|
11
|
+
filePath: string,
|
|
12
|
+
heading: string,
|
|
13
|
+
bullets: string[],
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
if (bullets.length === 0) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const content = await readFile(filePath, "utf8");
|
|
20
|
+
const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
21
|
+
const sectionRegex = new RegExp(
|
|
22
|
+
`(${escapedHeading}\\n\\n)([\\s\\S]*?)(?=\\n## |$)`,
|
|
23
|
+
"u",
|
|
24
|
+
);
|
|
25
|
+
const match = content.match(sectionRegex);
|
|
26
|
+
if (!match) {
|
|
27
|
+
await writeFile(
|
|
28
|
+
filePath,
|
|
29
|
+
`${content.trimEnd()}\n\n${heading}\n\n${bullets.map((bullet) => `- ${bullet}`).join("\n")}\n`,
|
|
30
|
+
"utf8",
|
|
31
|
+
);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const prefix = match[1];
|
|
36
|
+
const body = match[2].trimEnd();
|
|
37
|
+
const merged = [body, ...bullets.map((bullet) => `- ${bullet}`)]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join("\n");
|
|
40
|
+
await writeFile(
|
|
41
|
+
filePath,
|
|
42
|
+
content.replace(sectionRegex, `${prefix}${merged}\n`),
|
|
43
|
+
"utf8",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function syncOmniMemory(
|
|
48
|
+
rootDir: string,
|
|
49
|
+
request: SyncRequest,
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
const sessionPath = path.join(rootDir, ".omni", "SESSION-SUMMARY.md");
|
|
52
|
+
const decisionsPath = path.join(rootDir, ".omni", "DECISIONS.md");
|
|
53
|
+
|
|
54
|
+
await appendBullets(sessionPath, "## Recent progress", [request.summary]);
|
|
55
|
+
await appendBullets(
|
|
56
|
+
sessionPath,
|
|
57
|
+
"## Next handoff notes",
|
|
58
|
+
request.nextHandoffNotes ?? [],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (request.decisions && request.decisions.length > 0) {
|
|
62
|
+
const decisionLines = request.decisions.map(
|
|
63
|
+
(decision) =>
|
|
64
|
+
`Date: pending\n - Decision: ${decision}\n - Why: Captured during sync.\n - Impact: To be refined.`,
|
|
65
|
+
);
|
|
66
|
+
const content = await readFile(decisionsPath, "utf8");
|
|
67
|
+
const next = `${content.trimEnd()}\n${decisionLines.map((line) => `\n- ${line}`).join("\n")}\n`;
|
|
68
|
+
await writeFile(decisionsPath, next, "utf8");
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/tasks.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import type { TaskBrief, TaskStatus } from "./contracts.js";
|
|
4
|
+
|
|
5
|
+
export function escapeTaskTableCell(value: string): string {
|
|
6
|
+
return value.replaceAll("\\", "\\\\").replaceAll("|", "\\|");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function unescapeTaskTableCell(value: string): string {
|
|
10
|
+
return value.replace(/\\([\\|])/gu, "$1");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function splitMarkdownTableRow(row: string): string[] {
|
|
14
|
+
const columns: string[] = [];
|
|
15
|
+
let current = "";
|
|
16
|
+
let escaped = false;
|
|
17
|
+
|
|
18
|
+
for (const char of row) {
|
|
19
|
+
if (escaped) {
|
|
20
|
+
current += `\\${char}`;
|
|
21
|
+
escaped = false;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (char === "\\") {
|
|
26
|
+
escaped = true;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (char === "|") {
|
|
31
|
+
columns.push(current.trim());
|
|
32
|
+
current = "";
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
current += char;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (escaped) {
|
|
40
|
+
current += "\\";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
columns.push(current.trim());
|
|
44
|
+
return columns;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function parseTaskRow(row: string): TaskBrief | null {
|
|
48
|
+
const columns = splitMarkdownTableRow(row)
|
|
49
|
+
.slice(1, -1)
|
|
50
|
+
.map(unescapeTaskTableCell);
|
|
51
|
+
|
|
52
|
+
if (columns.length !== 6) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const [id, title, role, dependsOn, status, doneCriteria] = columns;
|
|
57
|
+
return {
|
|
58
|
+
id,
|
|
59
|
+
title,
|
|
60
|
+
objective: title,
|
|
61
|
+
contextFiles: [],
|
|
62
|
+
skills: [],
|
|
63
|
+
doneCriteria:
|
|
64
|
+
doneCriteria === "-"
|
|
65
|
+
? []
|
|
66
|
+
: doneCriteria
|
|
67
|
+
.split(";")
|
|
68
|
+
.map((item) => item.trim())
|
|
69
|
+
.filter(Boolean),
|
|
70
|
+
role: role === "expert" ? "expert" : "worker",
|
|
71
|
+
status: (status as TaskStatus) || "todo",
|
|
72
|
+
dependsOn:
|
|
73
|
+
dependsOn === "-"
|
|
74
|
+
? []
|
|
75
|
+
: dependsOn
|
|
76
|
+
.split(",")
|
|
77
|
+
.map((item) => item.trim())
|
|
78
|
+
.filter(Boolean),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function readTasks(taskPath: string): Promise<TaskBrief[]> {
|
|
83
|
+
const content = await readFile(taskPath, "utf8");
|
|
84
|
+
return content
|
|
85
|
+
.split("\n")
|
|
86
|
+
.filter((line) => line.startsWith("| T"))
|
|
87
|
+
.map(parseTaskRow)
|
|
88
|
+
.filter((task): task is TaskBrief => task !== null);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function renderTaskTable(tasks: TaskBrief[]): string {
|
|
92
|
+
const rows = tasks.map((task) => {
|
|
93
|
+
const dependsOn =
|
|
94
|
+
task.dependsOn.length > 0 ? task.dependsOn.join(", ") : "-";
|
|
95
|
+
const doneCriteria =
|
|
96
|
+
task.doneCriteria.length > 0 ? task.doneCriteria.join("; ") : "-";
|
|
97
|
+
return `| ${escapeTaskTableCell(task.id)} | ${escapeTaskTableCell(task.title)} | ${escapeTaskTableCell(task.role)} | ${escapeTaskTableCell(dependsOn)} | ${escapeTaskTableCell(task.status)} | ${escapeTaskTableCell(doneCriteria)} |`;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return `# Tasks
|
|
101
|
+
|
|
102
|
+
## Task slices
|
|
103
|
+
|
|
104
|
+
| ID | Title | Role | Depends On | Status | Done Criteria |
|
|
105
|
+
| --- | --- | --- | --- | --- | --- |
|
|
106
|
+
${rows.join("\n")}
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function writeTasks(
|
|
111
|
+
taskPath: string,
|
|
112
|
+
tasks: TaskBrief[],
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
await writeFile(taskPath, renderTaskTable(tasks), "utf8");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function findNextExecutableTask(tasks: TaskBrief[]): TaskBrief | null {
|
|
118
|
+
const doneIds = new Set(
|
|
119
|
+
tasks.filter((task) => task.status === "done").map((task) => task.id),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
for (const task of tasks) {
|
|
123
|
+
if (task.status !== "todo") {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (task.dependsOn.every((dependency) => doneIds.has(dependency))) {
|
|
128
|
+
return task;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function updateTaskStatus(
|
|
136
|
+
tasks: TaskBrief[],
|
|
137
|
+
taskId: string,
|
|
138
|
+
status: TaskStatus,
|
|
139
|
+
): TaskBrief[] {
|
|
140
|
+
return tasks.map((task) => (task.id === taskId ? { ...task, status } : task));
|
|
141
|
+
}
|
package/src/templates.ts
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { OMNI_DIR } from "./contracts.js";
|
|
2
|
+
|
|
3
|
+
export interface StarterFile {
|
|
4
|
+
path: string;
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const starterFiles: StarterFile[] = [
|
|
9
|
+
{
|
|
10
|
+
path: `${OMNI_DIR}/PROJECT.md`,
|
|
11
|
+
content: `# Project
|
|
12
|
+
|
|
13
|
+
## Goal
|
|
14
|
+
|
|
15
|
+
Describe what this project should achieve.
|
|
16
|
+
|
|
17
|
+
## Users
|
|
18
|
+
|
|
19
|
+
- Primary users:
|
|
20
|
+
- Secondary users:
|
|
21
|
+
|
|
22
|
+
## Constraints
|
|
23
|
+
|
|
24
|
+
- Technical constraints:
|
|
25
|
+
- Product constraints:
|
|
26
|
+
|
|
27
|
+
## Success Criteria
|
|
28
|
+
|
|
29
|
+
- What does success look like?
|
|
30
|
+
`,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
path: `${OMNI_DIR}/IDEAS.md`,
|
|
34
|
+
content: `# Ideas
|
|
35
|
+
|
|
36
|
+
## Active ideas
|
|
37
|
+
|
|
38
|
+
-
|
|
39
|
+
|
|
40
|
+
## Future ideas
|
|
41
|
+
|
|
42
|
+
-
|
|
43
|
+
|
|
44
|
+
## Parking lot
|
|
45
|
+
|
|
46
|
+
-
|
|
47
|
+
`,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
path: `${OMNI_DIR}/DECISIONS.md`,
|
|
51
|
+
content: `# Decisions
|
|
52
|
+
|
|
53
|
+
Record important choices here as the project evolves.
|
|
54
|
+
|
|
55
|
+
## Entries
|
|
56
|
+
|
|
57
|
+
- Date: YYYY-MM-DD
|
|
58
|
+
- Decision:
|
|
59
|
+
- Why:
|
|
60
|
+
- Impact:
|
|
61
|
+
`,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: `${OMNI_DIR}/STATE.md`,
|
|
65
|
+
content: `# State
|
|
66
|
+
|
|
67
|
+
Current Phase: Understand
|
|
68
|
+
Active Task: None
|
|
69
|
+
Status Summary: Project initialized. Ready to capture goals and constraints.
|
|
70
|
+
Blockers: None
|
|
71
|
+
Next Step: Run /omni-plan after the initial project details are captured.
|
|
72
|
+
`,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
path: `${OMNI_DIR}/SKILLS.md`,
|
|
76
|
+
content: `# Skills
|
|
77
|
+
|
|
78
|
+
## Installed
|
|
79
|
+
|
|
80
|
+
- None yet
|
|
81
|
+
|
|
82
|
+
## Recommended
|
|
83
|
+
|
|
84
|
+
- None yet
|
|
85
|
+
|
|
86
|
+
## Deferred
|
|
87
|
+
|
|
88
|
+
- None yet
|
|
89
|
+
|
|
90
|
+
## Rejected
|
|
91
|
+
|
|
92
|
+
- None yet
|
|
93
|
+
|
|
94
|
+
## Usage Notes
|
|
95
|
+
|
|
96
|
+
- Record why a skill was installed, recommended, or skipped.
|
|
97
|
+
`,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
path: `${OMNI_DIR}/SPEC.md`,
|
|
101
|
+
content: `# Spec
|
|
102
|
+
|
|
103
|
+
## Problem
|
|
104
|
+
|
|
105
|
+
## Solution shape
|
|
106
|
+
|
|
107
|
+
## Key workflows
|
|
108
|
+
|
|
109
|
+
## Risks
|
|
110
|
+
|
|
111
|
+
## Open questions
|
|
112
|
+
`,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
path: `${OMNI_DIR}/TASKS.md`,
|
|
116
|
+
content: `# Tasks
|
|
117
|
+
|
|
118
|
+
## Task slices
|
|
119
|
+
|
|
120
|
+
| ID | Title | Role | Depends On | Status | Done Criteria |
|
|
121
|
+
| --- | --- | --- | --- | --- | --- |
|
|
122
|
+
`,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
path: `${OMNI_DIR}/TESTS.md`,
|
|
126
|
+
content: `# Tests
|
|
127
|
+
|
|
128
|
+
## Project-wide checks
|
|
129
|
+
|
|
130
|
+
-
|
|
131
|
+
|
|
132
|
+
## Task-specific checks
|
|
133
|
+
|
|
134
|
+
-
|
|
135
|
+
|
|
136
|
+
## Retry policy
|
|
137
|
+
|
|
138
|
+
- Worker retries before expert takeover: 2
|
|
139
|
+
|
|
140
|
+
## Escalation threshold
|
|
141
|
+
|
|
142
|
+
- Escalate after repeated failures or when the planner marks the task as high-risk.
|
|
143
|
+
`,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
path: `${OMNI_DIR}/SESSION-SUMMARY.md`,
|
|
147
|
+
content: `# Session Summary
|
|
148
|
+
|
|
149
|
+
## Current understanding
|
|
150
|
+
|
|
151
|
+
-
|
|
152
|
+
|
|
153
|
+
## Recent progress
|
|
154
|
+
|
|
155
|
+
-
|
|
156
|
+
|
|
157
|
+
## Next handoff notes
|
|
158
|
+
|
|
159
|
+
-
|
|
160
|
+
`,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
path: `${OMNI_DIR}/PROGRESS.md`,
|
|
164
|
+
content: `# Progress
|
|
165
|
+
|
|
166
|
+
Ongoing log of project progress.
|
|
167
|
+
|
|
168
|
+
`,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
path: `${OMNI_DIR}/plans/INDEX.md`,
|
|
172
|
+
content: `# Plan Index
|
|
173
|
+
|
|
174
|
+
| ID | Title | Status | Created | Completed |
|
|
175
|
+
| --- | --- | --- | --- | --- |
|
|
176
|
+
`,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
path: `${OMNI_DIR}/research/README.md`,
|
|
180
|
+
content: `# Research
|
|
181
|
+
|
|
182
|
+
Store external research summaries and package notes here.
|
|
183
|
+
`,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
path: `${OMNI_DIR}/specs/README.md`,
|
|
187
|
+
content: `# Specs
|
|
188
|
+
|
|
189
|
+
Store versioned detailed specs here.
|
|
190
|
+
`,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
path: `${OMNI_DIR}/tasks/README.md`,
|
|
194
|
+
content: `# Task Artifacts
|
|
195
|
+
|
|
196
|
+
Store per-task briefs, outputs, and failure histories here.
|
|
197
|
+
`,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
path: `.pi/agents/omni-worker.md`,
|
|
201
|
+
content: `---
|
|
202
|
+
name: omni-worker
|
|
203
|
+
description: Omni-Pi worker for bounded implementation tasks
|
|
204
|
+
model: anthropic/claude-sonnet-4-5
|
|
205
|
+
tools: read, grep, find, ls, bash, edit, write
|
|
206
|
+
skill: omni-execution, omni-verification
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
You are Omni-Pi's worker subagent.
|
|
210
|
+
|
|
211
|
+
Complete the assigned task directly, keep the scope tight, run the required checks when possible, and end with JSON only using this schema:
|
|
212
|
+
|
|
213
|
+
{"summary":"...","verification":{"passed":true,"checksRun":["..."],"failureSummary":[],"retryRecommended":false}}
|
|
214
|
+
`,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
path: `.pi/agents/omni-planner.md`,
|
|
218
|
+
content: `---
|
|
219
|
+
name: omni-planner
|
|
220
|
+
description: Omni-Pi planner for spec decomposition and task breakdown
|
|
221
|
+
model: openai/gpt-5.4
|
|
222
|
+
tools: read, grep, find, ls
|
|
223
|
+
skill: omni-planning
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
You are Omni-Pi's planner subagent.
|
|
227
|
+
|
|
228
|
+
Analyze the project context, produce or refine .omni/SPEC.md, break work into bounded task slices in .omni/TASKS.md, and define verification criteria in .omni/TESTS.md. Keep tasks small enough that a single worker session can complete each one.
|
|
229
|
+
`,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
path: `.pi/agents/omni-brain.md`,
|
|
233
|
+
content: `---
|
|
234
|
+
name: omni-brain
|
|
235
|
+
description: Omni-Pi brain for user-facing orchestration and progress updates
|
|
236
|
+
model: anthropic/claude-opus-4-6
|
|
237
|
+
tools: read, grep, find, ls, bash
|
|
238
|
+
skill: omni-planning, omni-execution, omni-verification
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
You are Omni-Pi's brain — the user-facing orchestrator. Speak in plain English, give progress updates, decide when to invoke the planner, worker, or expert, and keep .omni/STATE.md current.
|
|
242
|
+
`,
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
path: `.pi/agents/omni-expert.md`,
|
|
246
|
+
content: `---
|
|
247
|
+
name: omni-expert
|
|
248
|
+
description: Omni-Pi expert for escalated implementation tasks
|
|
249
|
+
model: anthropic/claude-opus-4-1
|
|
250
|
+
tools: read, grep, find, ls, bash, edit, write
|
|
251
|
+
skill: omni-escalation, omni-verification
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
You are Omni-Pi's expert subagent.
|
|
255
|
+
|
|
256
|
+
Take over difficult or repeatedly failing tasks, fix the root cause, run the required checks when possible, and end with JSON only using this schema:
|
|
257
|
+
|
|
258
|
+
{"summary":"...","verification":{"passed":true,"checksRun":["..."],"failureSummary":[],"retryRecommended":false}}
|
|
259
|
+
`,
|
|
260
|
+
},
|
|
261
|
+
];
|