@howlil/ez-agents 3.1.0 → 3.4.2

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 (110) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +288 -718
  3. package/bin/install.js +438 -71
  4. package/commands/ez/auth.md +87 -0
  5. package/commands/ez/join-discord.md +18 -18
  6. package/ez-agents/bin/ez-tools.cjs +120 -2
  7. package/ez-agents/bin/lib/assistant-adapter.cjs +264 -205
  8. package/ez-agents/bin/lib/audit-exec.cjs +26 -9
  9. package/ez-agents/bin/lib/auth.cjs +2 -1
  10. package/ez-agents/bin/lib/circuit-breaker.cjs +118 -118
  11. package/ez-agents/bin/lib/commands.cjs +42 -23
  12. package/ez-agents/bin/lib/config.cjs +190 -183
  13. package/ez-agents/bin/lib/core.cjs +42 -25
  14. package/ez-agents/bin/lib/file-lock.cjs +236 -236
  15. package/ez-agents/bin/lib/frontmatter.cjs +299 -299
  16. package/ez-agents/bin/lib/fs-utils.cjs +153 -153
  17. package/ez-agents/bin/lib/git-utils.cjs +203 -203
  18. package/ez-agents/bin/lib/health-check.cjs +2 -3
  19. package/ez-agents/bin/lib/index.cjs +113 -113
  20. package/ez-agents/bin/lib/init.cjs +757 -710
  21. package/ez-agents/bin/lib/logger.cjs +52 -15
  22. package/ez-agents/bin/lib/milestone.cjs +241 -241
  23. package/ez-agents/bin/lib/model-provider.cjs +241 -146
  24. package/ez-agents/bin/lib/phase.cjs +925 -908
  25. package/ez-agents/bin/lib/planning-write.cjs +107 -0
  26. package/ez-agents/bin/lib/retry.cjs +119 -119
  27. package/ez-agents/bin/lib/roadmap.cjs +306 -305
  28. package/ez-agents/bin/lib/safe-exec.cjs +91 -5
  29. package/ez-agents/bin/lib/safe-path.cjs +130 -130
  30. package/ez-agents/bin/lib/state.cjs +736 -721
  31. package/ez-agents/bin/lib/temp-file.cjs +239 -239
  32. package/ez-agents/bin/lib/template.cjs +223 -222
  33. package/ez-agents/bin/lib/test-file-lock.cjs +112 -112
  34. package/ez-agents/bin/lib/test-graceful.cjs +93 -93
  35. package/ez-agents/bin/lib/test-logger.cjs +60 -60
  36. package/ez-agents/bin/lib/test-safe-exec.cjs +38 -38
  37. package/ez-agents/bin/lib/test-safe-path.cjs +33 -33
  38. package/ez-agents/bin/lib/test-temp-file.cjs +125 -125
  39. package/ez-agents/bin/lib/timeout-exec.cjs +63 -62
  40. package/ez-agents/bin/lib/verify.cjs +69 -26
  41. package/ez-agents/references/checkpoints.md +776 -776
  42. package/ez-agents/references/continuation-format.md +249 -249
  43. package/ez-agents/references/questioning.md +162 -162
  44. package/ez-agents/references/tdd.md +263 -263
  45. package/ez-agents/templates/codebase/concerns.md +310 -310
  46. package/ez-agents/templates/codebase/conventions.md +307 -307
  47. package/ez-agents/templates/codebase/integrations.md +280 -280
  48. package/ez-agents/templates/codebase/stack.md +186 -186
  49. package/ez-agents/templates/codebase/testing.md +480 -480
  50. package/ez-agents/templates/config.json +37 -37
  51. package/ez-agents/templates/continue-here.md +78 -78
  52. package/ez-agents/templates/milestone-archive.md +123 -123
  53. package/ez-agents/templates/milestone.md +115 -115
  54. package/ez-agents/templates/requirements.md +231 -231
  55. package/ez-agents/templates/research-project/ARCHITECTURE.md +204 -204
  56. package/ez-agents/templates/research-project/FEATURES.md +147 -147
  57. package/ez-agents/templates/research-project/PITFALLS.md +200 -200
  58. package/ez-agents/templates/research-project/STACK.md +120 -120
  59. package/ez-agents/templates/research-project/SUMMARY.md +170 -170
  60. package/ez-agents/templates/retrospective.md +54 -54
  61. package/ez-agents/templates/roadmap.md +202 -202
  62. package/ez-agents/templates/summary-minimal.md +41 -41
  63. package/ez-agents/templates/summary-standard.md +48 -48
  64. package/ez-agents/templates/summary.md +248 -248
  65. package/ez-agents/templates/user-setup.md +311 -311
  66. package/ez-agents/templates/verification-report.md +322 -322
  67. package/ez-agents/workflows/add-phase.md +112 -112
  68. package/ez-agents/workflows/add-tests.md +351 -351
  69. package/ez-agents/workflows/add-todo.md +158 -158
  70. package/ez-agents/workflows/audit-milestone.md +332 -332
  71. package/ez-agents/workflows/autonomous.md +743 -743
  72. package/ez-agents/workflows/check-todos.md +177 -177
  73. package/ez-agents/workflows/cleanup.md +152 -152
  74. package/ez-agents/workflows/complete-milestone.md +766 -766
  75. package/ez-agents/workflows/diagnose-issues.md +219 -219
  76. package/ez-agents/workflows/discovery-phase.md +289 -289
  77. package/ez-agents/workflows/discuss-phase.md +762 -762
  78. package/ez-agents/workflows/execute-phase.md +468 -468
  79. package/ez-agents/workflows/execute-plan.md +483 -483
  80. package/ez-agents/workflows/health.md +159 -159
  81. package/ez-agents/workflows/help.md +492 -492
  82. package/ez-agents/workflows/insert-phase.md +130 -130
  83. package/ez-agents/workflows/list-phase-assumptions.md +178 -178
  84. package/ez-agents/workflows/map-codebase.md +316 -316
  85. package/ez-agents/workflows/new-milestone.md +384 -384
  86. package/ez-agents/workflows/new-project.md +1113 -1111
  87. package/ez-agents/workflows/node-repair.md +92 -92
  88. package/ez-agents/workflows/pause-work.md +122 -122
  89. package/ez-agents/workflows/plan-milestone-gaps.md +274 -274
  90. package/ez-agents/workflows/plan-phase.md +651 -651
  91. package/ez-agents/workflows/progress.md +382 -382
  92. package/ez-agents/workflows/quick.md +610 -610
  93. package/ez-agents/workflows/remove-phase.md +155 -155
  94. package/ez-agents/workflows/research-phase.md +74 -74
  95. package/ez-agents/workflows/resume-project.md +307 -307
  96. package/ez-agents/workflows/set-profile.md +81 -81
  97. package/ez-agents/workflows/settings.md +242 -242
  98. package/ez-agents/workflows/stats.md +57 -57
  99. package/ez-agents/workflows/transition.md +544 -544
  100. package/ez-agents/workflows/ui-phase.md +290 -290
  101. package/ez-agents/workflows/ui-review.md +157 -157
  102. package/ez-agents/workflows/update.md +320 -320
  103. package/ez-agents/workflows/validate-phase.md +167 -167
  104. package/ez-agents/workflows/verify-phase.md +243 -243
  105. package/ez-agents/workflows/verify-work.md +584 -584
  106. package/package.json +2 -3
  107. package/scripts/build-hooks.js +43 -43
  108. package/scripts/fix-qwen-installation.js +144 -0
  109. package/scripts/run-tests.cjs +29 -29
  110. package/README.zh-CN.md +0 -702
