
Hve Core Installer
- 35 installs
- 1.3k repo stars
- Updated July 27, 2026
- microsoft/hve-core
hve-core-installer is an agent skill for installing HVE-Core via VS Code extension or clone-based methods with validation.
About
The hve-core-installer skill is a decision-driven installer for HVE-Core with environment detection, six clone-based methods, extension quick-install, validation, MCP configuration, and optional agent customization. It operates as Installer and Validator personas: the Installer detects the environment and executes steps, then the Validator verifies paths, settings, and agent accessibility before completion. Phase 1 obtains consent and detects the user environment. Phase 2 offers Quick Install via the VS Code Marketplace extension or clone-based installation for customization. Clone paths run a decision matrix recommending methods based on shell, IDE, and team needs across six clone strategies. Phase 5 validation confirms installation success, Phase 6 configures gitignore and presents MCP guidance, and Phase 7 optionally copies agents for local customization on clone installs only. Re-running validates existing installs or offers upgrade and is safe anytime. Compatibility requires VS Code or Insiders with git and network for clone methods. Use when installing HVE-Core agents, prompts, instructions, and skills into a development environment.
- Seven-phase flow with Installer and Validator persona handoff.
- Quick Install via VS Code Marketplace extension in about ten seconds.
- Six clone-based methods selected through environment decision matrix.
- Post-install validation checks paths, settings, and agent accessibility.
- Optional Phase 7 agent customization for clone-based installations only.
Hve Core Installer by the numbers
- 35 all-time installs (skills.sh)
- +3 installs in the week ending Jun 21, 2026 (Skillselion tracking)
- Ranked #8,704 of 16,659 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
hve-core-installer capabilities & compatibility
- Capabilities
- environment detection and consent flow · extension and clone install path selection · six clone based installation methods · post install validation and mcp setup · optional agent copy customization
- Use cases
- orchestration
- IDEs
- vscode
What hve-core-installer says it does
Decision-driven installer for HVE-Core with environment detection
Running the installer again validates an existing installation or offers upgrade.
Requires VS Code or VS Code Insiders.
npx skills add https://github.com/microsoft/hve-core --skill hve-core-installerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 35 |
|---|---|
| repo stars | ★ 1.3k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | microsoft/hve-core ↗ |
How do I install HVE-Core agents, prompts, and skills in my VS Code environment?
Install HVE-Core agents, prompts, and skills via VS Code extension or clone-based methods with validation.
Who is it for?
Developers setting up HVE-Core with either quick extension install or customizable clone workflows.
Skip if: Skip for using individual HVE skills after install or non-VS Code editor targets.
When should I use this skill?
User wants to install, upgrade, or validate HVE-Core agents and skills in VS Code.
What you get
A validated HVE-Core installation with configured settings, MCP guidance, and optional local agent copies.
Files
HVE-Core Installer Skill
Decision-driven installer for HVE-Core with environment detection, 6 clone-based installation methods, extension quick-install, validation, MCP configuration, and agent customization workflows.
Role Definition
Operate as two collaborating personas:
- The Installer persona detects the environment, guides method selection, and executes installation steps
- The Validator persona verifies installation success by checking paths, settings, and agent accessibility
The Installer persona handles all detection and execution. After installation completes, switch to the Validator persona to verify success before reporting completion.
Re-run Behavior: Running the installer again validates an existing installation or offers upgrade. Safe to re-run anytime.
Required Phases
| Phase | Name | Purpose |
|---|---|---|
| 1 | Environment Detection | Obtain consent and detect user's environment |
| 2 | Installation Path Selection | Choose between Extension (quick) or Clone-based installation |
| 3 | Environment Detection & Decision Matrix | For clone path: detect environment and recommend method |
| 4 | Installation Methods | Execute the selected installation method |
| 5 | Validation | Verify installation success and configure settings |
| 6 | Post-Installation Setup | Configure gitignore and present MCP guidance |
| 7 | Agent Customization | Optional: copy agents for local customization (clone-based only) |
Flow paths:
- Extension path: Phase 1 → Phase 2 → Phase 6 → Complete
- Clone-based path: Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5 → Phase 6 → Phase 7 → Complete
Phase 1: Environment Detection
Before presenting options, detect the user's environment to filter applicable installation methods.
Checkpoint 1: Initial Consent
Present the following and await explicit consent:
🚀 HVE-Core Installer
I'll help you install HVE-Core agents, prompts, instructions and skills.
Available content:
• 25+ specialized agents (task-researcher, task-planner, etc.)
• Reusable prompt templates for common workflows
• Technology-specific coding instructions (bash, python, markdown, etc.)
• Domain-specific skills (pr-reference, etc.)
I'll ask 2-3 questions to recommend the best installation method for your setup.
Would you like to proceed?If user declines, respond: "Installation cancelled. You can invoke this skill anytime to restart."
Upon consent, proceed to Phase 2 to offer the installation path choice.
Phase 2: Installation Path Selection
Present the installation path choice before environment detection. Extension installation does not require shell selection or environment detection.
Checkpoint 2: Installation Path Choice
Present the following choice:
<!-- <extension-quick-install-checkpoint> -->
🚀 Choose Your Installation Path
**Option 1: Quick Install (Recommended)**
Install the HVE Core extension from VS Code Marketplace.
• ⏱️ Takes about 10 seconds
• 🔄 Automatic updates
• ✅ No configuration needed
**Option 2: Clone-Based Installation**
Clone HVE-Core repository for customization.
• 🎨 Full customization support
• 📁 Files visible in your workspace
• 🤝 Team version control options
Which would you prefer? (1/2 or quick/clone)<!-- </extension-quick-install-checkpoint> -->
User input handling:
- "1", "quick", "extension", "marketplace" → Execute Extension Installation
- "2", "clone", "custom", "team" → Continue to Phase 3 (Environment Detection)
- Unclear response → Ask for clarification
If user selects Option 1 (Quick Install):
1. Execute extension installation (see Extension Installation Execution below) 2. Validate installation success 3. Display success report or offer fallback options
If user selects Option 2 (Clone-Based):
- Ask: "Which shell would you prefer? (powershell/bash)"
- Shell detection rules:
- "powershell", "pwsh", "ps1", "ps" → PowerShell
- "bash", "sh", "zsh" → Bash
- Unclear response → Windows = PowerShell, macOS/Linux = Bash
- Continue to Prerequisites Check, then Environment Detection Script and Phase 3 workflow
When to choose Clone over Extension:
- Need to customize agents, prompts, instructions, or skills
- Team requires version-controlled HVE-Core
- Offline or air-gapped environment
Prerequisites Check
Before clone-based installation, verify git is available:
- Run:
git --version - If fails: "Git is required for clone-based installation. Install git or choose Extension Quick Install."
Extension Installation Execution
When user selects Quick Install, first ask which VS Code variant they are using:
<!-- <vscode-variant-prompt> -->
Which VS Code variant are you using?
[1] VS Code (stable)
[2] VS Code Insiders
Your choice? (1/2)<!-- </vscode-variant-prompt> -->
User input handling:
- "1", "code", "stable" → Use
codeCLI - "2", "insiders", "code-insiders" → Use
code-insidersCLI - Unclear response → Ask for clarification
Store the user's choice as the code_cli variable for use in validation scripts.
Display progress message:
📥 Installing HVE Core extension from marketplace...
Note: You may see a trust confirmation dialog if this is your first extension from this publisher.Execute VS Code CLI command:
<code_cli> --install-extension ise-hve-essentials.hve-coreAfter command execution, proceed to Extension Validation.
Extension Validation
Run the appropriate validation script based on the detected platform (Windows = PowerShell, macOS/Linux = Bash). Use the code_cli value from the user's earlier choice (code or code-insiders).
PowerShell: Run scripts/validate-extension.ps1 with the code_cli variable set.
Bash: Run scripts/validate-extension.sh with the code_cli variable set.
Extension Success Report
Upon successful validation, display:
<!-- <extension-success-report> -->
✅ Extension Installation Complete!
The HVE Core extension has been installed from the VS Code Marketplace.
📦 Extension: ise-hve-essentials.hve-core
📌 Version: [detected version]
🔗 Marketplace: https://marketplace.visualstudio.com/items?itemName=ise-hve-essentials.hve-core
🧪 Available Agents:
• task-researcher, task-planner, task-implementor, task-reviewer
• github-backlog-manager, adr-creation, doc-ops, pr-review
• prompt-builder, memory, and more!
📋 Configuring optional settings...<!-- </extension-success-report> -->
After displaying the extension success report, proceed to Phase 6: Post-Installation Setup for gitignore and MCP configuration options.
Extension Error Recovery
If extension installation fails, provide targeted guidance:
<!-- <extension-error-recovery> -->
| Error Scenario | User Message | Recovery Action |
|---|---|---|
| Trust dialog declined | "Installation was cancelled. You may have declined the publisher trust prompt." | Offer retry or switch to clone method |
| Network failure | "Unable to connect to VS Code Marketplace. Check your network connection." | Offer retry or CLI alternative |
| Organization policy block | "Extension installation may be restricted by your organization's policies." | Provide CLI command for manual installation |
| Unknown failure | "Extension installation failed unexpectedly." | Offer clone-based installation as fallback |
<!-- </extension-error-recovery> -->
Flow Control After Failure:
If extension installation fails and user cannot resolve:
- Offer: "Would you like to try a clone-based installation method instead? (yes/no)"
- If yes: Continue to Environment Detection Script and Phase 3 workflow
- If no: End session with manual installation instructions
Environment Detection Script
Run the appropriate detection script based on the user's shell:
PowerShell: Run scripts/detect-environment.ps1
Bash: Run scripts/detect-environment.sh
Phase 3: Environment Detection & Decision Matrix
Based on detected environment, ask the following questions to determine the recommended method.
Question 1: Environment Confirmation
Present options filtered by detection results:
<!-- <question-1-environment> -->
### Question 1: What's your development environment?
Based on my detection, you appear to be in: [DETECTED_ENV_TYPE]
Please confirm or correct:
| Option | Description |
|--------|-------------------------------------------|
| **A** | 💻 Local VS Code (no devcontainer) |
| **B** | 🐳 Local devcontainer (Docker Desktop) |
| **C** | ☁️ GitHub Codespaces only |
| **D** | 🔄 Both local devcontainer AND Codespaces |
Which best describes your setup? (A/B/C/D)<!-- </question-1-environment> -->
Question 2: Team or Solo
<!-- <question-2-team> -->
### Question 2: Team or solo development?
| Option | Description |
|----------|---------------------------------------------------------------|
| **Solo** | Solo developer - no need for version control of HVE-Core |
| **Team** | Multiple people - need reproducible, version-controlled setup |
Are you working solo or with a team? (solo/team)<!-- </question-2-team> -->
Question 3: Update Preference
Ask this question only when multiple methods match the environment + team answers:
<!-- <question-3-updates> -->
### Question 3: Update preference?
| Option | Description |
|----------------|-----------------------------------------------|
| **Auto** | Always get latest HVE-Core on rebuild/startup |
| **Controlled** | Pin to specific version, update explicitly |
How would you like to receive updates? (auto/controlled)<!-- </question-3-updates> -->
Decision Matrix
Use this matrix to determine the recommended method:
<!-- <decision-matrix> -->
| Environment | Team | Updates | Recommended Method |
|---|---|---|---|
| Any (simplest) | Any | - | Extension Quick Install (works in all environments) |
| Local (no container) | Solo | - | Method 1: Peer Clone |
| Local (no container) | Team | Controlled | Method 6: Submodule |
| Local devcontainer | Solo | Auto | Method 2: Git-Ignored |
| Local devcontainer | Team | Controlled | Method 6: Submodule |
| Codespaces only | Solo | Auto | Method 4: Codespaces |
| Codespaces only | Team | Controlled | Method 6: Submodule |
| Both local + Codespaces | Any | Any | Method 5: Multi-Root Workspace |
| HVE-Core repo (Codespaces) | - | - | Method 4: Codespaces (already configured) |
<!-- </decision-matrix> -->
Method Selection Logic
After gathering answers:
1. Match answers to decision matrix 2. Present recommendation with rationale 3. Offer alternative if user prefers different approach
<!-- <recommendation-template> -->
## 📋 Your Recommended Setup
Based on your answers:
* **Environment**: [answer]
* **Team**: [answer]
* **Updates**: [answer]
### ✅ Recommended: Method [N] - [Name]
**Why this fits your needs:**
* [Benefit 1 matching their requirements]
* [Benefit 2 matching their requirements]
* [Benefit 3 matching their requirements]
Would you like to proceed with this method, or see alternatives?<!-- </recommendation-template> -->
Phase 4: Installation Methods
Execute the installation workflow based on the method selected via the decision matrix. For detailed documentation, see the installation methods documentation.
Method Configuration
| Method | Documentation | Target Location | Settings Path Prefix | Best For |
|---|---|---|---|---|
| 1. Peer Clone | peer-clone.md | ../hve-core | ../hve-core | Local VS Code, solo developers |
| 2. Git-Ignored | git-ignored.md | .hve-core/ | .hve-core | Devcontainer, isolation |
| 3. Mounted* | mounted.md | /workspaces/hve-core | /workspaces/hve-core | Devcontainer + host clone |
| 4. Codespaces | codespaces.md | /workspaces/hve-core | /workspaces/hve-core | Codespaces |
| 5. Multi-Root | multi-root.md | Per workspace file | Actual clone path | Local VS Code, best IDE integration |
| 6. Submodule | submodule.md | lib/hve-core | lib/hve-core | Team version control |
*Method 3 (Mounted) is for advanced scenarios where host already has hve-core cloned. Most devcontainer users should use Method 2.
Common Clone Operation
Generate a script for the user's shell (PowerShell or Bash) that:
1. Determines workspace root via git rev-parse --show-toplevel 2. Calculates target path based on method from table 3. Checks if target already exists 4. Clones if missing: git clone https://github.com/microsoft/hve-core.git <target> 5. Reports success with ✅ or skip with ⏭️
<!-- <clone-reference-powershell> -->
$ErrorActionPreference = 'Stop'
$hveCoreDir = "<METHOD_TARGET_PATH>" # Replace per method
if (-not (Test-Path $hveCoreDir)) {
git clone https://github.com/microsoft/hve-core.git $hveCoreDir
Write-Host "✅ Cloned HVE-Core to $hveCoreDir"
} else {
Write-Host "⏭️ HVE-Core already exists at $hveCoreDir"
}<!-- </clone-reference-powershell> -->
For Bash: Use set -euo pipefail, test -d for existence checks, and echo for output.
Settings Configuration
After cloning, update .vscode/settings.json with entries for each collection subdirectory. Replace <PREFIX> with the settings path prefix from the method table. Do not use ** glob patterns in paths because chat.*Locations settings do not support them.
Enumerate each collection subdirectory under .github/agents/, .github/prompts/, and .github/instructions/ from the cloned HVE-Core directory. Create one entry per subdirectory. For .github/agents/, also check each collection folder for a subagents/ subfolder and include it when present (e.g., hve-core/subagents). For .github/skills/, list only the collection-level folders directly under .github/skills/ (e.g., shared); do not enumerate deeper subfolders (individual skill directories like shared/pr-reference/ are not listed). Exclude the installer collection from chat.agentSkillsLocations because it is the installer skill itself and not intended for end-user settings.
Any folder named experimental under any artifact type (agents, prompts, instructions, or skills) must not be included without first asking the user whether they want experimental features. If the user opts in, add the experimental entries (and experimental/subagents for agents when that subfolder exists).
<!-- <settings-template> -->
{
"chat.agentFilesLocations": {
"<PREFIX>/.github/agents/ado": true,
"<PREFIX>/.github/agents/coding-standards": true,
"<PREFIX>/.github/agents/data-science": true,
"<PREFIX>/.github/agents/design-thinking": true,
"<PREFIX>/.github/agents/github": true,
"<PREFIX>/.github/agents/hve-core": true,
"<PREFIX>/.github/agents/hve-core/subagents": true,
"<PREFIX>/.github/agents/project-planning": true,
"<PREFIX>/.github/agents/security": true
},
"chat.promptFilesLocations": {
"<PREFIX>/.github/prompts/ado": true,
"<PREFIX>/.github/prompts/coding-standards": true,
"<PREFIX>/.github/prompts/design-thinking": true,
"<PREFIX>/.github/prompts/github": true,
"<PREFIX>/.github/prompts/hve-core": true,
"<PREFIX>/.github/prompts/security": true
},
"chat.instructionsFilesLocations": {
"<PREFIX>/.github/instructions/ado": true,
"<PREFIX>/.github/instructions/coding-standards": true,
"<PREFIX>/.github/instructions/github": true,
"<PREFIX>/.github/instructions/hve-core": true,
"<PREFIX>/.github/instructions/shared": true
},
"chat.agentSkillsLocations": {
"<PREFIX>/.github/skills": true,
"<PREFIX>/.github/skills/coding-standards": true,
"<PREFIX>/.github/skills/design-thinking": true,
"<PREFIX>/.github/skills/project-planning": true,
"<PREFIX>/.github/skills/rai": true,
"<PREFIX>/.github/skills/security": true,
"<PREFIX>/.github/skills/shared": true
}
}<!-- </settings-template> -->
Method-Specific Instructions
Method 1: Peer Clone
Clone to parent directory: Split-Path $workspaceRoot -Parent | Join-Path -ChildPath "hve-core"
Method 2: Git-Ignored
Additional steps before cloning:
1. Create .hve-core/ directory 2. Add .hve-core/ to .gitignore (create if missing) 3. Clone into .hve-core/
Method 3: Mounted Directory
Requires host-side setup and container rebuild:
Step 1: Display pre-rebuild instructions:
📋 Pre-Rebuild Setup Required
Clone hve-core on your HOST machine (not in container):
cd <parent-of-your-project>
git clone https://github.com/microsoft/hve-core.gitStep 2: Add mount to devcontainer.json:
<!-- <method-3-devcontainer-mount> -->
{
"mounts": [
"source=${localWorkspaceFolder}/../hve-core,target=/workspaces/hve-core,type=bind,readonly=true,consistency=cached"
]
}<!-- </method-3-devcontainer-mount> -->
Step 3: After rebuild, validate mount exists at /workspaces/hve-core
Method 4: postCreateCommand (Codespaces)
Add to devcontainer.json:
<!-- <method-4-devcontainer> -->
{
"postCreateCommand": "[ -d /workspaces/hve-core ] || git clone --depth 1 https://github.com/microsoft/hve-core.git /workspaces/hve-core",
"customizations": {
"vscode": {
"settings": {
"chat.agentFilesLocations": {
"/workspaces/hve-core/.github/agents/ado": true,
"/workspaces/hve-core/.github/agents/coding-standards": true,
"/workspaces/hve-core/.github/agents/data-science": true,
"/workspaces/hve-core/.github/agents/design-thinking": true,
"/workspaces/hve-core/.github/agents/github": true,
"/workspaces/hve-core/.github/agents/hve-core": true,
"/workspaces/hve-core/.github/agents/hve-core/subagents": true,
"/workspaces/hve-core/.github/agents/project-planning": true,
"/workspaces/hve-core/.github/agents/security": true
},
"chat.promptFilesLocations": {
"/workspaces/hve-core/.github/prompts/ado": true,
"/workspaces/hve-core/.github/prompts/coding-standards": true,
"/workspaces/hve-core/.github/prompts/design-thinking": true,
"/workspaces/hve-core/.github/prompts/github": true,
"/workspaces/hve-core/.github/prompts/hve-core": true,
"/workspaces/hve-core/.github/prompts/security": true
},
"chat.instructionsFilesLocations": {
"/workspaces/hve-core/.github/instructions/ado": true,
"/workspaces/hve-core/.github/instructions/coding-standards": true,
"/workspaces/hve-core/.github/instructions/github": true,
"/workspaces/hve-core/.github/instructions/hve-core": true,
"/workspaces/hve-core/.github/instructions/shared": true
},
"chat.agentSkillsLocations": {
"/workspaces/hve-core/.github/skills": true,
"/workspaces/hve-core/.github/skills/coding-standards": true,
"/workspaces/hve-core/.github/skills/design-thinking": true,
"/workspaces/hve-core/.github/skills/project-planning": true,
"/workspaces/hve-core/.github/skills/rai": true,
"/workspaces/hve-core/.github/skills/security": true,
"/workspaces/hve-core/.github/skills/shared": true
}
}
}
}
}<!-- </method-4-devcontainer> -->
Optional: Add updateContentCommand for auto-updates on rebuild.
Method 5: Multi-Root Workspace
Create hve-core.code-workspace file with folders array pointing to both project and HVE-Core.
Use the actual clone path (not the folder display name) as the settings prefix. Folder display names in chat.*Locations settings do not resolve reliably.
[!IMPORTANT]
The dev container spec has noworkspaceFileproperty. Codespaces and devcontainers always open in single-folder mode. The user must manually open the.code-workspacefile after the container starts (File > Open Workspace from File...orcode <path>.code-workspace). For Codespaces, Method 4 is usually more convenient because it configures settings automatically without requiring a workspace switch.
Local VS Code: use a relative clone path from the workspace file's directory.
<!-- <method-5-workspace-local> -->
{
"folders": [
{ "name": "My Project", "path": "." },
{ "path": "../hve-core" }
],
"settings": { /* Same as settings template with ../hve-core prefix */ }
}<!-- </method-5-workspace-local> -->
User opens the .code-workspace file instead of the folder.
Method 6: Submodule
Use git submodule commands instead of clone:
git submodule add https://github.com/microsoft/hve-core.git lib/hve-core
git submodule update --init --recursive
git add .gitmodules lib/hve-core
git commit -m "Add HVE-Core as submodule"Team members run git submodule update --init --recursive after cloning.
Optional devcontainer.json for auto-initialization:
<!-- <method-6-devcontainer> -->
{
"onCreateCommand": "git submodule update --init --recursive",
"updateContentCommand": "git submodule update --remote lib/hve-core || true"
}<!-- </method-6-devcontainer> -->
Phase 5: Validation (Validator Persona)
After installation completes, switch to the Validator persona and verify the installation.
[!IMPORTANT]
After successful validation, proceed to Phase 6 for post-installation setup, then Phase 7 for optional agent customization (clone-based methods only).
Checkpoint 3: Settings Authorization
Before modifying settings.json, present the following:
⚙️ VS Code Settings Update
I will now update your VS Code settings to add HVE-Core paths.
Changes to be made:
• [List paths based on selected method]
⚠️ Authorization Required: Do you authorize these settings changes? (yes/no)If user declines: "Installation cancelled. No settings changes were made."
Validation Workflow
Run validation based on the selected method. Set the base path variable before running:
| Method | Base Path |
|---|---|
| 1 | ../hve-core |
| 2 | .hve-core |
| 3, 4 | /workspaces/hve-core |
| 5 | Check workspace file |
| 6 | lib/hve-core |
PowerShell: Run scripts/validate-installation.ps1 with the method and basePath variables set.
Bash: Run scripts/validate-installation.sh with the method number and base path as arguments.
Success Report
Upon successful validation, display:
<!-- <success-report> -->
✅ Core Installation Complete!
Method [N]: [Name] installed successfully.
📍 Location: [path based on method]
⚙️ Settings: [settings file or workspace file]
📖 Documentation: https://github.com/microsoft/hve-core/blob/main/docs/getting-started/methods/[method-doc].md
🧪 Available Agents:
• task-researcher, task-planner, task-implementor, task-reviewer
• github-backlog-manager, adr-creation, doc-ops, pr-review
• prompt-builder, memory, and more!
📋 Configuring optional settings...<!-- </success-report> -->
After displaying the success report, proceed to Phase 6 for post-installation setup.
Phase 6: Post-Installation Setup
This phase applies to all installation methods (Extension and Clone-based). Both paths converge here for consistent post-installation configuration.
Checkpoint 4: Gitignore Configuration
🛡️ Configuring gitignore...
Check and configure gitignore entries based on the installation method. Different methods may require different gitignore entries.
Method-Specific Gitignore Entries
| Method | Gitignore Entry | Reason |
|---|---|---|
| 2 (Git-Ignored) | .hve-core/ | Excludes the local HVE-Core clone |
| All methods | .copilot-tracking/ | Excludes AI workflow artifacts |
Detection: Check if .gitignore exists and contains the required entries.
For Method 2 (Git-Ignored): If .hve-core/ is not in .gitignore, it should have been added during Phase 4 installation. Verify it exists.
For all methods: Check if .copilot-tracking/ should be added to .gitignore. This directory stores local AI workflow artifacts (plans, changes, research notes) that are typically user-specific and not meant for version control.
- If pattern found → Skip this checkpoint silently
- If
.gitignoremissing or pattern not found → Present the prompt below
<!-- <gitignore-prompt> -->
📋 Gitignore Recommendation
The `.copilot-tracking/` directory stores local AI workflow artifacts:
• Plans and implementation tracking
• Research notes and change records
• User-specific prompts and handoff logs
These files are typically not meant for version control.
Would you like to add `.copilot-tracking/` to your .gitignore? (yes/no)<!-- </gitignore-prompt> -->
User input handling:
- "yes", "y" → Add entry to
.gitignore - "no", "n", "skip" → Skip without changes
- Unclear response → Ask for clarification
Modification: If user approves:
- If
.gitignoreexists: Append the following at the end of the file - If
.gitignoremissing: Create it with the content below
<!-- <gitignore-entry> -->
# HVE-Core AI workflow artifacts (local only)
.copilot-tracking/<!-- </gitignore-entry> -->
Report: "✅ Added .copilot-tracking/ to .gitignore"
After the gitignore checkpoint, proceed to Checkpoint 5 (MCP Configuration).
Checkpoint 5: MCP Configuration Guidance
After the gitignore checkpoint (for any installation method), present MCP configuration guidance. This helps users who want to use agents that integrate with Azure DevOps, GitHub, or documentation services.
<!-- <mcp-guidance-prompt> -->
📡 MCP Server Configuration (Optional)
Some HVE-Core agents integrate with external services via MCP (Model Context Protocol):
| Agent | MCP Server | Purpose |
|------------------------|--------------------------|--------------------------------------|
| ado-prd-to-wit | ado | Azure DevOps work items |
| github-backlog-manager | github | GitHub backlog management |
| task-researcher | context7, microsoft-docs | Documentation lookup |
| dt-coach | figma | FigJam board export for DT artifacts |
⚠️ Jira agents (jira-backlog-manager, jira-prd-to-wit) use environment variables
instead of MCP. Run /jira-setup in Copilot Chat to configure Jira credentials.
Would you like to configure MCP servers? (yes/no)<!-- </mcp-guidance-prompt> -->
User input handling:
- "yes", "y" → Ask which servers to configure (see MCP Server Selection below)
- "no", "n", "skip" → Proceed to Final Completion Report
- Enter, "continue", "done" → Proceed to Final Completion Report
- Unclear response → Proceed to Final Completion Report (non-blocking)
MCP Server Selection
If user chooses to configure MCP, present:
<!-- <mcp-server-selection> -->
Which MCP servers would you like to configure?
| Server | Purpose | Recommended For |
|----------------|---------------------------|----------------------------------|
| github | GitHub issues and repos | GitHub-hosted repositories |
| ado | Azure DevOps work items | Azure DevOps repositories |
| context7 | SDK/library documentation | All users (optional) |
| microsoft-docs | Microsoft Learn docs | All users (optional) |
| figma | FigJam & Figma design | Design Thinking collection users |
⚠️ Suggest EITHER github OR ado based on where your repo is hosted, not both.
Enter server names separated by commas (e.g., "github, context7"):<!-- </mcp-server-selection> -->
Parse the user's response to determine which servers to include.
MCP Configuration Templates
Create .vscode/mcp.json using ONLY the templates below. Use HTTP type with managed authentication where available.
[!IMPORTANT]
These are the only correct configurations. Do not use stdio/npx for servers that support HTTP.
github server (HTTP with managed auth)
{
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/"
}
}ado server (stdio with inputs)
{
"inputs": [
{
"id": "ado_org",
"type": "promptString",
"description": "Azure DevOps organization name (e.g. 'contoso')",
"default": ""
},
{
"id": "ado_tenant",
"type": "promptString",
"description": "Azure tenant ID (required for multi-tenant scenarios)",
"default": ""
}
],
"servers": {
"ado": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@azure-devops/mcp", "${input:ado_org}", "--tenant", "${input:ado_tenant}", "-d", "core", "work", "work-items", "search", "repositories", "pipelines"]
}
}
}context7 server (stdio)
{
"context7": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}microsoft-docs server (HTTP)
{
"microsoft-docs": {
"type": "http",
"url": "https://learn.microsoft.com/api/mcp"
}
}figma server (HTTP with managed auth)
{
"figma": {
"type": "http",
"url": "https://mcp.figma.com/mcp"
}
}MCP File Generation
When creating .vscode/mcp.json:
1. Create .vscode/ directory if it does not exist 2. Combine only the selected server configurations into a single JSON object 3. Include inputs array only if ado server is selected 4. Merge all selected servers under a single servers object
Example combined configuration for "github, context7":
<!-- <mcp-combined-example> -->
{
"servers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/"
},
"context7": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}
}<!-- </mcp-combined-example> -->
After creating the file, display:
✅ Created .vscode/mcp.json with [server names] configuration
📖 Full documentation: https://github.com/microsoft/hve-core/blob/main/docs/getting-started/mcp-configuration.mdFinal Completion Report
After gitignore and MCP checkpoints complete, display the final completion message:
<!-- <final-completion-report> -->
✅ Setup Complete!
▶️ Next Steps:
1. Reload VS Code (Ctrl+Shift+P → "Reload Window")
2. Open Copilot Chat (`Ctrl+Alt+I`) and click the agent picker dropdown
3. Select an agent to start working
💡 Select `task-researcher` from the picker to explore HVE-Core capabilities<!-- </final-completion-report> -->
For Extension installations, also include:
---
📝 Want to customize HVE-Core or share with your team?
Run this skill again and choose "Clone-Based Installation" for full customization options.For Clone-based installations, proceed to Phase 7 for optional agent customization.
Phase 7: Agent Customization (Optional)
[!IMPORTANT]
Generated scripts in this phase require PowerShell 7+ (pwsh). Windows PowerShell 5.1 is not supported.After Phase 6 completes, offer users the option to copy agent files into their target repository. This phase ONLY applies to clone-based installation methods (1-6), NOT to extension installation.
Skip Condition
If user selected Extension Quick Install (Option 1) in Phase 2, skip Phase 7 entirely. Extension installation bundles agents automatically.
Checkpoint 6: Agent Copy Decision
Present the agent selection prompt:
<!-- <agent-copy-prompt> -->
📂 Agent Customization (Optional)
HVE-Core includes specialized agents for common workflows.
Copying agents enables local customization and offline use.
🔬 RPI Core (Research-Plan-Implement workflow)
• task-researcher - Technical research and evidence gathering
• task-planner - Implementation plan creation
• task-implementor - Plan execution with tracking
• task-reviewer - Implementation review and validation
• rpi-agent - RPI workflow coordinator
📋 Planning & Documentation
• adr-creation, agile-coach, brd-builder, doc-ops, prd-builder
• product-manager-advisor, security-planner, ux-ui-designer
⚙️ Generators
• gen-data-spec, gen-jupyter-notebook, gen-streamlit-dashboard
✅ Review & Testing
• pr-review, prompt-builder, test-streamlit-dashboard
🧠 Utilities
• memory - Conversation memory and session continuity
🔗 Platform-Specific
• ado-prd-to-wit (Azure DevOps)
• github-backlog-manager (GitHub)
Options:
[1] Install RPI Core only (recommended)
[2] Install by collection
[3] Skip agent installation
Your choice? (1/2/3)<!-- </agent-copy-prompt> -->
User input handling:
- "1", "rpi", "rpi core", "core" → Copy RPI Core bundle only
- "2", "collection", "by collection" → Proceed to Collection Selection sub-flow
- "3", "skip", "none", "no" → Skip to success report
- Unclear response → Ask for clarification
Collection Selection Sub-Flow
When the user selects option 2, read collection manifests to present available collections.
Step 1: Read collections and build collection agent counts
Read collections/*.collection.yml from the HVE-Core source (at $hveCoreBasePath). Derive collection options from collection id and name. For each selected collection, count agent items where kind equals agent and effective item maturity is stable (item maturity omitted defaults to stable; exclude experimental and deprecated).
Step 2: Present collection options
<!-- <collection-selection-prompt> -->
🎭 Collection Selection
Choose one or more collections to install agents tailored to your role, more to come in the future.
| # | Collection | Agents | Description |
|---|------------|--------|---------------------------------|
| 1 | Developer | [N] | Software engineers writing code |
Enter collection number(s) separated by commas (e.g., "1"):<!-- </collection-selection-prompt> -->
Agent counts [N] include agents matching the collection with stable maturity.
User input handling:
- Single number (e.g., "1") → Select that collection
- Multiple numbers (e.g., "1, 3") → Combine agent sets from selected collections
- Collection name (e.g., "developer") → Match by identifier
- Unclear response → Ask for clarification
Step 3: Build filtered agent list
For each selected collection identifier:
1. Iterate through items in the collection manifest 2. Include items where kind is agent AND maturity is stable 3. Deduplicate across multiple selected collections
Step 4: Present filtered agents for confirmation
<!-- <collection-confirmation-prompt> -->
📋 Agents for [Collection Name(s)]
The following [N] agents will be copied:
• [agent-name-1] - tags: [tag-1, tag-2]
• [agent-name-2] - tags: [tag-1, tag-2]
...
Proceed with installation? (yes/no)<!-- </collection-confirmation-prompt> -->
User input handling:
- "yes", "y" → Proceed with copy using filtered agent list
- "no", "n" → Return to Checkpoint 6 for re-selection
- Unclear response → Ask for clarification
[!NOTE]
Collection filtering applies to agents only. Copying of related prompts, instructions, and skills based on collection is planned for a future release.
Agent Bundle Definitions
| Bundle | Agents |
|---|---|
hve-core | task-researcher, task-planner, task-implementor, task-reviewer, rpi-agent |
collection:<id> | Stable agents matching the collection |
Collision Detection
Before copying, check for existing agent files with matching names.
PowerShell: Run scripts/collision-detection.ps1 with the hveCoreBasePath, selection, and optional collectionAgents variables set.
Bash: Run scripts/collision-detection.sh with the HVE-Core base path and file list as arguments.
Collision Resolution Prompt
If collisions are detected, present:
<!-- <collision-prompt> -->
⚠️ Existing Agents Detected
The following agents already exist in your project:
• [list collision files]
Options:
[O] Overwrite with HVE-Core version
[K] Keep existing (skip these files)
[C] Compare (show diff for first file)
Or for all conflicts:
[OA] Overwrite all
[KA] Keep all existing
Your choice?<!-- </collision-prompt> -->
User input handling:
- "o", "overwrite" → Overwrite current file, ask about next
- "k", "keep" → Keep current file, ask about next
- "c", "compare" → Show diff, then re-prompt
- "oa", "overwrite all" → Overwrite all collisions
- "ka", "keep all" → Keep all existing files
Agent Copy Execution
After selection and collision resolution, execute the copy operation.
PowerShell: Run scripts/agent-copy.ps1 with the required variables set.
Bash: Run scripts/agent-copy.sh with the HVE-Core base path, collection ID, and file list as arguments.
Agent Copy Success Report
Upon successful copy, display:
<!-- <agent-copy-success> -->
✅ Agent Installation Complete!
Copied [N] agents to .github/agents/
Created .hve-tracking.json for upgrade tracking
📄 Installed Agents:
• [list of copied agent names]
🔄 Upgrade Workflow:
Run this installer again to check for agent updates.
Modified files will prompt before overwriting.
Use 'eject' to take ownership of any file.
Proceeding to final success report...<!-- </agent-copy-success> -->
Phase 7 Upgrade Mode
When .hve-tracking.json already exists, Phase 7 operates in upgrade mode.
Upgrade Detection
At Phase 7 start, check for existing manifest.
PowerShell: Run scripts/upgrade-detection.ps1 with the hveCoreBasePath variable set.
Bash: Run scripts/upgrade-detection.sh with the HVE-Core base path as an argument.
Upgrade Prompt
If upgrade mode with version change:
<!-- <upgrade-prompt> -->
🔄 HVE-Core Agent Upgrade
Source: microsoft/hve-core v[SOURCE_VERSION]
Installed: v[INSTALLED_VERSION]
Checking file status...<!-- </upgrade-prompt> -->
File Status Check
Compare current files against manifest.
PowerShell: Run scripts/file-status-check.ps1.
Bash: Run scripts/file-status-check.sh to compare files against the manifest.
Upgrade Summary Display
Present upgrade summary:
<!-- <upgrade-summary> -->
📋 Upgrade Summary
Files to update (managed):
✅ .github/agents/hve-core/task-researcher.agent.md
✅ .github/agents/hve-core/task-planner.agent.md
Files requiring decision (modified):
⚠️ .github/agents/hve-core/task-implementor.agent.md
Files skipped (ejected):
🔒 .github/agents/custom-agent.agent.md
For modified files, choose:
[A] Accept upstream (overwrite your changes)
[K] Keep local (skip this update)
[E] Eject (never update this file again)
[D] Show diff
Process file: task-implementor.agent.md?<!-- </upgrade-summary> -->
Diff Display
When user requests diff:
<!-- <diff-display> -->
─────────────────────────────────────
File: .github/agents/hve-core/task-implementor.agent.md
Status: modified
─────────────────────────────────────
--- Local version
+++ HVE-Core version
@@ -10,3 +10,5 @@
## Role Definition
-Your local modifications here
+Updated behavior with new capabilities
+
+New section added in latest version
─────────────────────────────────────
[A] Accept upstream / [K] Keep local / [E] Eject<!-- </diff-display> -->
Status Transitions
After user decision, update manifest:
| Decision | Status Change | Manifest Update |
|---|---|---|
| Accept | modified → managed | Update hash, version |
| Keep | modified → modified | No change (skip file) |
| Eject | * → ejected | Add ejectedAt timestamp |
Eject Implementation
When user ejects a file:
PowerShell: Run scripts/eject.ps1 with the FilePath parameter.
Bash: Run scripts/eject.sh with the file path as an argument.
Upgrade Completion
After processing all files:
<!-- <upgrade-success> -->
✅ Upgrade Complete!
Updated: [N] files
Skipped: [M] files (kept local or ejected)
Version: v[OLD] → v[NEW]
Proceeding to final success report...<!-- </upgrade-success> -->
Error Recovery
Provide targeted guidance when steps fail:
<!-- <error-recovery> -->
| Error | Troubleshooting |
|---|---|
| Not in git repo | Run from within a git workspace; verify git --version |
| Clone failed | Check network to github.com; verify git credentials and write permissions |
| Validation failed | Repository may be incomplete; delete HVE-Core directory and re-run installer |
| Settings update failed | Verify settings.json is valid JSON; check permissions; try closing VS Code |
<!-- </error-recovery> -->
Rollback
To remove a failed or unwanted installation:
| Method | Cleanup |
|---|---|
| Extension | VS Code → Extensions → HVE Core → Uninstall |
| 1 (Peer Clone) | rm -rf ../hve-core |
| 2 (Git-Ignored) | rm -rf .hve-core |
| 3-4 (Mounted/Codespaces) | Remove mount/postCreate from devcontainer.json |
| 5 (Multi-Root) | Delete .code-workspace file |
| 6 (Submodule) | git submodule deinit lib/hve-core && git rm lib/hve-core |
Then remove HVE-Core paths from .vscode/settings.json.
If you used Phase 7 agent copy, also delete .hve-tracking.json and optionally .github/agents/ if you no longer need copied agents.
Authorization Guardrails
Never modify files without explicit user authorization. Always explain changes before making them. Respect denial at any checkpoint.
Checkpoints requiring authorization:
1. Initial Consent (Phase 1) - before starting detection 2. Settings Authorization (Phase 5, Checkpoint 3) - before editing settings/devcontainer
Output Format Requirements
Progress Reporting
Use these exact emojis for consistency:
In-progress indicators (always end with ellipsis ...):
- "📂 Detecting environment..."
- "🔍 Asking configuration questions..."
- "📋 Recommending installation method..."
- "📥 Installing HVE-Core..."
- "🔍 Validating installation..."
- "⚙️ Updating settings..."
- "🛡️ Configuring gitignore..."
- "📡 Configuring MCP servers..."
Completion indicators:
- "✅ [Success message]"
- "❌ [Error message]"
- "⏭️ [Skipped message]"
Success Criteria
Success: Environment detected, method selected, HVE-Core directories validated (agents, prompts, instructions, skills), settings configured, user directed to reload.
Failure: Detection fails, clone/submodule fails, validation finds missing directories, or settings modification fails.
Script Usage Examples
Environment Detection
Detect whether the current environment is local, devcontainer, or Codespaces.
./scripts/detect-environment.ps1./scripts/detect-environment.shCollision Detection
Check for existing agent files before copying.
./scripts/collision-detection.ps1 -Selection hve-coreUse a custom collection with explicit agent paths.
./scripts/collision-detection.ps1 -Selection my-collection -CollectionAgents @('my-collection/custom.agent.md')Agent Copy
Copy agents from the HVE-Core source into the target project.
./scripts/agent-copy.ps1 -HveCoreBasePath ./lib/hve-core -CollectionId hve-core -FilesToCopy @('hve-core/task-researcher.agent.md', 'hve-core/task-planner.agent.md')Preserve existing files during an upgrade.
./scripts/agent-copy.ps1 -HveCoreBasePath ./lib/hve-core -CollectionId hve-core -FilesToCopy @('hve-core/task-researcher.agent.md') -KeepExisting -Collisions @('.github/agents/task-researcher.agent.md')Upgrade Detection
Check whether an existing installation needs upgrading.
./scripts/upgrade-detection.ps1 -HveCoreBasePath ./lib/hve-coreValidate Installation
Verify a clone-based installation for a specific method.
./scripts/validate-installation.ps1 -BasePath ./my-project -Method 1Validate a multi-root workspace installation (method 5).
./scripts/validate-installation.ps1 -BasePath ./my-project -Method 5Validate Extension
Check whether the HVE-Core VS Code extension is installed.
./scripts/validate-extension.ps1Use an alternate VS Code CLI.
./scripts/validate-extension.ps1 -CodeCli 'code-insiders'File Status Check
Compare installed file hashes against the tracking manifest.
./scripts/file-status-check.ps1Eject a File
Mark a managed file as ejected so upgrades no longer overwrite it.
./scripts/eject.ps1 -FilePath .github/agents/task-researcher.agent.md🤖 Crafted with precision by ✨Copilot following brilliant human instruction, then carefully refined by our team of discerning human reviewers.
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
<#
.SYNOPSIS
Copies selected HVE-Core agents to the target repository.
.DESCRIPTION
Creates .github/agents/ directory, copies agent files, computes SHA256 hashes,
and writes .hve-tracking.json manifest for upgrade tracking.
.PARAMETER HveCoreBasePath
Root path of the local HVE-Core clone used as the copy source.
.PARAMETER CollectionId
Collection identifier recorded in the tracking manifest.
.PARAMETER FilesToCopy
Array of agent file paths relative to the source agents directory.
.PARAMETER KeepExisting
When set, existing files listed in Collisions are preserved instead of overwritten.
.PARAMETER Collisions
Array of target file paths that already exist and may conflict.
.EXAMPLE
./scripts/agent-copy.ps1 -HveCoreBasePath ../hve-core -CollectionId hve-core -FilesToCopy @('hve-core/task-researcher.agent.md')
.OUTPUTS
Per-file copy status and manifest creation confirmation.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path $_ })]
[string]$HveCoreBasePath,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$CollectionId,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string[]]$FilesToCopy,
[Parameter()]
[switch]$KeepExisting,
[Parameter()]
[string[]]$Collisions = @()
)
$ErrorActionPreference = 'Stop'
$sourceBase = "$hveCoreBasePath/.github/agents"
$targetDir = ".github/agents"
$manifestPath = ".hve-tracking.json"
# Create target directory
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
Write-Host "✅ Created $targetDir"
}
# Initialize manifest
$manifest = @{
source = "microsoft/hve-core"
version = (Get-Content "$hveCoreBasePath/package.json" | ConvertFrom-Json).version
installed = (Get-Date -Format "o")
collection = $collectionId # "hve-core" or collection id(s) e.g. "developer" or "developer,devops"
files = @{}; skip = @()
}
# Copy files (source paths are relative to agents/, target is flat)
foreach ($file in $filesToCopy) {
$fileName = Split-Path $file -Leaf
$sourcePath = Join-Path $sourceBase $file
$targetPath = Join-Path $targetDir $fileName
$relPath = ".github/agents/$fileName"
if ($keepExisting -and $collisions -contains $targetPath) {
Write-Host "⏭️ Kept existing: $fileName"; continue
}
Set-Content -Path $targetPath -Value (Get-Content $sourcePath -Raw) -NoNewline
$hash = (Get-FileHash -Path $targetPath -Algorithm SHA256).Hash.ToLower()
$manifest.files[$relPath] = @{ version = $manifest.version; sha256 = $hash; status = "managed" }
Write-Host "✅ Copied $fileName"
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content $manifestPath
Write-Host "✅ Created $manifestPath"
#!/usr/bin/env bash
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
# Copies selected HVE-Core agents to the target repository.
# Creates .github/agents/, copies agent files, computes SHA256 hashes,
# and writes .hve-tracking.json manifest for upgrade tracking.
# Usage: agent-copy.sh <hve_core_base_path> <collection_id> <file1> [file2...]
# Files are paths relative to the agents/ directory.
set -euo pipefail
hve_core_base_path="${1:?Usage: $0 <hve_core_base_path> <collection_id> <file1> [file2...]}"
collection_id="${2:?Usage: $0 <hve_core_base_path> <collection_id> <file1> [file2...]}"
shift 2
source_base="$hve_core_base_path/.github/agents"
target_dir=".github/agents"
manifest_path=".hve-tracking.json"
keep_existing="${KEEP_EXISTING:-false}"
collisions_file="${COLLISIONS_FILE:-}"
has_jq=false
command -v jq >/dev/null 2>&1 && has_jq=true
# Load collision list when keep_existing is enabled
declare -A collision_set
if [ "$keep_existing" = "true" ] && [ -n "$collisions_file" ] && [ -f "$collisions_file" ]; then
while IFS= read -r line; do
[ -n "$line" ] && collision_set["$line"]=1
done < "$collisions_file"
fi
# Create target directory
mkdir -p "$target_dir"
# Get version from package.json
if [ "$has_jq" = true ]; then
version=$(jq -r '.version' "$hve_core_base_path/package.json")
else
version=$(grep -o '"version": *"[^"]*"' "$hve_core_base_path/package.json" | head -1 | sed 's/.*"\([^"]*\)"/\1/')
fi
# Initialize manifest JSON
installed=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
files_json="{}"
# Copy files (source paths are relative to agents/, target is flat)
for file in "$@"; do
filename=$(basename "$file")
source_path="$source_base/$file"
target_path="$target_dir/$filename"
rel_path=".github/agents/$filename"
# Skip files the user chose to keep during collision resolution
if [ "$keep_existing" = "true" ] && [ -n "${collision_set[$target_path]+x}" ]; then
echo "⏭️ Kept existing: $filename"
continue
fi
cp "$source_path" "$target_path"
hash=$(sha256sum "$target_path" | cut -d' ' -f1)
if [ "$has_jq" = true ]; then
files_json=$(echo "$files_json" | jq --arg path "$rel_path" --arg ver "$version" --arg sha "$hash" \
'. + {($path): {"version": $ver, "sha256": $sha, "status": "managed"}}')
else
# Build JSON entries without jq
if [ "$files_json" = "{}" ]; then
files_json="{\"$rel_path\": {\"version\": \"$version\", \"sha256\": \"$hash\", \"status\": \"managed\"}}"
else
files_json="${files_json%\}}, \"$rel_path\": {\"version\": \"$version\", \"sha256\": \"$hash\", \"status\": \"managed\"}}"
fi
fi
echo "✅ Copied $filename"
done
# Write manifest
if [ "$has_jq" = true ]; then
jq -n --arg src "microsoft/hve-core" --arg ver "$version" --arg inst "$installed" \
--arg col "$collection_id" --argjson files "$files_json" \
'{source: $src, version: $ver, installed: $inst, collection: $col, files: $files}' \
> "$manifest_path"
else
cat > "$manifest_path" <<EOF
{"source": "microsoft/hve-core", "version": "$version", "installed": "$installed", "collection": "$collection_id", "files": $files_json}
EOF
fi
echo "✅ Created $manifest_path"
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
<#
.SYNOPSIS
Detects file collisions before copying HVE-Core agents.
.DESCRIPTION
Checks the target directory for existing agent files that would conflict
with the selected agent bundle or collection.
.PARAMETER Selection
Agent bundle to check. Use 'hve-core' for the default set or a collection identifier.
.PARAMETER CollectionAgents
Array of agent file paths relative to the agents directory for non-default collections.
.EXAMPLE
./scripts/collision-detection.ps1 -Selection hve-core
.EXAMPLE
./scripts/collision-detection.ps1 -Selection my-collection -CollectionAgents @('my-collection/custom.agent.md')
.OUTPUTS
COLLISIONS_DETECTED=true/false and COLLISION_FILES list.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Selection,
[Parameter()]
[string[]]$CollectionAgents = @()
)
$ErrorActionPreference = 'Stop'
$targetDir = ".github/agents"
# Get files to copy based on selection (paths relative to agents/)
$filesToCopy = switch ($selection) {
"hve-core" { @("hve-core/task-researcher.agent.md", "hve-core/task-planner.agent.md", "hve-core/task-implementor.agent.md", "hve-core/task-reviewer.agent.md", "hve-core/rpi-agent.agent.md") }
default {
# Collection-based: paths from collection manifest relative to agents/
$collectionAgents
}
}
# Check for collisions (target uses filename only)
$collisions = @()
foreach ($file in $filesToCopy) {
$fileName = Split-Path $file -Leaf
$targetPath = Join-Path $targetDir $fileName
if (Test-Path $targetPath) { $collisions += $targetPath }
}
if ($collisions.Count -gt 0) {
Write-Host "COLLISIONS_DETECTED=true"
Write-Host "COLLISION_FILES=$($collisions -join ',')"
} else {
Write-Host "COLLISIONS_DETECTED=false"
}
#!/usr/bin/env bash
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
# Detects file collisions before copying HVE-Core agents.
# Usage: collision-detection.sh <hve_core_base_path> <selection> [collection_agents...]
# selection: 'hve-core' for RPI core bundle, or collection id
# collection_agents: space-separated relative paths (when selection is a collection)
set -euo pipefail
hve_core_base_path="${1:?Usage: $0 <hve_core_base_path> <selection> [collection_agents...]}"
selection="${2:?Usage: $0 <hve_core_base_path> <selection> [collection_agents...]}"
shift 2
target_dir=".github/agents"
# Build file list based on selection
case "$selection" in
hve-core)
files_to_copy=(
"hve-core/task-researcher.agent.md"
"hve-core/task-planner.agent.md"
"hve-core/task-implementor.agent.md"
"hve-core/task-reviewer.agent.md"
"hve-core/rpi-agent.agent.md"
)
;;
*)
files_to_copy=("$@")
;;
esac
# Check for collisions (target uses filename only)
collisions=()
for file in "${files_to_copy[@]}"; do
filename=$(basename "$file")
target_path="$target_dir/$filename"
if [ -f "$target_path" ]; then
collisions+=("$target_path")
fi
done
if [ ${#collisions[@]} -gt 0 ]; then
echo "COLLISIONS_DETECTED=true"
IFS=','; echo "COLLISION_FILES=${collisions[*]}"
else
echo "COLLISIONS_DETECTED=false"
fi
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
<#
.SYNOPSIS
Detects the current development environment for HVE-Core installation.
.DESCRIPTION
Identifies whether the user is in a local VS Code, devcontainer, or Codespaces
environment and reports relevant configuration details.
.EXAMPLE
./scripts/detect-environment.ps1
.OUTPUTS
Key-value pairs: ENV_TYPE, IS_CODESPACES, IS_DEVCONTAINER,
HAS_DEVCONTAINER_JSON, HAS_WORKSPACE_FILE, IS_HVE_CORE_REPO.
#>
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
# Detect environment type
$env_type = "local"
$is_codespaces = $false
$is_devcontainer = $false
if ($env:CODESPACES -eq "true") {
$env_type = "codespaces"
$is_codespaces = $true
$is_devcontainer = $true
} elseif ((Test-Path "/.dockerenv") -or ($env:REMOTE_CONTAINERS -eq "true")) {
$env_type = "devcontainer"
$is_devcontainer = $true
}
$has_devcontainer_json = Test-Path ".devcontainer/devcontainer.json"
$has_workspace_file = (Get-ChildItem -Filter "*.code-workspace" -ErrorAction SilentlyContinue | Measure-Object).Count -gt 0
try {
$is_hve_core_repo = (Split-Path (git rev-parse --show-toplevel 2>$null) -Leaf) -eq "hve-core"
} catch {
$is_hve_core_repo = $false
}
Write-Host "ENV_TYPE=$env_type"
Write-Host "IS_CODESPACES=$is_codespaces"
Write-Host "IS_DEVCONTAINER=$is_devcontainer"
Write-Host "HAS_DEVCONTAINER_JSON=$has_devcontainer_json"
Write-Host "HAS_WORKSPACE_FILE=$has_workspace_file"
Write-Host "IS_HVE_CORE_REPO=$is_hve_core_repo"
#!/usr/bin/env bash
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
# Detects the current development environment for HVE-Core installation.
# Outputs key-value pairs: ENV_TYPE, IS_CODESPACES, IS_DEVCONTAINER,
# HAS_DEVCONTAINER_JSON, HAS_WORKSPACE_FILE, IS_HVE_CORE_REPO.
set -euo pipefail
# Detect environment type
env_type="local"
is_codespaces=false
is_devcontainer=false
if [ "${CODESPACES:-}" = "true" ]; then
env_type="codespaces"
is_codespaces=true
is_devcontainer=true
elif [ -f "/.dockerenv" ] || [ "${REMOTE_CONTAINERS:-}" = "true" ]; then
env_type="devcontainer"
is_devcontainer=true
fi
has_devcontainer_json=false
[ -f ".devcontainer/devcontainer.json" ] && has_devcontainer_json=true
has_workspace_file=false
[ -n "$(find . -maxdepth 1 -name '*.code-workspace' -print -quit 2>/dev/null)" ] && has_workspace_file=true
is_hve_core_repo=false
repo_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
[ -n "$repo_root" ] && [ "$(basename "$repo_root")" = "hve-core" ] && is_hve_core_repo=true
echo "ENV_TYPE=$env_type"
echo "IS_CODESPACES=$is_codespaces"
echo "IS_DEVCONTAINER=$is_devcontainer"
echo "HAS_DEVCONTAINER_JSON=$has_devcontainer_json"
echo "HAS_WORKSPACE_FILE=$has_workspace_file"
echo "IS_HVE_CORE_REPO=$is_hve_core_repo"
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
<#
.SYNOPSIS
Ejects a tracked file from HVE-Core upgrade management.
.DESCRIPTION
Marks a file as 'ejected' in .hve-tracking.json so future upgrades
skip it. The file remains on disk but is owned by the user.
.PARAMETER FilePath
The relative path to the file to eject (e.g., .github/agents/task-implementor.agent.md).
.EXAMPLE
./scripts/eject.ps1 -FilePath '.github/agents/task-implementor.agent.md'
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$FilePath
)
$ErrorActionPreference = 'Stop'
$manifest = Get-Content ".hve-tracking.json" | ConvertFrom-Json -AsHashtable
if ($manifest.files.ContainsKey($FilePath)) {
$manifest.files[$FilePath].status = "ejected"
$manifest.files[$FilePath].ejectedAt = (Get-Date -Format "o")
$manifest | ConvertTo-Json -Depth 10 | Set-Content ".hve-tracking.json"
Write-Host "✅ Ejected: $FilePath"
Write-Host " This file will never be updated by HVE-Core."
} else {
Write-Host "❌ File not found in tracking manifest: $FilePath"
}
#!/usr/bin/env bash
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
# Ejects a tracked file from HVE-Core upgrade management.
# Marks the file as 'ejected' in .hve-tracking.json so future upgrades skip it.
# Usage: eject.sh <file_path>
set -euo pipefail
file_path="${1:?Usage: $0 <file_path>}"
manifest_path=".hve-tracking.json"
if [ ! -f "$manifest_path" ]; then
echo "❌ No .hve-tracking.json found" >&2
exit 1
fi
if jq -e --arg fp "$file_path" '.files[$fp]' "$manifest_path" >/dev/null 2>&1; then
ejected_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
jq --arg fp "$file_path" --arg ea "$ejected_at" \
'.files[$fp].status = "ejected" | .files[$fp].ejectedAt = $ea' \
"$manifest_path" > "${manifest_path}.tmp" && mv "${manifest_path}.tmp" "$manifest_path"
echo "✅ Ejected: $file_path"
echo " This file will never be updated by HVE-Core."
else
echo "❌ File not found in tracking manifest: $file_path"
fi
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
<#
.SYNOPSIS
Compares current agent files against the .hve-tracking.json manifest.
.DESCRIPTION
For each tracked file, computes the current SHA256 hash and compares it
against the stored hash to determine status: managed, modified, ejected,
or missing.
.EXAMPLE
./scripts/file-status-check.ps1
.OUTPUTS
Per-file status lines: FILE=<path>|STATUS=<status>|ACTION=<action>.
#>
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
$manifest = Get-Content ".hve-tracking.json" | ConvertFrom-Json -AsHashtable
$statusReport = @()
foreach ($file in $manifest.files.Keys) {
$entry = $manifest.files[$file]
$status = $entry.status
if ($status -eq "ejected") {
$statusReport += @{
file = $file
status = "ejected"
action = "Skip (user owns this file)"
}
continue
}
if (-not (Test-Path $file)) {
$statusReport += @{
file = $file
status = "missing"
action = "Will restore"
}
continue
}
$currentHash = (Get-FileHash -Path $file -Algorithm SHA256).Hash.ToLower()
if ($currentHash -ne $entry.sha256) {
$statusReport += @{
file = $file
status = "modified"
action = "Requires decision"
currentHash = $currentHash
storedHash = $entry.sha256
}
} else {
$statusReport += @{
file = $file
status = "managed"
action = "Will update"
}
}
}
$statusReport | ForEach-Object {
Write-Host "FILE=$($_.file)|STATUS=$($_.status)|ACTION=$($_.action)"
}
#!/usr/bin/env bash
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
# Compares current agent files against the .hve-tracking.json manifest.
# Requires jq. Outputs per-file status lines.
set -euo pipefail
manifest_path=".hve-tracking.json"
if [ ! -f "$manifest_path" ]; then
echo "❌ No .hve-tracking.json found" >&2
exit 1
fi
jq -r '.files | to_entries[] | "\(.key)|\(.value.status)|\(.value.sha256)"' "$manifest_path" | while IFS='|' read -r file status stored_hash; do
if [ "$status" = "ejected" ]; then
echo "FILE=$file|STATUS=ejected|ACTION=Skip (user owns this file)"
continue
fi
if [ ! -f "$file" ]; then
echo "FILE=$file|STATUS=missing|ACTION=Will restore"
continue
fi
current_hash=$(sha256sum "$file" | cut -d' ' -f1)
if [ "$current_hash" != "$stored_hash" ]; then
echo "FILE=$file|STATUS=modified|ACTION=Requires decision"
else
echo "FILE=$file|STATUS=managed|ACTION=Will update"
fi
done
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
<#
.SYNOPSIS
Detects whether the current installation is eligible for upgrade.
.DESCRIPTION
Checks for .hve-tracking.json and compares installed version against
the source HVE-Core version from package.json.
.PARAMETER HveCoreBasePath
Root path of the local HVE-Core clone containing package.json.
.EXAMPLE
./scripts/upgrade-detection.ps1 -HveCoreBasePath ../hve-core
.OUTPUTS
UPGRADE_MODE, INSTALLED_VERSION, SOURCE_VERSION, VERSION_CHANGED,
INSTALLED_COLLECTION key-value pairs.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path $_ })]
[string]$HveCoreBasePath
)
$ErrorActionPreference = 'Stop'
$manifestPath = ".hve-tracking.json"
if (Test-Path $manifestPath) {
$manifest = Get-Content $manifestPath | ConvertFrom-Json -AsHashtable
$sourceVersion = (Get-Content "$hveCoreBasePath/package.json" | ConvertFrom-Json).version
Write-Host "UPGRADE_MODE=true"
Write-Host "INSTALLED_VERSION=$($manifest.version)"
Write-Host "SOURCE_VERSION=$sourceVersion"
Write-Host "VERSION_CHANGED=$($sourceVersion -ne $manifest.version)"
$collection = if ($manifest.collection) { $manifest.collection } else { 'hve-core' }
Write-Host "INSTALLED_COLLECTION=$collection"
} else {
Write-Host "UPGRADE_MODE=false"
}
#!/usr/bin/env bash
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
# Detects whether the current installation is eligible for upgrade.
# Checks for .hve-tracking.json and compares installed version against source.
# Usage: upgrade-detection.sh <hve_core_base_path>
set -euo pipefail
hve_core_base_path="${1:?Usage: $0 <hve_core_base_path>}"
manifest_path=".hve-tracking.json"
if [ -f "$manifest_path" ]; then
if command -v jq >/dev/null 2>&1; then
installed_version=$(jq -r '.version' "$manifest_path")
installed_collection=$(jq -r '.collection // "hve-core"' "$manifest_path")
source_version=$(jq -r '.version' "$hve_core_base_path/package.json")
else
installed_version=$(grep -o '"version": *"[^"]*"' "$manifest_path" | head -1 | sed 's/.*"\([^"]*\)"/\1/')
installed_collection="hve-core"
source_version=$(grep -o '"version": *"[^"]*"' "$hve_core_base_path/package.json" | head -1 | sed 's/.*"\([^"]*\)"/\1/')
fi
version_changed=false
[ "$source_version" != "$installed_version" ] && version_changed=true
echo "UPGRADE_MODE=true"
echo "INSTALLED_VERSION=$installed_version"
echo "SOURCE_VERSION=$source_version"
echo "VERSION_CHANGED=$version_changed"
echo "INSTALLED_COLLECTION=$installed_collection"
else
echo "UPGRADE_MODE=false"
fi
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
<#
.SYNOPSIS
Validates that the HVE Core VS Code extension is installed.
.DESCRIPTION
Checks the installed extensions list for ise-hve-essentials.hve-core
and reports version information.
.PARAMETER CodeCli
Path or name of the VS Code CLI executable. Defaults to 'code'.
.EXAMPLE
./scripts/validate-extension.ps1
.EXAMPLE
./scripts/validate-extension.ps1 -CodeCli 'code-insiders'
.OUTPUTS
EXTENSION_INSTALLED=True/False and version details.
#>
[CmdletBinding()]
param(
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$CodeCli = 'code'
)
$ErrorActionPreference = 'Stop'
# Check if extension is installed
$extensions = & $codeCli --list-extensions 2>$null
if ($extensions -match "ise-hve-essentials.hve-core") {
Write-Host "✅ HVE Core extension installed successfully"
$installed = $true
} else {
Write-Host "❌ Extension not found in installed extensions"
$installed = $false
}
# Verify version (optional)
$versionOutput = & $codeCli --list-extensions --show-versions 2>$null | Select-String "ise-hve-essentials.hve-core"
if ($versionOutput) {
Write-Host "📌 Version: $($versionOutput -replace '.*@', '')"
}
Write-Host "EXTENSION_INSTALLED=$installed"
#!/usr/bin/env bash
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
# Validates that the HVE Core VS Code extension is installed.
# Set code_cli to 'code' or 'code-insiders' before running.
# Outputs EXTENSION_INSTALLED=true/false and version details.
set -euo pipefail
# Set based on user's earlier choice: 'code' or 'code-insiders'
code_cli="${code_cli:-code}"
# Check if extension is installed
if "$code_cli" --list-extensions 2>/dev/null | grep -q "ise-hve-essentials.hve-core"; then
echo "✅ HVE Core extension installed successfully"
installed=true
else
echo "❌ Extension not found in installed extensions"
installed=false
fi
# Verify version (optional)
version=$("$code_cli" --list-extensions --show-versions 2>/dev/null | grep "ise-hve-essentials.hve-core" | sed 's/.*@//' || true)
[ -n "$version" ] && echo "📌 Version: $version"
echo "EXTENSION_INSTALLED=$installed"
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
<#
.SYNOPSIS
Validates an HVE-Core clone-based installation.
.DESCRIPTION
Checks that required directories exist and method-specific configuration
is correct (workspace file for multi-root, .gitmodules for submodule).
.PARAMETER BasePath
Root path of the installation to validate.
.PARAMETER Method
Installation method number (1-6) that determines which validations to run.
.EXAMPLE
./scripts/validate-installation.ps1 -BasePath ./my-project -Method 1
.EXAMPLE
./scripts/validate-installation.ps1 -BasePath ./my-project -Method 5
.OUTPUTS
Per-directory pass/fail status and overall validation result.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path $_ })]
[string]$BasePath,
[Parameter(Mandatory)]
[ValidateRange(1, 6)]
[int]$Method
)
$ErrorActionPreference = 'Stop'
$valid = $true
foreach ($dir in @("$basePath/.github/agents", "$basePath/.github/prompts", "$basePath/.github/instructions", "$basePath/.github/skills")) {
if (-not (Test-Path $dir)) { $valid = $false; Write-Host "❌ Missing: $dir" }
else { Write-Host "✅ Found: $dir" }
}
# Optional: informational check for experimental subdirectories (absence is not a failure)
foreach ($dir in @("$basePath/.github/skills/experimental", "$basePath/.github/agents/experimental")) {
if (Test-Path $dir) { Write-Host "ℹ️ Found optional: $dir" }
}
# Method 5 additional check: workspace file
if ($method -eq 5 -and (Test-Path "hve-core.code-workspace")) {
$workspace = Get-Content "hve-core.code-workspace" | ConvertFrom-Json
if ($workspace.folders.Count -lt 2) { $valid = $false; Write-Host "❌ Multi-root not configured" }
else { Write-Host "✅ Multi-root configured" }
}
# Method 6 additional check: submodule
if ($method -eq 6) {
if (-not (Test-Path ".gitmodules") -or -not (Select-String -Path ".gitmodules" -Pattern "lib/hve-core" -Quiet)) {
$valid = $false; Write-Host "❌ Submodule not in .gitmodules"
}
}
if ($valid) { Write-Host "✅ Installation validated successfully" }
#!/usr/bin/env bash
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
# Validates an HVE-Core clone-based installation.
# Usage: validate-installation.sh <method> <base_path>
# method: Installation method number (1-6)
# base_path: Path to hve-core root directory
set -euo pipefail
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <method> <base_path>" >&2
echo " method: Installation method number (1-6)" >&2
echo " base_path: Path to hve-core root directory" >&2
exit 1
fi
method="$1"
base_path="$2"
valid=true
for path in "$base_path/.github/agents" "$base_path/.github/prompts" "$base_path/.github/instructions" "$base_path/.github/skills"; do
if [ -d "$path" ]; then echo "✅ Found: $path"; else echo "❌ Missing: $path"; valid=false; fi
done
# Optional: informational check for experimental subdirectories (absence is not a failure)
for path in "$base_path/.github/skills/experimental" "$base_path/.github/agents/experimental"; do
if [ -d "$path" ]; then echo "ℹ️ Found optional: $path"; fi
done
# Method 5: workspace file check (requires jq)
if [ "$method" = "5" ]; then
if ! command -v jq >/dev/null 2>&1; then
echo "⚠️ jq not installed - skipping workspace JSON validation"
echo " Install jq for full validation, or manually verify hve-core.code-workspace has 2+ folders"
elif [ -f "hve-core.code-workspace" ] && jq -e '.folders | length >= 2' hve-core.code-workspace >/dev/null 2>&1; then
echo "✅ Multi-root configured"
else
echo "❌ Multi-root not configured"; valid=false
fi
fi
# Method 6: submodule check
[ "$method" = "6" ] && { grep -q "lib/hve-core" .gitmodules 2>/dev/null && echo "✅ Submodule configured" || { echo "❌ Submodule not in .gitmodules"; valid=false; }; }
[ "$valid" = true ] && echo "✅ Installation validated successfully"
#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
Describe 'agent-copy' -Tag 'Unit' {
BeforeAll {
$script:scriptPath = Join-Path $PSScriptRoot '../scripts/agent-copy.ps1'
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "hve-test-agent-copy-$([guid]::NewGuid().ToString('N'))"
$script:sourceRoot = Join-Path $script:testRoot 'source'
$script:targetRoot = Join-Path $script:testRoot 'target'
}
BeforeEach {
# Create clean source and target directories for each test
New-Item -ItemType Directory -Path $script:sourceRoot -Force | Out-Null
New-Item -ItemType Directory -Path $script:targetRoot -Force | Out-Null
# Create source agents directory with sample files
$agentsDir = Join-Path $script:sourceRoot '.github/agents/hve-core'
New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
Set-Content -Path (Join-Path $agentsDir 'task-researcher.agent.md') -Value '# Researcher' -NoNewline
Set-Content -Path (Join-Path $agentsDir 'task-planner.agent.md') -Value '# Planner' -NoNewline
# Create package.json in source
@{ version = '2.0.0' } | ConvertTo-Json | Set-Content (Join-Path $script:sourceRoot 'package.json')
Push-Location $script:targetRoot
}
AfterEach {
Pop-Location
}
AfterAll {
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
Context 'Directory creation' {
It 'Creates .github/agents directory when it does not exist' {
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md')
Test-Path '.github/agents' | Should -BeTrue
}
It 'Does not fail when .github/agents directory already exists' {
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
{ & $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md') } | Should -Not -Throw
}
}
Context 'File copying' {
It 'Copies specified agent files to target directory' {
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md', 'hve-core/task-planner.agent.md')
Test-Path '.github/agents/task-researcher.agent.md' | Should -BeTrue
Test-Path '.github/agents/task-planner.agent.md' | Should -BeTrue
}
It 'Copies file content correctly' {
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md')
Get-Content '.github/agents/task-researcher.agent.md' -Raw | Should -Be '# Researcher'
}
It 'Skips collision files when keepExisting is true' {
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path '.github/agents/task-researcher.agent.md' -Value '# Custom' -NoNewline
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md') -KeepExisting -Collisions @(Join-Path '.github/agents' 'task-researcher.agent.md')
Get-Content '.github/agents/task-researcher.agent.md' -Raw | Should -Be '# Custom'
}
It 'Overwrites files when keepExisting is false' {
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path '.github/agents/task-researcher.agent.md' -Value '# Custom' -NoNewline
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md')
Get-Content '.github/agents/task-researcher.agent.md' -Raw | Should -Be '# Researcher'
}
}
Context 'Manifest creation' {
It 'Creates .hve-tracking.json manifest' {
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md')
Test-Path '.hve-tracking.json' | Should -BeTrue
}
It 'Writes correct manifest structure' {
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md')
$manifest = Get-Content '.hve-tracking.json' | ConvertFrom-Json
$manifest.source | Should -Be 'microsoft/hve-core'
$manifest.version | Should -Be '2.0.0'
$manifest.collection | Should -Be 'hve-core'
}
It 'Stores SHA256 hashes for copied files' {
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md')
$manifest = Get-Content '.hve-tracking.json' | ConvertFrom-Json -AsHashtable
$fileEntry = $manifest.files['.github/agents/task-researcher.agent.md']
$fileEntry | Should -Not -BeNullOrEmpty
$fileEntry.sha256 | Should -Not -BeNullOrEmpty
$fileEntry.status | Should -Be 'managed'
}
It 'Records correct SHA256 hash matching the file on disk' {
& $script:scriptPath -HveCoreBasePath $script:sourceRoot -CollectionId 'hve-core' -FilesToCopy @('hve-core/task-researcher.agent.md')
$manifest = Get-Content '.hve-tracking.json' | ConvertFrom-Json -AsHashtable
$expectedHash = (Get-FileHash -Path '.github/agents/task-researcher.agent.md' -Algorithm SHA256).Hash.ToLower()
$manifest.files['.github/agents/task-researcher.agent.md'].sha256 | Should -Be $expectedHash
}
}
}
#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
Describe 'collision-detection' -Tag 'Unit' {
BeforeAll {
$script:scriptPath = Join-Path $PSScriptRoot '../scripts/collision-detection.ps1'
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "hve-test-collision-$([guid]::NewGuid().ToString('N'))"
$script:sourceRoot = Join-Path $script:testRoot 'source'
}
BeforeEach {
New-Item -ItemType Directory -Path $script:testRoot -Force | Out-Null
New-Item -ItemType Directory -Path $script:sourceRoot -Force | Out-Null
# Create source agents
$agentsDir = Join-Path $script:sourceRoot '.github/agents/hve-core'
New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
Set-Content -Path (Join-Path $agentsDir 'task-researcher.agent.md') -Value '# Researcher'
Set-Content -Path (Join-Path $agentsDir 'task-planner.agent.md') -Value '# Planner'
Push-Location $script:testRoot
}
AfterEach {
Pop-Location
}
AfterAll {
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
Context 'No collisions' {
It 'Reports COLLISIONS_DETECTED=false when target directory does not exist' {
$output = & $script:scriptPath -Selection 'hve-core' 6>&1 | Out-String
$output | Should -Match 'COLLISIONS_DETECTED=false'
}
It 'Reports COLLISIONS_DETECTED=false when no matching files exist' {
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path '.github/agents/unrelated.md' -Value '# Other'
$output = & $script:scriptPath -Selection 'hve-core' 6>&1 | Out-String
$output | Should -Match 'COLLISIONS_DETECTED=false'
}
}
Context 'Collisions detected' {
It 'Reports COLLISIONS_DETECTED=true when target files exist' {
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path '.github/agents/task-researcher.agent.md' -Value '# Existing'
$output = & $script:scriptPath -Selection 'hve-core' 6>&1 | Out-String
$output | Should -Match 'COLLISIONS_DETECTED=true'
}
It 'Lists collision file paths' {
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path '.github/agents/task-researcher.agent.md' -Value '# Existing'
Set-Content -Path '.github/agents/task-planner.agent.md' -Value '# Existing'
$output = & $script:scriptPath -Selection 'hve-core' 6>&1 | Out-String
$output | Should -Match 'COLLISION_FILES='
$output | Should -Match 'task-researcher\.agent\.md'
$output | Should -Match 'task-planner\.agent\.md'
}
}
Context 'Collection selection' {
It 'Uses collectionAgents for non-hve-core selection' {
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path '.github/agents/custom-agent.agent.md' -Value '# Custom'
$output = & $script:scriptPath -Selection 'my-collection' -CollectionAgents @('my-collection/custom-agent.agent.md') 6>&1 | Out-String
$output | Should -Match 'COLLISIONS_DETECTED=true'
$output | Should -Match 'custom-agent\.agent\.md'
}
It 'Reports no collisions for empty collectionAgents' {
$output = & $script:scriptPath -Selection 'my-collection' -CollectionAgents @() 6>&1 | Out-String
$output | Should -Match 'COLLISIONS_DETECTED=false'
}
}
}
#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
Describe 'detect-environment' -Tag 'Unit' {
BeforeAll {
$script:scriptPath = Join-Path $PSScriptRoot '../scripts/detect-environment.ps1'
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "hve-test-detect-env-$([guid]::NewGuid().ToString('N'))"
}
BeforeEach {
# Clean and recreate temp directory for full isolation between tests
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Path $script:testRoot -Force | Out-Null
Push-Location $script:testRoot
# Save and clear environment variables
$script:savedCodespaces = $env:CODESPACES
$script:savedRemoteContainers = $env:REMOTE_CONTAINERS
$env:CODESPACES = $null
$env:REMOTE_CONTAINERS = $null
}
AfterEach {
Pop-Location
$env:CODESPACES = $script:savedCodespaces
$env:REMOTE_CONTAINERS = $script:savedRemoteContainers
}
AfterAll {
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
Context 'Local environment detection' {
It 'Detects local environment when no container markers are present' {
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'ENV_TYPE=local'
$output | Should -Match 'IS_CODESPACES=False'
$output | Should -Match 'IS_DEVCONTAINER=False'
}
}
Context 'Codespaces environment detection' {
It 'Detects Codespaces when CODESPACES env var is true' {
$env:CODESPACES = 'true'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'ENV_TYPE=codespaces'
$output | Should -Match 'IS_CODESPACES=True'
$output | Should -Match 'IS_DEVCONTAINER=True'
}
}
Context 'Devcontainer detection' {
It 'Detects devcontainer when REMOTE_CONTAINERS env var is true' {
$env:REMOTE_CONTAINERS = 'true'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'ENV_TYPE=devcontainer'
$output | Should -Match 'IS_DEVCONTAINER=True'
}
}
Context 'Devcontainer JSON detection' {
It 'Reports HAS_DEVCONTAINER_JSON=True when file exists' {
New-Item -ItemType Directory -Path '.devcontainer' -Force | Out-Null
Set-Content -Path '.devcontainer/devcontainer.json' -Value '{}'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'HAS_DEVCONTAINER_JSON=True'
}
It 'Reports HAS_DEVCONTAINER_JSON=False when file does not exist' {
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'HAS_DEVCONTAINER_JSON=False'
}
}
Context 'Workspace file detection' {
It 'Reports HAS_WORKSPACE_FILE=True when .code-workspace file exists' {
Set-Content -Path 'test.code-workspace' -Value '{}'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'HAS_WORKSPACE_FILE=True'
}
It 'Reports HAS_WORKSPACE_FILE=False when no workspace file exists' {
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'HAS_WORKSPACE_FILE=False'
}
}
Context 'HVE-Core repo detection' {
It 'Reports IS_HVE_CORE_REPO output' {
# In a temp directory outside a hve-core repo, this should be False
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'IS_HVE_CORE_REPO='
}
}
}
#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
Describe 'eject' -Tag 'Unit' {
BeforeAll {
$script:scriptPath = Join-Path $PSScriptRoot '../scripts/eject.ps1'
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "hve-test-eject-$([guid]::NewGuid().ToString('N'))"
}
BeforeEach {
New-Item -ItemType Directory -Path $script:testRoot -Force | Out-Null
Push-Location $script:testRoot
}
AfterEach {
Pop-Location
}
AfterAll {
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
Context 'Successful ejection' {
It 'Sets file status to ejected in manifest' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
files = @{
'.github/agents/task-implementor.agent.md' = @{
version = '1.0.0'
sha256 = 'abc123'
status = 'managed'
}
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
& $script:scriptPath -FilePath '.github/agents/task-implementor.agent.md'
$updated = Get-Content '.hve-tracking.json' | ConvertFrom-Json -AsHashtable
$updated.files['.github/agents/task-implementor.agent.md'].status | Should -Be 'ejected'
}
It 'Adds ejectedAt timestamp to the file entry' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
files = @{
'.github/agents/task-implementor.agent.md' = @{
version = '1.0.0'
sha256 = 'abc123'
status = 'managed'
}
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
& $script:scriptPath -FilePath '.github/agents/task-implementor.agent.md'
$updated = Get-Content '.hve-tracking.json' | ConvertFrom-Json -AsHashtable
$updated.files['.github/agents/task-implementor.agent.md'].ejectedAt | Should -Not -BeNullOrEmpty
}
It 'Preserves other files in the manifest' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
files = @{
'.github/agents/task-implementor.agent.md' = @{
version = '1.0.0'
sha256 = 'abc123'
status = 'managed'
}
'.github/agents/task-researcher.agent.md' = @{
version = '1.0.0'
sha256 = 'def456'
status = 'managed'
}
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
& $script:scriptPath -FilePath '.github/agents/task-implementor.agent.md'
$updated = Get-Content '.hve-tracking.json' | ConvertFrom-Json -AsHashtable
$updated.files['.github/agents/task-researcher.agent.md'].status | Should -Be 'managed'
}
}
Context 'File not found in manifest' {
It 'Outputs error message for untracked file' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
files = @{}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath -FilePath '.github/agents/nonexistent.md' 6>&1 | Out-String
$output | Should -Match 'not found in tracking manifest'
}
It 'Does not modify manifest when file is not tracked' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
files = @{
'.github/agents/task-implementor.agent.md' = @{
version = '1.0.0'
sha256 = 'abc123'
status = 'managed'
}
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
& $script:scriptPath -FilePath '.github/agents/nonexistent.md'
$updated = Get-Content '.hve-tracking.json' | ConvertFrom-Json -AsHashtable
$updated.files['.github/agents/task-implementor.agent.md'].status | Should -Be 'managed'
}
}
Context 'Parameter validation' {
It 'Has a mandatory FilePath parameter' {
$command = Get-Command $script:scriptPath
$param = $command.Parameters['FilePath']
$param | Should -Not -BeNullOrEmpty
$param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] -and $_.Mandatory } | Should -Not -BeNullOrEmpty
}
}
}
#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
Describe 'file-status-check' -Tag 'Unit' {
BeforeAll {
$script:scriptPath = Join-Path $PSScriptRoot '../scripts/file-status-check.ps1'
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "hve-test-file-status-$([guid]::NewGuid().ToString('N'))"
}
BeforeEach {
New-Item -ItemType Directory -Path $script:testRoot -Force | Out-Null
Push-Location $script:testRoot
}
AfterEach {
Pop-Location
}
AfterAll {
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
Context 'Managed files (unchanged)' {
It 'Reports managed status when file hash matches manifest' {
$filePath = '.github/agents/task-researcher.agent.md'
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path $filePath -Value '# Researcher' -NoNewline
$hash = (Get-FileHash -Path $filePath -Algorithm SHA256).Hash.ToLower()
$manifest = @{
files = @{
$filePath = @{ sha256 = $hash; status = 'managed' }
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match "FILE=$([regex]::Escape($filePath))\|STATUS=managed\|ACTION=Will update"
}
}
Context 'Modified files' {
It 'Reports modified status when file hash differs from manifest' {
$filePath = '.github/agents/task-researcher.agent.md'
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path $filePath -Value '# Modified Content' -NoNewline
$manifest = @{
files = @{
$filePath = @{ sha256 = 'old_hash_value'; status = 'managed' }
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match "FILE=$([regex]::Escape($filePath))\|STATUS=modified\|ACTION=Requires decision"
}
}
Context 'Missing files' {
It 'Reports missing status when tracked file does not exist on disk' {
$filePath = '.github/agents/missing-file.agent.md'
$manifest = @{
files = @{
$filePath = @{ sha256 = 'abc123'; status = 'managed' }
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match "FILE=$([regex]::Escape($filePath))\|STATUS=missing\|ACTION=Will restore"
}
}
Context 'Ejected files' {
It 'Reports ejected status and skip action for ejected files' {
$filePath = '.github/agents/custom.agent.md'
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
Set-Content -Path $filePath -Value '# Custom' -NoNewline
$manifest = @{
files = @{
$filePath = @{
sha256 = 'abc123'
status = 'ejected'
ejectedAt = '2025-01-01T00:00:00Z'
}
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match "FILE=$([regex]::Escape($filePath))\|STATUS=ejected\|ACTION=Skip"
}
It 'Does not check hash for ejected files' {
$filePath = '.github/agents/ejected.agent.md'
# File does not exist on disk, but ejected files should not be hash-checked
$manifest = @{
files = @{
$filePath = @{
sha256 = 'abc123'
status = 'ejected'
ejectedAt = '2025-01-01T00:00:00Z'
}
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath 6>&1 | Out-String
# Should report ejected, not missing
$output | Should -Match 'STATUS=ejected'
$output | Should -Not -Match 'STATUS=missing'
}
}
Context 'Multiple files' {
It 'Reports status for each tracked file independently' {
New-Item -ItemType Directory -Path '.github/agents' -Force | Out-Null
$managedPath = '.github/agents/managed.agent.md'
Set-Content -Path $managedPath -Value '# Managed' -NoNewline
$managedHash = (Get-FileHash -Path $managedPath -Algorithm SHA256).Hash.ToLower()
$modifiedPath = '.github/agents/modified.agent.md'
Set-Content -Path $modifiedPath -Value '# Modified' -NoNewline
$missingPath = '.github/agents/missing.agent.md'
$manifest = @{
files = @{
$managedPath = @{ sha256 = $managedHash; status = 'managed' }
$modifiedPath = @{ sha256 = 'wrong_hash'; status = 'managed' }
$missingPath = @{ sha256 = 'abc123'; status = 'managed' }
}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath 6>&1 | Out-String
$output | Should -Match 'STATUS=managed'
$output | Should -Match 'STATUS=modified'
$output | Should -Match 'STATUS=missing'
}
}
}
#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
Describe 'upgrade-detection' -Tag 'Unit' {
BeforeAll {
$script:scriptPath = Join-Path $PSScriptRoot '../scripts/upgrade-detection.ps1'
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "hve-test-upgrade-$([guid]::NewGuid().ToString('N'))"
$script:sourceRoot = Join-Path $script:testRoot 'source'
}
BeforeEach {
New-Item -ItemType Directory -Path $script:testRoot -Force | Out-Null
New-Item -ItemType Directory -Path $script:sourceRoot -Force | Out-Null
Push-Location $script:testRoot
}
AfterEach {
Pop-Location
}
AfterAll {
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
Context 'Fresh installation (no manifest)' {
It 'Reports UPGRADE_MODE=false when no manifest exists' {
$output = & $script:scriptPath -HveCoreBasePath $script:sourceRoot 6>&1 | Out-String
$output | Should -Match 'UPGRADE_MODE=false'
}
}
Context 'Existing installation' {
BeforeEach {
@{ version = '2.0.0' } | ConvertTo-Json | Set-Content (Join-Path $script:sourceRoot 'package.json')
}
It 'Reports UPGRADE_MODE=true when manifest exists' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
collection = 'hve-core'
files = @{}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath -HveCoreBasePath $script:sourceRoot 6>&1 | Out-String
$output | Should -Match 'UPGRADE_MODE=true'
}
It 'Reports installed and source versions' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
collection = 'hve-core'
files = @{}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath -HveCoreBasePath $script:sourceRoot 6>&1 | Out-String
$output | Should -Match 'INSTALLED_VERSION=1\.0\.0'
$output | Should -Match 'SOURCE_VERSION=2\.0\.0'
}
It 'Reports VERSION_CHANGED=True when versions differ' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
collection = 'hve-core'
files = @{}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath -HveCoreBasePath $script:sourceRoot 6>&1 | Out-String
$output | Should -Match 'VERSION_CHANGED=True'
}
It 'Reports VERSION_CHANGED=False when versions match' {
$manifest = @{
source = 'microsoft/hve-core'
version = '2.0.0'
collection = 'hve-core'
files = @{}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath -HveCoreBasePath $script:sourceRoot 6>&1 | Out-String
$output | Should -Match 'VERSION_CHANGED=False'
}
It 'Reports INSTALLED_COLLECTION from manifest' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
collection = 'developer'
files = @{}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath -HveCoreBasePath $script:sourceRoot 6>&1 | Out-String
$output | Should -Match 'INSTALLED_COLLECTION=developer'
}
It 'Defaults INSTALLED_COLLECTION to hve-core when not set in manifest' {
$manifest = @{
source = 'microsoft/hve-core'
version = '1.0.0'
files = @{}
}
$manifest | ConvertTo-Json -Depth 10 | Set-Content '.hve-tracking.json'
$output = & $script:scriptPath -HveCoreBasePath $script:sourceRoot 6>&1 | Out-String
$output | Should -Match 'INSTALLED_COLLECTION=hve-core'
}
}
}
#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
Describe 'validate-extension' -Tag 'Unit' {
BeforeAll {
$script:scriptPath = Join-Path $PSScriptRoot '../scripts/validate-extension.ps1'
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "hve-test-valext-$([guid]::NewGuid().ToString('N'))"
New-Item -ItemType Directory -Path $script:testRoot -Force | Out-Null
}
AfterAll {
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
Context 'Extension installed' {
It 'Reports EXTENSION_INSTALLED=True when extension is listed' {
# Create a mock script that simulates the code CLI
$mockPath = Join-Path $script:testRoot 'mock-code-found.ps1'
@'
if ($args -contains '--show-versions') {
'ms-vscode.powershell@2024.1.0'
'ise-hve-essentials.hve-core@3.0.2'
'github.copilot@1.0.0'
} else {
'ms-vscode.powershell'
'ise-hve-essentials.hve-core'
'github.copilot'
}
'@ | Set-Content $mockPath
$output = & $script:scriptPath -CodeCli $mockPath 6>&1 | Out-String
$output | Should -Match 'EXTENSION_INSTALLED=True'
}
It 'Reports extension version when available' {
$mockPath = Join-Path $script:testRoot 'mock-code-version.ps1'
@'
if ($args -contains '--show-versions') {
'ise-hve-essentials.hve-core@3.0.2'
} else {
'ise-hve-essentials.hve-core'
}
'@ | Set-Content $mockPath
$output = & $script:scriptPath -CodeCli $mockPath 6>&1 | Out-String
$output | Should -Match '3\.0\.2'
}
}
Context 'Extension not installed' {
It 'Reports EXTENSION_INSTALLED=False when extension is not listed' {
$mockPath = Join-Path $script:testRoot 'mock-code-notfound.ps1'
@'
'ms-vscode.powershell'
'github.copilot'
'@ | Set-Content $mockPath
$output = & $script:scriptPath -CodeCli $mockPath 6>&1 | Out-String
$output | Should -Match 'EXTENSION_INSTALLED=False'
}
}
Context 'Default codeCli' {
It 'Has a default value for CodeCli parameter' {
$param = (Get-Command $script:scriptPath).Parameters['CodeCli']
$param | Should -Not -BeNullOrEmpty
$param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } | Should -Not -BeNullOrEmpty
}
}
}
#Requires -Modules Pester
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
Describe 'validate-installation' -Tag 'Unit' {
BeforeAll {
$script:scriptPath = Join-Path $PSScriptRoot '../scripts/validate-installation.ps1'
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "hve-test-validate-$([guid]::NewGuid().ToString('N'))"
}
BeforeEach {
New-Item -ItemType Directory -Path $script:testRoot -Force | Out-Null
Push-Location $script:testRoot
}
AfterEach {
Pop-Location
}
AfterAll {
if (Test-Path $script:testRoot) {
Remove-Item $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
Context 'Parameter validation' {
It 'Has a mandatory BasePath parameter' {
$param = (Get-Command $script:scriptPath).Parameters['BasePath']
$param | Should -Not -BeNullOrEmpty
$attr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
$attr.Mandatory | Should -BeTrue
}
It 'Has a mandatory Method parameter' {
$param = (Get-Command $script:scriptPath).Parameters['Method']
$param | Should -Not -BeNullOrEmpty
$attr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
$attr.Mandatory | Should -BeTrue
}
}
Context 'Directory validation' {
It 'Passes when all required directories exist' {
$installDir = Join-Path $script:testRoot 'install'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
$output = & $script:scriptPath -BasePath $installDir -Method 1 6>&1 | Out-String
$output | Should -Match 'Installation validated successfully'
}
It 'Reports missing directories' {
$installDir = Join-Path $script:testRoot 'empty-install'
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
$output = & $script:scriptPath -BasePath $installDir -Method 1 6>&1 | Out-String
$output | Should -Match 'Missing'
}
It 'Checks agents directory' {
$installDir = Join-Path $script:testRoot 'partial'
New-Item -ItemType Directory -Path (Join-Path $installDir '.github/prompts') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $installDir '.github/instructions') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $installDir '.github/skills') -Force | Out-Null
$output = & $script:scriptPath -BasePath $installDir -Method 1 6>&1 | Out-String
$output | Should -Match '(?s)Missing.*agents'
}
It 'Checks skills directory' {
$installDir = Join-Path $script:testRoot 'no-skills'
New-Item -ItemType Directory -Path (Join-Path $installDir '.github/agents') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $installDir '.github/prompts') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $installDir '.github/instructions') -Force | Out-Null
$output = & $script:scriptPath -BasePath $installDir -Method 1 6>&1 | Out-String
$output | Should -Match '(?s)Missing.*skills'
}
}
Context 'Experimental subdirectories' {
It 'Passes validation when experimental directories are absent' {
$installDir = Join-Path $script:testRoot 'no-experimental'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
$output = & $script:scriptPath -BasePath $installDir -Method 1 6>&1 | Out-String
$LASTEXITCODE | Should -Not -Be 1
$output | Should -Match 'Installation validated successfully'
$output | Should -Not -Match 'Missing.*experimental'
}
It 'Reports optional experimental directories when present' {
$installDir = Join-Path $script:testRoot 'with-experimental'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills',
'.github/skills/experimental', '.github/agents/experimental')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
$output = & $script:scriptPath -BasePath $installDir -Method 1 6>&1 | Out-String
$output | Should -Match '(?s)Found optional.*skills/experimental'
$output | Should -Match '(?s)Found optional.*agents/experimental'
$output | Should -Match 'Installation validated successfully'
}
}
Context 'Method 5: Multi-root workspace validation' {
It 'Validates multi-root workspace configuration' {
$installDir = Join-Path $script:testRoot 'method5'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
$workspace = @{
folders = @(
@{ path = '.' }
@{ path = 'lib/hve-core' }
)
}
$workspace | ConvertTo-Json -Depth 5 | Set-Content 'hve-core.code-workspace'
$output = & $script:scriptPath -BasePath $installDir -Method 5 6>&1 | Out-String
$output | Should -Match 'Multi-root configured'
}
It 'Fails multi-root check when workspace has fewer than 2 folders' {
$installDir = Join-Path $script:testRoot 'method5-bad'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
$workspace = @{
folders = @(
@{ path = '.' }
)
}
$workspace | ConvertTo-Json -Depth 5 | Set-Content 'hve-core.code-workspace'
$output = & $script:scriptPath -BasePath $installDir -Method 5 6>&1 | Out-String
$output | Should -Match 'Multi-root not configured'
}
}
Context 'Method 6: Submodule validation' {
It 'Validates submodule entry in .gitmodules' {
$installDir = Join-Path $script:testRoot 'method6'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
@'
[submodule "lib/hve-core"]
path = lib/hve-core
url = https://github.com/microsoft/hve-core.git
'@ | Set-Content '.gitmodules'
$output = & $script:scriptPath -BasePath $installDir -Method 6 6>&1 | Out-String
$output | Should -Not -Match 'Submodule not in .gitmodules'
}
It 'Fails when .gitmodules missing submodule entry' {
$installDir = Join-Path $script:testRoot 'method6-bad'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
Set-Content '.gitmodules' -Value '[submodule "other"]'
$output = & $script:scriptPath -BasePath $installDir -Method 6 6>&1 | Out-String
$output | Should -Match 'Submodule not in .gitmodules'
}
It 'Fails when .gitmodules does not exist' {
$installDir = Join-Path $script:testRoot 'method6-nomod'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
# Ensure .gitmodules does not exist
Remove-Item '.gitmodules' -ErrorAction SilentlyContinue
$output = & $script:scriptPath -BasePath $installDir -Method 6 6>&1 | Out-String
$output | Should -Match 'Submodule not in .gitmodules'
}
}
Context 'Standard methods (1-4)' {
It 'Passes validation for method 1 with all directories' {
$installDir = Join-Path $script:testRoot 'method1'
foreach ($dir in @('.github/agents', '.github/prompts', '.github/instructions', '.github/skills')) {
New-Item -ItemType Directory -Path (Join-Path $installDir $dir) -Force | Out-Null
}
$output = & $script:scriptPath -BasePath $installDir -Method 1 6>&1 | Out-String
$output | Should -Match 'Installation validated successfully'
}
}
}
Related skills
FAQ
What are the two HVE-Core install paths?
Quick Install via the VS Code Marketplace extension or clone-based installation for full customization.
Is it safe to re-run hve-core-installer?
Yes, re-running validates an existing installation or offers upgrade.
Is hve-core-installer safe to install?
Review the Security Audits panel on this page before installing in production.