@kitsy/coop 2.2.0 → 2.2.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/dist/index.js +682 -194
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -237,8 +237,8 @@ function findTaskFileById(root, id) {
|
|
|
237
237
|
function todayIsoDate() {
|
|
238
238
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
239
239
|
}
|
|
240
|
-
function normalizeIdPart(
|
|
241
|
-
const normalized =
|
|
240
|
+
function normalizeIdPart(input2, fallback, maxLength = 12) {
|
|
241
|
+
const normalized = input2.toUpperCase().replace(/[^A-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-").replace(/-/g, "");
|
|
242
242
|
if (!normalized) return fallback;
|
|
243
243
|
return normalized.slice(0, maxLength);
|
|
244
244
|
}
|
|
@@ -274,24 +274,24 @@ function shortDateToken(now = /* @__PURE__ */ new Date()) {
|
|
|
274
274
|
function randomToken() {
|
|
275
275
|
return crypto.randomBytes(4).toString("hex").toUpperCase();
|
|
276
276
|
}
|
|
277
|
-
function sanitizeTemplateValue(
|
|
278
|
-
const normalized =
|
|
277
|
+
function sanitizeTemplateValue(input2, fallback = "X") {
|
|
278
|
+
const normalized = input2.toUpperCase().replace(/[^A-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
279
279
|
return normalized || fallback;
|
|
280
280
|
}
|
|
281
|
-
function sanitizeSemanticWord(
|
|
282
|
-
return
|
|
281
|
+
function sanitizeSemanticWord(input2) {
|
|
282
|
+
return input2.toUpperCase().replace(/[^A-Z0-9]+/g, "").trim();
|
|
283
283
|
}
|
|
284
|
-
function semanticWords(
|
|
285
|
-
const rawWords =
|
|
284
|
+
function semanticWords(input2) {
|
|
285
|
+
const rawWords = input2.split(/[^a-zA-Z0-9]+/g).map(sanitizeSemanticWord).filter(Boolean);
|
|
286
286
|
if (rawWords.length <= 1) {
|
|
287
287
|
return rawWords;
|
|
288
288
|
}
|
|
289
289
|
const filtered = rawWords.filter((word) => !SEMANTIC_STOP_WORDS.has(word));
|
|
290
290
|
return filtered.length > 0 ? filtered : rawWords;
|
|
291
291
|
}
|
|
292
|
-
function semanticTitleToken(
|
|
292
|
+
function semanticTitleToken(input2, maxLength = DEFAULT_TITLE_TOKEN_LENGTH) {
|
|
293
293
|
const safeMaxLength = Number.isFinite(maxLength) ? Math.max(4, Math.floor(maxLength)) : DEFAULT_TITLE_TOKEN_LENGTH;
|
|
294
|
-
const words = semanticWords(
|
|
294
|
+
const words = semanticWords(input2);
|
|
295
295
|
if (words.length === 0) {
|
|
296
296
|
return "UNTITLED";
|
|
297
297
|
}
|
|
@@ -452,27 +452,27 @@ function generateConfiguredId(root, existingIds, context) {
|
|
|
452
452
|
|
|
453
453
|
// src/utils/aliases.ts
|
|
454
454
|
var ALIAS_PATTERN = /^[A-Z0-9]+(?:[.-][A-Z0-9]+)*$/;
|
|
455
|
-
function toPosixPath(
|
|
456
|
-
return
|
|
455
|
+
function toPosixPath(input2) {
|
|
456
|
+
return input2.replace(/\\/g, "/");
|
|
457
457
|
}
|
|
458
458
|
function indexFilePath(root) {
|
|
459
459
|
const { indexDataFormat } = readCoopConfig(root);
|
|
460
460
|
const extension = indexDataFormat === "json" ? "json" : "yml";
|
|
461
461
|
return path2.join(ensureCoopInitialized(root), ".index", `aliases.${extension}`);
|
|
462
462
|
}
|
|
463
|
-
function normalizeAliasValue(
|
|
464
|
-
return
|
|
463
|
+
function normalizeAliasValue(input2) {
|
|
464
|
+
return input2.trim().toUpperCase().replace(/_/g, ".").replace(/\.+/g, ".");
|
|
465
465
|
}
|
|
466
|
-
function normalizePatternValue(
|
|
467
|
-
return
|
|
466
|
+
function normalizePatternValue(input2) {
|
|
467
|
+
return input2.trim().toUpperCase().replace(/_/g, ".");
|
|
468
468
|
}
|
|
469
|
-
function normalizeAlias(
|
|
470
|
-
const normalized = normalizeAliasValue(
|
|
469
|
+
function normalizeAlias(input2) {
|
|
470
|
+
const normalized = normalizeAliasValue(input2);
|
|
471
471
|
if (!normalized) {
|
|
472
472
|
throw new Error("Alias cannot be empty.");
|
|
473
473
|
}
|
|
474
474
|
if (!ALIAS_PATTERN.test(normalized)) {
|
|
475
|
-
throw new Error(`Invalid alias '${
|
|
475
|
+
throw new Error(`Invalid alias '${input2}'. Use letters/numbers with '.' and '-'.`);
|
|
476
476
|
}
|
|
477
477
|
return normalized;
|
|
478
478
|
}
|
|
@@ -659,8 +659,8 @@ function updateTaskAliases(filePath, aliases) {
|
|
|
659
659
|
function updateIdeaAliases(filePath, aliases) {
|
|
660
660
|
const parsed = parseIdeaFile(filePath);
|
|
661
661
|
const nextRaw = { ...parsed.raw, aliases };
|
|
662
|
-
const
|
|
663
|
-
fs2.writeFileSync(filePath,
|
|
662
|
+
const output2 = stringifyFrontmatter(nextRaw, parsed.body);
|
|
663
|
+
fs2.writeFileSync(filePath, output2, "utf8");
|
|
664
664
|
}
|
|
665
665
|
function resolveFilePath(root, target) {
|
|
666
666
|
return path2.join(root, ...target.file.split("/"));
|
|
@@ -832,8 +832,8 @@ import { Octokit } from "octokit";
|
|
|
832
832
|
function isObject(value) {
|
|
833
833
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
834
834
|
}
|
|
835
|
-
function sanitizeBranchPart(
|
|
836
|
-
const normalized =
|
|
835
|
+
function sanitizeBranchPart(input2, fallback) {
|
|
836
|
+
const normalized = input2.trim().toLowerCase().replace(/[^a-z0-9/_-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
837
837
|
return normalized || fallback;
|
|
838
838
|
}
|
|
839
839
|
function readGitRemote(root) {
|
|
@@ -993,15 +993,15 @@ function toGitHubClient(config) {
|
|
|
993
993
|
baseUrl: config.apiBaseUrl
|
|
994
994
|
});
|
|
995
995
|
return {
|
|
996
|
-
async createPullRequest(
|
|
996
|
+
async createPullRequest(input2) {
|
|
997
997
|
const response = await octokit.rest.pulls.create({
|
|
998
|
-
owner:
|
|
999
|
-
repo:
|
|
1000
|
-
title:
|
|
1001
|
-
body:
|
|
1002
|
-
head:
|
|
1003
|
-
base:
|
|
1004
|
-
draft:
|
|
998
|
+
owner: input2.owner,
|
|
999
|
+
repo: input2.repo,
|
|
1000
|
+
title: input2.title,
|
|
1001
|
+
body: input2.body,
|
|
1002
|
+
head: input2.head,
|
|
1003
|
+
base: input2.base,
|
|
1004
|
+
draft: input2.draft
|
|
1005
1005
|
});
|
|
1006
1006
|
return {
|
|
1007
1007
|
number: response.data.number,
|
|
@@ -1011,14 +1011,14 @@ function toGitHubClient(config) {
|
|
|
1011
1011
|
draft: response.data.draft
|
|
1012
1012
|
};
|
|
1013
1013
|
},
|
|
1014
|
-
async updatePullRequest(
|
|
1014
|
+
async updatePullRequest(input2) {
|
|
1015
1015
|
const response = await octokit.rest.pulls.update({
|
|
1016
|
-
owner:
|
|
1017
|
-
repo:
|
|
1018
|
-
pull_number:
|
|
1019
|
-
title:
|
|
1020
|
-
body:
|
|
1021
|
-
base:
|
|
1016
|
+
owner: input2.owner,
|
|
1017
|
+
repo: input2.repo,
|
|
1018
|
+
pull_number: input2.pull_number,
|
|
1019
|
+
title: input2.title,
|
|
1020
|
+
body: input2.body,
|
|
1021
|
+
base: input2.base
|
|
1022
1022
|
});
|
|
1023
1023
|
return {
|
|
1024
1024
|
number: response.data.number,
|
|
@@ -1028,11 +1028,11 @@ function toGitHubClient(config) {
|
|
|
1028
1028
|
draft: response.data.draft
|
|
1029
1029
|
};
|
|
1030
1030
|
},
|
|
1031
|
-
async getPullRequest(
|
|
1031
|
+
async getPullRequest(input2) {
|
|
1032
1032
|
const response = await octokit.rest.pulls.get({
|
|
1033
|
-
owner:
|
|
1034
|
-
repo:
|
|
1035
|
-
pull_number:
|
|
1033
|
+
owner: input2.owner,
|
|
1034
|
+
repo: input2.repo,
|
|
1035
|
+
pull_number: input2.pull_number
|
|
1036
1036
|
});
|
|
1037
1037
|
return {
|
|
1038
1038
|
number: response.data.number,
|
|
@@ -1042,12 +1042,12 @@ function toGitHubClient(config) {
|
|
|
1042
1042
|
draft: response.data.draft
|
|
1043
1043
|
};
|
|
1044
1044
|
},
|
|
1045
|
-
async mergePullRequest(
|
|
1045
|
+
async mergePullRequest(input2) {
|
|
1046
1046
|
const response = await octokit.rest.pulls.merge({
|
|
1047
|
-
owner:
|
|
1048
|
-
repo:
|
|
1049
|
-
pull_number:
|
|
1050
|
-
merge_method:
|
|
1047
|
+
owner: input2.owner,
|
|
1048
|
+
repo: input2.repo,
|
|
1049
|
+
pull_number: input2.pull_number,
|
|
1050
|
+
merge_method: input2.merge_method
|
|
1051
1051
|
});
|
|
1052
1052
|
return {
|
|
1053
1053
|
merged: response.data.merged,
|
|
@@ -1416,8 +1416,8 @@ function loadIdeaEntry(root, idOrAlias) {
|
|
|
1416
1416
|
return { filePath, parsed: parseIdeaFile2(filePath) };
|
|
1417
1417
|
}
|
|
1418
1418
|
function writeIdeaFile(filePath, parsed, idea, body = parsed.body) {
|
|
1419
|
-
const
|
|
1420
|
-
fs3.writeFileSync(filePath,
|
|
1419
|
+
const output2 = stringifyFrontmatter2({ ...parsed.raw, ...idea }, body);
|
|
1420
|
+
fs3.writeFileSync(filePath, output2, "utf8");
|
|
1421
1421
|
}
|
|
1422
1422
|
function validateTaskForWrite(task, filePath) {
|
|
1423
1423
|
const structuralIssues = validateStructural2(task, { filePath });
|
|
@@ -1574,12 +1574,12 @@ function clearWorkingContext(root, coopHome, scope) {
|
|
|
1574
1574
|
delete next[scope];
|
|
1575
1575
|
return writeWorkingContext(root, coopHome, next);
|
|
1576
1576
|
}
|
|
1577
|
-
function resolveContextValueWithSource(explicit,
|
|
1577
|
+
function resolveContextValueWithSource(explicit, contextValue2, sharedDefault2) {
|
|
1578
1578
|
if (explicit?.trim()) {
|
|
1579
1579
|
return { value: explicit.trim(), source: "arg" };
|
|
1580
1580
|
}
|
|
1581
|
-
if (
|
|
1582
|
-
return { value:
|
|
1581
|
+
if (contextValue2?.trim()) {
|
|
1582
|
+
return { value: contextValue2.trim(), source: "use" };
|
|
1583
1583
|
}
|
|
1584
1584
|
if (sharedDefault2?.trim()) {
|
|
1585
1585
|
return { value: sharedDefault2.trim(), source: "config" };
|
|
@@ -2143,10 +2143,11 @@ import {
|
|
|
2143
2143
|
import { create_provider_idea_decomposer, decompose_idea_to_tasks } from "@kitsy/coop-ai";
|
|
2144
2144
|
|
|
2145
2145
|
// src/utils/prompt.ts
|
|
2146
|
-
import readline from "readline
|
|
2146
|
+
import readline from "readline";
|
|
2147
|
+
import readlinePromises from "readline/promises";
|
|
2147
2148
|
import process2 from "process";
|
|
2148
2149
|
async function ask(question, defaultValue = "") {
|
|
2149
|
-
const rl =
|
|
2150
|
+
const rl = readlinePromises.createInterface({
|
|
2150
2151
|
input: process2.stdin,
|
|
2151
2152
|
output: process2.stdout
|
|
2152
2153
|
});
|
|
@@ -2155,6 +2156,71 @@ async function ask(question, defaultValue = "") {
|
|
|
2155
2156
|
rl.close();
|
|
2156
2157
|
return answer || defaultValue;
|
|
2157
2158
|
}
|
|
2159
|
+
function renderSelect(question, choices, selected) {
|
|
2160
|
+
process2.stdout.write(`${question}
|
|
2161
|
+
`);
|
|
2162
|
+
for (let index = 0; index < choices.length; index += 1) {
|
|
2163
|
+
const choice = choices[index];
|
|
2164
|
+
const prefix = index === selected ? ">" : " ";
|
|
2165
|
+
const hint = choice.hint ? ` - ${choice.hint}` : "";
|
|
2166
|
+
process2.stdout.write(` ${prefix} ${choice.label}${hint}
|
|
2167
|
+
`);
|
|
2168
|
+
}
|
|
2169
|
+
process2.stdout.write("\nUse \u2191/\u2193 to choose, Enter to confirm.\n");
|
|
2170
|
+
}
|
|
2171
|
+
function moveCursorUp(lines) {
|
|
2172
|
+
if (lines <= 0) return;
|
|
2173
|
+
readline.moveCursor(process2.stdout, 0, -lines);
|
|
2174
|
+
readline.clearScreenDown(process2.stdout);
|
|
2175
|
+
}
|
|
2176
|
+
async function select(question, choices, defaultIndex = 0) {
|
|
2177
|
+
if (choices.length === 0) {
|
|
2178
|
+
throw new Error(`No choices available for '${question}'.`);
|
|
2179
|
+
}
|
|
2180
|
+
if (!process2.stdin.isTTY || !process2.stdout.isTTY) {
|
|
2181
|
+
return choices[Math.min(Math.max(defaultIndex, 0), choices.length - 1)].value;
|
|
2182
|
+
}
|
|
2183
|
+
readline.emitKeypressEvents(process2.stdin);
|
|
2184
|
+
const previousRawMode = process2.stdin.isRaw;
|
|
2185
|
+
process2.stdin.setRawMode(true);
|
|
2186
|
+
let selected = Math.min(Math.max(defaultIndex, 0), choices.length - 1);
|
|
2187
|
+
const renderedLines = choices.length + 2;
|
|
2188
|
+
renderSelect(question, choices, selected);
|
|
2189
|
+
return await new Promise((resolve, reject) => {
|
|
2190
|
+
const cleanup = () => {
|
|
2191
|
+
process2.stdin.off("keypress", onKeypress);
|
|
2192
|
+
process2.stdin.setRawMode(previousRawMode ?? false);
|
|
2193
|
+
process2.stdout.write("\n");
|
|
2194
|
+
};
|
|
2195
|
+
const rerender = () => {
|
|
2196
|
+
moveCursorUp(renderedLines + 1);
|
|
2197
|
+
renderSelect(question, choices, selected);
|
|
2198
|
+
};
|
|
2199
|
+
const onKeypress = (_input, key) => {
|
|
2200
|
+
if (key.name === "up") {
|
|
2201
|
+
selected = selected === 0 ? choices.length - 1 : selected - 1;
|
|
2202
|
+
rerender();
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
if (key.name === "down") {
|
|
2206
|
+
selected = selected === choices.length - 1 ? 0 : selected + 1;
|
|
2207
|
+
rerender();
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
if (key.name === "return") {
|
|
2211
|
+
const value = choices[selected].value;
|
|
2212
|
+
cleanup();
|
|
2213
|
+
resolve(value);
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
if (key.ctrl && key.name === "c") {
|
|
2217
|
+
cleanup();
|
|
2218
|
+
reject(new Error("Prompt cancelled."));
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
process2.stdin.on("keypress", onKeypress);
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2158
2224
|
|
|
2159
2225
|
// src/utils/idea-drafts.ts
|
|
2160
2226
|
import fs5 from "fs";
|
|
@@ -2580,28 +2646,28 @@ function updateIdeaLinkedTasks(filePath, idea, raw, body, linked) {
|
|
|
2580
2646
|
};
|
|
2581
2647
|
fs7.writeFileSync(filePath, stringifyFrontmatter4(nextRaw, body), "utf8");
|
|
2582
2648
|
}
|
|
2583
|
-
function makeTaskDraft(
|
|
2649
|
+
function makeTaskDraft(input2) {
|
|
2584
2650
|
return {
|
|
2585
|
-
title:
|
|
2586
|
-
type:
|
|
2587
|
-
status:
|
|
2588
|
-
track:
|
|
2589
|
-
priority:
|
|
2590
|
-
body:
|
|
2591
|
-
acceptance: unique(
|
|
2592
|
-
testsRequired: unique(
|
|
2593
|
-
authorityRefs: unique(
|
|
2594
|
-
derivedRefs: unique(
|
|
2651
|
+
title: input2.title,
|
|
2652
|
+
type: input2.type,
|
|
2653
|
+
status: input2.status,
|
|
2654
|
+
track: input2.track,
|
|
2655
|
+
priority: input2.priority,
|
|
2656
|
+
body: input2.body,
|
|
2657
|
+
acceptance: unique(input2.acceptance ?? []),
|
|
2658
|
+
testsRequired: unique(input2.testsRequired ?? []),
|
|
2659
|
+
authorityRefs: unique(input2.authorityRefs ?? []),
|
|
2660
|
+
derivedRefs: unique(input2.derivedRefs ?? [])
|
|
2595
2661
|
};
|
|
2596
2662
|
}
|
|
2597
2663
|
function registerCreateCommand(program) {
|
|
2598
2664
|
const create = program.command("create").description("Create COOP entities");
|
|
2599
|
-
create.command("task").description("Create a task").argument("[title]", "Task title").option("--id <id>", "Task id").option("--from <idea>", "Create task(s) from an idea id/alias").option("--ai", "Use AI-assisted decomposition for --from").option("--title <title>", "Task title").option("--type <type>", `Task type (${Object.values(TaskType2).join(", ")})`).option("--status <status>", `Task status (${Object.values(TaskStatus3).join(", ")})`).option("--track <track>", "
|
|
2665
|
+
create.command("task").description("Create a task").argument("[title]", "Task title").option("--id <id>", "Task id").option("--from <idea>", "Create task(s) from an idea id/alias").option("--ai", "Use AI-assisted decomposition for --from").option("--title <title>", "Task title").option("--type <type>", `Task type (${Object.values(TaskType2).join(", ")})`).option("--status <status>", `Task status (${Object.values(TaskStatus3).join(", ")})`).option("--track <track>", "Home/origin track id").option("--delivery <delivery>", "Primary delivery id").option("--priority <priority>", "Task priority").option("--body <body>", "Markdown body").option("--acceptance <items>", "Comma-separated acceptance criteria", collectMultiValue, []).option("--tests-required <items>", "Comma-separated required tests", collectMultiValue, []).option("--authority-ref <ref>", "Authority document reference", collectMultiValue, []).option("--derived-ref <ref>", "Derived planning document reference", collectMultiValue, []).option("--from-file <path>", "Create task(s) from task draft/refinement draft file").option("--stdin", "Read task draft/refinement draft from stdin").option("--interactive", "Prompt for optional fields").action(async (titleArg, options) => {
|
|
2600
2666
|
const root = resolveRepoRoot();
|
|
2601
2667
|
const coop = ensureCoopInitialized(root);
|
|
2602
2668
|
const interactive = Boolean(options.interactive);
|
|
2603
2669
|
if (options.fromFile?.trim() || options.stdin) {
|
|
2604
|
-
if (options.id || options.from || options.ai || options.title || titleArg || options.type || options.status || options.track || options.priority || options.body || (options.acceptance?.length ?? 0) > 0 || (options.testsRequired?.length ?? 0) > 0 || (options.authorityRef?.length ?? 0) > 0 || (options.derivedRef?.length ?? 0) > 0) {
|
|
2670
|
+
if (options.id || options.from || options.ai || options.title || titleArg || options.type || options.status || options.track || options.delivery || options.priority || options.body || (options.acceptance?.length ?? 0) > 0 || (options.testsRequired?.length ?? 0) > 0 || (options.authorityRef?.length ?? 0) > 0 || (options.derivedRef?.length ?? 0) > 0) {
|
|
2605
2671
|
throw new Error("Cannot combine --from-file/--stdin with direct task field flags. Use one input mode.");
|
|
2606
2672
|
}
|
|
2607
2673
|
const draftInput = await readDraftContent(root, {
|
|
@@ -2623,6 +2689,7 @@ function registerCreateCommand(program) {
|
|
|
2623
2689
|
const statusInput = (options.status?.trim() || (interactive ? await ask("Task status", "todo") : "todo")).toLowerCase();
|
|
2624
2690
|
const track = options.track?.trim() || (interactive ? await ask("Track", "unassigned") : "unassigned");
|
|
2625
2691
|
const priority = options.priority?.trim() || (interactive ? await ask("Priority", "p2") : "p2");
|
|
2692
|
+
const delivery = options.delivery?.trim() || (interactive ? await ask("Delivery (optional)", "") : "");
|
|
2626
2693
|
const body = options.body ?? (interactive ? await ask("Task body (optional)", "") : "");
|
|
2627
2694
|
const acceptance = options.acceptance && options.acceptance.length > 0 ? unique(options.acceptance) : interactive ? parseCsv(await ask("Acceptance criteria (comma-separated, optional)", "")) : [];
|
|
2628
2695
|
const testsRequired = options.testsRequired && options.testsRequired.length > 0 ? unique(options.testsRequired) : interactive ? parseCsv(await ask("Tests required (comma-separated, optional)", "")) : [];
|
|
@@ -2733,6 +2800,7 @@ function registerCreateCommand(program) {
|
|
|
2733
2800
|
aliases: [],
|
|
2734
2801
|
track: draft.track,
|
|
2735
2802
|
priority: draft.priority,
|
|
2803
|
+
delivery: delivery || void 0,
|
|
2736
2804
|
acceptance: draft.acceptance,
|
|
2737
2805
|
tests_required: draft.testsRequired,
|
|
2738
2806
|
origin: draft.authorityRefs.length > 0 || draft.derivedRefs.length > 0 || options.from?.trim() ? {
|
|
@@ -2983,6 +3051,27 @@ ${message}`);
|
|
|
2983
3051
|
}
|
|
2984
3052
|
|
|
2985
3053
|
// src/commands/current.ts
|
|
3054
|
+
function contextValue(value) {
|
|
3055
|
+
return value?.trim() || "unset";
|
|
3056
|
+
}
|
|
3057
|
+
function selectionScope(context) {
|
|
3058
|
+
if (context.delivery?.trim()) {
|
|
3059
|
+
return `delivery '${context.delivery.trim()}'`;
|
|
3060
|
+
}
|
|
3061
|
+
if (context.track?.trim()) {
|
|
3062
|
+
return `track '${context.track.trim()}'`;
|
|
3063
|
+
}
|
|
3064
|
+
return "workspace-wide (no working track or delivery set)";
|
|
3065
|
+
}
|
|
3066
|
+
function selectionReason(context, score) {
|
|
3067
|
+
if (context.delivery?.trim()) {
|
|
3068
|
+
return `top ready task for delivery '${context.delivery.trim()}' with score ${score.toFixed(1)}`;
|
|
3069
|
+
}
|
|
3070
|
+
if (context.track?.trim()) {
|
|
3071
|
+
return `top ready task for track '${context.track.trim()}' with score ${score.toFixed(1)}`;
|
|
3072
|
+
}
|
|
3073
|
+
return `top ready task across the workspace with score ${score.toFixed(1)}`;
|
|
3074
|
+
}
|
|
2986
3075
|
function registerCurrentCommand(program) {
|
|
2987
3076
|
program.command("current").description("Show active project, working context, my in-progress tasks, and the next ready task").action(() => {
|
|
2988
3077
|
const root = resolveRepoRoot();
|
|
@@ -2994,9 +3083,14 @@ function registerCurrentCommand(program) {
|
|
|
2994
3083
|
);
|
|
2995
3084
|
console.log(`Project: ${identity.name} (${identity.id})`);
|
|
2996
3085
|
console.log(`Actor: ${actor}`);
|
|
2997
|
-
console.log(
|
|
2998
|
-
console.log(
|
|
2999
|
-
console.log(
|
|
3086
|
+
console.log("");
|
|
3087
|
+
console.log("Working Context:");
|
|
3088
|
+
console.log(`- Track: ${contextValue(context.track)}`);
|
|
3089
|
+
console.log(`- Delivery: ${contextValue(context.delivery)}`);
|
|
3090
|
+
console.log(`- Version: ${contextValue(context.version)}`);
|
|
3091
|
+
if (!context.track?.trim() && !context.delivery?.trim()) {
|
|
3092
|
+
console.log("- Hint: use `coop use track <id>` and/or `coop use delivery <id>` to set your working context.");
|
|
3093
|
+
}
|
|
3000
3094
|
console.log("");
|
|
3001
3095
|
console.log("My Active Tasks:");
|
|
3002
3096
|
if (inProgress.length === 0) {
|
|
@@ -3014,6 +3108,11 @@ function registerCurrentCommand(program) {
|
|
|
3014
3108
|
delivery: context.delivery,
|
|
3015
3109
|
version: context.version
|
|
3016
3110
|
});
|
|
3111
|
+
console.log(`Selection scope: ${selectionScope(context)}`);
|
|
3112
|
+
console.log(`Why selected: ${selectionReason(context, selected.entry.score)}`);
|
|
3113
|
+
if ((selected.entry.task.track ?? "").trim().toLowerCase() === "unassigned") {
|
|
3114
|
+
console.log("Warning: selected task has no assigned track.");
|
|
3115
|
+
}
|
|
3017
3116
|
console.log(formatSelectedTask(selected.entry, selected.selection));
|
|
3018
3117
|
} catch (error) {
|
|
3019
3118
|
console.log(error instanceof Error ? error.message : String(error));
|
|
@@ -3024,7 +3123,7 @@ function registerCurrentCommand(program) {
|
|
|
3024
3123
|
// src/commands/deps.ts
|
|
3025
3124
|
import { load_graph as load_graph3 } from "@kitsy/coop-core";
|
|
3026
3125
|
function registerDepsCommand(program) {
|
|
3027
|
-
program.command("deps").description("Show dependencies and reverse dependencies for a task").argument("<id>", "Task id or alias").action((id) => {
|
|
3126
|
+
program.command("deps").description("Show dependencies and reverse dependencies for a task, including status and title").argument("<id>", "Task id or alias").action((id) => {
|
|
3028
3127
|
const root = resolveRepoRoot();
|
|
3029
3128
|
const graph = load_graph3(coopDir(root));
|
|
3030
3129
|
const reference = resolveReference(root, id, "task");
|
|
@@ -3033,9 +3132,25 @@ function registerDepsCommand(program) {
|
|
|
3033
3132
|
throw new Error(`Task '${reference.id}' not found.`);
|
|
3034
3133
|
}
|
|
3035
3134
|
const reverse = Array.from(graph.reverse.get(task.id) ?? []).sort((a, b) => a.localeCompare(b));
|
|
3036
|
-
console.log(`Task: ${task.id}`);
|
|
3037
|
-
console.log(
|
|
3038
|
-
|
|
3135
|
+
console.log(`Task: ${task.id} [${task.status}] ${task.title}`);
|
|
3136
|
+
console.log("Depends On:");
|
|
3137
|
+
if (!task.depends_on || task.depends_on.length === 0) {
|
|
3138
|
+
console.log("- none");
|
|
3139
|
+
} else {
|
|
3140
|
+
for (const depId of task.depends_on) {
|
|
3141
|
+
const dep = graph.nodes.get(depId);
|
|
3142
|
+
console.log(`- ${depId}${dep ? ` [${dep.status}] ${dep.title}` : " [missing]"}`);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
console.log("Required By:");
|
|
3146
|
+
if (reverse.length === 0) {
|
|
3147
|
+
console.log("- none");
|
|
3148
|
+
} else {
|
|
3149
|
+
for (const dependentId of reverse) {
|
|
3150
|
+
const dependent = graph.nodes.get(dependentId);
|
|
3151
|
+
console.log(`- ${dependentId}${dependent ? ` [${dependent.status}] ${dependent.title}` : " [missing]"}`);
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3039
3154
|
});
|
|
3040
3155
|
}
|
|
3041
3156
|
|
|
@@ -3055,10 +3170,10 @@ import {
|
|
|
3055
3170
|
function normalize(value) {
|
|
3056
3171
|
return value.trim().toLowerCase();
|
|
3057
3172
|
}
|
|
3058
|
-
function resolveDelivery(graph,
|
|
3059
|
-
const direct = graph.deliveries.get(
|
|
3173
|
+
function resolveDelivery(graph, input2) {
|
|
3174
|
+
const direct = graph.deliveries.get(input2);
|
|
3060
3175
|
if (direct) return direct;
|
|
3061
|
-
const target = normalize(
|
|
3176
|
+
const target = normalize(input2);
|
|
3062
3177
|
const byId = Array.from(graph.deliveries.values()).find((delivery) => normalize(delivery.id) === target);
|
|
3063
3178
|
if (byId) return byId;
|
|
3064
3179
|
const byName = Array.from(graph.deliveries.values()).filter((delivery) => normalize(delivery.name) === target);
|
|
@@ -3066,9 +3181,9 @@ function resolveDelivery(graph, input3) {
|
|
|
3066
3181
|
return byName[0];
|
|
3067
3182
|
}
|
|
3068
3183
|
if (byName.length > 1) {
|
|
3069
|
-
throw new Error(`Multiple deliveries match '${
|
|
3184
|
+
throw new Error(`Multiple deliveries match '${input2}'. Use delivery id instead.`);
|
|
3070
3185
|
}
|
|
3071
|
-
throw new Error(`Delivery '${
|
|
3186
|
+
throw new Error(`Delivery '${input2}' not found.`);
|
|
3072
3187
|
}
|
|
3073
3188
|
|
|
3074
3189
|
// src/commands/graph.ts
|
|
@@ -3223,6 +3338,7 @@ var catalog = {
|
|
|
3223
3338
|
"Use `coop use show` to inspect the current user-local working defaults for track, delivery, and version.",
|
|
3224
3339
|
"Use `coop graph next --delivery <delivery>` or `coop next task` to choose work. Do not reprioritize outside COOP unless the user explicitly overrides it.",
|
|
3225
3340
|
"Commands resolve selection scope from: explicit CLI arg, then `coop use` working context, then shared project defaults.",
|
|
3341
|
+
"Use `--track` for the workstream lens (home track or delivery_tracks). Use `--delivery` for release/scope membership.",
|
|
3226
3342
|
"Use `coop show <id>` or `coop show task <id>` before implementation to read acceptance, tests_required, dependencies, origin refs, and task metadata.",
|
|
3227
3343
|
"Use `coop refine idea` or `coop refine task` when the task lacks planning detail. COOP owns canonical writes; agents should not edit `.coop` files directly."
|
|
3228
3344
|
],
|
|
@@ -3290,6 +3406,7 @@ var catalog = {
|
|
|
3290
3406
|
{ usage: "coop create idea --from-file idea-draft.yml", purpose: "Ingest a structured idea draft file." },
|
|
3291
3407
|
{ usage: "cat idea.md | coop create idea --stdin", purpose: "Ingest an idea draft from stdin." },
|
|
3292
3408
|
{ usage: 'coop create task "Implement webhook pipeline"', purpose: "Create a task with defaults." },
|
|
3409
|
+
{ usage: 'coop create task --title "Lock auth contract" --track MVP --delivery MVP', purpose: "Create a task directly inside a track and delivery scope." },
|
|
3293
3410
|
{
|
|
3294
3411
|
usage: 'coop create task --title "Lock auth contract" --acceptance "Contract approved,Client mapping documented" --tests-required "Contract fixture test" --authority-ref docs/webapp-mvp-plan.md#auth',
|
|
3295
3412
|
purpose: "Create a planning-grade task with acceptance, tests, and origin refs."
|
|
@@ -3333,15 +3450,18 @@ var catalog = {
|
|
|
3333
3450
|
description: "Read backlog state, task details, and planning output.",
|
|
3334
3451
|
commands: [
|
|
3335
3452
|
{ usage: "coop list tasks --status todo", purpose: "List tasks with filters." },
|
|
3336
|
-
{ usage: "coop list tasks --track MVP --delivery MVP --ready", purpose: "List ready tasks with
|
|
3453
|
+
{ usage: "coop list tasks --track MVP --delivery MVP --ready --columns id,title,p,assignee,score", purpose: "List ready tasks with lean columns and score visible." },
|
|
3337
3454
|
{ usage: "coop list tasks --mine", purpose: "List tasks assigned to the current default COOP author." },
|
|
3338
3455
|
{ usage: 'coop search "auth and login form"', purpose: "Run deterministic non-AI search across tasks, ideas, and deliveries." },
|
|
3339
3456
|
{ usage: 'coop search "auth" --open', purpose: "Require a single match and print the resolved summary row." },
|
|
3340
3457
|
{ usage: "coop show PM-101", purpose: "Resolve a task, idea, or delivery by reference without an extra entity noun." },
|
|
3458
|
+
{ usage: "coop show PM-101 --compact", purpose: "Show a smaller summary view for a large task." },
|
|
3341
3459
|
{ usage: "coop show task PM-101", purpose: "Show a task with acceptance, tests_required, refs, and runbook sections." },
|
|
3342
3460
|
{ usage: "coop show idea IDEA-101", purpose: "Show an idea." },
|
|
3343
|
-
{ usage: "coop deps PM-101", purpose: "Show task dependencies and reverse dependencies." },
|
|
3461
|
+
{ usage: "coop deps PM-101", purpose: "Show task dependencies and reverse dependencies with status and title." },
|
|
3344
3462
|
{ usage: "coop prompt PM-101 --format markdown", purpose: "Generate a manual handoff prompt from a task and current working context." },
|
|
3463
|
+
{ usage: "coop update PM-101 --track MVP --delivery MVP", purpose: "Update a task's home track or primary delivery without editing `.coop` files directly." },
|
|
3464
|
+
{ usage: "coop update PM-101 --add-delivery-track MVP --priority-in MVP:p0", purpose: "Add a contributing track lens and scoped priority override." },
|
|
3345
3465
|
{ usage: "coop update PM-101 --priority p1 --add-fix-version v2", purpose: "Update task metadata without editing `.coop` files directly." },
|
|
3346
3466
|
{ usage: 'coop comment PM-101 --message "Needs API review"', purpose: "Append a comment to a task." },
|
|
3347
3467
|
{ usage: 'coop log-time PM-101 --hours 2 --kind worked --note "pairing"', purpose: "Append a planned or worked time log to a task." },
|
|
@@ -3595,6 +3715,9 @@ function renderAiHelp(format) {
|
|
|
3595
3715
|
const heading = format === "markdown" ? "# COOP AI Help" : "COOP AI Help";
|
|
3596
3716
|
lines.push(heading, "");
|
|
3597
3717
|
lines.push(catalog.purpose, "");
|
|
3718
|
+
lines.push("Fast agent handoff:");
|
|
3719
|
+
lines.push("- `coop help-ai --initial-prompt --strict --repo C:/path/to/repo --delivery MVP --command coop.cmd`");
|
|
3720
|
+
lines.push("");
|
|
3598
3721
|
const bullet = (value) => format === "markdown" ? `- ${value}` : `- ${value}`;
|
|
3599
3722
|
lines.push(format === "markdown" ? "## Selection Rules" : "Selection Rules");
|
|
3600
3723
|
for (const rule of catalog.selection_rules) {
|
|
@@ -3680,7 +3803,7 @@ function registerHelpAiCommand(program) {
|
|
|
3680
3803
|
} catch {
|
|
3681
3804
|
artifactsDir = "docs";
|
|
3682
3805
|
}
|
|
3683
|
-
const
|
|
3806
|
+
const output2 = options.initialPrompt ? renderAiInitialPrompt({
|
|
3684
3807
|
repoPath: options.repo,
|
|
3685
3808
|
delivery: options.delivery,
|
|
3686
3809
|
track: options.track,
|
|
@@ -3688,7 +3811,7 @@ function registerHelpAiCommand(program) {
|
|
|
3688
3811
|
rigour,
|
|
3689
3812
|
artifactsDir
|
|
3690
3813
|
}) : topic ? renderAiHelpTopic(format, topic) : renderAiHelp(format);
|
|
3691
|
-
console.log(
|
|
3814
|
+
console.log(output2.trimEnd());
|
|
3692
3815
|
});
|
|
3693
3816
|
}
|
|
3694
3817
|
function resolveHelpTopic(options) {
|
|
@@ -3775,8 +3898,6 @@ function registerIndexCommand(program) {
|
|
|
3775
3898
|
import fs10 from "fs";
|
|
3776
3899
|
import path13 from "path";
|
|
3777
3900
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3778
|
-
import { createInterface } from "readline/promises";
|
|
3779
|
-
import { stdin as input, stdout as output } from "process";
|
|
3780
3901
|
import { CURRENT_SCHEMA_VERSION, write_schema_version } from "@kitsy/coop-core";
|
|
3781
3902
|
|
|
3782
3903
|
// src/hooks/pre-commit.ts
|
|
@@ -4101,6 +4222,28 @@ function installPostMergeHook(repoRoot) {
|
|
|
4101
4222
|
}
|
|
4102
4223
|
|
|
4103
4224
|
// src/commands/init.ts
|
|
4225
|
+
var NAMING_TEMPLATE_PRESETS = [
|
|
4226
|
+
{
|
|
4227
|
+
label: "<TYPE>-<TITLE16>-<SEQ>",
|
|
4228
|
+
value: "<TYPE>-<TITLE16>-<SEQ>",
|
|
4229
|
+
hint: "Balanced semantic default"
|
|
4230
|
+
},
|
|
4231
|
+
{
|
|
4232
|
+
label: "<TYPE>-<TRACK>-<TITLE16>-<SEQ>",
|
|
4233
|
+
value: "<TYPE>-<TRACK>-<TITLE16>-<SEQ>",
|
|
4234
|
+
hint: "Include track in ids"
|
|
4235
|
+
},
|
|
4236
|
+
{
|
|
4237
|
+
label: "<TYPE>-<USER>-<YYMMDD>-<RAND>",
|
|
4238
|
+
value: "<TYPE>-<USER>-<YYMMDD>-<RAND>",
|
|
4239
|
+
hint: "Actor/date/random oriented"
|
|
4240
|
+
},
|
|
4241
|
+
{
|
|
4242
|
+
label: "Custom template",
|
|
4243
|
+
value: "__custom__",
|
|
4244
|
+
hint: "Enter a custom naming template manually"
|
|
4245
|
+
}
|
|
4246
|
+
];
|
|
4104
4247
|
function normalizeProjectId(value) {
|
|
4105
4248
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
4106
4249
|
}
|
|
@@ -4321,36 +4464,27 @@ function installMergeDrivers(root) {
|
|
|
4321
4464
|
return "Updated .gitattributes but could not register merge drivers in git config.";
|
|
4322
4465
|
}
|
|
4323
4466
|
async function promptInitIdentity(root, options) {
|
|
4324
|
-
const rl = createInterface({ input, output });
|
|
4325
|
-
const ask2 = async (question, fallback) => {
|
|
4326
|
-
try {
|
|
4327
|
-
const answer = await rl.question(`${question} [${fallback}]: `);
|
|
4328
|
-
return answer.trim() || fallback;
|
|
4329
|
-
} catch {
|
|
4330
|
-
return fallback;
|
|
4331
|
-
}
|
|
4332
|
-
};
|
|
4333
4467
|
const defaultName = repoDisplayName(root);
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
projectId,
|
|
4348
|
-
projectAliases: parseAliases2(aliasesInput),
|
|
4349
|
-
namingTemplate
|
|
4350
|
-
};
|
|
4351
|
-
} finally {
|
|
4352
|
-
rl.close();
|
|
4468
|
+
const initialName = options.name?.trim() || defaultName;
|
|
4469
|
+
const projectName = options.name?.trim() || await ask("Project name", initialName);
|
|
4470
|
+
const defaultId = options.id?.trim() || normalizeProjectId(projectName) || repoIdentityId(root);
|
|
4471
|
+
const projectIdRaw = options.id?.trim() || await ask("Project id", defaultId);
|
|
4472
|
+
const projectId = normalizeProjectId(projectIdRaw);
|
|
4473
|
+
if (!projectId) {
|
|
4474
|
+
throw new Error("Invalid project id. Use letters, numbers, and hyphens.");
|
|
4475
|
+
}
|
|
4476
|
+
const aliasesInput = options.aliases !== void 0 ? options.aliases : await ask("Aliases (csv, optional)", "");
|
|
4477
|
+
let namingTemplate = options.naming?.trim();
|
|
4478
|
+
if (!namingTemplate) {
|
|
4479
|
+
const selected = await select("ID naming template", [...NAMING_TEMPLATE_PRESETS], 0);
|
|
4480
|
+
namingTemplate = selected === "__custom__" ? await ask("Custom ID naming template", DEFAULT_ID_NAMING_TEMPLATE) : selected;
|
|
4353
4481
|
}
|
|
4482
|
+
return {
|
|
4483
|
+
projectName,
|
|
4484
|
+
projectId,
|
|
4485
|
+
projectAliases: parseAliases2(aliasesInput),
|
|
4486
|
+
namingTemplate
|
|
4487
|
+
};
|
|
4354
4488
|
}
|
|
4355
4489
|
async function resolveInitIdentity(root, options) {
|
|
4356
4490
|
const defaultName = repoDisplayName(root);
|
|
@@ -4358,7 +4492,7 @@ async function resolveInitIdentity(root, options) {
|
|
|
4358
4492
|
const fallbackId = normalizeProjectId(options.id?.trim() || fallbackName) || repoIdentityId(root);
|
|
4359
4493
|
const fallbackAliases = parseAliases2(options.aliases);
|
|
4360
4494
|
const fallbackNaming = options.naming?.trim() || DEFAULT_ID_NAMING_TEMPLATE;
|
|
4361
|
-
const interactive = Boolean(options.interactive || !options.yes &&
|
|
4495
|
+
const interactive = Boolean(options.interactive || !options.yes && process.stdin.isTTY && process.stdout.isTTY);
|
|
4362
4496
|
if (interactive) {
|
|
4363
4497
|
return promptInitIdentity(root, options);
|
|
4364
4498
|
}
|
|
@@ -4435,6 +4569,8 @@ function registerInitCommand(program) {
|
|
|
4435
4569
|
console.log(' 1. coop create idea "Describe the idea"');
|
|
4436
4570
|
console.log(' 2. coop create task "Describe the task"');
|
|
4437
4571
|
console.log(" 3. coop graph validate");
|
|
4572
|
+
console.log(` 4. coop help-ai --initial-prompt --strict --repo ${root.replace(/\\/g, "/")} --command coop.cmd`);
|
|
4573
|
+
console.log(" 5. after you create a delivery, add --delivery <id> to that help-ai prompt for agent handoff");
|
|
4438
4574
|
});
|
|
4439
4575
|
}
|
|
4440
4576
|
|
|
@@ -4501,7 +4637,14 @@ function registerLifecycleCommands(program) {
|
|
|
4501
4637
|
|
|
4502
4638
|
// src/commands/list.ts
|
|
4503
4639
|
import path15 from "path";
|
|
4504
|
-
import {
|
|
4640
|
+
import {
|
|
4641
|
+
effective_priority as effective_priority3,
|
|
4642
|
+
load_graph as load_graph6,
|
|
4643
|
+
parseDeliveryFile as parseDeliveryFile2,
|
|
4644
|
+
parseIdeaFile as parseIdeaFile4,
|
|
4645
|
+
parseTaskFile as parseTaskFile10,
|
|
4646
|
+
schedule_next as schedule_next3
|
|
4647
|
+
} from "@kitsy/coop-core";
|
|
4505
4648
|
import chalk2 from "chalk";
|
|
4506
4649
|
function statusColor(status) {
|
|
4507
4650
|
switch (status) {
|
|
@@ -4519,8 +4662,210 @@ function statusColor(status) {
|
|
|
4519
4662
|
return status;
|
|
4520
4663
|
}
|
|
4521
4664
|
}
|
|
4522
|
-
function
|
|
4523
|
-
return
|
|
4665
|
+
function parseColumns(input2) {
|
|
4666
|
+
return (input2 ?? "").split(",").map((value) => value.trim().toLowerCase()).filter(Boolean);
|
|
4667
|
+
}
|
|
4668
|
+
function normalizeTaskColumns(value, ready) {
|
|
4669
|
+
if (!value?.trim()) {
|
|
4670
|
+
return ready ? ["id", "title", "priority", "status", "assignee", "score"] : ["id", "title", "priority", "status", "assignee"];
|
|
4671
|
+
}
|
|
4672
|
+
const raw = parseColumns(value);
|
|
4673
|
+
if (raw.length === 1 && raw[0] === "all") {
|
|
4674
|
+
return ["id", "title", "priority", "status", "assignee", "track", "delivery", "score", "file"];
|
|
4675
|
+
}
|
|
4676
|
+
const normalized = raw.map((column) => {
|
|
4677
|
+
if (column === "p") return "priority";
|
|
4678
|
+
return column;
|
|
4679
|
+
});
|
|
4680
|
+
const valid = /* @__PURE__ */ new Set(["id", "title", "status", "priority", "assignee", "track", "delivery", "score", "file"]);
|
|
4681
|
+
for (const column of normalized) {
|
|
4682
|
+
if (!valid.has(column)) {
|
|
4683
|
+
throw new Error(`Invalid task column '${column}'. Expected id|title|status|priority|assignee|track|delivery|score|file|all.`);
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
return normalized;
|
|
4687
|
+
}
|
|
4688
|
+
function normalizeIdeaColumns(value) {
|
|
4689
|
+
if (!value?.trim()) {
|
|
4690
|
+
return ["id", "title", "status"];
|
|
4691
|
+
}
|
|
4692
|
+
const raw = parseColumns(value);
|
|
4693
|
+
if (raw.length === 1 && raw[0] === "all") {
|
|
4694
|
+
return ["id", "title", "status", "file"];
|
|
4695
|
+
}
|
|
4696
|
+
const valid = /* @__PURE__ */ new Set(["id", "title", "status", "file"]);
|
|
4697
|
+
for (const column of raw) {
|
|
4698
|
+
if (!valid.has(column)) {
|
|
4699
|
+
throw new Error(`Invalid idea column '${column}'. Expected id|title|status|file|all.`);
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
return raw;
|
|
4703
|
+
}
|
|
4704
|
+
function normalizeTaskSort(value, fallback) {
|
|
4705
|
+
switch ((value ?? "").trim().toLowerCase()) {
|
|
4706
|
+
case "":
|
|
4707
|
+
return fallback;
|
|
4708
|
+
case "id":
|
|
4709
|
+
case "priority":
|
|
4710
|
+
case "status":
|
|
4711
|
+
case "title":
|
|
4712
|
+
case "updated":
|
|
4713
|
+
case "created":
|
|
4714
|
+
case "score":
|
|
4715
|
+
return value.trim().toLowerCase();
|
|
4716
|
+
default:
|
|
4717
|
+
throw new Error(`Invalid sort '${value}'. Expected id|priority|status|title|updated|created|score.`);
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
function normalizeIdeaSort(value, fallback) {
|
|
4721
|
+
switch ((value ?? "").trim().toLowerCase()) {
|
|
4722
|
+
case "":
|
|
4723
|
+
return fallback;
|
|
4724
|
+
case "id":
|
|
4725
|
+
case "status":
|
|
4726
|
+
case "title":
|
|
4727
|
+
case "updated":
|
|
4728
|
+
case "created":
|
|
4729
|
+
return value.trim().toLowerCase();
|
|
4730
|
+
default:
|
|
4731
|
+
throw new Error(`Invalid sort '${value}'. Expected id|status|title|updated|created.`);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
function priorityRank(value) {
|
|
4735
|
+
switch (value) {
|
|
4736
|
+
case "p0":
|
|
4737
|
+
return 0;
|
|
4738
|
+
case "p1":
|
|
4739
|
+
return 1;
|
|
4740
|
+
case "p2":
|
|
4741
|
+
return 2;
|
|
4742
|
+
case "p3":
|
|
4743
|
+
return 3;
|
|
4744
|
+
default:
|
|
4745
|
+
return 4;
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
function taskStatusRank(value) {
|
|
4749
|
+
switch (value) {
|
|
4750
|
+
case "in_progress":
|
|
4751
|
+
return 0;
|
|
4752
|
+
case "in_review":
|
|
4753
|
+
return 1;
|
|
4754
|
+
case "todo":
|
|
4755
|
+
return 2;
|
|
4756
|
+
case "blocked":
|
|
4757
|
+
return 3;
|
|
4758
|
+
case "done":
|
|
4759
|
+
return 4;
|
|
4760
|
+
case "canceled":
|
|
4761
|
+
return 5;
|
|
4762
|
+
default:
|
|
4763
|
+
return 6;
|
|
4764
|
+
}
|
|
4765
|
+
}
|
|
4766
|
+
function ideaStatusRank(value) {
|
|
4767
|
+
switch (value) {
|
|
4768
|
+
case "active":
|
|
4769
|
+
return 0;
|
|
4770
|
+
case "captured":
|
|
4771
|
+
return 1;
|
|
4772
|
+
case "validated":
|
|
4773
|
+
return 2;
|
|
4774
|
+
case "rejected":
|
|
4775
|
+
return 3;
|
|
4776
|
+
case "promoted":
|
|
4777
|
+
return 4;
|
|
4778
|
+
default:
|
|
4779
|
+
return 5;
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
function compareDatesDesc(a, b) {
|
|
4783
|
+
return (b ?? "").localeCompare(a ?? "");
|
|
4784
|
+
}
|
|
4785
|
+
function compareTaskScoreLike(a, b, readyOrder, track) {
|
|
4786
|
+
const aReady = readyOrder.get(a.id);
|
|
4787
|
+
const bReady = readyOrder.get(b.id);
|
|
4788
|
+
if (aReady !== void 0 || bReady !== void 0) {
|
|
4789
|
+
if (aReady === void 0) return 1;
|
|
4790
|
+
if (bReady === void 0) return -1;
|
|
4791
|
+
return aReady - bReady;
|
|
4792
|
+
}
|
|
4793
|
+
const status = taskStatusRank(a.status) - taskStatusRank(b.status);
|
|
4794
|
+
if (status !== 0) return status;
|
|
4795
|
+
const priority = priorityRank(effective_priority3(b.task, track)) - priorityRank(effective_priority3(a.task, track));
|
|
4796
|
+
if (priority !== 0) return priority;
|
|
4797
|
+
const updated = compareDatesDesc(a.task.updated, b.task.updated);
|
|
4798
|
+
if (updated !== 0) return updated;
|
|
4799
|
+
return a.id.localeCompare(b.id);
|
|
4800
|
+
}
|
|
4801
|
+
function sortTaskRows(rows, sortMode, readyOrder, track) {
|
|
4802
|
+
return [...rows].sort((a, b) => {
|
|
4803
|
+
switch (sortMode) {
|
|
4804
|
+
case "score":
|
|
4805
|
+
return compareTaskScoreLike(a, b, readyOrder, track);
|
|
4806
|
+
case "priority": {
|
|
4807
|
+
const priority = priorityRank(a.priority) - priorityRank(b.priority);
|
|
4808
|
+
if (priority !== 0) return priority;
|
|
4809
|
+
return a.id.localeCompare(b.id);
|
|
4810
|
+
}
|
|
4811
|
+
case "status": {
|
|
4812
|
+
const status = taskStatusRank(a.status) - taskStatusRank(b.status);
|
|
4813
|
+
if (status !== 0) return status;
|
|
4814
|
+
return a.id.localeCompare(b.id);
|
|
4815
|
+
}
|
|
4816
|
+
case "title":
|
|
4817
|
+
return a.title.localeCompare(b.title);
|
|
4818
|
+
case "updated": {
|
|
4819
|
+
const updated = compareDatesDesc(a.task.updated, b.task.updated);
|
|
4820
|
+
if (updated !== 0) return updated;
|
|
4821
|
+
return a.id.localeCompare(b.id);
|
|
4822
|
+
}
|
|
4823
|
+
case "created": {
|
|
4824
|
+
const created = compareDatesDesc(a.task.created, b.task.created);
|
|
4825
|
+
if (created !== 0) return created;
|
|
4826
|
+
return a.id.localeCompare(b.id);
|
|
4827
|
+
}
|
|
4828
|
+
case "id":
|
|
4829
|
+
default:
|
|
4830
|
+
return a.id.localeCompare(b.id);
|
|
4831
|
+
}
|
|
4832
|
+
});
|
|
4833
|
+
}
|
|
4834
|
+
function taskColumnHeader(column) {
|
|
4835
|
+
switch (column) {
|
|
4836
|
+
case "priority":
|
|
4837
|
+
return "P";
|
|
4838
|
+
case "assignee":
|
|
4839
|
+
return "Assignee";
|
|
4840
|
+
case "track":
|
|
4841
|
+
return "Track";
|
|
4842
|
+
case "delivery":
|
|
4843
|
+
return "Delivery";
|
|
4844
|
+
case "score":
|
|
4845
|
+
return "Score";
|
|
4846
|
+
case "file":
|
|
4847
|
+
return "File";
|
|
4848
|
+
case "status":
|
|
4849
|
+
return "Status";
|
|
4850
|
+
case "title":
|
|
4851
|
+
return "Title";
|
|
4852
|
+
case "id":
|
|
4853
|
+
default:
|
|
4854
|
+
return "ID";
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
function ideaColumnHeader(column) {
|
|
4858
|
+
switch (column) {
|
|
4859
|
+
case "file":
|
|
4860
|
+
return "File";
|
|
4861
|
+
case "status":
|
|
4862
|
+
return "Status";
|
|
4863
|
+
case "title":
|
|
4864
|
+
return "Title";
|
|
4865
|
+
case "id":
|
|
4866
|
+
default:
|
|
4867
|
+
return "ID";
|
|
4868
|
+
}
|
|
4524
4869
|
}
|
|
4525
4870
|
function loadTasks2(root) {
|
|
4526
4871
|
return listTaskFiles(root).map((filePath) => ({
|
|
@@ -4539,24 +4884,30 @@ function listTasks(options) {
|
|
|
4539
4884
|
ensureCoopInitialized(root);
|
|
4540
4885
|
const context = readWorkingContext(root, resolveCoopHome());
|
|
4541
4886
|
const graph = load_graph6(coopDir(root));
|
|
4542
|
-
const resolvedTrack = resolveContextValueWithSource(options.track, context.track
|
|
4543
|
-
const resolvedDelivery = resolveContextValueWithSource(options.delivery, context.delivery
|
|
4887
|
+
const resolvedTrack = resolveContextValueWithSource(options.track, context.track);
|
|
4888
|
+
const resolvedDelivery = resolveContextValueWithSource(options.delivery, context.delivery);
|
|
4889
|
+
const resolvedVersion = resolveContextValueWithSource(options.version, context.version);
|
|
4544
4890
|
const assignee = options.mine ? defaultCoopAuthor(root) : options.assignee?.trim();
|
|
4545
4891
|
const deliveryScope = resolvedDelivery.value ? new Set(graph.deliveries.get(resolvedDelivery.value)?.scope.include ?? []) : null;
|
|
4892
|
+
const defaultSort = options.ready || resolvedTrack.value || resolvedDelivery.value ? "score" : "id";
|
|
4893
|
+
const sortMode = normalizeTaskSort(options.sort, defaultSort);
|
|
4894
|
+
const columns = normalizeTaskColumns(options.columns, Boolean(options.ready));
|
|
4546
4895
|
if (isVerboseRequested()) {
|
|
4547
4896
|
for (const line of formatResolvedContextMessage({
|
|
4548
4897
|
track: resolvedTrack,
|
|
4549
|
-
delivery: resolvedDelivery
|
|
4898
|
+
delivery: resolvedDelivery,
|
|
4899
|
+
version: resolvedVersion
|
|
4550
4900
|
})) {
|
|
4551
4901
|
console.log(line);
|
|
4552
4902
|
}
|
|
4553
4903
|
}
|
|
4554
|
-
const
|
|
4904
|
+
const scoredEntries = sortMode === "score" || options.ready ? schedule_next3(graph, {
|
|
4555
4905
|
track: resolvedTrack.value,
|
|
4556
4906
|
delivery: resolvedDelivery.value
|
|
4557
|
-
}) :
|
|
4558
|
-
const readyIds =
|
|
4559
|
-
const readyOrder =
|
|
4907
|
+
}) : [];
|
|
4908
|
+
const readyIds = options.ready ? new Set(scoredEntries.map((entry) => entry.task.id)) : null;
|
|
4909
|
+
const readyOrder = new Map(scoredEntries.map((entry, index) => [entry.task.id, index]));
|
|
4910
|
+
const scoreMap = new Map(scoredEntries.map((entry) => [entry.task.id, entry.score]));
|
|
4560
4911
|
const rows = loadTasks2(root).filter(({ task }) => {
|
|
4561
4912
|
if (readyIds && !readyIds.has(task.id)) return false;
|
|
4562
4913
|
if (options.status && task.status !== options.status) return false;
|
|
@@ -4566,11 +4917,12 @@ function listTasks(options) {
|
|
|
4566
4917
|
if (resolvedDelivery.value && task.delivery !== resolvedDelivery.value && !deliveryScope?.has(task.id)) return false;
|
|
4567
4918
|
if (options.priority && taskEffectivePriority(task, resolvedTrack.value) !== options.priority) return false;
|
|
4568
4919
|
if (assignee && (task.assignee ?? "") !== assignee) return false;
|
|
4569
|
-
if (
|
|
4920
|
+
if (resolvedVersion.value && !(task.fix_versions ?? []).includes(resolvedVersion.value) && !(task.released_in ?? []).includes(resolvedVersion.value)) {
|
|
4570
4921
|
return false;
|
|
4571
4922
|
}
|
|
4572
4923
|
return true;
|
|
4573
4924
|
}).map(({ task, filePath }) => ({
|
|
4925
|
+
task,
|
|
4574
4926
|
id: task.id,
|
|
4575
4927
|
title: task.title,
|
|
4576
4928
|
status: task.status,
|
|
@@ -4580,24 +4932,39 @@ function listTasks(options) {
|
|
|
4580
4932
|
delivery: task.delivery ?? "-",
|
|
4581
4933
|
filePath
|
|
4582
4934
|
}));
|
|
4583
|
-
const sorted =
|
|
4935
|
+
const sorted = sortTaskRows(rows, sortMode, readyOrder, resolvedTrack.value);
|
|
4584
4936
|
if (sorted.length === 0) {
|
|
4585
4937
|
console.log("No tasks found.");
|
|
4586
4938
|
return;
|
|
4587
4939
|
}
|
|
4588
4940
|
console.log(
|
|
4589
4941
|
formatTable(
|
|
4590
|
-
|
|
4591
|
-
sorted.map(
|
|
4592
|
-
entry.
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4942
|
+
columns.map(taskColumnHeader),
|
|
4943
|
+
sorted.map(
|
|
4944
|
+
(entry) => columns.map((column) => {
|
|
4945
|
+
switch (column) {
|
|
4946
|
+
case "title":
|
|
4947
|
+
return entry.title;
|
|
4948
|
+
case "status":
|
|
4949
|
+
return statusColor(entry.status);
|
|
4950
|
+
case "priority":
|
|
4951
|
+
return entry.priority;
|
|
4952
|
+
case "assignee":
|
|
4953
|
+
return entry.assignee;
|
|
4954
|
+
case "track":
|
|
4955
|
+
return entry.track;
|
|
4956
|
+
case "delivery":
|
|
4957
|
+
return entry.delivery;
|
|
4958
|
+
case "score":
|
|
4959
|
+
return scoreMap.has(entry.id) ? scoreMap.get(entry.id).toFixed(1) : "-";
|
|
4960
|
+
case "file":
|
|
4961
|
+
return path15.relative(root, entry.filePath);
|
|
4962
|
+
case "id":
|
|
4963
|
+
default:
|
|
4964
|
+
return entry.id;
|
|
4965
|
+
}
|
|
4966
|
+
})
|
|
4967
|
+
)
|
|
4601
4968
|
)
|
|
4602
4969
|
);
|
|
4603
4970
|
console.log(`
|
|
@@ -4606,6 +4973,8 @@ Total tasks: ${sorted.length}`);
|
|
|
4606
4973
|
function listIdeas(options) {
|
|
4607
4974
|
const root = resolveRepoRoot();
|
|
4608
4975
|
ensureCoopInitialized(root);
|
|
4976
|
+
const sortMode = normalizeIdeaSort(options.sort, "id");
|
|
4977
|
+
const columns = normalizeIdeaColumns(options.columns);
|
|
4609
4978
|
const rows = loadIdeas(root).filter(({ idea }) => {
|
|
4610
4979
|
if (options.status && idea.status !== options.status) return false;
|
|
4611
4980
|
return true;
|
|
@@ -4617,22 +4986,45 @@ function listIdeas(options) {
|
|
|
4617
4986
|
track: "-",
|
|
4618
4987
|
filePath
|
|
4619
4988
|
}));
|
|
4620
|
-
const sorted =
|
|
4989
|
+
const sorted = [...rows].sort((a, b) => {
|
|
4990
|
+
switch (sortMode) {
|
|
4991
|
+
case "status": {
|
|
4992
|
+
const status = ideaStatusRank(a.status) - ideaStatusRank(b.status);
|
|
4993
|
+
if (status !== 0) return status;
|
|
4994
|
+
return a.id.localeCompare(b.id);
|
|
4995
|
+
}
|
|
4996
|
+
case "title":
|
|
4997
|
+
return a.title.localeCompare(b.title);
|
|
4998
|
+
case "updated":
|
|
4999
|
+
case "created":
|
|
5000
|
+
return a.id.localeCompare(b.id);
|
|
5001
|
+
case "id":
|
|
5002
|
+
default:
|
|
5003
|
+
return a.id.localeCompare(b.id);
|
|
5004
|
+
}
|
|
5005
|
+
});
|
|
4621
5006
|
if (sorted.length === 0) {
|
|
4622
5007
|
console.log("No ideas found.");
|
|
4623
5008
|
return;
|
|
4624
5009
|
}
|
|
4625
5010
|
console.log(
|
|
4626
5011
|
formatTable(
|
|
4627
|
-
|
|
4628
|
-
sorted.map(
|
|
4629
|
-
entry.
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
5012
|
+
columns.map(ideaColumnHeader),
|
|
5013
|
+
sorted.map(
|
|
5014
|
+
(entry) => columns.map((column) => {
|
|
5015
|
+
switch (column) {
|
|
5016
|
+
case "title":
|
|
5017
|
+
return entry.title;
|
|
5018
|
+
case "status":
|
|
5019
|
+
return statusColor(entry.status);
|
|
5020
|
+
case "file":
|
|
5021
|
+
return path15.relative(root, entry.filePath);
|
|
5022
|
+
case "id":
|
|
5023
|
+
default:
|
|
5024
|
+
return entry.id;
|
|
5025
|
+
}
|
|
5026
|
+
})
|
|
5027
|
+
)
|
|
4636
5028
|
)
|
|
4637
5029
|
);
|
|
4638
5030
|
console.log(`
|
|
@@ -4655,10 +5047,10 @@ function listDeliveries() {
|
|
|
4655
5047
|
}
|
|
4656
5048
|
function registerListCommand(program) {
|
|
4657
5049
|
const list = program.command("list").description("List COOP entities");
|
|
4658
|
-
list.command("tasks").description("List tasks").option("--status <status>", "Filter by status").option("--track <track>", "Filter by track, using `coop use track` if omitted").option("--delivery <delivery>", "Filter by delivery, using `coop use delivery` if omitted").option("--priority <priority>", "Filter by effective priority").option("--assignee <assignee>", "Filter by assignee").option("--version <version>", "Filter by fix/released version, using `coop use version` if omitted").option("--mine", "Filter to the current default COOP author").option("--ready", "Only list ready tasks in scored order").action((options) => {
|
|
5050
|
+
list.command("tasks").description("List tasks").option("--status <status>", "Filter by status").option("--track <track>", "Filter by home/contributing track lens, using `coop use track` if omitted").option("--delivery <delivery>", "Filter by delivery membership, using `coop use delivery` if omitted").option("--priority <priority>", "Filter by effective priority").option("--assignee <assignee>", "Filter by assignee").option("--version <version>", "Filter by fix/released version, using `coop use version` if omitted").option("--mine", "Filter to the current default COOP author").option("--ready", "Only list ready tasks in scored order").option("--sort <sort>", "Sort by id|priority|status|title|updated|created|score").option("--columns <columns>", "Columns: id,title,status,priority,assignee,track,delivery,score,file or all").action((options) => {
|
|
4659
5051
|
listTasks(options);
|
|
4660
5052
|
});
|
|
4661
|
-
list.command("ideas").description("List ideas").option("--status <status>", "Filter by status").action((options) => {
|
|
5053
|
+
list.command("ideas").description("List ideas").option("--status <status>", "Filter by status").option("--sort <sort>", "Sort by id|status|title|updated|created").option("--columns <columns>", "Columns: id,title,status,file or all").action((options) => {
|
|
4662
5054
|
listIdeas(options);
|
|
4663
5055
|
});
|
|
4664
5056
|
list.command("alias").description("List aliases").argument("[pattern]", "Wildcard pattern, e.g. PAY*").action((pattern) => {
|
|
@@ -4672,17 +5064,30 @@ function registerListCommand(program) {
|
|
|
4672
5064
|
// src/utils/logger.ts
|
|
4673
5065
|
import fs11 from "fs";
|
|
4674
5066
|
import path16 from "path";
|
|
4675
|
-
function
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
5067
|
+
function resolveWorkspaceRoot(start = process.cwd()) {
|
|
5068
|
+
let current = path16.resolve(start);
|
|
5069
|
+
while (true) {
|
|
5070
|
+
const gitDir = path16.join(current, ".git");
|
|
5071
|
+
const coopDir2 = coopWorkspaceDir(current);
|
|
5072
|
+
const workspaceConfig = path16.join(coopDir2, "config.yml");
|
|
5073
|
+
const projectsDir = path16.join(coopDir2, "projects");
|
|
5074
|
+
if (fs11.existsSync(gitDir)) {
|
|
5075
|
+
return current;
|
|
5076
|
+
}
|
|
5077
|
+
if (fs11.existsSync(workspaceConfig) || fs11.existsSync(projectsDir)) {
|
|
5078
|
+
return current;
|
|
5079
|
+
}
|
|
5080
|
+
const parent = path16.dirname(current);
|
|
5081
|
+
if (parent === current) {
|
|
5082
|
+
return null;
|
|
5083
|
+
}
|
|
5084
|
+
current = parent;
|
|
4680
5085
|
}
|
|
4681
5086
|
}
|
|
4682
5087
|
function resolveCliLogFile(start = process.cwd()) {
|
|
4683
|
-
const root =
|
|
4684
|
-
|
|
4685
|
-
|
|
5088
|
+
const root = resolveWorkspaceRoot(start);
|
|
5089
|
+
if (root) {
|
|
5090
|
+
const workspace = coopWorkspaceDir(root);
|
|
4686
5091
|
return path16.join(workspace, "logs", "cli.log");
|
|
4687
5092
|
}
|
|
4688
5093
|
return path16.join(resolveCoopHome(), "logs", "cli.log");
|
|
@@ -4794,8 +5199,8 @@ function registerLogTimeCommand(program) {
|
|
|
4794
5199
|
// src/commands/migrate.ts
|
|
4795
5200
|
import fs12 from "fs";
|
|
4796
5201
|
import path17 from "path";
|
|
4797
|
-
import { createInterface
|
|
4798
|
-
import { stdin as
|
|
5202
|
+
import { createInterface } from "readline/promises";
|
|
5203
|
+
import { stdin as input, stdout as output } from "process";
|
|
4799
5204
|
import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as parseYamlFile2, writeYamlFile as writeYamlFile4 } from "@kitsy/coop-core";
|
|
4800
5205
|
var COOP_IGNORE_TEMPLATE2 = `.index/
|
|
4801
5206
|
logs/
|
|
@@ -4868,7 +5273,7 @@ function parseAliases3(value) {
|
|
|
4868
5273
|
);
|
|
4869
5274
|
}
|
|
4870
5275
|
async function promptProjectIdentity(defaults, options) {
|
|
4871
|
-
const rl =
|
|
5276
|
+
const rl = createInterface({ input, output });
|
|
4872
5277
|
const ask2 = async (question, fallback) => {
|
|
4873
5278
|
try {
|
|
4874
5279
|
const answer = await rl.question(`${question} [${fallback}]: `);
|
|
@@ -4901,7 +5306,7 @@ async function resolveMigrationIdentity(root, options) {
|
|
|
4901
5306
|
projectId: normalizeProjectId2(existing.projectId || repoIdentityId(root)) || repoIdentityId(root),
|
|
4902
5307
|
projectAliases: options.aliases !== void 0 ? parseAliases3(options.aliases) : existing.projectAliases
|
|
4903
5308
|
};
|
|
4904
|
-
const interactive = Boolean(options.interactive || !options.yes &&
|
|
5309
|
+
const interactive = Boolean(options.interactive || !options.yes && input.isTTY && output.isTTY);
|
|
4905
5310
|
if (interactive) {
|
|
4906
5311
|
return promptProjectIdentity(defaults, options);
|
|
4907
5312
|
}
|
|
@@ -5638,15 +6043,15 @@ function registerPromptCommand(program) {
|
|
|
5638
6043
|
const root = resolveRepoRoot();
|
|
5639
6044
|
const payload = buildPayload(root, id);
|
|
5640
6045
|
const format = options.format ?? "text";
|
|
5641
|
-
let
|
|
6046
|
+
let output2 = "";
|
|
5642
6047
|
if (format === "json") {
|
|
5643
|
-
|
|
6048
|
+
output2 = `${JSON.stringify(payload, null, 2)}
|
|
5644
6049
|
`;
|
|
5645
6050
|
} else {
|
|
5646
|
-
|
|
6051
|
+
output2 = renderMarkdown(payload);
|
|
5647
6052
|
}
|
|
5648
6053
|
if (options.save) {
|
|
5649
|
-
fs14.writeFileSync(options.save,
|
|
6054
|
+
fs14.writeFileSync(options.save, output2, "utf8");
|
|
5650
6055
|
}
|
|
5651
6056
|
if (isVerboseRequested()) {
|
|
5652
6057
|
for (const line of formatResolvedContextMessage({
|
|
@@ -5657,7 +6062,7 @@ function registerPromptCommand(program) {
|
|
|
5657
6062
|
console.log(line);
|
|
5658
6063
|
}
|
|
5659
6064
|
}
|
|
5660
|
-
console.log(
|
|
6065
|
+
console.log(output2.trimEnd());
|
|
5661
6066
|
});
|
|
5662
6067
|
}
|
|
5663
6068
|
|
|
@@ -6244,13 +6649,31 @@ function formatTimeSummary(task) {
|
|
|
6244
6649
|
const plannedLogged = (task.time?.logs ?? []).filter((entry) => entry.kind === "planned").reduce((sum, entry) => sum + entry.hours, 0);
|
|
6245
6650
|
return `planned_hours=${planned ?? "-"} | planned_logged=${plannedLogged || 0} | worked=${worked || 0}`;
|
|
6246
6651
|
}
|
|
6247
|
-
function showTask(taskId) {
|
|
6652
|
+
function showTask(taskId, options = {}) {
|
|
6248
6653
|
const root = resolveRepoRoot();
|
|
6249
6654
|
const context = readWorkingContext(root, resolveCoopHome());
|
|
6250
6655
|
const { filePath, parsed } = loadTaskEntry(root, taskId);
|
|
6251
6656
|
const task = parsed.task;
|
|
6252
6657
|
const body = parsed.body.trim();
|
|
6253
6658
|
const computed = loadComputedFromIndex(root, task.id);
|
|
6659
|
+
if (options.compact) {
|
|
6660
|
+
const compactLines = [
|
|
6661
|
+
`Task: ${task.id}`,
|
|
6662
|
+
`Title: ${task.title}`,
|
|
6663
|
+
`Status: ${task.status}`,
|
|
6664
|
+
`Priority: ${task.priority ?? "-"}`,
|
|
6665
|
+
`Effective Priority: ${taskEffectivePriority(task, context.track)}`,
|
|
6666
|
+
`Track: ${task.track ?? "-"}`,
|
|
6667
|
+
`Delivery: ${task.delivery ?? "-"}`,
|
|
6668
|
+
`Assignee: ${task.assignee ?? "-"}`,
|
|
6669
|
+
`Depends On: ${stringify(task.depends_on)}`,
|
|
6670
|
+
`Acceptance: ${task.acceptance && task.acceptance.length > 0 ? task.acceptance.join(" | ") : "-"}`,
|
|
6671
|
+
`Tests Required: ${task.tests_required && task.tests_required.length > 0 ? task.tests_required.join(", ") : "-"}`,
|
|
6672
|
+
`File: ${path22.relative(root, filePath)}`
|
|
6673
|
+
];
|
|
6674
|
+
console.log(compactLines.join("\n"));
|
|
6675
|
+
return;
|
|
6676
|
+
}
|
|
6254
6677
|
const lines = [
|
|
6255
6678
|
`Task: ${task.id}`,
|
|
6256
6679
|
`Title: ${task.title}`,
|
|
@@ -6315,13 +6738,26 @@ function showTask(taskId) {
|
|
|
6315
6738
|
}
|
|
6316
6739
|
console.log(lines.join("\n"));
|
|
6317
6740
|
}
|
|
6318
|
-
function showIdea(ideaId) {
|
|
6741
|
+
function showIdea(ideaId, options = {}) {
|
|
6319
6742
|
const root = resolveRepoRoot();
|
|
6320
6743
|
const target = resolveReference(root, ideaId, "idea");
|
|
6321
6744
|
const ideaFile = path22.join(root, ...target.file.split("/"));
|
|
6322
6745
|
const parsed = parseIdeaFile7(ideaFile);
|
|
6323
6746
|
const idea = parsed.idea;
|
|
6324
6747
|
const body = parsed.body.trim();
|
|
6748
|
+
if (options.compact) {
|
|
6749
|
+
console.log(
|
|
6750
|
+
[
|
|
6751
|
+
`Idea: ${idea.id}`,
|
|
6752
|
+
`Title: ${idea.title}`,
|
|
6753
|
+
`Status: ${idea.status}`,
|
|
6754
|
+
`Tags: ${stringify(idea.tags)}`,
|
|
6755
|
+
`Linked Tasks: ${stringify(idea.linked_tasks)}`,
|
|
6756
|
+
`File: ${path22.relative(root, ideaFile)}`
|
|
6757
|
+
].join("\n")
|
|
6758
|
+
);
|
|
6759
|
+
return;
|
|
6760
|
+
}
|
|
6325
6761
|
const lines = [
|
|
6326
6762
|
`Idea: ${idea.id}`,
|
|
6327
6763
|
`Title: ${idea.title}`,
|
|
@@ -6339,9 +6775,22 @@ function showIdea(ideaId) {
|
|
|
6339
6775
|
];
|
|
6340
6776
|
console.log(lines.join("\n"));
|
|
6341
6777
|
}
|
|
6342
|
-
function showDelivery(ref) {
|
|
6778
|
+
function showDelivery(ref, options = {}) {
|
|
6343
6779
|
const root = resolveRepoRoot();
|
|
6344
6780
|
const { filePath, delivery, body } = resolveDeliveryEntry(root, ref);
|
|
6781
|
+
if (options.compact) {
|
|
6782
|
+
console.log(
|
|
6783
|
+
[
|
|
6784
|
+
`Delivery: ${delivery.id}`,
|
|
6785
|
+
`Name: ${delivery.name}`,
|
|
6786
|
+
`Status: ${delivery.status}`,
|
|
6787
|
+
`Target Date: ${delivery.target_date ?? "-"}`,
|
|
6788
|
+
`Scope Include: ${delivery.scope.include.length > 0 ? delivery.scope.include.join(", ") : "-"}`,
|
|
6789
|
+
`File: ${path22.relative(root, filePath)}`
|
|
6790
|
+
].join("\n")
|
|
6791
|
+
);
|
|
6792
|
+
return;
|
|
6793
|
+
}
|
|
6345
6794
|
const lines = [
|
|
6346
6795
|
`Delivery: ${delivery.id}`,
|
|
6347
6796
|
`Name: ${delivery.name}`,
|
|
@@ -6359,35 +6808,35 @@ function showDelivery(ref) {
|
|
|
6359
6808
|
];
|
|
6360
6809
|
console.log(lines.join("\n"));
|
|
6361
6810
|
}
|
|
6362
|
-
function showByReference(ref) {
|
|
6811
|
+
function showByReference(ref, options = {}) {
|
|
6363
6812
|
const root = resolveRepoRoot();
|
|
6364
6813
|
try {
|
|
6365
6814
|
const resolved = resolveReference(root, ref);
|
|
6366
6815
|
if (resolved.type === "task") {
|
|
6367
|
-
showTask(ref);
|
|
6816
|
+
showTask(ref, options);
|
|
6368
6817
|
return;
|
|
6369
6818
|
}
|
|
6370
|
-
showIdea(ref);
|
|
6819
|
+
showIdea(ref, options);
|
|
6371
6820
|
return;
|
|
6372
6821
|
} catch {
|
|
6373
|
-
showDelivery(ref);
|
|
6822
|
+
showDelivery(ref, options);
|
|
6374
6823
|
}
|
|
6375
6824
|
}
|
|
6376
6825
|
function registerShowCommand(program) {
|
|
6377
|
-
const show = program.command("show").description("Show detailed COOP entities").argument("[ref]", "Task, idea, or delivery reference").action((ref) => {
|
|
6826
|
+
const show = program.command("show").description("Show detailed COOP entities").argument("[ref]", "Task, idea, or delivery reference").option("--compact", "Show a smaller summary view").action((ref, options) => {
|
|
6378
6827
|
if (!ref?.trim()) {
|
|
6379
6828
|
throw new Error("Provide a task, idea, or delivery reference.");
|
|
6380
6829
|
}
|
|
6381
|
-
showByReference(ref);
|
|
6830
|
+
showByReference(ref, options);
|
|
6382
6831
|
});
|
|
6383
|
-
show.command("task").description("Show task details").argument("<id>", "Task ID").action((id) => {
|
|
6384
|
-
showTask(id);
|
|
6832
|
+
show.command("task").description("Show task details").argument("<id>", "Task ID").option("--compact", "Show a smaller summary view").action((id, options) => {
|
|
6833
|
+
showTask(id, options);
|
|
6385
6834
|
});
|
|
6386
|
-
show.command("idea").description("Show idea details").argument("<id>", "Idea ID").action((id) => {
|
|
6387
|
-
showIdea(id);
|
|
6835
|
+
show.command("idea").description("Show idea details").argument("<id>", "Idea ID").option("--compact", "Show a smaller summary view").action((id, options) => {
|
|
6836
|
+
showIdea(id, options);
|
|
6388
6837
|
});
|
|
6389
|
-
show.command("delivery").description("Show delivery details").argument("<id>", "Delivery id or name").action((id) => {
|
|
6390
|
-
showDelivery(id);
|
|
6838
|
+
show.command("delivery").description("Show delivery details").argument("<id>", "Delivery id or name").option("--compact", "Show a smaller summary view").action((id, options) => {
|
|
6839
|
+
showDelivery(id, options);
|
|
6391
6840
|
});
|
|
6392
6841
|
}
|
|
6393
6842
|
|
|
@@ -6581,7 +7030,7 @@ function printResolvedSelectionContext(root, options) {
|
|
|
6581
7030
|
}
|
|
6582
7031
|
function registerTaskFlowCommands(program) {
|
|
6583
7032
|
const next = program.command("next").description("Select the next COOP work item");
|
|
6584
|
-
next.command("task").description("Show the top ready task").option("--track <track>", "Filter by track, else config.defaults.track if set").option("--delivery <delivery>", "Filter by delivery").option("--executor <executor>", "Filter by executor (human|ai|ci|hybrid)").option("--today <date>", "Evaluation date (YYYY-MM-DD)").action((options) => {
|
|
7033
|
+
next.command("task").description("Show the top ready task").option("--track <track>", "Filter by the home/contributing track lens, else config.defaults.track if set").option("--delivery <delivery>", "Filter by delivery membership").option("--executor <executor>", "Filter by executor (human|ai|ci|hybrid)").option("--today <date>", "Evaluation date (YYYY-MM-DD)").action((options) => {
|
|
6585
7034
|
const root = resolveRepoRoot();
|
|
6586
7035
|
printResolvedSelectionContext(root, options);
|
|
6587
7036
|
const selected = selectTopReadyTask(root, {
|
|
@@ -6593,7 +7042,7 @@ function registerTaskFlowCommands(program) {
|
|
|
6593
7042
|
console.log(formatSelectedTask(selected.entry, selected.selection));
|
|
6594
7043
|
});
|
|
6595
7044
|
const pick = program.command("pick").description("Pick the next COOP work item");
|
|
6596
|
-
pick.command("task").description("Select the top ready task and move it into active work").argument("[id]", "Task ID or alias").option("--track <track>", "Filter by track, else config.defaults.track if set").option("--delivery <delivery>", "Filter by delivery").option("--executor <executor>", "Filter by executor (human|ai|ci|hybrid)").option("--today <date>", "Evaluation date (YYYY-MM-DD)").option("--promote", "Promote the task in the current track/version context before starting it").option("--to <assignee>", "Assign the selected task before starting it").option("--claim", "Assign the selected task to the actor/user/default author before starting it").option("--actor <actor>", "Actor performing assignment/transition").option("--user <user>", "Current user for advisory authorization checks").option("--force", "Override advisory authorization checks").action(async (id, options) => {
|
|
7045
|
+
pick.command("task").description("Select the top ready task and move it into active work").argument("[id]", "Task ID or alias").option("--track <track>", "Filter by the home/contributing track lens, else config.defaults.track if set").option("--delivery <delivery>", "Filter by delivery membership").option("--executor <executor>", "Filter by executor (human|ai|ci|hybrid)").option("--today <date>", "Evaluation date (YYYY-MM-DD)").option("--promote", "Promote the task in the current track/version context before starting it").option("--to <assignee>", "Assign the selected task before starting it").option("--claim", "Assign the selected task to the actor/user/default author before starting it").option("--actor <actor>", "Actor performing assignment/transition").option("--user <user>", "Current user for advisory authorization checks").option("--force", "Override advisory authorization checks").action(async (id, options) => {
|
|
6597
7046
|
const root = resolveRepoRoot();
|
|
6598
7047
|
printResolvedSelectionContext(root, options);
|
|
6599
7048
|
const selected = id?.trim() ? {
|
|
@@ -6623,7 +7072,7 @@ function registerTaskFlowCommands(program) {
|
|
|
6623
7072
|
await claimAndStart(root, selected.entry.task.id, options);
|
|
6624
7073
|
});
|
|
6625
7074
|
const start = program.command("start").description("Start COOP work on a task");
|
|
6626
|
-
start.command("task").description("Start a specific task, or the top ready task if no id is provided").argument("[id]", "Task ID or alias").option("--track <track>", "Filter by track when no id is provided, else config.defaults.track if set").option("--delivery <delivery>", "Filter by delivery when no id is provided").option("--executor <executor>", "Filter by executor (human|ai|ci|hybrid) when no id is provided").option("--today <date>", "Evaluation date (YYYY-MM-DD) when no id is provided").option("--promote", "Promote the task in the current track/version context before starting it").option("--to <assignee>", "Assign the task before starting it").option("--claim", "Assign the task to the actor/user/default author before starting it").option("--actor <actor>", "Actor performing assignment/transition").option("--user <user>", "Current user for advisory authorization checks").option("--force", "Override advisory authorization checks").action(async (id, options) => {
|
|
7075
|
+
start.command("task").description("Start a specific task, or the top ready task if no id is provided").argument("[id]", "Task ID or alias").option("--track <track>", "Filter by the home/contributing track lens when no id is provided, else config.defaults.track if set").option("--delivery <delivery>", "Filter by delivery membership when no id is provided").option("--executor <executor>", "Filter by executor (human|ai|ci|hybrid) when no id is provided").option("--today <date>", "Evaluation date (YYYY-MM-DD) when no id is provided").option("--promote", "Promote the task in the current track/version context before starting it").option("--to <assignee>", "Assign the task before starting it").option("--claim", "Assign the task to the actor/user/default author before starting it").option("--actor <actor>", "Actor performing assignment/transition").option("--user <user>", "Current user for advisory authorization checks").option("--force", "Override advisory authorization checks").action(async (id, options) => {
|
|
6627
7076
|
const root = resolveRepoRoot();
|
|
6628
7077
|
printResolvedSelectionContext(root, options);
|
|
6629
7078
|
const taskId = id?.trim() || selectTopReadyTask(root, {
|
|
@@ -6899,7 +7348,7 @@ function updateIdea(id, options) {
|
|
|
6899
7348
|
console.log(`Updated ${next.id}`);
|
|
6900
7349
|
}
|
|
6901
7350
|
function registerUpdateCommand(program) {
|
|
6902
|
-
program.command("update").description("Update an existing COOP task or idea").argument("<id-or-type>", "Task or idea id/alias, or an explicit entity type").argument("[id]", "Entity id when an explicit type is provided").option("--title <title>").option("--priority <priority>").option("--status <status>").option("--assign <user>").option("--track <id>").option("--delivery <id>").option("--story-points <n>").option("--planned-hours <n>").option("--add-delivery-track <id>", "", collect, []).option("--remove-delivery-track <id>", "", collect, []).option("--priority-in <track:priority>", "", collect, []).option("--clear-priority-in <track>", "", collect, []).option("--add-dep <id>", "", collect, []).option("--remove-dep <id>", "", collect, []).option("--add-tag <tag>", "", collect, []).option("--remove-tag <tag>", "", collect, []).option("--add-fix-version <v>", "", collect, []).option("--remove-fix-version <v>", "", collect, []).option("--add-released-in <v>", "", collect, []).option("--remove-released-in <v>", "", collect, []).option("--acceptance-add <text>", "", collect, []).option("--acceptance-remove <text>", "", collect, []).option("--tests-add <text>", "", collect, []).option("--tests-remove <text>", "", collect, []).option("--add-linked-task <id>", "", collect, []).option("--remove-linked-task <id>", "", collect, []).option("--body-file <path>").option("--body-stdin").option("--dry-run").action((first, second, options) => {
|
|
7351
|
+
program.command("update").description("Update an existing COOP task or idea").argument("<id-or-type>", "Task or idea id/alias, or an explicit entity type").argument("[id]", "Entity id when an explicit type is provided").option("--title <title>").option("--priority <priority>").option("--status <status>").option("--assign <user>").option("--track <id>", "Set the task home/origin track").option("--delivery <id>", "Set the task primary delivery id").option("--story-points <n>").option("--planned-hours <n>").option("--add-delivery-track <id>", "", collect, []).option("--remove-delivery-track <id>", "", collect, []).option("--priority-in <track:priority>", "", collect, []).option("--clear-priority-in <track>", "", collect, []).option("--add-dep <id>", "", collect, []).option("--remove-dep <id>", "", collect, []).option("--add-tag <tag>", "", collect, []).option("--remove-tag <tag>", "", collect, []).option("--add-fix-version <v>", "", collect, []).option("--remove-fix-version <v>", "", collect, []).option("--add-released-in <v>", "", collect, []).option("--remove-released-in <v>", "", collect, []).option("--acceptance-add <text>", "", collect, []).option("--acceptance-remove <text>", "", collect, []).option("--tests-add <text>", "", collect, []).option("--tests-remove <text>", "", collect, []).option("--add-linked-task <id>", "", collect, []).option("--remove-linked-task <id>", "", collect, []).option("--body-file <path>").option("--body-stdin").option("--dry-run").action((first, second, options) => {
|
|
6903
7352
|
let resolved = resolveOptionalEntityArg(first, second, ["task", "idea"], "task");
|
|
6904
7353
|
if (!second && resolved.entity === "task") {
|
|
6905
7354
|
try {
|
|
@@ -6923,9 +7372,17 @@ function registerUpdateCommand(program) {
|
|
|
6923
7372
|
|
|
6924
7373
|
// src/commands/use.ts
|
|
6925
7374
|
function printContext(values) {
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
7375
|
+
const track = values.track?.trim() || "unset";
|
|
7376
|
+
const delivery = values.delivery?.trim() || "unset";
|
|
7377
|
+
const version = values.version?.trim() || "unset";
|
|
7378
|
+
console.log("Working Context:");
|
|
7379
|
+
console.log(`- Track: ${track}`);
|
|
7380
|
+
console.log(`- Delivery: ${delivery}`);
|
|
7381
|
+
console.log(`- Version: ${version}`);
|
|
7382
|
+
if (track === "unset" && delivery === "unset" && version === "unset") {
|
|
7383
|
+
console.log("");
|
|
7384
|
+
console.log("Hint: use `coop use track <id>`, `coop use delivery <id>`, or `coop use version <id>`.");
|
|
7385
|
+
}
|
|
6929
7386
|
}
|
|
6930
7387
|
function registerUseCommand(program) {
|
|
6931
7388
|
const use = program.command("use").description("Manage user-local working defaults for the current COOP project");
|
|
@@ -7352,16 +7809,16 @@ function chooseFieldValue(key, baseValue, oursValue, theirsValue, oursUpdated, t
|
|
|
7352
7809
|
}
|
|
7353
7810
|
function mergeTaskFrontmatter(base, ours, theirs) {
|
|
7354
7811
|
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)]);
|
|
7355
|
-
const
|
|
7812
|
+
const output2 = {};
|
|
7356
7813
|
const oursUpdated = asTimestamp(ours.updated);
|
|
7357
7814
|
const theirsUpdated = asTimestamp(theirs.updated);
|
|
7358
7815
|
for (const key of keys) {
|
|
7359
7816
|
const merged = chooseFieldValue(key, base[key], ours[key], theirs[key], oursUpdated, theirsUpdated);
|
|
7360
7817
|
if (merged !== void 0) {
|
|
7361
|
-
|
|
7818
|
+
output2[key] = merged;
|
|
7362
7819
|
}
|
|
7363
7820
|
}
|
|
7364
|
-
return
|
|
7821
|
+
return output2;
|
|
7365
7822
|
}
|
|
7366
7823
|
function mergeTextWithGit(ancestor, ours, theirs) {
|
|
7367
7824
|
const result = spawnSync5("git", ["merge-file", "-p", ours, ancestor, theirs], {
|
|
@@ -7392,8 +7849,8 @@ function mergeTaskFile(ancestorPath, oursPath, theirsPath) {
|
|
|
7392
7849
|
fs21.writeFileSync(oursBody, ours.body, "utf8");
|
|
7393
7850
|
fs21.writeFileSync(theirsBody, theirs.body, "utf8");
|
|
7394
7851
|
const mergedBody = mergeTextWithGit(ancestorBody, oursBody, theirsBody);
|
|
7395
|
-
const
|
|
7396
|
-
fs21.writeFileSync(oursPath,
|
|
7852
|
+
const output2 = stringifyFrontmatter6(mergedFrontmatter, mergedBody.output);
|
|
7853
|
+
fs21.writeFileSync(oursPath, output2, "utf8");
|
|
7397
7854
|
return mergedBody.ok ? 0 : 1;
|
|
7398
7855
|
} finally {
|
|
7399
7856
|
fs21.rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -7422,6 +7879,27 @@ function runMergeDriver(kind, ancestorPath, oursPath, theirsPath) {
|
|
|
7422
7879
|
return mergeTaskFile(ancestorPath, oursPath, theirsPath);
|
|
7423
7880
|
}
|
|
7424
7881
|
|
|
7882
|
+
// src/utils/basic-help.ts
|
|
7883
|
+
function renderBasicHelp() {
|
|
7884
|
+
return [
|
|
7885
|
+
"COOP Basics",
|
|
7886
|
+
"",
|
|
7887
|
+
"Day-to-day commands:",
|
|
7888
|
+
"- `coop current`: show working context, active work, and the next ready task",
|
|
7889
|
+
"- `coop use track <id>` / `coop use delivery <id>`: set working scope defaults",
|
|
7890
|
+
"- `coop next task` or `coop pick task`: choose work from COOP",
|
|
7891
|
+
"- `coop show <id>`: inspect a task, idea, or delivery",
|
|
7892
|
+
"- `coop list tasks --track <id>`: browse scoped work",
|
|
7893
|
+
"- `coop update <id> --track <id> --delivery <id>`: update task metadata",
|
|
7894
|
+
'- `coop comment <id> --message "..."`: append a task comment',
|
|
7895
|
+
"- `coop log-time <id> --hours 2 --kind worked`: append time spent",
|
|
7896
|
+
"- `coop review task <id>` / `coop complete task <id>`: move work through lifecycle",
|
|
7897
|
+
"- `coop help-ai --initial-prompt --strict --repo C:/path/to/repo --delivery MVP --command coop.cmd`: hand off COOP context to an agent",
|
|
7898
|
+
"",
|
|
7899
|
+
"Use `coop <command> --help` for detailed flags."
|
|
7900
|
+
].join("\n");
|
|
7901
|
+
}
|
|
7902
|
+
|
|
7425
7903
|
// src/utils/not-implemented.ts
|
|
7426
7904
|
function printNotImplemented(command, phase) {
|
|
7427
7905
|
console.log(`${command}: Not yet implemented - coming in Phase ${phase}.`);
|
|
@@ -7447,9 +7925,16 @@ function createProgram() {
|
|
|
7447
7925
|
const program = new Command();
|
|
7448
7926
|
program.name("coop");
|
|
7449
7927
|
program.version(readVersion());
|
|
7450
|
-
program.description("COOP CLI");
|
|
7928
|
+
program.description("COOP CLI for Git-native planning, backlog management, task execution, and delivery workflows.");
|
|
7451
7929
|
program.option("--verbose", "Print stack traces for command errors");
|
|
7452
7930
|
program.option("-p, --project <id>", "Select the active COOP project");
|
|
7931
|
+
program.addHelpText("after", `
|
|
7932
|
+
Common day-to-day commands:
|
|
7933
|
+
coop basics
|
|
7934
|
+
coop current
|
|
7935
|
+
coop next task
|
|
7936
|
+
coop show <id>
|
|
7937
|
+
`);
|
|
7453
7938
|
registerInitCommand(program);
|
|
7454
7939
|
registerCreateCommand(program);
|
|
7455
7940
|
registerCurrentCommand(program);
|
|
@@ -7485,6 +7970,9 @@ function createProgram() {
|
|
|
7485
7970
|
registerViewCommand(program);
|
|
7486
7971
|
registerWebhookCommand(program);
|
|
7487
7972
|
registerPhasePlaceholder(program, "ext", 3, "Plugin extension commands");
|
|
7973
|
+
program.command("basics").alias("help-basic").description("Show the small set of COOP commands that cover most day-to-day work").action(() => {
|
|
7974
|
+
console.log(renderBasicHelp());
|
|
7975
|
+
});
|
|
7488
7976
|
const hooks = program.command("hook");
|
|
7489
7977
|
hooks.command("pre-commit").option("--repo <path>", "Repository root", process.cwd()).action((options) => {
|
|
7490
7978
|
const repoRoot = options.repo ?? process.cwd();
|