ac-framework 1.7.0 → 1.9.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 (91) hide show
  1. package/README.md +26 -0
  2. package/bin/postinstall.js +21 -1
  3. package/framework/.agent/skills/acfm-memory/SKILL.md +312 -0
  4. package/framework/.agent/workflows/ac-lite.md +192 -0
  5. package/framework/.agent/workflows/ac.md +40 -0
  6. package/framework/.amazonq/prompts/ac-lite.md +192 -0
  7. package/framework/.amazonq/prompts/ac.md +40 -0
  8. package/framework/.amazonq/skills/acfm-memory/SKILL.md +312 -0
  9. package/framework/.antigravity/skills/acfm-memory/SKILL.md +312 -0
  10. package/framework/.antigravity/workflows/ac-lite.md +192 -0
  11. package/framework/.antigravity/workflows/ac.md +40 -0
  12. package/framework/.augment/commands/ac-lite.md +192 -0
  13. package/framework/.augment/commands/ac.md +40 -0
  14. package/framework/.augment/skills/acfm-memory/SKILL.md +312 -0
  15. package/framework/.claude/commands/opsx/ac-lite.md +192 -0
  16. package/framework/.claude/commands/opsx/ac.md +40 -0
  17. package/framework/.claude/skills/acfm-memory/SKILL.md +312 -0
  18. package/framework/.cline/commands/opsx/ac-lite.md +192 -0
  19. package/framework/.cline/commands/opsx/ac.md +40 -0
  20. package/framework/.cline/skills/acfm-memory/SKILL.md +312 -0
  21. package/framework/.clinerules/skills/acfm-memory/SKILL.md +312 -0
  22. package/framework/.clinerules/workflows/ac-lite.md +192 -0
  23. package/framework/.clinerules/workflows/ac.md +40 -0
  24. package/framework/.codebuddy/commands/opsx/ac-lite.md +192 -0
  25. package/framework/.codebuddy/commands/opsx/ac.md +40 -0
  26. package/framework/.codebuddy/skills/acfm-memory/SKILL.md +312 -0
  27. package/framework/.codex/skills/acfm-memory/SKILL.md +312 -0
  28. package/framework/.continue/prompts/ac-lite.md +192 -0
  29. package/framework/.continue/prompts/ac.md +40 -0
  30. package/framework/.continue/skills/acfm-memory/SKILL.md +312 -0
  31. package/framework/.cospec/openspec/commands/ac-lite.md +192 -0
  32. package/framework/.cospec/openspec/commands/ac.md +40 -0
  33. package/framework/.cospec/skills/acfm-memory/SKILL.md +312 -0
  34. package/framework/.crush/commands/opsx/ac-lite.md +192 -0
  35. package/framework/.crush/commands/opsx/ac.md +40 -0
  36. package/framework/.crush/skills/acfm-memory/SKILL.md +312 -0
  37. package/framework/.cursor/commands/ac-lite.md +192 -0
  38. package/framework/.cursor/commands/ac.md +40 -0
  39. package/framework/.cursor/skills/acfm-memory/SKILL.md +312 -0
  40. package/framework/.factory/commands/ac-lite.md +192 -0
  41. package/framework/.factory/commands/ac.md +40 -0
  42. package/framework/.factory/skills/acfm-memory/SKILL.md +312 -0
  43. package/framework/.gemini/commands/opsx/ac-lite.md +192 -0
  44. package/framework/.gemini/commands/opsx/ac.md +40 -0
  45. package/framework/.gemini/skills/acfm-memory/SKILL.md +312 -0
  46. package/framework/.github/prompts/ac-lite.md +192 -0
  47. package/framework/.github/prompts/ac.md +40 -0
  48. package/framework/.github/skills/acfm-memory/SKILL.md +312 -0
  49. package/framework/.iflow/commands/ac-lite.md +192 -0
  50. package/framework/.iflow/commands/ac.md +40 -0
  51. package/framework/.iflow/skills/acfm-memory/SKILL.md +312 -0
  52. package/framework/.kilocode/skills/acfm-memory/SKILL.md +312 -0
  53. package/framework/.kilocode/workflows/ac-lite.md +192 -0
  54. package/framework/.kilocode/workflows/ac.md +40 -0
  55. package/framework/.kimi/skills/acfm-memory/SKILL.md +312 -0
  56. package/framework/.kimi/workflows/ac-lite.md +192 -0
  57. package/framework/.kimi/workflows/ac.md +40 -0
  58. package/framework/.opencode/command/ac-lite.md +192 -0
  59. package/framework/.opencode/command/ac.md +40 -0
  60. package/framework/.opencode/skills/acfm-memory/SKILL.md +312 -0
  61. package/framework/.qoder/commands/opsx/ac-lite.md +192 -0
  62. package/framework/.qoder/commands/opsx/ac.md +40 -0
  63. package/framework/.qoder/skills/acfm-memory/SKILL.md +312 -0
  64. package/framework/.qwen/commands/ac-lite.md +192 -0
  65. package/framework/.qwen/commands/ac.md +40 -0
  66. package/framework/.qwen/skills/acfm-memory/SKILL.md +312 -0
  67. package/framework/.roo/commands/ac-lite.md +192 -0
  68. package/framework/.roo/commands/ac.md +40 -0
  69. package/framework/.roo/skills/acfm-memory/SKILL.md +312 -0
  70. package/framework/.trae/skills/acfm-memory/SKILL.md +312 -0
  71. package/framework/.windsurf/skills/acfm-memory/SKILL.md +312 -0
  72. package/framework/.windsurf/workflows/ac-lite.md +192 -0
  73. package/framework/.windsurf/workflows/ac.md +40 -0
  74. package/framework/AGENTS.md +39 -0
  75. package/framework/CLAUDE.md +39 -0
  76. package/framework/GEMINI.md +39 -0
  77. package/framework/copilot-instructions.md +39 -0
  78. package/package.json +5 -2
  79. package/src/cli.js +2 -0
  80. package/src/commands/init.js +98 -0
  81. package/src/commands/memory.js +833 -0
  82. package/src/index.js +46 -0
  83. package/src/mcp/server.js +345 -0
  84. package/src/mcp/server.js.bak +727 -0
  85. package/src/memory/autosave.js +382 -0
  86. package/src/memory/database.js +178 -0
  87. package/src/memory/engine.js +727 -0
  88. package/src/memory/index.js +62 -0
  89. package/src/memory/utils.js +128 -0
  90. package/src/services/mcp-installer.js +194 -0
  91. package/src/services/spec-engine.js +69 -1
