scribeclock 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -2
- package/bin/scribeclock.js +21 -0
- package/dist/auth/oauth.js +230 -0
- package/dist/auth/store.js +212 -0
- package/dist/auth/token.js +77 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/errors.js +48 -0
- package/dist/cli/output.js +185 -0
- package/dist/cli/parser.js +230 -0
- package/dist/cli/policy.js +49 -0
- package/dist/cli/registry.js +138 -0
- package/dist/commands/_shared.js +220 -0
- package/dist/commands/auth.js +228 -0
- package/dist/commands/automation.js +420 -0
- package/dist/commands/cf.js +270 -0
- package/dist/commands/clock.js +189 -0
- package/dist/commands/files.js +210 -0
- package/dist/commands/gitflow.js +208 -0
- package/dist/commands/index.js +35 -0
- package/dist/commands/meetings.js +254 -0
- package/dist/commands/meta.js +129 -0
- package/dist/commands/ops.js +324 -0
- package/dist/commands/projects.js +354 -0
- package/dist/commands/tasks.js +492 -0
- package/dist/commands/team.js +190 -0
- package/dist/commands/tools.js +257 -0
- package/dist/commands/vault.js +133 -0
- package/dist/commands/workflow.js +525 -0
- package/dist/config.js +102 -0
- package/dist/context.js +125 -0
- package/dist/git.js +144 -0
- package/dist/index.js +134 -0
- package/dist/mcp/catalog.js +97 -0
- package/dist/mcp/client.js +164 -0
- package/dist/mcp/schema.js +242 -0
- package/dist/render.js +117 -0
- package/dist/types.js +9 -0
- package/dist/update-check.js +28 -0
- package/dist/update.js +47 -0
- package/dist/util/format.js +176 -0
- package/package.json +35 -4
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { usageError } from "../cli/errors.js";
|
|
3
|
+
import { diffLines, fmtDate, parseWhen } from "../util/format.js";
|
|
4
|
+
import { PROJECT_FLAG, bool, cmd, emit, num, personName, projectArg, requirePositional, resolveProjectId, str } from "./_shared.js";
|
|
5
|
+
export const projectsList = cmd({
|
|
6
|
+
path: "projects list",
|
|
7
|
+
group: "Projects & PRDs",
|
|
8
|
+
summary: "Projects with status, progress, repositories and open-task counts.",
|
|
9
|
+
tool: "list_projects",
|
|
10
|
+
positionals: [{ name: "query", description: "Name/description filter." }],
|
|
11
|
+
flags: [
|
|
12
|
+
{ name: "status", alias: "s", type: "string", choices: ["pipeline", "planning", "active", "on_hold", "completed", "archived"], description: "Only this status." },
|
|
13
|
+
{ name: "limit", alias: "n", type: "number", description: "Max projects (default 100)." },
|
|
14
|
+
],
|
|
15
|
+
async run(ctx, inv) {
|
|
16
|
+
const data = await ctx.call("list_projects", { query: inv.positionals.join(" ") || undefined, status: str(inv.flags.status), limit: num(inv.flags.limit) });
|
|
17
|
+
return emit(ctx, data, () => {
|
|
18
|
+
const projects = (data.projects ?? []);
|
|
19
|
+
ctx.out.table(["name", "status", "progress", "open", "urgency", "repos", "organization"], projects.map((p) => [p.name, p.status, `${String(p.progress_percent ?? 0)}%`, p.open_task_count, p.urgency, p.github_repos?.join(", ") ?? "", p.organization?.name]));
|
|
20
|
+
ctx.out.info(ctx.out.dim(`${projects.length} project${projects.length === 1 ? "" : "s"}`));
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
export const projectsShow = cmd({
|
|
25
|
+
path: "projects show",
|
|
26
|
+
group: "Projects & PRDs",
|
|
27
|
+
summary: "Project detail: assignees, task counts, PRD versions, Drive, design links.",
|
|
28
|
+
tool: "get_project",
|
|
29
|
+
positionals: [{ name: "project", description: "Project uuid or exact name (default from .scribeclock.json)." }],
|
|
30
|
+
async run(ctx, inv) {
|
|
31
|
+
const project_id = projectArg(ctx, inv, { required: true, positional: 0 });
|
|
32
|
+
const data = await ctx.call("get_project", { project_id });
|
|
33
|
+
return emit(ctx, data, () => {
|
|
34
|
+
const p = (data.project ?? data);
|
|
35
|
+
ctx.out.log(ctx.out.bold(String(p.name ?? "")));
|
|
36
|
+
ctx.out.kv([
|
|
37
|
+
["status", p.status],
|
|
38
|
+
["urgency", p.urgency],
|
|
39
|
+
["progress", `${String(p.progress_percent ?? 0)}%${p.progress_manual_override ? " (manual)" : ""}`],
|
|
40
|
+
["deadline", p.target_deadline ? fmtDate(String(p.target_deadline)) : ""],
|
|
41
|
+
["repos", p.github_repos?.join(", ")],
|
|
42
|
+
["website", p.website_url],
|
|
43
|
+
["organization", p.organization?.name],
|
|
44
|
+
["tasks", p.task_counts ? JSON.stringify(p.task_counts) : p.open_task_count !== undefined ? `${String(p.open_task_count)} open` : ""],
|
|
45
|
+
["PRD versions", p.prd_version_count ?? (Array.isArray(p.prd_versions) ? p.prd_versions.length : "")],
|
|
46
|
+
["Google Drive", p.has_google_drive ? "linked" : "not linked"],
|
|
47
|
+
["url", p.url],
|
|
48
|
+
]);
|
|
49
|
+
if (typeof p.description === "string" && p.description) {
|
|
50
|
+
ctx.out.log("");
|
|
51
|
+
ctx.out.log(` ${p.description.replace(/<[^>]+>/g, "").trim()}`);
|
|
52
|
+
}
|
|
53
|
+
const assignees = (p.assignees ?? data.assignees ?? []);
|
|
54
|
+
if (assignees.length) {
|
|
55
|
+
ctx.out.log("");
|
|
56
|
+
ctx.out.log(ctx.out.bold("Assignees"));
|
|
57
|
+
for (const a of assignees)
|
|
58
|
+
ctx.out.log(` ${personName(a.person ?? a)}${a.role ? ctx.out.dim(` · ${String(a.role)}`) : ""}`);
|
|
59
|
+
}
|
|
60
|
+
const links = (p.design_links ?? []);
|
|
61
|
+
if (links.length) {
|
|
62
|
+
ctx.out.log("");
|
|
63
|
+
ctx.out.log(ctx.out.bold("Design links"));
|
|
64
|
+
for (const l of links)
|
|
65
|
+
ctx.out.log(` ${String(l.provider ?? l.kind ?? "")} ${String(l.url ?? "")}`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
export const projectsCreate = cmd({
|
|
71
|
+
path: "projects create",
|
|
72
|
+
group: "Projects & PRDs",
|
|
73
|
+
summary: "Create a project (admins/staff).",
|
|
74
|
+
tool: "create_project",
|
|
75
|
+
positionals: [{ name: "name", required: true, description: "Project name." }],
|
|
76
|
+
flags: [
|
|
77
|
+
{ name: "description", alias: "d", type: "string", description: "Description." },
|
|
78
|
+
{ name: "status", alias: "s", type: "string", choices: ["pipeline", "planning", "active", "on_hold", "completed", "archived"], description: "Initial status (default pipeline)." },
|
|
79
|
+
{ name: "urgency", type: "string", choices: ["low", "normal", "high", "urgent"], description: "Urgency." },
|
|
80
|
+
{ name: "deadline", type: "string", placeholder: "<YYYY-MM-DD>", description: "Target deadline." },
|
|
81
|
+
{ name: "repo", type: "string", placeholder: "<owner/repo>", description: "Primary GitHub repository." },
|
|
82
|
+
{ name: "website", type: "string", description: "Website URL." },
|
|
83
|
+
],
|
|
84
|
+
async run(ctx, inv) {
|
|
85
|
+
const name = requirePositional(inv, 0, "A project name", 'scribeclock projects create "Kisa Print"');
|
|
86
|
+
const data = await ctx.call("create_project", { name, description: str(inv.flags.description), status: str(inv.flags.status), urgency: str(inv.flags.urgency), target_deadline: str(inv.flags.deadline), github_repo_full_name: str(inv.flags.repo), website_url: str(inv.flags.website) });
|
|
87
|
+
return emit(ctx, data, () => {
|
|
88
|
+
const p = (data.project ?? data);
|
|
89
|
+
ctx.out.log(`${ctx.out.green("✓")} Created project ${ctx.out.bold(String(p.name ?? name))}${p.url ? ctx.out.dim(` ${String(p.url)}`) : ""}`);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
export const projectsUpdate = cmd({
|
|
94
|
+
path: "projects update",
|
|
95
|
+
group: "Projects & PRDs",
|
|
96
|
+
summary: "Change status, progress, urgency, deadline, description, repo, website or design links.",
|
|
97
|
+
tool: "update_project",
|
|
98
|
+
positionals: [{ name: "project", description: "Project uuid or exact name (default from .scribeclock.json)." }],
|
|
99
|
+
flags: [
|
|
100
|
+
{ name: "name", type: "string", description: "Rename." },
|
|
101
|
+
{ name: "description", alias: "d", type: "string", description: "New description (@file reads a file)." },
|
|
102
|
+
{ name: "status", alias: "s", type: "string", choices: ["pipeline", "planning", "active", "on_hold", "completed", "archived"], description: "New status." },
|
|
103
|
+
{ name: "urgency", type: "string", choices: ["low", "normal", "high", "urgent"], description: "Urgency." },
|
|
104
|
+
{ name: "progress", type: "number", placeholder: "<0-100>", description: "Progress percent (sets a manual override)." },
|
|
105
|
+
{ name: "deadline", type: "string", placeholder: "<YYYY-MM-DD|none>", description: "Target deadline; none clears." },
|
|
106
|
+
{ name: "repo", type: "string", placeholder: "<owner/repo>", description: "GitHub repository." },
|
|
107
|
+
{ name: "website", type: "string", description: "Website URL." },
|
|
108
|
+
{ name: "add-design", type: "string", placeholder: "<url>", description: "Attach a Paper/Figma/Canva/Rive/Stitch link." },
|
|
109
|
+
{ name: "remove-design", type: "string", placeholder: "<url>", description: "Remove a design link." },
|
|
110
|
+
],
|
|
111
|
+
async run(ctx, inv) {
|
|
112
|
+
const project_id = projectArg(ctx, inv, { required: true, positional: 0 });
|
|
113
|
+
const description = typeof inv.flags.description === "string" ? (inv.flags.description.startsWith("@") ? readFileSync(inv.flags.description.slice(1), "utf8") : inv.flags.description) : undefined;
|
|
114
|
+
const args = {
|
|
115
|
+
project_id,
|
|
116
|
+
name: str(inv.flags.name),
|
|
117
|
+
description,
|
|
118
|
+
status: str(inv.flags.status),
|
|
119
|
+
urgency: str(inv.flags.urgency),
|
|
120
|
+
progress_percent: num(inv.flags.progress),
|
|
121
|
+
target_deadline: inv.flags.deadline === "none" ? null : str(inv.flags.deadline),
|
|
122
|
+
github_repo_full_name: str(inv.flags.repo),
|
|
123
|
+
website_url: str(inv.flags.website),
|
|
124
|
+
add_design_link: str(inv.flags["add-design"]),
|
|
125
|
+
remove_design_link: str(inv.flags["remove-design"]),
|
|
126
|
+
};
|
|
127
|
+
if (!Object.entries(args).some(([k, v]) => k !== "project_id" && v !== undefined))
|
|
128
|
+
throw usageError("Nothing to change.", "Pass --status, --progress, --deadline, --description…");
|
|
129
|
+
const data = await ctx.call("update_project", args);
|
|
130
|
+
return emit(ctx, data, () => ctx.out.log(`${ctx.out.green("✓")} Updated ${ctx.out.bold(String((data.project ?? data).name ?? project_id))}.`));
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
export const prdList = cmd({
|
|
134
|
+
path: "prd list",
|
|
135
|
+
group: "Projects & PRDs",
|
|
136
|
+
summary: "Versions / phases of a project's requirements document.",
|
|
137
|
+
tool: "list_prd_versions",
|
|
138
|
+
positionals: [{ name: "project", description: "Project uuid or name (default from .scribeclock.json)." }],
|
|
139
|
+
async run(ctx, inv) {
|
|
140
|
+
const project_id = projectArg(ctx, inv, { required: true, positional: 0 });
|
|
141
|
+
const data = await ctx.call("list_prd_versions", { project_id });
|
|
142
|
+
return emit(ctx, data, () => {
|
|
143
|
+
const versions = (data.versions ?? []);
|
|
144
|
+
if (!versions.length)
|
|
145
|
+
ctx.out.log(ctx.out.dim("No PRD versions yet. Create one: scribeclock prd create --project <p> --file prd.md"));
|
|
146
|
+
else
|
|
147
|
+
ctx.out.table(["version", "phase", "order", "complete", "baseline", "size", "updated"], versions.map((v) => [v.version_code, v.phase_label, v.sort_order, v.completed ? "yes" : "", v.is_production_milestone || v.is_locked_baseline ? "yes" : "", v.size_chars ?? v.size ?? v.markdown_chars, fmtDate(String(v.updated_at ?? v.created_at ?? ""))]));
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
async function fetchPrd(ctx, project_id, version) {
|
|
152
|
+
const args = /^[0-9a-f-]{36}$/i.test(version ?? "") ? { prd_version_id: version } : { project_id, version_code: version };
|
|
153
|
+
return ctx.call("get_prd_version", args);
|
|
154
|
+
}
|
|
155
|
+
function prdMarkdown(data) {
|
|
156
|
+
const v = (data.version ?? data);
|
|
157
|
+
return String(v.requirements_markdown ?? data.requirements_markdown ?? "");
|
|
158
|
+
}
|
|
159
|
+
export const prdShow = cmd({
|
|
160
|
+
path: "prd show",
|
|
161
|
+
group: "Projects & PRDs",
|
|
162
|
+
summary: "Print a PRD version as Markdown (latest by default): prd show [version] --project P.",
|
|
163
|
+
tool: "get_prd_version",
|
|
164
|
+
positionals: [{ name: "version", description: "Version code (v2, MVP) or version uuid. Default: latest." }],
|
|
165
|
+
flags: [PROJECT_FLAG],
|
|
166
|
+
examples: ["scribeclock prd show --project 'Scribe Clock' > prd.md", "scribeclock prd show v2 -p 'Scribe Clock'"],
|
|
167
|
+
async run(ctx, inv) {
|
|
168
|
+
const version = inv.positionals[0];
|
|
169
|
+
const project_id = projectArg(ctx, inv, { required: !/^[0-9a-f-]{36}$/i.test(version ?? "") });
|
|
170
|
+
const data = await fetchPrd(ctx, project_id, version);
|
|
171
|
+
return emit(ctx, data, () => {
|
|
172
|
+
const v = (data.version ?? data);
|
|
173
|
+
if (v.version_code)
|
|
174
|
+
ctx.out.info(ctx.out.dim(`${String(v.version_code)}${v.phase_label ? ` · ${String(v.phase_label)}` : ""}`));
|
|
175
|
+
ctx.out.log(prdMarkdown(data));
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
export const prdCreate = cmd({
|
|
180
|
+
path: "prd create",
|
|
181
|
+
group: "Projects & PRDs",
|
|
182
|
+
summary: "Add a PRD version from a Markdown file: prd create --project P --file prd.md.",
|
|
183
|
+
tool: "create_prd_version",
|
|
184
|
+
flags: [
|
|
185
|
+
PROJECT_FLAG,
|
|
186
|
+
{ name: "file", alias: "f", type: "string", placeholder: "<path>", description: "Markdown file (- for stdin).", required: true },
|
|
187
|
+
{ name: "version", alias: "v", type: "string", description: "Version code (default next vN)." },
|
|
188
|
+
{ name: "phase", type: "string", description: "Phase label." },
|
|
189
|
+
{ name: "order", type: "number", description: "Sort order." },
|
|
190
|
+
{ name: "milestone", type: "boolean", description: "Mark as production milestone." },
|
|
191
|
+
],
|
|
192
|
+
async run(ctx, inv) {
|
|
193
|
+
const project_id = projectArg(ctx, inv, { required: true });
|
|
194
|
+
const file = String(inv.flags.file);
|
|
195
|
+
const requirements_markdown = file === "-" ? readFileSync(0, "utf8") : readFileSync(file, "utf8");
|
|
196
|
+
const data = await ctx.call("create_prd_version", { project_id, requirements_markdown, version_code: str(inv.flags.version), phase_label: str(inv.flags.phase), sort_order: num(inv.flags.order), is_production_milestone: bool(inv.flags.milestone) });
|
|
197
|
+
return emit(ctx, data, () => {
|
|
198
|
+
const v = (data.version ?? data);
|
|
199
|
+
ctx.out.log(`${ctx.out.green("✓")} Created PRD ${ctx.out.bold(String(v.version_code ?? ""))} (${requirements_markdown.length} chars).`);
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
export const prdUpdate = cmd({
|
|
204
|
+
path: "prd update",
|
|
205
|
+
group: "Projects & PRDs",
|
|
206
|
+
summary: "Replace or append to a PRD version, or change its label / order / completion.",
|
|
207
|
+
tool: "update_prd_version",
|
|
208
|
+
positionals: [{ name: "version", description: "Version code or uuid (default: latest when --project is given… pass it explicitly to be safe)." }],
|
|
209
|
+
flags: [
|
|
210
|
+
PROJECT_FLAG,
|
|
211
|
+
{ name: "file", alias: "f", type: "string", placeholder: "<path>", description: "Markdown file with the new content (- for stdin)." },
|
|
212
|
+
{ name: "append", type: "boolean", description: "Append instead of replacing the Markdown." },
|
|
213
|
+
{ name: "phase", type: "string", description: "Phase label." },
|
|
214
|
+
{ name: "order", type: "number", description: "Sort order." },
|
|
215
|
+
{ name: "complete", type: "boolean", description: "Mark complete (--no-complete reopens)." },
|
|
216
|
+
{ name: "milestone", type: "boolean", description: "Production milestone flag." },
|
|
217
|
+
],
|
|
218
|
+
async run(ctx, inv) {
|
|
219
|
+
const version = inv.positionals[0];
|
|
220
|
+
if (!version)
|
|
221
|
+
throw usageError("Which version? Pass its code (v2) or uuid.", "scribeclock prd list --project <p>");
|
|
222
|
+
const project_id = projectArg(ctx, inv, { required: !/^[0-9a-f-]{36}$/i.test(version) });
|
|
223
|
+
const file = str(inv.flags.file);
|
|
224
|
+
const requirements_markdown = file ? (file === "-" ? readFileSync(0, "utf8") : readFileSync(file, "utf8")) : undefined;
|
|
225
|
+
const args = /^[0-9a-f-]{36}$/i.test(version) ? { prd_version_id: version } : { project_id, version_code: version };
|
|
226
|
+
Object.assign(args, { requirements_markdown, mode: inv.flags.append ? "append" : undefined, phase_label: str(inv.flags.phase), sort_order: num(inv.flags.order), completed: bool(inv.flags.complete), is_production_milestone: bool(inv.flags.milestone) });
|
|
227
|
+
const data = await ctx.call("update_prd_version", args);
|
|
228
|
+
return emit(ctx, data, () => ctx.out.log(`${ctx.out.green("✓")} Updated PRD ${version}.`));
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
export const prdDiff = cmd({
|
|
232
|
+
path: "prd diff",
|
|
233
|
+
group: "Projects & PRDs",
|
|
234
|
+
summary: "Line diff between two PRD versions: prd diff v1 v2 --project P.",
|
|
235
|
+
tool: "get_prd_version",
|
|
236
|
+
positionals: [{ name: "from", required: true, description: "Older version code or uuid." }, { name: "to", required: true, description: "Newer version code or uuid." }],
|
|
237
|
+
flags: [PROJECT_FLAG, { name: "context", alias: "c", type: "number", description: "Unchanged lines to show around changes (default 2; -1 for all)." }],
|
|
238
|
+
examples: ["scribeclock prd diff v1 v2 --project 'Scribe Clock'"],
|
|
239
|
+
async run(ctx, inv) {
|
|
240
|
+
const [from, to] = [requirePositional(inv, 0, "The older version", "scribeclock prd diff v1 v2 -p <project>"), requirePositional(inv, 1, "The newer version", "scribeclock prd diff v1 v2 -p <project>")];
|
|
241
|
+
const project_id = projectArg(ctx, inv, { required: !(/^[0-9a-f-]{36}$/i.test(from) && /^[0-9a-f-]{36}$/i.test(to)) });
|
|
242
|
+
const [a, b] = await Promise.all([fetchPrd(ctx, project_id, from), fetchPrd(ctx, project_id, to)]);
|
|
243
|
+
const diff = diffLines(prdMarkdown(a).split("\n"), prdMarkdown(b).split("\n"));
|
|
244
|
+
const added = diff.filter((d) => d.op === "+").length;
|
|
245
|
+
const removed = diff.filter((d) => d.op === "-").length;
|
|
246
|
+
if (ctx.flags.json) {
|
|
247
|
+
ctx.out.jsonOut({ ok: true, from, to, added, removed, hunks: diff.filter((d) => d.op !== " ").map((d) => ({ op: d.op, text: d.text })), lines: diff });
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
const context = inv.flags.context === undefined ? 2 : Number(inv.flags.context);
|
|
251
|
+
ctx.out.log(ctx.out.bold(`--- ${from} +++ ${to} ${ctx.out.green(`+${added}`)} ${ctx.out.red(`-${removed}`)}`));
|
|
252
|
+
const keep = new Set();
|
|
253
|
+
diff.forEach((d, i) => {
|
|
254
|
+
if (d.op !== " ")
|
|
255
|
+
for (let j = Math.max(0, i - context); j <= Math.min(diff.length - 1, i + context); j++)
|
|
256
|
+
keep.add(j);
|
|
257
|
+
});
|
|
258
|
+
let lastShown = -1;
|
|
259
|
+
diff.forEach((d, i) => {
|
|
260
|
+
if (context >= 0 && !keep.has(i))
|
|
261
|
+
return;
|
|
262
|
+
if (lastShown >= 0 && i > lastShown + 1)
|
|
263
|
+
ctx.out.log(ctx.out.dim(" …"));
|
|
264
|
+
lastShown = i;
|
|
265
|
+
const line = `${d.op} ${d.text}`;
|
|
266
|
+
ctx.out.log(d.op === "+" ? ctx.out.green(line) : d.op === "-" ? ctx.out.red(line) : ctx.out.dim(line));
|
|
267
|
+
});
|
|
268
|
+
return 0;
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
/** Headings, task-list items and "As a … I want …" stories → draft tasks. */
|
|
272
|
+
export function prdToDraftTasks(markdown) {
|
|
273
|
+
const drafts = [];
|
|
274
|
+
let section = "";
|
|
275
|
+
const lines = markdown.split("\n");
|
|
276
|
+
for (let i = 0; i < lines.length; i++) {
|
|
277
|
+
const line = lines[i];
|
|
278
|
+
const h = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
279
|
+
if (h) {
|
|
280
|
+
section = h[2].trim();
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const task = /^\s*[-*]\s+\[[ xX]?\]\s+(.*)$/.exec(line);
|
|
284
|
+
const story = /^\s*(?:[-*]\s+)?(As an? .+?,? I (?:want|need|can) .+)$/i.exec(line);
|
|
285
|
+
const req = /^\s*(?:[-*]|\d+\.)\s+((?:The (?:system|app|user|admin)|Users?|Admins?|Must|Should|Shall)\b.*)$/i.exec(line);
|
|
286
|
+
const text = task?.[1] ?? story?.[1] ?? req?.[1];
|
|
287
|
+
if (!text)
|
|
288
|
+
continue;
|
|
289
|
+
const title = text.replace(/\*\*/g, "").trim().slice(0, 140);
|
|
290
|
+
if (title.length < 6)
|
|
291
|
+
continue;
|
|
292
|
+
// Collect indented continuation lines as the description.
|
|
293
|
+
const detail = [];
|
|
294
|
+
for (let j = i + 1; j < lines.length && /^\s{2,}\S/.test(lines[j]); j++)
|
|
295
|
+
detail.push(lines[j].trim());
|
|
296
|
+
drafts.push({ title, description: [section ? `From PRD section: ${section}` : "", ...detail].filter(Boolean).join("\n"), section });
|
|
297
|
+
}
|
|
298
|
+
return drafts;
|
|
299
|
+
}
|
|
300
|
+
export const prdToTasks = cmd({
|
|
301
|
+
path: "prd to-tasks",
|
|
302
|
+
group: "Projects & PRDs",
|
|
303
|
+
summary: "Break a PRD version into draft tasks (checklist items, user stories, requirements) and create them after review.",
|
|
304
|
+
tool: ["get_prd_version", "create_task"],
|
|
305
|
+
positionals: [{ name: "version", description: "Version code or uuid (default latest)." }],
|
|
306
|
+
flags: [
|
|
307
|
+
PROJECT_FLAG,
|
|
308
|
+
{ name: "create", type: "boolean", description: "Create the tasks (otherwise only the drafts are printed)." },
|
|
309
|
+
{ name: "priority", type: "string", choices: ["low", "medium", "high"], description: "Priority for created tasks." },
|
|
310
|
+
{ name: "assignee", alias: "a", type: "string", description: "Assignee for created tasks." },
|
|
311
|
+
{ name: "limit", alias: "n", type: "number", description: "Max drafts (default 50)." },
|
|
312
|
+
],
|
|
313
|
+
examples: ["scribeclock prd to-tasks --project 'Scribe Clock'", "scribeclock prd to-tasks v2 -p 'Scribe Clock' --create --yes"],
|
|
314
|
+
async run(ctx, inv) {
|
|
315
|
+
const version = inv.positionals[0];
|
|
316
|
+
const project_id = projectArg(ctx, inv, { required: !/^[0-9a-f-]{36}$/i.test(version ?? "") });
|
|
317
|
+
const data = await fetchPrd(ctx, project_id, version);
|
|
318
|
+
const drafts = prdToDraftTasks(prdMarkdown(data)).slice(0, num(inv.flags.limit) ?? 50);
|
|
319
|
+
if (!inv.flags.create) {
|
|
320
|
+
return emit(ctx, { ok: true, count: drafts.length, drafts }, () => {
|
|
321
|
+
if (!drafts.length)
|
|
322
|
+
ctx.out.log(ctx.out.dim("No checklist items, user stories or requirement lines found."));
|
|
323
|
+
else
|
|
324
|
+
ctx.out.table(["#", "title", "section"], drafts.map((d, i) => [i + 1, d.title, d.section]));
|
|
325
|
+
ctx.out.info(ctx.out.dim(`${drafts.length} drafts. Add --create to file them as tasks.`));
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (!drafts.length)
|
|
329
|
+
throw usageError("Nothing to create.");
|
|
330
|
+
const { confirm } = await import("../cli/confirm.js");
|
|
331
|
+
if (!ctx.flags.dryRun) {
|
|
332
|
+
const ok = await confirm(ctx, `Create ${drafts.length} tasks in project ${project_id ?? "(from version)"}?`);
|
|
333
|
+
if (!ok)
|
|
334
|
+
return 1;
|
|
335
|
+
}
|
|
336
|
+
const created = [];
|
|
337
|
+
for (const d of drafts) {
|
|
338
|
+
if (ctx.flags.dryRun) {
|
|
339
|
+
ctx.out.log(`${ctx.out.dim("dry run →")} create_task ${JSON.stringify({ title: d.title, project_id })}`);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const r = await ctx.call("create_task", { title: d.title, description: d.description || undefined, project_id: await resolveProjectId(ctx, typeof project_id === "string" ? project_id : undefined), priority: str(inv.flags.priority), assignee: str(inv.flags.assignee) });
|
|
343
|
+
created.push((r.task ?? r));
|
|
344
|
+
ctx.out.info(`${ctx.out.green("✓")} ${String((r.task ?? r).key ?? "")} ${d.title}`);
|
|
345
|
+
}
|
|
346
|
+
if (ctx.flags.json)
|
|
347
|
+
ctx.out.jsonOut({ ok: true, created: created.length, tasks: created });
|
|
348
|
+
else if (!ctx.flags.dryRun)
|
|
349
|
+
ctx.out.log(`Created ${created.length} tasks.`);
|
|
350
|
+
return 0;
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
export const PROJECT_COMMANDS = [projectsList, projectsShow, projectsCreate, projectsUpdate, prdList, prdShow, prdCreate, prdUpdate, prdDiff, prdToTasks];
|
|
354
|
+
void parseWhen;
|