@chrono-meta/fh-gate 1.4.95 → 1.4.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/AGENTS.md +18 -0
  3. package/CHEATSHEET.md +1 -1
  4. package/knowledge/shared/harness-core/fh_detail_protocols.md +12 -0
  5. package/knowledge/shared/harness-core/ship_readiness_gate.md +7 -4
  6. package/knowledge/shared/learnings/subagent_invocations_log.yaml +43 -1
  7. package/package.json +6 -1
  8. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  9. package/plugins/fh-commons/agents/quench-challenger.md +49 -23
  10. package/plugins/fh-commons/skills/convergence-loop/SKILL.md +14 -0
  11. package/plugins/fh-commons/skills/deliberation/SKILL.md +14 -0
  12. package/plugins/fh-commons/skills/mcp-circuit-breaker/SKILL.md +10 -1
  13. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  14. package/plugins/fh-meta/CHANGELOG.md +36 -0
  15. package/plugins/fh-meta/agents/beginner.md +4 -1
  16. package/plugins/fh-meta/agents/challenger.md +7 -1
  17. package/plugins/fh-meta/agents/expert.md +1 -1
  18. package/plugins/fh-meta/agents/fact-checker.md +7 -1
  19. package/plugins/fh-meta/agents/hub-persona-auditor.md +2 -1
  20. package/plugins/fh-meta/agents/main-player.md +4 -1
  21. package/plugins/fh-meta/agents/persona-innovator.md +10 -2
  22. package/plugins/fh-meta/skills/agent-composer/SKILL.md +2 -2
  23. package/plugins/fh-meta/skills/apex-review/SKILL.md +5 -0
  24. package/plugins/fh-meta/skills/asset-placement-gate/SKILL.md +38 -8
  25. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +16 -2
  26. package/plugins/fh-meta/skills/context-doctor/SKILL_detail.md +45 -10
  27. package/plugins/fh-meta/skills/corpus-grounding-expander/SKILL.md +14 -5
  28. package/plugins/fh-meta/skills/cross-ecosystem-synergy-detection/SKILL.md +93 -30
  29. package/plugins/fh-meta/skills/deep-clarify/SKILL.md +28 -9
  30. package/plugins/fh-meta/skills/fh/SKILL.md +4 -0
  31. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +64 -8
  32. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +20 -7
  33. package/plugins/fh-meta/skills/goal-quench/SKILL.md +48 -15
  34. package/plugins/fh-meta/skills/goal-quench/SKILL_detail.md +58 -11
  35. package/plugins/fh-meta/skills/harness-doctor/SKILL_detail.md +109 -33
  36. package/plugins/fh-meta/skills/harvest-loop/SKILL.md +6 -1
  37. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +126 -17
  38. package/plugins/fh-meta/skills/install-doctor/SKILL.md +50 -14
  39. package/plugins/fh-meta/skills/install-wizard/SKILL.md +26 -7
  40. package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +68 -21
  41. package/plugins/fh-meta/skills/memory-hygiene/SKILL.md +64 -17
  42. package/plugins/fh-meta/skills/meta-prompt-builder/SKILL.md +38 -4
  43. package/plugins/fh-meta/skills/persona-roster-expander/SKILL.md +15 -7
  44. package/plugins/fh-meta/skills/plugin-recommender/SKILL.md +39 -11
  45. package/plugins/fh-meta/skills/plugin-recommender/SKILL_detail.md +24 -7
  46. package/plugins/fh-meta/skills/prompt-regression/SKILL.md +54 -11
  47. package/plugins/fh-meta/skills/salience-splitter/SKILL.md +120 -7
  48. package/plugins/fh-meta/skills/salience-splitter/SKILL_detail.md +46 -13
  49. package/plugins/fh-meta/skills/sim-conductor/SKILL_detail.md +28 -3
  50. package/plugins/fh-meta/skills/steel-quench/SKILL.md +3 -1
  51. package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +72 -14
  52. package/scripts/count_check.sh +47 -1
  53. package/scripts/degrade_direction_scan.sh +276 -6
  54. package/scripts/degrade_probe_capability.sh +105 -0
  55. package/scripts/package_coverage_check.sh +8 -0
  56. package/scripts/psa_probe_capability.sh +78 -0
  57. package/scripts/public_surface_scan_files.sh +8 -0
  58. package/scripts/selfcheck.sh +15 -0
  59. package/scripts/test_capability_entrypoint_shipping.sh +132 -0
  60. package/scripts/test_count_check_readme_format_lanes.sh +75 -0
  61. package/scripts/test_degrade_scan_shell_probes.sh +415 -0
  62. package/scripts/validate_yaml.sh +146 -0
  63. package/templates/degrade_direction_scan.sh +276 -6
