initial-development #1

Merged
fragmented merged 23 commits from initial-development into mainline 2026-05-20 04:09:06 +00:00
Owner
No description provided.
Documentation:
- Add design doc capturing architecture, component breakdown,
  technique registry approach, and v1 scope
- Add PROJECT_CONTEXT.md with implementation status, architecture
  decisions, deferred items, domain model reference
- Add 8-phase implementation plan with file lists, steps, and
  decisions-made sections for each phase

Build System:
- CMake 3.22 project targeting macOS 11.0+, C++17
- FetchContent pulls JUCE 8.0.4 (upgraded from planned 7.x for
  macOS 15 compat after CGWindowListCreateImage was removed),
  pugixml v1.14, nlohmann/json 3.11.3, Catch2 v3.5.4
- juce_add_gui_app target "GuitarPrometheus" producing
  "Guitar Prometheus.app" bundle at dev.nullfragment.guitarprometheus

Application:
- Minimal JUCEApplication with placeholder window and centered
  scaffold-status text; em-dash wrapped with
  juce::String::fromUTF8 to avoid compiler source-encoding issues

Tests:
- Catch2 runner discovered via catch_discover_tests
- Smoke tests confirm Catch2 wiring, pugixml parse, nlohmann/json
  round-trip (3/3 passing)

Documentation Structure:
- Root README with build/test instructions
- documentation/ hub stub linking to agentic-context/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the original Phase 2's flat Note/Score model with a domain-
modeled data layer that respects how musicians actually think:
per-measure key/time/tempo with forward inheritance, engraved duration
symbols distinct from played MIDI events, key-aware accidental
resolution.

Data Model:
- New namespaces GuitarPrometheus::MusicModels (theory primitives) and
  GuitarPrometheus::SongStructure (composed pieces)
- enum class Accidental + enum Mode/PitchClass/NoteValue with NUM_*
  sentinels for sequential validation
- KeySignature with two constructors: (fifths, mode) for parser and
  (tonic, accidental, mode) for human-friendly construction; static
  lookup tables resolve accidentals (15 entries, fifths-keyed) and
  tonic (15 entries, fifths+mode-keyed)
- Engraving class replaces Note: pitch + accidental + octave +
  Duration (NoteValue + dots + tuplet) + position metadata
- Measure carries denormalized effective TimeSig/Key/Tempo so
  downstream consumers never walk back for inheritance
- Part identity = juce::Uuid + scorePartId string; Score immutable
- Old Note.h, Score.h, Part.h, Measure.h deleted

Parser:
- MusicXmlParser rewritten in GuitarPrometheus::Parser namespace
- Propagates effective time/key/tempo forward across measure changes
- Parses NoteValue from <type>, dots from <dot/>, tuplets from
  <time-modification>, accidentals from <alter>, drums from
  <unpitched>
- Tick tracking with chord/grace handling and <backup>/<forward>
  cursor adjustments
- Result type now exposes warnings list separately from errors

Tests:
- 20 tests across 5 files, all green
- KeySignatureTests: fifths-based + tonic-based construction,
  out-of-range rejection, round-trip tonic preservation
- EngravingTests: duration computation (whole through 64th, dots,
  tuplets), MIDI note number with key-aware accidental fallback
- MeasureInheritanceTests: hand-crafted XML fixture exercises
  mid-song time/key/tempo changes
- ParserStructuralTests: rewritten against new types, validates
  parts (5), score attributes, per-part note counts (2342/368/350/
  1142/1729), engraving detail, tick tracking, drum populations,
  effective-value denormalization
- tests/fixtures/ now holds lesson-riff-v14.{xml,mid} regression
  corpus + inheritance-test.xml

Build:
- Source layout split into src/public/ (headers) and src/private/
  (implementations) following Unreal-style API/impl separation
- CMake target_include_directories points at src/public; old
  include paths in source files unchanged
- guitar_prometheus_core static library now links juce::juce_core
  for juce::Uuid (only JUCE dep in the core)
- .clang-format added with 4-space indent, 150-col limit,
  IndentAccessModifiers, sorted/regrouped includes, BSD braces
- format-check + format CMake targets; format-check wired as a
  build dependency of GuitarPrometheus and GuitarPrometheusTests
  so misformatted code blocks the build
- find_program prefers Homebrew clang-format (22+) over Apple CLT
  (17) so the modern config keys (AlignFunctionDeclarations etc.)
  are recognized

Documentation:
- PROJECT_CONTEXT updated with Music-theory-aware data model
  decision, full Coding Conventions section (style principles,
  mixed enum style, class member access, parameter passing rules,
  source layout, formatting enforcement), refreshed Domain Model
  Reference
- Implementation plan: Phase 2 marked Complete (rebuilt) with
  Decisions Made entries; Phase 5 scope expanded to include drum
  mapping profile + per-track octave offset; new Phase 9 (Future
  Implementations) with score merging + visual score renderer
  entries (custom JUCE renderer preferred, Verovio LGPL-3.0
  fallback)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parses Guitar Pro's performance modifiers into the in-memory Score so
the keyswitch injector (Phase 5) has everything it needs: per-note
techniques, multi-note spans, dynamics with forward inheritance, and
Guitar Pro's non-standard extensions embedded in XML processing
instructions. Unknown articulations surface as warnings rather than
silently dropping.

Techniques Model:
- GuitarPrometheus::Techniques namespace with Technique enum (15
  members across Sustained / PerNote / Span categories) and Category
  enum
- TechniqueRegistry: static lookup (xml element name) -> (Technique,
  Category); single source of truth
- TechniqueSet: std::bitset-backed collection on each Engraving for
  per-note techniques
- TechniqueSpan on Measure, using absolute ticks (not per-measure
  indices) so spans that cross measure boundaries are representable

Parser - standard-XML techniques:
- Walks <notations>, <articulations>, <technical>, and
  <play><mute>straight</mute></play> per note, dispatching through
  the registry for Sustained and PerNote categories
- Span detection via stack-per-(technique, number) at the part
  level - handles nested/concurrent spans correctly (176 slurs
  without number attribute all default to number=1)
- <bend> emitted as a single-note span at the current note's tick;
  <release/> handling deferred to Phase 5
- Counts verified against lesson-riff-v14.xml: 122 staccato, 10
  accent, 116 strong-accent, 8 scoop, 16 tap, 572 palm-mute, 26
  hammer-on spans, 176 slur spans, 150 slide spans, 20 bend spans

