Skip to content

fix(scripts): use text stubs for plugin links when symlinks unavailable#695

Merged
katriendg merged 4 commits intomainfrom
fix/windows-symlink-crossplatform
Feb 20, 2026
Merged

fix(scripts): use text stubs for plugin links when symlinks unavailable#695
katriendg merged 4 commits intomainfrom
fix/windows-symlink-crossplatform

Conversation

@WilliamBerryiii
Copy link
Copy Markdown
Member

@WilliamBerryiii WilliamBerryiii commented Feb 20, 2026

fix(scripts): use text stubs for plugin links when symlinks unavailable

Description

When core.symlinks is false (Windows without Developer Mode or elevated privileges), New-Item -ItemType SymbolicLink fails and plugin generation breaks. This PR replaces forced symlink creation with a dual-path strategy: probe symlink capability once at generation start, then either create real symlinks or write git-compatible text stubs containing the relative path. Text stubs match the 120000-mode format git produces when it checks out symlinks as plain files, keeping git status clean.

Also normalizes path separators in LintingHelpers where git rev-parse --show-toplevel returns forward slashes but Resolve-Path returns backslashes on Windows.

Batch Symlink Index Repair

When text stubs are written on a core.symlinks=false system, git records them as 100644 (regular file) in the index even though they represent symlinks. Repair-PluginSymlinkIndex corrects all plugins/ entries that should be 120000 mode, using a two-bucket approach:

  • Tracked files (already in the index) are batched via git update-index --index-info for efficiency
  • Untracked files (newly generated, not yet in the index) use individual git update-index --add --cacheinfo calls, since --index-info silently ignores paths not yet tracked via PowerShell pipes

This runs automatically at the end of Generate-Plugins.ps1 when symlinks are unavailable.

Related Issue(s)

Fixes #691

Type of Change

Select all that apply:

Code & Documentation:

  • Bug fix (non-breaking change fixing an issue)
  • New feature (non-breaking change adding functionality)
  • Breaking change (fix or feature causing existing functionality to change)
  • Documentation update

Infrastructure & Configuration:

  • GitHub Actions workflow
  • Linting configuration (markdown, PowerShell, etc.)
  • Security configuration
  • DevContainer configuration
  • Dependency update

AI Artifacts:

  • Reviewed contribution with prompt-builder agent and addressed all feedback
  • Copilot instructions (.github/instructions/*.instructions.md)
  • Copilot prompt (.github/prompts/*.prompt.md)
  • Copilot agent (.github/agents/*.agent.md)
  • Copilot skill (.github/skills/*/SKILL.md)

Other:

  • Script/automation (.ps1, .sh, .py)
  • Other (please describe):

Testing

  • All 1394 Pester tests pass (npm run test:ps)
  • All 247 text stubs in plugins/ are byte-identical to git's 120000-mode symlink stubs
  • Verified npm run plugin:generate produces zero git diff changes in plugins/
  • Verified incremental generation: modified a source file, regenerated, confirmed text stubs unaffected and plugins/ remains clean
  • WSL cross-platform verification: 274/274 plugin entries at 120000 mode materialized as real working OS-level symlinks on WSL — zero broken, zero text stubs remaining
  • Verified Repair-PluginSymlinkIndex correctly handles both tracked and untracked files in the git index

Checklist

Required Checks

  • Documentation is updated (if applicable)
  • Files follow existing naming conventions
  • Changes are backwards compatible (if applicable)
  • Tests added for new functionality (if applicable)

Required Automated Checks

The following validation commands must pass before merging:

  • Markdown linting: npm run lint:md
  • Spell checking: npm run spell-check
  • Frontmatter validation: npm run lint:frontmatter
  • Skill structure validation: npm run validate:skills
  • Link validation: npm run lint:md-links
  • PowerShell analysis: npm run lint:ps

Security Considerations

  • This PR does not contain any sensitive or NDA information
  • Any new dependencies have been reviewed for security issues
  • Security-related scripts follow the principle of least privilege

