sxn 0.2.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 (156) hide show
  1. checksums.yaml +7 -0
  2. data/.gem_rbs_collection/addressable/2.8/.rbs_meta.yaml +9 -0
  3. data/.gem_rbs_collection/addressable/2.8/addressable.rbs +62 -0
  4. data/.gem_rbs_collection/async/2.12/.rbs_meta.yaml +9 -0
  5. data/.gem_rbs_collection/async/2.12/async.rbs +119 -0
  6. data/.gem_rbs_collection/async/2.12/kernel.rbs +5 -0
  7. data/.gem_rbs_collection/async/2.12/manifest.yaml +7 -0
  8. data/.gem_rbs_collection/bcrypt/3.1/.rbs_meta.yaml +9 -0
  9. data/.gem_rbs_collection/bcrypt/3.1/bcrypt.rbs +47 -0
  10. data/.gem_rbs_collection/bcrypt/3.1/manifest.yaml +2 -0
  11. data/.gem_rbs_collection/bigdecimal/3.1/.rbs_meta.yaml +9 -0
  12. data/.gem_rbs_collection/bigdecimal/3.1/bigdecimal-math.rbs +119 -0
  13. data/.gem_rbs_collection/bigdecimal/3.1/bigdecimal.rbs +1630 -0
  14. data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +9 -0
  15. data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +4 -0
  16. data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +26 -0
  17. data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +4 -0
  18. data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +65 -0
  19. data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +249 -0
  20. data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +5 -0
  21. data/.gem_rbs_collection/diff-lcs/1.5/.rbs_meta.yaml +9 -0
  22. data/.gem_rbs_collection/diff-lcs/1.5/diff-lcs.rbs +11 -0
  23. data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +9 -0
  24. data/.gem_rbs_collection/listen/3.9/listen.rbs +25 -0
  25. data/.gem_rbs_collection/listen/3.9/listener.rbs +24 -0
  26. data/.gem_rbs_collection/mini_mime/0.1/.rbs_meta.yaml +9 -0
  27. data/.gem_rbs_collection/mini_mime/0.1/mini_mime.rbs +14 -0
  28. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +9 -0
  29. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +86 -0
  30. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +9 -0
  31. data/.gem_rbs_collection/rake/13.0/manifest.yaml +2 -0
  32. data/.gem_rbs_collection/rake/13.0/rake.rbs +39 -0
  33. data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +9 -0
  34. data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +822 -0
  35. data/.gem_rbs_collection/sqlite3/2.0/.rbs_meta.yaml +9 -0
  36. data/.gem_rbs_collection/sqlite3/2.0/database.rbs +20 -0
  37. data/.gem_rbs_collection/sqlite3/2.0/pragmas.rbs +5 -0
  38. data/.rspec +4 -0
  39. data/.rubocop.yml +121 -0
  40. data/.simplecov +51 -0
  41. data/CHANGELOG.md +49 -0
  42. data/Gemfile +24 -0
  43. data/Gemfile.lock +329 -0
  44. data/LICENSE.txt +21 -0
  45. data/README.md +225 -0
  46. data/Rakefile +54 -0
  47. data/Steepfile +50 -0
  48. data/bin/sxn +6 -0
  49. data/lib/sxn/CLI.rb +275 -0
  50. data/lib/sxn/commands/init.rb +137 -0
  51. data/lib/sxn/commands/projects.rb +350 -0
  52. data/lib/sxn/commands/rules.rb +435 -0
  53. data/lib/sxn/commands/sessions.rb +300 -0
  54. data/lib/sxn/commands/worktrees.rb +416 -0
  55. data/lib/sxn/commands.rb +13 -0
  56. data/lib/sxn/config/config_cache.rb +295 -0
  57. data/lib/sxn/config/config_discovery.rb +242 -0
  58. data/lib/sxn/config/config_validator.rb +562 -0
  59. data/lib/sxn/config.rb +259 -0
  60. data/lib/sxn/core/config_manager.rb +290 -0
  61. data/lib/sxn/core/project_manager.rb +307 -0
  62. data/lib/sxn/core/rules_manager.rb +306 -0
  63. data/lib/sxn/core/session_manager.rb +336 -0
  64. data/lib/sxn/core/worktree_manager.rb +281 -0
  65. data/lib/sxn/core.rb +13 -0
  66. data/lib/sxn/database/errors.rb +29 -0
  67. data/lib/sxn/database/session_database.rb +691 -0
  68. data/lib/sxn/database.rb +24 -0
  69. data/lib/sxn/errors.rb +76 -0
  70. data/lib/sxn/rules/base_rule.rb +367 -0
  71. data/lib/sxn/rules/copy_files_rule.rb +346 -0
  72. data/lib/sxn/rules/errors.rb +28 -0
  73. data/lib/sxn/rules/project_detector.rb +871 -0
  74. data/lib/sxn/rules/rules_engine.rb +485 -0
  75. data/lib/sxn/rules/setup_commands_rule.rb +307 -0
  76. data/lib/sxn/rules/template_rule.rb +262 -0
  77. data/lib/sxn/rules.rb +148 -0
  78. data/lib/sxn/runtime_validations.rb +96 -0
  79. data/lib/sxn/security/secure_command_executor.rb +364 -0
  80. data/lib/sxn/security/secure_file_copier.rb +478 -0
  81. data/lib/sxn/security/secure_path_validator.rb +258 -0
  82. data/lib/sxn/security.rb +15 -0
  83. data/lib/sxn/templates/common/gitignore.liquid +99 -0
  84. data/lib/sxn/templates/common/session-info.md.liquid +58 -0
  85. data/lib/sxn/templates/errors.rb +36 -0
  86. data/lib/sxn/templates/javascript/README.md.liquid +59 -0
  87. data/lib/sxn/templates/javascript/session-info.md.liquid +206 -0
  88. data/lib/sxn/templates/rails/CLAUDE.md.liquid +78 -0
  89. data/lib/sxn/templates/rails/database.yml.liquid +31 -0
  90. data/lib/sxn/templates/rails/session-info.md.liquid +144 -0
  91. data/lib/sxn/templates/template_engine.rb +346 -0
  92. data/lib/sxn/templates/template_processor.rb +279 -0
  93. data/lib/sxn/templates/template_security.rb +410 -0
  94. data/lib/sxn/templates/template_variables.rb +713 -0
  95. data/lib/sxn/templates.rb +28 -0
  96. data/lib/sxn/ui/output.rb +103 -0
  97. data/lib/sxn/ui/progress_bar.rb +91 -0
  98. data/lib/sxn/ui/prompt.rb +116 -0
  99. data/lib/sxn/ui/table.rb +183 -0
  100. data/lib/sxn/ui.rb +12 -0
  101. data/lib/sxn/version.rb +5 -0
  102. data/lib/sxn.rb +63 -0
  103. data/rbs_collection.lock.yaml +180 -0
  104. data/rbs_collection.yaml +39 -0
  105. data/scripts/test.sh +31 -0
  106. data/sig/external/liquid.rbs +116 -0
  107. data/sig/external/thor.rbs +99 -0
  108. data/sig/external/tty.rbs +71 -0
  109. data/sig/sxn/cli.rbs +46 -0
  110. data/sig/sxn/commands/init.rbs +38 -0
  111. data/sig/sxn/commands/projects.rbs +72 -0
  112. data/sig/sxn/commands/rules.rbs +95 -0
  113. data/sig/sxn/commands/sessions.rbs +62 -0
  114. data/sig/sxn/commands/worktrees.rbs +82 -0
  115. data/sig/sxn/commands.rbs +6 -0
  116. data/sig/sxn/config/config_cache.rbs +67 -0
  117. data/sig/sxn/config/config_discovery.rbs +64 -0
  118. data/sig/sxn/config/config_validator.rbs +64 -0
  119. data/sig/sxn/config.rbs +74 -0
  120. data/sig/sxn/core/config_manager.rbs +67 -0
  121. data/sig/sxn/core/project_manager.rbs +52 -0
  122. data/sig/sxn/core/rules_manager.rbs +54 -0
  123. data/sig/sxn/core/session_manager.rbs +59 -0
  124. data/sig/sxn/core/worktree_manager.rbs +50 -0
  125. data/sig/sxn/core.rbs +87 -0
  126. data/sig/sxn/database/errors.rbs +37 -0
  127. data/sig/sxn/database/session_database.rbs +151 -0
  128. data/sig/sxn/database.rbs +83 -0
  129. data/sig/sxn/errors.rbs +89 -0
  130. data/sig/sxn/rules/base_rule.rbs +137 -0
  131. data/sig/sxn/rules/copy_files_rule.rbs +65 -0
  132. data/sig/sxn/rules/errors.rbs +33 -0
  133. data/sig/sxn/rules/project_detector.rbs +115 -0
  134. data/sig/sxn/rules/rules_engine.rbs +118 -0
  135. data/sig/sxn/rules/setup_commands_rule.rbs +60 -0
  136. data/sig/sxn/rules/template_rule.rbs +44 -0
  137. data/sig/sxn/rules.rbs +287 -0
  138. data/sig/sxn/runtime_validations.rbs +16 -0
  139. data/sig/sxn/security/secure_command_executor.rbs +63 -0
  140. data/sig/sxn/security/secure_file_copier.rbs +79 -0
  141. data/sig/sxn/security/secure_path_validator.rbs +30 -0
  142. data/sig/sxn/security.rbs +128 -0
  143. data/sig/sxn/templates/errors.rbs +43 -0
  144. data/sig/sxn/templates/template_engine.rbs +50 -0
  145. data/sig/sxn/templates/template_processor.rbs +44 -0
  146. data/sig/sxn/templates/template_security.rbs +62 -0
  147. data/sig/sxn/templates/template_variables.rbs +103 -0
  148. data/sig/sxn/templates.rbs +104 -0
  149. data/sig/sxn/ui/output.rbs +50 -0
  150. data/sig/sxn/ui/progress_bar.rbs +39 -0
  151. data/sig/sxn/ui/prompt.rbs +38 -0
  152. data/sig/sxn/ui/table.rbs +43 -0
  153. data/sig/sxn/ui.rbs +63 -0
  154. data/sig/sxn/version.rbs +5 -0
  155. data/sig/sxn.rbs +29 -0
  156. metadata +635 -0
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Sxn
6
+ module Security
7
+ # SecurePathValidator provides security controls for file system path operations.
8
+ # It prevents directory traversal attacks, validates paths stay within project boundaries,
9
+ # checks for dangerous symlinks, and ensures no ".." components in paths.
10
+ #
11
+ # @example
12
+ # validator = SecurePathValidator.new("/path/to/project")
13
+ # validator.validate_path("config/database.yml") # => "/path/to/project/config/database.yml"
14
+ # validator.validate_path("../etc/passwd") # => raises PathValidationError
15
+ #
16
+ class SecurePathValidator
17
+ # @param project_root [String] The absolute path to the project root directory
18
+ # @raise [ArgumentError] if project_root is nil, empty, or not an absolute path
19
+ def initialize(project_root)
20
+ raise ArgumentError, "Project root cannot be nil or empty" if project_root.nil? || project_root.empty?
21
+
22
+ # Resolve relative paths to absolute paths
23
+ absolute_root = if Pathname.new(project_root).absolute?
24
+ project_root
25
+ else
26
+ File.expand_path(project_root)
27
+ end
28
+
29
+ @project_root = File.realpath(absolute_root)
30
+ @project_root_pathname = Pathname.new(@project_root)
31
+ rescue Errno::ENOENT
32
+ raise PathValidationError, "Project root does not exist: #{project_root}"
33
+ end
34
+
35
+ # Validates that a path is safe and within project boundaries
36
+ #
37
+ # @param path [String] The path to validate (can be relative or absolute)
38
+ # @param allow_creation [Boolean] Whether to allow validation of non-existent paths
39
+ # @return [String] The absolute, validated path
40
+ # @raise [PathValidationError] if the path is unsafe or outside project boundaries
41
+ def validate_path(path, allow_creation: false)
42
+ raise ArgumentError, "Path cannot be nil or empty" if path.nil? || (path.respond_to?(:empty?) && path.empty?)
43
+ raise ArgumentError, "Path must be a string" unless path.is_a?(String)
44
+
45
+ # Check for dangerous patterns in the raw path
46
+ validate_path_components!(path)
47
+
48
+ # Convert to absolute path relative to project root
49
+ absolute_path = if Pathname.new(path).absolute?
50
+ path
51
+ else
52
+ File.join(@project_root, path)
53
+ end
54
+
55
+ # Normalize the path and check boundaries
56
+ normalized_path = if File.exist?(absolute_path)
57
+ File.realpath(absolute_path)
58
+ elsif allow_creation
59
+ # For non-existent paths, we need to validate the normalized path manually
60
+ normalize_path_manually(absolute_path)
61
+ else
62
+ # If file doesn't exist and creation isn't allowed, use realpath which will fail
63
+ File.realpath(absolute_path)
64
+ end
65
+
66
+ validate_within_boundaries!(normalized_path)
67
+ validate_symlink_safety!(normalized_path) if File.exist?(normalized_path)
68
+
69
+ normalized_path
70
+ end
71
+
72
+ # Validates a source and destination pair for file operations
73
+ #
74
+ # @param source [String] The source path
75
+ # @param destination [String] The destination path
76
+ # @param allow_creation [Boolean] Whether to allow validation of non-existent destination
77
+ # @return [Array<String>] Array containing [validated_source, validated_destination]
78
+ # @raise [PathValidationError] if either path is unsafe
79
+ def validate_file_operation(source, destination, allow_creation: true)
80
+ validated_source = validate_path(source, allow_creation: false)
81
+ validated_destination = validate_path(destination, allow_creation: allow_creation)
82
+
83
+ # Additional checks for file operations
84
+ raise PathValidationError, "Source cannot be a directory: #{source}" if File.exist?(validated_source) && File.directory?(validated_source)
85
+
86
+ [validated_source, validated_destination]
87
+ end
88
+
89
+ # Checks if a path is within the project boundaries without full validation
90
+ #
91
+ # @param path [String] The path to check
92
+ # @return [Boolean] true if the path appears to be within boundaries
93
+ def within_boundaries?(path)
94
+ return false if path.nil? || path.empty?
95
+
96
+ begin
97
+ validate_path(path, allow_creation: true)
98
+ true
99
+ rescue PathValidationError
100
+ false
101
+ end
102
+ end
103
+
104
+ # Returns the project root path
105
+ #
106
+ # @return [String] The absolute project root path
107
+ attr_reader :project_root
108
+
109
+ private
110
+
111
+ # Validates individual path components for dangerous patterns
112
+ def validate_path_components!(path)
113
+ # Check for null bytes (directory traversal in some filesystems)
114
+ if path.include?("\x00")
115
+ # Also check if it contains directory traversal
116
+ if path.include?("../") || path.include?("..\\") || path.include?("..")
117
+ raise PathValidationError, "Path contains directory traversal sequences: #{path}"
118
+ end
119
+
120
+ raise PathValidationError, "Path contains null bytes: #{path}"
121
+
122
+ end
123
+
124
+ # Check for obvious directory traversal attempts
125
+ if path.include?("../") || path.include?("..\\") || path == ".."
126
+ raise PathValidationError, "Path contains directory traversal sequences: #{path}"
127
+ end
128
+
129
+ # Check for other dangerous patterns
130
+ dangerous_patterns = [
131
+ %r{/\.\.(?:/|\z)}, # /../ or /.. at end
132
+ %r{\A\.\.(?:/|\z)}, # ../ or .. at start
133
+ %r{//+} # multiple slashes (potential bypass)
134
+ ]
135
+
136
+ dangerous_patterns.each do |pattern|
137
+ raise PathValidationError, "Path contains dangerous pattern: #{path}" if path.match?(pattern)
138
+ end
139
+ end
140
+
141
+ # Validates that a normalized path is within project boundaries
142
+ def validate_within_boundaries!(absolute_path)
143
+ absolute_pathname = Pathname.new(absolute_path)
144
+
145
+ # Check if the path is under the project root
146
+ begin
147
+ relative_path = absolute_pathname.relative_path_from(@project_root_pathname)
148
+
149
+ # relative_path_from raises ArgumentError if paths don't share a common ancestor
150
+ # Additional check: ensure the relative path doesn't start with ../
151
+ if relative_path.to_s.start_with?("../") || relative_path.to_s == ".."
152
+ raise PathValidationError, "Path is outside project boundaries: #{absolute_path}"
153
+ end
154
+ rescue ArgumentError
155
+ # This means the paths don't share a common ancestor
156
+ raise PathValidationError, "Path is outside project boundaries: #{absolute_path}"
157
+ end
158
+ end
159
+
160
+ # Validates symlink safety to prevent symlink attacks
161
+ def validate_symlink_safety!(path)
162
+ # Convert path to Pathname for manipulation
163
+ pathname = Pathname.new(path)
164
+
165
+ # For absolute paths, we need to check each component from the path itself
166
+ # For relative paths, build from project root
167
+ if pathname.absolute?
168
+ # Ensure both paths are resolved consistently to avoid symlink resolution issues
169
+ resolved_path = File.exist?(path) ? File.realpath(path) : path
170
+ resolved_pathname = Pathname.new(resolved_path)
171
+ project_root_pathname = Pathname.new(@project_root)
172
+
173
+ # Make path relative to project root to get the components to check
174
+ begin
175
+ relative_path = resolved_pathname.relative_path_from(project_root_pathname)
176
+ path_parts = relative_path.each_filename.to_a
177
+ current_path = Pathname.new(@project_root)
178
+ rescue ArgumentError
179
+ # Path is not within project boundaries, but this should have been caught earlier
180
+ raise PathValidationError, "Path is outside project boundaries: #{path}"
181
+ end
182
+ else
183
+ # For relative paths, use them directly
184
+ path_parts = pathname.each_filename.to_a
185
+ current_path = Pathname.new(@project_root)
186
+ end
187
+
188
+ path_parts.each do |part|
189
+ current_path = current_path.join(part)
190
+
191
+ next unless current_path.symlink?
192
+
193
+ target = current_path.readlink
194
+
195
+ # If symlink target is absolute, validate it
196
+ if target.absolute?
197
+ # Resolve absolute symlink targets to handle symlink chains
198
+ resolved_absolute = File.exist?(target.to_s) ? File.realpath(target.to_s) : target.to_s
199
+ validate_within_boundaries!(resolved_absolute)
200
+ else
201
+ # If relative, resolve relative to symlink location and validate
202
+ resolved_target = current_path.dirname.join(target).cleanpath
203
+ # Ensure consistent path resolution by using realpath if the target exists
204
+ final_target = File.exist?(resolved_target.to_s) ? File.realpath(resolved_target.to_s) : resolved_target.to_s
205
+ validate_within_boundaries!(final_target)
206
+ end
207
+ end
208
+ end
209
+
210
+ # Manually normalize a path without requiring it to exist
211
+ def normalize_path_manually(path)
212
+ # Convert to Pathname for easier manipulation
213
+ pathname = Pathname.new(path)
214
+
215
+ # Special check for the dangerous traversal case we need to catch:
216
+ # Paths like "/a/../../../etc/passwd" where we have more .. than directories to back out of
217
+
218
+ # First, let's check the cleaned path to see if it results in something that goes outside the root
219
+ cleaned = pathname.cleanpath
220
+
221
+ # For absolute paths, check for dangerous traversal patterns
222
+ if pathname.absolute?
223
+ # Use cleanpath first to see what the path actually resolves to
224
+ cleaned = pathname.cleanpath
225
+
226
+ # If cleanpath results in going to parent of root, check if it's truly dangerous
227
+ if cleaned.to_s == "/"
228
+ # This is fine - just resolves to root
229
+ else
230
+ # Now simulate the original path resolution to detect dangerous traversal
231
+ parts = path.split(File::SEPARATOR).reject { |p| p.empty? || p == "." }
232
+ stack = []
233
+ exceeded_root = false
234
+
235
+ parts.each do |part|
236
+ if part == ".."
237
+ if stack.empty?
238
+ # This would go above root, track it
239
+ exceeded_root = true
240
+ else
241
+ stack.pop
242
+ end
243
+ else
244
+ stack << part
245
+ end
246
+ end
247
+
248
+ # Only raise error if we exceeded root AND there are still meaningful path components
249
+ raise PathValidationError, "path traversal detected: #{path}" if exceeded_root && !stack.empty?
250
+ end
251
+ end
252
+
253
+ # For all paths, use cleanpath for the actual normalization
254
+ cleaned.to_s
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "security/secure_path_validator"
4
+ require_relative "security/secure_command_executor"
5
+ require_relative "security/secure_file_copier"
6
+
7
+ module Sxn
8
+ # Security namespace provides components for secure file operations,
9
+ # command execution, path validation, and audit logging.
10
+ #
11
+ # This module follows Ruby gem best practices by using explicit requires
12
+ # instead of autoload for better loading performance and dependency clarity.
13
+ module Security
14
+ end
15
+ end
@@ -0,0 +1,99 @@
1
+ # Session-specific .gitignore for {{ session.name }}
2
+ # This file contains patterns specific to this development session
3
+
4
+ # Session metadata
5
+ .sxn/
6
+ session-info.md
7
+ *.session.log
8
+
9
+ # Temporary files created during development
10
+ *.tmp
11
+ *.temp
12
+ .temp/
13
+ temp/
14
+
15
+ # IDE and editor files
16
+ .vscode/settings.json
17
+ .idea/workspace.xml
18
+ *.swp
19
+ *.swo
20
+ *~
21
+ .DS_Store
22
+
23
+ # OS-specific files
24
+ Thumbs.db
25
+ .Spotlight-V100
26
+ .Trashes
27
+
28
+ # Development logs
29
+ npm-debug.log*
30
+ yarn-debug.log*
31
+ yarn-error.log*
32
+ pnpm-debug.log*
33
+
34
+ # Environment files (session-specific)
35
+ .env.{{ session.name }}
36
+ .env.local.{{ session.name }}
37
+
38
+ {% if project.type == "rails" %}
39
+ # Rails session-specific ignores
40
+ /log/*.log
41
+ /tmp/cache/
42
+ /tmp/pids/
43
+ /tmp/sockets/
44
+ /vendor/bundle
45
+ .bundle/
46
+ {% endif %}
47
+
48
+ {% if project.type == "javascript" or project.type == "typescript" or project.type == "nodejs" %}
49
+ # Node.js session-specific ignores
50
+ node_modules/
51
+ .npm/
52
+ .pnpm-store/
53
+ .yarn/cache/
54
+ .yarn/unplugged/
55
+ .yarn/build-state.yml
56
+ .yarn/install-state.gz
57
+ .next/
58
+ .nuxt/
59
+ dist/
60
+ build/
61
+ {% endif %}
62
+
63
+ # Database files (session-specific)
64
+ {% if project.type == "rails" %}
65
+ /db/*.sqlite3
66
+ /db/*.sqlite3-*
67
+ {% endif %}
68
+ *.db
69
+ *.sqlite
70
+ *.sqlite3
71
+
72
+ # Coverage reports
73
+ coverage/
74
+ .nyc_output/
75
+ .coverage/
76
+
77
+ # Dependency directories
78
+ vendor/
79
+ bower_components/
80
+
81
+ # Optional npm cache directory
82
+ .npm
83
+
84
+ # Optional REPL history
85
+ .node_repl_history
86
+
87
+ # Output of 'npm pack'
88
+ *.tgz
89
+
90
+ # Yarn Integrity file
91
+ .yarn-integrity
92
+
93
+ # parcel-bundler cache (https://parceljs.org/)
94
+ .cache
95
+ .parcel-cache
96
+
97
+ # Session-specific backup files
98
+ *.{{ session.name }}.backup
99
+ backup.{{ session.name }}/
@@ -0,0 +1,58 @@
1
+ # Session: {{session.name}}
2
+
3
+ ## Session Details
4
+ - **Name**: {{session.name}}
5
+ - **Created**: {{session.created_at | date: "%Y-%m-%d %H:%M:%S"}}
6
+ - **Updated**: {{session.updated_at | date: "%Y-%m-%d %H:%M:%S"}}
7
+ - **Status**: {{session.status}}
8
+ - **Path**: `{{session.path}}`
9
+ {% if session.linear_task %}- **Linear Task**: {{session.linear_task}}{% endif %}
10
+ {% if session.description %}- **Description**: {{session.description}}{% endif %}
11
+
12
+ ## Worktrees
13
+
14
+ {% if session.worktrees %}
15
+ {% for worktree in session.worktrees %}
16
+ ### {{worktree.name}}
17
+ - **Path**: `{{worktree.path}}`
18
+ - **Branch**: `{{worktree.branch}}`
19
+ - **Created**: {{worktree.created_at | date: "%Y-%m-%d %H:%M:%S"}}
20
+ {% endfor %}
21
+ {% else %}
22
+ No worktrees configured for this session.
23
+ {% endif %}
24
+
25
+ ## Environment
26
+
27
+ - **User**: {{user.username}}
28
+ - **Home Directory**: {{user.home}}
29
+ - **Ruby**: {{environment.ruby.version}}
30
+ {% if environment.node.version %}- **Node.js**: {{environment.node.version}}{% endif %}
31
+ {% if environment.python.version %}- **Python**: {{environment.python.version}}{% endif %}
32
+
33
+ ## Session Commands
34
+
35
+ ```bash
36
+ # List all sessions
37
+ sxn list
38
+
39
+ # Switch to this session
40
+ sxn use {{session.name}}
41
+
42
+ # Add a new worktree
43
+ sxn worktree add <project-name> <branch-name>
44
+
45
+ # Remove session
46
+ sxn remove {{session.name}}
47
+ ```
48
+
49
+ {% if session.tags %}
50
+ ## Tags
51
+ {% for tag in session.tags %}
52
+ - {{tag}}
53
+ {% endfor %}
54
+ {% endif %}
55
+
56
+ ---
57
+
58
+ *Session information generated at {{timestamp}}*
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../errors"
4
+
5
+ module Sxn
6
+ module Templates
7
+ module Errors
8
+ # Base class for template-related errors
9
+ class TemplateError < Sxn::Error; end
10
+
11
+ # Raised when template syntax is invalid
12
+ class TemplateSyntaxError < TemplateError; end
13
+
14
+ # Raised when template processing fails
15
+ class TemplateProcessingError < TemplateError; end
16
+
17
+ # Raised when template file is not found
18
+ class TemplateNotFoundError < TemplateError; end
19
+
20
+ # Raised when template exceeds size limits
21
+ class TemplateTooLargeError < TemplateError; end
22
+
23
+ # Raised when template processing times out
24
+ class TemplateTimeoutError < TemplateError; end
25
+
26
+ # Raised when template contains security violations
27
+ class TemplateSecurityError < TemplateError; end
28
+
29
+ # Raised when template rendering encounters errors
30
+ class TemplateRenderError < TemplateError; end
31
+
32
+ # Raised when template variable collection fails
33
+ class TemplateVariableError < TemplateError; end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,59 @@
1
+ # {{project.name}} - JavaScript Development Session
2
+
3
+ ## Project Information
4
+ - **Session Name**: {{session.name}}
5
+ - **Project Type**: {{project.type | capitalize}}
6
+ - **Package Manager**: {{project.package_manager | default: 'npm'}}
7
+ - **Node.js Version**: {{environment.node.version | default: 'Not detected'}}
8
+
9
+ ## Available Scripts
10
+
11
+ {% if project.scripts %}
12
+ {% for script in project.scripts %}
13
+ - `{{script[0]}}`: {{script[1]}}
14
+ {% endfor %}
15
+ {% else %}
16
+ No scripts found in package.json
17
+ {% endif %}
18
+
19
+ ## Dependencies
20
+
21
+ {% if project.dependencies %}
22
+ ### Production Dependencies
23
+ {% for dep in project.dependencies %}
24
+ - {{dep}}
25
+ {% endfor %}
26
+ {% endif %}
27
+
28
+ {% if project.dev_dependencies %}
29
+ ### Development Dependencies
30
+ {% for dep in project.dev_dependencies %}
31
+ - {{dep}}
32
+ {% endfor %}
33
+ {% endif %}
34
+
35
+ ## Getting Started
36
+
37
+ ```bash
38
+ # Use the correct Node.js version
39
+ nvm use {{environment.node.version | default: '18.0.0'}}
40
+
41
+ # Install dependencies
42
+ {{project.package_manager | default: 'npm'}} install
43
+
44
+ # Start development server
45
+ {{project.package_manager | default: 'npm'}} run dev
46
+ ```
47
+
48
+ ## Cursor Rules Integration
49
+
50
+ This project uses cursor rules for enhanced development:
51
+
52
+ ```javascript
53
+ mcp__cursor_rules__get_contextual_rules({ context: "frontend_internationalization" })
54
+ mcp__cursor_rules__get_contextual_rules({ context: "cli_development" })
55
+ ```
56
+
57
+ ---
58
+
59
+ *Session: {{session.name}} | Created: {{session.created_at | date: "%Y-%m-%d %H:%M:%S"}}*