driftdetect 0.2.2 → 0.3.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 (82) hide show
  1. package/dist/bin/drift.js +15 -1
  2. package/dist/bin/drift.js.map +1 -1
  3. package/dist/commands/boundaries.d.ts.map +1 -0
  4. package/dist/commands/boundaries.js +602 -0
  5. package/dist/commands/boundaries.js.map +1 -0
  6. package/dist/commands/dna/export.d.ts.map +1 -0
  7. package/dist/commands/dna/export.js +91 -0
  8. package/dist/commands/dna/export.js.map +1 -0
  9. package/dist/commands/dna/gene.d.ts.map +1 -0
  10. package/dist/commands/dna/gene.js +96 -0
  11. package/dist/commands/dna/gene.js.map +1 -0
  12. package/dist/commands/dna/index.d.ts.map +1 -0
  13. package/dist/commands/dna/index.js +25 -0
  14. package/dist/commands/dna/index.js.map +1 -0
  15. package/dist/commands/dna/mutations.d.ts.map +1 -0
  16. package/dist/commands/dna/mutations.js +111 -0
  17. package/dist/commands/dna/mutations.js.map +1 -0
  18. package/dist/commands/dna/playbook.d.ts.map +1 -0
  19. package/dist/commands/dna/playbook.js +67 -0
  20. package/dist/commands/dna/playbook.js.map +1 -0
  21. package/dist/commands/dna/scan.d.ts.map +1 -0
  22. package/dist/commands/dna/scan.js +117 -0
  23. package/dist/commands/dna/scan.js.map +1 -0
  24. package/dist/commands/dna/status.d.ts.map +1 -0
  25. package/dist/commands/dna/status.js +106 -0
  26. package/dist/commands/dna/status.js.map +1 -0
  27. package/dist/commands/index.d.ts.map +1 -1
  28. package/dist/commands/index.js +4 -0
  29. package/dist/commands/index.js.map +1 -1
  30. package/dist/commands/parser.d.ts.map +1 -0
  31. package/dist/commands/parser.js +521 -0
  32. package/dist/commands/parser.js.map +1 -0
  33. package/dist/commands/scan.d.ts.map +1 -1
  34. package/dist/commands/scan.js +118 -3
  35. package/dist/commands/scan.js.map +1 -1
  36. package/dist/commands/trends.d.ts.map +1 -0
  37. package/dist/commands/trends.js +115 -0
  38. package/dist/commands/trends.js.map +1 -0
  39. package/dist/commands/watch.js +2 -2
  40. package/dist/commands/watch.js.map +1 -1
  41. package/dist/services/boundary-scanner.d.ts.map +1 -0
  42. package/dist/services/boundary-scanner.js +374 -0
  43. package/dist/services/boundary-scanner.js.map +1 -0
  44. package/dist/services/contract-scanner.d.ts.map +1 -1
  45. package/dist/services/contract-scanner.js +49 -1
  46. package/dist/services/contract-scanner.js.map +1 -1
  47. package/dist/services/scanner-service.d.ts.map +1 -1
  48. package/dist/services/scanner-service.js +32 -1
  49. package/dist/services/scanner-service.js.map +1 -1
  50. package/package.json +6 -6
  51. package/dist/bin/drift.d.ts +0 -11
  52. package/dist/commands/approve.d.ts +0 -18
  53. package/dist/commands/check.d.ts +0 -39
  54. package/dist/commands/dashboard.d.ts +0 -16
  55. package/dist/commands/export.d.ts +0 -16
  56. package/dist/commands/files.d.ts +0 -15
  57. package/dist/commands/ignore.d.ts +0 -18
  58. package/dist/commands/index.d.ts +0 -18
  59. package/dist/commands/init.d.ts +0 -19
  60. package/dist/commands/report.d.ts +0 -20
  61. package/dist/commands/scan.d.ts +0 -29
  62. package/dist/commands/status.d.ts +0 -18
  63. package/dist/commands/watch.d.ts +0 -13
  64. package/dist/commands/where.d.ts +0 -15
  65. package/dist/git/hooks.d.ts +0 -108
  66. package/dist/git/index.d.ts +0 -6
  67. package/dist/git/staged-files.d.ts +0 -41
  68. package/dist/index.d.ts +0 -17
  69. package/dist/reporters/github-reporter.d.ts +0 -13
  70. package/dist/reporters/gitlab-reporter.d.ts +0 -16
  71. package/dist/reporters/index.d.ts +0 -9
  72. package/dist/reporters/json-reporter.d.ts +0 -13
  73. package/dist/reporters/text-reporter.d.ts +0 -13
  74. package/dist/reporters/types.d.ts +0 -42
  75. package/dist/services/contract-scanner.d.ts +0 -35
  76. package/dist/services/scanner-service.d.ts +0 -166
  77. package/dist/types/index.d.ts +0 -24
  78. package/dist/ui/index.d.ts +0 -11
  79. package/dist/ui/progress.d.ts +0 -115
  80. package/dist/ui/prompts.d.ts +0 -91
  81. package/dist/ui/spinner.d.ts +0 -109
  82. package/dist/ui/table.d.ts +0 -118
