patchwarden 0.4.0 → 0.6.1

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 (189) hide show
  1. package/PatchWarden.cmd +51 -0
  2. package/README.en.md +1022 -0
  3. package/README.md +897 -358
  4. package/dist/assessments/agentAssessor.d.ts +15 -0
  5. package/dist/assessments/agentAssessor.js +293 -0
  6. package/dist/assessments/assessmentStore.d.ts +133 -0
  7. package/dist/assessments/assessmentStore.js +238 -0
  8. package/dist/assessments/confirmCli.d.ts +9 -0
  9. package/dist/assessments/confirmCli.js +26 -0
  10. package/dist/config.d.ts +22 -1
  11. package/dist/config.js +156 -3
  12. package/dist/direct/directAudit.d.ts +23 -0
  13. package/dist/direct/directAudit.js +309 -0
  14. package/dist/direct/directGuards.d.ts +20 -0
  15. package/dist/direct/directGuards.js +137 -0
  16. package/dist/direct/directPatch.d.ts +17 -0
  17. package/dist/direct/directPatch.js +113 -0
  18. package/dist/direct/directSessionStore.d.ts +63 -0
  19. package/dist/direct/directSessionStore.js +192 -0
  20. package/dist/direct/directVerification.d.ts +12 -0
  21. package/dist/direct/directVerification.js +96 -0
  22. package/dist/doctor.js +73 -5
  23. package/dist/logging.d.ts +52 -0
  24. package/dist/logging.js +123 -0
  25. package/dist/runner/agentInvocation.d.ts +23 -0
  26. package/dist/runner/agentInvocation.js +106 -0
  27. package/dist/runner/changeCapture.d.ts +70 -0
  28. package/dist/runner/changeCapture.js +278 -6
  29. package/dist/runner/runTask.js +271 -65
  30. package/dist/runner/simpleProcess.d.ts +21 -0
  31. package/dist/runner/simpleProcess.js +206 -0
  32. package/dist/runner/watch.js +17 -2
  33. package/dist/security/commandGuard.d.ts +2 -1
  34. package/dist/security/commandGuard.js +25 -3
  35. package/dist/security/riskEngine.d.ts +27 -0
  36. package/dist/security/riskEngine.js +118 -0
  37. package/dist/security/runtimeGuard.d.ts +6 -0
  38. package/dist/security/runtimeGuard.js +28 -0
  39. package/dist/smoke-test.js +1353 -7
  40. package/dist/test/unit/android-doctor.test.d.ts +1 -0
  41. package/dist/test/unit/android-doctor.test.js +118 -0
  42. package/dist/test/unit/chinese-path.test.d.ts +1 -0
  43. package/dist/test/unit/chinese-path.test.js +91 -0
  44. package/dist/test/unit/command-guard.test.d.ts +1 -0
  45. package/dist/test/unit/command-guard.test.js +160 -0
  46. package/dist/test/unit/direct-guards.test.d.ts +1 -0
  47. package/dist/test/unit/direct-guards.test.js +213 -0
  48. package/dist/test/unit/logging.test.d.ts +1 -0
  49. package/dist/test/unit/logging.test.js +275 -0
  50. package/dist/test/unit/path-guard.test.d.ts +1 -0
  51. package/dist/test/unit/path-guard.test.js +109 -0
  52. package/dist/test/unit/safe-status.test.d.ts +1 -0
  53. package/dist/test/unit/safe-status.test.js +165 -0
  54. package/dist/test/unit/sensitive-guard.test.d.ts +1 -0
  55. package/dist/test/unit/sensitive-guard.test.js +104 -0
  56. package/dist/test/unit/sync-file.test.d.ts +1 -0
  57. package/dist/test/unit/sync-file.test.js +154 -0
  58. package/dist/test/unit/watcher-status.test.d.ts +1 -0
  59. package/dist/test/unit/watcher-status.test.js +169 -0
  60. package/dist/tools/androidDoctor.d.ts +38 -0
  61. package/dist/tools/androidDoctor.js +391 -0
  62. package/dist/tools/applyPatch.d.ts +16 -0
  63. package/dist/tools/applyPatch.js +41 -0
  64. package/dist/tools/auditSession.d.ts +5 -0
  65. package/dist/tools/auditSession.js +12 -0
  66. package/dist/tools/auditTask.d.ts +7 -0
  67. package/dist/tools/auditTask.js +105 -7
  68. package/dist/tools/createDirectSession.d.ts +14 -0
  69. package/dist/tools/createDirectSession.js +54 -0
  70. package/dist/tools/createTask.d.ts +48 -1
  71. package/dist/tools/createTask.js +298 -47
  72. package/dist/tools/finalizeDirectSession.d.ts +19 -0
  73. package/dist/tools/finalizeDirectSession.js +84 -0
  74. package/dist/tools/getTaskSummary.d.ts +50 -0
  75. package/dist/tools/getTaskSummary.js +79 -2
  76. package/dist/tools/healthCheck.d.ts +27 -12
  77. package/dist/tools/healthCheck.js +44 -6
  78. package/dist/tools/readWorkspaceFile.d.ts +7 -1
  79. package/dist/tools/readWorkspaceFile.js +48 -2
  80. package/dist/tools/registry.js +270 -19
  81. package/dist/tools/runVerification.d.ts +16 -0
  82. package/dist/tools/runVerification.js +32 -0
  83. package/dist/tools/safeStatus.d.ts +19 -0
  84. package/dist/tools/safeStatus.js +72 -0
  85. package/dist/tools/savePlan.d.ts +1 -0
  86. package/dist/tools/savePlan.js +38 -7
  87. package/dist/tools/searchWorkspace.d.ts +19 -0
  88. package/dist/tools/searchWorkspace.js +205 -0
  89. package/dist/tools/syncFile.d.ts +18 -0
  90. package/dist/tools/syncFile.js +65 -0
  91. package/dist/tools/taskOutputs.d.ts +2 -2
  92. package/dist/tools/taskTemplates.js +2 -1
  93. package/dist/tools/toolCatalog.d.ts +4 -3
  94. package/dist/tools/toolCatalog.js +34 -5
  95. package/dist/tools/waitForTask.d.ts +2 -2
  96. package/dist/tools/waitForTask.js +1 -1
  97. package/dist/version.d.ts +2 -2
  98. package/dist/version.js +2 -2
  99. package/dist/watcherStatus.d.ts +1 -0
  100. package/dist/watcherStatus.js +96 -4
  101. package/docs/chatgpt-usage.md +101 -0
  102. package/docs/performance-notes.md +55 -0
  103. package/docs/release-checklist.md +14 -0
  104. package/docs/release-v0.6.0.md +71 -0
  105. package/docs/release-v0.6.1.md +75 -0
  106. package/examples/config.example.json +5 -0
  107. package/examples/openai-tunnel/README.md +11 -4
  108. package/examples/openai-tunnel/chatgpt-test-prompt.md +18 -14
  109. package/examples/openai-tunnel/tunnel-client.example.yaml +8 -0
  110. package/package.json +13 -8
  111. package/scripts/brand-check.js +58 -12
  112. package/scripts/control-smoke.js +206 -0
  113. package/scripts/http-mcp-smoke.js +10 -2
  114. package/scripts/launchers/Check-PatchWarden-Health.cmd +6 -0
  115. package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +6 -0
  116. package/scripts/launchers/Restart-PatchWarden.cmd +6 -0
  117. package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +7 -0
  118. package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +7 -0
  119. package/scripts/lifecycle-smoke.js +402 -8
  120. package/scripts/manage-patchwarden.ps1 +639 -0
  121. package/scripts/mcp-manifest-check.js +123 -58
  122. package/scripts/mcp-smoke.js +160 -2
  123. package/scripts/pack-clean.js +160 -5
  124. package/scripts/package-manifest-check.js +78 -0
  125. package/scripts/patchwarden-mcp-direct.cmd +7 -0
  126. package/scripts/patchwarden-mcp-stdio.cmd +1 -1
  127. package/scripts/restart-patchwarden.ps1 +5 -4
  128. package/scripts/start-patchwarden-tunnel.ps1 +261 -30
  129. package/scripts/tunnel-supervisor-smoke.js +36 -4
  130. package/scripts/unit-tests.js +36 -0
  131. package/scripts/watcher-supervisor-smoke.js +10 -6
  132. package/src/assessments/agentAssessor.ts +324 -0
  133. package/src/assessments/assessmentStore.ts +426 -0
  134. package/src/assessments/confirmCli.ts +29 -0
  135. package/src/config.ts +178 -4
  136. package/src/direct/directAudit.ts +400 -0
  137. package/src/direct/directGuards.ts +279 -0
  138. package/src/direct/directPatch.ts +258 -0
  139. package/src/direct/directSessionStore.ts +345 -0
  140. package/src/direct/directVerification.ts +138 -0
  141. package/src/doctor.ts +103 -7
  142. package/src/logging.ts +152 -0
  143. package/src/runner/agentInvocation.ts +125 -0
  144. package/src/runner/changeCapture.ts +352 -6
  145. package/src/runner/runTask.ts +279 -63
  146. package/src/runner/simpleProcess.ts +223 -0
  147. package/src/runner/watch.ts +18 -2
  148. package/src/security/commandGuard.ts +46 -4
  149. package/src/security/riskEngine.ts +160 -0
  150. package/src/security/runtimeGuard.ts +41 -0
  151. package/src/smoke-test.ts +1291 -4
  152. package/src/test/unit/android-doctor.test.ts +158 -0
  153. package/src/test/unit/chinese-path.test.ts +106 -0
  154. package/src/test/unit/command-guard.test.ts +221 -0
  155. package/src/test/unit/direct-guards.test.ts +297 -0
  156. package/src/test/unit/logging.test.ts +325 -0
  157. package/src/test/unit/path-guard.test.ts +150 -0
  158. package/src/test/unit/safe-status.test.ts +187 -0
  159. package/src/test/unit/sensitive-guard.test.ts +124 -0
  160. package/src/test/unit/sync-file.test.ts +231 -0
  161. package/src/test/unit/watcher-status.test.ts +190 -0
  162. package/src/tools/androidDoctor.ts +424 -0
  163. package/src/tools/applyPatch.ts +86 -0
  164. package/src/tools/auditSession.ts +28 -0
  165. package/src/tools/auditTask.ts +111 -7
  166. package/src/tools/createDirectSession.ts +113 -0
  167. package/src/tools/createTask.ts +405 -55
  168. package/src/tools/finalizeDirectSession.ts +144 -0
  169. package/src/tools/getTaskSummary.ts +133 -2
  170. package/src/tools/healthCheck.ts +45 -6
  171. package/src/tools/readWorkspaceFile.ts +85 -2
  172. package/src/tools/registry.ts +305 -19
  173. package/src/tools/runVerification.ts +58 -0
  174. package/src/tools/safeStatus.ts +96 -0
  175. package/src/tools/savePlan.ts +57 -7
  176. package/src/tools/searchWorkspace.ts +275 -0
  177. package/src/tools/syncFile.ts +122 -0
  178. package/src/tools/taskTemplates.ts +2 -1
  179. package/src/tools/toolCatalog.ts +37 -6
  180. package/src/tools/waitForTask.ts +3 -3
  181. package/src/version.ts +2 -2
  182. package/src/watcherStatus.ts +101 -4
  183. package/tsconfig.json +18 -17
  184. package/Check-PatchWarden-Health.cmd +0 -6
  185. package/Reset-PatchWarden-Tunnel-Key.cmd +0 -6
  186. package/Restart-PatchWarden.cmd +0 -19
  187. package/Start-PatchWarden-Tunnel.cmd +0 -7
  188. package/docs/release-v0.3.0.md +0 -43
  189. package/docs/release-v0.4.0.md +0 -74
