@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
|
@@ -23,6 +23,7 @@ import YAML from 'yaml';
|
|
|
23
23
|
|
|
24
24
|
import { USER_SCOPE_PATHS } from './install.js';
|
|
25
25
|
import { CLAUDE_MARKETPLACE_NAME, CLAUDE_PLUGIN_ID } from './_lib/claude_plugin.js';
|
|
26
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
26
27
|
|
|
27
28
|
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
28
29
|
export const MATRIX_PATH = path.join(REPO_ROOT, 'src', 'config', 'surface-matrix.yml');
|
|
@@ -59,9 +60,24 @@ export function load_surface_matrix(matrixPath: string = MATRIX_PATH): Record<st
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export function main(): number {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
// Replaces the ad-hoc `existsSync(MATRIX_PATH)` precondition. The matrix
|
|
64
|
+
// yaml is the gate's only file input — an emptied `tools:` mapping is
|
|
65
|
+
// already caught by the set-equality against USER_SCOPE_PATHS (a code
|
|
66
|
+
// constant a path migration cannot empty), so a moved file is the one
|
|
67
|
+
// remaining way this gate can go quiet. Exit 1 = "could not run" here; 3
|
|
68
|
+
// stays reserved for an unexpected throw at the CLI boundary.
|
|
69
|
+
try {
|
|
70
|
+
assertWatchlistResolves({
|
|
71
|
+
gate: 'lint_surface_matrix',
|
|
72
|
+
candidates: [path.relative(REPO_ROOT, MATRIX_PATH)],
|
|
73
|
+
repoRoot: REPO_ROOT,
|
|
74
|
+
});
|
|
75
|
+
} catch (e) {
|
|
76
|
+
if (e instanceof DeadScopeError) {
|
|
77
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
throw e;
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
const errors: string[] = [];
|
|
@@ -42,6 +42,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
42
42
|
|
|
43
43
|
import YAML from 'yaml';
|
|
44
44
|
|
|
45
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
46
|
+
|
|
45
47
|
const _HERE = fileURLToPath(import.meta.url);
|
|
46
48
|
|
|
47
49
|
// REPO = Path(__file__).resolve().parents[2]
|
|
@@ -676,6 +678,27 @@ function lint(): number {
|
|
|
676
678
|
return 3;
|
|
677
679
|
}
|
|
678
680
|
|
|
681
|
+
// The asserted unit is the roadmap corpus, not the ticket bundles: a repo
|
|
682
|
+
// with no bundles yet is a real state (the early return below), whereas a
|
|
683
|
+
// vanished agents/roadmaps/ answers "no orphan markers" for the spine
|
|
684
|
+
// check and "not materialised" for nothing, both silently.
|
|
685
|
+
try {
|
|
686
|
+
assertScanned({
|
|
687
|
+
gate: 'lint_ticket_buildable',
|
|
688
|
+
scanned: _glob(ROADMAPS, '*.md').length,
|
|
689
|
+
units: 'roadmap file(s)',
|
|
690
|
+
roots: [ROADMAPS],
|
|
691
|
+
});
|
|
692
|
+
} catch (exc) {
|
|
693
|
+
if (exc instanceof DeadScopeError) {
|
|
694
|
+
// 3 = IO/setup error, this gate's "could not run" code; 1 means
|
|
695
|
+
// lint failures were found.
|
|
696
|
+
process.stderr.write(`${exc.message}\n`);
|
|
697
|
+
return 3;
|
|
698
|
+
}
|
|
699
|
+
throw exc;
|
|
700
|
+
}
|
|
701
|
+
|
|
679
702
|
if (!_exists(TICKETS_ROOT)) {
|
|
680
703
|
process.stdout.write('agents/tickets/: no bundles yet — nothing to lint\n');
|
|
681
704
|
return 0;
|
|
@@ -26,6 +26,8 @@ import * as path from 'node:path';
|
|
|
26
26
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
27
|
import { parse as parseYaml, YAMLParseError } from 'yaml';
|
|
28
28
|
|
|
29
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
30
|
+
|
|
29
31
|
// src/scripts/lint_topics_yaml.ts → two dirs up is the repo root
|
|
30
32
|
// (mirrors Path(__file__).resolve().parents[2]).
|
|
31
33
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -39,15 +41,6 @@ function _relTo(target: string, root: string): string {
|
|
|
39
41
|
return path.relative(root, target).split(path.sep).join('/');
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
function _exists(p: string): boolean {
|
|
43
|
-
try {
|
|
44
|
-
fs.statSync(p);
|
|
45
|
-
return true;
|
|
46
|
-
} catch {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
44
|
/**
|
|
52
45
|
* Render a value the way Python's `repr()` / `{x!r}` would for the values
|
|
53
46
|
* that flow into the finding messages: strings (single-quoted, with escape
|
|
@@ -103,9 +96,23 @@ function _fail(msg: string): void {
|
|
|
103
96
|
}
|
|
104
97
|
|
|
105
98
|
function main(): number {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
// Replaces the ad-hoc `_exists(TOPICS_FILE)` precondition: the gate's whole
|
|
100
|
+
// corpus is this one named file, so its absence is a dead scope rather than
|
|
101
|
+
// a content finding. Same stderr channel, same exit 1 (the only failure code).
|
|
102
|
+
try {
|
|
103
|
+
assertWatchlistResolves({
|
|
104
|
+
gate: 'lint_topics_yaml',
|
|
105
|
+
candidates: [_relTo(TOPICS_FILE, ROOT)],
|
|
106
|
+
repoRoot: ROOT,
|
|
107
|
+
});
|
|
108
|
+
} catch (e) {
|
|
109
|
+
if (e instanceof DeadScopeError) {
|
|
110
|
+
// Not via `_fail`: its `topics.yml:` prefix would stutter against
|
|
111
|
+
// the gate name the DeadScopeError message already carries.
|
|
112
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
throw e;
|
|
109
116
|
}
|
|
110
117
|
let doc: unknown;
|
|
111
118
|
try {
|
|
@@ -43,6 +43,8 @@ import * as fs from 'node:fs';
|
|
|
43
43
|
import * as path from 'node:path';
|
|
44
44
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
45
45
|
|
|
46
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
47
|
+
|
|
46
48
|
const _HERE = fileURLToPath(import.meta.url);
|
|
47
49
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
48
50
|
const ROUTER = path.join(REPO_ROOT, 'dist', 'router.json');
|
|
@@ -108,6 +110,31 @@ export function main(): number {
|
|
|
108
110
|
return 1;
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
// The routed-rule population, not `found` below: `found` counts VIOLATIONS,
|
|
114
|
+
// so zero is the direction this ratchet is pushing toward. A router.json
|
|
115
|
+
// regenerated over a moved rule tree parses fine and yields zero short
|
|
116
|
+
// keywords — indistinguishable from a clean run. Exit 1 is the existing
|
|
117
|
+
// "gate could not run" code (missing / unparseable router); 2 stays
|
|
118
|
+
// "over budget".
|
|
119
|
+
const routed = ['tier_1', 'tier_2'].reduce(
|
|
120
|
+
(n, tier) => n + (Array.isArray(router[tier]) ? (router[tier] as unknown[]).length : 0),
|
|
121
|
+
0,
|
|
122
|
+
);
|
|
123
|
+
try {
|
|
124
|
+
assertScanned({
|
|
125
|
+
gate: 'lint_trigger_precision',
|
|
126
|
+
scanned: routed,
|
|
127
|
+
units: 'routed rule(s)',
|
|
128
|
+
roots: ['dist/router.json'],
|
|
129
|
+
});
|
|
130
|
+
} catch (e) {
|
|
131
|
+
if (e instanceof DeadScopeError) {
|
|
132
|
+
process.stderr.write(`error: ${e.message}\n`);
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
throw e;
|
|
136
|
+
}
|
|
137
|
+
|
|
111
138
|
const found = short_keywords(router);
|
|
112
139
|
const over = found.length > SHORT_KEYWORD_BUDGET;
|
|
113
140
|
|
|
@@ -26,6 +26,7 @@ import * as fs from 'node:fs';
|
|
|
26
26
|
import * as path from 'node:path';
|
|
27
27
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
28
28
|
import { strip_source_prefix } from './_lib/agent_src.js';
|
|
29
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
29
30
|
|
|
30
31
|
const _HERE = fileURLToPath(import.meta.url);
|
|
31
32
|
const _DEFAULT_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -317,6 +318,26 @@ function main(argv: readonly string[] = []): number {
|
|
|
317
318
|
const manifest = _load_manifest(args.manifest);
|
|
318
319
|
const kernel = _load_kernel(args.router);
|
|
319
320
|
|
|
321
|
+
// All three invariants iterate `artefacts`; the existing guard only covers a
|
|
322
|
+
// manifest that is ABSENT. One that parses but carries no artefacts — a
|
|
323
|
+
// discovery build over a moved source tree — visits nothing and prints the
|
|
324
|
+
// same success line, just with zeroes in it. Exit 1 is the gate's only
|
|
325
|
+
// failure code (the missing-manifest SystemExit lands on 1 too).
|
|
326
|
+
try {
|
|
327
|
+
assertScanned({
|
|
328
|
+
gate: 'lint_trust_coherence',
|
|
329
|
+
scanned: _asArray(manifest['artefacts']).length,
|
|
330
|
+
units: 'manifest artefact(s)',
|
|
331
|
+
roots: [args.manifest],
|
|
332
|
+
});
|
|
333
|
+
} catch (e) {
|
|
334
|
+
if (e instanceof DeadScopeError) {
|
|
335
|
+
process.stderr.write(`ERROR: ${e.message}\n`);
|
|
336
|
+
return 1;
|
|
337
|
+
}
|
|
338
|
+
throw e;
|
|
339
|
+
}
|
|
340
|
+
|
|
320
341
|
const errs: string[] = [];
|
|
321
342
|
errs.push(..._check_pack_safety_floors(manifest));
|
|
322
343
|
errs.push(..._check_human_review_banners(manifest, args.compiledSrc));
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
type StackBundle,
|
|
34
34
|
compose_bundle,
|
|
35
35
|
} from '../agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.js';
|
|
36
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
36
37
|
|
|
37
38
|
const QUIET = process.argv.includes('--quiet');
|
|
38
39
|
|
|
@@ -153,9 +154,22 @@ function check_lane(lane: string, bundle: StackBundle): void {
|
|
|
153
154
|
|
|
154
155
|
function main(): number {
|
|
155
156
|
const lanes = Object.keys(STACK_BUNDLES).sort();
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
// Replaces the ad-hoc `lanes.length === 0` check with the shared assertion,
|
|
158
|
+
// so an emptied bundle map names the root it came from instead of a bare
|
|
159
|
+
// "nothing to check". Exit 1 is the gate's only failure code.
|
|
160
|
+
try {
|
|
161
|
+
assertScanned({
|
|
162
|
+
gate: 'lint_ui_stack_bundles',
|
|
163
|
+
scanned: lanes.length,
|
|
164
|
+
units: 'stack lane(s)',
|
|
165
|
+
roots: ['src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts'],
|
|
166
|
+
});
|
|
167
|
+
} catch (e) {
|
|
168
|
+
if (e instanceof DeadScopeError) {
|
|
169
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
throw e;
|
|
159
173
|
}
|
|
160
174
|
for (const lane of lanes) {
|
|
161
175
|
check_lane(lane, STACK_BUNDLES[lane] as StackBundle);
|
|
@@ -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 { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
24
|
+
|
|
23
25
|
const _HERE = fileURLToPath(import.meta.url);
|
|
24
26
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
25
27
|
const DASHBOARD = path.join(REPO_ROOT, 'docs', 'value.md');
|
|
@@ -176,9 +178,23 @@ function _seqEqual(a: readonly unknown[], b: readonly unknown[]): boolean {
|
|
|
176
178
|
function lint(quiet = false): number {
|
|
177
179
|
const violations: string[] = [];
|
|
178
180
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
// Replaces the ad-hoc `_exists(DASHBOARD)` precondition. Only the dashboard
|
|
182
|
+
// is guarded: `LATEST` is genuinely optional (its absence is the documented
|
|
183
|
+
// "placeholder" success path below), so listing it here would let a moved
|
|
184
|
+
// dashboard pass whenever the report happened to exist. Exit 1 = the gate's
|
|
185
|
+
// only failure code.
|
|
186
|
+
try {
|
|
187
|
+
assertWatchlistResolves({
|
|
188
|
+
gate: 'lint_value_dashboard',
|
|
189
|
+
candidates: [_relPosix(DASHBOARD, REPO_ROOT)],
|
|
190
|
+
repoRoot: REPO_ROOT,
|
|
191
|
+
});
|
|
192
|
+
} catch (e) {
|
|
193
|
+
if (e instanceof DeadScopeError) {
|
|
194
|
+
_log(`FAIL: ${e.message}`, quiet, true);
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
throw e;
|
|
182
198
|
}
|
|
183
199
|
const text = fs.readFileSync(DASHBOARD, 'utf-8');
|
|
184
200
|
violations.push(...check_required_sections(text));
|
|
@@ -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_versioned_cache.ts';
|
|
35
37
|
|
|
36
38
|
/** Window (lines) around a cache-path literal scanned for a justification. */
|
|
@@ -135,7 +137,17 @@ export function scanFile(file: string, source: string): Violation[] {
|
|
|
135
137
|
|
|
136
138
|
export function runChecks(root: string): Violation[] {
|
|
137
139
|
const violations: Violation[] = [];
|
|
138
|
-
|
|
140
|
+
const files = listTsFiles(root);
|
|
141
|
+
// The unit is every `.ts` walked — `.ts` defines this gate's corpus, while
|
|
142
|
+
// the cache-suffix match is the finding. Asserting on matches would read
|
|
143
|
+
// green for a moved `--dir` exactly as it does for a clean tree.
|
|
144
|
+
assertScanned({
|
|
145
|
+
gate: 'lint_versioned_cache',
|
|
146
|
+
scanned: files.length,
|
|
147
|
+
units: 'TypeScript file(s)',
|
|
148
|
+
roots: [root],
|
|
149
|
+
});
|
|
150
|
+
for (const file of files) {
|
|
139
151
|
// Never lint this file itself — its regexes contain the very suffixes
|
|
140
152
|
// it hunts for.
|
|
141
153
|
if (path.basename(file) === 'lint_versioned_cache.ts') continue;
|
|
@@ -177,6 +189,12 @@ export function main(argv: string[]): number {
|
|
|
177
189
|
try {
|
|
178
190
|
violations = runChecks(path.resolve(dir));
|
|
179
191
|
} catch (exc) {
|
|
192
|
+
// 3 (internal) over 2 (violations found): a dead `--dir` means the
|
|
193
|
+
// gate could not run, not that it found an unversioned cache.
|
|
194
|
+
if (exc instanceof DeadScopeError) {
|
|
195
|
+
process.stderr.write(`${PROG}: ${exc.message}\n`);
|
|
196
|
+
return 3;
|
|
197
|
+
}
|
|
180
198
|
process.stderr.write(`${PROG}: internal error: ${String(exc)}\n`);
|
|
181
199
|
return 3;
|
|
182
200
|
}
|
|
@@ -31,6 +31,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
31
31
|
|
|
32
32
|
import YAML from 'yaml';
|
|
33
33
|
|
|
34
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
34
35
|
import { py_json_dumps_indent2 } from './_lib/security_lint.js';
|
|
35
36
|
|
|
36
37
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -72,14 +73,6 @@ function _isFile(p: string): boolean {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
function _isDir(p: string): boolean {
|
|
76
|
-
try {
|
|
77
|
-
return fs.statSync(p).isDirectory();
|
|
78
|
-
} catch {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
76
|
function _asObject(v: JsonValue | undefined): JsonObject | null {
|
|
84
77
|
if (v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v)) {
|
|
85
78
|
return v as JsonObject;
|
|
@@ -557,18 +550,33 @@ export function main(argv?: string[]): number {
|
|
|
557
550
|
throw e;
|
|
558
551
|
}
|
|
559
552
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
553
|
+
// Replaces the former `!_isDir(WORKFLOWS_DIR)` → exit 0: a missing dir and
|
|
554
|
+
// an existing-but-empty one are the same blindness, and reporting
|
|
555
|
+
// "0 HIGH, 0 MEDIUM" over zero workflows is the vacuous pass.
|
|
556
|
+
const yml = _sortedGlob('.yml');
|
|
557
|
+
const yaml = _sortedGlob('.yaml');
|
|
558
|
+
try {
|
|
559
|
+
assertScanned({
|
|
560
|
+
gate: 'lint_workflow_security',
|
|
561
|
+
scanned: yml.length + yaml.length,
|
|
562
|
+
units: 'workflow file(s)',
|
|
563
|
+
roots: [WORKFLOWS_DIR],
|
|
564
|
+
});
|
|
565
|
+
} catch (e) {
|
|
566
|
+
if (e instanceof DeadScopeError) {
|
|
567
|
+
// 2 (the usage / could-not-run code) over 1, which means
|
|
568
|
+
// "a HIGH finding blocked under --strict".
|
|
569
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
570
|
+
return 2;
|
|
563
571
|
}
|
|
564
|
-
|
|
572
|
+
throw e;
|
|
565
573
|
}
|
|
566
574
|
|
|
567
575
|
const all_findings: Finding[] = [];
|
|
568
|
-
for (const wf_path of
|
|
576
|
+
for (const wf_path of yml) {
|
|
569
577
|
all_findings.push(...scan_workflow(wf_path, allowlist));
|
|
570
578
|
}
|
|
571
|
-
for (const wf_path of
|
|
579
|
+
for (const wf_path of yaml) {
|
|
572
580
|
all_findings.push(...scan_workflow(wf_path, allowlist));
|
|
573
581
|
}
|
|
574
582
|
|
|
@@ -37,7 +37,7 @@ type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]:
|
|
|
37
37
|
type JsonObject = { [k: string]: JsonValue };
|
|
38
38
|
|
|
39
39
|
// Tool names across platforms whose command field carries a shell command
|
|
40
|
-
// (kept in lock-step with
|
|
40
|
+
// (kept in lock-step with before_complete_hook.COMMAND_TOOLS).
|
|
41
41
|
const COMMAND_TOOLS: ReadonlySet<string> = new Set([
|
|
42
42
|
"launch-process",
|
|
43
43
|
"launch_process",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Ported from the retired Python `skill_collision_clusters.py` (Phase 8 / Wave 8e).
|
|
6
6
|
*
|
|
7
|
-
* Walks
|
|
7
|
+
* Walks `src/skills/<id>/SKILL.md`, extracts the
|
|
8
8
|
* `description` frontmatter, computes pairwise keyword overlap, and groups
|
|
9
9
|
* high-overlap skill pairs into clusters. The output drives the
|
|
10
10
|
* selection-accuracy fixture set defined by council file 05 (Round-3
|
|
@@ -18,11 +18,26 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
18
18
|
|
|
19
19
|
import { parse as parseYaml } from 'yaml';
|
|
20
20
|
|
|
21
|
+
import { SRC_SKILLS } from './_lib/agent_src.js';
|
|
22
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
23
|
+
|
|
21
24
|
const _HERE = fileURLToPath(import.meta.url);
|
|
22
25
|
|
|
23
26
|
const REPO_ROOT = path.resolve(_HERE, '..', '..', '..');
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The live skills tree, via the shared ADR-051 resolver.
|
|
29
|
+
*
|
|
30
|
+
* This was a hardcoded path into the source container ADR-051 retired, so the
|
|
31
|
+
* gate read a directory that does not exist and clustered 0 of 288 skills. It
|
|
32
|
+
* was invisible for two reasons at once: the dead-root sweep and the hardening
|
|
33
|
+
* ratchet both defined "a gate" with a prefix set that excluded `skill_*`, so
|
|
34
|
+
* nothing in the repo was looking at it.
|
|
35
|
+
*/
|
|
36
|
+
const SKILLS_DIR = SRC_SKILLS();
|
|
37
|
+
// `agents/runtime/reports/`, as this file's own header has always declared. The
|
|
38
|
+
// code said `agents/reports/` (tracked) — harmless while the gate read a dead
|
|
39
|
+
// root and never got far enough to write, load-bearing the moment it did.
|
|
40
|
+
const OUT_JSON = path.join(REPO_ROOT, 'agents', 'runtime', 'reports', 'skill-collision-clusters.json');
|
|
26
41
|
|
|
27
42
|
const KEYWORD_OVERLAP_THRESHOLD = 0.4;
|
|
28
43
|
const MIN_SHARED_KEYWORDS = 3;
|
|
@@ -274,6 +289,25 @@ export function main(): number {
|
|
|
274
289
|
process.stderr.write(`❌ Skills dir not found: ${SKILLS_DIR}\n`);
|
|
275
290
|
return 2;
|
|
276
291
|
}
|
|
292
|
+
// The dir-exists check above does not cover an existing-but-empty root — a
|
|
293
|
+
// stray retired-container directory left behind by a local test run passes
|
|
294
|
+
// it — which would write a `0 clusters from 0 skills` report and exit green.
|
|
295
|
+
// The unit is the SKILL.md files found, not the subset that parsed a
|
|
296
|
+
// description.
|
|
297
|
+
try {
|
|
298
|
+
assertScanned({
|
|
299
|
+
gate: 'skill_collision_clusters',
|
|
300
|
+
scanned: _sortedSkillMds().length,
|
|
301
|
+
units: 'SKILL.md file(s)',
|
|
302
|
+
roots: ['src/skills'],
|
|
303
|
+
});
|
|
304
|
+
} catch (exc) {
|
|
305
|
+
if (exc instanceof DeadScopeError) {
|
|
306
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
307
|
+
return 2;
|
|
308
|
+
}
|
|
309
|
+
throw exc;
|
|
310
|
+
}
|
|
277
311
|
const skills = load_skills();
|
|
278
312
|
const clusters = build_clusters(skills);
|
|
279
313
|
fs.mkdirSync(path.dirname(OUT_JSON), { recursive: true });
|
|
@@ -31,6 +31,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
31
31
|
import { parse as parseYaml } from 'yaml';
|
|
32
32
|
|
|
33
33
|
import { load_agent_settings } from './_lib/agent_settings.js';
|
|
34
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
34
35
|
import { event4u_root as _event4u_root_impl } from './_lib/user_global_paths.js';
|
|
35
36
|
|
|
36
37
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -339,6 +340,25 @@ export function main(argv: string[] | null = null): number {
|
|
|
339
340
|
}
|
|
340
341
|
|
|
341
342
|
const catalog = load_catalog();
|
|
343
|
+
// A recommender still owes an honest scope: analytics events are optional
|
|
344
|
+
// and degrade gracefully by design, but a vanished skills projection makes
|
|
345
|
+
// every class recommend nothing while the run still reports success.
|
|
346
|
+
try {
|
|
347
|
+
assertScanned({
|
|
348
|
+
gate: 'skill_discovery',
|
|
349
|
+
scanned: catalog.size,
|
|
350
|
+
units: 'catalog skill(s)',
|
|
351
|
+
roots: ['dist/agent-src/skills'],
|
|
352
|
+
});
|
|
353
|
+
} catch (exc) {
|
|
354
|
+
if (exc instanceof DeadScopeError) {
|
|
355
|
+
// 2 is this gate's only non-zero code (bad / missing role); here it
|
|
356
|
+
// reads as "could not run".
|
|
357
|
+
process.stderr.write(`${exc.message}\n`);
|
|
358
|
+
return 2;
|
|
359
|
+
}
|
|
360
|
+
throw exc;
|
|
361
|
+
}
|
|
342
362
|
const shortlist = load_role_shortlist(role);
|
|
343
363
|
const use_analytics = analytics_enabled(settings);
|
|
344
364
|
const events = use_analytics ? load_analytics_events() : [];
|
|
@@ -43,6 +43,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
43
43
|
|
|
44
44
|
import { parse as parseYaml } from 'yaml';
|
|
45
45
|
|
|
46
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
47
|
+
|
|
46
48
|
const _HERE = fileURLToPath(import.meta.url);
|
|
47
49
|
export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
48
50
|
|
|
@@ -327,6 +329,26 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
327
329
|
}
|
|
328
330
|
}
|
|
329
331
|
|
|
332
|
+
// Every mode below is decided by the skills walk, and every one of them
|
|
333
|
+
// reads innocently at zero: `--check` passes a floor of 0/0, `--write-floor`
|
|
334
|
+
// pins that emptiness as the new floor, and the report prints `n/a`.
|
|
335
|
+
try {
|
|
336
|
+
assertScanned({
|
|
337
|
+
gate: 'skill_eval_coverage',
|
|
338
|
+
scanned: _allSkills(_skillsDir({})).length,
|
|
339
|
+
units: 'skill(s)',
|
|
340
|
+
roots: ['src/skills'],
|
|
341
|
+
});
|
|
342
|
+
} catch (exc) {
|
|
343
|
+
if (exc instanceof DeadScopeError) {
|
|
344
|
+
// 2 (usage) over 1, which this gate documents as a ratchet
|
|
345
|
+
// regression — a finding, not an inability to run.
|
|
346
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
347
|
+
return 2;
|
|
348
|
+
}
|
|
349
|
+
throw exc;
|
|
350
|
+
}
|
|
351
|
+
|
|
330
352
|
if (argv.includes('--write-floor')) {
|
|
331
353
|
const report = writeFloor();
|
|
332
354
|
process.stdout.write(
|
|
@@ -16,14 +16,25 @@ import * as fs from 'node:fs';
|
|
|
16
16
|
import * as path from 'node:path';
|
|
17
17
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
18
18
|
|
|
19
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
20
|
+
import { SRC_SKILLS } from './_lib/agent_src.js';
|
|
19
21
|
import { pyRound } from './_lib/value_ladder.js';
|
|
20
22
|
|
|
21
23
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
22
24
|
// parent.parent.parent of the .py file (src/scripts/skill_overlap.py) is the
|
|
23
25
|
// repo root — two dirs up from src/scripts.
|
|
24
26
|
const REPO = path.resolve(_HERE, '..', '..');
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
// The live skills tree via the shared ADR-051 resolver. This was a hardcoded
|
|
28
|
+
// path into the source container ADR-051 retired, so the gate
|
|
29
|
+
// scored overlap across 0 of 288 skills. Invisible until the hardening
|
|
30
|
+
// population stopped excluding the `skill_*` prefix.
|
|
31
|
+
const SKILLS = SRC_SKILLS();
|
|
32
|
+
// `agents/metrics/` was retired by the agents/ taxonomy consolidation, and
|
|
33
|
+
// `agents/reports/skill-overlap.md` is written by the successor gate
|
|
34
|
+
// `audit_skill_overlap` — writing there would silently clobber it. Runtime is
|
|
35
|
+
// the typed, gitignored home for a report nothing tracks. Reviving a gate whose
|
|
36
|
+
// scan root was dead also revives its writes; this one had nowhere legal to land.
|
|
37
|
+
const OUT = path.join(REPO, 'agents', 'runtime', 'reports', 'skill-overlap.md');
|
|
27
38
|
|
|
28
39
|
const STRONG_TOKEN = 0.6;
|
|
29
40
|
const STRONG_SYMBOL = 0.6;
|
|
@@ -290,7 +301,7 @@ export function render(pairs: Pair[], total: number): string {
|
|
|
290
301
|
'# Skill Structural Overlap (baseline)',
|
|
291
302
|
'',
|
|
292
303
|
'> Generated by `scripts/skill_overlap.py`. Scans',
|
|
293
|
-
'>
|
|
304
|
+
'> `src/skills/*/SKILL.md` frontmatter (description +',
|
|
294
305
|
'> trigger metadata) and body symbol references. Reports pairs in two',
|
|
295
306
|
`> tiers: **strong** ≥ ${pyFloatStr(STRONG_TOKEN)} description-token Jaccard or ≥ ${pyFloatStr(STRONG_SYMBOL)}`,
|
|
296
307
|
`> symbol-set Jaccard (roadmap floor); **candidate** ≥ ${pyFloatStr(CANDIDATE_TOKEN)} / ≥ ${pyFloatStr(CANDIDATE_SYMBOL)}`,
|
|
@@ -375,9 +386,24 @@ export function parse_args(argv: string[]): ParsedArgs {
|
|
|
375
386
|
export function main(argv?: string[]): number {
|
|
376
387
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
377
388
|
const skills = load_skills(SKILLS);
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
389
|
+
try {
|
|
390
|
+
// A report is a certification too: writing `Skills scanned: 0` into the
|
|
391
|
+
// baseline is the same lie a green gate tells, in a quieter voice.
|
|
392
|
+
assertScanned({
|
|
393
|
+
gate: 'skill_overlap',
|
|
394
|
+
scanned: skills.length,
|
|
395
|
+
units: 'skill(s)',
|
|
396
|
+
roots: ['src/skills'],
|
|
397
|
+
});
|
|
398
|
+
} catch (exc) {
|
|
399
|
+
if (exc instanceof DeadScopeError) {
|
|
400
|
+
// `no skills under <root>` is the pinned line; the assertion's
|
|
401
|
+
// named-root detail follows rather than replaces it.
|
|
402
|
+
process.stderr.write(`no skills under ${SKILLS}\n`);
|
|
403
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
404
|
+
return 1;
|
|
405
|
+
}
|
|
406
|
+
throw exc;
|
|
381
407
|
}
|
|
382
408
|
const pairs = analyse(skills);
|
|
383
409
|
fs.mkdirSync(path.dirname(args.out), { recursive: true });
|
|
@@ -24,6 +24,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
24
24
|
import { pathToFileURL } from 'node:url';
|
|
25
25
|
import { parse as parseYaml } from 'yaml';
|
|
26
26
|
|
|
27
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
28
|
+
|
|
27
29
|
const _HERE = fileURLToPath(import.meta.url);
|
|
28
30
|
// src/scripts/skill_preview.ts → parents[2] is the package root (mirrors the
|
|
29
31
|
// Python module's parent.parent.parent resolution).
|
|
@@ -513,6 +515,35 @@ export function main(argv: string[] | null = null): number {
|
|
|
513
515
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
514
516
|
const name = args.name as string;
|
|
515
517
|
|
|
518
|
+
// A reporting tool, not a CI gate — but a moved corpus makes it answer
|
|
519
|
+
// "no skill named X" for EVERY name, which reads as the caller's typo
|
|
520
|
+
// rather than as a dead root. The corpus is in-repo (unlike a $HOME
|
|
521
|
+
// session directory, which may legitimately be empty in CI), so zero
|
|
522
|
+
// skill directories is never a valid state. Exit 2 is the existing
|
|
523
|
+
// "cannot preview" code; there is no other failure code.
|
|
524
|
+
let corpus = 0;
|
|
525
|
+
try {
|
|
526
|
+
corpus = fs
|
|
527
|
+
.readdirSync(_SKILLS_DIR, { withFileTypes: true })
|
|
528
|
+
.filter((e) => e.isDirectory()).length;
|
|
529
|
+
} catch {
|
|
530
|
+
corpus = 0;
|
|
531
|
+
}
|
|
532
|
+
try {
|
|
533
|
+
assertScanned({
|
|
534
|
+
gate: 'skill_preview',
|
|
535
|
+
scanned: corpus,
|
|
536
|
+
units: 'skill director(ies)',
|
|
537
|
+
roots: [path.relative(REPO_ROOT, _SKILLS_DIR) || _SKILLS_DIR],
|
|
538
|
+
});
|
|
539
|
+
} catch (exc) {
|
|
540
|
+
if (exc instanceof DeadScopeError) {
|
|
541
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
542
|
+
return 2;
|
|
543
|
+
}
|
|
544
|
+
throw exc;
|
|
545
|
+
}
|
|
546
|
+
|
|
516
547
|
let preview: Preview;
|
|
517
548
|
try {
|
|
518
549
|
preview = load_preview(name);
|
|
@@ -22,6 +22,7 @@ import * as path from 'node:path';
|
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
23
|
|
|
24
24
|
import { artefact_roots, resolve_logical } from './_lib/agent_src.js';
|
|
25
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
25
26
|
import * as user_global_paths from './_lib/user_global_paths.js';
|
|
26
27
|
|
|
27
28
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -966,6 +967,23 @@ export function main(argv?: string[]): number {
|
|
|
966
967
|
}
|
|
967
968
|
|
|
968
969
|
const skills = load_skill_metas();
|
|
970
|
+
// The full catalogue IS the routing condition — an empty one still yields
|
|
971
|
+
// a complete-looking result file with precision/recall computed over a
|
|
972
|
+
// router that was never given anything to choose from.
|
|
973
|
+
try {
|
|
974
|
+
assertScanned({
|
|
975
|
+
gate: 'skill_trigger_eval',
|
|
976
|
+
scanned: skills.length,
|
|
977
|
+
units: 'catalogue skill(s)',
|
|
978
|
+
roots: ['src/skills', 'src/agent-src/skills'],
|
|
979
|
+
});
|
|
980
|
+
} catch (exc) {
|
|
981
|
+
if (exc instanceof DeadScopeError) {
|
|
982
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
983
|
+
return 2;
|
|
984
|
+
}
|
|
985
|
+
throw exc;
|
|
986
|
+
}
|
|
969
987
|
let router: TriggerRouter;
|
|
970
988
|
let defaultOutput: string;
|
|
971
989
|
if (args.dry_run) {
|