Run agents in Obsidian - OpenCode, Codex, Claude Code etc.
This report summarizes scanner evidence for logancyang/obsidian-copilot, a TypeScript project with 7,919 GitHub stars. SynthScan examined 326,065 lines of code across 1701 source files, recording 400 pattern matches distributed across 11 syntactic categories. The legacy adjusted pattern signal is 1.6 (Low pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Payload schema: 1.0.0 · Engine: 2.0.0 · Scoring: heuristic-density-v1 · Scan date: 2026-10-31.
Can maintainers explain intent, assumptions, and trade-offs? 379 finding(s).
Are imports declared and package/API names verified? 9 finding(s).
Are wrappers, guards, and repeated structures necessary? 343 finding(s).
Which changes need focused human review? 400 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 400 distinct pattern matches across 11 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/bundleSizeGuard.js | 67 | function dedupeEsbuildLegalComments(source) { | CODE |
| LOW | dev/gallery/audit.ts | 217 | function hasVisibleUnsupportedColor(value: string): boolean { | CODE |
| LOW | dev/gallery/audit.ts | 222 | export function resolveObsidianColorTokens(doc: Document): ReadonlySet<string> { | CODE |
| LOW | dev/gallery/main.test.ts | 215 | function installImperativeRenderSimulation(retainedClosedStoryId?: string): jest.Mock { | CODE |
| LOW | src/aiParams.ts | 104 | export function subscribeToModelKeyChange(callback: () => void): () => void { | CODE |
| LOW | src/aiParams.ts | 118 | export function subscribeToChainTypeChange(callback: () => void): () => void { | CODE |
| LOW | src/utils.ts | 172 | export function getPropertyValuesFromNote(app: App, file: TFile, key: string): string[] { | CODE |
| LOW | src/utils.ts | 272 | export function isAllowedFileForNoteContext(file: TFile | null): boolean { | CODE |
| LOW | src/utils.ts | 303 | function resolveNoteFilesFromTitles(noteTitles: string[], vault: Vault): TFile[] { | CODE |
| LOW | src/utils.ts | 359 | export function processVariableNameForNotePath(variableName: string): string { | CODE |
| LOW | src/utils.ts | 547 | function createReadableStreamFromString(input: string) { | CODE |
| LOW | src/plusUtils.ts | 95 | function subscribeToVerifiedEntitlement(listener: () => void): () => void { | CODE |
| LOW | src/plusUtils.ts | 139 | export async function ensureMultiAgentEntitlement(app?: App): Promise<boolean> { | CODE |
| LOW | src/plusUtils.ts | 149 | export function showMultiAgentUpgradePrompt(): void { | CODE |
| LOW | src/plusUtils.ts | 255 | function waitForLicensedChatModelId(): Promise<string | undefined> { | CODE |
| LOW | src/plusUtils.ts | 306 | export function markPaidPendingEntitlement(): void { | CODE |
| LOW | src/plusUtils.test.ts | 185 | function settingsWithFlashConfigured(): CopilotSettings { | CODE |
| LOW | src/instructions/agentsFile.test.ts | 14 | function setCaseInsensitiveFilesystem(value: boolean) { | CODE |
| LOW | src/instructions/agentsFile.ts | 20 | export async function agentsFileIsUninitialized(app: App, folderPath: string): Promise<boolean> { | CODE |
| LOW | src/instructions/agentsFile.ts | 47 | export async function removeGeneratedInstructionFiles(app: App, folderPath: string): Promise<void> { | CODE |
| LOW | src/instructions/agentsFile.ts | 117 | export async function ensureAgentsFileForDiscovery(app: App, folderPath: string): Promise<void> { | CODE |
| LOW | src/instructions/agentsFile.ts | 140 | export function findCachedInstructionFile(app: App, filePath: string): TFile | null { | CODE |
| LOW | src/instructions/agentsFile.ts | 168 | async function convertLegacyGeneratedFile( | CODE |
| LOW | src/settings/revealCopilotFolder.ts | 16 | export async function revealHiddenCopilotFolder( | CODE |
| LOW | src/settings/deviceProfiles.ts | 68 | function stripDeviceFieldsFromBackends(backends: Backends | undefined): Backends { | CODE |
| LOW | src/settings/model.ts | 349 | function preserveProvidersWithCredentials( | CODE |
| LOW | src/settings/model.ts | 358 | function preserveConfiguredModelsForProviders( | CODE |
| LOW | src/settings/model.ts | 421 | export function subscribeToSettingsChange( | CODE |
| LOW | src/settings/model.ts | 846 | export function sanitizeBuiltinPreferences(raw: unknown): BuiltinPreferences { | CODE |
| LOW | src/settings/model.ts | 1031 | function sanitizeClaudeAutoModePermission(raw: unknown): ClaudeAutoModePermission | undefined { | CODE |
| LOW | src/settings/model.ts | 1038 | function sanitizeClaudeBackendSettings(raw: unknown): ClaudeBackendSettings { | CODE |
| LOW | src/settings/model.ts | 1050 | function sanitizeCodexBackendSettings(raw: unknown): CodexBackendSettings { | CODE |
| LOW | src/settings/model.ts | 1071 | function sanitizeOpencodeBackendSettings(raw: unknown): OpencodeBackendSettings { | CODE |
| LOW | src/settings/model.ts | 1094 | function sanitizeDeviceAgentProfile(raw: unknown): DeviceAgentProfile | undefined { | CODE |
| LOW | src/settings/openSettings.test.ts | 65 | function settingsWithCorePluginsTab() { | CODE |
| LOW | src/settings/upgradeNotice.ts | 70 | export function buildUpgradeRelocationEntries( | CODE |
| LOW | src/settings/skillLoadErrorState.ts | 6 | export function publishSkillLoadErrorCount(count: number): void { | CODE |
| LOW | src/settings/copilotRootChange.ts | 39 | export function findCopilotRootFileConflict(app: App, folder: string): string | null { | CODE |
| LOW | src/settings/openSettings.ts | 45 | export function consumeRequestedCopilotSettingsTab(): CopilotSettingsTabId { | CODE |
| LOW⚡ | src/settings/copilotFolder.ts | 26 | export function deriveConversationsFolder(settings: FolderSettings): string { | CODE |
| LOW⚡ | src/settings/copilotFolder.ts | 30 | export function deriveConversationAttachmentsFolder(conversationsFolder: string): string { | CODE |
| LOW⚡ | src/settings/copilotFolder.ts | 34 | export function deriveCustomPromptsFolder(settings: FolderSettings): string { | CODE |
| LOW⚡ | src/settings/copilotFolder.ts | 38 | export function deriveSystemPromptsFolder(settings: FolderSettings): string { | CODE |
| LOW⚡ | src/settings/copilotFolder.ts | 54 | export function getEffectiveCopilotFolder(): string { | CODE |
| LOW⚡ | src/settings/copilotFolder.ts | 58 | export function getEffectiveConversationsFolder(): string { | CODE |
| LOW⚡ | src/settings/copilotFolder.ts | 62 | export function getEffectiveCustomPromptsFolder(): string { | CODE |
| LOW⚡ | src/settings/copilotFolder.ts | 66 | export function getEffectiveSystemPromptsFolder(): string { | CODE |
| LOW | src/settings/copilotFolder.ts | 78 | export function getEffectiveProjectsFolder(): string { | CODE |
| LOW | src/settings/v2/components/configuredModelGrouping.ts | 80 | export function opencodeOnlySubGroupLabel(model: ConfiguredModel, provider: Provider): string { | CODE |
| LOW | src/settings/v2/components/configuredModelGrouping.ts | 116 | function opencodeProviderDestination(providerId: string): Destination { | CODE |
| LOW | src/tools/ToolResultFormatter.ts | 1 | export function deriveReadNoteDisplayName(rawNotePath: string): string { | CODE |
| LOW | src/tools/NoteTools.ts | 258 | function extractLinkedNoteMetadata( | CODE |
| LOW | src/tools/TimeTools.ts | 456 | async function convertTimeBetweenTimezones( | CODE |
| LOW | src/types/message.ts | 36 | export function isNoteSelectedTextContext( | CODE |
| LOW | src/context/contextUpdatesBlockBuilder.ts | 7 | export function buildProjectContextUpdatesBlock(): string { | CODE |
| LOW | src/context/L2ContextCompactor.ts | 15 | function extractSourceFromCommonTags(xmlBlock: string): string { | CODE |
| LOW | src/LLMProviders/selfHostServices.test.ts | 57 | function requestAgentSearchChannel( | CODE |
| LOW | src/LLMProviders/selfHostServices.ts | 46 | export function createSelfHostWebSearchAgentBridge( | CODE |
| LOW | src/LLMProviders/chainRunner/README.md | 478 | async function executeSequentialToolCall( | CODE |
| LOW | …c/LLMProviders/chainRunner/utils/toolPromptDebugger.ts | 59 | function formatPromptSectionsWithAnnotations(sections: PromptSection[]): string { | CODE |
| 271 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| CRITICAL | src/agentMode/ui/useRelevantNotesPaneOpen.test.tsx | 76 | expect(fixture.workspace.offref.mock.calls.map(([ref]) => ref.name).sort()).toEqual([ | CODE |
| CRITICAL | src/agentMode/skills/SkillManager.test.ts | 277 | const renameHandler = app.vault.on.mock.calls.find(([event]) => event === "rename")?.[1]; | CODE |
| CRITICAL | src/agentMode/skills/SkillManager.test.ts | 769 | const call = app.vault.on.mock.calls.find(([e]) => e === event); | CODE |
| CRITICAL | src/components/modals/OpenArtifactsModal.tsx | 114 | await event.currentTarget.win.navigator.clipboard.writeText(receipt.url); | CODE |
| CRITICAL | src/components/modals/OpenArtifactsModal.tsx | 226 | await event.currentTarget.win.navigator.clipboard.writeText(state?.url ?? docUrl ?? ""); | CODE |
| CRITICAL | src/services/releaseUpdateNotice.ts | 26 | const fragment = app.workspace.containerEl.doc.win.createFragment(); | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | .github/workflows/release.yml | 76 | # ── Step 2: Checkout the merge commit on master ───────────────────────── | COMMENT |
| MEDIUM⚡ | .github/workflows/release.yml | 132 | # ── Step 4: Set up Node.js ─────────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | .github/workflows/release.yml | 139 | # ── Step 5: Install dependencies ──────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | .github/workflows/release.yml | 146 | # ── Step 6: Build the plugin ───────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | .github/workflows/release.yml | 150 | # ── Step 7: Write PR body to a file (avoids shell injection) ───────────── | COMMENT |
| MEDIUM⚡ | .github/workflows/release.yml | 159 | # ── Step 8: Check if release already exists (idempotency guard) ───────── | COMMENT |
| MEDIUM | .github/workflows/release.yml | 172 | # ── Step 9: Prepare release-asset manifest ────────────────────────────── | COMMENT |
| MEDIUM | .github/workflows/release.yml | 186 | # ── Step 10: Generate build provenance attestation ────────────────────── | COMMENT |
| MEDIUM | .github/workflows/release.yml | 204 | # ── Step 11: Create GitHub Release ─────────────────────────────────────── | COMMENT |
| MEDIUM | .github/workflows/canary.yml | 46 | # ── Step 1: Authorize the commenter and resolve the PR ────────────────── | COMMENT |
| MEDIUM | .github/workflows/canary.yml | 79 | # ── Step 2: Checkout the PR head ──────────────────────────────────────── | COMMENT |
| MEDIUM | .github/workflows/canary.yml | 135 | # ── Step 4: Replace this version's previous canary ────────────────────── | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | .claude/agents/prerelease.md | 20 | ### Step 0: Pre-flight Sanity Checks | COMMENT |
| LOW | .claude/agents/prerelease.md | 99 | ### Step 1: Determine Prerelease Identity | COMMENT |
| LOW | .claude/agents/prerelease.md | 110 | ### Step 2: Prepare the Branch | COMMENT |
| LOW | .claude/agents/prerelease.md | 123 | ### Step 3: Bump the Version | COMMENT |
| LOW | .claude/agents/prerelease.md | 147 | ### Step 4: Gather and Understand Merged PRs | COMMENT |
| LOW | .claude/agents/prerelease.md | 158 | ### Step 5: Generate Prerelease Notes | COMMENT |
| LOW | .claude/agents/prerelease.md | 203 | ### Step 6: Update RELEASES.md | COMMENT |
| LOW | .claude/agents/prerelease.md | 209 | ### Step 7: Commit and Create PR | COMMENT |
| LOW | .claude/agents/prerelease.md | 238 | ### Step 8: Report Back | COMMENT |
| LOW | .claude/agents/release.md | 22 | ### Step 0: Pre-flight Sanity Checks | COMMENT |
| LOW | .claude/agents/release.md | 94 | ### Step 1: Determine Release Type | COMMENT |
| LOW | .claude/agents/release.md | 102 | ### Step 2: Prepare the Branch | COMMENT |
| LOW | .claude/agents/release.md | 115 | ### Step 3: Bump the Version | COMMENT |
| LOW | .claude/agents/release.md | 123 | ### Step 4: Gather and Understand Merged PRs | COMMENT |
| LOW | .claude/agents/release.md | 159 | ### Step 5: Generate Release Notes | COMMENT |
| LOW | .claude/agents/release.md | 195 | ### Step 6: Update RELEASES.md | COMMENT |
| LOW | .claude/agents/release.md | 199 | ### Step 7: Commit and Create PR | COMMENT |
| LOW | .claude/agents/release.md | 226 | ### Step 8: Report Back | COMMENT |
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 180 | // Step 1: Extract video metadata from JSON-LD (structured data) | COMMENT |
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 250 | // Step 2: Fallback to DOM selectors for missing metadata | COMMENT |
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 266 | // Step 3: Find or open the transcript panel | COMMENT |
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 330 | // Step 4: Extract transcript segments from the panel | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | RELEASES.md | 1570 | - #1809 Show embedding model @logancyang | CODE |
| MEDIUM | RELEASES.md | 1874 | - #1405 Encrypt embedding model api keys @logancyang | CODE |
| MEDIUM | RELEASES.md | 1927 | - #1266 Add support for rendering `<think>` sections and fix RAG with reasoning models @logancyang | CODE |
| MEDIUM | RELEASES.md | 2033 | - #1107 Add copilot-plus-large embedding model @logancyang | CODE |
| MEDIUM | RELEASES.md | 2121 | - #1001 Add copilot plus multilingual embedding model @logancyang | CODE |
| MEDIUM | RELEASES.md | 2159 | - #969 Enable batch embedding and add experimental `copilot-plus-small` embedding model for Plus users @logancyang | CODE |
| MEDIUM | local_copilot.md | 34 | #### Important note about setting context window | COMMENT |
| MEDIUM | internal-docs/STYLE_GUIDE.md | 129 | ## Writing testable code (dependency injection) | COMMENT |
| MEDIUM | docs/context-and-mentions.md | 60 | Agent mentions select OpenCode, Claude, or Codex. They are different from note, folder, and web context mentions. See [M | CODE |
| MEDIUM | docs/index.md | 17 | - [Ask multiple agents](agent-mode-and-tools.md#multi-agent-answers) to research or review the same question in parallel | CODE |
| MEDIUM | src/search/v3/README.md | 76 | ### 3. Merge and dedup at orchestration layer | COMMENT |
| MEDIUM | src/search/v3/README.md | 86 | ### 4. How they reach final RAG context | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/obsidian-vault.sh | 1 | # Sourced by the scripts that deploy into $COPILOT_TEST_VAULT_PATH and reload a | COMMENT |
| LOW | scripts/seed-skills-scenarios.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | .github/workflows/canary.yml | 1 | # Canary workflow: a maintainer comments `/canary` on an open stable release PR | COMMENT |
| LOW | src/settings/model.ts | 381 | } | COMMENT |
| LOW | src/agentMode/backends/opencode/ui/opencodeVersion.ts | 1 | // Before bumping, check that opencodeCopilotPlugin.ts still matches OpenCode's | COMMENT |
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 261 | if (!duration) { | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 180 | // Step 1: Extract video metadata from JSON-LD (structured data) | COMMENT |
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 250 | // Step 2: Fallback to DOM selectors for missing metadata | COMMENT |
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 266 | // Step 3: Find or open the transcript panel | COMMENT |
| LOW | …c/services/webViewerService/webViewerServiceActions.ts | 330 | // Step 4: Extract transcript segments from the panel | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | src/styles/tailwind.css | 3 | This file is generated by Copilot plugin. | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | jest.setup.js | 61 | for (const key of ["title", "value", "type", "placeholder", "href"]) { | CODE |
| LOW | src/agentMode/session/AgentSessionManager.test.ts | 4264 | sessionId: "placeholder", | CODE |
| LOW | …/modelManagement/ui/components/ModelChecklist.test.tsx | 95 | expect(inputs[1].getAttribute("placeholder")).toBe("Search available models…"); | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/test-reset-data.sh | 7 | # Usage: | COMMENT |
| LOW | scripts/seed-skills-scenarios.sh | 8 | # Usage: | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | scripts/seed-skills-scenarios.sh | 113 | # Create a relative symlink from an agent skills folder back to the canonical | COMMENT |