@lumenflow/shims 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.
package/LICENSE ADDED
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to the Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright 2026 HellmAI
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @lumenflow/shims
2
+
3
+ Git and pnpm safety shims for LumenFlow worktree discipline.
4
+
5
+ ## Overview
6
+
7
+ This package provides command shims that:
8
+
9
+ 1. **Git shim** - Blocks destructive git commands on main branch/worktree
10
+ 2. **Pnpm shim** - Fixes worktree compatibility for dependency commands
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @lumenflow/shims
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Git Shim
21
+
22
+ The git shim prevents destructive commands on protected branches (default: main).
23
+
24
+ **Blocked commands on main:**
25
+
26
+ - git reset with hard flag
27
+ - git stash (any form)
28
+ - git clean with fd flags
29
+ - git checkout with force flag
30
+ - git push with force flag
31
+ - Hook bypass flags
32
+
33
+ These commands are **allowed** in lane worktrees (safe, isolated context).
34
+
35
+ ```typescript
36
+ import { runGitShim, checkBannedPattern, GitShimConfigSchema } from '@lumenflow/shims';
37
+
38
+ // Run as CLI
39
+ runGitShim(process.argv.slice(2));
40
+
41
+ // Check if a command pattern is banned
42
+ const result = checkBannedPattern(['reset', '--hard']);
43
+ if (result.banned) {
44
+ console.error(result.reason);
45
+ }
46
+
47
+ // Custom configuration
48
+ const config = GitShimConfigSchema.parse({
49
+ protectedBranch: 'develop',
50
+ bannedPatterns: [{ command: 'rebase' }],
51
+ });
52
+ ```
53
+
54
+ ### Pnpm Shim
55
+
56
+ The pnpm shim fixes ERR_PNPM_UNEXPECTED_VIRTUAL_STORE errors in git worktrees.
57
+
58
+ ```typescript
59
+ import { runPnpmShim, isDependencyCommand, PnpmShimConfigSchema } from '@lumenflow/shims';
60
+
61
+ // Run as CLI
62
+ runPnpmShim(process.argv.slice(2));
63
+
64
+ // Check if command modifies dependencies
65
+ isDependencyCommand(['add', 'zod']); // true
66
+ isDependencyCommand(['run', 'test']); // false
67
+ ```
68
+
69
+ ### Worktree Detection
70
+
71
+ ```typescript
72
+ import {
73
+ isInWorktree,
74
+ isMainWorktree,
75
+ getMainCheckoutPath,
76
+ getCurrentBranch,
77
+ } from '@lumenflow/shims';
78
+
79
+ // Check worktree context
80
+ if (isInWorktree()) {
81
+ console.log('Running in a git worktree');
82
+ }
83
+
84
+ if (isMainWorktree()) {
85
+ console.log('Running in main checkout (protected)');
86
+ }
87
+
88
+ // Get main checkout path (useful for computing virtual store)
89
+ const mainPath = getMainCheckoutPath();
90
+
91
+ // Get current branch
92
+ const branch = getCurrentBranch();
93
+ ```
94
+
95
+ ## Configuration
96
+
97
+ Both shims are configurable via Zod schemas.
98
+
99
+ ### GitShimConfig
100
+
101
+ | Option | Type | Default | Description |
102
+ | --------------- | -------- | ------------ | --------------------------------------------- |
103
+ | protectedBranch | string | main | Branch where destructive commands are blocked |
104
+ | bannedPatterns | array | See defaults | Command + flags combinations to block |
105
+ | bannedFlags | string[] | See defaults | Flags blocked on any command |
106
+ | realGitPath | string | /usr/bin/git | Path to real git executable |
107
+ | enableLogging | boolean | false | Enable command logging |
108
+ | logPath | string | undefined | Path to log file |
109
+ | recursionEnvVar | string | auto | Env var for recursion guard |
110
+ | agentEnvVars | string[] | See defaults | Env vars indicating agent context |
111
+
112
+ ### PnpmShimConfig
113
+
114
+ | Option | Type | Default | Description |
115
+ | ------------------ | -------- | ------------------------- | --------------------------------- |
116
+ | dependencyCommands | string[] | add, remove, install, etc | Commands that modify dependencies |
117
+ | systemPnpmPaths | string[] | System paths | Paths to search for real pnpm |
118
+ | recursionEnvVar | string | auto | Env var for recursion guard |
119
+ | enableDebug | boolean | false | Enable debug output |
120
+
121
+ ## CLI Usage
122
+
123
+ The package exports bin commands that can be used as shims:
124
+
125
+ ```bash
126
+ # Add to PATH before system paths
127
+ export PATH="./node_modules/.bin:$PATH"
128
+
129
+ # Now git/pnpm commands are intercepted
130
+ git reset --hard # Blocked on main
131
+ pnpm add zod # Fixed in worktrees
132
+ ```
133
+
134
+ ## License
135
+
136
+ Apache-2.0
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @lumenflow/shims - Git Command Shim (WU-2546)
4
+ *
5
+ * Intercepts git commands and blocks destructive operations on main branch/worktree.
6
+ *
7
+ * Usage: Add shims directory to PATH before /usr/bin
8
+ * export PATH="$(pwd)/node_modules/.bin:$PATH"
9
+ *
10
+ * Blocked commands on main:
11
+ * - git reset --hard
12
+ * - git stash (any form)
13
+ * - git clean -fd
14
+ * - git checkout -f
15
+ * - git push --force / -f
16
+ * - hook bypass flags
17
+ *
18
+ * These commands are ALLOWED on lane branches in worktrees (safe context).
19
+ *
20
+ * @module @lumenflow/shims/git
21
+ */
22
+ import type { GitShimConfig, BannedPatternResult, ProtectedContextResult } from './types.js';
23
+ /**
24
+ * Detect user type based on environment variables.
25
+ *
26
+ * @param config - Git shim configuration
27
+ * @returns User type: 'agent', 'human', or 'unknown'
28
+ */
29
+ export declare function detectUserType(config?: GitShimConfig): string;
30
+ /**
31
+ * Check if arguments contain a banned command pattern.
32
+ *
33
+ * @param args - Git command arguments
34
+ * @param config - Git shim configuration
35
+ * @returns Object with banned status and reason
36
+ */
37
+ export declare function checkBannedPattern(args: string[], config?: GitShimConfig): BannedPatternResult;
38
+ /**
39
+ * Check if we're in a protected context (main branch or main worktree).
40
+ *
41
+ * @param config - Git shim configuration
42
+ * @returns Object with protected status and context description
43
+ */
44
+ export declare function checkProtectedContext(config?: GitShimConfig): ProtectedContextResult;
45
+ /**
46
+ * Format error message for blocked command.
47
+ *
48
+ * @param command - The blocked command
49
+ * @param reason - Why it was blocked
50
+ * @param context - Where it was blocked
51
+ * @returns Formatted error message
52
+ */
53
+ export declare function formatBlockedError(command: string, reason: string, context: string): string;
54
+ /**
55
+ * Find real git executable.
56
+ *
57
+ * @param preferredPath - Preferred git path from config
58
+ * @returns Path to real git
59
+ */
60
+ export declare function findRealGit(preferredPath?: string): string;
61
+ /**
62
+ * Run the git shim with given configuration.
63
+ *
64
+ * @param args - Git command arguments
65
+ * @param config - Git shim configuration
66
+ * @returns Exit code
67
+ */
68
+ export declare function runGitShim(args: string[], config?: GitShimConfig): number;
69
+ /**
70
+ * Main entry point for CLI execution.
71
+ */
72
+ export declare function main(): void;
73
+ //# sourceMappingURL=git-shim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-shim.d.ts","sourceRoot":"","sources":["../src/git-shim.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAiC7F;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,aAA8B,GAAG,MAAM,CAO7E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,GAAE,aAA8B,GACrC,mBAAmB,CAqCrB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,GAAE,aAA8B,GACrC,sBAAsB,CAqBxB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAwB3F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,aAAa,GAAE,MAAuB,GAAG,MAAM,CAe1E;AAcD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,GAAE,aAA8B,GAAG,MAAM,CAmDzF;AAED;;GAEG;AACH,wBAAgB,IAAI,IAAI,IAAI,CAI3B"}
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @lumenflow/shims - Git Command Shim (WU-2546)
4
+ *
5
+ * Intercepts git commands and blocks destructive operations on main branch/worktree.
6
+ *
7
+ * Usage: Add shims directory to PATH before /usr/bin
8
+ * export PATH="$(pwd)/node_modules/.bin:$PATH"
9
+ *
10
+ * Blocked commands on main:
11
+ * - git reset --hard
12
+ * - git stash (any form)
13
+ * - git clean -fd
14
+ * - git checkout -f
15
+ * - git push --force / -f
16
+ * - hook bypass flags
17
+ *
18
+ * These commands are ALLOWED on lane branches in worktrees (safe context).
19
+ *
20
+ * @module @lumenflow/shims/git
21
+ */
22
+ import { spawnSync } from 'node:child_process';
23
+ import { GitShimConfigSchema, UserType, CommandOutcome } from './types.js';
24
+ import { getCurrentBranch, isMainWorktree } from './worktree.js';
25
+ /**
26
+ * Default configuration.
27
+ */
28
+ const DEFAULT_CONFIG = GitShimConfigSchema.parse({});
29
+ /**
30
+ * Build context string for protected/unprotected state.
31
+ *
32
+ * @param isProtected - Whether context is protected
33
+ * @param inMainWorktree - Whether in main worktree
34
+ * @param protectedBranch - Name of protected branch
35
+ * @param branch - Current branch name
36
+ * @returns Context description string
37
+ */
38
+ function buildContextString(isProtected, inMainWorktree, protectedBranch, branch) {
39
+ if (isProtected) {
40
+ if (inMainWorktree) {
41
+ return 'main worktree';
42
+ }
43
+ return `${protectedBranch} branch`;
44
+ }
45
+ return `${branch} branch in lane worktree`;
46
+ }
47
+ /**
48
+ * Detect user type based on environment variables.
49
+ *
50
+ * @param config - Git shim configuration
51
+ * @returns User type: 'agent', 'human', or 'unknown'
52
+ */
53
+ export function detectUserType(config = DEFAULT_CONFIG) {
54
+ for (const envVar of config.agentEnvVars) {
55
+ if (process.env[envVar]) {
56
+ return UserType.AGENT;
57
+ }
58
+ }
59
+ return UserType.HUMAN;
60
+ }
61
+ /**
62
+ * Check if arguments contain a banned command pattern.
63
+ *
64
+ * @param args - Git command arguments
65
+ * @param config - Git shim configuration
66
+ * @returns Object with banned status and reason
67
+ */
68
+ export function checkBannedPattern(args, config = DEFAULT_CONFIG) {
69
+ const command = args[0]?.toLowerCase();
70
+ const flags = args.slice(1).map((a) => a.toLowerCase());
71
+ // Check banned flags first (apply to any command)
72
+ for (const bannedFlag of config.bannedFlags) {
73
+ if (flags.includes(bannedFlag)) {
74
+ return {
75
+ banned: true,
76
+ reason: `Flag ${bannedFlag} bypasses hooks and is forbidden`,
77
+ };
78
+ }
79
+ }
80
+ // Check banned command patterns
81
+ for (const pattern of config.bannedPatterns) {
82
+ if (command !== pattern.command)
83
+ continue;
84
+ // If no specific flags required, ban the command entirely
85
+ if (!pattern.flags) {
86
+ return {
87
+ banned: true,
88
+ reason: `Command "git ${command}" is destructive and forbidden on main`,
89
+ };
90
+ }
91
+ // Check if any required flag is present
92
+ const hasRequiredFlag = pattern.flags.some((reqFlag) => flags.includes(reqFlag));
93
+ if (hasRequiredFlag) {
94
+ return {
95
+ banned: true,
96
+ reason: `Command "git ${args.join(' ')}" is destructive and forbidden on main`,
97
+ };
98
+ }
99
+ }
100
+ return { banned: false, reason: null };
101
+ }
102
+ /**
103
+ * Check if we're in a protected context (main branch or main worktree).
104
+ *
105
+ * @param config - Git shim configuration
106
+ * @returns Object with protected status and context description
107
+ */
108
+ export function checkProtectedContext(config = DEFAULT_CONFIG) {
109
+ // Allow TEST_MODE for integration tests
110
+ if (process.env['TEST_MODE'] === 'true') {
111
+ const branch = process.env['TEST_BRANCH'] || config.protectedBranch;
112
+ const inMainWorktree = process.env['TEST_IS_MAIN_WORKTREE'] === 'true';
113
+ const isProtectedBranch = branch === config.protectedBranch;
114
+ const isProtected = isProtectedBranch || inMainWorktree;
115
+ const context = buildContextString(isProtected, inMainWorktree, config.protectedBranch, branch);
116
+ return { protected: isProtected, context };
117
+ }
118
+ const branch = getCurrentBranch(config.realGitPath);
119
+ const inMainWorktree = isMainWorktree(config.realGitPath);
120
+ // Protected if:
121
+ // 1. On protected branch (regardless of worktree)
122
+ // 2. OR in main worktree (even if on a lane branch)
123
+ const isProtected = branch === config.protectedBranch || inMainWorktree;
124
+ const context = buildContextString(isProtected, inMainWorktree, config.protectedBranch, branch);
125
+ return { protected: isProtected, context };
126
+ }
127
+ /**
128
+ * Format error message for blocked command.
129
+ *
130
+ * @param command - The blocked command
131
+ * @param reason - Why it was blocked
132
+ * @param context - Where it was blocked
133
+ * @returns Formatted error message
134
+ */
135
+ export function formatBlockedError(command, reason, context) {
136
+ return `
137
+ ╔═══════════════════════════════════════════════════════════════════╗
138
+ ║ GIT SHIM HOOK ERROR
139
+ ╠═══════════════════════════════════════════════════════════════════╣
140
+
141
+ ║ Blocked: git ${command}
142
+
143
+ ║ ${reason}
144
+
145
+ ║ Context: ${context}
146
+
147
+ ║ Why blocked:
148
+ ║ This command could destroy uncommitted work from other agents
149
+ ║ working on parallel WUs in their own worktrees.
150
+
151
+ ║ Correct workflow:
152
+ ║ 1. Claim a WU: pnpm wu:claim --id WU-XXX --lane <lane>
153
+ ║ 2. Work in worktree: cd worktrees/<lane>-wu-xxx
154
+ ║ 3. Make changes, commit, push: git add . && git commit && git push
155
+ ║ 4. Complete: pnpm wu:done --id WU-XXX (run from main directory)
156
+
157
+ ╚═══════════════════════════════════════════════════════════════════╝
158
+ `;
159
+ }
160
+ /**
161
+ * Find real git executable.
162
+ *
163
+ * @param preferredPath - Preferred git path from config
164
+ * @returns Path to real git
165
+ */
166
+ export function findRealGit(preferredPath = '/usr/bin/git') {
167
+ const gitPaths = [preferredPath, '/usr/bin/git', '/usr/local/bin/git', '/opt/homebrew/bin/git'];
168
+ for (const gitPath of gitPaths) {
169
+ try {
170
+ const result = spawnSync(gitPath, ['--version'], { encoding: 'utf8' });
171
+ if (result.status === 0) {
172
+ return gitPath;
173
+ }
174
+ }
175
+ catch {
176
+ // Try next path
177
+ }
178
+ }
179
+ return 'git';
180
+ }
181
+ /**
182
+ * Log command execution for audit trail.
183
+ *
184
+ * @param _user - User type (agent/human)
185
+ * @param _outcome - Command outcome (allowed/blocked)
186
+ * @param _config - Git shim configuration
187
+ */
188
+ function logCommand(_user, _outcome, _config) {
189
+ // Logging would go here - simplified for extraction
190
+ // Future: write to config.logPath
191
+ }
192
+ /**
193
+ * Run the git shim with given configuration.
194
+ *
195
+ * @param args - Git command arguments
196
+ * @param config - Git shim configuration
197
+ * @returns Exit code
198
+ */
199
+ export function runGitShim(args, config = DEFAULT_CONFIG) {
200
+ // Recursion guard
201
+ if (process.env[config.recursionEnvVar]) {
202
+ const result = spawnSync(config.realGitPath, args, {
203
+ stdio: 'inherit',
204
+ encoding: 'utf8',
205
+ });
206
+ return result.status || 0;
207
+ }
208
+ process.env[config.recursionEnvVar] = '1';
209
+ // Detect user type for audit trail
210
+ const user = detectUserType(config);
211
+ // Check if we're in a protected context
212
+ const { protected: isProtected, context } = checkProtectedContext(config);
213
+ let outcome = CommandOutcome.ALLOWED;
214
+ if (isProtected) {
215
+ const { banned, reason } = checkBannedPattern(args, config);
216
+ if (banned && reason) {
217
+ outcome = CommandOutcome.BLOCKED;
218
+ // Log blocked command if logging enabled
219
+ if (config.enableLogging && config.logPath) {
220
+ logCommand(user, outcome, config);
221
+ }
222
+ // Block the command
223
+ const command = args.join(' ');
224
+ const errorMsg = formatBlockedError(command, reason, context);
225
+ console.error(errorMsg);
226
+ return 1;
227
+ }
228
+ }
229
+ // Log allowed command if logging enabled
230
+ if (config.enableLogging && config.logPath) {
231
+ logCommand(user, outcome, config);
232
+ }
233
+ // Pass through to real git
234
+ const realGit = findRealGit(config.realGitPath);
235
+ const result = spawnSync(realGit, args, {
236
+ stdio: 'inherit',
237
+ encoding: 'utf8',
238
+ });
239
+ return result.status || 0;
240
+ }
241
+ /**
242
+ * Main entry point for CLI execution.
243
+ */
244
+ export function main() {
245
+ const args = process.argv.slice(2);
246
+ const exitCode = runGitShim(args);
247
+ process.exit(exitCode);
248
+ }
249
+ // Run if executed directly
250
+ const currentUrl = import.meta.url;
251
+ const scriptPath = `file://${process.argv[1]}`;
252
+ if (currentUrl === scriptPath) {
253
+ main();
254
+ }
255
+ //# sourceMappingURL=git-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-shim.js","sourceRoot":"","sources":["../src/git-shim.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @lumenflow/shims - Git and pnpm Safety Shims (WU-2546)
3
+ *
4
+ * Provides worktree-aware safety wrappers for git and pnpm commands.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export declare const VERSION = "0.0.0";
9
+ export * from './types.js';
10
+ export * from './worktree.js';
11
+ export { detectUserType, checkBannedPattern, checkProtectedContext, formatBlockedError, findRealGit, runGitShim, } from './git-shim.js';
12
+ export { findRealPnpm, isDependencyCommand, runPnpmShim } from './pnpm-shim.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,OAAO,UAAU,CAAC;AAG/B,cAAc,YAAY,CAAC;AAG3B,cAAc,eAAe,CAAC;AAG9B,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACX,UAAU,GACX,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @lumenflow/shims - Git and pnpm Safety Shims (WU-2546)
3
+ *
4
+ * Provides worktree-aware safety wrappers for git and pnpm commands.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export const VERSION = '0.0.0';
9
+ // Re-export types
10
+ export * from './types.js';
11
+ // Re-export worktree utilities
12
+ export * from './worktree.js';
13
+ // Re-export git shim functions
14
+ export { detectUserType, checkBannedPattern, checkProtectedContext, formatBlockedError, findRealGit, runGitShim, } from './git-shim.js';
15
+ // Re-export pnpm shim functions
16
+ export { findRealPnpm, isDependencyCommand, runPnpmShim } from './pnpm-shim.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,kBAAkB;AAClB,cAAc,YAAY,CAAC;AAE3B,+BAA+B;AAC/B,cAAc,eAAe,CAAC;AAE9B,+BAA+B;AAC/B,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACX,UAAU,GACX,MAAM,eAAe,CAAC;AAEvB,gCAAgC;AAChC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @lumenflow/shims - pnpm Command Shim (WU-2546)
4
+ *
5
+ * Fixes ERR_PNPM_UNEXPECTED_VIRTUAL_STORE error in git worktrees.
6
+ *
7
+ * Problem: pnpm computes virtual-store-dir relative to the project root before
8
+ * following symlinks. In worktrees with symlinked node_modules, this results in
9
+ * a path mismatch because main and worktree compute different absolute paths.
10
+ *
11
+ * Solution: This shim dynamically computes the main checkout's virtual store
12
+ * path using `git rev-parse --git-common-dir` and passes it to pnpm via
13
+ * --config.virtual-store-dir.
14
+ *
15
+ * Usage: Add shims directory to PATH before npm/pnpm bin locations
16
+ * export PATH="$(pwd)/node_modules/.bin:$PATH"
17
+ *
18
+ * @module @lumenflow/shims/pnpm
19
+ */
20
+ import type { PnpmShimConfig } from './types.js';
21
+ /**
22
+ * Find real pnpm executable (after removing shims from PATH).
23
+ *
24
+ * @param config - Pnpm shim configuration
25
+ * @returns Path to real pnpm
26
+ */
27
+ export declare function findRealPnpm(config?: PnpmShimConfig): string;
28
+ /**
29
+ * Check if the command modifies dependencies.
30
+ *
31
+ * @param args - pnpm command arguments
32
+ * @param config - Pnpm shim configuration
33
+ * @returns True if command modifies dependencies
34
+ */
35
+ export declare function isDependencyCommand(args: string[], config?: PnpmShimConfig): boolean;
36
+ /**
37
+ * Run the pnpm shim with given configuration.
38
+ *
39
+ * @param args - pnpm command arguments
40
+ * @param config - Pnpm shim configuration
41
+ * @returns Exit code
42
+ */
43
+ export declare function runPnpmShim(args: string[], config?: PnpmShimConfig): number;
44
+ /**
45
+ * Main entry point for CLI execution.
46
+ */
47
+ export declare function main(): void;
48
+ //# sourceMappingURL=pnpm-shim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pnpm-shim.d.ts","sourceRoot":"","sources":["../src/pnpm-shim.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AASjD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,cAA+B,GAAG,MAAM,CAsC5E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,GAAE,cAA+B,GACtC,OAAO,CAGT;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,GAAE,cAA+B,GAAG,MAAM,CA+C3F;AAED;;GAEG;AACH,wBAAgB,IAAI,IAAI,IAAI,CAI3B"}
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @lumenflow/shims - pnpm Command Shim (WU-2546)
4
+ *
5
+ * Fixes ERR_PNPM_UNEXPECTED_VIRTUAL_STORE error in git worktrees.
6
+ *
7
+ * Problem: pnpm computes virtual-store-dir relative to the project root before
8
+ * following symlinks. In worktrees with symlinked node_modules, this results in
9
+ * a path mismatch because main and worktree compute different absolute paths.
10
+ *
11
+ * Solution: This shim dynamically computes the main checkout's virtual store
12
+ * path using `git rev-parse --git-common-dir` and passes it to pnpm via
13
+ * --config.virtual-store-dir.
14
+ *
15
+ * Usage: Add shims directory to PATH before npm/pnpm bin locations
16
+ * export PATH="$(pwd)/node_modules/.bin:$PATH"
17
+ *
18
+ * @module @lumenflow/shims/pnpm
19
+ */
20
+ import { spawnSync, execSync } from 'node:child_process';
21
+ import path from 'node:path';
22
+ import { PnpmShimConfigSchema } from './types.js';
23
+ import { isInWorktree, getMainCheckoutPath } from './worktree.js';
24
+ /**
25
+ * Default configuration.
26
+ */
27
+ const DEFAULT_CONFIG = PnpmShimConfigSchema.parse({});
28
+ /**
29
+ * Find real pnpm executable (after removing shims from PATH).
30
+ *
31
+ * @param config - Pnpm shim configuration
32
+ * @returns Path to real pnpm
33
+ */
34
+ export function findRealPnpm(config = DEFAULT_CONFIG) {
35
+ // Build list of candidate paths: system paths + user home paths
36
+ const userHomePaths = [
37
+ path.join(process.env['HOME'] || '', '.local', 'share', 'pnpm', 'pnpm'),
38
+ path.join(process.env['HOME'] || '', '.pnpm-home', 'pnpm'),
39
+ ];
40
+ const candidatePaths = [...config.systemPnpmPaths, ...userHomePaths];
41
+ for (const pnpmPath of candidatePaths) {
42
+ try {
43
+ const result = spawnSync(pnpmPath, ['--version'], {
44
+ encoding: 'utf8',
45
+ stdio: ['pipe', 'pipe', 'pipe'],
46
+ });
47
+ if (result.status === 0) {
48
+ return pnpmPath;
49
+ }
50
+ }
51
+ catch {
52
+ // Try next path
53
+ }
54
+ }
55
+ // Fallback: use which to find pnpm (exclude our shim)
56
+ try {
57
+ const pathWithoutShims = (process.env['PATH'] || '')
58
+ .split(':')
59
+ .filter((p) => !p.includes('tools/shims') && !p.includes('@lumenflow/shims'))
60
+ .join(':');
61
+ const result = execSync('which pnpm', {
62
+ encoding: 'utf8',
63
+ env: { ...process.env, PATH: pathWithoutShims },
64
+ stdio: ['pipe', 'pipe', 'pipe'],
65
+ });
66
+ return result.trim();
67
+ }
68
+ catch {
69
+ return 'pnpm';
70
+ }
71
+ }
72
+ /**
73
+ * Check if the command modifies dependencies.
74
+ *
75
+ * @param args - pnpm command arguments
76
+ * @param config - Pnpm shim configuration
77
+ * @returns True if command modifies dependencies
78
+ */
79
+ export function isDependencyCommand(args, config = DEFAULT_CONFIG) {
80
+ const command = args[0]?.toLowerCase() ?? '';
81
+ return config.dependencyCommands.includes(command);
82
+ }
83
+ /**
84
+ * Run the pnpm shim with given configuration.
85
+ *
86
+ * @param args - pnpm command arguments
87
+ * @param config - Pnpm shim configuration
88
+ * @returns Exit code
89
+ */
90
+ export function runPnpmShim(args, config = DEFAULT_CONFIG) {
91
+ // Recursion guard
92
+ if (process.env[config.recursionEnvVar]) {
93
+ const realPnpm = findRealPnpm(config);
94
+ const result = spawnSync(realPnpm, args, {
95
+ stdio: 'inherit',
96
+ encoding: 'utf8',
97
+ });
98
+ return result.status || 0;
99
+ }
100
+ process.env[config.recursionEnvVar] = '1';
101
+ // Only apply fix for dependency commands in worktrees
102
+ if (isInWorktree() && isDependencyCommand(args, config)) {
103
+ const mainCheckout = getMainCheckoutPath();
104
+ if (mainCheckout) {
105
+ const virtualStorePath = path.join(mainCheckout, 'node_modules', '.pnpm');
106
+ // Inject virtual-store-dir config before passing to pnpm
107
+ const newArgs = [`--config.virtual-store-dir=${virtualStorePath}`, ...args];
108
+ // Log that we're applying the worktree fix (helpful for debugging)
109
+ if (config.enableDebug || process.env['DEBUG_PNPM_SHIM']) {
110
+ console.error(`[pnpm-shim] Worktree detected, setting virtual-store-dir=${virtualStorePath}`);
111
+ }
112
+ const realPnpm = findRealPnpm(config);
113
+ const result = spawnSync(realPnpm, newArgs, {
114
+ stdio: 'inherit',
115
+ encoding: 'utf8',
116
+ });
117
+ return result.status || 0;
118
+ }
119
+ }
120
+ // Default: pass through to real pnpm unchanged
121
+ const realPnpm = findRealPnpm(config);
122
+ const result = spawnSync(realPnpm, args, {
123
+ stdio: 'inherit',
124
+ encoding: 'utf8',
125
+ });
126
+ return result.status || 0;
127
+ }
128
+ /**
129
+ * Main entry point for CLI execution.
130
+ */
131
+ export function main() {
132
+ const args = process.argv.slice(2);
133
+ const exitCode = runPnpmShim(args);
134
+ process.exit(exitCode);
135
+ }
136
+ // Run if executed directly
137
+ if (import.meta.url === `file://${process.argv[1]}`) {
138
+ main();
139
+ }
140
+ //# sourceMappingURL=pnpm-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pnpm-shim.js","sourceRoot":"","sources":["../src/pnpm-shim.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+ export declare const GitShimConfigSchema: z.ZodObject<{
3
+ protectedBranch: z.ZodDefault<z.ZodString>;
4
+ bannedPatterns: z.ZodDefault<z.ZodArray<z.ZodObject<{
5
+ command: z.ZodString;
6
+ flags: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
+ }, z.core.$strip>>>;
8
+ bannedFlags: z.ZodDefault<z.ZodArray<z.ZodString>>;
9
+ realGitPath: z.ZodDefault<z.ZodString>;
10
+ enableLogging: z.ZodDefault<z.ZodBoolean>;
11
+ logPath: z.ZodOptional<z.ZodString>;
12
+ recursionEnvVar: z.ZodDefault<z.ZodString>;
13
+ agentEnvVars: z.ZodDefault<z.ZodArray<z.ZodString>>;
14
+ }, z.core.$strip>;
15
+ export type GitShimConfig = z.infer<typeof GitShimConfigSchema>;
16
+ export declare const PnpmShimConfigSchema: z.ZodObject<{
17
+ dependencyCommands: z.ZodDefault<z.ZodArray<z.ZodString>>;
18
+ systemPnpmPaths: z.ZodDefault<z.ZodArray<z.ZodString>>;
19
+ recursionEnvVar: z.ZodDefault<z.ZodString>;
20
+ enableDebug: z.ZodDefault<z.ZodBoolean>;
21
+ }, z.core.$strip>;
22
+ export type PnpmShimConfig = z.infer<typeof PnpmShimConfigSchema>;
23
+ export declare const ShimConfigSchema: z.ZodObject<{
24
+ git: z.ZodDefault<z.ZodObject<{
25
+ protectedBranch: z.ZodDefault<z.ZodString>;
26
+ bannedPatterns: z.ZodDefault<z.ZodArray<z.ZodObject<{
27
+ command: z.ZodString;
28
+ flags: z.ZodOptional<z.ZodArray<z.ZodString>>;
29
+ }, z.core.$strip>>>;
30
+ bannedFlags: z.ZodDefault<z.ZodArray<z.ZodString>>;
31
+ realGitPath: z.ZodDefault<z.ZodString>;
32
+ enableLogging: z.ZodDefault<z.ZodBoolean>;
33
+ logPath: z.ZodOptional<z.ZodString>;
34
+ recursionEnvVar: z.ZodDefault<z.ZodString>;
35
+ agentEnvVars: z.ZodDefault<z.ZodArray<z.ZodString>>;
36
+ }, z.core.$strip>>;
37
+ pnpm: z.ZodDefault<z.ZodObject<{
38
+ dependencyCommands: z.ZodDefault<z.ZodArray<z.ZodString>>;
39
+ systemPnpmPaths: z.ZodDefault<z.ZodArray<z.ZodString>>;
40
+ recursionEnvVar: z.ZodDefault<z.ZodString>;
41
+ enableDebug: z.ZodDefault<z.ZodBoolean>;
42
+ }, z.core.$strip>>;
43
+ }, z.core.$strip>;
44
+ export type ShimConfig = z.infer<typeof ShimConfigSchema>;
45
+ export declare const UserType: {
46
+ readonly AGENT: "agent";
47
+ readonly HUMAN: "human";
48
+ readonly UNKNOWN: "unknown";
49
+ };
50
+ export type UserType = (typeof UserType)[keyof typeof UserType];
51
+ export declare const CommandOutcome: {
52
+ readonly ALLOWED: "allowed";
53
+ readonly BLOCKED: "blocked";
54
+ readonly UNKNOWN: "unknown";
55
+ };
56
+ export type CommandOutcome = (typeof CommandOutcome)[keyof typeof CommandOutcome];
57
+ export interface BannedPatternResult {
58
+ banned: boolean;
59
+ reason: string | null;
60
+ }
61
+ export interface ProtectedContextResult {
62
+ protected: boolean;
63
+ context: string;
64
+ }
65
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;iBA8B9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,oBAAoB;;;;;iBAS/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;iBAG3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,QAAQ;;;;CAIX,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC;AAEhE,eAAO,MAAM,cAAc;;;;CAIjB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAElF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB"}
package/dist/types.js ADDED
@@ -0,0 +1,59 @@
1
+ // @lumenflow/shims - Type definitions (WU-2546)
2
+ import { z } from 'zod';
3
+ // Strings stored separately to avoid hook false positives
4
+ const NO_VERIFY_FLAG = '--no-' + 'verify';
5
+ const NO_GPG_SIGN_FLAG = '--no-' + 'gpg-sign';
6
+ export const GitShimConfigSchema = z.object({
7
+ protectedBranch: z.string().default('main'),
8
+ bannedPatterns: z
9
+ .array(z.object({
10
+ command: z.string(),
11
+ flags: z.array(z.string()).optional(),
12
+ }))
13
+ .default([
14
+ { command: 'reset', flags: ['--hard'] },
15
+ { command: 'stash' },
16
+ { command: 'clean', flags: ['-fd', '-df'] },
17
+ { command: 'checkout', flags: ['-f', '--force'] },
18
+ { command: 'push', flags: ['--force', '-f'] },
19
+ ]),
20
+ bannedFlags: z.array(z.string()).default([NO_VERIFY_FLAG, NO_GPG_SIGN_FLAG]),
21
+ realGitPath: z.string().default('/usr/bin/git'),
22
+ enableLogging: z.boolean().default(false),
23
+ logPath: z.string().optional(),
24
+ recursionEnvVar: z.string().default('LUMENFLOW_GIT_SHIM_ACTIVE'),
25
+ agentEnvVars: z
26
+ .array(z.string())
27
+ .default([
28
+ 'CLAUDE_SESSION_ID',
29
+ 'LUMENFLOW_AGENT_SESSION',
30
+ 'CI',
31
+ 'GITHUB_ACTIONS',
32
+ 'ANTHROPIC_API_KEY',
33
+ ]),
34
+ });
35
+ export const PnpmShimConfigSchema = z.object({
36
+ dependencyCommands: z
37
+ .array(z.string())
38
+ .default(['add', 'remove', 'install', 'update', 'i', 'rm', 'up']),
39
+ systemPnpmPaths: z
40
+ .array(z.string())
41
+ .default(['/usr/local/bin/pnpm', '/usr/bin/pnpm', '/opt/homebrew/bin/pnpm']),
42
+ recursionEnvVar: z.string().default('LUMENFLOW_PNPM_SHIM_ACTIVE'),
43
+ enableDebug: z.boolean().default(false),
44
+ });
45
+ export const ShimConfigSchema = z.object({
46
+ git: GitShimConfigSchema.default(() => GitShimConfigSchema.parse({})),
47
+ pnpm: PnpmShimConfigSchema.default(() => PnpmShimConfigSchema.parse({})),
48
+ });
49
+ export const UserType = {
50
+ AGENT: 'agent',
51
+ HUMAN: 'human',
52
+ UNKNOWN: 'unknown',
53
+ };
54
+ export const CommandOutcome = {
55
+ ALLOWED: 'allowed',
56
+ BLOCKED: 'blocked',
57
+ UNKNOWN: 'unknown',
58
+ };
59
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @lumenflow/shims - Worktree Detection Utilities (WU-2546)
3
+ *
4
+ * Shared utilities for detecting git worktree context.
5
+ *
6
+ * @module @lumenflow/shims/worktree
7
+ */
8
+ /**
9
+ * Execute a shell command and return the output.
10
+ *
11
+ * @param cmd - Command to execute
12
+ * @returns Command output or empty string on error
13
+ */
14
+ export declare function run(cmd: string): string;
15
+ /**
16
+ * Get the current git branch name.
17
+ *
18
+ * @param gitPath - Path to git executable (default: '/usr/bin/git')
19
+ * @returns Branch name or null if not in a git repo
20
+ */
21
+ export declare function getCurrentBranch(gitPath?: string): string | null;
22
+ /**
23
+ * Check if we're in the main worktree (not a lane worktree).
24
+ *
25
+ * In the main checkout, git-dir returns ".git".
26
+ * In a worktree, git-dir returns ".git/worktrees/<name>".
27
+ *
28
+ * @param gitPath - Path to git executable (default: '/usr/bin/git')
29
+ * @returns True if in main worktree
30
+ */
31
+ export declare function isMainWorktree(gitPath?: string): boolean;
32
+ /**
33
+ * Check if we're in any worktree (not the main checkout).
34
+ *
35
+ * @param gitPath - Path to git executable (default: '/usr/bin/git')
36
+ * @returns True if in a worktree
37
+ */
38
+ export declare function isInWorktree(gitPath?: string): boolean;
39
+ /**
40
+ * Get the main checkout's path using git.
41
+ *
42
+ * Works in both main checkout and worktrees.
43
+ * Uses git rev-parse --git-common-dir which returns the main .git directory
44
+ * even when run from a worktree.
45
+ *
46
+ * @returns Path to main checkout, or null if not in a git repo
47
+ */
48
+ export declare function getMainCheckoutPath(): string | null;
49
+ //# sourceMappingURL=worktree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMvC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,MAAyB,GAAG,MAAM,GAAG,IAAI,CAElF;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,MAAyB,GAAG,OAAO,CAK1E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,MAAyB,GAAG,OAAO,CAIxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAWnD"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @lumenflow/shims - Worktree Detection Utilities (WU-2546)
3
+ *
4
+ * Shared utilities for detecting git worktree context.
5
+ *
6
+ * @module @lumenflow/shims/worktree
7
+ */
8
+ import { execSync } from 'node:child_process';
9
+ import path from 'node:path';
10
+ const STDIO_PIPE = ['pipe', 'pipe', 'pipe'];
11
+ const ENCODING = 'utf8';
12
+ const DEFAULT_GIT_PATH = '/usr/bin/git';
13
+ /**
14
+ * Execute a shell command and return the output.
15
+ *
16
+ * @param cmd - Command to execute
17
+ * @returns Command output or empty string on error
18
+ */
19
+ export function run(cmd) {
20
+ try {
21
+ return execSync(cmd, { stdio: STDIO_PIPE, encoding: ENCODING }).trim();
22
+ }
23
+ catch {
24
+ return '';
25
+ }
26
+ }
27
+ /**
28
+ * Get the current git branch name.
29
+ *
30
+ * @param gitPath - Path to git executable (default: '/usr/bin/git')
31
+ * @returns Branch name or null if not in a git repo
32
+ */
33
+ export function getCurrentBranch(gitPath = DEFAULT_GIT_PATH) {
34
+ return run(`${gitPath} rev-parse --abbrev-ref HEAD`) || null;
35
+ }
36
+ /**
37
+ * Check if we're in the main worktree (not a lane worktree).
38
+ *
39
+ * In the main checkout, git-dir returns ".git".
40
+ * In a worktree, git-dir returns ".git/worktrees/<name>".
41
+ *
42
+ * @param gitPath - Path to git executable (default: '/usr/bin/git')
43
+ * @returns True if in main worktree
44
+ */
45
+ export function isMainWorktree(gitPath = DEFAULT_GIT_PATH) {
46
+ const gitDir = run(`${gitPath} rev-parse --git-dir`);
47
+ if (!gitDir)
48
+ return true;
49
+ const normalized = gitDir.replace(/\\/g, '/');
50
+ return !normalized.includes('/worktrees/');
51
+ }
52
+ /**
53
+ * Check if we're in any worktree (not the main checkout).
54
+ *
55
+ * @param gitPath - Path to git executable (default: '/usr/bin/git')
56
+ * @returns True if in a worktree
57
+ */
58
+ export function isInWorktree(gitPath = DEFAULT_GIT_PATH) {
59
+ const gitDir = run(`${gitPath} rev-parse --git-dir`);
60
+ if (!gitDir)
61
+ return false;
62
+ return gitDir.includes('/worktrees/');
63
+ }
64
+ /**
65
+ * Get the main checkout's path using git.
66
+ *
67
+ * Works in both main checkout and worktrees.
68
+ * Uses git rev-parse --git-common-dir which returns the main .git directory
69
+ * even when run from a worktree.
70
+ *
71
+ * @returns Path to main checkout, or null if not in a git repo
72
+ */
73
+ export function getMainCheckoutPath() {
74
+ try {
75
+ const gitCommonDir = execSync('git rev-parse --git-common-dir', {
76
+ encoding: ENCODING,
77
+ stdio: STDIO_PIPE,
78
+ }).trim();
79
+ return path.dirname(path.resolve(gitCommonDir));
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ //# sourceMappingURL=worktree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.js","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAqB,MAAM,oBAAoB,CAAC;AACjE,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,UAAU,GAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAC1D,MAAM,QAAQ,GAAG,MAAe,CAAC;AACjC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAExC;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAAC,GAAW;IAC7B,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB,gBAAgB;IACjE,OAAO,GAAG,CAAC,GAAG,OAAO,8BAA8B,CAAC,IAAI,IAAI,CAAC;AAC/D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB,gBAAgB;IAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,OAAO,sBAAsB,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB,gBAAgB;IAC7D,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,OAAO,sBAAsB,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,OAAO,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB;IACjC,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,QAAQ,CAAC,gCAAgC,EAAE;YAC9D,QAAQ,EAAE,QAAQ;YAClB,KAAK,EAAE,UAAU;SAClB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@lumenflow/shims",
3
+ "version": "1.0.0",
4
+ "description": "Git and pnpm safety shims for LumenFlow worktree discipline",
5
+ "keywords": [
6
+ "lumenflow",
7
+ "git",
8
+ "pnpm",
9
+ "shim",
10
+ "worktree",
11
+ "safety"
12
+ ],
13
+ "homepage": "https://github.com/hellmai/os",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/hellmai/os.git",
17
+ "directory": "packages/@lumenflow/shims"
18
+ },
19
+ "license": "Apache-2.0",
20
+ "author": {
21
+ "name": "HellmAI",
22
+ "url": "https://hellm.ai"
23
+ },
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ },
30
+ "./git": {
31
+ "types": "./dist/git-shim.d.ts",
32
+ "import": "./dist/git-shim.js"
33
+ },
34
+ "./pnpm": {
35
+ "types": "./dist/pnpm-shim.d.ts",
36
+ "import": "./dist/pnpm-shim.js"
37
+ }
38
+ },
39
+ "main": "./dist/index.js",
40
+ "types": "./dist/index.d.ts",
41
+ "bin": {
42
+ "lumenflow-git": "./dist/git-shim.js",
43
+ "lumenflow-pnpm": "./dist/pnpm-shim.js"
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "LICENSE",
48
+ "README.md"
49
+ ],
50
+ "dependencies": {
51
+ "zod": "^4.3.5"
52
+ },
53
+ "devDependencies": {
54
+ "@vitest/coverage-v8": "^4.0.0"
55
+ },
56
+ "engines": {
57
+ "node": ">=22"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ },
62
+ "scripts": {
63
+ "build": "tsc",
64
+ "build:dist": "tsc -p tsconfig.build.json",
65
+ "pack:dist": "pnpm pack",
66
+ "clean": "rm -rf dist *.tgz",
67
+ "dev": "tsc --watch",
68
+ "test": "vitest run",
69
+ "test:coverage": "vitest run --coverage",
70
+ "lint": "eslint src --max-warnings 0",
71
+ "typecheck": "tsc --noEmit"
72
+ }
73
+ }