@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,7 @@ import * as path from 'node:path';
|
|
|
19
19
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
20
|
|
|
21
21
|
import { SRC_AGENT } from './_lib/agent_src.js';
|
|
22
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
22
23
|
|
|
23
24
|
const _HERE = fileURLToPath(import.meta.url);
|
|
24
25
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -253,6 +254,25 @@ function _fileSize(p: string): number {
|
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
function main(): number {
|
|
257
|
+
// No corpus walk — the contract binds two named files, so the watch list IS
|
|
258
|
+
// the scope. `SRC_AGENT()` has already moved once (ADR-051); if it moves
|
|
259
|
+
// again the per-target `not found` errors say nothing about the root.
|
|
260
|
+
try {
|
|
261
|
+
assertWatchlistResolves({
|
|
262
|
+
gate: 'lint_agents_md',
|
|
263
|
+
candidates: TARGETS.map((t) => _relPosix(t.path, ROOT)),
|
|
264
|
+
repoRoot: ROOT,
|
|
265
|
+
});
|
|
266
|
+
} catch (exc) {
|
|
267
|
+
if (exc instanceof DeadScopeError) {
|
|
268
|
+
// 1 is the gate's only failure code — a missing target already
|
|
269
|
+
// returns it, so "could not run at all" cannot be louder than that.
|
|
270
|
+
process.stdout.write(` ❌ ${exc.message}\n`);
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
throw exc;
|
|
274
|
+
}
|
|
275
|
+
|
|
256
276
|
let rc = 0;
|
|
257
277
|
for (const t of TARGETS) {
|
|
258
278
|
const [ok, errors, warnings] = lint_file(t);
|
|
@@ -30,6 +30,7 @@ import * as path from 'node:path';
|
|
|
30
30
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
31
31
|
|
|
32
32
|
import { artefact_roots } from './_lib/agent_src.js';
|
|
33
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
33
34
|
|
|
34
35
|
const _HERE = fileURLToPath(import.meta.url);
|
|
35
36
|
|
|
@@ -40,9 +41,8 @@ const REPO = path.resolve(path.dirname(_HERE), '..', '..');
|
|
|
40
41
|
const ARCHIVE_DIR = path.join(REPO, 'agents', 'evidence', 'archived-skills');
|
|
41
42
|
|
|
42
43
|
// Live skill directories live under every artefact root.
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
.filter((d) => _isDir(d));
|
|
44
|
+
const ALL_SKILLS_DIRS = artefact_roots().map((root) => path.join(root, 'skills'));
|
|
45
|
+
const SKILLS_DIRS = ALL_SKILLS_DIRS.filter((d) => _isDir(d));
|
|
46
46
|
|
|
47
47
|
const REQUIRED_FIELDS = [
|
|
48
48
|
'slug',
|
|
@@ -128,6 +128,27 @@ function main(): number {
|
|
|
128
128
|
|
|
129
129
|
const notes = archived_slugs();
|
|
130
130
|
const live = live_skill_slugs();
|
|
131
|
+
|
|
132
|
+
// The scanned unit is the LIVE skills tree, not the archive notes. Zero
|
|
133
|
+
// notes is a real success state (nothing has been archived yet), but three
|
|
134
|
+
// of the five contract checks — replacement-resolves, no-zombie, and the
|
|
135
|
+
// replaced_by cross-check — are decided entirely by `live`, so an empty or
|
|
136
|
+
// moved skills root silently answers "no zombies, all replacements fine".
|
|
137
|
+
try {
|
|
138
|
+
assertScanned({
|
|
139
|
+
gate: 'lint_archived_skills',
|
|
140
|
+
scanned: live.size,
|
|
141
|
+
units: 'live skill(s)',
|
|
142
|
+
roots: ALL_SKILLS_DIRS.map((d) => path.relative(REPO, d)),
|
|
143
|
+
});
|
|
144
|
+
} catch (exc) {
|
|
145
|
+
if (exc instanceof DeadScopeError) {
|
|
146
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
throw exc;
|
|
150
|
+
}
|
|
151
|
+
|
|
131
152
|
const errors: string[] = [];
|
|
132
153
|
|
|
133
154
|
const archivedKeys = new Set<string>();
|
|
@@ -31,6 +31,8 @@ import * as fs from 'node:fs';
|
|
|
31
31
|
import * as path from 'node:path';
|
|
32
32
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
33
33
|
|
|
34
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
35
|
+
|
|
34
36
|
const _HERE = fileURLToPath(import.meta.url);
|
|
35
37
|
export const REPO = path.resolve(path.dirname(_HERE), '..', '..');
|
|
36
38
|
let SKILLS_DIR = path.join(REPO, 'src', 'skills');
|
|
@@ -57,20 +59,28 @@ export interface FreshnessResult {
|
|
|
57
59
|
ok: boolean;
|
|
58
60
|
stale: Array<{ skill: string; pinned: string; current: string }>;
|
|
59
61
|
inScope: number;
|
|
62
|
+
/**
|
|
63
|
+
* Skill directories walked. `inScope` counts only the PINNED subset, which
|
|
64
|
+
* is legitimately 0 today (nothing has opted in yet) — so it can never
|
|
65
|
+
* anchor the scope assertion; this can.
|
|
66
|
+
*/
|
|
67
|
+
scanned: number;
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
export function checkFreshness(): FreshnessResult {
|
|
63
71
|
const stale: FreshnessResult['stale'] = [];
|
|
64
72
|
let inScope = 0;
|
|
73
|
+
let scanned = 0;
|
|
65
74
|
let entries: fs.Dirent[];
|
|
66
75
|
try {
|
|
67
76
|
entries = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
|
|
68
77
|
} catch {
|
|
69
|
-
return { ok: true, stale, inScope };
|
|
78
|
+
return { ok: true, stale, inScope, scanned };
|
|
70
79
|
}
|
|
71
80
|
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
72
81
|
for (const e of entries) {
|
|
73
82
|
if (!e.isDirectory()) continue;
|
|
83
|
+
scanned += 1;
|
|
74
84
|
const evalsPath = path.join(SKILLS_DIR, e.name, 'evals', 'evals.json');
|
|
75
85
|
const skillMd = path.join(SKILLS_DIR, e.name, 'SKILL.md');
|
|
76
86
|
if (!fs.existsSync(evalsPath) || !fs.existsSync(skillMd)) continue;
|
|
@@ -91,7 +101,7 @@ export function checkFreshness(): FreshnessResult {
|
|
|
91
101
|
stale.push({ skill: e.name, pinned, current });
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
|
-
return { ok: stale.length === 0, stale, inScope };
|
|
104
|
+
return { ok: stale.length === 0, stale, inScope, scanned };
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
@@ -103,7 +113,23 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
103
113
|
return 2;
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
|
-
const { ok, stale, inScope } = checkFreshness();
|
|
116
|
+
const { ok, stale, inScope, scanned } = checkFreshness();
|
|
117
|
+
// `checkFreshness` swallows an unreadable skills root and returns ok:true,
|
|
118
|
+
// so a moved `src/skills` prints "0 pinned eval(s) current" and passes.
|
|
119
|
+
try {
|
|
120
|
+
assertScanned({
|
|
121
|
+
gate: 'lint_behavioural_eval_freshness',
|
|
122
|
+
scanned,
|
|
123
|
+
units: 'skill director(ies)',
|
|
124
|
+
roots: ['src/skills'],
|
|
125
|
+
});
|
|
126
|
+
} catch (e) {
|
|
127
|
+
if (e instanceof DeadScopeError) {
|
|
128
|
+
process.stderr.write(`${e.message}\n`);
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
throw e;
|
|
132
|
+
}
|
|
107
133
|
if (!ok) {
|
|
108
134
|
for (const s of stale) {
|
|
109
135
|
process.stderr.write(
|
|
@@ -20,6 +20,8 @@ import * as path from 'node:path';
|
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
21
21
|
import { parse as parseYaml } from 'yaml';
|
|
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 REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -269,6 +271,29 @@ function parse_args(argv: readonly string[]): Args {
|
|
|
269
271
|
|
|
270
272
|
function main(argv?: readonly string[]): number {
|
|
271
273
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
274
|
+
|
|
275
|
+
// The two corpora are the scope. `docs/benchmark.md` is deliberately NOT on
|
|
276
|
+
// the list: it is optional (lint_doc skips when unrendered), and a watch list
|
|
277
|
+
// passes when ANY entry resolves — including it would let both corpora
|
|
278
|
+
// disappear behind a rendered doc.
|
|
279
|
+
try {
|
|
280
|
+
assertWatchlistResolves({
|
|
281
|
+
gate: 'lint_bench_ab',
|
|
282
|
+
candidates: [TRACK_A_PATH, TRACK_B_PATH].map((p) =>
|
|
283
|
+
path.relative(REPO_ROOT, p).split(path.sep).join('/'),
|
|
284
|
+
),
|
|
285
|
+
repoRoot: REPO_ROOT,
|
|
286
|
+
});
|
|
287
|
+
} catch (exc) {
|
|
288
|
+
if (exc instanceof DeadScopeError) {
|
|
289
|
+
// 1 = the gate's violation code; 2 is reserved for the pinned
|
|
290
|
+
// argparse usage contract, so it cannot carry "could not run".
|
|
291
|
+
process.stderr.write(`lint_bench_ab: ${exc.message}\n`);
|
|
292
|
+
return 1;
|
|
293
|
+
}
|
|
294
|
+
throw exc;
|
|
295
|
+
}
|
|
296
|
+
|
|
272
297
|
try {
|
|
273
298
|
lint_track_a();
|
|
274
299
|
lint_track_b();
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* Exit codes:
|
|
11
11
|
* 0 contract holds across every corpus
|
|
12
12
|
* 1 one or more violations
|
|
13
|
-
* 2 invocation error (no corpora found
|
|
13
|
+
* 2 invocation error (no corpora found — now reported through _lib/scan_scope,
|
|
14
|
+
* which names the empty roots; corpus dir missing)
|
|
14
15
|
*
|
|
15
16
|
* Flags:
|
|
16
17
|
* --quiet suppress per-file OK lines
|
|
@@ -21,6 +22,7 @@ import path from "node:path";
|
|
|
21
22
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
23
|
import { parse as parseYaml, YAMLParseError } from "yaml";
|
|
23
24
|
import { artefact_roots } from "./_lib/agent_src.js";
|
|
25
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
24
26
|
|
|
25
27
|
const QUIET = process.argv.includes("--quiet");
|
|
26
28
|
const REQUIRE_FULL = process.argv.includes("--require-full");
|
|
@@ -348,9 +350,23 @@ export function main(): number {
|
|
|
348
350
|
if (_isDir(ROUTER_COVERAGE_DIR)) {
|
|
349
351
|
corpora.push(...globSorted(ROUTER_COVERAGE_DIR, "", ".yaml"));
|
|
350
352
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
353
|
+
try {
|
|
354
|
+
assertScanned({
|
|
355
|
+
gate: "lint_bench_corpus",
|
|
356
|
+
scanned: corpora.length,
|
|
357
|
+
units: "benchmark corpus file(s)",
|
|
358
|
+
roots: ["tests/eval", "internal/bench/corpora/router-coverage"],
|
|
359
|
+
});
|
|
360
|
+
} catch (exc) {
|
|
361
|
+
if (exc instanceof DeadScopeError) {
|
|
362
|
+
// Exit 2 stays — the documented code for "no corpora found". The Taskfile
|
|
363
|
+
// fails on any non-zero, so this was never a silent green; what the
|
|
364
|
+
// assertion adds is a message that names the roots that came back empty
|
|
365
|
+
// instead of a bare "error: no corpora found".
|
|
366
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
367
|
+
return 2;
|
|
368
|
+
}
|
|
369
|
+
throw exc;
|
|
354
370
|
}
|
|
355
371
|
|
|
356
372
|
const skills = live_skills();
|
|
@@ -30,6 +30,8 @@ import * as fs from 'node:fs';
|
|
|
30
30
|
import * as path from 'node:path';
|
|
31
31
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
32
32
|
|
|
33
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
34
|
+
|
|
33
35
|
const _HERE = fileURLToPath(import.meta.url);
|
|
34
36
|
// src/scripts/lint_breaking_changes_index.ts → two levels up is the repo root.
|
|
35
37
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -237,6 +239,28 @@ function main(argv?: readonly string[]): number {
|
|
|
237
239
|
? _splitlines(fs.readFileSync(changelogPath, 'utf-8'))
|
|
238
240
|
: [];
|
|
239
241
|
|
|
242
|
+
// The diff is between two blobs, but the corpus is the working-tree
|
|
243
|
+
// CHANGELOG: an absent one yields no lines, hence no added majors, hence the
|
|
244
|
+
// green "no new released BREAKING entries vs trunk" — a clean bill of health
|
|
245
|
+
// for a file that was never read. The base blob is legitimately empty for a
|
|
246
|
+
// newly added CHANGELOG, so only this side is asserted.
|
|
247
|
+
try {
|
|
248
|
+
assertScanned({
|
|
249
|
+
gate: 'lint_breaking_changes_index',
|
|
250
|
+
scanned: newLines.length,
|
|
251
|
+
units: `${CHANGELOG_REL} line(s)`,
|
|
252
|
+
roots: [CHANGELOG_REL],
|
|
253
|
+
});
|
|
254
|
+
} catch (exc) {
|
|
255
|
+
if (exc instanceof DeadScopeError) {
|
|
256
|
+
// 1 = the gate's violation code; 2 is reserved for the pinned
|
|
257
|
+
// argparse usage contract, so it cannot carry "could not run".
|
|
258
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
throw exc;
|
|
262
|
+
}
|
|
263
|
+
|
|
240
264
|
// Was BREAKING_CHANGES.md touched (staged or unstaged) vs the base?
|
|
241
265
|
const indexChanged = _git('diff', '--name-only', base, '--', INDEX_REL).trim() !== '';
|
|
242
266
|
|
|
@@ -21,6 +21,8 @@ import * as fs from 'node:fs';
|
|
|
21
21
|
import * as path from 'node:path';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
25
|
+
|
|
24
26
|
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
25
27
|
const CONFIG_DIR = path.join(REPO_ROOT, 'src', 'config');
|
|
26
28
|
|
|
@@ -52,9 +54,24 @@ export function budgetFiles(dir: string = CONFIG_DIR): string[] {
|
|
|
52
54
|
|
|
53
55
|
export function main(): number {
|
|
54
56
|
const files = budgetFiles();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
// `*budget*.json` IS this gate's corpus definition, not a content-derived
|
|
58
|
+
// subset: zero matches means src/config/ moved or the budgets were deleted,
|
|
59
|
+
// and the old "nothing to lint" exit 0 could not tell either from a healthy
|
|
60
|
+
// tree. Of the two documented failure codes, 1 is the one the gate actually
|
|
61
|
+
// returns when it will not vouch for the tree; 2 is reserved for a throw.
|
|
62
|
+
try {
|
|
63
|
+
assertScanned({
|
|
64
|
+
gate: 'lint_budget_ownership',
|
|
65
|
+
scanned: files.length,
|
|
66
|
+
units: 'budget config(s)',
|
|
67
|
+
roots: ['src/config'],
|
|
68
|
+
});
|
|
69
|
+
} catch (exc) {
|
|
70
|
+
if (exc instanceof DeadScopeError) {
|
|
71
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
throw exc;
|
|
58
75
|
}
|
|
59
76
|
const errors: string[] = [];
|
|
60
77
|
const today = new Date().toISOString().slice(0, 10);
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
REPO_ROOT,
|
|
34
34
|
VERSION_HEADING_RE,
|
|
35
35
|
} from './_lib/changelog_eras.js';
|
|
36
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
36
37
|
|
|
37
38
|
const _HERE = fileURLToPath(import.meta.url);
|
|
38
39
|
|
|
@@ -243,6 +244,29 @@ function main(argv?: readonly string[]): number {
|
|
|
243
244
|
const text = fs.readFileSync(changelog_path, 'utf-8');
|
|
244
245
|
const lines = text.split(/\r\n|\r|\n/);
|
|
245
246
|
|
|
247
|
+
// Scope is the version sections the heading regex actually recognises, not
|
|
248
|
+
// the line count: a CHANGELOG whose heading shape drifts away from
|
|
249
|
+
// VERSION_HEADING_RE still reads as a big file while presenting zero
|
|
250
|
+
// sections to check, and the gate reports clean. Counted before the cutoff
|
|
251
|
+
// and patch filters — those are judgement, this is reach.
|
|
252
|
+
const sections = lines.filter((ln) => VERSION_HEADING_RE.test(ln)).length;
|
|
253
|
+
try {
|
|
254
|
+
assertScanned({
|
|
255
|
+
gate: 'lint_changelog_rollback',
|
|
256
|
+
scanned: sections,
|
|
257
|
+
units: 'version section(s)',
|
|
258
|
+
roots: [path.relative(REPO_ROOT, changelog_path)],
|
|
259
|
+
});
|
|
260
|
+
} catch (exc) {
|
|
261
|
+
if (exc instanceof DeadScopeError) {
|
|
262
|
+
// 1 = the gate's violation code; 2 is reserved for the pinned
|
|
263
|
+
// argparse usage contract, so it cannot carry "could not run".
|
|
264
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
265
|
+
return 1;
|
|
266
|
+
}
|
|
267
|
+
throw exc;
|
|
268
|
+
}
|
|
269
|
+
|
|
246
270
|
const violations = check_rollback_lines(lines, cutoff);
|
|
247
271
|
if (violations.length > 0) {
|
|
248
272
|
process.stderr.write(
|
|
@@ -22,6 +22,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
22
22
|
import { parse as parseYaml } from 'yaml';
|
|
23
23
|
|
|
24
24
|
import { _iter_domains_commands } from './_lib/agent_src.js';
|
|
25
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
25
26
|
|
|
26
27
|
const _HERE = fileURLToPath(import.meta.url);
|
|
27
28
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -119,6 +120,27 @@ export function main(): number {
|
|
|
119
120
|
return 3;
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
// `_iter_domains_commands` returns nothing when the command tree is not a
|
|
124
|
+
// directory, so a moved root turns every mapped ref into a "phantom" — the
|
|
125
|
+
// gate would blame surface-map.yaml for a walk that never happened. Counted
|
|
126
|
+
// on the walk itself, before any comparison with the map.
|
|
127
|
+
try {
|
|
128
|
+
assertScanned({
|
|
129
|
+
gate: 'lint_command_flow_coverage',
|
|
130
|
+
scanned: real.size,
|
|
131
|
+
units: 'command file(s)',
|
|
132
|
+
roots: ['src/domains/<pack>/**/command.md'],
|
|
133
|
+
});
|
|
134
|
+
} catch (exc) {
|
|
135
|
+
if (exc instanceof DeadScopeError) {
|
|
136
|
+
// 3 = internal error, i.e. "this gate could not run" — 1 would
|
|
137
|
+
// claim the surface map is wrong, which is exactly the wrong story.
|
|
138
|
+
process.stderr.write(`lint_command_flow_coverage: ${exc.message}\n`);
|
|
139
|
+
return 3;
|
|
140
|
+
}
|
|
141
|
+
throw exc;
|
|
142
|
+
}
|
|
143
|
+
|
|
122
144
|
const user_flows = new Set<string>(
|
|
123
145
|
(Array.isArray(data['user_work_flows']) ? (data['user_work_flows'] as unknown[]) : [])
|
|
124
146
|
.map((x) => String(x)),
|
|
@@ -20,6 +20,7 @@ import fs from "node:fs";
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
22
|
import { artefact_roots } from "./_lib/agent_src.js";
|
|
23
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
23
24
|
|
|
24
25
|
const QUIET = process.argv.includes("--quiet");
|
|
25
26
|
|
|
@@ -315,11 +316,34 @@ function rglobNamed(root: string, name: string): string[] {
|
|
|
315
316
|
|
|
316
317
|
export function main(): number {
|
|
317
318
|
const commandsDirs = COMMANDS_DIRS();
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
// Each pass below guards its own emptiness, but nothing guarded the case
|
|
320
|
+
// where every pass is skipped: COMMANDS_DIRS() drops a root that holds no
|
|
321
|
+
// .md, and both DOMAINS_DIR and the condensed projection are conditional, so
|
|
322
|
+
// rc could stay 0 having read no command at all. The sum replaces the older
|
|
323
|
+
// "no commands dir found" check, which only saw the directory, not the files.
|
|
324
|
+
const commandsIn = (d: string): number =>
|
|
325
|
+
rglobMd(d).filter((p) => path.basename(p) !== "AGENTS.md").length;
|
|
326
|
+
const scanned =
|
|
327
|
+
(_isDir(DOMAINS_DIR) ? rglobNamed(DOMAINS_DIR, "command.md").length : 0) +
|
|
328
|
+
commandsDirs.reduce((n, d) => n + commandsIn(d), 0) +
|
|
329
|
+
(_isDir(COMMANDS_DIR_CONDENSED) ? commandsIn(COMMANDS_DIR_CONDENSED) : 0);
|
|
330
|
+
try {
|
|
331
|
+
assertScanned({
|
|
332
|
+
gate: "lint_command_tiers",
|
|
333
|
+
scanned,
|
|
334
|
+
units: "command file(s)",
|
|
335
|
+
roots: [
|
|
336
|
+
relPosix(DOMAINS_DIR, REPO),
|
|
337
|
+
...commandsDirs.map((d) => relPosix(d, REPO)),
|
|
338
|
+
relPosix(COMMANDS_DIR_CONDENSED, REPO),
|
|
339
|
+
],
|
|
340
|
+
});
|
|
341
|
+
} catch (exc) {
|
|
342
|
+
if (exc instanceof DeadScopeError) {
|
|
343
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
344
|
+
return 1;
|
|
345
|
+
}
|
|
346
|
+
throw exc;
|
|
323
347
|
}
|
|
324
348
|
let rc = 0;
|
|
325
349
|
if (_isDir(DOMAINS_DIR)) {
|
|
@@ -28,6 +28,8 @@ import * as path from 'node:path';
|
|
|
28
28
|
import * as fs from 'node:fs';
|
|
29
29
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
30
30
|
|
|
31
|
+
import { assertScanned } from './_lib/scan_scope.js';
|
|
32
|
+
|
|
31
33
|
const _HERE = fileURLToPath(import.meta.url);
|
|
32
34
|
|
|
33
35
|
const BLOCKLIST: ReadonlySet<string> = new Set([
|
|
@@ -182,6 +184,19 @@ function main(argv?: readonly string[]): number {
|
|
|
182
184
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
183
185
|
|
|
184
186
|
const subjects = fetch_subjects(args.base, args.head);
|
|
187
|
+
assertScanned({
|
|
188
|
+
gate: 'lint_commit_subjects',
|
|
189
|
+
scanned: subjects.length,
|
|
190
|
+
units: 'commit subject(s)',
|
|
191
|
+
roots: [`git log ${args.base}..${args.head}`],
|
|
192
|
+
allowEmpty:
|
|
193
|
+
'EMPTY_VALID: a branch that is level with its base contributes no subjects to the ' +
|
|
194
|
+
'release changelog, so zero subjects is the success state. Deletion test: no ' +
|
|
195
|
+
'deleted root can produce it — the range is computed from refs, not a tree. An ' +
|
|
196
|
+
'UNREADABLE range is the distinct case and is announced, not swallowed: ' +
|
|
197
|
+
'`fetch_subjects` writes `⚠️ git log <base>..<head> failed: …` to stderr first, ' +
|
|
198
|
+
'and staying advisory there is the pinned contract (never block on git plumbing).',
|
|
199
|
+
});
|
|
185
200
|
if (subjects.length === 0) {
|
|
186
201
|
if (!args.quiet) {
|
|
187
202
|
process.stdout.write(
|
|
@@ -35,6 +35,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
35
35
|
|
|
36
36
|
import * as sl from './_lib/security_lint.js';
|
|
37
37
|
import { TOKEN_RE, classifyToken } from './_lib/confusables.js';
|
|
38
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
38
39
|
|
|
39
40
|
export const CHECK = 'mixed-script-confusable';
|
|
40
41
|
|
|
@@ -113,12 +114,35 @@ export function main(argv: readonly string[] | null = null): number {
|
|
|
113
114
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
114
115
|
|
|
115
116
|
const findings: sl.Finding[] = [];
|
|
117
|
+
let scanned = 0;
|
|
116
118
|
for (const sf of sl.iter_corpus()) {
|
|
119
|
+
// Counted on the corpus walk, not on `_scan`'s return: `iter_corpus`
|
|
120
|
+
// skips a root that does not exist, so every root moving at once yields
|
|
121
|
+
// zero files and the report line still says "clean (scanned <roots>)".
|
|
122
|
+
scanned += 1;
|
|
117
123
|
for (const h of _scan(sf)) {
|
|
118
124
|
findings.push(h);
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
|
|
128
|
+
try {
|
|
129
|
+
assertScanned({
|
|
130
|
+
gate: 'lint_confusables',
|
|
131
|
+
scanned,
|
|
132
|
+
units: 'file(s)',
|
|
133
|
+
roots: sl.DEFAULT_SCAN_ROOTS,
|
|
134
|
+
});
|
|
135
|
+
} catch (exc) {
|
|
136
|
+
if (exc instanceof DeadScopeError) {
|
|
137
|
+
// 2 = "could not run" (the usage-error code), never 0 and never the
|
|
138
|
+
// finding code 1: a homoglyph lint that read nothing is blind, and
|
|
139
|
+
// `--json` callers must not read an empty payload as clean either.
|
|
140
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
141
|
+
return 2;
|
|
142
|
+
}
|
|
143
|
+
throw exc;
|
|
144
|
+
}
|
|
145
|
+
|
|
122
146
|
if (args.json) {
|
|
123
147
|
const payload = sl.py_json_dumps_indent2(findings.map((f) => f.toDict()));
|
|
124
148
|
process.stdout.write(payload + '\n');
|
|
@@ -52,6 +52,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
52
52
|
|
|
53
53
|
import { parse as parseYaml } from 'yaml';
|
|
54
54
|
|
|
55
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
56
|
+
|
|
55
57
|
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
56
58
|
const SKILLS_DIR = path.join(REPO_ROOT, 'dist', 'agent-src', 'skills');
|
|
57
59
|
|
|
@@ -174,9 +176,11 @@ export function main(): number {
|
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
const findings: string[] = [];
|
|
179
|
+
let projected = 0;
|
|
177
180
|
for (const name of skillDirs) {
|
|
178
181
|
const abs = path.join(SKILLS_DIR, name, 'SKILL.md');
|
|
179
182
|
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) continue;
|
|
183
|
+
projected += 1;
|
|
180
184
|
const ws = skillWorkspaces(abs);
|
|
181
185
|
if (isExclusivelyMaintainer(ws)) continue;
|
|
182
186
|
let text: string;
|
|
@@ -189,6 +193,26 @@ export function main(): number {
|
|
|
189
193
|
findings.push(...scanText(name, relPath, text));
|
|
190
194
|
}
|
|
191
195
|
|
|
196
|
+
// Counted BEFORE the maintainer-exclusive skip, so an all-maintainer
|
|
197
|
+
// projection is not mistaken for a dead root. The dir-exists guard above
|
|
198
|
+
// only catches a deleted tree; a regenerated-but-empty projection reaches
|
|
199
|
+
// here and would print the ✅ line over nothing. Exit 2 follows that same
|
|
200
|
+
// guard: the projection is unusable, which is not a "finding" (1).
|
|
201
|
+
try {
|
|
202
|
+
assertScanned({
|
|
203
|
+
gate: 'lint_consumer_internal_refs',
|
|
204
|
+
scanned: projected,
|
|
205
|
+
units: 'projected SKILL.md file(s)',
|
|
206
|
+
roots: ['dist/agent-src/skills'],
|
|
207
|
+
});
|
|
208
|
+
} catch (exc) {
|
|
209
|
+
if (exc instanceof DeadScopeError) {
|
|
210
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
211
|
+
return 2;
|
|
212
|
+
}
|
|
213
|
+
throw exc;
|
|
214
|
+
}
|
|
215
|
+
|
|
192
216
|
if (findings.length > 0) {
|
|
193
217
|
for (const f of findings) {
|
|
194
218
|
process.stderr.write(`❌ consumer-internal reference: ${f}\n`);
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
*/
|
|
45
45
|
import fs from "node:fs";
|
|
46
46
|
import path from "node:path";
|
|
47
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
47
48
|
|
|
48
49
|
interface Finding {
|
|
49
50
|
rule: string;
|
|
@@ -277,6 +278,27 @@ function main(): void {
|
|
|
277
278
|
}
|
|
278
279
|
}
|
|
279
280
|
|
|
281
|
+
// The opt-in escape above is the "consumer has no UI tree" path. Reaching
|
|
282
|
+
// here means the root EXISTS and still yielded nothing scannable — a moved
|
|
283
|
+
// subtree, a drifted extension list, or an over-broad ignoreFiles glob. That
|
|
284
|
+
// prints "clean — 0 file(s) checked" today. Exit 1 is this linter's
|
|
285
|
+
// internal-error code; 2 is reserved for real findings.
|
|
286
|
+
try {
|
|
287
|
+
assertScanned({
|
|
288
|
+
gate: "lint_design_quality",
|
|
289
|
+
scanned: htmlCssFiles.length,
|
|
290
|
+
units: "HTML/CSS file(s)",
|
|
291
|
+
// Absolute: --dir may point outside the cwd (or outside any repo).
|
|
292
|
+
roots: [scanDir],
|
|
293
|
+
});
|
|
294
|
+
} catch (exc) {
|
|
295
|
+
if (exc instanceof DeadScopeError) {
|
|
296
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
throw exc;
|
|
300
|
+
}
|
|
301
|
+
|
|
280
302
|
const allFindings: Finding[] = [];
|
|
281
303
|
for (const filePath of htmlCssFiles) {
|
|
282
304
|
const relPath = path.relative(process.cwd(), filePath);
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
import fs from "node:fs";
|
|
39
39
|
import path from "node:path";
|
|
40
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
40
41
|
import {
|
|
41
42
|
SLOP_RULES,
|
|
42
43
|
type DesignContext,
|
|
@@ -197,6 +198,28 @@ function main(): void {
|
|
|
197
198
|
if (!ignoreGlobs.some((g) => f.includes(g.replace("**", "")))) files.push(f);
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
// The opt-in escape above is the "consumer has no UI tree" path. Reaching
|
|
202
|
+
// here means the root EXISTS and still yielded nothing scannable — a moved
|
|
203
|
+
// subtree, a drifted extension list, or an over-broad ignoreFiles glob. This
|
|
204
|
+
// detector never hard-blocks on findings, so "no tells in 0 file(s)" is
|
|
205
|
+
// indistinguishable from a clean scan. Exit 1 is its internal-error code; 2
|
|
206
|
+
// is reserved for the --fail-on threshold.
|
|
207
|
+
try {
|
|
208
|
+
assertScanned({
|
|
209
|
+
gate: "lint_design_slop",
|
|
210
|
+
scanned: files.length,
|
|
211
|
+
units: "design source file(s)",
|
|
212
|
+
// Absolute: --dir may point outside the cwd (or outside any repo).
|
|
213
|
+
roots: [scanDir],
|
|
214
|
+
});
|
|
215
|
+
} catch (exc) {
|
|
216
|
+
if (exc instanceof DeadScopeError) {
|
|
217
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
throw exc;
|
|
221
|
+
}
|
|
222
|
+
|
|
200
223
|
const findings: Finding[] = [];
|
|
201
224
|
for (const fp of files) {
|
|
202
225
|
const rel = path.relative(process.cwd(), fp);
|
|
@@ -31,6 +31,8 @@ import * as path from 'node:path';
|
|
|
31
31
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
32
32
|
import YAML from 'yaml';
|
|
33
33
|
|
|
34
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
35
|
+
|
|
34
36
|
const _HERE = fileURLToPath(import.meta.url);
|
|
35
37
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
36
38
|
const SCHEMA_PATH = path.join(ROOT, 'docs', 'contracts', 'discovery-manifest.schema.json');
|
|
@@ -247,6 +249,27 @@ function main(argv?: readonly string[]): number {
|
|
|
247
249
|
return 1;
|
|
248
250
|
}
|
|
249
251
|
|
|
252
|
+
// A manifest built over a dead source root is still well-formed and still
|
|
253
|
+
// checksums correctly — it just indexes nothing, and this gate signs it off
|
|
254
|
+
// with "OK …: 0 artefacts, 0 unassigned, checksum verified". Asserted after
|
|
255
|
+
// the structural checks so their own messages stay the first explanation.
|
|
256
|
+
try {
|
|
257
|
+
assertScanned({
|
|
258
|
+
gate: 'lint_discovery_manifest',
|
|
259
|
+
scanned: _asArray(manifest['artefacts']).length,
|
|
260
|
+
units: 'manifest artefact(s)',
|
|
261
|
+
roots: [_relPosix(args.manifest, ROOT)],
|
|
262
|
+
});
|
|
263
|
+
} catch (exc) {
|
|
264
|
+
if (exc instanceof DeadScopeError) {
|
|
265
|
+
// 1 = schema/integrity failure, the gate's only failure code; 2 is
|
|
266
|
+
// reserved for the pinned argparse usage contract.
|
|
267
|
+
process.stderr.write(`error: ${exc.message}\n`);
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
throw exc;
|
|
271
|
+
}
|
|
272
|
+
|
|
250
273
|
const vocab_errs = _check_vocab(manifest);
|
|
251
274
|
const det_errs = _check_capability_pack_determinism(manifest);
|
|
252
275
|
const all_errs = [...vocab_errs, ...det_errs];
|