@@ -1,183 +1,190 @@
1
- /**
2
- * Config — Planning config CRUD operations
3
- */
4
-
5
- const fs = require('fs');
6
- const path = require('path');
7
- const { output, error } = require('./core.cjs');
8
-
9
- const VALID_CONFIG_KEYS = new Set([
10
- 'mode', 'granularity', 'parallelization', 'commit_docs', 'model_profile',
11
- 'search_gitignored', 'brave_search',
12
- 'workflow.research', 'workflow.plan_check', 'workflow.verifier',
13
- 'workflow.nyquist_validation', 'workflow.ui_phase', 'workflow.ui_safety_gate',
14
- 'workflow._auto_chain_active',
15
- 'git.branching_strategy', 'git.phase_branch_template', 'git.milestone_branch_template',
16
- 'planning.commit_docs', 'planning.search_gitignored',
17
- ]);
18
-
19
- function cmdConfigEnsureSection(cwd, raw) {
20
- const configPath = path.join(cwd, '.planning', 'config.json');
21
- const planningDir = path.join(cwd, '.planning');
22
-
23
- // Ensure .planning directory exists
24
- try {
25
- if (!fs.existsSync(planningDir)) {
26
- fs.mkdirSync(planningDir, { recursive: true });
27
- }
28
- } catch (err) {
29
- error('Failed to create .planning directory: ' + err.message);
30
- }
31
-
32
- // Check if config already exists
33
- if (fs.existsSync(configPath)) {
34
- const result = { created: false, reason: 'already_exists' };
35
- output(result, raw, 'exists');
36
- return;
37
- }
38
-
39
- // Detect Brave Search API key availability
40
- const homedir = require('os').homedir();
41
- const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
42
- const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
43
-
44
- // Load user-level defaults from ~/.gsd/defaults.json if available
45
- const globalDefaultsPath = path.join(homedir, '.gsd', 'defaults.json');
46
- let userDefaults = {};
47
- try {
48
- if (fs.existsSync(globalDefaultsPath)) {
49
- userDefaults = JSON.parse(fs.readFileSync(globalDefaultsPath, 'utf-8'));
50
- // Migrate deprecated "depth" key to "granularity"
51
- if ('depth' in userDefaults && !('granularity' in userDefaults)) {
52
- const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
53
- userDefaults.granularity = depthToGranularity[userDefaults.depth] || userDefaults.depth;
54
- delete userDefaults.depth;
55
- try { fs.writeFileSync(globalDefaultsPath, JSON.stringify(userDefaults, null, 2), 'utf-8'); } catch {}
56
- }
57
- }
58
- } catch (err) {
59
- // Ignore malformed global defaults, fall back to hardcoded
60
- }
61
-
62
- // Create default config (user-level defaults override hardcoded defaults)
63
- const hardcoded = {
64
- model_profile: 'balanced',
65
- commit_docs: true,
66
- search_gitignored: false,
67
- branching_strategy: 'none',
68
- phase_branch_template: 'gsd/phase-{phase}-{slug}',
69
- milestone_branch_template: 'gsd/{milestone}-{slug}',
70
- workflow: {
71
- research: true,
72
- plan_check: true,
73
- verifier: true,
74
- nyquist_validation: true,
75
- },
76
- parallelization: true,
77
- brave_search: hasBraveSearch,
78
- };
79
- const defaults = {
80
- ...hardcoded,
81
- ...userDefaults,
82
- workflow: { ...hardcoded.workflow, ...(userDefaults.workflow || {}) },
83
- };
84
-
85
- try {
86
- fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), 'utf-8');
87
- const result = { created: true, path: '.planning/config.json' };
88
- output(result, raw, 'created');
89
- } catch (err) {
90
- error('Failed to create config.json: ' + err.message);
91
- }
92
- }
93
-
94
- function cmdConfigSet(cwd, keyPath, value, raw) {
95
- const configPath = path.join(cwd, '.planning', 'config.json');
96
-
97
- if (!keyPath) {
98
- error('Usage: config-set <key.path> <value>');
99
- }
100
-
101
- if (!VALID_CONFIG_KEYS.has(keyPath)) {
102
- error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}`);
103
- }
104
-
105
- // Parse value (handle booleans and numbers)
106
- let parsedValue = value;
107
- if (value === 'true') parsedValue = true;
108
- else if (value === 'false') parsedValue = false;
109
- else if (!isNaN(value) && value !== '') parsedValue = Number(value);
110
-
111
- // Load existing config or start with empty object
112
- let config = {};
113
- try {
114
- if (fs.existsSync(configPath)) {
115
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
116
- }
117
- } catch (err) {
118
- error('Failed to read config.json: ' + err.message);
119
- }
120
-
121
- // Set nested value using dot notation (e.g., "workflow.research")
122
- const keys = keyPath.split('.');
123
- let current = config;
124
- for (let i = 0; i < keys.length - 1; i++) {
125
- const key = keys[i];
126
- if (current[key] === undefined || typeof current[key] !== 'object') {
127
- current[key] = {};
128
- }
129
- current = current[key];
130
- }
131
- current[keys[keys.length - 1]] = parsedValue;
132
-
133
- // Write back
134
- try {
135
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
136
- const result = { updated: true, key: keyPath, value: parsedValue };
137
- output(result, raw, `${keyPath}=${parsedValue}`);
138
- } catch (err) {
139
- error('Failed to write config.json: ' + err.message);
140
- }
141
- }
142
-
143
- function cmdConfigGet(cwd, keyPath, raw) {
144
- const configPath = path.join(cwd, '.planning', 'config.json');
145
-
146
- if (!keyPath) {
147
- error('Usage: config-get <key.path>');
148
- }
149
-
150
- let config = {};
151
- try {
152
- if (fs.existsSync(configPath)) {
153
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
154
- } else {
155
- error('No config.json found at ' + configPath);
156
- }
157
- } catch (err) {
158
- if (err.message.startsWith('No config.json')) throw err;
159
- error('Failed to read config.json: ' + err.message);
160
- }
161
-
162
- // Traverse dot-notation path (e.g., "workflow.auto_advance")
163
- const keys = keyPath.split('.');
164
- let current = config;
165
- for (const key of keys) {
166
- if (current === undefined || current === null || typeof current !== 'object') {
167
- error(`Key not found: ${keyPath}`);
168
- }
169
- current = current[key];
170
- }
171
-
172
- if (current === undefined) {
173
- error(`Key not found: ${keyPath}`);
174
- }
175
-
176
- output(current, raw, String(current));
177
- }
178
-
179
- module.exports = {
180
- cmdConfigEnsureSection,
181
- cmdConfigSet,
182
- cmdConfigGet,
183
- };
1
+ /**
2
+ * Config — Planning config CRUD operations
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { output, error } = require('./core.cjs');
8
+ const { safePlanningWriteSync } = require('./planning-write.cjs');
9
+
10
+ const VALID_CONFIG_KEYS = new Set([
11
+ 'mode', 'granularity', 'parallelization', 'commit_docs', 'model_profile',
12
+ 'search_gitignored', 'brave_search',
13
+ 'workflow.research', 'workflow.plan_check', 'workflow.verifier',
14
+ 'workflow.nyquist_validation', 'workflow.ui_phase', 'workflow.ui_safety_gate',
15
+ 'workflow._auto_chain_active',
16
+ 'git.branching_strategy', 'git.phase_branch_template', 'git.milestone_branch_template',
17
+ 'planning.commit_docs', 'planning.search_gitignored',
18
+ ]);
19
+
20
+ function cmdConfigEnsureSection(cwd, raw) {
21
+ const configPath = path.join(cwd, '.planning', 'config.json');
22
+ const planningDir = path.join(cwd, '.planning');
23
+
24
+ // Ensure .planning directory exists
25
+ try {
26
+ if (!fs.existsSync(planningDir)) {
27
+ fs.mkdirSync(planningDir, { recursive: true });
28
+ }
29
+ } catch (err) {
30
+ error('Failed to create .planning directory: ' + err.message);
31
+ }
32
+
33
+ // Check if config already exists
34
+ if (fs.existsSync(configPath)) {
35
+ const result = { created: false, reason: 'already_exists' };
36
+ output(result, raw, 'exists');
37
+ return;
38
+ }
39
+
40
+ // Detect Brave Search API key availability (prefer ~/.ez)
41
+ const homedir = require('os').homedir();
42
+ const braveKeyCandidates = [
43
+ path.join(homedir, '.ez', 'brave_api_key'),
44
+ ];
45
+ const hasBraveSearch = !!(process.env.BRAVE_API_KEY || braveKeyCandidates.some(p => fs.existsSync(p)));
46
+
47
+ // Load user-level defaults from ~/.ez/defaults.json
48
+ const defaultsCandidates = [
49
+ path.join(homedir, '.ez', 'defaults.json'),
50
+ ];
51
+ const existingDefaultsPath = defaultsCandidates.find(p => fs.existsSync(p));
52
+ const globalDefaultsPath = existingDefaultsPath || defaultsCandidates[0];
53
+ let userDefaults = {};
54
+ try {
55
+ if (existingDefaultsPath) {
56
+ userDefaults = JSON.parse(fs.readFileSync(globalDefaultsPath, 'utf-8'));
57
+ // Migrate deprecated "depth" key to "granularity"
58
+ if ('depth' in userDefaults && !('granularity' in userDefaults)) {
59
+ const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
60
+ userDefaults.granularity = depthToGranularity[userDefaults.depth] || userDefaults.depth;
61
+ delete userDefaults.depth;
62
+ try { fs.writeFileSync(globalDefaultsPath, JSON.stringify(userDefaults, null, 2), 'utf-8'); } catch {}
63
+ }
64
+ }
65
+ } catch (err) {
66
+ // Ignore malformed global defaults, fall back to hardcoded
67
+ }
68
+
69
+ // Create default config (user-level defaults override hardcoded defaults)
70
+ const hardcoded = {
71
+ model_profile: 'balanced',
72
+ commit_docs: true,
73
+ search_gitignored: false,
74
+ branching_strategy: 'none',
75
+ phase_branch_template: 'ez/phase-{phase}-{slug}',
76
+ milestone_branch_template: 'ez/{milestone}-{slug}',
77
+ workflow: {
78
+ research: true,
79
+ plan_check: true,
80
+ verifier: true,
81
+ nyquist_validation: true,
82
+ },
83
+ parallelization: true,
84
+ brave_search: hasBraveSearch,
85
+ };
86
+ const defaults = {
87
+ ...hardcoded,
88
+ ...userDefaults,
89
+ workflow: { ...hardcoded.workflow, ...(userDefaults.workflow || {}) },
90
+ };
91
+
92
+ try {
93
+ safePlanningWriteSync(configPath, JSON.stringify(defaults, null, 2));
94
+ const result = { created: true, path: '.planning/config.json' };
95
+ output(result, raw, 'created');
96
+ } catch (err) {
97
+ error('Failed to create config.json: ' + err.message);
98
+ }
99
+ }
100
+
101
+ function cmdConfigSet(cwd, keyPath, value, raw) {
102
+ const configPath = path.join(cwd, '.planning', 'config.json');
103
+
104
+ if (!keyPath) {
105
+ error('Usage: config-set <key.path> <value>');
106
+ }
107
+
108
+ if (!VALID_CONFIG_KEYS.has(keyPath)) {
109
+ error(`Unknown config key: "${keyPath}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}`);
110
+ }
111
+
112
+ // Parse value (handle booleans and numbers)
113
+ let parsedValue = value;
114
+ if (value === 'true') parsedValue = true;
115
+ else if (value === 'false') parsedValue = false;
116
+ else if (!isNaN(value) && value !== '') parsedValue = Number(value);
117
+
118
+ // Load existing config or start with empty object
119
+ let config = {};
120
+ try {
121
+ if (fs.existsSync(configPath)) {
122
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
123
+ }
124
+ } catch (err) {
125
+ error('Failed to read config.json: ' + err.message);
126
+ }
127
+
128
+ // Set nested value using dot notation (e.g., "workflow.research")
129
+ const keys = keyPath.split('.');
130
+ let current = config;
131
+ for (let i = 0; i < keys.length - 1; i++) {
132
+ const key = keys[i];
133
+ if (current[key] === undefined || typeof current[key] !== 'object') {
134
+ current[key] = {};
135
+ }
136
+ current = current[key];
137
+ }
138
+ current[keys[keys.length - 1]] = parsedValue;
139
+
140
+ // Write back
141
+ try {
142
+ safePlanningWriteSync(configPath, JSON.stringify(config, null, 2));
143
+ const result = { updated: true, key: keyPath, value: parsedValue };
144
+ output(result, raw, `${keyPath}=${parsedValue}`);
145
+ } catch (err) {
146
+ error('Failed to write config.json: ' + err.message);
147
+ }
148
+ }
149
+
150
+ function cmdConfigGet(cwd, keyPath, raw) {
151
+ const configPath = path.join(cwd, '.planning', 'config.json');
152
+
153
+ if (!keyPath) {
154
+ error('Usage: config-get <key.path>');
155
+ }
156
+
157
+ let config = {};
158
+ try {
159
+ if (fs.existsSync(configPath)) {
160
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
161
+ } else {
162
+ error('No config.json found at ' + configPath);
163
+ }
164
+ } catch (err) {
165
+ if (err.message.startsWith('No config.json')) throw err;
166
+ error('Failed to read config.json: ' + err.message);
167
+ }
168
+
169
+ // Traverse dot-notation path (e.g., "workflow.auto_advance")
170
+ const keys = keyPath.split('.');
171
+ let current = config;
172
+ for (const key of keys) {
173
+ if (current === undefined || current === null || typeof current !== 'object') {
174
+ error(`Key not found: ${keyPath}`);
175
+ }
176
+ current = current[key];
177
+ }
178
+
179
+ if (current === undefined) {
180
+ error(`Key not found: ${keyPath}`);
181
+ }
182
+
183
+ output(current, raw, String(current));
184
+ }
185
+
186
+ module.exports = {
187
+ cmdConfigEnsureSection,
188
+ cmdConfigSet,
189
+ cmdConfigGet,
190
+ };
@@ -4,7 +4,8 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { execSync } = require('child_process');
7
+ const { safeExec, auditExec } = require('./safe-exec.cjs');
8
+ const { defaultLogger: logger } = require('./logger.cjs');
8
9
 
