agileflow 2.90.7 → 2.92.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 (144) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +818 -0
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate-names.js +3 -3
  22. package/lib/validate.js +116 -52
  23. package/package.json +4 -1
  24. package/scripts/af +34 -0
  25. package/scripts/agent-loop.js +63 -9
  26. package/scripts/agileflow-configure.js +2 -2
  27. package/scripts/agileflow-welcome.js +435 -23
  28. package/scripts/archive-completed-stories.sh +57 -11
  29. package/scripts/claude-tmux.sh +102 -0
  30. package/scripts/damage-control-bash.js +3 -70
  31. package/scripts/damage-control-edit.js +3 -20
  32. package/scripts/damage-control-write.js +3 -20
  33. package/scripts/dependency-check.js +310 -0
  34. package/scripts/get-env.js +11 -4
  35. package/scripts/lib/configure-detect.js +23 -1
  36. package/scripts/lib/configure-features.js +43 -2
  37. package/scripts/lib/context-formatter.js +771 -0
  38. package/scripts/lib/context-loader.js +699 -0
  39. package/scripts/lib/damage-control-utils.js +107 -0
  40. package/scripts/lib/json-utils.sh +162 -0
  41. package/scripts/lib/state-migrator.js +353 -0
  42. package/scripts/lib/story-state-machine.js +437 -0
  43. package/scripts/obtain-context.js +118 -1048
  44. package/scripts/pre-push-check.sh +46 -0
  45. package/scripts/precompact-context.sh +36 -11
  46. package/scripts/query-codebase.js +538 -0
  47. package/scripts/ralph-loop.js +5 -5
  48. package/scripts/session-manager.js +220 -42
  49. package/scripts/spawn-parallel.js +651 -0
  50. package/scripts/tui/blessed/data/watcher.js +180 -0
  51. package/scripts/tui/blessed/index.js +244 -0
  52. package/scripts/tui/blessed/panels/output.js +101 -0
  53. package/scripts/tui/blessed/panels/sessions.js +150 -0
  54. package/scripts/tui/blessed/panels/trace.js +97 -0
  55. package/scripts/tui/blessed/ui/help.js +77 -0
  56. package/scripts/tui/blessed/ui/screen.js +52 -0
  57. package/scripts/tui/blessed/ui/statusbar.js +47 -0
  58. package/scripts/tui/blessed/ui/tabbar.js +99 -0
  59. package/scripts/tui/index.js +38 -30
  60. package/scripts/validators/README.md +143 -0
  61. package/scripts/validators/component-validator.js +239 -0
  62. package/scripts/validators/json-schema-validator.js +186 -0
  63. package/scripts/validators/markdown-validator.js +152 -0
  64. package/scripts/validators/migration-validator.js +129 -0
  65. package/scripts/validators/security-validator.js +380 -0
  66. package/scripts/validators/story-format-validator.js +197 -0
  67. package/scripts/validators/test-result-validator.js +114 -0
  68. package/scripts/validators/workflow-validator.js +247 -0
  69. package/src/core/agents/accessibility.md +6 -0
  70. package/src/core/agents/adr-writer.md +6 -0
  71. package/src/core/agents/analytics.md +6 -0
  72. package/src/core/agents/api.md +6 -0
  73. package/src/core/agents/ci.md +6 -0
  74. package/src/core/agents/codebase-query.md +261 -0
  75. package/src/core/agents/compliance.md +6 -0
  76. package/src/core/agents/configuration-damage-control.md +6 -0
  77. package/src/core/agents/configuration-visual-e2e.md +6 -0
  78. package/src/core/agents/database.md +10 -0
  79. package/src/core/agents/datamigration.md +6 -0
  80. package/src/core/agents/design.md +6 -0
  81. package/src/core/agents/devops.md +6 -0
  82. package/src/core/agents/documentation.md +6 -0
  83. package/src/core/agents/epic-planner.md +6 -0
  84. package/src/core/agents/integrations.md +6 -0
  85. package/src/core/agents/mentor.md +6 -0
  86. package/src/core/agents/mobile.md +6 -0
  87. package/src/core/agents/monitoring.md +6 -0
  88. package/src/core/agents/multi-expert.md +6 -0
  89. package/src/core/agents/performance.md +6 -0
  90. package/src/core/agents/product.md +6 -0
  91. package/src/core/agents/qa.md +6 -0
  92. package/src/core/agents/readme-updater.md +6 -0
  93. package/src/core/agents/refactor.md +6 -0
  94. package/src/core/agents/research.md +6 -0
  95. package/src/core/agents/security.md +6 -0
  96. package/src/core/agents/testing.md +10 -0
  97. package/src/core/agents/ui.md +6 -0
  98. package/src/core/commands/adr.md +114 -0
  99. package/src/core/commands/agent.md +120 -0
  100. package/src/core/commands/assign.md +145 -0
  101. package/src/core/commands/audit.md +401 -0
  102. package/src/core/commands/babysit.md +32 -5
  103. package/src/core/commands/board.md +1 -0
  104. package/src/core/commands/changelog.md +118 -0
  105. package/src/core/commands/configure.md +42 -6
  106. package/src/core/commands/diagnose.md +114 -0
  107. package/src/core/commands/epic.md +205 -1
  108. package/src/core/commands/handoff.md +128 -0
  109. package/src/core/commands/help.md +76 -0
  110. package/src/core/commands/metrics.md +1 -0
  111. package/src/core/commands/pr.md +96 -0
  112. package/src/core/commands/research/analyze.md +1 -0
  113. package/src/core/commands/research/ask.md +2 -0
  114. package/src/core/commands/research/import.md +1 -0
  115. package/src/core/commands/research/list.md +2 -0
  116. package/src/core/commands/research/synthesize.md +584 -0
  117. package/src/core/commands/research/view.md +2 -0
  118. package/src/core/commands/roadmap/analyze.md +400 -0
  119. package/src/core/commands/session/new.md +113 -6
  120. package/src/core/commands/session/spawn.md +197 -0
  121. package/src/core/commands/sprint.md +22 -0
  122. package/src/core/commands/status.md +200 -1
  123. package/src/core/commands/story/list.md +9 -9
  124. package/src/core/commands/story/view.md +1 -0
  125. package/src/core/commands/story.md +143 -4
  126. package/src/core/experts/codebase-query/expertise.yaml +190 -0
  127. package/src/core/experts/codebase-query/question.md +73 -0
  128. package/src/core/experts/codebase-query/self-improve.md +105 -0
  129. package/src/core/templates/agileflow-metadata.json +55 -2
  130. package/src/core/templates/plan-template.md +125 -0
  131. package/src/core/templates/story-lifecycle.md +213 -0
  132. package/src/core/templates/story-template.md +4 -0
  133. package/src/core/templates/tdd-test-template.js +241 -0
  134. package/tools/cli/commands/setup.js +86 -0
  135. package/tools/cli/installers/core/installer.js +94 -0
  136. package/tools/cli/installers/ide/_base-ide.js +20 -11
  137. package/tools/cli/installers/ide/codex.js +29 -47
  138. package/tools/cli/lib/config-manager.js +17 -2
  139. package/tools/cli/lib/content-transformer.js +271 -0
  140. package/tools/cli/lib/error-handler.js +14 -22
  141. package/tools/cli/lib/ide-error-factory.js +421 -0
  142. package/tools/cli/lib/ide-health-monitor.js +364 -0
  143. package/tools/cli/lib/ide-registry.js +114 -1
  144. package/tools/cli/lib/ui.js +14 -25
