@kitsy/coop 2.2.0 → 2.2.2
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 +881 -245
- 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,
|
|
@@ -1391,6 +1391,7 @@ import fs3 from "fs";
|
|
|
1391
1391
|
import path3 from "path";
|
|
1392
1392
|
import {
|
|
1393
1393
|
effective_priority,
|
|
1394
|
+
parseYamlFile as parseYamlFile2,
|
|
1394
1395
|
parseDeliveryFile,
|
|
1395
1396
|
parseIdeaFile as parseIdeaFile2,
|
|
1396
1397
|
parseTaskFile as parseTaskFile4,
|
|
@@ -1399,6 +1400,76 @@ import {
|
|
|
1399
1400
|
validateSemantic,
|
|
1400
1401
|
writeTask as writeTask3
|
|
1401
1402
|
} from "@kitsy/coop-core";
|
|
1403
|
+
function existingTrackMap(root) {
|
|
1404
|
+
const map = /* @__PURE__ */ new Map();
|
|
1405
|
+
for (const filePath of listTrackFiles(root)) {
|
|
1406
|
+
const track = parseYamlFile2(filePath);
|
|
1407
|
+
if (track.id?.trim()) {
|
|
1408
|
+
map.set(track.id.trim().toLowerCase(), track.id.trim());
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
return map;
|
|
1412
|
+
}
|
|
1413
|
+
function existingDeliveryMap(root) {
|
|
1414
|
+
const map = /* @__PURE__ */ new Map();
|
|
1415
|
+
for (const filePath of listDeliveryFiles(root)) {
|
|
1416
|
+
const delivery = parseDeliveryFile(filePath).delivery;
|
|
1417
|
+
if (delivery.id?.trim()) {
|
|
1418
|
+
map.set(delivery.id.trim().toLowerCase(), delivery.id.trim());
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return map;
|
|
1422
|
+
}
|
|
1423
|
+
function resolveExistingTrackId(root, trackId) {
|
|
1424
|
+
const normalized = trackId.trim().toLowerCase();
|
|
1425
|
+
if (!normalized) return void 0;
|
|
1426
|
+
if (normalized === "unassigned") return "unassigned";
|
|
1427
|
+
return existingTrackMap(root).get(normalized);
|
|
1428
|
+
}
|
|
1429
|
+
function resolveExistingDeliveryId(root, deliveryId) {
|
|
1430
|
+
const normalized = deliveryId.trim().toLowerCase();
|
|
1431
|
+
if (!normalized) return void 0;
|
|
1432
|
+
return existingDeliveryMap(root).get(normalized);
|
|
1433
|
+
}
|
|
1434
|
+
function assertExistingTrackId(root, trackId) {
|
|
1435
|
+
const resolved = resolveExistingTrackId(root, trackId);
|
|
1436
|
+
if (resolved) {
|
|
1437
|
+
return resolved;
|
|
1438
|
+
}
|
|
1439
|
+
throw new Error(
|
|
1440
|
+
`Unknown track '${trackId}'. Create it first with \`coop create track --id ${trackId} --name "${trackId}"\` or use \`unassigned\`.`
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
function assertExistingDeliveryId(root, deliveryId) {
|
|
1444
|
+
const resolved = resolveExistingDeliveryId(root, deliveryId);
|
|
1445
|
+
if (resolved) {
|
|
1446
|
+
return resolved;
|
|
1447
|
+
}
|
|
1448
|
+
throw new Error(`Unknown delivery '${deliveryId}'. Create it first with \`coop create delivery --id ${deliveryId} --name "${deliveryId}"\`.`);
|
|
1449
|
+
}
|
|
1450
|
+
function unique(values) {
|
|
1451
|
+
return Array.from(new Set(values));
|
|
1452
|
+
}
|
|
1453
|
+
function normalizeTaskReferences(root, task) {
|
|
1454
|
+
const next = { ...task };
|
|
1455
|
+
if (next.track?.trim()) {
|
|
1456
|
+
next.track = assertExistingTrackId(root, next.track.trim());
|
|
1457
|
+
}
|
|
1458
|
+
if (next.delivery?.trim()) {
|
|
1459
|
+
next.delivery = assertExistingDeliveryId(root, next.delivery.trim());
|
|
1460
|
+
}
|
|
1461
|
+
if (next.delivery_tracks?.length) {
|
|
1462
|
+
next.delivery_tracks = unique(next.delivery_tracks.map((trackId) => assertExistingTrackId(root, trackId)));
|
|
1463
|
+
}
|
|
1464
|
+
if (next.priority_context && typeof next.priority_context === "object") {
|
|
1465
|
+
const normalized = {};
|
|
1466
|
+
for (const [trackId, priority] of Object.entries(next.priority_context)) {
|
|
1467
|
+
normalized[assertExistingTrackId(root, trackId)] = priority;
|
|
1468
|
+
}
|
|
1469
|
+
next.priority_context = normalized;
|
|
1470
|
+
}
|
|
1471
|
+
return next;
|
|
1472
|
+
}
|
|
1402
1473
|
function resolveTaskFile(root, idOrAlias) {
|
|
1403
1474
|
const reference = resolveReference(root, idOrAlias, "task");
|
|
1404
1475
|
return path3.join(root, ...reference.file.split("/"));
|
|
@@ -1416,20 +1487,22 @@ function loadIdeaEntry(root, idOrAlias) {
|
|
|
1416
1487
|
return { filePath, parsed: parseIdeaFile2(filePath) };
|
|
1417
1488
|
}
|
|
1418
1489
|
function writeIdeaFile(filePath, parsed, idea, body = parsed.body) {
|
|
1419
|
-
const
|
|
1420
|
-
fs3.writeFileSync(filePath,
|
|
1490
|
+
const output2 = stringifyFrontmatter2({ ...parsed.raw, ...idea }, body);
|
|
1491
|
+
fs3.writeFileSync(filePath, output2, "utf8");
|
|
1421
1492
|
}
|
|
1422
|
-
function validateTaskForWrite(task, filePath) {
|
|
1423
|
-
const
|
|
1424
|
-
const
|
|
1493
|
+
function validateTaskForWrite(root, task, filePath) {
|
|
1494
|
+
const normalized = normalizeTaskReferences(root, task);
|
|
1495
|
+
const structuralIssues = validateStructural2(normalized, { filePath });
|
|
1496
|
+
const semanticIssues = validateSemantic(normalized);
|
|
1425
1497
|
const errors = [...structuralIssues, ...semanticIssues].filter((issue) => issue.level === "error");
|
|
1426
1498
|
if (errors.length > 0) {
|
|
1427
1499
|
throw new Error(errors.map((issue) => `- ${issue.message}`).join("\n"));
|
|
1428
1500
|
}
|
|
1501
|
+
return normalized;
|
|
1429
1502
|
}
|
|
1430
|
-
function writeTaskEntry(filePath, parsed, task, body = parsed.body) {
|
|
1431
|
-
validateTaskForWrite(task, filePath);
|
|
1432
|
-
writeTask3(
|
|
1503
|
+
function writeTaskEntry(root, filePath, parsed, task, body = parsed.body) {
|
|
1504
|
+
const normalized = validateTaskForWrite(root, task, filePath);
|
|
1505
|
+
writeTask3(normalized, {
|
|
1433
1506
|
body,
|
|
1434
1507
|
raw: parsed.raw,
|
|
1435
1508
|
filePath
|
|
@@ -1574,12 +1647,12 @@ function clearWorkingContext(root, coopHome, scope) {
|
|
|
1574
1647
|
delete next[scope];
|
|
1575
1648
|
return writeWorkingContext(root, coopHome, next);
|
|
1576
1649
|
}
|
|
1577
|
-
function resolveContextValueWithSource(explicit,
|
|
1650
|
+
function resolveContextValueWithSource(explicit, contextValue2, sharedDefault2) {
|
|
1578
1651
|
if (explicit?.trim()) {
|
|
1579
1652
|
return { value: explicit.trim(), source: "arg" };
|
|
1580
1653
|
}
|
|
1581
|
-
if (
|
|
1582
|
-
return { value:
|
|
1654
|
+
if (contextValue2?.trim()) {
|
|
1655
|
+
return { value: contextValue2.trim(), source: "use" };
|
|
1583
1656
|
}
|
|
1584
1657
|
if (sharedDefault2?.trim()) {
|
|
1585
1658
|
return { value: sharedDefault2.trim(), source: "config" };
|
|
@@ -1613,10 +1686,12 @@ function configDefaultVersion(root) {
|
|
|
1613
1686
|
}
|
|
1614
1687
|
function resolveSelectionOptions(root, options) {
|
|
1615
1688
|
const context = readWorkingContext(root, resolveCoopHome());
|
|
1689
|
+
const track = options.track?.trim() || context.track?.trim() || configDefaultTrack(root);
|
|
1690
|
+
const delivery = options.delivery?.trim() || context.delivery?.trim() || configDefaultDelivery(root);
|
|
1616
1691
|
return {
|
|
1617
1692
|
...options,
|
|
1618
|
-
track:
|
|
1619
|
-
delivery:
|
|
1693
|
+
track: track ? resolveExistingTrackId(root, track) ?? track : void 0,
|
|
1694
|
+
delivery: delivery ? resolveExistingDeliveryId(root, delivery) ?? delivery : void 0,
|
|
1620
1695
|
version: options.version?.trim() || context.version?.trim() || configDefaultVersion(root)
|
|
1621
1696
|
};
|
|
1622
1697
|
}
|
|
@@ -1885,7 +1960,7 @@ function registerCommentCommand(program) {
|
|
|
1885
1960
|
const root = resolveRepoRoot();
|
|
1886
1961
|
const { filePath, parsed } = loadTaskEntry(root, id);
|
|
1887
1962
|
const task = appendTaskComment(parsed.task, options.author?.trim() || defaultCoopAuthor(root), options.message.trim());
|
|
1888
|
-
writeTaskEntry(filePath, parsed, task);
|
|
1963
|
+
writeTaskEntry(root, filePath, parsed, task);
|
|
1889
1964
|
console.log(`Commented ${task.id}`);
|
|
1890
1965
|
});
|
|
1891
1966
|
}
|
|
@@ -2137,16 +2212,16 @@ import {
|
|
|
2137
2212
|
parseTaskFile as parseTaskFile7,
|
|
2138
2213
|
writeYamlFile as writeYamlFile3,
|
|
2139
2214
|
stringifyFrontmatter as stringifyFrontmatter4,
|
|
2140
|
-
validateStructural as validateStructural5,
|
|
2141
2215
|
writeTask as writeTask6
|
|
2142
2216
|
} from "@kitsy/coop-core";
|
|
2143
2217
|
import { create_provider_idea_decomposer, decompose_idea_to_tasks } from "@kitsy/coop-ai";
|
|
2144
2218
|
|
|
2145
2219
|
// src/utils/prompt.ts
|
|
2146
|
-
import readline from "readline
|
|
2220
|
+
import readline from "readline";
|
|
2221
|
+
import readlinePromises from "readline/promises";
|
|
2147
2222
|
import process2 from "process";
|
|
2148
2223
|
async function ask(question, defaultValue = "") {
|
|
2149
|
-
const rl =
|
|
2224
|
+
const rl = readlinePromises.createInterface({
|
|
2150
2225
|
input: process2.stdin,
|
|
2151
2226
|
output: process2.stdout
|
|
2152
2227
|
});
|
|
@@ -2155,6 +2230,76 @@ async function ask(question, defaultValue = "") {
|
|
|
2155
2230
|
rl.close();
|
|
2156
2231
|
return answer || defaultValue;
|
|
2157
2232
|
}
|
|
2233
|
+
function renderSelect(question, choices, selected) {
|
|
2234
|
+
process2.stdout.write(`${question}
|
|
2235
|
+
`);
|
|
2236
|
+
for (let index = 0; index < choices.length; index += 1) {
|
|
2237
|
+
const choice = choices[index];
|
|
2238
|
+
const prefix = index === selected ? ">" : " ";
|
|
2239
|
+
const hint = choice.hint ? ` - ${choice.hint}` : "";
|
|
2240
|
+
process2.stdout.write(` ${prefix} ${choice.label}${hint}
|
|
2241
|
+
`);
|
|
2242
|
+
}
|
|
2243
|
+
process2.stdout.write("\nUse Up/Down arrows to choose, Enter to confirm.\n");
|
|
2244
|
+
}
|
|
2245
|
+
function moveCursorUp(lines) {
|
|
2246
|
+
if (lines <= 0) return;
|
|
2247
|
+
readline.moveCursor(process2.stdout, 0, -lines);
|
|
2248
|
+
readline.clearScreenDown(process2.stdout);
|
|
2249
|
+
}
|
|
2250
|
+
async function select(question, choices, defaultIndex = 0) {
|
|
2251
|
+
if (choices.length === 0) {
|
|
2252
|
+
throw new Error(`No choices available for '${question}'.`);
|
|
2253
|
+
}
|
|
2254
|
+
if (!process2.stdin.isTTY || !process2.stdout.isTTY) {
|
|
2255
|
+
return choices[Math.min(Math.max(defaultIndex, 0), choices.length - 1)].value;
|
|
2256
|
+
}
|
|
2257
|
+
readline.emitKeypressEvents(process2.stdin);
|
|
2258
|
+
const previousRawMode = process2.stdin.isRaw;
|
|
2259
|
+
const wasPaused = typeof process2.stdin.isPaused === "function" ? process2.stdin.isPaused() : false;
|
|
2260
|
+
process2.stdin.setRawMode(true);
|
|
2261
|
+
process2.stdin.resume();
|
|
2262
|
+
let selected = Math.min(Math.max(defaultIndex, 0), choices.length - 1);
|
|
2263
|
+
const renderedLines = choices.length + 2;
|
|
2264
|
+
renderSelect(question, choices, selected);
|
|
2265
|
+
return await new Promise((resolve, reject) => {
|
|
2266
|
+
const cleanup = () => {
|
|
2267
|
+
process2.stdin.off("keypress", onKeypress);
|
|
2268
|
+
process2.stdin.setRawMode(previousRawMode ?? false);
|
|
2269
|
+
if (wasPaused) {
|
|
2270
|
+
process2.stdin.pause();
|
|
2271
|
+
}
|
|
2272
|
+
process2.stdout.write("\n");
|
|
2273
|
+
};
|
|
2274
|
+
const rerender = () => {
|
|
2275
|
+
moveCursorUp(renderedLines + 1);
|
|
2276
|
+
renderSelect(question, choices, selected);
|
|
2277
|
+
};
|
|
2278
|
+
const onKeypress = (_input, key) => {
|
|
2279
|
+
if (key.name === "up") {
|
|
2280
|
+
selected = selected === 0 ? choices.length - 1 : selected - 1;
|
|
2281
|
+
rerender();
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
if (key.name === "down") {
|
|
2285
|
+
selected = selected === choices.length - 1 ? 0 : selected + 1;
|
|
2286
|
+
rerender();
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
if (key.name === "return") {
|
|
2290
|
+
const value = choices[selected].value;
|
|
2291
|
+
cleanup();
|
|
2292
|
+
resolve(value);
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
if (key.ctrl && key.name === "c") {
|
|
2296
|
+
cleanup();
|
|
2297
|
+
reject(new Error("Prompt cancelled."));
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
process2.stdin.on("keypress", onKeypress);
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2158
2303
|
|
|
2159
2304
|
// src/utils/idea-drafts.ts
|
|
2160
2305
|
import fs5 from "fs";
|
|
@@ -2565,7 +2710,7 @@ function plusDaysIso(days) {
|
|
|
2565
2710
|
date.setUTCDate(date.getUTCDate() + days);
|
|
2566
2711
|
return date.toISOString().slice(0, 10);
|
|
2567
2712
|
}
|
|
2568
|
-
function
|
|
2713
|
+
function unique2(values) {
|
|
2569
2714
|
return Array.from(new Set(values));
|
|
2570
2715
|
}
|
|
2571
2716
|
function resolveIdeaFile2(root, idOrAlias) {
|
|
@@ -2573,35 +2718,35 @@ function resolveIdeaFile2(root, idOrAlias) {
|
|
|
2573
2718
|
return path8.join(root, ...target.file.split("/"));
|
|
2574
2719
|
}
|
|
2575
2720
|
function updateIdeaLinkedTasks(filePath, idea, raw, body, linked) {
|
|
2576
|
-
const next =
|
|
2721
|
+
const next = unique2([...idea.linked_tasks ?? [], ...linked]).sort((a, b) => a.localeCompare(b));
|
|
2577
2722
|
const nextRaw = {
|
|
2578
2723
|
...raw,
|
|
2579
2724
|
linked_tasks: next
|
|
2580
2725
|
};
|
|
2581
2726
|
fs7.writeFileSync(filePath, stringifyFrontmatter4(nextRaw, body), "utf8");
|
|
2582
2727
|
}
|
|
2583
|
-
function makeTaskDraft(
|
|
2728
|
+
function makeTaskDraft(input2) {
|
|
2584
2729
|
return {
|
|
2585
|
-
title:
|
|
2586
|
-
type:
|
|
2587
|
-
status:
|
|
2588
|
-
track:
|
|
2589
|
-
priority:
|
|
2590
|
-
body:
|
|
2591
|
-
acceptance:
|
|
2592
|
-
testsRequired:
|
|
2593
|
-
authorityRefs:
|
|
2594
|
-
derivedRefs:
|
|
2730
|
+
title: input2.title,
|
|
2731
|
+
type: input2.type,
|
|
2732
|
+
status: input2.status,
|
|
2733
|
+
track: input2.track,
|
|
2734
|
+
priority: input2.priority,
|
|
2735
|
+
body: input2.body,
|
|
2736
|
+
acceptance: unique2(input2.acceptance ?? []),
|
|
2737
|
+
testsRequired: unique2(input2.testsRequired ?? []),
|
|
2738
|
+
authorityRefs: unique2(input2.authorityRefs ?? []),
|
|
2739
|
+
derivedRefs: unique2(input2.derivedRefs ?? [])
|
|
2595
2740
|
};
|
|
2596
2741
|
}
|
|
2597
2742
|
function registerCreateCommand(program) {
|
|
2598
2743
|
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>", "
|
|
2744
|
+
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
2745
|
const root = resolveRepoRoot();
|
|
2601
2746
|
const coop = ensureCoopInitialized(root);
|
|
2602
2747
|
const interactive = Boolean(options.interactive);
|
|
2603
2748
|
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) {
|
|
2749
|
+
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
2750
|
throw new Error("Cannot combine --from-file/--stdin with direct task field flags. Use one input mode.");
|
|
2606
2751
|
}
|
|
2607
2752
|
const draftInput = await readDraftContent(root, {
|
|
@@ -2623,11 +2768,12 @@ function registerCreateCommand(program) {
|
|
|
2623
2768
|
const statusInput = (options.status?.trim() || (interactive ? await ask("Task status", "todo") : "todo")).toLowerCase();
|
|
2624
2769
|
const track = options.track?.trim() || (interactive ? await ask("Track", "unassigned") : "unassigned");
|
|
2625
2770
|
const priority = options.priority?.trim() || (interactive ? await ask("Priority", "p2") : "p2");
|
|
2771
|
+
const delivery = options.delivery?.trim() || (interactive ? await ask("Delivery (optional)", "") : "");
|
|
2626
2772
|
const body = options.body ?? (interactive ? await ask("Task body (optional)", "") : "");
|
|
2627
|
-
const acceptance = options.acceptance && options.acceptance.length > 0 ?
|
|
2628
|
-
const testsRequired = options.testsRequired && options.testsRequired.length > 0 ?
|
|
2629
|
-
const authorityRefs = options.authorityRef && options.authorityRef.length > 0 ?
|
|
2630
|
-
const derivedRefs = options.derivedRef && options.derivedRef.length > 0 ?
|
|
2773
|
+
const acceptance = options.acceptance && options.acceptance.length > 0 ? unique2(options.acceptance) : interactive ? parseCsv(await ask("Acceptance criteria (comma-separated, optional)", "")) : [];
|
|
2774
|
+
const testsRequired = options.testsRequired && options.testsRequired.length > 0 ? unique2(options.testsRequired) : interactive ? parseCsv(await ask("Tests required (comma-separated, optional)", "")) : [];
|
|
2775
|
+
const authorityRefs = options.authorityRef && options.authorityRef.length > 0 ? unique2(options.authorityRef) : interactive ? parseCsv(await ask("Authority refs (comma-separated, optional)", "")) : [];
|
|
2776
|
+
const derivedRefs = options.derivedRef && options.derivedRef.length > 0 ? unique2(options.derivedRef) : interactive ? parseCsv(await ask("Derived refs (comma-separated, optional)", "")) : [];
|
|
2631
2777
|
const date = todayIsoDate();
|
|
2632
2778
|
const drafts = [];
|
|
2633
2779
|
let sourceIdeaPath = null;
|
|
@@ -2733,6 +2879,7 @@ function registerCreateCommand(program) {
|
|
|
2733
2879
|
aliases: [],
|
|
2734
2880
|
track: draft.track,
|
|
2735
2881
|
priority: draft.priority,
|
|
2882
|
+
delivery: delivery || void 0,
|
|
2736
2883
|
acceptance: draft.acceptance,
|
|
2737
2884
|
tests_required: draft.testsRequired,
|
|
2738
2885
|
origin: draft.authorityRefs.length > 0 || draft.derivedRefs.length > 0 || options.from?.trim() ? {
|
|
@@ -2742,13 +2889,8 @@ function registerCreateCommand(program) {
|
|
|
2742
2889
|
} : void 0
|
|
2743
2890
|
};
|
|
2744
2891
|
const filePath = path8.join(coop, "tasks", `${id}.md`);
|
|
2745
|
-
const
|
|
2746
|
-
|
|
2747
|
-
const message = structuralIssues.map((issue) => `- ${issue.message}`).join("\n");
|
|
2748
|
-
throw new Error(`Task failed structural validation:
|
|
2749
|
-
${message}`);
|
|
2750
|
-
}
|
|
2751
|
-
writeTask6(task, {
|
|
2892
|
+
const normalizedTask = validateTaskForWrite(root, task, filePath);
|
|
2893
|
+
writeTask6(normalizedTask, {
|
|
2752
2894
|
body: draft.body,
|
|
2753
2895
|
filePath
|
|
2754
2896
|
});
|
|
@@ -2824,7 +2966,7 @@ ${message}`);
|
|
|
2824
2966
|
const interactive = Boolean(options.interactive);
|
|
2825
2967
|
const name = options.name?.trim() || nameArg?.trim() || await ask("Track name");
|
|
2826
2968
|
if (!name) throw new Error("Track name is required.");
|
|
2827
|
-
const capacityProfiles =
|
|
2969
|
+
const capacityProfiles = unique2(
|
|
2828
2970
|
options.profiles ? parseCsv(options.profiles) : interactive ? parseCsv(await ask("Capacity profiles (comma-separated)", "backend_team")) : ["backend_team"]
|
|
2829
2971
|
);
|
|
2830
2972
|
const maxWipInput = options.maxWip?.trim() || (interactive ? await ask("Max concurrent tasks", "6") : "6");
|
|
@@ -2832,7 +2974,7 @@ ${message}`);
|
|
|
2832
2974
|
if (typeof maxWip !== "number" || maxWip <= 0 || !Number.isInteger(maxWip)) {
|
|
2833
2975
|
throw new Error("max-wip must be a positive integer.");
|
|
2834
2976
|
}
|
|
2835
|
-
const allowed =
|
|
2977
|
+
const allowed = unique2(
|
|
2836
2978
|
options.allowedTypes ? parseCsv(options.allowedTypes).map((entry) => entry.toLowerCase()) : interactive ? parseCsv(await ask("Allowed types (comma-separated)", "feature,bug,chore,spike")).map(
|
|
2837
2979
|
(entry) => entry.toLowerCase()
|
|
2838
2980
|
) : ["feature", "bug", "chore", "spike"]
|
|
@@ -2914,7 +3056,7 @@ ${message}`);
|
|
|
2914
3056
|
options.budgetCost?.trim() || (interactive ? await ask("Budget cost USD (optional)", "") : void 0),
|
|
2915
3057
|
"budget-cost"
|
|
2916
3058
|
);
|
|
2917
|
-
const capacityProfiles =
|
|
3059
|
+
const capacityProfiles = unique2(
|
|
2918
3060
|
options.profiles ? parseCsv(options.profiles) : interactive ? parseCsv(await ask("Capacity profiles (comma-separated)", "backend_team")) : ["backend_team"]
|
|
2919
3061
|
);
|
|
2920
3062
|
const tasks = listTaskFiles(root).map((filePath2) => {
|
|
@@ -2927,12 +3069,12 @@ ${message}`);
|
|
|
2927
3069
|
console.log(`- ${task.id}: ${task.title}`);
|
|
2928
3070
|
}
|
|
2929
3071
|
}
|
|
2930
|
-
const scopeInclude =
|
|
3072
|
+
const scopeInclude = unique2(
|
|
2931
3073
|
options.scope ? parseCsv(options.scope).map((value) => value.toUpperCase()) : interactive ? parseCsv(await ask("Scope include task IDs (comma-separated)", tasks.map((task) => task.id).join(","))).map(
|
|
2932
3074
|
(value) => value.toUpperCase()
|
|
2933
3075
|
) : []
|
|
2934
3076
|
);
|
|
2935
|
-
const scopeExclude =
|
|
3077
|
+
const scopeExclude = unique2(
|
|
2936
3078
|
options.exclude ? parseCsv(options.exclude).map((value) => value.toUpperCase()) : interactive ? parseCsv(await ask("Scope exclude task IDs (comma-separated)", "")).map((value) => value.toUpperCase()) : []
|
|
2937
3079
|
);
|
|
2938
3080
|
const knownTaskIds = new Set(tasks.map((task) => task.id));
|
|
@@ -2983,6 +3125,27 @@ ${message}`);
|
|
|
2983
3125
|
}
|
|
2984
3126
|
|
|
2985
3127
|
// src/commands/current.ts
|
|
3128
|
+
function contextValue(value) {
|
|
3129
|
+
return value?.trim() || "unset";
|
|
3130
|
+
}
|
|
3131
|
+
function selectionScope(context) {
|
|
3132
|
+
if (context.delivery?.trim()) {
|
|
3133
|
+
return `delivery '${context.delivery.trim()}'`;
|
|
3134
|
+
}
|
|
3135
|
+
if (context.track?.trim()) {
|
|
3136
|
+
return `track '${context.track.trim()}'`;
|
|
3137
|
+
}
|
|
3138
|
+
return "workspace-wide (no working track or delivery set)";
|
|
3139
|
+
}
|
|
3140
|
+
function selectionReason(context, score) {
|
|
3141
|
+
if (context.delivery?.trim()) {
|
|
3142
|
+
return `top ready task for delivery '${context.delivery.trim()}' with score ${score.toFixed(1)}`;
|
|
3143
|
+
}
|
|
3144
|
+
if (context.track?.trim()) {
|
|
3145
|
+
return `top ready task for track '${context.track.trim()}' with score ${score.toFixed(1)}`;
|
|
3146
|
+
}
|
|
3147
|
+
return `top ready task across the workspace with score ${score.toFixed(1)}`;
|
|
3148
|
+
}
|
|
2986
3149
|
function registerCurrentCommand(program) {
|
|
2987
3150
|
program.command("current").description("Show active project, working context, my in-progress tasks, and the next ready task").action(() => {
|
|
2988
3151
|
const root = resolveRepoRoot();
|
|
@@ -2994,9 +3157,14 @@ function registerCurrentCommand(program) {
|
|
|
2994
3157
|
);
|
|
2995
3158
|
console.log(`Project: ${identity.name} (${identity.id})`);
|
|
2996
3159
|
console.log(`Actor: ${actor}`);
|
|
2997
|
-
console.log(
|
|
2998
|
-
console.log(
|
|
2999
|
-
console.log(
|
|
3160
|
+
console.log("");
|
|
3161
|
+
console.log("Working Context:");
|
|
3162
|
+
console.log(`- Track: ${contextValue(context.track)}`);
|
|
3163
|
+
console.log(`- Delivery: ${contextValue(context.delivery)}`);
|
|
3164
|
+
console.log(`- Version: ${contextValue(context.version)}`);
|
|
3165
|
+
if (!context.track?.trim() && !context.delivery?.trim()) {
|
|
3166
|
+
console.log("- Hint: use `coop use track <id>` and/or `coop use delivery <id>` to set your working context.");
|
|
3167
|
+
}
|
|
3000
3168
|
console.log("");
|
|
3001
3169
|
console.log("My Active Tasks:");
|
|
3002
3170
|
if (inProgress.length === 0) {
|
|
@@ -3014,6 +3182,11 @@ function registerCurrentCommand(program) {
|
|
|
3014
3182
|
delivery: context.delivery,
|
|
3015
3183
|
version: context.version
|
|
3016
3184
|
});
|
|
3185
|
+
console.log(`Selection scope: ${selectionScope(context)}`);
|
|
3186
|
+
console.log(`Why selected: ${selectionReason(context, selected.entry.score)}`);
|
|
3187
|
+
if ((selected.entry.task.track ?? "").trim().toLowerCase() === "unassigned") {
|
|
3188
|
+
console.log("Warning: selected task has no assigned track.");
|
|
3189
|
+
}
|
|
3017
3190
|
console.log(formatSelectedTask(selected.entry, selected.selection));
|
|
3018
3191
|
} catch (error) {
|
|
3019
3192
|
console.log(error instanceof Error ? error.message : String(error));
|
|
@@ -3024,7 +3197,7 @@ function registerCurrentCommand(program) {
|
|
|
3024
3197
|
// src/commands/deps.ts
|
|
3025
3198
|
import { load_graph as load_graph3 } from "@kitsy/coop-core";
|
|
3026
3199
|
function registerDepsCommand(program) {
|
|
3027
|
-
program.command("deps").description("Show dependencies and reverse dependencies for a task").argument("<id>", "Task id or alias").action((id) => {
|
|
3200
|
+
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
3201
|
const root = resolveRepoRoot();
|
|
3029
3202
|
const graph = load_graph3(coopDir(root));
|
|
3030
3203
|
const reference = resolveReference(root, id, "task");
|
|
@@ -3033,9 +3206,25 @@ function registerDepsCommand(program) {
|
|
|
3033
3206
|
throw new Error(`Task '${reference.id}' not found.`);
|
|
3034
3207
|
}
|
|
3035
3208
|
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
|
-
|
|
3209
|
+
console.log(`Task: ${task.id} [${task.status}] ${task.title}`);
|
|
3210
|
+
console.log("Depends On:");
|
|
3211
|
+
if (!task.depends_on || task.depends_on.length === 0) {
|
|
3212
|
+
console.log("- none");
|
|
3213
|
+
} else {
|
|
3214
|
+
for (const depId of task.depends_on) {
|
|
3215
|
+
const dep = graph.nodes.get(depId);
|
|
3216
|
+
console.log(`- ${depId}${dep ? ` [${dep.status}] ${dep.title}` : " [missing]"}`);
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
console.log("Required By:");
|
|
3220
|
+
if (reverse.length === 0) {
|
|
3221
|
+
console.log("- none");
|
|
3222
|
+
} else {
|
|
3223
|
+
for (const dependentId of reverse) {
|
|
3224
|
+
const dependent = graph.nodes.get(dependentId);
|
|
3225
|
+
console.log(`- ${dependentId}${dependent ? ` [${dependent.status}] ${dependent.title}` : " [missing]"}`);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3039
3228
|
});
|
|
3040
3229
|
}
|
|
3041
3230
|
|
|
@@ -3055,10 +3244,10 @@ import {
|
|
|
3055
3244
|
function normalize(value) {
|
|
3056
3245
|
return value.trim().toLowerCase();
|
|
3057
3246
|
}
|
|
3058
|
-
function resolveDelivery(graph,
|
|
3059
|
-
const direct = graph.deliveries.get(
|
|
3247
|
+
function resolveDelivery(graph, input2) {
|
|
3248
|
+
const direct = graph.deliveries.get(input2);
|
|
3060
3249
|
if (direct) return direct;
|
|
3061
|
-
const target = normalize(
|
|
3250
|
+
const target = normalize(input2);
|
|
3062
3251
|
const byId = Array.from(graph.deliveries.values()).find((delivery) => normalize(delivery.id) === target);
|
|
3063
3252
|
if (byId) return byId;
|
|
3064
3253
|
const byName = Array.from(graph.deliveries.values()).filter((delivery) => normalize(delivery.name) === target);
|
|
@@ -3066,9 +3255,9 @@ function resolveDelivery(graph, input3) {
|
|
|
3066
3255
|
return byName[0];
|
|
3067
3256
|
}
|
|
3068
3257
|
if (byName.length > 1) {
|
|
3069
|
-
throw new Error(`Multiple deliveries match '${
|
|
3258
|
+
throw new Error(`Multiple deliveries match '${input2}'. Use delivery id instead.`);
|
|
3070
3259
|
}
|
|
3071
|
-
throw new Error(`Delivery '${
|
|
3260
|
+
throw new Error(`Delivery '${input2}' not found.`);
|
|
3072
3261
|
}
|
|
3073
3262
|
|
|
3074
3263
|
// src/commands/graph.ts
|
|
@@ -3221,8 +3410,10 @@ var catalog = {
|
|
|
3221
3410
|
selection_rules: [
|
|
3222
3411
|
"Use `coop project show` first to confirm the active workspace and project.",
|
|
3223
3412
|
"Use `coop use show` to inspect the current user-local working defaults for track, delivery, and version.",
|
|
3413
|
+
"Before assigning `track` or `delivery` values to tasks, inspect or create named entities with `coop list tracks`, `coop list deliveries`, `coop create track`, and `coop create delivery`.",
|
|
3224
3414
|
"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
3415
|
"Commands resolve selection scope from: explicit CLI arg, then `coop use` working context, then shared project defaults.",
|
|
3416
|
+
"Use `--track` for the workstream lens (home track or delivery_tracks). Use `--delivery` for release/scope membership.",
|
|
3226
3417
|
"Use `coop show <id>` or `coop show task <id>` before implementation to read acceptance, tests_required, dependencies, origin refs, and task metadata.",
|
|
3227
3418
|
"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
3419
|
],
|
|
@@ -3277,6 +3468,8 @@ var catalog = {
|
|
|
3277
3468
|
{ usage: "coop use track <id>", purpose: "Set the default working track for commands that can infer scope." },
|
|
3278
3469
|
{ usage: "coop use delivery <id>", purpose: "Set the default working delivery for commands that need delivery scope." },
|
|
3279
3470
|
{ usage: "coop use version <id>", purpose: "Set the default working version for promotion and prompt generation." },
|
|
3471
|
+
{ usage: "coop list tracks", purpose: "List valid named tracks before assigning or updating task track values." },
|
|
3472
|
+
{ usage: "coop list deliveries", purpose: "List valid named deliveries before assigning or updating task delivery values." },
|
|
3280
3473
|
{ usage: "coop current", purpose: "Show the active project, working context, my active tasks, and the next ready task." },
|
|
3281
3474
|
{ usage: "coop naming", purpose: "Explain the current naming template, tokens, and examples." },
|
|
3282
3475
|
{ usage: 'coop naming preview "Natural-language COOP command recommender"', purpose: "Preview a semantic ID before creating an item." }
|
|
@@ -3290,6 +3483,8 @@ var catalog = {
|
|
|
3290
3483
|
{ usage: "coop create idea --from-file idea-draft.yml", purpose: "Ingest a structured idea draft file." },
|
|
3291
3484
|
{ usage: "cat idea.md | coop create idea --stdin", purpose: "Ingest an idea draft from stdin." },
|
|
3292
3485
|
{ usage: 'coop create task "Implement webhook pipeline"', purpose: "Create a task with defaults." },
|
|
3486
|
+
{ usage: 'coop create task --title "Lock auth contract" --track MVP --delivery MVP', purpose: "Create a task directly inside a track and delivery scope." },
|
|
3487
|
+
{ usage: "coop create track --id mvp --name MVP", purpose: "Create a named track before tasks refer to it." },
|
|
3293
3488
|
{
|
|
3294
3489
|
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
3490
|
purpose: "Create a planning-grade task with acceptance, tests, and origin refs."
|
|
@@ -3333,15 +3528,20 @@ var catalog = {
|
|
|
3333
3528
|
description: "Read backlog state, task details, and planning output.",
|
|
3334
3529
|
commands: [
|
|
3335
3530
|
{ usage: "coop list tasks --status todo", purpose: "List tasks with filters." },
|
|
3336
|
-
{ usage: "coop list
|
|
3531
|
+
{ usage: "coop list tracks", purpose: "List valid named tracks." },
|
|
3532
|
+
{ usage: "coop list deliveries", purpose: "List valid named deliveries." },
|
|
3533
|
+
{ 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
3534
|
{ usage: "coop list tasks --mine", purpose: "List tasks assigned to the current default COOP author." },
|
|
3338
3535
|
{ usage: 'coop search "auth and login form"', purpose: "Run deterministic non-AI search across tasks, ideas, and deliveries." },
|
|
3339
3536
|
{ usage: 'coop search "auth" --open', purpose: "Require a single match and print the resolved summary row." },
|
|
3340
3537
|
{ usage: "coop show PM-101", purpose: "Resolve a task, idea, or delivery by reference without an extra entity noun." },
|
|
3538
|
+
{ usage: "coop show PM-101 --compact", purpose: "Show a smaller summary view for a large task." },
|
|
3341
3539
|
{ usage: "coop show task PM-101", purpose: "Show a task with acceptance, tests_required, refs, and runbook sections." },
|
|
3342
3540
|
{ usage: "coop show idea IDEA-101", purpose: "Show an idea." },
|
|
3343
|
-
{ usage: "coop deps PM-101", purpose: "Show task dependencies and reverse dependencies." },
|
|
3541
|
+
{ usage: "coop deps PM-101", purpose: "Show task dependencies and reverse dependencies with status and title." },
|
|
3344
3542
|
{ usage: "coop prompt PM-101 --format markdown", purpose: "Generate a manual handoff prompt from a task and current working context." },
|
|
3543
|
+
{ usage: "coop update PM-101 --track MVP --delivery MVP", purpose: "Update a task's home track or primary delivery without editing `.coop` files directly." },
|
|
3544
|
+
{ usage: "coop update PM-101 --add-delivery-track MVP --priority-in MVP:p0", purpose: "Add a contributing track lens and scoped priority override." },
|
|
3345
3545
|
{ usage: "coop update PM-101 --priority p1 --add-fix-version v2", purpose: "Update task metadata without editing `.coop` files directly." },
|
|
3346
3546
|
{ usage: 'coop comment PM-101 --message "Needs API review"', purpose: "Append a comment to a task." },
|
|
3347
3547
|
{ 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 +3795,9 @@ function renderAiHelp(format) {
|
|
|
3595
3795
|
const heading = format === "markdown" ? "# COOP AI Help" : "COOP AI Help";
|
|
3596
3796
|
lines.push(heading, "");
|
|
3597
3797
|
lines.push(catalog.purpose, "");
|
|
3798
|
+
lines.push("Fast agent handoff:");
|
|
3799
|
+
lines.push("- `coop help-ai --initial-prompt --strict --repo C:/path/to/repo --delivery MVP --command coop.cmd`");
|
|
3800
|
+
lines.push("");
|
|
3598
3801
|
const bullet = (value) => format === "markdown" ? `- ${value}` : `- ${value}`;
|
|
3599
3802
|
lines.push(format === "markdown" ? "## Selection Rules" : "Selection Rules");
|
|
3600
3803
|
for (const rule of catalog.selection_rules) {
|
|
@@ -3680,7 +3883,7 @@ function registerHelpAiCommand(program) {
|
|
|
3680
3883
|
} catch {
|
|
3681
3884
|
artifactsDir = "docs";
|
|
3682
3885
|
}
|
|
3683
|
-
const
|
|
3886
|
+
const output2 = options.initialPrompt ? renderAiInitialPrompt({
|
|
3684
3887
|
repoPath: options.repo,
|
|
3685
3888
|
delivery: options.delivery,
|
|
3686
3889
|
track: options.track,
|
|
@@ -3688,7 +3891,7 @@ function registerHelpAiCommand(program) {
|
|
|
3688
3891
|
rigour,
|
|
3689
3892
|
artifactsDir
|
|
3690
3893
|
}) : topic ? renderAiHelpTopic(format, topic) : renderAiHelp(format);
|
|
3691
|
-
console.log(
|
|
3894
|
+
console.log(output2.trimEnd());
|
|
3692
3895
|
});
|
|
3693
3896
|
}
|
|
3694
3897
|
function resolveHelpTopic(options) {
|
|
@@ -3711,11 +3914,11 @@ function resolveHelpTopic(options) {
|
|
|
3711
3914
|
if (options.naming) {
|
|
3712
3915
|
requestedTopics.push("naming");
|
|
3713
3916
|
}
|
|
3714
|
-
const
|
|
3715
|
-
if (
|
|
3716
|
-
throw new Error(`Specify only one focused help-ai topic at a time. Received: ${
|
|
3917
|
+
const unique4 = [...new Set(requestedTopics)];
|
|
3918
|
+
if (unique4.length > 1) {
|
|
3919
|
+
throw new Error(`Specify only one focused help-ai topic at a time. Received: ${unique4.join(", ")}.`);
|
|
3717
3920
|
}
|
|
3718
|
-
const topic =
|
|
3921
|
+
const topic = unique4[0];
|
|
3719
3922
|
if (topic !== void 0 && topic !== "state-transitions" && topic !== "artifacts" && topic !== "post-execution" && topic !== "selection" && topic !== "naming") {
|
|
3720
3923
|
throw new Error(`Unsupported help-ai topic '${topic}'. Expected state-transitions|artifacts|post-execution|selection|naming.`);
|
|
3721
3924
|
}
|
|
@@ -3775,15 +3978,13 @@ function registerIndexCommand(program) {
|
|
|
3775
3978
|
import fs10 from "fs";
|
|
3776
3979
|
import path13 from "path";
|
|
3777
3980
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3778
|
-
import { createInterface } from "readline/promises";
|
|
3779
|
-
import { stdin as input, stdout as output } from "process";
|
|
3780
3981
|
import { CURRENT_SCHEMA_VERSION, write_schema_version } from "@kitsy/coop-core";
|
|
3781
3982
|
|
|
3782
3983
|
// src/hooks/pre-commit.ts
|
|
3783
3984
|
import fs8 from "fs";
|
|
3784
3985
|
import path11 from "path";
|
|
3785
3986
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3786
|
-
import { detect_cycle, parseTaskContent, parseTaskFile as parseTaskFile8, validateStructural as
|
|
3987
|
+
import { detect_cycle, parseTaskContent, parseTaskFile as parseTaskFile8, validateStructural as validateStructural5 } from "@kitsy/coop-core";
|
|
3787
3988
|
var HOOK_BLOCK_START = "# COOP_PRE_COMMIT_START";
|
|
3788
3989
|
var HOOK_BLOCK_END = "# COOP_PRE_COMMIT_END";
|
|
3789
3990
|
function runGit(repoRoot, args, allowFailure = false) {
|
|
@@ -3867,7 +4068,7 @@ function parseStagedTasks(repoRoot, relativePaths) {
|
|
|
3867
4068
|
errors.push(`[COOP] ${message}`);
|
|
3868
4069
|
continue;
|
|
3869
4070
|
}
|
|
3870
|
-
const issues =
|
|
4071
|
+
const issues = validateStructural5(task, { filePath: absolutePath });
|
|
3871
4072
|
for (const issue of issues) {
|
|
3872
4073
|
errors.push(`[COOP] ${relativePath}: ${issue.message}`);
|
|
3873
4074
|
}
|
|
@@ -4101,6 +4302,28 @@ function installPostMergeHook(repoRoot) {
|
|
|
4101
4302
|
}
|
|
4102
4303
|
|
|
4103
4304
|
// src/commands/init.ts
|
|
4305
|
+
var NAMING_TEMPLATE_PRESETS = [
|
|
4306
|
+
{
|
|
4307
|
+
label: "<TYPE>-<TITLE16>-<SEQ>",
|
|
4308
|
+
value: "<TYPE>-<TITLE16>-<SEQ>",
|
|
4309
|
+
hint: "Balanced semantic default"
|
|
4310
|
+
},
|
|
4311
|
+
{
|
|
4312
|
+
label: "<TYPE>-<TRACK>-<TITLE16>-<SEQ>",
|
|
4313
|
+
value: "<TYPE>-<TRACK>-<TITLE16>-<SEQ>",
|
|
4314
|
+
hint: "Include track in ids"
|
|
4315
|
+
},
|
|
4316
|
+
{
|
|
4317
|
+
label: "<TYPE>-<USER>-<YYMMDD>-<RAND>",
|
|
4318
|
+
value: "<TYPE>-<USER>-<YYMMDD>-<RAND>",
|
|
4319
|
+
hint: "Actor/date/random oriented"
|
|
4320
|
+
},
|
|
4321
|
+
{
|
|
4322
|
+
label: "Custom template",
|
|
4323
|
+
value: "__custom__",
|
|
4324
|
+
hint: "Enter a custom naming template manually"
|
|
4325
|
+
}
|
|
4326
|
+
];
|
|
4104
4327
|
function normalizeProjectId(value) {
|
|
4105
4328
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
4106
4329
|
}
|
|
@@ -4321,36 +4544,27 @@ function installMergeDrivers(root) {
|
|
|
4321
4544
|
return "Updated .gitattributes but could not register merge drivers in git config.";
|
|
4322
4545
|
}
|
|
4323
4546
|
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
4547
|
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();
|
|
4548
|
+
const initialName = options.name?.trim() || defaultName;
|
|
4549
|
+
const projectName = options.name?.trim() || await ask("Project name", initialName);
|
|
4550
|
+
const defaultId = options.id?.trim() || normalizeProjectId(projectName) || repoIdentityId(root);
|
|
4551
|
+
const projectIdRaw = options.id?.trim() || await ask("Project id", defaultId);
|
|
4552
|
+
const projectId = normalizeProjectId(projectIdRaw);
|
|
4553
|
+
if (!projectId) {
|
|
4554
|
+
throw new Error("Invalid project id. Use letters, numbers, and hyphens.");
|
|
4555
|
+
}
|
|
4556
|
+
const aliasesInput = options.aliases !== void 0 ? options.aliases : await ask("Aliases (csv, optional)", "");
|
|
4557
|
+
let namingTemplate = options.naming?.trim();
|
|
4558
|
+
if (!namingTemplate) {
|
|
4559
|
+
const selected = await select("ID naming template", [...NAMING_TEMPLATE_PRESETS], 0);
|
|
4560
|
+
namingTemplate = selected === "__custom__" ? await ask("Custom ID naming template", DEFAULT_ID_NAMING_TEMPLATE) : selected;
|
|
4353
4561
|
}
|
|
4562
|
+
return {
|
|
4563
|
+
projectName,
|
|
4564
|
+
projectId,
|
|
4565
|
+
projectAliases: parseAliases2(aliasesInput),
|
|
4566
|
+
namingTemplate
|
|
4567
|
+
};
|
|
4354
4568
|
}
|
|
4355
4569
|
async function resolveInitIdentity(root, options) {
|
|
4356
4570
|
const defaultName = repoDisplayName(root);
|
|
@@ -4358,7 +4572,7 @@ async function resolveInitIdentity(root, options) {
|
|
|
4358
4572
|
const fallbackId = normalizeProjectId(options.id?.trim() || fallbackName) || repoIdentityId(root);
|
|
4359
4573
|
const fallbackAliases = parseAliases2(options.aliases);
|
|
4360
4574
|
const fallbackNaming = options.naming?.trim() || DEFAULT_ID_NAMING_TEMPLATE;
|
|
4361
|
-
const interactive = Boolean(options.interactive || !options.yes &&
|
|
4575
|
+
const interactive = Boolean(options.interactive || !options.yes && process.stdin.isTTY && process.stdout.isTTY);
|
|
4362
4576
|
if (interactive) {
|
|
4363
4577
|
return promptInitIdentity(root, options);
|
|
4364
4578
|
}
|
|
@@ -4435,6 +4649,8 @@ function registerInitCommand(program) {
|
|
|
4435
4649
|
console.log(' 1. coop create idea "Describe the idea"');
|
|
4436
4650
|
console.log(' 2. coop create task "Describe the task"');
|
|
4437
4651
|
console.log(" 3. coop graph validate");
|
|
4652
|
+
console.log(` 4. coop help-ai --initial-prompt --strict --repo ${root.replace(/\\/g, "/")} --command coop.cmd`);
|
|
4653
|
+
console.log(" 5. after you create a delivery, add --delivery <id> to that help-ai prompt for agent handoff");
|
|
4438
4654
|
});
|
|
4439
4655
|
}
|
|
4440
4656
|
|
|
@@ -4501,8 +4717,54 @@ function registerLifecycleCommands(program) {
|
|
|
4501
4717
|
|
|
4502
4718
|
// src/commands/list.ts
|
|
4503
4719
|
import path15 from "path";
|
|
4504
|
-
import {
|
|
4720
|
+
import {
|
|
4721
|
+
effective_priority as effective_priority3,
|
|
4722
|
+
load_graph as load_graph6,
|
|
4723
|
+
parseDeliveryFile as parseDeliveryFile2,
|
|
4724
|
+
parseIdeaFile as parseIdeaFile4,
|
|
4725
|
+
parseTaskFile as parseTaskFile10,
|
|
4726
|
+
parseYamlFile as parseYamlFile3,
|
|
4727
|
+
schedule_next as schedule_next3
|
|
4728
|
+
} from "@kitsy/coop-core";
|
|
4505
4729
|
import chalk2 from "chalk";
|
|
4730
|
+
var TASK_COLUMN_WIDTHS = {
|
|
4731
|
+
id: 24,
|
|
4732
|
+
title: 30,
|
|
4733
|
+
status: 12,
|
|
4734
|
+
priority: 4,
|
|
4735
|
+
assignee: 16,
|
|
4736
|
+
track: 16,
|
|
4737
|
+
delivery: 16,
|
|
4738
|
+
score: 6,
|
|
4739
|
+
file: 30
|
|
4740
|
+
};
|
|
4741
|
+
var IDEA_COLUMN_WIDTHS = {
|
|
4742
|
+
id: 24,
|
|
4743
|
+
title: 30,
|
|
4744
|
+
status: 12,
|
|
4745
|
+
file: 30
|
|
4746
|
+
};
|
|
4747
|
+
function truncateCell(value, maxWidth) {
|
|
4748
|
+
if (maxWidth <= 0 || value.length <= maxWidth) {
|
|
4749
|
+
return value;
|
|
4750
|
+
}
|
|
4751
|
+
if (maxWidth <= 3) {
|
|
4752
|
+
return value.slice(0, maxWidth);
|
|
4753
|
+
}
|
|
4754
|
+
return `${value.slice(0, maxWidth - 3)}...`;
|
|
4755
|
+
}
|
|
4756
|
+
function truncateMiddleCell(value, maxWidth) {
|
|
4757
|
+
if (maxWidth <= 0 || value.length <= maxWidth) {
|
|
4758
|
+
return value;
|
|
4759
|
+
}
|
|
4760
|
+
if (maxWidth <= 3) {
|
|
4761
|
+
return value.slice(0, maxWidth);
|
|
4762
|
+
}
|
|
4763
|
+
const available = maxWidth - 3;
|
|
4764
|
+
const head = Math.ceil(available / 2);
|
|
4765
|
+
const tail = Math.floor(available / 2);
|
|
4766
|
+
return `${value.slice(0, head)}...${value.slice(value.length - tail)}`;
|
|
4767
|
+
}
|
|
4506
4768
|
function statusColor(status) {
|
|
4507
4769
|
switch (status) {
|
|
4508
4770
|
case "done":
|
|
@@ -4519,8 +4781,210 @@ function statusColor(status) {
|
|
|
4519
4781
|
return status;
|
|
4520
4782
|
}
|
|
4521
4783
|
}
|
|
4522
|
-
function
|
|
4523
|
-
return
|
|
4784
|
+
function parseColumns(input2) {
|
|
4785
|
+
return (input2 ?? "").split(",").map((value) => value.trim().toLowerCase()).filter(Boolean);
|
|
4786
|
+
}
|
|
4787
|
+
function normalizeTaskColumns(value, ready) {
|
|
4788
|
+
if (!value?.trim()) {
|
|
4789
|
+
return ready ? ["id", "title", "priority", "status", "assignee", "score"] : ["id", "title", "priority", "status", "assignee"];
|
|
4790
|
+
}
|
|
4791
|
+
const raw = parseColumns(value);
|
|
4792
|
+
if (raw.length === 1 && raw[0] === "all") {
|
|
4793
|
+
return ["id", "title", "priority", "status", "assignee", "track", "delivery", "score", "file"];
|
|
4794
|
+
}
|
|
4795
|
+
const normalized = raw.map((column) => {
|
|
4796
|
+
if (column === "p") return "priority";
|
|
4797
|
+
return column;
|
|
4798
|
+
});
|
|
4799
|
+
const valid = /* @__PURE__ */ new Set(["id", "title", "status", "priority", "assignee", "track", "delivery", "score", "file"]);
|
|
4800
|
+
for (const column of normalized) {
|
|
4801
|
+
if (!valid.has(column)) {
|
|
4802
|
+
throw new Error(`Invalid task column '${column}'. Expected id|title|status|priority|assignee|track|delivery|score|file|all.`);
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
return normalized;
|
|
4806
|
+
}
|
|
4807
|
+
function normalizeIdeaColumns(value) {
|
|
4808
|
+
if (!value?.trim()) {
|
|
4809
|
+
return ["id", "title", "status"];
|
|
4810
|
+
}
|
|
4811
|
+
const raw = parseColumns(value);
|
|
4812
|
+
if (raw.length === 1 && raw[0] === "all") {
|
|
4813
|
+
return ["id", "title", "status", "file"];
|
|
4814
|
+
}
|
|
4815
|
+
const valid = /* @__PURE__ */ new Set(["id", "title", "status", "file"]);
|
|
4816
|
+
for (const column of raw) {
|
|
4817
|
+
if (!valid.has(column)) {
|
|
4818
|
+
throw new Error(`Invalid idea column '${column}'. Expected id|title|status|file|all.`);
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
return raw;
|
|
4822
|
+
}
|
|
4823
|
+
function normalizeTaskSort(value, fallback) {
|
|
4824
|
+
switch ((value ?? "").trim().toLowerCase()) {
|
|
4825
|
+
case "":
|
|
4826
|
+
return fallback;
|
|
4827
|
+
case "id":
|
|
4828
|
+
case "priority":
|
|
4829
|
+
case "status":
|
|
4830
|
+
case "title":
|
|
4831
|
+
case "updated":
|
|
4832
|
+
case "created":
|
|
4833
|
+
case "score":
|
|
4834
|
+
return value.trim().toLowerCase();
|
|
4835
|
+
default:
|
|
4836
|
+
throw new Error(`Invalid sort '${value}'. Expected id|priority|status|title|updated|created|score.`);
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
function normalizeIdeaSort(value, fallback) {
|
|
4840
|
+
switch ((value ?? "").trim().toLowerCase()) {
|
|
4841
|
+
case "":
|
|
4842
|
+
return fallback;
|
|
4843
|
+
case "id":
|
|
4844
|
+
case "status":
|
|
4845
|
+
case "title":
|
|
4846
|
+
case "updated":
|
|
4847
|
+
case "created":
|
|
4848
|
+
return value.trim().toLowerCase();
|
|
4849
|
+
default:
|
|
4850
|
+
throw new Error(`Invalid sort '${value}'. Expected id|status|title|updated|created.`);
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4853
|
+
function priorityRank(value) {
|
|
4854
|
+
switch (value) {
|
|
4855
|
+
case "p0":
|
|
4856
|
+
return 0;
|
|
4857
|
+
case "p1":
|
|
4858
|
+
return 1;
|
|
4859
|
+
case "p2":
|
|
4860
|
+
return 2;
|
|
4861
|
+
case "p3":
|
|
4862
|
+
return 3;
|
|
4863
|
+
default:
|
|
4864
|
+
return 4;
|
|
4865
|
+
}
|
|
4866
|
+
}
|
|
4867
|
+
function taskStatusRank(value) {
|
|
4868
|
+
switch (value) {
|
|
4869
|
+
case "in_progress":
|
|
4870
|
+
return 0;
|
|
4871
|
+
case "in_review":
|
|
4872
|
+
return 1;
|
|
4873
|
+
case "todo":
|
|
4874
|
+
return 2;
|
|
4875
|
+
case "blocked":
|
|
4876
|
+
return 3;
|
|
4877
|
+
case "done":
|
|
4878
|
+
return 4;
|
|
4879
|
+
case "canceled":
|
|
4880
|
+
return 5;
|
|
4881
|
+
default:
|
|
4882
|
+
return 6;
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
function ideaStatusRank(value) {
|
|
4886
|
+
switch (value) {
|
|
4887
|
+
case "active":
|
|
4888
|
+
return 0;
|
|
4889
|
+
case "captured":
|
|
4890
|
+
return 1;
|
|
4891
|
+
case "validated":
|
|
4892
|
+
return 2;
|
|
4893
|
+
case "rejected":
|
|
4894
|
+
return 3;
|
|
4895
|
+
case "promoted":
|
|
4896
|
+
return 4;
|
|
4897
|
+
default:
|
|
4898
|
+
return 5;
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
function compareDatesDesc(a, b) {
|
|
4902
|
+
return (b ?? "").localeCompare(a ?? "");
|
|
4903
|
+
}
|
|
4904
|
+
function compareTaskScoreLike(a, b, readyOrder, track) {
|
|
4905
|
+
const aReady = readyOrder.get(a.id);
|
|
4906
|
+
const bReady = readyOrder.get(b.id);
|
|
4907
|
+
if (aReady !== void 0 || bReady !== void 0) {
|
|
4908
|
+
if (aReady === void 0) return 1;
|
|
4909
|
+
if (bReady === void 0) return -1;
|
|
4910
|
+
return aReady - bReady;
|
|
4911
|
+
}
|
|
4912
|
+
const status = taskStatusRank(a.status) - taskStatusRank(b.status);
|
|
4913
|
+
if (status !== 0) return status;
|
|
4914
|
+
const priority = priorityRank(effective_priority3(b.task, track)) - priorityRank(effective_priority3(a.task, track));
|
|
4915
|
+
if (priority !== 0) return priority;
|
|
4916
|
+
const updated = compareDatesDesc(a.task.updated, b.task.updated);
|
|
4917
|
+
if (updated !== 0) return updated;
|
|
4918
|
+
return a.id.localeCompare(b.id);
|
|
4919
|
+
}
|
|
4920
|
+
function sortTaskRows(rows, sortMode, readyOrder, track) {
|
|
4921
|
+
return [...rows].sort((a, b) => {
|
|
4922
|
+
switch (sortMode) {
|
|
4923
|
+
case "score":
|
|
4924
|
+
return compareTaskScoreLike(a, b, readyOrder, track);
|
|
4925
|
+
case "priority": {
|
|
4926
|
+
const priority = priorityRank(a.priority) - priorityRank(b.priority);
|
|
4927
|
+
if (priority !== 0) return priority;
|
|
4928
|
+
return a.id.localeCompare(b.id);
|
|
4929
|
+
}
|
|
4930
|
+
case "status": {
|
|
4931
|
+
const status = taskStatusRank(a.status) - taskStatusRank(b.status);
|
|
4932
|
+
if (status !== 0) return status;
|
|
4933
|
+
return a.id.localeCompare(b.id);
|
|
4934
|
+
}
|
|
4935
|
+
case "title":
|
|
4936
|
+
return a.title.localeCompare(b.title);
|
|
4937
|
+
case "updated": {
|
|
4938
|
+
const updated = compareDatesDesc(a.task.updated, b.task.updated);
|
|
4939
|
+
if (updated !== 0) return updated;
|
|
4940
|
+
return a.id.localeCompare(b.id);
|
|
4941
|
+
}
|
|
4942
|
+
case "created": {
|
|
4943
|
+
const created = compareDatesDesc(a.task.created, b.task.created);
|
|
4944
|
+
if (created !== 0) return created;
|
|
4945
|
+
return a.id.localeCompare(b.id);
|
|
4946
|
+
}
|
|
4947
|
+
case "id":
|
|
4948
|
+
default:
|
|
4949
|
+
return a.id.localeCompare(b.id);
|
|
4950
|
+
}
|
|
4951
|
+
});
|
|
4952
|
+
}
|
|
4953
|
+
function taskColumnHeader(column) {
|
|
4954
|
+
switch (column) {
|
|
4955
|
+
case "priority":
|
|
4956
|
+
return "P";
|
|
4957
|
+
case "assignee":
|
|
4958
|
+
return "Assignee";
|
|
4959
|
+
case "track":
|
|
4960
|
+
return "Track";
|
|
4961
|
+
case "delivery":
|
|
4962
|
+
return "Delivery";
|
|
4963
|
+
case "score":
|
|
4964
|
+
return "Score";
|
|
4965
|
+
case "file":
|
|
4966
|
+
return "File";
|
|
4967
|
+
case "status":
|
|
4968
|
+
return "Status";
|
|
4969
|
+
case "title":
|
|
4970
|
+
return "Title";
|
|
4971
|
+
case "id":
|
|
4972
|
+
default:
|
|
4973
|
+
return "ID";
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
function ideaColumnHeader(column) {
|
|
4977
|
+
switch (column) {
|
|
4978
|
+
case "file":
|
|
4979
|
+
return "File";
|
|
4980
|
+
case "status":
|
|
4981
|
+
return "Status";
|
|
4982
|
+
case "title":
|
|
4983
|
+
return "Title";
|
|
4984
|
+
case "id":
|
|
4985
|
+
default:
|
|
4986
|
+
return "ID";
|
|
4987
|
+
}
|
|
4524
4988
|
}
|
|
4525
4989
|
function loadTasks2(root) {
|
|
4526
4990
|
return listTaskFiles(root).map((filePath) => ({
|
|
@@ -4539,24 +5003,38 @@ function listTasks(options) {
|
|
|
4539
5003
|
ensureCoopInitialized(root);
|
|
4540
5004
|
const context = readWorkingContext(root, resolveCoopHome());
|
|
4541
5005
|
const graph = load_graph6(coopDir(root));
|
|
4542
|
-
const
|
|
4543
|
-
const
|
|
5006
|
+
const rawResolvedTrack = resolveContextValueWithSource(options.track, context.track);
|
|
5007
|
+
const rawResolvedDelivery = resolveContextValueWithSource(options.delivery, context.delivery);
|
|
5008
|
+
const resolvedTrack = {
|
|
5009
|
+
...rawResolvedTrack,
|
|
5010
|
+
value: rawResolvedTrack.value ? resolveExistingTrackId(root, rawResolvedTrack.value) ?? rawResolvedTrack.value : void 0
|
|
5011
|
+
};
|
|
5012
|
+
const resolvedDelivery = {
|
|
5013
|
+
...rawResolvedDelivery,
|
|
5014
|
+
value: rawResolvedDelivery.value ? resolveExistingDeliveryId(root, rawResolvedDelivery.value) ?? rawResolvedDelivery.value : void 0
|
|
5015
|
+
};
|
|
5016
|
+
const resolvedVersion = resolveContextValueWithSource(options.version, context.version);
|
|
4544
5017
|
const assignee = options.mine ? defaultCoopAuthor(root) : options.assignee?.trim();
|
|
4545
5018
|
const deliveryScope = resolvedDelivery.value ? new Set(graph.deliveries.get(resolvedDelivery.value)?.scope.include ?? []) : null;
|
|
5019
|
+
const defaultSort = options.ready || resolvedTrack.value || resolvedDelivery.value ? "score" : "id";
|
|
5020
|
+
const sortMode = normalizeTaskSort(options.sort, defaultSort);
|
|
5021
|
+
const columns = normalizeTaskColumns(options.columns, Boolean(options.ready));
|
|
4546
5022
|
if (isVerboseRequested()) {
|
|
4547
5023
|
for (const line of formatResolvedContextMessage({
|
|
4548
5024
|
track: resolvedTrack,
|
|
4549
|
-
delivery: resolvedDelivery
|
|
5025
|
+
delivery: resolvedDelivery,
|
|
5026
|
+
version: resolvedVersion
|
|
4550
5027
|
})) {
|
|
4551
5028
|
console.log(line);
|
|
4552
5029
|
}
|
|
4553
5030
|
}
|
|
4554
|
-
const
|
|
5031
|
+
const scoredEntries = sortMode === "score" || options.ready ? schedule_next3(graph, {
|
|
4555
5032
|
track: resolvedTrack.value,
|
|
4556
5033
|
delivery: resolvedDelivery.value
|
|
4557
|
-
}) :
|
|
4558
|
-
const readyIds =
|
|
4559
|
-
const readyOrder =
|
|
5034
|
+
}) : [];
|
|
5035
|
+
const readyIds = options.ready ? new Set(scoredEntries.map((entry) => entry.task.id)) : null;
|
|
5036
|
+
const readyOrder = new Map(scoredEntries.map((entry, index) => [entry.task.id, index]));
|
|
5037
|
+
const scoreMap = new Map(scoredEntries.map((entry) => [entry.task.id, entry.score]));
|
|
4560
5038
|
const rows = loadTasks2(root).filter(({ task }) => {
|
|
4561
5039
|
if (readyIds && !readyIds.has(task.id)) return false;
|
|
4562
5040
|
if (options.status && task.status !== options.status) return false;
|
|
@@ -4566,11 +5044,12 @@ function listTasks(options) {
|
|
|
4566
5044
|
if (resolvedDelivery.value && task.delivery !== resolvedDelivery.value && !deliveryScope?.has(task.id)) return false;
|
|
4567
5045
|
if (options.priority && taskEffectivePriority(task, resolvedTrack.value) !== options.priority) return false;
|
|
4568
5046
|
if (assignee && (task.assignee ?? "") !== assignee) return false;
|
|
4569
|
-
if (
|
|
5047
|
+
if (resolvedVersion.value && !(task.fix_versions ?? []).includes(resolvedVersion.value) && !(task.released_in ?? []).includes(resolvedVersion.value)) {
|
|
4570
5048
|
return false;
|
|
4571
5049
|
}
|
|
4572
5050
|
return true;
|
|
4573
5051
|
}).map(({ task, filePath }) => ({
|
|
5052
|
+
task,
|
|
4574
5053
|
id: task.id,
|
|
4575
5054
|
title: task.title,
|
|
4576
5055
|
status: task.status,
|
|
@@ -4580,24 +5059,42 @@ function listTasks(options) {
|
|
|
4580
5059
|
delivery: task.delivery ?? "-",
|
|
4581
5060
|
filePath
|
|
4582
5061
|
}));
|
|
4583
|
-
const sorted =
|
|
5062
|
+
const sorted = sortTaskRows(rows, sortMode, readyOrder, resolvedTrack.value);
|
|
4584
5063
|
if (sorted.length === 0) {
|
|
4585
5064
|
console.log("No tasks found.");
|
|
4586
5065
|
return;
|
|
4587
5066
|
}
|
|
4588
5067
|
console.log(
|
|
4589
5068
|
formatTable(
|
|
4590
|
-
|
|
4591
|
-
sorted.map(
|
|
4592
|
-
entry.
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
5069
|
+
columns.map(taskColumnHeader),
|
|
5070
|
+
sorted.map(
|
|
5071
|
+
(entry) => columns.map((column) => {
|
|
5072
|
+
const rawValue = (() => {
|
|
5073
|
+
switch (column) {
|
|
5074
|
+
case "title":
|
|
5075
|
+
return entry.title;
|
|
5076
|
+
case "status":
|
|
5077
|
+
return statusColor(entry.status);
|
|
5078
|
+
case "priority":
|
|
5079
|
+
return entry.priority;
|
|
5080
|
+
case "assignee":
|
|
5081
|
+
return entry.assignee;
|
|
5082
|
+
case "track":
|
|
5083
|
+
return entry.track;
|
|
5084
|
+
case "delivery":
|
|
5085
|
+
return entry.delivery;
|
|
5086
|
+
case "score":
|
|
5087
|
+
return scoreMap.has(entry.id) ? scoreMap.get(entry.id).toFixed(1) : "-";
|
|
5088
|
+
case "file":
|
|
5089
|
+
return path15.relative(root, entry.filePath);
|
|
5090
|
+
case "id":
|
|
5091
|
+
default:
|
|
5092
|
+
return entry.id;
|
|
5093
|
+
}
|
|
5094
|
+
})();
|
|
5095
|
+
return column === "file" ? truncateMiddleCell(rawValue, TASK_COLUMN_WIDTHS[column]) : truncateCell(rawValue, TASK_COLUMN_WIDTHS[column]);
|
|
5096
|
+
})
|
|
5097
|
+
)
|
|
4601
5098
|
)
|
|
4602
5099
|
);
|
|
4603
5100
|
console.log(`
|
|
@@ -4606,6 +5103,8 @@ Total tasks: ${sorted.length}`);
|
|
|
4606
5103
|
function listIdeas(options) {
|
|
4607
5104
|
const root = resolveRepoRoot();
|
|
4608
5105
|
ensureCoopInitialized(root);
|
|
5106
|
+
const sortMode = normalizeIdeaSort(options.sort, "id");
|
|
5107
|
+
const columns = normalizeIdeaColumns(options.columns);
|
|
4609
5108
|
const rows = loadIdeas(root).filter(({ idea }) => {
|
|
4610
5109
|
if (options.status && idea.status !== options.status) return false;
|
|
4611
5110
|
return true;
|
|
@@ -4617,22 +5116,36 @@ function listIdeas(options) {
|
|
|
4617
5116
|
track: "-",
|
|
4618
5117
|
filePath
|
|
4619
5118
|
}));
|
|
4620
|
-
const sorted =
|
|
5119
|
+
const sorted = [...rows].sort((a, b) => {
|
|
5120
|
+
switch (sortMode) {
|
|
5121
|
+
case "status": {
|
|
5122
|
+
const status = ideaStatusRank(a.status) - ideaStatusRank(b.status);
|
|
5123
|
+
if (status !== 0) return status;
|
|
5124
|
+
return a.id.localeCompare(b.id);
|
|
5125
|
+
}
|
|
5126
|
+
case "title":
|
|
5127
|
+
return a.title.localeCompare(b.title);
|
|
5128
|
+
case "updated":
|
|
5129
|
+
case "created":
|
|
5130
|
+
return a.id.localeCompare(b.id);
|
|
5131
|
+
case "id":
|
|
5132
|
+
default:
|
|
5133
|
+
return a.id.localeCompare(b.id);
|
|
5134
|
+
}
|
|
5135
|
+
});
|
|
4621
5136
|
if (sorted.length === 0) {
|
|
4622
5137
|
console.log("No ideas found.");
|
|
4623
5138
|
return;
|
|
4624
5139
|
}
|
|
4625
5140
|
console.log(
|
|
4626
5141
|
formatTable(
|
|
4627
|
-
|
|
4628
|
-
sorted.map(
|
|
4629
|
-
entry.
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
path15.relative(root, entry.filePath)
|
|
4635
|
-
])
|
|
5142
|
+
columns.map(ideaColumnHeader),
|
|
5143
|
+
sorted.map(
|
|
5144
|
+
(entry) => columns.map((column) => {
|
|
5145
|
+
const rawValue = column === "title" ? entry.title : column === "status" ? statusColor(entry.status) : column === "file" ? path15.relative(root, entry.filePath) : entry.id;
|
|
5146
|
+
return column === "file" ? truncateMiddleCell(rawValue, IDEA_COLUMN_WIDTHS[column]) : truncateCell(rawValue, IDEA_COLUMN_WIDTHS[column]);
|
|
5147
|
+
})
|
|
5148
|
+
)
|
|
4636
5149
|
)
|
|
4637
5150
|
);
|
|
4638
5151
|
console.log(`
|
|
@@ -4641,11 +5154,11 @@ Total ideas: ${sorted.length}`);
|
|
|
4641
5154
|
function listDeliveries() {
|
|
4642
5155
|
const root = resolveRepoRoot();
|
|
4643
5156
|
const rows = listDeliveryFiles(root).map((filePath) => ({ delivery: parseDeliveryFile2(filePath).delivery, filePath })).map(({ delivery, filePath }) => [
|
|
4644
|
-
delivery.id,
|
|
4645
|
-
delivery.name,
|
|
4646
|
-
statusColor(delivery.status),
|
|
4647
|
-
delivery.target_date ?? "-",
|
|
4648
|
-
path15.relative(root, filePath)
|
|
5157
|
+
truncateCell(delivery.id, 24),
|
|
5158
|
+
truncateCell(delivery.name, 30),
|
|
5159
|
+
truncateCell(statusColor(delivery.status), 12),
|
|
5160
|
+
truncateCell(delivery.target_date ?? "-", 16),
|
|
5161
|
+
truncateMiddleCell(path15.relative(root, filePath), 30)
|
|
4649
5162
|
]);
|
|
4650
5163
|
if (rows.length === 0) {
|
|
4651
5164
|
console.log("No deliveries found.");
|
|
@@ -4653,18 +5166,36 @@ function listDeliveries() {
|
|
|
4653
5166
|
}
|
|
4654
5167
|
console.log(formatTable(["ID", "Name", "Status", "Target", "File"], rows));
|
|
4655
5168
|
}
|
|
5169
|
+
function listTracks() {
|
|
5170
|
+
const root = resolveRepoRoot();
|
|
5171
|
+
const rows = listTrackFiles(root).map((filePath) => ({ track: parseYamlFile3(filePath), filePath })).sort((a, b) => a.track.id.localeCompare(b.track.id)).map(({ track, filePath }) => [
|
|
5172
|
+
truncateCell(track.id, 24),
|
|
5173
|
+
truncateCell(track.name, 30),
|
|
5174
|
+
truncateCell((track.capacity_profiles ?? []).join(", ") || "-", 24),
|
|
5175
|
+
truncateCell(String(track.constraints?.max_concurrent_tasks ?? "-"), 8),
|
|
5176
|
+
truncateMiddleCell(path15.relative(root, filePath), 30)
|
|
5177
|
+
]);
|
|
5178
|
+
if (rows.length === 0) {
|
|
5179
|
+
console.log("No tracks found.");
|
|
5180
|
+
return;
|
|
5181
|
+
}
|
|
5182
|
+
console.log(formatTable(["ID", "Name", "Profiles", "Max WIP", "File"], rows));
|
|
5183
|
+
}
|
|
4656
5184
|
function registerListCommand(program) {
|
|
4657
5185
|
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) => {
|
|
5186
|
+
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
5187
|
listTasks(options);
|
|
4660
5188
|
});
|
|
4661
|
-
list.command("ideas").description("List ideas").option("--status <status>", "Filter by status").action((options) => {
|
|
5189
|
+
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
5190
|
listIdeas(options);
|
|
4663
5191
|
});
|
|
4664
5192
|
list.command("alias").description("List aliases").argument("[pattern]", "Wildcard pattern, e.g. PAY*").action((pattern) => {
|
|
4665
5193
|
listAliasRows(pattern);
|
|
4666
5194
|
});
|
|
4667
|
-
list.command("
|
|
5195
|
+
list.command("tracks").alias("track").description("List tracks").action(() => {
|
|
5196
|
+
listTracks();
|
|
5197
|
+
});
|
|
5198
|
+
list.command("deliveries").alias("delivery").description("List deliveries").action(() => {
|
|
4668
5199
|
listDeliveries();
|
|
4669
5200
|
});
|
|
4670
5201
|
}
|
|
@@ -4672,17 +5203,30 @@ function registerListCommand(program) {
|
|
|
4672
5203
|
// src/utils/logger.ts
|
|
4673
5204
|
import fs11 from "fs";
|
|
4674
5205
|
import path16 from "path";
|
|
4675
|
-
function
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
5206
|
+
function resolveWorkspaceRoot(start = process.cwd()) {
|
|
5207
|
+
let current = path16.resolve(start);
|
|
5208
|
+
while (true) {
|
|
5209
|
+
const gitDir = path16.join(current, ".git");
|
|
5210
|
+
const coopDir2 = coopWorkspaceDir(current);
|
|
5211
|
+
const workspaceConfig = path16.join(coopDir2, "config.yml");
|
|
5212
|
+
const projectsDir = path16.join(coopDir2, "projects");
|
|
5213
|
+
if (fs11.existsSync(gitDir)) {
|
|
5214
|
+
return current;
|
|
5215
|
+
}
|
|
5216
|
+
if (fs11.existsSync(workspaceConfig) || fs11.existsSync(projectsDir)) {
|
|
5217
|
+
return current;
|
|
5218
|
+
}
|
|
5219
|
+
const parent = path16.dirname(current);
|
|
5220
|
+
if (parent === current) {
|
|
5221
|
+
return null;
|
|
5222
|
+
}
|
|
5223
|
+
current = parent;
|
|
4680
5224
|
}
|
|
4681
5225
|
}
|
|
4682
5226
|
function resolveCliLogFile(start = process.cwd()) {
|
|
4683
|
-
const root =
|
|
4684
|
-
|
|
4685
|
-
|
|
5227
|
+
const root = resolveWorkspaceRoot(start);
|
|
5228
|
+
if (root) {
|
|
5229
|
+
const workspace = coopWorkspaceDir(root);
|
|
4686
5230
|
return path16.join(workspace, "logs", "cli.log");
|
|
4687
5231
|
}
|
|
4688
5232
|
return path16.join(resolveCoopHome(), "logs", "cli.log");
|
|
@@ -4786,7 +5330,7 @@ function registerLogTimeCommand(program) {
|
|
|
4786
5330
|
hours,
|
|
4787
5331
|
options.note?.trim() || void 0
|
|
4788
5332
|
);
|
|
4789
|
-
writeTaskEntry(filePath, parsed, task);
|
|
5333
|
+
writeTaskEntry(root, filePath, parsed, task);
|
|
4790
5334
|
console.log(`Logged ${hours}h ${options.kind} on ${task.id}`);
|
|
4791
5335
|
});
|
|
4792
5336
|
}
|
|
@@ -4794,9 +5338,9 @@ function registerLogTimeCommand(program) {
|
|
|
4794
5338
|
// src/commands/migrate.ts
|
|
4795
5339
|
import fs12 from "fs";
|
|
4796
5340
|
import path17 from "path";
|
|
4797
|
-
import { createInterface
|
|
4798
|
-
import { stdin as
|
|
4799
|
-
import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as
|
|
5341
|
+
import { createInterface } from "readline/promises";
|
|
5342
|
+
import { stdin as input, stdout as output } from "process";
|
|
5343
|
+
import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as parseYamlFile4, writeYamlFile as writeYamlFile4 } from "@kitsy/coop-core";
|
|
4800
5344
|
var COOP_IGNORE_TEMPLATE2 = `.index/
|
|
4801
5345
|
logs/
|
|
4802
5346
|
tmp/
|
|
@@ -4868,7 +5412,7 @@ function parseAliases3(value) {
|
|
|
4868
5412
|
);
|
|
4869
5413
|
}
|
|
4870
5414
|
async function promptProjectIdentity(defaults, options) {
|
|
4871
|
-
const rl =
|
|
5415
|
+
const rl = createInterface({ input, output });
|
|
4872
5416
|
const ask2 = async (question, fallback) => {
|
|
4873
5417
|
try {
|
|
4874
5418
|
const answer = await rl.question(`${question} [${fallback}]: `);
|
|
@@ -4901,7 +5445,7 @@ async function resolveMigrationIdentity(root, options) {
|
|
|
4901
5445
|
projectId: normalizeProjectId2(existing.projectId || repoIdentityId(root)) || repoIdentityId(root),
|
|
4902
5446
|
projectAliases: options.aliases !== void 0 ? parseAliases3(options.aliases) : existing.projectAliases
|
|
4903
5447
|
};
|
|
4904
|
-
const interactive = Boolean(options.interactive || !options.yes &&
|
|
5448
|
+
const interactive = Boolean(options.interactive || !options.yes && input.isTTY && output.isTTY);
|
|
4905
5449
|
if (interactive) {
|
|
4906
5450
|
return promptProjectIdentity(defaults, options);
|
|
4907
5451
|
}
|
|
@@ -4963,7 +5507,7 @@ async function migrateWorkspaceLayout(root, options) {
|
|
|
4963
5507
|
}
|
|
4964
5508
|
const movedConfigPath = path17.join(projectRoot, "config.yml");
|
|
4965
5509
|
if (fs12.existsSync(movedConfigPath)) {
|
|
4966
|
-
const movedConfig =
|
|
5510
|
+
const movedConfig = parseYamlFile4(movedConfigPath);
|
|
4967
5511
|
const nextProject = typeof movedConfig.project === "object" && movedConfig.project !== null ? { ...movedConfig.project } : {};
|
|
4968
5512
|
nextProject.name = identity.projectName;
|
|
4969
5513
|
nextProject.id = projectId;
|
|
@@ -5358,7 +5902,7 @@ function registerPromoteCommand(program) {
|
|
|
5358
5902
|
track: resolvedTrack.value,
|
|
5359
5903
|
version: resolvedVersion.value
|
|
5360
5904
|
});
|
|
5361
|
-
writeTaskEntry(filePath, parsed, task);
|
|
5905
|
+
writeTaskEntry(root, filePath, parsed, task);
|
|
5362
5906
|
console.log(`Promoted ${task.id}`);
|
|
5363
5907
|
});
|
|
5364
5908
|
}
|
|
@@ -5638,15 +6182,15 @@ function registerPromptCommand(program) {
|
|
|
5638
6182
|
const root = resolveRepoRoot();
|
|
5639
6183
|
const payload = buildPayload(root, id);
|
|
5640
6184
|
const format = options.format ?? "text";
|
|
5641
|
-
let
|
|
6185
|
+
let output2 = "";
|
|
5642
6186
|
if (format === "json") {
|
|
5643
|
-
|
|
6187
|
+
output2 = `${JSON.stringify(payload, null, 2)}
|
|
5644
6188
|
`;
|
|
5645
6189
|
} else {
|
|
5646
|
-
|
|
6190
|
+
output2 = renderMarkdown(payload);
|
|
5647
6191
|
}
|
|
5648
6192
|
if (options.save) {
|
|
5649
|
-
fs14.writeFileSync(options.save,
|
|
6193
|
+
fs14.writeFileSync(options.save, output2, "utf8");
|
|
5650
6194
|
}
|
|
5651
6195
|
if (isVerboseRequested()) {
|
|
5652
6196
|
for (const line of formatResolvedContextMessage({
|
|
@@ -5657,7 +6201,7 @@ function registerPromptCommand(program) {
|
|
|
5657
6201
|
console.log(line);
|
|
5658
6202
|
}
|
|
5659
6203
|
}
|
|
5660
|
-
console.log(
|
|
6204
|
+
console.log(output2.trimEnd());
|
|
5661
6205
|
});
|
|
5662
6206
|
}
|
|
5663
6207
|
|
|
@@ -6244,13 +6788,31 @@ function formatTimeSummary(task) {
|
|
|
6244
6788
|
const plannedLogged = (task.time?.logs ?? []).filter((entry) => entry.kind === "planned").reduce((sum, entry) => sum + entry.hours, 0);
|
|
6245
6789
|
return `planned_hours=${planned ?? "-"} | planned_logged=${plannedLogged || 0} | worked=${worked || 0}`;
|
|
6246
6790
|
}
|
|
6247
|
-
function showTask(taskId) {
|
|
6791
|
+
function showTask(taskId, options = {}) {
|
|
6248
6792
|
const root = resolveRepoRoot();
|
|
6249
6793
|
const context = readWorkingContext(root, resolveCoopHome());
|
|
6250
6794
|
const { filePath, parsed } = loadTaskEntry(root, taskId);
|
|
6251
6795
|
const task = parsed.task;
|
|
6252
6796
|
const body = parsed.body.trim();
|
|
6253
6797
|
const computed = loadComputedFromIndex(root, task.id);
|
|
6798
|
+
if (options.compact) {
|
|
6799
|
+
const compactLines = [
|
|
6800
|
+
`Task: ${task.id}`,
|
|
6801
|
+
`Title: ${task.title}`,
|
|
6802
|
+
`Status: ${task.status}`,
|
|
6803
|
+
`Priority: ${task.priority ?? "-"}`,
|
|
6804
|
+
`Effective Priority: ${taskEffectivePriority(task, context.track)}`,
|
|
6805
|
+
`Track: ${task.track ?? "-"}`,
|
|
6806
|
+
`Delivery: ${task.delivery ?? "-"}`,
|
|
6807
|
+
`Assignee: ${task.assignee ?? "-"}`,
|
|
6808
|
+
`Depends On: ${stringify(task.depends_on)}`,
|
|
6809
|
+
`Acceptance: ${task.acceptance && task.acceptance.length > 0 ? task.acceptance.join(" | ") : "-"}`,
|
|
6810
|
+
`Tests Required: ${task.tests_required && task.tests_required.length > 0 ? task.tests_required.join(", ") : "-"}`,
|
|
6811
|
+
`File: ${path22.relative(root, filePath)}`
|
|
6812
|
+
];
|
|
6813
|
+
console.log(compactLines.join("\n"));
|
|
6814
|
+
return;
|
|
6815
|
+
}
|
|
6254
6816
|
const lines = [
|
|
6255
6817
|
`Task: ${task.id}`,
|
|
6256
6818
|
`Title: ${task.title}`,
|
|
@@ -6315,13 +6877,26 @@ function showTask(taskId) {
|
|
|
6315
6877
|
}
|
|
6316
6878
|
console.log(lines.join("\n"));
|
|
6317
6879
|
}
|
|
6318
|
-
function showIdea(ideaId) {
|
|
6880
|
+
function showIdea(ideaId, options = {}) {
|
|
6319
6881
|
const root = resolveRepoRoot();
|
|
6320
6882
|
const target = resolveReference(root, ideaId, "idea");
|
|
6321
6883
|
const ideaFile = path22.join(root, ...target.file.split("/"));
|
|
6322
6884
|
const parsed = parseIdeaFile7(ideaFile);
|
|
6323
6885
|
const idea = parsed.idea;
|
|
6324
6886
|
const body = parsed.body.trim();
|
|
6887
|
+
if (options.compact) {
|
|
6888
|
+
console.log(
|
|
6889
|
+
[
|
|
6890
|
+
`Idea: ${idea.id}`,
|
|
6891
|
+
`Title: ${idea.title}`,
|
|
6892
|
+
`Status: ${idea.status}`,
|
|
6893
|
+
`Tags: ${stringify(idea.tags)}`,
|
|
6894
|
+
`Linked Tasks: ${stringify(idea.linked_tasks)}`,
|
|
6895
|
+
`File: ${path22.relative(root, ideaFile)}`
|
|
6896
|
+
].join("\n")
|
|
6897
|
+
);
|
|
6898
|
+
return;
|
|
6899
|
+
}
|
|
6325
6900
|
const lines = [
|
|
6326
6901
|
`Idea: ${idea.id}`,
|
|
6327
6902
|
`Title: ${idea.title}`,
|
|
@@ -6339,9 +6914,22 @@ function showIdea(ideaId) {
|
|
|
6339
6914
|
];
|
|
6340
6915
|
console.log(lines.join("\n"));
|
|
6341
6916
|
}
|
|
6342
|
-
function showDelivery(ref) {
|
|
6917
|
+
function showDelivery(ref, options = {}) {
|
|
6343
6918
|
const root = resolveRepoRoot();
|
|
6344
6919
|
const { filePath, delivery, body } = resolveDeliveryEntry(root, ref);
|
|
6920
|
+
if (options.compact) {
|
|
6921
|
+
console.log(
|
|
6922
|
+
[
|
|
6923
|
+
`Delivery: ${delivery.id}`,
|
|
6924
|
+
`Name: ${delivery.name}`,
|
|
6925
|
+
`Status: ${delivery.status}`,
|
|
6926
|
+
`Target Date: ${delivery.target_date ?? "-"}`,
|
|
6927
|
+
`Scope Include: ${delivery.scope.include.length > 0 ? delivery.scope.include.join(", ") : "-"}`,
|
|
6928
|
+
`File: ${path22.relative(root, filePath)}`
|
|
6929
|
+
].join("\n")
|
|
6930
|
+
);
|
|
6931
|
+
return;
|
|
6932
|
+
}
|
|
6345
6933
|
const lines = [
|
|
6346
6934
|
`Delivery: ${delivery.id}`,
|
|
6347
6935
|
`Name: ${delivery.name}`,
|
|
@@ -6359,35 +6947,35 @@ function showDelivery(ref) {
|
|
|
6359
6947
|
];
|
|
6360
6948
|
console.log(lines.join("\n"));
|
|
6361
6949
|
}
|
|
6362
|
-
function showByReference(ref) {
|
|
6950
|
+
function showByReference(ref, options = {}) {
|
|
6363
6951
|
const root = resolveRepoRoot();
|
|
6364
6952
|
try {
|
|
6365
6953
|
const resolved = resolveReference(root, ref);
|
|
6366
6954
|
if (resolved.type === "task") {
|
|
6367
|
-
showTask(ref);
|
|
6955
|
+
showTask(ref, options);
|
|
6368
6956
|
return;
|
|
6369
6957
|
}
|
|
6370
|
-
showIdea(ref);
|
|
6958
|
+
showIdea(ref, options);
|
|
6371
6959
|
return;
|
|
6372
6960
|
} catch {
|
|
6373
|
-
showDelivery(ref);
|
|
6961
|
+
showDelivery(ref, options);
|
|
6374
6962
|
}
|
|
6375
6963
|
}
|
|
6376
6964
|
function registerShowCommand(program) {
|
|
6377
|
-
const show = program.command("show").description("Show detailed COOP entities").argument("[ref]", "Task, idea, or delivery reference").action((ref) => {
|
|
6965
|
+
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
6966
|
if (!ref?.trim()) {
|
|
6379
6967
|
throw new Error("Provide a task, idea, or delivery reference.");
|
|
6380
6968
|
}
|
|
6381
|
-
showByReference(ref);
|
|
6969
|
+
showByReference(ref, options);
|
|
6382
6970
|
});
|
|
6383
|
-
show.command("task").description("Show task details").argument("<id>", "Task ID").action((id) => {
|
|
6384
|
-
showTask(id);
|
|
6971
|
+
show.command("task").description("Show task details").argument("<id>", "Task ID").option("--compact", "Show a smaller summary view").action((id, options) => {
|
|
6972
|
+
showTask(id, options);
|
|
6385
6973
|
});
|
|
6386
|
-
show.command("idea").description("Show idea details").argument("<id>", "Idea ID").action((id) => {
|
|
6387
|
-
showIdea(id);
|
|
6974
|
+
show.command("idea").description("Show idea details").argument("<id>", "Idea ID").option("--compact", "Show a smaller summary view").action((id, options) => {
|
|
6975
|
+
showIdea(id, options);
|
|
6388
6976
|
});
|
|
6389
|
-
show.command("delivery").description("Show delivery details").argument("<id>", "Delivery id or name").action((id) => {
|
|
6390
|
-
showDelivery(id);
|
|
6977
|
+
show.command("delivery").description("Show delivery details").argument("<id>", "Delivery id or name").option("--compact", "Show a smaller summary view").action((id, options) => {
|
|
6978
|
+
showDelivery(id, options);
|
|
6391
6979
|
});
|
|
6392
6980
|
}
|
|
6393
6981
|
|
|
@@ -6563,7 +7151,7 @@ function maybePromote(root, taskId, options) {
|
|
|
6563
7151
|
track: options.track ?? context.track,
|
|
6564
7152
|
version: options.version ?? context.version
|
|
6565
7153
|
});
|
|
6566
|
-
writeTaskEntry(filePath, parsed, promoted);
|
|
7154
|
+
writeTaskEntry(root, filePath, parsed, promoted);
|
|
6567
7155
|
console.log(`Promoted ${promoted.id}`);
|
|
6568
7156
|
}
|
|
6569
7157
|
function printResolvedSelectionContext(root, options) {
|
|
@@ -6581,7 +7169,7 @@ function printResolvedSelectionContext(root, options) {
|
|
|
6581
7169
|
}
|
|
6582
7170
|
function registerTaskFlowCommands(program) {
|
|
6583
7171
|
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) => {
|
|
7172
|
+
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
7173
|
const root = resolveRepoRoot();
|
|
6586
7174
|
printResolvedSelectionContext(root, options);
|
|
6587
7175
|
const selected = selectTopReadyTask(root, {
|
|
@@ -6593,7 +7181,7 @@ function registerTaskFlowCommands(program) {
|
|
|
6593
7181
|
console.log(formatSelectedTask(selected.entry, selected.selection));
|
|
6594
7182
|
});
|
|
6595
7183
|
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) => {
|
|
7184
|
+
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
7185
|
const root = resolveRepoRoot();
|
|
6598
7186
|
printResolvedSelectionContext(root, options);
|
|
6599
7187
|
const selected = id?.trim() ? {
|
|
@@ -6623,7 +7211,7 @@ function registerTaskFlowCommands(program) {
|
|
|
6623
7211
|
await claimAndStart(root, selected.entry.task.id, options);
|
|
6624
7212
|
});
|
|
6625
7213
|
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) => {
|
|
7214
|
+
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
7215
|
const root = resolveRepoRoot();
|
|
6628
7216
|
printResolvedSelectionContext(root, options);
|
|
6629
7217
|
const taskId = id?.trim() || selectTopReadyTask(root, {
|
|
@@ -6775,7 +7363,7 @@ import { IdeaStatus as IdeaStatus3, TaskPriority as TaskPriority3, TaskStatus as
|
|
|
6775
7363
|
function collect(value, previous = []) {
|
|
6776
7364
|
return [...previous, value];
|
|
6777
7365
|
}
|
|
6778
|
-
function
|
|
7366
|
+
function unique3(items) {
|
|
6779
7367
|
return [...new Set(items.map((item) => item.trim()).filter(Boolean))];
|
|
6780
7368
|
}
|
|
6781
7369
|
function removeValues(source, values) {
|
|
@@ -6785,7 +7373,7 @@ function removeValues(source, values) {
|
|
|
6785
7373
|
return next.length > 0 ? next : void 0;
|
|
6786
7374
|
}
|
|
6787
7375
|
function addValues(source, values) {
|
|
6788
|
-
const next =
|
|
7376
|
+
const next = unique3([...source ?? [], ...values ?? []]);
|
|
6789
7377
|
return next.length > 0 ? next : void 0;
|
|
6790
7378
|
}
|
|
6791
7379
|
function loadBody(options) {
|
|
@@ -6867,13 +7455,13 @@ function updateTask(id, options) {
|
|
|
6867
7455
|
tests_required: addValues(removeValues(next.tests_required, options.testsRemove), options.testsAdd)
|
|
6868
7456
|
};
|
|
6869
7457
|
next = clearTrackPriorityOverrides(applyTrackPriorityOverrides(next, options.priorityIn), options.clearPriorityIn);
|
|
6870
|
-
validateTaskForWrite(next, filePath);
|
|
7458
|
+
next = validateTaskForWrite(root, next, filePath);
|
|
6871
7459
|
const nextBody = loadBody(options) ?? parsed.body;
|
|
6872
7460
|
if (options.dryRun) {
|
|
6873
7461
|
console.log(renderTaskPreview(next, nextBody).trimEnd());
|
|
6874
7462
|
return;
|
|
6875
7463
|
}
|
|
6876
|
-
writeTaskEntry(filePath, parsed, next, nextBody);
|
|
7464
|
+
writeTaskEntry(root, filePath, parsed, next, nextBody);
|
|
6877
7465
|
console.log(`Updated ${next.id}`);
|
|
6878
7466
|
}
|
|
6879
7467
|
function updateIdea(id, options) {
|
|
@@ -6899,7 +7487,7 @@ function updateIdea(id, options) {
|
|
|
6899
7487
|
console.log(`Updated ${next.id}`);
|
|
6900
7488
|
}
|
|
6901
7489
|
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) => {
|
|
7490
|
+
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
7491
|
let resolved = resolveOptionalEntityArg(first, second, ["task", "idea"], "task");
|
|
6904
7492
|
if (!second && resolved.entity === "task") {
|
|
6905
7493
|
try {
|
|
@@ -6923,9 +7511,17 @@ function registerUpdateCommand(program) {
|
|
|
6923
7511
|
|
|
6924
7512
|
// src/commands/use.ts
|
|
6925
7513
|
function printContext(values) {
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
7514
|
+
const track = values.track?.trim() || "unset";
|
|
7515
|
+
const delivery = values.delivery?.trim() || "unset";
|
|
7516
|
+
const version = values.version?.trim() || "unset";
|
|
7517
|
+
console.log("Working Context:");
|
|
7518
|
+
console.log(`- Track: ${track}`);
|
|
7519
|
+
console.log(`- Delivery: ${delivery}`);
|
|
7520
|
+
console.log(`- Version: ${version}`);
|
|
7521
|
+
if (track === "unset" && delivery === "unset" && version === "unset") {
|
|
7522
|
+
console.log("");
|
|
7523
|
+
console.log("Hint: use `coop use track <id>`, `coop use delivery <id>`, or `coop use version <id>`.");
|
|
7524
|
+
}
|
|
6929
7525
|
}
|
|
6930
7526
|
function registerUseCommand(program) {
|
|
6931
7527
|
const use = program.command("use").description("Manage user-local working defaults for the current COOP project");
|
|
@@ -6935,11 +7531,19 @@ function registerUseCommand(program) {
|
|
|
6935
7531
|
});
|
|
6936
7532
|
use.command("track").description("Set the default working track").argument("<id>", "Track id").action((id) => {
|
|
6937
7533
|
const root = resolveRepoRoot();
|
|
6938
|
-
|
|
7534
|
+
const trackId = resolveExistingTrackId(root, id);
|
|
7535
|
+
if (!trackId) {
|
|
7536
|
+
throw new Error(`Unknown track '${id}'. Create it first with \`coop create track --id ${id} --name "${id}"\` or use \`unassigned\`.`);
|
|
7537
|
+
}
|
|
7538
|
+
printContext(updateWorkingContext(root, resolveCoopHome(), { track: trackId }));
|
|
6939
7539
|
});
|
|
6940
7540
|
use.command("delivery").description("Set the default working delivery").argument("<id>", "Delivery id").action((id) => {
|
|
6941
7541
|
const root = resolveRepoRoot();
|
|
6942
|
-
|
|
7542
|
+
const deliveryId = resolveExistingDeliveryId(root, id);
|
|
7543
|
+
if (!deliveryId) {
|
|
7544
|
+
throw new Error(`Unknown delivery '${id}'. Create it first with \`coop create delivery --id ${id} --name "${id}"\`.`);
|
|
7545
|
+
}
|
|
7546
|
+
printContext(updateWorkingContext(root, resolveCoopHome(), { delivery: deliveryId }));
|
|
6943
7547
|
});
|
|
6944
7548
|
use.command("version").description("Set the default working version").argument("<id>", "Version label").action((id) => {
|
|
6945
7549
|
const root = resolveRepoRoot();
|
|
@@ -7352,16 +7956,16 @@ function chooseFieldValue(key, baseValue, oursValue, theirsValue, oursUpdated, t
|
|
|
7352
7956
|
}
|
|
7353
7957
|
function mergeTaskFrontmatter(base, ours, theirs) {
|
|
7354
7958
|
const keys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)]);
|
|
7355
|
-
const
|
|
7959
|
+
const output2 = {};
|
|
7356
7960
|
const oursUpdated = asTimestamp(ours.updated);
|
|
7357
7961
|
const theirsUpdated = asTimestamp(theirs.updated);
|
|
7358
7962
|
for (const key of keys) {
|
|
7359
7963
|
const merged = chooseFieldValue(key, base[key], ours[key], theirs[key], oursUpdated, theirsUpdated);
|
|
7360
7964
|
if (merged !== void 0) {
|
|
7361
|
-
|
|
7965
|
+
output2[key] = merged;
|
|
7362
7966
|
}
|
|
7363
7967
|
}
|
|
7364
|
-
return
|
|
7968
|
+
return output2;
|
|
7365
7969
|
}
|
|
7366
7970
|
function mergeTextWithGit(ancestor, ours, theirs) {
|
|
7367
7971
|
const result = spawnSync5("git", ["merge-file", "-p", ours, ancestor, theirs], {
|
|
@@ -7392,8 +7996,8 @@ function mergeTaskFile(ancestorPath, oursPath, theirsPath) {
|
|
|
7392
7996
|
fs21.writeFileSync(oursBody, ours.body, "utf8");
|
|
7393
7997
|
fs21.writeFileSync(theirsBody, theirs.body, "utf8");
|
|
7394
7998
|
const mergedBody = mergeTextWithGit(ancestorBody, oursBody, theirsBody);
|
|
7395
|
-
const
|
|
7396
|
-
fs21.writeFileSync(oursPath,
|
|
7999
|
+
const output2 = stringifyFrontmatter6(mergedFrontmatter, mergedBody.output);
|
|
8000
|
+
fs21.writeFileSync(oursPath, output2, "utf8");
|
|
7397
8001
|
return mergedBody.ok ? 0 : 1;
|
|
7398
8002
|
} finally {
|
|
7399
8003
|
fs21.rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -7422,6 +8026,28 @@ function runMergeDriver(kind, ancestorPath, oursPath, theirsPath) {
|
|
|
7422
8026
|
return mergeTaskFile(ancestorPath, oursPath, theirsPath);
|
|
7423
8027
|
}
|
|
7424
8028
|
|
|
8029
|
+
// src/utils/basic-help.ts
|
|
8030
|
+
function renderBasicHelp() {
|
|
8031
|
+
return [
|
|
8032
|
+
"COOP Basics",
|
|
8033
|
+
"",
|
|
8034
|
+
"Day-to-day commands:",
|
|
8035
|
+
"- `coop current`: show working context, active work, and the next ready task",
|
|
8036
|
+
"- `coop use track <id>` / `coop use delivery <id>`: set working scope defaults",
|
|
8037
|
+
"- `coop list tracks` / `coop list deliveries`: inspect valid named values before assigning them",
|
|
8038
|
+
"- `coop next task` or `coop pick task`: choose work from COOP",
|
|
8039
|
+
"- `coop show <id>`: inspect a task, idea, or delivery",
|
|
8040
|
+
"- `coop list tasks --track <id>`: browse scoped work",
|
|
8041
|
+
"- `coop update <id> --track <id> --delivery <id>`: update task metadata",
|
|
8042
|
+
'- `coop comment <id> --message "..."`: append a task comment',
|
|
8043
|
+
"- `coop log-time <id> --hours 2 --kind worked`: append time spent",
|
|
8044
|
+
"- `coop review task <id>` / `coop complete task <id>`: move work through lifecycle",
|
|
8045
|
+
"- `coop help-ai --initial-prompt --strict --repo C:/path/to/repo --delivery MVP --command coop.cmd`: hand off COOP context to an agent",
|
|
8046
|
+
"",
|
|
8047
|
+
"Use `coop <command> --help` for detailed flags."
|
|
8048
|
+
].join("\n");
|
|
8049
|
+
}
|
|
8050
|
+
|
|
7425
8051
|
// src/utils/not-implemented.ts
|
|
7426
8052
|
function printNotImplemented(command, phase) {
|
|
7427
8053
|
console.log(`${command}: Not yet implemented - coming in Phase ${phase}.`);
|
|
@@ -7447,9 +8073,16 @@ function createProgram() {
|
|
|
7447
8073
|
const program = new Command();
|
|
7448
8074
|
program.name("coop");
|
|
7449
8075
|
program.version(readVersion());
|
|
7450
|
-
program.description("COOP CLI");
|
|
8076
|
+
program.description("COOP CLI for Git-native planning, backlog management, task execution, and delivery workflows.");
|
|
7451
8077
|
program.option("--verbose", "Print stack traces for command errors");
|
|
7452
8078
|
program.option("-p, --project <id>", "Select the active COOP project");
|
|
8079
|
+
program.addHelpText("after", `
|
|
8080
|
+
Common day-to-day commands:
|
|
8081
|
+
coop basics
|
|
8082
|
+
coop current
|
|
8083
|
+
coop next task
|
|
8084
|
+
coop show <id>
|
|
8085
|
+
`);
|
|
7453
8086
|
registerInitCommand(program);
|
|
7454
8087
|
registerCreateCommand(program);
|
|
7455
8088
|
registerCurrentCommand(program);
|
|
@@ -7485,6 +8118,9 @@ function createProgram() {
|
|
|
7485
8118
|
registerViewCommand(program);
|
|
7486
8119
|
registerWebhookCommand(program);
|
|
7487
8120
|
registerPhasePlaceholder(program, "ext", 3, "Plugin extension commands");
|
|
8121
|
+
program.command("basics").alias("help-basic").description("Show the small set of COOP commands that cover most day-to-day work").action(() => {
|
|
8122
|
+
console.log(renderBasicHelp());
|
|
8123
|
+
});
|
|
7488
8124
|
const hooks = program.command("hook");
|
|
7489
8125
|
hooks.command("pre-commit").option("--repo <path>", "Repository root", process.cwd()).action((options) => {
|
|
7490
8126
|
const repoRoot = options.repo ?? process.cwd();
|