Add 9 comprehensive automation skills and enforce tool usage
Create complete automation toolkit with 9 new skills covering all major workflows, saving 147-205 hrs/year from 936 hrs/year baseline. ## New Skills (Time Savings) Quick wins: - pre-commit: Complete validation workflow (8-16 hrs/year) - docker-rebuild: Full rebuild after changes (8 hrs/year) - agent-deploy-full: Complete agent deployment (3 hrs/year) Safety and quality: - migration-check: ADR-008 validation (prevents critical errors) - production-health: System health monitoring (deployment confidence) - security-scan: Sept 2026 audit compliance (prevent regressions) Advanced workflows: - vm-snapshot: Hyper-V snapshot management (78 hrs/year) - plugin-new: Scaffold new plugins (30-60 min per plugin) - full-deploy: Air-gap deployment orchestration (50-100 hrs/year) ## Tool Engagement Enforcement Updated settings.json to load TOOL-ENGAGEMENT-PROTOCOL.md in every session: - Protocol now always in context (cannot forget to check) - MCP tool requirements visible before starting work - Enforces coordinator.announce_work, Gortex usage, skill invocation ## Code Cleanup Removed unicode symbols from all skill files per CLAUDE.md: - No emojis (replaced with text) - No unicode checkmarks/arrows (replaced with ASCII) - No em-dashes (replaced with hyphens) - No box-drawing characters (replaced with +, -, |) - Plain ASCII only Total: 13 skills (4 existing + 9 new), all documentation plain ASCII Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
707
.claude/skills/plugin-new.md
Normal file
707
.claude/skills/plugin-new.md
Normal file
@@ -0,0 +1,707 @@
|
||||
---
|
||||
name: plugin-new
|
||||
description: Scaffold new ShopDB plugin following ADRs - directory structure, BasePlugin boilerplate, migrations, Vue component
|
||||
---
|
||||
|
||||
# New Plugin Scaffolding
|
||||
|
||||
Generate complete plugin structure following ShopDB architecture (ADR-001, ADR-008, ADR-010).
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
Creates production-ready plugin scaffold:
|
||||
1. Plugin directory structure
|
||||
2. BasePlugin subclass with manifest
|
||||
3. SQLAlchemy models directory
|
||||
4. Flask API blueprint
|
||||
5. Alembic migrations directory
|
||||
6. Vue frontend component
|
||||
7. README with checklist
|
||||
8. Adds plugin to PLUGIN_TABLE_OWNERS
|
||||
|
||||
Saves 30-60 minutes of setup per new plugin.
|
||||
|
||||
## Usage
|
||||
|
||||
`/plugin-new "plugin-name" "description"`
|
||||
|
||||
Example: `/plugin-new "tooling" "Track hand tools and tooling inventory"`
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Validate Plugin Name
|
||||
|
||||
**Check naming conventions:**
|
||||
- Lowercase, no underscores
|
||||
- Singular form ("tool" not "tools", exception: existing plurals like "computers")
|
||||
- No spaces or special characters
|
||||
- Not a Python reserved word
|
||||
|
||||
**Check plugin doesn't exist:**
|
||||
```bash
|
||||
if [ -d "plugins/$plugin_name" ]; then
|
||||
echo "ERROR: Plugin '$plugin_name' already exists"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Suggest corrections:**
|
||||
- "tool_tracking" -> "tooling" (remove underscore)
|
||||
- "Tools" -> "tools" (lowercase)
|
||||
- "hand-tools" -> "handtools" (remove hyphen)
|
||||
|
||||
### 2. Create Directory Structure
|
||||
|
||||
```bash
|
||||
mkdir -p plugins/$plugin_name/{api,models,frontend/views,migrations/versions,tests}
|
||||
```
|
||||
|
||||
**Expected structure:**
|
||||
```
|
||||
plugins/tooling/
|
||||
__init__.py
|
||||
plugin.py # BasePlugin subclass
|
||||
api/
|
||||
__init__.py
|
||||
routes.py # Flask blueprint
|
||||
models/
|
||||
__init__.py
|
||||
tool.py # SQLAlchemy model
|
||||
frontend/
|
||||
views/
|
||||
ToolList.vue # Main Vue component
|
||||
migrations/
|
||||
env.py # Alembic environment
|
||||
script.py.mako # Migration template
|
||||
versions/ # Migration files
|
||||
tests/
|
||||
__init__.py
|
||||
test_tooling.py # Plugin tests
|
||||
README.md # Plugin documentation
|
||||
```
|
||||
|
||||
### 3. Generate BasePlugin Subclass
|
||||
|
||||
**File:** `plugins/$plugin_name/plugin.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
$Plugin_Name Plugin
|
||||
|
||||
$Description
|
||||
"""
|
||||
from shopdb.api import BasePlugin
|
||||
|
||||
|
||||
class ${Plugin_Name}Plugin(BasePlugin):
|
||||
"""$Description"""
|
||||
|
||||
name = "$plugin_name"
|
||||
display_name = "$Display Name"
|
||||
description = "$description"
|
||||
version = "0.1.0"
|
||||
author = "ShopDB Team"
|
||||
|
||||
# Dependencies (other plugins this depends on)
|
||||
dependencies = []
|
||||
|
||||
# Frontend routes
|
||||
frontend_routes = [
|
||||
{
|
||||
"path": "/$plugin_name",
|
||||
"name": "$Plugin_Name",
|
||||
"component": "$Plugin_NameList",
|
||||
"meta": {"requiresAuth": True, "title": "$Display Name"}
|
||||
}
|
||||
]
|
||||
|
||||
# Sidebar menu entry
|
||||
sidebar_items = [
|
||||
{
|
||||
"title": "$Display Name",
|
||||
"icon": "mdi-tools", # Material Design Icon
|
||||
"path": "/$plugin_name",
|
||||
"permission": "$plugin_name.view"
|
||||
}
|
||||
]
|
||||
|
||||
# Permissions this plugin defines
|
||||
permissions = [
|
||||
("$plugin_name.view", "View $display_name"),
|
||||
("$plugin_name.create", "Create $display_name"),
|
||||
("$plugin_name.edit", "Edit $display_name"),
|
||||
("$plugin_name.delete", "Delete $display_name"),
|
||||
]
|
||||
|
||||
def register_blueprints(self, app):
|
||||
"""Register Flask blueprints"""
|
||||
from .api.routes import bp
|
||||
app.register_blueprint(bp, url_prefix=f"/api/$plugin_name")
|
||||
|
||||
def init_app(self, app):
|
||||
"""Initialize plugin with Flask app"""
|
||||
self.register_blueprints(app)
|
||||
```
|
||||
|
||||
**Variables to substitute:**
|
||||
- `$plugin_name`: "tooling"
|
||||
- `$Plugin_Name`: "Tooling"
|
||||
- `$Display Name`: "Tool Tracking"
|
||||
- `$description`: User-provided description
|
||||
|
||||
### 4. Create SQLAlchemy Model
|
||||
|
||||
**File:** `plugins/$plugin_name/models/$model_name.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
$Model_Name model for $plugin_name plugin
|
||||
"""
|
||||
from shopdb.api import db, Asset
|
||||
|
||||
|
||||
class $Model_Name(Asset):
|
||||
"""$Description"""
|
||||
|
||||
__tablename__ = "$tablename"
|
||||
__mapper_args__ = {"polymorphic_identity": "$plugin_name"}
|
||||
|
||||
# Polymorphic inheritance from Asset (ADR-001)
|
||||
id = db.Column(db.Integer, db.ForeignKey('asset.id'), primary_key=True)
|
||||
|
||||
# Plugin-specific fields
|
||||
tool_type = db.Column(db.String(100), nullable=False)
|
||||
manufacturer = db.Column(db.String(100))
|
||||
model_number = db.Column(db.String(100))
|
||||
serial_number = db.Column(db.String(100), unique=True)
|
||||
purchase_date = db.Column(db.Date)
|
||||
status = db.Column(db.String(50), default='available')
|
||||
|
||||
# Relationships (example: assigned to employee)
|
||||
assigned_to_id = db.Column(db.Integer, db.ForeignKey('employee.id'))
|
||||
assigned_to = db.relationship('Employee', backref='tools')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<$Model_Name {self.serial_number}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Serialize model to dictionary"""
|
||||
data = super().to_dict() # Get Asset base fields
|
||||
data.update({
|
||||
'tool_type': self.tool_type,
|
||||
'manufacturer': self.manufacturer,
|
||||
'model_number': self.model_number,
|
||||
'serial_number': self.serial_number,
|
||||
'purchase_date': self.purchase_date.isoformat() if self.purchase_date else None,
|
||||
'status': self.status,
|
||||
'assigned_to': self.assigned_to.to_dict() if self.assigned_to else None
|
||||
})
|
||||
return data
|
||||
```
|
||||
|
||||
**Models/__init__.py:**
|
||||
```python
|
||||
from .tool import Tool
|
||||
|
||||
__all__ = ['Tool']
|
||||
```
|
||||
|
||||
### 5. Create Flask API Blueprint
|
||||
|
||||
**File:** `plugins/$plugin_name/api/routes.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
API routes for $plugin_name plugin
|
||||
"""
|
||||
from flask import Blueprint, request, jsonify
|
||||
from shopdb.api import db, requires_auth
|
||||
from ..models import $Model_Name
|
||||
|
||||
bp = Blueprint('$plugin_name', __name__)
|
||||
|
||||
|
||||
@bp.route('/', methods=['GET'])
|
||||
@requires_auth
|
||||
def list_$plural():
|
||||
"""List all $plural"""
|
||||
items = $Model_Name.query.all()
|
||||
return jsonify([item.to_dict() for item in items])
|
||||
|
||||
|
||||
@bp.route('/<int:id>', methods=['GET'])
|
||||
@requires_auth
|
||||
def get_$singular(id):
|
||||
"""Get specific $singular by ID"""
|
||||
item = $Model_Name.query.get_or_404(id)
|
||||
return jsonify(item.to_dict())
|
||||
|
||||
|
||||
@bp.route('/', methods=['POST'])
|
||||
@requires_auth
|
||||
def create_$singular():
|
||||
"""Create new $singular"""
|
||||
data = request.json
|
||||
|
||||
# Validate required fields
|
||||
required = ['tool_type', 'serial_number']
|
||||
if not all(field in data for field in required):
|
||||
return jsonify({'error': 'Missing required fields'}), 400
|
||||
|
||||
# Create item
|
||||
item = $Model_Name(**data)
|
||||
db.session.add(item)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(item.to_dict()), 201
|
||||
|
||||
|
||||
@bp.route('/<int:id>', methods=['PUT'])
|
||||
@requires_auth
|
||||
def update_$singular(id):
|
||||
"""Update existing $singular"""
|
||||
item = $Model_Name.query.get_or_404(id)
|
||||
data = request.json
|
||||
|
||||
# Update fields
|
||||
for key, value in data.items():
|
||||
if hasattr(item, key):
|
||||
setattr(item, key, value)
|
||||
|
||||
db.session.commit()
|
||||
return jsonify(item.to_dict())
|
||||
|
||||
|
||||
@bp.route('/<int:id>', methods=['DELETE'])
|
||||
@requires_auth
|
||||
def delete_$singular(id):
|
||||
"""Delete $singular"""
|
||||
item = $Model_Name.query.get_or_404(id)
|
||||
db.session.delete(item)
|
||||
db.session.commit()
|
||||
|
||||
return '', 204
|
||||
```
|
||||
|
||||
### 6. Initialize Alembic Migrations
|
||||
|
||||
**File:** `plugins/$plugin_name/migrations/env.py`
|
||||
|
||||
Copy from template (similar to other plugins)
|
||||
|
||||
**File:** `plugins/$plugin_name/migrations/script.py.mako`
|
||||
|
||||
Copy from template
|
||||
|
||||
**Create initial migration:**
|
||||
```bash
|
||||
docker compose exec api flask plugin revision -p $plugin_name -m "Initial $plugin_name schema"
|
||||
```
|
||||
|
||||
This generates: `plugins/$plugin_name/migrations/versions/001_initial_schema.py`
|
||||
|
||||
**Verify migration:**
|
||||
- Check revision ID generated
|
||||
- Verify down_revision is None (first migration)
|
||||
- Verify upgrade() creates table
|
||||
- Verify downgrade() drops table
|
||||
|
||||
### 7. Create Vue Frontend Component
|
||||
|
||||
**File:** `plugins/$plugin_name/frontend/views/$Plugin_NameList.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="$plugin_name-list">
|
||||
<h1>$Display Name</h1>
|
||||
|
||||
<v-btn color="primary" @click="createDialog = true">
|
||||
<v-icon left>mdi-plus</v-icon>
|
||||
Add $Singular
|
||||
</v-btn>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
class="elevation-1 mt-4"
|
||||
>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-icon small @click="editItem(item)" class="mr-2">
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
<v-icon small @click="deleteItem(item)">
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
<!-- Create/Edit Dialog -->
|
||||
<v-dialog v-model="createDialog" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">{{ editedIndex === -1 ? 'New' : 'Edit' }} $Singular</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-form ref="form" v-model="valid">
|
||||
<v-text-field
|
||||
v-model="editedItem.tool_type"
|
||||
label="Tool Type"
|
||||
:rules="[v => !!v || 'Required']"
|
||||
required
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="editedItem.serial_number"
|
||||
label="Serial Number"
|
||||
:rules="[v => !!v || 'Required']"
|
||||
required
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="editedItem.manufacturer"
|
||||
label="Manufacturer"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="editedItem.model_number"
|
||||
label="Model Number"
|
||||
></v-text-field>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn text @click="closeDialog">Cancel</v-btn>
|
||||
<v-btn color="primary" text @click="saveItem" :disabled="!valid">
|
||||
Save
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: '$Plugin_NameList',
|
||||
|
||||
data: () => ({
|
||||
items: [],
|
||||
loading: false,
|
||||
createDialog: false,
|
||||
valid: false,
|
||||
editedIndex: -1,
|
||||
editedItem: {
|
||||
tool_type: '',
|
||||
serial_number: '',
|
||||
manufacturer: '',
|
||||
model_number: ''
|
||||
},
|
||||
defaultItem: {
|
||||
tool_type: '',
|
||||
serial_number: '',
|
||||
manufacturer: '',
|
||||
model_number: ''
|
||||
},
|
||||
headers: [
|
||||
{ text: 'Serial Number', value: 'serial_number' },
|
||||
{ text: 'Tool Type', value: 'tool_type' },
|
||||
{ text: 'Manufacturer', value: 'manufacturer' },
|
||||
{ text: 'Model', value: 'model_number' },
|
||||
{ text: 'Status', value: 'status' },
|
||||
{ text: 'Actions', value: 'actions', sortable: false }
|
||||
]
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
this.fetchItems()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchItems() {
|
||||
this.loading = true
|
||||
try {
|
||||
const response = await this.$http.get('/api/$plugin_name/')
|
||||
this.items = response.data
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch $plural:', error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
editItem(item) {
|
||||
this.editedIndex = this.items.indexOf(item)
|
||||
this.editedItem = Object.assign({}, item)
|
||||
this.createDialog = true
|
||||
},
|
||||
|
||||
async deleteItem(item) {
|
||||
if (confirm('Are you sure you want to delete this $singular?')) {
|
||||
try {
|
||||
await this.$http.delete(`/api/$plugin_name/${item.id}`)
|
||||
this.fetchItems()
|
||||
} catch (error) {
|
||||
console.error('Failed to delete $singular:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
closeDialog() {
|
||||
this.createDialog = false
|
||||
this.$refs.form.reset()
|
||||
this.editedItem = Object.assign({}, this.defaultItem)
|
||||
this.editedIndex = -1
|
||||
},
|
||||
|
||||
async saveItem() {
|
||||
if (!this.$refs.form.validate()) return
|
||||
|
||||
try {
|
||||
if (this.editedIndex > -1) {
|
||||
// Update
|
||||
await this.$http.put(`/api/$plugin_name/${this.editedItem.id}`, this.editedItem)
|
||||
} else {
|
||||
// Create
|
||||
await this.$http.post('/api/$plugin_name/', this.editedItem)
|
||||
}
|
||||
this.fetchItems()
|
||||
this.closeDialog()
|
||||
} catch (error) {
|
||||
console.error('Failed to save $singular:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 8. Add to PLUGIN_TABLE_OWNERS
|
||||
|
||||
**File:** `shopdb/config.py`
|
||||
|
||||
```python
|
||||
PLUGIN_TABLE_OWNERS = [
|
||||
'employees', # Core dependency (if using Employee FK)
|
||||
'assets', # Core dependency (Asset polymorphism)
|
||||
# ... existing plugins ...
|
||||
'$plugin_name', # ADD NEW PLUGIN HERE
|
||||
]
|
||||
```
|
||||
|
||||
**Placement considerations:**
|
||||
- After plugins you reference (foreign keys)
|
||||
- Before plugins that reference you
|
||||
- Alphabetical within same dependency level
|
||||
|
||||
### 9. Create Plugin Tests
|
||||
|
||||
**File:** `plugins/$plugin_name/tests/test_tooling.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
Tests for $plugin_name plugin
|
||||
"""
|
||||
import pytest
|
||||
from shopdb.api import db
|
||||
from plugins.$plugin_name.models import $Model_Name
|
||||
|
||||
|
||||
def test_create_$singular(client, auth_header):
|
||||
"""Test creating $singular via API"""
|
||||
response = client.post('/api/$plugin_name/', json={
|
||||
'tool_type': 'Wrench',
|
||||
'serial_number': 'WR-001',
|
||||
'manufacturer': 'Craftsman'
|
||||
}, headers=auth_header)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json
|
||||
assert data['tool_type'] == 'Wrench'
|
||||
assert data['serial_number'] == 'WR-001'
|
||||
|
||||
|
||||
def test_list_$plural(client, auth_header):
|
||||
"""Test listing all $plural"""
|
||||
response = client.get('/api/$plugin_name/', headers=auth_header)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json, list)
|
||||
|
||||
|
||||
def test_get_$singular(client, auth_header, test_$singular):
|
||||
"""Test getting specific $singular"""
|
||||
response = client.get(f'/api/$plugin_name/{test_$singular.id}', headers=auth_header)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert data['id'] == test_$singular.id
|
||||
|
||||
|
||||
def test_update_$singular(client, auth_header, test_$singular):
|
||||
"""Test updating $singular"""
|
||||
response = client.put(f'/api/$plugin_name/{test_$singular.id}', json={
|
||||
'status': 'in_use'
|
||||
}, headers=auth_header)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json
|
||||
assert data['status'] == 'in_use'
|
||||
|
||||
|
||||
def test_delete_$singular(client, auth_header, test_$singular):
|
||||
"""Test deleting $singular"""
|
||||
response = client.delete(f'/api/$plugin_name/{test_$singular.id}', headers=auth_header)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
# Verify deleted
|
||||
response = client.get(f'/api/$plugin_name/{test_$singular.id}', headers=auth_header)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_$singular(db_session):
|
||||
"""Create test $singular"""
|
||||
$singular = $Model_Name(
|
||||
tool_type='Hammer',
|
||||
serial_number='TEST-001',
|
||||
manufacturer='TestCo'
|
||||
)
|
||||
db_session.add($singular)
|
||||
db_session.commit()
|
||||
return $singular
|
||||
```
|
||||
|
||||
### 10. Create Plugin README
|
||||
|
||||
**File:** `plugins/$plugin_name/README.md`
|
||||
|
||||
```markdown
|
||||
# $Display Name Plugin
|
||||
|
||||
$description
|
||||
|
||||
## Features
|
||||
|
||||
- Track $plural with serial numbers
|
||||
- Manage tool assignments
|
||||
- Monitor tool status (available, in_use, maintenance, retired)
|
||||
|
||||
## Models
|
||||
|
||||
### $Model_Name
|
||||
|
||||
Inherits from `Asset` (ADR-001: Asset as platform contract)
|
||||
|
||||
**Fields:**
|
||||
- `tool_type` (str): Type of tool
|
||||
- `manufacturer` (str): Manufacturer name
|
||||
- `model_number` (str): Model number
|
||||
- `serial_number` (str, unique): Serial number
|
||||
- `status` (str): Tool status
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- `GET /api/$plugin_name/` - List all $plural
|
||||
- `GET /api/$plugin_name/<id>` - Get specific $singular
|
||||
- `POST /api/$plugin_name/` - Create new $singular
|
||||
- `PUT /api/$plugin_name/<id>` - Update $singular
|
||||
- `DELETE /api/$plugin_name/<id>` - Delete $singular
|
||||
|
||||
## Frontend
|
||||
|
||||
Main component: `$Plugin_NameList.vue`
|
||||
Route: `/$plugin_name`
|
||||
|
||||
## Development Checklist
|
||||
|
||||
- [ ] Models defined with proper relationships
|
||||
- [ ] Initial migration created and tested
|
||||
- [ ] API endpoints implemented with authentication
|
||||
- [ ] Frontend component renders list and detail views
|
||||
- [ ] Tests written and passing
|
||||
- [ ] Added to PLUGIN_TABLE_OWNERS in correct order
|
||||
- [ ] Documentation updated
|
||||
- [ ] Permissions defined
|
||||
- [ ] Sidebar menu item configured
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
pytest plugins/$plugin_name/tests/ -v
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
```bash
|
||||
# Create new migration
|
||||
docker compose exec api flask plugin revision -p $plugin_name -m "description"
|
||||
|
||||
# Apply migration
|
||||
docker compose exec api flask plugin upgrade $plugin_name
|
||||
```
|
||||
```
|
||||
|
||||
### 11. Summary Report
|
||||
|
||||
```
|
||||
Plugin scaffolding complete: $plugin_name
|
||||
|
||||
Created files:
|
||||
[X] plugins/$plugin_name/plugin.py (BasePlugin subclass)
|
||||
[X] plugins/$plugin_name/models/$model_name.py (SQLAlchemy model)
|
||||
[X] plugins/$plugin_name/api/routes.py (Flask blueprint, 5 endpoints)
|
||||
[X] plugins/$plugin_name/frontend/views/$Plugin_NameList.vue (Vue component)
|
||||
[X] plugins/$plugin_name/migrations/ (Alembic initialized)
|
||||
[X] plugins/$plugin_name/tests/test_$plugin_name.py (pytest tests)
|
||||
[X] plugins/$plugin_name/README.md (documentation)
|
||||
|
||||
Updated files:
|
||||
[X] shopdb/config.py (added to PLUGIN_TABLE_OWNERS)
|
||||
|
||||
Next steps:
|
||||
1. Review generated code and customize as needed
|
||||
2. Create initial migration: docker compose exec api flask plugin revision -p $plugin_name -m "Initial schema"
|
||||
3. Apply migration: docker compose exec api flask plugin upgrade $plugin_name
|
||||
4. Run tests: pytest plugins/$plugin_name/tests/ -v
|
||||
5. Start dev server and test frontend: npm run dev
|
||||
6. Commit changes: git add plugins/$plugin_name shopdb/config.py
|
||||
|
||||
Time saved: ~45 minutes of manual scaffolding
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
After scaffolding, customize:
|
||||
|
||||
**Models:**
|
||||
- Add plugin-specific fields
|
||||
- Define relationships to other plugins
|
||||
- Add custom methods
|
||||
|
||||
**API:**
|
||||
- Add custom endpoints (search, filters, bulk operations)
|
||||
- Implement business logic
|
||||
- Add validation schemas
|
||||
|
||||
**Frontend:**
|
||||
- Customize table columns
|
||||
- Add filters and search
|
||||
- Implement detail views
|
||||
- Add charts/visualizations
|
||||
|
||||
**Tests:**
|
||||
- Add tests for custom logic
|
||||
- Test edge cases
|
||||
- Add integration tests
|
||||
|
||||
## Related
|
||||
|
||||
- ADR-001: Asset as platform contract
|
||||
- ADR-008: Plugin migration ownership
|
||||
- ADR-010: Frontend plugin hooks
|
||||
- `CLAUDE.md` - Plugin development guide
|
||||
- `/pre-commit` - Test before committing
|
||||
Reference in New Issue
Block a user