@fermindi/pwn-cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +265 -251
  3. package/cli/batch.js +333 -333
  4. package/cli/codespaces.js +303 -303
  5. package/cli/index.js +112 -91
  6. package/cli/inject.js +90 -67
  7. package/cli/knowledge.js +531 -531
  8. package/cli/migrate.js +466 -0
  9. package/cli/notify.js +135 -135
  10. package/cli/patterns.js +665 -665
  11. package/cli/save.js +206 -0
  12. package/cli/status.js +91 -91
  13. package/cli/update.js +189 -0
  14. package/cli/validate.js +61 -61
  15. package/package.json +70 -70
  16. package/src/core/inject.js +300 -204
  17. package/src/core/state.js +91 -91
  18. package/src/core/validate.js +202 -202
  19. package/src/core/workspace.js +176 -176
  20. package/src/index.js +20 -20
  21. package/src/knowledge/gc.js +308 -308
  22. package/src/knowledge/lifecycle.js +401 -401
  23. package/src/knowledge/promote.js +364 -364
  24. package/src/knowledge/references.js +342 -342
  25. package/src/patterns/matcher.js +218 -218
  26. package/src/patterns/registry.js +375 -375
  27. package/src/patterns/triggers.js +423 -423
  28. package/src/services/batch-service.js +849 -849
  29. package/src/services/notification-service.js +342 -342
  30. package/templates/codespaces/devcontainer.json +52 -52
  31. package/templates/codespaces/setup.sh +70 -70
  32. package/templates/workspace/.ai/README.md +164 -164
  33. package/templates/workspace/.ai/agents/README.md +204 -204
  34. package/templates/workspace/.ai/agents/claude.md +625 -625
  35. package/templates/workspace/.ai/config/README.md +79 -79
  36. package/templates/workspace/.ai/config/notifications.template.json +20 -20
  37. package/templates/workspace/.ai/memory/deadends.md +79 -79
  38. package/templates/workspace/.ai/memory/decisions.md +58 -58
  39. package/templates/workspace/.ai/memory/patterns.md +65 -65
  40. package/templates/workspace/.ai/patterns/backend/README.md +126 -126
  41. package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
  42. package/templates/workspace/.ai/patterns/index.md +256 -256
  43. package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
  44. package/templates/workspace/.ai/patterns/universal/README.md +141 -141
  45. package/templates/workspace/.ai/state.template.json +8 -8
  46. package/templates/workspace/.ai/tasks/active.md +77 -77
  47. package/templates/workspace/.ai/tasks/backlog.md +95 -95
  48. package/templates/workspace/.ai/workflows/batch-task.md +356 -356
