Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8822672
perf(cli): let tests resolve core modules individually, and stop two …
yiliang114 Sep 3, 2026
8b3d0b0
fix(cli): restore the named core subpaths the previous commit dropped
yiliang114 Sep 3, 2026
a1a84a2
Merge branch 'main' into perf/core-subpath-imports
yiliang114 Sep 3, 2026
028826b
perf(cli): import core modules directly where no test mocks the package
yiliang114 Sep 3, 2026
0e47663
fix(cli): point ProviderModelConfig at the module that declares it, a…
yiliang114 Sep 3, 2026
2e7b7eb
perf(cli): import core modules directly in another 114 files
yiliang114 Sep 3, 2026
bd41633
test(cli): move three barrel mocks onto the modules they actually stub
yiliang114 Sep 3, 2026
fa54b2b
Revert the migrated modules that any failing suite depends on
yiliang114 Sep 3, 2026
6626ccb
fix(core): let consumers outside the cli package resolve a core modul…
yiliang114 Sep 3, 2026
cb09221
fix(cli): repair the mock pairs the revert split, and keep dev on source
yiliang114 Sep 4, 2026
c41c2d4
Cover the exports catch-all, and stop it hiding from the architecture…
yiliang114 Sep 4, 2026
b3d5443
fix(cli): point the session-picker branch mock at the module it imports
yiliang114 Sep 4, 2026
9168b14
chore: merge main into perf/core-subpath-imports-batch3
yiliang114 Sep 4, 2026
af5f02d
fix(dev): preserve source-backed core entry paths
yiliang114 Sep 4, 2026
ccbc6ac
fix(scripts): close core subpath verification gaps
yiliang114 Sep 4, 2026
a18ffeb
fix(scripts): gate dep-only core subpath exports in the check
yiliang114 Sep 4, 2026
03886a4
docs(harness): note terminal-capture loader is manually exercised
yiliang114 Sep 4, 2026
647e97f
fix(scripts): reject core subpath targets outside the published dist
yiliang114 Sep 4, 2026
18aa484
Merge origin/main into perf/core-subpath-imports-batch3
yiliang114 Sep 4, 2026
c10be7f
fix(scripts): allow published core package.json in subpath gate, cove…
yiliang114 Sep 4, 2026
47128e7
fix(terminal-capture): add conversationsRuntimeMarker to harness load…
yiliang114 Sep 4, 2026
30ddedb
fix(integration-tests): map conversationsRuntimeMarker to core source…
yiliang114 Sep 5, 2026
b235256
test(scripts): assert loader map values and reverse key-set against c…
yiliang114 Sep 5, 2026
fedf053
test(scripts): make the exact-key-precedence fixture verdict-sensitive
yiliang114 Sep 5, 2026
cb9ee87
docs(cli): correct the subpath-alias and mock-wiring comments
yiliang114 Sep 5, 2026
508b002
Merge origin/main into perf/core-subpath-imports-batch3
yiliang114 Sep 5, 2026
6c9d2bf
Merge origin/main into perf/core-subpath-imports-batch3
yiliang114 Sep 5, 2026
da0498b
Merge origin/main into perf/core-subpath-imports-batch3
yiliang114 Sep 5, 2026
cdabd7d
ci: pin the new core subpath exports step in the lint lane payload
yiliang114 Sep 5, 2026
2dd1c65
Merge branch 'main' into perf/core-subpath-imports-batch3
yiliang114 Sep 6, 2026
e82b671
Merge branch 'main' into perf/core-subpath-imports-batch3
yiliang114 Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,19 @@ jobs:
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"
run: 'npm run check:serve-fast-path-bundle'

# cli sources name core modules by path (`.../config/storage.js`). In the
# repo those resolve through tsconfig paths or vitest aliases; in the
# published package neither exists and Node resolves them against core's
# `exports`, where one `./*` entry carries all of them. Nothing else
# exercises that entry, so without this a change to it — or to the
# dist layout — leaves every suite green and breaks `qwen` at startup.
# Runs after the step above has already built the workspace.
- name: 'Check core subpath exports resolve'
if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}"
run: |-
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
npm run build --workspace=packages/core
npm run check:core-subpath-exports

