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
@@ -62,10 +62,10 @@ export function runSimpleProcessSync(options: SimpleProcessOptions): SimpleProce
62
62
  );
63
63
 
64
64
  if (options.stdoutPath && stdout) {
65
- try { writeFileSyncAppend(options.stdoutPath, stdout); } catch {}
65
+ try { writeFileSyncAppend(options.stdoutPath, stdout); } catch {} // best-effort log write; failure doesn't affect result
66
66
  }
67
67
  if (options.stderrPath && stderr) {
68
- try { writeFileSyncAppend(options.stderrPath, stderr); } catch {}
68
+ try { writeFileSyncAppend(options.stderrPath, stderr); } catch {} // best-effort log write; failure doesn't affect result
69
69
  }
70
70
 
71
71
  return {
@@ -196,7 +196,7 @@ function gracefulKill(child: ChildProcess): void {
196
196
  try {
197
197
  if (process.platform !== "win32" && child.pid) process.kill(-child.pid, "SIGTERM");
198
198
  else child.kill("SIGTERM");
199
- } catch {}
199
+ } catch {} // cleanup failure is safe to ignore
200
200
  }
201
201
 
202
202
  function forceKill(child: ChildProcess): void {
@@ -213,7 +213,7 @@ function forceKill(child: ChildProcess): void {
213
213
  process.kill(-child.pid, "SIGKILL");
214
214
  }
215
215
  } catch {
216
- try { child.kill("SIGKILL"); } catch {}
216
+ try { child.kill("SIGKILL"); } catch {} // cleanup failure is safe to ignore
217
217
  }
218
218
  }
219
219
 
@@ -25,6 +25,7 @@ import { guardAgentCommand, guardTestCommand } from "../security/commandGuard.js
25
25
  import { validateAssessmentFreshness } from "../assessments/assessmentStore.js";
26
26
  import { captureRepoSnapshot } from "./changeCapture.js";
27
27
  import { runTask } from "./runTask.js";
28
+ import { logger } from "../logging.js";
28
29
 
29
30
  // ── Bootstrap ─────────────────────────────────────────────────────
30
31
 
