panopticon-cli 0.6.8 → 0.6.9
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/dist/{agents-D_2oRFVf.js → agents-BQOqo27C.js} +1 -1
- package/dist/{agents-CfFDs52G.js → agents-DezveQ1x.js} +4 -4
- package/dist/{agents-CfFDs52G.js.map → agents-DezveQ1x.js.map} +1 -1
- package/dist/cli/index.js +34 -34
- package/dist/{config-yaml-DGbLSMCa.js → config-yaml-BHD2Qdd8.js} +22 -1
- package/dist/config-yaml-BHD2Qdd8.js.map +1 -0
- package/dist/{config-yaml-Dqt4FWQH.js → config-yaml-IlSnFzJQ.js} +1 -1
- package/dist/dashboard/{agent-enrichment-DdO7ZqjI.js → agent-enrichment-BKZjVvlL.js} +3 -3
- package/dist/dashboard/{agent-enrichment-DdO7ZqjI.js.map → agent-enrichment-BKZjVvlL.js.map} +1 -1
- package/dist/dashboard/{agent-enrichment-dLeGE1fX.js → agent-enrichment-iY3_PylI.js} +1 -1
- package/dist/dashboard/{agents-DCpQQ_W5.js → agents-BQWA-Vps.js} +4 -4
- package/dist/dashboard/{agents-DCpQQ_W5.js.map → agents-BQWA-Vps.js.map} +1 -1
- package/dist/dashboard/{agents-Dgh2TjSp.js → agents-Dinc9j_8.js} +1 -1
- package/dist/dashboard/{config-yaml-DkresmrS.js → config-yaml-CNNnB4Mu.js} +1 -1
- package/dist/dashboard/{config-yaml-DSfYpzN6.js → config-yaml-DUu0JI25.js} +22 -1
- package/dist/dashboard/{config-yaml-DSfYpzN6.js.map → config-yaml-DUu0JI25.js.map} +1 -1
- package/dist/dashboard/{factory-C8nhLGHB.js → factory-CBY0WWeE.js} +2 -2
- package/dist/dashboard/{factory-C8nhLGHB.js.map → factory-CBY0WWeE.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-7eour7EA.js → inspect-agent-KKOeNR7E.js} +3 -3
- package/dist/dashboard/{inspect-agent-7eour7EA.js.map → inspect-agent-KKOeNR7E.js.map} +1 -1
- package/dist/dashboard/{issue-service-singleton-Wv4xBm3y.js → issue-service-singleton-BCZ62hLj.js} +3 -3
- package/dist/dashboard/{issue-service-singleton-Wv4xBm3y.js.map → issue-service-singleton-BCZ62hLj.js.map} +1 -1
- package/dist/dashboard/{issue-service-singleton-Co__-6kL.js → issue-service-singleton-BGKf0A95.js} +1 -1
- package/dist/dashboard/{lifecycle-BcUmtkR4.js → lifecycle-Dpgg-IeP.js} +1 -1
- package/dist/dashboard/{merge-agent-CGN3TT0a.js → merge-agent-CqvQu-n_.js} +1 -1
- package/dist/dashboard/{merge-agent-yudQOPZc.js → merge-agent-Dxxc4JEE.js} +5 -5
- package/dist/dashboard/{merge-agent-yudQOPZc.js.map → merge-agent-Dxxc4JEE.js.map} +1 -1
- package/dist/dashboard/public/assets/{dist-C-wcq54x.js → dist-DS1gmhe1.js} +1 -1
- package/dist/dashboard/public/assets/index-DjGsaJLv.js +212 -0
- package/dist/dashboard/public/index.html +1 -1
- package/dist/dashboard/{review-status-BtXqWBhS.js → review-status-Dww2OKUX.js} +1 -1
- package/dist/dashboard/{review-status-Bymwzh2i.js → review-status-d_wOE-XQ.js} +3 -3
- package/dist/dashboard/{review-status-Bymwzh2i.js.map → review-status-d_wOE-XQ.js.map} +1 -1
- package/dist/dashboard/server.js +97 -97
- package/dist/dashboard/settings-BHlDG7TK.js.map +1 -1
- package/dist/dashboard/{spawn-planning-session-D5hrVdWM.js → spawn-planning-session-D5uEpHzf.js} +1 -1
- package/dist/dashboard/{spawn-planning-session-33Jf-d5T.js → spawn-planning-session-DtbNfA2Q.js} +3 -3
- package/dist/dashboard/{spawn-planning-session-33Jf-d5T.js.map → spawn-planning-session-DtbNfA2Q.js.map} +1 -1
- package/dist/dashboard/{specialist-context-DGukHSn8.js → specialist-context-CEKqWqyF.js} +4 -4
- package/dist/dashboard/{specialist-context-DGukHSn8.js.map → specialist-context-CEKqWqyF.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-CIw4qfTy.js → specialist-logs-CBGVRoQF.js} +1 -1
- package/dist/dashboard/{specialists-Cp-PgspS.js → specialists-sIFlMd3s.js} +1 -1
- package/dist/dashboard/{specialists-B_zrayaP.js → specialists-saEYE0-z.js} +20 -20
- package/dist/dashboard/{specialists-B_zrayaP.js.map → specialists-saEYE0-z.js.map} +1 -1
- package/dist/dashboard/{test-agent-queue-ypF_ecHo.js → test-agent-queue-7jXB2KkN.js} +3 -3
- package/dist/dashboard/{test-agent-queue-ypF_ecHo.js.map → test-agent-queue-7jXB2KkN.js.map} +1 -1
- package/dist/dashboard/{tracker-config-BP59uH4V.js → tracker-config-BX6ijWOc.js} +1 -1
- package/dist/dashboard/{tracker-config-e7ph1QqT.js → tracker-config-tD22z5sv.js} +2 -2
- package/dist/dashboard/{tracker-config-e7ph1QqT.js.map → tracker-config-tD22z5sv.js.map} +1 -1
- package/dist/dashboard/{work-agent-prompt-fCg67nyo.js → work-agent-prompt-D3tPzPvb.js} +2 -2
- package/dist/dashboard/{work-agent-prompt-fCg67nyo.js.map → work-agent-prompt-D3tPzPvb.js.map} +1 -1
- package/dist/dashboard/{work-type-router-CWVW2Wk_.js → work-type-router-7kwLSwrP.js} +4 -2
- package/dist/dashboard/work-type-router-7kwLSwrP.js.map +1 -0
- package/dist/dashboard/{work-type-router-Di5gCQwh.js → work-type-router-ByOOudGz.js} +1 -1
- package/dist/dashboard/workflows-BDpPjK18.js +2 -0
- package/dist/dashboard/{workflows-BSMipN07.js → workflows-DcEeDkbS.js} +3 -3
- package/dist/dashboard/{workflows-BSMipN07.js.map → workflows-DcEeDkbS.js.map} +1 -1
- package/dist/{factory-BRBGw6OB.js → factory-BR48tuUR.js} +1 -1
- package/dist/{factory-DzsOiZVc.js → factory-D6LJaZ__.js} +2 -2
- package/dist/{factory-DzsOiZVc.js.map → factory-D6LJaZ__.js.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/{merge-agent-DlUiUanN.js → merge-agent-BBwHwpn2.js} +3 -3
- package/dist/{merge-agent-DlUiUanN.js.map → merge-agent-BBwHwpn2.js.map} +1 -1
- package/dist/{review-status-DEDvCKMP.js → review-status-Ba6llgCb.js} +3 -3
- package/dist/{review-status-DEDvCKMP.js.map → review-status-Ba6llgCb.js.map} +1 -1
- package/dist/{review-status-D6H2WOw8.js → review-status-Chxzuwn2.js} +1 -1
- package/dist/{settings-BcWPTrua.js → settings-A-CWz_ph.js} +6 -2
- package/dist/{settings-BcWPTrua.js.map → settings-A-CWz_ph.js.map} +1 -1
- package/dist/{specialist-context-BAUWL1Fl.js → specialist-context-B3lknlwi.js} +4 -4
- package/dist/{specialist-context-BAUWL1Fl.js.map → specialist-context-B3lknlwi.js.map} +1 -1
- package/dist/{specialist-logs-DQKKQV9B.js → specialist-logs-DDyY4xqo.js} +1 -1
- package/dist/{specialists-D7Kj5o6s.js → specialists-DvTYu1VZ.js} +20 -20
- package/dist/{specialists-D7Kj5o6s.js.map → specialists-DvTYu1VZ.js.map} +1 -1
- package/dist/{specialists-Bfb9ATzw.js → specialists-DyB4IRlM.js} +1 -1
- package/dist/sync-CLVqiGl4.js +2 -0
- package/dist/{sync-DMfgd389.js → sync-DTHFlEc-.js} +2 -2
- package/dist/{sync-DMfgd389.js.map → sync-DTHFlEc-.js.map} +1 -1
- package/dist/{tracker-BhYYvU3p.js → tracker-CYpb7oUa.js} +2 -2
- package/dist/{tracker-BhYYvU3p.js.map → tracker-CYpb7oUa.js.map} +1 -1
- package/dist/{work-type-router-CHjciPyS.js → work-type-router-oCgTPXsP.js} +4 -2
- package/dist/work-type-router-oCgTPXsP.js.map +1 -0
- package/package.json +1 -1
- package/dist/config-yaml-DGbLSMCa.js.map +0 -1
- package/dist/dashboard/public/assets/index-DKlrFY1k.js +0 -212
- package/dist/dashboard/work-type-router-CWVW2Wk_.js.map +0 -1
- package/dist/dashboard/workflows-DaYWQIS2.js +0 -2
- package/dist/sync-TL6y-8K6.js +0 -2
- package/dist/work-type-router-CHjciPyS.js.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { I as isRunning, O as getTmuxSessionName, Y as spawnEphemeralSpecialist, Z as submitToSpecialistQueue, f as getAllProjectSpecialistStatuses, j as init_specialists, r as checkSpecialistQueue, x as getProjectSpecialistMetadata } from "./specialists-
|
|
1
|
+
import { I as isRunning, O as getTmuxSessionName, Y as spawnEphemeralSpecialist, Z as submitToSpecialistQueue, f as getAllProjectSpecialistStatuses, j as init_specialists, r as checkSpecialistQueue, x as getProjectSpecialistMetadata } from "./specialists-DvTYu1VZ.js";
|
|
2
2
|
init_specialists();
|
|
3
3
|
export { checkSpecialistQueue, getAllProjectSpecialistStatuses, getProjectSpecialistMetadata, getTmuxSessionName, isRunning, spawnEphemeralSpecialist, submitToSpecialistQueue };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { G as init_paths, K as isDevMode, R as SYNC_TARGET, b as PANOPTICON_HOME } from "./paths-CDJ_HsbN.js";
|
|
2
2
|
import { a as init_config, i as getDevrootPath, o as loadConfig } from "./config-BQNKsi9G.js";
|
|
3
|
-
import { d as migrateStalePersonalContent, f as planHooksSync, g as syncStatusline, h as syncHooks, l as executeSync, m as refreshCache, o as loadSettings, p as planSync, v as createBackup } from "./settings-
|
|
3
|
+
import { d as migrateStalePersonalContent, f as planHooksSync, g as syncStatusline, h as syncHooks, l as executeSync, m as refreshCache, o as loadSettings, p as planSync, v as createBackup } from "./settings-A-CWz_ph.js";
|
|
4
4
|
import { h as listProjects, p as init_projects } from "./projects-Bk-5QhFQ.js";
|
|
5
5
|
import { a as migratePanopticonToPan, i as init_workspace_manager } from "./workspace-manager-DuLhnzJV.js";
|
|
6
6
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -690,4 +690,4 @@ async function syncCommand(options) {
|
|
|
690
690
|
//#endregion
|
|
691
691
|
export { needsMigration as i, hasLegacySettings as n, migrateConfig as r, syncCommand as t };
|
|
692
692
|
|
|
693
|
-
//# sourceMappingURL=sync-
|
|
693
|
+
//# sourceMappingURL=sync-DTHFlEc-.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-DMfgd389.js","names":[],"sources":["../src/lib/config-migration.ts","../src/lib/multi-tool-sync.ts","../src/cli/commands/sync.ts"],"sourcesContent":["/**\n * Configuration Migration\n *\n * Migrates from legacy settings.json format to new config.yaml format.\n * Legacy presets are no longer supported - all selection is now smart/capability-based.\n */\n\nimport { readFileSync, writeFileSync, existsSync, renameSync, readdirSync, lstatSync, readlinkSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport yaml from 'js-yaml';\nimport { loadSettings, type SettingsConfig } from './settings.js';\nimport { type YamlConfig } from './config-yaml.js';\nimport { type WorkTypeId } from './work-types.js';\nimport { type ModelId } from './settings.js';\n\n/** Path to legacy settings file */\nconst LEGACY_SETTINGS_PATH = join(homedir(), '.panopticon', 'settings.json');\n\n/** Path to new config file */\nconst NEW_CONFIG_PATH = join(homedir(), '.panopticon', 'config.yaml');\n\n/** Path to backup of legacy settings */\nconst BACKUP_SETTINGS_PATH = join(homedir(), '.panopticon', 'settings.json.backup');\n\n/**\n * Check if migration is needed\n * Returns true if settings.json exists and config.yaml doesn't\n */\nexport function needsMigration(): boolean {\n return existsSync(LEGACY_SETTINGS_PATH) && !existsSync(NEW_CONFIG_PATH);\n}\n\n/**\n * Check if legacy settings exist (even if already migrated)\n */\nexport function hasLegacySettings(): boolean {\n return existsSync(LEGACY_SETTINGS_PATH);\n}\n\n/**\n * Determine which providers are enabled based on API keys\n */\nfunction detectEnabledProviders(settings: SettingsConfig): {\n anthropic: boolean;\n openai: boolean;\n google: boolean;\n zai: boolean;\n kimi: boolean;\n} {\n return {\n anthropic: true, // Always enabled\n openai: !!settings.api_keys.openai,\n google: !!settings.api_keys.google,\n zai: !!settings.api_keys.zai,\n kimi: false, // Legacy settings don't have Kimi\n };\n}\n\n/**\n * Convert legacy settings.json to new config.yaml format\n */\nexport function convertToYamlConfig(settings: SettingsConfig): YamlConfig {\n const providers = detectEnabledProviders(settings);\n\n const config: YamlConfig = {\n models: {\n providers,\n overrides: {}, // No overrides from legacy\n gemini_thinking_level: 3,\n },\n api_keys: settings.api_keys,\n };\n\n return config;\n}\n\n/**\n * Migration options\n */\nexport interface MigrationOptions {\n /** Create backup of legacy settings (default: true) */\n backup?: boolean;\n /** Delete legacy settings after migration (default: false) */\n deleteLegacy?: boolean;\n /** Dry run - don't actually write files (default: false) */\n dryRun?: boolean;\n}\n\n/**\n * Migration result\n */\nexport interface MigrationResult {\n success: boolean;\n overridesCount: number;\n providersEnabled: string[];\n message: string;\n error?: string;\n}\n\nexport function migrateConfig(options: MigrationOptions = {}): MigrationResult {\n const { backup = true, deleteLegacy = false, dryRun = false } = options;\n\n try {\n // Check if migration is needed\n if (!needsMigration()) {\n if (existsSync(NEW_CONFIG_PATH)) {\n return {\n success: true,\n overridesCount: 0,\n providersEnabled: ['anthropic'],\n message: 'Config already migrated (config.yaml exists)',\n };\n }\n return {\n success: false,\n overridesCount: 0,\n providersEnabled: [],\n message: 'No legacy settings.json found to migrate',\n };\n }\n\n // Load legacy settings\n const settings = loadSettings();\n\n // Convert to YAML config\n const yamlConfig = convertToYamlConfig(settings);\n\n // Generate YAML content\n const yamlContent = yaml.dump(yamlConfig, {\n indent: 2,\n lineWidth: 120,\n noRefs: true,\n });\n\n // Dry run - just return what would happen\n if (dryRun) {\n const providersEnabled = Object.entries(yamlConfig.models?.providers || {})\n .filter(([_, enabled]) => enabled)\n .map(([name]) => name);\n\n return {\n success: true,\n overridesCount: Object.keys(yamlConfig.models?.overrides || {}).length,\n providersEnabled,\n message: `Would migrate to smart selection with ${providersEnabled.length} providers enabled`,\n };\n }\n\n // Write new config.yaml\n writeFileSync(NEW_CONFIG_PATH, yamlContent, 'utf-8');\n\n // Back up legacy settings if requested\n if (backup) {\n const legacyContent = readFileSync(LEGACY_SETTINGS_PATH, 'utf-8');\n writeFileSync(BACKUP_SETTINGS_PATH, legacyContent, 'utf-8');\n }\n\n // Delete legacy settings if requested\n if (deleteLegacy) {\n renameSync(LEGACY_SETTINGS_PATH, `${LEGACY_SETTINGS_PATH}.migrated`);\n }\n\n const providersEnabled = Object.entries(yamlConfig.models?.providers || {})\n .filter(([_, enabled]) => enabled)\n .map(([name]) => name);\n\n return {\n success: true,\n overridesCount: Object.keys(yamlConfig.models?.overrides || {}).length,\n providersEnabled,\n message: `Successfully migrated to smart selection with ${providersEnabled.length} providers`,\n };\n } catch (error: any) {\n return {\n success: false,\n overridesCount: 0,\n providersEnabled: [],\n message: 'Migration failed',\n error: error.message,\n };\n }\n}\n\n/**\n * Get migration status\n */\nexport function getMigrationStatus(): {\n needsMigration: boolean;\n hasLegacySettings: boolean;\n hasNewConfig: boolean;\n} {\n return {\n needsMigration: needsMigration(),\n hasLegacySettings: existsSync(LEGACY_SETTINGS_PATH),\n hasNewConfig: existsSync(NEW_CONFIG_PATH),\n };\n}\n\n/**\n * Clean up legacy runtime symlinks from removed runtimes.\n *\n * PAN-142: Panopticon consolidated to Claude Code as the sole runtime.\n * This removes any Panopticon-managed symlinks from legacy runtime directories\n * (codex, cursor, gemini, opencode).\n */\nexport interface LegacyCleanupResult {\n cleaned: string[];\n total: number;\n errors: string[];\n}\n\nexport function cleanupLegacyRuntimeSymlinks(): LegacyCleanupResult {\n const legacyDirs = [\n { name: 'codex', base: join(homedir(), '.codex') },\n { name: 'cursor', base: join(homedir(), '.cursor') },\n { name: 'gemini', base: join(homedir(), '.gemini') },\n { name: 'opencode', base: join(homedir(), '.opencode') },\n ];\n\n const cleaned: string[] = [];\n const errors: string[] = [];\n\n for (const { name, base } of legacyDirs) {\n for (const subdir of ['skills', 'commands', 'agents']) {\n const dir = join(base, subdir);\n if (!existsSync(dir)) continue;\n\n try {\n const entries = readdirSync(dir);\n for (const entry of entries) {\n const entryPath = join(dir, entry);\n try {\n const stats = lstatSync(entryPath);\n if (!stats.isSymbolicLink()) continue;\n\n const linkTarget = readlinkSync(entryPath);\n // Only remove symlinks pointing to Panopticon directories\n if (linkTarget.includes('.panopticon')) {\n unlinkSync(entryPath);\n cleaned.push(`${name}/${subdir}/${entry}`);\n }\n } catch (err: any) {\n errors.push(`${name}/${subdir}/${entry}: ${err.message}`);\n }\n }\n } catch (err: any) {\n // Directory may not be readable, that's fine\n errors.push(`${name}/${subdir}: ${err.message}`);\n }\n }\n }\n\n return { cleaned, total: cleaned.length, errors };\n}\n\n/**\n * Migrate legacy sync config by stripping the 'targets' field from config.toml.\n * This handles users who had `targets = [\"claude\", \"codex\"]` in their config.\n */\nexport function migrateSyncTargets(): { migrated: boolean; hadNonClaudeTargets: boolean } {\n const configPath = join(homedir(), '.panopticon', 'config.toml');\n\n if (!existsSync(configPath)) {\n return { migrated: false, hadNonClaudeTargets: false };\n }\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n\n // Check if targets field exists\n const targetsMatch = content.match(/^targets\\s*=\\s*\\[([^\\]]*)\\]/m);\n if (!targetsMatch) {\n return { migrated: false, hadNonClaudeTargets: false };\n }\n\n // Check if non-claude targets were configured\n const targetsStr = targetsMatch[1];\n const hadNonClaudeTargets = /codex|cursor|gemini|opencode/i.test(targetsStr);\n\n // Remove the targets line\n const newContent = content.replace(/^targets\\s*=\\s*\\[[^\\]]*\\]\\s*\\n?/m, '');\n writeFileSync(configPath, newContent, 'utf-8');\n\n return { migrated: true, hadNonClaudeTargets };\n } catch {\n return { migrated: false, hadNonClaudeTargets: false };\n }\n}\n","/**\n * Multi-Tool Skill Sync\n *\n * Writes Panopticon skills to other AI tool formats so skills authored once\n * in .pan/skills/ are available across all configured tools.\n *\n * Configured via `tools.also_sync` in ~/.panopticon/config.yaml and .pan.yaml.\n * Per-project .pan.yaml values are merged additively with global config.\n *\n * Supported targets:\n * cursor → .cursor/rules/<skill-name>.mdc\n * codex → AGENTS.md (named blocks)\n * windsurf → .windsurf/rules/<skill-name>.md\n * cline → .clinerules/<skill-name>.md\n * copilot → .github/instructions/<skill-name>.instructions.md\n * aider → CONVENTIONS.md (named blocks)\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport yaml from 'js-yaml';\nimport { PANOPTICON_HOME } from './paths.js';\n\nexport type AlsoSyncTool = 'cursor' | 'codex' | 'windsurf' | 'cline' | 'copilot' | 'aider';\n\nexport interface MultiToolSyncResult {\n tool: AlsoSyncTool;\n written: string[];\n skipped: string[];\n errors: string[];\n}\n\n/** Strip YAML frontmatter from a skill markdown file */\nfunction stripFrontmatter(content: string): string {\n if (!content.startsWith('---')) return content;\n const end = content.indexOf('\\n---', 4);\n if (end === -1) return content;\n return content.slice(end + 4).trimStart();\n}\n\n/** Extract the skill name from frontmatter, or fall back to dir name */\nfunction extractSkillName(content: string, fallback: string): string {\n if (!content.startsWith('---')) return fallback;\n const end = content.indexOf('\\n---', 4);\n if (end === -1) return fallback;\n const frontmatter = content.slice(4, end);\n const match = frontmatter.match(/^name:\\s*(.+)$/m);\n return match ? match[1].trim() : fallback;\n}\n\n/** Read main SKILL.md content for a skill directory */\nfunction readSkillContent(skillDir: string): string | null {\n const skillMd = join(skillDir, 'SKILL.md');\n if (!existsSync(skillMd)) {\n // Fallback: any .md file in root\n const files = existsSync(skillDir) ? readdirSync(skillDir).filter(f => f.endsWith('.md')) : [];\n if (files.length === 0) return null;\n return readFileSync(join(skillDir, files[0]), 'utf-8');\n }\n return readFileSync(skillMd, 'utf-8');\n}\n\n/** Collect all skill directories from the given skills root */\nfunction collectSkillDirs(skillsDir: string): Array<{ name: string; dir: string }> {\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir, { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => ({ name: e.name, dir: join(skillsDir, e.name) }));\n}\n\n/**\n * Update or insert a named block in a file.\n * Blocks are delimited by: <!-- panopticon:<skill-name> start --> ... <!-- panopticon:<skill-name> end -->\n */\nfunction upsertNamedBlock(filePath: string, blockName: string, content: string): void {\n const startTag = `<!-- panopticon:${blockName} start -->`;\n const endTag = `<!-- panopticon:${blockName} end -->`;\n const block = `${startTag}\\n${content}\\n${endTag}`;\n\n let existing = existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';\n\n const startIdx = existing.indexOf(startTag);\n const endIdx = existing.indexOf(endTag);\n\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n // Replace existing block\n existing = existing.slice(0, startIdx) + block + existing.slice(endIdx + endTag.length);\n } else {\n // Append new block\n if (existing.length > 0 && !existing.endsWith('\\n')) existing += '\\n';\n existing += '\\n' + block + '\\n';\n }\n\n writeFileSync(filePath, existing, 'utf-8');\n}\n\n/** Sync a single skill to the cursor target */\nfunction syncToCursor(projectPath: string, skillName: string, rawContent: string): void {\n const rulesDir = join(projectPath, '.cursor', 'rules');\n mkdirSync(rulesDir, { recursive: true });\n const body = stripFrontmatter(rawContent);\n // .mdc files: standard markdown, cursor accepts them as context rules\n writeFileSync(join(rulesDir, `${skillName}.mdc`), body, 'utf-8');\n}\n\n/** Sync a single skill to the windsurf target */\nfunction syncToWindsurf(projectPath: string, skillName: string, rawContent: string): void {\n const rulesDir = join(projectPath, '.windsurf', 'rules');\n mkdirSync(rulesDir, { recursive: true });\n writeFileSync(join(rulesDir, `${skillName}.md`), stripFrontmatter(rawContent), 'utf-8');\n}\n\n/** Sync a single skill to the cline target */\nfunction syncToCline(projectPath: string, skillName: string, rawContent: string): void {\n const rulesDir = join(projectPath, '.clinerules');\n mkdirSync(rulesDir, { recursive: true });\n writeFileSync(join(rulesDir, `${skillName}.md`), stripFrontmatter(rawContent), 'utf-8');\n}\n\n/** Sync a single skill to the copilot target */\nfunction syncToCopilot(projectPath: string, skillName: string, rawContent: string): void {\n const instructionsDir = join(projectPath, '.github', 'instructions');\n mkdirSync(instructionsDir, { recursive: true });\n writeFileSync(\n join(instructionsDir, `${skillName}.instructions.md`),\n stripFrontmatter(rawContent),\n 'utf-8',\n );\n}\n\n/** Sync a single skill to AGENTS.md (codex) as a named block */\nfunction syncToCodex(projectPath: string, skillName: string, rawContent: string): void {\n const agentsMd = join(projectPath, 'AGENTS.md');\n upsertNamedBlock(agentsMd, skillName, `## ${skillName}\\n\\n${stripFrontmatter(rawContent)}`);\n}\n\n/** Sync a single skill to CONVENTIONS.md (aider) as a named block */\nfunction syncToAider(projectPath: string, skillName: string, rawContent: string): void {\n const conventionsMd = join(projectPath, 'CONVENTIONS.md');\n upsertNamedBlock(conventionsMd, skillName, `## ${skillName}\\n\\n${stripFrontmatter(rawContent)}`);\n}\n\nconst TOOL_WRITERS: Record<AlsoSyncTool, (projectPath: string, name: string, content: string) => void> = {\n cursor: syncToCursor,\n windsurf: syncToWindsurf,\n cline: syncToCline,\n copilot: syncToCopilot,\n codex: syncToCodex,\n aider: syncToAider,\n};\n\n/**\n * Resolve the merged list of tools to sync.\n * Global config is the base; per-project .pan.yaml adds more (never removes).\n */\nexport function resolveAlsoSyncTools(projectPath?: string): AlsoSyncTool[] {\n const tools = new Set<AlsoSyncTool>();\n\n // Read from global config\n const globalConfig = join(PANOPTICON_HOME, 'config.yaml');\n if (existsSync(globalConfig)) {\n try {\n const parsed = yaml.load(readFileSync(globalConfig, 'utf-8')) as any;\n const globalTools: string[] = parsed?.tools?.also_sync || [];\n for (const t of globalTools) {\n if (t in TOOL_WRITERS) tools.add(t as AlsoSyncTool);\n }\n } catch { /* ignore parse errors */ }\n }\n\n // Merge per-project .pan.yaml (additive)\n if (projectPath) {\n const panYaml = join(projectPath, '.pan.yaml');\n const legacyYaml = join(projectPath, '.panopticon.yaml');\n const configPath = existsSync(panYaml) ? panYaml : existsSync(legacyYaml) ? legacyYaml : null;\n if (configPath) {\n try {\n const parsed = yaml.load(readFileSync(configPath, 'utf-8')) as any;\n const projectTools: string[] = parsed?.tools?.also_sync || [];\n for (const t of projectTools) {\n if (t in TOOL_WRITERS) tools.add(t as AlsoSyncTool);\n }\n } catch { /* ignore parse errors */ }\n }\n }\n\n return Array.from(tools);\n}\n\n/**\n * Sync skills from a skills directory to all configured tools.\n *\n * @param skillsDir Directory containing skill subdirectories\n * @param projectPath Project root where tool targets live\n * @param tools Tools to sync to (from resolveAlsoSyncTools)\n */\nexport function syncSkillsToTools(\n skillsDir: string,\n projectPath: string,\n tools: AlsoSyncTool[],\n): MultiToolSyncResult[] {\n if (tools.length === 0 || !existsSync(skillsDir)) return [];\n\n const skills = collectSkillDirs(skillsDir);\n const results: MultiToolSyncResult[] = [];\n\n for (const tool of tools) {\n const writer = TOOL_WRITERS[tool];\n const result: MultiToolSyncResult = { tool, written: [], skipped: [], errors: [] };\n\n for (const { name, dir } of skills) {\n try {\n const rawContent = readSkillContent(dir);\n if (!rawContent) {\n result.skipped.push(name);\n continue;\n }\n const displayName = extractSkillName(rawContent, name);\n writer(projectPath, displayName, rawContent);\n result.written.push(name);\n } catch (err: any) {\n result.errors.push(`${name}: ${err.message}`);\n }\n }\n\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * Run the full multi-tool sync for a project.\n * Sources: .pan/skills/ (project-local) and/or ~/.panopticon/skills/ (global).\n */\nexport function runMultiToolSync(projectPath: string): MultiToolSyncResult[] {\n const tools = resolveAlsoSyncTools(projectPath);\n if (tools.length === 0) return [];\n\n const allResults: MultiToolSyncResult[] = [];\n\n // 1. Global skills (from ~/.panopticon/skills/)\n const globalSkillsDir = join(PANOPTICON_HOME, 'skills');\n const globalResults = syncSkillsToTools(globalSkillsDir, projectPath, tools);\n allResults.push(...globalResults);\n\n // 2. Project-local skills (from .pan/skills/) — may overwrite global skill entries\n const projectSkillsDir = join(projectPath, '.pan', 'skills');\n if (existsSync(projectSkillsDir)) {\n const projectResults = syncSkillsToTools(projectSkillsDir, projectPath, tools);\n // Merge into existing results (project results override counts, don't duplicate tools)\n for (const pr of projectResults) {\n const existing = allResults.find(r => r.tool === pr.tool);\n if (existing) {\n existing.written.push(...pr.written);\n existing.errors.push(...pr.errors);\n } else {\n allResults.push(pr);\n }\n }\n }\n\n return allResults;\n}\n","import chalk from 'chalk';\nimport ora from 'ora';\nimport { execSync } from 'child_process';\nimport { existsSync, readdirSync, readFileSync, writeFileSync, statSync, symlinkSync, mkdirSync } from 'fs';\nimport { homedir } from 'os';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { loadConfig } from '../../lib/config.js';\nimport { createBackup } from '../../lib/backup.js';\nimport { planSync, executeSync, refreshCache, migrateStalePersonalContent, planHooksSync, syncHooks, syncStatusline } from '../../lib/sync.js';\nimport { SYNC_TARGET, isDevMode } from '../../lib/paths.js';\nimport { getDevrootPath } from '../../lib/config.js';\nimport { listProjects } from '../../lib/projects.js';\nimport { cleanupLegacyRuntimeSymlinks, migrateSyncTargets } from '../../lib/config-migration.js';\nimport { migratePanopticonToPan } from '../../lib/workspace-manager.js';\nimport { runMultiToolSync, resolveAlsoSyncTools } from '../../lib/multi-tool-sync.js';\n\n// Get path to bundled git hooks\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst BUNDLED_GIT_HOOKS_DIR = join(__dirname, '..', '..', 'scripts', 'git-hooks');\n\n// Helper to check if a command exists\nfunction checkCommand(cmd: string): boolean {\n try {\n execSync(`which ${cmd}`, { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n}\n\ninterface SyncOptions {\n dryRun?: boolean;\n force?: boolean;\n diff?: boolean;\n backupOnly?: boolean;\n}\n\nexport async function syncCommand(options: SyncOptions): Promise<void> {\n // Dry run mode\n if (options.dryRun) {\n console.log(chalk.bold('Sync Plan (dry run):\\n'));\n\n // Show dev mode status\n if (isDevMode()) {\n console.log(chalk.magenta('Developer mode detected - dev-skills will be synced\\n'));\n }\n\n // Show hooks plan\n const hooksPlan = planHooksSync();\n if (hooksPlan.length > 0) {\n console.log(chalk.cyan('hooks (bin scripts):'));\n for (const hook of hooksPlan) {\n const icon = hook.status === 'new' ? chalk.green('+') : chalk.blue('↻');\n const status = hook.status === 'new' ? '' : chalk.dim('[update]');\n console.log(` ${icon} ${hook.name} ${status}`);\n }\n console.log('');\n }\n\n const devrootPath = getDevrootPath();\n console.log(chalk.cyan(`devroot (${devrootPath || 'disabled'}):`));\n\n if (!devrootPath) {\n console.log(chalk.dim(' (devroot disabled — set sync.devroot in config)'));\n } else {\n const plan = planSync();\n const allItems = [...plan.skills, ...plan.agents, ...plan.rules, ...plan.commands];\n\n if (allItems.length === 0) {\n console.log(chalk.dim(' (nothing to sync)'));\n } else {\n for (const item of allItems) {\n const icon = item.status === 'conflict' ? chalk.yellow('!') :\n item.status === 'symlink' ? chalk.blue('↻') :\n chalk.green('+');\n const label = item.status === 'conflict' ? chalk.yellow('[modified]') :\n item.status === 'symlink' ? chalk.dim('[update]') :\n chalk.green('[new]');\n console.log(` ${icon} ${item.name} ${label}`);\n }\n }\n }\n\n // Show .pan/skills/ source files for each registered project\n const dryRunProjects = listProjects();\n for (const { config } of dryRunProjects) {\n if (!existsSync(config.path)) continue;\n const panSkillsDir = join(config.path, '.pan', 'skills');\n if (existsSync(panSkillsDir)) {\n const skills = readdirSync(panSkillsDir, { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => e.name);\n if (skills.length > 0) {\n console.log(chalk.cyan(`\\n.pan/skills/ (${config.name}):`));\n for (const skillName of skills) {\n console.log(` ${chalk.green('+')} ${skillName} ${chalk.green('[project-local]')}`);\n }\n }\n }\n\n // Show multi-tool sync targets\n const tools = resolveAlsoSyncTools(config.path);\n if (tools.length > 0) {\n console.log(chalk.cyan(`\\nmulti-tool sync (${config.name}): ${tools.join(', ')}`));\n const panSkillsDirExists = existsSync(join(config.path, '.pan', 'skills'));\n if (panSkillsDirExists) {\n const skills = readdirSync(join(config.path, '.pan', 'skills'), { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => e.name);\n for (const tool of tools) {\n for (const skillName of skills) {\n console.log(` ${chalk.green('+')} ${skillName} → ${tool}`);\n }\n }\n }\n }\n }\n\n console.log('');\n console.log(chalk.dim('Run without --dry-run to apply changes.'));\n return;\n }\n\n // Run one-time migration: strip legacy sync targets from config.toml\n const syncMigration = migrateSyncTargets();\n if (syncMigration.migrated) {\n if (syncMigration.hadNonClaudeTargets) {\n console.log(chalk.yellow('Config updated: removed non-Claude sync targets (Panopticon now syncs to Claude Code only).'));\n }\n }\n\n // Run one-time migration: remove Panopticon-managed symlinks from legacy runtime dirs\n const cleanupResult = cleanupLegacyRuntimeSymlinks();\n if (cleanupResult.cleaned.length > 0) {\n console.log(chalk.dim(`Removed ${cleanupResult.total} legacy runtime symlink(s): ${cleanupResult.cleaned.join(', ')}`));\n }\n\n // One-time migration: remove Panopticon symlinks from ~/.claude/ (devroot replaces this)\n const migration = migrateStalePersonalContent();\n if (migration.removedSymlinks.length > 0) {\n console.log(chalk.cyan(`Migrated: removed ${migration.removedSymlinks.length} Panopticon symlink(s) from ~/.claude/`));\n if (migration.preservedUserContent.length > 0) {\n console.log(chalk.dim(` Preserved ${migration.preservedUserContent.length} user-created item(s)`));\n }\n }\n\n const config = loadConfig();\n\n // Create backup if enabled\n if (config.sync.backup_before_sync) {\n const spinner = ora('Creating backup...').start();\n\n const backupDirs = [\n SYNC_TARGET.skills,\n SYNC_TARGET.commands,\n SYNC_TARGET.agents,\n ];\n\n const backup = createBackup(backupDirs);\n\n if (backup.targets.length > 0) {\n spinner.succeed(`Backup created: ${backup.timestamp}`);\n } else {\n spinner.info('No existing content to backup');\n }\n\n if (options.backupOnly) {\n return;\n }\n }\n\n // Refresh cache from repo source\n const cacheSpinner = ora('Refreshing cache from repo...').start();\n const cacheResult = refreshCache();\n const cacheParts = [];\n if (cacheResult.skills.copied > 0) cacheParts.push(`${cacheResult.skills.copied} skills`);\n if (cacheResult.agents.copied > 0) cacheParts.push(`${cacheResult.agents.copied} agents`);\n if (cacheResult.rules.copied > 0) cacheParts.push(`${cacheResult.rules.copied} rules`);\n cacheSpinner.succeed(`Cache refreshed: ${cacheParts.length > 0 ? cacheParts.join(', ') : 'up to date'}`);\n\n // Execute sync to devroot\n const devrootPath = getDevrootPath();\n const spinner = ora(`Syncing to devroot (${devrootPath || 'disabled'})...`).start();\n\n if (!devrootPath) {\n spinner.info('Devroot disabled (set sync.devroot in config to enable)');\n } else {\n const result = executeSync({ force: options.force, diff: options.diff });\n const totalSynced = result.created.length + result.updated.length;\n\n // Show diffs if requested\n if (result.diffs.length > 0) {\n spinner.info(`Showing diffs for ${result.diffs.length} modified file(s):\\n`);\n for (const d of result.diffs) {\n console.log(chalk.cyan(`--- ${d.path} (installed)`));\n console.log(chalk.cyan(`+++ ${d.path} (current on disk)`));\n // Simple line-by-line diff\n const sourceLines = d.sourceContent.split('\\n');\n const targetLines = d.targetContent.split('\\n');\n const maxLines = Math.max(sourceLines.length, targetLines.length);\n for (let i = 0; i < maxLines; i++) {\n if (sourceLines[i] !== targetLines[i]) {\n if (targetLines[i] !== undefined) console.log(chalk.red(`- ${targetLines[i]}`));\n if (sourceLines[i] !== undefined) console.log(chalk.green(`+ ${sourceLines[i]}`));\n }\n }\n console.log('');\n }\n }\n\n if (result.conflicts.length > 0 && !options.force) {\n spinner.warn(`Synced ${totalSynced} items, ${result.conflicts.length} conflicts`);\n console.log('');\n console.log(chalk.yellow('Modified since Panopticon installed:'));\n for (const name of result.conflicts) {\n console.log(chalk.dim(` - ${name}`));\n }\n console.log('');\n console.log(chalk.dim('Use --force to overwrite, --diff to see changes.'));\n } else if (result.skipped.length > 0) {\n spinner.succeed(`Synced ${totalSynced} items to devroot (${result.skipped.length} user-owned skipped)`);\n } else {\n spinner.succeed(`Synced ${totalSynced} items to devroot`);\n }\n }\n\n // Sync hooks (bin scripts)\n const hooksSpinner = ora('Syncing hooks...').start();\n const hooksResult = syncHooks();\n\n if (hooksResult.errors.length > 0) {\n hooksSpinner.warn(`Synced ${hooksResult.synced.length} hooks, ${hooksResult.errors.length} errors`);\n for (const error of hooksResult.errors) {\n console.log(chalk.red(` ✗ ${error}`));\n }\n } else if (hooksResult.synced.length > 0) {\n hooksSpinner.succeed(`Synced ${hooksResult.synced.length} hooks to ~/.panopticon/bin/`);\n } else {\n hooksSpinner.info('No hooks to sync');\n }\n\n const projects = listProjects();\n\n // Ensure beads database exists for each registered project (first-time setup guard).\n // bd install puts the binary in PATH, but bd init must be run once per project to\n // create the Dolt database. Without it, workspace beads creation silently fails.\n if (projects.length > 0 && checkCommand('bd')) {\n for (const { key, config } of projects) {\n if (!existsSync(config.path)) continue;\n const mainBeadsDir = join(config.path, '.beads');\n if (!existsSync(mainBeadsDir)) continue; // Project hasn't used beads yet — skip\n // Test connectivity. If the database is missing, auto-init.\n try {\n execSync('bd list --json --limit 0 2>&1', { cwd: config.path, stdio: 'pipe', timeout: 8000 });\n } catch (e: any) {\n const msg = String(e?.stdout ?? e?.stderr ?? e?.message ?? '');\n if (msg.includes('database') && (msg.includes('not found') || msg.includes('not exist') || msg.includes('defaulting'))) {\n const beadsSpinner = ora(`Initializing beads database for ${config.name}...`).start();\n try {\n const prefix = (key || config.name).toLowerCase().replace(/[^a-z0-9-]/g, '-');\n execSync(`bd init --prefix ${prefix}`, { cwd: config.path, stdio: 'pipe', timeout: 20000 });\n try { execSync('git config beads.role contributor', { cwd: config.path, stdio: 'pipe' }); } catch { /* non-fatal */ }\n beadsSpinner.succeed(`Beads database initialized for ${config.name} (prefix: ${prefix})`);\n } catch {\n beadsSpinner.warn(`Could not auto-initialize beads for ${config.name} — run: cd ${config.path} && bd init`);\n }\n }\n }\n }\n }\n\n\n // Check jq availability (required by statusline, beads, specialists)\n if (!checkCommand('jq')) {\n console.log(chalk.yellow('\\n ⚠ jq not found — statusline and other features need it'));\n console.log(chalk.dim(' Install: apt install jq / brew install jq\\n'));\n }\n\n // Sync statusline to all runtimes\n const statuslineSpinner = ora('Syncing statusline...').start();\n const statuslineResult = syncStatusline();\n\n if (statuslineResult.errors.length > 0) {\n statuslineSpinner.warn(`Synced statusline to ${statuslineResult.synced.length} runtime(s), ${statuslineResult.errors.length} error(s)`);\n for (const error of statuslineResult.errors) {\n console.log(chalk.red(` ✗ ${error}`));\n }\n } else if (statuslineResult.synced.length > 0) {\n statuslineSpinner.succeed(`Synced statusline to ${statuslineResult.synced.join(', ')}`);\n } else {\n statuslineSpinner.info('No statusline script found (scripts/statusline.sh)');\n }\n\n // Check and install claude-code-router if missing\n const hasRouter = checkCommand('claude-code-router');\n if (!hasRouter) {\n const routerSpinner = ora('Installing claude-code-router...').start();\n try {\n execSync('npm install -g @musistudio/claude-code-router', {\n stdio: 'pipe',\n timeout: 120000\n });\n routerSpinner.succeed('claude-code-router installed');\n } catch (error) {\n routerSpinner.warn('Failed to install claude-code-router - run: npm install -g @musistudio/claude-code-router');\n }\n }\n\n // Check and install mkcert if missing\n if (!checkCommand('mkcert')) {\n const mkcertSpinner = ora('Installing mkcert...').start();\n try {\n const binDir = join(homedir(), '.local', 'bin');\n mkdirSync(binDir, { recursive: true });\n const mkcertPath = join(binDir, 'mkcert');\n const arch = process.arch === 'x64' ? 'amd64' : process.arch;\n execSync(`curl -sL \"https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${arch}\" -o \"${mkcertPath}\" && chmod +x \"${mkcertPath}\"`, {\n stdio: 'pipe',\n timeout: 60000,\n });\n mkcertSpinner.succeed('mkcert installed');\n } catch {\n mkcertSpinner.warn('Failed to install mkcert - run: https://github.com/FiloSottile/mkcert/releases');\n }\n }\n\n // Check and install SageOx CLI if missing\n if (!checkCommand('ox')) {\n const oxSpinner = ora('Installing SageOx CLI (ox)...').start();\n try {\n const binDir = join(homedir(), '.local', 'bin');\n mkdirSync(binDir, { recursive: true });\n const oxPath = join(binDir, 'ox');\n const arch = process.arch === 'x64' ? 'amd64' : process.arch;\n const platform = process.platform === 'darwin' ? 'darwin' : 'linux';\n execSync(`curl -sL \"https://github.com/eltmon/ox/releases/download/latest/ox-${platform}-${arch}\" -o \"${oxPath}\" && chmod +x \"${oxPath}\"`, {\n stdio: 'pipe',\n timeout: 60000,\n });\n oxSpinner.succeed('SageOx CLI installed');\n } catch {\n oxSpinner.warn('Failed to install SageOx CLI - see: https://github.com/eltmon/ox/releases');\n }\n }\n\n\n // Enforce Playwright MCP --isolated flag to prevent stale zoom/profile state\n const mcpPath = join(homedir(), '.claude', 'mcp.json');\n try {\n if (existsSync(mcpPath)) {\n const mcpConfig = JSON.parse(readFileSync(mcpPath, 'utf-8'));\n const pw = mcpConfig?.mcpServers?.playwright;\n if (pw && Array.isArray(pw.args) && !pw.args.includes('--isolated')) {\n pw.args.push('--isolated');\n writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\\n');\n console.log(chalk.green('✓ Added --isolated to Playwright MCP (prevents stale zoom/profile state)'));\n }\n }\n } catch {\n // Non-fatal — skip if mcp.json can't be read/written\n }\n\n // Ensure beads database exists for each registered project (first-time setup guard).\n // bd install puts the binary in PATH, but bd init must be run once per project to\n // create the Dolt database. Without it, workspace beads creation silently fails.\n // Migrate .panopticon/ → .pan/ and run multi-tool sync in all registered projects\n\n for (const { config } of projects) {\n if (!existsSync(config.path)) continue;\n\n // Migrate .panopticon/ subdirs → .pan/\n const migResult = migratePanopticonToPan(config.path);\n if (migResult.migrated.length > 0) {\n console.log(chalk.cyan(`Migrated .panopticon/ → .pan/ in ${config.name}: ${migResult.migrated.join(', ')}`));\n }\n if (migResult.skipped.length > 0) {\n console.log(chalk.yellow(`Migration skipped (both exist) in ${config.name}: ${migResult.skipped.join(', ')}`));\n }\n for (const err of migResult.errors) {\n console.log(chalk.red(`Migration error in ${config.name}: ${err}`));\n }\n\n // Multi-tool skill sync (cursor, codex, windsurf, cline, copilot, aider)\n const toolSyncResults = runMultiToolSync(config.path);\n for (const r of toolSyncResults) {\n if (r.written.length > 0) {\n console.log(chalk.cyan(`Synced ${r.written.length} skill(s) to ${r.tool} in ${config.name}`));\n }\n for (const err of r.errors) {\n console.log(chalk.red(`Multi-tool sync error (${r.tool}) in ${config.name}: ${err}`));\n }\n }\n }\n\n // Sync git hooks to all registered projects (branch protection)\n if (projects.length > 0 && existsSync(BUNDLED_GIT_HOOKS_DIR)) {\n const gitHooksSpinner = ora('Installing git hooks in registered projects...').start();\n let totalInstalled = 0;\n let projectsUpdated = 0;\n\n for (const { config } of projects) {\n if (!existsSync(config.path)) continue;\n\n // Find all .git directories (handles polyrepos)\n const gitDirs: string[] = [];\n\n // Check root\n if (existsSync(join(config.path, '.git')) && statSync(join(config.path, '.git')).isDirectory()) {\n gitDirs.push(join(config.path, '.git'));\n } else {\n // Scan for polyrepo\n try {\n const entries = readdirSync(config.path);\n for (const entry of entries) {\n const entryPath = join(config.path, entry);\n const gitPath = join(entryPath, '.git');\n if (existsSync(gitPath) && statSync(gitPath).isDirectory()) {\n gitDirs.push(gitPath);\n }\n }\n } catch {\n // Skip unreadable directories\n }\n }\n\n // Install hooks in each git dir\n for (const gitDir of gitDirs) {\n const hooksTarget = join(gitDir, 'hooks');\n if (!existsSync(hooksTarget)) {\n mkdirSync(hooksTarget, { recursive: true });\n }\n\n try {\n const hooks = readdirSync(BUNDLED_GIT_HOOKS_DIR).filter(f =>\n statSync(join(BUNDLED_GIT_HOOKS_DIR, f)).isFile()\n );\n\n for (const hook of hooks) {\n const source = join(BUNDLED_GIT_HOOKS_DIR, hook);\n const target = join(hooksTarget, hook);\n\n // Skip if already a symlink to our hook\n if (existsSync(target)) {\n try {\n const { readlinkSync } = await import('fs');\n if (readlinkSync(target) === source) continue;\n } catch {\n // Not a symlink\n }\n // Backup existing\n const { renameSync } = await import('fs');\n try { renameSync(target, `${target}.backup`); } catch {}\n }\n\n try {\n symlinkSync(source, target);\n totalInstalled++;\n } catch {}\n }\n projectsUpdated++;\n } catch {}\n }\n }\n\n if (totalInstalled > 0) {\n gitHooksSpinner.succeed(`Installed git hooks in ${projectsUpdated} project(s)`);\n } else {\n gitHooksSpinner.info('Git hooks already up to date');\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sync-DTHFlEc-.js","names":[],"sources":["../src/lib/config-migration.ts","../src/lib/multi-tool-sync.ts","../src/cli/commands/sync.ts"],"sourcesContent":["/**\n * Configuration Migration\n *\n * Migrates from legacy settings.json format to new config.yaml format.\n * Legacy presets are no longer supported - all selection is now smart/capability-based.\n */\n\nimport { readFileSync, writeFileSync, existsSync, renameSync, readdirSync, lstatSync, readlinkSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport yaml from 'js-yaml';\nimport { loadSettings, type SettingsConfig } from './settings.js';\nimport { type YamlConfig } from './config-yaml.js';\nimport { type WorkTypeId } from './work-types.js';\nimport { type ModelId } from './settings.js';\n\n/** Path to legacy settings file */\nconst LEGACY_SETTINGS_PATH = join(homedir(), '.panopticon', 'settings.json');\n\n/** Path to new config file */\nconst NEW_CONFIG_PATH = join(homedir(), '.panopticon', 'config.yaml');\n\n/** Path to backup of legacy settings */\nconst BACKUP_SETTINGS_PATH = join(homedir(), '.panopticon', 'settings.json.backup');\n\n/**\n * Check if migration is needed\n * Returns true if settings.json exists and config.yaml doesn't\n */\nexport function needsMigration(): boolean {\n return existsSync(LEGACY_SETTINGS_PATH) && !existsSync(NEW_CONFIG_PATH);\n}\n\n/**\n * Check if legacy settings exist (even if already migrated)\n */\nexport function hasLegacySettings(): boolean {\n return existsSync(LEGACY_SETTINGS_PATH);\n}\n\n/**\n * Determine which providers are enabled based on API keys\n */\nfunction detectEnabledProviders(settings: SettingsConfig): {\n anthropic: boolean;\n openai: boolean;\n google: boolean;\n zai: boolean;\n kimi: boolean;\n} {\n return {\n anthropic: true, // Always enabled\n openai: !!settings.api_keys.openai,\n google: !!settings.api_keys.google,\n zai: !!settings.api_keys.zai,\n kimi: false, // Legacy settings don't have Kimi\n };\n}\n\n/**\n * Convert legacy settings.json to new config.yaml format\n */\nexport function convertToYamlConfig(settings: SettingsConfig): YamlConfig {\n const providers = detectEnabledProviders(settings);\n\n const config: YamlConfig = {\n models: {\n providers,\n overrides: {}, // No overrides from legacy\n gemini_thinking_level: 3,\n },\n api_keys: settings.api_keys,\n };\n\n return config;\n}\n\n/**\n * Migration options\n */\nexport interface MigrationOptions {\n /** Create backup of legacy settings (default: true) */\n backup?: boolean;\n /** Delete legacy settings after migration (default: false) */\n deleteLegacy?: boolean;\n /** Dry run - don't actually write files (default: false) */\n dryRun?: boolean;\n}\n\n/**\n * Migration result\n */\nexport interface MigrationResult {\n success: boolean;\n overridesCount: number;\n providersEnabled: string[];\n message: string;\n error?: string;\n}\n\nexport function migrateConfig(options: MigrationOptions = {}): MigrationResult {\n const { backup = true, deleteLegacy = false, dryRun = false } = options;\n\n try {\n // Check if migration is needed\n if (!needsMigration()) {\n if (existsSync(NEW_CONFIG_PATH)) {\n return {\n success: true,\n overridesCount: 0,\n providersEnabled: ['anthropic'],\n message: 'Config already migrated (config.yaml exists)',\n };\n }\n return {\n success: false,\n overridesCount: 0,\n providersEnabled: [],\n message: 'No legacy settings.json found to migrate',\n };\n }\n\n // Load legacy settings\n const settings = loadSettings();\n\n // Convert to YAML config\n const yamlConfig = convertToYamlConfig(settings);\n\n // Generate YAML content\n const yamlContent = yaml.dump(yamlConfig, {\n indent: 2,\n lineWidth: 120,\n noRefs: true,\n });\n\n // Dry run - just return what would happen\n if (dryRun) {\n const providersEnabled = Object.entries(yamlConfig.models?.providers || {})\n .filter(([_, enabled]) => enabled)\n .map(([name]) => name);\n\n return {\n success: true,\n overridesCount: Object.keys(yamlConfig.models?.overrides || {}).length,\n providersEnabled,\n message: `Would migrate to smart selection with ${providersEnabled.length} providers enabled`,\n };\n }\n\n // Write new config.yaml\n writeFileSync(NEW_CONFIG_PATH, yamlContent, 'utf-8');\n\n // Back up legacy settings if requested\n if (backup) {\n const legacyContent = readFileSync(LEGACY_SETTINGS_PATH, 'utf-8');\n writeFileSync(BACKUP_SETTINGS_PATH, legacyContent, 'utf-8');\n }\n\n // Delete legacy settings if requested\n if (deleteLegacy) {\n renameSync(LEGACY_SETTINGS_PATH, `${LEGACY_SETTINGS_PATH}.migrated`);\n }\n\n const providersEnabled = Object.entries(yamlConfig.models?.providers || {})\n .filter(([_, enabled]) => enabled)\n .map(([name]) => name);\n\n return {\n success: true,\n overridesCount: Object.keys(yamlConfig.models?.overrides || {}).length,\n providersEnabled,\n message: `Successfully migrated to smart selection with ${providersEnabled.length} providers`,\n };\n } catch (error: any) {\n return {\n success: false,\n overridesCount: 0,\n providersEnabled: [],\n message: 'Migration failed',\n error: error.message,\n };\n }\n}\n\n/**\n * Get migration status\n */\nexport function getMigrationStatus(): {\n needsMigration: boolean;\n hasLegacySettings: boolean;\n hasNewConfig: boolean;\n} {\n return {\n needsMigration: needsMigration(),\n hasLegacySettings: existsSync(LEGACY_SETTINGS_PATH),\n hasNewConfig: existsSync(NEW_CONFIG_PATH),\n };\n}\n\n/**\n * Clean up legacy runtime symlinks from removed runtimes.\n *\n * PAN-142: Panopticon consolidated to Claude Code as the sole runtime.\n * This removes any Panopticon-managed symlinks from legacy runtime directories\n * (codex, cursor, gemini, opencode).\n */\nexport interface LegacyCleanupResult {\n cleaned: string[];\n total: number;\n errors: string[];\n}\n\nexport function cleanupLegacyRuntimeSymlinks(): LegacyCleanupResult {\n const legacyDirs = [\n { name: 'codex', base: join(homedir(), '.codex') },\n { name: 'cursor', base: join(homedir(), '.cursor') },\n { name: 'gemini', base: join(homedir(), '.gemini') },\n { name: 'opencode', base: join(homedir(), '.opencode') },\n ];\n\n const cleaned: string[] = [];\n const errors: string[] = [];\n\n for (const { name, base } of legacyDirs) {\n for (const subdir of ['skills', 'commands', 'agents']) {\n const dir = join(base, subdir);\n if (!existsSync(dir)) continue;\n\n try {\n const entries = readdirSync(dir);\n for (const entry of entries) {\n const entryPath = join(dir, entry);\n try {\n const stats = lstatSync(entryPath);\n if (!stats.isSymbolicLink()) continue;\n\n const linkTarget = readlinkSync(entryPath);\n // Only remove symlinks pointing to Panopticon directories\n if (linkTarget.includes('.panopticon')) {\n unlinkSync(entryPath);\n cleaned.push(`${name}/${subdir}/${entry}`);\n }\n } catch (err: any) {\n errors.push(`${name}/${subdir}/${entry}: ${err.message}`);\n }\n }\n } catch (err: any) {\n // Directory may not be readable, that's fine\n errors.push(`${name}/${subdir}: ${err.message}`);\n }\n }\n }\n\n return { cleaned, total: cleaned.length, errors };\n}\n\n/**\n * Migrate legacy sync config by stripping the 'targets' field from config.toml.\n * This handles users who had `targets = [\"claude\", \"codex\"]` in their config.\n */\nexport function migrateSyncTargets(): { migrated: boolean; hadNonClaudeTargets: boolean } {\n const configPath = join(homedir(), '.panopticon', 'config.toml');\n\n if (!existsSync(configPath)) {\n return { migrated: false, hadNonClaudeTargets: false };\n }\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n\n // Check if targets field exists\n const targetsMatch = content.match(/^targets\\s*=\\s*\\[([^\\]]*)\\]/m);\n if (!targetsMatch) {\n return { migrated: false, hadNonClaudeTargets: false };\n }\n\n // Check if non-claude targets were configured\n const targetsStr = targetsMatch[1];\n const hadNonClaudeTargets = /codex|cursor|gemini|opencode/i.test(targetsStr);\n\n // Remove the targets line\n const newContent = content.replace(/^targets\\s*=\\s*\\[[^\\]]*\\]\\s*\\n?/m, '');\n writeFileSync(configPath, newContent, 'utf-8');\n\n return { migrated: true, hadNonClaudeTargets };\n } catch {\n return { migrated: false, hadNonClaudeTargets: false };\n }\n}\n","/**\n * Multi-Tool Skill Sync\n *\n * Writes Panopticon skills to other AI tool formats so skills authored once\n * in .pan/skills/ are available across all configured tools.\n *\n * Configured via `tools.also_sync` in ~/.panopticon/config.yaml and .pan.yaml.\n * Per-project .pan.yaml values are merged additively with global config.\n *\n * Supported targets:\n * cursor → .cursor/rules/<skill-name>.mdc\n * codex → AGENTS.md (named blocks)\n * windsurf → .windsurf/rules/<skill-name>.md\n * cline → .clinerules/<skill-name>.md\n * copilot → .github/instructions/<skill-name>.instructions.md\n * aider → CONVENTIONS.md (named blocks)\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport yaml from 'js-yaml';\nimport { PANOPTICON_HOME } from './paths.js';\n\nexport type AlsoSyncTool = 'cursor' | 'codex' | 'windsurf' | 'cline' | 'copilot' | 'aider';\n\nexport interface MultiToolSyncResult {\n tool: AlsoSyncTool;\n written: string[];\n skipped: string[];\n errors: string[];\n}\n\n/** Strip YAML frontmatter from a skill markdown file */\nfunction stripFrontmatter(content: string): string {\n if (!content.startsWith('---')) return content;\n const end = content.indexOf('\\n---', 4);\n if (end === -1) return content;\n return content.slice(end + 4).trimStart();\n}\n\n/** Extract the skill name from frontmatter, or fall back to dir name */\nfunction extractSkillName(content: string, fallback: string): string {\n if (!content.startsWith('---')) return fallback;\n const end = content.indexOf('\\n---', 4);\n if (end === -1) return fallback;\n const frontmatter = content.slice(4, end);\n const match = frontmatter.match(/^name:\\s*(.+)$/m);\n return match ? match[1].trim() : fallback;\n}\n\n/** Read main SKILL.md content for a skill directory */\nfunction readSkillContent(skillDir: string): string | null {\n const skillMd = join(skillDir, 'SKILL.md');\n if (!existsSync(skillMd)) {\n // Fallback: any .md file in root\n const files = existsSync(skillDir) ? readdirSync(skillDir).filter(f => f.endsWith('.md')) : [];\n if (files.length === 0) return null;\n return readFileSync(join(skillDir, files[0]), 'utf-8');\n }\n return readFileSync(skillMd, 'utf-8');\n}\n\n/** Collect all skill directories from the given skills root */\nfunction collectSkillDirs(skillsDir: string): Array<{ name: string; dir: string }> {\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir, { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => ({ name: e.name, dir: join(skillsDir, e.name) }));\n}\n\n/**\n * Update or insert a named block in a file.\n * Blocks are delimited by: <!-- panopticon:<skill-name> start --> ... <!-- panopticon:<skill-name> end -->\n */\nfunction upsertNamedBlock(filePath: string, blockName: string, content: string): void {\n const startTag = `<!-- panopticon:${blockName} start -->`;\n const endTag = `<!-- panopticon:${blockName} end -->`;\n const block = `${startTag}\\n${content}\\n${endTag}`;\n\n let existing = existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';\n\n const startIdx = existing.indexOf(startTag);\n const endIdx = existing.indexOf(endTag);\n\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n // Replace existing block\n existing = existing.slice(0, startIdx) + block + existing.slice(endIdx + endTag.length);\n } else {\n // Append new block\n if (existing.length > 0 && !existing.endsWith('\\n')) existing += '\\n';\n existing += '\\n' + block + '\\n';\n }\n\n writeFileSync(filePath, existing, 'utf-8');\n}\n\n/** Sync a single skill to the cursor target */\nfunction syncToCursor(projectPath: string, skillName: string, rawContent: string): void {\n const rulesDir = join(projectPath, '.cursor', 'rules');\n mkdirSync(rulesDir, { recursive: true });\n const body = stripFrontmatter(rawContent);\n // .mdc files: standard markdown, cursor accepts them as context rules\n writeFileSync(join(rulesDir, `${skillName}.mdc`), body, 'utf-8');\n}\n\n/** Sync a single skill to the windsurf target */\nfunction syncToWindsurf(projectPath: string, skillName: string, rawContent: string): void {\n const rulesDir = join(projectPath, '.windsurf', 'rules');\n mkdirSync(rulesDir, { recursive: true });\n writeFileSync(join(rulesDir, `${skillName}.md`), stripFrontmatter(rawContent), 'utf-8');\n}\n\n/** Sync a single skill to the cline target */\nfunction syncToCline(projectPath: string, skillName: string, rawContent: string): void {\n const rulesDir = join(projectPath, '.clinerules');\n mkdirSync(rulesDir, { recursive: true });\n writeFileSync(join(rulesDir, `${skillName}.md`), stripFrontmatter(rawContent), 'utf-8');\n}\n\n/** Sync a single skill to the copilot target */\nfunction syncToCopilot(projectPath: string, skillName: string, rawContent: string): void {\n const instructionsDir = join(projectPath, '.github', 'instructions');\n mkdirSync(instructionsDir, { recursive: true });\n writeFileSync(\n join(instructionsDir, `${skillName}.instructions.md`),\n stripFrontmatter(rawContent),\n 'utf-8',\n );\n}\n\n/** Sync a single skill to AGENTS.md (codex) as a named block */\nfunction syncToCodex(projectPath: string, skillName: string, rawContent: string): void {\n const agentsMd = join(projectPath, 'AGENTS.md');\n upsertNamedBlock(agentsMd, skillName, `## ${skillName}\\n\\n${stripFrontmatter(rawContent)}`);\n}\n\n/** Sync a single skill to CONVENTIONS.md (aider) as a named block */\nfunction syncToAider(projectPath: string, skillName: string, rawContent: string): void {\n const conventionsMd = join(projectPath, 'CONVENTIONS.md');\n upsertNamedBlock(conventionsMd, skillName, `## ${skillName}\\n\\n${stripFrontmatter(rawContent)}`);\n}\n\nconst TOOL_WRITERS: Record<AlsoSyncTool, (projectPath: string, name: string, content: string) => void> = {\n cursor: syncToCursor,\n windsurf: syncToWindsurf,\n cline: syncToCline,\n copilot: syncToCopilot,\n codex: syncToCodex,\n aider: syncToAider,\n};\n\n/**\n * Resolve the merged list of tools to sync.\n * Global config is the base; per-project .pan.yaml adds more (never removes).\n */\nexport function resolveAlsoSyncTools(projectPath?: string): AlsoSyncTool[] {\n const tools = new Set<AlsoSyncTool>();\n\n // Read from global config\n const globalConfig = join(PANOPTICON_HOME, 'config.yaml');\n if (existsSync(globalConfig)) {\n try {\n const parsed = yaml.load(readFileSync(globalConfig, 'utf-8')) as any;\n const globalTools: string[] = parsed?.tools?.also_sync || [];\n for (const t of globalTools) {\n if (t in TOOL_WRITERS) tools.add(t as AlsoSyncTool);\n }\n } catch { /* ignore parse errors */ }\n }\n\n // Merge per-project .pan.yaml (additive)\n if (projectPath) {\n const panYaml = join(projectPath, '.pan.yaml');\n const legacyYaml = join(projectPath, '.panopticon.yaml');\n const configPath = existsSync(panYaml) ? panYaml : existsSync(legacyYaml) ? legacyYaml : null;\n if (configPath) {\n try {\n const parsed = yaml.load(readFileSync(configPath, 'utf-8')) as any;\n const projectTools: string[] = parsed?.tools?.also_sync || [];\n for (const t of projectTools) {\n if (t in TOOL_WRITERS) tools.add(t as AlsoSyncTool);\n }\n } catch { /* ignore parse errors */ }\n }\n }\n\n return Array.from(tools);\n}\n\n/**\n * Sync skills from a skills directory to all configured tools.\n *\n * @param skillsDir Directory containing skill subdirectories\n * @param projectPath Project root where tool targets live\n * @param tools Tools to sync to (from resolveAlsoSyncTools)\n */\nexport function syncSkillsToTools(\n skillsDir: string,\n projectPath: string,\n tools: AlsoSyncTool[],\n): MultiToolSyncResult[] {\n if (tools.length === 0 || !existsSync(skillsDir)) return [];\n\n const skills = collectSkillDirs(skillsDir);\n const results: MultiToolSyncResult[] = [];\n\n for (const tool of tools) {\n const writer = TOOL_WRITERS[tool];\n const result: MultiToolSyncResult = { tool, written: [], skipped: [], errors: [] };\n\n for (const { name, dir } of skills) {\n try {\n const rawContent = readSkillContent(dir);\n if (!rawContent) {\n result.skipped.push(name);\n continue;\n }\n const displayName = extractSkillName(rawContent, name);\n writer(projectPath, displayName, rawContent);\n result.written.push(name);\n } catch (err: any) {\n result.errors.push(`${name}: ${err.message}`);\n }\n }\n\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * Run the full multi-tool sync for a project.\n * Sources: .pan/skills/ (project-local) and/or ~/.panopticon/skills/ (global).\n */\nexport function runMultiToolSync(projectPath: string): MultiToolSyncResult[] {\n const tools = resolveAlsoSyncTools(projectPath);\n if (tools.length === 0) return [];\n\n const allResults: MultiToolSyncResult[] = [];\n\n // 1. Global skills (from ~/.panopticon/skills/)\n const globalSkillsDir = join(PANOPTICON_HOME, 'skills');\n const globalResults = syncSkillsToTools(globalSkillsDir, projectPath, tools);\n allResults.push(...globalResults);\n\n // 2. Project-local skills (from .pan/skills/) — may overwrite global skill entries\n const projectSkillsDir = join(projectPath, '.pan', 'skills');\n if (existsSync(projectSkillsDir)) {\n const projectResults = syncSkillsToTools(projectSkillsDir, projectPath, tools);\n // Merge into existing results (project results override counts, don't duplicate tools)\n for (const pr of projectResults) {\n const existing = allResults.find(r => r.tool === pr.tool);\n if (existing) {\n existing.written.push(...pr.written);\n existing.errors.push(...pr.errors);\n } else {\n allResults.push(pr);\n }\n }\n }\n\n return allResults;\n}\n","import chalk from 'chalk';\nimport ora from 'ora';\nimport { execSync } from 'child_process';\nimport { existsSync, readdirSync, readFileSync, writeFileSync, statSync, symlinkSync, mkdirSync } from 'fs';\nimport { homedir } from 'os';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { loadConfig } from '../../lib/config.js';\nimport { createBackup } from '../../lib/backup.js';\nimport { planSync, executeSync, refreshCache, migrateStalePersonalContent, planHooksSync, syncHooks, syncStatusline } from '../../lib/sync.js';\nimport { SYNC_TARGET, isDevMode } from '../../lib/paths.js';\nimport { getDevrootPath } from '../../lib/config.js';\nimport { listProjects } from '../../lib/projects.js';\nimport { cleanupLegacyRuntimeSymlinks, migrateSyncTargets } from '../../lib/config-migration.js';\nimport { migratePanopticonToPan } from '../../lib/workspace-manager.js';\nimport { runMultiToolSync, resolveAlsoSyncTools } from '../../lib/multi-tool-sync.js';\n\n// Get path to bundled git hooks\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst BUNDLED_GIT_HOOKS_DIR = join(__dirname, '..', '..', 'scripts', 'git-hooks');\n\n// Helper to check if a command exists\nfunction checkCommand(cmd: string): boolean {\n try {\n execSync(`which ${cmd}`, { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n}\n\ninterface SyncOptions {\n dryRun?: boolean;\n force?: boolean;\n diff?: boolean;\n backupOnly?: boolean;\n}\n\nexport async function syncCommand(options: SyncOptions): Promise<void> {\n // Dry run mode\n if (options.dryRun) {\n console.log(chalk.bold('Sync Plan (dry run):\\n'));\n\n // Show dev mode status\n if (isDevMode()) {\n console.log(chalk.magenta('Developer mode detected - dev-skills will be synced\\n'));\n }\n\n // Show hooks plan\n const hooksPlan = planHooksSync();\n if (hooksPlan.length > 0) {\n console.log(chalk.cyan('hooks (bin scripts):'));\n for (const hook of hooksPlan) {\n const icon = hook.status === 'new' ? chalk.green('+') : chalk.blue('↻');\n const status = hook.status === 'new' ? '' : chalk.dim('[update]');\n console.log(` ${icon} ${hook.name} ${status}`);\n }\n console.log('');\n }\n\n const devrootPath = getDevrootPath();\n console.log(chalk.cyan(`devroot (${devrootPath || 'disabled'}):`));\n\n if (!devrootPath) {\n console.log(chalk.dim(' (devroot disabled — set sync.devroot in config)'));\n } else {\n const plan = planSync();\n const allItems = [...plan.skills, ...plan.agents, ...plan.rules, ...plan.commands];\n\n if (allItems.length === 0) {\n console.log(chalk.dim(' (nothing to sync)'));\n } else {\n for (const item of allItems) {\n const icon = item.status === 'conflict' ? chalk.yellow('!') :\n item.status === 'symlink' ? chalk.blue('↻') :\n chalk.green('+');\n const label = item.status === 'conflict' ? chalk.yellow('[modified]') :\n item.status === 'symlink' ? chalk.dim('[update]') :\n chalk.green('[new]');\n console.log(` ${icon} ${item.name} ${label}`);\n }\n }\n }\n\n // Show .pan/skills/ source files for each registered project\n const dryRunProjects = listProjects();\n for (const { config } of dryRunProjects) {\n if (!existsSync(config.path)) continue;\n const panSkillsDir = join(config.path, '.pan', 'skills');\n if (existsSync(panSkillsDir)) {\n const skills = readdirSync(panSkillsDir, { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => e.name);\n if (skills.length > 0) {\n console.log(chalk.cyan(`\\n.pan/skills/ (${config.name}):`));\n for (const skillName of skills) {\n console.log(` ${chalk.green('+')} ${skillName} ${chalk.green('[project-local]')}`);\n }\n }\n }\n\n // Show multi-tool sync targets\n const tools = resolveAlsoSyncTools(config.path);\n if (tools.length > 0) {\n console.log(chalk.cyan(`\\nmulti-tool sync (${config.name}): ${tools.join(', ')}`));\n const panSkillsDirExists = existsSync(join(config.path, '.pan', 'skills'));\n if (panSkillsDirExists) {\n const skills = readdirSync(join(config.path, '.pan', 'skills'), { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => e.name);\n for (const tool of tools) {\n for (const skillName of skills) {\n console.log(` ${chalk.green('+')} ${skillName} → ${tool}`);\n }\n }\n }\n }\n }\n\n console.log('');\n console.log(chalk.dim('Run without --dry-run to apply changes.'));\n return;\n }\n\n // Run one-time migration: strip legacy sync targets from config.toml\n const syncMigration = migrateSyncTargets();\n if (syncMigration.migrated) {\n if (syncMigration.hadNonClaudeTargets) {\n console.log(chalk.yellow('Config updated: removed non-Claude sync targets (Panopticon now syncs to Claude Code only).'));\n }\n }\n\n // Run one-time migration: remove Panopticon-managed symlinks from legacy runtime dirs\n const cleanupResult = cleanupLegacyRuntimeSymlinks();\n if (cleanupResult.cleaned.length > 0) {\n console.log(chalk.dim(`Removed ${cleanupResult.total} legacy runtime symlink(s): ${cleanupResult.cleaned.join(', ')}`));\n }\n\n // One-time migration: remove Panopticon symlinks from ~/.claude/ (devroot replaces this)\n const migration = migrateStalePersonalContent();\n if (migration.removedSymlinks.length > 0) {\n console.log(chalk.cyan(`Migrated: removed ${migration.removedSymlinks.length} Panopticon symlink(s) from ~/.claude/`));\n if (migration.preservedUserContent.length > 0) {\n console.log(chalk.dim(` Preserved ${migration.preservedUserContent.length} user-created item(s)`));\n }\n }\n\n const config = loadConfig();\n\n // Create backup if enabled\n if (config.sync.backup_before_sync) {\n const spinner = ora('Creating backup...').start();\n\n const backupDirs = [\n SYNC_TARGET.skills,\n SYNC_TARGET.commands,\n SYNC_TARGET.agents,\n ];\n\n const backup = createBackup(backupDirs);\n\n if (backup.targets.length > 0) {\n spinner.succeed(`Backup created: ${backup.timestamp}`);\n } else {\n spinner.info('No existing content to backup');\n }\n\n if (options.backupOnly) {\n return;\n }\n }\n\n // Refresh cache from repo source\n const cacheSpinner = ora('Refreshing cache from repo...').start();\n const cacheResult = refreshCache();\n const cacheParts = [];\n if (cacheResult.skills.copied > 0) cacheParts.push(`${cacheResult.skills.copied} skills`);\n if (cacheResult.agents.copied > 0) cacheParts.push(`${cacheResult.agents.copied} agents`);\n if (cacheResult.rules.copied > 0) cacheParts.push(`${cacheResult.rules.copied} rules`);\n cacheSpinner.succeed(`Cache refreshed: ${cacheParts.length > 0 ? cacheParts.join(', ') : 'up to date'}`);\n\n // Execute sync to devroot\n const devrootPath = getDevrootPath();\n const spinner = ora(`Syncing to devroot (${devrootPath || 'disabled'})...`).start();\n\n if (!devrootPath) {\n spinner.info('Devroot disabled (set sync.devroot in config to enable)');\n } else {\n const result = executeSync({ force: options.force, diff: options.diff });\n const totalSynced = result.created.length + result.updated.length;\n\n // Show diffs if requested\n if (result.diffs.length > 0) {\n spinner.info(`Showing diffs for ${result.diffs.length} modified file(s):\\n`);\n for (const d of result.diffs) {\n console.log(chalk.cyan(`--- ${d.path} (installed)`));\n console.log(chalk.cyan(`+++ ${d.path} (current on disk)`));\n // Simple line-by-line diff\n const sourceLines = d.sourceContent.split('\\n');\n const targetLines = d.targetContent.split('\\n');\n const maxLines = Math.max(sourceLines.length, targetLines.length);\n for (let i = 0; i < maxLines; i++) {\n if (sourceLines[i] !== targetLines[i]) {\n if (targetLines[i] !== undefined) console.log(chalk.red(`- ${targetLines[i]}`));\n if (sourceLines[i] !== undefined) console.log(chalk.green(`+ ${sourceLines[i]}`));\n }\n }\n console.log('');\n }\n }\n\n if (result.conflicts.length > 0 && !options.force) {\n spinner.warn(`Synced ${totalSynced} items, ${result.conflicts.length} conflicts`);\n console.log('');\n console.log(chalk.yellow('Modified since Panopticon installed:'));\n for (const name of result.conflicts) {\n console.log(chalk.dim(` - ${name}`));\n }\n console.log('');\n console.log(chalk.dim('Use --force to overwrite, --diff to see changes.'));\n } else if (result.skipped.length > 0) {\n spinner.succeed(`Synced ${totalSynced} items to devroot (${result.skipped.length} user-owned skipped)`);\n } else {\n spinner.succeed(`Synced ${totalSynced} items to devroot`);\n }\n }\n\n // Sync hooks (bin scripts)\n const hooksSpinner = ora('Syncing hooks...').start();\n const hooksResult = syncHooks();\n\n if (hooksResult.errors.length > 0) {\n hooksSpinner.warn(`Synced ${hooksResult.synced.length} hooks, ${hooksResult.errors.length} errors`);\n for (const error of hooksResult.errors) {\n console.log(chalk.red(` ✗ ${error}`));\n }\n } else if (hooksResult.synced.length > 0) {\n hooksSpinner.succeed(`Synced ${hooksResult.synced.length} hooks to ~/.panopticon/bin/`);\n } else {\n hooksSpinner.info('No hooks to sync');\n }\n\n const projects = listProjects();\n\n // Ensure beads database exists for each registered project (first-time setup guard).\n // bd install puts the binary in PATH, but bd init must be run once per project to\n // create the Dolt database. Without it, workspace beads creation silently fails.\n if (projects.length > 0 && checkCommand('bd')) {\n for (const { key, config } of projects) {\n if (!existsSync(config.path)) continue;\n const mainBeadsDir = join(config.path, '.beads');\n if (!existsSync(mainBeadsDir)) continue; // Project hasn't used beads yet — skip\n // Test connectivity. If the database is missing, auto-init.\n try {\n execSync('bd list --json --limit 0 2>&1', { cwd: config.path, stdio: 'pipe', timeout: 8000 });\n } catch (e: any) {\n const msg = String(e?.stdout ?? e?.stderr ?? e?.message ?? '');\n if (msg.includes('database') && (msg.includes('not found') || msg.includes('not exist') || msg.includes('defaulting'))) {\n const beadsSpinner = ora(`Initializing beads database for ${config.name}...`).start();\n try {\n const prefix = (key || config.name).toLowerCase().replace(/[^a-z0-9-]/g, '-');\n execSync(`bd init --prefix ${prefix}`, { cwd: config.path, stdio: 'pipe', timeout: 20000 });\n try { execSync('git config beads.role contributor', { cwd: config.path, stdio: 'pipe' }); } catch { /* non-fatal */ }\n beadsSpinner.succeed(`Beads database initialized for ${config.name} (prefix: ${prefix})`);\n } catch {\n beadsSpinner.warn(`Could not auto-initialize beads for ${config.name} — run: cd ${config.path} && bd init`);\n }\n }\n }\n }\n }\n\n\n // Check jq availability (required by statusline, beads, specialists)\n if (!checkCommand('jq')) {\n console.log(chalk.yellow('\\n ⚠ jq not found — statusline and other features need it'));\n console.log(chalk.dim(' Install: apt install jq / brew install jq\\n'));\n }\n\n // Sync statusline to all runtimes\n const statuslineSpinner = ora('Syncing statusline...').start();\n const statuslineResult = syncStatusline();\n\n if (statuslineResult.errors.length > 0) {\n statuslineSpinner.warn(`Synced statusline to ${statuslineResult.synced.length} runtime(s), ${statuslineResult.errors.length} error(s)`);\n for (const error of statuslineResult.errors) {\n console.log(chalk.red(` ✗ ${error}`));\n }\n } else if (statuslineResult.synced.length > 0) {\n statuslineSpinner.succeed(`Synced statusline to ${statuslineResult.synced.join(', ')}`);\n } else {\n statuslineSpinner.info('No statusline script found (scripts/statusline.sh)');\n }\n\n // Check and install claude-code-router if missing\n const hasRouter = checkCommand('claude-code-router');\n if (!hasRouter) {\n const routerSpinner = ora('Installing claude-code-router...').start();\n try {\n execSync('npm install -g @musistudio/claude-code-router', {\n stdio: 'pipe',\n timeout: 120000\n });\n routerSpinner.succeed('claude-code-router installed');\n } catch (error) {\n routerSpinner.warn('Failed to install claude-code-router - run: npm install -g @musistudio/claude-code-router');\n }\n }\n\n // Check and install mkcert if missing\n if (!checkCommand('mkcert')) {\n const mkcertSpinner = ora('Installing mkcert...').start();\n try {\n const binDir = join(homedir(), '.local', 'bin');\n mkdirSync(binDir, { recursive: true });\n const mkcertPath = join(binDir, 'mkcert');\n const arch = process.arch === 'x64' ? 'amd64' : process.arch;\n execSync(`curl -sL \"https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${arch}\" -o \"${mkcertPath}\" && chmod +x \"${mkcertPath}\"`, {\n stdio: 'pipe',\n timeout: 60000,\n });\n mkcertSpinner.succeed('mkcert installed');\n } catch {\n mkcertSpinner.warn('Failed to install mkcert - run: https://github.com/FiloSottile/mkcert/releases');\n }\n }\n\n // Check and install SageOx CLI if missing\n if (!checkCommand('ox')) {\n const oxSpinner = ora('Installing SageOx CLI (ox)...').start();\n try {\n const binDir = join(homedir(), '.local', 'bin');\n mkdirSync(binDir, { recursive: true });\n const oxPath = join(binDir, 'ox');\n const arch = process.arch === 'x64' ? 'amd64' : process.arch;\n const platform = process.platform === 'darwin' ? 'darwin' : 'linux';\n execSync(`curl -sL \"https://github.com/eltmon/ox/releases/download/latest/ox-${platform}-${arch}\" -o \"${oxPath}\" && chmod +x \"${oxPath}\"`, {\n stdio: 'pipe',\n timeout: 60000,\n });\n oxSpinner.succeed('SageOx CLI installed');\n } catch {\n oxSpinner.warn('Failed to install SageOx CLI - see: https://github.com/eltmon/ox/releases');\n }\n }\n\n\n // Enforce Playwright MCP --isolated flag to prevent stale zoom/profile state\n const mcpPath = join(homedir(), '.claude', 'mcp.json');\n try {\n if (existsSync(mcpPath)) {\n const mcpConfig = JSON.parse(readFileSync(mcpPath, 'utf-8'));\n const pw = mcpConfig?.mcpServers?.playwright;\n if (pw && Array.isArray(pw.args) && !pw.args.includes('--isolated')) {\n pw.args.push('--isolated');\n writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\\n');\n console.log(chalk.green('✓ Added --isolated to Playwright MCP (prevents stale zoom/profile state)'));\n }\n }\n } catch {\n // Non-fatal — skip if mcp.json can't be read/written\n }\n\n // Ensure beads database exists for each registered project (first-time setup guard).\n // bd install puts the binary in PATH, but bd init must be run once per project to\n // create the Dolt database. Without it, workspace beads creation silently fails.\n // Migrate .panopticon/ → .pan/ and run multi-tool sync in all registered projects\n\n for (const { config } of projects) {\n if (!existsSync(config.path)) continue;\n\n // Migrate .panopticon/ subdirs → .pan/\n const migResult = migratePanopticonToPan(config.path);\n if (migResult.migrated.length > 0) {\n console.log(chalk.cyan(`Migrated .panopticon/ → .pan/ in ${config.name}: ${migResult.migrated.join(', ')}`));\n }\n if (migResult.skipped.length > 0) {\n console.log(chalk.yellow(`Migration skipped (both exist) in ${config.name}: ${migResult.skipped.join(', ')}`));\n }\n for (const err of migResult.errors) {\n console.log(chalk.red(`Migration error in ${config.name}: ${err}`));\n }\n\n // Multi-tool skill sync (cursor, codex, windsurf, cline, copilot, aider)\n const toolSyncResults = runMultiToolSync(config.path);\n for (const r of toolSyncResults) {\n if (r.written.length > 0) {\n console.log(chalk.cyan(`Synced ${r.written.length} skill(s) to ${r.tool} in ${config.name}`));\n }\n for (const err of r.errors) {\n console.log(chalk.red(`Multi-tool sync error (${r.tool}) in ${config.name}: ${err}`));\n }\n }\n }\n\n // Sync git hooks to all registered projects (branch protection)\n if (projects.length > 0 && existsSync(BUNDLED_GIT_HOOKS_DIR)) {\n const gitHooksSpinner = ora('Installing git hooks in registered projects...').start();\n let totalInstalled = 0;\n let projectsUpdated = 0;\n\n for (const { config } of projects) {\n if (!existsSync(config.path)) continue;\n\n // Find all .git directories (handles polyrepos)\n const gitDirs: string[] = [];\n\n // Check root\n if (existsSync(join(config.path, '.git')) && statSync(join(config.path, '.git')).isDirectory()) {\n gitDirs.push(join(config.path, '.git'));\n } else {\n // Scan for polyrepo\n try {\n const entries = readdirSync(config.path);\n for (const entry of entries) {\n const entryPath = join(config.path, entry);\n const gitPath = join(entryPath, '.git');\n if (existsSync(gitPath) && statSync(gitPath).isDirectory()) {\n gitDirs.push(gitPath);\n }\n }\n } catch {\n // Skip unreadable directories\n }\n }\n\n // Install hooks in each git dir\n for (const gitDir of gitDirs) {\n const hooksTarget = join(gitDir, 'hooks');\n if (!existsSync(hooksTarget)) {\n mkdirSync(hooksTarget, { recursive: true });\n }\n\n try {\n const hooks = readdirSync(BUNDLED_GIT_HOOKS_DIR).filter(f =>\n statSync(join(BUNDLED_GIT_HOOKS_DIR, f)).isFile()\n );\n\n for (const hook of hooks) {\n const source = join(BUNDLED_GIT_HOOKS_DIR, hook);\n const target = join(hooksTarget, hook);\n\n // Skip if already a symlink to our hook\n if (existsSync(target)) {\n try {\n const { readlinkSync } = await import('fs');\n if (readlinkSync(target) === source) continue;\n } catch {\n // Not a symlink\n }\n // Backup existing\n const { renameSync } = await import('fs');\n try { renameSync(target, `${target}.backup`); } catch {}\n }\n\n try {\n symlinkSync(source, target);\n totalInstalled++;\n } catch {}\n }\n projectsUpdated++;\n } catch {}\n }\n }\n\n if (totalInstalled > 0) {\n gitHooksSpinner.succeed(`Installed git hooks in ${projectsUpdated} project(s)`);\n } else {\n gitHooksSpinner.info('Git hooks already up to date');\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { o as init_interface } from "./rally-Dy00NElU.js";
|
|
2
|
-
import { c as init_gitlab, f as init_linear, o as init_factory, u as init_github } from "./factory-
|
|
2
|
+
import { c as init_gitlab, f as init_linear, o as init_factory, u as init_github } from "./factory-D6LJaZ__.js";
|
|
3
3
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
@@ -195,4 +195,4 @@ init_factory();
|
|
|
195
195
|
//#endregion
|
|
196
196
|
export { addAlias as a, getShellRcFile as c, parseIssueRef as i, hasAlias as l, formatIssueRef as n, detectShell as o, getLinkManager as r, getAliasInstructions as s, LinkManager as t };
|
|
197
197
|
|
|
198
|
-
//# sourceMappingURL=tracker-
|
|
198
|
+
//# sourceMappingURL=tracker-CYpb7oUa.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracker-BhYYvU3p.js","names":[],"sources":["../src/lib/shell.ts","../src/lib/tracker/linking.ts","../src/lib/tracker/index.ts"],"sourcesContent":["import { existsSync, readFileSync, appendFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport type Shell = 'bash' | 'zsh' | 'fish' | 'unknown';\n\nexport function detectShell(): Shell {\n const shell = process.env.SHELL || '';\n\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('fish')) return 'fish';\n\n return 'unknown';\n}\n\nexport function getShellRcFile(shell: Shell): string | null {\n const home = homedir();\n\n switch (shell) {\n case 'zsh':\n return join(home, '.zshrc');\n case 'bash':\n // Prefer .bashrc, fall back to .bash_profile\n const bashrc = join(home, '.bashrc');\n if (existsSync(bashrc)) return bashrc;\n return join(home, '.bash_profile');\n case 'fish':\n return join(home, '.config', 'fish', 'config.fish');\n default:\n return null;\n }\n}\n\nconst ALIAS_LINE = 'alias pan=\"panopticon\"';\nconst ALIAS_MARKER = '# Panopticon CLI alias';\n\nexport function hasAlias(rcFile: string): boolean {\n if (!existsSync(rcFile)) return false;\n\n const content = readFileSync(rcFile, 'utf8');\n return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);\n}\n\nexport function addAlias(rcFile: string): void {\n if (hasAlias(rcFile)) return;\n\n const aliasBlock = `\n${ALIAS_MARKER}\n${ALIAS_LINE}\n`;\n\n appendFileSync(rcFile, aliasBlock, 'utf8');\n}\n\nexport function getAliasInstructions(shell: Shell): string {\n const rcFile = getShellRcFile(shell);\n\n if (!rcFile) {\n return `Add this to your shell config:\\n ${ALIAS_LINE}`;\n }\n\n return `Alias added to ${rcFile}. Run:\\n source ${rcFile}`;\n}\n","/**\n * Cross-Tracker Linking\n *\n * Manages links between issues in different trackers.\n * Links are stored in a local JSON file for persistence.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { TrackerType } from './interface.js';\n\n// Link direction types\nexport type LinkDirection = 'blocks' | 'blocked_by' | 'related' | 'duplicate_of';\n\n// A single link between two issues\nexport interface TrackerLink {\n sourceIssueRef: string; // e.g., \"MIN-630\"\n sourceTracker: TrackerType;\n targetIssueRef: string; // e.g., \"#42\"\n targetTracker: TrackerType;\n direction: LinkDirection;\n createdAt: string; // ISO timestamp\n}\n\n// Storage format\ninterface LinkStore {\n version: 1;\n links: TrackerLink[];\n}\n\n/**\n * Parse an issue reference to extract tracker and ID\n * Examples:\n * \"#42\" -> { tracker: \"github\", ref: \"#42\" }\n * \"github#42\" -> { tracker: \"github\", ref: \"#42\" }\n * \"MIN-630\" -> { tracker: \"linear\", ref: \"MIN-630\" }\n * \"gitlab#15\" -> { tracker: \"gitlab\", ref: \"#15\" }\n */\nexport function parseIssueRef(ref: string): { tracker: TrackerType; ref: string } | null {\n // Explicit tracker prefix\n if (ref.startsWith('github#')) {\n return { tracker: 'github', ref: `#${ref.slice(7)}` };\n }\n if (ref.startsWith('gitlab#')) {\n return { tracker: 'gitlab', ref: `#${ref.slice(7)}` };\n }\n if (ref.startsWith('linear:')) {\n return { tracker: 'linear', ref: ref.slice(7) };\n }\n\n // GitHub-style refs (#number)\n if (/^#\\d+$/.test(ref)) {\n return { tracker: 'github', ref };\n }\n\n // Linear-style refs (XXX-123)\n if (/^[A-Z]+-\\d+$/i.test(ref)) {\n return { tracker: 'linear', ref: ref.toUpperCase() };\n }\n\n return null;\n}\n\n/**\n * Format an issue ref with tracker prefix for display\n */\nexport function formatIssueRef(ref: string, tracker: TrackerType): string {\n if (tracker === 'github') {\n return ref.startsWith('#') ? `github${ref}` : `github#${ref}`;\n }\n if (tracker === 'gitlab') {\n return ref.startsWith('#') ? `gitlab${ref}` : `gitlab#${ref}`;\n }\n return ref; // Linear refs are already unique\n}\n\n/**\n * Link Manager for cross-tracker issue linking\n */\nexport class LinkManager {\n private storePath: string;\n private store: LinkStore;\n\n constructor(storePath?: string) {\n this.storePath = storePath ?? join(homedir(), '.panopticon', 'links.json');\n this.store = this.load();\n }\n\n private load(): LinkStore {\n if (existsSync(this.storePath)) {\n try {\n const data = JSON.parse(readFileSync(this.storePath, 'utf-8'));\n if (data.version === 1) {\n return data;\n }\n } catch {\n // Fall through to default\n }\n }\n return { version: 1, links: [] };\n }\n\n private save(): void {\n const dir = join(this.storePath, '..');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(this.storePath, JSON.stringify(this.store, null, 2));\n }\n\n /**\n * Add a link between two issues\n */\n addLink(\n source: { ref: string; tracker: TrackerType },\n target: { ref: string; tracker: TrackerType },\n direction: LinkDirection = 'related'\n ): TrackerLink {\n // Check if link already exists\n const existing = this.store.links.find(\n (l) =>\n l.sourceIssueRef === source.ref &&\n l.sourceTracker === source.tracker &&\n l.targetIssueRef === target.ref &&\n l.targetTracker === target.tracker\n );\n\n if (existing) {\n // Update direction if different\n if (existing.direction !== direction) {\n existing.direction = direction;\n this.save();\n }\n return existing;\n }\n\n const link: TrackerLink = {\n sourceIssueRef: source.ref,\n sourceTracker: source.tracker,\n targetIssueRef: target.ref,\n targetTracker: target.tracker,\n direction,\n createdAt: new Date().toISOString(),\n };\n\n this.store.links.push(link);\n this.save();\n return link;\n }\n\n /**\n * Remove a link between two issues\n */\n removeLink(\n source: { ref: string; tracker: TrackerType },\n target: { ref: string; tracker: TrackerType }\n ): boolean {\n const index = this.store.links.findIndex(\n (l) =>\n l.sourceIssueRef === source.ref &&\n l.sourceTracker === source.tracker &&\n l.targetIssueRef === target.ref &&\n l.targetTracker === target.tracker\n );\n\n if (index >= 0) {\n this.store.links.splice(index, 1);\n this.save();\n return true;\n }\n return false;\n }\n\n /**\n * Get all issues linked to a given issue\n */\n getLinkedIssues(ref: string, tracker: TrackerType): TrackerLink[] {\n return this.store.links.filter(\n (l) =>\n (l.sourceIssueRef === ref && l.sourceTracker === tracker) ||\n (l.targetIssueRef === ref && l.targetTracker === tracker)\n );\n }\n\n /**\n * Get all links (for debugging/admin)\n */\n getAllLinks(): TrackerLink[] {\n return [...this.store.links];\n }\n\n /**\n * Find linked issue in another tracker\n */\n findLinkedIssue(\n ref: string,\n sourceTracker: TrackerType,\n targetTracker: TrackerType\n ): string | null {\n // Check as source\n const asSource = this.store.links.find(\n (l) =>\n l.sourceIssueRef === ref &&\n l.sourceTracker === sourceTracker &&\n l.targetTracker === targetTracker\n );\n if (asSource) return asSource.targetIssueRef;\n\n // Check as target\n const asTarget = this.store.links.find(\n (l) =>\n l.targetIssueRef === ref &&\n l.targetTracker === sourceTracker &&\n l.sourceTracker === targetTracker\n );\n if (asTarget) return asTarget.sourceIssueRef;\n\n return null;\n }\n\n /**\n * Clear all links (for testing)\n */\n clear(): void {\n this.store.links = [];\n this.save();\n }\n}\n\n// Singleton instance\nlet _linkManager: LinkManager | null = null;\n\nexport function getLinkManager(): LinkManager {\n if (!_linkManager) {\n _linkManager = new LinkManager();\n }\n return _linkManager;\n}\n","/**\n * Issue Tracker Module\n *\n * Provides a unified interface for different issue tracking systems.\n */\n\n// Core interface and types\nexport type {\n IssueTracker,\n Issue,\n IssueFilters,\n IssueState,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\n\nexport {\n NotImplementedError,\n IssueNotFoundError,\n TrackerAuthError,\n} from './interface.js';\n\n// Tracker implementations\nexport { LinearTracker } from './linear.js';\nexport { GitHubTracker } from './github.js';\nexport { GitLabTracker } from './gitlab.js';\n\n// Factory functions\nexport type { TrackerConfig } from './factory.js';\nexport {\n createTracker,\n createTrackerFromConfig,\n getPrimaryTracker,\n getSecondaryTracker,\n getAllTrackers,\n} from './factory.js';\n\n// Cross-tracker linking\nexport type { TrackerLink, LinkDirection } from './linking.js';\nexport {\n LinkManager,\n getLinkManager,\n parseIssueRef,\n formatIssueRef,\n} from './linking.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tracker-CYpb7oUa.js","names":[],"sources":["../src/lib/shell.ts","../src/lib/tracker/linking.ts","../src/lib/tracker/index.ts"],"sourcesContent":["import { existsSync, readFileSync, appendFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport type Shell = 'bash' | 'zsh' | 'fish' | 'unknown';\n\nexport function detectShell(): Shell {\n const shell = process.env.SHELL || '';\n\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('fish')) return 'fish';\n\n return 'unknown';\n}\n\nexport function getShellRcFile(shell: Shell): string | null {\n const home = homedir();\n\n switch (shell) {\n case 'zsh':\n return join(home, '.zshrc');\n case 'bash':\n // Prefer .bashrc, fall back to .bash_profile\n const bashrc = join(home, '.bashrc');\n if (existsSync(bashrc)) return bashrc;\n return join(home, '.bash_profile');\n case 'fish':\n return join(home, '.config', 'fish', 'config.fish');\n default:\n return null;\n }\n}\n\nconst ALIAS_LINE = 'alias pan=\"panopticon\"';\nconst ALIAS_MARKER = '# Panopticon CLI alias';\n\nexport function hasAlias(rcFile: string): boolean {\n if (!existsSync(rcFile)) return false;\n\n const content = readFileSync(rcFile, 'utf8');\n return content.includes(ALIAS_MARKER) || content.includes(ALIAS_LINE);\n}\n\nexport function addAlias(rcFile: string): void {\n if (hasAlias(rcFile)) return;\n\n const aliasBlock = `\n${ALIAS_MARKER}\n${ALIAS_LINE}\n`;\n\n appendFileSync(rcFile, aliasBlock, 'utf8');\n}\n\nexport function getAliasInstructions(shell: Shell): string {\n const rcFile = getShellRcFile(shell);\n\n if (!rcFile) {\n return `Add this to your shell config:\\n ${ALIAS_LINE}`;\n }\n\n return `Alias added to ${rcFile}. Run:\\n source ${rcFile}`;\n}\n","/**\n * Cross-Tracker Linking\n *\n * Manages links between issues in different trackers.\n * Links are stored in a local JSON file for persistence.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { TrackerType } from './interface.js';\n\n// Link direction types\nexport type LinkDirection = 'blocks' | 'blocked_by' | 'related' | 'duplicate_of';\n\n// A single link between two issues\nexport interface TrackerLink {\n sourceIssueRef: string; // e.g., \"MIN-630\"\n sourceTracker: TrackerType;\n targetIssueRef: string; // e.g., \"#42\"\n targetTracker: TrackerType;\n direction: LinkDirection;\n createdAt: string; // ISO timestamp\n}\n\n// Storage format\ninterface LinkStore {\n version: 1;\n links: TrackerLink[];\n}\n\n/**\n * Parse an issue reference to extract tracker and ID\n * Examples:\n * \"#42\" -> { tracker: \"github\", ref: \"#42\" }\n * \"github#42\" -> { tracker: \"github\", ref: \"#42\" }\n * \"MIN-630\" -> { tracker: \"linear\", ref: \"MIN-630\" }\n * \"gitlab#15\" -> { tracker: \"gitlab\", ref: \"#15\" }\n */\nexport function parseIssueRef(ref: string): { tracker: TrackerType; ref: string } | null {\n // Explicit tracker prefix\n if (ref.startsWith('github#')) {\n return { tracker: 'github', ref: `#${ref.slice(7)}` };\n }\n if (ref.startsWith('gitlab#')) {\n return { tracker: 'gitlab', ref: `#${ref.slice(7)}` };\n }\n if (ref.startsWith('linear:')) {\n return { tracker: 'linear', ref: ref.slice(7) };\n }\n\n // GitHub-style refs (#number)\n if (/^#\\d+$/.test(ref)) {\n return { tracker: 'github', ref };\n }\n\n // Linear-style refs (XXX-123)\n if (/^[A-Z]+-\\d+$/i.test(ref)) {\n return { tracker: 'linear', ref: ref.toUpperCase() };\n }\n\n return null;\n}\n\n/**\n * Format an issue ref with tracker prefix for display\n */\nexport function formatIssueRef(ref: string, tracker: TrackerType): string {\n if (tracker === 'github') {\n return ref.startsWith('#') ? `github${ref}` : `github#${ref}`;\n }\n if (tracker === 'gitlab') {\n return ref.startsWith('#') ? `gitlab${ref}` : `gitlab#${ref}`;\n }\n return ref; // Linear refs are already unique\n}\n\n/**\n * Link Manager for cross-tracker issue linking\n */\nexport class LinkManager {\n private storePath: string;\n private store: LinkStore;\n\n constructor(storePath?: string) {\n this.storePath = storePath ?? join(homedir(), '.panopticon', 'links.json');\n this.store = this.load();\n }\n\n private load(): LinkStore {\n if (existsSync(this.storePath)) {\n try {\n const data = JSON.parse(readFileSync(this.storePath, 'utf-8'));\n if (data.version === 1) {\n return data;\n }\n } catch {\n // Fall through to default\n }\n }\n return { version: 1, links: [] };\n }\n\n private save(): void {\n const dir = join(this.storePath, '..');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(this.storePath, JSON.stringify(this.store, null, 2));\n }\n\n /**\n * Add a link between two issues\n */\n addLink(\n source: { ref: string; tracker: TrackerType },\n target: { ref: string; tracker: TrackerType },\n direction: LinkDirection = 'related'\n ): TrackerLink {\n // Check if link already exists\n const existing = this.store.links.find(\n (l) =>\n l.sourceIssueRef === source.ref &&\n l.sourceTracker === source.tracker &&\n l.targetIssueRef === target.ref &&\n l.targetTracker === target.tracker\n );\n\n if (existing) {\n // Update direction if different\n if (existing.direction !== direction) {\n existing.direction = direction;\n this.save();\n }\n return existing;\n }\n\n const link: TrackerLink = {\n sourceIssueRef: source.ref,\n sourceTracker: source.tracker,\n targetIssueRef: target.ref,\n targetTracker: target.tracker,\n direction,\n createdAt: new Date().toISOString(),\n };\n\n this.store.links.push(link);\n this.save();\n return link;\n }\n\n /**\n * Remove a link between two issues\n */\n removeLink(\n source: { ref: string; tracker: TrackerType },\n target: { ref: string; tracker: TrackerType }\n ): boolean {\n const index = this.store.links.findIndex(\n (l) =>\n l.sourceIssueRef === source.ref &&\n l.sourceTracker === source.tracker &&\n l.targetIssueRef === target.ref &&\n l.targetTracker === target.tracker\n );\n\n if (index >= 0) {\n this.store.links.splice(index, 1);\n this.save();\n return true;\n }\n return false;\n }\n\n /**\n * Get all issues linked to a given issue\n */\n getLinkedIssues(ref: string, tracker: TrackerType): TrackerLink[] {\n return this.store.links.filter(\n (l) =>\n (l.sourceIssueRef === ref && l.sourceTracker === tracker) ||\n (l.targetIssueRef === ref && l.targetTracker === tracker)\n );\n }\n\n /**\n * Get all links (for debugging/admin)\n */\n getAllLinks(): TrackerLink[] {\n return [...this.store.links];\n }\n\n /**\n * Find linked issue in another tracker\n */\n findLinkedIssue(\n ref: string,\n sourceTracker: TrackerType,\n targetTracker: TrackerType\n ): string | null {\n // Check as source\n const asSource = this.store.links.find(\n (l) =>\n l.sourceIssueRef === ref &&\n l.sourceTracker === sourceTracker &&\n l.targetTracker === targetTracker\n );\n if (asSource) return asSource.targetIssueRef;\n\n // Check as target\n const asTarget = this.store.links.find(\n (l) =>\n l.targetIssueRef === ref &&\n l.targetTracker === sourceTracker &&\n l.sourceTracker === targetTracker\n );\n if (asTarget) return asTarget.sourceIssueRef;\n\n return null;\n }\n\n /**\n * Clear all links (for testing)\n */\n clear(): void {\n this.store.links = [];\n this.save();\n }\n}\n\n// Singleton instance\nlet _linkManager: LinkManager | null = null;\n\nexport function getLinkManager(): LinkManager {\n if (!_linkManager) {\n _linkManager = new LinkManager();\n }\n return _linkManager;\n}\n","/**\n * Issue Tracker Module\n *\n * Provides a unified interface for different issue tracking systems.\n */\n\n// Core interface and types\nexport type {\n IssueTracker,\n Issue,\n IssueFilters,\n IssueState,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\n\nexport {\n NotImplementedError,\n IssueNotFoundError,\n TrackerAuthError,\n} from './interface.js';\n\n// Tracker implementations\nexport { LinearTracker } from './linear.js';\nexport { GitHubTracker } from './github.js';\nexport { GitLabTracker } from './gitlab.js';\n\n// Factory functions\nexport type { TrackerConfig } from './factory.js';\nexport {\n createTracker,\n createTrackerFromConfig,\n getPrimaryTracker,\n getSecondaryTracker,\n getAllTrackers,\n} from './factory.js';\n\n// Cross-tracker linking\nexport type { TrackerLink, LinkDirection } from './linking.js';\nexport {\n LinkManager,\n getLinkManager,\n parseIssueRef,\n formatIssueRef,\n} from './linking.js';\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __esmMin } from "./chunk-ruWRV7i3.js";
|
|
2
2
|
import { G as init_paths, t as AGENTS_DIR } from "./paths-CDJ_HsbN.js";
|
|
3
|
-
import { a as getModelCapability, i as MODEL_CAPABILITIES, n as init_config_yaml, o as init_model_capabilities, r as loadConfig } from "./config-yaml-
|
|
3
|
+
import { a as getModelCapability, i as MODEL_CAPABILITIES, n as init_config_yaml, o as init_model_capabilities, r as loadConfig } from "./config-yaml-BHD2Qdd8.js";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
//#region src/lib/hooks.ts
|
|
@@ -386,6 +386,7 @@ var init_model_fallback = __esmMin((() => {
|
|
|
386
386
|
"gemini-2.5-flash": "google",
|
|
387
387
|
"glm-4.7": "zai",
|
|
388
388
|
"glm-4.7-flash": "zai",
|
|
389
|
+
"glm-5.1": "zai",
|
|
389
390
|
"kimi-k2": "kimi",
|
|
390
391
|
"kimi-k2.5": "kimi",
|
|
391
392
|
"minimax-m2.7": "minimax",
|
|
@@ -400,6 +401,7 @@ var init_model_fallback = __esmMin((() => {
|
|
|
400
401
|
"gemini-3-flash-preview": "claude-haiku-4-5",
|
|
401
402
|
"glm-4.7": "claude-haiku-4-5",
|
|
402
403
|
"glm-4.7-flash": "claude-haiku-4-5",
|
|
404
|
+
"glm-5.1": "claude-sonnet-4-6",
|
|
403
405
|
"kimi-k2": "claude-sonnet-4-6",
|
|
404
406
|
"kimi-k2.5": "claude-sonnet-4-6",
|
|
405
407
|
"minimax-m2.7": "claude-sonnet-4-6",
|
|
@@ -926,4 +928,4 @@ var init_work_type_router = __esmMin((() => {
|
|
|
926
928
|
//#endregion
|
|
927
929
|
export { generateFixedPointPrompt as a, popFromHook as c, clearHook as i, pushToHook as l, init_work_type_router as n, initHook as o, checkHook as r, init_hooks as s, getModelId as t, sendMail as u };
|
|
928
930
|
|
|
929
|
-
//# sourceMappingURL=work-type-router-
|
|
931
|
+
//# sourceMappingURL=work-type-router-oCgTPXsP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-type-router-oCgTPXsP.js","names":[],"sources":["../src/lib/hooks.ts","../src/lib/work-types.ts","../src/lib/model-fallback.ts","../src/lib/smart-model-selector.ts","../src/lib/work-type-router.ts"],"sourcesContent":["/**\n * FPP Hooks System - Fixed Point Principle\n *\n * \"Any runnable action is a fixed point and must resolve before the system can rest.\"\n *\n * Inspired by Doctor Who: a fixed point in time must occur — it cannot be avoided.\n *\n * Hooks are persistent work queues for agents. When an agent starts,\n * it checks its hook for pending work and executes immediately.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\n\nexport interface HookItem {\n id: string;\n type: 'task' | 'message' | 'notification';\n priority: 'urgent' | 'high' | 'normal' | 'low';\n source: string;\n payload: {\n issueId?: string;\n message?: string;\n action?: string;\n context?: Record<string, any>;\n };\n createdAt: string;\n expiresAt?: string;\n}\n\nexport interface Hook {\n agentId: string;\n items: HookItem[];\n lastChecked?: string;\n}\n\nfunction getHookDir(agentId: string): string {\n return join(AGENTS_DIR, agentId);\n}\n\nfunction getHookFile(agentId: string): string {\n return join(getHookDir(agentId), 'hook.json');\n}\n\nfunction getMailDir(agentId: string): string {\n return join(getHookDir(agentId), 'mail');\n}\n\n/**\n * Initialize hook structure for an agent\n */\nexport function initHook(agentId: string): void {\n const hookDir = getHookDir(agentId);\n const mailDir = getMailDir(agentId);\n\n mkdirSync(hookDir, { recursive: true });\n mkdirSync(mailDir, { recursive: true });\n\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n const hook: Hook = {\n agentId,\n items: [],\n };\n writeFileSync(hookFile, JSON.stringify(hook, null, 2));\n }\n}\n\n/**\n * Get the hook for an agent\n */\nexport function getHook(agentId: string): Hook | null {\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(hookFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * Add work to an agent's hook (FPP trigger)\n */\nexport function pushToHook(agentId: string, item: Omit<HookItem, 'id' | 'createdAt'>): HookItem {\n initHook(agentId);\n\n const hook = getHook(agentId) || { agentId, items: [] };\n\n const newItem: HookItem = {\n ...item,\n id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n createdAt: new Date().toISOString(),\n };\n\n hook.items.push(newItem);\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return newItem;\n}\n\n/**\n * Check if agent has pending work (FPP check)\n */\nexport function checkHook(agentId: string): { hasWork: boolean; urgentCount: number; items: HookItem[] } {\n const hook = getHook(agentId);\n\n if (!hook || hook.items.length === 0) {\n // Also check mail directory for incoming messages\n const mailDir = getMailDir(agentId);\n if (existsSync(mailDir)) {\n const mails = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n if (mails.length > 0) {\n // Convert mail to hook items\n const mailItems: HookItem[] = mails.map((file) => {\n try {\n const content = readFileSync(join(mailDir, file), 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n }).filter(Boolean) as HookItem[];\n\n return {\n hasWork: mailItems.length > 0,\n urgentCount: mailItems.filter((i) => i.priority === 'urgent').length,\n items: mailItems,\n };\n }\n }\n\n return { hasWork: false, urgentCount: 0, items: [] };\n }\n\n // Filter out expired items\n const now = new Date();\n const activeItems = hook.items.filter((item) => {\n if (item.expiresAt) {\n return new Date(item.expiresAt) > now;\n }\n return true;\n });\n\n // Sort by priority: urgent > high > normal > low\n const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };\n activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);\n\n return {\n hasWork: activeItems.length > 0,\n urgentCount: activeItems.filter((i) => i.priority === 'urgent').length,\n items: activeItems,\n };\n}\n\n/**\n * Pop the next work item from hook (after execution)\n */\nexport function popFromHook(agentId: string, itemId: string): boolean {\n const hook = getHook(agentId);\n if (!hook) return false;\n\n const index = hook.items.findIndex((i) => i.id === itemId);\n if (index === -1) return false;\n\n hook.items.splice(index, 1);\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Clear all items from hook\n */\nexport function clearHook(agentId: string): void {\n const hook = getHook(agentId);\n if (!hook) return;\n\n hook.items = [];\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n}\n\n/**\n * Reorder hook items by providing a new order of item IDs\n * Used for manual queue management from dashboard\n */\nexport function reorderHookItems(agentId: string, orderedItemIds: string[]): boolean {\n const hook = getHook(agentId);\n if (!hook) return false;\n\n // Validate that all provided IDs exist in the hook\n const existingIds = new Set(hook.items.map((item) => item.id));\n const providedIds = new Set(orderedItemIds);\n\n // Check if all provided IDs exist\n for (const id of orderedItemIds) {\n if (!existingIds.has(id)) {\n console.error(`[hooks] Cannot reorder: item ${id} not found in hook`);\n return false;\n }\n }\n\n // Check if all existing IDs are provided\n if (existingIds.size !== providedIds.size) {\n console.error(`[hooks] Cannot reorder: mismatch in item count (existing: ${existingIds.size}, provided: ${providedIds.size})`);\n return false;\n }\n\n // Build a map for quick lookup\n const itemMap = new Map(hook.items.map((item) => [item.id, item]));\n\n // Reorder items based on provided IDs\n hook.items = orderedItemIds.map((id) => itemMap.get(id)!);\n\n // Write back to file\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Send a message to an agent's mailbox\n */\nexport function sendMail(\n toAgentId: string,\n from: string,\n message: string,\n priority: HookItem['priority'] = 'normal'\n): void {\n initHook(toAgentId);\n const mailDir = getMailDir(toAgentId);\n\n const mailItem: HookItem = {\n id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n type: 'message',\n priority,\n source: from,\n payload: { message },\n createdAt: new Date().toISOString(),\n };\n\n writeFileSync(\n join(mailDir, `${mailItem.id}.json`),\n JSON.stringify(mailItem, null, 2)\n );\n}\n\n/**\n * Get and clear mail for an agent\n */\nexport function collectMail(agentId: string): HookItem[] {\n const mailDir = getMailDir(agentId);\n if (!existsSync(mailDir)) return [];\n\n const mails: HookItem[] = [];\n const files = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n\n for (const file of files) {\n const filePath = join(mailDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n mails.push(JSON.parse(content));\n unlinkSync(filePath); // Remove after reading\n } catch {\n // Skip invalid mail\n }\n }\n\n return mails;\n}\n\n/**\n * Generate Fixed Point prompt for agent startup\n */\nexport function generateFixedPointPrompt(agentId: string): string | null {\n const { hasWork, urgentCount, items } = checkHook(agentId);\n\n if (!hasWork) return null;\n\n const lines: string[] = [\n '# FPP: Work Found on Your Hook',\n '',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n if (urgentCount > 0) {\n lines.push(`⚠️ **${urgentCount} URGENT item(s) require immediate attention**`);\n lines.push('');\n }\n\n lines.push(`## Pending Work Items (${items.length})`);\n lines.push('');\n\n for (const item of items) {\n const priorityEmoji = {\n urgent: '🔴',\n high: '🟠',\n normal: '🟢',\n low: '⚪',\n }[item.priority];\n\n lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);\n lines.push(`- Source: ${item.source}`);\n lines.push(`- Created: ${item.createdAt}`);\n\n if (item.payload.issueId) {\n lines.push(`- Issue: ${item.payload.issueId}`);\n }\n if (item.payload.message) {\n lines.push(`- Message: ${item.payload.message}`);\n }\n if (item.payload.action) {\n lines.push(`- Action: ${item.payload.action}`);\n }\n lines.push('');\n }\n\n lines.push('---');\n lines.push('');\n lines.push('Execute these items in priority order. Use `bd hook pop <id>` after completing each item.');\n\n return lines.join('\\n');\n}\n","/**\n * Work Type Registry\n *\n * Central registry of all work type IDs used for model routing.\n * Each work type represents a specific context where AI agents operate,\n * allowing fine-grained control over which models handle which tasks.\n */\n\n/**\n * Metadata for each work type\n */\nexport interface WorkTypeMetadata {\n /** Broad category this work type belongs to */\n category: 'issue-agent' | 'specialist' | 'subagent' | 'convoy' | 'pre-work' | 'cli';\n /** Optional phase within the category (e.g., for issue-agent phases) */\n phase?: string;\n /** Human-readable description */\n description: string;\n}\n\n/**\n * Complete registry of all 23 work types with metadata\n */\nexport const WORK_TYPES = {\n // Issue agent phases (6)\n 'issue-agent:exploration': {\n phase: 'exploration',\n category: 'issue-agent',\n description: 'Exploring codebase and understanding requirements',\n },\n 'issue-agent:implementation': {\n phase: 'implementation',\n category: 'issue-agent',\n description: 'Writing code to implement features or fixes',\n },\n 'issue-agent:testing': {\n phase: 'testing',\n category: 'issue-agent',\n description: 'Running tests and verifying functionality',\n },\n 'issue-agent:documentation': {\n phase: 'documentation',\n category: 'issue-agent',\n description: 'Writing documentation and updating docs',\n },\n 'issue-agent:review-response': {\n phase: 'review-response',\n category: 'issue-agent',\n description: 'Responding to code review feedback',\n },\n\n // Specialist agents (3)\n 'specialist-review-agent': {\n category: 'specialist',\n description: 'Comprehensive code review specialist',\n },\n 'specialist-test-agent': {\n category: 'specialist',\n description: 'Test generation and verification specialist',\n },\n 'specialist-merge-agent': {\n category: 'specialist',\n description: 'Merge request finalization specialist',\n },\n\n // Subagents (4)\n 'subagent:explore': {\n category: 'subagent',\n description: 'Fast codebase exploration subagent',\n },\n 'subagent:plan': {\n category: 'subagent',\n description: 'Implementation planning subagent',\n },\n 'subagent:bash': {\n category: 'subagent',\n description: 'Command execution specialist subagent',\n },\n 'subagent:general-purpose': {\n category: 'subagent',\n description: 'General-purpose task subagent',\n },\n\n // Convoy members (5)\n 'convoy:security-reviewer': {\n category: 'convoy',\n description: 'Security-focused code reviewer in convoy',\n },\n 'convoy:performance-reviewer': {\n category: 'convoy',\n description: 'Performance-focused code reviewer in convoy',\n },\n 'convoy:correctness-reviewer': {\n category: 'convoy',\n description: 'Correctness-focused code reviewer in convoy',\n },\n 'convoy:requirements-reviewer': {\n category: 'convoy',\n description: 'Verifies code changes satisfy the original issue requirements and vBRIEF acceptance criteria',\n },\n 'convoy:synthesis-agent': {\n category: 'convoy',\n description: 'Synthesizes findings from convoy reviewers',\n },\n\n // Pre-work agents (5)\n 'planning-agent': {\n category: 'pre-work',\n description: 'Interactive planning and discovery agent',\n },\n // CLI contexts (2)\n 'cli:interactive': {\n category: 'cli',\n description: 'Interactive CLI session',\n },\n 'cli:quick-command': {\n category: 'cli',\n description: 'Quick one-off CLI commands',\n },\n} as const;\n\n/**\n * Type-safe work type IDs\n */\nexport type WorkTypeId = keyof typeof WORK_TYPES;\n\n/**\n * Valid work type categories\n */\nexport type WorkTypeCategory = WorkTypeMetadata['category'];\n\n/**\n * Get all work type IDs\n */\nexport function getAllWorkTypes(): WorkTypeId[] {\n return Object.keys(WORK_TYPES) as WorkTypeId[];\n}\n\n/**\n * Get all work types in a specific category\n */\nexport function getWorkTypesByCategory(category: WorkTypeCategory): WorkTypeId[] {\n return getAllWorkTypes().filter((id) => WORK_TYPES[id].category === category);\n}\n\n/**\n * Check if a string is a valid work type ID\n */\nexport function isValidWorkType(id: string): id is WorkTypeId {\n return id in WORK_TYPES;\n}\n\n/**\n * Get metadata for a work type\n */\nexport function getWorkTypeMetadata(id: WorkTypeId): WorkTypeMetadata {\n return WORK_TYPES[id];\n}\n\n/**\n * Get human-readable name for a work type\n */\nexport function getWorkTypeName(id: WorkTypeId): string {\n const metadata = WORK_TYPES[id];\n if ('phase' in metadata && metadata.phase) {\n return `${metadata.category} (${metadata.phase})`;\n }\n return id;\n}\n\n/**\n * Validate work type ID and throw if invalid\n */\nexport function validateWorkType(id: string): asserts id is WorkTypeId {\n if (!isValidWorkType(id)) {\n throw new Error(\n `Invalid work type ID: ${id}. Valid types: ${getAllWorkTypes().join(', ')}`\n );\n }\n}\n","/**\n * Model Fallback Strategy\n *\n * When a non-Anthropic model is selected but its API key is missing,\n * automatically fallback to an equivalent Anthropic model. This ensures\n * Panopticon always works even without configuring external providers.\n */\n\nimport { ModelId, AnthropicModel, OpenAIModel, GoogleModel, ZAIModel } from './settings.js';\n\n/**\n * AI model provider types\n */\nexport type ModelProvider = 'anthropic' | 'openai' | 'google' | 'zai' | 'kimi' | 'minimax' | 'openrouter';\n\n/**\n * Map of model ID to provider\n */\nconst MODEL_PROVIDERS: Record<ModelId, ModelProvider> = {\n // Anthropic models\n 'claude-opus-4-6': 'anthropic',\n 'claude-sonnet-4-6': 'anthropic',\n 'claude-sonnet-4-5': 'anthropic',\n 'claude-haiku-4-5': 'anthropic',\n\n // OpenAI models\n 'gpt-5.2-codex': 'openai',\n 'o3-deep-research': 'openai',\n 'gpt-4o': 'openai',\n 'gpt-4o-mini': 'openai',\n\n // Google models\n 'gemini-3-pro-preview': 'google',\n 'gemini-3-flash-preview': 'google',\n 'gemini-2.5-pro': 'google',\n 'gemini-2.5-flash': 'google',\n\n // Z.AI models\n 'glm-4.7': 'zai',\n 'glm-4.7-flash': 'zai',\n 'glm-5.1': 'zai',\n\n // Kimi models\n 'kimi-k2': 'kimi',\n 'kimi-k2.5': 'kimi',\n\n // MiniMax models\n 'minimax-m2.7': 'minimax',\n 'minimax-m2.7-highspeed': 'minimax',\n};\n\n/**\n * Fallback mapping: non-Anthropic model → Anthropic equivalent\n *\n * Mapping strategy:\n * - Premium models (GPT-5.2, O3, Gemini Pro) → Sonnet 4.6 (good balance)\n * - Economy models (GPT-4o-mini, Gemini Flash, GLM Flash) → Haiku 4.5\n * - GPT-4o → Sonnet 4.6 (similar tier)\n * - GLM-4.7 → Haiku 4.5 (economy tier)\n *\n * Note: We intentionally avoid Opus 4.6 as default fallback to keep costs reasonable.\n * Users who want Opus can explicitly set it in their config.\n */\nconst FALLBACK_MAP: Record<string, AnthropicModel> = {\n // OpenAI → Anthropic\n 'gpt-5.2-codex': 'claude-sonnet-4-6', // Premium code model → Sonnet\n 'o3-deep-research': 'claude-sonnet-4-6', // Premium research model → Sonnet\n 'gpt-4o': 'claude-sonnet-4-6', // Flagship model → Sonnet\n 'gpt-4o-mini': 'claude-haiku-4-5', // Economy model → Haiku\n\n // Google → Anthropic\n 'gemini-3-pro-preview': 'claude-sonnet-4-6', // Premium model → Sonnet\n 'gemini-3-flash-preview': 'claude-haiku-4-5', // Fast model → Haiku\n\n // Z.AI → Anthropic\n 'glm-4.7': 'claude-haiku-4-5', // Standard model → Haiku\n 'glm-4.7-flash': 'claude-haiku-4-5', // Fast model → Haiku\n 'glm-5.1': 'claude-sonnet-4-6', // Flagship model → Sonnet\n\n // Kimi → Anthropic\n 'kimi-k2': 'claude-sonnet-4-6', // Good balance model → Sonnet\n 'kimi-k2.5': 'claude-sonnet-4-6', // Premium model → Sonnet\n\n // MiniMax → Anthropic\n 'minimax-m2.7': 'claude-sonnet-4-6', // Near-Opus performance → Sonnet\n 'minimax-m2.7-highspeed': 'claude-sonnet-4-6', // Same quality, faster → Sonnet\n};\n\n/**\n * Default fallback when model not in explicit mapping\n */\nconst DEFAULT_FALLBACK: AnthropicModel = 'claude-sonnet-4-6';\n\n/**\n * Check if a model ID is an OpenRouter model\n *\n * OpenRouter model IDs use the format \"organization/model-name\" (e.g., \"qwen/qwen3.6-plus:free\").\n * This is distinct from all other providers which use simple identifiers without slashes.\n */\nexport function isOpenRouterModel(modelId: string): boolean {\n return modelId.includes('/');\n}\n\n/**\n * Get the provider for a model ID\n */\nexport function getModelProvider(modelId: ModelId | string): ModelProvider {\n if (isOpenRouterModel(modelId)) return 'openrouter';\n return (MODEL_PROVIDERS as Record<string, ModelProvider>)[modelId] ?? 'anthropic';\n}\n\n/**\n * Check if a model requires an external API key\n */\nexport function requiresExternalKey(modelId: ModelId | string): boolean {\n return getModelProvider(modelId) !== 'anthropic';\n}\n\n/**\n * Get all models for a specific provider\n */\nexport function getModelsByProvider(provider: ModelProvider): ModelId[] {\n return Object.entries(MODEL_PROVIDERS)\n .filter(([_, p]) => p === provider)\n .map(([modelId]) => modelId as ModelId);\n}\n\n/**\n * Check if a provider is enabled (has API key configured)\n *\n * @param provider Provider to check\n * @param enabledProviders Set of enabled provider names\n * @returns true if provider is enabled or is Anthropic (always enabled)\n */\nexport function isProviderEnabled(\n provider: ModelProvider,\n enabledProviders: Set<ModelProvider>\n): boolean {\n // Anthropic is always enabled (required)\n if (provider === 'anthropic') return true;\n\n return enabledProviders.has(provider);\n}\n\n/**\n * Apply fallback strategy for a model\n *\n * If the model's provider is disabled (no API key), return an Anthropic equivalent.\n * Otherwise, return the original model.\n *\n * @param modelId Requested model\n * @param enabledProviders Set of enabled provider names\n * @returns Original model if provider enabled, otherwise Anthropic fallback\n */\nexport function applyFallback(\n modelId: ModelId,\n enabledProviders: Set<ModelProvider>\n): ModelId {\n const provider = getModelProvider(modelId);\n\n // If provider is enabled, use the requested model\n if (isProviderEnabled(provider, enabledProviders)) {\n return modelId;\n }\n\n // Provider disabled — fall back to the equivalent Anthropic model\n const fallback = getFallbackModel(modelId);\n console.warn(`Model ${modelId} requires ${provider} API key which is not configured, falling back to ${fallback}`);\n return fallback;\n}\n\n/**\n * Get the fallback model for a given model (useful for preview/display)\n *\n * @param modelId Model to get fallback for\n * @returns Anthropic fallback model\n */\nexport function getFallbackModel(modelId: ModelId): AnthropicModel {\n // Anthropic models fallback to themselves\n if (getModelProvider(modelId) === 'anthropic') {\n return modelId as AnthropicModel;\n }\n\n return FALLBACK_MAP[modelId] || DEFAULT_FALLBACK;\n}\n\n/**\n * Detect enabled providers from API keys configuration\n *\n * @param apiKeys API keys object from settings\n * @returns Set of enabled provider names\n */\nexport function detectEnabledProviders(apiKeys: {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n}): Set<ModelProvider> {\n const enabled = new Set<ModelProvider>(['anthropic']); // Always enabled\n\n // Check each optional provider\n if (apiKeys.openai && apiKeys.openai.trim()) {\n enabled.add('openai');\n }\n if (apiKeys.google && apiKeys.google.trim()) {\n enabled.add('google');\n }\n if (apiKeys.zai && apiKeys.zai.trim()) {\n enabled.add('zai');\n }\n if (apiKeys.kimi && apiKeys.kimi.trim()) {\n enabled.add('kimi');\n }\n\n return enabled;\n}\n\n/**\n * Filter a list of models to only those available with enabled providers\n *\n * @param models List of models to filter\n * @param enabledProviders Set of enabled provider names\n * @returns Filtered list of models\n */\nexport function filterAvailableModels(\n models: ModelId[],\n enabledProviders: Set<ModelProvider>\n): ModelId[] {\n return models.filter((modelId) => {\n const provider = getModelProvider(modelId);\n return isProviderEnabled(provider, enabledProviders);\n });\n}\n\n/**\n * Get all available models (across all enabled providers)\n *\n * @param enabledProviders Set of enabled provider names\n * @returns List of available model IDs\n */\nexport function getAvailableModels(enabledProviders: Set<ModelProvider>): ModelId[] {\n return Object.keys(MODEL_PROVIDERS).filter((modelId) => {\n const provider = MODEL_PROVIDERS[modelId as ModelId];\n return isProviderEnabled(provider, enabledProviders);\n }) as ModelId[];\n}\n","/**\n * Smart Model Selector\n *\n * Intelligently selects the best model for each work type based on:\n * 1. What models the user has enabled (API keys configured)\n * 2. Capability scores for the required skills\n *\n * This is an opinionated system - always pick the BEST model for each job.\n * Users control cost by which providers they enable, not a sensitivity slider.\n */\n\nimport { ModelId } from './settings.js';\nimport { WorkTypeId } from './work-types.js';\nimport {\n MODEL_CAPABILITIES,\n SkillDimension,\n ModelCapability,\n getModelCapability,\n} from './model-capabilities.js';\n\n/**\n * Skill requirements for a work type\n * Higher weight = more important for this task\n */\nexport interface SkillRequirement {\n skill: SkillDimension;\n weight: number; // 0-1, how important this skill is\n}\n\n/**\n * Work type to skill mapping\n * Defines what skills each work type needs\n */\nexport const WORK_TYPE_REQUIREMENTS: Record<WorkTypeId, SkillRequirement[]> = {\n // ═══════════════════════════════════════════════════════════════════════════\n // ISSUE AGENT PHASES\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'issue-agent:exploration': [\n { skill: 'speed', weight: 0.4 }, // Need fast exploration\n { skill: 'context-length', weight: 0.3 }, // Large codebases\n { skill: 'synthesis', weight: 0.3 }, // Understanding structure\n ],\n\n 'issue-agent:implementation': [\n { skill: 'code-generation', weight: 0.6 }, // Primary skill\n { skill: 'debugging', weight: 0.2 }, // Avoiding bugs\n { skill: 'testing', weight: 0.2 }, // Writing testable code\n ],\n\n 'issue-agent:testing': [\n { skill: 'testing', weight: 0.5 }, // Primary skill\n { skill: 'code-generation', weight: 0.3 }, // Writing test code\n { skill: 'debugging', weight: 0.2 }, // Finding edge cases\n ],\n\n 'issue-agent:documentation': [\n { skill: 'documentation', weight: 0.6 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Summarizing\n { skill: 'speed', weight: 0.1 }, // Fast iteration\n ],\n\n 'issue-agent:review-response': [\n { skill: 'code-review', weight: 0.4 }, // Understanding feedback\n { skill: 'code-generation', weight: 0.3 }, // Making fixes\n { skill: 'debugging', weight: 0.3 }, // Finding issues\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SPECIALIST AGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'specialist-review-agent': [\n { skill: 'code-review', weight: 0.5 }, // Primary skill\n { skill: 'security', weight: 0.25 }, // Security awareness\n { skill: 'performance', weight: 0.25 }, // Performance awareness\n ],\n\n 'specialist-test-agent': [\n { skill: 'testing', weight: 0.5 }, // Primary skill\n { skill: 'code-generation', weight: 0.3 }, // Writing tests\n { skill: 'debugging', weight: 0.2 }, // Finding issues\n ],\n\n 'specialist-merge-agent': [\n { skill: 'code-review', weight: 0.4 }, // Understanding conflicts\n { skill: 'synthesis', weight: 0.3 }, // Merging changes\n { skill: 'debugging', weight: 0.3 }, // Resolving issues\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // SUBAGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'subagent:explore': [\n { skill: 'speed', weight: 0.5 }, // Need speed\n { skill: 'context-length', weight: 0.3 }, // Large scope\n { skill: 'synthesis', weight: 0.2 }, // Quick understanding\n ],\n\n 'subagent:plan': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Combining info\n { skill: 'speed', weight: 0.2 }, // Quick iteration\n ],\n\n 'subagent:bash': [\n { skill: 'speed', weight: 0.6 }, // Fast execution\n { skill: 'code-generation', weight: 0.3 }, // Command generation\n { skill: 'debugging', weight: 0.1 }, // Error handling\n ],\n\n 'subagent:general-purpose': [\n { skill: 'speed', weight: 0.3 }, // Balanced\n { skill: 'synthesis', weight: 0.3 }, // General understanding\n { skill: 'code-generation', weight: 0.4 }, // General tasks\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // CONVOY MEMBERS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'convoy:security-reviewer': [\n { skill: 'security', weight: 0.7 }, // PRIMARY - never compromise\n { skill: 'code-review', weight: 0.2 }, // Code understanding\n { skill: 'debugging', weight: 0.1 }, // Finding vulnerabilities\n ],\n\n 'convoy:performance-reviewer': [\n { skill: 'performance', weight: 0.6 }, // Primary skill\n { skill: 'code-review', weight: 0.3 }, // Code understanding\n { skill: 'debugging', weight: 0.1 }, // Finding bottlenecks\n ],\n\n 'convoy:correctness-reviewer': [\n { skill: 'code-review', weight: 0.4 }, // Primary skill\n { skill: 'debugging', weight: 0.4 }, // Finding bugs\n { skill: 'testing', weight: 0.2 }, // Test coverage\n ],\n\n 'convoy:requirements-reviewer': [\n { skill: 'planning', weight: 0.4 }, // Mapping requirements to code\n { skill: 'code-review', weight: 0.4 }, // Understanding what code does\n { skill: 'documentation', weight: 0.2 }, // Reading vBRIEF structure\n ],\n\n 'convoy:synthesis-agent': [\n { skill: 'synthesis', weight: 0.6 }, // Primary skill\n { skill: 'documentation', weight: 0.2 }, // Clear writing\n { skill: 'planning', weight: 0.2 }, // Organizing findings\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // PRE-WORK AGENTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'planning-agent': [\n { skill: 'planning', weight: 0.5 }, // Primary skill\n { skill: 'synthesis', weight: 0.3 }, // Combining requirements\n { skill: 'documentation', weight: 0.2 }, // Documenting decisions\n ],\n\n // ═══════════════════════════════════════════════════════════════════════════\n // CLI CONTEXTS\n // ═══════════════════════════════════════════════════════════════════════════\n\n 'cli:interactive': [\n { skill: 'speed', weight: 0.4 }, // Responsive\n { skill: 'synthesis', weight: 0.3 }, // Understanding context\n { skill: 'code-generation', weight: 0.3 }, // Quick code\n ],\n\n 'cli:quick-command': [\n { skill: 'speed', weight: 0.7 }, // Must be fast\n { skill: 'code-generation', weight: 0.2 }, // Simple generation\n { skill: 'synthesis', weight: 0.1 }, // Quick understanding\n ],\n};\n\n/**\n * Selection result with explanation\n */\nexport interface ModelSelectionResult {\n /** Selected model */\n model: ModelId;\n /** Score that led to selection (0-100) */\n score: number;\n /** Why this model was selected */\n reason: string;\n /** All candidates that were considered */\n candidates: Array<{\n model: ModelId;\n score: number;\n available: boolean;\n }>;\n}\n\n/**\n * Selection options\n */\nexport interface SelectionOptions {\n /**\n * Minimum capability threshold (0-100)\n * Models below this score are excluded\n * Default: 50\n */\n minCapability?: number;\n\n /**\n * Force a specific model (bypass selection)\n */\n forceModel?: ModelId;\n}\n\n/**\n * Calculate weighted skill score for a model given requirements\n */\nfunction calculateSkillScore(\n model: ModelId,\n requirements: SkillRequirement[]\n): number {\n const cap = getModelCapability(model);\n let totalScore = 0;\n let totalWeight = 0;\n\n for (const req of requirements) {\n totalScore += cap.skills[req.skill] * req.weight;\n totalWeight += req.weight;\n }\n\n return totalWeight > 0 ? totalScore / totalWeight : 0;\n}\n\n/**\n * Calculate final selection score - pure capability based\n *\n * We're opinionated: always pick the BEST model for the job.\n * Users control cost by which providers they enable.\n */\nfunction calculateSelectionScore(\n model: ModelId,\n skillScore: number\n): number {\n // Pure quality - just return the skill score\n // Cost control is done by which providers the user enables\n return skillScore;\n}\n\n/**\n * Select the best model for a work type from available models\n */\nexport function selectModel(\n workType: WorkTypeId,\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): ModelSelectionResult {\n const { minCapability = 50, forceModel } = options;\n\n // Force model if specified and available\n if (forceModel) {\n if (availableModels.includes(forceModel)) {\n return {\n model: forceModel,\n score: 100,\n reason: `Forced selection: ${forceModel}`,\n candidates: [{ model: forceModel, score: 100, available: true }],\n };\n }\n // Fall through to normal selection if forced model not available\n }\n\n const requirements = WORK_TYPE_REQUIREMENTS[workType];\n const allModels = Object.keys(MODEL_CAPABILITIES) as ModelId[];\n\n // Calculate scores for all models - pure capability based\n // Users control cost by which providers they enable\n const candidates = allModels.map((model) => {\n const skillScore = calculateSkillScore(model, requirements);\n const selectionScore = calculateSelectionScore(model, skillScore);\n const available = availableModels.includes(model);\n\n return {\n model,\n skillScore,\n score: selectionScore,\n available,\n };\n });\n\n // Filter to available models with minimum capability\n const eligible = candidates.filter(\n (c) => c.available && c.skillScore >= minCapability\n );\n\n // Sort by selection score (descending)\n eligible.sort((a, b) => b.score - a.score);\n\n // Fallback: if no eligible models, use best available regardless of threshold\n if (eligible.length === 0) {\n const fallback = candidates\n .filter((c) => c.available)\n .sort((a, b) => b.score - a.score)[0];\n\n if (!fallback) {\n // No available models at all - use Anthropic default\n return {\n model: 'claude-sonnet-4-6',\n score: 0,\n reason: 'No models available, falling back to default',\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n }\n\n return {\n model: fallback.model,\n score: fallback.score,\n reason: `Best available (below min threshold): ${fallback.model}`,\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n }\n\n const selected = eligible[0];\n const cap = getModelCapability(selected.model);\n\n // Generate reason\n const topSkills = requirements\n .sort((a, b) => b.weight - a.weight)\n .slice(0, 2)\n .map((r) => r.skill);\n\n const reason = `Best for ${workType}: ${cap.displayName} (${topSkills.join(', ')}: ${Math.round(selected.skillScore)}, cost: $${cap.costPer1MTokens}/1M)`;\n\n return {\n model: selected.model,\n score: selected.score,\n reason,\n candidates: candidates.map((c) => ({\n model: c.model,\n score: c.score,\n available: c.available,\n })),\n };\n}\n\n/**\n * Select models for all work types at once\n */\nexport function selectAllModels(\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): Record<WorkTypeId, ModelSelectionResult> {\n const workTypes = Object.keys(WORK_TYPE_REQUIREMENTS) as WorkTypeId[];\n const results: Record<WorkTypeId, ModelSelectionResult> = {} as Record<\n WorkTypeId,\n ModelSelectionResult\n >;\n\n for (const workType of workTypes) {\n results[workType] = selectModel(workType, availableModels, options);\n }\n\n return results;\n}\n\n/**\n * Get simple model mapping (for backward compatibility with presets)\n */\nexport function getSimpleModelMapping(\n availableModels: ModelId[],\n options: SelectionOptions = {}\n): Record<WorkTypeId, ModelId> {\n const results = selectAllModels(availableModels, options);\n const mapping: Record<WorkTypeId, ModelId> = {} as Record<WorkTypeId, ModelId>;\n\n for (const [workType, result] of Object.entries(results)) {\n mapping[workType as WorkTypeId] = result.model;\n }\n\n return mapping;\n}\n\n/**\n * Pretty print selection results for debugging\n */\nexport function formatSelectionResults(\n results: Record<WorkTypeId, ModelSelectionResult>\n): string {\n const lines: string[] = ['Model Selection Results', '='.repeat(60)];\n\n for (const [workType, result] of Object.entries(results)) {\n lines.push(`${workType}: ${result.model}`);\n lines.push(` Reason: ${result.reason}`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n","/**\n * Work Type Router\n *\n * Routes work types to appropriate models using smart (capability-based) selection.\n * Picks the best model for each job based on:\n * 1. What models the user has enabled (API keys configured)\n * 2. Capability scores for the required skills\n * 3. Cost optimization (configurable)\n */\n\nimport { WorkTypeId, isValidWorkType, validateWorkType, getAllWorkTypes } from './work-types.js';\nimport { ModelId } from './settings.js';\nimport { applyFallback, ModelProvider, getModelsByProvider } from './model-fallback.js';\nimport { loadConfig, NormalizedConfig } from './config-yaml.js';\nimport { selectModel, ModelSelectionResult } from './smart-model-selector.js';\n\n// Re-export WorkTypeId for backward compatibility\nexport type { WorkTypeId } from './work-types.js';\n\n/**\n * Model resolution result with debugging info\n */\nexport interface ModelResolutionResult {\n /** Final model to use */\n model: ModelId;\n /** Work type that was resolved */\n workType: WorkTypeId;\n /** How the model was determined */\n source: 'override' | 'smart';\n /** Whether fallback was applied (provider disabled) */\n usedFallback: boolean;\n /** Original model before fallback */\n originalModel?: ModelId;\n /** Smart selection details */\n selection: {\n score: number;\n reason: string;\n };\n}\n\n/**\n * Work Type Router\n *\n * Main router class for resolving work types to models.\n */\nexport class WorkTypeRouter {\n private config: NormalizedConfig;\n private availableModels: ModelId[] | null = null;\n\n constructor(config?: NormalizedConfig) {\n this.config = config || loadConfig().config;\n }\n\n /**\n * Get list of available models based on enabled providers\n */\n private getAvailableModels(): ModelId[] {\n if (this.availableModels) {\n return this.availableModels;\n }\n\n const available: ModelId[] = [];\n for (const provider of this.config.enabledProviders) {\n available.push(...getModelsByProvider(provider));\n }\n this.availableModels = available;\n return available;\n }\n\n /**\n * Get model for a specific work type\n *\n * Resolution order:\n * 1. Per-project/global override (if configured)\n * 2. Smart selection (capability-based)\n */\n getModel(workTypeId: WorkTypeId): ModelResolutionResult {\n validateWorkType(workTypeId);\n\n let model: ModelId;\n let source: 'override' | 'smart';\n let originalModel: ModelId | undefined;\n let selection: { score: number; reason: string };\n\n // Check for override first\n if (this.config.overrides[workTypeId]) {\n model = this.config.overrides[workTypeId]!;\n source = 'override';\n selection = {\n score: 100,\n reason: `Explicit override: ${model}`,\n };\n } else {\n // Use smart (capability-based) selection\n const availableModels = this.getAvailableModels();\n const result = selectModel(workTypeId, availableModels);\n model = result.model;\n source = 'smart';\n selection = {\n score: result.score,\n reason: result.reason,\n };\n }\n\n // Apply fallback if provider is disabled\n originalModel = model;\n model = applyFallback(model, this.config.enabledProviders);\n\n return {\n model,\n workType: workTypeId,\n source,\n usedFallback: model !== originalModel,\n originalModel: model !== originalModel ? originalModel : undefined,\n selection,\n };\n }\n\n /**\n * Get just the model ID for a work type (convenience method)\n */\n getModelId(workTypeId: WorkTypeId): ModelId {\n return this.getModel(workTypeId).model;\n }\n\n /**\n * Check if a work type has an override configured\n */\n hasOverride(workTypeId: WorkTypeId): boolean {\n return workTypeId in this.config.overrides;\n }\n\n /**\n * Get the set of enabled providers\n */\n getEnabledProviders(): Set<ModelProvider> {\n return this.config.enabledProviders;\n }\n\n /**\n * Get all configured overrides\n */\n getOverrides(): Partial<Record<WorkTypeId, ModelId>> {\n return { ...this.config.overrides };\n }\n\n /**\n * Get API keys configuration\n */\n getApiKeys(): { openai?: string; google?: string; zai?: string; kimi?: string } {\n return { ...this.config.apiKeys };\n }\n\n /**\n * Get Gemini thinking level\n */\n getGeminiThinkingLevel(): 1 | 2 | 3 | 4 {\n return this.config.geminiThinkingLevel;\n }\n\n /**\n * Reload configuration from disk\n */\n reloadConfig(): void {\n this.config = loadConfig().config;\n this.availableModels = null; // Clear cache\n }\n\n /**\n * Get debug information about current configuration\n */\n getDebugInfo(): {\n enabledProviders: string[];\n availableModelCount: number;\n overrideCount: number;\n hasApiKeys: {\n openai: boolean;\n google: boolean;\n zai: boolean;\n kimi: boolean;\n };\n } {\n return {\n enabledProviders: Array.from(this.config.enabledProviders),\n availableModelCount: this.getAvailableModels().length,\n overrideCount: Object.keys(this.config.overrides).length,\n hasApiKeys: {\n openai: !!this.config.apiKeys.openai,\n google: !!this.config.apiKeys.google,\n zai: !!this.config.apiKeys.zai,\n kimi: !!this.config.apiKeys.kimi,\n },\n };\n }\n}\n\n/**\n * Global router instance\n */\nlet globalRouter: WorkTypeRouter | null = null;\n\n/**\n * Get the global work type router instance\n */\nexport function getGlobalRouter(): WorkTypeRouter {\n if (!globalRouter) {\n globalRouter = new WorkTypeRouter();\n }\n return globalRouter;\n}\n\n/**\n * Reset the global router (useful for testing)\n */\nexport function resetGlobalRouter(): void {\n globalRouter = null;\n}\n\n/**\n * Reload global router configuration\n */\nexport function reloadGlobalRouter(): void {\n if (globalRouter) {\n globalRouter.reloadConfig();\n }\n}\n\n/**\n * Get model using the global router\n */\nexport function getModel(workTypeId: WorkTypeId): ModelResolutionResult {\n return getGlobalRouter().getModel(workTypeId);\n}\n\n/**\n * Get just the model ID using the global router\n */\nexport function getModelId(workTypeId: WorkTypeId): ModelId {\n return getGlobalRouter().getModelId(workTypeId);\n}\n\n/**\n * Check for override using the global router\n */\nexport function hasOverride(workTypeId: WorkTypeId): boolean {\n return getGlobalRouter().hasOverride(workTypeId);\n}\n\n/**\n * Get debug info using the global router\n */\nexport function getDebugInfo() {\n return getGlobalRouter().getDebugInfo();\n}\n"],"mappings":"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"}
|