





























1+#!/usr/bin/env node
2+3+import path from "node:path";
4+import ts from "typescript";
5+import {
6+collectFileViolations,
7+resolveRepoRoot,
8+resolveSourceRoots,
9+runAsScript,
10+toLine,
11+unwrapExpression,
12+} from "./lib/ts-guard-utils.mjs";
13+14+const legacyTranscriptReaderModules = new Set([
15+"../gateway/session-utils.js",
16+"../gateway/session-utils.fs.js",
17+"../../gateway/session-utils.js",
18+"../../gateway/session-utils.fs.js",
19+"./session-utils.js",
20+"./session-utils.fs.js",
21+"../session-utils.js",
22+"../session-utils.fs.js",
23+]);
24+25+const transcriptReaderNames = new Set([
26+"attachOpenClawTranscriptMeta",
27+"capArrayByJsonBytes",
28+"readFirstUserMessageFromTranscript",
29+"readLatestRecentSessionUsageFromTranscriptAsync",
30+"readLatestSessionUsageFromTranscript",
31+"readLatestSessionUsageFromTranscriptAsync",
32+"readRecentSessionMessages",
33+"readRecentSessionMessagesAsync",
34+"readRecentSessionMessagesWithStats",
35+"readRecentSessionMessagesWithStatsAsync",
36+"readRecentSessionTranscriptLines",
37+"readRecentSessionUsageFromTranscript",
38+"readRecentSessionUsageFromTranscriptAsync",
39+"readSessionMessageByIdAsync",
40+"readSessionMessageCount",
41+"readSessionMessageCountAsync",
42+"readSessionMessages",
43+"readSessionMessagesAsync",
44+"readSessionMessagesWithSourceAsync",
45+"readSessionPreviewItemsFromTranscript",
46+"readSessionTitleFieldsFromTranscript",
47+"readSessionTitleFieldsFromTranscriptAsync",
48+"visitSessionMessages",
49+"visitSessionMessagesAsync",
50+]);
51+52+export const migratedSessionTranscriptReaderFiles = new Set([
53+"src/agents/main-session-restart-recovery.ts",
54+"src/agents/subagent-announce-output.test.ts",
55+"src/agents/subagent-announce-output.ts",
56+"src/agents/subagent-announce.runtime.ts",
57+"src/agents/subagent-orphan-recovery.test.ts",
58+"src/agents/subagent-orphan-recovery.ts",
59+"src/agents/tools/embedded-gateway-stub.runtime.ts",
60+"src/agents/tools/embedded-gateway-stub.test.ts",
61+"src/agents/tools/embedded-gateway-stub.ts",
62+"src/agents/tools/sessions-history-tool.ts",
63+"src/agents/tools/sessions-list-tool.ts",
64+"src/gateway/cli-session-history.claude.ts",
65+"src/gateway/gateway-models.profiles.live.test.ts",
66+"src/gateway/managed-image-attachments.test.ts",
67+"src/gateway/managed-image-attachments.ts",
68+"src/gateway/server-methods/artifacts.test.ts",
69+"src/gateway/server-methods/artifacts.ts",
70+"src/gateway/server-methods/chat.ts",
71+"src/gateway/server-methods/sessions-files.test.ts",
72+"src/gateway/server-methods/sessions-files.ts",
73+"src/gateway/server-methods/sessions.ts",
74+"src/gateway/server-session-events.ts",
75+"src/gateway/session-history-state.test.ts",
76+"src/gateway/session-history-state.ts",
77+"src/gateway/session-reset-service.ts",
78+"src/gateway/session-utils.ts",
79+"src/gateway/sessions-history-http.revocation.test.ts",
80+"src/gateway/sessions-history-http.ts",
81+"src/status/status-message.ts",
82+"src/tui/embedded-backend.test.ts",
83+"src/tui/embedded-backend.ts",
84+]);
85+86+function normalizeRelativePath(filePath) {
87+return filePath.replaceAll(path.sep, "/");
88+}
89+90+function importedModuleName(node) {
91+return node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)
92+ ? node.moduleSpecifier.text
93+ : null;
94+}
95+96+function bindingName(node) {
97+if (node.propertyName && ts.isIdentifier(node.propertyName)) {
98+return node.propertyName.text;
99+}
100+if (ts.isIdentifier(node.name)) {
101+return node.name.text;
102+}
103+return null;
104+}
105+106+function destructuresLegacyNamespace(node, legacyNamespaces) {
107+const pattern = node.parent;
108+const declaration = pattern?.parent;
109+if (
110+!pattern ||
111+!ts.isObjectBindingPattern(pattern) ||
112+!declaration ||
113+!ts.isVariableDeclaration(declaration) ||
114+!declaration.initializer
115+) {
116+return false;
117+}
118+119+const initializer = unwrapExpression(declaration.initializer);
120+return ts.isIdentifier(initializer) && legacyNamespaces.has(initializer.text);
121+}
122+123+export function findSessionTranscriptReaderBoundaryViolations(content, fileName = "source.ts") {
124+const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
125+const violations = [];
126+const legacyNamespaces = new Set();
127+128+const visit = (node) => {
129+if (ts.isImportDeclaration(node)) {
130+const moduleName = importedModuleName(node);
131+const namedBindings = node.importClause?.namedBindings;
132+if (moduleName && legacyTranscriptReaderModules.has(moduleName) && namedBindings) {
133+if (ts.isNamedImports(namedBindings)) {
134+for (const specifier of namedBindings.elements) {
135+const importedName = specifier.propertyName?.text ?? specifier.name.text;
136+if (transcriptReaderNames.has(importedName)) {
137+violations.push({
138+line: toLine(sourceFile, specifier),
139+reason: `imports transcript reader "${importedName}" from legacy module "${moduleName}"`,
140+});
141+}
142+}
143+} else if (ts.isNamespaceImport(namedBindings)) {
144+legacyNamespaces.add(namedBindings.name.text);
145+}
146+}
147+}
148+149+if (ts.isExportDeclaration(node)) {
150+const moduleName = importedModuleName(node);
151+if (moduleName && legacyTranscriptReaderModules.has(moduleName)) {
152+const exportClause = node.exportClause;
153+if (!exportClause) {
154+violations.push({
155+line: toLine(sourceFile, node),
156+reason: `re-exports transcript readers from legacy module "${moduleName}"`,
157+});
158+} else if (ts.isNamedExports(exportClause)) {
159+for (const specifier of exportClause.elements) {
160+const exportedName = specifier.propertyName?.text ?? specifier.name.text;
161+if (transcriptReaderNames.has(exportedName)) {
162+violations.push({
163+line: toLine(sourceFile, specifier),
164+reason: `re-exports transcript reader "${exportedName}" from legacy module "${moduleName}"`,
165+});
166+}
167+}
168+} else if (ts.isNamespaceExport(exportClause)) {
169+violations.push({
170+line: toLine(sourceFile, exportClause),
171+reason: `re-exports transcript reader namespace from legacy module "${moduleName}"`,
172+});
173+}
174+}
175+}
176+177+if (ts.isBindingElement(node)) {
178+const name = bindingName(node);
179+if (
180+name &&
181+transcriptReaderNames.has(name) &&
182+destructuresLegacyNamespace(node, legacyNamespaces)
183+) {
184+violations.push({
185+line: toLine(sourceFile, node),
186+reason: `aliases legacy transcript reader "${name}"`,
187+});
188+}
189+}
190+191+if (ts.isPropertyAccessExpression(node)) {
192+const receiver = unwrapExpression(node.expression);
193+if (
194+ts.isIdentifier(receiver) &&
195+legacyNamespaces.has(receiver.text) &&
196+transcriptReaderNames.has(node.name.text)
197+) {
198+violations.push({
199+line: toLine(sourceFile, node.name),
200+reason: `references legacy transcript reader "${node.name.text}"`,
201+});
202+}
203+}
204+205+if (
206+ts.isElementAccessExpression(node) &&
207+ts.isIdentifier(unwrapExpression(node.expression)) &&
208+legacyNamespaces.has(unwrapExpression(node.expression).text) &&
209+ts.isStringLiteral(node.argumentExpression) &&
210+transcriptReaderNames.has(node.argumentExpression.text)
211+) {
212+violations.push({
213+line: toLine(sourceFile, node.argumentExpression),
214+reason: `references legacy transcript reader "${node.argumentExpression.text}"`,
215+});
216+}
217+218+ts.forEachChild(node, visit);
219+};
220+221+visit(sourceFile);
222+return violations;
223+}
224+225+export async function main() {
226+const repoRoot = resolveRepoRoot(import.meta.url);
227+const sourceRoots = resolveSourceRoots(repoRoot, [
228+"src/agents",
229+"src/gateway",
230+"src/status",
231+"src/tui",
232+]);
233+const violations = await collectFileViolations({
234+ repoRoot,
235+ sourceRoots,
236+includeTests: true,
237+skipFile: (filePath) =>
238+!migratedSessionTranscriptReaderFiles.has(
239+normalizeRelativePath(path.relative(repoRoot, filePath)),
240+),
241+findViolations: findSessionTranscriptReaderBoundaryViolations,
242+});
243+244+if (violations.length === 0) {
245+console.log("session transcript reader boundary guard passed.");
246+return;
247+}
248+249+console.error("Found legacy transcript reader usage in migrated files:");
250+for (const violation of violations) {
251+console.error(`- ${violation.path}:${violation.line}: ${violation.reason}`);
252+}
253+console.error(
254+"Use src/gateway/session-transcript-readers.ts for migrated transcript reader paths. Expand this ratchet only after a slice migrates more files.",
255+);
256+process.exit(1);
257+}
258+259+runAsScript(import.meta.url, main);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。