@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,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,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,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,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,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
|
+
}
|