# The `github_ci_only` profile runs the .github/scripts helper tests, but a
# `full` PR that touches those scripts skips that path and `npm run
# test:ci` (vitest) does not collect `node:test` files — so run them here
Expand Down
3 changes: 2 additions & 1 deletion eslint-rules/no-core-root-barrel-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ function isCoreProductionFile(filename) {
}

const CORE_PACKAGE_SPECIFIER = '@qwen-code/qwen-code-core';
// The package exports map ("./src/*", "./dist/*") also exposes the root
// The package exports map ("./*", "./src/*", "./dist/*") also exposes the root
// barrel through these self-reference subpaths.
const CORE_BARREL_SPECIFIERS = new Set([
CORE_PACKAGE_SPECIFIER,
`${CORE_PACKAGE_SPECIFIER}/index.js`,
`${CORE_PACKAGE_SPECIFIER}/src/index.js`,
`${CORE_PACKAGE_SPECIFIER}/src/index.ts`,
`${CORE_PACKAGE_SPECIFIER}/dist/index.js`,
Expand Down
53 changes: 50 additions & 3 deletions eslint-rules/no-core-utils-upward-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,55 @@ function stripExtension(rel) {
return rel.replace(/\.(js|ts|tsx|mjs|cjs)$/, '');
}

function exportTargetOf(entry) {
return typeof entry === 'string' ? entry : entry?.import;
}
Comment thread
yiliang114 marked this conversation as resolved.
Resolved

// Node resolves a subpath against `exports` by exact key first, then by
// pattern: among keys containing a single `*`, the one with the longest
// literal prefix wins; ties prefer the longer full key. What `*` captured is
// substituted into the target.
// The rule has to follow the same order, because `packages/core/package.json`
// carries a `./*` catch-all — without pattern matching every deep specifier
// misses the exact lookup, resolves fine at runtime, and is reported by
// nothing.
export function resolveExportTarget(
exportKey,
packageExports = CORE_PACKAGE_EXPORTS,
) {
const exact = exportTargetOf(packageExports[exportKey]);
if (typeof exact === 'string') return exact;

let best = null;
for (const [pattern, entry] of Object.entries(packageExports)) {
const star = pattern.indexOf('*');
if (star < 0 || pattern.indexOf('*', star + 1) >= 0) continue;
const prefix = pattern.slice(0, star);
const suffix = pattern.slice(star + 1);
if (!exportKey.startsWith(prefix)) continue;
if (!exportKey.endsWith(suffix)) continue;
if (exportKey.length < prefix.length + suffix.length) continue;
if (
best &&
(best.prefix.length > prefix.length ||
(best.prefix.length === prefix.length &&
best.pattern.length >= pattern.length))
) {
continue;
}
best = { pattern, prefix, suffix, entry };
}
if (!best) return null;

const target = exportTargetOf(best.entry);
if (typeof target !== 'string' || !target.includes('*')) return null;
const captured = exportKey.slice(
best.prefix.length,
exportKey.length - best.suffix.length,
);
return target.replace('*', captured);
}

function corePackageSourcePath(importedPath) {
if (importedPath.startsWith(CORE_PACKAGE_SRC_PREFIX)) {
return importedPath.slice(CORE_PACKAGE_SRC_PREFIX.length);
Expand All @@ -83,9 +132,7 @@ function corePackageSourcePath(importedPath) {
}

const exportKey = `./${importedPath.slice(CORE_PACKAGE_SUBPATH_PREFIX.length)}`;
const exportEntry = CORE_PACKAGE_EXPORTS[exportKey];
const exportTarget =
typeof exportEntry === 'string' ? exportEntry : exportEntry?.import;
const exportTarget = resolveExportTarget(exportKey);
if (typeof exportTarget !== 'string') {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
* `@qwen-code/qwen-code-core`, which normally resolves through the package's
* built `dist` (absent on a fresh clone, and stale whenever core src moves
* ahead of the last build). To avoid both, this registers an ESM loader hook
* (same idea as scripts/dev.js) that redirects that specifier to
* `packages/core/index.ts`, then imports the core-dependent modules
* (same idea as scripts/dev.js) that redirects root and subpath specifiers to
* `packages/core` source, then imports the core-dependent modules
* DYNAMICALLY so they resolve through the hook. Type-only imports below are
* erased at runtime and never trigger core resolution.
*/
Expand Down Expand Up @@ -223,18 +223,46 @@ async function main() {
// Redirect @qwen-code/qwen-code-core to its TypeScript source so the harness
// runs without a build and can never pick up a stale dist. Registered before
// the dynamic imports below, which is what routes them through the hook.
//
// No CI test executes this loader: the harness is exercised manually only,
// via `npm run test:terminal-bench` (vitest.terminal-bench.config.ts). Keep
// the `named` map in sync with the named entries in the exports map of
// packages/core/package.json; scripts/check-core-subpath-exports.mjs gates
// the real (compiled) resolution path in CI.
const repoRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'../../..',
);
const coreSrcUrl = pathToFileURL(
path.join(repoRoot, 'packages', 'core', 'index.ts'),
).href;
const coreModulesUrl = pathToFileURL(
path.join(repoRoot, 'packages', 'core', 'src') + '/',
).href;
const loader = `
import { existsSync } from 'node:fs';
const prefix = '@qwen-code/qwen-code-core/';
const named = new Map([
['transcriptRecords', 'utils/transcript-records.ts'], ['envVarResolver', 'utils/envVarResolver.ts'],
['goalWire', 'goals/goal-wire.ts'], ['memoryScopes', 'memory/scopes.ts'],
['subSessionConstants', 'tools/sub-session-constants.ts'], ['toolWriteOrigin', 'services/tool-write-origin.ts'],
['userPromptSubmitContext', 'hooks/user-prompt-submit-context.ts'], ['noFollowOpen', 'utils/no-follow-open.ts'],
['conversationsRuntimeMarker', 'utils/conversations-runtime-marker.ts'],
]);
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
export function resolve(specifier, context, nextResolve) {
if (specifier === '@qwen-code/qwen-code-core') {
return { shortCircuit: true, url: '${coreSrcUrl}', format: 'module' };
}
if (specifier.startsWith(prefix)) {
const subpath = specifier.slice(prefix.length);
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
const namedPath = named.get(subpath);
if (namedPath) return { shortCircuit: true, url: new URL(namedPath, '${coreModulesUrl}').href, format: 'module' };
const stem = subpath.replace(/\\.js$/, '');
for (const ext of ['.ts', '.tsx']) {
const candidate = new URL(stem + ext, '${coreModulesUrl}');
if (existsSync(candidate)) return { shortCircuit: true, url: candidate.href, format: 'module' };
}
}
return nextResolve(specifier, context);
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
}
`;
Expand Down
24 changes: 20 additions & 4 deletions integration-tests/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@
// on whether someone had built them recently — a missing dist used to fail
// resolution outright and a stale one silently typechecked against old
// declarations. nodenext does no extension or index probing on
// substituted paths, so every subpath the program imports needs an
// explicit entry naming its source file; a bare wildcard falls through to
// the package exports map, i.e. back to dist. Keep these in sync with the
// packages' exports maps. The runtime vitest aliases
// substituted paths, but it does substitute `.js` for `.ts`, so the
// `@qwen-code/qwen-code-core/*` wildcard below covers every specifier
// whose source file sits at the matching path under `packages/core/src/`.
// Explicit entries remain necessary only where the specifier does not name
// its file — `noFollowOpen` lives at `utils/no-follow-open.ts`, and the
// wildcard would map it to a file that does not exist. Do not delete those
// entries: since core's exports map gained a `./*` catch-all they would
// still resolve, silently, against dist declarations rather than failing.
// Keep these in sync with the packages' exports maps. The runtime vitest
// aliases
// (`integration-tests/vitest.config.ts`) still point at the built SDK
// bundle to exercise the published-bundle shape; these entries only affect
// type resolution.
Expand All @@ -48,6 +54,16 @@
"@qwen-code/qwen-code-core/noFollowOpen": [
"../packages/core/src/utils/no-follow-open.ts"
],
"@qwen-code/qwen-code-core/toolWriteOrigin": [
"../packages/core/src/services/tool-write-origin.ts"
],
"@qwen-code/qwen-code-core/envVarResolver": [
"../packages/core/src/utils/envVarResolver.ts"
],
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
"@qwen-code/qwen-code-core/conversationsRuntimeMarker": [
"../packages/core/src/utils/conversations-runtime-marker.ts"
],
"@qwen-code/qwen-code-core/*": ["../packages/core/src/*"],
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
Comment thread
yiliang114 marked this conversation as resolved.
Resolved
"@qwen-code/qwen-code-core/goalWire": [
"../packages/core/src/goals/goal-wire.ts"
],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"check:lockfile": "node scripts/check-lockfile.js",
"check:desktop-isolation": "node scripts/check-desktop-isolation.js",
"check:tui-dep-direction": "node scripts/check-tui-dep-direction.mjs",
"check:core-subpath-exports": "node scripts/check-core-subpath-exports.mjs",
"check:serve-fast-path-bundle": "node scripts/clean-package-build-artifacts.js && npm run build -- --cli-only && cross-env DEV=true npm run bundle && node scripts/check-serve-fast-path-bundle.js",
"clean": "node scripts/clean.js",
"pre-commit": "node scripts/pre-commit.js"
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/agent-view/supervisor-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { createHash } from 'node:crypto';
import { atomicWriteFile, Storage } from '@qwen-code/qwen-code-core';
import { Storage } from '@qwen-code/qwen-code-core/config/storage.js';
import { atomicWriteFile } from '@qwen-code/qwen-code-core/utils/atomicFileWrite.js';
import type {
AgentViewActivityFile,
AgentViewLaunchFile,
Expand Down
12 changes: 5 additions & 7 deletions packages/cli/src/config/extension-file-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { watch as watchFs, type FSWatcher } from 'chokidar';
import {
createDebugLogger,
isSubpath,
Storage,
type Config,
type ExtensionMutationEvent,
} from '@qwen-code/qwen-code-core';
import type { Config } from '@qwen-code/qwen-code-core/config/config.js';
import { Storage } from '@qwen-code/qwen-code-core/config/storage.js';
import type { ExtensionMutationEvent } from '@qwen-code/qwen-code-core/extension/extensionManager.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js';
import { isSubpath } from '@qwen-code/qwen-code-core/utils/paths.js';
import { ExtensionRefreshState } from './extension-refresh-state.js';

const debugLogger = createDebugLogger('EXTENSION_FILE_WATCHER');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import type {
ControlResponse,
ControlRequestPayload,
} from '../types.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js';

const debugLogger = createDebugLogger('CONTROL_DISPATCHER');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@
* Abstracts all permission logic from the session manager to keep it clean.
*/

import type { TeammateApprovalRequestEvent } from '@qwen-code/qwen-code-core/agents/team/team-events.js';
import type { WorkflowApproval } from '@qwen-code/qwen-code-core/agents/workflow-run-registry.js';
import type { WaitingToolCall } from '@qwen-code/qwen-code-core/core/coreToolScheduler.js';
import type {
WaitingToolCall,
ToolExecuteConfirmationDetails,
ToolMcpConfirmationDetails,
TeammateApprovalRequestEvent,
ToolConfirmationPayload,
WorkflowApproval,
} from '@qwen-code/qwen-code-core';
} from '@qwen-code/qwen-code-core/tools/tools.js';
import {
ApprovalMode,
APPROVAL_MODES,
InputFormat,
ToolConfirmationOutcome,
ToolNames,
} from '@qwen-code/qwen-code-core';
} from '@qwen-code/qwen-code-core/config/approval-mode.js';
import { InputFormat } from '@qwen-code/qwen-code-core/output/types.js';
import { ToolNames } from '@qwen-code/qwen-code-core/tools/tool-names.js';
import { ToolConfirmationOutcome } from '@qwen-code/qwen-code-core/tools/tools.js';
import type {
CLIControlPermissionRequest,
CLIControlSetPermissionModeRequest,
Expand Down
12 changes: 5 additions & 7 deletions packages/cli/src/nonInteractive/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@
import type {
Config,
ConfigInitializeOptions,
} from '@qwen-code/qwen-code-core';
import {
createDebugLogger,
buildSessionRecoveryPlanFromApiHistory,
SendMessageType,
TURN_INTERRUPTION_HISTORY_TAIL_COUNT,
} from '@qwen-code/qwen-code-core';
} from '@qwen-code/qwen-code-core/config/config.js';
import { SendMessageType } from '@qwen-code/qwen-code-core/core/client.js';
import { buildSessionRecoveryPlanFromApiHistory } from '@qwen-code/qwen-code-core/core/session-recovery.js';
import { TURN_INTERRUPTION_HISTORY_TAIL_COUNT } from '@qwen-code/qwen-code-core/core/turn-interruption.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js';
import { StreamJsonInputReader } from './io/StreamJsonInputReader.js';
import { StreamJsonOutputAdapter } from './io/StreamJsonOutputAdapter.js';
import { ControlContext } from './control/ControlContext.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/remoteInput/RemoteInputWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
watchFile,
} from 'node:fs';
import { createInterface } from 'node:readline';
import { createDebugLogger } from '@qwen-code/qwen-code-core';
import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js';

const debugLogger = createDebugLogger('REMOTE_INPUT');

Expand Down
15 changes: 8 additions & 7 deletions packages/cli/src/serve/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import {
SETTINGS_DIRECTORY_NAME,
} from '../config/settings.js';
import { promisify } from 'node:util';
import type { Config, SandboxConfig } from '@qwen-code/qwen-code-core';
import {
FatalSandboxError,
Storage,
isSubpath,
resolveBundleDir,
} from '@qwen-code/qwen-code-core';
import type {
Config,
SandboxConfig,
} from '@qwen-code/qwen-code-core/config/config.js';
import { Storage } from '@qwen-code/qwen-code-core/config/storage.js';
import { resolveBundleDir } from '@qwen-code/qwen-code-core/utils/bundlePaths.js';
import { FatalSandboxError } from '@qwen-code/qwen-code-core/utils/errors.js';
import { isSubpath } from '@qwen-code/qwen-code-core/utils/paths.js';
import { randomBytes } from 'node:crypto';
import { writeStderrLine } from '../utils/stdioHelpers.js';
import { parseSandboxImageName } from '../utils/sandboxImageName.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/services/command-migration-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { glob } from 'glob';
import { convertTomlToMarkdown } from '@qwen-code/qwen-code-core';
import { convertTomlToMarkdown } from '@qwen-code/qwen-code-core/utils/toml-to-markdown-converter.js';
import { t } from '../i18n/index.js';

export interface MigrationResult {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/services/notificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* When disabled, no notification is sent.
*/

import { createDebugLogger } from '@qwen-code/qwen-code-core';
import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js';
import type { TerminalNotification } from '../ui/hooks/useTerminalNotification.js';
import { detectTerminal, generateKittyId } from '../utils/osc.js';

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/services/prompt-stash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

import * as fs from 'node:fs';
import * as path from 'node:path';
import { atomicWriteFileSync, Storage } from '@qwen-code/qwen-code-core';
import { Storage } from '@qwen-code/qwen-code-core/config/storage.js';
import { atomicWriteFileSync } from '@qwen-code/qwen-code-core/utils/atomicFileWrite.js';

const PROMPT_STASH_FILE = 'prompt-stash.json';

Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/services/tips/tipHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

import * as fs from 'node:fs';
import * as path from 'node:path';
import { atomicWriteFileSync, Storage } from '@qwen-code/qwen-code-core';
import { Storage } from '@qwen-code/qwen-code-core/config/storage.js';
import { atomicWriteFileSync } from '@qwen-code/qwen-code-core/utils/atomicFileWrite.js';

interface TipHistoryEntry {
totalShown: number;
Expand Down
15 changes: 9 additions & 6 deletions packages/cli/src/startup/worktreeStartup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@

import * as path from 'node:path';
import {
createDebugLogger,
GitWorktreeService,
readWorktreeSession,
readWorktreeSessionMarker,
isSessionRuntimeActive,
worktreeBranchForSlug,
writeWorktreeSession,
writeWorktreeSessionMarker,
} from '@qwen-code/qwen-code-core';
import type { Config, WorktreeSession } from '@qwen-code/qwen-code-core';
} from '@qwen-code/qwen-code-core/services/gitWorktreeService.js';
import {
readWorktreeSession,
isSessionRuntimeActive,
writeWorktreeSession,
} from '@qwen-code/qwen-code-core/services/worktreeSessionService.js';
import { createDebugLogger } from '@qwen-code/qwen-code-core/utils/debugLogger.js';
import type { Config } from '@qwen-code/qwen-code-core/config/config.js';
import type { WorktreeSession } from '@qwen-code/qwen-code-core/services/worktreeSessionService.js';

const debugLogger = createDebugLogger('WORKTREE_STARTUP');

Expand Down
Loading
Loading