patchwarden 0.6.4 → 1.5.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.
- package/README.en.md +54 -20
- package/README.md +48 -21
- package/dist/agents/agentRouter.d.ts +40 -0
- package/dist/agents/agentRouter.js +95 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +6 -2
- package/dist/controlCenter.js +212 -3
- package/dist/direct/directSessionStore.d.ts +2 -0
- package/dist/direct/directVerification.js +7 -0
- package/dist/doctor.js +32 -1
- package/dist/goal/acceptanceEngine.d.ts +62 -0
- package/dist/goal/acceptanceEngine.js +103 -0
- package/dist/goal/acceptanceTemplate.d.ts +10 -0
- package/dist/goal/acceptanceTemplate.js +104 -0
- package/dist/goal/goalGraph.d.ts +58 -0
- package/dist/goal/goalGraph.js +189 -0
- package/dist/goal/goalProgress.d.ts +81 -0
- package/dist/goal/goalProgress.js +216 -0
- package/dist/goal/goalStatus.d.ts +60 -0
- package/dist/goal/goalStatus.js +137 -0
- package/dist/goal/goalStore.d.ts +79 -0
- package/dist/goal/goalStore.js +211 -0
- package/dist/goal/handoffExport.d.ts +34 -0
- package/dist/goal/handoffExport.js +183 -0
- package/dist/goal/subgoalSync.d.ts +40 -0
- package/dist/goal/subgoalSync.js +72 -0
- package/dist/goal/worktreeManager.d.ts +96 -0
- package/dist/goal/worktreeManager.js +292 -0
- package/dist/logging.d.ts +44 -0
- package/dist/logging.js +68 -0
- package/dist/policy/projectPolicy.d.ts +55 -0
- package/dist/policy/projectPolicy.js +286 -0
- package/dist/release/releaseGate.d.ts +99 -0
- package/dist/release/releaseGate.js +475 -0
- package/dist/runner/postTaskCleanup.d.ts +13 -0
- package/dist/runner/postTaskCleanup.js +147 -0
- package/dist/runner/runTask.js +50 -10
- package/dist/security/discoveryTokenStore.d.ts +62 -0
- package/dist/security/discoveryTokenStore.js +100 -0
- package/dist/security/toolInvocationGuard.d.ts +35 -0
- package/dist/security/toolInvocationGuard.js +153 -0
- package/dist/smoke-test.js +18 -10
- package/dist/taskRuntime.d.ts +17 -0
- package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
- package/dist/test/unit/acceptance-engine.test.js +228 -0
- package/dist/test/unit/agent-router.test.d.ts +1 -0
- package/dist/test/unit/agent-router.test.js +287 -0
- package/dist/test/unit/audit-checks.test.d.ts +1 -0
- package/dist/test/unit/audit-checks.test.js +350 -0
- package/dist/test/unit/diagnose-task.test.d.ts +1 -0
- package/dist/test/unit/diagnose-task.test.js +457 -0
- package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
- package/dist/test/unit/discovery-token-store.test.js +139 -0
- package/dist/test/unit/evidence-pack.test.d.ts +1 -0
- package/dist/test/unit/evidence-pack.test.js +130 -0
- package/dist/test/unit/goal-graph.test.d.ts +1 -0
- package/dist/test/unit/goal-graph.test.js +298 -0
- package/dist/test/unit/goal-progress.test.d.ts +1 -0
- package/dist/test/unit/goal-progress.test.js +381 -0
- package/dist/test/unit/goal-status.test.d.ts +1 -0
- package/dist/test/unit/goal-status.test.js +215 -0
- package/dist/test/unit/goal-store.test.d.ts +1 -0
- package/dist/test/unit/goal-store.test.js +253 -0
- package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
- package/dist/test/unit/goal-subgoal-task.test.js +55 -0
- package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
- package/dist/test/unit/goal-tools-registry.test.js +190 -0
- package/dist/test/unit/handoff-export.test.d.ts +1 -0
- package/dist/test/unit/handoff-export.test.js +263 -0
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
- package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
- package/dist/test/unit/logging.test.js +127 -5
- package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
- package/dist/test/unit/post-task-cleanup.test.js +48 -0
- package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
- package/dist/test/unit/project-policy-release-mode.test.js +125 -0
- package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
- package/dist/test/unit/reconcile-tasks.test.js +456 -0
- package/dist/test/unit/release-gate.test.d.ts +1 -0
- package/dist/test/unit/release-gate.test.js +242 -0
- package/dist/test/unit/run-task-loop.test.d.ts +1 -0
- package/dist/test/unit/run-task-loop.test.js +380 -0
- package/dist/test/unit/safe-views.test.d.ts +1 -0
- package/dist/test/unit/safe-views.test.js +171 -0
- package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
- package/dist/test/unit/schema-drift-check.test.js +176 -0
- package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
- package/dist/test/unit/subgoal-sync.test.js +183 -0
- package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
- package/dist/test/unit/tool-invocation-guard.test.js +432 -0
- package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
- package/dist/test/unit/tool-usage-stats.test.js +300 -0
- package/dist/test/unit/toolSearch.test.d.ts +1 -0
- package/dist/test/unit/toolSearch.test.js +571 -0
- package/dist/test/unit/worktree-manager.test.d.ts +1 -0
- package/dist/test/unit/worktree-manager.test.js +176 -0
- package/dist/tools/auditTask.d.ts +103 -1
- package/dist/tools/auditTask.js +461 -8
- package/dist/tools/cancelTask.js +1 -1
- package/dist/tools/checkReleaseGate.d.ts +21 -0
- package/dist/tools/checkReleaseGate.js +22 -0
- package/dist/tools/createTask.d.ts +18 -2
- package/dist/tools/createTask.js +29 -1
- package/dist/tools/diagnoseTask.d.ts +45 -0
- package/dist/tools/diagnoseTask.js +347 -0
- package/dist/tools/discardWorktree.d.ts +24 -0
- package/dist/tools/discardWorktree.js +27 -0
- package/dist/tools/discoverTools.d.ts +11 -0
- package/dist/tools/discoverTools.js +39 -0
- package/dist/tools/evidencePack.d.ts +39 -0
- package/dist/tools/evidencePack.js +168 -0
- package/dist/tools/explainTool.d.ts +11 -0
- package/dist/tools/explainTool.js +21 -0
- package/dist/tools/getTaskSummary.js +2 -1
- package/dist/tools/goalSubgoalTask.d.ts +51 -0
- package/dist/tools/goalSubgoalTask.js +110 -0
- package/dist/tools/healthCheck.d.ts +1 -0
- package/dist/tools/healthCheck.js +5 -1
- package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
- package/dist/tools/invokeDiscoveredTool.js +112 -0
- package/dist/tools/listTasks.d.ts +3 -1
- package/dist/tools/listTasks.js +14 -1
- package/dist/tools/mergeWorktree.d.ts +24 -0
- package/dist/tools/mergeWorktree.js +27 -0
- package/dist/tools/recommendAgentForTask.d.ts +19 -0
- package/dist/tools/recommendAgentForTask.js +56 -0
- package/dist/tools/reconcileTasks.d.ts +41 -0
- package/dist/tools/reconcileTasks.js +352 -0
- package/dist/tools/registry.js +926 -2
- package/dist/tools/releaseMode.d.ts +50 -0
- package/dist/tools/releaseMode.js +370 -0
- package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
- package/dist/tools/runDirectVerificationBundle.js +64 -0
- package/dist/tools/runTaskLoop.d.ts +57 -0
- package/dist/tools/runTaskLoop.js +417 -0
- package/dist/tools/runVerification.d.ts +4 -0
- package/dist/tools/runVerification.js +4 -0
- package/dist/tools/safeStatus.d.ts +31 -1
- package/dist/tools/safeStatus.js +43 -1
- package/dist/tools/safeViews.d.ts +262 -0
- package/dist/tools/safeViews.js +252 -0
- package/dist/tools/schemaDriftCheck.d.ts +46 -0
- package/dist/tools/schemaDriftCheck.js +80 -0
- package/dist/tools/taskLineage.d.ts +91 -0
- package/dist/tools/taskLineage.js +175 -0
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +39 -11
- package/dist/tools/toolRegistry.d.ts +61 -0
- package/dist/tools/toolRegistry.js +834 -0
- package/dist/tools/toolSearch.d.ts +110 -0
- package/dist/tools/toolSearch.js +331 -0
- package/dist/tools/toolUsageStats.d.ts +41 -0
- package/dist/tools/toolUsageStats.js +116 -0
- package/dist/tools/waitForTask.js +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +29 -0
- package/dist/watcherStatus.js +92 -1
- package/docs/chatgpt-usage.md +31 -0
- package/docs/control-center/README.md +9 -0
- package/package.json +3 -2
- package/scripts/checks/control-center-smoke.js +87 -0
- package/scripts/checks/control-smoke.js +2 -2
- package/scripts/checks/doctor-smoke.js +1 -0
- package/scripts/checks/lifecycle-smoke.js +13 -9
- package/scripts/checks/mcp-manifest-check.js +65 -6
- package/scripts/checks/mcp-smoke.js +60 -9
- package/scripts/checks/watcher-supervisor-smoke.js +2 -2
- package/src/agents/agentRouter.ts +149 -0
- package/src/config.ts +9 -3
- package/src/controlCenter.ts +215 -4
- package/src/direct/directSessionStore.ts +2 -0
- package/src/direct/directVerification.ts +7 -0
- package/src/doctor.ts +36 -1
- package/src/goal/acceptanceEngine.ts +160 -0
- package/src/goal/acceptanceTemplate.ts +121 -0
- package/src/goal/goalGraph.ts +225 -0
- package/src/goal/goalProgress.ts +301 -0
- package/src/goal/goalStatus.ts +234 -0
- package/src/goal/goalStore.ts +306 -0
- package/src/goal/handoffExport.ts +211 -0
- package/src/goal/subgoalSync.ts +82 -0
- package/src/goal/worktreeManager.ts +404 -0
- package/src/logging.ts +91 -0
- package/src/policy/projectPolicy.ts +344 -0
- package/src/release/releaseGate.ts +567 -0
- package/src/runner/postTaskCleanup.ts +154 -0
- package/src/runner/runTask.ts +49 -10
- package/src/security/discoveryTokenStore.ts +157 -0
- package/src/security/toolInvocationGuard.ts +251 -0
- package/src/smoke-test.ts +15 -7
- package/src/taskRuntime.ts +17 -0
- package/src/test/unit/acceptance-engine.test.ts +261 -0
- package/src/test/unit/agent-router.test.ts +342 -0
- package/src/test/unit/audit-checks.test.ts +567 -0
- package/src/test/unit/diagnose-task.test.ts +544 -0
- package/src/test/unit/discovery-token-store.test.ts +181 -0
- package/src/test/unit/evidence-pack.test.ts +142 -0
- package/src/test/unit/goal-graph.test.ts +347 -0
- package/src/test/unit/goal-progress.test.ts +538 -0
- package/src/test/unit/goal-status.test.ts +270 -0
- package/src/test/unit/goal-store.test.ts +318 -0
- package/src/test/unit/goal-subgoal-task.test.ts +72 -0
- package/src/test/unit/goal-tools-registry.test.ts +243 -0
- package/src/test/unit/handoff-export.test.ts +295 -0
- package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
- package/src/test/unit/logging.test.ts +177 -5
- package/src/test/unit/post-task-cleanup.test.ts +53 -0
- package/src/test/unit/project-policy-release-mode.test.ts +156 -0
- package/src/test/unit/reconcile-tasks.test.ts +551 -0
- package/src/test/unit/release-gate.test.ts +314 -0
- package/src/test/unit/run-task-loop.test.ts +425 -0
- package/src/test/unit/safe-views.test.ts +184 -0
- package/src/test/unit/schema-drift-check.test.ts +260 -0
- package/src/test/unit/subgoal-sync.test.ts +236 -0
- package/src/test/unit/tool-invocation-guard.test.ts +542 -0
- package/src/test/unit/tool-usage-stats.test.ts +384 -0
- package/src/test/unit/toolSearch.test.ts +756 -0
- package/src/test/unit/worktree-manager.test.ts +247 -0
- package/src/tools/auditTask.ts +831 -402
- package/src/tools/cancelTask.ts +1 -1
- package/src/tools/checkReleaseGate.ts +45 -0
- package/src/tools/createTask.ts +64 -6
- package/src/tools/diagnoseTask.ts +460 -0
- package/src/tools/discardWorktree.ts +42 -0
- package/src/tools/discoverTools.ts +51 -0
- package/src/tools/evidencePack.ts +205 -0
- package/src/tools/explainTool.ts +26 -0
- package/src/tools/getTaskSummary.ts +2 -1
- package/src/tools/goalSubgoalTask.ts +170 -0
- package/src/tools/healthCheck.ts +4 -1
- package/src/tools/invokeDiscoveredTool.ts +163 -0
- package/src/tools/listTasks.ts +16 -2
- package/src/tools/listWorkspace.ts +71 -71
- package/src/tools/mergeWorktree.ts +42 -0
- package/src/tools/recommendAgentForTask.ts +79 -0
- package/src/tools/reconcileTasks.ts +464 -0
- package/src/tools/registry.ts +1093 -73
- package/src/tools/releaseMode.ts +450 -0
- package/src/tools/runDirectVerificationBundle.ts +98 -0
- package/src/tools/runTaskLoop.ts +526 -0
- package/src/tools/runVerification.ts +8 -0
- package/src/tools/safeStatus.ts +73 -2
- package/src/tools/safeViews.ts +273 -0
- package/src/tools/schemaDriftCheck.ts +120 -0
- package/src/tools/taskLineage.ts +300 -0
- package/src/tools/toolCatalog.ts +40 -11
- package/src/tools/toolRegistry.ts +896 -0
- package/src/tools/toolSearch.ts +464 -0
- package/src/tools/toolUsageStats.ts +151 -0
- package/src/tools/waitForTask.ts +1 -0
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +97 -1
- package/ui/pages/dashboard.html +143 -2
package/src/tools/listTasks.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { resolve, join } from "node:path";
|
|
|
3
3
|
import { getTasksDir, getPlansDir, getConfig } from "../config.js";
|
|
4
4
|
import { guardPath } from "../security/pathGuard.js";
|
|
5
5
|
import { readTaskRuntime } from "../taskRuntime.js";
|
|
6
|
-
import type { TaskPhase, TaskStatus } from "./createTask.js";
|
|
6
|
+
import type { TaskPhase, TaskStatus, AcceptanceStatus } from "./createTask.js";
|
|
7
7
|
import {
|
|
8
8
|
derivePendingReason,
|
|
9
9
|
readWatcherStatus,
|
|
@@ -19,6 +19,7 @@ export interface TaskEntry {
|
|
|
19
19
|
agent: string;
|
|
20
20
|
status: TaskStatus;
|
|
21
21
|
phase: TaskPhase;
|
|
22
|
+
acceptance_status: AcceptanceStatus;
|
|
22
23
|
created_at: string;
|
|
23
24
|
updated_at: string;
|
|
24
25
|
workspace_root: string;
|
|
@@ -38,6 +39,7 @@ export interface ListTasksInput {
|
|
|
38
39
|
status?: string;
|
|
39
40
|
repo_path?: string;
|
|
40
41
|
active_only?: boolean;
|
|
42
|
+
acceptance_status?: string;
|
|
41
43
|
limit?: number;
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -54,6 +56,7 @@ export function listTasks(input?: ListTasksInput): ListTasksOutput {
|
|
|
54
56
|
const plansDir = getPlansDir(config);
|
|
55
57
|
const limit = input?.limit && input.limit > 0 ? Math.min(input.limit, 100) : 20;
|
|
56
58
|
const filterStatus = input?.status || null;
|
|
59
|
+
const filterAcceptance = input?.acceptance_status || null;
|
|
57
60
|
const filterRepo = input?.repo_path?.trim().replace(/\\/g, "/") || null;
|
|
58
61
|
const watcher = readWatcherStatus(config);
|
|
59
62
|
|
|
@@ -88,7 +91,13 @@ export function listTasks(input?: ListTasksInput): ListTasksOutput {
|
|
|
88
91
|
const data = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
89
92
|
const runtime = readTaskRuntime(taskDir);
|
|
90
93
|
if (filterStatus && data.status !== filterStatus) continue;
|
|
91
|
-
if (
|
|
94
|
+
if (filterAcceptance) {
|
|
95
|
+
const taskAcceptance = data.status === "done_by_agent"
|
|
96
|
+
? (typeof data.acceptance_status === "string" ? data.acceptance_status : "pending")
|
|
97
|
+
: null;
|
|
98
|
+
if (taskAcceptance !== filterAcceptance) continue;
|
|
99
|
+
}
|
|
100
|
+
if (input?.active_only && !["pending", "running", "collecting_artifacts"].includes(String(data.status))) continue;
|
|
92
101
|
const normalizedRepo = String(data.repo_path || ".").replace(/\\/g, "/");
|
|
93
102
|
const normalizedResolvedRepo = String(data.resolved_repo_path || "").replace(/\\/g, "/");
|
|
94
103
|
if (filterRepo && normalizedRepo !== filterRepo && normalizedResolvedRepo !== filterRepo) continue;
|
|
@@ -109,6 +118,10 @@ export function listTasks(input?: ListTasksInput): ListTasksOutput {
|
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
const phase = runtime.phase || data.phase || "queued";
|
|
121
|
+
const VALID_ACCEPTANCE = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
|
|
122
|
+
const acceptanceStatus: AcceptanceStatus = data.status === "done_by_agent"
|
|
123
|
+
? (typeof data.acceptance_status === "string" && VALID_ACCEPTANCE.includes(data.acceptance_status) ? data.acceptance_status : "pending")
|
|
124
|
+
: null;
|
|
112
125
|
tasks.push({
|
|
113
126
|
task_id: taskId,
|
|
114
127
|
plan_id: data.plan_id || "",
|
|
@@ -116,6 +129,7 @@ export function listTasks(input?: ListTasksInput): ListTasksOutput {
|
|
|
116
129
|
agent: data.agent || "",
|
|
117
130
|
status: data.status || "pending",
|
|
118
131
|
phase,
|
|
132
|
+
acceptance_status: acceptanceStatus,
|
|
119
133
|
created_at: data.created_at || "",
|
|
120
134
|
updated_at: data.updated_at || "",
|
|
121
135
|
workspace_root: data.workspace_root || config.workspaceRoot,
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
import { readdirSync, statSync, existsSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { getConfig } from "../config.js";
|
|
4
|
-
import { guardPath } from "../security/pathGuard.js";
|
|
5
|
-
import { guardSensitivePath } from "../security/sensitiveGuard.js";
|
|
6
|
-
|
|
7
|
-
export interface ListEntry {
|
|
8
|
-
name: string;
|
|
9
|
-
type: "file" | "directory";
|
|
10
|
-
size?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ListWorkspaceOutput {
|
|
14
|
-
path: string;
|
|
15
|
-
entries: ListEntry[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const MAX_LIST_FILES = 200;
|
|
19
|
-
|
|
20
|
-
export function listWorkspace(relativePath?: string): ListWorkspaceOutput {
|
|
21
|
-
const config = getConfig();
|
|
22
|
-
const targetPath = relativePath
|
|
23
|
-
? resolve(config.workspaceRoot, relativePath)
|
|
24
|
-
: config.workspaceRoot;
|
|
25
|
-
|
|
26
|
-
const guarded = guardPath(targetPath, config.workspaceRoot);
|
|
27
|
-
|
|
28
|
-
if (!existsSync(guarded)) {
|
|
29
|
-
throw new Error(`Path not found: "${relativePath || "."}"`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const stat = statSync(guarded);
|
|
33
|
-
if (!stat.isDirectory()) {
|
|
34
|
-
throw new Error(`Not a directory: "${relativePath || "."}"`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const entries: ListEntry[] = [];
|
|
38
|
-
const raw = readdirSync(guarded);
|
|
39
|
-
|
|
40
|
-
for (const name of raw) {
|
|
41
|
-
// Skip hidden files and sensitive paths
|
|
42
|
-
const fullPath = resolve(guarded, name);
|
|
43
|
-
try {
|
|
44
|
-
guardSensitivePath(fullPath);
|
|
45
|
-
} catch {
|
|
46
|
-
continue; // skip sensitive files silently
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const s = statSync(fullPath);
|
|
51
|
-
entries.push({
|
|
52
|
-
name,
|
|
53
|
-
type: s.isDirectory() ? "directory" : "file",
|
|
54
|
-
size: s.isFile() ? s.size : undefined,
|
|
55
|
-
});
|
|
56
|
-
} catch {
|
|
57
|
-
// can't stat, skip
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (entries.length >= MAX_LIST_FILES) break;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
path: relativePath || ".",
|
|
65
|
-
entries: entries.sort((a, b) => {
|
|
66
|
-
// directories first, then alphabetically
|
|
67
|
-
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
68
|
-
return a.name.localeCompare(b.name);
|
|
69
|
-
}),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
1
|
+
import { readdirSync, statSync, existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { getConfig } from "../config.js";
|
|
4
|
+
import { guardPath } from "../security/pathGuard.js";
|
|
5
|
+
import { guardSensitivePath } from "../security/sensitiveGuard.js";
|
|
6
|
+
|
|
7
|
+
export interface ListEntry {
|
|
8
|
+
name: string;
|
|
9
|
+
type: "file" | "directory";
|
|
10
|
+
size?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ListWorkspaceOutput {
|
|
14
|
+
path: string;
|
|
15
|
+
entries: ListEntry[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const MAX_LIST_FILES = 200;
|
|
19
|
+
|
|
20
|
+
export function listWorkspace(relativePath?: string): ListWorkspaceOutput {
|
|
21
|
+
const config = getConfig();
|
|
22
|
+
const targetPath = relativePath
|
|
23
|
+
? resolve(config.workspaceRoot, relativePath)
|
|
24
|
+
: config.workspaceRoot;
|
|
25
|
+
|
|
26
|
+
const guarded = guardPath(targetPath, config.workspaceRoot);
|
|
27
|
+
|
|
28
|
+
if (!existsSync(guarded)) {
|
|
29
|
+
throw new Error(`Path not found: "${relativePath || "."}"`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const stat = statSync(guarded);
|
|
33
|
+
if (!stat.isDirectory()) {
|
|
34
|
+
throw new Error(`Not a directory: "${relativePath || "."}"`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const entries: ListEntry[] = [];
|
|
38
|
+
const raw = readdirSync(guarded);
|
|
39
|
+
|
|
40
|
+
for (const name of raw) {
|
|
41
|
+
// Skip hidden files and sensitive paths
|
|
42
|
+
const fullPath = resolve(guarded, name);
|
|
43
|
+
try {
|
|
44
|
+
guardSensitivePath(fullPath);
|
|
45
|
+
} catch {
|
|
46
|
+
continue; // skip sensitive files silently
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const s = statSync(fullPath);
|
|
51
|
+
entries.push({
|
|
52
|
+
name,
|
|
53
|
+
type: s.isDirectory() ? "directory" : "file",
|
|
54
|
+
size: s.isFile() ? s.size : undefined,
|
|
55
|
+
});
|
|
56
|
+
} catch {
|
|
57
|
+
// can't stat, skip
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (entries.length >= MAX_LIST_FILES) break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
path: relativePath || ".",
|
|
65
|
+
entries: entries.sort((a, b) => {
|
|
66
|
+
// directories first, then alphabetically
|
|
67
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
68
|
+
return a.name.localeCompare(b.name);
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.0.0 Part B: merge_worktree MCP 工具 handler — 把隔离 worktree 的 branch
|
|
3
|
+
* 合并回主工作区,并将 worktree 状态置为 "merged"。
|
|
4
|
+
*
|
|
5
|
+
* 参考 src/tools/auditTask.ts 的导出/返回风格。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getConfig } from "../config.js";
|
|
9
|
+
import { guardWorkspacePath } from "../security/pathGuard.js";
|
|
10
|
+
import { mergeWorktree } from "../goal/worktreeManager.js";
|
|
11
|
+
|
|
12
|
+
export interface MergeWorktreeInput {
|
|
13
|
+
worktree_id: string;
|
|
14
|
+
repo_path: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MergeWorktreeOutput {
|
|
18
|
+
worktree_id: string;
|
|
19
|
+
status: "merged";
|
|
20
|
+
merged_at: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 合并指定 worktree 的 branch 回 repo_path(主工作区)。
|
|
25
|
+
*
|
|
26
|
+
* @throws PatchWardenError("workspace_path_escape") 当 repo_path 逃逸出 workspaceRoot
|
|
27
|
+
* @throws PatchWardenError("worktree_not_found") 当 worktree 不存在
|
|
28
|
+
* @throws PatchWardenError("invalid_worktree_state") 当 worktree 非 active
|
|
29
|
+
* @throws PatchWardenError("worktree_merge_failed") 当 git merge 失败
|
|
30
|
+
*/
|
|
31
|
+
export function mergeWorktreeTool(input: MergeWorktreeInput): MergeWorktreeOutput {
|
|
32
|
+
const config = getConfig();
|
|
33
|
+
const repoPathSafe = guardWorkspacePath(input.repo_path, config.workspaceRoot);
|
|
34
|
+
|
|
35
|
+
mergeWorktree(input.worktree_id, repoPathSafe);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
worktree_id: input.worktree_id,
|
|
39
|
+
status: "merged",
|
|
40
|
+
merged_at: new Date().toISOString(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getConfig } from "../config.js";
|
|
2
|
+
import { guardWorkspacePath } from "../security/pathGuard.js";
|
|
3
|
+
import { routeAgent } from "../agents/agentRouter.js";
|
|
4
|
+
|
|
5
|
+
export interface RecommendAgentForTaskInput {
|
|
6
|
+
repo_path: string;
|
|
7
|
+
goal: string;
|
|
8
|
+
scope_files?: string[];
|
|
9
|
+
template?: string;
|
|
10
|
+
risk_hint?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RecommendAgentForTaskOutput {
|
|
14
|
+
repo_path: string;
|
|
15
|
+
resolved_repo_path: string;
|
|
16
|
+
recommended_agent: string;
|
|
17
|
+
fallback_agent: string | null;
|
|
18
|
+
fallback: boolean;
|
|
19
|
+
reason: string;
|
|
20
|
+
risk_notes: string[];
|
|
21
|
+
suggested_verify_commands: string[];
|
|
22
|
+
bounded: true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function recommendAgentForTask(input: RecommendAgentForTaskInput): RecommendAgentForTaskOutput {
|
|
26
|
+
const config = getConfig();
|
|
27
|
+
const repoPath = String(input.repo_path || "").trim();
|
|
28
|
+
const goal = String(input.goal || "").trim();
|
|
29
|
+
if (!repoPath) throw new Error("repo_path is required.");
|
|
30
|
+
if (!goal) throw new Error("goal is required.");
|
|
31
|
+
|
|
32
|
+
const resolvedRepoPath = guardWorkspacePath(repoPath, config.workspaceRoot);
|
|
33
|
+
const configuredAgents = Object.keys(config.agents);
|
|
34
|
+
const route = routeAgent({
|
|
35
|
+
goal,
|
|
36
|
+
scope: normalizeScope(input.scope_files),
|
|
37
|
+
inline_plan: input.risk_hint,
|
|
38
|
+
template: input.template,
|
|
39
|
+
configuredAgents,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
repo_path: repoPath,
|
|
44
|
+
resolved_repo_path: resolvedRepoPath,
|
|
45
|
+
recommended_agent: route.recommended_agent,
|
|
46
|
+
fallback_agent: route.fallback ? route.recommended_agent : configuredAgents.find((name) => name !== route.recommended_agent) || null,
|
|
47
|
+
fallback: route.fallback,
|
|
48
|
+
reason: route.reason,
|
|
49
|
+
risk_notes: buildRiskNotes(goal, input.risk_hint),
|
|
50
|
+
suggested_verify_commands: suggestVerifyCommands(config.allowedTestCommands),
|
|
51
|
+
bounded: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeScope(value: string[] | undefined): string[] | undefined {
|
|
56
|
+
if (!Array.isArray(value)) return undefined;
|
|
57
|
+
return value.map((entry) => String(entry).trim()).filter(Boolean).slice(0, 50);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildRiskNotes(goal: string, riskHint: string | undefined): string[] {
|
|
61
|
+
const text = `${goal} ${riskHint || ""}`.toLowerCase();
|
|
62
|
+
const notes: string[] = [];
|
|
63
|
+
if (/release|publish|push|tag|npm/.test(text)) {
|
|
64
|
+
notes.push("release_or_remote_write_language_detected");
|
|
65
|
+
}
|
|
66
|
+
if (/secret|token|cookie|\.env|ssh/.test(text)) {
|
|
67
|
+
notes.push("sensitive_file_language_detected");
|
|
68
|
+
}
|
|
69
|
+
if (/refactor|rewrite|redesign|migration/.test(text)) {
|
|
70
|
+
notes.push("broad_change_language_detected");
|
|
71
|
+
}
|
|
72
|
+
return notes.slice(0, 8);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function suggestVerifyCommands(commands: string[]): string[] {
|
|
76
|
+
const preferred = ["npm test", "npm run test", "npm run build", "npm run lint"];
|
|
77
|
+
const configured = new Set(commands);
|
|
78
|
+
return preferred.filter((command) => configured.has(command)).slice(0, 4);
|
|
79
|
+
}
|