No new dependencies introduced. Test-SymlinkCapability creates a temporary probe directory scoped to the current PID and cleans up unconditionally via finally. Text stubs are written with [System.IO.File]::WriteAllText() using only relative paths derived from the repository tree.

Additional Notes

  • New-RelativeSymlink is renamed to New-PluginLink to reflect the dual-path behavior
  • Test-SymlinkCapability probes once per generation run to avoid repeated filesystem operations
  • Repair-PluginSymlinkIndex batches git index corrections to avoid index.lock contention from rapid sequential --cacheinfo calls
  • Uses a two-bucket strategy: tracked files via --index-info stdin pipe, new files via individual --cacheinfo (PowerShell pipe encoding prevents --index-info from accepting untracked paths)
  • On symlink-capable systems (Linux, macOS, Windows with Developer Mode), behavior is unchanged — real symlinks are created and no index repair is needed

🔧 - Generated by Copilot

- add Test-SymlinkCapability probe-once detection in plugin generation
- replace New-RelativeSymlink with New-PluginLink supporting dual paths
- write git-compatible text stubs as fallback instead of copying files
- normalize path separators in LintingHelpers for cross-platform git paths
- add Pester tests for capability detection and text stub behavior

🔧 Fixes #691 - Generated by Copilot
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 20, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Feb 20, 2026

Codecov Report

❌ Patch coverage is 30.98592% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.61%. Comparing base (cfdcf11) to head (986af90).

Files with missing lines Patch % Lines
scripts/plugins/Modules/PluginHelpers.psm1 25.80% 46 Missing ⚠️
scripts/plugins/Generate-Plugins.ps1 50.00% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #695      +/-   ##
==========================================
- Coverage   85.43%   84.61%   -0.82%     
==========================================
  Files          23       23              
  Lines        4674     4737      +63     
