@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +18 -0
- package/CHEATSHEET.md +1 -1
- package/CLAUDE.md +1 -1
- package/knowledge/shared/harness-core/capability_composition_contract.md +25 -4
- 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 +67 -1
- package/package.json +7 -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 +40 -14
- package/plugins/fh-commons/skills/token-budget-gate/SKILL.md +31 -12
- 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/field-harvest/SKILL.md +34 -11
- 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/marketplace-gate/SKILL.md +59 -31
- 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/pipeline-conductor/SKILL.md +13 -3
- 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/public-surface-audit/SKILL.md +56 -22
- package/plugins/fh-meta/skills/public-surface-audit/SKILL_detail.md +50 -11
- package/plugins/fh-meta/skills/return-path-gate/SKILL.md +36 -7
- 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/capability_registry_check.sh +280 -0
- 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/psa_scan_lib.sh +53 -1
- package/scripts/public_surface_scan_files.sh +8 -0
- package/scripts/relay_channel.sh +6 -1
- 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
|
@@ -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"
|
|
@@ -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
|
|
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
|
|
105
|
-
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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**:
|
|
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
|
-
#
|
|
132
|
-
grep -r "<your-ghe-url>\|internal-domain\|
|
|
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 -
|
|
136
|
-
"$REPO_PATH" --include="*.md" --include="*.json"
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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.
|
|
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)
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
- **
|
|
51
|
-
- **The
|
|
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
|
|
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.
|