Merge _configs/ into config/ for single configuration directory. Update all path references. Changes: - Move _configs/* to config/ - Update .gitignore for new patterns - No code references to _configs/ found Impact: -1 root directory (layout_conventions.md compliance)
14 KiB
ADR-001: Wrapper Architecture Strategy
Date: 2025-11-17 Status: PROPOSED Author: Architecture Review Scope: Installation and execution wrappers in SYNTAXIS
Problem Statement
SYNTAXIS uses two different wrapper patterns:
-
Binary Execution Wrappers (existing
install-syntaxis):- Bash → NuShell → Rust (3 layers)
- Handles config discovery and injection
- Used by:
syntaxis-cli,syntaxis-tui,syntaxis-api
-
Installation Wrapper (new
provision.sh):- Bash orchestrator
- Wraps
install.sh+setup-config.sh - Used by: Bundle users during first-time installation
Question: Should both patterns be used? Or should they follow different strategies?
Analysis Framework
Cost-Benefit Model for Wrappers
Wrapper Value = Frequency × (User Burden Saved) × Maintenance Complexity
Where:
- Frequency: How often is this called? (per session, per day, per lifetime)
- User Burden: What pain does it eliminate? (typing, remembering steps, errors)
- Complexity: How many layers/moving parts? (1 layer vs 3 layers)
Pattern 1: Binary Execution Wrappers (install-syntaxis)
Current Implementation
Shell Invocation: syntaxis-cli status
↓
[Layer 1] Bash Wrapper (~212 bytes)
└─ Sets: NU_LIB_DIRS="$HOME/.config/syntaxis/scripts"
└─ Calls: nu "$HOME/.config/syntaxis/scripts/syntaxis-cli.nu" "status"
↓
[Layer 2] NuShell Wrapper (~2.2 KB)
└─ Imports: syntaxis-lib.nu, syntaxis-cli-lib.nu
└─ Discovers: Config file location
└─ Injects: --config flag automatically
└─ Calls: ~/.cargo/bin/syntaxis-cli.real --config ~/.config/syntaxis/config.toml status
↓
[Layer 3] Real Binary (7.2 MB)
└─ Executes: With config path already provided
Why This Pattern?
Without wrapper:
# Every single invocation requires:
$ syntaxis-cli --config ~/.config/syntaxis/config.toml status
$ syntaxis-cli --config ~/.config/syntaxis/config.toml project list
$ syntaxis-cli --config ~/.config/syntaxis/config.toml task create "name"
# ... repeated 100s of times per day
User Experience Problem:
- ❌ Users must remember config path
- ❌ Config path is long and error-prone
- ❌ Repetitive typing (every invocation)
- ❌ Non-obvious that
--configis required
With wrapper:
# Clean, simple invocations:
$ syntaxis-cli status
$ syntaxis-cli project list
$ syntaxis-cli task create "name"
# Config is automatic!
User Experience Solution:
- ✅ Config path is invisible
- ✅ Users follow Unix conventions
- ✅ No typing overhead
- ✅ Feels like a normal CLI tool
Wrapper Value Calculation
Frequency: ~100 invocations per active session
= Multiple times per hour, thousands per week
User Burden Saved:
- Per invocation: ~50 characters of typing
- Per session: ~5,000 characters typed
- Per year: ~2.6 million characters saved
- Mental load: No need to remember config path
Maintenance Complexity: 3 layers
- But: Stable (rarely changed)
- And: All in scripts (easy to modify)
- Risk: Moderate (can bypass via .real)
VERDICT: Value >> Complexity Cost
RECOMMENDATION: KEEP THIS PATTERN ✅
Existing File Locations
Installation Script:
scripts/install-syntaxis (bash wrapper)
scripts/install-syntaxis.nu (NuShell orchestrator)
Deployed Wrappers:
~/.cargo/bin/syntaxis-cli (bash wrapper)
~/.cargo/bin/syntaxis-tui (bash wrapper)
~/.cargo/bin/syntaxis-api (bash wrapper)
NuShell Wrappers:
~/.config/syntaxis/scripts/syntaxis-cli.nu (2.2 KB)
~/.config/syntaxis/scripts/syntaxis-tui.nu (2.2 KB)
~/.config/syntaxis/scripts/syntaxis-api.nu (3.6 KB)
Shared Libraries:
~/.config/syntaxis/scripts/syntaxis-lib.nu (1.1 KB)
~/.config/syntaxis/scripts/syntaxis-cli-lib.nu (1.5 KB)
~/.config/syntaxis/scripts/syntaxis-tui-lib.nu (743 B)
~/.config/syntaxis/scripts/syntaxis-api-lib.nu (2.2 KB)
Real Binaries:
~/.cargo/bin/syntaxis-cli.real (7.2 MB)
~/.cargo/bin/syntaxis-tui.real (3.9 MB)
~/.cargo/bin/syntaxis-api.real (8.3 MB)
Pattern 2: Installation Wrapper (provision.sh)
Current Implementation
User: ./provision full
↓
[Layer 1] Bash Wrapper (provision.sh)
└─ Detects: Shell availability (bash/zsh/NuShell)
└─ Offers: Interactive menu OR one-step install
└─ Orchestrates: install.sh → setup-config.sh
└─ Calls: Either install or install + config
↓
[Layer 2A] Install Script (install.sh)
└─ Installs: Binaries to ~/.local/bin/
└─ Updates: Shell PATH
└─ Creates: Data directories
↓
[Layer 2B] Config Script (setup-config.sh)
└─ Creates: TOML configs
└─ Interactive: Prompts user for options
└─ Deploys: Configs to ~/.config/syntaxis/
Why This Pattern Was Created?
Intended Value:
- Make installation easier for beginners
- One command instead of two
- Guided interactive experience
Reality Check:
- Users run installer: Once (during first installation)
- Users run binaries: 100s of times (during normal use)
Wrapper Value Calculation
Frequency: ~1 invocation per user
= Only once during initial setup
= Not multiple times per session
User Burden Saved:
- Per user: Saves 1 command typing (~50 characters)
- Per year: ~50 characters saved (one time!)
- Mental load: Not a problem (one-time activity)
Maintenance Complexity: 2+ layers
- Scripts call other scripts
- PATH updates, permission handling, error recovery
- Three different code paths (install only, config only, both)
- Need to handle shell detection and options
User Confusion:
- "Which command should I use?"
- install.sh or provision.sh?
- provision install vs provision config vs provision full?
- Documentation burden (explain 3 paths)
VERDICT: Value << Complexity Cost
RECOMMENDATION: REMOVE THIS PATTERN ❌
Why This Pattern Is Problematic
1. One-Time Usage
- Wrapper is called once during entire user lifetime
- Binary wrappers are called 100s of times per day
- One-time benefit doesn't justify ongoing complexity
2. Confusion
Bundle contains:
provision.sh ← Is this the way?
install.sh ← Or this?
setup-config.sh ← Or this?
Users think: "Which command do I run?"
"What's the difference?"
"Which one is recommended?"
Documentation Burden: Must explain 3 approaches instead of 1
3. Not Optional = Misleading
Current Status:
provision.sh is marked "optional"
BUT it's included in bundle
BUT its necessity is unclear
BUT install.sh works independently
Result: Confusing, not truly optional
4. Maintenance Burden
Code Paths to Test:
Path 1: provision full (both steps)
Path 2: provision install (binaries only)
Path 3: provision config (config only)
Path 4: bash install.sh (direct script)
Path 5: bash setup-config.sh (direct script)
Path 6: Manual (copy files) (user DIY)
Testing Matrix:
× 3 shells (bash, zsh, NuShell)
× 4 platforms (Linux, macOS x2, Windows)
× Multiple scenarios
= Significant test burden for one-time use
5. Violates SYNTAXIS Philosophy
From CLAUDE.md:
- ✅ "Modular with features to add or avoid"
- ✅ "No unnecessary complexity"
- ✅ "Users understand what's happening"
Wrapper violates:
- ❌ Not modular (orchestrates other scripts)
- ❌ Unnecessary complexity (for one-time use)
- ❌ Less transparent (abstraction not needed)
Comparison: Binary Wrappers vs Installation Wrapper
| Aspect | Binary Wrappers | Installation Wrapper |
|---|---|---|
| Frequency | 100s per day | Once per lifetime |
| User Pain | Config path typing (repetitive) | Two commands to type (one-time) |
| Value/Complexity | High | Low |
| Justification | Strong | Weak |
| Maintenance | Stable/low | Medium/scattered |
| Transparency | Can bypass via .real | No escape hatch |
| Philosophy | Transparent execution | Abstraction |
Decision Criteria
When Wrappers Are Justified
✅ Wrappers are justified when:
Frequency × User Burden > Maintenance Complexity Cost
Example: Binary wrappers
- Frequency: 100s/day
- User Burden: 50 chars/invocation
- Result: Worth the 3 layers
When Wrappers Are NOT Justified
❌ Wrappers are NOT justified when:
Frequency × User Burden < Maintenance Complexity Cost
Example: Installation wrapper
- Frequency: 1x lifetime
- User Burden: 50 chars once
- Result: Not worth the orchestration
Recommendation
For Binary Execution: KEEP Wrapper Pattern
Decision: Keep install-syntaxis wrapper pattern (Bash → NuShell → Rust)
Rationale:
- ✅ Users don't want to type
--config path100s of times per day - ✅ Config discovery is complex (multiple locations, precedence)
- ✅ Pattern is battle-tested and stable
- ✅ Complexity is justified by frequency of use
- ✅ Transparent to end users (no perceived overhead)
Action Items: None - keep as-is
For Installation: REMOVE Wrapper Pattern
Decision: Remove provision.sh wrapper, use direct scripts only
Rationale:
- ❌ Users call installer once, not repeatedly
- ❌ Wrapper adds unnecessary orchestration
- ❌ Two clear scripts are simpler than three options
- ❌ Violates "no unnecessary complexity" principle
- ❌ Maintenance burden not justified for one-time use
Better Approach:
# Bundle contains only:
install.sh ← Install binaries
setup-config.sh ← Configure
# Users follow two-step process:
bash install.sh --interactive
bash setup-config.sh --interactive
# Simple, clear, transparent, Unix-way
Benefits:
- ✅ No confusion (only 1 recommended path)
- ✅ Less code to maintain
- ✅ Clear sequence (install → config)
- ✅ User understands each step
- ✅ Easy to debug (direct scripts)
- ✅ Follows Unix philosophy (one tool per job)
Action Items:
- Remove
scripts/provisioning/provision.sh - Remove from
configs/provisioning.tomlartifacts.scripts array - Update
docs/installation.mdto show two-step process - Update
BUNDLE_README.mdto show simple sequence
Trade-offs
Keeping Binary Wrappers
Pros:
- Excellent user experience (no --config needed)
- Justified by frequency (100s per day)
- Config discovery is complex (needs orchestration)
- Transparent (users don't notice)
Cons:
- More complex architecture
- Requires both bash and NuShell
- Adds startup overhead (~50ms)
- More moving parts to understand
Removing Installation Wrapper
Pros:
- Simpler (fewer scripts)
- Clearer (no menu confusion)
- More transparent (users understand each step)
- Lower maintenance burden
Cons:
- Two commands instead of one
- Users might forget second step (but unlikely - one-time setup)
- No guided experience (but not needed - simple process)
Implementation
Phase 1: Decision
Status: APPROVED
Phase 2: Code Cleanup
# Remove wrapper
rm scripts/provisioning/provision.sh
# Update config
# Edit: configs/provisioning.toml
# Remove: "scripts/provisioning/provision.sh" from artifacts.scripts array
# Update documentation
# Edit: docs/docs/installation.md - show two-step process
# Edit: docs/BUNDLE_README.md - update quick start
# Delete: docs/BUNDLE_DOCS_GUIDE.md references to provision
Phase 3: Update Bundle
# Rebuild bundle
nu scripts/provisioning/pack.nu
# Verify changes
tar -tzf dist/syntaxis-v0.1.0-*.tar.gz | grep -E '(install|setup|provision)'
# Should show only: install.sh, install.nu, setup-config.sh
# Should NOT show: provision.sh
Phase 4: Verification
# Test installation workflow
tar -xzf dist/syntaxis-v0.1.0-*.tar.gz
cd syntaxis-v0.1.0-*
# Step 1: Install
bash install.sh --interactive
# Step 2: Configure
bash setup-config.sh --interactive
# Verify
syntaxis-cli --version
Risk Assessment
Low Risk
- ✅ Removing optional wrapper
- ✅ Direct scripts are proven to work
- ✅ No impact on binary wrappers
- ✅ Clear documentation available
Mitigation
- Document two-step process clearly
- Include both scripts in bundle (proven, working)
- Test complete installation flow
- Update all user-facing documentation
Future Considerations
If Installation Complexity Grows
If future bundle setups become complex:
- Consider wrapper again
- But only if frequency or complexity increases
- Apply same cost-benefit analysis
Wrapper Pattern Elsewhere
If other tools need wrappers:
- Apply same framework
- Justify by frequency × burden vs complexity
- Transparency is key
Appendix: Real-World Example
Scenario: New User Installs SYNTAXIS
With Wrapper (Current):
$ cd /tmp/bundle
$ ./provision full
# Menu appears, user selects options
# Both install and configure run
# Done!
Without Wrapper (Proposed):
$ cd /tmp/bundle
$ bash install.sh --interactive
# User prompted for installation options
# Binaries installed
# Done with step 1
$ bash setup-config.sh --interactive
# User prompted for configuration
# Configs created
# Done with step 2
Difference:
- Wrapper: ~5 seconds (one command, orchestrated)
- Direct: ~10 seconds (two commands, sequential)
- Complexity added: ~5 seconds saved, once per lifetime
- Complexity removed: Eliminates 100+ lines of code, maintenance
Clear winner: Remove wrapper
Conclusion
Binary Execution Wrappers: KEEP ✅
- Value: Users invoke 100s of times per day
- Problem Solved: Automatic config injection (major UX improvement)
- Complexity: Justified and battle-tested
Installation Wrapper: REMOVE ❌
- Value: Users invoke once per lifetime
- Problem Solved: Saves one command (minor UX improvement)
- Complexity: Not justified by benefit
Philosophy: Wrappers should enable frequent workflows, not orchestrate one-time procedures.
Recommendation: Implement Phase 2-4 cleanup to simplify bundle provisioning system.
Status: READY FOR DECISION