@howlil/ez-agents 3.4.2 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +77 -2
  2. package/agents/ez-observer-agent.md +260 -0
  3. package/agents/ez-release-agent.md +333 -0
  4. package/agents/ez-requirements-agent.md +377 -0
  5. package/agents/ez-scrum-master-agent.md +242 -0
  6. package/agents/ez-tech-lead-agent.md +267 -0
  7. package/bin/install.js +3221 -3272
  8. package/commands/ez/arch-review.md +102 -0
  9. package/commands/ez/execute-phase.md +11 -0
  10. package/commands/ez/export-session.md +79 -0
  11. package/commands/ez/gather-requirements.md +117 -0
  12. package/commands/ez/git-workflow.md +72 -0
  13. package/commands/ez/hotfix.md +120 -0
  14. package/commands/ez/import-session.md +82 -0
  15. package/commands/ez/list-sessions.md +96 -0
  16. package/commands/ez/package-manager.md +316 -0
  17. package/commands/ez/plan-phase.md +9 -1
  18. package/commands/ez/preflight.md +79 -0
  19. package/commands/ez/progress.md +13 -1
  20. package/commands/ez/release.md +153 -0
  21. package/commands/ez/resume.md +107 -0
  22. package/commands/ez/standup.md +85 -0
  23. package/ez-agents/bin/ez-tools.cjs +1095 -716
  24. package/ez-agents/bin/lib/bdd-validator.cjs +622 -0
  25. package/ez-agents/bin/lib/content-scanner.cjs +238 -0
  26. package/ez-agents/bin/lib/context-cache.cjs +154 -0
  27. package/ez-agents/bin/lib/context-errors.cjs +71 -0
  28. package/ez-agents/bin/lib/context-manager.cjs +220 -0
  29. package/ez-agents/bin/lib/discussion-synthesizer.cjs +458 -0
  30. package/ez-agents/bin/lib/file-access.cjs +207 -0
  31. package/ez-agents/bin/lib/git-errors.cjs +83 -0
  32. package/ez-agents/bin/lib/git-utils.cjs +321 -203
  33. package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
  34. package/ez-agents/bin/lib/index.cjs +46 -2
  35. package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
  36. package/ez-agents/bin/lib/logger.cjs +124 -154
  37. package/ez-agents/bin/lib/memory-compression.cjs +256 -0
  38. package/ez-agents/bin/lib/metrics-tracker.cjs +406 -0
  39. package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
  40. package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
  41. package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
  42. package/ez-agents/bin/lib/release-validator.cjs +614 -0
  43. package/ez-agents/bin/lib/safe-exec.cjs +128 -214
  44. package/ez-agents/bin/lib/session-chain.cjs +304 -0
  45. package/ez-agents/bin/lib/session-errors.cjs +81 -0
  46. package/ez-agents/bin/lib/session-export.cjs +251 -0
  47. package/ez-agents/bin/lib/session-import.cjs +262 -0
  48. package/ez-agents/bin/lib/session-manager.cjs +280 -0
  49. package/ez-agents/bin/lib/tier-manager.cjs +428 -0
  50. package/ez-agents/bin/lib/url-fetch.cjs +170 -0
  51. package/ez-agents/references/metrics-schema.md +118 -0
  52. package/ez-agents/references/planning-config.md +140 -0
  53. package/ez-agents/references/tier-strategy.md +103 -0
  54. package/ez-agents/templates/bdd-feature.md +173 -0
  55. package/ez-agents/templates/discussion.md +68 -0
  56. package/ez-agents/templates/incident-runbook.md +205 -0
  57. package/ez-agents/templates/release-checklist.md +133 -0
  58. package/ez-agents/templates/rollback-plan.md +201 -0
  59. package/ez-agents/workflows/arch-review.md +54 -0
  60. package/ez-agents/workflows/autonomous.md +844 -743
  61. package/ez-agents/workflows/execute-phase.md +45 -0
  62. package/ez-agents/workflows/export-session.md +255 -0
  63. package/ez-agents/workflows/gather-requirements.md +206 -0
  64. package/ez-agents/workflows/help.md +92 -0
  65. package/ez-agents/workflows/hotfix.md +291 -0
  66. package/ez-agents/workflows/import-session.md +303 -0
  67. package/ez-agents/workflows/new-milestone.md +713 -384
  68. package/ez-agents/workflows/new-project.md +1107 -1113
  69. package/ez-agents/workflows/plan-phase.md +22 -0
  70. package/ez-agents/workflows/progress.md +15 -25
  71. package/ez-agents/workflows/release.md +253 -0
  72. package/ez-agents/workflows/resume-session.md +215 -0
  73. package/ez-agents/workflows/standup.md +64 -0
  74. package/package.json +9 -2
