@kitsy/coop 2.1.2 → 2.2.0

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 +1378 -371
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import fs18 from "fs";
5
- import path24 from "path";
4
+ import fs22 from "fs";
5
+ import path26 from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
  import { Command } from "commander";
8
8
 
@@ -223,6 +223,9 @@ function listTrackFiles(root) {
223
223
  function listDeliveryFiles(root) {
224
224
  return walkFiles(path.join(ensureCoopInitialized(root), "deliveries"), /* @__PURE__ */ new Set([".yml", ".yaml", ".md"]));
225
225
  }
226
+ function loadTasks(root) {
227
+ return listTaskFiles(root).map((filePath) => parseTaskFile(filePath).task);
228
+ }
226
229
  function findTaskFileById(root, id) {
227
230
  const target = `${id}.md`.toLowerCase();
228
231
  const match = listTaskFiles(root).find((filePath) => path.basename(filePath).toLowerCase() === target);
@@ -799,18 +802,18 @@ function registerAliasCommand(program) {
799
802
  }
800
803
 
801
804
  // src/utils/taskflow.ts
802
- import path3 from "path";
805
+ import path5 from "path";
803
806
  import {
804
807
  TaskStatus,
805
808
  check_permission,
806
809
  load_auth_config,
807
810
  load_graph as load_graph2,
808
- parseTaskFile as parseTaskFile4,
811
+ parseTaskFile as parseTaskFile5,
809
812
  run_plugins_for_event as run_plugins_for_event2,
810
813
  schedule_next,
811
814
  transition as transition2,
812
- validateStructural as validateStructural2,
813
- writeTask as writeTask3
815
+ validateStructural as validateStructural3,
816
+ writeTask as writeTask4
814
817
  } from "@kitsy/coop-core";
815
818
 
816
819
  // src/integrations/github.ts
@@ -1383,16 +1386,238 @@ function createGitHubWebhookServer(root, options = {}) {
1383
1386
  return server;
1384
1387
  }
1385
1388
 
1386
- // src/utils/taskflow.ts
1387
- function configDefaultTrack(root) {
1389
+ // src/utils/work-items.ts
1390
+ import fs3 from "fs";
1391
+ import path3 from "path";
1392
+ import {
1393
+ effective_priority,
1394
+ parseDeliveryFile,
1395
+ parseIdeaFile as parseIdeaFile2,
1396
+ parseTaskFile as parseTaskFile4,
1397
+ stringifyFrontmatter as stringifyFrontmatter2,
1398
+ validateStructural as validateStructural2,
1399
+ validateSemantic,
1400
+ writeTask as writeTask3
1401
+ } from "@kitsy/coop-core";
1402
+ function resolveTaskFile(root, idOrAlias) {
1403
+ const reference = resolveReference(root, idOrAlias, "task");
1404
+ return path3.join(root, ...reference.file.split("/"));
1405
+ }
1406
+ function resolveIdeaFile(root, idOrAlias) {
1407
+ const reference = resolveReference(root, idOrAlias, "idea");
1408
+ return path3.join(root, ...reference.file.split("/"));
1409
+ }
1410
+ function loadTaskEntry(root, idOrAlias) {
1411
+ const filePath = resolveTaskFile(root, idOrAlias);
1412
+ return { filePath, parsed: parseTaskFile4(filePath) };
1413
+ }
1414
+ function loadIdeaEntry(root, idOrAlias) {
1415
+ const filePath = resolveIdeaFile(root, idOrAlias);
1416
+ return { filePath, parsed: parseIdeaFile2(filePath) };
1417
+ }
1418
+ function writeIdeaFile(filePath, parsed, idea, body = parsed.body) {
1419
+ const output3 = stringifyFrontmatter2({ ...parsed.raw, ...idea }, body);
1420
+ fs3.writeFileSync(filePath, output3, "utf8");
1421
+ }
1422
+ function validateTaskForWrite(task, filePath) {
1423
+ const structuralIssues = validateStructural2(task, { filePath });
1424
+ const semanticIssues = validateSemantic(task);
1425
+ const errors = [...structuralIssues, ...semanticIssues].filter((issue) => issue.level === "error");
1426
+ if (errors.length > 0) {
1427
+ throw new Error(errors.map((issue) => `- ${issue.message}`).join("\n"));
1428
+ }
1429
+ }
1430
+ function writeTaskEntry(filePath, parsed, task, body = parsed.body) {
1431
+ validateTaskForWrite(task, filePath);
1432
+ writeTask3(task, {
1433
+ body,
1434
+ raw: parsed.raw,
1435
+ filePath
1436
+ });
1437
+ }
1438
+ function appendTaskComment(task, author, body, at = (/* @__PURE__ */ new Date()).toISOString()) {
1439
+ return {
1440
+ ...task,
1441
+ updated: todayIsoDate(),
1442
+ comments: [...task.comments ?? [], { at, author, body }]
1443
+ };
1444
+ }
1445
+ function appendTaskTimeLog(task, actor, kind, hours, note, at = (/* @__PURE__ */ new Date()).toISOString()) {
1446
+ const current = task.time ?? {};
1447
+ return {
1448
+ ...task,
1449
+ updated: todayIsoDate(),
1450
+ time: {
1451
+ ...current,
1452
+ logs: [...current.logs ?? [], { at, actor, kind, hours, note }]
1453
+ }
1454
+ };
1455
+ }
1456
+ function setTaskPlannedHours(task, plannedHours) {
1457
+ return {
1458
+ ...task,
1459
+ updated: todayIsoDate(),
1460
+ time: {
1461
+ ...task.time ?? {},
1462
+ planned_hours: plannedHours
1463
+ }
1464
+ };
1465
+ }
1466
+ function promoteTaskForContext(task, context) {
1467
+ const next = {
1468
+ ...task,
1469
+ updated: todayIsoDate(),
1470
+ fix_versions: [...task.fix_versions ?? []],
1471
+ delivery_tracks: [...task.delivery_tracks ?? []],
1472
+ priority_context: { ...task.priority_context ?? {} }
1473
+ };
1474
+ const scopedTrack = context.track?.trim();
1475
+ if (scopedTrack) {
1476
+ if (task.track?.trim() !== scopedTrack && !next.delivery_tracks?.includes(scopedTrack)) {
1477
+ next.delivery_tracks = [...next.delivery_tracks ?? [], scopedTrack];
1478
+ }
1479
+ next.priority_context = {
1480
+ ...next.priority_context ?? {},
1481
+ [scopedTrack]: "p0"
1482
+ };
1483
+ } else {
1484
+ next.priority = "p0";
1485
+ }
1486
+ const version = context.version?.trim();
1487
+ if (version && !next.fix_versions?.includes(version)) {
1488
+ next.fix_versions = [...next.fix_versions ?? [], version];
1489
+ }
1490
+ return next;
1491
+ }
1492
+ function taskEffectivePriority(task, track) {
1493
+ return effective_priority(task, track);
1494
+ }
1495
+ function resolveDeliveryEntry(root, ref) {
1496
+ const files = listDeliveryFiles(root);
1497
+ const target = ref.trim().toLowerCase();
1498
+ const entries = files.map((filePath) => {
1499
+ const parsed = parseDeliveryFile(filePath);
1500
+ return { filePath, delivery: parsed.delivery, body: parsed.body };
1501
+ });
1502
+ const direct = entries.find((entry) => entry.delivery.id.toLowerCase() === target);
1503
+ if (direct) return direct;
1504
+ const byName = entries.filter((entry) => entry.delivery.name.toLowerCase() === target);
1505
+ if (byName.length === 1) return byName[0];
1506
+ if (byName.length > 1) {
1507
+ throw new Error(`Multiple deliveries match '${ref}'. Use the delivery id.`);
1508
+ }
1509
+ throw new Error(`Delivery '${ref}' not found.`);
1510
+ }
1511
+ function sharedDefault(root, key) {
1388
1512
  const config = readCoopConfig(root).raw;
1389
1513
  const defaults = typeof config.defaults === "object" && config.defaults !== null ? config.defaults : {};
1390
- return typeof defaults.track === "string" && defaults.track.trim() ? defaults.track.trim() : void 0;
1514
+ const value = defaults[key];
1515
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1516
+ }
1517
+
1518
+ // src/utils/working-context.ts
1519
+ import crypto3 from "crypto";
1520
+ import fs4 from "fs";
1521
+ import path4 from "path";
1522
+ function stableKey(root, projectId) {
1523
+ return crypto3.createHash("sha256").update(`${path4.resolve(root)}::${projectId}`).digest("hex").slice(0, 16);
1524
+ }
1525
+ function contextFilePath(root, coopHome) {
1526
+ const identity = readCoopIdentity(root);
1527
+ return path4.join(coopHome, "contexts", `${stableKey(root, identity.id)}.json`);
1528
+ }
1529
+ function readPersistedContext(root, coopHome) {
1530
+ const filePath = contextFilePath(root, coopHome);
1531
+ if (!fs4.existsSync(filePath)) {
1532
+ return null;
1533
+ }
1534
+ try {
1535
+ return JSON.parse(fs4.readFileSync(filePath, "utf8"));
1536
+ } catch {
1537
+ return null;
1538
+ }
1539
+ }
1540
+ function readWorkingContext(root, coopHome) {
1541
+ return readPersistedContext(root, coopHome)?.values ?? {};
1542
+ }
1543
+ function writeWorkingContext(root, coopHome, values) {
1544
+ const identity = readCoopIdentity(root);
1545
+ const filePath = contextFilePath(root, coopHome);
1546
+ fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
1547
+ const normalized = {
1548
+ track: values.track?.trim() || void 0,
1549
+ delivery: values.delivery?.trim() || void 0,
1550
+ version: values.version?.trim() || void 0
1551
+ };
1552
+ const payload = {
1553
+ repo_root: path4.resolve(root),
1554
+ project_id: identity.id,
1555
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
1556
+ values: normalized
1557
+ };
1558
+ fs4.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}
1559
+ `, "utf8");
1560
+ return normalized;
1561
+ }
1562
+ function updateWorkingContext(root, coopHome, patch) {
1563
+ return writeWorkingContext(root, coopHome, { ...readWorkingContext(root, coopHome), ...patch });
1564
+ }
1565
+ function clearWorkingContext(root, coopHome, scope) {
1566
+ if (scope === "all") {
1567
+ const filePath = contextFilePath(root, coopHome);
1568
+ if (fs4.existsSync(filePath)) {
1569
+ fs4.rmSync(filePath, { force: true });
1570
+ }
1571
+ return {};
1572
+ }
1573
+ const next = { ...readWorkingContext(root, coopHome) };
1574
+ delete next[scope];
1575
+ return writeWorkingContext(root, coopHome, next);
1576
+ }
1577
+ function resolveContextValueWithSource(explicit, contextValue, sharedDefault2) {
1578
+ if (explicit?.trim()) {
1579
+ return { value: explicit.trim(), source: "arg" };
1580
+ }
1581
+ if (contextValue?.trim()) {
1582
+ return { value: contextValue.trim(), source: "use" };
1583
+ }
1584
+ if (sharedDefault2?.trim()) {
1585
+ return { value: sharedDefault2.trim(), source: "config" };
1586
+ }
1587
+ return {};
1588
+ }
1589
+ function isVerboseRequested() {
1590
+ return process.argv.includes("--verbose");
1591
+ }
1592
+ function formatResolvedContextMessage(values) {
1593
+ const lines = [];
1594
+ for (const key of ["track", "delivery", "version"]) {
1595
+ const entry = values[key];
1596
+ if (!entry?.value || !entry.source || entry.source === "arg") {
1597
+ continue;
1598
+ }
1599
+ lines.push(`[COOP][context] ${key}=${entry.value} (from ${entry.source === "use" ? "`coop use`" : "config defaults"})`);
1600
+ }
1601
+ return lines;
1602
+ }
1603
+
1604
+ // src/utils/taskflow.ts
1605
+ function configDefaultTrack(root) {
1606
+ return sharedDefault(root, "track");
1607
+ }
1608
+ function configDefaultDelivery(root) {
1609
+ return sharedDefault(root, "delivery");
1610
+ }
1611
+ function configDefaultVersion(root) {
1612
+ return sharedDefault(root, "version");
1391
1613
  }
1392
1614
  function resolveSelectionOptions(root, options) {
1615
+ const context = readWorkingContext(root, resolveCoopHome());
1393
1616
  return {
1394
1617
  ...options,
1395
- track: options.track?.trim() || configDefaultTrack(root)
1618
+ track: options.track?.trim() || context.track?.trim() || configDefaultTrack(root),
1619
+ delivery: options.delivery?.trim() || context.delivery?.trim() || configDefaultDelivery(root),
1620
+ version: options.version?.trim() || context.version?.trim() || configDefaultVersion(root)
1396
1621
  };
1397
1622
  }
1398
1623
  function selectTopReadyTask(root = resolveRepoRoot(), options = {}) {
@@ -1417,8 +1642,9 @@ function formatSelectedTask(entry, selection = {}) {
1417
1642
  const lines = [
1418
1643
  `Selected task: ${entry.task.id}`,
1419
1644
  `Title: ${entry.task.title}`,
1420
- `Priority: ${entry.task.priority ?? "-"}`,
1645
+ `Priority: ${selection.track && entry.task.priority_context?.[selection.track] ? `${entry.task.priority ?? "-"} -> ${entry.task.priority_context[selection.track]}` : entry.task.priority ?? "-"}`,
1421
1646
  `Track: ${entry.task.track ?? "-"}`,
1647
+ `Delivery Tracks: ${entry.task.delivery_tracks && entry.task.delivery_tracks.length > 0 ? entry.task.delivery_tracks.join(", ") : "-"}`,
1422
1648
  `Score: ${entry.score.toFixed(1)}`,
1423
1649
  `Capacity fit: ${entry.fits_capacity ? "yes" : "no"}`,
1424
1650
  `WIP fit: ${entry.fits_wip ? "yes" : "no"}`
@@ -1507,8 +1733,8 @@ async function assignTaskByReference(root, id, options) {
1507
1733
  `Assignee '${assignee}' is not in resource profiles for track '${existing.track ?? "unassigned"}'. Known: ${known.join(", ")}.`
1508
1734
  );
1509
1735
  }
1510
- const filePath = path3.join(root, ...reference.file.split("/"));
1511
- const parsed = parseTaskFile4(filePath);
1736
+ const filePath = path5.join(root, ...reference.file.split("/"));
1737
+ const parsed = parseTaskFile5(filePath);
1512
1738
  const from = parsed.task.assignee ?? null;
1513
1739
  const actor = options.actor?.trim() || user;
1514
1740
  if (from === assignee) {
@@ -1519,13 +1745,13 @@ async function assignTaskByReference(root, id, options) {
1519
1745
  assignee,
1520
1746
  updated: todayIsoDate()
1521
1747
  };
1522
- const structuralIssues = validateStructural2(nextTask, { filePath });
1748
+ const structuralIssues = validateStructural3(nextTask, { filePath });
1523
1749
  if (structuralIssues.length > 0) {
1524
1750
  const errors = structuralIssues.map((issue) => `- ${issue.message}`).join("\n");
1525
1751
  throw new Error(`Updated task is structurally invalid:
1526
1752
  ${errors}`);
1527
1753
  }
1528
- writeTask3(nextTask, {
1754
+ writeTask4(nextTask, {
1529
1755
  body: parsed.body,
1530
1756
  raw: parsed.raw,
1531
1757
  filePath
@@ -1582,8 +1808,8 @@ async function transitionTaskByReference(root, id, status, options) {
1582
1808
  }
1583
1809
  console.warn(`[COOP][auth] override: user '${user}' forced transition_task on '${reference.id}'.`);
1584
1810
  }
1585
- const filePath = path3.join(root, ...reference.file.split("/"));
1586
- const parsed = parseTaskFile4(filePath);
1811
+ const filePath = path5.join(root, ...reference.file.split("/"));
1812
+ const parsed = parseTaskFile5(filePath);
1587
1813
  const result = transition2(parsed.task, target, {
1588
1814
  actor: options.actor ?? user,
1589
1815
  dependencyStatuses: dependencyStatusMapForTask(reference.id, graph)
@@ -1591,13 +1817,13 @@ async function transitionTaskByReference(root, id, status, options) {
1591
1817
  if (!result.success) {
1592
1818
  throw new Error(result.error ?? "Transition failed.");
1593
1819
  }
1594
- const structuralIssues = validateStructural2(result.task, { filePath });
1820
+ const structuralIssues = validateStructural3(result.task, { filePath });
1595
1821
  if (structuralIssues.length > 0) {
1596
1822
  const errors = structuralIssues.map((issue) => `- ${issue.message}`).join("\n");
1597
1823
  throw new Error(`Updated task is structurally invalid:
1598
1824
  ${errors}`);
1599
1825
  }
1600
- writeTask3(result.task, {
1826
+ writeTask4(result.task, {
1601
1827
  body: parsed.body,
1602
1828
  raw: parsed.raw,
1603
1829
  filePath
@@ -1640,6 +1866,30 @@ function registerAssignCommand(program) {
1640
1866
  });
1641
1867
  }
1642
1868
 
1869
+ // src/utils/command-args.ts
1870
+ function resolveOptionalEntityArg(first, second, allowed, fallback) {
1871
+ const normalizedFirst = first.trim().toLowerCase();
1872
+ if (allowed.includes(normalizedFirst) && second?.trim()) {
1873
+ return { entity: normalizedFirst, id: second.trim() };
1874
+ }
1875
+ return { entity: fallback, id: first.trim() };
1876
+ }
1877
+
1878
+ // src/commands/comment.ts
1879
+ function registerCommentCommand(program) {
1880
+ program.command("comment").description("Append a comment to a COOP task").argument("<id-or-type>", "Task id/alias, or the literal `task` followed by an id").argument("[id]", "Task id when an explicit entity type is provided").requiredOption("--message <text>", "Comment text").option("--author <id>", "Comment author").action((first, second, options) => {
1881
+ const { entity, id } = resolveOptionalEntityArg(first, second, ["task"], "task");
1882
+ if (entity !== "task") {
1883
+ throw new Error("Only task comments are supported.");
1884
+ }
1885
+ const root = resolveRepoRoot();
1886
+ const { filePath, parsed } = loadTaskEntry(root, id);
1887
+ const task = appendTaskComment(parsed.task, options.author?.trim() || defaultCoopAuthor(root), options.message.trim());
1888
+ writeTaskEntry(filePath, parsed, task);
1889
+ console.log(`Commented ${task.id}`);
1890
+ });
1891
+ }
1892
+
1643
1893
  // src/commands/config.ts
1644
1894
  var INDEX_DATA_KEY = "index.data";
1645
1895
  var ID_NAMING_KEY = "id.naming";
@@ -1874,21 +2124,21 @@ function registerConfigCommand(program) {
1874
2124
  }
1875
2125
 
1876
2126
  // src/commands/create.ts
1877
- import fs5 from "fs";
1878
- import path6 from "path";
2127
+ import fs7 from "fs";
2128
+ import path8 from "path";
1879
2129
  import {
1880
2130
  DeliveryStatus,
1881
2131
  IdeaStatus as IdeaStatus2,
1882
- parseIdeaFile as parseIdeaFile2,
2132
+ parseIdeaFile as parseIdeaFile3,
1883
2133
  TaskStatus as TaskStatus3,
1884
2134
  TaskType as TaskType2,
1885
2135
  check_permission as check_permission2,
1886
2136
  load_auth_config as load_auth_config2,
1887
- parseTaskFile as parseTaskFile6,
2137
+ parseTaskFile as parseTaskFile7,
1888
2138
  writeYamlFile as writeYamlFile3,
1889
- stringifyFrontmatter as stringifyFrontmatter3,
1890
- validateStructural as validateStructural4,
1891
- writeTask as writeTask5
2139
+ stringifyFrontmatter as stringifyFrontmatter4,
2140
+ validateStructural as validateStructural5,
2141
+ writeTask as writeTask6
1892
2142
  } from "@kitsy/coop-core";
1893
2143
  import { create_provider_idea_decomposer, decompose_idea_to_tasks } from "@kitsy/coop-ai";
1894
2144
 
@@ -1907,13 +2157,13 @@ async function ask(question, defaultValue = "") {
1907
2157
  }
1908
2158
 
1909
2159
  // src/utils/idea-drafts.ts
1910
- import fs3 from "fs";
1911
- import path4 from "path";
2160
+ import fs5 from "fs";
2161
+ import path6 from "path";
1912
2162
  import {
1913
2163
  IdeaStatus,
1914
2164
  parseFrontmatterContent,
1915
2165
  parseYamlContent,
1916
- stringifyFrontmatter as stringifyFrontmatter2
2166
+ stringifyFrontmatter as stringifyFrontmatter3
1917
2167
  } from "@kitsy/coop-core";
1918
2168
  function asUniqueStrings(value) {
1919
2169
  if (!Array.isArray(value)) return void 0;
@@ -1957,7 +2207,7 @@ function parseIdeaDraftInput(content, source) {
1957
2207
  return parseIdeaDraftObject(parseYamlContent(content, source), source);
1958
2208
  }
1959
2209
  function writeIdeaFromDraft(root, projectDir, draft) {
1960
- const existingIds = listIdeaFiles(root).map((filePath2) => path4.basename(filePath2, ".md"));
2210
+ const existingIds = listIdeaFiles(root).map((filePath2) => path6.basename(filePath2, ".md"));
1961
2211
  const id = draft.id?.trim()?.toUpperCase() || generateConfiguredId(root, existingIds, {
1962
2212
  entityType: "idea",
1963
2213
  title: draft.title,
@@ -1978,25 +2228,25 @@ function writeIdeaFromDraft(root, projectDir, draft) {
1978
2228
  source: draft.source ?? "manual",
1979
2229
  linked_tasks: draft.linked_tasks ?? []
1980
2230
  };
1981
- const filePath = path4.join(projectDir, "ideas", `${id}.md`);
1982
- if (fs3.existsSync(filePath)) {
2231
+ const filePath = path6.join(projectDir, "ideas", `${id}.md`);
2232
+ if (fs5.existsSync(filePath)) {
1983
2233
  throw new Error(`Idea '${id}' already exists.`);
1984
2234
  }
1985
- fs3.writeFileSync(filePath, stringifyFrontmatter2(frontmatter, draft.body ?? ""), "utf8");
2235
+ fs5.writeFileSync(filePath, stringifyFrontmatter3(frontmatter, draft.body ?? ""), "utf8");
1986
2236
  return filePath;
1987
2237
  }
1988
2238
 
1989
2239
  // src/utils/refinement-drafts.ts
1990
- import fs4 from "fs";
1991
- import path5 from "path";
2240
+ import fs6 from "fs";
2241
+ import path7 from "path";
1992
2242
  import {
1993
2243
  IndexManager,
1994
2244
  parseFrontmatterContent as parseFrontmatterContent2,
1995
- parseTaskFile as parseTaskFile5,
2245
+ parseTaskFile as parseTaskFile6,
1996
2246
  parseYamlContent as parseYamlContent2,
1997
2247
  stringifyYamlContent,
1998
- validateStructural as validateStructural3,
1999
- writeTask as writeTask4
2248
+ validateStructural as validateStructural4,
2249
+ writeTask as writeTask5
2000
2250
  } from "@kitsy/coop-core";
2001
2251
 
2002
2252
  // src/utils/stdin.ts
@@ -2023,16 +2273,16 @@ function nonEmptyStrings(value) {
2023
2273
  return entries.length > 0 ? entries : void 0;
2024
2274
  }
2025
2275
  function refinementDir(projectDir) {
2026
- const dir = path5.join(projectDir, "tmp", "refinements");
2027
- fs4.mkdirSync(dir, { recursive: true });
2276
+ const dir = path7.join(projectDir, "tmp", "refinements");
2277
+ fs6.mkdirSync(dir, { recursive: true });
2028
2278
  return dir;
2029
2279
  }
2030
2280
  function draftPath(projectDir, mode, sourceId) {
2031
2281
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2032
- return path5.join(refinementDir(projectDir), `${mode}-${sourceId}-${stamp}.yml`);
2282
+ return path7.join(refinementDir(projectDir), `${mode}-${sourceId}-${stamp}.yml`);
2033
2283
  }
2034
2284
  function assignCreateProposalIds(root, draft) {
2035
- const existingIds = listTaskFiles(root).map((filePath) => path5.basename(filePath, ".md"));
2285
+ const existingIds = listTaskFiles(root).map((filePath) => path7.basename(filePath, ".md"));
2036
2286
  const createdIds = [];
2037
2287
  const proposals = draft.proposals.map((proposal) => {
2038
2288
  if (proposal.action !== "create") {
@@ -2061,13 +2311,13 @@ function assignCreateProposalIds(root, draft) {
2061
2311
  };
2062
2312
  }
2063
2313
  function writeDraftFile(root, projectDir, draft, outputFile) {
2064
- const filePath = outputFile?.trim() ? path5.resolve(root, outputFile.trim()) : draftPath(projectDir, draft.mode, draft.source.id);
2065
- fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
2066
- fs4.writeFileSync(filePath, stringifyYamlContent(draft), "utf8");
2314
+ const filePath = outputFile?.trim() ? path7.resolve(root, outputFile.trim()) : draftPath(projectDir, draft.mode, draft.source.id);
2315
+ fs6.mkdirSync(path7.dirname(filePath), { recursive: true });
2316
+ fs6.writeFileSync(filePath, stringifyYamlContent(draft), "utf8");
2067
2317
  return filePath;
2068
2318
  }
2069
2319
  function printDraftSummary(root, draft, filePath) {
2070
- console.log(`[COOP] refinement draft created: ${path5.relative(root, filePath)}`);
2320
+ console.log(`[COOP] refinement draft created: ${path7.relative(root, filePath)}`);
2071
2321
  console.log(`[COOP] source: ${draft.source.entity_type} ${draft.source.id}`);
2072
2322
  console.log(`[COOP] summary: ${draft.summary}`);
2073
2323
  for (const proposal of draft.proposals) {
@@ -2076,7 +2326,7 @@ function printDraftSummary(root, draft, filePath) {
2076
2326
  `- ${proposal.action.toUpperCase()} ${target ?? "(pending-id)"} | ${proposal.title} | ${proposal.type ?? "feature"} | ${proposal.priority ?? "p2"}`
2077
2327
  );
2078
2328
  }
2079
- console.log(`[COOP] apply with: coop apply draft --from-file ${path5.relative(root, filePath)}`);
2329
+ console.log(`[COOP] apply with: coop apply draft --from-file ${path7.relative(root, filePath)}`);
2080
2330
  }
2081
2331
  function parseRefinementDraftInput(content, source) {
2082
2332
  const parsed = parseYamlContent2(content, source);
@@ -2160,16 +2410,16 @@ function applyCreateProposal(projectDir, proposal) {
2160
2410
  if (!id) {
2161
2411
  throw new Error(`Create proposal '${proposal.title}' is missing id.`);
2162
2412
  }
2163
- const filePath = path5.join(projectDir, "tasks", `${id}.md`);
2164
- if (fs4.existsSync(filePath)) {
2413
+ const filePath = path7.join(projectDir, "tasks", `${id}.md`);
2414
+ if (fs6.existsSync(filePath)) {
2165
2415
  throw new Error(`Task '${id}' already exists.`);
2166
2416
  }
2167
2417
  const task = taskFromProposal({ ...proposal, id }, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2168
- const issues = validateStructural3(task, { filePath });
2418
+ const issues = validateStructural4(task, { filePath });
2169
2419
  if (issues.length > 0) {
2170
2420
  throw new Error(issues.map((issue) => issue.message).join(" | "));
2171
2421
  }
2172
- writeTask4(task, { body: proposal.body ?? "", filePath });
2422
+ writeTask5(task, { body: proposal.body ?? "", filePath });
2173
2423
  return filePath;
2174
2424
  }
2175
2425
  function applyUpdateProposal(root, proposal) {
@@ -2178,7 +2428,7 @@ function applyUpdateProposal(root, proposal) {
2178
2428
  throw new Error(`Update proposal '${proposal.title}' is missing target_id.`);
2179
2429
  }
2180
2430
  const filePath = findTaskFileById(root, targetId);
2181
- const parsed = parseTaskFile5(filePath);
2431
+ const parsed = parseTaskFile6(filePath);
2182
2432
  const nextTask = {
2183
2433
  ...parsed.task,
2184
2434
  title: proposal.title || parsed.task.title,
@@ -2196,11 +2446,11 @@ function applyUpdateProposal(root, proposal) {
2196
2446
  derived_refs: proposal.derived_refs ?? parsed.task.origin?.derived_refs
2197
2447
  } : parsed.task.origin
2198
2448
  };
2199
- const issues = validateStructural3(nextTask, { filePath });
2449
+ const issues = validateStructural4(nextTask, { filePath });
2200
2450
  if (issues.length > 0) {
2201
2451
  throw new Error(issues.map((issue) => issue.message).join(" | "));
2202
2452
  }
2203
- writeTask4(nextTask, {
2453
+ writeTask5(nextTask, {
2204
2454
  body: proposal.body ?? parsed.body,
2205
2455
  raw: parsed.raw,
2206
2456
  filePath
@@ -2222,8 +2472,8 @@ function applyRefinementDraft(root, projectDir, draft) {
2222
2472
  }
2223
2473
  async function readDraftContent(root, options) {
2224
2474
  if (options.fromFile?.trim()) {
2225
- const filePath = path5.resolve(root, options.fromFile.trim());
2226
- return { content: fs4.readFileSync(filePath, "utf8"), source: filePath };
2475
+ const filePath = path7.resolve(root, options.fromFile.trim());
2476
+ return { content: fs6.readFileSync(filePath, "utf8"), source: filePath };
2227
2477
  }
2228
2478
  if (options.stdin) {
2229
2479
  return { content: await readStdinText(), source: "<stdin>" };
@@ -2318,9 +2568,9 @@ function plusDaysIso(days) {
2318
2568
  function unique(values) {
2319
2569
  return Array.from(new Set(values));
2320
2570
  }
2321
- function resolveIdeaFile(root, idOrAlias) {
2571
+ function resolveIdeaFile2(root, idOrAlias) {
2322
2572
  const target = resolveReference(root, idOrAlias, "idea");
2323
- return path6.join(root, ...target.file.split("/"));
2573
+ return path8.join(root, ...target.file.split("/"));
2324
2574
  }
2325
2575
  function updateIdeaLinkedTasks(filePath, idea, raw, body, linked) {
2326
2576
  const next = unique([...idea.linked_tasks ?? [], ...linked]).sort((a, b) => a.localeCompare(b));
@@ -2328,7 +2578,7 @@ function updateIdeaLinkedTasks(filePath, idea, raw, body, linked) {
2328
2578
  ...raw,
2329
2579
  linked_tasks: next
2330
2580
  };
2331
- fs5.writeFileSync(filePath, stringifyFrontmatter3(nextRaw, body), "utf8");
2581
+ fs7.writeFileSync(filePath, stringifyFrontmatter4(nextRaw, body), "utf8");
2332
2582
  }
2333
2583
  function makeTaskDraft(input3) {
2334
2584
  return {
@@ -2365,7 +2615,7 @@ function registerCreateCommand(program) {
2365
2615
  const written = applyRefinementDraft(root, coop, parsedDraft);
2366
2616
  console.log(`[COOP] created ${written.length} task file(s) from ${draftInput.source}`);
2367
2617
  for (const filePath of written) {
2368
- console.log(`Created task: ${path6.relative(root, filePath)}`);
2618
+ console.log(`Created task: ${path8.relative(root, filePath)}`);
2369
2619
  }
2370
2620
  return;
2371
2621
  }
@@ -2383,8 +2633,8 @@ function registerCreateCommand(program) {
2383
2633
  let sourceIdeaPath = null;
2384
2634
  let sourceIdeaParsed = null;
2385
2635
  if (options.from?.trim()) {
2386
- sourceIdeaPath = resolveIdeaFile(root, options.from.trim());
2387
- sourceIdeaParsed = parseIdeaFile2(sourceIdeaPath);
2636
+ sourceIdeaPath = resolveIdeaFile2(root, options.from.trim());
2637
+ sourceIdeaParsed = parseIdeaFile3(sourceIdeaPath);
2388
2638
  if (options.ai) {
2389
2639
  const providerDecomposer = create_provider_idea_decomposer(readCoopConfig(root).raw);
2390
2640
  const aiDrafts = await decompose_idea_to_tasks({
@@ -2456,7 +2706,7 @@ function registerCreateCommand(program) {
2456
2706
  if (options.id && drafts.length > 1) {
2457
2707
  throw new Error("Cannot combine --id with multi-task creation. Remove --id or disable --ai decomposition.");
2458
2708
  }
2459
- const existingIds = listTaskFiles(root).map((filePath) => path6.basename(filePath, ".md"));
2709
+ const existingIds = listTaskFiles(root).map((filePath) => path8.basename(filePath, ".md"));
2460
2710
  const createdIds = [];
2461
2711
  for (let index = 0; index < drafts.length; index += 1) {
2462
2712
  const draft = drafts[index];
@@ -2491,19 +2741,19 @@ function registerCreateCommand(program) {
2491
2741
  promoted_from: options.from?.trim() ? [sourceIdeaParsed?.idea.id ?? options.from.trim()] : void 0
2492
2742
  } : void 0
2493
2743
  };
2494
- const filePath = path6.join(coop, "tasks", `${id}.md`);
2495
- const structuralIssues = validateStructural4(task, { filePath });
2744
+ const filePath = path8.join(coop, "tasks", `${id}.md`);
2745
+ const structuralIssues = validateStructural5(task, { filePath });
2496
2746
  if (structuralIssues.length > 0) {
2497
2747
  const message = structuralIssues.map((issue) => `- ${issue.message}`).join("\n");
2498
2748
  throw new Error(`Task failed structural validation:
2499
2749
  ${message}`);
2500
2750
  }
2501
- writeTask5(task, {
2751
+ writeTask6(task, {
2502
2752
  body: draft.body,
2503
2753
  filePath
2504
2754
  });
2505
2755
  createdIds.push(id);
2506
- console.log(`Created task: ${path6.relative(root, filePath)}`);
2756
+ console.log(`Created task: ${path8.relative(root, filePath)}`);
2507
2757
  }
2508
2758
  if (sourceIdeaPath && sourceIdeaParsed && createdIds.length > 0) {
2509
2759
  updateIdeaLinkedTasks(
@@ -2530,7 +2780,7 @@ ${message}`);
2530
2780
  });