Parser - Guitar Pro processing instructions:
- Recursively walks the <note> subtree for <?GP <root>...</root> ?>
  processing instructions, parses each PI value as inner XML, and
  dispatches elements through TechniqueRegistry
- parse_pi flag added to pugixml load_file (pugixml drops PIs by
  default - silently, which is why initial PI counts came back as
  zero before the flag was found). Footgun documented in the plan.
- Tab data (<string>, <fret>) and GP extensions we don't model yet
  (<brush>, <WhammyBar>) are allow-listed and skipped silently
- PI counts verified: 606 letring, 102 lhtapping, 12 vibrato, 48
  vibratoWTremBar (44 Slight + 4 Wide)

Parser - unknown-element warnings:
- collectPerNoteTechniquesFromContainer takes warnOnUnknown bool and
  container label; <articulations> uses strict mode (closed list in
  MusicXML, unknown element = genuine surprise), while <technical>
  and direct children of <notations> stay relaxed to avoid false
  positives on non-technique elements like <bend-alter>, <release>,
  <tied>, <string>, <fret>
- GP PI parser warns on unknown elements inside <?GP?>

Dynamics:
- MusicModels::Dynamic enum (ppp, pp, p, mp, mf, f, ff, fff, sfz,
  fp, plus None sentinel meaning "inherit forward")
- defaultVelocityFor(Dynamic) - bundled MIDI velocity map (0..127)
  with MezzoForte = 64; Phase 4 profiles can override
- Engraving::dynamicChange: optional per-note marking
- Measure::effectiveDynamic: denormalized start-of-measure value,
  propagated forward across measures like time/key/tempo
- DynamicSpan struct on Measure for crescendo/decrescendo wedges
  (data model only; Phase 5 will interpolate)
- Parser distinguishes pre-first-note <direction> (becomes measure's
  effectiveDynamic) from mid-measure <direction> (attached to the
  next note via dynamicChange)

Tests:
- 37 tests, all green (up from 30 at end of Phase 2)
- TechniqueRegistryTests: all 15 registry entries + unknown rejection
- TechniqueSetTests: empty/add/contains/size/idempotence
- TechniqueParserTests: per-note counts and span counts against sample
  file; GP-PI counts for letring/lhtapping/vibrato/vibratoWTremBar
- DynamicsTests: defaultVelocityFor map + parser with hand-crafted
  dynamics-test.xml exercising pre-first-note, mid-measure, and
  cross-measure dynamic changes
- WarningTests: hand-crafted unknown-articulation.xml asserts
  warnings fire for <spiccato/> and <madeUpTechnique/>; regression
  test asserts sample file parses with zero warnings

Documentation:
- New canonical design doc
  agent-designs/2026-04-16-phase-2-rebuild-design.md captures the
  rebuilt data model as a single coherent spec (MusicModels +
  SongStructure namespaces, Engraving, KeySignature with two
  constructors, denormalized inheritance, Techniques/Dynamics
  subsystems, parser architecture, test layout, coding conventions)
- Original design doc (2026-04-15-...md) marked SUPERSEDED at the
  top with a pointer to the rebuild doc
- Implementation plan Phase 2 Files/Steps rewritten to describe the
  current files and behavior (no more references to
  Note.h/Voice.h/Unpitched); Phase 3 marked Complete with Decisions
  Made covering the rebuild, span stack-per-key, denormalized
  dynamics, GP PI parse_pi footgun
- Phase 4 scope expanded with "project profile" concept (per-file
  session state keyed by source fingerprint, separate from reusable
  VST profiles)
- PROJECT_CONTEXT: new Techniques subsystem + Dynamics sections in
  Domain Model Reference; parser status marked Complete; remaining-
  work section pruned to Phase 4+

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Design & Planning:
- Phase 4 design doc capturing unified Profile type with optional
  drumMap, JSON Schema-driven sanitize-and-warn validation, and
  ClipboardPort abstraction
- Review-feedback revisions: rename namespace to Profiles, spell
  out slug algorithm, clarify findUnmappedDrumNames semantics,
  introduce ClipboardPort seam
- Phase 4 step-by-step implementation plan
- GP drum name research doc derived from lesson-riff-v14.xml
- Follow-up plan for the Technique/Articulation split

Techniques:
- enum class Technique (11 members) and enum class Articulation
  (4 members) — MusicXML <technical> vs <articulations> distinction
  encoded in the type system so callers can no longer assign an
  ArticulationMod to a technique like PalmMute
- enum class Category for style consistency
- kNumTechniques / kNumArticulations constants + static_assert
  anchors (no NUM_* sentinel)
- TechniqueCoding + ArticulationCoding: free functions toJSON /
  toXML / toHumanReadable / getCategory (Technique only) /
  from*JSON / from*XML, all exhaustive switches with no default
- TechniqueSet + ArticulationSet — std::bitset wrappers

Data Model:
- Profile with three maps: techniqueKeyswitches (Technique -> int),
  articulationKeyswitches (Articulation -> int, some VSTs expose
  articulations as keyswitches), articulationMods (Articulation ->
  ArticulationMod)
- ArticulationMod struct (velocityBoost + lengthMultiplier)
- findUnmappedDrumNames API for Phase 7 UI caution icons
- Namespace GuitarPrometheus::Profiles (plural, avoids the
  Profile::Profile name collision)

Validation:
- JSON Schema Draft 2020-12 as the declarative validation contract
  (nlohmann/json-schema-validator dependency added)
- Sanitize-and-warn policy: three structural Errors, every other
  issue becomes a Warning with a sanitized field value
- DiagnosticReport formats ProfileIssue lists for stderr and the
  Phase 7 UI; errors sort before warnings

Storage:
- ProfileStore with atomic write-rename and UUID-keyed filename
  hashing (FNV-1a 32-bit over UUID + salt)
- File and clipboard import/export via ClipboardPort abstraction
  (JuceClipboardPort lives in the app binary — juce_gui_basics
  is not a core dependency)
- UUID collision resolution (replace vs. copy with new UUID)

Parser:
- Per-container routing: <technical> and <notations>-direct use
  techniqueFromXML (no warnings); <articulations> uses
  articulationFromXML and warns on unknown elements
