agentplane 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/LICENSE +22 -0
- package/README.md +19 -0
- package/assets/AGENTS.md +274 -0
- package/assets/agents/CODER.json +28 -0
- package/assets/agents/CREATOR.json +25 -0
- package/assets/agents/DOCS.json +25 -0
- package/assets/agents/INTEGRATOR.json +27 -0
- package/assets/agents/ORCHESTRATOR.json +31 -0
- package/assets/agents/PLANNER.json +28 -0
- package/assets/agents/REDMINE.json +26 -0
- package/assets/agents/REVIEWER.json +22 -0
- package/assets/agents/TESTER.json +27 -0
- package/assets/agents/UPDATER.json +23 -0
- package/assets/agents/UPGRADER.json +27 -0
- package/bin/agentplane.js +2 -0
- package/dist/agents-template.d.ts +10 -0
- package/dist/agents-template.d.ts.map +1 -0
- package/dist/agents-template.js +66 -0
- package/dist/bundled-recipes.d.ts +13 -0
- package/dist/bundled-recipes.d.ts.map +1 -0
- package/dist/bundled-recipes.js +4 -0
- package/dist/cli/fs-utils.d.ts +4 -0
- package/dist/cli/fs-utils.d.ts.map +1 -0
- package/dist/cli/fs-utils.js +28 -0
- package/dist/cli/prompts.d.ts +4 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +31 -0
- package/dist/cli/recipes-bundled.d.ts +9 -0
- package/dist/cli/recipes-bundled.d.ts.map +1 -0
- package/dist/cli/recipes-bundled.js +33 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +5 -0
- package/dist/command-guide.d.ts +4 -0
- package/dist/command-guide.d.ts.map +1 -0
- package/dist/command-guide.js +244 -0
- package/dist/comment-format.d.ts +8 -0
- package/dist/comment-format.d.ts.map +1 -0
- package/dist/comment-format.js +80 -0
- package/dist/env.d.ts +3 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +49 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +22 -0
- package/dist/help.d.ts +2 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +124 -0
- package/dist/run-cli.d.ts +2 -0
- package/dist/run-cli.d.ts.map +1 -0
- package/dist/run-cli.js +8443 -0
- package/dist/run-cli.test-helpers.d.ts +44 -0
- package/dist/run-cli.test-helpers.d.ts.map +1 -0
- package/dist/run-cli.test-helpers.js +280 -0
- package/dist/task-backend.d.ts +175 -0
- package/dist/task-backend.d.ts.map +1 -0
- package/dist/task-backend.js +1235 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/package.json +59 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const AGENTS_TEMPLATE_URL = new URL("../assets/AGENTS.md", import.meta.url);
|
|
5
|
+
const AGENTS_DIR_URL = new URL("../assets/agents/", import.meta.url);
|
|
6
|
+
const HEADING_RE = /^(#+)\s+(.*)$/;
|
|
7
|
+
function ensureTrailingNewline(text) {
|
|
8
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
9
|
+
}
|
|
10
|
+
function getHeadings(lines) {
|
|
11
|
+
const headings = [];
|
|
12
|
+
for (const [index, line] of lines.entries()) {
|
|
13
|
+
const match = HEADING_RE.exec(line);
|
|
14
|
+
if (!match)
|
|
15
|
+
continue;
|
|
16
|
+
headings.push({ index, level: match[1].length, title: match[2].trim() });
|
|
17
|
+
}
|
|
18
|
+
return headings;
|
|
19
|
+
}
|
|
20
|
+
function getSectionRange(lines, title) {
|
|
21
|
+
const headings = getHeadings(lines);
|
|
22
|
+
const heading = headings.find((entry) => entry.title === title);
|
|
23
|
+
if (!heading)
|
|
24
|
+
return null;
|
|
25
|
+
const next = headings
|
|
26
|
+
.filter((entry) => entry.index > heading.index)
|
|
27
|
+
.find((entry) => entry.level <= heading.level);
|
|
28
|
+
return [heading.index, next ? next.index : lines.length];
|
|
29
|
+
}
|
|
30
|
+
function removeSections(lines, titles) {
|
|
31
|
+
const removeRanges = titles
|
|
32
|
+
.map((title) => getSectionRange(lines, title))
|
|
33
|
+
.filter((range) => range !== null);
|
|
34
|
+
if (removeRanges.length === 0)
|
|
35
|
+
return lines;
|
|
36
|
+
const shouldRemove = new Set();
|
|
37
|
+
for (const [start, end] of removeRanges) {
|
|
38
|
+
for (let i = start; i < end; i += 1)
|
|
39
|
+
shouldRemove.add(i);
|
|
40
|
+
}
|
|
41
|
+
return lines.filter((_line, index) => !shouldRemove.has(index));
|
|
42
|
+
}
|
|
43
|
+
export async function loadAgentsTemplate() {
|
|
44
|
+
const text = await readFile(AGENTS_TEMPLATE_URL, "utf8");
|
|
45
|
+
return ensureTrailingNewline(text.trimEnd());
|
|
46
|
+
}
|
|
47
|
+
export async function loadAgentTemplates() {
|
|
48
|
+
const dirPath = fileURLToPath(AGENTS_DIR_URL);
|
|
49
|
+
const entries = await readdir(dirPath);
|
|
50
|
+
const jsonFiles = entries.filter((entry) => entry.endsWith(".json"));
|
|
51
|
+
const templates = [];
|
|
52
|
+
for (const fileName of jsonFiles) {
|
|
53
|
+
const filePath = path.join(dirPath, fileName);
|
|
54
|
+
const contents = await readFile(filePath, "utf8");
|
|
55
|
+
templates.push({ fileName, contents: ensureTrailingNewline(contents.trimEnd()) });
|
|
56
|
+
}
|
|
57
|
+
return templates;
|
|
58
|
+
}
|
|
59
|
+
export function filterAgentsByWorkflow(template, workflow) {
|
|
60
|
+
const lines = template.replaceAll("\r\n", "\n").split("\n");
|
|
61
|
+
const removeTitles = workflow === "direct"
|
|
62
|
+
? ["B) branch_pr mode (parallel work)", "INTEGRATION & CLOSURE (branch_pr)"]
|
|
63
|
+
: ["A) direct mode (single checkout)"];
|
|
64
|
+
const filtered = removeSections(lines, removeTitles);
|
|
65
|
+
return ensureTrailingNewline(filtered.join("\n").trimEnd());
|
|
66
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type BundledRecipesCatalog = {
|
|
2
|
+
schema_version: 1;
|
|
3
|
+
recipes: {
|
|
4
|
+
id: string;
|
|
5
|
+
summary: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
versions: {
|
|
8
|
+
version: string;
|
|
9
|
+
}[];
|
|
10
|
+
}[];
|
|
11
|
+
};
|
|
12
|
+
export declare const BUNDLED_RECIPES_CATALOG: BundledRecipesCatalog;
|
|
13
|
+
//# sourceMappingURL=bundled-recipes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundled-recipes.d.ts","sourceRoot":"","sources":["../src/bundled-recipes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG;IAClC,cAAc,EAAE,CAAC,CAAC;IAClB,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACjC,EAAE,CAAC;CACL,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,qBAGrC,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function fileExists(filePath: string): Promise<boolean>;
|
|
2
|
+
export declare function getPathKind(filePath: string): Promise<"file" | "dir" | null>;
|
|
3
|
+
export declare function backupPath(filePath: string): Promise<string>;
|
|
4
|
+
//# sourceMappingURL=fs-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-utils.d.ts","sourceRoot":"","sources":["../../src/cli/fs-utils.ts"],"names":[],"mappings":"AAEA,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC,CAOlF;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { access, lstat, rename } from "node:fs/promises";
|
|
2
|
+
export async function fileExists(filePath) {
|
|
3
|
+
try {
|
|
4
|
+
await access(filePath);
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function getPathKind(filePath) {
|
|
12
|
+
try {
|
|
13
|
+
const stats = await lstat(filePath);
|
|
14
|
+
return stats.isDirectory() ? "dir" : "file";
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function backupPath(filePath) {
|
|
21
|
+
const stamp = new Date().toISOString().replaceAll(/[:.]/g, "");
|
|
22
|
+
let dest = `${filePath}.bak-${stamp}`;
|
|
23
|
+
if (await fileExists(dest)) {
|
|
24
|
+
dest = `${filePath}.bak-${stamp}-${Math.random().toString(36).slice(2, 8)}`;
|
|
25
|
+
}
|
|
26
|
+
await rename(filePath, dest);
|
|
27
|
+
return dest;
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function promptChoice(prompt: string, choices: string[], defaultValue: string): Promise<string>;
|
|
2
|
+
export declare function promptYesNo(prompt: string, defaultValue: boolean): Promise<boolean>;
|
|
3
|
+
export declare function promptInput(prompt: string): Promise<string>;
|
|
4
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AAEA,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EAAE,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAQzF;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKjE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
export async function promptChoice(prompt, choices, defaultValue) {
|
|
3
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
4
|
+
const question = `${prompt} [${choices.join("/")}] (default ${defaultValue}): `;
|
|
5
|
+
const answer = await rl.question(question);
|
|
6
|
+
rl.close();
|
|
7
|
+
const trimmed = answer.trim();
|
|
8
|
+
if (!trimmed)
|
|
9
|
+
return defaultValue;
|
|
10
|
+
if (!choices.includes(trimmed)) {
|
|
11
|
+
process.stdout.write(`Invalid choice, using default ${defaultValue}\n`);
|
|
12
|
+
return defaultValue;
|
|
13
|
+
}
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
export async function promptYesNo(prompt, defaultValue) {
|
|
17
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
18
|
+
const question = `${prompt} [${defaultValue ? "Y/n" : "y/N"}]: `;
|
|
19
|
+
const answer = await rl.question(question);
|
|
20
|
+
rl.close();
|
|
21
|
+
const trimmed = answer.trim().toLowerCase();
|
|
22
|
+
if (!trimmed)
|
|
23
|
+
return defaultValue;
|
|
24
|
+
return ["y", "yes", "true", "1", "on"].includes(trimmed);
|
|
25
|
+
}
|
|
26
|
+
export async function promptInput(prompt) {
|
|
27
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
+
const answer = await rl.question(prompt);
|
|
29
|
+
rl.close();
|
|
30
|
+
return answer.trim();
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type BundledRecipeInfo = {
|
|
2
|
+
id: string;
|
|
3
|
+
summary: string;
|
|
4
|
+
version: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function listBundledRecipes(): BundledRecipeInfo[];
|
|
7
|
+
export declare function renderBundledRecipesHint(): string;
|
|
8
|
+
export declare function validateBundledRecipesSelection(recipes: string[]): void;
|
|
9
|
+
//# sourceMappingURL=recipes-bundled.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipes-bundled.d.ts","sourceRoot":"","sources":["../../src/cli/recipes-bundled.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjF,wBAAgB,kBAAkB,IAAI,iBAAiB,EAAE,CAMxD;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CAMjD;AAED,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAevE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BUNDLED_RECIPES_CATALOG } from "../bundled-recipes.js";
|
|
2
|
+
import { CliError } from "../errors.js";
|
|
3
|
+
export function listBundledRecipes() {
|
|
4
|
+
return BUNDLED_RECIPES_CATALOG.recipes.map((recipe) => ({
|
|
5
|
+
id: recipe.id,
|
|
6
|
+
summary: recipe.summary,
|
|
7
|
+
version: recipe.versions.at(-1)?.version ?? "unknown",
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
export function renderBundledRecipesHint() {
|
|
11
|
+
const entries = listBundledRecipes();
|
|
12
|
+
if (entries.length === 0) {
|
|
13
|
+
return "Available bundled recipes: none";
|
|
14
|
+
}
|
|
15
|
+
return `Available bundled recipes: ${entries.map((entry) => entry.id).join(", ")}`;
|
|
16
|
+
}
|
|
17
|
+
export function validateBundledRecipesSelection(recipes) {
|
|
18
|
+
if (recipes.length === 0)
|
|
19
|
+
return;
|
|
20
|
+
const available = listBundledRecipes().map((entry) => entry.id);
|
|
21
|
+
if (available.length === 0) {
|
|
22
|
+
process.stdout.write(`${renderBundledRecipesHint()}\n`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const missing = recipes.filter((recipe) => !available.includes(recipe));
|
|
26
|
+
if (missing.length > 0) {
|
|
27
|
+
throw new CliError({
|
|
28
|
+
exitCode: 2,
|
|
29
|
+
code: "E_USAGE",
|
|
30
|
+
message: `Unknown recipes: ${missing.join(", ")}. ${renderBundledRecipesHint()}`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-guide.d.ts","sourceRoot":"","sources":["../src/command-guide.ts"],"names":[],"mappings":"AA4KA,wBAAgB,SAAS,IAAI,MAAM,EAAE,CAEpC;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOzD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CA2EzC"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const CHEAT_SHEET_ROWS = [
|
|
2
|
+
{
|
|
3
|
+
operation: "PLANNER: list/show tasks",
|
|
4
|
+
command: "`agentplane task list` / `agentplane task show <task-id>`",
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
operation: "PLANNER: create task (auto ID)",
|
|
8
|
+
command: '`agentplane task new --title "..." --description "..." --priority med --owner CODER --tag <tag>`',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
operation: "PLANNER: add/update task",
|
|
12
|
+
command: "`agentplane task add <task-id> ...` / `agentplane task update <task-id> ...`",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
operation: "PLANNER: scaffold artifact",
|
|
16
|
+
command: "`agentplane task scaffold <task-id>`",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
operation: "Config: show/set",
|
|
20
|
+
command: "`agentplane config show` / `agentplane config set <key> <value>`",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
operation: "CODER/TESTER/DOCS: start checkout (branch_pr)",
|
|
24
|
+
command: "`agentplane work start <task-id> --agent <ROLE> --slug <slug> --worktree`",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
operation: "CODER/TESTER/DOCS: update PR artifacts",
|
|
28
|
+
command: "`agentplane pr update <task-id>`",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
operation: "CODER/TESTER/DOCS/REVIEWER: add handoff note",
|
|
32
|
+
command: '`agentplane pr note <task-id> --author <ROLE> --body "..."`',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
operation: "CODER/TESTER: verify task",
|
|
36
|
+
command: "`agentplane verify <task-id>`",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
operation: "REVIEWER: check PR artifacts",
|
|
40
|
+
command: "`agentplane pr check <task-id>`",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
operation: "INTEGRATOR: integrate task",
|
|
44
|
+
command: "`agentplane integrate <task-id> --branch task/<task-id>/<slug> --merge-strategy squash --run-verify`",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
operation: "INTEGRATOR: finish task(s)",
|
|
48
|
+
command: '`agentplane finish <task-id> [<task-id> ...] --commit <git-rev> --author INTEGRATOR --body "Verified: ..."`',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
operation: "INTEGRATOR: commit closure",
|
|
52
|
+
command: '`agentplane commit <task-id> -m "<emoji> <suffix> close: <detailed changelog ...>" --auto-allow --allow-tasks --require-clean`',
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
const ROLE_GUIDES = [
|
|
56
|
+
{
|
|
57
|
+
role: "ORCHESTRATOR",
|
|
58
|
+
lines: [
|
|
59
|
+
"- Plan intake: `agentplane task list` / `agentplane task show <task-id>`",
|
|
60
|
+
'- After plan approval (unless the user opts out): `agentplane task new --title "..." --description "..." --priority med --owner ORCHESTRATOR --depends-on "[]" --tag <tag>`',
|
|
61
|
+
"- Optional scaffold: `agentplane task scaffold <task-id>`",
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
role: "PLANNER",
|
|
66
|
+
lines: [
|
|
67
|
+
'- TODO scan: `agentplane task list` / `agentplane task search "..."` / `agentplane task next`',
|
|
68
|
+
'- Create tasks: `agentplane task new --title "..." --description "..." --priority med --owner <ROLE> --depends-on "[]" --tag <tag>` (tags are required; use `task add` only for imported IDs)',
|
|
69
|
+
'- Update tasks: `agentplane task update <task-id> --title "..." --description "..." --priority med --owner <ROLE> --depends-on <task-id>`',
|
|
70
|
+
"- Scaffold artifacts: `agentplane task scaffold <task-id>`",
|
|
71
|
+
'- Task docs (when planning needs it): `agentplane task doc set <task-id> --section Summary --text "..."`',
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
role: "CODER",
|
|
76
|
+
lines: [
|
|
77
|
+
"- direct mode: work in the current checkout; optional `agentplane work start <task-id> --agent <ROLE> --slug <slug>` only scaffolds docs",
|
|
78
|
+
"- branch_pr: `agentplane work start <task-id> --agent <ROLE> --slug <slug> --worktree`",
|
|
79
|
+
'- Status updates: `agentplane start <task-id> --author <ROLE> --body "Start: ..."` / `agentplane block <task-id> --author <ROLE> --body "Blocked: ..."`',
|
|
80
|
+
"- Verify: `agentplane verify <task-id>`",
|
|
81
|
+
'- PR artifacts (branch_pr): `agentplane pr open <task-id> --branch task/<task-id>/<slug> --author <ROLE>` / `agentplane pr update <task-id>` / `agentplane pr note <task-id> --author <ROLE> --body "..."`',
|
|
82
|
+
'- Commit: `agentplane guard commit <task-id> -m "<emoji> <suffix> ..."` / `agentplane commit <task-id> -m "<emoji> <suffix> ..." --allow <path-prefix>`',
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
role: "TESTER",
|
|
87
|
+
lines: [
|
|
88
|
+
"- direct mode: work in the current checkout; optional `agentplane work start <task-id> --agent <ROLE> --slug <slug>` only scaffolds docs",
|
|
89
|
+
"- branch_pr: `agentplane work start <task-id> --agent <ROLE> --slug <slug> --worktree`",
|
|
90
|
+
'- Status updates: `agentplane start <task-id> --author <ROLE> --body "Start: ..."` / `agentplane block <task-id> --author <ROLE> --body "Blocked: ..."`',
|
|
91
|
+
"- Verify: `agentplane verify <task-id>`",
|
|
92
|
+
'- PR artifacts (branch_pr): `agentplane pr open <task-id> --branch task/<task-id>/<slug> --author <ROLE>` / `agentplane pr update <task-id>` / `agentplane pr note <task-id> --author <ROLE> --body "..."`',
|
|
93
|
+
'- Commit: `agentplane guard commit <task-id> -m "<emoji> <suffix> ..."` / `agentplane commit <task-id> -m "<emoji> <suffix> ..." --allow <path-prefix>`',
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
role: "DOCS",
|
|
98
|
+
lines: [
|
|
99
|
+
'- Task docs: `agentplane task doc set <task-id> --section Summary --text "..."` (repeat per section or use `--file`)',
|
|
100
|
+
'- PR notes: `agentplane pr note <task-id> --author DOCS --body "..."`',
|
|
101
|
+
'- Commit: `agentplane guard commit <task-id> -m "<emoji> <suffix> ..."` / `agentplane commit <task-id> -m "<emoji> <suffix> ..." --allow <path-prefix>`',
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
role: "REVIEWER",
|
|
106
|
+
lines: [
|
|
107
|
+
"- Review artifacts: `agentplane pr check <task-id>` / `agentplane task show <task-id>`",
|
|
108
|
+
'- Handoff notes: `agentplane pr note <task-id> --author REVIEWER --body "..."`',
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
role: "INTEGRATOR",
|
|
113
|
+
lines: [
|
|
114
|
+
'- branch_pr: `agentplane pr check <task-id>` -> `agentplane integrate <task-id> --branch task/<task-id>/<slug> --merge-strategy squash --run-verify` -> `agentplane finish <task-id> --commit <git-rev> --author INTEGRATOR --body "Verified: ..."`',
|
|
115
|
+
'- direct: task owner uses `agentplane finish <task-id> --commit <git-rev> --author <OWNER> --body "Verified: ..."` after the implementation commit',
|
|
116
|
+
"- Optional cleanup: `agentplane cleanup merged --yes`",
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
role: "CREATOR",
|
|
121
|
+
lines: [
|
|
122
|
+
'- Task bookkeeping: `agentplane task update <task-id> ...` / `agentplane start <task-id> --author CREATOR --body "Start: ..."`',
|
|
123
|
+
'- Commits: `agentplane guard commit <task-id> -m "<emoji> <suffix> ..."` / `agentplane commit <task-id> -m "<emoji> <suffix> ..." --allow <path-prefix>`',
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
role: "REDMINE",
|
|
128
|
+
lines: [
|
|
129
|
+
"- Sync before/after updates: `agentplane sync redmine --direction pull` / `agentplane sync redmine --direction push --yes`",
|
|
130
|
+
"- Then use normal task/doc commands (`agentplane task list` / `agentplane task show` / `agentplane task update` / `agentplane task doc set`) as needed.",
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
role: "UPDATER",
|
|
135
|
+
lines: [
|
|
136
|
+
'- Read-only audit: `agentplane task list` / `agentplane task show` / `agentplane task search "..."` / `agentplane task next` (no write commands).',
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
function renderCheatSheet(rows) {
|
|
141
|
+
const lines = ["Operation | Command", "--- | ---"];
|
|
142
|
+
for (const row of rows) {
|
|
143
|
+
lines.push(`${row.operation} | ${row.command}`);
|
|
144
|
+
}
|
|
145
|
+
return lines;
|
|
146
|
+
}
|
|
147
|
+
function renderRoleSection() {
|
|
148
|
+
const lines = [];
|
|
149
|
+
for (const guide of ROLE_GUIDES) {
|
|
150
|
+
lines.push(`### ${guide.role}`, ...guide.lines, "");
|
|
151
|
+
}
|
|
152
|
+
if (lines.at(-1) === "")
|
|
153
|
+
lines.pop();
|
|
154
|
+
return lines;
|
|
155
|
+
}
|
|
156
|
+
export function listRoles() {
|
|
157
|
+
return ROLE_GUIDES.map((guide) => guide.role);
|
|
158
|
+
}
|
|
159
|
+
export function renderRole(roleRaw) {
|
|
160
|
+
const trimmed = roleRaw.trim();
|
|
161
|
+
if (!trimmed)
|
|
162
|
+
return null;
|
|
163
|
+
const normalized = trimmed.toUpperCase();
|
|
164
|
+
const guide = ROLE_GUIDES.find((entry) => entry.role.toUpperCase() === normalized);
|
|
165
|
+
if (!guide)
|
|
166
|
+
return null;
|
|
167
|
+
return [`### ${guide.role}`, ...guide.lines].join("\n").trimEnd();
|
|
168
|
+
}
|
|
169
|
+
export function renderQuickstart() {
|
|
170
|
+
return [
|
|
171
|
+
"# agentplane quickstart",
|
|
172
|
+
"",
|
|
173
|
+
"agentplane CLI is the source of truth for task, PR, verify, and commit commands.",
|
|
174
|
+
"Do not edit `.agentplane/tasks.json` by hand.",
|
|
175
|
+
"",
|
|
176
|
+
"## Project setup",
|
|
177
|
+
"",
|
|
178
|
+
"- `agentplane init` (bootstrap `.agentplane/`)",
|
|
179
|
+
"- `agentplane config show` / `agentplane config set <key> <value>`",
|
|
180
|
+
"- `agentplane mode get` / `agentplane mode set <direct|branch_pr>`",
|
|
181
|
+
"- `agentplane ide sync` (regenerate IDE entrypoints)",
|
|
182
|
+
"",
|
|
183
|
+
"## Daily task workflow",
|
|
184
|
+
"",
|
|
185
|
+
"- `agentplane task list` / `agentplane task show <task-id>`",
|
|
186
|
+
'- `agentplane task new --title "..." --description "..." --priority med --owner CODER --tag <tag>`',
|
|
187
|
+
'- `agentplane start <task-id> --author <ROLE> --body "Start: ..."`',
|
|
188
|
+
"- `agentplane verify <task-id>`",
|
|
189
|
+
'- `agentplane finish <task-id> --author <ROLE> --body "Verified: ..."`',
|
|
190
|
+
"",
|
|
191
|
+
"## Branch workflow (branch_pr)",
|
|
192
|
+
"",
|
|
193
|
+
"- `agentplane work start <task-id> --agent <ROLE> --slug <slug> --worktree`",
|
|
194
|
+
"- `agentplane pr open <task-id>` / `agentplane pr update <task-id>` / `agentplane pr check <task-id>`",
|
|
195
|
+
"- `agentplane integrate <task-id> --branch task/<task-id>/<slug> --run-verify`",
|
|
196
|
+
"",
|
|
197
|
+
"## Recipes and scenarios",
|
|
198
|
+
"",
|
|
199
|
+
"- `agentplane recipes list`",
|
|
200
|
+
"- `agentplane recipes list --tag <tag>`",
|
|
201
|
+
"- `agentplane recipes explain <id>`",
|
|
202
|
+
"- `agentplane scenario list`",
|
|
203
|
+
"- `agentplane scenario run <recipe:scenario>`",
|
|
204
|
+
"",
|
|
205
|
+
"## More guidance",
|
|
206
|
+
"",
|
|
207
|
+
"- `agentplane quickstart` and `agentplane role <ROLE>` show command guidance.",
|
|
208
|
+
"",
|
|
209
|
+
"## Agent cheat sheet",
|
|
210
|
+
"",
|
|
211
|
+
...renderCheatSheet(CHEAT_SHEET_ROWS),
|
|
212
|
+
"",
|
|
213
|
+
"## Config management",
|
|
214
|
+
"",
|
|
215
|
+
"- Show the current config: `agentplane config show`",
|
|
216
|
+
"- Set a value by dotted key: `agentplane config set workflow_mode branch_pr`",
|
|
217
|
+
'- Set JSON values (lists/objects): `agentplane config set tasks.verify.required_tags \'["code","backend"]\'`',
|
|
218
|
+
"",
|
|
219
|
+
"## Role/phase command guide (when to use what)",
|
|
220
|
+
"",
|
|
221
|
+
"Use `agentplane role <ROLE>` to print a single block from this section.",
|
|
222
|
+
"",
|
|
223
|
+
...renderRoleSection(),
|
|
224
|
+
"",
|
|
225
|
+
"## Global flags",
|
|
226
|
+
"",
|
|
227
|
+
"- `--root <path>`: treat <path> as project root",
|
|
228
|
+
"- `--json`: emit JSON-formatted errors",
|
|
229
|
+
"- `--help` / `-h`: show help",
|
|
230
|
+
"- `--version`: show version",
|
|
231
|
+
"",
|
|
232
|
+
"Notes:",
|
|
233
|
+
"- `.env` at the repo root is loaded automatically (without overwriting existing environment variables).",
|
|
234
|
+
"",
|
|
235
|
+
"## Commit message format",
|
|
236
|
+
"",
|
|
237
|
+
"Use: `<emoji> <suffix> <detailed changelog ...>`.",
|
|
238
|
+
"",
|
|
239
|
+
"Notes:",
|
|
240
|
+
"- `suffix` is the task ID segment after the last dash.",
|
|
241
|
+
"- For batch commits, include every task suffix in the subject.",
|
|
242
|
+
"- When using comment-driven flags, the subject is auto-built as `<emoji> <suffix> <formatted comment>` from your status/finish body.",
|
|
243
|
+
].join("\n");
|
|
244
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AgentplaneConfig } from "@agentplaneorg/core";
|
|
2
|
+
export declare function normalizeCommentBodyForCommit(body: string): string;
|
|
3
|
+
export declare function splitSummaryAndDetails(text: string): {
|
|
4
|
+
summary: string;
|
|
5
|
+
details: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function formatCommentBodyForCommit(body: string, config: AgentplaneConfig): string;
|
|
8
|
+
//# sourceMappingURL=comment-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comment-format.d.ts","sourceRoot":"","sources":["../src/comment-format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIlE;AAiCD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAwB3F;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAgBzF"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function normalizeCommentBodyForCommit(body) {
|
|
2
|
+
const raw = body.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
3
|
+
const collapsed = raw.replaceAll(/\n+/g, " | ").replaceAll(/\s+/g, " ");
|
|
4
|
+
return collapsed.trim();
|
|
5
|
+
}
|
|
6
|
+
function normalizeCommentPrefix(prefix) {
|
|
7
|
+
let label = prefix.trim();
|
|
8
|
+
if (label.endsWith(":"))
|
|
9
|
+
label = label.slice(0, -1);
|
|
10
|
+
return label.trim().toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
function commentPrefixesForCommit(config) {
|
|
13
|
+
const prefixes = [];
|
|
14
|
+
for (const kind of ["start", "blocked", "verified"]) {
|
|
15
|
+
const raw = config.tasks.comments[kind]?.prefix ?? "";
|
|
16
|
+
const label = normalizeCommentPrefix(raw);
|
|
17
|
+
if (raw && label)
|
|
18
|
+
prefixes.push({ raw, label });
|
|
19
|
+
}
|
|
20
|
+
return prefixes;
|
|
21
|
+
}
|
|
22
|
+
function splitCommentPrefix(text, prefixes) {
|
|
23
|
+
const lowered = text.toLowerCase();
|
|
24
|
+
for (const { raw, label } of prefixes) {
|
|
25
|
+
const trimmed = raw.trim();
|
|
26
|
+
if (!trimmed)
|
|
27
|
+
continue;
|
|
28
|
+
if (lowered.startsWith(trimmed.toLowerCase())) {
|
|
29
|
+
return { label, remainder: text.slice(trimmed.length).trim() };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { label: null, remainder: text };
|
|
33
|
+
}
|
|
34
|
+
export function splitSummaryAndDetails(text) {
|
|
35
|
+
const cleaned = text.trim();
|
|
36
|
+
if (!cleaned)
|
|
37
|
+
return { summary: "", details: [] };
|
|
38
|
+
for (const pattern of [/\s*\|\s*/, /\s*;\s*/, /\s+--\s+/, /\s+-\s+/]) {
|
|
39
|
+
if (pattern.test(cleaned)) {
|
|
40
|
+
const parts = cleaned
|
|
41
|
+
.split(pattern)
|
|
42
|
+
.map((part) => part.trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
if (parts.length > 0) {
|
|
45
|
+
const [summary, ...details] = parts;
|
|
46
|
+
return { summary: summary ?? "", details };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const sentences = cleaned
|
|
51
|
+
.split(/(?<=[.!?])\s+/)
|
|
52
|
+
.map((part) => part.trim())
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
if (sentences.length > 1) {
|
|
55
|
+
const [summary, ...details] = sentences;
|
|
56
|
+
return { summary: summary ?? "", details };
|
|
57
|
+
}
|
|
58
|
+
return { summary: cleaned, details: [] };
|
|
59
|
+
}
|
|
60
|
+
export function formatCommentBodyForCommit(body, config) {
|
|
61
|
+
const compact = normalizeCommentBodyForCommit(body);
|
|
62
|
+
if (!compact)
|
|
63
|
+
return "";
|
|
64
|
+
const prefixes = commentPrefixesForCommit(config);
|
|
65
|
+
let { label, remainder } = splitCommentPrefix(compact, prefixes);
|
|
66
|
+
let { summary, details } = splitSummaryAndDetails(remainder);
|
|
67
|
+
if (!summary) {
|
|
68
|
+
summary = remainder || compact;
|
|
69
|
+
if (summary === compact && label)
|
|
70
|
+
label = null;
|
|
71
|
+
}
|
|
72
|
+
if (label)
|
|
73
|
+
summary = `${label}: ${summary}`.trim();
|
|
74
|
+
if (details.length > 0) {
|
|
75
|
+
const detailsText = details.filter(Boolean).join("; ").trim();
|
|
76
|
+
if (detailsText)
|
|
77
|
+
return `${summary} | details: ${detailsText}`;
|
|
78
|
+
}
|
|
79
|
+
return summary;
|
|
80
|
+
}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA4BhE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc/D"}
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function parseDotEnv(text) {
|
|
4
|
+
const out = {};
|
|
5
|
+
const lines = text.split(/\r?\n/u);
|
|
6
|
+
for (const rawLine of lines) {
|
|
7
|
+
const line = rawLine.trim();
|
|
8
|
+
if (!line || line.startsWith("#"))
|
|
9
|
+
continue;
|
|
10
|
+
const idx = line.indexOf("=");
|
|
11
|
+
if (idx <= 0)
|
|
12
|
+
continue;
|
|
13
|
+
const key = line.slice(0, idx).trim();
|
|
14
|
+
let value = line.slice(idx + 1).trim();
|
|
15
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
16
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
17
|
+
const quote = value[0];
|
|
18
|
+
value = value.slice(1, -1);
|
|
19
|
+
if (quote === '"') {
|
|
20
|
+
value = value
|
|
21
|
+
.replaceAll(String.raw `\n`, "\n")
|
|
22
|
+
.replaceAll(String.raw `\r`, "\r")
|
|
23
|
+
.replaceAll(String.raw `\t`, "\t")
|
|
24
|
+
.replaceAll(String.raw `\"`, '"')
|
|
25
|
+
.replaceAll(String.raw `\\`, "\\");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (key)
|
|
29
|
+
out[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
export async function loadDotEnv(rootDir) {
|
|
34
|
+
const envPath = path.join(rootDir, ".env");
|
|
35
|
+
let text = "";
|
|
36
|
+
try {
|
|
37
|
+
text = await readFile(envPath, "utf8");
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const code = err?.code;
|
|
41
|
+
if (code === "ENOENT")
|
|
42
|
+
return;
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
const parsed = parseDotEnv(text);
|
|
46
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
47
|
+
process.env[key] ??= value;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ErrorCode = "E_USAGE" | "E_VALIDATION" | "E_IO" | "E_GIT" | "E_BACKEND" | "E_NETWORK" | "E_INTERNAL";
|
|
2
|
+
export declare class CliError extends Error {
|
|
3
|
+
readonly exitCode: number;
|
|
4
|
+
readonly code: ErrorCode;
|
|
5
|
+
readonly context?: Record<string, unknown>;
|
|
6
|
+
constructor(opts: {
|
|
7
|
+
exitCode: number;
|
|
8
|
+
code: ErrorCode;
|
|
9
|
+
message: string;
|
|
10
|
+
context?: Record<string, unknown>;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export declare function formatJsonError(err: CliError, opts?: {
|
|
14
|
+
hint?: string;
|
|
15
|
+
}): string;
|
|
16
|
+
//# sourceMappingURL=errors.d.ts.map
|