2531
2781
  const written = writeIdeaFromDraft(root, coop, parseIdeaDraftInput(draftInput.content, draftInput.source));
2532
2782
  console.log(`[COOP] created 1 idea file from ${draftInput.source}`);
2533
- console.log(`Created idea: ${path6.relative(root, written)}`);
2783
+ console.log(`Created idea: ${path8.relative(root, written)}`);
2534
2784
  return;
2535
2785
  }
2536
2786
  const title = options.title?.trim() || titleArg?.trim() || await ask("Idea title");
@@ -2540,7 +2790,7 @@ ${message}`);
2540
2790
  const status = (options.status?.trim() || (interactive ? await ask("Idea status", "captured") : "captured")).toLowerCase();
2541
2791
  const tags = options.tags ? parseCsv(options.tags) : interactive ? parseCsv(await ask("Tags (comma-separated)", "")) : [];
2542
2792
  const body = options.body ?? (interactive ? await ask("Idea body (optional)", "") : "");
2543
- const existingIds = listIdeaFiles(root).map((filePath2) => path6.basename(filePath2, ".md"));
2793
+ const existingIds = listIdeaFiles(root).map((filePath2) => path8.basename(filePath2, ".md"));
2544
2794
  const id = options.id?.trim()?.toUpperCase() || generateConfiguredId(root, existingIds, {
2545
2795
  entityType: "idea",
2546
2796
  title,
@@ -2564,9 +2814,9 @@ ${message}`);
2564
2814
  if (!Object.values(IdeaStatus2).includes(status)) {
2565
2815
  throw new Error(`Invalid idea status '${status}'.`);
2566
2816
  }
2567
- const filePath = path6.join(coop, "ideas", `${id}.md`);
2568
- fs5.writeFileSync(filePath, stringifyFrontmatter3(frontmatter, body), "utf8");
2569
- console.log(`Created idea: ${path6.relative(root, filePath)}`);
2817
+ const filePath = path8.join(coop, "ideas", `${id}.md`);
2818
+ fs7.writeFileSync(filePath, stringifyFrontmatter4(frontmatter, body), "utf8");
2819
+ console.log(`Created idea: ${path8.relative(root, filePath)}`);
2570
2820
  });
2571
2821
  create.command("track").description("Create a track").argument("[name]", "Track name").option("--id <id>", "Track id").option("--name <name>", "Track name").option("--profiles <profiles>", "Comma-separated capacity profile ids").option("--max-wip <number>", "Max concurrent tasks").option("--allowed-types <types>", "Comma-separated allowed task types").option("--interactive", "Prompt for optional fields").action(async (nameArg, options) => {
2572
2822
  const root = resolveRepoRoot();
@@ -2592,7 +2842,7 @@ ${message}`);
2592
2842
  throw new Error(`Invalid task type in allowed-types: '${type}'.`);
2593
2843
  }
2594
2844
  }
2595
- const existingIds = listTrackFiles(root).map((filePath2) => path6.basename(filePath2).replace(/\.(yml|yaml)$/i, ""));
2845
+ const existingIds = listTrackFiles(root).map((filePath2) => path8.basename(filePath2).replace(/\.(yml|yaml)$/i, ""));
2596
2846
  const config = readCoopConfig(root);
2597
2847
  const idPrefixesRaw = typeof config.raw.id_prefixes === "object" && config.raw.id_prefixes !== null ? config.raw.id_prefixes : {};
2598
2848
  const prefix = typeof idPrefixesRaw.track === "string" ? idPrefixesRaw.track : "TRK";
@@ -2613,9 +2863,9 @@ ${message}`);
2613
2863
  allowed_types: allowed
2614
2864
  }
2615
2865
  };
2616
- const filePath = path6.join(coop, "tracks", `${id}.yml`);
2866
+ const filePath = path8.join(coop, "tracks", `${id}.yml`);
2617
2867
  writeYamlFile3(filePath, payload);
2618
- console.log(`Created track: ${path6.relative(root, filePath)}`);
2868
+ console.log(`Created track: ${path8.relative(root, filePath)}`);
2619
2869
  });
2620
2870
  create.command("delivery").description("Create a delivery").argument("[name]", "Delivery name").option("--id <id>", "Delivery id").option("--name <name>", "Delivery name").option("--status <status>", `Delivery status (${Object.values(DeliveryStatus).join(", ")})`).option("--commit", "Create delivery directly in committed state").option("--target-date <date>", "Target date (YYYY-MM-DD)").option("--budget-hours <hours>", "Engineering budget hours").option("--budget-cost <usd>", "Cost budget in USD").option("--profiles <profiles>", "Comma-separated capacity profile ids").option("--scope <ids>", "Comma-separated task ids to include in scope").option("--exclude <ids>", "Comma-separated task ids to exclude from scope").option("--user <user>", "Current user for advisory authorization checks").option("--force", "Override advisory authorization checks").option("--interactive", "Prompt for optional fields").action(async (nameArg, options) => {
2621
2871
  const root = resolveRepoRoot();
@@ -2668,7 +2918,7 @@ ${message}`);
2668
2918
  options.profiles ? parseCsv(options.profiles) : interactive ? parseCsv(await ask("Capacity profiles (comma-separated)", "backend_team")) : ["backend_team"]
2669
2919
  );
2670
2920
  const tasks = listTaskFiles(root).map((filePath2) => {
2671
- const parsed = parseTaskFile6(filePath2).task;
2921
+ const parsed = parseTaskFile7(filePath2).task;
2672
2922
  return { id: parsed.id, title: parsed.title };
2673
2923
  });
2674
2924
  if (interactive && tasks.length > 0) {
@@ -2697,7 +2947,7 @@ ${message}`);
2697
2947
  }
2698
2948
  }
2699
2949
  const existingIds = listDeliveryFiles(root).map(
2700
- (filePath2) => path6.basename(filePath2).replace(/\.(yml|yaml|md)$/i, "")
2950
+ (filePath2) => path8.basename(filePath2).replace(/\.(yml|yaml|md)$/i, "")
2701
2951
  );
2702
2952
  const idPrefixesRaw = typeof config.raw.id_prefixes === "object" && config.raw.id_prefixes !== null ? config.raw.id_prefixes : {};
2703
2953
  const prefix = typeof idPrefixesRaw.delivery === "string" ? idPrefixesRaw.delivery : "DEL";
@@ -2726,9 +2976,66 @@ ${message}`);
2726
2976
  exclude: scopeExclude
2727
2977
  }
2728
2978
  };
2729
- const filePath = path6.join(coop, "deliveries", `${id}.yml`);
2979
+ const filePath = path8.join(coop, "deliveries", `${id}.yml`);
2730
2980
  writeYamlFile3(filePath, payload);
2731
- console.log(`Created delivery: ${path6.relative(root, filePath)}`);
2981
+ console.log(`Created delivery: ${path8.relative(root, filePath)}`);
2982
+ });
2983
+ }
2984
+
2985
+ // src/commands/current.ts
2986
+ function registerCurrentCommand(program) {
2987
+ program.command("current").description("Show active project, working context, my in-progress tasks, and the next ready task").action(() => {
2988
+ const root = resolveRepoRoot();
2989
+ const identity = readCoopIdentity(root);
2990
+ const context = readWorkingContext(root, resolveCoopHome());
2991
+ const actor = defaultCoopAuthor(root);
2992
+ const inProgress = loadTasks(root).filter(
2993
+ (task) => task.assignee === actor && (task.status === "in_progress" || task.status === "in_review")
2994
+ );
2995
+ console.log(`Project: ${identity.name} (${identity.id})`);
2996
+ console.log(`Actor: ${actor}`);
2997
+ console.log(`Track: ${context.track ?? "-"}`);
2998
+ console.log(`Delivery: ${context.delivery ?? "-"}`);
2999
+ console.log(`Version: ${context.version ?? "-"}`);
3000
+ console.log("");
3001
+ console.log("My Active Tasks:");
3002
+ if (inProgress.length === 0) {
3003
+ console.log("- none");
3004
+ } else {
3005
+ for (const task of inProgress) {
3006
+ console.log(`- ${task.id} [${task.status}] ${task.title}`);
3007
+ }
3008
+ }
3009
+ console.log("");
3010
+ console.log("Next Ready:");
3011
+ try {
3012
+ const selected = selectTopReadyTask(root, {
3013
+ track: context.track,
3014
+ delivery: context.delivery,
3015
+ version: context.version
3016
+ });
3017
+ console.log(formatSelectedTask(selected.entry, selected.selection));
3018
+ } catch (error) {
3019
+ console.log(error instanceof Error ? error.message : String(error));
3020
+ }
3021
+ });
3022
+ }
3023
+
3024
+ // src/commands/deps.ts
3025
+ import { load_graph as load_graph3 } from "@kitsy/coop-core";
3026
+ function registerDepsCommand(program) {
3027
+ program.command("deps").description("Show dependencies and reverse dependencies for a task").argument("<id>", "Task id or alias").action((id) => {
3028
+ const root = resolveRepoRoot();
3029
+ const graph = load_graph3(coopDir(root));
3030
+ const reference = resolveReference(root, id, "task");
3031
+ const task = graph.nodes.get(reference.id);
3032
+ if (!task) {
3033
+ throw new Error(`Task '${reference.id}' not found.`);
3034
+ }
3035
+ 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(", ") : "-"}`);
2732
3039
  });
2733
3040
  }
2734
3041
 
