@docyrus/docyrus 0.0.45 → 0.0.48
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/agent-loader.js +1 -1
- package/agent-loader.js.map +2 -2
- package/main.js +111 -20
- package/main.js.map +3 -3
- package/package.json +1 -1
- package/resources/pi-agent/extensions/knowledge.ts +0 -153
- package/resources/pi-agent/extensions/server-auto-commit.ts +105 -0
- package/resources/pi-agent/extensions/tasks.ts +0 -226
- package/resources/pi-agent/prompts/agent-system.md +10 -5
- package/resources/pi-agent/prompts/coder-system.md +9 -8
- package/server-loader.js +207 -137
- package/server-loader.js.map +4 -4
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve as resolvePath } from "node:path";
|
|
4
|
-
import { Type } from "@sinclair/typebox";
|
|
5
4
|
import type {
|
|
6
5
|
ExtensionAPI,
|
|
7
6
|
ExtensionCommandContext,
|
|
@@ -10,17 +9,14 @@ import type {
|
|
|
10
9
|
TurnEndEvent,
|
|
11
10
|
} from "@mariozechner/pi-coding-agent";
|
|
12
11
|
import { getMarkdownTheme, keyHint } from "@mariozechner/pi-coding-agent";
|
|
13
|
-
import type { Theme } from "@mariozechner/pi-tui";
|
|
14
12
|
import { Box, Markdown, Text } from "@mariozechner/pi-tui";
|
|
15
13
|
|
|
16
|
-
const PREVIEW_LINES = 4;
|
|
17
14
|
const KNOWLEDGE_STATE_TYPE = "knowledge-session";
|
|
18
15
|
const KNOWLEDGE_WIDGET_KEY = "knowledge";
|
|
19
16
|
const KNOWLEDGE_REMINDER_INTERVAL_MS = 10 * 60 * 1000;
|
|
20
17
|
|
|
21
18
|
type Scope = "local" | "global";
|
|
22
19
|
type KnowledgeSeverity = "ok" | "watch" | "drift" | "critical";
|
|
23
|
-
type KnowledgeToolName = "read" | "write" | "edit";
|
|
24
20
|
|
|
25
21
|
interface ICliEnvironment {
|
|
26
22
|
executable: string;
|
|
@@ -156,34 +152,6 @@ async function runCliJson<TValue>(environment: ICliEnvironment, args: string[],
|
|
|
156
152
|
};
|
|
157
153
|
}
|
|
158
154
|
|
|
159
|
-
function collapsibleResult(
|
|
160
|
-
result: { content: Array<{ type: string; text?: string }> },
|
|
161
|
-
options: { expanded: boolean; isPartial: boolean },
|
|
162
|
-
theme: Theme,
|
|
163
|
-
) {
|
|
164
|
-
const text = result.content?.[0]?.type === "text" ? (result.content[0] as { type: "text"; text: string }).text : "";
|
|
165
|
-
if (!text) {
|
|
166
|
-
return new Text(theme.fg("dim", "(empty)"), 0, 0);
|
|
167
|
-
}
|
|
168
|
-
if (options.isPartial) {
|
|
169
|
-
return new Text(theme.fg("dim", "…"), 0, 0);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const markdownTheme = getMarkdownTheme();
|
|
173
|
-
if (options.expanded) {
|
|
174
|
-
return new Markdown(text, 0, 0, markdownTheme);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const lines = text.split("\n");
|
|
178
|
-
if (lines.length <= PREVIEW_LINES) {
|
|
179
|
-
return new Markdown(text, 0, 0, markdownTheme);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const preview = lines.slice(0, PREVIEW_LINES).join("\n");
|
|
183
|
-
const remaining = lines.length - PREVIEW_LINES;
|
|
184
|
-
const hint = keyHint("expandTools", "to expand");
|
|
185
|
-
return new Text(`${preview}\n${theme.fg("dim", `… ${remaining} more lines (${hint})`)}`, 0, 0);
|
|
186
|
-
}
|
|
187
155
|
|
|
188
156
|
function normalizeTrackedPath(pathValue: string, cwd: string): string {
|
|
189
157
|
const trimmed = pathValue.trim();
|
|
@@ -419,36 +387,6 @@ function trackToolResult(pi: ExtensionAPI, ctx: ExtensionContext, event: ToolRes
|
|
|
419
387
|
updateKnowledgeWidget(ctx, next);
|
|
420
388
|
}
|
|
421
389
|
|
|
422
|
-
function registerKnowledgeTool(
|
|
423
|
-
pi: ExtensionAPI,
|
|
424
|
-
name: string,
|
|
425
|
-
label: string,
|
|
426
|
-
description: string,
|
|
427
|
-
argsBuilder: (params: Record<string, unknown>) => string[],
|
|
428
|
-
schema: object,
|
|
429
|
-
preview: (args: Record<string, unknown>, theme: Theme) => string,
|
|
430
|
-
) {
|
|
431
|
-
pi.registerTool({
|
|
432
|
-
name,
|
|
433
|
-
label,
|
|
434
|
-
description,
|
|
435
|
-
promptSnippet: description,
|
|
436
|
-
parameters: schema,
|
|
437
|
-
async execute(_id, params) {
|
|
438
|
-
const environment = readCliEnvironment();
|
|
439
|
-
const result = await runCli(environment, argsBuilder(params as Record<string, unknown>), process.cwd(), false);
|
|
440
|
-
const text = result.stdout.trim() || summarizeFailure(result);
|
|
441
|
-
return {
|
|
442
|
-
content: [{ type: "text", text }],
|
|
443
|
-
...(result.code && result.code !== 0 ? { isError: true } : {}),
|
|
444
|
-
};
|
|
445
|
-
},
|
|
446
|
-
renderCall(args, theme) {
|
|
447
|
-
return new Text(preview(args as Record<string, unknown>, theme), 0, 0);
|
|
448
|
-
},
|
|
449
|
-
renderResult: collapsibleResult,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
390
|
|
|
453
391
|
function parseCommandArgs(rawArgs: string): string | null {
|
|
454
392
|
const trimmed = rawArgs.trim();
|
|
@@ -456,97 +394,6 @@ function parseCommandArgs(rawArgs: string): string | null {
|
|
|
456
394
|
}
|
|
457
395
|
|
|
458
396
|
export default function(pi: ExtensionAPI) {
|
|
459
|
-
registerKnowledgeTool(
|
|
460
|
-
pi,
|
|
461
|
-
"docyrus_knowledge_search",
|
|
462
|
-
"knowledge search",
|
|
463
|
-
"Semantic search across docyrus/knowledge sections",
|
|
464
|
-
(params) => {
|
|
465
|
-
const args = ["knowledge", "search"];
|
|
466
|
-
if (typeof params.query === "string" && params.query.trim().length > 0) {
|
|
467
|
-
args.push(params.query);
|
|
468
|
-
}
|
|
469
|
-
if (typeof params.limit === "number") {
|
|
470
|
-
args.push("--limit", String(params.limit));
|
|
471
|
-
}
|
|
472
|
-
return args;
|
|
473
|
-
},
|
|
474
|
-
Type.Object({
|
|
475
|
-
query: Type.String({ description: "Search query in natural language" }),
|
|
476
|
-
limit: Type.Optional(Type.Number({ description: "Maximum matches", default: 5 })),
|
|
477
|
-
}),
|
|
478
|
-
(args, theme) => `${theme.fg("toolTitle", theme.bold("knowledge search "))}${theme.fg("dim", `"${String(args.query || "")}"`)}`,
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
registerKnowledgeTool(
|
|
482
|
-
pi,
|
|
483
|
-
"docyrus_knowledge_section",
|
|
484
|
-
"knowledge section",
|
|
485
|
-
"Show a full knowledge section with refs and backlinks",
|
|
486
|
-
(params) => ["knowledge", "section", String(params.query || "")],
|
|
487
|
-
Type.Object({
|
|
488
|
-
query: Type.String({ description: "Section id or heading query" }),
|
|
489
|
-
}),
|
|
490
|
-
(args, theme) => `${theme.fg("toolTitle", theme.bold("knowledge section "))}${theme.fg("dim", `"${String(args.query || "")}"`)}`,
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
registerKnowledgeTool(
|
|
494
|
-
pi,
|
|
495
|
-
"docyrus_knowledge_locate",
|
|
496
|
-
"knowledge locate",
|
|
497
|
-
"Find knowledge sections by exact, short, or fuzzy match",
|
|
498
|
-
(params) => ["knowledge", "locate", String(params.query || "")],
|
|
499
|
-
Type.Object({
|
|
500
|
-
query: Type.String({ description: "Section id or heading query" }),
|
|
501
|
-
}),
|
|
502
|
-
(args, theme) => `${theme.fg("toolTitle", theme.bold("knowledge locate "))}${theme.fg("dim", `"${String(args.query || "")}"`)}`,
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
registerKnowledgeTool(
|
|
506
|
-
pi,
|
|
507
|
-
"docyrus_knowledge_refs",
|
|
508
|
-
"knowledge refs",
|
|
509
|
-
"Find markdown and code references to a knowledge section or source symbol",
|
|
510
|
-
(params) => {
|
|
511
|
-
const args = ["knowledge", "refs", String(params.query || "")];
|
|
512
|
-
if (typeof params.scope === "string" && params.scope.length > 0) {
|
|
513
|
-
args.push("--scope", params.scope);
|
|
514
|
-
}
|
|
515
|
-
return args;
|
|
516
|
-
},
|
|
517
|
-
Type.Object({
|
|
518
|
-
query: Type.String({ description: "Section id, source file, or source symbol query" }),
|
|
519
|
-
scope: Type.Optional(Type.String({ description: "md, code, or md+code" })),
|
|
520
|
-
}),
|
|
521
|
-
(args, theme) => `${theme.fg("toolTitle", theme.bold("knowledge refs "))}${theme.fg("dim", `"${String(args.query || "")}"`)}`,
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
registerKnowledgeTool(
|
|
525
|
-
pi,
|
|
526
|
-
"docyrus_knowledge_expand",
|
|
527
|
-
"knowledge expand",
|
|
528
|
-
"Expand [[refs]] into resolved section context",
|
|
529
|
-
(params) => ["knowledge", "expand", String(params.text || "")],
|
|
530
|
-
Type.Object({
|
|
531
|
-
text: Type.String({ description: "Text containing [[refs]]" }),
|
|
532
|
-
}),
|
|
533
|
-
(args, theme) => {
|
|
534
|
-
const text = String(args.text || "");
|
|
535
|
-
const preview = text.length > 60 ? `${text.slice(0, 60)}…` : text;
|
|
536
|
-
return `${theme.fg("toolTitle", theme.bold("knowledge expand "))}${theme.fg("dim", `"${preview}"`)}`;
|
|
537
|
-
},
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
registerKnowledgeTool(
|
|
541
|
-
pi,
|
|
542
|
-
"docyrus_knowledge_check",
|
|
543
|
-
"knowledge check",
|
|
544
|
-
"Validate the repo knowledge graph",
|
|
545
|
-
() => ["knowledge", "check"],
|
|
546
|
-
Type.Object({}),
|
|
547
|
-
(_args, theme) => `${theme.fg("toolTitle", theme.bold("knowledge check"))}`,
|
|
548
|
-
);
|
|
549
|
-
|
|
550
397
|
pi.registerCommand("knowledge-init", {
|
|
551
398
|
description: "Bootstrap docyrus/knowledge from the current repo and install hook guidance",
|
|
552
399
|
handler: async(rawArgs, ctx: ExtensionCommandContext) => {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Auto-Commit Extension (server-only)
|
|
3
|
+
*
|
|
4
|
+
* Automatically commits and pushes all changes after each agent turn
|
|
5
|
+
* when auto-commit is enabled for the session.
|
|
6
|
+
*
|
|
7
|
+
* Enable at session creation:
|
|
8
|
+
* POST /api/sessions { "autoCommit": true }
|
|
9
|
+
*
|
|
10
|
+
* Toggle on an active session:
|
|
11
|
+
* PATCH /api/sessions/:sessionId/config { "autoCommit": true|false }
|
|
12
|
+
*
|
|
13
|
+
* Check current config:
|
|
14
|
+
* GET /api/sessions/:sessionId/config
|
|
15
|
+
*
|
|
16
|
+
* Config is stored at: <agentDir>/session-config/<sessionId>.json
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
20
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
21
|
+
import { promises as fs } from "node:fs";
|
|
22
|
+
import * as os from "node:os";
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
import { promisify } from "node:util";
|
|
25
|
+
|
|
26
|
+
const execFile = promisify(execFileCb);
|
|
27
|
+
|
|
28
|
+
interface IExecResult {
|
|
29
|
+
stdout: string;
|
|
30
|
+
stderr: string;
|
|
31
|
+
code: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runGit(args: string[], cwd: string): Promise<IExecResult> {
|
|
35
|
+
try {
|
|
36
|
+
const result = await execFile("git", args, { cwd });
|
|
37
|
+
return { stdout: result.stdout, stderr: result.stderr, code: 0 };
|
|
38
|
+
} catch (error: unknown) {
|
|
39
|
+
const err = error as { stdout?: string; stderr?: string; code?: number };
|
|
40
|
+
return {
|
|
41
|
+
stdout: err.stdout ?? "",
|
|
42
|
+
stderr: err.stderr ?? "",
|
|
43
|
+
code: err.code ?? 1,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function expandUserPath(p: string): string {
|
|
49
|
+
if (p === "~") {return os.homedir();}
|
|
50
|
+
if (p.startsWith("~/")) {return path.join(os.homedir(), p.slice(2));}
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getScopedAgentDir(): string {
|
|
55
|
+
for (const key of ["PI_CODING_AGENT_DIR", "TAU_CODING_AGENT_DIR"]) {
|
|
56
|
+
const value = process.env[key]?.trim();
|
|
57
|
+
if (value) {return expandUserPath(value);}
|
|
58
|
+
}
|
|
59
|
+
return path.join(os.homedir(), ".pi", "agent");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function isAutoCommitEnabled(sessionId: string): Promise<boolean> {
|
|
63
|
+
try {
|
|
64
|
+
const configFile = path.join(getScopedAgentDir(), "session-config", `${sessionId}.json`);
|
|
65
|
+
const raw = await fs.readFile(configFile, "utf-8");
|
|
66
|
+
return (JSON.parse(raw) as { autoCommit?: boolean }).autoCommit === true;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default function(_pi: ExtensionAPI) {
|
|
73
|
+
_pi.on("agent_end", async(_event, ctx) => {
|
|
74
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
75
|
+
if (!(await isAutoCommitEnabled(sessionId))) {return;}
|
|
76
|
+
|
|
77
|
+
const status = await runGit(["status", "--porcelain"], ctx.cwd);
|
|
78
|
+
if (status.code !== 0 || !status.stdout.trim()) {return;}
|
|
79
|
+
|
|
80
|
+
const timestamp = new Date().toISOString();
|
|
81
|
+
|
|
82
|
+
const add = await runGit(["add", "-A"], ctx.cwd);
|
|
83
|
+
if (add.code !== 0) {
|
|
84
|
+
process.stderr.write(`[server-auto-commit] git add failed: ${add.stderr}\n`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const commit = await runGit(
|
|
89
|
+
["commit", "--no-verify", "-m", `auto: agent turn ${timestamp}`],
|
|
90
|
+
ctx.cwd,
|
|
91
|
+
);
|
|
92
|
+
if (commit.code !== 0) {
|
|
93
|
+
process.stderr.write(`[server-auto-commit] git commit failed: ${commit.stderr}\n`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const push = await runGit(["push", "--no-verify"], ctx.cwd);
|
|
98
|
+
if (push.code !== 0) {
|
|
99
|
+
process.stderr.write(`[server-auto-commit] git push failed: ${push.stderr}\n`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
process.stderr.write(`[server-auto-commit] committed and pushed at ${timestamp}\n`);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { StringEnum } from "@mariozechner/pi-ai";
|
|
3
|
-
import { Type } from "@sinclair/typebox";
|
|
4
2
|
import type {
|
|
5
3
|
ExtensionAPI,
|
|
6
4
|
ExtensionCommandContext,
|
|
7
5
|
} from "@mariozechner/pi-coding-agent";
|
|
8
|
-
import type { Theme } from "@mariozechner/pi-tui";
|
|
9
|
-
import { Text } from "@mariozechner/pi-tui";
|
|
10
|
-
|
|
11
|
-
type ITasksAction =
|
|
12
|
-
| "show"
|
|
13
|
-
| "get"
|
|
14
|
-
| "create-section"
|
|
15
|
-
| "create-feature"
|
|
16
|
-
| "create-task"
|
|
17
|
-
| "set-status"
|
|
18
|
-
| "create-linked-todo";
|
|
19
6
|
|
|
20
7
|
interface ICliEnvironment {
|
|
21
8
|
executable: string;
|
|
@@ -70,28 +57,6 @@ interface IProjectPlanShowPayload {
|
|
|
70
57
|
};
|
|
71
58
|
}
|
|
72
59
|
|
|
73
|
-
const TaskParams = Type.Object({
|
|
74
|
-
action: StringEnum([
|
|
75
|
-
"show",
|
|
76
|
-
"get",
|
|
77
|
-
"create-section",
|
|
78
|
-
"create-feature",
|
|
79
|
-
"create-task",
|
|
80
|
-
"set-status",
|
|
81
|
-
"create-linked-todo",
|
|
82
|
-
] as const),
|
|
83
|
-
taskId: Type.Optional(Type.String({ description: "Canonical task id" })),
|
|
84
|
-
featureId: Type.Optional(Type.String({ description: "Canonical feature id" })),
|
|
85
|
-
sectionId: Type.Optional(Type.String({ description: "Project plan section id" })),
|
|
86
|
-
title: Type.Optional(Type.String({ description: "Section, feature, or task title" })),
|
|
87
|
-
summary: Type.Optional(Type.String({ description: "Section, feature, or task summary" })),
|
|
88
|
-
slug: Type.Optional(Type.String({ description: "Section or feature slug" })),
|
|
89
|
-
type: Type.Optional(Type.String({ description: "Task type" })),
|
|
90
|
-
assignee: Type.Optional(Type.String({ description: "Task assignee" })),
|
|
91
|
-
status: Type.Optional(Type.String({ description: "Task status" })),
|
|
92
|
-
acceptanceCriteria: Type.Optional(Type.Array(Type.String({ description: "Acceptance criterion" }))),
|
|
93
|
-
});
|
|
94
|
-
|
|
95
60
|
function readCliEnvironment(env: NodeJS.ProcessEnv = process.env): ICliEnvironment {
|
|
96
61
|
const executable = env.DOCYRUS_CLI_EXECUTABLE?.trim();
|
|
97
62
|
const entryPath = env.DOCYRUS_CLI_ENTRY?.trim();
|
|
@@ -326,197 +291,6 @@ async function tasksCommandHandler(pi: ExtensionAPI, ctx: ExtensionCommandContex
|
|
|
326
291
|
}
|
|
327
292
|
|
|
328
293
|
export default function tasksExtension(pi: ExtensionAPI) {
|
|
329
|
-
pi.registerTool({
|
|
330
|
-
name: "project_task",
|
|
331
|
-
label: "Project Task",
|
|
332
|
-
description: "Manage the canonical repo-tracked project-plan graph and linked local subtasks.",
|
|
333
|
-
parameters: TaskParams,
|
|
334
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
335
|
-
const environment = readCliEnvironment();
|
|
336
|
-
const action = params.action as ITasksAction;
|
|
337
|
-
|
|
338
|
-
switch (action) {
|
|
339
|
-
case "show": {
|
|
340
|
-
const payload = await runCliJson<IProjectPlanShowPayload>(environment, ["project-plan", "show"], ctx.cwd);
|
|
341
|
-
const text = formatHierarchySummary(payload);
|
|
342
|
-
return {
|
|
343
|
-
content: [{ type: "text", text }],
|
|
344
|
-
details: payload,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
case "get": {
|
|
349
|
-
if (!params.taskId) {
|
|
350
|
-
return {
|
|
351
|
-
content: [{ type: "text", text: "taskId required" }],
|
|
352
|
-
isError: true,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
const payload = await runCliJson<Record<string, unknown>>(environment, [
|
|
356
|
-
"project-plan",
|
|
357
|
-
"get-task",
|
|
358
|
-
"--taskId",
|
|
359
|
-
String(params.taskId),
|
|
360
|
-
], ctx.cwd);
|
|
361
|
-
return {
|
|
362
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
363
|
-
details: payload,
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
case "create-section": {
|
|
368
|
-
if (!params.title) {
|
|
369
|
-
return {
|
|
370
|
-
content: [{ type: "text", text: "title required" }],
|
|
371
|
-
isError: true,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
const args = [
|
|
375
|
-
"project-plan",
|
|
376
|
-
"upsert-section",
|
|
377
|
-
"--title",
|
|
378
|
-
String(params.title),
|
|
379
|
-
];
|
|
380
|
-
if (params.slug) {
|
|
381
|
-
args.push("--slug", String(params.slug));
|
|
382
|
-
}
|
|
383
|
-
if (params.summary) {
|
|
384
|
-
args.push("--summary", String(params.summary));
|
|
385
|
-
}
|
|
386
|
-
const payload = await runCliJson<Record<string, unknown>>(environment, args, ctx.cwd);
|
|
387
|
-
return {
|
|
388
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
389
|
-
details: payload,
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
case "create-feature": {
|
|
394
|
-
if (!params.sectionId || !params.title) {
|
|
395
|
-
return {
|
|
396
|
-
content: [{ type: "text", text: "sectionId and title required" }],
|
|
397
|
-
isError: true,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
const args = [
|
|
401
|
-
"project-plan",
|
|
402
|
-
"upsert-feature",
|
|
403
|
-
"--sectionId",
|
|
404
|
-
String(params.sectionId),
|
|
405
|
-
"--title",
|
|
406
|
-
String(params.title),
|
|
407
|
-
];
|
|
408
|
-
if (params.slug) {
|
|
409
|
-
args.push("--slug", String(params.slug));
|
|
410
|
-
}
|
|
411
|
-
if (params.summary) {
|
|
412
|
-
args.push("--summary", String(params.summary));
|
|
413
|
-
}
|
|
414
|
-
const payload = await runCliJson<Record<string, unknown>>(environment, args, ctx.cwd);
|
|
415
|
-
return {
|
|
416
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
417
|
-
details: payload,
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
case "create-task": {
|
|
422
|
-
if (!params.featureId || !params.title || !params.type || !params.assignee) {
|
|
423
|
-
return {
|
|
424
|
-
content: [{ type: "text", text: "featureId, title, type, and assignee required" }],
|
|
425
|
-
isError: true,
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
const args = [
|
|
429
|
-
"project-plan",
|
|
430
|
-
"upsert-task",
|
|
431
|
-
"--featureId",
|
|
432
|
-
String(params.featureId),
|
|
433
|
-
"--title",
|
|
434
|
-
String(params.title),
|
|
435
|
-
"--type",
|
|
436
|
-
String(params.type),
|
|
437
|
-
"--assignee",
|
|
438
|
-
String(params.assignee),
|
|
439
|
-
];
|
|
440
|
-
if (params.sectionId) {
|
|
441
|
-
args.push("--sectionId", String(params.sectionId));
|
|
442
|
-
}
|
|
443
|
-
if (params.summary) {
|
|
444
|
-
args.push("--summary", String(params.summary));
|
|
445
|
-
}
|
|
446
|
-
if (params.status) {
|
|
447
|
-
args.push("--status", String(params.status));
|
|
448
|
-
}
|
|
449
|
-
if (Array.isArray(params.acceptanceCriteria) && params.acceptanceCriteria.length > 0) {
|
|
450
|
-
args.push("--acceptanceCriteria", JSON.stringify(params.acceptanceCriteria));
|
|
451
|
-
}
|
|
452
|
-
const payload = await runCliJson<Record<string, unknown>>(environment, args, ctx.cwd);
|
|
453
|
-
return {
|
|
454
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
455
|
-
details: payload,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
case "set-status": {
|
|
460
|
-
if (!params.taskId || !params.status) {
|
|
461
|
-
return {
|
|
462
|
-
content: [{ type: "text", text: "taskId and status required" }],
|
|
463
|
-
isError: true,
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
const payload = await runCliJson<Record<string, unknown>>(environment, [
|
|
467
|
-
"project-plan",
|
|
468
|
-
"set-task-status",
|
|
469
|
-
"--taskId",
|
|
470
|
-
String(params.taskId),
|
|
471
|
-
"--status",
|
|
472
|
-
String(params.status),
|
|
473
|
-
], ctx.cwd);
|
|
474
|
-
return {
|
|
475
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
476
|
-
details: payload,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
case "create-linked-todo": {
|
|
481
|
-
if (!params.taskId) {
|
|
482
|
-
return {
|
|
483
|
-
content: [{ type: "text", text: "taskId required" }],
|
|
484
|
-
isError: true,
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
const args = [
|
|
488
|
-
"project-plan",
|
|
489
|
-
"create-linked-todo",
|
|
490
|
-
"--taskId",
|
|
491
|
-
String(params.taskId),
|
|
492
|
-
];
|
|
493
|
-
if (params.title) {
|
|
494
|
-
args.push("--title", String(params.title));
|
|
495
|
-
}
|
|
496
|
-
if (params.summary) {
|
|
497
|
-
args.push("--body", String(params.summary));
|
|
498
|
-
}
|
|
499
|
-
const payload = await runCliJson<Record<string, unknown>>(environment, args, ctx.cwd);
|
|
500
|
-
return {
|
|
501
|
-
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
502
|
-
details: payload,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
},
|
|
507
|
-
renderCall(args, theme: Theme) {
|
|
508
|
-
return new Text(
|
|
509
|
-
`${theme.fg("toolTitle", theme.bold("project task "))}${theme.fg("muted", String(args.action || "show"))}`,
|
|
510
|
-
0,
|
|
511
|
-
0,
|
|
512
|
-
);
|
|
513
|
-
},
|
|
514
|
-
renderResult(result, _options, _theme) {
|
|
515
|
-
const text = result.content[0];
|
|
516
|
-
return new Text(text?.type === "text" ? text.text : "", 0, 0);
|
|
517
|
-
},
|
|
518
|
-
});
|
|
519
|
-
|
|
520
294
|
pi.registerCommand("tasks", {
|
|
521
295
|
description: "Browse project-plan sections, features, and tasks",
|
|
522
296
|
handler: async(args, ctx) => {
|
|
@@ -27,13 +27,18 @@ Docyrus concepts you should understand and use accurately:
|
|
|
27
27
|
|
|
28
28
|
Project plan system:
|
|
29
29
|
|
|
30
|
-
The project-plan is a repo-tracked work graph stored at `docyrus/project-plan/project-plan.json`. It organizes work into sections (standalone groupings like phases or feature areas), features, and tasks. A derived `PROJECT_PLAN.md` is always kept in sync. Features are also synced into the knowledge base features document when it exists.
|
|
30
|
+
The project-plan is a repo-tracked work graph stored at `docyrus/project-plan/project-plan.json`. It organizes work into sections (standalone groupings like phases or feature areas), features, and tasks. A derived `PROJECT_PLAN.md` is always kept in sync. Features are also synced into the knowledge base features document when it exists. Sections, features, and tasks each support an optional integer `order` field that controls display sequence (lower = first; entities without an order sort after ordered ones).
|
|
31
31
|
|
|
32
|
-
- `docyrus project-plan show` — view the full hierarchy with statuses
|
|
33
|
-
- `docyrus project-plan get-task --taskId <id
|
|
32
|
+
- `docyrus project-plan show --json` — view the full hierarchy with statuses; returns `{ graph, hierarchy }`
|
|
33
|
+
- `docyrus project-plan get-task --taskId <id> --json` — inspect a task and its linked local subtasks
|
|
34
34
|
- `docyrus project-plan set-task-status --taskId <id> --status <status>` — advance task status (`planned` → `in_progress` → `done`, or `blocked`)
|
|
35
|
-
- `docyrus project-plan
|
|
36
|
-
- `docyrus project-plan
|
|
35
|
+
- `docyrus project-plan create-linked-todo --taskId <id> [--title <title>] [--body <body>]` — create a local `.pi/todos` subtask linked to an agent-assigned canonical task
|
|
36
|
+
- `docyrus project-plan upsert-section --title <title> [--id <id>] [--slug <slug>] [--summary <summary>] [--order <n>]` — create or update a section
|
|
37
|
+
- `docyrus project-plan upsert-feature --sectionId <id> --title <title> [--featureId <id>] [--slug <slug>] [--summary <summary>] [--order <n>]` — create or update a feature
|
|
38
|
+
- `docyrus project-plan upsert-task --featureId <id> --title <title> --type <type> --assignee <assignee> [--taskId <id>] [--status <status>] [--summary <summary>] [--acceptanceCriteria <json-array>] [--order <n>]` — create or update a task
|
|
39
|
+
- `docyrus project-plan set-order --sectionId|--featureId|--taskId <id> --order <n>` — set display order on an existing entity (lower = first; unordered items sort last)
|
|
40
|
+
- `docyrus project-plan check` — validate section references and graph integrity
|
|
41
|
+
- `docyrus project-plan ensure` — initialize an empty project-plan graph if it does not yet exist
|
|
37
42
|
|
|
38
43
|
When working on a repo that has a project plan, read it at the start of a session to understand scope and priorities. Update task status as work progresses and after it completes.
|
|
39
44
|
|
|
@@ -72,17 +72,18 @@ Schema-first workflow for new Docyrus-backed apps and major features:
|
|
|
72
72
|
|
|
73
73
|
Project plan system:
|
|
74
74
|
|
|
75
|
-
The project-plan is a repo-tracked work graph stored at `docyrus/project-plan/project-plan.json` with a derived `PROJECT_PLAN.md` always kept in sync. Work is organized into sections (standalone groupings like phases or feature areas), features, and tasks. Features are also synced into the knowledge base features document when it exists. Tasks have a type (`new-implementation`, `bug-fix`, `api-test`, `browser-automation-test`, `work`), an assignee (`agent` or `user`), a status (`planned`, `in_progress`, `blocked`, `done`), and optional acceptance criteria.
|
|
75
|
+
The project-plan is a repo-tracked work graph stored at `docyrus/project-plan/project-plan.json` with a derived `PROJECT_PLAN.md` always kept in sync. Work is organized into sections (standalone groupings like phases or feature areas), features, and tasks. Features are also synced into the knowledge base features document when it exists. Tasks have a type (`new-implementation`, `bug-fix`, `api-test`, `browser-automation-test`, `work`), an assignee (`agent` or `user`), a status (`planned`, `in_progress`, `blocked`, `done`), and optional acceptance criteria. Sections, features, and tasks each support an optional integer `order` field that controls display sequence (lower = first; entities without an order sort after ordered ones).
|
|
76
76
|
|
|
77
77
|
Key commands:
|
|
78
78
|
|
|
79
|
-
- `docyrus project-plan show` — view the full hierarchy with feature and task statuses
|
|
80
|
-
- `docyrus project-plan get-task --taskId <id
|
|
81
|
-
- `docyrus project-plan set-task-status --taskId <id> --status <status>` — advance task status
|
|
82
|
-
- `docyrus project-plan create-linked-todo --taskId <id> --title <title> --body <body
|
|
83
|
-
- `docyrus project-plan upsert-section --title <title> --slug <slug> --summary <summary
|
|
84
|
-
- `docyrus project-plan upsert-feature --sectionId <id> --title <title> --slug <slug
|
|
85
|
-
- `docyrus project-plan upsert-task --featureId <id> --title <title> --type <type> --assignee <assignee
|
|
79
|
+
- `docyrus project-plan show --json` — view the full hierarchy with feature and task statuses; returns `{ graph, hierarchy }`
|
|
80
|
+
- `docyrus project-plan get-task --taskId <id> --json` — inspect a specific task and its linked local subtasks
|
|
81
|
+
- `docyrus project-plan set-task-status --taskId <id> --status <status>` — advance task status (`planned` → `in_progress` → `done`, or `blocked`)
|
|
82
|
+
- `docyrus project-plan create-linked-todo --taskId <id> [--title <title>] [--body <body>]` — create a local `.pi/todos` subtask linked to an agent-assigned canonical task
|
|
83
|
+
- `docyrus project-plan upsert-section --title <title> [--id <id>] [--slug <slug>] [--summary <summary>] [--order <n>]` — create or update a section
|
|
84
|
+
- `docyrus project-plan upsert-feature --sectionId <id> --title <title> [--featureId <id>] [--slug <slug>] [--summary <summary>] [--order <n>]` — create or update a feature
|
|
85
|
+
- `docyrus project-plan upsert-task --featureId <id> --title <title> --type <type> --assignee <assignee> [--taskId <id>] [--status <status>] [--summary <summary>] [--acceptanceCriteria <json-array>] [--order <n>]` — create or update a task
|
|
86
|
+
- `docyrus project-plan set-order --sectionId|--featureId|--taskId <id> --order <n>` — set display order on an existing entity (lower = first; unordered items sort last)
|
|
86
87
|
- `docyrus project-plan check` — validate section references and graph integrity
|
|
87
88
|
- `docyrus project-plan ensure` — initialize an empty project-plan graph if it does not yet exist
|
|
88
89
|
|