@@ -42,10 +43,10 @@ const WATCHER_LAUNCHER_PID = Number.isInteger(Number(process.env.PATCHWARDEN_WAT
42
43
  : null;
43
44
  mkdirSync(dirname(WATCHER_HEARTBEAT_FILE), { recursive: true });
44
45
 
45
- console.error(`[watcher] Workspace: ${wsRoot}`);
46
- console.error(`[watcher] Tasks: ${tasksDir}`);
47
- console.error(`[watcher] Polling every ${POLL_INTERVAL_MS / 1000}s`);
48
- console.error(`[watcher] Press Ctrl+C to stop`);
46
+ logger.info(`[watcher] Workspace: ${wsRoot}`);
47
+ logger.info(`[watcher] Tasks: ${tasksDir}`);
48
+ logger.info(`[watcher] Polling every ${POLL_INTERVAL_MS / 1000}s`);
49
+ logger.info(`[watcher] Press Ctrl+C to stop`);
49
50
 
50
51
  // Track executed tasks to prevent re-execution
51
52
  const executedTasks = new Set<string>();
@@ -110,7 +111,7 @@ async function tick() {
110
111
 
111
112
  // Assessment freshness revalidation
112
113
  if (statusData.assessment_id) {
113
- const preExecSnapshot = captureRepoSnapshot(resolvedRepoPath);
114
+ const preExecSnapshot = await captureRepoSnapshot(resolvedRepoPath);
114
115
  const validation = validateAssessmentFreshness(String(statusData.assessment_id), preExecSnapshot);
115
116
  if (!validation.valid) {
116
117
  throw new Error(`assessment validation failed: ${validation.failure_reason}`);
@@ -118,7 +119,7 @@ async function tick() {
118
119
  }
119
120
  } catch (err) {
120
121
  const errMsg = `[watcher] Safety check failed for ${taskId}: ${err instanceof Error ? err.message : String(err)}`;
121
- console.error(errMsg);
122
+ logger.error(errMsg);
122
123
 
123
124
  // Write error and mark as failed so it doesn't get re-picked
124
125
  try {
@@ -128,30 +129,32 @@ async function tick() {
128
129
  data.error = errMsg;
129
130
  data.updated_at = new Date().toISOString();
130
131
  writeFileSync(statusFile, JSON.stringify(data, null, 2), "utf-8");
131
- } catch {}
132
+ } catch (err) {
133
+ logger.warn("watcher error-log write failed", { error: err instanceof Error ? err.message : String(err) });
134
+ }
132
135
  executedTasks.add(taskId);
133
136
  continue;
134
137
  }
135
138
 
136
139
  // ── Execute ──
137
- console.error(`[watcher] Executing: ${taskId}`);
140
+ logger.info(`[watcher] Executing: ${taskId}`);
138
141
  executedTasks.add(taskId);
139
142
 
140
143
  try {
141
144
  const result = await runTask(taskId);
142
- console.error(`[watcher] ${taskId} → ${result.status}`);
145
+ logger.info(`[watcher] ${taskId} → ${result.status}`);
143
146
  } catch (err) {
144
- console.error(`[watcher] ${taskId} → error: ${err instanceof Error ? err.message : String(err)}`);
147
+ logger.error(`[watcher] ${taskId} → error: ${err instanceof Error ? err.message : String(err)}`);
145
148
  }
146
149
  }
147
150
  } catch (err) {
148
- console.error(`[watcher] Tick error: ${err instanceof Error ? err.message : String(err)}`);
151
+ logger.error(`[watcher] Tick error: ${err instanceof Error ? err.message : String(err)}`);
149
152
  }
150
153
  }
151
154
 
152
155
  // ── Start ─────────────────────────────────────────────────────────
153
156
 
154
- console.error("[watcher] Started");
157
+ logger.info("[watcher] Started");
155
158
  setInterval(tick, POLL_INTERVAL_MS);
156
159
 
157
160
  // Run first tick immediately
@@ -159,10 +162,10 @@ tick();
159
162
 
160
163
  // Graceful shutdown
161
164
  process.on("SIGINT", () => {
162
- console.error("[watcher] Stopped");
165
+ logger.info("[watcher] Stopped");
163
166
  process.exit(0);
164
167
  });
165
168
  process.on("SIGTERM", () => {
166
- console.error("[watcher] Stopped");
169
+ logger.info("[watcher] Stopped");
167
170
  process.exit(0);
168
171
  });
@@ -12,6 +12,7 @@ export interface StructuredRedactionResult<T> {
12
12
 
13
13
  interface RedactionRule {
14
14
  category: string;
15
+ reason: string;
15
16
  pattern: RegExp;
16
17
  replace: string;
17
18
  }
@@ -19,31 +20,59 @@ interface RedactionRule {
19
20
  const RULES: RedactionRule[] = [
20
21
  {
21
22
  category: "private_key",
23
+ reason: "matched private key block",
22
24
  pattern: /-----BEGIN [^-\r\n]*PRIVATE KEY-----[\s\S]*?-----END [^-\r\n]*PRIVATE KEY-----/gi,
23
25
  replace: "[REDACTED PRIVATE KEY]",
24
26
  },
25
27
  {
26
28
  category: "bearer_token",
29
+ reason: "matched bearer token",
27
30
  pattern: /\bBearer\s+[A-Za-z0-9._~+\/-]{12,}={0,2}/gi,
28
31
  replace: "Bearer [REDACTED]",
29
32
  },
30
33
  {
31
34
  category: "npm_token",
35
+ reason: "matched npm auth token",
32
36
  pattern: /(\b_?authToken\s*=\s*)[^\s\r\n]+/gi,
33
37
  replace: "$1[REDACTED]",
34
38
  },
35
39
  {
36
40
  category: "credential_assignment",
41
+ reason: "matched credential assignment",
37
42
  pattern: /\b((?:access[_ -]?token|api[_ -]?key|secret|password|credential|token)\s*[:=]\s*)(["']?)([^\s,"'\]}]{8,})(["']?)/gi,
38
43
  replace: "$1$2[REDACTED]$4",
39
44
  },
40
45
  {
41
46
  category: "known_token_format",
47
+ reason: "matched known token format",
42
48
  pattern: /\b(?:sk|ghp|github_pat)_[A-Za-z0-9_-]{16,}\b/g,
43
49
  replace: "[REDACTED TOKEN]",
44
50
  },
45
51
  ];
46
52
 
53
+ export interface RedactionCategoryCount {
54
+ category: string;
55
+ reason: string;
56
+ count: number;
57
+ }
58
+
59
+ export function countRedactionsByCategory(input: string): RedactionCategoryCount[] {
60
+ const counts = new Map<string, number>();
61
+ for (const rule of RULES) {
62
+ rule.pattern.lastIndex = 0;
63
+ const matches = input.match(rule.pattern);
64
+ if (matches && matches.length > 0) {
65
+ counts.set(rule.category, (counts.get(rule.category) || 0) + matches.length);
66
+ }
67
+ }
68
+ const result: RedactionCategoryCount[] = [];
69
+ for (const rule of RULES) {
70
+ const count = counts.get(rule.category);
71
+ if (count) result.push({ category: rule.category, reason: rule.reason, count });
72
+ }
73
+ return result;
74
+ }
75
+
47
76
  export function redactSensitiveContent(input: string): RedactionResult {
48
77
  let content = input;
49
78
  const categories: string[] = [];