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.
- package/.env.example +8 -3
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +11 -11
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route.js +11 -12
- package/.next/server/app/api/external-apps/[id]/route.js +14 -15
- package/.next/server/app/api/external-apps/route.js +12 -13
- package/.next/server/app/api/hooks/claude-done/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/slash-commands.body +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +7 -7
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +12 -13
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +3 -3
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +10 -10
- package/.next/server/chunks/1318.js +4 -4
- package/.next/server/chunks/1528.js +1 -1
- package/.next/server/chunks/7425.js +97 -48
- package/.next/server/chunks/9723.js +1 -1
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/src/middleware.js +2 -2
- package/.next/server/src/middleware.js.map +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-720605c2fb074444.js +1 -0
- package/.next/trace +5 -5
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +6 -4
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +64 -17
- package/dist/cli/commands/status.d.ts +4 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +95 -6
- package/dist/cli/commands/stop.d.ts +2 -0
- package/dist/cli/commands/stop.d.ts.map +1 -1
- package/dist/cli/commands/stop.js +27 -10
- package/dist/cli/index.js +16 -2
- package/dist/cli/types/index.d.ts +20 -0
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/cli/utils/daemon-factory.d.ts +105 -0
- package/dist/cli/utils/daemon-factory.d.ts.map +1 -0
- package/dist/cli/utils/daemon-factory.js +117 -0
- package/dist/cli/utils/daemon.d.ts.map +1 -1
- package/dist/cli/utils/daemon.js +4 -0
- package/dist/cli/utils/env-setup.d.ts +24 -12
- package/dist/cli/utils/env-setup.d.ts.map +1 -1
- package/dist/cli/utils/env-setup.js +64 -43
- package/dist/cli/utils/input-validators.d.ts +103 -0
- package/dist/cli/utils/input-validators.d.ts.map +1 -0
- package/dist/cli/utils/input-validators.js +163 -0
- package/dist/cli/utils/install-context.d.ts +53 -0
- package/dist/cli/utils/install-context.d.ts.map +1 -0
- package/dist/cli/utils/install-context.js +96 -0
- package/dist/cli/utils/pid-manager.d.ts +34 -0
- package/dist/cli/utils/pid-manager.d.ts.map +1 -1
- package/dist/cli/utils/pid-manager.js +43 -0
- package/dist/cli/utils/port-allocator.d.ts +108 -0
- package/dist/cli/utils/port-allocator.d.ts.map +1 -0
- package/dist/cli/utils/port-allocator.js +166 -0
- package/dist/cli/utils/resource-resolvers.d.ts +92 -0
- package/dist/cli/utils/resource-resolvers.d.ts.map +1 -0
- package/dist/cli/utils/resource-resolvers.js +175 -0
- package/dist/cli/utils/worktree-detector.d.ts +82 -0
- package/dist/cli/utils/worktree-detector.d.ts.map +1 -0
- package/dist/cli/utils/worktree-detector.js +221 -0
- package/dist/lib/errors.d.ts +111 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +153 -0
- package/dist/server/server.js +3 -0
- package/dist/server/src/cli/utils/install-context.js +96 -0
- package/dist/server/src/config/system-directories.js +40 -0
- package/dist/server/src/lib/auto-yes-manager.js +325 -0
- package/dist/server/src/lib/auto-yes-resolver.js +34 -0
- package/dist/server/src/lib/cli-patterns.js +6 -1
- package/dist/server/src/lib/db-instance.js +12 -2
- package/dist/server/src/lib/db-migrations.js +68 -1
- package/dist/server/src/lib/db-path-resolver.js +99 -0
- package/dist/server/src/lib/db.js +21 -0
- package/dist/server/src/lib/env.js +52 -3
- package/dist/server/src/lib/worktrees.js +36 -1
- package/dist/server/src/types/external-apps.js +20 -0
- package/package.json +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-aea2d5e7e28955be.js +0 -1
- /package/.next/static/chunks/app/{page-96a8aa2ec30a44e9.js → page-fe35d61f14b90a51.js} +0 -0
- /package/.next/static/{8o5rUyZun0GklIHWDgkmv → gRNW5YXY43KqCKbCdaJoJ}/_buildManifest.js +0 -0
- /package/.next/static/{8o5rUyZun0GklIHWDgkmv → gRNW5YXY43KqCKbCdaJoJ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Worktree Detector
|
|
4
|
+
* Issue #136: Phase 1 - Task 1.3
|
|
5
|
+
*
|
|
6
|
+
* Provides utilities for detecting git worktrees and extracting issue numbers.
|
|
7
|
+
* Used to automatically identify which worktree environment the CLI is running in.
|
|
8
|
+
*
|
|
9
|
+
* Security: Uses execFile instead of exec to prevent command injection.
|
|
10
|
+
*
|
|
11
|
+
* @module worktree-detector
|
|
12
|
+
*/
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.extractIssueNoFromPath = extractIssueNoFromPath;
|
|
18
|
+
exports.extractIssueNoFromBranch = extractIssueNoFromBranch;
|
|
19
|
+
exports.isWorktreeDirectory = isWorktreeDirectory;
|
|
20
|
+
exports.detectCurrentWorktree = detectCurrentWorktree;
|
|
21
|
+
exports.detectWorktreeContext = detectWorktreeContext;
|
|
22
|
+
const child_process_1 = require("child_process");
|
|
23
|
+
const util_1 = require("util");
|
|
24
|
+
const path_1 = __importDefault(require("path"));
|
|
25
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
26
|
+
/**
|
|
27
|
+
* Pattern for extracting issue number from worktree directory name
|
|
28
|
+
* Matches: commandmate-issue-{number}
|
|
29
|
+
*/
|
|
30
|
+
const WORKTREE_PATH_PATTERN = /commandmate-issue-(\d+)/;
|
|
31
|
+
/**
|
|
32
|
+
* Pattern for extracting issue number from branch name
|
|
33
|
+
* Matches:
|
|
34
|
+
* - feature/{number}-description
|
|
35
|
+
* - fix/{number}-description
|
|
36
|
+
* - hotfix/{number}-description
|
|
37
|
+
* - {number}-description (without prefix)
|
|
38
|
+
*/
|
|
39
|
+
const BRANCH_ISSUE_PATTERN = /^(?:feature\/|fix\/|hotfix\/)?(\d+)-/;
|
|
40
|
+
/**
|
|
41
|
+
* Extract issue number from a worktree directory path
|
|
42
|
+
*
|
|
43
|
+
* @param dirPath - Directory path to check
|
|
44
|
+
* @returns Issue number or null if not a worktree path
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* extractIssueNoFromPath('/home/user/repos/commandmate-issue-135'); // 135
|
|
49
|
+
* extractIssueNoFromPath('/home/user/repos/commandmate'); // null
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
function extractIssueNoFromPath(dirPath) {
|
|
53
|
+
// Normalize path and remove trailing slash
|
|
54
|
+
const normalizedPath = path_1.default.normalize(dirPath).replace(/\/$/, '');
|
|
55
|
+
const match = normalizedPath.match(WORKTREE_PATH_PATTERN);
|
|
56
|
+
if (!match) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const issueNo = parseInt(match[1], 10);
|
|
60
|
+
// Validate: must be positive integer
|
|
61
|
+
if (!Number.isInteger(issueNo) || issueNo <= 0) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return issueNo;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract issue number from a git branch name
|
|
68
|
+
*
|
|
69
|
+
* @param branchName - Git branch name
|
|
70
|
+
* @returns Issue number or null if not found
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* extractIssueNoFromBranch('feature/135-add-worktree'); // 135
|
|
75
|
+
* extractIssueNoFromBranch('main'); // null
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
function extractIssueNoFromBranch(branchName) {
|
|
79
|
+
const match = branchName.match(BRANCH_ISSUE_PATTERN);
|
|
80
|
+
if (!match) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const issueNo = parseInt(match[1], 10);
|
|
84
|
+
// Validate: must be positive integer
|
|
85
|
+
if (!Number.isInteger(issueNo) || issueNo <= 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return issueNo;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if a directory is inside a git worktree
|
|
92
|
+
*
|
|
93
|
+
* @param dirPath - Directory path to check
|
|
94
|
+
* @returns true if directory is inside a git repository
|
|
95
|
+
*/
|
|
96
|
+
async function isWorktreeDirectory(dirPath) {
|
|
97
|
+
try {
|
|
98
|
+
// Use execFile for security (no shell interpretation)
|
|
99
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
100
|
+
cwd: dirPath,
|
|
101
|
+
timeout: 5000, // 5 second timeout
|
|
102
|
+
});
|
|
103
|
+
return stdout.trim() === 'true';
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the git repository root for a directory
|
|
111
|
+
*
|
|
112
|
+
* @param dirPath - Directory path
|
|
113
|
+
* @returns Repository root path or null
|
|
114
|
+
*/
|
|
115
|
+
async function getGitRoot(dirPath) {
|
|
116
|
+
try {
|
|
117
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', '--show-toplevel'], {
|
|
118
|
+
cwd: dirPath,
|
|
119
|
+
timeout: 5000,
|
|
120
|
+
});
|
|
121
|
+
return stdout.trim();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get the current git branch name
|
|
129
|
+
*
|
|
130
|
+
* @param dirPath - Directory path within git repository
|
|
131
|
+
* @returns Branch name or null
|
|
132
|
+
*/
|
|
133
|
+
async function getCurrentBranch(dirPath) {
|
|
134
|
+
try {
|
|
135
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
136
|
+
cwd: dirPath,
|
|
137
|
+
timeout: 5000,
|
|
138
|
+
});
|
|
139
|
+
const branch = stdout.trim();
|
|
140
|
+
// Handle detached HEAD
|
|
141
|
+
if (branch === 'HEAD') {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return branch;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Detect if the current directory is within a CommandMate worktree
|
|
152
|
+
*
|
|
153
|
+
* Attempts to extract issue number from:
|
|
154
|
+
* 1. Directory path (e.g., commandmate-issue-135)
|
|
155
|
+
* 2. Git branch name (e.g., feature/135-description)
|
|
156
|
+
*
|
|
157
|
+
* @param dirPath - Directory path to check (defaults to cwd)
|
|
158
|
+
* @returns WorktreeInfo if detected, null otherwise
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const info = await detectCurrentWorktree();
|
|
163
|
+
* if (info) {
|
|
164
|
+
* console.log(`Running in worktree for Issue #${info.issueNo}`);
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
async function detectCurrentWorktree(dirPath) {
|
|
169
|
+
const targetPath = dirPath || process.cwd();
|
|
170
|
+
try {
|
|
171
|
+
// Get git root
|
|
172
|
+
const gitRoot = await getGitRoot(targetPath);
|
|
173
|
+
if (!gitRoot) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
// Get current branch
|
|
177
|
+
const branch = await getCurrentBranch(targetPath);
|
|
178
|
+
// Try to extract issue number from path first
|
|
179
|
+
let issueNo = extractIssueNoFromPath(gitRoot);
|
|
180
|
+
// If not found in path, try branch name
|
|
181
|
+
if (issueNo === null && branch) {
|
|
182
|
+
issueNo = extractIssueNoFromBranch(branch);
|
|
183
|
+
}
|
|
184
|
+
// If no issue number found, this is not a worktree for an issue
|
|
185
|
+
if (issueNo === null) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
issueNo,
|
|
190
|
+
path: gitRoot,
|
|
191
|
+
branch: branch || 'unknown',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Detect worktree from environment or current directory
|
|
200
|
+
* Convenience function that checks CM_ISSUE_NO env var first
|
|
201
|
+
*
|
|
202
|
+
* @returns WorktreeInfo if detected, null otherwise
|
|
203
|
+
*/
|
|
204
|
+
async function detectWorktreeContext() {
|
|
205
|
+
// Check environment variable first
|
|
206
|
+
const envIssueNo = process.env.CM_ISSUE_NO;
|
|
207
|
+
if (envIssueNo) {
|
|
208
|
+
const issueNo = parseInt(envIssueNo, 10);
|
|
209
|
+
if (Number.isInteger(issueNo) && issueNo > 0) {
|
|
210
|
+
const gitRoot = await getGitRoot(process.cwd());
|
|
211
|
+
const branch = await getCurrentBranch(process.cwd());
|
|
212
|
+
return {
|
|
213
|
+
issueNo,
|
|
214
|
+
path: gitRoot || process.cwd(),
|
|
215
|
+
branch: branch || 'unknown',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Fall back to detection
|
|
220
|
+
return detectCurrentWorktree();
|
|
221
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Definitions
|
|
3
|
+
* Issue #136: Phase 1 - Foundation
|
|
4
|
+
*
|
|
5
|
+
* Centralized error handling for the application.
|
|
6
|
+
* SF-SEC-003: Separates client-facing and internal error messages.
|
|
7
|
+
*
|
|
8
|
+
* @module errors
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Standard error codes used throughout the application
|
|
12
|
+
* These codes are safe to expose to clients.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ErrorCode: {
|
|
15
|
+
readonly INVALID_ISSUE_NO: "INVALID_ISSUE_NO";
|
|
16
|
+
readonly ISSUE_NO_OUT_OF_RANGE: "ISSUE_NO_OUT_OF_RANGE";
|
|
17
|
+
readonly INVALID_BRANCH_NAME: "INVALID_BRANCH_NAME";
|
|
18
|
+
readonly BRANCH_NAME_TOO_LONG: "BRANCH_NAME_TOO_LONG";
|
|
19
|
+
readonly INVALID_PORT: "INVALID_PORT";
|
|
20
|
+
readonly PORT_EXHAUSTED: "PORT_EXHAUSTED";
|
|
21
|
+
readonly PORT_IN_USE: "PORT_IN_USE";
|
|
22
|
+
readonly WORKTREE_NOT_FOUND: "WORKTREE_NOT_FOUND";
|
|
23
|
+
readonly WORKTREE_ALREADY_EXISTS: "WORKTREE_ALREADY_EXISTS";
|
|
24
|
+
readonly PID_FILE_EXISTS: "PID_FILE_EXISTS";
|
|
25
|
+
readonly PROCESS_NOT_RUNNING: "PROCESS_NOT_RUNNING";
|
|
26
|
+
readonly PATH_TRAVERSAL: "PATH_TRAVERSAL";
|
|
27
|
+
readonly UNAUTHORIZED: "UNAUTHORIZED";
|
|
28
|
+
readonly FORBIDDEN: "FORBIDDEN";
|
|
29
|
+
readonly DATABASE_ERROR: "DATABASE_ERROR";
|
|
30
|
+
readonly FILESYSTEM_ERROR: "FILESYSTEM_ERROR";
|
|
31
|
+
readonly GIT_ERROR: "GIT_ERROR";
|
|
32
|
+
readonly TIMEOUT: "TIMEOUT";
|
|
33
|
+
readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
|
|
34
|
+
};
|
|
35
|
+
export type ErrorCodeType = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
36
|
+
/**
|
|
37
|
+
* Application-specific error class
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* throw new AppError('INVALID_ISSUE_NO', 'Issue number must be a positive integer', { received: -1 });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare class AppError extends Error {
|
|
45
|
+
/**
|
|
46
|
+
* Error code - safe to expose to clients
|
|
47
|
+
*/
|
|
48
|
+
readonly code: string;
|
|
49
|
+
/**
|
|
50
|
+
* Additional details - may contain sensitive info, log only
|
|
51
|
+
*/
|
|
52
|
+
readonly details?: Record<string, unknown>;
|
|
53
|
+
/**
|
|
54
|
+
* Timestamp when error occurred
|
|
55
|
+
*/
|
|
56
|
+
readonly timestamp: string;
|
|
57
|
+
constructor(code: string, message: string, details?: Record<string, unknown>);
|
|
58
|
+
/**
|
|
59
|
+
* Create a client-safe representation (excludes sensitive details)
|
|
60
|
+
*/
|
|
61
|
+
toClientError(): {
|
|
62
|
+
code: string;
|
|
63
|
+
message: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Create a log-safe representation (includes details for debugging)
|
|
67
|
+
*/
|
|
68
|
+
toLogError(): {
|
|
69
|
+
code: string;
|
|
70
|
+
message: string;
|
|
71
|
+
details?: Record<string, unknown>;
|
|
72
|
+
timestamp: string;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Factory function to create AppError
|
|
77
|
+
*
|
|
78
|
+
* @param code - Error code from ErrorCode enum
|
|
79
|
+
* @param message - Human-readable error message
|
|
80
|
+
* @param details - Optional additional details (logged, not sent to client)
|
|
81
|
+
* @returns AppError instance
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* throw createAppError(ErrorCode.PORT_EXHAUSTED, 'No available ports in range', { range: [3001, 3100] });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function createAppError(code: string, message: string, details?: Record<string, unknown>): AppError;
|
|
89
|
+
/**
|
|
90
|
+
* Type guard to check if an error is an AppError
|
|
91
|
+
*
|
|
92
|
+
* @param error - Error to check
|
|
93
|
+
* @returns true if error is an AppError
|
|
94
|
+
*/
|
|
95
|
+
export declare function isAppError(error: unknown): error is AppError;
|
|
96
|
+
/**
|
|
97
|
+
* Wrap unknown error into AppError
|
|
98
|
+
*
|
|
99
|
+
* @param error - Unknown error
|
|
100
|
+
* @param defaultCode - Default error code if error is not AppError
|
|
101
|
+
* @returns AppError instance
|
|
102
|
+
*/
|
|
103
|
+
export declare function wrapError(error: unknown, defaultCode?: string): AppError;
|
|
104
|
+
/**
|
|
105
|
+
* Get error message from unknown error
|
|
106
|
+
*
|
|
107
|
+
* @param error - Unknown error
|
|
108
|
+
* @returns Error message string
|
|
109
|
+
*/
|
|
110
|
+
export declare function getErrorMessage(error: unknown): string;
|
|
111
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;CA6BZ,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEvE;;;;;;;GAOG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE3C;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAGzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAcnC;;OAEG;IACH,aAAa,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAOlD;;OAEG;IACH,UAAU,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;CAQtG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,QAAQ,CAEV;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,GAAE,MAAgC,GAAG,QAAQ,CAUjG;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKtD"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error Definitions
|
|
4
|
+
* Issue #136: Phase 1 - Foundation
|
|
5
|
+
*
|
|
6
|
+
* Centralized error handling for the application.
|
|
7
|
+
* SF-SEC-003: Separates client-facing and internal error messages.
|
|
8
|
+
*
|
|
9
|
+
* @module errors
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AppError = exports.ErrorCode = void 0;
|
|
13
|
+
exports.createAppError = createAppError;
|
|
14
|
+
exports.isAppError = isAppError;
|
|
15
|
+
exports.wrapError = wrapError;
|
|
16
|
+
exports.getErrorMessage = getErrorMessage;
|
|
17
|
+
/**
|
|
18
|
+
* Standard error codes used throughout the application
|
|
19
|
+
* These codes are safe to expose to clients.
|
|
20
|
+
*/
|
|
21
|
+
exports.ErrorCode = {
|
|
22
|
+
// Input validation errors
|
|
23
|
+
INVALID_ISSUE_NO: 'INVALID_ISSUE_NO',
|
|
24
|
+
ISSUE_NO_OUT_OF_RANGE: 'ISSUE_NO_OUT_OF_RANGE',
|
|
25
|
+
INVALID_BRANCH_NAME: 'INVALID_BRANCH_NAME',
|
|
26
|
+
BRANCH_NAME_TOO_LONG: 'BRANCH_NAME_TOO_LONG',
|
|
27
|
+
INVALID_PORT: 'INVALID_PORT',
|
|
28
|
+
// Resource errors
|
|
29
|
+
PORT_EXHAUSTED: 'PORT_EXHAUSTED',
|
|
30
|
+
PORT_IN_USE: 'PORT_IN_USE',
|
|
31
|
+
WORKTREE_NOT_FOUND: 'WORKTREE_NOT_FOUND',
|
|
32
|
+
WORKTREE_ALREADY_EXISTS: 'WORKTREE_ALREADY_EXISTS',
|
|
33
|
+
PID_FILE_EXISTS: 'PID_FILE_EXISTS',
|
|
34
|
+
PROCESS_NOT_RUNNING: 'PROCESS_NOT_RUNNING',
|
|
35
|
+
// Security errors
|
|
36
|
+
PATH_TRAVERSAL: 'PATH_TRAVERSAL',
|
|
37
|
+
UNAUTHORIZED: 'UNAUTHORIZED',
|
|
38
|
+
FORBIDDEN: 'FORBIDDEN',
|
|
39
|
+
// System errors
|
|
40
|
+
DATABASE_ERROR: 'DATABASE_ERROR',
|
|
41
|
+
FILESYSTEM_ERROR: 'FILESYSTEM_ERROR',
|
|
42
|
+
GIT_ERROR: 'GIT_ERROR',
|
|
43
|
+
TIMEOUT: 'TIMEOUT',
|
|
44
|
+
// Generic errors
|
|
45
|
+
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Application-specific error class
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* throw new AppError('INVALID_ISSUE_NO', 'Issue number must be a positive integer', { received: -1 });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
class AppError extends Error {
|
|
56
|
+
/**
|
|
57
|
+
* Error code - safe to expose to clients
|
|
58
|
+
*/
|
|
59
|
+
code;
|
|
60
|
+
/**
|
|
61
|
+
* Additional details - may contain sensitive info, log only
|
|
62
|
+
*/
|
|
63
|
+
details;
|
|
64
|
+
/**
|
|
65
|
+
* Timestamp when error occurred
|
|
66
|
+
*/
|
|
67
|
+
timestamp;
|
|
68
|
+
constructor(code, message, details) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = 'AppError';
|
|
71
|
+
this.code = code;
|
|
72
|
+
this.details = details;
|
|
73
|
+
this.timestamp = new Date().toISOString();
|
|
74
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
75
|
+
if (Error.captureStackTrace) {
|
|
76
|
+
Error.captureStackTrace(this, AppError);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Create a client-safe representation (excludes sensitive details)
|
|
81
|
+
*/
|
|
82
|
+
toClientError() {
|
|
83
|
+
return {
|
|
84
|
+
code: this.code,
|
|
85
|
+
message: this.message,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Create a log-safe representation (includes details for debugging)
|
|
90
|
+
*/
|
|
91
|
+
toLogError() {
|
|
92
|
+
return {
|
|
93
|
+
code: this.code,
|
|
94
|
+
message: this.message,
|
|
95
|
+
details: this.details,
|
|
96
|
+
timestamp: this.timestamp,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.AppError = AppError;
|
|
101
|
+
/**
|
|
102
|
+
* Factory function to create AppError
|
|
103
|
+
*
|
|
104
|
+
* @param code - Error code from ErrorCode enum
|
|
105
|
+
* @param message - Human-readable error message
|
|
106
|
+
* @param details - Optional additional details (logged, not sent to client)
|
|
107
|
+
* @returns AppError instance
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* throw createAppError(ErrorCode.PORT_EXHAUSTED, 'No available ports in range', { range: [3001, 3100] });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
function createAppError(code, message, details) {
|
|
115
|
+
return new AppError(code, message, details);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Type guard to check if an error is an AppError
|
|
119
|
+
*
|
|
120
|
+
* @param error - Error to check
|
|
121
|
+
* @returns true if error is an AppError
|
|
122
|
+
*/
|
|
123
|
+
function isAppError(error) {
|
|
124
|
+
return error instanceof AppError;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Wrap unknown error into AppError
|
|
128
|
+
*
|
|
129
|
+
* @param error - Unknown error
|
|
130
|
+
* @param defaultCode - Default error code if error is not AppError
|
|
131
|
+
* @returns AppError instance
|
|
132
|
+
*/
|
|
133
|
+
function wrapError(error, defaultCode = exports.ErrorCode.UNKNOWN_ERROR) {
|
|
134
|
+
if (isAppError(error)) {
|
|
135
|
+
return error;
|
|
136
|
+
}
|
|
137
|
+
if (error instanceof Error) {
|
|
138
|
+
return new AppError(defaultCode, error.message, { originalError: error.name });
|
|
139
|
+
}
|
|
140
|
+
return new AppError(defaultCode, String(error));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get error message from unknown error
|
|
144
|
+
*
|
|
145
|
+
* @param error - Unknown error
|
|
146
|
+
* @returns Error message string
|
|
147
|
+
*/
|
|
148
|
+
function getErrorMessage(error) {
|
|
149
|
+
if (error instanceof Error) {
|
|
150
|
+
return error.message;
|
|
151
|
+
}
|
|
152
|
+
return String(error);
|
|
153
|
+
}
|
package/dist/server/server.js
CHANGED
|
@@ -34,6 +34,7 @@ const ws_server_1 = require("./src/lib/ws-server");
|
|
|
34
34
|
const worktrees_1 = require("./src/lib/worktrees");
|
|
35
35
|
const db_instance_1 = require("./src/lib/db-instance");
|
|
36
36
|
const response_poller_1 = require("./src/lib/response-poller");
|
|
37
|
+
const auto_yes_manager_1 = require("./src/lib/auto-yes-manager");
|
|
37
38
|
const db_migrations_1 = require("./src/lib/db-migrations");
|
|
38
39
|
const env_1 = require("./src/lib/env");
|
|
39
40
|
const dev = process.env.NODE_ENV !== 'production';
|
|
@@ -104,6 +105,8 @@ app.prepare().then(() => {
|
|
|
104
105
|
console.log(`${signal} received: shutting down...`);
|
|
105
106
|
// Stop polling first
|
|
106
107
|
(0, response_poller_1.stopAllPolling)();
|
|
108
|
+
// Issue #138: Stop all auto-yes pollers
|
|
109
|
+
(0, auto_yes_manager_1.stopAllAutoYesPolling)();
|
|
107
110
|
// Close WebSocket connections immediately (don't wait)
|
|
108
111
|
(0, ws_server_1.closeWebSocket)();
|
|
109
112
|
// Force exit after 3 seconds if graceful shutdown fails
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Install Context Utility
|
|
4
|
+
* Issue #136: DRY refactoring - extract common install detection functions
|
|
5
|
+
*
|
|
6
|
+
* This module provides centralized functions for detecting the install type
|
|
7
|
+
* (global vs local npm install) and resolving configuration directories.
|
|
8
|
+
*
|
|
9
|
+
* Extracted from env-setup.ts to solve circular import issues and follow DRY principle.
|
|
10
|
+
*
|
|
11
|
+
* @module install-context
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.isGlobalInstall = isGlobalInstall;
|
|
15
|
+
exports.getConfigDir = getConfigDir;
|
|
16
|
+
exports.ensureConfigDir = ensureConfigDir;
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
const path_1 = require("path");
|
|
19
|
+
const os_1 = require("os");
|
|
20
|
+
/**
|
|
21
|
+
* Check if running as global npm package
|
|
22
|
+
* Issue #119: Determine .env location based on install type
|
|
23
|
+
* Issue #136: Extracted to avoid circular imports
|
|
24
|
+
*
|
|
25
|
+
* @returns true if running as global npm package
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* if (isGlobalInstall()) {
|
|
30
|
+
* // Use ~/.commandmate for config
|
|
31
|
+
* } else {
|
|
32
|
+
* // Use current working directory
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function isGlobalInstall() {
|
|
37
|
+
// Check if running from global node_modules
|
|
38
|
+
// Global installs typically have paths like:
|
|
39
|
+
// - /usr/local/lib/node_modules/
|
|
40
|
+
// - /Users/xxx/.npm-global/lib/node_modules/
|
|
41
|
+
// - C:\Users\xxx\AppData\Roaming\npm\node_modules\
|
|
42
|
+
const currentPath = (0, path_1.dirname)(__dirname);
|
|
43
|
+
return (currentPath.includes('/lib/node_modules/') ||
|
|
44
|
+
currentPath.includes('\\node_modules\\') ||
|
|
45
|
+
currentPath.includes('/node_modules/commandmate'));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the config directory path
|
|
49
|
+
* Issue #119: Returns ~/.commandmate for global, cwd for local
|
|
50
|
+
* Issue #125: Added symlink resolution for security (path traversal protection)
|
|
51
|
+
* Issue #136: Extracted to avoid circular imports
|
|
52
|
+
*
|
|
53
|
+
* @returns Path to config directory (absolute, with symlinks resolved)
|
|
54
|
+
* @throws Error if config directory resolves outside home directory (for global install)
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const configDir = getConfigDir();
|
|
59
|
+
* // Global: /Users/username/.commandmate
|
|
60
|
+
* // Local: /path/to/project (resolved from symlinks)
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
function getConfigDir() {
|
|
64
|
+
if (isGlobalInstall()) {
|
|
65
|
+
const configDir = (0, path_1.join)((0, os_1.homedir)(), '.commandmate');
|
|
66
|
+
// Verify config directory is within home directory (security check)
|
|
67
|
+
// Only validate if the directory exists (it may not exist yet during init)
|
|
68
|
+
if ((0, fs_1.existsSync)(configDir)) {
|
|
69
|
+
const realPath = (0, fs_1.realpathSync)(configDir);
|
|
70
|
+
const realHome = (0, fs_1.realpathSync)((0, os_1.homedir)());
|
|
71
|
+
if (!realPath.startsWith(realHome)) {
|
|
72
|
+
throw new Error(`Security error: Config directory ${configDir} is outside home directory`);
|
|
73
|
+
}
|
|
74
|
+
return realPath;
|
|
75
|
+
}
|
|
76
|
+
return configDir;
|
|
77
|
+
}
|
|
78
|
+
// Local install - resolve symlinks in cwd
|
|
79
|
+
const cwd = process.cwd();
|
|
80
|
+
return (0, fs_1.realpathSync)(cwd);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Ensure the config directory exists with proper permissions
|
|
84
|
+
* Issue #136: Utility for creating config directory if needed
|
|
85
|
+
*
|
|
86
|
+
* @returns Path to config directory (created if needed)
|
|
87
|
+
*/
|
|
88
|
+
function ensureConfigDir() {
|
|
89
|
+
const configDir = isGlobalInstall()
|
|
90
|
+
? (0, path_1.join)((0, os_1.homedir)(), '.commandmate')
|
|
91
|
+
: process.cwd();
|
|
92
|
+
if (!(0, fs_1.existsSync)(configDir)) {
|
|
93
|
+
(0, fs_1.mkdirSync)(configDir, { recursive: true, mode: 0o700 });
|
|
94
|
+
}
|
|
95
|
+
return getConfigDir();
|
|
96
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* System Directories Configuration
|
|
4
|
+
* Issue #135: DB path resolution logic fix
|
|
5
|
+
*
|
|
6
|
+
* Centralized list of system directories that are not allowed for DB storage.
|
|
7
|
+
* This supports security measures SEC-001, SEC-002, SEC-005.
|
|
8
|
+
*
|
|
9
|
+
* @module system-directories
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.SYSTEM_DIRECTORIES = void 0;
|
|
13
|
+
exports.isSystemDirectory = isSystemDirectory;
|
|
14
|
+
/**
|
|
15
|
+
* System directories that are not allowed for DB storage
|
|
16
|
+
*
|
|
17
|
+
* SEC-001: System directory protection
|
|
18
|
+
* These directories are protected to prevent writing database files
|
|
19
|
+
* to critical system paths which could cause security issues.
|
|
20
|
+
*/
|
|
21
|
+
exports.SYSTEM_DIRECTORIES = [
|
|
22
|
+
'/etc',
|
|
23
|
+
'/usr',
|
|
24
|
+
'/bin',
|
|
25
|
+
'/sbin',
|
|
26
|
+
'/var',
|
|
27
|
+
'/tmp',
|
|
28
|
+
'/dev',
|
|
29
|
+
'/sys',
|
|
30
|
+
'/proc',
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Check if a path is within a system directory
|
|
34
|
+
*
|
|
35
|
+
* @param resolvedPath - The resolved absolute path to check
|
|
36
|
+
* @returns true if the path is within a system directory
|
|
37
|
+
*/
|
|
38
|
+
function isSystemDirectory(resolvedPath) {
|
|
39
|
+
return exports.SYSTEM_DIRECTORIES.some((dir) => resolvedPath.startsWith(dir));
|
|
40
|
+
}
|