package/src/logging.ts ADDED
@@ -0,0 +1,152 @@
1
+ import { redactSensitiveContent } from "./security/contentRedaction.js";
2
+
3
+ // ── Types ─────────────────────────────────────────────────────────
4
+
5
+ export type LogLevel = "info" | "warn" | "error" | "audit";
6
+
7
+ export interface LogEntry {
8
+ timestamp: string;
9
+ level: LogLevel;
10
+ message: string;
11
+ [key: string]: unknown;
12
+ }
13
+
14
+ // ── Helpers ───────────────────────────────────────────────────────
15
+
16
+ /**
17
+ * Returns true when PATCHWARDEN_VERBOSE_LOG is set to "true".
18
+ * Verbose mode enables logging of sanitized tool-call arguments.
19
+ */
20
+ export function isVerboseLogging(): boolean {
21
+ return process.env.PATCHWARDEN_VERBOSE_LOG === "true";
22
+ }
23
+
24
+ /**
25
+ * Serialize a value to a JSON string, falling back to String() on failure
26
+ * (e.g. circular references, functions, symbols).
27
+ */
28
+ function safeStringify(value: unknown): string {
29
+ try {
30
+ const result = JSON.stringify(value);
31
+ return result === undefined ? String(value) : result;
32
+ } catch {
33
+ return String(value);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Write a single JSON log line to stderr.
39
+ *
40
+ * All log output goes to stderr — NEVER stdout — so that MCP JSON-RPC
41
+ * traffic on stdout is never polluted by log messages.
42
+ */
43
+ function emit(entry: LogEntry): void {
44
+ process.stderr.write(JSON.stringify(entry) + "\n");
45
+ }
46
+
47
+ // ── Logger ────────────────────────────────────────────────────────
48
+
49
+ /**
50
+ * Structured logger that emits JSON lines to stderr.
51
+ *
52
+ * Each entry contains `timestamp`, `level`, `message`, plus any optional
53
+ * context fields supplied by the caller.
54
+ */
55
+ export class Logger {
56
+ info(message: string, context?: Record<string, unknown>): void {
57
+ emit({ timestamp: new Date().toISOString(), level: "info", message, ...context });
58
+ }
59
+
60
+ warn(message: string, context?: Record<string, unknown>): void {
61
+ emit({ timestamp: new Date().toISOString(), level: "warn", message, ...context });
62
+ }
63
+
64
+ error(message: string, context?: Record<string, unknown>): void {
65
+ emit({ timestamp: new Date().toISOString(), level: "error", message, ...context });
66
+ }
67
+
68
+ /**
69
+ * Emit a tool-call audit log entry.
70
+ *
71
+ * Required fields: `tool`, `ok`, `duration_ms`.
72
+ * Optional fields: `error_reason`, `task_id`.
73
+ *
74
+ * By default raw arguments are NOT logged. When verbose mode is enabled
75
+ * (PATCHWARDEN_VERBOSE_LOG=true) and `args` is provided, the arguments
76
+ * are serialized and sanitized via `redactSensitiveContent` before being
77
+ * included in the `args` field.
78
+ */
79
+ audit(
80
+ tool: string,
81
+ ok: boolean,
82
+ durationMs: number,
83
+ errorReason?: string,
84
+ taskId?: string,
85
+ args?: unknown,
86
+ ): void {
87
+ const entry: LogEntry = {
88
+ timestamp: new Date().toISOString(),
89
+ level: "audit",
90
+ message: "tool_call_audit",
91
+ tool,
92
+ ok,
93
+ duration_ms: durationMs,
94
+ };
95
+ if (errorReason !== undefined) entry.error_reason = errorReason;
96
+ if (taskId !== undefined) entry.task_id = taskId;
97
+ if (args !== undefined && isVerboseLogging()) {
98
+ const redacted = redactSensitiveContent(safeStringify(args));
99
+ entry.args = redacted.content;
100
+ }
101
+ emit(entry);
102
+ }
103
+ }
104
+
105
+ /** Default singleton logger instance. */
106
+ export const logger = new Logger();
107
+
108
+ // ── Unhandled error helpers ───────────────────────────────────────
109
+
110
+ /**
111
+ * Produce a structured error log entry for an unhandled rejection or
112
+ * uncaught exception. Writes JSON to stderr.
113
+ */
114
+ export function logUnhandledError(error: unknown): void {
115
+ const entry: LogEntry = {
116
+ timestamp: new Date().toISOString(),
117
+ level: "error",
118
+ message: "unhandled_error",
119
+ error: error instanceof Error
120
+ ? error.message
121
+ : typeof error === "string"
122
+ ? error
123
+ : safeStringify(error),
124
+ error_name: error instanceof Error ? error.name : typeof error,
125
+ };
126
+ if (error instanceof Error && error.stack) {
127
+ entry.stack = error.stack;
128
+ }
129
+ emit(entry);
130
+ }
131
+
132
+ /**
133
+ * Register process-level handlers for `unhandledRejection` and
134
+ * `uncaughtException`.
135
+ *
136
+ * Both handlers log structured error output to stderr. The
137
+ * `uncaughtException` handler does NOT swallow the fatal error — after
138
+ * logging it exits with a non-zero status code to preserve the default
139
+ * crash behaviour required for truly fatal failures.
140
+ */
141
+ export function installGlobalHandlers(): void {
142
+ process.on("unhandledRejection", (reason: unknown) => {
143
+ logUnhandledError(reason);
144
+ });
145
+
146
+ process.on("uncaughtException", (error: Error) => {
147
+ logUnhandledError(error);
148
+ // Do not swallow: exit with failure so the process does not continue
149
+ // in an undefined state.
150
+ process.exit(1);
151
+ });
152
+ }
@@ -0,0 +1,125 @@
1
+ import { basename } from "node:path";
2
+ import { PatchWardenConfig } from "../config.js";
3
+ import { guardAgentCommand, sanitizePromptArg, type AllowedCommand } from "../security/commandGuard.js";
4
+
5
+ export interface AgentInvocation {
6
+ command: string;
7
+ args: string[];
8
+ cwd: string;
9
+ commandLabel: string;
10
+ promptMode: "inline" | "file";
11
+ promptFilePath?: string;
12
+ }
13
+
14
+ /**
15
+ * Build agent invocation parameters from config.
16
+ * Replaces {repo}, {prompt}, and {prompt_file} placeholders.
17
+ * runTask and agentAssessor share this function to ensure consistent agent startup.
18
+ */
19
+ export function buildAgentInvocation(
20
+ agentName: string,
21
+ repoPath: string,
22
+ prompt: string,
23
+ config: PatchWardenConfig,
24
+ promptFilePath?: string
25
+ ): AgentInvocation {
26
+ const agentCmd = guardAgentCommand(agentName, config);
27
+ const sanitizedPrompt = sanitizePromptArg(prompt);
28
+
29
+ const hasPromptFilePlaceholder = agentCmd.args.includes("{prompt_file}");
30
+ const promptMode: "inline" | "file" = hasPromptFilePlaceholder && promptFilePath ? "file" : "inline";
31
+
32
+ const resolvedArgs = agentCmd.args.map((arg) => {
33
+ if (arg === "{repo}") return repoPath;
34
+ if (arg === "{prompt}") return sanitizedPrompt;
35
+ if (arg === "{prompt_file}" && promptMode === "file" && promptFilePath) return promptFilePath;
36
+ return arg;
37
+ });
38
+
39
+ return {
40
+ command: agentCmd.command,
41
+ args: resolvedArgs,
42
+ cwd: repoPath,
43
+ commandLabel: `${basename(agentCmd.command)} (configured agent command)`,
44
+ promptMode,
45
+ ...(promptMode === "file" && promptFilePath ? { promptFilePath } : {}),
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Build task execution prompt. Mechanically extracted from runTask.ts.
51
+ */
52
+ export function buildExecutionPrompt(plan: string, repoPath: string, testCommand: string): string {
53
+ let prompt = `You are executing a pre-written plan in a local repository.
54
+
55
+ ## Repository
56
+ ${repoPath}
57
+
58
+ ## Plan
59
+ ${plan}
60
+
61
+ ## Instructions
62
+ 1. Read the plan carefully.
63
+ 2. Implement the changes in this repository only.
64
+ 3. Do NOT modify files outside this repository.
65
+ 4. Leave repository changes uncommitted for review; remote operations are outside this task.
66
+ 5. After implementing, describe what you changed.
67
+ 6. Output a summary with what was done, files modified, and issues encountered.
68
+ `;
69
+ if (testCommand) {
70
+ prompt += `\n7. You may run ${testCommand}; PatchWarden will independently run it again for verification.`;
71
+ }
72
+ return prompt;
73
+ }
74
+
75
+ /**
76
+ * Build agentAssessor inspect-only prompt.
77
+ */
78
+ export function buildAssessmentPrompt(goal: string, planContent: string, repoPath: string): string {
79
+ return `You are performing a READ-ONLY risk assessment of a planned task. Do NOT modify any files.
80
+
81
+ ## Repository
82
+ ${repoPath}
83
+
84
+ ## Goal
85
+ ${goal}
86
+
87
+ ## Plan
88
+ ${planContent}
89
+
90
+ ## Instructions
91
+ 1. Read the plan and goal carefully.
92
+ 2. Inspect the repository to understand the scope of changes.
93
+ 3. Do NOT create, edit, delete, rename, or generate any files.
94
+ 4. Assess the risk level of executing this plan.
95
+ 5. Identify affected file paths within the repository.
96
+ 6. Check for destructive actions, sensitive file access, or out-of-scope changes.
97
+ 7. Output your assessment as JSON after the marker ===ASSESSMENT_JSON=== on a new line.
98
+
99
+ ## Required JSON output format
100
+ After your analysis, output exactly this format:
101
+
102
+ ===ASSESSMENT_JSON===
103
+ {
104
+ "risk_level": "low" | "medium" | "high",
105
+ "reason_codes": ["short", "descriptive", "codes"],
106
+ "affected_paths": ["relative/path/to/file"],
107
+ "destructive_actions": ["description of any destructive actions"],
108
+ "requires_user_confirm": false,
109
+ "confidence": 0.0,
110
+ "notes": "Brief summary of findings"
111
+ }
112
+
113
+ ## Risk level guidelines
114
+ - low: Small source change, no sensitive files, no destructive actions
115
+ - medium: Multiple files, build artifacts, dependency changes, or uncertain scope
116
+ - high: Destructive actions, sensitive file access, out-of-scope changes, or large-scale deletion
117
+
118
+ ## Constraints
119
+ - affected_paths must be relative paths within the repository (no absolute paths)
120
+ - reason_codes: max 50 entries, each max 100 chars
121
+ - destructive_actions: max 20 entries, each max 200 chars
122
+ - notes: max 2000 chars
123
+ - confidence: number from 0.0 to 1.0
124
+ `;
125
+ }