@1aboveio/skills 1.0.7 → 1.0.9

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.
Files changed (224) hide show
  1. package/.codex-plugin/plugin.json +13 -0
  2. package/.codex-plugin/resources.json +1827 -0
  3. package/README.md +22 -0
  4. package/package.json +9 -2
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/airflow/release-check.mjs +3 -1
  7. package/skills/app-dev/android-foundation/SKILL.md +92 -34
  8. package/skills/app-dev/android-foundation/assets/android-ci-job.yml +13 -7
  9. package/skills/app-dev/android-foundation/references/ci.md +23 -13
  10. package/skills/app-dev/android-foundation/references/delivery-focus.md +29 -0
  11. package/skills/app-dev/android-foundation/references/diagnosis.md +136 -0
  12. package/skills/app-dev/android-foundation/references/gotchas.md +20 -11
  13. package/skills/app-dev/android-foundation/references/setup.md +15 -6
  14. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  15. package/skills/app-dev/android-foundation/scripts/device-run.sh +148 -42
  16. package/skills/app-dev/app-debug/SKILL.md +1 -1
  17. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  18. package/skills/app-dev/better-auth/SKILL.md +1 -1
  19. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  20. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  21. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  22. package/skills/app-dev/data-quality/SKILL.md +1 -1
  23. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  24. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  25. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  26. package/skills/app-dev/monorepo/SKILL.md +1 -1
  27. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  28. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  29. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  30. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  31. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  32. package/skills/app-dev/pyspark/SKILL.md +1 -1
  33. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  34. package/skills/app-dev/python-backend/SKILL.md +1 -1
  35. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  36. package/skills/app-dev/shadcn/SKILL.md +1 -1
  37. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  38. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  39. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  40. package/skills/cicd/cloud-build/SKILL.md +1 -1
  41. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  42. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  43. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  44. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  45. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  46. package/skills/cicd/docker/SKILL.md +1 -1
  47. package/skills/cicd/docker/release-check.mjs +3 -1
  48. package/skills/cicd/github-actions/SKILL.md +1 -1
  49. package/skills/cicd/github-actions/release-check.mjs +3 -1
  50. package/skills/cicd/github-flow/SKILL.md +1 -1
  51. package/skills/cicd/github-flow/release-check.mjs +3 -1
  52. package/skills/cicd/google-cloud/SKILL.md +1 -1
  53. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  54. package/skills/cicd/mergify/SKILL.md +1 -1
  55. package/skills/cicd/mergify/release-check.mjs +3 -1
  56. package/skills/cicd/podman/SKILL.md +1 -1
  57. package/skills/cicd/podman/release-check.mjs +3 -1
  58. package/skills/content/article-review/SKILL.md +1 -1
  59. package/skills/content/article-review/release-check.mjs +3 -1
  60. package/skills/content/article-to-image/SKILL.md +1 -1
  61. package/skills/content/article-to-image/release-check.mjs +3 -1
  62. package/skills/content/flowchart-svg/SKILL.md +1 -1
  63. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  64. package/skills/content/image-gen/SKILL.md +1 -1
  65. package/skills/content/image-gen/release-check.mjs +3 -1
  66. package/skills/content/mermaid/SKILL.md +1 -1
  67. package/skills/content/mermaid/release-check.mjs +3 -1
  68. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  69. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  70. package/skills/content/wechat-publisher/SKILL.md +1 -1
  71. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  72. package/skills/content/wechat-search/SKILL.md +1 -1
  73. package/skills/content/wechat-search/release-check.mjs +3 -1
  74. package/skills/content/xai-search/SKILL.md +88 -13
  75. package/skills/content/xai-search/release-check.mjs +3 -1
  76. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  77. package/skills/core/distill-writing/SKILL.md +1 -1
  78. package/skills/core/distill-writing/release-check.mjs +3 -1
  79. package/skills/core/doc-to-md/SKILL.md +1 -1
  80. package/skills/core/doc-to-md/release-check.mjs +3 -1
  81. package/skills/core/editorial-review/SKILL.md +1 -1
  82. package/skills/core/editorial-review/release-check.mjs +3 -1
  83. package/skills/core/house-style/SKILL.md +1 -1
  84. package/skills/core/house-style/release-check.mjs +3 -1
  85. package/skills/core/plain-language/SKILL.md +1 -1
  86. package/skills/core/plain-language/release-check.mjs +3 -1
  87. package/skills/core/polish-cn/SKILL.md +1 -1
  88. package/skills/core/polish-cn/release-check.mjs +3 -1
  89. package/skills/core/reg-parser/SKILL.md +1 -1
  90. package/skills/core/reg-parser/release-check.mjs +3 -1
  91. package/skills/core/terminology/SKILL.md +1 -1
  92. package/skills/core/terminology/release-check.mjs +3 -1
  93. package/skills/core/write-doc/SKILL.md +1 -1
  94. package/skills/core/write-doc/release-check.mjs +3 -1
  95. package/skills/engineering/better-review/SKILL.md +1 -1
  96. package/skills/engineering/better-review/release-check.mjs +3 -1
  97. package/skills/engineering/contributing/SKILL.md +1 -1
  98. package/skills/engineering/contributing/release-check.mjs +3 -1
  99. package/skills/engineering/e2e-test/SKILL.md +1 -1
  100. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  101. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  102. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  103. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  104. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  105. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  106. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  107. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  108. package/skills/engineering/golden-path/SKILL.md +1 -1
  109. package/skills/engineering/golden-path/release-check.mjs +3 -1
  110. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  111. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  112. package/skills/engineering/resolve-issues/SKILL.md +11 -20
  113. package/skills/engineering/resolve-issues/references/delivery-progress.md +63 -0
  114. package/skills/engineering/resolve-issues/references/expire.md +24 -11
  115. package/skills/engineering/resolve-issues/references/implementation.md +9 -1
  116. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  117. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  118. package/skills/engineering/resolve-issues/references/task-plan.md +12 -5
  119. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  120. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  121. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  122. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  123. package/skills/engineering/resolve-release/SKILL.md +1 -1
  124. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  125. package/skills/engineering/rush-issues/SKILL.md +5 -2
  126. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  127. package/skills/engineering/rush-release/SKILL.md +1 -1
  128. package/skills/engineering/rush-release/release-check.mjs +3 -1
  129. package/skills/engineering/test-quality/SKILL.md +1 -1
  130. package/skills/engineering/test-quality/release-check.mjs +3 -1
  131. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  132. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  133. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  134. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  135. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  136. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  137. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  138. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  139. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  140. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  141. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  142. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  143. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  144. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  145. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  146. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  147. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -172
  148. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  149. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  150. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  151. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  152. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  153. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  154. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  155. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  156. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  157. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  158. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  159. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  160. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  161. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  162. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  163. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  164. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  165. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  166. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  167. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  168. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  169. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  170. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  171. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  172. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  173. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  174. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  175. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  176. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  177. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  178. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  179. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  180. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  181. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  182. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  183. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  184. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  185. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  186. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  187. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  188. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  189. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  190. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  191. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  192. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  193. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  194. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  195. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  196. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  197. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  198. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  199. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  200. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  201. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  202. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  203. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  204. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  205. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  206. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  207. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  208. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  209. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  210. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  211. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  212. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  213. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  214. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  215. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  216. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  217. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  218. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  219. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  220. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  221. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  222. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  223. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  224. package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
