panopticon-cli 0.4.32 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -210
- package/dist/{agents-BDFHF4T3.js → agents-E43Y3HNU.js} +10 -7
- package/dist/chunk-7SN4L4PH.js +150 -0
- package/dist/chunk-7SN4L4PH.js.map +1 -0
- package/dist/{chunk-2NIAOCIC.js → chunk-AAFQANKW.js} +358 -97
- package/dist/chunk-AAFQANKW.js.map +1 -0
- package/dist/chunk-AQXETQHW.js +113 -0
- package/dist/chunk-AQXETQHW.js.map +1 -0
- package/dist/chunk-B3PF6JPQ.js +212 -0
- package/dist/chunk-B3PF6JPQ.js.map +1 -0
- package/dist/chunk-CFCUOV3Q.js +669 -0
- package/dist/chunk-CFCUOV3Q.js.map +1 -0
- package/dist/chunk-CWELWPWQ.js +32 -0
- package/dist/chunk-CWELWPWQ.js.map +1 -0
- package/dist/chunk-DI7ABPNQ.js +352 -0
- package/dist/chunk-DI7ABPNQ.js.map +1 -0
- package/dist/{chunk-VU4FLXV5.js → chunk-FQ66DECN.js} +31 -4
- package/dist/chunk-FQ66DECN.js.map +1 -0
- package/dist/{chunk-VIWUCJ4V.js → chunk-FTCPTHIJ.js} +57 -432
- package/dist/chunk-FTCPTHIJ.js.map +1 -0
- package/dist/{review-status-GWQYY77L.js → chunk-GFP3PIPB.js} +14 -7
- package/dist/chunk-GFP3PIPB.js.map +1 -0
- package/dist/chunk-GR6ZZMCX.js +816 -0
- package/dist/chunk-GR6ZZMCX.js.map +1 -0
- package/dist/chunk-HJSM6E6U.js +1038 -0
- package/dist/chunk-HJSM6E6U.js.map +1 -0
- package/dist/{chunk-XP2DXWYP.js → chunk-HZT2AOPN.js} +164 -39
- package/dist/chunk-HZT2AOPN.js.map +1 -0
- package/dist/chunk-JQBV3Q2W.js +29 -0
- package/dist/chunk-JQBV3Q2W.js.map +1 -0
- package/dist/{chunk-BWGFN44T.js → chunk-JT4O4YVM.js} +28 -16
- package/dist/chunk-JT4O4YVM.js.map +1 -0
- package/dist/chunk-NTO3EDB3.js +600 -0
- package/dist/chunk-NTO3EDB3.js.map +1 -0
- package/dist/{chunk-JY7R7V4G.js → chunk-OMNXYPXC.js} +2 -2
- package/dist/chunk-OMNXYPXC.js.map +1 -0
- package/dist/chunk-PELXV435.js +215 -0
- package/dist/chunk-PELXV435.js.map +1 -0
- package/dist/chunk-PPRFKTVC.js +154 -0
- package/dist/chunk-PPRFKTVC.js.map +1 -0
- package/dist/chunk-WQG2TYCB.js +677 -0
- package/dist/chunk-WQG2TYCB.js.map +1 -0
- package/dist/{chunk-HCTJFIJJ.js → chunk-YLPSQAM2.js} +2 -2
- package/dist/{chunk-HCTJFIJJ.js.map → chunk-YLPSQAM2.js.map} +1 -1
- package/dist/{chunk-6HXKTOD7.js → chunk-ZTFNYOC7.js} +53 -38
- package/dist/chunk-ZTFNYOC7.js.map +1 -0
- package/dist/cli/index.js +5103 -3165
- package/dist/cli/index.js.map +1 -1
- package/dist/{config-BOAMSKTF.js → config-4CJNUE3O.js} +7 -3
- package/dist/dashboard/prompts/merge-agent.md +217 -0
- package/dist/dashboard/prompts/review-agent.md +409 -0
- package/dist/dashboard/prompts/sync-main.md +84 -0
- package/dist/dashboard/prompts/test-agent.md +283 -0
- package/dist/dashboard/prompts/work-agent.md +249 -0
- package/dist/dashboard/public/assets/index-BxpjweAL.css +32 -0
- package/dist/dashboard/public/assets/index-DQHkwvvJ.js +743 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +17619 -4044
- package/dist/{dns-L3L2BB27.js → dns-7BDJSD3E.js} +4 -2
- package/dist/{feedback-writer-AAKF5BTK.js → feedback-writer-LVZ5TFYZ.js} +8 -4
- package/dist/feedback-writer-LVZ5TFYZ.js.map +1 -0
- package/dist/hume-WMAUBBV2.js +13 -0
- package/dist/index.d.ts +162 -40
- package/dist/index.js +67 -23
- package/dist/index.js.map +1 -1
- package/dist/{projects-VXRUCMLM.js → projects-JEIVIYC6.js} +3 -3
- package/dist/rally-RKFSWC7E.js +10 -0
- package/dist/{remote-agents-Z3R2A5BN.js → remote-agents-TFSMW7GN.js} +2 -2
- package/dist/{remote-workspace-2G6V2KNP.js → remote-workspace-AHVHQEES.js} +8 -8
- package/dist/review-status-EPFG4XM7.js +19 -0
- package/dist/shadow-state-5MDP6YXH.js +30 -0
- package/dist/shadow-state-5MDP6YXH.js.map +1 -0
- package/dist/{specialist-context-N32QBNNQ.js → specialist-context-ZC6A4M3I.js} +8 -7
- package/dist/{specialist-context-N32QBNNQ.js.map → specialist-context-ZC6A4M3I.js.map} +1 -1
- package/dist/{specialist-logs-GF3YV4KL.js → specialist-logs-KLGJCEUL.js} +7 -6
- package/dist/specialist-logs-KLGJCEUL.js.map +1 -0
- package/dist/{specialists-JBIW6MP4.js → specialists-O4HWDJL5.js} +7 -6
- package/dist/specialists-O4HWDJL5.js.map +1 -0
- package/dist/tldr-daemon-T3THOUGT.js +21 -0
- package/dist/tldr-daemon-T3THOUGT.js.map +1 -0
- package/dist/traefik-QN7R5I6V.js +19 -0
- package/dist/traefik-QN7R5I6V.js.map +1 -0
- package/dist/tunnel-W2GZBLEV.js +13 -0
- package/dist/tunnel-W2GZBLEV.js.map +1 -0
- package/dist/workspace-manager-IE4JL2JP.js +22 -0
- package/dist/workspace-manager-IE4JL2JP.js.map +1 -0
- package/package.json +2 -2
- package/scripts/heartbeat-hook +37 -10
- package/scripts/patches/llm-tldr-tsx-support.py +109 -0
- package/scripts/pre-tool-hook +26 -15
- package/scripts/record-cost-event.js +177 -43
- package/scripts/record-cost-event.ts +87 -3
- package/scripts/statusline.sh +169 -0
- package/scripts/stop-hook +21 -11
- package/scripts/tldr-post-edit +72 -0
- package/scripts/tldr-read-enforcer +275 -0
- package/scripts/work-agent-stop-hook +137 -0
- package/skills/check-merged/SKILL.md +143 -0
- package/skills/crash-investigation/SKILL.md +301 -0
- package/skills/github-cli/SKILL.md +185 -0
- package/skills/myn-standards/SKILL.md +351 -0
- package/skills/pan-reopen/SKILL.md +65 -0
- package/skills/pan-sync-main/SKILL.md +87 -0
- package/skills/pan-tldr/SKILL.md +149 -0
- package/skills/react-best-practices/SKILL.md +125 -0
- package/skills/spec-readiness/REPORT-TEMPLATE.md +158 -0
- package/skills/spec-readiness/SCORING-REFERENCE.md +369 -0
- package/skills/spec-readiness/SKILL.md +400 -0
- package/skills/spec-readiness-setup/SKILL.md +361 -0
- package/skills/workspace-status/SKILL.md +56 -0
- package/skills/write-spec/SKILL.md +138 -0
- package/templates/traefik/dynamic/panopticon.yml.template +0 -5
- package/templates/traefik/traefik.yml +0 -8
- package/dist/chunk-2NIAOCIC.js.map +0 -1
- package/dist/chunk-3XAB4IXF.js +0 -51
- package/dist/chunk-3XAB4IXF.js.map +0 -1
- package/dist/chunk-6HXKTOD7.js.map +0 -1
- package/dist/chunk-BBCUK6N2.js +0 -241
- package/dist/chunk-BBCUK6N2.js.map +0 -1
- package/dist/chunk-BWGFN44T.js.map +0 -1
- package/dist/chunk-ELK6Q7QI.js +0 -545
- package/dist/chunk-ELK6Q7QI.js.map +0 -1
- package/dist/chunk-JY7R7V4G.js.map +0 -1
- package/dist/chunk-LYSBSZYV.js +0 -1523
- package/dist/chunk-LYSBSZYV.js.map +0 -1
- package/dist/chunk-VIWUCJ4V.js.map +0 -1
- package/dist/chunk-VU4FLXV5.js.map +0 -1
- package/dist/chunk-XP2DXWYP.js.map +0 -1
- package/dist/dashboard/public/assets/index-C7X6LP5Z.css +0 -32
- package/dist/dashboard/public/assets/index-ClYqpcAJ.js +0 -645
- package/dist/feedback-writer-AAKF5BTK.js.map +0 -1
- package/dist/review-status-GWQYY77L.js.map +0 -1
- package/dist/traefik-CUJM6K5Z.js +0 -12
- /package/dist/{agents-BDFHF4T3.js.map → agents-E43Y3HNU.js.map} +0 -0
- /package/dist/{config-BOAMSKTF.js.map → config-4CJNUE3O.js.map} +0 -0
- /package/dist/{dns-L3L2BB27.js.map → dns-7BDJSD3E.js.map} +0 -0
- /package/dist/{projects-VXRUCMLM.js.map → hume-WMAUBBV2.js.map} +0 -0
- /package/dist/{remote-agents-Z3R2A5BN.js.map → projects-JEIVIYC6.js.map} +0 -0
- /package/dist/{specialist-logs-GF3YV4KL.js.map → rally-RKFSWC7E.js.map} +0 -0
- /package/dist/{specialists-JBIW6MP4.js.map → remote-agents-TFSMW7GN.js.map} +0 -0
- /package/dist/{remote-workspace-2G6V2KNP.js.map → remote-workspace-AHVHQEES.js.map} +0 -0
- /package/dist/{traefik-CUJM6K5Z.js.map → review-status-EPFG4XM7.js.map} +0 -0
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
saveWorkspaceMetadata
|
|
3
3
|
} from "./chunk-44EOY2ZL.js";
|
|
4
|
-
import {
|
|
5
|
-
extractTeamPrefix,
|
|
6
|
-
findProjectByTeam,
|
|
7
|
-
init_projects
|
|
8
|
-
} from "./chunk-JY7R7V4G.js";
|
|
9
4
|
import {
|
|
10
5
|
createExeProvider,
|
|
11
6
|
init_exe_provider
|
|
@@ -13,8 +8,13 @@ import {
|
|
|
13
8
|
import {
|
|
14
9
|
init_config,
|
|
15
10
|
loadConfig
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import
|
|
11
|
+
} from "./chunk-FQ66DECN.js";
|
|
12
|
+
import {
|
|
13
|
+
extractTeamPrefix,
|
|
14
|
+
findProjectByTeam,
|
|
15
|
+
init_projects
|
|
16
|
+
} from "./chunk-OMNXYPXC.js";
|
|
17
|
+
import "./chunk-ZTFNYOC7.js";
|
|
18
18
|
import {
|
|
19
19
|
init_esm_shims
|
|
20
20
|
} from "./chunk-ZHC57RCV.js";
|
|
@@ -178,4 +178,4 @@ EOF`);
|
|
|
178
178
|
export {
|
|
179
179
|
createRemoteWorkspace
|
|
180
180
|
};
|
|
181
|
-
//# sourceMappingURL=remote-workspace-
|
|
181
|
+
//# sourceMappingURL=remote-workspace-AHVHQEES.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearReviewStatus,
|
|
3
|
+
getReviewStatus,
|
|
4
|
+
init_review_status,
|
|
5
|
+
loadReviewStatuses,
|
|
6
|
+
saveReviewStatuses,
|
|
7
|
+
setReviewStatus
|
|
8
|
+
} from "./chunk-GFP3PIPB.js";
|
|
9
|
+
import "./chunk-JQBV3Q2W.js";
|
|
10
|
+
import "./chunk-ZHC57RCV.js";
|
|
11
|
+
init_review_status();
|
|
12
|
+
export {
|
|
13
|
+
clearReviewStatus,
|
|
14
|
+
getReviewStatus,
|
|
15
|
+
loadReviewStatuses,
|
|
16
|
+
saveReviewStatuses,
|
|
17
|
+
setReviewStatus
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=review-status-EPFG4XM7.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createShadowState,
|
|
3
|
+
getDisplayStatus,
|
|
4
|
+
getPendingSyncCount,
|
|
5
|
+
getShadowState,
|
|
6
|
+
getUnsyncedHistory,
|
|
7
|
+
isShadowed,
|
|
8
|
+
listShadowedIssues,
|
|
9
|
+
markAsSynced,
|
|
10
|
+
needsSync,
|
|
11
|
+
removeShadowState,
|
|
12
|
+
updateShadowState,
|
|
13
|
+
updateTrackerStatusCache
|
|
14
|
+
} from "./chunk-B3PF6JPQ.js";
|
|
15
|
+
import "./chunk-ZHC57RCV.js";
|
|
16
|
+
export {
|
|
17
|
+
createShadowState,
|
|
18
|
+
getDisplayStatus,
|
|
19
|
+
getPendingSyncCount,
|
|
20
|
+
getShadowState,
|
|
21
|
+
getUnsyncedHistory,
|
|
22
|
+
isShadowed,
|
|
23
|
+
listShadowedIssues,
|
|
24
|
+
markAsSynced,
|
|
25
|
+
needsSync,
|
|
26
|
+
removeShadowState,
|
|
27
|
+
updateShadowState,
|
|
28
|
+
updateTrackerStatusCache
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=shadow-state-5MDP6YXH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getRecentRunLogs,
|
|
3
3
|
init_specialist_logs
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-AAFQANKW.js";
|
|
5
5
|
import {
|
|
6
6
|
getModelId,
|
|
7
7
|
init_work_type_router
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-FTCPTHIJ.js";
|
|
9
|
+
import "./chunk-JQBV3Q2W.js";
|
|
10
|
+
import "./chunk-HJSM6E6U.js";
|
|
10
11
|
import {
|
|
11
12
|
getProject,
|
|
12
13
|
init_projects
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-OMNXYPXC.js";
|
|
14
15
|
import {
|
|
15
16
|
getPanopticonHome,
|
|
16
17
|
init_paths
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ZTFNYOC7.js";
|
|
18
19
|
import {
|
|
19
20
|
__esm,
|
|
20
21
|
init_esm_shims
|
|
@@ -66,7 +67,7 @@ function getDigestModel(projectKey, specialistType) {
|
|
|
66
67
|
const workTypeId = `specialist-${specialistType}`;
|
|
67
68
|
return getModelId(workTypeId);
|
|
68
69
|
} catch (error) {
|
|
69
|
-
return "claude-sonnet-4-
|
|
70
|
+
return "claude-sonnet-4-6";
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
async function generateContextDigest(projectKey, specialistType, options = {}) {
|
|
@@ -253,4 +254,4 @@ export {
|
|
|
253
254
|
regenerateContextDigest,
|
|
254
255
|
scheduleDigestGeneration
|
|
255
256
|
};
|
|
256
|
-
//# sourceMappingURL=specialist-context-
|
|
257
|
+
//# sourceMappingURL=specialist-context-ZC6A4M3I.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.panopticon/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { getPanopticonHome } from '../paths.js';\nimport { getRecentRunLogs, type RunLogEntry } from './specialist-logs.js';\nimport { getProject } from '../projects.js';\nimport { getModelId } from '../work-type-router.js';\n\nconst execAsync = promisify(exec);\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getPanopticonHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProject(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProject(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n // Fall back to specialist's model\n try {\n const workTypeId = `specialist-${specialistType}` as any;\n return getModelId(workTypeId);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-5';\n }\n}\n\n/**\n * Generate a context digest from recent runs using AI\n *\n * Creates an AI-generated summary of recent specialist runs to provide\n * context for the next run. This includes patterns, learnings, and common issues.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Generation options\n * @returns Generated digest or null if generation failed\n */\nexport async function generateContextDigest(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[specialist-context] Generating digest for ${projectKey}/${specialistType} using ${model}...`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getPanopticonHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt\n const { stdout, stderr } = await execAsync(\n `claude --dangerously-skip-permissions --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[specialist-context] Claude stderr:`, stderr);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[specialist-context] Empty digest generated`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[specialist-context] Generated digest (${digest.length} chars)`);\n return digest;\n } catch (error: any) {\n console.error(`[specialist-context] Failed to generate digest:`, error.message);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProject(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}\n\n/**\n * Regenerate the context digest\n *\n * Forces regeneration even if a digest already exists.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Generated digest or null if generation failed\n */\nexport async function regenerateContextDigest(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return generateContextDigest(projectKey, specialistType, { force: true });\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n generateContextDigest(projectKey, specialistType).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.panopticon/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { getPanopticonHome } from '../paths.js';\nimport { getRecentRunLogs, type RunLogEntry } from './specialist-logs.js';\nimport { getProject } from '../projects.js';\nimport { getModelId } from '../work-type-router.js';\n\nconst execAsync = promisify(exec);\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getPanopticonHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProject(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProject(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n // Fall back to specialist's model\n try {\n const workTypeId = `specialist-${specialistType}` as any;\n return getModelId(workTypeId);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}\n\n/**\n * Generate a context digest from recent runs using AI\n *\n * Creates an AI-generated summary of recent specialist runs to provide\n * context for the next run. This includes patterns, learnings, and common issues.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Generation options\n * @returns Generated digest or null if generation failed\n */\nexport async function generateContextDigest(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[specialist-context] Generating digest for ${projectKey}/${specialistType} using ${model}...`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getPanopticonHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt\n const { stdout, stderr } = await execAsync(\n `claude --dangerously-skip-permissions --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[specialist-context] Claude stderr:`, stderr);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[specialist-context] Empty digest generated`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[specialist-context] Generated digest (${digest.length} chars)`);\n return digest;\n } catch (error: any) {\n console.error(`[specialist-context] Failed to generate digest:`, error.message);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProject(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}\n\n/**\n * Regenerate the context digest\n *\n * Forces regeneration even if a digest already exists.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Generated digest or null if generation failed\n */\nexport async function regenerateContextDigest(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return generateContextDigest(projectKey, specialistType, { force: true });\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n generateContextDigest(projectKey, specialistType).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAUA,SAAS,YAAY,WAAW,cAAc,eAAe,kBAAkB;AAC/E,SAAS,YAAY;AACrB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAS1B,SAAS,oBAA4B;AACnC,SAAO,KAAK,kBAAkB,GAAG,aAAa;AAChD;AAKO,SAAS,oBAAoB,YAAoB,gBAAgC;AACtF,SAAO,KAAK,kBAAkB,GAAG,YAAY,gBAAgB,SAAS;AACxE;AAKO,SAAS,qBAAqB,YAAoB,gBAAgC;AACvF,QAAM,aAAa,oBAAoB,YAAY,cAAc;AACjE,SAAO,KAAK,YAAY,kBAAkB;AAC5C;AAKA,SAAS,uBAAuB,YAAoB,gBAA8B;AAChF,QAAM,aAAa,oBAAoB,YAAY,cAAc;AACjE,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,cAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACF;AASO,SAAS,kBAAkB,YAAoB,gBAAuC;AAC3F,QAAM,aAAa,qBAAqB,YAAY,cAAc;AAElE,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,WAAO,aAAa,YAAY,OAAO;AAAA,EACzC,SAAS,OAAO;AACd,YAAQ,MAAM,kDAAkD,UAAU,IAAI,cAAc,KAAK,KAAK;AACtG,WAAO;AAAA,EACT;AACF;AAUA,SAAS,oBAAoB,YAA4B;AACvD,QAAM,UAAU,WAAW,UAAU;AACrC,SAAO,SAAS,aAAa,gBAAgB;AAC/C;AAWA,SAAS,eAAe,YAAoB,gBAAgC;AAC1E,QAAM,UAAU,WAAW,UAAU;AAGrC,MAAI,SAAS,aAAa,cAAc;AACtC,WAAO,QAAQ,YAAY;AAAA,EAC7B;AAGA,MAAI;AACF,UAAM,aAAa,cAAc,cAAc;AAC/C,WAAO,WAAW,UAAU;AAAA,EAC9B,SAAS,OAAO;AAEd,WAAO;AAAA,EACT;AACF;AAaA,eAAsB,sBACpB,YACA,gBACA,UAII,CAAC,GACmB;AACxB,yBAAuB,YAAY,cAAc;AAGjD,QAAM,WAAW,QAAQ,YAAY,oBAAoB,UAAU;AACnE,QAAM,aAAa,iBAAiB,YAAY,gBAAgB,QAAQ;AAExE,MAAI,WAAW,WAAW,KAAK,CAAC,QAAQ,OAAO;AAC7C,YAAQ,IAAI,2CAA2C,UAAU,IAAI,cAAc,8BAA8B;AACjH,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,kBAAkB,YAAY,gBAAgB,UAAU;AACvE,QAAM,QAAQ,QAAQ,SAAS,eAAe,YAAY,cAAc;AAExE,MAAI;AACF,YAAQ,IAAI,8CAA8C,UAAU,IAAI,cAAc,UAAU,KAAK,KAAK;AAI1G,UAAM,UAAU,KAAK,kBAAkB,GAAG,KAAK;AAC/C,QAAI,CAAC,WAAW,OAAO,GAAG;AACxB,gBAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,IACxC;AAEA,UAAM,aAAa,KAAK,SAAS,iBAAiB,KAAK,IAAI,CAAC,KAAK;AACjE,kBAAc,YAAY,QAAQ,OAAO;AAGzC,UAAM,EAAE,QAAQ,OAAO,IAAI,MAAM;AAAA,MAC/B,iDAAiD,KAAK,YAAY,UAAU;AAAA,MAC5E;AAAA,QACE,UAAU;AAAA,QACV,WAAW,KAAK,OAAO;AAAA;AAAA,QACvB,SAAS;AAAA;AAAA,MACX;AAAA,IACF;AAGA,QAAI;AACF,iBAAW,UAAU;AAAA,IACvB,QAAQ;AAAA,IAER;AAEA,QAAI,UAAU,CAAC,OAAO,SAAS,SAAS,GAAG;AACzC,cAAQ,MAAM,uCAAuC,MAAM;AAAA,IAC7D;AAEA,UAAM,SAAS,OAAO,KAAK;AAE3B,QAAI,CAAC,QAAQ;AACX,cAAQ,MAAM,6CAA6C;AAC3D,aAAO;AAAA,IACT;AAGA,UAAM,aAAa,qBAAqB,YAAY,cAAc;AAClE,kBAAc,YAAY,QAAQ,OAAO;AAEzC,YAAQ,IAAI,0CAA0C,OAAO,MAAM,SAAS;AAC5E,WAAO;AAAA,EACT,SAAS,OAAY;AACnB,YAAQ,MAAM,mDAAmD,MAAM,OAAO;AAE9E,WAAO;AAAA,EACT;AACF;AAUA,SAAS,kBACP,YACA,gBACA,YACQ;AACR,QAAM,UAAU,WAAW,UAAU;AACrC,QAAM,cAAc,SAAS,QAAQ;AAErC,MAAI,SAAS,6CAA6C,cAAc,uBAAuB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAc1G,MAAI,WAAW,WAAW,GAAG;AAC3B,cAAU;AAAA;AAAA;AACV,cAAU;AAAA;AAAA,EACZ,OAAO;AACL,eAAW,QAAQ,CAAC,KAAK,UAAU;AACjC,gBAAU,WAAW,QAAQ,CAAC,KAAK,IAAI,SAAS,OAAO,KAAK,IAAI,SAAS,UAAU,SAAS;AAAA;AAC5F,gBAAU,YAAY,IAAI,SAAS,SAAS;AAAA;AAC5C,UAAI,IAAI,SAAS,YAAY;AAC3B,kBAAU,aAAa,IAAI,SAAS,UAAU;AAAA;AAAA,MAChD;AACA,UAAI,IAAI,SAAS,UAAU;AACzB,cAAM,cAAc,KAAK,MAAM,IAAI,SAAS,WAAW,GAAI;AAC3D,cAAM,UAAU,KAAK,MAAM,cAAc,EAAE;AAC3C,cAAM,UAAU,cAAc;AAC9B,kBAAU,aAAa,OAAO,KAAK,OAAO;AAAA;AAAA,MAC5C;AACA,UAAI,IAAI,SAAS,OAAO;AACtB,kBAAU,UAAU,IAAI,SAAS,KAAK;AAAA;AAAA,MACxC;AAGA,UAAI;AACF,cAAM,aAAa,aAAa,IAAI,UAAU,OAAO;AAErD,cAAM,WAAW;AACjB,cAAM,kBAAkB,WAAW,MAAM,8CAA8C;AACvF,YAAI,iBAAiB;AACnB,cAAI,aAAa,gBAAgB,CAAC,EAAE,KAAK;AACzC,cAAI,WAAW,SAAS,UAAU;AAChC,yBAAa,WAAW,UAAU,GAAG,QAAQ,IAAI;AAAA,UACnD;AACA,oBAAU;AAAA;AAAA,EAA0B,UAAU;AAAA;AAAA,QAChD;AAAA,MACF,SAAS,OAAO;AAAA,MAEhB;AAEA,gBAAU;AAAA;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,YAAU;AAAA;AAAA;AAAA;AAAA;AAAA,WAID,cAAc,gBAAgB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBlD,SAAO;AACT;AAWA,eAAsB,wBACpB,YACA,gBACwB;AACxB,SAAO,sBAAsB,YAAY,gBAAgB,EAAE,OAAO,KAAK,CAAC;AAC1E;AAWO,SAAS,yBAAyB,YAAoB,gBAA8B;AAEzF,wBAAsB,YAAY,cAAc,EAAE,MAAM,CAAC,UAAU;AACjE,YAAQ;AAAA,MACN,gEAAgE,UAAU,IAAI,cAAc;AAAA,MAC5F;AAAA,IACF;AAAA,EACF,CAAC;AACH;AASO,SAAS,iBAAiB,YAAoB,gBAAiC;AACpF,QAAM,aAAa,qBAAqB,YAAY,cAAc;AAClE,SAAO,WAAW,UAAU;AAC9B;AAWO,SAAS,oBAAoB,YAAoB,gBAAiC;AACvF,QAAM,aAAa,qBAAqB,YAAY,cAAc;AAElE,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,MAAI;AACF,eAAW,UAAU;AACrB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,YAAQ,MAAM,iDAAiD,KAAK;AACpE,WAAO;AAAA,EACT;AACF;AA9WA,IAmBM;AAnBN;AAAA;AAAA;AAcA;AACA;AACA;AACA;AAEA,IAAM,YAAY,UAAU,IAAI;AAAA;AAAA;","names":[]}
|
|
@@ -16,11 +16,12 @@ import {
|
|
|
16
16
|
isRunLogActive,
|
|
17
17
|
listRunLogs,
|
|
18
18
|
parseLogMetadata
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
21
|
-
import "./chunk-
|
|
22
|
-
import "./chunk-
|
|
23
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-AAFQANKW.js";
|
|
20
|
+
import "./chunk-FTCPTHIJ.js";
|
|
21
|
+
import "./chunk-JQBV3Q2W.js";
|
|
22
|
+
import "./chunk-HJSM6E6U.js";
|
|
23
|
+
import "./chunk-OMNXYPXC.js";
|
|
24
|
+
import "./chunk-ZTFNYOC7.js";
|
|
24
25
|
import "./chunk-ZHC57RCV.js";
|
|
25
26
|
init_specialist_logs();
|
|
26
27
|
export {
|
|
@@ -41,4 +42,4 @@ export {
|
|
|
41
42
|
listRunLogs,
|
|
42
43
|
parseLogMetadata
|
|
43
44
|
};
|
|
44
|
-
//# sourceMappingURL=specialist-logs-
|
|
45
|
+
//# sourceMappingURL=specialist-logs-KLGJCEUL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -55,11 +55,12 @@ import {
|
|
|
55
55
|
wakeSpecialist,
|
|
56
56
|
wakeSpecialistOrQueue,
|
|
57
57
|
wakeSpecialistWithTask
|
|
58
|
-
} from "./chunk-
|
|
59
|
-
import "./chunk-
|
|
60
|
-
import "./chunk-
|
|
61
|
-
import "./chunk-
|
|
62
|
-
import "./chunk-
|
|
58
|
+
} from "./chunk-AAFQANKW.js";
|
|
59
|
+
import "./chunk-FTCPTHIJ.js";
|
|
60
|
+
import "./chunk-JQBV3Q2W.js";
|
|
61
|
+
import "./chunk-HJSM6E6U.js";
|
|
62
|
+
import "./chunk-OMNXYPXC.js";
|
|
63
|
+
import "./chunk-ZTFNYOC7.js";
|
|
63
64
|
import "./chunk-ZHC57RCV.js";
|
|
64
65
|
init_specialists();
|
|
65
66
|
export {
|
|
@@ -119,4 +120,4 @@ export {
|
|
|
119
120
|
wakeSpecialistOrQueue,
|
|
120
121
|
wakeSpecialistWithTask
|
|
121
122
|
};
|
|
122
|
-
//# sourceMappingURL=specialists-
|
|
123
|
+
//# sourceMappingURL=specialists-O4HWDJL5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TldrDaemonService,
|
|
3
|
+
captureTldrMetrics,
|
|
4
|
+
getTldrDaemonService,
|
|
5
|
+
getTldrMetrics,
|
|
6
|
+
init_tldr_daemon,
|
|
7
|
+
listTldrDaemonServices,
|
|
8
|
+
removeTldrDaemonService
|
|
9
|
+
} from "./chunk-DI7ABPNQ.js";
|
|
10
|
+
import "./chunk-ZTFNYOC7.js";
|
|
11
|
+
import "./chunk-ZHC57RCV.js";
|
|
12
|
+
init_tldr_daemon();
|
|
13
|
+
export {
|
|
14
|
+
TldrDaemonService,
|
|
15
|
+
captureTldrMetrics,
|
|
16
|
+
getTldrDaemonService,
|
|
17
|
+
getTldrMetrics,
|
|
18
|
+
listTldrDaemonServices,
|
|
19
|
+
removeTldrDaemonService
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=tldr-daemon-T3THOUGT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cleanupStaleTlsSections,
|
|
3
|
+
cleanupTemplateFiles,
|
|
4
|
+
ensureProjectCerts,
|
|
5
|
+
generatePanopticonTraefikConfig,
|
|
6
|
+
generateTlsConfig
|
|
7
|
+
} from "./chunk-PPRFKTVC.js";
|
|
8
|
+
import "./chunk-FQ66DECN.js";
|
|
9
|
+
import "./chunk-OMNXYPXC.js";
|
|
10
|
+
import "./chunk-ZTFNYOC7.js";
|
|
11
|
+
import "./chunk-ZHC57RCV.js";
|
|
12
|
+
export {
|
|
13
|
+
cleanupStaleTlsSections,
|
|
14
|
+
cleanupTemplateFiles,
|
|
15
|
+
ensureProjectCerts,
|
|
16
|
+
generatePanopticonTraefikConfig,
|
|
17
|
+
generateTlsConfig
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=traefik-QN7R5I6V.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addTunnelIngress,
|
|
3
|
+
init_tunnel,
|
|
4
|
+
removeTunnelIngress
|
|
5
|
+
} from "./chunk-PELXV435.js";
|
|
6
|
+
import "./chunk-CWELWPWQ.js";
|
|
7
|
+
import "./chunk-ZHC57RCV.js";
|
|
8
|
+
init_tunnel();
|
|
9
|
+
export {
|
|
10
|
+
addTunnelIngress,
|
|
11
|
+
removeTunnelIngress
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=tunnel-W2GZBLEV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createWorkspace,
|
|
3
|
+
init_workspace_manager,
|
|
4
|
+
preTrustDirectory,
|
|
5
|
+
removeWorkspace,
|
|
6
|
+
stopWorkspaceDocker
|
|
7
|
+
} from "./chunk-GR6ZZMCX.js";
|
|
8
|
+
import "./chunk-7SN4L4PH.js";
|
|
9
|
+
import "./chunk-AQXETQHW.js";
|
|
10
|
+
import "./chunk-ZTFNYOC7.js";
|
|
11
|
+
import "./chunk-JT4O4YVM.js";
|
|
12
|
+
import "./chunk-PELXV435.js";
|
|
13
|
+
import "./chunk-CWELWPWQ.js";
|
|
14
|
+
import "./chunk-ZHC57RCV.js";
|
|
15
|
+
init_workspace_manager();
|
|
16
|
+
export {
|
|
17
|
+
createWorkspace,
|
|
18
|
+
preTrustDirectory,
|
|
19
|
+
removeWorkspace,
|
|
20
|
+
stopWorkspaceDocker
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=workspace-manager-IE4JL2JP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panopticon-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Multi-agent orchestration for AI coding assistants (Claude Code, Codex, Cursor, Gemini CLI)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"build:scripts": "esbuild scripts/record-cost-event.ts --bundle --platform=node --format=esm --outfile=scripts/record-cost-event.js",
|
|
54
54
|
"build:dashboard": "npm run build:dashboard:frontend && npm run build:dashboard:server",
|
|
55
55
|
"build:dashboard:frontend": "cd src/dashboard/frontend && npm run build",
|
|
56
|
-
"build:dashboard:server": "cd src/dashboard/server && npm run build",
|
|
56
|
+
"build:dashboard:server": "cd src/dashboard/server && npm run build && mkdir -p ../../../dist/dashboard/prompts && cp ../../lib/cloister/prompts/*.md ../../../dist/dashboard/prompts/",
|
|
57
57
|
"typecheck": "tsc --noEmit",
|
|
58
58
|
"lint": "eslint src/",
|
|
59
59
|
"test": "vitest --run --no-file-parallelism && cd src/dashboard/frontend && npm test",
|
package/scripts/heartbeat-hook
CHANGED
|
@@ -105,18 +105,18 @@ if [ -n "$ACTIVITY_ENTRY" ]; then
|
|
|
105
105
|
fi
|
|
106
106
|
fi
|
|
107
107
|
|
|
108
|
-
# Update agent runtime state
|
|
109
|
-
|
|
108
|
+
# Update agent runtime state in runtime.json (NOT state.json — prevents corrupting AgentState config)
|
|
109
|
+
RUNTIME_FILE="$AGENT_DIR/runtime.json"
|
|
110
110
|
TIMESTAMP="$(date -Iseconds)"
|
|
111
111
|
|
|
112
|
-
if [ -f "$
|
|
113
|
-
# Update existing state
|
|
114
|
-
|
|
112
|
+
if [ -f "$RUNTIME_FILE" ]; then
|
|
113
|
+
# Update existing runtime state
|
|
114
|
+
TEMP_RUNTIME="$RUNTIME_FILE.tmp"
|
|
115
115
|
jq --arg ts "$TIMESTAMP" --arg tool "$TOOL_NAME" \
|
|
116
116
|
'.lastActivity = $ts | .currentTool = $tool | .state = "active"' \
|
|
117
|
-
"$
|
|
117
|
+
"$RUNTIME_FILE" > "$TEMP_RUNTIME" 2>/dev/null && mv "$TEMP_RUNTIME" "$RUNTIME_FILE" 2>/dev/null || true
|
|
118
118
|
else
|
|
119
|
-
# Create initial state
|
|
119
|
+
# Create initial runtime state
|
|
120
120
|
jq -n \
|
|
121
121
|
--arg ts "$TIMESTAMP" \
|
|
122
122
|
--arg tool "$TOOL_NAME" \
|
|
@@ -124,7 +124,7 @@ else
|
|
|
124
124
|
state: "active",
|
|
125
125
|
lastActivity: $ts,
|
|
126
126
|
currentTool: $tool
|
|
127
|
-
}' > "$
|
|
127
|
+
}' > "$RUNTIME_FILE" 2>/dev/null || true
|
|
128
128
|
fi
|
|
129
129
|
|
|
130
130
|
# Record cost event from tool usage (PAN-81)
|
|
@@ -137,12 +137,39 @@ elif [ -f "$(dirname "$0")/record-cost-event.js" ]; then
|
|
|
137
137
|
fi
|
|
138
138
|
|
|
139
139
|
if [ -n "$COST_SCRIPT" ] && [ -f "$COST_SCRIPT" ]; then
|
|
140
|
+
# Infer issue ID from git branch if not set by Panopticon agent launcher
|
|
141
|
+
# Matches branches like: feature/pan-208, feature-min-665, pan-142
|
|
142
|
+
if [ -z "$PANOPTICON_ISSUE_ID" ]; then
|
|
143
|
+
if [[ "$GIT_BRANCH" =~ (pan|min|aud|PAN|MIN|AUD)[-]([0-9]+) ]]; then
|
|
144
|
+
PANOPTICON_ISSUE_ID="${BASH_REMATCH[1]^^}-${BASH_REMATCH[2]}"
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# Fallback: infer from workspace path (e.g., /workspaces/feature-pan-208/)
|
|
149
|
+
if [ -z "$PANOPTICON_ISSUE_ID" ]; then
|
|
150
|
+
if [[ "$WORKSPACE" =~ (pan|min|aud|PAN|MIN|AUD)[-]([0-9]+) ]]; then
|
|
151
|
+
PANOPTICON_ISSUE_ID="${BASH_REMATCH[1]^^}-${BASH_REMATCH[2]}"
|
|
152
|
+
fi
|
|
153
|
+
fi
|
|
154
|
+
|
|
140
155
|
# Export issue context for the cost recording script
|
|
141
156
|
export PANOPTICON_ISSUE_ID="${PANOPTICON_ISSUE_ID:-UNKNOWN}"
|
|
142
157
|
export PANOPTICON_SESSION_TYPE="${PANOPTICON_SESSION_TYPE:-implementation}"
|
|
143
158
|
|
|
144
|
-
#
|
|
145
|
-
|
|
159
|
+
# Serialize per-session cost recording with flock to prevent duplicate events.
|
|
160
|
+
# Claude Code fires parallel tool calls, which spawn multiple heartbeat-hooks
|
|
161
|
+
# simultaneously. Without serialization they race on the same offset file and
|
|
162
|
+
# record duplicate cost events. flock -x waits for exclusive access, ensuring
|
|
163
|
+
# only one process reads/updates the offset at a time per session.
|
|
164
|
+
STATE_DIR="$HOME/.panopticon/costs/state"
|
|
165
|
+
mkdir -p "$STATE_DIR"
|
|
166
|
+
SESSION_ID=$(echo "$TOOL_INFO" | jq -r '.session_id // "unknown"' 2>/dev/null || echo "unknown")
|
|
167
|
+
LOCK_FILE="$STATE_DIR/${SESSION_ID}.lock"
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
flock -x -w 30 200
|
|
171
|
+
echo "$TOOL_INFO" | node "$COST_SCRIPT" 2>/dev/null || true
|
|
172
|
+
} 200>"$LOCK_FILE"
|
|
146
173
|
fi
|
|
147
174
|
|
|
148
175
|
# Always exit successfully - never break Claude Code execution
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Patch llm-tldr to support .tsx and .jsx file extensions.
|
|
3
|
+
|
|
4
|
+
llm-tldr v1.5.2 maps TypeScript to only .ts in _get_module_exports().
|
|
5
|
+
This patch adds .tsx for TypeScript and .jsx for JavaScript.
|
|
6
|
+
|
|
7
|
+
Apply: python3 scripts/patches/llm-tldr-tsx-support.py <venv-path>
|
|
8
|
+
Example: python3 scripts/patches/llm-tldr-tsx-support.py .venv
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import re
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
def patch_api(venv_path: str) -> bool:
|
|
16
|
+
"""Patch api.py in the given venv to support .tsx/.jsx extensions."""
|
|
17
|
+
api_file = Path(venv_path) / "lib" / "python3.12" / "site-packages" / "tldr" / "api.py"
|
|
18
|
+
|
|
19
|
+
if not api_file.exists():
|
|
20
|
+
# Try python3.13, etc.
|
|
21
|
+
for pydir in Path(venv_path).glob("lib/python3.*/site-packages/tldr/api.py"):
|
|
22
|
+
api_file = pydir
|
|
23
|
+
break
|
|
24
|
+
|
|
25
|
+
if not api_file.exists():
|
|
26
|
+
print(f"ERROR: {api_file} not found")
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
content = api_file.read_text()
|
|
30
|
+
|
|
31
|
+
# Check if already patched
|
|
32
|
+
if '".tsx"' in content:
|
|
33
|
+
print(f"Already patched: {api_file}")
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
# Pattern: the old ext_map with single string values
|
|
37
|
+
old_pattern = ''' ext_map = {
|
|
38
|
+
"python": ".py",
|
|
39
|
+
"typescript": ".ts",
|
|
40
|
+
"go": ".go",
|
|
41
|
+
"rust": ".rs"
|
|
42
|
+
}
|
|
43
|
+
ext = ext_map.get(language, ".py")'''
|
|
44
|
+
|
|
45
|
+
new_code = ''' ext_map = {
|
|
46
|
+
"python": [".py"],
|
|
47
|
+
"typescript": [".ts", ".tsx"],
|
|
48
|
+
"javascript": [".js", ".jsx"],
|
|
49
|
+
"go": [".go"],
|
|
50
|
+
"rust": [".rs"],
|
|
51
|
+
}
|
|
52
|
+
extensions = ext_map.get(language, [".py"])'''
|
|
53
|
+
|
|
54
|
+
if old_pattern not in content:
|
|
55
|
+
print(f"WARNING: Could not find ext_map pattern — may already be patched or llm-tldr version changed")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
content = content.replace(old_pattern, new_code)
|
|
59
|
+
|
|
60
|
+
# Also fix the module file resolution to iterate over extensions
|
|
61
|
+
old_resolve = ''' # Try to find the module file
|
|
62
|
+
# module_path "providers/anthropic" -> providers/anthropic.py
|
|
63
|
+
module_file = project / f"{module_path}{ext}"
|
|
64
|
+
|
|
65
|
+
if not module_file.exists():
|
|
66
|
+
# Try as directory with __init__.py (Python package)
|
|
67
|
+
init_file = project / module_path / "__init__.py"
|
|
68
|
+
if init_file.exists():
|
|
69
|
+
module_file = init_file
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(f"Module not found: {module_path} (tried {module_file} and {init_file})")'''
|
|
72
|
+
|
|
73
|
+
new_resolve = ''' # Try to find the module file
|
|
74
|
+
# module_path "providers/anthropic" -> providers/anthropic.py
|
|
75
|
+
module_file = None
|
|
76
|
+
for ext in extensions:
|
|
77
|
+
candidate = project / f"{module_path}{ext}"
|
|
78
|
+
if candidate.exists():
|
|
79
|
+
module_file = candidate
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
if module_file is None:
|
|
83
|
+
# Try as directory with __init__.py (Python package)
|
|
84
|
+
init_file = project / module_path / "__init__.py"
|
|
85
|
+
if init_file.exists():
|
|
86
|
+
module_file = init_file
|
|
87
|
+
else:
|
|
88
|
+
tried = ", ".join(str(project / f"{module_path}{e}") for e in extensions)
|
|
89
|
+
raise ValueError(f"Module not found: {module_path} (tried {tried} and {init_file})")'''
|
|
90
|
+
|
|
91
|
+
if old_resolve not in content:
|
|
92
|
+
print(f"WARNING: Could not find module resolution pattern")
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
content = content.replace(old_resolve, new_resolve)
|
|
96
|
+
|
|
97
|
+
api_file.write_text(content)
|
|
98
|
+
print(f"Patched: {api_file}")
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
if len(sys.argv) < 2:
|
|
104
|
+
print("Usage: python3 llm-tldr-tsx-support.py <venv-path>")
|
|
105
|
+
print("Example: python3 llm-tldr-tsx-support.py .venv")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
success = patch_api(sys.argv[1])
|
|
109
|
+
sys.exit(0 if success else 1)
|
package/scripts/pre-tool-hook
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
# ~/.panopticon/bin/pre-tool-hook
|
|
3
3
|
# Called by Claude Code before every tool use
|
|
4
4
|
#
|
|
5
|
-
# This hook
|
|
6
|
-
#
|
|
5
|
+
# This hook updates runtime.json (NOT state.json) to prevent corrupting
|
|
6
|
+
# AgentState config. runtime.json is the dedicated file for hook-based
|
|
7
|
+
# heartbeat tracking, while state.json stores agent config set at spawn.
|
|
7
8
|
|
|
8
9
|
# Don't use set -e - we want the hook to be resilient to failures
|
|
9
10
|
# and never break Claude Code execution
|
|
@@ -33,19 +34,29 @@ TOOL_NAME=$(echo "$TOOL_INFO" | jq -r '.tool_name // "unknown"' 2>/dev/null || e
|
|
|
33
34
|
STATE_DIR="$HOME/.panopticon/agents/$AGENT_ID"
|
|
34
35
|
mkdir -p "$STATE_DIR"
|
|
35
36
|
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
lastActivity
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
# Update runtime.json (separate from state.json to avoid corrupting AgentState)
|
|
38
|
+
RUNTIME_FILE="$STATE_DIR/runtime.json"
|
|
39
|
+
TEMP_FILE="$STATE_DIR/runtime.json.tmp"
|
|
40
|
+
TIMESTAMP="$(date -Iseconds)"
|
|
41
|
+
|
|
42
|
+
if [ -f "$RUNTIME_FILE" ]; then
|
|
43
|
+
# Update existing runtime state
|
|
44
|
+
jq --arg ts "$TIMESTAMP" --arg tool "$TOOL_NAME" \
|
|
45
|
+
'.lastActivity = $ts | .currentTool = $tool | .state = "active"' \
|
|
46
|
+
"$RUNTIME_FILE" > "$TEMP_FILE" 2>/dev/null && mv "$TEMP_FILE" "$RUNTIME_FILE" 2>/dev/null || true
|
|
47
|
+
else
|
|
48
|
+
# Create initial runtime state
|
|
49
|
+
jq -n \
|
|
50
|
+
--arg timestamp "$TIMESTAMP" \
|
|
51
|
+
--arg state "active" \
|
|
52
|
+
--arg tool "$TOOL_NAME" \
|
|
53
|
+
'{
|
|
54
|
+
state: $state,
|
|
55
|
+
lastActivity: $timestamp,
|
|
56
|
+
currentTool: $tool
|
|
57
|
+
}' > "$TEMP_FILE" 2>/dev/null || true
|
|
58
|
+
mv "$TEMP_FILE" "$RUNTIME_FILE" 2>/dev/null || true
|
|
59
|
+
fi
|
|
49
60
|
|
|
50
61
|
# Optionally send heartbeat to API server (non-blocking)
|
|
51
62
|
# Only if dashboard is running
|