@chrono-meta/fh-gate 1.4.94 → 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 (75) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/AGENTS.md +18 -0
  3. package/CHEATSHEET.md +1 -1
  4. package/CLAUDE.md +1 -1
  5. package/knowledge/shared/harness-core/capability_composition_contract.md +25 -4
  6. package/knowledge/shared/harness-core/fh_detail_protocols.md +12 -0
  7. package/knowledge/shared/harness-core/ship_readiness_gate.md +7 -4
  8. package/knowledge/shared/learnings/subagent_invocations_log.yaml +67 -1
  9. package/package.json +7 -1
  10. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  11. package/plugins/fh-commons/agents/quench-challenger.md +49 -23
  12. package/plugins/fh-commons/skills/convergence-loop/SKILL.md +14 -0
  13. package/plugins/fh-commons/skills/deliberation/SKILL.md +14 -0
  14. package/plugins/fh-commons/skills/mcp-circuit-breaker/SKILL.md +40 -14
  15. package/plugins/fh-commons/skills/token-budget-gate/SKILL.md +31 -12
  16. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  17. package/plugins/fh-meta/CHANGELOG.md +36 -0
  18. package/plugins/fh-meta/agents/beginner.md +4 -1
  19. package/plugins/fh-meta/agents/challenger.md +7 -1
  20. package/plugins/fh-meta/agents/expert.md +1 -1
  21. package/plugins/fh-meta/agents/fact-checker.md +7 -1
  22. package/plugins/fh-meta/agents/hub-persona-auditor.md +2 -1
  23. package/plugins/fh-meta/agents/main-player.md +4 -1
  24. package/plugins/fh-meta/agents/persona-innovator.md +10 -2
  25. package/plugins/fh-meta/skills/agent-composer/SKILL.md +2 -2
  26. package/plugins/fh-meta/skills/apex-review/SKILL.md +5 -0
  27. package/plugins/fh-meta/skills/asset-placement-gate/SKILL.md +38 -8
  28. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +16 -2
  29. package/plugins/fh-meta/skills/context-doctor/SKILL_detail.md +45 -10
  30. package/plugins/fh-meta/skills/corpus-grounding-expander/SKILL.md +14 -5
  31. package/plugins/fh-meta/skills/cross-ecosystem-synergy-detection/SKILL.md +93 -30
  32. package/plugins/fh-meta/skills/deep-clarify/SKILL.md +28 -9
  33. package/plugins/fh-meta/skills/fh/SKILL.md +4 -0
  34. package/plugins/fh-meta/skills/field-harvest/SKILL.md +34 -11
  35. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +64 -8
  36. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +20 -7
  37. package/plugins/fh-meta/skills/goal-quench/SKILL.md +48 -15
  38. package/plugins/fh-meta/skills/goal-quench/SKILL_detail.md +58 -11
  39. package/plugins/fh-meta/skills/harness-doctor/SKILL_detail.md +109 -33
  40. package/plugins/fh-meta/skills/harvest-loop/SKILL.md +6 -1
  41. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +126 -17
  42. package/plugins/fh-meta/skills/install-doctor/SKILL.md +50 -14
  43. package/plugins/fh-meta/skills/install-wizard/SKILL.md +26 -7
  44. package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +68 -21
  45. package/plugins/fh-meta/skills/marketplace-gate/SKILL.md +59 -31
  46. package/plugins/fh-meta/skills/memory-hygiene/SKILL.md +64 -17
  47. package/plugins/fh-meta/skills/meta-prompt-builder/SKILL.md +38 -4
  48. package/plugins/fh-meta/skills/persona-roster-expander/SKILL.md +15 -7
  49. package/plugins/fh-meta/skills/pipeline-conductor/SKILL.md +13 -3
  50. package/plugins/fh-meta/skills/plugin-recommender/SKILL.md +39 -11
  51. package/plugins/fh-meta/skills/plugin-recommender/SKILL_detail.md +24 -7
  52. package/plugins/fh-meta/skills/prompt-regression/SKILL.md +54 -11
  53. package/plugins/fh-meta/skills/public-surface-audit/SKILL.md +56 -22
  54. package/plugins/fh-meta/skills/public-surface-audit/SKILL_detail.md +50 -11
  55. package/plugins/fh-meta/skills/return-path-gate/SKILL.md +36 -7
  56. package/plugins/fh-meta/skills/salience-splitter/SKILL.md +120 -7
  57. package/plugins/fh-meta/skills/salience-splitter/SKILL_detail.md +46 -13
  58. package/plugins/fh-meta/skills/sim-conductor/SKILL_detail.md +28 -3
  59. package/plugins/fh-meta/skills/steel-quench/SKILL.md +3 -1
  60. package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +72 -14
  61. package/scripts/capability_registry_check.sh +280 -0
  62. package/scripts/count_check.sh +47 -1
  63. package/scripts/degrade_direction_scan.sh +276 -6
  64. package/scripts/degrade_probe_capability.sh +105 -0
  65. package/scripts/package_coverage_check.sh +8 -0
  66. package/scripts/psa_probe_capability.sh +78 -0
  67. package/scripts/psa_scan_lib.sh +53 -1
  68. package/scripts/public_surface_scan_files.sh +8 -0
  69. package/scripts/relay_channel.sh +6 -1
  70. package/scripts/selfcheck.sh +15 -0
  71. package/scripts/test_capability_entrypoint_shipping.sh +132 -0
  72. package/scripts/test_count_check_readme_format_lanes.sh +75 -0
  73. package/scripts/test_degrade_scan_shell_probes.sh +415 -0
  74. package/scripts/validate_yaml.sh +146 -0
  75. package/templates/degrade_direction_scan.sh +276 -6
