@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.
- package/LICENSE +21 -21
- package/README.md +84 -20
- package/agents/ez-observer-agent.md +260 -0
- package/agents/ez-release-agent.md +333 -0
- package/agents/ez-requirements-agent.md +377 -0
- package/agents/ez-scrum-master-agent.md +242 -0
- package/agents/ez-tech-lead-agent.md +267 -0
- package/bin/install.js +3221 -3230
- package/commands/ez/arch-review.md +102 -0
- package/commands/ez/execute-phase.md +11 -0
- package/commands/ez/export-session.md +79 -0
- package/commands/ez/gather-requirements.md +117 -0
- package/commands/ez/git-workflow.md +72 -0
- package/commands/ez/hotfix.md +120 -0
- package/commands/ez/import-session.md +82 -0
- package/commands/ez/join-discord.md +18 -18
- package/commands/ez/list-sessions.md +96 -0
- package/commands/ez/package-manager.md +316 -0
- package/commands/ez/plan-phase.md +9 -1
- package/commands/ez/preflight.md +79 -0
- package/commands/ez/progress.md +13 -1
- package/commands/ez/release.md +153 -0
- package/commands/ez/resume.md +107 -0
- package/commands/ez/standup.md +85 -0
- package/ez-agents/bin/ez-tools.cjs +1095 -716
- package/ez-agents/bin/lib/assistant-adapter.cjs +264 -264
- package/ez-agents/bin/lib/audit-exec.cjs +7 -2
- package/ez-agents/bin/lib/bdd-validator.cjs +622 -0
- package/ez-agents/bin/lib/circuit-breaker.cjs +118 -118
- package/ez-agents/bin/lib/config.cjs +190 -190
- package/ez-agents/bin/lib/content-scanner.cjs +238 -0
- package/ez-agents/bin/lib/context-cache.cjs +154 -0
- package/ez-agents/bin/lib/context-errors.cjs +71 -0
- package/ez-agents/bin/lib/context-manager.cjs +220 -0
- package/ez-agents/bin/lib/discussion-synthesizer.cjs +458 -0
- package/ez-agents/bin/lib/file-access.cjs +207 -0
- package/ez-agents/bin/lib/file-lock.cjs +236 -236
- package/ez-agents/bin/lib/frontmatter.cjs +299 -299
- package/ez-agents/bin/lib/fs-utils.cjs +153 -153
- package/ez-agents/bin/lib/git-errors.cjs +83 -0
- package/ez-agents/bin/lib/git-utils.cjs +118 -0
- package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
- package/ez-agents/bin/lib/index.cjs +157 -113
- package/ez-agents/bin/lib/init.cjs +757 -757
- package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
- package/ez-agents/bin/lib/logger.cjs +124 -124
- package/ez-agents/bin/lib/memory-compression.cjs +256 -0
- package/ez-agents/bin/lib/metrics-tracker.cjs +406 -0
- package/ez-agents/bin/lib/milestone.cjs +241 -241
- package/ez-agents/bin/lib/model-provider.cjs +241 -241
- package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
- package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
- package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
- package/ez-agents/bin/lib/phase.cjs +925 -925
- package/ez-agents/bin/lib/planning-write.cjs +107 -107
- package/ez-agents/bin/lib/release-validator.cjs +614 -0
- package/ez-agents/bin/lib/retry.cjs +119 -119
- package/ez-agents/bin/lib/roadmap.cjs +306 -306
- package/ez-agents/bin/lib/safe-exec.cjs +128 -128
- package/ez-agents/bin/lib/safe-path.cjs +130 -130
- package/ez-agents/bin/lib/session-chain.cjs +304 -0
- package/ez-agents/bin/lib/session-errors.cjs +81 -0
- package/ez-agents/bin/lib/session-export.cjs +251 -0
- package/ez-agents/bin/lib/session-import.cjs +262 -0
- package/ez-agents/bin/lib/session-manager.cjs +280 -0
- package/ez-agents/bin/lib/state.cjs +736 -736
- package/ez-agents/bin/lib/temp-file.cjs +239 -239
- package/ez-agents/bin/lib/template.cjs +223 -223
- package/ez-agents/bin/lib/test-file-lock.cjs +112 -112
- package/ez-agents/bin/lib/test-graceful.cjs +93 -93
- package/ez-agents/bin/lib/test-logger.cjs +60 -60
- package/ez-agents/bin/lib/test-safe-exec.cjs +38 -38
- package/ez-agents/bin/lib/test-safe-path.cjs +33 -33
- package/ez-agents/bin/lib/test-temp-file.cjs +125 -125
- package/ez-agents/bin/lib/tier-manager.cjs +428 -0
- package/ez-agents/bin/lib/timeout-exec.cjs +63 -63
- package/ez-agents/bin/lib/url-fetch.cjs +170 -0
- package/ez-agents/bin/lib/verify.cjs +15 -1
- package/ez-agents/references/checkpoints.md +776 -776
- package/ez-agents/references/continuation-format.md +249 -249
- package/ez-agents/references/metrics-schema.md +118 -0
- package/ez-agents/references/planning-config.md +140 -0
- package/ez-agents/references/questioning.md +162 -162
- package/ez-agents/references/tdd.md +263 -263
- package/ez-agents/references/tier-strategy.md +103 -0
- package/ez-agents/templates/bdd-feature.md +173 -0
- package/ez-agents/templates/codebase/concerns.md +310 -310
- package/ez-agents/templates/codebase/conventions.md +307 -307
- package/ez-agents/templates/codebase/integrations.md +280 -280
- package/ez-agents/templates/codebase/stack.md +186 -186
- package/ez-agents/templates/codebase/testing.md +480 -480
- package/ez-agents/templates/config.json +37 -37
- package/ez-agents/templates/continue-here.md +78 -78
- package/ez-agents/templates/discussion.md +68 -0
- package/ez-agents/templates/incident-runbook.md +205 -0
- package/ez-agents/templates/milestone-archive.md +123 -123
- package/ez-agents/templates/milestone.md +115 -115
- package/ez-agents/templates/release-checklist.md +133 -0
- package/ez-agents/templates/requirements.md +231 -231
- package/ez-agents/templates/research-project/ARCHITECTURE.md +204 -204
- package/ez-agents/templates/research-project/FEATURES.md +147 -147
- package/ez-agents/templates/research-project/PITFALLS.md +200 -200
- package/ez-agents/templates/research-project/STACK.md +120 -120
- package/ez-agents/templates/research-project/SUMMARY.md +170 -170
- package/ez-agents/templates/retrospective.md +54 -54
- package/ez-agents/templates/roadmap.md +202 -202
- package/ez-agents/templates/rollback-plan.md +201 -0
- package/ez-agents/templates/summary-minimal.md +41 -41
- package/ez-agents/templates/summary-standard.md +48 -48
- package/ez-agents/templates/summary.md +248 -248
- package/ez-agents/templates/user-setup.md +311 -311
- package/ez-agents/templates/verification-report.md +322 -322
- package/ez-agents/workflows/add-phase.md +112 -112
- package/ez-agents/workflows/add-tests.md +351 -351
- package/ez-agents/workflows/add-todo.md +158 -158
- package/ez-agents/workflows/arch-review.md +54 -0
- package/ez-agents/workflows/audit-milestone.md +332 -332
- package/ez-agents/workflows/autonomous.md +131 -30
- package/ez-agents/workflows/check-todos.md +177 -177
- package/ez-agents/workflows/cleanup.md +152 -152
- package/ez-agents/workflows/complete-milestone.md +766 -766
- package/ez-agents/workflows/diagnose-issues.md +219 -219
- package/ez-agents/workflows/discovery-phase.md +289 -289
- package/ez-agents/workflows/discuss-phase.md +762 -762
- package/ez-agents/workflows/execute-phase.md +513 -468
- package/ez-agents/workflows/execute-plan.md +483 -483
- package/ez-agents/workflows/export-session.md +255 -0
- package/ez-agents/workflows/gather-requirements.md +206 -0
- package/ez-agents/workflows/health.md +159 -159
- package/ez-agents/workflows/help.md +584 -492
- package/ez-agents/workflows/hotfix.md +291 -0
- package/ez-agents/workflows/import-session.md +303 -0
- package/ez-agents/workflows/insert-phase.md +130 -130
- package/ez-agents/workflows/list-phase-assumptions.md +178 -178
- package/ez-agents/workflows/map-codebase.md +316 -316
- package/ez-agents/workflows/new-milestone.md +339 -10
- package/ez-agents/workflows/new-project.md +293 -299
- package/ez-agents/workflows/node-repair.md +92 -92
- package/ez-agents/workflows/pause-work.md +122 -122
- package/ez-agents/workflows/plan-milestone-gaps.md +274 -274
- package/ez-agents/workflows/plan-phase.md +673 -651
- package/ez-agents/workflows/progress.md +372 -382
- package/ez-agents/workflows/quick.md +610 -610
- package/ez-agents/workflows/release.md +253 -0
- package/ez-agents/workflows/remove-phase.md +155 -155
- package/ez-agents/workflows/research-phase.md +74 -74
- package/ez-agents/workflows/resume-project.md +307 -307
- package/ez-agents/workflows/resume-session.md +215 -0
- package/ez-agents/workflows/set-profile.md +81 -81
- package/ez-agents/workflows/settings.md +242 -242
- package/ez-agents/workflows/standup.md +64 -0
- package/ez-agents/workflows/stats.md +57 -57
- package/ez-agents/workflows/transition.md +544 -544
- package/ez-agents/workflows/ui-phase.md +290 -290
- package/ez-agents/workflows/ui-review.md +157 -157
- package/ez-agents/workflows/update.md +320 -320
- package/ez-agents/workflows/validate-phase.md +167 -167
- package/ez-agents/workflows/verify-phase.md +243 -243
- package/ez-agents/workflows/verify-work.md +584 -584
- package/package.json +10 -4
- package/scripts/build-hooks.js +43 -43
- package/scripts/run-tests.cjs +29 -29
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* EZ FS Utils — Cross-platform file system utilities
|
|
5
|
-
*
|
|
6
|
-
* Replaces Unix commands (find, grep, head, tail) with
|
|
7
|
-
* cross-platform JavaScript implementations.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* const { findFiles, grepContent, readLines } = require('./fs-utils.cjs');
|
|
11
|
-
* const files = await findFiles('./src', /\.js$/);
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const Logger = require('./logger.cjs');
|
|
17
|
-
const logger = new Logger();
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Find files matching patterns (cross-platform find replacement)
|
|
21
|
-
* @param {string} dir - Directory to search
|
|
22
|
-
* @param {RegExp[]} patterns - File patterns to match
|
|
23
|
-
* @param {Object} options - Search options
|
|
24
|
-
* @returns {string[]} - Array of matching file paths
|
|
25
|
-
*/
|
|
26
|
-
function findFiles(dir, patterns, options = {}) {
|
|
27
|
-
const { maxDepth = 5, exclude = ['node_modules', '.git', '.planning'] } = options;
|
|
28
|
-
const results = [];
|
|
29
|
-
|
|
30
|
-
function recurse(currentDir, depth) {
|
|
31
|
-
if (depth > maxDepth) return;
|
|
32
|
-
|
|
33
|
-
let entries;
|
|
34
|
-
try {
|
|
35
|
-
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
36
|
-
} catch (err) {
|
|
37
|
-
logger.warn('Cannot read directory', { dir: currentDir, error: err.message });
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
if (exclude.includes(entry.name)) continue;
|
|
43
|
-
|
|
44
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
45
|
-
|
|
46
|
-
if (entry.isDirectory()) {
|
|
47
|
-
recurse(fullPath, depth + 1);
|
|
48
|
-
} else if (patterns.some(p => p.test(entry.name))) {
|
|
49
|
-
results.push(fullPath);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
recurse(dir, 0);
|
|
55
|
-
return results;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Search file content (cross-platform grep replacement)
|
|
60
|
-
* @param {string} filePath - File to search
|
|
61
|
-
* @param {RegExp} pattern - Pattern to match
|
|
62
|
-
* @returns {Object[]} - Array of { line, lineNumber, match }
|
|
63
|
-
*/
|
|
64
|
-
function grepContent(filePath, pattern) {
|
|
65
|
-
const results = [];
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
69
|
-
const lines = content.split('\n');
|
|
70
|
-
|
|
71
|
-
lines.forEach((line, index) => {
|
|
72
|
-
if (pattern.test(line)) {
|
|
73
|
-
results.push({
|
|
74
|
-
line: line.trim(),
|
|
75
|
-
lineNumber: index + 1,
|
|
76
|
-
match: line.match(pattern)?.[0]
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
} catch (err) {
|
|
81
|
-
logger.warn('Cannot read file for grep', { filePath, error: err.message });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return results;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Read first N lines (cross-platform head replacement)
|
|
89
|
-
* @param {string} filePath - File to read
|
|
90
|
-
* @param {number} count - Number of lines
|
|
91
|
-
* @returns {string[]} - Array of lines
|
|
92
|
-
*/
|
|
93
|
-
function readFirstLines(filePath, count = 10) {
|
|
94
|
-
try {
|
|
95
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
96
|
-
return content.split('\n').slice(0, count);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
logger.warn('Cannot read file', { filePath, error: err.message });
|
|
99
|
-
return [];
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Read last N lines (cross-platform tail replacement)
|
|
105
|
-
* @param {string} filePath - File to read
|
|
106
|
-
* @param {number} count - Number of lines
|
|
107
|
-
* @returns {string[]} - Array of lines
|
|
108
|
-
*/
|
|
109
|
-
function readLastLines(filePath, count = 10) {
|
|
110
|
-
try {
|
|
111
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
112
|
-
const lines = content.split('\n');
|
|
113
|
-
return lines.slice(-count);
|
|
114
|
-
} catch (err) {
|
|
115
|
-
logger.warn('Cannot read file', { filePath, error: err.message });
|
|
116
|
-
return [];
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Count lines in file (cross-platform wc -l replacement)
|
|
122
|
-
* @param {string} filePath - File to count
|
|
123
|
-
* @returns {number} - Line count
|
|
124
|
-
*/
|
|
125
|
-
function countLines(filePath) {
|
|
126
|
-
try {
|
|
127
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
128
|
-
return content.split('\n').length;
|
|
129
|
-
} catch (err) {
|
|
130
|
-
logger.warn('Cannot count lines', { filePath, error: err.message });
|
|
131
|
-
return 0;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Ensure directory exists
|
|
137
|
-
* @param {string} dirPath - Directory path
|
|
138
|
-
*/
|
|
139
|
-
function ensureDir(dirPath) {
|
|
140
|
-
if (!fs.existsSync(dirPath)) {
|
|
141
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
142
|
-
logger.debug('Directory created', { dir: dirPath });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
module.exports = {
|
|
147
|
-
findFiles,
|
|
148
|
-
grepContent,
|
|
149
|
-
readFirstLines,
|
|
150
|
-
readLastLines,
|
|
151
|
-
countLines,
|
|
152
|
-
ensureDir
|
|
153
|
-
};
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EZ FS Utils — Cross-platform file system utilities
|
|
5
|
+
*
|
|
6
|
+
* Replaces Unix commands (find, grep, head, tail) with
|
|
7
|
+
* cross-platform JavaScript implementations.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const { findFiles, grepContent, readLines } = require('./fs-utils.cjs');
|
|
11
|
+
* const files = await findFiles('./src', /\.js$/);
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const Logger = require('./logger.cjs');
|
|
17
|
+
const logger = new Logger();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Find files matching patterns (cross-platform find replacement)
|
|
21
|
+
* @param {string} dir - Directory to search
|
|
22
|
+
* @param {RegExp[]} patterns - File patterns to match
|
|
23
|
+
* @param {Object} options - Search options
|
|
24
|
+
* @returns {string[]} - Array of matching file paths
|
|
25
|
+
*/
|
|
26
|
+
function findFiles(dir, patterns, options = {}) {
|
|
27
|
+
const { maxDepth = 5, exclude = ['node_modules', '.git', '.planning'] } = options;
|
|
28
|
+
const results = [];
|
|
29
|
+
|
|
30
|
+
function recurse(currentDir, depth) {
|
|
31
|
+
if (depth > maxDepth) return;
|
|
32
|
+
|
|
33
|
+
let entries;
|
|
34
|
+
try {
|
|
35
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
36
|
+
} catch (err) {
|
|
37
|
+
logger.warn('Cannot read directory', { dir: currentDir, error: err.message });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (exclude.includes(entry.name)) continue;
|
|
43
|
+
|
|
44
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
45
|
+
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
recurse(fullPath, depth + 1);
|
|
48
|
+
} else if (patterns.some(p => p.test(entry.name))) {
|
|
49
|
+
results.push(fullPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
recurse(dir, 0);
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Search file content (cross-platform grep replacement)
|
|
60
|
+
* @param {string} filePath - File to search
|
|
61
|
+
* @param {RegExp} pattern - Pattern to match
|
|
62
|
+
* @returns {Object[]} - Array of { line, lineNumber, match }
|
|
63
|
+
*/
|
|
64
|
+
function grepContent(filePath, pattern) {
|
|
65
|
+
const results = [];
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
69
|
+
const lines = content.split('\n');
|
|
70
|
+
|
|
71
|
+
lines.forEach((line, index) => {
|
|
72
|
+
if (pattern.test(line)) {
|
|
73
|
+
results.push({
|
|
74
|
+
line: line.trim(),
|
|
75
|
+
lineNumber: index + 1,
|
|
76
|
+
match: line.match(pattern)?.[0]
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
logger.warn('Cannot read file for grep', { filePath, error: err.message });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read first N lines (cross-platform head replacement)
|
|
89
|
+
* @param {string} filePath - File to read
|
|
90
|
+
* @param {number} count - Number of lines
|
|
91
|
+
* @returns {string[]} - Array of lines
|
|
92
|
+
*/
|
|
93
|
+
function readFirstLines(filePath, count = 10) {
|
|
94
|
+
try {
|
|
95
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
96
|
+
return content.split('\n').slice(0, count);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
logger.warn('Cannot read file', { filePath, error: err.message });
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Read last N lines (cross-platform tail replacement)
|
|
105
|
+
* @param {string} filePath - File to read
|
|
106
|
+
* @param {number} count - Number of lines
|
|
107
|
+
* @returns {string[]} - Array of lines
|
|
108
|
+
*/
|
|
109
|
+
function readLastLines(filePath, count = 10) {
|
|
110
|
+
try {
|
|
111
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
112
|
+
const lines = content.split('\n');
|
|
113
|
+
return lines.slice(-count);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
logger.warn('Cannot read file', { filePath, error: err.message });
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Count lines in file (cross-platform wc -l replacement)
|
|
122
|
+
* @param {string} filePath - File to count
|
|
123
|
+
* @returns {number} - Line count
|
|
124
|
+
*/
|
|
125
|
+
function countLines(filePath) {
|
|
126
|
+
try {
|
|
127
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
128
|
+
return content.split('\n').length;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
logger.warn('Cannot count lines', { filePath, error: err.message });
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Ensure directory exists
|
|
137
|
+
* @param {string} dirPath - Directory path
|
|
138
|
+
*/
|
|
139
|
+
function ensureDir(dirPath) {
|
|
140
|
+
if (!fs.existsSync(dirPath)) {
|
|
141
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
142
|
+
logger.debug('Directory created', { dir: dirPath });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
findFiles,
|
|
148
|
+
grepContent,
|
|
149
|
+
readFirstLines,
|
|
150
|
+
readLastLines,
|
|
151
|
+
countLines,
|
|
152
|
+
ensureDir
|
|
153
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Git Workflow Error Classes
|
|
5
|
+
*
|
|
6
|
+
* Provides structured error handling for git operations
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
class GitWorkflowError extends Error {
|
|
10
|
+
constructor(message, options = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'GitWorkflowError';
|
|
13
|
+
this.code = options.code || 'GIT_WORKFLOW_ERROR';
|
|
14
|
+
this.details = options.details || {};
|
|
15
|
+
this.timestamp = new Date().toISOString();
|
|
16
|
+
Error.captureStackTrace(this, GitWorkflowError);
|
|
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 BranchExistsError extends GitWorkflowError {
|
|
31
|
+
constructor(branchName, options = {}) {
|
|
32
|
+
super(`Branch '${branchName}' already exists`, {
|
|
33
|
+
code: 'BRANCH_EXISTS',
|
|
34
|
+
details: { branchName, ...options.details }
|
|
35
|
+
});
|
|
36
|
+
this.name = 'BranchExistsError';
|
|
37
|
+
this.branchName = branchName;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class BranchNotFoundError extends GitWorkflowError {
|
|
42
|
+
constructor(branchName, options = {}) {
|
|
43
|
+
super(`Branch '${branchName}' does not exist`, {
|
|
44
|
+
code: 'BRANCH_NOT_FOUND',
|
|
45
|
+
details: { branchName, ...options.details }
|
|
46
|
+
});
|
|
47
|
+
this.name = 'BranchNotFoundError';
|
|
48
|
+
this.branchName = branchName;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class MergeConflictError extends GitWorkflowError {
|
|
53
|
+
constructor(sourceBranch, targetBranch, conflictingFiles = [], options = {}) {
|
|
54
|
+
super(`Merge conflict between '${sourceBranch}' and '${targetBranch}'`, {
|
|
55
|
+
code: 'MERGE_CONFLICT',
|
|
56
|
+
details: { sourceBranch, targetBranch, conflictingFiles, ...options.details }
|
|
57
|
+
});
|
|
58
|
+
this.name = 'MergeConflictError';
|
|
59
|
+
this.sourceBranch = sourceBranch;
|
|
60
|
+
this.targetBranch = targetBranch;
|
|
61
|
+
this.conflictingFiles = conflictingFiles;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class ValidationFailedError extends GitWorkflowError {
|
|
66
|
+
constructor(validationType, failures = [], options = {}) {
|
|
67
|
+
super(`${validationType} validation failed`, {
|
|
68
|
+
code: 'VALIDATION_FAILED',
|
|
69
|
+
details: { validationType, failures, ...options.details }
|
|
70
|
+
});
|
|
71
|
+
this.name = 'ValidationFailedError';
|
|
72
|
+
this.validationType = validationType;
|
|
73
|
+
this.failures = failures;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
GitWorkflowError,
|
|
79
|
+
BranchExistsError,
|
|
80
|
+
BranchNotFoundError,
|
|
81
|
+
MergeConflictError,
|
|
82
|
+
ValidationFailedError
|
|
83
|
+
};
|
|
@@ -20,6 +20,8 @@ const { promisify } = require('util');
|
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
21
|
const Logger = require('./logger.cjs');
|
|
22
22
|
const logger = new Logger();
|
|
23
|
+
const { BranchExistsError, BranchNotFoundError } = require('./git-errors.cjs');
|
|
24
|
+
const { MergeConflictError } = require('./git-errors.cjs');
|
|
23
25
|
|
|
24
26
|
class GitUtils {
|
|
25
27
|
constructor(cwd) {
|
|
@@ -137,6 +139,122 @@ class GitUtils {
|
|
|
137
139
|
return name;
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Check if branch exists
|
|
144
|
+
*/
|
|
145
|
+
async branchExists(branchName) {
|
|
146
|
+
try {
|
|
147
|
+
await this.exec(['rev-parse', '--verify', branchName]);
|
|
148
|
+
return true;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* List branches matching pattern
|
|
156
|
+
*/
|
|
157
|
+
async listBranches(pattern = '*') {
|
|
158
|
+
const { stdout } = await this.exec(['branch', '--list', pattern]);
|
|
159
|
+
return stdout.split('\n')
|
|
160
|
+
.map(b => b.trim().replace(/^\*\s*/, '').replace(/^\+\s*/, ''))
|
|
161
|
+
.filter(b => b);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Delete branch
|
|
166
|
+
*/
|
|
167
|
+
async deleteBranch(branchName, force = false) {
|
|
168
|
+
const exists = await this.branchExists(branchName);
|
|
169
|
+
if (!exists) {
|
|
170
|
+
throw new BranchNotFoundError(branchName);
|
|
171
|
+
}
|
|
172
|
+
const args = force ? ['branch', '-D', branchName] : ['branch', '-d', branchName];
|
|
173
|
+
await this.exec(args);
|
|
174
|
+
logger.info('Branch deleted', { branch: branchName, force });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get branch point (merge base with HEAD)
|
|
179
|
+
*/
|
|
180
|
+
async getBranchPoint(branchName) {
|
|
181
|
+
const { stdout } = await this.exec(['merge-base', 'HEAD', branchName]);
|
|
182
|
+
return stdout;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if merge would have conflicts
|
|
187
|
+
*/
|
|
188
|
+
async hasConflicts(sourceBranch, targetBranch) {
|
|
189
|
+
try {
|
|
190
|
+
await this.exec(['merge-tree', targetBranch, sourceBranch]);
|
|
191
|
+
return false;
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err.stdout?.includes('conflict')) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Merge with strategy (merge, squash, rebase)
|
|
202
|
+
*/
|
|
203
|
+
async mergeWithStrategy(source, target, strategy = 'merge') {
|
|
204
|
+
const currentBranch = await this.getCurrentBranch();
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
await this.checkout(target);
|
|
208
|
+
|
|
209
|
+
if (strategy === 'squash') {
|
|
210
|
+
await this.exec(['merge', '--squash', source]);
|
|
211
|
+
await this.exec(['commit', '-m', `Merge '${source}' into '${target}'`]);
|
|
212
|
+
} else if (strategy === 'rebase') {
|
|
213
|
+
await this.exec(['rebase', source]);
|
|
214
|
+
} else {
|
|
215
|
+
await this.exec(['merge', source, '--no-edit']);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
logger.info('Merge completed', { source, target, strategy });
|
|
219
|
+
return { success: true, strategy };
|
|
220
|
+
} catch (err) {
|
|
221
|
+
if (err.message?.includes('conflict')) {
|
|
222
|
+
throw new MergeConflictError(source, target);
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
225
|
+
} finally {
|
|
226
|
+
if (currentBranch !== target) {
|
|
227
|
+
await this.checkout(currentBranch);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Squash merge with custom commit message
|
|
234
|
+
*/
|
|
235
|
+
async squashMerge(source, target, commitMessage) {
|
|
236
|
+
await this.checkout(target);
|
|
237
|
+
await this.exec(['merge', '--squash', source]);
|
|
238
|
+
await this.exec(['commit', '-m', commitMessage]);
|
|
239
|
+
logger.info('Squash merge completed', { source, target, message: commitMessage });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Abort merge
|
|
244
|
+
*/
|
|
245
|
+
async abortMerge() {
|
|
246
|
+
await this.exec(['merge', '--abort']);
|
|
247
|
+
logger.info('Merge aborted');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Revert commit
|
|
252
|
+
*/
|
|
253
|
+
async revertCommit(commitHash) {
|
|
254
|
+
await this.exec(['revert', commitHash, '--no-edit']);
|
|
255
|
+
logger.info('Commit reverted', { hash: commitHash });
|
|
256
|
+
}
|
|
257
|
+
|
|
140
258
|
/**
|
|
141
259
|
* Switch to branch
|
|
142
260
|
*/
|