patchwarden 0.4.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/Check-PatchWarden-Health.cmd +6 -0
- package/LICENSE +21 -0
- package/README.md +526 -0
- package/Reset-PatchWarden-Tunnel-Key.cmd +6 -0
- package/Restart-PatchWarden.cmd +19 -0
- package/Start-PatchWarden-Tunnel.cmd +7 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +131 -0
- package/dist/doctor.d.ts +10 -0
- package/dist/doctor.js +388 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +27 -0
- package/dist/httpServer.d.ts +17 -0
- package/dist/httpServer.js +255 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +24 -0
- package/dist/runner/changeCapture.d.ts +43 -0
- package/dist/runner/changeCapture.js +245 -0
- package/dist/runner/cli.d.ts +9 -0
- package/dist/runner/cli.js +26 -0
- package/dist/runner/runTask.d.ts +8 -0
- package/dist/runner/runTask.js +821 -0
- package/dist/runner/watch.d.ts +19 -0
- package/dist/runner/watch.js +136 -0
- package/dist/security/commandGuard.d.ts +22 -0
- package/dist/security/commandGuard.js +56 -0
- package/dist/security/contentRedaction.d.ts +12 -0
- package/dist/security/contentRedaction.js +65 -0
- package/dist/security/pathGuard.d.ts +3 -0
- package/dist/security/pathGuard.js +98 -0
- package/dist/security/planGuard.d.ts +1 -0
- package/dist/security/planGuard.js +65 -0
- package/dist/security/sensitiveGuard.d.ts +2 -0
- package/dist/security/sensitiveGuard.js +60 -0
- package/dist/smoke-test.d.ts +15 -0
- package/dist/smoke-test.js +989 -0
- package/dist/taskProgress.d.ts +6 -0
- package/dist/taskProgress.js +36 -0
- package/dist/taskRuntime.d.ts +10 -0
- package/dist/taskRuntime.js +25 -0
- package/dist/tools/auditTask.d.ts +19 -0
- package/dist/tools/auditTask.js +307 -0
- package/dist/tools/cancelTask.d.ts +30 -0
- package/dist/tools/cancelTask.js +64 -0
- package/dist/tools/createTask.d.ts +42 -0
- package/dist/tools/createTask.js +188 -0
- package/dist/tools/getPlan.d.ts +12 -0
- package/dist/tools/getPlan.js +29 -0
- package/dist/tools/getTaskFile.d.ts +24 -0
- package/dist/tools/getTaskFile.js +84 -0
- package/dist/tools/getTaskProgress.d.ts +1 -0
- package/dist/tools/getTaskProgress.js +4 -0
- package/dist/tools/getTaskStatus.d.ts +46 -0
- package/dist/tools/getTaskStatus.js +36 -0
- package/dist/tools/getTaskStdoutTail.d.ts +9 -0
- package/dist/tools/getTaskStdoutTail.js +56 -0
- package/dist/tools/getTaskSummary.d.ts +66 -0
- package/dist/tools/getTaskSummary.js +237 -0
- package/dist/tools/healthCheck.d.ts +79 -0
- package/dist/tools/healthCheck.js +230 -0
- package/dist/tools/killTask.d.ts +15 -0
- package/dist/tools/killTask.js +4 -0
- package/dist/tools/listAgents.d.ts +12 -0
- package/dist/tools/listAgents.js +42 -0
- package/dist/tools/listTasks.d.ts +36 -0
- package/dist/tools/listTasks.js +94 -0
- package/dist/tools/listWorkspace.d.ts +10 -0
- package/dist/tools/listWorkspace.js +54 -0
- package/dist/tools/readWorkspaceFile.d.ts +8 -0
- package/dist/tools/readWorkspaceFile.js +26 -0
- package/dist/tools/registry.d.ts +24 -0
- package/dist/tools/registry.js +566 -0
- package/dist/tools/retryTask.d.ts +8 -0
- package/dist/tools/retryTask.js +45 -0
- package/dist/tools/savePlan.d.ts +10 -0
- package/dist/tools/savePlan.js +28 -0
- package/dist/tools/taskOutputs.d.ts +49 -0
- package/dist/tools/taskOutputs.js +136 -0
- package/dist/tools/taskTemplates.d.ts +15 -0
- package/dist/tools/taskTemplates.js +116 -0
- package/dist/tools/toolCatalog.d.ts +19 -0
- package/dist/tools/toolCatalog.js +71 -0
- package/dist/tools/waitForTask.d.ts +25 -0
- package/dist/tools/waitForTask.js +59 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/dist/watcherStatus.d.ts +20 -0
- package/dist/watcherStatus.js +77 -0
- package/docs/assets/patchwarden-chatgpt-demo.svg +30 -0
- package/docs/demo.md +58 -0
- package/docs/migration-from-safe-bifrost.md +31 -0
- package/docs/release-v0.3.0.md +43 -0
- package/docs/release-v0.4.0.md +74 -0
- package/examples/config.example.json +40 -0
- package/examples/openai-tunnel/README.md +48 -0
- package/examples/openai-tunnel/chatgpt-test-prompt.md +53 -0
- package/examples/openai-tunnel/tunnel-client.example.yaml +39 -0
- package/package.json +73 -0
- package/scripts/brand-check.js +44 -0
- package/scripts/doctor-smoke.js +52 -0
- package/scripts/get-patchwarden-health.ps1 +114 -0
- package/scripts/http-mcp-smoke.js +351 -0
- package/scripts/lifecycle-smoke.js +453 -0
- package/scripts/mcp-manifest-check.js +120 -0
- package/scripts/mcp-smoke.js +336 -0
- package/scripts/pack-clean.js +142 -0
- package/scripts/patchwarden-mcp-stdio.cmd +7 -0
- package/scripts/restart-patchwarden.ps1 +242 -0
- package/scripts/start-patchwarden-tunnel.ps1 +503 -0
- package/scripts/tunnel-supervisor-smoke.js +77 -0
- package/scripts/watcher-supervisor-smoke.js +195 -0
- package/src/config.ts +165 -0
- package/src/doctor.ts +438 -0
- package/src/errors.ts +26 -0
- package/src/httpServer.ts +277 -0
- package/src/index.ts +34 -0
- package/src/runner/changeCapture.ts +304 -0
- package/src/runner/cli.ts +34 -0
- package/src/runner/runTask.ts +935 -0
- package/src/runner/watch.ts +152 -0
- package/src/security/commandGuard.ts +97 -0
- package/src/security/contentRedaction.ts +85 -0
- package/src/security/pathGuard.ts +175 -0
- package/src/security/planGuard.ts +93 -0
- package/src/security/sensitiveGuard.ts +72 -0
- package/src/smoke-test.ts +1078 -0
- package/src/taskProgress.ts +41 -0
- package/src/taskRuntime.ts +37 -0
- package/src/tools/auditTask.ts +340 -0
- package/src/tools/cancelTask.ts +70 -0
- package/src/tools/createTask.ts +337 -0
- package/src/tools/getPlan.ts +48 -0
- package/src/tools/getTaskFile.ts +111 -0
- package/src/tools/getTaskProgress.ts +5 -0
- package/src/tools/getTaskStatus.ts +85 -0
- package/src/tools/getTaskStdoutTail.ts +61 -0
- package/src/tools/getTaskSummary.ts +307 -0
- package/src/tools/healthCheck.ts +234 -0
- package/src/tools/killTask.ts +5 -0
- package/src/tools/listAgents.ts +54 -0
- package/src/tools/listTasks.ts +139 -0
- package/src/tools/listWorkspace.ts +71 -0
- package/src/tools/readWorkspaceFile.ts +41 -0
- package/src/tools/registry.ts +666 -0
- package/src/tools/retryTask.ts +53 -0
- package/src/tools/savePlan.ts +46 -0
- package/src/tools/taskOutputs.ts +155 -0
- package/src/tools/taskTemplates.ts +162 -0
- package/src/tools/toolCatalog.ts +92 -0
- package/src/tools/waitForTask.ts +90 -0
- package/src/version.ts +2 -0
- package/src/watcherStatus.ts +102 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getTasksDir, getPlansDir, getConfig } from "../config.js";
|
|
4
|
+
import { readTaskRuntime } from "../taskRuntime.js";
|
|
5
|
+
import { derivePendingReason, readWatcherStatus, } from "../watcherStatus.js";
|
|
6
|
+
export function listTasks(input) {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
const tasksDir = getTasksDir(config);
|
|
9
|
+
const plansDir = getPlansDir(config);
|
|
10
|
+
const limit = input?.limit && input.limit > 0 ? Math.min(input.limit, 100) : 20;
|
|
11
|
+
const filterStatus = input?.status || null;
|
|
12
|
+
const filterRepo = input?.repo_path?.trim().replace(/\\/g, "/") || null;
|
|
13
|
+
const watcher = readWatcherStatus(config);
|
|
14
|
+
if (!existsSync(tasksDir)) {
|
|
15
|
+
return { tasks: [], total: 0, returned: 0, watcher };
|
|
16
|
+
}
|
|
17
|
+
const entries = readdirSync(tasksDir, { withFileTypes: true })
|
|
18
|
+
.filter((e) => e.isDirectory())
|
|
19
|
+
.sort((a, b) => {
|
|
20
|
+
// Sort by mtime descending (newest first)
|
|
21
|
+
try {
|
|
22
|
+
const sa = statSync(join(tasksDir, a.name, "status.json"));
|
|
23
|
+
const sb = statSync(join(tasksDir, b.name, "status.json"));
|
|
24
|
+
return sb.mtimeMs - sa.mtimeMs;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return b.name.localeCompare(a.name);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const tasks = [];
|
|
31
|
+
let totalMatched = 0;
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const taskId = entry.name;
|
|
34
|
+
const taskDir = join(tasksDir, taskId);
|
|
35
|
+
const statusFile = join(taskDir, "status.json");
|
|
36
|
+
if (!existsSync(statusFile))
|
|
37
|
+
continue;
|
|
38
|
+
try {
|
|
39
|
+
const data = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
40
|
+
const runtime = readTaskRuntime(taskDir);
|
|
41
|
+
if (filterStatus && data.status !== filterStatus)
|
|
42
|
+
continue;
|
|
43
|
+
if (input?.active_only && data.status !== "pending" && data.status !== "running")
|
|
44
|
+
continue;
|
|
45
|
+
const normalizedRepo = String(data.repo_path || ".").replace(/\\/g, "/");
|
|
46
|
+
const normalizedResolvedRepo = String(data.resolved_repo_path || "").replace(/\\/g, "/");
|
|
47
|
+
if (filterRepo && normalizedRepo !== filterRepo && normalizedResolvedRepo !== filterRepo)
|
|
48
|
+
continue;
|
|
49
|
+
totalMatched++;
|
|
50
|
+
if (tasks.length >= limit)
|
|
51
|
+
continue;
|
|
52
|
+
// Read plan title from plans directory (not task dir)
|
|
53
|
+
let title = `Plan: ${data.plan_id || "unknown"}`;
|
|
54
|
+
if (data.plan_id) {
|
|
55
|
+
const planFile = join(plansDir, data.plan_id, "plan.md");
|
|
56
|
+
if (existsSync(planFile)) {
|
|
57
|
+
try {
|
|
58
|
+
const planContent = readFileSync(planFile, "utf-8");
|
|
59
|
+
const titleMatch = planContent.match(/^#\s*(.+)/m);
|
|
60
|
+
if (titleMatch)
|
|
61
|
+
title = titleMatch[1];
|
|
62
|
+
}
|
|
63
|
+
catch { /* keep default */ }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const phase = runtime.phase || data.phase || "queued";
|
|
67
|
+
tasks.push({
|
|
68
|
+
task_id: taskId,
|
|
69
|
+
plan_id: data.plan_id || "",
|
|
70
|
+
title,
|
|
71
|
+
agent: data.agent || "",
|
|
72
|
+
status: data.status || "pending",
|
|
73
|
+
phase,
|
|
74
|
+
created_at: data.created_at || "",
|
|
75
|
+
updated_at: data.updated_at || "",
|
|
76
|
+
workspace_root: data.workspace_root || config.workspaceRoot,
|
|
77
|
+
repo_path: data.repo_path || ".",
|
|
78
|
+
resolved_repo_path: data.resolved_repo_path || data.repo_path || config.workspaceRoot,
|
|
79
|
+
test_command: data.test_command || "",
|
|
80
|
+
verify_commands: Array.isArray(data.verify_commands) ? data.verify_commands : [],
|
|
81
|
+
error: data.error || null,
|
|
82
|
+
last_heartbeat_at: runtime.last_heartbeat_at || data.last_heartbeat_at || data.updated_at || "",
|
|
83
|
+
current_command: runtime.current_command ?? data.current_command ?? null,
|
|
84
|
+
timeout_seconds: data.timeout_seconds || config.defaultTaskTimeoutSeconds,
|
|
85
|
+
pending_reason: derivePendingReason({ status: data.status, phase }, watcher),
|
|
86
|
+
watcher_status: watcher.status,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// skip corrupted entries
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { tasks, total: totalMatched, returned: tasks.length, watcher };
|
|
94
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ListEntry {
|
|
2
|
+
name: string;
|
|
3
|
+
type: "file" | "directory";
|
|
4
|
+
size?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ListWorkspaceOutput {
|
|
7
|
+
path: string;
|
|
8
|
+
entries: ListEntry[];
|
|
9
|
+
}
|
|
10
|
+
export declare function listWorkspace(relativePath?: string): ListWorkspaceOutput;
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
const MAX_LIST_FILES = 200;
|
|
7
|
+
export function listWorkspace(relativePath) {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const targetPath = relativePath
|
|
10
|
+
? resolve(config.workspaceRoot, relativePath)
|
|
11
|
+
: config.workspaceRoot;
|
|
12
|
+
const guarded = guardPath(targetPath, config.workspaceRoot);
|
|
13
|
+
if (!existsSync(guarded)) {
|
|
14
|
+
throw new Error(`Path not found: "${relativePath || "."}"`);
|
|
15
|
+
}
|
|
16
|
+
const stat = statSync(guarded);
|
|
17
|
+
if (!stat.isDirectory()) {
|
|
18
|
+
throw new Error(`Not a directory: "${relativePath || "."}"`);
|
|
19
|
+
}
|
|
20
|
+
const entries = [];
|
|
21
|
+
const raw = readdirSync(guarded);
|
|
22
|
+
for (const name of raw) {
|
|
23
|
+
// Skip hidden files and sensitive paths
|
|
24
|
+
const fullPath = resolve(guarded, name);
|
|
25
|
+
try {
|
|
26
|
+
guardSensitivePath(fullPath);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
continue; // skip sensitive files silently
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const s = statSync(fullPath);
|
|
33
|
+
entries.push({
|
|
34
|
+
name,
|
|
35
|
+
type: s.isDirectory() ? "directory" : "file",
|
|
36
|
+
size: s.isFile() ? s.size : undefined,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// can't stat, skip
|
|
41
|
+
}
|
|
42
|
+
if (entries.length >= MAX_LIST_FILES)
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
path: relativePath || ".",
|
|
47
|
+
entries: entries.sort((a, b) => {
|
|
48
|
+
// directories first, then alphabetically
|
|
49
|
+
if (a.type !== b.type)
|
|
50
|
+
return a.type === "directory" ? -1 : 1;
|
|
51
|
+
return a.name.localeCompare(b.name);
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { getConfig } from "../config.js";
|
|
3
|
+
import { guardReadPath } from "../security/pathGuard.js";
|
|
4
|
+
import { guardSensitivePath } from "../security/sensitiveGuard.js";
|
|
5
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
6
|
+
export function readWorkspaceFile(relativePath) {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
const safePath = guardReadPath(relativePath, config.workspaceRoot);
|
|
9
|
+
guardSensitivePath(safePath);
|
|
10
|
+
const stat = statSync(safePath);
|
|
11
|
+
if (stat.size > config.maxReadFileBytes) {
|
|
12
|
+
throw new Error(`File is ${stat.size} bytes, exceeds max of ${config.maxReadFileBytes} bytes.`);
|
|
13
|
+
}
|
|
14
|
+
// Only allow text-like files
|
|
15
|
+
if (stat.size > 5_000_000) {
|
|
16
|
+
throw new Error("File exceeds 5 MB size limit.");
|
|
17
|
+
}
|
|
18
|
+
const redaction = redactSensitiveContent(readFileSync(safePath, "utf-8"));
|
|
19
|
+
return {
|
|
20
|
+
path: safePath,
|
|
21
|
+
content: redaction.content,
|
|
22
|
+
size: stat.size,
|
|
23
|
+
redacted: redaction.redacted,
|
|
24
|
+
redaction_categories: redaction.redaction_categories,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tool registry for PatchWarden MCP server.
|
|
3
|
+
* Used by both stdio (index.ts) and HTTP (httpServer.ts) transports.
|
|
4
|
+
*/
|
|
5
|
+
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
|
+
import { type ToolCatalogSnapshot } from "./toolCatalog.js";
|
|
7
|
+
export interface ToolDef {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object";
|
|
12
|
+
properties: Record<string, unknown>;
|
|
13
|
+
required?: string[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare function getToolDefs(): ToolDef[];
|
|
17
|
+
export declare function getToolCatalogSnapshot(): ToolCatalogSnapshot;
|
|
18
|
+
export declare function handleToolCall(name: string, args: Record<string, unknown> | undefined): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
24
|
+
export declare function registerTools(server: Server): void;
|