@@ -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
+ };
@@ -1,203 +1,321 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * EZ Git Utils — Safe git operations with atomic commits
5
- *
6
- * Provides:
7
- * - Atomic commits with validation
8
- * - Branch creation and management
9
- * - Safe git operations using execFile
10
- * - Error handling with recovery suggestions
11
- *
12
- * Usage:
13
- * const GitUtils = require('./git-utils.cjs');
14
- * const git = new GitUtils(process.cwd());
15
- * await git.commitAtomic('feat: add feature', ['file1.js']);
16
- */
17
-
18
- const { execFile } = require('child_process');
19
- const { promisify } = require('util');
20
- const execFileAsync = promisify(execFile);
21
- const Logger = require('./logger.cjs');
22
- const logger = new Logger();
23
-
24
- class GitUtils {
25
- constructor(cwd) {
26
- this.cwd = cwd || process.cwd();
27
- }
28
-
29
- /**
30
- * Execute git command safely
31
- */
32
- async exec(args, options = {}) {
33
- const { timeout = 30000 } = options;
34
-
35
- try {
36
- const { stdout, stderr } = await execFileAsync('git', args, {
37
- cwd: this.cwd,
38
- encoding: 'utf-8',
39
- timeout,
40
- maxBuffer: 10 * 1024 * 1024
41
- });
42
-
43
- logger.debug('Git command executed', { args, stdout_length: stdout?.length });
44
-
45
- return { stdout: stdout?.trim(), stderr: stderr?.trim() };
46
- } catch (err) {
47
- logger.error('Git command failed', { args, error: err.message });
48
- throw err;
49
- }
50
- }
51
-
52
- /**
53
- * Check if directory is a git repo
54
- */
55
- async isGitRepo() {
56
- try {
57
- await this.exec(['rev-parse', '--git-dir']);
58
- return true;
59
- } catch {
60
- return false;
61
- }
62
- }
63
-
64
- /**
65
- * Get current branch name
66
- */
67
- async getCurrentBranch() {
68
- const { stdout } = await this.exec(['rev-parse', '--abbrev-ref', 'HEAD']);
69
- return stdout;
70
- }
71
-
72
- /**
73
- * Get git status (porcelain format)
74
- */
75
- async getStatus() {
76
- const { stdout } = await this.exec(['status', '--porcelain']);
77
- return stdout.split('\n').filter(line => line.trim());
78
- }
79
-
80
- /**
81
- * Check if there are uncommitted changes
82
- */
83
- async hasChanges() {
84
- const status = await this.getStatus();
85
- return status.length > 0;
86
- }
87
-
88
- /**
89
- * Stage files for commit
90
- */
91
- async add(files) {
92
- if (!Array.isArray(files)) files = [files];
93
- await this.exec(['add', ...files]);
94
- logger.info('Files staged', { files });
95
- }
96
-
97
- /**
98
- * Add all changes
99
- */
100
- async addAll() {
101
- await this.exec(['add', '-A']);
102
- logger.info('All files staged');
103
- }
104
-
105
- /**
106
- * Create atomic commit with validation
107
- */
108
- async commitAtomic(message, files = []) {
109
- // Stage files if provided
110
- if (files.length > 0) {
111
- await this.add(files);
112
- }
113
-
114
- // Validate commit message format (conventional commits)
115
- const conventionalPattern = /^(feat|fix|docs|chore|test|refactor|style|perf)(\([^)]+\))?:\s.+/;
116
- if (!conventionalPattern.test(message)) {
117
- logger.warn('Commit message may not follow conventional commits', { message });
118
- }
119
-
120
- // Create commit
121
- await this.exec(['commit', '-m', message]);
122
-
123
- // Get commit hash
124
- const { stdout } = await this.exec(['rev-parse', '--short', 'HEAD']);
125
-
126
- logger.info('Commit created', { hash: stdout, message });
127
-
128
- return stdout;
129
- }
130
-
131
- /**
132
- * Create new branch
133
- */
134
- async createBranch(name, from = 'HEAD') {
135
- await this.exec(['checkout', '-b', name, from]);
136
- logger.info('Branch created', { name, from });
137
- return name;
138
- }
139
-
140
- /**
141
- * Switch to branch
142
- */
143
- async checkout(branch) {
144
- await this.exec(['checkout', branch]);
145
- logger.info('Checked out branch', { branch });
146
- }
147
-
148
- /**
149
- * Merge branch
150
- */
151
- async mergeBranch(branch, squash = false) {
152
- const args = squash ? ['merge', '--squash', branch] : ['merge', branch];
153
- await this.exec(args);
154
- logger.info('Branch merged', { branch, squash });
155
- }
156
-
157
- /**
158
- * Create tag
159
- */
160
- async tagRelease(version, message = '') {
161
- const args = ['-a', version, '-m', message || `Release ${version}`];
162
- await this.exec(['tag', ...args]);
163
- logger.info('Tag created', { version });
164
- }
165
-
166
- /**
167
- * Push to remote
168
- */
169
- async push(remote = 'origin', branch = null) {
170
- const args = ['push', remote];
171
- if (branch) args.push(branch);
172
- await this.exec(args);
173
- logger.info('Pushed to remote', { remote, branch });
174
- }
175
-
176
- /**
177
- * Pull from remote
178
- */
179
- async pull(remote = 'origin', branch = null) {
180
- const args = ['pull', remote];
181
- if (branch) args.push(branch);
182
- await this.exec(args);
183
- logger.info('Pulled from remote', { remote, branch });
184
- }
185
-
186
- /**
187
- * Get recent commits
188
- */
189
- async getCommits(count = 5) {
190
- const { stdout } = await this.exec(['log', `-n${count}`, '--oneline']);
191
- return stdout.split('\n');
192
- }
193
-
194
- /**
195
- * Get diff between commits/branches
196
- */
197
- async getDiff(from, to = 'HEAD') {
198
- const { stdout } = await this.exec(['diff', `${from}..${to}`, '--stat']);
199
- return stdout;
200
- }
201
- }
202
-
203
- module.exports = GitUtils;
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * EZ Git Utils — Safe git operations with atomic commits
5
+ *
6
+ * Provides:
7
+ * - Atomic commits with validation
8
+ * - Branch creation and management
9
+ * - Safe git operations using execFile
10
+ * - Error handling with recovery suggestions
11
+ *
12
+ * Usage:
13
+ * const GitUtils = require('./git-utils.cjs');
14
+ * const git = new GitUtils(process.cwd());
15
+ * await git.commitAtomic('feat: add feature', ['file1.js']);
16
+ */
17
+
18
+ const { execFile } = require('child_process');
19
+ const { promisify } = require('util');
20
+ const execFileAsync = promisify(execFile);
21
+ const Logger = require('./logger.cjs');
22
+ const logger = new Logger();
23
+ const { BranchExistsError, BranchNotFoundError } = require('./git-errors.cjs');
24
+ const { MergeConflictError } = require('./git-errors.cjs');
25
+
26
+ class GitUtils {
27
+ constructor(cwd) {
28
+ this.cwd = cwd || process.cwd();
29
+ }
30
+
31
+ /**
32
+ * Execute git command safely
33
+ */
34
+ async exec(args, options = {}) {
35
+ const { timeout = 30000 } = options;
36
+
37
+ try {
38
+ const { stdout, stderr } = await execFileAsync('git', args, {
39
+ cwd: this.cwd,
40
+ encoding: 'utf-8',
41
+ timeout,
42
+ maxBuffer: 10 * 1024 * 1024
43
+ });
44
+
45
+ logger.debug('Git command executed', { args, stdout_length: stdout?.length });
46
+
47
+ return { stdout: stdout?.trim(), stderr: stderr?.trim() };
48
+ } catch (err) {
49
+ logger.error('Git command failed', { args, error: err.message });
50
+ throw err;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Check if directory is a git repo
56
+ */
57
+ async isGitRepo() {
58
+ try {
59
+ await this.exec(['rev-parse', '--git-dir']);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Get current branch name
68
+ */
69
+ async getCurrentBranch() {
70
+ const { stdout } = await this.exec(['rev-parse', '--abbrev-ref', 'HEAD']);
71
+ return stdout;
72
+ }
73
+
74
+ /**
75
+ * Get git status (porcelain format)
76
+ */
77
+ async getStatus() {
78
+ const { stdout } = await this.exec(['status', '--porcelain']);
79
+ return stdout.split('\n').filter(line => line.trim());
80
+ }
81
+
82
+ /**
83
+ * Check if there are uncommitted changes
84
+ */
85
+ async hasChanges() {
86
+ const status = await this.getStatus();
87
+ return status.length > 0;
88
+ }
89
+
90
+ /**
91
+ * Stage files for commit
92
+ */
93
+ async add(files) {
94
+ if (!Array.isArray(files)) files = [files];
95
+ await this.exec(['add', ...files]);
96
+ logger.info('Files staged', { files });
97
+ }
98
+
99
+ /**
100
+ * Add all changes
101
+ */
102
+ async addAll() {
103
+ await this.exec(['add', '-A']);
104
+ logger.info('All files staged');
105
+ }
106
+
107
+ /**
108
+ * Create atomic commit with validation
109
+ */
110
+ async commitAtomic(message, files = []) {
111
+ // Stage files if provided
112
+ if (files.length > 0) {
113
+ await this.add(files);
114
+ }
115
+
116
+ // Validate commit message format (conventional commits)
117
+ const conventionalPattern = /^(feat|fix|docs|chore|test|refactor|style|perf)(\([^)]+\))?:\s.+/;
118
+ if (!conventionalPattern.test(message)) {
119
+ logger.warn('Commit message may not follow conventional commits', { message });
120
+ }
121
+
122
+ // Create commit
123
+ await this.exec(['commit', '-m', message]);
124
+
125
+ // Get commit hash
126
+ const { stdout } = await this.exec(['rev-parse', '--short', 'HEAD']);
127
+
128
+ logger.info('Commit created', { hash: stdout, message });
129
+
130
+ return stdout;
131
+ }
132
+
133
+ /**
134
+ * Create new branch
135
+ */
136
+ async createBranch(name, from = 'HEAD') {
137
+ await this.exec(['checkout', '-b', name, from]);
138
+ logger.info('Branch created', { name, from });
139
+ return name;
140
+ }
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
+
258
+ /**
259
+ * Switch to branch
260
+ */
261
+ async checkout(branch) {
262
+ await this.exec(['checkout', branch]);
263
+ logger.info('Checked out branch', { branch });
264
+ }
265
+
266
+ /**
267
+ * Merge branch
268
+ */
269
+ async mergeBranch(branch, squash = false) {
270
+ const args = squash ? ['merge', '--squash', branch] : ['merge', branch];
271
+ await this.exec(args);
272
+ logger.info('Branch merged', { branch, squash });
273
+ }
274
+
275
+ /**
276
+ * Create tag
277
+ */
278
+ async tagRelease(version, message = '') {
279
+ const args = ['-a', version, '-m', message || `Release ${version}`];
280
+ await this.exec(['tag', ...args]);
281
+ logger.info('Tag created', { version });
282
+ }
283
+
284
+ /**
285
+ * Push to remote
286
+ */
287
+ async push(remote = 'origin', branch = null) {
288
+ const args = ['push', remote];
289
+ if (branch) args.push(branch);
290
+ await this.exec(args);
291
+ logger.info('Pushed to remote', { remote, branch });
292
+ }
293
+
294
+ /**
295
+ * Pull from remote
296
+ */
297
+ async pull(remote = 'origin', branch = null) {
298
+ const args = ['pull', remote];
299
+ if (branch) args.push(branch);
300
+ await this.exec(args);
301
+ logger.info('Pulled from remote', { remote, branch });
302
+ }
303
+
304
+ /**
305
+ * Get recent commits
306
+ */
307
+ async getCommits(count = 5) {
308
+ const { stdout } = await this.exec(['log', `-n${count}`, '--oneline']);
309
+ return stdout.split('\n');
310
+ }
311
+
312
+ /**
313
+ * Get diff between commits/branches
314
+ */
315
+ async getDiff(from, to = 'HEAD') {
316
+ const { stdout } = await this.exec(['diff', `${from}..${to}`, '--stat']);
317
+ return stdout;
318
+ }
319
+ }
320
+
321
+ module.exports = GitUtils;