lattice-orchestrator 0.7.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/LICENSE +201 -0
- package/README.md +58 -0
- package/config/logrotate.conf +15 -0
- package/dist/cli-parser.d.ts +11 -0
- package/dist/cli-parser.d.ts.map +1 -0
- package/dist/cli-parser.js +48 -0
- package/dist/cli-parser.js.map +1 -0
- package/dist/lattice-server.d.ts +70 -0
- package/dist/lattice-server.d.ts.map +1 -0
- package/dist/lattice-server.js +969 -0
- package/dist/lattice-server.js.map +1 -0
- package/dist/mcp-server/index.d.ts +3 -0
- package/dist/mcp-server/index.d.ts.map +1 -0
- package/dist/mcp-server/index.js +190 -0
- package/dist/mcp-server/index.js.map +1 -0
- package/dist/mcp-server/lattice-tools.d.ts +15 -0
- package/dist/mcp-server/lattice-tools.d.ts.map +1 -0
- package/dist/mcp-server/lattice-tools.js +366 -0
- package/dist/mcp-server/lattice-tools.js.map +1 -0
- package/dist/middleware/cors-setup.d.ts +7 -0
- package/dist/middleware/cors-setup.d.ts.map +1 -0
- package/dist/middleware/cors-setup.js +8 -0
- package/dist/middleware/cors-setup.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +4 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +27 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/query-parser.d.ts +11 -0
- package/dist/middleware/query-parser.d.ts.map +1 -0
- package/dist/middleware/query-parser.js +68 -0
- package/dist/middleware/query-parser.js.map +1 -0
- package/dist/middleware/request-logger.d.ts +4 -0
- package/dist/middleware/request-logger.d.ts.map +1 -0
- package/dist/middleware/request-logger.js +6 -0
- package/dist/middleware/request-logger.js.map +1 -0
- package/dist/process-daemon/index.d.ts +14 -0
- package/dist/process-daemon/index.d.ts.map +1 -0
- package/dist/process-daemon/index.js +51 -0
- package/dist/process-daemon/index.js.map +1 -0
- package/dist/process-daemon/process-daemon.d.ts +101 -0
- package/dist/process-daemon/process-daemon.d.ts.map +1 -0
- package/dist/process-daemon/process-daemon.js +846 -0
- package/dist/process-daemon/process-daemon.js.map +1 -0
- package/dist/process-daemon/process-manager-client.d.ts +123 -0
- package/dist/process-daemon/process-manager-client.d.ts.map +1 -0
- package/dist/process-daemon/process-manager-client.js +329 -0
- package/dist/process-daemon/process-manager-client.js.map +1 -0
- package/dist/process-daemon/process-manager-interface.d.ts +61 -0
- package/dist/process-daemon/process-manager-interface.d.ts.map +1 -0
- package/dist/process-daemon/process-manager-interface.js +8 -0
- package/dist/process-daemon/process-manager-interface.js.map +1 -0
- package/dist/process-daemon/test-daemon.d.ts +12 -0
- package/dist/process-daemon/test-daemon.d.ts.map +1 -0
- package/dist/process-daemon/test-daemon.js +81 -0
- package/dist/process-daemon/test-daemon.js.map +1 -0
- package/dist/process-daemon/types.d.ts +97 -0
- package/dist/process-daemon/types.d.ts.map +1 -0
- package/dist/process-daemon/types.js +8 -0
- package/dist/process-daemon/types.js.map +1 -0
- package/dist/routes/analysis.routes.d.ts +13 -0
- package/dist/routes/analysis.routes.d.ts.map +1 -0
- package/dist/routes/analysis.routes.js +520 -0
- package/dist/routes/analysis.routes.js.map +1 -0
- package/dist/routes/config.routes.d.ts +4 -0
- package/dist/routes/config.routes.d.ts.map +1 -0
- package/dist/routes/config.routes.js +27 -0
- package/dist/routes/config.routes.js.map +1 -0
- package/dist/routes/conversation.routes.d.ts +43 -0
- package/dist/routes/conversation.routes.d.ts.map +1 -0
- package/dist/routes/conversation.routes.js +79 -0
- package/dist/routes/conversation.routes.js.map +1 -0
- package/dist/routes/filesystem.routes.d.ts +4 -0
- package/dist/routes/filesystem.routes.d.ts.map +1 -0
- package/dist/routes/filesystem.routes.js +86 -0
- package/dist/routes/filesystem.routes.js.map +1 -0
- package/dist/routes/insights.routes.d.ts +17 -0
- package/dist/routes/insights.routes.d.ts.map +1 -0
- package/dist/routes/insights.routes.js +633 -0
- package/dist/routes/insights.routes.js.map +1 -0
- package/dist/routes/lattice.routes.d.ts +10 -0
- package/dist/routes/lattice.routes.d.ts.map +1 -0
- package/dist/routes/lattice.routes.js +123 -0
- package/dist/routes/lattice.routes.js.map +1 -0
- package/dist/routes/license.routes.d.ts +3 -0
- package/dist/routes/license.routes.d.ts.map +1 -0
- package/dist/routes/license.routes.js +95 -0
- package/dist/routes/license.routes.js.map +1 -0
- package/dist/routes/log.routes.d.ts +3 -0
- package/dist/routes/log.routes.d.ts.map +1 -0
- package/dist/routes/log.routes.js +184 -0
- package/dist/routes/log.routes.js.map +1 -0
- package/dist/routes/pending-question.routes.d.ts +9 -0
- package/dist/routes/pending-question.routes.d.ts.map +1 -0
- package/dist/routes/pending-question.routes.js +162 -0
- package/dist/routes/pending-question.routes.js.map +1 -0
- package/dist/routes/permission.routes.d.ts +18 -0
- package/dist/routes/permission.routes.d.ts.map +1 -0
- package/dist/routes/permission.routes.js +370 -0
- package/dist/routes/permission.routes.js.map +1 -0
- package/dist/routes/process-events.routes.d.ts +9 -0
- package/dist/routes/process-events.routes.d.ts.map +1 -0
- package/dist/routes/process-events.routes.js +141 -0
- package/dist/routes/process-events.routes.js.map +1 -0
- package/dist/routes/prototype.routes.d.ts +9 -0
- package/dist/routes/prototype.routes.d.ts.map +1 -0
- package/dist/routes/prototype.routes.js +757 -0
- package/dist/routes/prototype.routes.js.map +1 -0
- package/dist/routes/question.routes.d.ts +8 -0
- package/dist/routes/question.routes.d.ts.map +1 -0
- package/dist/routes/question.routes.js +83 -0
- package/dist/routes/question.routes.js.map +1 -0
- package/dist/routes/session-control.routes.d.ts +29 -0
- package/dist/routes/session-control.routes.d.ts.map +1 -0
- package/dist/routes/session-control.routes.js +455 -0
- package/dist/routes/session-control.routes.js.map +1 -0
- package/dist/routes/session-lifecycle.routes.d.ts +21 -0
- package/dist/routes/session-lifecycle.routes.d.ts.map +1 -0
- package/dist/routes/session-lifecycle.routes.js +256 -0
- package/dist/routes/session-lifecycle.routes.js.map +1 -0
- package/dist/routes/session-query.routes.d.ts +25 -0
- package/dist/routes/session-query.routes.d.ts.map +1 -0
- package/dist/routes/session-query.routes.js +363 -0
- package/dist/routes/session-query.routes.js.map +1 -0
- package/dist/routes/session-stream.routes.d.ts +21 -0
- package/dist/routes/session-stream.routes.d.ts.map +1 -0
- package/dist/routes/session-stream.routes.js +235 -0
- package/dist/routes/session-stream.routes.js.map +1 -0
- package/dist/routes/streaming.routes.d.ts +4 -0
- package/dist/routes/streaming.routes.d.ts.map +1 -0
- package/dist/routes/streaming.routes.js +33 -0
- package/dist/routes/streaming.routes.js.map +1 -0
- package/dist/routes/system.routes.d.ts +7 -0
- package/dist/routes/system.routes.d.ts.map +1 -0
- package/dist/routes/system.routes.js +214 -0
- package/dist/routes/system.routes.js.map +1 -0
- package/dist/routes/walkthrough.routes.d.ts +19 -0
- package/dist/routes/walkthrough.routes.d.ts.map +1 -0
- package/dist/routes/walkthrough.routes.js +688 -0
- package/dist/routes/walkthrough.routes.js.map +1 -0
- package/dist/routes/working-directories.routes.d.ts +4 -0
- package/dist/routes/working-directories.routes.d.ts.map +1 -0
- package/dist/routes/working-directories.routes.js +25 -0
- package/dist/routes/working-directories.routes.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +34 -0
- package/dist/server.js.map +1 -0
- package/dist/services/ToolMetricsService.d.ts +53 -0
- package/dist/services/ToolMetricsService.d.ts.map +1 -0
- package/dist/services/ToolMetricsService.js +230 -0
- package/dist/services/ToolMetricsService.js.map +1 -0
- package/dist/services/claude-router-service.d.ts +19 -0
- package/dist/services/claude-router-service.d.ts.map +1 -0
- package/dist/services/claude-router-service.js +160 -0
- package/dist/services/claude-router-service.js.map +1 -0
- package/dist/services/commands-service.d.ts +20 -0
- package/dist/services/commands-service.d.ts.map +1 -0
- package/dist/services/commands-service.js +115 -0
- package/dist/services/commands-service.js.map +1 -0
- package/dist/services/connection-debug-logger.d.ts +85 -0
- package/dist/services/connection-debug-logger.d.ts.map +1 -0
- package/dist/services/connection-debug-logger.js +221 -0
- package/dist/services/connection-debug-logger.js.map +1 -0
- package/dist/services/debug-log.d.ts +6 -0
- package/dist/services/debug-log.d.ts.map +1 -0
- package/dist/services/debug-log.js +27 -0
- package/dist/services/debug-log.js.map +1 -0
- package/dist/services/gemini-service.d.ts +35 -0
- package/dist/services/gemini-service.d.ts.map +1 -0
- package/dist/services/gemini-service.js +256 -0
- package/dist/services/gemini-service.js.map +1 -0
- package/dist/services/infrastructure/config-service.d.ts +79 -0
- package/dist/services/infrastructure/config-service.d.ts.map +1 -0
- package/dist/services/infrastructure/config-service.js +431 -0
- package/dist/services/infrastructure/config-service.js.map +1 -0
- package/dist/services/infrastructure/cost-tracker.d.ts +112 -0
- package/dist/services/infrastructure/cost-tracker.d.ts.map +1 -0
- package/dist/services/infrastructure/cost-tracker.js +423 -0
- package/dist/services/infrastructure/cost-tracker.js.map +1 -0
- package/dist/services/infrastructure/file-system-service.d.ts +61 -0
- package/dist/services/infrastructure/file-system-service.d.ts.map +1 -0
- package/dist/services/infrastructure/file-system-service.js +348 -0
- package/dist/services/infrastructure/file-system-service.js.map +1 -0
- package/dist/services/infrastructure/log-formatter.d.ts +5 -0
- package/dist/services/infrastructure/log-formatter.d.ts.map +1 -0
- package/dist/services/infrastructure/log-formatter.js +77 -0
- package/dist/services/infrastructure/log-formatter.js.map +1 -0
- package/dist/services/infrastructure/log-stream-buffer.d.ts +11 -0
- package/dist/services/infrastructure/log-stream-buffer.d.ts.map +1 -0
- package/dist/services/infrastructure/log-stream-buffer.js +36 -0
- package/dist/services/infrastructure/log-stream-buffer.js.map +1 -0
- package/dist/services/infrastructure/logger.d.ts +71 -0
- package/dist/services/infrastructure/logger.d.ts.map +1 -0
- package/dist/services/infrastructure/logger.js +215 -0
- package/dist/services/infrastructure/logger.js.map +1 -0
- package/dist/services/infrastructure/service-registry.d.ts +86 -0
- package/dist/services/infrastructure/service-registry.d.ts.map +1 -0
- package/dist/services/infrastructure/service-registry.js +162 -0
- package/dist/services/infrastructure/service-registry.js.map +1 -0
- package/dist/services/infrastructure/stream-manager.d.ts +87 -0
- package/dist/services/infrastructure/stream-manager.d.ts.map +1 -0
- package/dist/services/infrastructure/stream-manager.js +436 -0
- package/dist/services/infrastructure/stream-manager.js.map +1 -0
- package/dist/services/infrastructure/timing.d.ts +72 -0
- package/dist/services/infrastructure/timing.d.ts.map +1 -0
- package/dist/services/infrastructure/timing.js +115 -0
- package/dist/services/infrastructure/timing.js.map +1 -0
- package/dist/services/insights/anthropic-service.d.ts +224 -0
- package/dist/services/insights/anthropic-service.d.ts.map +1 -0
- package/dist/services/insights/anthropic-service.js +1062 -0
- package/dist/services/insights/anthropic-service.js.map +1 -0
- package/dist/services/insights/insight-audit-repository.d.ts +119 -0
- package/dist/services/insights/insight-audit-repository.d.ts.map +1 -0
- package/dist/services/insights/insight-audit-repository.js +242 -0
- package/dist/services/insights/insight-audit-repository.js.map +1 -0
- package/dist/services/insights/insight-queue.d.ts +99 -0
- package/dist/services/insights/insight-queue.d.ts.map +1 -0
- package/dist/services/insights/insight-queue.js +277 -0
- package/dist/services/insights/insight-queue.js.map +1 -0
- package/dist/services/insights/insights-computer.d.ts +132 -0
- package/dist/services/insights/insights-computer.d.ts.map +1 -0
- package/dist/services/insights/insights-computer.js +936 -0
- package/dist/services/insights/insights-computer.js.map +1 -0
- package/dist/services/insights/insights-coordinator.d.ts +165 -0
- package/dist/services/insights/insights-coordinator.d.ts.map +1 -0
- package/dist/services/insights/insights-coordinator.js +1678 -0
- package/dist/services/insights/insights-coordinator.js.map +1 -0
- package/dist/services/insights/insights-event-log.d.ts +196 -0
- package/dist/services/insights/insights-event-log.d.ts.map +1 -0
- package/dist/services/insights/insights-event-log.js +319 -0
- package/dist/services/insights/insights-event-log.js.map +1 -0
- package/dist/services/lattice-service.d.ts +77 -0
- package/dist/services/lattice-service.d.ts.map +1 -0
- package/dist/services/lattice-service.js +195 -0
- package/dist/services/lattice-service.js.map +1 -0
- package/dist/services/license-service.d.ts +69 -0
- package/dist/services/license-service.d.ts.map +1 -0
- package/dist/services/license-service.js +330 -0
- package/dist/services/license-service.js.map +1 -0
- package/dist/services/mcp-config-generator.d.ts +32 -0
- package/dist/services/mcp-config-generator.d.ts.map +1 -0
- package/dist/services/mcp-config-generator.js +126 -0
- package/dist/services/mcp-config-generator.js.map +1 -0
- package/dist/services/message-filter.d.ts +22 -0
- package/dist/services/message-filter.d.ts.map +1 -0
- package/dist/services/message-filter.js +57 -0
- package/dist/services/message-filter.js.map +1 -0
- package/dist/services/notification-service.d.ts +45 -0
- package/dist/services/notification-service.d.ts.map +1 -0
- package/dist/services/notification-service.js +184 -0
- package/dist/services/notification-service.js.map +1 -0
- package/dist/services/pending-question-service.d.ts +97 -0
- package/dist/services/pending-question-service.d.ts.map +1 -0
- package/dist/services/pending-question-service.js +223 -0
- package/dist/services/pending-question-service.js.map +1 -0
- package/dist/services/permission-event-log.d.ts +136 -0
- package/dist/services/permission-event-log.d.ts.map +1 -0
- package/dist/services/permission-event-log.js +252 -0
- package/dist/services/permission-event-log.js.map +1 -0
- package/dist/services/permission-pattern-matcher.d.ts +82 -0
- package/dist/services/permission-pattern-matcher.d.ts.map +1 -0
- package/dist/services/permission-pattern-matcher.js +294 -0
- package/dist/services/permission-pattern-matcher.js.map +1 -0
- package/dist/services/permission-tracker.d.ts +67 -0
- package/dist/services/permission-tracker.d.ts.map +1 -0
- package/dist/services/permission-tracker.js +162 -0
- package/dist/services/permission-tracker.js.map +1 -0
- package/dist/services/process/claude-process-manager.d.ts +142 -0
- package/dist/services/process/claude-process-manager.d.ts.map +1 -0
- package/dist/services/process/claude-process-manager.js +1218 -0
- package/dist/services/process/claude-process-manager.js.map +1 -0
- package/dist/services/process/conversation-status-manager.d.ts +110 -0
- package/dist/services/process/conversation-status-manager.d.ts.map +1 -0
- package/dist/services/process/conversation-status-manager.js +349 -0
- package/dist/services/process/conversation-status-manager.js.map +1 -0
- package/dist/services/process/json-lines-parser.d.ts +19 -0
- package/dist/services/process/json-lines-parser.d.ts.map +1 -0
- package/dist/services/process/json-lines-parser.js +59 -0
- package/dist/services/process/json-lines-parser.js.map +1 -0
- package/dist/services/process/process-event-log.d.ts +263 -0
- package/dist/services/process/process-event-log.d.ts.map +1 -0
- package/dist/services/process/process-event-log.js +509 -0
- package/dist/services/process/process-event-log.js.map +1 -0
- package/dist/services/process/process-manager-factory.d.ts +109 -0
- package/dist/services/process/process-manager-factory.d.ts.map +1 -0
- package/dist/services/process/process-manager-factory.js +338 -0
- package/dist/services/process/process-manager-factory.js.map +1 -0
- package/dist/services/question-tracker.d.ts +51 -0
- package/dist/services/question-tracker.d.ts.map +1 -0
- package/dist/services/question-tracker.js +111 -0
- package/dist/services/question-tracker.js.map +1 -0
- package/dist/services/sessions/claude-history-reader.d.ts +139 -0
- package/dist/services/sessions/claude-history-reader.d.ts.map +1 -0
- package/dist/services/sessions/claude-history-reader.js +864 -0
- package/dist/services/sessions/claude-history-reader.js.map +1 -0
- package/dist/services/sessions/conversation-cache.d.ts +98 -0
- package/dist/services/sessions/conversation-cache.d.ts.map +1 -0
- package/dist/services/sessions/conversation-cache.js +329 -0
- package/dist/services/sessions/conversation-cache.js.map +1 -0
- package/dist/services/sessions/session-activity-watcher.d.ts +67 -0
- package/dist/services/sessions/session-activity-watcher.d.ts.map +1 -0
- package/dist/services/sessions/session-activity-watcher.js +236 -0
- package/dist/services/sessions/session-activity-watcher.js.map +1 -0
- package/dist/services/sessions/session-analysis-service.d.ts +72 -0
- package/dist/services/sessions/session-analysis-service.d.ts.map +1 -0
- package/dist/services/sessions/session-analysis-service.js +373 -0
- package/dist/services/sessions/session-analysis-service.js.map +1 -0
- package/dist/services/sessions/session-branch-service.d.ts +76 -0
- package/dist/services/sessions/session-branch-service.d.ts.map +1 -0
- package/dist/services/sessions/session-branch-service.js +355 -0
- package/dist/services/sessions/session-branch-service.js.map +1 -0
- package/dist/services/sessions/session-info-service.d.ts +455 -0
- package/dist/services/sessions/session-info-service.d.ts.map +1 -0
- package/dist/services/sessions/session-info-service.js +1640 -0
- package/dist/services/sessions/session-info-service.js.map +1 -0
- package/dist/services/sessions/session-marks-repository.d.ts +78 -0
- package/dist/services/sessions/session-marks-repository.d.ts.map +1 -0
- package/dist/services/sessions/session-marks-repository.js +263 -0
- package/dist/services/sessions/session-marks-repository.js.map +1 -0
- package/dist/services/sessions/session-marks-service.d.ts +137 -0
- package/dist/services/sessions/session-marks-service.d.ts.map +1 -0
- package/dist/services/sessions/session-marks-service.js +562 -0
- package/dist/services/sessions/session-marks-service.js.map +1 -0
- package/dist/services/sessions/session-review-service.d.ts +98 -0
- package/dist/services/sessions/session-review-service.d.ts.map +1 -0
- package/dist/services/sessions/session-review-service.js +629 -0
- package/dist/services/sessions/session-review-service.js.map +1 -0
- package/dist/services/sessions/turn-capture-service.d.ts +83 -0
- package/dist/services/sessions/turn-capture-service.d.ts.map +1 -0
- package/dist/services/sessions/turn-capture-service.js +477 -0
- package/dist/services/sessions/turn-capture-service.js.map +1 -0
- package/dist/services/sessions/turn-repository.d.ts +48 -0
- package/dist/services/sessions/turn-repository.d.ts.map +1 -0
- package/dist/services/sessions/turn-repository.js +103 -0
- package/dist/services/sessions/turn-repository.js.map +1 -0
- package/dist/services/walkthrough-service.d.ts +226 -0
- package/dist/services/walkthrough-service.d.ts.map +1 -0
- package/dist/services/walkthrough-service.js +1112 -0
- package/dist/services/walkthrough-service.js.map +1 -0
- package/dist/services/walkthrough-skill-prompt.d.ts +34 -0
- package/dist/services/walkthrough-skill-prompt.d.ts.map +1 -0
- package/dist/services/walkthrough-skill-prompt.js +313 -0
- package/dist/services/walkthrough-skill-prompt.js.map +1 -0
- package/dist/services/web-push-service.d.ts +48 -0
- package/dist/services/web-push-service.d.ts.map +1 -0
- package/dist/services/web-push-service.js +186 -0
- package/dist/services/web-push-service.js.map +1 -0
- package/dist/services/working-directories-service.d.ts +19 -0
- package/dist/services/working-directories-service.d.ts.map +1 -0
- package/dist/services/working-directories-service.js +103 -0
- package/dist/services/working-directories-service.js.map +1 -0
- package/dist/types/config.d.ts +122 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +21 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/express.d.ts +5 -0
- package/dist/types/express.d.ts.map +1 -0
- package/dist/types/express.js +2 -0
- package/dist/types/express.js.map +1 -0
- package/dist/types/index.d.ts +400 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +41 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/insights.d.ts +176 -0
- package/dist/types/insights.d.ts.map +1 -0
- package/dist/types/insights.js +23 -0
- package/dist/types/insights.js.map +1 -0
- package/dist/types/license.d.ts +70 -0
- package/dist/types/license.d.ts.map +1 -0
- package/dist/types/license.js +5 -0
- package/dist/types/license.js.map +1 -0
- package/dist/types/router-config.d.ts +13 -0
- package/dist/types/router-config.d.ts.map +1 -0
- package/dist/types/router-config.js +2 -0
- package/dist/types/router-config.js.map +1 -0
- package/dist/utils/constants.d.ts +26 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +28 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/machine-id.d.ts +7 -0
- package/dist/utils/machine-id.d.ts.map +1 -0
- package/dist/utils/machine-id.js +76 -0
- package/dist/utils/machine-id.js.map +1 -0
- package/dist/utils/server-startup.d.ts +11 -0
- package/dist/utils/server-startup.d.ts.map +1 -0
- package/dist/utils/server-startup.js +9 -0
- package/dist/utils/server-startup.js.map +1 -0
- package/dist/utils/update-check.d.ts +13 -0
- package/dist/utils/update-check.d.ts.map +1 -0
- package/dist/utils/update-check.js +90 -0
- package/dist/utils/update-check.js.map +1 -0
- package/dist/web/assets/ArchivedCardPrototype-S9ifiasa.js +5 -0
- package/dist/web/assets/BannerGallery-B__rJV6F.js +1 -0
- package/dist/web/assets/BannerPrototype-DBKP9Uiu.js +52 -0
- package/dist/web/assets/CodeHikeExperiment-B8jjWAFy.js +15 -0
- package/dist/web/assets/ContextTooltipVariations-DzklAFam.js +1 -0
- package/dist/web/assets/FontShowcasePrototype-KIMEWeP2.js +13 -0
- package/dist/web/assets/GeometricGallery-DddlWhHK.js +1 -0
- package/dist/web/assets/HistoryWalkthroughPrototype-DeniRRdw.js +18 -0
- package/dist/web/assets/InlineWalkthroughPrototype-Csd5r_Hk.js +1 -0
- package/dist/web/assets/MarkButtonPrototype-CxhxE0RP.js +1 -0
- package/dist/web/assets/MenuStylesPrototype-D7neA6YM.js +1 -0
- package/dist/web/assets/MomentCardVariations-2GT7GyFn.js +1 -0
- package/dist/web/assets/MomentHeaderVariations-DhGEw4XC.js +1 -0
- package/dist/web/assets/NarrativeWalkthroughDemo-B5C566fu.js +389 -0
- package/dist/web/assets/OutcomeVariations-BrZfsVcs.js +1 -0
- package/dist/web/assets/PermissionPatternPickerPrototype-CBOhe2Me.js +1 -0
- package/dist/web/assets/PermissionPrototype-BcF-a5an.js +1 -0
- package/dist/web/assets/PipelineGallery-BJhyM0rx.js +1 -0
- package/dist/web/assets/ScopeHeaderPrototype-GD1HNfaO.js +1 -0
- package/dist/web/assets/ScopeHeaderStylesPrototype-aa4uNJJ1.js +1 -0
- package/dist/web/assets/ScrollycodingPrototype-CKW1bf72.js +70 -0
- package/dist/web/assets/SectionHeaderVariations-DM8vUwfj.js +1 -0
- package/dist/web/assets/SemanticGallery-CtQEo7St.js +1 -0
- package/dist/web/assets/SessionCardPrototype-CgHCIMHe.js +1 -0
- package/dist/web/assets/SessionSidebarVariations-DMQL3Azj.js +3 -0
- package/dist/web/assets/SessionStartPrototype-Cwsv01Rr.js +1 -0
- package/dist/web/assets/SmartMenuPrototype-Br37Qbs_.js +1 -0
- package/dist/web/assets/StyleGallery-rZgrploB.js +1 -0
- package/dist/web/assets/TimelineCardPrototype-lzPc5mhe.js +19 -0
- package/dist/web/assets/ToolbarPrototype-Dm4BNZra.js +1 -0
- package/dist/web/assets/TooltipExperiment-Dy8QzTIP.js +13 -0
- package/dist/web/assets/WalkthroughCTAPrototype-uHoovujd.js +1 -0
- package/dist/web/assets/WalkthroughHeaderVariations-Do7Di1g1.js +1 -0
- package/dist/web/assets/WalkthroughShowcase-sGmRoPoM.js +112 -0
- package/dist/web/assets/arrow-right-D46Nx1mC.js +1 -0
- package/dist/web/assets/brain-BXIZKtOZ.js +1 -0
- package/dist/web/assets/grid-3x3-Cb81B62m.js +1 -0
- package/dist/web/assets/main-B1fyog77.js +321 -0
- package/dist/web/assets/main-C2PK2Klg.css +1 -0
- package/dist/web/assets/semantic-variations-Bd-W7ea2.js +1 -0
- package/dist/web/assets/target-Cf92wDTW.js +1 -0
- package/dist/web/favicon.png +0 -0
- package/dist/web/favicon.svg +22 -0
- package/dist/web/icon-192x192.png +0 -0
- package/dist/web/icon-512x512.png +0 -0
- package/dist/web/index.html +45 -0
- package/dist/web/manifest.json +61 -0
- package/package.json +192 -0
- package/scripts/postinstall.js +60 -0
|
@@ -0,0 +1,1112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WalkthroughService - Generates AI-narrated walkthroughs from session data
|
|
3
|
+
*
|
|
4
|
+
* Extracts Edit/Write tool calls from a session and uses Claude to create
|
|
5
|
+
* a narrative explanation of the changes with Code Hike syntax highlighting.
|
|
6
|
+
*/
|
|
7
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
8
|
+
import { highlight } from 'codehike/code';
|
|
9
|
+
import * as Diff from 'diff';
|
|
10
|
+
import { SessionInfoService } from './sessions/session-info-service.js';
|
|
11
|
+
import { ConfigService } from './infrastructure/config-service.js';
|
|
12
|
+
import { createLogger } from './infrastructure/logger.js';
|
|
13
|
+
const logger = createLogger('WalkthroughService');
|
|
14
|
+
/**
|
|
15
|
+
* Validate a generated walkthrough for quality and completeness.
|
|
16
|
+
* Returns errors (must fix) and warnings (should consider).
|
|
17
|
+
*/
|
|
18
|
+
export function validateWalkthrough(walkthrough) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
const warnings = [];
|
|
21
|
+
// Type guard
|
|
22
|
+
if (!walkthrough || typeof walkthrough !== 'object') {
|
|
23
|
+
return { passed: false, errors: ['Walkthrough is not a valid object'], warnings: [] };
|
|
24
|
+
}
|
|
25
|
+
const wt = walkthrough;
|
|
26
|
+
// Required top-level fields
|
|
27
|
+
if (!wt.title || typeof wt.title !== 'string') {
|
|
28
|
+
errors.push('Missing or invalid title');
|
|
29
|
+
}
|
|
30
|
+
if (!wt.summary || typeof wt.summary !== 'string') {
|
|
31
|
+
errors.push('Missing or invalid summary');
|
|
32
|
+
}
|
|
33
|
+
if (!wt.phases || !Array.isArray(wt.phases)) {
|
|
34
|
+
errors.push('Missing or invalid phases array');
|
|
35
|
+
return { passed: false, errors, warnings };
|
|
36
|
+
}
|
|
37
|
+
// Count code blocks and collect files with code
|
|
38
|
+
const filesWithCode = new Set();
|
|
39
|
+
let totalCodeBlocks = 0;
|
|
40
|
+
let codeBlocksMissingLinkedPhrases = 0;
|
|
41
|
+
let codeBlocksMissingContext = 0;
|
|
42
|
+
for (const phase of wt.phases) {
|
|
43
|
+
if (!phase.name || typeof phase.name !== 'string') {
|
|
44
|
+
errors.push('Phase missing name');
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!phase.keyMoments || !Array.isArray(phase.keyMoments)) {
|
|
48
|
+
warnings.push(`Phase "${phase.name}" has no keyMoments`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
for (const moment of phase.keyMoments) {
|
|
52
|
+
if (!moment.code || !Array.isArray(moment.code))
|
|
53
|
+
continue;
|
|
54
|
+
for (const code of moment.code) {
|
|
55
|
+
totalCodeBlocks++;
|
|
56
|
+
const file = code.file;
|
|
57
|
+
if (file)
|
|
58
|
+
filesWithCode.add(file);
|
|
59
|
+
// Check required fields
|
|
60
|
+
if (!code.linkedPhrases || !Array.isArray(code.linkedPhrases) || code.linkedPhrases.length === 0) {
|
|
61
|
+
codeBlocksMissingLinkedPhrases++;
|
|
62
|
+
}
|
|
63
|
+
if (!code.contextBefore && !code.contextAfter) {
|
|
64
|
+
codeBlocksMissingContext++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Minimum code block requirement
|
|
70
|
+
if (totalCodeBlocks < 5) {
|
|
71
|
+
errors.push(`Only ${totalCodeBlocks} code blocks - need at least 5 to show meaningful changes`);
|
|
72
|
+
}
|
|
73
|
+
// Check linkedPhrases coverage
|
|
74
|
+
if (codeBlocksMissingLinkedPhrases > 0) {
|
|
75
|
+
const pct = Math.round((codeBlocksMissingLinkedPhrases / totalCodeBlocks) * 100);
|
|
76
|
+
if (pct > 50) {
|
|
77
|
+
errors.push(`${codeBlocksMissingLinkedPhrases}/${totalCodeBlocks} code blocks (${pct}%) missing linkedPhrases`);
|
|
78
|
+
}
|
|
79
|
+
else if (pct > 20) {
|
|
80
|
+
warnings.push(`${codeBlocksMissingLinkedPhrases}/${totalCodeBlocks} code blocks missing linkedPhrases`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Check context coverage
|
|
84
|
+
if (codeBlocksMissingContext > 0) {
|
|
85
|
+
const pct = Math.round((codeBlocksMissingContext / totalCodeBlocks) * 100);
|
|
86
|
+
if (pct > 50) {
|
|
87
|
+
warnings.push(`${codeBlocksMissingContext}/${totalCodeBlocks} code blocks missing context`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Check outcome section
|
|
91
|
+
const outcome = wt.outcome;
|
|
92
|
+
if (!outcome) {
|
|
93
|
+
errors.push('Missing outcome section');
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const filesChanged = outcome.filesChanged;
|
|
97
|
+
if (!filesChanged || !Array.isArray(filesChanged) || filesChanged.length === 0) {
|
|
98
|
+
errors.push('outcome.filesChanged is empty or missing');
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Check that files in filesChanged have code blocks
|
|
102
|
+
const missingCodeFiles = [];
|
|
103
|
+
for (const file of filesChanged) {
|
|
104
|
+
// Check both full path and just filename
|
|
105
|
+
const hasCode = filesWithCode.has(file) ||
|
|
106
|
+
Array.from(filesWithCode).some(f => f.endsWith(file) || file.endsWith(f.split('/').pop() || ''));
|
|
107
|
+
if (!hasCode) {
|
|
108
|
+
missingCodeFiles.push(file);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (missingCodeFiles.length > 0) {
|
|
112
|
+
const pct = Math.round((missingCodeFiles.length / filesChanged.length) * 100);
|
|
113
|
+
if (pct > 30) {
|
|
114
|
+
warnings.push(`${missingCodeFiles.length}/${filesChanged.length} files in filesChanged have no code blocks: ${missingCodeFiles.slice(0, 3).join(', ')}${missingCodeFiles.length > 3 ? '...' : ''}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Check that linesAdded/linesRemoved are reasonable (not obviously fake)
|
|
119
|
+
const linesAdded = outcome.linesAdded;
|
|
120
|
+
const linesRemoved = outcome.linesRemoved;
|
|
121
|
+
if (linesAdded !== undefined && linesRemoved !== undefined) {
|
|
122
|
+
// If claimed stats are very round numbers, flag as potentially fake
|
|
123
|
+
if (linesAdded % 100 === 0 && linesRemoved % 100 === 0 && linesAdded > 500) {
|
|
124
|
+
warnings.push(`Line stats (${linesAdded}/${linesRemoved}) are suspiciously round - verify accuracy`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
passed: errors.length === 0,
|
|
130
|
+
errors,
|
|
131
|
+
warnings,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Fix linkedPhrase line numbers by finding where phrases actually appear in code.
|
|
136
|
+
* LLMs often produce off-by-one errors when counting lines.
|
|
137
|
+
* This function scans the code and corrects the line numbers.
|
|
138
|
+
*/
|
|
139
|
+
export function fixLinkedPhraseLines(walkthrough) {
|
|
140
|
+
if (!walkthrough || typeof walkthrough !== 'object')
|
|
141
|
+
return walkthrough;
|
|
142
|
+
const wt = walkthrough;
|
|
143
|
+
if (!wt.phases || !Array.isArray(wt.phases))
|
|
144
|
+
return walkthrough;
|
|
145
|
+
for (const phase of wt.phases) {
|
|
146
|
+
if (!phase.keyMoments || !Array.isArray(phase.keyMoments))
|
|
147
|
+
continue;
|
|
148
|
+
for (const moment of phase.keyMoments) {
|
|
149
|
+
if (!moment.code || !Array.isArray(moment.code))
|
|
150
|
+
continue;
|
|
151
|
+
for (const code of moment.code) {
|
|
152
|
+
const linkedPhrases = code.linkedPhrases;
|
|
153
|
+
if (!linkedPhrases || !Array.isArray(linkedPhrases))
|
|
154
|
+
continue;
|
|
155
|
+
// Get the code content - prefer 'after', fall back to content from Write
|
|
156
|
+
const codeContent = (code.after || code.content || '');
|
|
157
|
+
if (!codeContent)
|
|
158
|
+
continue;
|
|
159
|
+
const codeLines = codeContent.split('\n');
|
|
160
|
+
for (const lp of linkedPhrases) {
|
|
161
|
+
const phrase = lp.phrase;
|
|
162
|
+
if (!phrase)
|
|
163
|
+
continue;
|
|
164
|
+
// Find which line(s) actually contain this phrase
|
|
165
|
+
const matchingLines = [];
|
|
166
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
167
|
+
if (codeLines[i].includes(phrase)) {
|
|
168
|
+
matchingLines.push(i + 1); // 1-indexed
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (matchingLines.length > 0) {
|
|
172
|
+
// Update the lines spec
|
|
173
|
+
if (matchingLines.length === 1) {
|
|
174
|
+
lp.lines = String(matchingLines[0]);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// If phrase spans multiple lines or appears multiple times, use range
|
|
178
|
+
const min = Math.min(...matchingLines);
|
|
179
|
+
const max = Math.max(...matchingLines);
|
|
180
|
+
lp.lines = min === max ? String(min) : `${min}:${max}`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// If phrase not found, leave original lines (might be in contextBefore/After)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return walkthrough;
|
|
189
|
+
}
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// SCHEMA & PROMPTS
|
|
192
|
+
// ============================================================================
|
|
193
|
+
const WALKTHROUGH_SCHEMA = `
|
|
194
|
+
{
|
|
195
|
+
"title": "string - A concise title for this set of changes",
|
|
196
|
+
"summary": "string - 1-2 sentence overview of what was accomplished",
|
|
197
|
+
|
|
198
|
+
"context": {
|
|
199
|
+
"problem": "string - What was the user trying to achieve? What was broken, missing, or needed improvement?",
|
|
200
|
+
"priorState": "string - How did things work before? Describe the user experience, code flow, or system behavior.",
|
|
201
|
+
"approach": "string - High-level summary of the solution approach before diving into details"
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
"orientation": {
|
|
205
|
+
"narrative": "string - A brief tour of the relevant parts of the codebase BEFORE any changes. Help the reader understand where we're working.",
|
|
206
|
+
"keyFiles": [
|
|
207
|
+
{
|
|
208
|
+
"file": "string - relative file path",
|
|
209
|
+
"role": "string - what this file does in the system"
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
"sections": [
|
|
215
|
+
{
|
|
216
|
+
"heading": "string - Conceptual heading (not a filename)",
|
|
217
|
+
"narrative": "string - Explanation of this part of the changes. Write in first person as Claude. Use phrases that reference specific code elements.",
|
|
218
|
+
"linkedPhrases": [
|
|
219
|
+
{
|
|
220
|
+
"phrase": "string - exact text from narrative to make interactive (underlined, hoverable)",
|
|
221
|
+
"focusLines": "string - lines in the 'after' code to highlight on hover, e.g. '3:5' or '7'",
|
|
222
|
+
"explanation": "string (optional) - tooltip to show on hover"
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
"file": "string - relative file path",
|
|
226
|
+
"language": "string - programming language (tsx, typescript, python, etc.)",
|
|
227
|
+
"before": {
|
|
228
|
+
"description": "string - Brief description of what the code looked like before",
|
|
229
|
+
"code": "string - The code BEFORE the change (use // !focus and // !mark annotations)"
|
|
230
|
+
},
|
|
231
|
+
"after": {
|
|
232
|
+
"description": "string - Brief description of what changed",
|
|
233
|
+
"code": "string - The code AFTER the change (use // !focus and // !mark annotations)"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
|
|
238
|
+
"systemFlow": {
|
|
239
|
+
"description": "string - How the pieces work together after the changes",
|
|
240
|
+
"components": ["string - list of key components/files involved and their roles"]
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
// Language mapping for Code Hike
|
|
245
|
+
const LANG_MAP = {
|
|
246
|
+
tsx: 'tsx',
|
|
247
|
+
typescript: 'typescript',
|
|
248
|
+
ts: 'typescript',
|
|
249
|
+
javascript: 'javascript',
|
|
250
|
+
js: 'javascript',
|
|
251
|
+
jsx: 'jsx',
|
|
252
|
+
python: 'python',
|
|
253
|
+
py: 'python',
|
|
254
|
+
bash: 'bash',
|
|
255
|
+
sh: 'bash',
|
|
256
|
+
shell: 'bash',
|
|
257
|
+
json: 'json',
|
|
258
|
+
css: 'css',
|
|
259
|
+
html: 'html',
|
|
260
|
+
markdown: 'markdown',
|
|
261
|
+
md: 'markdown',
|
|
262
|
+
sql: 'sql',
|
|
263
|
+
yaml: 'yaml',
|
|
264
|
+
yml: 'yaml',
|
|
265
|
+
};
|
|
266
|
+
// ============================================================================
|
|
267
|
+
// SERVICE
|
|
268
|
+
// ============================================================================
|
|
269
|
+
export class WalkthroughService {
|
|
270
|
+
historyReader;
|
|
271
|
+
client = null;
|
|
272
|
+
constructor(historyReader) {
|
|
273
|
+
this.historyReader = historyReader;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Pre-compute all file change data for a session.
|
|
277
|
+
* This data is passed to the skill prompt so the model doesn't need to query the DB.
|
|
278
|
+
* @param sessionId - The session to precompute data for
|
|
279
|
+
* @param scope - Optional scope with editIndices and writeIndices to filter changes
|
|
280
|
+
*/
|
|
281
|
+
async precomputeWalkthroughData(sessionId, scope) {
|
|
282
|
+
logger.info('Pre-computing walkthrough data', { sessionId, hasScope: !!scope });
|
|
283
|
+
const sessionInfo = SessionInfoService.getInstance();
|
|
284
|
+
// Get file changes from database (returns camelCase props)
|
|
285
|
+
let fileChanges = sessionInfo.getFileChanges(sessionId);
|
|
286
|
+
// If scope provided, filter to only the specified indices
|
|
287
|
+
if (scope?.editIndices || scope?.writeIndices) {
|
|
288
|
+
const editIndices = new Set(scope.editIndices || []);
|
|
289
|
+
const writeIndices = new Set(scope.writeIndices || []);
|
|
290
|
+
// File changes are ordered by ID, which corresponds to the indices
|
|
291
|
+
// We need to track edit and write indices separately
|
|
292
|
+
let editIdx = 0;
|
|
293
|
+
let writeIdx = 0;
|
|
294
|
+
fileChanges = fileChanges.filter(change => {
|
|
295
|
+
if (change.toolName === 'Edit') {
|
|
296
|
+
const include = editIndices.has(editIdx);
|
|
297
|
+
editIdx++;
|
|
298
|
+
return include;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const include = writeIndices.has(writeIdx);
|
|
302
|
+
writeIdx++;
|
|
303
|
+
return include;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
logger.info('Filtered file changes by scope', {
|
|
307
|
+
originalCount: sessionInfo.getFileChanges(sessionId).length,
|
|
308
|
+
filteredCount: fileChanges.length,
|
|
309
|
+
editIndicesCount: editIndices.size,
|
|
310
|
+
writeIndicesCount: writeIndices.size,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// Get turn summaries from database (returns snake_case props)
|
|
314
|
+
const turns = await sessionInfo.getTurns(sessionId);
|
|
315
|
+
// Get session insights
|
|
316
|
+
const insights = await sessionInfo.getInsights(sessionId);
|
|
317
|
+
// Group changes by file
|
|
318
|
+
const fileMap = new Map();
|
|
319
|
+
let totalLinesAdded = 0;
|
|
320
|
+
let totalLinesRemoved = 0;
|
|
321
|
+
for (const change of fileChanges) {
|
|
322
|
+
const relativePath = change.filePath
|
|
323
|
+
.replace(/^\/home\/jason\/(claudia|cui-custom)\//, '')
|
|
324
|
+
.replace(/^\/home\/jason\//, '~/');
|
|
325
|
+
if (!fileMap.has(change.filePath)) {
|
|
326
|
+
fileMap.set(change.filePath, {
|
|
327
|
+
path: change.filePath,
|
|
328
|
+
relativePath,
|
|
329
|
+
turnNumbers: [],
|
|
330
|
+
totalEdits: 0,
|
|
331
|
+
totalWrites: 0,
|
|
332
|
+
changes: [],
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
const fileData = fileMap.get(change.filePath);
|
|
336
|
+
// Track turn numbers
|
|
337
|
+
if (!fileData.turnNumbers.includes(change.turnNumber)) {
|
|
338
|
+
fileData.turnNumbers.push(change.turnNumber);
|
|
339
|
+
}
|
|
340
|
+
// Count by tool type
|
|
341
|
+
if (change.toolName === 'Edit') {
|
|
342
|
+
fileData.totalEdits++;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
fileData.totalWrites++;
|
|
346
|
+
}
|
|
347
|
+
// Add change data
|
|
348
|
+
fileData.changes.push({
|
|
349
|
+
turn: change.turnNumber,
|
|
350
|
+
toolName: change.toolName,
|
|
351
|
+
oldString: change.oldString,
|
|
352
|
+
newString: change.newString,
|
|
353
|
+
});
|
|
354
|
+
// Compute line stats from actual changes
|
|
355
|
+
const newLines = (change.newString.match(/\n/g) || []).length + 1;
|
|
356
|
+
const oldLines = change.oldString ? (change.oldString.match(/\n/g) || []).length + 1 : 0;
|
|
357
|
+
if (change.toolName === 'Write') {
|
|
358
|
+
// New file - all lines are added
|
|
359
|
+
totalLinesAdded += newLines;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// Edit - compute diff
|
|
363
|
+
totalLinesAdded += Math.max(0, newLines - oldLines);
|
|
364
|
+
totalLinesRemoved += Math.max(0, oldLines - newLines);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Convert map to array and sort by first turn number
|
|
368
|
+
const files = Array.from(fileMap.values()).sort((a, b) => {
|
|
369
|
+
const aFirst = Math.min(...a.turnNumbers);
|
|
370
|
+
const bFirst = Math.min(...b.turnNumbers);
|
|
371
|
+
return aFirst - bFirst;
|
|
372
|
+
});
|
|
373
|
+
// Format turn summaries (TurnRecord uses snake_case)
|
|
374
|
+
const turnSummaries = turns.map((t) => ({
|
|
375
|
+
number: t.turn_number,
|
|
376
|
+
headline: t.headline,
|
|
377
|
+
tag: t.tag || 'implement',
|
|
378
|
+
toolCount: t.tool_count || 0,
|
|
379
|
+
}));
|
|
380
|
+
// Compute total turns (max turn number from changes or turns)
|
|
381
|
+
const maxTurnFromChanges = fileChanges.length > 0
|
|
382
|
+
? Math.max(...fileChanges.map(c => c.turnNumber))
|
|
383
|
+
: 0;
|
|
384
|
+
const maxTurnFromTurns = turns.length > 0
|
|
385
|
+
? Math.max(...turns.map((t) => t.turn_number))
|
|
386
|
+
: 0;
|
|
387
|
+
const totalTurns = Math.max(maxTurnFromChanges, maxTurnFromTurns);
|
|
388
|
+
logger.info('Pre-computed walkthrough data', {
|
|
389
|
+
sessionId,
|
|
390
|
+
totalFiles: files.length,
|
|
391
|
+
totalChanges: fileChanges.length,
|
|
392
|
+
totalTurns,
|
|
393
|
+
linesAdded: totalLinesAdded,
|
|
394
|
+
linesRemoved: totalLinesRemoved,
|
|
395
|
+
});
|
|
396
|
+
return {
|
|
397
|
+
sessionId,
|
|
398
|
+
totalTurns,
|
|
399
|
+
totalFiles: files.length,
|
|
400
|
+
linesAdded: totalLinesAdded,
|
|
401
|
+
linesRemoved: totalLinesRemoved,
|
|
402
|
+
files,
|
|
403
|
+
turns: turnSummaries,
|
|
404
|
+
purpose: insights?.purpose || undefined,
|
|
405
|
+
description: insights?.description || undefined,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
getClient() {
|
|
409
|
+
if (!this.client) {
|
|
410
|
+
const config = ConfigService.getInstance().getConfig();
|
|
411
|
+
const apiKey = config.anthropic?.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
412
|
+
if (!apiKey) {
|
|
413
|
+
throw new Error('No Anthropic API key configured');
|
|
414
|
+
}
|
|
415
|
+
this.client = new Anthropic({ apiKey });
|
|
416
|
+
}
|
|
417
|
+
return this.client;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Generate a walkthrough for a session, optionally filtered to specific indices
|
|
421
|
+
* @param sessionId - The session to generate walkthrough for
|
|
422
|
+
* @param scope - Optional scope with editIndices and writeIndices to filter changes
|
|
423
|
+
*/
|
|
424
|
+
async generateWalkthrough(sessionId, scope) {
|
|
425
|
+
logger.info('Generating walkthrough', { sessionId, hasScope: !!scope });
|
|
426
|
+
// Extract session data
|
|
427
|
+
let data = await this.extractWalkthroughData(sessionId);
|
|
428
|
+
if (data.edits.length === 0 && data.writes.length === 0) {
|
|
429
|
+
throw new Error('No code changes found in session');
|
|
430
|
+
}
|
|
431
|
+
// If scope indices provided, filter the data
|
|
432
|
+
if (scope?.editIndices || scope?.writeIndices) {
|
|
433
|
+
const editIndices = scope.editIndices || [];
|
|
434
|
+
const writeIndices = scope.writeIndices || [];
|
|
435
|
+
logger.info('Filtering to scope', {
|
|
436
|
+
editCount: editIndices.length,
|
|
437
|
+
writeCount: writeIndices.length,
|
|
438
|
+
totalEdits: data.edits.length,
|
|
439
|
+
totalWrites: data.writes.length,
|
|
440
|
+
});
|
|
441
|
+
// Filter edits and writes to only those in this scope
|
|
442
|
+
data = {
|
|
443
|
+
...data,
|
|
444
|
+
edits: editIndices.map(i => data.edits[i]).filter(Boolean),
|
|
445
|
+
writes: writeIndices.map(i => data.writes[i]).filter(Boolean),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
logger.info('Extracted session data', {
|
|
449
|
+
sessionId,
|
|
450
|
+
edits: data.edits.length,
|
|
451
|
+
writes: data.writes.length,
|
|
452
|
+
});
|
|
453
|
+
// Generate walkthrough with Claude
|
|
454
|
+
const walkthrough = await this.callClaude(data);
|
|
455
|
+
// Add syntax highlighting
|
|
456
|
+
const highlighted = await this.addHighlighting(walkthrough);
|
|
457
|
+
logger.info('Walkthrough generated', { sessionId, sections: highlighted.sections.length });
|
|
458
|
+
return highlighted;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Analyze session changes and suggest conceptual groupings for walkthroughs.
|
|
462
|
+
* Uses Haiku for fast analysis of what distinct features/tasks were worked on.
|
|
463
|
+
* @param sessionId - The session to analyze
|
|
464
|
+
* @param previousGroupings - Optional previous groupings to maintain stability
|
|
465
|
+
*/
|
|
466
|
+
async analyzeScope(sessionId, previousGroupings) {
|
|
467
|
+
logger.info('Analyzing walkthrough scope', { sessionId, hasPreviousGroupings: !!previousGroupings });
|
|
468
|
+
const data = await this.extractWalkthroughData(sessionId);
|
|
469
|
+
if (data.edits.length === 0 && data.writes.length === 0) {
|
|
470
|
+
throw new Error('No code changes found in session');
|
|
471
|
+
}
|
|
472
|
+
// Build a summary of changes for Haiku to analyze
|
|
473
|
+
const changesSummary = this.buildChangesSummaryForScoping(data);
|
|
474
|
+
// Build stability context if we have previous groupings
|
|
475
|
+
let stabilityContext = '';
|
|
476
|
+
if (previousGroupings && previousGroupings.length > 0) {
|
|
477
|
+
stabilityContext = `
|
|
478
|
+
IMPORTANT: This session was previously analyzed with these groupings:
|
|
479
|
+
${JSON.stringify(previousGroupings, null, 2)}
|
|
480
|
+
|
|
481
|
+
STABILITY RULES:
|
|
482
|
+
- Keep existing group IDs and titles when the same work is still present
|
|
483
|
+
- Only rename a group if its scope has fundamentally changed
|
|
484
|
+
- New changes (higher indices) should either extend existing groups OR form new groups
|
|
485
|
+
- Prefer extending existing groups over creating new ones when work is related
|
|
486
|
+
- If a previous group's changes are still present, keep that group
|
|
487
|
+
|
|
488
|
+
`;
|
|
489
|
+
}
|
|
490
|
+
const client = this.getClient();
|
|
491
|
+
const response = await client.messages.create({
|
|
492
|
+
model: 'claude-haiku-4-5-20251001',
|
|
493
|
+
max_tokens: 2048,
|
|
494
|
+
messages: [{
|
|
495
|
+
role: 'user',
|
|
496
|
+
content: `Analyze these code changes from a coding session and identify distinct conceptual groupings.
|
|
497
|
+
|
|
498
|
+
The user's original request was:
|
|
499
|
+
"${data.userRequest.slice(0, 500)}"
|
|
500
|
+
|
|
501
|
+
Here are the changes made, with reasoning before each:
|
|
502
|
+
|
|
503
|
+
${changesSummary}
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
${stabilityContext}
|
|
507
|
+
Identify 1-4 conceptual groupings based on WHAT was being accomplished (not just which files were touched).
|
|
508
|
+
Consider the reasoning text - it explains intent.
|
|
509
|
+
|
|
510
|
+
For each grouping, specify which edit/write indices belong to it (0-indexed).
|
|
511
|
+
|
|
512
|
+
Output JSON:
|
|
513
|
+
{
|
|
514
|
+
"groupings": [
|
|
515
|
+
{
|
|
516
|
+
"id": "unique-slug",
|
|
517
|
+
"title": "Short title (3-6 words)",
|
|
518
|
+
"description": "One sentence explaining what this group of changes accomplishes",
|
|
519
|
+
"editIndices": [0, 1, 5],
|
|
520
|
+
"writeIndices": [0]
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
If all changes are part of one cohesive feature, return just one grouping with all indices.
|
|
526
|
+
Output only valid JSON.`
|
|
527
|
+
}],
|
|
528
|
+
});
|
|
529
|
+
const content = response.content[0];
|
|
530
|
+
if (content.type !== 'text') {
|
|
531
|
+
throw new Error('Unexpected response type from scope analysis');
|
|
532
|
+
}
|
|
533
|
+
// Parse response
|
|
534
|
+
let jsonText = content.text.trim();
|
|
535
|
+
const jsonMatch = jsonText.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
|
|
536
|
+
if (jsonMatch) {
|
|
537
|
+
jsonText = jsonMatch[1];
|
|
538
|
+
}
|
|
539
|
+
let parsed;
|
|
540
|
+
try {
|
|
541
|
+
parsed = JSON.parse(jsonText);
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
logger.error('Failed to parse scope analysis', { response: content.text.slice(0, 500) });
|
|
545
|
+
// Fallback: single group with everything
|
|
546
|
+
parsed = {
|
|
547
|
+
groupings: [{
|
|
548
|
+
id: 'all-changes',
|
|
549
|
+
title: 'All Changes',
|
|
550
|
+
description: 'All changes in this session',
|
|
551
|
+
editIndices: data.edits.map((_, i) => i),
|
|
552
|
+
writeIndices: data.writes.map((_, i) => i),
|
|
553
|
+
}]
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
// Build scope options with file counts and turn ranges
|
|
557
|
+
const options = parsed.groupings.map(g => {
|
|
558
|
+
const files = new Set();
|
|
559
|
+
const turnNumbers = [];
|
|
560
|
+
g.editIndices.forEach(i => {
|
|
561
|
+
if (data.edits[i]) {
|
|
562
|
+
files.add(data.edits[i].file_path);
|
|
563
|
+
turnNumbers.push(data.edits[i].turnNumber);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
g.writeIndices.forEach(i => {
|
|
567
|
+
if (data.writes[i]) {
|
|
568
|
+
files.add(data.writes[i].file_path);
|
|
569
|
+
turnNumbers.push(data.writes[i].turnNumber);
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
// Compute turn range (min/max turn numbers in this group)
|
|
573
|
+
const minTurn = turnNumbers.length > 0 ? Math.min(...turnNumbers) : 1;
|
|
574
|
+
const maxTurn = turnNumbers.length > 0 ? Math.max(...turnNumbers) : 1;
|
|
575
|
+
return {
|
|
576
|
+
id: g.id,
|
|
577
|
+
title: g.title,
|
|
578
|
+
description: g.description,
|
|
579
|
+
editIndices: g.editIndices,
|
|
580
|
+
writeIndices: g.writeIndices,
|
|
581
|
+
fileCount: files.size,
|
|
582
|
+
changeCount: g.editIndices.length + g.writeIndices.length,
|
|
583
|
+
turnRange: [minTurn, maxTurn],
|
|
584
|
+
};
|
|
585
|
+
});
|
|
586
|
+
// Compute total turn count from all edits/writes
|
|
587
|
+
const allTurnNumbers = [
|
|
588
|
+
...data.edits.map(e => e.turnNumber),
|
|
589
|
+
...data.writes.map(w => w.turnNumber),
|
|
590
|
+
];
|
|
591
|
+
const totalTurns = allTurnNumbers.length > 0 ? Math.max(...allTurnNumbers) : 0;
|
|
592
|
+
// Compute all unique files for full session scope
|
|
593
|
+
const allFiles = new Set();
|
|
594
|
+
data.edits.forEach(e => allFiles.add(e.file_path));
|
|
595
|
+
data.writes.forEach(w => allFiles.add(w.file_path));
|
|
596
|
+
// Prepend "Complete Session" option if there are multiple groupings
|
|
597
|
+
// (If Haiku already returned a single "all changes" group, don't duplicate)
|
|
598
|
+
const hasMultipleGroupings = options.length > 1;
|
|
599
|
+
const fullSessionOption = {
|
|
600
|
+
id: 'complete-session',
|
|
601
|
+
title: 'Complete Session',
|
|
602
|
+
description: 'Walk through everything done in this session from start to finish',
|
|
603
|
+
editIndices: data.edits.map((_, i) => i),
|
|
604
|
+
writeIndices: data.writes.map((_, i) => i),
|
|
605
|
+
fileCount: allFiles.size,
|
|
606
|
+
changeCount: data.edits.length + data.writes.length,
|
|
607
|
+
turnRange: [1, totalTurns],
|
|
608
|
+
};
|
|
609
|
+
const finalOptions = hasMultipleGroupings ? [fullSessionOption, ...options] : options;
|
|
610
|
+
logger.info('Scope analysis complete', {
|
|
611
|
+
sessionId,
|
|
612
|
+
optionCount: finalOptions.length,
|
|
613
|
+
totalTurns,
|
|
614
|
+
hasFullSessionOption: hasMultipleGroupings,
|
|
615
|
+
options: finalOptions.map(o => ({ title: o.title, turnRange: o.turnRange }))
|
|
616
|
+
});
|
|
617
|
+
return {
|
|
618
|
+
sessionId,
|
|
619
|
+
totalEdits: data.edits.length,
|
|
620
|
+
totalWrites: data.writes.length,
|
|
621
|
+
totalTurns,
|
|
622
|
+
userRequest: data.userRequest.slice(0, 200),
|
|
623
|
+
options: finalOptions,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Build a concise summary of changes for scope analysis
|
|
628
|
+
*/
|
|
629
|
+
buildChangesSummaryForScoping(data) {
|
|
630
|
+
const lines = [];
|
|
631
|
+
data.edits.forEach((edit, i) => {
|
|
632
|
+
const shortPath = edit.file_path.replace(/^\/home\/jason\/(claudia|cui-custom)\//, '');
|
|
633
|
+
const reasoningSnippet = edit.reasoning.slice(0, 150).replace(/\n/g, ' ');
|
|
634
|
+
lines.push(`Edit ${i}: ${shortPath}`);
|
|
635
|
+
lines.push(` Reasoning: ${reasoningSnippet}...`);
|
|
636
|
+
lines.push(` Changed: ${edit.old_string.slice(0, 50).replace(/\n/g, '\\n')}... → ${edit.new_string.slice(0, 50).replace(/\n/g, '\\n')}...`);
|
|
637
|
+
lines.push('');
|
|
638
|
+
});
|
|
639
|
+
data.writes.forEach((write, i) => {
|
|
640
|
+
const shortPath = write.file_path.replace(/^\/home\/jason\/(claudia|cui-custom)\//, '');
|
|
641
|
+
const reasoningSnippet = write.reasoning.slice(0, 150).replace(/\n/g, ' ');
|
|
642
|
+
lines.push(`Write ${i}: ${shortPath} (new file)`);
|
|
643
|
+
lines.push(` Reasoning: ${reasoningSnippet}...`);
|
|
644
|
+
lines.push('');
|
|
645
|
+
});
|
|
646
|
+
return lines.join('\n');
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Check if a user message contains actual human text (not just tool_result).
|
|
650
|
+
*/
|
|
651
|
+
hasHumanText(content) {
|
|
652
|
+
if (typeof content === 'string' && content.trim()) {
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
if (Array.isArray(content)) {
|
|
656
|
+
return content.some(block => typeof block === 'object' && block !== null && block.type === 'text' && 'text' in block);
|
|
657
|
+
}
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Extract Edit/Write calls from session messages, including turn numbers.
|
|
662
|
+
*/
|
|
663
|
+
async extractWalkthroughData(sessionId) {
|
|
664
|
+
const details = await this.historyReader.fetchConversationDirect(sessionId);
|
|
665
|
+
if (!details || !details.messages) {
|
|
666
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
667
|
+
}
|
|
668
|
+
const messages = details.messages;
|
|
669
|
+
// Extract user request (first user message with human text)
|
|
670
|
+
let userRequest = '';
|
|
671
|
+
for (const msg of messages) {
|
|
672
|
+
if (msg.type === 'user' && this.hasHumanText(msg.message.content)) {
|
|
673
|
+
const content = msg.message.content;
|
|
674
|
+
if (typeof content === 'string') {
|
|
675
|
+
userRequest = content;
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
else if (Array.isArray(content)) {
|
|
679
|
+
const textBlock = content.find((b) => b.type === 'text');
|
|
680
|
+
if (textBlock?.text) {
|
|
681
|
+
userRequest = textBlock.text;
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Extract Edit/Write calls with reasoning and turn numbers
|
|
688
|
+
// A turn starts when we see a user message with actual human text
|
|
689
|
+
const edits = [];
|
|
690
|
+
const writes = [];
|
|
691
|
+
let currentReasoning = '';
|
|
692
|
+
let currentTurn = 0;
|
|
693
|
+
for (const msg of messages) {
|
|
694
|
+
// Detect turn boundaries - user message with human text starts a new turn
|
|
695
|
+
if (msg.type === 'user' && this.hasHumanText(msg.message.content)) {
|
|
696
|
+
currentTurn++;
|
|
697
|
+
}
|
|
698
|
+
if (msg.type === 'assistant') {
|
|
699
|
+
const content = msg.message.content;
|
|
700
|
+
if (Array.isArray(content)) {
|
|
701
|
+
// Capture reasoning from text blocks
|
|
702
|
+
const textBlocks = content.filter((b) => b.type === 'text');
|
|
703
|
+
if (textBlocks.length > 0) {
|
|
704
|
+
currentReasoning = textBlocks.map((b) => b.text || '').join('\n');
|
|
705
|
+
}
|
|
706
|
+
// Extract tool calls
|
|
707
|
+
for (const block of content) {
|
|
708
|
+
if (block.type === 'tool_use') {
|
|
709
|
+
if (block.name === 'Edit' && block.input) {
|
|
710
|
+
edits.push({
|
|
711
|
+
file_path: block.input.file_path,
|
|
712
|
+
old_string: block.input.old_string,
|
|
713
|
+
new_string: block.input.new_string,
|
|
714
|
+
reasoning: currentReasoning,
|
|
715
|
+
turnNumber: currentTurn,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
else if (block.name === 'Write' && block.input) {
|
|
719
|
+
writes.push({
|
|
720
|
+
file_path: block.input.file_path,
|
|
721
|
+
content: block.input.content,
|
|
722
|
+
reasoning: currentReasoning,
|
|
723
|
+
turnNumber: currentTurn,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return { sessionId, userRequest, edits, writes };
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Build prompt and call Claude to generate walkthrough
|
|
735
|
+
* Uses structured outputs beta for guaranteed JSON schema compliance
|
|
736
|
+
*/
|
|
737
|
+
async callClaude(data) {
|
|
738
|
+
const prompt = this.buildPrompt(data);
|
|
739
|
+
const client = this.getClient();
|
|
740
|
+
// Use structured outputs beta for guaranteed JSON schema compliance
|
|
741
|
+
const response = await client.beta.messages.create({
|
|
742
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
743
|
+
max_tokens: 16384,
|
|
744
|
+
betas: ['structured-outputs-2025-11-13'],
|
|
745
|
+
messages: [{ role: 'user', content: prompt }],
|
|
746
|
+
output_format: {
|
|
747
|
+
type: 'json_schema',
|
|
748
|
+
schema: {
|
|
749
|
+
type: 'object',
|
|
750
|
+
properties: {
|
|
751
|
+
title: { type: 'string' },
|
|
752
|
+
summary: { type: 'string' },
|
|
753
|
+
context: {
|
|
754
|
+
type: 'object',
|
|
755
|
+
properties: {
|
|
756
|
+
problem: { type: 'string' },
|
|
757
|
+
priorState: { type: 'string' },
|
|
758
|
+
approach: { type: 'string' },
|
|
759
|
+
},
|
|
760
|
+
required: ['problem', 'priorState', 'approach'],
|
|
761
|
+
additionalProperties: false,
|
|
762
|
+
},
|
|
763
|
+
orientation: {
|
|
764
|
+
type: 'object',
|
|
765
|
+
properties: {
|
|
766
|
+
narrative: { type: 'string' },
|
|
767
|
+
keyFiles: {
|
|
768
|
+
type: 'array',
|
|
769
|
+
items: {
|
|
770
|
+
type: 'object',
|
|
771
|
+
properties: {
|
|
772
|
+
file: { type: 'string' },
|
|
773
|
+
role: { type: 'string' },
|
|
774
|
+
},
|
|
775
|
+
required: ['file', 'role'],
|
|
776
|
+
additionalProperties: false,
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
required: ['narrative'],
|
|
781
|
+
additionalProperties: false,
|
|
782
|
+
},
|
|
783
|
+
sections: {
|
|
784
|
+
type: 'array',
|
|
785
|
+
items: {
|
|
786
|
+
type: 'object',
|
|
787
|
+
properties: {
|
|
788
|
+
heading: { type: 'string' },
|
|
789
|
+
narrative: { type: 'string' },
|
|
790
|
+
linkedPhrases: {
|
|
791
|
+
type: 'array',
|
|
792
|
+
items: {
|
|
793
|
+
type: 'object',
|
|
794
|
+
properties: {
|
|
795
|
+
phrase: { type: 'string' },
|
|
796
|
+
focusLines: { type: 'string' },
|
|
797
|
+
explanation: { type: 'string' },
|
|
798
|
+
},
|
|
799
|
+
required: ['phrase'],
|
|
800
|
+
additionalProperties: false,
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
file: { type: 'string' },
|
|
804
|
+
language: { type: 'string' },
|
|
805
|
+
before: {
|
|
806
|
+
type: 'object',
|
|
807
|
+
properties: {
|
|
808
|
+
description: { type: 'string' },
|
|
809
|
+
code: { type: 'string' },
|
|
810
|
+
},
|
|
811
|
+
required: ['description', 'code'],
|
|
812
|
+
additionalProperties: false,
|
|
813
|
+
},
|
|
814
|
+
after: {
|
|
815
|
+
type: 'object',
|
|
816
|
+
properties: {
|
|
817
|
+
description: { type: 'string' },
|
|
818
|
+
code: { type: 'string' },
|
|
819
|
+
},
|
|
820
|
+
required: ['description', 'code'],
|
|
821
|
+
additionalProperties: false,
|
|
822
|
+
},
|
|
823
|
+
},
|
|
824
|
+
required: ['heading', 'narrative'],
|
|
825
|
+
additionalProperties: false,
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
systemFlow: {
|
|
829
|
+
type: 'object',
|
|
830
|
+
properties: {
|
|
831
|
+
description: { type: 'string' },
|
|
832
|
+
components: {
|
|
833
|
+
type: 'array',
|
|
834
|
+
items: { type: 'string' },
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
required: ['description', 'components'],
|
|
838
|
+
additionalProperties: false,
|
|
839
|
+
},
|
|
840
|
+
},
|
|
841
|
+
required: ['title', 'summary', 'context', 'sections'],
|
|
842
|
+
additionalProperties: false,
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
});
|
|
846
|
+
const content = response.content[0];
|
|
847
|
+
if (content.type !== 'text') {
|
|
848
|
+
throw new Error('Unexpected response type');
|
|
849
|
+
}
|
|
850
|
+
// With structured outputs, the response is guaranteed valid JSON
|
|
851
|
+
return JSON.parse(content.text);
|
|
852
|
+
}
|
|
853
|
+
buildPrompt(data) {
|
|
854
|
+
// Group edits by file
|
|
855
|
+
const editsByFile = new Map();
|
|
856
|
+
for (const edit of data.edits) {
|
|
857
|
+
const existing = editsByFile.get(edit.file_path) || [];
|
|
858
|
+
existing.push(edit);
|
|
859
|
+
editsByFile.set(edit.file_path, existing);
|
|
860
|
+
}
|
|
861
|
+
let changesDescription = '';
|
|
862
|
+
for (const [filePath, fileEdits] of editsByFile) {
|
|
863
|
+
const shortPath = filePath.replace(/^\/home\/jason\/(claudia|cui-custom)\//, '');
|
|
864
|
+
changesDescription += `\n## ${shortPath}\n\n`;
|
|
865
|
+
for (let i = 0; i < fileEdits.length; i++) {
|
|
866
|
+
const edit = fileEdits[i];
|
|
867
|
+
changesDescription += `### Edit ${i + 1}\n`;
|
|
868
|
+
changesDescription += `**My reasoning at the time:**\n${edit.reasoning.slice(0, 500)}\n\n`;
|
|
869
|
+
changesDescription += `**Changed from:**\n\`\`\`\n${edit.old_string.slice(0, 400)}\n\`\`\`\n\n`;
|
|
870
|
+
changesDescription += `**Changed to:**\n\`\`\`\n${edit.new_string.slice(0, 400)}\n\`\`\`\n\n`;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
for (const write of data.writes) {
|
|
874
|
+
const shortPath = write.file_path.replace(/^\/home\/jason\/(claudia|cui-custom)\//, '');
|
|
875
|
+
changesDescription += `\n## ${shortPath} (new file)\n\n`;
|
|
876
|
+
changesDescription += `**My reasoning:**\n${write.reasoning.slice(0, 500)}\n\n`;
|
|
877
|
+
changesDescription += `**Content:**\n\`\`\`\n${write.content.slice(0, 600)}\n\`\`\`\n\n`;
|
|
878
|
+
}
|
|
879
|
+
return `You are creating an interactive walkthrough of code changes from a coding session. The walkthrough will show a narrative explanation with before/after code comparisons and diffs.
|
|
880
|
+
|
|
881
|
+
The user's original request was:
|
|
882
|
+
"${data.userRequest.slice(0, 1000)}"
|
|
883
|
+
|
|
884
|
+
Here are all the changes you made, with your reasoning at the time:
|
|
885
|
+
|
|
886
|
+
${changesDescription}
|
|
887
|
+
|
|
888
|
+
---
|
|
889
|
+
|
|
890
|
+
Create a narrative-driven walkthrough with BEFORE/AFTER code comparisons:
|
|
891
|
+
|
|
892
|
+
1. **Context** - Problem, prior state, approach (1-2 sentences each, set the scene)
|
|
893
|
+
|
|
894
|
+
2. **Orientation** - Brief tour of relevant files (just file paths and roles, no code snippets)
|
|
895
|
+
|
|
896
|
+
3. **Sections** - Each section explains ONE conceptual change with:
|
|
897
|
+
- A heading (conceptual, not just a filename)
|
|
898
|
+
- A narrative explaining what changed and WHY (first person, past tense)
|
|
899
|
+
- linkedPhrases: 2-4 phrases from the narrative that become interactive (underlined, hoverable)
|
|
900
|
+
- before: the code BEFORE the change
|
|
901
|
+
- after: the code AFTER the change (viewer will compute diff automatically)
|
|
902
|
+
|
|
903
|
+
INTERACTIVE LINKED PHRASES:
|
|
904
|
+
Pick key terms from your narrative that reference specific code. When the user hovers these underlined phrases, the corresponding lines in the "after" code will be highlighted. Include a brief tooltip explanation.
|
|
905
|
+
|
|
906
|
+
EXAMPLE for narrative "I wrapped the component in a SelectionProvider and made each step use Selectable":
|
|
907
|
+
linkedPhrases: [
|
|
908
|
+
{ "phrase": "SelectionProvider", "focusLines": "3:4", "explanation": "Creates shared selection context for all children" },
|
|
909
|
+
{ "phrase": "Selectable", "focusLines": "8:12", "explanation": "Makes this element respond to hover and scroll selection" }
|
|
910
|
+
]
|
|
911
|
+
|
|
912
|
+
CODE HIKE ANNOTATIONS (use in before/after code):
|
|
913
|
+
- // !focus(1:3) - dims other lines, highlights these (great for drawing attention!)
|
|
914
|
+
- // !mark[/text/] - highlights specific tokens inline
|
|
915
|
+
- // !tooltip[/text/] Explanation - shows tooltip on hover over text in code
|
|
916
|
+
|
|
917
|
+
Output JSON matching this schema:
|
|
918
|
+
${WALKTHROUGH_SCHEMA}
|
|
919
|
+
|
|
920
|
+
CRITICAL Guidelines:
|
|
921
|
+
- Each section needs BOTH before AND after code - show the transformation
|
|
922
|
+
- linkedPhrases must use EXACT text from the narrative (case-sensitive match)
|
|
923
|
+
- focusLines reference the AFTER code using 1-indexed line numbers
|
|
924
|
+
- Write in PAST TENSE - this is retrospective ("I added...", "This enabled...")
|
|
925
|
+
- Keep sections focused - 3-6 sections is ideal
|
|
926
|
+
- Code should be 10-40 lines, enough context to understand the change
|
|
927
|
+
- Don't include unrelated code in before/after - focus on what changed
|
|
928
|
+
|
|
929
|
+
IMPORTANT: Output ONLY the JSON object. No markdown, no prose, no explanation. Start with { end with }.`;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Add Code Hike syntax highlighting to all code blocks
|
|
933
|
+
*/
|
|
934
|
+
async addHighlighting(walkthrough) {
|
|
935
|
+
const theme = 'github-dark';
|
|
936
|
+
const highlightCode = async (code, lang) => {
|
|
937
|
+
const normalizedLang = LANG_MAP[lang.toLowerCase()] || lang || 'text';
|
|
938
|
+
try {
|
|
939
|
+
return await highlight({ value: code, lang: normalizedLang, meta: '' }, theme);
|
|
940
|
+
}
|
|
941
|
+
catch (err) {
|
|
942
|
+
logger.warn('Failed to highlight code', { lang: normalizedLang, error: err });
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
const inferLang = (file) => {
|
|
947
|
+
if (!file)
|
|
948
|
+
return 'typescript';
|
|
949
|
+
const ext = file.split('.').pop()?.toLowerCase() || '';
|
|
950
|
+
return LANG_MAP[ext] || ext || 'typescript';
|
|
951
|
+
};
|
|
952
|
+
// Highlight orientation keyFiles
|
|
953
|
+
if (walkthrough.orientation?.keyFiles) {
|
|
954
|
+
for (const keyFile of walkthrough.orientation.keyFiles) {
|
|
955
|
+
if (keyFile.relevantCode) {
|
|
956
|
+
const lang = inferLang(keyFile.file);
|
|
957
|
+
const highlighted = await highlightCode(keyFile.relevantCode, lang);
|
|
958
|
+
if (highlighted) {
|
|
959
|
+
keyFile.highlighted = highlighted;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
// Highlight sections and compute diffs
|
|
965
|
+
for (const section of walkthrough.sections) {
|
|
966
|
+
const sectionLang = section.language || section.codeBlocks?.[0]?.language || 'typescript';
|
|
967
|
+
// New scrollycoding format: highlight section.code
|
|
968
|
+
if (section.code) {
|
|
969
|
+
const lang = section.language || inferLang(section.file);
|
|
970
|
+
const highlighted = await highlightCode(section.code, lang);
|
|
971
|
+
if (highlighted) {
|
|
972
|
+
section.codeHighlighted = highlighted;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Legacy format: before/after
|
|
976
|
+
if (section.before?.code) {
|
|
977
|
+
const highlighted = await highlightCode(section.before.code, sectionLang);
|
|
978
|
+
if (highlighted) {
|
|
979
|
+
section.before.highlighted = highlighted;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (section.after?.code) {
|
|
983
|
+
const highlighted = await highlightCode(section.after.code, sectionLang);
|
|
984
|
+
if (highlighted) {
|
|
985
|
+
section.after.highlighted = highlighted;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
// Compute diff if we have before and after code
|
|
989
|
+
if (section.before?.code && section.after?.code) {
|
|
990
|
+
const filename = section.codeBlocks?.[0]?.file;
|
|
991
|
+
section.diff = this.computeDiff(section.before.code, section.after.code, filename);
|
|
992
|
+
}
|
|
993
|
+
if (section.codeBlocks) {
|
|
994
|
+
for (const block of section.codeBlocks) {
|
|
995
|
+
if (block.snippet) {
|
|
996
|
+
const highlighted = await highlightCode(block.snippet, block.language || 'typescript');
|
|
997
|
+
if (highlighted) {
|
|
998
|
+
block.highlighted = highlighted;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return walkthrough;
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Compute unified diff from old and new strings
|
|
1008
|
+
*/
|
|
1009
|
+
computeDiff(oldStr, newStr, filename) {
|
|
1010
|
+
const changes = Diff.diffLines(oldStr, newStr);
|
|
1011
|
+
const lines = [];
|
|
1012
|
+
let oldLineNum = 1;
|
|
1013
|
+
let newLineNum = 1;
|
|
1014
|
+
let added = 0;
|
|
1015
|
+
let removed = 0;
|
|
1016
|
+
for (const change of changes) {
|
|
1017
|
+
const changeLines = change.value.replace(/\n$/, '').split('\n');
|
|
1018
|
+
for (const line of changeLines) {
|
|
1019
|
+
if (change.added) {
|
|
1020
|
+
lines.push({
|
|
1021
|
+
type: 'added',
|
|
1022
|
+
content: line,
|
|
1023
|
+
lineNumber: { new: newLineNum },
|
|
1024
|
+
});
|
|
1025
|
+
newLineNum++;
|
|
1026
|
+
added++;
|
|
1027
|
+
}
|
|
1028
|
+
else if (change.removed) {
|
|
1029
|
+
lines.push({
|
|
1030
|
+
type: 'removed',
|
|
1031
|
+
content: line,
|
|
1032
|
+
lineNumber: { old: oldLineNum },
|
|
1033
|
+
});
|
|
1034
|
+
oldLineNum++;
|
|
1035
|
+
removed++;
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
lines.push({
|
|
1039
|
+
type: 'context',
|
|
1040
|
+
content: line,
|
|
1041
|
+
lineNumber: { old: oldLineNum, new: newLineNum },
|
|
1042
|
+
});
|
|
1043
|
+
oldLineNum++;
|
|
1044
|
+
newLineNum++;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
// Simplify to show only hunks with changes (context around changes)
|
|
1049
|
+
const contextLines = 3;
|
|
1050
|
+
const hunks = [];
|
|
1051
|
+
let currentHunk = null;
|
|
1052
|
+
let lastChangeIndex = -100;
|
|
1053
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1054
|
+
const line = lines[i];
|
|
1055
|
+
const isChange = line.type !== 'context';
|
|
1056
|
+
if (isChange) {
|
|
1057
|
+
// Start new hunk if needed
|
|
1058
|
+
if (!currentHunk || i - lastChangeIndex > contextLines * 2) {
|
|
1059
|
+
// Add trailing context to previous hunk
|
|
1060
|
+
if (currentHunk) {
|
|
1061
|
+
const trailingStart = lastChangeIndex + 1;
|
|
1062
|
+
const trailingEnd = Math.min(trailingStart + contextLines, i);
|
|
1063
|
+
for (let j = trailingStart; j < trailingEnd; j++) {
|
|
1064
|
+
if (lines[j].type === 'context') {
|
|
1065
|
+
currentHunk.lines.push(lines[j]);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
hunks.push(currentHunk);
|
|
1069
|
+
}
|
|
1070
|
+
// Start new hunk with leading context
|
|
1071
|
+
currentHunk = {
|
|
1072
|
+
header: `@@ changes @@`,
|
|
1073
|
+
lines: [],
|
|
1074
|
+
};
|
|
1075
|
+
const leadingStart = Math.max(0, i - contextLines);
|
|
1076
|
+
for (let j = leadingStart; j < i; j++) {
|
|
1077
|
+
if (lines[j].type === 'context') {
|
|
1078
|
+
currentHunk.lines.push(lines[j]);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
currentHunk.lines.push(line);
|
|
1083
|
+
lastChangeIndex = i;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
// Finish last hunk
|
|
1087
|
+
if (currentHunk && currentHunk.lines.length > 0) {
|
|
1088
|
+
const trailingStart = lastChangeIndex + 1;
|
|
1089
|
+
const trailingEnd = Math.min(trailingStart + contextLines, lines.length);
|
|
1090
|
+
for (let j = trailingStart; j < trailingEnd; j++) {
|
|
1091
|
+
if (lines[j].type === 'context') {
|
|
1092
|
+
currentHunk.lines.push(lines[j]);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
hunks.push(currentHunk);
|
|
1096
|
+
}
|
|
1097
|
+
// If no hunks (no changes), return empty
|
|
1098
|
+
if (hunks.length === 0) {
|
|
1099
|
+
return {
|
|
1100
|
+
hunks: [],
|
|
1101
|
+
stats: { added: 0, removed: 0 },
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
return {
|
|
1105
|
+
oldFile: filename,
|
|
1106
|
+
newFile: filename,
|
|
1107
|
+
hunks,
|
|
1108
|
+
stats: { added, removed },
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
//# sourceMappingURL=walkthrough-service.js.map
|