@event4u/agent-config 14.6.0 → 14.8.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 +255 -145
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +264 -10
- package/dist/agent-src/commands/analyze.md +1 -1
- package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
- package/dist/agent-src/commands/evals/roadmap.json +17 -1
- package/dist/agent-src/commands/pr/create.md +14 -2
- package/dist/agent-src/commands/pr/merge.md +379 -0
- package/dist/agent-src/commands/roadmap/process-full.md +272 -32
- package/dist/agent-src/commands/roadmap.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +95 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +289 -106
- package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +67 -32
- package/dist/agent-src/rules/fix-what-you-see.md +108 -0
- package/dist/agent-src/rules/no-roadmap-references.md +0 -5
- package/dist/agent-src/rules/recurring-criticism.md +110 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +20 -1
- package/dist/agent-src/rules/rule-type-governance.md +0 -2
- package/dist/agent-src/rules/self-repair-loop.md +17 -0
- package/dist/agent-src/rules/skill-quality.md +0 -2
- package/dist/agent-src/rules/source-confidentiality.md +37 -7
- package/dist/agent-src/scripts/archival_sweep.ts +117 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +26 -7
- package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
- package/dist/agent-src/scripts/gate_budget.ts +282 -0
- package/dist/agent-src/scripts/gate_execute.ts +72 -31
- package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
- package/dist/agent-src/scripts/update_roadmap_progress.ts +296 -53
- package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
- package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
- package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
- package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
- package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
- package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
- package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
- package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
- package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
- package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
- package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
- package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
- package/dist/agent-src/skills/security/SKILL.md +8 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
- package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
- package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +65 -4
- package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/dist/cli/registry.js +2 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
- package/dist/cli-delegate/{chunk-P6LQDIWK.js → chunk-7PRE6ZV2.js} +338 -0
- package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
- package/dist/cli-delegate/chunk-E4XIZDIB.js +402 -0
- package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-EEJUMCDZ.js} +16 -16
- package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
- package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-H4VIVD4C.js} +385 -284
- package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
- package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-LPPWO5CE.js} +12 -8
- package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
- package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-N7CDDGI3.js} +4 -23
- package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-PMEKGDED.js} +74 -167
- package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
- package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
- package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
- package/dist/cli-delegate/cmd_conformance.js +466 -716
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +10 -10
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +10 -9
- package/dist/cli-delegate/cmd_handoff.js +10 -9
- package/dist/cli-delegate/cmd_memory_get.js +2 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +5 -5
- package/dist/cli-delegate/cmd_prune.js +11 -10
- package/dist/cli-delegate/cmd_route_audit.js +6 -5
- package/dist/cli-delegate/cmd_route_explain.js +3 -3
- package/dist/cli-delegate/cmd_session_recycle.js +840 -30
- package/dist/cli-delegate/cmd_settings_get.js +3 -3
- package/dist/cli-delegate/cmd_settings_migrate.js +10 -9
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +11 -10
- package/dist/cli-delegate/cmd_uninstall.js +13 -11
- package/dist/cli-delegate/cmd_update.js +4 -4
- package/dist/cli-delegate/cmd_upgrade.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +11 -10
- package/dist/cli-delegate/cmd_versions.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +111 -37
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +5 -5
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +15 -12
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +11 -8
- package/dist/hooks/dispatch.js +3165 -2192
- package/dist/install/claudePathsPlan.js +187 -0
- package/dist/install/claudePathsPlan.js.map +1 -0
- package/dist/install/globalRuleLayers.js +169 -0
- package/dist/install/globalRuleLayers.js.map +1 -0
- package/dist/install/hostLayerFingerprint.js +136 -0
- package/dist/install/hostLayerFingerprint.js.map +1 -0
- package/dist/install/install.mjs +553 -425
- package/dist/install/partitionEligibility.js +432 -0
- package/dist/install/partitionEligibility.js.map +1 -0
- package/dist/install/ruleLayerPartition.js +157 -0
- package/dist/install/ruleLayerPartition.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1863 -1234
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_src.js +41 -0
- package/dist/scripts/_lib/agent_src.js.map +1 -1
- package/dist/scripts/_lib/install_layout.js +83 -0
- package/dist/scripts/_lib/install_layout.js.map +1 -0
- package/dist/scripts/_lib/installed_lock.js +444 -0
- package/dist/scripts/_lib/installed_lock.js.map +1 -0
- package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
- package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
- package/dist/scripts/condense.js +63 -251
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +12 -6
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
- package/docs/guidelines/agent-infra/false-green.md +47 -1
- package/docs/guidelines/agent-infra/memory-access.md +17 -0
- package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archival_sweep.ts +117 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +26 -7
- package/src/agent-src/scripts/dashboard_mode.ts +212 -0
- package/src/agent-src/scripts/gate_budget.ts +282 -0
- package/src/agent-src/scripts/gate_execute.ts +72 -31
- package/src/agent-src/scripts/roadmap_gates.ts +74 -3
- package/src/agent-src/scripts/update_roadmap_progress.ts +296 -53
- package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/src/config/agent-settings.template.yml +27 -2
- package/src/config/ci-local-parity.yml +39 -0
- package/src/config/estate-count-budget.json +612 -3
- package/src/config/evaluator-budgets.json +3 -3
- package/src/config/gate-coverage.yml +220 -12
- package/src/config/gate-violation-baselines.json +73 -63
- package/src/config/gitignore-block.txt +15 -2
- package/src/config/hook-latency-budget.json +5 -0
- package/src/config/rule-activation-census.json +14 -6
- package/src/config/rule-stub-ceilings.json +34 -7
- package/src/install/claudePathsPlan.ts +196 -0
- package/src/install/globalRuleLayers.ts +197 -0
- package/src/install/hostLayerFingerprint.ts +145 -0
- package/src/install/partitionEligibility.ts +502 -0
- package/src/install/ruleLayerPartition.ts +212 -0
- package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
- package/src/scripts/_dispatch.bash +17 -1
- package/src/scripts/_lib/ac_heading.ts +30 -0
- package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
- package/src/scripts/_lib/adr_frontmatter.ts +459 -0
- package/src/scripts/_lib/agent_src.ts +42 -0
- package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
- package/src/scripts/_lib/envelope_grounding.ts +49 -9
- package/src/scripts/_lib/installed_lock.ts +50 -5
- package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
- package/src/scripts/_lib/md_section.ts +129 -0
- package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
- package/src/scripts/_lib/scope_guard.sh +26 -3
- package/src/scripts/_lib/self_repair.ts +19 -0
- package/src/scripts/_lib/self_repair_class_b.ts +171 -0
- package/src/scripts/adr/evidence_census.ts +899 -0
- package/src/scripts/adr/regenerate_index.ts +12 -32
- package/src/scripts/adr_cite_check.ts +540 -39
- package/src/scripts/ai_council/cli_help.ts +55 -0
- package/src/scripts/audit_adr_coverage.ts +103 -17
- package/src/scripts/before_complete_hook.ts +153 -28
- package/src/scripts/bench_ab_v2_complexity.ts +27 -7
- package/src/scripts/bench_hook_latency.ts +204 -0
- package/src/scripts/build_archive_index.ts +34 -0
- package/src/scripts/check_adr_frontmatter.ts +574 -39
- package/src/scripts/check_hook_bundle_content.ts +219 -0
- package/src/scripts/check_new_adr_evidence.ts +529 -0
- package/src/scripts/check_references.ts +78 -0
- package/src/scripts/check_release_published.ts +32 -11
- package/src/scripts/check_rule_layer_partition.ts +531 -0
- package/src/scripts/check_single_delivery.ts +194 -17
- package/src/scripts/check_standing_rule_delivery.ts +8 -2
- package/src/scripts/ci_settle.ts +170 -0
- package/src/scripts/condense.ts +79 -259
- package/src/scripts/council_cli.ts +3 -3
- package/src/scripts/dispatch_r2_reviewer.ts +6 -14
- package/src/scripts/external_sources_denylist.json +4 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +77 -14
- package/src/scripts/hooks/block_no_verify.ts +61 -10
- package/src/scripts/hooks/block_unauthorized_git.ts +21 -1
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/dispatch_hook.ts +154 -187
- package/src/scripts/hooks/dispatch_issues.ts +38 -19
- package/src/scripts/hooks/fallback_yaml.ts +66 -0
- package/src/scripts/hooks/host_semantics.ts +21 -0
- package/src/scripts/hooks/injection_budget.ts +13 -3
- package/src/scripts/hooks/py_json_dumps.ts +103 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
- package/src/scripts/hooks/run_continuation_hook.ts +8 -5
- package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
- package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
- package/src/scripts/hooks/state_io.ts +870 -21
- package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
- package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
- package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
- package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
- package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
- package/src/scripts/injection_scan_hook.ts +137 -5
- package/src/scripts/install.ts +16 -11
- package/src/scripts/language_mirror_hook.ts +561 -25
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_hook_manifest.ts +49 -0
- package/src/scripts/lint_plan_risk_register.ts +4 -3
- package/src/scripts/lint_provenance_vocabulary.ts +472 -4
- package/src/scripts/lint_skill_router_head.ts +4 -4
- package/src/scripts/mcp_server/tools.ts +2 -2
- package/src/scripts/new_roadmap.ts +136 -0
- package/src/scripts/pack_mcp_content.ts +20 -5
- package/src/scripts/release.ts +37 -38
- package/src/scripts/report_layer_overlap.ts +6 -0
- package/src/scripts/routing_doctor.ts +5 -1
- package/src/scripts/self_repair_hook.ts +37 -5
- package/src/scripts/session_register_hook.ts +11 -0
- package/src/scripts/skill_usage_collect.ts +11 -1
- package/src/scripts/skill_usage_report.ts +278 -8
- package/src/scripts/sync_pr_branch.ts +170 -20
- package/src/server/schemas/settings.ts +8 -0
- package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
- package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
- package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
- package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
- package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-MMSGVZKV.js} +3 -3
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
} from '../_lib/subagent_capsule.js';
|
|
42
42
|
import { collectGrounding } from '../_lib/envelope_grounding.js';
|
|
43
43
|
import { atomic_write_json } from '../hooks/state_io.js';
|
|
44
|
+
import { env_session_id } from '../sessions_cli.js';
|
|
44
45
|
|
|
45
46
|
export interface RecycleResult {
|
|
46
47
|
code: 0 | 1 | 2;
|
|
@@ -185,7 +186,14 @@ export function runSessionRecycle(
|
|
|
185
186
|
// composed. A model-written branch is a claim; this is a reading. The
|
|
186
187
|
// composer cannot override them — a "next_task" it got wrong is a
|
|
187
188
|
// proposal, a "head" it got wrong is a silent stale resume.
|
|
188
|
-
|
|
189
|
+
// The session id is the ONLY channel to this session's verify state, which
|
|
190
|
+
// the producer keys per session. `env_session_id` is the canonical resolver
|
|
191
|
+
// (package variable first, then the host's `CLAUDE_CODE_SESSION_ID`) and is
|
|
192
|
+
// imported rather than re-implemented — a second copy of that precedence
|
|
193
|
+
// order would drift from the one a test covers. A host that exports neither
|
|
194
|
+
// yields `null`, and `last_verify` is then simply absent from the envelope
|
|
195
|
+
// (the delete-then-set below already treats an unreadable fact as absent).
|
|
196
|
+
const grounding = collectGrounding(projectRoot, env_session_id());
|
|
189
197
|
// Drop the composer's factual keys UNCONDITIONALLY first. Guarding each
|
|
190
198
|
// assignment on `!== null` would leave a model-composed branch or head
|
|
191
199
|
// standing whenever the git read fails — the consumer would then compare
|
|
@@ -220,6 +220,7 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
|
|
|
220
220
|
(deliberate recycle instead of auto-compact). Flags:
|
|
221
221
|
--file <json> | --template; default reads stdin
|
|
222
222
|
roadmap:progress Regenerate agents/roadmaps-progress.md from open roadmaps
|
|
223
|
+
(archives completed roadmaps; --no-archive to skip)
|
|
223
224
|
roadmap:progress-check Fail if agents/roadmaps-progress.md is stale (for CI)
|
|
224
225
|
roadmap:archive Archive completed roadmaps (branch-touched by default;
|
|
225
226
|
--all for every complete one; --dry-run to preview)
|
|
@@ -237,6 +238,12 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
|
|
|
237
238
|
capabilities:index Regenerate CAPABILITIES.yaml — the package coverage index
|
|
238
239
|
(capability area → coverage → backing skills/commands → gaps).
|
|
239
240
|
Pass --check to fail if stale (for CI). Reads src/ (package repo).
|
|
241
|
+
adr:effective Effective state of one decision record: status, the
|
|
242
|
+
Decision section verbatim, clauses its own amendments
|
|
243
|
+
superseded, active amendments, provenance, evidence,
|
|
244
|
+
review trigger and trigger state. Read-only, and it
|
|
245
|
+
authorizes nothing. Exit 1 when the record still
|
|
246
|
+
asserts what its own amendment retired. Flag: --json
|
|
240
247
|
settings:check Validate .agent-settings.yml against the YAML-subset contract
|
|
241
248
|
(docs/contracts/settings-sync-yaml-subset.md). Read-only.
|
|
242
249
|
Exit 0 clean, 1 finding(s), 2 file absent / unreadable.
|
|
@@ -717,10 +724,14 @@ cmd_brand_status() {
|
|
|
717
724
|
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_brand_status.ts" "$@"
|
|
718
725
|
}
|
|
719
726
|
|
|
727
|
+
# `--archive`: a completed roadmap is archived here rather than reported as a
|
|
728
|
+
# warning the caller has to act on. `roadmap:progress-check` never passes it —
|
|
729
|
+
# a CI gate must not mutate the tree it is checking. Pass `--no-archive` to get
|
|
730
|
+
# the dashboard-only behaviour back.
|
|
720
731
|
cmd_roadmap_progress() {
|
|
721
732
|
local script
|
|
722
733
|
script="$(resolve_script "dist/agent-src/scripts/update_roadmap_progress.ts" ".augment/scripts/update_roadmap_progress.ts")"
|
|
723
|
-
exec_ts "$script" "$@"
|
|
734
|
+
exec_ts "$script" --archive "$@"
|
|
724
735
|
}
|
|
725
736
|
|
|
726
737
|
cmd_roadmap_progress_check() {
|
|
@@ -749,6 +760,10 @@ cmd_capabilities_index() {
|
|
|
749
760
|
exec_ts "$script" "$@"
|
|
750
761
|
}
|
|
751
762
|
|
|
763
|
+
cmd_adr_effective() {
|
|
764
|
+
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_adr_effective.ts" "$@"
|
|
765
|
+
}
|
|
766
|
+
|
|
752
767
|
cmd_first_run() {
|
|
753
768
|
local script
|
|
754
769
|
script="$(resolve_script "src/scripts/first-run.sh")"
|
|
@@ -1446,6 +1461,7 @@ main() {
|
|
|
1446
1461
|
roadmap:archive) cmd_roadmap_archive "$@" ;;
|
|
1447
1462
|
gates) cmd_gates "$@" ;;
|
|
1448
1463
|
capabilities:index) cmd_capabilities_index "$@" ;;
|
|
1464
|
+
adr:effective) cmd_adr_effective "$@" ;;
|
|
1449
1465
|
hooks:install) cmd_hooks_install "$@" ;;
|
|
1450
1466
|
keys:install-anthropic) cmd_keys_install_anthropic "$@" ;;
|
|
1451
1467
|
keys:install-openai) cmd_keys_install_openai "$@" ;;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one matcher for a roadmap's `## Acceptance criteria` heading.
|
|
3
|
+
*
|
|
4
|
+
* It lives here because the same defect has now been found three times, in
|
|
5
|
+
* three separately-authored copies of this regex:
|
|
6
|
+
*
|
|
7
|
+
* 1. `dispatch_r2_reviewer.extractAcceptanceCriteria` was case-sensitive, so
|
|
8
|
+
* `## Acceptance criteria` extracted nothing (zcs-close R2 review, 2026-08-09).
|
|
9
|
+
* 2. The same function was end-anchored, so a heading carrying a qualifier —
|
|
10
|
+
* `## Acceptance criteria (per phase, on promotion to ready)` — extracted
|
|
11
|
+
* nothing (measured 2026-08-18).
|
|
12
|
+
* 3. `lint_plan_risk_register.extractFeatures` carried BOTH faults after the
|
|
13
|
+
* other two were fixed, so the substantial-change heuristic hashed the empty
|
|
14
|
+
* string for eight of the thirty-two ready roadmaps — including the roadmap
|
|
15
|
+
* that repaired it. Any edit to those roadmaps' criteria was invisible to the
|
|
16
|
+
* gate, which is the silent half of the failure: the gate still exited 0.
|
|
17
|
+
*
|
|
18
|
+
* Two copies drift. Three copies drift twice. The predicate is a shared constant
|
|
19
|
+
* so the next author cannot fix one call site and leave another blind.
|
|
20
|
+
*
|
|
21
|
+
* `\b` after `criteria` is the deliberate near-miss guard: `## Acceptance
|
|
22
|
+
* criteriaXYZ` is a different heading and must not match. It is asserted from
|
|
23
|
+
* both directions in `tests/scripts/lint_plan_risk_register.test.ts`.
|
|
24
|
+
*/
|
|
25
|
+
export const AC_HEADING_RE = /^##\s+acceptance criteria\b/i;
|
|
26
|
+
|
|
27
|
+
/** True when `line` is a roadmap's acceptance-criteria section heading. */
|
|
28
|
+
export function isAcceptanceCriteriaHeading(line: string): boolean {
|
|
29
|
+
return AC_HEADING_RE.test(line);
|
|
30
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ADR frontmatter reader — coverage.
|
|
3
|
+
*
|
|
4
|
+
* The load-bearing test is `corpus equivalence`: the shared reader is run over
|
|
5
|
+
* every real ADR alongside a verbatim copy of each parser it replaces, and the
|
|
6
|
+
* scalar views must match key-for-key. A refactor of a parser three call sites
|
|
7
|
+
* depend on is only safe if it is provably the same function on the input that
|
|
8
|
+
* exists, and a hand-written fixture cannot establish that — the corpus can.
|
|
9
|
+
*
|
|
10
|
+
* The nested cases then cover what the old parsers could NOT do, and the
|
|
11
|
+
* negative cases pin the discriminations that make the new behaviour a
|
|
12
|
+
* decision rather than an accident.
|
|
13
|
+
*/
|
|
14
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
import { describe, expect, it } from 'vitest';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
evidenceOf,
|
|
22
|
+
isLowEvidenceAccepted,
|
|
23
|
+
provenanceOf,
|
|
24
|
+
readAdrFrontmatter,
|
|
25
|
+
readAdrFrontmatterScalars,
|
|
26
|
+
} from './adr_frontmatter.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Keys the shared reader parses as NESTED, where equivalence with the legacy
|
|
30
|
+
* parsers was never the promise.
|
|
31
|
+
*
|
|
32
|
+
* The legacy fold produced a meaningless string for these — ADR-239's evidence
|
|
33
|
+
* block came back as `"strength: E3 discovery: complete basis: - url1 - url2"`,
|
|
34
|
+
* a value nothing read and nothing could use. The equivalence contract is about
|
|
35
|
+
* SCALAR keys, which is where the three parsers actually diverged in ways a
|
|
36
|
+
* caller could observe. Naming the exception here rather than filtering it
|
|
37
|
+
* silently, and asserting below that the legacy value really was garbage — a
|
|
38
|
+
* skip nobody checks is how a real divergence hides behind an intended one.
|
|
39
|
+
*/
|
|
40
|
+
const NESTED_KEYS = ['provenance', 'evidence'];
|
|
41
|
+
|
|
42
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
const REPO_ROOT = path.resolve(HERE, '..', '..', '..');
|
|
44
|
+
const DECISIONS = path.join(REPO_ROOT, 'docs', 'decisions');
|
|
45
|
+
|
|
46
|
+
/** Verbatim copy of the parser `check_adr_frontmatter.ts` carried. */
|
|
47
|
+
function legacyStrictParser(text: string): Record<string, string> | null {
|
|
48
|
+
if (!text.startsWith('---\n')) return null;
|
|
49
|
+
const end = text.indexOf('\n---\n', 4);
|
|
50
|
+
if (end === -1) return null;
|
|
51
|
+
const out: Record<string, string> = {};
|
|
52
|
+
let key: string | null = null;
|
|
53
|
+
for (const raw of text.slice(4, end).split('\n')) {
|
|
54
|
+
const line = raw.replace(/\s+$/, '');
|
|
55
|
+
if (!line || line.trimStart().startsWith('#')) continue;
|
|
56
|
+
if (/^\s/.test(line) && key) {
|
|
57
|
+
out[key] = `${out[key]} ${line.trim()}`.trim();
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const idx = line.indexOf(':');
|
|
61
|
+
if (idx === -1) continue;
|
|
62
|
+
key = line.slice(0, idx).trim();
|
|
63
|
+
out[key] = line
|
|
64
|
+
.slice(idx + 1)
|
|
65
|
+
.trim()
|
|
66
|
+
.replace(/^["'](.*)["']$/, '$1');
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Verbatim copy of the parser `adr_cite_check.ts` carried (adds the `>-` strip). */
|
|
72
|
+
function legacyCiteParser(text: string): Record<string, string> | null {
|
|
73
|
+
if (!text.startsWith('---\n')) return null;
|
|
74
|
+
const end = text.indexOf('\n---\n', 4);
|
|
75
|
+
if (end === -1) return null;
|
|
76
|
+
const out: Record<string, string> = {};
|
|
77
|
+
let key: string | null = null;
|
|
78
|
+
for (const raw of text.slice(4, end).split('\n')) {
|
|
79
|
+
const line = raw.replace(/\s+$/, '');
|
|
80
|
+
if (!line || line.trimStart().startsWith('#')) continue;
|
|
81
|
+
if (/^\s/.test(line) && key !== null) {
|
|
82
|
+
out[key] = `${out[key]} ${line.trim()}`.trim();
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const idx = line.indexOf(':');
|
|
86
|
+
if (idx === -1) continue;
|
|
87
|
+
key = line.slice(0, idx).trim();
|
|
88
|
+
out[key] = line
|
|
89
|
+
.slice(idx + 1)
|
|
90
|
+
.trim()
|
|
91
|
+
.replace(/^["'](.*)["']$/, '$1')
|
|
92
|
+
.replace(/^>-?$/, '');
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function adrFiles(): string[] {
|
|
98
|
+
return readdirSync(DECISIONS)
|
|
99
|
+
.filter((name) => /^ADR-.*\.md$/.test(name))
|
|
100
|
+
.sort()
|
|
101
|
+
.map((name) => path.join(DECISIONS, name));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe('corpus equivalence with the parsers it replaces', () => {
|
|
105
|
+
const files = adrFiles();
|
|
106
|
+
|
|
107
|
+
it('finds the corpus (guards against a silently empty sweep)', () => {
|
|
108
|
+
expect(files.length).toBeGreaterThan(150);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('matches the cite parser key-for-key on every real ADR', () => {
|
|
112
|
+
const divergent: string[] = [];
|
|
113
|
+
for (const file of files) {
|
|
114
|
+
const text = readFileSync(file, 'utf8');
|
|
115
|
+
const legacy = legacyCiteParser(text);
|
|
116
|
+
const shared = readAdrFrontmatterScalars(text);
|
|
117
|
+
if (legacy === null || shared === null) {
|
|
118
|
+
if (legacy !== shared) divergent.push(`${path.basename(file)}: null-ness differs`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
for (const [key, value] of Object.entries(legacy)) {
|
|
122
|
+
if (NESTED_KEYS.includes(key)) continue;
|
|
123
|
+
if (shared[key] !== value) {
|
|
124
|
+
divergent.push(`${path.basename(file)}#${key}: ${JSON.stringify(value)} vs ${JSON.stringify(shared[key])}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
expect(divergent).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('the legacy value for a nested key really was garbage — so the exception is earned', () => {
|
|
132
|
+
// If a legacy parser ever produced something usable for a nested key,
|
|
133
|
+
// excluding it above would be hiding a real divergence. Assert the
|
|
134
|
+
// premise instead of trusting it.
|
|
135
|
+
let checked = 0;
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
const text = readFileSync(file, 'utf8');
|
|
138
|
+
const legacy = legacyCiteParser(text);
|
|
139
|
+
if (legacy === null) continue;
|
|
140
|
+
for (const key of NESTED_KEYS) {
|
|
141
|
+
const value = legacy[key];
|
|
142
|
+
if (value === undefined) continue;
|
|
143
|
+
checked += 1;
|
|
144
|
+
// A usable scalar would not carry an inner `key: value` pair or
|
|
145
|
+
// a bare list marker. The legacy fold always does.
|
|
146
|
+
expect(value).toMatch(/(\w+:\s)|(^|\s)- /);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// At least one record must exercise this, or the assertion is vacuous.
|
|
150
|
+
expect(checked).toBeGreaterThan(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('matches the strict parser on every real ADR, modulo the documented `>-` strip', () => {
|
|
154
|
+
const divergent: string[] = [];
|
|
155
|
+
for (const file of files) {
|
|
156
|
+
const text = readFileSync(file, 'utf8');
|
|
157
|
+
const legacy = legacyStrictParser(text);
|
|
158
|
+
const shared = readAdrFrontmatterScalars(text);
|
|
159
|
+
if (legacy === null || shared === null) continue;
|
|
160
|
+
for (const [key, value] of Object.entries(legacy)) {
|
|
161
|
+
// The strict parser kept the fold marker and then folded the
|
|
162
|
+
// block onto it (`>- Reopen when …`); the cite parser stripped
|
|
163
|
+
// it, and the shared reader keeps the cite behaviour. That is
|
|
164
|
+
// the one intended divergence, so it is normalised here and
|
|
165
|
+
// named rather than smoothed away.
|
|
166
|
+
if (NESTED_KEYS.includes(key)) continue;
|
|
167
|
+
const normalised = value.replace(/^\|[-+]?\s*|^>[-+]?\s*/, '');
|
|
168
|
+
if (shared[key] !== normalised) {
|
|
169
|
+
divergent.push(`${path.basename(file)}#${key}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
expect(divergent).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('scalar behaviour preserved', () => {
|
|
178
|
+
it('reads plain scalars and strips quotes', () => {
|
|
179
|
+
const fm = readAdrFrontmatter('---\nadr: 238\nstatus: "accepted"\n---\nbody\n');
|
|
180
|
+
expect(fm?.scalars.adr).toBe('238');
|
|
181
|
+
expect(fm?.scalars.status).toBe('accepted');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('folds a >- block into one scalar', () => {
|
|
185
|
+
const fm = readAdrFrontmatter(
|
|
186
|
+
'---\nreview_trigger: >-\n Reopen when the standard is withdrawn\n or the contract is retired.\n---\n',
|
|
187
|
+
);
|
|
188
|
+
expect(fm?.scalars.review_trigger).toBe('Reopen when the standard is withdrawn or the contract is retired.');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('folds an unmarked indented continuation, as the old readers did', () => {
|
|
192
|
+
const fm = readAdrFrontmatter('---\ndecision: a-long\n slug-continued\n---\n');
|
|
193
|
+
expect(fm?.scalars.decision).toBe('a-long slug-continued');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('returns null when there is no frontmatter at all', () => {
|
|
197
|
+
expect(readAdrFrontmatter('# ADR 0001 — per-area\n\n> Area: `router`\n')).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('skips comment lines', () => {
|
|
201
|
+
const fm = readAdrFrontmatter('---\n# a comment\nadr: 1\n---\n');
|
|
202
|
+
expect(fm?.scalars.adr).toBe('1');
|
|
203
|
+
expect(fm?.keyOrder).toEqual(['adr']);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('nested axes — what three parsers could not read', () => {
|
|
208
|
+
const doc = [
|
|
209
|
+
'---',
|
|
210
|
+
'adr: 238',
|
|
211
|
+
'status: accepted',
|
|
212
|
+
'provenance:',
|
|
213
|
+
' kind: agentic',
|
|
214
|
+
' decision_makers: [claude-sonnet-4-5, gpt-4o]',
|
|
215
|
+
' human_directed: false',
|
|
216
|
+
' agentic_mode: council',
|
|
217
|
+
'evidence:',
|
|
218
|
+
' strength: E1',
|
|
219
|
+
' discovery: incomplete',
|
|
220
|
+
' basis:',
|
|
221
|
+
' - docs/CLAIMS.md#code-graph-retrieval-null',
|
|
222
|
+
' - ADR-229:52',
|
|
223
|
+
'authority_basis: evidence',
|
|
224
|
+
'---',
|
|
225
|
+
'body',
|
|
226
|
+
'',
|
|
227
|
+
].join('\n');
|
|
228
|
+
|
|
229
|
+
it('parses a nested map', () => {
|
|
230
|
+
const p = provenanceOf(readAdrFrontmatter(doc)!);
|
|
231
|
+
expect(p).toEqual({
|
|
232
|
+
kind: 'agentic',
|
|
233
|
+
decisionMakers: ['claude-sonnet-4-5', 'gpt-4o'],
|
|
234
|
+
humanDirected: 'false',
|
|
235
|
+
agenticMode: 'council',
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('parses an inline list and a block list in the same record', () => {
|
|
240
|
+
const e = evidenceOf(readAdrFrontmatter(doc)!);
|
|
241
|
+
expect(e?.strength).toBe('E1');
|
|
242
|
+
expect(e?.discovery).toBe('incomplete');
|
|
243
|
+
expect(e?.basis).toEqual(['docs/CLAIMS.md#code-graph-retrieval-null', 'ADR-229:52']);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('keeps nested keys out of the scalar map', () => {
|
|
247
|
+
const fm = readAdrFrontmatter(doc)!;
|
|
248
|
+
expect(fm.scalars.provenance).toBeUndefined();
|
|
249
|
+
expect(fm.scalars.evidence).toBeUndefined();
|
|
250
|
+
expect(fm.scalars.status).toBe('accepted');
|
|
251
|
+
expect(fm.scalars.authority_basis).toBe('evidence');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('round-trips a nested record through the scalar view without losing the key', () => {
|
|
255
|
+
// The index reader only knows scalars; an absent key and an empty key
|
|
256
|
+
// print differently there, so the folded form has to be non-empty.
|
|
257
|
+
const flat = readAdrFrontmatterScalars(doc)!;
|
|
258
|
+
expect(flat.provenance).toContain('kind: agentic');
|
|
259
|
+
expect(flat.evidence).toContain('strength: E1');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('reads an empty inline list as an empty list, not as absent', () => {
|
|
263
|
+
const fm = readAdrFrontmatter('---\nevidence:\n strength: E0\n basis: []\n---\n')!;
|
|
264
|
+
expect(evidenceOf(fm)?.basis).toEqual([]);
|
|
265
|
+
expect(evidenceOf(fm)?.strength).toBe('E0');
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('negative cases — the discriminations are decisions, not accidents', () => {
|
|
270
|
+
it('does not turn a folded scalar into a map even when its prose contains a colon', () => {
|
|
271
|
+
const fm = readAdrFrontmatter(
|
|
272
|
+
'---\nreview_trigger: >-\n Reopen when: the standard is withdrawn.\n---\n',
|
|
273
|
+
)!;
|
|
274
|
+
expect(fm.nested.review_trigger).toBeUndefined();
|
|
275
|
+
expect(fm.scalars.review_trigger).toBe('Reopen when: the standard is withdrawn.');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('reports a missing axis as null rather than inventing a default', () => {
|
|
279
|
+
const fm = readAdrFrontmatter('---\nadr: 1\nstatus: accepted\n---\n')!;
|
|
280
|
+
expect(provenanceOf(fm)).toBeNull();
|
|
281
|
+
expect(evidenceOf(fm)).toBeNull();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('reports a malformed axis as null rather than half-reading it', () => {
|
|
285
|
+
const fm = readAdrFrontmatter('---\nprovenance: agentic\n---\n')!;
|
|
286
|
+
// A scalar where a map belongs: the accessor refuses it, and the raw
|
|
287
|
+
// value stays visible in `scalars` so a linter can report the shape.
|
|
288
|
+
expect(provenanceOf(fm)).toBeNull();
|
|
289
|
+
expect(fm.scalars.provenance).toBe('agentic');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('drops an orphan indented line instead of attaching it to nothing', () => {
|
|
293
|
+
const fm = readAdrFrontmatter('---\n orphan: value\nadr: 1\n---\n')!;
|
|
294
|
+
expect(fm.scalars.orphan).toBeUndefined();
|
|
295
|
+
expect(fm.scalars.adr).toBe('1');
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('low-evidence notice — provenance-independent by design', () => {
|
|
300
|
+
function build(status: string, kind: string, strength: string): string {
|
|
301
|
+
return [
|
|
302
|
+
'---',
|
|
303
|
+
`status: ${status}`,
|
|
304
|
+
'provenance:',
|
|
305
|
+
` kind: ${kind}`,
|
|
306
|
+
'evidence:',
|
|
307
|
+
` strength: ${strength}`,
|
|
308
|
+
' discovery: incomplete',
|
|
309
|
+
'---',
|
|
310
|
+
'',
|
|
311
|
+
].join('\n');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
it('fires on an accepted agentic E0 record', () => {
|
|
315
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('accepted', 'agentic', 'E0'))!)).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('fires on an accepted agentic E1 record', () => {
|
|
319
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('accepted', 'agentic', 'E1'))!)).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// The regression this rename exists for. The predicate used to require
|
|
323
|
+
// `kind === 'agentic'`, so an identical human E0 record printed nothing —
|
|
324
|
+
// while the notice is a statement about EVIDENCE strength, which a human
|
|
325
|
+
// snapshot has exactly as little of.
|
|
326
|
+
it('fires on an accepted HUMAN E0 record — the notice is about evidence, not authorship', () => {
|
|
327
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('accepted', 'human', 'E0'))!)).toBe(true);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('fires on mixed and unknown provenance too', () => {
|
|
331
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('accepted', 'mixed', 'E1'))!)).toBe(true);
|
|
332
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('accepted', 'unknown', 'E0'))!)).toBe(true);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('does NOT fire on E2 — the grade boundary is the whole point', () => {
|
|
336
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('accepted', 'agentic', 'E2'))!)).toBe(false);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('does NOT fire on E3 or E4', () => {
|
|
340
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('accepted', 'agentic', 'E3'))!)).toBe(false);
|
|
341
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('accepted', 'human', 'E4'))!)).toBe(false);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('does NOT fire on a superseded record', () => {
|
|
345
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(build('superseded', 'agentic', 'E0'))!)).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('does NOT fire when the axes are absent — silence is not a weak grade', () => {
|
|
349
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter('---\nstatus: accepted\n---\n')!)).toBe(false);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// The exemption, and the reason provenance was the wrong gate: an owner
|
|
353
|
+
// purpose statement is legitimately E0 and its alternatives ARE foreclosed —
|
|
354
|
+
// by ownership, not by evidence. `authority_basis` is the declared field for
|
|
355
|
+
// that, so it is the field that decides.
|
|
356
|
+
it('does NOT fire on an owner_intent record, at E0', () => {
|
|
357
|
+
const doc = '---\nstatus: accepted\nauthority_basis: owner_intent\nevidence:\n strength: E0\n discovery: complete\n---\n';
|
|
358
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(doc)!)).toBe(false);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('DOES fire on a thin human record that carries no owner claim', () => {
|
|
362
|
+
const doc = '---\nstatus: accepted\nprovenance:\n kind: human\nevidence:\n strength: E0\n discovery: incomplete\n---\n';
|
|
363
|
+
expect(isLowEvidenceAccepted(readAdrFrontmatter(doc)!)).toBe(true);
|
|
364
|
+
});
|
|
365
|
+
});
|