clean-room-skill 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.
Files changed (56) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.codex-plugin/plugin.json +36 -0
  4. package/LICENSE +21 -0
  5. package/README.md +376 -0
  6. package/agents/clean-architect.md +27 -0
  7. package/agents/clean-qa-editor.md +27 -0
  8. package/agents/contaminated-manager-verifier.md +35 -0
  9. package/agents/contaminated-source-analyst.md +26 -0
  10. package/bin/install.js +535 -0
  11. package/examples/codex/.codex/agents/clean-architect.toml +17 -0
  12. package/examples/codex/.codex/agents/clean-qa-editor.toml +17 -0
  13. package/examples/codex/.codex/agents/contaminated-manager-verifier.toml +21 -0
  14. package/examples/codex/.codex/agents/contaminated-source-analyst.toml +17 -0
  15. package/hooks/check-artifact-leakage.py +317 -0
  16. package/hooks/clean-room-hook.py +88 -0
  17. package/hooks/clean_room_paths.py +130 -0
  18. package/hooks/deny-clean-room-shell.py +30 -0
  19. package/hooks/deny-clean-source-read.py +104 -0
  20. package/hooks/deny-contaminated-clean-write.py +134 -0
  21. package/hooks/hooks.json +44 -0
  22. package/hooks/require-clean-room-env.py +127 -0
  23. package/hooks/validate-handoff-package.py +140 -0
  24. package/hooks/validate-json-schema.py +283 -0
  25. package/lib/fs-utils.cjs +123 -0
  26. package/lib/hooks.cjs +214 -0
  27. package/package.json +49 -0
  28. package/plugin.json +20 -0
  29. package/skills/attended/SKILL.md +25 -0
  30. package/skills/clean-room/SKILL.md +134 -0
  31. package/skills/clean-room/assets/behavior-spec.schema.json +367 -0
  32. package/skills/clean-room/assets/contamination-incident.schema.json +60 -0
  33. package/skills/clean-room/assets/coverage-ledger.schema.json +139 -0
  34. package/skills/clean-room/assets/evidence-ledger.schema.json +80 -0
  35. package/skills/clean-room/assets/handoff-package.schema.json +114 -0
  36. package/skills/clean-room/assets/qc-report.schema.json +248 -0
  37. package/skills/clean-room/assets/skeleton-manifest.schema.json +239 -0
  38. package/skills/clean-room/assets/source-index.schema.json +622 -0
  39. package/skills/clean-room/assets/task-manifest.schema.json +593 -0
  40. package/skills/clean-room/examples/README.md +18 -0
  41. package/skills/clean-room/examples/minimal-spec-package/behavior-spec.json +61 -0
  42. package/skills/clean-room/examples/minimal-spec-package/coverage-ledger.json +27 -0
  43. package/skills/clean-room/examples/minimal-spec-package/evidence-ledger.json +17 -0
  44. package/skills/clean-room/examples/minimal-spec-package/handoff-package.json +26 -0
  45. package/skills/clean-room/examples/minimal-spec-package/qc-report.json +25 -0
  46. package/skills/clean-room/examples/minimal-spec-package/skeleton-manifest.json +45 -0
  47. package/skills/clean-room/examples/minimal-spec-package/source-index.json +156 -0
  48. package/skills/clean-room/examples/minimal-spec-package/task-manifest.json +220 -0
  49. package/skills/clean-room/references/LEAKAGE-RULES.md +92 -0
  50. package/skills/clean-room/references/PROCESS.md +185 -0
  51. package/skills/clean-room/references/SPEC-SCHEMA.md +185 -0
  52. package/skills/clean-room/references/TARGET-LANGUAGE-GUIDE.md +43 -0
  53. package/skills/clean-room/scripts/build_source_index.py +1253 -0
  54. package/skills/clean-room/scripts/clean_room_tool_manager.py +199 -0
  55. package/skills/clean-room/scripts/clean_room_tooling.py +370 -0
  56. package/skills/unattended/SKILL.md +26 -0
