@mclawnet/task 0.1.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 (114) hide show
  1. package/README.md +100 -0
  2. package/dist/__tests__/compute-lead-briefing.test.d.ts +2 -0
  3. package/dist/__tests__/compute-lead-briefing.test.d.ts.map +1 -0
  4. package/dist/__tests__/compute-lead-briefing.test.js +81 -0
  5. package/dist/__tests__/compute-lead-briefing.test.js.map +1 -0
  6. package/dist/__tests__/compute-member-briefing.test.d.ts +2 -0
  7. package/dist/__tests__/compute-member-briefing.test.d.ts.map +1 -0
  8. package/dist/__tests__/compute-member-briefing.test.js +36 -0
  9. package/dist/__tests__/compute-member-briefing.test.js.map +1 -0
  10. package/dist/__tests__/compute-task-briefing.test.d.ts +2 -0
  11. package/dist/__tests__/compute-task-briefing.test.d.ts.map +1 -0
  12. package/dist/__tests__/compute-task-briefing.test.js +72 -0
  13. package/dist/__tests__/compute-task-briefing.test.js.map +1 -0
  14. package/dist/__tests__/dag.test.d.ts +2 -0
  15. package/dist/__tests__/dag.test.d.ts.map +1 -0
  16. package/dist/__tests__/dag.test.js +81 -0
  17. package/dist/__tests__/dag.test.js.map +1 -0
  18. package/dist/__tests__/format-lead-briefing.test.d.ts +2 -0
  19. package/dist/__tests__/format-lead-briefing.test.d.ts.map +1 -0
  20. package/dist/__tests__/format-lead-briefing.test.js +70 -0
  21. package/dist/__tests__/format-lead-briefing.test.js.map +1 -0
  22. package/dist/__tests__/format-member-briefing.test.d.ts +2 -0
  23. package/dist/__tests__/format-member-briefing.test.d.ts.map +1 -0
  24. package/dist/__tests__/format-member-briefing.test.js +33 -0
  25. package/dist/__tests__/format-member-briefing.test.js.map +1 -0
  26. package/dist/__tests__/format-task-briefing.test.d.ts +2 -0
  27. package/dist/__tests__/format-task-briefing.test.d.ts.map +1 -0
  28. package/dist/__tests__/format-task-briefing.test.js +74 -0
  29. package/dist/__tests__/format-task-briefing.test.js.map +1 -0
  30. package/dist/__tests__/locking.test.d.ts +2 -0
  31. package/dist/__tests__/locking.test.d.ts.map +1 -0
  32. package/dist/__tests__/locking.test.js +38 -0
  33. package/dist/__tests__/locking.test.js.map +1 -0
  34. package/dist/__tests__/meta.test.d.ts +2 -0
  35. package/dist/__tests__/meta.test.d.ts.map +1 -0
  36. package/dist/__tests__/meta.test.js +48 -0
  37. package/dist/__tests__/meta.test.js.map +1 -0
  38. package/dist/__tests__/path.test.d.ts +2 -0
  39. package/dist/__tests__/path.test.d.ts.map +1 -0
  40. package/dist/__tests__/path.test.js +45 -0
  41. package/dist/__tests__/path.test.js.map +1 -0
  42. package/dist/__tests__/schema.test.d.ts +2 -0
  43. package/dist/__tests__/schema.test.d.ts.map +1 -0
  44. package/dist/__tests__/schema.test.js +49 -0
  45. package/dist/__tests__/schema.test.js.map +1 -0
  46. package/dist/__tests__/store-create-deps.test.d.ts +2 -0
  47. package/dist/__tests__/store-create-deps.test.d.ts.map +1 -0
  48. package/dist/__tests__/store-create-deps.test.js +23 -0
  49. package/dist/__tests__/store-create-deps.test.js.map +1 -0
  50. package/dist/__tests__/store-link.test.d.ts +2 -0
  51. package/dist/__tests__/store-link.test.d.ts.map +1 -0
  52. package/dist/__tests__/store-link.test.js +64 -0
  53. package/dist/__tests__/store-link.test.js.map +1 -0
  54. package/dist/__tests__/store-rebuild.test.d.ts +2 -0
  55. package/dist/__tests__/store-rebuild.test.d.ts.map +1 -0
  56. package/dist/__tests__/store-rebuild.test.js +47 -0
  57. package/dist/__tests__/store-rebuild.test.js.map +1 -0
  58. package/dist/__tests__/store.test.d.ts +2 -0
  59. package/dist/__tests__/store.test.d.ts.map +1 -0
  60. package/dist/__tests__/store.test.js +70 -0
  61. package/dist/__tests__/store.test.js.map +1 -0
  62. package/dist/briefings/compute-lead.d.ts +17 -0
  63. package/dist/briefings/compute-lead.d.ts.map +1 -0
  64. package/dist/briefings/compute-lead.js +52 -0
  65. package/dist/briefings/compute-lead.js.map +1 -0
  66. package/dist/briefings/compute-member.d.ts +12 -0
  67. package/dist/briefings/compute-member.d.ts.map +1 -0
  68. package/dist/briefings/compute-member.js +30 -0
  69. package/dist/briefings/compute-member.js.map +1 -0
  70. package/dist/briefings/compute-task.d.ts +17 -0
  71. package/dist/briefings/compute-task.d.ts.map +1 -0
  72. package/dist/briefings/compute-task.js +60 -0
  73. package/dist/briefings/compute-task.js.map +1 -0
  74. package/dist/briefings/format-lead.d.ts +19 -0
  75. package/dist/briefings/format-lead.d.ts.map +1 -0
  76. package/dist/briefings/format-lead.js +47 -0
  77. package/dist/briefings/format-lead.js.map +1 -0
  78. package/dist/briefings/format-member.d.ts +18 -0
  79. package/dist/briefings/format-member.d.ts.map +1 -0
  80. package/dist/briefings/format-member.js +24 -0
  81. package/dist/briefings/format-member.js.map +1 -0
  82. package/dist/briefings/format-task.d.ts +19 -0
  83. package/dist/briefings/format-task.d.ts.map +1 -0
  84. package/dist/briefings/format-task.js +48 -0
  85. package/dist/briefings/format-task.js.map +1 -0
  86. package/dist/dag.d.ts +10 -0
  87. package/dist/dag.d.ts.map +1 -0
  88. package/dist/dag.js +33 -0
  89. package/dist/dag.js.map +1 -0
  90. package/dist/index.d.ts +17 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +13 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/locking.d.ts +8 -0
  95. package/dist/locking.d.ts.map +1 -0
  96. package/dist/locking.js +19 -0
  97. package/dist/locking.js.map +1 -0
  98. package/dist/meta.d.ts +21 -0
  99. package/dist/meta.d.ts.map +1 -0
  100. package/dist/meta.js +37 -0
  101. package/dist/meta.js.map +1 -0
  102. package/dist/path.d.ts +7 -0
  103. package/dist/path.d.ts.map +1 -0
  104. package/dist/path.js +34 -0
  105. package/dist/path.js.map +1 -0
  106. package/dist/schema.d.ts +42 -0
  107. package/dist/schema.d.ts.map +1 -0
  108. package/dist/schema.js +41 -0
  109. package/dist/schema.js.map +1 -0
  110. package/dist/store.d.ts +47 -0
  111. package/dist/store.d.ts.map +1 -0
  112. package/dist/store.js +248 -0
  113. package/dist/store.js.map +1 -0
  114. package/package.json +33 -0
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { formatTaskBriefing } from "../briefings/format-task.js";
3
+ import { createEmptyTask } from "../schema.js";
4
+ function mkTask(id, subject, status = "pending") {
5
+ const t = createEmptyTask({ id, teamName: "team-a", subject });
6
+ t.status = status;
7
+ return t;
8
+ }
9
+ function emptyBody() {
10
+ return {
11
+ actionable: [],
12
+ awareness: [],
13
+ counters: { actionable: 0, awareness: 0, blocked: 0, waitingOnUser: 0 },
14
+ warnings: [],
15
+ };
16
+ }
17
+ describe("formatTaskBriefing", () => {
18
+ it("renders the empty placeholder when body is fully empty", () => {
19
+ expect(formatTaskBriefing(emptyBody())).toBe("### 你的任务简报: 暂无任务");
20
+ });
21
+ it("renders one actionable task with instanceId in header", () => {
22
+ const body = {
23
+ ...emptyBody(),
24
+ actionable: [mkTask("T-123", "do the thing", "in_progress")],
25
+ counters: { actionable: 1, awareness: 0, blocked: 0, waitingOnUser: 0 },
26
+ };
27
+ const out = formatTaskBriefing(body, { instanceId: "worker-0" });
28
+ expect(out).toContain("### 你的任务简报 (worker-0)");
29
+ expect(out).toContain("**可执行 (1)**");
30
+ expect(out).toContain("T-123");
31
+ expect(out).toContain("[in_progress]");
32
+ expect(out).toContain("do the thing");
33
+ expect(out).toContain("**计数**: actionable=1 awareness=0 blocked=0 waitingOnUser=0");
34
+ });
35
+ it("truncates when actionable exceeds maxItems and preserves original count", () => {
36
+ const tasks = [];
37
+ for (let i = 0; i < 12; i++)
38
+ tasks.push(mkTask(`T-${i}`, `s${i}`));
39
+ const body = {
40
+ ...emptyBody(),
41
+ actionable: tasks,
42
+ counters: { actionable: 12, awareness: 0, blocked: 0, waitingOnUser: 0 },
43
+ };
44
+ const out = formatTaskBriefing(body, { maxItems: 10 });
45
+ const bulletLines = out.split("\n").filter((l) => /^- T-\d+ \[/.test(l));
46
+ expect(bulletLines).toHaveLength(10);
47
+ expect(out).toContain("- (还有 2 项被截断)");
48
+ expect(out).toContain("**计数**: actionable=12");
49
+ });
50
+ it("renders warnings section when warnings present", () => {
51
+ const body = {
52
+ ...emptyBody(),
53
+ actionable: [mkTask("T-1", "subj")],
54
+ counters: { actionable: 1, awareness: 0, blocked: 0, waitingOnUser: 0 },
55
+ warnings: ["broken-deps: task T-300 has cancelled/missing dependencies [T-299]"],
56
+ };
57
+ const out = formatTaskBriefing(body);
58
+ expect(out).toContain("**警告**:");
59
+ expect(out).toContain("- broken-deps: task T-300 has cancelled/missing dependencies [T-299]");
60
+ });
61
+ it("renders awareness bucket independently when actionable is empty", () => {
62
+ const body = {
63
+ ...emptyBody(),
64
+ awareness: [mkTask("T-200", "wait subject")],
65
+ counters: { actionable: 0, awareness: 1, blocked: 0, waitingOnUser: 0 },
66
+ };
67
+ const out = formatTaskBriefing(body);
68
+ expect(out).not.toContain("**可执行");
69
+ expect(out).toContain("**需关注 (1)**");
70
+ expect(out).toContain("- T-200 wait subject");
71
+ expect(out).toContain("**计数**: actionable=0 awareness=1 blocked=0 waitingOnUser=0");
72
+ });
73
+ });
74
+ //# sourceMappingURL=format-task-briefing.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-task-briefing.test.js","sourceRoot":"","sources":["../../src/__tests__/format-task-briefing.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=locking.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locking.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/locking.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { withLock } from "../locking.js";
6
+ describe("withLock", () => {
7
+ it("serializes concurrent operations on same key", async () => {
8
+ const dir = mkdtempSync(join(tmpdir(), "task-lock-"));
9
+ mkdirSync(dir, { recursive: true });
10
+ writeFileSync(join(dir, "target"), "");
11
+ const order = [];
12
+ const op = (label, ms) => withLock(join(dir, "target"), async () => {
13
+ order.push(`${label}-start`);
14
+ await new Promise(r => setTimeout(r, ms));
15
+ order.push(`${label}-end`);
16
+ });
17
+ await Promise.all([op("A", 30), op("B", 10)]);
18
+ // 不假设谁先拿锁(proper-lockfile 不保证 FIFO),只断言互斥:
19
+ // 谁的 *-start 先出现,谁的 *-end 必须紧随其后
20
+ expect(order.length).toBe(4);
21
+ const winner = order[0][0]; // 'A' 或 'B'
22
+ const loser = winner === "A" ? "B" : "A";
23
+ expect(order).toEqual([
24
+ `${winner}-start`, `${winner}-end`,
25
+ `${loser}-start`, `${loser}-end`,
26
+ ]);
27
+ });
28
+ it("propagates errors and releases lock", async () => {
29
+ const dir = mkdtempSync(join(tmpdir(), "task-lock-"));
30
+ writeFileSync(join(dir, "target"), "");
31
+ await expect(withLock(join(dir, "target"), async () => { throw new Error("boom"); })).rejects.toThrow("boom");
32
+ // 后续仍可拿锁
33
+ let ok = false;
34
+ await withLock(join(dir, "target"), async () => { ok = true; });
35
+ expect(ok).toBe(true);
36
+ });
37
+ });
38
+ //# sourceMappingURL=locking.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locking.test.js","sourceRoot":"","sources":["../../src/__tests__/locking.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=meta.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/meta.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mkdtempSync, readFileSync, existsSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { TaskStore } from "../store.js";
6
+ import { metaFile, projectRoot } from "../path.js";
7
+ describe("project meta.json bootstrap", () => {
8
+ const workDir = "/Users/test/proj";
9
+ it("TaskStore constructor writes meta.json on first use", () => {
10
+ const home = mkdtempSync(join(tmpdir(), "task-meta-"));
11
+ expect(existsSync(metaFile(workDir, home))).toBe(false);
12
+ new TaskStore({ workDir, home });
13
+ const path = metaFile(workDir, home);
14
+ expect(existsSync(path)).toBe(true);
15
+ const meta = JSON.parse(readFileSync(path, "utf-8"));
16
+ expect(meta).toMatchObject({ version: 1, workDir });
17
+ expect(typeof meta.createdAt).toBe("string");
18
+ // ISO-8601 sanity
19
+ expect(Number.isFinite(Date.parse(meta.createdAt))).toBe(true);
20
+ });
21
+ it("constructor is idempotent — does not overwrite existing createdAt", () => {
22
+ const home = mkdtempSync(join(tmpdir(), "task-meta-"));
23
+ new TaskStore({ workDir, home });
24
+ const first = JSON.parse(readFileSync(metaFile(workDir, home), "utf-8"));
25
+ // recreate
26
+ new TaskStore({ workDir, home });
27
+ const second = JSON.parse(readFileSync(metaFile(workDir, home), "utf-8"));
28
+ expect(second.createdAt).toBe(first.createdAt);
29
+ });
30
+ it("preserves unrelated fields written by other code", () => {
31
+ const home = mkdtempSync(join(tmpdir(), "task-meta-"));
32
+ // Pre-existing file from a different writer (e.g. demo fixture)
33
+ const root = projectRoot(workDir, home);
34
+ const { mkdirSync } = require("node:fs");
35
+ mkdirSync(root, { recursive: true });
36
+ writeFileSync(metaFile(workDir, home), JSON.stringify({
37
+ version: 1,
38
+ workDir,
39
+ createdAt: "2020-01-01T00:00:00.000Z",
40
+ swarms: { "old-swarm": { teamName: "x" } },
41
+ }));
42
+ new TaskStore({ workDir, home });
43
+ const meta = JSON.parse(readFileSync(metaFile(workDir, home), "utf-8"));
44
+ expect(meta.createdAt).toBe("2020-01-01T00:00:00.000Z");
45
+ expect(meta.swarms).toEqual({ "old-swarm": { teamName: "x" } });
46
+ });
47
+ });
48
+ //# sourceMappingURL=meta.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.test.js","sourceRoot":"","sources":["../../src/__tests__/meta.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEnD,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,MAAM,OAAO,GAAG,kBAAkB,CAAC;IAEnC,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;QACvD,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAExD,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAEjC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7C,kBAAkB;QAClB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;QACvD,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAEzE,WAAW;QACX,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAE1E,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;QACvD,gEAAgE;QAChE,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACxC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QACzC,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrC,aAAa,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;YACpD,OAAO,EAAE,CAAC;YACV,OAAO;YACP,SAAS,EAAE,0BAA0B;YACrC,MAAM,EAAE,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE;SAC3C,CAAC,CAAC,CAAC;QAEJ,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAEjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACxD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=path.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/path.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { encodeCwd, projectRoot, tasksDir, taskFile, indexFile } from "../path.js";
3
+ describe("encodeCwd", () => {
4
+ it("replaces / with - and strips leading dash", () => {
5
+ expect(encodeCwd("/Users/lcf/code/foo")).toBe("-Users-lcf-code-foo");
6
+ });
7
+ it("handles trailing slash", () => {
8
+ expect(encodeCwd("/Users/lcf/code/foo/")).toBe("-Users-lcf-code-foo");
9
+ });
10
+ it("rejects non-absolute paths", () => {
11
+ expect(() => encodeCwd("relative/path")).toThrow(/absolute/);
12
+ });
13
+ it("encodes Windows drive-letter paths", () => {
14
+ expect(encodeCwd("C:\\Users\\lcf\\code\\foo")).toBe("C--Users-lcf-code-foo");
15
+ });
16
+ it("encodes Windows paths with forward slashes", () => {
17
+ expect(encodeCwd("C:/Users/lcf/code/foo")).toBe("C--Users-lcf-code-foo");
18
+ });
19
+ it("encodes UNC paths", () => {
20
+ expect(encodeCwd("\\\\server\\share\\path")).toBe("--server-share-path");
21
+ });
22
+ it("handles trailing backslash on Windows paths", () => {
23
+ expect(encodeCwd("C:\\Users\\lcf\\")).toBe("C--Users-lcf");
24
+ });
25
+ it("still rejects truly relative Windows paths", () => {
26
+ expect(() => encodeCwd("Users\\lcf")).toThrow(/absolute/);
27
+ });
28
+ });
29
+ describe("path helpers", () => {
30
+ const cwd = "/Users/lcf/code/foo";
31
+ it("projectRoot composes ~/.clawnet/projects/<encoded>", () => {
32
+ const root = projectRoot(cwd, "/home/user");
33
+ expect(root).toBe("/home/user/.clawnet/projects/-Users-lcf-code-foo");
34
+ });
35
+ it("tasksDir = projectRoot/tasks", () => {
36
+ expect(tasksDir(cwd, "/home/user")).toMatch(/projects\/-Users-lcf-code-foo\/tasks$/);
37
+ });
38
+ it("taskFile uses <id>.json", () => {
39
+ expect(taskFile(cwd, "abc-123", "/home/user")).toMatch(/tasks\/abc-123\.json$/);
40
+ });
41
+ it("indexFile is tasks/index.json", () => {
42
+ expect(indexFile(cwd, "/home/user")).toMatch(/tasks\/index\.json$/);
43
+ });
44
+ });
45
+ //# sourceMappingURL=path.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.test.js","sourceRoot":"","sources":["../../src/__tests__/path.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEnF,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,MAAM,GAAG,GAAG,qBAAqB,CAAC;IAClC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schema.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/schema.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { TaskStatusSchema, validateTask, createEmptyTask } from "../schema.js";
3
+ describe("TaskStatusSchema", () => {
4
+ it("accepts valid statuses", () => {
5
+ for (const s of ["pending", "in_progress", "completed", "cancelled"]) {
6
+ expect(TaskStatusSchema.has(s)).toBe(true);
7
+ }
8
+ });
9
+ it("rejects invalid", () => {
10
+ expect(TaskStatusSchema.has("blocked")).toBe(false);
11
+ });
12
+ });
13
+ describe("createEmptyTask", () => {
14
+ it("returns full 13-field MVP task", () => {
15
+ const t = createEmptyTask({
16
+ id: "abcdefgh-1111-2222-3333-444455556666",
17
+ teamName: "dev-team",
18
+ subject: "do thing",
19
+ });
20
+ expect(t.id).toBe("abcdefgh-1111-2222-3333-444455556666");
21
+ expect(t.displayId).toBe("abcdefgh");
22
+ expect(t.status).toBe("pending");
23
+ expect(t.teamName).toBe("dev-team");
24
+ expect(t.swarmId).toBeNull();
25
+ expect(t.owner).toBeNull();
26
+ expect(t.subject).toBe("do thing");
27
+ expect(t.description).toBe("");
28
+ expect(t.blockedBy).toEqual([]);
29
+ expect(t.blocks).toEqual([]);
30
+ expect(t.historyEvents).toEqual([]);
31
+ expect(t.comments).toEqual([]);
32
+ expect(typeof t.createdAt).toBe("string");
33
+ expect(typeof t.updatedAt).toBe("string");
34
+ });
35
+ });
36
+ describe("validateTask", () => {
37
+ it("passes valid task", () => {
38
+ const t = createEmptyTask({ id: "x".repeat(36), teamName: "t", subject: "s" });
39
+ expect(() => validateTask(t)).not.toThrow();
40
+ });
41
+ it("rejects missing id", () => {
42
+ expect(() => validateTask({ ...createEmptyTask({ id: "x".repeat(36), teamName: "t", subject: "s" }), id: "" })).toThrow(/id/);
43
+ });
44
+ it("rejects bad status", () => {
45
+ const t = createEmptyTask({ id: "x".repeat(36), teamName: "t", subject: "s" });
46
+ expect(() => validateTask({ ...t, status: "blocked" })).toThrow(/status/);
47
+ });
48
+ });
49
+ //# sourceMappingURL=schema.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.test.js","sourceRoot":"","sources":["../../src/__tests__/schema.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store-create-deps.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-create-deps.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/store-create-deps.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { mkdtempSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { TaskStore } from "../store.js";
6
+ function makeStore() {
7
+ const home = mkdtempSync(join(tmpdir(), "task-create-deps-"));
8
+ return new TaskStore({ workDir: "/Users/test/proj", home });
9
+ }
10
+ describe("TaskStore.create with blockedBy", () => {
11
+ let store;
12
+ beforeEach(() => { store = makeStore(); });
13
+ it("creates task and writes reverse edges in deps", async () => {
14
+ const a = await store.create({ teamName: "t", subject: "a" });
15
+ const b = await store.create({ teamName: "t", subject: "b", blockedBy: [a.id] });
16
+ expect(b.blockedBy).toEqual([a.id]);
17
+ expect(store.get(a.id).blocks).toEqual([b.id]);
18
+ });
19
+ it("rejects creation referencing missing dep", async () => {
20
+ await expect(store.create({ teamName: "t", subject: "x", blockedBy: ["missing"] })).rejects.toThrow(/dependency.*not found/i);
21
+ });
22
+ });
23
+ //# sourceMappingURL=store-create-deps.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-create-deps.test.js","sourceRoot":"","sources":["../../src/__tests__/store-create-deps.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,SAAS,SAAS;IAChB,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAC9D,OAAO,IAAI,SAAS,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,IAAI,KAAgB,CAAC;IACrB,UAAU,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3C,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9D,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACjF,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,MAAM,CACV,KAAK,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CACtE,CAAC,OAAO,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store-link.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-link.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/store-link.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { TaskStore } from "../store.js";
6
+ import { taskFile } from "../path.js";
7
+ function makeStore() {
8
+ const home = mkdtempSync(join(tmpdir(), "task-link-"));
9
+ return { store: new TaskStore({ workDir: "/Users/test/proj", home }), home };
10
+ }
11
+ describe("TaskStore link/unlink", () => {
12
+ let store;
13
+ let home;
14
+ beforeEach(() => { ({ store, home } = makeStore()); });
15
+ it("link writes both edges atomically", async () => {
16
+ const a = await store.create({ teamName: "t", subject: "a" });
17
+ const b = await store.create({ teamName: "t", subject: "b" });
18
+ await store.link(b.id, a.id);
19
+ expect(store.get(b.id).blockedBy).toEqual([a.id]);
20
+ expect(store.get(a.id).blocks).toEqual([b.id]);
21
+ });
22
+ it("link rejects cycle", async () => {
23
+ const a = await store.create({ teamName: "t", subject: "a" });
24
+ const b = await store.create({ teamName: "t", subject: "b" });
25
+ await store.link(b.id, a.id);
26
+ await expect(store.link(a.id, b.id)).rejects.toThrow(/cycle/i);
27
+ });
28
+ it("link is idempotent", async () => {
29
+ const a = await store.create({ teamName: "t", subject: "a" });
30
+ const b = await store.create({ teamName: "t", subject: "b" });
31
+ await store.link(b.id, a.id);
32
+ await store.link(b.id, a.id);
33
+ expect(store.get(b.id).blockedBy).toEqual([a.id]);
34
+ expect(store.get(a.id).blocks).toEqual([b.id]);
35
+ });
36
+ it("unlink removes both edges", async () => {
37
+ const a = await store.create({ teamName: "t", subject: "a" });
38
+ const b = await store.create({ teamName: "t", subject: "b" });
39
+ await store.link(b.id, a.id);
40
+ await store.unlink(b.id, a.id);
41
+ expect(store.get(b.id).blockedBy).toEqual([]);
42
+ expect(store.get(a.id).blocks).toEqual([]);
43
+ });
44
+ it("unlink no-op when not linked", async () => {
45
+ const a = await store.create({ teamName: "t", subject: "a" });
46
+ const b = await store.create({ teamName: "t", subject: "b" });
47
+ await store.unlink(b.id, a.id);
48
+ expect(store.get(b.id).blockedBy).toEqual([]);
49
+ });
50
+ it("link repairs half-edge from previous crash", async () => {
51
+ const a = await store.create({ teamName: "t", subject: "a" });
52
+ const b = await store.create({ teamName: "t", subject: "b" });
53
+ // 模拟"上次只写了 from.blockedBy 没写 to.blocks"的脏状态——直接改 task 文件绕过 guard
54
+ const bPath = taskFile("/Users/test/proj", b.id, home);
55
+ const bTask = JSON.parse(readFileSync(bPath, "utf-8"));
56
+ bTask.blockedBy = [a.id];
57
+ writeFileSync(bPath, JSON.stringify(bTask, null, 2));
58
+ expect(store.get(a.id).blocks).toEqual([]); // 半边
59
+ await store.link(b.id, a.id);
60
+ expect(store.get(b.id).blockedBy).toEqual([a.id]); // 不重复
61
+ expect(store.get(a.id).blocks).toEqual([b.id]); // 修复
62
+ });
63
+ });
64
+ //# sourceMappingURL=store-link.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-link.test.js","sourceRoot":"","sources":["../../src/__tests__/store-link.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store-rebuild.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-rebuild.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/store-rebuild.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { TaskStore } from "../store.js";
6
+ import { tasksDir, indexFile } from "../path.js";
7
+ function makeStore() {
8
+ const home = mkdtempSync(join(tmpdir(), "task-rebuild-"));
9
+ return { store: new TaskStore({ workDir: "/Users/test/proj", home }), home };
10
+ }
11
+ describe("TaskStore.rebuildIndex", () => {
12
+ it("recovers index from task files when index is missing", async () => {
13
+ const { store, home } = makeStore();
14
+ const t = await store.create({ teamName: "t", subject: "a" });
15
+ rmSync(indexFile("/Users/test/proj", home));
16
+ await store.rebuildIndex();
17
+ const entry = store.list().find(e => e.id === t.id);
18
+ expect(entry.subject).toBe("a");
19
+ expect(entry.status).toBe("pending");
20
+ });
21
+ it("recovers index when index is stale (missing entry)", async () => {
22
+ const { store, home } = makeStore();
23
+ const a = await store.create({ teamName: "t", subject: "a" });
24
+ const dir = tasksDir("/Users/test/proj", home);
25
+ writeFileSync(join(dir, "ghost.json"), JSON.stringify({
26
+ ...a,
27
+ id: "ghost",
28
+ displayId: "ghost",
29
+ subject: "ghost-task",
30
+ }, null, 2));
31
+ await store.rebuildIndex();
32
+ const entries = store.list();
33
+ expect(entries.find(e => e.id === "ghost")).toBeTruthy();
34
+ expect(entries.find(e => e.id === a.id)).toBeTruthy();
35
+ });
36
+ it("removes stale entries pointing at deleted task files", async () => {
37
+ const { store, home } = makeStore();
38
+ const a = await store.create({ teamName: "t", subject: "a" });
39
+ const b = await store.create({ teamName: "t", subject: "b" });
40
+ rmSync(join(tasksDir("/Users/test/proj", home), `${b.id}.json`));
41
+ await store.rebuildIndex();
42
+ const entries = store.list();
43
+ expect(entries.find(e => e.id === a.id)).toBeTruthy();
44
+ expect(entries.find(e => e.id === b.id)).toBeUndefined();
45
+ });
46
+ });
47
+ //# sourceMappingURL=store-rebuild.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-rebuild.test.js","sourceRoot":"","sources":["../../src/__tests__/store-rebuild.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { mkdtempSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { TaskStore } from "../store.js";
6
+ function makeStore() {
7
+ const home = mkdtempSync(join(tmpdir(), "task-store-"));
8
+ return new TaskStore({ workDir: "/Users/test/proj", home });
9
+ }
10
+ describe("TaskStore CRUD", () => {
11
+ let store;
12
+ beforeEach(() => { store = makeStore(); });
13
+ it("create persists and is readable", async () => {
14
+ const t = await store.create({ teamName: "dev", subject: "hello" });
15
+ expect(t.id).toMatch(/^[0-9a-f-]{36}$/);
16
+ const fetched = store.get(t.id);
17
+ expect(fetched.subject).toBe("hello");
18
+ });
19
+ it("tryGet returns null for missing", () => {
20
+ expect(store.tryGet("missing-id")).toBeNull();
21
+ });
22
+ it("get throws for missing", () => {
23
+ expect(() => store.get("missing-id")).toThrow(/not found/);
24
+ });
25
+ it("update merges fields and bumps updatedAt", async () => {
26
+ const t = await store.create({ teamName: "dev", subject: "a" });
27
+ const before = t.updatedAt;
28
+ await new Promise(r => setTimeout(r, 5));
29
+ const updated = await store.update(t.id, { subject: "b" });
30
+ expect(updated.subject).toBe("b");
31
+ expect(updated.updatedAt).not.toBe(before);
32
+ });
33
+ it("addComment is idempotent on stable id", async () => {
34
+ const t = await store.create({ teamName: "dev", subject: "a" });
35
+ await store.addComment(t.id, { id: "c1", author: "system", type: "note", body: "x" });
36
+ await store.addComment(t.id, { id: "c1", author: "system", type: "note", body: "x" });
37
+ const fetched = store.get(t.id);
38
+ expect(fetched.comments.length).toBe(1);
39
+ });
40
+ it("list returns all created tasks via index", async () => {
41
+ await store.create({ teamName: "dev", subject: "a" });
42
+ await store.create({ teamName: "dev", subject: "b" });
43
+ const all = store.list();
44
+ expect(all.length).toBe(2);
45
+ });
46
+ it("index entry mirrors task header fields", async () => {
47
+ const t = await store.create({ teamName: "dev", subject: "hello" });
48
+ const entry = store.list().find(e => e.id === t.id);
49
+ expect(entry.subject).toBe("hello");
50
+ expect(entry.status).toBe("pending");
51
+ expect(entry.displayId).toBe(t.displayId);
52
+ });
53
+ });
54
+ describe("TaskStore.update reverse-edge guard", () => {
55
+ it("rejects patch with blockedBy", async () => {
56
+ const home = mkdtempSync(join(tmpdir(), "task-update-guard-"));
57
+ const store = new TaskStore({ workDir: "/Users/test/proj", home });
58
+ const t = await store.create({ teamName: "t", subject: "x" });
59
+ await expect(store.update(t.id, { blockedBy: ["y"] }))
60
+ .rejects.toThrow(/use link\/unlink/i);
61
+ });
62
+ it("rejects patch with blocks", async () => {
63
+ const home = mkdtempSync(join(tmpdir(), "task-update-guard-"));
64
+ const store = new TaskStore({ workDir: "/Users/test/proj", home });
65
+ const t = await store.create({ teamName: "t", subject: "x" });
66
+ await expect(store.update(t.id, { blocks: ["y"] }))
67
+ .rejects.toThrow(/use link\/unlink/i);
68
+ });
69
+ });
70
+ //# sourceMappingURL=store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.js","sourceRoot":"","sources":["../../src/__tests__/store.test.ts"],"names":[],"mappings":"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"}