@fermindi/pwn-cli 0.1.0 → 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 (46) 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 +98 -91
  6. package/cli/inject.js +78 -53
  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/status.js +91 -91
  12. package/cli/validate.js +61 -61
  13. package/package.json +70 -70
  14. package/src/core/inject.js +208 -128
  15. package/src/core/state.js +91 -91
  16. package/src/core/validate.js +202 -202
  17. package/src/core/workspace.js +176 -176
  18. package/src/index.js +20 -20
  19. package/src/knowledge/gc.js +308 -308
  20. package/src/knowledge/lifecycle.js +401 -401
  21. package/src/knowledge/promote.js +364 -364
  22. package/src/knowledge/references.js +342 -342
  23. package/src/patterns/matcher.js +218 -218
  24. package/src/patterns/registry.js +375 -375
  25. package/src/patterns/triggers.js +423 -423
  26. package/src/services/batch-service.js +849 -849
  27. package/src/services/notification-service.js +342 -342
  28. package/templates/codespaces/devcontainer.json +52 -52
  29. package/templates/codespaces/setup.sh +70 -70
  30. package/templates/workspace/.ai/README.md +164 -164
  31. package/templates/workspace/.ai/agents/README.md +204 -204
  32. package/templates/workspace/.ai/agents/claude.md +625 -625
  33. package/templates/workspace/.ai/config/README.md +79 -79
  34. package/templates/workspace/.ai/config/notifications.template.json +20 -20
  35. package/templates/workspace/.ai/memory/deadends.md +79 -79
  36. package/templates/workspace/.ai/memory/decisions.md +58 -58
  37. package/templates/workspace/.ai/memory/patterns.md +65 -65
  38. package/templates/workspace/.ai/patterns/backend/README.md +126 -126
  39. package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
  40. package/templates/workspace/.ai/patterns/index.md +256 -256
  41. package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
  42. package/templates/workspace/.ai/patterns/universal/README.md +141 -141
  43. package/templates/workspace/.ai/state.template.json +8 -8
  44. package/templates/workspace/.ai/tasks/active.md +77 -77
  45. package/templates/workspace/.ai/tasks/backlog.md +95 -95
  46. 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
+ };