@datafog/fogclaw 0.1.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.
- package/.github/workflows/harness-docs.yml +30 -0
- package/AGENTS.md +28 -0
- package/LICENSE +21 -0
- package/README.md +208 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +30 -0
- package/dist/config.js.map +1 -0
- package/dist/engines/gliner.d.ts +14 -0
- package/dist/engines/gliner.d.ts.map +1 -0
- package/dist/engines/gliner.js +75 -0
- package/dist/engines/gliner.js.map +1 -0
- package/dist/engines/regex.d.ts +5 -0
- package/dist/engines/regex.d.ts.map +1 -0
- package/dist/engines/regex.js +54 -0
- package/dist/engines/regex.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +157 -0
- package/dist/index.js.map +1 -0
- package/dist/redactor.d.ts +3 -0
- package/dist/redactor.d.ts.map +1 -0
- package/dist/redactor.js +37 -0
- package/dist/redactor.js.map +1 -0
- package/dist/scanner.d.ts +11 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +77 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/docs/DATA.md +28 -0
- package/docs/DESIGN.md +17 -0
- package/docs/DOMAIN_DOCS.md +30 -0
- package/docs/FRONTEND.md +24 -0
- package/docs/OBSERVABILITY.md +25 -0
- package/docs/PLANS.md +171 -0
- package/docs/PRODUCT_SENSE.md +20 -0
- package/docs/RELIABILITY.md +60 -0
- package/docs/SECURITY.md +50 -0
- package/docs/design-docs/core-beliefs.md +17 -0
- package/docs/design-docs/index.md +8 -0
- package/docs/generated/README.md +36 -0
- package/docs/generated/memory.md +1 -0
- package/docs/plans/2026-02-16-fogclaw-design.md +172 -0
- package/docs/plans/2026-02-16-fogclaw-implementation.md +1606 -0
- package/docs/plans/README.md +15 -0
- package/docs/plans/active/2026-02-16-feat-openclaw-official-submission-plan.md +386 -0
- package/docs/plans/active/2026-02-17-feat-release-fogclaw-via-datafog-package-plan.md +318 -0
- package/docs/plans/active/2026-02-17-feat-submit-fogclaw-to-openclaw-plan.md +244 -0
- package/docs/plans/tech-debt-tracker.md +42 -0
- package/docs/plugins/fogclaw.md +95 -0
- package/docs/runbooks/address-review-findings.md +30 -0
- package/docs/runbooks/ci-failures.md +46 -0
- package/docs/runbooks/code-review.md +34 -0
- package/docs/runbooks/merge-change.md +28 -0
- package/docs/runbooks/pull-request.md +45 -0
- package/docs/runbooks/record-evidence.md +43 -0
- package/docs/runbooks/reproduce-bug.md +42 -0
- package/docs/runbooks/respond-to-feedback.md +42 -0
- package/docs/runbooks/review-findings.md +31 -0
- package/docs/runbooks/submit-openclaw-plugin.md +68 -0
- package/docs/runbooks/update-agents-md.md +59 -0
- package/docs/runbooks/update-domain-docs.md +42 -0
- package/docs/runbooks/validate-current-state.md +41 -0
- package/docs/runbooks/verify-release.md +69 -0
- package/docs/specs/2026-02-16-feat-openclaw-official-submission-spec.md +115 -0
- package/docs/specs/2026-02-17-feat-submit-fogclaw-to-openclaw.md +125 -0
- package/docs/specs/README.md +5 -0
- package/docs/specs/index.md +8 -0
- package/docs/spikes/README.md +8 -0
- package/fogclaw.config.example.json +15 -0
- package/openclaw.plugin.json +45 -0
- package/package.json +37 -0
- package/scripts/ci/he-docs-config.json +123 -0
- package/scripts/ci/he-docs-drift.sh +112 -0
- package/scripts/ci/he-docs-lint.sh +234 -0
- package/scripts/ci/he-plans-lint.sh +354 -0
- package/scripts/ci/he-runbooks-lint.sh +445 -0
- package/scripts/ci/he-specs-lint.sh +258 -0
- package/scripts/ci/he-spikes-lint.sh +249 -0
- package/scripts/runbooks/select-runbooks.sh +154 -0
- package/src/config.ts +46 -0
- package/src/engines/gliner.ts +88 -0
- package/src/engines/regex.ts +71 -0
- package/src/index.ts +223 -0
- package/src/redactor.ts +51 -0
- package/src/scanner.ts +90 -0
- package/src/types.ts +52 -0
- package/tests/config.test.ts +104 -0
- package/tests/gliner.test.ts +184 -0
- package/tests/plugin-smoke.test.ts +114 -0
- package/tests/redactor.test.ts +320 -0
- package/tests/regex.test.ts +345 -0
- package/tests/scanner.test.ts +199 -0
- package/tsconfig.json +20 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Scanner } from "./scanner.js";
|
|
2
|
+
import { redact } from "./redactor.js";
|
|
3
|
+
import { loadConfig } from "./config.js";
|
|
4
|
+
export { Scanner } from "./scanner.js";
|
|
5
|
+
export { redact } from "./redactor.js";
|
|
6
|
+
export { loadConfig, DEFAULT_CONFIG } from "./config.js";
|
|
7
|
+
/**
|
|
8
|
+
* OpenClaw plugin definition.
|
|
9
|
+
*
|
|
10
|
+
* Registers:
|
|
11
|
+
* - `before_agent_start` hook for automatic PII guardrail
|
|
12
|
+
* - `fogclaw_scan` tool for on-demand entity detection
|
|
13
|
+
* - `fogclaw_redact` tool for on-demand redaction
|
|
14
|
+
*/
|
|
15
|
+
const fogclaw = {
|
|
16
|
+
id: "fogclaw",
|
|
17
|
+
name: "FogClaw",
|
|
18
|
+
register(api) {
|
|
19
|
+
const rawConfig = api.pluginConfig ?? api.getConfig?.() ?? {};
|
|
20
|
+
const config = loadConfig(rawConfig);
|
|
21
|
+
if (!config.enabled) {
|
|
22
|
+
api.logger?.info("[fogclaw] Plugin disabled via config");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const scanner = new Scanner(config);
|
|
26
|
+
// Initialize GLiNER in the background — regex works immediately,
|
|
27
|
+
// GLiNER becomes available once the model loads.
|
|
28
|
+
scanner.initialize().catch((err) => {
|
|
29
|
+
api.logger?.warn(`[fogclaw] GLiNER background init failed: ${String(err)}`);
|
|
30
|
+
});
|
|
31
|
+
// --- HOOK: Guardrail on incoming messages ---
|
|
32
|
+
api.on("before_agent_start", async (event) => {
|
|
33
|
+
const message = event.prompt ?? "";
|
|
34
|
+
if (!message)
|
|
35
|
+
return;
|
|
36
|
+
const result = await scanner.scan(message);
|
|
37
|
+
if (result.entities.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
// Classify entities by their configured action
|
|
40
|
+
const blocked = [];
|
|
41
|
+
const warned = [];
|
|
42
|
+
const toRedact = [];
|
|
43
|
+
for (const entity of result.entities) {
|
|
44
|
+
const action = config.entityActions[entity.label] ?? config.guardrail_mode;
|
|
45
|
+
if (action === "block")
|
|
46
|
+
blocked.push(entity);
|
|
47
|
+
else if (action === "warn")
|
|
48
|
+
warned.push(entity);
|
|
49
|
+
else if (action === "redact")
|
|
50
|
+
toRedact.push(entity);
|
|
51
|
+
}
|
|
52
|
+
const contextParts = [];
|
|
53
|
+
// "block" — inject a strong instruction to refuse
|
|
54
|
+
if (blocked.length > 0) {
|
|
55
|
+
const types = [...new Set(blocked.map((e) => e.label))].join(", ");
|
|
56
|
+
contextParts.push(`[FOGCLAW GUARDRAIL — BLOCKED] The user's message contains sensitive information (${types}). ` +
|
|
57
|
+
`Do NOT process or repeat this information. Ask the user to rephrase without sensitive data.`);
|
|
58
|
+
}
|
|
59
|
+
// "warn" — inject a warning notice
|
|
60
|
+
if (warned.length > 0) {
|
|
61
|
+
const types = [...new Set(warned.map((e) => e.label))].join(", ");
|
|
62
|
+
contextParts.push(`[FOGCLAW NOTICE] PII detected in user message: ${types}. Handle with care.`);
|
|
63
|
+
}
|
|
64
|
+
// "redact" — replace PII with tokens
|
|
65
|
+
if (toRedact.length > 0) {
|
|
66
|
+
const redacted = redact(message, toRedact, config.redactStrategy);
|
|
67
|
+
contextParts.push(`[FOGCLAW REDACTED] The following is the user's message with PII redacted:\n${redacted.redacted_text}`);
|
|
68
|
+
}
|
|
69
|
+
if (contextParts.length > 0) {
|
|
70
|
+
return { prependContext: contextParts.join("\n\n") };
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// --- TOOL: On-demand scan ---
|
|
74
|
+
api.registerTool({
|
|
75
|
+
name: "fogclaw_scan",
|
|
76
|
+
id: "fogclaw_scan",
|
|
77
|
+
description: "Scan text for PII and custom entities. Returns detected entities with types, positions, and confidence scores.",
|
|
78
|
+
schema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
text: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Text to scan for entities",
|
|
84
|
+
},
|
|
85
|
+
custom_labels: {
|
|
86
|
+
type: "array",
|
|
87
|
+
items: { type: "string" },
|
|
88
|
+
description: "Additional entity labels for zero-shot detection (e.g., ['competitor name', 'project codename'])",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
required: ["text"],
|
|
92
|
+
},
|
|
93
|
+
handler: async ({ text, custom_labels, }) => {
|
|
94
|
+
const result = await scanner.scan(text, custom_labels);
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: JSON.stringify({
|
|
100
|
+
entities: result.entities,
|
|
101
|
+
count: result.entities.length,
|
|
102
|
+
summary: result.entities.length > 0
|
|
103
|
+
? `Found ${result.entities.length} entities: ${[...new Set(result.entities.map((e) => e.label))].join(", ")}`
|
|
104
|
+
: "No entities detected",
|
|
105
|
+
}, null, 2),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
// --- TOOL: On-demand redact ---
|
|
112
|
+
api.registerTool({
|
|
113
|
+
name: "fogclaw_redact",
|
|
114
|
+
id: "fogclaw_redact",
|
|
115
|
+
description: "Scan and redact PII/custom entities from text. Returns sanitized text with entities replaced.",
|
|
116
|
+
schema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
text: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Text to scan and redact",
|
|
122
|
+
},
|
|
123
|
+
strategy: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: 'Redaction strategy: "token" ([EMAIL_1]), "mask" (****), or "hash" ([EMAIL_a1b2c3...])',
|
|
126
|
+
enum: ["token", "mask", "hash"],
|
|
127
|
+
},
|
|
128
|
+
custom_labels: {
|
|
129
|
+
type: "array",
|
|
130
|
+
items: { type: "string" },
|
|
131
|
+
description: "Additional entity labels for zero-shot detection",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
required: ["text"],
|
|
135
|
+
},
|
|
136
|
+
handler: async ({ text, strategy, custom_labels, }) => {
|
|
137
|
+
const result = await scanner.scan(text, custom_labels);
|
|
138
|
+
const redacted = redact(text, result.entities, strategy ?? config.redactStrategy);
|
|
139
|
+
return {
|
|
140
|
+
content: [
|
|
141
|
+
{
|
|
142
|
+
type: "text",
|
|
143
|
+
text: JSON.stringify({
|
|
144
|
+
redacted_text: redacted.redacted_text,
|
|
145
|
+
entities_found: result.entities.length,
|
|
146
|
+
mapping: redacted.mapping,
|
|
147
|
+
}, null, 2),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
api.logger?.info(`[fogclaw] Plugin registered — guardrail: ${config.guardrail_mode}, model: ${config.model}, custom entities: ${config.custom_entities.length}`);
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
export default fogclaw;
|
|
157
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactor.d.ts","sourceRoot":"","sources":["../src/redactor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEvE,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAAE,EAClB,QAAQ,GAAE,cAAwB,GACjC,YAAY,CAoBd"}
|
package/dist/redactor.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export function redact(text, entities, strategy = "token") {
|
|
3
|
+
if (entities.length === 0) {
|
|
4
|
+
return { redacted_text: text, mapping: {}, entities: [] };
|
|
5
|
+
}
|
|
6
|
+
// Sort by start position descending so we replace from end to start
|
|
7
|
+
// without corrupting earlier offsets
|
|
8
|
+
const sorted = [...entities].sort((a, b) => b.start - a.start);
|
|
9
|
+
const counters = {};
|
|
10
|
+
const mapping = {};
|
|
11
|
+
let result = text;
|
|
12
|
+
for (const entity of sorted) {
|
|
13
|
+
const replacement = makeReplacement(entity, strategy, counters);
|
|
14
|
+
mapping[replacement] = entity.text;
|
|
15
|
+
result = result.slice(0, entity.start) + replacement + result.slice(entity.end);
|
|
16
|
+
}
|
|
17
|
+
return { redacted_text: result, mapping, entities };
|
|
18
|
+
}
|
|
19
|
+
function makeReplacement(entity, strategy, counters) {
|
|
20
|
+
switch (strategy) {
|
|
21
|
+
case "token": {
|
|
22
|
+
counters[entity.label] = (counters[entity.label] ?? 0) + 1;
|
|
23
|
+
return `[${entity.label}_${counters[entity.label]}]`;
|
|
24
|
+
}
|
|
25
|
+
case "mask": {
|
|
26
|
+
return "*".repeat(Math.max(entity.text.length, 1));
|
|
27
|
+
}
|
|
28
|
+
case "hash": {
|
|
29
|
+
const digest = createHash("sha256")
|
|
30
|
+
.update(entity.text)
|
|
31
|
+
.digest("hex")
|
|
32
|
+
.slice(0, 12);
|
|
33
|
+
return `[${entity.label}_${digest}]`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=redactor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactor.js","sourceRoot":"","sources":["../src/redactor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,UAAU,MAAM,CACpB,IAAY,EACZ,QAAkB,EAClB,WAA2B,OAAO;IAElC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC5D,CAAC;IAED,oEAAoE;IACpE,qCAAqC;IACrC,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAE/D,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,MAAM,GAAG,IAAI,CAAC;IAElB,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChE,OAAO,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC;QACnC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClF,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtD,CAAC;AAED,SAAS,eAAe,CACtB,MAAc,EACd,QAAwB,EACxB,QAAgC;IAEhC,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAC3D,OAAO,IAAI,MAAM,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACvD,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC;iBAChC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;iBACnB,MAAM,CAAC,KAAK,CAAC;iBACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChB,OAAO,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,GAAG,CAAC;QACvC,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FogClawConfig, ScanResult } from "./types.js";
|
|
2
|
+
export declare class Scanner {
|
|
3
|
+
private regexEngine;
|
|
4
|
+
private glinerEngine;
|
|
5
|
+
private glinerAvailable;
|
|
6
|
+
private config;
|
|
7
|
+
constructor(config: FogClawConfig);
|
|
8
|
+
initialize(): Promise<void>;
|
|
9
|
+
scan(text: string, extraLabels?: string[]): Promise<ScanResult>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIpE,qBAAa,OAAO;IAClB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,aAAa;IAY3B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;CAqBtE"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { RegexEngine } from "./engines/regex.js";
|
|
2
|
+
import { GlinerEngine } from "./engines/gliner.js";
|
|
3
|
+
export class Scanner {
|
|
4
|
+
regexEngine;
|
|
5
|
+
glinerEngine;
|
|
6
|
+
glinerAvailable = false;
|
|
7
|
+
config;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.regexEngine = new RegexEngine();
|
|
11
|
+
this.glinerEngine = new GlinerEngine(config.model, config.confidence_threshold);
|
|
12
|
+
if (config.custom_entities.length > 0) {
|
|
13
|
+
this.glinerEngine.setCustomLabels(config.custom_entities);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async initialize() {
|
|
17
|
+
try {
|
|
18
|
+
await this.glinerEngine.initialize();
|
|
19
|
+
this.glinerAvailable = true;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
console.warn(`[fogclaw] GLiNER failed to initialize, falling back to regex-only mode: ${err instanceof Error ? err.message : String(err)}`);
|
|
23
|
+
this.glinerAvailable = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async scan(text, extraLabels) {
|
|
27
|
+
if (!text)
|
|
28
|
+
return { entities: [], text };
|
|
29
|
+
// Step 1: Regex pass (always runs, synchronous)
|
|
30
|
+
const regexEntities = this.regexEngine.scan(text);
|
|
31
|
+
// Step 2: GLiNER pass (if available)
|
|
32
|
+
let glinerEntities = [];
|
|
33
|
+
if (this.glinerAvailable) {
|
|
34
|
+
try {
|
|
35
|
+
glinerEntities = await this.glinerEngine.scan(text, extraLabels);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.warn(`[fogclaw] GLiNER scan failed, using regex results only: ${err instanceof Error ? err.message : String(err)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Step 3: Merge and deduplicate
|
|
42
|
+
const merged = deduplicateEntities([...regexEntities, ...glinerEntities]);
|
|
43
|
+
return { entities: merged, text };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Remove overlapping entity spans. When two entities overlap,
|
|
48
|
+
* keep the one with higher confidence. If equal, prefer regex.
|
|
49
|
+
*/
|
|
50
|
+
function deduplicateEntities(entities) {
|
|
51
|
+
if (entities.length <= 1)
|
|
52
|
+
return entities;
|
|
53
|
+
// Sort by start position, then by confidence descending
|
|
54
|
+
const sorted = [...entities].sort((a, b) => {
|
|
55
|
+
if (a.start !== b.start)
|
|
56
|
+
return a.start - b.start;
|
|
57
|
+
return b.confidence - a.confidence;
|
|
58
|
+
});
|
|
59
|
+
const result = [sorted[0]];
|
|
60
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
61
|
+
const current = sorted[i];
|
|
62
|
+
const last = result[result.length - 1];
|
|
63
|
+
// Check for overlap
|
|
64
|
+
if (current.start < last.end) {
|
|
65
|
+
// Overlapping: keep higher confidence (already in result if first)
|
|
66
|
+
if (current.confidence > last.confidence) {
|
|
67
|
+
result[result.length - 1] = current;
|
|
68
|
+
}
|
|
69
|
+
// Otherwise keep what's already in result
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
result.push(current);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface Entity {
|
|
2
|
+
text: string;
|
|
3
|
+
label: string;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
confidence: number;
|
|
7
|
+
source: "regex" | "gliner";
|
|
8
|
+
}
|
|
9
|
+
export type RedactStrategy = "token" | "mask" | "hash";
|
|
10
|
+
export type GuardrailAction = "redact" | "block" | "warn";
|
|
11
|
+
export interface FogClawConfig {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
guardrail_mode: GuardrailAction;
|
|
14
|
+
redactStrategy: RedactStrategy;
|
|
15
|
+
model: string;
|
|
16
|
+
confidence_threshold: number;
|
|
17
|
+
custom_entities: string[];
|
|
18
|
+
entityActions: Record<string, GuardrailAction>;
|
|
19
|
+
}
|
|
20
|
+
export interface ScanResult {
|
|
21
|
+
entities: Entity[];
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
export interface RedactResult {
|
|
25
|
+
redacted_text: string;
|
|
26
|
+
mapping: Record<string, string>;
|
|
27
|
+
entities: Entity[];
|
|
28
|
+
}
|
|
29
|
+
export declare const CANONICAL_TYPE_MAP: Record<string, string>;
|
|
30
|
+
export declare function canonicalType(entityType: string): string;
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC5B;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,eAAe,CAAC;IAChC,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAYrD,CAAC;AAEF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGxD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const CANONICAL_TYPE_MAP = {
|
|
2
|
+
DOB: "DATE",
|
|
3
|
+
ZIP: "ZIP_CODE",
|
|
4
|
+
PER: "PERSON",
|
|
5
|
+
ORG: "ORGANIZATION",
|
|
6
|
+
GPE: "LOCATION",
|
|
7
|
+
LOC: "LOCATION",
|
|
8
|
+
FAC: "ADDRESS",
|
|
9
|
+
PHONE_NUMBER: "PHONE",
|
|
10
|
+
SOCIAL_SECURITY_NUMBER: "SSN",
|
|
11
|
+
CREDIT_CARD_NUMBER: "CREDIT_CARD",
|
|
12
|
+
DATE_OF_BIRTH: "DATE",
|
|
13
|
+
};
|
|
14
|
+
export function canonicalType(entityType) {
|
|
15
|
+
const normalized = entityType.toUpperCase().trim();
|
|
16
|
+
return CANONICAL_TYPE_MAP[normalized] ?? normalized;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAkCA,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,SAAS;IACd,YAAY,EAAE,OAAO;IACrB,sBAAsB,EAAE,KAAK;IAC7B,kBAAkB,EAAE,aAAa;IACjC,aAAa,EAAE,MAAM;CACtB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IACnD,OAAO,kBAAkB,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;AACtD,CAAC"}
|
package/docs/DATA.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Data"
|
|
3
|
+
use_when: "Capturing data model and data-change safety rules for this repo (schemas, migrations, backfills, integrity, and operational safety)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Data Model
|
|
7
|
+
|
|
8
|
+
- Source of truth for schemas (ORM models, migrations, schema dump files) and where they live.
|
|
9
|
+
- Entity ownership boundaries (what owns IDs, who can write which tables/collections).
|
|
10
|
+
|
|
11
|
+
## Migrations
|
|
12
|
+
|
|
13
|
+
- Migration rules (forward-only vs reversible, locking/online migration expectations, index/constraint strategy).
|
|
14
|
+
- Validation steps for schema changes (commands and what to check).
|
|
15
|
+
|
|
16
|
+
## Backfills And Data Fixes
|
|
17
|
+
|
|
18
|
+
- How to run backfills safely (idempotence, batching, checkpoints).
|
|
19
|
+
- How to verify correctness and how to roll back (or compensate) if needed.
|
|
20
|
+
|
|
21
|
+
## Integrity And Consistency
|
|
22
|
+
|
|
23
|
+
- Constraints and invariants that must remain true (unique keys, foreign keys, referential rules).
|
|
24
|
+
- Concurrency expectations (transactions/isolation, retry policies) where relevant.
|
|
25
|
+
|
|
26
|
+
## Sensitive Data Notes
|
|
27
|
+
|
|
28
|
+
- Pointers to where sensitive fields live and how they must be handled (logging/redaction, retention, deletion).
|
package/docs/DESIGN.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Design"
|
|
3
|
+
use_when: "Documenting UI/UX design principles, visual direction, and interaction standards for this repo."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Design Principles
|
|
7
|
+
- Clarity over cleverness; make the primary action obvious.
|
|
8
|
+
- Consistency beats novelty; reuse patterns unless there is a strong reason not to.
|
|
9
|
+
- Accessible by default (contrast, focus, keyboard).
|
|
10
|
+
|
|
11
|
+
## Visual Direction
|
|
12
|
+
- Use design tokens (colors, spacing, typography) to keep the UI cohesive.
|
|
13
|
+
- Prefer a small, intentional palette and a consistent type scale.
|
|
14
|
+
|
|
15
|
+
## Interaction Standards
|
|
16
|
+
- Every async action has loading, success, and error states with clear messaging.
|
|
17
|
+
- Forms validate inline and preserve user input; errors explain how to recover.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Domain Docs Registry
|
|
2
|
+
|
|
3
|
+
Reference for agents: what domain docs exist, how to detect relevant content, and when to create or update them. Domain docs are deployed at bootstrap with baseline guidance. Flesh them out with real, repo-specific content on demand.
|
|
4
|
+
|
|
5
|
+
## Domain Docs
|
|
6
|
+
|
|
7
|
+
| Doc | Path | Purpose | Auto-Detect Signals | Seed Question |
|
|
8
|
+
|---|---|---|---|---|
|
|
9
|
+
| DESIGN.md | `docs/DESIGN.md` | Design principles, visual direction, interaction standards | — | What are your core design principles? |
|
|
10
|
+
| DATA.md | `docs/DATA.md` | Data model and data-change safety rules (migrations/backfills/integrity) | `db/`, migrations, ORM schema files, backfill scripts | What are your data model and migration/backfill safety rules? |
|
|
11
|
+
| FRONTEND.md | `docs/FRONTEND.md` | Frontend stack, conventions, component architecture | `package.json` (react/vue/angular/svelte), `next.config.*`, `vite.config.*`, `tsconfig.json` | What's your frontend stack and key conventions? |
|
|
12
|
+
| PRODUCT_SENSE.md | `docs/PRODUCT_SENSE.md` | Target users, key outcomes, decision heuristics | — | Who are your target users and what outcomes matter most? |
|
|
13
|
+
| RELIABILITY.md | `docs/RELIABILITY.md` | Uptime targets, failure modes, operational guardrails | Dockerfile, health check routes, CI config | What are your reliability requirements? |
|
|
14
|
+
| SECURITY.md | `docs/SECURITY.md` | Threat model, auth, data sensitivity, compliance | Auth deps, middleware files, env var references | What security concerns apply? |
|
|
15
|
+
| OBSERVABILITY.md | `docs/OBSERVABILITY.md` | Logging, metrics, traces, health checks, agent access | Logging libs (winston/pino/structlog/slog), `/metrics`, opentelemetry/jaeger config, `/healthz` | What observability tools do you use? |
|
|
16
|
+
| core-beliefs.md | `docs/design-docs/core-beliefs.md` | Non-negotiable engineering beliefs | — | What are 2-3 non-negotiable engineering beliefs? |
|
|
17
|
+
|
|
18
|
+
## When to Create or Update
|
|
19
|
+
|
|
20
|
+
- **he-plan**: Identify relevant/missing domain docs during planning, then create/populate them at end-of-`he-plan` after final plan approval and before transition
|
|
21
|
+
- **he-implement**: If implementation reveals a missing, wrong, or incomplete domain doc, create or update it in-place and note in Revision Notes
|
|
22
|
+
- **he-learn**: Post-release policy updates from lessons learned
|
|
23
|
+
- **he-doc-gardening**: Flag stale domain docs for refresh
|
|
24
|
+
|
|
25
|
+
## How to Create or Update
|
|
26
|
+
|
|
27
|
+
1. Check if the domain doc file exists (bootstrap deploys all baseline docs)
|
|
28
|
+
2. If the doc has only baseline guidance (template defaults): replace with real, repo-specific content using auto-detect signals and current context
|
|
29
|
+
3. If it has real content: append or revise — never overwrite working policies without replacing them with something better
|
|
30
|
+
4. Preserve section structure (headings stay, content fills in)
|
package/docs/FRONTEND.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Frontend"
|
|
3
|
+
use_when: "Documenting frontend stack, conventions, component architecture, performance budgets, and accessibility requirements for this repo."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Stack
|
|
7
|
+
- Define supported browsers/platforms and the minimum accessibility target.
|
|
8
|
+
- Prefer a small set of core dependencies and consistent build tooling across the app.
|
|
9
|
+
|
|
10
|
+
## Conventions
|
|
11
|
+
- Keep components small and named by what they do; avoid "utils soup" without ownership.
|
|
12
|
+
- Centralize shared UI primitives; avoid duplicating patterns across pages.
|
|
13
|
+
|
|
14
|
+
## Component Architecture
|
|
15
|
+
- Separate UI rendering from data fetching/mutations where practical.
|
|
16
|
+
- Prefer explicit data flow and local state; introduce global state only with a clear boundary.
|
|
17
|
+
|
|
18
|
+
## Performance
|
|
19
|
+
- Avoid unnecessary client work: minimize re-renders, split code on route/feature boundaries, and lazy-load heavy modules.
|
|
20
|
+
- Measure before optimizing; keep a short list of performance budgets that matter to users.
|
|
21
|
+
|
|
22
|
+
## Accessibility
|
|
23
|
+
- Keyboard navigation works for all interactive controls; focus states are visible.
|
|
24
|
+
- Use semantic HTML first; ARIA is for filling gaps, not replacing semantics.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Observability"
|
|
3
|
+
use_when: "Documenting logging, metrics, tracing, and health check conventions for this repo, including how agents can access signals to self-verify behavior."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Logging Strategy
|
|
7
|
+
- Prefer structured logs with consistent fields (service, env, request_id/trace_id, user_id when safe).
|
|
8
|
+
- Never log secrets; be deliberate about PII.
|
|
9
|
+
- Log at boundaries and on errors; avoid noisy per-loop logging in hot paths.
|
|
10
|
+
|
|
11
|
+
## Metrics
|
|
12
|
+
- Track the golden signals: latency, traffic, errors, saturation.
|
|
13
|
+
- Prefer histograms for latency; keep label cardinality low.
|
|
14
|
+
|
|
15
|
+
## Traces
|
|
16
|
+
- Propagate trace context across service boundaries.
|
|
17
|
+
- Trace the critical paths (requests, background jobs) with stable span names.
|
|
18
|
+
|
|
19
|
+
## Health Checks
|
|
20
|
+
- Health checks are fast and deterministic; readiness reflects dependency availability when needed.
|
|
21
|
+
- Document expected status codes and what "unhealthy" means operationally.
|
|
22
|
+
|
|
23
|
+
## Agent Access
|
|
24
|
+
- Provide at least one concrete way to query each signal (logs, metrics, traces) without tribal knowledge.
|
|
25
|
+
- Include 1-2 copy-pastable examples per signal once the stack is known (commands, URLs, or queries).
|