@@ -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"
@@ -84,7 +84,7 @@ Result: **PASS** / **PARTIAL** / **FAIL**
84
84
  ### Check 3 — Maintenance Signals
85
85
 
86
86
  ```bash
87
- cd "$REPO_PATH" 2>/dev/null || cd "$(pwd)"
87
+ cd "$REPO_PATH" 2>/dev/null || { echo "ABORT: cannot cd to $REPO_PATH — refusing to measure the current repo in its place"; exit 1; }
88
88
  git log -1 --format="Last commit: %ar (%ad)" --date=short 2>/dev/null
89
89
  ls CHANGELOG* 2>/dev/null && echo "CHANGELOG found" || echo "No CHANGELOG"
90
90
  git tag -l 2>/dev/null | tail -5
@@ -101,47 +101,66 @@ Result: **ACTIVE** / **STALE** (60–180 days) / **ABANDONED** (180+ days)
101
101
  ### Check 4 — Duplication / Conflict Detection
102
102
 
103
103
  ```bash
104
- # list skills in current repo (directory-based)
105
- find "$REPO_PATH" -name "SKILL.md" 2>/dev/null | xargs -I{} dirname {} | xargs -I{} basename {}
106
- # compare with existing FH skills (if FH_DIR is set)
107
- [ -n "$FH_DIR" ] && ls "$FH_DIR/plugins/fh-meta/skills/" 2>/dev/null
108
- # cross-check registered skills in plugin.json (SoT)
109
- [ -n "$FH_DIR" ] && python3 -c "
110
- import json, sys
111
- with open('$FH_DIR/plugins/fh-meta/.claude-plugin/plugin.json') as f:
112
- d = json.load(f)
113
- skills = [s['name'] for s in d.get('skills', [])]
114
- print('plugin.json registered skills (' + str(len(skills)) + '):', ', '.join(skills))
115
- " 2>/dev/null || echo "plugin.json parse failed (FH_DIR not set or path error)"
104
+ # list skills in target repo (directory-based — skills are directory-registered in this ecosystem;
105
+ # plugin.json carries no skills array, so a manifest read is NOT a skill list. An earlier version
106
+ # of this check read `plugin.json["skills"]`, a key that never exists, so it rendered every healthy
107
+ # repo as STALE a broken instrument, calibrated against this very repo.)
108
+ find "$REPO_PATH" -name "SKILL.md" 2>/dev/null | xargs -I{} dirname {} | xargs -I{} basename {} | sort > /tmp/_mkt_target_skills.txt
109
+ cat /tmp/_mkt_target_skills.txt
110
+ # compare with hub skills — SKIPPED must be visible, never silent. The readability test matters:
111
+ # FH_DIR set-but-wrong yields an empty ls through the pipe, which reads as "0 overlaps" — a silent
112
+ # skip wearing a pass. Set-but-unreadable is its own labeled state.
113
+ if [ -n "$FH_DIR" ] && [ -d "$FH_DIR/plugins" ]; then
114
+ ls "$FH_DIR"/plugins/*/skills/ 2>/dev/null | grep -v ':$' | grep -v '^$' | sort > /tmp/_mkt_hub_skills.txt
115
+ comm -12 /tmp/_mkt_target_skills.txt /tmp/_mkt_hub_skills.txt | sed 's/^/NAME-OVERLAP: /'
116
+ elif [ -n "$FH_DIR" ]; then
117
+ echo "SKIPPED: FH_DIR set but $FH_DIR/plugins unreadable — hub cross-check NOT run (not a CLEAN signal)"
118
+ else
119
+ echo "SKIPPED: FH_DIR unset — hub cross-check NOT run (this is not a CLEAN signal)"
120
+ fi
116
121
  ```
117
122
 
118
- **Duplication verdict**: If directory-based list and plugin.json list don't match → **STALE** warning.
123
+ **Duplication verdict**: name overlap with hub skills → **OVERLAP**/**CONFLICT** by role comparison.
124
+ Hub cross-check skipped → report `CLEAN (target-internal only — hub cross-check SKIPPED)`, never bare CLEAN.
119
125
 