@@ -0,0 +1,17 @@
1
+ {
2
+ "ledger_id": "evidence-ledger-example",
3
+ "task_id": "task-example",
4
+ "domain": "contaminated",
5
+ "entries": [
6
+ {
7
+ "evidence_id": "item-001",
8
+ "source_unit_ref": "unit-example-flow",
9
+ "evidence_type": "source-observation",
10
+ "description": "Neutral note that a behavior exists; no source text included.",
11
+ "evidence_location_ref": "contaminated-only:unit-example-flow:item-001",
12
+ "source_hash": "not-recorded-in-example",
13
+ "sensitivity": "medium",
14
+ "retained_in_contaminated_domain": true
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "package_id": "handoff-example",
3
+ "task_id": "task-example",
4
+ "from_domain": "contaminated",
5
+ "to_domain": "clean",
6
+ "created_by_role": "contaminated-source-analyst",
7
+ "artifacts": [
8
+ {
9
+ "artifact_id": "spec-example-flow",
10
+ "artifact_type": "behavior-spec",
11
+ "path": "clean-artifacts/behavior-spec.json",
12
+ "sha256": "0000000000000000000000000000000000000000000000000000000000000000"
13
+ }
14
+ ],
15
+ "excluded_material": [
16
+ "source_excerpt",
17
+ "raw_diff",
18
+ "private_identifier",
19
+ "source_shaped_pseudocode"
20
+ ],
21
+ "leakage_review": {
22
+ "status": "passed",
23
+ "reviewer_role": "contaminated-source-analyst",
24
+ "notes": "Example package contains only schema-shaped data."
25
+ }
26
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "report_id": "qc-example",
3
+ "reviewer_role": "clean-qa-editor",
4
+ "reviewed_at": "2000-01-01T00:00:00Z",
5
+ "reviewed_artifacts": [
6
+ "behavior-spec.json",
7
+ "skeleton-manifest.json"
8
+ ],
9
+ "artifact_hashes": [
10
+ {
11
+ "artifact": "behavior-spec.json",
12
+ "sha256": "0000000000000000000000000000000000000000000000000000000000000000"
13
+ }
14
+ ],
15
+ "schema_validator_version": "manual-example",
16
+ "schema_status": "passed",
17
+ "leakage_status": "passed",
18
+ "leakage_scan_summary": "No blocked markers in example.",
19
+ "coverage_status": "complete",
20
+ "required_rerun": false,
21
+ "contamination_incidents": [],
22
+ "findings": [],
23
+ "abstract_delta_tickets": [],
24
+ "final_status": "passed"
25
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "manifest_id": "skeleton-example",
3
+ "target_language": "unspecified",
4
+ "area_id_naming_policy": "Use neutral clean ids that do not mirror source layout.",
5
+ "target_constraints": [],
6
+ "areas": [
7
+ {
8
+ "area_id": "area-example-flow",
9
+ "purpose": "Own the example behavior.",
10
+ "spec_ids": [
11
+ "spec-example-flow"
12
+ ],
13
+ "public_contract_refs": [],
14
+ "target_constraints": [],
15
+ "dependency_constraints": [],
16
+ "test_obligations": [
17
+ "test-001"
18
+ ],
19
+ "open_decisions": []
20
+ }
21
+ ],
22
+ "public_contracts": [],
23
+ "dependency_constraints": [],
24
+ "implementation_forbidden_material": [
25
+ "source_excerpt",
26
+ "raw_diff",
27
+ "private_identifier",
28
+ "source_shaped_pseudocode"
29
+ ],
30
+ "test_mapping": [
31
+ {
32
+ "test_id": "test-001",
33
+ "spec_ids": [
34
+ "spec-example-flow"
35
+ ],
36
+ "scenario_refs": [
37
+ "test-001"
38
+ ]
39
+ }
40
+ ],
41
+ "test_obligations": [
42
+ "test-001"
43
+ ],
44
+ "open_decisions": []
45
+ }
@@ -0,0 +1,156 @@
1
+ {
2
+ "index_id": "source-index-task-example",
3
+ "task_id": "task-example",
4
+ "created_at": "2026-05-20T00:00:00Z",
5
+ "created_by_role": "controller-preflight",
6
+ "domain": "contaminated",
7
+ "generator": {
8
+ "name": "build_source_index.py",
9
+ "version": "1",
10
+ "python_version": "3.x",
11
+ "scanner_modes": [
12
+ "python-ast",
13
+ "javascript-typescript-scanner",
14
+ "text-metrics"
15
+ ]
16
+ },
17
+ "limits": {
18
+ "max_files": 2000,
19
+ "max_file_bytes": 1000000,
20
+ "max_total_bytes": 50000000,
21
+ "max_batch_tokens": 20000,
22
+ "ignore_dirs": [
23
+ ".git",
24
+ ".next",
25
+ ".venv",
26
+ "__pycache__",
27
+ "build",
28
+ "coverage",
29
+ "dist",
30
+ "node_modules",
31
+ "target",
32
+ "venv"
33
+ ]
34
+ },
35
+ "source_roots": [
36
+ {
37
+ "root_id": "root-001",
38
+ "path": "source-workspace"
39
+ }
40
+ ],
41
+ "files": [
42
+ {
43
+ "file_id": "file-000001",
44
+ "root_id": "root-001",
45
+ "path": "flow/controller.py",
46
+ "language": "python",
47
+ "scanner": "python-ast",
48
+ "metrics": {
49
+ "bytes": 120,
50
+ "lines": 6,
51
+ "words": 14,
52
+ "characters": 120,
53
+ "estimated_tokens": 30
54
+ },
55
+ "imports": [
56
+ {
57
+ "specifier": ".state",
58
+ "kind": "python-from-import",
59
+ "is_relative": true,
60
+ "names": [
61
+ "State"
62
+ ],
63
+ "resolved_file_id": "file-000002"
64
+ }
65
+ ],
66
+ "exports": [
67
+ {
68
+ "name": "Controller",
69
+ "kind": "top-level-class"
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "file_id": "file-000002",
75
+ "root_id": "root-001",
76
+ "path": "flow/state.py",
77
+ "language": "python",
78
+ "scanner": "python-ast",
79
+ "metrics": {
80
+ "bytes": 80,
81
+ "lines": 4,
82
+ "words": 9,
83
+ "characters": 80,
84
+ "estimated_tokens": 20
85
+ },
86
+ "imports": [],
87
+ "exports": [
88
+ {
89
+ "name": "State",
90
+ "kind": "top-level-class"
91
+ }
92
+ ]
93
+ }
94
+ ],
95
+ "relationships": [
96
+ {
97
+ "from_file_id": "file-000001",
98
+ "to_file_id": "file-000002",
99
+ "specifier": ".state",
100
+ "kind": "python-from-import"
101
+ }
102
+ ],
103
+ "groups": [
104
+ {
105
+ "group_id": "group-0001",
106
+ "reason": "dependency-component",
107
+ "file_ids": [
108
+ "file-000001",
109
+ "file-000002"
110
+ ],
111
+ "metrics": {
112
+ "bytes": 200,
113
+ "lines": 10,
114
+ "words": 23,
115
+ "characters": 200,
116
+ "estimated_tokens": 50
117
+ },
118
+ "notes": "Files connected by resolved local imports."
119
+ }
120
+ ],
121
+ "recommended_batches": [
122
+ {
123
+ "batch_id": "batch-0001",
124
+ "group_ids": [
125
+ "group-0001"
126
+ ],
127
+ "file_ids": [
128
+ "file-000001",
129
+ "file-000002"
130
+ ],
131
+ "metrics": {
132
+ "bytes": 200,
133
+ "lines": 10,
134
+ "words": 23,
135
+ "characters": 200,
136
+ "estimated_tokens": 50
137
+ },
138
+ "notes": "Example Agent 0 unit source batch."
139
+ }
140
+ ],
141
+ "skipped_entries": [],
142
+ "aggregate_metrics": {
143
+ "bytes": 200,
144
+ "lines": 10,
145
+ "words": 23,
146
+ "characters": 200,
147
+ "estimated_tokens": 50,
148
+ "file_count": 2,
149
+ "skipped_count": 0,
150
+ "skipped_entries_truncated": false,
151
+ "relationship_count": 1,
152
+ "resolved_relationship_count": 1,
153
+ "group_count": 1,
154
+ "batch_count": 1
155
+ }
156
+ }
@@ -0,0 +1,220 @@
1
+ {
2
+ "task_id": "task-example",
3
+ "target_identifier": "example-system",
4
+ "authorization": {
5
+ "requester": "example-requester",
6
+ "scope_statement": "Authorized example clean-room source-to-spec task.",
7
+ "allowed_actions": [
8
+ "read authorized source",
9
+ "produce spec-only artifacts"
10
+ ],
11
+ "prohibited_actions": [
12
+ "generate replacement implementation code",
13
+ "move source excerpts into clean artifacts"
14
+ ],
15
+ "evidence_handling": "Retain source evidence only in the contaminated workspace."
16
+ },
17
+ "source_acquisition_basis": "Authorized local source access.",
18
+ "license_contract_notes": "No legal conclusion recorded.",
19
+ "source_index_ref": "contaminated-artifacts/source-index.json",
20
+ "source_scope": {
21
+ "workspace_id": "source-workspace",
22
+ "description": "Authorized source workspace.",
23
+ "path_policy": "Clean roles must not read this workspace."
24
+ },
25
+ "clean_scope": {
26
+ "workspace_id": "clean-workspace",
27
+ "description": "Clean spec workspace.",
28
+ "path_policy": "Contaminated roles must not write here."
29
+ },
30
+ "trust_boundary": {
31
+ "contaminated_domain": "source-workspace",
32
+ "clean_domain": "clean-workspace",
33
+ "separation_controls": [
34
+ "separate workspace paths",
35
+ "separate profiles",
36
+ "schema validation",
37
+ "leakage review"
38
+ ]
39
+ },
40
+ "controller_policy": {
41
+ "mode": "attended",
42
+ "stop_conditions": [
43
+ "authorization-missing",
44
+ "scope-change",
45
+ "contamination-suspected",
46
+ "schema-validation-failed",
47
+ "leakage-scan-failed",
48
+ "unit-blocked",
49
+ "coverage-complete"
50
+ ],
51
+ "notes": "Missing controller policy defaults to attended mode."
52
+ },
53
+ "format_selection": {
54
+ "mode": "canonical-plus-target",
55
+ "selection_basis": "user-choice",
56
+ "target_profile": "speckit-feature-folder",
57
+ "native_artifacts": [
58
+ ".specify/memory/constitution.md - shared project constitution",
59
+ "specs/<feature-id>/spec.md - feature specification",
60
+ "specs/<feature-id>/plan.md - technical plan",
61
+ "specs/<feature-id>/tasks.md - executable work items"
62
+ ],
63
+ "formatting_rules": [
64
+ "Render a numbered feature folder under specs/<feature-id>/.",
65
+ "Use prioritized user stories with Given/When/Then acceptance scenarios in spec.md.",
66
+ "Use plan.md and tasks.md for technical plan and executable work items."
67
+ ]
68
+ },
69
+ "agent_pipeline": {
70
+ "agent_0": {
71
+ "role": "contaminated-manager-verifier",
72
+ "responsibility": "Manage scope, assign agents, verify coverage, and receive Agent 3 final report.",
73
+ "trust_domain": "contaminated"
74
+ },
75
+ "agent_1": {
76
+ "role": "contaminated-source-analyst",
77
+ "responsibility": "Read authorized source and generate neutral source units, task slices, and behavior specs.",
78
+ "trust_domain": "contaminated"
79
+ },
80
+ "agent_2": {
81
+ "role": "clean-architect",
82
+ "responsibility": "Merge approved handoff artifacts into the selected clean schema base and skeleton manifest without reading source or contaminated ledgers.",
83
+ "trust_domain": "clean"
84
+ },
85
+ "agent_3": {
86
+ "role": "clean-qa-editor",
87
+ "responsibility": "Run final schema, leakage, and coverage QC and report abstract findings to Agent 0.",
88
+ "trust_domain": "clean"
89
+ },
90
+ "handoff_rules": {
91
+ "allowed_inputs": [
92
+ "task-manifest.json",
93
+ "approved behavior-spec.json",
94
+ "handoff-package.json",
95
+ "abstract coverage summaries",
96
+ "abstract delta tickets"
97
+ ],
98
+ "allowed_outputs": [
99
+ "behavior-spec.json",
100
+ "skeleton-manifest.json",
101
+ "qc-report.json",
102
+ "abstract-delta-ticket"
103
+ ],
104
+ "blocked_material": [
105
+ "source excerpts",
106
+ "raw diffs",
107
+ "private identifiers",
108
+ "source-shaped pseudocode",
109
+ "contaminated ledgers in clean inputs"
110
+ ],
111
+ "report_back_to": "agent_0"
112
+ }
113
+ },
114
+ "required_profiles": [
115
+ {
116
+ "role": "contaminated-manager-verifier",
117
+ "profile_id": "contaminated-manager-profile",
118
+ "workspace_id": "source-workspace"
119
+ },
120
+ {
121
+ "role": "contaminated-source-analyst",
122
+ "profile_id": "contaminated-analyst-profile",
123
+ "workspace_id": "source-workspace"
124
+ },
125
+ {
126
+ "role": "clean-architect",
127
+ "profile_id": "clean-architect-profile",
128
+ "workspace_id": "clean-workspace"
129
+ },
130
+ {
131
+ "role": "clean-qa-editor",
132
+ "profile_id": "clean-qa-profile",
133
+ "workspace_id": "clean-workspace"
134
+ }
135
+ ],
136
+ "artifact_paths": {
137
+ "contaminated_artifacts": "contaminated-artifacts/",
138
+ "contaminated_artifact_roots": [
139
+ "contaminated-artifacts/"
140
+ ],
141
+ "clean_artifacts": "clean-artifacts/",
142
+ "quarantine": "quarantine/"
143
+ },
144
+ "handoff_policy": {
145
+ "allowed_artifacts": [
146
+ "behavior-spec",
147
+ "handoff-package",
148
+ "abstract-delta-ticket"
149
+ ],
150
+ "blocked_material": [
151
+ "source excerpts",
152
+ "raw diffs",
153
+ "private identifiers"
154
+ ],
155
+ "one_way_handoff": true,
156
+ "notes": "Only structured clean-approved artifacts cross the wall."
157
+ },
158
+ "tool_policy": [
159
+ "Run source indexing only as controller preflight before clean-room role sessions.",
160
+ "Use read-only source tools in contaminated roles.",
161
+ "Use write tools only in contaminated artifact roots for contaminated roles.",
162
+ "Use write tools only in clean spec workspace for clean roles."
163
+ ],
164
+ "model_policy": [
165
+ "Use separate profiles for contaminated and clean roles."
166
+ ],
167
+ "retention_policy": "Retain clean artifacts; keep contaminated evidence separate.",
168
+ "roles": [
169
+ {
170
+ "role": "contaminated-manager-verifier",
171
+ "trust_domain": "contaminated",
172
+ "allowed_access": [
173
+ "source-workspace",
174
+ "contaminated-artifacts"
175
+ ]
176
+ },
177
+ {
178
+ "role": "contaminated-source-analyst",
179
+ "trust_domain": "contaminated",
180
+ "allowed_access": [
181
+ "source-workspace",
182
+ "contaminated-artifacts"
183
+ ]
184
+ },
185
+ {
186
+ "role": "clean-architect",
187
+ "trust_domain": "clean",
188
+ "allowed_access": [
189
+ "clean-workspace"
190
+ ]
191
+ },
192
+ {
193
+ "role": "clean-qa-editor",
194
+ "trust_domain": "clean",
195
+ "allowed_access": [
196
+ "clean-workspace"
197
+ ]
198
+ }
199
+ ],
200
+ "units": [
201
+ {
202
+ "unit_id": "unit-example-flow",
203
+ "description": "Example observable flow.",
204
+ "status": "pending",
205
+ "source_index_refs": [
206
+ "source-index:batch-0001"
207
+ ],
208
+ "notes": "Neutral unit id."
209
+ }
210
+ ],
211
+ "expected_artifacts": [
212
+ "source-index.json",
213
+ "behavior-spec.json",
214
+ "skeleton-manifest.json",
215
+ "qc-report.json"
216
+ ],
217
+ "audit_log_refs": [
218
+ "audit-log:example"
219
+ ]
220
+ }
@@ -0,0 +1,92 @@
1
+ # Leakage Rules
2
+
3
+ ## Invariant
4
+
5
+ Only unprotected functional behavior and compatibility requirements should cross into the clean workspace. Source expression, source-shaped design, and contaminated context must stay out.
6
+
7
+ ## Never Cross
8
+
9
+ Block these from clean artifacts:
10
+
11
+ - raw source files
12
+ - copied source excerpts
13
+ - raw diffs
14
+ - copied comments
15
+ - decompiled output
16
+ - private package, module, class, helper, method, function, variable, constant, or field names
17
+ - source-only file layout
18
+ - distinctive internal identifiers
19
+ - implementation-shaped pseudocode
20
+ - stack traces containing source lines
21
+ - unique log messages, UI copy, or strings not needed for compatibility
22
+ - formatting, ordering, or naming that mirrors source without public-contract need
23
+
24
+ ## Allowed With Care
25
+
26
+ Allow these only when needed for compatibility or testing:
27
+
28
+ - public API names
29
+ - command names and flags
30
+ - documented config keys
31
+ - public protocol fields
32
+ - public file formats
33
+ - externally visible error codes
34
+ - interoperability-relevant strings
35
+
36
+ When keeping a public name, record why it is compatibility-relevant.
37
+
38
+ ## Identifier Boundary
39
+
40
+ Treat implementation identifiers as contaminated by default. Package names, namespace names, module paths, class names, method names, function names, variable names, constants, fields, and internal event names must not appear in clean specs unless they are public compatibility surface.
41
+
42
+ Public compatibility surface means the name is externally documented, required by an existing integration, visible in a public protocol or file format, or explicitly required by the destination scope. If a name is retained, place it in `public_surface` or `public_contracts` with `name`, `kind`, `visibility`, and a concrete compatibility reason. Valid `visibility` values are `public`, `destination`, `protocol`, and `user-required`. Do not mention source-private names in summaries, claims, tests, open questions, skeleton areas, QC findings, or delta tickets.
43
+
44
+ The contaminated side should maintain a private identifier denylist for guardrail scanning when practical. The denylist is line-oriented, ignores blank lines and `#` comments, and is bounded to 1,000,000 bytes per file, 20,000 total terms, and 512 characters per term. Keep that list out of clean-role readable roots and do not paste its contents into clean artifacts or model-visible reports.
45
+
46
+ ## Rewrite Pattern
47
+
48
+ Convert source-adjacent observations into neutral requirements:
49
+
50
+ - Bad: "Function `parseFooInternal` checks `if x == 7` then calls `retryLater`."
51
+ - Good: "When input mode is unsupported, the component rejects the request before persistence and exposes a retryable error."
52
+ - Bad: "Copy this loop structure."
53
+ - Good: "Process entries in input order and stop after the first validation failure."
54
+
55
+ ## Review Checklist
56
+
57
+ Before clean handoff, confirm:
58
+
59
+ - No copied source text remains.
60
+ - No source code block remains.
61
+ - No private helper or file names remain unless justified as public compatibility.
62
+ - No private package, module, class, function, method, variable, constant, or field names remain.
63
+ - No algorithm description is more specific than required by observable behavior.
64
+ - No formatting, ordering, or naming mirrors source by default.
65
+ - Every claim has an evidence status.
66
+ - Every retained public name has a compatibility reason.
67
+ - Every uncertain behavior is marked as an open question.
68
+
69
+ ## Contamination Response
70
+
71
+ If clean work receives blocked material:
72
+
73
+ 1. Stop clean processing for the affected artifact.
74
+ 2. Mark the artifact contaminated.
75
+ 3. Remove it from the clean workspace or quarantine it outside the clean artifact set.
76
+ 4. Regenerate a scrubbed artifact from the contaminated side.
77
+ 5. Record the incident in `qc-report.json` and, when useful, a standalone `contamination-incident.json`.
78
+
79
+ Do not try to "forget" source material inside the same clean context and continue.
80
+
81
+ ## Guardrail Scripts
82
+
83
+ Use hook scripts as audit and guardrail support, not as the only boundary:
84
+
85
+ - `hooks/deny-clean-source-read.py`: denies clean-role reads from `CLEAN_ROOM_SOURCE_ROOTS` and any path outside `CLEAN_ROOM_CLEAN_ROOTS` plus `CLEAN_ROOM_ALLOWED_READ_ROOTS`.
86
+ - `hooks/deny-contaminated-clean-write.py`: enforces write roots. Clean roles may write only under `CLEAN_ROOM_CLEAN_ROOTS`; contaminated roles may write only under `CLEAN_ROOM_CONTAMINATED_ARTIFACT_ROOTS`.
87
+ - `hooks/check-artifact-leakage.py`: scans clean artifacts for high-risk leakage markers, obvious source-like identifiers, and terms from optional `CLEAN_ROOM_PRIVATE_IDENTIFIER_DENYLIST` files.
88
+ - `hooks/validate-json-schema.py`: checks JSON syntax and common bundled schema constraints, including the conditional and bounded fields used by these schemas. It is not a full JSON Schema 2020-12 validator.
89
+ - `hooks/require-clean-room-env.py`: fails closed when the role, root, or schema environment block is missing.
90
+ - `hooks/deny-clean-room-shell.py`: denies shell-style tools for clean-room role sessions because shell reads can bypass path-aware hooks.
91
+
92
+ Set `CLEAN_ROOM_ROLE`, `CLEAN_ROOM_SOURCE_ROOTS`, `CLEAN_ROOM_CONTAMINATED_ARTIFACT_ROOTS`, `CLEAN_ROOM_CLEAN_ROOTS`, `CLEAN_ROOM_ALLOWED_READ_ROOTS`, and `CLEAN_ROOM_SCHEMA_DIR` explicitly before running hooks. Set `CLEAN_ROOM_PRIVATE_IDENTIFIER_DENYLIST` when the contaminated side has produced a private identifier list for hook-only scanning.