patchwarden 1.1.0 → 1.5.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/README.en.md +83 -8
- package/README.md +78 -10
- package/dist/assessments/agentAssessor.d.ts +1 -1
- package/dist/assessments/agentAssessor.js +3 -3
- package/dist/assessments/assessmentStore.d.ts +1 -1
- package/dist/assessments/assessmentStore.js +2 -2
- package/dist/assessments/confirmCli.js +5 -4
- package/dist/config.d.ts +7 -0
- package/dist/config.js +34 -0
- package/dist/control/middleware/auth.d.ts +10 -0
- package/dist/control/middleware/auth.js +8 -0
- package/dist/control/middleware/static.d.ts +3 -0
- package/dist/control/middleware/static.js +65 -0
- package/dist/control/routes/audit.d.ts +15 -0
- package/dist/control/routes/audit.js +277 -0
- package/dist/control/routes/evidence.d.ts +4 -0
- package/dist/control/routes/evidence.js +96 -0
- package/dist/control/routes/lineage.d.ts +3 -0
- package/dist/control/routes/lineage.js +71 -0
- package/dist/control/routes/policy.d.ts +3 -0
- package/dist/control/routes/policy.js +81 -0
- package/dist/control/routes/process.d.ts +5 -0
- package/dist/control/routes/process.js +200 -0
- package/dist/control/routes/sessions.d.ts +22 -0
- package/dist/control/routes/sessions.js +224 -0
- package/dist/control/routes/status.d.ts +6 -0
- package/dist/control/routes/status.js +250 -0
- package/dist/control/routes/taskActions.d.ts +21 -0
- package/dist/control/routes/taskActions.js +233 -0
- package/dist/control/routes/tasks.d.ts +20 -0
- package/dist/control/routes/tasks.js +310 -0
- package/dist/control/routes/workspace.d.ts +15 -0
- package/dist/control/routes/workspace.js +193 -0
- package/dist/control/runtime.d.ts +91 -0
- package/dist/control/runtime.js +392 -0
- package/dist/control/server.d.ts +13 -0
- package/dist/control/server.js +479 -0
- package/dist/control/shared.d.ts +35 -0
- package/dist/control/shared.js +288 -0
- package/dist/controlCenter.d.ts +6 -0
- package/dist/controlCenter.js +7 -2001
- package/dist/direct/directGuards.js +30 -8
- package/dist/direct/directSessionStore.d.ts +2 -0
- package/dist/direct/directVerification.js +7 -0
- package/dist/doctor.d.ts +18 -1
- package/dist/doctor.js +579 -348
- package/dist/goal/goalReport.d.ts +54 -0
- package/dist/goal/goalReport.js +204 -0
- package/dist/goal/goalStatus.d.ts +6 -0
- package/dist/goal/specKitImport.d.ts +63 -0
- package/dist/goal/specKitImport.js +220 -0
- package/dist/goal/subgoalSync.js +2 -1
- package/dist/httpServer.js +15 -12
- package/dist/index.js +7 -4
- package/dist/logging.d.ts +7 -1
- package/dist/logging.js +8 -0
- package/dist/policy/projectPolicy.d.ts +55 -0
- package/dist/policy/projectPolicy.js +286 -0
- package/dist/runner/changeCapture.d.ts +3 -3
- package/dist/runner/changeCapture.js +63 -39
- package/dist/runner/cli.js +7 -6
- package/dist/runner/postTaskCleanup.js +26 -2
- package/dist/runner/runTask.js +245 -221
- package/dist/runner/simpleProcess.js +4 -4
- package/dist/runner/watch.js +17 -14
- package/dist/security/contentRedaction.d.ts +6 -0
- package/dist/security/contentRedaction.js +22 -0
- package/dist/smoke-test.js +261 -255
- package/dist/test/unit/apply-patch.test.d.ts +1 -0
- package/dist/test/unit/apply-patch.test.js +225 -0
- package/dist/test/unit/create-task.test.d.ts +1 -0
- package/dist/test/unit/create-task.test.js +197 -0
- package/dist/test/unit/direct-guards.test.js +124 -9
- package/dist/test/unit/evidence-pack.test.d.ts +1 -0
- package/dist/test/unit/evidence-pack.test.js +224 -0
- package/dist/test/unit/get-task-status.test.d.ts +1 -0
- package/dist/test/unit/get-task-status.test.js +174 -0
- package/dist/test/unit/get-task-summary.test.d.ts +1 -0
- package/dist/test/unit/get-task-summary.test.js +146 -0
- package/dist/test/unit/goal-report.test.d.ts +1 -0
- package/dist/test/unit/goal-report.test.js +159 -0
- package/dist/test/unit/goal-subgoal-task.test.js +6 -6
- package/dist/test/unit/goal-tools-registry.test.js +6 -4
- package/dist/test/unit/path-guard.test.js +24 -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/run-task-loop.test.d.ts +1 -0
- package/dist/test/unit/run-task-loop.test.js +380 -0
- package/dist/test/unit/schema-drift-check.test.js +10 -9
- package/dist/test/unit/spec-kit-import.test.d.ts +1 -0
- package/dist/test/unit/spec-kit-import.test.js +341 -0
- package/dist/test/unit/wait-for-task.test.d.ts +1 -0
- package/dist/test/unit/wait-for-task.test.js +144 -0
- package/dist/tools/auditTask.d.ts +8 -63
- package/dist/tools/auditTask.js +12 -8
- package/dist/tools/createDirectSession.d.ts +1 -1
- package/dist/tools/createDirectSession.js +2 -2
- package/dist/tools/createTask.d.ts +2 -2
- package/dist/tools/createTask.js +4 -4
- package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
- package/dist/tools/dispatch/coreDispatch.js +282 -0
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +14 -0
- package/dist/tools/dispatch/diagnosticDispatch.js +78 -0
- package/dist/tools/dispatch/directDispatch.d.ts +8 -0
- package/dist/tools/dispatch/directDispatch.js +115 -0
- package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
- package/dist/tools/dispatch/goalDispatch.js +91 -0
- package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
- package/dist/tools/dispatch/releaseDispatch.js +45 -0
- package/dist/tools/dispatch/types.d.ts +23 -0
- package/dist/tools/dispatch/types.js +15 -0
- package/dist/tools/evidencePack.d.ts +45 -0
- package/dist/tools/evidencePack.js +375 -0
- package/dist/tools/finalizeDirectSession.d.ts +1 -1
- package/dist/tools/finalizeDirectSession.js +3 -3
- package/dist/tools/goalSubgoalTask.d.ts +1 -1
- package/dist/tools/goalSubgoalTask.js +2 -2
- package/dist/tools/healthCheck.js +3 -3
- package/dist/tools/recommendAgentForTask.d.ts +19 -0
- package/dist/tools/recommendAgentForTask.js +56 -0
- package/dist/tools/registry.d.ts +3 -3
- package/dist/tools/registry.js +333 -402
- package/dist/tools/releaseMode.d.ts +50 -0
- package/dist/tools/releaseMode.js +370 -0
- package/dist/tools/retryTask.d.ts +2 -2
- package/dist/tools/retryTask.js +2 -2
- 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/safeViews.d.ts +8 -2
- package/dist/tools/safeViews.js +4 -2
- package/dist/tools/taskLineage.d.ts +91 -0
- package/dist/tools/taskLineage.js +175 -0
- package/dist/tools/toolCatalog.d.ts +2 -2
- package/dist/tools/toolCatalog.js +6 -0
- package/dist/tools/toolRegistry.js +132 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/docs/CODE_WIKI.md +983 -0
- package/docs/agentseal-integration.md +150 -0
- package/docs/architecture.md +63 -0
- package/docs/assets/patchwarden-oss-demo.gif +0 -0
- package/docs/chatgpt-usage.md +31 -0
- package/docs/control-center/README.md +9 -0
- package/docs/dashboard-overview.md +86 -0
- package/docs/demo.md +8 -0
- package/docs/direct-session-workflow.md +98 -0
- package/docs/evidence-pack-schema.md +215 -0
- package/docs/execution-plan-2026-07-09.md +315 -0
- package/docs/lineage-evidence-pack-workflow.md +127 -0
- package/docs/mcp-inspector-testing.md +200 -0
- package/docs/open-source-application.md +162 -0
- package/docs/opencode-worker.md +151 -0
- package/docs/openhands-worker.md +181 -0
- package/docs/release-evidence.md +72 -0
- package/docs/roadmap-execution-and-acceptance.md +365 -0
- package/docs/spec-kit-integration.md +131 -0
- package/docs/task-safe-review-workflow.md +98 -0
- package/docs/threat-model.md +79 -0
- package/docs/user-feedback.md +40 -0
- package/docs/why-patchwarden.md +110 -0
- package/package.json +3 -3
- package/scripts/checks/control-center-smoke.js +443 -0
- package/scripts/checks/control-smoke.js +2 -2
- package/scripts/checks/lifecycle-smoke.js +29 -23
- package/scripts/checks/mcp-manifest-check.js +12 -0
- package/scripts/checks/mcp-smoke.js +33 -7
- package/scripts/checks/watcher-supervisor-smoke.js +1 -1
- package/scripts/generate-demo-gif.py +320 -0
- package/src/assessments/agentAssessor.ts +3 -3
- package/src/assessments/assessmentStore.ts +2 -2
- package/src/assessments/confirmCli.ts +5 -4
- package/src/config.ts +37 -0
- package/src/control/middleware/auth.ts +17 -0
- package/src/control/middleware/static.ts +71 -0
- package/src/control/routes/audit.ts +321 -0
- package/src/control/routes/evidence.ts +107 -0
- package/src/control/routes/lineage.ts +92 -0
- package/src/control/routes/policy.ts +81 -0
- package/src/control/routes/process.ts +204 -0
- package/src/control/routes/sessions.ts +251 -0
- package/src/control/routes/status.ts +325 -0
- package/src/control/routes/taskActions.ts +248 -0
- package/src/control/routes/tasks.ts +323 -0
- package/src/control/routes/workspace.ts +203 -0
- package/src/control/runtime.ts +472 -0
- package/src/control/server.ts +471 -0
- package/src/control/shared.ts +294 -0
- package/src/controlCenter.ts +7 -2150
- package/src/direct/directGuards.ts +28 -7
- package/src/direct/directSessionStore.ts +2 -0
- package/src/direct/directVerification.ts +7 -0
- package/src/doctor.ts +741 -481
- package/src/goal/goalReport.ts +271 -0
- package/src/goal/goalStatus.ts +6 -0
- package/src/goal/specKitImport.ts +355 -0
- package/src/goal/subgoalSync.ts +4 -2
- package/src/httpServer.ts +17 -14
- package/src/index.ts +7 -4
- package/src/logging.ts +10 -1
- package/src/policy/projectPolicy.ts +344 -0
- package/src/runner/changeCapture.ts +70 -42
- package/src/runner/cli.ts +7 -6
- package/src/runner/postTaskCleanup.ts +26 -2
- package/src/runner/runTask.ts +325 -223
- package/src/runner/simpleProcess.ts +4 -4
- package/src/runner/watch.ts +17 -14
- package/src/security/contentRedaction.ts +29 -0
- package/src/smoke-test.ts +254 -252
- package/src/test/unit/apply-patch.test.ts +293 -0
- package/src/test/unit/create-task.test.ts +255 -0
- package/src/test/unit/direct-guards.test.ts +178 -8
- package/src/test/unit/evidence-pack.test.ts +251 -0
- package/src/test/unit/get-task-status.test.ts +203 -0
- package/src/test/unit/get-task-summary.test.ts +173 -0
- package/src/test/unit/goal-report.test.ts +189 -0
- package/src/test/unit/goal-subgoal-task.test.ts +6 -6
- package/src/test/unit/goal-tools-registry.test.ts +7 -5
- package/src/test/unit/path-guard.test.ts +30 -0
- package/src/test/unit/project-policy-release-mode.test.ts +156 -0
- package/src/test/unit/run-task-loop.test.ts +425 -0
- package/src/test/unit/schema-drift-check.test.ts +11 -9
- package/src/test/unit/spec-kit-import.test.ts +429 -0
- package/src/test/unit/wait-for-task.test.ts +176 -0
- package/src/tools/auditTask.ts +99 -59
- package/src/tools/createDirectSession.ts +3 -3
- package/src/tools/createTask.ts +7 -7
- package/src/tools/dispatch/coreDispatch.ts +374 -0
- package/src/tools/dispatch/diagnosticDispatch.ts +101 -0
- package/src/tools/dispatch/directDispatch.ts +167 -0
- package/src/tools/dispatch/goalDispatch.ts +127 -0
- package/src/tools/dispatch/releaseDispatch.ts +65 -0
- package/src/tools/dispatch/types.ts +24 -0
- package/src/tools/evidencePack.ts +490 -0
- package/src/tools/finalizeDirectSession.ts +4 -4
- package/src/tools/goalSubgoalTask.ts +2 -2
- package/src/tools/healthCheck.ts +3 -3
- package/src/tools/listWorkspace.ts +71 -71
- package/src/tools/recommendAgentForTask.ts +79 -0
- package/src/tools/registry.ts +352 -509
- package/src/tools/releaseMode.ts +450 -0
- package/src/tools/retryTask.ts +2 -2
- package/src/tools/runDirectVerificationBundle.ts +98 -0
- package/src/tools/runTaskLoop.ts +526 -0
- package/src/tools/runVerification.ts +8 -0
- package/src/tools/safeViews.ts +4 -2
- package/src/tools/taskLineage.ts +300 -0
- package/src/tools/toolCatalog.ts +6 -0
- package/src/tools/toolRegistry.ts +132 -0
- package/src/version.ts +2 -2
- package/ui/pages/audit.html +192 -3
- package/ui/pages/dashboard.html +1070 -14
- package/ui/pages/direct-sessions.html +505 -53
- package/ui/pages/task-detail.html +456 -438
- package/ui/pages/tasks.html +598 -61
package/src/tools/auditTask.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
|
|
3
1
|
interface AuditCheck {
|
|
4
2
|
name: string;
|
|
5
3
|
result: "pass" | "warn" | "fail";
|
|
@@ -40,6 +38,43 @@ import { guardReadPath, guardWorkspacePath } from "../security/pathGuard.js";
|
|
|
40
38
|
import { guardSensitivePath, isSensitivePath } from "../security/sensitiveGuard.js";
|
|
41
39
|
import { evaluateAcceptance } from "../goal/acceptanceEngine.js";
|
|
42
40
|
import { renderAcceptanceMarkdown } from "../goal/acceptanceTemplate.js";
|
|
41
|
+
import type { ChangedFile } from "../runner/changeCapture.js";
|
|
42
|
+
|
|
43
|
+
interface ExtractedCommand {
|
|
44
|
+
type: "npm-run" | "npm-bare" | "node" | "npx" | "python";
|
|
45
|
+
name: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface TaskStatusData {
|
|
49
|
+
status?: string;
|
|
50
|
+
resolved_repo_path?: string;
|
|
51
|
+
repo_path?: string;
|
|
52
|
+
new_out_of_scope_changes?: Array<{ path: string; change: string }>;
|
|
53
|
+
out_of_scope_changes?: Array<{ path: string; change: string }>;
|
|
54
|
+
test_command?: string;
|
|
55
|
+
verify_commands?: string[];
|
|
56
|
+
forbidden?: string[];
|
|
57
|
+
done_evidence?: string[];
|
|
58
|
+
scope?: string[];
|
|
59
|
+
verification?: string[];
|
|
60
|
+
goal?: string;
|
|
61
|
+
artifact_status?: string;
|
|
62
|
+
acceptance_status?: string;
|
|
63
|
+
updated_at?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ChangedFilesEvidence {
|
|
67
|
+
changed_files?: ChangedFile[];
|
|
68
|
+
artifact_hygiene?: {
|
|
69
|
+
counts?: {
|
|
70
|
+
tracked_build_artifacts?: number | string;
|
|
71
|
+
ignored_untracked_artifacts?: number | string;
|
|
72
|
+
runtime_generated_files?: number | string;
|
|
73
|
+
suspicious_changes?: number | string;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
43
78
|
// Release claim patterns — anything that claims remote publish/release/deploy
|
|
44
79
|
const RELEASE_PATTERNS = [
|
|
45
80
|
/npm\s+package\s+version\s+published/i,
|
|
@@ -51,8 +86,8 @@ const RELEASE_PATTERNS = [
|
|
|
51
86
|
/npm\s+publish\s+completed/i,
|
|
52
87
|
/deploy(ed|ment)?\s+(to|on)\s+(npm|registry|github)/i,
|
|
53
88
|
];
|
|
54
|
-
function scanForReleaseClaims(text) {
|
|
55
|
-
const found = [];
|
|
89
|
+
function scanForReleaseClaims(text: string): string[] {
|
|
90
|
+
const found: string[] = [];
|
|
56
91
|
for (const pattern of RELEASE_PATTERNS) {
|
|
57
92
|
const match = text.match(pattern);
|
|
58
93
|
if (match)
|
|
@@ -60,8 +95,8 @@ function scanForReleaseClaims(text) {
|
|
|
60
95
|
}
|
|
61
96
|
return found;
|
|
62
97
|
}
|
|
63
|
-
function findMdFiles(dir, maxDepth = 3) {
|
|
64
|
-
const results = [];
|
|
98
|
+
function findMdFiles(dir: string, maxDepth = 3): string[] {
|
|
99
|
+
const results: string[] = [];
|
|
65
100
|
if (!existsSync(dir) || maxDepth <= 0)
|
|
66
101
|
return results;
|
|
67
102
|
try {
|
|
@@ -86,7 +121,7 @@ function findMdFiles(dir, maxDepth = 3) {
|
|
|
86
121
|
* `*` matches a single path segment (no `/`).
|
|
87
122
|
* Path separators in the pattern are expected to be `/`.
|
|
88
123
|
*/
|
|
89
|
-
function globToRegExp(pattern) {
|
|
124
|
+
function globToRegExp(pattern: string): RegExp {
|
|
90
125
|
// Normalize backslashes to forward slashes for matching consistency.
|
|
91
126
|
const normalized = pattern.replace(/\\/g, "/");
|
|
92
127
|
let re = "^";
|
|
@@ -127,7 +162,7 @@ function globToRegExp(pattern) {
|
|
|
127
162
|
* Returns pass when changed files do not match any forbidden pattern.
|
|
128
163
|
* Returns fail when any changed file path (or old_path) matches a forbidden glob.
|
|
129
164
|
*/
|
|
130
|
-
export function checkForbiddenScope(changedFiles, forbidden) {
|
|
165
|
+
export function checkForbiddenScope(changedFiles: ChangedFile[], forbidden: string[] | null): AuditCheck | null {
|
|
131
166
|
if (!forbidden || forbidden.length === 0)
|
|
132
167
|
return null;
|
|
133
168
|
if (changedFiles.length === 0) {
|
|
@@ -161,7 +196,7 @@ export function checkForbiddenScope(changedFiles, forbidden) {
|
|
|
161
196
|
* Returns pass when all listed files exist in the task directory.
|
|
162
197
|
* Returns warn when any listed file is missing.
|
|
163
198
|
*/
|
|
164
|
-
export function checkDoneEvidenceMissing(taskDir, doneEvidence) {
|
|
199
|
+
export function checkDoneEvidenceMissing(taskDir: string, doneEvidence: string[] | null): AuditCheck | null {
|
|
165
200
|
if (!doneEvidence || doneEvidence.length === 0)
|
|
166
201
|
return null;
|
|
167
202
|
const missing = [];
|
|
@@ -189,7 +224,7 @@ const CODE_EXTENSIONS = new Set([
|
|
|
189
224
|
* Returns pass when README.md or CHANGELOG.md (basename, case-insensitive) is also changed.
|
|
190
225
|
* Returns warn when code changed but neither documentation file was updated.
|
|
191
226
|
*/
|
|
192
|
-
export function checkReadmeChangelogSync(changedFiles) {
|
|
227
|
+
export function checkReadmeChangelogSync(changedFiles: ChangedFile[]): AuditCheck | null {
|
|
193
228
|
const paths = changedFiles.map((f) => f.path);
|
|
194
229
|
const hasCodeChange = paths.some((p) => {
|
|
195
230
|
const lower = p.toLowerCase();
|
|
@@ -223,14 +258,14 @@ export function checkReadmeChangelogSync(changedFiles) {
|
|
|
223
258
|
* Returns pass when package.json parses as JSON and has non-empty name and version strings.
|
|
224
259
|
* Returns warn otherwise.
|
|
225
260
|
*/
|
|
226
|
-
export function checkPackageManifestConsistency(changedFiles, repoPathSafe) {
|
|
261
|
+
export function checkPackageManifestConsistency(changedFiles: ChangedFile[], repoPathSafe: string): AuditCheck | null {
|
|
227
262
|
const includesPackageJson = changedFiles.some((f) => basename(f.path).toLowerCase() === "package.json");
|
|
228
263
|
if (!includesPackageJson)
|
|
229
264
|
return null;
|
|
230
265
|
try {
|
|
231
266
|
const pkgPath = join(repoPathSafe, "package.json");
|
|
232
267
|
const raw = readFileSync(pkgPath, "utf-8");
|
|
233
|
-
const parsed = JSON.parse(raw);
|
|
268
|
+
const parsed = JSON.parse(raw) as { name?: unknown; version?: unknown };
|
|
234
269
|
const hasName = typeof parsed.name === "string" && parsed.name.length > 0;
|
|
235
270
|
const hasVersion = typeof parsed.version === "string" && parsed.version.length > 0;
|
|
236
271
|
if (hasName && hasVersion) {
|
|
@@ -256,7 +291,7 @@ export function checkPackageManifestConsistency(changedFiles, repoPathSafe) {
|
|
|
256
291
|
* Returns pass when no changed file path (or old_path) is sensitive.
|
|
257
292
|
* Returns fail when any changed file path is flagged by isSensitivePath.
|
|
258
293
|
*/
|
|
259
|
-
export function checkSensitivePathAccess(changedFiles) {
|
|
294
|
+
export function checkSensitivePathAccess(changedFiles: ChangedFile[]): AuditCheck | null {
|
|
260
295
|
if (changedFiles.length === 0)
|
|
261
296
|
return null;
|
|
262
297
|
const hits = [];
|
|
@@ -287,8 +322,8 @@ const HIGH_RISK_COMMAND_PATTERNS = [
|
|
|
287
322
|
/\bRemove-Item\b[\s\S]{0,80}\b-Recurse\b/i,
|
|
288
323
|
/\brm\s+-rf\b/i,
|
|
289
324
|
];
|
|
290
|
-
function extractCommands(text) {
|
|
291
|
-
const found = [];
|
|
325
|
+
function extractCommands(text: string): ExtractedCommand[] {
|
|
326
|
+
const found: ExtractedCommand[] = [];
|
|
292
327
|
const runRe = /npm(?:\.cmd)?\s+run\s+([a-zA-Z0-9:_-]+)/gi;
|
|
293
328
|
let m;
|
|
294
329
|
while ((m = runRe.exec(text)) !== null) found.push({ type: "npm-run", name: m[1] });
|
|
@@ -302,31 +337,34 @@ function extractCommands(text) {
|
|
|
302
337
|
while ((m = pythonRe.exec(text)) !== null) found.push({ type: "python", name: normalizeCommandName(m[1]) });
|
|
303
338
|
return found;
|
|
304
339
|
}
|
|
305
|
-
function normalizeCommandName(value) {
|
|
340
|
+
function normalizeCommandName(value: string): string {
|
|
306
341
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
307
342
|
}
|
|
308
|
-
function commandKey(command) {
|
|
343
|
+
function commandKey(command: ExtractedCommand): string {
|
|
309
344
|
return command.name;
|
|
310
345
|
}
|
|
311
|
-
function readPackageScripts(repoPath) {
|
|
346
|
+
function readPackageScripts(repoPath: string | null): Record<string, string> {
|
|
312
347
|
if (!repoPath) return {};
|
|
313
348
|
const pkgJsonPath = join(repoPath, "package.json");
|
|
314
349
|
if (!existsSync(pkgJsonPath)) return {};
|
|
315
350
|
try {
|
|
316
351
|
guardSensitivePath(pkgJsonPath);
|
|
317
|
-
const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
318
|
-
|
|
319
|
-
|
|
352
|
+
const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf-8")) as { scripts?: Record<string, unknown> };
|
|
353
|
+
const scripts = parsed.scripts;
|
|
354
|
+
if (!scripts || typeof scripts !== "object" || Array.isArray(scripts)) return {};
|
|
355
|
+
return Object.fromEntries(
|
|
356
|
+
Object.entries(scripts).filter(([, value]) => typeof value === "string")
|
|
357
|
+
) as Record<string, string>;
|
|
320
358
|
}
|
|
321
359
|
catch {
|
|
322
360
|
return {};
|
|
323
361
|
}
|
|
324
362
|
}
|
|
325
|
-
function buildCommandWhitelist(verifyCommands, testCommand, repoPath) {
|
|
363
|
+
function buildCommandWhitelist(verifyCommands: string[], testCommand: string | null, repoPath: string | null): { allowed: Set<string>; transitive: Set<string> } {
|
|
326
364
|
const scripts = readPackageScripts(repoPath);
|
|
327
|
-
const allowed = new Set();
|
|
328
|
-
const transitive = new Set();
|
|
329
|
-
const queue = [];
|
|
365
|
+
const allowed = new Set<string>();
|
|
366
|
+
const transitive = new Set<string>();
|
|
367
|
+
const queue: Array<{ command: ExtractedCommand; depth: number }> = [];
|
|
330
368
|
const sources = [...verifyCommands];
|
|
331
369
|
if (testCommand) sources.push(testCommand);
|
|
332
370
|
for (const src of sources) {
|
|
@@ -336,9 +374,9 @@ function buildCommandWhitelist(verifyCommands, testCommand, repoPath) {
|
|
|
336
374
|
}
|
|
337
375
|
}
|
|
338
376
|
let inspectedFiles = 0;
|
|
339
|
-
const seen = new Set();
|
|
377
|
+
const seen = new Set<string>();
|
|
340
378
|
while (queue.length > 0) {
|
|
341
|
-
const { command, depth } = queue.shift()
|
|
379
|
+
const { command, depth } = queue.shift()!;
|
|
342
380
|
if (depth > 4) continue;
|
|
343
381
|
const key = `${command.type}:${command.name}`;
|
|
344
382
|
if (seen.has(key)) continue;
|
|
@@ -365,7 +403,7 @@ function buildCommandWhitelist(verifyCommands, testCommand, repoPath) {
|
|
|
365
403
|
}
|
|
366
404
|
return { allowed, transitive };
|
|
367
405
|
}
|
|
368
|
-
function readLocalScript(repoPath, scriptPath) {
|
|
406
|
+
function readLocalScript(repoPath: string, scriptPath: string): string | null {
|
|
369
407
|
const resolvedRepo = resolve(repoPath);
|
|
370
408
|
const resolvedScript = resolve(resolvedRepo, scriptPath);
|
|
371
409
|
const rel = relative(resolvedRepo, resolvedScript);
|
|
@@ -380,17 +418,17 @@ function readLocalScript(repoPath, scriptPath) {
|
|
|
380
418
|
return null;
|
|
381
419
|
}
|
|
382
420
|
}
|
|
383
|
-
function findHighRiskCommandEvidence(text) {
|
|
384
|
-
const found = [];
|
|
421
|
+
function findHighRiskCommandEvidence(text: string): string[] {
|
|
422
|
+
const found: string[] = [];
|
|
385
423
|
for (const pattern of HIGH_RISK_COMMAND_PATTERNS) {
|
|
386
424
|
const match = text.match(pattern);
|
|
387
425
|
if (match) found.push(match[0]);
|
|
388
426
|
}
|
|
389
427
|
return [...new Set(found)];
|
|
390
428
|
}
|
|
391
|
-
export function checkUnrecordedCommandExecution(testLogContent: string | null, resultMdContent: string | null, verifyCommands: string[], testCommand: string | null, repoPath: string | null = null) {
|
|
392
|
-
const hasLog = testLogContent && testLogContent.length > 0;
|
|
393
|
-
const hasResult = resultMdContent && resultMdContent.length > 0;
|
|
429
|
+
export function checkUnrecordedCommandExecution(testLogContent: string | null, resultMdContent: string | null, verifyCommands: string[], testCommand: string | null, repoPath: string | null = null): AuditCheck | null {
|
|
430
|
+
const hasLog = !!testLogContent && testLogContent.length > 0;
|
|
431
|
+
const hasResult = !!resultMdContent && resultMdContent.length > 0;
|
|
394
432
|
if (!hasLog && !hasResult) return null;
|
|
395
433
|
const combined = `${testLogContent || ""}\n${resultMdContent || ""}`;
|
|
396
434
|
const highRisk = findHighRiskCommandEvidence(combined);
|
|
@@ -398,10 +436,10 @@ export function checkUnrecordedCommandExecution(testLogContent: string | null, r
|
|
|
398
436
|
return { name: "unrecorded_command_execution", result: "fail", detail: `High-risk command evidence: ${highRisk.join(", ")}` };
|
|
399
437
|
}
|
|
400
438
|
const whitelist = buildCommandWhitelist(verifyCommands, testCommand, repoPath);
|
|
401
|
-
const discovered = [];
|
|
402
|
-
if (hasLog) discovered.push(...extractCommands(testLogContent));
|
|
403
|
-
if (hasResult) discovered.push(...extractCommands(resultMdContent));
|
|
404
|
-
const unrecorded = new Set();
|
|
439
|
+
const discovered: ExtractedCommand[] = [];
|
|
440
|
+
if (hasLog && testLogContent) discovered.push(...extractCommands(testLogContent));
|
|
441
|
+
if (hasResult && resultMdContent) discovered.push(...extractCommands(resultMdContent));
|
|
442
|
+
const unrecorded = new Set<string>();
|
|
405
443
|
for (const cmd of discovered) {
|
|
406
444
|
if (!whitelist.allowed.has(commandKey(cmd))) unrecorded.add(commandKey(cmd));
|
|
407
445
|
}
|
|
@@ -415,7 +453,7 @@ export function checkUnrecordedCommandExecution(testLogContent: string | null, r
|
|
|
415
453
|
detail: transitive.length > 0 ? `All commands are whitelisted; transitive_verified_command: ${transitive.join(", ")}` : "All commands in whitelist.",
|
|
416
454
|
};
|
|
417
455
|
}
|
|
418
|
-
export function auditTask(taskId) {
|
|
456
|
+
export function auditTask(taskId: string): AuditTaskOutput {
|
|
419
457
|
const config = getConfig();
|
|
420
458
|
const tasksDir = getTasksDir(config);
|
|
421
459
|
const taskDir = join(tasksDir, taskId);
|
|
@@ -424,17 +462,17 @@ export function auditTask(taskId) {
|
|
|
424
462
|
if (!existsSync(statusFile)) {
|
|
425
463
|
throw new Error(`Task not found: "${taskId}"`);
|
|
426
464
|
}
|
|
427
|
-
const statusData = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
428
|
-
const checks = [];
|
|
429
|
-
const risks = [];
|
|
430
|
-
const actions = [];
|
|
431
|
-
const possibleFalsePositives = [];
|
|
432
|
-
const manualVerificationItems = [];
|
|
433
|
-
const addManualVerification = (item) => {
|
|
465
|
+
const statusData = JSON.parse(readFileSync(statusFile, "utf-8")) as TaskStatusData;
|
|
466
|
+
const checks: AuditCheck[] = [];
|
|
467
|
+
const risks: AuditRisk[] = [];
|
|
468
|
+
const actions: string[] = [];
|
|
469
|
+
const possibleFalsePositives: Array<{ check: string; reason: string }> = [];
|
|
470
|
+
const manualVerificationItems: string[] = [];
|
|
471
|
+
const addManualVerification = (item: string) => {
|
|
434
472
|
if (!manualVerificationItems.includes(item))
|
|
435
473
|
manualVerificationItems.push(item);
|
|
436
474
|
};
|
|
437
|
-
const addPossibleFalsePositive = (check, reason) => {
|
|
475
|
+
const addPossibleFalsePositive = (check: string, reason: string) => {
|
|
438
476
|
if (!possibleFalsePositives.some((item) => item.check === check && item.reason === reason)) {
|
|
439
477
|
possibleFalsePositives.push({ check, reason });
|
|
440
478
|
}
|
|
@@ -478,7 +516,7 @@ export function auditTask(taskId) {
|
|
|
478
516
|
}
|
|
479
517
|
if (existsSync(verifyJsonFile)) {
|
|
480
518
|
try {
|
|
481
|
-
const verify = JSON.parse(readFileSync(verifyJsonFile, "utf-8"));
|
|
519
|
+
const verify = JSON.parse(readFileSync(verifyJsonFile, "utf-8")) as { status?: string };
|
|
482
520
|
checks.push({
|
|
483
521
|
name: "verify_status",
|
|
484
522
|
result: verify.status === "passed" ? "pass" : verify.status === "failed" ? "fail" : "warn",
|
|
@@ -504,11 +542,11 @@ export function auditTask(taskId) {
|
|
|
504
542
|
: "No new out-of-scope changes recorded.",
|
|
505
543
|
});
|
|
506
544
|
// Extract changed_files from change evidence (used by several v0.7.2 checks).
|
|
507
|
-
let changedFiles = [];
|
|
545
|
+
let changedFiles: ChangedFile[] = [];
|
|
508
546
|
const changedFilesFile = join(taskDir, "changed-files.json");
|
|
509
547
|
if (existsSync(changedFilesFile)) {
|
|
510
548
|
try {
|
|
511
|
-
const changeEvidence = JSON.parse(readFileSync(changedFilesFile, "utf-8"));
|
|
549
|
+
const changeEvidence = JSON.parse(readFileSync(changedFilesFile, "utf-8")) as ChangedFilesEvidence;
|
|
512
550
|
if (Array.isArray(changeEvidence.changed_files)) {
|
|
513
551
|
changedFiles = changeEvidence.changed_files;
|
|
514
552
|
}
|
|
@@ -596,11 +634,11 @@ export function auditTask(taskId) {
|
|
|
596
634
|
risks.push({ severity: "high", description: "repo_path inconsistent with workspace." });
|
|
597
635
|
// ── 6. package.json scripts ──
|
|
598
636
|
const pkgJsonPath = join(repoPathSafe, "package.json");
|
|
599
|
-
let pkgScripts = [];
|
|
637
|
+
let pkgScripts: string[] = [];
|
|
600
638
|
if (existsSync(pkgJsonPath)) {
|
|
601
639
|
try {
|
|
602
640
|
guardSensitivePath(pkgJsonPath);
|
|
603
|
-
const pkgData = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
641
|
+
const pkgData = JSON.parse(readFileSync(pkgJsonPath, "utf-8")) as { scripts?: Record<string, unknown> };
|
|
604
642
|
pkgScripts = Object.keys(pkgData.scripts || {});
|
|
605
643
|
checks.push({
|
|
606
644
|
name: "package_json_scripts",
|
|
@@ -625,10 +663,10 @@ export function auditTask(taskId) {
|
|
|
625
663
|
docsToScan.push(...findMdFiles(docsDir));
|
|
626
664
|
}
|
|
627
665
|
// Collect all npm run references from all docs
|
|
628
|
-
const allNpmRunRefs = new Set();
|
|
629
|
-
const allReleaseClaims = [];
|
|
666
|
+
const allNpmRunRefs = new Set<string>();
|
|
667
|
+
const allReleaseClaims: string[] = [];
|
|
630
668
|
for (const docPath of docsToScan) {
|
|
631
|
-
let content;
|
|
669
|
+
let content: string;
|
|
632
670
|
try {
|
|
633
671
|
if (docPath !== resultFile) {
|
|
634
672
|
guardReadPath(docPath, config.workspaceRoot);
|
|
@@ -693,9 +731,9 @@ export function auditTask(taskId) {
|
|
|
693
731
|
addManualVerification("Verify remote npm, GitHub Release, and Git tag claims against authoritative remote services.");
|
|
694
732
|
}
|
|
695
733
|
// ── 9. test.log Exit code check ──
|
|
696
|
-
let testLogContent = null;
|
|
734
|
+
let testLogContent: string | null = null;
|
|
697
735
|
if (hasTestLog) {
|
|
698
|
-
let logContent;
|
|
736
|
+
let logContent: string;
|
|
699
737
|
try {
|
|
700
738
|
logContent = readFileSync(testLogFile, "utf-8");
|
|
701
739
|
}
|
|
@@ -732,7 +770,7 @@ export function auditTask(taskId) {
|
|
|
732
770
|
}
|
|
733
771
|
// ── v0.7.2: New audit checks ──
|
|
734
772
|
// Read result.md content (guarded) for the unrecorded_command_execution check.
|
|
735
|
-
let resultMdContent = null;
|
|
773
|
+
let resultMdContent: string | null = null;
|
|
736
774
|
if (hasResult) {
|
|
737
775
|
try {
|
|
738
776
|
resultMdContent = readFileSync(resultFile, "utf-8");
|
|
@@ -818,8 +856,10 @@ export function auditTask(taskId) {
|
|
|
818
856
|
if (!existsSync(verifyJsonFile))
|
|
819
857
|
return null;
|
|
820
858
|
try {
|
|
821
|
-
const v = JSON.parse(readFileSync(verifyJsonFile, "utf-8"));
|
|
822
|
-
|
|
859
|
+
const v = JSON.parse(readFileSync(verifyJsonFile, "utf-8")) as { status?: string };
|
|
860
|
+
if (v.status === "passed") return "passed" as const;
|
|
861
|
+
if (v.status === "failed") return "failed" as const;
|
|
862
|
+
return "skipped" as const;
|
|
823
863
|
}
|
|
824
864
|
catch {
|
|
825
865
|
return null;
|
|
@@ -27,9 +27,9 @@ export interface CreateDirectSessionOutput {
|
|
|
27
27
|
|
|
28
28
|
// ── Tool implementation ────────────────────────────────────────────
|
|
29
29
|
|
|
30
|
-
export function createDirectSession(
|
|
30
|
+
export async function createDirectSession(
|
|
31
31
|
input: CreateDirectSessionInput
|
|
32
|
-
): CreateDirectSessionOutput {
|
|
32
|
+
): Promise<CreateDirectSessionOutput> {
|
|
33
33
|
const config = getConfig();
|
|
34
34
|
|
|
35
35
|
// ── Validate repo_path ───────────────────────────────────────────
|
|
@@ -88,7 +88,7 @@ export function createDirectSession(
|
|
|
88
88
|
|
|
89
89
|
// ── Capture repo snapshot ────────────────────────────────────────
|
|
90
90
|
|
|
91
|
-
const snapshot = captureRepoSnapshot(resolvedRepoPath);
|
|
91
|
+
const snapshot = await captureRepoSnapshot(resolvedRepoPath);
|
|
92
92
|
|
|
93
93
|
// ── Create session record ────────────────────────────────────────
|
|
94
94
|
|
package/src/tools/createTask.ts
CHANGED
|
@@ -181,9 +181,9 @@ export interface CreateTaskOutput {
|
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
export function createTask(input: CreateTaskInput & { execution_mode: "assess_only" }): AssessOnlyOutput
|
|
185
|
-
export function createTask(input: CreateTaskInput): CreateTaskOutput
|
|
186
|
-
export function createTask(input: CreateTaskInput): CreateTaskResult {
|
|
184
|
+
export function createTask(input: CreateTaskInput & { execution_mode: "assess_only" }): Promise<AssessOnlyOutput>;
|
|
185
|
+
export function createTask(input: CreateTaskInput): Promise<CreateTaskOutput>;
|
|
186
|
+
export async function createTask(input: CreateTaskInput): Promise<CreateTaskResult> {
|
|
187
187
|
const config = getConfig();
|
|
188
188
|
const tasksDir = getTasksDir(config);
|
|
189
189
|
const plansDir = getPlansDir(config);
|
|
@@ -243,7 +243,7 @@ export function createTask(input: CreateTaskInput): CreateTaskResult {
|
|
|
243
243
|
|
|
244
244
|
// Resolve repo alias if configured
|
|
245
245
|
let resolvedRepoPath = effectiveInput.repo_path?.trim() || "";
|
|
246
|
-
const aliases =
|
|
246
|
+
const aliases = config.repoAliases;
|
|
247
247
|
if (aliases && resolvedRepoPath && aliases[resolvedRepoPath]) {
|
|
248
248
|
resolvedRepoPath = aliases[resolvedRepoPath];
|
|
249
249
|
}
|
|
@@ -382,7 +382,7 @@ export function createTask(input: CreateTaskInput): CreateTaskResult {
|
|
|
382
382
|
planBlockReason = e instanceof PatchWardenError ? e.reason : "plan_content_blocked";
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
-
const snapshot = captureRepoSnapshot(safeRepoPath);
|
|
385
|
+
const snapshot = await captureRepoSnapshot(safeRepoPath);
|
|
386
386
|
const snapshotTruncated = snapshot.warnings.some((w) => w.includes("snapshot limited"));
|
|
387
387
|
|
|
388
388
|
let riskResult;
|
|
@@ -441,7 +441,7 @@ export function createTask(input: CreateTaskInput): CreateTaskResult {
|
|
|
441
441
|
preGeneratedAssessmentDir = createAssessmentDir(preGeneratedAssessmentId);
|
|
442
442
|
|
|
443
443
|
const assessorAgentName = config.agentAssessmentAgentName || effectiveInput.agent;
|
|
444
|
-
agentAssessmentSummary = runAgentAssessment({
|
|
444
|
+
agentAssessmentSummary = await runAgentAssessment({
|
|
445
445
|
assessmentId: preGeneratedAssessmentId,
|
|
446
446
|
assessmentDir: preGeneratedAssessmentDir,
|
|
447
447
|
agentName: assessorAgentName,
|
|
@@ -572,7 +572,7 @@ export function createTask(input: CreateTaskInput): CreateTaskResult {
|
|
|
572
572
|
|
|
573
573
|
// ── execute mode with assessment_id: validate freshness ──
|
|
574
574
|
if (assessmentRecord) {
|
|
575
|
-
const snapshot = captureRepoSnapshot(safeRepoPath);
|
|
575
|
+
const snapshot = await captureRepoSnapshot(safeRepoPath);
|
|
576
576
|
const validation = validateAssessmentFreshness(input.assessment_id!, snapshot);
|
|
577
577
|
if (!validation.valid) {
|
|
578
578
|
throw new PatchWardenError(
|