@@ -1,218 +1,218 @@
1
- /**
2
- * Pattern Matcher - Glob and regex matching utilities
3
- *
4
- * Provides minimatch-like glob matching without external dependencies.
5
- * Supports common glob patterns used in trigger evaluation.
6
- */
7
-
8
- /**
9
- * Convert glob pattern to regex
10
- * @param {string} pattern - Glob pattern
11
- * @returns {RegExp}
12
- */
13
- export function globToRegex(pattern) {
14
- // Escape special regex characters except glob wildcards
15
- let regex = pattern
16
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
17
- // ** matches any path segments
18
- .replace(/\*\*/g, '{{GLOBSTAR}}')
19
- // * matches any characters except path separator
20
- .replace(/\*/g, '[^/\\\\]*')
21
- // ? matches single character
22
- .replace(/\?/g, '[^/\\\\]')
23
- // Restore globstar
24
- .replace(/\{\{GLOBSTAR\}\}/g, '.*');
25
-
26
- // Handle brace expansion {a,b,c}
27
- if (regex.includes('\\{') && regex.includes('\\}')) {
28
- regex = regex.replace(/\\\{([^}]+)\\\}/g, (_, options) => {
29
- const parts = options.split(',').map(p => p.trim());
30
- return `(?:${parts.join('|')})`;
31
- });
32
- }
33
-
34
- return new RegExp(`^${regex}$`, 'i');
35
- }
36
-
37
- /**
38
- * Test if a string matches a glob pattern
39
- * @param {string} str - String to test
40
- * @param {string} pattern - Glob pattern
41
- * @param {Object} [options] - Match options
42
- * @param {boolean} [options.dot=false] - Match dotfiles
43
- * @param {boolean} [options.nocase=true] - Case insensitive
44
- * @returns {boolean}
45
- */
46
- export function minimatch(str, pattern, options = {}) {
47
- const { dot = false, nocase = true } = options;
48
-
49
- // Normalize path separators
50
- const normalizedStr = str.replace(/\\/g, '/');
51
- const normalizedPattern = pattern.replace(/\\/g, '/');
52
-
53
- // Handle negation
54
- if (normalizedPattern.startsWith('!')) {
55
- return !minimatch(normalizedStr, normalizedPattern.slice(1), options);
56
- }
57
-
58
- // Skip dotfiles unless dot option is true
59
- if (!dot && normalizedStr.split('/').some(part => part.startsWith('.'))) {
60
- // Allow if pattern explicitly matches dotfiles
61
- if (!normalizedPattern.includes('/.') && !normalizedPattern.startsWith('.')) {
62
- return false;
63
- }
64
- }
65
-
66
- try {
67
- const regex = globToRegex(normalizedPattern);
68
- if (nocase) {
69
- return new RegExp(regex.source, 'i').test(normalizedStr);
70
- }
71
- return regex.test(normalizedStr);
72
- } catch {
73
- // Fallback to simple string matching
74
- return normalizedStr === normalizedPattern;
75
- }
76
- }
77
-
78
- /**
79
- * Find all matching items from a list
80
- * @param {string[]} list - List of strings to match against
81
- * @param {string} pattern - Glob pattern
82
- * @param {Object} [options] - Match options
83
- * @returns {string[]}
84
- */
85
- export function minimatchFilter(list, pattern, options = {}) {
86
- return list.filter(item => minimatch(item, pattern, options));
87
- }
88
-
89
- /**
90
- * Test if string matches any of the patterns
91
- * @param {string} str - String to test
92
- * @param {string[]} patterns - Array of glob patterns
93
- * @param {Object} [options] - Match options
94
- * @returns {boolean}
95
- */
96
- export function minimatchAny(str, patterns, options = {}) {
97
- return patterns.some(pattern => minimatch(str, pattern, options));
98
- }
99
-
100
- /**
101
- * Test if string matches all of the patterns
102
- * @param {string} str - String to test
103
- * @param {string[]} patterns - Array of glob patterns
104
- * @param {Object} [options] - Match options
105
- * @returns {boolean}
106
- */
107
- export function minimatchAll(str, patterns, options = {}) {
108
- return patterns.every(pattern => minimatch(str, pattern, options));
109
- }
110
-
111
- /**
112
- * Create a matcher function for a pattern
113
- * @param {string} pattern - Glob pattern
114
- * @param {Object} [options] - Match options
115
- * @returns {Function} Matcher function
116
- */
117
- export function createMatcher(pattern, options = {}) {
118
- return (str) => minimatch(str, pattern, options);
119
- }
120
-
121
- /**
122
- * Match file extension
123
- * @param {string} fileName - File name or path
124
- * @param {string|string[]} extensions - Extension(s) to match (with or without dot)
125
- * @returns {boolean}
126
- */
127
- export function matchExtension(fileName, extensions) {
128
- const exts = Array.isArray(extensions) ? extensions : [extensions];
129
- const normalizedExts = exts.map(ext => ext.startsWith('.') ? ext : `.${ext}`);
130
- const fileExt = fileName.includes('.') ? '.' + fileName.split('.').pop() : '';
131
-
132
- return normalizedExts.some(ext => ext.toLowerCase() === fileExt.toLowerCase());
133
- }
134
-
135
- /**
136
- * Match path prefix
137
- * @param {string} filePath - File path
138
- * @param {string|string[]} prefixes - Path prefix(es) to match
139
- * @returns {boolean}
140
- */
141
- export function matchPathPrefix(filePath, prefixes) {
142
- const prfxs = Array.isArray(prefixes) ? prefixes : [prefixes];
143
- const normalizedPath = filePath.replace(/\\/g, '/');
144
-
145
- return prfxs.some(prefix => {
146
- const normalizedPrefix = prefix.replace(/\\/g, '/');
147
- return normalizedPath.startsWith(normalizedPrefix);
148
- });
149
- }
150
-
151
- /**
152
- * Extract file parts for matching
153
- * @param {string} filePath - File path
154
- * @returns {Object}
155
- */
156
- export function parseFilePath(filePath) {
157
- const normalized = filePath.replace(/\\/g, '/');
158
- const parts = normalized.split('/');
159
- const fileName = parts.pop() || '';
160
- const dirPath = parts.join('/');
161
- const extMatch = fileName.match(/\.([^.]+)$/);
162
- const extension = extMatch ? extMatch[1] : '';
163
- const baseName = extension ? fileName.slice(0, -(extension.length + 1)) : fileName;
164
-
165
- return {
166
- fullPath: normalized,
167
- dirPath,
168
- fileName,
169
- baseName,
170
- extension,
171
- depth: parts.length
172
- };
173
- }
174
-
175
- /**
176
- * Score a match for prioritization
177
- * Higher score = better match
178
- * @param {string} str - String that matched
179
- * @param {string} pattern - Pattern that matched
180
- * @returns {number}
181
- */
182
- export function scoreMatch(str, pattern) {
183
- let score = 0;
184
-
185
- // Exact match = highest score
186
- if (str === pattern) {
187
- score += 100;
188
- }
189
-
190
- // Shorter patterns = more specific = higher score
191
- const patternComplexity = (pattern.match(/\*/g) || []).length;
192
- score += 50 - patternComplexity * 10;
193
-
194
- // File extension patterns are more specific
195
- if (pattern.startsWith('*.') && !pattern.includes('/')) {
196
- score += 20;
197
- }
198
-
199
- // Path patterns with specific directories
200
- if (pattern.includes('/') && !pattern.startsWith('**/')) {
201
- score += 15;
202
- }
203
-
204
- return Math.max(0, score);
205
- }
206
-
207
- export default {
208
- globToRegex,
209
- minimatch,
210
- minimatchFilter,
211
- minimatchAny,
212
- minimatchAll,
213
- createMatcher,
214
- matchExtension,
215
- matchPathPrefix,
216
- parseFilePath,
217
- scoreMatch
218
- };
1
+ /**
2
+ * Pattern Matcher - Glob and regex matching utilities
3
+ *
4
+ * Provides minimatch-like glob matching without external dependencies.
5
+ * Supports common glob patterns used in trigger evaluation.
6
+ */
7
+
8
+ /**
9
+ * Convert glob pattern to regex
10
+ * @param {string} pattern - Glob pattern
11
+ * @returns {RegExp}
12
+ */
13
+ export function globToRegex(pattern) {
14
+ // Escape special regex characters except glob wildcards
15
+ let regex = pattern
16
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
17
+ // ** matches any path segments
18
+ .replace(/\*\*/g, '{{GLOBSTAR}}')
19
+ // * matches any characters except path separator
20
+ .replace(/\*/g, '[^/\\\\]*')
21
+ // ? matches single character
22
+ .replace(/\?/g, '[^/\\\\]')
23
+ // Restore globstar
24
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*');
25
+
26
+ // Handle brace expansion {a,b,c}
27
+ if (regex.includes('\\{') && regex.includes('\\}')) {
28
+ regex = regex.replace(/\\\{([^}]+)\\\}/g, (_, options) => {
29
+ const parts = options.split(',').map(p => p.trim());
30
+ return `(?:${parts.join('|')})`;
31
+ });
32
+ }
33
+
34
+ return new RegExp(`^${regex}$`, 'i');
35
+ }
36
+
37
+ /**
38
+ * Test if a string matches a glob pattern
39
+ * @param {string} str - String to test
40
+ * @param {string} pattern - Glob pattern
41
+ * @param {Object} [options] - Match options
42
+ * @param {boolean} [options.dot=false] - Match dotfiles
43
+ * @param {boolean} [options.nocase=true] - Case insensitive
44
+ * @returns {boolean}
45
+ */
46
+ export function minimatch(str, pattern, options = {}) {
47
+ const { dot = false, nocase = true } = options;
48
+
49
+ // Normalize path separators
50
+ const normalizedStr = str.replace(/\\/g, '/');
51
+ const normalizedPattern = pattern.replace(/\\/g, '/');
52
+
53
+ // Handle negation
54
+ if (normalizedPattern.startsWith('!')) {
55
+ return !minimatch(normalizedStr, normalizedPattern.slice(1), options);
56
+ }
57
+
58
+ // Skip dotfiles unless dot option is true
59
+ if (!dot && normalizedStr.split('/').some(part => part.startsWith('.'))) {
60
+ // Allow if pattern explicitly matches dotfiles
61
+ if (!normalizedPattern.includes('/.') && !normalizedPattern.startsWith('.')) {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ try {
67
+ const regex = globToRegex(normalizedPattern);
68
+ if (nocase) {
69
+ return new RegExp(regex.source, 'i').test(normalizedStr);
70
+ }
71
+ return regex.test(normalizedStr);
72
+ } catch {
73
+ // Fallback to simple string matching
74
+ return normalizedStr === normalizedPattern;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Find all matching items from a list
80
+ * @param {string[]} list - List of strings to match against
81
+ * @param {string} pattern - Glob pattern
82
+ * @param {Object} [options] - Match options
83
+ * @returns {string[]}
84
+ */
85
+ export function minimatchFilter(list, pattern, options = {}) {
86
+ return list.filter(item => minimatch(item, pattern, options));
87
+ }
88
+
89
+ /**
90
+ * Test if string matches any of the patterns
91
+ * @param {string} str - String to test
92
+ * @param {string[]} patterns - Array of glob patterns
93
+ * @param {Object} [options] - Match options
94
+ * @returns {boolean}
95
+ */
96
+ export function minimatchAny(str, patterns, options = {}) {
97
+ return patterns.some(pattern => minimatch(str, pattern, options));
98
+ }
99
+
100
+ /**
101
+ * Test if string matches all of the patterns
102
+ * @param {string} str - String to test
103
+ * @param {string[]} patterns - Array of glob patterns
104
+ * @param {Object} [options] - Match options
105
+ * @returns {boolean}
106
+ */
107
+ export function minimatchAll(str, patterns, options = {}) {
108
+ return patterns.every(pattern => minimatch(str, pattern, options));
109
+ }
110
+
111
+ /**
112
+ * Create a matcher function for a pattern
113
+ * @param {string} pattern - Glob pattern
114
+ * @param {Object} [options] - Match options
115
+ * @returns {Function} Matcher function
116
+ */
117
+ export function createMatcher(pattern, options = {}) {
118
+ return (str) => minimatch(str, pattern, options);
119
+ }
120
+
121
+ /**
122
+ * Match file extension
123
+ * @param {string} fileName - File name or path
124
+ * @param {string|string[]} extensions - Extension(s) to match (with or without dot)
125
+ * @returns {boolean}
126
+ */
127
+ export function matchExtension(fileName, extensions) {
128
+ const exts = Array.isArray(extensions) ? extensions : [extensions];
129
+ const normalizedExts = exts.map(ext => ext.startsWith('.') ? ext : `.${ext}`);
130
+ const fileExt = fileName.includes('.') ? '.' + fileName.split('.').pop() : '';
131
+
132
+ return normalizedExts.some(ext => ext.toLowerCase() === fileExt.toLowerCase());
133
+ }
134
+
135
+ /**
136
+ * Match path prefix
137
+ * @param {string} filePath - File path
138
+ * @param {string|string[]} prefixes - Path prefix(es) to match
139
+ * @returns {boolean}
140
+ */
141
+ export function matchPathPrefix(filePath, prefixes) {
142
+ const prfxs = Array.isArray(prefixes) ? prefixes : [prefixes];
143
+ const normalizedPath = filePath.replace(/\\/g, '/');
144
+
145
+ return prfxs.some(prefix => {
146
+ const normalizedPrefix = prefix.replace(/\\/g, '/');
147
+ return normalizedPath.startsWith(normalizedPrefix);
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Extract file parts for matching
153
+ * @param {string} filePath - File path
154
+ * @returns {Object}
155
+ */
156
+ export function parseFilePath(filePath) {
157
+ const normalized = filePath.replace(/\\/g, '/');
158
+ const parts = normalized.split('/');
159
+ const fileName = parts.pop() || '';
160
+ const dirPath = parts.join('/');
161
+ const extMatch = fileName.match(/\.([^.]+)$/);
162
+ const extension = extMatch ? extMatch[1] : '';
163
+ const baseName = extension ? fileName.slice(0, -(extension.length + 1)) : fileName;
164
+
165
+ return {
166
+ fullPath: normalized,
167
+ dirPath,
168
+ fileName,
169
+ baseName,
170
+ extension,
171
+ depth: parts.length
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Score a match for prioritization
177
+ * Higher score = better match
178
+ * @param {string} str - String that matched
179
+ * @param {string} pattern - Pattern that matched
180
+ * @returns {number}
181
+ */
182
+ export function scoreMatch(str, pattern) {
183
+ let score = 0;
184
+
185
+ // Exact match = highest score
186
+ if (str === pattern) {
187
+ score += 100;
188
+ }
189
+
190
+ // Shorter patterns = more specific = higher score
191
+ const patternComplexity = (pattern.match(/\*/g) || []).length;
192
+ score += 50 - patternComplexity * 10;
193
+
194
+ // File extension patterns are more specific
195
+ if (pattern.startsWith('*.') && !pattern.includes('/')) {
196
+ score += 20;
197
+ }
198
+
199
+ // Path patterns with specific directories
200
+ if (pattern.includes('/') && !pattern.startsWith('**/')) {
201
+ score += 15;
202
+ }
203
+
204
+ return Math.max(0, score);
205
+ }
206
+
207
+ export default {
208
+ globToRegex,
209
+ minimatch,
210
+ minimatchFilter,
211
+ minimatchAny,
212
+ minimatchAll,
213
+ createMatcher,
214
+ matchExtension,
215
+ matchPathPrefix,
216
+ parseFilePath,
217
+ scoreMatch
218
+ };