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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/tracker/rally-api.ts","../src/lib/tracker/interface.ts","../src/lib/tracker/rally.ts"],"sourcesContent":["/**\n * Rally WSAPI REST Client\n *\n * Thin wrapper around native fetch for Rally Web Services API v2.0.\n * Provides typed methods for query, create, and update operations.\n */\n\nexport interface RallyQueryConfig {\n type: string;\n fetch?: string[];\n query?: string;\n limit?: number;\n workspace?: string;\n project?: string;\n projectScopeDown?: boolean;\n order?: string;\n}\n\nexport interface RallyQueryResult {\n QueryResult: {\n Results: any[];\n TotalResultCount: number;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyCreateConfig {\n type: string;\n data: any;\n fetch?: string[];\n}\n\nexport interface RallyCreateResult {\n CreateResult: {\n Object: any;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyUpdateConfig {\n type: string;\n ref: string;\n data: any;\n fetch?: string[];\n}\n\nexport interface RallyUpdateResult {\n OperationResult: {\n Object: any;\n Errors: string[];\n Warnings: string[];\n };\n}\n\nexport interface RallyApiConfig {\n apiKey: string;\n server?: string;\n requestOptions?: {\n headers?: Record<string, string>;\n };\n}\n\nexport class RallyRestApi {\n private apiKey: string;\n public server: string;\n private customHeaders: Record<string, string>;\n\n constructor(config: RallyApiConfig) {\n this.apiKey = config.apiKey;\n this.server = config.server || 'https://rally1.rallydev.com';\n this.customHeaders = config.requestOptions?.headers || {};\n }\n\n /**\n * Query Rally artifacts\n */\n async query(config: RallyQueryConfig): Promise<RallyQueryResult> {\n const params = new URLSearchParams();\n\n if (config.query) {\n params.set('query', config.query);\n }\n\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n if (config.limit !== undefined) {\n params.set('pagesize', String(config.limit));\n }\n\n if (config.workspace) {\n params.set('workspace', config.workspace);\n }\n\n if (config.project) {\n params.set('project', config.project);\n if (config.projectScopeDown) {\n params.set('projectScopeDown', 'true');\n }\n }\n\n if (config.order) {\n params.set('order', config.order);\n }\n\n const url = `${this.server}/slm/webservice/v2.0/${config.type}?${params.toString()}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n throw new Error('Unauthorized: Invalid API key or insufficient permissions');\n }\n throw new Error(`Rally API query failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyQueryResult;\n\n if (result.QueryResult.Errors && result.QueryResult.Errors.length > 0) {\n const errorDetail = result.QueryResult.Errors.join(', ');\n const queryDetail = config.query ? ` (Query: ${config.query})` : '';\n if (process.env.DEBUG?.includes('rally')) {\n console.error('[Rally WSAPI] Query failed:', { query: config.query, errors: result.QueryResult.Errors });\n }\n throw new Error(`Rally API query failed: ${errorDetail}${queryDetail}`);\n }\n\n return result;\n }\n\n /**\n * Create a Rally object\n */\n async create(config: RallyCreateConfig): Promise<RallyCreateResult> {\n const url = `${this.server}/slm/webservice/v2.0/${config.type}/create`;\n\n const body: any = {\n [config.type]: config.data,\n };\n\n const params = new URLSearchParams();\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;\n\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Rally API create failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyCreateResult;\n\n if (result.CreateResult.Errors && result.CreateResult.Errors.length > 0) {\n throw new Error(`Rally API create failed: ${result.CreateResult.Errors.join(', ')}`);\n }\n\n return result;\n }\n\n /**\n * Update a Rally object\n */\n async update(config: RallyUpdateConfig): Promise<RallyUpdateResult> {\n // Extract ObjectID from ref (e.g., \"/hierarchicalrequirement/12345\" -> \"12345\")\n const objectId = config.ref.split('/').pop();\n const url = `${this.server}/slm/webservice/v2.0/${config.type}/${objectId}`;\n\n const body: any = {\n [config.type]: config.data,\n };\n\n const params = new URLSearchParams();\n if (config.fetch && config.fetch.length > 0) {\n params.set('fetch', config.fetch.join(','));\n }\n\n const finalUrl = params.toString() ? `${url}?${params.toString()}` : url;\n\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers: {\n 'ZSESSIONID': this.apiKey,\n 'Content-Type': 'application/json',\n ...this.customHeaders,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(`Rally API update failed: ${response.status} ${response.statusText}`);\n }\n\n const result = await response.json() as RallyUpdateResult;\n\n if (result.OperationResult.Errors && result.OperationResult.Errors.length > 0) {\n throw new Error(`Rally API update failed: ${result.OperationResult.Errors.join(', ')}`);\n }\n\n return result;\n }\n}\n","/**\n * Issue Tracker Abstraction Layer\n *\n * Provides a unified interface for different issue tracking systems\n * (Linear, GitHub Issues, GitLab Issues, etc.)\n */\n\n// Supported tracker types\nexport type TrackerType = 'linear' | 'github' | 'gitlab' | 'rally';\n\n// Normalized issue state (lowest common denominator)\nexport type IssueState = 'open' | 'in_progress' | 'closed';\n\n// Normalized issue format\nexport interface Issue {\n /** Tracker-specific unique ID */\n id: string;\n\n /** Human-readable reference (e.g., MIN-630, #42) */\n ref: string;\n\n /** Issue title */\n title: string;\n\n /** Issue description/body (markdown) */\n description: string;\n\n /** Normalized state */\n state: IssueState;\n\n /** Labels/tags */\n labels: string[];\n\n /** Assignee username/name */\n assignee?: string;\n\n /** Web URL to the issue */\n url: string;\n\n /** Which tracker this issue came from */\n tracker: TrackerType;\n\n /** Cross-tracker linked issue references */\n linkedIssues?: string[];\n\n /** Priority (1=urgent, 2=high, 3=normal, 4=low) */\n priority?: number;\n\n /** Due date (ISO string) */\n dueDate?: string;\n\n /** Creation timestamp (ISO string) */\n createdAt: string;\n\n /** Last update timestamp (ISO string) */\n updatedAt: string;\n\n /** Parent issue FormattedID (e.g., \"F1234\") for Rally hierarchy */\n parentRef?: string;\n\n /** Rally artifact type (e.g., \"HierarchicalRequirement\", \"PortfolioItem/Feature\") */\n artifactType?: string;\n\n /** Raw tracker state name before normalization (e.g., \"Discovering\", \"In-Progress\") */\n rawState?: string;\n}\n\n// Comment on an issue\nexport interface Comment {\n id: string;\n issueId: string;\n body: string;\n author: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// Filters for listing issues\nexport interface IssueFilters {\n /** Filter by state */\n state?: IssueState;\n\n /** Filter by labels (AND logic) */\n labels?: string[];\n\n /** Filter by assignee */\n assignee?: string;\n\n /** Filter by team/project (tracker-specific) */\n team?: string;\n\n /** Search query for title/description */\n query?: string;\n\n /** Maximum number of results */\n limit?: number;\n\n /** Include closed issues (default: false) */\n includeClosed?: boolean;\n}\n\n// Data for creating a new issue\nexport interface NewIssue {\n title: string;\n description?: string;\n labels?: string[];\n assignee?: string;\n team?: string;\n priority?: number;\n dueDate?: string;\n}\n\n// Data for updating an issue\nexport interface IssueUpdate {\n title?: string;\n description?: string;\n state?: IssueState;\n labels?: string[];\n assignee?: string;\n priority?: number;\n dueDate?: string;\n}\n\n/**\n * Abstract interface for issue trackers.\n * Implementations must handle normalization to/from tracker-specific formats.\n */\nexport interface IssueTracker {\n /** Tracker type identifier */\n readonly name: TrackerType;\n\n /**\n * List issues matching filters\n */\n listIssues(filters?: IssueFilters): Promise<Issue[]>;\n\n /**\n * Get a single issue by ID or ref\n * @param id - Issue ID or human-readable ref (e.g., \"MIN-630\", \"#42\")\n */\n getIssue(id: string): Promise<Issue>;\n\n /**\n * Update an existing issue\n */\n updateIssue(id: string, update: IssueUpdate): Promise<Issue>;\n\n /**\n * Create a new issue\n */\n createIssue(issue: NewIssue): Promise<Issue>;\n\n /**\n * Get comments on an issue\n */\n getComments(issueId: string): Promise<Comment[]>;\n\n /**\n * Add a comment to an issue\n */\n addComment(issueId: string, body: string): Promise<Comment>;\n\n /**\n * Transition issue to a new state\n */\n transitionIssue(id: string, state: IssueState): Promise<void>;\n\n /**\n * Link a PR/MR to an issue\n */\n linkPR(issueId: string, prUrl: string): Promise<void>;\n}\n\n/**\n * Error thrown when a tracker feature is not implemented\n */\nexport class NotImplementedError extends Error {\n constructor(feature: string) {\n super(`Not implemented: ${feature}`);\n this.name = 'NotImplementedError';\n }\n}\n\n/**\n * Error thrown when an issue is not found\n */\nexport class IssueNotFoundError extends Error {\n constructor(id: string, tracker: TrackerType) {\n super(`Issue not found: ${id} (tracker: ${tracker})`);\n this.name = 'IssueNotFoundError';\n }\n}\n\n/**\n * Error thrown when tracker authentication fails\n */\nexport class TrackerAuthError extends Error {\n constructor(tracker: TrackerType, message: string) {\n super(`Authentication failed for ${tracker}: ${message}`);\n this.name = 'TrackerAuthError';\n }\n}\n","/**\n * Rally Tracker Adapter\n *\n * Implements IssueTracker interface for Broadcom Rally (formerly CA Agile Central).\n * Supports all Rally work item types: User Stories, Defects, Tasks, and Features.\n */\n\nimport { RallyRestApi } from './rally-api.js';\nimport type {\n Issue,\n IssueFilters,\n IssueState,\n IssueTracker,\n IssueUpdate,\n NewIssue,\n Comment,\n TrackerType,\n} from './interface.js';\nimport { IssueNotFoundError, TrackerAuthError, NotImplementedError } from './interface.js';\n\n// Map Rally ScheduleState/State to normalized IssueState.\n// Covers all standard states for User Stories, Defects, Tasks, and Features.\nconst STATE_MAP: Record<string, IssueState> = {\n // User Stories (ScheduleState)\n 'New': 'open',\n 'Idea': 'open',\n 'Defined': 'open',\n 'In-Progress': 'in_progress',\n 'Completed': 'closed',\n 'Accepted': 'closed',\n\n // Defects (State)\n 'Submitted': 'open',\n 'Open': 'in_progress', // \"Open\" defects are actively being worked\n 'Fixed': 'closed',\n 'Closed': 'closed',\n\n // Features / PortfolioItems (State)\n 'Discovering': 'open',\n 'Developing': 'in_progress',\n 'Done': 'closed',\n};\n\n// Rally artifact types we support\ntype RallyArtifactType = 'HierarchicalRequirement' | 'Defect' | 'Task' | 'PortfolioItem/Feature';\n\n/**\n * Type-specific query configuration.\n *\n * Rally WSAPI cannot filter generic Artifact by ScheduleState because not all\n * subtypes have that field. Instead, we query each type separately with its\n * own state field and merge the results. (PAN-168)\n */\ninterface ArtifactTypeQuery {\n /** WSAPI endpoint type (lowercase) */\n type: string;\n /** The state field used for filtering on this artifact type */\n stateField: 'ScheduleState' | 'State';\n /** State values that represent \"closed\" for this type */\n closedStates: string[];\n}\n\nconst QUERYABLE_TYPES: ArtifactTypeQuery[] = [\n { type: 'hierarchicalrequirement', stateField: 'ScheduleState', closedStates: ['Completed', 'Accepted'] },\n { type: 'defect', stateField: 'State', closedStates: ['Closed'] },\n { type: 'task', stateField: 'State', closedStates: ['Completed'] },\n { type: 'portfolioitem/feature', stateField: 'State', closedStates: ['Done'] },\n];\n\nconst FETCH_FIELDS = [\n 'ObjectID',\n 'FormattedID',\n 'Name',\n 'Description',\n 'ScheduleState',\n 'State',\n 'Tags',\n 'Owner',\n 'Priority',\n 'DueDate',\n 'CreationDate',\n 'LastUpdateDate',\n 'Parent',\n 'PortfolioItem',\n '_type',\n];\n\n// Rally priority strings to numbers\nconst PRIORITY_MAP: Record<string, number> = {\n 'Resolve Immediately': 0,\n High: 1,\n Normal: 2,\n Low: 3,\n};\n\n// Reverse priority mapping\nconst REVERSE_PRIORITY_MAP: Record<number, string> = {\n 0: 'Resolve Immediately',\n 1: 'High',\n 2: 'Normal',\n 3: 'Low',\n 4: 'Low',\n};\n\nexport interface RallyConfig {\n apiKey: string;\n server?: string; // Default: rally1.rallydev.com\n workspace?: string; // Rally workspace OID (e.g., \"/workspace/12345\")\n project?: string; // Rally project OID (e.g., \"/project/67890\")\n}\n\nexport class RallyTracker implements IssueTracker {\n readonly name: TrackerType = 'rally' as TrackerType;\n private restApi: RallyRestApi;\n private workspace?: string;\n private project?: string;\n\n constructor(config: RallyConfig) {\n if (!config.apiKey) {\n throw new TrackerAuthError('rally' as TrackerType, 'API key is required');\n }\n\n this.restApi = new RallyRestApi({\n apiKey: config.apiKey,\n server: config.server || 'https://rally1.rallydev.com',\n requestOptions: {\n headers: {\n 'X-RallyIntegrationType': 'Panopticon',\n 'X-RallyIntegrationName': 'Panopticon CLI',\n 'X-RallyIntegrationVendor': 'Mind Your Now',\n 'X-RallyIntegrationVersion': '0.2.0',\n },\n },\n });\n\n this.workspace = config.workspace;\n this.project = config.project;\n }\n\n /**\n * List issues by querying each artifact type separately and merging results.\n *\n * Rally WSAPI cannot apply ScheduleState filters across the generic Artifact\n * endpoint because not all subtypes have that field. We query each type with\n * its own state field, then merge and sort. (PAN-168)\n */\n async listIssues(filters?: IssueFilters): Promise<Issue[]> {\n if (process.env.DEBUG?.includes('rally')) {\n console.debug('[Rally] Query filters:', JSON.stringify(filters));\n }\n\n const limit = filters?.limit ?? 50;\n\n // Extract ObjectID from project ref for explicit query scoping\n // e.g., \"/project/822404704163\" → \"822404704163\"\n let projectObjectId: string | undefined;\n if (this.project) {\n const match = this.project.match(/\\/project\\/(\\d+)/);\n if (match) projectObjectId = match[1];\n }\n\n const queries = QUERYABLE_TYPES.map(async (artifactType) => {\n const queryString = this.buildQueryStringForType(filters, artifactType, projectObjectId);\n\n if (process.env.DEBUG?.includes('rally')) {\n console.debug(`[Rally] ${artifactType.type} query:`, queryString);\n }\n\n const query: any = {\n type: artifactType.type,\n fetch: FETCH_FIELDS,\n limit,\n query: queryString,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n if (this.project) {\n query.project = this.project;\n query.projectScopeDown = true;\n }\n\n try {\n const result = await this.queryRally(query);\n return result.Results.map((artifact: any) => this.normalizeIssue(artifact));\n } catch (error: any) {\n if (error.message?.includes('Unauthorized') || error.message?.includes('401')) {\n throw new TrackerAuthError('rally' as TrackerType, 'Invalid API key or insufficient permissions');\n }\n // Log and skip individual type failures so other types still return\n if (process.env.DEBUG?.includes('rally')) {\n console.debug(`[Rally] Failed to query ${artifactType.type}:`, error.message);\n }\n return [];\n }\n });\n\n const results = await Promise.all(queries);\n const allIssues = results.flat();\n\n // Sort by most recently updated first, then apply overall limit\n allIssues.sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()\n );\n\n return allIssues.slice(0, limit);\n }\n\n async getIssue(id: string): Promise<Issue> {\n try {\n // Rally FormattedIDs look like: US123, DE456, TA789, F012\n const query: any = {\n type: 'artifact',\n fetch: [\n 'FormattedID',\n 'Name',\n 'Description',\n 'ScheduleState',\n 'State',\n 'Tags',\n 'Owner',\n 'Priority',\n 'DueDate',\n 'CreationDate',\n 'LastUpdateDate',\n 'Parent',\n '_type',\n ],\n query: `(FormattedID = \"${id}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n\n return this.normalizeIssue(result.Results[0]);\n } catch (error: any) {\n if (error instanceof IssueNotFoundError) throw error;\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n }\n\n async updateIssue(id: string, update: IssueUpdate): Promise<Issue> {\n const issue = await this.getIssue(id);\n\n // Get the Rally object reference\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', '_type'],\n query: `(FormattedID = \"${id}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(id, 'rally' as TrackerType);\n }\n\n const artifact = result.Results[0];\n const updatePayload: Record<string, unknown> = {};\n\n if (update.title !== undefined) {\n updatePayload.Name = update.title;\n }\n if (update.description !== undefined) {\n updatePayload.Description = update.description;\n }\n if (update.state !== undefined) {\n const artifactType = (artifact._type || '').toLowerCase();\n const kind = artifactType.startsWith('portfolioitem') ? 'feature'\n : artifactType === 'defect' ? 'defect' : 'story';\n const rallyState = this.reverseMapState(update.state, kind);\n if (kind === 'story') {\n updatePayload.ScheduleState = rallyState;\n } else {\n updatePayload.State = rallyState;\n }\n }\n if (update.priority !== undefined) {\n updatePayload.Priority = REVERSE_PRIORITY_MAP[update.priority] || 'Normal';\n }\n if (update.dueDate !== undefined) {\n updatePayload.DueDate = update.dueDate;\n }\n\n if (Object.keys(updatePayload).length > 0) {\n await this.updateRally(artifact._type.toLowerCase(), artifact._ref, updatePayload);\n }\n\n return this.getIssue(id);\n }\n\n async createIssue(newIssue: NewIssue): Promise<Issue> {\n if (!this.project && !newIssue.team) {\n throw new Error('Project is required to create an issue. Set it in config or provide team field.');\n }\n\n const project = newIssue.team || this.project;\n\n // Default to HierarchicalRequirement (User Story) for new issues\n const createPayload: Record<string, unknown> = {\n Name: newIssue.title,\n Description: newIssue.description || '',\n Project: project,\n };\n\n if (newIssue.priority !== undefined) {\n createPayload.Priority = REVERSE_PRIORITY_MAP[newIssue.priority] || 'Normal';\n }\n if (newIssue.dueDate) {\n createPayload.DueDate = newIssue.dueDate;\n }\n if (this.workspace) {\n createPayload.Workspace = this.workspace;\n }\n\n const result = await this.createRally('hierarchicalrequirement', createPayload);\n\n // Fetch the created issue to return normalized format\n return this.getIssue(result.Object.FormattedID);\n }\n\n async getComments(issueId: string): Promise<Comment[]> {\n const issue = await this.getIssue(issueId);\n\n // Get the Rally object to find its Discussion\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', 'Discussion'],\n query: `(FormattedID = \"${issueId}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n return [];\n }\n\n const artifact = result.Results[0];\n if (!artifact.Discussion) {\n return [];\n }\n\n // Query ConversationPosts for this Discussion\n const postsQuery: any = {\n type: 'conversationpost',\n fetch: ['ObjectID', 'Text', 'User', 'CreationDate', 'PostNumber'],\n query: `(Discussion = \"${artifact.Discussion._ref}\")`,\n order: 'PostNumber',\n };\n\n const postsResult = await this.queryRally(postsQuery);\n\n return (postsResult.Results || []).map((post: any) => ({\n id: post.ObjectID,\n issueId,\n body: post.Text || '',\n author: post.User?._refObjectName || 'Unknown',\n createdAt: post.CreationDate,\n updatedAt: post.CreationDate, // Rally doesn't track comment updates separately\n }));\n }\n\n async addComment(issueId: string, body: string): Promise<Comment> {\n // Get the Rally object to find its Discussion\n const query: any = {\n type: 'artifact',\n fetch: ['ObjectID', '_ref', 'Discussion'],\n query: `(FormattedID = \"${issueId}\")`,\n };\n\n if (this.workspace) {\n query.workspace = this.workspace;\n }\n\n const result = await this.queryRally(query);\n if (!result.Results || result.Results.length === 0) {\n throw new IssueNotFoundError(issueId, 'rally' as TrackerType);\n }\n\n const artifact = result.Results[0];\n\n // If no Discussion exists, create one\n let discussionRef = artifact.Discussion?._ref;\n if (!discussionRef) {\n const discussionResult = await this.createRally('conversationpost', {\n Artifact: artifact._ref,\n Text: body,\n });\n\n return {\n id: discussionResult.Object.ObjectID,\n issueId,\n body,\n author: 'Panopticon',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n }\n\n // Add a post to existing Discussion\n const postResult = await this.createRally('conversationpost', {\n Artifact: artifact._ref,\n Text: body,\n });\n\n return {\n id: postResult.Object.ObjectID,\n issueId,\n body,\n author: 'Panopticon',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n }\n\n async transitionIssue(id: string, state: IssueState): Promise<void> {\n await this.updateIssue(id, { state });\n }\n\n async linkPR(issueId: string, prUrl: string): Promise<void> {\n // Add a comment with the PR link\n await this.addComment(issueId, `Linked Pull Request: ${prUrl}`);\n }\n\n // Private helper methods\n\n /**\n * Build a Rally WSAPI query string for a specific artifact type.\n *\n * Each artifact type has its own state field:\n * - HierarchicalRequirement: ScheduleState (Defined, In-Progress, Completed, Accepted)\n * - Defect: State (Submitted, Open, Fixed, Closed)\n * - Task: State (Defined, In-Progress, Completed)\n *\n * Rally WSAPI v2.0 requires binary-nested AND/OR with outer parentheses.\n * (PAN-166, PAN-168)\n */\n private buildQueryStringForType(\n filters: IssueFilters | undefined,\n artifactType: ArtifactTypeQuery,\n projectObjectId?: string,\n ): string {\n const conditions: string[] = [];\n\n // Explicit project scoping — more reliable than WSAPI project param alone\n if (projectObjectId) {\n conditions.push(`(Project.ObjectID = \"${projectObjectId}\")`);\n }\n\n if (filters?.state && !filters.includeClosed) {\n const kind = artifactType.type.startsWith('portfolioitem') ? 'feature'\n : artifactType.type === 'defect' ? 'defect' : 'story';\n const rallyState = this.reverseMapState(filters.state, kind);\n conditions.push(`(${artifactType.stateField} = \"${rallyState}\")`);\n }\n\n if (!filters?.includeClosed) {\n // Exclude closed states for this specific artifact type\n const closedConditions = artifactType.closedStates.map(\n (state) => `(${artifactType.stateField} != \"${state}\")`\n );\n // Rally WSAPI only supports binary AND — nest into pairs\n const closedExpr = closedConditions.reduce(\n (acc, cond) => (acc ? `(${acc} AND ${cond})` : cond),\n '',\n );\n conditions.push(closedExpr);\n }\n\n if (filters?.assignee) {\n conditions.push(`(Owner.Name contains \"${filters.assignee}\")`);\n }\n\n if (filters?.labels && filters.labels.length > 0) {\n const labelConditions = filters.labels.map(\n (label) => `(Tags.Name contains \"${label}\")`\n );\n // Rally WSAPI only supports binary AND — nest into pairs\n const labelExpr = labelConditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, '');\n conditions.push(labelExpr);\n }\n\n if (filters?.query) {\n conditions.push(`((Name contains \"${filters.query}\") OR (Description contains \"${filters.query}\"))`);\n }\n\n // Rally WSAPI only supports binary (expr AND expr) — reduce into nested pairs\n return conditions.reduce((acc, cond) => acc ? `(${acc} AND ${cond})` : cond, '');\n }\n\n private normalizeIssue(rallyArtifact: any): Issue {\n // Determine state from ScheduleState (User Stories, Tasks) or State (Defects, Features)\n // For PortfolioItem/Feature, State is a Rally ref object with Name/_refObjectName, not a string\n const rawStateValue = rallyArtifact.ScheduleState || rallyArtifact.State || 'Defined';\n const stateValue = typeof rawStateValue === 'object' && rawStateValue !== null\n ? (rawStateValue.Name || rawStateValue._refObjectName || 'Defined')\n : rawStateValue;\n const state = this.mapState(stateValue);\n\n // Extract tags — ensure all entries are strings\n const labels: string[] = [];\n if (rallyArtifact.Tags && rallyArtifact.Tags._tagsNameArray) {\n for (const tag of rallyArtifact.Tags._tagsNameArray) {\n if (typeof tag === 'string') {\n labels.push(tag);\n } else if (tag?.Name) {\n labels.push(tag.Name);\n }\n }\n }\n\n // Map priority\n const priority = rallyArtifact.Priority\n ? PRIORITY_MAP[rallyArtifact.Priority] ?? 2\n : undefined;\n\n // Use ObjectID if available, fall back to FormattedID\n const objectId = rallyArtifact.ObjectID || rallyArtifact.FormattedID;\n const artifactType = rallyArtifact._type || 'artifact';\n\n // Build URL - Rally's web UI detail path\n const baseUrl = this.restApi.server.replace('/slm/webservice/', '');\n const url = `${baseUrl}/#/detail/${artifactType.toLowerCase()}/${objectId}`;\n\n // Resolve parent reference.\n // For User Stories, PortfolioItem links to the parent Feature (F-prefixed),\n // while Parent links to a parent Story in the hierarchy. Prefer PortfolioItem\n // so that stories are correctly grouped under their Feature. (PAN-202)\n let parentRef: string | undefined;\n if (rallyArtifact.PortfolioItem) {\n if (rallyArtifact.PortfolioItem.FormattedID) {\n parentRef = rallyArtifact.PortfolioItem.FormattedID;\n } else if (rallyArtifact.PortfolioItem._refObjectName) {\n parentRef = rallyArtifact.PortfolioItem._refObjectName;\n }\n } else if (rallyArtifact.Parent) {\n if (rallyArtifact.Parent.FormattedID) {\n parentRef = rallyArtifact.Parent.FormattedID;\n } else if (rallyArtifact.Parent._refObjectName) {\n parentRef = rallyArtifact.Parent._refObjectName;\n }\n }\n\n return {\n id: String(objectId),\n ref: rallyArtifact.FormattedID,\n title: rallyArtifact.Name || '',\n description: rallyArtifact.Description || '',\n state,\n labels,\n assignee: rallyArtifact.Owner?._refObjectName,\n url,\n tracker: 'rally' as TrackerType,\n priority,\n dueDate: rallyArtifact.DueDate,\n createdAt: rallyArtifact.CreationDate,\n updatedAt: rallyArtifact.LastUpdateDate,\n parentRef,\n artifactType,\n rawState: stateValue,\n };\n }\n\n private mapState(rallyState: string): IssueState {\n return STATE_MAP[rallyState] ?? 'open';\n }\n\n private reverseMapState(state: IssueState, kind: 'story' | 'defect' | 'feature' = 'story'): string {\n if (kind === 'feature') {\n // Features / PortfolioItems use State: Discovering, Developing, Done\n switch (state) {\n case 'open': return 'Discovering';\n case 'in_progress': return 'Developing';\n case 'closed': return 'Done';\n default: return 'Discovering';\n }\n }\n if (kind === 'defect') {\n // Defects use State: Submitted, Open, Fixed, Closed\n switch (state) {\n case 'open': return 'Submitted';\n case 'in_progress': return 'Open';\n case 'closed': return 'Closed';\n default: return 'Submitted';\n }\n }\n // User Stories / Tasks use ScheduleState: Defined, In-Progress, Completed\n switch (state) {\n case 'open': return 'Defined';\n case 'in_progress': return 'In-Progress';\n case 'closed': return 'Completed';\n default: return 'Defined';\n }\n }\n\n // Rally API wrapper methods\n private async queryRally(queryConfig: any): Promise<any> {\n const result = await this.restApi.query(queryConfig);\n // Extract Results from WSAPI response format\n return {\n Results: result.QueryResult.Results,\n TotalResultCount: result.QueryResult.TotalResultCount,\n };\n }\n\n private async createRally(type: string, data: any): Promise<any> {\n const result = await this.restApi.create({\n type,\n data,\n fetch: ['FormattedID', 'ObjectID', '_ref'],\n });\n // Extract Object from WSAPI response format\n return {\n Object: result.CreateResult.Object,\n };\n }\n\n private async updateRally(type: string, ref: string, data: any): Promise<any> {\n const result = await this.restApi.update({\n type,\n ref,\n data,\n fetch: ['FormattedID', 'ObjectID'],\n });\n // Extract Object from WSAPI response format\n return {\n Object: result.OperationResult.Object,\n };\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm,
|
|
3
|
+
init_esm_shims
|
|
4
|
+
} from "./chunk-ZHC57RCV.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/workspace-config.ts
|
|
7
|
+
function replacePlaceholders(template, placeholders) {
|
|
8
|
+
let result = template;
|
|
9
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
10
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
function getDefaultWorkspaceConfig() {
|
|
15
|
+
return {
|
|
16
|
+
type: "monorepo",
|
|
17
|
+
workspaces_dir: "workspaces"
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
var init_workspace_config = __esm({
|
|
21
|
+
"src/lib/workspace-config.ts"() {
|
|
22
|
+
"use strict";
|
|
23
|
+
init_esm_shims();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
replacePlaceholders,
|
|
29
|
+
getDefaultWorkspaceConfig,
|
|
30
|
+
init_workspace_config
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=chunk-CWELWPWQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/workspace-config.ts"],"sourcesContent":["/**\n * Workspace Configuration Types\n *\n * Defines the schema for project workspace configuration in projects.yaml\n */\n\nexport interface RepoConfig {\n /** Name of the repo in the workspace (e.g., 'fe', 'api') */\n name: string;\n /** Path to source repo relative to project root */\n path: string;\n /** Branch prefix for feature branches (default: 'feature/') */\n branch_prefix?: string;\n /** Default branch to create feature branches from (default: 'main') */\n default_branch?: string;\n}\n\nexport interface DnsConfig {\n /** Base domain (e.g., 'myn.test') */\n domain: string;\n /**\n * DNS entry patterns. Supports placeholders:\n * - {{FEATURE_FOLDER}}: e.g., 'feature-min-123'\n * - {{FEATURE_NAME}}: e.g., 'min-123'\n * - {{DOMAIN}}: the domain value\n */\n entries: string[];\n /** How to sync DNS: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' */\n sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq';\n}\n\nexport interface PortConfig {\n /** Port range [start, end] */\n range: [number, number];\n}\n\nexport interface DockerConfig {\n /** Path to Traefik compose file (relative to project root) */\n traefik?: string;\n /** Path to devcontainer template directory */\n compose_template?: string;\n}\n\nexport interface AgentTemplateConfig {\n /** Path to agent template directory */\n template_dir: string;\n /** Files to process with placeholder replacement */\n templates?: Array<{\n source: string;\n target: string;\n }>;\n /** Directories to copy from project template into workspace */\n copy_dirs?: string[];\n /** @deprecated Use copy_dirs instead */\n symlinks?: string[];\n}\n\nexport interface EnvConfig {\n /** Environment variable template with placeholders */\n template?: string;\n /** Additional env vars from secrets */\n secrets_file?: string;\n}\n\nexport interface ServiceConfig {\n /** Service name (e.g., 'api', 'frontend') */\n name: string;\n /** Path relative to workspace (e.g., 'api', 'fe') */\n path: string;\n /** Command to start the service natively (e.g., './run-dev.sh', 'pnpm start') */\n start_command: string;\n /** Command to start inside Docker container (if different) */\n docker_command?: string;\n /** Health check URL pattern (supports placeholders) */\n health_url?: string;\n /** Port the service runs on */\n port?: number;\n}\n\nexport interface TestConfig {\n /** Test type: 'maven' | 'vitest' | 'playwright' | 'jest' | 'pytest' | 'cargo' */\n type: string;\n /** Path to test directory (relative to workspace) */\n path: string;\n /** Command to run tests */\n command: string;\n /** Run inside container for feature workspaces */\n container?: boolean;\n /** Container name pattern (uses {{FEATURE_FOLDER}}) */\n container_name?: string;\n /** Additional environment variables */\n env?: Record<string, string>;\n}\n\nexport interface DatabaseConfig {\n /** Path to seed file for database initialization */\n seed_file?: string;\n /** Command to run after loading seed (e.g., sanitization script) */\n seed_command?: string;\n /** Command to create snapshots from external source (e.g., kubectl exec pg_dump) */\n snapshot_command?: string;\n /** External database connection for direct access */\n external_db?: {\n host: string;\n port?: number;\n database: string;\n user?: string;\n /** Environment variable name containing password */\n password_env?: string;\n };\n /** Container name pattern (supports {{PROJECT}} placeholder) */\n container_name?: string;\n /** Migration tool configuration */\n migrations?: {\n type: 'flyway' | 'liquibase' | 'prisma' | 'typeorm' | 'custom';\n path?: string;\n command?: string;\n };\n}\n\nexport interface TunnelHostname {\n /** Hostname pattern (supports {{FEATURE_FOLDER}} etc.) e.g., \"api-{{FEATURE_FOLDER}}.mindyournow.com\" */\n pattern: string;\n /** HTTP Host header for Traefik routing e.g., \"api-{{FEATURE_FOLDER}}.myn.localhost\" */\n http_host_header?: string;\n /** Skip TLS verification for local dev (default: true) */\n no_tls_verify?: boolean;\n}\n\nexport interface TunnelConfig {\n /** Tunnel provider (currently only Cloudflare) */\n provider: 'cloudflare';\n /** Cloudflare tunnel ID */\n tunnel_id: string;\n /** Cloudflare account ID */\n account_id: string;\n /** Cloudflare zone ID */\n zone_id: string;\n /** Path to credentials file (cert.pem) containing API token */\n credentials_file: string;\n /** Service target for ingress rules (e.g., \"https://localhost\") */\n service_target: string;\n /** Hostnames to create ingress rules + DNS records for */\n hostnames: TunnelHostname[];\n}\n\nexport interface HumeConfig {\n /** Env var name containing the Hume API key (default: HUME_API_KEY) */\n api_key_env?: string;\n /** Config ID of the production/template config to clone from */\n template_config_id: string;\n /** Config name pattern for workspaces (supports placeholders) */\n name_pattern: string;\n /** BYOLLM callback URL pattern (supports placeholders) */\n byollm_url_pattern: string;\n}\n\nexport interface WorkspaceConfig {\n /** Workspace type: 'polyrepo' (multiple git repos) or 'monorepo' (single repo, default) */\n type?: 'polyrepo' | 'monorepo';\n /** Where to create workspaces (relative to project path) */\n workspaces_dir?: string;\n /** Default branch for all repos (default: 'main'). Can be overridden per-repo. */\n default_branch?: string;\n /** Git repositories to include (for polyrepo) */\n repos?: RepoConfig[];\n /** DNS configuration */\n dns?: DnsConfig;\n /** Port assignments for services */\n ports?: Record<string, PortConfig>;\n /** Docker configuration */\n docker?: DockerConfig;\n /** Database seeding configuration */\n database?: DatabaseConfig;\n /** Agent configuration templates */\n agent?: AgentTemplateConfig;\n /** Environment variables */\n env?: EnvConfig;\n /** Service definitions for startup commands */\n services?: ServiceConfig[];\n /** Cloudflare tunnel configuration for external access */\n tunnel?: TunnelConfig;\n /** Hume EVI config management for workspace lifecycle */\n hume?: HumeConfig;\n /** PRD directory path (relative to project path, default: 'docs/prds') */\n prdDir?: string;\n}\n\nexport interface TestsConfig {\n [name: string]: TestConfig;\n}\n\nexport interface ProjectConfig {\n name: string;\n path: string;\n linear_team?: string;\n github_repo?: string;\n gitlab_repo?: string;\n\n /** Workspace configuration */\n workspace?: WorkspaceConfig;\n\n /** Test configuration */\n tests?: TestsConfig;\n\n /** Issue routing rules */\n issue_routing?: Array<{\n labels?: string[];\n path: string;\n default?: boolean;\n }>;\n\n /** Legacy: custom workspace command (deprecated, use workspace config) */\n workspace_command?: string;\n workspace_remove_command?: string;\n}\n\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Template placeholders that can be used in configuration\n */\nexport interface TemplatePlaceholders {\n FEATURE_NAME: string; // e.g., 'min-123'\n FEATURE_FOLDER: string; // e.g., 'feature-min-123'\n BRANCH_NAME: string; // e.g., 'feature/min-123'\n COMPOSE_PROJECT: string; // e.g., 'myn-feature-min-123'\n DOMAIN: string; // e.g., 'myn.test'\n PROJECT_NAME: string; // e.g., 'myn'\n PROJECT_PATH: string; // e.g., '/home/user/projects/myn'\n WORKSPACE_PATH: string; // e.g., '/home/user/projects/myn/workspaces/feature-min-123'\n HOME?: string; // e.g., '/home/user' (for docker-compose path sanitization)\n}\n\n/**\n * Replace template placeholders in a string\n */\nexport function replacePlaceholders(template: string, placeholders: TemplatePlaceholders): string {\n let result = template;\n for (const [key, value] of Object.entries(placeholders)) {\n result = result.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n }\n return result;\n}\n\n/**\n * Get default workspace config for a monorepo project\n */\nexport function getDefaultWorkspaceConfig(): WorkspaceConfig {\n return {\n type: 'monorepo',\n workspaces_dir: 'workspaces',\n };\n}\n\n/**\n * Service templates for common project types\n * These provide sensible defaults that can be overridden\n */\nexport const SERVICE_TEMPLATES: Record<string, Partial<ServiceConfig>> = {\n // Frontend frameworks\n 'react': {\n start_command: 'npm start',\n docker_command: 'npm start',\n port: 3000,\n },\n 'react-vite': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 5173,\n },\n 'react-pnpm': {\n start_command: 'pnpm start',\n docker_command: 'pnpm start',\n port: 3000,\n },\n 'nextjs': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 3000,\n },\n 'vue': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 5173,\n },\n 'angular': {\n start_command: 'ng serve',\n docker_command: 'ng serve',\n port: 4200,\n },\n\n // Backend frameworks\n 'spring-boot-maven': {\n start_command: './mvnw spring-boot:run',\n docker_command: './mvnw spring-boot:run',\n port: 8080,\n },\n 'spring-boot-gradle': {\n start_command: './gradlew bootRun',\n docker_command: './gradlew bootRun',\n port: 8080,\n },\n 'express': {\n start_command: 'npm start',\n docker_command: 'npm start',\n port: 3000,\n },\n 'fastapi': {\n start_command: 'uvicorn main:app --reload',\n docker_command: 'uvicorn main:app --host 0.0.0.0 --reload',\n port: 8000,\n },\n 'django': {\n start_command: 'python manage.py runserver',\n docker_command: 'python manage.py runserver 0.0.0.0:8000',\n port: 8000,\n },\n 'rails': {\n start_command: 'rails server',\n docker_command: 'rails server -b 0.0.0.0',\n port: 3000,\n },\n 'go': {\n start_command: 'go run .',\n docker_command: 'go run .',\n port: 8080,\n },\n 'rust-cargo': {\n start_command: 'cargo run',\n docker_command: 'cargo run',\n port: 8080,\n },\n};\n\n/**\n * Get service config from template with overrides\n */\nexport function getServiceFromTemplate(\n templateName: string,\n overrides: Partial<ServiceConfig>\n): ServiceConfig {\n const template = SERVICE_TEMPLATES[templateName] || {};\n return {\n name: overrides.name || templateName,\n path: overrides.path || '.',\n start_command: overrides.start_command || template.start_command || 'npm start',\n docker_command: overrides.docker_command || template.docker_command,\n health_url: overrides.health_url,\n port: overrides.port || template.port,\n };\n}\n"],"mappings":";;;;;;AA+OO,SAAS,oBAAoB,UAAkB,cAA4C;AAChG,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,aAAS,OAAO,QAAQ,IAAI,OAAO,SAAS,GAAG,UAAU,GAAG,GAAG,KAAK;AAAA,EACtE;AACA,SAAO;AACT;AAKO,SAAS,4BAA6C;AAC3D,SAAO;AAAA,IACL,MAAM;AAAA,IACN,gBAAgB;AAAA,EAClB;AACF;AA/PA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PANOPTICON_HOME,
|
|
3
|
+
init_paths
|
|
4
|
+
} from "./chunk-ZTFNYOC7.js";
|
|
5
|
+
import {
|
|
6
|
+
__esm,
|
|
7
|
+
init_esm_shims
|
|
8
|
+
} from "./chunk-ZHC57RCV.js";
|
|
9
|
+
|
|
10
|
+
// src/lib/tldr-daemon.ts
|
|
11
|
+
import { exec } from "child_process";
|
|
12
|
+
import { promisify } from "util";
|
|
13
|
+
import { existsSync, writeFileSync, readFileSync, mkdirSync, unlinkSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
import { createHash } from "crypto";
|
|
16
|
+
function getTldrMetrics(workspacePath, sinceCheckpoint = false) {
|
|
17
|
+
const tldrDir = join(workspacePath, ".tldr");
|
|
18
|
+
const interceptionsLog = join(tldrDir, "interceptions.log");
|
|
19
|
+
const bypassesLog = join(tldrDir, "bypasses.log");
|
|
20
|
+
const checkpointFile = join(tldrDir, "metrics-checkpoint.json");
|
|
21
|
+
let interceptionsStartLine = 0;
|
|
22
|
+
let bypassesStartLine = 0;
|
|
23
|
+
if (sinceCheckpoint && existsSync(checkpointFile)) {
|
|
24
|
+
try {
|
|
25
|
+
const checkpoint = JSON.parse(readFileSync(checkpointFile, "utf-8"));
|
|
26
|
+
interceptionsStartLine = checkpoint.interceptionsLine || 0;
|
|
27
|
+
bypassesStartLine = checkpoint.bypassesLine || 0;
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const allInterceptionLines = existsSync(interceptionsLog) ? readFileSync(interceptionsLog, "utf-8").split("\n").filter((l) => l.trim()) : [];
|
|
32
|
+
const newInterceptions = allInterceptionLines.slice(interceptionsStartLine);
|
|
33
|
+
let estimatedTokensSaved = 0;
|
|
34
|
+
const filesAnalyzed = [];
|
|
35
|
+
for (const line of newInterceptions) {
|
|
36
|
+
const parts = line.trim().split(" ");
|
|
37
|
+
if (parts.length >= 3) {
|
|
38
|
+
const fileSizeBytes = parseInt(parts[1], 10) || 0;
|
|
39
|
+
const relPath = parts.slice(2).join(" ");
|
|
40
|
+
const fullTokens = Math.round(fileSizeBytes / 4);
|
|
41
|
+
estimatedTokensSaved += Math.max(0, fullTokens - 1e3);
|
|
42
|
+
if (relPath && !filesAnalyzed.includes(relPath)) {
|
|
43
|
+
filesAnalyzed.push(relPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const allBypassLines = existsSync(bypassesLog) ? readFileSync(bypassesLog, "utf-8").split("\n").filter((l) => l.trim()) : [];
|
|
48
|
+
const newBypasses = allBypassLines.slice(bypassesStartLine);
|
|
49
|
+
const bypassReasons = {};
|
|
50
|
+
for (const line of newBypasses) {
|
|
51
|
+
const parts = line.trim().split(" ");
|
|
52
|
+
if (parts.length >= 2) {
|
|
53
|
+
const reason = parts[1];
|
|
54
|
+
bypassReasons[reason] = (bypassReasons[reason] || 0) + 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
interceptions: newInterceptions.length,
|
|
59
|
+
bypasses: newBypasses.length,
|
|
60
|
+
estimatedTokensSaved,
|
|
61
|
+
filesAnalyzed,
|
|
62
|
+
bypassReasons
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function captureTldrMetrics(workspacePath) {
|
|
66
|
+
const tldrDir = join(workspacePath, ".tldr");
|
|
67
|
+
if (!existsSync(tldrDir)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const metrics = getTldrMetrics(workspacePath, true);
|
|
71
|
+
const interceptionsLog = join(tldrDir, "interceptions.log");
|
|
72
|
+
const bypassesLog = join(tldrDir, "bypasses.log");
|
|
73
|
+
const checkpointFile = join(tldrDir, "metrics-checkpoint.json");
|
|
74
|
+
const interceptionsTotal = existsSync(interceptionsLog) ? readFileSync(interceptionsLog, "utf-8").split("\n").filter((l) => l.trim()).length : 0;
|
|
75
|
+
const bypassesTotal = existsSync(bypassesLog) ? readFileSync(bypassesLog, "utf-8").split("\n").filter((l) => l.trim()).length : 0;
|
|
76
|
+
const checkpoint = {
|
|
77
|
+
interceptionsLine: interceptionsTotal,
|
|
78
|
+
bypassesLine: bypassesTotal,
|
|
79
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
writeFileSync(checkpointFile, JSON.stringify(checkpoint, null, 2), "utf-8");
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
return metrics;
|
|
86
|
+
}
|
|
87
|
+
function ensureTldrStateDir() {
|
|
88
|
+
if (!existsSync(TLDR_STATE_DIR)) {
|
|
89
|
+
mkdirSync(TLDR_STATE_DIR, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function hashWorkspacePath(path) {
|
|
93
|
+
return createHash("sha256").update(path).digest("hex").substring(0, 16);
|
|
94
|
+
}
|
|
95
|
+
function getStateFilePath(workspacePath) {
|
|
96
|
+
ensureTldrStateDir();
|
|
97
|
+
const hash = hashWorkspacePath(workspacePath);
|
|
98
|
+
const stateDir = join(TLDR_STATE_DIR, hash);
|
|
99
|
+
if (!existsSync(stateDir)) {
|
|
100
|
+
mkdirSync(stateDir, { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
return join(stateDir, "daemon.json");
|
|
103
|
+
}
|
|
104
|
+
function writeStateFile(workspacePath, venvPath, running, pid) {
|
|
105
|
+
try {
|
|
106
|
+
const stateFile = getStateFilePath(workspacePath);
|
|
107
|
+
if (running) {
|
|
108
|
+
const state = {
|
|
109
|
+
running: true,
|
|
110
|
+
pid: pid || process.pid,
|
|
111
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
112
|
+
workspacePath,
|
|
113
|
+
venvPath
|
|
114
|
+
};
|
|
115
|
+
writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
116
|
+
} else {
|
|
117
|
+
if (existsSync(stateFile)) {
|
|
118
|
+
unlinkSync(stateFile);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.warn("Failed to write TLDR daemon state file:", error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function readStateFile(workspacePath) {
|
|
126
|
+
try {
|
|
127
|
+
const stateFile = getStateFilePath(workspacePath);
|
|
128
|
+
if (!existsSync(stateFile)) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const data = JSON.parse(readFileSync(stateFile, "utf-8"));
|
|
132
|
+
if (data.pid) {
|
|
133
|
+
try {
|
|
134
|
+
process.kill(data.pid, 0);
|
|
135
|
+
return data;
|
|
136
|
+
} catch {
|
|
137
|
+
unlinkSync(stateFile);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return data;
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function getTldrDaemonService(workspacePath, venvPath) {
|
|
147
|
+
const existing = daemonRegistry.get(workspacePath);
|
|
148
|
+
if (existing) {
|
|
149
|
+
return existing;
|
|
150
|
+
}
|
|
151
|
+
const service = new TldrDaemonService(workspacePath, venvPath);
|
|
152
|
+
daemonRegistry.set(workspacePath, service);
|
|
153
|
+
return service;
|
|
154
|
+
}
|
|
155
|
+
function removeTldrDaemonService(workspacePath) {
|
|
156
|
+
daemonRegistry.delete(workspacePath);
|
|
157
|
+
}
|
|
158
|
+
function listTldrDaemonServices() {
|
|
159
|
+
return Array.from(daemonRegistry.values());
|
|
160
|
+
}
|
|
161
|
+
var execAsync, TLDR_STATE_DIR, TldrDaemonService, daemonRegistry;
|
|
162
|
+
var init_tldr_daemon = __esm({
|
|
163
|
+
"src/lib/tldr-daemon.ts"() {
|
|
164
|
+
init_esm_shims();
|
|
165
|
+
init_paths();
|
|
166
|
+
execAsync = promisify(exec);
|
|
167
|
+
TLDR_STATE_DIR = join(PANOPTICON_HOME, "tldr");
|
|
168
|
+
TldrDaemonService = class {
|
|
169
|
+
workspacePath;
|
|
170
|
+
venvPath;
|
|
171
|
+
/**
|
|
172
|
+
* Create a new TLDR daemon service for a workspace
|
|
173
|
+
*
|
|
174
|
+
* @param workspacePath - Path to the workspace (project root or workspace directory)
|
|
175
|
+
* @param venvPath - Path to the Python venv containing llm-tldr
|
|
176
|
+
*/
|
|
177
|
+
constructor(workspacePath, venvPath) {
|
|
178
|
+
this.workspacePath = workspacePath;
|
|
179
|
+
this.venvPath = venvPath;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Start the TLDR daemon
|
|
183
|
+
*
|
|
184
|
+
* @param background - Run daemon in background (default: true)
|
|
185
|
+
*/
|
|
186
|
+
async start(background = true) {
|
|
187
|
+
const currentState = readStateFile(this.workspacePath);
|
|
188
|
+
if (currentState?.running) {
|
|
189
|
+
console.warn(`TLDR daemon already running for ${this.workspacePath} (PID: ${currentState.pid})`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const tldrBin = join(this.venvPath, "bin", "tldr");
|
|
193
|
+
if (!existsSync(tldrBin)) {
|
|
194
|
+
throw new Error(`tldr binary not found at ${tldrBin}. Ensure llm-tldr is installed in the venv.`);
|
|
195
|
+
}
|
|
196
|
+
console.log(`Starting TLDR daemon for ${this.workspacePath}...`);
|
|
197
|
+
try {
|
|
198
|
+
const cmd = background ? `cd "${this.workspacePath}" && "${tldrBin}" daemon start --project "${this.workspacePath}" >/dev/null 2>&1 &` : `cd "${this.workspacePath}" && "${tldrBin}" daemon start --project "${this.workspacePath}"`;
|
|
199
|
+
const { stdout, stderr } = await execAsync(cmd);
|
|
200
|
+
if (stderr && !stderr.includes("started")) {
|
|
201
|
+
console.warn(`TLDR daemon start warning: ${stderr}`);
|
|
202
|
+
}
|
|
203
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
204
|
+
let pid;
|
|
205
|
+
try {
|
|
206
|
+
const statusResult = await execAsync(`cd "${this.workspacePath}" && "${tldrBin}" daemon status`);
|
|
207
|
+
const pidMatch = statusResult.stdout.match(/PID[:\s]+(\d+)/i);
|
|
208
|
+
if (pidMatch) {
|
|
209
|
+
pid = parseInt(pidMatch[1]);
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
writeStateFile(this.workspacePath, this.venvPath, true, pid);
|
|
214
|
+
console.log(`\u2713 TLDR daemon started for ${this.workspacePath}${pid ? ` (PID: ${pid})` : ""}`);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
217
|
+
throw new Error(`Failed to start TLDR daemon: ${errorMessage}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Stop the TLDR daemon
|
|
222
|
+
*/
|
|
223
|
+
async stop() {
|
|
224
|
+
const currentState = readStateFile(this.workspacePath);
|
|
225
|
+
if (!currentState?.running) {
|
|
226
|
+
console.warn(`TLDR daemon not running for ${this.workspacePath}`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const tldrBin = join(this.venvPath, "bin", "tldr");
|
|
230
|
+
if (!existsSync(tldrBin)) {
|
|
231
|
+
console.warn(`tldr binary not found at ${tldrBin}, cleaning up state file`);
|
|
232
|
+
writeStateFile(this.workspacePath, this.venvPath, false);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
console.log(`Stopping TLDR daemon for ${this.workspacePath}...`);
|
|
236
|
+
try {
|
|
237
|
+
await execAsync(`cd "${this.workspacePath}" && "${tldrBin}" daemon stop`);
|
|
238
|
+
writeStateFile(this.workspacePath, this.venvPath, false);
|
|
239
|
+
console.log(`\u2713 TLDR daemon stopped for ${this.workspacePath}`);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (currentState.pid) {
|
|
242
|
+
try {
|
|
243
|
+
process.kill(currentState.pid, "SIGTERM");
|
|
244
|
+
console.log(`\u2713 Forcefully stopped TLDR daemon (PID: ${currentState.pid})`);
|
|
245
|
+
} catch (killError) {
|
|
246
|
+
console.warn(`Failed to kill TLDR daemon process: ${killError}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
writeStateFile(this.workspacePath, this.venvPath, false);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get daemon status
|
|
254
|
+
*/
|
|
255
|
+
async getStatus() {
|
|
256
|
+
const state = readStateFile(this.workspacePath);
|
|
257
|
+
if (!state?.running) {
|
|
258
|
+
return {
|
|
259
|
+
running: false,
|
|
260
|
+
workspacePath: this.workspacePath,
|
|
261
|
+
venvPath: this.venvPath,
|
|
262
|
+
healthy: false
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const healthy = await this.checkHealth();
|
|
266
|
+
return {
|
|
267
|
+
running: true,
|
|
268
|
+
pid: state.pid,
|
|
269
|
+
startedAt: state.startedAt ? new Date(state.startedAt) : void 0,
|
|
270
|
+
workspacePath: this.workspacePath,
|
|
271
|
+
venvPath: this.venvPath,
|
|
272
|
+
healthy
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Check if daemon is healthy (can respond to status queries)
|
|
277
|
+
*/
|
|
278
|
+
async checkHealth() {
|
|
279
|
+
const tldrBin = join(this.venvPath, "bin", "tldr");
|
|
280
|
+
if (!existsSync(tldrBin)) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
await execAsync(`cd "${this.workspacePath}" && "${tldrBin}" daemon status`, { timeout: 3e3 });
|
|
285
|
+
return true;
|
|
286
|
+
} catch {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Restart the daemon
|
|
292
|
+
*/
|
|
293
|
+
async restart() {
|
|
294
|
+
console.log(`Restarting TLDR daemon for ${this.workspacePath}...`);
|
|
295
|
+
await this.stop();
|
|
296
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
297
|
+
await this.start();
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Warm the index (trigger initial analysis)
|
|
301
|
+
*
|
|
302
|
+
* @param background - Run in background (default: true)
|
|
303
|
+
*/
|
|
304
|
+
async warm(background = true) {
|
|
305
|
+
const tldrBin = join(this.venvPath, "bin", "tldr");
|
|
306
|
+
if (!existsSync(tldrBin)) {
|
|
307
|
+
throw new Error(`tldr binary not found at ${tldrBin}`);
|
|
308
|
+
}
|
|
309
|
+
console.log(`Warming TLDR index for ${this.workspacePath}...`);
|
|
310
|
+
try {
|
|
311
|
+
const cmd = background ? `cd "${this.workspacePath}" && "${tldrBin}" warm . >/dev/null 2>&1 &` : `cd "${this.workspacePath}" && "${tldrBin}" warm .`;
|
|
312
|
+
await execAsync(cmd);
|
|
313
|
+
console.log(`\u2713 TLDR index warming initiated for ${this.workspacePath}`);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
316
|
+
throw new Error(`Failed to warm TLDR index: ${errorMessage}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Check if daemon is running
|
|
321
|
+
*/
|
|
322
|
+
isRunning() {
|
|
323
|
+
const state = readStateFile(this.workspacePath);
|
|
324
|
+
return state?.running ?? false;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get workspace path
|
|
328
|
+
*/
|
|
329
|
+
getWorkspacePath() {
|
|
330
|
+
return this.workspacePath;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get venv path
|
|
334
|
+
*/
|
|
335
|
+
getVenvPath() {
|
|
336
|
+
return this.venvPath;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
daemonRegistry = /* @__PURE__ */ new Map();
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
export {
|
|
344
|
+
getTldrMetrics,
|
|
345
|
+
captureTldrMetrics,
|
|
346
|
+
TldrDaemonService,
|
|
347
|
+
getTldrDaemonService,
|
|
348
|
+
removeTldrDaemonService,
|
|
349
|
+
listTldrDaemonServices,
|
|
350
|
+
init_tldr_daemon
|
|
351
|
+
};
|
|
352
|
+
//# sourceMappingURL=chunk-DI7ABPNQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/tldr-daemon.ts"],"sourcesContent":["/**\n * TLDR Daemon Service\n *\n * Manages llm-tldr daemon lifecycle for project root and workspaces.\n * Provides code analysis and summarization for token-efficient agent work.\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { existsSync, writeFileSync, readFileSync, mkdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { createHash } from 'crypto';\nimport { PANOPTICON_HOME } from './paths.js';\n\n// ============================================================================\n// TLDR Session Metrics (PAN-236)\n// ============================================================================\n\n/**\n * Per-session TLDR metrics — delta since last captured cost event.\n *\n * Metrics are file-based, stored in <workspace>/.tldr/:\n * interceptions.log — written by tldr-read-enforcer on each TLDR serve\n * bypasses.log — written by tldr-read-enforcer on each deliberate bypass\n * metrics-checkpoint.json — tracks line offsets for delta (per-cost-event) reporting\n */\nexport interface TldrSessionMetrics {\n interceptions: number; // TLDR summaries served since last checkpoint\n bypasses: number; // TLDR bypasses since last checkpoint\n estimatedTokensSaved: number; // Rough token savings (fullTokens - ~1000 per interception)\n filesAnalyzed: string[]; // Unique files summarized in this window\n bypassReasons: Record<string, number>; // e.g. { \"offset-limit\": 3, \"recently-edited\": 1 }\n}\n\n/** Checkpoint persisted to .tldr/metrics-checkpoint.json */\ninterface TldrMetricsCheckpoint {\n interceptionsLine: number;\n bypassesLine: number;\n capturedAt: string;\n}\n\n/**\n * Read TLDR session metrics for a workspace from log files.\n *\n * @param workspacePath - Workspace root (where .tldr/ lives)\n * @param sinceCheckpoint - Only return metrics since the last captured checkpoint\n */\nexport function getTldrMetrics(workspacePath: string, sinceCheckpoint = false): TldrSessionMetrics {\n const tldrDir = join(workspacePath, '.tldr');\n const interceptionsLog = join(tldrDir, 'interceptions.log');\n const bypassesLog = join(tldrDir, 'bypasses.log');\n const checkpointFile = join(tldrDir, 'metrics-checkpoint.json');\n\n let interceptionsStartLine = 0;\n let bypassesStartLine = 0;\n\n if (sinceCheckpoint && existsSync(checkpointFile)) {\n try {\n const checkpoint = JSON.parse(readFileSync(checkpointFile, 'utf-8')) as TldrMetricsCheckpoint;\n interceptionsStartLine = checkpoint.interceptionsLine || 0;\n bypassesStartLine = checkpoint.bypassesLine || 0;\n } catch { /* start from 0 on parse error */ }\n }\n\n // Parse interceptions log: each line is \"timestamp file_size rel_path\"\n const allInterceptionLines = existsSync(interceptionsLog)\n ? readFileSync(interceptionsLog, 'utf-8').split('\\n').filter(l => l.trim())\n : [];\n const newInterceptions = allInterceptionLines.slice(interceptionsStartLine);\n\n let estimatedTokensSaved = 0;\n const filesAnalyzed: string[] = [];\n\n for (const line of newInterceptions) {\n const parts = line.trim().split(' ');\n if (parts.length >= 3) {\n const fileSizeBytes = parseInt(parts[1], 10) || 0;\n const relPath = parts.slice(2).join(' ');\n // Rough estimate: ~1 token per 4 bytes for code; TLDR summary is ~1000 tokens\n const fullTokens = Math.round(fileSizeBytes / 4);\n estimatedTokensSaved += Math.max(0, fullTokens - 1000);\n if (relPath && !filesAnalyzed.includes(relPath)) {\n filesAnalyzed.push(relPath);\n }\n }\n }\n\n // Parse bypasses log: each line is \"timestamp reason [rel_path]\"\n const allBypassLines = existsSync(bypassesLog)\n ? readFileSync(bypassesLog, 'utf-8').split('\\n').filter(l => l.trim())\n : [];\n const newBypasses = allBypassLines.slice(bypassesStartLine);\n const bypassReasons: Record<string, number> = {};\n\n for (const line of newBypasses) {\n const parts = line.trim().split(' ');\n if (parts.length >= 2) {\n const reason = parts[1];\n bypassReasons[reason] = (bypassReasons[reason] || 0) + 1;\n }\n }\n\n return {\n interceptions: newInterceptions.length,\n bypasses: newBypasses.length,\n estimatedTokensSaved,\n filesAnalyzed,\n bypassReasons,\n };\n}\n\n/**\n * Capture TLDR metrics since the last checkpoint and advance the checkpoint.\n *\n * Call this once per cost event batch to get the delta metrics for that batch,\n * then update the checkpoint so the next call starts from here.\n *\n * @param workspacePath - Workspace root (where .tldr/ lives)\n * @returns Metrics delta since last capture, or null if no .tldr/ directory exists\n */\nexport function captureTldrMetrics(workspacePath: string): TldrSessionMetrics | null {\n const tldrDir = join(workspacePath, '.tldr');\n if (!existsSync(tldrDir)) {\n return null;\n }\n\n const metrics = getTldrMetrics(workspacePath, true);\n\n // Advance checkpoint to current line counts\n const interceptionsLog = join(tldrDir, 'interceptions.log');\n const bypassesLog = join(tldrDir, 'bypasses.log');\n const checkpointFile = join(tldrDir, 'metrics-checkpoint.json');\n\n const interceptionsTotal = existsSync(interceptionsLog)\n ? readFileSync(interceptionsLog, 'utf-8').split('\\n').filter(l => l.trim()).length\n : 0;\n const bypassesTotal = existsSync(bypassesLog)\n ? readFileSync(bypassesLog, 'utf-8').split('\\n').filter(l => l.trim()).length\n : 0;\n\n const checkpoint: TldrMetricsCheckpoint = {\n interceptionsLine: interceptionsTotal,\n bypassesLine: bypassesTotal,\n capturedAt: new Date().toISOString(),\n };\n\n try {\n writeFileSync(checkpointFile, JSON.stringify(checkpoint, null, 2), 'utf-8');\n } catch { /* non-fatal — metrics still returned */ }\n\n return metrics;\n}\n\nconst execAsync = promisify(exec);\n\n/** Directory for TLDR daemon state files */\nconst TLDR_STATE_DIR = join(PANOPTICON_HOME, 'tldr');\n\n/** Ensure TLDR state directory exists */\nfunction ensureTldrStateDir(): void {\n if (!existsSync(TLDR_STATE_DIR)) {\n mkdirSync(TLDR_STATE_DIR, { recursive: true });\n }\n}\n\n/**\n * TLDR daemon state\n */\ninterface TldrDaemonState {\n running: boolean;\n pid?: number;\n startedAt?: string;\n workspacePath: string;\n venvPath: string;\n}\n\n/**\n * TLDR daemon status\n */\nexport interface TldrDaemonStatus {\n running: boolean;\n pid?: number;\n startedAt?: Date;\n workspacePath: string;\n venvPath: string;\n healthy: boolean;\n}\n\n/**\n * Hash workspace path to create a stable identifier\n */\nfunction hashWorkspacePath(path: string): string {\n return createHash('sha256').update(path).digest('hex').substring(0, 16);\n}\n\n/**\n * Get state file path for a workspace\n */\nfunction getStateFilePath(workspacePath: string): string {\n ensureTldrStateDir();\n const hash = hashWorkspacePath(workspacePath);\n const stateDir = join(TLDR_STATE_DIR, hash);\n if (!existsSync(stateDir)) {\n mkdirSync(stateDir, { recursive: true });\n }\n return join(stateDir, 'daemon.json');\n}\n\n/**\n * Write daemon state to file\n */\nfunction writeStateFile(workspacePath: string, venvPath: string, running: boolean, pid?: number): void {\n try {\n const stateFile = getStateFilePath(workspacePath);\n if (running) {\n const state: TldrDaemonState = {\n running: true,\n pid: pid || process.pid,\n startedAt: new Date().toISOString(),\n workspacePath,\n venvPath,\n };\n writeFileSync(stateFile, JSON.stringify(state, null, 2));\n } else {\n if (existsSync(stateFile)) {\n unlinkSync(stateFile);\n }\n }\n } catch (error) {\n console.warn('Failed to write TLDR daemon state file:', error);\n }\n}\n\n/**\n * Read daemon state from file\n */\nfunction readStateFile(workspacePath: string): TldrDaemonState | null {\n try {\n const stateFile = getStateFilePath(workspacePath);\n if (!existsSync(stateFile)) {\n return null;\n }\n\n const data = JSON.parse(readFileSync(stateFile, 'utf-8')) as TldrDaemonState;\n\n // Verify the process is still running\n if (data.pid) {\n try {\n process.kill(data.pid, 0); // Signal 0 checks if process exists\n return data;\n } catch {\n // Process doesn't exist - clean up stale state file\n unlinkSync(stateFile);\n return null;\n }\n }\n\n return data;\n } catch {\n // State file doesn't exist or is corrupted\n return null;\n }\n}\n\n/**\n * TLDR Daemon Service\n *\n * Manages llm-tldr daemons for project root and workspaces.\n */\nexport class TldrDaemonService {\n private workspacePath: string;\n private venvPath: string;\n\n /**\n * Create a new TLDR daemon service for a workspace\n *\n * @param workspacePath - Path to the workspace (project root or workspace directory)\n * @param venvPath - Path to the Python venv containing llm-tldr\n */\n constructor(workspacePath: string, venvPath: string) {\n this.workspacePath = workspacePath;\n this.venvPath = venvPath;\n }\n\n /**\n * Start the TLDR daemon\n *\n * @param background - Run daemon in background (default: true)\n */\n async start(background = true): Promise<void> {\n // Check if daemon is already running\n const currentState = readStateFile(this.workspacePath);\n if (currentState?.running) {\n console.warn(`TLDR daemon already running for ${this.workspacePath} (PID: ${currentState.pid})`);\n return;\n }\n\n // Verify venv and tldr binary exist\n const tldrBin = join(this.venvPath, 'bin', 'tldr');\n if (!existsSync(tldrBin)) {\n throw new Error(`tldr binary not found at ${tldrBin}. Ensure llm-tldr is installed in the venv.`);\n }\n\n console.log(`Starting TLDR daemon for ${this.workspacePath}...`);\n\n try {\n // Start daemon with project path\n const cmd = background\n ? `cd \"${this.workspacePath}\" && \"${tldrBin}\" daemon start --project \"${this.workspacePath}\" >/dev/null 2>&1 &`\n : `cd \"${this.workspacePath}\" && \"${tldrBin}\" daemon start --project \"${this.workspacePath}\"`;\n\n const { stdout, stderr } = await execAsync(cmd);\n\n if (stderr && !stderr.includes('started')) {\n console.warn(`TLDR daemon start warning: ${stderr}`);\n }\n\n // Give daemon a moment to start and write its PID file\n await new Promise(r => setTimeout(r, 500));\n\n // Try to get PID from tldr's status command\n let pid: number | undefined;\n try {\n const statusResult = await execAsync(`cd \"${this.workspacePath}\" && \"${tldrBin}\" daemon status`);\n const pidMatch = statusResult.stdout.match(/PID[:\\s]+(\\d+)/i);\n if (pidMatch) {\n pid = parseInt(pidMatch[1]);\n }\n } catch {\n // Status command failed - daemon might not expose PID\n }\n\n writeStateFile(this.workspacePath, this.venvPath, true, pid);\n console.log(`✓ TLDR daemon started for ${this.workspacePath}${pid ? ` (PID: ${pid})` : ''}`);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to start TLDR daemon: ${errorMessage}`);\n }\n }\n\n /**\n * Stop the TLDR daemon\n */\n async stop(): Promise<void> {\n const currentState = readStateFile(this.workspacePath);\n if (!currentState?.running) {\n console.warn(`TLDR daemon not running for ${this.workspacePath}`);\n return;\n }\n\n const tldrBin = join(this.venvPath, 'bin', 'tldr');\n if (!existsSync(tldrBin)) {\n console.warn(`tldr binary not found at ${tldrBin}, cleaning up state file`);\n writeStateFile(this.workspacePath, this.venvPath, false);\n return;\n }\n\n console.log(`Stopping TLDR daemon for ${this.workspacePath}...`);\n\n try {\n // Stop daemon\n await execAsync(`cd \"${this.workspacePath}\" && \"${tldrBin}\" daemon stop`);\n\n writeStateFile(this.workspacePath, this.venvPath, false);\n console.log(`✓ TLDR daemon stopped for ${this.workspacePath}`);\n } catch (error) {\n // If stop fails, try to kill the process directly\n if (currentState.pid) {\n try {\n process.kill(currentState.pid, 'SIGTERM');\n console.log(`✓ Forcefully stopped TLDR daemon (PID: ${currentState.pid})`);\n } catch (killError) {\n console.warn(`Failed to kill TLDR daemon process: ${killError}`);\n }\n }\n\n // Clean up state file regardless\n writeStateFile(this.workspacePath, this.venvPath, false);\n }\n }\n\n /**\n * Get daemon status\n */\n async getStatus(): Promise<TldrDaemonStatus> {\n const state = readStateFile(this.workspacePath);\n\n if (!state?.running) {\n return {\n running: false,\n workspacePath: this.workspacePath,\n venvPath: this.venvPath,\n healthy: false,\n };\n }\n\n // Check health\n const healthy = await this.checkHealth();\n\n return {\n running: true,\n pid: state.pid,\n startedAt: state.startedAt ? new Date(state.startedAt) : undefined,\n workspacePath: this.workspacePath,\n venvPath: this.venvPath,\n healthy,\n };\n }\n\n /**\n * Check if daemon is healthy (can respond to status queries)\n */\n async checkHealth(): Promise<boolean> {\n const tldrBin = join(this.venvPath, 'bin', 'tldr');\n if (!existsSync(tldrBin)) {\n return false;\n }\n\n try {\n // Try to get daemon status\n await execAsync(`cd \"${this.workspacePath}\" && \"${tldrBin}\" daemon status`, { timeout: 3000 });\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Restart the daemon\n */\n async restart(): Promise<void> {\n console.log(`Restarting TLDR daemon for ${this.workspacePath}...`);\n await this.stop();\n await new Promise(r => setTimeout(r, 1000)); // Wait for cleanup\n await this.start();\n }\n\n /**\n * Warm the index (trigger initial analysis)\n *\n * @param background - Run in background (default: true)\n */\n async warm(background = true): Promise<void> {\n const tldrBin = join(this.venvPath, 'bin', 'tldr');\n if (!existsSync(tldrBin)) {\n throw new Error(`tldr binary not found at ${tldrBin}`);\n }\n\n console.log(`Warming TLDR index for ${this.workspacePath}...`);\n\n try {\n const cmd = background\n ? `cd \"${this.workspacePath}\" && \"${tldrBin}\" warm . >/dev/null 2>&1 &`\n : `cd \"${this.workspacePath}\" && \"${tldrBin}\" warm .`;\n\n await execAsync(cmd);\n console.log(`✓ TLDR index warming initiated for ${this.workspacePath}`);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to warm TLDR index: ${errorMessage}`);\n }\n }\n\n /**\n * Check if daemon is running\n */\n isRunning(): boolean {\n const state = readStateFile(this.workspacePath);\n return state?.running ?? false;\n }\n\n /**\n * Get workspace path\n */\n getWorkspacePath(): string {\n return this.workspacePath;\n }\n\n /**\n * Get venv path\n */\n getVenvPath(): string {\n return this.venvPath;\n }\n}\n\n/**\n * Global registry of TLDR daemon services by workspace path\n */\nconst daemonRegistry = new Map<string, TldrDaemonService>();\n\n/**\n * Get or create a TLDR daemon service for a workspace\n *\n * @param workspacePath - Path to the workspace\n * @param venvPath - Path to the Python venv\n */\nexport function getTldrDaemonService(workspacePath: string, venvPath: string): TldrDaemonService {\n const existing = daemonRegistry.get(workspacePath);\n if (existing) {\n return existing;\n }\n\n const service = new TldrDaemonService(workspacePath, venvPath);\n daemonRegistry.set(workspacePath, service);\n return service;\n}\n\n/**\n * Remove a daemon service from the registry\n *\n * @param workspacePath - Path to the workspace\n */\nexport function removeTldrDaemonService(workspacePath: string): void {\n daemonRegistry.delete(workspacePath);\n}\n\n/**\n * List all registered daemon services\n */\nexport function listTldrDaemonServices(): TldrDaemonService[] {\n return Array.from(daemonRegistry.values());\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CONFIG_FILE,
|
|
3
3
|
init_paths
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZTFNYOC7.js";
|
|
5
5
|
import {
|
|
6
6
|
__esm,
|
|
7
7
|
init_esm_shims
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/lib/config.ts
|
|
11
11
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
12
|
+
import { join, dirname, parse as parsePath } from "path";
|
|
13
|
+
import { homedir } from "os";
|
|
12
14
|
import { parse, stringify } from "@iarna/toml";
|
|
13
15
|
function deepMerge(defaults, overrides) {
|
|
14
16
|
const result = { ...defaults };
|
|
@@ -50,6 +52,29 @@ function getDashboardApiUrl() {
|
|
|
50
52
|
const port = config.dashboard?.api_port || 3011;
|
|
51
53
|
return `http://localhost:${port}`;
|
|
52
54
|
}
|
|
55
|
+
function getDevrootPath() {
|
|
56
|
+
const config = loadConfig();
|
|
57
|
+
const devroot = config.sync?.devroot;
|
|
58
|
+
if (!devroot) return null;
|
|
59
|
+
const resolved = devroot.startsWith("~/") ? join(homedir(), devroot.slice(2)) : devroot;
|
|
60
|
+
if (!existsSync(resolved)) return null;
|
|
61
|
+
return resolved;
|
|
62
|
+
}
|
|
63
|
+
function findDevrootForProject(projectPath) {
|
|
64
|
+
const configured = getDevrootPath();
|
|
65
|
+
if (configured) return configured;
|
|
66
|
+
let dir = projectPath;
|
|
67
|
+
const root = parsePath(dir).root;
|
|
68
|
+
while (dir !== root && dir !== homedir()) {
|
|
69
|
+
const parent = dirname(dir);
|
|
70
|
+
if (parent === dir) break;
|
|
71
|
+
if (existsSync(join(parent, ".claude"))) {
|
|
72
|
+
return parent;
|
|
73
|
+
}
|
|
74
|
+
dir = parent;
|
|
75
|
+
}
|
|
76
|
+
return projectPath;
|
|
77
|
+
}
|
|
53
78
|
var DEFAULT_CONFIG;
|
|
54
79
|
var init_config = __esm({
|
|
55
80
|
"src/lib/config.ts"() {
|
|
@@ -60,10 +85,10 @@ var init_config = __esm({
|
|
|
60
85
|
version: "1.0.0"
|
|
61
86
|
},
|
|
62
87
|
sync: {
|
|
63
|
-
targets: ["claude"],
|
|
64
88
|
backup_before_sync: true,
|
|
65
89
|
auto_sync: false,
|
|
66
|
-
strategy: "symlink"
|
|
90
|
+
strategy: "symlink",
|
|
91
|
+
devroot: "~/Projects"
|
|
67
92
|
},
|
|
68
93
|
trackers: {
|
|
69
94
|
primary: "linear",
|
|
@@ -99,6 +124,8 @@ export {
|
|
|
99
124
|
saveConfig,
|
|
100
125
|
getDefaultConfig,
|
|
101
126
|
getDashboardApiUrl,
|
|
127
|
+
getDevrootPath,
|
|
128
|
+
findDevrootForProject,
|
|
102
129
|
init_config
|
|
103
130
|
};
|
|
104
|
-
//# sourceMappingURL=chunk-
|
|
131
|
+
//# sourceMappingURL=chunk-FQ66DECN.js.map
|