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.
Files changed (258) hide show
  1. package/README.en.md +83 -8
  2. package/README.md +78 -10
  3. package/dist/assessments/agentAssessor.d.ts +1 -1
  4. package/dist/assessments/agentAssessor.js +3 -3
  5. package/dist/assessments/assessmentStore.d.ts +1 -1
  6. package/dist/assessments/assessmentStore.js +2 -2
  7. package/dist/assessments/confirmCli.js +5 -4
  8. package/dist/config.d.ts +7 -0
  9. package/dist/config.js +34 -0
  10. package/dist/control/middleware/auth.d.ts +10 -0
  11. package/dist/control/middleware/auth.js +8 -0
  12. package/dist/control/middleware/static.d.ts +3 -0
  13. package/dist/control/middleware/static.js +65 -0
  14. package/dist/control/routes/audit.d.ts +15 -0
  15. package/dist/control/routes/audit.js +277 -0
  16. package/dist/control/routes/evidence.d.ts +4 -0
  17. package/dist/control/routes/evidence.js +96 -0
  18. package/dist/control/routes/lineage.d.ts +3 -0
  19. package/dist/control/routes/lineage.js +71 -0
  20. package/dist/control/routes/policy.d.ts +3 -0
  21. package/dist/control/routes/policy.js +81 -0
  22. package/dist/control/routes/process.d.ts +5 -0
  23. package/dist/control/routes/process.js +200 -0
  24. package/dist/control/routes/sessions.d.ts +22 -0
  25. package/dist/control/routes/sessions.js +224 -0
  26. package/dist/control/routes/status.d.ts +6 -0
  27. package/dist/control/routes/status.js +250 -0
  28. package/dist/control/routes/taskActions.d.ts +21 -0
  29. package/dist/control/routes/taskActions.js +233 -0
  30. package/dist/control/routes/tasks.d.ts +20 -0
  31. package/dist/control/routes/tasks.js +310 -0
  32. package/dist/control/routes/workspace.d.ts +15 -0
  33. package/dist/control/routes/workspace.js +193 -0
  34. package/dist/control/runtime.d.ts +91 -0
  35. package/dist/control/runtime.js +392 -0
  36. package/dist/control/server.d.ts +13 -0
  37. package/dist/control/server.js +479 -0
  38. package/dist/control/shared.d.ts +35 -0
  39. package/dist/control/shared.js +288 -0
  40. package/dist/controlCenter.d.ts +6 -0
  41. package/dist/controlCenter.js +7 -2001
  42. package/dist/direct/directGuards.js +30 -8
  43. package/dist/direct/directSessionStore.d.ts +2 -0
  44. package/dist/direct/directVerification.js +7 -0
  45. package/dist/doctor.d.ts +18 -1
  46. package/dist/doctor.js +579 -348
  47. package/dist/goal/goalReport.d.ts +54 -0
  48. package/dist/goal/goalReport.js +204 -0
  49. package/dist/goal/goalStatus.d.ts +6 -0
  50. package/dist/goal/specKitImport.d.ts +63 -0
  51. package/dist/goal/specKitImport.js +220 -0
  52. package/dist/goal/subgoalSync.js +2 -1
  53. package/dist/httpServer.js +15 -12
  54. package/dist/index.js +7 -4
  55. package/dist/logging.d.ts +7 -1
  56. package/dist/logging.js +8 -0
  57. package/dist/policy/projectPolicy.d.ts +55 -0
  58. package/dist/policy/projectPolicy.js +286 -0
  59. package/dist/runner/changeCapture.d.ts +3 -3
  60. package/dist/runner/changeCapture.js +63 -39
  61. package/dist/runner/cli.js +7 -6
  62. package/dist/runner/postTaskCleanup.js +26 -2
  63. package/dist/runner/runTask.js +245 -221
  64. package/dist/runner/simpleProcess.js +4 -4
  65. package/dist/runner/watch.js +17 -14
  66. package/dist/security/contentRedaction.d.ts +6 -0
  67. package/dist/security/contentRedaction.js +22 -0
  68. package/dist/smoke-test.js +261 -255
  69. package/dist/test/unit/apply-patch.test.d.ts +1 -0
  70. package/dist/test/unit/apply-patch.test.js +225 -0
  71. package/dist/test/unit/create-task.test.d.ts +1 -0
  72. package/dist/test/unit/create-task.test.js +197 -0
  73. package/dist/test/unit/direct-guards.test.js +124 -9
  74. package/dist/test/unit/evidence-pack.test.d.ts +1 -0
  75. package/dist/test/unit/evidence-pack.test.js +224 -0
  76. package/dist/test/unit/get-task-status.test.d.ts +1 -0
  77. package/dist/test/unit/get-task-status.test.js +174 -0
  78. package/dist/test/unit/get-task-summary.test.d.ts +1 -0
  79. package/dist/test/unit/get-task-summary.test.js +146 -0
  80. package/dist/test/unit/goal-report.test.d.ts +1 -0
  81. package/dist/test/unit/goal-report.test.js +159 -0
  82. package/dist/test/unit/goal-subgoal-task.test.js +6 -6
  83. package/dist/test/unit/goal-tools-registry.test.js +6 -4
  84. package/dist/test/unit/path-guard.test.js +24 -0
  85. package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
  86. package/dist/test/unit/project-policy-release-mode.test.js +125 -0
  87. package/dist/test/unit/run-task-loop.test.d.ts +1 -0
  88. package/dist/test/unit/run-task-loop.test.js +380 -0
  89. package/dist/test/unit/schema-drift-check.test.js +10 -9
  90. package/dist/test/unit/spec-kit-import.test.d.ts +1 -0
  91. package/dist/test/unit/spec-kit-import.test.js +341 -0
  92. package/dist/test/unit/wait-for-task.test.d.ts +1 -0
  93. package/dist/test/unit/wait-for-task.test.js +144 -0
  94. package/dist/tools/auditTask.d.ts +8 -63
  95. package/dist/tools/auditTask.js +12 -8
  96. package/dist/tools/createDirectSession.d.ts +1 -1
  97. package/dist/tools/createDirectSession.js +2 -2
  98. package/dist/tools/createTask.d.ts +2 -2
  99. package/dist/tools/createTask.js +4 -4
  100. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  101. package/dist/tools/dispatch/coreDispatch.js +282 -0
  102. package/dist/tools/dispatch/diagnosticDispatch.d.ts +14 -0
  103. package/dist/tools/dispatch/diagnosticDispatch.js +78 -0
  104. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  105. package/dist/tools/dispatch/directDispatch.js +115 -0
  106. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  107. package/dist/tools/dispatch/goalDispatch.js +91 -0
  108. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  109. package/dist/tools/dispatch/releaseDispatch.js +45 -0
  110. package/dist/tools/dispatch/types.d.ts +23 -0
  111. package/dist/tools/dispatch/types.js +15 -0
  112. package/dist/tools/evidencePack.d.ts +45 -0
  113. package/dist/tools/evidencePack.js +375 -0
  114. package/dist/tools/finalizeDirectSession.d.ts +1 -1
  115. package/dist/tools/finalizeDirectSession.js +3 -3
  116. package/dist/tools/goalSubgoalTask.d.ts +1 -1
  117. package/dist/tools/goalSubgoalTask.js +2 -2
  118. package/dist/tools/healthCheck.js +3 -3
  119. package/dist/tools/recommendAgentForTask.d.ts +19 -0
  120. package/dist/tools/recommendAgentForTask.js +56 -0
  121. package/dist/tools/registry.d.ts +3 -3
  122. package/dist/tools/registry.js +333 -402
  123. package/dist/tools/releaseMode.d.ts +50 -0
  124. package/dist/tools/releaseMode.js +370 -0
  125. package/dist/tools/retryTask.d.ts +2 -2
  126. package/dist/tools/retryTask.js +2 -2
  127. package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
  128. package/dist/tools/runDirectVerificationBundle.js +64 -0
  129. package/dist/tools/runTaskLoop.d.ts +57 -0
  130. package/dist/tools/runTaskLoop.js +417 -0
  131. package/dist/tools/runVerification.d.ts +4 -0
  132. package/dist/tools/runVerification.js +4 -0
  133. package/dist/tools/safeViews.d.ts +8 -2
  134. package/dist/tools/safeViews.js +4 -2
  135. package/dist/tools/taskLineage.d.ts +91 -0
  136. package/dist/tools/taskLineage.js +175 -0
  137. package/dist/tools/toolCatalog.d.ts +2 -2
  138. package/dist/tools/toolCatalog.js +6 -0
  139. package/dist/tools/toolRegistry.js +132 -0
  140. package/dist/version.d.ts +2 -2
  141. package/dist/version.js +2 -2
  142. package/docs/CODE_WIKI.md +983 -0
  143. package/docs/agentseal-integration.md +150 -0
  144. package/docs/architecture.md +63 -0
  145. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  146. package/docs/chatgpt-usage.md +31 -0
  147. package/docs/control-center/README.md +9 -0
  148. package/docs/dashboard-overview.md +86 -0
  149. package/docs/demo.md +8 -0
  150. package/docs/direct-session-workflow.md +98 -0
  151. package/docs/evidence-pack-schema.md +215 -0
  152. package/docs/execution-plan-2026-07-09.md +315 -0
  153. package/docs/lineage-evidence-pack-workflow.md +127 -0
  154. package/docs/mcp-inspector-testing.md +200 -0
  155. package/docs/open-source-application.md +162 -0
  156. package/docs/opencode-worker.md +151 -0
  157. package/docs/openhands-worker.md +181 -0
  158. package/docs/release-evidence.md +72 -0
  159. package/docs/roadmap-execution-and-acceptance.md +365 -0
  160. package/docs/spec-kit-integration.md +131 -0
  161. package/docs/task-safe-review-workflow.md +98 -0
  162. package/docs/threat-model.md +79 -0
  163. package/docs/user-feedback.md +40 -0
  164. package/docs/why-patchwarden.md +110 -0
  165. package/package.json +3 -3
  166. package/scripts/checks/control-center-smoke.js +443 -0
  167. package/scripts/checks/control-smoke.js +2 -2
  168. package/scripts/checks/lifecycle-smoke.js +29 -23
  169. package/scripts/checks/mcp-manifest-check.js +12 -0
  170. package/scripts/checks/mcp-smoke.js +33 -7
  171. package/scripts/checks/watcher-supervisor-smoke.js +1 -1
  172. package/scripts/generate-demo-gif.py +320 -0
  173. package/src/assessments/agentAssessor.ts +3 -3
  174. package/src/assessments/assessmentStore.ts +2 -2
  175. package/src/assessments/confirmCli.ts +5 -4
  176. package/src/config.ts +37 -0
  177. package/src/control/middleware/auth.ts +17 -0
  178. package/src/control/middleware/static.ts +71 -0
  179. package/src/control/routes/audit.ts +321 -0
  180. package/src/control/routes/evidence.ts +107 -0
  181. package/src/control/routes/lineage.ts +92 -0
  182. package/src/control/routes/policy.ts +81 -0
  183. package/src/control/routes/process.ts +204 -0
  184. package/src/control/routes/sessions.ts +251 -0
  185. package/src/control/routes/status.ts +325 -0
  186. package/src/control/routes/taskActions.ts +248 -0
  187. package/src/control/routes/tasks.ts +323 -0
  188. package/src/control/routes/workspace.ts +203 -0
  189. package/src/control/runtime.ts +472 -0
  190. package/src/control/server.ts +471 -0
  191. package/src/control/shared.ts +294 -0
  192. package/src/controlCenter.ts +7 -2150
  193. package/src/direct/directGuards.ts +28 -7
  194. package/src/direct/directSessionStore.ts +2 -0
  195. package/src/direct/directVerification.ts +7 -0
  196. package/src/doctor.ts +741 -481
  197. package/src/goal/goalReport.ts +271 -0
  198. package/src/goal/goalStatus.ts +6 -0
  199. package/src/goal/specKitImport.ts +355 -0
  200. package/src/goal/subgoalSync.ts +4 -2
  201. package/src/httpServer.ts +17 -14
  202. package/src/index.ts +7 -4
  203. package/src/logging.ts +10 -1
  204. package/src/policy/projectPolicy.ts +344 -0
  205. package/src/runner/changeCapture.ts +70 -42
  206. package/src/runner/cli.ts +7 -6
  207. package/src/runner/postTaskCleanup.ts +26 -2
  208. package/src/runner/runTask.ts +325 -223
  209. package/src/runner/simpleProcess.ts +4 -4
  210. package/src/runner/watch.ts +17 -14
  211. package/src/security/contentRedaction.ts +29 -0
  212. package/src/smoke-test.ts +254 -252
  213. package/src/test/unit/apply-patch.test.ts +293 -0
  214. package/src/test/unit/create-task.test.ts +255 -0
  215. package/src/test/unit/direct-guards.test.ts +178 -8
  216. package/src/test/unit/evidence-pack.test.ts +251 -0
  217. package/src/test/unit/get-task-status.test.ts +203 -0
  218. package/src/test/unit/get-task-summary.test.ts +173 -0
  219. package/src/test/unit/goal-report.test.ts +189 -0
  220. package/src/test/unit/goal-subgoal-task.test.ts +6 -6
  221. package/src/test/unit/goal-tools-registry.test.ts +7 -5
  222. package/src/test/unit/path-guard.test.ts +30 -0
  223. package/src/test/unit/project-policy-release-mode.test.ts +156 -0
  224. package/src/test/unit/run-task-loop.test.ts +425 -0
  225. package/src/test/unit/schema-drift-check.test.ts +11 -9
  226. package/src/test/unit/spec-kit-import.test.ts +429 -0
  227. package/src/test/unit/wait-for-task.test.ts +176 -0
  228. package/src/tools/auditTask.ts +99 -59
  229. package/src/tools/createDirectSession.ts +3 -3
  230. package/src/tools/createTask.ts +7 -7
  231. package/src/tools/dispatch/coreDispatch.ts +374 -0
  232. package/src/tools/dispatch/diagnosticDispatch.ts +101 -0
  233. package/src/tools/dispatch/directDispatch.ts +167 -0
  234. package/src/tools/dispatch/goalDispatch.ts +127 -0
  235. package/src/tools/dispatch/releaseDispatch.ts +65 -0
  236. package/src/tools/dispatch/types.ts +24 -0
  237. package/src/tools/evidencePack.ts +490 -0
  238. package/src/tools/finalizeDirectSession.ts +4 -4
  239. package/src/tools/goalSubgoalTask.ts +2 -2
  240. package/src/tools/healthCheck.ts +3 -3
  241. package/src/tools/listWorkspace.ts +71 -71
  242. package/src/tools/recommendAgentForTask.ts +79 -0
  243. package/src/tools/registry.ts +352 -509
  244. package/src/tools/releaseMode.ts +450 -0
  245. package/src/tools/retryTask.ts +2 -2
  246. package/src/tools/runDirectVerificationBundle.ts +98 -0
  247. package/src/tools/runTaskLoop.ts +526 -0
  248. package/src/tools/runVerification.ts +8 -0
  249. package/src/tools/safeViews.ts +4 -2
  250. package/src/tools/taskLineage.ts +300 -0
  251. package/src/tools/toolCatalog.ts +6 -0
  252. package/src/tools/toolRegistry.ts +132 -0
  253. package/src/version.ts +2 -2
  254. package/ui/pages/audit.html +192 -3
  255. package/ui/pages/dashboard.html +1070 -14
  256. package/ui/pages/direct-sessions.html +505 -53
  257. package/ui/pages/task-detail.html +456 -438
  258. package/ui/pages/tasks.html +598 -61
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Dispatch handlers for release-mode tools (v1.3.0+).
3
+ *
4
+ * Wraps releaseCheck, releasePrepare, releaseVerify, and releaseCleanup
5
+ * from releaseMode.ts.
6
+ */
7
+ import { releaseCheck, releasePrepare, releaseVerify, releaseCleanup, } from "../releaseMode.js";
8
+ import { toResult } from "./types.js";
9
+ export const releaseHandlers = {
10
+ release_check: async (args) => {
11
+ return toResult(await releaseCheck({
12
+ repo_path: String(args?.repo_path ?? ""),
13
+ target_stage: String(args?.target_stage ?? "local_ready"),
14
+ package_name: args?.package_name ? String(args.package_name) : undefined,
15
+ version: args?.version ? String(args.version) : undefined,
16
+ github_repo: args?.github_repo ? String(args.github_repo) : undefined,
17
+ branch: args?.branch ? String(args.branch) : undefined,
18
+ }));
19
+ },
20
+ release_prepare: async (args) => {
21
+ return toResult(releasePrepare({
22
+ repo_path: String(args?.repo_path ?? ""),
23
+ required_commands: Array.isArray(args?.required_commands)
24
+ ? args.required_commands.map(String)
25
+ : undefined,
26
+ timeout_seconds: args?.timeout_seconds !== undefined ? Number(args.timeout_seconds) : undefined,
27
+ }));
28
+ },
29
+ release_verify: async (args) => {
30
+ return toResult(await releaseVerify({
31
+ repo_path: String(args?.repo_path ?? ""),
32
+ package_name: args?.package_name ? String(args.package_name) : undefined,
33
+ version: args?.version ? String(args.version) : undefined,
34
+ github_repo: args?.github_repo ? String(args.github_repo) : undefined,
35
+ branch: args?.branch ? String(args.branch) : undefined,
36
+ }));
37
+ },
38
+ release_cleanup: async (args) => {
39
+ return toResult(releaseCleanup({
40
+ repo_path: String(args?.repo_path ?? ""),
41
+ dry_run: args?.dry_run !== undefined ? Boolean(args.dry_run) : undefined,
42
+ patterns: Array.isArray(args?.patterns) ? args.patterns.map(String) : undefined,
43
+ }));
44
+ },
45
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared types and helpers for the dispatch map.
3
+ *
4
+ * Each domain dispatch file exports a `ToolHandlerMap` whose handlers
5
+ * directly call the existing tool functions — no logic is rewritten.
6
+ */
7
+ export type ToolHandler = (args: Record<string, unknown> | undefined) => Promise<{
8
+ content: Array<{
9
+ type: string;
10
+ text: string;
11
+ }>;
12
+ }>;
13
+ export type ToolHandlerMap = Record<string, ToolHandler>;
14
+ /**
15
+ * Wrap a plain data value into the MCP CallToolResult content envelope.
16
+ * Mirrors the original `toResult` helper from registry.ts.
17
+ */
18
+ export declare function toResult(data: unknown): {
19
+ content: Array<{
20
+ type: string;
21
+ text: string;
22
+ }>;
23
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared types and helpers for the dispatch map.
3
+ *
4
+ * Each domain dispatch file exports a `ToolHandlerMap` whose handlers
5
+ * directly call the existing tool functions — no logic is rewritten.
6
+ */
7
+ /**
8
+ * Wrap a plain data value into the MCP CallToolResult content envelope.
9
+ * Mirrors the original `toResult` helper from registry.ts.
10
+ */
11
+ export function toResult(data) {
12
+ return {
13
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
14
+ };
15
+ }
@@ -0,0 +1,45 @@
1
+ import { type SafeTaskLineage } from "./taskLineage.js";
2
+ export interface SafeEvidencePack {
3
+ evidence_pack_id: string;
4
+ lineage_id: string;
5
+ generated_at: string;
6
+ path: string;
7
+ files: {
8
+ json: string;
9
+ markdown: string;
10
+ risk: string;
11
+ verify: string;
12
+ diffstat: string;
13
+ lineage: string;
14
+ attestation: string;
15
+ redactions: string;
16
+ };
17
+ lineage: SafeTaskLineage;
18
+ policy: {
19
+ valid: boolean;
20
+ issue_count: number;
21
+ release_readiness: unknown;
22
+ };
23
+ catalog: {
24
+ server_version: string;
25
+ schema_epoch: string;
26
+ tool_profile: string | null;
27
+ tool_count: number | null;
28
+ tool_manifest_sha256: string | null;
29
+ };
30
+ omitted: string[];
31
+ next_action: string;
32
+ bounded: true;
33
+ }
34
+ export declare function exportTaskEvidencePack(input: {
35
+ lineage_id: string;
36
+ max_items?: number;
37
+ }): SafeEvidencePack;
38
+ export declare function listEvidencePacks(options?: {
39
+ max_items?: number;
40
+ }): {
41
+ evidence_packs: SafeEvidencePack[];
42
+ total: number;
43
+ truncated: boolean;
44
+ };
45
+ export declare function readEvidencePack(lineageId: string): SafeEvidencePack | null;
@@ -0,0 +1,375 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { getConfig } from "../config.js";
5
+ import { guardReadPath, guardWorkspacePath } from "../security/pathGuard.js";
6
+ import { countRedactionsByCategory, redactSensitiveValue } from "../security/contentRedaction.js";
7
+ import { getProjectPolicySummary } from "../policy/projectPolicy.js";
8
+ import { getTaskLineage } from "./taskLineage.js";
9
+ import { getLastToolCatalogSnapshot } from "./toolCatalog.js";
10
+ import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
11
+ import { PatchWardenError } from "../errors.js";
12
+ export function exportTaskEvidencePack(input) {
13
+ const lineageId = normalizeLineageId(input.lineage_id);
14
+ const maxItems = normalizeMaxItems(input.max_items);
15
+ const config = getConfig();
16
+ const lineage = getTaskLineage(lineageId, { max_items: maxItems });
17
+ const packDir = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs", lineageId);
18
+ guardWorkspacePath(packDir, config.workspaceRoot);
19
+ mkdirSync(packDir, { recursive: true });
20
+ const policySummary = readPolicySummary(lineage.repo_path);
21
+ const catalog = safeCatalog();
22
+ const generatedAt = new Date().toISOString();
23
+ const filePaths = {
24
+ json: join(packDir, "evidence.json"),
25
+ markdown: join(packDir, "EVIDENCE.md"),
26
+ risk: join(packDir, "risk.json"),
27
+ verify: join(packDir, "verify.json"),
28
+ diffstat: join(packDir, "diffstat.json"),
29
+ lineage: join(packDir, "lineage.json"),
30
+ attestation: join(packDir, "attestation.json"),
31
+ redactions: join(packDir, "redactions.json"),
32
+ };
33
+ // Build v2 structured artifact contents (raw, before redaction).
34
+ const riskContent = buildRiskContent(lineage);
35
+ const verifyContent = buildVerifyContent(lineage);
36
+ const diffstatContent = buildDiffstatContent(lineage, config);
37
+ const lineageSummary = buildLineageSummary(lineage);
38
+ const attestationContent = buildAttestation(catalog, generatedAt);
39
+ // Count redactions across all v2 raw contents for the audit trail.
40
+ const v2RawPayloads = [
41
+ riskContent,
42
+ verifyContent,
43
+ diffstatContent,
44
+ lineageSummary,
45
+ attestationContent,
46
+ ].map((value) => JSON.stringify(value));
47
+ const aggregated = new Map();
48
+ for (const raw of v2RawPayloads) {
49
+ for (const entry of countRedactionsByCategory(raw)) {
50
+ const existing = aggregated.get(entry.category);
51
+ if (existing)
52
+ existing.count += entry.count;
53
+ else
54
+ aggregated.set(entry.category, { category: entry.category, reason: entry.reason, count: entry.count });
55
+ }
56
+ }
57
+ const redactionsList = [...aggregated.values()];
58
+ const redactionsContent = {
59
+ redactions: redactionsList,
60
+ total_redacted: redactionsList.reduce((sum, entry) => sum + entry.count, 0),
61
+ bounded: true,
62
+ note: "Only categories and counts are recorded; original secret values are never persisted.",
63
+ };
64
+ // Redact and write each v2 file.
65
+ writeFileSync(filePaths.risk, JSON.stringify(redactSensitiveValue(riskContent).value, null, 2) + "\n", "utf-8");
66
+ writeFileSync(filePaths.verify, JSON.stringify(redactSensitiveValue(verifyContent).value, null, 2) + "\n", "utf-8");
67
+ writeFileSync(filePaths.diffstat, JSON.stringify(redactSensitiveValue(diffstatContent).value, null, 2) + "\n", "utf-8");
68
+ writeFileSync(filePaths.lineage, JSON.stringify(redactSensitiveValue(lineageSummary).value, null, 2) + "\n", "utf-8");
69
+ writeFileSync(filePaths.attestation, JSON.stringify(redactSensitiveValue(attestationContent).value, null, 2) + "\n", "utf-8");
70
+ writeFileSync(filePaths.redactions, JSON.stringify(redactSensitiveValue(redactionsContent).value, null, 2) + "\n", "utf-8");
71
+ const safePack = redactSensitiveValue({
72
+ evidence_pack_id: `evidence_${lineageId}`,
73
+ lineage_id: lineageId,
74
+ generated_at: generatedAt,
75
+ path: packDir,
76
+ files: filePaths,
77
+ lineage,
78
+ policy: {
79
+ valid: policySummary.valid,
80
+ issue_count: policySummary.issues.length,
81
+ release_readiness: policySummary.release_readiness,
82
+ },
83
+ catalog,
84
+ omitted: [
85
+ "stdout",
86
+ "stderr",
87
+ "diff",
88
+ "verification command logs",
89
+ "full artifact markdown",
90
+ "sensitive file contents",
91
+ "raw secret values (redactions.json stores only categories and counts)",
92
+ "full diff content (diffstat.json stores only file-level line counts)",
93
+ ],
94
+ next_action: buildNextAction(lineage),
95
+ bounded: true,
96
+ }).value;
97
+ writeFileSync(safePack.files.json, JSON.stringify(safePack, null, 2) + "\n", "utf-8");
98
+ writeFileSync(safePack.files.markdown, buildEvidenceMarkdown(safePack), "utf-8");
99
+ return safePack;
100
+ }
101
+ export function listEvidencePacks(options = {}) {
102
+ const maxItems = normalizeMaxItems(options.max_items);
103
+ const config = getConfig();
104
+ const root = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs");
105
+ guardWorkspacePath(root, config.workspaceRoot);
106
+ if (!existsSync(root))
107
+ return { evidence_packs: [], total: 0, truncated: false };
108
+ const packs = readdirSync(root, { withFileTypes: true })
109
+ .filter((entry) => entry.isDirectory())
110
+ .map((entry) => readEvidencePack(entry.name))
111
+ .filter((entry) => entry !== null)
112
+ .sort((a, b) => b.generated_at.localeCompare(a.generated_at));
113
+ return {
114
+ evidence_packs: packs.slice(0, maxItems),
115
+ total: packs.length,
116
+ truncated: packs.length > maxItems,
117
+ };
118
+ }
119
+ export function readEvidencePack(lineageId) {
120
+ const safeLineageId = normalizeLineageId(lineageId);
121
+ const config = getConfig();
122
+ const filePath = resolve(config.workspaceRoot, ".patchwarden", "evidence-packs", safeLineageId, "evidence.json");
123
+ guardReadPath(filePath, config.workspaceRoot, ".patchwarden/evidence-packs");
124
+ if (!existsSync(filePath))
125
+ return null;
126
+ const raw = readFileSync(filePath, "utf-8").replace(/^\uFEFF/, "");
127
+ return redactSensitiveValue(JSON.parse(raw)).value;
128
+ }
129
+ function normalizeLineageId(value) {
130
+ const lineageId = String(value || "").trim();
131
+ if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
132
+ throw new PatchWardenError("invalid_lineage_id", "lineage_id may contain only letters, numbers, underscores, and hyphens.", "Pass a lineage_id returned by run_task_loop.", true, { lineage_id: lineageId });
133
+ }
134
+ return lineageId;
135
+ }
136
+ function normalizeMaxItems(value) {
137
+ if (value === undefined)
138
+ return 12;
139
+ if (!Number.isInteger(value) || value < 1 || value > 50) {
140
+ throw new Error("max_items must be an integer from 1 to 50.");
141
+ }
142
+ return value;
143
+ }
144
+ function readPolicySummary(repoPath) {
145
+ try {
146
+ const policy = getProjectPolicySummary(repoPath || ".");
147
+ return {
148
+ valid: Boolean(policy.valid),
149
+ issues: Array.isArray(policy.issues) ? policy.issues : [],
150
+ release_readiness: policy.release_readiness || null,
151
+ };
152
+ }
153
+ catch (err) {
154
+ return {
155
+ valid: false,
156
+ issues: [{ code: "policy_unavailable", message: err instanceof Error ? err.message : String(err) }],
157
+ release_readiness: null,
158
+ };
159
+ }
160
+ }
161
+ function safeCatalog() {
162
+ try {
163
+ const catalog = getLastToolCatalogSnapshot();
164
+ if (!catalog)
165
+ throw new Error("catalog snapshot unavailable");
166
+ return {
167
+ server_version: catalog.server_version,
168
+ schema_epoch: catalog.schema_epoch,
169
+ tool_profile: catalog.tool_profile,
170
+ tool_count: catalog.tool_count,
171
+ tool_manifest_sha256: catalog.tool_manifest_sha256,
172
+ };
173
+ }
174
+ catch {
175
+ return {
176
+ server_version: PATCHWARDEN_VERSION,
177
+ schema_epoch: TOOL_SCHEMA_EPOCH,
178
+ tool_profile: null,
179
+ tool_count: null,
180
+ tool_manifest_sha256: null,
181
+ };
182
+ }
183
+ }
184
+ function buildNextAction(lineage) {
185
+ if (lineage.stop_reason === "success")
186
+ return "Use this evidence pack for review or release readiness.";
187
+ return lineage.next_action || "Review lineage before exporting release evidence.";
188
+ }
189
+ function buildRiskContent(lineage) {
190
+ const risks = [];
191
+ for (const round of lineage.rounds) {
192
+ for (const check of round.fail_checks) {
193
+ risks.push({ source: "round", task_id: round.task_id, severity: "high", category: "fail_check", detail: check });
194
+ }
195
+ for (const check of round.warn_checks) {
196
+ risks.push({ source: "round", task_id: round.task_id, severity: "medium", category: "warn_check", detail: check });
197
+ }
198
+ }
199
+ for (const warning of lineage.warnings) {
200
+ risks.push({ source: "lineage", severity: "low", category: "warning", detail: warning });
201
+ }
202
+ const by_severity = {
203
+ high: risks.filter((risk) => risk.severity === "high").length,
204
+ medium: risks.filter((risk) => risk.severity === "medium").length,
205
+ low: risks.filter((risk) => risk.severity === "low").length,
206
+ };
207
+ return { risks, count: risks.length, by_severity };
208
+ }
209
+ function buildVerifyContent(lineage) {
210
+ const records = [];
211
+ for (const round of lineage.rounds) {
212
+ records.push({
213
+ source: "round",
214
+ task_id: round.task_id,
215
+ role: round.role,
216
+ verification_status: round.verification_status,
217
+ audit_verdict: round.audit_verdict,
218
+ passed: round.verification_status === "passed",
219
+ });
220
+ }
221
+ for (const session of lineage.tasks.direct_sessions) {
222
+ records.push({
223
+ source: "direct_session",
224
+ session_id: session.session_id,
225
+ verification_status: session.status || "unknown",
226
+ audit_verdict: session.audit_decision || "not_run",
227
+ passed: session.status === "passed",
228
+ command_count: session.command_count,
229
+ passed_commands: session.passed_commands,
230
+ failed_commands: session.failed_commands,
231
+ });
232
+ }
233
+ const passed = records.filter((record) => record.passed).length;
234
+ return {
235
+ records,
236
+ count: records.length,
237
+ summary: { total: records.length, passed, failed: records.length - passed },
238
+ latest_status: lineage.verification.latest_status,
239
+ overall_passed: lineage.verification.passed,
240
+ };
241
+ }
242
+ function buildDiffstatContent(lineage, config) {
243
+ const taskIds = [
244
+ lineage.tasks.main,
245
+ ...lineage.tasks.fix,
246
+ ...lineage.tasks.cleanup,
247
+ ].filter((id) => Boolean(id));
248
+ const files = [];
249
+ let totalAdditions = 0;
250
+ let totalDeletions = 0;
251
+ for (const taskId of taskIds) {
252
+ if (!/^[A-Za-z0-9_-]+$/.test(taskId))
253
+ continue;
254
+ const statsPath = resolve(config.workspaceRoot, config.tasksDir, taskId, "file-stats.json");
255
+ try {
256
+ guardWorkspacePath(statsPath, config.workspaceRoot);
257
+ if (!existsSync(statsPath))
258
+ continue;
259
+ const raw = readFileSync(statsPath, "utf-8").replace(/^\uFEFF/, "");
260
+ const stats = JSON.parse(raw);
261
+ if (!Array.isArray(stats.files))
262
+ continue;
263
+ for (const file of stats.files) {
264
+ const additions = Number(file.additions) || 0;
265
+ const deletions = Number(file.deletions) || 0;
266
+ files.push({
267
+ path: String(file.path || ""),
268
+ status: String(file.status || "unknown"),
269
+ additions,
270
+ deletions,
271
+ task_id: taskId,
272
+ });
273
+ totalAdditions += additions;
274
+ totalDeletions += deletions;
275
+ }
276
+ }
277
+ catch {
278
+ // Skip unreadable or malformed file-stats.json entries.
279
+ }
280
+ }
281
+ return {
282
+ files,
283
+ count: files.length,
284
+ totals: { additions: totalAdditions, deletions: totalDeletions },
285
+ };
286
+ }
287
+ function buildLineageSummary(lineage) {
288
+ return {
289
+ lineage_id: lineage.lineage_id,
290
+ goal: lineage.goal,
291
+ final_status: lineage.final_status,
292
+ stop_reason: lineage.stop_reason,
293
+ iterations_count: lineage.rounds.length,
294
+ task_counts: {
295
+ main: lineage.tasks.main ? 1 : 0,
296
+ fix: lineage.tasks.fix.length,
297
+ cleanup: lineage.tasks.cleanup.length,
298
+ direct_sessions: lineage.tasks.direct_sessions.length,
299
+ },
300
+ verification: lineage.verification,
301
+ worktree: {
302
+ isolation_mode: lineage.worktree.isolation_mode,
303
+ status: lineage.worktree.status,
304
+ },
305
+ agent_routing: lineage.agent_routing ? { selected_agent: lineage.agent_routing.selected_agent } : null,
306
+ warnings_count: lineage.warnings.length,
307
+ errors_count: lineage.errors.length,
308
+ truncated: lineage.truncated,
309
+ };
310
+ }
311
+ function readGitCommitShort() {
312
+ try {
313
+ return execSync("git rev-parse --short HEAD", { encoding: "utf-8", timeout: 5000 }).trim();
314
+ }
315
+ catch {
316
+ return "unknown";
317
+ }
318
+ }
319
+ function buildAttestation(catalog, generatedAt) {
320
+ return {
321
+ patchwarden_version: PATCHWARDEN_VERSION,
322
+ package_version: PATCHWARDEN_VERSION,
323
+ commit: readGitCommitShort(),
324
+ node_version: process.version,
325
+ os: { platform: process.platform, arch: process.arch },
326
+ tool_profile: catalog.tool_profile,
327
+ schema_epoch: catalog.schema_epoch,
328
+ generated_at: generatedAt,
329
+ };
330
+ }
331
+ function buildEvidenceMarkdown(pack) {
332
+ const direct = pack.lineage.tasks.direct_sessions
333
+ .map((entry) => `- ${entry.session_id}: status=${entry.status || "unknown"}, audit=${entry.audit_decision || "unknown"}`)
334
+ .join("\n");
335
+ const rounds = pack.lineage.rounds
336
+ .map((round) => `- ${round.role} ${round.task_id}: status=${round.status}, verification=${round.verification_status}, audit=${round.audit_verdict}`)
337
+ .join("\n");
338
+ return [
339
+ "# PatchWarden Evidence Pack",
340
+ "",
341
+ `- Evidence pack: ${pack.evidence_pack_id}`,
342
+ `- Lineage: ${pack.lineage_id}`,
343
+ `- Generated: ${pack.generated_at}`,
344
+ `- Final status: ${pack.lineage.final_status}`,
345
+ `- Stop reason: ${pack.lineage.stop_reason}`,
346
+ `- Worktree: ${pack.lineage.worktree.isolation_mode} (${pack.lineage.worktree.status})`,
347
+ `- Tool manifest: ${pack.catalog.tool_manifest_sha256 || "unavailable"}`,
348
+ "",
349
+ "## Verification Rounds",
350
+ rounds || "- None.",
351
+ "",
352
+ "## Direct Evidence",
353
+ direct || "- None.",
354
+ "",
355
+ "## Policy And Release",
356
+ `- Policy valid: ${pack.policy.valid}`,
357
+ `- Policy issue count: ${pack.policy.issue_count}`,
358
+ "",
359
+ "## Evidence Pack v2 Files",
360
+ "- `evidence.json` — full bounded evidence pack (machine-readable).",
361
+ "- `EVIDENCE.md` — human-readable evidence summary.",
362
+ "- `risk.json` — aggregated risk items with severity (high/medium/low).",
363
+ "- `verify.json` — structured verification records per round and direct session.",
364
+ "- `diffstat.json` — file-level additions/deletions without full diff content.",
365
+ "- `lineage.json` — bounded lineage summary (goal, status, task counts).",
366
+ "- `attestation.json` — version, commit, Node/OS, tool profile, schema epoch.",
367
+ "- `redactions.json` — redaction categories and counts (no original secret values).",
368
+ "",
369
+ "## Omitted",
370
+ ...pack.omitted.map((item) => `- ${item}`),
371
+ "",
372
+ `Next action: ${pack.next_action}`,
373
+ "",
374
+ ].join("\n");
375
+ }
@@ -16,4 +16,4 @@ export interface FinalizeDirectSessionOutput {
16
16
  finalized: boolean;
17
17
  next_action: string;
18
18
  }
19
- export declare function finalizeDirectSession(input: FinalizeDirectSessionInput): FinalizeDirectSessionOutput;
19
+ export declare function finalizeDirectSession(input: FinalizeDirectSessionInput): Promise<FinalizeDirectSessionOutput>;
@@ -3,15 +3,15 @@ import { readDirectSession, finalizeDirectSessionRecord, getDirectSessionDir, }
3
3
  import { guardDirectSessionActive } from "../direct/directGuards.js";
4
4
  import { captureRepoSnapshot, buildChangeArtifacts, } from "../runner/changeCapture.js";
5
5
  // ── Main function ──────────────────────────────────────────────────
6
- export function finalizeDirectSession(input) {
6
+ export async function finalizeDirectSession(input) {
7
7
  const { session_id } = input;
8
8
  // 1. Read session and guard active (not expired, not finalized)
9
9
  const session = readDirectSession(session_id);
10
10
  guardDirectSessionActive(session);
11
11
  // 2. Capture after snapshot
12
- const afterSnapshot = captureRepoSnapshot(session.resolved_repo_path);
12
+ const afterSnapshot = await captureRepoSnapshot(session.resolved_repo_path);
13
13
  // 3. Build change artifacts from before/after snapshots
14
- const changeArtifacts = buildChangeArtifacts(session.resolved_repo_path, session.workspace_snapshot_before, afterSnapshot);
14
+ const changeArtifacts = await buildChangeArtifacts(session.resolved_repo_path, session.workspace_snapshot_before, afterSnapshot);
15
15
  // 4. Finalize session record — writes changed-files.json, diff.patch,
16
16
  // summary.json, summary.md and marks the session as finalized.
17
17
  finalizeDirectSessionRecord(session_id, afterSnapshot, changeArtifacts);
@@ -48,4 +48,4 @@ export interface CreateSubgoalTaskOutput {
48
48
  * @throws PatchWardenError("invalid_dependency") 当 depends_on 引用不存在的 subgoal
49
49
  * @throws PatchWardenError("invalid_execution_mode") 当 execution_mode 为 "assess_only"
50
50
  */
51
- export declare function createSubgoalTask(input: CreateSubgoalTaskInput): CreateSubgoalTaskOutput;
51
+ export declare function createSubgoalTask(input: CreateSubgoalTaskInput): Promise<CreateSubgoalTaskOutput>;
@@ -27,7 +27,7 @@ import { PatchWardenError } from "../errors.js";
27
27
  * @throws PatchWardenError("invalid_dependency") 当 depends_on 引用不存在的 subgoal
28
28
  * @throws PatchWardenError("invalid_execution_mode") 当 execution_mode 为 "assess_only"
29
29
  */
30
- export function createSubgoalTask(input) {
30
+ export async function createSubgoalTask(input) {
31
31
  // assess_only 不产生 task,无法关联到 subgoal
32
32
  if (input.execution_mode === "assess_only") {
33
33
  throw new PatchWardenError("invalid_execution_mode", "createSubgoalTask does not support execution_mode \"assess_only\"", "Use execution_mode \"execute\" (default) so a task is created and linked to the subgoal.", true, { goal_id: input.goal_id, execution_mode: input.execution_mode });
@@ -57,7 +57,7 @@ export function createSubgoalTask(input) {
57
57
  // 3. 调用 createTask 创建关联任务(强制 execute 模式,确保返回 CreateTaskOutput)
58
58
  let taskId;
59
59
  try {
60
- const taskResult = createTask({
60
+ const taskResult = await createTask({
61
61
  plan_id: input.plan_id,
62
62
  inline_plan: input.inline_plan,
63
63
  plan_title: input.plan_title,
@@ -185,7 +185,7 @@ function directoryStatus(path, allowCreatable, createRoot) {
185
185
  accessSync(parent, constants.R_OK | constants.W_OK);
186
186
  return { available: true, path, readable: false, writable: true, reason: "Directory will be created on first use." };
187
187
  }
188
- catch { }
188
+ catch { } // probe failure handled by return value
189
189
  }
190
190
  return { available: false, path, readable: false, writable: false, reason: "Directory does not exist." };
191
191
  }
@@ -195,12 +195,12 @@ function directoryStatus(path, allowCreatable, createRoot) {
195
195
  accessSync(path, constants.R_OK);
196
196
  readable = true;
197
197
  }
198
- catch { }
198
+ catch { } // probe failure handled by return value
199
199
  try {
200
200
  accessSync(path, constants.W_OK);
201
201
  writable = true;
202
202
  }
203
- catch { }
203
+ catch { } // probe failure handled by return value
204
204
  return {
205
205
  available: readable && writable,
206
206
  path,
@@ -0,0 +1,19 @@
1
+ export interface RecommendAgentForTaskInput {
2
+ repo_path: string;
3
+ goal: string;
4
+ scope_files?: string[];
5
+ template?: string;
6
+ risk_hint?: string;
7
+ }
8
+ export interface RecommendAgentForTaskOutput {
9
+ repo_path: string;
10
+ resolved_repo_path: string;
11
+ recommended_agent: string;
12
+ fallback_agent: string | null;
13
+ fallback: boolean;
14
+ reason: string;
15
+ risk_notes: string[];
16
+ suggested_verify_commands: string[];
17
+ bounded: true;
18
+ }
19
+ export declare function recommendAgentForTask(input: RecommendAgentForTaskInput): RecommendAgentForTaskOutput;
@@ -0,0 +1,56 @@
1
+ import { getConfig } from "../config.js";
2
+ import { guardWorkspacePath } from "../security/pathGuard.js";
3
+ import { routeAgent } from "../agents/agentRouter.js";
4
+ export function recommendAgentForTask(input) {
5
+ const config = getConfig();
6
+ const repoPath = String(input.repo_path || "").trim();
7
+ const goal = String(input.goal || "").trim();
8
+ if (!repoPath)
9
+ throw new Error("repo_path is required.");
10
+ if (!goal)
11
+ throw new Error("goal is required.");
12
+ const resolvedRepoPath = guardWorkspacePath(repoPath, config.workspaceRoot);
13
+ const configuredAgents = Object.keys(config.agents);
14
+ const route = routeAgent({
15
+ goal,
16
+ scope: normalizeScope(input.scope_files),
17
+ inline_plan: input.risk_hint,
18
+ template: input.template,
19
+ configuredAgents,
20
+ });
21
+ return {
22
+ repo_path: repoPath,
23
+ resolved_repo_path: resolvedRepoPath,
24
+ recommended_agent: route.recommended_agent,
25
+ fallback_agent: route.fallback ? route.recommended_agent : configuredAgents.find((name) => name !== route.recommended_agent) || null,
26
+ fallback: route.fallback,
27
+ reason: route.reason,
28
+ risk_notes: buildRiskNotes(goal, input.risk_hint),
29
+ suggested_verify_commands: suggestVerifyCommands(config.allowedTestCommands),
30
+ bounded: true,
31
+ };
32
+ }
33
+ function normalizeScope(value) {
34
+ if (!Array.isArray(value))
35
+ return undefined;
36
+ return value.map((entry) => String(entry).trim()).filter(Boolean).slice(0, 50);
37
+ }
38
+ function buildRiskNotes(goal, riskHint) {
39
+ const text = `${goal} ${riskHint || ""}`.toLowerCase();
40
+ const notes = [];
41
+ if (/release|publish|push|tag|npm/.test(text)) {
42
+ notes.push("release_or_remote_write_language_detected");
43
+ }
44
+ if (/secret|token|cookie|\.env|ssh/.test(text)) {
45
+ notes.push("sensitive_file_language_detected");
46
+ }
47
+ if (/refactor|rewrite|redesign|migration/.test(text)) {
48
+ notes.push("broad_change_language_detected");
49
+ }
50
+ return notes.slice(0, 8);
51
+ }
52
+ function suggestVerifyCommands(commands) {
53
+ const preferred = ["npm test", "npm run test", "npm run build", "npm run lint"];
54
+ const configured = new Set(commands);
55
+ return preferred.filter((command) => configured.has(command)).slice(0, 4);
56
+ }
@@ -16,9 +16,9 @@ export interface ToolDef {
16
16
  export declare function getToolDefs(): ToolDef[];
17
17
  export declare function getToolCatalogSnapshot(): ToolCatalogSnapshot;
18
18
  export declare function handleToolCall(name: string, args: Record<string, unknown> | undefined): Promise<{
19
- content: {
20
- type: "text";
19
+ content: Array<{
20
+ type: string;
21
21
  text: string;
22
- }[];
22
+ }>;
23
23
  }>;
24
24
  export declare function registerTools(server: Server): void;