@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.
Files changed (2) hide show
  1. package/dist/index.js +881 -245
  2. 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(input3, fallback, maxLength = 12) {
241
- const normalized = input3.toUpperCase().replace(/[^A-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-").replace(/-/g, "");
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(input3, fallback = "X") {
278
- const normalized = input3.toUpperCase().replace(/[^A-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
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(input3) {
282
- return input3.toUpperCase().replace(/[^A-Z0-9]+/g, "").trim();
281
+ function sanitizeSemanticWord(input2) {
282
+ return input2.toUpperCase().replace(/[^A-Z0-9]+/g, "").trim();
283
283
  }
284
- function semanticWords(input3) {
285
- const rawWords = input3.split(/[^a-zA-Z0-9]+/g).map(sanitizeSemanticWord).filter(Boolean);
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(input3, maxLength = DEFAULT_TITLE_TOKEN_LENGTH) {
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(input3);
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(input3) {
456
- return input3.replace(/\\/g, "/");
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(input3) {
464
- return input3.trim().toUpperCase().replace(/_/g, ".").replace(/\.+/g, ".");
463
+ function normalizeAliasValue(input2) {
464
+ return input2.trim().toUpperCase().replace(/_/g, ".").replace(/\.+/g, ".");
465
465
  }
466
- function normalizePatternValue(input3) {
467
- return input3.trim().toUpperCase().replace(/_/g, ".");
466
+ function normalizePatternValue(input2) {
467
+ return input2.trim().toUpperCase().replace(/_/g, ".");
468
468
  }
469
- function normalizeAlias(input3) {
470
- const normalized = normalizeAliasValue(input3);
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 '${input3}'. Use letters/numbers with '.' and '-'.`);
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 output3 = stringifyFrontmatter(nextRaw, parsed.body);
663
- fs2.writeFileSync(filePath, output3, "utf8");
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(input3, fallback) {
836
- const normalized = input3.trim().toLowerCase().replace(/[^a-z0-9/_-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
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(input3) {
996
+ async createPullRequest(input2) {
997
997
  const response = await octokit.rest.pulls.create({
998
- owner: input3.owner,
999
- repo: input3.repo,
1000
- title: input3.title,
1001
- body: input3.body,
1002
- head: input3.head,
1003
- base: input3.base,
1004
- draft: input3.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(input3) {
1014
+ async updatePullRequest(input2) {
1015
1015
  const response = await octokit.rest.pulls.update({
1016
- owner: input3.owner,
1017
- repo: input3.repo,
1018
- pull_number: input3.pull_number,
1019
- title: input3.title,
1020
- body: input3.body,
1021
- base: input3.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(input3) {
1031
+ async getPullRequest(input2) {
1032
1032
  const response = await octokit.rest.pulls.get({
1033
- owner: input3.owner,
1034
- repo: input3.repo,
1035
- pull_number: input3.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(input3) {
1045
+ async mergePullRequest(input2) {
1046
1046
  const response = await octokit.rest.pulls.merge({
1047
- owner: input3.owner,
1048
- repo: input3.repo,
1049
- pull_number: input3.pull_number,
1050
- merge_method: input3.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 output3 = stringifyFrontmatter2({ ...parsed.raw, ...idea }, body);
1420
- fs3.writeFileSync(filePath, output3, "utf8");
1490
+ const output2 = stringifyFrontmatter2({ ...parsed.raw, ...idea }, body);
1491
+ fs3.writeFileSync(filePath, output2, "utf8");
1421
1492
  }
1422
- function validateTaskForWrite(task, filePath) {
1423
- const structuralIssues = validateStructural2(task, { filePath });
1424
- const semanticIssues = validateSemantic(task);
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(task, {
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, contextValue, sharedDefault2) {
1650
+ function resolveContextValueWithSource(explicit, contextValue2, sharedDefault2) {
1578
1651
  if (explicit?.trim()) {
1579
1652
  return { value: explicit.trim(), source: "arg" };
1580
1653
  }
1581
- if (contextValue?.trim()) {
1582
- return { value: contextValue.trim(), source: "use" };
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: options.track?.trim() || context.track?.trim() || configDefaultTrack(root),
1619
- delivery: options.delivery?.trim() || context.delivery?.trim() || configDefaultDelivery(root),
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/promises";
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 = readline.createInterface({
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 unique(values) {
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 = unique([...idea.linked_tasks ?? [], ...linked]).sort((a, b) => a.localeCompare(b));
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(input3) {
2728
+ function makeTaskDraft(input2) {
2584
2729
  return {
2585
- title: input3.title,
2586
- type: input3.type,
2587
- status: input3.status,
2588
- track: input3.track,
2589
- priority: input3.priority,
2590
- body: input3.body,
2591
- acceptance: unique(input3.acceptance ?? []),
2592
- testsRequired: unique(input3.testsRequired ?? []),
2593
- authorityRefs: unique(input3.authorityRefs ?? []),
2594
- derivedRefs: unique(input3.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>", "Track 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) => {
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 ? unique(options.acceptance) : interactive ? parseCsv(await ask("Acceptance criteria (comma-separated, optional)", "")) : [];
2628
- const testsRequired = options.testsRequired && options.testsRequired.length > 0 ? unique(options.testsRequired) : interactive ? parseCsv(await ask("Tests required (comma-separated, optional)", "")) : [];
2629
- const authorityRefs = options.authorityRef && options.authorityRef.length > 0 ? unique(options.authorityRef) : interactive ? parseCsv(await ask("Authority refs (comma-separated, optional)", "")) : [];
2630
- const derivedRefs = options.derivedRef && options.derivedRef.length > 0 ? unique(options.derivedRef) : interactive ? parseCsv(await ask("Derived refs (comma-separated, optional)", "")) : [];
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 structuralIssues = validateStructural5(task, { filePath });
2746
- if (structuralIssues.length > 0) {
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 = unique(
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 = unique(
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 = unique(
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 = unique(
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 = unique(
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(`Track: ${context.track ?? "-"}`);
2998
- console.log(`Delivery: ${context.delivery ?? "-"}`);
2999
- console.log(`Version: ${context.version ?? "-"}`);
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(`Depends On: ${task.depends_on && task.depends_on.length > 0 ? task.depends_on.join(", ") : "-"}`);
3038
- console.log(`Required By: ${reverse.length > 0 ? reverse.join(", ") : "-"}`);
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, input3) {
3059
- const direct = graph.deliveries.get(input3);
3247
+ function resolveDelivery(graph, input2) {
3248
+ const direct = graph.deliveries.get(input2);
3060
3249
  if (direct) return direct;
3061
- const target = normalize(input3);
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 '${input3}'. Use delivery id instead.`);
3258
+ throw new Error(`Multiple deliveries match '${input2}'. Use delivery id instead.`);
3070
3259
  }
3071
- throw new Error(`Delivery '${input3}' not found.`);
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 tasks --track MVP --delivery MVP --ready", purpose: "List ready tasks with track and delivery filtering." },
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 output3 = options.initialPrompt ? renderAiInitialPrompt({
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(output3.trimEnd());
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 unique3 = [...new Set(requestedTopics)];
3715
- if (unique3.length > 1) {
3716
- throw new Error(`Specify only one focused help-ai topic at a time. Received: ${unique3.join(", ")}.`);
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 = unique3[0];
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 validateStructural6 } from "@kitsy/coop-core";
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 = validateStructural6(task, { filePath: absolutePath });
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
- try {
4335
- const initialName = options.name?.trim() || defaultName;
4336
- const projectName = options.name?.trim() || await ask2("Project name", initialName);
4337
- const defaultId = options.id?.trim() || normalizeProjectId(projectName) || repoIdentityId(root);
4338
- const projectIdRaw = options.id?.trim() || await ask2("Project id", defaultId);
4339
- const projectId = normalizeProjectId(projectIdRaw);
4340
- if (!projectId) {
4341
- throw new Error("Invalid project id. Use letters, numbers, and hyphens.");
4342
- }
4343
- const aliasesInput = options.aliases !== void 0 ? options.aliases : await ask2("Aliases (csv, optional)", "");
4344
- const namingTemplate = options.naming?.trim() || await ask2("ID naming template", DEFAULT_ID_NAMING_TEMPLATE);
4345
- return {
4346
- projectName,
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 && input.isTTY && output.isTTY);
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 { load_graph as load_graph6, parseDeliveryFile as parseDeliveryFile2, parseIdeaFile as parseIdeaFile4, parseTaskFile as parseTaskFile10, schedule_next as schedule_next3 } from "@kitsy/coop-core";
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 sortByIdAsc(items) {
4523
- return [...items].sort((a, b) => a.id.localeCompare(b.id));
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 resolvedTrack = resolveContextValueWithSource(options.track, context.track, sharedDefault(root, "track"));
4543
- const resolvedDelivery = resolveContextValueWithSource(options.delivery, context.delivery, sharedDefault(root, "delivery"));
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 readyEntries = options.ready ? schedule_next3(load_graph6(ensureCoopInitialized(root)), {
5031
+ const scoredEntries = sortMode === "score" || options.ready ? schedule_next3(graph, {
4555
5032
  track: resolvedTrack.value,
4556
5033
  delivery: resolvedDelivery.value
4557
- }) : null;
4558
- const readyIds = readyEntries ? new Set(readyEntries.map((entry) => entry.task.id)) : null;
4559
- const readyOrder = readyEntries ? new Map(readyEntries.map((entry, index) => [entry.task.id, index])) : null;
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 (options.version && !(task.fix_versions ?? []).includes(options.version) && !(task.released_in ?? []).includes(options.version)) {
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 = readyOrder ? [...rows].sort((a, b) => (readyOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (readyOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER)) : sortByIdAsc(rows);
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
- ["ID", "Title", "Status", "Priority", "Track", "Assignee", "Delivery", "File"],
4591
- sorted.map((entry) => [
4592
- entry.id,
4593
- entry.title,
4594
- statusColor(entry.status),
4595
- entry.priority,
4596
- entry.track,
4597
- entry.assignee,
4598
- entry.delivery,
4599
- path15.relative(root, entry.filePath)
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 = sortByIdAsc(rows);
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
- ["ID", "Title", "Status", "Priority", "Track", "File"],
4628
- sorted.map((entry) => [
4629
- entry.id,
4630
- entry.title,
4631
- statusColor(entry.status),
4632
- entry.priority,
4633
- entry.track,
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("deliveries").description("List deliveries").action(() => {
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 resolveRepoSafe(start = process.cwd()) {
4676
- try {
4677
- return resolveRepoRoot(start);
4678
- } catch {
4679
- return path16.resolve(start);
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 = resolveRepoSafe(start);
4684
- const workspace = coopWorkspaceDir(root);
4685
- if (fs11.existsSync(workspace)) {
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 as createInterface2 } from "readline/promises";
4798
- import { stdin as input2, stdout as output2 } from "process";
4799
- import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as parseYamlFile2, writeYamlFile as writeYamlFile4 } from "@kitsy/coop-core";
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 = createInterface2({ input: input2, output: output2 });
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 && input2.isTTY && output2.isTTY);
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 = parseYamlFile2(movedConfigPath);
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 output3 = "";
6185
+ let output2 = "";
5642
6186
  if (format === "json") {
5643
- output3 = `${JSON.stringify(payload, null, 2)}
6187
+ output2 = `${JSON.stringify(payload, null, 2)}
5644
6188
  `;
5645
6189
  } else {
5646
- output3 = renderMarkdown(payload);
6190
+ output2 = renderMarkdown(payload);
5647
6191
  }
5648
6192
  if (options.save) {
5649
- fs14.writeFileSync(options.save, output3, "utf8");
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(output3.trimEnd());
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 unique2(items) {
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 = unique2([...source ?? [], ...values ?? []]);
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
- console.log(`Track: ${values.track ?? "-"}`);
6927
- console.log(`Delivery: ${values.delivery ?? "-"}`);
6928
- console.log(`Version: ${values.version ?? "-"}`);
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
- printContext(updateWorkingContext(root, resolveCoopHome(), { track: id }));
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
- printContext(updateWorkingContext(root, resolveCoopHome(), { delivery: id }));
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 output3 = {};
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
- output3[key] = merged;
7965
+ output2[key] = merged;
7362
7966
  }
7363
7967
  }
7364
- return output3;
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 output3 = stringifyFrontmatter6(mergedFrontmatter, mergedBody.output);
7396
- fs21.writeFileSync(oursPath, output3, "utf8");
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();