jettypod 3.0.1

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 (122) hide show
  1. package/.claude/PROTECT_SKILLS.md +28 -0
  2. package/.claude/settings.json +24 -0
  3. package/.claude/settings.local.json +16 -0
  4. package/.claude/skills/epic-discover/SKILL.md +262 -0
  5. package/.claude/skills/feature-discover/SKILL.md +393 -0
  6. package/.claude/skills/speed-mode/SKILL.md +364 -0
  7. package/.claude/skills/stable-mode/SKILL.md +591 -0
  8. package/.github/workflows/test-safety.yml +85 -0
  9. package/README.md +25 -0
  10. package/SPEED-STABLE-AUDIT.md +853 -0
  11. package/SYSTEM-BEHAVIOR.md +1241 -0
  12. package/TEST_SAFETY_AUDIT.md +314 -0
  13. package/TEST_SAFETY_IMPLEMENTATION.md +97 -0
  14. package/cucumber.js +8 -0
  15. package/docs/COMMAND_REFERENCE.md +903 -0
  16. package/docs/DECISIONS.md +68 -0
  17. package/docs/README.md +48 -0
  18. package/docs/STANDARDS-SYSTEM-DOCUMENTATION.md +374 -0
  19. package/docs/TEST-REWRITE-PLAN.md +261 -0
  20. package/docs/ai-test-writing-requirements.md +219 -0
  21. package/docs/claude-code-skills.md +607 -0
  22. package/docs/core-jettypod-methodology/comprehensive-jettypod-methodology.md +582 -0
  23. package/docs/core-jettypod-methodology/deprecated/jettypod-comprehensive-standards.md +1222 -0
  24. package/docs/core-jettypod-methodology/deprecated/jettypod-operating-guide.md +3399 -0
  25. package/docs/core-jettypod-methodology/deprecated/jettypod-technical-checklist.md +1325 -0
  26. package/docs/core-jettypod-methodology/deprecated/jettypod-vibe-coding-framework.md +1544 -0
  27. package/docs/core-jettypod-methodology/deprecated/prompt-engineering-guide.md +320 -0
  28. package/docs/core-jettypod-methodology/deprecated/vibe-coding-cheatsheet (1).md +516 -0
  29. package/docs/core-jettypod-methodology/deprecated/vibe-coding-framework.md +1544 -0
  30. package/docs/features/jettypod-standards-explained.md +543 -0
  31. package/docs/features/standards-inventory.md +257 -0
  32. package/docs/gap-analysis-current-vs-comprehensive-methodology.md +939 -0
  33. package/docs/jettypod-system-overview.md +409 -0
  34. package/features/auto-generate-production-chores.feature +14 -0
  35. package/features/claude-md-protection/steps.js +487 -0
  36. package/features/decisions/index.js +490 -0
  37. package/features/decisions/index.test.js +208 -0
  38. package/features/git-hooks/git-hooks.feature +30 -0
  39. package/features/git-hooks/index.js +93 -0
  40. package/features/git-hooks/index.test.js +137 -0
  41. package/features/git-hooks/post-commit +56 -0
  42. package/features/git-hooks/post-merge +47 -0
  43. package/features/git-hooks/pre-commit +28 -0
  44. package/features/git-hooks/simple-steps.js +53 -0
  45. package/features/git-hooks/simple-test.feature +10 -0
  46. package/features/git-hooks/steps.js +196 -0
  47. package/features/jettypod-update-command.feature +46 -0
  48. package/features/mode-prompts/index.js +95 -0
  49. package/features/mode-prompts/simple-steps.js +44 -0
  50. package/features/mode-prompts/simple-test.feature +9 -0
  51. package/features/mode-prompts/validation.test.js +120 -0
  52. package/features/refactor-mode/steps.js +217 -0
  53. package/features/refactor-mode.feature +49 -0
  54. package/features/skills-update/index.test.js +216 -0
  55. package/features/step_definitions/auto-generate-production-chores.steps.js +162 -0
  56. package/features/step_definitions/terminal-logo.steps.js +145 -0
  57. package/features/step_definitions/update-command.steps.js +183 -0
  58. package/features/terminal-logo/index.js +39 -0
  59. package/features/terminal-logo/terminal-logo.feature +30 -0
  60. package/features/update-command/index.js +181 -0
  61. package/features/update-command/index.test.js +225 -0
  62. package/features/work-commands/bug-workflow-display.feature +22 -0
  63. package/features/work-commands/index.js +311 -0
  64. package/features/work-commands/simple-steps.js +69 -0
  65. package/features/work-commands/stable-tests.feature +57 -0
  66. package/features/work-commands/steps.js +1120 -0
  67. package/features/work-commands/validation.test.js +88 -0
  68. package/features/work-commands/work-commands.feature +13 -0
  69. package/features/work-tracking/discovery-validation.test.js +228 -0
  70. package/features/work-tracking/index.js +1511 -0
  71. package/features/work-tracking/mode-required.feature +112 -0
  72. package/features/work-tracking/phase-tracking.test.js +482 -0
  73. package/features/work-tracking/prototype-tracking.test.js +485 -0
  74. package/features/work-tracking/tree-view.test.js +310 -0
  75. package/features/work-tracking/work-set-mode.feature +71 -0
  76. package/features/work-tracking/work-start-mode.feature +88 -0
  77. package/full-test.txt +0 -0
  78. package/install.sh +89 -0
  79. package/jettypod.js +1640 -0
  80. package/lib/bug-workflow.js +94 -0
  81. package/lib/bug-workflow.test.js +177 -0
  82. package/lib/claudemd.js +130 -0
  83. package/lib/claudemd.test.js +195 -0
  84. package/lib/comprehensive-standards-full.json +1778 -0
  85. package/lib/config.js +181 -0
  86. package/lib/config.test.js +511 -0
  87. package/lib/constants.js +107 -0
  88. package/lib/constants.test.js +164 -0
  89. package/lib/current-work.js +130 -0
  90. package/lib/current-work.test.js +146 -0
  91. package/lib/database-project-config.test.js +107 -0
  92. package/lib/database.js +256 -0
  93. package/lib/database.test.js +106 -0
  94. package/lib/decisions-generator.js +102 -0
  95. package/lib/decisions-generator.test.js +457 -0
  96. package/lib/decisions-helpers.js +119 -0
  97. package/lib/decisions-helpers.test.js +310 -0
  98. package/lib/discovery-checkpoint.js +83 -0
  99. package/lib/docs-generator.js +280 -0
  100. package/lib/external-checklist.js +177 -0
  101. package/lib/git.js +142 -0
  102. package/lib/git.test.js +145 -0
  103. package/lib/logo.js +3 -0
  104. package/lib/migrations/001-epic-to-parent.js +24 -0
  105. package/lib/migrations/002-default-work-item-modes.js +37 -0
  106. package/lib/migrations/002-default-work-item-modes.test.js +351 -0
  107. package/lib/migrations/003-epic-discovery-fields.js +52 -0
  108. package/lib/migrations/004-discovery-decisions-table.js +32 -0
  109. package/lib/migrations/005-migrate-decision-data.js +62 -0
  110. package/lib/migrations/006-feature-phase-field.js +61 -0
  111. package/lib/migrations/007-prototype-tracking.js +38 -0
  112. package/lib/migrations/008-scenario-file-field.js +24 -0
  113. package/lib/migrations/index.js +74 -0
  114. package/lib/production-helpers.js +69 -0
  115. package/lib/project-state.test.js +92 -0
  116. package/lib/test-helpers.js +184 -0
  117. package/lib/test-helpers.test.js +255 -0
  118. package/package.json +36 -0
  119. package/prototypes/test/index.html +1 -0
  120. package/setup-dist-repo.sh +68 -0
  121. package/test-safety-check.sh +80 -0
  122. package/work-item-tracking-plan.md +199 -0
