Skip to content

AST Injection Issues - Troubleshooting Guide

AST Injection Issues - Troubleshooting Guide

Last Updated: February 28, 2026 Status: Production Reference Audience: CLI developers and users debugging injection failures


Table of Contents

  1. Understanding AST Injection
  2. Diagnostic Flowchart
  3. Common Failure Scenarios
  4. Syntax Error Debugging
  5. Architectural Violations
  6. Debugging Techniques
  7. Real-World Examples
  8. Prevention & Best Practices

Understanding AST Injection

What is AST Injection?

AST (Abstract Syntax Tree) injection is the process of programmatically modifying code by:

  1. Parsing the target file into a syntax tree
  2. Locating injection points (markers or structural patterns)
  3. Transforming the AST by adding/modifying nodes
  4. Generating valid code from the modified AST
  5. Writing the result back to the file

Why AST vs String Replacement?

# ❌ String replacement (fragile)
code = code.replace("router = APIRouter()",
"router = APIRouter(\nrouter.include_router(commerce)")
# Result: Syntax error (unclosed parenthesis, wrong indentation)
# ✅ AST manipulation (type-safe)
tree = ast.parse(code)
tree.body.append(ast.Expr(
value=ast.Call(
func=ast.Attribute(value=ast.Name(id='router'), attr='include_router'),
args=[ast.Name(id='commerce_router')],
keywords=[]
)
))
# Result: Valid Python syntax guaranteed

Benefits:

  • Syntax correctness guaranteed
  • Preserves formatting and comments (in most cases)
  • Idempotent (can detect if already injected)
  • Handles edge cases (multiline, type hints, complex nesting)

Tools We Use

  • Python: Built-in ast module for Python files
  • ast-grep: For Astro/JavaScript/TypeScript files
  • Synvert: For Ruby/Rails files (via SynvertAdapter)
  • Fallback: String markers when AST fails

Diagnostic Flowchart

┌─────────────────────────────────────┐
│ Does `sscli inject` complete? │
└──────────────┬──────────────────────┘
┌─────┴─────┐
NO YES
│ │
├─> See: "Command Failures"
└─> Are files created in features/?
┌───────┴────────┐
NO YES
│ │
├─> See: "Files Not Created"
└─> Are imports added?
┌───────┴────────┐
NO YES
│ │
├─> See: "Import Injection Failed"
└─> Are routes registered?
┌───────┴────────┐
NO YES
│ │
├─> See: "Route Registration Failed"
└─> Does feature work?
┌───────┴────────┐
NO YES
│ │
├─> See: "Runtime Errors"
└─> ✅ Success!

Common Failure Scenarios

1. AST Parsing Failed - Invalid Syntax

Error Message:

AstGrepError: ast-grep is not installed.
Install via: brew install ast-grep or cargo install ast-grep-cli

Or:

Error: AST parsing failed: invalid syntax (routes.py, line 42)
SyntaxError: unexpected indent

Cause: Target file has syntax errors before injection

Diagnosis:

Terminal window
# Check Python syntax
python -m py_compile src/ui/routes/api.py
# Check JavaScript/Astro syntax
npx eslint src/pages/index.astro --no-ignore
# Check Ruby syntax
ruby -c config/routes.rb

Solution:

Terminal window
# Fix syntax errors first
# Common issues:
# - Missing colons after function definitions (Python)
# - Unclosed brackets/parentheses
# - Wrong indentation (tabs/spaces mix)
# - Missing semicolons (JavaScript)
# Example: Fix Python indentation
# Before (❌):
def my_function():
return "value" # Wrong indentation
# After (✅):
def my_function():
return "value" # Correct indentation
# Retry injection after fixing
sscli inject commerce

2. Injection Markers Missing

Error Message:

Warning: No injection markers found in src/ui/routes/api.py
Skipping import injection

Cause: Target file doesn’t contain # SSCLI: markers

Diagnosis:

Terminal window
# Check for markers
grep "# SSCLI:" src/ui/routes/api.py

Solution - Add Markers Manually:

src/ui/routes/api.py
from fastapi import APIRouter
# SSCLI:IMPORTS:START
# Feature imports will be injected here
# SSCLI:IMPORTS:END
router = APIRouter()
# SSCLI:ROUTES:START
# Feature route registrations will be injected here
# SSCLI:ROUTES:END

