@event4u/agent-config 9.19.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 +89 -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 +38 -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_completion_review.ts +19 -4
- 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_review_dispositions.ts +188 -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/dispatch_r2_reviewer.ts +3 -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
|
@@ -21,6 +21,8 @@ import * as fs from 'node:fs';
|
|
|
21
21
|
import * as path from 'node:path';
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
23
|
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
25
|
+
|
|
24
26
|
const _HERE = fileURLToPath(import.meta.url);
|
|
25
27
|
// src/scripts/lint_no_new_atomic_commands.ts → parent.parent.parent is repo root.
|
|
26
28
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -288,6 +290,37 @@ function main(argv?: readonly string[]): number {
|
|
|
288
290
|
|
|
289
291
|
const clusters = load_locked_clusters();
|
|
290
292
|
const targets = args.all ? all_command_files() : added_command_files(args.baseline);
|
|
293
|
+
// Two scopes behind one variable. `--all` walks src/domains, and an empty
|
|
294
|
+
// walk there is a moved root — the gate would print "No new commands added"
|
|
295
|
+
// and pass over a corpus it never found. The default mode reads a git diff,
|
|
296
|
+
// where zero added command files is the ordinary clean-branch state, so it
|
|
297
|
+
// carries a justified allowEmpty. Exit 3 is the internal-error code ("could
|
|
298
|
+
// not run"); 1 means a real policy violation.
|
|
299
|
+
try {
|
|
300
|
+
assertScanned({
|
|
301
|
+
gate: 'lint_no_new_atomic_commands',
|
|
302
|
+
scanned: targets.length,
|
|
303
|
+
units: args.all ? 'command file(s)' : 'added command file(s)',
|
|
304
|
+
roots: [COMMANDS_DIR],
|
|
305
|
+
...(args.all
|
|
306
|
+
? {}
|
|
307
|
+
: {
|
|
308
|
+
allowEmpty:
|
|
309
|
+
'EMPTY_VALID: the default mode reads `git diff --diff-filter=A` plus ' +
|
|
310
|
+
'untracked status, so zero added command files IS the pass state on a ' +
|
|
311
|
+
'branch that adds none. Deletion test: deleting src/domains would not ' +
|
|
312
|
+
'produce this zero — a deletion shows up as diff-filter=D, never as A — ' +
|
|
313
|
+
'so an empty diff cannot launder a dead root. The tree-walking half of ' +
|
|
314
|
+
'this gate (--all) is asserted without the exemption.',
|
|
315
|
+
}),
|
|
316
|
+
});
|
|
317
|
+
} catch (exc) {
|
|
318
|
+
if (exc instanceof DeadScopeError) {
|
|
319
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
320
|
+
return 3;
|
|
321
|
+
}
|
|
322
|
+
throw exc;
|
|
323
|
+
}
|
|
291
324
|
if (targets.length === 0) {
|
|
292
325
|
process.stdout.write(
|
|
293
326
|
`✅ No new commands added under ${COMMANDS_DIR} ` +
|
|
@@ -22,6 +22,8 @@ import * as fs from 'node:fs';
|
|
|
22
22
|
import * as path from 'node:path';
|
|
23
23
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
24
|
|
|
25
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
26
|
+
|
|
25
27
|
const _HERE = fileURLToPath(import.meta.url);
|
|
26
28
|
export const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
27
29
|
const SCOPE = path.join(ROOT, 'src', 'scripts');
|
|
@@ -79,11 +81,27 @@ export function scan_file(rel: string, text: string): string[] {
|
|
|
79
81
|
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
80
82
|
const QUIET = argv.includes('--quiet');
|
|
81
83
|
const findings: string[] = [];
|
|
84
|
+
let scanned = 0;
|
|
82
85
|
for (const f of walk(SCOPE)) {
|
|
83
86
|
if (path.resolve(f) === path.resolve(_HERE)) continue; // this guard documents the shapes
|
|
87
|
+
scanned += 1;
|
|
84
88
|
const rel = path.relative(ROOT, f).split(path.sep).join('/');
|
|
85
89
|
findings.push(...scan_file(rel, fs.readFileSync(f, 'utf-8')));
|
|
86
90
|
}
|
|
91
|
+
try {
|
|
92
|
+
assertScanned({
|
|
93
|
+
gate: 'lint_no_python_twin_rationale',
|
|
94
|
+
scanned,
|
|
95
|
+
units: 'source file(s)',
|
|
96
|
+
roots: ['src/scripts'],
|
|
97
|
+
});
|
|
98
|
+
} catch (exc) {
|
|
99
|
+
if (exc instanceof DeadScopeError) {
|
|
100
|
+
process.stdout.write(`❌ ${exc.message}\n`);
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
throw exc;
|
|
104
|
+
}
|
|
87
105
|
if (findings.length === 0) {
|
|
88
106
|
if (!QUIET) process.stdout.write('✅ No Python-twin rationale in src/scripts comments.\n');
|
|
89
107
|
return 0;
|
|
@@ -28,6 +28,8 @@ import * as fs from 'node:fs';
|
|
|
28
28
|
import * as path from 'node:path';
|
|
29
29
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
30
30
|
|
|
31
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
32
|
+
|
|
31
33
|
const _HERE = fileURLToPath(import.meta.url);
|
|
32
34
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
33
35
|
// Mirrors the (scan-unused) module constant; kept for export parity.
|
|
@@ -159,7 +161,12 @@ function _exists(p: string): boolean {
|
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
|
|
164
|
+
/** Out-param so the caller can assert the scope `scan` just walked. */
|
|
165
|
+
export interface ScanStats {
|
|
166
|
+
entries: number;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function scan(root: string, today?: SimpleDate, stats?: ScanStats): Finding[] {
|
|
163
170
|
const day = today ?? _today_utc();
|
|
164
171
|
const base = path.join(root, 'scripts', '_one_off');
|
|
165
172
|
if (!_exists(base)) {
|
|
@@ -170,6 +177,11 @@ export function scan(root: string, today?: SimpleDate): Finding[] {
|
|
|
170
177
|
if (!_isDir(month_dir)) {
|
|
171
178
|
continue;
|
|
172
179
|
}
|
|
180
|
+
// A month dir is itself a checked unit — its name shape is validated
|
|
181
|
+
// below, so an empty one is still something this walk read.
|
|
182
|
+
if (stats) {
|
|
183
|
+
stats.entries += 1;
|
|
184
|
+
}
|
|
173
185
|
const monthName = path.basename(month_dir);
|
|
174
186
|
const anchor = _month_anchor(monthName);
|
|
175
187
|
if (anchor === null) {
|
|
@@ -186,6 +198,9 @@ export function scan(root: string, today?: SimpleDate): Finding[] {
|
|
|
186
198
|
if (fname === 'README.md' || _isDir(f)) {
|
|
187
199
|
continue;
|
|
188
200
|
}
|
|
201
|
+
if (stats) {
|
|
202
|
+
stats.entries += 1;
|
|
203
|
+
}
|
|
189
204
|
if (!NAME_RE.test(fname)) {
|
|
190
205
|
out.push({
|
|
191
206
|
path: _relTo(f, root),
|
|
@@ -315,13 +330,34 @@ function parse_args(argv: readonly string[]): ParsedArgs {
|
|
|
315
330
|
export function main(): number {
|
|
316
331
|
const args = parse_args(process.argv.slice(2));
|
|
317
332
|
let findings: Finding[];
|
|
333
|
+
const stats: ScanStats = { entries: 0 };
|
|
318
334
|
try {
|
|
319
|
-
findings = scan(args.root);
|
|
335
|
+
findings = scan(args.root, undefined, stats);
|
|
320
336
|
} catch (e) {
|
|
321
337
|
const msg = e instanceof Error ? e.message : String(e);
|
|
322
338
|
process.stderr.write(`Internal error: ${msg}\n`);
|
|
323
339
|
return 3;
|
|
324
340
|
}
|
|
341
|
+
try {
|
|
342
|
+
assertScanned({
|
|
343
|
+
gate: 'lint_one_off_age',
|
|
344
|
+
scanned: stats.entries,
|
|
345
|
+
units: 'one-off entr(y/ies)',
|
|
346
|
+
roots: ['scripts/_one_off'],
|
|
347
|
+
allowEmpty:
|
|
348
|
+
'OPTIONAL_INPUT: `scripts/_one_off/` is a TTL-managed scratch tree created on ' +
|
|
349
|
+
'demand and purged by this very policy — an absent or empty root is the ' +
|
|
350
|
+
'intended steady state, not a lost corpus. Delete the root and zero still ' +
|
|
351
|
+
'means "no one-off scripts exist", which is exactly what the TTL is for.',
|
|
352
|
+
});
|
|
353
|
+
} catch (e) {
|
|
354
|
+
// 1 is the hard-fail code (2 is argparse, 3 an internal error).
|
|
355
|
+
if (e instanceof DeadScopeError) {
|
|
356
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
357
|
+
return 1;
|
|
358
|
+
}
|
|
359
|
+
throw e;
|
|
360
|
+
}
|
|
325
361
|
if (args.format === 'json') {
|
|
326
362
|
// json.dumps([asdict(f) for f in findings], indent=2) — keys keep the
|
|
327
363
|
// dataclass declaration order (path, age_days, severity, reason).
|
|
@@ -24,6 +24,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
24
24
|
import { parse as parseYaml } from 'yaml';
|
|
25
25
|
|
|
26
26
|
import { resolve_logical } from './_lib/agent_src.js';
|
|
27
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
27
28
|
|
|
28
29
|
const _HERE = fileURLToPath(import.meta.url);
|
|
29
30
|
|
|
@@ -301,11 +302,30 @@ function main(argv?: readonly string[]): number {
|
|
|
301
302
|
if (args.file !== null) {
|
|
302
303
|
return lint(args.file);
|
|
303
304
|
}
|
|
304
|
-
|
|
305
|
-
|
|
305
|
+
const pipelines = _isDir(args.dir) ? _globYamlSorted(args.dir) : [];
|
|
306
|
+
try {
|
|
307
|
+
assertScanned({
|
|
308
|
+
gate: 'lint_orchestration_dsl',
|
|
309
|
+
scanned: pipelines.length,
|
|
310
|
+
units: 'pipeline file(s)',
|
|
311
|
+
roots: ['.agent-config/orchestrations'],
|
|
312
|
+
allowEmpty:
|
|
313
|
+
'OPTIONAL_INPUT: `.agent-config/orchestrations/` is an opt-in consumer ' +
|
|
314
|
+
'directory — most projects author no pipelines at all, and the DSL contract ' +
|
|
315
|
+
'only binds files that exist. Delete the root and zero still means "this ' +
|
|
316
|
+
'project declares no pipelines", never a corpus that went missing.',
|
|
317
|
+
});
|
|
318
|
+
} catch (exc) {
|
|
319
|
+
// 2 is the file/schema-load code; 1 is reserved for a pipeline that
|
|
320
|
+
// violates the contract.
|
|
321
|
+
if (exc instanceof DeadScopeError) {
|
|
322
|
+
process.stderr.write(`lint_orchestration_dsl: ${exc.message}\n`);
|
|
323
|
+
return 2;
|
|
324
|
+
}
|
|
325
|
+
throw exc;
|
|
306
326
|
}
|
|
307
327
|
let rc = 0;
|
|
308
|
-
for (const p of
|
|
328
|
+
for (const p of pipelines) {
|
|
309
329
|
rc = Math.max(rc, lint(p));
|
|
310
330
|
}
|
|
311
331
|
return rc;
|
|
@@ -13,6 +13,8 @@ import * as fs from 'node:fs';
|
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
15
|
|
|
16
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
17
|
+
|
|
16
18
|
const SCRIPTS_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
17
19
|
const ROOT = path.dirname(path.dirname(SCRIPTS_DIR));
|
|
18
20
|
const DOMAINS = path.join(ROOT, 'src', 'domains');
|
|
@@ -28,14 +30,6 @@ export interface Violation {
|
|
|
28
30
|
reason: string;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
function isDir(p: string): boolean {
|
|
32
|
-
try {
|
|
33
|
-
return fs.statSync(p).isDirectory();
|
|
34
|
-
} catch {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
33
|
/** Recursively collect files named `command.md`, sorted by POSIX path. */
|
|
40
34
|
function rglobCommandMd(dir: string): string[] {
|
|
41
35
|
const out: string[] = [];
|
|
@@ -78,10 +72,17 @@ export function _split_frontmatter(text: string): [string, string] {
|
|
|
78
72
|
|
|
79
73
|
export function check(): Violation[] {
|
|
80
74
|
const violations: Violation[] = [];
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
// Assert the whole walk, not the `auto_detect:`-carrying subset: opting out
|
|
76
|
+
// is the common case, so "0 opted in" is normal while "0 command files" can
|
|
77
|
+
// only mean the domains root moved out from under the literal above.
|
|
78
|
+
const commands = rglobCommandMd(DOMAINS);
|
|
79
|
+
assertScanned({
|
|
80
|
+
gate: 'lint_orchestrator_auto_detect',
|
|
81
|
+
scanned: commands.length,
|
|
82
|
+
units: 'command file(s)',
|
|
83
|
+
roots: ['src/domains'],
|
|
84
|
+
});
|
|
85
|
+
for (const filePath of commands) {
|
|
85
86
|
const text = fs.readFileSync(filePath, 'utf-8');
|
|
86
87
|
const [fm, body] = _split_frontmatter(text);
|
|
87
88
|
const m = AUTO_DETECT_RE.exec(fm);
|
|
@@ -149,6 +150,12 @@ export function main(argv?: readonly string[]): number {
|
|
|
149
150
|
try {
|
|
150
151
|
violations = check();
|
|
151
152
|
} catch (exc) {
|
|
153
|
+
// 3 = "the gate could not run" (1 is reserved for contract violations
|
|
154
|
+
// it actually found), so a dead scan root reports as the former.
|
|
155
|
+
if (exc instanceof DeadScopeError) {
|
|
156
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
157
|
+
return 3;
|
|
158
|
+
}
|
|
152
159
|
const msg = exc instanceof Error ? exc.message : String(exc);
|
|
153
160
|
process.stderr.write(`❌ lint-orchestrator-auto-detect: internal error: ${msg}\n`);
|
|
154
161
|
return 3;
|
|
@@ -57,6 +57,7 @@ import * as fs from 'node:fs';
|
|
|
57
57
|
import * as path from 'node:path';
|
|
58
58
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
59
59
|
|
|
60
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
60
61
|
import { shingles } from './_lib/shingle_similarity.js';
|
|
61
62
|
|
|
62
63
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -411,6 +412,27 @@ export function main(argv?: string[]): number {
|
|
|
411
412
|
else { process.stderr.write(`unexpected argument: ${a} (did you mean --changed ${a}?)\n${_usage()}`); return 2; }
|
|
412
413
|
}
|
|
413
414
|
|
|
415
|
+
// Asserted for BOTH modes, and before either: `--changed` compares each
|
|
416
|
+
// candidate against the same corpus, so a dead root there reports "no
|
|
417
|
+
// overlap" instead of "nothing to compare against" — the quieter failure of
|
|
418
|
+
// the two. Supersedes the post-corpus `scanned === 0` check the full audit
|
|
419
|
+
// used to carry alone; exit 3 is that check's code, kept so 1 still means
|
|
420
|
+
// "a real overlap was found".
|
|
421
|
+
try {
|
|
422
|
+
assertScanned({
|
|
423
|
+
gate: 'lint_originality',
|
|
424
|
+
scanned: CLASSES.reduce((n, spec) => n + spec.files().length, 0),
|
|
425
|
+
units: 'corpus file(s)',
|
|
426
|
+
roots: ['src/skills', 'src/agent-src/personas', 'src/domains'],
|
|
427
|
+
});
|
|
428
|
+
} catch (e) {
|
|
429
|
+
if (e instanceof DeadScopeError) {
|
|
430
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
431
|
+
return 3;
|
|
432
|
+
}
|
|
433
|
+
throw e;
|
|
434
|
+
}
|
|
435
|
+
|
|
414
436
|
if (changedFiles !== null) {
|
|
415
437
|
const { pairs, fails } = _changed(changedFiles);
|
|
416
438
|
if (pairs.length > 0) {
|
|
@@ -428,10 +450,6 @@ export function main(argv?: string[]): number {
|
|
|
428
450
|
// Full audit
|
|
429
451
|
const byClass = _corpus();
|
|
430
452
|
const scanned = [...byClass.values()].reduce((n, a) => n + a.length, 0);
|
|
431
|
-
if (scanned === 0) {
|
|
432
|
-
process.stderr.write('❌ No artifacts found under the corpus roots.\n');
|
|
433
|
-
return 3;
|
|
434
|
-
}
|
|
435
453
|
const dist = _distribution(byClass);
|
|
436
454
|
const pairs = _fullPairs(byClass);
|
|
437
455
|
_writeReport(byClass, pairs, dist);
|
|
@@ -32,6 +32,8 @@ import * as fs from 'node:fs';
|
|
|
32
32
|
import * as path from 'node:path';
|
|
33
33
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
34
34
|
|
|
35
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
36
|
+
|
|
35
37
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
36
38
|
const REPO = path.resolve(_HERE, '..', '..');
|
|
37
39
|
|
|
@@ -84,8 +86,12 @@ function _shingles(words: string[]): Set<string> {
|
|
|
84
86
|
return out;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
/** `read` counts every corpus file opened, `docs` only those long enough to
|
|
90
|
+
* compare — a corpus of stubs is a different problem from a corpus that is not
|
|
91
|
+
* there, and only the second one means the roots below have rotted. */
|
|
92
|
+
function _collect(): { docs: Doc[]; read: number } {
|
|
88
93
|
const docs: Doc[] = [];
|
|
94
|
+
let read = 0;
|
|
89
95
|
const push = (id: string, file: string): void => {
|
|
90
96
|
let body: string;
|
|
91
97
|
try {
|
|
@@ -93,6 +99,7 @@ function _collect(): Doc[] {
|
|
|
93
99
|
} catch {
|
|
94
100
|
return;
|
|
95
101
|
}
|
|
102
|
+
read += 1;
|
|
96
103
|
const sh = _shingles(_normalize(body));
|
|
97
104
|
if (sh.size >= 20) {
|
|
98
105
|
docs.push({ id, shingles: sh });
|
|
@@ -132,7 +139,7 @@ function _collect(): Doc[] {
|
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
|
-
return docs;
|
|
142
|
+
return { docs, read };
|
|
136
143
|
}
|
|
137
144
|
|
|
138
145
|
function _jaccardPct(a: Set<string>, b: Set<string>): number {
|
|
@@ -162,7 +169,25 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
162
169
|
else if (arg === '--quiet') quiet = true;
|
|
163
170
|
}
|
|
164
171
|
|
|
165
|
-
const docs = _collect();
|
|
172
|
+
const { docs, read } = _collect();
|
|
173
|
+
// Two of the three roots are behind `existsSync` guards, which is exactly
|
|
174
|
+
// how the personas root went missing for a whole release (see `_collect`).
|
|
175
|
+
// Exit 1 is the only failure this gate has; report-only mode included, since
|
|
176
|
+
// an empty corpus makes the report itself a false all-clear.
|
|
177
|
+
try {
|
|
178
|
+
assertScanned({
|
|
179
|
+
gate: 'lint_originality_shingles',
|
|
180
|
+
scanned: read,
|
|
181
|
+
units: 'corpus file(s)',
|
|
182
|
+
roots: ['src/skills', 'src/agent-src/personas', 'src/subagents'],
|
|
183
|
+
});
|
|
184
|
+
} catch (e) {
|
|
185
|
+
if (e instanceof DeadScopeError) {
|
|
186
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
throw e;
|
|
190
|
+
}
|
|
166
191
|
const pairs: Array<{ a: string; b: string; pct: number }> = [];
|
|
167
192
|
for (let i = 0; i < docs.length; i++) {
|
|
168
193
|
for (let j = i + 1; j < docs.length; j++) {
|
|
@@ -26,6 +26,8 @@ import fs from "node:fs";
|
|
|
26
26
|
import path from "node:path";
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
28
|
|
|
29
|
+
import { assertScanned, DeadScopeError } from "./_lib/scan_scope.js";
|
|
30
|
+
|
|
29
31
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
30
32
|
const REPO_ROOT = path.resolve(_HERE, "..", "..");
|
|
31
33
|
|
|
@@ -191,6 +193,23 @@ function main(): void {
|
|
|
191
193
|
const quiet = process.argv.includes("--quiet");
|
|
192
194
|
|
|
193
195
|
const files = collectFiles();
|
|
196
|
+
// `walkDir` returns nothing for a root that does not exist, so all three
|
|
197
|
+
// literals above can rot into a clean run. Exit 1 is the "internal error"
|
|
198
|
+
// slot; 2 means findings were actually located and must stay distinct.
|
|
199
|
+
try {
|
|
200
|
+
assertScanned({
|
|
201
|
+
gate: "lint_output_slop",
|
|
202
|
+
scanned: files.length,
|
|
203
|
+
units: "file(s)",
|
|
204
|
+
roots: ["src/skills", "src/rules", "docs/guidelines"],
|
|
205
|
+
});
|
|
206
|
+
} catch (e) {
|
|
207
|
+
if (e instanceof DeadScopeError) {
|
|
208
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
throw e;
|
|
212
|
+
}
|
|
194
213
|
const allFindings: Finding[] = [];
|
|
195
214
|
|
|
196
215
|
for (const file of files) {
|
|
@@ -47,6 +47,7 @@ import * as path from 'node:path';
|
|
|
47
47
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
48
48
|
|
|
49
49
|
import { KERNEL_RULE_IDS, is_kernel_rule } from './_lib/kernel_rules.js';
|
|
50
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
50
51
|
|
|
51
52
|
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
52
53
|
const REPO_ROOT = path.resolve(_HERE, '..', '..');
|
|
@@ -220,6 +221,35 @@ export function audit(): OverrideRow[] {
|
|
|
220
221
|
function main(argv: string[]): number {
|
|
221
222
|
const as_json = argv.includes('--json');
|
|
222
223
|
const strict = argv.includes('--strict');
|
|
224
|
+
|
|
225
|
+
// Not the override corpus: an empty `agents/overrides/rules/` is a real and
|
|
226
|
+
// common state (the report says so in words). The scope that cannot be
|
|
227
|
+
// allowed to die is `safety_floor_ids`' input — with src/rules gone the
|
|
228
|
+
// floor set is empty, every safety-floor override downgrades to "ordinary",
|
|
229
|
+
// and the replace/registration checks stop firing while the audit still
|
|
230
|
+
// reads clean. Exit 2 is this CLI's env-error slot; 1 stays "contract
|
|
231
|
+
// violated".
|
|
232
|
+
let rule_sources = 0;
|
|
233
|
+
try {
|
|
234
|
+
rule_sources = fs.readdirSync(RULES_DIR).filter((n) => n.endsWith('.md')).length;
|
|
235
|
+
} catch {
|
|
236
|
+
rule_sources = 0;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
assertScanned({
|
|
240
|
+
gate: 'lint_override_kernel_guard',
|
|
241
|
+
scanned: rule_sources,
|
|
242
|
+
units: 'rule source(s)',
|
|
243
|
+
roots: ['src/rules'],
|
|
244
|
+
});
|
|
245
|
+
} catch (e) {
|
|
246
|
+
if (e instanceof DeadScopeError) {
|
|
247
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
248
|
+
return 2;
|
|
249
|
+
}
|
|
250
|
+
throw e;
|
|
251
|
+
}
|
|
252
|
+
|
|
223
253
|
const rows = audit();
|
|
224
254
|
|
|
225
255
|
if (as_json) {
|
|
@@ -21,6 +21,8 @@ import * as fs from 'node:fs';
|
|
|
21
21
|
import * as path from 'node:path';
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
23
|
|
|
24
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
25
|
+
|
|
24
26
|
const _HERE = fileURLToPath(import.meta.url);
|
|
25
27
|
// src/scripts/lint_pack_first_win.ts → parents[2] is the repo root.
|
|
26
28
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -142,6 +144,7 @@ function _has_onboarding_block(packYaml: string): [boolean, string[]] {
|
|
|
142
144
|
|
|
143
145
|
function main(): number {
|
|
144
146
|
const errors: string[] = [];
|
|
147
|
+
let homesResolved = 0;
|
|
145
148
|
for (const pid of [...FEATURED_PACK_IDS].sort()) {
|
|
146
149
|
const packDir = _pack_home(pid);
|
|
147
150
|
if (packDir === null) {
|
|
@@ -151,6 +154,7 @@ function main(): number {
|
|
|
151
154
|
);
|
|
152
155
|
continue;
|
|
153
156
|
}
|
|
157
|
+
homesResolved += 1;
|
|
154
158
|
const firstWin = path.join(packDir, 'FIRST_WIN.md');
|
|
155
159
|
let firstWinOk = false;
|
|
156
160
|
try {
|
|
@@ -169,6 +173,25 @@ function main(): number {
|
|
|
169
173
|
);
|
|
170
174
|
}
|
|
171
175
|
}
|
|
176
|
+
// The success line below reports FEATURED_PACK_IDS.size — a constant that
|
|
177
|
+
// stays 5 no matter what is on disk. This asserts the number that does move:
|
|
178
|
+
// pack homes actually resolved through the three-candidate precedence. Zero
|
|
179
|
+
// means every candidate root is gone, which is a layout migration, not five
|
|
180
|
+
// separate missing packs. Exit 1 is the gate's only failure code.
|
|
181
|
+
try {
|
|
182
|
+
assertScanned({
|
|
183
|
+
gate: 'lint_pack_first_win',
|
|
184
|
+
scanned: homesResolved,
|
|
185
|
+
units: 'featured pack home(s)',
|
|
186
|
+
roots: ['src/packs', 'src/domains', 'packages'],
|
|
187
|
+
});
|
|
188
|
+
} catch (exc) {
|
|
189
|
+
if (exc instanceof DeadScopeError) {
|
|
190
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
throw exc;
|
|
194
|
+
}
|
|
172
195
|
if (errors.length > 0) {
|
|
173
196
|
process.stderr.write('❌ pack first-win lint failed:\n');
|
|
174
197
|
for (const e of errors) {
|
|
@@ -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 { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
22
|
+
|
|
21
23
|
const QUIET = process.argv.includes('--quiet');
|
|
22
24
|
const _HERE = fileURLToPath(import.meta.url);
|
|
23
25
|
const REPO = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -60,9 +62,23 @@ function _print(s: string): void {
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
export function main(): number {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
// packs.yml IS the scope — there is nowhere else a `risk_class: high`
|
|
66
|
+
// declaration can hide. Replaces the "not found → skip, exit 0" branch,
|
|
67
|
+
// which turned a moved declaration file into a permanent green. Exit 1 is
|
|
68
|
+
// the only failure code; here it means the invariant could not be checked,
|
|
69
|
+
// not that a pack violates it.
|
|
70
|
+
try {
|
|
71
|
+
assertWatchlistResolves({
|
|
72
|
+
gate: 'lint_pack_risk_class',
|
|
73
|
+
candidates: [path.relative(REPO, PACKS_YML)],
|
|
74
|
+
repoRoot: REPO,
|
|
75
|
+
});
|
|
76
|
+
} catch (exc) {
|
|
77
|
+
if (exc instanceof DeadScopeError) {
|
|
78
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
throw exc;
|
|
66
82
|
}
|
|
67
83
|
const violations = riskClassViolations(fs.readFileSync(PACKS_YML, 'utf-8'));
|
|
68
84
|
if (violations.length === 0) {
|
|
@@ -28,6 +28,7 @@ import * as fs from 'node:fs';
|
|
|
28
28
|
import * as path from 'node:path';
|
|
29
29
|
import { fileURLToPath } from 'node:url';
|
|
30
30
|
|
|
31
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
31
32
|
import type { Finding, WaiverRecord } from './_lib/persistence/types.js';
|
|
32
33
|
import { scan_dir as scan_sql_dir } from './_lib/persistence/adapter_raw_sql.js';
|
|
33
34
|
import { scan_dir as scan_n1_dir } from './_lib/persistence/detect_n1_eloquent.js';
|
|
@@ -150,6 +151,24 @@ function main(argv: string[]): number {
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
const resolved_stacks = stacks.length > 0 ? stacks : detect_stacks(dir);
|
|
154
|
+
// `--dir` existing is not the same as `--dir` holding anything scannable:
|
|
155
|
+
// every adapter is keyed off a resolved stack, so zero stacks provably
|
|
156
|
+
// reads zero files while still printing "no gate findings". Exit 2 is the
|
|
157
|
+
// usage/env slot this CLI already uses for an unusable `--dir`.
|
|
158
|
+
try {
|
|
159
|
+
assertScanned({
|
|
160
|
+
gate: 'lint_persistence',
|
|
161
|
+
scanned: resolved_stacks.length,
|
|
162
|
+
units: 'stack(s) to scan',
|
|
163
|
+
roots: [dir],
|
|
164
|
+
});
|
|
165
|
+
} catch (e) {
|
|
166
|
+
if (e instanceof DeadScopeError) {
|
|
167
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
168
|
+
return 2;
|
|
169
|
+
}
|
|
170
|
+
throw e;
|
|
171
|
+
}
|
|
153
172
|
const report = run_lint(dir, resolved_stacks, audit_scope);
|
|
154
173
|
|
|
155
174
|
if (format === 'json') {
|
|
@@ -22,6 +22,8 @@ import * as fs from 'node:fs';
|
|
|
22
22
|
import * as path from 'node:path';
|
|
23
23
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
24
24
|
|
|
25
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
26
|
+
|
|
25
27
|
const QUIET = process.argv.includes('--quiet');
|
|
26
28
|
|
|
27
29
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -214,9 +216,22 @@ function citations_for(personaId: string): string[] {
|
|
|
214
216
|
|
|
215
217
|
function main(): number {
|
|
216
218
|
const personas = collect_personas();
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
// Zero personas used to exit 0 with "nothing to lint" — but this gate has
|
|
220
|
+
// already been inert once on a stale root (see PERSONA_DIR above), and that
|
|
221
|
+
// message is indistinguishable from the root having moved again.
|
|
222
|
+
try {
|
|
223
|
+
assertScanned({
|
|
224
|
+
gate: 'lint_persona_governance',
|
|
225
|
+
scanned: personas.length,
|
|
226
|
+
units: 'persona file(s)',
|
|
227
|
+
roots: ['src/agent-src/personas'],
|
|
228
|
+
});
|
|
229
|
+
} catch (exc) {
|
|
230
|
+
if (exc instanceof DeadScopeError) {
|
|
231
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
throw exc;
|
|
220
235
|
}
|
|
221
236
|
|
|
222
237
|
const byDomain = new Map<string, string[]>();
|
|
@@ -22,6 +22,8 @@ import * as path from 'node:path';
|
|
|
22
22
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
23
|
import { parse as parseYaml } from 'yaml';
|
|
24
24
|
|
|
25
|
+
import { assertWatchlistResolves, DeadScopeError } from './_lib/scan_scope.js';
|
|
26
|
+
|
|
25
27
|
const _HERE = fileURLToPath(import.meta.url);
|
|
26
28
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
27
29
|
const README = path.join(REPO_ROOT, 'README.md');
|
|
@@ -155,6 +157,27 @@ function parse_args(argv: readonly string[]): ParsedArgs {
|
|
|
155
157
|
export function main(): number {
|
|
156
158
|
const args = parse_args(process.argv.slice(2));
|
|
157
159
|
|
|
160
|
+
// Four named surfaces ARE the scope; the gate only compares them against
|
|
161
|
+
// each other, so if the set moved there is nothing left to disagree. Asserted
|
|
162
|
+
// before the reads so a relocated repo layout names the roots instead of
|
|
163
|
+
// surfacing as a bare ENOENT stack. Exit 1 is the drift code and doubles as
|
|
164
|
+
// "could not run" (2 is reserved for the missing-YAML-runtime case).
|
|
165
|
+
try {
|
|
166
|
+
assertWatchlistResolves({
|
|
167
|
+
gate: 'lint_positioning',
|
|
168
|
+
candidates: [README, PACKAGE_JSON, ABOUT_YML, TOPICS_YML].map((p) =>
|
|
169
|
+
path.relative(REPO_ROOT, p),
|
|
170
|
+
),
|
|
171
|
+
repoRoot: REPO_ROOT,
|
|
172
|
+
});
|
|
173
|
+
} catch (e) {
|
|
174
|
+
if (e instanceof DeadScopeError) {
|
|
175
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
throw e;
|
|
179
|
+
}
|
|
180
|
+
|
|
158
181
|
let anchor: string;
|
|
159
182
|
let pkg_desc: string;
|
|
160
183
|
let about_desc: string;
|