kustos
v1.0.0
Published
Compliance linter for call transcripts in VTT and SRT format
Maintainers
Readme
Kustos
Compliance linter for call transcripts in VTT and SRT format. Define rules in YAML, point kustos at a directory, get a structured HTML report with every violation flagged by timestamp, severity, and rule.
Built for fintech and regtech teams that need a fast, free, offline-first compliance pre-check layer before AI-assisted review.
Contributing
pnpm install
pnpm prepare # sets up Husky hooksBefore pushing, the pre-commit hook runs lint-staged automatically — it formats and lints every staged .ts file. CI runs the full suite across Node 18, 20, and 22 on every push and PR.
Release flow
- Update
CHANGELOG.mdwith the new version under a## [x.y.z]header - Push to
main - The release workflow detects the version change, syncs
package.json, builds, publishes to npm, and creates a GitHub release automatically
Requires NPM_TOKEN set as a repository secret.
Install
npm install
npm run build
npm linkUsage
kustos ./callskustos looks for kustos.yaml inside the scanned directory by default. If your rules file lives elsewhere, pass it explicitly:
kustos ./calls --rules ./rules/finra.yamlTo write the report to a specific path:
kustos ./calls --out ./reports/2024-Q1.htmlThe short alias kt is also available after linking:
kt ./calls| Argument | Required | Default | Description |
| --------- | -------- | ---------------------- | ------------------------------------------- |
| <dir> | yes | — | Directory containing .vtt or .srt files |
| --rules | no | <dir>/kustos.yaml | Path to YAML rules file |
| --out | no | ./report/report.html | Output path for HTML report |
Rules Format
Place a kustos.yaml file inside your transcripts directory, or point to one with --rules.
- id: RULE-001
description: Advisor must not guarantee returns
type: keyword
pattern: "guaranteed returns"
severity: high
- id: RULE-002
description: Do not promise specific percentage gains
type: regex
pattern: "\\d+%\\s+(return|profit|gain)"
severity: high
- id: RULE-003
description: Risk must not be denied or minimized
type: keyword
pattern: "no risk"
severity: medium
- id: RULE-004
description: Urgency pressure tactics are prohibited
type: regex
pattern: "(limited time|act now|don't wait)"
severity: low| Field | Type | Values |
| ------------- | ------ | ---------------------------------------------- |
| id | string | Unique identifier — duplicates are rejected |
| description | string | Human-readable description shown in the report |
| type | string | keyword or regex |
| pattern | string | Literal string or JavaScript regex pattern |
| severity | string | low, medium, or high |
keyword does a case-insensitive literal match. regex evaluates the pattern as a JavaScript regular expression with gi flags. Invalid regex patterns are caught at startup before any files are scanned.
Exit Codes
| Code | Meaning |
| ---- | ------------------------------------------ |
| 0 | No high-severity violations found |
| 1 | One or more high-severity violations found |
The exit code is the integration hook. Any tooling that reads process exit codes — CI pipelines, shell scripts, pre-commit hooks — reacts to it automatically without parsing the HTML report.
CI/CD Integration
GitHub Actions
Drop this into any workflow that handles call recordings or transcript uploads. If a high-severity violation is found the step exits with 1, the pipeline stops, and the HTML report is still uploaded as an artifact so the team can see exactly what triggered it.
name: Compliance Check
on:
push:
paths:
- "recordings/**"
jobs:
compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install kustos
run: npm install && npm run build && npm link
- name: Scan transcripts
run: kustos ./recordings --out ./report.html
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: compliance-report
path: report.htmlif: always() on the upload step is intentional — when the scan fails the job stops, but you still want the report artifact. Without it, a failing scan would leave the team with no visibility into which calls triggered violations.
Pre-commit Hook
Catch violations before recordings hit the remote. Wire kustos into husky:
npx husky init
echo "kustos ./recordings" > .husky/pre-commitCommits that introduce high-severity violations are blocked locally before they ever reach CI.
Shell Script Composition
Because kustos exits with a standard code, it composes cleanly with anything:
kustos ./calls && echo "Passed" || echo "Violations found — check report.html"Or with a Slack webhook:
kustos ./calls \
&& curl -s -X POST $SLACK_WEBHOOK -d '{"text":"Compliance check passed"}' \
|| curl -s -X POST $SLACK_WEBHOOK -d '{"text":"Violations found — review report"}'Error Handling
kustos validates aggressively at startup and before processing any files:
- Missing or unreadable directory — exits immediately with a clear message
- Missing rules file — exits with a message pointing to the expected location
- Invalid YAML in rules file — exits and prints the parse error
- Invalid rule shape (wrong type, missing fields, bad regex, duplicate IDs) — exits and lists every issue
- Unreadable transcript file — skipped with a warning, scan continues
- Empty transcript file — skipped with a warning, scan continues
- Unparseable transcript — skipped with a warning, scan continues
- No valid files found after skipping — exits cleanly with code
0
Development
npm run dev -- ./examples
npm test
npm run lintProject Structure
src/
index.ts CLI entry, argument parsing, orchestration
parser.ts VTT and SRT parsing
engine.ts Rule evaluation
reporter.ts HTML report generation
validator.ts Rules file validation
types.ts Shared TypeScript interfaces
tests/
engine.test.ts
parser.test.ts
validator.test.ts
examples/
sample.vtt
sample.srt
kustos.yaml