@kitsy/coop 2.1.1 → 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 (3) hide show
  1. package/README.md +11 -0
  2. package/dist/index.js +1550 -371
  3. 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: [
@@ -2924,6 +3242,12 @@ var catalog = {
2924
3242
  "coop reopen task <id>",
2925
3243
  "coop transition task <id> <status>"
2926
3244
  ],
3245
+ lifecycle_requirements: [
3246
+ "`coop start task <id>` moves a ready task into `in_progress`.",
3247
+ "`coop review task <id>` requires the task to already be `in_progress` and moves it to `in_review`.",
3248
+ "`coop complete task <id>` requires the task to already be `in_review` and moves it to `done`.",
3249
+ "Do not call `coop complete task <id>` directly from `in_progress` unless COOP explicitly adds that transition later."
3250
+ ],
2927
3251
  unsupported_command_warnings: [
2928
3252
  "Do not invent COOP commands such as `coop complete` when they are not present in `help-ai` output.",
2929
3253
  "If a desired lifecycle action is not represented by an allowed command, state that explicitly instead of paraphrasing.",
@@ -2949,6 +3273,11 @@ var catalog = {
2949
3273
  { usage: "coop project list", purpose: "List projects in the current workspace." },
2950
3274
  { usage: "coop project show", purpose: "Show the active project id, name, path, and layout." },
2951
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." },
2952
3281
  { usage: "coop naming", purpose: "Explain the current naming template, tokens, and examples." },
2953
3282
  { usage: 'coop naming preview "Natural-language COOP command recommender"', purpose: "Preview a semantic ID before creating an item." }
2954
3283
  ]
@@ -2987,8 +3316,10 @@ var catalog = {
2987
3316
  commands: [
2988
3317
  { usage: "coop next task", purpose: "Show the top ready task using the default track or full workspace context." },
2989
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." },
2990
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." },
2991
- { 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." },
2992
3323
  { usage: "coop review task PM-101", purpose: "Move an in-progress task into in_review using a DX-friendly verb." },
2993
3324
  { usage: "coop complete task PM-101", purpose: "Move a task in review into done using a DX-friendly verb." },
2994
3325
  { usage: "coop block task PM-101", purpose: "Mark a task as blocked." },
@@ -3002,8 +3333,18 @@ var catalog = {
3002
3333
  description: "Read backlog state, task details, and planning output.",
3003
3334
  commands: [
3004
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." },
3005
3341
  { usage: "coop show task PM-101", purpose: "Show a task with acceptance, tests_required, refs, and runbook sections." },
3006
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." },
3007
3348
  { usage: "coop plan delivery MVP", purpose: "Run delivery feasibility analysis." },
3008
3349
  { usage: "coop plan delivery MVP --monte-carlo --iterations 5000", purpose: "Run probabilistic delivery forecasting." },
3009
3350
  { usage: "coop view velocity", purpose: "Show historical throughput." },
@@ -3014,7 +3355,13 @@ var catalog = {
3014
3355
  name: "Execute And Integrate",
3015
3356
  description: "Hand execution to an AI provider or code-agent CLI and expose COOP through MCP/API.",
3016
3357
  commands: [
3358
+ { usage: "coop help-ai --selection --format markdown", purpose: "Show the focused rules for deterministic task selection and workspace resolution." },
3359
+ { usage: "coop help-ai --state-transitions --format json", purpose: "Show exact lifecycle commands, prerequisites, and warnings against invented transitions." },
3360
+ { usage: "coop help-ai --artifacts --format markdown", purpose: "Show where contract-review, audit, and planning artifacts must be written." },
3361
+ { usage: "coop help-ai --post-execution --format markdown", purpose: "Show what an agent must do after finishing the selected task." },
3362
+ { usage: "coop help-ai --naming --format markdown", purpose: "Show naming guidance when IDs and naming templates matter." },
3017
3363
  { usage: "coop help-ai --initial-prompt --strict --repo C:/path/to/repo --delivery MVP --command coop.cmd", purpose: "Generate a strict agent bootstrap prompt for a repository and delivery." },
3364
+ { usage: "coop help-ai --initial-prompt --rigour balanced --repo C:/path/to/repo --delivery MVP --command coop.cmd", purpose: "Generate the recommended balanced agent bootstrap prompt for day-to-day implementation work." },
3018
3365
  { usage: "coop config ai.provider codex_cli", purpose: "Use the installed Codex CLI as the execution/refinement backend." },
3019
3366
  { usage: "coop config ai.provider claude_cli", purpose: "Use the installed Claude CLI as the execution/refinement backend." },
3020
3367
  { usage: "coop config ai.provider gemini_cli", purpose: "Use the installed Gemini CLI as the execution/refinement backend." },
@@ -3040,12 +3387,124 @@ var catalog = {
3040
3387
  execution_model: [
3041
3388
  "Agents or services may send drafts through files or stdin, but COOP owns canonical writes.",
3042
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.",
3043
3391
  "Use `coop log --last --verbose` when command execution fails and the concise error points to a stack trace.",
3044
3392
  "If a workflow depends on stable human-readable IDs, inspect `coop naming` or `coop config id.naming` before creating items."
3393
+ ],
3394
+ post_execution_rules: [
3395
+ "After completing the currently selected task, stop and wait instead of automatically selecting the next task.",
3396
+ "Do not auto-start a second task in the same run unless the user explicitly asks you to continue.",
3397
+ "If you changed task state in COOP, report the exact command used and the resulting new state before doing anything else."
3045
3398
  ]
3046
3399
  };
3400
+ function renderTopicPayload(topic) {
3401
+ if (topic === "state-transitions") {
3402
+ return {
3403
+ topic,
3404
+ allowed_lifecycle_commands: catalog.allowed_lifecycle_commands,
3405
+ lifecycle_requirements: catalog.lifecycle_requirements,
3406
+ warnings: catalog.unsupported_command_warnings
3407
+ };
3408
+ }
3409
+ if (topic === "artifacts") {
3410
+ return {
3411
+ topic,
3412
+ artifact_policy: catalog.artifact_policy
3413
+ };
3414
+ }
3415
+ if (topic === "post-execution") {
3416
+ return {
3417
+ topic,
3418
+ post_execution_rules: catalog.post_execution_rules
3419
+ };
3420
+ }
3421
+ if (topic === "selection") {
3422
+ return {
3423
+ topic,
3424
+ selection_rules: catalog.selection_rules,
3425
+ workspace_rules: catalog.workspace_rules
3426
+ };
3427
+ }
3428
+ return {
3429
+ topic,
3430
+ naming_guidance: [
3431
+ "Use `coop naming` to inspect the current naming template and token behavior.",
3432
+ 'Use `coop naming preview "<title>"` before creating a new idea, task, or delivery if predictable IDs matter.',
3433
+ "Use `coop config id.naming ...` to override the default semantic naming template."
3434
+ ]
3435
+ };
3436
+ }
3437
+ function renderAiHelpTopic(format, topic) {
3438
+ const payload = renderTopicPayload(topic);
3439
+ if (format === "json") {
3440
+ return `${JSON.stringify(payload, null, 2)}
3441
+ `;
3442
+ }
3443
+ const lines = [];
3444
+ const bullet = (value) => `- ${value}`;
3445
+ const title = topic.split("-").map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
3446
+ lines.push(format === "markdown" ? `# COOP AI Help: ${title}` : `COOP AI Help: ${title}`, "");
3447
+ if (topic === "state-transitions") {
3448
+ lines.push(format === "markdown" ? "## Allowed Lifecycle Commands" : "Allowed Lifecycle Commands");
3449
+ for (const item of catalog.allowed_lifecycle_commands) {
3450
+ lines.push(bullet(`\`${item}\``));
3451
+ }
3452
+ lines.push("");
3453
+ lines.push(format === "markdown" ? "## Lifecycle Requirements" : "Lifecycle Requirements");
3454
+ for (const item of catalog.lifecycle_requirements) {
3455
+ lines.push(bullet(item));
3456
+ }
3457
+ lines.push("");
3458
+ lines.push(format === "markdown" ? "## Warnings" : "Warnings");
3459
+ for (const item of catalog.unsupported_command_warnings) {
3460
+ lines.push(bullet(item));
3461
+ }
3462
+ lines.push("");
3463
+ return `${lines.join("\n")}
3464
+ `;
3465
+ }
3466
+ if (topic === "artifacts") {
3467
+ lines.push(bullet(`Config key: \`${catalog.artifact_policy.config_key}\``));
3468
+ lines.push(bullet(`Default dir: \`${catalog.artifact_policy.default_dir}\``));
3469
+ lines.push(bullet(`Required for: ${catalog.artifact_policy.required_for.join(", ")}`));
3470
+ for (const item of catalog.artifact_policy.guidance) {
3471
+ lines.push(bullet(item));
3472
+ }
3473
+ lines.push("");
3474
+ return `${lines.join("\n")}
3475
+ `;
3476
+ }
3477
+ if (topic === "post-execution") {
3478
+ for (const item of catalog.post_execution_rules) {
3479
+ lines.push(bullet(item));
3480
+ }
3481
+ lines.push("");
3482
+ return `${lines.join("\n")}
3483
+ `;
3484
+ }
3485
+ if (topic === "selection") {
3486
+ lines.push(format === "markdown" ? "## Selection Rules" : "Selection Rules");
3487
+ for (const item of catalog.selection_rules) {
3488
+ lines.push(bullet(item));
3489
+ }
3490
+ lines.push("");
3491
+ lines.push(format === "markdown" ? "## Workspace Rules" : "Workspace Rules");
3492
+ for (const item of catalog.workspace_rules) {
3493
+ lines.push(bullet(item));
3494
+ }
3495
+ lines.push("");
3496
+ return `${lines.join("\n")}
3497
+ `;
3498
+ }
3499
+ for (const item of renderTopicPayload("naming").naming_guidance) {
3500
+ lines.push(bullet(item));
3501
+ }
3502
+ lines.push("");
3503
+ return `${lines.join("\n")}
3504
+ `;
3505
+ }
3047
3506
  function normalizeRepoPath(repoPath) {
3048
- return repoPath ? path7.resolve(repoPath) : process.cwd();
3507
+ return repoPath ? path9.resolve(repoPath) : process.cwd();
3049
3508
  }
3050
3509
  function formatSelectionCommand(commandName, delivery, track) {
3051
3510
  if (delivery) {
@@ -3082,22 +3541,33 @@ function renderInitialPrompt(options = {}) {
3082
3541
  "",
3083
3542
  "Rules:",
3084
3543
  "- Learn COOP capabilities from `coop help-ai` before proposing commands.",
3544
+ `- If you are unsure how to pick work, run \`${commandName} help-ai --selection --format markdown\`.`,
3545
+ `- If you are unsure about lifecycle changes, run \`${commandName} help-ai --state-transitions --format markdown\`.`,
3546
+ `- If you are unsure where artifacts should go, run \`${commandName} help-ai --artifacts --format markdown\`.`,
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.`,
3085
3549
  "- Use only commands that actually exist in COOP. Do not invent command names.",
3086
3550
  "- Do not reprioritize work outside COOP unless the user explicitly overrides it.",
3087
3551
  "- Select only the first ready task from the COOP readiness output.",
3088
- "- Inspect the selected task with `coop show task <id>` before implementation.",
3552
+ "- Inspect the selected task with `coop show <id>` before implementation.",
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.",
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.",
3089
3557
  `- Write any contract-review, audit, or planning artifact under \`${artifactsDir}\` unless the user explicitly chooses another location.`
3090
3558
  ];
3091
3559
  if (rigour === "strict") {
3092
3560
  lines.push(
3093
3561
  "- Before mentioning a COOP command, verify the exact command name from `coop help-ai --format json`.",
3094
3562
  "- If a needed workflow is not supported by an exact COOP command, say that explicitly instead of inventing one.",
3095
- "- Do not switch to another task unless COOP state changes or the user explicitly redirects you."
3563
+ "- Do not switch to another task unless COOP state changes or the user explicitly redirects you.",
3564
+ "- After executing or completing the selected task, stop and wait for user confirmation before picking another task."
3096
3565
  );
3097
3566
  } else if (rigour === "balanced") {
3098
3567
  lines.push(
3099
3568
  "- Prefer exact COOP commands from `coop help-ai --format json` when describing next actions.",
3100
- "- If COOP lacks a named command for a desired action, state the limitation plainly."
3569
+ "- If COOP lacks a named command for a desired action, state the limitation plainly.",
3570
+ "- After finishing the selected task, report the resulting COOP state before proposing follow-up work."
3101
3571
  );
3102
3572
  } else {
3103
3573
  lines.push("- Keep COOP as the authority for task selection and lifecycle state.");
@@ -3141,6 +3611,11 @@ function renderAiHelp(format) {
3141
3611
  lines.push(bullet(`\`${item}\``));
3142
3612
  }
3143
3613
  lines.push("");
3614
+ lines.push(format === "markdown" ? "## Lifecycle Requirements" : "Lifecycle Requirements");
3615
+ for (const item of catalog.lifecycle_requirements) {
3616
+ lines.push(bullet(item));
3617
+ }
3618
+ lines.push("");
3144
3619
  lines.push(format === "markdown" ? "## Unsupported / Invented Command Warnings" : "Unsupported / Invented Command Warnings");
3145
3620
  for (const item of catalog.unsupported_command_warnings) {
3146
3621
  lines.push(bullet(item));
@@ -3173,6 +3648,11 @@ function renderAiHelp(format) {
3173
3648
  lines.push(bullet(item));
3174
3649
  }
3175
3650
  lines.push("");
3651
+ lines.push(format === "markdown" ? "## Post-Execution Rules" : "Post-Execution Rules");
3652
+ for (const item of catalog.post_execution_rules) {
3653
+ lines.push(bullet(item));
3654
+ }
3655
+ lines.push("");
3176
3656
  return `${lines.join("\n")}
3177
3657
  `;
3178
3658
  }
@@ -3182,7 +3662,7 @@ function renderAiInitialPrompt(options = {}) {
3182
3662
 
3183
3663
  // src/commands/help-ai.ts
3184
3664
  function registerHelpAiCommand(program) {
3185
- program.command("help-ai").description("Print AI-oriented COOP capability and usage help").option("--format <format>", "Output format: text | json | markdown", "text").option("--initial-prompt", "Print an agent bootstrap prompt instead of the capability catalog").option("--repo <path>", "Repository path to embed in the initial prompt").option("--delivery <delivery>", "Delivery to embed in the initial prompt").option("--track <track>", "Track to embed in the initial prompt").option("--command <name>", "Command name to embed, e.g. coop or coop.cmd", "coop").option("--rigour <level>", "Prompt rigour: strict | balanced | light", "balanced").option("--strict", "Shortcut for --rigour strict").option("--balanced", "Shortcut for --rigour balanced").option("--light", "Shortcut for --rigour light").action((options) => {
3665
+ program.command("help-ai").description("Print AI-oriented COOP capability and usage help").option("--format <format>", "Output format: text | json | markdown", "text").option("--initial-prompt", "Print an agent bootstrap prompt instead of the capability catalog").option("--topic <topic>", "Focused help topic: state-transitions | artifacts | post-execution | selection | naming").option("--state-transitions", "Focused help for lifecycle and state transition usage").option("--artifacts", "Focused help for artifact placement and policy").option("--post-execution", "Focused help for what to do after finishing the selected task").option("--selection", "Focused help for task selection and workspace resolution").option("--naming", "Focused help for naming IDs and naming templates").option("--repo <path>", "Repository path to embed in the initial prompt").option("--delivery <delivery>", "Delivery to embed in the initial prompt").option("--track <track>", "Track to embed in the initial prompt").option("--command <name>", "Command name to embed, e.g. coop or coop.cmd", "coop").option("--rigour <level>", "Prompt rigour: strict | balanced | light", "balanced").option("--strict", "Shortcut for --rigour strict").option("--balanced", "Shortcut for --rigour balanced").option("--light", "Shortcut for --rigour light").action((options) => {
3186
3666
  const format = options.format ?? "text";
3187
3667
  if (format !== "text" && format !== "json" && format !== "markdown") {
3188
3668
  throw new Error(`Unsupported help-ai format '${format}'. Expected text|json|markdown.`);
@@ -3191,6 +3671,7 @@ function registerHelpAiCommand(program) {
3191
3671
  if (rigour !== "strict" && rigour !== "balanced" && rigour !== "light") {
3192
3672
  throw new Error(`Unsupported help-ai rigour '${rigour}'. Expected strict|balanced|light.`);
3193
3673
  }
3674
+ const topic = resolveHelpTopic(options);
3194
3675
  let artifactsDir = "docs";
3195
3676
  const repoRoot = options.repo ? resolveRepoRoot(options.repo) : resolveRepoRoot();
3196
3677
  try {
@@ -3206,13 +3687,43 @@ function registerHelpAiCommand(program) {
3206
3687
  commandName: options.command,
3207
3688
  rigour,
3208
3689
  artifactsDir
3209
- }) : renderAiHelp(format);
3690
+ }) : topic ? renderAiHelpTopic(format, topic) : renderAiHelp(format);
3210
3691
  console.log(output3.trimEnd());
3211
3692
  });
3212
3693
  }
3694
+ function resolveHelpTopic(options) {
3695
+ const requestedTopics = [];
3696
+ if (options.topic) {
3697
+ requestedTopics.push(options.topic);
3698
+ }
3699
+ if (options.stateTransitions) {
3700
+ requestedTopics.push("state-transitions");
3701
+ }
3702
+ if (options.artifacts) {
3703
+ requestedTopics.push("artifacts");
3704
+ }
3705
+ if (options.postExecution) {
3706
+ requestedTopics.push("post-execution");
3707
+ }
3708
+ if (options.selection) {
3709
+ requestedTopics.push("selection");
3710
+ }
3711
+ if (options.naming) {
3712
+ requestedTopics.push("naming");
3713
+ }
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(", ")}.`);
3717
+ }
3718
+ const topic = unique3[0];
3719
+ if (topic !== void 0 && topic !== "state-transitions" && topic !== "artifacts" && topic !== "post-execution" && topic !== "selection" && topic !== "naming") {
3720
+ throw new Error(`Unsupported help-ai topic '${topic}'. Expected state-transitions|artifacts|post-execution|selection|naming.`);
3721
+ }
3722
+ return topic;
3723
+ }
3213
3724
 
3214
3725
  // src/commands/index.ts
3215
- import path8 from "path";
3726
+ import path10 from "path";
3216
3727
  import { IndexManager as IndexManager2 } from "@kitsy/coop-core";
3217
3728
  function runStatus(options) {
3218
3729
  const root = resolveRepoRoot();
@@ -3222,7 +3733,7 @@ function runStatus(options) {
3222
3733
  const freshness = status.stale ? "stale" : "fresh";
3223
3734
  const existsText = status.exists ? "present" : "missing";
3224
3735
  console.log(`[COOP] index ${existsText}, ${freshness}`);
3225
- console.log(`[COOP] graph: ${path8.relative(root, status.graph_path)}`);
3736
+ console.log(`[COOP] graph: ${path10.relative(root, status.graph_path)}`);
3226
3737
  if (status.generated_at) {
3227
3738
  console.log(`[COOP] generated_at: ${status.generated_at}`);
3228
3739
  }
@@ -3245,7 +3756,7 @@ function runRebuild() {
3245
3756
  const graph = manager.build_full_index();
3246
3757
  const elapsed = Date.now() - start;
3247
3758
  console.log(`[COOP] index rebuilt: ${graph.nodes.size} tasks (${elapsed} ms)`);
3248
- console.log(`[COOP] graph: ${path8.relative(root, manager.graphPath)}`);
3759
+ console.log(`[COOP] graph: ${path10.relative(root, manager.graphPath)}`);
3249
3760
  }
3250
3761
  function registerIndexCommand(program) {
3251
3762
  const index = program.command("index").description("Index management commands");
@@ -3261,18 +3772,18 @@ function registerIndexCommand(program) {
3261
3772
  }
3262
3773
 
3263
3774
  // src/commands/init.ts
3264
- import fs8 from "fs";
3265
- import path11 from "path";
3775
+ import fs10 from "fs";
3776
+ import path13 from "path";
3266
3777
  import { spawnSync as spawnSync4 } from "child_process";
3267
3778
  import { createInterface } from "readline/promises";
3268
3779
  import { stdin as input, stdout as output } from "process";
3269
3780
  import { CURRENT_SCHEMA_VERSION, write_schema_version } from "@kitsy/coop-core";
3270
3781
 
3271
3782
  // src/hooks/pre-commit.ts
3272
- import fs6 from "fs";
3273
- import path9 from "path";
3783
+ import fs8 from "fs";
3784
+ import path11 from "path";
3274
3785
  import { spawnSync as spawnSync3 } from "child_process";
3275
- 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";
3276
3787
  var HOOK_BLOCK_START = "# COOP_PRE_COMMIT_START";
3277
3788
  var HOOK_BLOCK_END = "# COOP_PRE_COMMIT_END";
3278
3789
  function runGit(repoRoot, args, allowFailure = false) {
@@ -3301,28 +3812,28 @@ function projectRootFromRelativePath(repoRoot, relativePath) {
3301
3812
  const normalized = toPosixPath2(relativePath);
3302
3813
  const projectMatch = /^\.coop\/projects\/([^/]+)\/tasks\/.+\.md$/i.exec(normalized);
3303
3814
  if (projectMatch?.[1]) {
3304
- return path9.join(repoRoot, ".coop", "projects", projectMatch[1]);
3815
+ return path11.join(repoRoot, ".coop", "projects", projectMatch[1]);
3305
3816
  }
3306
3817
  if (normalized.startsWith(".coop/tasks/")) {
3307
- return path9.join(repoRoot, ".coop");
3818
+ return path11.join(repoRoot, ".coop");
3308
3819
  }
3309
3820
  throw new Error(`Unsupported staged COOP task path '${relativePath}'.`);
3310
3821
  }
3311
3822
  function listTaskFilesForProject(projectRoot) {
3312
- const tasksDir = path9.join(projectRoot, "tasks");
3313
- if (!fs6.existsSync(tasksDir)) return [];
3823
+ const tasksDir = path11.join(projectRoot, "tasks");
3824
+ if (!fs8.existsSync(tasksDir)) return [];
3314
3825
  const out = [];
3315
3826
  const stack = [tasksDir];
3316
3827
  while (stack.length > 0) {
3317
3828
  const current = stack.pop();
3318
- const entries = fs6.readdirSync(current, { withFileTypes: true });
3829
+ const entries = fs8.readdirSync(current, { withFileTypes: true });
3319
3830
  for (const entry of entries) {
3320
- const fullPath = path9.join(current, entry.name);
3831
+ const fullPath = path11.join(current, entry.name);
3321
3832
  if (entry.isDirectory()) {
3322
3833
  stack.push(fullPath);
3323
3834
  continue;
3324
3835
  }
3325
- if (entry.isFile() && path9.extname(entry.name).toLowerCase() === ".md") {
3836
+ if (entry.isFile() && path11.extname(entry.name).toLowerCase() === ".md") {
3326
3837
  out.push(fullPath);
3327
3838
  }
3328
3839
  }
@@ -3341,7 +3852,7 @@ function parseStagedTasks(repoRoot, relativePaths) {
3341
3852
  const errors = [];
3342
3853
  const staged = [];
3343
3854
  for (const relativePath of relativePaths) {
3344
- const absolutePath = path9.join(repoRoot, ...relativePath.split("/"));
3855
+ const absolutePath = path11.join(repoRoot, ...relativePath.split("/"));
3345
3856
  const projectRoot = projectRootFromRelativePath(repoRoot, relativePath);
3346
3857
  const stagedBlob = readGitBlob(repoRoot, `:${relativePath}`);
3347
3858
  if (!stagedBlob) {
@@ -3356,7 +3867,7 @@ function parseStagedTasks(repoRoot, relativePaths) {
3356
3867
  errors.push(`[COOP] ${message}`);
3357
3868
  continue;
3358
3869
  }
3359
- const issues = validateStructural5(task, { filePath: absolutePath });
3870
+ const issues = validateStructural6(task, { filePath: absolutePath });
3360
3871
  for (const issue of issues) {
3361
3872
  errors.push(`[COOP] ${relativePath}: ${issue.message}`);
3362
3873
  }
@@ -3405,13 +3916,13 @@ function collectTasksForCycleCheck(projectRoot, stagedTasks) {
3405
3916
  }
3406
3917
  const tasks = [];
3407
3918
  for (const filePath of listTaskFilesForProject(projectRoot)) {
3408
- const normalized = toPosixPath2(path9.resolve(filePath));
3919
+ const normalized = toPosixPath2(path11.resolve(filePath));
3409
3920
  const stagedTask = stagedByPath.get(normalized);
3410
3921
  if (stagedTask) {
3411
3922
  tasks.push(stagedTask);
3412
3923
  continue;
3413
3924
  }
3414
- tasks.push(parseTaskFile7(filePath).task);
3925
+ tasks.push(parseTaskFile8(filePath).task);
3415
3926
  }
3416
3927
  return tasks;
3417
3928
  }
@@ -3435,7 +3946,7 @@ function runPreCommitChecks(repoRoot) {
3435
3946
  const graph = buildGraphForCycleCheck(tasks);
3436
3947
  const cycle = detect_cycle(graph);
3437
3948
  if (cycle) {
3438
- const projectLabel = toPosixPath2(path9.relative(repoRoot, projectRoot));
3949
+ const projectLabel = toPosixPath2(path11.relative(repoRoot, projectRoot));
3439
3950
  errors.push(`[COOP] Dependency cycle detected in ${projectLabel}: ${cycle.join(" -> ")}.`);
3440
3951
  }
3441
3952
  } catch (error) {
@@ -3467,9 +3978,9 @@ function hookScriptBlock() {
3467
3978
  ].join("\n");
3468
3979
  }
3469
3980
  function installPreCommitHook(repoRoot) {
3470
- const hookPath = path9.join(repoRoot, ".git", "hooks", "pre-commit");
3471
- const hookDir = path9.dirname(hookPath);
3472
- 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)) {
3473
3984
  return {
3474
3985
  installed: false,
3475
3986
  hookPath,
@@ -3477,18 +3988,18 @@ function installPreCommitHook(repoRoot) {
3477
3988
  };
3478
3989
  }
3479
3990
  const block = hookScriptBlock();
3480
- if (!fs6.existsSync(hookPath)) {
3991
+ if (!fs8.existsSync(hookPath)) {
3481
3992
  const content = ["#!/bin/sh", "", block].join("\n");
3482
- fs6.writeFileSync(hookPath, content, "utf8");
3993
+ fs8.writeFileSync(hookPath, content, "utf8");
3483
3994
  } else {
3484
- const existing = fs6.readFileSync(hookPath, "utf8");
3995
+ const existing = fs8.readFileSync(hookPath, "utf8");
3485
3996
  if (!existing.includes(HOOK_BLOCK_START)) {
3486
3997
  const suffix = existing.endsWith("\n") ? "" : "\n";
3487
- fs6.writeFileSync(hookPath, `${existing}${suffix}${block}`, "utf8");
3998
+ fs8.writeFileSync(hookPath, `${existing}${suffix}${block}`, "utf8");
3488
3999
  }
3489
4000
  }
3490
4001
  try {
3491
- fs6.chmodSync(hookPath, 493);
4002
+ fs8.chmodSync(hookPath, 493);
3492
4003
  } catch {
3493
4004
  }
3494
4005
  return {
@@ -3499,15 +4010,15 @@ function installPreCommitHook(repoRoot) {
3499
4010
  }
3500
4011
 
3501
4012
  // src/hooks/post-merge-validate.ts
3502
- import fs7 from "fs";
3503
- import path10 from "path";
4013
+ import fs9 from "fs";
4014
+ import path12 from "path";
3504
4015
  import { list_projects } from "@kitsy/coop-core";
3505
- 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";
3506
4017
  var HOOK_BLOCK_START2 = "# COOP_POST_MERGE_START";
3507
4018
  var HOOK_BLOCK_END2 = "# COOP_POST_MERGE_END";
3508
4019
  function runPostMergeValidate(repoRoot) {
3509
- const workspaceDir = path10.join(repoRoot, ".coop");
3510
- if (!fs7.existsSync(workspaceDir)) {
4020
+ const workspaceDir = path12.join(repoRoot, ".coop");
4021
+ if (!fs9.existsSync(workspaceDir)) {
3511
4022
  return {
3512
4023
  ok: true,
3513
4024
  warnings: ["[COOP] Skipped post-merge validation (.coop not found)."]
@@ -3523,7 +4034,7 @@ function runPostMergeValidate(repoRoot) {
3523
4034
  }
3524
4035
  const warnings = [];
3525
4036
  for (const project of projects) {
3526
- const graph = load_graph4(project.root);
4037
+ const graph = load_graph5(project.root);
3527
4038
  const issues = validate_graph2(graph);
3528
4039
  for (const issue of issues) {
3529
4040
  warnings.push(`[COOP] post-merge warning [${project.id}] [${issue.invariant}] ${issue.message}`);
@@ -3559,9 +4070,9 @@ function postMergeHookBlock() {
3559
4070
  ].join("\n");
3560
4071
  }
3561
4072
  function installPostMergeHook(repoRoot) {
3562
- const hookPath = path10.join(repoRoot, ".git", "hooks", "post-merge");
3563
- const hookDir = path10.dirname(hookPath);
3564
- 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)) {
3565
4076
  return {
3566
4077
  installed: false,
3567
4078
  hookPath,
@@ -3569,17 +4080,17 @@ function installPostMergeHook(repoRoot) {
3569
4080
  };
3570
4081
  }
3571
4082
  const block = postMergeHookBlock();
3572
- if (!fs7.existsSync(hookPath)) {
3573
- fs7.writeFileSync(hookPath, ["#!/bin/sh", "", block].join("\n"), "utf8");
4083
+ if (!fs9.existsSync(hookPath)) {
4084
+ fs9.writeFileSync(hookPath, ["#!/bin/sh", "", block].join("\n"), "utf8");
3574
4085
  } else {
3575
- const existing = fs7.readFileSync(hookPath, "utf8");
4086
+ const existing = fs9.readFileSync(hookPath, "utf8");
3576
4087
  if (!existing.includes(HOOK_BLOCK_START2)) {
3577
4088
  const suffix = existing.endsWith("\n") ? "" : "\n";
3578
- fs7.writeFileSync(hookPath, `${existing}${suffix}${block}`, "utf8");
4089
+ fs9.writeFileSync(hookPath, `${existing}${suffix}${block}`, "utf8");
3579
4090
  }
3580
4091
  }
3581
4092
  try {
3582
- fs7.chmodSync(hookPath, 493);
4093
+ fs9.chmodSync(hookPath, 493);
3583
4094
  } catch {
3584
4095
  }
3585
4096
  return {
@@ -3745,40 +4256,40 @@ tmp/
3745
4256
  *.tmp
3746
4257
  `;
3747
4258
  function ensureDir(dirPath) {
3748
- fs8.mkdirSync(dirPath, { recursive: true });
4259
+ fs10.mkdirSync(dirPath, { recursive: true });
3749
4260
  }
3750
4261
  function writeIfMissing(filePath, content) {
3751
- if (!fs8.existsSync(filePath)) {
3752
- fs8.writeFileSync(filePath, content, "utf8");
4262
+ if (!fs10.existsSync(filePath)) {
4263
+ fs10.writeFileSync(filePath, content, "utf8");
3753
4264
  }
3754
4265
  }
3755
4266
  function ensureGitignoreEntry(root, entry) {
3756
- const gitignorePath = path11.join(root, ".gitignore");
3757
- if (!fs8.existsSync(gitignorePath)) {
3758
- fs8.writeFileSync(gitignorePath, `${entry}
4267
+ const gitignorePath = path13.join(root, ".gitignore");
4268
+ if (!fs10.existsSync(gitignorePath)) {
4269
+ fs10.writeFileSync(gitignorePath, `${entry}
3759
4270
  `, "utf8");
3760
4271
  return;
3761
4272
  }
3762
- const content = fs8.readFileSync(gitignorePath, "utf8");
4273
+ const content = fs10.readFileSync(gitignorePath, "utf8");
3763
4274
  const lines = content.split(/\r?\n/).map((line) => line.trim());
3764
4275
  if (!lines.includes(entry)) {
3765
4276
  const suffix = content.endsWith("\n") ? "" : "\n";
3766
- fs8.writeFileSync(gitignorePath, `${content}${suffix}${entry}
4277
+ fs10.writeFileSync(gitignorePath, `${content}${suffix}${entry}
3767
4278
  `, "utf8");
3768
4279
  }
3769
4280
  }
3770
4281
  function ensureGitattributesEntry(root, entry) {
3771
- const attrsPath = path11.join(root, ".gitattributes");
3772
- if (!fs8.existsSync(attrsPath)) {
3773
- fs8.writeFileSync(attrsPath, `${entry}
4282
+ const attrsPath = path13.join(root, ".gitattributes");
4283
+ if (!fs10.existsSync(attrsPath)) {
4284
+ fs10.writeFileSync(attrsPath, `${entry}
3774
4285
  `, "utf8");
3775
4286
  return;
3776
4287
  }
3777
- const content = fs8.readFileSync(attrsPath, "utf8");
4288
+ const content = fs10.readFileSync(attrsPath, "utf8");
3778
4289
  const lines = content.split(/\r?\n/).map((line) => line.trim());
3779
4290
  if (!lines.includes(entry)) {
3780
4291
  const suffix = content.endsWith("\n") ? "" : "\n";
3781
- fs8.writeFileSync(attrsPath, `${content}${suffix}${entry}
4292
+ fs10.writeFileSync(attrsPath, `${content}${suffix}${entry}
3782
4293
  `, "utf8");
3783
4294
  }
3784
4295
  }
@@ -3864,7 +4375,7 @@ function registerInitCommand(program) {
3864
4375
  const workspaceDir = coopWorkspaceDir(root);
3865
4376
  const identity = await resolveInitIdentity(root, options);
3866
4377
  const projectId = identity.projectId;
3867
- const projectRoot = path11.join(workspaceDir, "projects", projectId);
4378
+ const projectRoot = path13.join(workspaceDir, "projects", projectId);
3868
4379
  const dirs = [
3869
4380
  "ideas",
3870
4381
  "tasks",
@@ -3880,23 +4391,23 @@ function registerInitCommand(program) {
3880
4391
  "history/deliveries",
3881
4392
  ".index"
3882
4393
  ];
3883
- ensureDir(path11.join(workspaceDir, "projects"));
4394
+ ensureDir(path13.join(workspaceDir, "projects"));
3884
4395
  for (const dir of dirs) {
3885
- ensureDir(path11.join(projectRoot, dir));
4396
+ ensureDir(path13.join(projectRoot, dir));
3886
4397
  }
3887
4398
  writeIfMissing(
3888
- path11.join(projectRoot, "config.yml"),
4399
+ path13.join(projectRoot, "config.yml"),
3889
4400
  buildProjectConfig(projectId, identity.projectName, identity.projectAliases, identity.namingTemplate)
3890
4401
  );
3891
- if (!fs8.existsSync(path11.join(projectRoot, "schema-version"))) {
4402
+ if (!fs10.existsSync(path13.join(projectRoot, "schema-version"))) {
3892
4403
  write_schema_version(projectRoot, CURRENT_SCHEMA_VERSION);
3893
4404
  }
3894
- writeIfMissing(path11.join(projectRoot, "templates/task.md"), TASK_TEMPLATE);
3895
- writeIfMissing(path11.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE);
3896
- writeIfMissing(path11.join(projectRoot, "plugins/console-log.yml"), PLUGIN_CONSOLE_TEMPLATE);
3897
- writeIfMissing(path11.join(projectRoot, "plugins/github-pr.yml"), PLUGIN_GITHUB_TEMPLATE);
3898
- writeIfMissing(path11.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE);
3899
- 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);
3900
4411
  writeWorkspaceConfig(root, { version: 2, current_project: projectId });
3901
4412
  ensureGitignoreEntry(root, ".coop/logs/");
3902
4413
  ensureGitignoreEntry(root, ".coop/tmp/");
@@ -3906,18 +4417,18 @@ function registerInitCommand(program) {
3906
4417
  const project = resolveProject(root, projectId);
3907
4418
  console.log("Initialized COOP workspace.");
3908
4419
  console.log(`- Root: ${root}`);
3909
- console.log(`- Workspace: ${path11.relative(root, workspaceDir)}`);
3910
- 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)})`);
3911
4422
  console.log(`- Name: ${identity.projectName}`);
3912
4423
  console.log(`- Aliases: ${identity.projectAliases.length > 0 ? identity.projectAliases.join(", ") : "(none)"}`);
3913
4424
  console.log(`- ID naming: ${identity.namingTemplate}`);
3914
4425
  console.log(`- ${preCommitHook.message}`);
3915
4426
  if (preCommitHook.installed) {
3916
- console.log(`- Hook: ${path11.relative(root, preCommitHook.hookPath)}`);
4427
+ console.log(`- Hook: ${path13.relative(root, preCommitHook.hookPath)}`);
3917
4428
  }
3918
4429
  console.log(`- ${postMergeHook.message}`);
3919
4430
  if (postMergeHook.installed) {
3920
- console.log(`- Hook: ${path11.relative(root, postMergeHook.hookPath)}`);
4431
+ console.log(`- Hook: ${path13.relative(root, postMergeHook.hookPath)}`);
3921
4432
  }
3922
4433
  console.log(`- ${mergeDrivers}`);
3923
4434
  console.log("- Next steps:");
@@ -3928,8 +4439,8 @@ function registerInitCommand(program) {
3928
4439
  }
3929
4440
 
3930
4441
  // src/commands/lifecycle.ts
3931
- import path12 from "path";
3932
- import { parseTaskFile as parseTaskFile8 } from "@kitsy/coop-core";
4442
+ import path14 from "path";
4443
+ import { parseTaskFile as parseTaskFile9 } from "@kitsy/coop-core";
3933
4444
  var lifecycleVerbs = [
3934
4445
  {
3935
4446
  name: "review",
@@ -3964,8 +4475,8 @@ var lifecycleVerbs = [
3964
4475
  ];
3965
4476
  function currentTaskSelection(root, id) {
3966
4477
  const reference = resolveReference(root, id, "task");
3967
- const filePath = path12.join(root, ...reference.file.split("/"));
3968
- const parsed = parseTaskFile8(filePath);
4478
+ const filePath = path14.join(root, ...reference.file.split("/"));
4479
+ const parsed = parseTaskFile9(filePath);
3969
4480
  return formatSelectedTask(
3970
4481
  {
3971
4482
  task: parsed.task,
@@ -3989,16 +4500,9 @@ function registerLifecycleCommands(program) {
3989
4500
  }
3990
4501
 
3991
4502
  // src/commands/list.ts
3992
- import path13 from "path";
3993
- 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";
3994
4505
  import chalk2 from "chalk";
3995
-
3996
- // src/utils/not-implemented.ts
3997
- function printNotImplemented(command, phase) {
3998
- console.log(`${command}: Not yet implemented - coming in Phase ${phase}.`);
3999
- }
4000
-
4001
- // src/commands/list.ts
4002
4506
  function statusColor(status) {
4003
4507
  switch (status) {
4004
4508
  case "done":
@@ -4018,49 +4522,81 @@ function statusColor(status) {
4018
4522
  function sortByIdAsc(items) {
4019
4523
  return [...items].sort((a, b) => a.id.localeCompare(b.id));
4020
4524
  }
4021
- function loadTasks(root) {
4525
+ function loadTasks2(root) {
4022
4526
  return listTaskFiles(root).map((filePath) => ({
4023
- task: parseTaskFile9(filePath).task,
4527
+ task: parseTaskFile10(filePath).task,
4024
4528
  filePath
4025
4529
  }));
4026
4530
  }
4027
4531
  function loadIdeas(root) {
4028
4532
  return listIdeaFiles(root).map((filePath) => ({
4029
- idea: parseIdeaFile3(filePath).idea,
4533
+ idea: parseIdeaFile4(filePath).idea,
4030
4534
  filePath
4031
4535
  }));
4032
4536
  }
4033
4537
  function listTasks(options) {
4034
4538
  const root = resolveRepoRoot();
4035
4539
  ensureCoopInitialized(root);
4036
- 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;
4037
4562
  if (options.status && task.status !== options.status) return false;
4038
- if (options.track && (task.track ?? "unassigned") !== options.track) return false;
4039
- 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
+ }
4040
4572
  return true;
4041
4573
  }).map(({ task, filePath }) => ({
4042
4574
  id: task.id,
4043
4575
  title: task.title,
4044
4576
  status: task.status,
4045
- priority: task.priority ?? "-",
4577
+ priority: taskEffectivePriority(task, resolvedTrack.value),
4046
4578
  track: task.track ?? "-",
4579
+ assignee: task.assignee ?? "-",
4580
+ delivery: task.delivery ?? "-",
4047
4581
  filePath
4048
4582
  }));
4049
- 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);
4050
4584
  if (sorted.length === 0) {
4051
4585
  console.log("No tasks found.");
4052
4586
  return;
4053
4587
  }
4054
4588
  console.log(
4055
4589
  formatTable(
4056
- ["ID", "Title", "Status", "Priority", "Track", "File"],
4590
+ ["ID", "Title", "Status", "Priority", "Track", "Assignee", "Delivery", "File"],
4057
4591
  sorted.map((entry) => [
4058
4592
  entry.id,
4059
4593
  entry.title,
4060
4594
  statusColor(entry.status),
4061
4595
  entry.priority,
4062
4596
  entry.track,
4063
- path13.relative(root, entry.filePath)
4597
+ entry.assignee,
4598
+ entry.delivery,
4599
+ path15.relative(root, entry.filePath)
4064
4600
  ])
4065
4601
  )
4066
4602
  );
@@ -4095,16 +4631,31 @@ function listIdeas(options) {
4095
4631
  statusColor(entry.status),
4096
4632
  entry.priority,
4097
4633
  entry.track,
4098
- path13.relative(root, entry.filePath)
4634
+ path15.relative(root, entry.filePath)
4099
4635
  ])
4100
4636
  )
4101
4637
  );
4102
4638
  console.log(`
4103
4639
  Total ideas: ${sorted.length}`);
4104
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
+ }
4105
4656
  function registerListCommand(program) {
4106
4657
  const list = program.command("list").description("List COOP entities");
4107
- 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) => {
4108
4659
  listTasks(options);
4109
4660
  });
4110
4661
  list.command("ideas").description("List ideas").option("--status <status>", "Filter by status").action((options) => {
@@ -4113,32 +4664,32 @@ function registerListCommand(program) {
4113
4664
  list.command("alias").description("List aliases").argument("[pattern]", "Wildcard pattern, e.g. PAY*").action((pattern) => {
4114
4665
  listAliasRows(pattern);
4115
4666
  });
4116
- list.command("deliveries").description("List deliveries (Phase 2)").action(() => {
4117
- printNotImplemented("coop list deliveries", 2);
4667
+ list.command("deliveries").description("List deliveries").action(() => {
4668
+ listDeliveries();
4118
4669
  });
4119
4670
  }
4120
4671
 
4121
4672
  // src/utils/logger.ts
4122
- import fs9 from "fs";
4123
- import path14 from "path";
4673
+ import fs11 from "fs";
4674
+ import path16 from "path";
4124
4675
  function resolveRepoSafe(start = process.cwd()) {
4125
4676
  try {
4126
4677
  return resolveRepoRoot(start);
4127
4678
  } catch {
4128
- return path14.resolve(start);
4679
+ return path16.resolve(start);
4129
4680
  }
4130
4681
  }
4131
4682
  function resolveCliLogFile(start = process.cwd()) {
4132
4683
  const root = resolveRepoSafe(start);
4133
4684
  const workspace = coopWorkspaceDir(root);
4134
- if (fs9.existsSync(workspace)) {
4135
- return path14.join(workspace, "logs", "cli.log");
4685
+ if (fs11.existsSync(workspace)) {
4686
+ return path16.join(workspace, "logs", "cli.log");
4136
4687
  }
4137
- return path14.join(resolveCoopHome(), "logs", "cli.log");
4688
+ return path16.join(resolveCoopHome(), "logs", "cli.log");
4138
4689
  }
4139
4690
  function appendLogEntry(entry, logFile) {
4140
- fs9.mkdirSync(path14.dirname(logFile), { recursive: true });
4141
- fs9.appendFileSync(logFile, `${JSON.stringify(entry)}
4691
+ fs11.mkdirSync(path16.dirname(logFile), { recursive: true });
4692
+ fs11.appendFileSync(logFile, `${JSON.stringify(entry)}
4142
4693
  `, "utf8");
4143
4694
  }
4144
4695
  function logCliError(error, start = process.cwd()) {
@@ -4177,8 +4728,8 @@ function parseLogLine(line) {
4177
4728
  }
4178
4729
  function readLastCliLog(start = process.cwd()) {
4179
4730
  const logFile = resolveCliLogFile(start);
4180
- if (!fs9.existsSync(logFile)) return null;
4181
- const content = fs9.readFileSync(logFile, "utf8");
4731
+ if (!fs11.existsSync(logFile)) return null;
4732
+ const content = fs11.readFileSync(logFile, "utf8");
4182
4733
  const lines = content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
4183
4734
  for (let i = lines.length - 1; i >= 0; i -= 1) {
4184
4735
  const entry = parseLogLine(lines[i] ?? "");
@@ -4212,9 +4763,37 @@ function registerLogCommand(program) {
4212
4763
  });
4213
4764
  }
4214
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
+
4215
4794
  // src/commands/migrate.ts
4216
- import fs10 from "fs";
4217
- import path15 from "path";
4795
+ import fs12 from "fs";
4796
+ import path17 from "path";
4218
4797
  import { createInterface as createInterface2 } from "readline/promises";
4219
4798
  import { stdin as input2, stdout as output2 } from "process";
4220
4799
  import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as parseYamlFile2, writeYamlFile as writeYamlFile4 } from "@kitsy/coop-core";
@@ -4233,22 +4812,22 @@ function parseTargetVersion(raw) {
4233
4812
  return parsed;
4234
4813
  }
4235
4814
  function writeIfMissing2(filePath, content) {
4236
- if (!fs10.existsSync(filePath)) {
4237
- fs10.writeFileSync(filePath, content, "utf8");
4815
+ if (!fs12.existsSync(filePath)) {
4816
+ fs12.writeFileSync(filePath, content, "utf8");
4238
4817
  }
4239
4818
  }
4240
4819
  function ensureGitignoreEntry2(root, entry) {
4241
- const gitignorePath = path15.join(root, ".gitignore");
4242
- if (!fs10.existsSync(gitignorePath)) {
4243
- fs10.writeFileSync(gitignorePath, `${entry}
4820
+ const gitignorePath = path17.join(root, ".gitignore");
4821
+ if (!fs12.existsSync(gitignorePath)) {
4822
+ fs12.writeFileSync(gitignorePath, `${entry}
4244
4823
  `, "utf8");
4245
4824
  return;
4246
4825
  }
4247
- const content = fs10.readFileSync(gitignorePath, "utf8");
4826
+ const content = fs12.readFileSync(gitignorePath, "utf8");
4248
4827
  const lines = content.split(/\r?\n/).map((line) => line.trim());
4249
4828
  if (!lines.includes(entry)) {
4250
4829
  const suffix = content.endsWith("\n") ? "" : "\n";
4251
- fs10.writeFileSync(gitignorePath, `${content}${suffix}${entry}
4830
+ fs12.writeFileSync(gitignorePath, `${content}${suffix}${entry}
4252
4831
  `, "utf8");
4253
4832
  }
4254
4833
  }
@@ -4273,7 +4852,7 @@ function legacyWorkspaceProjectEntries(root) {
4273
4852
  "backlog",
4274
4853
  "plans",
4275
4854
  "releases"
4276
- ].filter((entry) => fs10.existsSync(path15.join(workspaceDir, entry)));
4855
+ ].filter((entry) => fs12.existsSync(path17.join(workspaceDir, entry)));
4277
4856
  }
4278
4857
  function normalizeProjectId2(value) {
4279
4858
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
@@ -4318,7 +4897,7 @@ async function promptProjectIdentity(defaults, options) {
4318
4897
  async function resolveMigrationIdentity(root, options) {
4319
4898
  const existing = readCoopConfig(root);
4320
4899
  const defaults = {
4321
- projectName: existing.projectName || path15.basename(root),
4900
+ projectName: existing.projectName || path17.basename(root),
4322
4901
  projectId: normalizeProjectId2(existing.projectId || repoIdentityId(root)) || repoIdentityId(root),
4323
4902
  projectAliases: options.aliases !== void 0 ? parseAliases3(options.aliases) : existing.projectAliases
4324
4903
  };
@@ -4338,22 +4917,22 @@ async function migrateWorkspaceLayout(root, options) {
4338
4917
  throw new Error(`Unsupported workspace-layout target '${options.to ?? ""}'. Expected 'v2'.`);
4339
4918
  }
4340
4919
  const workspaceDir = coopWorkspaceDir(root);
4341
- if (!fs10.existsSync(workspaceDir)) {
4920
+ if (!fs12.existsSync(workspaceDir)) {
4342
4921
  throw new Error("Missing .coop directory. Run 'coop init' first.");
4343
4922
  }
4344
- const projectsDir = path15.join(workspaceDir, "projects");
4923
+ const projectsDir = path17.join(workspaceDir, "projects");
4345
4924
  const legacyEntries = legacyWorkspaceProjectEntries(root);
4346
- if (legacyEntries.length === 0 && fs10.existsSync(projectsDir)) {
4925
+ if (legacyEntries.length === 0 && fs12.existsSync(projectsDir)) {
4347
4926
  console.log("[COOP] workspace layout already uses v2.");
4348
4927
  return;
4349
4928
  }
4350
4929
  const identity = await resolveMigrationIdentity(root, options);
4351
4930
  const projectId = identity.projectId;
4352
- const projectRoot = path15.join(projectsDir, projectId);
4353
- if (fs10.existsSync(projectRoot) && !options.force) {
4354
- 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.`);
4355
4934
  }
4356
- 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)}`);
4357
4936
  changed.push(`.coop/config.yml -> workspace current_project=${projectId}`);
4358
4937
  console.log(`Workspace layout migration (${options.dryRun ? "DRY RUN" : "APPLY"})`);
4359
4938
  console.log(`- from: v1 flat layout`);
@@ -4369,21 +4948,21 @@ async function migrateWorkspaceLayout(root, options) {
4369
4948
  console.log("- no files were modified.");
4370
4949
  return;
4371
4950
  }
4372
- fs10.mkdirSync(projectsDir, { recursive: true });
4373
- fs10.mkdirSync(projectRoot, { recursive: true });
4951
+ fs12.mkdirSync(projectsDir, { recursive: true });
4952
+ fs12.mkdirSync(projectRoot, { recursive: true });
4374
4953
  for (const entry of legacyEntries) {
4375
- const source = path15.join(workspaceDir, entry);
4376
- const destination = path15.join(projectRoot, entry);
4377
- if (fs10.existsSync(destination)) {
4954
+ const source = path17.join(workspaceDir, entry);
4955
+ const destination = path17.join(projectRoot, entry);
4956
+ if (fs12.existsSync(destination)) {
4378
4957
  if (!options.force) {
4379
- throw new Error(`Destination '${path15.relative(root, destination)}' already exists.`);
4958
+ throw new Error(`Destination '${path17.relative(root, destination)}' already exists.`);
4380
4959
  }
4381
- fs10.rmSync(destination, { recursive: true, force: true });
4960
+ fs12.rmSync(destination, { recursive: true, force: true });
4382
4961
  }
4383
- fs10.renameSync(source, destination);
4962
+ fs12.renameSync(source, destination);
4384
4963
  }
4385
- const movedConfigPath = path15.join(projectRoot, "config.yml");
4386
- if (fs10.existsSync(movedConfigPath)) {
4964
+ const movedConfigPath = path17.join(projectRoot, "config.yml");
4965
+ if (fs12.existsSync(movedConfigPath)) {
4387
4966
  const movedConfig = parseYamlFile2(movedConfigPath);
4388
4967
  const nextProject = typeof movedConfig.project === "object" && movedConfig.project !== null ? { ...movedConfig.project } : {};
4389
4968
  nextProject.name = identity.projectName;
@@ -4400,13 +4979,13 @@ async function migrateWorkspaceLayout(root, options) {
4400
4979
  }
4401
4980
  const workspace = readWorkspaceConfig(root);
4402
4981
  writeWorkspaceConfig(root, { ...workspace, version: 2, current_project: projectId });
4403
- writeIfMissing2(path15.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE2);
4404
- 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);
4405
4984
  ensureGitignoreEntry2(root, ".coop/logs/");
4406
4985
  ensureGitignoreEntry2(root, ".coop/tmp/");
4407
4986
  const manager = new IndexManager3(projectRoot);
4408
4987
  manager.build_full_index();
4409
- 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)}`);
4410
4989
  }
4411
4990
  function registerMigrateCommand(program) {
4412
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) => {
@@ -4425,7 +5004,7 @@ function registerMigrateCommand(program) {
4425
5004
  if (report.changed_files.length > 0) {
4426
5005
  console.log("- changed files:");
4427
5006
  for (const filePath of report.changed_files) {
4428
- console.log(` - ${path15.relative(root, filePath)}`);
5007
+ console.log(` - ${path17.relative(root, filePath)}`);
4429
5008
  }
4430
5009
  }
4431
5010
  if (report.dry_run) {
@@ -4519,7 +5098,7 @@ import chalk3 from "chalk";
4519
5098
  import {
4520
5099
  analyze_feasibility,
4521
5100
  analyze_what_if,
4522
- load_graph as load_graph5,
5101
+ load_graph as load_graph7,
4523
5102
  monte_carlo_forecast,
4524
5103
  TaskPriority as TaskPriority2
4525
5104
  } from "@kitsy/coop-core";
@@ -4583,8 +5162,19 @@ function collectWhatIfModifications(options) {
4583
5162
  }
4584
5163
  async function runPlanDelivery(deliveryName, options) {
4585
5164
  const root = resolveRepoRoot();
4586
- const graph = load_graph5(coopDir(root));
4587
- 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);
4588
5178
  const modifications = collectWhatIfModifications(options);
4589
5179
  if (modifications.length > 0) {
4590
5180
  const comparison = analyze_what_if(
@@ -4699,7 +5289,7 @@ async function runPlanDelivery(deliveryName, options) {
4699
5289
  }
4700
5290
  function runPlanCapacity(trackArg, options) {
4701
5291
  const root = resolveRepoRoot();
4702
- const graph = load_graph5(coopDir(root));
5292
+ const graph = load_graph7(coopDir(root));
4703
5293
  const track = normalizeTrack(trackArg);
4704
5294
  const deliveries = Array.from(graph.deliveries.values()).sort((a, b) => a.id.localeCompare(b.id));
4705
5295
  if (deliveries.length === 0) {
@@ -4739,17 +5329,43 @@ function runPlanCapacity(trackArg, options) {
4739
5329
  }
4740
5330
  function registerPlanCommand(program) {
4741
5331
  const plan = program.command("plan").description("Planning commands");
4742
- 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) => {
4743
- 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);
4744
5334
  });
4745
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) => {
4746
5336
  runPlanCapacity(track, options);
4747
5337
  });
4748
5338
  }
4749
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
+
4750
5366
  // src/commands/project.ts
4751
- import fs11 from "fs";
4752
- import path16 from "path";
5367
+ import fs13 from "fs";
5368
+ import path18 from "path";
4753
5369
  import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3, write_schema_version as write_schema_version2 } from "@kitsy/coop-core";
4754
5370
  var TASK_TEMPLATE2 = `---
4755
5371
  id: TASK-001
@@ -4858,11 +5474,11 @@ var PROJECT_DIRS = [
4858
5474
  ".index"
4859
5475
  ];
4860
5476
  function ensureDir2(dirPath) {
4861
- fs11.mkdirSync(dirPath, { recursive: true });
5477
+ fs13.mkdirSync(dirPath, { recursive: true });
4862
5478
  }
4863
5479
  function writeIfMissing3(filePath, content) {
4864
- if (!fs11.existsSync(filePath)) {
4865
- fs11.writeFileSync(filePath, content, "utf8");
5480
+ if (!fs13.existsSync(filePath)) {
5481
+ fs13.writeFileSync(filePath, content, "utf8");
4866
5482
  }
4867
5483
  }
4868
5484
  function normalizeProjectId3(value) {
@@ -4870,17 +5486,17 @@ function normalizeProjectId3(value) {
4870
5486
  }
4871
5487
  function createProject(root, projectId, projectName, namingTemplate = DEFAULT_ID_NAMING_TEMPLATE) {
4872
5488
  const workspaceDir = coopWorkspaceDir(root);
4873
- const projectRoot = path16.join(workspaceDir, "projects", projectId);
4874
- ensureDir2(path16.join(workspaceDir, "projects"));
5489
+ const projectRoot = path18.join(workspaceDir, "projects", projectId);
5490
+ ensureDir2(path18.join(workspaceDir, "projects"));
4875
5491
  for (const dir of PROJECT_DIRS) {
4876
- ensureDir2(path16.join(projectRoot, dir));
5492
+ ensureDir2(path18.join(projectRoot, dir));
4877
5493
  }
4878
- writeIfMissing3(path16.join(projectRoot, "config.yml"), PROJECT_CONFIG_TEMPLATE(projectId, projectName, namingTemplate));
4879
- 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"))) {
4880
5496
  write_schema_version2(projectRoot, CURRENT_SCHEMA_VERSION3);
4881
5497
  }
4882
- writeIfMissing3(path16.join(projectRoot, "templates/task.md"), TASK_TEMPLATE2);
4883
- 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);
4884
5500
  return projectRoot;
4885
5501
  }
4886
5502
  function registerProjectCommand(program) {
@@ -4908,7 +5524,7 @@ function registerProjectCommand(program) {
4908
5524
  }
4909
5525
  console.log(`id=${active.id}`);
4910
5526
  console.log(`name=${active.name}`);
4911
- console.log(`path=${path16.relative(root, active.root)}`);
5527
+ console.log(`path=${path18.relative(root, active.root)}`);
4912
5528
  console.log(`layout=${active.layout}`);
4913
5529
  });
4914
5530
  project.command("use").description("Set the active COOP project").argument("<id>", "Project id").action((id) => {
@@ -4940,26 +5556,127 @@ function registerProjectCommand(program) {
4940
5556
  version: 2,
4941
5557
  current_project: workspace.current_project || projectId
4942
5558
  });
4943
- 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());
4944
5661
  });
4945
5662
  }
4946
5663
 
4947
5664
  // src/commands/refine.ts
4948
- import fs12 from "fs";
4949
- import path17 from "path";
4950
- 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";
4951
5668
  import { create_provider_refinement_client, refine_idea_to_draft, refine_task_to_draft } from "@kitsy/coop-ai";
4952
- function resolveTaskFile(root, idOrAlias) {
5669
+ function resolveTaskFile2(root, idOrAlias) {
4953
5670
  const target = resolveReference(root, idOrAlias, "task");
4954
- return path17.join(root, ...target.file.split("/"));
5671
+ return path19.join(root, ...target.file.split("/"));
4955
5672
  }
4956
- function resolveIdeaFile2(root, idOrAlias) {
5673
+ function resolveIdeaFile3(root, idOrAlias) {
4957
5674
  const target = resolveReference(root, idOrAlias, "idea");
4958
- return path17.join(root, ...target.file.split("/"));
5675
+ return path19.join(root, ...target.file.split("/"));
4959
5676
  }
4960
5677
  async function readSupplementalInput(root, options) {
4961
5678
  if (options.inputFile?.trim()) {
4962
- return fs12.readFileSync(path17.resolve(root, options.inputFile.trim()), "utf8");
5679
+ return fs15.readFileSync(path19.resolve(root, options.inputFile.trim()), "utf8");
4963
5680
  }
4964
5681
  if (options.stdin) {
4965
5682
  return readStdinText();
@@ -4977,11 +5694,11 @@ function loadAuthorityContext(root, refs) {
4977
5694
  for (const ref of refs ?? []) {
4978
5695
  const filePart = extractRefFile(ref);
4979
5696
  if (!filePart) continue;
4980
- const fullPath = path17.resolve(root, filePart);
4981
- 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;
4982
5699
  out.push({
4983
5700
  ref,
4984
- content: fs12.readFileSync(fullPath, "utf8")
5701
+ content: fs15.readFileSync(fullPath, "utf8")
4985
5702
  });
4986
5703
  }
4987
5704
  return out;
@@ -4991,8 +5708,8 @@ function registerRefineCommand(program) {
4991
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) => {
4992
5709
  const root = resolveRepoRoot();
4993
5710
  const projectDir = ensureCoopInitialized(root);
4994
- const ideaFile = resolveIdeaFile2(root, id);
4995
- const parsed = parseIdeaFile4(ideaFile);
5711
+ const ideaFile = resolveIdeaFile3(root, id);
5712
+ const parsed = parseIdeaFile5(ideaFile);
4996
5713
  const supplemental = await readSupplementalInput(root, options);
4997
5714
  const client = create_provider_refinement_client(readCoopConfig(root).raw);
4998
5715
  const refined = await refine_idea_to_draft(
@@ -5015,8 +5732,8 @@ function registerRefineCommand(program) {
5015
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) => {
5016
5733
  const root = resolveRepoRoot();
5017
5734
  const projectDir = ensureCoopInitialized(root);
5018
- const taskFile = resolveTaskFile(root, id);
5019
- const parsed = parseTaskFile10(taskFile);
5735
+ const taskFile = resolveTaskFile2(root, id);
5736
+ const parsed = parseTaskFile11(taskFile);
5020
5737
  const supplemental = await readSupplementalInput(root, options);
5021
5738
  const client = create_provider_refinement_client(readCoopConfig(root).raw);
5022
5739
  const draft = await refine_task_to_draft(
@@ -5044,15 +5761,15 @@ function registerRefineCommand(program) {
5044
5761
  const written = applyRefinementDraft(root, projectDir, draft);
5045
5762
  console.log(`[COOP] applied draft from ${draftInput.source}: ${written.length} task file(s) updated`);
5046
5763
  for (const filePath of written) {
5047
- console.log(`- ${path17.relative(root, filePath)}`);
5764
+ console.log(`- ${path19.relative(root, filePath)}`);
5048
5765
  }
5049
5766
  });
5050
5767
  }
5051
5768
 
5052
5769
  // src/commands/run.ts
5053
- import fs13 from "fs";
5054
- import path18 from "path";
5055
- 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";
5056
5773
  import {
5057
5774
  build_contract,
5058
5775
  create_provider_agent_client,
@@ -5061,11 +5778,11 @@ import {
5061
5778
  } from "@kitsy/coop-ai";
5062
5779
  function loadTask(root, idOrAlias) {
5063
5780
  const target = resolveReference(root, idOrAlias, "task");
5064
- const taskFile = path18.join(root, ...target.file.split("/"));
5065
- if (!fs13.existsSync(taskFile)) {
5781
+ const taskFile = path20.join(root, ...target.file.split("/"));
5782
+ if (!fs16.existsSync(taskFile)) {
5066
5783
  throw new Error(`Task file not found: ${target.file}`);
5067
5784
  }
5068
- return parseTaskFile11(taskFile).task;
5785
+ return parseTaskFile12(taskFile).task;
5069
5786
  }
5070
5787
  function printContract(contract) {
5071
5788
  console.log(JSON.stringify(contract, null, 2));
@@ -5078,7 +5795,7 @@ function registerRunCommand(program) {
5078
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) => {
5079
5796
  const root = resolveRepoRoot();
5080
5797
  const coop = ensureCoopInitialized(root);
5081
- const graph = load_graph6(coopDir(root));
5798
+ const graph = load_graph8(coopDir(root));
5082
5799
  const task = loadTask(root, id);
5083
5800
  const config = readCoopConfig(root).raw;
5084
5801
  const routedAgent = select_agent(task, config);
@@ -5101,26 +5818,141 @@ function registerRunCommand(program) {
5101
5818
  on_progress: (message) => console.log(`[COOP] ${message}`)
5102
5819
  });
5103
5820
  if (result.status === "failed") {
5104
- 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)}`);
5105
5822
  }
5106
5823
  if (result.status === "paused") {
5107
5824
  console.log(`[COOP] run paused: ${result.run.id}`);
5108
- console.log(`[COOP] log: ${path18.relative(root, result.log_path)}`);
5825
+ console.log(`[COOP] log: ${path20.relative(root, result.log_path)}`);
5109
5826
  return;
5110
5827
  }
5111
5828
  console.log(`[COOP] run completed: ${result.run.id}`);
5112
- 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
+ }
5113
5945
  });
5114
5946
  }
5115
5947
 
5116
5948
  // src/server/api.ts
5117
- import fs14 from "fs";
5949
+ import fs17 from "fs";
5118
5950
  import http2 from "http";
5119
- import path19 from "path";
5951
+ import path21 from "path";
5120
5952
  import {
5121
5953
  analyze_feasibility as analyze_feasibility2,
5122
- load_graph as load_graph7,
5123
- parseTaskFile as parseTaskFile12,
5954
+ load_graph as load_graph10,
5955
+ parseTaskFile as parseTaskFile14,
5124
5956
  resolve_external_dependencies
5125
5957
  } from "@kitsy/coop-core";
5126
5958
  function json(res, statusCode, payload) {
@@ -5162,12 +5994,12 @@ function taskSummary(graph, task, external = []) {
5162
5994
  };
5163
5995
  }
5164
5996
  function taskFileById(root, id) {
5165
- const tasksDir = path19.join(resolveProject(root).root, "tasks");
5166
- if (!fs14.existsSync(tasksDir)) return null;
5167
- 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 });
5168
6000
  const target = `${id}.md`.toLowerCase();
5169
6001
  const match = entries.find((entry) => entry.isFile() && entry.name.toLowerCase() === target);
5170
- return match ? path19.join(tasksDir, match.name) : null;
6002
+ return match ? path21.join(tasksDir, match.name) : null;
5171
6003
  }
5172
6004
  function loadRemoteConfig(root) {
5173
6005
  const raw = readCoopConfig(root).raw;
@@ -5233,7 +6065,7 @@ function createApiServer(root, options = {}) {
5233
6065
  }
5234
6066
  const project = resolveProject(repoRoot);
5235
6067
  const coopPath = project.root;
5236
- const graph = load_graph7(coopPath);
6068
+ const graph = load_graph10(coopPath);
5237
6069
  const resolutions = await externalResolutions(repoRoot, graph, options);
5238
6070
  if (pathname === "/api/tasks") {
5239
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) ?? []));
@@ -5248,7 +6080,7 @@ function createApiServer(root, options = {}) {
5248
6080
  notFound(res, `Task '${taskId}' not found.`);
5249
6081
  return;
5250
6082
  }
5251
- const parsed = parseTaskFile12(filePath);
6083
+ const parsed = parseTaskFile14(filePath);
5252
6084
  const task = graph.nodes.get(parsed.task.id) ?? parsed.task;
5253
6085
  json(res, 200, {
5254
6086
  ...workspaceMeta(repoRoot),
@@ -5257,7 +6089,7 @@ function createApiServer(root, options = {}) {
5257
6089
  created: task.created,
5258
6090
  updated: task.updated,
5259
6091
  body: parsed.body,
5260
- file_path: path19.relative(repoRoot, filePath).replace(/\\/g, "/")
6092
+ file_path: path21.relative(repoRoot, filePath).replace(/\\/g, "/")
5261
6093
  }
5262
6094
  });
5263
6095
  return;
@@ -5345,9 +6177,9 @@ function registerServeCommand(program) {
5345
6177
  }
5346
6178
 
5347
6179
  // src/commands/show.ts
5348
- import fs15 from "fs";
5349
- import path20 from "path";
5350
- 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";
5351
6183
  function stringify(value) {
5352
6184
  if (value === null || value === void 0) return "-";
5353
6185
  if (Array.isArray(value)) return value.length > 0 ? value.join(", ") : "-";
@@ -5365,13 +6197,13 @@ function pushListSection(lines, title, values) {
5365
6197
  }
5366
6198
  }
5367
6199
  function loadComputedFromIndex(root, taskId) {
5368
- const indexPath = path20.join(ensureCoopInitialized(root), ".index", "tasks.json");
5369
- if (!fs15.existsSync(indexPath)) {
6200
+ const indexPath = path22.join(ensureCoopInitialized(root), ".index", "tasks.json");
6201
+ if (!fs18.existsSync(indexPath)) {
5370
6202
  return null;
5371
6203
  }
5372
6204
  let parsed;
5373
6205
  try {
5374
- parsed = JSON.parse(fs15.readFileSync(indexPath, "utf8"));
6206
+ parsed = JSON.parse(fs18.readFileSync(indexPath, "utf8"));
5375
6207
  } catch {
5376
6208
  return null;
5377
6209
  }
@@ -5406,12 +6238,16 @@ function loadComputedFromIndex(root, taskId) {
5406
6238
  }
5407
6239
  return null;
5408
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
+ }
5409
6247
  function showTask(taskId) {
5410
6248
  const root = resolveRepoRoot();
5411
- const coop = ensureCoopInitialized(root);
5412
- const target = resolveReference(root, taskId, "task");
5413
- const taskFile = path20.join(root, ...target.file.split("/"));
5414
- const parsed = parseTaskFile13(taskFile);
6249
+ const context = readWorkingContext(root, resolveCoopHome());
6250
+ const { filePath, parsed } = loadTaskEntry(root, taskId);
5415
6251
  const task = parsed.task;
5416
6252
  const body = parsed.body.trim();
5417
6253
  const computed = loadComputedFromIndex(root, task.id);
@@ -5421,15 +6257,22 @@ function showTask(taskId) {
5421
6257
  `Status: ${task.status}`,
5422
6258
  `Type: ${task.type}`,
5423
6259
  `Priority: ${task.priority ?? "-"}`,
6260
+ `Effective Priority: ${taskEffectivePriority(task, context.track)}`,
5424
6261
  `Track: ${task.track ?? "-"}`,
6262
+ `Delivery Tracks: ${stringify(task.delivery_tracks)}`,
6263
+ `Priority Context: ${stringify(task.priority_context)}`,
5425
6264
  `Assignee: ${task.assignee ?? "-"}`,
5426
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)}`,
5427
6270
  `Aliases: ${stringify(task.aliases)}`,
5428
6271
  `Depends On: ${stringify(task.depends_on)}`,
5429
6272
  `Tags: ${stringify(task.tags)}`,
5430
6273
  `Created: ${task.created}`,
5431
6274
  `Updated: ${task.updated}`,
5432
- `File: ${path20.relative(root, taskFile)}`,
6275
+ `File: ${path22.relative(root, filePath)}`,
5433
6276
  ""
5434
6277
  ];
5435
6278
  pushListSection(lines, "Acceptance", task.acceptance);
@@ -5446,15 +6289,25 @@ function showTask(taskId) {
5446
6289
  lines.push(`- Promoted To: ${stringify(task.origin.promoted_to)}`);
5447
6290
  lines.push(`- Snapshot SHA256: ${task.origin.snapshot_sha256 ?? "-"}`);
5448
6291
  }
5449
- lines.push(
5450
- "",
5451
- "Body:",
5452
- body || "-",
5453
- "",
5454
- "Computed:"
5455
- );
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:");
5456
6309
  if (!computed) {
5457
- 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)`);
5458
6311
  } else {
5459
6312
  for (const [key, value] of Object.entries(computed)) {
5460
6313
  lines.push(`- ${key}: ${stringify(value)}`);
@@ -5464,10 +6317,9 @@ function showTask(taskId) {
5464
6317
  }
5465
6318
  function showIdea(ideaId) {
5466
6319
  const root = resolveRepoRoot();
5467
- ensureCoopInitialized(root);
5468
6320
  const target = resolveReference(root, ideaId, "idea");
5469
- const ideaFile = path20.join(root, ...target.file.split("/"));
5470
- const parsed = parseIdeaFile5(ideaFile);
6321
+ const ideaFile = path22.join(root, ...target.file.split("/"));
6322
+ const parsed = parseIdeaFile7(ideaFile);
5471
6323
  const idea = parsed.idea;
5472
6324
  const body = parsed.body.trim();
5473
6325
  const lines = [
@@ -5480,29 +6332,68 @@ function showIdea(ideaId) {
5480
6332
  `Tags: ${stringify(idea.tags)}`,
5481
6333
  `Linked Tasks: ${stringify(idea.linked_tasks)}`,
5482
6334
  `Created: ${idea.created}`,
5483
- `File: ${path20.relative(root, ideaFile)}`,
6335
+ `File: ${path22.relative(root, ideaFile)}`,
5484
6336
  "",
5485
6337
  "Body:",
5486
6338
  body || "-"
5487
6339
  ];
5488
6340
  console.log(lines.join("\n"));
5489
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
+ }
5490
6376
  function registerShowCommand(program) {
5491
- 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
+ });
5492
6383
  show.command("task").description("Show task details").argument("<id>", "Task ID").action((id) => {
5493
6384
  showTask(id);
5494
6385
  });
5495
6386
  show.command("idea").description("Show idea details").argument("<id>", "Idea ID").action((id) => {
5496
6387
  showIdea(id);
5497
6388
  });
5498
- show.command("delivery").description("Show delivery details (Phase 2)").argument("<name>", "Delivery name").action(() => {
5499
- printNotImplemented("coop show delivery", 2);
6389
+ show.command("delivery").description("Show delivery details").argument("<id>", "Delivery id or name").action((id) => {
6390
+ showDelivery(id);
5500
6391
  });
5501
6392
  }
5502
6393
 
5503
6394
  // src/commands/status.ts
5504
6395
  import chalk4 from "chalk";
5505
- 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";
5506
6397
  function countBy(values, keyFn) {
5507
6398
  const out = /* @__PURE__ */ new Map();
5508
6399
  for (const value of values) {
@@ -5523,10 +6414,16 @@ function completionSummary(done, totalTasks) {
5523
6414
  const percent = totalTasks > 0 ? Math.round(done / totalTasks * 100) : 0;
5524
6415
  return `${done}/${totalTasks} (${percent}%)`;
5525
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
+ }
5526
6423
  function registerStatusCommand(program) {
5527
6424
  program.command("status").description("Project dashboard overview").option("--today <date>", "Evaluation date (YYYY-MM-DD)").action((options) => {
5528
6425
  const root = resolveRepoRoot();
5529
- const graph = load_graph8(coopDir(root));
6426
+ const graph = load_graph11(coopDir(root));
5530
6427
  const today = options.today ?? /* @__PURE__ */ new Date();
5531
6428
  const tasks = Array.from(graph.nodes.values());
5532
6429
  const tasksByStatus = countBy(tasks, (task) => task.status);
@@ -5546,6 +6443,14 @@ function registerStatusCommand(program) {
5546
6443
  const trackRows = Array.from(tasksByTrack.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([track, count]) => [track, String(count)]);
5547
6444
  lines.push(formatTable(["Track", "Count"], trackRows));
5548
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("");
5549
6454
  lines.push(chalk4.bold("Delivery Health"));
5550
6455
  const deliveryRows = [];
5551
6456
  const riskLines = [];
@@ -5597,8 +6502,8 @@ function registerTransitionCommand(program) {
5597
6502
  }
5598
6503
 
5599
6504
  // src/commands/taskflow.ts
5600
- import path21 from "path";
5601
- import { parseTaskFile as parseTaskFile14 } from "@kitsy/coop-core";
6505
+ import path23 from "path";
6506
+ import { parseTaskFile as parseTaskFile15 } from "@kitsy/coop-core";
5602
6507
  function normalizeExecutor(value) {
5603
6508
  if (!value?.trim()) return void 0;
5604
6509
  const normalized = value.trim().toLowerCase();
@@ -5635,8 +6540,8 @@ async function claimAndStart(root, taskId, options) {
5635
6540
  }
5636
6541
  }
5637
6542
  const reference = resolveReference(root, taskId, "task");
5638
- const filePath = path21.join(root, ...reference.file.split("/"));
5639
- const parsed = parseTaskFile14(filePath);
6543
+ const filePath = path23.join(root, ...reference.file.split("/"));
6544
+ const parsed = parseTaskFile15(filePath);
5640
6545
  if (parsed.task.status === "in_progress") {
5641
6546
  console.log(`Task ${parsed.task.id} is already in_progress.`);
5642
6547
  return;
@@ -5648,10 +6553,37 @@ async function claimAndStart(root, taskId, options) {
5648
6553
  });
5649
6554
  console.log(`Updated ${transitioned.task.id}: ${transitioned.from} -> ${transitioned.to}`);
5650
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
+ }
5651
6582
  function registerTaskFlowCommands(program) {
5652
6583
  const next = program.command("next").description("Select the next COOP work item");
5653
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) => {
5654
6585
  const root = resolveRepoRoot();
6586
+ printResolvedSelectionContext(root, options);
5655
6587
  const selected = selectTopReadyTask(root, {
5656
6588
  track: options.track,
5657
6589
  delivery: options.delivery,
@@ -5661,29 +6593,49 @@ function registerTaskFlowCommands(program) {
5661
6593
  console.log(formatSelectedTask(selected.entry, selected.selection));
5662
6594
  });
5663
6595
  const pick = program.command("pick").description("Pick the next COOP work item");
5664
- 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) => {
5665
6597
  const root = resolveRepoRoot();
5666
- 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, {
5667
6615
  track: options.track,
5668
6616
  delivery: options.delivery,
6617
+ version: options.version,
5669
6618
  executor: normalizeExecutor(options.executor),
5670
6619
  today: options.today
5671
6620
  });
5672
6621
  console.log(formatSelectedTask(selected.entry, selected.selection));
6622
+ maybePromote(root, selected.entry.task.id, options);
5673
6623
  await claimAndStart(root, selected.entry.task.id, options);
5674
6624
  });
5675
6625
  const start = program.command("start").description("Start COOP work on a task");
5676
- 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) => {
5677
6627
  const root = resolveRepoRoot();
6628
+ printResolvedSelectionContext(root, options);
5678
6629
  const taskId = id?.trim() || selectTopReadyTask(root, {
5679
6630
  track: options.track,
5680
6631
  delivery: options.delivery,
6632
+ version: options.version,
5681
6633
  executor: normalizeExecutor(options.executor),
5682
6634
  today: options.today
5683
6635
  }).entry.task.id;
5684
6636
  const reference = resolveReference(root, taskId, "task");
5685
- const filePath = path21.join(root, ...reference.file.split("/"));
5686
- const parsed = parseTaskFile14(filePath);
6637
+ const filePath = path23.join(root, ...reference.file.split("/"));
6638
+ const parsed = parseTaskFile15(filePath);
5687
6639
  console.log(
5688
6640
  formatSelectedTask(
5689
6641
  {
@@ -5696,29 +6648,31 @@ function registerTaskFlowCommands(program) {
5696
6648
  {
5697
6649
  track: options.track,
5698
6650
  delivery: options.delivery,
6651
+ version: options.version,
5699
6652
  executor: normalizeExecutor(options.executor),
5700
6653
  today: options.today
5701
6654
  }
5702
6655
  )
5703
6656
  );
6657
+ maybePromote(root, reference.id, options);
5704
6658
  await claimAndStart(root, reference.id, options);
5705
6659
  });
5706
6660
  }
5707
6661
 
5708
6662
  // src/commands/ui.ts
5709
- import fs16 from "fs";
5710
- import path22 from "path";
6663
+ import fs19 from "fs";
6664
+ import path24 from "path";
5711
6665
  import { createRequire } from "module";
5712
6666
  import { fileURLToPath } from "url";
5713
6667
  import { spawn } from "child_process";
5714
6668
  import { IndexManager as IndexManager4 } from "@kitsy/coop-core";
5715
6669
  function findPackageRoot(entryPath) {
5716
- let current = path22.dirname(entryPath);
6670
+ let current = path24.dirname(entryPath);
5717
6671
  while (true) {
5718
- if (fs16.existsSync(path22.join(current, "package.json"))) {
6672
+ if (fs19.existsSync(path24.join(current, "package.json"))) {
5719
6673
  return current;
5720
6674
  }
5721
- const parent = path22.dirname(current);
6675
+ const parent = path24.dirname(current);
5722
6676
  if (parent === current) {
5723
6677
  throw new Error(`Unable to locate package root for ${entryPath}.`);
5724
6678
  }
@@ -5767,9 +6721,9 @@ async function startUiServer(repoRoot, host, port, shouldOpen) {
5767
6721
  const project = resolveProject(repoRoot);
5768
6722
  ensureIndex(repoRoot);
5769
6723
  const uiRoot = resolveUiPackageRoot();
5770
- const requireFromUi = createRequire(path22.join(uiRoot, "package.json"));
6724
+ const requireFromUi = createRequire(path24.join(uiRoot, "package.json"));
5771
6725
  const vitePackageJson = requireFromUi.resolve("vite/package.json");
5772
- const viteBin = path22.join(path22.dirname(vitePackageJson), "bin", "vite.js");
6726
+ const viteBin = path24.join(path24.dirname(vitePackageJson), "bin", "vite.js");
5773
6727
  const url = `http://${host}:${port}`;
5774
6728
  console.log(`COOP UI: ${url}`);
5775
6729
  const child = spawn(process.execPath, [viteBin, "--host", host, "--port", String(port)], {
@@ -5815,12 +6769,197 @@ function registerUiCommand(program) {
5815
6769
  });
5816
6770
  }
5817
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
+
5818
6957
  // src/commands/view.ts
5819
6958
  import {
5820
6959
  analyze_feasibility as analyze_feasibility4,
5821
6960
  compute_velocity,
5822
6961
  load_completed_runs,
5823
- load_graph as load_graph9,
6962
+ load_graph as load_graph12,
5824
6963
  simulate_schedule
5825
6964
  } from "@kitsy/coop-core";
5826
6965
  var STATUS_COLUMNS = [
@@ -5883,9 +7022,12 @@ function formatAccuracy(value) {
5883
7022
  if (value == null) return "-";
5884
7023
  return `${Math.round(value * 100)}%`;
5885
7024
  }
7025
+ function workedHours2(task) {
7026
+ return (task.time?.logs ?? []).filter((entry) => entry.kind === "worked").reduce((sum, entry) => sum + entry.hours, 0);
7027
+ }
5886
7028
  function runKanban() {
5887
7029
  const root = resolveRepoRoot();
5888
- const graph = load_graph9(coopDir(root));
7030
+ const graph = load_graph12(coopDir(root));
5889
7031
  const grouped = /* @__PURE__ */ new Map();
5890
7032
  for (const status of STATUS_COLUMNS) {
5891
7033
  grouped.set(status, []);
@@ -5917,11 +7059,19 @@ function runKanban() {
5917
7059
  }
5918
7060
  function runTimeline(options) {
5919
7061
  const root = resolveRepoRoot();
5920
- const graph = load_graph9(coopDir(root));
5921
- if (!options.delivery) {
5922
- 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>`.");
5923
7073
  }
5924
- const delivery = resolveDelivery(graph, options.delivery);
7074
+ const delivery = resolveDelivery(graph, deliveryRef);
5925
7075
  const include = new Set(delivery.scope.include);
5926
7076
  for (const id of delivery.scope.exclude) include.delete(id);
5927
7077
  const tasks = Array.from(include).map((id) => graph.nodes.get(id)).filter((task) => Boolean(task));
@@ -5962,7 +7112,7 @@ function runTimeline(options) {
5962
7112
  function runVelocity(options) {
5963
7113
  const root = resolveRepoRoot();
5964
7114
  const coopPath = coopDir(root);
5965
- const graph = load_graph9(coopPath);
7115
+ const graph = load_graph12(coopPath);
5966
7116
  const config = readCoopConfig(root).raw;
5967
7117
  const scheduling = typeof config.scheduling === "object" && config.scheduling !== null ? config.scheduling : {};
5968
7118
  const configuredWeeks = Number(scheduling.velocity_window_weeks);
@@ -5972,12 +7122,19 @@ function runVelocity(options) {
5972
7122
  today: options.today ?? /* @__PURE__ */ new Date(),
5973
7123
  graph
5974
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);
5975
7130
  console.log(`Velocity: last ${metrics.window_weeks} weeks`);
5976
7131
  console.log(`Trend: ${metrics.trend}`);
5977
7132
  console.log(`Completed runs: ${metrics.completed_runs}`);
5978
7133
  console.log(`Tasks/week: ${metrics.tasks_completed_per_week}`);
5979
7134
  console.log(`Hours/week: ${metrics.hours_delivered_per_week}`);
7135
+ console.log(`Story points/week: ${Number((storyPointsTotal / metrics.window_weeks).toFixed(2))}`);
5980
7136
  console.log(`Accuracy: ${formatAccuracy(metrics.accuracy_ratio)}`);
7137
+ console.log(`Planned vs Worked: ${plannedHoursTotal.toFixed(2)}h planned | ${workedHoursTotal.toFixed(2)}h worked`);
5981
7138
  console.log(`Sparkline: ${sparkline(metrics.points.map((point) => point.completed_tasks))}`);
5982
7139
  console.log("");
5983
7140
  console.log(
@@ -5993,12 +7150,20 @@ function runVelocity(options) {
5993
7150
  }
5994
7151
  function runBurndown(options) {
5995
7152
  const root = resolveRepoRoot();
7153
+ const context = readWorkingContext(root, resolveCoopHome());
5996
7154
  const coopPath = coopDir(root);
5997
- const graph = load_graph9(coopPath);
5998
- if (!options.delivery) {
5999
- 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>`.");
6000
7165
  }
6001
- const delivery = resolveDelivery(graph, options.delivery);
7166
+ const delivery = resolveDelivery(graph, deliveryRef);
6002
7167
  const include = new Set(delivery.scope.include);
6003
7168
  for (const id of delivery.scope.exclude) include.delete(id);
6004
7169
  const scopedTasks = Array.from(include).map((id) => graph.nodes.get(id)).filter((task) => Boolean(task));
@@ -6038,7 +7203,7 @@ function runBurndown(options) {
6038
7203
  }
6039
7204
  function runCapacity(options) {
6040
7205
  const root = resolveRepoRoot();
6041
- const graph = load_graph9(coopDir(root));
7206
+ const graph = load_graph12(coopDir(root));
6042
7207
  const deliveries = Array.from(graph.deliveries.values()).sort((a, b) => a.id.localeCompare(b.id));
6043
7208
  if (deliveries.length === 0) {
6044
7209
  console.log("No deliveries found.");
@@ -6126,11 +7291,11 @@ function registerWebhookCommand(program) {
6126
7291
  }
6127
7292
 
6128
7293
  // src/merge-driver/merge-driver.ts
6129
- import fs17 from "fs";
7294
+ import fs21 from "fs";
6130
7295
  import os2 from "os";
6131
- import path23 from "path";
7296
+ import path25 from "path";
6132
7297
  import { spawnSync as spawnSync5 } from "child_process";
6133
- 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";
6134
7299
  var STATUS_RANK = {
6135
7300
  blocked: 0,
6136
7301
  canceled: 0,
@@ -6211,33 +7376,33 @@ function mergeTextWithGit(ancestor, ours, theirs) {
6211
7376
  return { ok: false, output: stdout };
6212
7377
  }
6213
7378
  function mergeTaskFile(ancestorPath, oursPath, theirsPath) {
6214
- const ancestorRaw = fs17.readFileSync(ancestorPath, "utf8");
6215
- const oursRaw = fs17.readFileSync(oursPath, "utf8");
6216
- 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");
6217
7382
  const ancestor = parseTaskDocument(ancestorRaw, ancestorPath);
6218
7383
  const ours = parseTaskDocument(oursRaw, oursPath);
6219
7384
  const theirs = parseTaskDocument(theirsRaw, theirsPath);
6220
7385
  const mergedFrontmatter = mergeTaskFrontmatter(ancestor.frontmatter, ours.frontmatter, theirs.frontmatter);
6221
- const tempDir = fs17.mkdtempSync(path23.join(os2.tmpdir(), "coop-merge-body-"));
7386
+ const tempDir = fs21.mkdtempSync(path25.join(os2.tmpdir(), "coop-merge-body-"));
6222
7387
  try {
6223
- const ancestorBody = path23.join(tempDir, "ancestor.md");
6224
- const oursBody = path23.join(tempDir, "ours.md");
6225
- const theirsBody = path23.join(tempDir, "theirs.md");
6226
- fs17.writeFileSync(ancestorBody, ancestor.body, "utf8");
6227
- fs17.writeFileSync(oursBody, ours.body, "utf8");
6228
- 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");
6229
7394
  const mergedBody = mergeTextWithGit(ancestorBody, oursBody, theirsBody);
6230
- const output3 = stringifyFrontmatter4(mergedFrontmatter, mergedBody.output);
6231
- fs17.writeFileSync(oursPath, output3, "utf8");
7395
+ const output3 = stringifyFrontmatter6(mergedFrontmatter, mergedBody.output);
7396
+ fs21.writeFileSync(oursPath, output3, "utf8");
6232
7397
  return mergedBody.ok ? 0 : 1;
6233
7398
  } finally {
6234
- fs17.rmSync(tempDir, { recursive: true, force: true });
7399
+ fs21.rmSync(tempDir, { recursive: true, force: true });
6235
7400
  }
6236
7401
  }
6237
7402
  function mergeDeliveryFile(ancestorPath, oursPath, theirsPath) {
6238
- const ancestor = parseYamlContent3(fs17.readFileSync(ancestorPath, "utf8"), ancestorPath);
6239
- const ours = parseYamlContent3(fs17.readFileSync(oursPath, "utf8"), oursPath);
6240
- 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);
6241
7406
  const oursUpdated = asTimestamp(ours.updated);
6242
7407
  const theirsUpdated = asTimestamp(theirs.updated);
6243
7408
  const base = ancestor;
@@ -6247,7 +7412,7 @@ function mergeDeliveryFile(ancestorPath, oursPath, theirsPath) {
6247
7412
  const value = chooseFieldValue(key, base[key], ours[key], theirs[key], oursUpdated, theirsUpdated);
6248
7413
  if (value !== void 0) merged[key] = value;
6249
7414
  }
6250
- fs17.writeFileSync(oursPath, stringifyYamlContent2(merged), "utf8");
7415
+ fs21.writeFileSync(oursPath, stringifyYamlContent2(merged), "utf8");
6251
7416
  return 0;
6252
7417
  }
6253
7418
  function runMergeDriver(kind, ancestorPath, oursPath, theirsPath) {
@@ -6257,12 +7422,17 @@ function runMergeDriver(kind, ancestorPath, oursPath, theirsPath) {
6257
7422
  return mergeTaskFile(ancestorPath, oursPath, theirsPath);
6258
7423
  }
6259
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
+
6260
7430
  // src/index.ts
6261
7431
  function readVersion() {
6262
7432
  const currentFile = fileURLToPath2(import.meta.url);
6263
- const packageJsonPath = path24.resolve(path24.dirname(currentFile), "..", "package.json");
7433
+ const packageJsonPath = path26.resolve(path26.dirname(currentFile), "..", "package.json");
6264
7434
  try {
6265
- const parsed = JSON.parse(fs18.readFileSync(packageJsonPath, "utf8"));
7435
+ const parsed = JSON.parse(fs22.readFileSync(packageJsonPath, "utf8"));
6266
7436
  return parsed.version ?? "0.0.0";
6267
7437
  } catch {
6268
7438
  return "0.0.0";
@@ -6282,9 +7452,12 @@ function createProgram() {
6282
7452
  program.option("-p, --project <id>", "Select the active COOP project");
6283
7453
  registerInitCommand(program);
6284
7454
  registerCreateCommand(program);
7455
+ registerCurrentCommand(program);
6285
7456
  registerAssignCommand(program);
7457
+ registerCommentCommand(program);
6286
7458
  registerAliasCommand(program);
6287
7459
  registerConfigCommand(program);
7460
+ registerDepsCommand(program);
6288
7461
  registerListCommand(program);
6289
7462
  registerShowCommand(program);
6290
7463
  registerTransitionCommand(program);
@@ -6293,16 +7466,22 @@ function createProgram() {
6293
7466
  registerHelpAiCommand(program);
6294
7467
  registerIndexCommand(program);
6295
7468
  registerLogCommand(program);
7469
+ registerLogTimeCommand(program);
6296
7470
  registerLifecycleCommands(program);
6297
7471
  registerMigrateCommand(program);
6298
7472
  registerNamingCommand(program);
6299
7473
  registerPlanCommand(program);
7474
+ registerPromoteCommand(program);
6300
7475
  registerProjectCommand(program);
7476
+ registerPromptCommand(program);
6301
7477
  registerRefineCommand(program);
6302
7478
  registerRunCommand(program);
7479
+ registerSearchCommand(program);
6303
7480
  registerServeCommand(program);
6304
7481
  registerStatusCommand(program);
6305
7482
  registerUiCommand(program);
7483
+ registerUpdateCommand(program);
7484
+ registerUseCommand(program);
6306
7485
  registerViewCommand(program);
6307
7486
  registerWebhookCommand(program);
6308
7487
  registerPhasePlaceholder(program, "ext", 3, "Plugin extension commands");
@@ -6362,7 +7541,7 @@ async function runCli(argv = process.argv) {
6362
7541
  function isMainModule() {
6363
7542
  const entry = process.argv[1];
6364
7543
  if (!entry) return false;
6365
- return path24.resolve(entry) === fileURLToPath2(import.meta.url);
7544
+ return path26.resolve(entry) === fileURLToPath2(import.meta.url);
6366
7545
  }
6367
7546
  if (isMainModule()) {
6368
7547
  await runCli(process.argv);