9
10
  // ─── Path helpers ────────────────────────────────────────────────────────────
10
11
 
@@ -63,7 +64,8 @@ function error(message) {
63
64
  function safeReadFile(filePath) {
64
65
  try {
65
66
  return fs.readFileSync(filePath, 'utf-8');
66
- } catch {
67
+ } catch (err) {
68
+ logger.warn('safeReadFile failed', { filePath, error: err.message });
67
69
  return null;
68
70
  }
69
71
  }
@@ -75,8 +77,8 @@ function loadConfig(cwd) {
75
77
  commit_docs: true,
76
78
  search_gitignored: false,
77
79
  branching_strategy: 'none',
78
- phase_branch_template: 'gsd/phase-{phase}-{slug}',
79
- milestone_branch_template: 'gsd/{milestone}-{slug}',
80
+ phase_branch_template: 'ez/phase-{phase}-{slug}',
81
+ milestone_branch_template: 'ez/{milestone}-{slug}',
80
82
  research: true,
81
83
  plan_checker: true,
82
84
  verifier: true,
@@ -94,7 +96,11 @@ function loadConfig(cwd) {
94
96
  const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
95
97
  parsed.granularity = depthToGranularity[parsed.depth] || parsed.depth;
96
98
  delete parsed.depth;
97
- try { fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8'); } catch {}
99
+ try {
100
+ fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8');
101
+ } catch (err) {
102
+ logger.warn('Failed to persist migrated config depth->granularity', { configPath, error: err.message });
103
+ }
98
104
  }
99
105
 
100
106
  const get = (key, nested) => {
@@ -127,42 +133,43 @@ function loadConfig(cwd) {
127
133
  brave_search: get('brave_search') ?? defaults.brave_search,
128
134
  model_overrides: parsed.model_overrides || null,
129
135
  };
130
- } catch {
136
+ } catch (err) {
137
+ logger.warn('Failed to load config, using defaults', { configPath, error: err.message });
131
138
  return defaults;
132
139
  }
133
140
  }
134
141
 
135
142
  // ─── Git utilities ────────────────────────────────────────────────────────────
136
143
 
137
- function isGitIgnored(cwd, targetPath) {
144
+ async function isGitIgnored(cwd, targetPath) {
138
145
  try {
139
146
  // --no-index checks .gitignore rules regardless of whether the file is tracked.
140
147
  // Without it, git check-ignore returns "not ignored" for tracked files even when
141
148
  // .gitignore explicitly lists them — a common source of confusion when .planning/
142
149
  // was committed before being added to .gitignore.
143
- execSync('git check-ignore -q --no-index -- ' + targetPath.replace(/[^a-zA-Z0-9._\-/]/g, ''), {
150
+ const safePath = targetPath.replace(/[^a-zA-Z0-9._\-/]/g, '');
151
+ await auditExec('git', ['check-ignore', '-q', '--no-index', '--', safePath], {
144
152
  cwd,
145
- stdio: 'pipe',
153
+ context: 'isGitIgnored',
154
+ timeout: 5000
146
155
  });
147
156
  return true;
148
- } catch {
157
+ } catch (err) {
158
+ logger.warn('git check-ignore failed, assuming not ignored', { targetPath, error: err.message });
149
159
  return false;
150
160
  }
151
161
  }
152
162
 
153
- function execGit(cwd, args) {
163
+ async function execGit(cwd, args) {
154
164
  try {
155
- const escaped = args.map(a => {
156
- if (/^[a-zA-Z0-9._\-/=:@]+$/.test(a)) return a;
157
- return "'" + a.replace(/'/g, "'\\''") + "'";
158
- });
159
- const stdout = execSync('git ' + escaped.join(' '), {
165
+ const stdout = await auditExec('git', args, {
160
166
  cwd,
161
- stdio: 'pipe',
162
- encoding: 'utf-8',
167
+ context: 'execGit',
168
+ timeout: 30000
163
169
  });
164
170
  return { exitCode: 0, stdout: stdout.trim(), stderr: '' };
165
171
  } catch (err) {
172
+ logger.warn('execGit failed', { args, error: err.message });
166
173
  return {
167
174
  exitCode: err.status ?? 1,
168
175
  stdout: (err.stdout ?? '').toString().trim(),
@@ -254,7 +261,8 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
254
261
  has_context: hasContext,
255
262
  has_verification: hasVerification,
256
263
  };
257
- } catch {
264
+ } catch (err) {
265
+ logger.warn('Failed to search phase directory', { baseDir, normalized, error: err.message });
258
266
  return null;
259
267
  }
260
268
  }
@@ -291,7 +299,9 @@ function findPhaseInternal(cwd, phase) {
291
299
  return result;
292
300
  }
293
301
  }
294
- } catch {}
302
+ } catch (err) {
303
+ logger.warn('Failed while searching archived milestone phases', { milestonesDir, error: err.message });
304
+ }
295
305
 
296
306
  return null;
297
307
  }
@@ -326,7 +336,9 @@ function getArchivedPhaseDirs(cwd) {
326
336
  });
327
337
  }
328
338
  }
329
- } catch {}
339
+ } catch (err) {
340
+ logger.warn('Failed to enumerate archived phase directories', { milestonesDir, error: err.message });
341
+ }
330
342
 
