sqlew 5.1.0 → 5.2.1

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 (83) hide show
  1. package/CHANGELOG.md +2140 -2081
  2. package/LICENSE +190 -190
  3. package/NOTICE +24 -24
  4. package/README.md +204 -190
  5. package/dist/adapters/mysql-adapter.js +3 -3
  6. package/dist/adapters/postgresql-adapter.js +3 -3
  7. package/dist/cli/db-export.js +32 -32
  8. package/dist/cli/db-import.js +30 -30
  9. package/dist/cli/hooks/codex-transcript.d.ts +23 -0
  10. package/dist/cli/hooks/codex-transcript.d.ts.map +1 -0
  11. package/dist/cli/hooks/codex-transcript.js +134 -0
  12. package/dist/cli/hooks/codex-transcript.js.map +1 -0
  13. package/dist/cli/hooks/on-exit-plan.d.ts.map +1 -1
  14. package/dist/cli/hooks/on-exit-plan.js +72 -4
  15. package/dist/cli/hooks/on-exit-plan.js.map +1 -1
  16. package/dist/cli/hooks/on-prompt.d.ts.map +1 -1
  17. package/dist/cli/hooks/on-prompt.js +38 -16
  18. package/dist/cli/hooks/on-prompt.js.map +1 -1
  19. package/dist/cli/hooks/plan-processor.d.ts.map +1 -1
  20. package/dist/cli/hooks/plan-processor.js +16 -2
  21. package/dist/cli/hooks/plan-processor.js.map +1 -1
  22. package/dist/cli/hooks/pr-adr.js +5 -5
  23. package/dist/cli/hooks/stdin-parser.d.ts +43 -0
  24. package/dist/cli/hooks/stdin-parser.d.ts.map +1 -1
  25. package/dist/cli/hooks/stdin-parser.js +212 -6
  26. package/dist/cli/hooks/stdin-parser.js.map +1 -1
  27. package/dist/cli/hooks/track-plan.d.ts +13 -0
  28. package/dist/cli/hooks/track-plan.d.ts.map +1 -1
  29. package/dist/cli/hooks/track-plan.js +73 -18
  30. package/dist/cli/hooks/track-plan.js.map +1 -1
  31. package/dist/cli.js +48 -48
  32. package/dist/config/global-config.d.ts +7 -1
  33. package/dist/config/global-config.d.ts.map +1 -1
  34. package/dist/config/global-config.js +5 -26
  35. package/dist/config/global-config.js.map +1 -1
  36. package/dist/database/migrations/v4/20251126000000_v4_bootstrap.js +32 -32
  37. package/dist/database/migrations/v4/20251126000001_v4_migrate_data.d.ts.map +1 -1
  38. package/dist/database/migrations/v4/20251126000001_v4_migrate_data.js +2 -1
  39. package/dist/database/migrations/v4/20251126000001_v4_migrate_data.js.map +1 -1
  40. package/dist/database/migrations/v4/20260102204000_v4_fix_decision_set_example.js +3 -3
  41. package/dist/help-data/constraint.toml +259 -259
  42. package/dist/help-data/decision.toml +845 -845
  43. package/dist/help-data/queue.toml +134 -134
  44. package/dist/server/tool-schemas.js +30 -30
  45. package/dist/tests/docker/native/db-init.js +9 -9
  46. package/dist/tests/unit/hooks/codex-hook-normalization.test.d.ts +7 -0
  47. package/dist/tests/unit/hooks/codex-hook-normalization.test.d.ts.map +1 -0
  48. package/dist/tests/unit/hooks/codex-hook-normalization.test.js +112 -0
  49. package/dist/tests/unit/hooks/codex-hook-normalization.test.js.map +1 -0
  50. package/dist/tests/unit/hooks/grok-hook-normalization.test.d.ts +9 -0
  51. package/dist/tests/unit/hooks/grok-hook-normalization.test.d.ts.map +1 -0
  52. package/dist/tests/unit/hooks/grok-hook-normalization.test.js +136 -0
  53. package/dist/tests/unit/hooks/grok-hook-normalization.test.js.map +1 -0
  54. package/dist/tests/unit/hooks/grok-plan-template-injection.test.d.ts +7 -0
  55. package/dist/tests/unit/hooks/grok-plan-template-injection.test.d.ts.map +1 -0
  56. package/dist/tests/unit/hooks/grok-plan-template-injection.test.js +55 -0
  57. package/dist/tests/unit/hooks/grok-plan-template-injection.test.js.map +1 -0
  58. package/dist/tests/utils/db-schema.js +48 -48
  59. package/dist/tests/utils/test-helpers.js +48 -48
  60. package/dist/tools/constraints/actions/get.js +5 -5
  61. package/dist/utils/path-normalize.d.ts +23 -0
  62. package/dist/utils/path-normalize.d.ts.map +1 -0
  63. package/dist/utils/path-normalize.js +38 -0
  64. package/dist/utils/path-normalize.js.map +1 -0
  65. package/dist/utils/project-root.d.ts +7 -3
  66. package/dist/utils/project-root.d.ts.map +1 -1
  67. package/dist/utils/project-root.js +17 -3
  68. package/dist/utils/project-root.js.map +1 -1
  69. package/dist/watcher/base-watcher.d.ts +0 -4
  70. package/dist/watcher/base-watcher.d.ts.map +1 -1
  71. package/dist/watcher/base-watcher.js +11 -22
  72. package/dist/watcher/base-watcher.js.map +1 -1
  73. package/docs/ADR_CONCEPTS.md +152 -152
  74. package/docs/CLI_USAGE.md +392 -392
  75. package/docs/CONFIGURATION.md +157 -157
  76. package/docs/CROSS_DATABASE.md +66 -66
  77. package/docs/DATABASE_AUTH.md +135 -135
  78. package/docs/HOOKS_GUIDE.md +116 -67
  79. package/docs/MIGRATION_TO_SAAS.md +176 -176
  80. package/docs/SHARED_DATABASE.md +108 -108
  81. package/package.json +88 -88
  82. package/scripts/copy-help-data.js +19 -19
  83. package/scripts/filter-test-output.js +78 -78