- Scoop special-case: lives in MusicXML <articulations> but is a
  Technique in our domain model — routed to the technique set
  inside the articulations path
- Engraving carries both TechniqueSet and ArticulationSet

Bundled Defaults:
- Generic Guitar and Generic Bass with empty keyswitch maps
- General MIDI Drum Kit with 86 GP drum names → GM pitch mappings
- Compiled into binary as JSON string literals; byte-equal with
  resources/profiles/*.json (enforced by tests)

Testing:
- Step 0: parameterize high-duplication test files (Engraving,
  KeySignature, techniques) with Catch2 GENERATE(from_range)
- 78 tests total (was 36 pre-Phase-4)
- New: ArticulationCodingTests, ArticulationSetTests,
  TechniqueCodingTests (latter replaces TechniqueRegistryTests)
- TechniqueParserTests: countArticulation helper; articulation
  assertions query getArticulations() on the engraving
- ProfileTests: schema-consistency tests derive expected names by
  iterating the enums via toJSON — no hardcoded list; covers
  techniqueKeyswitches, articulationKeyswitches, articulationMods

Docs:
- PROJECT_CONTEXT Profile Subsystem section rewritten; stale
  MidiKeyswitcher path corrected
- Master implementation plan: Phase 4 marked complete with linked
  design and detailed plan

Tooling & Style:
- Switch header Doxygen from /// to /** ... */ Javadoc blocks
- Reflow all block and line comments to 120-column width
  (paragraph breaks, @param tags, and bullet lists preserved)
- ColumnLimit 150 → 120; BinPackParameters OnePerLine → BinPack
  so function decls pack onto one line when they fit
- clang-format runs in place on every build via a stamp-driven
  add_custom_command; attached as a dep to guitar_prometheus_core,
  GuitarPrometheus, and GuitarPrometheusTests. format-check
  target preserved for CI (dry-run, --Werror)
- -Wswitch-enum -Werror=switch-enum -Werror=return-type on our
  own targets (PRIVATE; JUCE untouched) — adding an enum member
  without updating every to/from function is a build failure

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drum-Part Model:
- Engraving gains optional instrumentRef + tieStart / tieStop
  flags. Part gains scoreInstruments (id->name) +
  instrumentMidiPitch (id->GM percussion pitch from
  <midi-instrument><midi-unpitched>) + isDrumPart() helper.
  Parser collects every <score-instrument> and every
  <midi-instrument> per part. Per-note <instrument id> ref +
  <tied> elements threaded into PerNoteMarkings and onto
  Engraving.

Score Tempo Map:
- Replace Score's initialTempoBpm scalar with a sparse
  std::map<int, int> tempoByMeasure plus cached scalar
  fields initialTempoBpm_ and currentScoreTempoBpm_ (first /
  last entries). Parser writes a map entry per <sound tempo>
  site, keeping the cache in sync. Reads are O(1) without
  map iteration.

Parser PPQN + Cursor + Transpose:
- Output PPQN normalized to 480 regardless of source
  <divisions> (per-measure scale factor applied at each
  duration computation). MIDI files now play correctly in any
  DAW.
- cursorTick is reset to a running measureStartTick at the
  start of every measure and advanced by the measure's
  musical length at the end. Empty / attributes-only / leading-
  rest measures still consume their full bar of time on the
  global timeline; parts with leading silence (e.g. P2 silent
  for 18 measures) start at the correct absolute tick.
- <transpose><octave-change> applied to each Engraving's
  octave so getMidiNoteNumber yields the *sounding* pitch.
  Guitar octave-change=-1, bass typically -2.

MIDI Event Pipeline:
- MidiEvent + ExportConfig + HumanizerConfig headers in
  src/public/midi/. MidiEvent carries sourceNoteId so
  downstream stages distinguish music from control data.
- KeyswitchInjector (5-pass walk): bank/program setup, per-
  note NoteOn/NoteOff with velocity + length mod, sustained
  run-merge, per-note fire-and-forget, span keyswitch,
  articulation keyswitch.
- flattenPartNotes filters: drop rests, grace notes, staff>1
  (GP's tab-staff duplicate), HammerOn span endTick (left-
  hand-articulated target), and merges tied-note pairs into
  one NoteOn covering the combined duration.
- Drum pitch resolution: Profile.drumMap override →
  Part.instrumentMidiPitch (from <midi-unpitched>, 1-128 →
  MIDI 0-127) → Engraving display pitch.
- OctaveTransposer: in-place per-part octave offset, skips
  drum parts and keyswitch events, clamps to [0, 127].
- MidiWriter: SMF Format 0 per-track + Format 1 merged via
  juce::MidiFile. Walks Score::getTempoByMeasure to emit
  one tempoMetaEvent per entry at the matching measure-start
  tick (computed via measureStartTicks helper).

Regression Test:
- PPQN match (exact), track count match (exact), per-track
  ProgramChange multiset (exact), per-track structural
  Note-pair compare (10% size + symmetric-difference
  tolerance, ±1 tick duration window), drum-part channel = 10
  assertion, distinct channels per pitched part assertion,
  always-on per-track stderr diagnostic.
- MusicXML <midi-program> 1-based → MIDI 0-based fix at the
  injector's ProgramChange emission.

Build + Tests:
- juce::juce_audio_basics linked into guitar_prometheus_core.
- 30+ new tests across injector (palm-mute, let-ring run,
  slide span, tap, accent, staccato, drum unmapped, drum
  warn-once, missing-keyswitch, staff-skip, grace-skip,
  tied-pair, tied-chain, hammer-on suppress), transposer
  (5), writer (3), regression (5 sections), parser drum
  (95 score-instruments, instrumentRef per note), tempo map
  (3), PPQN (3).

108/108 tests pass.

New Files:
- src/public/midi/{MidiEvent,ExportConfig,HumanizerConfig,
  KeyswitchInjector,OctaveTransposer,MidiWriter}.h
- src/private/midi/{KeyswitchInjector,OctaveTransposer,
  MidiWriter}.cpp
- tests/{KeyswitchInjectorTests,OctaveTransposerTests,
  MidiWriterTests,RegressionTest,ScoreTempoMapTests,
  PpqnNormalizationTests}.cpp
- documentation/agentic-context/implementation-plans/
  2026-04-24-phases-5-6-midi-pipeline.md
