@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
|
@@ -2,16 +2,54 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Enforce cross-pack reference boundaries.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* changes — historical quirks preserved (consumers pin the exact behaviour).
|
|
5
|
+
* Walks every markdown link in every source artefact and verifies the link
|
|
6
|
+
* target's pack is the same pack, `core` (always allowed), or reachable from
|
|
7
|
+
* the source pack's `requires` graph. A link that crosses an undeclared pack
|
|
8
|
+
* boundary is dead the moment a consumer installs the source pack without the
|
|
9
|
+
* target's pack.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* ## Scan-root repair (2026-08-02) — what changed and what parity survives
|
|
12
|
+
*
|
|
13
|
+
* This script was ported from a retired Python original and its header claimed
|
|
14
|
+
* byte-exact parity with it ("No behaviour changes — historical quirks
|
|
15
|
+
* preserved"). That claim is now PARTLY FALSE and is corrected here rather
|
|
16
|
+
* than left standing:
|
|
17
|
+
*
|
|
18
|
+
* - **BROKEN deliberately:** the scan root. The original walked the retired
|
|
19
|
+
* per-package source container under `packages/` — a tree ADR-051 split and
|
|
20
|
+
* a later commit deleted. The gate therefore printed `no packages/ tree to
|
|
21
|
+
* lint — skipping` and exited 0 on every run since — it enforced nothing.
|
|
22
|
+
* The corpus is now the live artefact tree via the shared resolver
|
|
23
|
+
* (`_lib/agent_src.iter_artefacts`), and an empty corpus is a LOUD failure
|
|
24
|
+
* (`_lib/scan_scope.assertScanned`), not a silent skip.
|
|
25
|
+
* - **BROKEN deliberately:** pack attribution. The original read the pack from
|
|
26
|
+
* the `packages/<pack>/` directory the artefact lived in. That layout is
|
|
27
|
+
* gone, so the pack now comes from the artefact's own `packs:` frontmatter
|
|
28
|
+
* (see `_packs_of` for the full rule, including the fail-safe).
|
|
29
|
+
* - **BROKEN deliberately:** `requires` is expanded TRANSITIVELY. The original
|
|
30
|
+
* compared against the direct list only, which reports a violation for a
|
|
31
|
+
* target the resolver provably does install (`nextjs` → `javascript`, via
|
|
32
|
+
* `typescript`). `src/config/discovery/packs.yml` documents `requires` as
|
|
33
|
+
* "the hard dependency graph (transitive, acyclic) the resolver expands";
|
|
34
|
+
* this gate now matches that definition.
|
|
35
|
+
* - **BROKEN deliberately:** exit code 2 is new (dead scan scope). 0 and 1
|
|
36
|
+
* keep their meanings.
|
|
37
|
+
* - **STILL HOLDS:** the CLI surface (`--format text|json`, `--quiet`, `-h`),
|
|
38
|
+
* the link regex and its fragment/query stripping, link-resolution semantics
|
|
39
|
+
* (external / absolute / out-of-tree targets ignored), the allow rule
|
|
40
|
+
* (same pack · `core` · declared `requires`), the per-violation text and
|
|
41
|
+
* JSON shapes, the stdout/stderr split, and exit 0 clean / 1 violations.
|
|
42
|
+
*
|
|
43
|
+
* ## Where the `requires` graph is read from
|
|
44
|
+
*
|
|
45
|
+
* `src/config/discovery/packs.yml` is the source of truth for the pack
|
|
46
|
+
* vocabulary and its `requires` edges — `generate_pack_manifests.ts` DERIVES
|
|
47
|
+
* every `src/{packs,domains}/<pack>/pack.yaml` from it. Reading the vocabulary
|
|
48
|
+
* therefore covers all 33 pack ids, including the three (`frontend-design`,
|
|
49
|
+
* `scale-discipline`, `history-discipline`) that artefacts declare but that own
|
|
50
|
+
* no pack-home directory and so have no generated manifest. The manifests are
|
|
51
|
+
* still read, as an overlay, for any pack home the vocabulary does not list
|
|
52
|
+
* (today: `core`).
|
|
15
53
|
*/
|
|
16
54
|
|
|
17
55
|
import * as fs from 'node:fs';
|
|
@@ -19,15 +57,72 @@ import * as path from 'node:path';
|
|
|
19
57
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
58
|
import { parse as parseYaml } from 'yaml';
|
|
21
59
|
|
|
22
|
-
|
|
60
|
+
import {
|
|
61
|
+
SRC_AGENT,
|
|
62
|
+
SRC_DOMAINS,
|
|
63
|
+
SRC_RULES,
|
|
64
|
+
SRC_SKILLS,
|
|
65
|
+
iter_artefacts,
|
|
66
|
+
} from './_lib/agent_src.js';
|
|
67
|
+
import { checkRatchet } from './_lib/gate_baseline.js';
|
|
68
|
+
import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
|
|
69
|
+
import { parse_frontmatter } from './validate_frontmatter.js';
|
|
70
|
+
|
|
23
71
|
const _HERE = path.resolve(fileURLToPath(import.meta.url));
|
|
24
|
-
|
|
25
|
-
|
|
72
|
+
|
|
73
|
+
// Module-level paths are test seams (mirrors the `_set_paths` shape used by
|
|
74
|
+
// the sibling `lint_artefact_frontmatter`). Mutable lets + a setter.
|
|
75
|
+
let ROOT = _realpath(path.resolve(path.dirname(_HERE), '..', '..'));
|
|
76
|
+
let VOCAB = path.join(ROOT, 'src', 'config', 'discovery', 'packs.yml');
|
|
77
|
+
|
|
78
|
+
const _DEFAULT_ROOT = ROOT;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* True while a caller has repointed the root at a fixture tree.
|
|
82
|
+
*
|
|
83
|
+
* Stateless on purpose (compared against the value captured at import) so a
|
|
84
|
+
* test restoring the real path also restores real-repo behaviour. Two things
|
|
85
|
+
* key off it: the scan roots — an injected tree is scanned EXCLUSIVELY, because
|
|
86
|
+
* the shared resolver still points at the real repo and would otherwise drag
|
|
87
|
+
* the whole corpus into a fixture run — and the violation ratchet, which
|
|
88
|
+
* records repo debt and must never judge a fixture.
|
|
89
|
+
*/
|
|
90
|
+
function _paths_overridden(): boolean {
|
|
91
|
+
return ROOT !== _DEFAULT_ROOT;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Test seam: repoint the root (and optionally the vocabulary file).
|
|
96
|
+
*
|
|
97
|
+
* Passing the default root restores real-repo behaviour, vocabulary included.
|
|
98
|
+
*/
|
|
99
|
+
function _set_paths_for_test(opts: { root?: string; vocab?: string }): void {
|
|
100
|
+
if (opts.root !== undefined) {
|
|
101
|
+
ROOT = opts.root;
|
|
102
|
+
VOCAB =
|
|
103
|
+
ROOT === _DEFAULT_ROOT
|
|
104
|
+
? path.join(ROOT, 'src', 'config', 'discovery', 'packs.yml')
|
|
105
|
+
: path.join(ROOT, 'packs.yml');
|
|
106
|
+
}
|
|
107
|
+
if (opts.vocab !== undefined) {
|
|
108
|
+
VOCAB = opts.vocab;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
26
111
|
|
|
27
112
|
// [..](target) with an optional #fragment / ?query stripped from the target.
|
|
28
113
|
const LINK_RE = /\[[^\]]*\]\(([^)#?]+)(?:[#?][^)]*)?\)/g;
|
|
29
114
|
|
|
115
|
+
/** A pack every install carries — a link into it can never dangle. */
|
|
116
|
+
const ALWAYS_INSTALLED = 'core';
|
|
117
|
+
|
|
118
|
+
type ViolationKind =
|
|
119
|
+
/** Link crosses into a pack the source pack does not require. */
|
|
120
|
+
| 'cross-pack'
|
|
121
|
+
/** A unit-defining artefact whose own pack could not be determined. */
|
|
122
|
+
| 'unresolved-pack';
|
|
123
|
+
|
|
30
124
|
interface ViolationRecord {
|
|
125
|
+
kind: ViolationKind;
|
|
31
126
|
source_pack: string;
|
|
32
127
|
target_pack: string;
|
|
33
128
|
source: string;
|
|
@@ -65,6 +160,14 @@ function _isDir(p: string): boolean {
|
|
|
65
160
|
}
|
|
66
161
|
}
|
|
67
162
|
|
|
163
|
+
function _isFile(p: string): boolean {
|
|
164
|
+
try {
|
|
165
|
+
return fs.statSync(p).isFile();
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
68
171
|
function _exists(p: string): boolean {
|
|
69
172
|
try {
|
|
70
173
|
fs.statSync(p);
|
|
@@ -89,29 +192,6 @@ function _load_pack_meta(pkg_dir: string): Record<string, unknown> {
|
|
|
89
192
|
: {};
|
|
90
193
|
}
|
|
91
194
|
|
|
92
|
-
/** Sorted list of immediate child dir entries of `packages/`, dirs only. */
|
|
93
|
-
function _sortedPackageDirs(): string[] {
|
|
94
|
-
let entries: fs.Dirent[];
|
|
95
|
-
try {
|
|
96
|
-
entries = fs.readdirSync(PACKAGES, { withFileTypes: true });
|
|
97
|
-
} catch {
|
|
98
|
-
return [];
|
|
99
|
-
}
|
|
100
|
-
return entries
|
|
101
|
-
.map((e) => path.join(PACKAGES, e.name))
|
|
102
|
-
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
|
|
103
|
-
.filter((p) => _isDir(p));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function _packId(meta: Record<string, unknown>, pkgName: string): string {
|
|
107
|
-
const id = meta['id'];
|
|
108
|
-
if (typeof id === 'string' && id.length > 0) {
|
|
109
|
-
return id;
|
|
110
|
-
}
|
|
111
|
-
// pkg.name.removeprefix("pack-")
|
|
112
|
-
return pkgName.startsWith('pack-') ? pkgName.slice('pack-'.length) : pkgName;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
195
|
/** Recursively list `*.md` files under `dir`, sorted (sorted(rglob)). */
|
|
116
196
|
function _rglobMdSorted(dir: string): string[] {
|
|
117
197
|
const out: string[] = [];
|
|
@@ -135,23 +215,223 @@ function _rglobMdSorted(dir: string): string[] {
|
|
|
135
215
|
return out.sort();
|
|
136
216
|
}
|
|
137
217
|
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
218
|
+
/**
|
|
219
|
+
* The scan roots, named individually rather than via one container.
|
|
220
|
+
*
|
|
221
|
+
* Deliberately NOT `artefact_roots()`: that category-append view also yields
|
|
222
|
+
* `src/templates`, an unrelated consumer-scaffold tree this gate never
|
|
223
|
+
* scanned. Repairing a root must restore the old scope, not silently widen it.
|
|
224
|
+
* `iter_artefacts()` (used below) walks exactly these four.
|
|
225
|
+
*/
|
|
226
|
+
function _scan_roots(): string[] {
|
|
227
|
+
if (_paths_overridden()) {
|
|
228
|
+
// Fixture mode — the injected tree is the whole world.
|
|
229
|
+
return [
|
|
230
|
+
path.join(ROOT, 'skills'),
|
|
231
|
+
path.join(ROOT, 'rules'),
|
|
232
|
+
path.join(ROOT, 'commands'),
|
|
233
|
+
].filter(_isDir);
|
|
143
234
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
235
|
+
return [SRC_AGENT(), SRC_SKILLS(), SRC_RULES(), SRC_DOMAINS()].filter(_isDir);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Every source artefact, as absolute paths, in deterministic order. */
|
|
239
|
+
function _iter_source_files(): string[] {
|
|
240
|
+
if (_paths_overridden()) {
|
|
241
|
+
const out: string[] = [];
|
|
242
|
+
for (const root of _scan_roots()) {
|
|
243
|
+
out.push(..._rglobMdSorted(root));
|
|
244
|
+
}
|
|
245
|
+
return out;
|
|
246
|
+
}
|
|
247
|
+
return [...iter_artefacts()];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Pack-home directories carrying a generated `pack.yaml`. */
|
|
251
|
+
function _pack_home_dirs(): string[] {
|
|
252
|
+
const parents = _paths_overridden()
|
|
253
|
+
? [path.join(ROOT, 'packs')]
|
|
254
|
+
: [path.join(ROOT, 'src', 'packs'), path.join(ROOT, 'src', 'domains')];
|
|
255
|
+
const out: string[] = [];
|
|
256
|
+
for (const parent of parents) {
|
|
257
|
+
if (!_isDir(parent)) {
|
|
147
258
|
continue;
|
|
148
259
|
}
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
260
|
+
for (const name of fs.readdirSync(parent).sort()) {
|
|
261
|
+
const dir = path.join(parent, name);
|
|
262
|
+
if (_isDir(dir) && _exists(path.join(dir, 'pack.yaml'))) {
|
|
263
|
+
out.push(dir);
|
|
264
|
+
}
|
|
152
265
|
}
|
|
153
266
|
}
|
|
154
|
-
return
|
|
267
|
+
return out;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* `pack id -> direct requires`.
|
|
272
|
+
*
|
|
273
|
+
* `packs.yml` first (the source of truth every `pack.yaml` is generated from,
|
|
274
|
+
* and the only place the three home-less packs are declared), then the
|
|
275
|
+
* manifests as an overlay for pack homes the vocabulary does not list.
|
|
276
|
+
*/
|
|
277
|
+
function _pack_requires(): Map<string, string[]> {
|
|
278
|
+
const out = new Map<string, string[]>();
|
|
279
|
+
const asList = (v: unknown): string[] => (Array.isArray(v) ? v.map(String) : []);
|
|
280
|
+
|
|
281
|
+
if (_exists(VOCAB)) {
|
|
282
|
+
const vocab = parseYaml(fs.readFileSync(VOCAB, 'utf-8'), { version: '1.1' });
|
|
283
|
+
if (Array.isArray(vocab)) {
|
|
284
|
+
for (const entry of vocab) {
|
|
285
|
+
if (entry !== null && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
286
|
+
const e = entry as Record<string, unknown>;
|
|
287
|
+
if (typeof e['id'] === 'string') {
|
|
288
|
+
out.set(e['id'], asList(e['requires']));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (const dir of _pack_home_dirs()) {
|
|
295
|
+
const meta = _load_pack_meta(dir);
|
|
296
|
+
const id = typeof meta['id'] === 'string' ? meta['id'] : path.basename(dir);
|
|
297
|
+
if (!out.has(id)) {
|
|
298
|
+
out.set(id, asList(meta['requires']));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Transitive expansion of `requires`, cycle-safe. */
|
|
305
|
+
function _requires_closure(pack: string, direct: Map<string, string[]>): Set<string> {
|
|
306
|
+
const seen = new Set<string>();
|
|
307
|
+
const stack = [...(direct.get(pack) ?? [])];
|
|
308
|
+
while (stack.length > 0) {
|
|
309
|
+
const next = stack.pop() as string;
|
|
310
|
+
if (seen.has(next)) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
seen.add(next);
|
|
314
|
+
for (const dep of direct.get(next) ?? []) {
|
|
315
|
+
stack.push(dep);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return seen;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Packs declared in an artefact's own frontmatter (`packs:`). */
|
|
322
|
+
function _declared_packs(p: string): string[] {
|
|
323
|
+
let text: string;
|
|
324
|
+
try {
|
|
325
|
+
text = fs.readFileSync(p, 'utf-8');
|
|
326
|
+
} catch {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
const [fm] = parse_frontmatter(text);
|
|
330
|
+
if (fm === null) {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
const packs = fm['packs'];
|
|
334
|
+
if (Array.isArray(packs)) {
|
|
335
|
+
return packs.map(String).filter((s) => s !== '');
|
|
336
|
+
}
|
|
337
|
+
return typeof packs === 'string' && packs !== '' ? [packs] : [];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* A unit-defining artefact — one ADR-013 requires to declare `packs:`.
|
|
342
|
+
*
|
|
343
|
+
* These are the files whose pack MUST be resolvable; the fail-safe below turns
|
|
344
|
+
* an unresolvable one into a violation rather than a silent exclusion.
|
|
345
|
+
*/
|
|
346
|
+
function _is_unit_file(abs: string): boolean {
|
|
347
|
+
const base = path.basename(abs);
|
|
348
|
+
if (base === 'SKILL.md') {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
// `command.md` only counts inside a command home. The same basename is
|
|
352
|
+
// also used by the authoring SCAFFOLDS under `src/agent-src/templates/`,
|
|
353
|
+
// which carry no frontmatter on purpose (quarantined in
|
|
354
|
+
// `src/config/discovery/unassigned-artefacts.yml`) and are not commands.
|
|
355
|
+
const commandRoot = _paths_overridden() ? path.join(ROOT, 'commands') : SRC_DOMAINS();
|
|
356
|
+
if (base === 'command.md') {
|
|
357
|
+
return !_relPosix(abs, commandRoot).startsWith('..');
|
|
358
|
+
}
|
|
359
|
+
const rulesRoot = _paths_overridden() ? path.join(ROOT, 'rules') : SRC_RULES();
|
|
360
|
+
return path.dirname(abs) === rulesRoot && base.endsWith('.md');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Resolve an artefact's pack set. Returns `[]` when it cannot be determined.
|
|
365
|
+
*
|
|
366
|
+
* The rule, in order (the `packages/<pack>/…` container the original gate read
|
|
367
|
+
* no longer exists, so attribution is declaration-first):
|
|
368
|
+
*
|
|
369
|
+
* 1. the artefact's own `packs:` frontmatter;
|
|
370
|
+
* 2. else the nearest ancestor `SKILL.md`'s packs — a skill's auxiliary
|
|
371
|
+
* reference files carry no frontmatter and ship with their skill;
|
|
372
|
+
* 3. else, under `src/domains/<pack>/`, the pack home directory it lives in;
|
|
373
|
+
* 4. else UNASSIGNED — contexts, personas, templates and other cross-cutting
|
|
374
|
+
* surfaces that legitimately belong to no pack (registry:
|
|
375
|
+
* `src/config/discovery/unassigned-artefacts.yml`).
|
|
376
|
+
*
|
|
377
|
+
* Fail-safe: an UNASSIGNED artefact is not indexed, so it is never a source and
|
|
378
|
+
* never launders a link as a target — and the count of them is printed in the
|
|
379
|
+
* summary, so the exclusion is visible rather than silent. For the classes that
|
|
380
|
+
* MUST declare a pack (`_is_unit_file`) an unassigned result is reported as an
|
|
381
|
+
* `unresolved-pack` violation: such an artefact cannot be boundary-checked and
|
|
382
|
+
* must not pass by omission. (`lint_artefact_frontmatter` enforces the same
|
|
383
|
+
* `packs:` presence independently, so this is a second lock, not the only one.)
|
|
384
|
+
*/
|
|
385
|
+
function _packs_of(abs: string, cache: Map<string, string[]>): string[] {
|
|
386
|
+
const cached = cache.get(abs);
|
|
387
|
+
if (cached !== undefined) {
|
|
388
|
+
return cached;
|
|
389
|
+
}
|
|
390
|
+
let packs = _declared_packs(abs);
|
|
391
|
+
|
|
392
|
+
if (packs.length === 0) {
|
|
393
|
+
// Nearest ancestor SKILL.md.
|
|
394
|
+
let dir = path.dirname(abs);
|
|
395
|
+
const stopAt = path.parse(dir).root;
|
|
396
|
+
while (dir !== stopAt && dir.startsWith(ROOT)) {
|
|
397
|
+
const owner = path.join(dir, 'SKILL.md');
|
|
398
|
+
if (owner !== abs && _isFile(owner)) {
|
|
399
|
+
packs = _declared_packs(owner);
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
dir = path.dirname(dir);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (packs.length === 0 && !_paths_overridden()) {
|
|
406
|
+
const rel = _relPosix(abs, SRC_DOMAINS());
|
|
407
|
+
if (!rel.startsWith('..') && rel.includes('/')) {
|
|
408
|
+
packs = [rel.split('/')[0] as string];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
cache.set(abs, packs);
|
|
412
|
+
return packs;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Map repo-relative POSIX artefact path -> declared pack ids. */
|
|
416
|
+
function _build_artefact_index(): {
|
|
417
|
+
index: Map<string, string[]>;
|
|
418
|
+
unassigned: string[];
|
|
419
|
+
scanned: number;
|
|
420
|
+
} {
|
|
421
|
+
const index = new Map<string, string[]>();
|
|
422
|
+
const unassigned: string[] = [];
|
|
423
|
+
const cache = new Map<string, string[]>();
|
|
424
|
+
const files = _iter_source_files();
|
|
425
|
+
for (const abs of files) {
|
|
426
|
+
const packs = _packs_of(abs, cache);
|
|
427
|
+
const rel = _relPosix(abs, ROOT);
|
|
428
|
+
if (packs.length === 0) {
|
|
429
|
+
unassigned.push(rel);
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
index.set(rel, packs);
|
|
433
|
+
}
|
|
434
|
+
return { index, unassigned, scanned: files.length };
|
|
155
435
|
}
|
|
156
436
|
|
|
157
437
|
/** Resolve a markdown link target to a repo-relative path, or null. */
|
|
@@ -194,7 +474,7 @@ function _scan_file(p: string): string[] {
|
|
|
194
474
|
LINK_RE.lastIndex = 0;
|
|
195
475
|
let m: RegExpExecArray | null;
|
|
196
476
|
while ((m = LINK_RE.exec(text)) !== null) {
|
|
197
|
-
out.push(m[1]
|
|
477
|
+
out.push(m[1] as string);
|
|
198
478
|
}
|
|
199
479
|
return out;
|
|
200
480
|
}
|
|
@@ -203,18 +483,37 @@ function _is_allowed(source_pack: string, target_pack: string, requires: string[
|
|
|
203
483
|
if (source_pack === target_pack) {
|
|
204
484
|
return true;
|
|
205
485
|
}
|
|
206
|
-
if (target_pack ===
|
|
486
|
+
if (target_pack === ALWAYS_INSTALLED) {
|
|
207
487
|
return true;
|
|
208
488
|
}
|
|
209
489
|
return (requires || []).includes(target_pack);
|
|
210
490
|
}
|
|
211
491
|
|
|
492
|
+
/**
|
|
493
|
+
* Set-level allow rule.
|
|
494
|
+
*
|
|
495
|
+
* EVERY source pack must reach SOME target pack: a multi-pack artefact ships
|
|
496
|
+
* in each of its packs independently, so a link that resolves in only one of
|
|
497
|
+
* them is dead in the others. A multi-pack TARGET is present as soon as any one
|
|
498
|
+
* of its packs is reachable.
|
|
499
|
+
*/
|
|
500
|
+
function _link_allowed(
|
|
501
|
+
source_packs: readonly string[],
|
|
502
|
+
target_packs: readonly string[],
|
|
503
|
+
closureOf: (pack: string) => Set<string>,
|
|
504
|
+
): boolean {
|
|
505
|
+
return source_packs.every((s) => {
|
|
506
|
+
const reach = [...closureOf(s)];
|
|
507
|
+
return target_packs.some((t) => _is_allowed(s, t, reach));
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
212
511
|
/** Mirror Python `json.dump(obj, indent=2)` with `ensure_ascii=True`. */
|
|
213
512
|
function _json_dumps_ascii(obj: unknown): string {
|
|
214
513
|
const raw = JSON.stringify(obj, null, 2);
|
|
215
514
|
let out = '';
|
|
216
515
|
for (const ch of raw) {
|
|
217
|
-
const code = ch.codePointAt(0)
|
|
516
|
+
const code = ch.codePointAt(0) as number;
|
|
218
517
|
if (code < 0x80) {
|
|
219
518
|
out += ch;
|
|
220
519
|
} else {
|
|
@@ -235,7 +534,7 @@ function parse_args(argv: readonly string[]): ParsedArgs {
|
|
|
235
534
|
let format: 'text' | 'json' = 'text';
|
|
236
535
|
let quiet = false;
|
|
237
536
|
for (let i = 0; i < argv.length; i++) {
|
|
238
|
-
const arg = argv[i]
|
|
537
|
+
const arg = argv[i] as string;
|
|
239
538
|
if (arg === '--format') {
|
|
240
539
|
const v = argv[++i];
|
|
241
540
|
if (v === undefined) {
|
|
@@ -274,41 +573,75 @@ function _argparse_error(message: string): never {
|
|
|
274
573
|
process.exit(2);
|
|
275
574
|
}
|
|
276
575
|
|
|
277
|
-
function main(): number {
|
|
278
|
-
const args = parse_args(process.argv.slice(2));
|
|
576
|
+
function main(argv?: readonly string[]): number {
|
|
577
|
+
const args = parse_args(argv ?? process.argv.slice(2));
|
|
279
578
|
|
|
280
|
-
const artefact_pack = _build_artefact_index();
|
|
281
|
-
if (artefact_pack.size === 0) {
|
|
282
|
-
process.stderr.write('no packages/ tree to lint — skipping\n');
|
|
283
|
-
return 0;
|
|
284
|
-
}
|
|
579
|
+
const { index: artefact_pack, unassigned, scanned } = _build_artefact_index();
|
|
285
580
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
581
|
+
// Scope assertion: 0 artefacts means the root moved, not that the tree is
|
|
582
|
+
// clean. That was this gate's shipped state until 2026-08-02 — it walked
|
|
583
|
+
// the deleted `packages/` tree and skipped green forever.
|
|
584
|
+
try {
|
|
585
|
+
assertScanned({
|
|
586
|
+
gate: 'lint_pack_boundaries',
|
|
587
|
+
scanned,
|
|
588
|
+
units: 'artefact(s)',
|
|
589
|
+
roots: _scan_roots().map((r) => _relPosix(r, ROOT) || '.'),
|
|
590
|
+
});
|
|
591
|
+
} catch (exc) {
|
|
592
|
+
if (!(exc instanceof DeadScopeError)) {
|
|
593
|
+
throw exc;
|
|
594
|
+
}
|
|
595
|
+
process.stderr.write(`❌ ${exc.message}\n`);
|
|
596
|
+
return 2;
|
|
292
597
|
}
|
|
293
598
|
|
|
599
|
+
const pack_requires = _pack_requires();
|
|
600
|
+
const closureCache = new Map<string, Set<string>>();
|
|
601
|
+
const closureOf = (pack: string): Set<string> => {
|
|
602
|
+
let c = closureCache.get(pack);
|
|
603
|
+
if (c === undefined) {
|
|
604
|
+
c = _requires_closure(pack, pack_requires);
|
|
605
|
+
closureCache.set(pack, c);
|
|
606
|
+
}
|
|
607
|
+
return c;
|
|
608
|
+
};
|
|
609
|
+
|
|
294
610
|
const violations: ViolationRecord[] = [];
|
|
295
|
-
|
|
611
|
+
|
|
612
|
+
// Fail-safe: a unit-defining artefact with no resolvable pack cannot be
|
|
613
|
+
// boundary-checked, so it is a finding rather than a silent exclusion.
|
|
614
|
+
for (const rel of unassigned) {
|
|
615
|
+
if (_is_unit_file(path.join(ROOT, rel))) {
|
|
616
|
+
violations.push({
|
|
617
|
+
kind: 'unresolved-pack',
|
|
618
|
+
source_pack: '(unresolved)',
|
|
619
|
+
target_pack: '',
|
|
620
|
+
source: rel,
|
|
621
|
+
target: '',
|
|
622
|
+
link: '',
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
for (const [rel_path, src_packs] of artefact_pack) {
|
|
296
628
|
const source_file = path.join(ROOT, rel_path);
|
|
297
629
|
for (const raw of _scan_file(source_file)) {
|
|
298
630
|
const target_rel = _resolve_link(source_file, raw);
|
|
299
631
|
if (target_rel === null) {
|
|
300
632
|
continue;
|
|
301
633
|
}
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
continue; //
|
|
634
|
+
const target_packs = artefact_pack.get(target_rel);
|
|
635
|
+
if (target_packs === undefined) {
|
|
636
|
+
continue; // docs/, scripts/, root files, unassigned — not pack-scoped
|
|
305
637
|
}
|
|
306
|
-
if (
|
|
638
|
+
if (_link_allowed(src_packs, target_packs, closureOf)) {
|
|
307
639
|
continue;
|
|
308
640
|
}
|
|
309
641
|
violations.push({
|
|
310
|
-
|
|
311
|
-
|
|
642
|
+
kind: 'cross-pack',
|
|
643
|
+
source_pack: src_packs.join('+'),
|
|
644
|
+
target_pack: target_packs.join('+'),
|
|
312
645
|
source: rel_path,
|
|
313
646
|
target: target_rel,
|
|
314
647
|
link: raw,
|
|
@@ -316,33 +649,64 @@ function main(): number {
|
|
|
316
649
|
}
|
|
317
650
|
}
|
|
318
651
|
|
|
652
|
+
const verdict = _paths_overridden()
|
|
653
|
+
? null
|
|
654
|
+
: checkRatchet({
|
|
655
|
+
gate: 'lint_pack_boundaries',
|
|
656
|
+
actual: violations.length,
|
|
657
|
+
repoRoot: ROOT,
|
|
658
|
+
});
|
|
659
|
+
const ok = violations.length === 0 || (verdict !== null && verdict.ok);
|
|
660
|
+
|
|
319
661
|
if (args.format === 'json') {
|
|
320
662
|
process.stdout.write(
|
|
321
|
-
_json_dumps_ascii({
|
|
663
|
+
_json_dumps_ascii({
|
|
664
|
+
violations,
|
|
665
|
+
count: violations.length,
|
|
666
|
+
scanned,
|
|
667
|
+
unassigned: unassigned.length,
|
|
668
|
+
ratchet:
|
|
669
|
+
verdict === null ? null : { status: verdict.status, baseline: verdict.baseline },
|
|
670
|
+
}) + '\n',
|
|
322
671
|
);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
672
|
+
return ok ? 0 : 1;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (!args.quiet) {
|
|
676
|
+
process.stdout.write(
|
|
677
|
+
`lint_pack_boundaries: scanned ${scanned} artefacts across ` +
|
|
678
|
+
`${pack_requires.size} packs (${unassigned.length} outside pack scope)\n`,
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
for (const v of violations) {
|
|
682
|
+
if (v.kind === 'unresolved-pack') {
|
|
331
683
|
process.stdout.write(
|
|
332
|
-
` ✗ ${v.
|
|
333
|
-
|
|
684
|
+
` ✗ ${v.source} : no \`packs:\` declared and no owning pack — ` +
|
|
685
|
+
'cannot be boundary-checked\n',
|
|
334
686
|
);
|
|
687
|
+
continue;
|
|
335
688
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
689
|
+
process.stdout.write(
|
|
690
|
+
` ✗ ${v.source_pack} -> ${v.target_pack} : ${v.source} → ${v.target} ` +
|
|
691
|
+
`(link: ${v.link})\n`,
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (violations.length > 0) {
|
|
695
|
+
process.stdout.write(
|
|
696
|
+
`\n${violations.length} cross-pack violation(s) — declare 'requires' in ` +
|
|
697
|
+
'pack.yaml or move the artefact\n',
|
|
698
|
+
);
|
|
699
|
+
if (verdict !== null) {
|
|
700
|
+
if (verdict.ok) {
|
|
701
|
+
process.stdout.write(`⚠️ ${verdict.message}\n`);
|
|
702
|
+
} else {
|
|
703
|
+
process.stderr.write(`❌ ${verdict.message}\n`);
|
|
704
|
+
}
|
|
343
705
|
}
|
|
706
|
+
} else if (!args.quiet) {
|
|
707
|
+
process.stdout.write('OK — no cross-pack drift\n');
|
|
344
708
|
}
|
|
345
|
-
return
|
|
709
|
+
return ok ? 0 : 1;
|
|
346
710
|
}
|
|
347
711
|
|
|
348
712
|
function _isCliEntry(): boolean {
|
|
@@ -372,14 +736,20 @@ if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
|
372
736
|
}
|
|
373
737
|
|
|
374
738
|
export {
|
|
739
|
+
type ViolationKind,
|
|
375
740
|
type ViolationRecord,
|
|
376
741
|
ROOT,
|
|
377
|
-
PACKAGES,
|
|
378
742
|
LINK_RE,
|
|
379
743
|
_build_artefact_index,
|
|
380
744
|
_resolve_link,
|
|
381
745
|
_scan_file,
|
|
382
746
|
_is_allowed,
|
|
747
|
+
_link_allowed,
|
|
383
748
|
_load_pack_meta,
|
|
749
|
+
_pack_requires,
|
|
750
|
+
_requires_closure,
|
|
751
|
+
_packs_of,
|
|
752
|
+
_scan_roots,
|
|
753
|
+
_set_paths_for_test,
|
|
384
754
|
main,
|
|
385
755
|
};
|