@intoinside/praxis 1.0.1
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/CODE_OF_CONDUCT.md +99 -0
- package/CONTRIBUTING.md +108 -0
- package/LICENSE +21 -0
- package/MAINTAINERS.md +9 -0
- package/README.md +319 -0
- package/intoinside-praxis-1.0.0.tgz +0 -0
- package/last_error.txt +42 -0
- package/package.json +40 -0
- package/src/commands/init.d.ts +1 -0
- package/src/commands/init.js +42 -0
- package/src/commands/init.ts +47 -0
- package/src/commands/integration/generate.d.ts +4 -0
- package/src/commands/integration/generate.js +69 -0
- package/src/commands/integration/generate.ts +85 -0
- package/src/commands/intent/create.d.ts +4 -0
- package/src/commands/intent/create.js +55 -0
- package/src/commands/intent/create.ts +61 -0
- package/src/commands/intent/list.d.ts +4 -0
- package/src/commands/intent/list.js +52 -0
- package/src/commands/intent/list.ts +63 -0
- package/src/commands/spec/apply.d.ts +4 -0
- package/src/commands/spec/apply.js +99 -0
- package/src/commands/spec/apply.ts +109 -0
- package/src/commands/spec/archive.d.ts +4 -0
- package/src/commands/spec/archive.js +114 -0
- package/src/commands/spec/archive.ts +121 -0
- package/src/commands/spec/delete.d.ts +4 -0
- package/src/commands/spec/delete.js +70 -0
- package/src/commands/spec/delete.ts +75 -0
- package/src/commands/spec/derive.d.ts +6 -0
- package/src/commands/spec/derive.js +107 -0
- package/src/commands/spec/derive.ts +117 -0
- package/src/core/command-generation/adapters/antigravity.d.ts +12 -0
- package/src/core/command-generation/adapters/antigravity.js +25 -0
- package/src/core/command-generation/adapters/antigravity.ts +30 -0
- package/src/core/command-generation/adapters/index.d.ts +6 -0
- package/src/core/command-generation/adapters/index.js +26 -0
- package/src/core/command-generation/adapters/index.ts +27 -0
- package/src/core/command-generation/generator.d.ts +20 -0
- package/src/core/command-generation/generator.js +26 -0
- package/src/core/command-generation/generator.ts +36 -0
- package/src/core/command-generation/index.d.ts +20 -0
- package/src/core/command-generation/index.js +23 -0
- package/src/core/command-generation/index.ts +33 -0
- package/src/core/command-generation/registry.d.ts +35 -0
- package/src/core/command-generation/registry.js +87 -0
- package/src/core/command-generation/registry.ts +95 -0
- package/src/core/command-generation/types.d.ts +54 -0
- package/src/core/command-generation/types.js +7 -0
- package/src/core/command-generation/types.ts +57 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +95 -0
- package/src/index.ts +97 -0
- package/src/manifest.d.ts +21 -0
- package/src/manifest.js +170 -0
- package/src/manifest.ts +188 -0
- package/templates/checklist-template.md +42 -0
- package/templates/intent-template.md +290 -0
- package/templates/spec-template.md +114 -0
- package/test_output.txt +0 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Action for 'praxis spec archive <spec-id>'
|
|
5
|
+
*/
|
|
6
|
+
export async function specArchiveAction(specId) {
|
|
7
|
+
const rootDir = process.cwd();
|
|
8
|
+
const specsDir = path.join(rootDir, '.praxis', 'specs');
|
|
9
|
+
const archiveDirBase = path.join(specsDir, 'archive');
|
|
10
|
+
// 1. Validate environment
|
|
11
|
+
if (!fs.existsSync(specsDir)) {
|
|
12
|
+
console.error('Error: Specs directory not found (.praxis/specs).');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// 2. Find spec directory
|
|
16
|
+
const specDir = findSpecDirectory(specsDir, specId);
|
|
17
|
+
if (!specDir) {
|
|
18
|
+
console.error(`Error: Spec with ID '${specId}' not found.`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Ensure we don't try to archive something already archived (though findSpecDirectory might exclude archive subfolder if implemented carefully)
|
|
22
|
+
if (specDir.includes(path.sep + 'archive' + path.sep)) {
|
|
23
|
+
console.error(`Error: Spec '${specId}' is already archived.`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// 3. Update status to Archived in spec.md
|
|
27
|
+
const specMdPath = path.join(specDir, 'spec.md');
|
|
28
|
+
if (fs.existsSync(specMdPath)) {
|
|
29
|
+
try {
|
|
30
|
+
let content = fs.readFileSync(specMdPath, 'utf-8');
|
|
31
|
+
// Simple regex to replace State: ... with State: Archived
|
|
32
|
+
// Supporting both "State: Draft" and "State:Draft"
|
|
33
|
+
const newState = 'State: Archived';
|
|
34
|
+
if (content.match(/^State:\s*.*$/m)) {
|
|
35
|
+
content = content.replace(/^State:\s*.*$/m, newState);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// If no state found, maybe just append it or warn?
|
|
39
|
+
// Most specs should have a state. Let's prepend it if it looks like frontmatter.
|
|
40
|
+
content = newState + '\n' + content;
|
|
41
|
+
}
|
|
42
|
+
fs.writeFileSync(specMdPath, content, 'utf-8');
|
|
43
|
+
console.log(`Updated status to '${newState}' in ${specMdPath}`);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error(`Warning: Could not update status in spec.md: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// 4. Archive
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(archiveDirBase)) {
|
|
52
|
+
fs.mkdirSync(archiveDirBase, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
const targetDir = path.join(archiveDirBase, specId);
|
|
55
|
+
// Handle collision in archive
|
|
56
|
+
if (fs.existsSync(targetDir)) {
|
|
57
|
+
const timestamp = new Date().getTime();
|
|
58
|
+
const collisionDir = path.join(archiveDirBase, `${specId}_${timestamp}`);
|
|
59
|
+
console.warn(`Warning: Archive for '${specId}' already exists. Moving to ${collisionDir} instead.`);
|
|
60
|
+
fs.renameSync(specDir, collisionDir);
|
|
61
|
+
console.log(`Successfully archived spec '${specId}' to ${collisionDir}.`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(`Archiving spec '${specId}' from ${specDir} to ${targetDir}...`);
|
|
65
|
+
fs.renameSync(specDir, targetDir);
|
|
66
|
+
console.log(`Successfully archived spec '${specId}'.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof Error) {
|
|
71
|
+
console.error(`Error archiving spec '${specId}': ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.error(`Error archiving spec '${specId}': Unknown error`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function findSpecDirectory(dir, targetId) {
|
|
79
|
+
try {
|
|
80
|
+
// Skip the archive directory itself during search
|
|
81
|
+
if (path.basename(dir) === 'archive') {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const list = fs.readdirSync(dir);
|
|
85
|
+
// Check if direct match exists
|
|
86
|
+
const directPath = path.join(dir, targetId);
|
|
87
|
+
if (fs.existsSync(directPath) && fs.statSync(directPath).isDirectory()) {
|
|
88
|
+
if (fs.existsSync(path.join(directPath, 'spec.md'))) {
|
|
89
|
+
return directPath;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Recursive search
|
|
93
|
+
for (const file of list) {
|
|
94
|
+
const filePath = path.join(dir, file);
|
|
95
|
+
const stat = fs.statSync(filePath);
|
|
96
|
+
if (stat.isDirectory()) {
|
|
97
|
+
// If the directory name matches targetId
|
|
98
|
+
if (file === targetId) {
|
|
99
|
+
if (fs.existsSync(path.join(filePath, 'spec.md'))) {
|
|
100
|
+
return filePath;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Recurse
|
|
104
|
+
const found = findSpecDirectory(filePath, targetId);
|
|
105
|
+
if (found)
|
|
106
|
+
return found;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
// Ignore permission errors etc
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Action for 'praxis spec archive <spec-id>'
|
|
6
|
+
*/
|
|
7
|
+
export async function specArchiveAction(specId: string) {
|
|
8
|
+
const rootDir = process.cwd();
|
|
9
|
+
const specsDir = path.join(rootDir, '.praxis', 'specs');
|
|
10
|
+
const archiveDirBase = path.join(specsDir, 'archive');
|
|
11
|
+
|
|
12
|
+
// 1. Validate environment
|
|
13
|
+
if (!fs.existsSync(specsDir)) {
|
|
14
|
+
console.error('Error: Specs directory not found (.praxis/specs).');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 2. Find spec directory
|
|
19
|
+
const specDir = findSpecDirectory(specsDir, specId);
|
|
20
|
+
if (!specDir) {
|
|
21
|
+
console.error(`Error: Spec with ID '${specId}' not found.`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Ensure we don't try to archive something already archived (though findSpecDirectory might exclude archive subfolder if implemented carefully)
|
|
26
|
+
if (specDir.includes(path.sep + 'archive' + path.sep)) {
|
|
27
|
+
console.error(`Error: Spec '${specId}' is already archived.`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 3. Update status to Archived in spec.md
|
|
32
|
+
const specMdPath = path.join(specDir, 'spec.md');
|
|
33
|
+
if (fs.existsSync(specMdPath)) {
|
|
34
|
+
try {
|
|
35
|
+
let content = fs.readFileSync(specMdPath, 'utf-8');
|
|
36
|
+
// Simple regex to replace State: ... with State: Archived
|
|
37
|
+
// Supporting both "State: Draft" and "State:Draft"
|
|
38
|
+
const newState = 'State: Archived';
|
|
39
|
+
if (content.match(/^State:\s*.*$/m)) {
|
|
40
|
+
content = content.replace(/^State:\s*.*$/m, newState);
|
|
41
|
+
} else {
|
|
42
|
+
// If no state found, maybe just append it or warn?
|
|
43
|
+
// Most specs should have a state. Let's prepend it if it looks like frontmatter.
|
|
44
|
+
content = newState + '\n' + content;
|
|
45
|
+
}
|
|
46
|
+
fs.writeFileSync(specMdPath, content, 'utf-8');
|
|
47
|
+
console.log(`Updated status to '${newState}' in ${specMdPath}`);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(`Warning: Could not update status in spec.md: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Archive
|
|
54
|
+
try {
|
|
55
|
+
if (!fs.existsSync(archiveDirBase)) {
|
|
56
|
+
fs.mkdirSync(archiveDirBase, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const targetDir = path.join(archiveDirBase, specId);
|
|
60
|
+
|
|
61
|
+
// Handle collision in archive
|
|
62
|
+
if (fs.existsSync(targetDir)) {
|
|
63
|
+
const timestamp = new Date().getTime();
|
|
64
|
+
const collisionDir = path.join(archiveDirBase, `${specId}_${timestamp}`);
|
|
65
|
+
console.warn(`Warning: Archive for '${specId}' already exists. Moving to ${collisionDir} instead.`);
|
|
66
|
+
fs.renameSync(specDir, collisionDir);
|
|
67
|
+
console.log(`Successfully archived spec '${specId}' to ${collisionDir}.`);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(`Archiving spec '${specId}' from ${specDir} to ${targetDir}...`);
|
|
70
|
+
fs.renameSync(specDir, targetDir);
|
|
71
|
+
console.log(`Successfully archived spec '${specId}'.`);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof Error) {
|
|
75
|
+
console.error(`Error archiving spec '${specId}': ${error.message}`);
|
|
76
|
+
} else {
|
|
77
|
+
console.error(`Error archiving spec '${specId}': Unknown error`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function findSpecDirectory(dir: string, targetId: string): string | null {
|
|
83
|
+
try {
|
|
84
|
+
// Skip the archive directory itself during search
|
|
85
|
+
if (path.basename(dir) === 'archive') {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const list = fs.readdirSync(dir);
|
|
90
|
+
|
|
91
|
+
// Check if direct match exists
|
|
92
|
+
const directPath = path.join(dir, targetId);
|
|
93
|
+
if (fs.existsSync(directPath) && fs.statSync(directPath).isDirectory()) {
|
|
94
|
+
if (fs.existsSync(path.join(directPath, 'spec.md'))) {
|
|
95
|
+
return directPath;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Recursive search
|
|
100
|
+
for (const file of list) {
|
|
101
|
+
const filePath = path.join(dir, file);
|
|
102
|
+
const stat = fs.statSync(filePath);
|
|
103
|
+
|
|
104
|
+
if (stat.isDirectory()) {
|
|
105
|
+
// If the directory name matches targetId
|
|
106
|
+
if (file === targetId) {
|
|
107
|
+
if (fs.existsSync(path.join(filePath, 'spec.md'))) {
|
|
108
|
+
return filePath;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Recurse
|
|
113
|
+
const found = findSpecDirectory(filePath, targetId);
|
|
114
|
+
if (found) return found;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch (e) {
|
|
118
|
+
// Ignore permission errors etc
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Action for 'praxis spec delete <spec-id>'
|
|
5
|
+
*/
|
|
6
|
+
export async function specDeleteAction(specId) {
|
|
7
|
+
const rootDir = process.cwd();
|
|
8
|
+
const specsDir = path.join(rootDir, '.praxis', 'specs');
|
|
9
|
+
// 1. Validate environment
|
|
10
|
+
if (!fs.existsSync(specsDir)) {
|
|
11
|
+
console.error('Error: Specs directory not found (.praxis/specs).');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
// 2. Find spec directory
|
|
15
|
+
const specDir = findSpecDirectory(specsDir, specId);
|
|
16
|
+
if (!specDir) {
|
|
17
|
+
console.error(`Error: Spec with ID '${specId}' not found.`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// 3. Delete
|
|
21
|
+
try {
|
|
22
|
+
console.log(`Deleting spec '${specId}' at ${specDir}...`);
|
|
23
|
+
fs.rmSync(specDir, { recursive: true, force: true });
|
|
24
|
+
console.log(`Successfully deleted spec '${specId}' and its artifacts.`);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error instanceof Error) {
|
|
28
|
+
console.error(`Error deleting spec '${specId}': ${error.message}`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.error(`Error deleting spec '${specId}': Unknown error`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function findSpecDirectory(dir, targetId) {
|
|
36
|
+
try {
|
|
37
|
+
const list = fs.readdirSync(dir);
|
|
38
|
+
// Check if direct match exists
|
|
39
|
+
const directPath = path.join(dir, targetId);
|
|
40
|
+
if (fs.existsSync(directPath) && fs.statSync(directPath).isDirectory()) {
|
|
41
|
+
// Optional: Check if it looks like a spec (has spec.md)?
|
|
42
|
+
// For now, we trust the ID matches the directory name.
|
|
43
|
+
// But to be safer/consistent with derive.ts:
|
|
44
|
+
if (fs.existsSync(path.join(directPath, 'spec.md'))) {
|
|
45
|
+
return directPath;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Recursive search
|
|
49
|
+
for (const file of list) {
|
|
50
|
+
const filePath = path.join(dir, file);
|
|
51
|
+
const stat = fs.statSync(filePath);
|
|
52
|
+
if (stat.isDirectory()) {
|
|
53
|
+
// If the directory name matches targetId
|
|
54
|
+
if (file === targetId) {
|
|
55
|
+
if (fs.existsSync(path.join(filePath, 'spec.md'))) {
|
|
56
|
+
return filePath;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Recurse
|
|
60
|
+
const found = findSpecDirectory(filePath, targetId);
|
|
61
|
+
if (found)
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
// Ignore permission errors etc
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Action for 'praxis spec delete <spec-id>'
|
|
6
|
+
*/
|
|
7
|
+
export async function specDeleteAction(specId: string) {
|
|
8
|
+
const rootDir = process.cwd();
|
|
9
|
+
const specsDir = path.join(rootDir, '.praxis', 'specs');
|
|
10
|
+
|
|
11
|
+
// 1. Validate environment
|
|
12
|
+
if (!fs.existsSync(specsDir)) {
|
|
13
|
+
console.error('Error: Specs directory not found (.praxis/specs).');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 2. Find spec directory
|
|
18
|
+
const specDir = findSpecDirectory(specsDir, specId);
|
|
19
|
+
if (!specDir) {
|
|
20
|
+
console.error(`Error: Spec with ID '${specId}' not found.`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 3. Delete
|
|
25
|
+
try {
|
|
26
|
+
console.log(`Deleting spec '${specId}' at ${specDir}...`);
|
|
27
|
+
fs.rmSync(specDir, { recursive: true, force: true });
|
|
28
|
+
console.log(`Successfully deleted spec '${specId}' and its artifacts.`);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof Error) {
|
|
31
|
+
console.error(`Error deleting spec '${specId}': ${error.message}`);
|
|
32
|
+
} else {
|
|
33
|
+
console.error(`Error deleting spec '${specId}': Unknown error`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findSpecDirectory(dir: string, targetId: string): string | null {
|
|
39
|
+
try {
|
|
40
|
+
const list = fs.readdirSync(dir);
|
|
41
|
+
|
|
42
|
+
// Check if direct match exists
|
|
43
|
+
const directPath = path.join(dir, targetId);
|
|
44
|
+
if (fs.existsSync(directPath) && fs.statSync(directPath).isDirectory()) {
|
|
45
|
+
// Optional: Check if it looks like a spec (has spec.md)?
|
|
46
|
+
// For now, we trust the ID matches the directory name.
|
|
47
|
+
// But to be safer/consistent with derive.ts:
|
|
48
|
+
if (fs.existsSync(path.join(directPath, 'spec.md'))) {
|
|
49
|
+
return directPath;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Recursive search
|
|
54
|
+
for (const file of list) {
|
|
55
|
+
const filePath = path.join(dir, file);
|
|
56
|
+
const stat = fs.statSync(filePath);
|
|
57
|
+
|
|
58
|
+
if (stat.isDirectory()) {
|
|
59
|
+
// If the directory name matches targetId
|
|
60
|
+
if (file === targetId) {
|
|
61
|
+
if (fs.existsSync(path.join(filePath, 'spec.md'))) {
|
|
62
|
+
return filePath;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Recurse
|
|
67
|
+
const found = findSpecDirectory(filePath, targetId);
|
|
68
|
+
if (found) return found;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// Ignore permission errors etc
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Action for 'praxis spec derive --from <intent-id>'
|
|
5
|
+
*/
|
|
6
|
+
export async function specDeriveAction(options) {
|
|
7
|
+
const intentId = options.from;
|
|
8
|
+
const rootDir = process.cwd();
|
|
9
|
+
const intentsDir = path.join(rootDir, '.praxis', 'intents');
|
|
10
|
+
const templatePath = path.join(rootDir, '.praxis', 'templates', 'spec-template.md');
|
|
11
|
+
// 1. Validate environment
|
|
12
|
+
if (!fs.existsSync(intentsDir)) {
|
|
13
|
+
console.error('Error: Intents directory not found (.praxis/intents).');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (!fs.existsSync(templatePath)) {
|
|
17
|
+
console.error(`Error: Spec template not found at ${templatePath}`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// 2. Find intent file
|
|
21
|
+
const intentFile = findIntentFile(intentsDir, intentId);
|
|
22
|
+
if (!intentFile) {
|
|
23
|
+
console.error(`Error: Intent with ID '${intentId}' not found.`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// 3. Read content
|
|
27
|
+
const intentContent = fs.readFileSync(intentFile, 'utf8');
|
|
28
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
29
|
+
// 4. Generate Prompt
|
|
30
|
+
const prompt = generateAiPrompt(intentId, intentContent, templateContent);
|
|
31
|
+
// 5. Output
|
|
32
|
+
console.log('\n--- IDE AI CHAT PROMPT ---');
|
|
33
|
+
console.log('Copy and paste the following into your IDE chat to generate the specification:');
|
|
34
|
+
console.log('--------------------------');
|
|
35
|
+
console.log(prompt);
|
|
36
|
+
console.log('--------------------------\n');
|
|
37
|
+
}
|
|
38
|
+
function findIntentFile(dir, targetId) {
|
|
39
|
+
try {
|
|
40
|
+
const list = fs.readdirSync(dir);
|
|
41
|
+
// Check if direct match exists (optimization)
|
|
42
|
+
const directPath = path.join(dir, targetId, 'intent.md');
|
|
43
|
+
if (fs.existsSync(directPath)) {
|
|
44
|
+
return directPath;
|
|
45
|
+
}
|
|
46
|
+
// Recursive search
|
|
47
|
+
for (const file of list) {
|
|
48
|
+
const filePath = path.join(dir, file);
|
|
49
|
+
const stat = fs.statSync(filePath);
|
|
50
|
+
if (stat.isDirectory()) {
|
|
51
|
+
// If the directory name *is* the ID, check for intent.md inside
|
|
52
|
+
if (file === targetId) {
|
|
53
|
+
const potentialIntent = path.join(filePath, 'intent.md');
|
|
54
|
+
if (fs.existsSync(potentialIntent)) {
|
|
55
|
+
return potentialIntent;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Otherwise recurse
|
|
59
|
+
const found = findIntentFile(filePath, targetId);
|
|
60
|
+
if (found)
|
|
61
|
+
return found;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
// Ignore permission errors etc during search
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function generateAiPrompt(intentId, intentContent, templateContent) {
|
|
71
|
+
return `You are an expert software architect and product owner.
|
|
72
|
+
I need to generate a specific technical specification (Spec) for an Intent in my project.
|
|
73
|
+
|
|
74
|
+
**Source Intent ID**: ${intentId}
|
|
75
|
+
|
|
76
|
+
**Context**:
|
|
77
|
+
An Intent can result in multiple Specifications (Specs). Each Spec represents a concrete feature or component derived from the Intent.
|
|
78
|
+
To keep things organized, every Spec needs a unique identifier.
|
|
79
|
+
|
|
80
|
+
**Task**:
|
|
81
|
+
1. **Analyze** the Intent provided below. Understand the goal and identify the specific scope for *this* specification.
|
|
82
|
+
2. **Generate a Spec ID**:
|
|
83
|
+
- Create a concise, kebab-case string (max 20 chars) that represents the content of this specific spec.
|
|
84
|
+
- Example: If the intent is "create-user-profile", this spec might be "profile-api" or "profile-ui".
|
|
85
|
+
3. **Generate the Specification** using the provided Template.
|
|
86
|
+
- Fill in ALL sections of the template.
|
|
87
|
+
- Use the intent information to populate "User Scenarios", "Requirements", etc.
|
|
88
|
+
- Ensure the Spec is detailed enough for an AI agent to implement the code later.
|
|
89
|
+
- Do NOT change the structure of the template.
|
|
90
|
+
- Set Status to "Draft".
|
|
91
|
+
4. **Output** the full Markdown content of the new specification file.
|
|
92
|
+
- **Important**: The file path MUST be: \`.praxis/specs/<intent-id><your-generated-spec-id>/spec.md\`
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
**INTENT CONTENT**:
|
|
96
|
+
\`\`\`markdown
|
|
97
|
+
${intentContent}
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
**SPEC TEMPLATE**:
|
|
102
|
+
\`\`\`markdown
|
|
103
|
+
${templateContent}
|
|
104
|
+
\`\`\`
|
|
105
|
+
|
|
106
|
+
Please generate the file creation tool call (if available) or the full markdown content with the specified path now.`;
|
|
107
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Action for 'praxis spec derive --from <intent-id>'
|
|
6
|
+
*/
|
|
7
|
+
export async function specDeriveAction(options: { from: string }) {
|
|
8
|
+
const intentId = options.from;
|
|
9
|
+
const rootDir = process.cwd();
|
|
10
|
+
const intentsDir = path.join(rootDir, '.praxis', 'intents');
|
|
11
|
+
const templatePath = path.join(rootDir, '.praxis', 'templates', 'spec-template.md');
|
|
12
|
+
|
|
13
|
+
// 1. Validate environment
|
|
14
|
+
if (!fs.existsSync(intentsDir)) {
|
|
15
|
+
console.error('Error: Intents directory not found (.praxis/intents).');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!fs.existsSync(templatePath)) {
|
|
19
|
+
console.error(`Error: Spec template not found at ${templatePath}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. Find intent file
|
|
24
|
+
const intentFile = findIntentFile(intentsDir, intentId);
|
|
25
|
+
if (!intentFile) {
|
|
26
|
+
console.error(`Error: Intent with ID '${intentId}' not found.`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 3. Read content
|
|
31
|
+
const intentContent = fs.readFileSync(intentFile, 'utf8');
|
|
32
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
33
|
+
|
|
34
|
+
// 4. Generate Prompt
|
|
35
|
+
const prompt = generateAiPrompt(intentId, intentContent, templateContent);
|
|
36
|
+
|
|
37
|
+
// 5. Output
|
|
38
|
+
console.log('\n--- IDE AI CHAT PROMPT ---');
|
|
39
|
+
console.log('Copy and paste the following into your IDE chat to generate the specification:');
|
|
40
|
+
console.log('--------------------------');
|
|
41
|
+
console.log(prompt);
|
|
42
|
+
console.log('--------------------------\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findIntentFile(dir: string, targetId: string): string | null {
|
|
46
|
+
try {
|
|
47
|
+
const list = fs.readdirSync(dir);
|
|
48
|
+
|
|
49
|
+
// Check if direct match exists (optimization)
|
|
50
|
+
const directPath = path.join(dir, targetId, 'intent.md');
|
|
51
|
+
if (fs.existsSync(directPath)) {
|
|
52
|
+
return directPath;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Recursive search
|
|
56
|
+
for (const file of list) {
|
|
57
|
+
const filePath = path.join(dir, file);
|
|
58
|
+
const stat = fs.statSync(filePath);
|
|
59
|
+
|
|
60
|
+
if (stat.isDirectory()) {
|
|
61
|
+
// If the directory name *is* the ID, check for intent.md inside
|
|
62
|
+
if (file === targetId) {
|
|
63
|
+
const potentialIntent = path.join(filePath, 'intent.md');
|
|
64
|
+
if (fs.existsSync(potentialIntent)) {
|
|
65
|
+
return potentialIntent;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Otherwise recurse
|
|
70
|
+
const found = findIntentFile(filePath, targetId);
|
|
71
|
+
if (found) return found;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
// Ignore permission errors etc during search
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function generateAiPrompt(intentId: string, intentContent: string, templateContent: string): string {
|
|
81
|
+
return `You are an expert software architect and product owner.
|
|
82
|
+
I need to generate a specific technical specification (Spec) for an Intent in my project.
|
|
83
|
+
|
|
84
|
+
**Source Intent ID**: ${intentId}
|
|
85
|
+
|
|
86
|
+
**Context**:
|
|
87
|
+
An Intent can result in multiple Specifications (Specs). Each Spec represents a concrete feature or component derived from the Intent.
|
|
88
|
+
To keep things organized, every Spec needs a unique identifier.
|
|
89
|
+
|
|
90
|
+
**Task**:
|
|
91
|
+
1. **Analyze** the Intent provided below. Understand the goal and identify the specific scope for *this* specification.
|
|
92
|
+
2. **Generate a Spec ID**:
|
|
93
|
+
- Create a concise, kebab-case string (max 20 chars) that represents the content of this specific spec.
|
|
94
|
+
- Example: If the intent is "create-user-profile", this spec might be "profile-api" or "profile-ui".
|
|
95
|
+
3. **Generate the Specification** using the provided Template.
|
|
96
|
+
- Fill in ALL sections of the template.
|
|
97
|
+
- Use the intent information to populate "User Scenarios", "Requirements", etc.
|
|
98
|
+
- Ensure the Spec is detailed enough for an AI agent to implement the code later.
|
|
99
|
+
- Do NOT change the structure of the template.
|
|
100
|
+
- Set Status to "Draft".
|
|
101
|
+
4. **Output** the full Markdown content of the new specification file.
|
|
102
|
+
- **Important**: The file path MUST be: \`.praxis/specs/<intent-id><your-generated-spec-id>/spec.md\`
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
**INTENT CONTENT**:
|
|
106
|
+
\`\`\`markdown
|
|
107
|
+
${intentContent}
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
**SPEC TEMPLATE**:
|
|
112
|
+
\`\`\`markdown
|
|
113
|
+
${templateContent}
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
Please generate the file creation tool call (if available) or the full markdown content with the specified path now.`;
|
|
117
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for Antigravity following its frontmatter specification.
|
|
5
|
+
*/
|
|
6
|
+
import type { ToolCommandAdapter } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Antigravity adapter for command generation.
|
|
9
|
+
* File path: .agent/workflows/praxis-<id>.md
|
|
10
|
+
* Frontmatter: description
|
|
11
|
+
*/
|
|
12
|
+
export declare const antigravityAdapter: ToolCommandAdapter;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for Antigravity following its frontmatter specification.
|
|
5
|
+
*/
|
|
6
|
+
import path from 'path';
|
|
7
|
+
/**
|
|
8
|
+
* Antigravity adapter for command generation.
|
|
9
|
+
* File path: .agent/workflows/praxis-<id>.md
|
|
10
|
+
* Frontmatter: description
|
|
11
|
+
*/
|
|
12
|
+
export const antigravityAdapter = {
|
|
13
|
+
toolId: 'antigravity',
|
|
14
|
+
getFilePath(commandId) {
|
|
15
|
+
return path.join('.agent', 'workflows', `praxis-${commandId}.md`);
|
|
16
|
+
},
|
|
17
|
+
formatFile(content) {
|
|
18
|
+
return `---
|
|
19
|
+
description: ${content.description}
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
${content.body}
|
|
23
|
+
`;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for Antigravity following its frontmatter specification.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import type { CommandContent, ToolCommandAdapter } from '../types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Antigravity adapter for command generation.
|
|
12
|
+
* File path: .agent/workflows/praxis-<id>.md
|
|
13
|
+
* Frontmatter: description
|
|
14
|
+
*/
|
|
15
|
+
export const antigravityAdapter: ToolCommandAdapter = {
|
|
16
|
+
toolId: 'antigravity',
|
|
17
|
+
|
|
18
|
+
getFilePath(commandId: string): string {
|
|
19
|
+
return path.join('.agent', 'workflows', `praxis-${commandId}.md`);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
formatFile(content: CommandContent): string {
|
|
23
|
+
return `---
|
|
24
|
+
description: ${content.description}
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
${content.body}
|
|
28
|
+
`;
|
|
29
|
+
},
|
|
30
|
+
};
|