- documentation/agentic-context/implementation-plans/
  2026-04-24-phases-5-6-followup.md
- documentation/agentic-context/implementation-plans/
  2026-04-24-regression-hardening.md
Deterministic velocity + timing jitter over the MidiEvent
stream. Off by default (default-constructed HumanizerConfig
produces byte-identical output), so the Phase 5 regression
test stays valid whether the humanizer runs or not.

Velocity Jitter:
- Gaussian(0, sigma) delta on every music NoteOn's
  velocity, clamped to [velocityFloor, velocityCeiling].
- Keyswitch NoteOns (no sourceNoteId) are never jittered.
- Non-zero cfg.seed is used directly; cfg.seed == 0 falls
  back to an FNV-1a hash of the event stream so identical
  inputs reproduce identically without the caller having
  to pick a seed.

Timing Jitter:
- Uniform(-timingJitterTicks, +timingJitterTicks) offset
  applied to each music NoteOn and its paired NoteOff,
  matched by sourceNoteId so duration is preserved exactly.
- Negative ticks clamp to 0 defensively.
- Post-jitter the per-part vector is stable-sorted by
  (tick, priority) — only when timingJitterTicks > 0, so
  pure velocity jitter doesn't disturb the injector's
  original order.

API:
- Free function `humanize(events, config)` — renamed from
  `apply` to avoid shadowing `std::apply` in test files
  that pull it into scope via a `using` declaration.

Tests (8):
- Default config no-op; zero HumanizerConfig no-op;
  determinism (same seed = same output); different seeds
  diverge; extreme sigma stays inside clamp; keyswitch
  velocity untouched; duration preserved under timing
  jitter; keyswitch tick positions untouched under
  timing jitter.

116/116 tests pass.

New Files:
- src/public/midi/Humanizer.h
- src/private/midi/Humanizer.cpp
- tests/HumanizerTests.cpp
End-to-end UI wrapping the existing parser / profile / pipeline /
writer stack so users can open a Guitar Pro MusicXML, pick profiles
per track, edit tempo and export to MIDI without leaving the app.
146 tests pass (116 inherited + 30 added through Phase 7 + F8.x
acceptance fixes).

Project Container:
- New `Project` model: id + salt + name + gzip+base64-embedded
  MusicXML + scorePartId-keyed track bindings + tempo overrides +
  per-part export config (octave / humanizer)
- ProjectSchema v1 (embedded JSON literal mirrored on disk under
  resources/schema/), ProjectValidator, ProjectStore with
  load/save/materializeScore + atomic-write + slug-hash filenames
- MusicXmlParser gains a string_view overload for materializing
  embedded XML; existing path-based overload now dispatches to a
  shared post-load helper

App Shell:
- AppController owns the active Project, parsed Score, ProfileStore,
  current path, parser warnings; broadcasts state changes via
  juce::ChangeBroadcaster
- MainWindow: DocumentWindow + MenuBarModel + ApplicationCommandTarget
  with File menu commands (New / Open / Save / Save As / Export /
  New Profile / Manage Profiles / Tempo Map / Close) and About
- Async DialogWindow modals for Export, Manage Profiles and Tempo Map

Track List Panel:
- TableListBox with seven columns (Index, Name, Instrument, MIDI
  Program, Channel, Notes, Techniques, Profile picker)
- Drum-aware profile combo: drum parts get drum-only profiles,
  pitched parts get pitched-only profiles; selection persists into
  the Project's trackBindings
- Hosts UnknownTechniquesBanner above the table when parser
  warnings are non-empty; banner summarizes by message with counts
- Instrument column falls back to Drum Kit / GM patch name for parts
  whose XML lacks a single <score-instrument>

Profile Editor:
- New ProfileEditorPanel: left-hand profile list + right-hand
  TabbedComponent with Techniques, Articulations, Articulation
  Mods, Velocities, and (when drum toggle is on) Drum Map tabs
- Pure `applyProfileEdits` / `toFormState` round-trip a Profile
  through a mutable ProfileFormState, preserving identity
- New PianoKeyPicker widget wraps juce::MidiKeyboardComponent
  (integration into editor rows lands in F8.5)
- New profile flow: AppController::createNewProfile mints an
  Untitled Profile and the editor opens with it pre-selected

Tempo Map:
- TempoMapPanel: per-measure BPM editor; shows Effective BPM
  (override -> score-derived -> initial fallback) and a Reset
- AppController::setTempoForMeasure rebuilds the immutable Project
  with updated tempoOverrides
- MidiWriter writeMerged / writePerTrack gain optional tempoOverride
  parameter so user edits land in the emitted .mid; Score itself
  stays immutable

Export Pipeline:
- Pure `Midi::assemblePipelineRun` chains inject -> applyOctaveOffsets
  -> humanize so the dialog and any future caller can drive a full
  export without widgets
- Builds a single-part Score per iteration so the existing
  whole-Score inject API supports per-part profile bindings
- ExportDialog: merged / per-track radio, output picker, in-dialog
  log; resolves profiles by UUID with name fallback and forwards
  the project's tempo overrides to the writer

MIDI Artifact Eradication (F8.x acceptance fixes):
- Re-articulation priority reorder (NoteOff before NoteOn at the
  same tick) so JUCE no longer inserts stuck-note OFFs when a pitch
  is restruck on a beat boundary
- Identical-pitch chord member dedup in emitNoteEvents so doubled
  drum hits collapse to one ON/OFF pair
- 530 -> 0 unmatched NoteOffs across all five tracks of the
  lesson-riff fixture export
- ASCII three-dots replace U+2026 in every user-visible string;
  the JUCE LookAndFeel font path on macOS rendered the Unicode
  ellipsis as mojibake

Shared Utilities:
- AtomicFileWrite + FnvHash extracted out of ProfileStore into
  src/private/util/ for ProjectStore reuse
- WarningSummary groups parser diagnostics by message with counts
  (drives the banner)
- GmPatchNames returns canonical GM patch names by program byte
  (drives MIDI Program column display + Instrument fallback)
- ProfileFilter (filterProfiles + isDrumProfile + isPitchedProfile)
  drives the per-track combo

Tests:
- 30 new tests across ProjectStore round-trip, ProjectValidator,
  ProjectSchema embed/disk byte-id, MusicXmlParser string overload,
  GmPatchNames, ProfileFilter, ProfileEdits, PipelineOrchestration,
  MidiWriter tempo override, WarningSummary, ReArticulation
  (priority + chord-dedup regression for the F8.1 artifact fix)