@@ -0,0 +1,62 @@
1
+ /**
2
+ * memory/index.js — API pública del sistema de memoria autónoma
3
+ *
4
+ * Sistema de persistencia de conocimiento para agentes AI.
5
+ * Guarda automáticamente decisiones, patrones, bugfixes y insights.
6
+ */
7
+
8
+ // Database
9
+ export { initDatabase, getDatabase, closeDatabase, isDatabaseInitialized } from './database.js';
10
+
11
+ // Core Engine
12
+ export {
13
+ saveMemory,
14
+ searchMemories,
15
+ getContext,
16
+ getTimeline,
17
+ getMemory,
18
+ updateMemory,
19
+ deleteMemory,
20
+ startSession,
21
+ endSession,
22
+ getStats,
23
+ findPatterns,
24
+ getConnections,
25
+ anticipateNeeds,
26
+ exportMemories,
27
+ importMemories,
28
+ pruneMemories
29
+ } from './engine.js';
30
+
31
+ // Utils
32
+ export {
33
+ redactPrivateContent,
34
+ extractKeywords,
35
+ textSimilarity,
36
+ truncate,
37
+ isCodeContent,
38
+ detectLanguage,
39
+ generateSummary,
40
+ sanitizeFTSQuery
41
+ } from './utils.js';
42
+
43
+ // Auto-save system
44
+ export { createAutoSaveHook, AutoSaveManager } from './autosave.js';
45
+
46
+ // Tipos de memoria válidos para referencia
47
+ export const MEMORY_TYPES = [
48
+ 'architectural_decision',
49
+ 'bugfix_pattern',
50
+ 'api_pattern',
51
+ 'performance_insight',
52
+ 'security_fix',
53
+ 'refactor_technique',
54
+ 'dependency_note',
55
+ 'workaround',
56
+ 'convention',
57
+ 'context_boundary',
58
+ 'general_insight',
59
+ 'session_summary'
60
+ ];
61
+
62
+ export const IMPORTANCE_LEVELS = ['critical', 'high', 'medium', 'low'];
@@ -0,0 +1,128 @@
1
+ /**
2
+ * utils.js — Utilidades para el sistema de memoria
3
+ */
4
+
5
+ /**
6
+ * Redacta contenido entre etiquetas <private>
7
+ */
8
+ export function redactPrivateContent(content) {
9
+ if (!content) return content;
10
+
11
+ const privateRegex = /<private>[\s\S]*?<\/private>/gi;
12
+ return content.replace(privateRegex, '[REDACTED PRIVATE CONTENT]');
13
+ }
14
+
15
+ /**
16
+ * Extrae palabras clave de un texto
17
+ */
18
+ export function extractKeywords(text, maxKeywords = 5) {
19
+ if (!text) return [];
20
+
21
+ const stopWords = new Set([
22
+ 'el', 'la', 'los', 'las', 'un', 'una', 'unos', 'unas', 'de', 'del', 'al',
23
+ 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
24
+ 'en', 'y', 'o', 'pero', 'por', 'para', 'con', 'sin', 'sobre', 'entre',
25
+ 'in', 'and', 'or', 'but', 'for', 'with', 'without', 'on', 'between',
26
+ 'que', 'como', 'cuando', 'donde', 'quien', 'cual', 'esto', 'eso',
27
+ 'that', 'what', 'when', 'where', 'who', 'which', 'this', 'that'
28
+ ]);
29
+
30
+ const wordCounts = text.toLowerCase()
31
+ .replace(/[^\w\s]/g, ' ')
32
+ .split(/\s+/)
33
+ .filter(w => w.length > 3 && !stopWords.has(w))
34
+ .reduce((acc, word) => {
35
+ acc[word] = (acc[word] || 0) + 1;
36
+ return acc;
37
+ }, {});
38
+
39
+ return Object.entries(wordCounts)
40
+ .sort((a, b) => b[1] - a[1])
41
+ .slice(0, maxKeywords)
42
+ .map(([word]) => word);
43
+ }
44
+
45
+ /**
46
+ * Calcula similitud simple entre dos textos (Jaccard)
47
+ */
48
+ export function textSimilarity(text1, text2) {
49
+ const set1 = new Set(text1.toLowerCase().split(/\s+/));
50
+ const set2 = new Set(text2.toLowerCase().split(/\s+/));
51
+
52
+ const intersection = new Set([...set1].filter(x => set2.has(x)));
53
+ const union = new Set([...set1, ...set2]);
54
+
55
+ return intersection.size / union.size;
56
+ }
57
+
58
+ /**
59
+ * Trunca texto con ellipsis
60
+ */
61
+ export function truncate(text, maxLength = 100) {
62
+ if (!text || text.length <= maxLength) return text;
63
+ return text.slice(0, maxLength - 3) + '...';
64
+ }
65
+
66
+ /**
67
+ * Detecta si un contenido parece código
68
+ */
69
+ export function isCodeContent(text) {
70
+ const codeIndicators = [
71
+ /function\s+\w+\s*\(/,
72
+ /const\s+\w+\s*=/,
73
+ /let\s+\w+\s*=/,
74
+ /var\s+\w+\s*=/,
75
+ /import\s+.*\s+from/,
76
+ /export\s+(default\s+)?/,
77
+ /class\s+\w+/,
78
+ /if\s*\(.*\)\s*\{/,
79
+ /for\s*\(.*\)\s*\{/,
80
+ /```[\s\S]*```/
81
+ ];
82
+
83
+ return codeIndicators.some(pattern => pattern.test(text));
84
+ }
85
+
86
+ /**
87
+ * Detecta lenguaje de programación
88
+ */
89
+ export function detectLanguage(text) {
90
+ if (/\.tsx?/.test(text) || /interface\s+\w+/.test(text) || /:\s*(string|number|boolean)/.test(text)) {
91
+ return 'typescript';
92
+ }
93
+ if (/\.jsx?/.test(text) || /React\./.test(text) || /useState|useEffect/.test(text)) {
94
+ return 'javascript';
95
+ }
96
+ if (/\.py/.test(text) || /def\s+\w+\s*\(/.test(text) || /import\s+\w+/.test(text)) {
97
+ return 'python';
98
+ }
99
+ if (/\.go/.test(text) || /func\s+\w+\s*\(/.test(text) || /package\s+\w+/.test(text)) {
100
+ return 'go';
101
+ }
102
+ if (/\.rs/.test(text) || /fn\s+\w+\s*\(/.test(text) || /let\s+mut/.test(text)) {
103
+ return 'rust';
104
+ }
105
+ return 'unknown';
106
+ }
107
+
108
+ /**
109
+ * Genera un resumen automático
110
+ */
111
+ export function generateSummary(text, maxSentences = 2) {
112
+ if (!text) return '';
113
+
114
+ const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 10);
115
+ return sentences.slice(0, maxSentences).join('. ') + '.';
116
+ }
117
+
118
+ /**
119
+ * Sanitiza input para FTS5
120
+ */
121
+ export function sanitizeFTSQuery(query) {
122
+ // Escapar caracteres especiales de FTS5
123
+ return query
124
+ .replace(/"/g, '""')
125
+ .replace(/\*/g, '')
126
+ .replace(/^\^/g, '')
127
+ .replace(/\$/g, '');
128
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * mcp-installer.js — MCP config installer for AC Framework
3
+ *
4
+ * Detects installed AI assistants and injects the ac-framework-memory
5
+ * MCP server into their config files.
6
+ *
7
+ * Best-practice config format per MCP spec:
8
+ * { "mcpServers": { "ac-framework-memory": { "command": "node", "args": [<absPath>] } } }
9
+ *
10
+ * Claude Code uses a different top-level key format (mcpServers inside claude.json).
11
+ */
12
+
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
14
+ import { dirname, join } from 'node:path';
15
+ import { homedir, platform } from 'node:os';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+
20
+ // Absolute path to the MCP server entry point (ESM, referenced by node)
21
+ export function getMCPServerPath() {
22
+ const srcPath = join(__dirname, '../mcp/server.js');
23
+ return srcPath;
24
+ }
25
+
26
+ // ── Supported assistants ──────────────────────────────────────────
27
+
28
+ const home = homedir();
29
+ const IS_WIN = platform() === 'win32';
30
+
31
+ /**
32
+ * Each assistant entry:
33
+ * configPath – absolute path to the JSON config file
34
+ * configKey – top-level key that holds server map ("mcpServers" | "servers")
35
+ * detectDir – directory whose existence signals the assistant is present
36
+ */
37
+ const ASSISTANTS = [
38
+ {
39
+ name: 'opencode',
40
+ configPath: join(home, '.opencode', 'mcp.json'),
41
+ configKey: 'mcpServers',
42
+ detectDir: join(home, '.opencode'),
43
+ },
44
+ {
45
+ name: 'claude',
46
+ // Claude Code CLI uses ~/.claude.json (mcpServers key)
47
+ configPath: join(home, '.claude.json'),
48
+ configKey: 'mcpServers',
49
+ detectDir: home,
50
+ detectFile: join(home, '.claude.json'),
51
+ },
52
+ {
53
+ name: 'cursor',
54
+ configPath: join(home, '.cursor', 'mcp.json'),
55
+ configKey: 'mcpServers',
56
+ detectDir: join(home, '.cursor'),
57
+ },
58
+ {
59
+ name: 'windsurf',
60
+ configPath: join(home, '.windsurf', 'mcp.json'),
61
+ configKey: 'mcpServers',
62
+ detectDir: join(home, '.windsurf'),
63
+ },
64
+ {
65
+ name: 'gemini',
66
+ configPath: join(home, '.gemini', 'mcp.json'),
67
+ configKey: 'mcpServers',
68
+ detectDir: join(home, '.gemini'),
69
+ },
70
+ {
71
+ name: 'codex',
72
+ configPath: join(home, '.codex', 'mcp.json'),
73
+ configKey: 'mcpServers',
74
+ detectDir: join(home, '.codex'),
75
+ },
76
+ ];
77
+
78
+ // ── Detection ─────────────────────────────────────────────────────
79
+
80
+ export function isAssistantInstalled(assistant) {
81
+ try {
82
+ // If a specific file is the detection signal, use that
83
+ if (assistant.detectFile) return existsSync(assistant.detectFile);
84
+ return existsSync(assistant.detectDir);
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ // ── Install / Uninstall ───────────────────────────────────────────
91
+
92
+ export function installMCPForAssistant(assistant) {
93
+ try {
94
+ const configDir = dirname(assistant.configPath);
95
+
96
+ if (!existsSync(configDir)) {
97
+ mkdirSync(configDir, { recursive: true });
98
+ }
99
+
100
+ // Read existing config or start fresh
101
+ let config = {};
102
+ if (existsSync(assistant.configPath)) {
103
+ try {
104
+ config = JSON.parse(readFileSync(assistant.configPath, 'utf8'));
105
+ } catch {
106
+ config = {};
107
+ }
108
+ }
109
+
110
+ // Ensure the server map key exists
111
+ if (!config[assistant.configKey]) {
112
+ config[assistant.configKey] = {};
113
+ }
114
+
115
+ // Add / overwrite our server entry
116
+ config[assistant.configKey]['ac-framework-memory'] = {
117
+ command: 'node',
118
+ args: [getMCPServerPath()],
119
+ };
120
+
121
+ writeFileSync(assistant.configPath, JSON.stringify(config, null, 2));
122
+ return true;
123
+ } catch (error) {
124
+ console.error(` Failed to install MCP for ${assistant.name}: ${error.message}`);
125
+ return false;
126
+ }
127
+ }
128
+
129
+ export function uninstallMCPForAssistant(assistant) {
130
+ try {
131
+ if (!existsSync(assistant.configPath)) return true;
132
+
133
+ let config = {};
134
+ try {
135
+ config = JSON.parse(readFileSync(assistant.configPath, 'utf8'));
136
+ } catch {
137
+ return true;
138
+ }
139
+
140
+ if (config[assistant.configKey]?.['ac-framework-memory']) {
141
+ delete config[assistant.configKey]['ac-framework-memory'];
142
+ writeFileSync(assistant.configPath, JSON.stringify(config, null, 2));
143
+ }
144
+
145
+ return true;
146
+ } catch (error) {
147
+ console.error(` Failed to uninstall MCP for ${assistant.name}: ${error.message}`);
148
+ return false;
149
+ }
150
+ }
151
+
152
+ // ── Batch helpers ─────────────────────────────────────────────────
153
+
154
+ /**
155
+ * Detects which assistants are installed and installs MCPs for them.
156
+ * Returns { installed, success } counts.
157
+ */
158
+ export function detectAndInstallMCPs() {
159
+ let installed = 0;
160
+ let success = 0;
161
+
162
+ for (const assistant of ASSISTANTS) {
163
+ if (isAssistantInstalled(assistant)) {
164
+ installed++;
165
+ if (installMCPForAssistant(assistant)) success++;
166
+ }
167
+ }
168
+
169
+ return { installed, success, assistants: ASSISTANTS };
170
+ }
171
+
172
+ /**
173
+ * Installs MCPs for ALL supported assistants regardless of detection.
174
+ */
175
+ export function installAllMCPs() {
176
+ let success = 0;
177
+ for (const assistant of ASSISTANTS) {
178
+ if (installMCPForAssistant(assistant)) success++;
179
+ }
180
+ return { total: ASSISTANTS.length, success };
181
+ }
182
+
183
+ /**
184
+ * Uninstalls MCPs from all detected assistants.
185
+ */
186
+ export function uninstallAllMCPs() {
187
+ let success = 0;
188
+ for (const assistant of ASSISTANTS) {
189
+ if (isAssistantInstalled(assistant) && uninstallMCPForAssistant(assistant)) success++;
190
+ }
191
+ return { success };
192
+ }
193
+
194
+ export { ASSISTANTS };
@@ -9,6 +9,7 @@ import { readFile, readdir, stat, mkdir, rename, access, writeFile } from 'node:
9
9
  import { resolve, join, dirname, relative } from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import yaml from 'js-yaml';
12
+ import { initDatabase, getContext, AutoSaveManager } from '../memory/index.js';
12
13
 
13
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
15
  const SCHEMAS_DIR = resolve(__dirname, '../schemas');
@@ -475,6 +476,21 @@ export async function getArtifactInstructions(artifactId, changeName, cwd = proc
475
476
  // Determine what this artifact unlocks
476
477
  const unlocks = artifact.unlocks || [];
477
478
 
479
+ // NUEVO: Recuperar contexto de memoria relevante
480
+ let relevantMemories = [];
481
+ try {
482
+ initDatabase(); // Asegurar DB inicializada
483
+ relevantMemories = getContext({
484
+ projectPath: cwd,
485
+ changeName,
486
+ limit: 5,
487
+ lookbackDays: 30
488
+ });
489
+ } catch {
490
+ // Si memory no está inicializado, continuar sin contexto
491
+ relevantMemories = [];
492
+ }
493
+
478
494
  return {
479
495
  context,
480
496
  rules,
@@ -483,6 +499,14 @@ export async function getArtifactInstructions(artifactId, changeName, cwd = proc
483
499
  outputPath,
484
500
  dependencies,
485
501
  unlocks,
502
+ // NUEVO: Memorias relevantes para el contexto del agente
503
+ relevantMemories: relevantMemories.map(m => ({
504
+ id: m.id,
505
+ type: m.type,
506
+ content: m.content,
507
+ importance: m.importance,
508
+ fromChange: m.changeName
509
+ }))
486
510
  };
487
511
  }
488
512
 
@@ -583,12 +607,56 @@ export async function getApplyInstructions(changeName, cwd = process.cwd()) {
583
607
  instruction = `${remaining} of ${total} tasks remaining. Implement each pending task, following the specs and design. Mark tasks as [x] when complete.`;
584
608
  }
585
609
 
610
+ // NUEVO: Recuperar contexto de memoria relevante para implementación
611
+ let relevantMemories = [];
612
+ let suggestedPatterns = [];
613
+ try {
614
+ initDatabase();
615
+
616
+ // Buscar patrones similares en memorias
617
+ relevantMemories = getContext({
618
+ projectPath: cwd,
619
+ changeName,
620
+ limit: 5,
621
+ lookbackDays: 30
622
+ });
623
+
624
+ // Buscar patrones de implementación específicos
625
+ const pendingTasks = tasks.filter(t => !t.done).map(t => t.task).join(' ');
626
+ if (pendingTasks) {
627
+ // Buscar memorias relacionadas con las tareas pendientes
628
+ const keywords = pendingTasks.toLowerCase().split(/\s+/).filter(w => w.length > 4);
629
+ if (keywords.length > 0) {
630
+ const { searchMemories } = await import('../memory/index.js');
631
+ suggestedPatterns = searchMemories(keywords.slice(0, 5).join(' OR '), {
632
+ type: 'refactor_technique',
633
+ limit: 3
634
+ });
635
+ }
636
+ }
637
+ } catch {
638
+ // Continuar sin contexto si hay error
639
+ }
640
+
586
641
  return {
587
642
  contextFiles,
588
643
  progress: { total, complete, remaining },
589
644
  state,
590
645
  instruction,
591
646
  tasks,
647
+ // NUEVO: Contexto de memoria para ayudar en implementación
648
+ relevantMemories: relevantMemories.map(m => ({
649
+ id: m.id,
650
+ type: m.type,
651
+ content: m.content,
652
+ importance: m.importance,
653
+ codeSnippet: m.codeSnippet
654
+ })),
655
+ suggestedPatterns: suggestedPatterns.map(m => ({
656
+ id: m.id,
657
+ content: m.content,
658
+ type: m.type
659
+ }))
592
660
  };
593
661
  }
594
662
 
@@ -607,7 +675,7 @@ export async function archiveChange(name, cwd = process.cwd()) {
607
675
  throw new Error(`Change "${name}" not found.`);
608
676
  }
609
677
 
610
- const archiveDir = join(changesDir(cwd), 'archive');
678
+ const archiveDir = join(await changesDir(cwd), 'archive');
611
679
  await mkdir(archiveDir, { recursive: true });
612
680
 
613
681
  const dateStr = new Date().toISOString().slice(0, 10); // YYYY-MM-DD