@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.
Files changed (61) hide show
  1. package/CODE_OF_CONDUCT.md +99 -0
  2. package/CONTRIBUTING.md +108 -0
  3. package/LICENSE +21 -0
  4. package/MAINTAINERS.md +9 -0
  5. package/README.md +319 -0
  6. package/intoinside-praxis-1.0.0.tgz +0 -0
  7. package/last_error.txt +42 -0
  8. package/package.json +40 -0
  9. package/src/commands/init.d.ts +1 -0
  10. package/src/commands/init.js +42 -0
  11. package/src/commands/init.ts +47 -0
  12. package/src/commands/integration/generate.d.ts +4 -0
  13. package/src/commands/integration/generate.js +69 -0
  14. package/src/commands/integration/generate.ts +85 -0
  15. package/src/commands/intent/create.d.ts +4 -0
  16. package/src/commands/intent/create.js +55 -0
  17. package/src/commands/intent/create.ts +61 -0
  18. package/src/commands/intent/list.d.ts +4 -0
  19. package/src/commands/intent/list.js +52 -0
  20. package/src/commands/intent/list.ts +63 -0
  21. package/src/commands/spec/apply.d.ts +4 -0
  22. package/src/commands/spec/apply.js +99 -0
  23. package/src/commands/spec/apply.ts +109 -0
  24. package/src/commands/spec/archive.d.ts +4 -0
  25. package/src/commands/spec/archive.js +114 -0
  26. package/src/commands/spec/archive.ts +121 -0
  27. package/src/commands/spec/delete.d.ts +4 -0
  28. package/src/commands/spec/delete.js +70 -0
  29. package/src/commands/spec/delete.ts +75 -0
  30. package/src/commands/spec/derive.d.ts +6 -0
  31. package/src/commands/spec/derive.js +107 -0
  32. package/src/commands/spec/derive.ts +117 -0
  33. package/src/core/command-generation/adapters/antigravity.d.ts +12 -0
  34. package/src/core/command-generation/adapters/antigravity.js +25 -0
  35. package/src/core/command-generation/adapters/antigravity.ts +30 -0
  36. package/src/core/command-generation/adapters/index.d.ts +6 -0
  37. package/src/core/command-generation/adapters/index.js +26 -0
  38. package/src/core/command-generation/adapters/index.ts +27 -0
  39. package/src/core/command-generation/generator.d.ts +20 -0
  40. package/src/core/command-generation/generator.js +26 -0
  41. package/src/core/command-generation/generator.ts +36 -0
  42. package/src/core/command-generation/index.d.ts +20 -0
  43. package/src/core/command-generation/index.js +23 -0
  44. package/src/core/command-generation/index.ts +33 -0
  45. package/src/core/command-generation/registry.d.ts +35 -0
  46. package/src/core/command-generation/registry.js +87 -0
  47. package/src/core/command-generation/registry.ts +95 -0
  48. package/src/core/command-generation/types.d.ts +54 -0
  49. package/src/core/command-generation/types.js +7 -0
  50. package/src/core/command-generation/types.ts +57 -0
  51. package/src/index.d.ts +2 -0
  52. package/src/index.js +95 -0
  53. package/src/index.ts +97 -0
  54. package/src/manifest.d.ts +21 -0
  55. package/src/manifest.js +170 -0
  56. package/src/manifest.ts +188 -0
  57. package/templates/checklist-template.md +42 -0
  58. package/templates/intent-template.md +290 -0
  59. package/templates/spec-template.md +114 -0
  60. package/test_output.txt +0 -0
  61. 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,4 @@
1
+ /**
2
+ * Action for 'praxis spec delete <spec-id>'
3
+ */
4
+ export declare function specDeleteAction(specId: string): Promise<void>;
@@ -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,6 @@
1
+ /**
2
+ * Action for 'praxis spec derive --from <intent-id>'
3
+ */
4
+ export declare function specDeriveAction(options: {
5
+ from: string;
6
+ }): Promise<void>;
@@ -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
+ };