120
126
  | Criterion | Check |
121
127
  |---|---|
122
- | No name conflict with existing FH skills | name comparison |
128
+ | No name conflict with existing FH skills | name comparison (or visible SKIPPED) |
123
129
  | No functional duplication | description keyword comparison |
124
- | plugin.json list matches directory list | cross-check (SoT consistency) |
125
130
 
126
131
  Result: **CLEAN** / **OVERLAP** (N candidates) / **CONFLICT** (direct conflict)
127
132
 
128
133
  ### Check 5 — Public Safety
129
134
 
135
+ **Primary path (no-reinvention)**: when `public-surface-audit` is installed, run it against
136
+ `$REPO_PATH` and map its verdict — `LEAK` → **BLOCKED** · `REVIEW` → **WARNING** · `CLEAN` → **SAFE**
137
+ · `NOT_CONFIGURED` → **WARNING(NOT_CONFIGURED)** (pattern source absent — not a clean bill). That
138
+ skill is the real token scanner; this check does not re-implement it. The `NOT_CONFIGURED` qualifier
139
+ survives into the Step 2 aggregate — see the 🟢 rule there (an unmeasured public surface must not be
140
+ absorbed into an ignorable ⚠️).
141
+
142
+ **Fallback (screening-grade only)** — when public-surface-audit is not installed:
143
+
130
144
  ```bash
131
- # detect hardcoded internal domains
132
- grep -r "<your-ghe-url>\|internal-domain\|sandbox\|internal-api" \
145
+ # placeholder-literal screening — catches template residue, NOT real internal hostnames or secrets
146
+ grep -r "<your-ghe-url>\|internal-domain\|internal-api" \
133
147
  "$REPO_PATH" --include="*.md" --include="*.json" --include="*.yaml" -l 2>/dev/null | head -10
134
- # sensitive information exposure
135
- grep -r "API_KEY\s*=\|SECRET\s*=\|PASSWORD\s*=" \
136
- "$REPO_PATH" --include="*.md" --include="*.json" -l 2>/dev/null | head -5
148
+ # sensitive information exposure (assignment shapes only)
149
+ grep -rE "API_KEY\s*=|SECRET\s*=|PASSWORD\s*=" \
150
+ "$REPO_PATH" --include="*.md" --include="*.json" --include="*.yaml" --include="*.yml" \
151
+ --include="*.sh" --include="*.env*" -l 2>/dev/null | head -5
137
152
  # license
138
153
  ls "$REPO_PATH"/LICENSE* 2>/dev/null && echo "LICENSE found" || echo "No LICENSE"
139
154
  ```
