@event4u/agent-config 9.20.0 → 9.21.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 +51 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-XGA2NTU4.js → chunk-3EWYC3YK.js} +40 -0
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/hooks/dispatch.js +58 -2
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +47 -29
- package/package.json +1 -1
- package/src/config/gate-coverage.yml +22 -4
- package/src/config/gate-violation-baselines.json +3 -8
- package/src/scripts/_lib/gate_population.ts +70 -0
- package/src/scripts/_lib/roadmap_granularity.ts +157 -0
- package/src/scripts/_lib/scan_scope.ts +44 -0
- package/src/scripts/_lib/stdin.ts +82 -0
- package/src/scripts/audit_adr_coverage.ts +38 -0
- package/src/scripts/audit_auto_rules.ts +17 -3
- package/src/scripts/audit_command_surface.ts +51 -0
- package/src/scripts/audit_initial_context.ts +39 -3
- package/src/scripts/audit_mcp_tools.ts +25 -0
- package/src/scripts/audit_overlap.ts +24 -1
- package/src/scripts/audit_skill_descriptions.ts +19 -0
- package/src/scripts/{verify_before_complete_hook.ts → before_complete_hook.ts} +16 -3
- package/src/scripts/check_adr_frontmatter.ts +23 -3
- package/src/scripts/check_always_budget.ts +24 -0
- package/src/scripts/check_artefact_checksums.ts +24 -1
- package/src/scripts/check_artefact_count_messaging.ts +20 -0
- package/src/scripts/check_augmentignore.ts +22 -15
- package/src/scripts/check_backstop_debt.ts +22 -0
- package/src/scripts/check_beta_review_markers.ts +22 -1
- package/src/scripts/check_bridge_derivation.ts +22 -1
- package/src/scripts/check_bundle_path_leakage.ts +26 -0
- package/src/scripts/check_ci_strict_superset.ts +21 -3
- package/src/scripts/check_claims.ts +22 -0
- package/src/scripts/check_cli_registry_budget_sync.ts +33 -4
- package/src/scripts/check_cluster_patterns.ts +26 -4
- package/src/scripts/check_command_count_messaging.ts +20 -4
- package/src/scripts/check_comparison.ts +18 -0
- package/src/scripts/check_condensed_paths.ts +23 -2
- package/src/scripts/check_consumer_scope_flip.ts +21 -2
- package/src/scripts/check_council_config_location.ts +20 -0
- package/src/scripts/check_council_layout.ts +22 -2
- package/src/scripts/check_council_references.ts +23 -1
- package/src/scripts/check_dependency_floors.ts +19 -3
- package/src/scripts/check_discovery_determinism.ts +26 -0
- package/src/scripts/check_enforcement_coverage.ts +20 -0
- package/src/scripts/check_evaluator_budgets.ts +23 -0
- package/src/scripts/check_finding_dispositions.ts +29 -0
- package/src/scripts/check_gate_coverage.ts +63 -22
- package/src/scripts/check_gate_paths.ts +30 -5
- package/src/scripts/check_generated_artefact_headers.ts +21 -0
- package/src/scripts/check_generator_output_coverage.ts +23 -0
- package/src/scripts/check_gitignore_freshness.ts +23 -0
- package/src/scripts/check_host_loadability.ts +34 -8
- package/src/scripts/check_installer_import_purity.ts +22 -8
- package/src/scripts/check_kernel_prefix_stability.ts +19 -1
- package/src/scripts/check_kernel_rule_bundle.ts +23 -0
- package/src/scripts/check_knowledge_cards.ts +13 -0
- package/src/scripts/check_knowledge_pages.ts +23 -0
- package/src/scripts/check_knowledge_sharing.ts +27 -0
- package/src/scripts/check_md_language.ts +24 -0
- package/src/scripts/check_media_deps.ts +23 -0
- package/src/scripts/check_memory.ts +26 -1
- package/src/scripts/check_memory_contradiction.ts +16 -1
- package/src/scripts/check_memory_proposal.ts +15 -0
- package/src/scripts/check_memory_similarity.ts +16 -0
- package/src/scripts/check_module_management_neutral.ts +19 -11
- package/src/scripts/check_no_conflict_markers.ts +22 -1
- package/src/scripts/check_no_external_sources.ts +26 -1
- package/src/scripts/check_no_local_settings_committed.ts +37 -10
- package/src/scripts/check_no_new_legacy_path.ts +30 -1
- package/src/scripts/check_one_off_location.ts +43 -0
- package/src/scripts/check_originality_freshness.ts +25 -1
- package/src/scripts/check_overlay_cascade_subdirs.ts +21 -0
- package/src/scripts/check_pack_size.ts +22 -0
- package/src/scripts/check_preamble_payload_budget.ts +35 -5
- package/src/scripts/check_proposal.ts +24 -1
- package/src/scripts/check_public_catalog_links.ts +27 -0
- package/src/scripts/check_public_links.ts +19 -0
- package/src/scripts/check_quality_regression.ts +19 -0
- package/src/scripts/check_reach_channels.ts +44 -0
- package/src/scripts/check_reach_staleness.ts +27 -0
- package/src/scripts/check_release_adjacent_health.ts +28 -0
- package/src/scripts/check_release_highlights.ts +34 -0
- package/src/scripts/check_release_includes_discovery.ts +25 -6
- package/src/scripts/check_release_pr_shape.ts +18 -1
- package/src/scripts/check_release_published.ts +21 -0
- package/src/scripts/check_release_surface_equality.ts +22 -0
- package/src/scripts/check_release_trunk_sync.ts +19 -0
- package/src/scripts/check_reply_consistency.ts +22 -0
- package/src/scripts/check_roadmap_trackable.ts +20 -0
- package/src/scripts/check_role_doc_links.ts +23 -0
- package/src/scripts/check_rule_invariants.ts +14 -0
- package/src/scripts/check_secret_leak.ts +47 -0
- package/src/scripts/check_skill_gaps.ts +32 -0
- package/src/scripts/check_skill_requires.ts +19 -0
- package/src/scripts/check_source_pointer_freshness.ts +20 -0
- package/src/scripts/check_structural_breaking.ts +29 -2
- package/src/scripts/check_surface_tiers.ts +34 -0
- package/src/scripts/check_template_pin_drift.ts +23 -1
- package/src/scripts/check_test_coverage_diff.ts +17 -1
- package/src/scripts/check_token_optimizer_freshness.ts +18 -3
- package/src/scripts/check_token_quality_golden.ts +21 -0
- package/src/scripts/check_token_regression.ts +25 -0
- package/src/scripts/check_tracked_but_ignored.ts +26 -0
- package/src/scripts/check_trigger_eval_presence.ts +23 -1
- package/src/scripts/check_trigger_evals.ts +36 -0
- package/src/scripts/check_trunk_drift.ts +23 -0
- package/src/scripts/check_update_banner.ts +24 -0
- package/src/scripts/gate_scope_classify.ts +513 -0
- package/src/scripts/hook_manifest.yaml +1 -1
- package/src/scripts/hooks/block_kernel_rule_writes.ts +1 -1
- package/src/scripts/hooks/concern_registry.ts +2 -2
- package/src/scripts/lint_agent_security.ts +19 -0
- package/src/scripts/lint_agent_skill_names.ts +30 -0
- package/src/scripts/lint_agents_layout.ts +20 -0
- package/src/scripts/lint_agents_md.ts +20 -0
- package/src/scripts/lint_archived_skills.ts +24 -3
- package/src/scripts/lint_behavioural_eval_freshness.ts +29 -3
- package/src/scripts/lint_bench_ab.ts +25 -0
- package/src/scripts/lint_bench_corpus.ts +20 -4
- package/src/scripts/lint_breaking_changes_index.ts +24 -0
- package/src/scripts/lint_budget_ownership.ts +20 -3
- package/src/scripts/lint_changelog_rollback.ts +24 -0
- package/src/scripts/lint_command_flow_coverage.ts +22 -0
- package/src/scripts/lint_command_tiers.ts +29 -5
- package/src/scripts/lint_commit_subjects.ts +15 -0
- package/src/scripts/lint_confusables.ts +24 -0
- package/src/scripts/lint_consumer_internal_refs.ts +24 -0
- package/src/scripts/lint_design_quality.ts +22 -0
- package/src/scripts/lint_design_slop.ts +23 -0
- package/src/scripts/lint_discovery_manifest.ts +23 -0
- package/src/scripts/lint_discovery_vocabulary.ts +19 -0
- package/src/scripts/lint_documented_commands.ts +23 -0
- package/src/scripts/lint_empty_roadmaps.ts +23 -0
- package/src/scripts/lint_eval_fixture_citations.ts +19 -0
- package/src/scripts/lint_eval_freshness.ts +35 -0
- package/src/scripts/lint_examples.ts +18 -3
- package/src/scripts/lint_explain_trace.ts +23 -0
- package/src/scripts/lint_featured_skills.ts +26 -1
- package/src/scripts/lint_flows.ts +19 -0
- package/src/scripts/lint_frontmatter_boilerplate.ts +20 -0
- package/src/scripts/lint_ghostwriter_source.ts +26 -6
- package/src/scripts/lint_glama_drift.ts +24 -0
- package/src/scripts/lint_global_paths.ts +24 -0
- package/src/scripts/lint_governed_writes.ts +20 -0
- package/src/scripts/lint_hidden_unicode.ts +25 -0
- package/src/scripts/lint_hook_concern_budget.ts +39 -6
- package/src/scripts/lint_hook_manifest.ts +22 -0
- package/src/scripts/lint_instruction_smuggling.ts +23 -0
- package/src/scripts/lint_knowledge_scale.ts +32 -2
- package/src/scripts/lint_legal_pack.ts +31 -0
- package/src/scripts/lint_marketplace.ts +26 -0
- package/src/scripts/lint_marketplace_install_completeness.ts +23 -0
- package/src/scripts/lint_mcp_config_security.ts +24 -0
- package/src/scripts/lint_mcp_registry_manifest.ts +22 -1
- package/src/scripts/lint_memory_tripwire.ts +35 -0
- package/src/scripts/lint_missions.ts +23 -7
- package/src/scripts/lint_model_tier_coverage.ts +18 -0
- package/src/scripts/lint_namespace_collisions.ts +19 -0
- package/src/scripts/lint_no_new_atomic_commands.ts +33 -0
- package/src/scripts/lint_no_python_twin_rationale.ts +18 -0
- package/src/scripts/lint_one_off_age.ts +38 -2
- package/src/scripts/lint_orchestration_dsl.ts +23 -3
- package/src/scripts/lint_orchestrator_auto_detect.ts +19 -12
- package/src/scripts/lint_originality.ts +22 -4
- package/src/scripts/lint_originality_shingles.ts +28 -3
- package/src/scripts/lint_output_slop.ts +19 -0
- package/src/scripts/lint_override_kernel_guard.ts +30 -0
- package/src/scripts/lint_pack_first_win.ts +23 -0
- package/src/scripts/lint_pack_risk_class.ts +19 -3
- package/src/scripts/lint_persistence.ts +19 -0
- package/src/scripts/lint_persona_governance.ts +18 -3
- package/src/scripts/lint_positioning.ts +23 -0
- package/src/scripts/lint_pre_migration_refs.ts +24 -0
- package/src/scripts/lint_profile_overlay_set_only.ts +24 -0
- package/src/scripts/lint_provenance.ts +21 -0
- package/src/scripts/lint_provenance_vocabulary.ts +24 -1
- package/src/scripts/lint_readme_jargon.ts +21 -3
- package/src/scripts/lint_readme_serial_comma.ts +21 -3
- package/src/scripts/lint_readme_size.ts +21 -3
- package/src/scripts/lint_regression.ts +28 -0
- package/src/scripts/lint_roadmap_blockers.ts +43 -0
- package/src/scripts/lint_roadmap_ci_steps.ts +45 -0
- package/src/scripts/lint_roadmap_complexity.ts +47 -0
- package/src/scripts/lint_roadmap_later_disposition.ts +23 -0
- package/src/scripts/lint_rule_interactions.ts +16 -2
- package/src/scripts/lint_rule_tiers.ts +19 -6
- package/src/scripts/lint_showcase_sessions.ts +19 -3
- package/src/scripts/lint_skill_descriptions.ts +19 -0
- package/src/scripts/lint_skill_frontmatter_safety.ts +24 -0
- package/src/scripts/lint_skill_originality.ts +18 -3
- package/src/scripts/lint_skill_scripts_readonly.ts +20 -0
- package/src/scripts/lint_skill_tools.ts +84 -145
- package/src/scripts/lint_spawn_payload.ts +27 -1
- package/src/scripts/lint_store_boundary.ts +19 -0
- package/src/scripts/lint_subagent_determinism.ts +23 -1
- package/src/scripts/lint_supported_tools_matrix.ts +18 -0
- package/src/scripts/lint_surface_matrix.ts +19 -3
- package/src/scripts/lint_ticket_buildable.ts +23 -0
- package/src/scripts/lint_topics_yaml.ts +19 -12
- package/src/scripts/lint_trigger_precision.ts +27 -0
- package/src/scripts/lint_trust_coherence.ts +21 -0
- package/src/scripts/lint_ui_stack_bundles.ts +17 -3
- package/src/scripts/lint_value_dashboard.ts +19 -3
- package/src/scripts/lint_versioned_cache.ts +19 -1
- package/src/scripts/lint_workflow_security.ts +22 -14
- package/src/scripts/pr_url_reminder_hook.ts +1 -1
- package/src/scripts/skill_collision_clusters.ts +37 -3
- package/src/scripts/skill_discovery.ts +20 -0
- package/src/scripts/skill_eval_coverage.ts +22 -0
- package/src/scripts/skill_overlap.ts +32 -6
- package/src/scripts/skill_preview.ts +31 -0
- package/src/scripts/skill_trigger_eval.ts +18 -0
- package/src/scripts/skill_usage_collect.ts +21 -0
- package/src/scripts/skill_usage_report.ts +21 -0
- package/src/scripts/sweep_dead_scan_roots.ts +11 -3
- package/src/scripts/verify_physical_move.ts +24 -0
- package/src/scripts/check_bite_sized_granularity.ts +0 -116
- package/src/scripts/lint_workspace_boundary.ts +0 -267
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* 1 drift, 1 no-commands-dir via stderr), stdout/stderr split,
|
|
9
9
|
* byte-identical messages, same canonical-count derivation
|
|
10
10
|
* (iter_commands union), same per-file pattern checks in the same order.
|
|
11
|
-
* No behaviour changes
|
|
11
|
+
* No behaviour changes — except that the no-commands-dir stderr line now comes
|
|
12
|
+
* from `_lib/scan_scope` and names the empty roots; the exit code is unchanged.
|
|
12
13
|
*
|
|
13
14
|
* Sources canonical counts from the command frontmatter and fails when any
|
|
14
15
|
* documented number drifts (README hero badge, getting-started browse line;
|
|
@@ -20,6 +21,7 @@ import * as path from 'node:path';
|
|
|
20
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
21
22
|
|
|
22
23
|
import { iter_commands } from './_lib/agent_src.js';
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
23
25
|
|
|
24
26
|
const _HERE = fileURLToPath(import.meta.url);
|
|
25
27
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -62,9 +64,23 @@ function _command_files(): string[] {
|
|
|
62
64
|
|
|
63
65
|
function canonical_counts(): [number, number, number] {
|
|
64
66
|
const files = _command_files();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
try {
|
|
68
|
+
assertScanned({
|
|
69
|
+
gate: 'check_command_count_messaging',
|
|
70
|
+
scanned: files.length,
|
|
71
|
+
units: 'command file(s)',
|
|
72
|
+
roots: ['src/domains/*/**/command.md', 'src/agent-src/commands'],
|
|
73
|
+
});
|
|
74
|
+
} catch (exc) {
|
|
75
|
+
if (exc instanceof DeadScopeError) {
|
|
76
|
+
// Exit 1 stays — the documented "no commands dir" code, and the
|
|
77
|
+
// Taskfile fails on any non-zero, so this was never a silent green.
|
|
78
|
+
// The assertion replaces a bare "no commands/ directory found" with
|
|
79
|
+
// a message that names the roots that came back empty.
|
|
80
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
throw exc;
|
|
68
84
|
}
|
|
69
85
|
let total = 0;
|
|
70
86
|
let shims = 0;
|
|
@@ -20,6 +20,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
20
20
|
import { parse as parseYaml } from 'yaml';
|
|
21
21
|
|
|
22
22
|
import { REPO, pointer_unresolved } from './check_claims.js';
|
|
23
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
23
24
|
|
|
24
25
|
const _HERE = fileURLToPath(import.meta.url);
|
|
25
26
|
export const DATA_REL = 'docs/comparison.yaml';
|
|
@@ -71,6 +72,23 @@ export function findErrors(rows: Row[]): string[] {
|
|
|
71
72
|
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
72
73
|
const quiet = argv.includes('--quiet');
|
|
73
74
|
const rows = loadRows();
|
|
75
|
+
// `loadRows` returns [] for a missing or rows-less `docs/comparison.yaml`,
|
|
76
|
+
// so a renamed data file made this gate certify "all our_evidence pointers
|
|
77
|
+
// resolve" over zero rows — the falsifiability lock passing vacuously.
|
|
78
|
+
try {
|
|
79
|
+
assertScanned({
|
|
80
|
+
gate: 'check_comparison',
|
|
81
|
+
scanned: rows.length,
|
|
82
|
+
units: 'comparison row(s)',
|
|
83
|
+
roots: [DATA_REL],
|
|
84
|
+
});
|
|
85
|
+
} catch (exc) {
|
|
86
|
+
if (exc instanceof DeadScopeError) {
|
|
87
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
throw exc;
|
|
91
|
+
}
|
|
74
92
|
const errs = findErrors(rows);
|
|
75
93
|
if (errs.length > 0) {
|
|
76
94
|
process.stdout.write('❌ comparison-honesty:\n');
|
|
@@ -47,6 +47,8 @@ import process from 'node:process';
|
|
|
47
47
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
48
48
|
import type * as YamlModule from 'yaml';
|
|
49
49
|
|
|
50
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
51
|
+
|
|
50
52
|
// ESM-standard `require` shim. The bare `require` global is present when this
|
|
51
53
|
// module is *imported* (tsx injects it) but absent when the module is the
|
|
52
54
|
// directly-executed CLI entry, so resolve it explicitly here to preserve the
|
|
@@ -359,7 +361,26 @@ function main(): number {
|
|
|
359
361
|
}
|
|
360
362
|
const viols: Violation[] = [];
|
|
361
363
|
const audited: Array<[string, IgnoreEntry]> = [];
|
|
362
|
-
|
|
364
|
+
const ruleFiles = _globMdSorted(RULES_DIR);
|
|
365
|
+
// The guard above only proves the directory node exists; an empty one still
|
|
366
|
+
// renders "clean (0 rules, 0 ignore(s) audited)". Exit 3 is the code that
|
|
367
|
+
// guard already uses for "there was nothing to check" — 1 means violations
|
|
368
|
+
// were found, which is a different claim.
|
|
369
|
+
try {
|
|
370
|
+
assertScanned({
|
|
371
|
+
gate: 'check_condensed_paths',
|
|
372
|
+
scanned: ruleFiles.length,
|
|
373
|
+
units: 'rule file(s)',
|
|
374
|
+
roots: ['dist/agent-src/rules'],
|
|
375
|
+
});
|
|
376
|
+
} catch (exc) {
|
|
377
|
+
if (exc instanceof DeadScopeError) {
|
|
378
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
379
|
+
return 3;
|
|
380
|
+
}
|
|
381
|
+
throw exc;
|
|
382
|
+
}
|
|
383
|
+
for (const ruleFile of ruleFiles) {
|
|
363
384
|
const text = fs.readFileSync(ruleFile, 'utf-8');
|
|
364
385
|
const [fm, body] = _split_frontmatter(text);
|
|
365
386
|
const ignores: IgnoreEntry[] = fm !== null ? _parse_ignores(fm) : [];
|
|
@@ -383,7 +404,7 @@ function main(): number {
|
|
|
383
404
|
process.stdout.write(`\n${viols.length} violation(s) in dist/agent-src/rules/\n`);
|
|
384
405
|
return 1;
|
|
385
406
|
}
|
|
386
|
-
const ruleCount =
|
|
407
|
+
const ruleCount = ruleFiles.length;
|
|
387
408
|
if (!QUIET) {
|
|
388
409
|
process.stdout.write(
|
|
389
410
|
`✅ condensed-path check clean (${ruleCount} rules, ${audited.length} ignore(s) audited)\n`,
|
|
@@ -32,6 +32,7 @@ import * as path from 'node:path';
|
|
|
32
32
|
import { parse as parseYaml } from 'yaml';
|
|
33
33
|
|
|
34
34
|
import { excludedRuleBasenames, ruleScopeFromSettings } from '../install/rule_scope.js';
|
|
35
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
35
36
|
|
|
36
37
|
export const RULES_DIR = 'dist/agent-src/rules';
|
|
37
38
|
export const TEMPLATE = 'src/config/agent-settings.template.yml';
|
|
@@ -112,8 +113,26 @@ async function main(): Promise<number> {
|
|
|
112
113
|
let legacyTok = 0;
|
|
113
114
|
let scopedRules = 0;
|
|
114
115
|
let scopedTok = 0;
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
// The shipped rule tree is the corpus the before/after evidence is measured
|
|
117
|
+
// over. An emptied `dist/agent-src/rules` keeps every verdict vacuously true
|
|
118
|
+
// — nothing excluded, nothing dropped — so `pass` stays true and the flip
|
|
119
|
+
// reports as "verified" against a zero-rule baseline (with a NaN delta).
|
|
120
|
+
const ruleFiles = fs.readdirSync(RULES_DIR).sort().filter((n) => n.endsWith('.md'));
|
|
121
|
+
try {
|
|
122
|
+
assertScanned({
|
|
123
|
+
gate: 'check_consumer_scope_flip',
|
|
124
|
+
scanned: ruleFiles.length,
|
|
125
|
+
units: 'projected rule file(s)',
|
|
126
|
+
roots: [RULES_DIR],
|
|
127
|
+
});
|
|
128
|
+
} catch (exc) {
|
|
129
|
+
if (exc instanceof DeadScopeError) {
|
|
130
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
throw exc;
|
|
134
|
+
}
|
|
135
|
+
for (const name of ruleFiles) {
|
|
117
136
|
const body = fs.readFileSync(path.join(RULES_DIR, name), 'utf-8');
|
|
118
137
|
const tok = gpt_tokens(body).tokens;
|
|
119
138
|
// legacy-all baseline mirrors the 2026-07-08 report: compat exclusion applies on both arms
|
|
@@ -54,6 +54,8 @@ import * as fs from 'node:fs';
|
|
|
54
54
|
import * as path from 'node:path';
|
|
55
55
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
56
56
|
|
|
57
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
58
|
+
|
|
57
59
|
const QUIET = process.argv.includes('--quiet');
|
|
58
60
|
|
|
59
61
|
// Agent-facing surfaces where council config must resolve to `.ai-council.yml`.
|
|
@@ -318,6 +320,24 @@ function find_path_violations(root: string): string[] {
|
|
|
318
320
|
|
|
319
321
|
function main(): number {
|
|
320
322
|
const root = process.cwd();
|
|
323
|
+
// PATH_CHECK_GLOBS is the superset of both passes, so its resolution is the
|
|
324
|
+
// whole corpus either check can see. Run from the wrong cwd — or after the
|
|
325
|
+
// council surfaces move — every glob resolves to nothing and the gate
|
|
326
|
+
// reports "clean". Exit 1 is its only failure code.
|
|
327
|
+
try {
|
|
328
|
+
assertScanned({
|
|
329
|
+
gate: 'check_council_config_location',
|
|
330
|
+
scanned: [...iter_files(root, PATH_CHECK_GLOBS)].length,
|
|
331
|
+
units: 'council-surface file(s)',
|
|
332
|
+
roots: PATH_CHECK_GLOBS,
|
|
333
|
+
});
|
|
334
|
+
} catch (exc) {
|
|
335
|
+
if (exc instanceof DeadScopeError) {
|
|
336
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
throw exc;
|
|
340
|
+
}
|
|
321
341
|
const findings = [...find_violations(root), ...find_path_violations(root)];
|
|
322
342
|
if (findings.length) {
|
|
323
343
|
process.stdout.write('❌ Council config-location violations:\n\n');
|
|
@@ -19,6 +19,8 @@ import * as fs from 'node:fs';
|
|
|
19
19
|
import * as path from 'node:path';
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
21
21
|
|
|
22
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
23
|
+
|
|
22
24
|
const _HERE = fileURLToPath(import.meta.url);
|
|
23
25
|
|
|
24
26
|
const AGENTS_ROOT = 'agents';
|
|
@@ -88,6 +90,16 @@ function _isFile(p: string): boolean {
|
|
|
88
90
|
|
|
89
91
|
function find_violations(root: string): string[] {
|
|
90
92
|
const findings: string[] = [];
|
|
93
|
+
// `agents/` is resolved relative to the cwd, so a run from anywhere but the
|
|
94
|
+
// repo root walks nothing and reports a clean layout. Thrown, not returned —
|
|
95
|
+
// `findings` is the violation channel, and a dead scope is not a violation.
|
|
96
|
+
const descendants = _isDir(root) ? _rglobAllSorted(root) : [];
|
|
97
|
+
assertScanned({
|
|
98
|
+
gate: 'check_council_layout',
|
|
99
|
+
scanned: descendants.length,
|
|
100
|
+
units: 'agents/ entries',
|
|
101
|
+
roots: [AGENTS_ROOT],
|
|
102
|
+
});
|
|
91
103
|
if (!_isDir(root)) {
|
|
92
104
|
return findings;
|
|
93
105
|
}
|
|
@@ -110,7 +122,7 @@ function find_violations(root: string): string[] {
|
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
// 2. Council artefacts in non-canonical subdirectories.
|
|
113
|
-
for (const p of
|
|
125
|
+
for (const p of descendants) {
|
|
114
126
|
if (!_isFile(p) || !is_council_artefact(path.basename(p))) {
|
|
115
127
|
continue;
|
|
116
128
|
}
|
|
@@ -144,7 +156,15 @@ function _posix(p: string): string {
|
|
|
144
156
|
|
|
145
157
|
function main(argv: readonly string[]): number {
|
|
146
158
|
const quiet = argv.includes('--quiet');
|
|
147
|
-
|
|
159
|
+
let findings: string[];
|
|
160
|
+
try {
|
|
161
|
+
findings = find_violations(AGENTS_ROOT);
|
|
162
|
+
} catch (exc) {
|
|
163
|
+
if (!(exc instanceof DeadScopeError)) throw exc;
|
|
164
|
+
// Exit 1 is this gate's only failure code.
|
|
165
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
148
168
|
if (findings.length > 0) {
|
|
149
169
|
process.stdout.write('❌ Council layout violations:\n\n');
|
|
150
170
|
for (const f of findings) {
|
|
@@ -40,6 +40,7 @@ import * as path from 'node:path';
|
|
|
40
40
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
41
41
|
|
|
42
42
|
import { artefact_roots, strip_source_prefix } from './_lib/agent_src.js';
|
|
43
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
43
44
|
|
|
44
45
|
const QUIET = process.argv.includes('--quiet');
|
|
45
46
|
|
|
@@ -284,7 +285,14 @@ function* _iter_files(roots: readonly string[]): Generator<string> {
|
|
|
284
285
|
|
|
285
286
|
function main(): number {
|
|
286
287
|
const violations: Array<[string, number, string]> = [];
|
|
287
|
-
|
|
288
|
+
const roots = _scan_roots();
|
|
289
|
+
// Counted before the allowlist filter — the question is what the walk
|
|
290
|
+
// reached, not what survived it. Every scan root is skipped when absent, so
|
|
291
|
+
// a relocated `docs/` or `agents/` leaves the linter reporting "no forbidden
|
|
292
|
+
// references" over an empty corpus. Exit 1 is its only failure code.
|
|
293
|
+
let scanned = 0;
|
|
294
|
+
for (const p of _iter_files(roots)) {
|
|
295
|
+
scanned += 1;
|
|
288
296
|
const rel = p.split(path.sep).join('/');
|
|
289
297
|
if (_is_allowlisted(rel)) {
|
|
290
298
|
continue;
|
|
@@ -293,6 +301,20 @@ function main(): number {
|
|
|
293
301
|
violations.push([p, ln, ref]);
|
|
294
302
|
}
|
|
295
303
|
}
|
|
304
|
+
try {
|
|
305
|
+
assertScanned({
|
|
306
|
+
gate: 'check_council_references',
|
|
307
|
+
scanned,
|
|
308
|
+
units: 'durable artefact file(s)',
|
|
309
|
+
roots,
|
|
310
|
+
});
|
|
311
|
+
} catch (exc) {
|
|
312
|
+
if (exc instanceof DeadScopeError) {
|
|
313
|
+
process.stdout.write(`❌ ${exc.message}\n`);
|
|
314
|
+
return 1;
|
|
315
|
+
}
|
|
316
|
+
throw exc;
|
|
317
|
+
}
|
|
296
318
|
|
|
297
319
|
if (violations.length === 0) {
|
|
298
320
|
if (!QUIET) {
|
|
@@ -37,6 +37,8 @@ import * as fs from 'node:fs';
|
|
|
37
37
|
import * as path from 'node:path';
|
|
38
38
|
import { fileURLToPath } from 'node:url';
|
|
39
39
|
|
|
40
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
41
|
+
|
|
40
42
|
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -137,9 +139,23 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
137
139
|
process.stderr.write(`❌ dependency floors: cannot read ${manifestPath}: ${String(err)}\n`);
|
|
138
140
|
return 2;
|
|
139
141
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
// Replaces the ad-hoc "gate would pass vacuously" guard with the shared
|
|
143
|
+
// assertion — same condition, same exit code (2 = the manifest yielded no
|
|
144
|
+
// corpus, vs 1 = a floor violates the rule), now counted on the unfiltered
|
|
145
|
+
// read rather than on what `evaluate` judges.
|
|
146
|
+
try {
|
|
147
|
+
assertScanned({
|
|
148
|
+
gate: 'check_dependency_floors',
|
|
149
|
+
scanned: Object.keys(dependencies).length,
|
|
150
|
+
units: 'runtime dependency floor(s)',
|
|
151
|
+
roots: ['package.json#dependencies'],
|
|
152
|
+
});
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (err instanceof DeadScopeError) {
|
|
155
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
156
|
+
return 2;
|
|
157
|
+
}
|
|
158
|
+
throw err;
|
|
143
159
|
}
|
|
144
160
|
const errors = evaluate(dependencies);
|
|
145
161
|
if (asJson) {
|
|
@@ -19,6 +19,8 @@ import * as path from 'node:path';
|
|
|
19
19
|
import * as fs from 'node:fs';
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
21
21
|
|
|
22
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
23
|
+
|
|
22
24
|
const _HERE = fileURLToPath(import.meta.url);
|
|
23
25
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
24
26
|
const SCANNER = path.join(ROOT, 'src', 'scripts', 'build_discovery_manifest.ts');
|
|
@@ -76,6 +78,30 @@ class ExitError extends Error {}
|
|
|
76
78
|
function main(): number {
|
|
77
79
|
const a = _normalise(_run());
|
|
78
80
|
const b = _normalise(_run());
|
|
81
|
+
|
|
82
|
+
// Determinism over an EMPTY manifest is vacuously true: if the scanner's
|
|
83
|
+
// artefact roots moved, both runs emit zero artefacts, the two payloads
|
|
84
|
+
// compare equal, and this gate reports "OK: deterministic" having compared
|
|
85
|
+
// nothing. Assert the corpus the comparison ran over, not the comparison.
|
|
86
|
+
// Deletion test: wiping the artefact tree would give 0 — which is blindness
|
|
87
|
+
// here, never success — so no `allowEmpty`. Exit 1 is this pinned CLI's
|
|
88
|
+
// only failure code (the scanner-failed path also exits 1, via ExitError).
|
|
89
|
+
const artefacts = a['artefacts'];
|
|
90
|
+
try {
|
|
91
|
+
assertScanned({
|
|
92
|
+
gate: 'check_discovery_determinism',
|
|
93
|
+
scanned: Array.isArray(artefacts) ? artefacts.length : 0,
|
|
94
|
+
units: 'manifest artefact(s)',
|
|
95
|
+
roots: ['src/scripts/build_discovery_manifest.ts → manifest.artefacts'],
|
|
96
|
+
});
|
|
97
|
+
} catch (exc) {
|
|
98
|
+
if (exc instanceof DeadScopeError) {
|
|
99
|
+
process.stderr.write(`${exc.message}\n`);
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
throw exc;
|
|
103
|
+
}
|
|
104
|
+
|
|
79
105
|
const sa = _dumps(a);
|
|
80
106
|
const sb = _dumps(b);
|
|
81
107
|
if (sa !== sb) {
|
|
@@ -37,6 +37,8 @@ import * as fs from 'node:fs';
|
|
|
37
37
|
import * as path from 'node:path';
|
|
38
38
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
39
39
|
|
|
40
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
41
|
+
|
|
40
42
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
41
43
|
const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
42
44
|
const RULES_DIR = path.join(REPO_ROOT, 'src', 'rules');
|
|
@@ -478,6 +480,24 @@ function main(argv: string[]): number {
|
|
|
478
480
|
}
|
|
479
481
|
|
|
480
482
|
const rows = collect();
|
|
483
|
+
// Count every rule read, not the declared subset: with an empty rules dir
|
|
484
|
+
// the report renders "0/0 rules (0%)" and exits green, and `summarise`
|
|
485
|
+
// special-cases the zero denominator rather than rejecting it. Exit 2 is
|
|
486
|
+
// the code the missing-rules-dir guard above already uses for this class.
|
|
487
|
+
try {
|
|
488
|
+
assertScanned({
|
|
489
|
+
gate: 'check_enforcement_coverage',
|
|
490
|
+
scanned: rows.length,
|
|
491
|
+
units: 'rule file(s)',
|
|
492
|
+
roots: ['src/rules'],
|
|
493
|
+
});
|
|
494
|
+
} catch (exc) {
|
|
495
|
+
if (exc instanceof DeadScopeError) {
|
|
496
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
497
|
+
return 2;
|
|
498
|
+
}
|
|
499
|
+
throw exc;
|
|
500
|
+
}
|
|
481
501
|
const summary = summarise(rows);
|
|
482
502
|
|
|
483
503
|
if (as_json) {
|
|
@@ -40,6 +40,8 @@ import * as fs from 'node:fs';
|
|
|
40
40
|
import * as path from 'node:path';
|
|
41
41
|
import { fileURLToPath } from 'node:url';
|
|
42
42
|
|
|
43
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
44
|
+
|
|
43
45
|
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
44
46
|
const BUDGETS_PATH = path.join(REPO_ROOT, 'src', 'config', 'evaluator-budgets.json');
|
|
45
47
|
|
|
@@ -230,6 +232,27 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
230
232
|
return 2;
|
|
231
233
|
}
|
|
232
234
|
|
|
235
|
+
// The budget document IS the corpus: every finding below is produced by
|
|
236
|
+
// iterating it, so a budgets file that parses to no entries reports
|
|
237
|
+
// "✅ evaluator budgets met (0 metrics)" — green over nothing, the exact
|
|
238
|
+
// shape this harness exists to end. A missing `budgets` key counts as 0 for
|
|
239
|
+
// the same reason. Exit 2 (documented internal error — the gate could not
|
|
240
|
+
// run), never 1, which means a metric actually breached.
|
|
241
|
+
try {
|
|
242
|
+
assertScanned({
|
|
243
|
+
gate: 'check_evaluator_budgets',
|
|
244
|
+
scanned: Object.keys(budgetsDoc.budgets ?? {}).length,
|
|
245
|
+
units: 'budgeted metric(s)',
|
|
246
|
+
roots: ['src/config/evaluator-budgets.json (budgets)'],
|
|
247
|
+
});
|
|
248
|
+
} catch (e) {
|
|
249
|
+
if (e instanceof DeadScopeError) {
|
|
250
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
251
|
+
return 2;
|
|
252
|
+
}
|
|
253
|
+
throw e;
|
|
254
|
+
}
|
|
255
|
+
|
|
233
256
|
// The committed record is optional: a fresh checkout before the first
|
|
234
257
|
// nightly has none, and a missing record must not fake a green drift check.
|
|
235
258
|
const recordPath = argValue(argv, '--recorded') ?? path.join(REPO_ROOT, RECORD_REL);
|
|
@@ -34,6 +34,8 @@ import * as path from 'node:path';
|
|
|
34
34
|
import process from 'node:process';
|
|
35
35
|
import { fileURLToPath } from 'node:url';
|
|
36
36
|
|
|
37
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
38
|
+
|
|
37
39
|
const _HERE = fileURLToPath(import.meta.url);
|
|
38
40
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
39
41
|
|
|
@@ -235,6 +237,33 @@ function main(argv: readonly string[]): number {
|
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
// Scope declaration, not a scope guard. The ledger is written per release by
|
|
241
|
+
// `--ingest`, so "no <release>.json" is the normal state of a release whose
|
|
242
|
+
// self-review reported nothing — the gate's own success line says so. That
|
|
243
|
+
// makes an absent file indistinguishable from a moved ledger dir here, and
|
|
244
|
+
// the honest reading is that the corpus count cannot carry that signal: the
|
|
245
|
+
// durable trigger for an un-ingested finding is `--pr` mode, which compares
|
|
246
|
+
// the ledger against what the self-review actually reported.
|
|
247
|
+
try {
|
|
248
|
+
assertScanned({
|
|
249
|
+
gate: 'check_finding_dispositions',
|
|
250
|
+
scanned: ledger.findings.length,
|
|
251
|
+
units: 'recorded finding(s)',
|
|
252
|
+
roots: [path.relative(REPO_ROOT, ledgerPath)],
|
|
253
|
+
allowEmpty:
|
|
254
|
+
'EMPTY_VALID: zero recorded findings IS the pass state for a release whose '
|
|
255
|
+
+ 'self-review reported none — the ledger file is created on first --ingest, so a '
|
|
256
|
+
+ 'clean release legitimately has no <version>.json. Un-ingested findings are '
|
|
257
|
+
+ 'caught by --pr (unrecorded_findings), never by this count.',
|
|
258
|
+
});
|
|
259
|
+
} catch (exc) {
|
|
260
|
+
if (exc instanceof DeadScopeError) {
|
|
261
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
throw exc;
|
|
265
|
+
}
|
|
266
|
+
|
|
238
267
|
const problems = missing_dispositions(ledger.findings);
|
|
239
268
|
if (pr) {
|
|
240
269
|
const reported = parse_comment_findings(_gh_comment_bodies(pr));
|
|
@@ -51,6 +51,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
51
51
|
import * as yaml from 'js-yaml';
|
|
52
52
|
import { checkRatchet } from './_lib/gate_baseline.js';
|
|
53
53
|
import { runCountedProbe } from './_lib/counted_probe.js';
|
|
54
|
+
import { listGateScripts } from './_lib/gate_population.js';
|
|
55
|
+
import { assertScanned } from './_lib/scan_scope.js';
|
|
54
56
|
|
|
55
57
|
const _HERE = fileURLToPath(import.meta.url);
|
|
56
58
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -260,6 +262,18 @@ export function main(argv: readonly string[]): number {
|
|
|
260
262
|
let specs: GateSpec[];
|
|
261
263
|
try {
|
|
262
264
|
specs = load_manifest();
|
|
265
|
+
// This guard's own scan scope is the manifest. It already refused an empty
|
|
266
|
+
// gate list ("a coverage guard over an empty set is vacuous"), but as a
|
|
267
|
+
// bespoke throw — invisible to the very population test below, which is how
|
|
268
|
+
// the meta-gate ended up outside its own definition of hardened. Routing the
|
|
269
|
+
// same condition through the shared assertion is the honest fix: no new
|
|
270
|
+
// behaviour, one shape for "this gate read nothing".
|
|
271
|
+
assertScanned({
|
|
272
|
+
gate: 'check_gate_coverage',
|
|
273
|
+
scanned: specs.length,
|
|
274
|
+
units: 'manifest gate(s)',
|
|
275
|
+
roots: [path.relative(REPO_ROOT, MANIFEST)],
|
|
276
|
+
});
|
|
263
277
|
} catch (e) {
|
|
264
278
|
process.stderr.write(`❌ gate-coverage manifest: ${(e as Error).message}\n`);
|
|
265
279
|
return 2;
|
|
@@ -358,33 +372,66 @@ export function report_hardening_ratchet(): number {
|
|
|
358
372
|
}
|
|
359
373
|
|
|
360
374
|
/**
|
|
361
|
-
*
|
|
362
|
-
* population still able to report success over an empty corpus.
|
|
375
|
+
* Manifest ids whose `scanned:` line is actually ENFORCED against a floor.
|
|
363
376
|
*
|
|
364
|
-
* A
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
377
|
+
* A `pending` entry, or an enforced one with `min_scanned: 0`, reads the line
|
|
378
|
+
* without being able to fail on it — so it cannot make a gate hardened either.
|
|
379
|
+
* Read best-effort: an unreadable manifest yields an empty set, which pushes
|
|
380
|
+
* every emit-only gate back into the unhardened count. That direction is
|
|
381
|
+
* deliberate — a coverage guard that cannot read its own manifest should
|
|
382
|
+
* over-report exposure, never under-report it.
|
|
368
383
|
*/
|
|
369
|
-
export function
|
|
370
|
-
let entries: string[];
|
|
384
|
+
export function enforced_manifest_ids(file = MANIFEST): Set<string> {
|
|
371
385
|
try {
|
|
372
|
-
|
|
386
|
+
return new Set(
|
|
387
|
+
load_manifest(file)
|
|
388
|
+
.filter((s) => s.status === 'enforced' && s.min_scanned >= 1)
|
|
389
|
+
.map((s) => s.id),
|
|
390
|
+
);
|
|
373
391
|
} catch {
|
|
374
|
-
return
|
|
392
|
+
return new Set<string>();
|
|
375
393
|
}
|
|
376
|
-
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Gate scripts that neither assert their scan scope nor publish an ENFORCED
|
|
398
|
+
* count — the population still able to report success over an empty corpus.
|
|
399
|
+
*
|
|
400
|
+
* ```
|
|
401
|
+
* hardened ⇔ routes through _lib/scan_scope
|
|
402
|
+
* ∨ (emits `scanned:` ∧ registered enforced in gate-coverage.yml with a floor)
|
|
403
|
+
* ```
|
|
404
|
+
*
|
|
405
|
+
* The second clause used to be a bare "emits `scanned:`". Measured 2026-08-04
|
|
406
|
+
* while chartering the conversion of the remaining 189: a printed count is only
|
|
407
|
+
* a *guard* for the gates this file actually runs against a floor. For an
|
|
408
|
+
* unregistered gate the line is decoration — it can print `scanned: 0` out of a
|
|
409
|
+
* deleted root and still exit 0, with nobody reading it. That made the cheapest
|
|
410
|
+
* route to a green ratchet (add one write per gate) also the route that changes
|
|
411
|
+
* nothing, while "pad the manifest" is an explicit non-goal of the conversion
|
|
412
|
+
* roadmap. Requiring registration closes the loop: to harden by emitting you
|
|
413
|
+
* must accept a floor, and everything else asserts.
|
|
414
|
+
*
|
|
415
|
+
* The tightening cost the existing population nothing — all 14 emit-only gates
|
|
416
|
+
* were already registered enforced — which is why it is a drift repair against
|
|
417
|
+
* this guard's own charter ("asserts its scan scope … or carries a justified
|
|
418
|
+
* `allowEmpty`") rather than a moved goalpost.
|
|
419
|
+
*/
|
|
420
|
+
export function list_unhardened_gates(
|
|
421
|
+
dir = path.join(REPO_ROOT, 'src/scripts'),
|
|
422
|
+
registered: ReadonlySet<string> = enforced_manifest_ids(),
|
|
423
|
+
): string[] {
|
|
377
424
|
const out: string[] = [];
|
|
378
|
-
for (const
|
|
425
|
+
for (const id of listGateScripts(dir, (d) => fs.readdirSync(d))) {
|
|
379
426
|
let src: string;
|
|
380
427
|
try {
|
|
381
|
-
src = fs.readFileSync(path.join(dir,
|
|
428
|
+
src = fs.readFileSync(path.join(dir, `${id}.ts`), 'utf8');
|
|
382
429
|
} catch {
|
|
383
430
|
continue;
|
|
384
431
|
}
|
|
385
|
-
const asserts = /assertScanned\(|assertWatchlistResolves\(/.test(src);
|
|
432
|
+
const asserts = /assertScanned\(|assertWatchlistResolves\(|reportScanned\(/.test(src);
|
|
386
433
|
const emits = /(?:process\.(?:stdout|stderr)\.write|lines\.push)\(\s*`scanned: /.test(src);
|
|
387
|
-
if (!asserts && !emits) out.push(
|
|
434
|
+
if (!asserts && !(emits && registered.has(id))) out.push(id);
|
|
388
435
|
}
|
|
389
436
|
return out.sort();
|
|
390
437
|
}
|
|
@@ -637,13 +684,7 @@ export function render_ledger(
|
|
|
637
684
|
|
|
638
685
|
/** Count of gate-shaped scripts, for the honest denominator in the ledger. */
|
|
639
686
|
export function count_gate_scripts(dir = path.join(REPO_ROOT, 'src/scripts')): number {
|
|
640
|
-
|
|
641
|
-
return fs
|
|
642
|
-
.readdirSync(dir)
|
|
643
|
-
.filter((f) => /^(lint|check|audit)_.*\.ts$/.test(f) && !f.endsWith('.d.ts')).length;
|
|
644
|
-
} catch {
|
|
645
|
-
return 0;
|
|
646
|
-
}
|
|
687
|
+
return listGateScripts(dir, (d) => fs.readdirSync(d)).length;
|
|
647
688
|
}
|
|
648
689
|
|
|
649
690
|
function run_canary_mode(specs: readonly GateSpec[], argv: readonly string[]): number {
|