@@ -2737,7 +3044,8 @@ import chalk from "chalk";
2737
3044
  import {
2738
3045
  compute_critical_path,
2739
3046
  compute_readiness_with_corrections,
2740
- load_graph as load_graph3,
3047
+ effective_priority as effective_priority2,
3048
+ load_graph as load_graph4,
2741
3049
  schedule_next as schedule_next2,
2742
3050
  topological_sort,
2743
3051
  validate_graph
@@ -2790,7 +3098,7 @@ function renderAsciiDag(tasks, order) {
2790
3098
  }
2791
3099
  function runValidate() {
2792
3100
  const root = resolveRepoRoot();
2793
- const graph = load_graph3(coopDir(root));
3101
+ const graph = load_graph4(coopDir(root));
2794
3102
  const issues = validate_graph(graph);
2795
3103
  if (issues.length === 0) {
2796
3104
  console.log(chalk.green("Graph is healthy. No invariant violations found."));
@@ -2805,12 +3113,20 @@ function runValidate() {
2805
3113
  }
2806
3114
  function runNext(options) {
2807
3115
  const root = resolveRepoRoot();
2808
- const graph = load_graph3(coopDir(root));
3116
+ const context = readWorkingContext(root, resolveCoopHome());
3117
+ const resolvedTrack = resolveContextValueWithSource(options.track, context.track, sharedDefault(root, "track"));
3118
+ const resolvedDelivery = resolveContextValueWithSource(options.delivery, context.delivery, sharedDefault(root, "delivery"));
3119
+ if (isVerboseRequested()) {
3120
+ for (const line of formatResolvedContextMessage({ track: resolvedTrack, delivery: resolvedDelivery })) {
3121
+ console.log(line);
3122
+ }
3123
+ }
3124
+ const graph = load_graph4(coopDir(root));
2809
3125
  const readiness = compute_readiness_with_corrections(graph);
2810
3126
  const limit = options.limit && options.limit.trim().length > 0 ? Number(options.limit) : void 0;
2811
3127
  const ready = schedule_next2(graph, {
2812
- track: options.track,
2813
- delivery: options.delivery,
3128
+ track: resolvedTrack.value,
3129
+ delivery: resolvedDelivery.value,
2814
3130
  executor: options.executor,
2815
3131
  today: options.today,
2816
3132
  limit: Number.isInteger(limit) && Number(limit) > 0 ? Number(limit) : void 0
@@ -2824,7 +3140,7 @@ function runNext(options) {
2824
3140
  ready.map((entry) => [
2825
3141
  entry.task.id,
2826
3142
  entry.task.title,
2827
- entry.task.priority ?? "-",
3143
+ effective_priority2(entry.task, resolvedTrack.value),
2828
3144
  entry.task.track ?? "-",
2829
3145
  entry.score.toFixed(1),
2830
3146
  entry.fits_capacity ? chalk.green("yes") : chalk.yellow("no"),
@@ -2854,13 +3170,13 @@ Warnings (${readiness.warnings.length}):`);
2854
3170
  }
2855
3171
  function runShow() {
2856
3172
  const root = resolveRepoRoot();
2857
- const graph = load_graph3(coopDir(root));
3173
+ const graph = load_graph4(coopDir(root));
2858
3174
  const order = topological_sort(graph);
2859
3175
  console.log(renderAsciiDag(graph.nodes, order));
2860
3176
  }
2861
3177
  function runCriticalPath(deliveryName) {
2862
3178
  const root = resolveRepoRoot();
2863
- const graph = load_graph3(coopDir(root));
3179
+ const graph = load_graph4(coopDir(root));
2864
3180
  const delivery = resolveDelivery(graph, deliveryName);
2865
3181
  const result = compute_critical_path(delivery, graph);
2866
3182
  console.log(`Critical Path: ${delivery.name} (${delivery.id})`);
@@ -2899,13 +3215,15 @@ function registerGraphCommand(program) {
2899
3215
  }
2900
3216
 
2901
3217
  // src/utils/ai-help.ts
2902
- import path7 from "path";
3218
+ import path9 from "path";
2903
3219
  var catalog = {
2904
3220
  purpose: "COOP is a Git-native planning, backlog, execution, and orchestration CLI. It stores canonical data in .coop/projects/<project.id>/ and should be treated as the source of truth for work selection and lifecycle state.",
2905
3221
  selection_rules: [
2906
3222
  "Use `coop project show` first to confirm the active workspace and project.",
3223
+ "Use `coop use show` to inspect the current user-local working defaults for track, delivery, and version.",
2907
3224
  "Use `coop graph next --delivery <delivery>` or `coop next task` to choose work. Do not reprioritize outside COOP unless the user explicitly overrides it.",
2908
- "Use `coop show task <id>` before implementation to read acceptance, tests_required, dependencies, and origin refs.",
3225
+ "Commands resolve selection scope from: explicit CLI arg, then `coop use` working context, then shared project defaults.",
3226
+ "Use `coop show <id>` or `coop show task <id>` before implementation to read acceptance, tests_required, dependencies, origin refs, and task metadata.",
2909
3227
  "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."
2910
3228
  ],
2911
3229
  workspace_rules: [
@@ -2955,6 +3273,11 @@ var catalog = {
2955
3273
  { usage: "coop project list", purpose: "List projects in the current workspace." },
2956
3274
  { usage: "coop project show", purpose: "Show the active project id, name, path, and layout." },
2957
3275
  { usage: "coop project use <id>", purpose: "Switch the active project in a multi-project workspace." },
3276
+ { usage: "coop use show", purpose: "Show the user-local working defaults for track, delivery, and version." },
3277
+ { usage: "coop use track <id>", purpose: "Set the default working track for commands that can infer scope." },
3278
+ { usage: "coop use delivery <id>", purpose: "Set the default working delivery for commands that need delivery scope." },
3279
+ { usage: "coop use version <id>", purpose: "Set the default working version for promotion and prompt generation." },
3280
+ { usage: "coop current", purpose: "Show the active project, working context, my active tasks, and the next ready task." },
2958
3281
  { usage: "coop naming", purpose: "Explain the current naming template, tokens, and examples." },
2959
3282
  { usage: 'coop naming preview "Natural-language COOP command recommender"', purpose: "Preview a semantic ID before creating an item." }
2960
3283
  ]
@@ -2993,8 +3316,10 @@ var catalog = {
2993
3316
  commands: [
2994
3317
  { usage: "coop next task", purpose: "Show the top ready task using the default track or full workspace context." },
2995
3318
  { usage: "coop graph next --delivery MVP", purpose: "Show the ready queue for a delivery with scores and blockers." },
3319
+ { usage: "coop pick task PM-101 --promote --claim --actor dev1 --user lead-user", purpose: "Select a specific task, optionally promote it in the current context, assign it, and move it to in_progress." },
2996
3320
  { usage: "coop pick task --delivery MVP --claim --actor dev1 --user lead-user", purpose: "Select the top ready task, optionally assign it, and move it to in_progress." },
2997
- { usage: "coop start task PM-101 --claim --actor dev1 --user lead-user", purpose: "Start a specific task or the top ready task if no id is provided." },
3321
+ { usage: "coop start task PM-101 --promote --claim --actor dev1 --user lead-user", purpose: "Start a specific task or the top ready task if no id is provided." },
3322
+ { usage: "coop promote task PM-101", purpose: "Promote a task using the current working track/version context." },
2998
3323
  { usage: "coop review task PM-101", purpose: "Move an in-progress task into in_review using a DX-friendly verb." },
2999
3324
  { usage: "coop complete task PM-101", purpose: "Move a task in review into done using a DX-friendly verb." },
3000
3325
  { usage: "coop block task PM-101", purpose: "Mark a task as blocked." },
@@ -3008,8 +3333,18 @@ var catalog = {
3008
3333
  description: "Read backlog state, task details, and planning output.",
3009
3334
  commands: [
3010
3335
  { 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." },
3337
+ { usage: "coop list tasks --mine", purpose: "List tasks assigned to the current default COOP author." },
3338
+ { usage: 'coop search "auth and login form"', purpose: "Run deterministic non-AI search across tasks, ideas, and deliveries." },
3339
+ { usage: 'coop search "auth" --open', purpose: "Require a single match and print the resolved summary row." },
3340
+ { usage: "coop show PM-101", purpose: "Resolve a task, idea, or delivery by reference without an extra entity noun." },
3011
3341
  { usage: "coop show task PM-101", purpose: "Show a task with acceptance, tests_required, refs, and runbook sections." },
3012
3342
  { usage: "coop show idea IDEA-101", purpose: "Show an idea." },
3343
+ { usage: "coop deps PM-101", purpose: "Show task dependencies and reverse dependencies." },
3344
+ { usage: "coop prompt PM-101 --format markdown", purpose: "Generate a manual handoff prompt from a task and current working context." },
3345
+ { usage: "coop update PM-101 --priority p1 --add-fix-version v2", purpose: "Update task metadata without editing `.coop` files directly." },
3346
+ { usage: 'coop comment PM-101 --message "Needs API review"', purpose: "Append a comment to a task." },
3347
+ { usage: 'coop log-time PM-101 --hours 2 --kind worked --note "pairing"', purpose: "Append a planned or worked time log to a task." },
3013
3348
  { usage: "coop plan delivery MVP", purpose: "Run delivery feasibility analysis." },
3014
3349
  { usage: "coop plan delivery MVP --monte-carlo --iterations 5000", purpose: "Run probabilistic delivery forecasting." },
3015
3350
  { usage: "coop view velocity", purpose: "Show historical throughput." },
@@ -3052,6 +3387,7 @@ var catalog = {
3052
3387
  execution_model: [
3053
3388
  "Agents or services may send drafts through files or stdin, but COOP owns canonical writes.",
3054
3389
  "Use `coop create ... --from-file|--stdin` and `coop apply draft` instead of editing `.coop` task or idea files directly.",
3390
+ "Use `coop update`, `coop comment`, and `coop log-time` for task mutations instead of manually editing task files.",
3055
3391
  "Use `coop log --last --verbose` when command execution fails and the concise error points to a stack trace.",
3056
3392
  "If a workflow depends on stable human-readable IDs, inspect `coop naming` or `coop config id.naming` before creating items."
3057
3393
  ],
@@ -3168,7 +3504,7 @@ function renderAiHelpTopic(format, topic) {
3168
3504
  `;
3169
3505
  }
3170
3506
  function normalizeRepoPath(repoPath) {
3171
- return repoPath ? path7.resolve(repoPath) : process.cwd();
3507
+ return repoPath ? path9.resolve(repoPath) : process.cwd();
3172
3508
  }
3173
3509
  function formatSelectionCommand(commandName, delivery, track) {
3174
3510
  if (delivery) {
@@ -3209,12 +3545,15 @@ function renderInitialPrompt(options = {}) {
3209
3545
  `- If you are unsure about lifecycle changes, run \`${commandName} help-ai --state-transitions --format markdown\`.`,
3210
3546
  `- If you are unsure where artifacts should go, run \`${commandName} help-ai --artifacts --format markdown\`.`,
3211
3547
  `- If you are unsure whether to continue after a task, run \`${commandName} help-ai --post-execution --format markdown\`.`,
3548
+ `- Use \`${commandName} use show\` to inspect current working track, delivery, and version defaults before selecting work.`,
3212
3549
  "- Use only commands that actually exist in COOP. Do not invent command names.",
3213
3550
  "- Do not reprioritize work outside COOP unless the user explicitly overrides it.",
3214
3551
  "- Select only the first ready task from the COOP readiness output.",
3215
- "- Inspect the selected task with `coop show task <id>` before implementation.",
3552
+ "- Inspect the selected task with `coop show <id>` before implementation.",
3216
3553
  "- Do not create, edit, move, or normalize files directly inside `.coop/`; use COOP commands, MCP, or API surfaces so COOP remains the canonical writer.",
3217
3554
  "- Respect lifecycle prerequisites: use `coop review task <id>` before `coop complete task <id>`; do not complete directly from `in_progress`.",
3555
+ "- Use `coop promote <id>` or `coop pick/start --promote` when the selected task must be escalated inside the active track/version context.",
3556
+ "- Use `coop update <id>`, `coop comment <id>`, and `coop log-time <id>` for task metadata changes; do not hand-edit task frontmatter.",
3218
3557
  `- Write any contract-review, audit, or planning artifact under \`${artifactsDir}\` unless the user explicitly chooses another location.`
3219
3558
  ];
3220
3559
  if (rigour === "strict") {
@@ -3372,11 +3711,11 @@ function resolveHelpTopic(options) {
3372
3711
  if (options.naming) {
3373
3712
  requestedTopics.push("naming");
3374
3713
  }
3375
- const unique2 = [...new Set(requestedTopics)];
3376
- if (unique2.length > 1) {
3377
- throw new Error(`Specify only one focused help-ai topic at a time. Received: ${unique2.join(", ")}.`);
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(", ")}.`);
3378
3717
  }
3379
- const topic = unique2[0];
3718
+ const topic = unique3[0];
3380
3719
  if (topic !== void 0 && topic !== "state-transitions" && topic !== "artifacts" && topic !== "post-execution" && topic !== "selection" && topic !== "naming") {
3381
3720
  throw new Error(`Unsupported help-ai topic '${topic}'. Expected state-transitions|artifacts|post-execution|selection|naming.`);
3382
3721
  }
@@ -3384,7 +3723,7 @@ function resolveHelpTopic(options) {
3384
3723
  }
3385
3724
 
3386
3725
  // src/commands/index.ts
3387
- import path8 from "path";
3726
+ import path10 from "path";
3388
3727
  import { IndexManager as IndexManager2 } from "@kitsy/coop-core";
3389
3728
  function runStatus(options) {
3390
3729
  const root = resolveRepoRoot();
@@ -3394,7 +3733,7 @@ function runStatus(options) {
3394
3733
  const freshness = status.stale ? "stale" : "fresh";
3395
3734
  const existsText = status.exists ? "present" : "missing";
3396
3735
  console.log(`[COOP] index ${existsText}, ${freshness}`);
3397
- console.log(`[COOP] graph: ${path8.relative(root, status.graph_path)}`);
3736
+ console.log(`[COOP] graph: ${path10.relative(root, status.graph_path)}`);
3398
3737
  if (status.generated_at) {
3399
3738
  console.log(`[COOP] generated_at: ${status.generated_at}`);
3400
3739
  }
@@ -3417,7 +3756,7 @@ function runRebuild() {
3417
3756
  const graph = manager.build_full_index();
3418
3757
  const elapsed = Date.now() - start;
3419
3758
  console.log(`[COOP] index rebuilt: ${graph.nodes.size} tasks (${elapsed} ms)`);
3420
- console.log(`[COOP] graph: ${path8.relative(root, manager.graphPath)}`);
3759
+ console.log(`[COOP] graph: ${path10.relative(root, manager.graphPath)}`);
3421
3760
  }
3422
3761
  function registerIndexCommand(program) {
3423
3762
  const index = program.command("index").description("Index management commands");
@@ -3433,18 +3772,18 @@ function registerIndexCommand(program) {
3433
3772
  }
3434
3773
 
3435
3774
  // src/commands/init.ts
3436
- import fs8 from "fs";
3437
- import path11 from "path";
3775
+ import fs10 from "fs";
3776
+ import path13 from "path";
3438
3777
  import { spawnSync as spawnSync4 } from "child_process";
3439
3778
  import { createInterface } from "readline/promises";
3440
3779
  import { stdin as input, stdout as output } from "process";
3441
3780
  import { CURRENT_SCHEMA_VERSION, write_schema_version } from "@kitsy/coop-core";
3442
3781
 
3443
3782
  // src/hooks/pre-commit.ts
3444
- import fs6 from "fs";
3445
- import path9 from "path";
3783
+ import fs8 from "fs";
3784
+ import path11 from "path";
3446
3785
  import { spawnSync as spawnSync3 } from "child_process";
3447
- import { detect_cycle, parseTaskContent, parseTaskFile as parseTaskFile7, validateStructural as validateStructural5 } from "@kitsy/coop-core";
3786
+ import { detect_cycle, parseTaskContent, parseTaskFile as parseTaskFile8, validateStructural as validateStructural6 } from "@kitsy/coop-core";
3448
3787
  var HOOK_BLOCK_START = "# COOP_PRE_COMMIT_START";
3449
3788
  var HOOK_BLOCK_END = "# COOP_PRE_COMMIT_END";
3450
3789
  function runGit(repoRoot, args, allowFailure = false) {
@@ -3473,28 +3812,28 @@ function projectRootFromRelativePath(repoRoot, relativePath) {
3473
3812
  const normalized = toPosixPath2(relativePath);
3474
3813
  const projectMatch = /^\.coop\/projects\/([^/]+)\/tasks\/.+\.md$/i.exec(normalized);
3475
3814
  if (projectMatch?.[1]) {
3476
- return path9.join(repoRoot, ".coop", "projects", projectMatch[1]);
3815
+ return path11.join(repoRoot, ".coop", "projects", projectMatch[1]);
3477
3816
  }
3478
3817
  if (normalized.startsWith(".coop/tasks/")) {
3479
- return path9.join(repoRoot, ".coop");
3818
+ return path11.join(repoRoot, ".coop");
3480
3819
  }
3481
3820
  throw new Error(`Unsupported staged COOP task path '${relativePath}'.`);
3482
3821
  }
3483
3822
  function listTaskFilesForProject(projectRoot) {
3484
- const tasksDir = path9.join(projectRoot, "tasks");
3485
- if (!fs6.existsSync(tasksDir)) return [];
3823
+ const tasksDir = path11.join(projectRoot, "tasks");
3824
+ if (!fs8.existsSync(tasksDir)) return [];
3486
3825
  const out = [];
3487
3826
  const stack = [tasksDir];
3488
3827
  while (stack.length > 0) {
3489
3828
  const current = stack.pop();
3490
- const entries = fs6.readdirSync(current, { withFileTypes: true });
3829
+ const entries = fs8.readdirSync(current, { withFileTypes: true });
3491
3830
  for (const entry of entries) {
3492
- const fullPath = path9.join(current, entry.name);
3831
+ const fullPath = path11.join(current, entry.name);
3493
3832
  if (entry.isDirectory()) {
3494
3833
  stack.push(fullPath);
3495
3834
  continue;
3496
3835
  }
3497
- if (entry.isFile() && path9.extname(entry.name).toLowerCase() === ".md") {
3836
+ if (entry.isFile() && path11.extname(entry.name).toLowerCase() === ".md") {
3498
3837
  out.push(fullPath);
3499
3838
  }
3500
3839
  }
@@ -3513,7 +3852,7 @@ function parseStagedTasks(repoRoot, relativePaths) {
3513
3852
  const errors = [];
3514
3853
  const staged = [];
3515
3854
  for (const relativePath of relativePaths) {
3516
- const absolutePath = path9.join(repoRoot, ...relativePath.split("/"));
3855
+ const absolutePath = path11.join(repoRoot, ...relativePath.split("/"));
3517
3856
  const projectRoot = projectRootFromRelativePath(repoRoot, relativePath);
3518
3857
  const stagedBlob = readGitBlob(repoRoot, `:${relativePath}`);
3519
3858
  if (!stagedBlob) {
@@ -3528,7 +3867,7 @@ function parseStagedTasks(repoRoot, relativePaths) {
3528
3867
  errors.push(`[COOP] ${message}`);
3529
3868
  continue;
3530
3869
  }
3531
- const issues = validateStructural5(task, { filePath: absolutePath });
3870
+ const issues = validateStructural6(task, { filePath: absolutePath });
3532
3871
  for (const issue of issues) {
3533
3872
  errors.push(`[COOP] ${relativePath}: ${issue.message}`);
3534
3873
  }
@@ -3577,13 +3916,13 @@ function collectTasksForCycleCheck(projectRoot, stagedTasks) {
3577
3916
  }
3578
3917
  const tasks = [];
3579
3918
  for (const filePath of listTaskFilesForProject(projectRoot)) {
3580
- const normalized = toPosixPath2(path9.resolve(filePath));
3919
+ const normalized = toPosixPath2(path11.resolve(filePath));
3581
3920
  const stagedTask = stagedByPath.get(normalized);
3582
3921
  if (stagedTask) {
3583
3922
  tasks.push(stagedTask);
3584
3923
  continue;
3585
3924
  }
3586
- tasks.push(parseTaskFile7(filePath).task);
3925
+ tasks.push(parseTaskFile8(filePath).task);
3587
3926
  }
3588
3927
  return tasks;
3589
3928
  }
@@ -3607,7 +3946,7 @@ function runPreCommitChecks(repoRoot) {
3607
3946
  const graph = buildGraphForCycleCheck(tasks);
3608
3947
  const cycle = detect_cycle(graph);
3609
3948
  if (cycle) {
3610
- const projectLabel = toPosixPath2(path9.relative(repoRoot, projectRoot));
3949
+ const projectLabel = toPosixPath2(path11.relative(repoRoot, projectRoot));
3611
3950
  errors.push(`[COOP] Dependency cycle detected in ${projectLabel}: ${cycle.join(" -> ")}.`);
3612
3951
  }
3613
3952
  } catch (error) {
@@ -3639,9 +3978,9 @@ function hookScriptBlock() {
3639
3978
  ].join("\n");
3640
3979
  }
3641
3980
  function installPreCommitHook(repoRoot) {
3642
- const hookPath = path9.join(repoRoot, ".git", "hooks", "pre-commit");
3643
- const hookDir = path9.dirname(hookPath);
3644
- if (!fs6.existsSync(hookDir)) {
3981
+ const hookPath = path11.join(repoRoot, ".git", "hooks", "pre-commit");
3982
+ const hookDir = path11.dirname(hookPath);
3983
+ if (!fs8.existsSync(hookDir)) {
3645
3984
  return {
3646
3985
  installed: false,
3647
3986
  hookPath,
@@ -3649,18 +3988,18 @@ function installPreCommitHook(repoRoot) {
3649
3988
  };
3650
3989
  }
3651
3990
  const block = hookScriptBlock();
3652
- if (!fs6.existsSync(hookPath)) {
3991
+ if (!fs8.existsSync(hookPath)) {
3653
3992
  const content = ["#!/bin/sh", "", block].join("\n");
3654
- fs6.writeFileSync(hookPath, content, "utf8");
3993
+ fs8.writeFileSync(hookPath, content, "utf8");
3655
3994
  } else {
3656
- const existing = fs6.readFileSync(hookPath, "utf8");
3995
+ const existing = fs8.readFileSync(hookPath, "utf8");
3657
3996
  if (!existing.includes(HOOK_BLOCK_START)) {
3658
3997
  const suffix = existing.endsWith("\n") ? "" : "\n";
3659
- fs6.writeFileSync(hookPath, `${existing}${suffix}${block}`, "utf8");
3998
+ fs8.writeFileSync(hookPath, `${existing}${suffix}${block}`, "utf8");
3660
3999
  }
3661
4000
  }
3662
4001
  try {
3663
- fs6.chmodSync(hookPath, 493);
4002
+ fs8.chmodSync(hookPath, 493);
3664
4003
  } catch {
3665
4004
  }
3666
4005
  return {
@@ -3671,15 +4010,15 @@ function installPreCommitHook(repoRoot) {
3671
4010
  }
3672
4011
 
3673
4012
  // src/hooks/post-merge-validate.ts
3674
- import fs7 from "fs";
3675
- import path10 from "path";
4013
+ import fs9 from "fs";
4014
+ import path12 from "path";
3676
4015
  import { list_projects } from "@kitsy/coop-core";
3677
- import { load_graph as load_graph4, validate_graph as validate_graph2 } from "@kitsy/coop-core";
4016
+ import { load_graph as load_graph5, validate_graph as validate_graph2 } from "@kitsy/coop-core";
3678
4017
  var HOOK_BLOCK_START2 = "# COOP_POST_MERGE_START";
3679
4018
  var HOOK_BLOCK_END2 = "# COOP_POST_MERGE_END";
3680
4019
  function runPostMergeValidate(repoRoot) {
3681
- const workspaceDir = path10.join(repoRoot, ".coop");
3682
- if (!fs7.existsSync(workspaceDir)) {
4020
+ const workspaceDir = path12.join(repoRoot, ".coop");
4021
+ if (!fs9.existsSync(workspaceDir)) {
3683
4022
  return {
3684
4023
  ok: true,
3685
4024
  warnings: ["[COOP] Skipped post-merge validation (.coop not found)."]
@@ -3695,7 +4034,7 @@ function runPostMergeValidate(repoRoot) {
3695
4034
  }
3696
4035
  const warnings = [];
3697
4036
  for (const project of projects) {
3698
- const graph = load_graph4(project.root);
4037
+ const graph = load_graph5(project.root);
3699
4038
  const issues = validate_graph2(graph);
3700
4039
  for (const issue of issues) {
3701
4040
  warnings.push(`[COOP] post-merge warning [${project.id}] [${issue.invariant}] ${issue.message}`);
@@ -3731,9 +4070,9 @@ function postMergeHookBlock() {
3731
4070
  ].join("\n");
3732
4071
  }
3733
4072
  function installPostMergeHook(repoRoot) {
3734
- const hookPath = path10.join(repoRoot, ".git", "hooks", "post-merge");
3735
- const hookDir = path10.dirname(hookPath);
3736
- if (!fs7.existsSync(hookDir)) {
4073
+ const hookPath = path12.join(repoRoot, ".git", "hooks", "post-merge");
4074
+ const hookDir = path12.dirname(hookPath);
4075
+ if (!fs9.existsSync(hookDir)) {
3737
4076
  return {
3738
4077
  installed: false,
3739
4078
  hookPath,
@@ -3741,17 +4080,17 @@ function installPostMergeHook(repoRoot) {
3741
4080
  };
3742
4081
  }
3743
4082
  const block = postMergeHookBlock();
3744
- if (!fs7.existsSync(hookPath)) {
3745
- fs7.writeFileSync(hookPath, ["#!/bin/sh", "", block].join("\n"), "utf8");
4083
+ if (!fs9.existsSync(hookPath)) {
4084
+ fs9.writeFileSync(hookPath, ["#!/bin/sh", "", block].join("\n"), "utf8");
3746
4085
  } else {
3747
- const existing = fs7.readFileSync(hookPath, "utf8");
4086
+ const existing = fs9.readFileSync(hookPath, "utf8");
3748
4087
  if (!existing.includes(HOOK_BLOCK_START2)) {
3749
4088
  const suffix = existing.endsWith("\n") ? "" : "\n";
3750
- fs7.writeFileSync(hookPath, `${existing}${suffix}${block}`, "utf8");
4089
+ fs9.writeFileSync(hookPath, `${existing}${suffix}${block}`, "utf8");
3751
4090
  }
3752
4091
  }
3753
4092
  try {
3754
- fs7.chmodSync(hookPath, 493);
4093
+ fs9.chmodSync(hookPath, 493);
3755
4094
  } catch {
3756
4095
  }
3757
4096
  return {
@@ -3917,40 +4256,40 @@ tmp/
3917
4256
  *.tmp
3918
4257
  `;
3919
4258
  function ensureDir(dirPath) {
3920
- fs8.mkdirSync(dirPath, { recursive: true });
4259
+ fs10.mkdirSync(dirPath, { recursive: true });
3921
4260
  }
3922
4261
  function writeIfMissing(filePath, content) {
3923
- if (!fs8.existsSync(filePath)) {
3924
- fs8.writeFileSync(filePath, content, "utf8");
4262
+ if (!fs10.existsSync(filePath)) {
4263
+ fs10.writeFileSync(filePath, content, "utf8");
3925
4264
  }
3926
4265
  }
3927
4266
  function ensureGitignoreEntry(root, entry) {
3928
- const gitignorePath = path11.join(root, ".gitignore");
3929
- if (!fs8.existsSync(gitignorePath)) {
3930
- fs8.writeFileSync(gitignorePath, `${entry}
4267
+ const gitignorePath = path13.join(root, ".gitignore");
4268
+ if (!fs10.existsSync(gitignorePath)) {
4269
+ fs10.writeFileSync(gitignorePath, `${entry}
3931
4270
  `, "utf8");
3932
4271
  return;
3933
4272
  }
3934
- const content = fs8.readFileSync(gitignorePath, "utf8");
4273
+ const content = fs10.readFileSync(gitignorePath, "utf8");
3935
4274
  const lines = content.split(/\r?\n/).map((line) => line.trim());
3936
4275
  if (!lines.includes(entry)) {
3937
4276
  const suffix = content.endsWith("\n") ? "" : "\n";
3938
- fs8.writeFileSync(gitignorePath, `${content}${suffix}${entry}
4277
+ fs10.writeFileSync(gitignorePath, `${content}${suffix}${entry}
3939
4278
  `, "utf8");
3940
4279
  }
3941
4280
  }
3942
4281
  function ensureGitattributesEntry(root, entry) {
3943
- const attrsPath = path11.join(root, ".gitattributes");
3944
- if (!fs8.existsSync(attrsPath)) {
3945
- fs8.writeFileSync(attrsPath, `${entry}
4282
+ const attrsPath = path13.join(root, ".gitattributes");
4283
+ if (!fs10.existsSync(attrsPath)) {
4284
+ fs10.writeFileSync(attrsPath, `${entry}
3946
4285
  `, "utf8");
3947
4286
  return;
3948
4287
  }
3949
- const content = fs8.readFileSync(attrsPath, "utf8");
4288
+ const content = fs10.readFileSync(attrsPath, "utf8");
3950
4289
  const lines = content.split(/\r?\n/).map((line) => line.trim());
3951
4290
  if (!lines.includes(entry)) {
3952
4291
  const suffix = content.endsWith("\n") ? "" : "\n";
3953
- fs8.writeFileSync(attrsPath, `${content}${suffix}${entry}
4292
+ fs10.writeFileSync(attrsPath, `${content}${suffix}${entry}
3954
4293
  `, "utf8");
3955
4294
  }
3956
4295
  }
@@ -4036,7 +4375,7 @@ function registerInitCommand(program) {
4036
4375
  const workspaceDir = coopWorkspaceDir(root);
4037
4376
  const identity = await resolveInitIdentity(root, options);
4038
4377
  const projectId = identity.projectId;
4039
- const projectRoot = path11.join(workspaceDir, "projects", projectId);
4378
+ const projectRoot = path13.join(workspaceDir, "projects", projectId);
4040
4379
  const dirs = [
4041
4380
  "ideas",
4042
4381
  "tasks",
@@ -4052,23 +4391,23 @@ function registerInitCommand(program) {
4052
4391
  "history/deliveries",
4053
4392
  ".index"
4054
4393
  ];
4055
- ensureDir(path11.join(workspaceDir, "projects"));
4394
+ ensureDir(path13.join(workspaceDir, "projects"));
4056
4395
  for (const dir of dirs) {
4057
- ensureDir(path11.join(projectRoot, dir));
4396
+ ensureDir(path13.join(projectRoot, dir));
4058
4397
  }
4059
4398
  writeIfMissing(
4060
- path11.join(projectRoot, "config.yml"),
4399
+ path13.join(projectRoot, "config.yml"),
4061
4400
  buildProjectConfig(projectId, identity.projectName, identity.projectAliases, identity.namingTemplate)
4062
4401
  );
4063
- if (!fs8.existsSync(path11.join(projectRoot, "schema-version"))) {
4402
+ if (!fs10.existsSync(path13.join(projectRoot, "schema-version"))) {
4064
4403
  write_schema_version(projectRoot, CURRENT_SCHEMA_VERSION);
4065
4404
  }
4066
- writeIfMissing(path11.join(projectRoot, "templates/task.md"), TASK_TEMPLATE);
4067
- writeIfMissing(path11.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE);
4068
- writeIfMissing(path11.join(projectRoot, "plugins/console-log.yml"), PLUGIN_CONSOLE_TEMPLATE);
4069
- writeIfMissing(path11.join(projectRoot, "plugins/github-pr.yml"), PLUGIN_GITHUB_TEMPLATE);
4070
- writeIfMissing(path11.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE);
4071
- writeIfMissing(path11.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE);
4405
+ writeIfMissing(path13.join(projectRoot, "templates/task.md"), TASK_TEMPLATE);
4406
+ writeIfMissing(path13.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE);
4407
+ writeIfMissing(path13.join(projectRoot, "plugins/console-log.yml"), PLUGIN_CONSOLE_TEMPLATE);
4408
+ writeIfMissing(path13.join(projectRoot, "plugins/github-pr.yml"), PLUGIN_GITHUB_TEMPLATE);
4409
+ writeIfMissing(path13.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE);
4410
+ writeIfMissing(path13.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE);
4072
4411
  writeWorkspaceConfig(root, { version: 2, current_project: projectId });
4073
4412
  ensureGitignoreEntry(root, ".coop/logs/");
4074
4413
  ensureGitignoreEntry(root, ".coop/tmp/");
@@ -4078,18 +4417,18 @@ function registerInitCommand(program) {
4078
4417
  const project = resolveProject(root, projectId);
4079
4418
  console.log("Initialized COOP workspace.");
4080
4419
  console.log(`- Root: ${root}`);
4081
- console.log(`- Workspace: ${path11.relative(root, workspaceDir)}`);
4082
- console.log(`- Project: ${project.id} (${path11.relative(root, project.root)})`);
4420
+ console.log(`- Workspace: ${path13.relative(root, workspaceDir)}`);
4421
+ console.log(`- Project: ${project.id} (${path13.relative(root, project.root)})`);
4083
4422
  console.log(`- Name: ${identity.projectName}`);
4084
4423
  console.log(`- Aliases: ${identity.projectAliases.length > 0 ? identity.projectAliases.join(", ") : "(none)"}`);
4085
4424
  console.log(`- ID naming: ${identity.namingTemplate}`);
4086
4425
  console.log(`- ${preCommitHook.message}`);
4087
4426
  if (preCommitHook.installed) {
4088
- console.log(`- Hook: ${path11.relative(root, preCommitHook.hookPath)}`);
4427
+ console.log(`- Hook: ${path13.relative(root, preCommitHook.hookPath)}`);
4089
4428
  }
4090
4429
  console.log(`- ${postMergeHook.message}`);
4091
4430
  if (postMergeHook.installed) {
4092
- console.log(`- Hook: ${path11.relative(root, postMergeHook.hookPath)}`);
4431
+ console.log(`- Hook: ${path13.relative(root, postMergeHook.hookPath)}`);
4093
4432
  }
4094
4433
  console.log(`- ${mergeDrivers}`);
4095
4434
  console.log("- Next steps:");
@@ -4100,8 +4439,8 @@ function registerInitCommand(program) {
4100
4439
  }
4101
4440
 
4102
4441
  // src/commands/lifecycle.ts
4103
- import path12 from "path";
4104
- import { parseTaskFile as parseTaskFile8 } from "@kitsy/coop-core";
4442
+ import path14 from "path";
4443
+ import { parseTaskFile as parseTaskFile9 } from "@kitsy/coop-core";
4105
4444
  var lifecycleVerbs = [
4106
4445
  {
4107
4446
  name: "review",
@@ -4136,8 +4475,8 @@ var lifecycleVerbs = [
4136
4475
  ];
4137
4476
  function currentTaskSelection(root, id) {
4138
4477
  const reference = resolveReference(root, id, "task");
4139
- const filePath = path12.join(root, ...reference.file.split("/"));
4140
- const parsed = parseTaskFile8(filePath);
4478
+ const filePath = path14.join(root, ...reference.file.split("/"));
4479
+ const parsed = parseTaskFile9(filePath);
4141
4480
  return formatSelectedTask(
4142
4481
  {
4143
4482
  task: parsed.task,
@@ -4161,16 +4500,9 @@ function registerLifecycleCommands(program) {
4161
4500
  }
4162
4501
 
4163
4502
  // src/commands/list.ts
4164
- import path13 from "path";
4165
- import { parseIdeaFile as parseIdeaFile3, parseTaskFile as parseTaskFile9 } from "@kitsy/coop-core";
4503
+ 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";
4166
4505
  import chalk2 from "chalk";
4167
-
4168
- // src/utils/not-implemented.ts
4169
- function printNotImplemented(command, phase) {
4170
- console.log(`${command}: Not yet implemented - coming in Phase ${phase}.`);
4171
- }
4172
-
4173
- // src/commands/list.ts
4174
4506
  function statusColor(status) {
4175
4507
  switch (status) {
4176
4508
  case "done":
@@ -4190,49 +4522,81 @@ function statusColor(status) {
4190
4522
  function sortByIdAsc(items) {
4191
4523
  return [...items].sort((a, b) => a.id.localeCompare(b.id));
4192
4524
  }
4193
- function loadTasks(root) {
4525
+ function loadTasks2(root) {
4194
4526
  return listTaskFiles(root).map((filePath) => ({
4195
- task: parseTaskFile9(filePath).task,
4527
+ task: parseTaskFile10(filePath).task,
4196
4528
  filePath
4197
4529
  }));
4198
4530
  }
4199
4531
  function loadIdeas(root) {
4200
4532
  return listIdeaFiles(root).map((filePath) => ({
4201
- idea: parseIdeaFile3(filePath).idea,
4533
+ idea: parseIdeaFile4(filePath).idea,
4202
4534
  filePath
4203
4535
  }));
4204
4536
  }
4205
4537
  function listTasks(options) {
4206
4538
  const root = resolveRepoRoot();
4207
4539
  ensureCoopInitialized(root);
4208
- const rows = loadTasks(root).filter(({ task }) => {
4540
+ const context = readWorkingContext(root, resolveCoopHome());
4541
+ 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"));
4544
+ const assignee = options.mine ? defaultCoopAuthor(root) : options.assignee?.trim();
4545
+ const deliveryScope = resolvedDelivery.value ? new Set(graph.deliveries.get(resolvedDelivery.value)?.scope.include ?? []) : null;
4546
+ if (isVerboseRequested()) {
4547
+ for (const line of formatResolvedContextMessage({
4548
+ track: resolvedTrack,
4549
+ delivery: resolvedDelivery
4550
+ })) {
4551
+ console.log(line);
4552
+ }
4553
+ }
4554
+ const readyEntries = options.ready ? schedule_next3(load_graph6(ensureCoopInitialized(root)), {
4555
+ track: resolvedTrack.value,
4556
+ 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;
4560
+ const rows = loadTasks2(root).filter(({ task }) => {
4561
+ if (readyIds && !readyIds.has(task.id)) return false;
4209
4562
  if (options.status && task.status !== options.status) return false;
4210
- if (options.track && (task.track ?? "unassigned") !== options.track) return false;
4211
- if (options.priority && (task.priority ?? "p2") !== options.priority) return false;
4563
+ if (resolvedTrack.value && task.track !== resolvedTrack.value && !(task.delivery_tracks ?? []).includes(resolvedTrack.value)) {
4564
+ return false;
4565
+ }
4566
+ if (resolvedDelivery.value && task.delivery !== resolvedDelivery.value && !deliveryScope?.has(task.id)) return false;
4567
+ if (options.priority && taskEffectivePriority(task, resolvedTrack.value) !== options.priority) return false;
4568
+ if (assignee && (task.assignee ?? "") !== assignee) return false;
4569
+ if (options.version && !(task.fix_versions ?? []).includes(options.version) && !(task.released_in ?? []).includes(options.version)) {
4570
+ return false;
4571
+ }
4212
4572
  return true;
4213
4573
  }).map(({ task, filePath }) => ({
4214
4574
  id: task.id,
4215
4575
  title: task.title,
4216
4576
  status: task.status,
4217
- priority: task.priority ?? "-",
4577
+ priority: taskEffectivePriority(task, resolvedTrack.value),
4218
4578
  track: task.track ?? "-",
4579
+ assignee: task.assignee ?? "-",
4580
+ delivery: task.delivery ?? "-",
4219
4581
  filePath
4220
4582
  }));
4221
- const sorted = sortByIdAsc(rows);
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);
4222
4584
  if (sorted.length === 0) {
4223
4585
  console.log("No tasks found.");
4224
4586
  return;
4225
4587
  }
4226
4588
  console.log(
4227
4589
  formatTable(
4228
- ["ID", "Title", "Status", "Priority", "Track", "File"],
4590
+ ["ID", "Title", "Status", "Priority", "Track", "Assignee", "Delivery", "File"],
4229
4591
  sorted.map((entry) => [
4230
4592
  entry.id,
4231
4593
  entry.title,
4232
4594
  statusColor(entry.status),
4233
4595
  entry.priority,
4234
4596
  entry.track,
4235
- path13.relative(root, entry.filePath)
4597
+ entry.assignee,
4598
+ entry.delivery,
4599
+ path15.relative(root, entry.filePath)
4236
4600
  ])
4237
4601
  )
4238
4602
  );
@@ -4267,16 +4631,31 @@ function listIdeas(options) {
4267
4631
  statusColor(entry.status),
4268
4632
  entry.priority,
4269
4633
  entry.track,
4270
- path13.relative(root, entry.filePath)
4634
+ path15.relative(root, entry.filePath)
4271
4635
  ])
4272
4636
  )
4273
4637
  );
4274
4638
  console.log(`
4275
4639
  Total ideas: ${sorted.length}`);
4276
4640
  }
4641
+ function listDeliveries() {
4642
+ const root = resolveRepoRoot();
4643
+ 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)
4649
+ ]);
4650
+ if (rows.length === 0) {
4651
+ console.log("No deliveries found.");
4652
+ return;
4653
+ }
4654
+ console.log(formatTable(["ID", "Name", "Status", "Target", "File"], rows));
4655
+ }
4277
4656
  function registerListCommand(program) {
4278
4657
  const list = program.command("list").description("List COOP entities");
4279
- list.command("tasks").description("List tasks").option("--status <status>", "Filter by status").option("--track <track>", "Filter by track").option("--priority <priority>", "Filter by priority").action((options) => {
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) => {
4280
4659
  listTasks(options);
4281
4660
  });
4282
4661
  list.command("ideas").description("List ideas").option("--status <status>", "Filter by status").action((options) => {
@@ -4285,32 +4664,32 @@ function registerListCommand(program) {
4285
4664
  list.command("alias").description("List aliases").argument("[pattern]", "Wildcard pattern, e.g. PAY*").action((pattern) => {
4286
4665
  listAliasRows(pattern);
4287
4666
  });
4288
- list.command("deliveries").description("List deliveries (Phase 2)").action(() => {
4289
- printNotImplemented("coop list deliveries", 2);
4667
+ list.command("deliveries").description("List deliveries").action(() => {
4668
+ listDeliveries();
4290
4669
  });
4291
4670
  }
4292
4671
 
4293
4672
  // src/utils/logger.ts
4294
- import fs9 from "fs";
4295
- import path14 from "path";
4673
+ import fs11 from "fs";
4674
+ import path16 from "path";
4296
4675
  function resolveRepoSafe(start = process.cwd()) {
4297
4676
  try {
4298
4677
  return resolveRepoRoot(start);
4299
4678
  } catch {
4300
- return path14.resolve(start);
4679
+ return path16.resolve(start);
4301
4680
  }
4302
4681
  }
4303
4682
  function resolveCliLogFile(start = process.cwd()) {
4304
4683
  const root = resolveRepoSafe(start);
4305
4684
  const workspace = coopWorkspaceDir(root);
4306
- if (fs9.existsSync(workspace)) {
4307
- return path14.join(workspace, "logs", "cli.log");
4685
+ if (fs11.existsSync(workspace)) {
4686
+ return path16.join(workspace, "logs", "cli.log");
4308
4687
  }
4309
- return path14.join(resolveCoopHome(), "logs", "cli.log");
4688
+ return path16.join(resolveCoopHome(), "logs", "cli.log");
4310
4689
  }
4311
4690
  function appendLogEntry(entry, logFile) {
4312
- fs9.mkdirSync(path14.dirname(logFile), { recursive: true });
4313
- fs9.appendFileSync(logFile, `${JSON.stringify(entry)}
4691
+ fs11.mkdirSync(path16.dirname(logFile), { recursive: true });
4692
+ fs11.appendFileSync(logFile, `${JSON.stringify(entry)}
4314
4693
  `, "utf8");
4315
4694
  }
4316
4695
  function logCliError(error, start = process.cwd()) {
@@ -4349,8 +4728,8 @@ function parseLogLine(line) {
4349
4728
  }
4350
4729
  function readLastCliLog(start = process.cwd()) {
4351
4730
  const logFile = resolveCliLogFile(start);
4352
- if (!fs9.existsSync(logFile)) return null;
4353
- const content = fs9.readFileSync(logFile, "utf8");
4731
+ if (!fs11.existsSync(logFile)) return null;
4732
+ const content = fs11.readFileSync(logFile, "utf8");
4354
4733
  const lines = content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
4355
4734
  for (let i = lines.length - 1; i >= 0; i -= 1) {
4356
4735
  const entry = parseLogLine(lines[i] ?? "");
@@ -4384,9 +4763,37 @@ function registerLogCommand(program) {
4384
4763
  });
4385
4764
  }
4386
4765
 
4766
+ // src/commands/log-time.ts
4767
+ function registerLogTimeCommand(program) {
4768
+ program.command("log-time").description("Append planned or worked time to a task").argument("<id-or-type>", "Task id/alias, or the literal `task` followed by an id").argument("[id]", "Task id when an explicit entity type is provided").requiredOption("--hours <n>", "Hours to log").requiredOption("--kind <kind>", "planned|worked").option("--note <text>", "Optional note").option("--actor <id>", "Actor for the log entry").action((first, second, options) => {
4769
+ const { entity, id } = resolveOptionalEntityArg(first, second, ["task"], "task");
4770
+ if (entity !== "task") {
4771
+ throw new Error("Only task time logging is supported.");
4772
+ }
4773
+ const hours = Number(options.hours);
4774
+ if (!Number.isFinite(hours) || hours < 0) {
4775
+ throw new Error(`Invalid --hours value '${options.hours}'. Expected a non-negative number.`);
4776
+ }
4777
+ if (options.kind !== "planned" && options.kind !== "worked") {
4778
+ throw new Error(`Invalid --kind value '${options.kind}'. Expected planned|worked.`);
4779
+ }
4780
+ const root = resolveRepoRoot();
4781
+ const { filePath, parsed } = loadTaskEntry(root, id);
4782
+ const task = appendTaskTimeLog(
4783
+ parsed.task,
4784
+ options.actor?.trim() || defaultCoopAuthor(root),
4785
+ options.kind,
4786
+ hours,
4787
+ options.note?.trim() || void 0
4788
+ );
4789
+ writeTaskEntry(filePath, parsed, task);
4790
+ console.log(`Logged ${hours}h ${options.kind} on ${task.id}`);
4791
+ });
4792
+ }
4793
+
4387
4794
  // src/commands/migrate.ts
4388
- import fs10 from "fs";
4389
- import path15 from "path";
4795
+ import fs12 from "fs";
4796
+ import path17 from "path";
4390
4797
  import { createInterface as createInterface2 } from "readline/promises";
4391
4798
  import { stdin as input2, stdout as output2 } from "process";
4392
4799
  import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as parseYamlFile2, writeYamlFile as writeYamlFile4 } from "@kitsy/coop-core";
@@ -4405,22 +4812,22 @@ function parseTargetVersion(raw) {
4405
4812
  return parsed;
4406
4813
  }
4407
4814
  function writeIfMissing2(filePath, content) {
4408
- if (!fs10.existsSync(filePath)) {
4409
- fs10.writeFileSync(filePath, content, "utf8");
4815
+ if (!fs12.existsSync(filePath)) {
4816
+ fs12.writeFileSync(filePath, content, "utf8");
4410
4817
  }
4411
4818
  }
4412
4819
  function ensureGitignoreEntry2(root, entry) {
4413
- const gitignorePath = path15.join(root, ".gitignore");
4414
- if (!fs10.existsSync(gitignorePath)) {
4415
- fs10.writeFileSync(gitignorePath, `${entry}
4820
+ const gitignorePath = path17.join(root, ".gitignore");
4821
+ if (!fs12.existsSync(gitignorePath)) {
4822
+ fs12.writeFileSync(gitignorePath, `${entry}
4416
4823
  `, "utf8");
4417
4824
  return;
4418
4825
  }
4419
- const content = fs10.readFileSync(gitignorePath, "utf8");
4826
+ const content = fs12.readFileSync(gitignorePath, "utf8");
4420
4827
  const lines = content.split(/\r?\n/).map((line) => line.trim());
4421
4828
  if (!lines.includes(entry)) {
4422
4829
  const suffix = content.endsWith("\n") ? "" : "\n";
4423
- fs10.writeFileSync(gitignorePath, `${content}${suffix}${entry}
4830
+ fs12.writeFileSync(gitignorePath, `${content}${suffix}${entry}
4424
4831
  `, "utf8");
4425
4832
  }
4426
4833
  }
@@ -4445,7 +4852,7 @@ function legacyWorkspaceProjectEntries(root) {
4445
4852
  "backlog",
4446
4853
  "plans",
4447
4854
  "releases"
4448
- ].filter((entry) => fs10.existsSync(path15.join(workspaceDir, entry)));
4855
+ ].filter((entry) => fs12.existsSync(path17.join(workspaceDir, entry)));
4449
4856
  }
4450
4857
  function normalizeProjectId2(value) {
4451
4858
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
@@ -4490,7 +4897,7 @@ async function promptProjectIdentity(defaults, options) {
4490
4897
  async function resolveMigrationIdentity(root, options) {
4491
4898
  const existing = readCoopConfig(root);
4492
4899
  const defaults = {
4493
- projectName: existing.projectName || path15.basename(root),
4900
+ projectName: existing.projectName || path17.basename(root),
4494
4901
  projectId: normalizeProjectId2(existing.projectId || repoIdentityId(root)) || repoIdentityId(root),
4495
4902
  projectAliases: options.aliases !== void 0 ? parseAliases3(options.aliases) : existing.projectAliases
4496
4903
  };
@@ -4510,22 +4917,22 @@ async function migrateWorkspaceLayout(root, options) {
4510
4917
  throw new Error(`Unsupported workspace-layout target '${options.to ?? ""}'. Expected 'v2'.`);
4511
4918
  }
4512
4919
  const workspaceDir = coopWorkspaceDir(root);
4513
- if (!fs10.existsSync(workspaceDir)) {
4920
+ if (!fs12.existsSync(workspaceDir)) {
4514
4921
  throw new Error("Missing .coop directory. Run 'coop init' first.");
4515
4922
  }
4516
- const projectsDir = path15.join(workspaceDir, "projects");
4923
+ const projectsDir = path17.join(workspaceDir, "projects");
4517
4924
  const legacyEntries = legacyWorkspaceProjectEntries(root);
4518
- if (legacyEntries.length === 0 && fs10.existsSync(projectsDir)) {
4925
+ if (legacyEntries.length === 0 && fs12.existsSync(projectsDir)) {
4519
4926
  console.log("[COOP] workspace layout already uses v2.");
4520
4927
  return;
4521
4928
  }
4522
4929
  const identity = await resolveMigrationIdentity(root, options);
4523
4930
  const projectId = identity.projectId;
4524
- const projectRoot = path15.join(projectsDir, projectId);
4525
- if (fs10.existsSync(projectRoot) && !options.force) {
4526
- throw new Error(`Project destination '${path15.relative(root, projectRoot)}' already exists. Re-run with --force.`);
4931
+ const projectRoot = path17.join(projectsDir, projectId);
4932
+ if (fs12.existsSync(projectRoot) && !options.force) {
4933
+ throw new Error(`Project destination '${path17.relative(root, projectRoot)}' already exists. Re-run with --force.`);
4527
4934
  }
4528
- const changed = legacyEntries.map((entry) => `${path15.join(".coop", entry)} -> ${path15.join(".coop", "projects", projectId, entry)}`);
4935
+ const changed = legacyEntries.map((entry) => `${path17.join(".coop", entry)} -> ${path17.join(".coop", "projects", projectId, entry)}`);
4529
4936
  changed.push(`.coop/config.yml -> workspace current_project=${projectId}`);
4530
4937
  console.log(`Workspace layout migration (${options.dryRun ? "DRY RUN" : "APPLY"})`);
4531
4938
  console.log(`- from: v1 flat layout`);
@@ -4541,21 +4948,21 @@ async function migrateWorkspaceLayout(root, options) {
4541
4948
  console.log("- no files were modified.");
4542
4949
  return;
4543
4950
  }
4544
- fs10.mkdirSync(projectsDir, { recursive: true });
4545
- fs10.mkdirSync(projectRoot, { recursive: true });
4951
+ fs12.mkdirSync(projectsDir, { recursive: true });
4952
+ fs12.mkdirSync(projectRoot, { recursive: true });
4546
4953
  for (const entry of legacyEntries) {
4547
- const source = path15.join(workspaceDir, entry);
4548
- const destination = path15.join(projectRoot, entry);
4549
- if (fs10.existsSync(destination)) {
4954
+ const source = path17.join(workspaceDir, entry);
4955
+ const destination = path17.join(projectRoot, entry);
4956
+ if (fs12.existsSync(destination)) {
4550
4957
  if (!options.force) {
4551
- throw new Error(`Destination '${path15.relative(root, destination)}' already exists.`);
4958
+ throw new Error(`Destination '${path17.relative(root, destination)}' already exists.`);
4552
4959
  }
4553
- fs10.rmSync(destination, { recursive: true, force: true });
4960
+ fs12.rmSync(destination, { recursive: true, force: true });
4554
4961
  }
4555
- fs10.renameSync(source, destination);
4962
+ fs12.renameSync(source, destination);
4556
4963
  }
4557
- const movedConfigPath = path15.join(projectRoot, "config.yml");
4558
- if (fs10.existsSync(movedConfigPath)) {
4964
+ const movedConfigPath = path17.join(projectRoot, "config.yml");
4965
+ if (fs12.existsSync(movedConfigPath)) {
4559
4966
  const movedConfig = parseYamlFile2(movedConfigPath);
4560
4967
  const nextProject = typeof movedConfig.project === "object" && movedConfig.project !== null ? { ...movedConfig.project } : {};
4561
4968
  nextProject.name = identity.projectName;
@@ -4572,13 +4979,13 @@ async function migrateWorkspaceLayout(root, options) {
4572
4979
  }
4573
4980
  const workspace = readWorkspaceConfig(root);
4574
4981
  writeWorkspaceConfig(root, { ...workspace, version: 2, current_project: projectId });
4575
- writeIfMissing2(path15.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE2);
4576
- writeIfMissing2(path15.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE2);
4982
+ writeIfMissing2(path17.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE2);
4983
+ writeIfMissing2(path17.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE2);
4577
4984
  ensureGitignoreEntry2(root, ".coop/logs/");
4578
4985
  ensureGitignoreEntry2(root, ".coop/tmp/");
4579
4986
  const manager = new IndexManager3(projectRoot);
4580
4987
  manager.build_full_index();
4581
- console.log(`[COOP] migrated workspace to v2 at ${path15.relative(root, projectRoot)}`);
4988
+ console.log(`[COOP] migrated workspace to v2 at ${path17.relative(root, projectRoot)}`);
4582
4989
  }
4583
4990
  function registerMigrateCommand(program) {
4584
4991
  const migrate = program.command("migrate").description("Migrate COOP data and workspace layouts").option("--dry-run", "Preview migration without writing files").option("--to <version>", "Target schema version", String(CURRENT_SCHEMA_VERSION2)).action((options) => {
@@ -4597,7 +5004,7 @@ function registerMigrateCommand(program) {
4597
5004
  if (report.changed_files.length > 0) {
4598
5005
  console.log("- changed files:");
4599
5006
  for (const filePath of report.changed_files) {
4600
- console.log(` - ${path15.relative(root, filePath)}`);
5007
+ console.log(` - ${path17.relative(root, filePath)}`);
4601
5008
  }
4602
5009
  }
4603
5010
  if (report.dry_run) {
@@ -4691,7 +5098,7 @@ import chalk3 from "chalk";
4691
5098
  import {
4692
5099
  analyze_feasibility,
4693
5100
  analyze_what_if,
4694
- load_graph as load_graph5,
5101
+ load_graph as load_graph7,
4695
5102
  monte_carlo_forecast,
4696
5103
  TaskPriority as TaskPriority2
4697
5104
  } from "@kitsy/coop-core";
@@ -4755,8 +5162,19 @@ function collectWhatIfModifications(options) {
4755
5162
  }
4756
5163
  async function runPlanDelivery(deliveryName, options) {
4757
5164
  const root = resolveRepoRoot();
4758
- const graph = load_graph5(coopDir(root));
4759
- const delivery = resolveDelivery(graph, deliveryName);
5165
+ const context = readWorkingContext(root, resolveCoopHome());
5166
+ const graph = load_graph7(coopDir(root));
5167
+ const resolvedDelivery = deliveryName?.trim() ? { value: deliveryName.trim(), source: "arg" } : resolveContextValueWithSource(void 0, context.delivery, sharedDefault(root, "delivery"));
5168
+ if (isVerboseRequested()) {
5169
+ for (const line of formatResolvedContextMessage({ delivery: resolvedDelivery })) {
5170
+ console.log(line);
5171
+ }
5172
+ }
5173
+ const deliveryRef = resolvedDelivery.value;
5174
+ if (!deliveryRef) {
5175
+ throw new Error("delivery is not set; pass a delivery id or set a default working delivery with `coop use delivery <id>`.");
5176
+ }
5177
+ const delivery = resolveDelivery(graph, deliveryRef);
4760
5178
  const modifications = collectWhatIfModifications(options);
4761
5179
  if (modifications.length > 0) {
4762
5180
  const comparison = analyze_what_if(
@@ -4871,7 +5289,7 @@ async function runPlanDelivery(deliveryName, options) {
4871
5289
  }
4872
5290
  function runPlanCapacity(trackArg, options) {
4873
5291
  const root = resolveRepoRoot();
4874
- const graph = load_graph5(coopDir(root));
5292
+ const graph = load_graph7(coopDir(root));
4875
5293
  const track = normalizeTrack(trackArg);
4876
5294
  const deliveries = Array.from(graph.deliveries.values()).sort((a, b) => a.id.localeCompare(b.id));
4877
5295
  if (deliveries.length === 0) {
@@ -4911,17 +5329,43 @@ function runPlanCapacity(trackArg, options) {
4911
5329
  }
4912
5330
  function registerPlanCommand(program) {
4913
5331
  const plan = program.command("plan").description("Planning commands");
4914
- plan.command("delivery").description("Analyze delivery feasibility").argument("<name>", "Delivery id or name").option("--today <date>", "Evaluation date (YYYY-MM-DD)").option("--monte-carlo", "Run Monte Carlo schedule forecast").option("--iterations <n>", "Monte Carlo iterations (default 10000)").option("--workers <n>", "Worker count hint (default 4)").option("--without <taskId>", "Remove a task and its exclusive dependencies from scope").option("--add-member <trackOrProfile>", "Add one default-capacity member to a human profile").option("--target <date>", "Run scenario with a different target date (YYYY-MM-DD)").option("--set <override>", "Override one field, for example TASK-1:priority=p0").action(async (name, options) => {
4915
- await runPlanDelivery(name, options);
5332
+ plan.command("delivery").description("Analyze delivery feasibility").argument("[name]", "Delivery id or name").option("--today <date>", "Evaluation date (YYYY-MM-DD)").option("--monte-carlo", "Run Monte Carlo schedule forecast").option("--iterations <n>", "Monte Carlo iterations (default 10000)").option("--workers <n>", "Worker count hint (default 4)").option("--without <taskId>", "Remove a task and its exclusive dependencies from scope").option("--add-member <trackOrProfile>", "Add one default-capacity member to a human profile").option("--target <date>", "Run scenario with a different target date (YYYY-MM-DD)").option("--set <override>", "Override one field, for example TASK-1:priority=p0").action(async (name, options) => {
5333
+ await runPlanDelivery(name ?? "", options);
4916
5334
  });
4917
5335
  plan.command("capacity").description("Show track capacity utilization across deliveries").argument("<track>", "Track id").option("--today <date>", "Evaluation date (YYYY-MM-DD)").action((track, options) => {
4918
5336
  runPlanCapacity(track, options);
4919
5337
  });
4920
5338
  }
4921
5339
 
5340
+ // src/commands/promote.ts
5341
+ function registerPromoteCommand(program) {
5342
+ program.command("promote").description("Promote a task within the current working context").argument("<id-or-type>", "Task id/alias, or the literal `task` followed by an id").argument("[id]", "Task id when an explicit entity type is provided").option("--track <track>", "Scoped track context").option("--version <version>", "Scoped version context").action((first, second, options) => {
5343
+ const { entity, id } = resolveOptionalEntityArg(first, second, ["task"], "task");
5344
+ if (entity !== "task") {
5345
+ throw new Error("Only task promotion is supported.");
5346
+ }
5347
+ const root = resolveRepoRoot();
5348
+ const context = readWorkingContext(root, resolveCoopHome());
5349
+ const resolvedTrack = resolveContextValueWithSource(options.track, context.track, sharedDefault(root, "track"));
5350
+ const resolvedVersion = resolveContextValueWithSource(options.version, context.version, sharedDefault(root, "version"));
5351
+ if (isVerboseRequested()) {
5352
+ for (const line of formatResolvedContextMessage({ track: resolvedTrack, version: resolvedVersion })) {
5353
+ console.log(line);
5354
+ }
5355
+ }
5356
+ const { filePath, parsed } = loadTaskEntry(root, id);
5357
+ const task = promoteTaskForContext(parsed.task, {
5358
+ track: resolvedTrack.value,
5359
+ version: resolvedVersion.value
5360
+ });
5361
+ writeTaskEntry(filePath, parsed, task);
5362
+ console.log(`Promoted ${task.id}`);
5363
+ });
5364
+ }
5365
+
4922
5366
  // src/commands/project.ts
4923
- import fs11 from "fs";
4924
- import path16 from "path";
5367
+ import fs13 from "fs";
5368
+ import path18 from "path";
4925
5369
  import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3, write_schema_version as write_schema_version2 } from "@kitsy/coop-core";
4926
5370
  var TASK_TEMPLATE2 = `---
4927
5371
  id: TASK-001
@@ -5030,11 +5474,11 @@ var PROJECT_DIRS = [
5030
5474
  ".index"
5031
5475
  ];
5032
5476
  function ensureDir2(dirPath) {
5033
- fs11.mkdirSync(dirPath, { recursive: true });
5477
+ fs13.mkdirSync(dirPath, { recursive: true });
5034
5478
  }
5035
5479
  function writeIfMissing3(filePath, content) {
5036
- if (!fs11.existsSync(filePath)) {
5037
- fs11.writeFileSync(filePath, content, "utf8");
5480
+ if (!fs13.existsSync(filePath)) {
5481
+ fs13.writeFileSync(filePath, content, "utf8");
5038
5482
  }
5039
5483
  }
5040
5484
  function normalizeProjectId3(value) {
@@ -5042,17 +5486,17 @@ function normalizeProjectId3(value) {
5042
5486
  }
5043
5487
  function createProject(root, projectId, projectName, namingTemplate = DEFAULT_ID_NAMING_TEMPLATE) {
5044
5488
  const workspaceDir = coopWorkspaceDir(root);
5045
- const projectRoot = path16.join(workspaceDir, "projects", projectId);
5046
- ensureDir2(path16.join(workspaceDir, "projects"));
5489
+ const projectRoot = path18.join(workspaceDir, "projects", projectId);
5490
+ ensureDir2(path18.join(workspaceDir, "projects"));
5047
5491
  for (const dir of PROJECT_DIRS) {
5048
- ensureDir2(path16.join(projectRoot, dir));
5492
+ ensureDir2(path18.join(projectRoot, dir));
5049
5493
  }
5050
- writeIfMissing3(path16.join(projectRoot, "config.yml"), PROJECT_CONFIG_TEMPLATE(projectId, projectName, namingTemplate));
5051
- if (!fs11.existsSync(path16.join(projectRoot, "schema-version"))) {
5494
+ writeIfMissing3(path18.join(projectRoot, "config.yml"), PROJECT_CONFIG_TEMPLATE(projectId, projectName, namingTemplate));
5495
+ if (!fs13.existsSync(path18.join(projectRoot, "schema-version"))) {
5052
5496
  write_schema_version2(projectRoot, CURRENT_SCHEMA_VERSION3);
5053
5497
  }
5054
- writeIfMissing3(path16.join(projectRoot, "templates/task.md"), TASK_TEMPLATE2);
5055
- writeIfMissing3(path16.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE2);
5498
+ writeIfMissing3(path18.join(projectRoot, "templates/task.md"), TASK_TEMPLATE2);
5499
+ writeIfMissing3(path18.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE2);
5056
5500
  return projectRoot;
5057
5501
  }
5058
5502
  function registerProjectCommand(program) {
@@ -5080,7 +5524,7 @@ function registerProjectCommand(program) {
5080
5524
  }
5081
5525
  console.log(`id=${active.id}`);
5082
5526
  console.log(`name=${active.name}`);
5083
- console.log(`path=${path16.relative(root, active.root)}`);
5527
+ console.log(`path=${path18.relative(root, active.root)}`);
5084
5528
  console.log(`layout=${active.layout}`);
5085
5529
  });
5086
5530
  project.command("use").description("Set the active COOP project").argument("<id>", "Project id").action((id) => {
@@ -5112,26 +5556,127 @@ function registerProjectCommand(program) {
5112
5556
  version: 2,
5113
5557
  current_project: workspace.current_project || projectId
5114
5558
  });
5115
- console.log(`Created project '${projectId}' at ${path16.relative(root, projectRoot)}`);
5559
+ console.log(`Created project '${projectId}' at ${path18.relative(root, projectRoot)}`);
5560
+ });
5561
+ }
5562
+
5563
+ // src/commands/prompt.ts
5564
+ import fs14 from "fs";
5565
+ function buildPayload(root, id) {
5566
+ const { parsed } = loadTaskEntry(root, id);
5567
+ const context = readWorkingContext(root, resolveCoopHome());
5568
+ const task = parsed.task;
5569
+ return {
5570
+ task: {
5571
+ id: task.id,
5572
+ title: task.title,
5573
+ status: task.status,
5574
+ type: task.type,
5575
+ track: task.track ?? null,
5576
+ delivery_tracks: task.delivery_tracks ?? [],
5577
+ delivery: task.delivery ?? null,
5578
+ priority: task.priority ?? null,
5579
+ effective_priority: taskEffectivePriority(task, context.track),
5580
+ fix_versions: task.fix_versions ?? [],
5581
+ released_in: task.released_in ?? [],
5582
+ acceptance: task.acceptance ?? [],
5583
+ tests_required: task.tests_required ?? [],
5584
+ refs: {
5585
+ authority: task.origin?.authority_refs ?? [],
5586
+ derived: task.origin?.derived_refs ?? []
5587
+ },
5588
+ execution: task.execution ?? null
5589
+ },
5590
+ working_context: context,
5591
+ body: parsed.body.trim()
5592
+ };
5593
+ }
5594
+ function renderMarkdown(payload) {
5595
+ const lines = [
5596
+ `# Task Prompt: ${payload.task.id}`,
5597
+ "",
5598
+ `- Title: ${payload.task.title}`,
5599
+ `- Status: ${payload.task.status}`,
5600
+ `- Type: ${payload.task.type}`,
5601
+ `- Home track: ${payload.task.track ?? "-"}`,
5602
+ `- Delivery tracks: ${payload.task.delivery_tracks.length > 0 ? payload.task.delivery_tracks.join(", ") : "-"}`,
5603
+ `- Delivery: ${payload.task.delivery ?? "-"}`,
5604
+ `- Effective priority: ${payload.task.effective_priority}`,
5605
+ `- Fix versions: ${payload.task.fix_versions.length > 0 ? payload.task.fix_versions.join(", ") : "-"}`,
5606
+ `- Released in: ${payload.task.released_in.length > 0 ? payload.task.released_in.join(", ") : "-"}`,
5607
+ "",
5608
+ "## Acceptance",
5609
+ ...payload.task.acceptance.length > 0 ? payload.task.acceptance.map((entry) => `- ${entry}`) : ["- none"],
5610
+ "",
5611
+ "## Tests Required",
5612
+ ...payload.task.tests_required.length > 0 ? payload.task.tests_required.map((entry) => `- ${entry}`) : ["- none"],
5613
+ "",
5614
+ "## Refs",
5615
+ `- Authority: ${payload.task.refs.authority.length > 0 ? payload.task.refs.authority.join(", ") : "-"}`,
5616
+ `- Derived: ${payload.task.refs.derived.length > 0 ? payload.task.refs.derived.join(", ") : "-"}`,
5617
+ "",
5618
+ "## Working Context",
5619
+ `- Track: ${payload.working_context.track ?? "-"}`,
5620
+ `- Delivery: ${payload.working_context.delivery ?? "-"}`,
5621
+ `- Version: ${payload.working_context.version ?? "-"}`,
5622
+ "",
5623
+ "## Task Body",
5624
+ payload.body || "-"
5625
+ ];
5626
+ if (payload.task.execution) {
5627
+ lines.push("", "## Execution Hints", "```json", JSON.stringify(payload.task.execution, null, 2), "```");
5628
+ }
5629
+ return `${lines.join("\n")}
5630
+ `;
5631
+ }
5632
+ function registerPromptCommand(program) {
5633
+ program.command("prompt").description("Generate a manual agent prompt from a task").argument("<id-or-type>", "Task id/alias, or the literal `task` followed by an id").argument("[id]", "Task id when an explicit entity type is provided").option("--format <format>", "text|markdown|json", "text").option("--save <path>", "Write the prompt to a file as well as stdout").action((first, second, options) => {
5634
+ const { entity, id } = resolveOptionalEntityArg(first, second, ["task"], "task");
5635
+ if (entity !== "task") {
5636
+ throw new Error("Only task prompts are supported.");
5637
+ }
5638
+ const root = resolveRepoRoot();
5639
+ const payload = buildPayload(root, id);
5640
+ const format = options.format ?? "text";
5641
+ let output3 = "";
5642
+ if (format === "json") {
5643
+ output3 = `${JSON.stringify(payload, null, 2)}
5644
+ `;
5645
+ } else {
5646
+ output3 = renderMarkdown(payload);
5647
+ }
5648
+ if (options.save) {
5649
+ fs14.writeFileSync(options.save, output3, "utf8");
5650
+ }
5651
+ if (isVerboseRequested()) {
5652
+ for (const line of formatResolvedContextMessage({
5653
+ track: payload.working_context.track ? { value: payload.working_context.track, source: "use" } : void 0,
5654
+ delivery: payload.working_context.delivery ? { value: payload.working_context.delivery, source: "use" } : void 0,
5655
+ version: payload.working_context.version ? { value: payload.working_context.version, source: "use" } : void 0
5656
+ })) {
5657
+ console.log(line);
5658
+ }
5659
+ }
5660
+ console.log(output3.trimEnd());
5116
5661
  });
5117
5662
  }
5118
5663
 
5119
5664
  // src/commands/refine.ts
5120
- import fs12 from "fs";
5121
- import path17 from "path";
5122
- import { parseIdeaFile as parseIdeaFile4, parseTaskFile as parseTaskFile10 } from "@kitsy/coop-core";
5665
+ import fs15 from "fs";
5666
+ import path19 from "path";
5667
+ import { parseIdeaFile as parseIdeaFile5, parseTaskFile as parseTaskFile11 } from "@kitsy/coop-core";
5123
5668
  import { create_provider_refinement_client, refine_idea_to_draft, refine_task_to_draft } from "@kitsy/coop-ai";
5124
- function resolveTaskFile(root, idOrAlias) {
5669
+ function resolveTaskFile2(root, idOrAlias) {
5125
5670
  const target = resolveReference(root, idOrAlias, "task");
5126
- return path17.join(root, ...target.file.split("/"));
5671
+ return path19.join(root, ...target.file.split("/"));
5127
5672
  }
5128
- function resolveIdeaFile2(root, idOrAlias) {
5673
+ function resolveIdeaFile3(root, idOrAlias) {
5129
5674
  const target = resolveReference(root, idOrAlias, "idea");
5130
- return path17.join(root, ...target.file.split("/"));
5675
+ return path19.join(root, ...target.file.split("/"));
5131
5676
  }
5132
5677
  async function readSupplementalInput(root, options) {
5133
5678
  if (options.inputFile?.trim()) {
5134
- return fs12.readFileSync(path17.resolve(root, options.inputFile.trim()), "utf8");
5679
+ return fs15.readFileSync(path19.resolve(root, options.inputFile.trim()), "utf8");
5135
5680
  }
5136
5681
  if (options.stdin) {
5137
5682
  return readStdinText();
@@ -5149,11 +5694,11 @@ function loadAuthorityContext(root, refs) {
5149
5694
  for (const ref of refs ?? []) {
5150
5695
  const filePart = extractRefFile(ref);
5151
5696
  if (!filePart) continue;
5152
- const fullPath = path17.resolve(root, filePart);
5153
- if (!fs12.existsSync(fullPath) || !fs12.statSync(fullPath).isFile()) continue;
5697
+ const fullPath = path19.resolve(root, filePart);
5698
+ if (!fs15.existsSync(fullPath) || !fs15.statSync(fullPath).isFile()) continue;
5154
5699
  out.push({
5155
5700
  ref,
5156
- content: fs12.readFileSync(fullPath, "utf8")
5701
+ content: fs15.readFileSync(fullPath, "utf8")
5157
5702
  });
5158
5703
  }
5159
5704
  return out;
@@ -5163,8 +5708,8 @@ function registerRefineCommand(program) {
5163
5708
  refine.command("idea").description("Refine an idea into proposed tasks").argument("<id>", "Idea ID or alias").option("--apply", "Apply the generated draft immediately").option("--output-file <path>", "Write the draft to a specific file").option("--input-file <path>", "Supplemental planning brief").option("--stdin", "Read supplemental planning brief from stdin").action(async (id, options) => {
5164
5709
  const root = resolveRepoRoot();
5165
5710
  const projectDir = ensureCoopInitialized(root);
5166
- const ideaFile = resolveIdeaFile2(root, id);
5167
- const parsed = parseIdeaFile4(ideaFile);
5711
+ const ideaFile = resolveIdeaFile3(root, id);
5712
+ const parsed = parseIdeaFile5(ideaFile);
5168
5713
  const supplemental = await readSupplementalInput(root, options);
5169
5714
  const client = create_provider_refinement_client(readCoopConfig(root).raw);
5170
5715
  const refined = await refine_idea_to_draft(
@@ -5187,8 +5732,8 @@ function registerRefineCommand(program) {
5187
5732
  refine.command("task").description("Refine a task into an execution-ready update draft").argument("<id>", "Task ID or alias").option("--apply", "Apply the generated draft immediately").option("--output-file <path>", "Write the draft to a specific file").option("--input-file <path>", "Supplemental planning brief").option("--stdin", "Read supplemental planning brief from stdin").action(async (id, options) => {
5188
5733
  const root = resolveRepoRoot();
5189
5734
  const projectDir = ensureCoopInitialized(root);
5190
- const taskFile = resolveTaskFile(root, id);
5191
- const parsed = parseTaskFile10(taskFile);
5735
+ const taskFile = resolveTaskFile2(root, id);
5736
+ const parsed = parseTaskFile11(taskFile);
5192
5737
  const supplemental = await readSupplementalInput(root, options);
5193
5738
  const client = create_provider_refinement_client(readCoopConfig(root).raw);
5194
5739
  const draft = await refine_task_to_draft(
@@ -5216,15 +5761,15 @@ function registerRefineCommand(program) {
5216
5761
  const written = applyRefinementDraft(root, projectDir, draft);
5217
5762
  console.log(`[COOP] applied draft from ${draftInput.source}: ${written.length} task file(s) updated`);
5218
5763
  for (const filePath of written) {
5219
- console.log(`- ${path17.relative(root, filePath)}`);
5764
+ console.log(`- ${path19.relative(root, filePath)}`);
5220
5765
  }
5221
5766
  });
5222
5767
  }
5223
5768
 
5224
5769
  // src/commands/run.ts
5225
- import fs13 from "fs";
5226
- import path18 from "path";
5227
- import { load_graph as load_graph6, parseTaskFile as parseTaskFile11 } from "@kitsy/coop-core";
5770
+ import fs16 from "fs";
5771
+ import path20 from "path";
5772
+ import { load_graph as load_graph8, parseTaskFile as parseTaskFile12 } from "@kitsy/coop-core";
5228
5773
  import {
5229
5774
  build_contract,
5230
5775
  create_provider_agent_client,
@@ -5233,11 +5778,11 @@ import {
5233
5778
  } from "@kitsy/coop-ai";
5234
5779
  function loadTask(root, idOrAlias) {
5235
5780
  const target = resolveReference(root, idOrAlias, "task");
5236
- const taskFile = path18.join(root, ...target.file.split("/"));
5237
- if (!fs13.existsSync(taskFile)) {
5781
+ const taskFile = path20.join(root, ...target.file.split("/"));
5782
+ if (!fs16.existsSync(taskFile)) {
5238
5783
  throw new Error(`Task file not found: ${target.file}`);
5239
5784
  }
5240
- return parseTaskFile11(taskFile).task;
5785
+ return parseTaskFile12(taskFile).task;
5241
5786
  }
5242
5787
  function printContract(contract) {
5243
5788
  console.log(JSON.stringify(contract, null, 2));
@@ -5250,7 +5795,7 @@ function registerRunCommand(program) {
5250
5795
  run.command("task").description("Execute a task runbook").argument("<id>", "Task ID or alias").option("--step <step>", "Run a single step by step id").option("--dry-run", "Print contract without executing").action(async (id, options) => {
5251
5796
  const root = resolveRepoRoot();
5252
5797
  const coop = ensureCoopInitialized(root);
5253
- const graph = load_graph6(coopDir(root));
5798
+ const graph = load_graph8(coopDir(root));
5254
5799
  const task = loadTask(root, id);
5255
5800
  const config = readCoopConfig(root).raw;
5256
5801
  const routedAgent = select_agent(task, config);
@@ -5273,26 +5818,141 @@ function registerRunCommand(program) {
5273
5818
  on_progress: (message) => console.log(`[COOP] ${message}`)
5274
5819
  });
5275
5820
  if (result.status === "failed") {
5276
- throw new Error(`Run failed: ${result.run.id}. Log: ${path18.relative(root, result.log_path)}`);
5821
+ throw new Error(`Run failed: ${result.run.id}. Log: ${path20.relative(root, result.log_path)}`);
5277
5822
  }
5278
5823
  if (result.status === "paused") {
5279
5824
  console.log(`[COOP] run paused: ${result.run.id}`);
5280
- console.log(`[COOP] log: ${path18.relative(root, result.log_path)}`);
5825
+ console.log(`[COOP] log: ${path20.relative(root, result.log_path)}`);
5281
5826
  return;
5282
5827
  }
5283
5828
  console.log(`[COOP] run completed: ${result.run.id}`);
5284
- console.log(`[COOP] log: ${path18.relative(root, result.log_path)}`);
5829
+ console.log(`[COOP] log: ${path20.relative(root, result.log_path)}`);
5830
+ });
5831
+ }
5832
+
5833
+ // src/commands/search.ts
5834
+ import { load_graph as load_graph9, parseDeliveryFile as parseDeliveryFile3, parseIdeaFile as parseIdeaFile6, parseTaskFile as parseTaskFile13 } from "@kitsy/coop-core";
5835
+ function haystackForTask(task) {
5836
+ return [
5837
+ task.id,
5838
+ task.title,
5839
+ ...task.aliases ?? [],
5840
+ ...task.tags ?? [],
5841
+ ...task.acceptance ?? [],
5842
+ ...task.tests_required ?? [],
5843
+ ...task.fix_versions ?? [],
5844
+ ...task.released_in ?? [],
5845
+ ...task.delivery_tracks ?? [],
5846
+ ...task.origin?.authority_refs ?? [],
5847
+ ...task.origin?.derived_refs ?? [],
5848
+ ...task.comments?.map((entry) => entry.body) ?? []
5849
+ ].join("\n").toLowerCase();
5850
+ }
5851
+ function haystackForIdea(idea, body) {
5852
+ return [idea.id, idea.title, ...idea.aliases ?? [], ...idea.tags, ...idea.linked_tasks, body].join("\n").toLowerCase();
5853
+ }
5854
+ function haystackForDelivery(delivery, body) {
5855
+ return [
5856
+ delivery.id,
5857
+ delivery.name,
5858
+ delivery.status,
5859
+ ...delivery.scope.include ?? [],
5860
+ ...delivery.scope.exclude ?? [],
5861
+ ...delivery.capacity_profiles ?? [],
5862
+ body
5863
+ ].join("\n").toLowerCase();
5864
+ }
5865
+ function includesQuery(haystack, query) {
5866
+ return haystack.includes(query.toLowerCase());
5867
+ }
5868
+ function registerSearchCommand(program) {
5869
+ program.command("search").description("Deterministic text search across tasks, ideas, and deliveries").argument("<query>", "Search text").option("--kind <kind>", "task|idea|delivery|all", "all").option("--track <id>", "Filter tasks by home/contributing track").option("--delivery <id>", "Filter tasks by delivery").option("--version <id>", "Filter tasks by fix/released version").option("--status <status>", "Filter by item status").option("--limit <n>", "Maximum result count", "20").option("--open", "Require exactly one match and print only that resolved summary row").action((query, options) => {
5870
+ const root = resolveRepoRoot();
5871
+ const graph = load_graph9(coopDir(root));
5872
+ const deliveryScope = options.delivery ? new Set(graph.deliveries.get(options.delivery)?.scope.include ?? []) : null;
5873
+ const limit = Number(options.limit ?? "20");
5874
+ const rows = [];
5875
+ if (options.kind === "all" || options.kind === "task" || !options.kind) {
5876
+ for (const filePath of listTaskFiles(root)) {
5877
+ const parsed = parseTaskFile13(filePath);
5878
+ const task = parsed.task;
5879
+ if (options.status && task.status !== options.status) continue;
5880
+ if (options.track && task.track !== options.track && !(task.delivery_tracks ?? []).includes(options.track)) {
5881
+ continue;
5882
+ }
5883
+ if (options.delivery && task.delivery !== options.delivery && !deliveryScope?.has(task.id)) continue;
5884
+ if (options.version && !(task.fix_versions ?? []).includes(options.version) && !(task.released_in ?? []).includes(options.version)) {
5885
+ continue;
5886
+ }
5887
+ if (!includesQuery(`${haystackForTask(task)}
5888
+ ${parsed.body}`, query)) continue;
5889
+ rows.push({
5890
+ kind: "task",
5891
+ id: task.id,
5892
+ title: task.title,
5893
+ status: task.status,
5894
+ extra: task.track ?? "-"
5895
+ });
5896
+ }
5897
+ }
5898
+ if (options.kind === "all" || options.kind === "idea") {
5899
+ for (const filePath of listIdeaFiles(root)) {
5900
+ const parsed = parseIdeaFile6(filePath);
5901
+ if (options.status && parsed.idea.status !== options.status) continue;
5902
+ if (!includesQuery(haystackForIdea(parsed.idea, parsed.body), query)) continue;
5903
+ rows.push({
5904
+ kind: "idea",
5905
+ id: parsed.idea.id,
5906
+ title: parsed.idea.title,
5907
+ status: parsed.idea.status
5908
+ });
5909
+ }
5910
+ }
5911
+ if (options.kind === "all" || options.kind === "delivery") {
5912
+ for (const filePath of listDeliveryFiles(root)) {
5913
+ const parsed = parseDeliveryFile3(filePath);
5914
+ if (options.status && parsed.delivery.status !== options.status) continue;
5915
+ if (!includesQuery(haystackForDelivery(parsed.delivery, parsed.body), query)) continue;
5916
+ rows.push({
5917
+ kind: "delivery",
5918
+ id: parsed.delivery.id,
5919
+ title: parsed.delivery.name,
5920
+ status: parsed.delivery.status,
5921
+ extra: parsed.delivery.target_date ?? "-"
5922
+ });
5923
+ }
5924
+ }
5925
+ rows.sort((a, b) => `${a.kind}:${a.id}`.localeCompare(`${b.kind}:${b.id}`));
5926
+ if (options.open) {
5927
+ if (rows.length === 0) {
5928
+ console.log("No matches found.");
5929
+ return;
5930
+ }
5931
+ if (rows.length !== 1) {
5932
+ throw new Error(`--open requires exactly one match, found ${rows.length}. Narrow the query or add filters.`);
5933
+ }
5934
+ }
5935
+ for (const row of rows.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 20)) {
5936
+ const suffix = row.extra ? ` | ${row.extra}` : "";
5937
+ console.log(`${row.kind} ${row.id} ${row.status} ${row.title}${suffix}`);
5938
+ if (options.open) {
5939
+ break;
5940
+ }
5941
+ }
5942
+ if (rows.length === 0) {
5943
+ console.log("No matches found.");
5944
+ }
5285
5945
  });
5286
5946
  }
5287
5947
 
5288
5948
  // src/server/api.ts
5289
- import fs14 from "fs";
5949
+ import fs17 from "fs";
5290
5950
  import http2 from "http";
5291
- import path19 from "path";
5951
+ import path21 from "path";
5292
5952
  import {
5293
5953
  analyze_feasibility as analyze_feasibility2,
5294
- load_graph as load_graph7,
5295
- parseTaskFile as parseTaskFile12,
5954
+ load_graph as load_graph10,
5955
+ parseTaskFile as parseTaskFile14,
5296
5956
  resolve_external_dependencies
5297
5957
  } from "@kitsy/coop-core";
5298
5958
  function json(res, statusCode, payload) {
@@ -5334,12 +5994,12 @@ function taskSummary(graph, task, external = []) {
5334
5994
  };
5335
5995
  }
5336
5996
  function taskFileById(root, id) {
5337
- const tasksDir = path19.join(resolveProject(root).root, "tasks");
5338
- if (!fs14.existsSync(tasksDir)) return null;
5339
- const entries = fs14.readdirSync(tasksDir, { withFileTypes: true });
5997
+ const tasksDir = path21.join(resolveProject(root).root, "tasks");
5998
+ if (!fs17.existsSync(tasksDir)) return null;
5999
+ const entries = fs17.readdirSync(tasksDir, { withFileTypes: true });
5340
6000
  const target = `${id}.md`.toLowerCase();
5341
6001
  const match = entries.find((entry) => entry.isFile() && entry.name.toLowerCase() === target);
5342
- return match ? path19.join(tasksDir, match.name) : null;
6002
+ return match ? path21.join(tasksDir, match.name) : null;
5343
6003
  }
5344
6004
  function loadRemoteConfig(root) {
5345
6005
  const raw = readCoopConfig(root).raw;
@@ -5405,7 +6065,7 @@ function createApiServer(root, options = {}) {
5405
6065
  }
5406
6066
  const project = resolveProject(repoRoot);
5407
6067
  const coopPath = project.root;
5408
- const graph = load_graph7(coopPath);
6068
+ const graph = load_graph10(coopPath);
5409
6069
  const resolutions = await externalResolutions(repoRoot, graph, options);
5410
6070
  if (pathname === "/api/tasks") {
5411
6071
  const tasks = Array.from(graph.nodes.values()).sort((a, b) => a.id.localeCompare(b.id)).map((task) => taskSummary(graph, task, resolutions.get(task.id) ?? []));
@@ -5420,7 +6080,7 @@ function createApiServer(root, options = {}) {
5420
6080
  notFound(res, `Task '${taskId}' not found.`);
5421
6081
  return;
5422
6082
  }
5423
- const parsed = parseTaskFile12(filePath);
6083
+ const parsed = parseTaskFile14(filePath);
5424
6084
  const task = graph.nodes.get(parsed.task.id) ?? parsed.task;
5425
6085
  json(res, 200, {
5426
6086
  ...workspaceMeta(repoRoot),
@@ -5429,7 +6089,7 @@ function createApiServer(root, options = {}) {
5429
6089
  created: task.created,
5430
6090
  updated: task.updated,
5431
6091
  body: parsed.body,
5432
- file_path: path19.relative(repoRoot, filePath).replace(/\\/g, "/")
6092
+ file_path: path21.relative(repoRoot, filePath).replace(/\\/g, "/")
5433
6093
  }
5434
6094
  });
5435
6095
  return;
@@ -5517,9 +6177,9 @@ function registerServeCommand(program) {
5517
6177
  }
5518
6178
 
5519
6179
  // src/commands/show.ts
5520
- import fs15 from "fs";
5521
- import path20 from "path";
5522
- import { parseIdeaFile as parseIdeaFile5, parseTaskFile as parseTaskFile13 } from "@kitsy/coop-core";
6180
+ import fs18 from "fs";
6181
+ import path22 from "path";
6182
+ import { parseIdeaFile as parseIdeaFile7 } from "@kitsy/coop-core";
5523
6183
  function stringify(value) {
5524
6184
  if (value === null || value === void 0) return "-";
5525
6185
  if (Array.isArray(value)) return value.length > 0 ? value.join(", ") : "-";
@@ -5537,13 +6197,13 @@ function pushListSection(lines, title, values) {
5537
6197
  }
5538
6198
  }
5539
6199
  function loadComputedFromIndex(root, taskId) {
5540
- const indexPath = path20.join(ensureCoopInitialized(root), ".index", "tasks.json");
5541
- if (!fs15.existsSync(indexPath)) {
6200
+ const indexPath = path22.join(ensureCoopInitialized(root), ".index", "tasks.json");
6201
+ if (!fs18.existsSync(indexPath)) {
5542
6202
  return null;
5543
6203
  }
5544
6204
  let parsed;
5545
6205
  try {
5546
- parsed = JSON.parse(fs15.readFileSync(indexPath, "utf8"));
6206
+ parsed = JSON.parse(fs18.readFileSync(indexPath, "utf8"));
5547
6207
  } catch {
5548
6208
  return null;
5549
6209
  }
@@ -5578,12 +6238,16 @@ function loadComputedFromIndex(root, taskId) {
5578
6238
  }
5579
6239
  return null;
5580
6240
  }
6241
+ function formatTimeSummary(task) {
6242
+ const planned = task.time?.planned_hours;
6243
+ const worked = (task.time?.logs ?? []).filter((entry) => entry.kind === "worked").reduce((sum, entry) => sum + entry.hours, 0);
6244
+ const plannedLogged = (task.time?.logs ?? []).filter((entry) => entry.kind === "planned").reduce((sum, entry) => sum + entry.hours, 0);
6245
+ return `planned_hours=${planned ?? "-"} | planned_logged=${plannedLogged || 0} | worked=${worked || 0}`;
6246
+ }
5581
6247
  function showTask(taskId) {
5582
6248
  const root = resolveRepoRoot();
5583
- const coop = ensureCoopInitialized(root);
5584
- const target = resolveReference(root, taskId, "task");
5585
- const taskFile = path20.join(root, ...target.file.split("/"));
5586
- const parsed = parseTaskFile13(taskFile);
6249
+ const context = readWorkingContext(root, resolveCoopHome());
6250
+ const { filePath, parsed } = loadTaskEntry(root, taskId);
5587
6251
  const task = parsed.task;
5588
6252
  const body = parsed.body.trim();
5589
6253
  const computed = loadComputedFromIndex(root, task.id);
@@ -5593,15 +6257,22 @@ function showTask(taskId) {
5593
6257
  `Status: ${task.status}`,
5594
6258
  `Type: ${task.type}`,
5595
6259
  `Priority: ${task.priority ?? "-"}`,
6260
+ `Effective Priority: ${taskEffectivePriority(task, context.track)}`,
5596
6261
  `Track: ${task.track ?? "-"}`,
6262
+ `Delivery Tracks: ${stringify(task.delivery_tracks)}`,
6263
+ `Priority Context: ${stringify(task.priority_context)}`,
5597
6264
  `Assignee: ${task.assignee ?? "-"}`,
5598
6265
  `Delivery: ${task.delivery ?? "-"}`,
6266
+ `Fix Versions: ${stringify(task.fix_versions)}`,
6267
+ `Released In: ${stringify(task.released_in)}`,
6268
+ `Story Points: ${task.story_points ?? "-"}`,
6269
+ `Time: ${formatTimeSummary(task)}`,
5599
6270
  `Aliases: ${stringify(task.aliases)}`,
5600
6271
  `Depends On: ${stringify(task.depends_on)}`,
5601
6272
  `Tags: ${stringify(task.tags)}`,
5602
6273
  `Created: ${task.created}`,
5603
6274
  `Updated: ${task.updated}`,
5604
- `File: ${path20.relative(root, taskFile)}`,
6275
+ `File: ${path22.relative(root, filePath)}`,
5605
6276
  ""
5606
6277
  ];
5607
6278
  pushListSection(lines, "Acceptance", task.acceptance);
@@ -5618,15 +6289,25 @@ function showTask(taskId) {
5618
6289
  lines.push(`- Promoted To: ${stringify(task.origin.promoted_to)}`);
5619
6290
  lines.push(`- Snapshot SHA256: ${task.origin.snapshot_sha256 ?? "-"}`);
5620
6291
  }
5621
- lines.push(
5622
- "",
5623
- "Body:",
5624
- body || "-",
5625
- "",
5626
- "Computed:"
5627
- );
6292
+ lines.push("", "Comments:");
6293
+ if (!task.comments || task.comments.length === 0) {
6294
+ lines.push("- none");
6295
+ } else {
6296
+ for (const comment of task.comments) {
6297
+ lines.push(`- ${comment.at} ${comment.author}: ${comment.body}`);
6298
+ }
6299
+ }
6300
+ lines.push("", "Time Logs:");
6301
+ if (!task.time?.logs || task.time.logs.length === 0) {
6302
+ lines.push("- none");
6303
+ } else {
6304
+ for (const entry of task.time.logs) {
6305
+ lines.push(`- ${entry.at} ${entry.actor} ${entry.kind} ${entry.hours}h${entry.note ? ` (${entry.note})` : ""}`);
6306
+ }
6307
+ }
6308
+ lines.push("", "Body:", body || "-", "", "Computed:");
5628
6309
  if (!computed) {
5629
- lines.push(`index not built (${path20.relative(root, path20.join(coop, ".index", "tasks.json"))} missing)`);
6310
+ lines.push(`index not built (${path22.relative(root, path22.join(ensureCoopInitialized(root), ".index", "tasks.json"))} missing)`);
5630
6311
  } else {
5631
6312
  for (const [key, value] of Object.entries(computed)) {
5632
6313
  lines.push(`- ${key}: ${stringify(value)}`);
@@ -5636,10 +6317,9 @@ function showTask(taskId) {
5636
6317
  }
5637
6318
  function showIdea(ideaId) {
5638
6319
  const root = resolveRepoRoot();
5639
- ensureCoopInitialized(root);
5640
6320
  const target = resolveReference(root, ideaId, "idea");
5641
- const ideaFile = path20.join(root, ...target.file.split("/"));
5642
- const parsed = parseIdeaFile5(ideaFile);
6321
+ const ideaFile = path22.join(root, ...target.file.split("/"));
6322
+ const parsed = parseIdeaFile7(ideaFile);
5643
6323
  const idea = parsed.idea;
5644
6324
  const body = parsed.body.trim();
5645
6325
  const lines = [
@@ -5652,29 +6332,68 @@ function showIdea(ideaId) {
5652
6332
  `Tags: ${stringify(idea.tags)}`,
5653
6333
  `Linked Tasks: ${stringify(idea.linked_tasks)}`,
5654
6334
  `Created: ${idea.created}`,
5655
- `File: ${path20.relative(root, ideaFile)}`,
6335
+ `File: ${path22.relative(root, ideaFile)}`,
5656
6336
  "",
5657
6337
  "Body:",
5658
6338
  body || "-"
5659
6339
  ];
5660
6340
  console.log(lines.join("\n"));
5661
6341
  }
6342
+ function showDelivery(ref) {
6343
+ const root = resolveRepoRoot();
6344
+ const { filePath, delivery, body } = resolveDeliveryEntry(root, ref);
6345
+ const lines = [
6346
+ `Delivery: ${delivery.id}`,
6347
+ `Name: ${delivery.name}`,
6348
+ `Status: ${delivery.status}`,
6349
+ `Target Date: ${delivery.target_date ?? "-"}`,
6350
+ `Started Date: ${delivery.started_date ?? "-"}`,
6351
+ `Delivered Date: ${delivery.delivered_date ?? "-"}`,
6352
+ `Capacity Profiles: ${delivery.capacity_profiles.length > 0 ? delivery.capacity_profiles.join(", ") : "-"}`,
6353
+ `Scope Include: ${delivery.scope.include.length > 0 ? delivery.scope.include.join(", ") : "-"}`,
6354
+ `Scope Exclude: ${delivery.scope.exclude.length > 0 ? delivery.scope.exclude.join(", ") : "-"}`,
6355
+ `File: ${path22.relative(root, filePath)}`,
6356
+ "",
6357
+ "Body:",
6358
+ body.trim() || "-"
6359
+ ];
6360
+ console.log(lines.join("\n"));
6361
+ }
6362
+ function showByReference(ref) {
6363
+ const root = resolveRepoRoot();
6364
+ try {
6365
+ const resolved = resolveReference(root, ref);
6366
+ if (resolved.type === "task") {
6367
+ showTask(ref);
6368
+ return;
6369
+ }
6370
+ showIdea(ref);
6371
+ return;
6372
+ } catch {
6373
+ showDelivery(ref);
6374
+ }
6375
+ }
5662
6376
  function registerShowCommand(program) {
5663
- const show = program.command("show").description("Show detailed COOP entities");
6377
+ const show = program.command("show").description("Show detailed COOP entities").argument("[ref]", "Task, idea, or delivery reference").action((ref) => {
6378
+ if (!ref?.trim()) {
6379
+ throw new Error("Provide a task, idea, or delivery reference.");
6380
+ }
6381
+ showByReference(ref);
6382
+ });
5664
6383
  show.command("task").description("Show task details").argument("<id>", "Task ID").action((id) => {
5665
6384
  showTask(id);
5666
6385
  });
5667
6386
  show.command("idea").description("Show idea details").argument("<id>", "Idea ID").action((id) => {
5668
6387
  showIdea(id);
5669
6388
  });
5670
- show.command("delivery").description("Show delivery details (Phase 2)").argument("<name>", "Delivery name").action(() => {
5671
- printNotImplemented("coop show delivery", 2);
6389
+ show.command("delivery").description("Show delivery details").argument("<id>", "Delivery id or name").action((id) => {
6390
+ showDelivery(id);
5672
6391
  });
5673
6392
  }
5674
6393
 
5675
6394
  // src/commands/status.ts
5676
6395
  import chalk4 from "chalk";
5677
- import { TaskStatus as TaskStatus4, analyze_feasibility as analyze_feasibility3, load_graph as load_graph8 } from "@kitsy/coop-core";
6396
+ import { TaskStatus as TaskStatus4, analyze_feasibility as analyze_feasibility3, load_graph as load_graph11 } from "@kitsy/coop-core";
5678
6397
  function countBy(values, keyFn) {
5679
6398
  const out = /* @__PURE__ */ new Map();
5680
6399
  for (const value of values) {
@@ -5695,10 +6414,16 @@ function completionSummary(done, totalTasks) {
5695
6414
  const percent = totalTasks > 0 ? Math.round(done / totalTasks * 100) : 0;
5696
6415
  return `${done}/${totalTasks} (${percent}%)`;
5697
6416
  }
6417
+ function workedHours(tasks) {
6418
+ return tasks.reduce(
6419
+ (sum, task) => sum + (task.time?.logs ?? []).filter((entry) => entry.kind === "worked").reduce((entrySum, entry) => entrySum + entry.hours, 0),
6420
+ 0
6421
+ );
6422
+ }
5698
6423
  function registerStatusCommand(program) {
5699
6424
  program.command("status").description("Project dashboard overview").option("--today <date>", "Evaluation date (YYYY-MM-DD)").action((options) => {
5700
6425
  const root = resolveRepoRoot();
5701
- const graph = load_graph8(coopDir(root));
6426
+ const graph = load_graph11(coopDir(root));
5702
6427
  const today = options.today ?? /* @__PURE__ */ new Date();
5703
6428
  const tasks = Array.from(graph.nodes.values());
5704
6429
  const tasksByStatus = countBy(tasks, (task) => task.status);
@@ -5718,6 +6443,14 @@ function registerStatusCommand(program) {
5718
6443
  const trackRows = Array.from(tasksByTrack.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([track, count]) => [track, String(count)]);
5719
6444
  lines.push(formatTable(["Track", "Count"], trackRows));
5720
6445
  lines.push("");
6446
+ const totalStoryPoints = tasks.reduce((sum, task) => sum + (task.story_points ?? 0), 0);
6447
+ const totalPlannedHours = tasks.reduce((sum, task) => sum + (task.time?.planned_hours ?? 0), 0);
6448
+ const totalWorkedHours = workedHours(tasks);
6449
+ lines.push(chalk4.bold("Effort Snapshot"));
6450
+ lines.push(`Story points: ${totalStoryPoints}`);
6451
+ lines.push(`Planned hours: ${totalPlannedHours.toFixed(2)}`);
6452
+ lines.push(`Worked hours: ${totalWorkedHours.toFixed(2)}`);
6453
+ lines.push("");
5721
6454
  lines.push(chalk4.bold("Delivery Health"));
5722
6455
  const deliveryRows = [];
5723
6456
  const riskLines = [];
@@ -5769,8 +6502,8 @@ function registerTransitionCommand(program) {
5769
6502
  }
5770
6503
 
5771
6504
  // src/commands/taskflow.ts
5772
- import path21 from "path";
5773
- import { parseTaskFile as parseTaskFile14 } from "@kitsy/coop-core";
6505
+ import path23 from "path";
6506
+ import { parseTaskFile as parseTaskFile15 } from "@kitsy/coop-core";
5774
6507
  function normalizeExecutor(value) {
5775
6508
  if (!value?.trim()) return void 0;
5776
6509
  const normalized = value.trim().toLowerCase();
@@ -5807,8 +6540,8 @@ async function claimAndStart(root, taskId, options) {
5807
6540
  }
5808
6541
  }
5809
6542
  const reference = resolveReference(root, taskId, "task");
5810
- const filePath = path21.join(root, ...reference.file.split("/"));
5811
- const parsed = parseTaskFile14(filePath);
6543
+ const filePath = path23.join(root, ...reference.file.split("/"));
6544
+ const parsed = parseTaskFile15(filePath);
5812
6545
  if (parsed.task.status === "in_progress") {
5813
6546
  console.log(`Task ${parsed.task.id} is already in_progress.`);
5814
6547
  return;
@@ -5820,10 +6553,37 @@ async function claimAndStart(root, taskId, options) {
5820
6553
  });
5821
6554
  console.log(`Updated ${transitioned.task.id}: ${transitioned.from} -> ${transitioned.to}`);
5822
6555
  }
6556
+ function maybePromote(root, taskId, options) {
6557
+ if (!options.promote) {
6558
+ return;
6559
+ }
6560
+ const context = readWorkingContext(root, resolveCoopHome());
6561
+ const { filePath, parsed } = loadTaskEntry(root, taskId);
6562
+ const promoted = promoteTaskForContext(parsed.task, {
6563
+ track: options.track ?? context.track,
6564
+ version: options.version ?? context.version
6565
+ });
6566
+ writeTaskEntry(filePath, parsed, promoted);
6567
+ console.log(`Promoted ${promoted.id}`);
6568
+ }
6569
+ function printResolvedSelectionContext(root, options) {
6570
+ if (!isVerboseRequested()) {
6571
+ return;
6572
+ }
6573
+ const context = readWorkingContext(root, resolveCoopHome());
6574
+ for (const line of formatResolvedContextMessage({
6575
+ track: resolveContextValueWithSource(options.track, context.track, sharedDefault(root, "track")),
6576
+ delivery: resolveContextValueWithSource(options.delivery, context.delivery, sharedDefault(root, "delivery")),
6577
+ version: resolveContextValueWithSource(options.version, context.version, sharedDefault(root, "version"))
6578
+ })) {
6579
+ console.log(line);
6580
+ }
6581
+ }
5823
6582
  function registerTaskFlowCommands(program) {
5824
6583
  const next = program.command("next").description("Select the next COOP work item");
5825
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) => {
5826
6585
  const root = resolveRepoRoot();
6586
+ printResolvedSelectionContext(root, options);
5827
6587
  const selected = selectTopReadyTask(root, {
5828
6588
  track: options.track,
5829
6589
  delivery: options.delivery,
@@ -5833,29 +6593,49 @@ function registerTaskFlowCommands(program) {
5833
6593
  console.log(formatSelectedTask(selected.entry, selected.selection));
5834
6594
  });
5835
6595
  const pick = program.command("pick").description("Pick the next COOP work item");
5836
- pick.command("task").description("Select the top ready task and move it into active work").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("--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 (options) => {
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) => {
5837
6597
  const root = resolveRepoRoot();
5838
- const selected = selectTopReadyTask(root, {
6598
+ printResolvedSelectionContext(root, options);
6599
+ const selected = id?.trim() ? {
6600
+ entry: {
6601
+ task: loadTaskEntry(root, id).parsed.task,
6602
+ score: 0,
6603
+ readiness: statusToReadiness(loadTaskEntry(root, id).parsed.task.status),
6604
+ fits_capacity: true,
6605
+ fits_wip: true
6606
+ },
6607
+ selection: {
6608
+ track: options.track,
6609
+ delivery: options.delivery,
6610
+ version: options.version,
6611
+ executor: normalizeExecutor(options.executor),
6612
+ today: options.today
6613
+ }
6614
+ } : selectTopReadyTask(root, {
5839
6615
  track: options.track,
5840
6616
  delivery: options.delivery,
6617
+ version: options.version,
5841
6618
  executor: normalizeExecutor(options.executor),
5842
6619
  today: options.today
5843
6620
  });
5844
6621
  console.log(formatSelectedTask(selected.entry, selected.selection));
6622
+ maybePromote(root, selected.entry.task.id, options);
5845
6623
  await claimAndStart(root, selected.entry.task.id, options);
5846
6624
  });
5847
6625
  const start = program.command("start").description("Start COOP work on a task");
5848
- 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("--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) => {
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) => {
5849
6627
  const root = resolveRepoRoot();
6628
+ printResolvedSelectionContext(root, options);
5850
6629
  const taskId = id?.trim() || selectTopReadyTask(root, {
5851
6630
  track: options.track,
5852
6631
  delivery: options.delivery,
6632
+ version: options.version,
5853
6633
  executor: normalizeExecutor(options.executor),
5854
6634
  today: options.today
5855
6635
  }).entry.task.id;
5856
6636
  const reference = resolveReference(root, taskId, "task");
5857
- const filePath = path21.join(root, ...reference.file.split("/"));
5858
- const parsed = parseTaskFile14(filePath);
6637
+ const filePath = path23.join(root, ...reference.file.split("/"));
6638
+ const parsed = parseTaskFile15(filePath);
5859
6639
  console.log(
5860
6640
  formatSelectedTask(
5861
6641
  {
@@ -5868,29 +6648,31 @@ function registerTaskFlowCommands(program) {
5868
6648
  {
5869
6649
  track: options.track,
5870
6650
  delivery: options.delivery,
6651
+ version: options.version,
5871
6652
  executor: normalizeExecutor(options.executor),
5872
6653
  today: options.today
5873
6654
  }
5874
6655
  )
5875
6656
  );
6657
+ maybePromote(root, reference.id, options);
5876
6658
  await claimAndStart(root, reference.id, options);
5877
6659
  });
5878
6660
  }
5879
6661
 
5880
6662
  // src/commands/ui.ts
5881
- import fs16 from "fs";
5882
- import path22 from "path";
6663
+ import fs19 from "fs";
6664
+ import path24 from "path";
5883
6665
  import { createRequire } from "module";
5884
6666
  import { fileURLToPath } from "url";
5885
6667
  import { spawn } from "child_process";
5886
6668
  import { IndexManager as IndexManager4 } from "@kitsy/coop-core";
5887
6669
  function findPackageRoot(entryPath) {
5888
- let current = path22.dirname(entryPath);
6670
+ let current = path24.dirname(entryPath);
5889
6671
  while (true) {
5890
- if (fs16.existsSync(path22.join(current, "package.json"))) {
6672
+ if (fs19.existsSync(path24.join(current, "package.json"))) {
5891
6673
  return current;
5892
6674
  }
5893
- const parent = path22.dirname(current);
6675
+ const parent = path24.dirname(current);
5894
6676
  if (parent === current) {
5895
6677
  throw new Error(`Unable to locate package root for ${entryPath}.`);
5896
6678
  }
@@ -5939,9 +6721,9 @@ async function startUiServer(repoRoot, host, port, shouldOpen) {
5939
6721
  const project = resolveProject(repoRoot);
5940
6722
  ensureIndex(repoRoot);
5941
6723
  const uiRoot = resolveUiPackageRoot();
5942
- const requireFromUi = createRequire(path22.join(uiRoot, "package.json"));
6724
+ const requireFromUi = createRequire(path24.join(uiRoot, "package.json"));
5943
6725
  const vitePackageJson = requireFromUi.resolve("vite/package.json");
5944
- const viteBin = path22.join(path22.dirname(vitePackageJson), "bin", "vite.js");
6726
+ const viteBin = path24.join(path24.dirname(vitePackageJson), "bin", "vite.js");
5945
6727
  const url = `http://${host}:${port}`;
5946
6728
  console.log(`COOP UI: ${url}`);
5947
6729
  const child = spawn(process.execPath, [viteBin, "--host", host, "--port", String(port)], {
@@ -5987,12 +6769,197 @@ function registerUiCommand(program) {
5987
6769
  });
5988
6770
  }
5989
6771
 
6772
+ // src/commands/update.ts
6773
+ import fs20 from "fs";
6774
+ import { IdeaStatus as IdeaStatus3, TaskPriority as TaskPriority3, TaskStatus as TaskStatus5, stringifyFrontmatter as stringifyFrontmatter5 } from "@kitsy/coop-core";
6775
+ function collect(value, previous = []) {
6776
+ return [...previous, value];
6777
+ }
6778
+ function unique2(items) {
6779
+ return [...new Set(items.map((item) => item.trim()).filter(Boolean))];
6780
+ }
6781
+ function removeValues(source, values) {
6782
+ if (!source) return source;
6783
+ const removals = new Set((values ?? []).map((value) => value.trim()));
6784
+ const next = source.filter((entry) => !removals.has(entry));
6785
+ return next.length > 0 ? next : void 0;
6786
+ }
6787
+ function addValues(source, values) {
6788
+ const next = unique2([...source ?? [], ...values ?? []]);
6789
+ return next.length > 0 ? next : void 0;
6790
+ }
6791
+ function loadBody(options) {
6792
+ if (options.bodyFile) {
6793
+ return fs20.readFileSync(options.bodyFile, "utf8");
6794
+ }
6795
+ if (options.bodyStdin) {
6796
+ return fs20.readFileSync(0, "utf8");
6797
+ }
6798
+ return void 0;
6799
+ }
6800
+ function applyTrackPriorityOverrides(task, instructions) {
6801
+ if (!instructions || instructions.length === 0) {
6802
+ return task;
6803
+ }
6804
+ const next = { ...task.priority_context ?? {} };
6805
+ for (const instruction of instructions) {
6806
+ const [track, priority] = instruction.split(":", 2).map((part) => part?.trim() ?? "");
6807
+ if (!track || !priority) {
6808
+ throw new Error(`Invalid --priority-in value '${instruction}'. Expected <track>:<priority>.`);
6809
+ }
6810
+ if (!Object.values(TaskPriority3).includes(priority)) {
6811
+ throw new Error(`Invalid priority '${priority}'. Expected one of ${Object.values(TaskPriority3).join(", ")}.`);
6812
+ }
6813
+ next[track] = priority;
6814
+ }
6815
+ return { ...task, priority_context: Object.keys(next).length > 0 ? next : void 0 };
6816
+ }
6817
+ function clearTrackPriorityOverrides(task, tracks) {
6818
+ if (!tracks || tracks.length === 0 || !task.priority_context) {
6819
+ return task;
6820
+ }
6821
+ const next = { ...task.priority_context };
6822
+ for (const track of tracks) {
6823
+ delete next[track];
6824
+ }
6825
+ return { ...task, priority_context: Object.keys(next).length > 0 ? next : void 0 };
6826
+ }
6827
+ function normalizeTaskStatus(status) {
6828
+ const value = status.trim().toLowerCase();
6829
+ if (!Object.values(TaskStatus5).includes(value)) {
6830
+ throw new Error(`Invalid status '${status}'. Expected one of ${Object.values(TaskStatus5).join(", ")}.`);
6831
+ }
6832
+ return value;
6833
+ }
6834
+ function normalizeTaskPriority(priority) {
6835
+ const value = priority.trim().toLowerCase();
6836
+ if (!Object.values(TaskPriority3).includes(value)) {
6837
+ throw new Error(`Invalid priority '${priority}'. Expected one of ${Object.values(TaskPriority3).join(", ")}.`);
6838
+ }
6839
+ return value;
6840
+ }
6841
+ function renderTaskPreview(task, body) {
6842
+ return stringifyFrontmatter5(task, body);
6843
+ }
6844
+ function updateTask(id, options) {
6845
+ const root = resolveRepoRoot();
6846
+ const { filePath, parsed } = loadTaskEntry(root, id);
6847
+ let next = {
6848
+ ...parsed.task,
6849
+ updated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
6850
+ };
6851
+ if (options.title) next.title = options.title.trim();
6852
+ if (options.priority) next.priority = normalizeTaskPriority(options.priority);
6853
+ if (options.status) next.status = normalizeTaskStatus(options.status);
6854
+ if (options.assign !== void 0) next.assignee = options.assign.trim() || null;
6855
+ if (options.track) next.track = options.track.trim();
6856
+ if (options.delivery !== void 0) next.delivery = options.delivery.trim() || null;
6857
+ if (options.storyPoints !== void 0) next.story_points = Number(options.storyPoints);
6858
+ if (options.plannedHours !== void 0) next = setTaskPlannedHours(next, Number(options.plannedHours));
6859
+ next = {
6860
+ ...next,
6861
+ delivery_tracks: addValues(removeValues(next.delivery_tracks, options.removeDeliveryTrack), options.addDeliveryTrack),
6862
+ depends_on: addValues(removeValues(next.depends_on, options.removeDep), options.addDep),
6863
+ tags: addValues(removeValues(next.tags, options.removeTag), options.addTag),
6864
+ fix_versions: addValues(removeValues(next.fix_versions, options.removeFixVersion), options.addFixVersion),
6865
+ released_in: addValues(removeValues(next.released_in, options.removeReleasedIn), options.addReleasedIn),
6866
+ acceptance: addValues(removeValues(next.acceptance, options.acceptanceRemove), options.acceptanceAdd),
6867
+ tests_required: addValues(removeValues(next.tests_required, options.testsRemove), options.testsAdd)
6868
+ };
6869
+ next = clearTrackPriorityOverrides(applyTrackPriorityOverrides(next, options.priorityIn), options.clearPriorityIn);
6870
+ validateTaskForWrite(next, filePath);
6871
+ const nextBody = loadBody(options) ?? parsed.body;
6872
+ if (options.dryRun) {
6873
+ console.log(renderTaskPreview(next, nextBody).trimEnd());
6874
+ return;
6875
+ }
6876
+ writeTaskEntry(filePath, parsed, next, nextBody);
6877
+ console.log(`Updated ${next.id}`);
6878
+ }
6879
+ function updateIdea(id, options) {
6880
+ const root = resolveRepoRoot();
6881
+ const { filePath, parsed } = loadIdeaEntry(root, id);
6882
+ const nextStatus = options.status?.trim().toLowerCase();
6883
+ if (nextStatus && !Object.values(IdeaStatus3).includes(nextStatus)) {
6884
+ throw new Error(`Invalid idea status '${options.status}'. Expected one of ${Object.values(IdeaStatus3).join(", ")}.`);
6885
+ }
6886
+ const next = {
6887
+ ...parsed.idea,
6888
+ title: options.title?.trim() || parsed.idea.title,
6889
+ status: nextStatus || parsed.idea.status,
6890
+ tags: addValues(removeValues(parsed.idea.tags, options.removeTag), options.addTag) ?? [],
6891
+ linked_tasks: addValues(removeValues(parsed.idea.linked_tasks, options.removeLinkedTask), options.addLinkedTask) ?? []
6892
+ };
6893
+ const nextBody = loadBody(options) ?? parsed.body;
6894
+ if (options.dryRun) {
6895
+ console.log(stringifyFrontmatter5(next, nextBody).trimEnd());
6896
+ return;
6897
+ }
6898
+ writeIdeaFile(filePath, parsed, next, nextBody);
6899
+ console.log(`Updated ${next.id}`);
6900
+ }
6901
+ 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) => {
6903
+ let resolved = resolveOptionalEntityArg(first, second, ["task", "idea"], "task");
6904
+ if (!second && resolved.entity === "task") {
6905
+ try {
6906
+ updateTask(resolved.id, options);
6907
+ return;
6908
+ } catch (error) {
6909
+ const message = error instanceof Error ? error.message : String(error);
6910
+ if (!message.includes("not found")) {
6911
+ throw error;
6912
+ }
6913
+ resolved = { entity: "idea", id: first.trim() };
6914
+ }
6915
+ }
6916
+ if (resolved.entity === "idea") {
6917
+ updateIdea(resolved.id, options);
6918
+ return;
6919
+ }
6920
+ updateTask(resolved.id, options);
6921
+ });
6922
+ }
6923
+
6924
+ // src/commands/use.ts
6925
+ function printContext(values) {
6926
+ console.log(`Track: ${values.track ?? "-"}`);
6927
+ console.log(`Delivery: ${values.delivery ?? "-"}`);
6928
+ console.log(`Version: ${values.version ?? "-"}`);
6929
+ }
6930
+ function registerUseCommand(program) {
6931
+ const use = program.command("use").description("Manage user-local working defaults for the current COOP project");
6932
+ use.command("show").description("Show the current working context").action(() => {
6933
+ const root = resolveRepoRoot();
6934
+ printContext(readWorkingContext(root, resolveCoopHome()));
6935
+ });
6936
+ use.command("track").description("Set the default working track").argument("<id>", "Track id").action((id) => {
6937
+ const root = resolveRepoRoot();
6938
+ printContext(updateWorkingContext(root, resolveCoopHome(), { track: id }));
6939
+ });
6940
+ use.command("delivery").description("Set the default working delivery").argument("<id>", "Delivery id").action((id) => {
6941
+ const root = resolveRepoRoot();
6942
+ printContext(updateWorkingContext(root, resolveCoopHome(), { delivery: id }));
6943
+ });
6944
+ use.command("version").description("Set the default working version").argument("<id>", "Version label").action((id) => {
6945
+ const root = resolveRepoRoot();
6946
+ printContext(updateWorkingContext(root, resolveCoopHome(), { version: id }));
6947
+ });
6948
+ use.command("clear").description("Clear one working-context key or all of them").argument("[scope]", "track|delivery|version|all", "all").action((scope) => {
6949
+ if (scope !== "track" && scope !== "delivery" && scope !== "version" && scope !== "all") {
6950
+ throw new Error(`Invalid scope '${scope}'. Expected track|delivery|version|all.`);
6951
+ }
6952
+ const root = resolveRepoRoot();
6953
+ printContext(clearWorkingContext(root, resolveCoopHome(), scope));
6954
+ });
6955
+ }
6956
+
5990
6957
  // src/commands/view.ts
5991
6958
  import {
5992
6959
  analyze_feasibility as analyze_feasibility4,
5993
6960
  compute_velocity,
5994
6961
  load_completed_runs,
5995
- load_graph as load_graph9,
6962
+ load_graph as load_graph12,
5996
6963
  simulate_schedule
5997
6964
  } from "@kitsy/coop-core";
5998
6965
  var STATUS_COLUMNS = [
@@ -6055,9 +7022,12 @@ function formatAccuracy(value) {
6055
7022
  if (value == null) return "-";
6056
7023
  return `${Math.round(value * 100)}%`;
6057
7024
  }
7025
+ function workedHours2(task) {
7026
+ return (task.time?.logs ?? []).filter((entry) => entry.kind === "worked").reduce((sum, entry) => sum + entry.hours, 0);
7027
+ }
6058
7028
  function runKanban() {
6059
7029
  const root = resolveRepoRoot();
6060
- const graph = load_graph9(coopDir(root));
7030
+ const graph = load_graph12(coopDir(root));
6061
7031
  const grouped = /* @__PURE__ */ new Map();
6062
7032
  for (const status of STATUS_COLUMNS) {
6063
7033
  grouped.set(status, []);
@@ -6089,11 +7059,19 @@ function runKanban() {
6089
7059
  }
6090
7060
  function runTimeline(options) {
6091
7061
  const root = resolveRepoRoot();
6092
- const graph = load_graph9(coopDir(root));
6093
- if (!options.delivery) {
6094
- throw new Error("view timeline requires --delivery <name>.");
7062
+ const context = readWorkingContext(root, resolveCoopHome());
7063
+ const graph = load_graph12(coopDir(root));
7064
+ const resolvedDelivery = resolveContextValueWithSource(options.delivery, context.delivery, sharedDefault(root, "delivery"));
7065
+ if (isVerboseRequested()) {
7066
+ for (const line of formatResolvedContextMessage({ delivery: resolvedDelivery })) {
7067
+ console.log(line);
7068
+ }
7069
+ }
7070
+ const deliveryRef = resolvedDelivery.value;
7071
+ if (!deliveryRef) {
7072
+ throw new Error("delivery is not set; pass --delivery <name> or set a default working delivery with `coop use delivery <id>`.");
6095
7073
  }
6096
- const delivery = resolveDelivery(graph, options.delivery);
7074
+ const delivery = resolveDelivery(graph, deliveryRef);
6097
7075
  const include = new Set(delivery.scope.include);
6098
7076
  for (const id of delivery.scope.exclude) include.delete(id);
6099
7077
  const tasks = Array.from(include).map((id) => graph.nodes.get(id)).filter((task) => Boolean(task));
@@ -6134,7 +7112,7 @@ function runTimeline(options) {
6134
7112
  function runVelocity(options) {
6135
7113
  const root = resolveRepoRoot();
6136
7114
  const coopPath = coopDir(root);
6137
- const graph = load_graph9(coopPath);
7115
+ const graph = load_graph12(coopPath);
6138
7116
  const config = readCoopConfig(root).raw;
6139
7117
  const scheduling = typeof config.scheduling === "object" && config.scheduling !== null ? config.scheduling : {};
6140
7118
  const configuredWeeks = Number(scheduling.velocity_window_weeks);
@@ -6144,12 +7122,19 @@ function runVelocity(options) {
6144
7122
  today: options.today ?? /* @__PURE__ */ new Date(),
6145
7123
  graph
6146
7124
  });
7125
+ const completedTaskIds = new Set(runs.map((run) => run.task));
7126
+ const completedTasks = Array.from(completedTaskIds).map((taskId) => graph.nodes.get(taskId)).filter((task) => Boolean(task));
7127
+ const storyPointsTotal = completedTasks.reduce((sum, task) => sum + (task.story_points ?? 0), 0);
7128
+ const plannedHoursTotal = completedTasks.reduce((sum, task) => sum + (task.time?.planned_hours ?? 0), 0);
7129
+ const workedHoursTotal = completedTasks.reduce((sum, task) => sum + workedHours2(task), 0);
6147
7130
  console.log(`Velocity: last ${metrics.window_weeks} weeks`);
6148
7131
  console.log(`Trend: ${metrics.trend}`);
6149
7132
  console.log(`Completed runs: ${metrics.completed_runs}`);
6150
7133
  console.log(`Tasks/week: ${metrics.tasks_completed_per_week}`);
6151
7134
  console.log(`Hours/week: ${metrics.hours_delivered_per_week}`);
7135
+ console.log(`Story points/week: ${Number((storyPointsTotal / metrics.window_weeks).toFixed(2))}`);
6152
7136
  console.log(`Accuracy: ${formatAccuracy(metrics.accuracy_ratio)}`);
7137
+ console.log(`Planned vs Worked: ${plannedHoursTotal.toFixed(2)}h planned | ${workedHoursTotal.toFixed(2)}h worked`);
6153
7138
  console.log(`Sparkline: ${sparkline(metrics.points.map((point) => point.completed_tasks))}`);
6154
7139
  console.log("");
6155
7140
  console.log(
@@ -6165,12 +7150,20 @@ function runVelocity(options) {
6165
7150
  }
6166
7151
  function runBurndown(options) {
6167
7152
  const root = resolveRepoRoot();
7153
+ const context = readWorkingContext(root, resolveCoopHome());
6168
7154
  const coopPath = coopDir(root);
6169
- const graph = load_graph9(coopPath);
6170
- if (!options.delivery) {
6171
- throw new Error("view burndown requires --delivery <name>.");
7155
+ const graph = load_graph12(coopPath);
7156
+ const resolvedDelivery = resolveContextValueWithSource(options.delivery, context.delivery, sharedDefault(root, "delivery"));
7157
+ if (isVerboseRequested()) {
7158
+ for (const line of formatResolvedContextMessage({ delivery: resolvedDelivery })) {
7159
+ console.log(line);
7160
+ }
7161
+ }
7162
+ const deliveryRef = resolvedDelivery.value;
7163
+ if (!deliveryRef) {
7164
+ throw new Error("delivery is not set; pass --delivery <name> or set a default working delivery with `coop use delivery <id>`.");
6172
7165
  }
6173
- const delivery = resolveDelivery(graph, options.delivery);
7166
+ const delivery = resolveDelivery(graph, deliveryRef);
6174
7167
  const include = new Set(delivery.scope.include);
6175
7168
  for (const id of delivery.scope.exclude) include.delete(id);
6176
7169
  const scopedTasks = Array.from(include).map((id) => graph.nodes.get(id)).filter((task) => Boolean(task));
@@ -6210,7 +7203,7 @@ function runBurndown(options) {
6210
7203
  }
6211
7204
  function runCapacity(options) {
6212
7205
  const root = resolveRepoRoot();
6213
- const graph = load_graph9(coopDir(root));
7206
+ const graph = load_graph12(coopDir(root));
6214
7207
  const deliveries = Array.from(graph.deliveries.values()).sort((a, b) => a.id.localeCompare(b.id));
6215
7208
  if (deliveries.length === 0) {
6216
7209
  console.log("No deliveries found.");
@@ -6298,11 +7291,11 @@ function registerWebhookCommand(program) {
6298
7291
  }
6299
7292
 
6300
7293
  // src/merge-driver/merge-driver.ts
6301
- import fs17 from "fs";
7294
+ import fs21 from "fs";
6302
7295
  import os2 from "os";
6303
- import path23 from "path";
7296
+ import path25 from "path";
6304
7297
  import { spawnSync as spawnSync5 } from "child_process";
6305
- import { stringifyFrontmatter as stringifyFrontmatter4, parseFrontmatterContent as parseFrontmatterContent3, parseYamlContent as parseYamlContent3, stringifyYamlContent as stringifyYamlContent2 } from "@kitsy/coop-core";
7298
+ import { stringifyFrontmatter as stringifyFrontmatter6, parseFrontmatterContent as parseFrontmatterContent3, parseYamlContent as parseYamlContent3, stringifyYamlContent as stringifyYamlContent2 } from "@kitsy/coop-core";
6306
7299
  var STATUS_RANK = {
6307
7300
  blocked: 0,
6308
7301
  canceled: 0,
@@ -6383,33 +7376,33 @@ function mergeTextWithGit(ancestor, ours, theirs) {
6383
7376
  return { ok: false, output: stdout };
6384
7377
  }
6385
7378
  function mergeTaskFile(ancestorPath, oursPath, theirsPath) {
6386
- const ancestorRaw = fs17.readFileSync(ancestorPath, "utf8");
6387
- const oursRaw = fs17.readFileSync(oursPath, "utf8");
6388
- const theirsRaw = fs17.readFileSync(theirsPath, "utf8");
7379
+ const ancestorRaw = fs21.readFileSync(ancestorPath, "utf8");
7380
+ const oursRaw = fs21.readFileSync(oursPath, "utf8");
7381
+ const theirsRaw = fs21.readFileSync(theirsPath, "utf8");
6389
7382
  const ancestor = parseTaskDocument(ancestorRaw, ancestorPath);
6390
7383
  const ours = parseTaskDocument(oursRaw, oursPath);
6391
7384
  const theirs = parseTaskDocument(theirsRaw, theirsPath);
6392
7385
  const mergedFrontmatter = mergeTaskFrontmatter(ancestor.frontmatter, ours.frontmatter, theirs.frontmatter);
6393
- const tempDir = fs17.mkdtempSync(path23.join(os2.tmpdir(), "coop-merge-body-"));
7386
+ const tempDir = fs21.mkdtempSync(path25.join(os2.tmpdir(), "coop-merge-body-"));
6394
7387
  try {
6395
- const ancestorBody = path23.join(tempDir, "ancestor.md");
6396
- const oursBody = path23.join(tempDir, "ours.md");
6397
- const theirsBody = path23.join(tempDir, "theirs.md");
6398
- fs17.writeFileSync(ancestorBody, ancestor.body, "utf8");
6399
- fs17.writeFileSync(oursBody, ours.body, "utf8");
6400
- fs17.writeFileSync(theirsBody, theirs.body, "utf8");
7388
+ const ancestorBody = path25.join(tempDir, "ancestor.md");
7389
+ const oursBody = path25.join(tempDir, "ours.md");
7390
+ const theirsBody = path25.join(tempDir, "theirs.md");
7391
+ fs21.writeFileSync(ancestorBody, ancestor.body, "utf8");
7392
+ fs21.writeFileSync(oursBody, ours.body, "utf8");
7393
+ fs21.writeFileSync(theirsBody, theirs.body, "utf8");
6401
7394
  const mergedBody = mergeTextWithGit(ancestorBody, oursBody, theirsBody);
6402
- const output3 = stringifyFrontmatter4(mergedFrontmatter, mergedBody.output);
6403
- fs17.writeFileSync(oursPath, output3, "utf8");
7395
+ const output3 = stringifyFrontmatter6(mergedFrontmatter, mergedBody.output);
7396
+ fs21.writeFileSync(oursPath, output3, "utf8");
6404
7397
  return mergedBody.ok ? 0 : 1;
6405
7398
  } finally {
6406
- fs17.rmSync(tempDir, { recursive: true, force: true });
7399
+ fs21.rmSync(tempDir, { recursive: true, force: true });
6407
7400
  }
6408
7401
  }
6409
7402
  function mergeDeliveryFile(ancestorPath, oursPath, theirsPath) {
6410
- const ancestor = parseYamlContent3(fs17.readFileSync(ancestorPath, "utf8"), ancestorPath);
6411
- const ours = parseYamlContent3(fs17.readFileSync(oursPath, "utf8"), oursPath);
6412
- const theirs = parseYamlContent3(fs17.readFileSync(theirsPath, "utf8"), theirsPath);
7403
+ const ancestor = parseYamlContent3(fs21.readFileSync(ancestorPath, "utf8"), ancestorPath);
7404
+ const ours = parseYamlContent3(fs21.readFileSync(oursPath, "utf8"), oursPath);
7405
+ const theirs = parseYamlContent3(fs21.readFileSync(theirsPath, "utf8"), theirsPath);
6413
7406
  const oursUpdated = asTimestamp(ours.updated);
6414
7407
  const theirsUpdated = asTimestamp(theirs.updated);
6415
7408
  const base = ancestor;
@@ -6419,7 +7412,7 @@ function mergeDeliveryFile(ancestorPath, oursPath, theirsPath) {
6419
7412
  const value = chooseFieldValue(key, base[key], ours[key], theirs[key], oursUpdated, theirsUpdated);
6420
7413
  if (value !== void 0) merged[key] = value;
6421
7414
  }
6422
- fs17.writeFileSync(oursPath, stringifyYamlContent2(merged), "utf8");
7415
+ fs21.writeFileSync(oursPath, stringifyYamlContent2(merged), "utf8");
6423
7416
  return 0;
6424
7417
  }
6425
7418
  function runMergeDriver(kind, ancestorPath, oursPath, theirsPath) {
@@ -6429,12 +7422,17 @@ function runMergeDriver(kind, ancestorPath, oursPath, theirsPath) {
6429
7422
  return mergeTaskFile(ancestorPath, oursPath, theirsPath);
6430
7423
  }
6431
7424
 
7425
+ // src/utils/not-implemented.ts
7426
+ function printNotImplemented(command, phase) {
7427
+ console.log(`${command}: Not yet implemented - coming in Phase ${phase}.`);
7428
+ }
7429
+
6432
7430
  // src/index.ts
6433
7431
  function readVersion() {
6434
7432
  const currentFile = fileURLToPath2(import.meta.url);
6435
- const packageJsonPath = path24.resolve(path24.dirname(currentFile), "..", "package.json");
7433
+ const packageJsonPath = path26.resolve(path26.dirname(currentFile), "..", "package.json");
6436
7434
  try {
6437
- const parsed = JSON.parse(fs18.readFileSync(packageJsonPath, "utf8"));
7435
+ const parsed = JSON.parse(fs22.readFileSync(packageJsonPath, "utf8"));
6438
7436
  return parsed.version ?? "0.0.0";
6439
7437
  } catch {
6440
7438
  return "0.0.0";
@@ -6454,9 +7452,12 @@ function createProgram() {
6454
7452
  program.option("-p, --project <id>", "Select the active COOP project");
6455
7453
  registerInitCommand(program);
6456
7454
  registerCreateCommand(program);
7455
+ registerCurrentCommand(program);
6457
7456
  registerAssignCommand(program);
7457
+ registerCommentCommand(program);
6458
7458
  registerAliasCommand(program);
6459
7459
  registerConfigCommand(program);
7460
+ registerDepsCommand(program);
6460
7461
  registerListCommand(program);
6461
7462
  registerShowCommand(program);
6462
7463
  registerTransitionCommand(program);
@@ -6465,16 +7466,22 @@ function createProgram() {
6465
7466
  registerHelpAiCommand(program);
6466
7467
  registerIndexCommand(program);
6467
7468
  registerLogCommand(program);
7469
+ registerLogTimeCommand(program);
6468
7470
  registerLifecycleCommands(program);
6469
7471
  registerMigrateCommand(program);
6470
7472
  registerNamingCommand(program);
6471
7473
  registerPlanCommand(program);
7474
+ registerPromoteCommand(program);
6472
7475
  registerProjectCommand(program);
7476
+ registerPromptCommand(program);
6473
7477
  registerRefineCommand(program);
6474
7478
  registerRunCommand(program);
7479
+ registerSearchCommand(program);
6475
7480
  registerServeCommand(program);
6476
7481
  registerStatusCommand(program);
6477
7482
  registerUiCommand(program);
7483
+ registerUpdateCommand(program);
7484
+ registerUseCommand(program);
6478
7485
  registerViewCommand(program);
6479
7486
  registerWebhookCommand(program);
6480
7487
  registerPhasePlaceholder(program, "ext", 3, "Plugin extension commands");
@@ -6534,7 +7541,7 @@ async function runCli(argv = process.argv) {
6534
7541
  function isMainModule() {
6535
7542
  const entry = process.argv[1];
6536
7543
  if (!entry) return false;
6537
- return path24.resolve(entry) === fileURLToPath2(import.meta.url);
7544
+ return path26.resolve(entry) === fileURLToPath2(import.meta.url);
6538
7545
  }
6539
7546
  if (isMainModule()) {
6540
7547
  await runCli(process.argv);