@devtrace-ai/cli 1.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.
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveClaudeSettingsPath = resolveClaudeSettingsPath;
37
+ exports.installClaudeHooks = installClaudeHooks;
38
+ exports.uninstallClaudeHooks = uninstallClaudeHooks;
39
+ exports.countDevtraceEntries = countDevtraceEntries;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ /**
43
+ * Installs/uninstalls the DevTrace hook entries into Claude Code's
44
+ * ~/.claude/settings.json (PostToolUse + SessionStart), so `devtrace
45
+ * claude-hook` runs as a first-party structured-event capture source
46
+ * instead of transcript scraping. See docs/plans/03b Gap 1.
47
+ *
48
+ * Merge, don't clobber: only DevTrace's own hook entries are added/removed;
49
+ * any other hooks or settings keys already present are preserved untouched.
50
+ *
51
+ * Path resolution mirrors src/hooks.ts's git-hook bake-in pattern: an
52
+ * absolute node execPath + absolute script path resolved once at install
53
+ * time, so the hook command never needs PATH or a network/registry hit.
54
+ */
55
+ const POST_TOOL_USE_MATCHER = 'Edit|Write|MultiEdit';
56
+ /** Resolves the absolute invocation command for the DevTrace hook subcommand. */
57
+ function resolveInvocationCommand() {
58
+ const execPath = process.execPath;
59
+ const scriptPath = path.resolve(require.main?.filename ?? process.argv[1]);
60
+ return `"${execPath}" "${scriptPath}" claude-hook`;
61
+ }
62
+ /**
63
+ * Resolves the target settings.json path. Honors CLAUDE_SETTINGS_PATH as a
64
+ * test/override hook (mirrors DEVTRACE_HOME's role for src/config.ts),
65
+ * falling back to the real ~/.claude/settings.json.
66
+ */
67
+ function resolveClaudeSettingsPath() {
68
+ if (process.env.CLAUDE_SETTINGS_PATH) {
69
+ return process.env.CLAUDE_SETTINGS_PATH;
70
+ }
71
+ const homeDir = process.env.HOME || process.env.USERPROFILE || '';
72
+ return path.join(homeDir, '.claude', 'settings.json');
73
+ }
74
+ function loadSettings(settingsPath) {
75
+ if (!fs.existsSync(settingsPath))
76
+ return {};
77
+ try {
78
+ const raw = fs.readFileSync(settingsPath, 'utf8');
79
+ const parsed = JSON.parse(raw);
80
+ return parsed && typeof parsed === 'object' ? parsed : {};
81
+ }
82
+ catch {
83
+ return {};
84
+ }
85
+ }
86
+ function saveSettings(settingsPath, settings) {
87
+ const dir = path.dirname(settingsPath);
88
+ if (!fs.existsSync(dir)) {
89
+ fs.mkdirSync(dir, { recursive: true });
90
+ }
91
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
92
+ }
93
+ /**
94
+ * Inserts (or replaces) a DevTrace-owned hook entry within a given event's
95
+ * matcher blocks, without touching any other tool's blocks or hook entries.
96
+ */
97
+ function upsertHookEntry(blocks, matcher, command) {
98
+ const result = blocks ? blocks.map(b => ({ ...b, hooks: [...b.hooks] })) : [];
99
+ // Strip any pre-existing DevTrace entries first (idempotent reinstall / upgrade path),
100
+ // dropping now-empty matcher blocks that only contained our entry.
101
+ for (let i = result.length - 1; i >= 0; i--) {
102
+ result[i].hooks = result[i].hooks.filter(h => !h.__devtrace__);
103
+ if (result[i].hooks.length === 0 && (result[i].matcher ?? undefined) === matcher) {
104
+ result.splice(i, 1);
105
+ }
106
+ }
107
+ const targetBlockIdx = result.findIndex(b => (b.matcher ?? undefined) === matcher);
108
+ const entry = { type: 'command', command, __devtrace__: true };
109
+ if (targetBlockIdx !== -1) {
110
+ result[targetBlockIdx].hooks.push(entry);
111
+ }
112
+ else {
113
+ const block = matcher !== undefined ? { matcher, hooks: [entry] } : { hooks: [entry] };
114
+ result.push(block);
115
+ }
116
+ return result;
117
+ }
118
+ /** Removes only DevTrace-owned hook entries from a given event's matcher blocks. */
119
+ function removeHookEntries(blocks) {
120
+ if (!blocks)
121
+ return blocks;
122
+ const result = [];
123
+ for (const b of blocks) {
124
+ const hooks = b.hooks.filter(h => !h.__devtrace__);
125
+ if (hooks.length > 0) {
126
+ result.push({ ...b, hooks });
127
+ }
128
+ }
129
+ return result;
130
+ }
131
+ /**
132
+ * Installs DevTrace's PostToolUse (Edit|Write|MultiEdit) and SessionStart
133
+ * hooks into Claude Code's settings.json. Merge-only: preserves any
134
+ * pre-existing unrelated settings keys and other tools' hook entries.
135
+ * Safe to run repeatedly (replaces only DevTrace's own entries).
136
+ */
137
+ function installClaudeHooks() {
138
+ const settingsPath = resolveClaudeSettingsPath();
139
+ const before = loadSettings(settingsPath);
140
+ const command = resolveInvocationCommand();
141
+ const after = { ...before, hooks: { ...(before.hooks ?? {}) } };
142
+ after.hooks.PostToolUse = upsertHookEntry(before.hooks?.PostToolUse, POST_TOOL_USE_MATCHER, command);
143
+ after.hooks.SessionStart = upsertHookEntry(before.hooks?.SessionStart, undefined, command);
144
+ saveSettings(settingsPath, after);
145
+ return { settingsPath, before, after };
146
+ }
147
+ /**
148
+ * Removes only DevTrace's PostToolUse/SessionStart hook entries from
149
+ * settings.json, leaving everything else (other hooks, unrelated settings
150
+ * keys) untouched.
151
+ */
152
+ function uninstallClaudeHooks() {
153
+ const settingsPath = resolveClaudeSettingsPath();
154
+ const before = loadSettings(settingsPath);
155
+ const after = { ...before };
156
+ if (before.hooks) {
157
+ after.hooks = { ...before.hooks };
158
+ after.hooks.PostToolUse = removeHookEntries(before.hooks.PostToolUse);
159
+ after.hooks.SessionStart = removeHookEntries(before.hooks.SessionStart);
160
+ // Drop empty arrays entirely so uninstall doesn't leave clutter like `"PostToolUse": []`.
161
+ if (after.hooks.PostToolUse && after.hooks.PostToolUse.length === 0)
162
+ delete after.hooks.PostToolUse;
163
+ if (after.hooks.SessionStart && after.hooks.SessionStart.length === 0)
164
+ delete after.hooks.SessionStart;
165
+ if (Object.keys(after.hooks).length === 0)
166
+ delete after.hooks;
167
+ }
168
+ saveSettings(settingsPath, after);
169
+ return { settingsPath, before, after };
170
+ }
171
+ /** Counts DevTrace-owned hook entries currently present, for summary printing. */
172
+ function countDevtraceEntries(settings) {
173
+ let count = 0;
174
+ for (const key of ['PostToolUse', 'SessionStart']) {
175
+ const blocks = settings.hooks?.[key];
176
+ if (!blocks)
177
+ continue;
178
+ for (const b of blocks) {
179
+ count += b.hooks.filter(h => h.__devtrace__).length;
180
+ }
181
+ }
182
+ return count;
183
+ }
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CLAUDE_HOOK_LOG_FILENAME = exports.CLAUDE_LEDGER_FILENAME = void 0;
37
+ exports.handleHookEvent = handleHookEvent;
38
+ exports.readStdinSync = readStdinSync;
39
+ exports.runClaudeHookCli = runClaudeHookCli;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ /**
43
+ * First-party Claude Code capture via hooks (tier-2 "instrumented").
44
+ *
45
+ * Design: docs/plans/03b-sota-capture-research-completion.md Gap 1.
46
+ * Claude Code invokes hook commands with stdin JSON. PostToolUse adds
47
+ * tool_name/tool_input/tool_response; no model is present there, so model
48
+ * is captured once per session via SessionStart and joined on session_id
49
+ * downstream (the ledger just records both event kinds; joining happens
50
+ * at commit-correlation time if ever needed).
51
+ *
52
+ * Privacy commitment: this module NEVER writes prompt text, old/new string
53
+ * contents, or transcript paths to disk — only counts and repo-relative
54
+ * paths. See handleHookEvent() below for the exact fields persisted.
55
+ *
56
+ * Reliability commitment: this must never block or break Claude Code. Every
57
+ * public entry point catches all errors, logs them to `.devtrace/hook.log`,
58
+ * and the CLI wrapper (src/index.ts `claude-hook` command) always exits 0.
59
+ */
60
+ exports.CLAUDE_LEDGER_FILENAME = 'claude-ledger.jsonl';
61
+ exports.CLAUDE_HOOK_LOG_FILENAME = 'hook.log';
62
+ const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit']);
63
+ /** Counts added/removed lines from a single string replacement (old -> new). */
64
+ function diffStringLines(oldStr, newStr) {
65
+ const oldLines = oldStr.length > 0 ? oldStr.split('\n').length : 0;
66
+ const newLines = newStr.length > 0 ? newStr.split('\n').length : 0;
67
+ if (oldStr === '') {
68
+ // Pure insertion (e.g. Write creating a new file, or Edit's old_string is empty).
69
+ return { added: newLines, removed: 0 };
70
+ }
71
+ if (newStr === '') {
72
+ return { added: 0, removed: oldLines };
73
+ }
74
+ // We don't have a real diff algorithm here (and don't want one — this is metadata-only,
75
+ // not content analysis). Use a coarse line-delta: net line count change, split into
76
+ // added/removed via the naive assumption that all old lines were replaced by all new lines.
77
+ return { added: newLines, removed: oldLines };
78
+ }
79
+ /**
80
+ * Computes {linesAdded, linesRemoved} for a PostToolUse Edit|Write|MultiEdit event from
81
+ * tool_input, WITHOUT ever retaining the actual string contents.
82
+ */
83
+ function computeLineDeltas(toolName, toolInput) {
84
+ if (toolName === 'MultiEdit' && Array.isArray(toolInput.edits)) {
85
+ let added = 0;
86
+ let removed = 0;
87
+ for (const edit of toolInput.edits) {
88
+ if (!edit || typeof edit !== 'object')
89
+ continue;
90
+ const e = edit;
91
+ const oldStr = typeof e.old_string === 'string' ? e.old_string : '';
92
+ const newStr = typeof e.new_string === 'string' ? e.new_string : '';
93
+ const d = diffStringLines(oldStr, newStr);
94
+ added += d.added;
95
+ removed += d.removed;
96
+ }
97
+ return { added, removed };
98
+ }
99
+ if (toolName === 'Edit') {
100
+ const oldStr = typeof toolInput.old_string === 'string' ? toolInput.old_string : '';
101
+ const newStr = typeof toolInput.new_string === 'string' ? toolInput.new_string : '';
102
+ return diffStringLines(oldStr, newStr);
103
+ }
104
+ if (toolName === 'Write') {
105
+ const content = typeof toolInput.content === 'string' ? toolInput.content : '';
106
+ return { added: content.length > 0 ? content.split('\n').length : 0, removed: 0 };
107
+ }
108
+ return { added: 0, removed: 0 };
109
+ }
110
+ /** Resolves the repo-relative file path from tool_input, given the repo root. */
111
+ function resolveRelativeFilePath(toolInput, repoRoot) {
112
+ const filePath = typeof toolInput.file_path === 'string' ? toolInput.file_path : null;
113
+ if (!filePath)
114
+ return null;
115
+ const rel = path.isAbsolute(filePath) ? path.relative(repoRoot, filePath) : filePath;
116
+ // Never store paths that resolve outside the repo (defensive; shouldn't normally happen).
117
+ if (rel.startsWith('..'))
118
+ return null;
119
+ return rel;
120
+ }
121
+ function appendLedgerLine(repoRoot, event) {
122
+ const devtraceDir = path.join(repoRoot, '.devtrace');
123
+ const ledgerPath = path.join(devtraceDir, exports.CLAUDE_LEDGER_FILENAME);
124
+ fs.appendFileSync(ledgerPath, JSON.stringify(event) + '\n', 'utf8');
125
+ }
126
+ function logHookError(repoRoot, message) {
127
+ try {
128
+ const devtraceDir = path.join(repoRoot, '.devtrace');
129
+ if (!fs.existsSync(devtraceDir))
130
+ return; // not initialized here — nothing to log into
131
+ const logPath = path.join(devtraceDir, exports.CLAUDE_HOOK_LOG_FILENAME);
132
+ const line = `[${new Date().toISOString()}] ${message}\n`;
133
+ fs.appendFileSync(logPath, line, 'utf8');
134
+ }
135
+ catch {
136
+ // Logging must never throw either.
137
+ }
138
+ }
139
+ /**
140
+ * Handles one Claude Code hook invocation (already-parsed JSON payload).
141
+ * Returns true if a ledger line was written, false if the event was a no-op
142
+ * (uninitialized repo, unsupported event, malformed payload). Never throws —
143
+ * all failures are caught internally and logged to .devtrace/hook.log.
144
+ */
145
+ function handleHookEvent(rawPayload) {
146
+ let repoRootForLogging = process.cwd();
147
+ try {
148
+ if (!rawPayload || typeof rawPayload !== 'object')
149
+ return false;
150
+ const payload = rawPayload;
151
+ const cwd = typeof payload.cwd === 'string' && payload.cwd ? payload.cwd : process.cwd();
152
+ repoRootForLogging = cwd;
153
+ const sessionId = typeof payload.session_id === 'string' ? payload.session_id : '';
154
+ const eventName = typeof payload.hook_event_name === 'string' ? payload.hook_event_name : '';
155
+ if (!sessionId || !eventName)
156
+ return false;
157
+ // Only act if DevTrace is initialized in this repo (.devtrace/ exists). Otherwise silent no-op.
158
+ const devtraceDir = path.join(cwd, '.devtrace');
159
+ if (!fs.existsSync(devtraceDir) || !fs.statSync(devtraceDir).isDirectory()) {
160
+ return false;
161
+ }
162
+ if (eventName === 'SessionStart') {
163
+ const model = typeof payload.model === 'string' && payload.model ? payload.model : null;
164
+ appendLedgerLine(cwd, {
165
+ kind: 'session-start',
166
+ ts: new Date().toISOString(),
167
+ sessionId,
168
+ model
169
+ });
170
+ return true;
171
+ }
172
+ if (eventName === 'PostToolUse') {
173
+ const toolName = typeof payload.tool_name === 'string' ? payload.tool_name : '';
174
+ if (!EDIT_TOOL_NAMES.has(toolName))
175
+ return false;
176
+ const toolInput = payload.tool_input && typeof payload.tool_input === 'object'
177
+ ? payload.tool_input
178
+ : {};
179
+ const relPath = resolveRelativeFilePath(toolInput, cwd);
180
+ if (!relPath)
181
+ return false;
182
+ const { added, removed } = computeLineDeltas(toolName, toolInput);
183
+ appendLedgerLine(cwd, {
184
+ kind: 'edit',
185
+ ts: new Date().toISOString(),
186
+ sessionId,
187
+ tool: toolName,
188
+ filePath: relPath,
189
+ linesAdded: added,
190
+ linesRemoved: removed
191
+ });
192
+ return true;
193
+ }
194
+ return false;
195
+ }
196
+ catch (err) {
197
+ logHookError(repoRootForLogging, `handleHookEvent error: ${err.message}`);
198
+ return false;
199
+ }
200
+ }
201
+ /**
202
+ * Reads all stdin synchronously (hook commands run once and exit; Claude Code
203
+ * waits for the process, so a blocking read is correct and simple here).
204
+ */
205
+ function readStdinSync() {
206
+ try {
207
+ return fs.readFileSync(0, 'utf8');
208
+ }
209
+ catch {
210
+ return '';
211
+ }
212
+ }
213
+ /**
214
+ * Entry point for the `devtrace claude-hook` CLI command. Reads stdin JSON,
215
+ * dispatches to handleHookEvent, and NEVER throws — malformed input, missing
216
+ * fields, or an uninitialized repo all resolve to a silent no-op so Claude
217
+ * Code is never blocked or broken by DevTrace.
218
+ */
219
+ function runClaudeHookCli() {
220
+ try {
221
+ const raw = readStdinSync();
222
+ if (!raw || !raw.trim())
223
+ return;
224
+ let parsed;
225
+ try {
226
+ parsed = JSON.parse(raw);
227
+ }
228
+ catch {
229
+ return; // malformed stdin: silent no-op, never crash
230
+ }
231
+ handleHookEvent(parsed);
232
+ }
233
+ catch {
234
+ // Absolute last resort — never let this throw out of the CLI entry point.
235
+ }
236
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CliHelper = void 0;
37
+ const readline = __importStar(require("readline"));
38
+ class CliHelper {
39
+ static rl = null;
40
+ static initInterface() {
41
+ if (!this.rl) {
42
+ this.rl = readline.createInterface({
43
+ input: process.stdin,
44
+ output: process.stdout
45
+ });
46
+ }
47
+ }
48
+ static closeInterface() {
49
+ if (this.rl) {
50
+ this.rl.close();
51
+ this.rl = null;
52
+ }
53
+ }
54
+ /**
55
+ * Prompts the user with a text question.
56
+ */
57
+ static askQuestion(query) {
58
+ this.initInterface();
59
+ return new Promise((resolve) => {
60
+ this.rl.question(query, (answer) => {
61
+ resolve(answer.trim());
62
+ });
63
+ });
64
+ }
65
+ /**
66
+ * Prompts the user with a multiple-choice question.
67
+ */
68
+ static async askChoice(query, options) {
69
+ console.log(`\n${query}`);
70
+ options.forEach((opt, idx) => {
71
+ console.log(` ${idx + 1}: ${opt}`);
72
+ });
73
+ while (true) {
74
+ const answer = await this.askQuestion(`Choice [1-${options.length}]: `);
75
+ const choice = parseInt(answer, 10);
76
+ if (choice >= 1 && choice <= options.length) {
77
+ return choice;
78
+ }
79
+ console.log(`Invalid choice. Please select a number between 1 and ${options.length}.`);
80
+ }
81
+ }
82
+ /**
83
+ * Prompts the user for a Likert rating scale (1-5).
84
+ */
85
+ static async askLikert(query, description) {
86
+ console.log(`\n✏️ ${query}`);
87
+ console.log(` (Scale 1-5: ${description})`);
88
+ while (true) {
89
+ const answer = await this.askQuestion('Score [1-5]: ');
90
+ const val = parseInt(answer, 10);
91
+ if (val >= 1 && val <= 5) {
92
+ return val;
93
+ }
94
+ console.log('Invalid input. Please enter a score from 1 to 5.');
95
+ }
96
+ }
97
+ /**
98
+ * Prompts the user for task complexity.
99
+ */
100
+ static async askComplexity() {
101
+ const choice = await this.askChoice('Rate Task Complexity:', [
102
+ 'Low (simple bugfixes, refactorings, documentation changes)',
103
+ 'Medium (standard features, styling changes, unit test suites)',
104
+ 'High (large architectural adjustments, complex algorithms)'
105
+ ]);
106
+ if (choice === 1)
107
+ return 'low';
108
+ if (choice === 2)
109
+ return 'medium';
110
+ return 'high';
111
+ }
112
+ }
113
+ exports.CliHelper = CliHelper;