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,417 @@
1
+ import { setTimeout as sleep } from "node:timers/promises";
2
+ import { getConfig } from "../config.js";
3
+ import { createWorktree } from "../goal/worktreeManager.js";
4
+ import { guardWorkspacePath } from "../security/pathGuard.js";
5
+ import { createDirectSession } from "./createDirectSession.js";
6
+ import { createTask } from "./createTask.js";
7
+ import { recommendAgentForTask } from "./recommendAgentForTask.js";
8
+ import { runDirectVerificationBundle } from "./runDirectVerificationBundle.js";
9
+ import { waitForTask } from "./waitForTask.js";
10
+ import { safeAudit, safeAuditDirectSession, safeFinalizeDirectSession, safeResult, safeTestSummary } from "./safeViews.js";
11
+ import { createLineageId, writeTaskLineage, } from "./taskLineage.js";
12
+ const DEFAULT_DEPS = {
13
+ createTask,
14
+ waitForTask,
15
+ safeResult,
16
+ safeAudit,
17
+ safeTestSummary,
18
+ createDirectSession,
19
+ runDirectVerificationBundle,
20
+ safeFinalizeDirectSession,
21
+ safeAuditDirectSession,
22
+ writeTaskLineage,
23
+ createLineageId,
24
+ recommendAgentForTask,
25
+ createWorktree,
26
+ now: () => new Date(),
27
+ sleep,
28
+ };
29
+ const TERMINAL_STATUSES = new Set([
30
+ "done",
31
+ "done_by_agent",
32
+ "failed",
33
+ "failed_verification",
34
+ "failed_scope_violation",
35
+ "failed_policy_violation",
36
+ "canceled",
37
+ ]);
38
+ export async function runTaskLoop(input) {
39
+ return runTaskLoopWithDeps(input, DEFAULT_DEPS);
40
+ }
41
+ export async function runTaskLoopWithDeps(input, deps) {
42
+ const normalized = normalizeInput(input);
43
+ const resolvedRepoPath = guardWorkspacePath(normalized.repo_path, getConfig().workspaceRoot);
44
+ const routing = resolveAgentRouting(normalized, deps);
45
+ const selectedAgent = routing.selected_agent;
46
+ const now = deps.now().toISOString();
47
+ const lineage = {
48
+ lineage_id: deps.createLineageId(deps.now()),
49
+ goal: normalized.goal,
50
+ repo_path: resolvedRepoPath,
51
+ created_at: now,
52
+ updated_at: now,
53
+ final_status: "blocked",
54
+ stop_reason: "policy_blocked",
55
+ next_action: "inspect_lineage",
56
+ main_task: null,
57
+ fix_tasks: [],
58
+ cleanup_tasks: [],
59
+ direct_sessions: [],
60
+ rounds: [],
61
+ warnings: [],
62
+ errors: [],
63
+ worktree: {
64
+ isolation_mode: normalized.isolation_mode,
65
+ cleanup: normalized.worktree_cleanup,
66
+ status: normalized.isolation_mode === "worktree" ? "active" : "not_used",
67
+ requested_base_branch: normalized.worktree_base_branch,
68
+ next_action: normalized.isolation_mode === "worktree"
69
+ ? "Review and explicitly merge or discard the worktree after acceptance."
70
+ : "none",
71
+ },
72
+ agent_routing: routing,
73
+ };
74
+ const finalize = (finalStatus, stopReason, nextAction, error) => {
75
+ lineage.final_status = finalStatus;
76
+ lineage.stop_reason = stopReason;
77
+ lineage.next_action = nextAction;
78
+ lineage.updated_at = deps.now().toISOString();
79
+ if (error)
80
+ lineage.errors.push(error);
81
+ const safe = deps.writeTaskLineage(lineage);
82
+ return {
83
+ ...safe,
84
+ created_task_count: [lineage.main_task, ...lineage.fix_tasks, ...lineage.cleanup_tasks].filter(Boolean).length,
85
+ auto_fix_tests: normalized.auto_fix_tests,
86
+ auto_cleanup_artifacts: normalized.auto_cleanup_artifacts,
87
+ direct_verify: normalized.direct_verify,
88
+ isolation_mode: normalized.isolation_mode,
89
+ worktree: safe.worktree,
90
+ stopped_before_execution: lineage.main_task === null,
91
+ };
92
+ };
93
+ let taskRepoPath = resolvedRepoPath;
94
+ if (normalized.isolation_mode === "worktree") {
95
+ try {
96
+ const worktree = deps.createWorktree(lineage.lineage_id, "task_loop", resolvedRepoPath);
97
+ taskRepoPath = worktree.worktreePath;
98
+ lineage.repo_path = taskRepoPath;
99
+ lineage.worktree = {
100
+ isolation_mode: "worktree",
101
+ worktree_id: worktree.worktreeId,
102
+ worktree_path: worktree.worktreePath,
103
+ branch: worktree.branch,
104
+ requested_base_branch: normalized.worktree_base_branch,
105
+ cleanup: normalized.worktree_cleanup,
106
+ status: "active",
107
+ next_action: "Explicitly inspect and merge_worktree or discard_worktree after reviewing this lineage.",
108
+ };
109
+ }
110
+ catch (err) {
111
+ lineage.worktree = {
112
+ isolation_mode: "worktree",
113
+ cleanup: normalized.worktree_cleanup,
114
+ requested_base_branch: normalized.worktree_base_branch,
115
+ status: "failed",
116
+ next_action: "Fix worktree creation prerequisites or rerun with isolation_mode=current_repo.",
117
+ };
118
+ return finalize("blocked", "policy_blocked", "Fix worktree creation prerequisites or rerun without worktree isolation.", err instanceof Error ? err.message : String(err));
119
+ }
120
+ }
121
+ let role = "main";
122
+ let latestFailurePrompt = normalized.goal;
123
+ for (let iteration = 1; iteration <= normalized.max_iterations; iteration++) {
124
+ const assessmentInput = {
125
+ template: role === "main" ? normalized.template : "fix_tests",
126
+ goal: role === "main" ? normalized.goal : latestFailurePrompt,
127
+ repo_path: taskRepoPath,
128
+ agent: selectedAgent,
129
+ verify_commands: normalized.verify_commands,
130
+ timeout_seconds: normalized.task_timeout_seconds,
131
+ execution_mode: "assess_only",
132
+ };
133
+ const assessment = await deps.createTask(assessmentInput);
134
+ if (assessment.decision === "blocked") {
135
+ return finalize("blocked", "high_risk_blocked", "Risk assessment blocked task execution.", assessment.reason_codes?.join(", "));
136
+ }
137
+ if (assessment.decision === "needs_confirm") {
138
+ return finalize("blocked", "user_confirmation_required", "Ask the user to confirm the assessment before executing the loop.");
139
+ }
140
+ const created = await deps.createTask({
141
+ execution_mode: "execute",
142
+ assessment_id: String(assessment.assessment_id || ""),
143
+ });
144
+ const taskId = String(created.task_id || "");
145
+ if (!taskId) {
146
+ return finalize("failed", "policy_blocked", "create_task returned no task_id.", "create_task returned no task_id");
147
+ }
148
+ if (role === "main")
149
+ lineage.main_task = taskId;
150
+ else
151
+ lineage.fix_tasks.push(taskId);
152
+ const wait = await waitUntilTerminal(taskId, normalized.task_timeout_seconds, deps);
153
+ if (wait.stop_reason) {
154
+ return finalize("blocked", wait.stop_reason, wait.next_action, wait.error);
155
+ }
156
+ const result = deps.safeResult(taskId, { max_items: 8 });
157
+ const tests = deps.safeTestSummary(taskId);
158
+ const audit = deps.safeAudit(taskId, { max_items: 8 });
159
+ const round = buildRound(iteration, taskId, role, result, tests, audit);
160
+ lineage.rounds.push(round);
161
+ lineage.updated_at = deps.now().toISOString();
162
+ if (isSuccessfulRound(round)) {
163
+ if (normalized.direct_verify) {
164
+ const direct = await runDirectVerification(lineage.lineage_id, normalized, taskRepoPath, deps);
165
+ lineage.direct_sessions.push(direct.evidence);
166
+ if (direct.warning)
167
+ lineage.warnings.push(direct.warning);
168
+ if (direct.stop_reason) {
169
+ return finalize(direct.final_status, direct.stop_reason, direct.next_action, direct.error);
170
+ }
171
+ }
172
+ return finalize("accepted", "success", "accept");
173
+ }
174
+ if (isHardStop(round, result, audit, normalized.stop_on_high_risk)) {
175
+ return finalize("blocked", hardStopReason(round, result), round.next_action || "review_task");
176
+ }
177
+ latestFailurePrompt = buildFixGoal(normalized.goal, result, round);
178
+ if (!normalized.auto_fix_tests || round.status !== "failed_verification") {
179
+ return finalize("needs_fix", "verification_failed", round.next_action || "create_followup_task");
180
+ }
181
+ role = "fix_tests";
182
+ }
183
+ return finalize("needs_fix", "max_iterations_reached", "review_lineage_and_create_manual_followup");
184
+ }
185
+ async function waitUntilTerminal(taskId, timeoutSeconds, deps) {
186
+ const deadline = Date.now() + timeoutSeconds * 1000;
187
+ while (Date.now() < deadline) {
188
+ const waited = await deps.waitForTask(taskId, 30);
189
+ if (waited.terminal || TERMINAL_STATUSES.has(String(waited.status))) {
190
+ return { next_action: waited.next_action || "safe_audit" };
191
+ }
192
+ if (waited.next_tool_call?.name === "health_check" || waited.continuation_required === false) {
193
+ return {
194
+ stop_reason: "watcher_blocked",
195
+ next_action: waited.next_action || "health_check",
196
+ error: waited.progress_summary?.hint || "Watcher is blocked or unavailable.",
197
+ };
198
+ }
199
+ await deps.sleep(250);
200
+ }
201
+ return {
202
+ stop_reason: "agent_timeout",
203
+ next_action: "inspect_task_status",
204
+ error: `Task loop timed out waiting for ${taskId}.`,
205
+ };
206
+ }
207
+ function buildRound(iteration, taskId, role, result, tests, audit) {
208
+ const failChecks = Array.isArray(audit.fail_checks) ? audit.fail_checks.map((entry) => String(entry.name || entry)) : [];
209
+ const warnChecks = Array.isArray(audit.warn_checks) ? audit.warn_checks.map((entry) => String(entry.name || entry)) : [];
210
+ return {
211
+ iteration,
212
+ task_id: taskId,
213
+ role,
214
+ status: String(result.status || "unknown"),
215
+ terminal: Boolean(result.terminal),
216
+ verification_status: String(tests.status || result.verification?.status || "not_available"),
217
+ audit_verdict: String(audit.verdict || audit.acceptance?.verdict || "unknown"),
218
+ fail_checks: failChecks,
219
+ warn_checks: warnChecks,
220
+ next_action: String(result.next_action || audit.recommended_next_actions?.[0] || "review_task"),
221
+ };
222
+ }
223
+ function isSuccessfulRound(round) {
224
+ return (round.terminal &&
225
+ ["done_by_agent", "done", "accepted"].includes(round.status) &&
226
+ round.verification_status === "passed" &&
227
+ round.fail_checks.length === 0 &&
228
+ round.warn_checks.length === 0 &&
229
+ round.audit_verdict === "pass");
230
+ }
231
+ function isHardStop(round, result, audit, stopOnHighRisk) {
232
+ if (["failed_scope_violation", "failed_policy_violation", "canceled"].includes(round.status))
233
+ return true;
234
+ if (!stopOnHighRisk)
235
+ return false;
236
+ const checkNames = [...round.fail_checks, ...round.warn_checks].join(" ").toLowerCase();
237
+ const reason = String(result.failure_reason || audit.acceptance?.reason || "").toLowerCase();
238
+ return /scope|secret|sensitive|publish|release|policy|push/.test(`${checkNames} ${reason}`);
239
+ }
240
+ function hardStopReason(round, result) {
241
+ if (round.status === "failed_scope_violation" || round.status === "failed_policy_violation")
242
+ return "policy_blocked";
243
+ if (String(result.failure_reason || "").toLowerCase().includes("timeout"))
244
+ return "agent_timeout";
245
+ return "high_risk_blocked";
246
+ }
247
+ function buildFixGoal(originalGoal, result, round) {
248
+ const failedCommand = result.failed_command ? ` Failed command: ${result.failed_command}.` : "";
249
+ return [
250
+ `Fix the failing verification for this PatchWarden loop without changing unrelated behavior.`,
251
+ `Original goal: ${originalGoal}`,
252
+ `Previous task: ${round.task_id}. Status: ${round.status}. Verification: ${round.verification_status}.${failedCommand}`,
253
+ "Do not commit, push, publish, weaken tests, or touch files outside the resolved repository path.",
254
+ ].join("\n");
255
+ }
256
+ function resolveAgentRouting(normalized, deps) {
257
+ if (normalized.agent && normalized.agent !== "auto") {
258
+ return {
259
+ requested_agent: normalized.agent,
260
+ selected_agent: normalized.agent,
261
+ reason: "explicit agent supplied",
262
+ fallback: false,
263
+ };
264
+ }
265
+ const recommendation = deps.recommendAgentForTask({
266
+ repo_path: normalized.repo_path,
267
+ goal: normalized.goal,
268
+ scope_files: normalized.scope_files,
269
+ template: normalized.template,
270
+ });
271
+ return {
272
+ requested_agent: normalized.agent || null,
273
+ selected_agent: recommendation.recommended_agent,
274
+ reason: recommendation.reason,
275
+ fallback: recommendation.fallback,
276
+ };
277
+ }
278
+ async function runDirectVerification(lineageId, normalized, repoPath, deps) {
279
+ const config = getConfig();
280
+ if (config.enableDirectProfile !== true) {
281
+ return {
282
+ evidence: {
283
+ session_id: "not_created",
284
+ status: "skipped",
285
+ audit_decision: "not_run",
286
+ next_action: "Enable Direct profile locally before requesting direct_verify.",
287
+ },
288
+ stop_reason: "direct_profile_disabled",
289
+ final_status: "blocked",
290
+ next_action: "Enable enableDirectProfile locally or rerun run_task_loop with direct_verify=false.",
291
+ error: "Direct profile is disabled by local config.",
292
+ };
293
+ }
294
+ let sessionId = "";
295
+ try {
296
+ const session = await deps.createDirectSession({
297
+ repo_path: repoPath,
298
+ title: `Direct verification for ${lineageId}`,
299
+ });
300
+ sessionId = session.session_id;
301
+ const bundle = await deps.runDirectVerificationBundle({
302
+ session_id: sessionId,
303
+ commands: normalized.direct_verify_commands,
304
+ timeout_seconds: normalized.direct_verify_timeout_seconds,
305
+ });
306
+ const finalized = await deps.safeFinalizeDirectSession(sessionId, { max_items: 8 });
307
+ const audit = deps.safeAuditDirectSession(sessionId, { max_items: 8 });
308
+ const evidence = {
309
+ session_id: sessionId,
310
+ status: bundle.status,
311
+ command_count: bundle.command_count,
312
+ passed_commands: bundle.passed_commands,
313
+ failed_commands: bundle.failed_commands,
314
+ timed_out_commands: bundle.timed_out_commands,
315
+ audit_decision: audit.decision || "not_run",
316
+ changed_files_total: Number(finalized.changed_files_total || audit.evidence?.changed_files_total || 0),
317
+ next_action: String(audit.next_action || bundle.next_action || "review_direct_session"),
318
+ };
319
+ if (bundle.status !== "passed") {
320
+ return {
321
+ evidence,
322
+ stop_reason: "direct_verification_failed",
323
+ final_status: "needs_fix",
324
+ next_action: "Review Direct verification summary and create a normal follow-up task.",
325
+ error: "Direct verification failed.",
326
+ };
327
+ }
328
+ if (audit.decision === "fail") {
329
+ return {
330
+ evidence,
331
+ stop_reason: "direct_audit_failed",
332
+ final_status: "blocked",
333
+ next_action: "Review Direct audit findings before accepting the loop.",
334
+ error: "Direct audit failed.",
335
+ };
336
+ }
337
+ return {
338
+ evidence,
339
+ final_status: "accepted",
340
+ next_action: "accept",
341
+ warning: audit.decision === "warn" ? "Direct audit completed with warnings." : undefined,
342
+ };
343
+ }
344
+ catch (err) {
345
+ const message = err instanceof Error ? err.message : String(err);
346
+ return {
347
+ evidence: {
348
+ session_id: sessionId || "not_created",
349
+ status: "failed",
350
+ audit_decision: "not_run",
351
+ next_action: "Review Direct verification configuration and command allow-list.",
352
+ },
353
+ stop_reason: "direct_verification_failed",
354
+ final_status: "needs_fix",
355
+ next_action: "Review Direct verification configuration and command allow-list.",
356
+ error: message,
357
+ };
358
+ }
359
+ }
360
+ function normalizeInput(input) {
361
+ const config = getConfig();
362
+ const repoPath = String(input.repo_path || "").trim();
363
+ const goal = String(input.goal || "").trim();
364
+ if (!repoPath)
365
+ throw new Error("repo_path is required.");
366
+ if (!goal)
367
+ throw new Error("goal is required.");
368
+ if (!Array.isArray(input.verify_commands) || input.verify_commands.length === 0) {
369
+ throw new Error("verify_commands must contain at least one command.");
370
+ }
371
+ const maxIterations = input.max_iterations ?? 3;
372
+ if (!Number.isInteger(maxIterations) || maxIterations < 1 || maxIterations > 5) {
373
+ throw new Error("max_iterations must be an integer from 1 to 5.");
374
+ }
375
+ const timeoutSeconds = input.task_timeout_seconds ?? config.defaultTaskTimeoutSeconds;
376
+ if (!Number.isInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > config.maxTaskTimeoutSeconds) {
377
+ throw new Error(`task_timeout_seconds must be an integer from 1 to ${config.maxTaskTimeoutSeconds}.`);
378
+ }
379
+ const template = input.template || "feature_small";
380
+ if (template !== "inspect_only" && template !== "feature_small" && template !== "release_check") {
381
+ throw new Error('template must be "inspect_only", "feature_small", or "release_check".');
382
+ }
383
+ const directVerifyCommands = Array.isArray(input.direct_verify_commands) && input.direct_verify_commands.length > 0
384
+ ? input.direct_verify_commands
385
+ : input.verify_commands;
386
+ if (input.direct_verify === true && (!Array.isArray(directVerifyCommands) || directVerifyCommands.length === 0)) {
387
+ throw new Error("direct_verify_commands must contain at least one command when provided.");
388
+ }
389
+ const directVerifyTimeout = input.direct_verify_timeout_seconds ?? 120;
390
+ const maxDirectTimeout = Math.min(config.maxTaskTimeoutSeconds, config.directSessionTtlSeconds);
391
+ if (!Number.isInteger(directVerifyTimeout) || directVerifyTimeout < 1 || directVerifyTimeout > maxDirectTimeout) {
392
+ throw new Error(`direct_verify_timeout_seconds must be an integer from 1 to ${maxDirectTimeout}.`);
393
+ }
394
+ return {
395
+ repo_path: repoPath,
396
+ goal,
397
+ verify_commands: input.verify_commands.map((command) => String(command).trim()),
398
+ agent: input.agent ? String(input.agent) : undefined,
399
+ template,
400
+ max_iterations: maxIterations,
401
+ task_timeout_seconds: timeoutSeconds,
402
+ auto_fix_tests: input.auto_fix_tests !== false,
403
+ auto_cleanup_artifacts: input.auto_cleanup_artifacts !== false,
404
+ stop_on_high_risk: input.stop_on_high_risk !== false,
405
+ direct_verify: input.direct_verify === true,
406
+ direct_verify_commands: directVerifyCommands.map((command) => String(command).trim()),
407
+ direct_verify_timeout_seconds: directVerifyTimeout,
408
+ scope_files: Array.isArray(input.scope_files)
409
+ ? input.scope_files.map((entry) => String(entry).trim()).filter(Boolean).slice(0, 50)
410
+ : undefined,
411
+ isolation_mode: input.isolation_mode === "worktree" ? "worktree" : "current_repo",
412
+ worktree_base_branch: input.worktree_base_branch ? String(input.worktree_base_branch).trim().slice(0, 160) : undefined,
413
+ worktree_cleanup: input.worktree_cleanup === "archive" || input.worktree_cleanup === "delete_ignored_only"
414
+ ? input.worktree_cleanup
415
+ : "keep",
416
+ };
417
+ }
@@ -8,6 +8,10 @@ export interface RunVerificationOutput {
8
8
  exit_code: number | null;
9
9
  passed: boolean;
10
10
  timed_out: boolean;
11
+ redacted: boolean;
12
+ redaction_categories: string[];
13
+ started_at: string;
14
+ finished_at: string;
11
15
  stdout_tail: string;
12
16
  stderr_tail: string;
13
17
  log_path: string;
@@ -22,6 +22,10 @@ export async function runVerification(input) {
22
22
  exit_code: result.run.exit_code,
23
23
  passed: result.run.passed,
24
24
  timed_out: result.run.timed_out,
25
+ redacted: Boolean(result.run.redacted),
26
+ redaction_categories: result.run.redaction_categories || [],
27
+ started_at: result.run.started_at,
28
+ finished_at: result.run.finished_at,
25
29
  stdout_tail: result.run.stdout_tail,
26
30
  stderr_tail: result.run.stderr_tail,
27
31
  log_path: result.run.log_path,
@@ -162,13 +162,15 @@ export declare function safeDirectSummary(sessionId: string, options?: SafeViewO
162
162
  exit_code: number | null;
163
163
  passed: boolean;
164
164
  timed_out: boolean;
165
+ redacted: boolean;
166
+ redaction_categories: string[];
165
167
  started_at: string;
166
168
  finished_at: string;
167
169
  }[];
168
170
  large_diff_omitted: boolean;
169
171
  truncated: boolean;
170
172
  };
171
- export declare function safeFinalizeDirectSession(sessionId: string, options?: SafeViewOptions): {
173
+ export declare function safeFinalizeDirectSession(sessionId: string, options?: SafeViewOptions): Promise<{
172
174
  finalized: boolean;
173
175
  next_action: string;
174
176
  view: "summary" | "finalize";
@@ -226,12 +228,14 @@ export declare function safeFinalizeDirectSession(sessionId: string, options?: S
226
228
  exit_code: number | null;
227
229
  passed: boolean;
228
230
  timed_out: boolean;
231
+ redacted: boolean;
232
+ redaction_categories: string[];
229
233
  started_at: string;
230
234
  finished_at: string;
231
235
  }[];
232
236
  large_diff_omitted: boolean;
233
237
  truncated: boolean;
234
- };
238
+ }>;
235
239
  export declare function safeAuditDirectSession(sessionId: string, options?: SafeViewOptions): {
236
240
  session_id: string;
237
241
  decision: "pass" | "warn" | "fail";
@@ -245,6 +249,8 @@ export declare function safeAuditDirectSession(sessionId: string, options?: Safe
245
249
  exit_code: number | null;
246
250
  passed: boolean;
247
251
  timed_out: boolean;
252
+ redacted: boolean;
253
+ redaction_categories: string[];
248
254
  started_at: string;
249
255
  finished_at: string;
250
256
  }[];
@@ -92,9 +92,9 @@ export function safeDirectSummary(sessionId, options = {}) {
92
92
  const session = readDirectSession(sessionId);
93
93
  return directSessionToSafe(session, maxItems, "summary");
94
94
  }
95
- export function safeFinalizeDirectSession(sessionId, options = {}) {
95
+ export async function safeFinalizeDirectSession(sessionId, options = {}) {
96
96
  const maxItems = normalizeMaxItems(options.max_items);
97
- const finalized = finalizeDirectSession({ session_id: sessionId });
97
+ const finalized = await finalizeDirectSession({ session_id: sessionId });
98
98
  const session = readDirectSession(sessionId);
99
99
  return redact({
100
100
  ...directSessionToSafe(session, maxItems, "finalize"),
@@ -193,6 +193,8 @@ function summarizeVerificationRuns(runs) {
193
193
  exit_code: run.exit_code,
194
194
  passed: run.passed,
195
195
  timed_out: run.timed_out,
196
+ redacted: Boolean(run.redacted),
197
+ redaction_categories: run.redaction_categories || [],
196
198
  started_at: run.started_at,
197
199
  finished_at: run.finished_at,
198
200
  }));
@@ -0,0 +1,91 @@
1
+ export type TaskLoopStopReason = "success" | "max_iterations_reached" | "verification_failed" | "high_risk_blocked" | "user_confirmation_required" | "agent_timeout" | "policy_blocked" | "watcher_blocked" | "direct_profile_disabled" | "direct_verification_failed" | "direct_audit_failed";
2
+ export interface TaskLineageDirectSession {
3
+ session_id: string;
4
+ status?: "passed" | "failed" | "skipped";
5
+ command_count?: number;
6
+ passed_commands?: number;
7
+ failed_commands?: number;
8
+ timed_out_commands?: number;
9
+ audit_decision?: "pass" | "warn" | "fail" | "not_run";
10
+ changed_files_total?: number;
11
+ next_action?: string;
12
+ }
13
+ export interface TaskLineageWorktree {
14
+ isolation_mode: "current_repo" | "worktree";
15
+ worktree_id?: string;
16
+ worktree_path?: string;
17
+ branch?: string;
18
+ requested_base_branch?: string;
19
+ cleanup: "keep" | "archive" | "delete_ignored_only";
20
+ status: "not_used" | "active" | "failed";
21
+ next_action: string;
22
+ }
23
+ export interface TaskLineageAgentRouting {
24
+ requested_agent: string | null;
25
+ selected_agent: string;
26
+ reason: string;
27
+ fallback: boolean;
28
+ }
29
+ export interface TaskLineageRound {
30
+ iteration: number;
31
+ task_id: string;
32
+ role: "main" | "fix_tests" | "cleanup";
33
+ status: string;
34
+ terminal: boolean;
35
+ verification_status: string;
36
+ audit_verdict: string;
37
+ fail_checks: string[];
38
+ warn_checks: string[];
39
+ next_action: string;
40
+ }
41
+ export interface TaskLineageRecord {
42
+ lineage_id: string;
43
+ goal: string;
44
+ repo_path: string;
45
+ created_at: string;
46
+ updated_at: string;
47
+ final_status: "accepted" | "needs_fix" | "blocked" | "failed";
48
+ stop_reason: TaskLoopStopReason;
49
+ next_action: string;
50
+ main_task: string | null;
51
+ fix_tasks: string[];
52
+ cleanup_tasks: string[];
53
+ direct_sessions: Array<string | TaskLineageDirectSession>;
54
+ rounds: TaskLineageRound[];
55
+ warnings: string[];
56
+ errors: string[];
57
+ worktree?: TaskLineageWorktree;
58
+ agent_routing?: TaskLineageAgentRouting;
59
+ }
60
+ export interface SafeTaskLineage {
61
+ lineage_id: string;
62
+ goal: string;
63
+ repo_path: string;
64
+ created_at: string;
65
+ updated_at: string;
66
+ final_status: TaskLineageRecord["final_status"];
67
+ stop_reason: TaskLoopStopReason;
68
+ next_action: string;
69
+ tasks: {
70
+ main: string | null;
71
+ fix: string[];
72
+ cleanup: string[];
73
+ direct_sessions: TaskLineageDirectSession[];
74
+ };
75
+ worktree: TaskLineageWorktree;
76
+ agent_routing: TaskLineageAgentRouting | null;
77
+ verification: {
78
+ latest_status: string;
79
+ passed: boolean;
80
+ };
81
+ rounds: TaskLineageRound[];
82
+ warnings: string[];
83
+ errors: string[];
84
+ truncated: boolean;
85
+ }
86
+ export declare function createLineageId(now?: Date): string;
87
+ export declare function writeTaskLineage(record: TaskLineageRecord): SafeTaskLineage;
88
+ export declare function getTaskLineage(lineageId: string, options?: {
89
+ max_items?: number;
90
+ }): SafeTaskLineage;
91
+ export declare function toSafeTaskLineage(record: TaskLineageRecord, maxItems?: number): SafeTaskLineage;