contextduty 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ContextDuty Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.4
2
+ Name: contextduty
3
+ Version: 0.1.0
4
+ Summary: Policy-driven context firewall for AI workflows — scan and redact sensitive data before prompts, logs, or traces leave your environment.
5
+ Author: ContextDuty Contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/SHUBHAGYTA24/contextduty
8
+ Project-URL: Repository, https://github.com/SHUBHAGYTA24/contextduty
9
+ Project-URL: Bug Tracker, https://github.com/SHUBHAGYTA24/contextduty/issues
10
+ Project-URL: Changelog, https://github.com/SHUBHAGYTA24/contextduty/blob/main/CHANGELOG.md
11
+ Keywords: security,pii,redaction,ai,llm,mcp,prompt,privacy,devsecops,secrets
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Security
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: System :: Logging
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # ContextDuty
30
+
31
+ > A policy-driven context firewall for AI workflows. Scan and redact sensitive data before prompts, logs, or traces leave your environment — locally, with no cloud calls.
32
+
33
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
34
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
35
+ [![MCP Compatible](https://img.shields.io/badge/MCP-compatible-purple.svg)](https://modelcontextprotocol.io)
36
+
37
+ ---
38
+
39
+ ## Why ContextDuty
40
+
41
+ AI coding assistants and agent workflows are spreading fast. So is accidental data leakage — API keys, emails, and PII flowing into prompts, logs, and traces that may be stored or sent to third-party services.
42
+
43
+ ContextDuty is a **local-first, policy-layered primitive** that fits into any workflow:
44
+ - **CLI** — pipe files through it in CI or pre-commit hooks
45
+ - **MCP server** — Cursor, VS Code, and any MCP client get automatic redaction
46
+ - **Policy inheritance** — teams extend org-wide baselines without copying rules
47
+
48
+ ---
49
+
50
+ ## Why not Presidio?
51
+
52
+ [Microsoft Presidio](https://github.com/microsoft/presidio) is great for NER-based PII detection in data pipelines. ContextDuty solves a different problem:
53
+
54
+ | | ContextDuty | Presidio |
55
+ |---|---|---|
56
+ | Target use case | AI prompts, logs, agent traces | Data pipelines, analytics |
57
+ | MCP-native | ✅ | ❌ |
58
+ | Policy layering (`extends`) | ✅ | ❌ |
59
+ | `block` mode for CI | ✅ | ❌ |
60
+ | Zero dependencies | ✅ | ❌ (heavy NLP stack) |
61
+ | Custom detectors (no code) | ✅ (regex in JSON) | Partial |
62
+ | Deployment | Local CLI / subprocess | Service / SDK |
63
+
64
+ Use Presidio when you need ML-based entity recognition at scale. Use ContextDuty when you need a lightweight, policy-enforceable firewall close to your AI toolchain.
65
+
66
+ ---
67
+
68
+ ## Detection coverage
69
+
70
+ | Detector | Example input | Masked as |
71
+ |---|---|---|
72
+ | `email` | `jane@corp.com` | `<EMAIL_a1b2c3d4e5>` |
73
+ | `phone` | `+1 415-555-1212` | `<PHONE_f6g7h8i9j0>` |
74
+ | `api_key` | `sk_live_ABC123...` | `<API_KEY_k1l2m3n4o5>` |
75
+ | `aws_key` | `AKIA1234567890ABCDEF` | `<AWS_KEY_p6q7r8s9t0>` |
76
+ | `bearer_token` | `Bearer eyJhbGci...` | `<BEARER_TOKEN_u1v2w3x4y5>` |
77
+
78
+ Masks are **deterministic** — the same value always produces the same mask, so you can correlate across log lines without exposing the raw value.
79
+
80
+ ---
81
+
82
+ ## Quickstart
83
+
84
+ ```bash
85
+ pip install contextduty
86
+ contextduty init
87
+ ```
88
+
89
+ Then scan and redact:
90
+
91
+ ```bash
92
+ contextduty scan sample.txt --report report.json
93
+ contextduty redact --in sample.txt --out clean.txt --report report.json
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Commands
99
+
100
+ | Command | Description |
101
+ |---|---|
102
+ | `contextduty init` | Create `.contextduty.json` in the current directory |
103
+ | `contextduty scan <file>` | Scan file, print JSON findings report |
104
+ | `contextduty redact --in <f> --out <f>` | Redact matches, write clean file |
105
+ | `contextduty policy validate --policy <f> [--strict]` | Validate and resolve a layered policy |
106
+
107
+ ---
108
+
109
+ ## MCP server (Cursor / VS Code / any MCP client)
110
+
111
+ ContextDuty runs as an MCP stdio server — drop it into your editor config and every file your agent touches is scanned automatically.
112
+
113
+ ```bash
114
+ contextduty-mcp
115
+ ```
116
+
117
+ **Cursor** — add to `~/.cursor/mcp.json`:
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "contextduty": {
122
+ "command": "contextduty-mcp"
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ Exposed tools:
129
+ - `contextduty_scan` (`path`, optional `policyPath`)
130
+ - `contextduty_redact` (`inputPath`, `outputPath`, optional `policyPath`)
131
+
132
+ ---
133
+
134
+ ## Policy file
135
+
136
+ Default `.contextduty.json`:
137
+
138
+ ```json
139
+ {
140
+ "mode": "redact",
141
+ "detectors": ["email", "phone", "api_key", "aws_key", "bearer_token"],
142
+ "custom_detectors": {}
143
+ }
144
+ ```
145
+
146
+ **Add a custom detector without touching code:**
147
+
148
+ ```json
149
+ {
150
+ "mode": "redact",
151
+ "detectors": ["email"],
152
+ "custom_detectors": {
153
+ "employee_id": "\\bEMP-[0-9]{6}\\b",
154
+ "internal_ticket": "\\bTICKET-[A-Z]{3}-[0-9]{4}\\b"
155
+ }
156
+ }
157
+ ```
158
+
159
+ `custom_detectors` are auto-enabled — just add the regex entry.
160
+
161
+ **Policy layering for teams and enterprises:**
162
+
163
+ ```json
164
+ {
165
+ "extends": "../../policies/org-baseline.json",
166
+ "mode": "block",
167
+ "detectors": ["internal_ticket"],
168
+ "custom_detectors": {
169
+ "internal_ticket": "\\bTICKET-[A-Z]{3}-[0-9]{4}\\b"
170
+ }
171
+ }
172
+ ```
173
+
174
+ Rules:
175
+ - `extends` can be a string or list (relative file paths)
176
+ - `detectors` are merged (parent + child)
177
+ - `custom_detectors` are merged (child overrides same key)
178
+ - `mode` is overridden by the child policy
179
+ - Cycles in `extends` are rejected with a clear error
180
+
181
+ **Modes:**
182
+
183
+ | Mode | Behaviour |
184
+ |---|---|
185
+ | `redact` | Replace matched values with deterministic masks |
186
+ | `warn` | Report findings, do not change content |
187
+ | `block` | Exit non-zero if findings exist (CI enforcement) |
188
+
189
+ ---
190
+
191
+ ## Compliance policy packs
192
+
193
+ Ready-made baselines for common frameworks — extend them in your own policy file:
194
+
195
+ | Pack | Path | Detectors included |
196
+ |---|---|---|
197
+ | SOC 2 | `policies/soc2-baseline.json` | email, phone, api_key, aws_key, bearer_token |
198
+ | HIPAA | `policies/hipaa-baseline.json` | email, phone + PHI custom patterns |
199
+
200
+ Usage:
201
+ ```json
202
+ {
203
+ "extends": "./node_modules/contextduty/policies/soc2-baseline.json",
204
+ "mode": "block"
205
+ }
206
+ ```
207
+
208
+ ---
209
+
210
+ ## CI integration
211
+
212
+ Add a pre-push check to block accidental secret commits:
213
+
214
+ ```yaml
215
+ # .github/workflows/contextduty.yml
216
+ - name: Scan for secrets
217
+ run: |
218
+ pip install contextduty
219
+ contextduty scan . --policy .contextduty.json
220
+ ```
221
+
222
+ Or use `mode: block` in your policy to make `contextduty scan` exit non-zero on any finding.
223
+
224
+ ---
225
+
226
+ ## Roadmap
227
+
228
+ - [ ] PyPI publish (`pip install contextduty`)
229
+ - [ ] Streaming JSONL mode for multi-GB datasets
230
+ - [ ] VS Code extension
231
+ - [ ] Policy packs for PCI-DSS
232
+ - [ ] GitHub Action (`uses: contextduty/action@v1`)
233
+
234
+ ---
235
+
236
+ ## Open source
237
+
238
+ | File | Purpose |
239
+ |---|---|
240
+ | `LICENSE` | MIT |
241
+ | `SECURITY.md` | Vulnerability reporting |
242
+ | `CONTRIBUTING.md` | How to contribute |
243
+ | `CODE_OF_CONDUCT.md` | Community standards |
244
+ | `CHANGELOG.md` | Version history |
245
+
246
+ ---
247
+
248
+ ## Contributing
249
+
250
+ Issues, PRs, and policy pack contributions are very welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.
@@ -0,0 +1,222 @@
1
+ # ContextDuty
2
+
3
+ > A policy-driven context firewall for AI workflows. Scan and redact sensitive data before prompts, logs, or traces leave your environment — locally, with no cloud calls.
4
+
5
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
+ [![MCP Compatible](https://img.shields.io/badge/MCP-compatible-purple.svg)](https://modelcontextprotocol.io)
8
+
9
+ ---
10
+
11
+ ## Why ContextDuty
12
+
13
+ AI coding assistants and agent workflows are spreading fast. So is accidental data leakage — API keys, emails, and PII flowing into prompts, logs, and traces that may be stored or sent to third-party services.
14
+
15
+ ContextDuty is a **local-first, policy-layered primitive** that fits into any workflow:
16
+ - **CLI** — pipe files through it in CI or pre-commit hooks
17
+ - **MCP server** — Cursor, VS Code, and any MCP client get automatic redaction
18
+ - **Policy inheritance** — teams extend org-wide baselines without copying rules
19
+
20
+ ---
21
+
22
+ ## Why not Presidio?
23
+
24
+ [Microsoft Presidio](https://github.com/microsoft/presidio) is great for NER-based PII detection in data pipelines. ContextDuty solves a different problem:
25
+
26
+ | | ContextDuty | Presidio |
27
+ |---|---|---|
28
+ | Target use case | AI prompts, logs, agent traces | Data pipelines, analytics |
29
+ | MCP-native | ✅ | ❌ |
30
+ | Policy layering (`extends`) | ✅ | ❌ |
31
+ | `block` mode for CI | ✅ | ❌ |
32
+ | Zero dependencies | ✅ | ❌ (heavy NLP stack) |
33
+ | Custom detectors (no code) | ✅ (regex in JSON) | Partial |
34
+ | Deployment | Local CLI / subprocess | Service / SDK |
35
+
36
+ Use Presidio when you need ML-based entity recognition at scale. Use ContextDuty when you need a lightweight, policy-enforceable firewall close to your AI toolchain.
37
+
38
+ ---
39
+
40
+ ## Detection coverage
41
+
42
+ | Detector | Example input | Masked as |
43
+ |---|---|---|
44
+ | `email` | `jane@corp.com` | `<EMAIL_a1b2c3d4e5>` |
45
+ | `phone` | `+1 415-555-1212` | `<PHONE_f6g7h8i9j0>` |
46
+ | `api_key` | `sk_live_ABC123...` | `<API_KEY_k1l2m3n4o5>` |
47
+ | `aws_key` | `AKIA1234567890ABCDEF` | `<AWS_KEY_p6q7r8s9t0>` |
48
+ | `bearer_token` | `Bearer eyJhbGci...` | `<BEARER_TOKEN_u1v2w3x4y5>` |
49
+
50
+ Masks are **deterministic** — the same value always produces the same mask, so you can correlate across log lines without exposing the raw value.
51
+
52
+ ---
53
+
54
+ ## Quickstart
55
+
56
+ ```bash
57
+ pip install contextduty
58
+ contextduty init
59
+ ```
60
+
61
+ Then scan and redact:
62
+
63
+ ```bash
64
+ contextduty scan sample.txt --report report.json
65
+ contextduty redact --in sample.txt --out clean.txt --report report.json
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Commands
71
+
72
+ | Command | Description |
73
+ |---|---|
74
+ | `contextduty init` | Create `.contextduty.json` in the current directory |
75
+ | `contextduty scan <file>` | Scan file, print JSON findings report |
76
+ | `contextduty redact --in <f> --out <f>` | Redact matches, write clean file |
77
+ | `contextduty policy validate --policy <f> [--strict]` | Validate and resolve a layered policy |
78
+
79
+ ---
80
+
81
+ ## MCP server (Cursor / VS Code / any MCP client)
82
+
83
+ ContextDuty runs as an MCP stdio server — drop it into your editor config and every file your agent touches is scanned automatically.
84
+
85
+ ```bash
86
+ contextduty-mcp
87
+ ```
88
+
89
+ **Cursor** — add to `~/.cursor/mcp.json`:
90
+ ```json
91
+ {
92
+ "mcpServers": {
93
+ "contextduty": {
94
+ "command": "contextduty-mcp"
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ Exposed tools:
101
+ - `contextduty_scan` (`path`, optional `policyPath`)
102
+ - `contextduty_redact` (`inputPath`, `outputPath`, optional `policyPath`)
103
+
104
+ ---
105
+
106
+ ## Policy file
107
+
108
+ Default `.contextduty.json`:
109
+
110
+ ```json
111
+ {
112
+ "mode": "redact",
113
+ "detectors": ["email", "phone", "api_key", "aws_key", "bearer_token"],
114
+ "custom_detectors": {}
115
+ }
116
+ ```
117
+
118
+ **Add a custom detector without touching code:**
119
+
120
+ ```json
121
+ {
122
+ "mode": "redact",
123
+ "detectors": ["email"],
124
+ "custom_detectors": {
125
+ "employee_id": "\\bEMP-[0-9]{6}\\b",
126
+ "internal_ticket": "\\bTICKET-[A-Z]{3}-[0-9]{4}\\b"
127
+ }
128
+ }
129
+ ```
130
+
131
+ `custom_detectors` are auto-enabled — just add the regex entry.
132
+
133
+ **Policy layering for teams and enterprises:**
134
+
135
+ ```json
136
+ {
137
+ "extends": "../../policies/org-baseline.json",
138
+ "mode": "block",
139
+ "detectors": ["internal_ticket"],
140
+ "custom_detectors": {
141
+ "internal_ticket": "\\bTICKET-[A-Z]{3}-[0-9]{4}\\b"
142
+ }
143
+ }
144
+ ```
145
+
146
+ Rules:
147
+ - `extends` can be a string or list (relative file paths)
148
+ - `detectors` are merged (parent + child)
149
+ - `custom_detectors` are merged (child overrides same key)
150
+ - `mode` is overridden by the child policy
151
+ - Cycles in `extends` are rejected with a clear error
152
+
153
+ **Modes:**
154
+
155
+ | Mode | Behaviour |
156
+ |---|---|
157
+ | `redact` | Replace matched values with deterministic masks |
158
+ | `warn` | Report findings, do not change content |
159
+ | `block` | Exit non-zero if findings exist (CI enforcement) |
160
+
161
+ ---
162
+
163
+ ## Compliance policy packs
164
+
165
+ Ready-made baselines for common frameworks — extend them in your own policy file:
166
+
167
+ | Pack | Path | Detectors included |
168
+ |---|---|---|
169
+ | SOC 2 | `policies/soc2-baseline.json` | email, phone, api_key, aws_key, bearer_token |
170
+ | HIPAA | `policies/hipaa-baseline.json` | email, phone + PHI custom patterns |
171
+
172
+ Usage:
173
+ ```json
174
+ {
175
+ "extends": "./node_modules/contextduty/policies/soc2-baseline.json",
176
+ "mode": "block"
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ## CI integration
183
+
184
+ Add a pre-push check to block accidental secret commits:
185
+
186
+ ```yaml
187
+ # .github/workflows/contextduty.yml
188
+ - name: Scan for secrets
189
+ run: |
190
+ pip install contextduty
191
+ contextduty scan . --policy .contextduty.json
192
+ ```
193
+
194
+ Or use `mode: block` in your policy to make `contextduty scan` exit non-zero on any finding.
195
+
196
+ ---
197
+
198
+ ## Roadmap
199
+
200
+ - [ ] PyPI publish (`pip install contextduty`)
201
+ - [ ] Streaming JSONL mode for multi-GB datasets
202
+ - [ ] VS Code extension
203
+ - [ ] Policy packs for PCI-DSS
204
+ - [ ] GitHub Action (`uses: contextduty/action@v1`)
205
+
206
+ ---
207
+
208
+ ## Open source
209
+
210
+ | File | Purpose |
211
+ |---|---|
212
+ | `LICENSE` | MIT |
213
+ | `SECURITY.md` | Vulnerability reporting |
214
+ | `CONTRIBUTING.md` | How to contribute |
215
+ | `CODE_OF_CONDUCT.md` | Community standards |
216
+ | `CHANGELOG.md` | Version history |
217
+
218
+ ---
219
+
220
+ ## Contributing
221
+
222
+ Issues, PRs, and policy pack contributions are very welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "contextduty"
7
+ version = "0.1.0"
8
+ description = "Policy-driven context firewall for AI workflows — scan and redact sensitive data before prompts, logs, or traces leave your environment."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "ContextDuty Contributors" }
14
+ ]
15
+ keywords = [
16
+ "security", "pii", "redaction", "ai", "llm", "mcp",
17
+ "prompt", "privacy", "devsecops", "secrets"
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Topic :: Security",
28
+ "Topic :: Software Development :: Libraries :: Python Modules",
29
+ "Topic :: System :: Logging",
30
+ ]
31
+ dependencies = []
32
+
33
+ [project.optional-dependencies]
34
+ dev = ["pytest>=7"]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/SHUBHAGYTA24/contextduty"
38
+ Repository = "https://github.com/SHUBHAGYTA24/contextduty"
39
+ "Bug Tracker" = "https://github.com/SHUBHAGYTA24/contextduty/issues"
40
+ Changelog = "https://github.com/SHUBHAGYTA24/contextduty/blob/main/CHANGELOG.md"
41
+
42
+ [project.scripts]
43
+ contextduty = "contextduty.cli:main"
44
+ contextduty-mcp = "contextduty.mcp_server:main"
45
+
46
+ [tool.setuptools]
47
+ package-dir = {"" = "src"}
48
+
49
+ [tool.setuptools.packages.find]
50
+ where = ["src"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+
55
+
56
+ [tool.ruff]
57
+ line-length = 100
58
+ target-version = "py310"
59
+
60
+ [tool.ruff.lint]
61
+ select = ["E", "F", "I"] # pycodestyle errors, pyflakes, isort
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """ContextDuty — policy-driven context firewall for AI workflows."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,127 @@
1
+ """CLI entrypoint for ContextDuty."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from .engine import redact_file, report_to_json, scan_file
11
+ from .policy import load_policy, unknown_detector_names, write_default_policy
12
+
13
+
14
+ def _parser() -> argparse.ArgumentParser:
15
+ from . import __version__
16
+
17
+ parser = argparse.ArgumentParser(
18
+ prog="contextduty", description="Protect AI context with policy checks."
19
+ )
20
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
21
+ subparsers = parser.add_subparsers(dest="command", required=True)
22
+
23
+ init_parser = subparsers.add_parser("init", help="Create default policy file.")
24
+ init_parser.add_argument("--path", default=".contextduty.json", help="Policy output path.")
25
+
26
+ scan_parser = subparsers.add_parser("scan", help="Scan a text file for risky data.")
27
+ scan_parser.add_argument("target", help="Input file path.")
28
+ scan_parser.add_argument("--policy", default=".contextduty.json", help="Policy path.")
29
+ scan_parser.add_argument("--report", help="Optional report output JSON path.")
30
+
31
+ redact_parser = subparsers.add_parser("redact", help="Redact risky data from an input file.")
32
+ redact_parser.add_argument("--in", dest="input_path", required=True, help="Input file path.")
33
+ redact_parser.add_argument("--out", dest="output_path", required=True, help="Output file path.")
34
+ redact_parser.add_argument("--policy", default=".contextduty.json", help="Policy path.")
35
+ redact_parser.add_argument("--report", help="Optional report output JSON path.")
36
+
37
+ policy_parser = subparsers.add_parser("policy", help="Policy operations.")
38
+ policy_subparsers = policy_parser.add_subparsers(dest="policy_command", required=True)
39
+ validate_parser = policy_subparsers.add_parser(
40
+ "validate", help="Validate and resolve a policy file."
41
+ )
42
+ validate_parser.add_argument("--policy", default=".contextduty.json", help="Policy path.")
43
+ validate_parser.add_argument(
44
+ "--strict",
45
+ action="store_true",
46
+ help="Fail validation when unknown detector names are present.",
47
+ )
48
+
49
+ return parser
50
+
51
+
52
+ def _load_policy_with_fallback(policy_path: str) -> tuple[Path | None, object]:
53
+ path = Path(policy_path)
54
+ if path.exists():
55
+ return path, load_policy(path)
56
+ return None, load_policy(None)
57
+
58
+
59
+ def main() -> None:
60
+ parser = _parser()
61
+ args = parser.parse_args()
62
+
63
+ if args.command == "init":
64
+ out_path = Path(args.path)
65
+ write_default_policy(out_path)
66
+ print(f"Created policy at {out_path}")
67
+ return
68
+
69
+ if args.command == "scan":
70
+ policy_ref, policy = _load_policy_with_fallback(args.policy)
71
+ result = scan_file(Path(args.target), policy)
72
+ report = report_to_json(result)
73
+ print(report)
74
+ if args.report:
75
+ Path(args.report).write_text(report + "\n", encoding="utf-8")
76
+ print(f"Saved report to {args.report}")
77
+ if result.blocked:
78
+ print(f"BLOCKED by policy ({policy_ref or 'default'})", file=sys.stderr)
79
+ raise SystemExit(2)
80
+ return
81
+
82
+ if args.command == "redact":
83
+ policy_ref, policy = _load_policy_with_fallback(args.policy)
84
+ result = redact_file(Path(args.input_path), Path(args.output_path), policy)
85
+ report = report_to_json(result)
86
+ print(report)
87
+ if args.report:
88
+ Path(args.report).write_text(report + "\n", encoding="utf-8")
89
+ print(f"Saved report to {args.report}")
90
+ if result.blocked:
91
+ print(f"BLOCKED by policy ({policy_ref or 'default'})", file=sys.stderr)
92
+ raise SystemExit(2)
93
+ return
94
+
95
+ if args.command == "policy":
96
+ if args.policy_command == "validate":
97
+ policy_path = Path(args.policy)
98
+ if policy_path.exists():
99
+ policy = load_policy(policy_path)
100
+ source = str(policy_path)
101
+ else:
102
+ policy = load_policy(None)
103
+ source = "default"
104
+ payload = {
105
+ "valid": True,
106
+ "source": source,
107
+ "mode": policy.mode,
108
+ "detectors": sorted(policy.detectors),
109
+ "custom_detectors": sorted(policy.custom_detectors.keys()),
110
+ }
111
+ if args.strict:
112
+ unknown = unknown_detector_names(policy)
113
+ if unknown:
114
+ print(
115
+ f"Unknown detector names in strict mode: {', '.join(unknown)}",
116
+ file=sys.stderr,
117
+ )
118
+ raise SystemExit(2)
119
+ print(json.dumps(payload, indent=2))
120
+ return
121
+ raise SystemExit(1)
122
+
123
+ raise SystemExit(1)
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()