@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
package/src/core/state.js CHANGED
@@ -1,91 +1,91 @@
1
- import { readFileSync, writeFileSync, existsSync } from 'fs';
2
- import { join } from 'path';
3
- import { execSync } from 'child_process';
4
-
5
- /**
6
- * Get the path to state.json in a workspace
7
- * @param {string} cwd - Working directory (defaults to process.cwd())
8
- * @returns {string} Path to state.json
9
- */
10
- export function getStatePath(cwd = process.cwd()) {
11
- return join(cwd, '.ai', 'state.json');
12
- }
13
-
14
- /**
15
- * Check if a PWN workspace exists in the given directory
16
- * @param {string} cwd - Working directory
17
- * @returns {boolean}
18
- */
19
- export function hasWorkspace(cwd = process.cwd()) {
20
- return existsSync(join(cwd, '.ai'));
21
- }
22
-
23
- /**
24
- * Get the current git username
25
- * @returns {string} Git username or 'unknown'
26
- */
27
- export function getGitUser() {
28
- try {
29
- return execSync('git config user.name', { encoding: 'utf8' }).trim();
30
- } catch {
31
- return 'unknown';
32
- }
33
- }
34
-
35
- /**
36
- * Read the current state from state.json
37
- * @param {string} cwd - Working directory
38
- * @returns {object|null} State object or null if not found
39
- */
40
- export function getState(cwd = process.cwd()) {
41
- const statePath = getStatePath(cwd);
42
-
43
- if (!existsSync(statePath)) {
44
- return null;
45
- }
46
-
47
- try {
48
- const content = readFileSync(statePath, 'utf8');
49
- return JSON.parse(content);
50
- } catch {
51
- return null;
52
- }
53
- }
54
-
55
- /**
56
- * Update the state.json file
57
- * @param {object} updates - Fields to update
58
- * @param {string} cwd - Working directory
59
- * @returns {object} Updated state
60
- */
61
- export function updateState(updates, cwd = process.cwd()) {
62
- const statePath = getStatePath(cwd);
63
- const currentState = getState(cwd) || {};
64
-
65
- const newState = {
66
- ...currentState,
67
- ...updates,
68
- last_updated: new Date().toISOString()
69
- };
70
-
71
- writeFileSync(statePath, JSON.stringify(newState, null, 2));
72
- return newState;
73
- }
74
-
75
- /**
76
- * Initialize a new state.json file
77
- * @param {string} cwd - Working directory
78
- * @returns {object} Initial state
79
- */
80
- export function initState(cwd = process.cwd()) {
81
- const state = {
82
- developer: getGitUser(),
83
- session_started: new Date().toISOString(),
84
- current_task: null,
85
- context_loaded: []
86
- };
87
-
88
- const statePath = getStatePath(cwd);
89
- writeFileSync(statePath, JSON.stringify(state, null, 2));
90
- return state;
91
- }
1
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { execSync } from 'child_process';
4
+
5
+ /**
6
+ * Get the path to state.json in a workspace
7
+ * @param {string} cwd - Working directory (defaults to process.cwd())
8
+ * @returns {string} Path to state.json
9
+ */
10
+ export function getStatePath(cwd = process.cwd()) {
11
+ return join(cwd, '.ai', 'state.json');
12
+ }
13
+
14
+ /**
15
+ * Check if a PWN workspace exists in the given directory
16
+ * @param {string} cwd - Working directory
17
+ * @returns {boolean}
18
+ */
19
+ export function hasWorkspace(cwd = process.cwd()) {
20
+ return existsSync(join(cwd, '.ai'));
21
+ }
22
+
23
+ /**
24
+ * Get the current git username
25
+ * @returns {string} Git username or 'unknown'
26
+ */
27
+ export function getGitUser() {
28
+ try {
29
+ return execSync('git config user.name', { encoding: 'utf8' }).trim();
30
+ } catch {
31
+ return 'unknown';
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Read the current state from state.json
37
+ * @param {string} cwd - Working directory
38
+ * @returns {object|null} State object or null if not found
39
+ */
40
+ export function getState(cwd = process.cwd()) {
41
+ const statePath = getStatePath(cwd);
42
+
43
+ if (!existsSync(statePath)) {
44
+ return null;
45
+ }
46
+
47
+ try {
48
+ const content = readFileSync(statePath, 'utf8');
49
+ return JSON.parse(content);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Update the state.json file
57
+ * @param {object} updates - Fields to update
58
+ * @param {string} cwd - Working directory
59
+ * @returns {object} Updated state
60
+ */
61
+ export function updateState(updates, cwd = process.cwd()) {
62
+ const statePath = getStatePath(cwd);
63
+ const currentState = getState(cwd) || {};
64
+
65
+ const newState = {
66
+ ...currentState,
67
+ ...updates,
68
+ last_updated: new Date().toISOString()
69
+ };
70
+
71
+ writeFileSync(statePath, JSON.stringify(newState, null, 2));
72
+ return newState;
73
+ }
74
+
75
+ /**
76
+ * Initialize a new state.json file
77
+ * @param {string} cwd - Working directory
78
+ * @returns {object} Initial state
79
+ */
80
+ export function initState(cwd = process.cwd()) {
81
+ const state = {
82
+ developer: getGitUser(),
83
+ session_started: new Date().toISOString(),
84
+ current_task: null,
85
+ context_loaded: []
86
+ };
87
+
88
+ const statePath = getStatePath(cwd);
89
+ writeFileSync(statePath, JSON.stringify(state, null, 2));
90
+ return state;
91
+ }
@@ -1,202 +1,202 @@
1
- import { existsSync, statSync, readFileSync } from 'fs';
2
- import { join } from 'path';
3
-
4
- /**
5
- * Expected workspace structure
6
- */
7
- const REQUIRED_STRUCTURE = {
8
- files: [
9
- 'README.md',
10
- 'state.json'
11
- ],
12
- directories: [
13
- 'memory',
14
- 'tasks',
15
- 'patterns',
16
- 'workflows',
17
- 'agents',
18
- 'config'
19
- ],
20
- memoryFiles: [
21
- 'memory/decisions.md',
22
- 'memory/patterns.md',
23
- 'memory/deadends.md'
24
- ],
25
- taskFiles: [
26
- 'tasks/active.md',
27
- 'tasks/backlog.md'
28
- ],
29
- agentFiles: [
30
- 'agents/README.md',
31
- 'agents/claude.md'
32
- ],
33
- patternFiles: [
34
- 'patterns/index.md'
35
- ]
36
- };
37
-
38
- /**
39
- * Validate a PWN workspace structure
40
- * @param {string} cwd - Working directory
41
- * @returns {object} Validation result with issues array
42
- */
43
- export function validate(cwd = process.cwd()) {
44
- const aiDir = join(cwd, '.ai');
45
- const issues = [];
46
- const warnings = [];
47
-
48
- // Check if .ai/ exists
49
- if (!existsSync(aiDir)) {
50
- return {
51
- valid: false,
52
- issues: ['No .ai/ directory found. Run: pwn inject'],
53
- warnings: []
54
- };
55
- }
56
-
57
- // Check required files
58
- for (const file of REQUIRED_STRUCTURE.files) {
59
- const filePath = join(aiDir, file);
60
- if (!existsSync(filePath)) {
61
- issues.push(`Missing file: .ai/${file}`);
62
- }
63
- }
64
-
65
- // Check required directories
66
- for (const dir of REQUIRED_STRUCTURE.directories) {
67
- const dirPath = join(aiDir, dir);
68
- if (!existsSync(dirPath)) {
69
- issues.push(`Missing directory: .ai/${dir}/`);
70
- } else if (!statSync(dirPath).isDirectory()) {
71
- issues.push(`.ai/${dir} exists but is not a directory`);
72
- }
73
- }
74
-
75
- // Check memory files
76
- for (const file of REQUIRED_STRUCTURE.memoryFiles) {
77
- const filePath = join(aiDir, file);
78
- if (!existsSync(filePath)) {
79
- warnings.push(`Missing memory file: .ai/${file}`);
80
- }
81
- }
82
-
83
- // Check task files
84
- for (const file of REQUIRED_STRUCTURE.taskFiles) {
85
- const filePath = join(aiDir, file);
86
- if (!existsSync(filePath)) {
87
- warnings.push(`Missing task file: .ai/${file}`);
88
- }
89
- }
90
-
91
- // Check agent files
92
- for (const file of REQUIRED_STRUCTURE.agentFiles) {
93
- const filePath = join(aiDir, file);
94
- if (!existsSync(filePath)) {
95
- warnings.push(`Missing agent file: .ai/${file}`);
96
- }
97
- }
98
-
99
- // Check pattern files
100
- for (const file of REQUIRED_STRUCTURE.patternFiles) {
101
- const filePath = join(aiDir, file);
102
- if (!existsSync(filePath)) {
103
- warnings.push(`Missing pattern file: .ai/${file}`);
104
- }
105
- }
106
-
107
- // Validate state.json format
108
- const stateValidation = validateStateJson(aiDir);
109
- if (!stateValidation.valid) {
110
- issues.push(...stateValidation.issues);
111
- }
112
-
113
- return {
114
- valid: issues.length === 0,
115
- issues,
116
- warnings
117
- };
118
- }
119
-
120
- /**
121
- * Validate state.json structure and content
122
- * @param {string} aiDir - Path to .ai directory
123
- * @returns {object} Validation result
124
- */
125
- function validateStateJson(aiDir) {
126
- const statePath = join(aiDir, 'state.json');
127
- const issues = [];
128
-
129
- if (!existsSync(statePath)) {
130
- return { valid: false, issues: ['state.json not found'] };
131
- }
132
-
133
- try {
134
- const content = readFileSync(statePath, 'utf8');
135
- const state = JSON.parse(content);
136
-
137
- // Check required fields
138
- const requiredFields = ['developer', 'session_started'];
139
- for (const field of requiredFields) {
140
- if (!(field in state)) {
141
- issues.push(`state.json missing required field: ${field}`);
142
- }
143
- }
144
-
145
- // Validate date format
146
- if (state.session_started && isNaN(Date.parse(state.session_started))) {
147
- issues.push('state.json: session_started is not a valid date');
148
- }
149
-
150
- } catch (error) {
151
- if (error instanceof SyntaxError) {
152
- issues.push('state.json contains invalid JSON');
153
- } else {
154
- issues.push(`state.json read error: ${error.message}`);
155
- }
156
- }
157
-
158
- return {
159
- valid: issues.length === 0,
160
- issues
161
- };
162
- }
163
-
164
- /**
165
- * Get a detailed report of workspace structure
166
- * @param {string} cwd - Working directory
167
- * @returns {object} Structure report
168
- */
169
- export function getStructureReport(cwd = process.cwd()) {
170
- const aiDir = join(cwd, '.ai');
171
- const report = {
172
- exists: existsSync(aiDir),
173
- directories: {},
174
- files: {}
175
- };
176
-
177
- if (!report.exists) {
178
- return report;
179
- }
180
-
181
- // Check directories
182
- for (const dir of REQUIRED_STRUCTURE.directories) {
183
- const dirPath = join(aiDir, dir);
184
- report.directories[dir] = existsSync(dirPath) && statSync(dirPath).isDirectory();
185
- }
186
-
187
- // Check all expected files
188
- const allFiles = [
189
- ...REQUIRED_STRUCTURE.files,
190
- ...REQUIRED_STRUCTURE.memoryFiles,
191
- ...REQUIRED_STRUCTURE.taskFiles,
192
- ...REQUIRED_STRUCTURE.agentFiles,
193
- ...REQUIRED_STRUCTURE.patternFiles
194
- ];
195
-
196
- for (const file of allFiles) {
197
- const filePath = join(aiDir, file);
198
- report.files[file] = existsSync(filePath);
199
- }
200
-
201
- return report;
202
- }
1
+ import { existsSync, statSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ /**
5
+ * Expected workspace structure
6
+ */
7
+ const REQUIRED_STRUCTURE = {
8
+ files: [
9
+ 'README.md',
10
+ 'state.json'
11
+ ],
12
+ directories: [
13
+ 'memory',
14
+ 'tasks',
15
+ 'patterns',
16
+ 'workflows',
17
+ 'agents',
18
+ 'config'
19
+ ],
20
+ memoryFiles: [
21
+ 'memory/decisions.md',
22
+ 'memory/patterns.md',
23
+ 'memory/deadends.md'
24
+ ],
25
+ taskFiles: [
26
+ 'tasks/active.md',
27
+ 'tasks/backlog.md'
28
+ ],
29
+ agentFiles: [
30
+ 'agents/README.md',
31
+ 'agents/claude.md'
32
+ ],
33
+ patternFiles: [
34
+ 'patterns/index.md'
35
+ ]
36
+ };
37
+
38
+ /**
39
+ * Validate a PWN workspace structure
40
+ * @param {string} cwd - Working directory
41
+ * @returns {object} Validation result with issues array
42
+ */
43
+ export function validate(cwd = process.cwd()) {
44
+ const aiDir = join(cwd, '.ai');
45
+ const issues = [];
46
+ const warnings = [];
47
+
48
+ // Check if .ai/ exists
49
+ if (!existsSync(aiDir)) {
50
+ return {
51
+ valid: false,
52
+ issues: ['No .ai/ directory found. Run: pwn inject'],
53
+ warnings: []
54
+ };
55
+ }
56
+
57
+ // Check required files
58
+ for (const file of REQUIRED_STRUCTURE.files) {
59
+ const filePath = join(aiDir, file);
60
+ if (!existsSync(filePath)) {
61
+ issues.push(`Missing file: .ai/${file}`);
62
+ }
63
+ }
64
+
65
+ // Check required directories
66
+ for (const dir of REQUIRED_STRUCTURE.directories) {
67
+ const dirPath = join(aiDir, dir);
68
+ if (!existsSync(dirPath)) {
69
+ issues.push(`Missing directory: .ai/${dir}/`);
70
+ } else if (!statSync(dirPath).isDirectory()) {
71
+ issues.push(`.ai/${dir} exists but is not a directory`);
72
+ }
73
+ }
74
+
75
+ // Check memory files
76
+ for (const file of REQUIRED_STRUCTURE.memoryFiles) {
77
+ const filePath = join(aiDir, file);
78
+ if (!existsSync(filePath)) {
79
+ warnings.push(`Missing memory file: .ai/${file}`);
80
+ }
81
+ }
82
+
83
+ // Check task files
84
+ for (const file of REQUIRED_STRUCTURE.taskFiles) {
85
+ const filePath = join(aiDir, file);
86
+ if (!existsSync(filePath)) {
87
+ warnings.push(`Missing task file: .ai/${file}`);
88
+ }
89
+ }
90
+
91
+ // Check agent files
92
+ for (const file of REQUIRED_STRUCTURE.agentFiles) {
93
+ const filePath = join(aiDir, file);
94
+ if (!existsSync(filePath)) {
95
+ warnings.push(`Missing agent file: .ai/${file}`);
96
+ }
97
+ }
98
+
99
+ // Check pattern files
100
+ for (const file of REQUIRED_STRUCTURE.patternFiles) {
101
+ const filePath = join(aiDir, file);
102
+ if (!existsSync(filePath)) {
103
+ warnings.push(`Missing pattern file: .ai/${file}`);
104
+ }
105
+ }
106
+
107
+ // Validate state.json format
108
+ const stateValidation = validateStateJson(aiDir);
109
+ if (!stateValidation.valid) {
110
+ issues.push(...stateValidation.issues);
111
+ }
112
+
113
+ return {
114
+ valid: issues.length === 0,
115
+ issues,
116
+ warnings
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Validate state.json structure and content
122
+ * @param {string} aiDir - Path to .ai directory
123
+ * @returns {object} Validation result
124
+ */
125
+ function validateStateJson(aiDir) {
126
+ const statePath = join(aiDir, 'state.json');
127
+ const issues = [];
128
+
129
+ if (!existsSync(statePath)) {
130
+ return { valid: false, issues: ['state.json not found'] };
131
+ }
132
+
133
+ try {
134
+ const content = readFileSync(statePath, 'utf8');
135
+ const state = JSON.parse(content);
136
+
137
+ // Check required fields
138
+ const requiredFields = ['developer', 'session_started'];
139
+ for (const field of requiredFields) {
140
+ if (!(field in state)) {
141
+ issues.push(`state.json missing required field: ${field}`);
142
+ }
143
+ }
144
+
145
+ // Validate date format
146
+ if (state.session_started && isNaN(Date.parse(state.session_started))) {
147
+ issues.push('state.json: session_started is not a valid date');
148
+ }
149
+
150
+ } catch (error) {
151
+ if (error instanceof SyntaxError) {
152
+ issues.push('state.json contains invalid JSON');
153
+ } else {
154
+ issues.push(`state.json read error: ${error.message}`);
155
+ }
156
+ }
157
+
158
+ return {
159
+ valid: issues.length === 0,
160
+ issues
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Get a detailed report of workspace structure
166
+ * @param {string} cwd - Working directory
167
+ * @returns {object} Structure report
168
+ */
169
+ export function getStructureReport(cwd = process.cwd()) {
170
+ const aiDir = join(cwd, '.ai');
171
+ const report = {
172
+ exists: existsSync(aiDir),
173
+ directories: {},
174
+ files: {}
175
+ };
176
+
177
+ if (!report.exists) {
178
+ return report;
179
+ }
180
+
181
+ // Check directories
182
+ for (const dir of REQUIRED_STRUCTURE.directories) {
183
+ const dirPath = join(aiDir, dir);
184
+ report.directories[dir] = existsSync(dirPath) && statSync(dirPath).isDirectory();
185
+ }
186
+
187
+ // Check all expected files
188
+ const allFiles = [
189
+ ...REQUIRED_STRUCTURE.files,
190
+ ...REQUIRED_STRUCTURE.memoryFiles,
191
+ ...REQUIRED_STRUCTURE.taskFiles,
192
+ ...REQUIRED_STRUCTURE.agentFiles,
193
+ ...REQUIRED_STRUCTURE.patternFiles
194
+ ];
195
+
196
+ for (const file of allFiles) {
197
+ const filePath = join(aiDir, file);
198
+ report.files[file] = existsSync(filePath);
199
+ }
200
+
201
+ return report;
202
+ }