@@ -0,0 +1,256 @@
1
+ const sqlite3 = require('sqlite3').verbose();
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ // Singleton database connection
6
+ let db = null;
7
+ let cachedJettypodDir = null;
8
+ let cachedDbPath = null;
9
+ let isClosing = false;
10
+
11
+ // Dynamic getters for paths (recompute on directory change)
12
+ function getJettypodDir() {
13
+ const cwd = process.cwd();
14
+ if (!cachedJettypodDir || !cachedJettypodDir.startsWith(cwd)) {
15
+ cachedJettypodDir = path.join(cwd, '.jettypod');
16
+ // Use separate database for tests
17
+ const dbFileName = process.env.NODE_ENV === 'test' ? 'test-work.db' : 'work.db';
18
+ cachedDbPath = path.join(cachedJettypodDir, dbFileName);
19
+ }
20
+ return cachedJettypodDir;
21
+ }
22
+
23
+ function getDbPath() {
24
+ getJettypodDir(); // Ensure cache is up to date
25
+ return cachedDbPath;
26
+ }
27
+
28
+ // Export as properties for backwards compatibility
29
+ const jettypodDir = getJettypodDir();
30
+ const dbPath = getDbPath();
31
+
32
+ /**
33
+ * Ensure .jettypod directory exists with proper permissions
34
+ * @throws {Error} If directory cannot be created or lacks write permissions
35
+ */
36
+ function ensureJettyPodDir() {
37
+ const dir = getJettypodDir();
38
+ try {
39
+ if (!fs.existsSync(dir)) {
40
+ fs.mkdirSync(dir, { recursive: true });
41
+ }
42
+
43
+ // Check write permissions
44
+ fs.accessSync(dir, fs.constants.W_OK);
45
+ } catch (err) {
46
+ if (err.code === 'EACCES') {
47
+ throw new Error(`No write permission for directory: ${dir}`);
48
+ }
49
+ throw new Error(`Failed to create JettyPod directory: ${err.message}`);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Get singleton database connection, creating schema if needed
55
+ * @returns {sqlite3.Database} Database connection
56
+ * @throws {Error} If database cannot be opened or schema fails
57
+ */
58
+ function getDb() {
59
+ // Check if directory changed - if so, close and reset
60
+ const dbFileName = process.env.NODE_ENV === 'test' ? 'test-work.db' : 'work.db';
61
+ const currentPath = path.join(process.cwd(), '.jettypod', dbFileName);
62
+ if (db && cachedDbPath && cachedDbPath !== currentPath) {
63
+ // Don't close synchronously - just null it out and let old connection be garbage collected
64
+ // This avoids FATAL errors from active prepared statements
65
+ const oldDb = db;
66
+ db = null;
67
+ cachedDbPath = null;
68
+ cachedJettypodDir = null; // Also reset this so paths are recomputed
69
+
70
+ // Schedule async close for cleanup (don't wait for it)
71
+ setImmediate(() => {
72
+ try {
73
+ oldDb.close((err) => {
74
+ if (err) {
75
+ // Silently ignore - connection is already abandoned
76
+ }
77
+ });
78
+ } catch (err) {
79
+ // Silently ignore
80
+ }
81
+ });
82
+ }
83
+
84
+ if (!db) {
85
+ ensureJettyPodDir();
86
+
87
+ try {
88
+ db = new sqlite3.Database(getDbPath(), (err) => {
89
+ if (err) {
90
+ throw new Error(`Failed to open database: ${err.message}`);
91
+ }
92
+ });
93
+
94
+ // Enable WAL mode for better concurrency and avoid locking issues
95
+ db.run('PRAGMA journal_mode = WAL');
96
+ db.run('PRAGMA synchronous = NORMAL');
97
+
98
+ initSchema();
99
+ } catch (err) {
100
+ db = null;
101
+ throw err;
102
+ }
103
+ }
104
+ return db;
105
+ }
106
+
107
+ /**
108
+ * Initialize database schema and run migrations
109
+ * @throws {Error} If schema creation fails
110
+ */
111
+ function initSchema() {
112
+ const { runMigrations } = require('./migrations');
113
+
114
+ db.serialize(() => {
115
+ db.run(`
116
+ CREATE TABLE IF NOT EXISTS work_items (
117
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
118
+ type TEXT NOT NULL,
119
+ title TEXT NOT NULL,
120
+ description TEXT,
121
+ status TEXT DEFAULT 'backlog',
122
+ parent_id INTEGER,
123
+ epic_id INTEGER,
124
+ branch_name TEXT,
125
+ file_paths TEXT,
126
+ commit_sha TEXT,
127
+ mode TEXT,
128
+ current INTEGER DEFAULT 0,
129
+ phase TEXT,
130
+ prototype_files TEXT,
131
+ discovery_winner TEXT,
132
+ scenario_file TEXT,
133
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
134
+ )
135
+ `, (err) => {
136
+ if (err) {
137
+ throw new Error(`Failed to create work_items table: ${err.message}`);
138
+ }
139
+ });
140
+
141
+ db.run(`
142
+ CREATE TABLE IF NOT EXISTS project_config (
143
+ id INTEGER PRIMARY KEY CHECK (id = 1),
144
+ project_state TEXT DEFAULT 'internal',
145
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
146
+ )
147
+ `, (err) => {
148
+ if (err) {
149
+ throw new Error(`Failed to create project_config table: ${err.message}`);
150
+ }
151
+ });
152
+
153
+ db.run(`
154
+ CREATE TABLE IF NOT EXISTS external_readiness_checklist (
155
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156
+ category TEXT NOT NULL,
157
+ item_key TEXT NOT NULL,
158
+ title TEXT NOT NULL,
159
+ description TEXT,
160
+ completed INTEGER DEFAULT 0,
161
+ completed_at DATETIME,
162
+ UNIQUE(category, item_key)
163
+ )
164
+ `, (err) => {
165
+ if (err) {
166
+ throw new Error(`Failed to create external_readiness_checklist table: ${err.message}`);
167
+ }
168
+
169
+ // Initialize checklist with default items
170
+ const { initializeChecklist } = require('./external-checklist');
171
+ initializeChecklist(db).catch(() => {
172
+ // Silently ignore - don't block startup
173
+ });
174
+ });
175
+
176
+ db.run(`
177
+ CREATE TABLE IF NOT EXISTS discovery_decisions (
178
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
179
+ work_item_id INTEGER NOT NULL,
180
+ aspect TEXT NOT NULL,
181
+ decision TEXT NOT NULL,
182
+ rationale TEXT NOT NULL,
183
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
184
+ FOREIGN KEY (work_item_id) REFERENCES work_items(id)
185
+ )
186
+ `, (err) => {
187
+ if (err) {
188
+ throw new Error(`Failed to create discovery_decisions table: ${err.message}`);
189
+ }
190
+ });
191
+
192
+ // Migrations: Add columns if they don't exist
193
+ // Note: ALTER TABLE errors are expected if column exists, so we silently ignore them
194
+ db.run(`ALTER TABLE work_items ADD COLUMN branch_name TEXT`, () => {});
195
+ db.run(`ALTER TABLE work_items ADD COLUMN file_paths TEXT`, () => {});
196
+ db.run(`ALTER TABLE work_items ADD COLUMN commit_sha TEXT`, () => {});
197
+ db.run(`ALTER TABLE work_items ADD COLUMN mode TEXT`, () => {});
198
+ db.run(`ALTER TABLE work_items ADD COLUMN current INTEGER DEFAULT 0`, () => {});
199
+ db.run(`ALTER TABLE work_items ADD COLUMN needs_discovery INTEGER DEFAULT 0`, () => {});
200
+ // NOTE: phase column is handled by migration 006-feature-phase-field.js
201
+ // Do NOT add it here - the migration includes important data migration logic
202
+ db.run(`ALTER TABLE work_items ADD COLUMN architectural_decision TEXT`, () => {
203
+ // Run data migrations after all schema operations complete (skip in test environments)
204
+ if (process.env.NODE_ENV !== 'test') {
205
+ runMigrations(db).catch(() => {
206
+ // Silently ignore migration errors - don't block startup
207
+ });
208
+ }
209
+ });
210
+ });
211
+ }
212
+
213
+ /**
214
+ * Close database connection
215
+ * @returns {Promise<void>} Resolves when database is closed
216
+ */
217
+ function closeDb() {
218
+ return new Promise((resolve) => {
219
+ // Guard against concurrent close attempts
220
+ if (isClosing || !db) {
221
+ resolve();
222
+ return;
223
+ }
224
+
225
+ isClosing = true;
226
+ db.close((err) => {
227
+ if (err) {
228
+ console.warn(`Warning: Error closing database: ${err.message}`);
229
+ }
230
+ db = null;
231
+ isClosing = false;
232
+ resolve();
233
+ });
234
+ });
235
+ }
236
+
237
+ /**
238
+ * Reset database connection (for testing)
239
+ * Forces the singleton to be recreated on next getDb() call
240
+ */
241
+ function resetDb() {
242
+ db = null;
243
+ cachedJettypodDir = null;
244
+ cachedDbPath = null;
245
+ isClosing = false;
246
+ }
247
+
248
+ module.exports = {
249
+ getDb,
250
+ closeDb,
251
+ resetDb,
252
+ getDbPath,
253
+ getJettypodDir,
254
+ dbPath, // Deprecated: use getDbPath() for dynamic path
255
+ jettypodDir // Deprecated: use getJettypodDir() for dynamic path
256
+ };
@@ -0,0 +1,106 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { createTestEnvironment } = require('./test-helpers');
4
+ const { getDb, closeDb, resetDb, getDbPath, getJettypodDir } = require('./database');
5
+
6
+ describe('Database Module', () => {
7
+ let testEnv;
8
+
9
+ beforeEach(() => {
10
+ resetDb(); // Clear singleton before test
11
+ testEnv = createTestEnvironment();
12
+ process.chdir(testEnv.testDir);
13
+ });
14
+
15
+ afterEach(async () => {
16
+
17
+
18
+ await closeDb();
19
+ testEnv.cleanup();
20
+ resetDb(); // Clear singleton after test
21
+ });
22
+
23
+ describe('getDb()', () => {
24
+ test('should create .jettypod directory if it does not exist', () => {
25
+ const db = getDb();
26
+ expect(fs.existsSync(getJettypodDir())).toBe(true);
27
+ expect(db).toBeTruthy();
28
+ });
29
+
30
+ test('should create database file', (done) => {
31
+ const db = getDb();
32
+ // Wait for database to be fully initialized
33
+ db.get("SELECT 1", (err) => {
34
+ expect(err).toBeNull();
35
+ expect(fs.existsSync(getDbPath())).toBe(true);
36
+ expect(db).toBeTruthy();
37
+ done();
38
+ });
39
+ });
40
+
41
+ test('should create work_items table', (done) => {
42
+ const db = getDb();
43
+ db.get("SELECT name FROM sqlite_master WHERE type='table' AND name='work_items'", (err, row) => {
44
+ expect(err).toBeNull();
45
+ expect(row).toBeTruthy();
46
+ expect(row.name).toBe('work_items');
47
+ done();
48
+ });
49
+ });
50
+
51
+ test('should return same database instance (singleton)', () => {
52
+ const db1 = getDb();
53
+ const db2 = getDb();
54
+ expect(db1).toBe(db2);
55
+ });
56
+
57
+ test('should have correct schema columns', (done) => {
58
+ const db = getDb();
59
+ db.all("PRAGMA table_info(work_items)", (err, columns) => {
60
+ expect(err).toBeNull();
61
+
62
+ const columnNames = columns.map(col => col.name);
63
+ expect(columnNames).toContain('id');
64
+ expect(columnNames).toContain('type');
65
+ expect(columnNames).toContain('title');
66
+ expect(columnNames).toContain('description');
67
+ expect(columnNames).toContain('status');
68
+ expect(columnNames).toContain('parent_id');
69
+ expect(columnNames).toContain('epic_id');
70
+ expect(columnNames).toContain('branch_name');
71
+ expect(columnNames).toContain('file_paths');
72
+ expect(columnNames).toContain('commit_sha');
73
+ expect(columnNames).toContain('mode');
74
+ expect(columnNames).toContain('current');
75
+ expect(columnNames).toContain('created_at');
76
+ done();
77
+ });
78
+ });
79
+ });
80
+
81
+ describe('closeDb()', () => {
82
+ test('should close database connection', async () => {
83
+ const db = getDb();
84
+ expect(db).toBeTruthy();
85
+
86
+ await closeDb();
87
+
88
+ // After close, getDb should create a new instance
89
+ const db2 = getDb();
90
+ expect(db2).toBeTruthy();
91
+ expect(db2).not.toBe(db);
92
+ });
93
+
94
+ test('should not throw when called without active connection', async () => {
95
+ await expect(closeDb()).resolves.not.toThrow();
96
+ });
97
+ });
98
+
99
+ describe('getDbPath() and getJettypodDir()', () => {
100
+ test('should return correct paths', () => {
101
+ expect(getDbPath()).toContain('.jettypod');
102
+ expect(getDbPath()).toContain('work.db');
103
+ expect(getJettypodDir()).toContain('.jettypod');
104
+ });
105
+ });
106
+ });
@@ -0,0 +1,102 @@
1
+ // Discovery Mode: DECISIONS.md generator - happy path only
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const config = require('./config');
5
+ const { getDb } = require('./database');
6
+
7
+ /**
8
+ * Generate DECISIONS.md from project and epic decisions
9
+ */
10
+ async function generateDecisionsFile() {
11
+ // Ensure docs directory exists
12
+ const docsDir = path.join(process.cwd(), 'docs');
13
+ if (!fs.existsSync(docsDir)) {
14
+ fs.mkdirSync(docsDir, { recursive: true });
15
+ }
16
+
17
+ const decisionsPath = path.join(docsDir, 'DECISIONS.md');
18
+
19
+ let content = '# Architectural and Technical Decisions\n\n';
20
+ content += 'This document records key decisions made during project discovery and epic planning.\n\n';
21
+ content += '---\n\n';
22
+
23
+ // Add project-level decisions
24
+ const projectConfig = config.read();
25
+ if (projectConfig.project_discovery && projectConfig.project_discovery.winner) {
26
+ content += '## Project-Level Decisions\n\n';
27
+ content += '### UX Approach & Tech Stack\n\n';
28
+ content += `**Decision:** ${projectConfig.project_discovery.winner}\n\n`;
29
+
30
+ if (projectConfig.project_discovery.rationale) {
31
+ content += `**Rationale:** ${projectConfig.project_discovery.rationale}\n\n`;
32
+ }
33
+
34
+ if (projectConfig.project_discovery.started_date) {
35
+ const date = new Date(projectConfig.project_discovery.started_date);
36
+ content += `**Date:** ${date.toLocaleDateString()}\n\n`;
37
+ }
38
+
39
+ content += '---\n\n';
40
+ }
41
+
42
+ // Add epic-level decisions
43
+ const db = getDb();
44
+
45
+ return new Promise((resolve, reject) => {
46
+ db.all(`
47
+ SELECT dd.*, w.title as epic_title, w.id as epic_id
48
+ FROM discovery_decisions dd
49
+ JOIN work_items w ON dd.work_item_id = w.id
50
+ ORDER BY w.id ASC, dd.created_at ASC
51
+ `, [], (err, rows) => {
52
+ if (err) {
53
+ return reject(err);
54
+ }
55
+
56
+ if (rows && rows.length > 0) {
57
+ content += '## Epic-Level Decisions\n\n';
58
+
59
+ // Group by epic
60
+ const epicGroups = {};
61
+ rows.forEach(row => {
62
+ if (!epicGroups[row.epic_id]) {
63
+ epicGroups[row.epic_id] = {
64
+ title: row.epic_title,
65
+ decisions: []
66
+ };
67
+ }
68
+ epicGroups[row.epic_id].decisions.push(row);
69
+ });
70
+
71
+ // Write each epic's decisions
72
+ Object.keys(epicGroups).forEach(epicId => {
73
+ const epic = epicGroups[epicId];
74
+ content += `### Epic #${epicId}: ${epic.title}\n\n`;
75
+
76
+ epic.decisions.forEach(decision => {
77
+ content += `**${decision.aspect}:** ${decision.decision}\n\n`;
78
+
79
+ if (decision.rationale) {
80
+ content += `*Rationale:* ${decision.rationale}\n\n`;
81
+ }
82
+
83
+ if (decision.created_at) {
84
+ const date = new Date(decision.created_at);
85
+ content += `*Date:* ${date.toLocaleDateString()}\n\n`;
86
+ }
87
+ });
88
+
89
+ content += '---\n\n';
90
+ });
91
+ }
92
+
93
+ // Write to file
94
+ fs.writeFileSync(decisionsPath, content, 'utf8');
95
+ resolve(decisionsPath);
96
+ });
97
+ });
98
+ }
99
+
100
+ module.exports = {
101
+ generateDecisionsFile
102
+ };