Build:
- juce_audio_utils linked for MidiKeyboardComponent
- guitar_prometheus_core gains all new core sources;
  GuitarPrometheus app gains the panel / window sources

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the obvious "next-step" gaps Phase 7 deferred: dirty-state
safety, recent projects, drag-and-drop opens, replace-XML, profile
management UI, the long-promised PianoKeyPicker integration into the
profile editor, and the per-technique keyswitch velocity Odin III's
articulation matrix needs. All additive on top of Phase 7's JUCE
shell, all individually shippable.

App preferences (F8.1):
- AppPreferences wraps juce::PropertiesFile + RecentlyOpenedFilesList;
  Open Recent submenu re-built from controller_.recentProjects() each
  time it shows. Greyed out for missing files; Clear Recents at the
  bottom

Dirty-state + autosave (F8.2):
- closeButtonPressed shows Save / Don't Save / Cancel when dirty
- AppController inherits juce::Timer with kAutosaveSeconds = 30 — only
  fires when currentPath_ + dirty_; never silently Save-As

Drag-and-drop opens (F8.3):
- New DropClassifier helper: classifyDroppedFile -> OpenProject /
  NewProject / Reject. .gpproj -> open; .musicxml / .xml -> new
  project flow with the drag's basename as the default project name
- MainWindow inherits juce::FileDragAndDropTarget; classification is
  pure + tested

Replace MusicXML (F8.4):
- AppController::replaceMusicXml(newSourcePath) re-parses, builds a
  fresh gzip + Project, and rewires bindings via new RebuildBindings
  helper (matching scorePartIds carry profile + name; new parts get
  default; missing parts produce a warning)
- Tempo overrides drop entries past the new score's measure count;
  per-part octave / humanizer drop entries for vanished parts. Every
  drop surfaces in the post-replace AlertWindow

Keyswitch picker integration (F8.5):
- New KeyswitchRow private class in ProfileEditorPanel: button
  labelled via formatKeyswitchLabel ("C2 (36)" or "(none)"); click
  opens an async DialogWindow hosting PianoKeyPicker; right-click
  clears the keyswitch
- formatKeyswitchLabel is a free function in src/public/profile/
  with its own KeyswitchLabelTests
- Applied on the Techniques and Articulations tabs only; Velocities
  + Articulation Mods + Drum Map keep their existing widgets

Profile management UI (F8.6):
- ProfileStore::remove deletes a profile's on-disk file (same fileByUuid_
  map already used during save)
- isBundledDefault free function in DefaultProfiles greys out Delete on
  bundled rows
- Editor gains New / Import... / Export... / Delete buttons with
  AlertWindow confirmations and Replace/Copy collision resolver
  (placeholder — F9.2 lifts to interactive)

Per-technique keyswitch velocity (F8.7):
- Profile gains optional keyswitchVelocityForTechnique map (additive in
  schema v1; old profiles parse cleanly with empty map)
- KeyswitchInjector adds keyswitchVelocityFor(profile, technique)
  helper; 4 of 5 emit sites use it (sustained, per-note, span, and
  the new override). Articulation site stays at 100 — F9.4 covers it
- Editor "KS Velocity" tab next to Techniques. 0 means "use default";
  positive value lands in the optional Profile field

MIDI artifact fixes (F8.0 follow-ups, squashed in):
- Re-articulation priority reorder: Music NoteOff sorts before
  Music NoteOn at the same tick. Fixes JUCE's MidiMessageSequence
  emitting stuck-note OFFs at re-articulation (530 -> 0 unmatched
  offs on lesson-riff)
- Identical-pitch chord member dedup in emitNoteEvents collapses
  two NoteOns at the same (tick, pitch) into one ON/OFF pair
- ReArticulationTests covers both fixes

Misc:
- File menu gained "New Profile..." that mints "Untitled Profile" via
  AppController::createNewProfile and opens the editor pre-selected
- TrackListPanel Instrument column falls back to "Drum Kit" or GM
  patch name when MusicXmlParser doesn't promote a single
  score-instrument to the part-wide instrumentName
- Menu / button ellipsis mojibake -> ASCII three-dots throughout

Tests: 146 (Phase 7) -> 172 after Phase 8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps every F9.x sub-phase plus the Phase 9/10/11 plan documents into
a single review commit. Tests: 146 (Phase 7 tip) -> 192 after Phase 9.
The schema migration framework lands first so subsequent sub-phases
can rely on it; mid-measure tempo (F9.6) is the first real exercise.

Schema migrations (F9.1, F9.6):
- ProjectMigrationRegistry framework with kCurrentSchemaVersion
- v1 -> v2 transformer in BundledProjectMigrations rewrites the
  legacy measure -> bpm tempo map into an array of
  {measure, tick, bpm} entries. ProjectStore::load registers the
  bundled migrations idempotently before validation
- project-v1.schema.json renamed to project-v2.schema.json; literal
  in ProjectSchema.cpp matches byte-for-byte (test enforces)
- New Project::TempoOverrides type alias propagates through Project,
  RebuildBindings, MidiWriter, PipelineOrchestration, ExportDialog,
  and AppController

Profile editor + store polish (F9.2, F9.3, F9.4, F9.7-SH1, F9.7-SH2):
- F9.2: Replace / Copy / Cancel collision dialog replaces the
  hardcoded resolver. ProfileStore exposes previewImportFromFile /
  previewImportFromClipboard so the editor can detect a UUID
  collision before committing
- F9.3: Always-on bundled defaults dropped. DefaultProfiles returns
  empty vectors; isBundledDefault is always false; the three
  resource JSONs (generic-guitar / -bass / GM-drumkit) are gone
- F9.4: Articulation keyswitch velocity mirrors F8.7 for the
  articulation map. Editor gains an "Articulation KS Velocity" tab;
  the injector calls keyswitchVelocityFor(profile, articulation) at
  the articulation site instead of hardcoding 100
- F9.7 (SH1): exportAllToZip / importAllFromZip via juce::ZipFile.
  Editor gains an Archive... button with Export All / Import All
  popup. Batch collision policy (Replace All / Copy All / Skip) is
  picked once before iteration to keep the resolver synchronous
