@filmwhisper/mcp-server 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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/data-integrity.test.d.ts +2 -0
  3. package/dist/__tests__/data-integrity.test.d.ts.map +1 -0
  4. package/dist/__tests__/data-integrity.test.js +171 -0
  5. package/dist/__tests__/data-integrity.test.js.map +1 -0
  6. package/dist/__tests__/engine/ipc-contract.test.d.ts +2 -0
  7. package/dist/__tests__/engine/ipc-contract.test.d.ts.map +1 -0
  8. package/dist/__tests__/engine/ipc-contract.test.js +122 -0
  9. package/dist/__tests__/engine/ipc-contract.test.js.map +1 -0
  10. package/dist/__tests__/fw-project-init.test.d.ts +2 -0
  11. package/dist/__tests__/fw-project-init.test.d.ts.map +1 -0
  12. package/dist/__tests__/fw-project-init.test.js +96 -0
  13. package/dist/__tests__/fw-project-init.test.js.map +1 -0
  14. package/dist/__tests__/helpers/mock-engine.d.ts +2 -0
  15. package/dist/__tests__/helpers/mock-engine.d.ts.map +1 -0
  16. package/dist/__tests__/helpers/mock-engine.js +81 -0
  17. package/dist/__tests__/helpers/mock-engine.js.map +1 -0
  18. package/dist/__tests__/r1-quality.test.d.ts +2 -0
  19. package/dist/__tests__/r1-quality.test.d.ts.map +1 -0
  20. package/dist/__tests__/r1-quality.test.js +539 -0
  21. package/dist/__tests__/r1-quality.test.js.map +1 -0
  22. package/dist/__tests__/search-fts5.test.d.ts +2 -0
  23. package/dist/__tests__/search-fts5.test.d.ts.map +1 -0
  24. package/dist/__tests__/search-fts5.test.js +135 -0
  25. package/dist/__tests__/search-fts5.test.js.map +1 -0
  26. package/dist/__tests__/tools/assemble.test.d.ts +2 -0
  27. package/dist/__tests__/tools/assemble.test.d.ts.map +1 -0
  28. package/dist/__tests__/tools/assemble.test.js +203 -0
  29. package/dist/__tests__/tools/assemble.test.js.map +1 -0
  30. package/dist/__tests__/tools/export.test.d.ts +2 -0
  31. package/dist/__tests__/tools/export.test.d.ts.map +1 -0
  32. package/dist/__tests__/tools/export.test.js +126 -0
  33. package/dist/__tests__/tools/export.test.js.map +1 -0
  34. package/dist/__tests__/tools/integration.test.d.ts +2 -0
  35. package/dist/__tests__/tools/integration.test.d.ts.map +1 -0
  36. package/dist/__tests__/tools/integration.test.js +707 -0
  37. package/dist/__tests__/tools/integration.test.js.map +1 -0
  38. package/dist/__tests__/tools/relink.test.d.ts +2 -0
  39. package/dist/__tests__/tools/relink.test.d.ts.map +1 -0
  40. package/dist/__tests__/tools/relink.test.js +107 -0
  41. package/dist/__tests__/tools/relink.test.js.map +1 -0
  42. package/dist/__tests__/tools/render-preview.test.d.ts +2 -0
  43. package/dist/__tests__/tools/render-preview.test.d.ts.map +1 -0
  44. package/dist/__tests__/tools/render-preview.test.js +99 -0
  45. package/dist/__tests__/tools/render-preview.test.js.map +1 -0
  46. package/dist/engine/engine-client.d.ts +29 -0
  47. package/dist/engine/engine-client.d.ts.map +1 -0
  48. package/dist/engine/engine-client.js +211 -0
  49. package/dist/engine/engine-client.js.map +1 -0
  50. package/dist/engine/engine-manager.d.ts +31 -0
  51. package/dist/engine/engine-manager.d.ts.map +1 -0
  52. package/dist/engine/engine-manager.js +144 -0
  53. package/dist/engine/engine-manager.js.map +1 -0
  54. package/dist/engine/types.d.ts +6 -0
  55. package/dist/engine/types.d.ts.map +1 -0
  56. package/dist/engine/types.js +2 -0
  57. package/dist/engine/types.js.map +1 -0
  58. package/dist/helpers/errors.d.ts +13 -0
  59. package/dist/helpers/errors.d.ts.map +1 -0
  60. package/dist/helpers/errors.js +14 -0
  61. package/dist/helpers/errors.js.map +1 -0
  62. package/dist/helpers/media-extensions.d.ts +8 -0
  63. package/dist/helpers/media-extensions.d.ts.map +1 -0
  64. package/dist/helpers/media-extensions.js +39 -0
  65. package/dist/helpers/media-extensions.js.map +1 -0
  66. package/dist/helpers/undo.d.ts +55 -0
  67. package/dist/helpers/undo.d.ts.map +1 -0
  68. package/dist/helpers/undo.js +142 -0
  69. package/dist/helpers/undo.js.map +1 -0
  70. package/dist/index.d.ts +7 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +108 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/prompts.d.ts +6 -0
  75. package/dist/prompts.d.ts.map +1 -0
  76. package/dist/prompts.js +62 -0
  77. package/dist/prompts.js.map +1 -0
  78. package/dist/resources.d.ts +7 -0
  79. package/dist/resources.d.ts.map +1 -0
  80. package/dist/resources.js +59 -0
  81. package/dist/resources.js.map +1 -0
  82. package/dist/server.d.ts +19 -0
  83. package/dist/server.d.ts.map +1 -0
  84. package/dist/server.js +33 -0
  85. package/dist/server.js.map +1 -0
  86. package/dist/storage/project-store.d.ts +92 -0
  87. package/dist/storage/project-store.d.ts.map +1 -0
  88. package/dist/storage/project-store.js +399 -0
  89. package/dist/storage/project-store.js.map +1 -0
  90. package/dist/storage/sqlite-store.d.ts +207 -0
  91. package/dist/storage/sqlite-store.d.ts.map +1 -0
  92. package/dist/storage/sqlite-store.js +983 -0
  93. package/dist/storage/sqlite-store.js.map +1 -0
  94. package/dist/tools/assemble.d.ts +17 -0
  95. package/dist/tools/assemble.d.ts.map +1 -0
  96. package/dist/tools/assemble.js +237 -0
  97. package/dist/tools/assemble.js.map +1 -0
  98. package/dist/tools/edit.d.ts +7 -0
  99. package/dist/tools/edit.d.ts.map +1 -0
  100. package/dist/tools/edit.js +159 -0
  101. package/dist/tools/edit.js.map +1 -0
  102. package/dist/tools/export.d.ts +7 -0
  103. package/dist/tools/export.d.ts.map +1 -0
  104. package/dist/tools/export.js +108 -0
  105. package/dist/tools/export.js.map +1 -0
  106. package/dist/tools/inspect.d.ts +7 -0
  107. package/dist/tools/inspect.d.ts.map +1 -0
  108. package/dist/tools/inspect.js +238 -0
  109. package/dist/tools/inspect.js.map +1 -0
  110. package/dist/tools/process.d.ts +7 -0
  111. package/dist/tools/process.d.ts.map +1 -0
  112. package/dist/tools/process.js +211 -0
  113. package/dist/tools/process.js.map +1 -0
  114. package/dist/tools/project.d.ts +7 -0
  115. package/dist/tools/project.d.ts.map +1 -0
  116. package/dist/tools/project.js +178 -0
  117. package/dist/tools/project.js.map +1 -0
  118. package/dist/tools/search.d.ts +7 -0
  119. package/dist/tools/search.d.ts.map +1 -0
  120. package/dist/tools/search.js +75 -0
  121. package/dist/tools/search.js.map +1 -0
  122. package/dist/tools/utility.d.ts +7 -0
  123. package/dist/tools/utility.d.ts.map +1 -0
  124. package/dist/tools/utility.js +108 -0
  125. package/dist/tools/utility.js.map +1 -0
  126. package/package.json +40 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FilmWhisper Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data-integrity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-integrity.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/data-integrity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Data Integrity Hardening tests:
3
+ * 1. Stale .tmp.* cleanup
4
+ * 2. WAL integrity check on a valid database
5
+ * 3. Cross-store reconciliation mismatch detection
6
+ */
7
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
8
+ import * as fs from "node:fs/promises";
9
+ import * as os from "node:os";
10
+ import * as path from "node:path";
11
+ import { ProjectStore } from "../storage/project-store.js";
12
+ import { SqliteStore } from "../storage/sqlite-store.js";
13
+ // ---------------------------------------------------------------------------
14
+ // Helpers
15
+ // ---------------------------------------------------------------------------
16
+ async function makeTmpDir() {
17
+ return fs.mkdtemp(path.join(os.tmpdir(), "fw-integrity-"));
18
+ }
19
+ async function initFwDir(projectPath) {
20
+ await fs.mkdir(path.join(projectPath, ".fw", "assets"), { recursive: true });
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // 1. Stale .tmp.* cleanup
24
+ // ---------------------------------------------------------------------------
25
+ describe("ProjectStore.cleanupStaleFiles", () => {
26
+ let tmpDir;
27
+ const store = new ProjectStore();
28
+ beforeEach(async () => {
29
+ tmpDir = await makeTmpDir();
30
+ await initFwDir(tmpDir);
31
+ });
32
+ afterEach(async () => {
33
+ await fs.rm(tmpDir, { recursive: true, force: true });
34
+ });
35
+ it("removes .tmp.{pid} files from .fw/ and nested dirs", async () => {
36
+ const fw = path.join(tmpDir, ".fw");
37
+ const assetsDir = path.join(fw, "assets");
38
+ // Create stale temp files
39
+ const stale1 = path.join(fw, "project.fw.json.tmp.12345");
40
+ const stale2 = path.join(assetsDir, "abc123.fw.json.tmp.99999");
41
+ await fs.writeFile(stale1, "orphan");
42
+ await fs.writeFile(stale2, "orphan");
43
+ // Create a legitimate file that should NOT be deleted
44
+ const legit = path.join(fw, "project.fw.json");
45
+ await fs.writeFile(legit, "{}");
46
+ const deleted = await store.cleanupStaleFiles(tmpDir);
47
+ expect(deleted).toHaveLength(2);
48
+ expect(deleted).toContain(stale1);
49
+ expect(deleted).toContain(stale2);
50
+ // Legitimate file untouched
51
+ await expect(fs.access(legit)).resolves.toBeUndefined();
52
+ // Stale files gone
53
+ await expect(fs.access(stale1)).rejects.toThrow();
54
+ await expect(fs.access(stale2)).rejects.toThrow();
55
+ });
56
+ it("returns empty array when no stale files exist", async () => {
57
+ const fw = path.join(tmpDir, ".fw");
58
+ await fs.writeFile(path.join(fw, "project.fw.json"), "{}");
59
+ const deleted = await store.cleanupStaleFiles(tmpDir);
60
+ expect(deleted).toHaveLength(0);
61
+ });
62
+ it("does not throw when .fw directory does not exist", async () => {
63
+ const emptyDir = await makeTmpDir();
64
+ try {
65
+ const deleted = await store.cleanupStaleFiles(emptyDir);
66
+ expect(deleted).toHaveLength(0);
67
+ }
68
+ finally {
69
+ await fs.rm(emptyDir, { recursive: true, force: true });
70
+ }
71
+ });
72
+ });
73
+ // ---------------------------------------------------------------------------
74
+ // 2. WAL integrity check
75
+ // ---------------------------------------------------------------------------
76
+ describe("SqliteStore.checkIntegrity", () => {
77
+ it("returns 'ok' for a freshly created in-memory database", () => {
78
+ const store = new SqliteStore(":memory:");
79
+ try {
80
+ expect(store.checkIntegrity()).toBe("ok");
81
+ }
82
+ finally {
83
+ store.close();
84
+ }
85
+ });
86
+ it("returns 'ok' for a freshly created on-disk database", async () => {
87
+ const tmpDir = await makeTmpDir();
88
+ const dbPath = path.join(tmpDir, "state.sqlite");
89
+ try {
90
+ const store = new SqliteStore(dbPath);
91
+ try {
92
+ expect(store.checkIntegrity()).toBe("ok");
93
+ }
94
+ finally {
95
+ store.close();
96
+ }
97
+ }
98
+ finally {
99
+ await fs.rm(tmpDir, { recursive: true, force: true });
100
+ }
101
+ });
102
+ it("walCheckpoint runs without error on a healthy database", () => {
103
+ const store = new SqliteStore(":memory:");
104
+ try {
105
+ expect(() => store.walCheckpoint()).not.toThrow();
106
+ }
107
+ finally {
108
+ store.close();
109
+ }
110
+ });
111
+ });
112
+ // ---------------------------------------------------------------------------
113
+ // 3. Cross-store reconciliation
114
+ // ---------------------------------------------------------------------------
115
+ describe("SqliteStore.getAssetHashes reconciliation", () => {
116
+ it("returns empty set when no clips or reviews exist", () => {
117
+ const store = new SqliteStore(":memory:");
118
+ try {
119
+ expect(store.getAssetHashes().size).toBe(0);
120
+ }
121
+ finally {
122
+ store.close();
123
+ }
124
+ });
125
+ it("returns hashes referenced by timeline clips", () => {
126
+ const store = new SqliteStore(":memory:");
127
+ try {
128
+ store.addClip({ assetHash: "hash-aaa", inPointMs: 0, outPointMs: 1000 });
129
+ store.addClip({ assetHash: "hash-bbb", inPointMs: 0, outPointMs: 500 });
130
+ const hashes = store.getAssetHashes();
131
+ expect(hashes.has("hash-aaa")).toBe(true);
132
+ expect(hashes.has("hash-bbb")).toBe(true);
133
+ expect(hashes.size).toBe(2);
134
+ }
135
+ finally {
136
+ store.close();
137
+ }
138
+ });
139
+ it("returns hashes referenced by segment reviews", () => {
140
+ const store = new SqliteStore(":memory:");
141
+ try {
142
+ store.setReview({ assetHash: "hash-ccc", segmentIndex: 0, status: "keep" });
143
+ const hashes = store.getAssetHashes();
144
+ expect(hashes.has("hash-ccc")).toBe(true);
145
+ }
146
+ finally {
147
+ store.close();
148
+ }
149
+ });
150
+ it("detects SQLite-only hashes (no corresponding JSON asset file)", () => {
151
+ // Simulate what fw_project_rescan does: compare JSON asset hash set with SQLite
152
+ const store = new SqliteStore(":memory:");
153
+ try {
154
+ store.addClip({ assetHash: "orphan-hash", inPointMs: 0, outPointMs: 1000 });
155
+ const jsonAssetHashes = new Set(["known-hash"]);
156
+ const sqliteHashes = store.getAssetHashes();
157
+ const sqliteOnlyHashes = [];
158
+ for (const hash of sqliteHashes) {
159
+ if (!jsonAssetHashes.has(hash)) {
160
+ sqliteOnlyHashes.push(hash);
161
+ }
162
+ }
163
+ expect(sqliteOnlyHashes).toContain("orphan-hash");
164
+ expect(sqliteOnlyHashes).toHaveLength(1);
165
+ }
166
+ finally {
167
+ store.close();
168
+ }
169
+ });
170
+ });
171
+ //# sourceMappingURL=data-integrity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-integrity.test.js","sourceRoot":"","sources":["../../src/__tests__/data-integrity.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ipc-contract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc-contract.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/engine/ipc-contract.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * IPC Contract Tests
3
+ *
4
+ * These tests spawn the real Python daemon and verify the JSON-RPC 2.0
5
+ * protocol contract between the TS client and the Python process.
6
+ */
7
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
8
+ import * as path from "node:path";
9
+ import * as fs from "node:fs";
10
+ import { EngineClient, EngineClientError } from "../../engine/engine-client.js";
11
+ import { EngineManager } from "../../engine/engine-manager.js";
12
+ const ENGINE_DIR = path.resolve(__dirname, "../../../../processing-engine");
13
+ const VENV_PYTHON = path.join(ENGINE_DIR, ".venv", "bin", "python3");
14
+ const PYTHON_PATH = fs.existsSync(VENV_PYTHON) ? VENV_PYTHON : "python3";
15
+ describe("IPC Contract Tests", () => {
16
+ let client;
17
+ beforeAll(async () => {
18
+ client = new EngineClient(PYTHON_PATH);
19
+ await client.start(ENGINE_DIR);
20
+ });
21
+ afterAll(async () => {
22
+ await client.stop();
23
+ });
24
+ it("initialize handshake returns protocol version", async () => {
25
+ const result = await client.request("initialize", { protocol_version: "1.0" }, 5_000);
26
+ expect(result).toHaveProperty("protocol_version", "1.0");
27
+ expect(result).toHaveProperty("capabilities");
28
+ expect(typeof result.capabilities).toBe("object");
29
+ });
30
+ it("ping returns pong with uptime", async () => {
31
+ const result = await client.request("ping", {}, 5_000);
32
+ expect(result).toHaveProperty("status", "pong");
33
+ expect(result).toHaveProperty("uptime_seconds");
34
+ expect(typeof result.uptime_seconds).toBe("number");
35
+ });
36
+ it("unknown method returns -32601 error", async () => {
37
+ try {
38
+ await client.request("nonexistent_method", {}, 5_000);
39
+ expect.fail("Should have thrown");
40
+ }
41
+ catch (err) {
42
+ expect(err).toBeInstanceOf(EngineClientError);
43
+ expect(err.code).toBe(-32601);
44
+ }
45
+ });
46
+ it("timeout produces clear error", async () => {
47
+ // Spawn a separate client that we'll stop mid-request to test timeout.
48
+ // We create a second client, start it, then kill its process so it can't
49
+ // respond, and verify the pending request times out.
50
+ const timeoutClient = new EngineClient(PYTHON_PATH);
51
+ await timeoutClient.start(ENGINE_DIR);
52
+ // Verify it's alive
53
+ const pong = await timeoutClient.request("ping", {}, 5_000);
54
+ expect(pong.status).toBe("pong");
55
+ // Kill the underlying process so the next request can never get a response.
56
+ // Access the pid and kill it with SIGSTOP to freeze (not exit) so the
57
+ // client stays "running" but unresponsive.
58
+ const pid = timeoutClient.pid;
59
+ expect(pid).not.toBeNull();
60
+ process.kill(pid, "SIGSTOP");
61
+ try {
62
+ await timeoutClient.request("ping", {}, 200);
63
+ expect.fail("Should have thrown");
64
+ }
65
+ catch (err) {
66
+ expect(err).toBeInstanceOf(EngineClientError);
67
+ expect(err.message).toMatch(/timeout/i);
68
+ }
69
+ finally {
70
+ // SIGCONT then stop cleanly
71
+ try {
72
+ process.kill(pid, "SIGCONT");
73
+ }
74
+ catch { /* process may have exited */ }
75
+ await timeoutClient.stop();
76
+ }
77
+ });
78
+ it("transcode returns FILE_NOT_FOUND for missing source", async () => {
79
+ try {
80
+ await client.request("transcode", {
81
+ asset_hash: "test123",
82
+ source_path: "/nonexistent_test.mp4",
83
+ proxy_spec: "720p",
84
+ output_path: "/tmp/fw_test_out.mp4",
85
+ }, 5_000);
86
+ throw new Error("Should have thrown");
87
+ }
88
+ catch (err) {
89
+ expect(err).toBeInstanceOf(EngineClientError);
90
+ expect(err.code).toBe(1003); // FILE_NOT_FOUND
91
+ }
92
+ });
93
+ });
94
+ describe("EngineManager lifecycle", () => {
95
+ let manager;
96
+ afterAll(async () => {
97
+ if (manager) {
98
+ await manager.shutdown();
99
+ }
100
+ });
101
+ it("auto-starts daemon on first request", async () => {
102
+ manager = new EngineManager(ENGINE_DIR, PYTHON_PATH);
103
+ expect(manager.isRunning).toBe(false);
104
+ const result = await manager.request("ping", {}, 5_000);
105
+ expect(manager.isRunning).toBe(true);
106
+ expect(result.status).toBe("pong");
107
+ });
108
+ it("healthCheck returns true when daemon is running", async () => {
109
+ const healthy = await manager.healthCheck();
110
+ expect(healthy).toBe(true);
111
+ });
112
+ it("shutdown stops the daemon", async () => {
113
+ await manager.shutdown();
114
+ expect(manager.isRunning).toBe(false);
115
+ });
116
+ it("restarts after shutdown on next request", async () => {
117
+ const result = await manager.request("ping", {}, 5_000);
118
+ expect(manager.isRunning).toBe(true);
119
+ expect(result.status).toBe("pong");
120
+ });
121
+ });
122
+ //# sourceMappingURL=ipc-contract.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc-contract.test.js","sourceRoot":"","sources":["../../../src/__tests__/engine/ipc-contract.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fw-project-init.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fw-project-init.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fw-project-init.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * TDD RED → GREEN: fw_project_init MCP tool test
3
+ * Uses InMemoryTransport for process-free testing
4
+ */
5
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
6
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
8
+ import * as fs from "node:fs/promises";
9
+ import * as os from "node:os";
10
+ import * as path from "node:path";
11
+ import { createServer } from "../server.js";
12
+ import { ProjectStore } from "../storage/project-store.js";
13
+ describe("fw_project_init tool", () => {
14
+ let client;
15
+ let cleanup;
16
+ let tmpDir;
17
+ beforeEach(async () => {
18
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "fw-test-"));
19
+ const deps = {
20
+ projectStore: new ProjectStore(),
21
+ projects: new Map(),
22
+ };
23
+ const server = createServer(deps);
24
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
25
+ await server.connect(serverTransport);
26
+ client = new Client({ name: "test-client", version: "1.0.0" });
27
+ await client.connect(clientTransport);
28
+ cleanup = async () => {
29
+ await client.close();
30
+ await server.close();
31
+ };
32
+ });
33
+ afterEach(async () => {
34
+ await cleanup();
35
+ await fs.rm(tmpDir, { recursive: true, force: true });
36
+ });
37
+ it("should be listed in available tools", async () => {
38
+ const { tools } = await client.listTools();
39
+ const toolNames = tools.map((t) => t.name);
40
+ expect(toolNames).toContain("fw_project_init");
41
+ });
42
+ it("should return project_id and asset counts on valid input", async () => {
43
+ const result = await client.callTool({
44
+ name: "fw_project_init",
45
+ arguments: {
46
+ name: "Test Project",
47
+ source_path: tmpDir,
48
+ },
49
+ });
50
+ expect(result.isError).toBeFalsy();
51
+ const content = result.content;
52
+ expect(content).toHaveLength(1);
53
+ expect(content[0].type).toBe("text");
54
+ const data = JSON.parse(content[0].text);
55
+ expect(data).toHaveProperty("project_id");
56
+ expect(data).toHaveProperty("assets_discovered");
57
+ expect(data).toHaveProperty("assets_new");
58
+ expect(data).toHaveProperty("revision");
59
+ expect(typeof data.project_id).toBe("string");
60
+ expect(data.project_id.length).toBeGreaterThan(0);
61
+ });
62
+ it("should accept optional fields", async () => {
63
+ const result = await client.callTool({
64
+ name: "fw_project_init",
65
+ arguments: {
66
+ name: "Full Options Project",
67
+ source_path: tmpDir,
68
+ proxy_spec: "1280x720",
69
+ language: "ko",
70
+ target_duration_seconds: 120,
71
+ },
72
+ });
73
+ expect(result.isError).toBeFalsy();
74
+ const data = JSON.parse(result.content[0].text);
75
+ expect(data.project_id).toBeTruthy();
76
+ });
77
+ it("should fail when name is missing", async () => {
78
+ const result = await client.callTool({
79
+ name: "fw_project_init",
80
+ arguments: {
81
+ source_path: tmpDir,
82
+ },
83
+ });
84
+ expect(result.isError).toBe(true);
85
+ });
86
+ it("should fail when source_path is missing", async () => {
87
+ const result = await client.callTool({
88
+ name: "fw_project_init",
89
+ arguments: {
90
+ name: "Test",
91
+ },
92
+ });
93
+ expect(result.isError).toBe(true);
94
+ });
95
+ });
96
+ //# sourceMappingURL=fw-project-init.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fw-project-init.test.js","sourceRoot":"","sources":["../../src/__tests__/fw-project-init.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function createMockEngineManager(): unknown;
2
+ //# sourceMappingURL=mock-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-engine.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/mock-engine.ts"],"names":[],"mappings":"AAMA,wBAAgB,uBAAuB,IAAI,OAAO,CAuFjD"}
@@ -0,0 +1,81 @@
1
+ export function createMockEngineManager() {
2
+ return {
3
+ async request(method, params, _timeout) {
4
+ switch (method) {
5
+ case "editorial_rank": {
6
+ // Echo back the segments that were passed in as ranked clips
7
+ const inputSegments = params.segments;
8
+ return {
9
+ ranked_clips: inputSegments.map((s) => ({
10
+ asset_hash: s.asset_hash,
11
+ segment_index: s.segment_index,
12
+ score: 0.9,
13
+ role: "interview",
14
+ selection_reason: "high story value",
15
+ })),
16
+ };
17
+ }
18
+ case "duration_trim": {
19
+ // Echo back the clips that were passed in, preserving asset_hash/segment_index
20
+ const inputClips = params.clips;
21
+ const trimmedClips = inputClips.map((c) => ({
22
+ asset_hash: c.asset_hash,
23
+ segment_index: c.segment_index,
24
+ in_point_ms: c.start_ms,
25
+ out_point_ms: c.end_ms,
26
+ trimmed: false,
27
+ }));
28
+ const totalDurationMs = inputClips.reduce((sum, c) => sum + (c.end_ms - c.start_ms), 0);
29
+ return {
30
+ trimmed_clips: trimmedClips,
31
+ total_duration_ms: totalDurationMs,
32
+ rationale: trimmedClips.map((_, i) => ({
33
+ clip_index: i,
34
+ role: "interview",
35
+ selection_reason: "included at full length",
36
+ })),
37
+ };
38
+ }
39
+ case "gap_fill": {
40
+ // Echo backbone clips back as-is (no actual gap fill in tests)
41
+ const backbone = params.backbone_clips;
42
+ const filledClips = backbone.map((c) => ({
43
+ asset_hash: c.asset_hash,
44
+ segment_index: c.segment_index,
45
+ in_point_ms: c.start_ms,
46
+ out_point_ms: c.end_ms,
47
+ is_gap_fill: false,
48
+ }));
49
+ return {
50
+ filled_clips: filledClips,
51
+ total_duration_ms: backbone.reduce((sum, c) => sum + (c.end_ms - c.start_ms), 0),
52
+ rationale: filledClips.map((_, i) => ({
53
+ clip_index: i,
54
+ role: "backbone",
55
+ selection_reason: "backbone clip",
56
+ })),
57
+ };
58
+ }
59
+ case "export":
60
+ return { output_path: params.output_path };
61
+ case "assemble":
62
+ return {
63
+ output_path: params.output_path,
64
+ duration_seconds: 30,
65
+ };
66
+ default:
67
+ throw new Error(`Mock: unknown method ${method}`);
68
+ }
69
+ },
70
+ async ensureRunning() {
71
+ // no-op
72
+ },
73
+ get isRunning() {
74
+ return true;
75
+ },
76
+ async shutdown() {
77
+ // no-op
78
+ },
79
+ };
80
+ }
81
+ //# sourceMappingURL=mock-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-engine.js","sourceRoot":"","sources":["../../../src/__tests__/helpers/mock-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=r1-quality.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r1-quality.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/r1-quality.test.ts"],"names":[],"mappings":""}