@datafog/fogclaw 0.2.0 → 0.3.0

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 (103) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/backlog-tools.d.ts +57 -0
  3. package/dist/backlog-tools.d.ts.map +1 -0
  4. package/dist/backlog-tools.js +173 -0
  5. package/dist/backlog-tools.js.map +1 -0
  6. package/dist/backlog.d.ts +82 -0
  7. package/dist/backlog.d.ts.map +1 -0
  8. package/dist/backlog.js +169 -0
  9. package/dist/backlog.js.map +1 -0
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +6 -0
  12. package/dist/config.js.map +1 -1
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +87 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/message-sending-handler.d.ts +2 -1
  18. package/dist/message-sending-handler.d.ts.map +1 -1
  19. package/dist/message-sending-handler.js +5 -1
  20. package/dist/message-sending-handler.js.map +1 -1
  21. package/dist/tool-result-handler.d.ts +2 -1
  22. package/dist/tool-result-handler.d.ts.map +1 -1
  23. package/dist/tool-result-handler.js +5 -1
  24. package/dist/tool-result-handler.js.map +1 -1
  25. package/dist/types.d.ts +15 -0
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/types.js.map +1 -1
  28. package/openclaw.plugin.json +11 -1
  29. package/package.json +7 -1
  30. package/.github/workflows/harness-docs.yml +0 -30
  31. package/AGENTS.md +0 -28
  32. package/docs/DATA.md +0 -28
  33. package/docs/DESIGN.md +0 -17
  34. package/docs/DOMAIN_DOCS.md +0 -30
  35. package/docs/FRONTEND.md +0 -24
  36. package/docs/OBSERVABILITY.md +0 -32
  37. package/docs/PLANS.md +0 -171
  38. package/docs/PRODUCT_SENSE.md +0 -20
  39. package/docs/RELIABILITY.md +0 -60
  40. package/docs/SECURITY.md +0 -52
  41. package/docs/design-docs/core-beliefs.md +0 -17
  42. package/docs/design-docs/index.md +0 -8
  43. package/docs/generated/README.md +0 -36
  44. package/docs/generated/memory.md +0 -1
  45. package/docs/plans/2026-02-16-fogclaw-design.md +0 -172
  46. package/docs/plans/2026-02-16-fogclaw-implementation.md +0 -1606
  47. package/docs/plans/README.md +0 -15
  48. package/docs/plans/active/2026-02-16-feat-openclaw-official-submission-plan.md +0 -386
  49. package/docs/plans/active/2026-02-17-feat-release-fogclaw-via-datafog-package-plan.md +0 -328
  50. package/docs/plans/active/2026-02-17-feat-submit-fogclaw-to-openclaw-plan.md +0 -244
  51. package/docs/plans/active/2026-02-17-feat-tool-result-pii-scanning-plan.md +0 -293
  52. package/docs/plans/tech-debt-tracker.md +0 -42
  53. package/docs/plugins/fogclaw.md +0 -101
  54. package/docs/runbooks/address-review-findings.md +0 -30
  55. package/docs/runbooks/ci-failures.md +0 -46
  56. package/docs/runbooks/code-review.md +0 -34
  57. package/docs/runbooks/merge-change.md +0 -28
  58. package/docs/runbooks/pull-request.md +0 -45
  59. package/docs/runbooks/record-evidence.md +0 -43
  60. package/docs/runbooks/reproduce-bug.md +0 -42
  61. package/docs/runbooks/respond-to-feedback.md +0 -42
  62. package/docs/runbooks/review-findings.md +0 -31
  63. package/docs/runbooks/submit-openclaw-plugin.md +0 -68
  64. package/docs/runbooks/update-agents-md.md +0 -59
  65. package/docs/runbooks/update-domain-docs.md +0 -42
  66. package/docs/runbooks/validate-current-state.md +0 -41
  67. package/docs/runbooks/verify-release.md +0 -69
  68. package/docs/specs/2026-02-16-feat-openclaw-official-submission-spec.md +0 -115
  69. package/docs/specs/2026-02-17-feat-outbound-message-pii-scanning-spec.md +0 -93
  70. package/docs/specs/2026-02-17-feat-submit-fogclaw-to-openclaw.md +0 -125
  71. package/docs/specs/2026-02-17-feat-tool-result-pii-scanning-spec.md +0 -122
  72. package/docs/specs/README.md +0 -5
  73. package/docs/specs/index.md +0 -8
  74. package/docs/spikes/README.md +0 -8
  75. package/fogclaw.config.example.json +0 -33
  76. package/scripts/ci/he-docs-config.json +0 -123
  77. package/scripts/ci/he-docs-drift.sh +0 -112
  78. package/scripts/ci/he-docs-lint.sh +0 -234
  79. package/scripts/ci/he-plans-lint.sh +0 -354
  80. package/scripts/ci/he-runbooks-lint.sh +0 -445
  81. package/scripts/ci/he-specs-lint.sh +0 -258
  82. package/scripts/ci/he-spikes-lint.sh +0 -249
  83. package/scripts/runbooks/select-runbooks.sh +0 -154
  84. package/src/config.ts +0 -183
  85. package/src/engines/gliner.ts +0 -240
  86. package/src/engines/regex.ts +0 -71
  87. package/src/extract.ts +0 -98
  88. package/src/index.ts +0 -381
  89. package/src/message-sending-handler.ts +0 -87
  90. package/src/redactor.ts +0 -51
  91. package/src/scanner.ts +0 -196
  92. package/src/tool-result-handler.ts +0 -133
  93. package/src/types.ts +0 -75
  94. package/tests/config.test.ts +0 -78
  95. package/tests/extract.test.ts +0 -185
  96. package/tests/gliner.test.ts +0 -289
  97. package/tests/message-sending-handler.test.ts +0 -244
  98. package/tests/plugin-smoke.test.ts +0 -250
  99. package/tests/redactor.test.ts +0 -320
  100. package/tests/regex.test.ts +0 -345
  101. package/tests/scanner.test.ts +0 -348
  102. package/tests/tool-result-handler.test.ts +0 -329
  103. package/tsconfig.json +0 -20