==========================================
+ Hits         3993     4008      +15     
- Misses        681      729      +48     
Flag Coverage Δ
pester 84.61% <30.98%> (-0.82%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
scripts/linting/Modules/LintingHelpers.psm1 100.00% <100.00%> (ø)
scripts/plugins/Generate-Plugins.ps1 76.04% <50.00%> (-1.74%) ⬇️
scripts/plugins/Modules/PluginHelpers.psm1 77.19% <25.80%> (-11.27%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- add Repair-PluginSymlinkIndex to fix 100644 -> 120000 for text stubs
- use batched git update-index --index-info to avoid index.lock contention
- integrate fixup into Generate-Plugins.ps1 for non-symlink systems

🔧 - Generated by Copilot
- split entries into tracked (batch --index-info) and new (--cacheinfo)
- --index-info silently ignores untracked paths from PowerShell pipes
- add WSL symlink test instruction for cross-platform verification

🔧 - Generated by Copilot
- delete wsl-symlink-test.instructions.md after successful verification
- restore collection manifests and plugin READMEs to main state

🧹 - Generated by Copilot
@bindsi
Copy link
Copy Markdown
Member

bindsi commented Feb 20, 2026

@WilliamBerryiii @katriendg I have tested the fix on MacOs and there are no regression and works like expected

@peterbryntesson
Copy link
Copy Markdown
Contributor

@WilliamBerryiii @katriendg I tested this branch on Windows in a dev container and I saw no regression. It works as expected

@katriendg
Copy link
Copy Markdown
Contributor

Thanks @bindsi and @peterbryntesson for the testing on your environments! I'm moving this into published PR for further review.

@katriendg katriendg marked this pull request as ready for review February 20, 2026 11:11
@katriendg katriendg requested a review from a team as a code owner February 20, 2026 11:11
Copy link
Copy Markdown
Contributor

@katriendg katriendg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also tested within WSL/devcontainer and working correctly as expected. Because this issue is affecting other users creating their PRs we will go ahead and merge in!

@katriendg katriendg merged commit d7650a3 into main Feb 20, 2026
19 checks passed
WilliamBerryiii added a commit that referenced this pull request Feb 20, 2026
…est (#701)

## Description

Stabilized the YAML display key ordering in
`Update-HveCoreAllCollection` by rebuilding the `display` section as an
ordered hashtable before serialization. `ConvertTo-Yaml` serialized
regular hashtables with non-deterministic key iteration order, causing
`featured` and `ordering` keys to appear in inconsistent sequences
across runs. The fix explicitly inserts `featured` before `ordering`
using `[ordered]@{}`, producing stable output. Added five Pester tests
covering display key ordering behavior.

- fix(scripts): rebuilt `display` section as `[ordered]@{}` with
explicit `featured`-then-`ordering` key insertion before
`ConvertTo-Yaml` serialization
- test(build): added five Pester tests for `Update-HveCoreAllCollection`
display key ordering — covers both-keys order, only-ordering,
only-featured, return hashtable shape, and DryRun mode

## Related Issue(s)

Fixes #702

## Type of Change

Select all that apply:

**Code & Documentation:**

- [x] Bug fix (non-breaking change fixing an issue)
- [ ] New feature (non-breaking change adding functionality)
- [ ] Breaking change (fix or feature causing existing functionality to
change)
- [ ] Documentation update

**Infrastructure & Configuration:**

- [ ] GitHub Actions workflow
- [ ] Linting configuration (markdown, PowerShell, etc.)
- [ ] Security configuration
- [ ] DevContainer configuration
- [ ] Dependency update

**AI Artifacts:**

- [ ] Reviewed contribution with `prompt-builder` agent and addressed
all feedback
- [ ] Copilot instructions (`.github/instructions/*.instructions.md`)
- [ ] Copilot prompt (`.github/prompts/*.prompt.md`)
- [ ] Copilot agent (`.github/agents/*.agent.md`)
- [ ] Copilot skill (`.github/skills/*/SKILL.md`)

> **Note for AI Artifact Contributors**:
>
> - **Agents**: Research, indexing/referencing other project (using
standard VS Code GitHub Copilot/MCP tools), planning, and general
implementation agents likely already exist. Review `.github/agents/`
before creating new ones.
> - **Skills**: Must include both bash and PowerShell scripts. See
[Skills](../docs/contributing/skills.md).
> - **Model Versions**: Only contributions targeting the **latest
Anthropic and OpenAI models** will be accepted. Older model versions
(e.g., GPT-3.5, Claude 3) will be rejected.
> - See [Agents Not
Accepted](../docs/contributing/custom-agents.md#agents-not-accepted) and
[Model Version
Requirements](../docs/contributing/ai-artifacts-common.md#model-version-requirements).

**Other:**

- [x] Script/automation (`.ps1`, `.sh`, `.py`)
- [ ] Other (please describe):

## Testing

Five new Pester tests added in
`scripts/tests/plugins/PluginHelpers.Tests.ps1` under the
`Update-HveCoreAllCollection - display key ordering` describe block:

1. **Preserves featured-then-ordering key order** — writes a manifest
with `ordering` before `featured` (reversed), runs the function, and
asserts `featured:` appears before `ordering:` in the output
2. **Handles display with only ordering key** — verifies output contains
`ordering: alpha` and does not contain `featured:`
3. **Handles display with only featured key** — verifies output contains
`featured:` and does not contain `ordering:`
4. **Returns expected result hashtable** — validates `ItemCount`,
`AddedCount`, `RemovedCount`, and `DeprecatedCount` keys in the return
value
5. **Does not write to disk in DryRun mode** — confirms the file remains
unmodified when `-DryRun` is passed

All 64 tests pass (59 existing + 5 new).

## Checklist

### Required Checks

- [ ] Documentation is updated (if applicable)
- [x] Files follow existing naming conventions
- [x] Changes are backwards compatible (if applicable)
- [x] Tests added for new functionality (if applicable)

### AI Artifact Contributions

<!-- Not applicable — no AI artifacts changed -->

### Required Automated Checks

The following validation commands must pass before merging:

- [ ] Markdown linting: `npm run lint:md`
- [ ] Spell checking: `npm run spell-check`
- [ ] Frontmatter validation: `npm run lint:frontmatter`
- [ ] Skill structure validation: `npm run validate:skills`
- [ ] Link validation: `npm run lint:md-links`
- [ ] PowerShell analysis: `npm run lint:ps`

## Security Considerations

- [x] This PR does not contain any sensitive or NDA information
- [ ] Any new dependencies have been reviewed for security issues
- [x] Security-related scripts follow the principle of least privilege

## Additional Notes

This fix addresses a YAML serialization inconsistency that was committed
after PR #695 was squash-merged into main. The commit was cherry-picked
from `f52e255` onto a dedicated branch from `origin/main`.

🔧 - Generated by Copilot
WilliamBerryiii pushed a commit that referenced this pull request Feb 20, 2026
🤖 I have created a release *beep* *boop*
---


##
[3.0.0](hve-core-v2.3.10...hve-core-v3.0.0)
(2026-02-20)


### ⚠ BREAKING CHANGES

* **skills:** migrate PR reference generation to self-contained skill
([#669](#669))
* restructure RPI collection to HVE Core naming convention
([#668](#668))

### ✨ Features

* **agents:** add agile-coach agent
([#562](#562))
([de8d86c](de8d86c))
* **agents:** add DT coach agent with tiered instruction loading
([#656](#656))
([206d3a7](206d3a7))
* **agents:** add product manager advisor and UX/UI designer agents
([#627](#627))
([539eb8a](539eb8a))
* **agents:** add system architecture reviewer for design trade-offs and
ADR creation ([#626](#626))
([de5cfd6](de5cfd6))
* **build:** pin devcontainer image and align tool parity
([#704](#704))
([6258b1c](6258b1c))
* **design-thinking:** add manufacturing industry context template
([#682](#682))
([ce864bf](ce864bf))
* **instructions:** add DT coaching state protocol for session
persistence ([#654](#654))
([5a5be4e](5a5be4e))
* **instructions:** add dt-coaching-identity ambient instruction
([#642](#642))
([6209a0d](6209a0d))
* **instructions:** add dt-method-01-deep for advanced scope
conversation techniques
([#673](#673))
([cc92ef9](cc92ef9))
* **instructions:** add dt-method-03-deep for advanced input synthesis
techniques ([#676](#676))
([0079a4f](0079a4f))
* **instructions:** add dt-method-09-deep instructions for Method 9
advanced coaching
([#703](#703))
([150b2a6](150b2a6))
* **instructions:** add dt-method-sequencing ambient instruction
([#650](#650))
([e465b2f](e465b2f))
* **instructions:** add dt-quality-constraints and design-thinking
collection ([#645](#645))
([17002bd](17002bd))
* **instructions:** add DT-to-RPI handoff contract specification
([#679](#679))
([87f9962](87f9962))
* **instructions:** add energy industry context template
([#687](#687))
([41088d8](41088d8))
* **instructions:** add healthcare industry context template
([#686](#686))
([b2d5281](b2d5281))
* **instructions:** add Method 1 Scope Conversations coaching knowledge
([#651](#651))
([93e2d48](93e2d48))
* **instructions:** add Method 2 Design Research coaching knowledge
([#652](#652))
([30f7f3b](30f7f3b))
* **instructions:** add Method 3 Input Synthesis coaching knowledge
([#653](#653))
([1efdb7d](1efdb7d))
* **instructions:** add Method 7 High-Fidelity Prototypes coaching
instruction ([#666](#666))
([9233eab](9233eab))
* **instructions:** add pull request instructions for PR generation
workflow ([#706](#706))
([73d23eb](73d23eb))
* **instructions:** create DT curriculum content (9 modules)
([#690](#690))
([9f7378f](9f7378f)),
closes [#617](#617)
* **instructions:** create dt-method-02-deep.instructions.md
([#700](#700))
([4d4d0ca](4d4d0ca))
* **instructions:** create dt-method-06-lofi-prototypes.instructions.md
([#684](#684))
([4d5f757](4d5f757))
* **instructions:** create dt-method-07-deep.instructions.md
([#678](#678))
([d3ec70d](d3ec70d))
* **instructions:** Create dt-method-08-deep.instructions.md
([#683](#683))
([d9e1115](d9e1115))
* **instructions:** create dt-method-08-testing.instructions.md
([#681](#681))
([3008ad8](3008ad8))
* **instructions:** create dt-method-09-iteration.instructions.md
([#685](#685))
([9d7f4f5](9d7f4f5))
* **instructions:** create dt-rpi-research-context.instructions.md
([#689](#689))
([34c7b89](34c7b89))
* **instructions:** create manufacturing reference learning scenario
([#692](#692))
([1bd3994](1bd3994))
* **instructions:** Design Thinking Method 4 brainstorming instruction
file ([#664](#664))
([06f90b0](06f90b0))
* **prompts:** add DT start-project prompt for coaching initialization
([#657](#657))
([ce583d5](ce583d5))
* **prompts:** add dt-resume-coaching prompt for session recovery
([#665](#665))
([11b93cb](11b93cb))
* **prompts:** create dt-handoff-problem-space.prompt.md
([#688](#688))
([277963d](277963d))
* **scripts:** add collection-level maturity field with validation,
gating, and notices
([#697](#697))
([7b1c8e8](7b1c8e8))
* **scripts:** add per-violation CI annotations and colorized console
output ([#637](#637))
([bd7d512](bd7d512))
* **skills:** edit SKILL frontmatter schema, add CI validation, and
documentation ([#625](#625))
([0138a78](0138a78))
* **skills:** mandate unit testing and document language support
([#636](#636))
([9263617](9263617))
* **skills:** migrate PR reference generation to self-contained skill
([#669](#669))
([cf8805f](cf8805f))


### 🐛 Bug Fixes

* **collections:** migrate artifacts into collection-based
subdirectories
([#658](#658))
([dfa5261](dfa5261))
* **instructions:** optimize Phase 1 DT token budgets and close
[#564](https://github.com/microsoft/hve-core/issues/564)/[#565](https://github.com/microsoft/hve-core/issues/565)
gaps ([#675](#675))
([4f42f00](4f42f00))
* **scripts:** add CI annotations and step summary to copyright header
check ([#638](#638))
([5fa6328](5fa6328))
* **scripts:** add grouped link-lang console diagnostics and failure
summary ([#661](#661))
([4d6871f](4d6871f))
* **scripts:** add per-violation Write-Host and Write-CIAnnotation
output to Test-DependencyPinning
([#640](#640))
([9d3b71d](9d3b71d))
* **scripts:** align agent frontmatter schema with VS Code spec
([#469](#469))
([254d445](254d445))
* **scripts:** optimize PSScriptAnalyzer linting performance in WSL2
([#667](#667))
([f120b93](f120b93))
* **scripts:** stabilize YAML display key ordering in collection
manifest ([#701](#701))
([73c0d2c](73c0d2c))
* **scripts:** use text stubs for plugin links when symlinks unavailable
([#695](#695))
([d7650a3](d7650a3))
* **skills:** fix powershell test coverage in pr-reference skill
([#699](#699))
([408e6b7](408e6b7))


### 📚 Documentation

* **dt:** add Method 5 Concepts and Method 6 Lo-Fi Prototypes
instructions ([#693](#693))
([cfdcf11](cfdcf11))
* **hve-guide:** add role-based guides and project lifecycle
documentation ([#663](#663))
([17a85da](17a85da))


### ♻️ Refactoring

* restructure RPI collection to HVE Core naming convention
([#668](#668))
([120dde0](120dde0))
* **scripts:** consolidate duplicate logging into shared SecurityHelpers
module ([#655](#655))
([627a877](627a877))
* **scripts:** use shared SecurityHelpers and CIHelpers modules in
security scripts
([#705](#705))
([3a0baa7](3a0baa7))


### 🔧 Maintenance

* **deps-dev:** bump markdownlint-cli2 from 0.20.0 to 0.21.0 in the
npm-dependencies group
([#609](#609))
([1486dd7](1486dd7))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: hve-core-release-please[bot] <254602402+hve-core-release-please[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: plugin generation fails on Windows without symlink capability

5 participants