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.
- package/PatchWarden.cmd +51 -0
- package/README.en.md +1022 -0
- package/README.md +897 -358
- package/dist/assessments/agentAssessor.d.ts +15 -0
- package/dist/assessments/agentAssessor.js +293 -0
- package/dist/assessments/assessmentStore.d.ts +133 -0
- package/dist/assessments/assessmentStore.js +238 -0
- package/dist/assessments/confirmCli.d.ts +9 -0
- package/dist/assessments/confirmCli.js +26 -0
- package/dist/config.d.ts +22 -1
- package/dist/config.js +156 -3
- package/dist/direct/directAudit.d.ts +23 -0
- package/dist/direct/directAudit.js +309 -0
- package/dist/direct/directGuards.d.ts +20 -0
- package/dist/direct/directGuards.js +137 -0
- package/dist/direct/directPatch.d.ts +17 -0
- package/dist/direct/directPatch.js +113 -0
- package/dist/direct/directSessionStore.d.ts +63 -0
- package/dist/direct/directSessionStore.js +192 -0
- package/dist/direct/directVerification.d.ts +12 -0
- package/dist/direct/directVerification.js +96 -0
- package/dist/doctor.js +73 -5
- package/dist/logging.d.ts +52 -0
- package/dist/logging.js +123 -0
- package/dist/runner/agentInvocation.d.ts +23 -0
- package/dist/runner/agentInvocation.js +106 -0
- package/dist/runner/changeCapture.d.ts +70 -0
- package/dist/runner/changeCapture.js +278 -6
- package/dist/runner/runTask.js +271 -65
- package/dist/runner/simpleProcess.d.ts +21 -0
- package/dist/runner/simpleProcess.js +206 -0
- package/dist/runner/watch.js +17 -2
- package/dist/security/commandGuard.d.ts +2 -1
- package/dist/security/commandGuard.js +25 -3
- package/dist/security/riskEngine.d.ts +27 -0
- package/dist/security/riskEngine.js +118 -0
- package/dist/security/runtimeGuard.d.ts +6 -0
- package/dist/security/runtimeGuard.js +28 -0
- package/dist/smoke-test.js +1353 -7
- package/dist/test/unit/android-doctor.test.d.ts +1 -0
- package/dist/test/unit/android-doctor.test.js +118 -0
- package/dist/test/unit/chinese-path.test.d.ts +1 -0
- package/dist/test/unit/chinese-path.test.js +91 -0
- package/dist/test/unit/command-guard.test.d.ts +1 -0
- package/dist/test/unit/command-guard.test.js +160 -0
- package/dist/test/unit/direct-guards.test.d.ts +1 -0
- package/dist/test/unit/direct-guards.test.js +213 -0
- package/dist/test/unit/logging.test.d.ts +1 -0
- package/dist/test/unit/logging.test.js +275 -0
- package/dist/test/unit/path-guard.test.d.ts +1 -0
- package/dist/test/unit/path-guard.test.js +109 -0
- package/dist/test/unit/safe-status.test.d.ts +1 -0
- package/dist/test/unit/safe-status.test.js +165 -0
- package/dist/test/unit/sensitive-guard.test.d.ts +1 -0
- package/dist/test/unit/sensitive-guard.test.js +104 -0
- package/dist/test/unit/sync-file.test.d.ts +1 -0
- package/dist/test/unit/sync-file.test.js +154 -0
- package/dist/test/unit/watcher-status.test.d.ts +1 -0
- package/dist/test/unit/watcher-status.test.js +169 -0
- package/dist/tools/androidDoctor.d.ts +38 -0
- package/dist/tools/androidDoctor.js +391 -0
- package/dist/tools/applyPatch.d.ts +16 -0
- package/dist/tools/applyPatch.js +41 -0
- package/dist/tools/auditSession.d.ts +5 -0
- package/dist/tools/auditSession.js +12 -0
- package/dist/tools/auditTask.d.ts +7 -0
- package/dist/tools/auditTask.js +105 -7
- package/dist/tools/createDirectSession.d.ts +14 -0
- package/dist/tools/createDirectSession.js +54 -0
- package/dist/tools/createTask.d.ts +48 -1
- package/dist/tools/createTask.js +298 -47
- package/dist/tools/finalizeDirectSession.d.ts +19 -0
- package/dist/tools/finalizeDirectSession.js +84 -0
- package/dist/tools/getTaskSummary.d.ts +50 -0
- package/dist/tools/getTaskSummary.js +79 -2
- package/dist/tools/healthCheck.d.ts +27 -12
- package/dist/tools/healthCheck.js +44 -6
- package/dist/tools/readWorkspaceFile.d.ts +7 -1
- package/dist/tools/readWorkspaceFile.js +48 -2
- package/dist/tools/registry.js +270 -19
- package/dist/tools/runVerification.d.ts +16 -0
- package/dist/tools/runVerification.js +32 -0
- package/dist/tools/safeStatus.d.ts +19 -0
- package/dist/tools/safeStatus.js +72 -0
- package/dist/tools/savePlan.d.ts +1 -0
- package/dist/tools/savePlan.js +38 -7
- package/dist/tools/searchWorkspace.d.ts +19 -0
- package/dist/tools/searchWorkspace.js +205 -0
- package/dist/tools/syncFile.d.ts +18 -0
- package/dist/tools/syncFile.js +65 -0
- package/dist/tools/taskOutputs.d.ts +2 -2
- package/dist/tools/taskTemplates.js +2 -1
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +34 -5
- package/dist/tools/waitForTask.d.ts +2 -2
- package/dist/tools/waitForTask.js +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +1 -0
- package/dist/watcherStatus.js +96 -4
- package/docs/chatgpt-usage.md +101 -0
- package/docs/performance-notes.md +55 -0
- package/docs/release-checklist.md +14 -0
- package/docs/release-v0.6.0.md +71 -0
- package/docs/release-v0.6.1.md +75 -0
- package/examples/config.example.json +5 -0
- package/examples/openai-tunnel/README.md +11 -4
- package/examples/openai-tunnel/chatgpt-test-prompt.md +18 -14
- package/examples/openai-tunnel/tunnel-client.example.yaml +8 -0
- package/package.json +13 -8
- package/scripts/brand-check.js +58 -12
- package/scripts/control-smoke.js +206 -0
- package/scripts/http-mcp-smoke.js +10 -2
- package/scripts/launchers/Check-PatchWarden-Health.cmd +6 -0
- package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +6 -0
- package/scripts/launchers/Restart-PatchWarden.cmd +6 -0
- package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +7 -0
- package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +7 -0
- package/scripts/lifecycle-smoke.js +402 -8
- package/scripts/manage-patchwarden.ps1 +639 -0
- package/scripts/mcp-manifest-check.js +123 -58
- package/scripts/mcp-smoke.js +160 -2
- package/scripts/pack-clean.js +160 -5
- package/scripts/package-manifest-check.js +78 -0
- package/scripts/patchwarden-mcp-direct.cmd +7 -0
- package/scripts/patchwarden-mcp-stdio.cmd +1 -1
- package/scripts/restart-patchwarden.ps1 +5 -4
- package/scripts/start-patchwarden-tunnel.ps1 +261 -30
- package/scripts/tunnel-supervisor-smoke.js +36 -4
- package/scripts/unit-tests.js +36 -0
- package/scripts/watcher-supervisor-smoke.js +10 -6
- package/src/assessments/agentAssessor.ts +324 -0
- package/src/assessments/assessmentStore.ts +426 -0
- package/src/assessments/confirmCli.ts +29 -0
- package/src/config.ts +178 -4
- package/src/direct/directAudit.ts +400 -0
- package/src/direct/directGuards.ts +279 -0
- package/src/direct/directPatch.ts +258 -0
- package/src/direct/directSessionStore.ts +345 -0
- package/src/direct/directVerification.ts +138 -0
- package/src/doctor.ts +103 -7
- package/src/logging.ts +152 -0
- package/src/runner/agentInvocation.ts +125 -0
- package/src/runner/changeCapture.ts +352 -6
- package/src/runner/runTask.ts +279 -63
- package/src/runner/simpleProcess.ts +223 -0
- package/src/runner/watch.ts +18 -2
- package/src/security/commandGuard.ts +46 -4
- package/src/security/riskEngine.ts +160 -0
- package/src/security/runtimeGuard.ts +41 -0
- package/src/smoke-test.ts +1291 -4
- package/src/test/unit/android-doctor.test.ts +158 -0
- package/src/test/unit/chinese-path.test.ts +106 -0
- package/src/test/unit/command-guard.test.ts +221 -0
- package/src/test/unit/direct-guards.test.ts +297 -0
- package/src/test/unit/logging.test.ts +325 -0
- package/src/test/unit/path-guard.test.ts +150 -0
- package/src/test/unit/safe-status.test.ts +187 -0
- package/src/test/unit/sensitive-guard.test.ts +124 -0
- package/src/test/unit/sync-file.test.ts +231 -0
- package/src/test/unit/watcher-status.test.ts +190 -0
- package/src/tools/androidDoctor.ts +424 -0
- package/src/tools/applyPatch.ts +86 -0
- package/src/tools/auditSession.ts +28 -0
- package/src/tools/auditTask.ts +111 -7
- package/src/tools/createDirectSession.ts +113 -0
- package/src/tools/createTask.ts +405 -55
- package/src/tools/finalizeDirectSession.ts +144 -0
- package/src/tools/getTaskSummary.ts +133 -2
- package/src/tools/healthCheck.ts +45 -6
- package/src/tools/readWorkspaceFile.ts +85 -2
- package/src/tools/registry.ts +305 -19
- package/src/tools/runVerification.ts +58 -0
- package/src/tools/safeStatus.ts +96 -0
- package/src/tools/savePlan.ts +57 -7
- package/src/tools/searchWorkspace.ts +275 -0
- package/src/tools/syncFile.ts +122 -0
- package/src/tools/taskTemplates.ts +2 -1
- package/src/tools/toolCatalog.ts +37 -6
- package/src/tools/waitForTask.ts +3 -3
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +101 -4
- package/tsconfig.json +18 -17
- package/Check-PatchWarden-Health.cmd +0 -6
- package/Reset-PatchWarden-Tunnel-Key.cmd +0 -6
- package/Restart-PatchWarden.cmd +0 -19
- package/Start-PatchWarden-Tunnel.cmd +0 -7
- package/docs/release-v0.3.0.md +0 -43
- package/docs/release-v0.4.0.md +0 -74
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
auditDirectSession,
|
|
3
|
+
type DirectSessionAuditOutput,
|
|
4
|
+
} from "../direct/directAudit.js";
|
|
5
|
+
import { updateDirectSession } from "../direct/directSessionStore.js";
|
|
6
|
+
|
|
7
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export interface AuditSessionInput {
|
|
10
|
+
session_id: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ── Main function ──────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export function auditSession(
|
|
16
|
+
input: AuditSessionInput
|
|
17
|
+
): DirectSessionAuditOutput {
|
|
18
|
+
const { session_id } = input;
|
|
19
|
+
|
|
20
|
+
// 1. Perform independent audit (16 audit checks)
|
|
21
|
+
const output = auditDirectSession(session_id);
|
|
22
|
+
|
|
23
|
+
// 2. Mark session as audited
|
|
24
|
+
updateDirectSession(session_id, { audited: true });
|
|
25
|
+
|
|
26
|
+
// 3. Return the audit output
|
|
27
|
+
return output;
|
|
28
|
+
}
|
package/src/tools/auditTask.ts
CHANGED
|
@@ -21,6 +21,13 @@ export interface AuditTaskOutput {
|
|
|
21
21
|
summary: string;
|
|
22
22
|
checks: AuditCheck[];
|
|
23
23
|
risks: AuditRisk[];
|
|
24
|
+
confirmed_failures: AuditCheck[];
|
|
25
|
+
possible_false_positives: Array<{
|
|
26
|
+
check: string;
|
|
27
|
+
reason: string;
|
|
28
|
+
}>;
|
|
29
|
+
manual_verification_required: boolean;
|
|
30
|
+
manual_verification_items: string[];
|
|
24
31
|
recommended_next_actions: string[];
|
|
25
32
|
}
|
|
26
33
|
|
|
@@ -78,6 +85,16 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
78
85
|
const checks: AuditCheck[] = [];
|
|
79
86
|
const risks: AuditRisk[] = [];
|
|
80
87
|
const actions: string[] = [];
|
|
88
|
+
const possibleFalsePositives: AuditTaskOutput["possible_false_positives"] = [];
|
|
89
|
+
const manualVerificationItems: string[] = [];
|
|
90
|
+
const addManualVerification = (item: string) => {
|
|
91
|
+
if (!manualVerificationItems.includes(item)) manualVerificationItems.push(item);
|
|
92
|
+
};
|
|
93
|
+
const addPossibleFalsePositive = (check: string, reason: string) => {
|
|
94
|
+
if (!possibleFalsePositives.some((item) => item.check === check && item.reason === reason)) {
|
|
95
|
+
possibleFalsePositives.push({ check, reason });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
81
98
|
|
|
82
99
|
// ── 1. Task status ──
|
|
83
100
|
const taskStatus = statusData.status || "unknown";
|
|
@@ -87,6 +104,9 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
87
104
|
result: taskStatus === "done" ? "pass" : failedStatuses.has(taskStatus) ? "fail" : "warn",
|
|
88
105
|
detail: `Task status is "${taskStatus}".`,
|
|
89
106
|
});
|
|
107
|
+
if (taskStatus !== "done" && !failedStatuses.has(taskStatus)) {
|
|
108
|
+
addManualVerification(`Task status is "${taskStatus}"; audit evidence may be incomplete until terminal state.`);
|
|
109
|
+
}
|
|
90
110
|
|
|
91
111
|
// ── 2. result.md ──
|
|
92
112
|
const resultFile = join(taskDir, "result.md");
|
|
@@ -111,6 +131,9 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
111
131
|
result: existsSync(verifyJsonFile) ? "pass" : "warn",
|
|
112
132
|
detail: existsSync(verifyJsonFile) ? "verify.json found." : "verify.json is missing.",
|
|
113
133
|
});
|
|
134
|
+
if (!existsSync(verifyJsonFile)) {
|
|
135
|
+
addManualVerification("verify.json is missing; determine whether independent verification was expected.");
|
|
136
|
+
}
|
|
114
137
|
if (existsSync(verifyJsonFile)) {
|
|
115
138
|
try {
|
|
116
139
|
const verify = JSON.parse(readFileSync(verifyJsonFile, "utf-8"));
|
|
@@ -124,15 +147,57 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
124
147
|
}
|
|
125
148
|
}
|
|
126
149
|
|
|
127
|
-
|
|
150
|
+
// Phase 4: Use new_out_of_scope_changes (task-caused) instead of out_of_scope_changes (all)
|
|
151
|
+
// Pre-existing external dirty files that didn't change during the task should NOT fail audit.
|
|
152
|
+
const newOutOfScope = Array.isArray(statusData.new_out_of_scope_changes)
|
|
153
|
+
? statusData.new_out_of_scope_changes
|
|
154
|
+
: Array.isArray(statusData.out_of_scope_changes)
|
|
155
|
+
? statusData.out_of_scope_changes
|
|
156
|
+
: [];
|
|
128
157
|
checks.push({
|
|
129
158
|
name: "scope_changes",
|
|
130
|
-
result:
|
|
131
|
-
detail:
|
|
132
|
-
? `${
|
|
133
|
-
: "No out-of-scope changes recorded.",
|
|
159
|
+
result: newOutOfScope.length > 0 ? "fail" : "pass",
|
|
160
|
+
detail: newOutOfScope.length > 0
|
|
161
|
+
? `${newOutOfScope.length} new out-of-scope change(s) detected during task execution.`
|
|
162
|
+
: "No new out-of-scope changes recorded.",
|
|
134
163
|
});
|
|
135
164
|
|
|
165
|
+
const changedFilesFile = join(taskDir, "changed-files.json");
|
|
166
|
+
if (existsSync(changedFilesFile)) {
|
|
167
|
+
try {
|
|
168
|
+
const changeEvidence = JSON.parse(readFileSync(changedFilesFile, "utf-8"));
|
|
169
|
+
const hygiene = changeEvidence.artifact_hygiene;
|
|
170
|
+
if (hygiene?.counts) {
|
|
171
|
+
const trackedArtifacts = Number(hygiene.counts.tracked_build_artifacts || 0);
|
|
172
|
+
const ignoredArtifacts = Number(hygiene.counts.ignored_untracked_artifacts || 0);
|
|
173
|
+
const runtimeFiles = Number(hygiene.counts.runtime_generated_files || 0);
|
|
174
|
+
const suspicious = Number(hygiene.counts.suspicious_changes || 0);
|
|
175
|
+
checks.push({
|
|
176
|
+
name: "artifact_hygiene",
|
|
177
|
+
result: suspicious > 0 ? "warn" : "pass",
|
|
178
|
+
detail: suspicious > 0
|
|
179
|
+
? `${suspicious} generated or artifact-like change(s) are tracked or not ignored and require review.`
|
|
180
|
+
: `${ignoredArtifacts} ignored artifact change(s) and ${runtimeFiles} runtime-generated change(s) are classified separately from source risk.`,
|
|
181
|
+
});
|
|
182
|
+
if (trackedArtifacts > 0) {
|
|
183
|
+
risks.push({ severity: "medium", description: `${trackedArtifacts} tracked build artifact change(s) require intentional source-control review.` });
|
|
184
|
+
addPossibleFalsePositive("artifact_hygiene", "Tracked build outputs may be intentional release assets rather than accidental source changes.");
|
|
185
|
+
}
|
|
186
|
+
if (suspicious > 0) {
|
|
187
|
+
actions.push("Review artifact_hygiene.suspicious_changes before accepting the task; add generated paths to Git ignore rules when appropriate.");
|
|
188
|
+
addPossibleFalsePositive("artifact_hygiene", "Artifact-like path classification is heuristic and may include intentionally maintained files.");
|
|
189
|
+
addManualVerification("Review artifact_hygiene.suspicious_changes and decide whether each path is intentional.");
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
checks.push({ name: "artifact_hygiene", result: "warn", detail: "Change evidence uses the legacy format without artifact classification." });
|
|
193
|
+
addManualVerification("Legacy changed-files evidence has no artifact classification; inspect changed paths manually.");
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
checks.push({ name: "artifact_hygiene", result: "warn", detail: "changed-files.json could not be parsed for artifact classification." });
|
|
197
|
+
addManualVerification("changed-files.json could not be parsed; inspect repository changes directly.");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
136
201
|
// ── 3. test.log ──
|
|
137
202
|
const testLogFile = join(taskDir, "test.log");
|
|
138
203
|
const hasTestLog = existsSync(testLogFile);
|
|
@@ -141,6 +206,7 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
141
206
|
result: hasTestLog ? "pass" : "warn",
|
|
142
207
|
detail: hasTestLog ? "test.log found." : "test.log is missing.",
|
|
143
208
|
});
|
|
209
|
+
if (!hasTestLog) addManualVerification("test.log is missing; confirm whether the task required an agent-side test run.");
|
|
144
210
|
|
|
145
211
|
// ── 4. git.diff ──
|
|
146
212
|
const diffFile = join(taskDir, "git.diff");
|
|
@@ -149,6 +215,7 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
149
215
|
result: existsSync(diffFile) ? "pass" : "warn",
|
|
150
216
|
detail: existsSync(diffFile) ? "git.diff found." : "git.diff is missing.",
|
|
151
217
|
});
|
|
218
|
+
if (!existsSync(diffFile)) addManualVerification("git.diff is missing; inspect repository state before accepting code changes.");
|
|
152
219
|
|
|
153
220
|
// ── 5. repo_path consistency — use resolved_repo_path, NOT resolve() ──
|
|
154
221
|
let repoPathSafe = "";
|
|
@@ -192,6 +259,7 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
192
259
|
});
|
|
193
260
|
} catch {
|
|
194
261
|
checks.push({ name: "package_json_scripts", result: "warn", detail: "package.json exists but could not be read (may be sensitive)." });
|
|
262
|
+
addManualVerification("package.json could not be read, so documented commands were not fully cross-checked.");
|
|
195
263
|
}
|
|
196
264
|
}
|
|
197
265
|
|
|
@@ -246,6 +314,11 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
246
314
|
description: `Command "npm run ${scriptName}" referenced in docs but not found in package.json scripts.`,
|
|
247
315
|
});
|
|
248
316
|
actions.push(`Verify whether "npm run ${scriptName}" should exist or if the agent fabricated it.`);
|
|
317
|
+
addPossibleFalsePositive(
|
|
318
|
+
`npm_script_${scriptName}`,
|
|
319
|
+
"Documentation may describe another package, historical version, or example command rather than the current package.json."
|
|
320
|
+
);
|
|
321
|
+
addManualVerification(`Check whether documented command "npm run ${scriptName}" belongs to another package or should be added here.`);
|
|
249
322
|
}
|
|
250
323
|
}
|
|
251
324
|
if (allNpmRunRefs.size > 0 && pkgScripts.length > 0) {
|
|
@@ -271,6 +344,7 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
271
344
|
description: `${allReleaseClaims.length} remote publish/release/deploy claim(s) found in docs. PatchWarden cannot independently verify npm/GitHub actions. Manual confirmation required.`,
|
|
272
345
|
});
|
|
273
346
|
actions.push("Manually verify all npm publish / GitHub release / git tag claims before accepting the task as complete.");
|
|
347
|
+
addManualVerification("Verify remote npm, GitHub Release, and Git tag claims against authoritative remote services.");
|
|
274
348
|
}
|
|
275
349
|
|
|
276
350
|
// ── 9. test.log Exit code check ──
|
|
@@ -282,6 +356,7 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
282
356
|
checks.push({ name: "test_command_in_log", result: "pass", detail: "test.log contains the configured test command." });
|
|
283
357
|
} else if (statusData.test_command) {
|
|
284
358
|
checks.push({ name: "test_command_in_log", result: "warn", detail: `test.log does not clearly show "${statusData.test_command}".` });
|
|
359
|
+
addManualVerification("Confirm that test.log belongs to the configured test command.");
|
|
285
360
|
}
|
|
286
361
|
|
|
287
362
|
// Extract Exit code
|
|
@@ -298,6 +373,7 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
298
373
|
} else if (statusData.test_command) {
|
|
299
374
|
checks.push({ name: "test_exit_code", result: "warn", detail: "test.log does not contain 'Exit code:' line — cannot verify test result." });
|
|
300
375
|
risks.push({ severity: "medium", description: "Test command was configured but test.log has no exit code." });
|
|
376
|
+
addManualVerification("The configured test command has no recorded exit code; rerun or inspect verification evidence.");
|
|
301
377
|
}
|
|
302
378
|
}
|
|
303
379
|
|
|
@@ -306,9 +382,11 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
306
382
|
const warnCount = checks.filter((c) => c.result === "warn").length;
|
|
307
383
|
const passCount = checks.filter((c) => c.result === "pass").length;
|
|
308
384
|
const verdict: AuditTaskOutput["verdict"] = failCount > 0 ? "fail" : warnCount > 0 ? "warn" : "pass";
|
|
385
|
+
const confirmedFailures = checks.filter((check) => check.result === "fail");
|
|
309
386
|
|
|
310
387
|
let summary = `Audit complete: ${passCount} pass, ${warnCount} warn, ${failCount} fail across ${checks.length} checks. `;
|
|
311
|
-
summary += risks.length > 0 ? `${risks.length} risk(s) identified
|
|
388
|
+
summary += risks.length > 0 ? `${risks.length} risk(s) identified. ` : "No risks identified. ";
|
|
389
|
+
summary += `${confirmedFailures.length} confirmed failure(s), ${possibleFalsePositives.length} possible false-positive warning(s), and ${manualVerificationItems.length} manual verification item(s).`;
|
|
312
390
|
|
|
313
391
|
if (actions.length === 0) {
|
|
314
392
|
actions.push("No specific actions recommended.");
|
|
@@ -330,11 +408,37 @@ export function auditTask(taskId: string): AuditTaskOutput {
|
|
|
330
408
|
"## Risks",
|
|
331
409
|
...risks.map((r) => `- [${r.severity}] ${r.description}`),
|
|
332
410
|
"",
|
|
411
|
+
"## Confirmed Failures",
|
|
412
|
+
...(confirmedFailures.length > 0
|
|
413
|
+
? confirmedFailures.map((check) => `- **${check.name}**: ${check.detail}`)
|
|
414
|
+
: ["- None."]),
|
|
415
|
+
"",
|
|
416
|
+
"## Possible False Positives",
|
|
417
|
+
...(possibleFalsePositives.length > 0
|
|
418
|
+
? possibleFalsePositives.map((item) => `- **${item.check}**: ${item.reason}`)
|
|
419
|
+
: ["- None identified."]),
|
|
420
|
+
"",
|
|
421
|
+
"## Manual Verification Required",
|
|
422
|
+
...(manualVerificationItems.length > 0
|
|
423
|
+
? manualVerificationItems.map((item) => `- ${item}`)
|
|
424
|
+
: ["- No additional manual verification identified by this audit."]),
|
|
425
|
+
"",
|
|
333
426
|
"## Recommended Actions",
|
|
334
427
|
...actions.map((a) => `- ${a}`),
|
|
335
428
|
].join("\n");
|
|
336
429
|
|
|
337
430
|
writeFileSync(join(taskDir, "independent-review.md"), reviewMd, "utf-8");
|
|
338
431
|
|
|
339
|
-
return {
|
|
432
|
+
return {
|
|
433
|
+
task_id: taskId,
|
|
434
|
+
verdict,
|
|
435
|
+
summary,
|
|
436
|
+
checks,
|
|
437
|
+
risks,
|
|
438
|
+
confirmed_failures: confirmedFailures,
|
|
439
|
+
possible_false_positives: possibleFalsePositives,
|
|
440
|
+
manual_verification_required: manualVerificationItems.length > 0,
|
|
441
|
+
manual_verification_items: manualVerificationItems,
|
|
442
|
+
recommended_next_actions: actions,
|
|
443
|
+
};
|
|
340
444
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { getConfig } from "../config.js";
|
|
3
|
+
import { PatchWardenError } from "../errors.js";
|
|
4
|
+
import { guardWorkspacePath } from "../security/pathGuard.js";
|
|
5
|
+
import { guardRuntimeSelfModification } from "../security/runtimeGuard.js";
|
|
6
|
+
import { captureRepoSnapshot } from "../runner/changeCapture.js";
|
|
7
|
+
import {
|
|
8
|
+
createDirectSession as createDirectSessionRecord,
|
|
9
|
+
} from "../direct/directSessionStore.js";
|
|
10
|
+
|
|
11
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface CreateDirectSessionInput {
|
|
14
|
+
repo_path: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CreateDirectSessionOutput {
|
|
19
|
+
session_id: string;
|
|
20
|
+
repo_path: string;
|
|
21
|
+
resolved_repo_path: string;
|
|
22
|
+
workspace_clean: boolean;
|
|
23
|
+
allowed_commands: string[];
|
|
24
|
+
expires_at: string;
|
|
25
|
+
next_action: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Tool implementation ────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export function createDirectSession(
|
|
31
|
+
input: CreateDirectSessionInput
|
|
32
|
+
): CreateDirectSessionOutput {
|
|
33
|
+
const config = getConfig();
|
|
34
|
+
|
|
35
|
+
// ── Validate repo_path ───────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
if (!input.repo_path || input.repo_path.trim() === "") {
|
|
38
|
+
throw new PatchWardenError(
|
|
39
|
+
"invalid_input",
|
|
40
|
+
"repo_path is required and must be a non-empty string.",
|
|
41
|
+
"Provide a repository path inside the configured workspaceRoot.",
|
|
42
|
+
true,
|
|
43
|
+
{ operation: "create_direct_session" }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const resolvedRepoPath = guardWorkspacePath(
|
|
48
|
+
input.repo_path,
|
|
49
|
+
config.workspaceRoot
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// ── Verify existence ─────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
if (!existsSync(resolvedRepoPath)) {
|
|
55
|
+
throw new PatchWardenError(
|
|
56
|
+
"repo_not_found",
|
|
57
|
+
`repo_path "${input.repo_path}" does not exist (resolved: "${resolvedRepoPath}").`,
|
|
58
|
+
"Provide a valid repository path inside the configured workspaceRoot.",
|
|
59
|
+
true,
|
|
60
|
+
{
|
|
61
|
+
repo_path: input.repo_path,
|
|
62
|
+
resolved_repo_path: resolvedRepoPath,
|
|
63
|
+
operation: "create_direct_session",
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Verify it is a directory ─────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
const stat = statSync(resolvedRepoPath);
|
|
71
|
+
if (!stat.isDirectory()) {
|
|
72
|
+
throw new PatchWardenError(
|
|
73
|
+
"repo_not_directory",
|
|
74
|
+
`repo_path "${input.repo_path}" is not a directory.`,
|
|
75
|
+
"Provide a directory path, not a file path.",
|
|
76
|
+
true,
|
|
77
|
+
{
|
|
78
|
+
repo_path: input.repo_path,
|
|
79
|
+
resolved_repo_path: resolvedRepoPath,
|
|
80
|
+
operation: "create_direct_session",
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Runtime self-modification protection ─────────────────────────
|
|
86
|
+
|
|
87
|
+
guardRuntimeSelfModification(resolvedRepoPath);
|
|
88
|
+
|
|
89
|
+
// ── Capture repo snapshot ────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
const snapshot = captureRepoSnapshot(resolvedRepoPath);
|
|
92
|
+
|
|
93
|
+
// ── Create session record ────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
const session = createDirectSessionRecord({
|
|
96
|
+
repo_path: input.repo_path,
|
|
97
|
+
resolved_repo_path: resolvedRepoPath,
|
|
98
|
+
title: input.title,
|
|
99
|
+
snapshot,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
session_id: session.session_id,
|
|
104
|
+
repo_path: session.repo_path,
|
|
105
|
+
resolved_repo_path: session.resolved_repo_path,
|
|
106
|
+
workspace_clean: !snapshot.workspace_dirty,
|
|
107
|
+
allowed_commands: session.allowed_commands,
|
|
108
|
+
expires_at: session.expires_at,
|
|
109
|
+
next_action:
|
|
110
|
+
"Use search_workspace/read_workspace_file, then apply_patch to make file changes within this session. " +
|
|
111
|
+
"After editing, call run_verification, finalize_direct_session, and audit_session.",
|
|
112
|
+
};
|
|
113
|
+
}
|