Skip to content

Commit 5dca438

Browse files
committed
Pre-cleanup checkpoint: Save all work before cleanup
1 parent 1e091f6 commit 5dca438

File tree

7 files changed

+262
-12
lines changed

7 files changed

+262
-12
lines changed

package.json

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,29 @@
9090
"promptcode.ignorePatterns": {
9191
"type": "string",
9292
"default": "",
93-
"description": "Custom patterns to ignore when scanning files (one per line)"
93+
"description": "[Deprecated] Use .promptcode_ignore file at workspace root instead. Custom patterns to ignore when scanning files (one per line)"
94+
},
95+
"promptcode.includeBuiltInTemplates": {
96+
"type": "boolean",
97+
"default": true,
98+
"description": "Include built-in prompt templates in the PromptCode UI"
99+
},
100+
"promptcode.promptFolders": {
101+
"type": "array",
102+
"items": {
103+
"type": "string"
104+
},
105+
"default": [
106+
".promptcode/prompts",
107+
".cursor/rules",
108+
".github/copilot-instructions.md",
109+
".zed/",
110+
".windsurfrules",
111+
".clinerules",
112+
".ai-rules/",
113+
"ai-docs/"
114+
],
115+
"description": "Additional folders/files to scan for prompt templates"
94116
},
95117
"promptcode.enableTelemetry": {
96118
"type": "boolean",

packages/core/tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "ES2020",
4-
"module": "es2020",
4+
"module": "commonjs",
55
"lib": ["ES2020"],
66
"declaration": true,
77
"outDir": "./dist",
@@ -11,7 +11,7 @@
1111
"skipLibCheck": true,
1212
"forceConsistentCasingInFileNames": true,
1313
"resolveJsonModule": true,
14-
"moduleResolution": "bundler"
14+
"moduleResolution": "node"
1515
},
1616
"include": ["src/**/*"],
1717
"exclude": ["node_modules", "dist"]