331
343
  return results;
332
344
  }
@@ -362,7 +374,8 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
362
374
  goal,
363
375
  section,
364
376
  };
365
- } catch {
377
+ } catch (err) {
378
+ logger.warn('Failed to read roadmap phase metadata', { roadmapPath, phaseNum, error: err.message });
366
379
  return null;
367
380
  }
368
381
  }
@@ -391,7 +404,8 @@ function pathExistsInternal(cwd, targetPath) {
391
404
  try {
392
405
  fs.statSync(fullPath);
393
406
  return true;
394
- } catch {
407
+ } catch (err) {
408
+ logger.warn('Path existence check failed', { fullPath, error: err.message });
395
409
  return false;
396
410
  }
397
411
  }
@@ -431,7 +445,8 @@ function getMilestoneInfo(cwd) {
431
445
  version: versionMatch ? versionMatch[0] : 'v1.0',
432
446
  name: 'milestone',
433
447
  };
434
- } catch {
448
+ } catch (err) {
449
+ logger.warn('Failed to load milestone info, using fallback', { error: err.message });
435
450
  return { version: 'v1.0', name: 'milestone' };
436
451
  }
437
452
  }
@@ -450,7 +465,9 @@ function getMilestonePhaseFilter(cwd) {
450
465
  while ((m = phasePattern.exec(roadmap)) !== null) {
451
466
  milestonePhaseNums.add(m[1]);
452
467
  }
453
- } catch {}
468
+ } catch (err) {
469
+ logger.warn('Failed to parse milestone phases from roadmap', { error: err.message });
470
+ }
454
471
 
455
472
  if (milestonePhaseNums.size === 0) {
456
473
  const passAll = () => true;