cli-jaw 2.2.25 → 2.3.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.
- package/README.md +5 -2
- package/dist/bin/cli-jaw.js +6 -1
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/doctor.js +39 -18
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +82 -23
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/messaging.js +262 -0
- package/dist/bin/commands/messaging.js.map +1 -0
- package/dist/server.js +30 -6
- package/dist/server.js.map +1 -1
- package/dist/src/cli/commands.js +6 -2
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers/remote-session-commands.js +94 -0
- package/dist/src/cli/handlers/remote-session-commands.js.map +1 -0
- package/dist/src/command-contract/catalog.js +1 -1
- package/dist/src/command-contract/catalog.js.map +1 -1
- package/dist/src/core/chat-sessions.js +4 -0
- package/dist/src/core/chat-sessions.js.map +1 -1
- package/dist/src/core/config.js +63 -22
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +6 -0
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/dispatch-approval-ingress.js +45 -3
- package/dist/src/core/dispatch-approval-ingress.js.map +1 -1
- package/dist/src/core/dispatch-approval.js +46 -0
- package/dist/src/core/dispatch-approval.js.map +1 -1
- package/dist/src/core/logger.js +67 -0
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/session-generation.js +74 -0
- package/dist/src/core/session-generation.js.map +1 -0
- package/dist/src/discord/bot.js +91 -5
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/commands.js +34 -1
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/discord/forwarder.js +4 -1
- package/dist/src/discord/forwarder.js.map +1 -1
- package/dist/src/discord/send-only-client.js +14 -7
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/messaging/access-policy.js +26 -0
- package/dist/src/messaging/access-policy.js.map +1 -0
- package/dist/src/messaging/approval-presentation.js +89 -0
- package/dist/src/messaging/approval-presentation.js.map +1 -0
- package/dist/src/messaging/channel-adapter.js +47 -0
- package/dist/src/messaging/channel-adapter.js.map +1 -0
- package/dist/src/messaging/channel-capabilities.js +47 -15
- package/dist/src/messaging/channel-capabilities.js.map +1 -1
- package/dist/src/messaging/channel-health.js +23 -3
- package/dist/src/messaging/channel-health.js.map +1 -1
- package/dist/src/messaging/delivery-outcome.js +12 -0
- package/dist/src/messaging/delivery-outcome.js.map +1 -1
- package/dist/src/messaging/durable-ingress.js +458 -0
- package/dist/src/messaging/durable-ingress.js.map +1 -0
- package/dist/src/messaging/effect-once.js +204 -0
- package/dist/src/messaging/effect-once.js.map +1 -0
- package/dist/src/messaging/inbound-envelope.js +168 -0
- package/dist/src/messaging/inbound-envelope.js.map +1 -0
- package/dist/src/messaging/ingress-audit.js +41 -0
- package/dist/src/messaging/ingress-audit.js.map +1 -0
- package/dist/src/messaging/ingress-generation.js +22 -0
- package/dist/src/messaging/ingress-generation.js.map +1 -0
- package/dist/src/messaging/metrics.js +69 -0
- package/dist/src/messaging/metrics.js.map +1 -0
- package/dist/src/messaging/outbound-outbox.js +184 -0
- package/dist/src/messaging/outbound-outbox.js.map +1 -0
- package/dist/src/messaging/remote-command-context.js +30 -0
- package/dist/src/messaging/remote-command-context.js.map +1 -0
- package/dist/src/messaging/retry.js +11 -2
- package/dist/src/messaging/retry.js.map +1 -1
- package/dist/src/messaging/runtime.js +129 -30
- package/dist/src/messaging/runtime.js.map +1 -1
- package/dist/src/messaging/send.js +31 -6
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/messaging/trace-context.js +56 -0
- package/dist/src/messaging/trace-context.js.map +1 -0
- package/dist/src/messaging/types.js +39 -1
- package/dist/src/messaging/types.js.map +1 -1
- package/dist/src/routes/orchestrate.js +10 -3
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/settings.js +26 -1
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/slack/bot.js +182 -70
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/commands.js +16 -1
- package/dist/src/slack/commands.js.map +1 -1
- package/dist/src/slack/format.js +4 -1
- package/dist/src/slack/format.js.map +1 -1
- package/dist/src/slack/manifest.js +5 -0
- package/dist/src/slack/manifest.js.map +1 -1
- package/dist/src/slack/send-handler.js +26 -4
- package/dist/src/slack/send-handler.js.map +1 -1
- package/dist/src/slack/send-only-client.js +26 -2
- package/dist/src/slack/send-only-client.js.map +1 -1
- package/dist/src/slack/socket.js +36 -13
- package/dist/src/slack/socket.js.map +1 -1
- package/dist/src/telegram/bot.js +122 -14
- package/dist/src/telegram/bot.js.map +1 -1
- package/package.json +4 -1
- package/public/dist/assets/ChannelEnablementControl-Da_t_jq6.js +1 -0
- package/public/dist/assets/ChannelsDiscord-kNjCCJcA.js +1 -0
- package/public/dist/assets/ChannelsSlack-BOuoCfeN.js +1 -0
- package/public/dist/assets/ChannelsTelegram-DBIFDz4R.js +1 -0
- package/public/dist/assets/{app-DUbf-v7B.js → app-TWwXw3iD.js} +4 -4
- package/public/dist/assets/bgtask-badge-BhzSqQHG.js +1 -0
- package/public/dist/assets/{bgtask-badge-jIH69Dp3.js → bgtask-badge-DaAUnoKD.js} +1 -1
- package/public/dist/assets/{employees-BNy8a6XX.js → employees-BFKkaVAe.js} +1 -1
- package/public/dist/assets/iframe-renderer-BxEzdKIA.js +1 -0
- package/public/dist/assets/{iframe-renderer-C6Zwg9w4.js → iframe-renderer-CvnKi77F.js} +2 -2
- package/public/dist/assets/{manager-DzCR6Pam.js → manager-BTpvAFwX.js} +1 -1
- package/public/dist/assets/memory-DHOtCiw5.js +1 -0
- package/public/dist/assets/{memory-C4O44HMr.js → memory-db_s-38T.js} +1 -1
- package/public/dist/assets/{message-history-DCxD21J1.js → message-history-BX9shNKA.js} +1 -1
- package/public/dist/assets/message-history-DaSxkyy0.js +1 -0
- package/public/dist/assets/{render-A--hkukv.js → render-DZnFK8Kv.js} +1 -1
- package/public/dist/assets/{settings-3ZdoKHIf.js → settings-BuFMKcQ-.js} +1 -1
- package/public/dist/assets/settings-DotRDITm.js +1 -0
- package/public/dist/assets/{settings-core-CQe1uP6x.js → settings-core-3fj0DVoa.js} +17 -17
- package/public/dist/assets/settings-core-DohSnMad.js +1 -0
- package/public/dist/assets/{sidebar-DxFp_449.js → sidebar-D5TP7TCO.js} +3 -3
- package/public/dist/assets/{skills-BU4q8Q8n.js → skills-CQzHFYJS.js} +1 -1
- package/public/dist/assets/skills-DZHEvl1x.js +1 -0
- package/public/dist/assets/{slash-commands-BiXOgZ5B.js → slash-commands-BWEWWf-c.js} +1 -1
- package/public/dist/assets/slash-commands-CuJdloNC.js +1 -0
- package/public/dist/assets/{trace-drawer-2BTQCor6.js → trace-drawer-CBR-PZB4.js} +1 -1
- package/public/dist/assets/ui-CWl27eMl.js +1 -0
- package/public/dist/assets/{ui-Csy42uNW.js → ui-Ca1Q-VFQ.js} +1 -1
- package/public/dist/index.html +1 -1
- package/public/dist/manager/index.html +1 -1
- package/public/js/features/settings-channel.ts +35 -5
- package/public/js/features/settings-types.ts +7 -0
- package/public/js/features/transport-status-row.ts +23 -4
- package/public/manager/src/settings/pages/ChannelsDiscord.tsx +39 -11
- package/public/manager/src/settings/pages/ChannelsSlack.tsx +24 -7
- package/public/manager/src/settings/pages/ChannelsTelegram.tsx +38 -10
- package/public/manager/src/settings/pages/components/ChannelEnablementControl.tsx +98 -0
- package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +23 -2
- package/scripts/docs/extract-commands.mts +3 -0
- package/scripts/generate-channel-capability-table.mts +161 -0
- package/scripts/promote-to-main.sh +175 -0
- package/scripts/release-gates.mjs +57 -3
- package/scripts/release-preview.sh +19 -0
- package/scripts/require-release-evidence.mjs +35 -1
- package/scripts/validate-messaging-certification.mts +83 -0
- package/scripts/write-messaging-certification.mts +65 -0
- package/public/dist/assets/ChannelsDiscord-D4fD-9hP.js +0 -1
- package/public/dist/assets/ChannelsSlack-_KBwRonM.js +0 -1
- package/public/dist/assets/ChannelsTelegram-tihzSWVE.js +0 -1
- package/public/dist/assets/TransportStatusChips-B0NR0ljO.js +0 -1
- package/public/dist/assets/bgtask-badge-B8KhuBa7.js +0 -1
- package/public/dist/assets/iframe-renderer-DBmV8gCQ.js +0 -1
- package/public/dist/assets/memory-Dt_Ji6aP.js +0 -1
- package/public/dist/assets/message-history-DyISauiv.js +0 -1
- package/public/dist/assets/settings-C6Pku2GC.js +0 -1
- package/public/dist/assets/settings-core-DqwLJkgP.js +0 -1
- package/public/dist/assets/skills-x2gH-1Ry.js +0 -1
- package/public/dist/assets/slash-commands-s8eR7Kd1.js +0 -1
- package/public/dist/assets/ui-CB0L33o0.js +0 -1
- package/scripts/release.sh +0 -224
- /package/public/dist/assets/{HealthBadge-CUaJ1PQH.js → HealthBadge-CiNMS-1k.js} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import type { SettingsClient } from '../../types';
|
|
3
3
|
|
|
4
|
+
export type MessengerChannel = 'telegram' | 'discord' | 'slack';
|
|
5
|
+
|
|
4
6
|
export type TransportStatus = {
|
|
5
7
|
configured: boolean;
|
|
6
8
|
activeInbound: boolean;
|
|
@@ -9,7 +11,9 @@ export type TransportStatus = {
|
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
export type ChannelHealth = {
|
|
12
|
-
|
|
14
|
+
/** @deprecated Use activeInboundChannels for the actual running set. */
|
|
15
|
+
activeInbound: MessengerChannel;
|
|
16
|
+
activeInboundChannels: MessengerChannel[];
|
|
13
17
|
telegram: TransportStatus;
|
|
14
18
|
discord: TransportStatus;
|
|
15
19
|
slack: TransportStatus;
|
|
@@ -23,13 +27,29 @@ function isTransportStatus(value: unknown): value is TransportStatus {
|
|
|
23
27
|
&& typeof row['sendCapable'] === 'boolean';
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
function isMessengerChannel(value: unknown): value is MessengerChannel {
|
|
31
|
+
return value === 'telegram' || value === 'discord' || value === 'slack';
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
export function parseChannelHealth(payload: unknown): ChannelHealth | null {
|
|
27
35
|
if (!payload || typeof payload !== 'object') return null;
|
|
28
36
|
const channels = (payload as { channels?: unknown }).channels;
|
|
29
37
|
if (!channels || typeof channels !== 'object') return null;
|
|
30
38
|
const row = channels as Record<string, unknown>;
|
|
39
|
+
const rawActiveChannels = row['activeInboundChannels'];
|
|
40
|
+
let activeInboundChannels: MessengerChannel[] | null;
|
|
41
|
+
if (rawActiveChannels !== undefined) {
|
|
42
|
+
activeInboundChannels = Array.isArray(rawActiveChannels)
|
|
43
|
+
&& rawActiveChannels.every(isMessengerChannel)
|
|
44
|
+
? [...new Set(rawActiveChannels)]
|
|
45
|
+
: null;
|
|
46
|
+
} else {
|
|
47
|
+
const legacyActive = row['activeInbound'];
|
|
48
|
+
activeInboundChannels = isMessengerChannel(legacyActive) ? [legacyActive] : null;
|
|
49
|
+
}
|
|
50
|
+
if (!activeInboundChannels) return null;
|
|
31
51
|
const active = row['activeInbound'];
|
|
32
|
-
if (active
|
|
52
|
+
if (!isMessengerChannel(active)) return null;
|
|
33
53
|
if (!isTransportStatus(row['telegram']) || !isTransportStatus(row['discord'])) return null;
|
|
34
54
|
// Slack is tolerated as absent: a newer Manager can be pointed at an older
|
|
35
55
|
// running cli-jaw, and rejecting the whole payload would hide Telegram and
|
|
@@ -39,6 +59,7 @@ export function parseChannelHealth(payload: unknown): ChannelHealth | null {
|
|
|
39
59
|
: { configured: false, activeInbound: false, sendCapable: false, reason: 'unavailable' };
|
|
40
60
|
return {
|
|
41
61
|
activeInbound: active,
|
|
62
|
+
activeInboundChannels,
|
|
42
63
|
telegram: row['telegram'],
|
|
43
64
|
discord: row['discord'],
|
|
44
65
|
slack,
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the messaging ChannelAdapter capability matrix inside
|
|
3
|
+
* `structure/CAPABILITY_TRUTH_TABLE.md`.
|
|
4
|
+
*
|
|
5
|
+
* node_modules/.bin/tsx scripts/generate-channel-capability-table.mts
|
|
6
|
+
* node_modules/.bin/tsx scripts/generate-channel-capability-table.mts --check
|
|
7
|
+
*
|
|
8
|
+
* Why a generator instead of a hand-kept table: the declaration in
|
|
9
|
+
* `src/messaging/channel-capabilities.ts` is the only thing the runtime obeys, so
|
|
10
|
+
* a hand-written doc can only ever be a second, quieter claim. `--check` is wired
|
|
11
|
+
* into `gate:truth-table-fresh`, which makes doc drift a release failure rather
|
|
12
|
+
* than a review-time nicety.
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import process from 'node:process';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
import { allChannelCapabilities, CHANNEL_CAPABILITY_KEYS } from '../src/messaging/channel-capabilities.ts';
|
|
20
|
+
import type { ChannelCapabilities } from '../src/messaging/channel-capabilities.ts';
|
|
21
|
+
import type { MessengerChannel } from '../src/messaging/types.ts';
|
|
22
|
+
|
|
23
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
24
|
+
|
|
25
|
+
const TABLE_REL = 'structure/CAPABILITY_TRUTH_TABLE.md';
|
|
26
|
+
const SOURCE_REL = 'src/messaging/channel-capabilities.ts';
|
|
27
|
+
const ADAPTER_REL = 'src/messaging/channel-adapter.ts';
|
|
28
|
+
const CONFORMANCE_REL = 'tests/unit/channel-contract-conformance.test.ts';
|
|
29
|
+
const GENERATOR_REL = 'scripts/generate-channel-capability-table.mts';
|
|
30
|
+
|
|
31
|
+
const START = '<!-- BEGIN GENERATED: messaging-channel-capabilities -->';
|
|
32
|
+
const END = '<!-- END GENERATED: messaging-channel-capabilities -->';
|
|
33
|
+
|
|
34
|
+
/** Prose for each capability key. Keyed by the closed set, so adding a capability
|
|
35
|
+
* without describing it is a compile error rather than an empty documentation cell. */
|
|
36
|
+
const CAPABILITY_NOTES: Record<keyof ChannelCapabilities, string> = {
|
|
37
|
+
sendText: '텍스트 전송',
|
|
38
|
+
editText: '전송된 메시지 수정',
|
|
39
|
+
deleteMessage: '전송된 메시지 삭제',
|
|
40
|
+
reaction: '리액션 부착',
|
|
41
|
+
typing: '입력 중 표시',
|
|
42
|
+
fileUpload: '파일 업로드',
|
|
43
|
+
voice: '음성 파일 전달 (녹음 UI 아님)',
|
|
44
|
+
threads: '스레드 타겟팅',
|
|
45
|
+
interactiveActions: '버튼 등 인터랙티브 액션',
|
|
46
|
+
durableIngress: '프로세스 재시작 후에도 유지되는 inbound 중복 제거',
|
|
47
|
+
replayableTransport: '미확인 프레임을 트랜스포트가 재전송',
|
|
48
|
+
maxMessageChars: '단일 메시지 문자 상한 (chunker 상수)',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function cell(value: boolean | number): string {
|
|
52
|
+
if (typeof value === 'number') return `\`${value.toLocaleString('en-US')}\``;
|
|
53
|
+
return value ? '✅' : '❌';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildBlock(): string {
|
|
57
|
+
const capabilities = allChannelCapabilities();
|
|
58
|
+
const channels = Object.keys(capabilities) as MessengerChannel[];
|
|
59
|
+
|
|
60
|
+
const lines: string[] = [];
|
|
61
|
+
lines.push('<!-- 이 블록은 생성됩니다. 손으로 고치지 마세요. -->');
|
|
62
|
+
lines.push(`<!-- 생성기: ${GENERATOR_REL} · 소스: ${SOURCE_REL} -->`);
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push(`> **생성된 블록입니다 — 직접 수정하지 마세요.** \`${SOURCE_REL}\`의`);
|
|
65
|
+
lines.push(`> 선언을 읽어 \`npm run docs:channel-capabilities\`가 다시 씁니다.`);
|
|
66
|
+
lines.push(`> 손으로 고친 내용은 \`gate:truth-table-fresh\`에서 drift로 실패합니다.`);
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push(`| Capability | ${channels.map((c) => c).join(' | ')} | 의미 |`);
|
|
69
|
+
lines.push(`| --- | ${channels.map(() => '---').join(' | ')} | --- |`);
|
|
70
|
+
for (const key of CHANNEL_CAPABILITY_KEYS) {
|
|
71
|
+
const values = channels.map((channel) => cell(capabilities[channel][key]));
|
|
72
|
+
lines.push(`| \`${key}\` | ${values.join(' | ')} | ${CAPABILITY_NOTES[key]} |`);
|
|
73
|
+
}
|
|
74
|
+
lines.push('');
|
|
75
|
+
lines.push('출처와 검증 지점:');
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push(`- 선언 (SoT): [\`${SOURCE_REL}\`](../${SOURCE_REL})`);
|
|
78
|
+
lines.push(`- 어댑터 계약: [\`${ADAPTER_REL}\`](../${ADAPTER_REL})`);
|
|
79
|
+
lines.push(`- conformance test: \`${CONFORMANCE_REL}\` — 이 스위트 통과가 \`true\` 선언의 유일한 근거`);
|
|
80
|
+
lines.push(`- 생성기: [\`${GENERATOR_REL}\`](../${GENERATOR_REL}) (\`--check\`는 \`gate:truth-table-fresh\`가 실행)`);
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push('`true`는 이 트리에서 오늘 호출 가능한 동작만을 뜻합니다. 벤더 SDK가 제공한다는');
|
|
83
|
+
lines.push('사실은 근거가 아닙니다 — conformance test가 통과할 때만 선언을 올립니다.');
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type Located = {
|
|
88
|
+
text: string;
|
|
89
|
+
startIdx: number;
|
|
90
|
+
endIdx: number;
|
|
91
|
+
committed: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function locate(text: string): Located {
|
|
95
|
+
const startIdx = text.indexOf(START);
|
|
96
|
+
const endIdx = text.indexOf(END);
|
|
97
|
+
if (startIdx < 0 || endIdx < 0 || endIdx < startIdx) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`${TABLE_REL} is missing the generated block markers.\n` +
|
|
100
|
+
` expected: ${START}\n ${END}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
text,
|
|
105
|
+
startIdx,
|
|
106
|
+
endIdx,
|
|
107
|
+
committed: text.slice(startIdx + START.length, endIdx),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** First differing line, so a failing gate points at the row instead of dumping both blocks. */
|
|
112
|
+
function firstDifference(committed: string, expected: string): string {
|
|
113
|
+
const a = committed.split('\n');
|
|
114
|
+
const b = expected.split('\n');
|
|
115
|
+
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
116
|
+
if (a[i] === b[i]) continue;
|
|
117
|
+
return [
|
|
118
|
+
` first difference at generated-block line ${i + 1}:`,
|
|
119
|
+
` committed: ${a[i] === undefined ? '(line missing)' : JSON.stringify(a[i])}`,
|
|
120
|
+
` expected: ${b[i] === undefined ? '(line missing)' : JSON.stringify(b[i])}`,
|
|
121
|
+
].join('\n');
|
|
122
|
+
}
|
|
123
|
+
return ' blocks differ in trailing bytes only';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function main(): number {
|
|
127
|
+
const check = process.argv.includes('--check');
|
|
128
|
+
const abs = path.join(repoRoot, TABLE_REL);
|
|
129
|
+
const original = fs.readFileSync(abs, 'utf8');
|
|
130
|
+
const located = locate(original);
|
|
131
|
+
const expected = `\n${buildBlock()}\n`;
|
|
132
|
+
|
|
133
|
+
if (check) {
|
|
134
|
+
if (located.committed === expected) {
|
|
135
|
+
console.log(`[ok] ${TABLE_REL} messaging capability matrix matches ${SOURCE_REL}`);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
console.error(
|
|
139
|
+
`[drift] ${TABLE_REL} messaging capability matrix does not match ${SOURCE_REL}.\n` +
|
|
140
|
+
`${firstDifference(located.committed, expected)}\n` +
|
|
141
|
+
` fix: npm run docs:channel-capabilities`,
|
|
142
|
+
);
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (located.committed === expected) {
|
|
147
|
+
console.log(`[unchanged] ${TABLE_REL}`);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
const next = original.slice(0, located.startIdx + START.length) + expected + original.slice(located.endIdx);
|
|
151
|
+
fs.writeFileSync(abs, next);
|
|
152
|
+
console.log(`[written] ${TABLE_REL} messaging capability matrix regenerated from ${SOURCE_REL}`);
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
process.exit(main());
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error(`[error] ${(err as Error).message}`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
cd "$(dirname "$0")/.."
|
|
5
|
+
|
|
6
|
+
command -v gh >/dev/null 2>&1 || { echo "ERROR: gh is required" >&2; exit 1; }
|
|
7
|
+
gh auth status >/dev/null
|
|
8
|
+
git fetch origin main preview --tags --prune
|
|
9
|
+
|
|
10
|
+
LIVE_PREVIEW_SHA="$(git rev-parse refs/remotes/origin/preview^{commit})"
|
|
11
|
+
PREVIEW_SHA="$(git rev-parse "${1:-$LIVE_PREVIEW_SHA}^{commit}")"
|
|
12
|
+
if [ "$PREVIEW_SHA" != "$LIVE_PREVIEW_SHA" ]; then
|
|
13
|
+
echo "ERROR: requested SHA is not the live origin/preview head" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
PREVIEW_VERSION="$(git show "$PREVIEW_SHA:package.json" \
|
|
18
|
+
| node -e 'const fs=require("node:fs"); process.stdout.write(JSON.parse(fs.readFileSync(0,"utf8")).version)')"
|
|
19
|
+
if [[ ! "$PREVIEW_VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-preview\.[0-9]+$ ]]; then
|
|
20
|
+
echo "ERROR: preview version must match X.Y.Z-preview.TIMESTAMP; got $PREVIEW_VERSION" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
STABLE_VERSION="${BASH_REMATCH[1]}"
|
|
24
|
+
|
|
25
|
+
TESTS_URL="$(gh run list \
|
|
26
|
+
--workflow test.yml \
|
|
27
|
+
--branch preview \
|
|
28
|
+
--commit "$PREVIEW_SHA" \
|
|
29
|
+
--event push \
|
|
30
|
+
--status success \
|
|
31
|
+
--limit 1 --json url --jq '.[0].url // ""')"
|
|
32
|
+
if [ -z "$TESTS_URL" ]; then
|
|
33
|
+
echo "ERROR: no successful Tests run for $PREVIEW_SHA on preview" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
MAIN_SHA="$(git rev-parse refs/remotes/origin/main^{commit})"
|
|
38
|
+
if ! git merge-base --is-ancestor "$MAIN_SHA" "$PREVIEW_SHA"; then
|
|
39
|
+
echo "ERROR: origin/main is not an ancestor of the certified preview SHA" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
WORKTREE="$(mktemp -d "${TMPDIR:-/tmp}/cli-jaw-promote.XXXXXX")"
|
|
44
|
+
PROMOTION_BRANCH="codex/promote-${STABLE_VERSION}-${PREVIEW_SHA:0:12}"
|
|
45
|
+
cleanup() {
|
|
46
|
+
git worktree remove --force "$WORKTREE" >/dev/null 2>&1 || true
|
|
47
|
+
rmdir "$WORKTREE" >/dev/null 2>&1 || true
|
|
48
|
+
}
|
|
49
|
+
trap cleanup EXIT
|
|
50
|
+
git worktree add -b "$PROMOTION_BRANCH" "$WORKTREE" "$PREVIEW_SHA"
|
|
51
|
+
|
|
52
|
+
(
|
|
53
|
+
cd "$WORKTREE"
|
|
54
|
+
npm version "$STABLE_VERSION" --no-git-tag-version --allow-same-version
|
|
55
|
+
node scripts/sync-electron-version.cjs
|
|
56
|
+
npm run gate:all
|
|
57
|
+
node scripts/require-release-evidence.mjs
|
|
58
|
+
git add package.json package-lock.json electron/package.json electron/package-lock.json
|
|
59
|
+
git commit -m "chore: promote v$STABLE_VERSION"
|
|
60
|
+
git push --set-upstream origin "$PROMOTION_BRANCH"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
PROMOTION_COMMIT="$(git -C "$WORKTREE" rev-parse HEAD)"
|
|
64
|
+
PR_URL="$(gh pr create \
|
|
65
|
+
--base main \
|
|
66
|
+
--head "$PROMOTION_BRANCH" \
|
|
67
|
+
--title "chore: promote v$STABLE_VERSION" \
|
|
68
|
+
--body "Promotes certified preview $PREVIEW_VERSION at $PREVIEW_SHA. Tests: $TESTS_URL")"
|
|
69
|
+
|
|
70
|
+
gh pr checks "$PR_URL" --required --watch --fail-fast
|
|
71
|
+
gh pr merge "$PR_URL" --squash --match-head-commit "$PROMOTION_COMMIT"
|
|
72
|
+
|
|
73
|
+
MERGED_AT="$(gh pr view "$PR_URL" --json mergedAt --jq '.mergedAt // ""')"
|
|
74
|
+
if [ -z "$MERGED_AT" ]; then
|
|
75
|
+
echo "ERROR: promotion PR did not merge" >&2
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
PR_MERGE_SHA="$(gh pr view "$PR_URL" --json mergeCommit --jq '.mergeCommit.oid // ""')"
|
|
79
|
+
if [ -z "$PR_MERGE_SHA" ]; then
|
|
80
|
+
echo "ERROR: promotion PR has no merge commit SHA" >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
git fetch origin main
|
|
85
|
+
MERGED_MAIN_SHA="$(git rev-parse refs/remotes/origin/main^{commit})"
|
|
86
|
+
LIVE_MAIN_SHA="$(git ls-remote origin refs/heads/main | cut -f1)"
|
|
87
|
+
if [ -z "$LIVE_MAIN_SHA" ] || [ "$MERGED_MAIN_SHA" != "$LIVE_MAIN_SHA" ] \
|
|
88
|
+
|| [ "$MERGED_MAIN_SHA" != "$PR_MERGE_SHA" ]; then
|
|
89
|
+
echo "ERROR: merged PR SHA, origin/main, and live main head do not match" >&2
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
MERGED_VERSION="$(git show "$MERGED_MAIN_SHA:package.json" \
|
|
93
|
+
| node -e 'const fs=require("node:fs"); process.stdout.write(JSON.parse(fs.readFileSync(0,"utf8")).version)')"
|
|
94
|
+
if [ "$MERGED_VERSION" != "$STABLE_VERSION" ]; then
|
|
95
|
+
echo "ERROR: merged main version is $MERGED_VERSION, expected $STABLE_VERSION" >&2
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
deadline=$((SECONDS + 1200))
|
|
100
|
+
MAIN_TESTS_URL=""
|
|
101
|
+
while [ "$SECONDS" -lt "$deadline" ]; do
|
|
102
|
+
MAIN_TESTS_URL="$(gh run list \
|
|
103
|
+
--workflow test.yml \
|
|
104
|
+
--branch main \
|
|
105
|
+
--commit "$MERGED_MAIN_SHA" \
|
|
106
|
+
--event push \
|
|
107
|
+
--status success \
|
|
108
|
+
--limit 1 --json url --jq '.[0].url // ""')"
|
|
109
|
+
[ -n "$MAIN_TESTS_URL" ] && break
|
|
110
|
+
failed="$(gh run list \
|
|
111
|
+
--workflow test.yml \
|
|
112
|
+
--branch main \
|
|
113
|
+
--commit "$MERGED_MAIN_SHA" \
|
|
114
|
+
--event push \
|
|
115
|
+
--status completed \
|
|
116
|
+
--limit 1 --json conclusion --jq '.[0].conclusion // ""')"
|
|
117
|
+
case "$failed" in
|
|
118
|
+
failure|cancelled|timed_out|startup_failure|action_required)
|
|
119
|
+
echo "ERROR: main Tests completed with $failed" >&2
|
|
120
|
+
exit 1
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
sleep 10
|
|
124
|
+
done
|
|
125
|
+
if [ -z "$MAIN_TESTS_URL" ]; then
|
|
126
|
+
echo "ERROR: timed out waiting for successful main Tests" >&2
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
deadline=$((SECONDS + 1200))
|
|
131
|
+
MAIN_PLATFORM_URL=""
|
|
132
|
+
while [ "$SECONDS" -lt "$deadline" ]; do
|
|
133
|
+
MAIN_PLATFORM_URL="$(gh run list \
|
|
134
|
+
--workflow postinstall-platform.yml \
|
|
135
|
+
--branch main \
|
|
136
|
+
--commit "$MERGED_MAIN_SHA" \
|
|
137
|
+
--event push \
|
|
138
|
+
--status success \
|
|
139
|
+
--limit 1 --json url --jq '.[0].url // ""')"
|
|
140
|
+
[ -n "$MAIN_PLATFORM_URL" ] && break
|
|
141
|
+
failed="$(gh run list \
|
|
142
|
+
--workflow postinstall-platform.yml \
|
|
143
|
+
--branch main \
|
|
144
|
+
--commit "$MERGED_MAIN_SHA" \
|
|
145
|
+
--event push \
|
|
146
|
+
--status completed \
|
|
147
|
+
--limit 1 --json conclusion --jq '.[0].conclusion // ""')"
|
|
148
|
+
case "$failed" in
|
|
149
|
+
failure|cancelled|timed_out|startup_failure|action_required)
|
|
150
|
+
echo "ERROR: main Postinstall Platform Checks completed with $failed" >&2
|
|
151
|
+
exit 1
|
|
152
|
+
;;
|
|
153
|
+
esac
|
|
154
|
+
sleep 10
|
|
155
|
+
done
|
|
156
|
+
if [ -z "$MAIN_PLATFORM_URL" ]; then
|
|
157
|
+
echo "ERROR: timed out waiting for successful main Postinstall Platform Checks" >&2
|
|
158
|
+
exit 1
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
LIVE_MAIN_SHA="$(git ls-remote origin refs/heads/main | cut -f1)"
|
|
162
|
+
if [ "$LIVE_MAIN_SHA" != "$MERGED_MAIN_SHA" ]; then
|
|
163
|
+
echo "ERROR: origin/main moved while waiting for release CI" >&2
|
|
164
|
+
exit 1
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
gh workflow run publish.yml \
|
|
168
|
+
--ref main \
|
|
169
|
+
-f version="$STABLE_VERSION" \
|
|
170
|
+
-f tag=latest \
|
|
171
|
+
-f expected-sha="$MERGED_MAIN_SHA" \
|
|
172
|
+
-f dry-run=false \
|
|
173
|
+
-f create-github-release=true
|
|
174
|
+
|
|
175
|
+
echo "stable publish dispatched: cli-jaw@$STABLE_VERSION from $MERGED_MAIN_SHA"
|
|
@@ -88,14 +88,24 @@ const GATES = {
|
|
|
88
88
|
},
|
|
89
89
|
},
|
|
90
90
|
'truth-table-fresh': {
|
|
91
|
-
description: 'CAPABILITY_TRUTH_TABLE.md edited within 7 days OR matches code refs',
|
|
91
|
+
description: 'CAPABILITY_TRUTH_TABLE.md edited within 7 days OR matches code refs, AND its generated messaging channel matrix matches src/messaging/channel-capabilities.ts',
|
|
92
92
|
check() {
|
|
93
93
|
const rel = 'structure/CAPABILITY_TRUTH_TABLE.md';
|
|
94
94
|
const abs = path.join(repoRoot, rel);
|
|
95
95
|
if (!fs.existsSync(abs)) return { ok: false, detail: `${rel} missing` };
|
|
96
|
+
// The generated messaging block first: a stale matrix is a wrong claim
|
|
97
|
+
// regardless of the file's mtime, so freshness must not excuse it.
|
|
98
|
+
// node_modules/.bin/tsx directly — npx resolution is not a gate dependency.
|
|
99
|
+
const generated = run(path.join('node_modules', '.bin', 'tsx'), [
|
|
100
|
+
'scripts/generate-channel-capability-table.mts', '--check',
|
|
101
|
+
], { timeout: 60_000 });
|
|
102
|
+
if (generated.status !== 0) {
|
|
103
|
+
const detail = [generated.stderr, generated.stdout].filter(Boolean).join('\n').trim();
|
|
104
|
+
return { ok: false, detail: `messaging capability matrix drift:\n${detail || `tsx exited with ${generated.status}`}` };
|
|
105
|
+
}
|
|
96
106
|
const stat = fs.statSync(abs);
|
|
97
107
|
const ageDays = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24);
|
|
98
|
-
if (ageDays <= 7) return { ok: true, detail: `truth table ${ageDays.toFixed(2)}d old` };
|
|
108
|
+
if (ageDays <= 7) return { ok: true, detail: `truth table ${ageDays.toFixed(2)}d old; messaging matrix in sync` };
|
|
99
109
|
const text = readFile(rel);
|
|
100
110
|
const required = ['action-intent', 'target-resolver', 'answer-artifact', 'source-audit'];
|
|
101
111
|
for (const term of required) {
|
|
@@ -103,7 +113,7 @@ const GATES = {
|
|
|
103
113
|
return { ok: false, detail: `truth table stale (${ageDays.toFixed(1)}d) and missing ${term}` };
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
|
-
return { ok: true, detail: `truth table ${ageDays.toFixed(1)}d old but matches required terms` };
|
|
116
|
+
return { ok: true, detail: `truth table ${ageDays.toFixed(1)}d old but matches required terms; messaging matrix in sync` };
|
|
107
117
|
},
|
|
108
118
|
},
|
|
109
119
|
'mcp-scope-frozen': {
|
|
@@ -474,6 +484,50 @@ const GATES = {
|
|
|
474
484
|
return { ok: true, detail: 'install-integrity + scriptless contract suites green' };
|
|
475
485
|
},
|
|
476
486
|
},
|
|
487
|
+
'messaging-conformance': {
|
|
488
|
+
description: 'Telegram/Slack/Discord durable ingress and command contract suites + functional certification artifact',
|
|
489
|
+
check() {
|
|
490
|
+
const publicDocs = ['README.md', 'README.ko.md', 'README.ja.md', 'README.zh-CN.md', 'AGENTS.md'];
|
|
491
|
+
for (const rel of publicDocs) {
|
|
492
|
+
const abs = path.join(repoRoot, rel);
|
|
493
|
+
if (!fs.existsSync(abs)) continue;
|
|
494
|
+
const text = fs.readFileSync(abs, 'utf8');
|
|
495
|
+
if (/exactly[- ]once/i.test(text)) {
|
|
496
|
+
return { ok: false, detail: `${rel} claims exactly-once` };
|
|
497
|
+
}
|
|
498
|
+
if (/release-certified/i.test(text)) {
|
|
499
|
+
return { ok: false, detail: `${rel} claims release-certified` };
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const suites = [
|
|
503
|
+
'tests/unit/channel-contract-conformance.test.ts',
|
|
504
|
+
'tests/unit/messaging-command-parity.test.ts',
|
|
505
|
+
'tests/unit/messaging-durable-ingress.test.ts',
|
|
506
|
+
'tests/unit/telegram-ingress-journal.test.ts',
|
|
507
|
+
'tests/unit/discord-ingress-journal.test.ts',
|
|
508
|
+
'tests/unit/ingress-generation.test.ts',
|
|
509
|
+
'tests/unit/messaging-trace-context.test.ts',
|
|
510
|
+
'tests/unit/messaging-metrics.test.ts',
|
|
511
|
+
];
|
|
512
|
+
const missing = suites.filter((rel) => !fs.existsSync(path.join(repoRoot, rel)));
|
|
513
|
+
if (missing.length > 0) {
|
|
514
|
+
return { ok: false, detail: `channel fixture missing: ${missing.join(', ')}` };
|
|
515
|
+
}
|
|
516
|
+
const tests = run('npx', ['tsx', '--experimental-test-module-mocks', 'tests/run.mts', ...suites], { timeout: 180_000 });
|
|
517
|
+
if (tests.status !== 0) {
|
|
518
|
+
return { ok: false, detail: [tests.stdout, tests.stderr].filter(Boolean).join('\n').trim().slice(-800) };
|
|
519
|
+
}
|
|
520
|
+
const written = run(path.join('node_modules', '.bin', 'tsx'), ['scripts/write-messaging-certification.mts'], { timeout: 30_000 });
|
|
521
|
+
if (written.status !== 0) {
|
|
522
|
+
return { ok: false, detail: [written.stdout, written.stderr].filter(Boolean).join('\n').trim().slice(-800) };
|
|
523
|
+
}
|
|
524
|
+
const validated = run(path.join('node_modules', '.bin', 'tsx'), ['scripts/validate-messaging-certification.mts'], { timeout: 30_000 });
|
|
525
|
+
if (validated.status !== 0) {
|
|
526
|
+
return { ok: false, detail: [validated.stdout, validated.stderr].filter(Boolean).join('\n').trim().slice(-800) };
|
|
527
|
+
}
|
|
528
|
+
return { ok: true, detail: 'three-channel messaging suites green; functional-certified artifact valid' };
|
|
529
|
+
},
|
|
530
|
+
},
|
|
477
531
|
'gate-docs': {
|
|
478
532
|
description: 'structure/INDEX.md documents exactly the gates that exist, and each is npm-addressable',
|
|
479
533
|
check() {
|
|
@@ -15,6 +15,11 @@ set -euo pipefail
|
|
|
15
15
|
|
|
16
16
|
cd "$(dirname "$0")/.."
|
|
17
17
|
|
|
18
|
+
if ! command -v gh &>/dev/null; then
|
|
19
|
+
echo "❌ gh CLI is required but not found in PATH."
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
18
23
|
# ─── Flag parsing ──────────────────────────────────────
|
|
19
24
|
BUMP_KIND="patch"
|
|
20
25
|
REQUIRE_EVIDENCE=false
|
|
@@ -203,6 +208,20 @@ else
|
|
|
203
208
|
git push origin HEAD:preview
|
|
204
209
|
fi
|
|
205
210
|
|
|
211
|
+
# ─── Dispatch publish workflow ─────────────────────────
|
|
212
|
+
RELEASE_SHA="$(git rev-parse HEAD)"
|
|
213
|
+
LIVE_SHA="$(git ls-remote origin refs/heads/preview | cut -f1)"
|
|
214
|
+
|
|
215
|
+
if [ -z "$LIVE_SHA" ] || [ "$LIVE_SHA" != "$RELEASE_SHA" ]; then
|
|
216
|
+
echo "❌ origin/preview moved after push (expected $RELEASE_SHA, got ${LIVE_SHA:-'(empty)'}). Aborting."
|
|
217
|
+
exit 1
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
gh workflow run publish.yml --ref preview \
|
|
221
|
+
-f version="$VERSION" -f tag=preview -f expected-sha="$RELEASE_SHA" -f dry-run=false
|
|
222
|
+
|
|
223
|
+
echo "🚀 publish.yml workflow dispatched for preview v$VERSION (sha $RELEASE_SHA)"
|
|
224
|
+
|
|
206
225
|
# ─── GitHub Prerelease stub ────────────────────────────
|
|
207
226
|
echo "📋 Creating/updating GitHub prerelease stub..."
|
|
208
227
|
RELEASE_BODY="## Preview Release v$VERSION
|
|
@@ -27,7 +27,7 @@ const installerSensitivePaths = [
|
|
|
27
27
|
'scripts/audit-fresh-install-evidence.mjs',
|
|
28
28
|
'scripts/verify-release-evidence.mjs',
|
|
29
29
|
'scripts/require-release-evidence.mjs',
|
|
30
|
-
'scripts/
|
|
30
|
+
'scripts/promote-to-main.sh',
|
|
31
31
|
'scripts/release-preview.sh',
|
|
32
32
|
'tests/unit/cli-detect.test.ts',
|
|
33
33
|
'tests/unit/install-sh-exec.test.ts',
|
|
@@ -46,6 +46,9 @@ function usage() {
|
|
|
46
46
|
|
|
47
47
|
Options:
|
|
48
48
|
--base-ref REF Compare installer-sensitive files against REF instead of the latest v* tag.
|
|
49
|
+
--changed-files-stdin
|
|
50
|
+
Read changed file paths from stdin and check whether any are installer-sensitive.
|
|
51
|
+
Cannot be combined with --base-ref.
|
|
49
52
|
-h, --help Show this help.
|
|
50
53
|
|
|
51
54
|
If installer-sensitive files changed, this script requires:
|
|
@@ -57,6 +60,7 @@ Then it runs scripts/verify-release-evidence.mjs before publish/release can cont
|
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
let baseRef = '';
|
|
63
|
+
let changedFilesStdin = false;
|
|
60
64
|
const args = process.argv.slice(2);
|
|
61
65
|
for (let i = 0; i < args.length; i += 1) {
|
|
62
66
|
const arg = args[i];
|
|
@@ -64,6 +68,10 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
64
68
|
usage();
|
|
65
69
|
process.exit(0);
|
|
66
70
|
}
|
|
71
|
+
if (arg === '--changed-files-stdin') {
|
|
72
|
+
changedFilesStdin = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
67
75
|
if (arg === '--base-ref') {
|
|
68
76
|
baseRef = args[++i] || '';
|
|
69
77
|
if (!baseRef) throw new Error('missing value for --base-ref');
|
|
@@ -72,6 +80,18 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
72
80
|
throw new Error(`unknown option: ${arg}`);
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
if (baseRef && changedFilesStdin) {
|
|
84
|
+
throw new Error('--changed-files-stdin cannot be combined with --base-ref');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function readChangedFilesFromStdin() {
|
|
88
|
+
const input = fs.readFileSync(0, 'utf8');
|
|
89
|
+
return input
|
|
90
|
+
.split(/\r?\n/)
|
|
91
|
+
.map((line) => line.replace(/\r$/, '').trim())
|
|
92
|
+
.filter((line) => line.length > 0);
|
|
93
|
+
}
|
|
94
|
+
|
|
75
95
|
function git(args, options = {}) {
|
|
76
96
|
return spawnSync('git', args, {
|
|
77
97
|
cwd: repoRoot,
|
|
@@ -175,6 +195,20 @@ function shortHash(files) {
|
|
|
175
195
|
return crypto.createHash('sha256').update(files.join('\n')).digest('hex').slice(0, 12);
|
|
176
196
|
}
|
|
177
197
|
|
|
198
|
+
if (changedFilesStdin) {
|
|
199
|
+
const inputFiles = readChangedFilesFromStdin();
|
|
200
|
+
const matched = inputFiles.filter((file) => installerSensitivePaths.includes(file));
|
|
201
|
+
if (matched.length === 0) {
|
|
202
|
+
console.log('[release-evidence-required] SKIP no installer-sensitive paths in stdin');
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
console.error(`[release-evidence-required] CHANGED installer-sensitive files (${matched.length}, ${shortHash(matched)})`);
|
|
206
|
+
for (const file of matched) {
|
|
207
|
+
console.error(`- ${file}`);
|
|
208
|
+
}
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
|
|
178
212
|
if (!isGitRepo()) {
|
|
179
213
|
console.error('[release-evidence-required] FAIL');
|
|
180
214
|
console.error('- Cannot determine installer-sensitive changes outside a git checkout.');
|