For Astro Templates:

---
// SSCLI:IMPORTS:START
// Component imports injected here
// SSCLI:IMPORTS:END
const title = "My Page";
---
<main>
{/* SSCLI:COMPONENTS:START */}
{/* Components injected here */}
{/* SSCLI:COMPONENTS:END */}
</main>

For Rails Routes:

config/routes.rb
Rails.application.routes.draw do
# SSCLI:ROUTES:START
# Feature routes injected here
# SSCLI:ROUTES:END
root to: "home#index"
end

3. File Not Writable

Error Message:

PermissionError: [Errno 13] Permission denied: 'src/ui/routes/api.py'

Diagnosis:

Terminal window
# Check file permissions
ls -l src/ui/routes/api.py
# Should show: -rw-r--r-- (readable and writable by owner)
# Check if file is open in another program
lsof src/ui/routes/api.py

Solution:

Terminal window
# Fix permissions
chmod 644 src/ui/routes/api.py
# If file is locked by Git
git status
# If showing merge conflicts, resolve them first
# If opened in editor, save and close

4. AST Variable Substitution Failed

Error Message:

Warning: Variable placeholder $$$PRICING_COMPONENT not found in target
Injection may be incomplete

Cause: Mismatch between variable name in rule and in code

Example - ast-grep Rule:

inject-commerce.yml
id: astro-inject-pricing
fix: |
$$$PRICING_COMPONENT # Variable expected here

Python Caller:

# ❌ Wrong: Variable name mismatch
runner.inject_rule(
target_path=path / "index.astro",
rule_name="astro-inject-pricing",
content_vars={"PRICING_COMP": "<PricingTiers />"} # Wrong name!
)
# ✅ Correct: Variable names match
runner.inject_rule(
target_path=path / "index.astro",
rule_name="astro-inject-pricing",
content_vars={"PRICING_COMPONENT": "<PricingTiers />"}
)

Diagnosis:

Terminal window
# Check rule file for variable names
cat foundry/rules/astro-injections/inject-commerce.yml | grep '\$\$\$'
# Check Python code for content_vars
grep -A 5 "inject_rule.*commerce" foundry/actions/features.py

5. Idempotency Check Failure

Symptom: Running injection twice creates duplicate code

Example - Duplicate Route Registration:

# After running `sscli inject commerce` twice:
router = APIRouter()
router.include_router(commerce_router, prefix="/commerce")
router.include_router(commerce_router, prefix="/commerce") # ❌ Duplicate!

Cause: Injection doesn’t check if already applied

Solution - Add Idempotency Check:

def inject_commerce_routes(tree: ast.AST, feature: str) -> bool:
"""
Inject commerce routes with idempotency check.
Returns:
True if injection performed, False if already exists.
"""
# Check if already injected
for node in ast.walk(tree):
if isinstance(node, ast.Call):
if (hasattr(node.func, 'attr') and
node.func.attr == 'include_router' and
any(arg.id == 'commerce_router' for arg in node.args if isinstance(arg, ast.Name))):
console.print(f"[yellow]⚠ {feature} already injected, skipping[/yellow]")
return False
# Not found, proceed with injection
registration = ast.Expr(
value=ast.Call(
func=ast.Attribute(
value=ast.Name(id='router', ctx=ast.Load()),
attr='include_router',
ctx=ast.Load()
),
args=[ast.Name(id='commerce_router', ctx=ast.Load())],
keywords=[
ast.keyword(arg='prefix', value=ast.Constant(value='/commerce'))
]
)
)
tree.body.append(registration)
return True

For ast-grep: Use pattern matching to detect existing code:

id: astro-verify-commerce-injection
rule:
pattern: |
<PricingComponent />

6. Malformed AST Transformation

Symptom: Generated code has syntax errors after injection

Example - Missing Context:

# ❌ Wrong: Missing ctx parameter
ast.Name(id='router') # Missing ctx=ast.Load()
# ✅ Correct:
ast.Name(id='router', ctx=ast.Load())

Example - Missing Keywords in Call:

