@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 path from 'node:path';
|
|
|
19
19
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
20
|
import { parse as parseYaml } from 'yaml';
|
|
21
21
|
|
|
22
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
23
|
+
|
|
22
24
|
const _HERE = path.resolve(fileURLToPath(import.meta.url));
|
|
23
25
|
// REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
24
26
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -179,6 +181,17 @@ function lint(quiet: boolean): number {
|
|
|
179
181
|
const workspaces = _load(WORKSPACES_YML);
|
|
180
182
|
const packs = _load(PACKS_YML);
|
|
181
183
|
|
|
184
|
+
// `_load` accepts an empty YAML list, so two present-but-emptied files read
|
|
185
|
+
// as a vocabulary of nothing. Assert the entries actually loaded — the ADR
|
|
186
|
+
// parity check below would report that as missing ids, which describes the
|
|
187
|
+
// symptom and not the cause.
|
|
188
|
+
assertScanned({
|
|
189
|
+
gate: 'lint_discovery_vocabulary',
|
|
190
|
+
scanned: workspaces.length + packs.length,
|
|
191
|
+
units: 'vocabulary entr(y/ies)',
|
|
192
|
+
roots: [_relToRoot(WORKSPACES_YML), _relToRoot(PACKS_YML)],
|
|
193
|
+
});
|
|
194
|
+
|
|
182
195
|
const ws_ids = new Set<unknown>(workspaces.map((entry) => entry['id']));
|
|
183
196
|
const pack_ids = new Set<unknown>(packs.map((entry) => entry['id']));
|
|
184
197
|
|
|
@@ -373,6 +386,12 @@ function main(): number {
|
|
|
373
386
|
process.stderr.write(`${err.detail}\n`);
|
|
374
387
|
return 1;
|
|
375
388
|
}
|
|
389
|
+
if (err instanceof DeadScopeError) {
|
|
390
|
+
// Same code as a missing vocabulary file (1) — the gate has one
|
|
391
|
+
// failure code, and 2 belongs to the pinned YAML-loader contract.
|
|
392
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
393
|
+
return 1;
|
|
394
|
+
}
|
|
376
395
|
throw err;
|
|
377
396
|
}
|
|
378
397
|
}
|
|
@@ -32,6 +32,8 @@ import * as path from 'node:path';
|
|
|
32
32
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
33
33
|
import { load as yamlLoad } from 'js-yaml';
|
|
34
34
|
|
|
35
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
36
|
+
|
|
35
37
|
// NOTE: deliberately no runtime module dependency on the CLI registry —
|
|
36
38
|
// src/cli/ is outside the package.json `files` whitelist, so importing it
|
|
37
39
|
// would crash a global install (prepack-check). The registry file is parsed
|
|
@@ -625,6 +627,27 @@ function main(): number {
|
|
|
625
627
|
const scanTrees: ScanTree[] = args.scanRoots.length
|
|
626
628
|
? args.scanRoots.map((dir) => ({ dir, match: (n: string) => n.endsWith('.md') }))
|
|
627
629
|
: [...DEFAULT_SCAN_TREES];
|
|
630
|
+
// `_collectTargets` skips a scan tree whose dir is absent, so a renamed
|
|
631
|
+
// src/skills (or a --scan-root typo) yields zero targets and the run prints
|
|
632
|
+
// "Every documented package command resolves" having opened no doc. Exit 3
|
|
633
|
+
// matches the scan-failure code below: no targets means the linter did not
|
|
634
|
+
// run, which is not "0 violations" (0).
|
|
635
|
+
const targets = _collectTargets(args.root, scanTrees).length;
|
|
636
|
+
try {
|
|
637
|
+
assertScanned({
|
|
638
|
+
gate: 'lint_documented_commands',
|
|
639
|
+
scanned: targets,
|
|
640
|
+
units: 'doc file(s)',
|
|
641
|
+
roots: scanTrees.map((t) => t.dir),
|
|
642
|
+
});
|
|
643
|
+
} catch (exc) {
|
|
644
|
+
if (exc instanceof DeadScopeError) {
|
|
645
|
+
process.stderr.write(`${exc.message}\n`);
|
|
646
|
+
return 3;
|
|
647
|
+
}
|
|
648
|
+
throw exc;
|
|
649
|
+
}
|
|
650
|
+
|
|
628
651
|
let violations: Violation[];
|
|
629
652
|
try {
|
|
630
653
|
violations = scan(args.root, scanTrees);
|
|
@@ -15,6 +15,8 @@ import * as fs from 'node:fs';
|
|
|
15
15
|
import * as path from 'node:path';
|
|
16
16
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
17
17
|
|
|
18
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
19
|
+
|
|
18
20
|
/** Mirror `QUIET = "--quiet" in sys.argv` (computed at import). */
|
|
19
21
|
const QUIET = process.argv.slice(2).includes('--quiet');
|
|
20
22
|
|
|
@@ -115,6 +117,27 @@ function find_empty_roadmaps(root: string): string[] {
|
|
|
115
117
|
|
|
116
118
|
function main(): number {
|
|
117
119
|
const root = _repo_root();
|
|
120
|
+
// The scanned unit is every roadmap `*.md`, never the empty ones: this gate
|
|
121
|
+
// reports an ABSENCE, so "no empty roadmaps" and "no roadmaps at all" print
|
|
122
|
+
// the same green line. `_repo_root()` falls back to CWD when it finds no
|
|
123
|
+
// `agents/roadmaps` anywhere up the chain, which is exactly how a moved tree
|
|
124
|
+
// reaches zero silently.
|
|
125
|
+
const base = path.join(root, ROADMAP_DIR);
|
|
126
|
+
try {
|
|
127
|
+
assertScanned({
|
|
128
|
+
gate: 'lint_empty_roadmaps',
|
|
129
|
+
scanned: _isDir(base) ? _rglobMdSorted(base).length : 0,
|
|
130
|
+
units: 'roadmap file(s)',
|
|
131
|
+
roots: [ROADMAP_DIR],
|
|
132
|
+
});
|
|
133
|
+
} catch (exc) {
|
|
134
|
+
if (exc instanceof DeadScopeError) {
|
|
135
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
throw exc;
|
|
139
|
+
}
|
|
140
|
+
|
|
118
141
|
const empties = find_empty_roadmaps(root);
|
|
119
142
|
|
|
120
143
|
if (empties.length === 0) {
|
|
@@ -19,6 +19,8 @@ import * as fs from 'node:fs';
|
|
|
19
19
|
import * as path from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
21
|
|
|
22
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
23
|
+
|
|
22
24
|
const QUIET = process.argv.includes('--quiet');
|
|
23
25
|
|
|
24
26
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -79,6 +81,23 @@ function main(): number {
|
|
|
79
81
|
for (const root of SCAN_ROOTS) {
|
|
80
82
|
walk(root, files);
|
|
81
83
|
}
|
|
84
|
+
// The citation haystack is the scanned unit. A vanished SCAN_ROOTS tree
|
|
85
|
+
// still fails — as "N of N uncited" — but names the fixtures as the
|
|
86
|
+
// culprit; asserting the walk here reports the real cause instead.
|
|
87
|
+
try {
|
|
88
|
+
assertScanned({
|
|
89
|
+
gate: 'lint_eval_fixture_citations',
|
|
90
|
+
scanned: files.length,
|
|
91
|
+
units: 'citation-surface file(s)',
|
|
92
|
+
roots: SCAN_ROOTS.map((r) => path.relative(REPO, r)),
|
|
93
|
+
});
|
|
94
|
+
} catch (exc) {
|
|
95
|
+
if (exc instanceof DeadScopeError) {
|
|
96
|
+
process.stderr.write(`${exc.message}\n`);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
throw exc;
|
|
100
|
+
}
|
|
82
101
|
const haystack = files
|
|
83
102
|
.map((f) => {
|
|
84
103
|
try {
|
|
@@ -40,6 +40,8 @@ import * as fs from 'node:fs';
|
|
|
40
40
|
import * as path from 'node:path';
|
|
41
41
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
42
42
|
|
|
43
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
44
|
+
|
|
43
45
|
const _HERE = fileURLToPath(import.meta.url);
|
|
44
46
|
|
|
45
47
|
// REPO = Path(__file__).resolve().parents[2]
|
|
@@ -119,6 +121,19 @@ function _manifestPaths(): string[] {
|
|
|
119
121
|
return out;
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
/** Immediate `src/skills/*` directories — the population `_manifestPaths` filters. */
|
|
125
|
+
function _skillDirs(): string[] {
|
|
126
|
+
let entries: fs.Dirent[];
|
|
127
|
+
try {
|
|
128
|
+
entries = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
return entries
|
|
133
|
+
.map((ent) => path.join(SKILLS_DIR, ent.name))
|
|
134
|
+
.filter((p) => _isDirPath(p));
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
function _isDirPath(p: string): boolean {
|
|
123
138
|
try {
|
|
124
139
|
return fs.statSync(p).isDirectory();
|
|
@@ -309,6 +324,26 @@ function main(argv?: readonly string[]): number {
|
|
|
309
324
|
}
|
|
310
325
|
const args = parsed.args as Args;
|
|
311
326
|
|
|
327
|
+
// The scanned unit is the skill directory, not the corpus manifests it
|
|
328
|
+
// filters down to: a skill without `data/manifest.json` is legitimately out
|
|
329
|
+
// of scope, so a zero-manifest count cannot be told from a healthy tree.
|
|
330
|
+
// Zero skill directories can — and `check()` returns clean over a missing
|
|
331
|
+
// SKILLS_DIR, which is the ✅ line printed over nothing.
|
|
332
|
+
try {
|
|
333
|
+
assertScanned({
|
|
334
|
+
gate: 'lint_eval_freshness',
|
|
335
|
+
scanned: _skillDirs().length,
|
|
336
|
+
units: 'skill director(ies)',
|
|
337
|
+
roots: [path.relative(REPO, SKILLS_DIR)],
|
|
338
|
+
});
|
|
339
|
+
} catch (exc) {
|
|
340
|
+
if (exc instanceof DeadScopeError) {
|
|
341
|
+
process.stderr.write(`${exc.message}\n`);
|
|
342
|
+
return 1;
|
|
343
|
+
}
|
|
344
|
+
throw exc;
|
|
345
|
+
}
|
|
346
|
+
|
|
312
347
|
const errors = check();
|
|
313
348
|
if (errors.length) {
|
|
314
349
|
process.stderr.write('eval-freshness: corpus-backed skills missing a current last_eval:\n');
|
|
@@ -23,6 +23,8 @@ import * as fs from 'node:fs';
|
|
|
23
23
|
import * as path from 'node:path';
|
|
24
24
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
25
25
|
|
|
26
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
27
|
+
|
|
26
28
|
const _HERE = fileURLToPath(import.meta.url);
|
|
27
29
|
// Path(__file__).resolve().parent.parent.parent — three dirs up from the file.
|
|
28
30
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -145,9 +147,22 @@ function _pyRepr(s: string): string {
|
|
|
145
147
|
|
|
146
148
|
export function main(): number {
|
|
147
149
|
const demos = _sorted_demos(REPO_ROOT);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
// Supersedes the bare `demos.length === 0 → exit 1` check: same exit code,
|
|
151
|
+
// but the message names the root, so a moved demo directory reads as a dead
|
|
152
|
+
// scope instead of "the glob matched nothing".
|
|
153
|
+
try {
|
|
154
|
+
assertScanned({
|
|
155
|
+
gate: 'lint_examples',
|
|
156
|
+
scanned: demos.length,
|
|
157
|
+
units: 'demo file(s)',
|
|
158
|
+
roots: [DEMO_GLOB],
|
|
159
|
+
});
|
|
160
|
+
} catch (exc) {
|
|
161
|
+
if (exc instanceof DeadScopeError) {
|
|
162
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
165
|
+
throw exc;
|
|
151
166
|
}
|
|
152
167
|
let failed = 0;
|
|
153
168
|
for (const demo of demos) {
|
|
@@ -32,6 +32,8 @@ 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 { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
36
|
+
|
|
35
37
|
const _HERE = fileURLToPath(import.meta.url);
|
|
36
38
|
|
|
37
39
|
function _repo_root(): string {
|
|
@@ -344,6 +346,27 @@ function main(argv?: readonly string[]): number {
|
|
|
344
346
|
const opts = parse_args(argv ?? process.argv.slice(2));
|
|
345
347
|
|
|
346
348
|
const schema = _load_schema();
|
|
349
|
+
// The payload is caller-supplied, so the gate's own scope is the schema —
|
|
350
|
+
// and `_load_schema` only proves the FILE is there. A schema that parses to
|
|
351
|
+
// an empty (or property-less) document validates every payload silently and
|
|
352
|
+
// prints `✅ explain-trace OK`. Count the property rules that will actually
|
|
353
|
+
// be applied; the keyword filtering below is judgement, this is reach.
|
|
354
|
+
try {
|
|
355
|
+
assertScanned({
|
|
356
|
+
gate: 'lint_explain_trace',
|
|
357
|
+
scanned: Object.keys(_isObject(schema['properties']) ? schema['properties'] : {}).length,
|
|
358
|
+
units: 'schema property rule(s)',
|
|
359
|
+
roots: ['docs/contracts/explain-trace.schema.json'],
|
|
360
|
+
});
|
|
361
|
+
} catch (exc) {
|
|
362
|
+
if (exc instanceof DeadScopeError) {
|
|
363
|
+
// 2 = invocation error — the same code a missing schema returns.
|
|
364
|
+
// 1 would claim the payload is invalid, which is not what happened.
|
|
365
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
366
|
+
throw new SystemExit(2);
|
|
367
|
+
}
|
|
368
|
+
throw exc;
|
|
369
|
+
}
|
|
347
370
|
// Python: jsonschema.Draft202012Validator.check_schema(schema) — the
|
|
348
371
|
// explain-trace schema is a valid Draft-2020-12 document, so this never
|
|
349
372
|
// raises in practice; the subset validator has no schema-validation step.
|
|
@@ -21,6 +21,8 @@ import * as fs from 'node:fs';
|
|
|
21
21
|
import * as path from 'node:path';
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
23
|
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
25
|
+
|
|
24
26
|
const _HERE = fileURLToPath(import.meta.url);
|
|
25
27
|
|
|
26
28
|
// CWD-relative, exactly as the Python module's Path(...) literals.
|
|
@@ -132,9 +134,32 @@ function main(argv: readonly string[]): number {
|
|
|
132
134
|
const [skills, commands, packs] = manifest_names(manifest);
|
|
133
135
|
const body = fs.readFileSync(DOC, 'utf-8');
|
|
134
136
|
|
|
137
|
+
// The doc existing is not the doc being readable by this gate: LINK_RE is
|
|
138
|
+
// pinned to the `../dist/agent-src/{skills,commands}/…` link shape, so a
|
|
139
|
+
// restructured or emptied Featured Skills page yields zero entries and the
|
|
140
|
+
// OK line reports "0 artefact entries validated". Assert the raw match list,
|
|
141
|
+
// before the dedupe the checks below run on.
|
|
142
|
+
const links = _findLinks(body);
|
|
143
|
+
try {
|
|
144
|
+
assertScanned({
|
|
145
|
+
gate: 'lint_featured_skills',
|
|
146
|
+
scanned: links.length,
|
|
147
|
+
units: 'featured artefact link(s)',
|
|
148
|
+
roots: [DOC],
|
|
149
|
+
});
|
|
150
|
+
} catch (exc) {
|
|
151
|
+
if (exc instanceof DeadScopeError) {
|
|
152
|
+
// 1 is the gate's only failure code — the same one a missing DOC or
|
|
153
|
+
// manifest already returns.
|
|
154
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
155
|
+
return 1;
|
|
156
|
+
}
|
|
157
|
+
throw exc;
|
|
158
|
+
}
|
|
159
|
+
|
|
135
160
|
const missing: string[] = [];
|
|
136
161
|
const seen = new Set<string>();
|
|
137
|
-
for (const [cat, raw] of
|
|
162
|
+
for (const [cat, raw] of links) {
|
|
138
163
|
const slug = slug_from_path(cat, raw);
|
|
139
164
|
const key = `${cat}\0${slug}`;
|
|
140
165
|
if (seen.has(key)) {
|
|
@@ -41,6 +41,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
41
41
|
import YAML from 'yaml';
|
|
42
42
|
|
|
43
43
|
import { resolve_logical } from './_lib/agent_src.js';
|
|
44
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
44
45
|
|
|
45
46
|
const _HERE = fileURLToPath(import.meta.url);
|
|
46
47
|
// Path(__file__).resolve().parents[2] — repo root, two dirs up from src/scripts.
|
|
@@ -647,6 +648,24 @@ export function main(argv?: string[]): number {
|
|
|
647
648
|
return 3;
|
|
648
649
|
}
|
|
649
650
|
|
|
651
|
+
// Exit 3, not 1: an empty flows dir is the same class of failure as the
|
|
652
|
+
// `flows dir not found` / `schema not found` short-circuits above (the gate
|
|
653
|
+
// could not do its job), not a flow that violates the schema.
|
|
654
|
+
try {
|
|
655
|
+
assertScanned({
|
|
656
|
+
gate: 'lint_flows',
|
|
657
|
+
scanned: files.length,
|
|
658
|
+
units: 'flow file(s)',
|
|
659
|
+
roots: ['src/flows'],
|
|
660
|
+
});
|
|
661
|
+
} catch (exc) {
|
|
662
|
+
if (exc instanceof DeadScopeError) {
|
|
663
|
+
process.stderr.write(`${exc.message}\n`);
|
|
664
|
+
return 3;
|
|
665
|
+
}
|
|
666
|
+
throw exc;
|
|
667
|
+
}
|
|
668
|
+
|
|
650
669
|
if (vios.length > 0) {
|
|
651
670
|
process.stderr.write(`lint_flows: ${vios.length} violation(s):\n`);
|
|
652
671
|
for (const v of vios) {
|
|
@@ -27,6 +27,7 @@ import * as path from 'node:path';
|
|
|
27
27
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
28
28
|
|
|
29
29
|
import { artefact_roots } from './_lib/agent_src.js';
|
|
30
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
30
31
|
import {
|
|
31
32
|
load_schema,
|
|
32
33
|
parse_frontmatter,
|
|
@@ -303,6 +304,25 @@ function main(argv: readonly string[]): number {
|
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
306
|
|
|
307
|
+
// `_iter` skips a category whose base directory is absent, so an
|
|
308
|
+
// `artefact_roots()` that resolves to nothing (or four renamed category
|
|
309
|
+
// subdirs) yields no files at all and the gate reports "0 artefact(s)
|
|
310
|
+
// clean" — clean being exactly what it cannot know.
|
|
311
|
+
try {
|
|
312
|
+
assertScanned({
|
|
313
|
+
gate: 'lint_frontmatter_boilerplate',
|
|
314
|
+
scanned: total,
|
|
315
|
+
units: 'artefact(s)',
|
|
316
|
+
roots: artefact_roots().map((r) => path.relative(ROOT, r).split(path.sep).join('/') || '.'),
|
|
317
|
+
});
|
|
318
|
+
} catch (exc) {
|
|
319
|
+
if (exc instanceof DeadScopeError) {
|
|
320
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
321
|
+
return 1;
|
|
322
|
+
}
|
|
323
|
+
throw exc;
|
|
324
|
+
}
|
|
325
|
+
|
|
306
326
|
if (offenders > 0) {
|
|
307
327
|
process.stderr.write(
|
|
308
328
|
`\n== frontmatter-boilerplate: ${offenders}/${total} artefact(s) carry a ` +
|
|
@@ -28,6 +28,8 @@ import * as path from 'node:path';
|
|
|
28
28
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
29
29
|
import YAML from 'yaml';
|
|
30
30
|
|
|
31
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
32
|
+
|
|
31
33
|
const _HERE = fileURLToPath(import.meta.url);
|
|
32
34
|
const QUIET = process.argv.slice(2).includes('--quiet');
|
|
33
35
|
|
|
@@ -294,6 +296,30 @@ function main(): number {
|
|
|
294
296
|
const cons_errors = lint_consumer_side();
|
|
295
297
|
const errors = [...pkg_errors, ...cons_errors];
|
|
296
298
|
|
|
299
|
+
// Both tiers together are the corpus. The consumer tier is legitimately
|
|
300
|
+
// empty in the package repo, so counting it alone would read a vanished
|
|
301
|
+
// package-fixture dir as "no profiles to lint" and stay green.
|
|
302
|
+
const pkg_count = _exists(PACKAGE_DIR)
|
|
303
|
+
? _globMdSorted(PACKAGE_DIR).filter((p) => !EXEMPT_STEMS.has(_stem(p))).length
|
|
304
|
+
: 0;
|
|
305
|
+
const cons_count = _exists(CONSUMER_DIR)
|
|
306
|
+
? _globMdSorted(CONSUMER_DIR).filter((p) => !EXEMPT_STEMS.has(_stem(p))).length
|
|
307
|
+
: 0;
|
|
308
|
+
try {
|
|
309
|
+
assertScanned({
|
|
310
|
+
gate: 'lint_ghostwriter_source',
|
|
311
|
+
scanned: pkg_count + cons_count,
|
|
312
|
+
units: 'profile(s)',
|
|
313
|
+
roots: ['src/agent-src/ghostwriter', 'agents/ghostwriter'],
|
|
314
|
+
});
|
|
315
|
+
} catch (exc) {
|
|
316
|
+
if (exc instanceof DeadScopeError) {
|
|
317
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
318
|
+
return 1;
|
|
319
|
+
}
|
|
320
|
+
throw exc;
|
|
321
|
+
}
|
|
322
|
+
|
|
297
323
|
if (errors.length) {
|
|
298
324
|
process.stderr.write(`❌ lint_ghostwriter_source: ${errors.length} violation(s)\n`);
|
|
299
325
|
for (const line of errors) {
|
|
@@ -304,12 +330,6 @@ function main(): number {
|
|
|
304
330
|
}
|
|
305
331
|
|
|
306
332
|
if (!QUIET) {
|
|
307
|
-
const pkg_count = _exists(PACKAGE_DIR)
|
|
308
|
-
? _globMdSorted(PACKAGE_DIR).filter((p) => !EXEMPT_STEMS.has(_stem(p))).length
|
|
309
|
-
: 0;
|
|
310
|
-
const cons_count = _exists(CONSUMER_DIR)
|
|
311
|
-
? _globMdSorted(CONSUMER_DIR).filter((p) => !EXEMPT_STEMS.has(_stem(p))).length
|
|
312
|
-
: 0;
|
|
313
333
|
process.stdout.write(
|
|
314
334
|
`✅ lint_ghostwriter_source: ${pkg_count} package fixture(s), ` +
|
|
315
335
|
`${cons_count} consumer profile(s), all compliant\n`,
|
|
@@ -18,6 +18,8 @@ import * as fs from 'node:fs';
|
|
|
18
18
|
import * as path from 'node:path';
|
|
19
19
|
import { fileURLToPath } 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
|
const REPO = path.resolve(path.dirname(_HERE), '..', '..');
|
|
23
25
|
const GLAMA_DIR = path.join(REPO, 'internal', 'glama');
|
|
@@ -44,6 +46,28 @@ function _canonicalCommand(scriptPath: string, stripPrefix?: string): string {
|
|
|
44
46
|
|
|
45
47
|
function main(): number {
|
|
46
48
|
const readmePath = path.join(GLAMA_DIR, 'README.md');
|
|
49
|
+
|
|
50
|
+
// No corpus — the gate guards three named files. If internal/glama/ moves,
|
|
51
|
+
// every read below throws rather than reporting; name the scope instead so
|
|
52
|
+
// the failure says which paths went missing.
|
|
53
|
+
try {
|
|
54
|
+
assertWatchlistResolves({
|
|
55
|
+
gate: 'lint_glama_drift',
|
|
56
|
+
candidates: [
|
|
57
|
+
path.posix.join('internal', 'glama', 'README.md'),
|
|
58
|
+
...SCRIPTS.map((s) => path.posix.join('internal', 'glama', s.file)),
|
|
59
|
+
],
|
|
60
|
+
repoRoot: REPO,
|
|
61
|
+
});
|
|
62
|
+
} catch (exc) {
|
|
63
|
+
if (exc instanceof DeadScopeError) {
|
|
64
|
+
// 1 is the gate's only failure code — drift and "cannot run" share it.
|
|
65
|
+
console.error(`❌ ${exc.message}`);
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
throw exc;
|
|
69
|
+
}
|
|
70
|
+
|
|
47
71
|
const readme = fs.readFileSync(readmePath, 'utf8');
|
|
48
72
|
|
|
49
73
|
let failed = false;
|
|
@@ -21,6 +21,8 @@ import os from "node:os";
|
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
23
|
|
|
24
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
25
|
+
|
|
24
26
|
const DEFAULT_POLICY = path.join(
|
|
25
27
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
26
28
|
"expected_perms.json",
|
|
@@ -171,7 +173,29 @@ export function lint(policyPath: string, quiet = false): number {
|
|
|
171
173
|
const policyObj: PolicyObject = isObject(policy) ? policy : {};
|
|
172
174
|
const findings: string[] = [];
|
|
173
175
|
|
|
176
|
+
// The unit is the POLICY rule, not the audited path: the global install tree
|
|
177
|
+
// is machine-state and is legitimately absent (CI, a fresh clone), so a count
|
|
178
|
+
// of audited paths would have to be `allowEmpty` and assert nothing. An empty
|
|
179
|
+
// policy is the real dead scope — it audits nothing while exiting 0. Exit 2
|
|
180
|
+
// matches the other policy-load failures above.
|
|
174
181
|
const rootSpecRaw = policyObj["global_root"];
|
|
182
|
+
try {
|
|
183
|
+
assertScanned({
|
|
184
|
+
gate: "lint_global_paths",
|
|
185
|
+
scanned:
|
|
186
|
+
(isObject(rootSpecRaw) ? 1 : 0) +
|
|
187
|
+
asArray(policyObj["files"]).length +
|
|
188
|
+
asArray(policyObj["directories"]).length,
|
|
189
|
+
units: "policy rule(s)",
|
|
190
|
+
roots: [policyPath],
|
|
191
|
+
});
|
|
192
|
+
} catch (exc) {
|
|
193
|
+
if (exc instanceof DeadScopeError) {
|
|
194
|
+
process.stderr.write(`error: ${exc.message}\n`);
|
|
195
|
+
return 2;
|
|
196
|
+
}
|
|
197
|
+
throw exc;
|
|
198
|
+
}
|
|
175
199
|
const rootSpec: PolicyObject = isObject(rootSpecRaw) ? rootSpecRaw : {};
|
|
176
200
|
const rootPath = _expand(asString(rootSpec["path"], "~/.event4u/agent-config"));
|
|
177
201
|
if (_exists(rootPath)) {
|
|
@@ -46,6 +46,8 @@ import fs from "node:fs";
|
|
|
46
46
|
import path from "node:path";
|
|
47
47
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
48
48
|
|
|
49
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
50
|
+
|
|
49
51
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
50
52
|
export const REPO_ROOT = path.resolve(_HERE, "..", "..");
|
|
51
53
|
export const SCRIPTS_DIR = path.join(REPO_ROOT, "src", "scripts");
|
|
@@ -288,6 +290,24 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
288
290
|
const jsonMode = argv.includes("--json");
|
|
289
291
|
const quiet = argv.includes("--quiet");
|
|
290
292
|
|
|
293
|
+
// The asserted unit is every eligible `.ts` file the walk yields, never the
|
|
294
|
+
// finding count: an empty findings list IS the pass state here, so a moved
|
|
295
|
+
// SCRIPTS_DIR would read as "clean" instead of "read nothing".
|
|
296
|
+
try {
|
|
297
|
+
assertScanned({
|
|
298
|
+
gate: "lint_governed_writes",
|
|
299
|
+
scanned: [...walkTsFiles(SCRIPTS_DIR, SCRIPTS_DIR)].length,
|
|
300
|
+
units: "script file(s)",
|
|
301
|
+
roots: ["src/scripts"],
|
|
302
|
+
});
|
|
303
|
+
} catch (exc) {
|
|
304
|
+
if (exc instanceof DeadScopeError) {
|
|
305
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
306
|
+
return 2;
|
|
307
|
+
}
|
|
308
|
+
throw exc;
|
|
309
|
+
}
|
|
310
|
+
|
|
291
311
|
const findings = scanScriptsDir(SCRIPTS_DIR);
|
|
292
312
|
|
|
293
313
|
if (jsonMode) {
|
|
@@ -34,6 +34,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
34
34
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
35
35
|
|
|
36
36
|
import * as sl from './_lib/security_lint.js';
|
|
37
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
37
38
|
|
|
38
39
|
export const CHECK = 'hidden-unicode';
|
|
39
40
|
|
|
@@ -499,7 +500,9 @@ export function main(argv: readonly string[] | null = null): number {
|
|
|
499
500
|
|
|
500
501
|
const findings: sl.Finding[] = [];
|
|
501
502
|
const flagged = new Set<string>();
|
|
503
|
+
let corpusFiles = 0;
|
|
502
504
|
for (const sf of sl.iter_corpus()) {
|
|
505
|
+
corpusFiles += 1;
|
|
503
506
|
const hits = _scan(sf);
|
|
504
507
|
for (const h of hits) {
|
|
505
508
|
findings.push(h);
|
|
@@ -509,6 +512,28 @@ export function main(argv: readonly string[] | null = null): number {
|
|
|
509
512
|
}
|
|
510
513
|
}
|
|
511
514
|
|
|
515
|
+
// `iter_corpus` skips a root that does not exist, so a moved corpus yields
|
|
516
|
+
// zero files and this security lint reports "clean" — and the umbrella
|
|
517
|
+
// aggregate reads that as 0 blocking findings. Count files READ, not
|
|
518
|
+
// findings. The second (source-bytes) pass states its own file count below
|
|
519
|
+
// and is separately visible, so this assertion guards the `.md` corpus.
|
|
520
|
+
// Exit 1 is this CLI's only failure code (2 is the pinned argparse usage
|
|
521
|
+
// error), so it carries "could not run" here.
|
|
522
|
+
try {
|
|
523
|
+
assertScanned({
|
|
524
|
+
gate: 'lint_hidden_unicode',
|
|
525
|
+
scanned: corpusFiles,
|
|
526
|
+
units: 'corpus file(s)',
|
|
527
|
+
roots: sl.DEFAULT_SCAN_ROOTS,
|
|
528
|
+
});
|
|
529
|
+
} catch (exc) {
|
|
530
|
+
if (exc instanceof DeadScopeError) {
|
|
531
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
532
|
+
return 1;
|
|
533
|
+
}
|
|
534
|
+
throw exc;
|
|
535
|
+
}
|
|
536
|
+
|
|
512
537
|
// Second pass — raw control bytes in text-intended SOURCE files, which the
|
|
513
538
|
// `.md` corpus above does not cover. Deliberately not folded into
|
|
514
539
|
// `iter_corpus`: different scope (all tracked text files, not the `.md`
|