@event4u/agent-config 9.13.0 → 9.14.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 +48 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/optimize/deep.md +147 -0
- package/dist/agent-src/commands/optimize.md +12 -6
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-QFKQLQ7X.js → chunk-GCVFLBP2.js} +399 -365
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +39 -11
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +5 -4
- package/dist/install/install.mjs +648 -614
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +46 -13
- package/package.json +1 -1
- package/src/scripts/_lib/exec_evidence.ts +5 -0
- package/src/scripts/_lib/scoped_projection.ts +212 -0
- package/src/scripts/check_artefact_count_messaging.ts +128 -15
- package/src/scripts/count_scoped_projection.ts +104 -0
- package/src/scripts/install.ts +13 -90
- package/src/scripts/skill_linter.ts +81 -14
- package/src/scripts/update_counts.ts +90 -11
package/src/scripts/install.ts
CHANGED
|
@@ -74,6 +74,7 @@ import type * as YamlModule from 'yaml';
|
|
|
74
74
|
import { build_merge_entries } from './_lib/json_pointers.js';
|
|
75
75
|
import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
|
|
76
76
|
import * as installed_lock from './_lib/installed_lock.js';
|
|
77
|
+
import * as scoped_projection from './_lib/scoped_projection.js';
|
|
77
78
|
import * as surface_tiers from './_lib/surface_tiers.js';
|
|
78
79
|
import * as global_deploy_inventory from './_lib/global_deploy_inventory.js';
|
|
79
80
|
import * as installed_tools from './_lib/installed_tools.js';
|
|
@@ -3344,89 +3345,16 @@ function _prune_lab_modules(
|
|
|
3344
3345
|
// `requires` graph). Mirrors the core-only lab prune above — same
|
|
3345
3346
|
// `_prune_modules_by` mechanics, a different predicate.
|
|
3346
3347
|
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
requires: string[];
|
|
3354
|
-
}
|
|
3355
|
-
|
|
3356
|
-
/**
|
|
3357
|
-
* Parse `src/config/discovery/packs.yml` into `{id, workspaces, requires}`
|
|
3358
|
-
* records. `requires` falls back to the legacy `requires_hint` field name
|
|
3359
|
-
* when `requires` is absent.
|
|
3360
|
-
*
|
|
3361
|
-
* Deliberately THROWS on a missing file or a parse result that is not a
|
|
3362
|
-
* list — an empty active-pack set would be the LEAST safe fallback here
|
|
3363
|
-
* (it would prune every tagged artefact, the opposite of the fail-safe
|
|
3364
|
-
* contract). The caller wraps this in a try/catch that restores the full
|
|
3365
|
-
* tree on any error instead.
|
|
3366
|
-
*/
|
|
3367
|
-
function _load_packs_registry(package_root: string): PackRecord[] {
|
|
3368
|
-
const vocab_path = path.join(package_root, 'src', 'config', 'discovery', 'packs.yml');
|
|
3369
|
-
const data = yamlSafeLoad(readText(vocab_path));
|
|
3370
|
-
if (!Array.isArray(data)) {
|
|
3371
|
-
throw new Error(`packs.yml did not parse to a list: ${vocab_path}`);
|
|
3372
|
-
}
|
|
3373
|
-
const out: PackRecord[] = [];
|
|
3374
|
-
for (const entry of data) {
|
|
3375
|
-
if (!_isPlainObject(entry)) continue;
|
|
3376
|
-
const id = entry['id'];
|
|
3377
|
-
if (typeof id !== 'string' || id === '') continue;
|
|
3378
|
-
const workspaces_raw = entry['workspaces'];
|
|
3379
|
-
const workspaces = Array.isArray(workspaces_raw)
|
|
3380
|
-
? workspaces_raw.filter((w): w is string => typeof w === 'string')
|
|
3381
|
-
: [];
|
|
3382
|
-
const requires_raw = entry['requires'] ?? entry['requires_hint'];
|
|
3383
|
-
const requires = Array.isArray(requires_raw)
|
|
3384
|
-
? requires_raw.filter((r): r is string => typeof r === 'string')
|
|
3385
|
-
: [];
|
|
3386
|
-
out.push({ id, workspaces, requires });
|
|
3387
|
-
}
|
|
3388
|
-
return out;
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3391
|
-
/**
|
|
3392
|
-
* Active pack id set for `scoped` mode: every pack whose `workspaces`
|
|
3393
|
-
* intersects `SCOPED_ACTIVE_WORKSPACES`, unioned with `runtime_active_packs`,
|
|
3394
|
-
* expanded over the `requires` graph (transitive closure).
|
|
3348
|
+
/*
|
|
3349
|
+
* The scoped-projection predicate lives in `_lib/scoped_projection.ts` so the
|
|
3350
|
+
* installer and `count_scoped_projection.ts` — which produces the number the
|
|
3351
|
+
* claims ledger publishes — run the SAME code rather than the same rule
|
|
3352
|
+
* written twice. Re-exported under the existing private names to keep the
|
|
3353
|
+
* prune tests' import surface unchanged.
|
|
3395
3354
|
*/
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
): Set<string> {
|
|
3400
|
-
const by_id = new Map<string, PackRecord>();
|
|
3401
|
-
for (const p of packs) by_id.set(p.id, p);
|
|
3402
|
-
|
|
3403
|
-
const active = new Set<string>();
|
|
3404
|
-
for (const p of packs) {
|
|
3405
|
-
if (p.workspaces.some((w) => SCOPED_ACTIVE_WORKSPACES.has(w))) {
|
|
3406
|
-
active.add(p.id);
|
|
3407
|
-
}
|
|
3408
|
-
}
|
|
3409
|
-
for (const id of runtime_active_packs) {
|
|
3410
|
-
active.add(id);
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
let frontier = [...active];
|
|
3414
|
-
while (frontier.length > 0) {
|
|
3415
|
-
const next: string[] = [];
|
|
3416
|
-
for (const id of frontier) {
|
|
3417
|
-
const rec = by_id.get(id);
|
|
3418
|
-
if (rec === undefined) continue;
|
|
3419
|
-
for (const dep of rec.requires) {
|
|
3420
|
-
if (!active.has(dep)) {
|
|
3421
|
-
active.add(dep);
|
|
3422
|
-
next.push(dep);
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
}
|
|
3426
|
-
frontier = next;
|
|
3427
|
-
}
|
|
3428
|
-
return active;
|
|
3429
|
-
}
|
|
3355
|
+
type PackRecord = scoped_projection.PackRecord;
|
|
3356
|
+
const _load_packs_registry = scoped_projection.load_packs_registry;
|
|
3357
|
+
const _compute_active_pack_ids = scoped_projection.compute_active_pack_ids;
|
|
3430
3358
|
|
|
3431
3359
|
/**
|
|
3432
3360
|
* Read the GLOBAL settings document that governs a global deploy, if one
|
|
@@ -3574,14 +3502,9 @@ function _prune_scoped_modules(
|
|
|
3574
3502
|
deploy_results: Record<string, DeployResult>,
|
|
3575
3503
|
active_ids: ReadonlySet<string>,
|
|
3576
3504
|
): [number, Record<string, DeployResult>] {
|
|
3577
|
-
return _prune_modules_by(deploy_results, (p) =>
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
for (const id of packs) {
|
|
3581
|
-
if (active_ids.has(id)) return false;
|
|
3582
|
-
}
|
|
3583
|
-
return true;
|
|
3584
|
-
});
|
|
3505
|
+
return _prune_modules_by(deploy_results, (p) =>
|
|
3506
|
+
scoped_projection.is_pruned_under_scoped(p, active_ids),
|
|
3507
|
+
);
|
|
3585
3508
|
}
|
|
3586
3509
|
|
|
3587
3510
|
function install_global(
|
|
@@ -2439,6 +2439,16 @@ export function gather_candidate_files_under(srcRoot: string): string[] {
|
|
|
2439
2439
|
*/
|
|
2440
2440
|
export let last_changed_scope_reason = '';
|
|
2441
2441
|
|
|
2442
|
+
/**
|
|
2443
|
+
* Whether the base-span diff (`base...HEAD`) itself ran cleanly.
|
|
2444
|
+
*
|
|
2445
|
+
* The verified-empty release outcome (see `main`) may only rest on an empty
|
|
2446
|
+
* change set that was actually COMPUTED — a base diff that errored (missing
|
|
2447
|
+
* tag object, corrupt ref) degrades to the worktree fallbacks and would
|
|
2448
|
+
* otherwise be indistinguishable from a genuinely empty span.
|
|
2449
|
+
*/
|
|
2450
|
+
export let last_changed_base_diff_ok = false;
|
|
2451
|
+
|
|
2442
2452
|
/**
|
|
2443
2453
|
* Candidate files changed against the resolved base.
|
|
2444
2454
|
*
|
|
@@ -2454,6 +2464,7 @@ export let last_changed_scope_reason = '';
|
|
|
2454
2464
|
export function gather_changed_candidate_files(root: string, since?: string | null): string[] {
|
|
2455
2465
|
const scope = resolveContentLintScope({ cwd: root, since: since ?? null });
|
|
2456
2466
|
last_changed_scope_reason = scope.reason;
|
|
2467
|
+
last_changed_base_diff_ok = false;
|
|
2457
2468
|
const diffCommands = [
|
|
2458
2469
|
['git', 'diff', '--name-only', `${scope.base}...HEAD`],
|
|
2459
2470
|
['git', 'diff', '--name-only', '--cached', 'HEAD'],
|
|
@@ -2461,10 +2472,15 @@ export function gather_changed_candidate_files(root: string, since?: string | nu
|
|
|
2461
2472
|
];
|
|
2462
2473
|
try {
|
|
2463
2474
|
let rawLines: string[] = [];
|
|
2475
|
+
let first = true;
|
|
2464
2476
|
for (const cmd of diffCommands) {
|
|
2465
2477
|
// A truncated diff would silently shrink the change set — the same
|
|
2466
2478
|
// degrade-to-zero shape this roadmap closes elsewhere.
|
|
2467
2479
|
const result = runCountedProbe(cmd[0] as string, cmd.slice(1), { cwd: root });
|
|
2480
|
+
if (first) {
|
|
2481
|
+
last_changed_base_diff_ok = result.ok;
|
|
2482
|
+
first = false;
|
|
2483
|
+
}
|
|
2468
2484
|
if (result.ok && result.stdout.trim()) {
|
|
2469
2485
|
rawLines = splitlines(result.stdout);
|
|
2470
2486
|
break;
|
|
@@ -4202,6 +4218,43 @@ function _release_scope_requires_findings(root: string, since?: string | null):
|
|
|
4202
4218
|
return scope.isRelease && scope.previousTag !== null;
|
|
4203
4219
|
}
|
|
4204
4220
|
|
|
4221
|
+
/**
|
|
4222
|
+
* True when the `--changed` corpus filter still matches tracked files at HEAD.
|
|
4223
|
+
*
|
|
4224
|
+
* The discriminator between a DEAD scope and a VERIFIED-EMPTY release span.
|
|
4225
|
+
* A release that touches no skill/rule file is a legitimate outcome — but only
|
|
4226
|
+
* provably so when the exact predicate `gather_changed_candidate_files`
|
|
4227
|
+
* applies to diff lines demonstrably still sees the corpus. Probing
|
|
4228
|
+
* `git ls-files` with that same predicate means a moved projection root
|
|
4229
|
+
* (the ADR-051 blindness class) fails this probe exactly as it would blind
|
|
4230
|
+
* the diff — so "empty because the root moved" cannot masquerade as
|
|
4231
|
+
* "empty because nothing changed".
|
|
4232
|
+
*/
|
|
4233
|
+
function _changed_filter_matches_head(root: string): boolean {
|
|
4234
|
+
// Probes the canonical projection root only (ADR-051): a corpus that
|
|
4235
|
+
// exists solely under a legacy path counts as dead, not alive.
|
|
4236
|
+
const result = runCountedProbe('git', ['ls-files', '--', 'dist/agent-src'], { cwd: root });
|
|
4237
|
+
if (!result.ok) {
|
|
4238
|
+
return false;
|
|
4239
|
+
}
|
|
4240
|
+
for (const raw of splitlines(result.stdout)) {
|
|
4241
|
+
const norm = raw.trim().replace(/\\/g, '/');
|
|
4242
|
+
if (!norm || norm.includes('/evals/')) {
|
|
4243
|
+
continue;
|
|
4244
|
+
}
|
|
4245
|
+
if (!norm.startsWith('dist/agent-src/') && !norm.includes('/dist/agent-src/')) {
|
|
4246
|
+
continue;
|
|
4247
|
+
}
|
|
4248
|
+
if (
|
|
4249
|
+
norm.endsWith('/SKILL.md') ||
|
|
4250
|
+
(norm.endsWith('.md') && (norm.includes('/rules/') || norm.includes('/commands/')))
|
|
4251
|
+
) {
|
|
4252
|
+
return true;
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
return false;
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4205
4258
|
export function main(argv: string[]): number {
|
|
4206
4259
|
let args: Args;
|
|
4207
4260
|
try {
|
|
@@ -4244,25 +4297,39 @@ export function main(argv: string[]): number {
|
|
|
4244
4297
|
process.stdout.write(`${format_json([])}\n`);
|
|
4245
4298
|
}
|
|
4246
4299
|
// A release PR that resolved a real tag span and still matched
|
|
4247
|
-
// nothing
|
|
4248
|
-
//
|
|
4300
|
+
// nothing needs a discriminator: DEAD scope (the diff errored, or
|
|
4301
|
+
// the corpus filter no longer matches anything at HEAD — the
|
|
4302
|
+
// ADR-051 moved-root blindness) fails; VERIFIED-EMPTY (the base
|
|
4303
|
+
// diff ran cleanly AND the filter provably still sees tracked
|
|
4304
|
+
// corpus files) is a legitimate release that simply changed no
|
|
4305
|
+
// skill/rule — failing it would red every scripts-/docs-only
|
|
4306
|
+
// release forever. Mirrors the scan-scope regime
|
|
4249
4307
|
// (`_lib/scan_scope.ts`; exit 2, as `check_iron_law_prominence`).
|
|
4250
4308
|
// Ordinary PRs keep the historical exit 0: a change set with no
|
|
4251
4309
|
// skill or rule in it is a legitimate empty scope, not a dead one.
|
|
4252
4310
|
if (args.changed && _release_scope_requires_findings(root, args.since)) {
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
}
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4311
|
+
if (last_changed_base_diff_ok && _changed_filter_matches_head(root)) {
|
|
4312
|
+
process.stderr.write(
|
|
4313
|
+
'✅ verified-empty release scope: the base-span diff ran cleanly and ' +
|
|
4314
|
+
'the corpus filter still matches tracked files at HEAD — this ' +
|
|
4315
|
+
'release genuinely changes no skill/rule file. The gate is alive, ' +
|
|
4316
|
+
'not blind.\n',
|
|
4317
|
+
);
|
|
4318
|
+
} else {
|
|
4319
|
+
try {
|
|
4320
|
+
assertScanned({
|
|
4321
|
+
gate: 'skill_linter --changed (release PR)',
|
|
4322
|
+
scanned: 0,
|
|
4323
|
+
units: 'skill/rule files',
|
|
4324
|
+
roots: [last_changed_scope_reason],
|
|
4325
|
+
});
|
|
4326
|
+
} catch (exc) {
|
|
4327
|
+
if (exc instanceof DeadScopeError) {
|
|
4328
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
4329
|
+
return 2;
|
|
4330
|
+
}
|
|
4331
|
+
throw exc;
|
|
4264
4332
|
}
|
|
4265
|
-
throw exc;
|
|
4266
4333
|
}
|
|
4267
4334
|
}
|
|
4268
4335
|
process.stderr.write('No matching skill/rule files found.\n');
|
|
@@ -26,6 +26,8 @@ import process from 'node:process';
|
|
|
26
26
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
27
|
|
|
28
28
|
import { artefact_roots, iter_commands } from './_lib/agent_src.js';
|
|
29
|
+
import { scoped_projection_stats } from './_lib/scoped_projection.js';
|
|
30
|
+
import { canonical_counts } from './check_command_count_messaging.js';
|
|
29
31
|
|
|
30
32
|
const _HERE = fileURLToPath(import.meta.url);
|
|
31
33
|
// _HERE === <repo>/src/scripts/update_counts.ts ; the retired Python implementation derives
|
|
@@ -77,7 +79,48 @@ function _exists(p: string): boolean {
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Every `SKILL.md` the canonical `count('skills')` counts, as absolute paths.
|
|
84
|
+
*
|
|
85
|
+
* Exported so `count_scoped_projection.ts` partitions the SAME file set the
|
|
86
|
+
* ledger's total is derived from. That is what makes the published pair
|
|
87
|
+
* ("N of M skills") coherent by construction: `projected + pruned === M`
|
|
88
|
+
* cannot drift, because there is one walk, not two.
|
|
89
|
+
*/
|
|
90
|
+
export function* iter_skills(): Generator<string> {
|
|
91
|
+
const seen = new Set<string>();
|
|
92
|
+
for (const root of artefact_roots()) {
|
|
93
|
+
const subdir = path.join(root, 'skills');
|
|
94
|
+
if (!_exists(subdir)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
for (const f of _rglob(subdir, (n) => n === 'SKILL.md')) {
|
|
98
|
+
const rel = path.relative(root, f).split(path.sep).join('/');
|
|
99
|
+
if (seen.has(rel)) continue;
|
|
100
|
+
seen.add(rel);
|
|
101
|
+
yield f;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
80
106
|
export function count(kind: string): number {
|
|
107
|
+
if (kind === 'commands_active') {
|
|
108
|
+
// Commands MINUS deprecation shims — the number the hero badge and the
|
|
109
|
+
// "Browse all N active commands" line carry, and the one prose
|
|
110
|
+
// mentions resolve to. Distinct from `commands` (raw file count):
|
|
111
|
+
// they are equal only while the shim set is empty, so anchoring an
|
|
112
|
+
// active-count position to `commands` would rewrite it to the wrong
|
|
113
|
+
// number the moment a command is superseded.
|
|
114
|
+
const [, , active] = canonical_counts();
|
|
115
|
+
return active;
|
|
116
|
+
}
|
|
117
|
+
if (kind === 'skills_scoped') {
|
|
118
|
+
// Skills a DEFAULT `projection.mode: scoped` install deploys. Derived
|
|
119
|
+
// by the installer's own prune predicate over the same walk the
|
|
120
|
+
// canonical `skills` total uses, so the published pair
|
|
121
|
+
// ("N of M skills") cannot drift apart. See count_scoped_projection.ts.
|
|
122
|
+
return scoped_projection_stats(REPO_ROOT, iter_skills()).projected;
|
|
123
|
+
}
|
|
81
124
|
if (kind === 'router_rules') {
|
|
82
125
|
// Routed rule entries in the compiled router (kernel + tier_1 +
|
|
83
126
|
// tier_2). Read from dist/router.json — the artifact hosts consume —
|
|
@@ -119,19 +162,19 @@ export function count(kind: string): number {
|
|
|
119
162
|
}
|
|
120
163
|
return total;
|
|
121
164
|
}
|
|
165
|
+
if (kind === 'skills') {
|
|
166
|
+
// Single walk, shared with count_scoped_projection.ts — see iter_skills.
|
|
167
|
+
for (const _ of iter_skills()) {
|
|
168
|
+
total += 1;
|
|
169
|
+
}
|
|
170
|
+
return total;
|
|
171
|
+
}
|
|
122
172
|
for (const root of artefact_roots()) {
|
|
123
173
|
const subdir = path.join(root, kind);
|
|
124
174
|
if (!_exists(subdir)) {
|
|
125
175
|
continue;
|
|
126
176
|
}
|
|
127
|
-
if (kind === '
|
|
128
|
-
for (const f of _rglob(subdir, (n) => n === 'SKILL.md')) {
|
|
129
|
-
const rel = path.relative(root, f).split(path.sep).join('/');
|
|
130
|
-
if (seen.has(rel)) continue;
|
|
131
|
-
seen.add(rel);
|
|
132
|
-
total += 1;
|
|
133
|
-
}
|
|
134
|
-
} else if (kind === 'personas') {
|
|
177
|
+
if (kind === 'personas') {
|
|
135
178
|
// personas live as flat .md files, README excluded
|
|
136
179
|
for (const f of _glob(subdir, (n) => n.endsWith('.md'))) {
|
|
137
180
|
if (path.basename(f) === 'README.md') continue;
|
|
@@ -164,6 +207,12 @@ export const TARGETS: ReadonlyArray<[string, ReadonlyArray<[string, string]>]> =
|
|
|
164
207
|
// badge is owned by check_command_count_messaging.
|
|
165
208
|
['(/badge/Skills-)(\\d+)(-)', 'skills'],
|
|
166
209
|
['(/badge/Rules-)(\\d+)(-)', 'rules'],
|
|
210
|
+
// The Commands badge WAS left to check_command_count_messaging
|
|
211
|
+
// alone ("avoids double-ownership"). Nothing generated it, so it
|
|
212
|
+
// drifted to 191 against a canonical 192 and only the checker
|
|
213
|
+
// noticed — the exact gap this roadmap closes. Generator writes,
|
|
214
|
+
// checker verifies: that IS the ownership split, not a conflict.
|
|
215
|
+
['(/badge/Commands-)(\\d+)(-)', 'commands_active'],
|
|
167
216
|
['(/badge/Guidelines-)(\\d+)(-)', 'guidelines'],
|
|
168
217
|
['(/badge/Personas-)(\\d+)(-)', 'personas'],
|
|
169
218
|
],
|
|
@@ -177,8 +226,22 @@ export const TARGETS: ReadonlyArray<[string, ReadonlyArray<[string, string]>]> =
|
|
|
177
226
|
['(- claim: )(\\d+)( skills\\.)', 'skills'],
|
|
178
227
|
['(- claim: )(\\d+)( commands\\.)', 'commands'],
|
|
179
228
|
['(- claim: )(\\d+)( governed rules\\.)', 'rules'],
|
|
229
|
+
// The scoped-projection claim: BOTH halves of "N of M skills".
|
|
230
|
+
// `M` was scanner-checked but generator-blind (a maintainer
|
|
231
|
+
// hand-edited it on every skill addition); `N` was guarded by
|
|
232
|
+
// nothing at all and had drifted 2 from the benchmark doc the
|
|
233
|
+
// claim names as its own method.
|
|
234
|
+
['(installs ships )(\\d+)( of )', 'skills_scoped'],
|
|
235
|
+
['( of )(\\d+)( skills \\(untagged core)', 'skills'],
|
|
236
|
+
// "14 of 111 governed rules" — the total was scanner-checked and
|
|
237
|
+
// generator-blind for the same reason.
|
|
238
|
+
['( of )(\\d+)( governed rules \\()', 'rules'],
|
|
180
239
|
],
|
|
181
240
|
],
|
|
241
|
+
[
|
|
242
|
+
'docs/command-flows.md',
|
|
243
|
+
[['(\\*\\*)(\\d+)( commands\\*\\*)', 'commands_active']],
|
|
244
|
+
],
|
|
182
245
|
[
|
|
183
246
|
'docs/getting-started-by-role.md',
|
|
184
247
|
[
|
|
@@ -206,6 +269,11 @@ export const TARGETS: ReadonlyArray<[string, ReadonlyArray<[string, string]>]> =
|
|
|
206
269
|
'docs/getting-started.md',
|
|
207
270
|
[
|
|
208
271
|
['(automatically by )(\\d+)( rules)', 'rules'],
|
|
272
|
+
// Was deliberately unsynced to avoid double-ownership with
|
|
273
|
+
// check_command_count_messaging; it drifted to 191 as a result.
|
|
274
|
+
// Now generated from `commands_active` — the SAME canonical the
|
|
275
|
+
// checker compares against, so the two cannot disagree.
|
|
276
|
+
['(Browse all )(\\d+)( active commands)', 'commands_active'],
|
|
209
277
|
// NOTE: the "Browse all N active commands" line here carries the
|
|
210
278
|
// *active* command count and is owned by
|
|
211
279
|
// check_command_count_messaging.py — intentionally not synced
|
|
@@ -254,7 +322,16 @@ export function apply_to_text(
|
|
|
254
322
|
return [new_text, drifts];
|
|
255
323
|
}
|
|
256
324
|
|
|
257
|
-
const KINDS = [
|
|
325
|
+
const KINDS = [
|
|
326
|
+
'skills',
|
|
327
|
+
'skills_scoped',
|
|
328
|
+
'rules',
|
|
329
|
+
'commands',
|
|
330
|
+
'commands_active',
|
|
331
|
+
'guidelines',
|
|
332
|
+
'personas',
|
|
333
|
+
'router_rules',
|
|
334
|
+
] as const;
|
|
258
335
|
|
|
259
336
|
function parse_args(argv: readonly string[]): { check: boolean } {
|
|
260
337
|
const args = { check: false };
|
|
@@ -282,8 +359,10 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
282
359
|
counts[k] = count(k);
|
|
283
360
|
}
|
|
284
361
|
process.stdout.write(
|
|
285
|
-
`📊 Truth: skills=${counts['skills']}
|
|
286
|
-
`
|
|
362
|
+
`📊 Truth: skills=${counts['skills']} skills_scoped=${counts['skills_scoped']} ` +
|
|
363
|
+
`rules=${counts['rules']} commands=${counts['commands']} ` +
|
|
364
|
+
`commands_active=${counts['commands_active']} ` +
|
|
365
|
+
`guidelines=${counts['guidelines']} ` +
|
|
287
366
|
`personas=${counts['personas']} router_rules=${counts['router_rules']}\n`,
|
|
288
367
|
);
|
|
289
368
|
|