[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DBF6V3XEN21N127YFPSKEN":3,"skill-same:01M6DBF6V3XEN21N127YFPSKEN":64},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":44,"commitSha":45,"skillDirectory":46,"forksCount":14,"repositoryHtmlUrl":47,"repositoryHomepageUrl":6,"ownerHtmlUrl":48,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":49,"install":50,"resources":63},"01M6DBF6V3XEN21N127YFPSKEN","harness-module-authoring",null,"Author, register, test, or review lifecycle harness modules and hook checks in agent-skills-toolchain. Use when changing ci\u002Fharness HookEvent or HookResult modules, hooks\u002F_dispatch\u002Fmanifest.json checks, fail-open behavior, redaction, persistent side effects, deduplication, calibrated-silence confidence gates, contraindication registry rows, or block-versus-inject policy.","Prekzursil\u002Fagent-skills-toolchain","Prekzursil","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F54636077?v=4","01M6DACZ1PCQHD99SCR1YXG28B","Andrei Mihai Visalon","01M6DACZ670WZF535V3WYYSS4Z",0,"MIT","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fharness-module-authoring\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"harness module authoring","module","harness","authoring","behavior","gates","agent-skills","modules","hooks","dispatch","use","review","json","open","lifecycle","manifest","deduplication","ci","test","policy","2026-11-10 10:12:30",false,"---\nname: harness-module-authoring\ndescription: Author, register, test, or review lifecycle harness modules and hook checks in agent-skills-toolchain. Use when changing ci\u002Fharness HookEvent or HookResult modules, hooks\u002F_dispatch\u002Fmanifest.json checks, fail-open behavior, redaction, persistent side effects, deduplication, calibrated-silence confidence gates, contraindication registry rows, or block-versus-inject policy.\n---\n\n# Harness Module Authoring\n\nUse the repository code as the contract. Read the exact files named below before\nediting; historical READMEs and changelog entries can lag behavior.\n\n## Start with the correct execution lane\n\nThis repository has two related but different hook systems:\n\n| Need | Runtime surface | Registration |\n|---|---|---|\n| Cross-provider Claude\u002FCodex lifecycle behavior | `ci\u002Fharness\u002F` | Add a module to `ci\u002Fharness\u002Fmodules.py`, keep `MODULE_ORDER` aligned, and instantiate it in `build_default_modules()` |\n| Existing Claude hook script consolidated behind one process | `hooks\u002F\u003Cname>\u002F` | Add a non-blocking inject-or-side-effect-only row to `hooks\u002F_dispatch\u002Fmanifest.json` |\n| A true blocking legacy gate | Standalone `hooks\u002F\u003Cname>\u002F` command hook | Wire it outside `_dispatch`; the consolidated dispatcher cannot preserve a block |\n| A known tool\u002Faction hazard with no new algorithm | `hooks\u002Fcontraindication\u002Fcontraindications.json` | Add a data row; do not add matching code |\n| A fatigue-aware advisory | A hook check plus `hooks\u002Fcalibrated-silence\u002F` | Gate repeats inside the check; there is no magic `confidence_gate` manifest field |\n\nDo not conflate `hooks\u002F_dispatch\u002Fmanifest.json` with\n`skills\u002FSKILLS-MANIFEST.md`. The former controls legacy hook checks; the latter is\nthe human-maintained skill inventory.\n\n## Provider-neutral harness contract\n\nRead these source-of-truth files together:\n\n- `ci\u002Fharness\u002Fmodels.py`: `HookEvent`, `HookResult`, and envelope types.\n- `ci\u002Fharness\u002Fadapters.py`: provider input normalization and native output rendering.\n- `ci\u002Fharness\u002Fdispatcher.py`: ordering, aggregation, redaction, failure handling, and terminal logging.\n- `ci\u002Fharness\u002Fmodules.py`: current module patterns and runtime registration.\n- `ci\u002Fharness\u002Fledger.py`, `redaction.py`, and `storage.py`: durable state primitives.\n\n### Consume `HookEvent`, not provider-shaped dictionaries\n\n`normalize_event()` converts provider input to schema version 1. Match on\n`event.event` and read normalized fields:\n\n```text\nschema_version, provider, event, session_id, turn_id, cwd, permission_mode\ntool.name, tool.id, tool.input, tool.result\nlast_assistant_message, prompt, source, trigger\nagent.id, agent.type, agent.transcript_path\n```\n\nTreat every field as untrusted and possibly empty. Gate on the event first, then\nvalidate the narrow input needed by the module. Keep provider-native field names and\nJSON out of module logic; adapters own that boundary.\n\n### Return one `HookResult` on every path\n\nUse `HookResult.success()` for normal outcomes and\n`HookResult.failed_open(event.event)` when a caught internal failure should be visible\nin the terminal ledger without blocking the host.\n\n| Action | Meaning in the current adapter |\n|---|---|\n| `allow` | No provider output; continue normally |\n| `inject` | Add context through `additionalContext` on context events and Claude `Stop`\u002F`SubagentStop`, or a `systemMessage` elsewhere |\n| `block` | Deny `PreToolUse` or `PermissionRequest`; render `decision: block` for other events |\n| `continue` | Render `decision: block` with continuation text; reserve for stop-like events that must keep the agent working |\n\n`status` is operational, not a provider action. Successful modules use\n`SUCCESS:hook`; failed-open results use `FAILED:hook-\u003Cevent>`. Never put provider wire\nfields or diagnostic blobs in `HookResult`.\n\nThe dispatcher combines results with this priority:\n\n```text\nallow \u003C inject \u003C continue \u003C block\n```\n\nIt preserves contexts in module order, removes exact duplicate context strings within\nthat one dispatch, and stops running later modules after a block. Put affirmative\nsecurity blockers before any module that could persist the blocked payload.\n\nUse this minimal shape inside `ci\u002Fharness\u002Fmodules.py`:\n\n```python\nclass ExampleModule:\n    name = \"example\"\n\n    def run(self, event: HookEvent) -> HookResult:\n        try:\n            if event.event != \"PostToolUse\" or event.tool.name != \"Example\":\n                return HookResult.success()\n            message = redact_text(\n                f\"Example result: {event.tool.result}\", max_chars=1000\n            )\n            return HookResult.success(\n                action=\"inject\",\n                reason_code=\"example_finding\",\n                additional_context=[message],\n            )\n        except (OSError, TypeError, ValueError):\n            return HookResult.failed_open(event.event)\n```\n\nThen add the name to `MODULE_ORDER` and the instance to the list returned by\n`build_default_modules()` in the same position. Runtime order comes from that returned\nlist; `MODULE_ORDER` documents the invariant but does not instantiate anything.\n\n### Keep aggregation behavior in mind\n\n- A later result wins only when its action has higher priority. For equal actions, a\n  later non-`ok` reason code can replace the earlier reason code.\n- All non-empty contexts survive unless they are exact duplicates. Do not make two\n  modules emit contradictory instructions.\n- An exception from one module is recorded as a failed module and otherwise fails open.\n- `dispatcher.main()` returns zero even on malformed input or an internal failure.\n- The terminal log stores redacted module names and metadata, never raw event payloads.\n\n## Legacy `_dispatch` check contract\n\nRead `hooks\u002F_dispatch\u002Fdispatch.py` and `manifest.json` before adding a check. The\ndispatcher supports `SessionStart`, `Stop`, `PostToolUse`, and `PreToolUse` only. It:\n\n1. Reads stdin once.\n2. Selects rows with `re.search(row.matcher, tool_name)`; matching is case-sensitive.\n3. Imports selected scripts serially under synthetic module names.\n4. Runs each script's `main()` in a daemon thread with thread-local stdin\u002Fstdout.\n5. Extracts only JSON `additionalContext` values from stdout.\n6. Joins them in manifest order and exits zero.\n\nA compatible script exposes `main()` or `main(argv)`. If `main` accepts a parameter,\nthe dispatcher passes the row's `args` list. Read one JSON object from stdin and emit\nat most one canonical JSON line:\n\n```python\ndef main(argv=None) -> int:\n    try:\n        payload = json.loads(sys.stdin.read() or \"{}\")\n        if not matches(payload):\n            return 0\n        message = build_redacted_message(payload)\n        print(json.dumps({\n            \"hookSpecificOutput\": {\n                \"hookEventName\": \"PreToolUse\",\n                \"additionalContext\": message,\n            }\n        }))\n    except Exception:\n        pass\n    return 0\n```\n\nDo not print debug text on stdout. The extractor ignores non-JSON lines, but they make\nstandalone behavior ambiguous. Send diagnostics to a redacted sidecar or stderr only\nwhen the standalone host contract calls for it.\n\n### Understand every manifest field\n\nAn ordinary check row uses:\n\n```json\n{\n  \"name\": \"example\u002Fcheck\",\n  \"script\": \"example\u002Fcheck.py\",\n  \"args\": [],\n  \"timeout\": 8,\n  \"matcher\": \"Bash|PowerShell\",\n  \"isolate\": null,\n  \"side_effect\": \"none\",\n  \"emits\": true\n}\n```\n\n| Field | Runtime meaning |\n|---|---|\n| `name` | Logical identity and basis for the synthetic import name |\n| `script` | Path relative to top-level `hooks_root` |\n| `args` | Passed only when `main` accepts at least one parameter |\n| `timeout` | Seconds allowed for that thread's result before its output is dropped |\n| `matcher` | Optional regex over normalized `tool_name`; absent means always |\n| `isolate` | Descriptive today; the dispatcher only clears the hard-coded `_common` and `stubscan` names |\n| `side_effect` | Descriptive audit label; it does not provide locking, rollback, or dedupe |\n| `emits` | Descriptive expectation; it is not runtime-enforced |\n| `slow` | Optional descriptive flag used by reviews, not scheduling logic |\n\nThe event-level `settings_matcher` and `settings_timeout` mirror intended live wiring;\n`dispatch.py` does not read them. If a new check needs a tool excluded by the live outer\nmatcher, changing only the row's matcher leaves the check unreachable. Update and verify\nthe actual hook registration during deployment.\n\nThe repository manifest's `hooks_root` points to the deployed live hook directory.\nRepository tests must call `dispatch.run()` with a test manifest or otherwise replace\nthe root; invoking the entry point from a clone can import live files instead of the\nfiles under test.\n\n### Timeouts do not cancel work\n\nAll selected threads start together. The join budget is measured from a shared start,\nand a timed-out daemon thread is abandoned, not killed. Its incomplete output is\ndropped, but its file or process side effect can still finish later. Therefore:\n\n- Make every side effect idempotent and replay-safe.\n- Bound all I\u002FO inside the check; do not rely on the dispatcher to stop it.\n- Use atomic replacement for mutable state and append locks for ledgers.\n- Never assume `timeout` provides transaction rollback.\n\n### Never put a blocker behind `_dispatch`\n\nThe legacy dispatcher ignores a check's return value and always exits zero. It only\nextracts `additionalContext`; an inner `return 2` or `sys.exit(2)` cannot block the\npending tool. Keep a dangerous-operation gate standalone, where exit 2 and a concise\nstderr reason reach the host. Use the consolidated manifest only for inject-only or\nside-effect-only checks.\n\n## Block versus inject\n\nUse the narrowest action supported by evidence:\n\n| Finding | Action |\n|---|---|\n| Positive match for an irreversible or security-dangerous pending operation | Block |\n| Recoverable error, quality issue, route, reminder, uncertainty, or safer alternative | Inject |\n| Missing state, corrupt config, timeout, classifier exception, or ambiguous match | Allow and record a failed-open terminal where available |\n| Stop-time unfinished work that must resume | `continue`, not a fake PreToolUse denial |\n\nDo not block because a prompt merely mentions a dangerous command. Inspect the actual\nshell command field at the system boundary. The current harness also treats user prompts\nas trusted for execution: redact secrets before persistence instead of rejecting the\nprompt on a broad secret regex.\n\nIn `ci\u002Fharness`, return `action=\"block\"` and let `adapters.py` render provider JSON. In a\nstandalone legacy gate, return exit 2 with the human-readable reason on stderr. Never\nuse exit 2 for advice.\n\n## Redact before every output and persistence boundary\n\nUse `ci\u002Fharness\u002Fredaction.py` rather than inventing a partial token regex:\n\n- Call `contains_secret()` only for a narrowly scoped affirmative security decision.\n- Call `redact_text()` before storing prompts, results, context, errors, or paths.\n- Set `redact_paths=True` for checkpoints, shared ledgers, and exported diagnostics.\n- Set `max_chars` on every model-facing or durable free-text value.\n\nThe provider-neutral dispatcher redacts returned contexts again at 4000 characters,\nbut that does not protect a module's own side effects. The legacy `_dispatch` performs\nno redaction. Redact at store time and emit time even when a later layer also redacts.\nPersist a digest or bounded classification when the raw payload is unnecessary.\n\nDo not log raw stdin, `tool.input`, `tool.result`, prompts, environment values, or\nexception representations. A failure record needs the module name and reason code, not\nthe sensitive value that caused it.\n\n## Design dedupe and side effects explicitly\n\nContext dedupe and durable dedupe are different:\n\n- `ci\u002Fharness\u002Fdispatcher.py` removes exact duplicate context strings only within one\n  dispatch.\n- `hooks\u002F_dispatch\u002Fdispatch.py` does not dedupe contexts.\n- Neither dispatcher dedupes file writes, process launches, or ledger appends.\n\nFor event-ledger writes, prefer `EventLedger.append(..., dedupe_key=stable_key)`. The\nledger SHA-256 hashes the key and returns `None` when that event id already exists.\nConstruct the key from stable identity such as provider, session, event, turn, and tool\nid; use a deterministic digest only as a fallback when the provider omitted an id.\n\nFor other state:\n\n1. Scope by session or project as the behavior requires.\n2. Use a stable action\u002Ffinding id, not the full raw payload, as the key.\n3. Write through `atomic_write_json()` or `atomic_write_text()`.\n4. Add a TTL only when the behavior is genuinely time-bounded.\n5. Test the same event twice and a replay after partial state.\n\nSet the manifest `side_effect` label truthfully (`none`, `ledger-append`,\n`seen-map`, `launch`, or another concise value). It is an audit promise, not a safety\nmechanism. A row marked `none` must be observationally pure.\n\n## Confidence-gated advisories\n\n`hooks\u002Fcalibrated-silence\u002Fcalibrated_silence.py` is the current adaptive confidence\ngate. It uses external user-reaction history, not an LLM's self-reported confidence.\nConsumers unpack `allow, reason = should_interject(type, context)` before an advisory\nand call `record_interjection(type, context_key, session_id)` only after actually\nemitting it.\n\nPreserve these invariants:\n\n- Cold start allows until `acted + ignored` reaches `MIN_SAMPLES`; `annoyed` and\n  `unknown` rows do not advance that sample count. Once out of cold start, recent\n  `annoyed` reactions can still raise the fatigue criterion.\n- A complete ledger-read failure or top-level gate exception allows the advisory.\n  Individual malformed JSONL rows are skipped; remaining valid history can still\n  suppress.\n- An environment kill switch also allows rather than suppresses.\n- Fatigue can suppress low-value repeats, not novel safety information.\n- A distinct harm finding bypasses calibrated silence. Only a repeat under the\n  consumer's explicit finding identity may enter the suppression gate.\n\nThe iatrogenics check demonstrates a recurrence gate: high-severity model-initiated\nlive mutations advise on the first occurrence, while command-pattern advisories get a\nfirst-use grace; counters still advance when an advisory is suppressed. Keep thresholds\nenvironment-tunable and test the count boundaries.\n\nThere is no `confidence_gate` property understood by `_dispatch\u002Fmanifest.json`.\nImplement confidence logic in the check and describe its state in `side_effect`.\n\n## Add contraindication rows without changing code\n\nUse `hooks\u002Fcontraindication\u002Fcontraindications.json` only for a real, documented\ntool\u002Faction combination with a known harm and a usable alternative. The canonical row\nis:\n\n```json\n{\n  \"id\": \"STABLE-UNIQUE-ID\",\n  \"tool_pattern\": \"Bash|PowerShell\",\n  \"input_pattern\": \"escaped-regex-over-serialized-tool-input\",\n  \"require_bg_active\": false,\n  \"harm\": \"What fails, under which combined preconditions, and why it matters.\",\n  \"safe_alternative\": \"A concrete lower-risk route that still advances the goal.\"\n}\n```\n\n`check.py` applies all supplied predicates with AND semantics and `re.IGNORECASE`.\n`tool_pattern` matches the tool name; `input_pattern` matches\n`json.dumps(tool_input)`; `require_bg_active` also requires a recent running\nasynchronous registry entry. A malformed regex or row is skipped as a non-match; the\nhook emits no warning for it and exits zero. Multiple matching rows are aggregated into\none advisory.\n\nThe check keeps a TTL-scoped per-session seen map by row id. If any matched id is new,\nthe whole finding bypasses calibrated silence and surfaces. Only when every matched row\nid was already seen may calibrated silence dedupe the repeat. This is row-ID identity,\nnot exact tool\u002Finput identity: a different call matching an already-seen row is repeat-\neligible. The contraindication hook is advisory and exits zero; a row does not become a\nblocker because its harm is severe.\n\nFor each row, add tests for:\n\n- one exact positive payload;\n- a close non-match that protects legitimate work;\n- the `require_bg_active` true and false cases when used;\n- the id, harm, and safe alternative in emitted context;\n- distinct-row-id bypass and previously-seen-row-id dedupe;\n- corrupt registry and malformed stdin fail-open behavior.\n\n## Verification protocol\n\nProve the smallest relevant contract before broad validation.\n\n### Module or check tests\n\n1. Positive: the exact trigger returns the intended action and reason code.\n2. Negative: a close legitimate input stays silent and allows.\n3. Boundary: wrong event, tool, provider, or missing field stays silent.\n4. Failure: malformed input and mocked I\u002FO errors allow with no traceback or secret.\n5. Redaction: seed representative token and absolute-path shapes; assert raw values are\n   absent from stdout, durable state, and terminal logs.\n6. Dedupe: replay the same event and assert one durable effect; use a distinct id and\n   assert a second effect.\n7. Adapter: assert provider-native rendering for every action the module can return.\n8. Wiring: run through `HarnessDispatcher` or `_dispatch.run()`, not only the predicate.\n\nFor a blocking legacy gate, execute the real subprocess and assert exit 2 plus a stderr\nreason on the positive case, exit zero on the negative and garbage cases. Do not test a\nblocker through the inject-only dispatcher.\n\nFor a manifest change, parse JSON, assert the script exists under the intended\n`hooks_root`, compile the matcher, verify the outer matcher can reach it, and compare\n`emits` and `side_effect` against observed behavior. Use a temporary state root and\nhermetic calibrated-silence ledger; never read or write live state during tests.\n\n### Final review\n\n- Re-read `models.py`, `adapters.py`, `dispatcher.py`, and the exact neighboring hook.\n- Confirm every module path returns a `HookResult`; confirm every legacy path exits zero\n  on internal failure.\n- Confirm only dangerous, positively identified operations block.\n- Confirm all guidance is inject-only and factual.\n- Confirm raw secrets and paths cannot reach output or state.\n- Confirm side effects are bounded, atomic, and replay-safe.\n- Confirm novel harm cannot be suppressed by confidence or fatigue logic.\n- Confirm manifest metadata describes what runtime tests observed.\n- Run targeted tests, then the repository's relevant validation command.\n- Report `SUCCESS:\u003Cid>` or `FAILED:\u003Cid> reason`; never call an unverified hook installed.\n","# Harness Module Authoring\n\nUse the repository code as the contract. Read the exact files named below before\nediting; historical READMEs and changelog entries can lag behavior.\n\n## Start with the correct execution lane\n\nThis repository has two related but different hook systems:\n\n| Need | Runtime surface | Registration |\n|---|---|---|\n| Cross-provider Claude\u002FCodex lifecycle behavior | `ci\u002Fharness\u002F` | Add a module to `ci\u002Fharness\u002Fmodules.py`, keep `MODULE_ORDER` aligned, and instantiate it in `build_default_modules()` |\n| Existing Claude hook script consolidated behind one process | `hooks\u002F\u003Cname>\u002F` | Add a non-blocking inject-or-side-effect-only row to `hooks\u002F_dispatch\u002Fmanifest.json` |\n| A true blocking legacy gate | Standalone `hooks\u002F\u003Cname>\u002F` command hook | Wire it outside `_dispatch`; the consolidated dispatcher cannot preserve a block |\n| A known tool\u002Faction hazard with no new algorithm | `hooks\u002Fcontraindication\u002Fcontraindications.json` | Add a data row; do not add matching code |\n| A fatigue-aware advisory | A hook check plus `hooks\u002Fcalibrated-silence\u002F` | Gate repeats inside the check; there is no magic `confidence_gate` manifest field |\n\nDo not conflate `hooks\u002F_dispatch\u002Fmanifest.json` with\n`skills\u002FSKILLS-MANIFEST.md`. The former controls legacy hook checks; the latter is\nthe human-maintained skill inventory.\n\n## Provider-neutral harness contract\n\nRead these source-of-truth files together:\n\n- `ci\u002Fharness\u002Fmodels.py`: `HookEvent`, `HookResult`, and envelope types.\n- `ci\u002Fharness\u002Fadapters.py`: provider input normalization and native output rendering.\n- `ci\u002Fharness\u002Fdispatcher.py`: ordering, aggregation, redaction, failure handling, and terminal logging.\n- `ci\u002Fharness\u002Fmodules.py`: current module patterns and runtime registration.\n- `ci\u002Fharness\u002Fledger.py`, `redaction.py`, and `storage.py`: durable state primitives.\n\n### Consume `HookEvent`, not provider-shaped dictionaries\n\n`normalize_event()` converts provider input to schema version 1. Match on\n`event.event` and read normalized fields:\n\n```text\nschema_version, provider, event, session_id, turn_id, cwd, permission_mode\ntool.name, tool.id, tool.input, tool.result\nlast_assistant_message, prompt, source, trigger\nagent.id, agent.type, agent.transcript_path\n```\n\nTreat every field as untrusted and possibly empty. Gate on the event first, then\nvalidate the narrow input needed by the module. Keep provider-native field names and\nJSON out of module logic; adapters own that boundary.\n\n### Return one `HookResult` on every path\n\nUse `HookResult.success()` for normal outcomes and\n`HookResult.failed_open(event.event)` when a caught internal failure should be visible\nin the terminal ledger without blocking the host.\n\n| Action | Meaning in the current adapter |\n|---|---|\n| `allow` | No provider output; continue normally |\n| `inject` | Add context through `additionalContext` on context events and Claude `Stop`\u002F`SubagentStop`, or a `systemMessage` elsewhere |\n| `block` | Deny `PreToolUse` or `PermissionRequest`; render `decision: block` for other events |\n| `continue` | Render `decision: block` with continuation text; reserve for stop-like events that must keep the agent working |\n\n`status` is operational, not a provider action. Successful modules use\n`SUCCESS:hook`; failed-open results use `FAILED:hook-\u003Cevent>`. Never put provider wire\nfields or diagnostic blobs in `HookResult`.\n\nThe dispatcher combines results with this priority:\n\n```text\nallow \u003C inject \u003C continue \u003C block\n```\n\nIt preserves contexts in module order, removes exact duplicate context strings within\nthat one dispatch, and stops running later modules after a block. Put affirmative\nsecurity blockers before any module that could persist the blocked payload.\n\nUse this minimal shape inside `ci\u002Fharness\u002Fmodules.py`:\n\n```python\nclass ExampleModule:\n    name = \"example\"\n\n    def run(self, event: HookEvent) -> HookResult:\n        try:\n            if event.event != \"PostToolUse\" or event.tool.name != \"Example\":\n                return HookResult.success()\n            message = redact_text(\n                f\"Example result: {event.tool.result}\", max_chars=1000\n            )\n            return HookResult.success(\n                action=\"inject\",\n                reason_code=\"example_finding\",\n                additional_context=[message],\n            )\n        except (OSError, TypeError, ValueError):\n            return HookResult.failed_open(event.event)\n```\n\nThen add the name to `MODULE_ORDER` and the instance to the list returned by\n`build_default_modules()` in the same position. Runtime order comes from that returned\nlist; `MODULE_ORDER` documents the invariant but does not instantiate anything.\n\n### Keep aggregation behavior in mind\n\n- A later result wins only when its action has higher priority. For equal actions, a\n  later non-`ok` reason code can replace the earlier reason code.\n- All non-empty contexts survive unless they are exact duplicates. Do not make two\n  modules emit contradictory instructions.\n- An exception from one module is recorded as a failed module and otherwise fails open.\n- `dispatcher.main()` returns zero even on malformed input or an internal failure.\n- The terminal log stores redacted module names and metadata, never raw event payloads.\n\n## Legacy `_dispatch` check contract\n\nRead `hooks\u002F_dispatch\u002Fdispatch.py` and `manifest.json` before adding a check. The\ndispatcher supports `SessionStart`, `Stop`, `PostToolUse`, and `PreToolUse` only. It:\n\n1. Reads stdin once.\n2. Selects rows with `re.search(row.matcher, tool_name)`; matching is case-sensitive.\n3. Imports selected scripts serially under synthetic module names.\n4. Runs each script's `main()` in a daemon thread with thread-local stdin\u002Fstdout.\n5. Extracts only JSON `additionalContext` values from stdout.\n6. Joins them in manifest order and exits zero.\n\nA compatible script exposes `main()` or `main(argv)`. If `main` accepts a parameter,\nthe dispatcher passes the row's `args` list. Read one JSON object from stdin and emit\nat most one canonical JSON line:\n\n```python\ndef main(argv=None) -> int:\n    try:\n        payload = json.loads(sys.stdin.read() or \"{}\")\n        if not matches(payload):\n            return 0\n        message = build_redacted_message(payload)\n        print(json.dumps({\n            \"hookSpecificOutput\": {\n                \"hookEventName\": \"PreToolUse\",\n                \"additionalContext\": message,\n            }\n        }))\n    except Exception:\n        pass\n    return 0\n```\n\nDo not print debug text on stdout. The extractor ignores non-JSON lines, but they make\nstandalone behavior ambiguous. Send diagnostics to a redacted sidecar or stderr only\nwhen the standalone host contract calls for it.\n\n### Understand every manifest field\n\nAn ordinary check row uses:\n\n```json\n{\n  \"name\": \"example\u002Fcheck\",\n  \"script\": \"example\u002Fcheck.py\",\n  \"args\": [],\n  \"timeout\": 8,\n  \"matcher\": \"Bash|PowerShell\",\n  \"isolate\": null,\n  \"side_effect\": \"none\",\n  \"emits\": true\n}\n```\n\n| Field | Runtime meaning |\n|---|---|\n| `name` | Logical identity and basis for the synthetic import name |\n| `script` | Path relative to top-level `hooks_root` |\n| `args` | Passed only when `main` accepts at least one parameter |\n| `timeout` | Seconds allowed for that thread's result before its output is dropped |\n| `matcher` | Optional regex over normalized `tool_name`; absent means always |\n| `isolate` | Descriptive today; the dispatcher only clears the hard-coded `_common` and `stubscan` names |\n| `side_effect` | Descriptive audit label; it does not provide locking, rollback, or dedupe |\n| `emits` | Descriptive expectation; it is not runtime-enforced |\n| `slow` | Optional descriptive flag used by reviews, not scheduling logic |\n\nThe event-level `settings_matcher` and `settings_timeout` mirror intended live wiring;\n`dispatch.py` does not read them. If a new check needs a tool excluded by the live outer\nmatcher, changing only the row's matcher leaves the check unreachable. Update and verify\nthe actual hook registration during deployment.\n\nThe repository manifest's `hooks_root` points to the deployed live hook directory.\nRepository tests must call `dispatch.run()` with a test manifest or otherwise replace\nthe root; invoking the entry point from a clone can import live files instead of the\nfiles under test.\n\n### Timeouts do not cancel work\n\nAll selected threads start together. The join budget is measured from a shared start,\nand a timed-out daemon thread is abandoned, not killed. Its incomplete output is\ndropped, but its file or process side effect can still finish later. Therefore:\n\n- Make every side effect idempotent and replay-safe.\n- Bound all I\u002FO inside the check; do not rely on the dispatcher to stop it.\n- Use atomic replacement for mutable state and append locks for ledgers.\n- Never assume `timeout` provides transaction rollback.\n\n### Never put a blocker behind `_dispatch`\n\nThe legacy dispatcher ignores a check's return value and always exits zero. It only\nextracts `additionalContext`; an inner `return 2` or `sys.exit(2)` cannot block the\npending tool. Keep a dangerous-operation gate standalone, where exit 2 and a concise\nstderr reason reach the host. Use the consolidated manifest only for inject-only or\nside-effect-only checks.\n\n## Block versus inject\n\nUse the narrowest action supported by evidence:\n\n| Finding | Action |\n|---|---|\n| Positive match for an irreversible or security-dangerous pending operation | Block |\n| Recoverable error, quality issue, route, reminder, uncertainty, or safer alternative | Inject |\n| Missing state, corrupt config, timeout, classifier exception, or ambiguous match | Allow and record a failed-open terminal where available |\n| Stop-time unfinished work that must resume | `continue`, not a fake PreToolUse denial |\n\nDo not block because a prompt merely mentions a dangerous command. Inspect the actual\nshell command field at the system boundary. The current harness also treats user prompts\nas trusted for execution: redact secrets before persistence instead of rejecting the\nprompt on a broad secret regex.\n\nIn `ci\u002Fharness`, return `action=\"block\"` and let `adapters.py` render provider JSON. In a\nstandalone legacy gate, return exit 2 with the human-readable reason on stderr. Never\nuse exit 2 for advice.\n\n## Redact before every output and persistence boundary\n\nUse `ci\u002Fharness\u002Fredaction.py` rather than inventing a partial token regex:\n\n- Call `contains_secret()` only for a narrowly scoped affirmative security decision.\n- Call `redact_text()` before storing prompts, results, context, errors, or paths.\n- Set `redact_paths=True` for checkpoints, shared ledgers, and exported diagnostics.\n- Set `max_chars` on every model-facing or durable free-text value.\n\nThe provider-neutral dispatcher redacts returned contexts again at 4000 characters,\nbut that does not protect a module's own side effects. The legacy `_dispatch` performs\nno redaction. Redact at store time and emit time even when a later layer also redacts.\nPersist a digest or bounded classification when the raw payload is unnecessary.\n\nDo not log raw stdin, `tool.input`, `tool.result`, prompts, environment values, or\nexception representations. A failure record needs the module name and reason code, not\nthe sensitive value that caused it.\n\n## Design dedupe and side effects explicitly\n\nContext dedupe and durable dedupe are different:\n\n- `ci\u002Fharness\u002Fdispatcher.py` removes exact duplicate context strings only within one\n  dispatch.\n- `hooks\u002F_dispatch\u002Fdispatch.py` does not dedupe contexts.\n- Neither dispatcher dedupes file writes, process launches, or ledger appends.\n\nFor event-ledger writes, prefer `EventLedger.append(..., dedupe_key=stable_key)`. The\nledger SHA-256 hashes the key and returns `None` when that event id already exists.\nConstruct the key from stable identity such as provider, session, event, turn, and tool\nid; use a deterministic digest only as a fallback when the provider omitted an id.\n\nFor other state:\n\n1. Scope by session or project as the behavior requires.\n2. Use a stable action\u002Ffinding id, not the full raw payload, as the key.\n3. Write through `atomic_write_json()` or `atomic_write_text()`.\n4. Add a TTL only when the behavior is genuinely time-bounded.\n5. Test the same event twice and a replay after partial state.\n\nSet the manifest `side_effect` label truthfully (`none`, `ledger-append`,\n`seen-map`, `launch`, or another concise value). It is an audit promise, not a safety\nmechanism. A row marked `none` must be observationally pure.\n\n## Confidence-gated advisories\n\n`hooks\u002Fcalibrated-silence\u002Fcalibrated_silence.py` is the current adaptive confidence\ngate. It uses external user-reaction history, not an LLM's self-reported confidence.\nConsumers unpack `allow, reason = should_interject(type, context)` before an advisory\nand call `record_interjection(type, context_key, session_id)` only after actually\nemitting it.\n\nPreserve these invariants:\n\n- Cold start allows until `acted + ignored` reaches `MIN_SAMPLES`; `annoyed` and\n  `unknown` rows do not advance that sample count. Once out of cold start, recent\n  `annoyed` reactions can still raise the fatigue criterion.\n- A complete ledger-read failure or top-level gate exception allows the advisory.\n  Individual malformed JSONL rows are skipped; remaining valid history can still\n  suppress.\n- An environment kill switch also allows rather than suppresses.\n- Fatigue can suppress low-value repeats, not novel safety information.\n- A distinct harm finding bypasses calibrated silence. Only a repeat under the\n  consumer's explicit finding identity may enter the suppression gate.\n\nThe iatrogenics check demonstrates a recurrence gate: high-severity model-initiated\nlive mutations advise on the first occurrence, while command-pattern advisories get a\nfirst-use grace; counters still advance when an advisory is suppressed. Keep thresholds\nenvironment-tunable and test the count boundaries.\n\nThere is no `confidence_gate` property understood by `_dispatch\u002Fmanifest.json`.\nImplement confidence logic in the check and describe its state in `side_effect`.\n\n## Add contraindication rows without changing code\n\nUse `hooks\u002Fcontraindication\u002Fcontraindications.json` only for a real, documented\ntool\u002Faction combination with a known harm and a usable alternative. The canonical row\nis:\n\n```json\n{\n  \"id\": \"STABLE-UNIQUE-ID\",\n  \"tool_pattern\": \"Bash|PowerShell\",\n  \"input_pattern\": \"escaped-regex-over-serialized-tool-input\",\n  \"require_bg_active\": false,\n  \"harm\": \"What fails, under which combined preconditions, and why it matters.\",\n  \"safe_alternative\": \"A concrete lower-risk route that still advances the goal.\"\n}\n```\n\n`check.py` applies all supplied predicates with AND semantics and `re.IGNORECASE`.\n`tool_pattern` matches the tool name; `input_pattern` matches\n`json.dumps(tool_input)`; `require_bg_active` also requires a recent running\nasynchronous registry entry. A malformed regex or row is skipped as a non-match; the\nhook emits no warning for it and exits zero. Multiple matching rows are aggregated into\none advisory.\n\nThe check keeps a TTL-scoped per-session seen map by row id. If any matched id is new,\nthe whole finding bypasses calibrated silence and surfaces. Only when every matched row\nid was already seen may calibrated silence dedupe the repeat. This is row-ID identity,\nnot exact tool\u002Finput identity: a different call matching an already-seen row is repeat-\neligible. The contraindication hook is advisory and exits zero; a row does not become a\nblocker because its harm is severe.\n\nFor each row, add tests for:\n\n- one exact positive payload;\n- a close non-match that protects legitimate work;\n- the `require_bg_active` true and false cases when used;\n- the id, harm, and safe alternative in emitted context;\n- distinct-row-id bypass and previously-seen-row-id dedupe;\n- corrupt registry and malformed stdin fail-open behavior.\n\n## Verification protocol\n\nProve the smallest relevant contract before broad validation.\n\n### Module or check tests\n\n1. Positive: the exact trigger returns the intended action and reason code.\n2. Negative: a close legitimate input stays silent and allows.\n3. Boundary: wrong event, tool, provider, or missing field stays silent.\n4. Failure: malformed input and mocked I\u002FO errors allow with no traceback or secret.\n5. Redaction: seed representative token and absolute-path shapes; assert raw values are\n   absent from stdout, durable state, and terminal logs.\n6. Dedupe: replay the same event and assert one durable effect; use a distinct id and\n   assert a second effect.\n7. Adapter: assert provider-native rendering for every action the module can return.\n8. Wiring: run through `HarnessDispatcher` or `_dispatch.run()`, not only the predicate.\n\nFor a blocking legacy gate, execute the real subprocess and assert exit 2 plus a stderr\nreason on the positive case, exit zero on the negative and garbage cases. Do not test a\nblocker through the inject-only dispatcher.\n\nFor a manifest change, parse JSON, assert the script exists under the intended\n`hooks_root`, compile the matcher, verify the outer matcher can reach it, and compare\n`emits` and `side_effect` against observed behavior. Use a temporary state root and\nhermetic calibrated-silence ledger; never read or write live state during tests.\n\n### Final review\n\n- Re-read `models.py`, `adapters.py`, `dispatcher.py`, and the exact neighboring hook.\n- Confirm every module path returns a `HookResult`; confirm every legacy path exits zero\n  on internal failure.\n- Confirm only dangerous, positively identified operations block.\n- Confirm all guidance is inject-only and factual.\n- Confirm raw secrets and paths cannot reach output or state.\n- Confirm side effects are bounded, atomic, and replay-safe.\n- Confirm novel harm cannot be suppressed by confidence or fatigue logic.\n- Confirm manifest metadata describes what runtime tests observed.\n- Run targeted tests, then the repository's relevant validation command.\n- Report `SUCCESS:\u003Cid>` or `FAILED:\u003Cid> reason`; never call an unverified hook installed.\n",{"name":5,"description":7},"main","a298c929b6cbd662e1fce54302ce520d5ceea1a5",".agents\u002Fskills\u002Fharness-module-authoring","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain","https:\u002F\u002Fgithub.com\u002FPrekzursil","2026-11-02 14:30:57",{"source":8,"skillName":5,"commands":51,"prompt":61,"zipAvailable":62},[52,55,58],{"tool":53,"command":54},"npx","npx skills add Prekzursil\u002Fagent-skills-toolchain --skill harness-module-authoring",{"tool":56,"command":57},"bunx","bunx skills add Prekzursil\u002Fagent-skills-toolchain --skill harness-module-authoring",{"tool":59,"command":60},"pnpm","pnpm dlx skills add Prekzursil\u002Fagent-skills-toolchain --skill harness-module-authoring","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fharness-module-authoring\u002FSKILL.md",true,[],[65,86,116,142,156,172],{"skillNo":66,"name":67,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":39},"01M6DF7D7478R55P46REYH4QAC","clarify-requirements","Resolve genuine pre-execution ambiguity by scanning repo context, generating 1-6 grounded options, and asking 1-3 focused questions via AskUserQuestion. NOT for mid-task use (injection risk); NOT when the goal is already unambiguous; use to disambiguate vague prompts before any file edits.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fworkspace-hooks\u002Fskills\u002Fclarify-requirements\u002FSKILL.md",[],[72,73,74,75,76,77,78,79,80,29,81,82,83,84,85],"clarify requirements","clarify","requirements","risk","options","task","prompts","goal","scanning","context","repo","injection","execution","file",{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":97,"publishedAt":39},"01M6DF79CA4BHJS66J8EZDBPWJ","openmm-md","Run FREE, open-source, Windows-native GPU molecular dynamics via the openmm-md MCP (OpenMM + CUDA on the local NVIDIA GPU). Use for protein \u002F protein-ligand MD when you want a free stack or are on Windows without a Schrodinger license. Covers the full workflow (prepare, solvate, minimize, equilibrate, production, analyze, mmgbsa) AND an honest decision guide vs Schrodinger Desmond, including how to run Desmond legitimately on Linux or a cloud GPU with a real license. This is a deliberately LESSER free alternative and does NOT replace the schrodinger MCP tools. Do NOT use it to pick a free tool when a validated FEP+, WaterMap, IFD-MD, or Prime MM-GBSA result is actually required.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fmcp\u002Fopenmm-md-mcp\u002Fskill\u002Fopenmm-md\u002FSKILL.md",[92],{"categoryNo":93,"categoryName":94,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[98,99,100,101,102,103,104,105,32,106,107,108,109,110,29,111,112,113,114,115],"openmm md","how","decision","pick","linux","analyze","tool","stack","NVIDIA","gpu","license","workflow","windows","free","molecular dynamics","local","do","MCP",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":128,"publishedAt":39},"01M6DF76C1MG85VEWP8VZCBNMN","visual-review","Take screenshots of web pages and UI using Playwright for visual review and iteration","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fvisual-review\u002FSKILL.md",[122,125],{"categoryNo":123,"categoryName":124,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":126,"categoryName":127,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000008","测试与质量",[30,129,130,131,132,133,134,135,136,137,99,138,139,140,141],"visual","visual review","pages","web","false","iteration","take","check","slides","ui","screenshot","playwright","screenshots",{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":146,"categories":147,"keywords":148,"publishedAt":39},"01M6DF73AQVCBZKEN0YXEX9FXP","status","Diagnostic status report — shows metaswarm installation state, project setup, and potential issues","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstatus\u002FSKILL.md",[],[144,149,150,151,152,153,154,155],"setup","report","project","installation","project setup","issues","state",{"skillNo":157,"name":158,"nameZh":6,"description":159,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":160,"categories":161,"keywords":163,"publishedAt":39},"01M6DF6X6C2XB0P6GX76P7TTYV","start","Use when starting work on any task, when the user mentions metaswarm, or when the user wants to begin tracked development work","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstart\u002FSKILL.md",[162],{"categoryNo":93,"categoryName":94,"taxonomyType":95,"taxonomyTypeDesc":96},[158,164,77,165,166,167,29,168,169,170,171],"start issue","issue","ready","work on issue","user","work","work on","development",{"skillNo":173,"name":149,"nameZh":6,"description":174,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":175,"categories":176,"keywords":177,"publishedAt":39},"01M6DF6QMJ9AJKFKHHKRT005PQ","Interactive project setup — detects your project, configures metaswarm, writes project-local files","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fsetup\u002FSKILL.md",[],[149,151,113,178,153,179],"files","interactive"]