@event4u/agent-config 9.20.0 → 9.22.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 +86 -0
- package/dist/agent-src/commands/worktree/cleanup.md +17 -0
- package/dist/agent-src/commands/worktree/status.md +6 -0
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +22 -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 +5 -5
- 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 +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- 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/bench_ab_activation.ts +368 -0
- 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/bench_ab_task_runner.ts +18 -0
- package/src/scripts/bench_ab_v2_run.ts +147 -6
- package/src/scripts/bench_ab_v2_stats.ts +74 -2
- 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/worktree_cleanup_check.ts +319 -4
- package/src/scripts/check_bite_sized_granularity.ts +0 -116
- package/src/scripts/lint_workspace_boundary.ts +0 -267
|
@@ -30,6 +30,8 @@ import * as fs from 'node:fs';
|
|
|
30
30
|
import * as path from 'node:path';
|
|
31
31
|
import { fileURLToPath } from 'node:url';
|
|
32
32
|
|
|
33
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
34
|
+
|
|
33
35
|
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
34
36
|
|
|
35
37
|
const PATTERNS: readonly { re: RegExp; label: string }[] = [
|
|
@@ -72,6 +74,7 @@ export function scanText(relPath: string, text: string): string[] {
|
|
|
72
74
|
|
|
73
75
|
export function main(): number {
|
|
74
76
|
const findings: string[] = [];
|
|
77
|
+
let scanned = 0;
|
|
75
78
|
for (const rel of shippedTrackedFiles()) {
|
|
76
79
|
if (CARVE_OUTS.has(rel)) continue;
|
|
77
80
|
if (SKIP_EXT.has(path.extname(rel))) continue;
|
|
@@ -83,8 +86,29 @@ export function main(): number {
|
|
|
83
86
|
} catch {
|
|
84
87
|
continue;
|
|
85
88
|
}
|
|
89
|
+
scanned += 1;
|
|
86
90
|
findings.push(...scanText(rel, text));
|
|
87
91
|
}
|
|
92
|
+
// The scope is an intersection — `git ls-files` ∩ the package.json `files`
|
|
93
|
+
// whitelist — and either side collapsing to nothing (no git, a rewritten
|
|
94
|
+
// whitelist) leaves the gate printing "no pre-migration install hints"
|
|
95
|
+
// over an empty set. Counts files whose text was READ, which is zero
|
|
96
|
+
// whenever the enumeration itself is empty. Exit 2 is the internal-error
|
|
97
|
+
// code ("could not run"); 1 means a live hint was found.
|
|
98
|
+
try {
|
|
99
|
+
assertScanned({
|
|
100
|
+
gate: 'lint_pre_migration_refs',
|
|
101
|
+
scanned,
|
|
102
|
+
units: 'shipped tracked file(s)',
|
|
103
|
+
roots: ['git ls-files ∩ package.json files[]'],
|
|
104
|
+
});
|
|
105
|
+
} catch (exc) {
|
|
106
|
+
if (exc instanceof DeadScopeError) {
|
|
107
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
108
|
+
return 2;
|
|
109
|
+
}
|
|
110
|
+
throw exc;
|
|
111
|
+
}
|
|
88
112
|
if (findings.length > 0) {
|
|
89
113
|
for (const f of findings) {
|
|
90
114
|
process.stderr.write(`❌ pre-migration reference: ${f}\n`);
|
|
@@ -21,6 +21,8 @@ import * as path from 'node:path';
|
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
22
|
import { parse as parseYaml } from 'yaml';
|
|
23
23
|
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
25
|
+
|
|
24
26
|
const _HERE = fileURLToPath(import.meta.url);
|
|
25
27
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
26
28
|
const ALIASES_YML = path.join(REPO_ROOT, 'src/config/discovery/session-profiles.yml');
|
|
@@ -237,11 +239,13 @@ function lint(quiet = false): number {
|
|
|
237
239
|
['profile', PROFILES_DIR, 'profile', 'packs'],
|
|
238
240
|
['pack', PACKS_DIR, 'pack', null],
|
|
239
241
|
];
|
|
242
|
+
let definitions = 0;
|
|
240
243
|
for (const [, directory, rootKey, seedKey] of dirs) {
|
|
241
244
|
if (!_exists(directory)) {
|
|
242
245
|
continue;
|
|
243
246
|
}
|
|
244
247
|
for (const p of _globYmlSorted(directory)) {
|
|
248
|
+
definitions += 1;
|
|
245
249
|
const doc = _load_yaml(p);
|
|
246
250
|
if (!_isPlainObject(doc)) {
|
|
247
251
|
continue;
|
|
@@ -291,6 +295,26 @@ function lint(quiet = false): number {
|
|
|
291
295
|
}
|
|
292
296
|
}
|
|
293
297
|
|
|
298
|
+
// Clauses 1b/2a/2b run per static definition file, and both their roots are
|
|
299
|
+
// skipped silently when absent — so the whole per-file half of this lint can
|
|
300
|
+
// go quiet while clause 1a keeps printing an alias count. Counted before the
|
|
301
|
+
// parse filter above: an unparseable file is still a file that was walked.
|
|
302
|
+
try {
|
|
303
|
+
assertScanned({
|
|
304
|
+
gate: 'lint_profile_overlay_set_only',
|
|
305
|
+
scanned: definitions,
|
|
306
|
+
units: 'static definition file(s)',
|
|
307
|
+
roots: ['src/agent-src/profiles', 'src/agent-src/packs'],
|
|
308
|
+
});
|
|
309
|
+
} catch (e) {
|
|
310
|
+
if (e instanceof DeadScopeError) {
|
|
311
|
+
// 1 is this CLI's only failure code — pinned by the ported contract.
|
|
312
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
throw e;
|
|
316
|
+
}
|
|
317
|
+
|
|
294
318
|
if (errors.length > 0) {
|
|
295
319
|
for (const e of errors) {
|
|
296
320
|
process.stderr.write(`❌ ${e}\n`);
|
|
@@ -54,6 +54,8 @@ import * as path from 'node:path';
|
|
|
54
54
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
55
55
|
import { parse as parseYaml } from 'yaml';
|
|
56
56
|
|
|
57
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
58
|
+
|
|
57
59
|
const _FILE = fileURLToPath(import.meta.url);
|
|
58
60
|
const _HERE = path.dirname(_FILE);
|
|
59
61
|
// src/scripts/lint_provenance.ts → two levels up is the repo root.
|
|
@@ -480,6 +482,25 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
480
482
|
const args = parseArgs(argv);
|
|
481
483
|
const ledgerPath = path.join(REPO, LEDGER_REL);
|
|
482
484
|
const noticesPath = path.join(REPO, NOTICES_REL);
|
|
485
|
+
// The ledger is the record this gate audits. Record COUNT is not the scope
|
|
486
|
+
// signal — an empty ledger is a legitimate "no borrows yet" state (it is
|
|
487
|
+
// empty today) — but an absent ledger file is not: the `existsSync ? … : ''`
|
|
488
|
+
// fallback below would read a moved ledger as zero borrows and re-render the
|
|
489
|
+
// notices to match, laundering the loss into a clean sync. Exit 2 is this
|
|
490
|
+
// linter's single failure code ("this run produced nothing trustworthy").
|
|
491
|
+
try {
|
|
492
|
+
assertWatchlistResolves({
|
|
493
|
+
gate: 'lint_provenance',
|
|
494
|
+
candidates: [LEDGER_REL],
|
|
495
|
+
repoRoot: REPO,
|
|
496
|
+
});
|
|
497
|
+
} catch (exc) {
|
|
498
|
+
if (exc instanceof DeadScopeError) {
|
|
499
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
500
|
+
return 2;
|
|
501
|
+
}
|
|
502
|
+
throw exc;
|
|
503
|
+
}
|
|
483
504
|
const text = fs.existsSync(ledgerPath) ? fs.readFileSync(ledgerPath, 'utf-8') : '';
|
|
484
505
|
const deny = resolveDenyPolicy(REPO);
|
|
485
506
|
const { records, findings } = lintLedgerText(text, REPO, deny);
|
|
@@ -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 CLAIMS_LEDGER_REL = 'docs/CLAIMS.md';
|
|
@@ -384,6 +386,16 @@ export function lintProvenanceVocabulary(repoRoot: string = REPO_ROOT): Violatio
|
|
|
384
386
|
for (const root of SCAN_ROOTS) {
|
|
385
387
|
for (const f of _collectMd(path.join(repoRoot, root))) files.add(f);
|
|
386
388
|
}
|
|
389
|
+
// `_collectMd` returns [] for a root that is not there, so a moved docs
|
|
390
|
+
// tree turns the honesty floor into a no-op that still prints its ✅ line.
|
|
391
|
+
// Counted over every markdown file walked — the banned-phrase scan reads
|
|
392
|
+
// all of them, long before any approved-vocabulary filtering.
|
|
393
|
+
assertScanned({
|
|
394
|
+
gate: 'lint_provenance_vocabulary',
|
|
395
|
+
scanned: files.size,
|
|
396
|
+
units: 'prose file(s)',
|
|
397
|
+
roots: SCAN_ROOTS,
|
|
398
|
+
});
|
|
387
399
|
for (const abs of [...files].sort()) {
|
|
388
400
|
const rel = path.relative(repoRoot, abs);
|
|
389
401
|
const text = fs.readFileSync(abs, 'utf-8');
|
|
@@ -398,7 +410,18 @@ function _print(quiet: boolean, msg: string): void {
|
|
|
398
410
|
|
|
399
411
|
export function main(): number {
|
|
400
412
|
const quiet = process.argv.slice(2).includes('--quiet');
|
|
401
|
-
|
|
413
|
+
let violations: Violation[];
|
|
414
|
+
try {
|
|
415
|
+
violations = lintProvenanceVocabulary();
|
|
416
|
+
} catch (e) {
|
|
417
|
+
// 1 is the only failure code this gate defines, so a dead scan root and
|
|
418
|
+
// a real violation share it — the message distinguishes them.
|
|
419
|
+
if (e instanceof DeadScopeError) {
|
|
420
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
421
|
+
return 1;
|
|
422
|
+
}
|
|
423
|
+
throw e;
|
|
424
|
+
}
|
|
402
425
|
if (violations.length > 0) {
|
|
403
426
|
for (const v of violations) {
|
|
404
427
|
process.stderr.write(`❌ [${v.rule}] ${v.file}: ${v.msg}\n`);
|
|
@@ -18,6 +18,8 @@ import * as fs from 'node:fs';
|
|
|
18
18
|
import * as path from 'node:path';
|
|
19
19
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
20
|
|
|
21
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
22
|
+
|
|
21
23
|
const _HERE = fileURLToPath(import.meta.url);
|
|
22
24
|
|
|
23
25
|
const README = 'README.md';
|
|
@@ -124,9 +126,25 @@ function _pyRepr(s: string): string {
|
|
|
124
126
|
|
|
125
127
|
function main(): number {
|
|
126
128
|
const quiet = process.argv.slice(2).includes('--quiet');
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
// README is this gate's whole scope and resolves against cwd, so a wrong
|
|
130
|
+
// cwd is indistinguishable from a deleted file. Replaces the bare
|
|
131
|
+
// existsSync check: same exit code, but the failure now names the root it
|
|
132
|
+
// looked under. Exit 1 is the only failure code this CLI has (over budget /
|
|
133
|
+
// missing) — here it means "could not run", not "over budget".
|
|
134
|
+
try {
|
|
135
|
+
assertWatchlistResolves({
|
|
136
|
+
gate: 'lint_readme_jargon',
|
|
137
|
+
candidates: [README],
|
|
138
|
+
repoRoot: process.cwd(),
|
|
139
|
+
});
|
|
140
|
+
} catch (exc) {
|
|
141
|
+
if (exc instanceof DeadScopeError) {
|
|
142
|
+
// First line is the pinned py2ts wording; the second names the root.
|
|
143
|
+
process.stderr.write(`error: ${README} not found\n`);
|
|
144
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
throw exc;
|
|
130
148
|
}
|
|
131
149
|
|
|
132
150
|
const allLines = _splitlines(fs.readFileSync(README, 'utf-8'));
|
|
@@ -20,6 +20,8 @@ import * as fs from 'node:fs';
|
|
|
20
20
|
import * as path from 'node:path';
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
22
|
|
|
23
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
24
|
+
|
|
23
25
|
const _HERE = fileURLToPath(import.meta.url);
|
|
24
26
|
|
|
25
27
|
const README = 'README.md';
|
|
@@ -116,9 +118,25 @@ function collect_hits(text: string): Hit[] {
|
|
|
116
118
|
|
|
117
119
|
function main(): number {
|
|
118
120
|
const quiet = process.argv.slice(2).includes('--quiet');
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
// README is this gate's whole scope and resolves against cwd, so a wrong
|
|
122
|
+
// cwd is indistinguishable from a deleted file. Replaces the bare
|
|
123
|
+
// existsSync check: same exit code, but the failure now names the root it
|
|
124
|
+
// looked under. Exit 1 is the only failure code this CLI has (violation /
|
|
125
|
+
// missing) — here it means "could not run", not "violation found".
|
|
126
|
+
try {
|
|
127
|
+
assertWatchlistResolves({
|
|
128
|
+
gate: 'lint_readme_serial_comma',
|
|
129
|
+
candidates: [README],
|
|
130
|
+
repoRoot: process.cwd(),
|
|
131
|
+
});
|
|
132
|
+
} catch (exc) {
|
|
133
|
+
if (exc instanceof DeadScopeError) {
|
|
134
|
+
// First line is the pinned wording; the second names the root.
|
|
135
|
+
process.stderr.write(`error: ${README} not found\n`);
|
|
136
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
throw exc;
|
|
122
140
|
}
|
|
123
141
|
|
|
124
142
|
const hits = collect_hits(fs.readFileSync(README, 'utf-8'));
|
|
@@ -16,6 +16,8 @@ import * as fs from 'node:fs';
|
|
|
16
16
|
import * as path from 'node:path';
|
|
17
17
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
18
|
|
|
19
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
20
|
+
|
|
19
21
|
const _HERE = fileURLToPath(import.meta.url);
|
|
20
22
|
|
|
21
23
|
const README = 'README.md';
|
|
@@ -45,9 +47,25 @@ function _splitlinesCount(text: string): number {
|
|
|
45
47
|
|
|
46
48
|
function main(): number {
|
|
47
49
|
const quiet = process.argv.slice(2).includes('--quiet');
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
// README is this gate's whole scope and resolves against cwd, so a wrong
|
|
51
|
+
// cwd is indistinguishable from a deleted file. Replaces the bare
|
|
52
|
+
// existsSync check: same exit code, but the failure now names the root it
|
|
53
|
+
// looked under. Exit 1 is the only failure code this CLI has (over budget /
|
|
54
|
+
// missing) — here it means "could not run", not "over budget".
|
|
55
|
+
try {
|
|
56
|
+
assertWatchlistResolves({
|
|
57
|
+
gate: 'lint_readme_size',
|
|
58
|
+
candidates: [README],
|
|
59
|
+
repoRoot: process.cwd(),
|
|
60
|
+
});
|
|
61
|
+
} catch (exc) {
|
|
62
|
+
if (exc instanceof DeadScopeError) {
|
|
63
|
+
// First line is the pinned py2ts wording; the second names the root.
|
|
64
|
+
process.stderr.write(`error: ${README} not found\n`);
|
|
65
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
throw exc;
|
|
51
69
|
}
|
|
52
70
|
const text = fs.readFileSync(README, 'utf-8');
|
|
53
71
|
const n = _splitlinesCount(text);
|
|
@@ -24,6 +24,8 @@ import * as os from 'node:os';
|
|
|
24
24
|
import * as path from 'node:path';
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
26
|
|
|
27
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
28
|
+
|
|
27
29
|
interface FileEntry {
|
|
28
30
|
status: string;
|
|
29
31
|
codes: Set<string>;
|
|
@@ -519,6 +521,32 @@ function main(): number {
|
|
|
519
521
|
// Sanity guard: two non-empty result sets sharing no file → not comparable.
|
|
520
522
|
const baseKeys = Object.keys(baseline_map);
|
|
521
523
|
const currKeys = Object.keys(current_map);
|
|
524
|
+
|
|
525
|
+
// The asserted unit is the WORKING TREE's linted-file population, not the
|
|
526
|
+
// baseline's. Baseline emptiness has two documented, legitimate causes (no
|
|
527
|
+
// linter file in the ref; a pre-migration `.py` baseline with no python3)
|
|
528
|
+
// and its own guard below, so asserting there would re-litigate the false
|
|
529
|
+
// reds this script already carries scars from. An empty CURRENT tree has no
|
|
530
|
+
// legitimate cause: it means the linter's corpus moved. Both guards below
|
|
531
|
+
// require `currKeys.length`, so today that case slips through all of them
|
|
532
|
+
// and every prior finding is reported as an improvement — exit 0.
|
|
533
|
+
// Exit 2 is the "runs are not comparable / could not run" code both guards
|
|
534
|
+
// already use; 1 stays "regressions found".
|
|
535
|
+
try {
|
|
536
|
+
assertScanned({
|
|
537
|
+
gate: 'lint_regression',
|
|
538
|
+
scanned: currKeys.length,
|
|
539
|
+
units: 'linted file(s) in the working tree',
|
|
540
|
+
roots: [`${root} (skill_linter --all)`],
|
|
541
|
+
});
|
|
542
|
+
} catch (e) {
|
|
543
|
+
if (e instanceof DeadScopeError) {
|
|
544
|
+
process.stderr.write(`Error: ${e.message}\n`);
|
|
545
|
+
return 2;
|
|
546
|
+
}
|
|
547
|
+
throw e;
|
|
548
|
+
}
|
|
549
|
+
|
|
522
550
|
const shared = baseKeys.some((k) => k in current_map);
|
|
523
551
|
if (baseKeys.length && currKeys.length && !shared) {
|
|
524
552
|
process.stderr.write(
|
|
@@ -20,6 +20,8 @@ import * as fs from 'node:fs';
|
|
|
20
20
|
import * as path from 'node:path';
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
22
|
|
|
23
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
24
|
+
|
|
23
25
|
const _HERE = fileURLToPath(import.meta.url);
|
|
24
26
|
const QUIET = process.argv.slice(2).includes('--quiet');
|
|
25
27
|
|
|
@@ -154,7 +156,48 @@ function _relPosix(target: string, root: string): string {
|
|
|
154
156
|
return path.relative(root, target).split(path.sep).join('/');
|
|
155
157
|
}
|
|
156
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Every `*.md` anywhere under the roadmap tree — active plus `archive/`,
|
|
161
|
+
* `later/`, `skipped/`.
|
|
162
|
+
*
|
|
163
|
+
* The active glob is the subset this gate judges, and it reaches zero
|
|
164
|
+
* legitimately once everything is archived, so it cannot double as the scope
|
|
165
|
+
* assertion. The whole tree reaching zero has only one cause: the root moved.
|
|
166
|
+
*/
|
|
167
|
+
function _countRoadmapTree(dir: string): number {
|
|
168
|
+
let entries: fs.Dirent[];
|
|
169
|
+
try {
|
|
170
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
171
|
+
} catch {
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
let n = 0;
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
n += _countRoadmapTree(path.join(dir, entry.name));
|
|
178
|
+
} else if (entry.name.endsWith('.md')) {
|
|
179
|
+
n += 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return n;
|
|
183
|
+
}
|
|
184
|
+
|
|
157
185
|
function main(): number {
|
|
186
|
+
try {
|
|
187
|
+
assertScanned({
|
|
188
|
+
gate: 'lint_roadmap_blockers',
|
|
189
|
+
scanned: _countRoadmapTree(path.join(REPO_ROOT, 'agents', 'roadmaps')),
|
|
190
|
+
units: 'roadmap file(s)',
|
|
191
|
+
roots: ['agents/roadmaps'],
|
|
192
|
+
});
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// 1 is the only failure code this gate defines.
|
|
195
|
+
if (e instanceof DeadScopeError) {
|
|
196
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
158
201
|
const roadmaps = _globRoadmaps();
|
|
159
202
|
if (roadmaps.length === 0) {
|
|
160
203
|
if (!QUIET) {
|
|
@@ -21,6 +21,7 @@ import * as path from 'node:path';
|
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
22
|
|
|
23
23
|
import { project_settings_path } from './_lib/agent_settings.js';
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
24
25
|
|
|
25
26
|
const _HERE = fileURLToPath(import.meta.url);
|
|
26
27
|
const QUIET = process.argv.slice(2).includes('--quiet');
|
|
@@ -173,6 +174,32 @@ function _relPosix(target: string, root: string): string {
|
|
|
173
174
|
return path.relative(root, target).split(path.sep).join('/');
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Every `*.md` anywhere under the roadmap tree — active plus `archive/`,
|
|
179
|
+
* `later/`, `skipped/`.
|
|
180
|
+
*
|
|
181
|
+
* The active glob is the subset this gate judges, and it reaches zero
|
|
182
|
+
* legitimately once everything is archived, so it cannot double as the scope
|
|
183
|
+
* assertion. The whole tree reaching zero has only one cause: the root moved.
|
|
184
|
+
*/
|
|
185
|
+
function _countRoadmapTree(dir: string): number {
|
|
186
|
+
let entries: fs.Dirent[];
|
|
187
|
+
try {
|
|
188
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
189
|
+
} catch {
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
let n = 0;
|
|
193
|
+
for (const entry of entries) {
|
|
194
|
+
if (entry.isDirectory()) {
|
|
195
|
+
n += _countRoadmapTree(path.join(dir, entry.name));
|
|
196
|
+
} else if (entry.name.endsWith('.md')) {
|
|
197
|
+
n += 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return n;
|
|
201
|
+
}
|
|
202
|
+
|
|
176
203
|
function main(): number {
|
|
177
204
|
if (_read_local_auto_run()) {
|
|
178
205
|
if (!QUIET) {
|
|
@@ -182,6 +209,24 @@ function main(): number {
|
|
|
182
209
|
}
|
|
183
210
|
return 0;
|
|
184
211
|
}
|
|
212
|
+
// Asserted only past the disabled-return: a declared no-op reads nothing by
|
|
213
|
+
// design, and failing there would red every repo that leaves the setting
|
|
214
|
+
// unset. Past it the gate claims to be armed, so its root must resolve.
|
|
215
|
+
try {
|
|
216
|
+
assertScanned({
|
|
217
|
+
gate: 'lint_roadmap_ci_steps',
|
|
218
|
+
scanned: _countRoadmapTree(path.join(REPO_ROOT, 'agents', 'roadmaps')),
|
|
219
|
+
units: 'roadmap file(s)',
|
|
220
|
+
roots: ['agents/roadmaps'],
|
|
221
|
+
});
|
|
222
|
+
} catch (e) {
|
|
223
|
+
// 1 is the only failure code this gate defines.
|
|
224
|
+
if (e instanceof DeadScopeError) {
|
|
225
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
throw e;
|
|
229
|
+
}
|
|
185
230
|
const roadmaps = _globRoadmaps();
|
|
186
231
|
if (roadmaps.length === 0) {
|
|
187
232
|
if (!QUIET) {
|
|
@@ -20,6 +20,8 @@ import * as path from 'node:path';
|
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
21
21
|
|
|
22
22
|
import { project_settings_path } from './_lib/agent_settings.js';
|
|
23
|
+
import { granularity_problems } from './_lib/roadmap_granularity.js';
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
23
25
|
|
|
24
26
|
/** Mirror `QUIET = "--quiet" in sys.argv` (computed at import). */
|
|
25
27
|
const QUIET = process.argv.slice(2).includes('--quiet');
|
|
@@ -184,6 +186,10 @@ function lint_roadmap(
|
|
|
184
186
|
if (complexity === 'lightweight') {
|
|
185
187
|
_check_lightweight(text, line_count, problems);
|
|
186
188
|
}
|
|
189
|
+
// Bite-sized granularity — self-gated to `complexity: structural`. Folded in
|
|
190
|
+
// here rather than shipped as its own gate; `_lib/roadmap_granularity.ts`
|
|
191
|
+
// carries the reason.
|
|
192
|
+
problems.push(...granularity_problems(text));
|
|
187
193
|
if (horizon_weeks <= 0) {
|
|
188
194
|
_check_no_plate(text, problems);
|
|
189
195
|
}
|
|
@@ -383,6 +389,21 @@ function _check_execution_mode(fm: string, problems: string[]): void {
|
|
|
383
389
|
}
|
|
384
390
|
|
|
385
391
|
function main(): number {
|
|
392
|
+
try {
|
|
393
|
+
assertScanned({
|
|
394
|
+
gate: 'lint_roadmap_complexity',
|
|
395
|
+
scanned: _countRoadmapTree(path.join(REPO_ROOT, 'agents', 'roadmaps')),
|
|
396
|
+
units: 'roadmap file(s)',
|
|
397
|
+
roots: ['agents/roadmaps'],
|
|
398
|
+
});
|
|
399
|
+
} catch (e) {
|
|
400
|
+
// 1 is the only failure code this gate defines.
|
|
401
|
+
if (e instanceof DeadScopeError) {
|
|
402
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
403
|
+
return 1;
|
|
404
|
+
}
|
|
405
|
+
throw e;
|
|
406
|
+
}
|
|
386
407
|
const roadmaps = _globRoadmaps();
|
|
387
408
|
const horizon_weeks = _read_horizon_weeks();
|
|
388
409
|
if (roadmaps.length === 0) {
|
|
@@ -477,6 +498,32 @@ function _relPosix(target: string, root: string): string {
|
|
|
477
498
|
return path.relative(root, target).split(path.sep).join('/');
|
|
478
499
|
}
|
|
479
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Every `*.md` anywhere under the roadmap tree — active plus `archive/`,
|
|
503
|
+
* `later/`, `skipped/`.
|
|
504
|
+
*
|
|
505
|
+
* The active glob is the subset this gate judges, and it reaches zero
|
|
506
|
+
* legitimately once everything is archived, so it cannot double as the scope
|
|
507
|
+
* assertion. The whole tree reaching zero has only one cause: the root moved.
|
|
508
|
+
*/
|
|
509
|
+
function _countRoadmapTree(dir: string): number {
|
|
510
|
+
let entries: fs.Dirent[];
|
|
511
|
+
try {
|
|
512
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
513
|
+
} catch {
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
let n = 0;
|
|
517
|
+
for (const entry of entries) {
|
|
518
|
+
if (entry.isDirectory()) {
|
|
519
|
+
n += _countRoadmapTree(path.join(dir, entry.name));
|
|
520
|
+
} else if (entry.name.endsWith('.md')) {
|
|
521
|
+
n += 1;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return n;
|
|
525
|
+
}
|
|
526
|
+
|
|
480
527
|
function _isCliEntry(): boolean {
|
|
481
528
|
if (process.argv[1] === undefined) {
|
|
482
529
|
return false;
|
|
@@ -44,6 +44,8 @@ import * as fs from 'node:fs';
|
|
|
44
44
|
import * as path from 'node:path';
|
|
45
45
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
46
46
|
|
|
47
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
48
|
+
|
|
47
49
|
const _HERE = fileURLToPath(import.meta.url);
|
|
48
50
|
|
|
49
51
|
// REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
@@ -313,6 +315,27 @@ function main(argv?: readonly string[]): number {
|
|
|
313
315
|
}
|
|
314
316
|
const args = parsed.args as Args;
|
|
315
317
|
|
|
318
|
+
// `check` walks the whole tree already, but returns only violations, and it
|
|
319
|
+
// opens with `if (!_isDir(root)) return []` — a moved root reads as "every
|
|
320
|
+
// blocked-for-later roadmap is parked correctly". Assert the unfiltered
|
|
321
|
+
// walk here rather than inside `check`, which is exercised against empty
|
|
322
|
+
// sandbox roots. Exit 2 is this CLI's internal-error slot; 1 stays
|
|
323
|
+
// "disposition violations found".
|
|
324
|
+
try {
|
|
325
|
+
assertScanned({
|
|
326
|
+
gate: 'lint_roadmap_later_disposition',
|
|
327
|
+
scanned: _rglobMdSorted(ROADMAP_ROOT).length,
|
|
328
|
+
units: 'roadmap file(s)',
|
|
329
|
+
roots: ['agents/roadmaps'],
|
|
330
|
+
});
|
|
331
|
+
} catch (e) {
|
|
332
|
+
if (e instanceof DeadScopeError) {
|
|
333
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
334
|
+
return 2;
|
|
335
|
+
}
|
|
336
|
+
throw e;
|
|
337
|
+
}
|
|
338
|
+
|
|
316
339
|
const violations = check(ROADMAP_ROOT);
|
|
317
340
|
|
|
318
341
|
if (args.json) {
|
|
@@ -21,6 +21,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
21
21
|
import { parse as parseYaml } from 'yaml';
|
|
22
22
|
|
|
23
23
|
import { resolve_logical, strip_source_prefix } from './_lib/agent_src.js';
|
|
24
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
24
25
|
|
|
25
26
|
const _HERE = fileURLToPath(import.meta.url);
|
|
26
27
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -125,8 +126,21 @@ function fail(errors: string[]): never {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
function _run(): number {
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
// Replaces the ad-hoc `_exists(MATRIX)` precondition: this gate's entire
|
|
130
|
+
// corpus is one named contract file, so a moved matrix is a dead scope, not
|
|
131
|
+
// a content finding. Exit 1 either way — the gate has a single failure code.
|
|
132
|
+
try {
|
|
133
|
+
assertWatchlistResolves({
|
|
134
|
+
gate: 'lint_rule_interactions',
|
|
135
|
+
candidates: [_relTo(MATRIX, ROOT)],
|
|
136
|
+
repoRoot: ROOT,
|
|
137
|
+
});
|
|
138
|
+
} catch (e) {
|
|
139
|
+
if (e instanceof DeadScopeError) {
|
|
140
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
141
|
+
throw new FailExit([e.message]);
|
|
142
|
+
}
|
|
143
|
+
throw e;
|
|
130
144
|
}
|
|
131
145
|
|
|
132
146
|
const data = parseYaml(fs.readFileSync(MATRIX, 'utf-8'), { version: '1.1' });
|
|
@@ -19,9 +19,12 @@ import fs from "node:fs";
|
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
21
|
import { artefact_roots } from "./_lib/agent_src.js";
|
|
22
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
22
23
|
|
|
23
24
|
const QUIET = process.argv.includes("--quiet");
|
|
24
25
|
|
|
26
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
27
|
+
|
|
25
28
|
// Rules live under every artefact root post-monorepo Phase 4.
|
|
26
29
|
function RULES_DIRS(): string[] {
|
|
27
30
|
return artefact_roots()
|
|
@@ -106,12 +109,22 @@ export function main(): number {
|
|
|
106
109
|
rules.push(...globMd(rulesDir));
|
|
107
110
|
}
|
|
108
111
|
rules.sort();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
// Supersedes the previous bare `rules.length === 0` message: same exit code
|
|
113
|
+
// (1 — the only failure this CLI contract defines), but the shared assertion
|
|
114
|
+
// names the roots and says the scope is dead rather than merely "no rules".
|
|
115
|
+
try {
|
|
116
|
+
assertScanned({
|
|
117
|
+
gate: "lint_rule_tiers",
|
|
118
|
+
scanned: rules.length,
|
|
119
|
+
units: "rule file(s)",
|
|
120
|
+
roots: artefact_roots().map((r) => `${path.relative(REPO_ROOT, r)}/rules`),
|
|
121
|
+
});
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (e instanceof DeadScopeError) {
|
|
124
|
+
process.stderr.write(`${e.message}\n`);
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
throw e;
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
const missing: string[] = [];
|