commandmate 0.1.10 → 0.1.11

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 (139) hide show
  1. package/.env.example +8 -3
  2. package/.next/BUILD_ID +1 -1
  3. package/.next/app-build-manifest.json +11 -11
  4. package/.next/app-path-routes-manifest.json +1 -1
  5. package/.next/build-manifest.json +2 -2
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/cache/config.json +3 -3
  8. package/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/.next/cache/webpack/client-production/1.pack +0 -0
  10. package/.next/cache/webpack/client-production/2.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack +0 -0
  12. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  13. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  14. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  15. package/.next/cache/webpack/server-production/0.pack +0 -0
  16. package/.next/cache/webpack/server-production/index.pack +0 -0
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +1 -1
  19. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/server/app/_not-found.html +1 -1
  21. package/.next/server/app/_not-found.rsc +1 -1
  22. package/.next/server/app/api/external-apps/[id]/health/route.js +11 -12
  23. package/.next/server/app/api/external-apps/[id]/route.js +14 -15
  24. package/.next/server/app/api/external-apps/route.js +12 -13
  25. package/.next/server/app/api/hooks/claude-done/route.js +1 -1
  26. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  27. package/.next/server/app/api/repositories/clone/route.js +1 -1
  28. package/.next/server/app/api/repositories/route.js +1 -1
  29. package/.next/server/app/api/repositories/scan/route.js +1 -1
  30. package/.next/server/app/api/repositories/sync/route.js +1 -1
  31. package/.next/server/app/api/slash-commands.body +1 -1
  32. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  33. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  34. package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
  35. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  36. package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
  37. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  38. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  39. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
  40. package/.next/server/app/api/worktrees/[id]/logs/route.js +7 -7
  41. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
  42. package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
  43. package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
  44. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  45. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  46. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  47. package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
  48. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  49. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  50. package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
  51. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
  52. package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
  53. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
  54. package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
  55. package/.next/server/app/api/worktrees/route.js +1 -1
  56. package/.next/server/app/index.html +2 -2
  57. package/.next/server/app/index.rsc +2 -2
  58. package/.next/server/app/page_client-reference-manifest.js +1 -1
  59. package/.next/server/app/proxy/[...path]/route.js +12 -13
  60. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  61. package/.next/server/app/worktrees/[id]/page.js +3 -3
  62. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  63. package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
  64. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  65. package/.next/server/app-paths-manifest.json +10 -10
  66. package/.next/server/chunks/1318.js +4 -4
  67. package/.next/server/chunks/1528.js +1 -1
  68. package/.next/server/chunks/7425.js +97 -48
  69. package/.next/server/chunks/9723.js +1 -1
  70. package/.next/server/functions-config-manifest.json +1 -1
  71. package/.next/server/middleware-manifest.json +5 -5
  72. package/.next/server/pages/404.html +1 -1
  73. package/.next/server/pages/500.html +1 -1
  74. package/.next/server/server-reference-manifest.json +1 -1
  75. package/.next/server/src/middleware.js +2 -2
  76. package/.next/server/src/middleware.js.map +1 -1
  77. package/.next/static/chunks/app/worktrees/[id]/page-720605c2fb074444.js +1 -0
  78. package/.next/trace +5 -5
  79. package/dist/cli/commands/init.d.ts.map +1 -1
  80. package/dist/cli/commands/init.js +6 -4
  81. package/dist/cli/commands/start.d.ts +2 -0
  82. package/dist/cli/commands/start.d.ts.map +1 -1
  83. package/dist/cli/commands/start.js +64 -17
  84. package/dist/cli/commands/status.d.ts +4 -1
  85. package/dist/cli/commands/status.d.ts.map +1 -1
  86. package/dist/cli/commands/status.js +95 -6
  87. package/dist/cli/commands/stop.d.ts +2 -0
  88. package/dist/cli/commands/stop.d.ts.map +1 -1
  89. package/dist/cli/commands/stop.js +27 -10
  90. package/dist/cli/index.js +16 -2
  91. package/dist/cli/types/index.d.ts +20 -0
  92. package/dist/cli/types/index.d.ts.map +1 -1
  93. package/dist/cli/utils/daemon-factory.d.ts +105 -0
  94. package/dist/cli/utils/daemon-factory.d.ts.map +1 -0
  95. package/dist/cli/utils/daemon-factory.js +117 -0
  96. package/dist/cli/utils/daemon.d.ts.map +1 -1
  97. package/dist/cli/utils/daemon.js +4 -0
  98. package/dist/cli/utils/env-setup.d.ts +24 -12
  99. package/dist/cli/utils/env-setup.d.ts.map +1 -1
  100. package/dist/cli/utils/env-setup.js +64 -43
  101. package/dist/cli/utils/input-validators.d.ts +103 -0
  102. package/dist/cli/utils/input-validators.d.ts.map +1 -0
  103. package/dist/cli/utils/input-validators.js +163 -0
  104. package/dist/cli/utils/install-context.d.ts +53 -0
  105. package/dist/cli/utils/install-context.d.ts.map +1 -0
  106. package/dist/cli/utils/install-context.js +96 -0
  107. package/dist/cli/utils/pid-manager.d.ts +34 -0
  108. package/dist/cli/utils/pid-manager.d.ts.map +1 -1
  109. package/dist/cli/utils/pid-manager.js +43 -0
  110. package/dist/cli/utils/port-allocator.d.ts +108 -0
  111. package/dist/cli/utils/port-allocator.d.ts.map +1 -0
  112. package/dist/cli/utils/port-allocator.js +166 -0
  113. package/dist/cli/utils/resource-resolvers.d.ts +92 -0
  114. package/dist/cli/utils/resource-resolvers.d.ts.map +1 -0
  115. package/dist/cli/utils/resource-resolvers.js +175 -0
  116. package/dist/cli/utils/worktree-detector.d.ts +82 -0
  117. package/dist/cli/utils/worktree-detector.d.ts.map +1 -0
  118. package/dist/cli/utils/worktree-detector.js +221 -0
  119. package/dist/lib/errors.d.ts +111 -0
  120. package/dist/lib/errors.d.ts.map +1 -0
  121. package/dist/lib/errors.js +153 -0
  122. package/dist/server/server.js +3 -0
  123. package/dist/server/src/cli/utils/install-context.js +96 -0
  124. package/dist/server/src/config/system-directories.js +40 -0
  125. package/dist/server/src/lib/auto-yes-manager.js +325 -0
  126. package/dist/server/src/lib/auto-yes-resolver.js +34 -0
  127. package/dist/server/src/lib/cli-patterns.js +6 -1
  128. package/dist/server/src/lib/db-instance.js +12 -2
  129. package/dist/server/src/lib/db-migrations.js +68 -1
  130. package/dist/server/src/lib/db-path-resolver.js +99 -0
  131. package/dist/server/src/lib/db.js +21 -0
  132. package/dist/server/src/lib/env.js +52 -3
  133. package/dist/server/src/lib/worktrees.js +36 -1
  134. package/dist/server/src/types/external-apps.js +20 -0
  135. package/package.json +1 -1
  136. package/.next/static/chunks/app/worktrees/[id]/page-aea2d5e7e28955be.js +0 -1
  137. /package/.next/static/chunks/app/{page-96a8aa2ec30a44e9.js → page-fe35d61f14b90a51.js} +0 -0
  138. /package/.next/static/{8o5rUyZun0GklIHWDgkmv → gRNW5YXY43KqCKbCdaJoJ}/_buildManifest.js +0 -0
  139. /package/.next/static/{8o5rUyZun0GklIHWDgkmv → gRNW5YXY43KqCKbCdaJoJ}/_ssgManifest.js +0 -0
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ /**
3
+ * Port Allocator
4
+ * Issue #136: Phase 1 - Foundation
5
+ *
6
+ * Provides automatic port allocation for worktree servers.
7
+ * SF-SEC-002: Implements MAX_WORKTREES limit to prevent port exhaustion attacks.
8
+ *
9
+ * @module port-allocator
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PortAllocator = exports.MAX_WORKTREES = exports.DEFAULT_PORT_RANGE = void 0;
13
+ exports.createPortAllocator = createPortAllocator;
14
+ const net_1 = require("net");
15
+ const errors_1 = require("../../lib/errors");
16
+ /**
17
+ * Default port range for worktree servers
18
+ * Main server uses 3000, worktrees use 3001-3100
19
+ */
20
+ exports.DEFAULT_PORT_RANGE = {
21
+ min: 3001,
22
+ max: 3100,
23
+ };
24
+ /**
25
+ * Maximum number of concurrent worktree servers
26
+ * SF-SEC-002: Prevents port exhaustion attacks
27
+ */
28
+ exports.MAX_WORKTREES = 10;
29
+ /**
30
+ * Port allocator for managing worktree server ports
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const allocator = new PortAllocator();
35
+ * const port = await allocator.findAvailablePort();
36
+ * allocator.markAllocated(port);
37
+ * // ... start server on port ...
38
+ * allocator.release(port);
39
+ * ```
40
+ */
41
+ class PortAllocator {
42
+ static instance = null;
43
+ range;
44
+ allocatedPorts;
45
+ issuePortMap;
46
+ constructor(range = exports.DEFAULT_PORT_RANGE) {
47
+ this.range = range;
48
+ this.allocatedPorts = new Set();
49
+ this.issuePortMap = new Map();
50
+ }
51
+ /**
52
+ * Get singleton instance
53
+ * Issue #136: Singleton pattern for CLI commands
54
+ */
55
+ static getInstance() {
56
+ if (!PortAllocator.instance) {
57
+ PortAllocator.instance = new PortAllocator();
58
+ }
59
+ return PortAllocator.instance;
60
+ }
61
+ /**
62
+ * Allocate a port for a specific issue (synchronous, deterministic)
63
+ * Issue #136: Simple port allocation based on issue number
64
+ *
65
+ * @param issueNo - Issue number
66
+ * @returns Allocated port number
67
+ */
68
+ allocate(issueNo) {
69
+ // Check if already allocated for this issue
70
+ const existing = this.issuePortMap.get(issueNo);
71
+ if (existing !== undefined) {
72
+ return existing;
73
+ }
74
+ // Deterministic port based on issue number (3001 + issueNo % 100)
75
+ const basePort = this.range.min + (issueNo % (this.range.max - this.range.min));
76
+ this.issuePortMap.set(issueNo, basePort);
77
+ this.allocatedPorts.add(basePort);
78
+ return basePort;
79
+ }
80
+ /**
81
+ * Find an available port in the configured range
82
+ *
83
+ * @returns Available port number
84
+ * @throws AppError with code PORT_EXHAUSTED if no ports available
85
+ * @throws AppError with code MAX_WORKTREES_EXCEEDED if limit reached
86
+ */
87
+ async findAvailablePort() {
88
+ // Check MAX_WORKTREES limit
89
+ if (this.allocatedPorts.size >= exports.MAX_WORKTREES) {
90
+ throw new errors_1.AppError('MAX_WORKTREES_EXCEEDED', `Maximum number of worktrees (${exports.MAX_WORKTREES}) exceeded`, { currentCount: this.allocatedPorts.size, limit: exports.MAX_WORKTREES });
91
+ }
92
+ for (let port = this.range.min; port <= this.range.max; port++) {
93
+ // Skip already allocated ports
94
+ if (this.allocatedPorts.has(port)) {
95
+ continue;
96
+ }
97
+ if (await this.isPortAvailable(port)) {
98
+ return port;
99
+ }
100
+ }
101
+ throw new errors_1.AppError(errors_1.ErrorCode.PORT_EXHAUSTED, `No available ports in range ${this.range.min}-${this.range.max}`, { range: this.range, allocatedPorts: Array.from(this.allocatedPorts) });
102
+ }
103
+ /**
104
+ * Check if a specific port is available
105
+ *
106
+ * @param port - Port number to check
107
+ * @returns true if port is available
108
+ */
109
+ async isPortAvailable(port) {
110
+ return new Promise((resolve) => {
111
+ const server = (0, net_1.createServer)();
112
+ server.on('error', () => {
113
+ resolve(false);
114
+ });
115
+ server.listen(port, () => {
116
+ server.close(() => {
117
+ resolve(true);
118
+ });
119
+ });
120
+ });
121
+ }
122
+ /**
123
+ * Mark a port as allocated
124
+ *
125
+ * @param port - Port number to mark as allocated
126
+ */
127
+ markAllocated(port) {
128
+ this.allocatedPorts.add(port);
129
+ }
130
+ /**
131
+ * Release an allocated port
132
+ *
133
+ * @param port - Port number to release
134
+ */
135
+ release(port) {
136
+ this.allocatedPorts.delete(port);
137
+ }
138
+ /**
139
+ * Get the count of allocated ports
140
+ */
141
+ getAllocatedCount() {
142
+ return this.allocatedPorts.size;
143
+ }
144
+ /**
145
+ * Check if a port is currently allocated (in-memory tracking)
146
+ *
147
+ * @param port - Port number to check
148
+ * @returns true if port is allocated
149
+ */
150
+ isAllocated(port) {
151
+ return this.allocatedPorts.has(port);
152
+ }
153
+ /**
154
+ * Get all allocated ports
155
+ */
156
+ getAllocatedPorts() {
157
+ return Array.from(this.allocatedPorts);
158
+ }
159
+ }
160
+ exports.PortAllocator = PortAllocator;
161
+ /**
162
+ * Create a port allocator with default settings
163
+ */
164
+ function createPortAllocator(range) {
165
+ return new PortAllocator(range);
166
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Resource Path Resolvers
3
+ * Issue #136: Phase 1 - Task 1.2
4
+ *
5
+ * Provides Strategy pattern implementation for resolving resource paths:
6
+ * - Database paths (cm.db, cm-{issueNo}.db)
7
+ * - PID file paths (.commandmate.pid, pids/{issueNo}.pid)
8
+ * - Log file paths (commandmate.log, commandmate-{issueNo}.log)
9
+ *
10
+ * SF-SEC-001: TOCTOU protection via try-catch pattern in validate()
11
+ *
12
+ * @module resource-resolvers
13
+ */
14
+ /**
15
+ * Interface for resource path resolvers
16
+ * Strategy pattern for unified resource path handling
17
+ */
18
+ export interface ResourcePathResolver {
19
+ /**
20
+ * Resolve the path for a resource
21
+ * @param issueNo - Optional issue number for worktree-specific resources
22
+ * @returns Absolute path to the resource
23
+ */
24
+ resolve(issueNo?: number): string;
25
+ /**
26
+ * Validate that a path is within the allowed directory
27
+ * SF-SEC-001: Uses try-catch pattern for TOCTOU protection
28
+ *
29
+ * @param targetPath - Path to validate
30
+ * @returns true if path is valid and within allowed boundaries
31
+ */
32
+ validate(targetPath: string): boolean;
33
+ }
34
+ /**
35
+ * Database path resolver
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const resolver = new DbPathResolver();
40
+ * resolver.resolve(); // ~/.commandmate/data/cm.db
41
+ * resolver.resolve(135); // ~/.commandmate/data/cm-135.db
42
+ * ```
43
+ */
44
+ export declare class DbPathResolver implements ResourcePathResolver {
45
+ resolve(issueNo?: number): string;
46
+ validate(targetPath: string): boolean;
47
+ }
48
+ /**
49
+ * PID file path resolver
50
+ *
51
+ * Maintains backward compatibility:
52
+ * - No issueNo: ~/.commandmate/.commandmate.pid (main server)
53
+ * - With issueNo: ~/.commandmate/pids/{issueNo}.pid (worktree server)
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const resolver = new PidPathResolver();
58
+ * resolver.resolve(); // ~/.commandmate/.commandmate.pid
59
+ * resolver.resolve(135); // ~/.commandmate/pids/135.pid
60
+ * ```
61
+ */
62
+ export declare class PidPathResolver implements ResourcePathResolver {
63
+ resolve(issueNo?: number): string;
64
+ validate(targetPath: string): boolean;
65
+ }
66
+ /**
67
+ * Log file path resolver
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const resolver = new LogPathResolver();
72
+ * resolver.resolve(); // ~/.commandmate/logs/commandmate.log
73
+ * resolver.resolve(135); // ~/.commandmate/logs/commandmate-135.log
74
+ * ```
75
+ */
76
+ export declare class LogPathResolver implements ResourcePathResolver {
77
+ resolve(issueNo?: number): string;
78
+ validate(targetPath: string): boolean;
79
+ }
80
+ /**
81
+ * Factory function for DbPathResolver
82
+ */
83
+ export declare function createDbPathResolver(): DbPathResolver;
84
+ /**
85
+ * Factory function for PidPathResolver
86
+ */
87
+ export declare function createPidPathResolver(): PidPathResolver;
88
+ /**
89
+ * Factory function for LogPathResolver
90
+ */
91
+ export declare function createLogPathResolver(): LogPathResolver;
92
+ //# sourceMappingURL=resource-resolvers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-resolvers.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/resource-resolvers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAElC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;CACvC;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAe,YAAW,oBAAoB;IACzD,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAQjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAqBtC;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,eAAgB,YAAW,oBAAoB;IAC1D,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IASjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAqBtC;AAED;;;;;;;;;GASG;AACH,qBAAa,eAAgB,YAAW,oBAAoB;IAC1D,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IASjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAqBtC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAErD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD"}
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ /**
3
+ * Resource Path Resolvers
4
+ * Issue #136: Phase 1 - Task 1.2
5
+ *
6
+ * Provides Strategy pattern implementation for resolving resource paths:
7
+ * - Database paths (cm.db, cm-{issueNo}.db)
8
+ * - PID file paths (.commandmate.pid, pids/{issueNo}.pid)
9
+ * - Log file paths (commandmate.log, commandmate-{issueNo}.log)
10
+ *
11
+ * SF-SEC-001: TOCTOU protection via try-catch pattern in validate()
12
+ *
13
+ * @module resource-resolvers
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.LogPathResolver = exports.PidPathResolver = exports.DbPathResolver = void 0;
20
+ exports.createDbPathResolver = createDbPathResolver;
21
+ exports.createPidPathResolver = createPidPathResolver;
22
+ exports.createLogPathResolver = createLogPathResolver;
23
+ const fs_1 = require("fs");
24
+ const path_1 = __importDefault(require("path"));
25
+ const install_context_1 = require("./install-context");
26
+ /**
27
+ * Database path resolver
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const resolver = new DbPathResolver();
32
+ * resolver.resolve(); // ~/.commandmate/data/cm.db
33
+ * resolver.resolve(135); // ~/.commandmate/data/cm-135.db
34
+ * ```
35
+ */
36
+ class DbPathResolver {
37
+ resolve(issueNo) {
38
+ const configDir = (0, install_context_1.getConfigDir)();
39
+ if (issueNo !== undefined) {
40
+ return path_1.default.join(configDir, 'data', `cm-${issueNo}.db`);
41
+ }
42
+ return path_1.default.join(configDir, 'data', 'cm.db');
43
+ }
44
+ validate(targetPath) {
45
+ const configDir = (0, install_context_1.getConfigDir)();
46
+ // SF-SEC-001: try-catch for TOCTOU protection
47
+ try {
48
+ const resolved = (0, fs_1.realpathSync)(targetPath);
49
+ return resolved.startsWith(configDir);
50
+ }
51
+ catch (error) {
52
+ if (error.code === 'ENOENT') {
53
+ // New file - validate parent directory
54
+ const parentDir = path_1.default.dirname(targetPath);
55
+ try {
56
+ const resolvedParent = (0, fs_1.realpathSync)(parentDir);
57
+ return resolvedParent.startsWith(configDir);
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ return false;
64
+ }
65
+ }
66
+ }
67
+ exports.DbPathResolver = DbPathResolver;
68
+ /**
69
+ * PID file path resolver
70
+ *
71
+ * Maintains backward compatibility:
72
+ * - No issueNo: ~/.commandmate/.commandmate.pid (main server)
73
+ * - With issueNo: ~/.commandmate/pids/{issueNo}.pid (worktree server)
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const resolver = new PidPathResolver();
78
+ * resolver.resolve(); // ~/.commandmate/.commandmate.pid
79
+ * resolver.resolve(135); // ~/.commandmate/pids/135.pid
80
+ * ```
81
+ */
82
+ class PidPathResolver {
83
+ resolve(issueNo) {
84
+ const configDir = (0, install_context_1.getConfigDir)();
85
+ if (issueNo !== undefined) {
86
+ return path_1.default.join(configDir, 'pids', `${issueNo}.pid`);
87
+ }
88
+ // Backward compatibility: main PID file in config root
89
+ return path_1.default.join(configDir, '.commandmate.pid');
90
+ }
91
+ validate(targetPath) {
92
+ const configDir = (0, install_context_1.getConfigDir)();
93
+ // SF-SEC-001: try-catch for TOCTOU protection
94
+ try {
95
+ const resolved = (0, fs_1.realpathSync)(targetPath);
96
+ return resolved.startsWith(configDir);
97
+ }
98
+ catch (error) {
99
+ if (error.code === 'ENOENT') {
100
+ // New file - validate parent directory
101
+ const parentDir = path_1.default.dirname(targetPath);
102
+ try {
103
+ const resolvedParent = (0, fs_1.realpathSync)(parentDir);
104
+ return resolvedParent.startsWith(configDir);
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
110
+ return false;
111
+ }
112
+ }
113
+ }
114
+ exports.PidPathResolver = PidPathResolver;
115
+ /**
116
+ * Log file path resolver
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * const resolver = new LogPathResolver();
121
+ * resolver.resolve(); // ~/.commandmate/logs/commandmate.log
122
+ * resolver.resolve(135); // ~/.commandmate/logs/commandmate-135.log
123
+ * ```
124
+ */
125
+ class LogPathResolver {
126
+ resolve(issueNo) {
127
+ const configDir = (0, install_context_1.getConfigDir)();
128
+ const logsDir = path_1.default.join(configDir, 'logs');
129
+ if (issueNo !== undefined) {
130
+ return path_1.default.join(logsDir, `commandmate-${issueNo}.log`);
131
+ }
132
+ return path_1.default.join(logsDir, 'commandmate.log');
133
+ }
134
+ validate(targetPath) {
135
+ const configDir = (0, install_context_1.getConfigDir)();
136
+ const logsDir = path_1.default.join(configDir, 'logs');
137
+ // SF-SEC-001: try-catch for TOCTOU protection
138
+ try {
139
+ const resolved = (0, fs_1.realpathSync)(targetPath);
140
+ return resolved.startsWith(logsDir);
141
+ }
142
+ catch (error) {
143
+ if (error.code === 'ENOENT') {
144
+ // New file - validate logs directory exists within config
145
+ try {
146
+ const resolvedLogsDir = (0, fs_1.realpathSync)(logsDir);
147
+ return resolvedLogsDir.startsWith(configDir);
148
+ }
149
+ catch {
150
+ return false;
151
+ }
152
+ }
153
+ return false;
154
+ }
155
+ }
156
+ }
157
+ exports.LogPathResolver = LogPathResolver;
158
+ /**
159
+ * Factory function for DbPathResolver
160
+ */
161
+ function createDbPathResolver() {
162
+ return new DbPathResolver();
163
+ }
164
+ /**
165
+ * Factory function for PidPathResolver
166
+ */
167
+ function createPidPathResolver() {
168
+ return new PidPathResolver();
169
+ }
170
+ /**
171
+ * Factory function for LogPathResolver
172
+ */
173
+ function createLogPathResolver() {
174
+ return new LogPathResolver();
175
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Worktree Detector
3
+ * Issue #136: Phase 1 - Task 1.3
4
+ *
5
+ * Provides utilities for detecting git worktrees and extracting issue numbers.
6
+ * Used to automatically identify which worktree environment the CLI is running in.
7
+ *
8
+ * Security: Uses execFile instead of exec to prevent command injection.
9
+ *
10
+ * @module worktree-detector
11
+ */
12
+ /**
13
+ * Information about a detected worktree
14
+ */
15
+ export interface WorktreeInfo {
16
+ /** Issue number associated with the worktree */
17
+ issueNo: number;
18
+ /** Absolute path to the worktree root */
19
+ path: string;
20
+ /** Current git branch */
21
+ branch: string;
22
+ }
23
+ /**
24
+ * Extract issue number from a worktree directory path
25
+ *
26
+ * @param dirPath - Directory path to check
27
+ * @returns Issue number or null if not a worktree path
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * extractIssueNoFromPath('/home/user/repos/commandmate-issue-135'); // 135
32
+ * extractIssueNoFromPath('/home/user/repos/commandmate'); // null
33
+ * ```
34
+ */
35
+ export declare function extractIssueNoFromPath(dirPath: string): number | null;
36
+ /**
37
+ * Extract issue number from a git branch name
38
+ *
39
+ * @param branchName - Git branch name
40
+ * @returns Issue number or null if not found
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * extractIssueNoFromBranch('feature/135-add-worktree'); // 135
45
+ * extractIssueNoFromBranch('main'); // null
46
+ * ```
47
+ */
48
+ export declare function extractIssueNoFromBranch(branchName: string): number | null;
49
+ /**
50
+ * Check if a directory is inside a git worktree
51
+ *
52
+ * @param dirPath - Directory path to check
53
+ * @returns true if directory is inside a git repository
54
+ */
55
+ export declare function isWorktreeDirectory(dirPath: string): Promise<boolean>;
56
+ /**
57
+ * Detect if the current directory is within a CommandMate worktree
58
+ *
59
+ * Attempts to extract issue number from:
60
+ * 1. Directory path (e.g., commandmate-issue-135)
61
+ * 2. Git branch name (e.g., feature/135-description)
62
+ *
63
+ * @param dirPath - Directory path to check (defaults to cwd)
64
+ * @returns WorktreeInfo if detected, null otherwise
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const info = await detectCurrentWorktree();
69
+ * if (info) {
70
+ * console.log(`Running in worktree for Issue #${info.issueNo}`);
71
+ * }
72
+ * ```
73
+ */
74
+ export declare function detectCurrentWorktree(dirPath?: string): Promise<WorktreeInfo | null>;
75
+ /**
76
+ * Detect worktree from environment or current directory
77
+ * Convenience function that checks CM_ISSUE_NO env var first
78
+ *
79
+ * @returns WorktreeInfo if detected, null otherwise
80
+ */
81
+ export declare function detectWorktreeContext(): Promise<WorktreeInfo | null>;
82
+ //# sourceMappingURL=worktree-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-detector.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/worktree-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAkBD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBrE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc1E;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAY3E;AA6CD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAkC1F;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmB1E"}