@xortex/xcode 3.0.8 → 3.1.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/INSTALLATION.md +285 -0
- package/QUICKSTART.md +151 -0
- package/SYSTEM_PROMPT.md +583 -0
- package/SYSTEM_PROMPT_EXTRACTED.md +1 -0
- package/Untitled +1 -0
- package/bin/xcode +33 -85
- package/bootstrap/state.ts +1758 -0
- package/bun.lock +645 -0
- package/context/QueuedMessageContext.tsx +63 -0
- package/context/fpsMetrics.tsx +30 -0
- package/context/mailbox.tsx +38 -0
- package/context/modalContext.tsx +58 -0
- package/context/notifications.tsx +240 -0
- package/context/overlayContext.tsx +151 -0
- package/context/promptOverlayContext.tsx +125 -0
- package/context/stats.tsx +220 -0
- package/context/voice.tsx +88 -0
- package/coordinator/coordinatorMode.ts +369 -0
- package/costHook.ts +22 -0
- package/dialogLaunchers.tsx +133 -0
- package/entrypoints/cli.tsx +1 -1
- package/extract_prompt.ts +304 -0
- package/ink.ts +85 -0
- package/install.sh +221 -0
- package/interactiveHelpers.tsx +366 -0
- package/macro.ts +1 -1
- package/memdir/findRelevantMemories.ts +141 -0
- package/memdir/memdir.ts +511 -0
- package/memdir/memoryAge.ts +53 -0
- package/memdir/memoryScan.ts +94 -0
- package/memdir/memoryTypes.ts +271 -0
- package/memdir/paths.ts +291 -0
- package/memdir/teamMemPaths.ts +292 -0
- package/memdir/teamMemPrompts.ts +100 -0
- package/moreright/useMoreRight.tsx +26 -0
- package/native-ts/color-diff/index.ts +999 -0
- package/native-ts/file-index/index.ts +370 -0
- package/native-ts/yoga-layout/enums.ts +134 -0
- package/native-ts/yoga-layout/index.ts +2578 -0
- package/outputStyles/loadOutputStylesDir.ts +98 -0
- package/package.json +3 -42
- package/plugins/builtinPlugins.ts +159 -0
- package/plugins/bundled/index.ts +23 -0
- package/projectOnboardingState.ts +83 -0
- package/public/claude-files.png +0 -0
- package/public/leak-tweet.png +0 -0
- package/query/config.ts +46 -0
- package/query/deps.ts +40 -0
- package/query/stopHooks.ts +470 -0
- package/query/tokenBudget.ts +93 -0
- package/replLauncher.tsx +27 -0
- package/schemas/hooks.ts +222 -0
- package/screens/Doctor.tsx +575 -0
- package/screens/REPL.tsx +7107 -0
- package/screens/ResumeConversation.tsx +399 -0
- package/scripts/postinstall.js +90 -0
- package/server/createDirectConnectSession.ts +88 -0
- package/server/directConnectManager.ts +213 -0
- package/server/types.ts +57 -0
- package/setup.ts +477 -0
- package/stub_types.sh +13 -0
- package/tasks.ts +39 -0
- package/tools.ts +396 -0
- package/upstreamproxy/relay.ts +455 -0
- package/upstreamproxy/upstreamproxy.ts +285 -0
- package/vim/motions.ts +82 -0
- package/vim/operators.ts +556 -0
- package/vim/textObjects.ts +186 -0
- package/vim/transitions.ts +490 -0
- package/vim/types.ts +199 -0
- package/voice/voiceModeEnabled.ts +54 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import memoize from 'lodash-es/memoize.js'
|
|
2
|
+
import { basename } from 'path'
|
|
3
|
+
import type { OutputStyleConfig } from '../constants/outputStyles.js'
|
|
4
|
+
import { logForDebugging } from '../utils/debug.js'
|
|
5
|
+
import { coerceDescriptionToString } from '../utils/frontmatterParser.js'
|
|
6
|
+
import { logError } from '../utils/log.js'
|
|
7
|
+
import {
|
|
8
|
+
extractDescriptionFromMarkdown,
|
|
9
|
+
loadMarkdownFilesForSubdir,
|
|
10
|
+
} from '../utils/markdownConfigLoader.js'
|
|
11
|
+
import { clearPluginOutputStyleCache } from '../utils/plugins/loadPluginOutputStyles.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads markdown files from .claude/output-styles directories throughout the project
|
|
15
|
+
* and from ~/.claude/output-styles directory and converts them to output styles.
|
|
16
|
+
*
|
|
17
|
+
* Each filename becomes a style name, and the file content becomes the style prompt.
|
|
18
|
+
* The frontmatter provides name and description.
|
|
19
|
+
*
|
|
20
|
+
* Structure:
|
|
21
|
+
* - Project .claude/output-styles/*.md -> project styles
|
|
22
|
+
* - User ~/.claude/output-styles/*.md -> user styles (overridden by project styles)
|
|
23
|
+
*
|
|
24
|
+
* @param cwd Current working directory for project directory traversal
|
|
25
|
+
*/
|
|
26
|
+
export const getOutputStyleDirStyles = memoize(
|
|
27
|
+
async (cwd: string): Promise<OutputStyleConfig[]> => {
|
|
28
|
+
try {
|
|
29
|
+
const markdownFiles = await loadMarkdownFilesForSubdir(
|
|
30
|
+
'output-styles',
|
|
31
|
+
cwd,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const styles = markdownFiles
|
|
35
|
+
.map(({ filePath, frontmatter, content, source }) => {
|
|
36
|
+
try {
|
|
37
|
+
const fileName = basename(filePath)
|
|
38
|
+
const styleName = fileName.replace(/\.md$/, '')
|
|
39
|
+
|
|
40
|
+
// Get style configuration from frontmatter
|
|
41
|
+
const name = (frontmatter['name'] || styleName) as string
|
|
42
|
+
const description =
|
|
43
|
+
coerceDescriptionToString(
|
|
44
|
+
frontmatter['description'],
|
|
45
|
+
styleName,
|
|
46
|
+
) ??
|
|
47
|
+
extractDescriptionFromMarkdown(
|
|
48
|
+
content,
|
|
49
|
+
`Custom ${styleName} output style`,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// Parse keep-coding-instructions flag (supports both boolean and string values)
|
|
53
|
+
const keepCodingInstructionsRaw =
|
|
54
|
+
frontmatter['keep-coding-instructions']
|
|
55
|
+
const keepCodingInstructions =
|
|
56
|
+
keepCodingInstructionsRaw === true ||
|
|
57
|
+
keepCodingInstructionsRaw === 'true'
|
|
58
|
+
? true
|
|
59
|
+
: keepCodingInstructionsRaw === false ||
|
|
60
|
+
keepCodingInstructionsRaw === 'false'
|
|
61
|
+
? false
|
|
62
|
+
: undefined
|
|
63
|
+
|
|
64
|
+
// Warn if force-for-plugin is set on non-plugin output style
|
|
65
|
+
if (frontmatter['force-for-plugin'] !== undefined) {
|
|
66
|
+
logForDebugging(
|
|
67
|
+
`Output style "${name}" has force-for-plugin set, but this option only applies to plugin output styles. Ignoring.`,
|
|
68
|
+
{ level: 'warn' },
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
name,
|
|
74
|
+
description,
|
|
75
|
+
prompt: content.trim(),
|
|
76
|
+
source,
|
|
77
|
+
keepCodingInstructions,
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logError(error)
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
.filter(style => style !== null)
|
|
85
|
+
|
|
86
|
+
return styles
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logError(error)
|
|
89
|
+
return []
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
export function clearOutputStyleCaches(): void {
|
|
95
|
+
getOutputStyleDirStyles.cache?.clear?.()
|
|
96
|
+
loadMarkdownFilesForSubdir.cache?.clear?.()
|
|
97
|
+
clearPluginOutputStyleCache()
|
|
98
|
+
}
|
package/package.json
CHANGED
|
@@ -1,55 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xortex/xcode",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "XCode - AI-powered coding assistant with XMem long-term memory. Supports Claude, Gemini, Kimi, DeepSeek via OpenRouter.",
|
|
5
5
|
"main": "main.tsx",
|
|
6
6
|
"bin": {
|
|
7
7
|
"xcode": "./bin/xcode"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "bun run ./entrypoints/cli.tsx
|
|
10
|
+
"start": "bun run ./entrypoints/cli.tsx",
|
|
11
|
+
"postinstall": "node scripts/postinstall.js",
|
|
11
12
|
"install-global": "npm install -g ."
|
|
12
13
|
},
|
|
13
|
-
"files": [
|
|
14
|
-
"bin/",
|
|
15
|
-
"entrypoints/",
|
|
16
|
-
"main.tsx",
|
|
17
|
-
"macro.ts",
|
|
18
|
-
"bun-bundle-hook.js",
|
|
19
|
-
"bun-bundle-shim.ts",
|
|
20
|
-
"tsconfig.json",
|
|
21
|
-
"src/",
|
|
22
|
-
"constants/",
|
|
23
|
-
"utils/",
|
|
24
|
-
"services/",
|
|
25
|
-
"tools/",
|
|
26
|
-
"commands/",
|
|
27
|
-
"hooks/",
|
|
28
|
-
"ink/",
|
|
29
|
-
"keybindings/",
|
|
30
|
-
"tasks/",
|
|
31
|
-
"types/",
|
|
32
|
-
"state/",
|
|
33
|
-
"components/",
|
|
34
|
-
"skills/",
|
|
35
|
-
"migrations/",
|
|
36
|
-
"buddy/",
|
|
37
|
-
"bridge/",
|
|
38
|
-
"remote/",
|
|
39
|
-
"assistant/",
|
|
40
|
-
"cli/",
|
|
41
|
-
"context.ts",
|
|
42
|
-
"commands.ts",
|
|
43
|
-
"cost-tracker.ts",
|
|
44
|
-
"history.ts",
|
|
45
|
-
"Task.ts",
|
|
46
|
-
"Tool.ts",
|
|
47
|
-
"QueryEngine.ts",
|
|
48
|
-
"query.ts",
|
|
49
|
-
"*.js",
|
|
50
|
-
"README.md",
|
|
51
|
-
"LICENSE"
|
|
52
|
-
],
|
|
53
14
|
"keywords": [
|
|
54
15
|
"ai",
|
|
55
16
|
"coding",
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Plugin Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages built-in plugins that ship with the CLI and can be enabled/disabled
|
|
5
|
+
* by users via the /plugin UI.
|
|
6
|
+
*
|
|
7
|
+
* Built-in plugins differ from bundled skills (src/skills/bundled/) in that:
|
|
8
|
+
* - They appear in the /plugin UI under a "Built-in" section
|
|
9
|
+
* - Users can enable/disable them (persisted to user settings)
|
|
10
|
+
* - They can provide multiple components (skills, hooks, MCP servers)
|
|
11
|
+
*
|
|
12
|
+
* Plugin IDs use the format `{name}@builtin` to distinguish them from
|
|
13
|
+
* marketplace plugins (`{name}@{marketplace}`).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Command } from '../commands.js'
|
|
17
|
+
import type { BundledSkillDefinition } from '../skills/bundledSkills.js'
|
|
18
|
+
import type { BuiltinPluginDefinition, LoadedPlugin } from '../types/plugin.js'
|
|
19
|
+
import { getSettings_DEPRECATED } from '../utils/settings/settings.js'
|
|
20
|
+
|
|
21
|
+
const BUILTIN_PLUGINS: Map<string, BuiltinPluginDefinition> = new Map()
|
|
22
|
+
|
|
23
|
+
export const BUILTIN_MARKETPLACE_NAME = 'builtin'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Register a built-in plugin. Call this from initBuiltinPlugins() at startup.
|
|
27
|
+
*/
|
|
28
|
+
export function registerBuiltinPlugin(
|
|
29
|
+
definition: BuiltinPluginDefinition,
|
|
30
|
+
): void {
|
|
31
|
+
BUILTIN_PLUGINS.set(definition.name, definition)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a plugin ID represents a built-in plugin (ends with @builtin).
|
|
36
|
+
*/
|
|
37
|
+
export function isBuiltinPluginId(pluginId: string): boolean {
|
|
38
|
+
return pluginId.endsWith(`@${BUILTIN_MARKETPLACE_NAME}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get a specific built-in plugin definition by name.
|
|
43
|
+
* Useful for the /plugin UI to show the skills/hooks/MCP list without
|
|
44
|
+
* a marketplace lookup.
|
|
45
|
+
*/
|
|
46
|
+
export function getBuiltinPluginDefinition(
|
|
47
|
+
name: string,
|
|
48
|
+
): BuiltinPluginDefinition | undefined {
|
|
49
|
+
return BUILTIN_PLUGINS.get(name)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get all registered built-in plugins as LoadedPlugin objects, split into
|
|
54
|
+
* enabled/disabled based on user settings (with defaultEnabled as fallback).
|
|
55
|
+
* Plugins whose isAvailable() returns false are omitted entirely.
|
|
56
|
+
*/
|
|
57
|
+
export function getBuiltinPlugins(): {
|
|
58
|
+
enabled: LoadedPlugin[]
|
|
59
|
+
disabled: LoadedPlugin[]
|
|
60
|
+
} {
|
|
61
|
+
const settings = getSettings_DEPRECATED()
|
|
62
|
+
const enabled: LoadedPlugin[] = []
|
|
63
|
+
const disabled: LoadedPlugin[] = []
|
|
64
|
+
|
|
65
|
+
for (const [name, definition] of BUILTIN_PLUGINS) {
|
|
66
|
+
if (definition.isAvailable && !definition.isAvailable()) {
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const pluginId = `${name}@${BUILTIN_MARKETPLACE_NAME}`
|
|
71
|
+
const userSetting = settings?.enabledPlugins?.[pluginId]
|
|
72
|
+
// Enabled state: user preference > plugin default > true
|
|
73
|
+
const isEnabled =
|
|
74
|
+
userSetting !== undefined
|
|
75
|
+
? userSetting === true
|
|
76
|
+
: (definition.defaultEnabled ?? true)
|
|
77
|
+
|
|
78
|
+
const plugin: LoadedPlugin = {
|
|
79
|
+
name,
|
|
80
|
+
manifest: {
|
|
81
|
+
name,
|
|
82
|
+
description: definition.description,
|
|
83
|
+
version: definition.version,
|
|
84
|
+
},
|
|
85
|
+
path: BUILTIN_MARKETPLACE_NAME, // sentinel — no filesystem path
|
|
86
|
+
source: pluginId,
|
|
87
|
+
repository: pluginId,
|
|
88
|
+
enabled: isEnabled,
|
|
89
|
+
isBuiltin: true,
|
|
90
|
+
hooksConfig: definition.hooks,
|
|
91
|
+
mcpServers: definition.mcpServers,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (isEnabled) {
|
|
95
|
+
enabled.push(plugin)
|
|
96
|
+
} else {
|
|
97
|
+
disabled.push(plugin)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { enabled, disabled }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get skills from enabled built-in plugins as Command objects.
|
|
106
|
+
* Skills from disabled plugins are not returned.
|
|
107
|
+
*/
|
|
108
|
+
export function getBuiltinPluginSkillCommands(): Command[] {
|
|
109
|
+
const { enabled } = getBuiltinPlugins()
|
|
110
|
+
const commands: Command[] = []
|
|
111
|
+
|
|
112
|
+
for (const plugin of enabled) {
|
|
113
|
+
const definition = BUILTIN_PLUGINS.get(plugin.name)
|
|
114
|
+
if (!definition?.skills) continue
|
|
115
|
+
for (const skill of definition.skills) {
|
|
116
|
+
commands.push(skillDefinitionToCommand(skill))
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return commands
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Clear built-in plugins registry (for testing).
|
|
125
|
+
*/
|
|
126
|
+
export function clearBuiltinPlugins(): void {
|
|
127
|
+
BUILTIN_PLUGINS.clear()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --
|
|
131
|
+
|
|
132
|
+
function skillDefinitionToCommand(definition: BundledSkillDefinition): Command {
|
|
133
|
+
return {
|
|
134
|
+
type: 'prompt',
|
|
135
|
+
name: definition.name,
|
|
136
|
+
description: definition.description,
|
|
137
|
+
hasUserSpecifiedDescription: true,
|
|
138
|
+
allowedTools: definition.allowedTools ?? [],
|
|
139
|
+
argumentHint: definition.argumentHint,
|
|
140
|
+
whenToUse: definition.whenToUse,
|
|
141
|
+
model: definition.model,
|
|
142
|
+
disableModelInvocation: definition.disableModelInvocation ?? false,
|
|
143
|
+
userInvocable: definition.userInvocable ?? true,
|
|
144
|
+
contentLength: 0,
|
|
145
|
+
// 'bundled' not 'builtin' — 'builtin' in Command.source means hardcoded
|
|
146
|
+
// slash commands (/help, /clear). Using 'bundled' keeps these skills in
|
|
147
|
+
// the Skill tool's listing, analytics name logging, and prompt-truncation
|
|
148
|
+
// exemption. The user-toggleable aspect is tracked on LoadedPlugin.isBuiltin.
|
|
149
|
+
source: 'bundled',
|
|
150
|
+
loadedFrom: 'bundled',
|
|
151
|
+
hooks: definition.hooks,
|
|
152
|
+
context: definition.context,
|
|
153
|
+
agent: definition.agent,
|
|
154
|
+
isEnabled: definition.isEnabled ?? (() => true),
|
|
155
|
+
isHidden: !(definition.userInvocable ?? true),
|
|
156
|
+
progressMessage: 'running',
|
|
157
|
+
getPromptForCommand: definition.getPromptForCommand,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Plugin Initialization
|
|
3
|
+
*
|
|
4
|
+
* Initializes built-in plugins that ship with the CLI and appear in the
|
|
5
|
+
* /plugin UI for users to enable/disable.
|
|
6
|
+
*
|
|
7
|
+
* Not all bundled features should be built-in plugins — use this for
|
|
8
|
+
* features that users should be able to explicitly enable/disable. For
|
|
9
|
+
* features with complex setup or automatic-enabling logic (e.g.
|
|
10
|
+
* claude-in-chrome), use src/skills/bundled/ instead.
|
|
11
|
+
*
|
|
12
|
+
* To add a new built-in plugin:
|
|
13
|
+
* 1. Import registerBuiltinPlugin from '../builtinPlugins.js'
|
|
14
|
+
* 2. Call registerBuiltinPlugin() with the plugin definition here
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize built-in plugins. Called during CLI startup.
|
|
19
|
+
*/
|
|
20
|
+
export function initBuiltinPlugins(): void {
|
|
21
|
+
// No built-in plugins registered yet — this is the scaffolding for
|
|
22
|
+
// migrating bundled skills that should be user-toggleable.
|
|
23
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import memoize from 'lodash-es/memoize.js'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import {
|
|
4
|
+
getCurrentProjectConfig,
|
|
5
|
+
saveCurrentProjectConfig,
|
|
6
|
+
} from './utils/config.js'
|
|
7
|
+
import { getCwd } from './utils/cwd.js'
|
|
8
|
+
import { isDirEmpty } from './utils/file.js'
|
|
9
|
+
import { getFsImplementation } from './utils/fsOperations.js'
|
|
10
|
+
|
|
11
|
+
export type Step = {
|
|
12
|
+
key: string
|
|
13
|
+
text: string
|
|
14
|
+
isComplete: boolean
|
|
15
|
+
isCompletable: boolean
|
|
16
|
+
isEnabled: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getSteps(): Step[] {
|
|
20
|
+
const hasClaudeMd = getFsImplementation().existsSync(
|
|
21
|
+
join(getCwd(), 'CLAUDE.md'),
|
|
22
|
+
)
|
|
23
|
+
const isWorkspaceDirEmpty = isDirEmpty(getCwd())
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
key: 'workspace',
|
|
28
|
+
text: 'Ask Claude to create a new app or clone a repository',
|
|
29
|
+
isComplete: false,
|
|
30
|
+
isCompletable: true,
|
|
31
|
+
isEnabled: isWorkspaceDirEmpty,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: 'claudemd',
|
|
35
|
+
text: 'Run /init to create a CLAUDE.md file with instructions for Claude',
|
|
36
|
+
isComplete: hasClaudeMd,
|
|
37
|
+
isCompletable: true,
|
|
38
|
+
isEnabled: !isWorkspaceDirEmpty,
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isProjectOnboardingComplete(): boolean {
|
|
44
|
+
return getSteps()
|
|
45
|
+
.filter(({ isCompletable, isEnabled }) => isCompletable && isEnabled)
|
|
46
|
+
.every(({ isComplete }) => isComplete)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function maybeMarkProjectOnboardingComplete(): void {
|
|
50
|
+
// Short-circuit on cached config — isProjectOnboardingComplete() hits
|
|
51
|
+
// the filesystem, and REPL.tsx calls this on every prompt submit.
|
|
52
|
+
if (getCurrentProjectConfig().hasCompletedProjectOnboarding) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
if (isProjectOnboardingComplete()) {
|
|
56
|
+
saveCurrentProjectConfig(current => ({
|
|
57
|
+
...current,
|
|
58
|
+
hasCompletedProjectOnboarding: true,
|
|
59
|
+
}))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const shouldShowProjectOnboarding = memoize((): boolean => {
|
|
64
|
+
const projectConfig = getCurrentProjectConfig()
|
|
65
|
+
// Short-circuit on cached config before isProjectOnboardingComplete()
|
|
66
|
+
// hits the filesystem — this runs during first render.
|
|
67
|
+
if (
|
|
68
|
+
projectConfig.hasCompletedProjectOnboarding ||
|
|
69
|
+
projectConfig.projectOnboardingSeenCount >= 4 ||
|
|
70
|
+
process.env.IS_DEMO
|
|
71
|
+
) {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return !isProjectOnboardingComplete()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
export function incrementProjectOnboardingSeenCount(): void {
|
|
79
|
+
saveCurrentProjectConfig(current => ({
|
|
80
|
+
...current,
|
|
81
|
+
projectOnboardingSeenCount: current.projectOnboardingSeenCount + 1,
|
|
82
|
+
}))
|
|
83
|
+
}
|
|
Binary file
|
|
Binary file
|
package/query/config.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getSessionId } from '../bootstrap/state.js'
|
|
2
|
+
import { checkStatsigFeatureGate_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js'
|
|
3
|
+
import type { SessionId } from '../types/ids.js'
|
|
4
|
+
import { isEnvTruthy } from '../utils/envUtils.js'
|
|
5
|
+
|
|
6
|
+
// -- config
|
|
7
|
+
|
|
8
|
+
// Immutable values snapshotted once at query() entry. Separating these from
|
|
9
|
+
// the per-iteration State struct and the mutable ToolUseContext makes future
|
|
10
|
+
// step() extraction tractable — a pure reducer can take (state, event, config)
|
|
11
|
+
// where config is plain data.
|
|
12
|
+
//
|
|
13
|
+
// Intentionally excludes feature() gates — those are tree-shaking boundaries
|
|
14
|
+
// and must stay inline at the guarded blocks for dead-code elimination.
|
|
15
|
+
export type QueryConfig = {
|
|
16
|
+
sessionId: SessionId
|
|
17
|
+
|
|
18
|
+
// Runtime gates (env/statsig). NOT feature() gates — see above.
|
|
19
|
+
gates: {
|
|
20
|
+
// Statsig — CACHED_MAY_BE_STALE already admits staleness, so snapshotting
|
|
21
|
+
// once per query() call stays within the existing contract.
|
|
22
|
+
streamingToolExecution: boolean
|
|
23
|
+
emitToolUseSummaries: boolean
|
|
24
|
+
isAnt: boolean
|
|
25
|
+
fastModeEnabled: boolean
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function buildQueryConfig(): QueryConfig {
|
|
30
|
+
return {
|
|
31
|
+
sessionId: getSessionId(),
|
|
32
|
+
gates: {
|
|
33
|
+
streamingToolExecution: checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
|
|
34
|
+
'tengu_streaming_tool_execution2',
|
|
35
|
+
),
|
|
36
|
+
emitToolUseSummaries: isEnvTruthy(
|
|
37
|
+
process.env.CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES,
|
|
38
|
+
),
|
|
39
|
+
isAnt: process.env.USER_TYPE === 'ant',
|
|
40
|
+
// Inlined from fastMode.ts to avoid pulling its heavy module graph
|
|
41
|
+
// (axios, settings, auth, model, oauth, config) into test shards that
|
|
42
|
+
// didn't previously load it — changes init order and breaks unrelated tests.
|
|
43
|
+
fastModeEnabled: !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_FAST_MODE),
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
}
|
package/query/deps.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto'
|
|
2
|
+
import { queryModelWithStreaming } from '../services/api/gemini.js'
|
|
3
|
+
import { autoCompactIfNeeded } from '../services/compact/autoCompact.js'
|
|
4
|
+
import { microcompactMessages } from '../services/compact/microCompact.js'
|
|
5
|
+
|
|
6
|
+
// -- deps
|
|
7
|
+
|
|
8
|
+
// I/O dependencies for query(). Passing a `deps` override into QueryParams
|
|
9
|
+
// lets tests inject fakes directly instead of spyOn-per-module — the most
|
|
10
|
+
// common mocks (callModel, autocompact) are each spied in 6-8 test files
|
|
11
|
+
// today with module-import-and-spy boilerplate.
|
|
12
|
+
//
|
|
13
|
+
// Using `typeof fn` keeps signatures in sync with the real implementations
|
|
14
|
+
// automatically. This file imports the real functions for both typing and
|
|
15
|
+
// the production factory — tests that import this file for typing are
|
|
16
|
+
// already importing query.ts (which imports everything), so there's no
|
|
17
|
+
// new module-graph cost.
|
|
18
|
+
//
|
|
19
|
+
// Scope is intentionally narrow (4 deps) to prove the pattern. Followup
|
|
20
|
+
// PRs can add runTools, handleStopHooks, logEvent, queue ops, etc.
|
|
21
|
+
export type QueryDeps = {
|
|
22
|
+
// -- model
|
|
23
|
+
callModel: typeof queryModelWithStreaming
|
|
24
|
+
|
|
25
|
+
// -- compaction
|
|
26
|
+
microcompact: typeof microcompactMessages
|
|
27
|
+
autocompact: typeof autoCompactIfNeeded
|
|
28
|
+
|
|
29
|
+
// -- platform
|
|
30
|
+
uuid: () => string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function productionDeps(): QueryDeps {
|
|
34
|
+
return {
|
|
35
|
+
callModel: queryModelWithStreaming,
|
|
36
|
+
microcompact: microcompactMessages,
|
|
37
|
+
autocompact: autoCompactIfNeeded,
|
|
38
|
+
uuid: randomUUID,
|
|
39
|
+
}
|
|
40
|
+
}
|