@@ -1,329 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { createToolResultHandler } from "../src/tool-result-handler.js";
3
- import { RegexEngine } from "../src/engines/regex.js";
4
- import type { FogClawConfig } from "../src/types.js";
5
-
6
- function makeConfig(overrides: Partial<FogClawConfig> = {}): FogClawConfig {
7
- return {
8
- enabled: true,
9
- guardrail_mode: "redact",
10
- redactStrategy: "token",
11
- model: "test",
12
- confidence_threshold: 0.5,
13
- custom_entities: [],
14
- entityActions: {},
15
- entityConfidenceThresholds: {},
16
- allowlist: { values: [], patterns: [], entities: {} },
17
- auditEnabled: false,
18
- ...overrides,
19
- };
20
- }
21
-
22
- function makeLogger() {
23
- return {
24
- info: vi.fn(),
25
- warn: vi.fn(),
26
- };
27
- }
28
-
29
- describe("createToolResultHandler", () => {
30
- const regexEngine = new RegexEngine();
31
-
32
- it("returns a synchronous function (not async)", () => {
33
- const handler = createToolResultHandler(makeConfig(), regexEngine);
34
- expect(typeof handler).toBe("function");
35
-
36
- // Verify it does not return a Promise
37
- const result = handler(
38
- { message: "no pii here" },
39
- {},
40
- );
41
- // undefined is expected for no-PII — not a Promise
42
- expect(result).toBeUndefined();
43
- });
44
-
45
- it("redacts SSN in a plain string message", () => {
46
- const handler = createToolResultHandler(makeConfig(), regexEngine);
47
- const result = handler(
48
- { message: "SSN is 123-45-6789" },
49
- {},
50
- );
51
- expect(result).toBeDefined();
52
- expect(result!.message).toBe("SSN is [SSN_1]");
53
- });
54
-
55
- it("redacts email in a content-string message", () => {
56
- const handler = createToolResultHandler(makeConfig(), regexEngine);
57
- const result = handler(
58
- { message: { role: "toolResult", content: "Contact john@example.com" } },
59
- {},
60
- );
61
- expect(result).toBeDefined();
62
- const msg = result!.message as Record<string, unknown>;
63
- expect(msg.content).toBe("Contact [EMAIL_1]");
64
- expect(msg.role).toBe("toolResult");
65
- });
66
-
67
- it("redacts phone number in content block array", () => {
68
- const handler = createToolResultHandler(makeConfig(), regexEngine);
69
- const result = handler(
70
- {
71
- message: {
72
- content: [{ type: "text", text: "Call 555-123-4567 please" }],
73
- },
74
- },
75
- {},
76
- );
77
- expect(result).toBeDefined();
78
- const msg = result!.message as Record<string, unknown>;
79
- const content = msg.content as Array<Record<string, unknown>>;
80
- expect(content[0].text).toBe("Call [PHONE_1] please");
81
- });
82
-
83
- it("redacts multiple PII types in one message", () => {
84
- const handler = createToolResultHandler(makeConfig(), regexEngine);
85
- const result = handler(
86
- { message: "Call 555-123-4567 or email john@example.com" },
87
- {},
88
- );
89
- expect(result).toBeDefined();
90
- const text = result!.message as string;
91
- expect(text).toContain("[PHONE_1]");
92
- expect(text).toContain("[EMAIL_1]");
93
- expect(text).not.toContain("555-123-4567");
94
- expect(text).not.toContain("john@example.com");
95
- });
96
-
97
- it("returns void when no PII is found", () => {
98
- const handler = createToolResultHandler(makeConfig(), regexEngine);
99
- const result = handler(
100
- { message: "This is clean text with no sensitive data." },
101
- {},
102
- );
103
- expect(result).toBeUndefined();
104
- });
105
-
106
- it("returns void for empty string message", () => {
107
- const handler = createToolResultHandler(makeConfig(), regexEngine);
108
- expect(handler({ message: "" }, {})).toBeUndefined();
109
- });
110
-
111
- it("returns void for null message", () => {
112
- const handler = createToolResultHandler(makeConfig(), regexEngine);
113
- expect(handler({ message: null }, {})).toBeUndefined();
114
- });
115
-
116
- it("returns void for message with no extractable text", () => {
117
- const handler = createToolResultHandler(makeConfig(), regexEngine);
118
- expect(
119
- handler(
120
- {
121
- message: {
122
- content: [{ type: "image", source: { data: "base64" } }],
123
- },
124
- },
125
- {},
126
- ),
127
- ).toBeUndefined();
128
- });
129
-
130
- it("respects allowlist — global values", () => {
131
- const config = makeConfig({
132
- allowlist: {
133
- values: ["noreply@example.com"],
134
- patterns: [],
135
- entities: {},
136
- },
137
- });
138
- const handler = createToolResultHandler(config, regexEngine);
139
- const result = handler(
140
- { message: "Contact noreply@example.com for help" },
141
- {},
142
- );
143
- // The allowlisted email should not be redacted
144
- expect(result).toBeUndefined();
145
- });
146
-
147
- it("respects allowlist — global patterns", () => {
148
- const config = makeConfig({
149
- allowlist: {
150
- values: [],
151
- patterns: ["^internal-"],
152
- entities: {},
153
- },
154
- });
155
- const handler = createToolResultHandler(config, regexEngine);
156
- const result = handler(
157
- { message: "Contact internal-noreply@company.com" },
158
- {},
159
- );
160
- expect(result).toBeUndefined();
161
- });
162
-
163
- it("respects allowlist — per-entity values", () => {
164
- const config = makeConfig({
165
- allowlist: {
166
- values: [],
167
- patterns: [],
168
- entities: { EMAIL: ["public@example.com"] },
169
- },
170
- });
171
- const handler = createToolResultHandler(config, regexEngine);
172
- const result = handler(
173
- { message: "Email public@example.com or secret@example.com" },
174
- {},
175
- );
176
- expect(result).toBeDefined();
177
- const text = result!.message as string;
178
- // public@example.com should be preserved, secret@example.com should be redacted
179
- expect(text).toContain("public@example.com");
180
- expect(text).not.toContain("secret@example.com");
181
- expect(text).toContain("[EMAIL_1]");
182
- });
183
-
184
- it("uses mask redaction strategy", () => {
185
- const config = makeConfig({ redactStrategy: "mask" });
186
- const handler = createToolResultHandler(config, regexEngine);
187
- const result = handler(
188
- { message: "SSN is 123-45-6789" },
189
- {},
190
- );
191
- expect(result).toBeDefined();
192
- const text = result!.message as string;
193
- expect(text).toContain("***********");
194
- expect(text).not.toContain("123-45-6789");
195
- });
196
-
197
- it("uses hash redaction strategy", () => {
198
- const config = makeConfig({ redactStrategy: "hash" });
199
- const handler = createToolResultHandler(config, regexEngine);
200
- const result = handler(
201
- { message: "SSN is 123-45-6789" },
202
- {},
203
- );
204
- expect(result).toBeDefined();
205
- const text = result!.message as string;
206
- expect(text).toMatch(/\[SSN_[a-f0-9]{12}\]/);
207
- expect(text).not.toContain("123-45-6789");
208
- });
209
-
210
- it("applies entityActions config — all modes produce redaction", () => {
211
- const config = makeConfig({
212
- entityActions: { SSN: "block", EMAIL: "warn" },
213
- });
214
- const handler = createToolResultHandler(config, regexEngine);
215
- const result = handler(
216
- { message: "SSN 123-45-6789, email john@example.com" },
217
- {},
218
- );
219
- expect(result).toBeDefined();
220
- const text = result!.message as string;
221
- // Both block and warn modes produce span-level redaction in tool results
222
- expect(text).toContain("[SSN_1]");
223
- expect(text).toContain("[EMAIL_1]");
224
- expect(text).not.toContain("123-45-6789");
225
- expect(text).not.toContain("john@example.com");
226
- });
227
-
228
- it("preserves non-text content blocks", () => {
229
- const handler = createToolResultHandler(makeConfig(), regexEngine);
230
- const result = handler(
231
- {
232
- message: {
233
- content: [
234
- { type: "text", text: "SSN is 123-45-6789" },
235
- { type: "image", source: { data: "imagedata" } },
236
- ],
237
- },
238
- },
239
- {},
240
- );
241
- expect(result).toBeDefined();
242
- const msg = result!.message as Record<string, unknown>;
243
- const content = msg.content as Array<Record<string, unknown>>;
244
- expect(content[0].text).toBe("SSN is [SSN_1]");
245
- expect((content[1] as any).type).toBe("image");
246
- expect((content[1] as any).source.data).toBe("imagedata");
247
- });
248
-
249
- describe("audit logging", () => {
250
- it("emits audit log when auditEnabled and PII found", () => {
251
- const config = makeConfig({ auditEnabled: true });
252
- const logger = makeLogger();
253
- const handler = createToolResultHandler(config, regexEngine, logger);
254
-
255
- handler(
256
- { message: "SSN 123-45-6789", toolName: "file_read" },
257
- {},
258
- );
259
-
260
- expect(logger.info).toHaveBeenCalledOnce();
261
- const logCall = logger.info.mock.calls[0][0] as string;
262
- expect(logCall).toContain("[FOGCLAW AUDIT]");
263
- expect(logCall).toContain("tool_result_scan");
264
- expect(logCall).toContain('"source":"tool_result"');
265
- expect(logCall).toContain('"toolName":"file_read"');
266
- expect(logCall).toContain('"SSN"');
267
- // Must not contain raw PII
268
- expect(logCall).not.toContain("123-45-6789");
269
- });
270
-
271
- it("does not emit audit log when auditEnabled is false", () => {
272
- const config = makeConfig({ auditEnabled: false });
273
- const logger = makeLogger();
274
- const handler = createToolResultHandler(config, regexEngine, logger);
275
-
276
- handler({ message: "SSN 123-45-6789" }, {});
277
-
278
- expect(logger.info).not.toHaveBeenCalled();
279
- });
280
-
281
- it("does not emit audit log when no PII found", () => {
282
- const config = makeConfig({ auditEnabled: true });
283
- const logger = makeLogger();
284
- const handler = createToolResultHandler(config, regexEngine, logger);
285
-
286
- handler({ message: "clean text" }, {});
287
-
288
- expect(logger.info).not.toHaveBeenCalled();
289
- });
290
-
291
- it("includes entity count and labels in audit log", () => {
292
- const config = makeConfig({ auditEnabled: true });
293
- const logger = makeLogger();
294
- const handler = createToolResultHandler(config, regexEngine, logger);
295
-
296
- handler(
297
- { message: "Call 555-123-4567, email john@example.com" },
298
- {},
299
- );
300
-
301
- const logCall = logger.info.mock.calls[0][0] as string;
302
- const parsed = JSON.parse(logCall.replace("[FOGCLAW AUDIT] tool_result_scan ", ""));
303
- expect(parsed.totalEntities).toBe(2);
304
- expect(parsed.labels).toContain("PHONE");
305
- expect(parsed.labels).toContain("EMAIL");
306
- expect(parsed.source).toBe("tool_result");
307
- });
308
- });
309
-
310
- it("handles multiple text blocks with PII in different blocks", () => {
311
- const handler = createToolResultHandler(makeConfig(), regexEngine);
312
- const result = handler(
313
- {
314
- message: {
315
- content: [
316
- { type: "text", text: "First block clean" },
317
- { type: "text", text: "Second block SSN 123-45-6789" },
318
- ],
319
- },
320
- },
321
- {},
322
- );
323
- expect(result).toBeDefined();
324
- const msg = result!.message as Record<string, unknown>;
325
- const content = msg.content as Array<Record<string, unknown>>;
326
- expect(content[0].text).toBe("First block clean");
327
- expect(content[1].text).toBe("Second block SSN [SSN_1]");
328
- });
329
- });
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "outDir": "dist",
8
- "rootDir": "src",
9
- "strict": true,
10
- "declaration": true,
11
- "declarationMap": true,
12
- "sourceMap": true,
13
- "esModuleInterop": true,
14
- "skipLibCheck": true,
15
- "forceConsistentCasingInFileNames": true,
16
- "resolveJsonModule": true
17
- },
18
- "include": ["src/**/*"],
19
- "exclude": ["node_modules", "dist", "tests"]
20
- }