@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.
- 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 +112 -91
- package/cli/inject.js +90 -67
- 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/save.js +206 -0
- package/cli/status.js +91 -91
- package/cli/update.js +189 -0
- package/cli/validate.js +61 -61
- package/package.json +70 -70
- package/src/core/inject.js +300 -204
- 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/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
|
+
}
|
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
|
+
}
|