@event4u/agent-config 14.9.0 → 14.10.0

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 (50) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +49 -0
  3. package/dist/agent-src/skills/async-python-patterns/SKILL.md +1 -1
  4. package/dist/agent-src/skills/code-refactoring/SKILL.md +1 -1
  5. package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
  6. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +1 -1
  7. package/dist/agent-src/skills/design-review/SKILL.md +1 -1
  8. package/dist/agent-src/skills/discovery-interview/SKILL.md +1 -1
  9. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
  10. package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -1
  11. package/dist/agent-src/skills/editorial-calendar/SKILL.md +1 -1
  12. package/dist/agent-src/skills/github-ci/SKILL.md +1 -1
  13. package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
  14. package/dist/agent-src/skills/judge-injection-defense/SKILL.md +1 -1
  15. package/dist/agent-src/skills/laravel-validation/SKILL.md +1 -1
  16. package/dist/agent-src/skills/legal-intake-triage/SKILL.md +1 -1
  17. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +1 -1
  18. package/dist/agent-src/skills/messaging-architecture/SKILL.md +1 -1
  19. package/dist/agent-src/skills/migration-architect/SKILL.md +1 -1
  20. package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +1 -1
  21. package/dist/agent-src/skills/module-management/SKILL.md +1 -1
  22. package/dist/agent-src/skills/motion-choreographer/SKILL.md +1 -1
  23. package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
  24. package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
  25. package/dist/agent-src/skills/prompt-optimizer/SKILL.md +1 -1
  26. package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
  27. package/dist/agent-src/skills/scene-expander/SKILL.md +1 -1
  28. package/dist/agent-src/skills/secrets-management/SKILL.md +1 -1
  29. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
  30. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  31. package/dist/discovery/deprecation-report.md +1 -1
  32. package/dist/discovery/discovery-manifest.json +29 -29
  33. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  34. package/dist/discovery/discovery-manifest.summary.md +1 -1
  35. package/dist/discovery/dormancy-report.md +1 -1
  36. package/dist/discovery/orphan-report.md +1 -1
  37. package/dist/discovery/packs.json +2 -2
  38. package/dist/discovery/trust-report.md +1 -1
  39. package/dist/discovery/workspaces.json +2 -2
  40. package/dist/mcp/registry-manifest.json +1 -1
  41. package/dist/mcp/server.json +2 -2
  42. package/package.json +1 -1
  43. package/src/config/gate-coverage.yml +20 -0
  44. package/src/config/preamble-payload-budget.json +71 -67
  45. package/src/scripts/ai_council/seating.ts +211 -0
  46. package/src/scripts/check_pack_passport_reconciliation.ts +274 -0
  47. package/src/scripts/generate_pack_manifests.ts +78 -0
  48. package/src/scripts/schemas/pack.schema.json +62 -4
  49. package/src/scripts/schemas/skill.schema.json +2 -2
  50. package/src/scripts/tighten_skill_descriptions.ts +264 -0
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Do the per-pack passports add up to the census the tree already runs?
4
+ *
5
+ * A per-pack number published beside a tree-wide number it contradicts is worse
6
+ * than no per-pack number: both get quoted, and nobody can say which is wrong.
7
+ * This is the check that says so out loud.
8
+ *
9
+ * THE ±10 % BAND THIS WAS SPECIFIED WITH RESTED ON A FALSE PREMISE, and the
10
+ * measurement is what refuted it. The band assumed packs roughly PARTITION the
11
+ * tree, so per-pack sums should approach the tree-wide census. Measured on
12
+ * 2026-08-22: across all 17 packs, `dependencies` claims **4 skills and 0
13
+ * rules**, against **291 skills and 118 rules** in the projection. Packs claim
14
+ * ~1 % of the artefacts the census counts.
15
+ *
16
+ * So the two numbers are not a reconciliation pair at all:
17
+ *
18
+ * census — the PROJECTED tree (`dist/agent-src/`), everything that ships
19
+ * passport — what a pack CLAIMS, which is a small, deliberate subset
20
+ *
21
+ * A gate failing on an 84 % gap would be failing on a property of the tree, not
22
+ * on a defect — and a gate that is red by construction gets disabled rather
23
+ * than fixed. So this REPORTS coverage as a first-class number and exits 0.
24
+ * That is exactly what the step behind it asked for: a passport that does not
25
+ * reconcile "must say what, in-band, rather than being published alongside a
26
+ * number it contradicts."
27
+ *
28
+ * What would make it a gate: pack coverage approaching the tree. Then the band
29
+ * becomes meaningful and the exit code can follow it. Until then the useful
30
+ * output is the RANKING plus the coverage figure.
31
+ *
32
+ * Both sides are `chars/4` — the basis `preamble-payload-budget.json` declares
33
+ * for itself. Mixing an exact tokenizer into one side would make the gap
34
+ * between two METHODS read as a discrepancy in the packs.
35
+ *
36
+ * Also emits the RANKING, because the useful output is not the numbers but
37
+ * which packs carry the weight: a per-pack table sorted by cost turns "the
38
+ * estate grew" into "this pack grew".
39
+ *
40
+ * Exit: 0 always (it is a REPORT, not a gate), 2 on a dead scan scope. See the
41
+ * band note above for why the exit code does not follow the gap.
42
+ */
43
+ import * as fs from 'node:fs';
44
+ import * as path from 'node:path';
45
+ import { fileURLToPath } from 'node:url';
46
+
47
+ import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
48
+ import { runSelfTest } from './_lib/gate_self_test.js';
49
+ import { censusRuleDir, censusSkillsCatalog } from './preamble_byte_census.js';
50
+
51
+ const _HERE = fileURLToPath(import.meta.url);
52
+ export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
53
+ export const PACKS_DIR = path.join(REPO_ROOT, 'src/packs');
54
+ export const RANKING = path.join(REPO_ROOT, 'agents/evidence/reports/pack-token-ranking.md');
55
+
56
+ /** ±10 %. See the module docstring for why a band exists at all. */
57
+ export const TOLERANCE = 0.1;
58
+
59
+ export interface Passport {
60
+ pack: string;
61
+ rules_tokens: number;
62
+ catalog_tokens: number;
63
+ commands_tokens: number;
64
+ other_tokens: number;
65
+ total_tokens: number;
66
+ }
67
+
68
+ /** Read `token_passport:` out of a generated pack.yaml — no YAML dependency. */
69
+ export function readPassport(pack: string, text: string): Passport | null {
70
+ const start = text.indexOf('token_passport:');
71
+ if (start < 0) return null;
72
+ const num = (key: string): number => {
73
+ const m = new RegExp(`^\\s+${key}:\\s*(\\d+)\\s*$`, 'm').exec(text.slice(start));
74
+ return m === null ? 0 : Number(m[1]);
75
+ };
76
+ return {
77
+ pack,
78
+ rules_tokens: num('rules_tokens'),
79
+ catalog_tokens: num('catalog_tokens'),
80
+ commands_tokens: num('commands_tokens'),
81
+ other_tokens: num('other_tokens'),
82
+ total_tokens: num('total_tokens'),
83
+ };
84
+ }
85
+
86
+ export function collectPassports(dir: string = PACKS_DIR): Passport[] {
87
+ if (!fs.existsSync(dir)) return [];
88
+ const out: Passport[] = [];
89
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
90
+ if (!e.isDirectory()) continue;
91
+ const f = path.join(dir, e.name, 'pack.yaml');
92
+ if (!fs.existsSync(f)) continue;
93
+ const p = readPassport(e.name, fs.readFileSync(f, 'utf-8'));
94
+ if (p !== null) out.push(p);
95
+ }
96
+ return out;
97
+ }
98
+
99
+ export interface Reconciliation {
100
+ bucket: string;
101
+ packSum: number;
102
+ censusTotal: number;
103
+ gapPct: number;
104
+ inBand: boolean;
105
+ }
106
+
107
+ export function reconcile(packSum: number, censusTotal: number, bucket: string): Reconciliation {
108
+ // Denominator is the CENSUS, which is the side with the independent
109
+ // measurement. Dividing by the pack sum would let a passport that measured
110
+ // almost nothing report a small gap.
111
+ const gapPct = censusTotal === 0 ? (packSum === 0 ? 0 : 1) : Math.abs(packSum - censusTotal) / censusTotal;
112
+ return { bucket, packSum, censusTotal, gapPct, inBand: gapPct <= TOLERANCE };
113
+ }
114
+
115
+ export function renderRanking(ps: readonly Passport[]): string {
116
+ const sorted = [...ps].sort((a, b) => b.total_tokens - a.total_tokens);
117
+ const total = sorted.reduce((a, p) => a + p.total_tokens, 0);
118
+ const lines = [
119
+ '<!-- evidence-type: analysis -->',
120
+ '# Pack token ranking — which packs carry the weight',
121
+ '',
122
+ 'GENERATED by `./scripts-run src/scripts/check_pack_passport_reconciliation --write-ranking`.',
123
+ 'Do not hand-edit. Basis `chars/4`, per `docs/contracts/pack-token-passport.md`.',
124
+ '',
125
+ 'The numbers are each pack\'s STANDING contribution — what is paid whether or',
126
+ 'not the pack is used. A skill body loaded on invocation is a runtime cost and',
127
+ 'is not here, and the residual bucket (tool definitions + dispatch prompt) is',
128
+ 'not attributable to a pack at all.',
129
+ '',
130
+ `**${String(sorted.length)} packs, ${String(total)} tokens total.**`,
131
+ '',
132
+ '| rank | pack | total | rules | catalog | commands | other | share |',
133
+ '|---:|---|---:|---:|---:|---:|---:|---:|',
134
+ ];
135
+ sorted.forEach((p, i) => {
136
+ const share = total === 0 ? 0 : (p.total_tokens / total) * 100;
137
+ lines.push(
138
+ `| ${String(i + 1)} | \`${p.pack}\` | ${String(p.total_tokens)} | ${String(p.rules_tokens)} | ` +
139
+ `${String(p.catalog_tokens)} | ${String(p.commands_tokens)} | ${String(p.other_tokens)} | ${share.toFixed(1)} % |`,
140
+ );
141
+ });
142
+ return `${lines.join('\n')}\n`;
143
+ }
144
+
145
+ /**
146
+ * Discrimination over the two pure units — the parser and the ratio.
147
+ *
148
+ * This script exits 0 on every path by design (it is a report, not a gate), so
149
+ * no planted file can make it red and the canary harness cannot prove anything
150
+ * about it. The `scanned:` floor is the one thing about it that CAN break, and
151
+ * a self-test is what proves the two functions behind the number actually
152
+ * discriminate rather than returning something shaped like an answer.
153
+ */
154
+ function selfTest(): number {
155
+ const yaml = [
156
+ 'artefact_count: 3',
157
+ 'token_passport:',
158
+ ' basis: chars/4',
159
+ ' catalog_tokens: 20',
160
+ ' commands_tokens: 5',
161
+ ' other_tokens: 1',
162
+ ' rules_tokens: 10',
163
+ ' total_tokens: 36',
164
+ ].join('\n');
165
+ return runSelfTest({
166
+ gate: 'check_pack_passport_reconciliation',
167
+ minCases: 6,
168
+ minRejectCases: 3,
169
+ cases: [
170
+ {
171
+ name: 'a manifest with a passport parses every field',
172
+ expect: 'reject',
173
+ run: () => {
174
+ const p = readPassport('x', yaml);
175
+ return p !== null && p.rules_tokens === 10 && p.total_tokens === 36 ? 1 : 0;
176
+ },
177
+ },
178
+ {
179
+ name: 'a manifest with NO passport returns null, not a zero passport',
180
+ expect: 'accept',
181
+ run: () => (readPassport('x', 'artefact_count: 3\n') === null ? 0 : 1),
182
+ },
183
+ {
184
+ name: 'an exact match is in band',
185
+ expect: 'accept',
186
+ run: () => (reconcile(100, 100, 'b').inBand ? 0 : 1),
187
+ },
188
+ {
189
+ name: 'a 9 % gap is in band and an 11 % gap is not',
190
+ expect: 'reject',
191
+ run: () => (reconcile(109, 100, 'b').inBand && !reconcile(111, 100, 'b').inBand ? 1 : 0),
192
+ },
193
+ {
194
+ // The denominator is the CENSUS, the side with the independent
195
+ // measurement. Dividing by the pack sum would let a passport
196
+ // that measured almost nothing report a small gap — which is
197
+ // exactly the failure this repo's own measurement produced.
198
+ name: 'the denominator is the census, not the pack sum',
199
+ expect: 'reject',
200
+ run: () => (Math.abs(reconcile(1, 100, 'b').gapPct - 0.99) < 1e-9 ? 1 : 0),
201
+ },
202
+ {
203
+ name: 'both sides zero is in band; a sum against a zero census is not',
204
+ expect: 'accept',
205
+ run: () => (reconcile(0, 0, 'b').inBand && !reconcile(5, 0, 'b').inBand ? 0 : 1),
206
+ },
207
+ ],
208
+ });
209
+ }
210
+
211
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
212
+ if (argv.includes('--self-test')) return selfTest();
213
+ const passports = collectPassports();
214
+ try {
215
+ assertScanned({
216
+ gate: 'check_pack_passport_reconciliation',
217
+ scanned: passports.length,
218
+ units: 'pack passport(s)',
219
+ roots: ['src/packs/*/pack.yaml'],
220
+ });
221
+ } catch (err) {
222
+ if (err instanceof DeadScopeError) {
223
+ process.stderr.write(`❌ passport reconciliation: ${err.message}\n`);
224
+ return 2;
225
+ }
226
+ throw err;
227
+ }
228
+ process.stdout.write(`scanned: ${String(passports.length)}\n`);
229
+
230
+ if (argv.includes('--write-ranking')) {
231
+ fs.mkdirSync(path.dirname(RANKING), { recursive: true });
232
+ fs.writeFileSync(RANKING, renderRanking(passports));
233
+ process.stdout.write(`✅ ranking written to ${path.relative(REPO_ROOT, RANKING)}\n`);
234
+ }
235
+
236
+ const rules = censusRuleDir(path.join(REPO_ROOT, 'dist', 'agent-src', 'rules'));
237
+ const catalog = censusSkillsCatalog(path.join(REPO_ROOT, 'dist', 'agent-src', 'skills'));
238
+ const tok = (chars: number): number => Math.round(chars / 4);
239
+ const recs = [
240
+ reconcile(
241
+ passports.reduce((a, p) => a + p.rules_tokens, 0),
242
+ tok(rules.chars),
243
+ 'project-scope rules',
244
+ ),
245
+ reconcile(
246
+ passports.reduce((a, p) => a + p.catalog_tokens, 0),
247
+ tok(catalog.chars),
248
+ 'preloaded skills catalog',
249
+ ),
250
+ ];
251
+ for (const r of recs) {
252
+ process.stdout.write(
253
+ ` ${r.inBand ? '✅' : '❌'} ${r.bucket.padEnd(26)} packs ${String(r.packSum).padStart(7)} · ` +
254
+ `census ${String(r.censusTotal).padStart(7)} · gap ${(r.gapPct * 100).toFixed(1)} %\n`,
255
+ );
256
+ }
257
+ const covered = recs.filter((r) => r.inBand).length;
258
+ process.stdout.write(
259
+ `\ncoverage: packs account for ${String(covered)} of ${String(recs.length)} census bucket(s) within ±${String(
260
+ TOLERANCE * 100,
261
+ )} %.\n` +
262
+ ' A gap here is a PROPERTY OF THE TREE, not a defect: pack `dependencies` claim a\n' +
263
+ ' small deliberate subset of what ships, so the two numbers are not a reconciliation\n' +
264
+ ' pair. Failing on that would be failing by construction, and a gate that is red by\n' +
265
+ ' construction gets disabled rather than fixed.\n' +
266
+ ' The useful output is the ranking above plus this figure. See\n' +
267
+ ' docs/contracts/pack-token-passport.md § Reconciliation for what would make it a gate.\n',
268
+ );
269
+ return 0;
270
+ }
271
+
272
+ if (process.argv[1] !== undefined && process.argv[1].includes('check_pack_passport_reconciliation')) {
273
+ process.exit(main(process.argv.slice(2)));
274
+ }
@@ -48,6 +48,17 @@ type Dict = Record<string, unknown>;
48
48
 