140
155
 
156
+ Fallback results are always labeled `(screening-grade — placeholder patterns; not a hostname/secret
157
+ scanner)`. A go-public action still owes the Pre-Publish Surface Gate's full chain regardless of a
158
+ SAFE here — this check screens listing readiness, it does not clear publication.
159
+
141
160
  | Criterion | Check |
142
161
  |---|---|
143
- | No hardcoded internal domains (or clearly marked as internal-only) | grep |
144
- | No sensitive information exposed | grep |
162
+ | No hardcoded internal domains (or clearly marked as internal-only) | public-surface-audit (or screening-grade grep, labeled) |
163
+ | No sensitive information exposed | public-surface-audit (or screening-grade grep, labeled) |
145
164
  | LICENSE file exists | ls |
146
165
 
147
166
  Result: **SAFE** / **WARNING** (N items to review) / **BLOCKED** (sensitive info exposed)
@@ -161,10 +180,14 @@ marketplace-gate — Listing Suitability Verdict
161
180
  Check 4 Duplication detection: ✅ CLEAN / ⚠️ OVERLAP({N}) / ❌ CONFLICT
162
181
  Check 5 Public safety : ✅ SAFE / ⚠️ WARNING({N}) / ❌ BLOCKED
163
182
 
164
- Overall verdict:
165
- 🟢 Recommended for listing — 0 FAIL + 0 BLOCKED
166
- 🟡 Conditional listing — ≤1 FAIL + 0 BLOCKED
167
- 🔴 Listing on hold 2+ FAIL or 1+ BLOCKED
183
+ Overall verdict — counted over the ❌-class {FAIL, ABANDONED, CONFLICT, BLOCKED}
184
+ (each check has its own vocabulary; the aggregate counts the ❌ column, not the token "FAIL" —
185
+ an ABANDONED or CONFLICT is a failure even though its word differs):
186
+ 🟢 Recommended for listing 0 ❌-class results, AND Check 5 does not carry the
187
+ NOT_CONFIGURED qualifier (an unmeasured surface caps
188
+ the verdict at 🟡 — unmeasured ≠ pass)
189
+ 🟡 Conditional listing — exactly 1 ❌-class result, and it is not BLOCKED
190
+ 🔴 Listing on hold — 2+ ❌-class results, or any BLOCKED
168
191
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
169
192
  ```
170
193
 
@@ -182,12 +205,17 @@ marketplace-gate — Listing Suitability Verdict
182
205
  ## Done When
183
206
 
184
207
  ```
