.kilocode/skills/skill_implementation/SKILL.md
# SKILL: DSP IMPLEMENTATION **Goal:** Implement audio processing where parameters control DSP **Focus:** PluginProcessor.h, PluginProcessor.cpp **Output Location:** `plugins/[Name]/Source/` --- ## 📊 PHASE 4: CODE (DSP Implementation) **Trigger:** `/impl [Name]` (after DESIGN phase complete) **Input:** Reads `plugins/[Name]/status.json` and `.ideas/parameter-spec.md` **Prerequisites:** Architecture plan complete, UI framework selected **State Validation:** ```powershell # Import state manage
npx skillsauth add noizefield/audio-plugin-coder .kilocode/skills/skill_implementationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Goal: Implement audio processing where parameters control DSP
Focus: PluginProcessor.h, PluginProcessor.cpp
Output Location: plugins/[Name]/Source/
Trigger: /impl [Name] (after DESIGN phase complete)
Input: Reads plugins/[Name]/status.json and .ideas/parameter-spec.md
Prerequisites: Architecture plan complete, UI framework selected
State Validation:
# Import state management module
. "$PSScriptRoot\..\scripts\state-management.ps1"
# Validate prerequisites
if (-not (Test-PluginState -PluginPath "plugins\[Name]" -RequiredPhase "design_complete" -RequiredFiles @(".ideas/architecture.md", ".ideas/plan.md"))) {
Write-Error "Prerequisites not met. Complete design phase first."
exit 1
}
# Check framework selection
$state = Get-PluginState -PluginPath "plugins\[Name]"
if ($state.ui_framework -eq "pending") {
Write-Error "UI framework not selected. Cannot proceed with implementation."
exit 1
}
Write-Host "Framework: $($state.ui_framework)" -ForegroundColor Cyan
IMPORTANT: This phase converts the approved design specifications into framework-specific code. User must approve the conversion before DSP implementation begins.
Framework Routing:
ui_framework == webview: use templates from templates/webview/ui_framework == visage: use templates from templates/visage/ and do not generate HTMLDesign/v[N]-ui-spec.md (latest approved version)Design/v[N]-style-guide.md (latest approved version).ideas/parameter-spec.md for parameter definitionsFor WebView Framework: Convert the approved design specs into production JUCE WebView code.
Create the required directory structure:
plugins/[Name]/Source/ui/
└───public/
│ index.html # Production UI based on approved design
│
└───js/
│ index.js # JUCE integration and parameter binding
│
└───juce/
check_native_interop.js # Development utility
index.js # JUCE frontend library
Implementation Steps:
plugins/[Name]/Source/ui/public/ and subdirectoriesmodules/juce_gui_extra/native/javascript/index.js to js/juce/index.jsjs/juce/check_native_interop.js for developmentindex.html with embedded CSSjs/index.js with parameter state setup and UI controlsConversion Process:
v[N]-style-guide.mdv[N]-ui-spec.mdExample Output (based on approved design):
ui/public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[Name] Plugin</title>
<script type="module" src="js/index.js"></script>
<style>
/* Styles based on approved v[N]-style-guide.md */
body {
background: [approved-background-color];
color: [approved-text-color];
font-family: [approved-font-family];
margin: 0;
padding: 20px;
}
/* Additional styles from approved design */
</style>
</head>
<body>
<!-- Layout based on approved v[N]-ui-spec.md -->
<div id="plugin-ui">
<!-- Controls generated from parameter-spec.md -->
</div>
</body>
</html>
ui/public/js/index.js:
import * as Juce from "./juce/index.js";
// Initialize parameter states from parameter-spec.md
const parameterStates = {};
document.addEventListener("DOMContentLoaded", () => {
// Create UI controls based on approved design
initializeUI();
console.log("WebView UI initialized from approved design");
});
function initializeUI() {
// Generate controls based on v[N]-ui-spec.md specifications
// Bind to JUCE parameter states
}
USER APPROVAL REQUIRED - CRITICAL STOP POINT:
✅ Design converted to WebView code
Files created:
- plugins/[Name]/Source/ui/public/index.html
- plugins/[Name]/Source/ui/public/js/index.js
- plugins/[Name]/Source/ui/public/js/juce/index.js
⚠️ **MANDATORY STOP** - You MUST test the WebView setup before proceeding to DSP implementation!
What would you like to do?
1. Test WebView - Open plugins/[Name]/Source/ui/public/index.html in browser and verify appearance
2. Approve - Proceed with DSP implementation (confirms WebView GUI is acceptable)
3. Revise - Make changes to the conversion
**YOU MUST CHOOSE OPTION 1 OR 2 BEFORE CONTINUING**
**DO NOT PROCEED TO PHASE 4.1 WITHOUT USER APPROVAL**
Choose (1-3): _
Approval Validation:
For Visage Framework:
Convert approved design to Visage C++ code (Source/VisageControls.h).
Use templates from templates/visage/ and the shared host in common/VisageJuceHost.h.
Before proceeding to DSP implementation, validate Visage setup:
.\scripts\validate-visage-setup.ps1 -PluginName [Name]
visage::visage in target_link_librariesSource/VisageControls.h presentPluginEditor.h includes VisageJuceHost.hVisagePluginEditorNEEDS_WEBVIEW2 TRUE and JUCE_WEB_BROWSER=1 not presentCRITICAL: When implementing WebView plugins, you MUST verify ALL 8 points below. Use templates from templates/webview/ and run validation script.
Before proceeding to Phase 4.1, validate WebView setup:
# Run validation script
.\scripts\validate-webview-setup.ps1 -PluginName [Name]
✅ CMakeLists.txt embeds web files
juce_add_binary_data([Name]_WebUI ...)target_link_libraries([Name] PRIVATE [Name]_WebUI ...)NEEDS_WEBVIEW2 TRUE in juce_add_plugin()JUCE_WEB_BROWSER=1 and JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING=1✅ WebBrowserComponent uses WebView2 backend
.withBackend(WebBrowserComponent::Options::Backend::webview2) is present✅ WebBrowserComponent has user data folder
.withUserDataFolder(File::getSpecialLocation(File::SpecialLocationType::tempDirectory)) is present✅ Native integration enabled
.withNativeIntegrationEnabled() is present✅ Resource provider implemented
.withResourceProvider([this](const auto& url) { return getResource(url); }) is presentgetResource() function exists and loads from embedded zipgetZipFile() helper function exists✅ Parameter relays created BEFORE WebBrowserComponent
std::make_unique<WebBrowserComponent>().withOptionsFrom(*relay) for each parameter✅ Parameter attachments created AFTER WebBrowserComponent
addAndMakeVisible(*webView)✅ Web content loaded via resource provider
webView->goToURL(WebBrowserComponent::getResourceProviderRoot())data:text/html;base64,... or loadHTML()⚠️ #1 CAUSE OF DAW CRASHES - VERIFY THIS FIRST
C++ destroys members in REVERSE order of declaration. If WebView is declared before relays, it will be destroyed AFTER relays, causing a crash when it tries to access freed relay memory.
✅ CORRECT ORDER (in PluginEditor.h):
private:
// 1. RELAYS FIRST (destroyed last)
juce::WebSliderRelay gainRelay { "GAIN" };
// 2. WEBVIEW SECOND (destroyed middle)
std::unique_ptr<juce::WebBrowserComponent> webView;
// 3. ATTACHMENTS LAST (destroyed first)
std::unique_ptr<juce::WebSliderParameterAttachment> gainAttachment;
❌ WRONG ORDER (causes DAW crash on unload):
private:
std::unique_ptr<juce::WebBrowserComponent> webView; // ❌ Too early!
juce::WebSliderRelay gainRelay { "GAIN" }; // ❌ Too late!
Verification: Run validation script before building:
.\scripts\validate-webview-member-order.ps1 -PluginName [Name]
See: ..kilocode/troubleshooting/resolutions/webview-member-order-crash.md
❌ DON'T: Declare webView before relays in header file ✅ DO: Always use order: Relays → WebView → Attachments
❌ DON'T: Use data URIs (data:text/html;base64,...)
✅ DO: Use getResourceProviderRoot() with embedded files
❌ DON'T: Create WebBrowserComponent without WebView2 backend
✅ DO: Explicitly specify .withBackend(webview2)
❌ DON'T: Create parameter attachments before WebBrowserComponent ✅ DO: Create relays → WebBrowserComponent → attachments (in that order)
❌ DON'T: Skip resource provider
✅ DO: Implement getResource() function to serve embedded files
❌ DON'T: Forget to embed web files in CMakeLists.txt
✅ DO: Use juce_add_binary_data() to embed all web UI files
Copy templates from: templates/webview/
PluginEditor.h.template → Source/PluginEditor.hPluginEditor.cpp.template → Source/PluginEditor.cppCMakeLists.txt.template → CMakeLists.txtReplace placeholders:
{{PLUGIN_NAME}} → Your plugin class name{{PLUGIN_NAME_LOWER}} → Lowercase plugin name{{PARAMETER_RELAYS}} → Your parameter relay declarations{{CREATE_PARAMETER_RELAYS}} → Code to create relays{{WITH_OPTIONS_FROM_RELAYS}} → .withOptionsFrom() calls{{CREATE_PARAMETER_ATTACHMENTS}} → Code to create attachmentstemplates/webview/_tools/JUCE/examples/Plugins/WebViewPluginDemo.hSource/ui/public/index.html, js/index.js, js/juce/index.jsDO NOT PROCEED TO DSP IMPLEMENTATION UNTIL ALL 8 CHECKS PASS
Read plugins/[Name]/.ideas/plan.md to determine implementation approach:
Complexity Score: [N]
If score ≤2: Single-pass implementation (all at once)
If score ≥3: Phased implementation (multiple passes)
Single-pass (Simple plugins):
Phased (Complex plugins):
Prerequisites: UI structure must be created (Phase 4.0) before DSP implementation begins.
Step 1: Read contracts
.ideas/creative-brief.md - Plugin purpose and behavior.ideas/architecture.md - DSP components and math.ideas/parameter-spec.md - Parameter bindingsStep 2: Update PluginProcessor.h
Add DSP member variables:
private:
// DSP Components from .ideas/architecture.md
juce::dsp::Gain inputGain;
juce::dsp::IIR::Filter filter;
juce::dsp::Compressor compressor;
// State
double currentSampleRate = 44100.0;
Step 3: Implement prepareToPlay()
Initialize DSP at sample rate:
void prepareToPlay(double sampleRate, int samplesPerBlock) override
{
currentSampleRate = sampleRate;
juce::dsp::ProcessSpec spec;
spec.sampleRate = sampleRate;
spec.maximumBlockSize = samplesPerBlock;
spec.numChannels = getTotalNumOutputChannels();
inputGain.prepare(spec);
filter.prepare(spec);
compressor.prepare(spec);
}
Step 4: Implement processBlock()
Add DSP processing:
void processBlock(juce::AudioBuffer& buffer,
juce::MidiBuffer& midiMessages) override
{
juce::ScopedNoDenormals noDenormals;
// Get parameter values from APVTS
auto gainValue = apvts.getRawParameterValue("gain")->load();
auto thresholdValue = apvts.getRawParameterValue("threshold")->load();
// Update DSP components
inputGain.setGainDecibels(gainValue);
compressor.setThreshold(thresholdValue);
// Process audio
juce::dsp::AudioBlock block(buffer);
juce::dsp::ProcessContextReplacing context(block);
inputGain.process(context);
filter.process(context);
compressor.process(context);
}
Step 5: Connect parameters to DSP
Ensure all parameters from .ideas/parameter-spec.md are:
Consistency Verification:
// Verify parameter consistency during development
#ifdef DEBUG
void verifyParameterConsistency()
{
// Check that parameter IDs match between spec and implementation
// This helps catch typos and ensures all specified parameters are implemented
const auto& parameters = apvts.processor.getParameters();
for (int i = 0; i < parameters.size(); ++i)
{
auto* param = dynamic_cast<juce::AudioProcessorParameterWithID*>(parameters[i]);
if (param != nullptr)
{
// Verify this parameter exists in parameter-spec.md
// (Implementation would read and parse the spec file)
}
}
}
#endif
plan.md will define phases like:
### Phase 4.1.1: Core Processing
- Input/output gain
- Basic filtering
### Phase 4.1.2: Dynamics
- Compressor
- Limiter
### Phase 4.1.3: Modulation
- LFO
- Envelope follower
Execute each phase sequentially:
Phase 4.1.1 - Implement core components
Phase 4.1.2 - Add dynamics components
Phase 4.1.3 - Add modulation
Decision menu after each phase:
✓ Phase 4.1.1 complete
Progress: 1 of 3 phases
What's next?
1. Continue to Phase 4.1.2 (recommended)
2. Test current state in DAW
3. Review Phase 4.1.1 code
4. Pause here
Choose (1-4): _
juce::ScopedNoDenormals at start of processBlock()// Helper function for parameter validation
float validateParameter(float value, float minVal, float maxVal, const char* paramName)
{
if (value < minVal || value > maxVal)
{
// Log warning but clamp to valid range
jassertfalse; // Debug warning
return juce::jlimit(minVal, maxVal, value);
}
return value;
}
// In processBlock() - with validation
auto* gainParam = apvts.getRawParameterValue("gain");
float rawGainValue = gainParam->load();
float validatedGain = validateParameter(rawGainValue, -60.0f, 24.0f, "gain");
inputGain.setGainDecibels(validatedGain);
// Member variables for smoothed parameters
juce::SmoothedValue<float> smoothedGain;
juce::SmoothedValue<float> smoothedCutoff;
juce::SmoothedValue<float> smoothedResonance;
// In prepareToPlay()
void prepareToPlay(double sampleRate, int samplesPerBlock) override
{
// Initialize smoothing with appropriate time constants
smoothedGain.reset(sampleRate, 0.020); // 20ms for gain
smoothedCutoff.reset(sampleRate, 0.050); // 50ms for filter
smoothedResonance.reset(sampleRate, 0.010); // 10ms for resonance
}
// In processBlock() - with smoothing
auto* gainParam = apvts.getRawParameterValue("gain");
smoothedGain.setTargetValue(gainParam->load());
for (int sample = 0; sample < numSamples; ++sample)
{
float currentGain = smoothedGain.getNextValue();
// Apply to DSP with per-sample smoothing
inputGain.setGainDecibels(currentGain);
}
// Template for different parameter types
enum class ParameterType
{
Linear,
Logarithmic,
Exponential,
Boolean
};
// Parameter mapping helper
float mapParameter(float normalizedValue, ParameterType type, float minVal, float maxVal)
{
switch (type)
{
case ParameterType::Linear:
return juce::jmap(normalizedValue, 0.0f, 1.0f, minVal, maxVal);
case ParameterType::Logarithmic:
return juce::jmap(juce::jlimit(0.0f, 1.0f, normalizedValue),
0.0f, 1.0f, minVal, maxVal, true);
case ParameterType::Exponential:
return minVal * std::pow(maxVal / minVal, normalizedValue);
case ParameterType::Boolean:
return normalizedValue > 0.5f ? maxVal : minVal;
default:
return normalizedValue;
}
}
// Usage example
auto* cutoffParam = apvts.getRawParameterValue("cutoff");
float normalizedCutoff = cutoffParam->load();
float mappedCutoff = mapParameter(normalizedCutoff,
ParameterType::Logarithmic,
20.0f, 20000.0f);
*filter.coefficients = juce::dsp::IIR::Coefficients::makeLowPass(
sampleRate, mappedCutoff);
// Helper to verify parameter consistency between spec and implementation
void verifyParameterConsistency()
{
// This should be called during development/debug builds
#ifdef DEBUG
// Check that all parameters in parameter-spec.md are implemented
// This is a development-time check, not runtime
#endif
}
// Standardized parameter binding pattern
void processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) override
{
juce::ScopedNoDenormals noDenormals;
// 1. Read all parameters with validation
auto* gainParam = apvts.getRawParameterValue("gain");
auto* cutoffParam = apvts.getRawParameterValue("cutoff");
auto* resonanceParam = apvts.getRawParameterValue("resonance");
// 2. Update smoothed values
smoothedGain.setTargetValue(gainParam->load());
smoothedCutoff.setTargetValue(cutoffParam->load());
smoothedResonance.setTargetValue(resonanceParam->load());
// 3. Process audio with per-sample parameter updates
const int numSamples = buffer.getNumSamples();
for (int sample = 0; sample < numSamples; ++sample)
{
// Get current smoothed values
float currentGain = smoothedGain.getNextValue();
float currentCutoff = smoothedCutoff.getNextValue();
float currentResonance = smoothedResonance.getNextValue();
// Apply to DSP components
inputGain.setGainDecibels(currentGain);
// Update filter with mapped values
float mappedCutoff = mapParameter(currentCutoff,
ParameterType::Logarithmic,
20.0f, 20000.0f);
float mappedResonance = mapParameter(currentResonance,
ParameterType::Linear,
0.1f, 10.0f);
*filter.coefficients = juce::dsp::IIR::Coefficients::makeLowPass(
currentSampleRate, mappedCutoff, mappedResonance);
// Process this sample
juce::dsp::AudioBlock block(buffer.getArrayOfWritePointers(),
buffer.getNumChannels(), 1);
juce::dsp::ProcessContextReplacing context(block);
inputGain.process(context);
filter.process(context);
}
}
// Handle zero-length buffers
if (buffer.getNumSamples() == 0)
return;
// Handle silent input
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear(i, 0, buffer.getNumSamples());
// Pre-allocate buffers in prepareToPlay()
void prepareToPlay(double sampleRate, int samplesPerBlock) override
{
tempBuffer.setSize(2, samplesPerBlock);
// Use tempBuffer in processBlock() - no allocation
}
After implementation complete:
Step 1: Validate JUCE/CMake setup
# Import state management module
. "$PSScriptRoot\..\scripts\state-management.ps1"
# Validate prerequisites using standardized function
if (-not (Validate-PhasePrerequisites -PluginPath "plugins\[Name]" -CurrentPhase "code" -RequiredPhase "design_complete" -RequiredFiles @(".ideas/architecture.md", ".ideas/plan.md"))) {
Write-Host "ERROR: Prerequisites not met. Complete design phase first." -ForegroundColor Red
exit 1
}
# Check JUCE installation
if (-not (Test-Path "C:\JUCE")) {
Write-Host "ERROR: JUCE not found at C:\JUCE" -ForegroundColor Red
Write-Host "Please install JUCE 8 and set up the project correctly" -ForegroundColor Yellow
exit 1
}
# Check CMake availability
if (-not (Get-Command cmake -ErrorAction SilentlyContinue)) {
Write-Host "ERROR: CMake not found" -ForegroundColor Red
Write-Host "Please install CMake and ensure it's in your PATH" -ForegroundColor Yellow
exit 1
}
# Validate project structure
if (-not (Test-Path "CMakeLists.txt")) {
Write-Host "ERROR: CMakeLists.txt not found in project root" -ForegroundColor Red
exit 1
}
# Validate canvas implementation for WebView framework
if ($state.ui_framework -eq "webview") {
if (-not (Test-CanvasImplementation -PluginPath "plugins\[Name]")) {
Write-Host "ERROR: Canvas implementation required for WebView framework" -ForegroundColor Red
Write-Host "WebView plugins must use HTML5 Canvas API with JUCE frontend library" -ForegroundColor Yellow
Write-Host "Please ensure Design/index.html uses canvas-based rendering" -ForegroundColor Yellow
exit 1
}
}
Step 2: Build plugin
# Build plugin with validation
try {
powershell -ExecutionPolicy Bypass -File .\scripts\build-and-install.ps1 -PluginName [Name]
Write-Host "Build completed successfully" -ForegroundColor Green
} catch {
Write-Host "Build failed: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Please check the build script and JUCE configuration" -ForegroundColor Yellow
exit 1
}
If build fails:
If build succeeds:
Run 5 automated tests:
If tests fail: Cannot proceed to SHIP phase until audio engine is stable.
CRITICAL CHOICE: Custom UI or headless?
✓ Audio Engine Working
DSP components: [N]
Parameters: [N]
Tests: All passed
What type of interface?
1. Add custom UI - WebView interface with mockup
2. Ship headless - DAW controls only (fast path)
3. Test in DAW first
Choose (1-3): _
.ideas/mockups//improve [Name]Ensure plugin state saves/loads correctly:
// getStateInformation()
void getStateInformation(juce::MemoryBlock& destData) override
{
auto state = apvts.copyState();
std::unique_ptr xml(state.createXml());
copyXmlToBinary(*xml, destData);
}
// setStateInformation()
void setStateInformation(const void* data, int sizeInBytes) override
{
std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes));
if (xmlState.get() != nullptr)
if (xmlState->hasTagName(apvts.state.getType()))
apvts.replaceState(juce::ValueTree::fromXml(*xmlState));
}
Critical: State must preserve all parameter values between sessions.
Git commit after each phase:
# Backup state before commit
Backup-PluginState -PluginPath "plugins\[Name]"
git add plugins/[Name]/Source/
git add plugins/[Name]/.ideas/plan.md
git commit -m "feat([Name]): Phase 4.1 DSP - [Phase description]
Implemented: [list components]
Parameters connected: [N]
Real-time safe: Yes
Generated with Kilo Code"
For single-pass:
# Backup state before final commit
Backup-PluginState -PluginPath "plugins\[Name]"
git commit -m "feat([Name]): Phase 4 CODE complete
All DSP components implemented
[N] parameters connected
Real-time safe audio processing
Generated with Kilo Code"
Update state after implementation:
# Mark implementation complete using standardized function
Complete-Phase -PluginPath "plugins\[Name]" -Phase "code" -Updates @{
"validation.code_complete" = $true
"validation.tests_passed" = $false # Will be set after testing
}
juce::dsp::Gain gain;
gain.setGainDecibels(dbValue);
gain.process(context);
juce::dsp::IIR::Filter filter;
*filter.coefficients = juce::dsp::IIR::Coefficients::makeLowPass(
sampleRate, cutoffFreq, resonance);
filter.process(context);
juce::dsp::Compressor compressor;
compressor.setThreshold(thresholdDb);
compressor.setRatio(ratio);
compressor.setAttack(attackMs);
compressor.setRelease(releaseMs);
compressor.process(context);
juce::SmoothedValue smoothedGain;
smoothedGain.reset(sampleRate, 0.05); // 50ms ramp
// In processBlock
smoothedGain.setTargetValue(newGainValue);
for (int sample = 0; sample < numSamples; ++sample)
{
float currentGain = smoothedGain.getNextValue();
// Apply currentGain to audio
}
Invoked by:
Updates:
Source/PluginProcessor.h - DSP member variablesSource/PluginProcessor.cpp - Audio processing logicPLUGINS.md - Phase statusplugins/[Name]/status.json - Project stateNext phase:
Build errors:
No audio output:
Crackling/artifacts:
Parameters don't respond:
development
# SKILL: TROUBLESHOOTING & ISSUE RESOLUTION ## STEP 1: CHECK KNOWN ISSUES FIRST **Before trying random solutions:** ```powershell # Search known issues database $errorPattern = "duplicate target juce" $knownIssues = Get-Content ...kilocode\troubleshooting\known-issues.yaml | ConvertFrom-Yaml $matches = $knownIssues.issues | Where-Object { $_.error_patterns -match $errorPattern } if ($matches) { Write-Host "✓ Known issue found: $($matches.title)" Write-Host "Resolution: $($matches
tools
# SKILL: TESTING **Goal:** Stability Check. ## TASKS 1. **Pluginval:** Run validation script. 2. **Crash Analysis:** If crash reported, read Documents/APC_CRASH_REPORT.txt. 3. **Manual Check:** Ask user to load in DAW and move knobs.
tools
# SKILL: ARCHITECTURE & PLANNING **Goal:** Define DSP architecture, complexity assessment, and implementation strategy **Trigger:** `/plan [Name]` **Input:** Reads `plugins/[Name]/.ideas/creative-brief.md` and `parameter-spec.md` **Output Location:** `plugins/[Name]/.ideas/` --- ## 🎯 PHASE 2: PLAN (Architecture & Strategy) **Prerequisites:** - `plugins/[Name]/.ideas/creative-brief.md` exists - `plugins/[Name]/.ideas/parameter-spec.md` exists - Phase 1 (DREAM) complete **Output Files:** - `p
tools
# SKILL: PACKAGING (Cross-Platform) **Goal:** Create professional, cross-platform plugin installers for Windows, macOS, and Linux **Trigger:** `/ship [Name]` or "Ship [Name]" **Prerequisites:** Phase 4 (CODE) complete, audio engine working, all tests passed **Output Location:** `dist/[Name]_v[version]/` --- ## Overview This skill handles the complete packaging and distribution process for APC plugins. It supports: - **Local builds** (current platform only) - **GitHub Actions builds** (cross