Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.0.4] - 2026-01-18

### Changed
- Updated licensing model: Extension code is now MIT licensed (open source)
- Backend infrastructure (API, licensing system, NEAR contracts) remains proprietary
- Added clear licensing documentation to README and separate LICENSE files for backend components

## [0.0.3] - 2026-01-18

### Changed
- Updated README with correct publisher name (VitalPoint) and extension ID (hopper-velocity)
- Fixed marketplace links and installation commands

## [0.0.2] - 2026-01-18

### Changed
- Updated description to clarify freemium model

## [0.0.1] - 2026-01-17

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "hopper-velocity",
"displayName": "Hopper Velocity",
"description": "Ship projects faster with AI-powered planning and execution. First phase free, upgrade to continue your project.",
"version": "0.0.3",
"version": "0.0.4",
"publisher": "VitalPoint",
"license": "MIT",
"icon": "resources/icon.png",
Expand Down
1 change: 1 addition & 0 deletions src/chat/commands/completeMilestone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export async function handleCompleteMilestone(ctx: CommandContext): Promise<IHop
const nextPhase = incompletePhases[0].number;
stream.button({
command: 'hopper.chat-participant.plan-phase',
arguments: [nextPhase],
title: `Plan Phase ${nextPhase}`
});
stream.markdown(' ');
Expand Down
2 changes: 1 addition & 1 deletion src/chat/commands/considerIssues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ export async function handleConsiderIssues(ctx: CommandContext): Promise<IHopper

if (urgent.length > 0) {
stream.markdown(`**${urgent.length} urgent issue(s)** may need immediate attention.\n`);
stream.markdown('Consider using `/insert-phase` to address before continuing (coming in Phase 5.1).\n\n');
stream.markdown('Consider using `/insert-phase` to address before continuing.\n\n');
}

if (naturalFit.length > 0) {
Expand Down
2 changes: 2 additions & 0 deletions src/chat/commands/createRoadmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export async function handleCreateRoadmap(ctx: CommandContext): Promise<IHopperR

stream.button({
command: 'hopper.chat-participant.plan-phase',
arguments: [1],
title: 'Plan Phase 1'
});

Expand Down Expand Up @@ -399,6 +400,7 @@ export async function handleCreateRoadmap(ctx: CommandContext): Promise<IHopperR

stream.button({
command: 'hopper.chat-participant.plan-phase',
arguments: [1],
title: 'Plan Phase 1'
});

Expand Down
2 changes: 2 additions & 0 deletions src/chat/commands/discoveryPhase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ export async function handleDiscoveryPhase(ctx: CommandContext): Promise<IHopper
stream.markdown('*No file created for verify depth. Use `/plan-phase` to proceed.*\n\n');
stream.button({
command: 'hopper.chat-participant.plan-phase',
arguments: [phaseNum],
title: `Plan Phase ${phaseNum}`
});
return { metadata: { lastCommand: 'discovery-phase', phaseNumber: phaseNum } };
Expand Down Expand Up @@ -1015,6 +1016,7 @@ export async function handleDiscoveryPhase(ctx: CommandContext): Promise<IHopper
stream.markdown('Discovery complete. The findings will be included when planning.\n\n');
stream.button({
command: 'hopper.chat-participant.plan-phase',
arguments: [phaseNum],
title: `Plan Phase ${phaseNum}`
});

Expand Down
79 changes: 68 additions & 11 deletions src/chat/commands/discussPhase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,31 @@ Guidelines:

Always return valid JSON.`;

/**
* System prompt for merging new context with existing context
*/
const CONTEXT_MERGE_PROMPT = `You are helping merge new context into an existing context document.

The user has provided additional thoughts about a phase. Merge them with the existing context, preserving what was there while incorporating the new information.

Output your response as JSON:
{
"vision": "Updated vision that incorporates both existing and new thoughts",
"essential": ["Merged list of essential items - keep existing, add new, remove duplicates"],
"outOfScope": ["Merged list of out-of-scope items"],
"specificIdeas": "Combined specific ideas from both sources",
"additionalNotes": "Combined additional notes"
}

Guidelines:
- PRESERVE existing context - don't discard it
- INTEGRATE new information naturally
- If new info contradicts old, prefer the new (user changed their mind)
- Remove duplicates but keep all unique items
- The merged vision should feel cohesive, not like two separate things stapled together

Always return valid JSON.`;

/**
* Parse phase information from ROADMAP.md content
*/
Expand Down Expand Up @@ -337,21 +362,35 @@ export async function handleDiscussPhase(ctx: CommandContext): Promise<IHopperRe
// File doesn't exist, which is expected
}

// Load existing context if it exists (for merging)
let existingContextContent: string | undefined;
if (existingContext) {
try {
const existingBytes = await vscode.workspace.fs.readFile(contextUri);
existingContextContent = Buffer.from(existingBytes).toString('utf-8');
} catch {
// Could not read existing file
}
}

if (existingContext && !userContext) {
stream.markdown('## Context Already Exists\n\n');
stream.markdown(`Phase ${phaseNum} (${targetPhase.name}) already has context.\n\n`);
stream.markdown('**Existing context:**\n');
stream.reference(contextUri);
stream.markdown('\n\n');
stream.markdown('To update, delete the existing file and run `/discuss-phase` again.\n\n');
stream.markdown('To add more context, run the command with your additional thoughts:\n');
stream.markdown(`\`/discuss-phase ${phaseNum} [additional context to merge]\`\n\n`);
stream.markdown('**Or proceed to next steps:**\n\n');
stream.button({
command: 'hopper.chat-participant.research-phase',
arguments: [phaseNum],
title: `Research Phase ${phaseNum}`
});
stream.markdown(' ');
stream.button({
command: 'hopper.chat-participant.plan-phase',
arguments: [phaseNum],
title: `Plan Phase ${phaseNum}`
});
return { metadata: { lastCommand: 'discuss-phase' } };
Expand All @@ -360,17 +399,32 @@ export async function handleDiscussPhase(ctx: CommandContext): Promise<IHopperRe
stream.progress('Preparing discussion...');

