@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,458 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { spawnSync } from 'node:child_process'
|
|
4
|
-
import { mkdtempSync, writeFileSync } from 'node:fs'
|
|
5
|
-
import { tmpdir } from 'node:os'
|
|
6
|
-
import { dirname, join } from 'node:path'
|
|
7
|
-
import { fileURLToPath } from 'node:url'
|
|
8
|
-
|
|
9
|
-
import { sizeCheck, longestChain, SizeCheckError, ExplorationUnfinished, PACK_LINES } from './size-check.mjs'
|
|
10
|
-
|
|
11
|
-
const script = join(dirname(fileURLToPath(import.meta.url)), 'size-check.mjs')
|
|
12
|
-
|
|
13
|
-
const unit = (id, dependsOn = []) => ({ id, dependsOn })
|
|
14
|
-
// One behavior, nothing missing, cheap proof: a clean slice.
|
|
15
|
-
const slice = (overrides = {}) => ({ behaviors: ['the cashier sees the total'], prerequisites: [], layers: ['android'], ...overrides })
|
|
16
|
-
const proof = (overrides = {}) => ({ level: 'unit', physical: [], ...overrides })
|
|
17
|
-
const SKILL = '/home/dev/.claude/skills/android-foundation/SKILL.md'
|
|
18
|
-
const suite = (status, overrides = {}) => ({ what: 'emulator journey suite', status, evidence: status === 'present' ? 'scripts/journey.py; green in CI' : 'no suite in scripts/', ...(status === 'present' ? { check: 'python3 scripts/journey.py --check', local: 'python3 scripts/journey.py' } : {}), skill: SKILL, ...overrides })
|
|
19
|
-
const emulator = (overrides = {}) => proof({ level: 'emulator', suite: suite('present'), ...overrides })
|
|
20
|
-
const entry = (lines, sliceOverrides, proofValue = proof()) => ({ estimatedLines: lines, slice: slice(sliceOverrides), proof: proofValue })
|
|
21
|
-
const estimates = (ids, lines) => ({ version: 2, units: Object.fromEntries(ids.map((id) => [id, entry(lines)])) })
|
|
22
|
-
const withUnit = (findings, id, value) => ({ ...findings, units: { ...findings.units, [id]: value } })
|
|
23
|
-
const FOUR = ['preview the total', 'charge once', 'recover the attempt', 'send a receipt']
|
|
24
|
-
|
|
25
|
-
test('a small plan of clean slices is ok', () => {
|
|
26
|
-
const plan = { units: [unit('a'), unit('b', ['a']), unit('c')] }
|
|
27
|
-
const result = sizeCheck(plan, estimates(['a', 'b', 'c'], 800))
|
|
28
|
-
assert.equal(result.verdict, 'ok')
|
|
29
|
-
assert.deepEqual(result.units.map((u) => [u.id, u.lines, u.verdict]), [['a', 800, 'ok'], ['b', 800, 'ok'], ['c', 800, 'ok']])
|
|
30
|
-
assert.deepEqual(result.runnable, ['a', 'b', 'c'])
|
|
31
|
-
assert.deepEqual(result.held, [])
|
|
32
|
-
assert.deepEqual(result.longestChain, { length: 2, end: 'b' })
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('a one-unit run is checked like any other', () => {
|
|
36
|
-
assert.equal(sizeCheck({ units: [unit('a')] }, estimates(['a'], 100)).verdict, 'ok')
|
|
37
|
-
const big = sizeCheck({ units: [unit('a')] }, { version: 2, units: { a: entry(100, { behaviors: FOUR }) } })
|
|
38
|
-
assert.equal(big.verdict, 'stop', 'nothing is left to run, so the run stops')
|
|
39
|
-
assert.deepEqual(big.runnable, [])
|
|
40
|
-
assert.equal(big.held[0].id, 'a')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('any number of runnable units is one run: no waves, no unit-count warning', () => {
|
|
44
|
-
// The dependency order decides what starts when; a count never splits the run.
|
|
45
|
-
const ids = Array.from({ length: 27 }, (_, i) => `u${i}`)
|
|
46
|
-
const units = ids.map((id, i) => unit(id, i > 0 && i % 5 === 0 ? [ids[i - 1]] : []))
|
|
47
|
-
const result = sizeCheck({ units }, estimates(ids, 100))
|
|
48
|
-
assert.equal(result.verdict, 'ok')
|
|
49
|
-
assert.deepEqual(result.runnable, ids)
|
|
50
|
-
assert.equal(result.waves, undefined)
|
|
51
|
-
assert.ok(!result.reasons.some((r) => /wave/.test(r.message)))
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('a chain of 6 or more warns but never stops the run', () => {
|
|
55
|
-
const chain = (n) => ({ units: Array.from({ length: n }, (_, i) => unit(`u${i}`, i ? [`u${i - 1}`] : [])) })
|
|
56
|
-
const ids = (n) => Array.from({ length: n }, (_, i) => `u${i}`)
|
|
57
|
-
assert.equal(sizeCheck(chain(5), estimates(ids(5), 100)).verdict, 'ok')
|
|
58
|
-
const six = sizeCheck(chain(6), estimates(ids(6), 100))
|
|
59
|
-
assert.equal(six.verdict, 'warn')
|
|
60
|
-
assert.equal(six.reasons[0].measure, 'chainUnits')
|
|
61
|
-
assert.equal(six.reasons[0].level, 'warn')
|
|
62
|
-
assert.deepEqual(six.longestChain, { length: 6, end: 'u5' })
|
|
63
|
-
assert.deepEqual(six.runnable, ids(6))
|
|
64
|
-
// A long chain with a held link: the links before it still run.
|
|
65
|
-
const held = sizeCheck(chain(8), { version: 2, units: Object.fromEntries(ids(8).map((id) => [id, id === 'u3' ? entry(100, { behaviors: FOUR }) : entry(100)])) })
|
|
66
|
-
assert.equal(held.verdict, 'hold')
|
|
67
|
-
assert.deepEqual(held.runnable, ['u0', 'u1', 'u2'])
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test('estimated lines are scaled by 1.3 before the limits: ok to 2500, warn above, hold above 5000', () => {
|
|
71
|
-
const plan = { units: [unit('a'), unit('b')] }
|
|
72
|
-
assert.equal(sizeCheck(plan, estimates(['a', 'b'], 1923)).verdict, 'ok', '1923 x 1.3 = 2500')
|
|
73
|
-
assert.equal(sizeCheck(plan, estimates(['a', 'b'], 1925)).verdict, 'warn', '1925 x 1.3 = 2503')
|
|
74
|
-
const hold = sizeCheck(plan, withUnit(estimates(['a', 'b'], 10), 'a', entry(3847)))
|
|
75
|
-
assert.equal(hold.verdict, 'hold')
|
|
76
|
-
assert.match(hold.held[0].reasons[0], /unit a is sized at 5001 changed lines \(estimate 3847 x 1\.3\).*sub-issues/)
|
|
77
|
-
assert.equal(hold.held[0].proposedSplit, null, 'a line overrun alone has no behavior split to propose')
|
|
78
|
-
assert.deepEqual(hold.runnable, ['b'])
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
test('two or three behaviors warn; four or more hold and propose the fewest slices of at most three', () => {
|
|
82
|
-
const plan = { units: [unit('a'), unit('b')] }
|
|
83
|
-
const check = (behaviors) => sizeCheck(plan, withUnit(estimates(['a', 'b'], 100), 'a', entry(100, { behaviors })))
|
|
84
|
-
assert.equal(check(FOUR.slice(0, 1)).verdict, 'ok')
|
|
85
|
-
for (const n of [2, 3]) {
|
|
86
|
-
const result = check(FOUR.slice(0, n))
|
|
87
|
-
assert.equal(result.verdict, 'warn', `${n} behaviors`)
|
|
88
|
-
assert.deepEqual(result.runnable, ['a', 'b'])
|
|
89
|
-
}
|
|
90
|
-
const result = check(FOUR)
|
|
91
|
-
assert.equal(result.verdict, 'hold')
|
|
92
|
-
assert.equal(result.reasons[0].measure, 'behaviors')
|
|
93
|
-
assert.equal(result.reasons[0].unit, 'a')
|
|
94
|
-
assert.deepEqual(result.held[0].proposedSplit, [
|
|
95
|
-
{ kind: 'behavior', title: 'preview the total; charge once', blockedBy: [] },
|
|
96
|
-
{ kind: 'behavior', title: 'recover the attempt; send a receipt', blockedBy: [] },
|
|
97
|
-
], 'two even slices, never one issue per behavior')
|
|
98
|
-
assert.deepEqual(result.runnable, ['b'])
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
test('proof: a missing suite warns and stays in the unit; it never holds the unit or becomes a prefactor', () => {
|
|
102
|
-
const missing = emulator({ suite: suite('missing', { what: 'emulator journey suite for Charge' }) })
|
|
103
|
-
const result = sizeCheck({ units: [unit('a')] }, { version: 2, units: { a: entry(100, {}, missing) } })
|
|
104
|
-
assert.equal(result.verdict, 'warn')
|
|
105
|
-
assert.deepEqual(result.runnable, ['a'])
|
|
106
|
-
assert.equal(result.reasons[0].measure, 'suite')
|
|
107
|
-
assert.equal(result.reasons[0].consideration, 'proof')
|
|
108
|
-
assert.match(result.reasons[0].message, /build the emulator journey it needs inside the unit, counted in its lines/)
|
|
109
|
-
assert.deepEqual(result.environmentChecks, [], 'a missing suite has nothing to check')
|
|
110
|
-
// Held for size, the unit's split still has no suite prefactor for every slice to wait on.
|
|
111
|
-
const big = sizeCheck({ units: [unit('a'), unit('b')] }, withUnit(estimates(['a', 'b'], 10), 'a', entry(6000, { behaviors: ['import', 'export'] }, missing)))
|
|
112
|
-
assert.deepEqual(big.held[0].proposedSplit.map((s) => [s.kind, s.blockedBy]), [['behavior', []], ['behavior', []]])
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
test('proof: one environment check per distinct suite, only for runnable units', () => {
|
|
116
|
-
const plan = { units: [unit('a'), unit('b'), unit('c'), unit('d')] }
|
|
117
|
-
const findings = {
|
|
118
|
-
version: 2,
|
|
119
|
-
units: {
|
|
120
|
-
a: entry(100, {}, emulator()),
|
|
121
|
-
b: entry(100, {}, emulator()),
|
|
122
|
-
c: entry(100, {}, proof({ level: 'browser', suite: suite('present', { what: 'Playwright', check: 'pnpm exec playwright --version' }) })),
|
|
123
|
-
d: entry(100, { behaviors: FOUR }, emulator()),
|
|
124
|
-
},
|
|
125
|
-
}
|
|
126
|
-
const result = sizeCheck(plan, findings)
|
|
127
|
-
assert.deepEqual(result.environmentChecks, [
|
|
128
|
-
{ check: 'python3 scripts/journey.py --check', suite: 'emulator journey suite', units: ['a', 'b'] },
|
|
129
|
-
{ check: 'pnpm exec playwright --version', suite: 'Playwright', units: ['c'] },
|
|
130
|
-
])
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
test('proof: real-device evidence becomes one pending release-gate record and a handback, never a hold', () => {
|
|
134
|
-
const findings = { version: 2, units: { a: entry(100, {}, emulator({ physical: ['six outcomes on a real phone and tablet'] })) } }
|
|
135
|
-
const result = sizeCheck({ units: [unit('a')] }, findings)
|
|
136
|
-
assert.equal(result.verdict, 'warn')
|
|
137
|
-
assert.equal(result.reasons[0].measure, 'physical')
|
|
138
|
-
assert.deepEqual(result.verification, [{ title: 'Physical-device verification at the release gate', units: ['a'], evidence: ['#a: six outcomes on a real phone and tablet'] }])
|
|
139
|
-
assert.deepEqual(result.handback, [{ what: 'Physical-device verification at the release gate (pending): #a: six outcomes on a real phone and tablet', unit: null }])
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
test('a separate missing prerequisite only warns; in a size split the first slice builds it', () => {
|
|
143
|
-
const missing = { what: 'backend decides Money, Tax and Tip', status: 'missing', separate: true, evidence: 'no authority in backend/services' }
|
|
144
|
-
const present = { what: 'exact Sale correlation', status: 'present', evidence: 'backend/services/sale-attempt-correlation.js:40' }
|
|
145
|
-
const small = sizeCheck({ units: [unit('a')] }, { version: 2, units: { a: entry(100, { prerequisites: [present, missing] }) } })
|
|
146
|
-
assert.equal(small.verdict, 'warn')
|
|
147
|
-
assert.deepEqual(small.held, [])
|
|
148
|
-
assert.match(small.reasons[0].message, /backend decides Money, Tax and Tip \(no authority in backend\/services\); build it inside the unit/)
|
|
149
|
-
assert.deepEqual(small.units[0].missingPrerequisites, ['backend decides Money, Tax and Tip'])
|
|
150
|
-
const big = sizeCheck({ units: [unit('a'), unit('b')] }, withUnit(estimates(['a', 'b'], 10), 'a', entry(6000, { behaviors: ['preview', 'charge'], prerequisites: [missing] })))
|
|
151
|
-
assert.deepEqual(big.held[0].proposedSplit, [
|
|
152
|
-
{ kind: 'behavior', title: 'preview', blockedBy: [], builds: ['backend decides Money, Tax and Tip'] },
|
|
153
|
-
{ kind: 'behavior', title: 'charge', blockedBy: ['preview'] },
|
|
154
|
-
], 'no prefactor of its own: the first slice starts at once')
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
test('a missing prerequisite that fits inside the issue only warns', () => {
|
|
158
|
-
const inside = { what: 'a format helper for the receipt total', status: 'missing', separate: false, evidence: 'no helper in android/src' }
|
|
159
|
-
const result = sizeCheck({ units: [unit('a')] }, { version: 2, units: { a: entry(100, { prerequisites: [inside] }) } })
|
|
160
|
-
assert.equal(result.verdict, 'warn')
|
|
161
|
-
assert.match(result.reasons[0].message, /must also build a format helper.*build it inside the unit/)
|
|
162
|
-
assert.deepEqual(result.held, [])
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
test('proof: emulator proof across three or more layers warns but does not hold', () => {
|
|
166
|
-
const plan = { units: [unit('a')] }
|
|
167
|
-
const two = sizeCheck(plan, { version: 2, units: { a: entry(100, { layers: ['backend', 'android'] }, emulator()) } })
|
|
168
|
-
assert.equal(two.verdict, 'ok')
|
|
169
|
-
const three = sizeCheck(plan, { version: 2, units: { a: entry(100, { layers: ['backend', 'android', 'shared'] }, emulator()) } })
|
|
170
|
-
assert.equal(three.verdict, 'warn')
|
|
171
|
-
assert.equal(three.reasons[0].measure, 'emulatorLayers')
|
|
172
|
-
const cheap = sizeCheck(plan, { version: 2, units: { a: entry(100, { layers: ['backend', 'android', 'shared'] }, proof({ level: 'database' })) } })
|
|
173
|
-
assert.equal(cheap.verdict, 'ok')
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
test('units that depend on a held unit wait with it; shared surfaces do not', () => {
|
|
177
|
-
const plan = { units: [unit('a'), unit('b', ['a']), unit('c', ['b']), unit('d'), unit('e')] }
|
|
178
|
-
plan.units[3].stacksOn = ['a']
|
|
179
|
-
const findings = withUnit(estimates(['a', 'b', 'c', 'd', 'e'], 100), 'a', entry(100, { behaviors: FOUR }))
|
|
180
|
-
const result = sizeCheck(plan, findings)
|
|
181
|
-
assert.equal(result.verdict, 'hold')
|
|
182
|
-
assert.deepEqual(result.held.map((h) => h.id), ['a'])
|
|
183
|
-
assert.deepEqual(result.units.map((u) => [u.id, u.heldBy]), [['a', []], ['b', ['a']], ['c', ['a']], ['d', []], ['e', []]])
|
|
184
|
-
assert.deepEqual(result.runnable, ['d', 'e'])
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
test('a three-behavior emulator unit runs on warnings; its separate prerequisites count in its lines', () => {
|
|
188
|
-
// Three behaviors, two separate prerequisites, emulator proof with no suite yet. It used to be
|
|
189
|
-
// held on four strong signals; now only its size can hold it, and the explorer counts the
|
|
190
|
-
// prerequisites it builds inside the unit.
|
|
191
|
-
const charge = (estimatedLines) => ({
|
|
192
|
-
estimatedLines,
|
|
193
|
-
slice: {
|
|
194
|
-
behaviors: ['preview the authoritative total', 'submit one exact Sale attempt and recover it', 'send an optional receipt'],
|
|
195
|
-
prerequisites: [
|
|
196
|
-
{ what: 'exact Sale correlation (#1129)', status: 'present', evidence: 'backend/services/sale-attempt-correlation.js' },
|
|
197
|
-
{ what: 'Money and Tax helpers shared with Android', status: 'missing', separate: true, evidence: 'only frontend/src/lib/money.ts' },
|
|
198
|
-
{ what: 'backend Store sale authority for Money, Tax and Tip', status: 'missing', separate: true, evidence: 'no service owns it' },
|
|
199
|
-
],
|
|
200
|
-
layers: ['backend', 'android', 'shared'],
|
|
201
|
-
},
|
|
202
|
-
proof: {
|
|
203
|
-
level: 'emulator',
|
|
204
|
-
suite: { what: 'emulator journey suite for native Charge', status: 'missing', evidence: 'no device script in scripts/', skill: SKILL },
|
|
205
|
-
physical: ['all six Charge outcomes on a real phone and tablet'],
|
|
206
|
-
},
|
|
207
|
-
})
|
|
208
|
-
const plan = { units: [unit('1131'), unit('1132')] }
|
|
209
|
-
const fits = sizeCheck(plan, withUnit(estimates(['1131', '1132'], 1550), '1131', charge(1900)))
|
|
210
|
-
assert.equal(fits.verdict, 'warn')
|
|
211
|
-
assert.deepEqual(fits.runnable, ['1131', '1132'])
|
|
212
|
-
assert.deepEqual(fits.reasons.filter((r) => r.unit === '1131').map((r) => `${r.consideration}:${r.level}:${r.measure}`), [
|
|
213
|
-
'scope:warn:behaviors', 'scope:warn:prerequisites', 'scope:warn:prerequisites',
|
|
214
|
-
'proof:warn:suite', 'proof:warn:emulatorLayers', 'proof:warn:physical',
|
|
215
|
-
])
|
|
216
|
-
// Counting both prerequisites inside, the estimate passes the line limit: held, packed into the fewest
|
|
217
|
-
// slices that fit (two and one behaviors), the later one behind the first.
|
|
218
|
-
const counted = sizeCheck(plan, withUnit(estimates(['1131', '1132'], 1550), '1131', charge(4000)))
|
|
219
|
-
assert.equal(counted.verdict, 'hold')
|
|
220
|
-
assert.deepEqual(counted.held[0].proposedSplit.map((s) => [s.kind, s.blockedBy.length, (s.builds ?? []).length]), [['behavior', 0, 2], ['behavior', 1, 0]])
|
|
221
|
-
assert.deepEqual(counted.held[0].proposedSplit[1].blockedBy, ['preview the authoritative total; submit one exact Sale attempt and recover it'])
|
|
222
|
-
assert.deepEqual(counted.held[0].physical, ['all six Charge outcomes on a real phone and tablet'])
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
test('open product, scope or approval questions hold their unit, or stop the run when unassigned', () => {
|
|
226
|
-
const plan = { units: [unit('a'), unit('b')] }
|
|
227
|
-
const base = estimates(['a', 'b'], 10)
|
|
228
|
-
const check = (openQuestions) => sizeCheck(plan, { ...base, openQuestions })
|
|
229
|
-
assert.equal(check([{ kind: 'technical', unit: 'a', text: 'which cursor encoding?' }]).verdict, 'ok')
|
|
230
|
-
assert.equal(check([{ kind: 'approval', text: 'may we rotate the key?', resolved: true }]).verdict, 'ok')
|
|
231
|
-
for (const kind of ['product', 'scope', 'approval']) {
|
|
232
|
-
assert.equal(check([{ kind, text: 'q' }]).verdict, 'stop', kind)
|
|
233
|
-
const held = check([{ kind, unit: 'b', text: 'release go/no-go' }])
|
|
234
|
-
assert.equal(held.verdict, 'hold', kind)
|
|
235
|
-
assert.deepEqual(held.held.map((h) => h.id), ['b'])
|
|
236
|
-
assert.deepEqual(held.runnable, ['a'])
|
|
237
|
-
}
|
|
238
|
-
assert.equal(check([{ kind: 'technical', text: 'unassigned' }]).verdict, 'ok', 'technical questions are notes, never blockers')
|
|
239
|
-
assert.equal(check([{ kind: 'product', unit: 'zz', text: 'unknown unit' }]).verdict, 'stop')
|
|
240
|
-
assert.equal(check([{ text: 'no kind' }]).verdict, 'stop')
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
test('handback items are reported without affecting the verdict', () => {
|
|
244
|
-
const result = sizeCheck({ units: [unit('a')] }, { ...estimates(['a'], 10), handback: [{ what: 'run the migration on staging', unit: 'a' }] })
|
|
245
|
-
assert.equal(result.verdict, 'ok')
|
|
246
|
-
assert.deepEqual(result.handback, [{ what: 'run the migration on staging', unit: 'a' }])
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
test('unfinished exploration is its own error, not a verdict', () => {
|
|
250
|
-
const plan = { units: [unit('a'), unit('b')] }
|
|
251
|
-
const unfinished = (findings, message) => assert.throws(() => sizeCheck(plan, findings), (e) => e instanceof ExplorationUnfinished && message.test(e.message))
|
|
252
|
-
unfinished({ units: {} }, /version is undefined, expected 2/)
|
|
253
|
-
unfinished({ version: 1, units: { a: { entries: [] }, b: { entries: [] } } }, /version is 1/)
|
|
254
|
-
unfinished({ version: 2 }, /no units map/)
|
|
255
|
-
unfinished({ version: 2, units: [] }, /no units map/)
|
|
256
|
-
unfinished(estimates(['a'], 10), /no findings for unit b; continue the explorer/)
|
|
257
|
-
// The explorer's scaffold: an empty entry for every unit.
|
|
258
|
-
unfinished({ version: 2, units: { a: {}, b: {} } }, /unit a: no slice; no proof; unit b: no slice; no proof/)
|
|
259
|
-
// Findings from before the slice record existed.
|
|
260
|
-
unfinished({ version: 2, units: { a: { estimatedLines: 10 }, b: entry(10) } }, /unit a: no slice/)
|
|
261
|
-
unfinished(withUnit(estimates(['a', 'b'], 10), 'b', { estimatedLines: -1, slice: slice(), proof: proof() }), /no estimatedLines for unit b/)
|
|
262
|
-
unfinished(withUnit(estimates(['a', 'b'], 10), 'b', { slice: slice(), proof: proof() }), /no estimatedLines for unit b/)
|
|
263
|
-
unfinished({ ...estimates(['a', 'b'], 10), openQuestions: { kind: 'product' } }, /openQuestions must be a list/)
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
test('a malformed slice is unfinished exploration, named field by field', () => {
|
|
267
|
-
const plan = { units: [unit('a')] }
|
|
268
|
-
const bad = (sliceOverrides, message) => assert.throws(
|
|
269
|
-
() => sizeCheck(plan, { version: 2, units: { a: entry(10, sliceOverrides) } }),
|
|
270
|
-
(e) => e instanceof ExplorationUnfinished && message.test(e.message),
|
|
271
|
-
)
|
|
272
|
-
bad({ behaviors: [] }, /behaviors must list at least one/)
|
|
273
|
-
bad({ behaviors: ['ok', ' '] }, /behaviors must list at least one/)
|
|
274
|
-
bad({ prerequisites: undefined }, /prerequisites must be a list/)
|
|
275
|
-
bad({ prerequisites: [{ what: 'x', status: 'partial', evidence: 'y' }] }, /status \(present or missing\)/)
|
|
276
|
-
bad({ prerequisites: [{ what: 'x', status: 'present' }] }, /needs what, status.*and evidence/)
|
|
277
|
-
bad({ prerequisites: [{ what: 'x', status: 'missing', evidence: 'y' }] }, /missing prerequisite needs separate/)
|
|
278
|
-
bad({ layers: [] }, /layers must list at least one/)
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
test('a malformed proof record is unfinished exploration, named field by field', () => {
|
|
282
|
-
const plan = { units: [unit('a')] }
|
|
283
|
-
const bad = (proofValue, message) => assert.throws(
|
|
284
|
-
() => sizeCheck(plan, { version: 2, units: { a: { estimatedLines: 10, slice: slice(), proof: proofValue } } }),
|
|
285
|
-
(e) => e instanceof ExplorationUnfinished && message.test(e.message),
|
|
286
|
-
)
|
|
287
|
-
bad(undefined, /unit a: no proof/)
|
|
288
|
-
bad(proof({ level: 'device' }), /level must be one of unit, database, browser, emulator \(real devices go in proof.physical\)/)
|
|
289
|
-
bad(proof({ level: 'emulator' }), /emulator proof needs an end-to-end suite/)
|
|
290
|
-
bad(proof({ level: 'browser', suite: { what: 'x', status: 'present', evidence: 'y' } }), /present end-to-end suite needs a check command/)
|
|
291
|
-
bad(proof({ physical: 'a phone' }), /physical must be a list/)
|
|
292
|
-
bad(proof({ level: 'unit', suite: 42 }), /suite is only for browser and emulator proof/)
|
|
293
|
-
bad(emulator({ suite: suite('present', { local: undefined }) }), /needs a local command that runs one journey off CI/)
|
|
294
|
-
bad(emulator({ suite: suite('present', { local: 'gh workflow run android.yml --ref rush/1918 -f richer-smoke=true' }) }), /local must run off CI, not dispatch it/)
|
|
295
|
-
bad(emulator({ suite: suite('present', { local: 'gh run rerun 39273527350 --failed' }) }), /local must run off CI, not dispatch it/)
|
|
296
|
-
bad(emulator({ suite: suite('present', { local: 'gh api -X POST repos/o/r/actions/workflows/android.yml/dispatches -f ref=main' }) }), /local must run off CI, not dispatch it/)
|
|
297
|
-
bad(emulator({ suite: suite('missing', { skill: undefined }) }), /skill must be the absolute SKILL.md path/)
|
|
298
|
-
bad(emulator({ suite: suite('present', { skill: 'android-foundation' }) }), /skill must be the absolute SKILL.md path/)
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
test('proof: a present suite may check through CI when its local command runs off CI; skill may be null', () => {
|
|
302
|
-
const ci = suite('present', { check: 'gh workflow run android.yml --ref <unit-branch> -f richer-smoke=true', local: 'bash scripts/android-foundation-journeys.sh', skill: null })
|
|
303
|
-
const result = sizeCheck({ units: [unit('a')] }, { version: 2, units: { a: entry(100, {}, emulator({ suite: ci })) } })
|
|
304
|
-
assert.equal(result.verdict, 'ok')
|
|
305
|
-
// A dispatch checks nothing on this host: the first implementer runs the local journey instead.
|
|
306
|
-
assert.deepEqual(result.environmentChecks, [{ check: 'bash scripts/android-foundation-journeys.sh', suite: 'emulator journey suite', units: ['a'] }])
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
test('findings for units no longer in the plan are ignored', () => {
|
|
310
|
-
assert.equal(sizeCheck({ units: [unit('a')] }, estimates(['a', 'trimmed'], 10)).verdict, 'ok')
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
test('a broken plan is a plan error, not unfinished exploration', () => {
|
|
314
|
-
const planError = (e) => e instanceof SizeCheckError && !(e instanceof ExplorationUnfinished)
|
|
315
|
-
assert.throws(() => sizeCheck({ units: [] }, {}), planError)
|
|
316
|
-
assert.throws(() => sizeCheck({ units: [unit('a', ['b']), unit('b', ['a'])] }, estimates(['a', 'b'], 10)), planError)
|
|
317
|
-
assert.throws(() => sizeCheck({ units: [{ title: 'no id' }] }, {}), /needs an id/)
|
|
318
|
-
assert.throws(() => sizeCheck({ units: [{ id: 'a', dependsOn: 'b' }] }, {}), (e) => planError(e) && /dependsOn must be a list/.test(e.message))
|
|
319
|
-
assert.throws(() => longestChain([unit('a', ['b']), unit('b', ['a'])]), /cycle/)
|
|
320
|
-
assert.throws(() => longestChain([unit('a', ['zz'])]), /unknown dependency zz/)
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
test('CLI exits 0 with the verdict in the output for ok, hold, stop, and unfinished exploration; 2 on a bad plan', () => {
|
|
324
|
-
const dir = mkdtempSync(join(tmpdir(), 'size-check-'))
|
|
325
|
-
const write = (name, value) => { const p = join(dir, name); writeFileSync(p, JSON.stringify(value)); return p }
|
|
326
|
-
const run = (plan, findings, ...extra) => spawnSync(process.execPath, [script, '--plan', plan, '--findings', findings, ...extra], { encoding: 'utf8' })
|
|
327
|
-
const okPlan = write('ok.json', { units: [unit('a')] })
|
|
328
|
-
const f = write('f.json', estimates(['a'], 10))
|
|
329
|
-
const ok = run(okPlan, f)
|
|
330
|
-
assert.equal(ok.status, 0, ok.stderr)
|
|
331
|
-
assert.equal(JSON.parse(ok.stdout).verdict, 'ok')
|
|
332
|
-
const twoPlan = write('two.json', { units: [unit('a'), unit('b')] })
|
|
333
|
-
const holdFindings = write('h.json', withUnit(estimates(['a', 'b'], 10), 'a', entry(10, { behaviors: FOUR })))
|
|
334
|
-
const hold = run(twoPlan, holdFindings)
|
|
335
|
-
assert.equal(hold.status, 0, hold.stderr)
|
|
336
|
-
assert.equal(JSON.parse(hold.stdout).verdict, 'hold')
|
|
337
|
-
assert.deepEqual(JSON.parse(hold.stdout).runnable, ['b'])
|
|
338
|
-
const stopFindings = write('q.json', { ...estimates(['a'], 10), openQuestions: [{ kind: 'scope', text: 'x' }] })
|
|
339
|
-
const stop = run(okPlan, stopFindings)
|
|
340
|
-
assert.equal(stop.status, 0, stop.stderr)
|
|
341
|
-
assert.equal(JSON.parse(stop.stdout).verdict, 'stop')
|
|
342
|
-
const unfinished = run(twoPlan, f)
|
|
343
|
-
assert.equal(unfinished.status, 0, unfinished.stderr)
|
|
344
|
-
assert.equal(unfinished.stderr, '')
|
|
345
|
-
const answer = JSON.parse(unfinished.stdout)
|
|
346
|
-
assert.equal(answer.verdict, 'exploration-unfinished')
|
|
347
|
-
assert.match(answer.reason, /no findings for unit b/)
|
|
348
|
-
assert.equal(JSON.parse(run(okPlan, join(dir, 'no-findings.json')).stdout).verdict, 'exploration-unfinished')
|
|
349
|
-
// bad plan or command: the script could not answer
|
|
350
|
-
assert.equal(run(join(dir, 'missing.json'), f).status, 2)
|
|
351
|
-
assert.equal(run(write('cycle.json', { units: [unit('a', ['b']), unit('b', ['a'])] }), f).status, 2)
|
|
352
|
-
assert.equal(spawnSync(process.execPath, [script, '--plan', okPlan], { encoding: 'utf8' }).status, 2)
|
|
353
|
-
// --exit-verdict restores the old codes (deprecated)
|
|
354
|
-
assert.equal(run(okPlan, f, '--exit-verdict').status, 0)
|
|
355
|
-
assert.equal(run(twoPlan, holdFindings, '--exit-verdict').status, 4)
|
|
356
|
-
assert.equal(run(okPlan, stopFindings, '--exit-verdict').status, 1)
|
|
357
|
-
const legacyUnfinished = run(twoPlan, f, '--exit-verdict')
|
|
358
|
-
assert.equal(legacyUnfinished.status, 3)
|
|
359
|
-
assert.equal(JSON.parse(legacyUnfinished.stdout).verdict, 'exploration-unfinished')
|
|
360
|
-
assert.equal(run(join(dir, 'missing.json'), f, '--exit-verdict').status, 2)
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
test('a prerequisite named twice in a held unit is built once, by the first slice', () => {
|
|
364
|
-
const name = 'emulator journey suite'
|
|
365
|
-
const findings = { version: 2, units: { a: entry(6000,
|
|
366
|
-
{ behaviors: ['import', 'export'], prerequisites: [{ what: name, status: 'missing', separate: true, evidence: 'none' }, { what: 'Emulator journey suite ', status: 'missing', separate: true, evidence: 'none' }] }) } }
|
|
367
|
-
const split = sizeCheck({ units: [unit('a'), unit('b')] }, withUnit(findings, 'b', entry(10))).held[0].proposedSplit
|
|
368
|
-
assert.deepEqual(split[0].builds, [name])
|
|
369
|
-
assert.deepEqual(split.at(-1).blockedBy, ['import'])
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
test('older findings that call the suite `harness` still check', () => {
|
|
373
|
-
const legacy = { level: 'browser', harness: { what: 'Playwright', status: 'missing', evidence: 'none', skill: null }, physical: [] }
|
|
374
|
-
const result = sizeCheck({ units: [unit('a'), unit('b')] }, withUnit(estimates(['a', 'b'], 10), 'a', { estimatedLines: 10, slice: slice(), proof: legacy }))
|
|
375
|
-
assert.equal(result.reasons.find((r) => r.unit === 'a').measure, 'suite')
|
|
376
|
-
assert.equal(result.units[0].suite, 'missing')
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
test('a lines hold splits along behaviors when there are two or more; otherwise the human decides', () => {
|
|
380
|
-
const plan = { units: [unit('a'), unit('b')] }
|
|
381
|
-
const held = (behaviors) => sizeCheck(plan, withUnit(estimates(['a', 'b'], 10), 'a', entry(6000, { behaviors }))).held[0]
|
|
382
|
-
assert.deepEqual(held(['import', 'export']).proposedSplit.map((s) => s.title), ['import', 'export'])
|
|
383
|
-
assert.equal(held(['import']).proposedSplit, null)
|
|
384
|
-
const question = sizeCheck(plan, { ...estimates(['a', 'b'], 10), openQuestions: [{ kind: 'product', unit: 'a', text: 'q' }] })
|
|
385
|
-
assert.equal(question.held[0].proposedSplit, null)
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
test('the device record is one per run and lists only units that will run', () => {
|
|
389
|
-
const plan = { units: [unit('a'), unit('b', ['a']), unit('c')] }
|
|
390
|
-
const device = emulator({ physical: ['on a real tablet'] })
|
|
391
|
-
const findings = { version: 2, units: { a: entry(100, { behaviors: FOUR }, device), b: entry(100, {}, device), c: entry(100, {}, device) } }
|
|
392
|
-
const result = sizeCheck(plan, findings)
|
|
393
|
-
assert.equal(result.verification.length, 1)
|
|
394
|
-
assert.deepEqual(result.verification[0].units, ['c'])
|
|
395
|
-
assert.equal(result.handback.length, 1)
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
test('a plan treats parents merged by an earlier run as met, not unknown', () => {
|
|
399
|
-
const plan = { merged: ['p'], units: [unit('c', ['p']), unit('d', ['c'])] }
|
|
400
|
-
const result = sizeCheck(plan, estimates(['c', 'd'], 100))
|
|
401
|
-
assert.equal(result.verdict, 'ok')
|
|
402
|
-
assert.deepEqual(result.runnable, ['c', 'd'])
|
|
403
|
-
assert.throws(() => sizeCheck({ units: [unit('c', ['p'])] }, estimates(['c'], 100)), /unknown dependency p/, 'an unmerged missing parent is still an error')
|
|
404
|
-
assert.throws(() => sizeCheck({ merged: 'p', units: [unit('c')] }, estimates(['c'], 100)), SizeCheckError)
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
test("a missing prerequisite that repeats the unit's own behavior is unfinished exploration", () => {
|
|
408
|
-
const findings = { version: 2, units: { a: entry(100, { behaviors: ['Persist the Charge layout'], prerequisites: [{ what: 'persist the charge layout', status: 'missing', separate: false, evidence: 'not in app/' }] }) } }
|
|
409
|
-
assert.throws(() => sizeCheck({ units: [unit('a')] }, findings), (e) => e instanceof ExplorationUnfinished && /own behavior/.test(e.message))
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
// ---- Proof-only units: split along proof groups ----
|
|
413
|
-
|
|
414
|
-
const campaign = (groups, setups = []) => entry(
|
|
415
|
-
groups.reduce((sum, g) => sum + g.estimatedLines, 0) + setups.reduce((sum, su) => sum + su.estimatedLines, 0),
|
|
416
|
-
{ behaviors: ['staff use the merged Merchant interface on Android'], newProductBehaviorCount: 0 },
|
|
417
|
-
proof({ groups, setups }),
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
test('a held proof-only campaign splits into a setup prefactor and the fewest group slices under 3,800 lines', () => {
|
|
421
|
-
// fmm #1343: an acceptance-only campaign of 25 groups held at 5,940 x 1.3 had no split to propose.
|
|
422
|
-
const setups = [{ id: 'device', what: 'owned emulator, database, and fixture pool', estimatedLines: 900 }]
|
|
423
|
-
const groups = Array.from({ length: 25 }, (_, i) => ({ id: `g${i + 1}`, covers: `#${1300 + i}`, estimatedLines: 200, setup: i < 20 ? 'device' : null }))
|
|
424
|
-
const result = sizeCheck({ units: [unit('1343')] }, { version: 2, units: { 1343: campaign(groups, setups) } })
|
|
425
|
-
assert.equal(result.verdict, 'stop')
|
|
426
|
-
const split = result.held[0].proposedSplit
|
|
427
|
-
assert.deepEqual(split[0], { kind: 'prefactor', title: 'Shared setup: owned emulator, database, and fixture pool', estimatedLines: 900, blockedBy: [] })
|
|
428
|
-
const slices = split.slice(1)
|
|
429
|
-
assert.ok(slices.every((s) => s.kind === 'proof' && s.estimatedLines < PACK_LINES), 'every slice fits')
|
|
430
|
-
assert.deepEqual(slices.flatMap((s) => s.groups), groups.map((g) => g.id), 'every group lands in exactly one slice, in order')
|
|
431
|
-
assert.deepEqual(slices.map((s) => s.groups.length), [18, 2, 5], 'groups sharing a setup pack together; the rest pack apart')
|
|
432
|
-
assert.deepEqual(slices[0].blockedBy, ['Shared setup: owned emulator, database, and fixture pool'])
|
|
433
|
-
assert.deepEqual(slices[2].blockedBy, [], 'groups with no setup wait on nothing')
|
|
434
|
-
assert.match(slices[0].title, /^Proof: g1, g2, .* \(#1300, #1301, /)
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
test('a proof-only split keeps prerequisite prefactors ahead of the groups', () => {
|
|
438
|
-
const groups = [{ id: 'a', covers: '#1', estimatedLines: 1800 }, { id: 'b', covers: '#2', estimatedLines: 1800 }, { id: 'c', covers: '#3', estimatedLines: 1800 }]
|
|
439
|
-
const unitEntry = campaign(groups)
|
|
440
|
-
unitEntry.slice.prerequisites = [{ what: 'a seeded fixture pool', status: 'missing', separate: true, evidence: 'none in scripts/' }]
|
|
441
|
-
const split = sizeCheck({ units: [unit('x')] }, { version: 2, units: { x: unitEntry } }).held[0].proposedSplit
|
|
442
|
-
assert.deepEqual(split.map((s) => [s.kind, s.groups ?? s.title, s.blockedBy]), [
|
|
443
|
-
['prefactor', 'a seeded fixture pool', []],
|
|
444
|
-
['proof', ['a', 'b'], ['a seeded fixture pool']],
|
|
445
|
-
['proof', ['c'], ['a seeded fixture pool']],
|
|
446
|
-
])
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
test('proof groups are required, valid, and small for a proof-only unit, and only for one', () => {
|
|
450
|
-
const run = (value) => () => sizeCheck({ units: [unit('x')] }, { version: 2, units: { x: value } })
|
|
451
|
-
assert.throws(run(entry(500, { newProductBehaviorCount: 0 })), (e) => e instanceof ExplorationUnfinished && /needs proof.groups/.test(e.message))
|
|
452
|
-
assert.throws(run(campaign([{ id: 'a', covers: '#1', estimatedLines: 2000 }])), /group a is estimated at 2000 lines; split it below 1900/)
|
|
453
|
-
assert.throws(run(campaign([{ id: 'a', covers: '#1', estimatedLines: 100, setup: 'nope' }])), /names setup nope/)
|
|
454
|
-
assert.throws(run(campaign([{ id: 'a', covers: '#1', estimatedLines: 100 }, { id: 'a', covers: '#2', estimatedLines: 100 }])), /listed twice/)
|
|
455
|
-
assert.throws(run(entry(500, {}, proof({ groups: [{ id: 'a', covers: '#1', estimatedLines: 1 }] }))), /only for a unit with slice.newProductBehaviorCount 0/)
|
|
456
|
-
assert.throws(run(entry(500, { newProductBehaviorCount: -1 })), /whole number/)
|
|
457
|
-
assert.equal(sizeCheck({ units: [unit('x')] }, { version: 2, units: { x: campaign([{ id: 'a', covers: '#1', estimatedLines: 400 }]) } }).verdict, 'ok', 'a small proof-only unit runs as is')
|
|
458
|
-
})
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
import { applyRelease, bumpPyproject, insertChangelog } from './apply.mjs'
|
|
7
|
-
|
|
8
|
-
test('insertChangelog cuts Unreleased and keeps handwritten notes', () => {
|
|
9
|
-
const before = `# Changelog\n\n## [Unreleased]\n\n- operator note\n\n## [1.0.0] - 2026-01-01\n`
|
|
10
|
-
const after = insertChangelog(before, {
|
|
11
|
-
version: '1.1.0',
|
|
12
|
-
date: '2026-08-24',
|
|
13
|
-
section: '## [1.1.0] - 2026-08-24\n\n### Added\n- export csv\n',
|
|
14
|
-
})
|
|
15
|
-
assert.match(after, /## \[Unreleased\]\n\n## \[1\.1\.0\] - 2026-08-24/)
|
|
16
|
-
assert.match(after, /export csv/)
|
|
17
|
-
assert.match(after, /operator note/)
|
|
18
|
-
assert.match(after, /## \[1\.0\.0\]/)
|
|
19
|
-
assert.equal((after.match(/## \[1\.1\.0\]/g) || []).length, 1)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test('insertChangelog is idempotent only when the recorded section is present', () => {
|
|
23
|
-
const section = '## [1.1.0] - 2026-08-24\n\n### Added\n- export csv'
|
|
24
|
-
const md = `# Changelog\n\n${section}\n`
|
|
25
|
-
assert.equal(insertChangelog(md, { version: '1.1.0', date: '2026-08-24', section }), md)
|
|
26
|
-
assert.throws(
|
|
27
|
-
() => insertChangelog(md, {
|
|
28
|
-
version: '1.1.0',
|
|
29
|
-
date: '2026-08-24',
|
|
30
|
-
section: '## [1.1.0] - 2026-08-24\n\n### Added\n- missing entry',
|
|
31
|
-
}),
|
|
32
|
-
/does not match the recorded plan/,
|
|
33
|
-
)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('bumpPyproject replaces [project] version only', () => {
|
|
37
|
-
const text = '[build-system]\nrequires = ["setuptools"]\n\n[project]\nname = "demo"\nversion = "0.1.0"\n'
|
|
38
|
-
assert.match(bumpPyproject(text, '0.2.0'), /\[project\]\nname = "demo"\nversion = "0\.2\.0"/)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('applyRelease writes changelog and identity files', () => {
|
|
42
|
-
const cwd = mkdtempSync(join(tmpdir(), 'rush-release-apply-'))
|
|
43
|
-
writeFileSync(join(cwd, 'package.json'), `${JSON.stringify({ name: 'demo', version: '1.2.0' }, null, 2)}\n`)
|
|
44
|
-
writeFileSync(join(cwd, 'package-lock.json'), `${JSON.stringify({ name: 'demo', version: '1.2.0', packages: { '': { name: 'demo', version: '1.2.0' } } }, null, 2)}\n`)
|
|
45
|
-
writeFileSync(join(cwd, 'VERSION'), '1.2.0\n')
|
|
46
|
-
writeFileSync(join(cwd, 'CHANGELOG.md'), '# Changelog\n\n## [Unreleased]\n\n')
|
|
47
|
-
const result = applyRelease({
|
|
48
|
-
version: '1.3.0',
|
|
49
|
-
date: '2026-08-24',
|
|
50
|
-
cwd,
|
|
51
|
-
changelogSection: '## [1.3.0] - 2026-08-24\n\n### Added\n- export csv\n',
|
|
52
|
-
})
|
|
53
|
-
assert.deepEqual(result.changed.sort(), ['CHANGELOG.md', 'VERSION', 'package-lock.json', 'package.json'])
|
|
54
|
-
assert.equal(JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8')).version, '1.3.0')
|
|
55
|
-
assert.equal(JSON.parse(readFileSync(join(cwd, 'package-lock.json'), 'utf8')).packages[''].version, '1.3.0')
|
|
56
|
-
assert.equal(readFileSync(join(cwd, 'VERSION'), 'utf8'), '1.3.0\n')
|
|
57
|
-
assert.match(readFileSync(join(cwd, 'CHANGELOG.md'), 'utf8'), /export csv/)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('applyRelease refuses to discard the recorded plan changelog', () => {
|
|
61
|
-
assert.throws(
|
|
62
|
-
() => applyRelease({ version: '1.3.0', date: '2026-08-24' }),
|
|
63
|
-
/requires the recorded plan changelog/,
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('applyRelease delegates a distribution release without adding a version to the private root package', () => {
|
|
68
|
-
const cwd = mkdtempSync(join(tmpdir(), 'rush-release-distribution-apply-'))
|
|
69
|
-
mkdirSync(join(cwd, 'skills/distribution'), { recursive: true })
|
|
70
|
-
writeFileSync(join(cwd, 'package.json'), `${JSON.stringify({ name: 'private-root', private: true }, null, 2)}\n`)
|
|
71
|
-
writeFileSync(join(cwd, 'skills/distribution/manifest.json'), `${JSON.stringify({
|
|
72
|
-
releaseVersion: '0.19.0',
|
|
73
|
-
sources: [{ renderTargets: [{ id: 'private-checkout', revision: 'main' }] }],
|
|
74
|
-
}, null, 2)}\n`)
|
|
75
|
-
writeFileSync(join(cwd, 'CHANGELOG.md'), '# Changelog\n\n')
|
|
76
|
-
const calls = []
|
|
77
|
-
|
|
78
|
-
const result = applyRelease({
|
|
79
|
-
version: '0.19.1',
|
|
80
|
-
date: '2026-08-30',
|
|
81
|
-
cwd,
|
|
82
|
-
changelogSection: '## [0.19.1] - 2026-08-30\n\n### Changed\n- manual trigger only\n',
|
|
83
|
-
cutDistribution: (options) => {
|
|
84
|
-
calls.push(options)
|
|
85
|
-
return ['skills/distribution/manifest.json', 'skills/engineering/engineering-runtime/coherence/workflow.json']
|
|
86
|
-
},
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
assert.deepEqual(calls, [{ version: '0.19.1', cwd }])
|
|
90
|
-
assert.deepEqual(result.changed, [
|
|
91
|
-
'CHANGELOG.md',
|
|
92
|
-
'skills/distribution/manifest.json',
|
|
93
|
-
'skills/engineering/engineering-runtime/coherence/workflow.json',
|
|
94
|
-
])
|
|
95
|
-
assert.deepEqual(JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8')), {
|
|
96
|
-
name: 'private-root',
|
|
97
|
-
private: true,
|
|
98
|
-
})
|
|
99
|
-
})
|