@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +18 -0
- package/CHEATSHEET.md +1 -1
- package/knowledge/shared/harness-core/fh_detail_protocols.md +12 -0
- package/knowledge/shared/harness-core/ship_readiness_gate.md +7 -4
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +43 -1
- package/package.json +6 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/agents/quench-challenger.md +49 -23
- package/plugins/fh-commons/skills/convergence-loop/SKILL.md +14 -0
- package/plugins/fh-commons/skills/deliberation/SKILL.md +14 -0
- package/plugins/fh-commons/skills/mcp-circuit-breaker/SKILL.md +10 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +36 -0
- package/plugins/fh-meta/agents/beginner.md +4 -1
- package/plugins/fh-meta/agents/challenger.md +7 -1
- package/plugins/fh-meta/agents/expert.md +1 -1
- package/plugins/fh-meta/agents/fact-checker.md +7 -1
- package/plugins/fh-meta/agents/hub-persona-auditor.md +2 -1
- package/plugins/fh-meta/agents/main-player.md +4 -1
- package/plugins/fh-meta/agents/persona-innovator.md +10 -2
- package/plugins/fh-meta/skills/agent-composer/SKILL.md +2 -2
- package/plugins/fh-meta/skills/apex-review/SKILL.md +5 -0
- package/plugins/fh-meta/skills/asset-placement-gate/SKILL.md +38 -8
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +16 -2
- package/plugins/fh-meta/skills/context-doctor/SKILL_detail.md +45 -10
- package/plugins/fh-meta/skills/corpus-grounding-expander/SKILL.md +14 -5
- package/plugins/fh-meta/skills/cross-ecosystem-synergy-detection/SKILL.md +93 -30
- package/plugins/fh-meta/skills/deep-clarify/SKILL.md +28 -9
- package/plugins/fh-meta/skills/fh/SKILL.md +4 -0
- package/plugins/fh-meta/skills/frontier-digest/SKILL.md +64 -8
- package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +20 -7
- package/plugins/fh-meta/skills/goal-quench/SKILL.md +48 -15
- package/plugins/fh-meta/skills/goal-quench/SKILL_detail.md +58 -11
- package/plugins/fh-meta/skills/harness-doctor/SKILL_detail.md +109 -33
- package/plugins/fh-meta/skills/harvest-loop/SKILL.md +6 -1
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +126 -17
- package/plugins/fh-meta/skills/install-doctor/SKILL.md +50 -14
- package/plugins/fh-meta/skills/install-wizard/SKILL.md +26 -7
- package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +68 -21
- package/plugins/fh-meta/skills/memory-hygiene/SKILL.md +64 -17
- package/plugins/fh-meta/skills/meta-prompt-builder/SKILL.md +38 -4
- package/plugins/fh-meta/skills/persona-roster-expander/SKILL.md +15 -7
- package/plugins/fh-meta/skills/plugin-recommender/SKILL.md +39 -11
- package/plugins/fh-meta/skills/plugin-recommender/SKILL_detail.md +24 -7
- package/plugins/fh-meta/skills/prompt-regression/SKILL.md +54 -11
- package/plugins/fh-meta/skills/salience-splitter/SKILL.md +120 -7
- package/plugins/fh-meta/skills/salience-splitter/SKILL_detail.md +46 -13
- package/plugins/fh-meta/skills/sim-conductor/SKILL_detail.md +28 -3
- package/plugins/fh-meta/skills/steel-quench/SKILL.md +3 -1
- package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +72 -14
- package/scripts/count_check.sh +47 -1
- package/scripts/degrade_direction_scan.sh +276 -6
- package/scripts/degrade_probe_capability.sh +105 -0
- package/scripts/package_coverage_check.sh +8 -0
- package/scripts/psa_probe_capability.sh +78 -0
- package/scripts/public_surface_scan_files.sh +8 -0
- package/scripts/selfcheck.sh +15 -0
- package/scripts/test_capability_entrypoint_shipping.sh +132 -0
- package/scripts/test_count_check_readme_format_lanes.sh +75 -0
- package/scripts/test_degrade_scan_shell_probes.sh +415 -0
- package/scripts/validate_yaml.sh +146 -0
- 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
|
|
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
|
-
|
|
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
|
-
-
|
|
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** (
|
|
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**
|
|
107
|
-
- **Markdown editing discipline mandatory**
|
|
108
|
-
- **Frontmatter description plain text only baseline**
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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:
|
|
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
|
|
126
|
-
as
|
|
127
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
|
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
|
-
|
|
226
|
-
|
|
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(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
287
|
-
+ Step
|
|
288
|
-
|
|
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
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
☐
|
|
308
|
-
|
|
309
|
-
☐
|
|
310
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
324
|
-
|
|
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.
|
|
511
|
-
#
|
|
512
|
-
#
|
|
513
|
-
#
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
ls
|
|
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
|
-
-
|
|
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`
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|