sdd-mcp-server 3.5.1 → 5.0.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 (124) hide show
  1. package/README.md +92 -683
  2. package/agents/architect.md +15 -93
  3. package/agents/implementer.md +16 -141
  4. package/agents/planner.md +16 -84
  5. package/agents/reviewer.md +16 -239
  6. package/agents/security-auditor.md +16 -114
  7. package/agents/tdd-guide.md +17 -228
  8. package/dist/adapters/cli/SDDToolAdapter.d.ts +14 -8
  9. package/dist/adapters/cli/SDDToolAdapter.js +188 -405
  10. package/dist/adapters/cli/SDDToolAdapter.js.map +1 -1
  11. package/dist/application/services/ContextCompactionService.d.ts +88 -16
  12. package/dist/application/services/ContextCompactionService.js +474 -187
  13. package/dist/application/services/ContextCompactionService.js.map +1 -1
  14. package/dist/application/services/ProjectService.js +3 -3
  15. package/dist/application/services/ProjectService.js.map +1 -1
  16. package/dist/application/services/SpecPathResolver.d.ts +24 -0
  17. package/dist/application/services/SpecPathResolver.js +70 -0
  18. package/dist/application/services/SpecPathResolver.js.map +1 -0
  19. package/dist/application/services/WorkflowEngineService.d.ts +214 -50
  20. package/dist/application/services/WorkflowEngineService.js +1447 -292
  21. package/dist/application/services/WorkflowEngineService.js.map +1 -1
  22. package/dist/application/services/WorkflowErrors.d.ts +16 -0
  23. package/dist/application/services/WorkflowErrors.js +53 -0
  24. package/dist/application/services/WorkflowErrors.js.map +1 -0
  25. package/dist/application/services/WorkflowValidationService.d.ts +25 -46
  26. package/dist/application/services/WorkflowValidationService.js +284 -627
  27. package/dist/application/services/WorkflowValidationService.js.map +1 -1
  28. package/dist/cli/install-skills.d.ts +3 -9
  29. package/dist/cli/install-skills.js +129 -174
  30. package/dist/cli/install-skills.js.map +1 -1
  31. package/dist/cli/install-target.d.ts +42 -8
  32. package/dist/cli/install-target.js +27 -9
  33. package/dist/cli/install-target.js.map +1 -1
  34. package/dist/cli/sdd-mcp-cli.d.ts +1 -1
  35. package/dist/cli/sdd-mcp-cli.js +7 -6
  36. package/dist/cli/sdd-mcp-cli.js.map +1 -1
  37. package/dist/cli/tool-support/claude-code.js +17 -34
  38. package/dist/cli/tool-support/claude-code.js.map +1 -1
  39. package/dist/cli/tool-support/codex.d.ts +0 -53
  40. package/dist/cli/tool-support/codex.js +10 -94
  41. package/dist/cli/tool-support/codex.js.map +1 -1
  42. package/dist/cli/tool-support/index.d.ts +3 -2
  43. package/dist/cli/tool-support/index.js +3 -1
  44. package/dist/cli/tool-support/index.js.map +1 -1
  45. package/dist/cli/tool-support/mcp-registration.d.ts +22 -0
  46. package/dist/cli/tool-support/mcp-registration.js +275 -0
  47. package/dist/cli/tool-support/mcp-registration.js.map +1 -0
  48. package/dist/cli/tool-support/omp.d.ts +5 -0
  49. package/dist/cli/tool-support/omp.js +47 -0
  50. package/dist/cli/tool-support/omp.js.map +1 -0
  51. package/dist/cli/tool-support/root-guidance.d.ts +2 -9
  52. package/dist/cli/tool-support/root-guidance.js +44 -37
  53. package/dist/cli/tool-support/root-guidance.js.map +1 -1
  54. package/dist/cli/tool-support/target-agent-renderer.d.ts +1 -0
  55. package/dist/cli/tool-support/target-agent-renderer.js +37 -4
  56. package/dist/cli/tool-support/target-agent-renderer.js.map +1 -1
  57. package/dist/cli/tool-support/target-installer.d.ts +9 -3
  58. package/dist/cli/tool-support/target-installer.js +100 -26
  59. package/dist/cli/tool-support/target-installer.js.map +1 -1
  60. package/dist/cli/utils/preserving-writer.d.ts +56 -0
  61. package/dist/cli/utils/preserving-writer.js +603 -10
  62. package/dist/cli/utils/preserving-writer.js.map +1 -1
  63. package/dist/domain/ports.d.ts +4 -0
  64. package/dist/domain/types.d.ts +52 -7
  65. package/dist/domain/types.js +5 -4
  66. package/dist/domain/types.js.map +1 -1
  67. package/dist/index.d.ts +13 -10
  68. package/dist/index.js +16 -1199
  69. package/dist/index.js.map +1 -1
  70. package/dist/infrastructure/adapters/NodeFileSystemAdapter.d.ts +3 -0
  71. package/dist/infrastructure/adapters/NodeFileSystemAdapter.js +10 -0
  72. package/dist/infrastructure/adapters/NodeFileSystemAdapter.js.map +1 -1
  73. package/dist/infrastructure/mcp/CapabilityNegotiator.js +3 -3
  74. package/dist/infrastructure/mcp/CapabilityNegotiator.js.map +1 -1
  75. package/dist/infrastructure/mcp/MCPServer.js +13 -13
  76. package/dist/infrastructure/mcp/MCPServer.js.map +1 -1
  77. package/dist/infrastructure/mcp/ToolRegistry.d.ts +5 -1
  78. package/dist/infrastructure/mcp/ToolRegistry.js +11 -4
  79. package/dist/infrastructure/mcp/ToolRegistry.js.map +1 -1
  80. package/dist/infrastructure/mcp/sddToolDefinitions.d.ts +6 -0
  81. package/dist/infrastructure/mcp/sddToolDefinitions.js +110 -0
  82. package/dist/infrastructure/mcp/sddToolDefinitions.js.map +1 -0
  83. package/dist/infrastructure/schemas/project.schema.d.ts +2 -2
  84. package/dist/infrastructure/schemas/project.schema.js +2 -2
  85. package/dist/infrastructure/schemas/project.schema.js.map +1 -1
  86. package/dist/shared/version.d.ts +3 -0
  87. package/dist/shared/version.js +4 -0
  88. package/dist/shared/version.js.map +1 -0
  89. package/dist/utils/atomicWrite.d.ts +8 -35
  90. package/dist/utils/atomicWrite.js +24 -57
  91. package/dist/utils/atomicWrite.js.map +1 -1
  92. package/dist/utils/withFilesystemLock.d.ts +22 -0
  93. package/dist/utils/withFilesystemLock.js +219 -0
  94. package/dist/utils/withFilesystemLock.js.map +1 -0
  95. package/mcp-server.js +5 -2883
  96. package/package.json +8 -3
  97. package/scripts/context-usage-report.mjs +602 -0
  98. package/sdd-entry.js +17 -6
  99. package/skills/sdd-commit/REFERENCE.md +31 -0
  100. package/skills/sdd-commit/SKILL.md +17 -273
  101. package/skills/sdd-design/REFERENCE.md +51 -0
  102. package/skills/sdd-design/SKILL.md +25 -262
  103. package/skills/sdd-implement/REFERENCE.md +30 -0
  104. package/skills/sdd-implement/SKILL.md +27 -284
  105. package/skills/sdd-requirements/REFERENCE.md +39 -0
  106. package/skills/sdd-requirements/SKILL.md +28 -132
  107. package/skills/sdd-review/REFERENCE.md +26 -0
  108. package/skills/sdd-review/SKILL.md +17 -181
  109. package/skills/sdd-security-check/REFERENCE.md +19 -0
  110. package/skills/sdd-security-check/SKILL.md +18 -184
  111. package/skills/sdd-steering/REFERENCE.md +25 -0
  112. package/skills/sdd-steering/SKILL.md +18 -216
  113. package/skills/sdd-steering-custom/REFERENCE.md +27 -0
  114. package/skills/sdd-steering-custom/SKILL.md +19 -203
  115. package/skills/sdd-tasks/REFERENCE.md +25 -0
  116. package/skills/sdd-tasks/SKILL.md +27 -244
  117. package/skills/sdd-test-gen/REFERENCE.md +15 -0
  118. package/skills/sdd-test-gen/SKILL.md +17 -287
  119. package/skills/simple-task/REFERENCE.md +22 -0
  120. package/skills/simple-task/SKILL.md +17 -138
  121. package/templates/CLAUDE.md +13 -31
  122. package/templates/codex-AGENTS.md +7 -9
  123. package/rules/git-workflow.md +0 -92
  124. package/rules/sdd-workflow.md +0 -116