# ❌ Wrong: Prefix won't work
ast.Call(
func=...,
args=[ast.Name(id='commerce_router')],
keywords=[] # Missing prefix!
)
# ✅ Correct:
ast.Call(
func=...,
args=[ast.Name(id='commerce_router')],
keywords=[
ast.keyword(arg='prefix', value=ast.Constant(value='/commerce'))
]
)

Debugging:

# Validate generated code before writing
try:
new_code = ast.unparse(tree)
ast.parse(new_code) # Validate syntax
console.print("[green]✓ Generated code is valid[/green]")
except SyntaxError as e:
console.print(f"[red]✗ Generated invalid code: {e}[/red]")
raise

Syntax Error Debugging

When injection produces syntax errors, narrow down the problem:

Terminal window
# 1. Save a backup
cp src/ui/routes/api.py src/ui/routes/api.py.backup
# 2. Run injection
sscli inject commerce
# 3. Check syntax
python -m py_compile src/ui/routes/api.py
# 4. If error, compare
diff src/ui/routes/api.py.backup src/ui/routes/api.py
# 5. Analyze the diff to find the problem line

Technique 2: AST Dump Inspection

Inspect the AST structure before/after:

import ast
# Before injection
original_code = """
router = APIRouter()
"""
tree_before = ast.parse(original_code)
print(ast.dump(tree_before, indent=2))
# After injection
# ... perform injection ...
tree_after = ast.parse(modified_code)
print(ast.dump(tree_after, indent=2))

Technique 3: Incremental Validation

Validate at each step:

def safe_inject_feature(filepath: str, feature: str):
"""Inject feature with validation at each step."""
try:
# Step 1: Parse
with open(filepath) as f:
code = f.read()
tree = ast.parse(code)
console.print("[green]✓ Step 1: Parse successful[/green]")
# Step 2: Check if already injected
if is_already_injected(tree, feature):
console.print(f"[yellow]⚠ {feature} already injected[/yellow]")
return
console.print("[green]✓ Step 2: Idempotency check passed[/green]")
# Step 3: Transform AST
tree = add_feature_imports(tree, feature)
console.print("[green]✓ Step 3: Imports added[/green]")
tree = add_feature_registration(tree, feature)
console.print("[green]✓ Step 4: Routes added[/green]")
# Step 4: Generate code
new_code = ast.unparse(tree)
console.print("[green]✓ Step 5: Code generation successful[/green]")
# Step 5: Validate syntax
ast.parse(new_code)
console.print("[green]✓ Step 6: Syntax validation passed[/green]")
# Step 6: Write back
with open(filepath, 'w') as f:
f.write(new_code)
console.print(f"[green]✓ {feature} injected successfully[/green]")
except SyntaxError as e:
console.print(f"[red]✗ Syntax error: {e}[/red]")
console.print(f"[red] Line {e.lineno}: {e.text}[/red]")
raise InjectionError("Target file has syntax errors")
except Exception as e:
console.print(f"[red]✗ Injection failed: {str(e)}[/red]")
raise

Architectural Violations

Hexagonal Architecture Rules (Python SaaS)

Core Principle: Dependencies flow inward: UI → Use Cases → Entities

Common Violations:

Violation 1: Entity Depending on Infrastructure

features/commerce/src/entities/payment.py
# ❌ WRONG: Entity importing from infrastructure
from features.commerce.src.infrastructure.stripe_client import StripeClient
class Payment:
def process(self):
client = StripeClient() # ❌ Entity depends on infrastructure!
return client.charge()

Fix:

features/commerce/src/entities/payment.py
# ✅ CORRECT: Entity remains pure
from decimal import Decimal
class Payment:
def __init__(self, amount: Decimal, currency: str):
self.amount = amount
self.currency = currency
self.status = "pending"
def mark_completed(self):
self.status = "completed"
# Infrastructure adapter handles external dependencies
# File: features/commerce/src/infrastructure/payment_gateway.py
from features.commerce.src.entities.payment import Payment
class StripePaymentGateway:
def process_payment(self, payment: Payment) -> bool:
# Stripe client lives here, not in entity
return self._stripe_client.charge(payment.amount)

Violation 2: Use Case Depending on UI

features/commerce/src/use_cases/process_payment.py
# ❌ WRONG: Use case importing from routes
from src.ui.routes.api import get_current_user # ❌ Wrong direction!
def process_payment(payment_data: dict):
user = get_current_user() # ❌ Use case depends on UI!