- F9.7 (SH2): six bundled VST starter profiles in
  resources/profiles/vst-presets/ scraped from public manuals
  (Shreddage 3.5 Hydra / Argent / Darkwall / Serpent + SubMission
  Audio UmanskyBass / GroveBass). Solemn Tones Odin III + Sanguine
  Bass deferred — their keyswitch chart is only inside the running
  plugin

Technique curation (F9.5):
- Add ClosedPalmMute, DeadMute (Sustained), and ChokedNote
  (PerNote) to the Technique enum. kNumTechniques bumps 11 -> 14
- Parser detects each via <other-technical type="closed-palm-mute"/>
  etc. — opens the standard MusicXML escape hatch through
  techniqueFromXML
- profile-v1 schema literal + on-disk JSON gain the three new names
- KeyswitchInjector kSustainedTechniques (4 -> 6) and
  kPerNoteTechniques (3 -> 4) extended in enum order

Mid-measure tempo (F9.6):
- TempoMapPanel gains a Beat picker per row that walks 1.0 ... N.875
  in 0.125 (32nd-note) steps. Changing the picker moves the row's
  override from oldTick to newTick, preserving BPM
- AppController gains setTempoOverride(measure, tick, bpm) and
  clearTempoOverride(measure, tick); setTempoForMeasure becomes a
  thin wrapper for the downbeat-only path
- MidiWriter::buildAbsoluteTempoMap resolves overrides via
  measureStartTicks(score) + tickWithinMeasure -> absolute MIDI tick

Documentation pipeline (F9.8):
- documentation/ reorganized into architecture / development /
  customer / agentic-context / api-reference per the
  documentation-and-plans rule
- Doxyfile emits XML only; build-api-docs.sh runs Doxygen ->
  Doxybook2 to produce Markdown under api-reference/ (gitignored)
- .forgejo/workflows/publish-docs.yaml builds the project, runs the
  API doc pipeline, and rsyncs every section into the project wiki
  on push to main
- New customer docs (getting-started, profile-binding) and
  development docs (building, testing, coding-rules, techniques,
  wishlist). Wishlist seeds from Phase 10 triage with explicit
  Future / Drop entries

Tests:
- 13 new test files: ProfileImportCollisionTests (5),
  ArticulationKeyswitchVelocityTests (3), ProfileArchiveTests (3),
  BundledVstPresetTests (3), plus extensions to existing files
  (ProjectMigrationTests gains chained migration + bundled v1->v2
  cases, MidiWriterTempoOverrideTests gains a mid-measure case,
  TechniqueCodingTests + TechniqueParserTests gain the three new
  Technique entries)
- 172 -> 192 tests, all pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- F10.1: Profile clipboard import/export
- F10.2: Per-file profile overrides (project
  schema v2→v3)
- F10.3: Velocity-curve editor
- F10.4: Multi-document spike (deferred)
- F10.5: Custom theming with 6 theme pairs,
  light/dark toggle, bundled Mulish + Hack fonts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- F11.1: Hand-rolled visual score renderer with
  noteheads, stems, beams, and staff lines
- F11.2: ScoreMerger utility + "Add MusicXML to
  Project" menu item
- F11.3: Slurs, ties, articulation marks, dynamics
- F11.4: Bravura SMuFL glyphs for music notation
- F11.5: Multi-part simultaneous rendering with
  system brackets