@@ -1,64 +1,31 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import writeFileAtomic from "write-file-atomic";
4
+ const pendingWrites = new Map();
1
5
  /**
2
- * Atomic file write utility
3
- * 原子性檔案寫入工具
4
- *
5
- * Prevents file corruption when write operations are interrupted (crash, kill signal).
6
- * Uses the temp-file + rename pattern which is atomic on POSIX systems.
7
- * 防止寫入操作被中斷時(crash、kill signal)造成檔案損壞。
8
- * 使用 temp-file + rename 模式,在 POSIX 系統上是原子操作。
9
- *
10
- * @note Windows Limitation / Windows 限制:
11
- * On Windows, fs.rename() may fail with EEXIST if target exists.
12
- * For Windows production use, consider using 'write-file-atomic' package.
13
- * 在 Windows 上,若目標檔案存在,fs.rename() 可能會失敗。
14
- * 若需在 Windows 生產環境使用,建議改用 'write-file-atomic' 套件。
15
- *
16
- * @module utils/atomicWrite
17
- */
18
- import * as fs from "fs/promises";
19
- import * as path from "path";
20
- /**
21
- * Write content to a file atomically.
22
- *
23
- * This function writes to a temporary file first, then renames it to the target path.
24
- * The rename operation is atomic on POSIX systems, ensuring the file is never left
25
- * in a corrupted state.
26
- *
27
- * @param filePath - The target file path
28
- * @param content - The content to write
29
- * @param options - Optional write options
30
- * @returns Promise that resolves when write is complete
31
- *
32
- * @example
33
- * ```typescript
34
- * await atomicWriteFile('/path/to/spec.json', JSON.stringify(data, null, 2));
35
- * ```
6
+ * Atomically replaces a file. `write-file-atomic` queues concurrent writes to
7
+ * the same destination, preserves an existing mode by default, cleans its
8
+ * temporary file on failure, and implements replacement on Windows.
36
9
  */
