From 7fbf33de2196c61ed5b95b5526c1530e98319542 Mon Sep 17 00:00:00 2001 From: Fadi Shehadeh Date: Tue, 21 Jul 2026 13:27:27 -0400 Subject: [PATCH 1/3] chore(deps): remove unused devDependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove @fontsource/material-icons, @material-icons/svg, and simple-icons — none are imported anywhere in src/, tests/, or CSS. Verified by full-repo grep before removal. --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index f12bc637..9f503305 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,6 @@ "devDependencies": { "@fontsource/ibm-plex-mono": "^5.2.7", "@fontsource/ibm-plex-sans": "^5.2.8", - "@fontsource/material-icons": "^5.2.7", - "@material-icons/svg": "^1.0.33", "@types/better-sqlite3": "^7.6.13", "@types/node": "^20.19.41", "@types/react": "^19.2.17", @@ -54,7 +52,6 @@ "drizzle-kit": "^0.31.10", "eslint": "^9.5.0", "eslint-plugin-react-hooks": "^7.1.1", - "simple-icons": "^16.23.0", "tsx": "^4.15.6", "typescript": "^5.4.5", "vite": "^8.0.16", From 633e3d9fdfcefaec3f2ae65064430e7e1ce1feaf Mon Sep 17 00:00:00 2001 From: Fadi Shehadeh Date: Tue, 21 Jul 2026 13:27:43 -0400 Subject: [PATCH 2/3] refactor: remove confirmed dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit admin - Delete defaultSkillSources.ts (PRELOADED_PROJECT_SKILL_SOURCE / preloadedProjectSkillSources — zero importers) - Drop generateSshKey() and getSshPublicKey() from ui/api.ts (superseded by generateSshKeyPair; no callers) review - Delete CopilotReviewAgent class + its test file; the class was never instantiated in production (all reviews run via Docker REVIEW_MODE=1); docs already called it 'legacy, unused' - Remove the ReviewAgent interface from reviewOrchestrator.ts (only existed to be implemented by the deleted class) - Clean dead CopilotReviewAgent mock from runtimeBootstrap.test.ts orchestrator - Remove handleGerritEvent() — Gerrit-named alias for handleReviewEvent(); update 4 test call sites - Remove unreachable else-branch in pushProjectChanges: all three VCS connectors implement pushDirect, making vcsConnector.push() path dead; replace with an explicit guard throw - Remove appendTicketFooter() and buildTicketFooter() private methods (only consumed by the dead else-branch) - Drop hasTicketFooter from the ticketFooterFormatter import and the now-unused ticketUrl parameter from pushProjectChanges - Remove the two dead test describe blocks from orchestratorCommitMessage.test.ts (same logic covered by ticketFooterFormatter.test.ts) plugins / bootstrap - Drop export from getActiveIntegrationsByType and getPrimaryActiveIntegration in runtimeBuilder.ts (zero external callers; functions remain as module-private helpers) - Drop export from getProviderTechnicalCapabilities in registry.ts (only called by getPluginCapabilities in the same file) All gates pass: npm test (2129), typecheck, lint. --- src/admin/defaultSkillSources.ts | 12 - src/admin/ui/api.ts | 8 - src/bootstrap/runtimeBuilder.ts | 4 +- src/orchestrator/orchestrator.ts | 54 +--- src/plugins/registry.ts | 2 +- src/review/copilotReviewAgent.ts | 282 ------------------- src/review/reviewOrchestrator.ts | 14 - tests/unit/copilotReviewAgent.test.ts | 274 ------------------ tests/unit/orchestrator.test.ts | 8 +- tests/unit/orchestratorCommitMessage.test.ts | 104 +------ tests/unit/runtimeBootstrap.test.ts | 3 - 11 files changed, 13 insertions(+), 752 deletions(-) delete mode 100644 src/admin/defaultSkillSources.ts delete mode 100644 src/review/copilotReviewAgent.ts delete mode 100644 tests/unit/copilotReviewAgent.test.ts diff --git a/src/admin/defaultSkillSources.ts b/src/admin/defaultSkillSources.ts deleted file mode 100644 index 03b5e02b..00000000 --- a/src/admin/defaultSkillSources.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { SkillSource } from "./adminProjectsRoutes.js"; - -export const PRELOADED_PROJECT_SKILL_SOURCE: SkillSource = { - source: "ssh://g1.sfl.io/sfl/agent-skills", - skills: [], - installAll: true, - sshPort: 29419, -}; - -export function preloadedProjectSkillSources(): SkillSource[] { - return [{ ...PRELOADED_PROJECT_SKILL_SOURCE, skills: [...PRELOADED_PROJECT_SKILL_SOURCE.skills] }]; -} diff --git a/src/admin/ui/api.ts b/src/admin/ui/api.ts index 156b0243..24ba0c36 100644 --- a/src/admin/ui/api.ts +++ b/src/admin/ui/api.ts @@ -186,19 +186,11 @@ export function getMe(): Promise { export interface AgentKey { publicKey: string; keyType: string; comment: string } -export function generateSshKey(integrationId: string): Promise<{ publicKey: string }> { - return request<{ publicKey: string }>("POST", `/api/admin/integrations/${integrationId}/ssh-key/generate`); -} - /** Generate a key pair without requiring an existing integration (returns both values for in-form state). */ export function generateSshKeyPair(provider: string, sshUser?: string): Promise<{ sshPrivateKeyEnc: string; sshPublicKey: string }> { return request<{ sshPrivateKeyEnc: string; sshPublicKey: string }>("POST", "/api/admin/ssh-key/generate", { provider, sshUser }); } -export function getSshPublicKey(integrationId: string): Promise<{ publicKey: string | null }> { - return request<{ publicKey: string | null }>("GET", `/api/admin/integrations/${integrationId}/ssh-key/public`); -} - export function listAgentKeys(): Promise<{ keys: AgentKey[]; agentAvailable: boolean }> { return request<{ keys: AgentKey[]; agentAvailable: boolean }>("GET", "/api/admin/ssh-agent/keys"); } diff --git a/src/bootstrap/runtimeBuilder.ts b/src/bootstrap/runtimeBuilder.ts index 6c25dd90..1a36ee9e 100644 --- a/src/bootstrap/runtimeBuilder.ts +++ b/src/bootstrap/runtimeBuilder.ts @@ -39,7 +39,7 @@ export function parseIntegrationConfig(integration: Integration | null): Record< } /** Return all active integrations of `provider`, sorted newest-first. */ -export function getActiveIntegrationsByType(pluginManager: PluginManager, provider: ProviderId): Integration[] { +function getActiveIntegrationsByType(pluginManager: PluginManager, provider: ProviderId): Integration[] { return pluginManager .getActiveIntegrationsByProvider(provider) .slice() @@ -47,7 +47,7 @@ export function getActiveIntegrationsByType(pluginManager: PluginManager, provid } /** Return the most-recently-updated active integration of `provider`, or null. */ -export function getPrimaryActiveIntegration(pluginManager: PluginManager, provider: ProviderId): Integration | null { +function getPrimaryActiveIntegration(pluginManager: PluginManager, provider: ProviderId): Integration | null { return getActiveIntegrationsByType(pluginManager, provider)[0] ?? null; } diff --git a/src/orchestrator/orchestrator.ts b/src/orchestrator/orchestrator.ts index 3129a651..7886a769 100644 --- a/src/orchestrator/orchestrator.ts +++ b/src/orchestrator/orchestrator.ts @@ -1,6 +1,6 @@ import pRetry from "p-retry"; import { randomUUID, createHash } from "crypto"; -import { formatTicketFooter, hasTicketFooter } from "../utils/ticketFooterFormatter.js"; +import { formatTicketFooter } from "../utils/ticketFooterFormatter.js"; import type { AgentAdapter, CommitDescriptor, @@ -319,11 +319,6 @@ export class Orchestrator { await this.checkReviewProgress(task); } - /** Gerrit-flavoured alias for handleReviewEvent. */ - async handleGerritEvent(changeId: ExternalChangeId): Promise { - await this.handleReviewEvent(changeId); - } - /** * Webhook entry points — look up the task for a review-system change id and * apply the appropriate lifecycle step. All three are no-ops for unknown or terminal tasks; @@ -886,7 +881,6 @@ export class Orchestrator { handle, projectPushTargets, commitMessage, - context.ticketUrl ?? "", agentResult.commits, projectRecord.gerritTopicOverride ); @@ -1465,7 +1459,6 @@ export class Orchestrator { handle: WorkspaceHandle, pushTargets: import("../interfaces.js").ProjectPushTargetRecord[], fallbackCommitMessage: string, - ticketUrl: string, agentCommits: CommitDescriptor[] | undefined = undefined, topicOverride: string | null = null ): Promise { @@ -1537,15 +1530,12 @@ export class Orchestrator { const volumeOpts = { volumeName: handle.volumeName, image: handle.containerImage, subPath: target.localPath }; try { - const commitMsg = this.appendTicketFooter(fallbackCommitMessage, task.ticketId, ticketUrl, task.ticketSourceLabel); const subjectHash = createHash("sha1").update(fallbackCommitMessage.split("\n")[0] ?? "").digest("hex"); - let pushResult; - if (vcsConnector.pushDirect) { - pushResult = await vcsConnector.pushDirect(handle.hostWorkspacePath, ref, topic, volumeOpts); - } else { - pushResult = await vcsConnector.push(handle.hostWorkspacePath, ref, commitMsg, undefined, volumeOpts); + if (!vcsConnector.pushDirect) { + throw new Error(`VCS connector for ${reviewSystemLabel} does not implement pushDirect`); } + const pushResult = await vcsConnector.pushDirect(handle.hostWorkspacePath, ref, topic, volumeOpts); // Use Change-Ids from agent commits when available — this is the source of truth // for multi-commit pushes where pushResult.changeId only reflects HEAD (the last commit). @@ -1825,42 +1815,6 @@ export class Orchestrator { return `feat: ${subject}`; } - /** - * Appends a ticket reference footer to a conventional commit message. - * - * The footer is formatted using the modular ticketFooterFormatter utility, - * which supports any configured ticketing system in ID format: "System: #ticketId" - * - * Footer is skipped if the message already contains an existing footer - * (idempotent — safe to call multiple times). - */ - private appendTicketFooter(message: string, ticketId: string, ticketUrl: string, ticketSourceLabel?: string): string { - if (hasTicketFooter(message, ticketSourceLabel)) { - return message; - } - - const footer = this.buildTicketFooter(ticketId, ticketUrl, ticketSourceLabel); - if (!footer) return message; - - return `${message.trimEnd()}\n\n${footer}\n`; - } - - /** - * Builds the footer line using the modular ticketFooterFormatter utility. - * Returns null if no footer is applicable (unknown system or missing data). - * - * All supported systems use ID format: "System: #ticketId" - * This is simple, consistent, and works across all review systems (GitLab, Gerrit, etc.) - * and is future-proof for new ticketing systems. - * - * To add support for a new ticketing system: - * 1. Add configuration to TICKET_SYSTEM_CONFIG in ticketFooterFormatter.ts - * 2. No changes needed here — automatically supported. - */ - private buildTicketFooter(ticketId: string, ticketUrl: string, ticketSourceLabel?: string): string | null { - return formatTicketFooter(ticketId, ticketUrl, ticketSourceLabel); - } - /** Extract acceptance-criteria lines (checklist or numbered items) from a ticket description. */ private extractAcceptanceCriteria(description: string): string[] { return description diff --git a/src/plugins/registry.ts b/src/plugins/registry.ts index 6adfe9fe..0b993008 100644 --- a/src/plugins/registry.ts +++ b/src/plugins/registry.ts @@ -348,7 +348,7 @@ export function getCapabilityIntake( } /** Return the technical (non-domain) capabilities derived from descriptor hooks. */ -export function getProviderTechnicalCapabilities(descriptor: ProviderDescriptor): TechnicalCapability[] { +function getProviderTechnicalCapabilities(descriptor: ProviderDescriptor): TechnicalCapability[] { const technical: TechnicalCapability[] = []; if (descriptor.oauth) technical.push("oauth"); if (descriptor.discoverResources) technical.push("discovery"); diff --git a/src/review/copilotReviewAgent.ts b/src/review/copilotReviewAgent.ts deleted file mode 100644 index bd649e6a..00000000 --- a/src/review/copilotReviewAgent.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { createRequire } from "node:module"; -import { getLogger } from "../logger.js"; -import type { ReviewAgent } from "./reviewOrchestrator.js"; -import type { ExternalChangeId } from "../interfaces.js"; - -const log = getLogger("copilot-review-agent"); - -/** - * Minimal subset of the `@github/copilot-sdk` API we rely on. Declaring it - * locally avoids a build-time hard dependency on the SDK's exact d.ts shape - * and lets tests inject a fake `createClient`. - */ -export interface CopilotSdkSession { - sendAndWait(input: { prompt: string }, timeoutMs?: number): Promise; - on(eventType: string, handler: (event: unknown) => void): (() => void) | void; -} -export interface CopilotSdkClient { - createSession(opts: { - model?: string | undefined; - workingDirectory?: string | undefined; - systemMessage?: { content: string } | undefined; - onPermissionRequest?: ((req: unknown) => unknown) | undefined; - infiniteSessions?: { enabled: boolean } | undefined; - }): Promise; -} -export type CopilotSdkClientFactory = (opts: { env: NodeJS.ProcessEnv; githubToken: string }) => CopilotSdkClient; - -/** - * Structured event emitted from the SDK session during a review. - * Matches the shape the agent-worker emits on stderr so both execution - * paths produce identical event streams. - */ -export interface ReviewStreamEvent { - type: string; - data: Record; -} - -export interface CopilotReviewAgentConfig { - /** GitHub token used to authenticate the review-side Copilot SDK client. */ - githubToken: string; - /** Override the model (defaults to whatever the integration is configured with). */ - model?: string | undefined; - /** System prompt for the review session. Loaded from DB (review-system-* prompt). */ - systemPrompt: string; - /** SDK client factory. Defaults to `@github/copilot-sdk`'s CopilotClient. */ - createClient?: CopilotSdkClientFactory | undefined; - /** Hard upper bound on a single review call (ms). */ - timeoutMs?: number | undefined; -} - -// 9 minutes: slightly under the agent-worker sendAndWait timeout so the orchestrator can surface a clean error first. -const DEFAULT_TIMEOUT_MS = 9 * 60 * 1000; // 9 minutes - -/** Copilot-backed `ReviewAgent`. Shares the same integration as the code-gen adapter. */ -export class CopilotReviewAgent implements ReviewAgent { - private readonly githubToken: string; - private readonly model: string | undefined; - private readonly systemPrompt: string; - private readonly createClient: CopilotSdkClientFactory; - private readonly timeoutMs: number; - - constructor(config: CopilotReviewAgentConfig) { - const githubToken = config.githubToken.trim(); - if (githubToken.length === 0) { - throw new Error("CopilotReviewAgent: githubToken is required"); - } - this.githubToken = githubToken; - this.model = config.model; - this.systemPrompt = config.systemPrompt.trim(); - if (this.systemPrompt.length === 0) { - throw new Error("CopilotReviewAgent: systemPrompt must not be empty"); - } - this.createClient = config.createClient ?? defaultCreateClient; - this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS; - } - - /** Run a single code-review session and return the raw text output from the agent. */ - async runReview( - input: { - changeId: ExternalChangeId; - patchset: number; - project: string; - prompt: string; - workingDirectory?: string | undefined; - }, - onEvent?: ((event: ReviewStreamEvent) => void) | undefined, - ): Promise<{ rawOutput: string }> { - log.info( - { changeId: input.changeId, patchset: input.patchset, project: input.project }, - "running Copilot review session" - ); - - const client = this.createClient({ env: { ...process.env, GITHUB_TOKEN: this.githubToken }, githubToken: this.githubToken }); - const session = await client.createSession({ - ...(this.model !== undefined ? { model: this.model } : {}), - ...(input.workingDirectory !== undefined ? { workingDirectory: input.workingDirectory } : {}), - systemMessage: { content: this.systemPrompt }, - infiniteSessions: { enabled: false }, - }); - - // Register SDK event handlers so callers get live streaming events. - if (onEvent !== undefined && typeof session.on === "function") { - registerReviewEventHandlers(session, onEvent); - } - - const response = await session.sendAndWait({ prompt: input.prompt }, this.timeoutMs); - const rawOutput = extractContent(response); - if (!rawOutput || rawOutput.trim().length === 0) { - throw new Error("Copilot review session returned empty / no content"); - } - return { rawOutput }; - } -} - -/** - * Walk a few common SDK response shapes to pull out the assistant text. - * The SDK has shifted between `{content}`, `{text}`, `{message:{content}}`, - * and array-of-messages over its 0.x releases; we accept all of them. - */ -function extractContent(response: unknown): string | null { - if (response == null) return null; - if (typeof response === "string") return response; - if (typeof response !== "object") return null; - const r = response as Record; - - // Primary SDK shape: assistant.message event { type, data: { content } } - const data = r["data"]; - if (data && typeof data === "object") { - const d = data as Record; - if (typeof d["content"] === "string") return d["content"] as string; - } - - if (typeof r["content"] === "string") return r["content"] as string; - if (typeof r["text"] === "string") return r["text"] as string; - if (typeof r["output"] === "string") return r["output"] as string; - - const message = r["message"]; - if (message && typeof message === "object") { - const m = message as Record; - if (typeof m["content"] === "string") return m["content"] as string; - } - - const messages = r["messages"]; - if (Array.isArray(messages)) { - // Use the last assistant message with non-empty content. - for (let i = messages.length - 1; i >= 0; i--) { - const m = messages[i]; - if (m && typeof m === "object") { - const c = (m as Record)["content"]; - if (typeof c === "string" && c.trim().length > 0) return c; - } - } - } - - return null; -} - -/** - * Deep-find a string value for any of the given keys within a nested object. - */ -function deepFindStr(obj: unknown, keys: string[]): string | null { - const seen = new Set(); - function visit(value: unknown): string | null { - if (!value || typeof value !== "object") return null; - if (seen.has(value)) return null; - seen.add(value); - const rec = value as Record; - for (const k of keys) { - if (typeof rec[k] === "string" && (rec[k] as string).trim()) return rec[k] as string; - } - for (const nested of Object.values(rec)) { - const found = visit(nested); - if (found !== null) return found; - } - return null; - } - return visit(obj); -} - -/** - * Deep-find a numeric value for any of the given keys within a nested object. - */ -function deepFindNum(obj: unknown, keys: string[]): number | null { - const seen = new Set(); - function visit(value: unknown): number | null { - if (!value || typeof value !== "object") return null; - if (seen.has(value)) return null; - seen.add(value); - const rec = value as Record; - for (const k of keys) { - if (typeof rec[k] === "number" && Number.isFinite(rec[k])) return rec[k] as number; - } - for (const nested of Object.values(rec)) { - const found = visit(nested); - if (found !== null) return found; - } - return null; - } - return visit(obj); -} - -/** - * Register SDK event handlers on a session so callers receive structured - * events identical to those the agent-worker emits on stderr. - */ -function registerReviewEventHandlers( - session: CopilotSdkSession, - onEvent: (event: ReviewStreamEvent) => void, -): void { - session.on("tool.execution_start", (e: unknown) => { - const name = deepFindStr(e, ["name", "toolName", "tool_name"]) ?? "unknown_tool"; - onEvent({ type: "tool.execution_start", data: { name } }); - }); - session.on("tool.execution_complete", (e: unknown) => { - const name = deepFindStr(e, ["name", "toolName", "tool_name"]) ?? "unknown_tool"; - const output = deepFindStr(e, ["output", "result", "content"]); - onEvent({ type: "tool.execution_complete", data: { name, output: output ? output.slice(0, 800) : null } }); - }); - session.on("tool.execution_progress", (e: unknown) => { - const name = deepFindStr(e, ["name", "toolName", "tool_name"]) ?? "unknown_tool"; - onEvent({ type: "tool.execution_progress", data: { name, message: deepFindStr(e, ["message", "progress", "text"]) } }); - }); - session.on("assistant.streaming_delta", (e: unknown) => { - const delta = deepFindStr(e, ["delta", "content", "text"]); - if (delta) onEvent({ type: "assistant.streaming_delta", data: { delta } }); - }); - session.on("assistant.message", (e: unknown) => { - const content = deepFindStr(e, ["content", "text", "message"]); - onEvent({ type: "assistant.message", data: { content: content ? content.slice(0, 3000) : null } }); - }); - session.on("assistant.usage", (e: unknown) => { - onEvent({ - type: "assistant.usage", - data: { - inputTokens: deepFindNum(e, ["inputTokens", "input_tokens", "promptTokens", "prompt_tokens"]), - outputTokens: deepFindNum(e, ["outputTokens", "output_tokens", "completionTokens", "completion_tokens"]), - cacheReadTokens: deepFindNum(e, ["cacheReadTokens", "cache_read_tokens", "cacheReadInputTokens"]), - cacheWriteTokens: deepFindNum(e, ["cacheWriteTokens", "cache_write_tokens", "cacheCreationInputTokens"]), - cost: deepFindNum(e, ["cost"]), - totalNanoAiu: deepFindNum(e, ["totalNanoAiu", "total_nano_aiu"]), - apiCallId: deepFindStr(e, ["apiCallId", "api_call_id"]), - providerCallId: deepFindStr(e, ["providerCallId", "provider_call_id"]), - model: deepFindStr(e, ["model"]), - }, - }); - }); - session.on("session.usage_info", (e: unknown) => { - onEvent({ - type: "session.usage_info", - data: { - tokenLimit: deepFindNum(e, ["tokenLimit"]), - currentTokens: deepFindNum(e, ["currentTokens"]), - }, - }); - }); - session.on("session.error", (e: unknown) => { - const msg = deepFindStr(e, ["message", "error", "reason"]) ?? String(e); - onEvent({ type: "session.error", data: { message: msg } }); - }); -} - -/** - * Lazy default factory: only requires the SDK when actually invoked, so tests - * that inject `createClient` never touch the real package. - */ -const defaultCreateClient: CopilotSdkClientFactory = ({ env, githubToken }) => { - // `@github/copilot-sdk` is a CJS module; use createRequire to load it from - // an ESM source file. We isolate the require here so test mocks via - // `createClient` short-circuit it entirely. - const req = createRequire(import.meta.url); - const sdk = req("@github/copilot-sdk") as { - CopilotClient: new (opts: { env: NodeJS.ProcessEnv; githubToken: string }) => CopilotSdkClient; - approveAll: (req: unknown) => unknown; - }; - const client = new sdk.CopilotClient({ env, githubToken }); - // Wrap createSession so callers always get onPermissionRequest: approveAll - // injected, satisfying the SDK requirement without callers needing to know. - const origCreateSession = client.createSession.bind(client); - client.createSession = (opts): Promise => - origCreateSession({ onPermissionRequest: sdk.approveAll, ...opts }); - return client; -}; diff --git a/src/review/reviewOrchestrator.ts b/src/review/reviewOrchestrator.ts index 4a810517..30bbe7db 100644 --- a/src/review/reviewOrchestrator.ts +++ b/src/review/reviewOrchestrator.ts @@ -31,20 +31,6 @@ import { agentLogBus, pushToTaskBuffer, clearTaskEventBuffer } from "../agents/a const log = getLogger("review-orchestrator"); -/** Legacy interface implemented by `CopilotReviewAgent`. Kept for backward compatibility. */ -export interface ReviewAgent { - runReview( - input: { - changeId: ExternalChangeId; - patchset: number; - project: string; - prompt: string; - workingDirectory?: string | undefined; - }, - onEvent?: ((event: { type: string; data?: unknown }) => void) | undefined, - ): Promise<{ rawOutput: string }>; -} - export interface ReviewOrchestratorDeps { stateStore: Pick< StateStore, diff --git a/tests/unit/copilotReviewAgent.test.ts b/tests/unit/copilotReviewAgent.test.ts deleted file mode 100644 index b64f54bb..00000000 --- a/tests/unit/copilotReviewAgent.test.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { CopilotReviewAgent } from "../../src/review/copilotReviewAgent.js"; -import type { ReviewStreamEvent } from "../../src/review/copilotReviewAgent.js"; -import { makeExternalChangeId } from "../../src/interfaces.js"; - -interface FakeSession { - sendAndWait: any; - on: any; - _handlers: Map void>>; - _emit: (type: string, payload: unknown) => void; -} -interface FakeClient { - createSession: any; -} - -function fakeSdk(rawOutput: string): { client: FakeClient; session: FakeSession } { - const handlers = new Map void>>(); - const session: FakeSession = { - sendAndWait: vi.fn(async () => ({ content: rawOutput })), - on: vi.fn((eventType: string, handler: (e: unknown) => void) => { - if (!handlers.has(eventType)) handlers.set(eventType, []); - handlers.get(eventType)!.push(handler); - return () => { /* unsubscribe stub */ }; - }), - _handlers: handlers, - _emit: (type: string, payload: unknown) => { - for (const h of handlers.get(type) ?? []) h(payload); - }, - }; - const client: FakeClient = { - createSession: vi.fn(async () => session), - }; - return { client, session }; -} - -const promptOk = - "REVIEW_RESULT_START\n" + - '{"comments":[],"summary":"ok","score":1}\n' + - "REVIEW_RESULT_END"; - -describe("CopilotReviewAgent", () => { - let createClient: ReturnType; - - beforeEach(() => { - createClient = vi.fn(); - }); - - it("creates a session and forwards the prompt", async () => { - const { client, session } = fakeSdk(promptOk); - createClient.mockReturnValue(client); - - const agent = new CopilotReviewAgent({ - githubToken: "ghp_review_token", - model: "gpt-4.1", - systemPrompt: "You are a code reviewer.", - createClient: createClient as any, - }); - - const result = await agent.runReview({ - changeId: makeExternalChangeId("I1234"), - patchset: 2, - project: "demo", - prompt: "review this", - }); - - expect(result.rawOutput).toContain("REVIEW_RESULT_START"); - expect(createClient).toHaveBeenCalledWith(expect.objectContaining({ - githubToken: "ghp_review_token", - env: expect.objectContaining({ - GITHUB_TOKEN: "ghp_review_token", - }), - })); - expect(client.createSession).toHaveBeenCalledOnce(); - const sessionArgs = client.createSession.mock.calls[0]?.[0] as { model: string }; - expect(sessionArgs.model).toBe("gpt-4.1"); - expect(session.sendAndWait).toHaveBeenCalledOnce(); - const sent = session.sendAndWait.mock.calls[0]?.[0] as { prompt: string }; - expect(sent.prompt).toBe("review this"); - }); - - it("extracts content from various SDK response shapes", async () => { - const cases: Array = [ - // SDK primary shape: assistant.message event { data: { content } } - { type: "assistant.message", data: { content: promptOk } }, - { data: { content: promptOk } }, - { content: promptOk }, - { text: promptOk }, - { message: { content: promptOk } }, - { messages: [{ content: promptOk }] }, - promptOk, - ]; - - for (const response of cases) { - const session = { sendAndWait: vi.fn(async () => response) } as unknown as FakeSession; - const client: FakeClient = { createSession: vi.fn(async () => session) }; - createClient.mockReturnValue(client); - - const agent = new CopilotReviewAgent({ - githubToken: "ghp_review_token", - systemPrompt: "You are a code reviewer.", - createClient: createClient as any, - }); - - const result = await agent.runReview({ - changeId: makeExternalChangeId("I"), - patchset: 1, - project: "p", - prompt: "x", - }); - expect(result.rawOutput).toContain("REVIEW_RESULT_START"); - } - }); - - it("throws if the SDK returns no parsable content", async () => { - const session = { sendAndWait: vi.fn(async () => ({})) } as unknown as FakeSession; - const client: FakeClient = { createSession: vi.fn(async () => session) }; - createClient.mockReturnValue(client); - - const agent = new CopilotReviewAgent({ - githubToken: "ghp_review_token", - systemPrompt: "You are a code reviewer.", - createClient: createClient as any, - }); - - await expect( - agent.runReview({ - changeId: makeExternalChangeId("I"), - patchset: 1, - project: "p", - prompt: "x", - }) - ).rejects.toThrow(/empty|no content/i); - }); - - it("passes workingDirectory to createSession when provided", async () => { - const { client } = fakeSdk(promptOk); - createClient.mockReturnValue(client); - - const agent = new CopilotReviewAgent({ - githubToken: "ghp_review_token", - systemPrompt: "You are a code reviewer.", - createClient: createClient as any, - }); - - await agent.runReview({ - changeId: makeExternalChangeId("I"), - patchset: 1, - project: "p", - prompt: "x", - workingDirectory: "/tmp/review-42-abc", - }); - - const sessionArgs = client.createSession.mock.calls[0]?.[0] as { workingDirectory?: string }; - expect(sessionArgs.workingDirectory).toBe("/tmp/review-42-abc"); - }); - - it("omits workingDirectory from createSession when not provided", async () => { - const { client } = fakeSdk(promptOk); - createClient.mockReturnValue(client); - - const agent = new CopilotReviewAgent({ - githubToken: "ghp_review_token", - systemPrompt: "You are a code reviewer.", - createClient: createClient as any, - }); - - await agent.runReview({ - changeId: makeExternalChangeId("I"), - patchset: 1, - project: "p", - prompt: "x", - }); - - const sessionArgs = client.createSession.mock.calls[0]?.[0] as Record; - expect("workingDirectory" in sessionArgs).toBe(false); - }); - - it("requires a GitHub token", () => { - expect(() => new CopilotReviewAgent({ githubToken: "", systemPrompt: "system" })).toThrow( - /githubToken/i - ); - }); - - it("uses the provided system prompt when supplied", async () => { - const { client } = fakeSdk(promptOk); - createClient.mockReturnValue(client); - - const agent = new CopilotReviewAgent({ - githubToken: "ghp_review_token", - systemPrompt: "You are a strict reviewer.", - createClient: createClient as any, - }); - - await agent.runReview({ - changeId: makeExternalChangeId("I"), - patchset: 1, - project: "p", - prompt: "review", - }); - - const args = client.createSession.mock.calls[0]?.[0] as { - systemMessage?: { content?: string }; - }; - expect(args.systemMessage?.content).toBe("You are a strict reviewer."); - }); - - it("registers SDK event handlers and forwards events via onEvent callback", async () => { - const { client, session } = fakeSdk(promptOk); - createClient.mockReturnValue(client); - - // Make sendAndWait emit some SDK events before resolving. - (session.sendAndWait as ReturnType).mockImplementation(async () => { - session._emit("tool.execution_start", { name: "read_file", input: { path: "foo.ts" } }); - session._emit("tool.execution_complete", { name: "read_file", result: "content" }); - session._emit("assistant.streaming_delta", { delta: "hello" }); - session._emit("assistant.message", { content: "review result" }); - session._emit("assistant.usage", { inputTokens: 100, outputTokens: 50 }); - session._emit("session.usage_info", { tokenLimit: 8000, currentTokens: 150 }); - session._emit("session.error", { message: "recoverable warning" }); - return { content: promptOk }; - }); - - const agent = new CopilotReviewAgent({ - githubToken: "ghp_review_token", - systemPrompt: "You are a code reviewer.", - createClient: createClient as any, - }); - - const receivedEvents: ReviewStreamEvent[] = []; - await agent.runReview( - { changeId: makeExternalChangeId("I"), patchset: 1, project: "p", prompt: "review" }, - (event) => receivedEvents.push(event), - ); - - // All 7 event types we emitted should have been forwarded. - const types = receivedEvents.map((e) => e.type); - expect(types).toContain("tool.execution_start"); - expect(types).toContain("tool.execution_complete"); - expect(types).toContain("assistant.streaming_delta"); - expect(types).toContain("assistant.message"); - expect(types).toContain("assistant.usage"); - expect(types).toContain("session.usage_info"); - expect(types).toContain("session.error"); - - // Verify data extraction works. - const toolStart = receivedEvents.find((e) => e.type === "tool.execution_start"); - expect(toolStart?.data["name"]).toBe("read_file"); - - const usage = receivedEvents.find((e) => e.type === "assistant.usage"); - expect(usage?.data["inputTokens"]).toBe(100); - expect(usage?.data["outputTokens"]).toBe(50); - }); - - it("does not register event handlers when no onEvent callback is passed", async () => { - const { client, session } = fakeSdk(promptOk); - createClient.mockReturnValue(client); - - const agent = new CopilotReviewAgent({ - githubToken: "ghp_review_token", - systemPrompt: "You are a code reviewer.", - createClient: createClient as any, - }); - - await agent.runReview({ - changeId: makeExternalChangeId("I"), - patchset: 1, - project: "p", - prompt: "review", - }); - - // session.on should not have been called when no callback is provided. - expect(session.on).not.toHaveBeenCalled(); - }); -}); diff --git a/tests/unit/orchestrator.test.ts b/tests/unit/orchestrator.test.ts index 422a8350..bfc15d07 100644 --- a/tests/unit/orchestrator.test.ts +++ b/tests/unit/orchestrator.test.ts @@ -331,7 +331,7 @@ describe("Orchestrator", () => { const stateStore = makeStateStore({ findTaskByExternalChangeId: vi.fn().mockResolvedValue(null) }); const orchestrator = makeOrchestrator({ stateStore }); - await orchestrator.handleGerritEvent(makeExternalChangeId("Imissing")); + await orchestrator.handleReviewEvent(makeExternalChangeId("Imissing")); expect(stateStore.findTaskByExternalChangeId).toHaveBeenCalledWith(null, "Imissing"); }); @@ -346,7 +346,7 @@ describe("Orchestrator", () => { const gerritConnector = makeGerritConnector(); const orchestrator = makeOrchestrator({ stateStore, gerritConnector }); - await orchestrator.handleGerritEvent(gerritChangeId); + await orchestrator.handleReviewEvent(gerritChangeId); expect(gerritConnector.getChangeStatus).not.toHaveBeenCalled(); }); @@ -360,7 +360,7 @@ describe("Orchestrator", () => { const orchestrator = makeOrchestrator({ stateStore }); const checkReviewProgress = vi.spyOn(orchestrator as any, "checkReviewProgress").mockResolvedValue(undefined); - await orchestrator.handleGerritEvent(gerritChangeId); + await orchestrator.handleReviewEvent(gerritChangeId); expect(checkReviewProgress).toHaveBeenCalledWith(task); }); @@ -375,7 +375,7 @@ describe("Orchestrator", () => { const orchestrator = makeOrchestrator({ stateStore }); const checkReviewProgress = vi.spyOn(orchestrator as any, "checkReviewProgress").mockResolvedValue(undefined); - await orchestrator.handleGerritEvent(gerritChangeId); + await orchestrator.handleReviewEvent(gerritChangeId); expect(checkReviewProgress).not.toHaveBeenCalled(); }); diff --git a/tests/unit/orchestratorCommitMessage.test.ts b/tests/unit/orchestratorCommitMessage.test.ts index 042d8c07..5e481f54 100644 --- a/tests/unit/orchestratorCommitMessage.test.ts +++ b/tests/unit/orchestratorCommitMessage.test.ts @@ -1,10 +1,8 @@ /** * Unit tests for Orchestrator.buildCommitMessage() — conventional commit fallback. - * Unit tests for Orchestrator.appendTicketFooter() — ticket reference footer. * - * NOTE: As of the modular refactor, ticket footer formatting is delegated to - * the ticketFooterFormatter utility. These tests verify orchestrator integration. - * For comprehensive footer format tests, see ticketFooterFormatter.test.ts. + * NOTE: Ticket footer formatting was previously tested here but has been moved + * to ticketFooterFormatter.test.ts, which tests the utility directly. */ import { describe, it, expect } from "vitest"; @@ -15,36 +13,6 @@ function callBuildCommitMessage(subject: string): string { return (Orchestrator.prototype as any).buildCommitMessage.call({}, {}, subject) as string; } -// buildTicketFooter is private — access via any cast -function callBuildTicketFooter( - reviewSystem: string, - ticketId: string, - ticketUrl: string, - ticketSourceLabel?: string -): string | null { - const proto = Orchestrator.prototype as any; - const self = { - config: { reviewSystem }, - }; - return proto.buildTicketFooter.call(self, ticketId, ticketUrl, ticketSourceLabel) as string | null; -} - -// appendTicketFooter is private — access via any cast -function callAppendTicketFooter( - reviewSystem: string, - message: string, - ticketId: string, - ticketUrl: string, - ticketSourceLabel?: string -): string { - const proto = Orchestrator.prototype as any; - const self = { - config: { reviewSystem }, - buildTicketFooter: proto.buildTicketFooter, - }; - return proto.appendTicketFooter.call(self, message, ticketId, ticketUrl, ticketSourceLabel) as string; -} - describe("Orchestrator.buildCommitMessage", () => { it("produces a conventional commit with feat type", () => { const msg = callBuildCommitMessage("Add /exit endpoint"); @@ -71,71 +39,3 @@ describe("Orchestrator.buildCommitMessage", () => { expect(msg).not.toMatch(/Automated by/); }); }); - -describe("Orchestrator.buildTicketFooter (modular approach)", () => { - it("formats gitlab as 'GitLab: #14'", () => { - const result = callBuildTicketFooter("gitlab", "14", "https://example.com/14", "gitlab:gl-1"); - expect(result).toBe("GitLab: #14"); - }); - - it("formats redmine as 'Redmine: #123'", () => { - const result = callBuildTicketFooter("gerrit", "123", "http://redmine.local/issues/123", "redmine"); - expect(result).toBe("Redmine: #123"); - }); - - it("returns null for unknown system", () => { - const result = callBuildTicketFooter("gerrit", "42", "https://example.com/42", "unknown-system"); - expect(result).toBeNull(); - }); - - it("returns null when no ticketSourceLabel provided", () => { - const result = callBuildTicketFooter("gerrit", "42", "https://example.com/42", undefined); - expect(result).toBeNull(); - }); -}); - -describe("Orchestrator.appendTicketFooter (integration)", () => { - it("appends 'GitLab: #14' footer for gitlab", () => { - const result = callAppendTicketFooter("gitlab", "feat: add feature", "14", "", "gitlab:gl-1"); - expect(result).toMatch(/\n\nGitLab: #14\n$/); - }); - - it("appends 'Redmine: #123' footer for redmine", () => { - const result = callAppendTicketFooter( - "gerrit", - "feat: add feature", - "123", - "http://redmine.local/issues/123", - "redmine" - ); - expect(result).toMatch(/\n\nRedmine: #123\n$/); - }); - - it("does NOT duplicate footer if message already contains 'GitLab:'", () => { - const msg = "feat: add feature\n\nGitLab: #14\n"; - const result = callAppendTicketFooter("gitlab", msg, "14", "", "gitlab:gl-1"); - expect(result).toBe(msg); - const occurrences = (result.match(/GitLab:/g) ?? []).length; - expect(occurrences).toBe(1); - }); - - it("does NOT duplicate footer if message already contains 'Redmine:'", () => { - const msg = "feat: add feature\n\nRedmine: #123\n"; - const result = callAppendTicketFooter("gerrit", msg, "123", "http://redmine.local/issues/123", "redmine"); - expect(result).toBe(msg); - const occurrences = (result.match(/Redmine:/g) ?? []).length; - expect(occurrences).toBe(1); - }); - - it("skips footer when source label is unknown", () => { - const msg = "feat: add feature"; - const result = callAppendTicketFooter("gerrit", msg, "42", "https://example.com/42", "unknown-system"); - expect(result).toBe(msg); - }); - - it("skips footer if reviewSystem is not a known type", () => { - const msg = "feat: add exit endpoint"; - const result = callAppendTicketFooter("unknown-system", msg, "14", "https://example.com/123"); - expect(result).toBe(msg); - }); -}); diff --git a/tests/unit/runtimeBootstrap.test.ts b/tests/unit/runtimeBootstrap.test.ts index 69063e0a..bb89fc63 100644 --- a/tests/unit/runtimeBootstrap.test.ts +++ b/tests/unit/runtimeBootstrap.test.ts @@ -406,8 +406,6 @@ async function importRuntime( vi.doMock("../../src/agents/copilotAdapter.js", () => ({ CopilotAdapter, })); - const CopilotReviewAgent = vi.fn().mockImplementation(function () { return { runReview: vi.fn() }; }); - vi.doMock("../../src/review/copilotReviewAgent.js", () => ({ CopilotReviewAgent })); const ReviewOrchestrator = vi.fn().mockImplementation(function () { return { startReviewTask: vi.fn().mockResolvedValue([]), @@ -460,7 +458,6 @@ async function importRuntime( createAdminServer, stateStore, pluginManagerInstance, - CopilotReviewAgent, ReviewOrchestrator, GerritSshReviewProvider, PluginIntegrationStreamEventsManager, From 359811a2fa77d338a76ddcc25ef610733b503141 Mon Sep 17 00:00:00 2001 From: Fadi Shehadeh Date: Wed, 22 Jul 2026 15:58:12 -0400 Subject: [PATCH 3/3] docs: drop copilotReviewAgent refs and sync lockfile Address Copilot review comments on PR #126: - Remove copilotReviewAgent.ts / ReviewAgent references from .github/copilot-instructions.md, .github/context/architecture.md, .github/context/testing.md, docs/ARCHITECTURE.md (file deleted in the prior refactor commit). - Regenerate package-lock.json after dropping @fontsource/material-icons, @material-icons/svg, and simple-icons devDependencies so npm ci stays consistent with package.json. --- .github/context/architecture.md | 1 - .github/context/testing.md | 2 +- .github/copilot-instructions.md | 8 +++---- docs/ARCHITECTURE.md | 2 -- package-lock.json | 40 --------------------------------- 5 files changed, 5 insertions(+), 48 deletions(-) diff --git a/.github/context/architecture.md b/.github/context/architecture.md index ab460429..51cb7507 100644 --- a/.github/context/architecture.md +++ b/.github/context/architecture.md @@ -63,7 +63,6 @@ It builds `TaskContext`, launches agent cycles, persists agent output, manages r ### Review runtime — `src/review/` - `reviewOrchestrator.ts` drives `REVIEW_PENDING → ... → REVIEW_DONE/REVIEW_FAILED`; the agent runs in the workspace container via `workspaceRunner.runReviewInDocker()` (`REVIEW_MODE=1`, prompt read from `USER_PROMPT_FILE`) -- `copilotReviewAgent.ts` (host-side SDK client) is **legacy** — never instantiated by the main bootstrap - `reviewPromptBuilder.ts` and `reviewResultParser.ts` build/parse the review prompt contract `src/index.ts` wires the Docker review path through `buildReviewBundle()` / `buildReviewTrigger()`, resolving the active review integration's `createReviewer()` descriptor hook. diff --git a/.github/context/testing.md b/.github/context/testing.md index 581d2528..2288080b 100644 --- a/.github/context/testing.md +++ b/.github/context/testing.md @@ -31,7 +31,7 @@ tests/ | Agents / Copilot | `copilotAdapter` (+ `.promptInjection`), `copilotConnectionValidator`, `copilotOAuthService`, `copilotModelsService`, `providerAuthService`, `mockAgentAdapter`, `agentEventTypes` (+ `.normalization`), `workerCommitProtocol`, `workerNetworkGuard`, `workerSkills`, `workerLocalSkills` | | Agents / Claude | `claudeAdapter`, `claudeConnectionValidator`, `claudeModelsService` | | Agents / Aider | `aiderAdapter`, `aiderDescriptor`, `aiderConnectionValidator`, `aiderModelsService`, `aiderWorker` | -| Review runtime | `copilotReviewAgent`, `reviewOrchestrator`, `reviewPromptBuilder`, `reviewResultParser`, `reviewLiveLogs`, `commentHash`, `commentSeverity`, `revisionPatchset` | +| Review runtime | `reviewOrchestrator`, `reviewPromptBuilder`, `reviewResultParser`, `reviewLiveLogs`, `commentHash`, `commentSeverity`, `revisionPatchset` | | Cost tracking | `cycleCost`, `stateStore.cost`, `adminCostRoutes` | | Plugins / runtime wiring | `pluginManager` (+ `.multiInstance`), `registry`, `runtimeBootstrap` (historical name; covers bootstrap wiring in `src/index.ts`), `integrationStreamEvents` | | Webhooks | `webhookServer`, `webhookHandlerRegistry` (+ the per-provider handlers listed above) | diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index fd9a0dcc..bdd1d023 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -87,7 +87,7 @@ src/ plugins/ # registry, pluginManager, init, descriptors/{index,github, # gitlab,gerrit,redmine,copilot,claude,aider,mock}.ts (unified # provider descriptors; githubOAuth/gitlabOAuth helpers) - review/ # reviewOrchestrator, copilotReviewAgent, + review/ # reviewOrchestrator, # reviewBootstrap (bundle + trigger factory), # reviewPromptBuilder, reviewResultParser, # commentFilter, commentHash, commentSeverity, @@ -214,12 +214,12 @@ Empty strings in env are treated as `undefined` (helpful for env overrides). ## Copilot Execution 1. **Worker-local headless CLI** — code-generation containers always spawn `copilot --headless` inside the container and connect the SDK to that local CLI server. -2. **Docker review execution** — review tasks also run in the agent container (`REVIEW_MODE=1` via `workspaceRunner.runReviewInDocker`); the worker reads the prompt from `USER_PROMPT_FILE` (`/ve-home/user-prompt.txt`) and returns raw LLM text for the host to parse. `src/review/copilotReviewAgent.ts` (host-side SDK client) is **legacy** — never instantiated in `src/`. +2. **Docker review execution** — review tasks also run in the agent container (`REVIEW_MODE=1` via `workspaceRunner.runReviewInDocker`); the worker reads the prompt from `USER_PROMPT_FILE` (`/ve-home/user-prompt.txt`) and returns raw LLM text for the host to parse. 3. **Container validation fallback** — when the local Node runtime lacks `node:sqlite`, `copilotConnectionValidator` runs the validation script inside `AGENT_CONTAINER_IMAGE`, which also starts a local headless CLI in-container. Worker `sendAndWait` timeout ≈ 540s. Host agent timeout = `AGENT_TIMEOUT_MS` (default 60 min). -Implementation: `src/agents/copilotAdapter.ts`, `src/agents/copilotOAuthService.ts`, `src/agents/copilotModelsService.ts`, `src/agents/copilotConnectionValidator.ts`, `src/review/copilotReviewAgent.ts`, `agent-worker/src/index.ts`. +Implementation: `src/agents/copilotAdapter.ts`, `src/agents/copilotOAuthService.ts`, `src/agents/copilotModelsService.ts`, `src/agents/copilotConnectionValidator.ts`, `agent-worker/src/index.ts`. ## Claude Execution (`agent_execution` alternative to Copilot) @@ -286,7 +286,7 @@ Body lines ≤72 chars. See `typescript-standard` skill. - **One provider, many capabilities**: there is no longer a `github-issue` vs `github-pull-request` (or `gitlab-issue` vs `gitlab-merge-request`) split. A single `github` / `gitlab` provider descriptor exposes multiple domain capabilities; resolve runtime dependencies by capability (`getConnectorForCapability`, `getActiveIntegrationsByCapability`) rather than by an integration type/role. - **Ticket-source uniqueness is app-enforced**: there is no DB unique index across projects for the issue_tracking binding. `projectStore` throws when a second project binds the same `(integrationId, ticketProjectKey)`; keep that check in application code. - **Multi-instance plugins**: all enabled integrations stay active in memory, including multiple rows of the same provider. Resolve runtime dependencies by `integrationId`, capability, or explicit integration lists; do not add new logic that assumes a single active integration per provider. -- **Copilot execution path**: no host/external CLI server support remains. Containers and validation scripts always boot a local headless CLI; reviews run in the agent container with `REVIEW_MODE=1` (`CopilotReviewAgent` is legacy, unused). +- **Copilot execution path**: no host/external CLI server support remains. Containers and validation scripts always boot a local headless CLI; reviews run in the agent container with `REVIEW_MODE=1`. - **Descriptor-driven event streams**: stream-capable integrations are reconciled through `descriptor.streamEvents` plus `PluginManager.getActiveIntegrations()`. Gerrit is the current stream-backed implementation, but the bootstrap is no longer Gerrit-specific. - **Descriptor-driven review backends**: generic review routing resolves active review integrations through `descriptor.createReviewer`; keep provider-specific clone/setup logic in the descriptor and out of `src/index.ts` / `src/review/reviewOrchestrator.ts`. - **Review tasks are integration-scoped**: webhook-triggered review flows must resolve the exact review integration by `integrationId`, and code-review tasks should preserve that integration in `ticketSourceLabel` / derived `ticketId` to avoid collisions between multiple active Gerrit instances. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 3c48e974..1bb04d33 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -142,8 +142,6 @@ src/ review/ # Code-review workflow reviewOrchestrator.ts # REVIEW_PENDING → REVIEW_DONE lifecycle - copilotReviewAgent.ts # LEGACY host-side Copilot SDK client (unused; reviews - # run in-container via REVIEW_MODE=1, see below) reviewPromptBuilder.ts reviewResultParser.ts commentFilter.ts # Filters comments to lines present in the diff diff --git a/package-lock.json b/package-lock.json index e4bd9826..bb9e1362 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,8 +22,6 @@ "devDependencies": { "@fontsource/ibm-plex-mono": "^5.2.7", "@fontsource/ibm-plex-sans": "^5.2.8", - "@fontsource/material-icons": "^5.2.7", - "@material-icons/svg": "^1.0.33", "@types/better-sqlite3": "^7.6.13", "@types/node": "^20.19.41", "@types/react": "^19.2.17", @@ -35,7 +33,6 @@ "drizzle-kit": "^0.31.10", "eslint": "^9.5.0", "eslint-plugin-react-hooks": "^7.1.1", - "simple-icons": "^16.23.0", "tsx": "^4.15.6", "typescript": "^5.4.5", "vite": "^8.0.16", @@ -1049,16 +1046,6 @@ "url": "https://github.com/sponsors/ayuhito" } }, - "node_modules/@fontsource/material-icons": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@fontsource/material-icons/-/material-icons-5.2.7.tgz", - "integrity": "sha512-crPmK0L34lPGmS5GSGLasKpRGQzl95SxMsLM+QhBHPgR9uxSsyI5CUTb0cgoMpjtR+Bf1bC9QOe6pavoybbBwg==", - "dev": true, - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, "node_modules/@github/copilot": { "version": "1.0.45", "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.45.tgz", @@ -1297,13 +1284,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@material-icons/svg": { - "version": "1.0.33", - "resolved": "https://registry.npmjs.org/@material-icons/svg/-/svg-1.0.33.tgz", - "integrity": "sha512-sYXcybBWH3rNijK1D6Dv1Se/aZ7OAC2cCNkn4HiZOXQwkswtRVoDqHPw+GfsBoIC70UintfPXUsmiuaxMicWtw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", @@ -5032,26 +5012,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/simple-icons": { - "version": "16.23.0", - "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.23.0.tgz", - "integrity": "sha512-08MaTpxj9zGYUIe38tfELYkaHiGE1YgbrbXmTBf+GPxi5mEqLSORQqOXrP0QKPdaFuzEDSmW5o4xkbLlFhmdCw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/simple-icons" - }, - { - "type": "github", - "url": "https://github.com/sponsors/simple-icons" - } - ], - "license": "CC0-1.0", - "engines": { - "node": ">=0.12.18" - } - }, "node_modules/sonic-boom": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",