@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,47 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export async function initCommand() {
5
+ const rootDir = process.cwd();
6
+ const praxisDir = path.join(rootDir, '.praxis');
7
+ const templatesDir = path.join(praxisDir, 'templates');
8
+ const intentsDir = path.join(praxisDir, 'intents');
9
+ const intentsWipDir = path.join(praxisDir, 'intents', 'wip');
10
+ const intentsArchiveDir = path.join(praxisDir, 'intents', 'archive');
11
+ const specsDir = path.join(praxisDir, 'specs');
12
+
13
+ const dirs = [praxisDir, templatesDir, intentsDir, intentsWipDir, intentsArchiveDir, specsDir];
14
+
15
+ for (const dir of dirs) {
16
+ if (!fs.existsSync(dir)) {
17
+ fs.mkdirSync(dir, { recursive: true });
18
+ console.log(`Created directory: ${path.relative(rootDir, dir)}`);
19
+
20
+ // Create a .gitkeep file to ensure the directory is tracked by git
21
+ fs.writeFileSync(path.join(dir, '.gitkeep'), '');
22
+ } else {
23
+ console.log(`Directory already exists: ${path.relative(rootDir, dir)}`);
24
+ }
25
+ }
26
+
27
+ // Copy templates from root templates/ to .praxis/templates/
28
+ const sourceTemplatesDir = path.join(rootDir, 'templates');
29
+ if (fs.existsSync(sourceTemplatesDir)) {
30
+ console.log('\nCopying templates...');
31
+ const templateFiles = fs.readdirSync(sourceTemplatesDir);
32
+ for (const file of templateFiles) {
33
+ const sourcePath = path.join(sourceTemplatesDir, file);
34
+ const targetPath = path.join(templatesDir, file);
35
+
36
+ if (fs.statSync(sourcePath).isFile()) {
37
+ fs.copyFileSync(sourcePath, targetPath);
38
+ console.log(`- Copied: ${file}`);
39
+ }
40
+ }
41
+ } else {
42
+ console.log('\nNo source templates directory found at root. Skipping template copy.');
43
+ }
44
+
45
+ console.log('\nPraxis project structure initialized successfully!');
46
+ console.log('You can now start defining intents in .praxis/intents/wip/');
47
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Action for 'praxis integration generate-slash-commands <tool>'
3
+ */
4
+ export declare function generateSlashCommandsAction(toolId: string): Promise<void>;
@@ -0,0 +1,69 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { manifest } from '../../manifest.js';
4
+ import { CommandAdapterRegistry } from '../../core/command-generation/index.js';
5
+ import { generateCommand } from '../../core/command-generation/generator.js';
6
+ /**
7
+ * Action for 'praxis integration generate-slash-commands <tool>'
8
+ */
9
+ export async function generateSlashCommandsAction(toolId) {
10
+ const adapter = CommandAdapterRegistry.get(toolId);
11
+ if (!adapter) {
12
+ console.error(`Error: Adapter for tool '${toolId}' not found.`);
13
+ console.log(`Available tools: ${CommandAdapterRegistry.getAll().map(a => a.toolId).join(', ')}`);
14
+ return;
15
+ }
16
+ console.log(`Generating slash commands for tool: ${toolId}...`);
17
+ const contents = flattenCommands(manifest);
18
+ let count = 0;
19
+ for (const content of contents) {
20
+ const generated = generateCommand(content, adapter);
21
+ const fullPath = path.join(process.cwd(), generated.path);
22
+ // Ensure directory exists
23
+ const dir = path.dirname(fullPath);
24
+ if (!fs.existsSync(dir)) {
25
+ fs.mkdirSync(dir, { recursive: true });
26
+ }
27
+ fs.writeFileSync(fullPath, generated.fileContent);
28
+ console.log(`- Generated: ${generated.path}`);
29
+ count++;
30
+ }
31
+ console.log(`\nSuccess! ${count} slash commands generated.`);
32
+ }
33
+ /**
34
+ * Flattens the command manifest into a tool-agnostic CommandContent array.
35
+ */
36
+ function flattenCommands(commands, parentName) {
37
+ let result = [];
38
+ for (const cmd of commands) {
39
+ const fullName = parentName ? `${parentName} ${cmd.name}` : cmd.name;
40
+ const id = fullName.replace(/\s+/g, '-');
41
+ if (cmd.subcommands) {
42
+ result = result.concat(flattenCommands(cmd.subcommands, fullName));
43
+ }
44
+ else {
45
+ // It's a runnable leaf command
46
+ result.push({
47
+ id: id,
48
+ name: `Praxis ${fullName}`,
49
+ description: cmd.description,
50
+ category: 'Praxis',
51
+ tags: ['praxis', cmd.name],
52
+ body: generateBody(fullName, cmd)
53
+ });
54
+ }
55
+ }
56
+ return result;
57
+ }
58
+ /**
59
+ * Generates the body content for a command workflow.
60
+ */
61
+ function generateBody(fullName, cmd) {
62
+ const args = cmd.arguments ? cmd.arguments.map(arg => `"$${arg.name}"`).join(' ') : '';
63
+ const cliCommand = `node --loader ts-node/esm src/index.ts ${fullName} ${args}`.trim();
64
+ let body = `To execute this Praxis command, run:\n\n// turbo\n${cliCommand}\n`;
65
+ if (fullName === 'intent create') {
66
+ body += `\nOnce the command is executed, follow the prompt generated in the output to complete the intent document with the AI.`;
67
+ }
68
+ return body;
69
+ }
@@ -0,0 +1,85 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { manifest, type CommandDefinition } from '../../manifest.js';
4
+ import { CommandAdapterRegistry } from '../../core/command-generation/index.js';
5
+ import { generateCommand } from '../../core/command-generation/generator.js';
6
+ import type { CommandContent } from '../../core/command-generation/types.js';
7
+
8
+ /**
9
+ * Action for 'praxis integration generate-slash-commands <tool>'
10
+ */
11
+ export async function generateSlashCommandsAction(toolId: string) {
12
+ const adapter = CommandAdapterRegistry.get(toolId);
13
+
14
+ if (!adapter) {
15
+ console.error(`Error: Adapter for tool '${toolId}' not found.`);
16
+ console.log(`Available tools: ${CommandAdapterRegistry.getAll().map(a => a.toolId).join(', ')}`);
17
+ return;
18
+ }
19
+
20
+ console.log(`Generating slash commands for tool: ${toolId}...`);
21
+
22
+ const contents = flattenCommands(manifest);
23
+ let count = 0;
24
+
25
+ for (const content of contents) {
26
+ const generated = generateCommand(content, adapter);
27
+ const fullPath = path.join(process.cwd(), generated.path);
28
+
29
+ // Ensure directory exists
30
+ const dir = path.dirname(fullPath);
31
+ if (!fs.existsSync(dir)) {
32
+ fs.mkdirSync(dir, { recursive: true });
33
+ }
34
+
35
+ fs.writeFileSync(fullPath, generated.fileContent);
36
+ console.log(`- Generated: ${generated.path}`);
37
+ count++;
38
+ }
39
+
40
+ console.log(`\nSuccess! ${count} slash commands generated.`);
41
+ }
42
+
43
+ /**
44
+ * Flattens the command manifest into a tool-agnostic CommandContent array.
45
+ */
46
+ function flattenCommands(commands: CommandDefinition[], parentName?: string): CommandContent[] {
47
+ let result: CommandContent[] = [];
48
+
49
+ for (const cmd of commands) {
50
+ const fullName = parentName ? `${parentName} ${cmd.name}` : cmd.name;
51
+ const id = fullName.replace(/\s+/g, '-');
52
+
53
+ if (cmd.subcommands) {
54
+ result = result.concat(flattenCommands(cmd.subcommands, fullName));
55
+ } else {
56
+ // It's a runnable leaf command
57
+ result.push({
58
+ id: id,
59
+ name: `Praxis ${fullName}`,
60
+ description: cmd.description,
61
+ category: 'Praxis',
62
+ tags: ['praxis', cmd.name],
63
+ body: generateBody(fullName, cmd)
64
+ });
65
+ }
66
+ }
67
+
68
+ return result;
69
+ }
70
+
71
+ /**
72
+ * Generates the body content for a command workflow.
73
+ */
74
+ function generateBody(fullName: string, cmd: CommandDefinition): string {
75
+ const args = cmd.arguments ? cmd.arguments.map(arg => `"$${arg.name}"`).join(' ') : '';
76
+ const cliCommand = `node --loader ts-node/esm src/index.ts ${fullName} ${args}`.trim();
77
+
78
+ let body = `To execute this Praxis command, run:\n\n// turbo\n${cliCommand}\n`;
79
+
80
+ if (fullName === 'intent create') {
81
+ body += `\nOnce the command is executed, follow the prompt generated in the output to complete the intent document with the AI.`;
82
+ }
83
+
84
+ return body;
85
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Action for 'praxis intent create <intent-description>'
3
+ */
4
+ export declare function intentCreateAction(description: string): Promise<void>;
@@ -0,0 +1,55 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ /**
4
+ * Action for 'praxis intent create <intent-description>'
5
+ */
6
+ export async function intentCreateAction(description) {
7
+ const rootDir = process.cwd();
8
+ const templatePath = path.join(rootDir, '.praxis', 'templates', 'intent-template.md');
9
+ if (!fs.existsSync(templatePath)) {
10
+ console.error(`Error: Template not found at ${templatePath}`);
11
+ console.error('Please run `praxis init` first to initialize the project structure.');
12
+ return;
13
+ }
14
+ const templateContent = fs.readFileSync(templatePath, 'utf8');
15
+ // Generate and display the AI prompt
16
+ const prompt = generateAiPrompt(description, templateContent);
17
+ console.log('\n--- IDE AI CHAT PROMPT ---');
18
+ console.log('Copy and paste the following into your IDE chat to complete the intent:');
19
+ console.log('--------------------------');
20
+ console.log(prompt);
21
+ console.log('--------------------------\n');
22
+ }
23
+ /**
24
+ * Generate a prompt for the AI to complete the intent definition.
25
+ */
26
+ function generateAiPrompt(description, templateContent) {
27
+ return `You are an expert product manager and software architect.
28
+ I need to create a new Intent in my Praxis project for the following feature:
29
+ "${description}"
30
+
31
+ Please perform the following actions:
32
+
33
+ 1. **Analyze** the request to understand the core intent.
34
+ 2. **Generate a Short ID**: Create a representative, kebab-case identifier for this intent.
35
+ - It MUST be concise (max 20 characters).
36
+ - Example: "user-login", "export-pdf", "dark-mode".
37
+ 3. **Create the Intent File**:
38
+ - Create a new directory: \`.praxis/intents/wip/<your-generated-id>/\`
39
+ - Create a new file: \`.praxis/intents/wip/<your-generated-id>/intent.md\`
40
+ 4. **Fill the Template**:
41
+ - Use the template content provided below.
42
+ - Smartly fill in ALL placeholders (like [FEATURE NAME], [DATE], etc.) based on the user's description.
43
+ - Write at least one draft User Story based on the description.
44
+ - Define initial Functional Requirements.
45
+ - Set the Status to "Draft".
46
+ - Keep the language used for the intent description.
47
+
48
+ Here is the template content to use:
49
+
50
+ \`\`\`markdown
51
+ ${templateContent}
52
+ \`\`\`
53
+
54
+ Now, please generate the file creation tool call and the filled content.`;
55
+ }
@@ -0,0 +1,61 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Action for 'praxis intent create <intent-description>'
6
+ */
7
+ export async function intentCreateAction(description: string) {
8
+ const rootDir = process.cwd();
9
+ const templatePath = path.join(rootDir, '.praxis', 'templates', 'intent-template.md');
10
+
11
+ if (!fs.existsSync(templatePath)) {
12
+ console.error(`Error: Template not found at ${templatePath}`);
13
+ console.error('Please run `praxis init` first to initialize the project structure.');
14
+ return;
15
+ }
16
+
17
+ const templateContent = fs.readFileSync(templatePath, 'utf8');
18
+
19
+ // Generate and display the AI prompt
20
+ const prompt = generateAiPrompt(description, templateContent);
21
+
22
+ console.log('\n--- IDE AI CHAT PROMPT ---');
23
+ console.log('Copy and paste the following into your IDE chat to complete the intent:');
24
+ console.log('--------------------------');
25
+ console.log(prompt);
26
+ console.log('--------------------------\n');
27
+ }
28
+
29
+ /**
30
+ * Generate a prompt for the AI to complete the intent definition.
31
+ */
32
+ function generateAiPrompt(description: string, templateContent: string): string {
33
+ return `You are an expert product manager and software architect.
34
+ I need to create a new Intent in my Praxis project for the following feature:
35
+ "${description}"
36
+
37
+ Please perform the following actions:
38
+
39
+ 1. **Analyze** the request to understand the core intent.
40
+ 2. **Generate a Short ID**: Create a representative, kebab-case identifier for this intent.
41
+ - It MUST be concise (max 20 characters).
42
+ - Example: "user-login", "export-pdf", "dark-mode".
43
+ 3. **Create the Intent File**:
44
+ - Create a new directory: \`.praxis/intents/wip/<your-generated-id>/\`
45
+ - Create a new file: \`.praxis/intents/wip/<your-generated-id>/intent.md\`
46
+ 4. **Fill the Template**:
47
+ - Use the template content provided below.
48
+ - Smartly fill in ALL placeholders (like [FEATURE NAME], [DATE], etc.) based on the user's description.
49
+ - Write at least one draft User Story based on the description.
50
+ - Define initial Functional Requirements.
51
+ - Set the Status to "Draft".
52
+ - Keep the language used for the intent description.
53
+
54
+ Here is the template content to use:
55
+
56
+ \`\`\`markdown
57
+ ${templateContent}
58
+ \`\`\`
59
+
60
+ Now, please generate the file creation tool call and the filled content.`;
61
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Action for 'praxis intent list'
3
+ */
4
+ export declare function intentListAction(): Promise<void>;
@@ -0,0 +1,52 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ /**
4
+ * Action for 'praxis intent list'
5
+ */
6
+ export async function intentListAction() {
7
+ const rootDir = process.cwd();
8
+ const intentsDir = path.join(rootDir, '.praxis', 'intents');
9
+ if (!fs.existsSync(intentsDir)) {
10
+ console.log('No intents found (directory .praxis/intents does not exist).');
11
+ return;
12
+ }
13
+ const intentFiles = findAllIntentFiles(intentsDir);
14
+ if (intentFiles.length === 0) {
15
+ console.log('No intents found.');
16
+ return;
17
+ }
18
+ // Print header
19
+ console.log('%-25s %-15s %s', 'ID', 'Status', 'Created');
20
+ console.log('-'.repeat(60));
21
+ for (const file of intentFiles) {
22
+ const content = fs.readFileSync(file, 'utf8');
23
+ const id = path.basename(path.dirname(file));
24
+ // Parse metadata using simple regex
25
+ const statusMatch = content.match(/\*\*Status\*\*:\s*(.+)/);
26
+ const createdMatch = content.match(/\*\*Created\*\*:\s*(.+)/);
27
+ const status = statusMatch ? statusMatch[1].trim() : 'Unknown';
28
+ const created = createdMatch ? createdMatch[1].trim() : 'Unknown';
29
+ // Simple column formatting
30
+ console.log(`${id.padEnd(25)} ${status.padEnd(15)} ${created}`);
31
+ }
32
+ }
33
+ function findAllIntentFiles(dir) {
34
+ let results = [];
35
+ try {
36
+ const list = fs.readdirSync(dir);
37
+ for (const file of list) {
38
+ const filePath = path.join(dir, file);
39
+ const stat = fs.statSync(filePath);
40
+ if (stat.isDirectory()) {
41
+ results = results.concat(findAllIntentFiles(filePath));
42
+ }
43
+ else if (file === 'intent.md') {
44
+ results.push(filePath);
45
+ }
46
+ }
47
+ }
48
+ catch (e) {
49
+ console.error(`Error reading directory ${dir}:`, e);
50
+ }
51
+ return results;
52
+ }
@@ -0,0 +1,63 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Action for 'praxis intent list'
6
+ */
7
+ export async function intentListAction() {
8
+ const rootDir = process.cwd();
9
+ const intentsDir = path.join(rootDir, '.praxis', 'intents');
10
+
11
+ if (!fs.existsSync(intentsDir)) {
12
+ console.log('No intents found (directory .praxis/intents does not exist).');
13
+ return;
14
+ }
15
+
16
+ const intentFiles = findAllIntentFiles(intentsDir);
17
+
18
+ if (intentFiles.length === 0) {
19
+ console.log('No intents found.');
20
+ return;
21
+ }
22
+
23
+ // Print header
24
+ console.log('%-25s %-15s %s', 'ID', 'Status', 'Created');
25
+ console.log('-'.repeat(60));
26
+
27
+ for (const file of intentFiles) {
28
+ const content = fs.readFileSync(file, 'utf8');
29
+ const id = path.basename(path.dirname(file));
30
+
31
+ // Parse metadata using simple regex
32
+ const statusMatch = content.match(/\*\*Status\*\*:\s*(.+)/);
33
+ const createdMatch = content.match(/\*\*Created\*\*:\s*(.+)/);
34
+
35
+ const status = statusMatch ? statusMatch[1].trim() : 'Unknown';
36
+ const created = createdMatch ? createdMatch[1].trim() : 'Unknown';
37
+
38
+ // Simple column formatting
39
+ console.log(`${id.padEnd(25)} ${status.padEnd(15)} ${created}`);
40
+ }
41
+ }
42
+
43
+ function findAllIntentFiles(dir: string): string[] {
44
+ let results: string[] = [];
45
+ try {
46
+ const list = fs.readdirSync(dir);
47
+
48
+ for (const file of list) {
49
+ const filePath = path.join(dir, file);
50
+ const stat = fs.statSync(filePath);
51
+
52
+ if (stat.isDirectory()) {
53
+ results = results.concat(findAllIntentFiles(filePath));
54
+ } else if (file === 'intent.md') {
55
+ results.push(filePath);
56
+ }
57
+ }
58
+ } catch (e) {
59
+ console.error(`Error reading directory ${dir}:`, e);
60
+ }
61
+
62
+ return results;
63
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Action for 'praxis spec apply <spec-id>'
3
+ */
4
+ export declare function specApplyAction(specId: string): Promise<void>;
@@ -0,0 +1,99 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ /**
4
+ * Action for 'praxis spec apply <spec-id>'
5
+ */
6
+ export async function specApplyAction(specId) {
7
+ const rootDir = process.cwd();
8
+ const specsDir = path.join(rootDir, '.praxis', 'specs');
9
+ // 1. Validate environment
10
+ if (!fs.existsSync(specsDir)) {
11
+ // As requested: "Se la spec specificata con l'id non esiste, il comando non deve fare niente."
12
+ return;
13
+ }
14
+ // 2. Find spec directory
15
+ const specDir = findSpecDirectory(specsDir, specId);
16
+ if (!specDir) {
17
+ // As requested: "Se la spec specificata con l'id non esiste, il comando non deve fare niente."
18
+ return;
19
+ }
20
+ // 3. Collect artifacts
21
+ const specFile = path.join(specDir, 'spec.md');
22
+ let specContent = '';
23
+ if (fs.existsSync(specFile)) {
24
+ specContent = fs.readFileSync(specFile, 'utf8');
25
+ }
26
+ const otherArtifacts = [];
27
+ const files = fs.readdirSync(specDir);
28
+ for (const file of files) {
29
+ if (file === 'spec.md' || file === '.gitkeep')
30
+ continue;
31
+ const filePath = path.join(specDir, file);
32
+ if (fs.statSync(filePath).isFile()) {
33
+ otherArtifacts.push({
34
+ name: file,
35
+ content: fs.readFileSync(filePath, 'utf8')
36
+ });
37
+ }
38
+ }
39
+ // 4. Generate Prompt
40
+ const prompt = generateApplyPrompt(specId, specContent, otherArtifacts);
41
+ // 5. Output
42
+ console.log('\n--- IDE AI CHAT PROMPT ---');
43
+ console.log('Copy and paste the following into your IDE chat to implement the specification:');
44
+ console.log('--------------------------');
45
+ console.log(prompt);
46
+ console.log('--------------------------\n');
47
+ }
48
+ function findSpecDirectory(dir, targetId) {
49
+ try {
50
+ const list = fs.readdirSync(dir);
51
+ // Check if direct match exists
52
+ const directPath = path.join(dir, targetId);
53
+ if (fs.existsSync(directPath) && fs.statSync(directPath).isDirectory()) {
54
+ if (fs.existsSync(path.join(directPath, 'spec.md'))) {
55
+ return directPath;
56
+ }
57
+ }
58
+ // Recursive search
59
+ for (const file of list) {
60
+ const filePath = path.join(dir, file);
61
+ const stat = fs.statSync(filePath);
62
+ if (stat.isDirectory()) {
63
+ if (file === targetId) {
64
+ if (fs.existsSync(path.join(filePath, 'spec.md'))) {
65
+ return filePath;
66
+ }
67
+ }
68
+ const found = findSpecDirectory(filePath, targetId);
69
+ if (found)
70
+ return found;
71
+ }
72
+ }
73
+ }
74
+ catch (e) {
75
+ // Ignore errors
76
+ }
77
+ return null;
78
+ }
79
+ function generateApplyPrompt(specId, specContent, artifacts) {
80
+ let artifactsSection = '';
81
+ if (artifacts.length > 0) {
82
+ artifactsSection = '\n**Additional Artifacts found in spec directory**:\n';
83
+ artifacts.forEach(art => {
84
+ artifactsSection += `\n- **${art.name}**:\n\`\`\`\n${art.content}\n\`\`\`\n`;
85
+ });
86
+ }
87
+ return `I want to implement the specification with ID: **${specId}**.
88
+
89
+ Please carefully review the specification content and any associated artifacts provided below.
90
+ Your task is to proceed with the implementation in the codebase, following the requirements and design decisions documented in these files.
91
+
92
+ **SPECIFICATION CONTENT**:
93
+ \`\`\`markdown
94
+ ${specContent || 'No spec.md content found.'}
95
+ \`\`\`
96
+ ${artifactsSection}
97
+
98
+ Please start the implementation now.`;
99
+ }
@@ -0,0 +1,109 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Action for 'praxis spec apply <spec-id>'
6
+ */
7
+ export async function specApplyAction(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
+ // As requested: "Se la spec specificata con l'id non esiste, il comando non deve fare niente."
14
+ return;
15
+ }
16
+
17
+ // 2. Find spec directory
18
+ const specDir = findSpecDirectory(specsDir, specId);
19
+ if (!specDir) {
20
+ // As requested: "Se la spec specificata con l'id non esiste, il comando non deve fare niente."
21
+ return;
22
+ }
23
+
24
+ // 3. Collect artifacts
25
+ const specFile = path.join(specDir, 'spec.md');
26
+ let specContent = '';
27
+ if (fs.existsSync(specFile)) {
28
+ specContent = fs.readFileSync(specFile, 'utf8');
29
+ }
30
+
31
+ const otherArtifacts: { name: string, content: string }[] = [];
32
+ const files = fs.readdirSync(specDir);
33
+ for (const file of files) {
34
+ if (file === 'spec.md' || file === '.gitkeep') continue;
35
+ const filePath = path.join(specDir, file);
36
+ if (fs.statSync(filePath).isFile()) {
37
+ otherArtifacts.push({
38
+ name: file,
39
+ content: fs.readFileSync(filePath, 'utf8')
40
+ });
41
+ }
42
+ }
43
+
44
+ // 4. Generate Prompt
45
+ const prompt = generateApplyPrompt(specId, specContent, otherArtifacts);
46
+
47
+ // 5. Output
48
+ console.log('\n--- IDE AI CHAT PROMPT ---');
49
+ console.log('Copy and paste the following into your IDE chat to implement the specification:');
50
+ console.log('--------------------------');
51
+ console.log(prompt);
52
+ console.log('--------------------------\n');
53
+ }
54
+
55
+ function findSpecDirectory(dir: string, targetId: string): string | null {
56
+ try {
57
+ const list = fs.readdirSync(dir);
58
+
59
+ // Check if direct match exists
60
+ const directPath = path.join(dir, targetId);
61
+ if (fs.existsSync(directPath) && fs.statSync(directPath).isDirectory()) {
62
+ if (fs.existsSync(path.join(directPath, 'spec.md'))) {
63
+ return directPath;
64
+ }
65
+ }
66
+
67
+ // Recursive search
68
+ for (const file of list) {
69
+ const filePath = path.join(dir, file);
70
+ const stat = fs.statSync(filePath);
71
+
72
+ if (stat.isDirectory()) {
73
+ if (file === targetId) {
74
+ if (fs.existsSync(path.join(filePath, 'spec.md'))) {
75
+ return filePath;
76
+ }
77
+ }
78
+ const found = findSpecDirectory(filePath, targetId);
79
+ if (found) return found;
80
+ }
81
+ }
82
+ } catch (e) {
83
+ // Ignore errors
84
+ }
85
+ return null;
86
+ }
87
+
88
+ function generateApplyPrompt(specId: string, specContent: string, artifacts: { name: string, content: string }[]): string {
89
+ let artifactsSection = '';
90
+ if (artifacts.length > 0) {
91
+ artifactsSection = '\n**Additional Artifacts found in spec directory**:\n';
92
+ artifacts.forEach(art => {
93
+ artifactsSection += `\n- **${art.name}**:\n\`\`\`\n${art.content}\n\`\`\`\n`;
94
+ });
95
+ }
96
+
97
+ return `I want to implement the specification with ID: **${specId}**.
98
+
99
+ Please carefully review the specification content and any associated artifacts provided below.
100
+ Your task is to proceed with the implementation in the codebase, following the requirements and design decisions documented in these files.
101
+
102
+ **SPECIFICATION CONTENT**:
103
+ \`\`\`markdown
104
+ ${specContent || 'No spec.md content found.'}
105
+ \`\`\`
106
+ ${artifactsSection}
107
+
108
+ Please start the implementation now.`;
109
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Action for 'praxis spec archive <spec-id>'
3
+ */
4
+ export declare function specArchiveAction(specId: string): Promise<void>;