@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
|
@@ -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();
|
|
@@ -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) {
|