@event4u/agent-config 9.13.0 → 9.15.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 +113 -67
- package/README.md +9 -1
- package/dist/agent-src/commands/agents/audit.md +7 -7
- package/dist/agent-src/commands/agents/optimize.md +1 -1
- package/dist/agent-src/commands/analyze/reference-repo.md +1 -1
- package/dist/agent-src/commands/chat-history.md +1 -0
- package/dist/agent-src/commands/context.md +1 -0
- package/dist/agent-src/commands/council.md +1 -1
- package/dist/agent-src/commands/ghostwriter.md +1 -1
- package/dist/agent-src/commands/memory.md +2 -1
- package/dist/agent-src/commands/module.md +1 -0
- package/dist/agent-src/commands/optimize/agents-dir.md +1 -1
- package/dist/agent-src/commands/optimize/deep.md +150 -0
- package/dist/agent-src/commands/optimize/skills.md +5 -5
- package/dist/agent-src/commands/optimize.md +12 -6
- package/dist/agent-src/commands/override.md +1 -0
- package/dist/agent-src/commands/prediction-pool.md +4 -4
- package/dist/agent-src/commands/research/deep.md +1 -1
- package/dist/agent-src/commands/roadmap.md +2 -1
- package/dist/agent-src/commands/rule-compliance-audit.md +1 -1
- package/dist/agent-src/commands/tdd.md +1 -0
- package/dist/agent-src/commands/tests.md +1 -0
- package/dist/agent-src/commands/upstream-contribute.md +5 -5
- package/dist/agent-src/commands/video/from-script.md +1 -1
- package/dist/agent-src/commands/video/from-song.md +1 -1
- package/dist/agent-src/commands/video/scene.md +1 -1
- package/dist/agent-src/commands/video/stitch.md +1 -1
- package/dist/agent-src/commands/video/storyboard.md +1 -1
- package/dist/agent-src/commands/worktree/create.md +2 -1
- package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
- package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +5 -5
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -3
- package/dist/agent-src/contexts/contracts/emergency-triage-block.md +2 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +4 -3
- package/dist/agent-src/contexts/execution/subagent-routing.md +19 -0
- package/dist/agent-src/contexts/override-system.md +1 -1
- package/dist/agent-src/ghostwriter/fictional-fixture-v1.md +1 -1
- package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +2 -1
- package/dist/agent-src/rules/brand-consistency.md +1 -0
- package/dist/agent-src/rules/brand-source-of-truth.md +1 -0
- package/dist/agent-src/rules/code-comment-discipline.md +2 -0
- package/dist/agent-src/rules/communication-through-line.md +1 -0
- package/dist/agent-src/rules/content-quoting-floor.md +1 -0
- package/dist/agent-src/rules/cross-source-consistency.md +0 -1
- package/dist/agent-src/rules/downstream-changes.md +1 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +1 -0
- package/dist/agent-src/rules/external-reference-deep-dive.md +1 -0
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +0 -1
- package/dist/agent-src/rules/icon-consistency.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/improve-before-implement.md +1 -1
- package/dist/agent-src/rules/invite-challenge.md +1 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +4 -3
- package/dist/agent-src/rules/media-sync-ground-truth.md +1 -0
- package/dist/agent-src/rules/no-attribution-footers.md +1 -0
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +1 -0
- package/dist/agent-src/rules/no-pr-progress-comments.md +1 -0
- package/dist/agent-src/rules/onboarding-gate.md +1 -0
- package/dist/agent-src/rules/output-discipline.md +1 -0
- package/dist/agent-src/rules/prefer-enums-over-literals.md +1 -0
- package/dist/agent-src/rules/preservation-guard.md +1 -0
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +1 -0
- package/dist/agent-src/rules/role-mode-adherence.md +1 -0
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -0
- package/dist/agent-src/rules/session-canary.md +9 -2
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/source-of-truth.md +1 -0
- package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -0
- package/dist/agent-src/rules/think-before-action.md +1 -0
- package/dist/agent-src/rules/token-budget-discipline.md +1 -0
- package/dist/agent-src/rules/token-efficiency.md +1 -0
- package/dist/agent-src/rules/token-optimizer-maintenance.md +5 -5
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
- package/dist/agent-src/rules/user-interaction.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +1 -1
- package/dist/agent-src/skills/check-refs/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +1 -1
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +1 -1
- package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
- package/dist/agent-src/skills/github-ci/SKILL.md +1 -0
- package/dist/agent-src/skills/guideline-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/lint-skills/SKILL.md +1 -1
- package/dist/agent-src/skills/readme-reviewer/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-management/SKILL.md +8 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +4 -4
- package/dist/agent-src/skills/token-optimizer/SKILL.md +4 -4
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +6 -6
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +25 -0
- package/dist/agent-src/templates/AGENTS.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/command.md +1 -1
- package/dist/agent-src/templates/copilot-instructions.md +3 -3
- package/dist/agent-src/templates/rule.md +2 -2
- package/dist/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
- package/dist/agent-src/templates/skill-archive-note.md +5 -5
- package/dist/agent-src/templates/skill.md +1 -1
- package/dist/cli/agent-config.js +66 -19
- package/dist/cli/agent-config.js.map +1 -1
- package/dist/cli/main.js +9 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/python/workspace_drive.js +2 -0
- package/dist/cli/python/workspace_drive.js.map +1 -1
- package/dist/cli/python/workspace_drive_health.js +2 -0
- package/dist/cli/python/workspace_drive_health.js.map +1 -1
- package/dist/cli/python/workspace_hosts.js +2 -0
- package/dist/cli/python/workspace_hosts.js.map +1 -1
- package/dist/cli/python/workspace_sessions.js +2 -0
- package/dist/cli/python/workspace_sessions.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-A3HJ6NTI.js → chunk-63WSP6YZ.js} +2 -1
- package/dist/cli-delegate/{chunk-EJYIGZ3T.js → chunk-BKO2AZ7Z.js} +1 -1
- package/dist/cli-delegate/{chunk-QFKQLQ7X.js → chunk-H574KOUH.js} +340 -462
- package/dist/cli-delegate/chunk-NXFWFAYV.js +339 -0
- package/dist/cli-delegate/{chunk-TEGJU2JX.js → chunk-ZXXJLL5K.js} +2 -2
- package/dist/cli-delegate/cmd_conformance.js +29 -23
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +163 -103
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +729 -430
- package/dist/install/install.mjs +700 -625
- package/dist/install/rule_scope.js +18 -2
- package/dist/install/rule_scope.js.map +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +564 -424
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_settings_hooks.js +30 -5
- package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
- package/dist/scripts/_lib/scoped_projection.js +223 -0
- package/dist/scripts/_lib/scoped_projection.js.map +1 -0
- package/dist/scripts/_lib/surface_tiers.js +125 -0
- package/dist/scripts/_lib/surface_tiers.js.map +1 -0
- package/dist/scripts/_lib/token_count.js +166 -0
- package/dist/scripts/_lib/token_count.js.map +1 -0
- package/dist/scripts/_lib/trigger_eval_floors.js +8 -0
- package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -1
- package/dist/scripts/condense.js +139 -9
- package/dist/scripts/condense.js.map +1 -1
- package/dist/scripts/project_thin_rules.js +450 -0
- package/dist/scripts/project_thin_rules.js.map +1 -0
- package/dist/server/routes/workspace.js +2 -0
- package/dist/server/routes/workspace.js.map +1 -1
- package/dist/server/schemas/settings.js +9 -3
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/index-DJY_vQVn.js +40 -0
- package/dist/ui/assets/index-DJY_vQVn.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/package.json +11 -11
- package/src/agent-src/templates/AGENTS.md +1 -1
- package/src/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
- package/src/config/agent-settings.template.yml +59 -11
- package/src/config/ci-local-parity.yml +8 -0
- package/src/config/discovery/unassigned-artefacts.yml +23 -25
- package/src/config/evaluator-budgets.json +35 -5
- package/src/config/gate-coverage.yml +147 -0
- package/src/config/gate-violation-baselines.json +25 -0
- package/src/install/rule_scope.ts +27 -2
- package/src/scripts/_cli/cmd_conformance.ts +13 -24
- package/src/scripts/_dispatch.bash +10 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +30 -4
- package/src/scripts/_lib/exec_evidence.ts +5 -0
- package/src/scripts/_lib/gate_baseline.ts +213 -0
- package/src/scripts/_lib/scoped_projection.ts +262 -0
- package/src/scripts/_lib/tier_budget_routing.ts +234 -0
- package/src/scripts/_lib/tool_adapter_registry.ts +8 -1
- package/src/scripts/_lib/trigger_eval_floors.ts +9 -0
- package/src/scripts/_lib/trigger_routers.ts +17 -5
- package/src/scripts/adr/regenerate_index.ts +4 -3
- package/src/scripts/ai-video/lib/parse-blueprint.sh +1 -1
- package/src/scripts/ai_council/compile_corpus.ts +1 -1
- package/src/scripts/audit_cloud_compatibility.ts +37 -2
- package/src/scripts/audit_initial_context.ts +53 -19
- package/src/scripts/audit_likelihood.ts +31 -2
- package/src/scripts/audit_user_type_axis.ts +58 -11
- package/src/scripts/bench_hook_latency.ts +172 -41
- package/src/scripts/build_mcp_catalog.ts +12 -1
- package/src/scripts/check_always_budget.ts +122 -11
- package/src/scripts/check_artefact_count_messaging.ts +128 -15
- package/src/scripts/check_augment_description_cap.ts +24 -3
- package/src/scripts/check_ci_strict_superset.ts +150 -0
- package/src/scripts/check_cluster_patterns.ts +100 -0
- package/src/scripts/check_consumer_scope_flip.ts +26 -4
- package/src/scripts/check_evaluator_budgets.ts +198 -19
- package/src/scripts/check_gate_coverage.ts +337 -1
- package/src/scripts/check_generator_output_coverage.ts +2 -1
- package/src/scripts/check_iron_law_prominence.ts +1 -1
- package/src/scripts/check_kernel_rule_bundle.ts +1 -1
- package/src/scripts/check_no_new_legacy_path.ts +167 -3
- package/src/scripts/check_pack_size.ts +18 -4
- package/src/scripts/check_portability.ts +37 -7
- package/src/scripts/check_references.ts +19 -3
- package/src/scripts/check_release_pr_shape.ts +13 -0
- package/src/scripts/check_token_regression.ts +9 -2
- package/src/scripts/condense.ts +155 -12
- package/src/scripts/cost/budget.mjs +63 -2
- package/src/scripts/count_scoped_projection.ts +104 -0
- package/src/scripts/evaluator_umbrella.sh +34 -1
- package/src/scripts/generate_capability_matrix.ts +8 -0
- package/src/scripts/generate_router_coverage_from_matrix.ts +155 -0
- package/src/scripts/hook_manifest.yaml +17 -3
- package/src/scripts/hooks/block_config_weakening.ts +337 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/probe_throttle.ts +75 -0
- package/src/scripts/install-hooks.sh +24 -2
- package/src/scripts/install.ts +15 -92
- package/src/scripts/lint_agents_md.ts +9 -3
- package/src/scripts/lint_archived_skills.ts +1 -1
- package/src/scripts/lint_artefact_frontmatter.ts +185 -37
- package/src/scripts/lint_command_routing.ts +21 -52
- package/src/scripts/lint_command_verbs.ts +47 -28
- package/src/scripts/lint_context_spine_usage.ts +38 -12
- package/src/scripts/lint_framework_leakage.ts +25 -6
- package/src/scripts/lint_handoffs.ts +49 -2
- package/src/scripts/lint_media_policy_linkage.ts +62 -30
- package/src/scripts/lint_namespace.ts +92 -7
- package/src/scripts/lint_new_skill_gate.ts +39 -31
- package/src/scripts/lint_originality_shingles.ts +7 -1
- package/src/scripts/lint_override_kernel_guard.ts +21 -3
- package/src/scripts/lint_pack_boundaries.ts +461 -91
- package/src/scripts/lint_pack_dependencies.ts +24 -57
- package/src/scripts/lint_persona_governance.ts +1 -1
- package/src/scripts/lint_role_experiences.ts +34 -5
- package/src/scripts/lint_trigger_precision.ts +142 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +12 -201
- package/src/scripts/mcp_server/tools.ts +1 -1
- package/src/scripts/prepack-check.mjs +40 -1
- package/src/scripts/prepack_router_targets.d.mts +7 -0
- package/src/scripts/prepack_router_targets.mjs +93 -0
- package/src/scripts/print_required_checks.ts +72 -18
- package/src/scripts/profile_staleness_hook.ts +12 -0
- package/src/scripts/record_evaluator_measurements.mjs +105 -0
- package/src/scripts/recruit_preflight.sh +1 -1
- package/src/scripts/release.ts +105 -1
- package/src/scripts/router_target_paths.d.mts +4 -0
- package/src/scripts/router_target_paths.mjs +69 -0
- package/src/scripts/router_telemetry.ts +52 -1
- package/src/scripts/routing_doctor.ts +485 -0
- package/src/scripts/rule_trigger_eval.ts +403 -0
- package/src/scripts/schemas/rule.schema.json +5 -1
- package/src/scripts/session_canary_hook.ts +96 -5
- package/src/scripts/skill_linter.ts +90 -42
- package/src/scripts/smoke/skills.sh +1 -1
- package/src/scripts/sweep_dead_scan_roots.ts +356 -10
- package/src/scripts/trigger_coverage.ts +13 -2
- package/src/scripts/update_counts.ts +91 -12
- package/src/scripts/validate_telegraph_carveouts.ts +1 -1
- package/src/scripts/wrapper_freshness_hook.ts +14 -0
- package/src/server/schemas/settings.ts +19 -5
- package/dist/cli-delegate/chunk-TJIAM5FW.js +0 -133
- package/dist/ui/assets/index-BpSQt5vm.js +0 -40
- package/dist/ui/assets/index-BpSQt5vm.js.map +0 -1
|
@@ -224,7 +224,19 @@ function _is_carve_out_pointer_line(line: string): boolean {
|
|
|
224
224
|
const SHAPE_EXAMPLE_RE =
|
|
225
225
|
/\((?:Laravel|Symfony|Django|Rails|Next\.js)[^)]*\b(?:shape|example)\b|\b(?:Laravel|Symfony|Django|Rails) (?:shape|example):/i;
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Real frontmatter only — the block that OPENS the file, not any `---` fence.
|
|
229
|
+
*
|
|
230
|
+
* This carried `/m` with no start-of-file guard, the only such parser in the
|
|
231
|
+
* suite (every sibling anchors at index 0). With `/m`, a file that has no
|
|
232
|
+
* leading frontmatter latched onto the first `---`-delimited span anywhere in
|
|
233
|
+
* its body — and since a non-null match makes the caller `continue`, the whole
|
|
234
|
+
* file was then exempted from leakage scanning. What collides with it is the
|
|
235
|
+
* house convention of quoting a `---`-fenced frontmatter EXAMPLE in prose: one
|
|
236
|
+
* such example carrying a `framework:` line silently disarms the gate for that
|
|
237
|
+
* file (road-to-gates-that-can-fail Phase 6.2, finding 4).
|
|
238
|
+
*/
|
|
239
|
+
const FRONTMATTER_FRAMEWORK_RE = /^---[ \t]*\n([\s\S]*?)\n---/;
|
|
228
240
|
const FRAMEWORK_KEY_RE = /^(?:framework|\s+framework)\s*:\s*(\S+)/m;
|
|
229
241
|
|
|
230
242
|
export function is_carve_out(p: string): boolean {
|
|
@@ -246,11 +258,12 @@ export function is_inventory_file(p: string): boolean {
|
|
|
246
258
|
if (parts.includes('contexts')) {
|
|
247
259
|
return true;
|
|
248
260
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
)
|
|
261
|
+
// DEAD-ROOT REPAIR (road-to-renewal-foundation Phase 1). Both comparisons
|
|
262
|
+
// were unreachable: `.agent-src.uncondensed` was deleted by ADR-051, and
|
|
263
|
+
// `'dist/agent-src'` can never equal a single `/`-split segment. So the
|
|
264
|
+
// inventory-README exemption never fired for any file, and the real
|
|
265
|
+
// inventory file (`src/agent-src/README.md`) got no exemption at all.
|
|
266
|
+
if (path.basename(rel) === 'README.md' && parts.length === 3 && parts[0] === 'src') {
|
|
254
267
|
return true;
|
|
255
268
|
}
|
|
256
269
|
return false;
|
|
@@ -504,8 +517,13 @@ export function main(argv?: readonly string[]): number {
|
|
|
504
517
|
const file_hits: Array<[string, Hit[]]> = [];
|
|
505
518
|
let total_hits = 0;
|
|
506
519
|
let allowlisted_total = 0;
|
|
520
|
+
// gate-coverage contract (src/config/gate-coverage.yml): files actually
|
|
521
|
+
// inspected. NOTE the "N files" in the human summary is files WITH HITS —
|
|
522
|
+
// reading that as coverage is exactly how a blind gate looks healthy.
|
|
523
|
+
let scannedFiles = 0;
|
|
507
524
|
|
|
508
525
|
for (const f of iter_md_files(args.paths)) {
|
|
526
|
+
scannedFiles += 1;
|
|
509
527
|
if (is_carve_out(f)) {
|
|
510
528
|
continue;
|
|
511
529
|
}
|
|
@@ -580,6 +598,7 @@ export function main(argv?: readonly string[]): number {
|
|
|
580
598
|
`\n${total_hits} hits across ${file_hits.length} files ` +
|
|
581
599
|
`(${allowlisted_total} allowlisted)\n`,
|
|
582
600
|
);
|
|
601
|
+
process.stderr.write(`scanned: ${String(scannedFiles)}\n`);
|
|
583
602
|
return total_hits ? 1 : 0;
|
|
584
603
|
}
|
|
585
604
|
|
|
@@ -16,9 +16,20 @@ 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 { SRC_SKILLS } from './_lib/agent_src.js';
|
|
20
|
+
import { checkRatchet } from './_lib/gate_baseline.js';
|
|
21
|
+
import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
|
|
22
|
+
|
|
19
23
|
const _HERE = fileURLToPath(import.meta.url);
|
|
20
24
|
const REPO = path.resolve(path.dirname(_HERE), '..', '..');
|
|
21
|
-
|
|
25
|
+
/**
|
|
26
|
+
* ADR-051 moved skill authoring to `src/skills`. Until 2026-08-02 this default
|
|
27
|
+
* still named the retired pre-ADR-051 source container, so the CLI walked a
|
|
28
|
+
* missing directory and printed "no violations under <that root>" while
|
|
29
|
+
* reading zero files. Resolved through the shared resolver so the next root
|
|
30
|
+
* move updates every consumer at once.
|
|
31
|
+
*/
|
|
32
|
+
const SKILLS_DIR = SRC_SKILLS();
|
|
22
33
|
|
|
23
34
|
/** Mirror `QUIET = "--quiet" in sys.argv` (computed at import). */
|
|
24
35
|
const QUIET = process.argv.slice(2).includes('--quiet');
|
|
@@ -369,11 +380,32 @@ export function main(argv?: readonly string[]): number {
|
|
|
369
380
|
if (args.length > 0) {
|
|
370
381
|
skills_dir = _resolve(args[0] as string);
|
|
371
382
|
}
|
|
383
|
+
const isDefaultRoot = skills_dir === SKILLS_DIR;
|
|
384
|
+
|
|
385
|
+
// Scope assertion: zero skill files means the root moved, not that the
|
|
386
|
+
// corpus is clean. This is the state the gate shipped in until 2026-08-02.
|
|
387
|
+
const scanned = _rglobSkillMd(skills_dir).length;
|
|
388
|
+
try {
|
|
389
|
+
assertScanned({
|
|
390
|
+
gate: 'lint_handoffs',
|
|
391
|
+
scanned,
|
|
392
|
+
units: 'skill file(s)',
|
|
393
|
+
roots: [_relTo(skills_dir, REPO)],
|
|
394
|
+
});
|
|
395
|
+
} catch (exc) {
|
|
396
|
+
if (!(exc instanceof DeadScopeError)) {
|
|
397
|
+
throw exc;
|
|
398
|
+
}
|
|
399
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
400
|
+
return 2;
|
|
401
|
+
}
|
|
402
|
+
|
|
372
403
|
const violations = lint(skills_dir);
|
|
373
404
|
if (violations.length === 0) {
|
|
374
405
|
if (!QUIET) {
|
|
375
406
|
process.stdout.write(
|
|
376
|
-
`✅ lint_handoffs: no violations under ${_relTo(skills_dir, REPO)}
|
|
407
|
+
`✅ lint_handoffs: no violations under ${_relTo(skills_dir, REPO)} ` +
|
|
408
|
+
`(${scanned} skill file(s) scanned)\n`,
|
|
377
409
|
);
|
|
378
410
|
}
|
|
379
411
|
return 0;
|
|
@@ -381,6 +413,21 @@ export function main(argv?: readonly string[]): number {
|
|
|
381
413
|
for (const v of violations) {
|
|
382
414
|
process.stdout.write(_render(v, REPO) + '\n');
|
|
383
415
|
}
|
|
416
|
+
// Ratchet applies to the repo corpus only — an explicit fixture root is
|
|
417
|
+
// judged on its own findings, never against the repo's recorded debt.
|
|
418
|
+
if (isDefaultRoot) {
|
|
419
|
+
const verdict = checkRatchet({
|
|
420
|
+
gate: 'lint_handoffs',
|
|
421
|
+
actual: violations.length,
|
|
422
|
+
repoRoot: REPO,
|
|
423
|
+
});
|
|
424
|
+
if (verdict.ok) {
|
|
425
|
+
process.stdout.write(`\n⚠️ ${verdict.message}\n`);
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
process.stderr.write(`\n❌ ${verdict.message}\n`);
|
|
429
|
+
return 1;
|
|
430
|
+
}
|
|
384
431
|
process.stderr.write(`\n❌ lint_handoffs: ${violations.length} violation(s)\n`);
|
|
385
432
|
return 1;
|
|
386
433
|
}
|
|
@@ -6,35 +6,57 @@
|
|
|
6
6
|
* Phase 4 / Wave 4b). The CLI contract is pinned — `--quiet`
|
|
7
7
|
* flag read from argv at module load, scan roots + policy ordering (policies
|
|
8
8
|
* sorted; scan files via rglob), finding messages, output channel (all on
|
|
9
|
-
* stdout except the final orphan summary on stderr), exit codes.
|
|
10
|
-
* behaviour changes — historical quirks preserved (consumers pin the exact behaviour). (note: `POLICY_DIR` /
|
|
11
|
-
* `SCAN_ROOTS` point at `agents/policies/media`, matching the .py).
|
|
9
|
+
* stdout except the final orphan summary on stderr), exit codes.
|
|
12
10
|
*
|
|
13
|
-
* Every policy file under `agents/policies/media/` (except README)
|
|
14
|
-
* linked from at least one of a skill SKILL.md, a routing rule, or a
|
|
15
|
-
* policy file. A policy that no surface references is a silent —
|
|
11
|
+
* Every policy file under `agents/settings/policies/media/` (except README)
|
|
12
|
+
* must be linked from at least one of a skill SKILL.md, a routing rule, or a
|
|
13
|
+
* sibling policy file. A policy that no surface references is a silent —
|
|
14
|
+
* failed — policy.
|
|
16
15
|
*
|
|
17
|
-
*
|
|
16
|
+
* The inherited `POLICY_DIR` and `SCAN_ROOTS` were faithful ports of the .py's
|
|
17
|
+
* paths, and every one of them was stale: the policy root was a level too
|
|
18
|
+
* shallow and the referrer roots named the pre-ADR-051 container. Repaired
|
|
19
|
+
* 2026-08-02; see the constants below.
|
|
20
|
+
*
|
|
21
|
+
* Exit codes: 0 all policies linked, 1 one or more orphan policies, 2 dead scope.
|
|
18
22
|
*/
|
|
19
23
|
|
|
20
24
|
import * as fs from 'node:fs';
|
|
21
25
|
import * as path from 'node:path';
|
|
22
26
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
23
27
|
|
|
28
|
+
import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
|
|
29
|
+
|
|
24
30
|
const QUIET = process.argv.includes('--quiet');
|
|
25
31
|
|
|
26
32
|
const _HERE = fileURLToPath(import.meta.url);
|
|
27
33
|
const REPO = path.resolve(path.dirname(_HERE), '..', '..');
|
|
28
|
-
|
|
34
|
+
/**
|
|
35
|
+
* The policies whose reachability is being checked.
|
|
36
|
+
*
|
|
37
|
+
* This named `agents/policies/media` while the policies have always lived one
|
|
38
|
+
* level deeper, under `agents/settings/policies/media` — the gate's own
|
|
39
|
+
* "…/settings/policies/media/ missing" message named the right path while the
|
|
40
|
+
* constant named the wrong one. Result: `main()` took the missing-dir early
|
|
41
|
+
* return and exited 0 without checking a single policy.
|
|
42
|
+
*/
|
|
43
|
+
const POLICY_DIR = path.join(REPO, 'agents', 'settings', 'policies', 'media');
|
|
29
44
|
const EXEMPT_STEMS: ReadonlySet<string> = new Set(['README']);
|
|
30
45
|
|
|
31
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Surfaces scanned for inbound references to policy files.
|
|
48
|
+
*
|
|
49
|
+
* Three of these named the pre-ADR-051 source container and a fourth named
|
|
50
|
+
* `.claude/skills`, a generated projection absent from this repo — so the
|
|
51
|
+
* reference scan read nothing either. Now the real authoring roots (a command
|
|
52
|
+
* is `src/domains/<pack>/<subpath>/command.md`, hence the domains root) plus
|
|
53
|
+
* the policy directory itself, since a sibling policy is a valid referrer.
|
|
54
|
+
*/
|
|
32
55
|
const SCAN_ROOTS: readonly string[] = [
|
|
33
|
-
path.join(REPO, '
|
|
34
|
-
path.join(REPO, '
|
|
35
|
-
path.join(REPO, '
|
|
36
|
-
|
|
37
|
-
path.join(REPO, 'agents', 'policies', 'media'),
|
|
56
|
+
path.join(REPO, 'src', 'skills'),
|
|
57
|
+
path.join(REPO, 'src', 'rules'),
|
|
58
|
+
path.join(REPO, 'src', 'domains'),
|
|
59
|
+
POLICY_DIR,
|
|
38
60
|
];
|
|
39
61
|
|
|
40
62
|
function _exists(p: string): boolean {
|
|
@@ -165,24 +187,34 @@ function referrers_for(policy: string, scanFiles: readonly string[]): string[] {
|
|
|
165
187
|
}
|
|
166
188
|
|
|
167
189
|
function main(): number {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return 0;
|
|
174
|
-
}
|
|
175
|
-
|
|
190
|
+
// Scope assertions replace the two silent early returns this gate used to
|
|
191
|
+
// take. Both are deliberately WITHOUT `allowEmpty`: "the policy directory
|
|
192
|
+
// is missing" was not a benign optional-surface case here, it was the bug —
|
|
193
|
+
// the constant pointed one level too shallow and the gate green-lit an
|
|
194
|
+
// unchecked policy set for as long as it has existed.
|
|
176
195
|
const policies = collect_policies();
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
196
|
+
const scanFiles = collect_scan_files();
|
|
197
|
+
try {
|
|
198
|
+
assertScanned({
|
|
199
|
+
gate: 'lint_media_policy_linkage',
|
|
200
|
+
scanned: policies.length,
|
|
201
|
+
units: 'policy file(s)',
|
|
202
|
+
roots: [_relToPosix(POLICY_DIR, REPO)],
|
|
203
|
+
});
|
|
204
|
+
assertScanned({
|
|
205
|
+
gate: 'lint_media_policy_linkage',
|
|
206
|
+
scanned: scanFiles.length,
|
|
207
|
+
units: 'referrer-candidate file(s)',
|
|
208
|
+
roots: SCAN_ROOTS.map((r) => _relToPosix(r, REPO)),
|
|
209
|
+
});
|
|
210
|
+
} catch (exc) {
|
|
211
|
+
if (!(exc instanceof DeadScopeError)) {
|
|
212
|
+
throw exc;
|
|
213
|
+
}
|
|
214
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
215
|
+
return 2;
|
|
183
216
|
}
|
|
184
217
|
|
|
185
|
-
const scanFiles = collect_scan_files();
|
|
186
218
|
const orphans: string[] = [];
|
|
187
219
|
for (const policy of policies) {
|
|
188
220
|
const referrers = referrers_for(policy, scanFiles);
|
|
@@ -209,7 +241,7 @@ function main(): number {
|
|
|
209
241
|
|
|
210
242
|
emit(
|
|
211
243
|
`media-policy-linkage: ${policies.length} policy file(s) — all ` +
|
|
212
|
-
`linked.`,
|
|
244
|
+
`linked (${scanFiles.length} file(s) scanned for referrers).`,
|
|
213
245
|
);
|
|
214
246
|
return 0;
|
|
215
247
|
}
|
|
@@ -18,10 +18,18 @@ 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 {
|
|
22
|
+
SRC_AGENT,
|
|
23
|
+
SRC_RULES,
|
|
24
|
+
SRC_SKILLS,
|
|
25
|
+
iter_commands,
|
|
26
|
+
logical_relpath,
|
|
27
|
+
} from './_lib/agent_src.js';
|
|
28
|
+
import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
|
|
29
|
+
|
|
21
30
|
const _HERE = fileURLToPath(import.meta.url);
|
|
22
31
|
|
|
23
32
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
24
|
-
const SRC = path.join(ROOT, '.agent-src.uncondensed');
|
|
25
33
|
|
|
26
34
|
// Source-of-truth regex; mirrored in docs/contracts/namespace.md § 1.
|
|
27
35
|
const NAME_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
@@ -44,14 +52,65 @@ interface Target {
|
|
|
44
52
|
subVerb: boolean;
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/**
|
|
56
|
+
* ADR-051 moved every one of these roots out of the pre-ADR-051 source
|
|
57
|
+
* container. Until 2026-08-02 the table still named it, so `scan()` skipped
|
|
58
|
+
* all four `_isDir` checks and reported `BASELINE: 0 issues · 0 name(s)
|
|
59
|
+
* checked` — a green verdict over an empty corpus.
|
|
60
|
+
*
|
|
61
|
+
* Commands are NOT in this table: they no longer live in one flat directory.
|
|
62
|
+
* `src/domains/<pack>/<subpath>/command.md` is walked by `_command_targets()`
|
|
63
|
+
* below, which reconstructs the same name/sub-verb semantics from the logical
|
|
64
|
+
* command path.
|
|
65
|
+
*/
|
|
47
66
|
const TARGETS: Target[] = [
|
|
48
|
-
{ kind: 'skill', root:
|
|
49
|
-
{ kind: 'rule', root:
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
{ kind: 'skill', root: SRC_SKILLS(), glob: '*/SKILL.md', depth: 1, subVerb: false },
|
|
68
|
+
{ kind: 'rule', root: SRC_RULES(), glob: '*.md', depth: 0, subVerb: false },
|
|
69
|
+
{
|
|
70
|
+
kind: 'persona',
|
|
71
|
+
root: path.join(SRC_AGENT(), 'personas'),
|
|
72
|
+
glob: '*.md',
|
|
73
|
+
depth: 0,
|
|
74
|
+
subVerb: false,
|
|
75
|
+
},
|
|
53
76
|
];
|
|
54
77
|
|
|
78
|
+
/** One command name to check, plus the physical file that carries it. */
|
|
79
|
+
interface CommandTarget {
|
|
80
|
+
fullPath: string;
|
|
81
|
+
name: string;
|
|
82
|
+
subVerb: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Command names, reconstructed from the `src/domains` layout.
|
|
87
|
+
*
|
|
88
|
+
* The retired flat layout globbed `commands/*.md` (top-level verb, sub_verb
|
|
89
|
+
* false) and `commands/*/*.md` (sub-verb, sub_verb true — reserved words are
|
|
90
|
+
* legal there). The logical path of a domains command is the same
|
|
91
|
+
* `commands/<subpath>.md`, so the mapping is exact: the LAST segment is the
|
|
92
|
+
* name, and anything nested below the top level is a sub-verb.
|
|
93
|
+
*/
|
|
94
|
+
function _command_targets(): CommandTarget[] {
|
|
95
|
+
const out: CommandTarget[] = [];
|
|
96
|
+
for (const physical of iter_commands()) {
|
|
97
|
+
let logical: string;
|
|
98
|
+
try {
|
|
99
|
+
logical = logical_relpath(physical);
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const segments = logical.slice('commands/'.length).replace(/\.md$/, '').split('/');
|
|
104
|
+
const name = segments[segments.length - 1] as string;
|
|
105
|
+
if (!name) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
out.push({ fullPath: physical, name, subVerb: segments.length > 1 });
|
|
109
|
+
}
|
|
110
|
+
out.sort((a, b) => (a.fullPath < b.fullPath ? -1 : a.fullPath > b.fullPath ? 1 : 0));
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
55
114
|
function _isDir(p: string): boolean {
|
|
56
115
|
try {
|
|
57
116
|
return fs.statSync(p).isDirectory();
|
|
@@ -205,6 +264,14 @@ function scan(): [number, number] {
|
|
|
205
264
|
}
|
|
206
265
|
}
|
|
207
266
|
}
|
|
267
|
+
for (const { fullPath, name, subVerb } of _command_targets()) {
|
|
268
|
+
checked += 1;
|
|
269
|
+
for (const e of _shape_errors(name, subVerb, 'command')) {
|
|
270
|
+
const rel2 = path.relative(ROOT, fullPath).split(path.sep).join('/');
|
|
271
|
+
process.stderr.write(`❌ ${rel2}: ${e}\n`);
|
|
272
|
+
issues += 1;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
208
275
|
return [checked, issues];
|
|
209
276
|
}
|
|
210
277
|
|
|
@@ -261,6 +328,25 @@ function main(argv?: readonly string[]): number {
|
|
|
261
328
|
return check_single(args.name);
|
|
262
329
|
}
|
|
263
330
|
const [checked, issues] = scan();
|
|
331
|
+
// Scope assertion: 0 names checked means every root moved, not that every
|
|
332
|
+
// name is valid. That was this gate's shipped state until 2026-08-02.
|
|
333
|
+
try {
|
|
334
|
+
assertScanned({
|
|
335
|
+
gate: 'lint_namespace',
|
|
336
|
+
scanned: checked,
|
|
337
|
+
units: 'name(s)',
|
|
338
|
+
roots: [
|
|
339
|
+
...TARGETS.map((t) => path.relative(ROOT, t.root).split(path.sep).join('/')),
|
|
340
|
+
'src/domains/**/command.md',
|
|
341
|
+
],
|
|
342
|
+
});
|
|
343
|
+
} catch (exc) {
|
|
344
|
+
if (!(exc instanceof DeadScopeError)) {
|
|
345
|
+
throw exc;
|
|
346
|
+
}
|
|
347
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
348
|
+
return 2;
|
|
349
|
+
}
|
|
264
350
|
if (issues) {
|
|
265
351
|
process.stderr.write(`BASELINE: ${issues} issue(s) across ${checked} name(s)\n`);
|
|
266
352
|
return 1;
|
|
@@ -299,7 +385,6 @@ if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
|
299
385
|
|
|
300
386
|
export {
|
|
301
387
|
ROOT,
|
|
302
|
-
SRC,
|
|
303
388
|
NAME_RE,
|
|
304
389
|
RESERVED,
|
|
305
390
|
NON_ARTEFACTS,
|
|
@@ -33,6 +33,9 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
33
33
|
|
|
34
34
|
import YAML from 'yaml';
|
|
35
35
|
|
|
36
|
+
import { SRC_SKILLS } from './_lib/agent_src.js';
|
|
37
|
+
import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
|
|
38
|
+
|
|
36
39
|
const _HERE = fileURLToPath(import.meta.url);
|
|
37
40
|
// Path(__file__).resolve().parent.parent.parent — repo root (three dirs up).
|
|
38
41
|
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
@@ -86,36 +89,19 @@ function _exists(p: string): boolean {
|
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Mirror `_skill_roots()`.
|
|
94
|
+
*
|
|
95
|
+
* ADR-051 moved skill authoring to `src/skills`; a later commit deleted the
|
|
96
|
+
* per-package source trees this used to enumerate FIRST. That preferred branch
|
|
97
|
+
* has resolved to [] on every checkout since, so the 2026-07-29 repair's
|
|
98
|
+
* `src/skills` fallback is the only one that has ever fired. The dead
|
|
99
|
+
* preference is removed rather than left as a trap that would silently take
|
|
100
|
+
* precedence again if a `packages/` directory reappeared for another reason.
|
|
101
|
+
*/
|
|
90
102
|
function _skill_roots(): string[] {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
if (_isDir(pkgs)) {
|
|
94
|
-
let entries: string[];
|
|
95
|
-
try {
|
|
96
|
-
entries = fs.readdirSync(pkgs);
|
|
97
|
-
} catch {
|
|
98
|
-
entries = [];
|
|
99
|
-
}
|
|
100
|
-
entries.sort();
|
|
101
|
-
for (const name of entries) {
|
|
102
|
-
const cand = path.join(pkgs, name, '.agent-src.uncondensed', 'skills');
|
|
103
|
-
if (_isDir(cand)) {
|
|
104
|
-
roots.push(cand);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// ADR-051 moved skill authoring to `src/skills`; a later commit deleted
|
|
109
|
-
// `packages/` entirely. Until 2026-07-29 the only fallback was the retired
|
|
110
|
-
// container, so this resolved to [] and the overlap gate silently passed
|
|
111
|
-
// every new skill.
|
|
112
|
-
if (roots.length === 0) {
|
|
113
|
-
const cand = path.join(ROOT, 'src', 'skills');
|
|
114
|
-
if (_isDir(cand)) {
|
|
115
|
-
roots = [cand];
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return roots;
|
|
103
|
+
const cand = SRC_SKILLS();
|
|
104
|
+
return _isDir(cand) ? [cand] : [];
|
|
119
105
|
}
|
|
120
106
|
|
|
121
107
|
/** Mirror `_keyword_vector(text)`. */
|
|
@@ -378,16 +364,38 @@ export function main(argv?: string[]): number {
|
|
|
378
364
|
}
|
|
379
365
|
const { baseline, quiet } = parsed;
|
|
380
366
|
|
|
367
|
+
// Scope assertion FIRST, before the no-new-skills early return: the dedupe
|
|
368
|
+
// half of this gate compares a new skill against the existing corpus, so an
|
|
369
|
+
// empty corpus does not mean "nothing to check", it means every new skill
|
|
370
|
+
// is waved through as unique. Asserting only on the diff would miss that.
|
|
371
|
+
const allSkills = collect();
|
|
372
|
+
try {
|
|
373
|
+
assertScanned({
|
|
374
|
+
gate: 'lint_new_skill_gate',
|
|
375
|
+
scanned: allSkills.length,
|
|
376
|
+
units: 'existing skill(s)',
|
|
377
|
+
roots: _skill_roots().map((r) => path.relative(ROOT, r).split(path.sep).join('/')),
|
|
378
|
+
});
|
|
379
|
+
} catch (exc) {
|
|
380
|
+
if (!(exc instanceof DeadScopeError)) {
|
|
381
|
+
throw exc;
|
|
382
|
+
}
|
|
383
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
384
|
+
return 2;
|
|
385
|
+
}
|
|
386
|
+
|
|
381
387
|
const added = added_skill_files(baseline);
|
|
382
388
|
if (added.length === 0) {
|
|
383
389
|
if (!quiet) {
|
|
384
|
-
process.stdout.write(
|
|
390
|
+
process.stdout.write(
|
|
391
|
+
`✅ No new skills added (baseline: ${baseline}; ` +
|
|
392
|
+
`${allSkills.length} existing skill(s) scanned).\n`,
|
|
393
|
+
);
|
|
385
394
|
}
|
|
386
395
|
return 0;
|
|
387
396
|
}
|
|
388
397
|
|
|
389
398
|
const addedSet = new Set(added);
|
|
390
|
-
const allSkills = collect();
|
|
391
399
|
const existing = allSkills.filter((s) => !addedSet.has(s.relpath));
|
|
392
400
|
|
|
393
401
|
const violations: string[] = [];
|
|
@@ -104,7 +104,13 @@ function _collect(): Doc[] {
|
|
|
104
104
|
push(`skill:${d.name}`, path.join(skillsDir, d.name, 'SKILL.md'));
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
// `src/agent-src/personas`, not `src/personas` — the latter has never existed
|
|
108
|
+
// since ADR-051 moved the container, so the `existsSync` guard below silently
|
|
109
|
+
// dropped all 30 personas from a comparison whose own header promises to cover
|
|
110
|
+
// them. Same dead-root class the rest of this roadmap repairs; it escaped the
|
|
111
|
+
// class-A sweep only because the literal does not carry a retired-container
|
|
112
|
+
// prefix (road-to-gates-that-can-fail Phase 1).
|
|
113
|
+
const personasDir = path.join(REPO, 'src', 'agent-src', 'personas');
|
|
108
114
|
if (fs.existsSync(personasDir)) {
|
|
109
115
|
const walk = (dir: string, prefix: string): void => {
|
|
110
116
|
for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -65,10 +65,28 @@ export interface OverrideRow {
|
|
|
65
65
|
violations: string[];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* `**Mode:** \`extend\`` — the header form the override contract specifies.
|
|
70
|
+
*
|
|
71
|
+
* Reads EVERY `**Mode:**` line, not the first. A non-global `exec` took
|
|
72
|
+
* whichever declaration appeared earliest, and the override contract doc itself
|
|
73
|
+
* carries two example `**Mode:**` lines — so a file whose real declaration is
|
|
74
|
+
* `replace` could be read as `extend` from an example sitting above it. On a
|
|
75
|
+
* safety-floor rule that is the difference between a blocked override and a
|
|
76
|
+
* silently permitted one, which makes first-match-wins a bypass rather than a
|
|
77
|
+
* parsing nit (road-to-gates-that-can-fail Phase 6.2, finding 5).
|
|
78
|
+
*
|
|
79
|
+
* Disagreeing declarations resolve to `unknown`, which the caller already
|
|
80
|
+
* treats as a violation — fail closed. A guard that cannot tell which mode a
|
|
81
|
+
* file declares must say so, never pick one.
|
|
82
|
+
*/
|
|
69
83
|
export function parse_mode(text: string): 'extend' | 'replace' | 'unknown' {
|
|
70
|
-
const
|
|
71
|
-
|
|
84
|
+
const found = new Set<string>();
|
|
85
|
+
const re = /^\s*\*\*Mode:\*\*\s*`?(extend|replace)`?\s*$/gim;
|
|
86
|
+
let m: RegExpExecArray | null;
|
|
87
|
+
while ((m = re.exec(text)) !== null) found.add(m[1] as string);
|
|
88
|
+
if (found.size !== 1) return 'unknown';
|
|
89
|
+
return [...found][0] as 'extend' | 'replace';
|
|
72
90
|
}
|
|
73
91
|
|
|
74
92
|
/** `> Overrides: <rule> §<section> — <reason>` — the citation obligation. */
|