{"_id":"@capdiem/pi-repetition-guard","_rev":"5-c54e7392e3fe9ddde77ecaa22f7a2c2d","name":"@capdiem/pi-repetition-guard","dist-tags":{"latest":"0.3.2"},"versions":{"0.2.0":{"name":"@capdiem/pi-repetition-guard","version":"0.2.0","keywords":["pi-package","pi","coding-agent","repetition","repetition-loop","thinking-runaway","runaway","guard","abort","steer"],"author":{"name":"capdiem","email":"capdiem@live.com"},"license":"MIT","_id":"@capdiem/pi-repetition-guard@0.2.0","maintainers":[{"name":"capdiem","email":"capdiem@live.com"}],"homepage":"https://github.com/capdiem/pi-extensions#readme","bugs":{"url":"https://github.com/capdiem/pi-extensions/issues"},"pi":{"extensions":["./index.min.js"]},"dist":{"shasum":"aa24f94bb9a9c028ca5aac370f769fad268c3cd6","tarball":"https://registry.npmjs.org/@capdiem/pi-repetition-guard/-/pi-repetition-guard-0.2.0.tgz","fileCount":5,"integrity":"sha512-/RHS2Fg/iV3cNZSyjgRl1QWeir1nT1Chb2NkNbhM8osp0+BhpHiK6AroJNGGXG7iU+sVQTT2jYOEWSoQ2Bc6TQ==","signatures":[{"sig":"MEUCIFlPumH5Cp1CyYaL/EqcjQRtaRgXuNCAV5rRTC8rVx1wAiEA+ULxDq/zfLVkQPX+CBKtDmqgJNLSt5AAtLUKhWLdFyo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":23644},"type":"module","gitHead":"1f015b86a43d029bc7c6ae1cd79d598c6f2552ab","_npmUser":{"name":"capdiem","email":"capdiem@live.com"},"repository":{"url":"git+https://github.com/capdiem/pi-extensions.git","type":"git","directory":"extensions/pi-repetition-guard"},"_npmVersion":"11.12.1","description":"A repetition-loop guard for the Pi coding agent: detects thinking-runaway / 万字复读 (the model repeating the same text in a loop) in streamed output via message_update, then aborts and re-steers the model with a corrective steer message","directories":{},"_nodeVersion":"24.15.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"peerDependencies":{"typebox":"*","@earendil-works/pi-ai":"*","@earendil-works/pi-tui":"*","@earendil-works/pi-coding-agent":"*"},"_npmOperationalInternal":{"tmp":"tmp/pi-repetition-guard_0.2.0_1787889876386_0.10854179545741172","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@capdiem/pi-repetition-guard","version":"0.2.1","keywords":["pi-package","pi","coding-agent","repetition","repetition-loop","thinking-runaway","runaway","guard","abort","steer"],"author":{"name":"capdiem","email":"capdiem@live.com"},"license":"MIT","_id":"@capdiem/pi-repetition-guard@0.2.1","maintainers":[{"name":"capdiem","email":"capdiem@live.com"}],"homepage":"https://github.com/capdiem/pi-extensions#readme","bugs":{"url":"https://github.com/capdiem/pi-extensions/issues"},"pi":{"extensions":["./index.min.js"]},"dist":{"shasum":"27d62af9ea2169dd325fa0bde44579eab1a58898","tarball":"https://registry.npmjs.org/@capdiem/pi-repetition-guard/-/pi-repetition-guard-0.2.1.tgz","fileCount":5,"integrity":"sha512-be8nZEgagcezZRAshz7wkT2zkxVTFedAPCwiOEO3ldDe+6gvgocqo2iOmM+WLV5dNYMVCCWWvhsdMp/PMXZIbQ==","signatures":[{"sig":"MEUCIHlz9U7R33qq+FHiwoEQNO3lDtzewd+QCWEJWd22Dtr7AiEAoKEFt3ZLUuW/KxNogRcl+ocLXAPKQO6sEJTQKrzr1WU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@capdiem%2fpi-repetition-guard@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":45260},"type":"module","gitHead":"3223fff502458a60ea5d5b9ad42332c90f78baee","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:98bfd9ad-c3ff-4b31-8950-ee6a0dbbb5d4"}},"repository":{"url":"git+https://github.com/capdiem/pi-extensions.git","type":"git","directory":"extensions/pi-repetition-guard"},"_npmVersion":"11.17.0","description":"A repetition-loop guard for the Pi coding agent: detects thinking-runaway / 万字复读 (the model repeating the same text in a loop) in streamed output via message_update, then aborts and re-steers the model with a corrective steer message","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"peerDependencies":{"typebox":"*","@earendil-works/pi-ai":"*","@earendil-works/pi-tui":"*","@earendil-works/pi-coding-agent":"*"},"_npmOperationalInternal":{"tmp":"tmp/pi-repetition-guard_0.2.1_1787912867754_0.1845407136178443","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@capdiem/pi-repetition-guard","version":"0.3.0","keywords":["pi-package","pi","coding-agent","repetition","repetition-loop","thinking-runaway","runaway","guard","abort","steer"],"author":{"name":"capdiem","email":"capdiem@live.com"},"license":"MIT","_id":"@capdiem/pi-repetition-guard@0.3.0","maintainers":[{"name":"capdiem","email":"capdiem@live.com"}],"homepage":"https://github.com/capdiem/pi-extensions#readme","bugs":{"url":"https://github.com/capdiem/pi-extensions/issues"},"pi":{"extensions":["./index.min.js"]},"dist":{"shasum":"c459401b4dabf374bb0d4a9ca9e192d904801f5d","tarball":"https://registry.npmjs.org/@capdiem/pi-repetition-guard/-/pi-repetition-guard-0.3.0.tgz","fileCount":5,"integrity":"sha512-QRfC86ettHKfHfB4UHAaCdDX5vOfMXtwr2P8j1ahJLANDmgfiGINR9Q1MmENp/i8U8fvO5hPCAdFOzyOS3BbLQ==","signatures":[{"sig":"MEQCIFWyJZ93eXBeE/eGk+469GZjkiaPvYvOHvAD9J2QdsvuAiBTlxHmqdJnjtPTWviayQhp3qmpX10GiJTg/QFmLN4Alw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@capdiem%2fpi-repetition-guard@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":51906},"type":"module","gitHead":"a90d9fb4062aee994b250fee057cd25f900e9698","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:98bfd9ad-c3ff-4b31-8950-ee6a0dbbb5d4"}},"repository":{"url":"git+https://github.com/capdiem/pi-extensions.git","type":"git","directory":"extensions/pi-repetition-guard"},"_npmVersion":"11.17.0","description":"A repetition-loop guard for the Pi coding agent: detects thinking-runaway / 万字复读 (the model repeating the same text in a loop) in streamed output via message_update, then aborts and re-steers the model with a corrective steer message","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"peerDependencies":{"typebox":"*","@earendil-works/pi-ai":"*","@earendil-works/pi-tui":"*","@earendil-works/pi-coding-agent":"*"},"_npmOperationalInternal":{"tmp":"tmp/pi-repetition-guard_0.3.0_1788235850719_0.0310216552226934","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@capdiem/pi-repetition-guard","version":"0.3.1","keywords":["pi-package","pi","coding-agent","repetition","repetition-loop","thinking-runaway","runaway","guard","abort","steer"],"author":{"name":"capdiem","email":"capdiem@live.com"},"license":"MIT","_id":"@capdiem/pi-repetition-guard@0.3.1","maintainers":[{"name":"capdiem","email":"capdiem@live.com"}],"homepage":"https://github.com/capdiem/pi-extensions#readme","bugs":{"url":"https://github.com/capdiem/pi-extensions/issues"},"pi":{"extensions":["./index.min.js"]},"dist":{"shasum":"fb00ca9eca386f19cc597ae4b824b3e1ca4910bb","tarball":"https://registry.npmjs.org/@capdiem/pi-repetition-guard/-/pi-repetition-guard-0.3.1.tgz","fileCount":5,"integrity":"sha512-EOR8v5assbzw/OhkxsM3YrGg0/wdk7JT4rfZrLt6fwDRbKXYY3xbjEGhR3zPYJB8B5zfwz5UzkTOg0IRXFCP1w==","signatures":[{"sig":"MEYCIQCAvkHtIPx+XWgCPmxHFR46AuMKeMHLI7sPksMVefDtqwIhAKkxKw2w2cYA6nQWa7IrHDFUrbz/0Xg2ERM0X4k7fJnF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@capdiem%2fpi-repetition-guard@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":51906},"type":"module","gitHead":"71149c2d783b219302cf4beba965c4d2e9be07e5","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:98bfd9ad-c3ff-4b31-8950-ee6a0dbbb5d4"}},"repository":{"url":"git+https://github.com/capdiem/pi-extensions.git","type":"git","directory":"extensions/pi-repetition-guard"},"_npmVersion":"11.17.0","description":"A repetition-loop guard for the Pi coding agent: detects thinking-runaway / 万字复读 (the model repeating the same text in a loop) in streamed output via message_update, then aborts and re-steers the model with a corrective steer message","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"peerDependencies":{"typebox":"*","@earendil-works/pi-ai":"*","@earendil-works/pi-tui":"*","@earendil-works/pi-coding-agent":"*"},"_npmOperationalInternal":{"tmp":"tmp/pi-repetition-guard_0.3.1_1788237374750_0.39805234019673663","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@capdiem/pi-repetition-guard","version":"0.3.2","description":"A repetition-loop guard for the Pi coding agent: detects thinking-runaway / 万字复读 (the model repeating the same text in a loop) in streamed output via message_update, then aborts and re-steers the model with a corrective steer message","type":"module","pi":{"extensions":["./index.min.js"]},"keywords":["pi-package","pi","coding-agent","repetition","repetition-loop","thinking-runaway","runaway","guard","abort","steer"],"author":{"name":"capdiem","email":"capdiem@live.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/capdiem/pi-extensions.git","directory":"extensions/pi-repetition-guard"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"peerDependencies":{"@earendil-works/pi-ai":"*","@earendil-works/pi-coding-agent":"*","@earendil-works/pi-tui":"*","typebox":"*"},"gitHead":"14a0b5ac333b27bf4ef67deb90f5a8883db83dfb","_id":"@capdiem/pi-repetition-guard@0.3.2","bugs":{"url":"https://github.com/capdiem/pi-extensions/issues"},"homepage":"https://github.com/capdiem/pi-extensions#readme","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-xL4KkxG+okumhHls+1VV6w+kxQZ/6iky8Gz7BeEjr4X2A/XkJ4P3OdUNMp3DgnIQNHGdrPDbiEAcR/jO+vbhfg==","shasum":"19a65969ccc31c3fb79442ee0578d658ff288a87","tarball":"https://registry.npmjs.org/@capdiem/pi-repetition-guard/-/pi-repetition-guard-0.3.2.tgz","fileCount":5,"unpackedSize":51906,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@capdiem%2fpi-repetition-guard@0.3.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCBuQxPk3C5af9FrIhUG665AqWdrmETx8oSdVBaegxJYwIgG/wIUNhi8IxPBkVbJl8iIE21zql6qqC023dwadQ3psE="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:98bfd9ad-c3ff-4b31-8950-ee6a0dbbb5d4"}},"directories":{},"maintainers":[{"name":"capdiem","email":"capdiem@live.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pi-repetition-guard_0.3.2_1788250259224_0.7452312472456257"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-28T04:04:36.251Z","modified":"2026-09-01T08:10:59.668Z","0.2.0":"2026-08-28T04:04:36.529Z","0.2.1":"2026-08-28T10:27:47.899Z","0.3.0":"2026-09-01T04:10:50.859Z","0.3.1":"2026-09-01T04:36:14.905Z","0.3.2":"2026-09-01T08:10:59.360Z"},"bugs":{"url":"https://github.com/capdiem/pi-extensions/issues"},"author":{"name":"capdiem","email":"capdiem@live.com"},"license":"MIT","homepage":"https://github.com/capdiem/pi-extensions#readme","keywords":["pi-package","pi","coding-agent","repetition","repetition-loop","thinking-runaway","runaway","guard","abort","steer"],"repository":{"type":"git","url":"git+https://github.com/capdiem/pi-extensions.git","directory":"extensions/pi-repetition-guard"},"description":"A repetition-loop guard for the Pi coding agent: detects thinking-runaway / 万字复读 (the model repeating the same text in a loop) in streamed output via message_update, then aborts and re-steers the model with a corrective steer message","maintainers":[{"name":"capdiem","email":"capdiem@live.com"}],"readme":"# pi-repetition-guard\n\nA **repetition-loop guard** for the [Pi coding agent](https://pi.dev/). It\ndetects **thinking-runaway / 万字复读** — the failure mode where a model in a\nlong-thinking (or plain output) stage starts repeating the same text in a loop,\nproducing tens of thousands of redundant characters instead of converging to an\nanswer — and **aborts + re-steers** the model so you get a real answer instead\nof a wall of repeated prose.\n\nIt is model-agnostic: it wraps whatever model Pi is calling, observes the\nstreamed output, and never rewrites mid-stream (Pi does not allow that — see\n[ADR 0001](../../docs/adr/0001-ask-user-triggering-metadata-first.md)). It works\npurely on the assistant message text, covering both the `thinking` stream and\nthe final `text` stream (whichever hits the threshold first).\n\n> **Design note (ADR 0002):** this extension deliberately performs *active\n> intervention* (`ctx.abort()` + a corrective `steer` message), which goes\n> beyond the \"no active injection\" decision in ADR 0001. That ADR was scoped to\n> `ask_user` triggering; the repetition guard is a safety mechanism where\n> active abort is the whole point. See\n> [docs/adr/0002-repetition-guard-active-abort.md](../../docs/adr/0002-repetition-guard-active-abort.md).\n\n## Features\n\n- **Two runaway detectors**, both model-agnostic and strictly within-message:\n  - **Text repetition loop** (thinking-runaway / 万字复读) — the model re-emits\n    the same text contiguously during long thinking or output. Detected via\n    **tail periodicity** on `message_update`: the trailing `2×P` characters\n    being an exact repetition of a fixed unit `P` (a \"tape loop\").\n  - **Variation loop** (v5) — the model rewording the same intent over and over\n    (\"Let me read L1000-1035\" / \"Let me read the signature end\" / ...) without\n    advancing. Detected via **short-segment near-repetition dominance**: if\n    ≥90% of a message's short segments (≤100 chars) are near-duplicates of each\n    other (5-gram shingle overlap ≥0.7), the message is a loop. Restricting to\n    short segments is what keeps it from false-positiving on legitimately long,\n    templated thinking (which is built of long paragraphs).\n  - **Tool-call loop** — the model repeatedly invokes the **same tool with the\n    same input** and never settles (e.g. checking `git status` / reading a file\n    over and over). Detected on `tool_call` by counting identical\n    (tool, normalized-input) calls in a sliding window.\n- The text signals are OR'd: a message triggers if either exact tail\n  periodicity OR short-segment near-repetition dominance fires.\n- **Zero false positives on legitimate content** — long self-verifying thinking,\n  recaps, code, and templated lists interleave new material, so their tails are\n  never periodic; varied tool usage (distinct calls) never trips the tool-loop\n  counter. (v1 used a trailing-window shingle-novelty signal that\n  false-triggered on legitimate long thinking — the bug this v2/v3 line fixes.\n  See [ADR 0002](#design-notes-adr-0002) and [CONTEXT.md](../../CONTEXT.md)\n  触发策略.)\n- **Intervention**, per failure mode:\n  - *Text loop*: `ctx.abort()` stops the generation, then a corrective `steer`\n    is sent from `message_end` with a **truncated sample of the repeated unit**.\n  - *Tool loop*: the repetitive tool call is **blocked** (`tool_call` →\n    `{ block, terminate }`), then a steer naming the tool + repeated input is\n    sent after the run settles.\n- **Action-oriented steering (v4)**: steer messages redirect the model to\n  *execute*, not to \"give an answer\" — a stuck loop is usually \"rehearsing the\n  next action without doing it\" (e.g. repeating \"let me check git status\"\n  without ever committing), so the steer tells it to stop describing and do the\n  operation.\n- **Retry budget**: max **2** steer retries per logical user turn, with\n  escalating wording (1 → 2) and a clear \"give up or state the blocker\"\n  fallback on the final retry.\n- **Auto-compact and continue**: when the retry budget is exhausted (still\n  looping after 2 steers), the guard compacts the context and retries **once**\n  more (`MAX_COMPACTION_RETRIES = 1`) with a fresh budget, then gives up. This\n  attacks the long-context degradation that feeds the loop (a model stuck in a\n  loop is usually also drowning in its own garbage; shrinking the context gives\n  it a clean shot at the original task). Still hard-capped — no infinite loop.\n\n> **Why compact instead of just giving up (measured root cause):** the infinite\n> loops we observe are not a sampling-level \"repetition bug\" — they are a\n> **long-context tracking failure**. In practice the loop appears almost\n> exclusively on **deepseek-flash** (a fast/cheap tier model with weaker\n> long-context coherence) and mostly once the context has grown to roughly\n> **30% of the 1M window (~300K tokens)**. At that size the model's attention\n> dilutes past its trained effective range: it loses \"what did I just do\" and\n> starts re-issuing the same action or re-stating the same intent. Two\n> consequences matter for the guard:\n> 1. The loop is *context-driven*, so shrinking the context is the direct\n>    countermeasure — compaction puts the model back in the regime where it can\n>    converge, which is why we retry after compacting rather than giving up.\n> 2. The guard's own abort + steer *appends* the junk and the steer to the\n>    context (no cleanup, ADR 0002), which feeds the very degradation that\n>    caused the loop. Auto-compacting breaks that self-reinforcing feedback.\n>    (This is also the argument for *not* raising `MAX_COMPACTION_RETRIES`\n>    casually: every extra cycle costs a compaction and loses detail.)\n- **User-only control**: `/runaway on|off` slash command, default **on**. The\n  guard cannot be disabled by the LLM itself (there is no LLM-callable toggle).\n\n> **Why is there no \"stage-1 / two-stage\" trigger anymore?** The original design\n> had a two-stage trigger (record a \"suspect\" early, abort only on a harder\n> threshold). Measured against realistic content, *every* early-warning signal\n> (shingle novelty, block-repeat, fractional-period) false-triggers on\n> legitimate long structured thinking, while exact ≥2-copy contiguity never\n> does. So the two-stage was collapsed into the single clean signal. See\n> CONTEXT.md 触发策略 for the record.\n\n## Install\n\n```bash\npi install npm:@capdiem/pi-repetition-guard\n```\n\nTo try it without installing:\n\n```bash\npi -e ./extensions/pi-repetition-guard/index.ts\n```\n\n## Usage\n\nNothing to configure — it is on by default and runs silently in the background.\n\n- **Normal operation:** you see nothing. The guard only acts on an unambiguous\n  contiguous tape-loop or a repeated identical tool call.\n- **Text runaway detected:** the generation is aborted and a `steer` retry runs\n  automatically. In the TUI you'll see the aborted message, then the corrective\n  message, then a fresh answer. (The aborted junk stays in history — no cleanup,\n  per the settled design; see ADR 0002.)\n- **Tool-call loop detected:** the repetitive tool call is blocked (a\n  \"Repetition guard: tool-call loop\" reason), the run terminates, and a steer\n  naming the tool + repeated input is sent. If it loops again, the second steer\n  escalates; after the final retry, if it still loops, the context is\n  auto-compacted and the task retried once more before giving up.\n- **Disable / re-enable:**\n  ```\n  /runaway off\n  /runaway on\n  ```\n  A bare `/runaway` toggles. In the TUI the footer status shows `guard:on` /\n  `guard:off`.\n\n## Behavior details\n\n- **Budget reset:** a new logical user turn (a user message that is *not* our\n  own steer) resets the retry budget to 2 (and the per-turn auto-compact counter).\n- **Auto-compact-and-continue flow:** after the 2nd steer fails, the guard queues\n  a compaction, fires it at the next settle point (`message_end` for text loops,\n  `agent_end` for tool loops), and on completion resets the retry budget and\n  sends a \"continue\" steer telling the model to finish the original task from\n  the compacted context. If compaction fails, the guard gives up (no worse than\n  before). Per turn this happens at most `MAX_COMPACTION_RETRIES` times (1), so\n  the guard remains hard-capped — it can never loop forever.\n- **Scope:** only the current assistant message's own text is compared — the\n  detector does not compare against the user's message or prior assistant\n  messages, so restating the user's words never false-triggers.\n- **Trigger signal (text):** either exact tail periodicity (≥2 exact copies of a\n  ≥30-char unit at the tail) OR short-segment near-repetition dominance (≥90% of\n  the message's short ≤100-char segments are near-duplicates). Both strictly\n  within one message. Wholesale contiguous repetition is, by definition, the\n  runaway signature; content that merely *echoes* earlier material while still\n  advancing is not.\n- **Trigger signal (tool):** the same tool called with the same normalized input\n  ≥4 times within the last 8 calls. Varied legitimate tool usage never trips it.\n- **Known limitation:** the text signals are deliberately conservative — a\n  message is only flagged when either exactly periodic OR dominated by short\n  near-duplicate restatements. A loop that is neither (long-form repetition with\n  meaningful variation between copies) may slip through. This is the accepted\n  trade-off to keep false positives at zero. See ADR 0002.\n- **Sampling parameters are untouched:** Pi exposes no built-in\n  `repetition_penalty` / `no_repeat_ngram_size`; this guard detects and\n  intervenes client-side instead of relying on provider-side sampling knobs.\n\n## Diagnostics\n\nDetection events (hard trigger, retry count, budget exhaustion) are logged with\na `[pi-repetition-guard]` prefix. These records support tuning the fixed\ndetection thresholds over time — consistent with the repo's diagnostics\ndiscipline (ADR 0001).\n\n## License\n\nMIT\n","readmeFilename":"README.md"}