- F11.6: Persist merged MusicXML manifests in
  project (schema v3→v4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- F12.1: Legible score rendering geometry fixes
- F12.2: Score panel picker race condition fixes
- F12.3: Preserve mergedSources across Project
  mutations (all with* builders)
- F12.4: Tie matcher + layout helpers
- F12.5: Renderer + theming minor cleanup
- F12.6: Revert font multiplier + glyph anchors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the hand-rolled score renderer with Verovio
(LGPL v3 C++17 engraving library).

- F13.1: Vendor Verovio v6.1.0 via FetchContent
  with C wrapper API for C++20/17 boundary
- F13.2: Delete LayoutEngine + SmuflGlyphs
  (net -1500 LOC)
- F13.3: Rebuild ScoreRenderPanel on Verovio +
  juce::Drawable, with sanitizeForVerovio pre-pass
- F13.4: Drop bundled Bravura font
- F13.5: Documentation cleanup
- Fix: Verovio crash on GP MusicXML input

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expand techniques from 14 to 27 with a generic
variant/pickable keyswitch system, parser detection,
and bundled VST presets.

Technique System:
- Remove LetRing (no VST supports it)
- Add 13 new techniques: SemiClosedPalmMute,
  NaturalHarmonic, PinchHarmonic, FingerPluckOpen,
  FingerPluckDead, TremoloPick, PickScrape,
  ThumbSlap, Pop, PullOff, SlideInFromAbove,
  SlideInFromBelow, SlideOutDown, SlideOutUp
- Table-driven TechniqueCoding (single kTable
  replaces 8 parallel switch blocks)
- isUniversal() classification with editor toggle

Variant/Pickable Infrastructure:
- Profile: keyswitchTree, techniqueFlags,
  variantDefaults fields
- Injector: resolveKeyswitchNote tree walker with
  variant + pickable + flat-int fallback
- Pick direction latching with reset after directed
  passage

Parser Detection:
- Harmonics, tremolo, pluck, pull-off, text
  directions, down-bow/up-bow, directional slides,
  dead-note promotion

Bundled Presets:
- Shreddage 3.5, Submission Audio Basses, Odin III

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Verovio with an in-tree SMuFL-based SVG score renderer.

Renderer:
- New `LayoutEngine` computes staff/note positions from the
  MusicXML model with no external engraving dependency.
- New `SmuflGlyphs` resolves Bravura glyph codepoints + metrics.
- New `SvgScoreRenderer` walks the layout and emits a JUCE
  `Drawable` SVG, including accidentals and ledger lines.
- `ScoreRenderPanel` now drives the custom pipeline.

Verovio Removal:
- Drop the Verovio FetchContent block, target_compile_definitions
  for VEROVIO_RESOURCE_DIR, and the verovio link dependency.
- Delete `MusicXmlEngraver.{h,cpp}` and its tests
  (`MusicXmlEngraverTests.cpp`, `VerovioSmokeTests.cpp`).

Resources:
- Bundle Bravura.otf + LICENSE-Bravura.txt under
  `resources/fonts/` as binary data.

Tests:
- Add `LayoutEngineTests.cpp` and `SmuflGlyphsTests.cpp` covering
  baseline/staff geometry and glyph resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Profile Editor (Techniques Tab):
- Rebuild row emission as a flat pipeline driven by `rebuildEditor`
  with three section dividers (Picking, Per-Note, Span).
- Add mode ComboBox per technique (Single / Variant / Pickable)
  with conversion logic between int leaf and pickable object.
- Render variant children with tree-graph connectors (geometric
  lines, not unicode), editable child names via TextEditor, and
  a `+ Add child` row at the end of each variant.
- PickDirection special-cased: fixed DownPick / UpPick /
  AlternatePick rows + user-added editable rows.
- Fixed-width column layout (mode + default/keyswitch + clear)
  so columns align across rows regardless of indent.
- Bundled-profile immutability propagated to row-level controls:
  editable name TextEditors greyed out and AddChildRow hidden.
- Per-profile `showUnmapped` toggle persisted into profile JSON
  for user profiles and into AppPreferences (keyed by UUID) for
  bundled profiles. Defaults to true.
- Use string-key indirection (`form_.keyswitchTree[parent][key]`)
  in row callbacks so JSON inserts can't dangle references.

Profile Model:
- Add `showUnmapped` field to Profile + ProfileFormState with
  lenient deserialization (default true if absent from JSON).
- Round-trip the field through toFormState / applyProfileEdits /
  formStateToJson / formStateFromJson and ProfileStore::serializeToJson.
- ProfileValidator: bump kKnownTopLevelKeys 14 -> 15.

Technique Pipeline:
- Consolidate `Scoop` into `SlideInFromBelow` and `PalmMute` /
  `DeadMute` / `ClosedPalmMute` / `SemiClosedPalmMute` into
  `Muting` with variant children. Update bundled VST profiles
  (Odin III, Shreddage 3 Argent / Darkwall / Hydra / Serpent,
  Submission Audio Basses) accordingly.
- Add mute-variant fallback in keyswitch resolution.
- KeyswitchInjector: default keyswitch velocity 127, skip pitch
  0, proportional 10% durations for VST registration.

Bundled Presets:
- Profile shadowing: bundled UUID match deletes user-disk profile
  to prevent stale shadow content.

Documentation:
- Update `documentation/development/techniques.md` with the
  consolidated technique table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
App Preferences:
- Persist TableHeader column state and showBundledPresets via
  juce::PropertiesFile.
- Add per-UUID `showUnmapped` accessors so bundled (read-only)
  profiles remember the toggle individually.

Track List Panel:
- Reorder + width-configurable columns (saved to prefs).
- Octave offset bound to a text-input override surfaced from the
  AppController.
- Wire profile-list double-select + row-height fixes.

Main Window:
- Open Profiles Folder menu item.
- Title bar reflects project name + dirty marker; em-dashes routed
  through CharPointer_UTF8 so juce::String stops asserting.
- Replace/Add MusicXML diagnostics dialog titles use UTF-8.

Theming:
- AppLookAndFeel button contrast + scrollbar fixes.
- `ThemeColourIds`: switch unscoped enum to `enum : int` with
  `static_cast<int>(0x...u)` values so `findColour` no longer
  triggers `-Wsign-conversion` at every site.
- Drop deprecated `juce::Font(...)` constructors in favour of
  `juce::Font(juce::FontOptions {...})`.

UnknownTechniquesBanner:
- Tighten layout + delete dead `kBannerHeight` constant.

TempoMapPanel:
- Drop names of unused `width`/`height` params on
  `paintRowBackground`.

ProfileEditorPanel (cleanup only — feature work in prior commit):
- Cast `profiles_[int]` indexers to `std::size_t` (4 sites).
- Rename shadowed lambda capture in `launchKeyswitchPicker`.
- Remove the verbose `DBG()` block from `selectedRowsChanged`.

Documentation:
- LICENSE (PolyForm-NC) + README updates.
- PROJECT_CONTEXT.md notes for the Phase 15 work.
- Stage seven `implementation-plans/` plan files documenting the
  editor rebuild, technique consolidation, MIDI completeness, and
  build-cleanup efforts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Export-8 bundle:
- New Odin presets and updated Shreddage 3 profiles to match the
  refreshed library shape
- Score merger, technique coding, and pulloff pitch detection
  adjustments tracked under
  documentation/agentic-context/implementation-plans/2026-05-04-odin-export-8-fix-bundle.md
  and 2026-05-05-pulloff-pitch-detection.md
- New integration test (tests/OdinTechniquesIntegrationTests.cpp)
  with fixtures/odin-techniques.xml

Profile-level octave shift:
- octaveOffset added to Profile, ProfileFormState, profile JSON
  schema, and the ProfileValidator known-keys list
- OctaveTransposer takes std::map<juce::Uuid,int> effectiveOctave
  built by PipelineOrchestration (per-part override beats profile
  default beats 0)
- ProfileEditorPanel main mode gets an Octave Shift combo wired to
  form_.octaveOffset
- Tests updated to construct the new map directly; 3 new
  PipelineOrchestrationTests cases cover the resolution order

Override editor:
- ProfileEditorPanel override-mode constructor drops name /
  category / drum toggles and surfaces just an Octave Shift combo
  that commits immediately via setOctaveOffsetForPart
- hasUnsavedChanges + saveAndClose route through a baseline form
  snapshot so the dialog can prompt Save/Discard/Cancel on close
- TrackListPanel uses a file-local OverrideDialog subclass with
  closeButtonPressed and a keyPressed override that routes Escape
  through the prompt
- AppController::setProfileOverrideForPart strips form.name and
  form.category before persisting

Plans:
- documentation/agentic-context/implementation-plans/2026-05-06-future-work-native-guitar-pro-import.md
- documentation/agentic-context/implementation-plans/2026-05-06-phase-16-followups-override-editor.md
Native source-file support:
- ScoreImporter dispatcher picks importer by extension
  (.musicxml / .xml -> MusicXmlParser, .gp -> GpFileParser,
  .gpx -> GpxFileParser). All three feed the shared
  GpifReader -> IntermediateScore -> ScoreBuilder pipeline.
- GpFileParser opens .gp ZIP archives and extracts
  Content/score.gpif.
- GpxFileParser decodes the BCFZ-compressed BCFS sector
  filesystem (algorithm ported from alphaTab under MPL-2.0
  attribution) and extracts the inner score.gpif.
- Slide-flag detection: bits 0x40 / 0x80 = pick scrape, with
  FingerPluckDead suppression on those notes (GP's playback
  hack would otherwise route them to the dead-mute keyswitch).
- Tuning capture survives both GP6 and GP7/8 layouts.

Release tooling:
- scripts/build-release.sh + .ps1 wrap CMake + CPack on each
  host platform.
- scripts/clean.sh wipes build artefacts (build/, dist/,
  generated icons, .DS_Store) without touching tracked files.
- scripts/generate-icons.sh rasterises the SVG icon source to
  a 1024 PNG (and, on macOS, a multi-resolution .icns for the
  DMG volume icon).
- CMakeLists.txt: JUCE 8.0.13 (was 8.0.4), CPack DragNDrop on
  macOS / ZIP on Windows portable layout, ICON_BIG generated
  from resources/icons/guitar-prometheus.svg, json-schema-
  validator dropped (unused).
- Util::getPersistenceRoot picks the per-platform config
  directory (Windows: exe parent for portable mode; macOS /
  Linux: app-support).

App icon:
- resources/icons/guitar-prometheus.svg from svgrepo.com
  (Public Domain). Generated PNG / .icns / .iconset live
  under build/generated-icons (gitignored).

Odin profile:
- Restore "PickScrape": 19 (was accidentally dropped during
  the Phase 16 profile-shape overhaul).
Parser walker classes:
- MusicXmlParser::parseLoadedDocument split into PartWalker
  (measure-spanning state) + MeasureBodyWalker (within-
  measure cursor + spans). Orchestrator down to ~50 lines.
- GpifReader::read split into GpifIndex + readMasterBars +
  GpifTempoReader + GpifTrackReader + GpifBeatWalker. Read
  orchestrator down to ~25 lines.
- Shared ParserUtilities consolidates readIntChild and
  kSafeXmlParseFlags (drop legacy ParserSafety.h).
- Out-parameter purge: midiToPitch -> DecodedPitch,
  applySlideFlags -> decodeSlideFlags -> DecodedSlideFlags,
  parseGpPiTechniques / collectGpPiTechniques -> GpPiResult.
  TechniqueSet gains unionWith for the recursive PI walker.
- XML recursion-depth guard (256 levels) wraps every loaded
  doc; depth-bomb payloads surface a Diagnostic.

Humanizer:
- Profile gains humanizerEnabled + HumanizerConfig settings
  with full JSON round-trip (schema + ProfileStore +
  ProfileValidator + ProfileEdits).
- ProfileEditorPanel exposes the toggle + sigma / floor /
  ceiling / jitter / seed widgets in both profile mode and
  the per-part override editor. AppController::
  setHumanizerForPart mirrors the octave-offset path.
- PipelineOrchestration overlays profile humanizer settings
  onto explicit per-part overrides; Humanizer::apply stays
  a pure no-op when neither is configured.

Technique enrichment:
- AutoSlide added to the Technique enum + coding table
  (kNumTechniques 23 -> 24); restored to the Odin profile.
- BendPoint / BendCurve model + per-Engraving optional
  bendCurve_; GpifReader consolidates GP's seven Bend* /
  Offset properties into the curve.
- OctaveCorrectionMode { Manual, Inferred }; inferOctaveOffset
  derives the offset from tuning + per-note string/fret data.
- AutoPowerChord documented as a permanent non-feature
  (GP exports never carry enough info to disambiguate).

GP project round-trip:
- IScoreImporter gains importMemory(bytes, size) with a
  temp-file default; MusicXmlParser overrides to route
  through parse(string_view) directly.
- ScoreImporter::importMemory(bytes, size, SourceFormat)
  dispatches by format.
- ProjectStore::materializeScore routes every SourceFormat
  through the new memory path. Replaces the previous
  "MusicXml only" guard.

Test infrastructure:
- tests/support/{Builders,XmlFixtures} consolidates 7+
  duplicated builder copies + fixture loader.
- Multi-line inline XML literals migrated to tests/fixtures.
- New error-path tests across all three parsers (15 cases),
  inferred-octave tests, GP project round-trip tests,
  Humanizer mean-velocity property test, malicious XML
  depth-bomb test.
- Scoped -Werror=switch-enum re-enabled on our test sources
  (JUCE bridges unaffected).

295 -> 318 tests; all pass.
Root + hub:
- README.md: status updated through Phase 24, .gp / .gpx
  formats called out, macOS + Windows requirements,
  tech-stack mini-section, licence line clarified.
- documentation/README.md adds entries for releases,
  building-windows, code-review-standards, native-import,
  and the new implementation-plans index.

Architecture overview:
- Mermaid (adaptive pastel) diagrams for the import pipeline
  (sources -> dispatcher -> parsers -> GpifReader -> IR ->
  builder -> Score) and the export pipeline (inject ->
  transpose -> humanize -> write).
- Domain sections refreshed for walker-class splits, the
  PersistenceRoot helper, and the humanizer plumbing.

New customer + development docs:
- customer/native-import.md: GP / GPx workflow, supported
  technique table, BCFZ security cap, round-trip status.
- development/releases.md: build-release scripts, CPack
  generators, smoke procedure.
- development/code-review-standards.md: Phase 19 / 20
  standards (constructor-sink, out-param purge, public-API
  test assertions).
- development/building-windows.md: MSVC + CMake instructions,
  portable-mode ZIP layout.

Agentic context:
- implementation-plans/README.md indexes every phase plan.
- 2026-05-19-deferred-work-consolidation.md captures the
  Phase 19-24 omnibus plan that drove the refactor.
- 2026-05-19-cleanup-icon-3-commit-squash.md captures the
  cleanup + squash plan executed in this branch.
- PROJECT_CONTEXT.md refresh: status table walked through
  Phases 17 / 18 / 19 (humanizer + walker classes) /
  20 (test infra) / 22 / 23. Permanent non-feature note
  for AutoPowerChord.

API reference:
- documentation/api-reference/README.md explains the
  Doxygen + doxybook2 generation step.
fragmented merged commit 5054764452 into mainline 2026-05-20 04:09:06 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
tools/guitar-prometheus!1
No description provided.