Fix:

features/commerce/src/use_cases/process_payment.py
# ✅ CORRECT: Pass user as parameter
from features.commerce.src.entities.payment import Payment
def process_payment(payment_data: dict, user_id: str) -> Payment:
"""
Process payment for a user.
Args:
payment_data: Payment details
user_id: ID of user (passed from UI layer)
"""
payment = Payment(**payment_data)
# Business logic here
return payment
# UI layer passes user_id
# File: src/ui/routes/commerce.py
@router.post("/payments")
async def create_payment(
payment: PaymentRequest,
current_user: User = Depends(get_current_user)
):
result = process_payment(
payment.dict(),
user_id=current_user.id # ✅ UI provides user_id
)
return result

Violation 3: Circular Dependencies

features/commerce/src/use_cases/order.py
# ❌ WRONG: Circular import
from features.commerce.src.use_cases.payment import process_payment
def create_order(order_data: dict):
payment_result = process_payment(order_data["payment"])
# File: features/commerce/src/use_cases/payment.py
from features.commerce.src.use_cases.order import create_order # ❌ Circular!
def process_payment(payment_data: dict):
if payment_data["type"] == "order":
create_order(payment_data) # ❌ Circular dependency!

Fix:

features/commerce/src/entities/interfaces.py
# ✅ CORRECT: Introduce shared interface
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process(self, data: dict): pass
# File: features/commerce/src/use_cases/order.py
from features.commerce.src.entities.interfaces import PaymentProcessor
def create_order(order_data: dict, payment_processor: PaymentProcessor):
payment_processor.process(order_data["payment"]) # ✅ Dependency injected
# File: features/commerce/src/use_cases/payment.py
from features.commerce.src.entities.interfaces import PaymentProcessor
class PaymentService(PaymentProcessor):
def process(self, data: dict):
# Payment logic
pass

Detection Script

def validate_hexagonal_architecture(project_path: Path):
"""
Validate that injected code follows hexagonal architecture.
Rules:
- Entities must not import from use_cases or infrastructure
- Use cases must not import from ui
- No circular dependencies
"""
violations = []
# Check entities
for entity_file in (project_path / "features" / "*/src/entities").glob("*.py"):
content = entity_file.read_text()
# Entities should not import from infrastructure
if "from .infrastructure" in content or "from ..infrastructure" in content:
violations.append(f"{entity_file}: Entity imports from infrastructure")
# Entities should not import from use_cases
if "from .use_cases" in content or "from ..use_cases" in content:
violations.append(f"{entity_file}: Entity imports from use_cases")
# Check use_cases
for usecase_file in (project_path / "features" / "*/src/use_cases").glob("*.py"):
content = usecase_file.read_text()
# Use cases should not import from ui
if "from src.ui" in content:
violations.append(f"{usecase_file}: Use case imports from UI layer")
if violations:
console.print("[red]✗ Architectural violations detected:[/red]")
for violation in violations:
console.print(f" [red]- {violation}[/red]")
return False
console.print("[green]✓ Hexagonal architecture validated[/green]")
return True

Debugging Techniques

1. Verbose Mode / Debug Logging

Enable debug output:

Terminal window
# CLI debug mode
sscli inject commerce --debug
# Python internal debugging
SSCLI_DEBUG=1 sscli inject commerce

In code:

import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
def inject_feature(path: Path, feature: str):
logger.debug(f"Starting injection for {feature}")
logger.debug(f"Target path: {path}")
tree = ast.parse(path.read_text())
logger.debug(f"AST parsed: {len(tree.body)} nodes")
# ... injection logic ...
logger.debug(f"AST modified: {len(tree.body)} nodes")

2. Dry Run Mode

Test injection without modifying files:

def inject_feature_dry_run(path: Path, feature: str):
"""
Simulate injection without writing files.
"""
console.print(f"[yellow]DRY RUN: Would inject {feature} into {path}[/yellow]")
# Parse and transform
tree = ast.parse(path.read_text())
tree_modified = add_feature(tree, feature)
# Generate code
new_code = ast.unparse(tree_modified)
# Show diff instead of writing
console.print("[cyan]--- Original[/cyan]")
console.print(path.read_text())
console.print("[cyan]+++ Modified[/cyan]")
console.print(new_code)
console.print("[yellow]⚠ No files modified (dry run)[/yellow]")