@@ -1,244 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { spawnSync } from 'node:child_process';
3
- import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { dirname, join } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import test from 'node:test';
8
-
9
- const script = join(dirname(fileURLToPath(import.meta.url)), 'polish_blocks.py');
10
-
11
- const REPORT = `---
12
- title: 月报
13
- ---
14
- # 2026 年 10 月报告
15
-
16
- 本期授权尝试 4,946 笔,授权成功率 45.79%。0 笔销售未能匹配授权,覆盖六个月。
17
- 详见 [方法说明](method.zh.md)。
18
-
19
- | 指标 | 值 |
20
- |---|---|
21
- | 授权尝试 | 4,946 |
22
-
23
- - 授权拒绝 1,111 笔,\`C1\` 为高风险。
24
- - 疑似欺诈响应不是已确认损失。
25
-
26
- \`\`\`python
27
- print("本期授权尝试 4,946 笔")
28
- \`\`\`
29
- `;
30
-
31
- function polish(...args) {
32
- return spawnSync('python3', [script, ...args], { encoding: 'utf8' });
33
- }
34
-
35
- // Writes a document, extracts it, and returns helpers. `propose` keeps every block it does not
36
- // rewrite, so the proposal covers all blocks unless `partial` is set.
37
- function setup(content = REPORT) {
38
- const dir = mkdtempSync(join(tmpdir(), 'polish-cn-test-'));
39
- const doc = join(dir, 'report.zh.md');
40
- writeFileSync(doc, content);
41
- const blocksPath = join(dir, 'blocks.json');
42
- const result = polish('extract', doc, '--out', blocksPath);
43
- assert.equal(result.status, 0, result.stderr);
44
- const blocks = JSON.parse(readFileSync(blocksPath, 'utf8')).blocks;
45
- const byText = (needle) => blocks.find((block) => block.text.includes(needle));
46
- const propose = (entries, { partial = false } = {}) => {
47
- const path = join(dir, `proposals-${Math.random().toString(36).slice(2)}.json`);
48
- const rewritten = {};
49
- for (const [needle, text] of entries) {
50
- const block = byText(needle);
51
- rewritten[block.id] = { sha: block.sha, text };
52
- }
53
- const kept = partial ? {} : Object.fromEntries(
54
- blocks.filter((block) => !rewritten[block.id]).map((block) => [block.id, 'already natural']));
55
- writeFileSync(path, JSON.stringify({ blocks: rewritten, kept, notes: ['note kept'] }));
56
- return path;
57
- };
58
- return { dir, doc, blocksPath, blocks, byText, propose };
59
- }
60
-
61
- test('extract returns prose only: front matter, tables and code fences are never blocks', () => {
62
- const { blocks } = setup();
63
- assert.deepEqual(blocks.map((block) => block.kind), ['heading', 'paragraph', 'list-item', 'list-item']);
64
- assert.ok(blocks.every((block) => !block.text.includes('|') && !block.text.includes('print(')));
65
- assert.equal(blocks[0].prefix, '# ');
66
- assert.deepEqual(blocks[1].lines, [6, 7]);
67
- });
68
-
69
- test('extract skips HTML comments and blocks, indented code, pipe tables without edge pipes, and link definitions', () => {
70
- const { blocks } = setup([
71
- '正文一。', '', '<!--', '注释 99', '-->', '', '<div>', '内嵌 HTML 7', '</div>', '',
72
- ' indented_code(3)', '', '指标 | 值', '--- | ---', '尝试 | 12', '', '[ref]: https://example.com/x', '', '正文二。', '',
73
- ].join('\n'));
74
- assert.deepEqual(blocks.map((block) => block.text), ['正文一。', '正文二。']);
75
- });
76
-
77
- test('check accepts a rewrite that keeps every protected token, even in a new order', () => {
78
- const { blocksPath, propose } = setup();
79
- const proposals = propose([[
80
- '本期授权尝试',
81
- '本期授权成功率为 45.79%,授权尝试共 4,946 笔。0 笔销售未能匹配授权,覆盖六个月。详见 [方法](method.zh.md)。',
82
- ]]);
83
- const result = polish('check', blocksPath, proposals);
84
- assert.equal(result.status, 0, result.stdout + result.stderr);
85
- assert.equal(JSON.parse(result.stdout).accepted.length, 1);
86
- });
87
-
88
- test('check fails when a block is neither rewritten nor kept, or is both, unless --partial', () => {
89
- const { dir, blocksPath, byText, propose } = setup();
90
- const partial = propose([['疑似欺诈', '疑似欺诈响应并非已确认的损失。']], { partial: true });
91
- const strict = polish('check', blocksPath, partial);
92
- assert.equal(strict.status, 1);
93
- assert.equal(JSON.parse(strict.stdout).unreviewed.length, 3);
94
- assert.equal(polish('check', blocksPath, partial, '--partial').status, 0);
95
-
96
- const block = byText('疑似欺诈');
97
- const both = JSON.parse(readFileSync(propose([['疑似欺诈', '疑似欺诈响应并非已确认的损失。']]), 'utf8'));
98
- both.kept[block.id] = 'also kept';
99
- const bothPath = join(dir, 'both.json');
100
- writeFileSync(bothPath, JSON.stringify(both));
101
- const result = polish('check', blocksPath, bothPath);
102
- assert.equal(result.status, 1);
103
- assert.deepEqual(JSON.parse(result.stdout).partitionProblems, [block.id]);
104
- });
105
-
106
- test('check rejects changed, added or dropped numbers, signs, currency, links, images, code and protected terms', () => {
107
- const { blocksPath, propose } = setup();
108
- const cases = [
109
- ['本期授权尝试', '本期授权尝试 4,964 笔,授权成功率 45.79%。0 笔销售未能匹配授权,覆盖六个月。详见 [方法说明](method.zh.md)。', /number '4,946'/],
110
- ['本期授权尝试', '本期授权尝试 4,946 笔,授权成功率 45.79%。0 笔销售未能匹配授权,覆盖 6 个月。详见 [方法说明](method.zh.md)。', /added number '6'/],
111
- ['本期授权尝试', '本期授权尝试 4,946 笔,授权成功率 45.79%。0 笔销售未能匹配授权,覆盖六个月。', /link 'method\.zh\.md'/],
112
- ['本期授权尝试', '本期授权尝试 4,946 笔,授权成功率 45.79%。0 笔销售未能匹配授权,覆盖六个月。详见 ![方法说明](method.zh.md)。', /image 'method\.zh\.md'/],
113
- ['本期授权尝试', '本期授权尝试 4,946 笔,授权成功率 +45.79%。0 笔销售未能匹配授权,覆盖六个月。详见 [方法说明](method.zh.md)。', /symbol '\+'/],
114
- ['本期授权尝试', '本期授权尝试 4,946 笔,授权成功率 45.79%,约 ¥0。0 笔销售未能匹配授权,覆盖六个月。详见 [方法说明](method.zh.md)。', /symbol '¥'/],
115
- ['授权拒绝', '拒付 1,111 笔,`C1` 为高风险。', /term '授权拒绝'/],
116
- ['授权拒绝', '授权拒绝 1,111 笔,C2 为高风险。', /code '`C1`'/],
117
- ];
118
- for (const [needle, text, reason] of cases) {
119
- const result = polish('check', blocksPath, propose([[needle, text]]), '--protect', '授权拒绝', '--protect', '拒付');
120
- assert.equal(result.status, 1, `expected rejection for: ${text}`);
121
- const rejected = Object.values(JSON.parse(result.stdout).rejected);
122
- assert.match(rejected[0], reason);
123
- }
124
- });
125
-
126
- test('check reads TERMINOLOGY.md as concepts: mentions are kept, Avoid terms may become preferred but never appear', () => {
127
- const { dir, blocks, blocksPath, propose } = setup('本月拒绝交易 1 笔,授权拒绝 2 笔。\n\n疑似欺诈响应不是已确认损失。\n');
128
- const terms = join(dir, 'TERMINOLOGY.md');
129
- writeFileSync(terms, [
130
- '# Terminology: test', '', '## Disputes', '',
131
- '| Concept | English | 简体 | 繁體(港) | Avoid | Meaning | Status | Source |',
132
- '| --- | --- | --- | --- | --- | --- | --- | --- |',
133
- '| Decline | decline | 授权拒绝 | 授權拒絕 | 拒绝交易、拒付(指授权拒绝) | Issuer refusal | user, 2026-10-20 | user |',
134
- '| Chargeback | chargeback | 拒付 | — | 退单(指 Visa 拒付) | Dispute | proposed | a.md |',
135
- '| Piped | pipe | 甲\\|乙 | — | | Escaped pipe | proposed | a.md |', '',
136
- '## Sources', '', '| id | Source |', '| --- | --- |', '| a.md | 授权拒绝 |', '',
137
- ].join('\n'));
138
- const run = (needle, text) => polish('check', blocksPath, propose([[needle, text]]), '--terminology', terms);
139
-
140
- const normalized = run('拒绝交易', '本月授权拒绝 1 笔,授权拒绝 2 笔。');
141
- assert.equal(normalized.status, 0, normalized.stdout);
142
- assert.deepEqual(Object.keys(JSON.parse(normalized.stdout).avoidedTermsInSource), [blocks[0].id]);
143
-
144
- const added = run('拒绝交易', '本月拒绝交易 1 笔,拒绝交易 2 笔。');
145
- assert.equal(added.status, 1);
146
- assert.match(Object.values(JSON.parse(added.stdout).rejected)[0], /avoids: 拒绝交易/);
147
-
148
- const dropped = run('拒绝交易', '本月有 1 笔,授权拒绝 2 笔。');
149
- assert.equal(dropped.status, 1);
150
- assert.match(Object.values(JSON.parse(dropped.stdout).rejected)[0], /concept 'c1'.*c1 = 授权拒绝/);
151
-
152
- const scopedNote = run('疑似欺诈', '疑似欺诈响应不是已确认损失,与拒付无关。');
153
- assert.equal(scopedNote.status, 1, 'adding 拒付 adds a mention of the chargeback concept');
154
- assert.match(Object.values(JSON.parse(scopedNote.stdout).rejected)[0], /c2 = 拒付/);
155
-
156
- const emDash = run('疑似欺诈', '疑似欺诈响应——不是已确认损失。');
157
- assert.equal(emDash.status, 0, 'an em dash is not a protected term: — means an empty cell');
158
- });
159
-
160
- test('check flags ids in kept that are not blocks', () => {
161
- const { dir, blocksPath, propose } = setup();
162
- const proposals = JSON.parse(readFileSync(propose([]), 'utf8'));
163
- proposals.kept.b999_note = 'invented';
164
- const path = join(dir, 'unknown.json');
165
- writeFileSync(path, JSON.stringify(proposals));
166
- const result = polish('check', blocksPath, path);
167
- assert.equal(result.status, 1);
168
- assert.deepEqual(JSON.parse(result.stdout).partitionProblems, ['b999_note (unknown id in kept)']);
169
- });
170
-
171
- test('check refuses line breaks and new Markdown structure inside a proposal', () => {
172
- const { blocksPath, propose } = setup();
173
- for (const text of ['疑似欺诈响应\n# 新标题', '疑似欺诈响应\r# 注入', '# 疑似欺诈响应不是已确认损失。', '- 疑似欺诈响应不是已确认损失。']) {
174
- const result = polish('check', blocksPath, propose([['疑似欺诈', text]]));
175
- assert.equal(result.status, 1, `expected rejection for: ${JSON.stringify(text)}`);
176
- assert.match(Object.values(JSON.parse(result.stdout).rejected)[0], /one line|block syntax/);
177
- }
178
- });
179
-
180
- test('check refuses deletions, stale blocks and malformed proposal files', () => {
181
- const { dir, blocksPath, byText, propose } = setup();
182
- const empty = polish('check', blocksPath, propose([['疑似欺诈', ' ']]));
183
- assert.equal(empty.status, 1);
184
- assert.match(empty.stdout, /never deletes a block/);
185
-
186
- const block = byText('疑似欺诈');
187
- const stale = JSON.parse(readFileSync(propose([['疑似欺诈', '疑似欺诈响应并非已确认的损失。']]), 'utf8'));
188
- stale.blocks[block.id].sha = 'old';
189
- const stalePath = join(dir, 'stale.json');
190
- writeFileSync(stalePath, JSON.stringify(stale));
191
- const result = polish('check', blocksPath, stalePath);
192
- assert.equal(result.status, 1);
193
- assert.match(result.stdout, /does not match block sha/);
194
-
195
- const nullPath = join(dir, 'null.json');
196
- writeFileSync(nullPath, 'null');
197
- const malformed = polish('check', blocksPath, nullPath);
198
- assert.equal(malformed.status, 2);
199
- assert.match(malformed.stderr, /needs an object/);
200
- });
201
-
202
- test('apply rewrites accepted prose, keeps tables and code byte-identical, and writes the record', () => {
203
- const { dir, doc, blocksPath, byText, propose } = setup();
204
- const proposals = propose([
205
- ['疑似欺诈', '疑似欺诈响应并非已确认的损失。'],
206
- ['授权拒绝', '授权拒绝共 1,111 笔,`C1` 列为高风险。'],
207
- ]);
208
- const record = join(dir, 'record.json');
209
- const result = polish('apply', doc, blocksPath, proposals, '--reject', byText('授权拒绝').id,
210
- '--record', record, '--model', 'deepseek/deepseek-flash');
211
- assert.equal(result.status, 0, result.stderr);
212
- const after = readFileSync(doc, 'utf8');
213
- assert.match(after, /^- 疑似欺诈响应并非已确认的损失。$/m);
214
- assert.match(after, /^- 授权拒绝 1,111 笔,`C1` 为高风险。$/m, 'a caller-rejected block stays unchanged');
215
- assert.ok(after.includes('| 授权尝试 | 4,946 |\n'));
216
- assert.ok(after.includes('print("本期授权尝试 4,946 笔")'));
217
- assert.ok(after.endsWith('```\n'), 'the trailing newline survives');
218
- const written = JSON.parse(readFileSync(record, 'utf8'));
219
- assert.equal(written.model, 'deepseek/deepseek-flash');
220
- assert.equal(written.applied.length, 1);
221
- assert.deepEqual(written.notes, ['note kept']);
222
- });
223
-
224
- test('apply keeps CRLF line endings and leaves HTML comments intact', () => {
225
- const crlf = ['正文 123。', '', '<!--', '注释 99', '-->', '', '结尾。', ''].join('\r\n');
226
- const { doc, blocksPath, propose } = setup(crlf);
227
- const result = polish('apply', doc, blocksPath, propose([['正文', '正文为 123。']]));
228
- assert.equal(result.status, 0, result.stderr);
229
- assert.equal(readFileSync(doc, 'utf8'), crlf.replace('正文 123。', '正文为 123。'));
230
- });
231
-
232
- test('apply refuses an incomplete proposal file and a document changed after extract', () => {
233
- const { doc, blocksPath, propose } = setup();
234
- const incomplete = polish('apply', doc, blocksPath, propose([['疑似欺诈', '疑似欺诈响应并非已确认的损失。']], { partial: true }));
235
- assert.equal(incomplete.status, 1);
236
- assert.match(incomplete.stderr, /every block needs/);
237
-
238
- const proposals = propose([['疑似欺诈', '疑似欺诈响应并非已确认的损失。']]);
239
- writeFileSync(doc, REPORT.replace('疑似欺诈响应不是', '疑似欺诈响应并不是'));
240
- const result = polish('apply', doc, blocksPath, proposals);
241
- assert.equal(result.status, 1);
242
- assert.match(result.stderr, /changed since extract/);
243
- assert.ok(readFileSync(doc, 'utf8').includes('疑似欺诈响应并不是'));
244
- });
@@ -1,165 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { spawnSync } from 'node:child_process';
3
- import { mkdtempSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { dirname, join } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import test from 'node:test';
8
-
9
- const scriptsDir = dirname(fileURLToPath(import.meta.url));
10
- const skillDir = join(scriptsDir, '..');
11
- const checklistsDir = join(skillDir, 'references', 'checklists');
12
- const mandatesDir = join(skillDir, 'references', 'mandates');
13
-
14
- function run(cmd, args) {
15
- return spawnSync(cmd, args, { encoding: 'utf8' });
16
- }
17
-
18
- function py(script, ...args) {
19
- return run('python3', [join(scriptsDir, script), ...args]);
20
- }
21
-
22
- function scratch(name, content) {
23
- const path = join(mkdtempSync(join(tmpdir(), 'reg-parser-test-')), name);
24
- writeFileSync(path, content);
25
- return path;
26
- }
27
-
28
- const HEADER = `\`\`\`yaml
29
- name: "Test Checklist"
30
- regulation: "Directive 2015/849"
31
- version: "1.0"
32
- scope: "Test"
33
- date: "2026-10-19"
34
- summary:
35
- categories: 1
36
- total_items: 1
37
- critical: 1
38
- major: 0
39
- minor: 0
40
- \`\`\`
41
-
42
- ## CAT-01: Customer Due Diligence
43
-
44
- `;
45
-
46
- // The item template in SKILL.md, filled in. If the template drifts from what
47
- // the validator accepts, every checklist an agent builds from it fails.
48
- function templateItem() {
49
- const skill = readFileSync(join(skillDir, 'SKILL.md'), 'utf8');
50
- const block = skill.match(/Each obligation from Step 2 becomes one checklist item:\n\n```markdown\n([\s\S]*?)```/)[1];
51
- return block
52
- .split('\n')
53
- .filter((line) => !line.includes('# source-native scheme'))
54
- .map((line) => line.replace(/\s+# cat scheme$/, ''))
55
- .join('\n')
56
- .replace('CAT-XX-NNN', 'CAT-01-001')
57
- .replace('[Requirement title]', 'Verify identity')
58
- .replace('[plain-language obligation]', 'Verify customer identity before onboarding.')
59
- .replace('section [ref], [regulation]', 'Art. 13(1)(a), Directive 2015/849')
60
- .replace('critical | major | minor', 'critical')
61
- .replace('[Concrete observable step]', 'ID checked against a reliable source')
62
- .replace('[Another step]', 'Check date recorded')
63
- .replace('[Document or record that proves compliance]', 'Verification log');
64
- }
65
-
66
- test('every bundled checklist validates', () => {
67
- for (const name of readdirSync(checklistsDir).filter((f) => f.endsWith('.md'))) {
68
- const result = py('validate_checklist.py', join(checklistsDir, name));
69
- assert.equal(result.status, 0, `${name}\n${result.stdout}${result.stderr}`);
70
- }
71
- });
72
-
73
- test('every bundled mandate validates', () => {
74
- for (const name of readdirSync(mandatesDir).filter((f) => f.endsWith('.md'))) {
75
- const result = py('validate_mandate.py', join(mandatesDir, name));
76
- assert.equal(result.status, 0, `${name}\n${result.stdout}${result.stderr}`);
77
- }
78
- });
79
-
80
- test('every bundled checklist JSON equals a fresh dump of its markdown', () => {
81
- for (const name of readdirSync(checklistsDir).filter((f) => f.endsWith('.json'))) {
82
- const md = join(checklistsDir, name.replace(/\.json$/, '.md'));
83
- const result = py('dump_checklist.py', md);
84
- assert.equal(result.status, 0, result.stderr);
85
- assert.deepEqual(JSON.parse(result.stdout), JSON.parse(readFileSync(join(checklistsDir, name), 'utf8')), name);
86
- }
87
- });
88
-
89
- test('the SKILL.md item template passes the validator', () => {
90
- const result = py('validate_checklist.py', scratch('ok.md', HEADER + templateItem() + '\n'));
91
- assert.equal(result.status, 0, result.stdout + result.stderr);
92
- });
93
-
94
- test('a checklist with colons outside the bold labels fails', () => {
95
- const item = templateItem().replace(/\*\*(\w+):\*\*/g, '**$1**:');
96
- const result = py('validate_checklist.py', scratch('bad.md', HEADER + item + '\n'));
97
- assert.equal(result.status, 1, result.stdout);
98
- });
99
-
100
- test('a checklist item with empty fields fails', () => {
101
- const item = `### CAT-01-001: Empty
102
-
103
- - **Requirement:**
104
- - **Source:**
105
- - **Severity:** critical
106
- - **Verification:**
107
- - **Evidence:**
108
- `;
109
- const result = py('validate_checklist.py', scratch('empty.md', HEADER + item));
110
- assert.equal(result.status, 1, result.stdout);
111
- assert.match(result.stdout + result.stderr, /field Requirement is empty/);
112
- assert.match(result.stdout + result.stderr, /Verification has no '- \[ \]' steps/);
113
- });
114
-
115
- test('a raw preprocess output fails mandate validation', () => {
116
- const source = scratch('src.md', 'Section 1 The licensee must keep records.\n```\nfenced\n```\n');
117
- const out = join(dirname(source), 'mandate.md');
118
- const pre = run('bash', [join(scriptsDir, 'preprocess-mandate.sh'), source, '--source', 'Test', '--out', out]);
119
- assert.equal(pre.status, 0, pre.stderr);
120
- const result = py('validate_mandate.py', out);
121
- assert.equal(result.status, 1, result.stdout);
122
- assert.match(result.stdout + result.stderr, /Status: RAW/);
123
- });
124
-
125
- test('preprocess refuses to overwrite a finished mandate', () => {
126
- const finished = join(mandatesDir, 'visa-vars.md');
127
- const copy = scratch('visa-vars.md', readFileSync(finished, 'utf8'));
128
- const source = scratch('src.md', 'Section 1 must.\n');
129
- const pre = run('bash', [join(scriptsDir, 'preprocess-mandate.sh'), source, '--source', 'Test', '--out', copy]);
130
- assert.equal(pre.status, 1, pre.stdout);
131
- assert.equal(readFileSync(copy, 'utf8'), readFileSync(finished, 'utf8'));
132
- });
133
-
134
- test('a mandate section with only prose or a hollow entry fails', () => {
135
- const sections = (body) => `# Test — Contract Obligation Extract
136
-
137
- > **Source:** Test Act 2026
138
-
139
- ## Mandatory Downstream Agreement Clauses
140
- ${body}
141
-
142
- ## Prohibited Conduct
143
- _None_
144
-
145
- ## Thresholds & Triggers
146
- _None_
147
-
148
- ## Audit & Oversight Rights
149
- _None_
150
-
151
- ## Notes
152
- Jurisdiction: test only. Effective 2026-10-19. Version 1. Caveat: synthetic fixture for the validator.
153
- `;
154
- assert.equal(py('validate_mandate.py', scratch('ok.md', sections('**section 4(1) Records**: Keep records five years.'))).status, 0);
155
- assert.equal(py('validate_mandate.py', scratch('prose.md', sections('This part has no obligations worth noting, n/a mostly.'))).status, 1);
156
- assert.equal(py('validate_mandate.py', scratch('hollow.md', sections('**section **:'))).status, 1);
157
- });
158
-
159
- test('compare_checklist counts item headings, not ID mentions', () => {
160
- const target = join(skillDir, 'references', 'tests', 'vars-checklist.md');
161
- const canonical = py('compare_checklist.py', join(checklistsDir, 'visa-vars-checklist.md'), target);
162
- assert.equal(canonical.status, 0, canonical.stdout);
163
- const ids = [...new Set(readFileSync(target, 'utf8').match(/\b[A-Z]{4}\.C\d+\.\d+\b/g))].join(' ');
164
- assert.equal(py('compare_checklist.py', scratch('fake.md', ids), target).status, 1);
165
- });
@@ -1,168 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { threeDotRange, parseNameStatus, parseLog, fastForwardable, isPromotionTarget, isIntegrationBranch, isPromotion, reviewNote, normalizeHeadName } from './pr-context.mjs'
4
-
5
- test('threeDotRange uses three dots (the merge diff, not two-dot)', () => {
6
- assert.equal(threeDotRange('abc123'), 'abc123...HEAD')
7
- assert.equal(threeDotRange('abc', 'feat'), 'abc...feat')
8
- })
9
-
10
- test('parseNameStatus handles add/modify/delete and rename', () => {
11
- const text = ['M\tsrc/a.ts', 'A\tsrc/new.ts', 'D\tsrc/old.ts', 'R100\tsrc/from.ts\tsrc/to.ts'].join('\n')
12
- assert.deepEqual(parseNameStatus(text), [
13
- { status: 'M', path: 'src/a.ts' },
14
- { status: 'A', path: 'src/new.ts' },
15
- { status: 'D', path: 'src/old.ts' },
16
- { status: 'R100', path: 'src/to.ts' },
17
- ])
18
- })
19
-
20
- test('parseNameStatus empty → []', () => {
21
- assert.deepEqual(parseNameStatus(''), [])
22
- })
23
-
24
- test('parseLog splits sha + subject', () => {
25
- const text = '1a2b3c add export\n4d5e6f fix expired coupon'
26
- assert.deepEqual(parseLog(text), [
27
- { sha: '1a2b3c', subject: 'add export' },
28
- { sha: '4d5e6f', subject: 'fix expired coupon' },
29
- ])
30
- })
31
-
32
- test('fastForwardable: empty = FF possible; non-empty (base has commits head lacks) = not FF', () => {
33
- assert.equal(fastForwardable([]), true)
34
- assert.equal(fastForwardable([{ sha: 'h0tf1x', subject: 'hotfix prod' }]), false)
35
- assert.equal(fastForwardable(undefined), true)
36
- })
37
-
38
- test('isPromotionTarget: production branches only (the base half of a promotion)', () => {
39
- assert.equal(isPromotionTarget('main'), true)
40
- assert.equal(isPromotionTarget('master'), true)
41
- assert.equal(isPromotionTarget('production'), true)
42
- assert.equal(isPromotionTarget('origin/main'), true)
43
- assert.equal(isPromotionTarget('dev'), false)
44
- assert.equal(isPromotionTarget('feature/x'), false)
45
- assert.equal(isPromotionTarget('release/1.2'), false)
46
- })
47
-
48
- test('isIntegrationBranch: only integration/release-shaped heads (the head half of a promotion)', () => {
49
- // integration branches
50
- assert.equal(isIntegrationBranch('dev'), true)
51
- assert.equal(isIntegrationBranch('develop'), true)
52
- assert.equal(isIntegrationBranch('origin/dev'), true)
53
- assert.equal(isIntegrationBranch('refs/heads/develop'), true)
54
- // release / hotfix / staging shapes
55
- assert.equal(isIntegrationBranch('release/1.4'), true)
56
- assert.equal(isIntegrationBranch('release/2.0.0'), true)
57
- assert.equal(isIntegrationBranch('hotfix/urgent-fix'), true)
58
- assert.equal(isIntegrationBranch('staging'), true)
59
- assert.equal(isIntegrationBranch('staging/eu'), true)
60
- assert.equal(isIntegrationBranch('origin/release/1.4'), true)
61
- // ordinary feature branches — NOT integration
62
- assert.equal(isIntegrationBranch('feature/x'), false)
63
- assert.equal(isIntegrationBranch('chore/388-doc-code-prose-audit'), false)
64
- assert.equal(isIntegrationBranch('issue-392-promotion-heuristic'), false)
65
- assert.equal(isIntegrationBranch('main'), false)
66
- assert.equal(isIntegrationBranch('develop-tweaks'), false) // not exactly `develop`
67
- assert.equal(isIntegrationBranch('feature/release-notes'), false) // `release` not its own segment
68
- // unresolvable head (a bare SHA, or empty) — NOT integration → safe default
69
- assert.equal(isIntegrationBranch('a1b2c3d'), false)
70
- assert.equal(isIntegrationBranch(''), false)
71
- assert.equal(isIntegrationBranch(undefined), false)
72
- })
73
-
74
- test('isPromotion: true ONLY when base is production AND head is integration/release-shaped', () => {
75
- // the ~15x false-positive this fixes: a feature branch → main is NOT a promotion
76
- assert.equal(isPromotion('main', 'chore/388-doc-code-prose-audit'), false)
77
- assert.equal(isPromotion('main', 'feature/x'), false)
78
- assert.equal(isPromotion('main', 'issue-392-promotion-heuristic'), false)
79
- // genuine promotions: integration/release branch → production
80
- assert.equal(isPromotion('main', 'dev'), true)
81
- assert.equal(isPromotion('main', 'release/1.4'), true)
82
- assert.equal(isPromotion('main', 'hotfix/x'), true)
83
- assert.equal(isPromotion('master', 'develop'), true)
84
- assert.equal(isPromotion('origin/main', 'origin/dev'), true)
85
- // base is not a production branch → never a promotion, even from an integration head
86
- assert.equal(isPromotion('dev', 'feature/x'), false)
87
- assert.equal(isPromotion('release/1.4', 'dev'), false)
88
- // unresolvable head (bare SHA / empty) → false (documented safe default)
89
- assert.equal(isPromotion('main', 'a1b2c3d'), false)
90
- assert.equal(isPromotion('main', ''), false)
91
- assert.equal(isPromotion('main', undefined), false)
92
- })
93
-
94
- test('normalizeHeadName strips ONLY known ref prefixes — never an arbitrary leading segment', () => {
95
- // known ref prefixes → stripped to the bare name
96
- assert.equal(normalizeHeadName('refs/heads/develop'), 'develop')
97
- assert.equal(normalizeHeadName('refs/remotes/origin/release/1.4'), 'release/1.4')
98
- assert.equal(normalizeHeadName('origin/dev'), 'dev')
99
- assert.equal(normalizeHeadName('origin/release/1.4'), 'release/1.4')
100
- assert.equal(normalizeHeadName('upstream/staging'), 'staging')
101
- // NOT a known prefix → left intact (the any-prefix hole this closes)
102
- assert.equal(normalizeHeadName('jsmith/dev'), 'jsmith/dev')
103
- assert.equal(normalizeHeadName('feature/release/notes'), 'feature/release/notes')
104
- assert.equal(normalizeHeadName('team/staging/eu'), 'team/staging/eu')
105
- // plain names pass through
106
- assert.equal(normalizeHeadName('dev'), 'dev')
107
- assert.equal(normalizeHeadName('release/1.4'), 'release/1.4')
108
- assert.equal(normalizeHeadName(''), '')
109
- assert.equal(normalizeHeadName(undefined), '')
110
- })
111
-
112
- // LEFT-BOUNDARY REGRESSION (issue #392, fix round): the integration/release name must
113
- // open the branch (after ref-prefix stripping), never be buried under a namespace. A
114
- // nested-prefix feature branch is an ordinary feature PR, not a promotion — the unsafe
115
- // direction if mis-classified. Removing the `^`/normalization makes these go RED.
116
- test('isIntegrationBranch: nested-prefix feature branches are NOT integration (left-boundary hole)', () => {
117
- // line-79 (release/hotfix/staging path) hole
118
- assert.equal(isIntegrationBranch('feature/release/notes'), false)
119
- assert.equal(isIntegrationBranch('feature/hotfix/x'), false)
120
- assert.equal(isIntegrationBranch('team/staging/eu'), false)
121
- // line-77 (exact dev/develop/staging) hole
122
- assert.equal(isIntegrationBranch('jsmith/dev'), false)
123
- assert.equal(isIntegrationBranch('alice/develop'), false)
124
- assert.equal(isIntegrationBranch('wip/staging'), false)
125
- })
126
-
127
- test('isIntegrationBranch: supported ref prefixes still resolve to TRUE after normalization', () => {
128
- assert.equal(isIntegrationBranch('origin/dev'), true)
129
- assert.equal(isIntegrationBranch('refs/heads/develop'), true)
130
- assert.equal(isIntegrationBranch('refs/remotes/origin/release/1.4'), true)
131
- })
132
-
133
- test('isPromotion: nested-prefix feature branches → main are NOT promotions (left-boundary hole)', () => {
134
- // these all classified promotion=true before the anchor fix (the fail-open direction)
135
- assert.equal(isPromotion('main', 'feature/release/notes'), false)
136
- assert.equal(isPromotion('main', 'feature/hotfix/x'), false)
137
- assert.equal(isPromotion('main', 'team/staging/eu'), false)
138
- assert.equal(isPromotion('main', 'jsmith/dev'), false)
139
- assert.equal(isPromotion('main', 'alice/develop'), false)
140
- assert.equal(isPromotion('main', 'wip/staging'), false)
141
- // supported prefixes remain genuine promotions
142
- assert.equal(isPromotion('main', 'refs/heads/develop'), true)
143
- assert.equal(isPromotion('main', 'refs/remotes/origin/release/1.4'), true)
144
- })
145
-
146
- test('reviewNote: PROMOTION + NOT fast-forwardable note still fires for a genuine promotion that is not FF', () => {
147
- const note = reviewNote({
148
- promotion: true,
149
- fastForwardable: false,
150
- base: 'main',
151
- head: 'release/1.4',
152
- commitsOnBaseNotInHead: [{ sha: 'h0tf1x', subject: 'hotfix prod' }],
153
- })
154
- assert.match(note, /PROMOTION \+ NOT fast-forwardable/)
155
- assert.match(note, /main has 1 commit\(s\) not in release\/1\.4/)
156
- assert.match(note, /HOLD/)
157
- })
158
-
159
- test('reviewNote: fast-forwardable promotion → release-readiness note (not a re-review)', () => {
160
- const note = reviewNote({ promotion: true, fastForwardable: true, base: 'main', head: 'dev', commitsOnBaseNotInHead: [] })
161
- assert.match(note, /PROMOTION \(fast-forwardable\)/)
162
- assert.doesNotMatch(note, /NOT fast-forwardable/)
163
- })
164
-
165
- test('reviewNote: not a promotion → review the NET diff (the common feature-PR path)', () => {
166
- const note = reviewNote({ promotion: false, fastForwardable: true, base: 'main', head: 'feature/x', commitsOnBaseNotInHead: [] })
167
- assert.match(note, /Review the NET diff/)
168
- })