agent-bios 0.9.3 → 0.9.5
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/CLAUDE.md +1 -1
- package/claude/agents/workhorse.md +2 -2
- package/claude/guides/cli-multi-model-workflow.md +2 -2
- package/claude/guides/learning-flow.md +2 -2
- package/claude/settings.json +60 -0
- package/codex/AGENTS.md +1 -1
- package/codex/guides/cli-multi-model-workflow.md +2 -2
- package/codex/guides/learning-flow.md +2 -2
- package/config/agent-launch.toml +2 -2
- package/package.json +8 -2
- package/scripts/assemble.py +322 -0
- package/scripts/build-promotions.py +183 -0
- package/scripts/canary.sh +28 -0
- package/scripts/check-domains.py +242 -0
- package/scripts/check-parity.sh +2 -2
- package/scripts/ingest-learnings-export.py +258 -0
- package/scripts/install.sh +28 -0
- package/scripts/session-cost.py +38 -15
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Derive the promotion manifest from the ledger (collection loop, Phase 4).
|
|
3
|
+
|
|
4
|
+
`config/promotions.json` names the personal learnings that have been PROMOTED
|
|
5
|
+
into the shared corpus, so the user-side migrate rule can clear the now-absorbed
|
|
6
|
+
personal copy (scripts/migrate-learnings.py). The manifest is DERIVED — never
|
|
7
|
+
hand-edited: a promoted user learning is a ledger entry with `status == placed`
|
|
8
|
+
AND a `learning_id` (the Phase 3 dedup key kept on merge). Session-distill
|
|
9
|
+
entries have no `learning_id`, so they are never in the manifest.
|
|
10
|
+
|
|
11
|
+
F3 hardening (the one silent-loss path in the safety design): the audience a
|
|
12
|
+
promotion belongs to (which users' bundles carry it) is derived from where the
|
|
13
|
+
bullet ACTUALLY landed — the placed entry's `placed_anchor`, resolved against
|
|
14
|
+
`config/domains.json` (the placement source of truth) — NOT the ledger's free
|
|
15
|
+
`domain` tag. A placed+learning_id entry with a missing or unresolvable
|
|
16
|
+
`placed_anchor` FAILS the build/--check, so a promotion can neither ship with a
|
|
17
|
+
wrong audience nor ship without actually landing in the corpus.
|
|
18
|
+
|
|
19
|
+
Manifest shape (no PII — learning_id + the derived audience):
|
|
20
|
+
{ "version": 1, "promotions": [
|
|
21
|
+
{ "learning_id": "<uuid>", "tier": "core|infra|domain", "domains": ["<key>", ...] } ] }
|
|
22
|
+
(`domains` is empty for the universal tiers core/infra.)
|
|
23
|
+
|
|
24
|
+
Release step: regenerate before a push. `--check` fails if the on-disk manifest
|
|
25
|
+
is stale vs the ledger (a check-parity gate). `--self-test` runs the derivation.
|
|
26
|
+
"""
|
|
27
|
+
import argparse
|
|
28
|
+
import json
|
|
29
|
+
import pathlib
|
|
30
|
+
import sys
|
|
31
|
+
|
|
32
|
+
REPO = pathlib.Path(__file__).resolve().parent.parent
|
|
33
|
+
LEDGER = REPO / "design" / "session-distill" / "ledger.json"
|
|
34
|
+
DOMAINS = REPO / "config" / "domains.json"
|
|
35
|
+
MANIFEST = REPO / "config" / "promotions.json"
|
|
36
|
+
FIXTURE = REPO / "design" / "collection-loop" / "fixtures" / "ledger-promote-sample.json"
|
|
37
|
+
MANIFEST_VERSION = 1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ManifestError(Exception):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def resolve_audience(anchor, domains_manifest):
|
|
45
|
+
"""(tier, [domains]) for a placed anchor, or None if it does not resolve.
|
|
46
|
+
Bullets match by their `anchor` field; a whole guide/hook/agent matches by
|
|
47
|
+
key. This is the authoritative placement audience (mirrors what assemble.py
|
|
48
|
+
reads to decide who gets the bullet)."""
|
|
49
|
+
for b in domains_manifest.get("bullets", []):
|
|
50
|
+
if b.get("anchor") == anchor:
|
|
51
|
+
return b.get("tier"), list(b.get("domains", []))
|
|
52
|
+
for kind in ("guides", "hooks", "agents"):
|
|
53
|
+
entry = domains_manifest.get(kind, {}).get(anchor)
|
|
54
|
+
if entry is not None:
|
|
55
|
+
return entry.get("tier"), list(entry.get("domains", []))
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def build_manifest(ledger, domains_manifest):
|
|
60
|
+
"""Ledger + domains manifest -> the promotion manifest. Raises ManifestError
|
|
61
|
+
on a placed+learning_id entry whose placement can't be verified (missing or
|
|
62
|
+
unresolvable placed_anchor) — the F3 silent-loss guard. Sorted by
|
|
63
|
+
learning_id for a stable, deterministic artifact."""
|
|
64
|
+
promotions = []
|
|
65
|
+
for e in ledger.get("entries", []):
|
|
66
|
+
if not isinstance(e, dict):
|
|
67
|
+
continue
|
|
68
|
+
lid = e.get("learning_id")
|
|
69
|
+
if e.get("status") != "placed" or not (isinstance(lid, str) and lid):
|
|
70
|
+
continue
|
|
71
|
+
anchor = e.get("placed_anchor")
|
|
72
|
+
if not anchor:
|
|
73
|
+
raise ManifestError(
|
|
74
|
+
f"placed learning {e.get('id')!r} ({lid}) has no `placed_anchor` — "
|
|
75
|
+
"record where the bullet landed (a config/domains.json anchor/key) "
|
|
76
|
+
"before promoting")
|
|
77
|
+
aud = resolve_audience(anchor, domains_manifest)
|
|
78
|
+
if aud is None:
|
|
79
|
+
raise ManifestError(
|
|
80
|
+
f"placed_anchor {anchor!r} (learning {lid}) does not resolve in "
|
|
81
|
+
"config/domains.json — the promotion has no verifiable placement")
|
|
82
|
+
tier, domains = aud
|
|
83
|
+
promotions.append({"learning_id": lid.lower(), "tier": tier, "domains": domains})
|
|
84
|
+
promotions.sort(key=lambda p: p["learning_id"])
|
|
85
|
+
return {"version": MANIFEST_VERSION, "promotions": promotions}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def render(manifest):
|
|
89
|
+
return json.dumps(manifest, ensure_ascii=False, indent=2) + "\n"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _self_test():
|
|
93
|
+
ledger = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
94
|
+
# Fixture domains manifest the fixture's placed_anchors resolve against.
|
|
95
|
+
domains = {
|
|
96
|
+
"bullets": [
|
|
97
|
+
{"anchor": "universal placed rule", "tier": "core", "domains": []},
|
|
98
|
+
{"anchor": "builder placed rule", "tier": "domain", "domains": ["builder-base"]},
|
|
99
|
+
],
|
|
100
|
+
"guides": {}, "hooks": {}, "agents": {},
|
|
101
|
+
}
|
|
102
|
+
m = build_manifest(ledger, domains)
|
|
103
|
+
by = {p["learning_id"]: p for p in m["promotions"]}
|
|
104
|
+
a1 = "0f8c1c2a-4d1e-4abc-9def-0000000000a1"
|
|
105
|
+
b2 = "0f8c1c2a-4d1e-4abc-9def-0000000000b2"
|
|
106
|
+
|
|
107
|
+
checks = [
|
|
108
|
+
("only placed+learning_id promoted (cardinality > 0)", set(by) == {a1, b2}),
|
|
109
|
+
("session-distill (no learning_id) excluded",
|
|
110
|
+
all(p["learning_id"] for p in m["promotions"])),
|
|
111
|
+
("incubating excluded",
|
|
112
|
+
"0f8c1c2a-4d1e-4abc-9def-0000000000c1" not in by),
|
|
113
|
+
("audience DERIVED from the anchor, not the ledger domain tag",
|
|
114
|
+
by[a1]["tier"] == "core" and by[a1]["domains"] == []
|
|
115
|
+
and by[b2]["tier"] == "domain" and by[b2]["domains"] == ["builder-base"]),
|
|
116
|
+
("sorted + deterministic",
|
|
117
|
+
[p["learning_id"] for p in m["promotions"]] == sorted(by)
|
|
118
|
+
and render(build_manifest(ledger, domains)) == render(m)),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
# F3 guards: missing / unresolvable placed_anchor must FAIL.
|
|
122
|
+
def raises(mut):
|
|
123
|
+
led = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
|
124
|
+
mut(led)
|
|
125
|
+
try:
|
|
126
|
+
build_manifest(led, domains)
|
|
127
|
+
return False
|
|
128
|
+
except ManifestError:
|
|
129
|
+
return True
|
|
130
|
+
|
|
131
|
+
checks.append(("missing placed_anchor fails",
|
|
132
|
+
raises(lambda l: l["entries"][0].pop("placed_anchor", None))))
|
|
133
|
+
checks.append(("unresolvable placed_anchor fails",
|
|
134
|
+
raises(lambda l: l["entries"][0].__setitem__("placed_anchor", "no-such-anchor"))))
|
|
135
|
+
|
|
136
|
+
failed = [n for n, ok in checks if not ok]
|
|
137
|
+
if failed:
|
|
138
|
+
for n in failed:
|
|
139
|
+
print(f"build-promotions --self-test: FAIL: {n}", file=sys.stderr)
|
|
140
|
+
sys.exit(1)
|
|
141
|
+
print(f"build-promotions --self-test: OK ({len(checks)} manifest-derivation checks)")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _load_and_build():
|
|
145
|
+
ledger = json.loads(LEDGER.read_text(encoding="utf-8"))
|
|
146
|
+
domains = json.loads(DOMAINS.read_text(encoding="utf-8"))
|
|
147
|
+
try:
|
|
148
|
+
return render(build_manifest(ledger, domains))
|
|
149
|
+
except ManifestError as e:
|
|
150
|
+
print(f"build-promotions: {e}", file=sys.stderr)
|
|
151
|
+
sys.exit(1)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def main():
|
|
155
|
+
ap = argparse.ArgumentParser(description="Derive config/promotions.json from the ledger.")
|
|
156
|
+
ap.add_argument("--check", action="store_true",
|
|
157
|
+
help="fail if config/promotions.json is stale vs the ledger (gate)")
|
|
158
|
+
ap.add_argument("--self-test", action="store_true")
|
|
159
|
+
args = ap.parse_args()
|
|
160
|
+
|
|
161
|
+
if args.self_test:
|
|
162
|
+
_self_test()
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
want = _load_and_build()
|
|
166
|
+
|
|
167
|
+
if args.check:
|
|
168
|
+
have = MANIFEST.read_text(encoding="utf-8") if MANIFEST.is_file() else ""
|
|
169
|
+
if have != want:
|
|
170
|
+
print("build-promotions --check: config/promotions.json is STALE vs the "
|
|
171
|
+
"ledger — run `python3 scripts/build-promotions.py` and commit.",
|
|
172
|
+
file=sys.stderr)
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
print("build-promotions --check: promotions.json is current with the ledger")
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
MANIFEST.write_text(want, encoding="utf-8")
|
|
178
|
+
n = want.count('"learning_id"')
|
|
179
|
+
print(f"build-promotions: wrote {MANIFEST} ({n} promotion(s))")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == "__main__":
|
|
183
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Activation canary: proves the central bundle actually LOADS in a live
|
|
3
|
+
# session — file presence cannot detect a declined @import approval or a
|
|
4
|
+
# broken entry import line, so this asks a headless session to echo the
|
|
5
|
+
# bundle's rev marker back.
|
|
6
|
+
# Exit: 0 loaded | 1 not loading (or no bundle) | 3 cannot probe (no CLI).
|
|
7
|
+
set -u
|
|
8
|
+
CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
9
|
+
BUNDLE="$CLAUDE_DIR/central/bundle.md"
|
|
10
|
+
|
|
11
|
+
[ -f "$BUNDLE" ] || { echo "CANARY FAIL: no bundle at $BUNDLE (run: agent-bios install --domains ...)"; exit 1; }
|
|
12
|
+
expected="$(grep -m1 '^agent-bios-bundle-rev: ' "$BUNDLE")"
|
|
13
|
+
[ -n "$expected" ] || { echo "CANARY FAIL: bundle has no rev marker (reassemble with a current assemble.py)"; exit 1; }
|
|
14
|
+
command -v claude >/dev/null 2>&1 || { echo "CANARY SKIP: claude CLI not found — cannot probe activation"; exit 3; }
|
|
15
|
+
|
|
16
|
+
probe="Somewhere in your loaded instruction context there may be a line that starts with 'agent-bios-bundle-rev:'. Reply with ONLY that line, verbatim. If no such line is in your context, reply with exactly: BUNDLE-NOT-LOADED"
|
|
17
|
+
out="$(cd "$HOME" && claude -p "$probe" 2>/dev/null)"
|
|
18
|
+
|
|
19
|
+
if printf '%s' "$out" | grep -qF "$expected"; then
|
|
20
|
+
echo "CANARY PASS: central bundle is loading ($expected)"
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
echo "CANARY FAIL: central bundle is NOT loading in live sessions."
|
|
24
|
+
echo " expected marker: $expected"
|
|
25
|
+
echo " probe replied: $(printf '%s' "$out" | head -c 200)"
|
|
26
|
+
echo " Likely causes: the CLAUDE.md import approval was declined (open a session and re-approve imports),"
|
|
27
|
+
echo " or the entry file lost its '@central/bundle.md' line. Diagnose with: agent-bios verify"
|
|
28
|
+
exit 1
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Bijection/coverage gate: config/domains.json vs the canonical corpus.
|
|
3
|
+
|
|
4
|
+
The manifest is the sole classification authority (tagged-monolith layout);
|
|
5
|
+
claude/CLAUDE.md stays the sole text authority. This gate closes the
|
|
6
|
+
silent-drop window: a bullet reworded without a manifest update, or a
|
|
7
|
+
manifest entry whose anchor no longer matches, fails HERE, before landing.
|
|
8
|
+
|
|
9
|
+
Blocking checks (any violation exits 1, all violations listed):
|
|
10
|
+
1. schema shape + registry legality (tiers/domains from the manifest's own
|
|
11
|
+
registry; domains list non-empty iff tier == "domain")
|
|
12
|
+
2. bullet bijection: every manifest anchor matches exactly ONE `- ` bullet
|
|
13
|
+
in claude/CLAUDE.md, and every bullet is claimed by exactly ONE entry
|
|
14
|
+
3. file coverage: every file in claude/guides|hooks|agents claimed exactly
|
|
15
|
+
once; every claimed file exists on disk
|
|
16
|
+
4. router-guide co-package: a bullet referencing guides/<g>.md must have an
|
|
17
|
+
audience covered by that guide's audience (universal bullet -> universal
|
|
18
|
+
guide; domain bullet -> guide covering all its domains)
|
|
19
|
+
5. hook source_guide: a hook naming its source guide must carry the same
|
|
20
|
+
tier + domain set as that guide
|
|
21
|
+
6. non-vacuity: every subject set this gate judges is non-empty, so a green
|
|
22
|
+
run cannot be vacuous
|
|
23
|
+
|
|
24
|
+
Informational (non-blocking until budgets are set in the manifest): estimated
|
|
25
|
+
token size per package.
|
|
26
|
+
|
|
27
|
+
--self-test: runs negative controls (mutated manifests that MUST fail) and
|
|
28
|
+
exits 0 only if every mutation is caught. Proves the gate can fail.
|
|
29
|
+
"""
|
|
30
|
+
import json
|
|
31
|
+
import pathlib
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
|
|
35
|
+
REPO = pathlib.Path(__file__).resolve().parent.parent
|
|
36
|
+
MANIFEST = REPO / "config" / "domains.json"
|
|
37
|
+
MONOLITH = REPO / "claude" / "CLAUDE.md"
|
|
38
|
+
FILE_SECTIONS = { # manifest key -> corpus dir, glob
|
|
39
|
+
"guides": ("claude/guides", "*.md"),
|
|
40
|
+
"hooks": ("claude/hooks", "*"),
|
|
41
|
+
"agents": ("claude/agents", "*.md"),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_manifest(path=MANIFEST):
|
|
46
|
+
with open(path, encoding="utf-8") as f:
|
|
47
|
+
return json.load(f)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def corpus_bullets(path=MONOLITH):
|
|
51
|
+
text = path.read_text(encoding="utf-8")
|
|
52
|
+
return [ln for ln in text.splitlines() if ln.startswith("- ")]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def audience(entry, errors, ctx):
|
|
56
|
+
"""Universal set marker, or the frozen domain set; records legality errors."""
|
|
57
|
+
tier = entry.get("tier")
|
|
58
|
+
domains = entry.get("domains", [])
|
|
59
|
+
if tier in ("core", "infra"):
|
|
60
|
+
if domains:
|
|
61
|
+
errors.append(f"{ctx}: tier {tier} must not list domains, got {domains}")
|
|
62
|
+
return "UNIVERSAL"
|
|
63
|
+
if tier == "env-personal":
|
|
64
|
+
if domains:
|
|
65
|
+
errors.append(f"{ctx}: env-personal must not list domains")
|
|
66
|
+
return "NEVER"
|
|
67
|
+
if tier == "domain":
|
|
68
|
+
if not domains:
|
|
69
|
+
errors.append(f"{ctx}: tier domain requires >=1 domain")
|
|
70
|
+
return frozenset(domains)
|
|
71
|
+
errors.append(f"{ctx}: illegal tier {tier!r}")
|
|
72
|
+
return "NEVER"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def covers(guide_aud, bullet_aud):
|
|
76
|
+
if guide_aud == "UNIVERSAL":
|
|
77
|
+
return True
|
|
78
|
+
if bullet_aud in ("UNIVERSAL", "NEVER"):
|
|
79
|
+
return False # universal bullet needs universal guide; NEVER refs nothing
|
|
80
|
+
return isinstance(guide_aud, frozenset) and guide_aud >= bullet_aud
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def run_gate(manifest, bullets, repo=REPO):
|
|
84
|
+
errors = []
|
|
85
|
+
tiers = set(manifest.get("tiers", []))
|
|
86
|
+
domains_reg = set(manifest.get("domains", {}))
|
|
87
|
+
if not tiers or not domains_reg:
|
|
88
|
+
errors.append("registry: tiers/domains registry empty")
|
|
89
|
+
|
|
90
|
+
def check_registry(entry, ctx):
|
|
91
|
+
if entry.get("tier") not in tiers:
|
|
92
|
+
errors.append(f"{ctx}: tier {entry.get('tier')!r} not in registry")
|
|
93
|
+
for d in entry.get("domains", []):
|
|
94
|
+
if d not in domains_reg:
|
|
95
|
+
errors.append(f"{ctx}: domain {d!r} not in registry")
|
|
96
|
+
|
|
97
|
+
# -- 2. bullet bijection ------------------------------------------------
|
|
98
|
+
mb = manifest.get("bullets", [])
|
|
99
|
+
if not bullets:
|
|
100
|
+
errors.append("non-vacuity: no bullets found in monolith")
|
|
101
|
+
if not mb:
|
|
102
|
+
errors.append("non-vacuity: manifest has no bullet entries")
|
|
103
|
+
if len(mb) != len(bullets):
|
|
104
|
+
errors.append(f"bijection: manifest has {len(mb)} bullet entries, monolith has {len(bullets)} bullets")
|
|
105
|
+
claimed = [0] * len(bullets)
|
|
106
|
+
seen_anchors = set()
|
|
107
|
+
for i, entry in enumerate(mb):
|
|
108
|
+
ctx = f"bullets[{i}] anchor={entry.get('anchor', '')!r:.60}"
|
|
109
|
+
check_registry(entry, ctx)
|
|
110
|
+
anchor = entry.get("anchor", "")
|
|
111
|
+
if not anchor:
|
|
112
|
+
errors.append(f"{ctx}: empty anchor")
|
|
113
|
+
continue
|
|
114
|
+
if anchor in seen_anchors:
|
|
115
|
+
errors.append(f"{ctx}: duplicate anchor")
|
|
116
|
+
seen_anchors.add(anchor)
|
|
117
|
+
hits = [j for j, b in enumerate(bullets) if anchor in b]
|
|
118
|
+
if len(hits) != 1:
|
|
119
|
+
errors.append(f"{ctx}: anchor matches {len(hits)} bullets (need exactly 1)")
|
|
120
|
+
for j in hits:
|
|
121
|
+
claimed[j] += 1
|
|
122
|
+
for j, n in enumerate(claimed):
|
|
123
|
+
if n != 1:
|
|
124
|
+
errors.append(f"bijection: bullet line {bullets[j][:70]!r} claimed {n} times (need exactly 1)")
|
|
125
|
+
|
|
126
|
+
# -- 3. file coverage ---------------------------------------------------
|
|
127
|
+
entries = {}
|
|
128
|
+
for key, (rel, glob) in FILE_SECTIONS.items():
|
|
129
|
+
section = manifest.get(key, {})
|
|
130
|
+
entries[key] = section
|
|
131
|
+
if not section:
|
|
132
|
+
errors.append(f"non-vacuity: manifest section {key!r} empty")
|
|
133
|
+
on_disk = {p.name for p in (repo / rel).glob(glob) if p.is_file()}
|
|
134
|
+
if not on_disk:
|
|
135
|
+
errors.append(f"non-vacuity: no files on disk under {rel}")
|
|
136
|
+
for name, entry in section.items():
|
|
137
|
+
check_registry(entry, f"{key}/{name}")
|
|
138
|
+
if name not in on_disk:
|
|
139
|
+
errors.append(f"{key}: claimed file {name} does not exist in {rel}")
|
|
140
|
+
for name in sorted(on_disk - set(section)):
|
|
141
|
+
errors.append(f"{key}: file {name} in {rel} not claimed by the manifest")
|
|
142
|
+
|
|
143
|
+
# -- 4. router-guide co-package ----------------------------------------
|
|
144
|
+
guide_aud = {n: audience(e, errors, f"guides/{n}") for n, e in entries["guides"].items()}
|
|
145
|
+
anchor_of = {id(e): e.get("anchor", "?") for e in mb}
|
|
146
|
+
ref_checks = 0
|
|
147
|
+
for entry in mb:
|
|
148
|
+
hits = [b for b in bullets if entry.get("anchor", "\0") in b]
|
|
149
|
+
if len(hits) != 1:
|
|
150
|
+
continue # already reported by bijection
|
|
151
|
+
b_aud = audience(entry, errors, f"bullet {entry.get('anchor', '')!r:.40}")
|
|
152
|
+
for g in re.findall(r"guides/([a-z0-9-]+\.md)", hits[0]):
|
|
153
|
+
ref_checks += 1
|
|
154
|
+
if g not in guide_aud:
|
|
155
|
+
errors.append(f"router: bullet {entry['anchor']!r:.40} references unclaimed guide {g}")
|
|
156
|
+
elif not covers(guide_aud[g], b_aud):
|
|
157
|
+
errors.append(
|
|
158
|
+
f"router: bullet {entry['anchor']!r:.40} (audience {b_aud}) references guide {g} "
|
|
159
|
+
f"(audience {guide_aud[g]}) — user can hold the router without the guide"
|
|
160
|
+
)
|
|
161
|
+
if ref_checks == 0:
|
|
162
|
+
errors.append("non-vacuity: no router->guide references were checked")
|
|
163
|
+
|
|
164
|
+
# -- 5. hook source_guide -----------------------------------------------
|
|
165
|
+
for name, entry in entries["hooks"].items():
|
|
166
|
+
src = entry.get("source_guide")
|
|
167
|
+
if src:
|
|
168
|
+
g = entries["guides"].get(src)
|
|
169
|
+
if g is None:
|
|
170
|
+
errors.append(f"hooks/{name}: source_guide {src} not in manifest guides")
|
|
171
|
+
elif (entry.get("tier"), sorted(entry.get("domains", []))) != (g.get("tier"), sorted(g.get("domains", []))):
|
|
172
|
+
errors.append(f"hooks/{name}: tier/domains differ from source guide {src}")
|
|
173
|
+
|
|
174
|
+
# -- informational token report ------------------------------------------
|
|
175
|
+
sizes = {}
|
|
176
|
+
for entry in mb:
|
|
177
|
+
key = entry.get("tier") if entry.get("tier") != "domain" else ",".join(entry.get("domains", ["?"])[:1])
|
|
178
|
+
hit = next((b for b in bullets if entry.get("anchor", "\0") in b), "")
|
|
179
|
+
sizes[key] = sizes.get(key, 0) + len(hit) // 4
|
|
180
|
+
for name, entry in entries["guides"].items():
|
|
181
|
+
key = entry.get("tier") if entry.get("tier") != "domain" else ",".join(entry.get("domains", ["?"])[:1])
|
|
182
|
+
p = repo / FILE_SECTIONS["guides"][0] / name
|
|
183
|
+
if p.is_file():
|
|
184
|
+
sizes[key] = sizes.get(key, 0) + len(p.read_text(encoding="utf-8")) // 4
|
|
185
|
+
return errors, sizes
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def self_test(manifest, bullets):
|
|
189
|
+
"""Negative controls: each mutation MUST make the gate fail."""
|
|
190
|
+
import copy
|
|
191
|
+
|
|
192
|
+
muts = []
|
|
193
|
+
m1 = copy.deepcopy(manifest)
|
|
194
|
+
m1["bullets"] = m1["bullets"][1:]
|
|
195
|
+
muts.append(("dropped bullet entry", m1, bullets))
|
|
196
|
+
m2 = copy.deepcopy(manifest)
|
|
197
|
+
m2["bullets"][0]["anchor"] = "zz-no-such-phrase-zz"
|
|
198
|
+
muts.append(("anchor matches nothing (reword drift)", m2, bullets))
|
|
199
|
+
m3 = copy.deepcopy(manifest)
|
|
200
|
+
m3["bullets"][1]["anchor"] = m3["bullets"][0]["anchor"]
|
|
201
|
+
muts.append(("duplicate anchor claim", m3, bullets))
|
|
202
|
+
m4 = copy.deepcopy(manifest)
|
|
203
|
+
first_guide = next(iter(m4["guides"]))
|
|
204
|
+
del m4["guides"][first_guide]
|
|
205
|
+
muts.append((f"unclaimed guide {first_guide}", m4, bullets))
|
|
206
|
+
b5 = bullets + ["- a brand new bullet the manifest never heard of"]
|
|
207
|
+
muts.append(("bullet added without manifest entry", copy.deepcopy(manifest), b5))
|
|
208
|
+
|
|
209
|
+
failed = []
|
|
210
|
+
for name, mm, bb in muts:
|
|
211
|
+
errs, _ = run_gate(mm, bb)
|
|
212
|
+
if not errs:
|
|
213
|
+
failed.append(name)
|
|
214
|
+
print(f"self-test [{'CAUGHT' if errs else 'MISSED'}] {name}")
|
|
215
|
+
return failed
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def main():
|
|
219
|
+
manifest = load_manifest()
|
|
220
|
+
bullets = corpus_bullets()
|
|
221
|
+
if "--self-test" in sys.argv:
|
|
222
|
+
missed = self_test(manifest, bullets)
|
|
223
|
+
if missed:
|
|
224
|
+
print(f"SELF-TEST FAIL: gate missed: {missed}")
|
|
225
|
+
return 1
|
|
226
|
+
print("SELF-TEST OK: every negative control was caught")
|
|
227
|
+
return 0
|
|
228
|
+
errors, sizes = run_gate(manifest, bullets)
|
|
229
|
+
for e in errors:
|
|
230
|
+
print(f"FAIL: {e}")
|
|
231
|
+
print("-- package token estimate (informational) --")
|
|
232
|
+
for k in sorted(sizes):
|
|
233
|
+
print(f" {k}: ~{sizes[k]} tokens")
|
|
234
|
+
if errors:
|
|
235
|
+
print(f"DOMAINS GATE FAIL: {len(errors)} violation(s)")
|
|
236
|
+
return 1
|
|
237
|
+
print(f"DOMAINS GATE OK: {len(bullets)} bullets bijective, all files claimed, routers co-packaged")
|
|
238
|
+
return 0
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
sys.exit(main())
|
package/scripts/check-parity.sh
CHANGED
|
@@ -274,7 +274,7 @@ for tier, (model, effort) in expected_launch_tiers.items():
|
|
|
274
274
|
expected_claude_tiers = {
|
|
275
275
|
"frontier": ("claude-fable-5", "max"),
|
|
276
276
|
"helm": ("claude-opus-5", "xhigh"),
|
|
277
|
-
"workhorse": ("claude-
|
|
277
|
+
"workhorse": ("claude-opus-5", "medium"),
|
|
278
278
|
"sweep": ("claude-haiku-4-5", "low"),
|
|
279
279
|
}
|
|
280
280
|
claude_launch_tiers = launch_profile.get("hosts", {}).get("claude", {}).get("tiers", {})
|
|
@@ -410,7 +410,7 @@ for guide_name in [
|
|
|
410
410
|
for slot, claude_model in (
|
|
411
411
|
("FRONTIER", "Claude Fable 5"),
|
|
412
412
|
("HELM", "Claude Opus 5"),
|
|
413
|
-
("WORKHORSE", "Claude
|
|
413
|
+
("WORKHORSE", "Claude Opus 5"),
|
|
414
414
|
("SWEEP", "Claude Haiku 4.5"),
|
|
415
415
|
):
|
|
416
416
|
if rows[slot] and claude_model not in rows[slot][1]:
|