3. AST Visualization

Visualize the syntax tree:

def visualize_ast(code: str):
"""Pretty-print AST structure."""
tree = ast.parse(code)
print(ast.dump(tree, indent=2))
# Example usage
code = """
from fastapi import APIRouter
router = APIRouter()
router.include_router(commerce_router, prefix="/commerce")
"""
visualize_ast(code)
# Output:
# Module(
# body=[
# ImportFrom(
# module='fastapi',
# names=[alias(name='APIRouter')],
# level=0),
# Assign(
# targets=[Name(id='router', ctx=Store())],
# value=Call(
# func=Name(id='APIRouter', ctx=Load()),
# args=[],
# keywords=[])),
# Expr(
# value=Call(
# func=Attribute(
# value=Name(id='router', ctx=Load()),
# attr='include_router',
# ctx=Load()),
# args=[Name(id='commerce_router', ctx=Load())],
# keywords=[
# keyword(
# arg='prefix',
# value=Constant(value='/commerce'))]))],
# type_ignores=[])

4. Incremental Injection

Test injection step-by-step:

Terminal window
# Test 1: Just copy files
sscli inject commerce --files-only
# Test 2: Add imports
sscli inject commerce --imports-only
# Test 3: Add routes
sscli inject commerce --routes-only
# Test 4: Full injection
sscli inject commerce

5. Rollback Testing

Test injection with automatic rollback:

import shutil
from contextlib import contextmanager
@contextmanager
def rollback_on_error(target_path: Path):
"""
Backup file before injection, restore on error.
"""
backup_path = target_path.with_suffix(target_path.suffix + ".backup")
try:
# Backup
shutil.copy(target_path, backup_path)
console.print(f"[cyan]Backup created: {backup_path}[/cyan]")
yield
# Success - remove backup
backup_path.unlink()
console.print("[green]✓ Injection successful, backup removed[/green]")
except Exception as e:
# Error - restore backup
console.print(f"[red]✗ Injection failed: {e}[/red]")
shutil.copy(backup_path, target_path)
console.print(f"[yellow]⚠ Restored from backup[/yellow]")
raise
# Usage
with rollback_on_error(Path("src/ui/routes/api.py")):
inject_feature(Path("src/ui/routes/api.py"), "commerce")

Real-World Examples

Example 1: Failed Python SaaS Commerce Injection

Initial State:

src/ui/routes/api.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
def health_check():
return {"status": "ok"}

Command:

Terminal window
sscli inject commerce

Error:

Error: AST parsing failed: invalid syntax (api.py, line 8)
SyntaxError: unexpected indent

Diagnosis:

Terminal window
python -m py_compile src/ui/routes/api.py
# File "src/ui/routes/api.py", line 8
# return {"status": "ok"}
# ^
# IndentationError: unexpected indent

Root Cause: Mixed tabs and spaces in original file

Resolution:

src/ui/routes/api.py
# Fix indentation (use spaces only)
from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
def health_check():
return {"status": "ok"} # Fixed: 4 spaces, not tab
# Add markers for injection
# SSCLI:IMPORTS:START
# SSCLI:IMPORTS:END
# SSCLI:ROUTES:START
# SSCLI:ROUTES:END

Retry:

Terminal window
sscli inject commerce
# ✓ Commerce feature injected successfully

Example 2: Astro Theme Injection with Missing Placeholder

Initial State:

src/styles/themes.css
:root {
--color-primary: #000;
}

Command:

Terminal window
sscli generate --name my-landing --template static-landing --with-themes

Error:

Warning: Theme placeholder not found in themes.css
Falling back to append mode

Root Cause: Missing [SS-FEATURE-THEMES-PLACEHOLDER] marker

Resolution:

src/styles/themes.css
/* [SS-FEATURE-THEMES-PLACEHOLDER] */
:root {
--color-primary: #000;
}

Retry:

Terminal window
sscli generate --name my-landing --template static-landing --with-themes
# ✓ Themes injected at placeholder location

Example 3: Rails Route Injection Idempotency Failure

