@dotdotgod/pi 0.1.21
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/LICENSE +94 -0
- package/README.md +120 -0
- package/extensions/context-metrics/utils.ts +66 -0
- package/extensions/load-project/README.md +44 -0
- package/extensions/load-project/index.ts +76 -0
- package/extensions/load-project/utils.ts +338 -0
- package/extensions/plan-mode/README.md +65 -0
- package/extensions/plan-mode/index.ts +830 -0
- package/extensions/plan-mode/utils.ts +747 -0
- package/package.json +65 -0
- package/skills/project-initializer/SKILL.md +66 -0
- package/skills/project-initializer/agents/openai.yaml +4 -0
- package/skills/project-initializer/references/agent-docs.md +25 -0
- package/skills/project-initializer/scripts/init_project.sh +344 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure utility functions for project memory loading.
|
|
3
|
+
* Extracted for testability.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
8
|
+
import { join, relative } from "node:path";
|
|
9
|
+
|
|
10
|
+
export const MARKER_FILES = [
|
|
11
|
+
"AGENTS.md",
|
|
12
|
+
"CLAUDE.md",
|
|
13
|
+
"CODEX.md",
|
|
14
|
+
"README.md",
|
|
15
|
+
"docs/README.md",
|
|
16
|
+
"docs/spec/README.md",
|
|
17
|
+
"docs/test/README.md",
|
|
18
|
+
"docs/arch/README.md",
|
|
19
|
+
"docs/plan/README.md",
|
|
20
|
+
"docs/archive/README.md",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export const MEMORY_DIRECTORIES = ["docs/spec", "docs/test", "docs/arch", "docs/plan"];
|
|
24
|
+
|
|
25
|
+
export interface ProjectMemorySnapshot {
|
|
26
|
+
present: string[];
|
|
27
|
+
missing: string[];
|
|
28
|
+
directories: Array<{ path: string; exists: boolean; markdownFiles: string[] }>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LoadCommandInfo {
|
|
32
|
+
name: string;
|
|
33
|
+
sourceInfo?: { path?: string };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LoadSnapshotRunResult {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
command?: string;
|
|
39
|
+
data?: unknown;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface LoadSnapshotLike {
|
|
44
|
+
commandGuidance?: {
|
|
45
|
+
source?: string;
|
|
46
|
+
packageManager?: string;
|
|
47
|
+
install?: string | null;
|
|
48
|
+
validate?: string;
|
|
49
|
+
loadSnapshot?: string;
|
|
50
|
+
index?: string;
|
|
51
|
+
status?: string;
|
|
52
|
+
verify?: string | null;
|
|
53
|
+
};
|
|
54
|
+
cache?: {
|
|
55
|
+
status?: string;
|
|
56
|
+
ok?: boolean;
|
|
57
|
+
indexedFiles?: number;
|
|
58
|
+
currentFiles?: number;
|
|
59
|
+
staleFiles?: number;
|
|
60
|
+
archiveBodiesIncluded?: boolean;
|
|
61
|
+
graph?: { nodes?: number; edges?: number };
|
|
62
|
+
};
|
|
63
|
+
metadata?: {
|
|
64
|
+
cacheRefreshed?: boolean;
|
|
65
|
+
previousStatus?: string;
|
|
66
|
+
changedFiles?: number;
|
|
67
|
+
fullRebuild?: boolean;
|
|
68
|
+
indexedFiles?: number;
|
|
69
|
+
indexSizeBytes?: number;
|
|
70
|
+
archiveBodiesIncluded?: boolean;
|
|
71
|
+
};
|
|
72
|
+
graph?: {
|
|
73
|
+
nodes?: number;
|
|
74
|
+
edges?: number;
|
|
75
|
+
byType?: Record<string, number>;
|
|
76
|
+
};
|
|
77
|
+
memoryAreas?: {
|
|
78
|
+
areas?: Array<{
|
|
79
|
+
area?: string;
|
|
80
|
+
label?: string;
|
|
81
|
+
role?: string;
|
|
82
|
+
priority?: number;
|
|
83
|
+
files?: string[];
|
|
84
|
+
count?: number;
|
|
85
|
+
omitted?: number;
|
|
86
|
+
}>;
|
|
87
|
+
total?: number;
|
|
88
|
+
method?: string;
|
|
89
|
+
};
|
|
90
|
+
communities?: {
|
|
91
|
+
communities?: Array<{
|
|
92
|
+
id?: string;
|
|
93
|
+
label?: string;
|
|
94
|
+
files?: string[];
|
|
95
|
+
docs?: string[];
|
|
96
|
+
commands?: string[];
|
|
97
|
+
events?: string[];
|
|
98
|
+
tests?: string[];
|
|
99
|
+
nodeCount?: number;
|
|
100
|
+
edgeCount?: number;
|
|
101
|
+
omitted?: number;
|
|
102
|
+
}>;
|
|
103
|
+
omitted?: number;
|
|
104
|
+
total?: number;
|
|
105
|
+
method?: string;
|
|
106
|
+
fallback?: boolean;
|
|
107
|
+
};
|
|
108
|
+
bounds?: { fullGraphIncluded?: boolean };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function estimateTextMetrics(text: string): { characters: number; words: number; approxTokens: number } {
|
|
112
|
+
const trimmed = text.trim();
|
|
113
|
+
return {
|
|
114
|
+
characters: text.length,
|
|
115
|
+
words: trimmed ? trimmed.split(/\s+/).length : 0,
|
|
116
|
+
approxTokens: Math.ceil(text.length / 4),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function pathExists(cwd: string, path: string): boolean {
|
|
121
|
+
return existsSync(join(cwd, path));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function listMarkdownFiles(cwd: string, directory: string, limit = 20): string[] {
|
|
125
|
+
const root = join(cwd, directory);
|
|
126
|
+
if (!existsSync(root)) return [];
|
|
127
|
+
|
|
128
|
+
const results: string[] = [];
|
|
129
|
+
const walk = (current: string): void => {
|
|
130
|
+
if (results.length >= limit) return;
|
|
131
|
+
let entries;
|
|
132
|
+
try {
|
|
133
|
+
entries = readdirSync(current, { withFileTypes: true, encoding: "utf8" });
|
|
134
|
+
} catch {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
if (results.length >= limit) return;
|
|
140
|
+
const absolute = join(current, entry.name);
|
|
141
|
+
if (entry.isDirectory()) {
|
|
142
|
+
walk(absolute);
|
|
143
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
144
|
+
results.push(relative(cwd, absolute));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
walk(root);
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function collectSnapshot(cwd: string): ProjectMemorySnapshot {
|
|
154
|
+
const present = MARKER_FILES.filter((file) => pathExists(cwd, file));
|
|
155
|
+
const missing = MARKER_FILES.filter((file) => !pathExists(cwd, file));
|
|
156
|
+
const directories = MEMORY_DIRECTORIES.map((directory) => ({
|
|
157
|
+
path: directory,
|
|
158
|
+
exists: pathExists(cwd, directory),
|
|
159
|
+
markdownFiles: listMarkdownFiles(cwd, directory),
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
return { present, missing, directories };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function hasOtherLoadCommand(commands: readonly LoadCommandInfo[]): boolean {
|
|
166
|
+
return commands.some((command) => {
|
|
167
|
+
if (command.name !== "load" && !/^load:\d+$/.test(command.name)) return false;
|
|
168
|
+
const sourcePath = command.sourceInfo?.path ?? "";
|
|
169
|
+
return !sourcePath.includes("extensions/load-project");
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseLoadSnapshotJson(stdout: string): unknown {
|
|
174
|
+
const trimmed = stdout.trim();
|
|
175
|
+
if (!trimmed) throw new Error("load-snapshot returned empty output");
|
|
176
|
+
return JSON.parse(trimmed) as unknown;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function runDotdotgodLoadSnapshot(cwd: string, timeoutMs = 10_000): LoadSnapshotRunResult {
|
|
180
|
+
const localCli = join(cwd, "packages/cli/bin/dotdotgod.mjs");
|
|
181
|
+
const candidates = existsSync(localCli)
|
|
182
|
+
? [
|
|
183
|
+
{ command: process.execPath, args: [localCli, "load-snapshot", cwd, "--json"], label: "local workspace CLI" },
|
|
184
|
+
{ command: "dotdotgod", args: ["load-snapshot", cwd, "--json"], label: "dotdotgod" },
|
|
185
|
+
]
|
|
186
|
+
: [{ command: "dotdotgod", args: ["load-snapshot", cwd, "--json"], label: "dotdotgod" }];
|
|
187
|
+
|
|
188
|
+
const errors: string[] = [];
|
|
189
|
+
for (const candidate of candidates) {
|
|
190
|
+
try {
|
|
191
|
+
const stdout = execFileSync(candidate.command, candidate.args, {
|
|
192
|
+
cwd,
|
|
193
|
+
encoding: "utf8",
|
|
194
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
195
|
+
timeout: timeoutMs,
|
|
196
|
+
maxBuffer: 1024 * 1024,
|
|
197
|
+
});
|
|
198
|
+
return { ok: true, command: candidate.label, data: parseLoadSnapshotJson(stdout) };
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
errors.push(`${candidate.label}: ${message}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { ok: false, error: errors.join("; ") || "dotdotgod load-snapshot failed" };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function asLoadSnapshot(value: unknown): LoadSnapshotLike | undefined {
|
|
209
|
+
return value && typeof value === "object" ? (value as LoadSnapshotLike) : undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function formatCount(value: unknown): string {
|
|
213
|
+
return typeof value === "number" ? value.toLocaleString() : "unknown";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function formatTopTypes(byType: Record<string, number> | undefined, limit = 6): string {
|
|
217
|
+
if (!byType) return "unknown";
|
|
218
|
+
const entries = Object.entries(byType)
|
|
219
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
220
|
+
.slice(0, limit);
|
|
221
|
+
return entries.length > 0 ? entries.map(([type, count]) => `${type}:${count}`).join(", ") : "none";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function truncateList(items: string[] | undefined, limit: number): string {
|
|
225
|
+
if (!items || items.length === 0) return "none";
|
|
226
|
+
const shown = items.slice(0, limit);
|
|
227
|
+
const suffix = items.length > limit ? `, +${items.length - limit} more` : "";
|
|
228
|
+
return `${shown.join(", ")}${suffix}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function formatLoadSnapshotSummary(result: LoadSnapshotRunResult, communityLimit = 5): string {
|
|
232
|
+
if (!result.ok) {
|
|
233
|
+
return `Load snapshot: unavailable; using lightweight fallback snapshot. Reason: ${result.error ?? "unknown error"}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const snapshot = asLoadSnapshot(result.data);
|
|
237
|
+
if (!snapshot) return "Load snapshot: unavailable; using lightweight fallback snapshot. Reason: invalid snapshot shape";
|
|
238
|
+
|
|
239
|
+
const cache = snapshot.cache;
|
|
240
|
+
const metadata = snapshot.metadata;
|
|
241
|
+
const graph = snapshot.graph ?? cache?.graph;
|
|
242
|
+
const memoryAreas = snapshot.memoryAreas;
|
|
243
|
+
const communities = snapshot.communities;
|
|
244
|
+
const lines = [
|
|
245
|
+
"Load snapshot:",
|
|
246
|
+
`- Source: ${result.command ?? "dotdotgod load-snapshot"}`,
|
|
247
|
+
`- Cache: status=${cache?.status ?? "unknown"}, ok=${String(cache?.ok ?? "unknown")}, indexedFiles=${formatCount(cache?.indexedFiles)}, staleFiles=${formatCount(cache?.staleFiles)}, archiveBodiesIncluded=${String(cache?.archiveBodiesIncluded ?? metadata?.archiveBodiesIncluded ?? "unknown")}`,
|
|
248
|
+
`- Refresh: cacheRefreshed=${String(metadata?.cacheRefreshed ?? false)}, previousStatus=${metadata?.previousStatus ?? "none"}, changedFiles=${formatCount(metadata?.changedFiles)}, fullRebuild=${String(metadata?.fullRebuild ?? false)}`,
|
|
249
|
+
`- Graph: nodes=${formatCount(graph?.nodes)}, edges=${formatCount(graph?.edges)}, topTypes=${formatTopTypes(snapshot.graph?.byType)}`,
|
|
250
|
+
`- Memory areas: method=${memoryAreas?.method ?? "unknown"}, shown=${formatCount(memoryAreas?.areas?.length)}, total=${formatCount(memoryAreas?.total)}`,
|
|
251
|
+
`- Communities: method=${communities?.method ?? "unknown"}, fallback=${String(communities?.fallback ?? "unknown")}, shown=${formatCount(communities?.communities?.slice(0, communityLimit).length)}, total=${formatCount(communities?.total)}, omitted=${formatCount(communities?.omitted)}`,
|
|
252
|
+
`- Bounds: fullGraphIncluded=${String(snapshot.bounds?.fullGraphIncluded ?? false)}`,
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
const guidance = snapshot.commandGuidance;
|
|
256
|
+
if (guidance) {
|
|
257
|
+
lines.push(
|
|
258
|
+
`- Commands: source=${guidance.source ?? "unknown"}, packageManager=${guidance.packageManager ?? "unknown"}, validate=${guidance.validate ?? "unknown"}, loadSnapshot=${guidance.loadSnapshot ?? "unknown"}, index=${guidance.index ?? "unknown"}, status=${guidance.status ?? "unknown"}`,
|
|
259
|
+
);
|
|
260
|
+
if (guidance.install) lines.push(` - Install CLI if needed: ${guidance.install}`);
|
|
261
|
+
if (guidance.verify) lines.push(` - Verify: ${guidance.verify}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const area of memoryAreas?.areas?.slice(0, communityLimit) ?? []) {
|
|
265
|
+
lines.push(
|
|
266
|
+
` - ${area.label ?? area.area ?? "memory area"}: role=${area.role ?? "unknown"}; priority=${formatCount(area.priority)}; files=${truncateList(area.files, 3)}; omitted=${formatCount(area.omitted)}`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const community of communities?.communities?.slice(0, communityLimit) ?? []) {
|
|
271
|
+
lines.push(
|
|
272
|
+
` - ${community.label ?? community.id ?? "community"}: files=${truncateList(community.files, 3)}; docs=${truncateList(community.docs, 3)}; commands=${truncateList(community.commands, 4)}; events=${truncateList(community.events, 4)}; tests=${truncateList(community.tests, 3)}; omitted=${formatCount(community.omitted)}`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return lines.join("\n");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function buildLoadPrompt(
|
|
280
|
+
cwd: string,
|
|
281
|
+
args: string,
|
|
282
|
+
snapshot: ProjectMemorySnapshot,
|
|
283
|
+
loadSnapshot?: LoadSnapshotRunResult,
|
|
284
|
+
): string {
|
|
285
|
+
const present = snapshot.present.length > 0 ? snapshot.present.map((file) => `- ${file}`).join("\n") : "- none";
|
|
286
|
+
const missing = snapshot.missing.length > 0 ? snapshot.missing.map((file) => `- ${file}`).join("\n") : "- none";
|
|
287
|
+
const hasLoadSnapshot = loadSnapshot?.ok === true;
|
|
288
|
+
const directorySummary = hasLoadSnapshot
|
|
289
|
+
? snapshot.directories
|
|
290
|
+
.map((directory) => {
|
|
291
|
+
if (!directory.exists) return `- ${directory.path}: missing`;
|
|
292
|
+
return `- ${directory.path}: available; follow its README.md only if relevant`;
|
|
293
|
+
})
|
|
294
|
+
.join("\n")
|
|
295
|
+
: snapshot.directories
|
|
296
|
+
.map((directory) => {
|
|
297
|
+
if (!directory.exists) return `- ${directory.path}: missing`;
|
|
298
|
+
if (directory.markdownFiles.length === 0) return `- ${directory.path}: no markdown files`;
|
|
299
|
+
return `- ${directory.path}:\n${directory.markdownFiles.map((file) => ` - ${file}`).join("\n")}`;
|
|
300
|
+
})
|
|
301
|
+
.join("\n");
|
|
302
|
+
|
|
303
|
+
const mode = args.trim() ? `\nUser arguments: ${args.trim()}\n` : "";
|
|
304
|
+
const loadSnapshotText = loadSnapshot ? `\n${formatLoadSnapshotSummary(loadSnapshot)}\n` : "";
|
|
305
|
+
|
|
306
|
+
return `Load the dotdotgod project memory.${mode}
|
|
307
|
+
Current working directory: ${cwd}
|
|
308
|
+
${loadSnapshotText}
|
|
309
|
+
Detected memory files:
|
|
310
|
+
${present}
|
|
311
|
+
|
|
312
|
+
Missing baseline files:
|
|
313
|
+
${missing}
|
|
314
|
+
|
|
315
|
+
Documentation directory summary:
|
|
316
|
+
${directorySummary}
|
|
317
|
+
|
|
318
|
+
Instructions:
|
|
319
|
+
1. Use the Load snapshot section first when present. Treat it as the bounded project-memory map for cache status, graph size, related communities, and archive inclusion policy.
|
|
320
|
+
2. Use only read-only tools such as read, ls, grep, and find to inspect project memory files.
|
|
321
|
+
3. Start with AGENTS.md, README.md, and docs/README.md only when they are not already clear from the loaded context.
|
|
322
|
+
4. Inspect docs/spec, docs/arch, and docs/test selectively based on the user request, the load snapshot communities, and README indexes. Do not re-scan every listed file unless the task needs a full refresh.
|
|
323
|
+
5. Follow README.md indexes, including domain directories such as docs/<area>/<domain>/README.md and expanded convention directories such as docs/arch/conventions/README.md.
|
|
324
|
+
6. For docs/plan, list entries first and selectively read only the relevant README.md or markdown files.
|
|
325
|
+
7. For docs/archive, do not scan it as part of the documentation directory summary. Use docs/archive/README.md as the history map, and use targeted archive paths only when the user request or current task makes completed plans/reports relevant.
|
|
326
|
+
8. Summarize the result concisely.
|
|
327
|
+
|
|
328
|
+
Response format:
|
|
329
|
+
- Project summary
|
|
330
|
+
- Key working rules
|
|
331
|
+
- Available commands and verification methods
|
|
332
|
+
- Documentation map
|
|
333
|
+
- Active plans
|
|
334
|
+
- Relevant archive notes
|
|
335
|
+
- Open TODO/TBD items or questions to clarify
|
|
336
|
+
|
|
337
|
+
Do not modify files. Only load and summarize project memory.`;
|
|
338
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Plan Mode Extension
|
|
2
|
+
|
|
3
|
+
A customized planning mode for Pi. Source changes are blocked during planning, while markdown plan/archive files under `docs/plan/` and `docs/archive/` may be created or updated.
|
|
4
|
+
|
|
5
|
+
## Changes
|
|
6
|
+
|
|
7
|
+
- Plan progress uses the `/todos` command.
|
|
8
|
+
- Only the `Plan:` heading is parsed for step extraction.
|
|
9
|
+
- Plan mode can use `pi-web-access` tools when installed:
|
|
10
|
+
- `web_search`
|
|
11
|
+
- `code_search`
|
|
12
|
+
- `fetch_content`
|
|
13
|
+
- `get_search_content`
|
|
14
|
+
- The planning prompt stays generic across project types.
|
|
15
|
+
- If the session is long or noisy, Plan Mode automatically requests planning-focused compaction with `customInstructions` that preserve decisions, active plan status, relevant docs, verification results, risks, next steps, and `[DONE:n]` markers.
|
|
16
|
+
- Active plan tasks are managed as kebab-case directories under `docs/plan/<task-slug>/` for projects initialized with `project-initializer`.
|
|
17
|
+
- Under `docs/`, all directories use kebab-case and all markdown file names use UPPER_SNAKE_CASE, including `README.md`.
|
|
18
|
+
- Each task directory keeps its overview and index in `README.md`; supporting plan files such as `RESEARCH_NOTES.md` or `VERIFICATION.md` live alongside it.
|
|
19
|
+
- Plan mode does not render saved-plan file previews in the TUI; users review the durable markdown plan file when needed.
|
|
20
|
+
- Execute/stay/refine choices are shown after every active plan markdown file under `docs/plan/` is created or updated.
|
|
21
|
+
- When the latest planning request contains explicit `[[...]]` refs, Plan Mode adds bounded `dotdotgod expand` results to planning context before broad search.
|
|
22
|
+
- When the request contains high-signal natural refs such as `PLAN_MODE`, path-like mentions, or quoted doc names, Plan Mode may add bounded `dotdotgod expand --fuzzy` results before broad search; fuzzy low-signal suppression follows the resolved dotdotgod CLI config.
|
|
23
|
+
- Completed task directories should be moved to `docs/archive/plan/<task-slug>/` after execution and verification.
|
|
24
|
+
- Plans are encouraged to include target files, risks, and verification steps.
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
- `/plan` - Toggle plan mode
|
|
29
|
+
- `/todos` - Show current plan progress
|
|
30
|
+
- `Ctrl+Alt+P` - Toggle plan mode
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
1. Enable plan mode with `/plan`.
|
|
35
|
+
2. Ask the agent to analyze the task and create a plan.
|
|
36
|
+
3. The agent should create or update a focused kebab-case task directory under `docs/plan/<task-slug>/`.
|
|
37
|
+
4. The task overview, index, scope, and status belong in `docs/plan/<task-slug>/README.md`.
|
|
38
|
+
5. Supporting research, checklists, or verification notes can be added as UPPER_SNAKE_CASE markdown files in the same directory.
|
|
39
|
+
6. If the session is long or noisy, Plan Mode automatically compacts with planning-focused instructions before continuing.
|
|
40
|
+
7. After the agent creates or updates a plan file, Pi asks whether to execute, stay in plan mode, or refine the plan.
|
|
41
|
+
8. The agent should write concrete executable steps in the final `Plan:` section. Generic section labels such as `Target files and rationale`, `Implementation steps`, and `Verification method` are ignored for todo extraction.
|
|
42
|
+
9. Choose `Execute the plan` in the UI to switch into implementation mode.
|
|
43
|
+
10. During execution, the agent must mark every completed step in the same response with `[DONE:n]` tags.
|
|
44
|
+
11. After implementation and verification, the agent moves the completed task directory to `docs/archive/plan/<task-slug>/` and includes that step's `[DONE:n]` tag.
|
|
45
|
+
12. Use `/todos` to inspect progress.
|
|
46
|
+
|
|
47
|
+
## Plan Mode Restrictions
|
|
48
|
+
|
|
49
|
+
Allowed:
|
|
50
|
+
|
|
51
|
+
- File/code reading: `read`, `grep`, `find`, `ls`
|
|
52
|
+
- Plan/archive markdown updates under `docs/plan/` and `docs/archive/`: `edit`, `write`
|
|
53
|
+
- Directory names under `docs/` must be kebab-case; markdown file names must be UPPER_SNAKE_CASE.md
|
|
54
|
+
- Read-only bash commands: `rg`, `git status`, `git diff`, `yarn info`, `npm view`, etc.
|
|
55
|
+
- Bounded dotdotgod context/status commands: `dotdotgod status ...`, `dotdotgod load-snapshot ...`, `dotdotgod resolve ...`, `dotdotgod expand ...`, `dotdotgod graph impact ...`, `dotdotgod graph communities ...`, `dotdotgod config ...`, and `dotdotgod index ...`.
|
|
56
|
+
- Plan/archive housekeeping bash commands when every affected path stays under `docs/plan/` or `docs/archive/`: `mkdir -p docs/archive/plan`, `mv docs/plan/<task-slug> docs/archive/plan/<task-slug>`, `rm -r docs/plan/<task-slug>`
|
|
57
|
+
- Web/document research: `web_search`, `code_search`, `fetch_content`, `get_search_content`
|
|
58
|
+
|
|
59
|
+
Blocked:
|
|
60
|
+
|
|
61
|
+
- `edit`, `write` outside `docs/plan/` and `docs/archive/`
|
|
62
|
+
- `rm`, `mv`, `cp`, `mkdir`, `touch` outside the constrained plan/archive housekeeping allowance
|
|
63
|
+
- `git add`, `git commit`, `git push`, `git reset`, etc.
|
|
64
|
+
- `npm install`, `yarn add`, `pnpm add`, etc.
|
|
65
|
+
- `sudo`, `kill`, editor launches, etc.
|