37
- export async function atomicWriteFile(filePath, content, options) {
38
- const encoding = options?.encoding ?? "utf8";
39
- const dir = path.dirname(filePath);
40
- const filename = path.basename(filePath);
41
- // Create temp file path with process ID, timestamp, and random suffix for uniqueness
42
- // Random suffix prevents collisions when multiple writes occur within the same millisecond
43
- const uniqueSuffix = `${Date.now()}.${Math.random().toString(36).slice(2, 10)}`;
44
- const tempPath = path.join(dir, `.${filename}.${process.pid}.${uniqueSuffix}.tmp`);
10
+ export async function atomicWriteFile(filePath, content, options = {}) {
11
+ const key = resolve(filePath);
12
+ const prior = pendingWrites.get(key) ?? Promise.resolve();
13
+ const operation = prior
14
+ .catch(() => undefined)
15
+ .then(async () => {
16
+ await mkdir(dirname(filePath), { recursive: true });
17
+ await writeFileAtomic(filePath, content, {
18
+ encoding: options.encoding ?? "utf8",
19
+ mode: options.mode,
20
+ });
21
+ });
22
+ pendingWrites.set(key, operation);
45
23
  try {
46
- // Ensure directory exists
47
- await fs.mkdir(dir, { recursive: true });
48
- // Write to temp file
49
- await fs.writeFile(tempPath, content, encoding);
50
- // Atomic rename (on POSIX systems)
51
- await fs.rename(tempPath, filePath);
24
+ await operation;
52
25
  }
53
- catch (error) {
54
- // Clean up temp file if it exists
55
- try {
56
- await fs.unlink(tempPath);
57
- }
58
- catch {
59
- // Ignore cleanup errors
60
- }
61
- throw error;
26
+ finally {
27
+ if (pendingWrites.get(key) === operation)
28
+ pendingWrites.delete(key);
62
29
  }
63
30
  }
64
31
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"atomicWrite.js","sourceRoot":"","sources":["../../src/utils/atomicWrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,OAAe,EACf,OAAuC;IAEvC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEzC,qFAAqF;IACrF,2FAA2F;IAC3F,MAAM,YAAY,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAChF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,IAAI,OAAO,CAAC,GAAG,IAAI,YAAY,MAAM,CAAC,CAAC;IAEnF,IAAI,CAAC;QACH,0BAA0B;QAC1B,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEzC,qBAAqB;QACrB,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAEhD,mCAAmC;QACnC,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,kCAAkC;QAClC,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,IAAa,EACb,OAA6B;IAE7B,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,MAAM,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"atomicWrite.js","sourceRoot":"","sources":["../../src/utils/atomicWrite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAEhD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAyB,CAAC;AAOvD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,OAAe,EACf,UAA8B,EAAE;IAEhC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1D,MAAM,SAAS,GAAG,KAAK;SACpB,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;SACtB,IAAI,CAAC,KAAK,IAAI,EAAE;QACf,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE;YACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,MAAM;YACpC,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACL,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,IAAa,EACb,OAA6B;IAE7B,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,MAAM,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,22 @@
1
+ export interface FilesystemLockLease {
2
+ assertHeld(): Promise<void>;
3
+ }
4
+ export interface FilesystemLockOptions {
5
+ timeoutMs?: number;
6
+ retryDelayMs?: number;
7
+ }
8
+ export declare class FilesystemLockTimeoutError extends Error {
9
+ readonly lockPath: string;
10
+ readonly code = "LockTimeout";
11
+ constructor(lockPath: string);
12
+ }
13
+ export declare class FilesystemLockCompromisedError extends Error {
14
+ readonly lockPath: string;
15
+ readonly code = "LockCompromised";
16
+ constructor(lockPath: string);
17
+ }
18
+ /**
19
+ * Runs an action while holding a token-fenced, cross-process filesystem lease.
20
+ * Call assertHeld immediately after every durable write made by the action.
21
+ */
22
+ export declare function withFilesystemLock<T>(lockPath: string, action: (lease: FilesystemLockLease) => Promise<T>, options?: FilesystemLockOptions): Promise<T>;
@@ -0,0 +1,219 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { link, lstat, mkdir, open, rename, unlink } from 'node:fs/promises';
3
+ import { hostname as localHostname } from 'node:os';
4
+ import { basename, dirname, join } from 'node:path';
5
+ export class FilesystemLockTimeoutError extends Error {
6
+ lockPath;
7
+ code = 'LockTimeout';
8
+ constructor(lockPath) {
9
+ super(`Timed out acquiring filesystem lock: ${lockPath}`);
10
+ this.lockPath = lockPath;
11
+ this.name = 'FilesystemLockTimeoutError';
12
+ }
13
+ }
14
+ export class FilesystemLockCompromisedError extends Error {
15
+ lockPath;
16
+ code = 'LockCompromised';
17
+ constructor(lockPath) {
18
+ super(`Filesystem lock ownership was lost: ${lockPath}`);
19
+ this.lockPath = lockPath;
20
+ this.name = 'FilesystemLockCompromisedError';
21
+ }
22
+ }
23
+ function parseOwner(bytes) {
24
+ try {
25
+ const value = JSON.parse(bytes);
26
+ if (typeof value.token !== 'string' || value.token.length === 0
27
+ || !Number.isSafeInteger(value.pid) || value.pid <= 0
28
+ || typeof value.hostname !== 'string' || value.hostname.length === 0)
29
+ return undefined;
30
+ return value;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ async function readSnapshot(lockPath) {
37
+ let handle;
38
+ try {
39
+ handle = await open(lockPath, 'r');
40
+ }
41
+ catch (error) {
42
+ if (error.code === 'ENOENT')
43
+ return undefined;
44
+ throw error;
45
+ }
46
+ try {
47
+ const stat = await handle.stat({ bigint: true });
48
+ const bytes = stat.size <= 4096n ? await handle.readFile('utf8') : undefined;
49
+ const canonical = await lstat(lockPath, { bigint: true }).catch((error) => {
50
+ if (error.code === 'ENOENT')
51
+ return undefined;
52
+ throw error;
53
+ });
54
+ if (!canonical || canonical.dev !== stat.dev || canonical.ino !== stat.ino)
55
+ return undefined;
56
+ return {
57
+ owner: bytes === undefined ? undefined : parseOwner(bytes),
58
+ identity: { dev: stat.dev, ino: stat.ino },
59
+ };
60
+ }
61
+ finally {
62
+ await handle.close();
63
+ }
64
+ }
65
+ function sameIdentity(left, right) {
66
+ return left.dev === right.dev && left.ino === right.ino;
67
+ }
68
+ function ownerIsProvenDead(owner, hostname) {
69
+ if (owner.hostname !== hostname)
70
+ return false;
71
+ try {
72
+ process.kill(owner.pid, 0);
73
+ return false;
74
+ }
75
+ catch (error) {
76
+ return error.code === 'ESRCH';
77
+ }
78
+ }
79
+ async function removeIfPresent(path) {
80
+ try {
81
+ await unlink(path);
82
+ }
83
+ catch (error) {
84
+ if (error.code !== 'ENOENT')
85
+ throw error;
86
+ }
87
+ }
88
+ async function writeDurableOwner(path, owner) {
89
+ const handle = await open(path, 'wx', 0o600);
90
+ try {
91
+ await handle.writeFile(JSON.stringify(owner), 'utf8');
92
+ await handle.sync();
93
+ }
94
+ finally {
95
+ await handle.close();
96
+ }
97
+ }
98
+ async function tryEvictDeadOwner(lockPath, observed, hostname) {
99
+ if (!observed.owner || !ownerIsProvenDead(observed.owner, hostname))
100
+ return false;
101
+ const current = await readSnapshot(lockPath);
102
+ if (!current?.owner
103
+ || current.owner.token !== observed.owner.token
104
+ || !sameIdentity(current.identity, observed.identity)
105
+ || !ownerIsProvenDead(current.owner, hostname))
106
+ return false;
107
+ const quarantine = join(dirname(lockPath), `.${basename(lockPath)}.${randomUUID()}.quarantine`);
108
+ try {
109
+ await rename(lockPath, quarantine);
110
+ }
111
+ catch (error) {
112
+ if (error.code === 'ENOENT')
113
+ return true;
114
+ throw error;
115
+ }
116
+ const moved = await readSnapshot(quarantine);
117
+ if (!moved?.owner
118
+ || moved.owner.token !== observed.owner.token
119
+ || moved.owner.pid !== observed.owner.pid
120
+ || moved.owner.hostname !== observed.owner.hostname
121
+ || !sameIdentity(moved.identity, observed.identity)) {
122
+ try {
123
+ await link(quarantine, lockPath);
124
+ }
125
+ catch {
126
+ // Preserve whichever owner currently occupies the canonical path.
127
+ }
128
+ await removeIfPresent(quarantine);
129
+ return false;
130
+ }
131
+ await removeIfPresent(quarantine);
132
+ return true;
133
+ }
134
+ async function delay(milliseconds) {
135
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
136
+ }
137
+ /**
138
+ * Runs an action while holding a token-fenced, cross-process filesystem lease.
139
+ * Call assertHeld immediately after every durable write made by the action.
140
+ */
141
+ export async function withFilesystemLock(lockPath, action, options = {}) {
142
+ const timeoutMs = options.timeoutMs ?? 2_000;
143
+ const retryDelayMs = options.retryDelayMs ?? 25;
144
+ const deadline = Date.now() + timeoutMs;
145
+ await mkdir(dirname(lockPath), { recursive: true });
146
+ const owner = { token: randomUUID(), pid: process.pid, hostname: localHostname() };
147
+ const tempPath = join(dirname(lockPath), `.${basename(lockPath)}.${owner.token}.tmp`);
148
+ await writeDurableOwner(tempPath, owner);
149
+ let acquired = false;
150
+ try {
151
+ while (!acquired) {
152
+ try {
153
+ await link(tempPath, lockPath);
154
+ acquired = true;
155
+ }
156
+ catch (error) {
157
+ const code = error.code;
158
+ if (code !== 'EEXIST')
159
+ throw error;
160
+ const observed = await readSnapshot(lockPath);
161
+ if (observed)
162
+ await tryEvictDeadOwner(lockPath, observed, owner.hostname);
163
+ if (Date.now() >= deadline)
164
+ throw new FilesystemLockTimeoutError(lockPath);
165
+ await delay(retryDelayMs);
166
+ }
167
+ }
168
+ }
169
+ finally {
170
+ await removeIfPresent(tempPath);
171
+ }
172
+ const acquiredSnapshot = await readSnapshot(lockPath);
173
+ if (!acquiredSnapshot?.owner || acquiredSnapshot.owner.token !== owner.token) {
174
+ throw new FilesystemLockCompromisedError(lockPath);
175
+ }
176
+ const assertHeld = async () => {
177
+ const current = await readSnapshot(lockPath);
178
+ if (!current?.owner
179
+ || current.owner.token !== owner.token
180
+ || !sameIdentity(current.identity, acquiredSnapshot.identity))
181
+ throw new FilesystemLockCompromisedError(lockPath);
182
+ };
183
+ let actionError;
184
+ let value;
185
+ try {
186
+ await assertHeld();
187
+ value = await action({ assertHeld });
188
+ }
189
+ catch (error) {
190
+ actionError = error;
191
+ }
192
+ try {
193
+ await assertHeld();
194
+ const releasePath = join(dirname(lockPath), `.${basename(lockPath)}.${owner.token}.release`);
195
+ await rename(lockPath, releasePath);
196
+ const released = await readSnapshot(releasePath);
197
+ if (!released?.owner
198
+ || released.owner.token !== owner.token
199
+ || !sameIdentity(released.identity, acquiredSnapshot.identity)) {
200
+ try {
201
+ await link(releasePath, lockPath);
202
+ }
203
+ catch {
204
+ // Preserve whichever owner currently occupies the canonical path.
205
+ }
206
+ throw new FilesystemLockCompromisedError(lockPath);
207
+ }
208
+ await removeIfPresent(releasePath);
209
+ }
210
+ catch (releaseError) {
211
+ if (actionError === undefined
212
+ || releaseError instanceof FilesystemLockCompromisedError)
213
+ actionError = releaseError;
214
+ }
215
+ if (actionError !== undefined)
216
+ throw actionError;
217
+ return value;
218
+ }
219
+ //# sourceMappingURL=withFilesystemLock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withFilesystemLock.js","sourceRoot":"","sources":["../../src/utils/withFilesystemLock.ts"],"names":[],"mappings":"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"}