Initial State:

config/routes.rb
Rails.application.routes.draw do
root to: "home#index"
end

Command (run twice):

Terminal window
sscli inject commerce
sscli inject commerce # Run again

Result (before fix):

# File: config/routes.rb (after 2nd injection)
Rails.application.routes.draw do
root to: "home#index"
# Commerce routes
namespace :commerce do
resources :payments
end
# Commerce routes
namespace :commerce do
resources :payments # ❌ Duplicate!
end
end

Root Cause: No idempotency check in Rails injection

Resolution:

# Add detection pattern in Synvert rewriter
def inject_commerce_routes
# Check if already exists
if_not_exist do
within_node type: 'send', receiver: 'Rails.application', message: 'routes' do
append """
# Commerce routes
namespace :commerce do
resources :payments
end
"""
end
end
end

Retry:

Terminal window
sscli inject commerce
# ✓ Commerce routes injected
sscli inject commerce
# ⚠ Commerce already injected, skipping

Example 4: Circular Dependency After Injection

Initial State:

features/commerce/src/use_cases/order.py
def create_order(order_data: dict):
# Order creation logic
pass

After Injection:

features/commerce/src/use_cases/order.py
from features.commerce.src.use_cases.payment import process_payment
def create_order(order_data: dict):
payment = process_payment(order_data["payment"])
# File: features/commerce/src/use_cases/payment.py
from features.commerce.src.use_cases.order import create_order # ❌ Circular!
def process_payment(payment_data: dict):
if payment_data.get("create_order"):
create_order(payment_data)

Error:

Terminal window
ImportError: cannot import name 'create_order' from partially initialized module

Resolution - Refactor to Use Dependency Injection:

features/commerce/src/entities/interfaces.py
from abc import ABC, abstractmethod
class OrderService(ABC):
@abstractmethod
def create(self, data: dict): pass
class PaymentService(ABC):
@abstractmethod
def process(self, data: dict): pass
# File: features/commerce/src/use_cases/order.py
from features.commerce.src.entities.interfaces import OrderService, PaymentService
class OrderCreationService(OrderService):
def __init__(self, payment_service: PaymentService):
self._payment_service = payment_service
def create(self, order_data: dict):
payment = self._payment_service.process(order_data["payment"])
# Continue order creation
# File: features/commerce/src/use_cases/payment.py
from features.commerce.src.entities.interfaces import PaymentService
class PaymentProcessingService(PaymentService):
def process(self, payment_data: dict):
# Payment logic (no import of order module)
pass

Prevention & Best Practices

Before Injection

1. Pre-flight Validation Checklist

pre_inject_check.sh
#!/bin/bash
echo "🔍 Pre-Injection Validation"
echo "=========================="
# 1. Git status clean
if [[ -n $(git status -s) ]]; then
echo "⚠️ Warning: Uncommitted changes detected"
echo " Recommend: git commit -am 'pre-injection checkpoint'"
fi
# 2. Python syntax
echo "✓ Checking Python syntax..."
find src -name "*.py" -exec python -m py_compile {} \; 2>&1 | grep -q "SyntaxError" && {
echo "❌ Python syntax errors detected"
exit 1
}
# 3. JavaScript/Astro syntax (if applicable)
if [ -f "package.json" ]; then
echo "✓ Checking JavaScript syntax..."
npm run lint || echo "⚠️ Linting issues detected"
fi
# 4. Disk space
available=$(df -h . | tail -1 | awk '{print $4}')
echo "✓ Disk space available: $available"
# 5. Injection markers present
if [ -f "src/ui/routes/api.py" ]; then
grep -q "# SSCLI:" src/ui/routes/api.py || {
echo "⚠️ Warning: No SSCLI markers found in api.py"
echo " Injection may use fallback mechanisms"
}
fi
echo ""
echo "✅ Pre-flight checks complete"
echo "Ready to run: sscli inject <feature>"

2. Backup Strategy

Terminal window
# Before injection, create backup branch
git checkout -b pre-injection-$(date +%Y%m%d-%H%M%S)
git commit -am "Pre-injection snapshot"
# Run injection
sscli inject commerce
# Review changes
git diff pre-injection-*

During Injection

3. Use Verbose Mode

