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.
- checksums.yaml +7 -0
- data/.gem_rbs_collection/addressable/2.8/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/addressable/2.8/addressable.rbs +62 -0
- data/.gem_rbs_collection/async/2.12/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/async/2.12/async.rbs +119 -0
- data/.gem_rbs_collection/async/2.12/kernel.rbs +5 -0
- data/.gem_rbs_collection/async/2.12/manifest.yaml +7 -0
- data/.gem_rbs_collection/bcrypt/3.1/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/bcrypt/3.1/bcrypt.rbs +47 -0
- data/.gem_rbs_collection/bcrypt/3.1/manifest.yaml +2 -0
- data/.gem_rbs_collection/bigdecimal/3.1/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/bigdecimal/3.1/bigdecimal-math.rbs +119 -0
- data/.gem_rbs_collection/bigdecimal/3.1/bigdecimal.rbs +1630 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +4 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +26 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +4 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +65 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +249 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +5 -0
- data/.gem_rbs_collection/diff-lcs/1.5/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/diff-lcs/1.5/diff-lcs.rbs +11 -0
- data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/listen/3.9/listen.rbs +25 -0
- data/.gem_rbs_collection/listen/3.9/listener.rbs +24 -0
- data/.gem_rbs_collection/mini_mime/0.1/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/mini_mime/0.1/mini_mime.rbs +14 -0
- data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/parallel/1.20/parallel.rbs +86 -0
- data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rake/13.0/manifest.yaml +2 -0
- data/.gem_rbs_collection/rake/13.0/rake.rbs +39 -0
- data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +822 -0
- data/.gem_rbs_collection/sqlite3/2.0/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/sqlite3/2.0/database.rbs +20 -0
- data/.gem_rbs_collection/sqlite3/2.0/pragmas.rbs +5 -0
- data/.rspec +4 -0
- data/.rubocop.yml +121 -0
- data/.simplecov +51 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +329 -0
- data/LICENSE.txt +21 -0
- data/README.md +225 -0
- data/Rakefile +54 -0
- data/Steepfile +50 -0
- data/bin/sxn +6 -0
- data/lib/sxn/CLI.rb +275 -0
- data/lib/sxn/commands/init.rb +137 -0
- data/lib/sxn/commands/projects.rb +350 -0
- data/lib/sxn/commands/rules.rb +435 -0
- data/lib/sxn/commands/sessions.rb +300 -0
- data/lib/sxn/commands/worktrees.rb +416 -0
- data/lib/sxn/commands.rb +13 -0
- data/lib/sxn/config/config_cache.rb +295 -0
- data/lib/sxn/config/config_discovery.rb +242 -0
- data/lib/sxn/config/config_validator.rb +562 -0
- data/lib/sxn/config.rb +259 -0
- data/lib/sxn/core/config_manager.rb +290 -0
- data/lib/sxn/core/project_manager.rb +307 -0
- data/lib/sxn/core/rules_manager.rb +306 -0
- data/lib/sxn/core/session_manager.rb +336 -0
- data/lib/sxn/core/worktree_manager.rb +281 -0
- data/lib/sxn/core.rb +13 -0
- data/lib/sxn/database/errors.rb +29 -0
- data/lib/sxn/database/session_database.rb +691 -0
- data/lib/sxn/database.rb +24 -0
- data/lib/sxn/errors.rb +76 -0
- data/lib/sxn/rules/base_rule.rb +367 -0
- data/lib/sxn/rules/copy_files_rule.rb +346 -0
- data/lib/sxn/rules/errors.rb +28 -0
- data/lib/sxn/rules/project_detector.rb +871 -0
- data/lib/sxn/rules/rules_engine.rb +485 -0
- data/lib/sxn/rules/setup_commands_rule.rb +307 -0
- data/lib/sxn/rules/template_rule.rb +262 -0
- data/lib/sxn/rules.rb +148 -0
- data/lib/sxn/runtime_validations.rb +96 -0
- data/lib/sxn/security/secure_command_executor.rb +364 -0
- data/lib/sxn/security/secure_file_copier.rb +478 -0
- data/lib/sxn/security/secure_path_validator.rb +258 -0
- data/lib/sxn/security.rb +15 -0
- data/lib/sxn/templates/common/gitignore.liquid +99 -0
- data/lib/sxn/templates/common/session-info.md.liquid +58 -0
- data/lib/sxn/templates/errors.rb +36 -0
- data/lib/sxn/templates/javascript/README.md.liquid +59 -0
- data/lib/sxn/templates/javascript/session-info.md.liquid +206 -0
- data/lib/sxn/templates/rails/CLAUDE.md.liquid +78 -0
- data/lib/sxn/templates/rails/database.yml.liquid +31 -0
- data/lib/sxn/templates/rails/session-info.md.liquid +144 -0
- data/lib/sxn/templates/template_engine.rb +346 -0
- data/lib/sxn/templates/template_processor.rb +279 -0
- data/lib/sxn/templates/template_security.rb +410 -0
- data/lib/sxn/templates/template_variables.rb +713 -0
- data/lib/sxn/templates.rb +28 -0
- data/lib/sxn/ui/output.rb +103 -0
- data/lib/sxn/ui/progress_bar.rb +91 -0
- data/lib/sxn/ui/prompt.rb +116 -0
- data/lib/sxn/ui/table.rb +183 -0
- data/lib/sxn/ui.rb +12 -0
- data/lib/sxn/version.rb +5 -0
- data/lib/sxn.rb +63 -0
- data/rbs_collection.lock.yaml +180 -0
- data/rbs_collection.yaml +39 -0
- data/scripts/test.sh +31 -0
- data/sig/external/liquid.rbs +116 -0
- data/sig/external/thor.rbs +99 -0
- data/sig/external/tty.rbs +71 -0
- data/sig/sxn/cli.rbs +46 -0
- data/sig/sxn/commands/init.rbs +38 -0
- data/sig/sxn/commands/projects.rbs +72 -0
- data/sig/sxn/commands/rules.rbs +95 -0
- data/sig/sxn/commands/sessions.rbs +62 -0
- data/sig/sxn/commands/worktrees.rbs +82 -0
- data/sig/sxn/commands.rbs +6 -0
- data/sig/sxn/config/config_cache.rbs +67 -0
- data/sig/sxn/config/config_discovery.rbs +64 -0
- data/sig/sxn/config/config_validator.rbs +64 -0
- data/sig/sxn/config.rbs +74 -0
- data/sig/sxn/core/config_manager.rbs +67 -0
- data/sig/sxn/core/project_manager.rbs +52 -0
- data/sig/sxn/core/rules_manager.rbs +54 -0
- data/sig/sxn/core/session_manager.rbs +59 -0
- data/sig/sxn/core/worktree_manager.rbs +50 -0
- data/sig/sxn/core.rbs +87 -0
- data/sig/sxn/database/errors.rbs +37 -0
- data/sig/sxn/database/session_database.rbs +151 -0
- data/sig/sxn/database.rbs +83 -0
- data/sig/sxn/errors.rbs +89 -0
- data/sig/sxn/rules/base_rule.rbs +137 -0
- data/sig/sxn/rules/copy_files_rule.rbs +65 -0
- data/sig/sxn/rules/errors.rbs +33 -0
- data/sig/sxn/rules/project_detector.rbs +115 -0
- data/sig/sxn/rules/rules_engine.rbs +118 -0
- data/sig/sxn/rules/setup_commands_rule.rbs +60 -0
- data/sig/sxn/rules/template_rule.rbs +44 -0
- data/sig/sxn/rules.rbs +287 -0
- data/sig/sxn/runtime_validations.rbs +16 -0
- data/sig/sxn/security/secure_command_executor.rbs +63 -0
- data/sig/sxn/security/secure_file_copier.rbs +79 -0
- data/sig/sxn/security/secure_path_validator.rbs +30 -0
- data/sig/sxn/security.rbs +128 -0
- data/sig/sxn/templates/errors.rbs +43 -0
- data/sig/sxn/templates/template_engine.rbs +50 -0
- data/sig/sxn/templates/template_processor.rbs +44 -0
- data/sig/sxn/templates/template_security.rbs +62 -0
- data/sig/sxn/templates/template_variables.rbs +103 -0
- data/sig/sxn/templates.rbs +104 -0
- data/sig/sxn/ui/output.rbs +50 -0
- data/sig/sxn/ui/progress_bar.rbs +39 -0
- data/sig/sxn/ui/prompt.rbs +38 -0
- data/sig/sxn/ui/table.rbs +43 -0
- data/sig/sxn/ui.rbs +63 -0
- data/sig/sxn/version.rbs +5 -0
- data/sig/sxn.rbs +29 -0
- 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
|
data/lib/sxn/security.rb
ADDED
@@ -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"}}*
|