@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
|
@@ -25,6 +25,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
25
25
|
import { parse as parseYaml } from 'yaml';
|
|
26
26
|
|
|
27
27
|
import { project_settings_path } from './_lib/agent_settings.js';
|
|
28
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
28
29
|
|
|
29
30
|
const _HERE = fileURLToPath(import.meta.url);
|
|
30
31
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -148,14 +149,16 @@ function _isPlainDict(v: unknown): v is Record<string, unknown> {
|
|
|
148
149
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
/** @returns the number of platform/event concern lists inspected. */
|
|
151
153
|
function _check_concern_counts(
|
|
152
154
|
manifest: Manifest,
|
|
153
155
|
max_per_event: number,
|
|
154
156
|
warnings: string[],
|
|
155
|
-
):
|
|
157
|
+
): number {
|
|
158
|
+
let inspected = 0;
|
|
156
159
|
const platforms = manifest['platforms'] ?? {};
|
|
157
160
|
if (!_isPlainDict(platforms)) {
|
|
158
|
-
return;
|
|
161
|
+
return inspected;
|
|
159
162
|
}
|
|
160
163
|
for (const [plat, block] of Object.entries(platforms)) {
|
|
161
164
|
if (!_isPlainDict(block) || block['fallback_only']) {
|
|
@@ -165,6 +168,7 @@ function _check_concern_counts(
|
|
|
165
168
|
if (!Array.isArray(names)) {
|
|
166
169
|
continue;
|
|
167
170
|
}
|
|
171
|
+
inspected += 1;
|
|
168
172
|
const count = names.length;
|
|
169
173
|
if (count > max_per_event) {
|
|
170
174
|
warnings.push(
|
|
@@ -175,18 +179,22 @@ function _check_concern_counts(
|
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
181
|
}
|
|
182
|
+
return inspected;
|
|
178
183
|
}
|
|
179
184
|
|
|
180
|
-
|
|
185
|
+
/** @returns the number of concern declarations inspected. */
|
|
186
|
+
function _check_fail_closed_tier(manifest: Manifest, tier1: string[], errors: string[]): number {
|
|
187
|
+
let inspected = 0;
|
|
181
188
|
const concerns = manifest['concerns'] ?? {};
|
|
182
189
|
if (!_isPlainDict(concerns)) {
|
|
183
|
-
return;
|
|
190
|
+
return inspected;
|
|
184
191
|
}
|
|
185
192
|
const allowed = new Set(tier1);
|
|
186
193
|
for (const [name, spec] of Object.entries(concerns)) {
|
|
187
194
|
if (!_isPlainDict(spec)) {
|
|
188
195
|
continue;
|
|
189
196
|
}
|
|
197
|
+
inspected += 1;
|
|
190
198
|
if (spec['fail_closed'] === true && !allowed.has(name)) {
|
|
191
199
|
errors.push(
|
|
192
200
|
`concerns.${name}: fail_closed=true but not declared in ` +
|
|
@@ -195,6 +203,7 @@ function _check_fail_closed_tier(manifest: Manifest, tier1: string[], errors: st
|
|
|
195
203
|
);
|
|
196
204
|
}
|
|
197
205
|
}
|
|
206
|
+
return inspected;
|
|
198
207
|
}
|
|
199
208
|
|
|
200
209
|
export function lint(
|
|
@@ -227,8 +236,32 @@ export function lint(
|
|
|
227
236
|
|
|
228
237
|
const warnings: string[] = [];
|
|
229
238
|
const errors: string[] = [];
|
|
230
|
-
|
|
231
|
-
|
|
239
|
+
const inspected =
|
|
240
|
+
_check_concern_counts(manifest, max_per_event, warnings) +
|
|
241
|
+
_check_fail_closed_tier(manifest, tier1, errors);
|
|
242
|
+
|
|
243
|
+
// The manifest loads and parses, then every walk below silently tolerates a
|
|
244
|
+
// shape it does not recognise — no `platforms:`, no `concerns:`, a renamed
|
|
245
|
+
// top-level key — and the gate exits 0 having budgeted nothing. Counts units
|
|
246
|
+
// INSPECTED (platform/event concern lists + concern declarations), not
|
|
247
|
+
// warnings or errors. Exit 1 is the load-failure code and is the right one
|
|
248
|
+
// here: the manifest was readable but carried nothing this gate governs; 2
|
|
249
|
+
// is reserved for a hard-fail WITH violations.
|
|
250
|
+
try {
|
|
251
|
+
assertScanned({
|
|
252
|
+
gate: 'lint_hook_concern_budget',
|
|
253
|
+
scanned: inspected,
|
|
254
|
+
units: 'manifest concern unit(s)',
|
|
255
|
+
roots: [manifest_path],
|
|
256
|
+
});
|
|
257
|
+
} catch (exc) {
|
|
258
|
+
if (exc instanceof DeadScopeError) {
|
|
259
|
+
// The message already carries the gate name as its prefix.
|
|
260
|
+
process.stderr.write(`${exc.message}\n`);
|
|
261
|
+
return 1;
|
|
262
|
+
}
|
|
263
|
+
throw exc;
|
|
264
|
+
}
|
|
232
265
|
|
|
233
266
|
for (const w of warnings) {
|
|
234
267
|
process.stderr.write(`warn: ${w}\n`);
|
|
@@ -28,6 +28,8 @@ import path from "node:path";
|
|
|
28
28
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
29
29
|
import { parse as parseYaml } from "yaml";
|
|
30
30
|
|
|
31
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
32
|
+
|
|
31
33
|
// src/scripts/lint_hook_manifest.ts → two levels up is the repo root.
|
|
32
34
|
const REPO_ROOT = path.resolve(
|
|
33
35
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
@@ -322,6 +324,26 @@ export function lint(manifestPath: string, strict: boolean): number {
|
|
|
322
324
|
process.stderr.write(`error: ${e}\n`);
|
|
323
325
|
}
|
|
324
326
|
|
|
327
|
+
// Concerns are the units read — each one resolves a script path on disk.
|
|
328
|
+
// Runs after the findings are printed so the existing `'concerns' must be a
|
|
329
|
+
// non-empty mapping` line still reaches the caller. Exit 1, not 2: the
|
|
330
|
+
// manifest loaded fine (2 is reserved for a file that could not be read or
|
|
331
|
+
// parsed); what is empty is its declared scope.
|
|
332
|
+
try {
|
|
333
|
+
assertScanned({
|
|
334
|
+
gate: "lint_hook_manifest",
|
|
335
|
+
scanned: concernNames.size,
|
|
336
|
+
units: "concern(s)",
|
|
337
|
+
roots: [manifestPath],
|
|
338
|
+
});
|
|
339
|
+
} catch (exc) {
|
|
340
|
+
if (exc instanceof DeadScopeError) {
|
|
341
|
+
process.stderr.write(`error: ${exc.message}\n`);
|
|
342
|
+
return 1;
|
|
343
|
+
}
|
|
344
|
+
throw exc;
|
|
345
|
+
}
|
|
346
|
+
|
|
325
347
|
if (errors.length > 0) {
|
|
326
348
|
return 1;
|
|
327
349
|
}
|
|
@@ -28,6 +28,7 @@ import * as fs from 'node:fs';
|
|
|
28
28
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
29
29
|
|
|
30
30
|
import * as sl from './_lib/security_lint.js';
|
|
31
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
31
32
|
|
|
32
33
|
export const CHECK = 'instruction-smuggling';
|
|
33
34
|
|
|
@@ -152,12 +153,34 @@ export function main(argv: readonly string[] | null = null): number {
|
|
|
152
153
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
153
154
|
|
|
154
155
|
const findings: sl.Finding[] = [];
|
|
156
|
+
let corpusFiles = 0;
|
|
155
157
|
for (const sf of sl.iter_corpus()) {
|
|
158
|
+
corpusFiles += 1;
|
|
156
159
|
for (const h of _scan(sf)) {
|
|
157
160
|
findings.push(h);
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
// `iter_corpus` skips a root that does not exist, so a moved corpus yields
|
|
165
|
+
// zero files and this security lint reports "clean" — and the umbrella
|
|
166
|
+
// aggregate reads that as 0 blocking findings. Count files READ, not
|
|
167
|
+
// findings. Exit 1 is this CLI's only failure code (2 is reserved for the
|
|
168
|
+
// pinned argparse usage error), so it carries "could not run" here.
|
|
169
|
+
try {
|
|
170
|
+
assertScanned({
|
|
171
|
+
gate: 'lint_instruction_smuggling',
|
|
172
|
+
scanned: corpusFiles,
|
|
173
|
+
units: 'corpus file(s)',
|
|
174
|
+
roots: sl.DEFAULT_SCAN_ROOTS,
|
|
175
|
+
});
|
|
176
|
+
} catch (exc) {
|
|
177
|
+
if (exc instanceof DeadScopeError) {
|
|
178
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
throw exc;
|
|
182
|
+
}
|
|
183
|
+
|
|
161
184
|
if (args.json) {
|
|
162
185
|
const payload = sl.py_json_dumps_indent2(findings.map((f) => f.toDict()));
|
|
163
186
|
process.stdout.write(payload + '\n');
|
|
@@ -31,6 +31,8 @@ import * as path from 'node:path';
|
|
|
31
31
|
import process from 'node:process';
|
|
32
32
|
import { fileURLToPath } from 'node:url';
|
|
33
33
|
|
|
34
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
35
|
+
|
|
34
36
|
const PROG = 'lint_knowledge_scale.ts';
|
|
35
37
|
|
|
36
38
|
export const INTAKE_EVENTS_MAX = 2000;
|
|
@@ -109,7 +111,12 @@ export function collectTypeCounts(root: string): Map<string, number> {
|
|
|
109
111
|
return counts;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
/** Out-param so the caller can assert the scope it just walked. */
|
|
115
|
+
export interface ScanStats {
|
|
116
|
+
corpusFiles: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function runChecks(root: string, stats?: ScanStats): Warning[] {
|
|
113
120
|
const warnings: Warning[] = [];
|
|
114
121
|
|
|
115
122
|
// 1. Intake scale.
|
|
@@ -156,6 +163,9 @@ export function runChecks(root: string): Warning[] {
|
|
|
156
163
|
});
|
|
157
164
|
}
|
|
158
165
|
}
|
|
166
|
+
if (stats) {
|
|
167
|
+
stats.corpusFiles = total;
|
|
168
|
+
}
|
|
159
169
|
if (total > CORPUS_FILES_MAX) {
|
|
160
170
|
warnings.push({
|
|
161
171
|
rule: 'corpus-scale',
|
|
@@ -252,12 +262,32 @@ function main(argv: string[]): number {
|
|
|
252
262
|
}
|
|
253
263
|
|
|
254
264
|
let warnings: Warning[];
|
|
265
|
+
const stats: ScanStats = { corpusFiles: 0 };
|
|
255
266
|
try {
|
|
256
|
-
warnings = runChecks(path.resolve(dir));
|
|
267
|
+
warnings = runChecks(path.resolve(dir), stats);
|
|
257
268
|
} catch (exc) {
|
|
258
269
|
process.stderr.write(`${PROG}: internal error: ${String(exc)}\n`);
|
|
259
270
|
return 3;
|
|
260
271
|
}
|
|
272
|
+
try {
|
|
273
|
+
assertScanned({
|
|
274
|
+
gate: 'lint_knowledge_scale',
|
|
275
|
+
scanned: stats.corpusFiles,
|
|
276
|
+
units: 'knowledge/memory file(s)',
|
|
277
|
+
roots: ['agents/knowledge', 'agents/memory'],
|
|
278
|
+
allowEmpty:
|
|
279
|
+
'OPTIONAL_INPUT: the memory/knowledge substrate accumulates in the consumer ' +
|
|
280
|
+
'tree over time — a project that has recorded nothing yet has no corpus, and ' +
|
|
281
|
+
'the tripwires it would trip are all upper bounds. Delete both roots and zero ' +
|
|
282
|
+
'still means "nothing recorded", never "the scale checks stopped looking".',
|
|
283
|
+
});
|
|
284
|
+
} catch (exc) {
|
|
285
|
+
if (exc instanceof DeadScopeError) {
|
|
286
|
+
process.stderr.write(`${PROG}: ${exc.message}\n`);
|
|
287
|
+
return 3;
|
|
288
|
+
}
|
|
289
|
+
throw exc;
|
|
290
|
+
}
|
|
261
291
|
|
|
262
292
|
if (format === 'json') {
|
|
263
293
|
process.stdout.write(JSON.stringify({ warnings }, null, 2) + '\n');
|
|
@@ -24,6 +24,8 @@ import * as fs from 'node:fs';
|
|
|
24
24
|
import * as path from 'node:path';
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
26
|
|
|
27
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
28
|
+
|
|
27
29
|
const QUIET = process.argv.includes('--quiet');
|
|
28
30
|
|
|
29
31
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -246,7 +248,36 @@ export function lintLegalPack(skillsDir: string = SKILLS_DIR): Violation[] {
|
|
|
246
248
|
return violations;
|
|
247
249
|
}
|
|
248
250
|
|
|
251
|
+
/** SKILL.md files under `skillsDir` — the corpus `lintLegalPack` walks. */
|
|
252
|
+
function countSkillFiles(skillsDir: string = SKILLS_DIR): number {
|
|
253
|
+
try {
|
|
254
|
+
return fs
|
|
255
|
+
.readdirSync(skillsDir)
|
|
256
|
+
.filter((d) => fs.existsSync(path.join(skillsDir, d, 'SKILL.md'))).length;
|
|
257
|
+
} catch {
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
249
262
|
export function main(): number {
|
|
263
|
+
// The unit is every SKILL.md, not the `packs: [legal-review-prep]` subset:
|
|
264
|
+
// the pack is opt-in, so a zero-legal-pack-skills count is a normal state
|
|
265
|
+
// and asserting on it would prove nothing. Over a vanished src/skills the
|
|
266
|
+
// subset is zero too — indistinguishable from "the pack is not installed".
|
|
267
|
+
try {
|
|
268
|
+
assertScanned({
|
|
269
|
+
gate: 'lint_legal_pack',
|
|
270
|
+
scanned: countSkillFiles(),
|
|
271
|
+
units: 'SKILL.md file(s)',
|
|
272
|
+
roots: ['src/skills'],
|
|
273
|
+
});
|
|
274
|
+
} catch (exc) {
|
|
275
|
+
if (exc instanceof DeadScopeError) {
|
|
276
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
277
|
+
return 1;
|
|
278
|
+
}
|
|
279
|
+
throw exc;
|
|
280
|
+
}
|
|
250
281
|
const violations = lintLegalPack();
|
|
251
282
|
if (violations.length === 0) {
|
|
252
283
|
_print('✅ legal pack — all legal-pack skills carry the attorney-review line + Jurisdiction tag');
|
|
@@ -27,6 +27,8 @@ import fs from "node:fs";
|
|
|
27
27
|
import path from "node:path";
|
|
28
28
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
29
29
|
|
|
30
|
+
import { assertWatchlistResolves, DeadScopeError } from "./_lib/scan_scope.js";
|
|
31
|
+
|
|
30
32
|
const ROOT = ".";
|
|
31
33
|
const MARKETPLACE = path.join(ROOT, ".claude-plugin", "marketplace.json");
|
|
32
34
|
const PACKAGE_JSON = path.join(ROOT, "package.json");
|
|
@@ -87,6 +89,30 @@ function require_key(
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
export function main(): number {
|
|
92
|
+
// No corpus to count — the shim is exactly one manifest plus one pointer
|
|
93
|
+
// skill, and every count the shape offers (plugins, skill entries, dirs under
|
|
94
|
+
// .claude-plugin/skills/) is a value an existing negative case drives to zero
|
|
95
|
+
// on purpose. What CAN vanish is the fixed set of paths this resolves against
|
|
96
|
+
// cwd, so the watch list is the honest guard: run from the wrong directory
|
|
97
|
+
// and the gate now says so instead of reporting a missing manifest.
|
|
98
|
+
try {
|
|
99
|
+
assertWatchlistResolves({
|
|
100
|
+
gate: "lint_marketplace",
|
|
101
|
+
candidates: [
|
|
102
|
+
".claude-plugin/marketplace.json",
|
|
103
|
+
"package.json",
|
|
104
|
+
`${POINTER_SKILL_ENTRY.replace(/^\.\//, "")}/SKILL.md`,
|
|
105
|
+
],
|
|
106
|
+
repoRoot: path.resolve(ROOT),
|
|
107
|
+
});
|
|
108
|
+
} catch (exc) {
|
|
109
|
+
if (exc instanceof DeadScopeError) {
|
|
110
|
+
process.stdout.write(`❌ ${exc.message}\n`);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
throw exc;
|
|
114
|
+
}
|
|
115
|
+
|
|
90
116
|
if (!_exists(MARKETPLACE)) {
|
|
91
117
|
process.stdout.write(`❌ ${MARKETPLACE} not found\n`);
|
|
92
118
|
return 1;
|
|
@@ -25,6 +25,8 @@ import * as fs from 'node:fs';
|
|
|
25
25
|
import * as path from 'node:path';
|
|
26
26
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
27
|
|
|
28
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
29
|
+
|
|
28
30
|
const _HERE = fileURLToPath(import.meta.url);
|
|
29
31
|
|
|
30
32
|
// src/scripts/lint_marketplace_install_completeness.ts → two dirs up is the
|
|
@@ -150,6 +152,27 @@ function lint(
|
|
|
150
152
|
const commands = load_hook_commands(hooks_path);
|
|
151
153
|
const known = load_dispatcher_subcommands(dispatch_path);
|
|
152
154
|
|
|
155
|
+
// `load_hook_commands` skips every non-conforming `hooks` shape silently, so
|
|
156
|
+
// a restructured hooks.json yields an empty command list and the gate
|
|
157
|
+
// announces "0 hook command(s) checked" — green. Assert the UNFILTERED list:
|
|
158
|
+
// `checked` below excludes entries whose command line failed to parse, and
|
|
159
|
+
// those are exactly the ones this gate must still report on. Exit 2 is the
|
|
160
|
+
// schema/file-error code ("could not run"); 1 means an unknown subcommand.
|
|
161
|
+
try {
|
|
162
|
+
assertScanned({
|
|
163
|
+
gate: 'lint-marketplace-install',
|
|
164
|
+
scanned: commands.length,
|
|
165
|
+
units: 'hook command(s)',
|
|
166
|
+
roots: [hooks_path],
|
|
167
|
+
});
|
|
168
|
+
} catch (exc) {
|
|
169
|
+
if (exc instanceof DeadScopeError) {
|
|
170
|
+
process.stderr.write(`${exc.message}\n`);
|
|
171
|
+
return 2;
|
|
172
|
+
}
|
|
173
|
+
throw exc;
|
|
174
|
+
}
|
|
175
|
+
|
|
153
176
|
const issues: string[] = [];
|
|
154
177
|
let checked = 0;
|
|
155
178
|
for (const [event, cmd] of commands) {
|
|
@@ -24,6 +24,7 @@ import * as fs from 'node:fs';
|
|
|
24
24
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
25
25
|
|
|
26
26
|
import * as sl from './_lib/security_lint.js';
|
|
27
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
27
28
|
|
|
28
29
|
export const CHECK = 'mcp-config-security';
|
|
29
30
|
|
|
@@ -204,12 +205,35 @@ export function main(argv: readonly string[] | null = null): number {
|
|
|
204
205
|
// scan .md (fenced examples) under the default roots PLUS named MCP configs
|
|
205
206
|
// under src/templates (where the shipped claude_desktop_config template lives).
|
|
206
207
|
const roots = [...sl.DEFAULT_SCAN_ROOTS, 'src/templates'];
|
|
208
|
+
let corpusFiles = 0;
|
|
207
209
|
for (const sf of sl.iter_corpus(roots, ['.md', '.json', '.template'])) {
|
|
210
|
+
corpusFiles += 1;
|
|
208
211
|
for (const h of _scan(sf)) {
|
|
209
212
|
findings.push(h);
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
215
|
|
|
216
|
+
// `iter_corpus` skips a root that does not exist, so a moved
|
|
217
|
+
// `src/templates` (where the only shipped MCP config template lives) leaves
|
|
218
|
+
// this lint scanning `.md` fences alone — or nothing at all — while still
|
|
219
|
+
// printing "clean". Count files READ, not findings. Exit 1 is this CLI's
|
|
220
|
+
// only failure code (2 is the pinned argparse usage error), so it carries
|
|
221
|
+
// "could not run" here.
|
|
222
|
+
try {
|
|
223
|
+
assertScanned({
|
|
224
|
+
gate: 'lint_mcp_config_security',
|
|
225
|
+
scanned: corpusFiles,
|
|
226
|
+
units: 'corpus file(s)',
|
|
227
|
+
roots,
|
|
228
|
+
});
|
|
229
|
+
} catch (exc) {
|
|
230
|
+
if (exc instanceof DeadScopeError) {
|
|
231
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
throw exc;
|
|
235
|
+
}
|
|
236
|
+
|
|
213
237
|
if (args.json) {
|
|
214
238
|
const payload = sl.py_json_dumps_indent2(findings.map((f) => f.toDict()));
|
|
215
239
|
process.stdout.write(payload + '\n');
|
|
@@ -26,6 +26,8 @@ import * as fs from 'node:fs';
|
|
|
26
26
|
import * as path from 'node:path';
|
|
27
27
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
28
28
|
|
|
29
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
30
|
+
|
|
29
31
|
const _HERE = path.resolve(fileURLToPath(import.meta.url));
|
|
30
32
|
// ROOT = Path(__file__).resolve().parents[2]
|
|
31
33
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -295,7 +297,26 @@ function main(argv: readonly string[]): number {
|
|
|
295
297
|
throw e;
|
|
296
298
|
}
|
|
297
299
|
|
|
298
|
-
|
|
300
|
+
const artefacts = [SCHEMA, MANIFEST, ROW_MD, CF_JSON, SERVER_JSON];
|
|
301
|
+
// The per-artefact loop below stays authoritative — it names the FIRST
|
|
302
|
+
// missing file and is the pinned `missing: <path>` contract. This assertion
|
|
303
|
+
// covers the case that loop reports misleadingly: all five gone at once,
|
|
304
|
+
// which is a relocated ROOT rather than an unbuilt artefact, and deserves a
|
|
305
|
+
// message that says so. Exit 1 is the gate's only failure code.
|
|
306
|
+
try {
|
|
307
|
+
assertWatchlistResolves({
|
|
308
|
+
gate: 'lint_mcp_registry_manifest',
|
|
309
|
+
candidates: artefacts.map(_relToRoot),
|
|
310
|
+
repoRoot: ROOT,
|
|
311
|
+
});
|
|
312
|
+
} catch (e) {
|
|
313
|
+
if (e instanceof DeadScopeError) {
|
|
314
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
315
|
+
return 1;
|
|
316
|
+
}
|
|
317
|
+
throw e;
|
|
318
|
+
}
|
|
319
|
+
for (const p of artefacts) {
|
|
299
320
|
if (!fs.existsSync(p)) {
|
|
300
321
|
return _fail(`missing: ${_relToRoot(p)}`);
|
|
301
322
|
}
|
|
@@ -24,6 +24,8 @@ import fs from 'node:fs';
|
|
|
24
24
|
import path from 'node:path';
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
26
|
|
|
27
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
28
|
+
|
|
27
29
|
const _HERE = fileURLToPath(import.meta.url);
|
|
28
30
|
export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
29
31
|
|
|
@@ -73,6 +75,15 @@ function _git(root: string, args: string[]): string {
|
|
|
73
75
|
return execFileSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
/** How many tracked memory files `scanWorktree` has to read (0 when git fails). */
|
|
79
|
+
function _trackedCount(root: string): number {
|
|
80
|
+
try {
|
|
81
|
+
return _git(root, ['ls-files', TRACKED_GLOB_ROOT]).split('\n').filter(Boolean).length;
|
|
82
|
+
} catch {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
/** Scan the current worktree's tracked memory files. */
|
|
77
88
|
export function scanWorktree(root: string): Hit[] {
|
|
78
89
|
const out: Hit[] = [];
|
|
@@ -147,6 +158,15 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
147
158
|
process.stdout.write(
|
|
148
159
|
`scanned ${commits} commit(s), ${blobs} unique blob(s) under ${TRACKED_GLOB_ROOT}\n`,
|
|
149
160
|
);
|
|
161
|
+
// The honest-null verdict rests on this scan having covered the
|
|
162
|
+
// full history; a "zero fires" certification over zero blobs would
|
|
163
|
+
// silently re-certify nothing at all.
|
|
164
|
+
assertScanned({
|
|
165
|
+
gate: 'lint_memory_tripwire',
|
|
166
|
+
scanned: blobs,
|
|
167
|
+
units: 'historical blob(s)',
|
|
168
|
+
roots: [TRACKED_GLOB_ROOT],
|
|
169
|
+
});
|
|
150
170
|
if (hits.length === 0) {
|
|
151
171
|
process.stdout.write('✅ zero tripwire fires across the full history.\n');
|
|
152
172
|
return 0;
|
|
@@ -157,6 +177,15 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
157
177
|
process.stdout.write(`❌ ${hits.length} fire(s). HALT — never rewrite; a human decides.\n`);
|
|
158
178
|
return 1;
|
|
159
179
|
}
|
|
180
|
+
// Count the corpus separately from the hits: `scanWorktree` returns
|
|
181
|
+
// only fires, and "no fires" is the pass state, so an unlistable or
|
|
182
|
+
// moved agents/memory tree would otherwise read as clean.
|
|
183
|
+
assertScanned({
|
|
184
|
+
gate: 'lint_memory_tripwire',
|
|
185
|
+
scanned: _trackedCount(root),
|
|
186
|
+
units: 'tracked memory file(s)',
|
|
187
|
+
roots: [TRACKED_GLOB_ROOT],
|
|
188
|
+
});
|
|
160
189
|
const hits = scanWorktree(root);
|
|
161
190
|
if (hits.length === 0) {
|
|
162
191
|
process.stdout.write('✅ tripwire clean on tracked memory files.\n');
|
|
@@ -168,6 +197,12 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
168
197
|
process.stdout.write(`❌ ${hits.length} fire(s). HALT — never rewrite; a human decides.\n`);
|
|
169
198
|
return 1;
|
|
170
199
|
} catch (exc) {
|
|
200
|
+
if (exc instanceof DeadScopeError) {
|
|
201
|
+
// A dead scope is a tripwire failure, not an internal fault: the
|
|
202
|
+
// gate reports the same HALT code it uses for a fire.
|
|
203
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
204
|
+
return 1;
|
|
205
|
+
}
|
|
171
206
|
process.stderr.write(`lint_memory_tripwire: internal error: ${String(exc)}\n`);
|
|
172
207
|
return 3;
|
|
173
208
|
}
|
|
@@ -35,6 +35,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
35
35
|
|
|
36
36
|
import YAML from 'yaml';
|
|
37
37
|
|
|
38
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
39
|
+
|
|
38
40
|
const _HERE = fileURLToPath(import.meta.url);
|
|
39
41
|
|
|
40
42
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -684,16 +686,30 @@ export function main(argv?: string[]): number {
|
|
|
684
686
|
const mission_schema = _load_schema(MISSION_SCHEMA_PATH);
|
|
685
687
|
const catalog_schema = _load_schema(CATALOG_SCHEMA_PATH);
|
|
686
688
|
|
|
687
|
-
if (!_isDir(MISSIONS_ROOT)) {
|
|
688
|
-
if (!args.quiet) {
|
|
689
|
-
process.stderr.write(`No missions directory found at ${MISSIONS_ROOT}\n`);
|
|
690
|
-
}
|
|
691
|
-
return 0;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
689
|
const all_findings: Finding[] = [];
|
|
695
690
|
const mission_dirs = _sortedMissionDirs();
|
|
696
691
|
|
|
692
|
+
// Supersedes the `No missions directory found → exit 0` short-circuit: this
|
|
693
|
+
// gate is warn-only about what a mission SAYS, never about whether there are
|
|
694
|
+
// missions to read, and a missing root produced the same silent 0 as a clean
|
|
695
|
+
// run. Exit 1 is the hard-failure code (2 is reserved for argparse and
|
|
696
|
+
// schema-load errors), and it fires regardless of --strict — a scope that
|
|
697
|
+
// read nothing is not a warning about a manifest.
|
|
698
|
+
try {
|
|
699
|
+
assertScanned({
|
|
700
|
+
gate: 'lint_missions',
|
|
701
|
+
scanned: mission_dirs.length,
|
|
702
|
+
units: 'mission(s)',
|
|
703
|
+
roots: ['src/missions'],
|
|
704
|
+
});
|
|
705
|
+
} catch (exc) {
|
|
706
|
+
if (exc instanceof DeadScopeError) {
|
|
707
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
708
|
+
return 1;
|
|
709
|
+
}
|
|
710
|
+
throw exc;
|
|
711
|
+
}
|
|
712
|
+
|
|
697
713
|
for (const mission_dir of mission_dirs) {
|
|
698
714
|
const findings = validate_mission(mission_dir, mission_schema, catalog_schema);
|
|
699
715
|
all_findings.push(...findings);
|
|
@@ -23,6 +23,7 @@ import * as path from 'node:path';
|
|
|
23
23
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
24
|
import { parse_frontmatter } from './validate_frontmatter.js';
|
|
25
25
|
import { artefact_roots, iter_commands } from './_lib/agent_src.js';
|
|
26
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
26
27
|
|
|
27
28
|
const _HERE = fileURLToPath(import.meta.url);
|
|
28
29
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -99,6 +100,23 @@ function main(argv: readonly string[]): number {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
// `total` is the artefacts read, `missing` the verdict — a coverage gate
|
|
104
|
+
// whose enumerator yields nothing reports full coverage of nothing.
|
|
105
|
+
try {
|
|
106
|
+
assertScanned({
|
|
107
|
+
gate: 'lint_model_tier_coverage',
|
|
108
|
+
scanned: total,
|
|
109
|
+
units: 'artefact(s)',
|
|
110
|
+
roots: ['src/skills', 'src/agent-src/commands', 'src/domains'],
|
|
111
|
+
});
|
|
112
|
+
} catch (exc) {
|
|
113
|
+
if (exc instanceof DeadScopeError) {
|
|
114
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
throw exc;
|
|
118
|
+
}
|
|
119
|
+
|
|
102
120
|
if (missing.length > 0) {
|
|
103
121
|
for (const [kind, rel] of missing.slice(0, 40)) {
|
|
104
122
|
process.stdout.write(
|
|
@@ -29,6 +29,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
29
29
|
import { parse as parseYaml } from 'yaml';
|
|
30
30
|
|
|
31
31
|
import { iter_all_sources } from './_lib/agent_src.js';
|
|
32
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
32
33
|
|
|
33
34
|
const _HERE = fileURLToPath(import.meta.url);
|
|
34
35
|
|
|
@@ -135,6 +136,24 @@ function main(): number {
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
// `total` is the names read, not the collisions found: uniqueness holds
|
|
140
|
+
// vacuously over an empty name space, so only the corpus count can tell
|
|
141
|
+
// "no collisions" apart from "no artefacts reached the normalizer".
|
|
142
|
+
try {
|
|
143
|
+
assertScanned({
|
|
144
|
+
gate: 'lint_namespace_collisions',
|
|
145
|
+
scanned: total,
|
|
146
|
+
units: 'artefact name(s)',
|
|
147
|
+
roots: ['src/skills', 'src/rules', 'src/agent-src/commands', 'src/domains'],
|
|
148
|
+
});
|
|
149
|
+
} catch (exc) {
|
|
150
|
+
if (exc instanceof DeadScopeError) {
|
|
151
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
throw exc;
|
|
155
|
+
}
|
|
156
|
+
|
|
138
157
|
const collisions = [...seen.entries()].filter(([, v]) => v.length > 1);
|
|
139
158
|
if (collisions.length) {
|
|
140
159
|
// sorted(collisions) — by (namespace, name) tuple, which the key
|