@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
|
@@ -25,6 +25,7 @@ import * as path from 'node:path';
|
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
26
|
|
|
27
27
|
import { artefact_roots } from './_lib/agent_src.js';
|
|
28
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
28
29
|
|
|
29
30
|
export const MIN_LENGTH = 150;
|
|
30
31
|
// Mirrors scripts/skill_linter.py `description_too_long` threshold.
|
|
@@ -386,6 +387,24 @@ export function main(argv: string[] | null = null): number {
|
|
|
386
387
|
for (const r of roots) {
|
|
387
388
|
findings.push(...collect_findings(r));
|
|
388
389
|
}
|
|
390
|
+
// The roots check above only proves a `skills/` DIRECTORY exists; roots that
|
|
391
|
+
// hold no SKILL.md still render "Audited 0 skills … ✅ All descriptions look
|
|
392
|
+
// reasonable." Exit 2 is this gate's only failure code, and it already
|
|
393
|
+
// covers the sibling "no skills/ directories found" case.
|
|
394
|
+
try {
|
|
395
|
+
assertScanned({
|
|
396
|
+
gate: 'audit_skill_descriptions',
|
|
397
|
+
scanned: findings.length,
|
|
398
|
+
units: 'SKILL.md file(s)',
|
|
399
|
+
roots,
|
|
400
|
+
});
|
|
401
|
+
} catch (exc) {
|
|
402
|
+
if (exc instanceof DeadScopeError) {
|
|
403
|
+
process.stderr.write(`error: ${exc.message}\n`);
|
|
404
|
+
return 2;
|
|
405
|
+
}
|
|
406
|
+
throw exc;
|
|
407
|
+
}
|
|
389
408
|
if (args.json) {
|
|
390
409
|
process.stdout.write(pyJsonDumpsIndent2(findings.map(_asdict)) + '\n');
|
|
391
410
|
} else {
|
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Platform-agnostic hook for the `verify-before-complete` rule.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Named `before_complete_hook`, not `verify_before_complete_hook`: `verify_` is
|
|
6
|
+
* one of the gate-shaped prefixes in `_lib/gate_population.ts`, so the old name
|
|
7
|
+
* put an observability hook into the gate population, where the scan-scope
|
|
8
|
+
* ratchet counted it as a gate that cannot assert a scan scope (it scans no
|
|
9
|
+
* corpus — its only inputs are its stdin envelope and the state file it writes).
|
|
10
|
+
* AI council 2026-08-05 rejected the alternative of excluding `_hook.ts` in the
|
|
11
|
+
* population filter as population-shrinking: `.d.ts` / `.test.ts` are excluded
|
|
12
|
+
* because the language and the test runner make them structurally
|
|
13
|
+
* non-executable, whereas "a hook never blocks" is an operational property you
|
|
14
|
+
* can only learn by reading `hook_manifest.yaml` — and manifest-driven
|
|
15
|
+
* classification was already rejected once. Renaming keeps classification
|
|
16
|
+
* structural and needs no exclusion rule.
|
|
17
|
+
*
|
|
18
|
+
* Ported from the retired Python `src/scripts/verify_before_complete_hook.py`
|
|
19
|
+
* (ADR-200 — Python→TS migration, Phase 6 / hooks). Public API mirrors the
|
|
20
|
+
* Python module exactly (snake_case kept deliberately — fidelity over TS idiom).
|
|
8
21
|
*
|
|
9
22
|
* Records observable evidence that a verification command (tests, quality
|
|
10
23
|
* tools, build) ran. The rule body cites the resulting state file as the
|
|
@@ -35,6 +35,8 @@ import * as fs from 'node:fs';
|
|
|
35
35
|
import * as path from 'node:path';
|
|
36
36
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
37
37
|
|
|
38
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
39
|
+
|
|
38
40
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
39
41
|
const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
40
42
|
const ADR_DIR = path.join(REPO_ROOT, 'docs', 'decisions');
|
|
@@ -149,6 +151,27 @@ export function check(dir: string = ADR_DIR): AdrFinding[] {
|
|
|
149
151
|
function main(argv: string[]): number {
|
|
150
152
|
const as_json = argv.includes('--json');
|
|
151
153
|
const findings = check();
|
|
154
|
+
const total = fs.existsSync(ADR_DIR)
|
|
155
|
+
? fs.readdirSync(ADR_DIR).filter((f) => /^ADR-.*\.md$/.test(f)).length
|
|
156
|
+
: 0;
|
|
157
|
+
|
|
158
|
+
// `check()` returns findings, never a count, so a moved `docs/decisions/`
|
|
159
|
+
// yields an empty list — reported as "0 ADR(s) · 0 error(s)" and green.
|
|
160
|
+
// Exit 1 is the violation code; 2 stays reserved for usage/env errors.
|
|
161
|
+
try {
|
|
162
|
+
assertScanned({
|
|
163
|
+
gate: 'check_adr_frontmatter',
|
|
164
|
+
scanned: total,
|
|
165
|
+
units: 'ADR file(s)',
|
|
166
|
+
roots: ['docs/decisions'],
|
|
167
|
+
});
|
|
168
|
+
} catch (exc) {
|
|
169
|
+
if (exc instanceof DeadScopeError) {
|
|
170
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
throw exc;
|
|
174
|
+
}
|
|
152
175
|
|
|
153
176
|
if (as_json) {
|
|
154
177
|
process.stdout.write(JSON.stringify({ findings }, null, 2) + '\n');
|
|
@@ -157,9 +180,6 @@ function main(argv: string[]): number {
|
|
|
157
180
|
|
|
158
181
|
const errors = findings.filter((f) => f.level === 'error');
|
|
159
182
|
const warns = findings.filter((f) => f.level === 'warn');
|
|
160
|
-
const total = fs.existsSync(ADR_DIR)
|
|
161
|
-
? fs.readdirSync(ADR_DIR).filter((f) => /^ADR-.*\.md$/.test(f)).length
|
|
162
|
-
: 0;
|
|
163
183
|
|
|
164
184
|
process.stdout.write(
|
|
165
185
|
`ADR frontmatter: ${total} ADR(s) · ${errors.length} error(s) · ` +
|
|
@@ -29,6 +29,8 @@ import process from 'node:process';
|
|
|
29
29
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
30
30
|
import YAML from 'yaml';
|
|
31
31
|
|
|
32
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
33
|
+
|
|
32
34
|
// src/scripts/check_always_budget.ts → parents[2] is the repo root
|
|
33
35
|
// (mirrors the Python module's Path(__file__).resolve().parents[2]).
|
|
34
36
|
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
@@ -552,6 +554,28 @@ function main(): number {
|
|
|
552
554
|
return 3;
|
|
553
555
|
}
|
|
554
556
|
|
|
557
|
+
// The scanned unit is every projected rule, not the `type: always` subset the
|
|
558
|
+
// budget is measured over: `isDir` above catches a MISSING projection, but a
|
|
559
|
+
// present-and-empty one (a sync that produced no rules) reaches here, and
|
|
560
|
+
// "no always-rules" cannot distinguish that from a tree whose rules are all
|
|
561
|
+
// `type: auto`. Exit 3 stays — this file's documented internal-error code,
|
|
562
|
+
// already used for the empty case, and the Taskfile fails on any non-zero, so
|
|
563
|
+
// this was never a silent green.
|
|
564
|
+
try {
|
|
565
|
+
assertScanned({
|
|
566
|
+
gate: 'check_always_budget',
|
|
567
|
+
scanned: allRules().length,
|
|
568
|
+
units: 'projected rule file(s)',
|
|
569
|
+
roots: [path.relative(REPO_ROOT, RULES_DIR)],
|
|
570
|
+
});
|
|
571
|
+
} catch (exc) {
|
|
572
|
+
if (exc instanceof DeadScopeError) {
|
|
573
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
574
|
+
return 3;
|
|
575
|
+
}
|
|
576
|
+
throw exc;
|
|
577
|
+
}
|
|
578
|
+
|
|
555
579
|
const rules = allwaysRules();
|
|
556
580
|
if (rules.length === 0) {
|
|
557
581
|
process.stderr.write(`❌ no always-rules found under ${RULES_DIR}\n`);
|
|
@@ -33,6 +33,7 @@ import * as fs from 'node:fs';
|
|
|
33
33
|
import * as path from 'node:path';
|
|
34
34
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
35
35
|
|
|
36
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
36
37
|
import {
|
|
37
38
|
apply_schema_defaults,
|
|
38
39
|
load_schema,
|
|
@@ -153,6 +154,16 @@ function _check(manifestPath: string): [number, string[]] {
|
|
|
153
154
|
const artefacts = Array.isArray(manifest['artefacts'])
|
|
154
155
|
? (manifest['artefacts'] as Json[])
|
|
155
156
|
: [];
|
|
157
|
+
// A readable manifest with an empty (or non-array) `artefacts` reports
|
|
158
|
+
// "0 artefact checksums verified" and exits green — the drift this gate
|
|
159
|
+
// exists to catch would be invisible in exactly the case where the
|
|
160
|
+
// generator produced nothing.
|
|
161
|
+
assertScanned({
|
|
162
|
+
gate: 'check_artefact_checksums',
|
|
163
|
+
scanned: artefacts.length,
|
|
164
|
+
units: 'manifest artefact(s)',
|
|
165
|
+
roots: [manifestPath],
|
|
166
|
+
});
|
|
156
167
|
for (const artRaw of artefacts) {
|
|
157
168
|
const art = (artRaw ?? {}) as JsonObject;
|
|
158
169
|
const rel = art['path'];
|
|
@@ -238,7 +249,19 @@ function parse_args(argv: readonly string[]): ParsedArgs {
|
|
|
238
249
|
function main(argv: readonly string[]): number {
|
|
239
250
|
const args = parse_args(argv);
|
|
240
251
|
|
|
241
|
-
|
|
252
|
+
let code: number;
|
|
253
|
+
let errors: string[];
|
|
254
|
+
try {
|
|
255
|
+
[code, errors] = _check(args.manifest);
|
|
256
|
+
} catch (exc) {
|
|
257
|
+
if (exc instanceof DeadScopeError) {
|
|
258
|
+
// 1 is this gate's only non-zero code, and it already covers the
|
|
259
|
+
// sibling could-not-run case (`manifest not found`).
|
|
260
|
+
process.stderr.write(`error: ${exc.message}\n`);
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
throw exc;
|
|
264
|
+
}
|
|
242
265
|
if (code !== 0) {
|
|
243
266
|
for (const e of errors.slice(0, 20)) {
|
|
244
267
|
process.stderr.write(`error: ${e}\n`);
|
|
@@ -32,6 +32,7 @@ import * as fs from 'node:fs';
|
|
|
32
32
|
import * as path from 'node:path';
|
|
33
33
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
34
34
|
|
|
35
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
35
36
|
import { canonical_counts } from './check_command_count_messaging.js';
|
|
36
37
|
import { count, TARGETS } from './update_counts.js';
|
|
37
38
|
|
|
@@ -253,6 +254,25 @@ export function anchor_coverage_gaps(root: string = ROOT): CoverageGap[] {
|
|
|
253
254
|
|
|
254
255
|
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
255
256
|
const QUIET = argv.includes('--quiet');
|
|
257
|
+
// This gate walks no tree — it guards a hand-maintained surface list, and
|
|
258
|
+
// every missing entry is skipped with a warning so nothing hard-breaks on a
|
|
259
|
+
// doc rename. That tolerance is also the failure mode: a repo-wide docs
|
|
260
|
+
// move leaves every surface "skipped" and the gate reporting all counts in
|
|
261
|
+
// sync. Deleting these paths must fail, never quieten, the gate.
|
|
262
|
+
try {
|
|
263
|
+
assertWatchlistResolves({
|
|
264
|
+
gate: 'check_artefact_count_messaging',
|
|
265
|
+
candidates: [...SURFACES, ...STRUCTURED_SURFACES.map((s) => s.file)],
|
|
266
|
+
repoRoot: ROOT,
|
|
267
|
+
});
|
|
268
|
+
} catch (exc) {
|
|
269
|
+
if (exc instanceof DeadScopeError) {
|
|
270
|
+
// 1 is this gate's only non-zero code.
|
|
271
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
|
274
|
+
throw exc;
|
|
275
|
+
}
|
|
256
276
|
const expected: Record<string, number> = {};
|
|
257
277
|
for (const [kind] of KIND_PATTERNS) {
|
|
258
278
|
expected[kind] = canonical_for(kind);
|
|
@@ -24,6 +24,8 @@ import * as fs from 'node:fs';
|
|
|
24
24
|
import * as path from 'node:path';
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
26
|
|
|
27
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
28
|
+
|
|
27
29
|
const QUIET = process.argv.slice(2).includes('--quiet');
|
|
28
30
|
|
|
29
31
|
const STALE_DAYS = 90;
|
|
@@ -33,15 +35,6 @@ const MIN_USEFUL_LINES = 5;
|
|
|
33
35
|
const _HERE = fileURLToPath(import.meta.url);
|
|
34
36
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
35
37
|
|
|
36
|
-
function _exists(p: string): boolean {
|
|
37
|
-
try {
|
|
38
|
-
fs.statSync(p);
|
|
39
|
-
return true;
|
|
40
|
-
} catch {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
38
|
function _emit(notes: readonly string[]): void {
|
|
46
39
|
if (notes.length === 0) {
|
|
47
40
|
if (!QUIET) {
|
|
@@ -60,12 +53,26 @@ function check(): number {
|
|
|
60
53
|
const target = path.join(REPO_ROOT, '.augmentignore');
|
|
61
54
|
const notes: string[] = [];
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
// The whole scan scope is this one file, so its absence IS the dead-scope
|
|
57
|
+
// case. It used to surface as a scaffolding hint on stdout, which reads the
|
|
58
|
+
// same whether the file was never written or the root moved. The exit code
|
|
59
|
+
// stays 0 because "always exits 0" is this file's pinned contract (advisory,
|
|
60
|
+
// not a gate) — there is no failure code to choose between.
|
|
61
|
+
try {
|
|
62
|
+
assertWatchlistResolves({
|
|
63
|
+
gate: 'check_augmentignore',
|
|
64
|
+
candidates: ['.augmentignore'],
|
|
65
|
+
repoRoot: REPO_ROOT,
|
|
66
|
+
});
|
|
67
|
+
} catch (exc) {
|
|
68
|
+
if (exc instanceof DeadScopeError) {
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
`⚠️ ${exc.message}\n` +
|
|
71
|
+
' Run `/optimize augmentignore` to scaffold it.\n',
|
|
72
|
+
);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
throw exc;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
const mtimeMs = fs.statSync(target).mtimeMs;
|
|
@@ -45,6 +45,8 @@ import * as fs from 'node:fs';
|
|
|
45
45
|
import * as path from 'node:path';
|
|
46
46
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
47
47
|
|
|
48
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
49
|
+
|
|
48
50
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
49
51
|
const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
50
52
|
const BASELINE = path.join(REPO_ROOT, 'internal', 'reports', 'rule-backstop-debt.json');
|
|
@@ -107,6 +109,26 @@ function main(argv: string[]): number {
|
|
|
107
109
|
const as_json = argv.includes('--json');
|
|
108
110
|
const write = argv.includes('--write-baseline');
|
|
109
111
|
|
|
112
|
+
// This file owns no corpus — it ratchets five NAMED gates, so its scope is
|
|
113
|
+
// that list. A renamed or deleted gate script still spawns, fails, and lands
|
|
114
|
+
// in `unparsed`, whose message sends the reader to fix a regex that is not
|
|
115
|
+
// the problem. Asserted before `--write-baseline` too: a baseline anchored
|
|
116
|
+
// over phantom gates is a ratchet that can never rise. Exit 2 (usage / env
|
|
117
|
+
// error) rather than 1 — the ratchet could not run, no count rose.
|
|
118
|
+
try {
|
|
119
|
+
assertWatchlistResolves({
|
|
120
|
+
gate: 'check_backstop_debt',
|
|
121
|
+
candidates: GATES.map((g) => `src/scripts/${g.script}.ts`),
|
|
122
|
+
repoRoot: REPO_ROOT,
|
|
123
|
+
});
|
|
124
|
+
} catch (exc) {
|
|
125
|
+
if (exc instanceof DeadScopeError) {
|
|
126
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
127
|
+
return 2;
|
|
128
|
+
}
|
|
129
|
+
throw exc;
|
|
130
|
+
}
|
|
131
|
+
|
|
110
132
|
const measured = GATES.map((g) => measure(g));
|
|
111
133
|
|
|
112
134
|
if (as_json) {
|
|
@@ -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
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
24
26
|
const CONTRACTS_DIR = 'docs/contracts';
|
|
@@ -191,9 +193,28 @@ function main(): number {
|
|
|
191
193
|
const args = parse_args(process.argv.slice(2));
|
|
192
194
|
const todayOrdinal = _todayOrdinal();
|
|
193
195
|
const violations: Violation[] = [];
|
|
194
|
-
|
|
196
|
+
const contracts = _globMdSorted(path.join(ROOT, CONTRACTS_DIR));
|
|
197
|
+
for (const p of contracts) {
|
|
195
198
|
violations.push(...check_one(p, todayOrdinal));
|
|
196
199
|
}
|
|
200
|
+
// Count every contract read, not the `stability: beta` subset: over a moved
|
|
201
|
+
// `docs/contracts/` "no beta contracts" and "no contracts at all" produce
|
|
202
|
+
// the same clean line, and only the second is a dead gate. Exit 1 is the
|
|
203
|
+
// violation code; 3 stays reserved for the internal-error handler below.
|
|
204
|
+
try {
|
|
205
|
+
assertScanned({
|
|
206
|
+
gate: 'check_beta_review_markers',
|
|
207
|
+
scanned: contracts.length,
|
|
208
|
+
units: 'contract file(s)',
|
|
209
|
+
roots: [CONTRACTS_DIR],
|
|
210
|
+
});
|
|
211
|
+
} catch (exc) {
|
|
212
|
+
if (exc instanceof DeadScopeError) {
|
|
213
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
throw exc;
|
|
217
|
+
}
|
|
197
218
|
if (args.json) {
|
|
198
219
|
process.stdout.write(JSON.stringify({ violations }, null, 2) + '\n');
|
|
199
220
|
} else {
|
|
@@ -25,6 +25,8 @@ import * as fs from 'node:fs';
|
|
|
25
25
|
import * as path from 'node:path';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
|
|
28
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
29
|
+
|
|
28
30
|
const _HERE = fileURLToPath(import.meta.url);
|
|
29
31
|
const REPO_ROOT = path.join(path.dirname(_HERE), '..', '..');
|
|
30
32
|
|
|
@@ -135,6 +137,17 @@ export function run(repoRoot: string): string[] {
|
|
|
135
137
|
const before: Record<string, Record<string, string>> = {};
|
|
136
138
|
for (const r of roots) before[r] = snapshot_tree(r);
|
|
137
139
|
|
|
140
|
+
// Both legs are diffs over these snapshots: with no bridge trees on disk
|
|
141
|
+
// there is nothing to diff and nothing to validate, and the check reports a
|
|
142
|
+
// fixpoint it never tested. Thrown, not returned — `errors` is the drift
|
|
143
|
+
// channel, and a dead scope is not drift.
|
|
144
|
+
assertScanned({
|
|
145
|
+
gate: 'check_bridge_derivation',
|
|
146
|
+
scanned: Object.values(before).reduce((n, snap) => n + Object.keys(snap).length, 0),
|
|
147
|
+
units: 'bridge entries',
|
|
148
|
+
roots: BRIDGE_ROOTS,
|
|
149
|
+
});
|
|
150
|
+
|
|
138
151
|
// Regenerate in a child process so MODULE_STATE resolves exactly as the
|
|
139
152
|
// real pipeline does (no partial in-process state). `--generate-tools`
|
|
140
153
|
// covers the host trees; `--project-augment` covers `.augment` (it is
|
|
@@ -162,7 +175,15 @@ const isMain = process.argv[1] !== undefined && path.resolve(process.argv[1]) ==
|
|
|
162
175
|
if (isMain) {
|
|
163
176
|
const idx = process.argv.indexOf('--root');
|
|
164
177
|
const root = idx !== -1 ? (process.argv[idx + 1] ?? REPO_ROOT) : REPO_ROOT;
|
|
165
|
-
|
|
178
|
+
let errors: string[];
|
|
179
|
+
try {
|
|
180
|
+
errors = run(root);
|
|
181
|
+
} catch (exc) {
|
|
182
|
+
if (!(exc instanceof DeadScopeError)) throw exc;
|
|
183
|
+
// Exit 1 is this gate's only failure code.
|
|
184
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
166
187
|
if (errors.length > 0) {
|
|
167
188
|
for (const e of errors) process.stderr.write(`❌ ${e}\n`);
|
|
168
189
|
process.exit(1);
|
|
@@ -57,6 +57,8 @@ import * as fs from 'node:fs';
|
|
|
57
57
|
import * as path from 'node:path';
|
|
58
58
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
59
59
|
|
|
60
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
61
|
+
|
|
60
62
|
const _HERE = fileURLToPath(import.meta.url);
|
|
61
63
|
const REPO = path.resolve(path.dirname(_HERE), '..', '..');
|
|
62
64
|
|
|
@@ -290,6 +292,30 @@ function parse_args(argv: readonly string[]): Options {
|
|
|
290
292
|
function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
291
293
|
const opts = parse_args(argv);
|
|
292
294
|
const targets = opts.files.length > 0 ? opts.files : tracked_bundle_files();
|
|
295
|
+
|
|
296
|
+
// `git ls-files <untracked-root>` returns nothing, by design (BUNDLE_ROOTS
|
|
297
|
+
// lists roots that may not be tracked yet). That tolerance is also the
|
|
298
|
+
// failure mode: if `dist/install/` stopped being force-included, every root
|
|
299
|
+
// resolves empty and the gate prints "0 tracked bundle file(s) scanned"
|
|
300
|
+
// with exit 0. Deletion test: a repo that ships an install bundle always
|
|
301
|
+
// tracks at least one bundle file, so 0 is never success here.
|
|
302
|
+
// Exit 2 (usage/env) over 1 (leak found) — a dead scope means the gate
|
|
303
|
+
// could not run, not that it found a leak.
|
|
304
|
+
try {
|
|
305
|
+
assertScanned({
|
|
306
|
+
gate: 'check_bundle_path_leakage',
|
|
307
|
+
scanned: targets.length,
|
|
308
|
+
units: 'bundle file(s)',
|
|
309
|
+
roots: opts.files.length > 0 ? ['<explicit file arguments>'] : BUNDLE_ROOTS,
|
|
310
|
+
});
|
|
311
|
+
} catch (exc) {
|
|
312
|
+
if (exc instanceof DeadScopeError) {
|
|
313
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
314
|
+
return 2;
|
|
315
|
+
}
|
|
316
|
+
throw exc;
|
|
317
|
+
}
|
|
318
|
+
|
|
293
319
|
const hits = scan_files(targets);
|
|
294
320
|
|
|
295
321
|
if (hits.length > 0) {
|
|
@@ -38,6 +38,8 @@ import * as fs from 'node:fs';
|
|
|
38
38
|
import * as path from 'node:path';
|
|
39
39
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
40
40
|
|
|
41
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
42
|
+
|
|
41
43
|
const _HERE = fileURLToPath(import.meta.url);
|
|
42
44
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
43
45
|
const TASKFILE = path.join(REPO_ROOT, 'Taskfile.yml');
|
|
@@ -117,9 +119,25 @@ export function main(): number {
|
|
|
117
119
|
(e) => !e.startsWith('- defer:') && e !== '- task: _ci-start' && e !== '- task: _ci-end',
|
|
118
120
|
);
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
// Replaces the ad-hoc `gateEntries.length === 0` guard. Exit 2, not the 1 it
|
|
123
|
+
// returned: a task list with nothing in it means the gate could not verify
|
|
124
|
+
// the invariant — the documented meaning of 2, and what the two
|
|
125
|
+
// missing-task branches above already return — not that the invariant is
|
|
126
|
+
// violated. Counted after the wrapper filter on purpose: a `ci-strict` made
|
|
127
|
+
// of nothing but `_ci-start`/`_ci-end` is just as dead a scope.
|
|
128
|
+
try {
|
|
129
|
+
assertScanned({
|
|
130
|
+
gate: 'check_ci_strict_superset',
|
|
131
|
+
scanned: gateEntries.length,
|
|
132
|
+
units: 'ci-strict gate entr(y|ies)',
|
|
133
|
+
roots: ['Taskfile.yml (ci-strict.cmds)'],
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (err instanceof DeadScopeError) {
|
|
137
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
138
|
+
return 2;
|
|
139
|
+
}
|
|
140
|
+
throw err;
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
if (gateEntries[0] !== DELEGATION) {
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
parse_exec_pointer,
|
|
45
45
|
run_exec_evidence,
|
|
46
46
|
} from './_lib/exec_evidence.js';
|
|
47
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
47
48
|
|
|
48
49
|
const _FILE = fileURLToPath(import.meta.url);
|
|
49
50
|
const _HERE = path.dirname(_FILE);
|
|
@@ -271,6 +272,27 @@ interface Finding {
|
|
|
271
272
|
function main(argv: string[] = process.argv.slice(2)): number {
|
|
272
273
|
const args = parse_args(argv);
|
|
273
274
|
const ledger = load_ledger();
|
|
275
|
+
// The ledger is the corpus both passes depend on: markers resolve against
|
|
276
|
+
// it and the rot guard walks it. `load_ledger` returns an empty map for a
|
|
277
|
+
// missing docs/CLAIMS.md, and with no ledger there is nothing to declare
|
|
278
|
+
// dangling — so a moved ledger would print "0 markered claim(s) bound ·
|
|
279
|
+
// ledger 0 entries" and certify the anti-hype guardrail over nothing.
|
|
280
|
+
// Marker count is deliberately not the unit: a surface tree with zero
|
|
281
|
+
// markers is a legitimate state, an absent ledger never is.
|
|
282
|
+
try {
|
|
283
|
+
assertScanned({
|
|
284
|
+
gate: 'check_claims',
|
|
285
|
+
scanned: ledger.size,
|
|
286
|
+
units: 'ledger entry(s)',
|
|
287
|
+
roots: [LEDGER_REL],
|
|
288
|
+
});
|
|
289
|
+
} catch (exc) {
|
|
290
|
+
if (exc instanceof DeadScopeError) {
|
|
291
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
292
|
+
return 2;
|
|
293
|
+
}
|
|
294
|
+
throw exc;
|
|
295
|
+
}
|
|
274
296
|
const markers = scan_markers();
|
|
275
297
|
const findings: Finding[] = [];
|
|
276
298
|
|
|
@@ -27,12 +27,20 @@ import * as fs from 'node:fs';
|
|
|
27
27
|
import * as path from 'node:path';
|
|
28
28
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
29
29
|
|
|
30
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
31
|
+
|
|
30
32
|
const _HERE = fileURLToPath(import.meta.url);
|
|
31
33
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
/** The gate's entire scan scope: three named inputs, repo-relative. */
|
|
36
|
+
const INPUTS = [
|
|
37
|
+
'src/cli/registry.ts',
|
|
38
|
+
'src/config/evaluator-budgets.json',
|
|
39
|
+
'agents/evidence/metrics/evaluator-measurements.json',
|
|
40
|
+
] as const;
|
|
41
|
+
const [REGISTRY_PATH, BUDGETS_PATH, RECORD_PATH] = INPUTS.map((rel) =>
|
|
42
|
+
path.join(REPO_ROOT, rel),
|
|
43
|
+
) as [string, string, string];
|
|
36
44
|
|
|
37
45
|
const METRIC = 'cli_help_command_count';
|
|
38
46
|
|
|
@@ -105,6 +113,27 @@ function _read_json(p: string): Record<string, unknown> {
|
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
function main(): number {
|
|
116
|
+
// No corpus to walk — the scope is the watch list above. The reads below
|
|
117
|
+
// already fail closed, but they surface an ENOENT for whichever path is read
|
|
118
|
+
// first; this names all three, and it keeps the `scanned:` line derived from
|
|
119
|
+
// what actually resolved instead of the literal 3 it used to print. Exit 2
|
|
120
|
+
// is the same fail-closed code those reads use: a gate that cannot read its
|
|
121
|
+
// inputs must not certify them.
|
|
122
|
+
let resolved: string[];
|
|
123
|
+
try {
|
|
124
|
+
resolved = assertWatchlistResolves({
|
|
125
|
+
gate: 'check_cli_registry_budget_sync',
|
|
126
|
+
candidates: INPUTS,
|
|
127
|
+
repoRoot: REPO_ROOT,
|
|
128
|
+
});
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (err instanceof DeadScopeError) {
|
|
131
|
+
process.stderr.write(`ERROR: ${err.message}\n`);
|
|
132
|
+
return 2;
|
|
133
|
+
}
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
|
|
108
137
|
let registrySource: string;
|
|
109
138
|
let budgetsDoc: Record<string, unknown>;
|
|
110
139
|
let recordDoc: Record<string, unknown>;
|
|
@@ -128,7 +157,7 @@ function main(): number {
|
|
|
128
157
|
recorded: measurements?.[METRIC],
|
|
129
158
|
});
|
|
130
159
|
|
|
131
|
-
process.stdout.write(
|
|
160
|
+
process.stdout.write(`scanned: ${String(resolved.length)}\n`);
|
|
132
161
|
if (findings.length > 0) {
|
|
133
162
|
for (const f of findings) {
|
|
134
163
|
process.stdout.write(`❌ ${METRIC} out of sync: ${f}\n`);
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
* a few contract rows point at full working commands that merely happen to
|
|
32
32
|
* carry one nested sub-command, and those own no dispatch table.
|
|
33
33
|
*
|
|
34
|
-
* Exit codes: 0 = clean, 1 = pattern violations, 3 = internal error
|
|
34
|
+
* Exit codes: 0 = clean, 1 = pattern violations, 3 = internal error (which now
|
|
35
|
+
* includes a dead scan scope — an unparseable cluster table — reported through
|
|
36
|
+
* `_lib/scan_scope` so the message names the root).
|
|
35
37
|
*/
|
|
36
38
|
|
|
37
39
|
import * as fs from 'node:fs';
|
|
@@ -39,6 +41,7 @@ import * as path from 'node:path';
|
|
|
39
41
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
40
42
|
|
|
41
43
|
import { SRC_DOMAINS, command_slug, resolve_logical } from './_lib/agent_src.js';
|
|
44
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
42
45
|
|
|
43
46
|
const _HERE = fileURLToPath(import.meta.url);
|
|
44
47
|
// src/scripts/check_cluster_patterns.ts → two dirs up is the repo root.
|
|
@@ -369,10 +372,29 @@ function _relativeToRootOrAbs(p: string): string {
|
|
|
369
372
|
}
|
|
370
373
|
|
|
371
374
|
export function main(): number {
|
|
375
|
+
// The locked-clusters table IS the corpus — every row drives one dispatcher
|
|
376
|
+
// read. A renamed contract file or a renamed `## Locked clusters` heading
|
|
377
|
+
// parses zero rows, which used to exit 3 (internal error, advisory at call
|
|
378
|
+
// sites) rather than fail: a blind gate, not a broken one.
|
|
372
379
|
const rows = load_cluster_table();
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
380
|
+
try {
|
|
381
|
+
assertScanned({
|
|
382
|
+
gate: 'check_cluster_patterns',
|
|
383
|
+
scanned: rows.length,
|
|
384
|
+
units: 'locked-cluster row(s)',
|
|
385
|
+
roots: [_relativeToRootOrAbs(CONTRACT)],
|
|
386
|
+
});
|
|
387
|
+
} catch (exc) {
|
|
388
|
+
if (exc instanceof DeadScopeError) {
|
|
389
|
+
// Exit 3, not 1: this gate's CLI contract is pinned by its py2ts
|
|
390
|
+
// port, and an empty table was ALREADY loud here (3 is non-zero, and
|
|
391
|
+
// the Taskfile fails on any non-zero). Hardening means routing the
|
|
392
|
+
// check through the shared assertion so the message names the root —
|
|
393
|
+
// not renumbering an exit code that was never a silent green.
|
|
394
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
395
|
+
return 3;
|
|
396
|
+
}
|
|
397
|
+
throw exc;
|
|
376
398
|
}
|
|
377
399
|
|
|
378
400
|
const slug_map = build_slug_map();
|