@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.
- package/CHANGELOG.md +11 -0
- package/dist/backlog-tools.d.ts +57 -0
- package/dist/backlog-tools.d.ts.map +1 -0
- package/dist/backlog-tools.js +173 -0
- package/dist/backlog-tools.js.map +1 -0
- package/dist/backlog.d.ts +82 -0
- package/dist/backlog.d.ts.map +1 -0
- package/dist/backlog.js +169 -0
- package/dist/backlog.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +87 -2
- package/dist/index.js.map +1 -1
- package/dist/message-sending-handler.d.ts +2 -1
- package/dist/message-sending-handler.d.ts.map +1 -1
- package/dist/message-sending-handler.js +5 -1
- package/dist/message-sending-handler.js.map +1 -1
- package/dist/tool-result-handler.d.ts +2 -1
- package/dist/tool-result-handler.d.ts.map +1 -1
- package/dist/tool-result-handler.js +5 -1
- package/dist/tool-result-handler.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/openclaw.plugin.json +11 -1
- package/package.json +7 -1
- package/.github/workflows/harness-docs.yml +0 -30
- package/AGENTS.md +0 -28
- package/docs/DATA.md +0 -28
- package/docs/DESIGN.md +0 -17
- package/docs/DOMAIN_DOCS.md +0 -30
- package/docs/FRONTEND.md +0 -24
- package/docs/OBSERVABILITY.md +0 -32
- package/docs/PLANS.md +0 -171
- package/docs/PRODUCT_SENSE.md +0 -20
- package/docs/RELIABILITY.md +0 -60
- package/docs/SECURITY.md +0 -52
- package/docs/design-docs/core-beliefs.md +0 -17
- package/docs/design-docs/index.md +0 -8
- package/docs/generated/README.md +0 -36
- package/docs/generated/memory.md +0 -1
- package/docs/plans/2026-02-16-fogclaw-design.md +0 -172
- package/docs/plans/2026-02-16-fogclaw-implementation.md +0 -1606
- package/docs/plans/README.md +0 -15
- package/docs/plans/active/2026-02-16-feat-openclaw-official-submission-plan.md +0 -386
- package/docs/plans/active/2026-02-17-feat-release-fogclaw-via-datafog-package-plan.md +0 -328
- package/docs/plans/active/2026-02-17-feat-submit-fogclaw-to-openclaw-plan.md +0 -244
- package/docs/plans/active/2026-02-17-feat-tool-result-pii-scanning-plan.md +0 -293
- package/docs/plans/tech-debt-tracker.md +0 -42
- package/docs/plugins/fogclaw.md +0 -101
- package/docs/runbooks/address-review-findings.md +0 -30
- package/docs/runbooks/ci-failures.md +0 -46
- package/docs/runbooks/code-review.md +0 -34
- package/docs/runbooks/merge-change.md +0 -28
- package/docs/runbooks/pull-request.md +0 -45
- package/docs/runbooks/record-evidence.md +0 -43
- package/docs/runbooks/reproduce-bug.md +0 -42
- package/docs/runbooks/respond-to-feedback.md +0 -42
- package/docs/runbooks/review-findings.md +0 -31
- package/docs/runbooks/submit-openclaw-plugin.md +0 -68
- package/docs/runbooks/update-agents-md.md +0 -59
- package/docs/runbooks/update-domain-docs.md +0 -42
- package/docs/runbooks/validate-current-state.md +0 -41
- package/docs/runbooks/verify-release.md +0 -69
- package/docs/specs/2026-02-16-feat-openclaw-official-submission-spec.md +0 -115
- package/docs/specs/2026-02-17-feat-outbound-message-pii-scanning-spec.md +0 -93
- package/docs/specs/2026-02-17-feat-submit-fogclaw-to-openclaw.md +0 -125
- package/docs/specs/2026-02-17-feat-tool-result-pii-scanning-spec.md +0 -122
- package/docs/specs/README.md +0 -5
- package/docs/specs/index.md +0 -8
- package/docs/spikes/README.md +0 -8
- package/fogclaw.config.example.json +0 -33
- package/scripts/ci/he-docs-config.json +0 -123
- package/scripts/ci/he-docs-drift.sh +0 -112
- package/scripts/ci/he-docs-lint.sh +0 -234
- package/scripts/ci/he-plans-lint.sh +0 -354
- package/scripts/ci/he-runbooks-lint.sh +0 -445
- package/scripts/ci/he-specs-lint.sh +0 -258
- package/scripts/ci/he-spikes-lint.sh +0 -249
- package/scripts/runbooks/select-runbooks.sh +0 -154
- package/src/config.ts +0 -183
- package/src/engines/gliner.ts +0 -240
- package/src/engines/regex.ts +0 -71
- package/src/extract.ts +0 -98
- package/src/index.ts +0 -381
- package/src/message-sending-handler.ts +0 -87
- package/src/redactor.ts +0 -51
- package/src/scanner.ts +0 -196
- package/src/tool-result-handler.ts +0 -133
- package/src/types.ts +0 -75
- package/tests/config.test.ts +0 -78
- package/tests/extract.test.ts +0 -185
- package/tests/gliner.test.ts +0 -289
- package/tests/message-sending-handler.test.ts +0 -244
- package/tests/plugin-smoke.test.ts +0 -250
- package/tests/redactor.test.ts +0 -320
- package/tests/regex.test.ts +0 -345
- package/tests/scanner.test.ts +0 -348
- package/tests/tool-result-handler.test.ts +0 -329
- package/tsconfig.json +0 -20
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# ---------------------------------------------------------------------------
|
|
5
|
-
# he-spikes-lint.sh — Lint spike documents under docs/spikes
|
|
6
|
-
# ---------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
10
|
-
|
|
11
|
-
DEFAULT_CONFIG_PATH="scripts/ci/he-docs-config.json"
|
|
12
|
-
|
|
13
|
-
# Default required headings (one per line for easy iteration)
|
|
14
|
-
DEFAULT_REQUIRED_HEADINGS=(
|
|
15
|
-
"## Context"
|
|
16
|
-
"## Validation Goal"
|
|
17
|
-
"## Approach"
|
|
18
|
-
"## Findings"
|
|
19
|
-
"## Decisions"
|
|
20
|
-
"## Recommendation"
|
|
21
|
-
"## Impact on Upstream Docs"
|
|
22
|
-
"## Spike Code"
|
|
23
|
-
"## Remaining Unknowns"
|
|
24
|
-
"## Time Spent"
|
|
25
|
-
"## Revision Notes"
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# Counters
|
|
29
|
-
errors=0
|
|
30
|
-
warnings=0
|
|
31
|
-
|
|
32
|
-
# ---------------------------------------------------------------------------
|
|
33
|
-
# Helpers
|
|
34
|
-
# ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
gh_annotate() {
|
|
37
|
-
local level="$1" file="$2" title="$3" msg="$4"
|
|
38
|
-
if [[ -n "$file" ]]; then
|
|
39
|
-
echo "::${level} file=${file},title=${title}::${msg}"
|
|
40
|
-
else
|
|
41
|
-
echo "::${level} title=${title}::${msg}"
|
|
42
|
-
fi
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
emit() {
|
|
46
|
-
local level="$1" file="$2" title="$3" msg="$4"
|
|
47
|
-
gh_annotate "$level" "$file" "$title" "$msg"
|
|
48
|
-
local upper
|
|
49
|
-
upper="$(echo "$level" | tr '[:lower:]' '[:upper:]')"
|
|
50
|
-
echo "${upper}: ${msg}" >&2
|
|
51
|
-
if [[ "$level" == "error" ]]; then
|
|
52
|
-
(( errors++ )) || true
|
|
53
|
-
else
|
|
54
|
-
(( warnings++ )) || true
|
|
55
|
-
fi
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
# Extract YAML frontmatter (text between first two --- lines, exclusive).
|
|
59
|
-
# Prints frontmatter to stdout. Returns 1 if no valid frontmatter found.
|
|
60
|
-
extract_frontmatter() {
|
|
61
|
-
local file="$1"
|
|
62
|
-
local first_line
|
|
63
|
-
first_line="$(head -n1 "$file" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
64
|
-
if [[ "$first_line" != "---" ]]; then
|
|
65
|
-
return 1
|
|
66
|
-
fi
|
|
67
|
-
# Find the closing --- (skip line 1, start from line 2)
|
|
68
|
-
local line_num=0
|
|
69
|
-
local found=0
|
|
70
|
-
while IFS= read -r line; do
|
|
71
|
-
line_num=$((line_num + 1))
|
|
72
|
-
if [[ $line_num -eq 1 ]]; then
|
|
73
|
-
continue
|
|
74
|
-
fi
|
|
75
|
-
local trimmed
|
|
76
|
-
trimmed="$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
77
|
-
if [[ "$trimmed" == "---" ]]; then
|
|
78
|
-
found=1
|
|
79
|
-
break
|
|
80
|
-
fi
|
|
81
|
-
echo "$line"
|
|
82
|
-
done < "$file"
|
|
83
|
-
if [[ $found -eq 0 ]]; then
|
|
84
|
-
return 1
|
|
85
|
-
fi
|
|
86
|
-
return 0
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# Extract keys from frontmatter text (stdin).
|
|
90
|
-
# Outputs one key per line.
|
|
91
|
-
frontmatter_keys() {
|
|
92
|
-
while IFS= read -r raw; do
|
|
93
|
-
local line
|
|
94
|
-
line="$(echo "$raw" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
95
|
-
# skip blank lines and comments
|
|
96
|
-
[[ -z "$line" ]] && continue
|
|
97
|
-
[[ "$line" == \#* ]] && continue
|
|
98
|
-
# must contain a colon
|
|
99
|
-
[[ "$line" != *:* ]] && continue
|
|
100
|
-
# extract key (everything before first colon), trimmed
|
|
101
|
-
local key
|
|
102
|
-
key="$(echo "$line" | cut -d: -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
103
|
-
echo "$key"
|
|
104
|
-
done
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
# Check if a file contains an exact full line matching the needle.
|
|
108
|
-
has_exact_line() {
|
|
109
|
-
local file="$1" needle="$2"
|
|
110
|
-
grep -qFx "$needle" "$file"
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
# ---------------------------------------------------------------------------
|
|
114
|
-
# Config loading
|
|
115
|
-
# ---------------------------------------------------------------------------
|
|
116
|
-
|
|
117
|
-
load_config() {
|
|
118
|
-
local config_rel="${HARNESS_DOCS_CONFIG:-$DEFAULT_CONFIG_PATH}"
|
|
119
|
-
local config_path="$REPO_ROOT/$config_rel"
|
|
120
|
-
if [[ ! -f "$config_path" ]]; then
|
|
121
|
-
echo "Error: he-spikes-lint missing/invalid config: Missing config '${config_rel}'. Fix: create it (bootstrap should do this) or set HARNESS_DOCS_CONFIG." >&2
|
|
122
|
-
exit 2
|
|
123
|
-
fi
|
|
124
|
-
# Validate it is a JSON object
|
|
125
|
-
if ! jq -e 'type == "object"' "$config_path" >/dev/null 2>&1; then
|
|
126
|
-
echo "Error: he-spikes-lint missing/invalid config: Config must be a JSON object." >&2
|
|
127
|
-
exit 2
|
|
128
|
-
fi
|
|
129
|
-
CONFIG_PATH="$config_path"
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
# Read a JSON array from config as newline-delimited strings.
|
|
133
|
-
config_string_array() {
|
|
134
|
-
local key="$1"
|
|
135
|
-
jq -r "(.${key} // []) | if type == \"array\" then .[] else empty end" "$CONFIG_PATH" 2>/dev/null | while IFS= read -r v; do
|
|
136
|
-
# only emit strings
|
|
137
|
-
echo "$v"
|
|
138
|
-
done
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
# ---------------------------------------------------------------------------
|
|
142
|
-
# Per-spike checks
|
|
143
|
-
# ---------------------------------------------------------------------------
|
|
144
|
-
|
|
145
|
-
check_placeholders() {
|
|
146
|
-
local rel="$1" file="$2" fail_ph="$3"
|
|
147
|
-
shift 3
|
|
148
|
-
local patterns=("$@")
|
|
149
|
-
for p in "${patterns[@]}"; do
|
|
150
|
-
[[ -z "$p" ]] && continue
|
|
151
|
-
if grep -qF "$p" "$file"; then
|
|
152
|
-
local msg="Spike '${rel}' contains placeholder token '${p}'."
|
|
153
|
-
if [[ "$fail_ph" == "1" ]]; then
|
|
154
|
-
emit "error" "$rel" "Placeholder token" "$msg"
|
|
155
|
-
else
|
|
156
|
-
emit "warning" "$rel" "Placeholder token" "${msg} (Set HARNESS_FAIL_ON_ARTIFACT_PLACEHOLDERS=1 to enforce.)"
|
|
157
|
-
fi
|
|
158
|
-
break
|
|
159
|
-
fi
|
|
160
|
-
done
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
check_spike() {
|
|
164
|
-
local file="$1"
|
|
165
|
-
local rel="${file#"$REPO_ROOT"/}"
|
|
166
|
-
|
|
167
|
-
# --- frontmatter ---
|
|
168
|
-
local fm
|
|
169
|
-
if ! fm="$(extract_frontmatter "$file")"; then
|
|
170
|
-
emit "error" "$rel" "Missing YAML frontmatter" \
|
|
171
|
-
"Spike '${rel}' must start with YAML frontmatter delimited by '---' lines."
|
|
172
|
-
return
|
|
173
|
-
fi
|
|
174
|
-
|
|
175
|
-
# Check required frontmatter keys
|
|
176
|
-
local fm_keys
|
|
177
|
-
fm_keys="$(echo "$fm" | frontmatter_keys)"
|
|
178
|
-
|
|
179
|
-
local required_keys
|
|
180
|
-
required_keys="$(config_string_array "required_spike_frontmatter_keys")"
|
|
181
|
-
|
|
182
|
-
if [[ -n "$required_keys" ]]; then
|
|
183
|
-
while IFS= read -r k; do
|
|
184
|
-
[[ -z "$k" ]] && continue
|
|
185
|
-
if ! echo "$fm_keys" | grep -qFx "$k"; then
|
|
186
|
-
emit "error" "$rel" "Missing frontmatter key" \
|
|
187
|
-
"Spike '${rel}' missing YAML frontmatter key '${k}:'."
|
|
188
|
-
fi
|
|
189
|
-
done <<< "$required_keys"
|
|
190
|
-
fi
|
|
191
|
-
|
|
192
|
-
# --- required headings ---
|
|
193
|
-
for h in "${DEFAULT_REQUIRED_HEADINGS[@]}"; do
|
|
194
|
-
if ! has_exact_line "$file" "$h"; then
|
|
195
|
-
emit "error" "$rel" "Missing heading" \
|
|
196
|
-
"Spike '${rel}' missing required heading line '${h}'."
|
|
197
|
-
fi
|
|
198
|
-
done
|
|
199
|
-
|
|
200
|
-
# --- placeholder tokens ---
|
|
201
|
-
local placeholder_patterns=()
|
|
202
|
-
while IFS= read -r p; do
|
|
203
|
-
[[ -z "$p" ]] && continue
|
|
204
|
-
placeholder_patterns+=("$p")
|
|
205
|
-
done < <(config_string_array "artifact_placeholder_patterns")
|
|
206
|
-
|
|
207
|
-
local fail_ph="${HARNESS_FAIL_ON_ARTIFACT_PLACEHOLDERS:-0}"
|
|
208
|
-
if [[ ${#placeholder_patterns[@]} -gt 0 ]]; then
|
|
209
|
-
check_placeholders "$rel" "$file" "$fail_ph" "${placeholder_patterns[@]}"
|
|
210
|
-
fi
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
# ---------------------------------------------------------------------------
|
|
214
|
-
# Main
|
|
215
|
-
# ---------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
load_config
|
|
218
|
-
|
|
219
|
-
echo "he-spikes-lint: starting"
|
|
220
|
-
echo "Repro: bash scripts/ci/he-spikes-lint.sh"
|
|
221
|
-
|
|
222
|
-
spikes_dir="$REPO_ROOT/docs/spikes"
|
|
223
|
-
if [[ ! -d "$spikes_dir" ]]; then
|
|
224
|
-
echo "he-spikes-lint: OK (docs/spikes not present)"
|
|
225
|
-
exit 0
|
|
226
|
-
fi
|
|
227
|
-
|
|
228
|
-
# Collect spike files sorted
|
|
229
|
-
spike_files=()
|
|
230
|
-
while IFS= read -r -d '' f; do
|
|
231
|
-
spike_files+=("$f")
|
|
232
|
-
done < <(find "$spikes_dir" -maxdepth 1 -name '*-spike.md' -print0 | sort -z)
|
|
233
|
-
|
|
234
|
-
if [[ ${#spike_files[@]} -eq 0 ]]; then
|
|
235
|
-
echo "he-spikes-lint: OK (no spike files)"
|
|
236
|
-
exit 0
|
|
237
|
-
fi
|
|
238
|
-
|
|
239
|
-
for f in "${spike_files[@]}"; do
|
|
240
|
-
check_spike "$f"
|
|
241
|
-
done
|
|
242
|
-
|
|
243
|
-
if [[ $errors -gt 0 ]]; then
|
|
244
|
-
echo "he-spikes-lint: FAIL (${errors} error(s), ${warnings} warning(s))" >&2
|
|
245
|
-
exit 1
|
|
246
|
-
fi
|
|
247
|
-
|
|
248
|
-
echo "he-spikes-lint: OK (${warnings} warning(s))"
|
|
249
|
-
exit 0
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# Select runbooks whose called_from frontmatter matches a skill or step name.
|
|
5
|
-
# Prints matching runbook paths (relative to repo root) to stdout.
|
|
6
|
-
|
|
7
|
-
# --- Repo root: two parents up from this script's directory ---
|
|
8
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
10
|
-
|
|
11
|
-
# --- CLI argument parsing ---
|
|
12
|
-
SKILL=""
|
|
13
|
-
STEP=""
|
|
14
|
-
|
|
15
|
-
while [[ $# -gt 0 ]]; do
|
|
16
|
-
case "$1" in
|
|
17
|
-
--skill)
|
|
18
|
-
SKILL="$2"
|
|
19
|
-
shift 2
|
|
20
|
-
;;
|
|
21
|
-
--step)
|
|
22
|
-
STEP="$2"
|
|
23
|
-
shift 2
|
|
24
|
-
;;
|
|
25
|
-
*)
|
|
26
|
-
echo "Usage: $0 --skill <name> [--step <name>]" >&2
|
|
27
|
-
exit 1
|
|
28
|
-
;;
|
|
29
|
-
esac
|
|
30
|
-
done
|
|
31
|
-
|
|
32
|
-
if [[ -z "$SKILL" ]]; then
|
|
33
|
-
echo "Error: --skill is required" >&2
|
|
34
|
-
exit 1
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
# --- Main logic ---
|
|
38
|
-
RUNBOOKS_DIR="$REPO_ROOT/docs/runbooks"
|
|
39
|
-
|
|
40
|
-
if [[ ! -d "$RUNBOOKS_DIR" ]]; then
|
|
41
|
-
exit 0
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
# Extract the frontmatter block (between first --- and next ---).
|
|
45
|
-
# Parse called_from entries. Print the file path if skill or step matches.
|
|
46
|
-
process_file() {
|
|
47
|
-
local file="$1"
|
|
48
|
-
local in_frontmatter=0
|
|
49
|
-
local in_called_from=0
|
|
50
|
-
local first_line=1
|
|
51
|
-
local called_from_items=()
|
|
52
|
-
|
|
53
|
-
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
54
|
-
local trimmed
|
|
55
|
-
trimmed="$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
56
|
-
|
|
57
|
-
# First non-empty consideration: frontmatter must start at line 1 with ---
|
|
58
|
-
if [[ "$first_line" -eq 1 ]]; then
|
|
59
|
-
first_line=0
|
|
60
|
-
if [[ "$trimmed" == "---" ]]; then
|
|
61
|
-
in_frontmatter=1
|
|
62
|
-
continue
|
|
63
|
-
else
|
|
64
|
-
# No frontmatter
|
|
65
|
-
return
|
|
66
|
-
fi
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
# Inside frontmatter
|
|
70
|
-
if [[ "$in_frontmatter" -eq 1 ]]; then
|
|
71
|
-
# Closing delimiter
|
|
72
|
-
if [[ "$trimmed" == "---" ]]; then
|
|
73
|
-
break
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
# Skip empty lines and comments
|
|
77
|
-
if [[ -z "$trimmed" || "$trimmed" == \#* ]]; then
|
|
78
|
-
# Empty lines inside a YAML list block: keep scanning
|
|
79
|
-
if [[ "$in_called_from" -eq 1 && -z "$trimmed" ]]; then
|
|
80
|
-
continue
|
|
81
|
-
fi
|
|
82
|
-
continue
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
# If we're collecting YAML list items for called_from
|
|
86
|
-
if [[ "$in_called_from" -eq 1 ]]; then
|
|
87
|
-
# Check if this is a list item (starts with -)
|
|
88
|
-
if [[ "$trimmed" == -* ]]; then
|
|
89
|
-
local item
|
|
90
|
-
item="$(echo "$trimmed" | sed "s/^-[[:space:]]*//;s/^[\"']//;s/[\"']$//")"
|
|
91
|
-
if [[ -n "$item" ]]; then
|
|
92
|
-
called_from_items+=("$item")
|
|
93
|
-
fi
|
|
94
|
-
continue
|
|
95
|
-
else
|
|
96
|
-
# Not a list item; if it contains a colon it's a new key — stop collecting
|
|
97
|
-
if echo "$trimmed" | grep -q ':'; then
|
|
98
|
-
in_called_from=0
|
|
99
|
-
# Fall through to process this line as a new key
|
|
100
|
-
else
|
|
101
|
-
continue
|
|
102
|
-
fi
|
|
103
|
-
fi
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
# Check for key: value lines
|
|
107
|
-
if echo "$trimmed" | grep -q ':'; then
|
|
108
|
-
local key val
|
|
109
|
-
key="$(echo "$trimmed" | sed 's/^\([^:]*\):.*/\1/' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
110
|
-
val="$(echo "$trimmed" | sed 's/^[^:]*://' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
111
|
-
|
|
112
|
-
if [[ "$key" == "called_from" ]]; then
|
|
113
|
-
# Inline list: called_from: [a, b]
|
|
114
|
-
if [[ "$val" == \[* ]]; then
|
|
115
|
-
local inner
|
|
116
|
-
inner="$(echo "$val" | sed 's/^\[//;s/\]$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
117
|
-
if [[ -n "$inner" ]]; then
|
|
118
|
-
IFS=',' read -ra parts <<< "$inner"
|
|
119
|
-
for part in "${parts[@]}"; do
|
|
120
|
-
local cleaned
|
|
121
|
-
cleaned="$(echo "$part" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
122
|
-
if [[ -n "$cleaned" ]]; then
|
|
123
|
-
called_from_items+=("$cleaned")
|
|
124
|
-
fi
|
|
125
|
-
done
|
|
126
|
-
fi
|
|
127
|
-
else
|
|
128
|
-
# YAML list form — start collecting on subsequent lines
|
|
129
|
-
in_called_from=1
|
|
130
|
-
fi
|
|
131
|
-
fi
|
|
132
|
-
fi
|
|
133
|
-
fi
|
|
134
|
-
done < "$file"
|
|
135
|
-
|
|
136
|
-
# Check for matches
|
|
137
|
-
for item in "${called_from_items[@]+"${called_from_items[@]}"}"; do
|
|
138
|
-
if [[ "$item" == "$SKILL" ]]; then
|
|
139
|
-
echo "${file#"$REPO_ROOT"/}"
|
|
140
|
-
return
|
|
141
|
-
fi
|
|
142
|
-
if [[ -n "$STEP" && "$item" == "$STEP" ]]; then
|
|
143
|
-
echo "${file#"$REPO_ROOT"/}"
|
|
144
|
-
return
|
|
145
|
-
fi
|
|
146
|
-
done
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
# Find all .md files, sorted for deterministic output
|
|
150
|
-
while IFS= read -r mdfile; do
|
|
151
|
-
process_file "$mdfile"
|
|
152
|
-
done < <(find "$RUNBOOKS_DIR" -name '*.md' -type f | sort)
|
|
153
|
-
|
|
154
|
-
exit 0
|
package/src/config.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
canonicalType,
|
|
3
|
-
type EntityAllowlist,
|
|
4
|
-
type FogClawConfig,
|
|
5
|
-
type GuardrailAction,
|
|
6
|
-
type RedactStrategy,
|
|
7
|
-
} from "./types.js";
|
|
8
|
-
|
|
9
|
-
const VALID_GUARDRAIL_MODES: GuardrailAction[] = ["redact", "block", "warn"];
|
|
10
|
-
const VALID_REDACT_STRATEGIES: RedactStrategy[] = ["token", "mask", "hash"];
|
|
11
|
-
|
|
12
|
-
function ensureStringList(value: unknown, path: string): string[] {
|
|
13
|
-
if (!Array.isArray(value)) {
|
|
14
|
-
throw new Error(`${path} must be an array of strings`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const entries = value.filter((entry): entry is string => {
|
|
18
|
-
if (typeof entry !== "string") {
|
|
19
|
-
throw new Error(`${path} must contain only strings`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return true;
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function ensureEntityAllowlist(value: unknown): EntityAllowlist {
|
|
29
|
-
if (value == null) {
|
|
30
|
-
return { values: [], patterns: [], entities: {} };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (typeof value !== "object" || Array.isArray(value)) {
|
|
34
|
-
throw new Error("allowlist must be an object");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const raw = value as Record<string, unknown>;
|
|
38
|
-
const values = ensureStringList(raw.values ?? [], "allowlist.values");
|
|
39
|
-
const patterns = ensureStringList(raw.patterns ?? [], "allowlist.patterns");
|
|
40
|
-
|
|
41
|
-
for (const pattern of patterns) {
|
|
42
|
-
try {
|
|
43
|
-
new RegExp(pattern);
|
|
44
|
-
} catch {
|
|
45
|
-
throw new Error(`allowlist.patterns contains invalid regex pattern: "${pattern}"`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const entitiesValue = raw.entities ?? {};
|
|
50
|
-
if (
|
|
51
|
-
typeof entitiesValue !== "object" ||
|
|
52
|
-
Array.isArray(entitiesValue) ||
|
|
53
|
-
entitiesValue === null
|
|
54
|
-
) {
|
|
55
|
-
throw new Error("allowlist.entities must be an object mapping entity labels to string arrays");
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const entities: Record<string, string[]> = {};
|
|
59
|
-
for (const [entityType, entryValue] of Object.entries(entitiesValue)) {
|
|
60
|
-
const normalizedType = canonicalType(entityType);
|
|
61
|
-
entities[normalizedType] = ensureStringList(entryValue, `allowlist.entities.${entityType}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
values: [...new Set(values)],
|
|
66
|
-
patterns: [...new Set(patterns)],
|
|
67
|
-
entities,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function ensureEntityConfidenceThresholds(
|
|
72
|
-
value: unknown,
|
|
73
|
-
): Record<string, number> {
|
|
74
|
-
if (!value) {
|
|
75
|
-
return {};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (typeof value !== "object" || Array.isArray(value) || value === null) {
|
|
79
|
-
throw new Error("entityConfidenceThresholds must be an object");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const raw = value as Record<string, unknown>;
|
|
83
|
-
const normalized: Record<string, number> = {};
|
|
84
|
-
|
|
85
|
-
for (const [entityType, rawThreshold] of Object.entries(raw)) {
|
|
86
|
-
if (typeof rawThreshold !== "number" || Number.isNaN(rawThreshold)) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
`entityConfidenceThresholds["${entityType}"] must be a number between 0 and 1, got ${String(
|
|
89
|
-
rawThreshold,
|
|
90
|
-
)}`,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (rawThreshold < 0 || rawThreshold > 1) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
`entityConfidenceThresholds["${entityType}"] must be between 0 and 1, got ${rawThreshold}`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const canonicalTypeKey = canonicalType(entityType);
|
|
101
|
-
normalized[canonicalTypeKey] = rawThreshold;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return normalized;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export const DEFAULT_CONFIG: FogClawConfig = {
|
|
108
|
-
enabled: true,
|
|
109
|
-
guardrail_mode: "redact",
|
|
110
|
-
redactStrategy: "token",
|
|
111
|
-
model: "onnx-community/gliner_large-v2.1",
|
|
112
|
-
confidence_threshold: 0.5,
|
|
113
|
-
custom_entities: [],
|
|
114
|
-
entityActions: {},
|
|
115
|
-
entityConfidenceThresholds: {},
|
|
116
|
-
allowlist: {
|
|
117
|
-
values: [],
|
|
118
|
-
patterns: [],
|
|
119
|
-
entities: {},
|
|
120
|
-
},
|
|
121
|
-
auditEnabled: true,
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export function loadConfig(overrides: Partial<FogClawConfig>): FogClawConfig {
|
|
125
|
-
const config: FogClawConfig = {
|
|
126
|
-
...DEFAULT_CONFIG,
|
|
127
|
-
...overrides,
|
|
128
|
-
entityActions: {
|
|
129
|
-
...DEFAULT_CONFIG.entityActions,
|
|
130
|
-
...(overrides.entityActions ?? {}),
|
|
131
|
-
},
|
|
132
|
-
entityConfidenceThresholds: {
|
|
133
|
-
...DEFAULT_CONFIG.entityConfidenceThresholds,
|
|
134
|
-
...(overrides.entityConfidenceThresholds ?? {}),
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
config.allowlist = ensureEntityAllowlist(overrides.allowlist ?? DEFAULT_CONFIG.allowlist);
|
|
139
|
-
config.entityConfidenceThresholds = ensureEntityConfidenceThresholds(
|
|
140
|
-
config.entityConfidenceThresholds,
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
if (typeof config.enabled !== "boolean") {
|
|
144
|
-
throw new Error(`enabled must be true or false`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (!VALID_GUARDRAIL_MODES.includes(config.guardrail_mode)) {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`Invalid guardrail_mode "${config.guardrail_mode}". Must be one of: ${VALID_GUARDRAIL_MODES.join(", ")}`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (!VALID_REDACT_STRATEGIES.includes(config.redactStrategy)) {
|
|
154
|
-
throw new Error(
|
|
155
|
-
`Invalid redactStrategy "${config.redactStrategy}". Must be one of: ${VALID_REDACT_STRATEGIES.join(", ")}`,
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (config.confidence_threshold < 0 || config.confidence_threshold > 1) {
|
|
160
|
-
throw new Error(
|
|
161
|
-
`confidence_threshold must be between 0 and 1, got ${config.confidence_threshold}`,
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (typeof config.auditEnabled !== "boolean") {
|
|
166
|
-
throw new Error(`auditEnabled must be true or false`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const normalizedActions: Record<string, GuardrailAction> = {};
|
|
170
|
-
for (const [entityType, action] of Object.entries(config.entityActions)) {
|
|
171
|
-
if (!VALID_GUARDRAIL_MODES.includes(action)) {
|
|
172
|
-
throw new Error(
|
|
173
|
-
`Invalid action "${action}" for entity type "${entityType}". Must be one of: ${VALID_GUARDRAIL_MODES.join(", ")}`,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const normalizedType = canonicalType(entityType);
|
|
178
|
-
normalizedActions[normalizedType] = action;
|
|
179
|
-
}
|
|
180
|
-
config.entityActions = normalizedActions;
|
|
181
|
-
|
|
182
|
-
return config;
|
|
183
|
-
}
|