@howlil/ez-agents 2.0.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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +845 -0
  3. package/README.zh-CN.md +702 -0
  4. package/agents/ez-codebase-mapper.md +770 -0
  5. package/agents/ez-debugger.md +1255 -0
  6. package/agents/ez-executor.md +487 -0
  7. package/agents/ez-integration-checker.md +443 -0
  8. package/agents/ez-nyquist-auditor.md +176 -0
  9. package/agents/ez-phase-researcher.md +553 -0
  10. package/agents/ez-plan-checker.md +706 -0
  11. package/agents/ez-planner.md +1307 -0
  12. package/agents/ez-project-researcher.md +629 -0
  13. package/agents/ez-research-synthesizer.md +247 -0
  14. package/agents/ez-roadmapper.md +650 -0
  15. package/agents/ez-ui-auditor.md +441 -0
  16. package/agents/ez-ui-checker.md +302 -0
  17. package/agents/ez-ui-researcher.md +355 -0
  18. package/agents/ez-verifier.md +579 -0
  19. package/bin/install.js +2862 -0
  20. package/bin/update.js +214 -0
  21. package/commands/ez/add-phase.md +43 -0
  22. package/commands/ez/add-tests.md +41 -0
  23. package/commands/ez/add-todo.md +47 -0
  24. package/commands/ez/audit-milestone.md +36 -0
  25. package/commands/ez/autonomous.md +41 -0
  26. package/commands/ez/check-todos.md +45 -0
  27. package/commands/ez/cleanup.md +18 -0
  28. package/commands/ez/complete-milestone.md +136 -0
  29. package/commands/ez/debug.md +168 -0
  30. package/commands/ez/discuss-phase.md +90 -0
  31. package/commands/ez/execute-phase.md +41 -0
  32. package/commands/ez/health.md +22 -0
  33. package/commands/ez/help.md +22 -0
  34. package/commands/ez/insert-phase.md +32 -0
  35. package/commands/ez/join-discord.md +18 -0
  36. package/commands/ez/list-phase-assumptions.md +46 -0
  37. package/commands/ez/map-codebase.md +71 -0
  38. package/commands/ez/new-milestone.md +44 -0
  39. package/commands/ez/new-project.md +42 -0
  40. package/commands/ez/pause-work.md +38 -0
  41. package/commands/ez/plan-milestone-gaps.md +34 -0
  42. package/commands/ez/plan-phase.md +45 -0
  43. package/commands/ez/progress.md +24 -0
  44. package/commands/ez/quick.md +45 -0
  45. package/commands/ez/reapply-patches.md +124 -0
  46. package/commands/ez/remove-phase.md +31 -0
  47. package/commands/ez/research-phase.md +190 -0
  48. package/commands/ez/resume-work.md +40 -0
  49. package/commands/ez/set-profile.md +34 -0
  50. package/commands/ez/settings.md +36 -0
  51. package/commands/ez/stats.md +18 -0
  52. package/commands/ez/ui-phase.md +34 -0
  53. package/commands/ez/ui-review.md +32 -0
  54. package/commands/ez/update.md +37 -0
  55. package/commands/ez/validate-phase.md +35 -0
  56. package/commands/ez/verify-work.md +38 -0
  57. package/get-shit-done/bin/ez-tools.cjs +598 -0
  58. package/get-shit-done/bin/lib/assistant-adapter.cjs +205 -0
  59. package/get-shit-done/bin/lib/audit-exec.cjs +150 -0
  60. package/get-shit-done/bin/lib/auth.cjs +175 -0
  61. package/get-shit-done/bin/lib/circuit-breaker.cjs +118 -0
  62. package/get-shit-done/bin/lib/commands.cjs +666 -0
  63. package/get-shit-done/bin/lib/config.cjs +183 -0
  64. package/get-shit-done/bin/lib/core.cjs +495 -0
  65. package/get-shit-done/bin/lib/file-lock.cjs +236 -0
  66. package/get-shit-done/bin/lib/frontmatter.cjs +299 -0
  67. package/get-shit-done/bin/lib/fs-utils.cjs +153 -0
  68. package/get-shit-done/bin/lib/git-utils.cjs +203 -0
  69. package/get-shit-done/bin/lib/health-check.cjs +163 -0
  70. package/get-shit-done/bin/lib/index.cjs +113 -0
  71. package/get-shit-done/bin/lib/init.cjs +710 -0
  72. package/get-shit-done/bin/lib/logger.cjs +117 -0
  73. package/get-shit-done/bin/lib/milestone.cjs +241 -0
  74. package/get-shit-done/bin/lib/model-provider.cjs +146 -0
  75. package/get-shit-done/bin/lib/phase.cjs +908 -0
  76. package/get-shit-done/bin/lib/retry.cjs +119 -0
  77. package/get-shit-done/bin/lib/roadmap.cjs +305 -0
  78. package/get-shit-done/bin/lib/safe-exec.cjs +128 -0
  79. package/get-shit-done/bin/lib/safe-path.cjs +130 -0
  80. package/get-shit-done/bin/lib/state.cjs +721 -0
  81. package/get-shit-done/bin/lib/temp-file.cjs +239 -0
  82. package/get-shit-done/bin/lib/template.cjs +222 -0
  83. package/get-shit-done/bin/lib/test-file-lock.cjs +112 -0
  84. package/get-shit-done/bin/lib/test-graceful.cjs +93 -0
  85. package/get-shit-done/bin/lib/test-logger.cjs +60 -0
  86. package/get-shit-done/bin/lib/test-safe-exec.cjs +38 -0
  87. package/get-shit-done/bin/lib/test-safe-path.cjs +33 -0
  88. package/get-shit-done/bin/lib/test-temp-file.cjs +125 -0
  89. package/get-shit-done/bin/lib/timeout-exec.cjs +62 -0
  90. package/get-shit-done/bin/lib/verify.cjs +820 -0
  91. package/get-shit-done/references/checkpoints.md +776 -0
  92. package/get-shit-done/references/continuation-format.md +249 -0
  93. package/get-shit-done/references/decimal-phase-calculation.md +65 -0
  94. package/get-shit-done/references/git-integration.md +248 -0
  95. package/get-shit-done/references/git-planning-commit.md +38 -0
  96. package/get-shit-done/references/model-profile-resolution.md +34 -0
  97. package/get-shit-done/references/model-profiles.md +93 -0
  98. package/get-shit-done/references/phase-argument-parsing.md +61 -0
  99. package/get-shit-done/references/planning-config.md +200 -0
  100. package/get-shit-done/references/questioning.md +162 -0
  101. package/get-shit-done/references/tdd.md +263 -0
  102. package/get-shit-done/references/ui-brand.md +160 -0
  103. package/get-shit-done/references/verification-patterns.md +612 -0
  104. package/get-shit-done/templates/DEBUG.md +164 -0
  105. package/get-shit-done/templates/UAT.md +247 -0
  106. package/get-shit-done/templates/UI-SPEC.md +100 -0
  107. package/get-shit-done/templates/VALIDATION.md +76 -0
  108. package/get-shit-done/templates/codebase/architecture.md +255 -0
  109. package/get-shit-done/templates/codebase/concerns.md +310 -0
  110. package/get-shit-done/templates/codebase/conventions.md +307 -0
  111. package/get-shit-done/templates/codebase/integrations.md +280 -0
  112. package/get-shit-done/templates/codebase/stack.md +186 -0
  113. package/get-shit-done/templates/codebase/structure.md +285 -0
  114. package/get-shit-done/templates/codebase/testing.md +480 -0
  115. package/get-shit-done/templates/config.json +37 -0
  116. package/get-shit-done/templates/context.md +352 -0
  117. package/get-shit-done/templates/continue-here.md +78 -0
  118. package/get-shit-done/templates/copilot-instructions.md +7 -0
  119. package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
  120. package/get-shit-done/templates/discovery.md +146 -0
  121. package/get-shit-done/templates/milestone-archive.md +123 -0
  122. package/get-shit-done/templates/milestone.md +115 -0
  123. package/get-shit-done/templates/phase-prompt.md +610 -0
  124. package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
  125. package/get-shit-done/templates/project.md +184 -0
  126. package/get-shit-done/templates/requirements.md +231 -0
  127. package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
  128. package/get-shit-done/templates/research-project/FEATURES.md +147 -0
  129. package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
  130. package/get-shit-done/templates/research-project/STACK.md +120 -0
  131. package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
  132. package/get-shit-done/templates/research.md +552 -0
  133. package/get-shit-done/templates/retrospective.md +54 -0
  134. package/get-shit-done/templates/roadmap.md +202 -0
  135. package/get-shit-done/templates/state.md +176 -0
  136. package/get-shit-done/templates/summary-complex.md +59 -0
  137. package/get-shit-done/templates/summary-minimal.md +41 -0
  138. package/get-shit-done/templates/summary-standard.md +48 -0
  139. package/get-shit-done/templates/summary.md +248 -0
  140. package/get-shit-done/templates/user-setup.md +311 -0
  141. package/get-shit-done/templates/verification-report.md +322 -0
  142. package/get-shit-done/workflows/add-phase.md +112 -0
  143. package/get-shit-done/workflows/add-tests.md +351 -0
  144. package/get-shit-done/workflows/add-todo.md +158 -0
  145. package/get-shit-done/workflows/audit-milestone.md +332 -0
  146. package/get-shit-done/workflows/autonomous.md +743 -0
  147. package/get-shit-done/workflows/check-todos.md +177 -0
  148. package/get-shit-done/workflows/cleanup.md +152 -0
  149. package/get-shit-done/workflows/complete-milestone.md +766 -0
  150. package/get-shit-done/workflows/diagnose-issues.md +219 -0
  151. package/get-shit-done/workflows/discovery-phase.md +289 -0
  152. package/get-shit-done/workflows/discuss-phase.md +762 -0
  153. package/get-shit-done/workflows/execute-phase.md +468 -0
  154. package/get-shit-done/workflows/execute-plan.md +483 -0
  155. package/get-shit-done/workflows/health.md +159 -0
  156. package/get-shit-done/workflows/help.md +492 -0
  157. package/get-shit-done/workflows/insert-phase.md +130 -0
  158. package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
  159. package/get-shit-done/workflows/map-codebase.md +316 -0
  160. package/get-shit-done/workflows/new-milestone.md +384 -0
  161. package/get-shit-done/workflows/new-project.md +1111 -0
  162. package/get-shit-done/workflows/node-repair.md +92 -0
  163. package/get-shit-done/workflows/pause-work.md +122 -0
  164. package/get-shit-done/workflows/plan-milestone-gaps.md +274 -0
  165. package/get-shit-done/workflows/plan-phase.md +651 -0
  166. package/get-shit-done/workflows/progress.md +382 -0
  167. package/get-shit-done/workflows/quick.md +610 -0
  168. package/get-shit-done/workflows/remove-phase.md +155 -0
  169. package/get-shit-done/workflows/research-phase.md +74 -0
  170. package/get-shit-done/workflows/resume-project.md +307 -0
  171. package/get-shit-done/workflows/set-profile.md +81 -0
  172. package/get-shit-done/workflows/settings.md +242 -0
  173. package/get-shit-done/workflows/stats.md +57 -0
  174. package/get-shit-done/workflows/transition.md +544 -0
  175. package/get-shit-done/workflows/ui-phase.md +290 -0
  176. package/get-shit-done/workflows/ui-review.md +157 -0
  177. package/get-shit-done/workflows/update.md +320 -0
  178. package/get-shit-done/workflows/validate-phase.md +167 -0
  179. package/get-shit-done/workflows/verify-phase.md +243 -0
  180. package/get-shit-done/workflows/verify-work.md +584 -0
  181. package/package.json +55 -0
  182. package/scripts/build-hooks.js +43 -0
  183. package/scripts/run-tests.cjs +29 -0
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * GSD Logger — Centralized logging module for GSD workflow
5
+ *
6
+ * Provides structured logging with levels (ERROR, WARN, INFO, DEBUG)
7
+ * Writes to .planning/logs/gsd-{timestamp}.log
8
+ * Replaces silent catch {} blocks with proper error logging
9
+ *
10
+ * Usage:
11
+ * const Logger = require('./logger.cjs');
12
+ * const logger = new Logger();
13
+ * logger.error('Something failed', { context: 'details' });
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ class Logger {
20
+ /**
21
+ * Create a Logger instance
22
+ * @param {string} logDir - Directory for log files (default: .planning/logs)
23
+ */
24
+ constructor(logDir = '.planning/logs') {
25
+ this.logDir = logDir;
26
+ this.logFile = null;
27
+ this._ensureLogDir();
28
+ }
29
+
30
+ /**
31
+ * Ensure log directory exists and initialize log file
32
+ */
33
+ _ensureLogDir() {
34
+ if (!fs.existsSync(this.logDir)) {
35
+ fs.mkdirSync(this.logDir, { recursive: true });
36
+ }
37
+ this.logFile = path.join(this.logDir, `gsd-${Date.now()}.log`);
38
+ }
39
+
40
+ /**
41
+ * Get current log file path
42
+ * @returns {string} - Path to log file
43
+ */
44
+ getLogFile() {
45
+ return this.logFile;
46
+ }
47
+
48
+ /**
49
+ * Write a log entry
50
+ * @param {string} level - Log level (ERROR, WARN, INFO, DEBUG)
51
+ * @param {string} message - Log message
52
+ * @param {Object} context - Additional context data
53
+ */
54
+ log(level, message, context = {}) {
55
+ const entry = {
56
+ timestamp: new Date().toISOString(),
57
+ level,
58
+ message,
59
+ context,
60
+ pid: process.pid
61
+ };
62
+
63
+ try {
64
+ fs.appendFileSync(this.logFile, JSON.stringify(entry) + '\n');
65
+
66
+ // Always output ERROR level to console for visibility
67
+ if (level === 'ERROR') {
68
+ console.error(`[GSD ${level}] ${message}`);
69
+ }
70
+ } catch (err) {
71
+ // Fallback: log to console if file write fails
72
+ console.error(`[GSD ${level}] ${message} (file write failed: ${err.message})`);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Log an ERROR level message
78
+ * @param {string} msg - Error message
79
+ * @param {Object} ctx - Additional context
80
+ */
81
+ error(msg, ctx) {
82
+ this.log('ERROR', msg, ctx);
83
+ }
84
+
85
+ /**
86
+ * Log a WARN level message
87
+ * @param {string} msg - Warning message
88
+ * @param {Object} ctx - Additional context
89
+ */
90
+ warn(msg, ctx) {
91
+ this.log('WARN', msg, ctx);
92
+ }
93
+
94
+ /**
95
+ * Log an INFO level message
96
+ * @param {string} msg - Info message
97
+ * @param {Object} ctx - Additional context
98
+ */
99
+ info(msg, ctx) {
100
+ this.log('INFO', msg, ctx);
101
+ }
102
+
103
+ /**
104
+ * Log a DEBUG level message
105
+ * @param {string} msg - Debug message
106
+ * @param {Object} ctx - Additional context
107
+ */
108
+ debug(msg, ctx) {
109
+ this.log('DEBUG', msg, ctx);
110
+ }
111
+ }
112
+
113
+ // Singleton instance for default usage
114
+ const defaultLogger = new Logger();
115
+
116
+ module.exports = Logger;
117
+ module.exports.defaultLogger = defaultLogger;
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Milestone — Milestone and requirements lifecycle operations
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { escapeRegex, getMilestonePhaseFilter, output, error } = require('./core.cjs');
8
+ const { extractFrontmatter } = require('./frontmatter.cjs');
9
+ const { writeStateMd } = require('./state.cjs');
10
+
11
+ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
12
+ if (!reqIdsRaw || reqIdsRaw.length === 0) {
13
+ error('requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02');
14
+ }
15
+
16
+ // Accept comma-separated, space-separated, or bracket-wrapped: [REQ-01, REQ-02]
17
+ const reqIds = reqIdsRaw
18
+ .join(' ')
19
+ .replace(/[\[\]]/g, '')
20
+ .split(/[,\s]+/)
21
+ .map(r => r.trim())
22
+ .filter(Boolean);
23
+
24
+ if (reqIds.length === 0) {
25
+ error('no valid requirement IDs found');
26
+ }
27
+
28
+ const reqPath = path.join(cwd, '.planning', 'REQUIREMENTS.md');
29
+ if (!fs.existsSync(reqPath)) {
30
+ output({ updated: false, reason: 'REQUIREMENTS.md not found', ids: reqIds }, raw, 'no requirements file');
31
+ return;
32
+ }
33
+
34
+ let reqContent = fs.readFileSync(reqPath, 'utf-8');
35
+ const updated = [];
36
+ const notFound = [];
37
+
38
+ for (const reqId of reqIds) {
39
+ let found = false;
40
+ const reqEscaped = escapeRegex(reqId);
41
+
42
+ // Update checkbox: - [ ] **REQ-ID** → - [x] **REQ-ID**
43
+ const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqEscaped}\\*\\*)`, 'gi');
44
+ if (checkboxPattern.test(reqContent)) {
45
+ reqContent = reqContent.replace(checkboxPattern, '$1x$2');
46
+ found = true;
47
+ }
48
+
49
+ // Update traceability table: | REQ-ID | Phase N | Pending | → | REQ-ID | Phase N | Complete |
50
+ const tablePattern = new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi');
51
+ if (tablePattern.test(reqContent)) {
52
+ // Re-read since test() advances lastIndex for global regex
53
+ reqContent = reqContent.replace(
54
+ new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'),
55
+ '$1 Complete $2'
56
+ );
57
+ found = true;
58
+ }
59
+
60
+ if (found) {
61
+ updated.push(reqId);
62
+ } else {
63
+ notFound.push(reqId);
64
+ }
65
+ }
66
+
67
+ if (updated.length > 0) {
68
+ fs.writeFileSync(reqPath, reqContent, 'utf-8');
69
+ }
70
+
71
+ output({
72
+ updated: updated.length > 0,
73
+ marked_complete: updated,
74
+ not_found: notFound,
75
+ total: reqIds.length,
76
+ }, raw, `${updated.length}/${reqIds.length} requirements marked complete`);
77
+ }
78
+
79
+ function cmdMilestoneComplete(cwd, version, options, raw) {
80
+ if (!version) {
81
+ error('version required for milestone complete (e.g., v1.0)');
82
+ }
83
+
84
+ const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
85
+ const reqPath = path.join(cwd, '.planning', 'REQUIREMENTS.md');
86
+ const statePath = path.join(cwd, '.planning', 'STATE.md');
87
+ const milestonesPath = path.join(cwd, '.planning', 'MILESTONES.md');
88
+ const archiveDir = path.join(cwd, '.planning', 'milestones');
89
+ const phasesDir = path.join(cwd, '.planning', 'phases');
90
+ const today = new Date().toISOString().split('T')[0];
91
+ const milestoneName = options.name || version;
92
+
93
+ // Ensure archive directory exists
94
+ fs.mkdirSync(archiveDir, { recursive: true });
95
+
96
+ // Scope stats and accomplishments to only the phases belonging to the
97
+ // current milestone's ROADMAP. Uses the shared filter from core.cjs
98
+ // (same logic used by cmdPhasesList and other callers).
99
+ const isDirInMilestone = getMilestonePhaseFilter(cwd);
100
+
101
+ // Gather stats from phases (scoped to current milestone only)
102
+ let phaseCount = 0;
103
+ let totalPlans = 0;
104
+ let totalTasks = 0;
105
+ const accomplishments = [];
106
+
107
+ try {
108
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
109
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
110
+
111
+ for (const dir of dirs) {
112
+ if (!isDirInMilestone(dir)) continue;
113
+
114
+ phaseCount++;
115
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
116
+ const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
117
+ const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
118
+ totalPlans += plans.length;
119
+
120
+ // Extract one-liners from summaries
121
+ for (const s of summaries) {
122
+ try {
123
+ const content = fs.readFileSync(path.join(phasesDir, dir, s), 'utf-8');
124
+ const fm = extractFrontmatter(content);
125
+ if (fm['one-liner']) {
126
+ accomplishments.push(fm['one-liner']);
127
+ }
128
+ // Count tasks
129
+ const taskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
130
+ totalTasks += taskMatches.length;
131
+ } catch {}
132
+ }
133
+ }
134
+ } catch {}
135
+
136
+ // Archive ROADMAP.md
137
+ if (fs.existsSync(roadmapPath)) {
138
+ const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
139
+ fs.writeFileSync(path.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, 'utf-8');
140
+ }
141
+
142
+ // Archive REQUIREMENTS.md
143
+ if (fs.existsSync(reqPath)) {
144
+ const reqContent = fs.readFileSync(reqPath, 'utf-8');
145
+ const archiveHeader = `# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`.planning/REQUIREMENTS.md\`.\n\n---\n\n`;
146
+ fs.writeFileSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent, 'utf-8');
147
+ }
148
+
149
+ // Archive audit file if exists
150
+ const auditFile = path.join(cwd, '.planning', `${version}-MILESTONE-AUDIT.md`);
151
+ if (fs.existsSync(auditFile)) {
152
+ fs.renameSync(auditFile, path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
153
+ }
154
+
155
+ // Create/append MILESTONES.md entry
156
+ const accomplishmentsList = accomplishments.map(a => `- ${a}`).join('\n');
157
+ const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}\n\n---\n\n`;
158
+
159
+ if (fs.existsSync(milestonesPath)) {
160
+ const existing = fs.readFileSync(milestonesPath, 'utf-8');
161
+ if (!existing.trim()) {
162
+ // Empty file — treat like new
163
+ fs.writeFileSync(milestonesPath, `# Milestones\n\n${milestoneEntry}`, 'utf-8');
164
+ } else {
165
+ // Insert after the header line(s) for reverse chronological order (newest first)
166
+ const headerMatch = existing.match(/^(#{1,3}\s+[^\n]*\n\n?)/);
167
+ if (headerMatch) {
168
+ const header = headerMatch[1];
169
+ const rest = existing.slice(header.length);
170
+ fs.writeFileSync(milestonesPath, header + milestoneEntry + rest, 'utf-8');
171
+ } else {
172
+ // No recognizable header — prepend the entry
173
+ fs.writeFileSync(milestonesPath, milestoneEntry + existing, 'utf-8');
174
+ }
175
+ }
176
+ } else {
177
+ fs.writeFileSync(milestonesPath, `# Milestones\n\n${milestoneEntry}`, 'utf-8');
178
+ }
179
+
180
+ // Update STATE.md
181
+ if (fs.existsSync(statePath)) {
182
+ let stateContent = fs.readFileSync(statePath, 'utf-8');
183
+ stateContent = stateContent.replace(
184
+ /(\*\*Status:\*\*\s*).*/,
185
+ `$1${version} milestone complete`
186
+ );
187
+ stateContent = stateContent.replace(
188
+ /(\*\*Last Activity:\*\*\s*).*/,
189
+ `$1${today}`
190
+ );
191
+ stateContent = stateContent.replace(
192
+ /(\*\*Last Activity Description:\*\*\s*).*/,
193
+ `$1${version} milestone completed and archived`
194
+ );
195
+ writeStateMd(statePath, stateContent, cwd);
196
+ }
197
+
198
+ // Archive phase directories if requested
199
+ let phasesArchived = false;
200
+ if (options.archivePhases) {
201
+ try {
202
+ const phaseArchiveDir = path.join(archiveDir, `${version}-phases`);
203
+ fs.mkdirSync(phaseArchiveDir, { recursive: true });
204
+
205
+ const phaseEntries = fs.readdirSync(phasesDir, { withFileTypes: true });
206
+ const phaseDirNames = phaseEntries.filter(e => e.isDirectory()).map(e => e.name);
207
+ let archivedCount = 0;
208
+ for (const dir of phaseDirNames) {
209
+ if (!isDirInMilestone(dir)) continue;
210
+ fs.renameSync(path.join(phasesDir, dir), path.join(phaseArchiveDir, dir));
211
+ archivedCount++;
212
+ }
213
+ phasesArchived = archivedCount > 0;
214
+ } catch {}
215
+ }
216
+
217
+ const result = {
218
+ version,
219
+ name: milestoneName,
220
+ date: today,
221
+ phases: phaseCount,
222
+ plans: totalPlans,
223
+ tasks: totalTasks,
224
+ accomplishments,
225
+ archived: {
226
+ roadmap: fs.existsSync(path.join(archiveDir, `${version}-ROADMAP.md`)),
227
+ requirements: fs.existsSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`)),
228
+ audit: fs.existsSync(path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
229
+ phases: phasesArchived,
230
+ },
231
+ milestones_updated: true,
232
+ state_updated: fs.existsSync(statePath),
233
+ };
234
+
235
+ output(result, raw);
236
+ }
237
+
238
+ module.exports = {
239
+ cmdRequirementsMarkComplete,
240
+ cmdMilestoneComplete,
241
+ };
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * GSD Model Provider — Unified API for multiple AI providers
5
+ *
6
+ * Supports: Anthropic, Moonshot (Kimi), Alibaba (Qwen), OpenAI
7
+ *
8
+ * Usage:
9
+ * const ModelProvider = require('./model-provider.cjs');
10
+ * const model = new ModelProvider({ provider: 'anthropic', model: 'sonnet' });
11
+ * const response = await model.chat([{ role: 'user', content: 'Hello' }]);
12
+ */
13
+
14
+ const Logger = require('./logger.cjs');
15
+ const logger = new Logger();
16
+
17
+ class ModelProvider {
18
+ /**
19
+ * Create model provider
20
+ * @param {Object} config - Configuration
21
+ */
22
+ constructor(config) {
23
+ this.provider = config.provider || 'anthropic';
24
+ this.model = config.model || 'sonnet';
25
+ this.apiKey = config.apiKey || process.env[`${this.provider.toUpperCase()}_API_KEY`];
26
+
27
+ if (!this.apiKey) {
28
+ logger.warn('API key not configured', { provider: this.provider });
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Send chat message
34
+ * @param {Object[]} messages - Chat messages
35
+ * @param {Object} options - Chat options
36
+ * @returns {Promise<Object>} - Response
37
+ */
38
+ async chat(messages, options = {}) {
39
+ logger.info('Chat request', {
40
+ provider: this.provider,
41
+ model: this.model,
42
+ messageCount: messages.length
43
+ });
44
+
45
+ switch (this.provider) {
46
+ case 'anthropic':
47
+ return this._chatAnthropic(messages, options);
48
+ case 'moonshot':
49
+ return this._chatMoonshot(messages, options);
50
+ case 'alibaba':
51
+ return this._chatQwen(messages, options);
52
+ case 'openai':
53
+ return this._chatOpenAI(messages, options);
54
+ default:
55
+ throw new Error(`Unsupported provider: ${this.provider}`);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Anthropic Claude API
61
+ */
62
+ async _chatAnthropic(messages, options) {
63
+ // Placeholder - would use @anthropic-ai/sdk in production
64
+ logger.debug('Anthropic chat', { model: this.model });
65
+ return {
66
+ content: '[Anthropic response placeholder]',
67
+ provider: 'anthropic',
68
+ model: this.model
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Moonshot (Kimi) API
74
+ */
75
+ async _chatMoonshot(messages, options) {
76
+ // Placeholder - would use moonshot SDK in production
77
+ logger.debug('Moonshot chat', { model: this.model });
78
+ return {
79
+ content: '[Moonshot response placeholder]',
80
+ provider: 'moonshot',
81
+ model: this.model
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Alibaba Qwen API
87
+ */
88
+ async _chatQwen(messages, options) {
89
+ // Placeholder - would use DashScope SDK in production
90
+ logger.debug('Qwen chat', { model: this.model });
91
+ return {
92
+ content: '[Qwen response placeholder]',
93
+ provider: 'alibaba',
94
+ model: this.model
95
+ };
96
+ }
97
+
98
+ /**
99
+ * OpenAI API
100
+ */
101
+ async _chatOpenAI(messages, options) {
102
+ // Placeholder - would use openai SDK in production
103
+ logger.debug('OpenAI chat', { model: this.model });
104
+ return {
105
+ content: '[OpenAI response placeholder]',
106
+ provider: 'openai',
107
+ model: this.model
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Count tokens (approximate)
113
+ * @param {string} text - Text to count
114
+ * @returns {number} - Approximate token count
115
+ */
116
+ countTokens(text) {
117
+ // Rough estimate: 1 token ≈ 4 characters
118
+ return Math.ceil(text.length / 4);
119
+ }
120
+
121
+ /**
122
+ * Get provider info
123
+ * @returns {Object} - Provider information
124
+ */
125
+ getInfo() {
126
+ return {
127
+ provider: this.provider,
128
+ model: this.model,
129
+ hasApiKey: !!this.apiKey
130
+ };
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Create provider from config
136
+ * @param {Object} config - Provider config
137
+ * @returns {ModelProvider} - Model provider instance
138
+ */
139
+ function createProvider(config) {
140
+ return new ModelProvider(config);
141
+ }
142
+
143
+ module.exports = {
144
+ ModelProvider,
145
+ createProvider
146
+ };