@kitsy/coop 2.2.1 → 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 +238 -90
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -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("/"));
|
|
@@ -1419,17 +1490,19 @@ function writeIdeaFile(filePath, parsed, idea, body = parsed.body) {
|
|
|
1419
1490
|
const output2 = stringifyFrontmatter2({ ...parsed.raw, ...idea }, body);
|
|
1420
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
|
|
@@ -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,7 +2212,6 @@ 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";
|
|
@@ -2166,7 +2240,7 @@ function renderSelect(question, choices, selected) {
|
|
|
2166
2240
|
process2.stdout.write(` ${prefix} ${choice.label}${hint}
|
|
2167
2241
|
`);
|
|
2168
2242
|
}
|
|
2169
|
-
process2.stdout.write("\nUse
|
|
2243
|
+
process2.stdout.write("\nUse Up/Down arrows to choose, Enter to confirm.\n");
|
|
2170
2244
|
}
|
|
2171
2245
|
function moveCursorUp(lines) {
|
|
2172
2246
|
if (lines <= 0) return;
|
|
@@ -2182,7 +2256,9 @@ async function select(question, choices, defaultIndex = 0) {
|
|
|
2182
2256
|
}
|
|
2183
2257
|
readline.emitKeypressEvents(process2.stdin);
|
|
2184
2258
|
const previousRawMode = process2.stdin.isRaw;
|
|
2259
|
+
const wasPaused = typeof process2.stdin.isPaused === "function" ? process2.stdin.isPaused() : false;
|
|
2185
2260
|
process2.stdin.setRawMode(true);
|
|
2261
|
+
process2.stdin.resume();
|
|
2186
2262
|
let selected = Math.min(Math.max(defaultIndex, 0), choices.length - 1);
|
|
2187
2263
|
const renderedLines = choices.length + 2;
|
|
2188
2264
|
renderSelect(question, choices, selected);
|
|
@@ -2190,6 +2266,9 @@ async function select(question, choices, defaultIndex = 0) {
|
|
|
2190
2266
|
const cleanup = () => {
|
|
2191
2267
|
process2.stdin.off("keypress", onKeypress);
|
|
2192
2268
|
process2.stdin.setRawMode(previousRawMode ?? false);
|
|
2269
|
+
if (wasPaused) {
|
|
2270
|
+
process2.stdin.pause();
|
|
2271
|
+
}
|
|
2193
2272
|
process2.stdout.write("\n");
|
|
2194
2273
|
};
|
|
2195
2274
|
const rerender = () => {
|
|
@@ -2631,7 +2710,7 @@ function plusDaysIso(days) {
|
|
|
2631
2710
|
date.setUTCDate(date.getUTCDate() + days);
|
|
2632
2711
|
return date.toISOString().slice(0, 10);
|
|
2633
2712
|
}
|
|
2634
|
-
function
|
|
2713
|
+
function unique2(values) {
|
|
2635
2714
|
return Array.from(new Set(values));
|
|
2636
2715
|
}
|
|
2637
2716
|
function resolveIdeaFile2(root, idOrAlias) {
|
|
@@ -2639,7 +2718,7 @@ function resolveIdeaFile2(root, idOrAlias) {
|
|
|
2639
2718
|
return path8.join(root, ...target.file.split("/"));
|
|
2640
2719
|
}
|
|
2641
2720
|
function updateIdeaLinkedTasks(filePath, idea, raw, body, linked) {
|
|
2642
|
-
const next =
|
|
2721
|
+
const next = unique2([...idea.linked_tasks ?? [], ...linked]).sort((a, b) => a.localeCompare(b));
|
|
2643
2722
|
const nextRaw = {
|
|
2644
2723
|
...raw,
|
|
2645
2724
|
linked_tasks: next
|
|
@@ -2654,10 +2733,10 @@ function makeTaskDraft(input2) {
|
|
|
2654
2733
|
track: input2.track,
|
|
2655
2734
|
priority: input2.priority,
|
|
2656
2735
|
body: input2.body,
|
|
2657
|
-
acceptance:
|
|
2658
|
-
testsRequired:
|
|
2659
|
-
authorityRefs:
|
|
2660
|
-
derivedRefs:
|
|
2736
|
+
acceptance: unique2(input2.acceptance ?? []),
|
|
2737
|
+
testsRequired: unique2(input2.testsRequired ?? []),
|
|
2738
|
+
authorityRefs: unique2(input2.authorityRefs ?? []),
|
|
2739
|
+
derivedRefs: unique2(input2.derivedRefs ?? [])
|
|
2661
2740
|
};
|
|
2662
2741
|
}
|
|
2663
2742
|
function registerCreateCommand(program) {
|
|
@@ -2691,10 +2770,10 @@ function registerCreateCommand(program) {
|
|
|
2691
2770
|
const priority = options.priority?.trim() || (interactive ? await ask("Priority", "p2") : "p2");
|
|
2692
2771
|
const delivery = options.delivery?.trim() || (interactive ? await ask("Delivery (optional)", "") : "");
|
|
2693
2772
|
const body = options.body ?? (interactive ? await ask("Task body (optional)", "") : "");
|
|
2694
|
-
const acceptance = options.acceptance && options.acceptance.length > 0 ?
|
|
2695
|
-
const testsRequired = options.testsRequired && options.testsRequired.length > 0 ?
|
|
2696
|
-
const authorityRefs = options.authorityRef && options.authorityRef.length > 0 ?
|
|
2697
|
-
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)", "")) : [];
|
|
2698
2777
|
const date = todayIsoDate();
|
|
2699
2778
|
const drafts = [];
|
|
2700
2779
|
let sourceIdeaPath = null;
|
|
@@ -2810,13 +2889,8 @@ function registerCreateCommand(program) {
|
|
|
2810
2889
|
} : void 0
|
|
2811
2890
|
};
|
|
2812
2891
|
const filePath = path8.join(coop, "tasks", `${id}.md`);
|
|
2813
|
-
const
|
|
2814
|
-
|
|
2815
|
-
const message = structuralIssues.map((issue) => `- ${issue.message}`).join("\n");
|
|
2816
|
-
throw new Error(`Task failed structural validation:
|
|
2817
|
-
${message}`);
|
|
2818
|
-
}
|
|
2819
|
-
writeTask6(task, {
|
|
2892
|
+
const normalizedTask = validateTaskForWrite(root, task, filePath);
|
|
2893
|
+
writeTask6(normalizedTask, {
|
|
2820
2894
|
body: draft.body,
|
|
2821
2895
|
filePath
|
|
2822
2896
|
});
|
|
@@ -2892,7 +2966,7 @@ ${message}`);
|
|
|
2892
2966
|
const interactive = Boolean(options.interactive);
|
|
2893
2967
|
const name = options.name?.trim() || nameArg?.trim() || await ask("Track name");
|
|
2894
2968
|
if (!name) throw new Error("Track name is required.");
|
|
2895
|
-
const capacityProfiles =
|
|
2969
|
+
const capacityProfiles = unique2(
|
|
2896
2970
|
options.profiles ? parseCsv(options.profiles) : interactive ? parseCsv(await ask("Capacity profiles (comma-separated)", "backend_team")) : ["backend_team"]
|
|
2897
2971
|
);
|
|
2898
2972
|
const maxWipInput = options.maxWip?.trim() || (interactive ? await ask("Max concurrent tasks", "6") : "6");
|
|
@@ -2900,7 +2974,7 @@ ${message}`);
|
|
|
2900
2974
|
if (typeof maxWip !== "number" || maxWip <= 0 || !Number.isInteger(maxWip)) {
|
|
2901
2975
|
throw new Error("max-wip must be a positive integer.");
|
|
2902
2976
|
}
|
|
2903
|
-
const allowed =
|
|
2977
|
+
const allowed = unique2(
|
|
2904
2978
|
options.allowedTypes ? parseCsv(options.allowedTypes).map((entry) => entry.toLowerCase()) : interactive ? parseCsv(await ask("Allowed types (comma-separated)", "feature,bug,chore,spike")).map(
|
|
2905
2979
|
(entry) => entry.toLowerCase()
|
|
2906
2980
|
) : ["feature", "bug", "chore", "spike"]
|
|
@@ -2982,7 +3056,7 @@ ${message}`);
|
|
|
2982
3056
|
options.budgetCost?.trim() || (interactive ? await ask("Budget cost USD (optional)", "") : void 0),
|
|
2983
3057
|
"budget-cost"
|
|
2984
3058
|
);
|
|
2985
|
-
const capacityProfiles =
|
|
3059
|
+
const capacityProfiles = unique2(
|
|
2986
3060
|
options.profiles ? parseCsv(options.profiles) : interactive ? parseCsv(await ask("Capacity profiles (comma-separated)", "backend_team")) : ["backend_team"]
|
|
2987
3061
|
);
|
|
2988
3062
|
const tasks = listTaskFiles(root).map((filePath2) => {
|
|
@@ -2995,12 +3069,12 @@ ${message}`);
|
|
|
2995
3069
|
console.log(`- ${task.id}: ${task.title}`);
|
|
2996
3070
|
}
|
|
2997
3071
|
}
|
|
2998
|
-
const scopeInclude =
|
|
3072
|
+
const scopeInclude = unique2(
|
|
2999
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(
|
|
3000
3074
|
(value) => value.toUpperCase()
|
|
3001
3075
|
) : []
|
|
3002
3076
|
);
|
|
3003
|
-
const scopeExclude =
|
|
3077
|
+
const scopeExclude = unique2(
|
|
3004
3078
|
options.exclude ? parseCsv(options.exclude).map((value) => value.toUpperCase()) : interactive ? parseCsv(await ask("Scope exclude task IDs (comma-separated)", "")).map((value) => value.toUpperCase()) : []
|
|
3005
3079
|
);
|
|
3006
3080
|
const knownTaskIds = new Set(tasks.map((task) => task.id));
|
|
@@ -3336,6 +3410,7 @@ var catalog = {
|
|
|
3336
3410
|
selection_rules: [
|
|
3337
3411
|
"Use `coop project show` first to confirm the active workspace and project.",
|
|
3338
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`.",
|
|
3339
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.",
|
|
3340
3415
|
"Commands resolve selection scope from: explicit CLI arg, then `coop use` working context, then shared project defaults.",
|
|
3341
3416
|
"Use `--track` for the workstream lens (home track or delivery_tracks). Use `--delivery` for release/scope membership.",
|
|
@@ -3393,6 +3468,8 @@ var catalog = {
|
|
|
3393
3468
|
{ usage: "coop use track <id>", purpose: "Set the default working track for commands that can infer scope." },
|
|
3394
3469
|
{ usage: "coop use delivery <id>", purpose: "Set the default working delivery for commands that need delivery scope." },
|
|
3395
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." },
|
|
3396
3473
|
{ usage: "coop current", purpose: "Show the active project, working context, my active tasks, and the next ready task." },
|
|
3397
3474
|
{ usage: "coop naming", purpose: "Explain the current naming template, tokens, and examples." },
|
|
3398
3475
|
{ usage: 'coop naming preview "Natural-language COOP command recommender"', purpose: "Preview a semantic ID before creating an item." }
|
|
@@ -3407,6 +3484,7 @@ var catalog = {
|
|
|
3407
3484
|
{ usage: "cat idea.md | coop create idea --stdin", purpose: "Ingest an idea draft from stdin." },
|
|
3408
3485
|
{ usage: 'coop create task "Implement webhook pipeline"', purpose: "Create a task with defaults." },
|
|
3409
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." },
|
|
3410
3488
|
{
|
|
3411
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',
|
|
3412
3490
|
purpose: "Create a planning-grade task with acceptance, tests, and origin refs."
|
|
@@ -3450,6 +3528,8 @@ var catalog = {
|
|
|
3450
3528
|
description: "Read backlog state, task details, and planning output.",
|
|
3451
3529
|
commands: [
|
|
3452
3530
|
{ usage: "coop list tasks --status todo", purpose: "List tasks with filters." },
|
|
3531
|
+
{ usage: "coop list tracks", purpose: "List valid named tracks." },
|
|
3532
|
+
{ usage: "coop list deliveries", purpose: "List valid named deliveries." },
|
|
3453
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." },
|
|
3454
3534
|
{ usage: "coop list tasks --mine", purpose: "List tasks assigned to the current default COOP author." },
|
|
3455
3535
|
{ usage: 'coop search "auth and login form"', purpose: "Run deterministic non-AI search across tasks, ideas, and deliveries." },
|
|
@@ -3834,11 +3914,11 @@ function resolveHelpTopic(options) {
|
|
|
3834
3914
|
if (options.naming) {
|
|
3835
3915
|
requestedTopics.push("naming");
|
|
3836
3916
|
}
|
|
3837
|
-
const
|
|
3838
|
-
if (
|
|
3839
|
-
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(", ")}.`);
|
|
3840
3920
|
}
|
|
3841
|
-
const topic =
|
|
3921
|
+
const topic = unique4[0];
|
|
3842
3922
|
if (topic !== void 0 && topic !== "state-transitions" && topic !== "artifacts" && topic !== "post-execution" && topic !== "selection" && topic !== "naming") {
|
|
3843
3923
|
throw new Error(`Unsupported help-ai topic '${topic}'. Expected state-transitions|artifacts|post-execution|selection|naming.`);
|
|
3844
3924
|
}
|
|
@@ -3904,7 +3984,7 @@ import { CURRENT_SCHEMA_VERSION, write_schema_version } from "@kitsy/coop-core";
|
|
|
3904
3984
|
import fs8 from "fs";
|
|
3905
3985
|
import path11 from "path";
|
|
3906
3986
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3907
|
-
import { detect_cycle, parseTaskContent, parseTaskFile as parseTaskFile8, validateStructural as
|
|
3987
|
+
import { detect_cycle, parseTaskContent, parseTaskFile as parseTaskFile8, validateStructural as validateStructural5 } from "@kitsy/coop-core";
|
|
3908
3988
|
var HOOK_BLOCK_START = "# COOP_PRE_COMMIT_START";
|
|
3909
3989
|
var HOOK_BLOCK_END = "# COOP_PRE_COMMIT_END";
|
|
3910
3990
|
function runGit(repoRoot, args, allowFailure = false) {
|
|
@@ -3988,7 +4068,7 @@ function parseStagedTasks(repoRoot, relativePaths) {
|
|
|
3988
4068
|
errors.push(`[COOP] ${message}`);
|
|
3989
4069
|
continue;
|
|
3990
4070
|
}
|
|
3991
|
-
const issues =
|
|
4071
|
+
const issues = validateStructural5(task, { filePath: absolutePath });
|
|
3992
4072
|
for (const issue of issues) {
|
|
3993
4073
|
errors.push(`[COOP] ${relativePath}: ${issue.message}`);
|
|
3994
4074
|
}
|
|
@@ -4643,9 +4723,48 @@ import {
|
|
|
4643
4723
|
parseDeliveryFile as parseDeliveryFile2,
|
|
4644
4724
|
parseIdeaFile as parseIdeaFile4,
|
|
4645
4725
|
parseTaskFile as parseTaskFile10,
|
|
4726
|
+
parseYamlFile as parseYamlFile3,
|
|
4646
4727
|
schedule_next as schedule_next3
|
|
4647
4728
|
} from "@kitsy/coop-core";
|
|
4648
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
|
+
}
|
|
4649
4768
|
function statusColor(status) {
|
|
4650
4769
|
switch (status) {
|
|
4651
4770
|
case "done":
|
|
@@ -4884,8 +5003,16 @@ function listTasks(options) {
|
|
|
4884
5003
|
ensureCoopInitialized(root);
|
|
4885
5004
|
const context = readWorkingContext(root, resolveCoopHome());
|
|
4886
5005
|
const graph = load_graph6(coopDir(root));
|
|
4887
|
-
const
|
|
4888
|
-
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
|
+
};
|
|
4889
5016
|
const resolvedVersion = resolveContextValueWithSource(options.version, context.version);
|
|
4890
5017
|
const assignee = options.mine ? defaultCoopAuthor(root) : options.assignee?.trim();
|
|
4891
5018
|
const deliveryScope = resolvedDelivery.value ? new Set(graph.deliveries.get(resolvedDelivery.value)?.scope.include ?? []) : null;
|
|
@@ -4942,27 +5069,30 @@ function listTasks(options) {
|
|
|
4942
5069
|
columns.map(taskColumnHeader),
|
|
4943
5070
|
sorted.map(
|
|
4944
5071
|
(entry) => columns.map((column) => {
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
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]);
|
|
4966
5096
|
})
|
|
4967
5097
|
)
|
|
4968
5098
|
)
|
|
@@ -5012,17 +5142,8 @@ function listIdeas(options) {
|
|
|
5012
5142
|
columns.map(ideaColumnHeader),
|
|
5013
5143
|
sorted.map(
|
|
5014
5144
|
(entry) => columns.map((column) => {
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
return entry.title;
|
|
5018
|
-
case "status":
|
|
5019
|
-
return statusColor(entry.status);
|
|
5020
|
-
case "file":
|
|
5021
|
-
return path15.relative(root, entry.filePath);
|
|
5022
|
-
case "id":
|
|
5023
|
-
default:
|
|
5024
|
-
return entry.id;
|
|
5025
|
-
}
|
|
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]);
|
|
5026
5147
|
})
|
|
5027
5148
|
)
|
|
5028
5149
|
)
|
|
@@ -5033,11 +5154,11 @@ Total ideas: ${sorted.length}`);
|
|
|
5033
5154
|
function listDeliveries() {
|
|
5034
5155
|
const root = resolveRepoRoot();
|
|
5035
5156
|
const rows = listDeliveryFiles(root).map((filePath) => ({ delivery: parseDeliveryFile2(filePath).delivery, filePath })).map(({ delivery, filePath }) => [
|
|
5036
|
-
delivery.id,
|
|
5037
|
-
delivery.name,
|
|
5038
|
-
statusColor(delivery.status),
|
|
5039
|
-
delivery.target_date ?? "-",
|
|
5040
|
-
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)
|
|
5041
5162
|
]);
|
|
5042
5163
|
if (rows.length === 0) {
|
|
5043
5164
|
console.log("No deliveries found.");
|
|
@@ -5045,6 +5166,21 @@ function listDeliveries() {
|
|
|
5045
5166
|
}
|
|
5046
5167
|
console.log(formatTable(["ID", "Name", "Status", "Target", "File"], rows));
|
|
5047
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
|
+
}
|
|
5048
5184
|
function registerListCommand(program) {
|
|
5049
5185
|
const list = program.command("list").description("List COOP entities");
|
|
5050
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) => {
|
|
@@ -5056,7 +5192,10 @@ function registerListCommand(program) {
|
|
|
5056
5192
|
list.command("alias").description("List aliases").argument("[pattern]", "Wildcard pattern, e.g. PAY*").action((pattern) => {
|
|
5057
5193
|
listAliasRows(pattern);
|
|
5058
5194
|
});
|
|
5059
|
-
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(() => {
|
|
5060
5199
|
listDeliveries();
|
|
5061
5200
|
});
|
|
5062
5201
|
}
|
|
@@ -5191,7 +5330,7 @@ function registerLogTimeCommand(program) {
|
|
|
5191
5330
|
hours,
|
|
5192
5331
|
options.note?.trim() || void 0
|
|
5193
5332
|
);
|
|
5194
|
-
writeTaskEntry(filePath, parsed, task);
|
|
5333
|
+
writeTaskEntry(root, filePath, parsed, task);
|
|
5195
5334
|
console.log(`Logged ${hours}h ${options.kind} on ${task.id}`);
|
|
5196
5335
|
});
|
|
5197
5336
|
}
|
|
@@ -5201,7 +5340,7 @@ import fs12 from "fs";
|
|
|
5201
5340
|
import path17 from "path";
|
|
5202
5341
|
import { createInterface } from "readline/promises";
|
|
5203
5342
|
import { stdin as input, stdout as output } from "process";
|
|
5204
|
-
import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as
|
|
5343
|
+
import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as parseYamlFile4, writeYamlFile as writeYamlFile4 } from "@kitsy/coop-core";
|
|
5205
5344
|
var COOP_IGNORE_TEMPLATE2 = `.index/
|
|
5206
5345
|
logs/
|
|
5207
5346
|
tmp/
|
|
@@ -5368,7 +5507,7 @@ async function migrateWorkspaceLayout(root, options) {
|
|
|
5368
5507
|
}
|
|
5369
5508
|
const movedConfigPath = path17.join(projectRoot, "config.yml");
|
|
5370
5509
|
if (fs12.existsSync(movedConfigPath)) {
|
|
5371
|
-
const movedConfig =
|
|
5510
|
+
const movedConfig = parseYamlFile4(movedConfigPath);
|
|
5372
5511
|
const nextProject = typeof movedConfig.project === "object" && movedConfig.project !== null ? { ...movedConfig.project } : {};
|
|
5373
5512
|
nextProject.name = identity.projectName;
|
|
5374
5513
|
nextProject.id = projectId;
|
|
@@ -5763,7 +5902,7 @@ function registerPromoteCommand(program) {
|
|
|
5763
5902
|
track: resolvedTrack.value,
|
|
5764
5903
|
version: resolvedVersion.value
|
|
5765
5904
|
});
|
|
5766
|
-
writeTaskEntry(filePath, parsed, task);
|
|
5905
|
+
writeTaskEntry(root, filePath, parsed, task);
|
|
5767
5906
|
console.log(`Promoted ${task.id}`);
|
|
5768
5907
|
});
|
|
5769
5908
|
}
|
|
@@ -7012,7 +7151,7 @@ function maybePromote(root, taskId, options) {
|
|
|
7012
7151
|
track: options.track ?? context.track,
|
|
7013
7152
|
version: options.version ?? context.version
|
|
7014
7153
|
});
|
|
7015
|
-
writeTaskEntry(filePath, parsed, promoted);
|
|
7154
|
+
writeTaskEntry(root, filePath, parsed, promoted);
|
|
7016
7155
|
console.log(`Promoted ${promoted.id}`);
|
|
7017
7156
|
}
|
|
7018
7157
|
function printResolvedSelectionContext(root, options) {
|
|
@@ -7224,7 +7363,7 @@ import { IdeaStatus as IdeaStatus3, TaskPriority as TaskPriority3, TaskStatus as
|
|
|
7224
7363
|
function collect(value, previous = []) {
|
|
7225
7364
|
return [...previous, value];
|
|
7226
7365
|
}
|
|
7227
|
-
function
|
|
7366
|
+
function unique3(items) {
|
|
7228
7367
|
return [...new Set(items.map((item) => item.trim()).filter(Boolean))];
|
|
7229
7368
|
}
|
|
7230
7369
|
function removeValues(source, values) {
|
|
@@ -7234,7 +7373,7 @@ function removeValues(source, values) {
|
|
|
7234
7373
|
return next.length > 0 ? next : void 0;
|
|
7235
7374
|
}
|
|
7236
7375
|
function addValues(source, values) {
|
|
7237
|
-
const next =
|
|
7376
|
+
const next = unique3([...source ?? [], ...values ?? []]);
|
|
7238
7377
|
return next.length > 0 ? next : void 0;
|
|
7239
7378
|
}
|
|
7240
7379
|
function loadBody(options) {
|
|
@@ -7316,13 +7455,13 @@ function updateTask(id, options) {
|
|
|
7316
7455
|
tests_required: addValues(removeValues(next.tests_required, options.testsRemove), options.testsAdd)
|
|
7317
7456
|
};
|
|
7318
7457
|
next = clearTrackPriorityOverrides(applyTrackPriorityOverrides(next, options.priorityIn), options.clearPriorityIn);
|
|
7319
|
-
validateTaskForWrite(next, filePath);
|
|
7458
|
+
next = validateTaskForWrite(root, next, filePath);
|
|
7320
7459
|
const nextBody = loadBody(options) ?? parsed.body;
|
|
7321
7460
|
if (options.dryRun) {
|
|
7322
7461
|
console.log(renderTaskPreview(next, nextBody).trimEnd());
|
|
7323
7462
|
return;
|
|
7324
7463
|
}
|
|
7325
|
-
writeTaskEntry(filePath, parsed, next, nextBody);
|
|
7464
|
+
writeTaskEntry(root, filePath, parsed, next, nextBody);
|
|
7326
7465
|
console.log(`Updated ${next.id}`);
|
|
7327
7466
|
}
|
|
7328
7467
|
function updateIdea(id, options) {
|
|
@@ -7392,11 +7531,19 @@ function registerUseCommand(program) {
|
|
|
7392
7531
|
});
|
|
7393
7532
|
use.command("track").description("Set the default working track").argument("<id>", "Track id").action((id) => {
|
|
7394
7533
|
const root = resolveRepoRoot();
|
|
7395
|
-
|
|
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 }));
|
|
7396
7539
|
});
|
|
7397
7540
|
use.command("delivery").description("Set the default working delivery").argument("<id>", "Delivery id").action((id) => {
|
|
7398
7541
|
const root = resolveRepoRoot();
|
|
7399
|
-
|
|
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 }));
|
|
7400
7547
|
});
|
|
7401
7548
|
use.command("version").description("Set the default working version").argument("<id>", "Version label").action((id) => {
|
|
7402
7549
|
const root = resolveRepoRoot();
|
|
@@ -7887,6 +8034,7 @@ function renderBasicHelp() {
|
|
|
7887
8034
|
"Day-to-day commands:",
|
|
7888
8035
|
"- `coop current`: show working context, active work, and the next ready task",
|
|
7889
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",
|
|
7890
8038
|
"- `coop next task` or `coop pick task`: choose work from COOP",
|
|
7891
8039
|
"- `coop show <id>`: inspect a task, idea, or delivery",
|
|
7892
8040
|
"- `coop list tasks --track <id>`: browse scoped work",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitsy/coop",
|
|
3
3
|
"description": "COOP command-line interface.",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"publishConfig": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"chalk": "^5.6.2",
|
|
40
40
|
"commander": "^14.0.0",
|
|
41
41
|
"octokit": "^5.0.5",
|
|
42
|
-
"@kitsy/coop-
|
|
43
|
-
"@kitsy/coop-
|
|
44
|
-
"@kitsy/coop-
|
|
42
|
+
"@kitsy/coop-core": "2.2.2",
|
|
43
|
+
"@kitsy/coop-ai": "2.2.2",
|
|
44
|
+
"@kitsy/coop-ui": "^2.2.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^24.12.0",
|