@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,114 +0,0 @@
|
|
|
1
|
-
// Tests for the delivery declaration and the flow guess. The guess fixtures are
|
|
2
|
-
// the facts read from four real repos on 2026-10-04, each with the flow its owner
|
|
3
|
-
// says it uses.
|
|
4
|
-
import { test } from 'node:test'
|
|
5
|
-
import assert from 'node:assert/strict'
|
|
6
|
-
import { spawnSync } from 'node:child_process'
|
|
7
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
8
|
-
import { tmpdir } from 'node:os'
|
|
9
|
-
import { join } from 'node:path'
|
|
10
|
-
import { fileURLToPath } from 'node:url'
|
|
11
|
-
|
|
12
|
-
import { guessFlow, parseDelivery, renderDelivery, validateDelivery } from './flow-core.mjs'
|
|
13
|
-
|
|
14
|
-
const CLI = fileURLToPath(new URL('./flow.mjs', import.meta.url))
|
|
15
|
-
const direct = (n) => Array.from({ length: n }, (_, i) => `docs: note ${i}`)
|
|
16
|
-
const merged = (n) => Array.from({ length: n }, (_, i) => `Merge pull request #${100 + i} from 1aboveio/topic-${i}`)
|
|
17
|
-
const squashed = (n) => Array.from({ length: n }, (_, i) => `fix: thing ${i} (#${200 + i})`)
|
|
18
|
-
|
|
19
|
-
test('proxy-cache: a PR rule everyone bypasses, direct history -> direct', () => {
|
|
20
|
-
const guess = guessFlow({ mergifyConfig: false, ruleTypes: ['pull_request'], autoMerge: false, allowedMethods: ['squash', 'merge', 'rebase'], subjects: direct(20) })
|
|
21
|
-
assert.equal(guess.flow, 'direct')
|
|
22
|
-
assert.equal(guess.done, 'committed')
|
|
23
|
-
assert.ok(guess.evidence.some((e) => /bypass/.test(e)))
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('github-runner: recent PR merges after older direct pushes -> pull-request with merge commits', () => {
|
|
27
|
-
const subjects = [...merged(5), ...direct(13), ...merged(2)]
|
|
28
|
-
const guess = guessFlow({ mergifyConfig: false, ruleTypes: ['pull_request'], autoMerge: false, allowedMethods: ['squash', 'merge', 'rebase'], subjects })
|
|
29
|
-
assert.equal(guess.flow, 'pull-request')
|
|
30
|
-
assert.equal(guess.mergeMethod, 'merge')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('pi-better-harness: required checks and auto-merge -> pull-request, squash, auto-merge', () => {
|
|
34
|
-
const guess = guessFlow({ mergifyConfig: false, ruleTypes: ['pull_request', 'deletion', 'non_fast_forward', 'required_status_checks'], autoMerge: true, allowedMethods: ['squash', 'merge', 'rebase'], subjects: squashed(20) })
|
|
35
|
-
assert.deepEqual([guess.flow, guess.mergeMethod, guess.autoMerge, guess.queue], ['pull-request', 'squash', true, null])
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('ai: a Mergify config -> merge-queue through mergify', () => {
|
|
39
|
-
const guess = guessFlow({ mergifyConfig: true, ruleTypes: ['pull_request', 'required_status_checks', 'update'], autoMerge: true, allowedMethods: ['squash', 'merge'], subjects: merged(20) })
|
|
40
|
-
assert.deepEqual([guess.flow, guess.queue], ['merge-queue', 'mergify'])
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('a GitHub merge_queue rule -> merge-queue through github', () => {
|
|
44
|
-
const guess = guessFlow({ mergifyConfig: false, ruleTypes: ['merge_queue', 'pull_request'], autoMerge: true, allowedMethods: ['squash'], subjects: squashed(3) })
|
|
45
|
-
assert.deepEqual([guess.flow, guess.queue], ['merge-queue', 'github'])
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test('unreadable GitHub settings still guess from history and say so', () => {
|
|
49
|
-
const guess = guessFlow({ mergifyConfig: false, ruleTypes: null, autoMerge: null, allowedMethods: null, subjects: direct(4) })
|
|
50
|
-
assert.equal(guess.flow, 'direct')
|
|
51
|
-
assert.ok(guess.evidence.includes('GitHub branch rules unreadable'))
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('the history merge method is used only when the repo allows it', () => {
|
|
55
|
-
const guess = guessFlow({ mergifyConfig: false, ruleTypes: ['required_status_checks'], autoMerge: false, allowedMethods: ['rebase'], subjects: merged(5) })
|
|
56
|
-
assert.equal(guess.mergeMethod, 'rebase')
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
test('every rendered declaration parses back to the same values', () => {
|
|
60
|
-
for (const flow of ['direct', 'pull-request', 'merge-queue']) {
|
|
61
|
-
for (const done of ['merged', 'committed']) {
|
|
62
|
-
for (const mergeMethod of ['squash', 'merge', 'rebase']) {
|
|
63
|
-
const text = `# Contributing\n\n${renderDelivery({ flow, done, mergeMethod })}\n## Next\n- flow: direct\n`
|
|
64
|
-
const parsed = parseDelivery(text)
|
|
65
|
-
assert.deepEqual(parsed, { flow, done, mergeMethod: flow === 'pull-request' ? mergeMethod : 'squash' })
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('the rendered section tells the agent not to stop at the commit only when done is merged', () => {
|
|
72
|
-
assert.match(renderDelivery({ flow: 'direct', done: 'merged' }), /Do not stop at the commit/)
|
|
73
|
-
assert.match(renderDelivery({ flow: 'direct', done: 'committed' }), /ask before pushing/)
|
|
74
|
-
assert.doesNotMatch(renderDelivery({ flow: 'direct', done: 'committed' }), /Do not stop/)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
test('a hand-written section is read; a missing or unknown flow is not a declaration', () => {
|
|
78
|
-
assert.deepEqual(parseDelivery('## Delivery\n\nflow: merge-queue\ndone: merged\n'), { flow: 'merge-queue', done: 'merged', mergeMethod: 'squash' })
|
|
79
|
-
assert.equal(parseDelivery('## Delivery\n\n- done: merged\n'), null)
|
|
80
|
-
assert.equal(parseDelivery('## Delivery\n\n- flow: trunk\n'), null)
|
|
81
|
-
assert.equal(parseDelivery('## Other\n\n- flow: direct\n'), null)
|
|
82
|
-
assert.equal(parseDelivery(null), null)
|
|
83
|
-
assert.equal(parseDelivery('## Delivery\n\n- flow: direct\n').done, 'committed')
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test('validateDelivery rejects unknown values', () => {
|
|
87
|
-
assert.deepEqual(validateDelivery({ flow: 'direct' }), [])
|
|
88
|
-
assert.equal(validateDelivery({ flow: 'gitflow', done: 'shipped', mergeMethod: 'ff' }).length, 3)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('the CLI prints the declared flow without calling GitHub', () => {
|
|
92
|
-
const dir = mkdtempSync(join(tmpdir(), 'github-flow-'))
|
|
93
|
-
try {
|
|
94
|
-
spawnSync('git', ['init', '-q', dir])
|
|
95
|
-
writeFileSync(join(dir, 'CONTRIBUTING.md'), `# Contributing\n\n${renderDelivery({ flow: 'pull-request', mergeMethod: 'merge' })}`)
|
|
96
|
-
writeFileSync(join(dir, '.mergify.yml'), 'queue_rules: []\n')
|
|
97
|
-
const res = spawnSync(process.execPath, [CLI, '--repo-root', dir], { encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin' } })
|
|
98
|
-
assert.equal(res.status, 0, res.stderr)
|
|
99
|
-
const out = JSON.parse(res.stdout)
|
|
100
|
-
assert.deepEqual([out.source, out.flow, out.done, out.mergeMethod, out.queue], ['declared', 'pull-request', 'merged', 'merge', null])
|
|
101
|
-
} finally {
|
|
102
|
-
rmSync(dir, { recursive: true, force: true })
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
test('the CLI fails with exit 2 outside a git repo and on an unknown argument', () => {
|
|
107
|
-
const dir = mkdtempSync(join(tmpdir(), 'github-flow-'))
|
|
108
|
-
try {
|
|
109
|
-
assert.equal(spawnSync(process.execPath, [CLI, '--repo-root', dir], { encoding: 'utf8', env: { ...process.env, GIT_CEILING_DIRECTORIES: tmpdir() } }).status, 2)
|
|
110
|
-
assert.equal(spawnSync(process.execPath, [CLI, '--wat'], { encoding: 'utf8' }).status, 2)
|
|
111
|
-
} finally {
|
|
112
|
-
rmSync(dir, { recursive: true, force: true })
|
|
113
|
-
}
|
|
114
|
-
})
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// Tests for github-flow's persist-conventions: the real CLI against real files in a temp repo.
|
|
2
|
-
import { test } from 'node:test'
|
|
3
|
-
import assert from 'node:assert/strict'
|
|
4
|
-
import { spawnSync } from 'node:child_process'
|
|
5
|
-
import { existsSync, lstatSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
|
6
|
-
import { tmpdir } from 'node:os'
|
|
7
|
-
import { join } from 'node:path'
|
|
8
|
-
import { fileURLToPath } from 'node:url'
|
|
9
|
-
|
|
10
|
-
import { parseDelivery } from './flow-core.mjs'
|
|
11
|
-
|
|
12
|
-
const CLI = fileURLToPath(new URL('./persist-conventions.mjs', import.meta.url))
|
|
13
|
-
|
|
14
|
-
function withRepo(fn) {
|
|
15
|
-
const dir = mkdtempSync(join(tmpdir(), 'github-flow-persist-'))
|
|
16
|
-
try {
|
|
17
|
-
return fn(dir, (name) => readFileSync(join(dir, name), 'utf8'))
|
|
18
|
-
} finally {
|
|
19
|
-
rmSync(dir, { recursive: true, force: true })
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const run = (dir, ...args) => spawnSync(process.execPath, [CLI, '--repo-root', dir, ...args], { encoding: 'utf8' })
|
|
24
|
-
|
|
25
|
-
test('without --write it previews and writes nothing', () => {
|
|
26
|
-
withRepo((dir) => {
|
|
27
|
-
const res = run(dir, '--flow', 'direct')
|
|
28
|
-
assert.equal(res.status, 0)
|
|
29
|
-
assert.match(res.stdout, /CI conventions: preview only/)
|
|
30
|
-
assert.match(res.stdout, /Delivery: preview only/)
|
|
31
|
-
assert.equal(existsSync(join(dir, 'CONTRIBUTING.md')), false)
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('--write with --flow writes both sections, and the declaration reads back', () => {
|
|
36
|
-
withRepo((dir, read) => {
|
|
37
|
-
assert.equal(run(dir, '--flow', 'pull-request', '--merge-method', 'merge', '--write').status, 0)
|
|
38
|
-
const text = read('CONTRIBUTING.md')
|
|
39
|
-
assert.deepEqual(text.match(/^## .+$/gm), ['## CI conventions', '## Delivery'])
|
|
40
|
-
assert.deepEqual(parseDelivery(text), { flow: 'pull-request', done: 'merged', mergeMethod: 'merge' })
|
|
41
|
-
assert.match(read('AGENTS.md'), /\(CONTRIBUTING\.md\)/)
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test('a re-run with a new flow replaces only the Delivery block', () => {
|
|
46
|
-
withRepo((dir, read) => {
|
|
47
|
-
run(dir, '--flow', 'direct', '--write')
|
|
48
|
-
const ci = read('CONTRIBUTING.md').split('## Delivery')[0]
|
|
49
|
-
assert.equal(run(dir, '--flow', 'merge-queue', '--done', 'committed', '--write').status, 0)
|
|
50
|
-
const text = read('CONTRIBUTING.md')
|
|
51
|
-
assert.equal(text.split('## Delivery')[0], ci)
|
|
52
|
-
assert.equal(text.match(/^## Delivery$/gm).length, 1)
|
|
53
|
-
assert.deepEqual(parseDelivery(text), { flow: 'merge-queue', done: 'committed', mergeMethod: 'squash' })
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
test('without --flow it writes CI conventions only and says Delivery was skipped', () => {
|
|
58
|
-
withRepo((dir, read) => {
|
|
59
|
-
const res = run(dir, '--write')
|
|
60
|
-
assert.equal(res.status, 0)
|
|
61
|
-
assert.match(res.stdout, /Delivery: skipped/)
|
|
62
|
-
assert.deepEqual(read('CONTRIBUTING.md').match(/^## .+$/gm), ['## CI conventions'])
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('an invalid delivery value fails with exit 2 and writes nothing', () => {
|
|
67
|
-
withRepo((dir) => {
|
|
68
|
-
for (const args of [['--flow', 'gitflow'], ['--done', 'merged'], ['--flow', 'direct', '--merge-method', 'ff']]) {
|
|
69
|
-
assert.equal(run(dir, ...args, '--write').status, 2, args.join(' '))
|
|
70
|
-
}
|
|
71
|
-
assert.equal(existsSync(join(dir, 'CONTRIBUTING.md')), false)
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('--delivery-only writes the Delivery section without CI conventions', () => {
|
|
76
|
-
withRepo((dir, read) => {
|
|
77
|
-
assert.equal(run(dir, '--flow', 'direct', '--delivery-only', '--write').status, 0)
|
|
78
|
-
assert.deepEqual(read('CONTRIBUTING.md').match(/^## .+$/gm), ['## Delivery'])
|
|
79
|
-
assert.equal(run(dir, '--delivery-only', '--write').status, 2)
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
test('CLAUDE.md reaches the conventions: created as a symlink to AGENTS.md, appended when silent, kept otherwise', () => {
|
|
84
|
-
withRepo((dir, read) => {
|
|
85
|
-
assert.match(run(dir, '--flow', 'direct').stdout, /CLAUDE\.md: would link/)
|
|
86
|
-
assert.equal(existsSync(join(dir, 'CLAUDE.md')), false)
|
|
87
|
-
assert.match(run(dir, '--flow', 'direct', '--write').stdout, /CLAUDE\.md: linked to AGENTS\.md/)
|
|
88
|
-
assert.ok(lstatSync(join(dir, 'CLAUDE.md')).isSymbolicLink())
|
|
89
|
-
assert.equal(read('CLAUDE.md'), read('AGENTS.md'))
|
|
90
|
-
})
|
|
91
|
-
withRepo((dir, read) => {
|
|
92
|
-
writeFileSync(join(dir, 'CLAUDE.md'), '# Claude\n\nUse pnpm.\n')
|
|
93
|
-
run(dir, '--flow', 'direct', '--write')
|
|
94
|
-
assert.ok(read('CLAUDE.md').startsWith('# Claude\n\nUse pnpm.\n\n## Contributing'))
|
|
95
|
-
assert.match(read('CLAUDE.md'), /\[CONTRIBUTING\.md\]\(CONTRIBUTING\.md\)/)
|
|
96
|
-
})
|
|
97
|
-
withRepo((dir, read) => {
|
|
98
|
-
writeFileSync(join(dir, 'CLAUDE.md'), 'See AGENTS.md.\n')
|
|
99
|
-
assert.match(run(dir, '--flow', 'direct', '--write').stdout, /CLAUDE\.md: unchanged/)
|
|
100
|
-
assert.equal(read('CLAUDE.md'), 'See AGENTS.md.\n')
|
|
101
|
-
})
|
|
102
|
-
withRepo((dir) => {
|
|
103
|
-
writeFileSync(join(dir, 'AGENTS.md'), '# Agents\n')
|
|
104
|
-
symlinkSync('AGENTS.md', join(dir, 'CLAUDE.md'))
|
|
105
|
-
assert.match(run(dir, '--flow', 'direct', '--write').stdout, /CLAUDE\.md: unchanged/)
|
|
106
|
-
assert.ok(lstatSync(join(dir, 'CLAUDE.md')).isSymbolicLink())
|
|
107
|
-
})
|
|
108
|
-
})
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import fs from 'node:fs'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import { spawnSync } from 'node:child_process'
|
|
6
|
-
|
|
7
|
-
// @characterizes mergify-audit-cli
|
|
8
|
-
// @covers mergify-audit-core
|
|
9
|
-
// @level integration
|
|
10
|
-
import { auditText, blockUnder, pullRequestBranches } from './audit-core.mjs'
|
|
11
|
-
|
|
12
|
-
const SCRIPT = fileURLToPath(new URL('./audit.mjs', import.meta.url))
|
|
13
|
-
const TEMPLATE = fileURLToPath(new URL('../assets/templates/mergify.yml', import.meta.url))
|
|
14
|
-
const status = (f, check) => f.find((x) => x.check === check)?.status
|
|
15
|
-
const run = (args) => spawnSync('node', [SCRIPT, ...args], { encoding: 'utf8' })
|
|
16
|
-
const tmp = (body) => { const p = `${fs.mkdtempSync('/tmp/mgaudit-')}/m.yml`; fs.writeFileSync(p, body); return p }
|
|
17
|
-
|
|
18
|
-
// ---- blockUnder: the one bit of structure-awareness ----
|
|
19
|
-
test('blockUnder returns the indented body of a key, and null when absent', () => {
|
|
20
|
-
const y = 'a:\n x: 1\n y: 2\nb:\n z: 3\n'
|
|
21
|
-
assert.match(blockUnder(y, 'a'), /x: 1/)
|
|
22
|
-
assert.doesNotMatch(blockUnder(y, 'a'), /z: 3/) // stops at the dedent to `b`
|
|
23
|
-
assert.equal(blockUnder(y, 'nope'), null)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// ---- the shipped template audits clean (dogfood: the default passes its own audit) ----
|
|
27
|
-
test('the skill template passes its own audit with no warn/fail', () => {
|
|
28
|
-
const f = auditText(fs.readFileSync(TEMPLATE, 'utf8'))
|
|
29
|
-
const bad = f.filter((x) => x.status === 'warn' || x.status === 'fail')
|
|
30
|
-
assert.deepEqual(bad, [], `template should be clean, got: ${JSON.stringify(bad)}`)
|
|
31
|
-
assert.equal(status(f, 'enqueue-policy'), 'pass')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
// ---- decidable failures ----
|
|
35
|
-
test('deprecated auto_merge: true is a FAIL', () => {
|
|
36
|
-
assert.equal(status(auditText('merge_protections_settings:\n auto_merge: true\n'), 'deprecated-forms'), 'fail')
|
|
37
|
-
})
|
|
38
|
-
test('deprecated autoqueue is a FAIL', () => {
|
|
39
|
-
assert.equal(status(auditText('queue_rules:\n - name: d\n autoqueue: true\n'), 'deprecated-forms'), 'fail')
|
|
40
|
-
})
|
|
41
|
-
test('a comment that merely NAMES a deprecated form does not trip the check', () => {
|
|
42
|
-
assert.equal(status(auditText('# do not use auto_merge: true here\nqueue_rules: {}\n'), 'deprecated-forms'), 'pass')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// ---- enqueue policy ----
|
|
46
|
-
test('auto_merge_conditions without a review arm WARNs (the retired indiscriminate form)', () => {
|
|
47
|
-
assert.equal(status(auditText('merge_protections_settings:\n auto_merge_conditions:\n - "-draft"\n'), 'enqueue-policy'), 'warn')
|
|
48
|
-
})
|
|
49
|
-
test('auto_merge_conditions with approval but no changes-requested arm WARNs', () => {
|
|
50
|
-
const y = 'merge_protections_settings:\n auto_merge_conditions:\n - "#approved-reviews-by >= 1"\n'
|
|
51
|
-
assert.equal(status(auditText(y), 'enqueue-policy'), 'warn')
|
|
52
|
-
})
|
|
53
|
-
test('hybrid (approval + no open change request) PASSes', () => {
|
|
54
|
-
const y = 'merge_protections_settings:\n auto_merge_conditions:\n - "#approved-reviews-by >= 1"\n - "#changes-requested-reviews-by = 0"\n'
|
|
55
|
-
assert.equal(status(auditText(y), 'enqueue-policy'), 'pass')
|
|
56
|
-
})
|
|
57
|
-
test('no auto_merge_conditions is a NOTE (manual enqueue, ok if deliberate) โ not a warn', () => {
|
|
58
|
-
assert.equal(status(auditText('merge_protections_settings:\n post_comment: true\n'), 'enqueue-policy'), 'note')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// ---- injection mode ----
|
|
62
|
-
test('missing branch_protection_injection_mode: merge WARNs when a queue exists', () => {
|
|
63
|
-
assert.equal(status(auditText('queue_rules:\n - name: d\n'), 'injection-mode'), 'warn')
|
|
64
|
-
})
|
|
65
|
-
test('no queue_rules โ injection check ABSTAINs, never a false pass', () => {
|
|
66
|
-
assert.equal(status(auditText('merge_protections_settings: {}\n'), 'injection-mode'), 'abstain')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
// ---- draft placement (epic-986) ----
|
|
70
|
-
test('-draft inside queue_conditions WARNs; in auto_merge_conditions it does NOT', () => {
|
|
71
|
-
const bad = 'queue_rules:\n - name: d\n queue_conditions:\n - -draft\n'
|
|
72
|
-
assert.equal(status(auditText(bad), 'draft-placement'), 'warn')
|
|
73
|
-
const ok = 'merge_protections_settings:\n auto_merge_conditions:\n - "-draft"\n - "#approved-reviews-by >= 1"\n'
|
|
74
|
-
assert.equal(status(auditText(ok), 'draft-placement'), 'pass')
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// ---- gate anchor ----
|
|
78
|
-
test('a gate anchor asserted <2ร WARNs; โฅ2ร PASSes', () => {
|
|
79
|
-
assert.equal(status(auditText('x: &gate {}\ny: *gate\n'), 'gate-anchor'), 'warn')
|
|
80
|
-
assert.equal(status(auditText('x: &gate {}\ny: *gate\nz: *gate\n'), 'gate-anchor'), 'pass')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
// ---- batch mode (SKILL.md Rule 3) ----
|
|
84
|
-
test('no batch_size WARNs โ unset runs at Mergify\'s built-in 1, so batches never form', () => {
|
|
85
|
-
assert.equal(status(auditText('queue_rules:\n - name: d\n queue_conditions:\n - base = main\n'), 'batch-mode'), 'warn')
|
|
86
|
-
})
|
|
87
|
-
test('explicit batch_size: 1 WARNs (batching off)', () => {
|
|
88
|
-
assert.equal(status(auditText('queue_rules:\n - name: d\n batch_size: 1\n'), 'batch-mode'), 'warn')
|
|
89
|
-
})
|
|
90
|
-
test('batch_size: 3 PASSes (batching on)', () => {
|
|
91
|
-
assert.equal(status(auditText('queue_rules:\n - name: d\n batch_size: 3\n'), 'batch-mode'), 'pass')
|
|
92
|
-
})
|
|
93
|
-
test('dynamic batch_size {min, max} PASSes (batching on)', () => {
|
|
94
|
-
assert.equal(status(auditText('queue_rules:\n - name: d\n batch_size:\n min: 1\n max: 10\n'), 'batch-mode'), 'pass')
|
|
95
|
-
})
|
|
96
|
-
test('no queue_rules โ batch check ABSTAINs, never a false pass', () => {
|
|
97
|
-
assert.equal(status(auditText('merge_protections_settings: {}\n'), 'batch-mode'), 'abstain')
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
test('a queue rule with no checks_timeout WARNs: unset means auto, which dequeues green PRs', () => {
|
|
101
|
-
const f = auditText('queue_rules:\n - name: main\n batch_size: 3\n')
|
|
102
|
-
assert.equal(status(f, 'checks-timeout'), 'warn')
|
|
103
|
-
assert.match(f.find((x) => x.check === 'checks-timeout').detail, /main \(unset = auto\)/)
|
|
104
|
-
})
|
|
105
|
-
test('an explicit checks_timeout duration WARNs and names the rule', () => {
|
|
106
|
-
const f = auditText('queue_rules:\n - name: docs\n checks_timeout: null\n - name: main\n checks_timeout: 35 minutes\n')
|
|
107
|
-
assert.equal(status(f, 'checks-timeout'), 'warn')
|
|
108
|
-
const detail = f.find((x) => x.check === 'checks-timeout').detail
|
|
109
|
-
assert.match(detail, /main \(35 minutes\)/)
|
|
110
|
-
assert.doesNotMatch(detail, /docs/)
|
|
111
|
-
})
|
|
112
|
-
test('checks_timeout: null on every rule PASSes', () => {
|
|
113
|
-
const f = auditText('queue_rules:\n - name: docs\n checks_timeout: null\n - name: main\n batch_size: 3\n checks_timeout: null\n')
|
|
114
|
-
assert.equal(status(f, 'checks-timeout'), 'pass')
|
|
115
|
-
})
|
|
116
|
-
test('checks_timeout: null in defaults.queue_rule covers rules that do not override it', () => {
|
|
117
|
-
const base = 'defaults:\n queue_rule:\n checks_timeout: null\nqueue_rules:\n - name: main\n batch_size: 3\n'
|
|
118
|
-
assert.equal(status(auditText(base), 'checks-timeout'), 'pass')
|
|
119
|
-
assert.equal(status(auditText(base + ' - name: slow\n checks_timeout: 1 hour\n'), 'checks-timeout'), 'warn')
|
|
120
|
-
})
|
|
121
|
-
test('a comment that mentions checks_timeout: null does not count as setting it', () => {
|
|
122
|
-
const f = auditText('queue_rules:\n - name: main\n # checks_timeout: null\n batch_size: 3\n')
|
|
123
|
-
assert.equal(status(f, 'checks-timeout'), 'warn')
|
|
124
|
-
})
|
|
125
|
-
test('no queue_rules โ checks-timeout ABSTAINs, never a false pass', () => {
|
|
126
|
-
assert.equal(status(auditText('merge_protections_settings: {}\n'), 'checks-timeout'), 'abstain')
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
test('absent batch_size WARN points at batch_size: 3 (the skill default)', () => {
|
|
130
|
-
const f = auditText('queue_rules:\n - name: d\n')
|
|
131
|
-
const d = f.find((x) => x.check === 'batch-mode').detail
|
|
132
|
-
assert.match(d, /batch_size: 3/)
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
// ---- CLI exit codes ----
|
|
136
|
-
// ---- stack-ready: commits kept, CI on stacked PRs (configuration.md, "Stack-ready") ----
|
|
137
|
-
test('a queue rule that squashes is a NOTE and names the rule; merge and merge-batch PASS', () => {
|
|
138
|
-
// kyc 2026-10-16: the default queue squashed, so a PR built on another conflicted after its parent landed.
|
|
139
|
-
// stack-sandbox 2026-10-20: a squash queue landed a GitHub-native stack, partial landing included, so it only matters off native stacks.
|
|
140
|
-
const cfg = (m) => `queue_rules:\n - name: release\n merge_method: merge\n - name: default\n merge_method: ${m}\n`
|
|
141
|
-
const squash = auditText(cfg('squash')).find((x) => x.check === 'merge-method')
|
|
142
|
-
assert.equal(squash.status, 'note')
|
|
143
|
-
assert.match(squash.detail, /default \(squash\) โ fine for GitHub-native stacks; a Depends-On stack/)
|
|
144
|
-
assert.doesNotMatch(squash.detail, /release/)
|
|
145
|
-
assert.equal(status(auditText(cfg('merge-batch')), 'merge-method'), 'pass')
|
|
146
|
-
assert.equal(status(auditText(cfg("'squash'")), 'merge-method'), 'note', 'a quoted squash is still a squash')
|
|
147
|
-
assert.equal(status(auditText(cfg('somethingnew')), 'merge-method'), 'abstain', 'an unrecognized method is checked by hand, never passed')
|
|
148
|
-
assert.equal(status(auditText('a: 1\n'), 'merge-method'), 'abstain')
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
test('pullRequestBranches reads the base filter in every trigger shape, and null means any base', () => {
|
|
152
|
-
assert.deepEqual(pullRequestBranches('on:\n pull_request:\n branches: [main]\n paths-ignore:\n - docs/**\n'), ['main'], 'fmm-express test.yml')
|
|
153
|
-
assert.equal(pullRequestBranches('on:\n pull_request:\n branches: ["**"]\n schedule:\n - cron: "0 6 * * 1"\n'), null, 'a1 security-scan.yml')
|
|
154
|
-
assert.deepEqual(pullRequestBranches('on:\n pull_request:\n branches:\n - main\n - release/*\n'), ['main', 'release/*'])
|
|
155
|
-
assert.deepEqual(pullRequestBranches('on: { pull_request: { branches: [main] }, workflow_dispatch: }\n'), ['main'])
|
|
156
|
-
assert.equal(pullRequestBranches('on: { pull_request: , workflow_dispatch: }\n'), null)
|
|
157
|
-
assert.equal(pullRequestBranches('on:\n pull_request:\n workflow_dispatch:\n'), null)
|
|
158
|
-
assert.equal(pullRequestBranches('on:\n push:\n branches: [main]\n'), null, 'no pull_request trigger is not this check\'s business')
|
|
159
|
-
assert.equal(pullRequestBranches('on:\n pull_request_target:\n branches: [main]\n'), null)
|
|
160
|
-
assert.equal(pullRequestBranches('on:\n pull_request:\n # branches: [main]\n'), null, 'a commented-out filter is no filter')
|
|
161
|
-
// Round-5 audit: branches-ignore skips some base too, so a stack on that base gets no CI.
|
|
162
|
-
assert.deepEqual(pullRequestBranches('on:\n pull_request:\n branches-ignore: [rush/**]\n'), ['!rush/**'])
|
|
163
|
-
assert.deepEqual(pullRequestBranches('on:\n pull_request:\n branches-ignore:\n - \'stack/**\'\n'), ['!stack/**'])
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
test('stacked-pr-ci is a NOTE on base-filtered workflows, PASSes without filters, and ABSTAINs with none read', () => {
|
|
167
|
-
// fmm-express and identity 2026-10-16: required CI ran on PRs into main only, so Depends-On stacked PRs got none.
|
|
168
|
-
// stack-sandbox 2026-10-20: a [main]-filtered workflow ran on every PR of a GitHub-native stack.
|
|
169
|
-
const w = (name, text) => ({ name, text })
|
|
170
|
-
const filtered = auditText('a: 1\n', { workflows: [w('test.yml', 'on:\n pull_request:\n branches: [main]\n'), w('deploy.yml', 'on:\n push:\n branches: [main]\n')] }).find((x) => x.check === 'stacked-pr-ci')
|
|
171
|
-
assert.equal(filtered.status, 'note')
|
|
172
|
-
assert.match(filtered.detail, /test\.yml \[main\] โ fine for GitHub-native stacks/)
|
|
173
|
-
assert.doesNotMatch(filtered.detail, /deploy\.yml/)
|
|
174
|
-
assert.equal(status(auditText('a: 1\n', { workflows: [w('ci.yml', 'on:\n pull_request:\n')] }), 'stacked-pr-ci'), 'pass')
|
|
175
|
-
assert.equal(status(auditText('a: 1\n', { workflows: [] }), 'stacked-pr-ci'), 'abstain')
|
|
176
|
-
assert.equal(status(auditText('a: 1\n'), 'stacked-pr-ci'), 'abstain')
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
test('CLI reads .github/workflows beside the config, or --workflows', () => {
|
|
180
|
-
const dir = fs.mkdtempSync('/tmp/mgaudit-')
|
|
181
|
-
fs.mkdirSync(`${dir}/.github/workflows`, { recursive: true })
|
|
182
|
-
fs.writeFileSync(`${dir}/.github/workflows/test.yml`, 'on:\n pull_request:\n branches: [main]\n')
|
|
183
|
-
fs.copyFileSync(TEMPLATE, `${dir}/.mergify.yml`)
|
|
184
|
-
const beside = run(['--file', `${dir}/.mergify.yml`, '--json'])
|
|
185
|
-
assert.equal(beside.status, 0, 'a base filter is a note: GitHub-native stacks still get CI')
|
|
186
|
-
assert.equal(JSON.parse(beside.stdout).findings.find((x) => x.check === 'stacked-pr-ci').status, 'note')
|
|
187
|
-
fs.mkdirSync(`${dir}/clean`)
|
|
188
|
-
fs.writeFileSync(`${dir}/clean/ci.yml`, 'on:\n pull_request:\n')
|
|
189
|
-
const clean = run(['--file', `${dir}/.mergify.yml`, '--workflows', `${dir}/clean`, '--json'])
|
|
190
|
-
assert.equal(clean.status, 0)
|
|
191
|
-
assert.equal(JSON.parse(clean.stdout).findings.find((x) => x.check === 'stacked-pr-ci').status, 'pass')
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
test('CLI exits 2 on FAIL, 1 on WARN, 0 with --allow-warn, 0 on PASS', () => {
|
|
195
|
-
assert.equal(run(['--file', tmp('merge_protections_settings:\n auto_merge: true\n')]).status, 2)
|
|
196
|
-
const warnCfg = tmp('queue_rules:\n - name: d\n')
|
|
197
|
-
assert.equal(run(['--file', warnCfg]).status, 1)
|
|
198
|
-
assert.equal(run(['--file', warnCfg, '--allow-warn']).status, 0)
|
|
199
|
-
assert.equal(run(['--file', TEMPLATE]).status, 0)
|
|
200
|
-
assert.equal(run(['--file', '/nonexistent.yml']).status, 2)
|
|
201
|
-
})
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// @covers mergify-core-wrapper-topology
|
|
2
|
-
// @level integration
|
|
3
|
-
import { test } from 'node:test'
|
|
4
|
-
import assert from 'node:assert/strict'
|
|
5
|
-
import { spawnSync } from 'node:child_process'
|
|
6
|
-
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync } from 'node:fs'
|
|
7
|
-
import { tmpdir } from 'node:os'
|
|
8
|
-
import { dirname, join } from 'node:path'
|
|
9
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
10
|
-
|
|
11
|
-
const SCRIPTS = dirname(fileURLToPath(import.meta.url))
|
|
12
|
-
const SKILL = dirname(SCRIPTS)
|
|
13
|
-
const TEMPLATE = join(SKILL, 'assets', 'templates', 'mergify.yml')
|
|
14
|
-
const AUDIT_CORE = join(SCRIPTS, 'audit-core.mjs')
|
|
15
|
-
const AUDIT_WRAPPER = join(SCRIPTS, 'audit.mjs')
|
|
16
|
-
const WATCH_CORE = join(SCRIPTS, 'watch-pr-delivery-core.mjs')
|
|
17
|
-
const WATCH_WRAPPER = join(SCRIPTS, 'watch-pr-delivery.mjs')
|
|
18
|
-
const STACK_CORE = join(SCRIPTS, 'stack-push-core.mjs')
|
|
19
|
-
const STACK_WRAPPER = join(SCRIPTS, 'stack-push.mjs')
|
|
20
|
-
const PRODUCTION_MODULES = [AUDIT_CORE, AUDIT_WRAPPER, WATCH_CORE, WATCH_WRAPPER, STACK_CORE, STACK_WRAPPER]
|
|
21
|
-
|
|
22
|
-
const runNode = (args) => spawnSync(process.execPath, args, { encoding: 'utf8' })
|
|
23
|
-
|
|
24
|
-
function importWrapper(wrapper, argv) {
|
|
25
|
-
const source = [
|
|
26
|
-
`process.argv = ['node', ${JSON.stringify(wrapper)}, ...${JSON.stringify(argv)}]`,
|
|
27
|
-
`const namespace = await import(${JSON.stringify(pathToFileURL(wrapper).href)})`,
|
|
28
|
-
"process.stderr.write(`WRAPPER_EXPORTS:${JSON.stringify(Object.keys(namespace))}`)",
|
|
29
|
-
].join('\n')
|
|
30
|
-
return runNode(['--input-type=module', '--eval', source])
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function assertSameRun(actual, expected, label) {
|
|
34
|
-
assert.equal(actual.status, expected.status, `${label}: exit status changed`)
|
|
35
|
-
assert.equal(actual.stdout, expected.stdout, `${label}: stdout changed`)
|
|
36
|
-
assert.equal(actual.stderr, expected.stderr, `${label}: stderr changed`)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
test('importing either core is quiet and never launches a CLI', () => {
|
|
40
|
-
for (const core of [AUDIT_CORE, WATCH_CORE, STACK_CORE]) {
|
|
41
|
-
const result = runNode(['--input-type=module', '--eval', `await import(${JSON.stringify(pathToFileURL(core).href)})`])
|
|
42
|
-
assert.equal(result.status, 0, `${core}: ${result.stderr}`)
|
|
43
|
-
assert.equal(result.stdout, '', `${core} wrote CLI output during import`)
|
|
44
|
-
assert.equal(result.stderr, '', `${core} wrote CLI diagnostics during import`)
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test('wrappers export nothing and invoke their core even when loaded through import()', () => {
|
|
49
|
-
const audit = importWrapper(AUDIT_WRAPPER, ['--file', TEMPLATE, '--json'])
|
|
50
|
-
assert.equal(audit.status, 0, audit.stderr)
|
|
51
|
-
assert.match(audit.stdout, /"verdict": "PASS"/)
|
|
52
|
-
assert.equal(audit.stderr, 'WRAPPER_EXPORTS:[]')
|
|
53
|
-
|
|
54
|
-
const watch = importWrapper(WATCH_WRAPPER, ['--help'])
|
|
55
|
-
assert.equal(watch.status, 0, watch.stderr)
|
|
56
|
-
assert.match(watch.stdout, /watch-pr-delivery --repo/)
|
|
57
|
-
assert.equal(watch.stderr, 'WRAPPER_EXPORTS:[]')
|
|
58
|
-
|
|
59
|
-
const stack = importWrapper(STACK_WRAPPER, ['--help'])
|
|
60
|
-
assert.equal(stack.status, 0, stack.stderr)
|
|
61
|
-
assert.match(stack.stdout, /stack-push --trunk/)
|
|
62
|
-
assert.equal(stack.stderr, 'WRAPPER_EXPORTS:[]')
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('direct, canonical-copy, and Claude-symlink wrapper launches preserve both CLI contracts', () => {
|
|
66
|
-
const home = mkdtempSync(join(tmpdir(), 'mergify-install-topology-'))
|
|
67
|
-
try {
|
|
68
|
-
const canonical = join(home, '.agents', 'skills', 'mergify')
|
|
69
|
-
const claude = join(home, '.claude', 'skills', 'mergify')
|
|
70
|
-
mkdirSync(dirname(canonical), { recursive: true })
|
|
71
|
-
mkdirSync(dirname(claude), { recursive: true })
|
|
72
|
-
cpSync(SKILL, canonical, { recursive: true })
|
|
73
|
-
symlinkSync(canonical, claude, 'dir')
|
|
74
|
-
|
|
75
|
-
const directAudit = runNode([AUDIT_WRAPPER, '--file', TEMPLATE, '--json'])
|
|
76
|
-
const directWatch = runNode([WATCH_WRAPPER, '--help'])
|
|
77
|
-
assert.equal(directAudit.status, 0, directAudit.stderr)
|
|
78
|
-
assert.equal(directWatch.status, 0, directWatch.stderr)
|
|
79
|
-
|
|
80
|
-
for (const [label, installedSkill] of [['canonical', canonical], ['claude-symlink', claude]]) {
|
|
81
|
-
assertSameRun(
|
|
82
|
-
runNode([join(installedSkill, 'scripts', 'audit.mjs'), '--file', TEMPLATE, '--json']),
|
|
83
|
-
directAudit,
|
|
84
|
-
`${label} audit`,
|
|
85
|
-
)
|
|
86
|
-
assertSameRun(
|
|
87
|
-
runNode([join(installedSkill, 'scripts', 'watch-pr-delivery.mjs'), '--help']),
|
|
88
|
-
directWatch,
|
|
89
|
-
`${label} watcher`,
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
} finally {
|
|
93
|
-
rmSync(home, { recursive: true, force: true })
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test('Mergify production modules have no Engineering Workflow import or runtime dependency', () => {
|
|
98
|
-
for (const file of PRODUCTION_MODULES) {
|
|
99
|
-
const source = readFileSync(file, 'utf8')
|
|
100
|
-
assert.doesNotMatch(
|
|
101
|
-
source,
|
|
102
|
-
/engineering-runtime|engineering\/_lib|_lib\/main-module|isMainModule/,
|
|
103
|
-
`${file} reaches into Engineering Workflow`,
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
})
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"author": {
|
|
4
|
-
"login": "mergify"
|
|
5
|
-
},
|
|
6
|
-
"createdAt": "2026-07-28T10:31:42Z",
|
|
7
|
-
"body": "# Merge Protections\n\n๐ด **1 of 2 protections blocking** ยท waiting on ๐ reviews\n\n| | Protection | Waiting on |\n|:--:|:--|:--:|\n| ๐ด | **๐ฆ Auto-queue** | ๐ reviews |\n| ๐ข | skill-script-tests (or exempt) before merge | โ |\n\n## ๐ด ๐ฆ Auto-queue\n\n**Waiting for**\n\n- [ ] `#approved-reviews-by >= 1`\n\n<details><summary>This rule is failing.</summary>\n\nWhen all merge protections are satisfied and these conditions match, this pull request will be queued automatically.\n- [ ] `#approved-reviews-by >= 1`\n- [X] `#changes-requested-reviews-by = 0`\n- [X] `-draft`\n- [X] any of:\n - [X] `check-success = skill-script-tests`\n - [ ] all of:\n - [ ] `-files ~= ^skills/`\n - [X] `-files ~= ^\\.github/workflows/skill-gates\\.yml$`\n\n</details>\n\n---\n\n<details><summary>Show 1 satisfied protection</summary>\n\n## ๐ข skill-script-tests (or exempt) before merge\n\nThe one CI gate, asserted as a required merge protection. Green when skill-script-tests succeeded, or when the PR touches no gated path (so the job was never created) โ never absent, so the ruleset can require it without stranding a docs-only PR.\n- [X] any of:\n - [X] `check-success = skill-script-tests`\n - [ ] all of:\n - [ ] `-files ~= ^skills/`\n - [X] `-files ~= ^\\.github/workflows/skill-gates\\.yml$`\n\n</details>"
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"author": {
|
|
11
|
-
"login": "mergify"
|
|
12
|
-
},
|
|
13
|
-
"createdAt": "2026-07-28T11:02:07Z",
|
|
14
|
-
"body": "<!---\nDO NOT EDIT\n-*- Mergify Payload -*-\n{\"version\": 1, \"state\": \"dequeued\", \"queue_rule_name\": \"default\", \"queued_at\": \"2026-07-28T11:02:06.211243+00:00\", \"estimated_time_of_merge\": null, \"speculative_check_pr\": null, \"required_conditions\": []}\n-*- Mergify Payload End -*-\n-->\n\n# Merge Queue Status\n\n- โ
**Entered queue** โ `2026-07-28 11:02 UTC` ยท Rule: `default` ยท triggered by @exoulster with the [`@mergifyio queue` command](https://github.com/1aboveio/ai/pull/620#issuecomment-5103273321)\n- โ **Checks failed** ยท on draft #624\n- ๐ซ **Left the queue** โ `2026-07-28 11:04 UTC` ยท at `e6a855b3c80d22c47661b3a47cadba7ad58a862e`\n\nThis pull request spent **2 minutes 33 seconds** in the queue, including **1 minute 14 seconds** running CI.\n\n<details>\n<summary><strong>Waiting for</strong></summary>\n\n- [ ] any of:\n - [ ] `check-success = skill-script-tests`\n - [ ] `-files ~= ^skills/`\n- [ ] any of:\n - [ ] `check-success = skill-script-tests`\n - [ ] `-files ~= ^skills/`\n- [ ] any of:\n - [ ] `check-success = skill-script-tests-queue`\n - [ ] `-files ~= ^skills/`\n\n</details>\n<details>\n<summary>All conditions</summary>\n\n- [ ] all of [๐ก Merge Protections rule `skill-script-tests (or exempt) before merge`]:\n - [ ] any of:\n - [ ] `check-success = skill-script-tests`\n - [ ] all of:\n - [ ] `-files ~= ^skills/`\n - [X] `-files ~= ^\\.github/workflows/skill-gates\\.yml$`\n- [ ] any of:\n - [ ] `check-success = skill-script-tests`\n - [ ] all of:\n - [ ] `-files ~= ^skills/`\n - [X] `-files ~= ^\\.github/workflows/skill-gates\\.yml$`\n- [ ] any of:\n - [ ] `check-success = skill-script-tests-queue`\n - [ ] all of:\n - [ ] `-files ~= ^skills/`\n - [X] `-files ~= ^\\.github/workflows/skill-gates\\.yml$`\n\n</details>\n\n## Reason\n\nThe merge conditions cannot be satisfied due to failing checks\n\n- `skill-script-tests-queue`\n\nFailing checks:\n- ๐ [skill-script-tests](https://github.com/1aboveio/ai/actions/runs/30353228583/job/90255382376) ([job log](https://github.com/1aboveio/ai/actions/runs/30353228583/job/90255382376))\n- โ [skill-script-tests-queue](https://github.com/1aboveio/ai/actions/runs/30353228583/job/90255382413) ([job log](https://github.com/1aboveio/ai/actions/runs/30353228583/job/90255382413))\n\n## Hint\n\nYou may have to fix your CI before adding the pull request to the queue again.\nIf you update this pull request, to fix the CI, it will automatically be requeued once the queue conditions match again.\nIf you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a `@mergifyio queue` comment.\n\nRequeued โ the merge queue status continues in [this comment โ](https://github.com/1aboveio/ai/pull/620#issuecomment-5103476982)."
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"author": {
|
|
18
|
-
"login": "mergify"
|
|
19
|
-
},
|
|
20
|
-
"createdAt": "2026-07-28T11:20:58Z",
|
|
21
|
-
"body": "<!---\nDO NOT EDIT\n-*- Mergify Payload -*-\n{\"version\": 1, \"state\": \"merged\", \"queue_rule_name\": \"default\", \"queued_at\": \"2026-07-28T11:20:56.782765+00:00\", \"estimated_time_of_merge\": null, \"speculative_check_pr\": null, \"required_conditions\": []}\n-*- Mergify Payload End -*-\n-->\n\n# Merge Queue Status\n\n- โ
**Entered queue** โ `2026-07-28 11:20 UTC` ยท Rule: `default` ยท triggered by @exoulster with the [`@mergifyio queue` command](https://github.com/1aboveio/ai/pull/620#issuecomment-5103472242)\n- โ
**Checks passed** ยท on draft #627\n- โ
**Merged** โ `2026-07-28 11:25 UTC` ยท at `e6a855b3c80d22c47661b3a47cadba7ad58a862e` ยท merge\n\nThis pull request spent **4 minutes 29 seconds** in the queue, including **3 minutes 1 second** running CI.\n\n<details>\n<summary>Required conditions to merge</summary>\n\n- [X] all of [๐ก Merge Protections rule `skill-script-tests (or exempt) before merge`]:\n - [X] any of:\n - [X] `check-success = skill-script-tests`\n - [ ] all of:\n - [ ] `-files ~= ^skills/`\n - [X] `-files ~= ^\\.github/workflows/skill-gates\\.yml$`\n- [X] any of:\n - [X] `check-success = skill-script-tests`\n - [ ] all of:\n - [ ] `-files ~= ^skills/`\n - [X] `-files ~= ^\\.github/workflows/skill-gates\\.yml$`\n- [X] any of:\n - [X] `check-success = skill-script-tests-queue`\n - [ ] all of:\n - [ ] `-files ~= ^skills/`\n - [X] `-files ~= ^\\.github/workflows/skill-gates\\.yml$`\n\n</details>"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"author": {
|
|
25
|
-
"login": "mergify"
|
|
26
|
-
},
|
|
27
|
-
"createdAt": "2026-07-28T11:21:32Z",
|
|
28
|
-
"body": "> queue\n\n#### โ๏ธ Command `queue` ignored because it is already running from a previous command.\n\n\n\n"
|
|
29
|
-
}
|
|
30
|
-
]
|