185
- All steps 0–2 completed
186
- + Full 5-point check results output (Check 1–5 individual verdicts)
187
- + Overall verdict output (🟢 Recommended / 🟡 Conditional / 🔴 On hold)
208
+ All steps 0–2 completed — mandatory-pass
209
+ + Full 5-point check results output (Check 1–5 individual
210
+ verdicts, skipped legs rendered as visible SKIPPED) — mandatory-pass
211
+ + Overall verdict output (🟢/🟡/🔴) counted over the ❌-class — measured (❌-class count)
212
+ + Before any 🟢 Recommended verdict: phantom-quench ran over
213
+ the target's citations/URLs/path refs; phantom refs found
214
+ → verdict auto-downgrades to 🟡 Conditional — mandatory-pass
188
215
  ```
189
216
 
190
- **→ Mandatory before 🟢 Recommended verdict: `phantom-quench`** forward axis check on all citations, external URLs, and file path references in the asset being reviewed. A 🟢 verdict without phantom-quench is incomplete. If phantom-quench finds phantom refs → verdict downgrades to 🟡 Conditional automatically.
217
+ (The phantom-quench leg sits inside Done When on purpose an earlier version stated it below the
218
+ fence, so the fence alone could be satisfied without it.)
191
219
 
192
220
  > When `agent-composer` receives a "comprehensive marketplace listing audit" request,
193
221
  > recommend: Wave 0 `fact-checker` → Wave 1 `marketplace-gate` + `hub-persona-auditor` in parallel.
@@ -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
@@ -2,7 +2,7 @@
2
2
  name: persona-roster-expander
3
3
  description: Expands a named persona seed into a tiered, judgment-mapped cast — tiering each persona by a domain safety rule, mapping each to a decision-lens in the user's vocabulary, then proposing additional voices with sourced anchors.
4
4
  user-invocable: true
5
- allowed-tools: ["Read", "Grep", "WebSearch", "WebFetch"]
5
+ allowed-tools: ["Read", "Grep", "WebSearch", "WebFetch", "Write", "Agent"]
6
6
  model: sonnet
7
7
  ---
8
8
 
@@ -15,7 +15,12 @@ caller-supplied safety rule so the expansion stays faithful to the domain's cons
15
15
 
16
16
  > Origin: harvested from the-bible (2026-06-20) — an operator persona seed (priest/nun/angel/devil/
17
17
  > God/Jesus/Holy-Spirit/apostles) tiered relay-vs-lens by a relay-safety rule and mapped to
18
- > engineering-judgment lenses, +4 sourced proposals. See
18
+ > engineering-judgment lenses, +4 sourced proposals. **The grounds, inline, because the harvest
19
+ > record does not ship**: an ungated persona cast drifts into inventing its own authority — a voice
20
+ > given a lens will speak past what the domain lets it assert — so tiering by a *caller-supplied*
21
+ > rule is what keeps the expansion faithful, and the lens mapping is what makes a named voice
22
+ > usable as a decision instrument instead of flavor. Full harvest record — **hub-local, not
23
+ > distributed in the npm package**:
19
24
  > `tracks/_contrib/field_harvest_2026-06-20_gate-locality-and-grounding-capabilities.md`.
20
25
 
21
26
  ## Triggers
@@ -41,14 +46,17 @@ caller-supplied safety rule so the expansion stays faithful to the domain's cons
41
46
  4. **Propose 2–4 additions** filling lenses the seed doesn't cover (delegate net-new *name*
42
47
  generation to the `persona-innovator` agent — this skill's distinct value is the tiering +
43
48
  lens-mapping, not naming). For the strongest 2–3, find a real anchor (a sourced
44
- example/reference), not a shallow stub.
45
- 5. **Emit the tiered, lens-mapped cast** (named + proposed) as a structured artifact the system can
46
- load (e.g. `personas.json`).
49
+ example/reference); **any remaining proposal ships with a literal `stub:` prefix** — unanchored
50
+ and unlabeled is not an allowed output (see Done When).
51
+ 5. **Emit the tiered, lens-mapped cast** (named + proposed) by **writing it to a structured file**
52
+ the system can load (e.g. `personas.json`), then confirm the written file parses. Leaving the
53
+ cast in the response text only does not complete this step.
47
54
 
48
55
  ## Done When
49
56
  - **Every persona has a tier + lens label + invoke-condition.** *Check class: mandatory-pass (binary — all three fields present per persona).*
50
- - **Each net-new proposal names a real anchor** (not a stub). *Check class: judged, pair: an anchor-existence check (the cited source/reference resolves).*
51
- - **The tiering respects the caller's safety rule** (no persona exceeds its tier's allowed emission). *Check class: judged, pair: an adversarial read find a persona whose mapping lets it act beyond its tier.*
57
+ - **Every proposal is either anchored or explicitly labeled a stub.** An anchored proposal names a source/reference that resolves; a proposal without one is emitted with a literal `stub:` prefix and is **not counted as a candidate**. *Check class: mandatory-pass (binary each proposal carries either a resolving anchor or a `stub:` label; an unanchored, unlabeled proposal is FAIL). Anchor resolution is checked mechanically (fetch/look up the cited reference), not by judgment.* (This is the Done-When floor; Step 4's "strongest 2–3" is the effort target that sits above it — a 4th proposal may ship as `stub:` without violating either.)
58
+ - **The cast is materialized as a loadable artifact** (e.g. `personas.json`) that a consumer can parse — the file exists on disk and a parse of it succeeds against the declared schema (per persona: tier / lens / invoke-condition; per proposal additionally `anchor` or `stub:`). *Check class: mandatory-pass (binary file exists and parses; a cast that exists only in the response text is UNMET).*
59
+ - **The tiering respects the caller's safety rule** (no persona exceeds its tier's allowed emission). *Check class: judged, pair: dispatch `fh-meta:challenger` at the tier-escalation angle — "find a persona whose lens mapping lets it emit beyond its tier". The pairing is a **different-agent** read; the author's own re-read does not satisfy it. If that agent is unreachable, record `pair: unavailable (<reason>)` — the condition stays UNMET and is **reported as a named residual**, never self-scored closed. It does **not** block delivery: a roster is a reversible artifact, so the degrade direction here is declare-and-ship, not fail-closed (that direction is reserved for irreversible surfaces — publish, delete, history-rewrite). Shipping with an UNMET pairing is honest; silently marking it met is the defect.*
52
60
 
53
61
  ## Guards
54
62
  - **Caller-supplied safety rule is mandatory** — the skill tiers by the domain's rule, it does not
@@ -94,7 +94,11 @@ The four constituent skills use heterogeneous scope models. Translate the pipeli
94
94
 
95
95
  ## Step 0.5. return-path-gate — Pre-flight Chain Audit
96
96
 
97
- > Skip if return-path-gate is not installed or scope is a single non-pipeline skill.
97
+ > Skip only when scope is a single non-pipeline skill. **return-path-gate not installed is a recorded
98
+ > degrade, never a free skip**: proceed, but record `degraded: return-path-gate (not installed)` in the
99
+ > final report — and, symmetric with the user-override row below, the sweep **cannot reach
100
+ > `CLEAN (--full)`**. A missing tool must not pass more cheaply than an explicit refusal (that
101
+ > asymmetry trains uninstalling the gate).
98
102
 
99
103
  Run `/return-path-gate --skill [scope]`.
100
104
 
@@ -197,7 +201,8 @@ After all steps complete (or after chain halt), output the aggregated report (te
197
201
 
198
202
  | Condition | Overall |
199
203
  |---|---|
200
- | All steps PASS | `CLEAN ({mode})` |
204
+ | All steps PASS, zero `degraded:` entries | `CLEAN ({mode})` |
205
+ | All steps PASS but 1+ `degraded:` entries (skip/not-installed/override) | `CLEAN ({mode}, degraded: [list])` — and the mode may not render `--full` (the Step 0.5 "cannot reach CLEAN (--full)" rule binds HERE, in the aggregate, not only in prose) |
201
206
  | Any step CONDITIONAL_PASS or accepted ESCALATE; none FAIL | `PENDING` |
202
207
  | Any step FAIL or unresolved ESCALATE (option c) | `BLOCKED` |
203
208
 
@@ -263,7 +268,7 @@ complexity_routing:
263
268
 
264
269
  ```