@@ -1,134 +1,134 @@
1
- # Queue Tool - Hook Queue Management
2
- # Manage pending items in .sqlew/queue/pending.json
3
-
4
- [tool]
5
- name = "queue"
6
- description = "Hook queue management - list, clear, remove pending items from .sqlew/queue/pending.json. Safe operations with lock mechanism to prevent race conditions with QueueWatcher."
7
-
8
- # =============================================================================
9
- # ACTIONS
10
- # =============================================================================
11
-
12
- [[actions]]
13
- name = "list"
14
- description = "List all pending items in the queue"
15
-
16
- [[actions.params]]
17
- name = "action"
18
- type = "string"
19
- required = true
20
- description = "Must be \"list\""
21
-
22
- [[actions.examples]]
23
- title = "List queue items"
24
- code = '''
25
- {
26
- "action": "list"
27
- }
28
- '''
29
- explanation = "Show all pending decisions/constraints in the queue with their index, type, and metadata"
30
-
31
- # -----------------------------------------------------------------------------
32
-
33
- [[actions]]
34
- name = "clear"
35
- description = "Clear all items from the queue"
36
-
37
- [[actions.params]]
38
- name = "action"
39
- type = "string"
40
- required = true
41
- description = "Must be \"clear\""
42
-
43
- [[actions.examples]]
44
- title = "Clear all queue items"
45
- code = '''
46
- {
47
- "action": "clear"
48
- }
49
- '''
50
- explanation = "Remove all pending items from the queue at once"
51
-
52
- # -----------------------------------------------------------------------------
53
-
54
- [[actions]]
55
- name = "remove"
56
- description = "Remove a specific item from the queue by index"
57
-
58
- [[actions.params]]
59
- name = "action"
60
- type = "string"
61
- required = true
62
- description = "Must be \"remove\""
63
-
64
- [[actions.params]]
65
- name = "index"
66
- type = "number"
67
- required = true
68
- description = "0-based index of the item to remove (use 'list' first to see indices)"
69
-
70
- [[actions.examples]]
71
- title = "Remove item at index 0"
72
- code = '''
73
- {
74
- "action": "remove",
75
- "index": 0
76
- }
77
- '''
78
- explanation = "Remove the first item from the queue. Use 'list' first to see available indices."
79
-
80
- # -----------------------------------------------------------------------------
81
-
82
- [[actions]]
83
- name = "help"
84
- description = "Get queue tool documentation"
85
-
86
- [[actions.params]]
87
- name = "action"
88
- type = "string"
89
- required = true
90
- description = "Must be \"help\""
91
-
92
- [[actions.examples]]
93
- title = "Get help"
94
- code = '''
95
- {
96
- "action": "help"
97
- }
98
- '''
99
- explanation = "Display queue tool documentation"
100
-
101
- # -----------------------------------------------------------------------------
102
-
103
- [[actions]]
104
- name = "example"
105
- description = "Get queue tool usage examples"
106
-
107
- [[actions.params]]
108
- name = "action"
109
- type = "string"
110
- required = true
111
- description = "Must be \"example\""
112
-
113
- [[actions.examples]]
114
- title = "Get examples"
115
- code = '''
116
- {
117
- "action": "example"
118
- }
119
- '''
120
- explanation = "Display queue tool usage examples"
121
-
122
- # =============================================================================
123
- # USE CASES
124
- # =============================================================================
125
-
126
- [[use_cases]]
127
- title = "Handle stuck items due to High Similarity"
128
- description = "Items may remain in queue if they have 60%+ similarity to existing decisions"
129
- steps = [
130
- "1. Use `queue list` to see stuck items",
131
- "2. Check existing decisions with `/sqlew search for <topic>`",
132
- "3. If truly duplicate: Use `queue remove` with the item index",
133
- "4. If different intent: Update the key to be more specific and retry"
134
- ]
1
+ # Queue Tool - Hook Queue Management
2
+ # Manage pending items in .sqlew/queue/pending.json
3
+
4
+ [tool]
5
+ name = "queue"
6
+ description = "Hook queue management - list, clear, remove pending items from .sqlew/queue/pending.json. Safe operations with lock mechanism to prevent race conditions with QueueWatcher."
7
+
8
+ # =============================================================================
9
+ # ACTIONS
10
+ # =============================================================================
11
+
12
+ [[actions]]
13
+ name = "list"
14
+ description = "List all pending items in the queue"
15
+
16
+ [[actions.params]]
17
+ name = "action"
18
+ type = "string"
19
+ required = true
20
+ description = "Must be \"list\""
21
+
22
+ [[actions.examples]]
23
+ title = "List queue items"
24
+ code = '''
25
+ {
26
+ "action": "list"
27
+ }
28
+ '''
29
+ explanation = "Show all pending decisions/constraints in the queue with their index, type, and metadata"
30
+
31
+ # -----------------------------------------------------------------------------
32
+
33
+ [[actions]]
34
+ name = "clear"
35
+ description = "Clear all items from the queue"
36
+
37
+ [[actions.params]]
38
+ name = "action"
39
+ type = "string"
40
+ required = true
41
+ description = "Must be \"clear\""
42
+
43
+ [[actions.examples]]
44
+ title = "Clear all queue items"
45
+ code = '''
46
+ {
47
+ "action": "clear"
48
+ }
49
+ '''
50
+ explanation = "Remove all pending items from the queue at once"
51
+
52
+ # -----------------------------------------------------------------------------
53
+
54
+ [[actions]]
55
+ name = "remove"
56
+ description = "Remove a specific item from the queue by index"
57
+
58
+ [[actions.params]]
59
+ name = "action"
60
+ type = "string"
61
+ required = true
62
+ description = "Must be \"remove\""
63
+
64
+ [[actions.params]]
65
+ name = "index"
66
+ type = "number"
67
+ required = true
68
+ description = "0-based index of the item to remove (use 'list' first to see indices)"
69
+
70
+ [[actions.examples]]
71
+ title = "Remove item at index 0"
72
+ code = '''
73
+ {
74
+ "action": "remove",
75
+ "index": 0
76
+ }
77
+ '''
78
+ explanation = "Remove the first item from the queue. Use 'list' first to see available indices."
79
+
80
+ # -----------------------------------------------------------------------------
81
+
82
+ [[actions]]
83
+ name = "help"
84
+ description = "Get queue tool documentation"
85
+
86
+ [[actions.params]]
87
+ name = "action"
88
+ type = "string"
89
+ required = true
90
+ description = "Must be \"help\""
91
+
92
+ [[actions.examples]]
93
+ title = "Get help"
94
+ code = '''
95
+ {
96
+ "action": "help"
97
+ }
98
+ '''
99
+ explanation = "Display queue tool documentation"
100
+
101
+ # -----------------------------------------------------------------------------
102
+
103
+ [[actions]]
104
+ name = "example"
105
+ description = "Get queue tool usage examples"
106
+
107
+ [[actions.params]]
108
+ name = "action"
109
+ type = "string"
110
+ required = true
111
+ description = "Must be \"example\""
112
+
113
+ [[actions.examples]]
114
+ title = "Get examples"
115
+ code = '''
116
+ {
117
+ "action": "example"
118
+ }
119
+ '''
120
+ explanation = "Display queue tool usage examples"
121
+
122
+ # =============================================================================
123
+ # USE CASES
124
+ # =============================================================================
125
+
126
+ [[use_cases]]
127
+ title = "Handle stuck items due to High Similarity"
128
+ description = "Items may remain in queue if they have 60%+ similarity to existing decisions"
129
+ steps = [
130
+ "1. Use `queue list` to see stuck items",
131
+ "2. Check existing decisions with `/sqlew search for <topic>`",
132
+ "3. If truly duplicate: Use `queue remove` with the item index",
133
+ "4. If different intent: Update the key to be more specific and retry"
134
+ ]
@@ -121,38 +121,38 @@ export const TOOL_SCHEMAS = {
121
121
  export const TOOL_DESCRIPTIONS = {
122
122
  decision: 'Context Management - Store decisions with versioning and metadata. Use action: "help" for documentation.',
123
123
  constraint: 'Architectural Rules - Define and manage project constraints with priorities. Use action: "help" for documentation.',
124
- help: `**REQUIRED PARAMETER**: action (must be specified in ALL calls)
125
-
126
- Help System - Query action documentation, parameters, and workflow guidance
127
-
128
- Actions:
129
- - query_action: Get action documentation with parameters and examples
130
- - query_params: Get parameter list only (quick reference)
131
- - query_tool: Get tool overview and all actions
132
- - workflow_hints: Get common next actions after current action
133
- - batch_guide: Get guidance for batch operations
134
- - error_recovery: Analyze errors and suggest fixes
135
-
124
+ help: `**REQUIRED PARAMETER**: action (must be specified in ALL calls)
125
+
126
+ Help System - Query action documentation, parameters, and workflow guidance
127
+
128
+ Actions:
129
+ - query_action: Get action documentation with parameters and examples
130
+ - query_params: Get parameter list only (quick reference)
131
+ - query_tool: Get tool overview and all actions
132
+ - workflow_hints: Get common next actions after current action
133
+ - batch_guide: Get guidance for batch operations
134
+ - error_recovery: Analyze errors and suggest fixes
135
+
136
136
  Use this tool to understand how to use other sqlew tools. Returns only requested information (80-95% token reduction vs legacy help).`,
137
- example: `**REQUIRED PARAMETER**: action (must be specified in ALL calls)
138
-
139
- Example System - Browse and search code examples for sqlew tools
140
-
141
- Actions:
142
- - get: Get examples by tool, action, or topic
143
- - search: Search examples by keyword
144
- - list_all: List all available examples with filtering
145
-
137
+ example: `**REQUIRED PARAMETER**: action (must be specified in ALL calls)
138
+
139
+ Example System - Browse and search code examples for sqlew tools
140
+
141
+ Actions:
142
+ - get: Get examples by tool, action, or topic
143
+ - search: Search examples by keyword
144
+ - list_all: List all available examples with filtering
145
+
146
146
  Use this tool to find working code snippets. Returns only requested examples (token-efficient).`,
147
- use_case: `**REQUIRED PARAMETER**: action (must be specified in ALL calls)
148
-
149
- Use Case Catalog - Browse and search complete workflow scenarios
150
-
151
- Actions:
152
- - get: Get complete use case workflow by ID
153
- - search: Search use cases by keyword/category
154
- - list_all: List all use cases with filtering and pagination
155
-
147
+ use_case: `**REQUIRED PARAMETER**: action (must be specified in ALL calls)
148
+
149
+ Use Case Catalog - Browse and search complete workflow scenarios
150
+
151
+ Actions:
152
+ - get: Get complete use case workflow by ID
153
+ - search: Search use cases by keyword/category
154
+ - list_all: List all use cases with filtering and pagination
155
+
156
156
  Use this tool to learn end-to-end workflows and multi-step operations. Returns workflow steps with executable code examples.`,
157
157
  suggest: 'Intelligent decision/constraint suggestion system. Find related decisions by key pattern, tags, or full context. Prevents duplicates and ensures consistency.',
158
158
  queue: 'Hook queue management - list, clear, remove pending items from .sqlew/queue/pending.json. Use action: "help" for documentation.',
@@ -103,11 +103,11 @@ export async function teardownDatabase(db) {
103
103
  async function dropAllTables(db, dbType) {
104
104
  if (dbType === 'mysql' || dbType === 'mariadb') {
105
105
  await db.raw('SET FOREIGN_KEY_CHECKS=0');
106
- const tables = await db.raw(`
107
- SELECT TABLE_NAME
108
- FROM INFORMATION_SCHEMA.TABLES
109
- WHERE TABLE_SCHEMA = DATABASE()
110
- AND TABLE_TYPE = 'BASE TABLE'
106
+ const tables = await db.raw(`
107
+ SELECT TABLE_NAME
108
+ FROM INFORMATION_SCHEMA.TABLES
109
+ WHERE TABLE_SCHEMA = DATABASE()
110
+ AND TABLE_TYPE = 'BASE TABLE'
111
111
  `);
112
112
  for (const row of tables[0]) {
113
113
  await db.raw(`DROP TABLE IF EXISTS \`${row.TABLE_NAME}\``);
@@ -115,10 +115,10 @@ async function dropAllTables(db, dbType) {
115
115
  await db.raw('SET FOREIGN_KEY_CHECKS=1');
116
116
  }
117
117
  else if (dbType === 'postgresql') {
118
- const tables = await db.raw(`
119
- SELECT tablename
120
- FROM pg_tables
121
- WHERE schemaname = 'public'
118
+ const tables = await db.raw(`
119
+ SELECT tablename
120
+ FROM pg_tables
121
+ WHERE schemaname = 'public'
122
122
  `);
123
123
  for (const row of tables.rows) {
124
124
  await db.raw(`DROP TABLE IF EXISTS "${row.tablename}" CASCADE`);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Codex Hook Normalization Unit Tests
3
+ *
4
+ * @since v5.2.1
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=codex-hook-normalization.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-hook-normalization.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/unit/hooks/codex-hook-normalization.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Codex Hook Normalization Unit Tests
3
+ *
4
+ * @since v5.2.1
5
+ */
6
+ import { describe, it, beforeEach, afterEach } from 'node:test';
7
+ import assert from 'node:assert';
8
+ import { homedir } from 'os';
9
+ import { join } from 'path';
10
+ import { normalizeHookInput, isPlanMode, } from '../../../cli/hooks/stdin-parser.js';
11
+ import { findCodexTranscriptPath, extractPlanMarkdownFromCodexTranscript, } from '../../../cli/hooks/codex-transcript.js';
12
+ import { determineProjectRoot } from '../../../utils/project-root.js';
13
+ describe('codex-hook-normalization', () => {
14
+ const originalEnv = { ...process.env };
15
+ beforeEach(() => {
16
+ delete process.env.CODEX_SESSION_ID;
17
+ delete process.env.CODEX_CWD;
18
+ delete process.env.CODEX_HOME;
19
+ delete process.env.GROK_HOOK_EVENT;
20
+ delete process.env.GROK_WORKSPACE_ROOT;
21
+ delete process.env.CLAUDE_PROJECT_DIR;
22
+ delete process.env.SQLEW_PROJECT_ROOT;
23
+ });
24
+ afterEach(() => {
25
+ process.env = { ...originalEnv };
26
+ });
27
+ describe('normalizeHookInput', () => {
28
+ it('should normalize Codex shell_command to Bash', () => {
29
+ const result = normalizeHookInput({
30
+ hook_event_name: 'PreToolUse',
31
+ tool_name: 'shell_command',
32
+ cwd: 'C:/project',
33
+ });
34
+ assert.strictEqual(result.client, 'codex');
35
+ assert.strictEqual(result.tool_name, 'Bash');
36
+ assert.strictEqual(result.cwd, 'C:/project');
37
+ });
38
+ it('should normalize Codex apply_patch to Edit', () => {
39
+ const result = normalizeHookInput({
40
+ hook_event_name: 'PostToolUse',
41
+ tool_name: 'apply_patch',
42
+ cwd: 'C:/project',
43
+ });
44
+ assert.strictEqual(result.client, 'codex');
45
+ assert.strictEqual(result.tool_name, 'Edit');
46
+ });
47
+ it('should map collaboration_mode plan to permission_mode plan', () => {
48
+ const result = normalizeHookInput({
49
+ hook_event_name: 'UserPromptSubmit',
50
+ collaboration_mode: { mode: 'plan' },
51
+ cwd: 'C:/project',
52
+ session_id: 'sess-codex-1',
53
+ });
54
+ assert.strictEqual(result.client, 'codex');
55
+ assert.strictEqual(result.collaboration_mode, 'plan');
56
+ assert.strictEqual(result.permission_mode, 'plan');
57
+ });
58
+ it('should use CODEX env vars when stdin is empty', () => {
59
+ process.env.CODEX_SESSION_ID = 'sess-1';
60
+ process.env.CODEX_CWD = 'C:/project';
61
+ const result = normalizeHookInput({});
62
+ assert.strictEqual(result.client, 'codex');
63
+ assert.strictEqual(result.session_id, 'sess-1');
64
+ assert.strictEqual(result.cwd, 'C:/project');
65
+ });
66
+ });
67
+ describe('isPlanMode', () => {
68
+ it('should detect Codex plan mode via collaboration_mode', () => {
69
+ assert.strictEqual(isPlanMode({ client: 'codex', collaboration_mode: 'plan' }), true);
70
+ });
71
+ it('should detect Codex after normalization', () => {
72
+ const normalized = normalizeHookInput({
73
+ hook_event_name: 'UserPromptSubmit',
74
+ collaboration_mode_kind: 'plan',
75
+ cwd: 'C:/project',
76
+ });
77
+ assert.strictEqual(isPlanMode(normalized), true);
78
+ });
79
+ });
80
+ describe('findCodexTranscriptPath', () => {
81
+ it('should reject invalid session ids', () => {
82
+ assert.strictEqual(findCodexTranscriptPath('../escape'), null);
83
+ assert.strictEqual(findCodexTranscriptPath(''), null);
84
+ });
85
+ it('should find an existing local rollout transcript when present', () => {
86
+ const sessionId = '019e85cb-a131-7760-9a30-f164fcf83073';
87
+ const expected = join(homedir(), '.codex', 'sessions', '2026', '06', '02', `rollout-2026-06-02T09-46-15-${sessionId}.jsonl`);
88
+ const found = findCodexTranscriptPath(sessionId);
89
+ if (found) {
90
+ assert.strictEqual(found, expected);
91
+ }
92
+ });
93
+ });
94
+ describe('extractPlanMarkdownFromCodexTranscript', () => {
95
+ it('should return null for missing files', () => {
96
+ assert.strictEqual(extractPlanMarkdownFromCodexTranscript('C:/missing/transcript.jsonl'), null);
97
+ });
98
+ });
99
+ describe('determineProjectRoot', () => {
100
+ // determineProjectRoot uses path.isAbsolute(), which is platform-specific:
101
+ // 'C:/...' is absolute only on win32. Build paths that are absolute on the
102
+ // current platform so the precedence assertions hold on Linux CI too.
103
+ const absRoot = (p) => (process.platform === 'win32' ? `C:${p}` : p);
104
+ it('should prefer CODEX_CWD over SQLEW_PROJECT_ROOT', () => {
105
+ const codexRoot = absRoot('/codex-workspace');
106
+ process.env.CODEX_CWD = codexRoot;
107
+ process.env.SQLEW_PROJECT_ROOT = absRoot('/other');
108
+ assert.strictEqual(determineProjectRoot({}), codexRoot);
109
+ });
110
+ });
111
+ });
112
+ //# sourceMappingURL=codex-hook-normalization.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-hook-normalization.test.js","sourceRoot":"","sources":["../../../../src/tests/unit/hooks/codex-hook-normalization.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EACL,kBAAkB,EAClB,UAAU,GACX,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,uBAAuB,EACvB,sCAAsC,GACvC,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAEtE,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,MAAM,WAAW,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAEvC,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;QACpC,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;QAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QACnC,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QACvC,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QACtC,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,CAAC,GAAG,GAAG,EAAE,GAAG,WAAW,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,MAAM,GAAG,kBAAkB,CAAC;gBAChC,eAAe,EAAE,YAAY;gBAC7B,SAAS,EAAE,eAAe;gBAC1B,GAAG,EAAE,YAAY;aAClB,CAAC,CAAC;YACH,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAC7C,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,MAAM,GAAG,kBAAkB,CAAC;gBAChC,eAAe,EAAE,aAAa;gBAC9B,SAAS,EAAE,aAAa;gBACxB,GAAG,EAAE,YAAY;aAClB,CAAC,CAAC;YACH,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;YACpE,MAAM,MAAM,GAAG,kBAAkB,CAAC;gBAChC,eAAe,EAAE,kBAAkB;gBACnC,kBAAkB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;gBACpC,GAAG,EAAE,YAAY;gBACjB,UAAU,EAAE,cAAc;aAC3B,CAAC,CAAC;YACH,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;YACtD,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,QAAQ,CAAC;YACxC,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,YAAY,CAAC;YAErC,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAAC;YACtC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YAChD,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;YAC9D,MAAM,CAAC,WAAW,CAChB,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,CAAC,EAC3D,IAAI,CACL,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,UAAU,GAAG,kBAAkB,CAAC;gBACpC,eAAe,EAAE,kBAAkB;gBACnC,uBAAuB,EAAE,MAAM;gBAC/B,GAAG,EAAE,YAAY;aAClB,CAAC,CAAC;YACH,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACvC,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;YAC3C,MAAM,CAAC,WAAW,CAAC,uBAAuB,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,CAAC;YAC/D,MAAM,CAAC,WAAW,CAAC,uBAAuB,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;YACvE,MAAM,SAAS,GAAG,sCAAsC,CAAC;YACzD,MAAM,QAAQ,GAAG,IAAI,CACnB,OAAO,EAAE,EACT,QAAQ,EACR,UAAU,EACV,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,+BAA+B,SAAS,QAAQ,CACjD,CAAC;YACF,MAAM,KAAK,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;YACjD,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACtC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACtD,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC9C,MAAM,CAAC,WAAW,CAChB,sCAAsC,CAAC,6BAA6B,CAAC,EACrE,IAAI,CACL,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpC,2EAA2E;QAC3E,2EAA2E;QAC3E,sEAAsE;QACtE,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAErF,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;YACzD,MAAM,SAAS,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,kBAAkB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;YACnD,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Grok Build Hook Normalization Unit Tests
3
+ *
4
+ * Tests normalizeHookInput() and computeGrokPlanPath() for Grok Build compatibility.
5
+ *
6
+ * @since v5.2.0
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=grok-hook-normalization.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grok-hook-normalization.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/unit/hooks/grok-hook-normalization.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Grok Build Hook Normalization Unit Tests
3
+ *
4
+ * Tests normalizeHookInput() and computeGrokPlanPath() for Grok Build compatibility.
5
+ *
6
+ * @since v5.2.0
7
+ */
8
+ import { describe, it, beforeEach, afterEach } from 'node:test';
9
+ import assert from 'node:assert';
10
+ import { homedir } from 'os';
11
+ import { join } from 'path';
12
+ import { normalizeHookInput, computeGrokPlanPath, isPlanMode, } from '../../../cli/hooks/stdin-parser.js';
13
+ import { determineProjectRoot } from '../../../utils/project-root.js';
14
+ describe('grok-hook-normalization', () => {
15
+ const originalEnv = { ...process.env };
16
+ beforeEach(() => {
17
+ delete process.env.GROK_HOOK_EVENT;
18
+ delete process.env.GROK_WORKSPACE_ROOT;
19
+ delete process.env.GROK_SESSION_ID;
20
+ delete process.env.CLAUDE_PROJECT_DIR;
21
+ delete process.env.SQLEW_PROJECT_ROOT;
22
+ });
23
+ afterEach(() => {
24
+ process.env = { ...originalEnv };
25
+ });
26
+ describe('normalizeHookInput', () => {
27
+ it('should pass through Claude payloads unchanged', () => {
28
+ const input = {
29
+ hook_event_name: 'PostToolUse',
30
+ tool_name: 'ExitPlanMode',
31
+ cwd: 'C:/project',
32
+ session_id: 'abc-123',
33
+ };
34
+ const result = normalizeHookInput(input);
35
+ assert.strictEqual(result.hook_event_name, 'PostToolUse');
36
+ assert.strictEqual(result.tool_name, 'ExitPlanMode');
37
+ assert.strictEqual(result.client, undefined);
38
+ });
39
+ it('should normalize Grok camelCase payload to Claude shape', () => {
40
+ const result = normalizeHookInput({
41
+ hookEventName: 'post_tool_use',
42
+ toolName: 'exit_plan_mode',
43
+ sessionId: '019eb585-d6ec-7ae0-992a-d60340763a20',
44
+ workspaceRoot: 'C:\\Users\\kitayama\\RustroverProjects\\mcp-sqlew',
45
+ });
46
+ assert.strictEqual(result.client, 'grok');
47
+ assert.strictEqual(result.hook_event_name, 'PostToolUse');
48
+ assert.strictEqual(result.tool_name, 'ExitPlanMode');
49
+ assert.strictEqual(result.session_id, '019eb585-d6ec-7ae0-992a-d60340763a20');
50
+ assert.strictEqual(result.cwd, 'C:\\Users\\kitayama\\RustroverProjects\\mcp-sqlew');
51
+ });
52
+ it('should map enter_plan_mode PreToolUse event', () => {
53
+ const result = normalizeHookInput({
54
+ hookEventName: 'pre_tool_use',
55
+ toolName: 'enter_plan_mode',
56
+ workspaceRoot: 'C:/project',
57
+ });
58
+ assert.strictEqual(result.hook_event_name, 'PreToolUse');
59
+ assert.strictEqual(result.tool_name, 'EnterPlanMode');
60
+ });
61
+ it('should use env vars when stdin is empty', () => {
62
+ process.env.GROK_HOOK_EVENT = 'post_tool_use';
63
+ process.env.GROK_WORKSPACE_ROOT = 'C:/project';
64
+ process.env.GROK_SESSION_ID = 'sess-1';
65
+ const result = normalizeHookInput({});
66
+ assert.strictEqual(result.client, 'grok');
67
+ assert.strictEqual(result.hook_event_name, 'PostToolUse');
68
+ assert.strictEqual(result.cwd, 'C:/project');
69
+ assert.strictEqual(result.session_id, 'sess-1');
70
+ });
71
+ });
72
+ describe('isPlanMode', () => {
73
+ it('should detect Claude plan mode via permission_mode', () => {
74
+ assert.strictEqual(isPlanMode({ hook_event_name: 'UserPromptSubmit', permission_mode: 'plan' }), true);
75
+ });
76
+ it('should return false for Claude non-plan prompts', () => {
77
+ assert.strictEqual(isPlanMode({ hook_event_name: 'UserPromptSubmit', permission_mode: 'default' }), false);
78
+ });
79
+ it('should detect Grok plan mode after normalization (enter_plan_mode)', () => {
80
+ // Regression: normalizeHookInput maps enter_plan_mode -> EnterPlanMode,
81
+ // so isPlanMode must match the normalized PascalCase name.
82
+ const normalized = normalizeHookInput({
83
+ hookEventName: 'pre_tool_use',
84
+ toolName: 'enter_plan_mode',
85
+ workspaceRoot: 'C:/project',
86
+ });
87
+ assert.strictEqual(isPlanMode(normalized), true);
88
+ });
89
+ it('should detect Grok plan mode after normalization (exit_plan_mode)', () => {
90
+ const normalized = normalizeHookInput({
91
+ hookEventName: 'post_tool_use',
92
+ toolName: 'exit_plan_mode',
93
+ workspaceRoot: 'C:/project',
94
+ });
95
+ assert.strictEqual(isPlanMode(normalized), true);
96
+ });
97
+ it('should accept raw snake_case tool names defensively', () => {
98
+ assert.strictEqual(isPlanMode({ tool_name: 'enter_plan_mode' }), true);
99
+ });
100
+ it('should return false for unrelated tools', () => {
101
+ assert.strictEqual(isPlanMode({ tool_name: 'Bash' }), false);
102
+ });
103
+ });
104
+ describe('computeGrokPlanPath', () => {
105
+ it('should encode Windows workspace path for session directory', () => {
106
+ const workspace = 'C:\\Users\\kitayama\\RustroverProjects\\mcp-sqlew';
107
+ const sessionId = '019eb585-d6ec-7ae0-992a-d60340763a20';
108
+ const planPath = computeGrokPlanPath(workspace, sessionId);
109
+ const encoded = encodeURIComponent(workspace);
110
+ assert.strictEqual(planPath, join(homedir(), '.grok', 'sessions', encoded, sessionId, 'plan.md'));
111
+ });
112
+ it('should reject path traversal in sessionId', () => {
113
+ assert.strictEqual(computeGrokPlanPath('C:/project', '../escape'), null);
114
+ assert.strictEqual(computeGrokPlanPath('C:/project', ''), null);
115
+ });
116
+ });
117
+ describe('determineProjectRoot', () => {
118
+ // determineProjectRoot uses path.isAbsolute(), which is platform-specific:
119
+ // 'C:/...' is absolute only on win32. Build paths that are absolute on the
120
+ // current platform so the precedence assertions hold on Linux CI too.
121
+ const absRoot = (p) => (process.platform === 'win32' ? `C:${p}` : p);
122
+ it('should prefer GROK_WORKSPACE_ROOT over SQLEW_PROJECT_ROOT', () => {
123
+ const grokRoot = absRoot('/grok-workspace');
124
+ process.env.GROK_WORKSPACE_ROOT = grokRoot;
125
+ process.env.SQLEW_PROJECT_ROOT = absRoot('/other');
126
+ assert.strictEqual(determineProjectRoot({}), grokRoot);
127
+ });
128
+ it('should prefer CLAUDE_PROJECT_DIR over GROK_WORKSPACE_ROOT', () => {
129
+ const claudeRoot = absRoot('/claude-project');
130
+ process.env.CLAUDE_PROJECT_DIR = claudeRoot;
131
+ process.env.GROK_WORKSPACE_ROOT = absRoot('/grok-workspace');
132
+ assert.strictEqual(determineProjectRoot({}), claudeRoot);
133
+ });
134
+ });
135
+ });
136
+ //# sourceMappingURL=grok-hook-normalization.test.js.map