Terminal window
# See what's happening in real-time
sscli inject commerce --verbose --debug

4. Dry Run First

Terminal window
# Test without modifying files
sscli inject commerce --dry-run
# Review what would change, then apply
sscli inject commerce

After Injection

5. Post-Injection Validation

post_inject_check.sh
#!/bin/bash
echo "🔍 Post-Injection Validation"
echo "==========================="
# 1. Syntax check
echo "✓ Checking syntax..."
python -m py_compile src/**/*.py || exit 1
# 2. Import check
echo "✓ Checking imports..."
python -c "import src.ui.routes.api" || exit 1
# 3. Test suite
echo "✓ Running tests..."
pytest tests/ || exit 1
# 4. Lint
echo "✓ Running linters..."
ruff check src/ || echo "⚠️ Linting issues"
# 5. Type check
echo "✓ Type checking..."
mypy src/ || echo "⚠️ Type errors"
# 6. Architecture validation
echo "✓ Validating hexagonal architecture..."
python scripts/validate_architecture.py
echo ""
echo "✅ Post-injection validation complete"

6. Manual Testing

Terminal window
# Start services
docker-compose up -d
# Check logs
docker-compose logs backend | grep -i error
# Test new endpoints
curl http://localhost:8000/api/commerce/health
# Check OpenAPI docs
open http://localhost:8000/docs

7. Code Review

Terminal window
# Review all changes
git diff
# Check only injected files
git diff --name-only | grep -E '(features|routes)'
# Review changes by type
git diff src/ui/routes/api.py # Route registration
git diff features/commerce/ # New feature files

Continuous Prevention

8. Add Pre-commit Hook

.git/hooks/pre-commit
#!/bin/bash
# Prevent commits with syntax errors
find src -name "*.py" -exec python -m py_compile {} \; 2>&1 | grep -q "SyntaxError" && {
echo "❌ Python syntax errors detected. Commit aborted."
exit 1
}
# Validate hexagonal architecture
python scripts/validate_architecture.py || {
echo "❌ Architecture violations detected. Commit aborted."
exit 1
}
exit 0

9. CI/CD Validation

.github/workflows/validate-injection.yml
name: Validate Injection
on: [push]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install ast-grep-cli
- name: Validate syntax
run: |
find src -name "*.py" -exec python -m py_compile {} \;
- name: Validate architecture
run: |
python scripts/validate_architecture.py
- name: Run tests
run: |
pytest tests/ -v

Debug Checklist

When injection fails, systematically check:

  • CLI Version: sscli --version (use latest)
  • Python Version: python --version (3.10+ required)
  • Dependencies: pip list | grep -E "(ast-grep|pyyaml|rich)"
  • File Syntax: python -m py_compile <target_file>
  • Markers Exist: grep "# SSCLI:" <target_file>
  • File Writable: ls -l <target_file> (check permissions)
  • No Git Conflicts: git status (no merge conflicts)
  • Disk Space: df -h . (sufficient space)
  • License Valid: sscli account info
  • Feature Available: sscli features list
  • Idempotency: Already injected? Check file content

Getting Help

If injection still fails after following this guide:

1. Collect Diagnostic Information

Terminal window
# Generate diagnostic report
sscli doctor --verbose > diagnostic.log
# Run injection with debug output
sscli inject commerce --debug > injection.log 2>&1
# Capture system info
echo "OS: $(uname -a)" >> diagnostic.log
echo "Python: $(python --version)" >> diagnostic.log
echo "CLI: $(sscli --version)" >> diagnostic.log

2. Create Minimal Reproduction

Terminal window
# Create minimal failing example
mkdir test-injection
cd test-injection
sscli init --template python-saas --name test
sscli inject commerce --debug
# Capture exactly what fails

3. Report Issue

Create a GitHub issue with:

  1. Operating System & Python version
  2. CLI version (sscli --version)
  3. Command that failed
  4. Full error output (injection.log)
  5. Minimal reproduction steps
  6. Relevant file contents (anonymized)

GitHub: https://github.com/seed-source/stack-cli/issues

4. Emergency Manual Injection

If automated injection completely fails, see:



Last Updated: February 28, 2026 Maintainer: Seed & Source CLI Team Feedback: Open an issue or submit a PR