package/dist/bin/drift.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { Command } from 'commander';
11
11
  import { VERSION } from '../index.js';
12
- import { initCommand, scanCommand, checkCommand, statusCommand, approveCommand, ignoreCommand, reportCommand, exportCommand, whereCommand, filesCommand, watchCommand, dashboardCommand, } from '../commands/index.js';
12
+ import { initCommand, scanCommand, checkCommand, statusCommand, approveCommand, ignoreCommand, reportCommand, exportCommand, whereCommand, filesCommand, watchCommand, dashboardCommand, trendsCommand, parserCommand, dnaCommand, boundariesCommand, } from '../commands/index.js';
13
13
  /**
14
14
  * Create and configure the main CLI program
15
15
  */
@@ -34,6 +34,10 @@ function createProgram() {
34
34
  program.addCommand(filesCommand);
35
35
  program.addCommand(watchCommand);
36
36
  program.addCommand(dashboardCommand);
37
+ program.addCommand(trendsCommand);
38
+ program.addCommand(parserCommand);
39
+ program.addCommand(dnaCommand);
40
+ program.addCommand(boundariesCommand);
37
41
  // Add help examples
38
42
  program.addHelpText('after', `
39
43
  Examples:
@@ -58,6 +62,16 @@ Examples:
58
62
  $ drift watch --context .drift-context.md Auto-update AI context file
59
63
  $ drift dashboard Launch the web dashboard
60
64
  $ drift dashboard --port 8080 Launch on a custom port
65
+ $ drift trends View pattern regressions over time
66
+ $ drift trends --period 30d View trends for last 30 days
67
+ $ drift parser Show parser status and capabilities
68
+ $ drift parser --test file.py Test parsing a specific file
69
+ $ drift dna Show styling DNA status
70
+ $ drift dna scan Analyze codebase styling DNA
71
+ $ drift dna playbook Generate styling playbook
72
+ $ drift boundaries Show data access boundaries
73
+ $ drift boundaries tables List discovered tables
74
+ $ drift boundaries check Check for boundary violations
61
75
 
62
76
  Documentation:
63
77
  https://github.com/drift/drift
@@ -1 +1 @@
1
- {"version":3,"file":"drift.js","sourceRoot":"","sources":["../../src/bin/drift.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,SAAS,aAAa;IACpB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,OAAO,CAAC;SACb,WAAW,CAAC,qEAAqE,CAAC;SAClF,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,4BAA4B,CAAC;SAC/D,MAAM,CAAC,WAAW,EAAE,uBAAuB,CAAC;SAC5C,MAAM,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC;IAElD,wBAAwB;IACxB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACnC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAErC,oBAAoB;IACpB,OAAO,CAAC,WAAW,CACjB,OAAO,EACP;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BH,CACE,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;IAEhC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACzC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,cAAc;AACd,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"drift.js","sourceRoot":"","sources":["../../src/bin/drift.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,UAAU,EACV,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,SAAS,aAAa;IACpB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,OAAO,CAAC;SACb,WAAW,CAAC,qEAAqE,CAAC;SAClF,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,4BAA4B,CAAC;SAC/D,MAAM,CAAC,WAAW,EAAE,uBAAuB,CAAC;SAC5C,MAAM,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC;IAElD,wBAAwB;IACxB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACnC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACrC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAC/B,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAEtC,oBAAoB;IACpB,OAAO,CAAC,WAAW,CACjB,OAAO,EACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCH,CACE,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;IAEhC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACzC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,cAAc;AACd,IAAI,EAAE,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../../src/commands/boundaries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,MAAM,WAAW,iBAAiB;IAChC,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAgnBD;;GAEG;AACH,eAAO,MAAM,iBAAiB,SAIL,CAAC"}
@@ -0,0 +1,602 @@
1
+ /**
2
+ * Boundaries Command - drift boundaries
3
+ *
4
+ * Show data access boundaries and check for violations.
5
+ * Tracks which code accesses which database tables/fields.
6
+ *
7
+ * @requirements Data Boundaries Feature
8
+ */
9
+ import { Command } from 'commander';
10
+ import * as fs from 'node:fs/promises';
11
+ import * as path from 'node:path';
12
+ import chalk from 'chalk';
13
+ import { createBoundaryStore, } from 'driftdetect-core';
14
+ /** Directory name for drift configuration */
15
+ const DRIFT_DIR = '.drift';
16
+ /** Directory name for boundaries */
17
+ const BOUNDARIES_DIR = 'boundaries';
18
+ /** Rules file name */
19
+ const RULES_FILE = 'rules.json';
20
+ /**
21
+ * Check if boundaries directory exists
22
+ */
23
+ async function boundariesExist(rootDir) {
24
+ try {
25
+ await fs.access(path.join(rootDir, DRIFT_DIR, BOUNDARIES_DIR));
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Show helpful message when boundaries not initialized
34
+ */
35
+ function showNotInitializedMessage() {
36
+ console.log();
37
+ console.log(chalk.yellow('⚠️ No data boundaries discovered yet.'));
38
+ console.log();
39
+ console.log(chalk.gray('Data boundaries track which code accesses which database tables.'));
40
+ console.log(chalk.gray('Run a scan to discover data access patterns:'));
41
+ console.log();
42
+ console.log(chalk.cyan(' drift scan'));
43
+ console.log();
44
+ }
45
+ /**
46
+ * Overview subcommand - default view
47
+ */
48
+ async function overviewAction(options) {
49
+ const rootDir = process.cwd();
50
+ const format = options.format ?? 'text';
51
+ if (!(await boundariesExist(rootDir))) {
52
+ if (format === 'json') {
53
+ console.log(JSON.stringify({ error: 'No boundaries data found' }));
54
+ }
55
+ else {
56
+ showNotInitializedMessage();
57
+ }
58
+ return;
59
+ }
60
+ const store = createBoundaryStore({ rootDir });
61
+ await store.initialize();
62
+ const accessMap = store.getAccessMap();
63
+ const sensitiveFields = store.getSensitiveAccess();
64
+ // JSON output
65
+ if (format === 'json') {
66
+ console.log(JSON.stringify({
67
+ tables: accessMap.stats.totalTables,
68
+ accessPoints: accessMap.stats.totalAccessPoints,
69
+ sensitiveFields: accessMap.stats.totalSensitiveFields,
70
+ models: accessMap.stats.totalModels,
71
+ tableList: Object.keys(accessMap.tables),
72
+ }, null, 2));
73
+ return;
74
+ }
75
+ // Text output
76
+ console.log();
77
+ console.log(chalk.bold('🗄️ Data Boundaries'));
78
+ console.log();
79
+ // Summary stats
80
+ console.log(`Tables Discovered: ${chalk.cyan(accessMap.stats.totalTables)}`);
81
+ console.log(`Access Points: ${chalk.cyan(accessMap.stats.totalAccessPoints)}`);
82
+ console.log(`Sensitive Fields: ${chalk.cyan(accessMap.stats.totalSensitiveFields)}`);
83
+ console.log();
84
+ // Top accessed tables
85
+ const tableEntries = Object.entries(accessMap.tables)
86
+ .map(([name, info]) => ({
87
+ name,
88
+ accessCount: info.accessedBy.length,
89
+ fileCount: new Set(info.accessedBy.map(ap => ap.file)).size,
90
+ }))
91
+ .sort((a, b) => b.accessCount - a.accessCount)
92
+ .slice(0, 5);
93
+ if (tableEntries.length > 0) {
94
+ console.log(chalk.bold('Top Accessed Tables:'));
95
+ for (const table of tableEntries) {
96
+ const name = table.name.padEnd(16);
97
+ console.log(` ${chalk.white(name)} ${chalk.gray(`${table.accessCount} access points (${table.fileCount} files)`)}`);
98
+ }
99
+ console.log();
100
+ }
101
+ // Sensitive field access
102
+ if (sensitiveFields.length > 0) {
103
+ console.log(chalk.bold('Sensitive Field Access:'));
104
+ // Group by field name and count locations
105
+ const fieldCounts = new Map();
106
+ for (const field of sensitiveFields) {
107
+ const key = field.table ? `${field.table}.${field.field}` : field.field;
108
+ fieldCounts.set(key, (fieldCounts.get(key) ?? 0) + 1);
109
+ }
110
+ const sortedFields = Array.from(fieldCounts.entries())
111
+ .sort((a, b) => b[1] - a[1])
112
+ .slice(0, 5);
113
+ for (const [fieldName, count] of sortedFields) {
114
+ const name = fieldName.padEnd(24);
115
+ console.log(` ${chalk.yellow(name)} ${chalk.gray(`${count} locations`)}`);
116
+ }
117
+ console.log();
118
+ }
119
+ // Quick actions
120
+ console.log(chalk.gray("Run 'drift boundaries table <name>' for details"));
121
+ console.log();
122
+ }
123
+ /**
124
+ * Tables subcommand - list all discovered tables
125
+ */
126
+ async function tablesAction(options) {
127
+ const rootDir = process.cwd();
128
+ const format = options.format ?? 'text';
129
+ if (!(await boundariesExist(rootDir))) {
130
+ if (format === 'json') {
131
+ console.log(JSON.stringify({ error: 'No boundaries data found', tables: [] }));
132
+ }
133
+ else {
134
+ showNotInitializedMessage();
135
+ }
136
+ return;
137
+ }
138
+ const store = createBoundaryStore({ rootDir });
139
+ await store.initialize();
140
+ const accessMap = store.getAccessMap();
141
+ // JSON output
142
+ if (format === 'json') {
143
+ const tables = Object.entries(accessMap.tables).map(([name, info]) => ({
144
+ name,
145
+ model: info.model,
146
+ fields: info.fields,
147
+ accessCount: info.accessedBy.length,
148
+ sensitiveFields: info.sensitiveFields.map(f => f.field),
149
+ }));
150
+ console.log(JSON.stringify({ tables }, null, 2));
151
+ return;
152
+ }
153
+ // Text output
154
+ console.log();
155
+ console.log(chalk.bold('🗄️ Discovered Tables'));
156
+ console.log(chalk.gray('─'.repeat(60)));
157
+ console.log();
158
+ const tableEntries = Object.entries(accessMap.tables)
159
+ .sort((a, b) => b[1].accessedBy.length - a[1].accessedBy.length);
160
+ if (tableEntries.length === 0) {
161
+ console.log(chalk.gray(' No tables discovered yet.'));
162
+ console.log();
163
+ return;
164
+ }
165
+ for (const [name, info] of tableEntries) {
166
+ const fileCount = new Set(info.accessedBy.map(ap => ap.file)).size;
167
+ const hasSensitive = info.sensitiveFields.length > 0;
168
+ const tableName = hasSensitive ? chalk.yellow(name) : chalk.white(name);
169
+ const modelInfo = info.model ? chalk.gray(` (${info.model})`) : '';
170
+ console.log(` ${tableName}${modelInfo}`);
171
+ console.log(chalk.gray(` ${info.accessedBy.length} access points in ${fileCount} files`));
172
+ if (info.sensitiveFields.length > 0) {
173
+ console.log(chalk.yellow(` ⚠ ${info.sensitiveFields.length} sensitive fields`));
174
+ }
175
+ console.log();
176
+ }
177
+ }
178
+ /**
179
+ * Table subcommand - show access to specific table
180
+ */
181
+ async function tableAction(tableName, options) {
182
+ const rootDir = process.cwd();
183
+ const format = options.format ?? 'text';
184
+ if (!(await boundariesExist(rootDir))) {
185
+ if (format === 'json') {
186
+ console.log(JSON.stringify({ error: 'No boundaries data found' }));
187
+ }
188
+ else {
189
+ showNotInitializedMessage();
190
+ }
191
+ return;
192
+ }
193
+ const store = createBoundaryStore({ rootDir });
194
+ await store.initialize();
195
+ const tableInfo = store.getTableAccess(tableName);
196
+ if (!tableInfo) {
197
+ if (format === 'json') {
198
+ console.log(JSON.stringify({ error: `Table '${tableName}' not found` }));
199
+ }
200
+ else {
201
+ console.log();
202
+ console.log(chalk.red(`Table '${tableName}' not found.`));
203
+ console.log(chalk.gray("Run 'drift boundaries tables' to see all discovered tables."));
204
+ console.log();
205
+ }
206
+ return;
207
+ }
208
+ // JSON output
209
+ if (format === 'json') {
210
+ console.log(JSON.stringify({
211
+ name: tableInfo.name,
212
+ model: tableInfo.model,
213
+ fields: tableInfo.fields,
214
+ sensitiveFields: tableInfo.sensitiveFields,
215
+ accessPoints: tableInfo.accessedBy,
216
+ }, null, 2));
217
+ return;
218
+ }
219
+ // Text output
220
+ console.log();
221
+ console.log(chalk.bold(`🗄️ Table: ${tableName}`));
222
+ console.log(chalk.gray('─'.repeat(60)));
223
+ console.log();
224
+ if (tableInfo.model) {
225
+ console.log(`Model: ${chalk.cyan(tableInfo.model)}`);
226
+ }
227
+ console.log(`Fields: ${chalk.gray(tableInfo.fields.join(', ') || 'none detected')}`);
228
+ console.log(`Access Points: ${chalk.cyan(tableInfo.accessedBy.length)}`);
229
+ console.log();
230
+ // Sensitive fields
231
+ if (tableInfo.sensitiveFields.length > 0) {
232
+ console.log(chalk.bold.yellow('Sensitive Fields:'));
233
+ for (const field of tableInfo.sensitiveFields) {
234
+ console.log(` ${chalk.yellow('⚠')} ${field.field} ${chalk.gray(`(${field.sensitivityType})`)}`);
235
+ }
236
+ console.log();
237
+ }
238
+ // Access points grouped by file
239
+ const byFile = new Map();
240
+ for (const ap of tableInfo.accessedBy) {
241
+ if (!byFile.has(ap.file)) {
242
+ byFile.set(ap.file, []);
243
+ }
244
+ byFile.get(ap.file).push(ap);
245
+ }
246
+ console.log(chalk.bold('Access Points:'));
247
+ for (const [file, accessPoints] of byFile) {
248
+ console.log(` ${chalk.cyan(file)}`);
249
+ for (const ap of accessPoints) {
250
+ const opColor = ap.operation === 'write' ? chalk.yellow :
251
+ ap.operation === 'delete' ? chalk.red : chalk.gray;
252
+ console.log(` Line ${ap.line}: ${opColor(ap.operation)} ${chalk.gray(ap.fields.join(', ') || '')}`);
253
+ }
254
+ }
255
+ console.log();
256
+ }
257
+ /**
258
+ * File subcommand - show what data a file/pattern accesses
259
+ */
260
+ async function fileAction(pattern, options) {
261
+ const rootDir = process.cwd();
262
+ const format = options.format ?? 'text';
263
+ if (!(await boundariesExist(rootDir))) {
264
+ if (format === 'json') {
265
+ console.log(JSON.stringify({ error: 'No boundaries data found', files: [] }));
266
+ }
267
+ else {
268
+ showNotInitializedMessage();
269
+ }
270
+ return;
271
+ }
272
+ const store = createBoundaryStore({ rootDir });
273
+ await store.initialize();
274
+ const fileAccess = store.getFileAccess(pattern);
275
+ // JSON output
276
+ if (format === 'json') {
277
+ console.log(JSON.stringify({ files: fileAccess }, null, 2));
278
+ return;
279
+ }
280
+ // Text output
281
+ console.log();
282
+ console.log(chalk.bold(`📁 Data Access: ${pattern}`));
283
+ console.log(chalk.gray('─'.repeat(60)));
284
+ console.log();
285
+ if (fileAccess.length === 0) {
286
+ console.log(chalk.gray(` No data access found for pattern '${pattern}'.`));
287
+ console.log();
288
+ return;
289
+ }
290
+ for (const fileInfo of fileAccess) {
291
+ console.log(chalk.cyan(fileInfo.file));
292
+ console.log(` Tables: ${chalk.white(fileInfo.tables.join(', '))}`);
293
+ console.log(` Access Points: ${fileInfo.accessPoints.length}`);
294
+ for (const ap of fileInfo.accessPoints) {
295
+ const opColor = ap.operation === 'write' ? chalk.yellow :
296
+ ap.operation === 'delete' ? chalk.red : chalk.gray;
297
+ console.log(` Line ${ap.line}: ${opColor(ap.operation)} ${chalk.white(ap.table)} ${chalk.gray(ap.fields.join(', '))}`);
298
+ }
299
+ console.log();
300
+ }
301
+ }
302
+ /**
303
+ * Sensitive subcommand - show all sensitive field access
304
+ */
305
+ async function sensitiveAction(options) {
306
+ const rootDir = process.cwd();
307
+ const format = options.format ?? 'text';
308
+ if (!(await boundariesExist(rootDir))) {
309
+ if (format === 'json') {
310
+ console.log(JSON.stringify({ error: 'No boundaries data found', sensitiveFields: [] }));
311
+ }
312
+ else {
313
+ showNotInitializedMessage();
314
+ }
315
+ return;
316
+ }
317
+ const store = createBoundaryStore({ rootDir });
318
+ await store.initialize();
319
+ const sensitiveFields = store.getSensitiveAccess();
320
+ // JSON output
321
+ if (format === 'json') {
322
+ console.log(JSON.stringify({ sensitiveFields }, null, 2));
323
+ return;
324
+ }
325
+ // Text output
326
+ console.log();
327
+ console.log(chalk.bold('🔒 Sensitive Field Access'));
328
+ console.log(chalk.gray('─'.repeat(60)));
329
+ console.log();
330
+ if (sensitiveFields.length === 0) {
331
+ console.log(chalk.gray(' No sensitive fields detected.'));
332
+ console.log();
333
+ return;
334
+ }
335
+ // Group by sensitivity type
336
+ const byType = new Map();
337
+ for (const field of sensitiveFields) {
338
+ const type = field.sensitivityType;
339
+ if (!byType.has(type)) {
340
+ byType.set(type, []);
341
+ }
342
+ byType.get(type).push(field);
343
+ }
344
+ const typeColors = {
345
+ pii: chalk.yellow,
346
+ credentials: chalk.red,
347
+ financial: chalk.magenta,
348
+ health: chalk.cyan,
349
+ unknown: chalk.gray,
350
+ };
351
+ for (const [type, fields] of byType) {
352
+ const color = typeColors[type] ?? chalk.gray;
353
+ console.log(color.bold(`${type.toUpperCase()} (${fields.length}):`));
354
+ for (const field of fields) {
355
+ const fieldName = field.table ? `${field.table}.${field.field}` : field.field;
356
+ console.log(` ${color('●')} ${chalk.white(fieldName)}`);
357
+ console.log(chalk.gray(` ${field.file}:${field.line}`));
358
+ }
359
+ console.log();
360
+ }
361
+ }
362
+ /**
363
+ * Check subcommand - check for boundary violations
364
+ */
365
+ async function checkAction(options) {
366
+ const rootDir = process.cwd();
367
+ const format = options.format ?? 'text';
368
+ if (!(await boundariesExist(rootDir))) {
369
+ if (format === 'json') {
370
+ console.log(JSON.stringify({ error: 'No boundaries data found' }));
371
+ }
372
+ else {
373
+ showNotInitializedMessage();
374
+ }
375
+ return;
376
+ }
377
+ const store = createBoundaryStore({ rootDir });
378
+ await store.initialize();
379
+ const rules = store.getRules();
380
+ if (!rules) {
381
+ if (format === 'json') {
382
+ console.log(JSON.stringify({ error: 'No rules.json found', violations: [] }));
383
+ }
384
+ else {
385
+ console.log();
386
+ console.log(chalk.yellow('⚠️ No boundary rules configured.'));
387
+ console.log();
388
+ console.log(chalk.gray('Create rules to enforce data access boundaries:'));
389
+ console.log(chalk.cyan(' drift boundaries init-rules'));
390
+ console.log();
391
+ }
392
+ return;
393
+ }
394
+ const violations = store.checkAllViolations();
395
+ // JSON output
396
+ if (format === 'json') {
397
+ console.log(JSON.stringify({
398
+ rulesCount: rules.boundaries.length,
399
+ violations,
400
+ violationCount: violations.length,
401
+ }, null, 2));
402
+ return;
403
+ }
404
+ // Text output
405
+ console.log();
406
+ console.log(chalk.bold('🔍 Boundary Check'));
407
+ console.log(chalk.gray('─'.repeat(60)));
408
+ console.log();
409
+ console.log(`Rules: ${chalk.cyan(rules.boundaries.length)}`);
410
+ console.log(`Violations: ${violations.length > 0 ? chalk.red(violations.length) : chalk.green(0)}`);
411
+ console.log();
412
+ if (violations.length === 0) {
413
+ console.log(chalk.green('✓ No boundary violations found.'));
414
+ console.log();
415
+ return;
416
+ }
417
+ // Group violations by severity
418
+ const errors = violations.filter(v => v.severity === 'error');
419
+ const warnings = violations.filter(v => v.severity === 'warning');
420
+ const infos = violations.filter(v => v.severity === 'info');
421
+ if (errors.length > 0) {
422
+ console.log(chalk.red.bold(`Errors (${errors.length}):`));
423
+ for (const v of errors) {
424
+ console.log(chalk.red(` ✗ ${v.file}:${v.line}`));
425
+ console.log(chalk.gray(` ${v.message}`));
426
+ if (v.suggestion) {
427
+ console.log(chalk.gray(` → ${v.suggestion}`));
428
+ }
429
+ }
430
+ console.log();
431
+ }
432
+ if (warnings.length > 0) {
433
+ console.log(chalk.yellow.bold(`Warnings (${warnings.length}):`));
434
+ for (const v of warnings) {
435
+ console.log(chalk.yellow(` ⚠ ${v.file}:${v.line}`));
436
+ console.log(chalk.gray(` ${v.message}`));
437
+ }
438
+ console.log();
439
+ }
440
+ if (infos.length > 0) {
441
+ console.log(chalk.blue.bold(`Info (${infos.length}):`));
442
+ for (const v of infos) {
443
+ console.log(chalk.blue(` ℹ ${v.file}:${v.line}`));
444
+ console.log(chalk.gray(` ${v.message}`));
445
+ }
446
+ console.log();
447
+ }
448
+ }
449
+ /**
450
+ * Init-rules subcommand - generate starter rules.json
451
+ */
452
+ async function initRulesAction(options) {
453
+ const rootDir = process.cwd();
454
+ const format = options.format ?? 'text';
455
+ const rulesPath = path.join(rootDir, DRIFT_DIR, BOUNDARIES_DIR, RULES_FILE);
456
+ // Check if rules already exist
457
+ try {
458
+ await fs.access(rulesPath);
459
+ if (format === 'json') {
460
+ console.log(JSON.stringify({ error: 'rules.json already exists', path: rulesPath }));
461
+ }
462
+ else {
463
+ console.log();
464
+ console.log(chalk.yellow(`⚠️ ${rulesPath} already exists.`));
465
+ console.log(chalk.gray('Delete it first if you want to regenerate.'));
466
+ console.log();
467
+ }
468
+ return;
469
+ }
470
+ catch {
471
+ // File doesn't exist, continue
472
+ }
473
+ // Ensure directory exists
474
+ await fs.mkdir(path.join(rootDir, DRIFT_DIR, BOUNDARIES_DIR), { recursive: true });
475
+ // Generate starter rules
476
+ const starterRules = {
477
+ version: '1.0',
478
+ sensitivity: {
479
+ critical: [
480
+ 'users.password_hash',
481
+ 'users.ssn',
482
+ 'payments.card_number',
483
+ ],
484
+ sensitive: [
485
+ 'users.email',
486
+ 'users.phone',
487
+ 'users.address',
488
+ ],
489
+ general: [],
490
+ },
491
+ boundaries: [
492
+ {
493
+ id: 'sensitive-data-access',
494
+ description: 'Sensitive user data should only be accessed from user services',
495
+ tables: ['users'],
496
+ fields: ['users.email', 'users.phone', 'users.address'],
497
+ allowedPaths: [
498
+ '**/services/user*.ts',
499
+ '**/services/user*.js',
500
+ '**/repositories/user*.ts',
501
+ ],
502
+ excludePaths: ['**/*.test.ts', '**/*.spec.ts', '**/tests/**'],
503
+ severity: 'warning',
504
+ enabled: true,
505
+ },
506
+ {
507
+ id: 'credentials-access',
508
+ description: 'Credentials should only be accessed from auth services',
509
+ fields: ['users.password_hash', 'users.ssn'],
510
+ allowedPaths: [
511
+ '**/services/auth*.ts',
512
+ '**/auth/**',
513
+ ],
514
+ excludePaths: ['**/*.test.ts', '**/*.spec.ts'],
515
+ severity: 'error',
516
+ enabled: true,
517
+ },
518
+ {
519
+ id: 'payment-data-access',
520
+ description: 'Payment data should only be accessed from payment services',
521
+ tables: ['payments', 'transactions'],
522
+ allowedPaths: [
523
+ '**/services/payment*.ts',
524
+ '**/services/billing*.ts',
525
+ '**/payments/**',
526
+ ],
527
+ excludePaths: ['**/*.test.ts', '**/*.spec.ts'],
528
+ severity: 'error',
529
+ enabled: true,
530
+ },
531
+ ],
532
+ globalExcludes: [
533
+ '**/node_modules/**',
534
+ '**/dist/**',
535
+ '**/build/**',
536
+ '**/*.d.ts',
537
+ ],
538
+ };
539
+ await fs.writeFile(rulesPath, JSON.stringify(starterRules, null, 2));
540
+ // JSON output
541
+ if (format === 'json') {
542
+ console.log(JSON.stringify({ success: true, path: rulesPath, rules: starterRules }, null, 2));
543
+ return;
544
+ }
545
+ // Text output
546
+ console.log();
547
+ console.log(chalk.green('✓ Created starter rules.json'));
548
+ console.log();
549
+ console.log(chalk.gray(`Location: ${rulesPath}`));
550
+ console.log();
551
+ console.log(chalk.bold('Included rules:'));
552
+ for (const rule of starterRules.boundaries) {
553
+ const severityColor = rule.severity === 'error' ? chalk.red :
554
+ rule.severity === 'warning' ? chalk.yellow : chalk.blue;
555
+ console.log(` ${severityColor('●')} ${rule.id}`);
556
+ console.log(chalk.gray(` ${rule.description}`));
557
+ }
558
+ console.log();
559
+ console.log(chalk.gray('Edit the rules.json file to customize boundaries for your project.'));
560
+ console.log(chalk.gray("Then run 'drift boundaries check' to validate."));
561
+ console.log();
562
+ }
563
+ /**
564
+ * Create the boundaries command with subcommands
565
+ */
566
+ export const boundariesCommand = new Command('boundaries')
567
+ .description('Show data access boundaries and check for violations')
568
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
569
+ .option('--verbose', 'Enable verbose output')
570
+ .action(overviewAction);
571
+ // Subcommands
572
+ boundariesCommand
573
+ .command('tables')
574
+ .description('List all discovered tables')
575
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
576
+ .action(tablesAction);
577
+ boundariesCommand
578
+ .command('table <name>')
579
+ .description('Show access to a specific table')
580
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
581
+ .action(tableAction);
582
+ boundariesCommand
583
+ .command('file <pattern>')
584
+ .description('Show what data a file or pattern accesses')
585
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
586
+ .action(fileAction);
587
+ boundariesCommand
588
+ .command('sensitive')
589
+ .description('Show all sensitive field access')
590
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
591
+ .action(sensitiveAction);
592
+ boundariesCommand
593
+ .command('check')
594
+ .description('Check for boundary violations (requires rules.json)')
595
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
596
+ .action(checkAction);
597
+ boundariesCommand
598
+ .command('init-rules')
599
+ .description('Generate a starter rules.json file')
600
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
601
+ .action(initRulesAction);
602
+ //# sourceMappingURL=boundaries.js.map