265
270
  Step 0 scope confirmed and scope translation table applied
266
- + Step 0.5 return-path-gate pre-flight: PASS / CONDITIONAL_PASS / FAIL (halts sweep) / explicitly skipped / degraded (user override)
271
+ + Step 0.5 return-path-gate pre-flight: PASS / CONDITIONAL_PASS / FAIL (halts sweep) / explicitly skipped / degraded (user override | not installed)
267
272
  + All in-scope steps executed and verdicts emitted
268
273
  + Aggregated report output (Step 5 format)
269
274
  + Report saved to tracks/_meta/ (or skip warning issued)
@@ -273,6 +278,11 @@ Step 0 scope confirmed and scope translation table applied
273
278
  + If ESCALATE occurred: user presented three options; choice recorded in report
274
279
  ```
275
280
 
281
+ **Check classes**: every fence row above is *mandatory-pass*, except the completion-claim discipline
282
+ below, which is *measured* — its reconciliation (failed+skipped list length vs. in-scope − passed) is
283
+ the mechanical anchor. No Done When row here is judged: verdict aggregation is per-step-typed, not
284
+ discretionary.
285
+
276
286
  Verdict: PASS (all conditions met, sweep complete) | CONDITIONAL_PASS (sweep complete, pending items captured) | FAIL (chain halted, blocking items remain) | ESCALATE (chain paused, human decision required)
277
287
 
278
288
  A sweep is not done until the Step 5 report is output. Emitting per-step verdicts without the aggregated report is incomplete.