try {
// If user provided context, synthesize it directly
// If user provided context, synthesize it (merging with existing if present)
if (userContext) {
stream.markdown('## Capturing Context\n\n');
stream.markdown(`**Phase ${phaseNum}: ${targetPhase.name}**\n\n`);
stream.markdown(`**Goal:** ${targetPhase.goal}\n\n`);
stream.markdown('Processing your context...\n\n');
const isMerging = !!existingContextContent;

if (isMerging) {
stream.markdown('## Merging Additional Context\n\n');
stream.markdown(`**Phase ${phaseNum}: ${targetPhase.name}**\n\n`);
stream.markdown(`**Goal:** ${targetPhase.goal}\n\n`);
stream.markdown('Merging your new context with existing...\n\n');
} else {
stream.markdown('## Capturing Context\n\n');
stream.markdown(`**Phase ${phaseNum}: ${targetPhase.name}**\n\n`);
stream.markdown(`**Goal:** ${targetPhase.goal}\n\n`);
stream.markdown('Processing your context...\n\n');
}

// Choose prompt based on whether we're merging or creating new
const prompt = isMerging ? CONTEXT_MERGE_PROMPT : CONTEXT_SYNTHESIS_PROMPT;
const userInput = isMerging
? `Phase: ${targetPhase.name}\nGoal: ${targetPhase.goal}\n\n--- EXISTING CONTEXT ---\n${existingContextContent}\n\n--- NEW INPUT TO MERGE ---\n${userContext}`
: `Phase: ${targetPhase.name}\nGoal: ${targetPhase.goal}\n\nUser's input:\n${userContext}`;

// Synthesize context
// Synthesize/merge context
const synthesisMessages: vscode.LanguageModelChatMessage[] = [
vscode.LanguageModelChatMessage.User(CONTEXT_SYNTHESIS_PROMPT),
vscode.LanguageModelChatMessage.User(`Phase: ${targetPhase.name}\nGoal: ${targetPhase.goal}\n\nUser's input:\n${userContext}`)
vscode.LanguageModelChatMessage.User(prompt),
vscode.LanguageModelChatMessage.User(userInput)
];

const synthesisResponse = await request.model.sendRequest(synthesisMessages, {}, token);
Expand Down Expand Up @@ -429,7 +483,7 @@ export async function handleDiscussPhase(ctx: CommandContext): Promise<IHopperRe
);

// Success!
stream.markdown('## Context Captured\n\n');
stream.markdown(isMerging ? '## Context Updated\n\n' : '## Context Captured\n\n');
stream.markdown(`**Phase ${phaseNum}: ${targetPhase.name}**\n\n`);
stream.markdown('### Vision Summary\n\n');
stream.markdown(`${context.vision}\n\n`);
Expand All @@ -442,7 +496,7 @@ export async function handleDiscussPhase(ctx: CommandContext): Promise<IHopperRe
stream.markdown('\n');
}

stream.markdown('**Created:**\n');
stream.markdown(isMerging ? '**Updated:**\n' : '**Created:**\n');
stream.reference(contextUri);
stream.markdown('\n\n');

Expand All @@ -453,11 +507,13 @@ export async function handleDiscussPhase(ctx: CommandContext): Promise<IHopperRe
stream.markdown('### Next Steps\n\n');
stream.button({
command: 'hopper.chat-participant.research-phase',
arguments: [phaseNum],
title: `Research Phase ${phaseNum}`
});
stream.markdown(' ');
stream.button({
command: 'hopper.chat-participant.plan-phase',
arguments: [phaseNum],
title: `Plan Phase ${phaseNum}`
});

Expand Down Expand Up @@ -535,6 +591,7 @@ export async function handleDiscussPhase(ctx: CommandContext): Promise<IHopperRe
stream.markdown('If you\'re ready to plan without detailed context:\n\n');
stream.button({
command: 'hopper.chat-participant.plan-phase',
arguments: [phaseNum],
title: `Plan Phase ${phaseNum}`
});

Expand Down
Loading