package/lib/colors.js CHANGED
@@ -11,21 +11,123 @@
11
11
  * - Cyan (#00CED1): 4.6:1 ✓ (meets AA for normal text)
12
12
  * - Brand (#e8683a): 3.8:1 ✓ (meets AA for large text/UI elements)
13
13
  *
14
+ * WCAG AAA Contrast Ratios (high-contrast mode, 7:1+ ratio):
15
+ * - HC Green (#7CFC00): 11.3:1 ✓ (lawn green - meets AAA)
16
+ * - HC Red (#FF6B6B): 5.0:1 → #FF9999 8.1:1 ✓ (light coral - meets AAA)
17
+ * - HC Yellow (#FFFF00): 19.6:1 ✓ (pure yellow - meets AAA)
18
+ * - HC Cyan (#00FFFF): 14.0:1 ✓ (aqua - meets AAA)
19
+ * - HC White (#FFFFFF): 21.0:1 ✓ (pure white - meets AAA)
20
+ *
14
21
  * Note: Standard ANSI colors vary by terminal theme. The above ratios
15
22
  * are for typical dark terminal configurations.
16
23
  */
17
24
 
25
+ // High-contrast mode detection
26
+ let _highContrastMode = null;
27
+
28
+ /**
29
+ * Check if high-contrast mode is enabled.
30
+ * Checks: AGILEFLOW_HIGH_CONTRAST env var, or cached value.
31
+ * @returns {boolean} True if high-contrast mode is enabled
32
+ */
33
+ function isHighContrast() {
34
+ if (_highContrastMode !== null) {
35
+ return _highContrastMode;
36
+ }
37
+ const envValue = process.env.AGILEFLOW_HIGH_CONTRAST;
38
+ return envValue === '1' || envValue === 'true' || envValue === 'yes';
39
+ }
40
+
41
+ /**
42
+ * Enable or disable high-contrast mode programmatically.
43
+ * @param {boolean} enabled - Whether to enable high-contrast mode
44
+ */
45
+ function setHighContrast(enabled) {
46
+ _highContrastMode = enabled;
47
+ }
48
+
49
+ /**
50
+ * Reset high-contrast mode to use environment variable.
51
+ */
52
+ function resetHighContrast() {
53
+ _highContrastMode = null;
54
+ }
55
+
18
56
  /**
19
57
  * Brand color hex value for chalk compatibility.
20
58
  * Use with chalk.hex(BRAND_HEX) in files that use chalk.
21
59
  */
22
60
  const BRAND_HEX = '#e8683a';
23
61
 
62
+ /**
63
+ * WCAG AAA high-contrast color palette (7:1+ contrast ratio).
64
+ * Used when AGILEFLOW_HIGH_CONTRAST=1 or --high-contrast flag.
65
+ */
66
+ const hc = {
67
+ // Reset and modifiers
68
+ reset: '\x1b[0m',
69
+ bold: '\x1b[1m',
70
+ dim: '\x1b[0m', // No dimming in high-contrast (use regular text)
71
+ italic: '\x1b[3m',
72
+ underline: '\x1b[4m',
73
+
74
+ // High-contrast standard colors (bright variants for max visibility)
75
+ red: '\x1b[91m', // Bright red
76
+ green: '\x1b[92m', // Bright green
77
+ yellow: '\x1b[93m', // Bright yellow
78
+ blue: '\x1b[94m', // Bright blue
79
+ magenta: '\x1b[95m', // Bright magenta
80
+ cyan: '\x1b[96m', // Bright cyan
81
+ white: '\x1b[97m', // Bright white
82
+
83
+ // Bright variants (same in high-contrast mode)
84
+ brightBlack: '\x1b[37m', // Use white instead of gray
85
+ brightRed: '\x1b[91m',
86
+ brightGreen: '\x1b[92m',
87
+ brightYellow: '\x1b[93m',
88
+ brightBlue: '\x1b[94m',
89
+ brightMagenta: '\x1b[95m',
90
+ brightCyan: '\x1b[96m',
91
+ brightWhite: '\x1b[97m',
92
+
93
+ // 256-color high-contrast alternatives (all 7:1+ ratio)
94
+ mintGreen: '\x1b[92m', // Bright green
95
+ peach: '\x1b[93m', // Bright yellow
96
+ coral: '\x1b[91m', // Bright red
97
+ lightGreen: '\x1b[92m', // Bright green
98
+ lightYellow: '\x1b[93m', // Bright yellow
99
+ lightPink: '\x1b[91m', // Bright red
100
+ skyBlue: '\x1b[96m', // Bright cyan
101
+ lavender: '\x1b[95m', // Bright magenta
102
+ softGold: '\x1b[93m', // Bright yellow
103
+ teal: '\x1b[96m', // Bright cyan
104
+ slate: '\x1b[97m', // White (instead of gray)
105
+ rose: '\x1b[91m', // Bright red
106
+ amber: '\x1b[93m', // Bright yellow
107
+ powder: '\x1b[96m', // Bright cyan
108
+
109
+ // Brand color - use bright orange/yellow for visibility
110
+ brand: '\x1b[38;2;255;165;0m', // Bright orange (#FFA500 - 8.0:1 ratio)
111
+ orange: '\x1b[38;2;255;165;0m',
112
+
113
+ // Background colors (same as standard)
114
+ bgRed: '\x1b[41m',
115
+ bgGreen: '\x1b[42m',
116
+ bgYellow: '\x1b[43m',
117
+ bgBlue: '\x1b[44m',
118
+
119
+ // Semantic aliases
120
+ success: '\x1b[92m',
121
+ error: '\x1b[91m',
122
+ warning: '\x1b[93m',
123
+ info: '\x1b[96m',
124
+ };
125
+
24
126
  /**
25
127
  * ANSI color codes for terminal output.
26
128
  * Includes standard colors, 256-color palette, and brand colors.
27
129
  */
28
- const c = {
130
+ const cStandard = {
29
131
  // Reset and modifiers
30
132
  reset: '\x1b[0m',
31
133
  bold: '\x1b[1m',
@@ -85,6 +187,36 @@ const c = {
85
187
  info: '\x1b[36m', // Same as cyan
86
188
  };
87
189
 
190
+ /**
191
+ * Get the active color palette based on high-contrast mode.
192
+ * @returns {Object} Color palette object (either cStandard or hc)
193
+ */
194
+ function getColors() {
195
+ return isHighContrast() ? hc : cStandard;
196
+ }
197
+
198
+ // For backwards compatibility, export a Proxy that delegates to the active palette
199
+ const c = new Proxy(
200
+ {},
201
+ {
202
+ get(_, prop) {
203
+ return getColors()[prop];
204
+ },
205
+ has(_, prop) {
206
+ return prop in cStandard;
207
+ },
208
+ ownKeys() {
209
+ return Object.keys(cStandard);
210
+ },
211
+ getOwnPropertyDescriptor(_, prop) {
212
+ if (prop in cStandard) {
213
+ return { enumerable: true, configurable: true, value: getColors()[prop] };
214
+ }
215
+ return undefined;
216
+ },
217
+ }
218
+ );
219
+
88
220
  /**
89
221
  * Box drawing characters for tables and borders.
90
222
  */
@@ -114,18 +246,49 @@ const box = {
114
246
  };
115
247
 
116
248
  /**
117
- * Status indicators with colors.
249
+ * Get status indicators with current color palette.
250
+ * Uses a Proxy to dynamically generate colored indicators.
118
251
  */
119
- const status = {
120
- success: `${c.green}✓${c.reset}`,
121
- warning: `${c.yellow}⚠️${c.reset}`,
122
- error: `${c.red}✗${c.reset}`,
123
- info: `${c.cyan}ℹ${c.reset}`,
124
- pending: `${c.dim}○${c.reset}`,
125
- inProgress: `${c.yellow}◐${c.reset}`,
126
- done: `${c.green}●${c.reset}`,
127
- blocked: `${c.red}◆${c.reset}`,
128
- };
252
+ const status = new Proxy(
253
+ {},
254
+ {
255
+ get(_, prop) {
256
+ const colors = getColors();
257
+ const indicators = {
258
+ success: `${colors.green}✓${colors.reset}`,
259
+ warning: `${colors.yellow}⚠️${colors.reset}`,
260
+ error: `${colors.red}✗${colors.reset}`,
261
+ info: `${colors.cyan}ℹ${colors.reset}`,
262
+ pending: `${isHighContrast() ? colors.white : colors.dim}○${colors.reset}`,
263
+ inProgress: `${colors.yellow}◐${colors.reset}`,
264
+ done: `${colors.green}●${colors.reset}`,
265
+ blocked: `${colors.red}◆${colors.reset}`,
266
+ };
267
+ return indicators[prop];
268
+ },
269
+ has(_, prop) {
270
+ return [
271
+ 'success',
272
+ 'warning',
273
+ 'error',
274
+ 'info',
275
+ 'pending',
276
+ 'inProgress',
277
+ 'done',
278
+ 'blocked',
279
+ ].includes(prop);
280
+ },
281
+ ownKeys() {
282
+ return ['success', 'warning', 'error', 'info', 'pending', 'inProgress', 'done', 'blocked'];
283
+ },
284
+ getOwnPropertyDescriptor(_, prop) {
285
+ if (status.has(_, prop)) {
286
+ return { enumerable: true, configurable: true };
287
+ }
288
+ return undefined;
289
+ },
290
+ }
291
+ );
129
292
 
130
293
  /**
131
294
  * Wrap text with color codes.
@@ -199,9 +362,22 @@ function brand(text) {
199
362
  }
200
363
 
201
364
  module.exports = {
365
+ // Color palettes
202
366
  c,
367
+ cStandard,
368
+ hc,
369
+ getColors,
370
+
371
+ // High-contrast mode control
372
+ isHighContrast,
373
+ setHighContrast,
374
+ resetHighContrast,
375
+
376
+ // UI elements
203
377
  box,
204
378
  status,
379
+
380
+ // Helper functions
205
381
  colorize,
206
382
  dim,
207
383
  bold,
@@ -209,5 +385,7 @@ module.exports = {
209
385
  warning,
210
386
  error,
211
387
  brand,
388
+
389
+ // Constants
212
390
  BRAND_HEX,
213
391
  };
package/lib/consent.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * consent.js
3
+ *
4
+ * GDPR consent handling for AgileFlow (US-0149)
5
+ *
6
+ * Manages privacy consent during setup:
7
+ * - Prompts user to acknowledge privacy policy
8
+ * - Stores consent timestamp in .agileflow/config/consent.json
9
+ * - Supports --accept-privacy flag for CI environments
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const readline = require('readline');
15
+
16
+ /**
17
+ * Consent configuration
18
+ */
19
+ const CONSENT_FILE = '.agileflow/config/consent.json';
20
+ const PRIVACY_POLICY_URL =
21
+ 'https://github.com/projectquestorg/AgileFlow/blob/main/packages/cli/PRIVACY.md';
22
+
23
+ /**
24
+ * Consent status types
25
+ */
26
+ const ConsentStatus = {
27
+ ACCEPTED: 'accepted',
28
+ DECLINED: 'declined',
29
+ PENDING: 'pending',
30
+ };
31
+
32
+ /**
33
+ * Check if consent has been given
34
+ * @returns {{ hasConsent: boolean, consent: Object | null }}
35
+ */
36
+ function checkConsent() {
37
+ const consentPath = path.resolve(process.cwd(), CONSENT_FILE);
38
+
39
+ try {
40
+ if (fs.existsSync(consentPath)) {
41
+ const consent = JSON.parse(fs.readFileSync(consentPath, 'utf8'));
42
+ return {
43
+ hasConsent: consent.status === ConsentStatus.ACCEPTED,
44
+ consent,
45
+ };
46
+ }
47
+ } catch {
48
+ // Ignore errors, treat as no consent
49
+ }
50
+
51
+ return { hasConsent: false, consent: null };
52
+ }
53
+
54
+ /**
55
+ * Record consent
56
+ * @param {string} status - 'accepted' or 'declined'
57
+ * @param {Object} options - Additional options
58
+ * @param {string} options.method - How consent was given ('interactive', 'flag', 'api')
59
+ * @param {string} options.version - Privacy policy version
60
+ * @returns {{ ok: boolean, path: string }}
61
+ */
62
+ function recordConsent(status, options = {}) {
63
+ const { method = 'interactive', version = '1.0.0' } = options;
64
+
65
+ const consentPath = path.resolve(process.cwd(), CONSENT_FILE);
66
+ const consentDir = path.dirname(consentPath);
67
+
68
+ // Ensure directory exists
69
+ if (!fs.existsSync(consentDir)) {
70
+ fs.mkdirSync(consentDir, { recursive: true });
71
+ }
72
+
73
+ const consent = {
74
+ status,
75
+ timestamp: new Date().toISOString(),
76
+ method,
77
+ policy_version: version,
78
+ policy_url: PRIVACY_POLICY_URL,
79
+ };
80
+
81
+ try {
82
+ fs.writeFileSync(consentPath, JSON.stringify(consent, null, 2));
83
+ return { ok: true, path: consentPath };
84
+ } catch (err) {
85
+ return { ok: false, error: err.message };
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Prompt user for consent interactively
91
+ * @param {Object} options - Prompt options
92
+ * @param {WritableStream} options.output - Output stream (default: process.stdout)
93
+ * @param {ReadableStream} options.input - Input stream (default: process.stdin)
94
+ * @returns {Promise<{ accepted: boolean }>}
95
+ */
96
+ async function promptConsent(options = {}) {
97
+ const { output = process.stdout, input = process.stdin } = options;
98
+
99
+ const rl = readline.createInterface({
100
+ input,
101
+ output,
102
+ terminal: false,
103
+ });
104
+
105
+ // Display privacy notice
106
+ const notice = `
107
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
108
+ ┃ Privacy Notice ┃
109
+ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
110
+
111
+ AgileFlow respects your privacy:
112
+
113
+ • All data is stored locally on your machine
114
+ • No telemetry, analytics, or tracking
115
+ • No data is transmitted to external servers
116
+ • You can delete all data at any time
117
+
118
+ For details, see: ${PRIVACY_POLICY_URL}
119
+
120
+ `;
121
+
122
+ output.write(notice);
123
+
124
+ return new Promise(resolve => {
125
+ const question = 'Do you accept the privacy policy? (yes/no): ';
126
+ output.write(question);
127
+
128
+ rl.once('line', answer => {
129
+ rl.close();
130
+ const normalized = answer.toLowerCase().trim();
131
+ const accepted = normalized === 'yes' || normalized === 'y';
132
+ resolve({ accepted });
133
+ });
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Handle consent during setup
139
+ * @param {Object} options - Setup options
140
+ * @param {boolean} options.acceptPrivacy - --accept-privacy flag was passed
141
+ * @param {boolean} options.silent - Silent mode (no prompts)
142
+ * @param {WritableStream} options.output - Output stream
143
+ * @param {ReadableStream} options.input - Input stream
144
+ * @returns {Promise<{ ok: boolean, status: string, skipped: boolean }>}
145
+ */
146
+ async function handleSetupConsent(options = {}) {
147
+ const {
148
+ acceptPrivacy = false,
149
+ silent = false,
150
+ output = process.stdout,
151
+ input = process.stdin,
152
+ } = options;
153
+
154
+ // Check if consent already given
155
+ const { hasConsent, consent } = checkConsent();
156
+ if (hasConsent) {
157
+ return { ok: true, status: 'already_consented', skipped: false, consent };
158
+ }
159
+
160
+ // If --accept-privacy flag provided
161
+ if (acceptPrivacy) {
162
+ const result = recordConsent(ConsentStatus.ACCEPTED, { method: 'flag' });
163
+ return { ok: result.ok, status: 'accepted_via_flag', skipped: false };
164
+ }
165
+
166
+ // If silent mode (CI without flag)
167
+ if (silent) {
168
+ return { ok: false, status: 'consent_required', skipped: true };
169
+ }
170
+
171
+ // Interactive prompt
172
+ const { accepted } = await promptConsent({ output, input });
173
+
174
+ if (accepted) {
175
+ const result = recordConsent(ConsentStatus.ACCEPTED, { method: 'interactive' });
176
+ return { ok: result.ok, status: 'accepted_interactive', skipped: false };
177
+ } else {
178
+ const result = recordConsent(ConsentStatus.DECLINED, { method: 'interactive' });
179
+ return { ok: false, status: 'declined', skipped: false };
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Get consent status for display
185
+ * @returns {{ status: string, timestamp: string | null, method: string | null }}
186
+ */
187
+ function getConsentStatus() {
188
+ const { hasConsent, consent } = checkConsent();
189
+
190
+ if (!consent) {
191
+ return { status: ConsentStatus.PENDING, timestamp: null, method: null };
192
+ }
193
+
194
+ return {
195
+ status: consent.status,
196
+ timestamp: consent.timestamp,
197
+ method: consent.method,
198
+ policyVersion: consent.policy_version,
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Revoke consent (delete consent file)
204
+ * @returns {{ ok: boolean }}
205
+ */
206
+ function revokeConsent() {
207
+ const consentPath = path.resolve(process.cwd(), CONSENT_FILE);
208
+
209
+ try {
210
+ if (fs.existsSync(consentPath)) {
211
+ fs.unlinkSync(consentPath);
212
+ }
213
+ return { ok: true };
214
+ } catch (err) {
215
+ return { ok: false, error: err.message };
216
+ }
217
+ }
218
+
219
+ module.exports = {
220
+ // Constants
221
+ CONSENT_FILE,
222
+ PRIVACY_POLICY_URL,
223
+ ConsentStatus,
224
+
225
+ // Functions
226
+ checkConsent,
227
+ recordConsent,
228
+ promptConsent,
229
+ handleSetupConsent,
230
+ getConsentStatus,
231
+ revokeConsent,
232
+ };