maskweaver 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +638 -592
- package/README.md +671 -667
- package/dist/cli/doctor.js +5 -21
- package/dist/cli/install.d.ts +0 -8
- package/dist/cli/install.js +0 -39
- package/dist/context/config.d.ts +0 -22
- package/dist/context/config.js +0 -28
- package/dist/context/feature.d.ts +0 -39
- package/dist/context/feature.js +0 -77
- package/dist/context/files.d.ts +0 -13
- package/dist/context/files.js +1 -24
- package/dist/context/index.d.ts +0 -7
- package/dist/context/index.js +0 -12
- package/dist/context/project.d.ts +0 -21
- package/dist/context/project.js +0 -30
- package/dist/context/types.d.ts +0 -48
- package/dist/context/types.js +0 -12
- package/dist/context/utils.d.ts +0 -18
- package/dist/context/utils.js +0 -27
- package/dist/core/engine/promptBuilder.d.ts +0 -17
- package/dist/core/engine/promptBuilder.js +0 -28
- package/dist/core/index.d.ts +0 -6
- package/dist/core/index.js +0 -9
- package/dist/core/loader/MaskLoader.d.ts +0 -23
- package/dist/core/loader/MaskLoader.js +0 -29
- package/dist/core/schema/types.d.ts +0 -47
- package/dist/core/schema/types.js +0 -6
- package/dist/core/schema/validator.d.ts +0 -14
- package/dist/core/schema/validator.js +0 -18
- package/dist/i18n/index.d.ts +0 -18
- package/dist/i18n/index.js +4 -23
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -8
- package/dist/lib.d.ts +0 -5
- package/dist/lib.js +0 -12
- package/dist/memory/chunking.d.ts +0 -22
- package/dist/memory/chunking.js +2 -37
- package/dist/memory/core.d.ts +0 -29
- package/dist/memory/core.js +1 -52
- package/dist/memory/index.d.ts +0 -5
- package/dist/memory/index.js +0 -10
- package/dist/memory/indexer.d.ts +0 -21
- package/dist/memory/indexer.js +0 -44
- package/dist/memory/providers/examples.d.ts +0 -5
- package/dist/memory/providers/examples.js +4 -64
- package/dist/memory/providers/factory.d.ts +0 -44
- package/dist/memory/providers/factory.js +0 -46
- package/dist/memory/providers/index.d.ts +0 -26
- package/dist/memory/providers/index.js +0 -28
- package/dist/memory/providers/ollama.d.ts +0 -6
- package/dist/memory/providers/ollama.js +1 -8
- package/dist/memory/providers/openai.d.ts +0 -6
- package/dist/memory/providers/openai.js +1 -8
- package/dist/memory/providers/openrouter.d.ts +0 -6
- package/dist/memory/providers/openrouter.js +0 -8
- package/dist/memory/providers/text-only.d.ts +0 -13
- package/dist/memory/providers/text-only.js +0 -17
- package/dist/memory/providers/types.d.ts +0 -39
- package/dist/memory/providers/types.js +0 -7
- package/dist/memory/providers/voyage.d.ts +0 -22
- package/dist/memory/providers/voyage.js +1 -24
- package/dist/memory/search/hybrid.d.ts +0 -12
- package/dist/memory/search/hybrid.js +1 -22
- package/dist/memory/store/sqlite.d.ts +0 -72
- package/dist/memory/store/sqlite.js +4 -127
- package/dist/plugin/config/index.d.ts +0 -112
- package/dist/plugin/config/index.js +0 -115
- package/dist/plugin/index.d.ts +0 -13
- package/dist/plugin/index.js +1 -123
- package/dist/plugin/tools/command-registry.d.ts +0 -6
- package/dist/plugin/tools/command-registry.js +0 -14
- package/dist/plugin/tools/context.d.ts +0 -12
- package/dist/plugin/tools/context.js +0 -58
- package/dist/plugin/tools/maskSave.d.ts +0 -3
- package/dist/plugin/tools/maskSave.js +0 -3
- package/dist/plugin/tools/memoryGet.d.ts +0 -3
- package/dist/plugin/tools/memoryGet.js +0 -3
- package/dist/plugin/tools/memoryIndexer.d.ts +0 -3
- package/dist/plugin/tools/memoryIndexer.js +0 -10
- package/dist/plugin/tools/memorySearch.d.ts +0 -31
- package/dist/plugin/tools/memorySearch.js +0 -79
- package/dist/plugin/tools/memoryWrite.d.ts +0 -8
- package/dist/plugin/tools/memoryWrite.js +0 -32
- package/dist/plugin/tools/retrospect.d.ts +0 -3
- package/dist/plugin/tools/retrospect.js +0 -3
- package/dist/plugin/tools/slashcommand.d.ts +0 -11
- package/dist/plugin/tools/slashcommand.js +0 -38
- package/dist/plugin/tools/squad.d.ts +0 -12
- package/dist/plugin/tools/squad.js +11 -83
- package/dist/plugin/tools/weave.d.ts +0 -6
- package/dist/plugin/tools/weave.js +0 -78
- package/dist/plugin/types.d.ts +0 -20
- package/dist/plugin/types.js +0 -7
- package/dist/retrospect/index.d.ts +0 -7
- package/dist/retrospect/index.js +0 -9
- package/dist/retrospect/mask-save.d.ts +0 -12
- package/dist/retrospect/mask-save.js +1 -80
- package/dist/retrospect/retrospect.d.ts +0 -18
- package/dist/retrospect/retrospect.js +0 -63
- package/dist/retrospect/strategies/base.d.ts +0 -15
- package/dist/retrospect/strategies/base.js +0 -7
- package/dist/retrospect/strategies/deep.d.ts +0 -12
- package/dist/retrospect/strategies/deep.js +0 -24
- package/dist/retrospect/strategies/index.d.ts +0 -12
- package/dist/retrospect/strategies/index.js +0 -12
- package/dist/retrospect/strategies/quick.d.ts +0 -12
- package/dist/retrospect/strategies/quick.js +0 -19
- package/dist/retrospect/strategies/standard.d.ts +0 -12
- package/dist/retrospect/strategies/standard.js +0 -15
- package/dist/retrospect/types.d.ts +0 -7
- package/dist/retrospect/types.js +0 -7
- package/dist/shared/config.d.ts +0 -105
- package/dist/shared/config.js +0 -33
- package/dist/shared/errors.d.ts +0 -18
- package/dist/shared/errors.js +0 -19
- package/dist/shared/generate-agents.d.ts +0 -69
- package/dist/shared/generate-agents.js +2 -86
- package/dist/shared/image.d.ts +0 -67
- package/dist/shared/image.js +6 -104
- package/dist/shared/index.d.ts +0 -5
- package/dist/shared/index.js +0 -7
- package/dist/shared/model-registry.d.ts +0 -72
- package/dist/shared/model-registry.js +5 -95
- package/dist/shared/types.d.ts +0 -15
- package/dist/shared/types.js +0 -3
- package/dist/shared-context/dag.d.ts +0 -105
- package/dist/shared-context/dag.js +3 -114
- package/dist/shared-context/index.d.ts +0 -5
- package/dist/shared-context/index.js +0 -15
- package/dist/shared-context/logger.d.ts +0 -37
- package/dist/shared-context/logger.js +0 -41
- package/dist/shared-context/parallel-executor.d.ts +0 -54
- package/dist/shared-context/parallel-executor.js +4 -56
- package/dist/shared-context/session.d.ts +0 -56
- package/dist/shared-context/session.js +0 -47
- package/dist/shared-context/squad.d.ts +0 -68
- package/dist/shared-context/squad.js +0 -63
- package/dist/shared-context/storage.d.ts +0 -132
- package/dist/shared-context/storage.js +0 -116
- package/dist/shared-context/task.d.ts +0 -120
- package/dist/shared-context/task.js +0 -152
- package/dist/shared-context/test/dag.test.js +9 -14
- package/dist/shared-context/test/logger.test.d.ts +0 -8
- package/dist/shared-context/test/logger.test.js +0 -52
- package/dist/shared-context/test/session.test.d.ts +0 -7
- package/dist/shared-context/test/session.test.js +0 -63
- package/dist/shared-context/test/squad.test.d.ts +0 -10
- package/dist/shared-context/test/squad.test.js +2 -68
- package/dist/shared-context/test/storage.test.d.ts +0 -8
- package/dist/shared-context/test/storage.test.js +0 -68
- package/dist/shared-context/test/task.test.d.ts +0 -7
- package/dist/shared-context/test/task.test.js +0 -54
- package/dist/shared-context/test/watchdog.test.d.ts +0 -7
- package/dist/shared-context/test/watchdog.test.js +3 -58
- package/dist/shared-context/types.d.ts +0 -215
- package/dist/shared-context/types.js +0 -125
- package/dist/shared-context/watchdog.d.ts +0 -127
- package/dist/shared-context/watchdog.js +0 -148
- package/dist/shared-context/worktree.d.ts +0 -68
- package/dist/shared-context/worktree.js +2 -34
- package/dist/verify/budget.d.ts +0 -29
- package/dist/verify/budget.js +0 -34
- package/dist/verify/critical-files.d.ts +0 -17
- package/dist/verify/critical-files.js +0 -37
- package/dist/verify/escalation.d.ts +0 -20
- package/dist/verify/escalation.js +0 -22
- package/dist/verify/index.d.ts +0 -5
- package/dist/verify/index.js +0 -11
- package/dist/verify/prompts.d.ts +0 -20
- package/dist/verify/prompts.js +0 -20
- package/dist/verify/types.d.ts +0 -26
- package/dist/verify/types.js +1 -12
- package/dist/verify/verifier.d.ts +0 -29
- package/dist/verify/verifier.js +0 -54
- package/dist/version.d.ts +1 -16
- package/dist/version.js +1 -16
- package/dist/weave/bridge.d.ts +0 -35
- package/dist/weave/bridge.js +0 -51
- package/dist/weave/environment/detector.d.ts +0 -6
- package/dist/weave/environment/detector.js +4 -45
- package/dist/weave/environment/index.d.ts +0 -19
- package/dist/weave/environment/index.js +1 -39
- package/dist/weave/environment/issues.d.ts +0 -35
- package/dist/weave/environment/issues.js +0 -59
- package/dist/weave/git.d.ts +0 -8
- package/dist/weave/git.js +0 -8
- package/dist/weave/index.d.ts +0 -13
- package/dist/weave/index.js +2 -28
- package/dist/weave/knowledge/global.d.ts +0 -39
- package/dist/weave/knowledge/global.js +2 -78
- package/dist/weave/loop.js +0 -3
- package/dist/weave/orchestrator.d.ts +0 -69
- package/dist/weave/orchestrator.js +1 -101
- package/dist/weave/phase-manager.d.ts +0 -64
- package/dist/weave/phase-manager.js +0 -89
- package/dist/weave/security/secret-scan.d.ts +0 -14
- package/dist/weave/security/secret-scan.js +0 -19
- package/dist/weave/stages/build.js +0 -15
- package/dist/weave/stages/execute.d.ts +0 -42
- package/dist/weave/stages/execute.js +4 -86
- package/dist/weave/stages/handoff.d.ts +0 -7
- package/dist/weave/stages/handoff.js +0 -43
- package/dist/weave/stages/index.d.ts +0 -3
- package/dist/weave/stages/index.js +0 -3
- package/dist/weave/stages/intake.d.ts +0 -8
- package/dist/weave/stages/intake.js +5 -65
- package/dist/weave/stages/map.d.ts +0 -1
- package/dist/weave/stages/openspec.d.ts +0 -1
- package/dist/weave/stages/plan.d.ts +0 -11
- package/dist/weave/stages/plan.js +1 -53
- package/dist/weave/stages/refine.d.ts +0 -7
- package/dist/weave/stages/refine.js +0 -7
- package/dist/weave/stages/research.d.ts +0 -6
- package/dist/weave/stages/research.js +0 -6
- package/dist/weave/stages/spec.d.ts +0 -12
- package/dist/weave/stages/spec.js +0 -17
- package/dist/weave/types.d.ts +0 -20
- package/dist/weave/types.js +0 -5
- package/dist/weave/verification/commands.d.ts +0 -12
- package/dist/weave/verification/commands.js +0 -19
- package/dist/weave/verification/index.d.ts +0 -6
- package/dist/weave/verification/index.js +1 -19
- package/dist/weave/verification/playwright.d.ts +0 -47
- package/dist/weave/verification/playwright.js +1 -90
- package/dist/weave/worktree.d.ts +0 -16
- package/dist/weave/worktree.js +0 -23
- package/dist/weave/yaml-repair.d.ts +0 -39
- package/dist/weave/yaml-repair.js +13 -116
- package/package.json +1 -1
|
@@ -1,176 +1,114 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storage Adapter Unit Tests
|
|
3
|
-
*
|
|
4
|
-
* "First make it work, then make it right, then make it fast."
|
|
5
|
-
* - Kent Beck
|
|
6
|
-
*
|
|
7
|
-
* @author Kent Beck's Dummy Human
|
|
8
|
-
*/
|
|
9
1
|
import { test, expect, describe, beforeEach, afterEach } from "vitest";
|
|
10
2
|
import { mkdtemp, rm, readFile, writeFile } from "fs/promises";
|
|
11
3
|
import { tmpdir } from "os";
|
|
12
4
|
import { join } from "path";
|
|
13
5
|
import { validatePath, FileStorageAdapter } from "../storage.js";
|
|
14
|
-
// ============================================================
|
|
15
|
-
// validatePath Tests - Path Traversal Prevention
|
|
16
|
-
// ============================================================
|
|
17
6
|
describe("validatePath", () => {
|
|
18
7
|
const baseDir = "/home/user/data";
|
|
19
8
|
test("should allow valid paths within base directory", () => {
|
|
20
|
-
// Arrange & Act & Assert
|
|
21
9
|
expect(validatePath("/home/user/data/file.json", baseDir)).toBe(true);
|
|
22
10
|
expect(validatePath("/home/user/data/sub/file.json", baseDir)).toBe(true);
|
|
23
11
|
});
|
|
24
12
|
test("should reject paths containing '..' (Path Traversal attack)", () => {
|
|
25
|
-
// Arrange
|
|
26
13
|
const maliciousPath = "/home/user/data/../secret/passwords.json";
|
|
27
|
-
// Act & Assert
|
|
28
14
|
expect(validatePath(maliciousPath, baseDir)).toBe(false);
|
|
29
15
|
});
|
|
30
16
|
test("should reject paths outside base directory", () => {
|
|
31
|
-
// Arrange
|
|
32
17
|
const outsidePath = "/home/user/other/file.json";
|
|
33
|
-
// Act & Assert
|
|
34
18
|
expect(validatePath(outsidePath, baseDir)).toBe(false);
|
|
35
19
|
});
|
|
36
20
|
test("should reject hidden path traversal attempts", () => {
|
|
37
|
-
// Arrange - Various traversal attempts
|
|
38
21
|
const traversalAttempts = [
|
|
39
22
|
"/home/user/data/../../etc/passwd",
|
|
40
23
|
"/home/user/data/sub/../../../root",
|
|
41
24
|
"/home/user/data/..\\windows\\system32",
|
|
42
25
|
];
|
|
43
|
-
// Act & Assert
|
|
44
26
|
for (const path of traversalAttempts) {
|
|
45
27
|
expect(validatePath(path, baseDir)).toBe(false);
|
|
46
28
|
}
|
|
47
29
|
});
|
|
48
30
|
});
|
|
49
|
-
// ============================================================
|
|
50
|
-
// FileStorageAdapter Tests
|
|
51
|
-
// ============================================================
|
|
52
31
|
describe("FileStorageAdapter", () => {
|
|
53
32
|
let tempDir;
|
|
54
33
|
let storage;
|
|
55
34
|
beforeEach(async () => {
|
|
56
|
-
// Create isolated temp directory for each test
|
|
57
35
|
tempDir = await mkdtemp(join(tmpdir(), "storage-test-"));
|
|
58
36
|
storage = new FileStorageAdapter(tempDir);
|
|
59
37
|
});
|
|
60
38
|
afterEach(async () => {
|
|
61
|
-
// Clean up temp directory
|
|
62
39
|
await rm(tempDir, { recursive: true, force: true });
|
|
63
40
|
});
|
|
64
|
-
// ----------------------------------------------------------
|
|
65
|
-
// write & read Tests
|
|
66
|
-
// ----------------------------------------------------------
|
|
67
41
|
describe("write and read", () => {
|
|
68
42
|
test("should write and read JSON data successfully", async () => {
|
|
69
|
-
// Arrange
|
|
70
43
|
const testData = { name: "Kent Beck", role: "TDD Master" };
|
|
71
44
|
const path = "test.json";
|
|
72
|
-
// Act
|
|
73
45
|
await storage.write(path, testData);
|
|
74
46
|
const result = await storage.read(path);
|
|
75
|
-
// Assert
|
|
76
47
|
expect(result).toEqual(testData);
|
|
77
48
|
});
|
|
78
49
|
test("should return null when reading non-existent file", async () => {
|
|
79
|
-
// Arrange
|
|
80
50
|
const path = "non-existent.json";
|
|
81
|
-
// Act
|
|
82
51
|
const result = await storage.read(path);
|
|
83
|
-
// Assert
|
|
84
52
|
expect(result).toBeNull();
|
|
85
53
|
});
|
|
86
54
|
test("should handle nested paths correctly", async () => {
|
|
87
|
-
// Arrange
|
|
88
55
|
const testData = { nested: true };
|
|
89
56
|
const path = "deep/nested/path/data.json";
|
|
90
|
-
// Act
|
|
91
57
|
await storage.write(path, testData);
|
|
92
58
|
const result = await storage.read(path);
|
|
93
|
-
// Assert
|
|
94
59
|
expect(result).toEqual(testData);
|
|
95
60
|
});
|
|
96
61
|
test("should throw error on Path Traversal in read", async () => {
|
|
97
|
-
// Arrange
|
|
98
62
|
const maliciousPath = "../../../etc/passwd";
|
|
99
|
-
// Act & Assert
|
|
100
63
|
await expect(storage.read(maliciousPath)).rejects.toThrow("Path traversal detected");
|
|
101
64
|
});
|
|
102
65
|
test("should throw error on Path Traversal in write", async () => {
|
|
103
|
-
// Arrange
|
|
104
66
|
const maliciousPath = "../../../tmp/hack.json";
|
|
105
67
|
const data = { hacked: true };
|
|
106
|
-
// Act & Assert
|
|
107
68
|
await expect(storage.write(maliciousPath, data)).rejects.toThrow("Path traversal detected");
|
|
108
69
|
});
|
|
109
70
|
});
|
|
110
|
-
// ----------------------------------------------------------
|
|
111
|
-
// append Tests
|
|
112
|
-
// ----------------------------------------------------------
|
|
113
71
|
describe("append", () => {
|
|
114
72
|
test("should append line to file", async () => {
|
|
115
|
-
// Arrange
|
|
116
73
|
const path = "log.txt";
|
|
117
74
|
const line1 = "First line";
|
|
118
75
|
const line2 = "Second line";
|
|
119
|
-
// Act
|
|
120
76
|
await storage.append(path, line1);
|
|
121
77
|
await storage.append(path, line2);
|
|
122
|
-
// Assert - Read raw file content
|
|
123
78
|
const fullPath = storage.getFullPath(path);
|
|
124
79
|
const content = await readFile(fullPath, "utf-8");
|
|
125
80
|
expect(content).toBe("First line\nSecond line\n");
|
|
126
81
|
});
|
|
127
82
|
test("should throw error on Path Traversal in append", async () => {
|
|
128
|
-
// Arrange
|
|
129
83
|
const maliciousPath = "../../../tmp/inject.txt";
|
|
130
|
-
// Act & Assert
|
|
131
84
|
await expect(storage.append(maliciousPath, "hacked")).rejects.toThrow("Path traversal detected");
|
|
132
85
|
});
|
|
133
86
|
});
|
|
134
|
-
// ----------------------------------------------------------
|
|
135
|
-
// exists Tests
|
|
136
|
-
// ----------------------------------------------------------
|
|
137
87
|
describe("exists", () => {
|
|
138
88
|
test("should return true for existing file", async () => {
|
|
139
|
-
// Arrange
|
|
140
89
|
const path = "exists.json";
|
|
141
90
|
await storage.write(path, { exists: true });
|
|
142
|
-
// Act & Assert
|
|
143
91
|
expect(storage.exists(path)).toBe(true);
|
|
144
92
|
});
|
|
145
93
|
test("should return false for non-existing file", () => {
|
|
146
|
-
// Arrange
|
|
147
94
|
const path = "ghost.json";
|
|
148
|
-
// Act & Assert
|
|
149
95
|
expect(storage.exists(path)).toBe(false);
|
|
150
96
|
});
|
|
151
97
|
});
|
|
152
|
-
// ----------------------------------------------------------
|
|
153
|
-
// ensureDir Tests
|
|
154
|
-
// ----------------------------------------------------------
|
|
155
98
|
describe("ensureDir", () => {
|
|
156
99
|
test("should create nested directories", async () => {
|
|
157
|
-
// Arrange - use absolute path directly (ensureDir takes absolute paths)
|
|
158
100
|
const nestedPath = join(tempDir, "level1", "level2", "level3");
|
|
159
|
-
// Act - ensureDir expects absolute paths
|
|
160
101
|
const { mkdir } = await import("fs/promises");
|
|
161
102
|
await mkdir(nestedPath, { recursive: true });
|
|
162
|
-
// Assert - Write a file to verify directory exists
|
|
163
103
|
const testFile = join(nestedPath, "test.txt");
|
|
164
104
|
await writeFile(testFile, "test");
|
|
165
105
|
const content = await readFile(testFile, "utf-8");
|
|
166
106
|
expect(content).toBe("test");
|
|
167
107
|
});
|
|
168
108
|
test("should not throw when directory already exists", async () => {
|
|
169
|
-
// Arrange - use absolute path directly
|
|
170
109
|
const existingPath = join(tempDir, "existing");
|
|
171
110
|
const { mkdir } = await import("fs/promises");
|
|
172
111
|
await mkdir(existingPath, { recursive: true });
|
|
173
|
-
// Act & Assert - mkdir with recursive: true is idempotent
|
|
174
112
|
let error = null;
|
|
175
113
|
try {
|
|
176
114
|
await mkdir(existingPath, { recursive: true });
|
|
@@ -181,16 +119,10 @@ describe("FileStorageAdapter", () => {
|
|
|
181
119
|
expect(error).toBeNull();
|
|
182
120
|
});
|
|
183
121
|
});
|
|
184
|
-
// ----------------------------------------------------------
|
|
185
|
-
// getFullPath Tests
|
|
186
|
-
// ----------------------------------------------------------
|
|
187
122
|
describe("getFullPath", () => {
|
|
188
123
|
test("should return correct full path", () => {
|
|
189
|
-
// Arrange
|
|
190
124
|
const relativePath = "sub/data.json";
|
|
191
|
-
// Act
|
|
192
125
|
const fullPath = storage.getFullPath(relativePath);
|
|
193
|
-
// Assert
|
|
194
126
|
expect(fullPath).toBe(join(tempDir, "sub/data.json"));
|
|
195
127
|
});
|
|
196
128
|
});
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task Management Tests
|
|
3
|
-
*
|
|
4
|
-
* "테스트가 없으면 버그가 아니다" - Kent Beck
|
|
5
|
-
*
|
|
6
|
-
* @author Kent Beck's TDD Approach
|
|
7
|
-
*/
|
|
8
1
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
9
2
|
import { mkdtemp, rm } from "fs/promises";
|
|
10
3
|
import { tmpdir } from "os";
|
|
@@ -15,9 +8,6 @@ import { createSession } from "../session.js";
|
|
|
15
8
|
import { createSquad, getSquad } from "../squad.js";
|
|
16
9
|
import { assignTask, getTask, updateTask, completeTask } from "../task.js";
|
|
17
10
|
import { LIMITS } from "../types.js";
|
|
18
|
-
// ============================================================================
|
|
19
|
-
// Test Fixtures
|
|
20
|
-
// ============================================================================
|
|
21
11
|
let tempDir;
|
|
22
12
|
let storage;
|
|
23
13
|
let session;
|
|
@@ -40,9 +30,6 @@ afterEach(async () => {
|
|
|
40
30
|
await rm(tempDir, { recursive: true, force: true });
|
|
41
31
|
}
|
|
42
32
|
});
|
|
43
|
-
// ============================================================================
|
|
44
|
-
// assignTask Tests
|
|
45
|
-
// ============================================================================
|
|
46
33
|
describe("assignTask", () => {
|
|
47
34
|
test("should successfully assign a task to a squad", async () => {
|
|
48
35
|
const task = await assignTask(session, squadId, {
|
|
@@ -71,14 +58,12 @@ describe("assignTask", () => {
|
|
|
71
58
|
})).rejects.toThrow("Squad not found: squad-nonexistent");
|
|
72
59
|
});
|
|
73
60
|
test("should throw ValidationError when max tasks per squad exceeded", async () => {
|
|
74
|
-
// Arrange: 최대 개수만큼 Task 생성
|
|
75
61
|
for (let i = 0; i < LIMITS.maxTasksPerSquad; i++) {
|
|
76
62
|
await assignTask(session, squadId, {
|
|
77
63
|
assignee: `worker-${i}`,
|
|
78
64
|
description: `Task ${i}`,
|
|
79
65
|
});
|
|
80
66
|
}
|
|
81
|
-
// Act & Assert: 한도 초과 시 에러
|
|
82
67
|
await expect(assignTask(session, squadId, {
|
|
83
68
|
assignee: "worker-overflow",
|
|
84
69
|
description: "One too many",
|
|
@@ -141,9 +126,6 @@ describe("assignTask", () => {
|
|
|
141
126
|
expect(squad?.state.tasks.map((t) => t.taskId)).toContain(task3.taskId);
|
|
142
127
|
});
|
|
143
128
|
});
|
|
144
|
-
// ============================================================================
|
|
145
|
-
// updateTask Tests - State Transition through Aggregate Root
|
|
146
|
-
// ============================================================================
|
|
147
129
|
describe("updateTask", () => {
|
|
148
130
|
test("should successfully update a task status", async () => {
|
|
149
131
|
const task = await assignTask(session, squadId, {
|
|
@@ -232,20 +214,14 @@ describe("updateTask", () => {
|
|
|
232
214
|
expect(completed.result).toEqual({ done: true });
|
|
233
215
|
});
|
|
234
216
|
});
|
|
235
|
-
// ============================================================================
|
|
236
|
-
// getTask Tests - Domain Entity Lookup
|
|
237
|
-
// ============================================================================
|
|
238
217
|
describe("getTask", () => {
|
|
239
218
|
test("should return task when it exists", async () => {
|
|
240
|
-
// Arrange: assign a task
|
|
241
219
|
const createdTask = await assignTask(session, squadId, {
|
|
242
220
|
assignee: "worker-1",
|
|
243
221
|
description: "Find me later",
|
|
244
222
|
priority: "high",
|
|
245
223
|
});
|
|
246
|
-
// Act: retrieve the task
|
|
247
224
|
const foundTask = await getTask(session, squadId, createdTask.taskId);
|
|
248
|
-
// Assert: task should match
|
|
249
225
|
expect(foundTask).not.toBeNull();
|
|
250
226
|
expect(foundTask?.taskId).toBe(createdTask.taskId);
|
|
251
227
|
expect(foundTask?.description).toBe("Find me later");
|
|
@@ -253,17 +229,13 @@ describe("getTask", () => {
|
|
|
253
229
|
expect(foundTask?.priority).toBe("high");
|
|
254
230
|
});
|
|
255
231
|
test("should return null when task does not exist", async () => {
|
|
256
|
-
// Act: try to find non-existent task
|
|
257
232
|
const result = await getTask(session, squadId, "task-nonexistent");
|
|
258
|
-
// Assert: should be null, not throw
|
|
259
233
|
expect(result).toBeNull();
|
|
260
234
|
});
|
|
261
235
|
test("should throw StorageError when squad does not exist", async () => {
|
|
262
|
-
// Act & Assert: should throw for missing squad
|
|
263
236
|
await expect(getTask(session, "squad-nonexistent", "task-12345678")).rejects.toThrow("Squad not found: squad-nonexistent");
|
|
264
237
|
});
|
|
265
238
|
test("should find correct task among multiple tasks", async () => {
|
|
266
|
-
// Arrange: create multiple tasks
|
|
267
239
|
const task1 = await assignTask(session, squadId, {
|
|
268
240
|
assignee: "worker-1",
|
|
269
241
|
description: "First task",
|
|
@@ -276,24 +248,19 @@ describe("getTask", () => {
|
|
|
276
248
|
assignee: "worker-3",
|
|
277
249
|
description: "Third task",
|
|
278
250
|
});
|
|
279
|
-
// Act: find the middle task
|
|
280
251
|
const foundTask = await getTask(session, squadId, task2.taskId);
|
|
281
|
-
// Assert: should find correct task
|
|
282
252
|
expect(foundTask).not.toBeNull();
|
|
283
253
|
expect(foundTask?.taskId).toBe(task2.taskId);
|
|
284
254
|
expect(foundTask?.description).toBe("Second task");
|
|
285
255
|
});
|
|
286
256
|
test("should return task with all fields intact", async () => {
|
|
287
|
-
// Arrange: create task with all optional fields
|
|
288
257
|
const createdTask = await assignTask(session, squadId, {
|
|
289
258
|
assignee: "worker-alpha",
|
|
290
259
|
description: "Complete task with all fields",
|
|
291
260
|
priority: "critical",
|
|
292
261
|
dependencies: ["dep-1", "dep-2"],
|
|
293
262
|
});
|
|
294
|
-
// Act: retrieve the task
|
|
295
263
|
const foundTask = await getTask(session, squadId, createdTask.taskId);
|
|
296
|
-
// Assert: all fields should be preserved
|
|
297
264
|
expect(foundTask).not.toBeNull();
|
|
298
265
|
expect(foundTask?.status).toBe("pending");
|
|
299
266
|
expect(foundTask?.priority).toBe("critical");
|
|
@@ -301,9 +268,6 @@ describe("getTask", () => {
|
|
|
301
268
|
expect(foundTask?.createdAt).toBe(createdTask.createdAt);
|
|
302
269
|
});
|
|
303
270
|
});
|
|
304
|
-
// ============================================================================
|
|
305
|
-
// completeTask Tests - Convenience Wrapper for Task Completion
|
|
306
|
-
// ============================================================================
|
|
307
271
|
describe("completeTask", () => {
|
|
308
272
|
test("should mark task as completed on success", async () => {
|
|
309
273
|
const task = await assignTask(session, squadId, {
|
|
@@ -354,13 +318,8 @@ describe("completeTask", () => {
|
|
|
354
318
|
expect(found?.result).toEqual(result);
|
|
355
319
|
});
|
|
356
320
|
});
|
|
357
|
-
// ============================================================================
|
|
358
|
-
// Task Lifecycle Integration Tests
|
|
359
|
-
// ============================================================================
|
|
360
321
|
describe("Task Lifecycle Integration", () => {
|
|
361
322
|
test("full task lifecycle: assign → start → complete", async () => {
|
|
362
|
-
// 1. 세션과 squad는 beforeEach에서 이미 생성됨
|
|
363
|
-
// 2. assignTask()로 task 할당
|
|
364
323
|
const task = await assignTask(session, squadId, {
|
|
365
324
|
assignee: "worker-alpha",
|
|
366
325
|
description: "Integration test task",
|
|
@@ -369,7 +328,6 @@ describe("Task Lifecycle Integration", () => {
|
|
|
369
328
|
expect(task.status).toBe("pending");
|
|
370
329
|
expect(task.startedAt).toBeUndefined();
|
|
371
330
|
expect(task.completedAt).toBeUndefined();
|
|
372
|
-
// 3. updateTask()로 status: "active", startedAt 설정
|
|
373
331
|
const startedAt = new Date().toISOString();
|
|
374
332
|
const activeTask = await updateTask(session, squadId, task.taskId, {
|
|
375
333
|
status: "active",
|
|
@@ -377,7 +335,6 @@ describe("Task Lifecycle Integration", () => {
|
|
|
377
335
|
});
|
|
378
336
|
expect(activeTask.status).toBe("active");
|
|
379
337
|
expect(activeTask.startedAt).toBe(startedAt);
|
|
380
|
-
// 4. completeTask()로 성공 결과와 함께 완료
|
|
381
338
|
const result = {
|
|
382
339
|
success: true,
|
|
383
340
|
output: { filesCreated: ["src/auth.ts", "src/auth.test.ts"] },
|
|
@@ -387,7 +344,6 @@ describe("Task Lifecycle Integration", () => {
|
|
|
387
344
|
expect(completedTask.status).toBe("completed");
|
|
388
345
|
expect(completedTask.completedAt).toBeDefined();
|
|
389
346
|
expect(completedTask.result).toEqual(result);
|
|
390
|
-
// 5. getTask()로 최종 상태 확인
|
|
391
347
|
const finalTask = await getTask(session, squadId, task.taskId);
|
|
392
348
|
expect(finalTask).not.toBeNull();
|
|
393
349
|
expect(finalTask?.status).toBe("completed");
|
|
@@ -398,20 +354,17 @@ describe("Task Lifecycle Integration", () => {
|
|
|
398
354
|
expect(finalTask?.priority).toBe("high");
|
|
399
355
|
});
|
|
400
356
|
test("failed task lifecycle: assign → start → fail", async () => {
|
|
401
|
-
// 1. assignTask()로 task 할당
|
|
402
357
|
const task = await assignTask(session, squadId, {
|
|
403
358
|
assignee: "worker-beta",
|
|
404
359
|
description: "This task will fail",
|
|
405
360
|
priority: "critical",
|
|
406
361
|
});
|
|
407
362
|
expect(task.status).toBe("pending");
|
|
408
|
-
// 2. updateTask()로 시작
|
|
409
363
|
const startedAt = new Date().toISOString();
|
|
410
364
|
await updateTask(session, squadId, task.taskId, {
|
|
411
365
|
status: "active",
|
|
412
366
|
startedAt,
|
|
413
367
|
});
|
|
414
|
-
// 3. completeTask()로 실패 결과와 함께 완료
|
|
415
368
|
const result = {
|
|
416
369
|
success: false,
|
|
417
370
|
error: {
|
|
@@ -424,13 +377,11 @@ describe("Task Lifecycle Integration", () => {
|
|
|
424
377
|
expect(failedTask.status).toBe("failed");
|
|
425
378
|
expect(failedTask.completedAt).toBeDefined();
|
|
426
379
|
expect(failedTask.result).toEqual(result);
|
|
427
|
-
// 4. getTask()로 최종 상태 확인
|
|
428
380
|
const finalTask = await getTask(session, squadId, task.taskId);
|
|
429
381
|
expect(finalTask?.status).toBe("failed");
|
|
430
382
|
expect(finalTask?.startedAt).toBe(startedAt);
|
|
431
383
|
});
|
|
432
384
|
test("multiple tasks in squad lifecycle", async () => {
|
|
433
|
-
// 여러 태스크가 독립적으로 실행되는 시나리오
|
|
434
385
|
const task1 = await assignTask(session, squadId, {
|
|
435
386
|
assignee: "worker-1",
|
|
436
387
|
description: "Task 1",
|
|
@@ -439,27 +390,22 @@ describe("Task Lifecycle Integration", () => {
|
|
|
439
390
|
assignee: "worker-2",
|
|
440
391
|
description: "Task 2",
|
|
441
392
|
});
|
|
442
|
-
// task1 시작
|
|
443
393
|
await updateTask(session, squadId, task1.taskId, {
|
|
444
394
|
status: "active",
|
|
445
395
|
startedAt: new Date().toISOString(),
|
|
446
396
|
});
|
|
447
|
-
// task2 시작
|
|
448
397
|
await updateTask(session, squadId, task2.taskId, {
|
|
449
398
|
status: "active",
|
|
450
399
|
startedAt: new Date().toISOString(),
|
|
451
400
|
});
|
|
452
|
-
// task1 완료
|
|
453
401
|
await completeTask(session, squadId, task1.taskId, {
|
|
454
402
|
success: true,
|
|
455
403
|
output: "Task 1 done",
|
|
456
404
|
});
|
|
457
|
-
// task2 실패
|
|
458
405
|
await completeTask(session, squadId, task2.taskId, {
|
|
459
406
|
success: false,
|
|
460
407
|
error: { code: "ERROR", message: "Task 2 failed" },
|
|
461
408
|
});
|
|
462
|
-
// 최종 상태 확인
|
|
463
409
|
const squad = await getSquad(session, squadId);
|
|
464
410
|
expect(squad?.state.tasks).toHaveLength(2);
|
|
465
411
|
const t1 = squad?.state.tasks.find((t) => t.taskId === task1.taskId);
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Watchdog Tests - Squad Timeout Monitoring
|
|
3
|
-
*
|
|
4
|
-
* "If you can't measure it, you can't improve it." - Brendan Gregg
|
|
5
|
-
*
|
|
6
|
-
* @author Kent Beck's TDD Approach
|
|
7
|
-
*/
|
|
8
1
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
9
2
|
import { mkdtemp, rm } from "fs/promises";
|
|
10
3
|
import { tmpdir } from "os";
|
|
@@ -16,9 +9,6 @@ import { createSquad, getSquad, updateSquadState } from "../squad.js";
|
|
|
16
9
|
import { assignTask, updateTask } from "../task.js";
|
|
17
10
|
import { checkSquadTimeout, checkTaskTimeout, markSquadExpired, runWatchdog } from "../watchdog.js";
|
|
18
11
|
import { LIMITS } from "../types.js";
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// Test Fixtures
|
|
21
|
-
// ============================================================================
|
|
22
12
|
let tempDir;
|
|
23
13
|
let storage;
|
|
24
14
|
let session;
|
|
@@ -41,9 +31,6 @@ afterEach(async () => {
|
|
|
41
31
|
await rm(tempDir, { recursive: true, force: true });
|
|
42
32
|
}
|
|
43
33
|
});
|
|
44
|
-
// ============================================================================
|
|
45
|
-
// Helper Functions
|
|
46
|
-
// ============================================================================
|
|
47
34
|
function createMockSquadState(updatedAt) {
|
|
48
35
|
return {
|
|
49
36
|
squadId: "squad-mock",
|
|
@@ -65,9 +52,6 @@ function createMockTaskState(options) {
|
|
|
65
52
|
startedAt: options.startedAt,
|
|
66
53
|
};
|
|
67
54
|
}
|
|
68
|
-
// ============================================================================
|
|
69
|
-
// checkSquadTimeout Tests
|
|
70
|
-
// ============================================================================
|
|
71
55
|
describe("checkSquadTimeout", () => {
|
|
72
56
|
test("should return isExpired: false before timeout", () => {
|
|
73
57
|
const now = new Date();
|
|
@@ -101,7 +85,7 @@ describe("checkSquadTimeout", () => {
|
|
|
101
85
|
const now = new Date();
|
|
102
86
|
const twoMinutesAgo = new Date(now.getTime() - 2 * 60 * 1000);
|
|
103
87
|
const squadState = createMockSquadState(twoMinutesAgo.toISOString());
|
|
104
|
-
const customTimeoutMs = 60 * 1000;
|
|
88
|
+
const customTimeoutMs = 60 * 1000;
|
|
105
89
|
const status = checkSquadTimeout(squadState, customTimeoutMs);
|
|
106
90
|
expect(status.isExpired).toBe(true);
|
|
107
91
|
expect(status.elapsedMs).toBeGreaterThanOrEqual(2 * 60 * 1000);
|
|
@@ -110,7 +94,7 @@ describe("checkSquadTimeout", () => {
|
|
|
110
94
|
const now = new Date();
|
|
111
95
|
const tenMinutesAgo = new Date(now.getTime() - 10 * 60 * 1000);
|
|
112
96
|
const squadState = createMockSquadState(tenMinutesAgo.toISOString());
|
|
113
|
-
const customTimeoutMs = 30 * 60 * 1000;
|
|
97
|
+
const customTimeoutMs = 30 * 60 * 1000;
|
|
114
98
|
const status = checkSquadTimeout(squadState, customTimeoutMs);
|
|
115
99
|
expect(status.isExpired).toBe(false);
|
|
116
100
|
expect(status.remainingMs).toBeGreaterThan(0);
|
|
@@ -131,9 +115,6 @@ describe("checkSquadTimeout", () => {
|
|
|
131
115
|
expect(status.lastActivityAt).toBe(timestamp);
|
|
132
116
|
});
|
|
133
117
|
});
|
|
134
|
-
// ============================================================================
|
|
135
|
-
// markSquadExpired Tests
|
|
136
|
-
// ============================================================================
|
|
137
118
|
describe("markSquadExpired", () => {
|
|
138
119
|
test("should change status to 'failed'", async () => {
|
|
139
120
|
const squad = await getSquad(session, squadId);
|
|
@@ -178,29 +159,20 @@ describe("markSquadExpired", () => {
|
|
|
178
159
|
expect(expiredState.sharedContext).toEqual({ key: "value" });
|
|
179
160
|
});
|
|
180
161
|
});
|
|
181
|
-
// ============================================================================
|
|
182
|
-
// checkTaskTimeout Tests - Fine-grained task observability
|
|
183
|
-
// ============================================================================
|
|
184
162
|
describe("checkTaskTimeout", () => {
|
|
185
163
|
test("should return null for task that hasn't started", () => {
|
|
186
|
-
// Arrange: task without startedAt (pending task)
|
|
187
164
|
const task = createMockTaskState({ status: "pending" });
|
|
188
|
-
// Act
|
|
189
165
|
const status = checkTaskTimeout(task);
|
|
190
|
-
// Assert: null means no timeout check needed
|
|
191
166
|
expect(status).toBeNull();
|
|
192
167
|
});
|
|
193
168
|
test("should return isExpired: false for recently started task", () => {
|
|
194
|
-
// Arrange: task started 1 minute ago
|
|
195
169
|
const now = new Date();
|
|
196
170
|
const oneMinuteAgo = new Date(now.getTime() - 60 * 1000);
|
|
197
171
|
const task = createMockTaskState({
|
|
198
172
|
startedAt: oneMinuteAgo.toISOString(),
|
|
199
173
|
status: "active",
|
|
200
174
|
});
|
|
201
|
-
// Act
|
|
202
175
|
const status = checkTaskTimeout(task);
|
|
203
|
-
// Assert
|
|
204
176
|
expect(status).not.toBeNull();
|
|
205
177
|
expect(status.isExpired).toBe(false);
|
|
206
178
|
expect(status.elapsedMs).toBeGreaterThanOrEqual(60 * 1000);
|
|
@@ -208,59 +180,45 @@ describe("checkTaskTimeout", () => {
|
|
|
208
180
|
expect(status.lastActivityAt).toBe(oneMinuteAgo.toISOString());
|
|
209
181
|
});
|
|
210
182
|
test("should return isExpired: true for expired task", () => {
|
|
211
|
-
// Arrange: task started 10 minutes ago (beyond 5 min default)
|
|
212
183
|
const now = new Date();
|
|
213
184
|
const tenMinutesAgo = new Date(now.getTime() - 10 * 60 * 1000);
|
|
214
185
|
const task = createMockTaskState({
|
|
215
186
|
startedAt: tenMinutesAgo.toISOString(),
|
|
216
187
|
status: "active",
|
|
217
188
|
});
|
|
218
|
-
// Act
|
|
219
189
|
const status = checkTaskTimeout(task);
|
|
220
|
-
// Assert
|
|
221
190
|
expect(status).not.toBeNull();
|
|
222
191
|
expect(status.isExpired).toBe(true);
|
|
223
192
|
expect(status.elapsedMs).toBeGreaterThanOrEqual(10 * 60 * 1000);
|
|
224
193
|
expect(status.remainingMs).toBeLessThan(0);
|
|
225
194
|
});
|
|
226
195
|
test("should respect custom timeout parameter", () => {
|
|
227
|
-
// Arrange: task started 2 minutes ago
|
|
228
196
|
const now = new Date();
|
|
229
197
|
const twoMinutesAgo = new Date(now.getTime() - 2 * 60 * 1000);
|
|
230
198
|
const task = createMockTaskState({
|
|
231
199
|
startedAt: twoMinutesAgo.toISOString(),
|
|
232
200
|
status: "active",
|
|
233
201
|
});
|
|
234
|
-
const customTimeoutMs = 60 * 1000;
|
|
235
|
-
// Act
|
|
202
|
+
const customTimeoutMs = 60 * 1000;
|
|
236
203
|
const status = checkTaskTimeout(task, customTimeoutMs);
|
|
237
|
-
// Assert: 2 minutes > 1 minute, should be expired
|
|
238
204
|
expect(status).not.toBeNull();
|
|
239
205
|
expect(status.isExpired).toBe(true);
|
|
240
206
|
});
|
|
241
207
|
test("should use default LIMITS.watchdogTimeoutMs when no timeout specified", () => {
|
|
242
|
-
// Arrange: task started 3 minutes ago (within default 5 min)
|
|
243
208
|
const now = new Date();
|
|
244
209
|
const threeMinutesAgo = new Date(now.getTime() - 3 * 60 * 1000);
|
|
245
210
|
const task = createMockTaskState({
|
|
246
211
|
startedAt: threeMinutesAgo.toISOString(),
|
|
247
212
|
status: "active",
|
|
248
213
|
});
|
|
249
|
-
// Act
|
|
250
214
|
const status = checkTaskTimeout(task);
|
|
251
|
-
// Assert: 3 minutes < 5 minutes, should not be expired
|
|
252
215
|
expect(status).not.toBeNull();
|
|
253
216
|
expect(status.isExpired).toBe(false);
|
|
254
217
|
expect(status.remainingMs).toBeGreaterThan(0);
|
|
255
218
|
});
|
|
256
219
|
});
|
|
257
|
-
// ============================================================================
|
|
258
|
-
// runWatchdog Tests - Periodic Health Check
|
|
259
|
-
// "If you can't measure it, you can't improve it." - Brendan Gregg
|
|
260
|
-
// ============================================================================
|
|
261
220
|
describe("runWatchdog", () => {
|
|
262
221
|
test("should return empty results for fresh squads", async () => {
|
|
263
|
-
// Squad was just created, not expired yet
|
|
264
222
|
const summary = await runWatchdog(session);
|
|
265
223
|
expect(summary.checkedSquads).toBe(1);
|
|
266
224
|
expect(summary.checkedTasks).toBe(0);
|
|
@@ -268,9 +226,7 @@ describe("runWatchdog", () => {
|
|
|
268
226
|
expect(summary.expiredTasks).toHaveLength(0);
|
|
269
227
|
});
|
|
270
228
|
test("should detect expired squad", async () => {
|
|
271
|
-
// Simulate squad that was updated 10 minutes ago
|
|
272
229
|
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
273
|
-
// Manually update the squad's updatedAt via storage (simulate old state)
|
|
274
230
|
const squadPath = `${session.sessionPath}/squads/${squadId}/state.json`;
|
|
275
231
|
const currentState = await session.storage.read(squadPath);
|
|
276
232
|
await session.storage.write(squadPath, {
|
|
@@ -283,7 +239,6 @@ describe("runWatchdog", () => {
|
|
|
283
239
|
expect(summary.expiredSquads).toHaveLength(1);
|
|
284
240
|
});
|
|
285
241
|
test("should mark expired squad as failed when not in dryRun mode", async () => {
|
|
286
|
-
// Simulate old squad
|
|
287
242
|
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
288
243
|
const squadPath = `${session.sessionPath}/squads/${squadId}/state.json`;
|
|
289
244
|
const currentState = await session.storage.read(squadPath);
|
|
@@ -297,7 +252,6 @@ describe("runWatchdog", () => {
|
|
|
297
252
|
expect(squad?.state.status).toBe("failed");
|
|
298
253
|
});
|
|
299
254
|
test("should NOT mark expired squad as failed in dryRun mode", async () => {
|
|
300
|
-
// Simulate old squad
|
|
301
255
|
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
302
256
|
const squadPath = `${session.sessionPath}/squads/${squadId}/state.json`;
|
|
303
257
|
const currentState = await session.storage.read(squadPath);
|
|
@@ -308,17 +262,14 @@ describe("runWatchdog", () => {
|
|
|
308
262
|
});
|
|
309
263
|
const summary = await runWatchdog(session, { dryRun: true });
|
|
310
264
|
expect(summary.expiredSquads).toContain(squadId);
|
|
311
|
-
// Squad should still be active (not mutated)
|
|
312
265
|
const squad = await getSquad(session, squadId);
|
|
313
266
|
expect(squad?.state.status).toBe("active");
|
|
314
267
|
});
|
|
315
268
|
test("should detect expired tasks", async () => {
|
|
316
|
-
// Create and start a task
|
|
317
269
|
const task = await assignTask(session, squadId, {
|
|
318
270
|
assignee: "worker-1",
|
|
319
271
|
description: "Long running task",
|
|
320
272
|
});
|
|
321
|
-
// Start the task with old startedAt (10 minutes ago)
|
|
322
273
|
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
323
274
|
await updateTask(session, squadId, task.taskId, {
|
|
324
275
|
status: "active",
|
|
@@ -348,7 +299,6 @@ describe("runWatchdog", () => {
|
|
|
348
299
|
expect(updatedTask?.result).toBeDefined();
|
|
349
300
|
});
|
|
350
301
|
test("should use custom timeouts", async () => {
|
|
351
|
-
// Create a task that started 2 minutes ago
|
|
352
302
|
const task = await assignTask(session, squadId, {
|
|
353
303
|
assignee: "worker-1",
|
|
354
304
|
description: "Quick task",
|
|
@@ -358,10 +308,8 @@ describe("runWatchdog", () => {
|
|
|
358
308
|
status: "active",
|
|
359
309
|
startedAt: twoMinutesAgo,
|
|
360
310
|
});
|
|
361
|
-
// With default timeout (5 min), should NOT be expired
|
|
362
311
|
const defaultSummary = await runWatchdog(session, { dryRun: true });
|
|
363
312
|
expect(defaultSummary.expiredTasks).toHaveLength(0);
|
|
364
|
-
// With short timeout (1 min), SHOULD be expired
|
|
365
313
|
const shortSummary = await runWatchdog(session, {
|
|
366
314
|
taskTimeoutMs: 60 * 1000,
|
|
367
315
|
dryRun: true,
|
|
@@ -372,7 +320,6 @@ describe("runWatchdog", () => {
|
|
|
372
320
|
await updateSquadState(session, squadId, {
|
|
373
321
|
status: "completed",
|
|
374
322
|
});
|
|
375
|
-
// Even with old updatedAt, completed squads are skipped
|
|
376
323
|
const squadPath = `${session.sessionPath}/squads/${squadId}/state.json`;
|
|
377
324
|
const currentState = await session.storage.read(squadPath);
|
|
378
325
|
await session.storage.write(squadPath, {
|
|
@@ -384,12 +331,10 @@ describe("runWatchdog", () => {
|
|
|
384
331
|
expect(summary.expiredSquads).toHaveLength(0);
|
|
385
332
|
});
|
|
386
333
|
test("should only check active tasks with startedAt", async () => {
|
|
387
|
-
// Task 1: pending (no startedAt) - should not be checked
|
|
388
334
|
await assignTask(session, squadId, {
|
|
389
335
|
assignee: "worker-1",
|
|
390
336
|
description: "Pending task",
|
|
391
337
|
});
|
|
392
|
-
// Task 2: active with startedAt - should be checked
|
|
393
338
|
const activeTask = await assignTask(session, squadId, {
|
|
394
339
|
assignee: "worker-2",
|
|
395
340
|
description: "Active task",
|