@1aboveio/skills 1.0.8 → 1.0.10
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/.codex-plugin/plugin.json +13 -0
- package/.codex-plugin/resources.json +1827 -0
- package/README.md +22 -0
- package/package.json +9 -2
- package/skills/app-dev/airflow/SKILL.md +1 -1
- package/skills/app-dev/airflow/release-check.mjs +3 -1
- package/skills/app-dev/android-foundation/SKILL.md +1 -1
- package/skills/app-dev/android-foundation/release-check.mjs +3 -1
- package/skills/app-dev/app-debug/SKILL.md +1 -1
- package/skills/app-dev/app-debug/release-check.mjs +3 -1
- package/skills/app-dev/better-auth/SKILL.md +1 -1
- package/skills/app-dev/better-auth/release-check.mjs +3 -1
- package/skills/app-dev/data-catalog/SKILL.md +1 -1
- package/skills/app-dev/data-catalog/release-check.mjs +3 -1
- package/skills/app-dev/data-quality/SKILL.md +1 -1
- package/skills/app-dev/data-quality/release-check.mjs +3 -1
- package/skills/app-dev/ios-foundation/SKILL.md +1 -1
- package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
- package/skills/app-dev/monorepo/SKILL.md +1 -1
- package/skills/app-dev/monorepo/release-check.mjs +3 -1
- package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
- package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
- package/skills/app-dev/prisma-setup/SKILL.md +1 -1
- package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
- package/skills/app-dev/pyspark/SKILL.md +1 -1
- package/skills/app-dev/pyspark/release-check.mjs +3 -1
- package/skills/app-dev/python-backend/SKILL.md +1 -1
- package/skills/app-dev/python-backend/release-check.mjs +3 -1
- package/skills/app-dev/shadcn/SKILL.md +1 -1
- package/skills/app-dev/shadcn/release-check.mjs +3 -1
- package/skills/app-dev/zod-v4/SKILL.md +1 -1
- package/skills/app-dev/zod-v4/release-check.mjs +3 -1
- package/skills/cicd/cloud-build/SKILL.md +1 -1
- package/skills/cicd/cloud-build/release-check.mjs +3 -1
- package/skills/cicd/cloud-debug/SKILL.md +1 -1
- package/skills/cicd/cloud-debug/release-check.mjs +3 -1
- package/skills/cicd/cloud-deploy/SKILL.md +1 -1
- package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
- package/skills/cicd/docker/SKILL.md +1 -1
- package/skills/cicd/docker/release-check.mjs +3 -1
- package/skills/cicd/github-actions/SKILL.md +1 -1
- package/skills/cicd/github-actions/release-check.mjs +3 -1
- package/skills/cicd/github-flow/SKILL.md +1 -1
- package/skills/cicd/github-flow/release-check.mjs +3 -1
- package/skills/cicd/google-cloud/SKILL.md +1 -1
- package/skills/cicd/google-cloud/release-check.mjs +3 -1
- package/skills/cicd/mergify/SKILL.md +1 -1
- package/skills/cicd/mergify/release-check.mjs +3 -1
- package/skills/cicd/podman/SKILL.md +1 -1
- package/skills/cicd/podman/release-check.mjs +3 -1
- package/skills/content/article-review/SKILL.md +1 -1
- package/skills/content/article-review/release-check.mjs +3 -1
- package/skills/content/article-to-image/SKILL.md +1 -1
- package/skills/content/article-to-image/release-check.mjs +3 -1
- package/skills/content/flowchart-svg/SKILL.md +1 -1
- package/skills/content/flowchart-svg/release-check.mjs +3 -1
- package/skills/content/image-gen/SKILL.md +1 -1
- package/skills/content/image-gen/release-check.mjs +3 -1
- package/skills/content/mermaid/SKILL.md +1 -1
- package/skills/content/mermaid/release-check.mjs +3 -1
- package/skills/content/wechat-article-writer/SKILL.md +1 -1
- package/skills/content/wechat-article-writer/release-check.mjs +3 -1
- package/skills/content/wechat-publisher/SKILL.md +1 -1
- package/skills/content/wechat-publisher/release-check.mjs +3 -1
- package/skills/content/wechat-search/SKILL.md +1 -1
- package/skills/content/wechat-search/release-check.mjs +3 -1
- package/skills/content/xai-search/SKILL.md +88 -13
- package/skills/content/xai-search/release-check.mjs +3 -1
- package/skills/content/xai-search/scripts/xai_search.sh +125 -149
- package/skills/core/distill-writing/SKILL.md +1 -1
- package/skills/core/distill-writing/release-check.mjs +3 -1
- package/skills/core/doc-to-md/SKILL.md +1 -1
- package/skills/core/doc-to-md/release-check.mjs +3 -1
- package/skills/core/editorial-review/SKILL.md +1 -1
- package/skills/core/editorial-review/release-check.mjs +3 -1
- package/skills/core/house-style/SKILL.md +1 -1
- package/skills/core/house-style/release-check.mjs +3 -1
- package/skills/core/plain-language/SKILL.md +1 -1
- package/skills/core/plain-language/release-check.mjs +3 -1
- package/skills/core/polish-cn/SKILL.md +1 -1
- package/skills/core/polish-cn/release-check.mjs +3 -1
- package/skills/core/reg-parser/SKILL.md +1 -1
- package/skills/core/reg-parser/release-check.mjs +3 -1
- package/skills/core/terminology/SKILL.md +1 -1
- package/skills/core/terminology/release-check.mjs +3 -1
- package/skills/core/write-doc/SKILL.md +1 -1
- package/skills/core/write-doc/release-check.mjs +3 -1
- package/skills/engineering/better-review/SKILL.md +1 -1
- package/skills/engineering/better-review/release-check.mjs +3 -1
- package/skills/engineering/contributing/SKILL.md +1 -1
- package/skills/engineering/contributing/release-check.mjs +3 -1
- package/skills/engineering/e2e-test/SKILL.md +1 -1
- package/skills/engineering/e2e-test/release-check.mjs +3 -1
- package/skills/engineering/engineering-runtime/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
- package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
- package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
- package/skills/engineering/ensure-coverage/SKILL.md +1 -1
- package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
- package/skills/engineering/golden-path/SKILL.md +1 -1
- package/skills/engineering/golden-path/release-check.mjs +3 -1
- package/skills/engineering/harness-runtime/SKILL.md +1 -1
- package/skills/engineering/harness-runtime/release-check.mjs +3 -1
- package/skills/engineering/resolve-issues/SKILL.md +1 -1
- package/skills/engineering/resolve-issues/references/preflight.md +7 -0
- package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
- package/skills/engineering/resolve-issues/references/task-plan.md +5 -1
- package/skills/engineering/resolve-issues/release-check.mjs +3 -1
- package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
- package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
- package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
- package/skills/engineering/resolve-release/SKILL.md +1 -1
- package/skills/engineering/resolve-release/release-check.mjs +3 -1
- package/skills/engineering/rush-issues/SKILL.md +1 -1
- package/skills/engineering/rush-issues/release-check.mjs +3 -1
- package/skills/engineering/rush-release/SKILL.md +1 -1
- package/skills/engineering/rush-release/release-check.mjs +3 -1
- package/skills/engineering/test-quality/SKILL.md +1 -1
- package/skills/engineering/test-quality/release-check.mjs +3 -1
- package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
- package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
- package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
- package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
- package/skills/risk-compliance/contract-review/SKILL.md +1 -1
- package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
- package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
- package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
- package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
- package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
- package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
- package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
- package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
- package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
- package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -436
- package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
- package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
- package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
- package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
- package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
- package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
- package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
- package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
- package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
- package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
- package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
- package/skills/content/image-gen/scripts/options.test.mjs +0 -245
- package/skills/content/xai-search/tests/smoke_test.sh +0 -36
- package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
- package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
- package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
- package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
- package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
- package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
- package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
- package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
- package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
- package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
- package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
- package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
- package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
- package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
- package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
- package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
- package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
- package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
- package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
- package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
- package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
- package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
- package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
- package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
- package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
- package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
- package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
- package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
- package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
- package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
- package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
- package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
- package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
- package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
- package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
- package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
- package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
- package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
- package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
- package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
- package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
- package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
- package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
- package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
- package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
- package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
- package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
- package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
- package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
- package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
- package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
- package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
- package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
- package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
- package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
- package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
- package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
- package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
- package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import { auditCorpus, buildAdoptionPlan, classifySmoke, renderAdoptionIssue } from './ci-audit.mjs'
|
|
5
|
-
|
|
6
|
-
const file = (name, text) => ({ name, text })
|
|
7
|
-
|
|
8
|
-
test('blocking golden-path smoke passes', () => {
|
|
9
|
-
const report = auditCorpus([file('.github/workflows/ci.yml', `
|
|
10
|
-
jobs:
|
|
11
|
-
smoke:
|
|
12
|
-
steps:
|
|
13
|
-
- run: node smoke.mjs verdict --manifest smoke.manifest.json --target local --results smoke-results.json
|
|
14
|
-
`)])
|
|
15
|
-
assert.equal(report.ok, true)
|
|
16
|
-
assert.equal(report.smoke.status, 'present')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test('the generated golden-path workflow counts as the smoke gate', () => {
|
|
20
|
-
const report = auditCorpus([file('.github/workflows/golden-paths.yml', `
|
|
21
|
-
jobs:
|
|
22
|
-
golden-paths:
|
|
23
|
-
steps:
|
|
24
|
-
- run: node scripts/golden-paths/golden-path.mjs verdict --profile "$GOLDEN_PATH_PROFILE" --target "$BASE_URL"
|
|
25
|
-
`)])
|
|
26
|
-
assert.equal(report.ok, true)
|
|
27
|
-
assert.equal(report.smoke.status, 'present')
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
test('the former coverage gates are ignored', () => {
|
|
31
|
-
const report = auditCorpus([file('.github/workflows/ci.yml', `
|
|
32
|
-
- run: node coverage-ledger.mjs validate
|
|
33
|
-
- run: node presentation-sweep.mjs
|
|
34
|
-
- run: pnpm playwright test
|
|
35
|
-
- run: node lint-tests.mjs --rules mock-internal-seam
|
|
36
|
-
- run: node check-quarantine-expiry.mjs
|
|
37
|
-
`)])
|
|
38
|
-
assert.equal(report.ok, false)
|
|
39
|
-
assert.equal(report.smoke.status, 'absent')
|
|
40
|
-
assert.deepEqual(Object.keys(report), ['smoke', 'ok', 'scanned'])
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('a weakened smoke job is warn-only and fails by default', () => {
|
|
44
|
-
const report = auditCorpus([file('cloudbuild.yaml', `
|
|
45
|
-
- id: smoke
|
|
46
|
-
allowFailure: true
|
|
47
|
-
args: ['-c', 'node smoke.mjs verdict --manifest smoke.manifest.json --results smoke-results.json']
|
|
48
|
-
`)])
|
|
49
|
-
assert.equal(report.ok, false)
|
|
50
|
-
assert.equal(report.smoke.status, 'warn-only')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('--allow-warn accepts warn-only smoke when explicitly requested', () => {
|
|
54
|
-
const report = auditCorpus([file('ci.yml', 'run: node smoke.mjs verdict --results out.json || true')], { allowWarn: true })
|
|
55
|
-
assert.equal(report.ok, true)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test('comments do not count as smoke wiring or weakening', () => {
|
|
59
|
-
const absent = auditCorpus([file('ci.yml', '# node smoke.mjs verdict --results out.json')])
|
|
60
|
-
assert.equal(absent.smoke.status, 'absent')
|
|
61
|
-
|
|
62
|
-
const corpus = [{ name: 'ci.yml', lines: [
|
|
63
|
-
'# never add continue-on-error: true',
|
|
64
|
-
'run: node smoke.mjs verdict --results out.json',
|
|
65
|
-
] }]
|
|
66
|
-
assert.equal(classifySmoke(corpus).status, 'present')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
test('weakening far from smoke does not bleed into its status', () => {
|
|
70
|
-
const lines = ['continue-on-error: true', ...Array.from({ length: 20 }, (_, index) => `# filler ${index}`), 'run: smoke-gate']
|
|
71
|
-
assert.equal(classifySmoke([{ name: 'ci.yml', lines }]).status, 'present')
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test('adoption output requests only golden-path smoke', () => {
|
|
75
|
-
const report = auditCorpus([file('.github/workflows/ci.yml', 'run: node coverage-ledger.mjs validate')])
|
|
76
|
-
const plan = buildAdoptionPlan(report)
|
|
77
|
-
const body = renderAdoptionIssue(plan)
|
|
78
|
-
assert.equal(plan.needed, true)
|
|
79
|
-
assert.match(body, /Wire golden-path smoke into CI/)
|
|
80
|
-
assert.doesNotMatch(body, /coverage-ledger|presentation sweep|quarantine expiry|all-gates|static-gates/)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('adoption is unnecessary when smoke is blocking', () => {
|
|
84
|
-
const report = auditCorpus([file('ci.yml', 'run: smoke-gate')])
|
|
85
|
-
assert.equal(buildAdoptionPlan(report).needed, false)
|
|
86
|
-
})
|
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
// Tests for the Coverage Ledger reference tool, focused on the Presentation axis
|
|
2
|
-
// spine (issue #195). Run: `node --test` from scripts/, or `npm test`.
|
|
3
|
-
|
|
4
|
-
import { test } from 'node:test'
|
|
5
|
-
import assert from 'node:assert/strict'
|
|
6
|
-
import fs from 'node:fs'
|
|
7
|
-
import os from 'node:os'
|
|
8
|
-
import path from 'node:path'
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
depthStatus, baselineFor, build, PRESENTATION_VIEWPORT_FLOOR,
|
|
12
|
-
isPresentationLevel, presentationEscalation, applyCellExclusions, computeMetrics,
|
|
13
|
-
satisfies, PRESENTATION_LEVELS, PRESENTATION_ROUTE_FLOOR,
|
|
14
|
-
} from './coverage-ledger.mjs'
|
|
15
|
-
|
|
16
|
-
// `presentation:fidelity` is doctrine in references/presentation/axis.md. Doctrine
|
|
17
|
-
// that names a graded token the grader has never heard of is the exact failure this
|
|
18
|
-
// skill family keeps re-learning: the prose reads enforced, the machinery ignores it.
|
|
19
|
-
test('presentation:fidelity is a registered, opt-in, exact-match level', () => {
|
|
20
|
-
assert.ok(PRESENTATION_LEVELS.has('presentation:fidelity'), 'the grader knows the token')
|
|
21
|
-
assert.ok(isPresentationLevel('presentation:fidelity'), 'and grades it real-render like its siblings')
|
|
22
|
-
assert.ok(!PRESENTATION_ROUTE_FLOOR.includes('presentation:fidelity'), 'opt-in: a surface earns it by citing a design source, not by being a route')
|
|
23
|
-
|
|
24
|
-
// Exact-match in both directions. The tempting substitution is `presentation:visual`
|
|
25
|
-
// ("we have a baseline, isn't that the same check?") — it is not: a baseline
|
|
26
|
-
// captured from the built page ratifies whatever shipped, which is the failure
|
|
27
|
-
// fidelity exists to catch.
|
|
28
|
-
assert.ok(satisfies(['presentation:fidelity'], 'presentation:fidelity'))
|
|
29
|
-
assert.ok(!satisfies(['presentation:visual'], 'presentation:fidelity'))
|
|
30
|
-
assert.ok(!satisfies(['presentation:fidelity'], 'presentation:visual'))
|
|
31
|
-
assert.ok(!satisfies(['browser-journey', 'e2e'], 'presentation:fidelity'), 'a functional journey does not prove it')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('presentation:fidelity grades like its siblings: DOM-only is Over-mocked, real-render needs attestation', () => {
|
|
35
|
-
// A JSDOM test cannot read computed style off a laid-out page, so it can only ever
|
|
36
|
-
// restate the manifest back to itself — the axis's over-mock.
|
|
37
|
-
const domOnly = depthStatus(['presentation:fidelity'], [{ levels: ['presentation:fidelity'], realBrowser: false, intercepts: false }])
|
|
38
|
-
assert.equal(domOnly.status, 'Over-mocked')
|
|
39
|
-
assert.match(domOnly.note, /real rendered-page evidence/)
|
|
40
|
-
|
|
41
|
-
// Real browser, self-declared → Unverified until the test is marked read, the
|
|
42
|
-
// same promotion path every presentation level takes.
|
|
43
|
-
const t = [{ levels: ['presentation:fidelity'], realBrowser: true, intercepts: false }]
|
|
44
|
-
assert.equal(depthStatus(['presentation:fidelity'], t).status, 'Unverified')
|
|
45
|
-
assert.equal(depthStatus(['presentation:fidelity'], t, { attested: true }).status, 'Covered')
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
// A synthetic scanned-test entry, matching what scanTests() produces.
|
|
49
|
-
function t(levels, { intercepts = false, realBrowser = false, domOnly = false } = {}) {
|
|
50
|
-
return { file: 'x.spec.ts', levels: new Set(levels), intercepts, realBrowser, domOnly }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ---------- baseline: every browser route auto-owes presentation:spatial ----------
|
|
54
|
-
|
|
55
|
-
test('browser routes carry the presentation floor for every class', () => {
|
|
56
|
-
for (const cls of ['read', 'detail', 'mutation']) {
|
|
57
|
-
const floor = baselineFor('route', cls)
|
|
58
|
-
assert.ok(floor.includes('browser-journey'), `${cls} keeps functional floor`)
|
|
59
|
-
assert.ok(floor.includes('presentation:render-health'), `${cls} gains render-health spine`)
|
|
60
|
-
assert.ok(floor.includes('presentation:spatial'), `${cls} gains spatial floor`)
|
|
61
|
-
assert.ok(floor.includes('presentation:perceivability'), `${cls} gains perceivability floor`)
|
|
62
|
-
assert.ok(floor.includes('presentation:reachability'), `${cls} gains reachability floor`)
|
|
63
|
-
assert.ok(floor.includes('presentation:temporal'), `${cls} gains temporal floor`)
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('render-health is the spine (first presentation level on the floor)', () => {
|
|
68
|
-
const floor = baselineFor('route', 'read')
|
|
69
|
-
const firstPresentation = floor.find((l) => l.startsWith('presentation:'))
|
|
70
|
-
assert.equal(firstPresentation, 'presentation:render-health')
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test('render-health grades like a presentation level (real render required)', () => {
|
|
74
|
-
const RH = ['presentation:render-health']
|
|
75
|
-
// a real-browser, non-intercepting test, marked read (verified) → Covered
|
|
76
|
-
assert.equal(depthStatus(RH, [t(['presentation:render-health'], { realBrowser: true })], { attested: true }).status, 'Covered')
|
|
77
|
-
// DOM-only cannot observe a pageerror on real mount → Over-mocked
|
|
78
|
-
assert.equal(depthStatus(RH, [t(['presentation:render-health'], { realBrowser: false, domOnly: true })], { attested: true }).status, 'Over-mocked')
|
|
79
|
-
// a spec that intercepts first-party traffic does not prove the real page renders
|
|
80
|
-
assert.equal(depthStatus(RH, [t(['presentation:render-health'], { realBrowser: true, intercepts: true })], { attested: true }).status, 'Over-mocked')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('render-health breadth floor unifies the old `smoke` token (back-compat alias)', () => {
|
|
84
|
-
// the renamed token satisfies the floor
|
|
85
|
-
assert.notEqual(depthStatus(['render-health'], [t(['render-health'], { realBrowser: true })], { attested: true }).status, 'Missing')
|
|
86
|
-
// a legacy `@level smoke` tag still satisfies a render-health floor (alias)
|
|
87
|
-
assert.notEqual(depthStatus(['render-health'], [t(['smoke'], { realBrowser: true })], { attested: true }).status, 'Missing')
|
|
88
|
-
// and a render-health tag satisfies a legacy `smoke` floor (reverse alias)
|
|
89
|
-
assert.notEqual(depthStatus(['smoke'], [t(['render-health'], { realBrowser: true })], { attested: true }).status, 'Missing')
|
|
90
|
-
// a fuller browser journey also satisfies render-health
|
|
91
|
-
assert.notEqual(depthStatus(['render-health'], [t(['browser-journey'], { realBrowser: true })], { attested: true }).status, 'Missing')
|
|
92
|
-
// no test → Missing (the floor still bites)
|
|
93
|
-
assert.equal(depthStatus(['render-health'], [], { attested: true }).status, 'Missing')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
// ---------- over-mock rate metric (depth-axis honesty) ----------
|
|
97
|
-
|
|
98
|
-
// A synthetic index row, matching what build() stores per surface.
|
|
99
|
-
function row(depthStatus, depthNote = '') {
|
|
100
|
-
return { surface: 's', depthStatus, depthNote }
|
|
101
|
-
}
|
|
102
|
-
const idx = (rows) => new Map(rows.map((r, i) => [`s${i}`, r]))
|
|
103
|
-
|
|
104
|
-
test('over-mock rate = over-mocked / tested surfaces, behaviour vs presentation split', () => {
|
|
105
|
-
const m = computeMetrics(idx([
|
|
106
|
-
row('Covered'),
|
|
107
|
-
row('Over-mocked', 'behavior level met only by internal interception: api'),
|
|
108
|
-
row('Over-mocked', 'presentation level proven without real rendered-page evidence: presentation:spatial'),
|
|
109
|
-
row('Wrong Level'),
|
|
110
|
-
]))
|
|
111
|
-
assert.equal(m.tested, 4) // all four have tests (none Missing)
|
|
112
|
-
assert.equal(m.overMocked, 2)
|
|
113
|
-
assert.equal(m.overMockedBehavior, 1)
|
|
114
|
-
assert.equal(m.overMockedPresentation, 1)
|
|
115
|
-
assert.equal(m.overMockRate, 0.5)
|
|
116
|
-
assert.equal(m.overMockRatePct, 50)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test('Missing surfaces are absent, not over-mocked — excluded from the denominator', () => {
|
|
120
|
-
// 1 over-mocked, 1 covered (tested=2); two Missing surfaces don't dilute the rate.
|
|
121
|
-
const m = computeMetrics(idx([row('Over-mocked', 'behavior level: x'), row('Covered'), row('Missing'), row('Missing')]))
|
|
122
|
-
assert.equal(m.tested, 2)
|
|
123
|
-
assert.equal(m.overMockRate, 0.5)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
test('reachability/excluded/null states are not depth-graded — never counted', () => {
|
|
127
|
-
const m = computeMetrics(idx([row('Reached'), row('Dead'), row('Excluded'), row(null), row('Covered')]))
|
|
128
|
-
assert.equal(m.depthGraded, 1) // only the Covered row owes a depth check
|
|
129
|
-
assert.equal(m.tested, 1)
|
|
130
|
-
assert.equal(m.overMocked, 0)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
test('rate is 0 (never NaN) when nothing is tested', () => {
|
|
134
|
-
const m = computeMetrics(idx([row('Missing'), row('Excluded')]))
|
|
135
|
-
assert.equal(m.tested, 0)
|
|
136
|
-
assert.equal(m.overMockRate, 0)
|
|
137
|
-
assert.equal(m.overMockRatePct, 0)
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
test('non-browser surfaces do NOT carry a presentation floor', () => {
|
|
141
|
-
assert.ok(!baselineFor('api', 'read').includes('presentation:spatial'))
|
|
142
|
-
assert.ok(!baselineFor('api', 'mutation').includes('presentation:spatial'))
|
|
143
|
-
assert.deepEqual(baselineFor('table'), [])
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
test('viewport floor is the documented 375 + 1280 pair', () => {
|
|
147
|
-
assert.deepEqual(PRESENTATION_VIEWPORT_FLOOR, [375, 1280])
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
// ---------- depth grading of presentation:spatial ----------
|
|
151
|
-
|
|
152
|
-
const SPATIAL = ['presentation:spatial']
|
|
153
|
-
|
|
154
|
-
test('real-rendered + marked read (verified) → Covered', () => {
|
|
155
|
-
const ds = depthStatus(SPATIAL, [t(['presentation:spatial'], { realBrowser: true })], { attested: true })
|
|
156
|
-
assert.equal(ds.status, 'Covered')
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
test('real-rendered but not marked read → Unverified', () => {
|
|
160
|
-
const ds = depthStatus(SPATIAL, [t(['presentation:spatial'], { realBrowser: true })], { attested: false })
|
|
161
|
-
assert.equal(ds.status, 'Unverified')
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
test('DOM-only / JSDOM presentation test → Over-mocked (not Covered)', () => {
|
|
165
|
-
const ds = depthStatus(SPATIAL, [t(['presentation:spatial'], { realBrowser: false, domOnly: true })], { attested: true })
|
|
166
|
-
assert.equal(ds.status, 'Over-mocked')
|
|
167
|
-
assert.match(ds.note, /real rendered-page evidence/)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
test('presentation test that intercepts first-party traffic → Over-mocked', () => {
|
|
171
|
-
const ds = depthStatus(SPATIAL, [t(['presentation:spatial'], { realBrowser: true, intercepts: true })], { attested: true })
|
|
172
|
-
assert.equal(ds.status, 'Over-mocked')
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
test('a functional browser-journey does NOT satisfy presentation:spatial', () => {
|
|
176
|
-
// journey drives the DOM; it never asserts the page is laid out without overflow.
|
|
177
|
-
const ds = depthStatus(SPATIAL, [t(['browser-journey'], { realBrowser: true })], { attested: true })
|
|
178
|
-
assert.equal(ds.status, 'Wrong Level')
|
|
179
|
-
assert.match(ds.note, /presentation:spatial/)
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
test('no test at all → Missing', () => {
|
|
183
|
-
const ds = depthStatus(SPATIAL, [], { attested: true })
|
|
184
|
-
assert.equal(ds.status, 'Missing')
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
// ---------- perceivability behaves like a presentation level ----------
|
|
188
|
-
|
|
189
|
-
const PERCEIVE = ['presentation:perceivability']
|
|
190
|
-
|
|
191
|
-
test('perceivability: real-rendered + attested → Covered', () => {
|
|
192
|
-
const ds = depthStatus(PERCEIVE, [t(['presentation:perceivability'], { realBrowser: true })], { attested: true })
|
|
193
|
-
assert.equal(ds.status, 'Covered')
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
test('perceivability: DOM-only contrast check → Over-mocked', () => {
|
|
197
|
-
const ds = depthStatus(PERCEIVE, [t(['presentation:perceivability'], { domOnly: true })], { attested: true })
|
|
198
|
-
assert.equal(ds.status, 'Over-mocked')
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
test('perceivability: a spatial test does not satisfy perceivability', () => {
|
|
202
|
-
const ds = depthStatus(PERCEIVE, [t(['presentation:spatial'], { realBrowser: true })], { attested: true })
|
|
203
|
-
assert.equal(ds.status, 'Wrong Level')
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
// ---------- reachability behaves like a presentation level ----------
|
|
207
|
-
|
|
208
|
-
const REACH = ['presentation:reachability']
|
|
209
|
-
|
|
210
|
-
test('reachability: real-rendered + attested → Covered', () => {
|
|
211
|
-
const ds = depthStatus(REACH, [t(['presentation:reachability'], { realBrowser: true })], { attested: true })
|
|
212
|
-
assert.equal(ds.status, 'Covered')
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
test('reachability: DOM-only (no real crawl/render) → Over-mocked', () => {
|
|
216
|
-
const ds = depthStatus(REACH, [t(['presentation:reachability'], { domOnly: true })], { attested: true })
|
|
217
|
-
assert.equal(ds.status, 'Over-mocked')
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
// ---------- temporal behaves like a presentation level ----------
|
|
221
|
-
|
|
222
|
-
const TEMPORAL = ['presentation:temporal']
|
|
223
|
-
|
|
224
|
-
test('temporal: real-rendered + attested → Covered', () => {
|
|
225
|
-
const ds = depthStatus(TEMPORAL, [t(['presentation:temporal'], { realBrowser: true })], { attested: true })
|
|
226
|
-
assert.equal(ds.status, 'Covered')
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
test('temporal: real-rendered but NOT marked read → Unverified (the third-party-lane state)', () => {
|
|
230
|
-
const ds = depthStatus(TEMPORAL, [t(['presentation:temporal'], { realBrowser: true })], { attested: false })
|
|
231
|
-
assert.equal(ds.status, 'Unverified')
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
test('temporal: DOM-only → Over-mocked', () => {
|
|
235
|
-
const ds = depthStatus(TEMPORAL, [t(['presentation:temporal'], { domOnly: true })], { attested: true })
|
|
236
|
-
assert.equal(ds.status, 'Over-mocked')
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
// ---------- feature-derived escalation engine (#200) ----------
|
|
240
|
-
|
|
241
|
-
test('isPresentationLevel: prefix-based, covers cell-qualified levels', () => {
|
|
242
|
-
assert.equal(isPresentationLevel('presentation:spatial'), true)
|
|
243
|
-
assert.equal(isPresentationLevel('presentation:perceivability@dark'), true)
|
|
244
|
-
assert.equal(isPresentationLevel('browser-journey'), false)
|
|
245
|
-
assert.equal(isPresentationLevel(undefined), false)
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
test('presentationEscalation: theme toggle → dark perceivability cell', () => {
|
|
249
|
-
assert.deepEqual(presentationEscalation({ themeToggle: true }), ['presentation:perceivability@dark'])
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
test('presentationEscalation: i18n → long-locale + RTL spatial cells', () => {
|
|
253
|
-
assert.deepEqual(presentationEscalation({ i18n: true }), ['presentation:spatial@long-locale', 'presentation:spatial@rtl'])
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
test('presentationEscalation: deterministic (sorted, deduped) and empty for no signals', () => {
|
|
257
|
-
assert.deepEqual(presentationEscalation({}), [])
|
|
258
|
-
// same signals → identical output regardless of key order
|
|
259
|
-
const a = presentationEscalation({ themeToggle: true, i18n: true })
|
|
260
|
-
const b = presentationEscalation({ i18n: true, themeToggle: true })
|
|
261
|
-
assert.deepEqual(a, b)
|
|
262
|
-
assert.deepEqual(a, ['presentation:perceivability@dark', 'presentation:spatial@long-locale', 'presentation:spatial@rtl'])
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
test('applyCellExclusions: gated removal of an escalated cell yields a census', () => {
|
|
266
|
-
const levels = ['presentation:spatial', 'presentation:perceivability@dark']
|
|
267
|
-
const r = applyCellExclusions(levels, { 'presentation:perceivability@dark': { category: 'deprecated', reason: 'dark mode being removed' } })
|
|
268
|
-
assert.deepEqual(r.kept, ['presentation:spatial'])
|
|
269
|
-
assert.equal(r.census.length, 1)
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
test('applyCellExclusions: cannot exclude a base (unqualified) floor level', () => {
|
|
273
|
-
const r = applyCellExclusions(['presentation:spatial'], { 'presentation:spatial': { category: 'x', reason: 'y' } })
|
|
274
|
-
assert.deepEqual(r.kept, ['presentation:spatial']) // kept despite the entry
|
|
275
|
-
assert.equal(r.rejected.length, 1)
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
test('applyCellExclusions: escalated cell without category/reason is rejected (kept)', () => {
|
|
279
|
-
const levels = ['presentation:perceivability@dark']
|
|
280
|
-
const r = applyCellExclusions(levels, { 'presentation:perceivability@dark': { category: 'deprecated' } })
|
|
281
|
-
assert.deepEqual(r.kept, ['presentation:perceivability@dark'])
|
|
282
|
-
assert.equal(r.rejected.length, 1)
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
test('build(): a route with a themeToggle signal gains the dark-mode obligation', async () => {
|
|
286
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'covesc-'))
|
|
287
|
-
try {
|
|
288
|
-
const gen = path.join(dir, 'docs/tests/_generated')
|
|
289
|
-
fs.mkdirSync(gen, { recursive: true })
|
|
290
|
-
fs.writeFileSync(path.join(gen, 'surface-inventory.json'), JSON.stringify({
|
|
291
|
-
surfaces: [{ id: '/app', kind: 'route', class: 'read', reachability: 'reachable', signals: { themeToggle: true } }],
|
|
292
|
-
}))
|
|
293
|
-
const cfg = {
|
|
294
|
-
inventory: path.join(gen, 'surface-inventory.json'),
|
|
295
|
-
coverageDir: path.join(dir, 'docs/tests'),
|
|
296
|
-
testRoots: [dir], requirementGlobs: {}, acceptedMarker: /x/, unverifiedBlocks: true,
|
|
297
|
-
}
|
|
298
|
-
const { index } = await build(cfg)
|
|
299
|
-
const row = index.get('/app')
|
|
300
|
-
assert.ok(row.requiredLevels.includes('presentation:perceivability@dark'), 'dark cell auto-added from signal')
|
|
301
|
-
assert.ok(row.requiredLevels.includes('presentation:perceivability'), 'base floor still present')
|
|
302
|
-
} finally {
|
|
303
|
-
fs.rmSync(dir, { recursive: true, force: true })
|
|
304
|
-
}
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
// ---------- end-to-end build() against a temp fixture ----------
|
|
308
|
-
|
|
309
|
-
test('build() grades a real browser route end-to-end', async () => {
|
|
310
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'covledger-'))
|
|
311
|
-
try {
|
|
312
|
-
const gen = path.join(dir, 'docs/tests/_generated')
|
|
313
|
-
fs.mkdirSync(gen, { recursive: true })
|
|
314
|
-
fs.writeFileSync(path.join(gen, 'surface-inventory.json'), JSON.stringify({
|
|
315
|
-
surfaces: [{ id: '/dashboard', kind: 'route', class: 'read', reachability: 'reachable' }],
|
|
316
|
-
}))
|
|
317
|
-
// A real-browser presentation test for the route.
|
|
318
|
-
const e2e = path.join(dir, 'e2e')
|
|
319
|
-
fs.mkdirSync(e2e, { recursive: true })
|
|
320
|
-
// Satisfies ALL route floors: the functional browser-journey plus the full
|
|
321
|
-
// presentation floor (render-health spine + spatial, perceivability,
|
|
322
|
-
// reachability, temporal), in one real-browser spec.
|
|
323
|
-
fs.writeFileSync(path.join(e2e, 'dashboard.spec.ts'), [
|
|
324
|
-
'// @covers /dashboard',
|
|
325
|
-
'// @level browser-journey',
|
|
326
|
-
'// @level presentation:render-health',
|
|
327
|
-
'// @level presentation:spatial',
|
|
328
|
-
'// @level presentation:perceivability',
|
|
329
|
-
'// @level presentation:reachability',
|
|
330
|
-
'// @level presentation:temporal',
|
|
331
|
-
"import { test, expect } from '@playwright/test'",
|
|
332
|
-
"test('renders, stable, perceivable, reachable', async ({ page }) => { await page.goto('/dashboard') })",
|
|
333
|
-
].join('\n'))
|
|
334
|
-
|
|
335
|
-
const cfg = {
|
|
336
|
-
inventory: path.join(gen, 'surface-inventory.json'),
|
|
337
|
-
coverageDir: path.join(dir, 'docs/tests'),
|
|
338
|
-
testRoots: [dir],
|
|
339
|
-
requirementGlobs: {},
|
|
340
|
-
acceptedMarker: /status:\s*accepted/i,
|
|
341
|
-
unverifiedBlocks: true,
|
|
342
|
-
}
|
|
343
|
-
const { index } = await build(cfg)
|
|
344
|
-
const row = index.get('/dashboard')
|
|
345
|
-
assert.ok(row, 'route is in the index')
|
|
346
|
-
assert.ok(row.requiredLevels.includes('presentation:spatial'), 'presentation floor applied')
|
|
347
|
-
// real-rendered but not yet marked read → Unverified
|
|
348
|
-
assert.equal(row.depthStatus, 'Unverified')
|
|
349
|
-
} finally {
|
|
350
|
-
fs.rmSync(dir, { recursive: true, force: true })
|
|
351
|
-
}
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
test('build(): Unverified is a warning unless the user opts into unverifiedBlocks', async () => {
|
|
355
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'covunv-'))
|
|
356
|
-
try {
|
|
357
|
-
const gen = path.join(dir, 'docs/tests/_generated')
|
|
358
|
-
fs.mkdirSync(gen, { recursive: true })
|
|
359
|
-
fs.writeFileSync(path.join(gen, 'surface-inventory.json'), JSON.stringify({
|
|
360
|
-
surfaces: [{ id: '/dashboard', kind: 'route', class: 'read', reachability: 'reachable' }],
|
|
361
|
-
}))
|
|
362
|
-
fs.mkdirSync(path.join(dir, 'e2e'), { recursive: true })
|
|
363
|
-
fs.writeFileSync(path.join(dir, 'e2e', 'dashboard.spec.ts'), [
|
|
364
|
-
'// @covers /dashboard',
|
|
365
|
-
'// @level browser-journey',
|
|
366
|
-
'// @level presentation:render-health',
|
|
367
|
-
'// @level presentation:spatial',
|
|
368
|
-
'// @level presentation:perceivability',
|
|
369
|
-
'// @level presentation:reachability',
|
|
370
|
-
'// @level presentation:temporal',
|
|
371
|
-
"import { test } from '@playwright/test'",
|
|
372
|
-
"test('dashboard', async ({ page }) => { await page.goto('/dashboard') })",
|
|
373
|
-
].join('\n'))
|
|
374
|
-
const base = {
|
|
375
|
-
inventory: path.join(gen, 'surface-inventory.json'),
|
|
376
|
-
coverageDir: path.join(dir, 'docs/tests'),
|
|
377
|
-
testRoots: [dir], requirementGlobs: {}, acceptedMarker: /x/,
|
|
378
|
-
}
|
|
379
|
-
const severityOf = async (cfg) => (await build(cfg)).findings
|
|
380
|
-
.filter((f) => f.kind === 'Unverified' && f.surface === '/dashboard').map((f) => f.severity)
|
|
381
|
-
assert.deepEqual(await severityOf(base), ['warn'])
|
|
382
|
-
assert.deepEqual(await severityOf({ ...base, unverifiedBlocks: true }), ['error'])
|
|
383
|
-
} finally {
|
|
384
|
-
fs.rmSync(dir, { recursive: true, force: true })
|
|
385
|
-
}
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
// ---------- acceptance gate (requirementGlobs) ----------
|
|
389
|
-
|
|
390
|
-
test('build(): the acceptance gate holds every accepted ADR to a criteria doc or a tracked gap', async () => {
|
|
391
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'covadr-'))
|
|
392
|
-
try {
|
|
393
|
-
const gen = path.join(dir, 'docs/tests/_generated')
|
|
394
|
-
const adr = path.join(dir, 'docs/adr')
|
|
395
|
-
const cov = path.join(dir, 'docs/tests/adr')
|
|
396
|
-
fs.mkdirSync(gen, { recursive: true }); fs.mkdirSync(adr, { recursive: true }); fs.mkdirSync(cov, { recursive: true })
|
|
397
|
-
fs.writeFileSync(path.join(gen, 'surface-inventory.json'), JSON.stringify({ surfaces: [] }))
|
|
398
|
-
fs.writeFileSync(path.join(dir, 'docs/tests/0008-criteria.md'), '# criteria')
|
|
399
|
-
// 0008: accepted, coverage file links criteria → clean
|
|
400
|
-
fs.writeFileSync(path.join(adr, '0008-allowlist.md'), '# ADR 8\nstatus: accepted\n')
|
|
401
|
-
fs.writeFileSync(path.join(cov, '0008-allowlist.coverage.json'), JSON.stringify({ criteria: path.join(dir, 'docs/tests/0008-criteria.md'), surfaces: {} }))
|
|
402
|
-
// 0009: accepted (bold markdown status form), coverage file with neither criteria nor gap → error
|
|
403
|
-
fs.writeFileSync(path.join(adr, '0009-dispatch.md'), '# ADR 9\n**Status:** accepted\n')
|
|
404
|
-
fs.writeFileSync(path.join(cov, '0009-dispatch.coverage.json'), JSON.stringify({ surfaces: {} }))
|
|
405
|
-
// 0010: accepted, no coverage file at all → error
|
|
406
|
-
fs.writeFileSync(path.join(adr, '0010-billing.md'), '# ADR 10\nStatus: Accepted\n')
|
|
407
|
-
// 0011: accepted but declares no runtime behavior → exempt
|
|
408
|
-
fs.writeFileSync(path.join(adr, '0011-terminology.md'), '# ADR 11\nstatus: accepted\n\nThis ADR has no runtime behavior.\n')
|
|
409
|
-
// 0012: accepted, coverage file names a tracked gap instead of criteria → clean
|
|
410
|
-
fs.writeFileSync(path.join(adr, '0012-export.md'), '# ADR 12\nstatus: accepted\n')
|
|
411
|
-
fs.writeFileSync(path.join(cov, '0012-export.coverage.json'), JSON.stringify({ gapIssue: '#412', surfaces: {} }))
|
|
412
|
-
// 0013: draft → ignored
|
|
413
|
-
fs.writeFileSync(path.join(adr, '0013-draft.md'), '# ADR 13\nstatus: proposed\n')
|
|
414
|
-
|
|
415
|
-
const cfg = {
|
|
416
|
-
inventory: path.join(gen, 'surface-inventory.json'),
|
|
417
|
-
coverageDir: path.join(dir, 'docs/tests'),
|
|
418
|
-
testRoots: [dir],
|
|
419
|
-
requirementGlobs: { adr },
|
|
420
|
-
acceptedMarker: /\bstatus\b[\s*:_-]{0,6}accepted/i,
|
|
421
|
-
unverifiedBlocks: true,
|
|
422
|
-
}
|
|
423
|
-
const { findings } = await build(cfg)
|
|
424
|
-
const gate = findings.filter((f) => f.kind === 'Gate').map((f) => f.surface).sort()
|
|
425
|
-
assert.deepEqual(gate, ['adr:0009', 'adr:0010'], `only the placeholder and the missing file fail the gate, got ${JSON.stringify(findings.filter((f) => f.kind === 'Gate'))}`)
|
|
426
|
-
assert.ok(!findings.some((f) => f.kind === 'Gate skipped'), 'the gate ran')
|
|
427
|
-
} finally {
|
|
428
|
-
fs.rmSync(dir, { recursive: true, force: true })
|
|
429
|
-
}
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
test('build(): a requirementGlobs directory that does not exist is a gate error, not a silent skip', async () => {
|
|
433
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'covadr-'))
|
|
434
|
-
try {
|
|
435
|
-
const gen = path.join(dir, 'docs/tests/_generated')
|
|
436
|
-
fs.mkdirSync(gen, { recursive: true })
|
|
437
|
-
fs.writeFileSync(path.join(gen, 'surface-inventory.json'), JSON.stringify({ surfaces: [] }))
|
|
438
|
-
const cfg = {
|
|
439
|
-
inventory: path.join(gen, 'surface-inventory.json'), coverageDir: path.join(dir, 'docs/tests'), testRoots: [dir],
|
|
440
|
-
requirementGlobs: { adr: path.join(dir, 'docs/adr') }, acceptedMarker: /status:\s*accepted/i, unverifiedBlocks: true,
|
|
441
|
-
}
|
|
442
|
-
const { findings } = await build(cfg)
|
|
443
|
-
assert.ok(findings.some((f) => f.kind === 'Gate' && f.surface === 'adr:*'), 'names the missing directory')
|
|
444
|
-
} finally {
|
|
445
|
-
fs.rmSync(dir, { recursive: true, force: true })
|
|
446
|
-
}
|
|
447
|
-
})
|