claude-dev-env 2.5.0 → 2.7.1

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 (153) hide show
  1. package/CLAUDE.md +20 -57
  2. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -1
  3. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +12 -2
  4. package/_shared/pr-loop/scripts/code_rules_gate_parts/baseline_import_isolation.py +309 -0
  5. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_regression.py +540 -0
  6. package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +206 -70
  7. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/__init__.py +1 -0
  8. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/_repo_test_helpers.py +76 -0
  9. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_baseline_import_isolation.py +248 -0
  10. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_regression.py +309 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +91 -58
  12. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +202 -0
  13. package/_shared/pr-loop/worker-spawn.md +3 -1
  14. package/agents/CLAUDE.md +1 -1
  15. package/agents/code-verifier.md +36 -7
  16. package/bin/codex-compat.mjs +104 -0
  17. package/bin/codex-compat.test.mjs +51 -0
  18. package/codex-capability-map.json +13 -0
  19. package/docs/CODE_RULES.md +2 -0
  20. package/docs/codex-compatibility.md +25 -0
  21. package/docs/nas-ssh-invocation.md +96 -12
  22. package/docs/references/code-review-enforcement.md +31 -6
  23. package/hooks/blocking/CLAUDE.md +3 -0
  24. package/hooks/blocking/config/code_review_enforcement_constants.py +40 -10
  25. package/hooks/blocking/config/test_code_review_enforcement_constants.py +56 -3
  26. package/hooks/blocking/eli11_reply_enforcer.py +479 -0
  27. package/hooks/blocking/gh_body_arg_blocker.py +1 -1
  28. package/hooks/blocking/nas_ssh_binary_enforcer.py +8 -46
  29. package/hooks/blocking/shell_substitution_blocker.py +129 -0
  30. package/hooks/blocking/state_description_blocker.py +1 -1
  31. package/hooks/blocking/stop_dispatcher.py +1 -1
  32. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +2 -3
  33. package/hooks/blocking/test_eli11_reply_enforcer.py +457 -0
  34. package/hooks/blocking/test_shell_substitution_blocker.py +124 -0
  35. package/hooks/blocking/test_stop_dispatcher.py +23 -0
  36. package/hooks/blocking/test_unscoped_search_blocker.py +102 -0
  37. package/hooks/blocking/test_verdict_directory_write_blocker.py +4 -8
  38. package/hooks/blocking/unscoped_search_blocker.py +391 -0
  39. package/hooks/git-hooks/CLAUDE.md +3 -0
  40. package/hooks/git-hooks/conftest.py +30 -0
  41. package/hooks/git-hooks/gate_utils.py +2 -2
  42. package/hooks/git-hooks/git_hooks_constants/__init__.py +41 -2
  43. package/hooks/git-hooks/pre_push.py +75 -4
  44. package/hooks/git-hooks/pre_push_base_reference.py +166 -0
  45. package/hooks/git-hooks/test_config.py +0 -15
  46. package/hooks/git-hooks/test_gate_utils.py +3 -15
  47. package/hooks/git-hooks/test_pre_commit.py +1 -15
  48. package/hooks/git-hooks/test_pre_push.py +236 -27
  49. package/hooks/git-hooks/test_pre_push_base_reference.py +339 -0
  50. package/hooks/hooks.json +0 -12
  51. package/hooks/hooks_constants/CLAUDE.md +5 -1
  52. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +4 -4
  53. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +101 -0
  54. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +2 -8
  55. package/hooks/hooks_constants/shell_command_segments.py +82 -0
  56. package/hooks/hooks_constants/shell_substitution_blocker_constants.py +67 -0
  57. package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -0
  58. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +5 -6
  59. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +1 -0
  60. package/hooks/hooks_constants/unscoped_search_blocker_constants.py +153 -0
  61. package/package.json +4 -2
  62. package/rules/CLAUDE.md +17 -23
  63. package/rules/agent-spawn-protocol.md +6 -6
  64. package/rules/anti-corollary-tests.md +1 -1
  65. package/rules/bdd.md +1 -1
  66. package/rules/cleanup-temp-files.md +10 -4
  67. package/rules/code-standards.md +7 -0
  68. package/rules/conservative-action.md +1 -5
  69. package/rules/context7.md +0 -4
  70. package/rules/destructive-commands.md +47 -0
  71. package/rules/doc-inventory-integrity.md +48 -0
  72. package/rules/doc-prose-cuts.md +58 -0
  73. package/rules/docstring-prose-matches-implementation.md +10 -2
  74. package/rules/durable-post-artifacts.md +0 -4
  75. package/rules/eli11-replies.md +31 -0
  76. package/rules/explore-thoroughly.md +4 -4
  77. package/rules/falsify-before-green.md +68 -0
  78. package/rules/file-global-constants.md +1 -1
  79. package/rules/filesystem-search.md +51 -0
  80. package/rules/gh-cli-conventions.md +27 -0
  81. package/rules/git-workflow.md +26 -0
  82. package/rules/hedging-claims.md +9 -0
  83. package/rules/long-horizon-autonomy.md +0 -4
  84. package/rules/measurement-denominators.md +48 -0
  85. package/rules/nas-ssh-invocation.md +23 -5
  86. package/rules/parallel-tools.md +2 -2
  87. package/rules/plain-illustrative-docstrings.md +3 -7
  88. package/rules/plain-language.md +2 -0
  89. package/rules/proof-of-work-pr-comments.md +0 -4
  90. package/rules/re-stage-before-commit.md +2 -0
  91. package/rules/research-mode.md +10 -0
  92. package/rules/shell-invocation.md +21 -0
  93. package/rules/testing.md +4 -0
  94. package/rules/verified-commit-gate-skip.md +3 -27
  95. package/rules/verify-before-asking.md +5 -0
  96. package/rules/windows-filesystem-safe.md +1 -1
  97. package/rules/workers-done-before-complete.md +4 -0
  98. package/scripts/CLAUDE.md +3 -3
  99. package/scripts/Migrate-ShellPolicy.ps1 +1 -1
  100. package/scripts/codex_capability_bridge.py +171 -0
  101. package/scripts/codex_compat_materializer.py +1087 -0
  102. package/scripts/codex_compat_watcher.py +502 -0
  103. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -1
  104. package/scripts/dev_env_scripts_constants/code_review_constants.py +37 -0
  105. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +79 -13
  106. package/scripts/grok_headless_runner.py +213 -16
  107. package/scripts/invoke_code_review.py +11 -4
  108. package/scripts/resolve_worker_spawn.py +56 -10
  109. package/scripts/spawn_grok_batch.py +43 -22
  110. package/scripts/sync_to_cursor/rules.py +0 -10
  111. package/scripts/test_grok_headless_runner.py +592 -10
  112. package/scripts/test_invoke_code_review.py +143 -0
  113. package/scripts/test_invoke_code_review_chain.py +1 -1
  114. package/scripts/test_invoke_code_review_contract.py +1 -1
  115. package/scripts/test_resolve_worker_spawn.py +179 -15
  116. package/scripts/test_spawn_grok_batch.py +225 -22
  117. package/scripts/tests/test_code_review_constants.py +80 -0
  118. package/scripts/tests/test_codex_capability_bridge.py +91 -0
  119. package/scripts/tests/test_codex_compat_materializer.py +632 -0
  120. package/scripts/tests/test_codex_compat_watcher.py +599 -0
  121. package/scripts/tests/test_sync_to_cursor.py +0 -1
  122. package/skills/autoconverge/workflow/converge.contract.test.mjs +28 -6
  123. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +73 -0
  124. package/skills/autoconverge/workflow/converge.mjs +64 -15
  125. package/skills/bugteam/reference/copilot-gap-analysis.md +1 -1
  126. package/skills/condensing-instructions/SKILL.md +42 -51
  127. package/skills/fresh-branch/CLAUDE.md +1 -1
  128. package/skills/fresh-branch/SKILL.md +5 -6
  129. package/skills/fresh-branch/scripts/create_fresh_branch.py +42 -24
  130. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -3
  131. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +30 -126
  132. package/skills/grok-spawn/SKILL.md +5 -3
  133. package/skills/grok-spawn/reference/flag-profiles.md +3 -1
  134. package/skills/orchestrator/SKILL.md +23 -9
  135. package/skills/orchestrator-refresh/SKILL.md +20 -1
  136. package/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
  137. package/skills/session-log/SKILL.md +1 -1
  138. package/rules/claude-md-orphan-file.md +0 -28
  139. package/rules/cleanup-command-forms.md +0 -23
  140. package/rules/code-reviews.md +0 -11
  141. package/rules/env-var-table-code-drift.md +0 -10
  142. package/rules/gh-body-file.md +0 -5
  143. package/rules/gh-paginate.md +0 -3
  144. package/rules/hook-prose-matches-detector.md +0 -15
  145. package/rules/no-historical-clutter.md +0 -26
  146. package/rules/no-inline-destructive-literals.md +0 -9
  147. package/rules/no-justification-noise.md +0 -61
  148. package/rules/package-inventory-stale-entry.md +0 -25
  149. package/rules/right-sized-engineering.md +0 -28
  150. package/rules/self-contained-docs.md +0 -17
  151. package/rules/shell-invocation-policy.md +0 -5
  152. package/rules/state-what-is.md +0 -25
  153. package/rules/tdd.md +0 -7