@@ -73,18 +73,48 @@ Self-precision catch areas after first cc review (following previous PR self-cat
73
73
  - Check explicit statement that audience-specific guides are limited to original developer environment
74
74
  - Check explicit statement of organization-specific areas
75
75
 
76
- Self-catch areas 0 items = skip this entire catch matrix (no token-filling / following `feedback_simplification_evidence`).
76
+ Self-catch areas 0 items = skip this entire catch matrix do not pad with token-filling to make the section look populated.
77
77
 
78
78
  ### Step 4. Review Comment Attachment
79
79
 
80
- Attach the review comment (8-matrix results + self-catch + refinement suggestions + merge recommendation) via `gh pr comment`. Within this skill's execution authority (automatic).
80
+ **Mandatory before any `gh pr comment`: run `/public-surface-audit` over the composed comment text.**
81
+ A PR comment is a **paste on a public surface**, and the repo's mechanical privacy floor does not
82
+ reach it — the pre-commit confidentiality guard scans *staged tracked content* and has **no view of
83
+ PR-body text** (`.claude/rules/fh_4axis_gate.md §Reviewer-visible evidence` says so explicitly).
84
+ This step's own inputs make that acute: Step 2 matrix #2 greps the operator's **local memory files**,
85
+ so an unfiltered paste can carry absolute home paths and private memory prose onto a public PR.
86
+
87
+ ```
88
+ verdict CLEAN → attach (automatic, within this skill's authority)
89
+ verdict REVIEW / LEAK → do NOT attach. Redact the flagged spans, re-scan, then attach
90
+ verdict NOT_CONFIGURED, or the
91
+ skill is unavailable → do NOT attach automatically. This is an irreversible surface
92
+ (a posted comment is public the instant it lands and may be
93
+ mirrored before deletion) → **fail-closed**: hand the composed
94
+ text to the operator, or take an explicit logged override
95
+ ```
96
+
97
+ **Never paste raw Step 2 grep output.** Write a *sanitized capsule* — what was checked, what it
98
+ returned, what was found — never the matched lines themselves. Same rule as the marker: the file is
99
+ a local artifact, the capsule is what crosses the boundary.
100
+
101
+ Then attach the review comment (8-matrix results + self-catch + refinement suggestions + merge
102
+ recommendation) via `gh pr comment`.
81
103
 
82
104
  > **Detail**: See `SKILL_detail.md §Step 4 Comment Template` — `gh pr comment` heredoc template — read when attaching the comment.
83
105
 
84
106
  ### Step 5. Admin Override Merge Recommendation
85
107
 
86
108
  **User decision delegation** (this skill = review/recording automation / no merge authority):
87
- - Beta stage policy (`enforce_admins: false`) adherence admin override possible
109
+ - **Read the branch-protection state at run time never from this line.** This repo moved to
110
+ `enforce_admins: true` + `required_approving_review_count: 0` on 2026-07-20, and an earlier version
111
+ of this bullet still claimed `false`: a gate skill was recommending an override on a **field that
112
+ had already flipped**. Protection is also two independent layers (legacy + rulesets, strictest
113
+ wins), so one object is never the effective answer — check both:
114
+ `gh api repos/{owner}/{repo}/branches/main/protection` **and**
115
+ `gh api repos/{owner}/{repo}/rules/branches/main`
116
+ - Self-approve is impossible when this cc authored the PR → after a completed review, `--admin` is
117
+ the normal route, not a shortcut
88
118
  - Self-approve blocked (GHE policy) → admin override path adherence
89
119
  - When this cc authored the change, admin override path is mandatory
90
120
  - N+1th operation proof = baseline stabilization acceleration path
@@ -96,39 +126,118 @@ Attach the review comment (8-matrix results + self-catch + refinement suggestion
96
126
  | Stage | Approval |
97
127
  |---|---|
98
128
  | Step 1~3 check auto-activation | **Automatic** (editable afterward) |
99
- | Step 4 review comment attachment | **Automatic** (gh pr comment within this skill's execution authority) |
129
+ | Step 4 review comment attachment | **Automatic only after `/public-surface-audit` on the comment text returns CLEAN.** REVIEW/LEAK → redact and re-scan; NOT_CONFIGURED or audit unavailable → **fail-closed**, hand to the operator (a posted comment is public on landing) |
100
130
  | Step 5 admin override merge execution | **User decision** (this skill = recommendation only / no merge authority) |
101
131
 
102
132
  ## Constraints
103
133
 
104
134
  - **This skill = review/recording automation / no merge authority** — user admin override or other reviewer merge decision
105
135
  - **No single-person decision application** — following `fact-checker` rule (narrow 1 / broad N+1 / this cc self-catch joins fact-checker count)
106
- - **Simplification guard consistency** (`feedback_simplification_evidence`) — when creating/modifying this skill, update SKILL.md only. No new auxiliary files
107
- - **Markdown editing discipline mandatory** (`feedback_markdown_edit_discipline`) — Edit first. No Write
108
- - **Frontmatter description plain text only baseline** (`feedback_skill_frontmatter_description_plain_text`) — avoid markdown bold
136
+ - **Simplification guard consistency** — when creating/modifying this skill, update SKILL.md only. No new auxiliary files
137
+ - **Markdown editing discipline mandatory** — Edit first. No Write
138
+ - **Frontmatter description plain text only baseline** — avoid markdown bold
139
+
140
+ > The three rules above were previously each attributed to a `memory feedback_*.md` file. Those files
141
+ > do not exist (verified 2026-08-11 against the operator's memory root, with a known-positive control
142
+ > in the same run). The **rules stand on their own**; only the pointers were dead, and citing a
143
+ > non-resolving file as the authority is the phantom-reference class this skill is supposed to catch.
144
+ > Do not re-attach a memory citation here unless `ls` resolves it **in the same run that cites it**.
109
145
 
110
146
  > **Detail**: See `SKILL_detail.md §Sister Asset Utilization Path`, `§External User Environment Adaptation Path`, `§Disable Path`, `§Persona Synergy Catch` — cross-ecosystem utilization, external-environment fallback, own-PRS disable resolution, and deep-insight simultaneous-activation handling — read when operating in an external user environment, resolving own-PRS conflict, or coordinating with deep-insight.
111
147
 
112
148
  ## Done When
113
149
 
150
+ This is a **gate/routing skill** — its output is a merge verdict — so every judged condition below
151
+ names its adversarial pairing. No judge-only path.
152
+
114
153
  ```
115
154
  All 5 Steps completed
116
- + Baseline consistency check 8-matrix results output (✅/⚠️/❌ each item)
155
+ mandatory-pass: each step produced its output or is marked N/A with a reason
156
+
157
+ + Baseline consistency check 8-matrix results output (OK/WARN/BLOCK each item)
158
+ — measured: count items REPORTED vs items ACTUALLY CHECKED; the two must
159
+ match. Matrix #2 (memory baseline) reports SKIPPED when no memory file
160
+ resolves — it is never folded into the pass count (see §References)
161
+
162
+ + Axis 1 run in --pr mode with a typed verdict read from
163
+ REGRESSION_GUARD_RESULT_FILE
164
+ — mandatory-pass: result is `pass` or `block`. `skip` and exit 3 are NOT
165
+ passes; they mean Axis 1 did not examine this PR and the recommendation
166
+ may not cite it as green
167
+
168
+ + /public-surface-audit run over the composed comment text BEFORE any
169
+ gh pr comment
170
+ — mandatory-pass, fail-closed: CLEAN attaches; REVIEW/LEAK redact-and-rescan;
171
+ NOT_CONFIGURED or audit unavailable hands to the operator. A posted comment
172
+ is irreversible, so tooling-down is a block, never a free skip
173
+
117
174
  + Review comment attached via gh pr comment command
118
- + Admin override merge recommendation output (merge execution is user's decision)
119
- + External verification path: harvest-loop Step 3.75 Critic isolation Agent can independently judge based on above criteria (skill_quality_rubric.md verifiable criteria)
175
+ mandatory-pass: the comment URL is returned by the command
176
+
177
+ + Admin override merge recommendation output (merge execution is user's
178
+ decision)
179
+ — judged; adversarial pairing: the branch-protection state is re-read at run
180
+ time from BOTH layers (`.../branches/main/protection` and
181
+ `.../rules/branches/main`, strictest wins) in the same run that recommends.
182
+ A recommendation citing this file's prose instead of a live read is
183
+ unfounded — that exact defect already shipped once here, on a field that
184
+ had flipped
185
+
186
+ + External verification path: an isolated Critic agent can reach the same
187
+ verdict from the artifacts alone
188
+ — judged; adversarial pairing: the reviewer-visible evidence must be
189
+ reproducible WITHOUT the author's local files. Any verdict resting on a
190
+ gitignored local artifact ships labelled LOCAL-ONLY ATTESTATION -
191
+ UNVERIFIED, which leaves the condition UNMET rather than met
192
+ (`.claude/rules/fh_4axis_gate.md` §Reviewer-visible evidence)
120
193
  ```
121
194
 
122
- **→ Mandatory when PR contains SKILL.md / rules / templates changes: `bash templates/regression_guard.sh`** — run Axis 1 (backward check) before merge recommendation is issued. If regression_guard exits with M-tier block, merge recommendation must change to ❌ regardless of other checks.
195
+ **→ Mandatory when PR contains SKILL.md / rules / templates changes: run Axis 1 (backward check) in
196
+ `--pr` mode, against the PR's head branch** — before the merge recommendation is issued. If
197
+ regression_guard reports an M-tier block, the merge recommendation must change to ❌ regardless of
198
+ other checks.
199
+
200
+ ```bash
201
+ # Precondition: the PR head branch must exist locally. This skill reads the PR via `gh pr diff`
202
+ # without checking anything out, so fetch the head ref first or --pr has nothing to resolve.
203
+ PR_BRANCH="$(gh pr view "$PR" --json headRefName -q .headRefName)"
204
+ git fetch origin "$PR_BRANCH":"refs/remotes/origin/$PR_BRANCH" # skip if already present
205
+ bash templates/regression_guard.sh --pr "origin/$PR_BRANCH"
206
+ ```
207
+
208
+ ⚠️ **Do not run it with no arguments.** Bare `bash templates/regression_guard.sh` diffs the **working
209
+ tree**, and this skill's own workflow leaves the reviewer standing on a clean `main` — so the bare
210
+ form returns `REGRESSION_GUARD_RESULT=skip` with `exit 0` **100% of the time**, and the mandatory
211
+ Axis-1 gate never examines the PR at all. Measured 2026-08-11 on a clean checkout: bare form →
212
+ `rc=0 / result=skip`; `--pr <branch>` on the same commit → `rc=0 / result=pass` having actually read
213
+ the changed SKILL.md. Canonical form is `--pr {BRANCH}` (`.claude/rules/fh_4axis_gate.md`).
214
+
123
215
  **Read the verdict from the typed channel, not the exit code** — `exit 0` means pass **or** skip
124
216
  (not-checked). Run with `REGRESSION_GUARD_RESULT_FILE=/tmp/rg.$$` and read `result=` from that file:
125
- `skip` means Axis 1 **did not examine** this PR (no matching file) record it as "Axis 1 N/A", never
126
- as a green check. A merge recommendation that cites an unexamined Axis 1 as PASS is the 2026-07-22
127
- fail-open class.
217
+ `skip` means Axis 1 **did not examine** this PR (no matching file, or the wrong invocation form)
218
+ record it as "Axis 1 N/A", never as a green check. `exit 3` means the invocation itself failed
219
+ (unresolvable branch) — also not a pass; fetch the ref and re-run. A merge recommendation that cites
220
+ an unexamined Axis 1 as PASS is the 2026-07-22 fail-open class.
128
221
 
129
222
  ## References
130
223
 
131
- - Rule body: `memory feedback_command_tower_gate.md` (hub gate accumulated naming baseline) + `memory feedback_field_to_hub_sync_protocol.md` (Option C Hybrid sync policy)
132
- - Consistency rules: `feedback_simplification_evidence` · `feedback_markdown_edit_discipline` · `feedback_skill_frontmatter_description_plain_text` · `feedback_bidirectional_self_validation` · `feedback_reference_own_hub_assets_first`
224
+ > ⚠️ **The memory filenames below were audited 2026-08-11 and **none of them exist** 8/8 absent in
225
+ > the operator's own memory root, i.e. they were never reachable, not merely absent externally. They
226
+ > are kept, struck, as the record of a phantom-reference class: a gate skill citing rule bodies that
227
+ > resolve nowhere, while matrix #2 silently "skips" and the run still reports an 8-matrix pass.
228
+ > **Matrix #2 is therefore a 7-matrix in practice** — report it as `matrix 2: SKIPPED (no resolvable
229
+ > memory baseline)` rather than folding it into the pass count (`not found ≠ 0`).
230
+ > Re-populate this list only with paths verified by `ls` **in the same run that cites them**.
231
+ >
232
+ > **Arithmetic reconciled 2026-08-11**: the "8/8" above is now true of the list below — all 8 cited
233
+ > filenames are struck (re-verified in one run: 8 cited / 8 absent, with a known-positive and a
234
+ > known-negative control). Previously only 7 were struck while the 8th
235
+ > (`feedback_autonomous_commit_proposal`) was still cited live, and three more were cited as live
236
+ > authority up in §Constraints and §Step 3 — where an executor actually reads, since References is
237
+ > not on the execution path. **Those live citations are removed; the rules they carried are stated
238
+ > directly.** A struck entry in References is not a fix if the same name is still load-bearing above.
239
+
240
+ - ~~Rule body: `memory feedback_command_tower_gate.md` + `memory feedback_field_to_hub_sync_protocol.md`~~ — **absent (verified 2026-08-11)**
241
+ - ~~Consistency rules: `feedback_simplification_evidence` · `feedback_markdown_edit_discipline` · `feedback_skill_frontmatter_description_plain_text` · `feedback_bidirectional_self_validation` · `feedback_reference_own_hub_assets_first`~~ — **absent (verified 2026-08-11)**
133
242
  - Sister skills: `cross-ecosystem-synergy-detection` (sister asset cluster baseline) · `verify-bidirectional` (bidirectional self-validation automation / self-catch auxiliary axis) · `harvest-loop` (weekly audit automation / operation proof accumulation cross-link)
134
- - Autonomous commit proposal §2.19 baseline: `memory feedback_autonomous_commit_proposal.md` ( development source automation + PR proposal under human approval)
243
+ - ~~Autonomous commit proposal §2.19 baseline: `memory feedback_autonomous_commit_proposal.md`~~ — **absent (verified 2026-08-11)**. The rule it stood for is live and lives in `CLAUDE.md §AI Contribution Model`: development-source automation is allowed, PR submission requires explicit human approval. Cite that, not this filename.
@@ -221,20 +221,38 @@ If existing retrospective/audit files exist → `harvest-loop` will create files
221
221
  # Check MCP server transport settings
222
222
  grep -r "\"transport\"" .mcp.json 2>/dev/null | grep -i "http\|sse"
223
223
 
224
- # List MCP servers using HTTP transport
225
- python3 -c "
226
- import json, sys
224
+ # List MCP servers using HTTP transport.
225
+ # `except: pass` made this exit 0 on ANY failure, so the `|| echo` fallback was dead code and a
226
+ # corrupt .mcp.json rendered identically to "no risky servers" — a silent pass on a security check.
227
+ # Four states, four distinct exits: absent(0) · unparseable(2) · risky(1) · all-stdio(0).
228
+ python3 - <<'PY'
229
+ import json, os, sys
230
+ p = '.mcp.json'
231
+ if not os.path.exists(p):
232
+ print(' MCP-CHECK: NOT-APPLICABLE — .mcp.json absent (no MCP servers configured)')
233
+ sys.exit(0)
227
234
  try:
228
- d = json.load(open('.mcp.json'))
229
- servers = d.get('mcpServers', {})
230
- for name, cfg in servers.items():
231
- t = cfg.get('transport', 'stdio')
232
- if t != 'stdio':
233
- print(f' ⚠️ {name}: transport={t}')
234
- except: pass
235
- " 2>/dev/null || echo " .mcp.json absent or unparseable"
235
+ d = json.load(open(p))
236
+ except Exception as e:
237
+ print(f' MCP-CHECK: UNPARSEABLE {p}: {e}')
238
+ print(' Transport risk UNMEASURED — this is NOT a pass. Fix the file and re-run.')
239
+ sys.exit(2)
240
+ servers = (d.get('mcpServers') or {})
241
+ risky = [(n, (c or {}).get('transport', 'stdio')) for n, c in servers.items()
242
+ if (c or {}).get('transport', 'stdio') != 'stdio']
243
+ if risky:
244
+ for n, t in risky:
245
+ print(f' WARNING {n}: transport={t} — verify localhost binding + auth')
246
+ sys.exit(1)
247
+ print(f' MCP-CHECK: PASS — {len(servers)} server(s), all stdio')
248
+ PY
236
249
  ```
237
250
 
251
+ > **Read the exit code, not just the text.** `2` (unparseable) is an *unmeasured* check and must be
252
+ > reported 🟧 in Step 3, never folded into 🟩. Known-pair calibration for this block: a deliberately
253
+ > truncated `.mcp.json` must yield `2`, and a config with one `transport: http` server must yield `1`.
254
+ > If both print nothing, the check is dead.
255
+
238
256
  **Known MCP HTTP transport vulnerability patterns** (based on HTTP port exposure security principles): When MCP servers using HTTP/SSE transport expose ports without authentication, remote access risk within local networks may occur. stdio transport is not affected.
239
257
 
240
258
  | Transport | Risk |
@@ -283,11 +301,29 @@ If you installed only the plugin in a different project cwd:
283
301
  ## Done When
284
302
 
285
303
  ```
286
- All Steps 1~4 completed
287
- + Step 3 diagnosis report output (🟥 immediate action / 🟧 check recommended / 🟩 no conflicts)
288
- + "Plugin install is safe in current environment" confirmed when 0 conflicts
304
+ Steps 0~4 completed (mandatory-pass)
305
+ + Step 0 node-floor results recorded per check, each as one of
306
+ PASS / FAIL / UNMEASURED never blank (measured: 2 floors
307
+ hook exec-bit, SessionStart
308
+ registration)
309
+ + Step 2-6 MCP transport check exited 0/1/2 and the exit code is
310
+ carried into the Step 3 tier (2 = UNMEASURED → 🟧, never 🟩) (mandatory-pass)
311
+ + Step 3 diagnosis report emitted with a tier per area
312
+ (🟥 immediate action / 🟧 check recommended / 🟩 no conflicts) (mandatory-pass)
313
+ + Verdict is derived from the recorded per-area tiers: 🟩 only if
314
+ every area is 🟩 AND none is UNMEASURED (measured: count of
315
+ non-🟩 areas == 0)
289
316
  ```
290
317
 
318
+ **Why `Steps 0~4`, not `1~4`**: Step 0 holds the only *mechanical floor* checks in this skill (hook
319
+ exec-bit, SessionStart registration, Node floor). Leaving it outside the completion condition let a
320
+ run report "done" having never touched the one part that is not judgement.
321
+
322
+ **Why the verdict is counted, not stated**: the previous condition was `"Plugin install is safe in
323
+ current environment" confirmed` — that measures whether a *sentence was printed*, which a run
324
+ satisfies by printing it. An unmeasured area (e.g. an unparseable `.mcp.json`) must not be
325
+ absorbed into 🟩; absence of a finding is not a finding of absence.
326
+
291
327
  ## Simplification Guard
292
328
 
293
329
  - 0 existing assets (new environment) → output "New environment. No conflicts." and exit immediately in Step 1
@@ -301,13 +301,32 @@ On Claude API / MCP failure → refer to [`references/fallback-guide.md`](../../
301
301
  ## Done When
302
302
 
303
303
  ```
304
- ☐ Environment detection complete: shell, CC version, OS, project type identified
305
- Gap diagnosis output: present vs missing items listed
306
- User approval/decline recorded for each suggested item
307
- All approved items installed with no failure state
308
- (failed installs surfaced to user not silently skipped)
309
- Acceleration baseline confirmed: zshrc alias + sentinels active (or user declined)
310
- Summary output: "N items installed, M items skipped — setup complete"
304
+ ☐ Environment detection complete: shell, CC version, OS, project type
305
+ identified each field carries a value or the literal "unknown",
306
+ never blank (measured: 4 fields resolved)
307
+ Settings probe distinguished ABSENT / UNPARSEABLE / OK — an
308
+ unparseable config is never treated as absent (mandatory-pass)
309
+ Gap diagnosis output: present vs missing items listed (measured: count of items
310
+ present + missing == items
311
+ scanned)
312
+ ☐ User approval/decline recorded for each suggested item —
313
+ a missing answer is a decline, never an assumed yes (measured: recorded answers
314
+ == suggested items)
315
+ ☐ All approved items installed with no failure state; every
316
+ failure surfaced to the user, not silently skipped (mandatory-pass)
317
+ ☐ Acceleration baseline: zshrc block either appended with
318
+ SUBSTITUTED values (no literal "{FH_DIR}" in the target file)
319
+ or explicitly declined and the decline recorded (mandatory-pass)
320
+ ☐ Step 3-D dispatch consent recorded in the three-part form
321
+ (quoted words · dated lease · scope) or a recorded decline —
322
+ a two-part record is invalid and counts as absent (mandatory-pass)
323
+ ☐ Summary output: "N items installed, M items skipped" where
324
+ N + M equals the number of items offered (measured: N + M == offered)
325
+ ☐ Nothing was overwritten that the user did not approve (judged — adversarial pairing:
326
+ re-run the wizard on a
327
+ populated .claude/ and diff
328
+ the tree before/after; any
329
+ unapproved delta is a FAIL)
311
330
  ```
312
331
 
313
332
  `--dry-run` mode Done When: gap diagnosis report written, no installation executed.
@@ -222,13 +222,23 @@ echo "CC_HUB_DIR=${CC_HUB_DIR:-not set}"
222
222
  basename "$(pwd)"
223
223
  ls .claude/ 2>/dev/null
224
224
 
225
- # CC settings (handle both dict and list for plugins)
226
- cat .claude/settings.json 2>/dev/null | python3 -c "
227
- import json,sys
228
- d=json.load(sys.stdin)
229
- p=d.get('plugins',{})
230
- print('plugins:', list(p.keys()) if isinstance(p,dict) else p)
231
- " 2>/dev/null || echo "settings.json not found"
225
+ # CC settings (handle both dict and list for plugins).
226
+ # Split existence from parseability FIRST. The old `cat file | python3 || echo "not found"` read
227
+ # $? from python, so a CORRUPT settings.json printed "settings.json not found" — the wizard would
228
+ # then happily create a fresh one and silently clobber the user's real (broken) config.
229
+ if [ ! -f .claude/settings.json ]; then
230
+ echo "settings.json: ABSENT"
231
+ else
232
+ python3 - <<'PY' || echo "settings.json: UNPARSEABLE (present but unreadable — NOT the same as absent; do NOT overwrite, ask the user)"
233
+ import json, sys
234
+ try:
235
+ d = json.load(open('.claude/settings.json'))
236
+ except Exception as e:
237
+ print(f' parse error: {e}', file=sys.stderr); sys.exit(2)
238
+ p = d.get('plugins', {})
239
+ print('settings.json: OK — plugins:', list(p.keys()) if isinstance(p, dict) else p)
240
+ PY
241
+ fi
232
242
 
233
243
  # MCP plugin connection status
234
244
  python3 -c "import json,os; d=json.load(open(os.path.expanduser('~/.claude.json'))); print('MCP:', list(d.get('mcpServers',{}).keys()))" 2>/dev/null || echo "MCP config not found"
@@ -319,9 +329,18 @@ Reduction targets are only meta operation rules (PR procedures, commit guides, F
319
329
  **Detection bash:**
320
330
 
321
331
  ```bash
322
- # Detect existing harness scale
323
- CLAUDE_MD_LINES=$(wc -l < CLAUDE.md 2>/dev/null || echo 0)
324
- RULES_COUNT=$(ls .claude/rules/*.md 2>/dev/null | wc -l || echo 0)
332
+ # Detect existing harness scale.
333
+ # `| wc -l || echo 0` is a conditional disarm, not a harmless idiom: with `set -o pipefail` active
334
+ # (which the caller's shell may well have) a failing upstream stage makes the pipeline exit non-zero
335
+ # AFTER wc has already printed its count, so the fallback appends a SECOND line, the value becomes
336
+ # "0\n0", and the `-ge 3` test below dies with "integer expression expected" — on stderr only —
337
+ # leaving the guard silent. Measured both directions 2026-08-12: single line without pipefail,
338
+ # two lines with it. Sanitize instead of falling back.
339
+ CLAUDE_MD_LINES=$(wc -l < CLAUDE.md 2>/dev/null || true)
340
+ RULES_COUNT=$(ls .claude/rules/*.md 2>/dev/null | wc -l || true)
341
+ # head -n1 FIRST, then strip: `tr -dc '0-9'` alone would fuse a two-line "12\n0" into "120".
342
+ CLAUDE_MD_LINES=$(printf '%s\n' "$CLAUDE_MD_LINES" | head -n1 | tr -dc '0-9'); CLAUDE_MD_LINES=${CLAUDE_MD_LINES:-0}
343
+ RULES_COUNT=$(printf '%s\n' "$RULES_COUNT" | head -n1 | tr -dc '0-9'); RULES_COUNT=${RULES_COUNT:-0}
325
344
 
326
345
  echo "CLAUDE.md: ${CLAUDE_MD_LINES} lines"
327
346
  echo ".claude/rules/: ${RULES_COUNT} files"
@@ -493,6 +512,15 @@ propose adding rows to `agent-composer/SKILL.md` Step 1 mapping table in this fo
493
512
  | {skill name} related task | {skill name} (S) | — |
494
513
  ```
495
514
 
515
+ > **Verify this block against a temp file, never against `~/.zshrc`.** Set `ZSHRC_TARGET` to a
516
+ > scratch path and run the five arms: FH_DIR unset → rc=1, nothing written · CC_HUB_DIR unset →
517
+ > rc=1, nothing written · `$FH_DIR` without the script → ABORT, nothing written · consent absent →
518
+ > SKIPPED, nothing written · consent Y with both vars → block appended with values **substituted**
519
+ > and `$HOME`/`$FH_DIR` left literal, then `zsh -c 'source <temp>'` exits 0. Re-running must not
520
+ > duplicate the block. **Beware a contaminated control**: if your own shell already exports
521
+ > `FH_DIR`, the "unset" arm is not actually negative — isolate with `env -u FH_DIR`. That mistake
522
+ > made the first pass of this very verification report a false PASS.
523
+
496
524
  Output preview before execution:
497
525
  ```
498
526
  ▶ agent-composer mapping update
@@ -507,18 +535,37 @@ Output preview before execution:
507
535
 
508
536
  ```bash
509
537
  # zshrc hook — preview then confirm. The wizard is AI-mediated: SHOW the user the exact block
510
- # below and ask in-chat "Append this to ~/.zshrc? (Y/N)" BEFORE running the append. An earlier
511
- # revision's comment promised preview-then-confirm while the bash appended unconditionally —
512
- # the decline-integrity sim (2026-08-10) caught the mismatch; the gate is the instruction, the
513
- # bash below runs ONLY on Y.
514
- if ! grep -q "fh_audit_check.zsh" ~/.zshrc 2>/dev/null; then
515
- # (run only after an explicit in-chat Y)
516
- cat >> ~/.zshrc << 'EOF'
517
- export FH_DIR="{FH_DIR}"
518
- export CC_HUB_DIR="{CC_HUB_DIR}"
519
- export CC_SENTINELS_DIR="$HOME/.cc_sentinels"
520
- source "$FH_DIR/templates/fh_audit_check.zsh"
538
+ # below and ask in-chat "Append this to ~/.zshrc? (Y/N)" BEFORE running the append.
539
+ #
540
+ # TWO defects were fixed here (2026-08-12), both of which corrupt every future shell:
541
+ # (1) The heredoc was QUOTED (<< 'EOF'), so the literal text `{FH_DIR}` was written to ~/.zshrc.
542
+ # The resulting `source "$FH_DIR/templates/fh_audit_check.zsh"` then resolved to
543
+ # `{FH_DIR}/templates/...` and every subsequent shell start failed with
544
+ # `no such file or directory` (rc=127, reproduced in a sandbox against a temp file).
545
+ # Fix: unquoted heredoc so FH_DIR/CC_HUB_DIR expand AT WRITE TIME, with `\$HOME` and
546
+ # `\$FH_DIR` escaped so THOSE stay literal and resolve at shell-start.
547
+ # (2) The consent gate was a COMMENT ("run only after an explicit in-chat Y"). A comment does
548
+ # not gate anything — pasted or scripted, the block appended unconditionally. It is now a
549
+ # real conditional on FH_WIZARD_ZSHRC_CONSENT, default N (fail-closed).
550
+ #
551
+ # Set FH_WIZARD_ZSHRC_CONSENT=Y only after the user answers Y in chat.
552
+ ZSHRC="${ZSHRC_TARGET:-$HOME/.zshrc}"
553
+ : "${FH_DIR:?FH_DIR is unset — refusing to write a broken source line into $ZSHRC}"
554
+ : "${CC_HUB_DIR:?CC_HUB_DIR is unset — refusing to write an incomplete block into $ZSHRC}"
555
+ if [ ! -f "$FH_DIR/templates/fh_audit_check.zsh" ]; then
556
+ echo "ABORT: \$FH_DIR/templates/fh_audit_check.zsh does not exist — would wire a dead source line"
557
+ elif [ "${FH_WIZARD_ZSHRC_CONSENT:-N}" != "Y" ]; then
558
+ echo "zshrc hook: SKIPPED (no explicit Y) — nothing written"
559
+ elif grep -q "fh_audit_check.zsh" "$ZSHRC" 2>/dev/null; then
560
+ echo "zshrc hook: already present — no change"
561
+ else
562
+ cat >> "$ZSHRC" <<EOF
563
+ export FH_DIR="$FH_DIR"
564
+ export CC_HUB_DIR="$CC_HUB_DIR"
565
+ export CC_SENTINELS_DIR="\$HOME/.cc_sentinels"
566
+ source "\$FH_DIR/templates/fh_audit_check.zsh"
521
567
  EOF
568
+ echo "zshrc hook: appended"
522
569
  fi
523
570
  # On N: do NOT append; record the decline and state its consequence in one line —
524
571
  # echo "zshrc_hook" >> "$HOME/.cc_sentinels/{project}_wizard_declined"
@@ -56,15 +56,31 @@ re-verification is possible).
56
56
 
57
57
  ### Step 1 — Scan memory/*.md
58
58
 
59
+ Enumerate **every** root with a per-root state — an absent root and an empty root are different
60
+ facts, and the Step-4 snapshot scope is bound to this enumeration:
61
+
59
62
  ```bash
60
- ls ~/.claude/projects/*/memory/*.md 2>/dev/null
61
- # or hub-local:
62
- ls memory/*.md 2>/dev/null
63
+ for ROOT in "$HOME"/.claude/projects/*/memory "$PWD/memory"; do
64
+ if [ -d "$ROOT" ]; then
65
+ n=$(ls "$ROOT"/*.md 2>/dev/null | wc -l | tr -d ' ')
66
+ echo "EXISTS(n=$n) $ROOT"
67
+ else
68
+ echo "ABSENT $ROOT"
69
+ fi
70
+ done
63
71
  ```
64
72
 
73
+ **`ABSENT` is never counted as 0 entries.** Swallowing both into `2>/dev/null` makes "this root does
74
+ not exist here" indistinguishable from "this root is empty" — and the snapshot in Constraints below
75
+ must cover every root reported `EXISTS`, so a root mis-rendered as empty silently narrows the
76
+ snapshot scope.
77
+
65
78
  For each file, extract:
66
79
  - `metadata.type` from frontmatter
67
- - `date:` or any date-like field in frontmatter or body
80
+ - **Age source, in this order** (measured over the live corpus, n=267): `metadata.modified`
81
+ (ISO8601, present on 77/267) → else the file's filesystem mtime → else **`AGE-UNKNOWN`**.
82
+ There is **no top-level `date:` field in this corpus** (measured 0/267) — do not look for one,
83
+ and never treat `AGE-UNKNOWN` as fresh.
68
84
  - Key factual claims (GitHub URLs, status strings, version numbers, dates)
69
85
 
70
86
  ### Step 2 — Classify by Staleness
@@ -89,7 +105,9 @@ For each stale entry, run the appropriate re-verification:
89
105
  **Reference type** (URLs, DOIs, GitHub):
90
106
  - Use `gh api` for GitHub resources
91
107
  - Use `WebFetch` for DOIs and arXiv records
92
- - Mark `verified_at: YYYY-MM-DD` in frontmatter if still valid
108
+ - Mark `metadata.verified_at: YYYY-MM-DD` **nested under `metadata:`**, the layer this corpus
109
+ actually uses (`metadata:` present on 265/267; a top-level `verified_at` measured 0/267 and would
110
+ be invisible to the Step-1 extractor)
93
111
  - Flag `⚠ DRIFTED` if content has changed materially
94
112
 
95
113
  **Project type** (status, milestones):
@@ -121,19 +139,47 @@ Apply updates? [y / N per item]
121
139
 
122
140
  ### Step 5 — Record Run
123
141
 
142
+ Use `printf`, never `echo "…\n…"` — `echo` expands `\n` in zsh but writes it **literally** in bash
143
+ (measured: 6 lines vs 1), which silently produces a one-line file with a broken frontmatter.
144
+
124
145
  ```bash
125
- # Create hygiene log
126
- echo "---\ndate: $(date +%Y-%m-%d)\nentries_checked: N\nupdated: N\ndrifted: N\n---" \
127
- > tracks/_meta/memory_hygiene_$(date +%Y-%m-%d).md
146
+ mkdir -p tracks/_meta
147
+ printf '%s\n' \
148
+ '---' \
149
+ "date: $(date +%Y-%m-%d)" \
150
+ "entries_checked: ${ENTRIES_CHECKED}" \
151
+ "demoted_to_archive: ${DEMOTED}" \
152
+ "orphans_indexed: ${ORPHANS}" \
153
+ "merged: ${MERGED}" \
154
+ "drifted: ${DRIFTED}" \
155
+ "hard_deleted: ${HARD_DELETED}" \
156
+ '---' \
157
+ > "tracks/_meta/memory_hygiene_$(date +%Y-%m-%d).md"
128
158
  ```
129
159
 
160
+ **Every count is a measured value from Steps 1–4.** A log still containing a literal `N` (or an
161
+ unexpanded `${…}`) is **an invalid log** — treat that run as not-recorded, because the log's
162
+ existence is what arms the 7-day cadence guard, so a placeholder log suppresses the *next* real run.
163
+
130
164
  ## Constraints
131
165
 
132
166
  - **No auto-deletion**: Archive candidates are proposed, not deleted. Human confirmation required.
133
167
  - **Snapshot before archive (Destructive-Op Gate for memory)**: before applying any confirmed
134
168
  archive/removal in Step 4, snapshot **every memory root the entry could live in** first — tar **all**
135
169
  roots Step 1 enumerated (`~/.claude/projects/*/memory/` AND hub-local `memory/`), not just one:
136
- `tar czf tracks/_meta/memory_snapshot_$(date +%Y%m%d-%H%M%S).tgz <each Step-1 root that exists>`so
170
+ the snapshot **must be gated on its own exit status** an unwritable or missing `tracks/_meta`
171
+ makes `tar` exit 1 and produce no tarball (measured), and without a gate the irreversible archive
172
+ proceeds anyway:
173
+
174
+ ```bash
175
+ SNAP="tracks/_meta/memory_snapshot_$(date +%Y%m%d-%H%M%S).tgz"
176
+ mkdir -p tracks/_meta || { echo "SNAPSHOT FAILED (mkdir) — abort archive"; exit 1; }
177
+ tar czf "$SNAP" <each Step-1 root reported EXISTS> || { echo "SNAPSHOT FAILED (tar) — abort archive"; exit 1; }
178
+ [ -s "$SNAP" ] || { echo "SNAPSHOT FAILED (empty tarball) — abort archive"; exit 1; }
179
+ echo "SNAPSHOT OK: $SNAP"
180
+ ```
181
+
182
+ Only on `SNAPSHOT OK` may the archive proceed — so
137
183
  a wrong archive in any scanned root is one-command recoverable (a single-root snapshot can pass while
138
184
  the archived entry lived in the other root — bind the scope to the entry, not to "a tarball exists").
139
185
  Archive moves entries to a `.archive/` sibling, never hard-deletes (mirrors the Curator's
@@ -147,14 +193,15 @@ echo "---\ndate: $(date +%Y-%m-%d)\nentries_checked: N\nupdated: N\ndrifted: N\n
147
193
 
148
194
  ## Done When
149
195
 
150
- ```
151
- Step 1~5 complete
152
- + Staleness roster output (Step 2 = mechanical no-LLM pre-pass)
153
- + Re-verification run for all STALE entries
154
- + User gate presented and responded to (y/N per item)
155
- + If any archive confirmed: snapshot covering the archived entry's root written before the move (tracks/_meta/memory_snapshot_*.tgz, spanning all existing Step-1 roots) check class: mandatory-pass (snapshot covers the entry's dir, not merely "a tarball exists")
156
- + Hygiene log written to tracks/_meta/memory_hygiene_{date}.md
157
- ```
196
+ | Condition | Check class |
197
+ |---|---|
198
+ | Step 1~5 complete, every memory root reported `EXISTS(n=…)` or `ABSENT` | **mandatory-pass** |
199
+ | Staleness roster output (Step 2 = mechanical no-LLM pre-pass), with `AGE-UNKNOWN` entries listed separately and not as FRESH | **measured** (age from `metadata.modified` / mtime, not recall) |
200
+ | Re-verification run for all STALE entries | **mandatory-pass** |
201
+ | Each re-verified entry's verdict (VERIFIED / DRIFTED) is correct | **judged** adversarial pairing: `fh-meta:fact-checker` re-greps the hub for the same claim; any disagreement downgrades the entry to DRIFTED (no judge-only PASS) |
202
+ | User gate presented and responded to (y/N per item) | **mandatory-pass** |
203
+ | If any archive confirmed: snapshot written **and its exit status checked** before the move (`tracks/_meta/memory_snapshot_*.tgz`, spanning every root reported `EXISTS`) | **mandatory-pass** — snapshot must cover the archived entry's dir; `SNAPSHOT OK` printed, not merely "a tarball exists" |
204
+ | Hygiene log written to `tracks/_meta/memory_hygiene_{date}.md` with real counts (no literal `N`, no unexpanded `${…}`) | **measured** |
158
205
 
159
206
  ## References
160
207
 
@@ -153,11 +153,45 @@ Acceptable form examples:
153
153
 
154
154
  ## Done When
155
155
 
156
+ This skill's own Done When must satisfy the 3-part completeness check it imposes on others
157
+ (**measurement subject · measurement timing · version/baseline**). The previous version —
158
+ `All steps 0–3 completed` — met 0 of 3 and matched its own `Unconditioned completion` WARN pattern.
159
+
156
160
  ```
157
- All steps 0–3 completed
158
- + Per-agent prompt drafts output per Wave (Goal/Context/Constraints/Done When 4-field included)
159
- + Step 3 quality self-validation complete ([WARN] items delegated to user if present)
160
- + Generated prompts awaiting user review
161
+ Measurement subject: this skill's own output — the prompt draft set
162
+ produced in this invocation (not the dispatched
163
+ agents' results, which this skill never sees)
164
+ Measurement timing: absolute, at end of this run — no comparison to a
165
+ previous run is implied or required
166
+ Version / baseline: the Step 3 checklist and the WARN-trigger table in
167
+ THIS file; note the SKILL.md revision used if the
168
+ table has changed since the draft was written
169
+
170
+ ☐ Steps 0–3 each produced their named artifact — a step with no
171
+ artifact is incomplete, not "completed" (measured: 4 steps, 4 artifacts)
172
+ ☐ One prompt draft exists per agent named in the dispatch plan (measured: drafts == agents
173
+ in plan)
174
+ ☐ Every draft carries all four fields (Goal / Context /
175
+ Constraints / Done When), none empty (measured: 4 fields × N drafts,
176
+ 0 empty)
177
+ ☐ Each draft's own Done When was run through the 3-part
178
+ completeness check and the WARN-trigger table, with the
179
+ result recorded per draft (mandatory-pass)
180
+ ☐ Every Goal was checked against the target agent's declared
181
+ allowed-tools by READING that agent's SKILL.md — not from
182
+ recall (mandatory-pass)
183
+ ☐ [WARN] items are surfaced to the user with the reason, and
184
+ the decision is the user's (mandatory-pass)
185
+ ☐ Drafts are handed over for review, not dispatched (mandatory-pass)
186
+ ☐ The drafts are usable by the target agent as written (judged — adversarial pairing:
187
+ generator and validator are the
188
+ same model, so self-validation
189
+ repeats its own bias. Have the
190
+ target agent, or a different
191
+ model, read one draft cold and
192
+ state what it would do; a
193
+ divergence from the intended
194
+ task is a FAIL)
161
195
  ```
162
196
 
163
197
  ## Simplification Guard