@event4u/agent-config 9.20.0 → 9.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +51 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-XGA2NTU4.js → chunk-3EWYC3YK.js} +40 -0
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/hooks/dispatch.js +58 -2
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +47 -29
- package/package.json +1 -1
- package/src/config/gate-coverage.yml +22 -4
- package/src/config/gate-violation-baselines.json +3 -8
- package/src/scripts/_lib/gate_population.ts +70 -0
- package/src/scripts/_lib/roadmap_granularity.ts +157 -0
- package/src/scripts/_lib/scan_scope.ts +44 -0
- package/src/scripts/_lib/stdin.ts +82 -0
- package/src/scripts/audit_adr_coverage.ts +38 -0
- package/src/scripts/audit_auto_rules.ts +17 -3
- package/src/scripts/audit_command_surface.ts +51 -0
- package/src/scripts/audit_initial_context.ts +39 -3
- package/src/scripts/audit_mcp_tools.ts +25 -0
- package/src/scripts/audit_overlap.ts +24 -1
- package/src/scripts/audit_skill_descriptions.ts +19 -0
- package/src/scripts/{verify_before_complete_hook.ts → before_complete_hook.ts} +16 -3
- package/src/scripts/check_adr_frontmatter.ts +23 -3
- package/src/scripts/check_always_budget.ts +24 -0
- package/src/scripts/check_artefact_checksums.ts +24 -1
- package/src/scripts/check_artefact_count_messaging.ts +20 -0
- package/src/scripts/check_augmentignore.ts +22 -15
- package/src/scripts/check_backstop_debt.ts +22 -0
- package/src/scripts/check_beta_review_markers.ts +22 -1
- package/src/scripts/check_bridge_derivation.ts +22 -1
- package/src/scripts/check_bundle_path_leakage.ts +26 -0
- package/src/scripts/check_ci_strict_superset.ts +21 -3
- package/src/scripts/check_claims.ts +22 -0
- package/src/scripts/check_cli_registry_budget_sync.ts +33 -4
- package/src/scripts/check_cluster_patterns.ts +26 -4
- package/src/scripts/check_command_count_messaging.ts +20 -4
- package/src/scripts/check_comparison.ts +18 -0
- package/src/scripts/check_condensed_paths.ts +23 -2
- package/src/scripts/check_consumer_scope_flip.ts +21 -2
- package/src/scripts/check_council_config_location.ts +20 -0
- package/src/scripts/check_council_layout.ts +22 -2
- package/src/scripts/check_council_references.ts +23 -1
- package/src/scripts/check_dependency_floors.ts +19 -3
- package/src/scripts/check_discovery_determinism.ts +26 -0
- package/src/scripts/check_enforcement_coverage.ts +20 -0
- package/src/scripts/check_evaluator_budgets.ts +23 -0
- package/src/scripts/check_finding_dispositions.ts +29 -0
- package/src/scripts/check_gate_coverage.ts +63 -22
- package/src/scripts/check_gate_paths.ts +30 -5
- package/src/scripts/check_generated_artefact_headers.ts +21 -0
- package/src/scripts/check_generator_output_coverage.ts +23 -0
- package/src/scripts/check_gitignore_freshness.ts +23 -0
- package/src/scripts/check_host_loadability.ts +34 -8
- package/src/scripts/check_installer_import_purity.ts +22 -8
- package/src/scripts/check_kernel_prefix_stability.ts +19 -1
- package/src/scripts/check_kernel_rule_bundle.ts +23 -0
- package/src/scripts/check_knowledge_cards.ts +13 -0
- package/src/scripts/check_knowledge_pages.ts +23 -0
- package/src/scripts/check_knowledge_sharing.ts +27 -0
- package/src/scripts/check_md_language.ts +24 -0
- package/src/scripts/check_media_deps.ts +23 -0
- package/src/scripts/check_memory.ts +26 -1
- package/src/scripts/check_memory_contradiction.ts +16 -1
- package/src/scripts/check_memory_proposal.ts +15 -0
- package/src/scripts/check_memory_similarity.ts +16 -0
- package/src/scripts/check_module_management_neutral.ts +19 -11
- package/src/scripts/check_no_conflict_markers.ts +22 -1
- package/src/scripts/check_no_external_sources.ts +26 -1
- package/src/scripts/check_no_local_settings_committed.ts +37 -10
- package/src/scripts/check_no_new_legacy_path.ts +30 -1
- package/src/scripts/check_one_off_location.ts +43 -0
- package/src/scripts/check_originality_freshness.ts +25 -1
- package/src/scripts/check_overlay_cascade_subdirs.ts +21 -0
- package/src/scripts/check_pack_size.ts +22 -0
- package/src/scripts/check_preamble_payload_budget.ts +35 -5
- package/src/scripts/check_proposal.ts +24 -1
- package/src/scripts/check_public_catalog_links.ts +27 -0
- package/src/scripts/check_public_links.ts +19 -0
- package/src/scripts/check_quality_regression.ts +19 -0
- package/src/scripts/check_reach_channels.ts +44 -0
- package/src/scripts/check_reach_staleness.ts +27 -0
- package/src/scripts/check_release_adjacent_health.ts +28 -0
- package/src/scripts/check_release_highlights.ts +34 -0
- package/src/scripts/check_release_includes_discovery.ts +25 -6
- package/src/scripts/check_release_pr_shape.ts +18 -1
- package/src/scripts/check_release_published.ts +21 -0
- package/src/scripts/check_release_surface_equality.ts +22 -0
- package/src/scripts/check_release_trunk_sync.ts +19 -0
- package/src/scripts/check_reply_consistency.ts +22 -0
- package/src/scripts/check_roadmap_trackable.ts +20 -0
- package/src/scripts/check_role_doc_links.ts +23 -0
- package/src/scripts/check_rule_invariants.ts +14 -0
- package/src/scripts/check_secret_leak.ts +47 -0
- package/src/scripts/check_skill_gaps.ts +32 -0
- package/src/scripts/check_skill_requires.ts +19 -0
- package/src/scripts/check_source_pointer_freshness.ts +20 -0
- package/src/scripts/check_structural_breaking.ts +29 -2
- package/src/scripts/check_surface_tiers.ts +34 -0
- package/src/scripts/check_template_pin_drift.ts +23 -1
- package/src/scripts/check_test_coverage_diff.ts +17 -1
- package/src/scripts/check_token_optimizer_freshness.ts +18 -3
- package/src/scripts/check_token_quality_golden.ts +21 -0
- package/src/scripts/check_token_regression.ts +25 -0
- package/src/scripts/check_tracked_but_ignored.ts +26 -0
- package/src/scripts/check_trigger_eval_presence.ts +23 -1
- package/src/scripts/check_trigger_evals.ts +36 -0
- package/src/scripts/check_trunk_drift.ts +23 -0
- package/src/scripts/check_update_banner.ts +24 -0
- package/src/scripts/gate_scope_classify.ts +513 -0
- package/src/scripts/hook_manifest.yaml +1 -1
- package/src/scripts/hooks/block_kernel_rule_writes.ts +1 -1
- package/src/scripts/hooks/concern_registry.ts +2 -2
- package/src/scripts/lint_agent_security.ts +19 -0
- package/src/scripts/lint_agent_skill_names.ts +30 -0
- package/src/scripts/lint_agents_layout.ts +20 -0
- package/src/scripts/lint_agents_md.ts +20 -0
- package/src/scripts/lint_archived_skills.ts +24 -3
- package/src/scripts/lint_behavioural_eval_freshness.ts +29 -3
- package/src/scripts/lint_bench_ab.ts +25 -0
- package/src/scripts/lint_bench_corpus.ts +20 -4
- package/src/scripts/lint_breaking_changes_index.ts +24 -0
- package/src/scripts/lint_budget_ownership.ts +20 -3
- package/src/scripts/lint_changelog_rollback.ts +24 -0
- package/src/scripts/lint_command_flow_coverage.ts +22 -0
- package/src/scripts/lint_command_tiers.ts +29 -5
- package/src/scripts/lint_commit_subjects.ts +15 -0
- package/src/scripts/lint_confusables.ts +24 -0
- package/src/scripts/lint_consumer_internal_refs.ts +24 -0
- package/src/scripts/lint_design_quality.ts +22 -0
- package/src/scripts/lint_design_slop.ts +23 -0
- package/src/scripts/lint_discovery_manifest.ts +23 -0
- package/src/scripts/lint_discovery_vocabulary.ts +19 -0
- package/src/scripts/lint_documented_commands.ts +23 -0
- package/src/scripts/lint_empty_roadmaps.ts +23 -0
- package/src/scripts/lint_eval_fixture_citations.ts +19 -0
- package/src/scripts/lint_eval_freshness.ts +35 -0
- package/src/scripts/lint_examples.ts +18 -3
- package/src/scripts/lint_explain_trace.ts +23 -0
- package/src/scripts/lint_featured_skills.ts +26 -1
- package/src/scripts/lint_flows.ts +19 -0
- package/src/scripts/lint_frontmatter_boilerplate.ts +20 -0
- package/src/scripts/lint_ghostwriter_source.ts +26 -6
- package/src/scripts/lint_glama_drift.ts +24 -0
- package/src/scripts/lint_global_paths.ts +24 -0
- package/src/scripts/lint_governed_writes.ts +20 -0
- package/src/scripts/lint_hidden_unicode.ts +25 -0
- package/src/scripts/lint_hook_concern_budget.ts +39 -6
- package/src/scripts/lint_hook_manifest.ts +22 -0
- package/src/scripts/lint_instruction_smuggling.ts +23 -0
- package/src/scripts/lint_knowledge_scale.ts +32 -2
- package/src/scripts/lint_legal_pack.ts +31 -0
- package/src/scripts/lint_marketplace.ts +26 -0
- package/src/scripts/lint_marketplace_install_completeness.ts +23 -0
- package/src/scripts/lint_mcp_config_security.ts +24 -0
- package/src/scripts/lint_mcp_registry_manifest.ts +22 -1
- package/src/scripts/lint_memory_tripwire.ts +35 -0
- package/src/scripts/lint_missions.ts +23 -7
- package/src/scripts/lint_model_tier_coverage.ts +18 -0
- package/src/scripts/lint_namespace_collisions.ts +19 -0
- package/src/scripts/lint_no_new_atomic_commands.ts +33 -0
- package/src/scripts/lint_no_python_twin_rationale.ts +18 -0
- package/src/scripts/lint_one_off_age.ts +38 -2
- package/src/scripts/lint_orchestration_dsl.ts +23 -3
- package/src/scripts/lint_orchestrator_auto_detect.ts +19 -12
- package/src/scripts/lint_originality.ts +22 -4
- package/src/scripts/lint_originality_shingles.ts +28 -3
- package/src/scripts/lint_output_slop.ts +19 -0
- package/src/scripts/lint_override_kernel_guard.ts +30 -0
- package/src/scripts/lint_pack_first_win.ts +23 -0
- package/src/scripts/lint_pack_risk_class.ts +19 -3
- package/src/scripts/lint_persistence.ts +19 -0
- package/src/scripts/lint_persona_governance.ts +18 -3
- package/src/scripts/lint_positioning.ts +23 -0
- package/src/scripts/lint_pre_migration_refs.ts +24 -0
- package/src/scripts/lint_profile_overlay_set_only.ts +24 -0
- package/src/scripts/lint_provenance.ts +21 -0
- package/src/scripts/lint_provenance_vocabulary.ts +24 -1
- package/src/scripts/lint_readme_jargon.ts +21 -3
- package/src/scripts/lint_readme_serial_comma.ts +21 -3
- package/src/scripts/lint_readme_size.ts +21 -3
- package/src/scripts/lint_regression.ts +28 -0
- package/src/scripts/lint_roadmap_blockers.ts +43 -0
- package/src/scripts/lint_roadmap_ci_steps.ts +45 -0
- package/src/scripts/lint_roadmap_complexity.ts +47 -0
- package/src/scripts/lint_roadmap_later_disposition.ts +23 -0
- package/src/scripts/lint_rule_interactions.ts +16 -2
- package/src/scripts/lint_rule_tiers.ts +19 -6
- package/src/scripts/lint_showcase_sessions.ts +19 -3
- package/src/scripts/lint_skill_descriptions.ts +19 -0
- package/src/scripts/lint_skill_frontmatter_safety.ts +24 -0
- package/src/scripts/lint_skill_originality.ts +18 -3
- package/src/scripts/lint_skill_scripts_readonly.ts +20 -0
- package/src/scripts/lint_skill_tools.ts +84 -145
- package/src/scripts/lint_spawn_payload.ts +27 -1
- package/src/scripts/lint_store_boundary.ts +19 -0
- package/src/scripts/lint_subagent_determinism.ts +23 -1
- package/src/scripts/lint_supported_tools_matrix.ts +18 -0
- package/src/scripts/lint_surface_matrix.ts +19 -3
- package/src/scripts/lint_ticket_buildable.ts +23 -0
- package/src/scripts/lint_topics_yaml.ts +19 -12
- package/src/scripts/lint_trigger_precision.ts +27 -0
- package/src/scripts/lint_trust_coherence.ts +21 -0
- package/src/scripts/lint_ui_stack_bundles.ts +17 -3
- package/src/scripts/lint_value_dashboard.ts +19 -3
- package/src/scripts/lint_versioned_cache.ts +19 -1
- package/src/scripts/lint_workflow_security.ts +22 -14
- package/src/scripts/pr_url_reminder_hook.ts +1 -1
- package/src/scripts/skill_collision_clusters.ts +37 -3
- package/src/scripts/skill_discovery.ts +20 -0
- package/src/scripts/skill_eval_coverage.ts +22 -0
- package/src/scripts/skill_overlap.ts +32 -6
- package/src/scripts/skill_preview.ts +31 -0
- package/src/scripts/skill_trigger_eval.ts +18 -0
- package/src/scripts/skill_usage_collect.ts +21 -0
- package/src/scripts/skill_usage_report.ts +21 -0
- package/src/scripts/sweep_dead_scan_roots.ts +11 -3
- package/src/scripts/verify_physical_move.ts +24 -0
- package/src/scripts/check_bite_sized_granularity.ts +0 -116
- package/src/scripts/lint_workspace_boundary.ts +0 -267
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
* synchronous. No behaviour changes.
|
|
21
21
|
*
|
|
22
22
|
* Exit codes: 0 = all enforced targets resolve under the source tree ·
|
|
23
|
-
* 1 = at least one missing / out-of-tree target · 2 = a gate failed to import
|
|
23
|
+
* 1 = at least one missing / out-of-tree target · 2 = a gate failed to import
|
|
24
|
+
* (which now includes a dead scan scope — zero enforced targets across the
|
|
25
|
+
* whole GATES list — reported through `_lib/scan_scope`).
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
import { spawnSync } from 'node:child_process';
|
|
@@ -28,6 +30,8 @@ import * as fs from 'node:fs';
|
|
|
28
30
|
import * as path from 'node:path';
|
|
29
31
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
30
32
|
|
|
33
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
34
|
+
|
|
31
35
|
const _HERE = fileURLToPath(import.meta.url);
|
|
32
36
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
33
37
|
|
|
@@ -190,6 +194,31 @@ function main(): number {
|
|
|
190
194
|
process.stderr.write(`❌ check-gate-paths: ${msg}\n`);
|
|
191
195
|
return 2;
|
|
192
196
|
}
|
|
197
|
+
let total = 0;
|
|
198
|
+
for (const v of named.values()) {
|
|
199
|
+
total += v.length;
|
|
200
|
+
}
|
|
201
|
+
// The enforced targets ARE the corpus. A per-gate empty GATE_CORE_PATHS is
|
|
202
|
+
// already exit 2 inside the subprocess, but an emptied GATES list is not:
|
|
203
|
+
// it yields zero targets, zero failures, and a green "0 enforced target(s)
|
|
204
|
+
// … resolve" — the path-integrity gate certifying that nothing is enforced.
|
|
205
|
+
try {
|
|
206
|
+
assertScanned({
|
|
207
|
+
gate: 'check_gate_paths',
|
|
208
|
+
scanned: total,
|
|
209
|
+
units: 'enforced target(s)',
|
|
210
|
+
roots: ['src/scripts (GATE_CORE_PATHS of the GATES list)'],
|
|
211
|
+
});
|
|
212
|
+
} catch (exc) {
|
|
213
|
+
if (exc instanceof DeadScopeError) {
|
|
214
|
+
// Exit 2, not 1: 1 means "a declared target failed to resolve" (a
|
|
215
|
+
// finding), while 2 is this file's existing "a gate cannot be
|
|
216
|
+
// checked" code — which is exactly what an empty target set is.
|
|
217
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
218
|
+
return 2;
|
|
219
|
+
}
|
|
220
|
+
throw exc;
|
|
221
|
+
}
|
|
193
222
|
const failures = check_paths(named);
|
|
194
223
|
if (failures.length > 0) {
|
|
195
224
|
process.stdout.write(
|
|
@@ -202,10 +231,6 @@ function main(): number {
|
|
|
202
231
|
process.stdout.write(' GATE_CORE_PATHS or the move.\n');
|
|
203
232
|
return 1;
|
|
204
233
|
}
|
|
205
|
-
let total = 0;
|
|
206
|
-
for (const v of named.values()) {
|
|
207
|
-
total += v.length;
|
|
208
|
-
}
|
|
209
234
|
process.stdout.write(
|
|
210
235
|
`✅ check-gate-paths: ${total} enforced target(s) across ` +
|
|
211
236
|
`${named.size} gate(s) resolve under the source tree.\n`,
|
|
@@ -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 { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
19
|
+
|
|
18
20
|
const _HERE = fileURLToPath(import.meta.url);
|
|
19
21
|
const REPO_ROOT = path.join(path.dirname(_HERE), '..', '..');
|
|
20
22
|
|
|
@@ -55,6 +57,25 @@ function main(argv?: readonly string[]): number {
|
|
|
55
57
|
const args = argv ?? process.argv.slice(2);
|
|
56
58
|
const quiet = args.includes('--quiet');
|
|
57
59
|
|
|
60
|
+
// Every artefact is skipped when absent, so all three moving at once prints
|
|
61
|
+
// "✅ All 3 generated artefacts carry the freshness marker" over zero reads.
|
|
62
|
+
// The per-file skip below stays — a single gitignored artefact is a real
|
|
63
|
+
// state; none of them existing is a moved root. 1 is the only failure code
|
|
64
|
+
// this gate has, so "could not run" and "found something" share it.
|
|
65
|
+
try {
|
|
66
|
+
assertWatchlistResolves({
|
|
67
|
+
gate: 'check_generated_artefact_headers',
|
|
68
|
+
candidates: GENERATED_ARTEFACTS.map((e) => e.path),
|
|
69
|
+
repoRoot: REPO_ROOT,
|
|
70
|
+
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof DeadScopeError) {
|
|
73
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
const errors: string[] = [];
|
|
59
80
|
const warnings: string[] = [];
|
|
60
81
|
|
|
@@ -18,6 +18,8 @@ import * as fs from 'node:fs';
|
|
|
18
18
|
import * as path from 'node:path';
|
|
19
19
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
20
|
|
|
21
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
22
|
+
|
|
21
23
|
const _HERE = fileURLToPath(import.meta.url);
|
|
22
24
|
const REPO_ROOT = path.join(path.dirname(_HERE), '..', '..');
|
|
23
25
|
|
|
@@ -103,6 +105,27 @@ function main(argv?: readonly string[]): number {
|
|
|
103
105
|
.filter((l) => Boolean(l) && !l.startsWith('#')),
|
|
104
106
|
);
|
|
105
107
|
|
|
108
|
+
// The registry below is the corpus — the manifest and .gitignore are only
|
|
109
|
+
// what each root is checked AGAINST, and both already fail closed above. An
|
|
110
|
+
// emptied registry (a refactor that moves the list, a bad merge) leaves the
|
|
111
|
+
// loop with nothing to classify and prints "✅ All 0 generator output roots
|
|
112
|
+
// are classified". Exit 2, the code this gate already uses for "a required
|
|
113
|
+
// input is not there", never 1, which means a root is unclassified.
|
|
114
|
+
try {
|
|
115
|
+
assertScanned({
|
|
116
|
+
gate: 'check_generator_output_coverage',
|
|
117
|
+
scanned: GENERATOR_OUTPUT_ROOTS.length,
|
|
118
|
+
units: 'generator output root(s)',
|
|
119
|
+
roots: ['src/scripts/check_generator_output_coverage.ts (GENERATOR_OUTPUT_ROOTS)'],
|
|
120
|
+
});
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if (err instanceof DeadScopeError) {
|
|
123
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
const unclassified: typeof GENERATOR_OUTPUT_ROOTS[number][] = [];
|
|
107
130
|
for (const entry of GENERATOR_OUTPUT_ROOTS) {
|
|
108
131
|
if (!isClassified(entry.root, manifestPaths, gitignoreLines)) {
|
|
@@ -17,6 +17,8 @@ import * as fs from 'node:fs';
|
|
|
17
17
|
import * as path from 'node:path';
|
|
18
18
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
19
19
|
|
|
20
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
21
|
+
|
|
20
22
|
const _HERE = fileURLToPath(import.meta.url);
|
|
21
23
|
const REPO_ROOT = path.join(path.dirname(_HERE), '..', '..');
|
|
22
24
|
|
|
@@ -83,6 +85,27 @@ function main(argv?: readonly string[]): number {
|
|
|
83
85
|
const blockLines = new Set(blockText.split('\n').map((l) => l.trim()).filter(Boolean));
|
|
84
86
|
|
|
85
87
|
const entries = parseManifest(manifestText);
|
|
88
|
+
// Counted on the whole parse, not on the consumer-scoped subset the loop
|
|
89
|
+
// judges: `_isFile` above only proves the manifest exists, and a shape change
|
|
90
|
+
// that makes `- path:` stop matching yields zero entries, zero missing paths,
|
|
91
|
+
// and "✅ covers all 0 consumer-scoped ignored paths". Exit 2 (documented
|
|
92
|
+
// "invalid args or missing file" — the gate could not run), not 1, which
|
|
93
|
+
// means the block is genuinely missing a path.
|
|
94
|
+
try {
|
|
95
|
+
assertScanned({
|
|
96
|
+
gate: 'check_gitignore_freshness',
|
|
97
|
+
scanned: entries.length,
|
|
98
|
+
units: 'manifest entr(y|ies)',
|
|
99
|
+
roots: ['src/config/agents-paths.yml'],
|
|
100
|
+
});
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (err instanceof DeadScopeError) {
|
|
103
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
104
|
+
return 2;
|
|
105
|
+
}
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
|
|
86
109
|
const missing: string[] = [];
|
|
87
110
|
|
|
88
111
|
for (const e of entries) {
|
|
@@ -16,10 +16,14 @@ import * as path from 'node:path';
|
|
|
16
16
|
|
|
17
17
|
import { parse as parseYaml } from 'yaml';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
20
|
+
|
|
21
|
+
/** @returns how many skill directories were examined. */
|
|
22
|
+
export function check_claude_skills(root: string, errors: string[]): number {
|
|
20
23
|
const dir = path.join(root, '.claude', 'skills');
|
|
21
|
-
if (!fs.existsSync(dir)) return;
|
|
22
|
-
|
|
24
|
+
if (!fs.existsSync(dir)) return 0;
|
|
25
|
+
const names = fs.readdirSync(dir);
|
|
26
|
+
for (const name of names) {
|
|
23
27
|
const skillMd = path.join(dir, name, 'SKILL.md');
|
|
24
28
|
if (!fs.existsSync(skillMd)) {
|
|
25
29
|
errors.push(`${skillMd}: missing SKILL.md`);
|
|
@@ -35,13 +39,17 @@ export function check_claude_skills(root: string, errors: string[]): void {
|
|
|
35
39
|
else if (meta['name'] !== name) errors.push(`${skillMd}: name '${meta['name']}' != dir '${name}'`);
|
|
36
40
|
if (typeof meta['description'] !== 'string' || !meta['description']) errors.push(`${skillMd}: missing description`);
|
|
37
41
|
}
|
|
42
|
+
return names.length;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
/** @returns how many `.mdc` rule files were examined. */
|
|
46
|
+
export function check_cursor_rules(root: string, errors: string[]): number {
|
|
41
47
|
const dir = path.join(root, '.cursor', 'rules');
|
|
42
|
-
if (!fs.existsSync(dir)) return;
|
|
48
|
+
if (!fs.existsSync(dir)) return 0;
|
|
49
|
+
let scanned = 0;
|
|
43
50
|
for (const name of fs.readdirSync(dir)) {
|
|
44
51
|
if (!name.endsWith('.mdc')) continue;
|
|
52
|
+
scanned += 1;
|
|
45
53
|
const p = path.join(dir, name);
|
|
46
54
|
const src = fs.readFileSync(p, 'utf-8');
|
|
47
55
|
const m = src.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -57,12 +65,22 @@ export function check_cursor_rules(root: string, errors: string[]): void {
|
|
|
57
65
|
try { parseYaml(lenient); }
|
|
58
66
|
catch (e) { errors.push(`${p}: frontmatter YAML invalid (${(e as Error).message})`); }
|
|
59
67
|
}
|
|
68
|
+
return scanned;
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
export function run(root: string): string[] {
|
|
63
72
|
const errors: string[] = [];
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
// Both host trees are generated and gitignored, and each checker returns
|
|
74
|
+
// early when its tree is absent — so "no malformed artefacts" and "no
|
|
75
|
+
// artefacts" are the same green. Thrown, not returned: `errors` names
|
|
76
|
+
// malformed files, and an unprojected tree is not one.
|
|
77
|
+
const scanned = check_claude_skills(root, errors) + check_cursor_rules(root, errors);
|
|
78
|
+
assertScanned({
|
|
79
|
+
gate: 'check_host_loadability',
|
|
80
|
+
scanned,
|
|
81
|
+
units: 'host artefact(s)',
|
|
82
|
+
roots: ['.claude/skills', '.cursor/rules'],
|
|
83
|
+
});
|
|
66
84
|
return errors;
|
|
67
85
|
}
|
|
68
86
|
|
|
@@ -71,7 +89,15 @@ const isMain = process.argv[1] !== undefined
|
|
|
71
89
|
if (isMain) {
|
|
72
90
|
const rootIdx = process.argv.indexOf('--root');
|
|
73
91
|
const root = rootIdx !== -1 ? process.argv[rootIdx + 1] ?? '.' : '.';
|
|
74
|
-
|
|
92
|
+
let errors: string[];
|
|
93
|
+
try {
|
|
94
|
+
errors = run(root);
|
|
95
|
+
} catch (exc) {
|
|
96
|
+
if (!(exc instanceof DeadScopeError)) throw exc;
|
|
97
|
+
// Exit 1 is this gate's only failure code.
|
|
98
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
75
101
|
if (errors.length > 0) {
|
|
76
102
|
for (const e of errors) process.stderr.write(`❌ ${e}\n`);
|
|
77
103
|
process.exit(1);
|
|
@@ -59,6 +59,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
59
59
|
|
|
60
60
|
import ts from 'typescript';
|
|
61
61
|
|
|
62
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
63
|
+
|
|
62
64
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
63
65
|
const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
64
66
|
|
|
@@ -208,14 +210,6 @@ function parse(file: string): ts.SourceFile | null {
|
|
|
208
210
|
*/
|
|
209
211
|
function auditEntry(entryRel: string, root: string = REPO_ROOT): Finding[] {
|
|
210
212
|
const entryAbs = path.join(root, entryRel);
|
|
211
|
-
if (!fs.existsSync(entryAbs)) {
|
|
212
|
-
process.stderr.write(
|
|
213
|
-
`check-installer-import-purity: entry ${entryRel} does not exist — ` +
|
|
214
|
-
'update BUNDLE_ENTRIES\n',
|
|
215
|
-
);
|
|
216
|
-
process.exitCode = 1;
|
|
217
|
-
return [];
|
|
218
|
-
}
|
|
219
213
|
const findings: Finding[] = [];
|
|
220
214
|
const seen = new Set<string>([entryAbs]);
|
|
221
215
|
// BFS so the reported chain is a shortest path — the least confusing one.
|
|
@@ -245,6 +239,26 @@ function auditEntry(entryRel: string, root: string = REPO_ROOT): Finding[] {
|
|
|
245
239
|
}
|
|
246
240
|
|
|
247
241
|
function main(): number {
|
|
242
|
+
// BUNDLE_ENTRIES is the whole scan scope — every module examined is reached
|
|
243
|
+
// from one of them. This REPLACES the per-entry existence guard in
|
|
244
|
+
// `auditEntry`, which could not fail: it set `process.exitCode = 1` and
|
|
245
|
+
// returned no findings, and `process.exit(main())` then overwrote that with
|
|
246
|
+
// main's 0, so a moved entry printed "✅ … reach no module-level
|
|
247
|
+
// process.exit()". 1 is the only failure code this gate has.
|
|
248
|
+
try {
|
|
249
|
+
assertWatchlistResolves({
|
|
250
|
+
gate: 'check_installer_import_purity',
|
|
251
|
+
candidates: BUNDLE_ENTRIES,
|
|
252
|
+
repoRoot: REPO_ROOT,
|
|
253
|
+
});
|
|
254
|
+
} catch (err) {
|
|
255
|
+
if (err instanceof DeadScopeError) {
|
|
256
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
257
|
+
return 1;
|
|
258
|
+
}
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
|
|
248
262
|
const all: Finding[] = [];
|
|
249
263
|
for (const entry of BUNDLE_ENTRIES) {
|
|
250
264
|
all.push(...auditEntry(entry));
|
|
@@ -37,6 +37,8 @@ import * as path from 'node:path';
|
|
|
37
37
|
import process from 'node:process';
|
|
38
38
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
39
39
|
|
|
40
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
41
|
+
|
|
40
42
|
const _HERE = fileURLToPath(import.meta.url);
|
|
41
43
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
42
44
|
const ROUTER = path.join(REPO_ROOT, 'dist/router.json');
|
|
@@ -96,7 +98,17 @@ function _live_prefix(): KernelPrefix | null {
|
|
|
96
98
|
const router = _readJson(ROUTER);
|
|
97
99
|
if (router === null) return null;
|
|
98
100
|
const ids = kernel_ids(router);
|
|
99
|
-
|
|
101
|
+
// Scope only — the baseline and the comparison are untouched. An empty kernel
|
|
102
|
+
// list used to collapse into the same `null` as an unreadable router, so a
|
|
103
|
+
// router that compiled to zero kernel rules was reported as a missing file;
|
|
104
|
+
// it is a dead scan scope, and a digest over no bodies is a constant that
|
|
105
|
+
// would then look byte-stable forever.
|
|
106
|
+
assertScanned({
|
|
107
|
+
gate: 'check_kernel_prefix_stability',
|
|
108
|
+
scanned: ids.length,
|
|
109
|
+
units: 'kernel rule(s)',
|
|
110
|
+
roots: ['dist/router.json (kernel)', 'dist/agent-src/rules'],
|
|
111
|
+
});
|
|
100
112
|
return compute_prefix(ids, (id) => fs.readFileSync(path.join(RULES_DIR, `${id}.md`), 'utf-8'));
|
|
101
113
|
}
|
|
102
114
|
|
|
@@ -118,6 +130,12 @@ function main(argv: string[]): number {
|
|
|
118
130
|
try {
|
|
119
131
|
current = _live_prefix();
|
|
120
132
|
} catch (e) {
|
|
133
|
+
// 1 is the documented "file error" code — the gate could not run. 2 is
|
|
134
|
+
// reserved for drift, i.e. a prefix that was read and did change.
|
|
135
|
+
if (e instanceof DeadScopeError) {
|
|
136
|
+
process.stderr.write(`error: ${e.message}\n`);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
121
139
|
process.stderr.write(`error: cannot read kernel bodies: ${(e as Error).message}\n`);
|
|
122
140
|
return 1;
|
|
123
141
|
}
|
|
@@ -37,6 +37,7 @@ import * as path from 'node:path';
|
|
|
37
37
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
38
38
|
import { KERNEL_RULE_FILENAMES } from './_lib/kernel_rules.js';
|
|
39
39
|
import { runCountedProbe } from './_lib/counted_probe.js';
|
|
40
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
40
41
|
|
|
41
42
|
const KERNEL_RULES: ReadonlySet<string> = KERNEL_RULE_FILENAMES;
|
|
42
43
|
|
|
@@ -55,6 +56,8 @@ const KERNEL_RULES: ReadonlySet<string> = KERNEL_RULE_FILENAMES;
|
|
|
55
56
|
*/
|
|
56
57
|
const KERNEL_DIR = 'src/rules';
|
|
57
58
|
const DEFAULT_LABEL = 'bundled-always-rules-acknowledged';
|
|
59
|
+
// src/scripts/check_kernel_rule_bundle.ts → two levels up is the repo root.
|
|
60
|
+
const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
58
61
|
|
|
59
62
|
function _git_changed_files(base_ref: string): string[] {
|
|
60
63
|
// Bounded read: a truncated diff would shrink the change set and let the
|
|
@@ -188,6 +191,26 @@ function parse_args(argv: readonly string[]): Args {
|
|
|
188
191
|
export function main(argv?: readonly string[]): number {
|
|
189
192
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
190
193
|
|
|
194
|
+
// This gate has no corpus — it matches changed paths against a fixed set of
|
|
195
|
+
// 9 kernel rules, so its scope IS that watch list. It has already been
|
|
196
|
+
// inert once for exactly this reason (see KERNEL_DIR above): a prefix that
|
|
197
|
+
// no longer exists never matches, and every kernel-rule edit scored
|
|
198
|
+
// "no kernel rule touched". Exit 3 (internal error) over 1 (bundle
|
|
199
|
+
// violated) — a phantom watch list means the gate could not run.
|
|
200
|
+
try {
|
|
201
|
+
assertWatchlistResolves({
|
|
202
|
+
gate: 'check_kernel_rule_bundle',
|
|
203
|
+
candidates: [...KERNEL_RULES].sort().map((name) => `${KERNEL_DIR}/${name}`),
|
|
204
|
+
repoRoot: REPO,
|
|
205
|
+
});
|
|
206
|
+
} catch (exc) {
|
|
207
|
+
if (exc instanceof DeadScopeError) {
|
|
208
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
209
|
+
return 3;
|
|
210
|
+
}
|
|
211
|
+
throw exc;
|
|
212
|
+
}
|
|
213
|
+
|
|
191
214
|
// Python: args.files or _git_changed_files(...). An empty list from
|
|
192
215
|
// `--files` (no values) is falsy in Python → falls through to git diff.
|
|
193
216
|
const files =
|
|
@@ -39,6 +39,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
39
39
|
// byte-faithful for behaviour (the only observable effect is the --global path).
|
|
40
40
|
import * as _kg from './_lib/knowledge_global.js';
|
|
41
41
|
import * as _kgr from './_lib/knowledge_global_redaction.js';
|
|
42
|
+
import { assertScanned } from './_lib/scan_scope.js';
|
|
42
43
|
|
|
43
44
|
// ROOT = Path(__file__).resolve().parent.parent.parent — src/scripts/ → repo root.
|
|
44
45
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
@@ -373,6 +374,18 @@ function main(): number {
|
|
|
373
374
|
}
|
|
374
375
|
|
|
375
376
|
const cards = _sortedGlobMd(card_dir).filter((p) => path.basename(p).toLowerCase() !== 'readme.md');
|
|
377
|
+
assertScanned({
|
|
378
|
+
gate: 'check_knowledge_cards',
|
|
379
|
+
scanned: cards.length,
|
|
380
|
+
units: 'knowledge card(s)',
|
|
381
|
+
roots: [path.relative(ROOT, card_dir) || card_dir],
|
|
382
|
+
allowEmpty:
|
|
383
|
+
'OPTIONAL_INPUT: cards are project-authored runtime content under agents/knowledge/ '
|
|
384
|
+
+ '(or a --dir / --global store), never shipped package source. Delete the root and '
|
|
385
|
+
+ 'zero still reads as "this project has captured no cards yet" — which is why both '
|
|
386
|
+
+ 'the missing-dir and empty-dir paths are pinned as exit 0 by the CLI contract '
|
|
387
|
+
+ 'below. A stale root cannot hide here: --dir is caller-supplied per invocation.',
|
|
388
|
+
});
|
|
376
389
|
if (cards.length === 0) {
|
|
377
390
|
process.stdout.write('No knowledge cards found — nothing to check.\n');
|
|
378
391
|
return 0;
|
|
@@ -29,6 +29,8 @@ import process from 'node:process';
|
|
|
29
29
|
import { fileURLToPath } from 'node:url';
|
|
30
30
|
import YAML from 'yaml';
|
|
31
31
|
|
|
32
|
+
import { assertScanned } from './_lib/scan_scope.js';
|
|
33
|
+
|
|
32
34
|
const PROG = 'check_knowledge_pages.ts';
|
|
33
35
|
const PAGE_LINE_BUDGET = 200;
|
|
34
36
|
const CONTESTED_WARN_COUNT = 2;
|
|
@@ -135,6 +137,14 @@ function listMarkdownFiles(dirPath: string): string[] {
|
|
|
135
137
|
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
/** Pages `lintAll` reads — the corpus, independent of how many warn. */
|
|
141
|
+
function countPages(knowledgeRoot: string): number {
|
|
142
|
+
return TYPED_DIRS.reduce(
|
|
143
|
+
(n, dir) => n + listMarkdownFiles(path.join(knowledgeRoot, dir)).length,
|
|
144
|
+
0,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
138
148
|
export function lintAll(knowledgeRoot: string, today: Date = new Date()): Warning[] {
|
|
139
149
|
const warnings: Warning[] = [];
|
|
140
150
|
for (const dir of TYPED_DIRS) {
|
|
@@ -187,6 +197,19 @@ export function main(argv: string[]): number {
|
|
|
187
197
|
|
|
188
198
|
const knowledgeRoot = path.join(dir, 'agents', 'knowledge');
|
|
189
199
|
const warnings = lintAll(knowledgeRoot);
|
|
200
|
+
// Pages read, not warnings emitted — this lint never fails the build, so a
|
|
201
|
+
// moved root would print "no warnings" indefinitely with nothing behind it.
|
|
202
|
+
assertScanned({
|
|
203
|
+
gate: 'check_knowledge_pages',
|
|
204
|
+
scanned: countPages(knowledgeRoot),
|
|
205
|
+
units: 'knowledge page(s)',
|
|
206
|
+
roots: TYPED_DIRS.map((d) => path.join('agents', 'knowledge', d)),
|
|
207
|
+
allowEmpty:
|
|
208
|
+
'OPTIONAL_INPUT: the lifecycle-typed pages are project-authored runtime content, '
|
|
209
|
+
+ 'not shipped package source, and the four typed subdirs are created on first '
|
|
210
|
+
+ 'capture. Delete them and zero still reads as "this project has written no '
|
|
211
|
+
+ 'knowledge pages yet" — the state every consumer starts in.',
|
|
212
|
+
});
|
|
190
213
|
|
|
191
214
|
if (format === 'json') {
|
|
192
215
|
process.stdout.write(JSON.stringify(warnings, null, 2) + '\n');
|
|
@@ -29,6 +29,8 @@ import process from 'node:process';
|
|
|
29
29
|
import { fileURLToPath } from 'node:url';
|
|
30
30
|
import YAML from 'yaml';
|
|
31
31
|
|
|
32
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
33
|
+
|
|
32
34
|
const PROG = 'check_knowledge_sharing.ts';
|
|
33
35
|
const CREATION_BUDGET_WARN = 5;
|
|
34
36
|
const FRONTMATTER_RE = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n/;
|
|
@@ -150,6 +152,31 @@ export function main(argv: string[]): number {
|
|
|
150
152
|
return 3;
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
// Diff-driven: the corpus is whatever is staged, and an empty index is the
|
|
156
|
+
// normal state of a clean tree — so zero is allowed here. The dangerous
|
|
157
|
+
// empty (git itself failing) is NOT swallowed: it exits 3 above rather than
|
|
158
|
+
// reaching this point with an empty list. Exit 3 (internal error) is the
|
|
159
|
+
// code a dead scope would share, for the same "could not run" reason.
|
|
160
|
+
try {
|
|
161
|
+
assertScanned({
|
|
162
|
+
gate: 'check_knowledge_sharing',
|
|
163
|
+
scanned: staged.length,
|
|
164
|
+
units: 'staged path(s)',
|
|
165
|
+
roots: ['git diff --cached --name-status'],
|
|
166
|
+
allowEmpty:
|
|
167
|
+
'EMPTY_VALID: nothing staged means there is no commit to judge — an absent ' +
|
|
168
|
+
'question, not an empty corpus. Deletion test: deleting agents/knowledge/ ' +
|
|
169
|
+
'would not produce this zero (the staged deletions would themselves show up ' +
|
|
170
|
+
'in the diff); only an empty index does, and that is a clean tree.',
|
|
171
|
+
});
|
|
172
|
+
} catch (err) {
|
|
173
|
+
if (err instanceof DeadScopeError) {
|
|
174
|
+
process.stderr.write(`❌ ${err.message}\n`);
|
|
175
|
+
return 3;
|
|
176
|
+
}
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
|
|
153
180
|
const { blocked, warnings } = checkSharing(staged, gitStagedContent);
|
|
154
181
|
|
|
155
182
|
for (const w of warnings) {
|
|
@@ -20,6 +20,8 @@ import * as fs from 'node:fs';
|
|
|
20
20
|
import * as path from 'node:path';
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
22
|
|
|
23
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
24
|
+
|
|
23
25
|
// Umlauts and German-only characters.
|
|
24
26
|
const UMLAUT_RE = /[äöüÄÖÜß]/g;
|
|
25
27
|
|
|
@@ -247,6 +249,7 @@ export function main(argv?: readonly string[]): number {
|
|
|
247
249
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
248
250
|
|
|
249
251
|
const all_violations: Violation[] = [];
|
|
252
|
+
let scanned = 0;
|
|
250
253
|
for (const raw_path of args.paths) {
|
|
251
254
|
if (!_exists(raw_path)) {
|
|
252
255
|
process.stderr.write(`⚠️ Not found: ${raw_path}\n`);
|
|
@@ -256,9 +259,30 @@ export function main(argv?: readonly string[]): number {
|
|
|
256
259
|
process.stderr.write(`⚠️ Skipping non-.md: ${raw_path}\n`);
|
|
257
260
|
continue;
|
|
258
261
|
}
|
|
262
|
+
scanned += 1;
|
|
259
263
|
all_violations.push(...scan_file(raw_path));
|
|
260
264
|
}
|
|
261
265
|
|
|
266
|
+
// The caller expands the path list (`find docs -name '*.md' …`), so a moved
|
|
267
|
+
// docs tree hands this gate paths that every skip above swallows into a
|
|
268
|
+
// stderr warning while stdout still reports "No German content detected".
|
|
269
|
+
// Exit 3 is the documented "internal error" code — the gate could not run;
|
|
270
|
+
// 1 means it read files and found German in them.
|
|
271
|
+
try {
|
|
272
|
+
assertScanned({
|
|
273
|
+
gate: 'check_md_language',
|
|
274
|
+
scanned,
|
|
275
|
+
units: '.md file(s)',
|
|
276
|
+
roots: [`${String(args.paths.length)} caller-supplied path(s)`],
|
|
277
|
+
});
|
|
278
|
+
} catch (exc) {
|
|
279
|
+
if (exc instanceof DeadScopeError) {
|
|
280
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
281
|
+
return 3;
|
|
282
|
+
}
|
|
283
|
+
throw exc;
|
|
284
|
+
}
|
|
285
|
+
|
|
262
286
|
if (args.format === 'json') {
|
|
263
287
|
process.stdout.write(JSON.stringify(all_violations, null, 2) + '\n');
|
|
264
288
|
} else if (all_violations.length === 0) {
|
|
@@ -21,6 +21,8 @@ import * as path from 'node:path';
|
|
|
21
21
|
import * as fs from 'node:fs';
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
23
|
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
25
|
+
|
|
24
26
|
const _FILE = fileURLToPath(import.meta.url);
|
|
25
27
|
|
|
26
28
|
export interface MediaTool {
|
|
@@ -71,6 +73,27 @@ export function missingTools(tools: MediaTool[] = MEDIA_TOOLS): MediaTool[] {
|
|
|
71
73
|
|
|
72
74
|
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
73
75
|
const quiet = argv.includes('--quiet');
|
|
76
|
+
|
|
77
|
+
// The scan scope is the declared tool list, not a directory: an empty
|
|
78
|
+
// MEDIA_TOOLS probes nothing and still prints the ✅ "all present" line.
|
|
79
|
+
// Deletion test: emptying the declaration would legitimately yield 0 only
|
|
80
|
+
// if "no tooling is required", which is never true for a prereq check.
|
|
81
|
+
// Exit 1 is the only failure code this CLI has, so a dead scope shares it.
|
|
82
|
+
try {
|
|
83
|
+
assertScanned({
|
|
84
|
+
gate: 'check_media_deps',
|
|
85
|
+
scanned: MEDIA_TOOLS.length,
|
|
86
|
+
units: 'declared media tool(s)',
|
|
87
|
+
roots: ['MEDIA_TOOLS (declared in src/scripts/check_media_deps.ts)'],
|
|
88
|
+
});
|
|
89
|
+
} catch (exc) {
|
|
90
|
+
if (exc instanceof DeadScopeError) {
|
|
91
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
throw exc;
|
|
95
|
+
}
|
|
96
|
+
|
|
74
97
|
const missing = missingTools();
|
|
75
98
|
if (missing.length === 0) {
|
|
76
99
|
if (!quiet) {
|
|
@@ -53,6 +53,8 @@ import process from 'node:process';
|
|
|
53
53
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
54
54
|
import YAML, { parseDocument } from 'yaml';
|
|
55
55
|
|
|
56
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
57
|
+
|
|
56
58
|
type Severity = 'error' | 'warning' | 'info';
|
|
57
59
|
|
|
58
60
|
const REQUIRED_KEYS: ReadonlySet<string> = new Set([
|
|
@@ -722,11 +724,34 @@ function main(): number {
|
|
|
722
724
|
}
|
|
723
725
|
return 0;
|
|
724
726
|
}
|
|
727
|
+
// An ABSENT root stays a pinned exit-0 no-op (the branch above; the memory
|
|
728
|
+
// tree is an optional surface and `task check-memory` short-circuits it).
|
|
729
|
+
// A root that EXISTS but walks to zero `*.yml` is the case nothing covered:
|
|
730
|
+
// entries moved to a subtree this walk does not reach, and the validator
|
|
731
|
+
// reported "0 error(s)" over an empty read. No `allowEmpty` for that half —
|
|
732
|
+
// a present-but-unreadable corpus is blindness, not a clean bill of health.
|
|
733
|
+
// Exit 1 is this gate's only failure code (see `_emit`).
|
|
734
|
+
const ymls = _rglobYmlSorted(root);
|
|
735
|
+
try {
|
|
736
|
+
assertScanned({
|
|
737
|
+
gate: 'check_memory',
|
|
738
|
+
scanned: ymls.length,
|
|
739
|
+
units: 'memory entry file(s)',
|
|
740
|
+
roots: [`${root}/**/*.yml`],
|
|
741
|
+
});
|
|
742
|
+
} catch (exc) {
|
|
743
|
+
if (exc instanceof DeadScopeError) {
|
|
744
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
745
|
+
return 1;
|
|
746
|
+
}
|
|
747
|
+
throw exc;
|
|
748
|
+
}
|
|
749
|
+
|
|
725
750
|
const criticalCounts: Record<string, number> = {};
|
|
726
751
|
for (const key of Object.keys(_TYPE_COUNTS)) {
|
|
727
752
|
delete _TYPE_COUNTS[key];
|
|
728
753
|
}
|
|
729
|
-
for (const yml of
|
|
754
|
+
for (const yml of ymls) {
|
|
730
755
|
_validateFile(yml, findings, criticalCounts);
|
|
731
756
|
}
|
|
732
757
|
// Tier-0 inflation warning — soft cap on `priority: critical` per type.
|
|
@@ -34,6 +34,7 @@ import process from 'node:process';
|
|
|
34
34
|
import { fileURLToPath } from 'node:url';
|
|
35
35
|
import YAML from 'yaml';
|
|
36
36
|
|
|
37
|
+
import { assertScanned } from './_lib/scan_scope.js';
|
|
37
38
|
import { jaccardSimilarity } from './_lib/text_similarity.js';
|
|
38
39
|
|
|
39
40
|
const PROG = 'check_memory_contradiction.ts';
|
|
@@ -234,7 +235,21 @@ function main(argv: string[]): number {
|
|
|
234
235
|
|
|
235
236
|
let hit: ContradictionHit | null;
|
|
236
237
|
try {
|
|
237
|
-
|
|
238
|
+
const existing = loadCuratedEntries(path.resolve(memoryRoot), type);
|
|
239
|
+
// Curated entries READ — the corpus every verdict below is decided
|
|
240
|
+
// against. Zero of them makes "no contradiction" unfalsifiable.
|
|
241
|
+
assertScanned({
|
|
242
|
+
gate: 'check_memory_contradiction',
|
|
243
|
+
scanned: existing.length,
|
|
244
|
+
units: 'curated entry(ies)',
|
|
245
|
+
roots: [path.join(memoryRoot, type), path.join(memoryRoot, `${type}.yml`)],
|
|
246
|
+
allowEmpty:
|
|
247
|
+
'OPTIONAL_INPUT: the per-type curated store is created by the first '
|
|
248
|
+
+ '/memory promote of that type. Delete it and zero still reads as "nothing '
|
|
249
|
+
+ 'of this type has been curated yet" — a state every consumer starts in, and '
|
|
250
|
+
+ 'one in which no contradiction can exist by definition.',
|
|
251
|
+
});
|
|
252
|
+
hit = findContradiction(key, body, existing);
|
|
238
253
|
} catch (exc) {
|
|
239
254
|
process.stderr.write(`${PROG}: internal error: ${String(exc)}\n`);
|
|
240
255
|
return 3;
|