@alipay/alipay-aipay 1.2.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +5 -0
  2. package/dist/commands/install.d.ts.map +1 -1
  3. package/dist/commands/install.js +13 -12
  4. package/dist/commands/install.js.map +1 -1
  5. package/dist/commands/uninstall.d.ts.map +1 -1
  6. package/dist/commands/uninstall.js +17 -9
  7. package/dist/commands/uninstall.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +5 -5
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/index.js +4 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/utils/config.d.ts +5 -3
  14. package/dist/utils/config.d.ts.map +1 -1
  15. package/dist/utils/config.js +34 -12
  16. package/dist/utils/config.js.map +1 -1
  17. package/dist/utils/file.d.ts +8 -0
  18. package/dist/utils/file.d.ts.map +1 -1
  19. package/dist/utils/file.js +161 -9
  20. package/dist/utils/file.js.map +1 -1
  21. package/dist/utils/jq.d.ts.map +1 -1
  22. package/dist/utils/jq.js +8 -0
  23. package/dist/utils/jq.js.map +1 -1
  24. package/package.json +9 -5
  25. package/skill/SKILL.md +15 -169
  26. package/skill/VERSION +1 -1
  27. package/skill/references/integration/flow.md +388 -291
  28. package/skill/references/integration/modules/alipay-sdk-reminder.md +5 -3
  29. package/skill/references/integration/modules/checklist.md +54 -32
  30. package/skill/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +199 -0
  31. package/skill/references/integration/modules/code-examples/csharp/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +8 -4
  32. package/skill/references/integration/modules/code-examples/csharp/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +6 -2
  33. package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +3 -1
  34. package/skill/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +184 -0
  35. package/skill/references/integration/modules/code-examples/java/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +4 -1
  36. package/skill/references/integration/modules/code-examples/java/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -2
  37. package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +3 -1
  38. package/skill/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +135 -0
  39. package/skill/references/integration/modules/code-examples/nodejs/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +16 -10
  40. package/skill/references/integration/modules/code-examples/nodejs/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +10 -4
  41. package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +2 -1
  42. package/skill/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +157 -0
  43. package/skill/references/integration/modules/code-examples/php/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +4 -1
  44. package/skill/references/integration/modules/code-examples/php/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -2
  45. package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +2 -1
  46. package/skill/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +128 -0
  47. package/skill/references/integration/modules/code-examples/python/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +4 -1
  48. package/skill/references/integration/modules/code-examples/python/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -1
  49. package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +4 -2
  50. package/skill/references/integration/modules/interface-contract/aipay-interface-contract.md +52 -0
  51. package/skill/references/integration/modules/interface-contract/apppay-interface-contract.md +45 -0
  52. package/skill/references/integration/modules/interface-contract/common-trade-contract.md +42 -0
  53. package/skill/references/integration/modules/interface-contract/notify-url-contract.md +68 -0
  54. package/skill/references/integration/modules/interface-contract/webpay-interface-contract.md +44 -0
  55. package/skill/references/integration/modules/interface-guide.md +38 -71
  56. package/skill/references/integration/modules/product-decision.md +18 -35
  57. package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +62 -94
  58. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +43 -40
  59. package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +93 -114
  60. package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +358 -148
  61. package/skill/references/integration/modules/scripts/sandbox_config.sh +299 -0
  62. package/skill/references/integration/modules/troubleshooting-flow.md +28 -0
  63. package/skill/references/normal/alipay-cli-env.md +15 -5
  64. package/skill/references/normal/full-process-routing.md +82 -0
  65. package/skill/references/normal/json/customer-messages.json +942 -0
  66. package/skill/references/normal/json/policy-rules.json +179 -0
  67. package/skill/references/normal/rejection-guide.md +12 -7
  68. package/skill/references/normal/scripts/alipay_cli_env.mjs +33 -0
  69. package/skill/references/normal/scripts/alipay_cli_env.sh +55 -0
  70. package/skill/references/normal/scripts/common.sh +151 -0
  71. package/skill/references/normal/scripts/detect_dev_tool.sh +5 -1
  72. package/skill/references/normal/scripts/flow_loader.mjs +241 -0
  73. package/skill/references/normal/scripts/integration_context_runner.mjs +166 -0
  74. package/skill/references/normal/scripts/integration_message_runner.mjs +1023 -0
  75. package/skill/references/normal/scripts/interaction_reply.mjs +133 -0
  76. package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +224 -0
  77. package/skill/references/normal/scripts/onboarding_message_runner.mjs +451 -0
  78. package/skill/references/normal/scripts/onboarding_recovery_runner.mjs +96 -0
  79. package/skill/references/normal/scripts/open_official_url.sh +82 -0
  80. package/skill/references/normal/scripts/platform_compat.mjs +39 -0
  81. package/skill/references/normal/scripts/project_route_inspector.sh +132 -0
  82. package/skill/references/normal/scripts/python_script_runner.mjs +56 -0
  83. package/skill/references/normal/scripts/render_customer_message.mjs +1288 -0
  84. package/skill/references/normal/scripts/self_update.mjs +160 -0
  85. package/skill/references/normal/scripts/telemetry.mjs +1327 -0
  86. package/skill/references/normal/self-update.md +18 -51
  87. package/skill/references/normal/telemetry.md +26 -0
  88. package/skill/references/onboarding/flow.md +421 -309
  89. package/skill/references/onboarding/modules/app-release.md +44 -438
  90. package/skill/references/onboarding/modules/authorization.md +25 -281
  91. package/skill/references/onboarding/modules/error-handling.md +31 -237
  92. package/skill/references/onboarding/modules/product-sign.md +25 -294
  93. package/skill/references/onboarding/modules/scripts/app.sh +445 -169
  94. package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +47 -28
  95. package/skill/references/onboarding/modules/scripts/auth.sh +484 -167
  96. package/skill/references/onboarding/modules/scripts/download_key_tool.sh +157 -0
  97. package/skill/references/onboarding/modules/scripts/error_handler.sh +101 -130
  98. package/skill/references/onboarding/modules/scripts/network_retry.sh +96 -0
  99. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +20 -71
  100. package/skill/references/onboarding/modules/scripts/service.sh +154 -89
  101. package/skill/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
  102. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +34 -20
  103. package/skill/references/onboarding/modules/service-registration.md +20 -317
  104. package/skill/references/runtime-entry.md +63 -0
