@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.
- package/LICENSE +21 -21
- package/README.md +265 -251
- package/cli/batch.js +333 -333
- package/cli/codespaces.js +303 -303
- package/cli/index.js +98 -91
- package/cli/inject.js +78 -53
- package/cli/knowledge.js +531 -531
- package/cli/migrate.js +466 -0
- package/cli/notify.js +135 -135
- package/cli/patterns.js +665 -665
- package/cli/status.js +91 -91
- package/cli/validate.js +61 -61
- package/package.json +70 -70
- package/src/core/inject.js +208 -128
- package/src/core/state.js +91 -91
- package/src/core/validate.js +202 -202
- package/src/core/workspace.js +176 -176
- package/src/index.js +20 -20
- package/src/knowledge/gc.js +308 -308
- package/src/knowledge/lifecycle.js +401 -401
- package/src/knowledge/promote.js +364 -364
- package/src/knowledge/references.js +342 -342
- package/src/patterns/matcher.js +218 -218
- package/src/patterns/registry.js +375 -375
- package/src/patterns/triggers.js +423 -423
- package/src/services/batch-service.js +849 -849
- package/src/services/notification-service.js +342 -342
- package/templates/codespaces/devcontainer.json +52 -52
- package/templates/codespaces/setup.sh +70 -70
- package/templates/workspace/.ai/README.md +164 -164
- package/templates/workspace/.ai/agents/README.md +204 -204
- package/templates/workspace/.ai/agents/claude.md +625 -625
- package/templates/workspace/.ai/config/README.md +79 -79
- package/templates/workspace/.ai/config/notifications.template.json +20 -20
- package/templates/workspace/.ai/memory/deadends.md +79 -79
- package/templates/workspace/.ai/memory/decisions.md +58 -58
- package/templates/workspace/.ai/memory/patterns.md +65 -65
- package/templates/workspace/.ai/patterns/backend/README.md +126 -126
- package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
- package/templates/workspace/.ai/patterns/index.md +256 -256
- package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
- package/templates/workspace/.ai/patterns/universal/README.md +141 -141
- package/templates/workspace/.ai/state.template.json +8 -8
- package/templates/workspace/.ai/tasks/active.md +77 -77
- package/templates/workspace/.ai/tasks/backlog.md +95 -95
- package/templates/workspace/.ai/workflows/batch-task.md +356 -356
package/src/core/validate.js
CHANGED
|
@@ -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
|
+
}
|