scripts/build-tests.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const testEntryPoints = [
1010
'src/test/runTests.ts',
1111
'src/test/filePattern.test.ts',
1212
'src/test/generatePatternsFromSelection.test.ts',
13+
'src/test/smoke.test.ts',
14+
'src/test/migration.test.ts',
1315
// Also compile the utils that tests depend on
1416
'src/utils/filePattern.ts',
1517
'src/utils/generatePatternsFromSelection.ts'

src/extension.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ import { FileListProcessor } from './fileListProcessor';
1717
// Import the moved type
1818
import type { SelectedFile } from '@promptcode/core';
1919

20+
// Security helper to prevent path traversal attacks
21+
function resolveSecurePath(rootPath: string, relativePath: string): string {
22+
const resolved = path.resolve(rootPath, relativePath);
23+
const rootResolved = path.resolve(rootPath) + path.sep;
24+
if (!resolved.startsWith(rootResolved)) {
25+
throw new Error('Security Error: Path traversal attempt detected');
26+
}
27+
return resolved;
28+
}
29+
2030
let lastGeneratedPrompt: string | null = null; // Variable to store the last generated prompt
2131

2232
// Export a getter for the last generated prompt
@@ -99,7 +109,7 @@ export function activate(context: vscode.ExtensionContext) {
99109

100110
// Initialize output channel
101111
const outputChannel = vscode.window.createOutputChannel('PromptCode');
102-
outputChannel.show(true);
112+
// Don't show automatically - only show on error or user request
103113

104114
// Get the workspace folder
105115
const workspaceRoot = vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0
@@ -181,7 +191,7 @@ export function activate(context: vscode.ExtensionContext) {
181191
// Register select all command
182192
const selectAllCommand = vscode.commands.registerCommand('promptcode.selectAll', async () => {
183193
await vscode.window.withProgress({
184-
location: vscode.ProgressLocation.Notification,
194+
location: vscode.ProgressLocation.Window,
185195
title: "Selecting all files...",
186196
cancellable: false
187197
}, async () => {
@@ -193,7 +203,7 @@ export function activate(context: vscode.ExtensionContext) {
193203
// Register deselect all command
194204
const deselectAllCommand = vscode.commands.registerCommand('promptcode.deselectAll', async () => {
195205
await vscode.window.withProgress({
196-
location: vscode.ProgressLocation.Notification,
206+
location: vscode.ProgressLocation.Window,
197207
title: "Deselecting all files...",
198208
cancellable: false
199209
}, async () => {
@@ -236,7 +246,7 @@ export function activate(context: vscode.ExtensionContext) {
236246
const generatePromptCommand = vscode.commands.registerCommand('promptcode.generatePrompt', async () => {
237247
// Generate the prompt text
238248
vscode.window.withProgress({
239-
location: vscode.ProgressLocation.Notification,
249+
location: vscode.ProgressLocation.Window,
240250
title: 'Generating Prompt',
241251
cancellable: false
242252
}, async (progress) => {
@@ -353,7 +363,7 @@ export function activate(context: vscode.ExtensionContext) {
353363
const copyPromptDirectlyCommand = vscode.commands.registerCommand('promptcode.copyPromptDirectly', async () => {
354364
// Generate the prompt text and copy to clipboard
355365
vscode.window.withProgress({
356-
location: vscode.ProgressLocation.Notification,
366+
location: vscode.ProgressLocation.Window,
357367
title: 'Generating and Copying Prompt',
358368
cancellable: false
359369
}, async (progress) => {
@@ -440,7 +450,7 @@ export function activate(context: vscode.ExtensionContext) {
440450
}
441451

442452
// Construct the full file path using the determined workspace folder
443-
const fullPath = path.join(targetWorkspaceFolder.uri.fsPath, filePath);
453+
const fullPath = resolveSecurePath(targetWorkspaceFolder.uri.fsPath, filePath);
444454

445455
// Handle different file operations
446456
switch (fileOperation.toUpperCase()) {
@@ -457,6 +467,17 @@ export function activate(context: vscode.ExtensionContext) {
457467
break;
458468

459469
case 'DELETE':
470+
// Confirm deletion with user for security
471+
const userChoice = await vscode.window.showWarningMessage(
472+
`Are you sure you want to delete ${path.basename(fullPath)}?`,
473+
{ modal: true, detail: `Full path: ${fullPath}` },
474+
'Delete',
475+
'Cancel'
476+
);
477+
if (userChoice !== 'Delete') {
478+
vscode.window.showInformationMessage('Deletion cancelled');
479+
return;
480+
}
460481
// Delete the file
461482
await fs.promises.unlink(fullPath);
462483
break;
@@ -687,7 +708,7 @@ export function activate(context: vscode.ExtensionContext) {
687708

688709
// Show progress indicator
689710
await vscode.window.withProgress({
690-
location: vscode.ProgressLocation.Notification,
711+
location: vscode.ProgressLocation.Window,
691712
title: "Processing selected files...",
692713
cancellable: false
693714
}, async (progress) => {
@@ -1112,7 +1133,8 @@ export function activate(context: vscode.ExtensionContext) {
11121133
} else {
11131134
// It's a relative path, check if workspaceFolderRootPath is provided and valid
11141135
if (workspaceFolderRootPath && fs.existsSync(workspaceFolderRootPath)) {
1115-
fileUri = vscode.Uri.file(path.join(workspaceFolderRootPath, filePath));
1136+
const securePath = resolveSecurePath(workspaceFolderRootPath, filePath);
1137+
fileUri = vscode.Uri.file(securePath);
11161138
} else {
11171139
// Try to find the file in one of the workspace folders
11181140
let found = false;

src/test/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export function run(): Promise<void> {
66
// Create the mocha test
77
const mocha = new Mocha({
88
ui: 'tdd',
9-
color: true
9+
color: true,
10+
timeout: 10000 // 10 second timeout
1011
});
1112

1213
const testsRoot = path.resolve(__dirname, '.');
@@ -28,6 +29,10 @@ export function run(): Promise<void> {
2829
} else {
2930
c();
3031
}
32+
// Force exit after tests complete
33+
setTimeout(() => {
34+
process.exit(failures > 0 ? 1 : 0);
35+
}, 1000);
3136
});
3237
} catch (err) {
3338
console.error(err);

src/test/migration.test.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import * as assert from 'assert';
2+
import { buildPrompt, buildTreeFromSelection, generatePatternsFromSelection, countTokens } from '@promptcode/core';
3+
import type { SelectedFile } from '@promptcode/core';
4+
5+
suite('Core Migration Test', () => {
6+
test('buildPrompt should generate correct output with tag compatibility', async () => {
7+
const selectedFiles: SelectedFile[] = [
8+
{
9+
path: '/test/file1.ts',
10+
name: 'file1.ts',
11+
content: 'const hello = "world";',
12+
relativePath: 'file1.ts',
13+
rootPath: '/test',
14+
tokens: 10
15+
},
16+
{
17+
path: '/test/dir/file2.ts',
18+
name: 'file2.ts',
19+
content: 'export function test() { return true; }',
20+
relativePath: 'dir/file2.ts',
21+
rootPath: '/test',
22+
tokens: 15
23+
}
24+
];
25+
26+
const result = await buildPrompt(selectedFiles, 'Test instructions', {
27+
includeFiles: true,
28+
includeInstructions: true,
29+
includeFileContents: true
30+
});
31+
32+
const prompt = result.prompt;
33+
34+
// Verify the prompt contains expected tags
35+
assert.ok(prompt.includes('<user_instructions>'));
36+
assert.ok(prompt.includes('Test instructions'));
37+
assert.ok(prompt.includes('</user_instructions>'));
38+
assert.ok(prompt.includes('<file_tree>'));
39+
assert.ok(prompt.includes('</file_tree>'));
40+
assert.ok(prompt.includes('<files>'));
41+
assert.ok(prompt.includes('file1.ts'));
42+
assert.ok(prompt.includes('file2.ts'));
43+
assert.ok(prompt.includes('const hello = "world";'));
44+
assert.ok(prompt.includes('export function test() { return true; }'));
45+
assert.ok(prompt.includes('</files>'));
46+
});
47+
48+
test('buildTreeFromSelection should generate correct tree structure', () => {
49+
const selectedFiles: SelectedFile[] = [
50+
{
51+
path: '/test/src/index.ts',
52+
name: 'index.ts',
53+
content: '',
54+
relativePath: 'src/index.ts',
55+
rootPath: '/test',
56+
tokens: 0
57+
},
58+
{
59+
path: '/test/src/utils/helper.ts',
60+
name: 'helper.ts',
61+
content: '',
62+
relativePath: 'src/utils/helper.ts',
63+
rootPath: '/test',
64+
tokens: 0
65+
},
66+
{
67+
path: '/test/README.md',
68+
name: 'README.md',
69+
content: '',
70+
relativePath: 'README.md',
71+
rootPath: '/test',
72+
tokens: 0
73+
}
74+
];
75+
76+
const tree = buildTreeFromSelection(selectedFiles);
77+
78+
// Verify tree structure
79+
assert.ok(tree.includes('src/'));
80+
assert.ok(tree.includes('index.ts'));
81+
assert.ok(tree.includes('utils/'));
82+
assert.ok(tree.includes('helper.ts'));
83+
assert.ok(tree.includes('README.md'));
84+
});
85+
86+
test('generatePatternsFromSelection should create correct patterns', () => {
87+
const selectedFiles: SelectedFile[] = [
88+
{
89+
path: '/test/src/components/Button.tsx',
90+
name: 'Button.tsx',
91+
content: '',
92+
relativePath: 'src/components/Button.tsx',
93+
rootPath: '/test',
94+
tokens: 0
95+
},
96+
{
97+
path: '/test/src/components/Input.tsx',
98+
name: 'Input.tsx',
99+
content: '',
100+
relativePath: 'src/components/Input.tsx',
101+
rootPath: '/test',
102+
tokens: 0
103+
},
104+
{
105+
path: '/test/src/utils/helper.ts',
106+
name: 'helper.ts',
107+
content: '',
108+
relativePath: 'src/utils/helper.ts',
109+
rootPath: '/test',
110+
tokens: 0
111+
}
112+
];
113+
114+
const patterns = generatePatternsFromSelection(selectedFiles);
115+
116+
// Should consolidate to pattern
117+
assert.ok(patterns.includes('src/components/*.tsx'));
118+
assert.ok(patterns.includes('src/utils/helper.ts'));
119+
});
120+
121+
test('countTokens should return consistent results', () => {
122+
const text = 'This is a test string for counting tokens in the VS Code extension.';
123+
const tokens = countTokens(text);
124+
125+
// Verify token count is reasonable
126+
assert.ok(tokens > 0);
127+
assert.ok(tokens < 100); // Should be much less than 100 for this short string
128+
});
129+
130+
test('Tag compatibility layer should transform old tags to new ones', () => {
131+
// This tests the tag transformation that happens in extension.ts
132+
const oldPrompt = '<instructions>Test</instructions><file_map>tree</file_map><file_contents>content</file_contents>';
133+
134+
// Simulate the tag transformation logic from extension.ts
135+
const TAG_MAP = {
136+
'instructions': 'user_instructions',
137+
'/instructions': '/user_instructions',
138+
'file_map': 'file_tree',
139+
'/file_map': '/file_tree',
140+
'file_contents': 'files',
141+
'/file_contents': '/files'
142+
} as const;
143+
144+
const newPrompt = oldPrompt.replace(/<(\/?)(instructions|file_map|file_contents)>/g, (match, slash, tag) => {
145+
const key = `${slash}${tag}` as keyof typeof TAG_MAP;
146+
return TAG_MAP[key] ? `<${TAG_MAP[key]}>` : match;
147+
});
148+
149+
assert.strictEqual(newPrompt, '<user_instructions>Test</user_instructions><file_tree>tree</file_tree><files>content</files>');
150+
});
151+
});

src/test/smoke.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as assert from 'assert';
2+
import * as vscode from 'vscode';
3+
4+
suite('Extension Smoke Test', () => {
5+
vscode.window.showInformationMessage('Start smoke tests.');
6+
7+
test('Extension should be present', () => {
8+
assert.ok(vscode.extensions.getExtension('cogflows.promptcode'));
9+
});
10+
11+
test('Extension should activate', async () => {
12+
const ext = vscode.extensions.getExtension('cogflows.promptcode');
13+
assert.ok(ext);
14+
await ext!.activate();
15+
assert.ok(ext!.isActive);
16+
});
17+
18+
test('Commands should be registered', async () => {
19+
const commands = await vscode.commands.getCommands(true);
20+
21+
// Check that main commands are registered
22+
assert.ok(commands.includes('promptcode.showPromptCodeView'));
23+
assert.ok(commands.includes('promptcode.generatePrompt'));
24+
assert.ok(commands.includes('promptcode.selectAll'));
25+
assert.ok(commands.includes('promptcode.deselectAll'));
26+
assert.ok(commands.includes('promptcode.clearTokenCache'));
27+
});
28+
29+
test('Core integration should work', async () => {
30+
// This test verifies that the core package is properly integrated
31+
const ext = vscode.extensions.getExtension('cogflows.promptcode');
32+
await ext!.activate();
33+
34+
// Try to get selected files (should return empty array initially)
35+
const result = await vscode.commands.executeCommand('promptcode.getSelectedFiles');
36+
assert.ok(Array.isArray(result));
37+
});
38+
39+
test('Should handle deactivation gracefully', () => {
40+
// This test ensures the extension can deactivate without errors
41+
const ext = vscode.extensions.getExtension('cogflows.promptcode');
42+
if (ext && ext.exports && ext.exports.deactivate) {
43+
assert.doesNotThrow(() => {
44+
ext.exports.deactivate();
45+
});
46+
}
47+
});
48+
});

0 commit comments

Comments
 (0)