diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ab37d48..32a7446 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Professional Claude Code skills marketplace — production-ready skills spanning GitHub and git operations (including current-base contributor PR review), document conversion and generation (Markdown, PDF, PPTX, DOCX), diagram and UI-design extraction, the full audio pipeline (ASR transcription, TTS, transcript correction, meeting minutes), financial and investment-research data, web scraping and content capture, security/PII tooling and secure repomix packaging, macOS and iOS development, CLI demo and terminal automation, prompt and skill engineering, deep research and fact-checking, QA and LLM-evaluation infrastructure, internationalization, network/Tailscale and remote-desktop diagnostics, and Claude Code operations (fast local conversation discovery across Claude Code and Codex, session recovery, CLAUDE.md optimization, statusline, multi-provider profile isolation, troubleshooting, marketplace development, repo health-check). Suite plugins (daymade-audio, daymade-claude-code, daymade-docs, daymade-financial, daymade-skill) bundle related skills under shared namespaces, including the StepFun StepAudio 2.5 audio family. See the Available Skills list in the README for the authoritative per-skill breakdown.", - "version": "1.87.0" + "version": "1.88.0" }, "plugins": [ { @@ -1183,6 +1183,32 @@ "设计", "风格" ] + }, + { + "name": "macos-watchdog", + "description": "Design, deploy, and discipline macOS launchd watchdogs — LaunchAgents/LaunchDaemons that detect a recurring problem and auto-remediate it. Use whenever creating or editing a persistent background monitor / daemon / agent on macOS, writing a launchd plist, scheduling a self-healing script, or when a watchdog has become a disturbance itself: re-launching apps the user quit, firing repeated notifications, re-running its full repair ladder every few minutes on an unfixable network, or hammering the system (crash loops, fork storms, runaway restarts). Also use for stop/disable semantics (bootout vs bootstrap vs disable vs unload), adding cooldown / backoff / notification throttling to a self-healer, binding a monitor's lifecycle to its premise state, or auditing existing LaunchAgents. 中文触发:launchd 守护进程、常驻任务、开机自启、后台监控、定时自愈脚本。 Covers KeepAlive/ThrottleInterval/domains/logging, premise self-checks, auto-cooldown, alert layering, batch throttling.", + "source": "./macos-watchdog", + "strict": false, + "version": "1.0.0", + "category": "developer-tools", + "keywords": [ + "macos", + "launchd", + "launchagent", + "launchdaemon", + "daemon", + "watchdog", + "plist", + "bootout", + "bootstrap", + "monitoring", + "self-healing", + "cooldown", + "backoff", + "守护进程", + "常驻任务", + "后台监控" + ] } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 65de030..6a9bb26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **devils-advocate** (`daymade-financial` v1.1.0): new skill — structured devil's-advocate pressure-testing of an investment thesis against user-supplied evidence materials. A local reimplementation of LinqAlpha's hedge-fund "Devil's Advocate" agent, built from the production prompt template and JSON schema the vendor published on the AWS ML blog (2026-02), and extended with three layers that implementation lacks: a Mauboussin base-rate outside view (materials-bounded — no invented statistics, no side retrieval), a RAND Assumption-Based-Planning signpost list that turns the one-shot critique into a monitoring routine, and an explicit materials-bias/coverage declaration. Flow: decompose the thesis into explicit assertions and implicit assumptions (A1/A2 ids; fact/forecast/mechanism typing; load-bearing test; opposite-conclusion sub-claims must split), retrieve per-assumption counter-evidence under a source-credibility ladder with verbatim citations plus ACH's absent-evidence question, emit an auditable JSON object (`run_metadata`/`findings`/`deferred_assumptions`, `citations` array, `rebuttal` field, risk-flag rubric with an anti-inflation guard) and render a theme-grouped analyst narrative with references and a survived-assumptions list. Evidence-anchoring is load-bearing by design: role-played dissent underperforms authentic dissent (Nemeth 2001/2018), so free-form contrarianism is banned and every counterpoint must cite. Shipped after one fresh-context independent review (P0×1/P1×5/P2×14 — the P0 was a step-renumbering with four dangling cross-references; all fixed and re-verified), plus a full production test by a context-free agent on a real optical-module thesis with 5 research reports: 8 assumptions, 38 mechanically verified verbatim citations, schema-conformant output, and two counterarguments the authoring session's own parallel analysis had missed; the nine ambiguities that test surfaced (risk-flag inflation 6/8 High without a rubric, bare-array output with nowhere to put the coverage declaration, single-citation slot breaking multi-fact counterarguments, and six more) were folded back into the skill in the same session. - **frontend-visual-qa** (v1.12.0): new reference **`reference-parity-decomposition.md`** — the reference-parity profile was the only profile in the skill with no method file attached, and a real engagement proved the cost: a login-page rebuild against a public product's sign-in screen, with this skill loaded, took five user-caught correction rounds because every round fixed exactly the one delta the user's side-by-side screenshot pointed out and then declared parity. The new file makes the measured structural inventory of the reference the first deliverable (anchoring pinned-vs-centered, container vs full-bleed, aspect-ratio ownership, scale ladder, material chrome, column ratios, intra-region alignment, spacing rhythm — each with an operational diagnostic), defines match criteria (categorical relationships match exactly; scalars match at the project's token granularity), and encodes four traps that outlive the inventory: a user-caught delta falsifies the inventory rather than just the pixel; self-authored geometry assertions are Level D *for the parity claim* (a 22-assertion suite stayed green through three consecutive structural misreads — claim-type scoping stated against the host's evidence table, which keeps project E2E at Level B for geometry/regression claims); a vetoed effect ("never crop the image") indicts the structural premise that forces the effect, not the parameter that picks its flavor (`cover`→`contain` swaps cropping for letterboxing inside the same wrong fixed-size container); and user-supplied assets render faithfully by default — a silently chosen crop focal point is editing the user's material. SKILL.md wires the file into the audit-contract step with the lifecycle boundary stated (decomposition is the first act of the audit, applied to the reference, which always already exists; greenfield visual direction still routes to design skills), adds a conditional reference-parity inventory block to the report schema, and states the division of labor with `data_viz_tier_and_token_audit.md` for data-page tier parity. Marketplace description gains the "compare a rendered artifact with a visual reference" clause SKILL.md already carried. Verified by historical-task replay (each of the five failure rounds now has a specific sentence that names it before it happens) plus two fresh-context independent review rounds: round one returned 10 findings (4 substantive — no measurement method/artifact home for static-screenshot decomposition, no matched-verdict tolerance, an inaccurate host evidence-table citation, and a load-window conflict with the skill's after-implementation scope), all 10 fixed; round two verified the fixes. - **claude-code-hooks** (`daymade-claude-code` v1.43.0): new pitfalls **#30** and **#31**, both incidental discoveries from live work on a private hooks repo this session (not synthesized on request). **#30 — `UserPromptSubmit` fires on a task-notification's own arrival, not just on a human keystroke, and the stdin JSON has no field that says which**: a keyword-scanning hook fired the moment a background subagent's completion report landed, because the report's own text happened to match the trigger regex — no human had typed anything nearby. The transcript JSONL distinguishes the two internally (`origin.kind: "human"` vs `"task-notification"`), but that metadata never reaches the hook; the official stdin schema (verified against the live docs, not memory) is exactly `session_id`/`transcript_path`/`cwd`/`permission_mode`/`hook_event_name`/`prompt_id`/`prompt` — nothing marks provenance. SKILL.md's pre-existing "`UserPromptSubmit` only ever sees user input" claim gets a precise footnote rather than a rewrite: the core argument (it can't see the model's own current-turn output) still holds, it just isn't proof `.prompt` always originated from a keystroke. **#31 — a compounding-artifact staleness tracker keyed on file *kind* re-flags files nobody touched, and a written justification can't clear it, because nothing reads prose**: the tracker's `kinds` array accumulates across a whole session-scoped "turn," so re-editing *any* file of an already-flagged kind re-triggers the whole group regardless of a per-file justification already written and committed — the escape hatch its own message describes is real for a human reader, but the mechanism doesn't parse markdown to check whether it was used correctly. An independent fresh-context review — dispatched to *re-derive*, not just read and trust, the three evidentiary claims (the docs schema via its own WebFetch, the transcript shape via its own direct JSONL parse, the tracker's ledger via its own file read) — found every specific factual claim accurate, but caught two real bugs in #30's *prescribed* Fix before merge: the gate condition `origin.kind == "human" and promptSource == "typed"` silently rejects genuine human input arriving mid-turn (`promptSource: "queued"` — confirmed against a real several-sentence human message in this session's own transcript, independently re-verified before applying the fix), corrected to gate on `origin.kind` alone; and the fix told readers to look up `prompt_id` in the transcript JSONL, a string that occurs there 0 times across 1745 records — the field is `promptId`, camelCase, while the hook's own stdin JSON carries snake_case `prompt_id`, the same twin-blind-spot shape pitfall #20 already warns about on a different field pair. +- **macos-watchdog** (v1.0.0): new skill — design, deploy, and discipline macOS launchd watchdogs (LaunchAgents/LaunchDaemons that detect a recurring problem and auto-remediate). Distilled from 15 production watchdogs running on the author's machine and their incident history — the recurring failure mode was never "how to install a plist" but the watchdog itself becoming the disturbance. Ships the **quiet-watchdog contract** (four clauses, each from a real incident): premise-state self-check (a monitor's lifecycle binds to its premise — a recovery watcher kept notifying "still broken" for 2h after the system healed); patient mode (defer disruption, not detection — one measured chain self-recovered in ≤3 min, so force-reconnect on blips was net-harmful); escalating auto-cooldown (a failed repair ladder on an unfixable network re-ran every 5 min forever — ThrottleInterval can't fix this, it throttles process respawn only and has no backoff); and never-resurrect-what-the-user-quit (`open ` launches apps, `open` without `-g` steals foreground — the watchdog read as "I quit it and it came back"). Also covers deploy mechanics that bite (gui vs system domain, StandardOut/ErrorPath, TCC/FDA on the actual interpreter), stop semantics (`unload` is deprecated and gets resurrected by `RunAtLoad` — bootout/bootstrap/disable only), batch-loop throttling by default (an unthrottled replay forked 1,041 procs/sec and pushed the die to 83 °C), and SRE alert layering (page vs ticket, fatigue numbers). Bundles two reusable scripts (`watchdog-cooldown.sh` — source-able escalating cooldown + manual pause state machine; `new-launchagent.sh` — idempotent installer with validation) and an annotated plist template. Eval'd against baseline on three realistic prompts (new-install / noisy-watchdog triage / config audit): with-skill 17/17 assertions vs baseline 16/17 (baseline's one miss: prescribed the deprecated `unload`). The eval harness itself caught a real bug in the cooldown library — the exhausted-round counter went stale during cool-down waits longer than the stale window, pinning backoff at tier 1 forever; fixed in the library and in the production watchdog it was distilled from. - **slides-creator** (v2.0.0): **DEPRECATED — skill retired to a stub, no longer maintained.** Same consolidation as ppt-creator (daymade-docs v1.6.0, same release): the PPT toolchain merge of 2026-08-07 folded its methodology — First Law (user's voice is primary), the ABCDEFG narrative-discussion framework, the baoyu-slide-deck delegation protocol, and the four-layer directory governance — into deck-creator's Route A · narrative in the author's private marketplace. For external users: this is the final version, kept for install compatibility, receiving no further updates; physical removal in a future major release. references/ and scripts/ removed here (git history preserves them); SKILL.md is now a deprecation notice. diff --git a/CLAUDE.md b/CLAUDE.md index 48a8674..74e764f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -371,6 +371,8 @@ This applies when you change ANY file under a skill directory: 89. **docx-creator** - Produce production-grade Word (.docx) documents, especially Chinese ones, by driving the minimax-docx OpenXML engine correctly — alignment-layering rule, per-list numbering restart, and other corrections the underlying engine doesn't ship (daymade-docs suite member) 90. **claude-code-hooks** - Write, test, register, and debug Claude Code hooks — PreToolUse/PostToolUse/SessionStart/Stop Bash guards that enforce a rule the model would otherwise talk itself past, with token-level shlex matching, bash -n + real-JSON end-to-end testing discipline, and multi-profile registration convergence (daymade-claude-code suite member) 91. **devils-advocate** - Structured devil's-advocate pressure-testing of an investment thesis against user-supplied materials — decompose explicit/implicit assumptions, retrieve evidence-linked counterarguments with verbatim citations under a source-credibility ladder, risk-flag with an anti-inflation rubric, add a materials-bounded base-rate outside view, and emit dual-layer output (audit JSON + theme-grouped analyst narrative) plus a monitoring signpost list (daymade-financial suite member) +92. **macos-watchdog** - Design, deploy, and discipline macOS launchd watchdogs (LaunchAgents/LaunchDaemons that auto-remediate recurring problems) — quiet-watchdog contract (premise-state self-check, patient mode, escalating auto-cooldown, never-resurrect-what-the-user-quit), deploy mechanics (gui vs system domain, TCC/FDA, bootout vs deprecated unload), batch-loop throttling, SRE alert layering, with a source-able cooldown library and idempotent installer script +93. **daymade-sector-research** - A-share sector research workflow: compute Top-N gainers across all sector constituents, retrieve announcement windows (weekly/monthly), grade market-sentiment evidence (L1/L2/L3), and run Agent Team parallel orchestration with fresh-context adversarial verification (daymade-financial suite member) **Recommendation**: Always suggest `skill-creator` first for users interested in creating skills or extending Claude Code. diff --git a/README.md b/README.md index 81f9c2f..88104c5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![简体中文](https://img.shields.io/badge/语言-简体中文-red)](./README.zh-CN.md) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Version](https://img.shields.io/badge/version-1.86.0-green.svg)](https://github.com/daymade/claude-code-skills) +[![Version](https://img.shields.io/badge/version-1.88.0-green.svg)](https://github.com/daymade/claude-code-skills) [![Claude Code](https://img.shields.io/badge/Claude%20Code-2.0.13+-purple.svg)](https://claude.com/code) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/daymade/claude-code-skills/graphs/commit-activity) @@ -3289,6 +3289,72 @@ my hook is false-blocking a healthy command debug this hook that's poisoning my session ``` +### 93. **macos-watchdog** - Design & Discipline macOS launchd Watchdogs + +> **Install**: `claude plugin install macos-watchdog@daymade-skills` + +Design, deploy, and discipline macOS launchd watchdogs — LaunchAgents / +LaunchDaemons that detect a recurring problem and auto-remediate it, without +the watchdog itself becoming the disturbance. Distilled from 15 production +watchdogs and their incident history. + +**Key features:** +- The quiet-watchdog contract — premise-state self-check (a monitor's lifecycle binds to its premise), patient mode (defer disruption, not detection), escalating auto-cooldown, and never-resurrect-what-the-user-quit +- Deploy mechanics that bite — gui vs system domain, StandardOut/ErrorPath, TCC/FDA on the actual interpreter, and stop semantics (`unload` is deprecated and gets resurrected by `RunAtLoad` — bootout/bootstrap/disable only) +- Batch-loop throttling by default and SRE alert layering (page vs ticket, fatigue numbers) +- Bundles `watchdog-cooldown.sh` (source-able escalating cooldown + manual pause state machine), `new-launchagent.sh` (idempotent installer with validation), and an annotated plist template + +**Example usage:** +```text +make a launchd watchdog that reconnects my VPN when it drops +my watchdog keeps re-launching an app I quit — make it stop +audit my existing LaunchAgents for noise and crash loops +``` + +### 94. **devils-advocate** - Pressure-Test an Investment Thesis + +> **Install**: `claude plugin install daymade-financial@daymade-skills` +> (suite-only — invoked as `daymade-financial:devils-advocate`) + +Structured devil's-advocate pressure-testing of an investment thesis against +user-supplied evidence materials — evidence-anchored dissent, not role-played +contrarianism (every counterpoint must cite, verbatim). + +**Key features:** +- Decomposes the thesis into explicit assertions and implicit assumptions (fact/forecast/mechanism typing, load-bearing test) +- Retrieves counter-evidence per assumption under a source-credibility ladder with verbatim citations, plus a Mauboussin base-rate outside view bounded to your materials +- Emits an auditable findings JSON and a theme-grouped analyst narrative, and converts the critique into a RAND-style signpost monitoring list + +**Example usage:** +```text +/daymade-financial:devils-advocate +stress-test my bull case on against these three research reports +what would have to be true for this thesis to be wrong? +``` + +### 95. **daymade-sector-research** - A-Share Sector Research Workflow + +> **Install**: `claude plugin install daymade-financial@daymade-skills` +> (suite-only — invoked as `daymade-financial:daymade-sector-research`) + +A-share (Chinese market) sector research workflow: Top-N gainers across all +sector constituents, announcement-window retrieval, and market-sentiment +judgment with graded evidence — executed by an Agent Team with fresh-context +adversarial verification. All public, no-login data sources. + +**Key features:** +- Top-N gainer pipeline across full sector constituent lists (East Money push2 + Sina realtime snapshots, cross-checked) +- Announcement windows per stock (weekly/monthly) from cninfo + East Money, covering Shanghai/Shenzhen/Beijing exchanges +- Sentiment verdicts graded by evidence level — L1 first-hand quotes / L2 timestamped media / L3 unverified headlines — with "rather uncertain than wrong" as the standing discipline + +**Example usage:** +```text +/daymade-financial:daymade-sector-research +医药行业今天的 Top 10 标的 +这些标的最近一个月发过哪些公告 +判断医药板块现在的市场情绪 +``` + --- ## 🎬 Interactive Demo Gallery diff --git a/README.zh-CN.md b/README.zh-CN.md index 1576fd1..c00726d 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,7 +6,7 @@ [![简体中文](https://img.shields.io/badge/语言-简体中文-red)](./README.zh-CN.md) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Version](https://img.shields.io/badge/version-1.86.0-green.svg)](https://github.com/daymade/claude-code-skills) +[![Version](https://img.shields.io/badge/version-1.88.0-green.svg)](https://github.com/daymade/claude-code-skills) [![Claude Code](https://img.shields.io/badge/Claude%20Code-2.0.13+-purple.svg)](https://claude.com/code) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/daymade/claude-code-skills/graphs/commit-activity) @@ -3304,6 +3304,70 @@ completion drive 随时能盖过它;hook 才是一堵墙。 调试这个把 session 搞坏的 hook ``` + +### 93. **macos-watchdog** - 设计与纪律化 macOS launchd 看门狗 + +> **安装**:`claude plugin install macos-watchdog@daymade-skills` + +设计、部署、纪律化 macOS launchd 看门狗——检测复发问题并自动修复的 +LaunchAgent / LaunchDaemon,同时不让看门狗自己变成干扰源。提炼自 +15 个生产环境看门狗及其事故史。 + +**核心能力:** +- 安静看门狗契约——前提状态自检(监控的生命周期绑定其前提)、耐心模式(延迟打扰而非延迟检测)、升级式自动冷却、绝不复活用户主动退出的应用 +- 会咬人的部署细节——gui vs system domain、StandardOut/ErrorPath、作用于真实解释器的 TCC/FDA,以及停止语义(`unload` 已废弃且会被 `RunAtLoad` 复活——只用 bootout/bootstrap/disable) +- 默认批量循环节流 + SRE 告警分层(page vs ticket、疲劳数字) +- 附带 `watchdog-cooldown.sh`(可 source 的升级式冷却 + 手动暂停状态机)、`new-launchagent.sh`(带校验的幂等安装器)与带注释的 plist 模板 + +**使用示例:** +```text +做一个 VPN 掉线自动重连的 launchd 看门狗 +我的看门狗总是把我退掉的 app 又拉起来——让它停 +审查我现有的 LaunchAgent 有没有噪音和崩溃循环 +``` + +### 94. **devils-advocate** - 压力测试你的投资论点 + +> **安装**:`claude plugin install daymade-financial@daymade-skills>` +> (仅随套件提供 — 以 `daymade-financial:devils-advocate` 调用) + +针对你提供的证据材料,对投资论点做结构化的「魔鬼代言人」压力测试—— +基于证据的反对意见,而不是角色扮演的抬杠(每个反方观点都必须逐字引用出处)。 + +**核心能力:** +- 把论点拆成显式断言与隐式假设(事实/预测/机制分型,承重性测试) +- 按来源可信度阶梯为每条假设检索反证(逐字引用),并叠加 Mauboussin 基准率外部视角(不超出你的材料范围) +- 产出可审计的 findings JSON + 按主题分组的分析师叙事,并把批评转成 RAND 式路标监控清单 + +**使用示例:** +```text +/daymade-financial:devils-advocate +用这三份研报压力测试我看多 <股票> 的逻辑 +这个论点要错的话,需要发生什么? +``` + +### 95. **daymade-sector-research** - A股行业投研工作流 + +> **安装**:`claude plugin install daymade-financial@daymade-skills` +> (仅随套件提供 — 以 `daymade-financial:daymade-sector-research` 调用) + +A 股行业投研工作流:全板块成分股 Top N 涨幅计算、公告窗口检索、 +按证据分级判断市场情绪——由 Agent Team 并行执行 + fresh-context 对抗验证。 +全部公开免登录数据源。 + +**核心能力:** +- 全板块成分股 Top N 涨幅管线(东财 push2 + 新浪实时快照双源交叉) +- 按个股检索公告窗口(周/月),覆盖沪深北三所(巨潮 + 东财) +- 情绪结论按证据等级分级——L1 一手行情 / L2 带时间戳媒体 / L3 未核实标题——常驻纪律是「宁可标注不确定,不可给错误答案」 + +**使用示例:** +```text +/daymade-financial:daymade-sector-research +医药行业今天的 Top 10 标的 +这些标的最近一个月发过哪些公告 +判断医药板块现在的市场情绪 +``` + --- ## 🎬 交互式演示画廊 diff --git a/macos-watchdog/.security-scan-passed b/macos-watchdog/.security-scan-passed new file mode 100644 index 0000000..c2828da --- /dev/null +++ b/macos-watchdog/.security-scan-passed @@ -0,0 +1,4 @@ +Security scan passed +Scanned at: 2026-08-17T08:08:24.011382+00:00 +Tool: gitleaks + pattern-based validation +Content hash: bdee3b1128049dc26d58a5dcfe9d832bd836a9be571c1ca8aeeb3de9e54ad1fa diff --git a/macos-watchdog/SKILL.md b/macos-watchdog/SKILL.md new file mode 100644 index 0000000..ee02aa4 --- /dev/null +++ b/macos-watchdog/SKILL.md @@ -0,0 +1,91 @@ +--- +name: macos-watchdog +description: >- + Design, deploy, and discipline macOS launchd watchdogs — LaunchAgents/LaunchDaemons that detect a recurring problem and auto-remediate it. Use whenever creating or editing a persistent background monitor / daemon / agent on macOS, writing a launchd plist, scheduling a self-healing script, or when a watchdog has become a disturbance itself: re-launching apps the user quit, firing repeated notifications, re-running its full repair ladder every few minutes on an unfixable network, or hammering the system (crash loops, fork storms, runaway restarts). Also use for stop/disable semantics (bootout vs bootstrap vs disable vs unload), adding cooldown / backoff / notification throttling to a self-healer, binding a monitor's lifecycle to its premise state, or auditing existing LaunchAgents. 中文触发:launchd 守护进程、常驻任务、开机自启、后台监控、定时自愈脚本。 Covers KeepAlive/ThrottleInterval/domains/logging, premise self-checks, auto-cooldown, alert layering, batch throttling. +--- + +# macOS Watchdog + +A watchdog is a launchd job that periodically detects a recurring problem and remediates it without a human. The craft is not "how to install a plist" — it is **how to keep the watchdog from becoming a new disturbance**: every watchdog on this machine was born from an incident, and the recurring failure mode afterward is the watchdog itself (false "all good" reports, notification floods, re-launching apps the user quit, fork-bomb replays). + +The governing principle, learned the expensive way: **a watchdog's lifecycle is bound to its premise state**. When the condition it exists to fix cannot be fixed by it (broken WiFi, user quit the target app, prerequisite state gone), the watchdog must stand down *by itself* — not wait for a human to disable it. + +## Entry decision tree + +| The situation is… | Go to | +|---|---| +| Installing a NEW watchdog from scratch | § Deploy, then § The quiet-watchdog contract | +| An existing watchdog misbehaves (spam, re-launches apps, hammers) | § The quiet-watchdog contract, diagnose which clause it violates | +| Stopping / disabling / restarting a job | § Stop semantics | +| plist key details (KeepAlive forms, domains, logging, resource limits) | `references/launchd-plist-reference.md` | +| Cooldown/backoff/notification-throttle patterns + sanitized war stories | `references/quiet-watchdog-patterns.md` | +| SRE alert layering (page vs ticket, fatigue numbers) | `references/alert-discipline.md` | + +## The quiet-watchdog contract (the four clauses) + +Before shipping or blessing any watchdog, all four must hold. Each clause exists because a real watchdog violated it. + +### 1. Premise-state self-check — it knows when it has no job + +The script's first act on every run: verify the state that justifies its existence still holds. If not, exit silently — no remediation, no notification, no side effects. + +- A proxy-repair watchdog checks the proxy app is running first; user quit it → skip the cycle. +- A "did the config switch back" watcher checks the config state it watches; already switched → self-stop, not another round of misleading notifications. (Real case: a recovery watcher kept firing for 2h after its premise resolved, sending 3 spurious notices, because nothing told it to stop.) + +### 2. Remediate first, page only on sustained failure + +Detection stays honest on every cycle, but the *disruptive action* defers until the failure persists across N consecutive cycles (patient mode). Rationale: oscillating chains self-recover in minutes; a force-reconnect on a self-limiting blip is net-harmful. Measure your system's real self-recovery window before choosing N (one chain's 94-min observatory run showed ≤3 min self-recovery → N=2 cycles at 5-min interval). + +Escalation ladder (cheap → disruptive): refresh state → restart connection → remote repair. Each rung verifies before climbing. + +### 3. Escalating auto-cooldown — an unfixable environment means silence + +When the full repair ladder fails, the environment is unfixable by the watchdog (broken WiFi, captive portal, dead upstream). The naive behavior — re-run the entire ladder + notification every interval forever — is exactly "the watchdog keeps re-launching the app every 10 minutes." + +`ThrottleInterval` does **not** fix this: it throttles process respawn, is a fixed delay with no backoff, and does nothing for a job that exits 0 after spamming. Cool-down must live in the application layer: + +- Record consecutive exhausted rounds in a state file. +- After each exhausted round, stand down for an escalating tier (e.g. 30 min → 2 h → 6 h, last tier repeats). +- One notification when *entering* cool-down; zero during it. On tier expiry, retry one round; any real heal clears the counter and the cool-down state. +- A manual `pause [duration]` command with a TTL state file is the fallback — but the auto path must work with no human command at all. A disable mechanism that requires the user to remember a command is not a mechanism. + +Reusable implementation: `scripts/watchdog-cooldown.sh` (source it; provides `paused_any`, `record_exhausted`, `clear_exhausted`, `cmd_pause`/`cmd_resume`). + +### 4. Never resurrect what the user explicitly quit + +On macOS, `open ` **launches the app** when it isn't running, and `open` without `-g` steals foreground. A watchdog whose remediation uses URL schemes (or `open -a`, or restarting a GUI app) will read to the user as "I quit it and it came back." + +Gate every such action: check the target process is alive before invoking its scheme, and pass `-g` so a legitimate action never pops a window. If the user quit the app mid-remediation, abort the ladder — cleanup traps must honor the same gate, or the "ensure connected on exit" fallback becomes the resurrector. + +## Deploy (mechanics that bite) + +1. **Location**: user agent → `~/Library/LaunchAgents/` (GUI session context: can `open` apps, show notifications); system daemon → `/Library/LaunchDaemons/` (root, no GUI access). Choose by whether the job needs the user's GUI session, not by habit. +2. **plist**: start from `assets/launchagent.template.plist` (annotated: Label, ProgramArguments, StartInterval, StandardOutPath/StandardErrorPath, ThrottleInterval, Nice). Validate with `plutil -lint`. `ProgramArguments` element 0 = absolute path; never rely on PATH inheritance. +3. **Load/reload**: `launchctl bootstrap gui/$(id -u) `; after editing a plist, `bootout` then `bootstrap` again — launchd's active state must match disk. Force one run with `launchctl kickstart -k gui/$(id -u)/