@@ -0,0 +1,1023 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from 'node:crypto';
4
+ import { spawnSync } from 'node:child_process';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import process from 'node:process';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ import { isWindowsPlatform } from './platform_compat.mjs';
12
+ import { renderMessage } from './render_customer_message.mjs';
13
+ import { emitTerminalMetric } from './telemetry.mjs';
14
+
15
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
16
+ const inspectorPath = path.join(scriptDir, 'project_route_inspector.sh');
17
+
18
+ const productVariants = {
19
+ aipay: { variant: 'AIPAY', productName: 'AI 按量付费' },
20
+ webpay: { variant: 'WEBPAY', productName: 'AI 网页应用收款' },
21
+ apppay: { variant: 'APPPAY', productName: 'AI 移动应用收款' }
22
+ };
23
+ const currentProjectAliases = new Set(['.', './', '当前项目', '当前目录', '这个项目', '这个仓库', '这里']);
24
+ const projectMarkerNames = [
25
+ 'package.json',
26
+ 'pom.xml',
27
+ 'build.gradle',
28
+ 'build.gradle.kts',
29
+ 'settings.gradle',
30
+ 'settings.gradle.kts',
31
+ 'gradlew',
32
+ 'mvnw',
33
+ 'pyproject.toml',
34
+ 'requirements.txt',
35
+ 'Pipfile',
36
+ 'poetry.lock',
37
+ 'composer.json',
38
+ 'artisan',
39
+ 'manage.py'
40
+ ];
41
+ const projectMarkerNameSet = new Set(projectMarkerNames);
42
+ const skippedProjectDirs = new Set([
43
+ '.git',
44
+ '.hg',
45
+ '.svn',
46
+ '.cache',
47
+ '.gradle',
48
+ '.idea',
49
+ '.next',
50
+ '.nuxt',
51
+ '.pytest_cache',
52
+ '.tox',
53
+ '.Trash',
54
+ '.venv',
55
+ 'Library',
56
+ '__pycache__',
57
+ 'build',
58
+ 'coverage',
59
+ 'dist',
60
+ 'node_modules',
61
+ 'target',
62
+ 'vendor'
63
+ ]);
64
+ const sourceFileExtensions = new Set(['.java', '.kt', '.js', '.mjs', '.cjs', '.ts', '.tsx', '.py', '.php', '.cs']);
65
+ const commentOnlyLinePattern = /^\s*(?:\/\/|#|\*|<!--)/;
66
+ const webpayMarker = 'alipay\\.trade\\.page\\.pay|AlipayTradePagePay(Request|Model|Response)';
67
+ const apppayMarker = 'alipay\\.trade\\.app\\.pay|AlipayTradeAppPay(Request|Model|Response)';
68
+ const tradeQueryMarker = 'alipay\\.trade\\.query|AlipayTradeQuery(Request|Model|Response)|alipay_trade_query_response';
69
+ const tradeRefundMarker = 'alipay\\.trade\\.refund|AlipayTradeRefund(Request|Model|Response)|alipay_trade_refund_response';
70
+ const tradeRefundQueryMarker = 'alipay\\.trade\\.fastpay\\.refund\\.query|AlipayTradeFastpayRefundQuery(Request|Model|Response)|alipay_trade_fastpay_refund_query_response';
71
+ const tradeCloseMarker = 'alipay\\.trade\\.close|AlipayTradeClose(Request|Model|Response)|alipay_trade_close_response';
72
+ const tradeNotifyMarker = 'notify_url|notifyUrl|NotifyUrl|setNotifyUrl|异步通知';
73
+ const aipayPaymentNeededMarker = 'Payment-Needed';
74
+ const aipayPaymentProofMarker = 'Payment-Proof';
75
+ const aipayPaymentValidationMarker = 'Payment-Validation';
76
+ const aipayPaymentVerifyMarker = 'alipay\\.aipay\\.agent\\.payment\\.verify|AlipayAipayAgentPaymentVerify(Request|Model|Response)|alipay_aipay_agent_payment_verify_response';
77
+ const aipayFulfillmentConfirmMarker = 'alipay\\.aipay\\.agent\\.fulfillment\\.confirm|AlipayAipayAgentFulfillmentConfirm(Request|Model|Response)|alipay_aipay_agent_fulfillment_confirm_response';
78
+ const aipayMarker = `${aipayPaymentNeededMarker}|${aipayPaymentProofMarker}|${aipayPaymentVerifyMarker}|${aipayFulfillmentConfirmMarker}`;
79
+ const internalPrefix = 'ALIPAY_AIPAY_INTERNAL:';
80
+ const receiptKinds = Object.freeze({
81
+ startConfirmation: 'integration-start-confirmation',
82
+ checklist: 'integration-checklist-scope',
83
+ a2mExecution: 'integration-a2m-execution'
84
+ });
85
+
86
+ function parseArgs(argv) {
87
+ const command = argv[2];
88
+ const args = {};
89
+ for (let index = 3; index < argv.length; index += 1) {
90
+ const key = argv[index];
91
+ if (!key.startsWith('--')) throw new Error(`未知参数 ${key}`);
92
+ const value = argv[index + 1];
93
+ if (value === undefined || value.startsWith('--')) throw new Error(`参数 ${key} 缺少取值`);
94
+ const name = key.slice(2);
95
+ if (Object.prototype.hasOwnProperty.call(args, name)) throw new Error(`参数 ${key} 重复`);
96
+ args[name] = value;
97
+ index += 1;
98
+ }
99
+ return { command, args };
100
+ }
101
+
102
+ function required(args, name) {
103
+ const value = args[name];
104
+ if (typeof value !== 'string' || value.length === 0) throw new Error(`缺少参数 --${name}`);
105
+ return value;
106
+ }
107
+
108
+ function stripWrappingQuotes(value) {
109
+ const trimmed = value.trim();
110
+ if (
111
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
112
+ (trimmed.startsWith("'") && trimmed.endsWith("'")) ||
113
+ (trimmed.startsWith('`') && trimmed.endsWith('`'))
114
+ ) {
115
+ return trimmed.slice(1, -1).trim();
116
+ }
117
+ return trimmed;
118
+ }
119
+
120
+ function sanitizeProjectInput(raw) {
121
+ const value = stripWrappingQuotes(raw);
122
+ if (!value || value.length > 4096 || /[\u0000-\u001f\u007f`|]/.test(value)) {
123
+ throw new Error('project-input 必须是不含控制字符或 Markdown 边界的安全路径描述');
124
+ }
125
+ return value;
126
+ }
127
+
128
+ function normalizeBasePath(raw) {
129
+ const basePath = path.resolve(stripWrappingQuotes(raw));
130
+ if (!path.isAbsolute(basePath) || !fs.existsSync(basePath) || !fs.statSync(basePath).isDirectory()) {
131
+ throw new Error('base-path 必须是实际存在的绝对目录');
132
+ }
133
+ return fs.realpathSync(basePath);
134
+ }
135
+
136
+ function safeReaddir(directory) {
137
+ try {
138
+ return fs.readdirSync(directory, { withFileTypes: true });
139
+ } catch {
140
+ return [];
141
+ }
142
+ }
143
+
144
+ function listProjectMarkers(directory) {
145
+ const markers = [];
146
+ for (const entry of safeReaddir(directory)) {
147
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
148
+ if (projectMarkerNameSet.has(entry.name)) {
149
+ markers.push(entry.name);
150
+ } else if (/\.(?:csproj|sln)$/i.test(entry.name)) {
151
+ markers.push(entry.name);
152
+ }
153
+ }
154
+ return markers.sort((left, right) => {
155
+ const leftIndex = projectMarkerNames.indexOf(left);
156
+ const rightIndex = projectMarkerNames.indexOf(right);
157
+ if (leftIndex >= 0 || rightIndex >= 0) {
158
+ if (leftIndex < 0) return 1;
159
+ if (rightIndex < 0) return -1;
160
+ return leftIndex - rightIndex;
161
+ }
162
+ return left.localeCompare(right);
163
+ });
164
+ }
165
+
166
+ function isRecognizableProjectRoot(directory) {
167
+ return listProjectMarkers(directory).length > 0;
168
+ }
169
+
170
+ function shouldSkipProjectDir(entry) {
171
+ return !entry.isDirectory() || entry.isSymbolicLink() || skippedProjectDirs.has(entry.name) || entry.name.startsWith('.');
172
+ }
173
+
174
+ function parseDepth(value) {
175
+ if (value === undefined) return 3;
176
+ if (!/^[0-4]$/.test(value)) throw new Error('max-depth 仅支持 0 到 4');
177
+ return Number(value);
178
+ }
179
+
180
+ function locateProjectCandidates(searchBasePath, maxDepth = 3, maxCandidates = 20, displayBasePath = searchBasePath, classificationBasePath = displayBasePath) {
181
+ const queue = [{ directory: searchBasePath, depth: 0 }];
182
+ const seen = new Set([searchBasePath]);
183
+ const candidates = [];
184
+
185
+ for (let index = 0; index < queue.length && candidates.length < maxCandidates; index += 1) {
186
+ const { directory, depth } = queue[index];
187
+ const markers = listProjectMarkers(directory);
188
+ if (markers.length > 0) {
189
+ const projectPath = fs.realpathSync(directory);
190
+ candidates.push({
191
+ projectPath,
192
+ relativePath: path.relative(displayBasePath, projectPath) || '.',
193
+ projectSelection: classifyExistingProject(
194
+ projectPath,
195
+ classificationBasePath,
196
+ projectPath === classificationBasePath ? '当前目录' : projectPath
197
+ ),
198
+ projectOrigin: 'EXISTING_PROJECT',
199
+ projectOriginLabel: '现有项目',
200
+ markers
201
+ });
202
+ }
203
+ if (depth >= maxDepth) continue;
204
+ const childDirs = safeReaddir(directory)
205
+ .filter((entry) => !shouldSkipProjectDir(entry))
206
+ .map((entry) => path.join(directory, entry.name))
207
+ .sort((left, right) => left.localeCompare(right));
208
+ for (const child of childDirs) {
209
+ let realChild;
210
+ try {
211
+ realChild = fs.realpathSync(child);
212
+ } catch {
213
+ continue;
214
+ }
215
+ if (seen.has(realChild)) continue;
216
+ seen.add(realChild);
217
+ queue.push({ directory: realChild, depth: depth + 1 });
218
+ }
219
+ }
220
+
221
+ return candidates;
222
+ }
223
+
224
+ function formatProjectCandidateHint(basePath, candidates) {
225
+ const visible = candidates.slice(0, 5);
226
+ if (visible.length === 0) return '';
227
+ return visible
228
+ .map((candidate) => {
229
+ const relative = candidate.relativePath || path.relative(basePath, candidate.projectPath) || '.';
230
+ return `${relative}(${candidate.markers.slice(0, 3).join('、')})`;
231
+ })
232
+ .join(';');
233
+ }
234
+
235
+ function safeInline(value) {
236
+ return value.replace(/[`|\r\n]/g, ' ').replace(/\s+/g, ' ').trim();
237
+ }
238
+
239
+ function formatLocatedCandidateRows(candidates) {
240
+ if (candidates.length === 0) {
241
+ return '未找到可识别项目候选。可以提供更精确的项目文件夹,或说明要新建的目录。';
242
+ }
243
+ return candidates
244
+ .slice(0, 10)
245
+ .map((candidate, index) => {
246
+ const markers = candidate.markers.slice(0, 3).join('、');
247
+ return `${index + 1}. \`${safeInline(candidate.relativePath)}\`(识别依据:${markers})`;
248
+ })
249
+ .join('\n');
250
+ }
251
+
252
+ function projectRootError(searchBasePath, prefix, displayBasePath = searchBasePath) {
253
+ const candidates = locateProjectCandidates(searchBasePath, 3, 5, displayBasePath, displayBasePath);
254
+ const markerHint = 'package.json、pom.xml、build.gradle、pyproject.toml、requirements.txt、composer.json、*.csproj 或 *.sln';
255
+ const candidateHint = formatProjectCandidateHint(displayBasePath, candidates);
256
+ if (candidateHint) {
257
+ return new Error(`${prefix};请从候选项目中选择一个具体目录,或提供更精确路径。候选:${candidateHint}。如果要新建项目,请说明“在当前目录新建 pay-demo”或“新建项目”。项目根通常包含 ${markerHint}`);
258
+ }
259
+ return new Error(`${prefix};未在指定目录下找到可识别项目候选。请提供现有服务端项目的具体目录,或说明“在当前目录新建 pay-demo”或“新建项目”。项目根通常包含 ${markerHint}`);
260
+ }
261
+
262
+ function isDefaultNewProjectRequest(input) {
263
+ return /^(?:帮我)?(?:新建|创建)(?:一个)?(?:默认)?(?:项目|目录)?$/u.test(input) ||
264
+ /^(?:帮我)?(?:新建|创建)(?:一个)?默认项目$/u.test(input);
265
+ }
266
+
267
+ function explicitNewArtifactRequest(input) {
268
+ return /^(?:请|麻烦)?(?:给我|帮我)?(?:新建|创建)(?:一个|个|一套)?[^,,。;;!?!?\r\n]{0,80}(?:项目|工程|网站|站点|商城|商店|店铺|应用程序|应用)(?:[,,。;;!?!?].*)?$/iu.test(input);
269
+ }
270
+
271
+ function looksLikeExplicitPath(value) {
272
+ return /^(?:[A-Za-z]:[\\/]|\\\\|[./~])/u.test(value) ||
273
+ /[\\/]/u.test(value) ||
274
+ /^[A-Za-z0-9._-]+$/u.test(value);
275
+ }
276
+
277
+ function extractNewProjectPath(input) {
278
+ const prefixed = '(?:请|麻烦)?(?:给我|帮我)?';
279
+ const currentRoot = '(?:当前目录|当前项目|这个项目|这个仓库|这里)';
280
+ const patterns = [
281
+ new RegExp(`^${prefixed}(?:在)?${currentRoot}(?:下|里)?(?:新建|创建)(?:一个)?(?:项目|目录)?\\s+(.+)$`, 'u'),
282
+ new RegExp(`^${prefixed}(?:新建|创建)(?:一个)?(?:项目|目录)(?:到|在)\\s*(.+)$`, 'u')
283
+ ];
284
+ for (const pattern of patterns) {
285
+ const match = input.match(pattern);
286
+ if (match?.[1]) {
287
+ const candidate = stripWrappingQuotes(match[1]);
288
+ if (looksLikeExplicitPath(candidate)) return candidate;
289
+ }
290
+ }
291
+
292
+ const simple = input.match(/^(?:请|麻烦)?(?:给我|帮我)?(?:新建|创建)(?:一个)?\s+(.+)$/u);
293
+ if (simple?.[1]) {
294
+ const candidate = stripWrappingQuotes(simple[1]);
295
+ if (looksLikeExplicitPath(candidate)) return candidate;
296
+ }
297
+ return '';
298
+ }
299
+
300
+ function isExplicitNewProjectRequest(input) {
301
+ return isDefaultNewProjectRequest(input) || explicitNewArtifactRequest(input) || extractNewProjectPath(input) !== '';
302
+ }
303
+
304
+ function parseExistingProjectPath(input) {
305
+ if (/^(?:请|麻烦)?(?:给我|帮我)?(?:在)?(?:当前目录|当前项目|这个项目|这个仓库|这里)(?:中|内|下|里)?新增.+$/u.test(input)) {
306
+ return '当前项目';
307
+ }
308
+ return input;
309
+ }
310
+
311
+ function hasDirectoryEntries(directory) {
312
+ try {
313
+ return safeReaddir(directory).length > 0;
314
+ } catch {
315
+ return true;
316
+ }
317
+ }
318
+
319
+ function prepareNewProject(projectPath) {
320
+ if (!path.isAbsolute(projectPath)) throw new Error('ROUTE_INSPECT_ERROR:projectPath 必须是绝对路径');
321
+ if (fs.existsSync(projectPath)) {
322
+ const stat = fs.lstatSync(projectPath);
323
+ if (stat.isSymbolicLink() || !stat.isDirectory() || hasDirectoryEntries(projectPath)) {
324
+ throw new Error('ROUTE_INSPECT_ERROR:NEW_PROJECT 路径必须尚不存在或为空且不能是符号链接');
325
+ }
326
+ }
327
+ return {
328
+ projectSelection: 'NEW_PROJECT',
329
+ projectOrigin: 'NEW_PROJECT',
330
+ projectOriginLabel: '本轮新建项目',
331
+ preparationStatus: 'READY'
332
+ };
333
+ }
334
+
335
+ function parseSingleJson(stdout, context) {
336
+ const trimmed = stdout.trim();
337
+ if (!trimmed) throw new Error(`${context} 未输出 JSON`);
338
+ try {
339
+ const parsed = JSON.parse(trimmed);
340
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error();
341
+ return parsed;
342
+ } catch {
343
+ throw new Error(`${context} 未输出唯一合法 JSON 对象`);
344
+ }
345
+ }
346
+
347
+ function runUnixInspector(args, failureContext, jsonContext) {
348
+ const inspected = spawnSync('bash', [inspectorPath, ...args], {
349
+ encoding: 'utf8',
350
+ shell: false
351
+ });
352
+ if (inspected.status !== 0) {
353
+ const detail = (inspected.stderr || inspected.stdout || '').trim() || `退出码 ${inspected.status}`;
354
+ throw new Error(`${failureContext}失败:${detail}`);
355
+ }
356
+ return parseSingleJson(inspected.stdout, jsonContext);
357
+ }
358
+
359
+ function defaultNewProjectPath() {
360
+ const homeInput = process.env.HOME || os.homedir() || '';
361
+ if (!homeInput) throw new Error('无法确定用户目录;请提供明确的新项目路径');
362
+ const homePath = normalizeBasePath(homeInput);
363
+ const container = path.join(homePath, 'alipay-aipay-projects');
364
+ if (fs.existsSync(container)) {
365
+ const stat = fs.lstatSync(container);
366
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
367
+ throw new Error('默认项目父目录不可用;请提供明确的新项目路径');
368
+ }
369
+ }
370
+ for (let index = 1; index <= 50; index += 1) {
371
+ const suffix = index === 1 ? '' : `-${index}`;
372
+ const candidate = path.join(container, `pay-demo${suffix}`);
373
+ if (!fs.existsSync(candidate)) return candidate;
374
+ const stat = fs.lstatSync(candidate);
375
+ if (stat.isDirectory() && !stat.isSymbolicLink() && !hasDirectoryEntries(candidate)) return candidate;
376
+ }
377
+ throw new Error('默认项目目录已被占用;请提供明确的新项目路径');
378
+ }
379
+
380
+ function parseNewProjectPath(input) {
381
+ const explicitPath = extractNewProjectPath(input);
382
+ if (explicitPath) return explicitPath;
383
+ if (isDefaultNewProjectRequest(input) || explicitNewArtifactRequest(input)) return defaultNewProjectPath();
384
+ return input;
385
+ }
386
+
387
+ function resolveAgainstBase(input, basePath) {
388
+ const candidate = path.isAbsolute(input) ? path.resolve(input) : path.resolve(basePath, input);
389
+ if (!path.isAbsolute(input)) {
390
+ const relative = path.relative(basePath, candidate);
391
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
392
+ throw new Error('相对项目路径必须位于 base-path 内;如需目录外项目,请提供绝对路径');
393
+ }
394
+ }
395
+ return candidate;
396
+ }
397
+
398
+ function resolveSearchBasePath(args, basePath) {
399
+ const rawInput = args['search-input'];
400
+ if (rawInput === undefined) return basePath;
401
+ const input = parseProjectSearchInput(sanitizeProjectInput(rawInput));
402
+ const candidate = currentProjectAliases.has(input) ? basePath : resolveAgainstBase(input, basePath);
403
+ const searchBasePath = fs.existsSync(candidate) ? fs.realpathSync(candidate) : candidate;
404
+ if (!fs.existsSync(searchBasePath) || !fs.statSync(searchBasePath).isDirectory()) {
405
+ throw new Error('search-input 必须指向实际存在的目录;可以使用当前目录、当前目录下子目录或绝对路径');
406
+ }
407
+ return searchBasePath;
408
+ }
409
+
410
+ function parseProjectSearchInput(input) {
411
+ const trimmed = stripWrappingQuotes(input);
412
+ if (/^(?:帮我)?(?:找|查找|定位)(?:一下)?(?:项目|项目目录|工程|工程目录)$/u.test(trimmed)) {
413
+ return '当前目录';
414
+ }
415
+ const patterns = [
416
+ /^(?:帮我)?(?:在|到)\s*(.+?)\s*(?:下|里|目录下|目录里)?(?:找|查找|定位)(?:一下)?(?:项目|项目目录|工程|工程目录)$/u,
417
+ /^(?:帮我)?(?:找|查找|定位)(?:一下)?\s*(.+?)\s*(?:下|里|目录下|目录里)?(?:的)?(?:项目|项目目录|工程|工程目录)$/u
418
+ ];
419
+ for (const pattern of patterns) {
420
+ const match = trimmed.match(pattern);
421
+ if (match?.[1]) return stripWrappingQuotes(match[1]);
422
+ }
423
+ return trimmed;
424
+ }
425
+
426
+ function classifyExistingProject(projectPath, basePath, rawInput) {
427
+ if (currentProjectAliases.has(rawInput)) return 'CURRENT_PROJECT';
428
+ const relative = path.relative(basePath, projectPath);
429
+ if (relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) {
430
+ return 'CURRENT_PROJECT';
431
+ }
432
+ return 'OTHER_PROJECT';
433
+ }
434
+
435
+ function isReadableDirectory(directory) {
436
+ try {
437
+ const stat = fs.statSync(directory);
438
+ if (!stat.isDirectory()) return false;
439
+ fs.accessSync(directory, fs.constants.R_OK);
440
+ return true;
441
+ } catch {
442
+ return false;
443
+ }
444
+ }
445
+
446
+ function sourceFiles(projectPath) {
447
+ const files = [];
448
+ const stack = [projectPath];
449
+ while (stack.length > 0) {
450
+ const directory = stack.pop();
451
+ for (const entry of safeReaddir(directory)) {
452
+ const entryPath = path.join(directory, entry.name);
453
+ if (entry.isDirectory()) {
454
+ if (!entry.isSymbolicLink() && !skippedProjectDirs.has(entry.name)) stack.push(entryPath);
455
+ continue;
456
+ }
457
+ if (entry.isSymbolicLink() || !entry.isFile()) continue;
458
+ if (sourceFileExtensions.has(path.extname(entry.name))) files.push(entryPath);
459
+ }
460
+ }
461
+ return files.sort((left, right) => left.localeCompare(right));
462
+ }
463
+
464
+ function projectSnapshotFingerprint(projectPath) {
465
+ if (!path.isAbsolute(projectPath) || !isReadableDirectory(projectPath)) {
466
+ throw new Error('项目快照路径必须是可访问的绝对目录');
467
+ }
468
+ const canonicalPath = fs.realpathSync(projectPath);
469
+ const tracked = new Set(sourceFiles(canonicalPath));
470
+ for (const name of [...projectMarkerNames, '.alipay-sandbox.json', '.gitignore']) {
471
+ const candidate = path.join(canonicalPath, name);
472
+ try {
473
+ if (fs.statSync(candidate).isFile() && !fs.lstatSync(candidate).isSymbolicLink()) tracked.add(candidate);
474
+ } catch {
475
+ // Missing optional project facts do not enter the snapshot.
476
+ }
477
+ }
478
+ const hash = crypto.createHash('sha256');
479
+ hash.update(`project=${canonicalPath}\n`);
480
+ for (const file of [...tracked].sort((left, right) => left.localeCompare(right))) {
481
+ const stat = fs.statSync(file);
482
+ const relative = path.relative(canonicalPath, file);
483
+ hash.update(`${relative}\0${stat.size}\0`);
484
+ if (path.basename(file) === '.alipay-sandbox.json') {
485
+ hash.update(`${stat.mtimeMs}\n`);
486
+ } else {
487
+ hash.update(fs.readFileSync(file));
488
+ hash.update('\n');
489
+ }
490
+ }
491
+ return hash.digest('hex');
492
+ }
493
+
494
+ function encodeReceipt(payload) {
495
+ return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');
496
+ }
497
+
498
+ function decodeReceipt(value, expectedKind) {
499
+ if (typeof value !== 'string' || !value) throw new Error('缺少 receipt');
500
+ let payload;
501
+ try {
502
+ payload = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
503
+ } catch {
504
+ throw new Error('receipt 不是合法编码');
505
+ }
506
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload) || payload.schema_ver !== 1 || payload.kind !== expectedKind) {
507
+ throw new Error('receipt 类型或版本非法');
508
+ }
509
+ return payload;
510
+ }
511
+
512
+ function scanPattern(files, pattern) {
513
+ const regex = new RegExp(pattern);
514
+ for (const file of files) {
515
+ let content;
516
+ try {
517
+ content = fs.readFileSync(file, 'utf8');
518
+ } catch {
519
+ continue;
520
+ }
521
+ for (const line of content.split(/\r?\n/u)) {
522
+ if (!commentOnlyLinePattern.test(line) && regex.test(line)) return file;
523
+ }
524
+ }
525
+ return '';
526
+ }
527
+
528
+ function scanProjectRoute(productType, projectPath, projectSelection) {
529
+ if (!['aipay', 'webpay', 'apppay'].includes(productType)) {
530
+ throw new Error('ROUTE_INSPECT_ERROR:productType 非法');
531
+ }
532
+ if (!['CURRENT_PROJECT', 'OTHER_PROJECT', 'PREPARED_NEW_PROJECT'].includes(projectSelection)) {
533
+ throw new Error('ROUTE_INSPECT_ERROR:projectSelection 非法');
534
+ }
535
+ if (!path.isAbsolute(projectPath)) throw new Error('ROUTE_INSPECT_ERROR:projectPath 必须是绝对路径');
536
+ if (!isReadableDirectory(projectPath)) throw new Error('ROUTE_INSPECT_ERROR:项目路径不可访问');
537
+
538
+ const projectOrigin = projectSelection === 'PREPARED_NEW_PROJECT' ? 'NEW_PROJECT' : 'EXISTING_PROJECT';
539
+ const projectOriginLabel = projectSelection === 'PREPARED_NEW_PROJECT' ? '本轮新建项目' : '现有项目';
540
+ const targetMarker = productType === 'aipay' ? aipayMarker : productType === 'webpay' ? webpayMarker : apppayMarker;
541
+ const files = sourceFiles(projectPath);
542
+ const otherProducts = [];
543
+
544
+ if (scanPattern(files, webpayMarker) && productType !== 'webpay') otherProducts.push('AI 网页应用收款');
545
+ if (scanPattern(files, apppayMarker) && productType !== 'apppay') otherProducts.push('AI 移动应用收款');
546
+ if (scanPattern(files, aipayMarker) && productType !== 'aipay') otherProducts.push('AI 按量付费');
547
+
548
+ const otherProductsText = otherProducts.length > 0 ? otherProducts.join(',') : '无';
549
+ let integrationStatus;
550
+ let evidence;
551
+
552
+ if (!scanPattern(files, targetMarker)) {
553
+ if (otherProductsText === '无') {
554
+ integrationStatus = 'NO_PAYMENT';
555
+ evidence = projectSelection === 'PREPARED_NEW_PROJECT'
556
+ ? '固定检查器扫描本轮已初始化的新项目,未发现目标产品或其他受支持支付代码'
557
+ : '固定检查器未发现目标产品或其他受支持支付代码';
558
+ } else {
559
+ integrationStatus = 'OTHER_PRODUCT_ONLY';
560
+ evidence = '固定检查器只发现其他受支持支付产品代码';
561
+ }
562
+ } else {
563
+ const requiredPatterns = productType === 'aipay'
564
+ ? [aipayPaymentNeededMarker, aipayPaymentProofMarker, aipayPaymentVerifyMarker, aipayFulfillmentConfirmMarker, aipayPaymentValidationMarker]
565
+ : productType === 'webpay'
566
+ ? [webpayMarker, tradeQueryMarker, tradeRefundMarker, tradeRefundQueryMarker, tradeCloseMarker, tradeNotifyMarker]
567
+ : [apppayMarker, tradeQueryMarker, tradeRefundMarker, tradeRefundQueryMarker, tradeCloseMarker, tradeNotifyMarker];
568
+ const missing = requiredPatterns.filter((pattern) => !scanPattern(files, pattern));
569
+ integrationStatus = 'TARGET_PARTIAL';
570
+ evidence = missing.length === 0
571
+ ? '固定检查器逐项发现目标产品入口及全部核心接口标记,但静态扫描不能证明配置、安全、测试和 checklist 完成;必须进入 Integration 验证分支'
572
+ : '固定检查器发现目标产品代码,但至少一个核心接口标记缺失;不得按完整集成路由';
573
+ }
574
+
575
+ return {
576
+ projectSelection,
577
+ projectOrigin,
578
+ projectOriginLabel,
579
+ integrationStatus,
580
+ evidence,
581
+ otherProducts: otherProductsText
582
+ };
583
+ }
584
+
585
+ function runResolveProject(args) {
586
+ const intent = required(args, 'intent');
587
+ if (!['existing', 'new'].includes(intent)) throw new Error('intent 非法,仅支持 existing、new');
588
+ const basePath = normalizeBasePath(required(args, 'base-path'));
589
+ const input = sanitizeProjectInput(required(args, 'project-input'));
590
+
591
+ if (intent === 'existing') {
592
+ if (isExplicitNewProjectRequest(input)) {
593
+ throw new Error('明确新建项目请求必须使用 --intent new,不能改用当前项目');
594
+ }
595
+ const existingInput = parseExistingProjectPath(input);
596
+ const candidate = currentProjectAliases.has(existingInput) ? basePath : resolveAgainstBase(existingInput, basePath);
597
+ const projectPath = fs.existsSync(candidate) ? fs.realpathSync(candidate) : candidate;
598
+ if (!fs.existsSync(projectPath) || !fs.statSync(projectPath).isDirectory()) {
599
+ throw new Error('现有项目路径不可访问;请提供当前目录内相对路径或实际存在的绝对路径');
600
+ }
601
+ if (!isRecognizableProjectRoot(projectPath)) {
602
+ const prefix = currentProjectAliases.has(existingInput)
603
+ ? '当前目录不是可识别的项目根,不能直接作为现有项目扫描'
604
+ : '该路径未识别到项目根标记,不能确认是服务端项目根';
605
+ const searchBasePath = currentProjectAliases.has(input) ? basePath : projectPath;
606
+ throw projectRootError(searchBasePath, prefix, basePath);
607
+ }
608
+ return JSON.stringify({
609
+ projectPath,
610
+ projectSelection: classifyExistingProject(projectPath, basePath, existingInput),
611
+ projectOrigin: 'EXISTING_PROJECT',
612
+ projectOriginLabel: '现有项目'
613
+ });
614
+ }
615
+
616
+ const newPathInput = sanitizeProjectInput(parseNewProjectPath(input));
617
+ if (currentProjectAliases.has(newPathInput)) {
618
+ throw new Error('新项目必须明确尚不存在或为空的子目录名');
619
+ }
620
+ const projectPath = resolveAgainstBase(newPathInput, basePath);
621
+ const route = isWindowsPlatform()
622
+ ? prepareNewProject(projectPath)
623
+ : runUnixInspector(['prepare-new', projectPath], '新项目路径检查', '新项目路径检查器');
624
+ return JSON.stringify({ projectPath, ...route });
625
+ }
626
+
627
+ function runLocateProjects(args) {
628
+ const basePath = normalizeBasePath(required(args, 'base-path'));
629
+ const searchBasePath = resolveSearchBasePath(args, basePath);
630
+ const maxDepth = parseDepth(args['max-depth']);
631
+ const format = args.format || 'json';
632
+ if (!['json', 'message'].includes(format)) throw new Error('format 仅支持 json 或 message');
633
+ const candidates = locateProjectCandidates(searchBasePath, maxDepth, 20, basePath, basePath);
634
+ if (format === 'message') {
635
+ return renderMessage('integration.project_candidates.select', 'DEFAULT', {
636
+ searchBasePath,
637
+ candidateRows: formatLocatedCandidateRows(candidates)
638
+ });
639
+ }
640
+ return JSON.stringify({
641
+ basePath,
642
+ searchBasePath,
643
+ maxDepth,
644
+ candidates,
645
+ nextAction: '请选择一个候选项目目录作为现有项目,或说明要新建的目录,例如“在当前目录新建 pay-demo”或“新建项目”。'
646
+ });
647
+ }
648
+
649
+ function normalizeLanguage(input) {
650
+ const value = input.trim();
651
+ const compact = value.toLowerCase().replace(/[\s_.-]+/g, '');
652
+ if (compact === 'java') return 'Java';
653
+ if (compact === 'python' || compact === 'py' || compact === 'python3') return 'Python';
654
+ if (compact === 'nodejs' || compact === 'node' || compact === 'javascript' || compact === 'js') return 'Node.js';
655
+ if (compact === 'c#' || compact === 'csharp' || compact === 'dotnet' || compact === 'net') return 'C#';
656
+ if (compact === 'php') return 'PHP';
657
+ throw new Error('服务端语言非法,仅支持 Java、Python、Node.js、C#、PHP');
658
+ }
659
+
660
+ function startConfirmationFacts(args) {
661
+ const productType = required(args, 'product-type');
662
+ const mapping = productVariants[productType];
663
+ if (!mapping) throw new Error('product-type 非法,仅支持 aipay、webpay、apppay');
664
+
665
+ const projectPath = required(args, 'project-path');
666
+ const projectSelection = required(args, 'project-selection');
667
+ const language = normalizeLanguage(required(args, 'language'));
668
+ const framework = required(args, 'framework');
669
+ const productName = args['product-name'] || mapping.productName;
670
+ if (productName !== mapping.productName) throw new Error('product-name 与 product-type 不匹配');
671
+ if (!path.isAbsolute(projectPath)) throw new Error('project-path 必须是绝对路径');
672
+
673
+ const route = isWindowsPlatform()
674
+ ? scanProjectRoute(productType, projectPath, projectSelection)
675
+ : runUnixInspector(['scan', productType, projectPath, projectSelection], '项目检查', '项目检查器');
676
+ if (route.projectSelection !== projectSelection) throw new Error('项目检查结果与本轮 projectSelection 不一致');
677
+ const existing = projectSelection === 'CURRENT_PROJECT' || projectSelection === 'OTHER_PROJECT';
678
+ const preparedNew = projectSelection === 'PREPARED_NEW_PROJECT';
679
+ if (
680
+ !(preparedNew && route.projectOrigin === 'NEW_PROJECT' && route.projectOriginLabel === '本轮新建项目') &&
681
+ !(existing && route.projectOrigin === 'EXISTING_PROJECT' && route.projectOriginLabel === '现有项目')
682
+ ) {
683
+ throw new Error('项目来源与本轮项目选择不一致');
684
+ }
685
+ if (!['TARGET_PARTIAL', 'OTHER_PRODUCT_ONLY', 'NO_PAYMENT'].includes(route.integrationStatus)) {
686
+ throw new Error('项目集成状态不允许发起代码开发确认');
687
+ }
688
+ if (typeof route.evidence !== 'string' || typeof route.otherProducts !== 'string') {
689
+ throw new Error('项目检查结果缺少必要事实字段');
690
+ }
691
+
692
+ return {
693
+ productType,
694
+ variant: mapping.variant,
695
+ productName,
696
+ projectPath: fs.realpathSync(projectPath),
697
+ projectSelection,
698
+ language,
699
+ framework,
700
+ route,
701
+ projectFingerprint: projectSnapshotFingerprint(projectPath)
702
+ };
703
+ }
704
+
705
+ function confirmationReceipt(facts) {
706
+ return encodeReceipt({
707
+ schema_ver: 1,
708
+ kind: receiptKinds.startConfirmation,
709
+ product_type: facts.productType,
710
+ product_name: facts.productName,
711
+ project_path: facts.projectPath,
712
+ project_selection: facts.projectSelection,
713
+ language: facts.language,
714
+ framework: facts.framework,
715
+ project_fingerprint: facts.projectFingerprint,
716
+ route: {
717
+ project_origin: facts.route.projectOrigin,
718
+ project_origin_label: facts.route.projectOriginLabel,
719
+ integration_status: facts.route.integrationStatus,
720
+ evidence: facts.route.evidence,
721
+ other_products: facts.route.otherProducts
722
+ }
723
+ });
724
+ }
725
+
726
+ function renderStartConfirmation(facts) {
727
+ return renderMessage('integration.start.confirm', facts.variant, {
728
+ productName: facts.productName,
729
+ projectPath: facts.projectPath,
730
+ projectOriginLabel: facts.route.projectOriginLabel,
731
+ integrationStatus: facts.route.integrationStatus,
732
+ otherProducts: facts.route.otherProducts,
733
+ language: facts.language,
734
+ framework: facts.framework,
735
+ nextFlow: 'integration'
736
+ });
737
+ }
738
+
739
+ export function createStartConfirmation(args) {
740
+ const facts = startConfirmationFacts(args);
741
+ return { message: renderStartConfirmation(facts), receipt: confirmationReceipt(facts) };
742
+ }
743
+
744
+ export function validateStartConfirmationReceipt(receipt) {
745
+ const saved = decodeReceipt(receipt, receiptKinds.startConfirmation);
746
+ const facts = startConfirmationFacts({
747
+ 'product-type': saved.product_type,
748
+ 'product-name': saved.product_name,
749
+ 'project-path': saved.project_path,
750
+ 'project-selection': saved.project_selection,
751
+ language: saved.language,
752
+ framework: saved.framework
753
+ });
754
+ const currentReceipt = confirmationReceipt(facts);
755
+ if (currentReceipt === receipt) return { valid: true, receipt, productType: saved.product_type };
756
+ return {
757
+ valid: false,
758
+ receipt: currentReceipt,
759
+ message: renderStartConfirmation(facts),
760
+ productType: saved.product_type
761
+ };
762
+ }
763
+
764
+ export function createChecklistScopeReceipt(projectPath) {
765
+ const canonicalPath = fs.realpathSync(required({ projectPath }, 'projectPath'));
766
+ return encodeReceipt({
767
+ schema_ver: 1,
768
+ kind: receiptKinds.checklist,
769
+ project_path: canonicalPath,
770
+ project_fingerprint: projectSnapshotFingerprint(canonicalPath)
771
+ });
772
+ }
773
+
774
+ function validateChecklistScopeReceipt(receipt, expectedProjectPath) {
775
+ const payload = decodeReceipt(receipt, receiptKinds.checklist);
776
+ const canonicalExpectedPath = fs.realpathSync(expectedProjectPath);
777
+ return payload.project_path === canonicalExpectedPath
778
+ && projectSnapshotFingerprint(canonicalExpectedPath) === payload.project_fingerprint;
779
+ }
780
+
781
+ function a2mExecutionFacts(args) {
782
+ const projectPath = fs.realpathSync(required(args, 'project-path'));
783
+ if (!isReadableDirectory(projectPath)) throw new Error('A2M 联调项目路径不可访问');
784
+ const method = required(args, 'http-method').toUpperCase();
785
+ if (!['GET', 'POST'].includes(method)) throw new Error('http-method 仅支持 GET 或 POST');
786
+ let serviceUrl;
787
+ try {
788
+ serviceUrl = new URL(required(args, 'service-url'));
789
+ } catch {
790
+ throw new Error('service-url 不是合法 URL');
791
+ }
792
+ if (!['http:', 'https:'].includes(serviceUrl.protocol) || serviceUrl.username || serviceUrl.password || serviceUrl.hash) {
793
+ throw new Error('service-url 只允许不含凭据和 fragment 的 HTTP(S) URL');
794
+ }
795
+ let requestBodyHash = '';
796
+ const requestBodyFile = args['request-body-file'];
797
+ if (method === 'POST') {
798
+ if (!requestBodyFile || !path.isAbsolute(requestBodyFile)) throw new Error('POST 必须传入绝对 --request-body-file');
799
+ const stat = fs.lstatSync(requestBodyFile);
800
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('request-body-file 必须是普通文件且不能是符号链接');
801
+ requestBodyHash = crypto.createHash('sha256').update(fs.readFileSync(requestBodyFile)).digest('hex');
802
+ } else if (requestBodyFile !== undefined) {
803
+ throw new Error('GET 禁止传入 request-body-file');
804
+ }
805
+ return {
806
+ project_path: projectPath,
807
+ project_fingerprint: projectSnapshotFingerprint(projectPath),
808
+ service_url: serviceUrl.href,
809
+ http_method: method,
810
+ request_body_sha256: requestBodyHash
811
+ };
812
+ }
813
+
814
+ function a2mExecutionReceipt(facts, state = 'READY') {
815
+ return encodeReceipt({
816
+ schema_ver: 1,
817
+ kind: receiptKinds.a2mExecution,
818
+ state,
819
+ ...facts
820
+ });
821
+ }
822
+
823
+ export function guardA2mExecution(args) {
824
+ const facts = a2mExecutionFacts(args);
825
+ const priorReceipt = args['prior-receipt'];
826
+ if (!priorReceipt) return { decision: 'RUN', receipt: a2mExecutionReceipt(facts) };
827
+ const prior = decodeReceipt(priorReceipt, receiptKinds.a2mExecution);
828
+ const unchanged = ['project_path', 'project_fingerprint', 'service_url', 'http_method', 'request_body_sha256']
829
+ .every((key) => prior[key] === facts[key]);
830
+ if (!unchanged) return { decision: 'RUN', receipt: a2mExecutionReceipt(facts) };
831
+ if (prior.state === 'PASSED') return { decision: 'REUSE_PASSED', receipt: priorReceipt };
832
+ if (prior.state === 'FAILED') return { decision: 'BLOCK_UNCHANGED', receipt: priorReceipt };
833
+ return { decision: 'UNKNOWN_PREVIOUS_EXECUTION', receipt: priorReceipt };
834
+ }
835
+
836
+ export function completeA2mExecution(receipt, outcome) {
837
+ if (!['PASSED', 'FAILED', 'INTERRUPTED'].includes(outcome)) {
838
+ throw new Error('outcome 仅支持 PASSED、FAILED、INTERRUPTED');
839
+ }
840
+ const prior = decodeReceipt(receipt, receiptKinds.a2mExecution);
841
+ if (prior.state !== 'READY') throw new Error('A2M execution receipt 已经终态化');
842
+ const { schema_ver, kind, state, ...facts } = prior;
843
+ void schema_ver;
844
+ void kind;
845
+ void state;
846
+ return a2mExecutionReceipt(facts, outcome);
847
+ }
848
+
849
+ function runContextRequired(args) {
850
+ return renderMessage('integration.context.required', 'DEFAULT', {
851
+ missingItems: required(args, 'missing-items')
852
+ });
853
+ }
854
+
855
+ function runProductClarify() {
856
+ return renderMessage('product.clarify', 'DEFAULT', {});
857
+ }
858
+
859
+ function runWindowsSandboxSetup() {
860
+ return renderMessage('sandbox.windows.manual_setup', 'DEFAULT', {});
861
+ }
862
+
863
+ function runLocalAcceptanceNotice() {
864
+ return renderMessage('integration.local_acceptance.notice', 'DEFAULT', {});
865
+ }
866
+
867
+ function runA2mTestStart() {
868
+ return renderMessage('a2m.test.start', 'DEFAULT', {});
869
+ }
870
+
871
+ function runServiceNotReady(args) {
872
+ return renderMessage('integration.service.not_ready', 'DEFAULT', {
873
+ recoveryAction: required(args, 'recovery-action')
874
+ });
875
+ }
876
+
877
+ function runSafetyCloseout(args) {
878
+ const variant = required(args, 'variant');
879
+ if (!['AIPAY', 'WEBPAY', 'APPPAY'].includes(variant)) throw new Error('variant 非法,仅支持 AIPAY、WEBPAY、APPPAY');
880
+ return renderMessage('integration.safety.closeout', variant, {
881
+ acceptanceSummary: required(args, 'acceptance-summary')
882
+ });
883
+ }
884
+
885
+ function runWindowsSandboxSummary(args) {
886
+ const summary = renderMessage('sandbox.environment.summary', 'DEFAULT', {
887
+ productName: required(args, 'product-name'),
888
+ configPath: required(args, 'config-path'),
889
+ environmentRows: required(args, 'environment-rows')
890
+ });
891
+ const reminder = renderMessage('sandbox.environment.reminder', 'WINDOWS', {});
892
+ return `${summary}\n\n${reminder}`;
893
+ }
894
+
895
+ async function readStdin() {
896
+ let input = '';
897
+ for await (const chunk of process.stdin) input += chunk;
898
+ return input.trim() ? JSON.parse(input) : {};
899
+ }
900
+
901
+ function checklistInputFromArgs(args) {
902
+ const mapping = {
903
+ 'overall-result': 'overallResult',
904
+ 'failed-items': 'failedItems',
905
+ 'manual-items': 'manualItems',
906
+ 'sandbox-result': 'sandboxResult',
907
+ 'webpay-experience-result': 'webpayExperienceResult',
908
+ 'sandbox-config-state': 'sandboxConfigState',
909
+ 'blocking-defect-state': 'blockingDefectState',
910
+ 'execution-mode': 'executionMode',
911
+ 'next-flow-reminder': 'nextFlowReminder'
912
+ };
913
+ const hasTypedInput = Object.keys(mapping).some((name) => Object.prototype.hasOwnProperty.call(args, name));
914
+ if (!hasTypedInput) return null;
915
+
916
+ const input = {};
917
+ for (const [argName, fieldName] of Object.entries(mapping)) {
918
+ if (Object.prototype.hasOwnProperty.call(args, argName)) input[fieldName] = args[argName];
919
+ }
920
+ for (const requiredName of [
921
+ 'overall-result',
922
+ 'failed-items',
923
+ 'manual-items',
924
+ 'sandbox-config-state',
925
+ 'blocking-defect-state',
926
+ 'execution-mode',
927
+ 'next-flow-reminder'
928
+ ]) {
929
+ required(args, requiredName);
930
+ }
931
+ return input;
932
+ }
933
+
934
+ async function runChecklistResult(args) {
935
+ const variant = required(args, 'variant');
936
+ if (!['AIPAY', 'WEBPAY', 'APPPAY'].includes(variant)) throw new Error('variant 非法,仅支持 AIPAY、WEBPAY、APPPAY');
937
+ const scopeReceipt = required(args, 'scope-receipt');
938
+ const projectPath = required(args, 'project-path');
939
+ if (!validateChecklistScopeReceipt(scopeReceipt, projectPath)) {
940
+ throw new Error('项目在当前 checklist 开始后发生变化;必须重新进入 checklist 阶段并重跑受影响检查');
941
+ }
942
+ const input = checklistInputFromArgs(args) || await readStdin();
943
+ const rendered = renderMessage('integration.checklist.result', variant, input);
944
+ if (input.executionMode === '仅代码开发') {
945
+ emitTerminalMetric({ rootIntent: 'integration_only', productType: variant });
946
+ }
947
+ return rendered;
948
+ }
949
+
950
+ async function main() {
951
+ const { command, args } = parseArgs(process.argv);
952
+ if (command === 'resolve-project') {
953
+ console.log(runResolveProject(args));
954
+ return;
955
+ }
956
+ if (command === 'locate-projects') {
957
+ console.log(runLocateProjects(args));
958
+ return;
959
+ }
960
+ if (command === 'start-confirm') {
961
+ const result = createStartConfirmation(args);
962
+ console.log(result.message);
963
+ process.stderr.write(`${internalPrefix}PROJECT_CONFIRM_RECEIPT=${result.receipt}\n`);
964
+ return;
965
+ }
966
+ if (command === 'checklist-scope-receipt') {
967
+ process.stderr.write(`${internalPrefix}CHECKLIST_SCOPE_RECEIPT=${createChecklistScopeReceipt(required(args, 'project-path'))}\n`);
968
+ return;
969
+ }
970
+ if (command === 'a2m-execution-guard') {
971
+ process.stderr.write(`${internalPrefix}A2M_EXECUTION=${JSON.stringify(guardA2mExecution(args))}\n`);
972
+ return;
973
+ }
974
+ if (command === 'a2m-execution-result') {
975
+ const receipt = completeA2mExecution(required(args, 'receipt'), required(args, 'outcome'));
976
+ process.stderr.write(`${internalPrefix}A2M_EXECUTION=${JSON.stringify({ decision: 'RECORDED', receipt })}\n`);
977
+ return;
978
+ }
979
+ if (command === 'context-required') {
980
+ console.log(runContextRequired(args));
981
+ return;
982
+ }
983
+ if (command === 'product-clarify') {
984
+ console.log(runProductClarify());
985
+ return;
986
+ }
987
+ if (command === 'windows-sandbox-setup') {
988
+ console.log(runWindowsSandboxSetup());
989
+ return;
990
+ }
991
+ if (command === 'local-acceptance-notice') {
992
+ console.log(runLocalAcceptanceNotice());
993
+ return;
994
+ }
995
+ if (command === 'a2m-test-start') {
996
+ console.log(runA2mTestStart());
997
+ return;
998
+ }
999
+ if (command === 'service-not-ready') {
1000
+ console.log(runServiceNotReady(args));
1001
+ return;
1002
+ }
1003
+ if (command === 'safety-closeout') {
1004
+ console.log(runSafetyCloseout(args));
1005
+ return;
1006
+ }
1007
+ if (command === 'windows-sandbox-summary') {
1008
+ console.log(runWindowsSandboxSummary(args));
1009
+ return;
1010
+ }
1011
+ if (command === 'checklist-result') {
1012
+ console.log(await runChecklistResult(args));
1013
+ return;
1014
+ }
1015
+ throw new Error('用法: integration_message_runner.mjs <resolve-project|locate-projects|start-confirm|checklist-scope-receipt|a2m-execution-guard|a2m-execution-result|context-required|product-clarify|windows-sandbox-setup|local-acceptance-notice|a2m-test-start|service-not-ready|safety-closeout|windows-sandbox-summary|checklist-result> ...');
1016
+ }
1017
+
1018
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
1019
+ main().catch((error) => {
1020
+ console.error(`INTEGRATION_MESSAGE_ERROR:${error.message}`);
1021
+ process.exitCode = 1;
1022
+ });
1023
+ }