@@ -40,3 +40,29 @@ User-level rule: applies to **every** git repo that uses GitHub with `gh` (no ex
40
40
  | `*.plan.md` | Temporary planning files |
41
41
  | `SESSION_STATE.md` | Local session state |
42
42
  | `*.png *.jpg *.jpeg *.gif *.webp *.avif *.svg *.ico` | Images go to external storage, not GitHub |
43
+
44
+ An image a PR needs as visual evidence is not an exception to that row. Upload it to the repository's durable `artifacts` release with `python3 ~/.claude/scripts/gh_artifact_upload.py <file> <owner/repo>` and embed the permanent URL in the proof comment. The image lives on GitHub without entering the repository tree.
45
+
46
+ ## Responding to review feedback
47
+
48
+ **When this applies:** GitHub PR review feedback on a branch you are fixing.
49
+
50
+ 1. Fetch every reviewer comment before making any fix.
51
+ 2. Create a TodoWrite checklist with one item per comment.
52
+ 3. Fix systematically, marking each todo complete.
53
+ 4. Reply to each comment inline.
54
+ 5. Create one review-fix commit. Do not squash it with the original.
55
+
56
+ Repair only reported findings, then re-verify after every repair.
57
+
58
+ ## See also
59
+
60
+ | Rule | Covers |
61
+ |---|---|
62
+ | [`gh-cli-conventions.md`](gh-cli-conventions.md) | `--body-file` for post bodies; `--paginate --slurp` for list reads |
63
+ | [`proof-of-work-pr-comments.md`](proof-of-work-pr-comments.md) | The five-part proof comment every PR carries before leaving draft |
64
+ | [`re-stage-before-commit.md`](re-stage-before-commit.md) | Staging session edits so a commit does not drop them |
65
+ | [`verified-commit-gate-skip.md`](verified-commit-gate-skip.md) | Optional code-verifier review guidance |
66
+ | [`durable-post-artifacts.md`](durable-post-artifacts.md) | Keeping volatile scratch paths out of a post body |
67
+ | [`destructive-commands.md`](destructive-commands.md) | Allowed removal forms; destructive literals in commit and post bodies |
68
+ | [`code-standards.md`](code-standards.md) | The code standards a PR's diff is reviewed against |
@@ -0,0 +1,9 @@
1
+ # Hedging Claims
2
+
3
+ State a claim with the evidence that backs it, or name the claim unverified. A hedge word stands in for evidence you did not gather.
4
+
5
+ `hedging_language_blocker` (Stop hook, hosted by `stop_dispatcher`) blocks a response carrying one — `likely`, `probably`, `presumably`, `perhaps`, `possibly`, `seemingly`, `apparently`, `arguably`, `supposedly`, `ostensibly`, `conceivably`, `plausibly`, `unlikely`, `probable`, and the phrases `might be`, `could be`, `seems to be`, `appears to be`, `in all likelihood`, `more likely than not`, `it's possible that`.
6
+
7
+ Deleting the hedge word and keeping the claim does not clear the block. Gather the source, run the probe, or ask the user through `AskUserQuestion` — then re-output the whole revised response.
8
+
9
+ Sibling rules: [`research-mode.md`](research-mode.md) names what counts as a citation; [`verify-runtime-state.md`](verify-runtime-state.md) names the live probe a runtime verdict rests on.
@@ -37,7 +37,3 @@ Terse shorthand between tool calls is fine — that is you thinking. The final m
37
37
  ## Keep going on context
38
38
 
39
39
  A remaining-context or token count is not a reason to stop. Do not pause, summarize, or float a fresh session on account of context limits; keep working. When the user must see content word-for-word (a partial deliverable, a direct answer to a mid-run question), surface it through the channel the harness gives for that, not by ending the turn.
40
-
41
- ## Why
42
-
43
- A capable model under-delivers on long runs for predictable reasons: it overplans when it could act, stops on a promise, blocks on subagents, skips its own verification, fabricates progress, buries the result in working shorthand, or quits early over a context count. Each section above removes one of those failure modes so the run finishes.
@@ -0,0 +1,48 @@
1
+ ---
2
+ paths:
3
+ - "**/*.md"
4
+ - "**/*.py"
5
+ ---
6
+
7
+ # Measurement Denominators
8
+
9
+ **When this applies:** Any Write or Edit that states a count, rate, or coverage figure in a docstring or a `.md` file.
10
+
11
+ ## Rule
12
+
13
+ Every count names what it scanned. "Read 10 of 10 changed files" states the scan and the whole set it was drawn from. "Swept the files" states neither, and a reader takes it as full coverage of a scan that touched a fraction.
14
+
15
+ Three parts travel with each figure. The report author writes part 1 for every figure and part 2 when the figure is a rate; the consumer runs part 3:
16
+
17
+ 1. **A count carries its denominator.** Name both numbers: files read of files changed, rounds fired of rounds available, tests reached of tests collected, entries compared of entries present.
18
+ 2. **A rate needs two runs and a stated denominator.** One run yields a count. Written down as a rate — "fires 40% of the time" — it claims a spread across runs that one run cannot back. Report the count and the run that produced it until a second run exists; once it does, the rate stays counts across runs — "failed twice across 5 runs on this branch" — never a percentage.
19
+ 3. **The consumer cross-checks the count against the scope its denominator names.** A sweep reporting 3 files read of 10 changed fails coverage: its verdict reaches 3 of the 10 files its own denominator names. A sweep reporting 3 of 3 security-sensitive files states its own scope and covers all of it, so it passes. That comparison settles the sweep before anyone reads its result.
20
+
21
+ A number written into a docstring or a doc carries its denominator beside it. A number written there as a rate carries its run count too.
22
+
23
+ ## Shapes
24
+
25
+ | Bare figure | With its denominator |
26
+ |---|---|
27
+ | swept the files | read 10 of 10 changed files |
28
+ | all tests pass | 412 of 412 collected tests pass, 0 skipped |
29
+ | the hook catches this | the hook denies 6 of 6 recorded shapes |
30
+ | fails about 40% of the time | failed twice across 5 runs on this branch |
31
+ | most entries match | 88 of 90 entries match |
32
+
33
+ ## Cross-check at the consumer
34
+
35
+ A sweep reports "read 3 files". The change under review touches 10 files. The arithmetic reads: the sweep covered 3 of 10, so its clean verdict covers 3 of 10. The gap is the finding — send the sweep back for the other 7, and hold the verdict until the two numbers meet.
36
+
37
+ ## Sibling rules
38
+
39
+ | Rule | Role |
40
+ |---|---|
41
+ | `falsify-before-green.md` | A check's green counts once the check was shown red |
42
+ | `anti-corollary-tests.md` | Each test carries information; no corollary matrices; no suite that only matches the dead default |
43
+ | `docstring-prose-matches-implementation.md` | A docstring enumeration covers every behavior the body applies |
44
+ | `measurement-denominators.md` | Every count names what it scanned; a rate needs two runs |
45
+
46
+ ## Enforcement
47
+
48
+ This rule binds as prose discipline: a reviewer applies it to the counts a PR's prose and docstrings state, and to the counts a report claims. No hook backs it, because weighing a figure's denominator against the scope it covers needs meaning a regex cannot read.
@@ -1,11 +1,29 @@
1
- # NAS SSH Invocation Policy
1
+ # Running Commands on the NAS
2
2
 
3
- Reach the NAS through the Windows `System32/OpenSSH` binary with `-o BatchMode=yes` on every `ssh`, `scp`, or `sftp` command:
3
+ Reach the NAS through the `nas_ssh_key.py` runner, never through `ssh`, `scp` or `sftp`:
4
4
 
5
5
  ```
6
- "/c/Windows/System32/OpenSSH/ssh.exe" -o BatchMode=yes -o ConnectTimeout=10 -p 22 operator@nas.example.local "<cmd>"
6
+ python <runner-path> <command-script.sh> <private-key-path>
7
7
  ```
8
8
 
9
- Git Bash's MSYS `ssh` falls back to an interactive password prompt that hangs an unattended session; the `System32/OpenSSH` binary authenticates the key without a prompt, and `-o BatchMode=yes` turns an auth failure into a loud non-zero exit. `nas_ssh_binary_enforcer.py` (PreToolUse on Bash) enforces this: it denies a bare ssh-family word aimed at the NAS, and denies the full binary when `-o BatchMode=yes` is absent.
9
+ The first argument is a **path to a file** holding a bash script, not a command string. Write that
10
+ file with the Write tool. The whole script runs in one shell, so variables, `cd` and `source` carry
11
+ from line to line.
10
12
 
11
- Host, user, and port config, the `scp`/`sftp` forms, and the full rationale: `@~/.claude/docs/nas-ssh-invocation.md`.
13
+ The runner ships with the automation project it serves; read its path from that project's own
14
+ reference. Use the ops key under `~/.claude/keys` — keys under `~/.ssh` either carry a passphrase,
15
+ which cannot be answered unattended, or belong to other hosts.
16
+
17
+ The runner loads the key with paramiko and signs in the same process. The command-line clients check
18
+ the key file's permissions first and refuse it, and Git Bash's `ssh` then falls back to a password
19
+ prompt that hangs an unattended run. `nas_ssh_binary_enforcer.py` (PreToolUse on Bash) denies a bare
20
+ ssh-family word aimed at the NAS, and denies the full `System32/OpenSSH` binary when
21
+ `-o BatchMode=yes` is missing.
22
+
23
+ Host, ssh port and ssh user are constants inside the runner and are also under the `nas` key in
24
+ `~/.claude/local-identity.json`. Keep all three out of anything committed or posted.
25
+
26
+ To copy a file, write it inside the script with a quoted heredoc rather than reaching for `scp`.
27
+
28
+ The `/tmp` size limit, how to tell a real break from a platform-only one, and a failure-to-cause
29
+ table: `@~/.claude/docs/nas-ssh-invocation.md`.
@@ -18,6 +18,6 @@ When multiple tool calls have no dependencies between them, make all independent
18
18
  - Use real parameter values only. Do not guess or use placeholders to force parallelism.
19
19
  - If you are unsure whether calls are independent, run them sequentially.
20
20
 
21
- ## Why
21
+ ## The one surface that serializes
22
22
 
23
- Explicit reinforcement of parallel calling boosts compliance to near 100%. Sequential calls for independent operations waste time and round-trips for the user.
23
+ Harness tool calls Read, Grep, Glob, and the rest carry no shared cost and parallelize freely. A large shell filesystem walk does not: parallel full-tree searches contend for the shell and can lock the host, so issue those one at a time. See [`filesystem-search.md`](filesystem-search.md).
@@ -48,7 +48,7 @@ cannot tell which name states the truth. Rename the callee to a neutral form
48
48
  the two names agree on at every call site.
49
49
  ```
50
50
 
51
- The live version of this docstring sits on `check_polarity_name_contradiction` in `packages/claude-dev-env/hooks/blocking/code_rules_naming_collection.py`.
51
+ The live version of this docstring sits on `check_polarity_name_contradiction` in `~/.claude/hooks/blocking/code_rules_naming_collection.py`.
52
52
 
53
53
  A short narrative with no diagram is fine when a couple of plain sentences carry the whole picture. The diagram earns its place once the explanation grows past what two or three lines hold — the moment a wall of prose starts to form.
54
54
 
@@ -86,10 +86,6 @@ one — and you're debugging blind.
86
86
 
87
87
  Two surfaces carry this standard:
88
88
 
89
- - **Hook (the run-on backstop).** `check_docstring_runon_sentence` in `packages/claude-dev-env/hooks/blocking/code_rules_docstrings.py` flags the one mechanical mark of a wall: a single narrative sentence that is both over the word limit and joined by an em-dash or a semicolon. A hook cannot judge whether prose paints a picture, so it catches only this structural mark. It reads the narrative through a shared partition that sets aside any `::` literal block and any doctest, so a diagram's own arrows and dashes never count against the sentence.
89
+ - **Hook (the run-on backstop).** `check_docstring_runon_sentence` in `~/.claude/hooks/blocking/code_rules_docstrings.py` flags the one mechanical mark of a wall: a single narrative sentence that is both over the word limit and joined by an em-dash or a semicolon. A hook cannot judge whether prose paints a picture, so it catches only this structural mark. It reads the narrative through a shared partition that sets aside any `::` literal block and any doctest, so a diagram's own arrows and dashes never count against the sentence.
90
90
  - **Hook (the prose-wall backstop).** `check_docstring_prose_wall_without_illustration` in the same module flags a narrative that runs more than six prose lines with no diagram block. It marks the wall so the writer shows the behavior with a `::` example or a doctest and trims the prose to a few short lines. It cannot judge whether the diagram illustrates well; that stays with the audit lane.
91
- - **Audit (the judgment lane).** Category O sub-bucket O9 in `packages/claude-dev-env/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md` carries the illustrative-and-brief judgment the hook cannot. The audit teammate reads each changed docstring's narrative and asks whether a general developer follows it on the first read.
92
-
93
- ## Why
94
-
95
- A docstring earns its place by saving the reader a trip into the body. A wall of stacked machinery nouns costs more to read than the code it describes, so the reader skips it and the docstring becomes dead weight. Prose that paints a concrete scene — the moment, the input, the outcome — lets a reader reason about the code without reading it. Naming this standard makes the wall a finding at write time and at audit, rather than a slow defect a reader meets months later.
91
+ - **Audit (the judgment lane).** Category O sub-bucket O9 in `~/.claude/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md` carries the illustrative-and-brief judgment the hook cannot. The audit teammate reads each changed docstring's narrative and asks whether a general developer follows it on the first read.
@@ -4,4 +4,6 @@ All prose a person reads (chat, `AskUserQuestion`, docs, PR/issue bodies, commit
4
4
 
5
5
  The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit) blocks a heavy word and names the everyday swap; code fences, inline code, blockquotes, URLs, and file paths are skipped.
6
6
 
7
+ [`eli11-replies`](eli11-replies.md) governs reply length and shape; this rule governs word choice.
8
+
7
9
  A project can keep its own domain words out of the check with a `.claude/plain-language-allow.json` file: a JSON array of terms. An exact, case-insensitive, whole-word match on any term passes. The hook reads this file only from inside the project tree, up to the repository root, so each project's allowlist stays with its own code.
@@ -20,7 +20,3 @@ The `pr_description_enforcer` hook enforces the standard at two points:
20
20
  - **On `gh pr ready`:** the hook reads the PR's comments and blocks readying while no comment passes the audit. `gh pr ready --undo` returns a PR to draft and is never blocked.
21
21
 
22
22
  A `gh` failure (network, auth, missing executable) never blocks — the gate fails open on tooling problems, and the comment audit skips bodies it cannot read.
23
-
24
- ## Why
25
-
26
- A PR body says what changed; the proof comment shows that it worked. Real command output, measured numbers, and a rendered image let a reviewer check the claim in seconds, with no need to re-run the work. Stating the gaps keeps the proof honest: the reviewer knows exactly what still rests on trust and where that is covered. Gating draft-to-ready makes the comment land before review starts, on every machine, whatever the session's habits.
@@ -4,6 +4,8 @@ Stage the files you edited this session right before you commit them. A plain `g
4
4
 
5
5
  `session_edit_stage_gate` (PreToolUse on Bash `git commit`) denies a commit that would drop tracked session edits and names the fix: `git add <paths>`, `git commit -a`, or a `# partial-commit` marker.
6
6
 
7
+ Staging covers tracked files you edited. Do not commit untracked files unless the user explicitly instructs it — an untracked file in the working tree is outside the change until they say otherwise.
8
+
7
9
  ## Escapes the denial does not restate
8
10
 
9
11
  - **A pathspec** — `git commit -- <paths>` or `git commit <paths>` commits only the named paths on purpose and steps the gate aside.
@@ -19,5 +19,15 @@ If you generate a claim and cannot find a supporting source, retract it. Do not
19
19
  ## 3. Direct quotes for factual grounding
20
20
  When working from documents, extract the actual text first before analyzing. Ground your response in word-for-word quotes, not paraphrased summaries. Reference the quote when making your point.
21
21
 
22
+ ## How citations appear in a chat reply
23
+
24
+ The grounding requirement above never relaxes: state no claim you cannot source. What changes with the channel is how much of the source you print. A chat reply carries the source in compact form — a linked source name, or a `file:line` reference. Word-for-word quotes and full citation lists belong in artifacts, PR bodies, and issue bodies, or in a reply when the user asks for them.
25
+
22
26
  ## Exceptions
23
27
  Creative thinking, brainstorming, and novel ideas don't require citation. You can synthesize across sources to reach new conclusions, but the inputs must be grounded.
28
+
29
+ ## Sibling rules
30
+
31
+ - [`explore-thoroughly.md`](explore-thoroughly.md) — grounds an implementation plan in the codebase, as this rule grounds a factual claim in a source.
32
+ - [`verify-runtime-state.md`](verify-runtime-state.md) — a runtime verdict needs a live probe from this session, not a citation.
33
+ - [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches an unverified claim wearing a hedge word.
@@ -0,0 +1,21 @@
1
+ # Shell Invocation
2
+
3
+ Two constraints govern every shell command an agent issues: which shell runs it, and what the command string may contain.
4
+
5
+ ## Use pwsh
6
+
7
+ Every Bash-tool shell command on Windows uses `pwsh`: `pwsh -NoProfile -File '<script>.ps1' <args>` for scripts, `pwsh -NoProfile -Command "..."` (or a literal `@'...'@` here-string) for inline work, or the built-in `PowerShell` tool for pure-PowerShell workflows (it supports `run_in_background`). Never wrap a script path in `-Command "& '...'"` — `-File` keeps `permissions.allow` matching. The `&` call operator is fine for invoking an executable at a path (`& '<venv>\Scripts\python.exe' script.py`).
8
+
9
+ The mandate covers the shell a command runs through, not every executable a command names. A direct interpreter invocation another rule documents — the paramiko NAS helper in [`nas-ssh-invocation.md`](nas-ssh-invocation.md), a `python` call on a repo script — conforms as written.
10
+
11
+ Keep `powershell`, `powershell.exe`, `cmd /c`, and `bash -c` out of the `settings.json` permission rules. `Audit-ShellPolicy.ps1` reports those forms and `Migrate-ShellPolicy.ps1` rewrites them to `pwsh`. Both ship in the claude-dev-env repo at `packages/claude-dev-env/scripts/` and run on demand, not as a live gate.
12
+
13
+ ## No shell substitution
14
+
15
+ No `$(...)`, unescaped backticks, or `<(...)` / `>(...)` process substitution in Bash tool commands. The allowlist matcher reads the raw command string, so a substitution wrapper forces a permission prompt even when every inner segment is auto-allowed. Split into separate tool calls, or use flag forms like `git -C "<path>" rev-parse HEAD`. Arithmetic `$((...))` passes: it spawns no subshell.
16
+
17
+ When a script file's literal body needs `$(...)`, author it with the Write tool, not a Bash heredoc.
18
+
19
+ ## Enforcement
20
+
21
+ `shell_substitution_blocker.py` (PreToolUse on Bash, hosted by `bash_pre_tool_use_dispatcher`) denies a command carrying a live substitution and returns the split-into-two-calls rewrite. Single-quoted runs are stripped before the scan, and a backtick preceded by an odd number of backslashes is escaped, so an inert mention passes.
package/rules/testing.md CHANGED
@@ -18,3 +18,7 @@ paths:
18
18
 
19
19
  If a component renders field X, the mock must have field X with a valid value.
20
20
  Incomplete mocks make it impossible to distinguish "broken code" from "missing data".
21
+
22
+ ## Tests Exercise Real Behavior
23
+
24
+ Tests exercise real behavior, real data, and production code paths. A test that asserts on a stand-in for the production path proves the stand-in works.
@@ -1,28 +1,4 @@
1
- # Verified-Commit-Gate Skip Marker
1
+ # Optional Verifier Review
2
2
 
3
- **When this applies:** A `git commit` or `git push` through the Bash tool is blocked by the `verified_commit_gate` hook.
4
-
5
- ## The marker
6
-
7
- Appending the comment `# verify-skip` as a trailing shell comment to the Bash command exempts that single commit or push from the gate. The hook (`hooks/blocking/verified_commit_gate.py`, marker constant `VERIFICATION_BYPASS_MARKER`) recognizes the marker only when its leading `#` sits at a word boundary and outside every quoted region — a genuine comment, not a data-only mention inside a quoted commit message or `gh` body — and lets the command run without a minted verdict.
8
-
9
- ## When the marker is allowed
10
-
11
- Use it only when both hold:
12
-
13
- 1. The gate is blocking the command, and
14
- 2. The branch surface content is the same code a `code-verifier` already passed clean — nothing effectively changed since that clean verdict.
15
-
16
- That situation arises when the verdict fails to cover the current surface even though the code is the same: a clean verdict that never minted (the verifier's fenced block ran in a resumed turn, so the SubagentStop minter never fired), a surface hash perturbed by index-only staging churn, or a concurrent worktree write that was fully reverted back to the verified content. Before using the marker, confirm the match yourself: the test suite the verifier ran still passes, and the diff holds no content beyond what the clean verdict covered.
17
-
18
- ## When the marker is not allowed
19
-
20
- Every other case runs the verification: spawn the `code-verifier` agent — `model: sonnet`, worker-model routing per [`skills/orchestrator/SKILL.md`](../skills/orchestrator/SKILL.md#workflow-agent-routing); resolver-supplied sonnet-equivalent on third-party hosts — and let the SubagentStop hook mint the verdict. In particular, never use the marker to:
21
-
22
- - Skip a branch's first verification.
23
- - Commit or push after any real code change since the last clean verdict — one changed line means a fresh verification.
24
- - Work around a verifier that reported findings; findings get repaired and re-verified.
25
-
26
- ## One command, one exemption
27
-
28
- The marker exempts only the command that carries it. The next commit or push on the branch faces the gate again, so a follow-up change still verifies before it lands.
3
+ Code-verifier review is an optional workflow selected by the task owner.
4
+ Commit and push operations use the checks selected for the task.
@@ -47,3 +47,8 @@ Reserve user questions for:
47
47
 
48
48
  **Wrong:** "Is there a Prisma schema in this project?"
49
49
  **Right:** Glob for `schema.prisma` and check.
50
+
51
+ ## Parent rules
52
+
53
+ - [`ask-user-question-required.md`](ask-user-question-required.md) — once a question genuinely belongs to the user, this rule routes it through `AskUserQuestion`.
54
+ - [`verify-runtime-state.md`](verify-runtime-state.md) — the same duty applied to a verdict: gather the live probe before you conclude, not just before you ask.
@@ -14,4 +14,4 @@ In Node, call `mkdirSync(targetPath, { recursive: true })` on possibly-existing
14
14
 
15
15
  The `windows_rmtree_blocker.py` PreToolUse hook (Write/Edit/Bash) blocks the unsafe rmtree pattern and returns the full `force_rmtree` safe-pattern code.
16
16
 
17
- Define the safe handler trio (`_strip_read_only_and_retry`, `_force_remove_tree` / `force_rmtree`, and the `inspect.signature` onexc/onerror guard) once in a shared Windows-filesystem utility module, and import it from every call site. A second local copy drifts from the first — a fix lands in one and the other keeps the bug (CODE_RULES.md section 3, Reuse before create). The `duplicate_rmtree_helper_blocker.py` PreToolUse hook (Write/Edit) blocks a local re-definition of any trio member outside the shared home and points the writer at the import. This complements the same-directory `check_duplicate_function_body_across_files` gate, which a copy between two distant packages slips past.
17
+ Define the safe handler trio (`_strip_read_only_and_retry`, `_force_remove_tree` / `force_rmtree`, and the `inspect.signature` onexc/onerror guard) once in a shared Windows-filesystem utility module, and import it from every call site. A second local copy drifts from the first — a fix lands in one and the other keeps the bug (CODE_RULES.md, CORE PRINCIPLES — "Reuse before create"). The `duplicate_rmtree_helper_blocker.py` PreToolUse hook (Write/Edit) blocks a local re-definition of any trio member outside the shared home and points the writer at the import. This complements the same-directory `check_duplicate_function_body_across_files` gate, which a copy between two distant packages slips past.
@@ -2,4 +2,8 @@
2
2
 
3
3
  Before marking a task `completed` that spawned workers — subagents, workflow agents, or background shells — confirm both: every worker has finished, and each worker's result is merged into run state (`state.json`, `pr-converge-state.json`, the task list, or whatever record the task keeps). A worker still running, or one whose output never landed in run state, keeps the task `in_progress`: list the live workers, report any dead or hung one as a finding rather than dropping it in silence, and schedule a wakeup so the run picks the workers back up before the task closes.
4
4
 
5
+ This rule gates a task's status, not your own work. It never says wait before acting: keep working while a worker runs, and hold only the `completed` mark until the worker's result has landed.
6
+
7
+ Verify every sub-agent file list, count, description, and finding against the repository and the diff before you merge it into run state or repeat it to the user.
8
+
5
9
  Checklist, examples, and run-state detail: `@~/.claude/docs/worker-completion-gate.md`.
package/scripts/CLAUDE.md CHANGED
@@ -9,13 +9,13 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
9
9
  | `claude_chain_runner.py` | Runs a `claude` invocation through a usage-ranked fallback chain (`~/.claude/claude-chain.json`): probes weekly remaining once via `claude_chain_usage` / the usage-pause OAuth probe, tries the highest-remaining account first, and falls over to the next ranked binary only on a usage-limit failure; usable as an imported module (`run_claude`) or a CLI. Copy `claude-chain.example.json` to `~/.claude/claude-chain.json` and list your account binaries. Optional per-entry `credentials_path` names that account's OAuth credentials file for the usage probe |
10
10
  | `claude_chain_usage.py` | Reports remaining weekly usage for every account in `~/.claude/claude-chain.json` via the usage-pause OAuth probe; prints JSON (`accounts` with `weekly_remaining_percent` or null plus `error`); importable `report_chain_weekly_usage` and `rank_accounts_by_weekly_remaining` (highest remaining first, ties keep config order, unmeasurable last). The chain runner consumes this ranking for try order |
11
11
  | `gh_artifact_upload.py` | Uploads a file to a repo's durable `artifacts` prerelease under a timestamped asset name and prints the permanent download URL a GitHub post can link |
12
- | `grok_headless_runner.py` | Runs one worker as headless `grok`: builds argv, mints a unique leader socket, captures streams, kills on timeout with grace, classifies ok/usage_limit/auth_failure/timeout/error; imported by `spawn_grok_batch.py` |
12
+ | `grok_headless_runner.py` | Runs one worker as headless `grok`: builds argv with no turn cap (the timeout is the only bound), mints a unique leader socket, captures streams, refuses a timeout that is missing, below `MIN_WORKER_TIMEOUT_SECONDS`, or above the `MAXIMUM_WORKER_TIMEOUT_SECONDS` (5400) ceiling, kills the whole process tree on timeout with grace and retries the kill-and-drain round once, classifies ok/usage_limit/auth_failure/timeout/kill_failed/error; exports `require_timeout_within_bounds` so a dispatcher can apply the same bounds without launching; imported by `spawn_grok_batch.py` and `resolve_worker_spawn.py` |
13
13
  | `grok_worker_preflight.py` | Soft gate for the headless grok tier: binary on PATH, `grok models` auth, install manifest + role agents, opt-in cached live ping; non-zero exit is fallthrough, not failure |
14
14
  | `setup_project_paths.py` | One-time bootstrap: discovers git repos via `es.exe` (Everything) and writes `~/.claude/project-paths.json`; never hardcodes scan roots |
15
- | `spawn_grok_batch.py` | Launches a fleet of headless grok workers from a JSON batch spec: gates once through the preflight, assembles each prompt from part files, staggers starts, runs each through `grok_headless_runner.py`, and emits one batch summary JSON |
15
+ | `spawn_grok_batch.py` | Launches a fleet of headless grok workers from a JSON batch spec: gates once through the preflight, refuses a spec whose `timeout_seconds` exceeds `MAXIMUM_WORKER_TIMEOUT_SECONDS` (5400) rather than clamping it, assembles each prompt from part files, staggers starts, runs each through `grok_headless_runner.py`, and emits one batch summary JSON |
16
16
  | `sweep_empty_dirs.py` | Deletes empty directories older than a configurable age under a given root; runs once (`--once`) or in continuous-watch mode |
17
17
  | `sync_to_cursor.py` | Entry point for syncing Claude rules to Cursor `.mdc` files; delegates to the `sync_to_cursor/` package |
18
- | `resolve_worker_spawn.py` | Dispatches a worker role through grok then claude fallback tiers (preflight, headless grok, `claude_agent_required` handoff, optional claude headless); protocol: [`../_shared/pr-loop/worker-spawn.md`](../_shared/pr-loop/worker-spawn.md) |
18
+ | `resolve_worker_spawn.py` | Dispatches a worker role through grok then claude fallback tiers (preflight, headless grok, `claude_agent_required` handoff, optional claude headless); applies `require_timeout_within_bounds` before the preflight, so an out-of-bounds `--timeout-seconds` prints a `timeout_out_of_bounds` outcome and exits 3 on every tier; protocol: [`../_shared/pr-loop/worker-spawn.md`](../_shared/pr-loop/worker-spawn.md) |
19
19
 
20
20
  ## PowerShell scripts
21
21
 
@@ -9,7 +9,7 @@
9
9
  settings.local.json, and settings.local.json.template, and rewrites permission
10
10
  rule strings that invoke powershell / powershell.exe / bash -c / cmd /c
11
11
  into their pwsh equivalents per the migration mapping in
12
- rules/shell-invocation-policy.md. Defaults to dry-run; pass -Apply to write
12
+ rules/shell-invocation.md. Defaults to dry-run; pass -Apply to write
13
13
  changes. Prints exactly one summary line.
14
14
 
15
15
  .PARAMETER Roots
@@ -0,0 +1,171 @@
1
+ """Translate Claude capability requests into declarative Codex records."""
2
+
3
+ import argparse
4
+ import json
5
+ import re
6
+ import sys
7
+ from pathlib import Path
8
+
9
+
10
+ class CapabilityArgumentError(ValueError):
11
+ """Raised when bridge command-line input cannot be translated."""
12
+
13
+
14
+ class CapabilityArgumentParser(argparse.ArgumentParser):
15
+ """Parse bridge arguments while keeping failures inside the JSON contract."""
16
+
17
+ def error(self, message: str) -> None:
18
+ raise CapabilityArgumentError(f"invalid bridge arguments: {message}")
19
+
20
+
21
+ def _load_capability_map() -> dict[str, object]:
22
+ capability_map_path = Path(__file__).resolve().parents[1] / "codex-capability-map.json"
23
+ with capability_map_path.open(encoding="utf-8") as map_file:
24
+ return json.load(map_file)
25
+
26
+
27
+ def _build_error_record(source_surface: str, message: str) -> dict[str, object]:
28
+ return {"source": source_surface, "status": "error", "error": message}
29
+
30
+
31
+ def _parse_command_arguments() -> argparse.Namespace:
32
+ argument_parser = CapabilityArgumentParser()
33
+ argument_parser.add_argument("source_surface")
34
+ argument_parser.add_argument("payload_json", nargs="?")
35
+ return argument_parser.parse_args()
36
+
37
+
38
+ def _read_payload(payload_json: str) -> object:
39
+ try:
40
+ return json.loads(payload_json)
41
+ except json.JSONDecodeError as payload_error:
42
+ raise CapabilityArgumentError("payload must be valid JSON") from payload_error
43
+
44
+
45
+ def _capability_by_surface() -> dict[str, dict[str, object]]:
46
+ all_capabilities = _load_capability_map()["capabilities"]
47
+ capability_by_surface: dict[str, dict[str, object]] = {}
48
+ for each_capability in all_capabilities:
49
+ capability_by_surface[each_capability["source"].casefold()] = each_capability
50
+ for each_alias in each_capability["aliases"]:
51
+ capability_by_surface[each_alias.casefold()] = each_capability
52
+ return capability_by_surface
53
+
54
+
55
+ def _contains_unsafe_path(all_capability_payload: object) -> bool:
56
+ path_pattern = re.compile(
57
+ r"(?:^[A-Za-z]:[\\/]|^\\\\|^/|^~(?:[\\/]|$)|^(?:%[^%]+%|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)(?:[\\/]|$)|(?:^|[\\/])(?:Users|home|\.ssh|\.codex)(?:[\\/]|$)|(?:^|[\\/])\.\.(?:[\\/]|$))",
58
+ re.IGNORECASE,
59
+ )
60
+ if isinstance(all_capability_payload, str):
61
+ return bool(path_pattern.search(all_capability_payload))
62
+ if isinstance(all_capability_payload, dict):
63
+ return any(_contains_unsafe_path(each_payload_entry) for each_payload_entry in all_capability_payload.values())
64
+ if isinstance(all_capability_payload, list):
65
+ return any(_contains_unsafe_path(each_payload_entry) for each_payload_entry in all_capability_payload)
66
+ return False
67
+
68
+
69
+ def _status_by_source() -> dict[str, str]:
70
+ return {"pending": "pending", "queued": "pending", "in_progress": "in_progress", "in-progress": "in_progress", "active": "in_progress", "completed": "completed", "complete": "completed", "done": "completed"}
71
+
72
+
73
+ def _task_plan_entry(all_capability_payload: dict[str, object]) -> dict[str, str] | None:
74
+ task_name = all_capability_payload.get("subject", all_capability_payload.get("name", all_capability_payload.get("title")))
75
+ task_status = all_capability_payload.get("status", "pending")
76
+ status_by_source = _status_by_source()
77
+ if not isinstance(task_name, str) or not task_name.strip():
78
+ return None
79
+ if not isinstance(task_status, str) or task_status.casefold() not in status_by_source:
80
+ return None
81
+ return {"step": task_name, "status": status_by_source[task_status.casefold()]}
82
+
83
+
84
+ def _has_required_fields(all_capability_payload: dict[str, object], all_required_field_definitions: dict[str, str]) -> bool:
85
+ status_by_source = _status_by_source()
86
+ for each_required_field_name, each_required_field_schema in all_required_field_definitions.items():
87
+ required_field_content = all_capability_payload.get(each_required_field_name)
88
+ if each_required_field_schema == "string" and (not isinstance(required_field_content, str) or not required_field_content.strip()):
89
+ return False
90
+ if each_required_field_schema == "status" and (not isinstance(required_field_content, str) or required_field_content.casefold() not in status_by_source):
91
+ return False
92
+ return True
93
+
94
+
95
+ def _has_valid_plan(all_capability_payload: dict[str, object]) -> bool:
96
+ all_task_entries = all_capability_payload.get("plan")
97
+ return isinstance(all_task_entries, list) and all(isinstance(each_task_entry, dict) and _task_plan_entry(each_task_entry) is not None for each_task_entry in all_task_entries)
98
+
99
+
100
+ def translate_capability(source_surface: str, payload: object) -> dict[str, object]:
101
+ """Translate one capability request into a deterministic declarative record.
102
+
103
+ The dispatcher selects a mapped target, rejects unsafe or malformed payloads,
104
+ conditionally dispatches TaskList plans through ``_has_valid_plan``, and emits
105
+ manual-review records for unsupported scheduling requests.
106
+
107
+ Args:
108
+ source_surface: Claude capability name or configured alias.
109
+ payload: JSON-compatible request payload.
110
+
111
+ Returns:
112
+ A declarative translation record that never executes the target action.
113
+ """
114
+ capability = _capability_by_surface().get(source_surface.casefold())
115
+ if capability is None:
116
+ return {"source": source_surface, "status": "unknown", "error": "unknown source surface"}
117
+ if _contains_unsafe_path(payload):
118
+ return {"source": capability["source"], "status": "rejected", "error": "path-bearing payload is not allowed"}
119
+ if not isinstance(payload, dict):
120
+ return {"source": capability["source"], "status": "malformed", "error": "payload must be a JSON object"}
121
+ if capability["status"] == "unsupported":
122
+ return {"source": capability["source"], "target": None, "status": "unsupported", "manual_review": True, "error": "scheduling is unavailable"}
123
+ if capability["target"] == "update_plan":
124
+ if capability["source"] == "TaskList":
125
+ if not _has_valid_plan(payload):
126
+ return {"source": capability["source"], "status": "malformed", "error": "TaskList requires a plan of named tasks"}
127
+ return {"source": capability["source"], "target": capability["target"], "transformation": "snapshot", "payload": {"plan": payload["plan"]}, "manual_review": True, "status": "mapped"}
128
+ task_plan_entry = _task_plan_entry(payload)
129
+ if task_plan_entry is None:
130
+ return {"source": capability["source"], "status": "malformed", "error": "task requires a name and supported status"}
131
+ operation = "status mutation" if capability["source"] == "TaskUpdate" else "replace-plan-item" if payload.get("operation") == "replace" else "append"
132
+ return {"source": capability["source"], "target": capability["target"], "transformation": operation, "payload": {"plan": [task_plan_entry]}, "status": "mapped"}
133
+ all_required_field_definitions = capability.get("required_fields", {})
134
+ if not isinstance(all_required_field_definitions, dict) or not _has_required_fields(payload, all_required_field_definitions):
135
+ return {"source": capability["source"], "status": "malformed", "error": "payload is missing required fields"}
136
+ return {"source": capability["source"], "target": capability["target"], "transformation": capability["transformation"], "payload": payload, "manual_review": capability["manual_review"], "status": "mapped"}
137
+
138
+
139
+ def main() -> int:
140
+ """Dispatch a command-line request and print its JSON translation record.
141
+
142
+ The dispatcher reads an optional payload argument or stdin, translates it
143
+ through the public capability dispatcher, and emits JSON.
144
+
145
+ Args:
146
+ None. Command-line arguments are read from the process argument vector.
147
+
148
+ Returns:
149
+ Zero after the translation record is written.
150
+ """
151
+ try:
152
+ command_arguments = _parse_command_arguments()
153
+ payload_json = command_arguments.payload_json or sys.stdin.read()
154
+ capability_payload = _read_payload(payload_json)
155
+ translated_record = translate_capability(command_arguments.source_surface, capability_payload)
156
+ all_translation_error_statuses = ("unknown", "rejected", "malformed")
157
+ if translated_record["status"] in all_translation_error_statuses:
158
+ print(json.dumps(translated_record, sort_keys=True, separators=(",", ":")))
159
+ return 2
160
+ print(json.dumps(translated_record, sort_keys=True, separators=(",", ":")))
161
+ return 0
162
+ except CapabilityArgumentError as argument_error:
163
+ print(json.dumps(_build_error_record("unknown", str(argument_error)), separators=(",", ":")))
164
+ return 2
165
+ except (OSError, KeyError, TypeError, json.JSONDecodeError) as configuration_error:
166
+ print(json.dumps(_build_error_record("unknown", f"bridge configuration error: {configuration_error}"), separators=(",", ":")))
167
+ return 2
168
+
169
+
170
+ if __name__ == "__main__":
171
+ raise SystemExit(main())