@howlil/ez-agents 3.4.1 → 3.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.
Files changed (162) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +84 -20
  3. package/agents/ez-observer-agent.md +260 -0
  4. package/agents/ez-release-agent.md +333 -0
  5. package/agents/ez-requirements-agent.md +377 -0
  6. package/agents/ez-scrum-master-agent.md +242 -0
  7. package/agents/ez-tech-lead-agent.md +267 -0
  8. package/bin/install.js +3221 -3230
  9. package/commands/ez/arch-review.md +102 -0
  10. package/commands/ez/execute-phase.md +11 -0
  11. package/commands/ez/export-session.md +79 -0
  12. package/commands/ez/gather-requirements.md +117 -0
  13. package/commands/ez/git-workflow.md +72 -0
  14. package/commands/ez/hotfix.md +120 -0
  15. package/commands/ez/import-session.md +82 -0
  16. package/commands/ez/join-discord.md +18 -18
  17. package/commands/ez/list-sessions.md +96 -0
  18. package/commands/ez/package-manager.md +316 -0
  19. package/commands/ez/plan-phase.md +9 -1
  20. package/commands/ez/preflight.md +79 -0
  21. package/commands/ez/progress.md +13 -1
  22. package/commands/ez/release.md +153 -0
  23. package/commands/ez/resume.md +107 -0
  24. package/commands/ez/standup.md +85 -0
  25. package/ez-agents/bin/ez-tools.cjs +1095 -716
  26. package/ez-agents/bin/lib/assistant-adapter.cjs +264 -264
  27. package/ez-agents/bin/lib/audit-exec.cjs +7 -2
  28. package/ez-agents/bin/lib/bdd-validator.cjs +622 -0
  29. package/ez-agents/bin/lib/circuit-breaker.cjs +118 -118
  30. package/ez-agents/bin/lib/config.cjs +190 -190
  31. package/ez-agents/bin/lib/content-scanner.cjs +238 -0
  32. package/ez-agents/bin/lib/context-cache.cjs +154 -0
  33. package/ez-agents/bin/lib/context-errors.cjs +71 -0
  34. package/ez-agents/bin/lib/context-manager.cjs +220 -0
  35. package/ez-agents/bin/lib/discussion-synthesizer.cjs +458 -0
  36. package/ez-agents/bin/lib/file-access.cjs +207 -0
  37. package/ez-agents/bin/lib/file-lock.cjs +236 -236
  38. package/ez-agents/bin/lib/frontmatter.cjs +299 -299
  39. package/ez-agents/bin/lib/fs-utils.cjs +153 -153
  40. package/ez-agents/bin/lib/git-errors.cjs +83 -0
  41. package/ez-agents/bin/lib/git-utils.cjs +118 -0
  42. package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
  43. package/ez-agents/bin/lib/index.cjs +157 -113
  44. package/ez-agents/bin/lib/init.cjs +757 -757
  45. package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
  46. package/ez-agents/bin/lib/logger.cjs +124 -124
  47. package/ez-agents/bin/lib/memory-compression.cjs +256 -0
  48. package/ez-agents/bin/lib/metrics-tracker.cjs +406 -0
  49. package/ez-agents/bin/lib/milestone.cjs +241 -241
  50. package/ez-agents/bin/lib/model-provider.cjs +241 -241
  51. package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
  52. package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
  53. package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
  54. package/ez-agents/bin/lib/phase.cjs +925 -925
  55. package/ez-agents/bin/lib/planning-write.cjs +107 -107
  56. package/ez-agents/bin/lib/release-validator.cjs +614 -0
  57. package/ez-agents/bin/lib/retry.cjs +119 -119
  58. package/ez-agents/bin/lib/roadmap.cjs +306 -306
  59. package/ez-agents/bin/lib/safe-exec.cjs +128 -128
  60. package/ez-agents/bin/lib/safe-path.cjs +130 -130
  61. package/ez-agents/bin/lib/session-chain.cjs +304 -0
  62. package/ez-agents/bin/lib/session-errors.cjs +81 -0
  63. package/ez-agents/bin/lib/session-export.cjs +251 -0
  64. package/ez-agents/bin/lib/session-import.cjs +262 -0
  65. package/ez-agents/bin/lib/session-manager.cjs +280 -0
  66. package/ez-agents/bin/lib/state.cjs +736 -736
  67. package/ez-agents/bin/lib/temp-file.cjs +239 -239
  68. package/ez-agents/bin/lib/template.cjs +223 -223
  69. package/ez-agents/bin/lib/test-file-lock.cjs +112 -112
  70. package/ez-agents/bin/lib/test-graceful.cjs +93 -93
  71. package/ez-agents/bin/lib/test-logger.cjs +60 -60
  72. package/ez-agents/bin/lib/test-safe-exec.cjs +38 -38
  73. package/ez-agents/bin/lib/test-safe-path.cjs +33 -33
  74. package/ez-agents/bin/lib/test-temp-file.cjs +125 -125
  75. package/ez-agents/bin/lib/tier-manager.cjs +428 -0
  76. package/ez-agents/bin/lib/timeout-exec.cjs +63 -63
  77. package/ez-agents/bin/lib/url-fetch.cjs +170 -0
  78. package/ez-agents/bin/lib/verify.cjs +15 -1
  79. package/ez-agents/references/checkpoints.md +776 -776
  80. package/ez-agents/references/continuation-format.md +249 -249
  81. package/ez-agents/references/metrics-schema.md +118 -0
  82. package/ez-agents/references/planning-config.md +140 -0
  83. package/ez-agents/references/questioning.md +162 -162
  84. package/ez-agents/references/tdd.md +263 -263
  85. package/ez-agents/references/tier-strategy.md +103 -0
  86. package/ez-agents/templates/bdd-feature.md +173 -0
  87. package/ez-agents/templates/codebase/concerns.md +310 -310
  88. package/ez-agents/templates/codebase/conventions.md +307 -307
  89. package/ez-agents/templates/codebase/integrations.md +280 -280
  90. package/ez-agents/templates/codebase/stack.md +186 -186
  91. package/ez-agents/templates/codebase/testing.md +480 -480
  92. package/ez-agents/templates/config.json +37 -37
  93. package/ez-agents/templates/continue-here.md +78 -78
  94. package/ez-agents/templates/discussion.md +68 -0
  95. package/ez-agents/templates/incident-runbook.md +205 -0
  96. package/ez-agents/templates/milestone-archive.md +123 -123
  97. package/ez-agents/templates/milestone.md +115 -115
  98. package/ez-agents/templates/release-checklist.md +133 -0
  99. package/ez-agents/templates/requirements.md +231 -231
  100. package/ez-agents/templates/research-project/ARCHITECTURE.md +204 -204
  101. package/ez-agents/templates/research-project/FEATURES.md +147 -147
  102. package/ez-agents/templates/research-project/PITFALLS.md +200 -200
  103. package/ez-agents/templates/research-project/STACK.md +120 -120
  104. package/ez-agents/templates/research-project/SUMMARY.md +170 -170
  105. package/ez-agents/templates/retrospective.md +54 -54
  106. package/ez-agents/templates/roadmap.md +202 -202
  107. package/ez-agents/templates/rollback-plan.md +201 -0
  108. package/ez-agents/templates/summary-minimal.md +41 -41
  109. package/ez-agents/templates/summary-standard.md +48 -48
  110. package/ez-agents/templates/summary.md +248 -248
  111. package/ez-agents/templates/user-setup.md +311 -311
  112. package/ez-agents/templates/verification-report.md +322 -322
  113. package/ez-agents/workflows/add-phase.md +112 -112
  114. package/ez-agents/workflows/add-tests.md +351 -351
  115. package/ez-agents/workflows/add-todo.md +158 -158
  116. package/ez-agents/workflows/arch-review.md +54 -0
  117. package/ez-agents/workflows/audit-milestone.md +332 -332
  118. package/ez-agents/workflows/autonomous.md +131 -30
  119. package/ez-agents/workflows/check-todos.md +177 -177
  120. package/ez-agents/workflows/cleanup.md +152 -152
  121. package/ez-agents/workflows/complete-milestone.md +766 -766
  122. package/ez-agents/workflows/diagnose-issues.md +219 -219
  123. package/ez-agents/workflows/discovery-phase.md +289 -289
  124. package/ez-agents/workflows/discuss-phase.md +762 -762
  125. package/ez-agents/workflows/execute-phase.md +513 -468
  126. package/ez-agents/workflows/execute-plan.md +483 -483
  127. package/ez-agents/workflows/export-session.md +255 -0
  128. package/ez-agents/workflows/gather-requirements.md +206 -0
  129. package/ez-agents/workflows/health.md +159 -159
  130. package/ez-agents/workflows/help.md +584 -492
  131. package/ez-agents/workflows/hotfix.md +291 -0
  132. package/ez-agents/workflows/import-session.md +303 -0
  133. package/ez-agents/workflows/insert-phase.md +130 -130
  134. package/ez-agents/workflows/list-phase-assumptions.md +178 -178
  135. package/ez-agents/workflows/map-codebase.md +316 -316
  136. package/ez-agents/workflows/new-milestone.md +339 -10
  137. package/ez-agents/workflows/new-project.md +293 -299
  138. package/ez-agents/workflows/node-repair.md +92 -92
  139. package/ez-agents/workflows/pause-work.md +122 -122
  140. package/ez-agents/workflows/plan-milestone-gaps.md +274 -274
  141. package/ez-agents/workflows/plan-phase.md +673 -651
  142. package/ez-agents/workflows/progress.md +372 -382
  143. package/ez-agents/workflows/quick.md +610 -610
  144. package/ez-agents/workflows/release.md +253 -0
  145. package/ez-agents/workflows/remove-phase.md +155 -155
  146. package/ez-agents/workflows/research-phase.md +74 -74
  147. package/ez-agents/workflows/resume-project.md +307 -307
  148. package/ez-agents/workflows/resume-session.md +215 -0
  149. package/ez-agents/workflows/set-profile.md +81 -81
  150. package/ez-agents/workflows/settings.md +242 -242
  151. package/ez-agents/workflows/standup.md +64 -0
  152. package/ez-agents/workflows/stats.md +57 -57
  153. package/ez-agents/workflows/transition.md +544 -544
  154. package/ez-agents/workflows/ui-phase.md +290 -290
  155. package/ez-agents/workflows/ui-review.md +157 -157
  156. package/ez-agents/workflows/update.md +320 -320
  157. package/ez-agents/workflows/validate-phase.md +167 -167
  158. package/ez-agents/workflows/verify-phase.md +243 -243
  159. package/ez-agents/workflows/verify-work.md +584 -584
  160. package/package.json +10 -4
  161. package/scripts/build-hooks.js +43 -43
  162. package/scripts/run-tests.cjs +29 -29
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Session Chain — Navigate linked sessions
5
+ *
6
+ * Provides chain navigation, visualization, and repair capabilities
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { SessionChainError } = require('./session-errors.cjs');
12
+ const { defaultLogger: logger } = require('./logger.cjs');
13
+
14
+ class SessionChain {
15
+ /**
16
+ * Create a SessionChain instance
17
+ * @param {Object} sessionManager - SessionManager instance
18
+ */
19
+ constructor(sessionManager) {
20
+ this.sessionManager = sessionManager;
21
+ }
22
+
23
+ /**
24
+ * Navigate to adjacent session in chain
25
+ * @param {string} sessionId - Current session ID
26
+ * @param {string} direction - Navigation direction ('previous' or 'next')
27
+ * @returns {Object|null} Adjacent session or null
28
+ */
29
+ navigate(sessionId, direction) {
30
+ const session = this.sessionManager.loadSession(sessionId);
31
+ if (!session) {
32
+ return null;
33
+ }
34
+
35
+ const chain = session.metadata?.session_chain || [];
36
+ const currentIndex = chain.indexOf(sessionId);
37
+
38
+ if (currentIndex === -1) {
39
+ // Session not in chain, check if it's the last one
40
+ logger.warn('Session not in chain', { sessionId });
41
+ return null;
42
+ }
43
+
44
+ if (direction === 'previous') {
45
+ if (currentIndex > 0) {
46
+ const previousId = chain[currentIndex - 1];
47
+ return this.sessionManager.loadSession(previousId);
48
+ }
49
+ return null;
50
+ }
51
+
52
+ if (direction === 'next') {
53
+ if (currentIndex < chain.length - 1) {
54
+ const nextId = chain[currentIndex + 1];
55
+ return this.sessionManager.loadSession(nextId);
56
+ }
57
+ return null;
58
+ }
59
+
60
+ throw new SessionChainError(`Invalid direction: ${direction}`, chain);
61
+ }
62
+
63
+ /**
64
+ * Get full chain as array of session objects
65
+ * @param {string} sessionId - Session ID in the chain
66
+ * @returns {Array} Array of session objects
67
+ */
68
+ getChain(sessionId) {
69
+ const session = this.sessionManager.loadSession(sessionId);
70
+ if (!session) {
71
+ return [];
72
+ }
73
+
74
+ const chain = session.metadata?.session_chain || [];
75
+ const chainSessions = [];
76
+
77
+ for (const id of chain) {
78
+ const chainSession = this.sessionManager.loadSession(id);
79
+ if (chainSession) {
80
+ chainSessions.push(chainSession);
81
+ } else {
82
+ logger.warn('Missing session in chain', { id });
83
+ }
84
+ }
85
+
86
+ // Include current session if not already in chain
87
+ if (!chain.includes(sessionId)) {
88
+ chainSessions.push(session);
89
+ }
90
+
91
+ return chainSessions;
92
+ }
93
+
94
+ /**
95
+ * Get chain visualization string
96
+ * @param {string} sessionId - Session ID
97
+ * @returns {string} Formatted chain visualization
98
+ */
99
+ getChainVisualization(sessionId) {
100
+ const session = this.sessionManager.loadSession(sessionId);
101
+ if (!session) {
102
+ return `Session not found: ${sessionId}`;
103
+ }
104
+
105
+ const chain = session.metadata?.session_chain || [];
106
+ const currentIndex = chain.indexOf(sessionId);
107
+
108
+ let viz = `Session Chain for ${sessionId}:\n\n`;
109
+
110
+ chain.forEach((id, index) => {
111
+ const chainSession = this.sessionManager.loadSession(id);
112
+ const startedAt = chainSession?.metadata?.started_at || 'Unknown';
113
+ const status = chainSession?.metadata?.status || 'unknown';
114
+ const marker = index === currentIndex ? ' <-- Current' : '';
115
+ viz += `[${index + 1}] ${id} (${startedAt}) - ${status}${marker}\n`;
116
+ });
117
+
118
+ if (!chain.includes(sessionId)) {
119
+ const startedAt = session.metadata?.started_at || 'Unknown';
120
+ const status = session.metadata?.status || 'unknown';
121
+ viz += `[${chain.length + 1}] ${sessionId} (${startedAt}) - ${status} <-- Current\n`;
122
+ }
123
+
124
+ viz += `\nNavigation:\n`;
125
+ if (currentIndex > 0) {
126
+ viz += `- Previous: ${chain[currentIndex - 1]}\n`;
127
+ } else {
128
+ viz += `- Previous: none\n`;
129
+ }
130
+
131
+ if (currentIndex < chain.length - 1) {
132
+ viz += `- Next: ${chain[currentIndex + 1]}\n`;
133
+ } else {
134
+ viz += `- Next: none\n`;
135
+ }
136
+
137
+ return viz;
138
+ }
139
+
140
+ /**
141
+ * Repair broken chain links
142
+ * @param {string} sessionId - Session ID
143
+ * @returns {Object} Repair result with warnings
144
+ */
145
+ repairChain(sessionId) {
146
+ const session = this.sessionManager.loadSession(sessionId);
147
+ if (!session) {
148
+ throw new SessionChainError(`Session not found: ${sessionId}`, []);
149
+ }
150
+
151
+ const chain = session.metadata?.session_chain || [];
152
+ const warnings = [];
153
+ const repaired = [];
154
+
155
+ // Get all available sessions
156
+ const allSessions = this.sessionManager.listSessions();
157
+ const availableIds = new Set(allSessions.map(s => s.session_id));
158
+
159
+ // Find missing links
160
+ const missingLinks = [];
161
+ for (const id of chain) {
162
+ if (!availableIds.has(id)) {
163
+ missingLinks.push(id);
164
+ }
165
+ }
166
+
167
+ if (missingLinks.length === 0) {
168
+ return { repaired: false, warnings: ['Chain is intact'] };
169
+ }
170
+
171
+ // Attempt to repair by finding closest timestamp match
172
+ for (const missingId of missingLinks) {
173
+ const match = this._findClosestSessionMatch(missingId, allSessions);
174
+ if (match) {
175
+ logger.info('Auto-repaired chain link', { missing: missingId, found: match.session_id });
176
+ repaired.push({ missing: missingId, found: match.session_id });
177
+ } else {
178
+ warnings.push(`Unrecoverable link: ${missingId}`);
179
+ }
180
+ }
181
+
182
+ // Update chain with repaired links
183
+ if (repaired.length > 0) {
184
+ const newChain = chain.map(id => {
185
+ const repair = repaired.find(r => r.missing === id);
186
+ return repair ? repair.found : id;
187
+ });
188
+
189
+ this.sessionManager.updateSession(sessionId, {
190
+ metadata: { session_chain: newChain }
191
+ });
192
+ }
193
+
194
+ return {
195
+ repaired: repaired.length > 0,
196
+ repairs: repaired,
197
+ warnings
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Find closest session match by timestamp
203
+ * @private
204
+ */
205
+ _findClosestSessionMatch(missingId, allSessions) {
206
+ // Extract timestamp from missing ID
207
+ const timestampMatch = missingId.match(/session-(.+)/);
208
+ if (!timestampMatch) {
209
+ return null;
210
+ }
211
+
212
+ const missingTimestamp = timestampMatch[1];
213
+
214
+ // Find session with closest timestamp
215
+ let closestMatch = null;
216
+ let minDiff = Infinity;
217
+
218
+ for (const session of allSessions) {
219
+ const sessionTimestamp = session.session_id.replace('session-', '');
220
+ const diff = this._compareTimestamps(missingTimestamp, sessionTimestamp);
221
+
222
+ if (diff < minDiff) {
223
+ minDiff = diff;
224
+ closestMatch = session;
225
+ }
226
+ }
227
+
228
+ // Only return match if within reasonable threshold (1 hour)
229
+ if (minDiff < 3600000) {
230
+ return closestMatch;
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Compare timestamps and return difference in ms
238
+ * @private
239
+ */
240
+ _compareTimestamps(ts1, ts2) {
241
+ try {
242
+ const date1 = new Date(ts1.replace(/-/g, ':').replace('T', ' '));
243
+ const date2 = new Date(ts2.replace(/-/g, ':').replace('T', ' '));
244
+ return Math.abs(date1 - date2);
245
+ } catch {
246
+ return Infinity;
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Add session to chain
252
+ * @param {string} sessionId - Session ID to add to
253
+ * @param {string} linkedSessionId - Session ID to link
254
+ * @param {string} position - Position ('before' or 'after')
255
+ * @returns {boolean} Success
256
+ */
257
+ addToChain(sessionId, linkedSessionId, position = 'after') {
258
+ const session = this.sessionManager.loadSession(sessionId);
259
+ if (!session) {
260
+ return false;
261
+ }
262
+
263
+ const linkedSession = this.sessionManager.loadSession(linkedSessionId);
264
+ if (!linkedSession) {
265
+ return false;
266
+ }
267
+
268
+ let chain = session.metadata?.session_chain || [];
269
+
270
+ // Ensure current session is in chain
271
+ if (!chain.includes(sessionId)) {
272
+ chain.push(sessionId);
273
+ }
274
+
275
+ const currentIndex = chain.indexOf(sessionId);
276
+
277
+ if (position === 'after') {
278
+ // Insert after current
279
+ chain.splice(currentIndex + 1, 0, linkedSessionId);
280
+ } else if (position === 'before') {
281
+ // Insert before current
282
+ chain.splice(currentIndex, 0, linkedSessionId);
283
+ }
284
+
285
+ // Update both sessions
286
+ this.sessionManager.updateSession(sessionId, {
287
+ metadata: { session_chain: chain }
288
+ });
289
+
290
+ // Also update linked session's chain
291
+ const linkedChain = linkedSession.metadata?.session_chain || [];
292
+ if (!linkedChain.includes(sessionId)) {
293
+ linkedChain.push(sessionId);
294
+ this.sessionManager.updateSession(linkedSessionId, {
295
+ metadata: { session_chain: linkedChain }
296
+ });
297
+ }
298
+
299
+ logger.info('Session added to chain', { sessionId, linkedSessionId, position });
300
+ return true;
301
+ }
302
+ }
303
+
304
+ module.exports = SessionChain;
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Session Error Classes
5
+ *
6
+ * Provides structured error handling for session operations
7
+ */
8
+
9
+ class SessionError extends Error {
10
+ constructor(message, options = {}) {
11
+ super(message);
12
+ this.name = 'SessionError';
13
+ this.code = options.code || 'SESSION_ERROR';
14
+ this.details = options.details || {};
15
+ this.timestamp = new Date().toISOString();
16
+ Error.captureStackTrace(this, SessionError);
17
+ }
18
+
19
+ toJSON() {
20
+ return {
21
+ name: this.name,
22
+ code: this.code,
23
+ message: this.message,
24
+ details: this.details,
25
+ timestamp: this.timestamp
26
+ };
27
+ }
28
+ }
29
+
30
+ class SessionNotFoundError extends SessionError {
31
+ constructor(sessionId, options = {}) {
32
+ super(`Session '${sessionId}' not found`, {
33
+ code: 'SESSION_NOT_FOUND',
34
+ details: { sessionId, ...options.details }
35
+ });
36
+ this.name = 'SessionNotFoundError';
37
+ this.sessionId = sessionId;
38
+ }
39
+ }
40
+
41
+ class SessionChainError extends SessionError {
42
+ constructor(message, chain = [], options = {}) {
43
+ super(message, {
44
+ code: 'SESSION_CHAIN_ERROR',
45
+ details: { chain, ...options.details }
46
+ });
47
+ this.name = 'SessionChainError';
48
+ this.chain = chain;
49
+ }
50
+ }
51
+
52
+ class SessionExportError extends SessionError {
53
+ constructor(format, reason, options = {}) {
54
+ super(`Export failed for format '${format}': ${reason}`, {
55
+ code: 'SESSION_EXPORT_ERROR',
56
+ details: { format, reason, ...options.details }
57
+ });
58
+ this.name = 'SessionExportError';
59
+ this.format = format;
60
+ this.reason = reason;
61
+ }
62
+ }
63
+
64
+ class SessionImportError extends SessionError {
65
+ constructor(message, validationErrors = [], options = {}) {
66
+ super(message, {
67
+ code: 'SESSION_IMPORT_ERROR',
68
+ details: { validationErrors, ...options.details }
69
+ });
70
+ this.name = 'SessionImportError';
71
+ this.validationErrors = validationErrors;
72
+ }
73
+ }
74
+
75
+ module.exports = {
76
+ SessionError,
77
+ SessionNotFoundError,
78
+ SessionChainError,
79
+ SessionExportError,
80
+ SessionImportError
81
+ };
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Session Export — Export session data for model handoff
5
+ *
6
+ * Supports markdown and JSON export formats
7
+ */
8
+
9
+ const { SessionExportError, SessionNotFoundError } = require('./session-errors.cjs');
10
+ const { safePlanningWriteSync } = require('./planning-write.cjs');
11
+
12
+ class SessionExport {
13
+ /**
14
+ * Create a SessionExport instance
15
+ * @param {Object} sessionManager - SessionManager instance
16
+ */
17
+ constructor(sessionManager) {
18
+ this.sessionManager = sessionManager;
19
+ }
20
+
21
+ /**
22
+ * Export a session in the specified format
23
+ * @param {string} sessionId - Session ID
24
+ * @param {string} format - Export format ('markdown' or 'json')
25
+ * @returns {Object} Export result with content and path
26
+ */
27
+ export(sessionId, format = 'markdown') {
28
+ const session = this.sessionManager.loadSession(sessionId);
29
+ if (!session) {
30
+ throw new SessionNotFoundError(sessionId);
31
+ }
32
+
33
+ let content;
34
+ switch (format) {
35
+ case 'markdown':
36
+ content = this.toMarkdown(session);
37
+ break;
38
+ case 'json':
39
+ content = this.toJSON(session);
40
+ break;
41
+ default:
42
+ throw new SessionExportError(format, 'Unsupported format');
43
+ }
44
+
45
+ const ext = format === 'markdown' ? 'md' : 'json';
46
+ const outputPath = `.planning/sessions/export-${sessionId}.${ext}`;
47
+
48
+ return {
49
+ success: true,
50
+ format,
51
+ content,
52
+ outputPath
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Convert session to markdown format
58
+ * @param {Object} session - Session object
59
+ * @returns {string} Markdown string
60
+ */
61
+ toMarkdown(session) {
62
+ const { metadata, context, state } = session;
63
+ const exportedAt = new Date().toISOString();
64
+
65
+ // Calculate duration
66
+ let duration = 'N/A';
67
+ if (metadata.started_at && metadata.ended_at) {
68
+ const start = new Date(metadata.started_at);
69
+ const end = new Date(metadata.ended_at);
70
+ const diffMs = end - start;
71
+ const diffHrs = Math.floor(diffMs / (1000 * 60 * 60));
72
+ const diffMins = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
73
+ if (diffHrs > 0) {
74
+ duration = `${diffHrs}h ${diffMins}m`;
75
+ } else {
76
+ duration = `${diffMins}m`;
77
+ }
78
+ }
79
+
80
+ // Build markdown
81
+ let md = `# Session Export: ${metadata.session_id}\n\n`;
82
+ md += `**Exported:** ${exportedAt}\n`;
83
+ md += `**Model:** ${metadata.model || 'Unknown'}\n`;
84
+ md += `**Phase:** ${metadata.phase || 'N/A'}\n`;
85
+ md += `**Plan:** ${metadata.plan || 'N/A'}\n`;
86
+ md += `**Duration:** ${duration}\n\n`;
87
+ md += `---\n\n`;
88
+
89
+ // Session Summary
90
+ md += `## Session Summary\n\n`;
91
+ md += `**Objective:** ${state.next_recommended_action || 'Not specified'}\n\n`;
92
+
93
+ md += `**Completed:**\n`;
94
+ if (context.tasks && context.tasks.length > 0) {
95
+ const completedTasks = context.tasks.filter(t => t.status === 'completed');
96
+ if (completedTasks.length > 0) {
97
+ completedTasks.forEach(t => md += `- ${t.name || t.description || 'Task'}\n`);
98
+ } else {
99
+ md += `- None\n`;
100
+ }
101
+ } else {
102
+ md += `- None\n`;
103
+ }
104
+ md += `\n`;
105
+
106
+ md += `**Incomplete:**\n`;
107
+ if (state.incomplete_tasks && state.incomplete_tasks.length > 0) {
108
+ state.incomplete_tasks.forEach(t => md += `- ${t}\n`);
109
+ } else {
110
+ md += `- None\n`;
111
+ }
112
+ md += `\n`;
113
+
114
+ md += `---\n\n`;
115
+
116
+ // Key Decisions
117
+ md += `## Key Decisions\n\n`;
118
+ if (context.decisions && context.decisions.length > 0) {
119
+ context.decisions.forEach((decision, index) => {
120
+ md += `${index + 1}. **${decision.title || 'Decision'}**\n`;
121
+ if (decision.rationale) {
122
+ md += ` - Rationale: ${decision.rationale}\n`;
123
+ }
124
+ if (decision.status) {
125
+ md += ` - Status: ${decision.status}\n`;
126
+ }
127
+ });
128
+ } else {
129
+ md += `No decisions recorded\n`;
130
+ }
131
+ md += `\n`;
132
+
133
+ md += `---\n\n`;
134
+
135
+ // File Changes
136
+ md += `## File Changes\n\n`;
137
+ if (context.file_changes && context.file_changes.length > 0) {
138
+ md += `| File | Action | Reason |\n`;
139
+ md += `|------|--------|--------|\n`;
140
+ context.file_changes.forEach(change => {
141
+ md += `| ${change.file || 'Unknown'} | ${change.action || 'modified'} | ${change.reason || '-'} |\n`;
142
+ });
143
+ } else {
144
+ md += `No file changes recorded\n`;
145
+ }
146
+ md += `\n`;
147
+
148
+ md += `---\n\n`;
149
+
150
+ // Open Questions
151
+ md += `## Open Questions\n\n`;
152
+ if (context.open_questions && context.open_questions.length > 0) {
153
+ context.open_questions.forEach(q => md += `- ${q}\n`);
154
+ } else {
155
+ md += `None\n`;
156
+ }
157
+ md += `\n`;
158
+
159
+ md += `---\n\n`;
160
+
161
+ // Blockers
162
+ md += `## Blockers/Concerns\n\n`;
163
+ if (context.blockers && context.blockers.length > 0) {
164
+ context.blockers.forEach(b => md += `- ${b}\n`);
165
+ } else {
166
+ md += `None\n`;
167
+ }
168
+ md += `\n`;
169
+
170
+ md += `---\n\n`;
171
+
172
+ // Recommended Next Actions
173
+ md += `## Recommended Next Actions\n\n`;
174
+ if (state.next_recommended_action) {
175
+ md += `- ${state.next_recommended_action}\n`;
176
+ } else {
177
+ md += `None\n`;
178
+ }
179
+ md += `\n`;
180
+
181
+ md += `---\n\n`;
182
+
183
+ // Session Chain
184
+ md += `## Session Chain\n\n`;
185
+ const chain = metadata.session_chain || [];
186
+ const currentIndex = chain.indexOf(metadata.session_id);
187
+
188
+ if (chain.length > 0) {
189
+ md += `- Previous: ${currentIndex > 0 ? chain[currentIndex - 1] : 'none'}\n`;
190
+ md += `- Current: ${metadata.session_id}\n`;
191
+ md += `- Next: ${currentIndex < chain.length - 1 ? chain[currentIndex + 1] : 'none'}\n`;
192
+ } else {
193
+ md += `- Previous: none\n`;
194
+ md += `- Current: ${metadata.session_id}\n`;
195
+ md += `- Next: none\n`;
196
+ }
197
+ md += `\n`;
198
+
199
+ // Token Usage (if available)
200
+ if (metadata.token_usage && (metadata.token_usage.input || metadata.token_usage.output)) {
201
+ md += `---\n\n`;
202
+ md += `## Token Usage\n\n`;
203
+ md += `- Input: ${metadata.token_usage.input || 0}\n`;
204
+ md += `- Output: ${metadata.token_usage.output || 0}\n`;
205
+ md += `- Total: ${metadata.token_usage.total || 0}\n`;
206
+ md += `\n`;
207
+ }
208
+
209
+ return md;
210
+ }
211
+
212
+ /**
213
+ * Convert session to JSON format
214
+ * @param {Object} session - Session object
215
+ * @returns {string} JSON string
216
+ */
217
+ toJSON(session) {
218
+ const exportData = {
219
+ export_version: '1.0',
220
+ exported_at: new Date().toISOString(),
221
+ export_format: 'json',
222
+ session: session
223
+ };
224
+ return JSON.stringify(exportData, null, 2);
225
+ }
226
+
227
+ /**
228
+ * Export session to file
229
+ * @param {string} sessionId - Session ID
230
+ * @param {string} format - Export format
231
+ * @param {string} outputPath - Output file path
232
+ * @returns {Object} Export result with path
233
+ */
234
+ exportToFile(sessionId, format, outputPath) {
235
+ const result = this.export(sessionId, format);
236
+
237
+ if (!outputPath) {
238
+ outputPath = result.outputPath;
239
+ }
240
+
241
+ safePlanningWriteSync(outputPath, result.content);
242
+
243
+ return {
244
+ success: true,
245
+ path: outputPath,
246
+ format
247
+ };
248
+ }
249
+ }
250
+
251
+ module.exports = SessionExport;