49
49
  interface Artefact {
50
50
  path: string;
51
+ /**
52
+ * On-disk byte size, captured where the ABSOLUTE path is still known.
53
+ *
54
+ * `path` above is a LOGICAL relative path whose root differs per collection
55
+ * mode, so it cannot be stat'd later. A first version of the token passport
56
+ * did exactly that, and every `statSync` threw into a catch that returned 0
57
+ * — 17 packs generated an all-zero passport that would then have
58
+ * "reconciled" against nothing. Carrying the number from the one place it
59
+ * is knowable removes the failure mode rather than handling it.
60
+ */
61
+ bytes: number;
51
62
  name: string;
52
63
  description: string;
53
64
  category: string;
@@ -143,8 +154,15 @@ function _artefact_record(p: string, logicalRel: string, fm: Dict): Artefact {
143
154
  : path.basename(p) === 'SKILL.md'
144
155
  ? path.basename(path.dirname(p))
145
156
  : _stem(p);
157
+ let bytes = 0;
158
+ try {
159
+ bytes = fs.statSync(p).size;
160
+ } catch {
161
+ bytes = 0;
162
+ }
146
163
  return {
147
164
  path: logicalRel,
165
+ bytes,
148
166
  name,
149
167
  description: _pyStrip(_pyTruthy(fm.description) ? String(fm.description) : ''),
150
168
  category: _category_from_logical(logicalRel),
@@ -247,6 +265,65 @@ function _category_from_logical(logicalRel: string): string {
247
265
  * Build the pack.yaml meta dict. Mirrors `_build_pack_yaml`. Insertion
248
266
  * order here is irrelevant — `_py_safe_dump` sorts keys (sort_keys=True).
249
267
  */
268
+ /**
269
+ * What this pack contributes to the STANDING session payload.
270
+ *
271
+ * Derivation, tokenizer and reconciliation target are fixed by
272
+ * `docs/contracts/pack-token-passport.md`, written before the first passport
273
+ * was generated — a number whose derivation is undocumented becomes a figure
274
+ * people quote without being able to reproduce.
275
+ *
276
+ * `chars/4`, deliberately, because that is the basis
277
+ * `preamble-payload-budget.json` declares and `preamble_byte_census.ts` uses. A
278
+ * passport measured with an exact tokenizer could not be reconciled against a
279
+ * census measured with a proxy without the gap between the two METHODS reading
280
+ * as a discrepancy in the packs.
281
+ *
282
+ * The catalog line is built the same way `censusSkillsCatalog` builds it —
283
+ * `- <name>: <description>\n` — rather than approximated, so the two sides of
284
+ * the reconciliation are counting the same string.
285
+ *
286
+ * It measures the STANDING contribution only. A skill body loaded on invocation
287
+ * is a runtime cost and is not here; the residual bucket (tool definitions +
288
+ * dispatch prompt) is not attributable to a pack at all. Both are stated in the
289
+ * contract, which is where the number lives.
290
+ */
291
+ function _token_passport(artefacts: readonly Artefact[]): Dict {
292
+ const tok = (chars: number): number => Math.round(chars / 4);
293
+ let ruleChars = 0;
294
+ let catalogChars = 0;
295
+ let commandChars = 0;
296
+ let otherChars = 0;
297
+ for (const a of artefacts) {
298
+ if (a.category === 'skill') {
299
+ catalogChars += `- ${a.name}: ${a.description}\n`.length;
300
+ continue;
301
+ }
302
+ if (a.category === 'rule') ruleChars += a.bytes;
303
+ else if (a.category === 'command') commandChars += a.bytes;
304
+ // Everything else a pack CLAIMS — contexts, personas, presets,
305
+ // profiles, user-types. Counted, because a pack that claims 47
306
+ // artefacts and reports 0 tokens is worse than no passport: it reads as
307
+ // "this pack is free" when it means "the three named buckets do not
308
+ // cover what this pack holds". Measured: `core` claims 47 artefacts and
309
+ // none of them is a rule, a skill or a command — `src/agent-src/`
310
+ // carries no `rules/` or `skills/` subtree at all.
311
+ else otherChars += a.bytes;
312
+ }
313
+ const rules_tokens = tok(ruleChars);
314
+ const catalog_tokens = tok(catalogChars);
315
+ const commands_tokens = tok(commandChars);
316
+ const other_tokens = tok(otherChars);
317
+ return {
318
+ basis: 'chars/4',
319
+ rules_tokens,
320
+ catalog_tokens,
321
+ commands_tokens,
322
+ other_tokens,
323
+ total_tokens: rules_tokens + catalog_tokens + commands_tokens + other_tokens,
324
+ };
325
+ }
326
+
250
327
  function _build_pack_yaml(pid: string, vocab: Record<string, Dict>, artefacts: Artefact[]): Dict {
251
328
  const meta = vocab[pid] ?? {};
252
329
  const label = _pyTruthy(meta.label)
@@ -276,6 +353,7 @@ function _build_pack_yaml(pid: string, vocab: Record<string, Dict>, artefacts: A
276
353
  ? (meta.trust_level_default as unknown)
277
354
  : 'core',
278
355
  artefact_count: artefacts.length,
356
+ token_passport: _token_passport(artefacts),
279
357
  };
280
358
  if (_isPlainObject(meta.onboarding)) {
281
359
  out.onboarding = meta.onboarding;
@@ -6,7 +6,10 @@
6
6
  "$comment": "Generated by scripts/generate_pack_manifests.py and validated on load by scripts/validate_pack_yaml.py. Schema introduced in road-to-6.0.0-D Phase 0 Step 2. `dependencies` is the artefact-level include graph (skills/rules the pack's commands reference); `requires` is the pack-level hard-dependency graph (must be acyclic — enforced by scripts/lint_pack_dependencies.py).",
7
7
  "type": "object",
8
8
  "additionalProperties": false,
9
- "required": ["id", "artefact_count"],
9
+ "required": [
10
+ "id",
11
+ "artefact_count"
12
+ ],
10
13
  "properties": {
11
14
  "id": {
12
15
  "type": "string",
@@ -27,7 +30,9 @@
27
30
  },
28
31
  "owner": {
29
32
  "type": "array",
30
- "items": { "type": "string" }
33
+ "items": {
34
+ "type": "string"
35
+ }
31
36
  },
32
37
  "requires": {
33
38
  "type": "array",
@@ -47,12 +52,21 @@
47
52
  },
48
53
  "size_class": {
49
54
  "type": "string",
50
- "enum": ["core", "small", "medium", "large", "platform"],
55
+ "enum": [
56
+ "core",
57
+ "small",
58
+ "medium",
59
+ "large",
60
+ "platform"
61
+ ],
51
62
  "description": "Per-pack visible-command budget input."
52
63
  },
53
64
  "surface_tier": {
54
65
  "type": "string",
55
- "enum": ["core", "lab"],
66
+ "enum": [
67
+ "core",
68
+ "lab"
69
+ ],
56
70
  "description": "Install-layout surface tier (road-to-install-contract-stability Phase 2). `core` = the lean stable multi-host engine that users install (rules + skills + install + condensation); `lab` = experimental / pilot domain tooling that must not destabilise the core install. Default `core`. The core-only install path excludes `lab` packs."
57
71
  },
58
72
  "trust_level_default": {
@@ -92,6 +106,50 @@
92
106
  }
93
107
  }
94
108
  }
109
+ },
110
+ "token_passport": {
111
+ "type": "object",
112
+ "additionalProperties": false,
113
+ "required": [
114
+ "basis",
115
+ "rules_tokens",
116
+ "catalog_tokens",
117
+ "commands_tokens",
118
+ "other_tokens",
119
+ "total_tokens"
120
+ ],
121
+ "description": "GENERATED — what this pack contributes to the STANDING session payload. Derivation, tokenizer and the reconciliation target are fixed by docs/contracts/pack-token-passport.md, written before the first passport existed. Standing cost only: a skill body loaded on invocation is a runtime cost and is not here, and the residual bucket (tool definitions + dispatch prompt) is not attributable to a pack at all. It is a floor on a pack's session cost, never the whole of it.",
122
+ "properties": {
123
+ "basis": {
124
+ "type": "string",
125
+ "enum": [
126
+ "chars/4"
127
+ ],
128
+ "description": "Pinned to an enum on purpose. chars/4 is the basis preamble-payload-budget.json declares for itself; a passport measured with an exact tokenizer could not be compared against a census measured with a proxy without the gap between two METHODS reading as a discrepancy in the packs."
129
+ },
130
+ "rules_tokens": {
131
+ "type": "integer",
132
+ "minimum": 0
133
+ },
134
+ "catalog_tokens": {
135
+ "type": "integer",
136
+ "minimum": 0,
137
+ "description": "Sum of `- <name>: <description>\\n` over the pack's skill artefacts — the SAME construction censusSkillsCatalog uses, not an approximation of it."
138
+ },
139
+ "commands_tokens": {
140
+ "type": "integer",
141
+ "minimum": 0
142
+ },
143
+ "other_tokens": {
144
+ "type": "integer",
145
+ "minimum": 0,
146
+ "description": "Every other claimed artefact — contexts, personas, presets, profiles, user-types. Exists because the three named buckets produced a misleading zero: `core` claimed 47 artefacts and reported 0 tokens, which reads as 'this pack is free'."
147
+ },
148
+ "total_tokens": {
149
+ "type": "integer",
150
+ "minimum": 0
151
+ }
152
+ }
95
153
  }
96
154
  }
97
155
  }
@@ -25,8 +25,8 @@
25
25
  "description": {
26
26
  "type": "string",
27
27
  "minLength": 1,
28
- "maxLength": 220,
29
- "description": "Trigger phrase; ≤ 200 chars recommended, 220 is the ceiling (lean-initial-context: descriptions load eagerly via progressive disclosure). Over-cap is a soft warning, not a hard fail — a warning window so authors adapt."
28
+ "maxLength": 200,
29
+ "description": "Trigger phrase; the ceiling is 200 chars, measured. Lowered from 220 on 2026-08-22 because 220 described no artefact that existed: across all 290 skills the distribution was median 181 / p75 189 / max 200, with ZERO over 200, so the 20 chars above the recommended line were dead headroom a future author could fill with no gate objecting. p75 = 189 is the recorded soft target; 200 is the hard line. Descriptions load eagerly via progressive disclosure, so every char here is standing cost on every spawn."
30
30
  },
31
31
  "compatibility": {
32
32
  "type": "string",