@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +49 -0
- package/dist/agent-src/skills/async-python-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/code-refactoring/SKILL.md +1 -1
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/design-review/SKILL.md +1 -1
- package/dist/agent-src/skills/discovery-interview/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +1 -1
- package/dist/agent-src/skills/github-ci/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
- package/dist/agent-src/skills/judge-injection-defense/SKILL.md +1 -1
- package/dist/agent-src/skills/laravel-validation/SKILL.md +1 -1
- package/dist/agent-src/skills/legal-intake-triage/SKILL.md +1 -1
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +1 -1
- package/dist/agent-src/skills/messaging-architecture/SKILL.md +1 -1
- package/dist/agent-src/skills/migration-architect/SKILL.md +1 -1
- package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +1 -1
- package/dist/agent-src/skills/module-management/SKILL.md +1 -1
- package/dist/agent-src/skills/motion-choreographer/SKILL.md +1 -1
- package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
- package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +1 -1
- package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
- package/dist/agent-src/skills/scene-expander/SKILL.md +1 -1
- package/dist/agent-src/skills/secrets-management/SKILL.md +1 -1
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +29 -29
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/package.json +1 -1
- package/src/config/gate-coverage.yml +20 -0
- package/src/config/preamble-payload-budget.json +71 -67
- package/src/scripts/ai_council/seating.ts +211 -0
- package/src/scripts/check_pack_passport_reconciliation.ts +274 -0
- package/src/scripts/generate_pack_manifests.ts +78 -0
- package/src/scripts/schemas/pack.schema.json +62 -4
- package/src/scripts/schemas/skill.schema.json +2 -2
- 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": [
|
|
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": {
|
|
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": [
|
|
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": [
|
|
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":
|
|
29
|
-
"description": "Trigger phrase;
|
|
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",
|