Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
/**
|
|
* Cassette Normalizer
|
|
*
|
|
* Strips dynamic content from prompts and CLI args before hashing into a cassette key.
|
|
* Dynamic content (UUIDs, temp paths, timestamps, session numbers) varies between
|
|
* test runs but doesn't affect how the agent responds — so we replace them with
|
|
* stable placeholders to get a stable cache key.
|
|
*/
|
|
|
|
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
const NANOID_RE = /(?<![A-Za-z0-9])[A-Za-z0-9_-]{21}(?![A-Za-z0-9_-])/g;
|
|
const ISO_TIMESTAMP_RE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?/g;
|
|
const UNIX_EPOCH_MS_RE = /\b1[0-9]{12}\b/g;
|
|
const SESSION_NUM_RE = /\bsession[_\s-]?\d+\b/gi;
|
|
// Agent worktree paths: agent-workdirs/<random-agent-name> (with or without trailing slash)
|
|
// The agent name (e.g. "available-sheep") changes every run but is not a UUID or nanoid.
|
|
// Stop at the first slash so the project name after it is preserved.
|
|
const AGENT_WORKDIR_RE = /agent-workdirs\/[^\s/\\]+/g;
|
|
|
|
/**
|
|
* Normalize a prompt for stable cassette key generation.
|
|
*
|
|
* Replacements applied in order (most-specific first to avoid partial matches):
|
|
* 1. Absolute workspace root path → __WORKSPACE__
|
|
* 2. UUIDs → __UUID__
|
|
* 2.5. Nanoid IDs (21-char alphanumeric) → __ID__
|
|
* 3. ISO 8601 timestamps → __TIMESTAMP__
|
|
* 4. Unix epoch milliseconds → __EPOCH__
|
|
* 5. Session numbers → session__N__
|
|
* 6. Agent worktree path segment → agent-workdirs/__AGENT__/
|
|
*/
|
|
export function normalizePrompt(prompt: string, workspaceRoot: string): string {
|
|
let normalized = prompt;
|
|
|
|
if (workspaceRoot) {
|
|
normalized = normalized.replaceAll(workspaceRoot, '__WORKSPACE__');
|
|
}
|
|
|
|
normalized = normalized.replace(UUID_RE, '__UUID__');
|
|
normalized = normalized.replace(NANOID_RE, '__ID__');
|
|
normalized = normalized.replace(ISO_TIMESTAMP_RE, '__TIMESTAMP__');
|
|
normalized = normalized.replace(UNIX_EPOCH_MS_RE, '__EPOCH__');
|
|
normalized = normalized.replace(SESSION_NUM_RE, 'session__N__');
|
|
normalized = normalized.replace(AGENT_WORKDIR_RE, 'agent-workdirs/__AGENT__');
|
|
|
|
return normalized;
|
|
}
|
|
|
|
/**
|
|
* Strip the prompt value from CLI args to produce stable modelArgs for the cassette key.
|
|
*
|
|
* Handles all provider prompt flag styles:
|
|
* - Native: `-p <prompt>` (Claude)
|
|
* - Flag: `--prompt <prompt>`, `-p <prompt>` (Gemini, Cursor, Auggie, Amp, Opencode)
|
|
* - Also removes the bare prompt value if it appears as a positional arg.
|
|
*/
|
|
export function stripPromptFromArgs(args: string[], prompt: string): string[] {
|
|
if (!prompt) return [...args];
|
|
|
|
const result: string[] = [];
|
|
let i = 0;
|
|
while (i < args.length) {
|
|
const arg = args[i];
|
|
const PROMPT_FLAGS = ['-p', '--prompt', '--message'];
|
|
|
|
if (PROMPT_FLAGS.includes(arg) && args[i + 1] === prompt) {
|
|
i += 2; // skip flag + value
|
|
} else if (arg === prompt) {
|
|
i += 1; // skip bare positional prompt
|
|
} else {
|
|
result.push(arg);
|
|
i++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|