diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 7ec1cdb..aad687d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "buidl", - "version": "3.4.0", - "description": "Full dev lifecycle for OP_NET Bitcoin L1 projects: idea → challenge → spec → build → review → ship. Includes the OP_NET Bible (2000+ lines of rules, patterns, and known mistakes) that prevents AI agents from hallucinating package versions, using forbidden patterns, or shipping exploitable code. Now with shell-enforced E2E testing gates, frontend runtime smoke checks, pre-flight anti-pattern scanning, PUA exhaustive problem-solving methodology, and GSD-2 debugging discipline — agents never skip testing or ship buggy frontends.", + "version": "3.5.0", + "description": "Full dev lifecycle for OP_NET Bitcoin L1 projects: idea → challenge → spec → build → review → ship. Self-learning across sessions with pattern extraction, agent performance scoring, cross-layer validation, and starter templates. Includes shell-enforced E2E testing gates, frontend runtime smoke checks, PUA problem-solving methodology, and the OP_NET Bible (2000+ lines). Agents get smarter with every project.", "author": { "name": "dannyplainview + bob" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f55bbe4..035dfb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## [3.5.0] - 2026-03-13 + +### Added +- **Adaptive learning pattern store** (`learning/patterns.yaml`): Structured YAML store for patterns auto-extracted from session retrospectives. Patterns are categorized by domain (contract/frontend/backend/deployment/testing), failure type, and tech stack. Auto-deduplicates and tracks occurrence count across sessions. +- **Pattern extraction script** (`scripts/extract-patterns.sh`): Reads a retrospective markdown file, extracts anti-patterns and failures, appends structured entries to patterns.yaml. Auto-promotes patterns with 3+ occurrences to relevant knowledge slices with `[LEARNED]` tag. +- **Agent performance scoring** (`learning/agent-scores.yaml`): Rolling metrics per agent — sessions completed, success rate, average cycles to pass review, average tokens consumed, model history with per-model success rates. Updated automatically after each session. +- **Score update script** (`scripts/update-scores.sh`): Reads session state.yaml after completion, extracts agent outcomes, computes rolling averages, updates agent-scores.yaml. +- **Cross-layer validator agent** (`agents/cross-layer-validator.md`): READ-ONLY agent that validates integration correctness across contract/frontend/backend layers. Checks ABI-to-frontend method mapping, parameter types, contract address consistency, network config alignment, signer configuration, and event names. Runs after builders, before auditor. +- **Cross-layer validation knowledge slice** (`knowledge/slices/cross-layer-validation.md`): 8 documented mismatch types with detection rules, fixes, and routing decisions. Validation checklist for frontend/backend contract calls. +- **OP-20 starter template** (`templates/starters/op20-token/`): Complete starter for OP-20 token projects — AssemblyScript contract with parameterized name/symbol/supply, unit tests, OPNet-ready Vite frontend with WalletConnect scaffold, and template.yaml manifest with customization points. +- **`validating` active phase**: Added to stop-hook, guard-state, and guard-state-bash so the loop stays blocked during cross-layer validation. + +### Changed +- **Orchestrator Phase 4 Step 0** (`commands/buidl.md`): Learning consultation now has 4 sub-steps — (a) query pattern store filtered by project type, (b) check agent scores and suggest model upgrades for underperforming agents, (c) read retrospectives, (d) check starter templates for matching project type. +- **Orchestrator Phase 4 Step 2b.5** (`commands/buidl.md`): New cross-layer validation step between builders and auditor. Dispatches cross-layer-validator, routes MISMATCH findings to responsible agents, passes WARNING findings to auditor. +- **Orchestrator Phase 6** (`commands/buidl.md`): After retrospective, now calls extract-patterns.sh and update-scores.sh to update the adaptive learning system. +- **Auditor dispatch** (`commands/buidl.md`): Now imports cross-layer validation report as additional context. +- **Plugin version**: 3.4.0 -> 3.5.0 + +### Deferred to v3.6 +- **Score-based routing** (US-6): Routing reviewer findings to agents based on historical success rates. Requires more data points before it's useful. +- **Project-type profiles** (US-8): Auto-generated profiles after 5+ projects of the same type. Needs accumulation of pattern data first. + +### Why +The plugin had a learning system that saved retrospectives but barely used them — the orchestrator read them as advisory text with no structure, no indexing, and no feedback loop into agent prompts. Agents kept repeating the same mistakes across sessions. The pattern store + agent scoring creates a real feedback loop: every session's lessons are extracted, scored, and injected into future agent prompts. Cross-layer validation catches the #1 source of wasted audit/E2E cycles (ABI mismatches) before they reach expensive downstream agents. Starter templates eliminate boilerplate for the most common project type (OP-20 tokens). + ## [3.4.0] - 2026-03-13 ### Added diff --git a/README.md b/README.md index c6f8eca..5fe04d8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Plugin Tests](https://github.com/bc1plainview/buidl-opnet-plugin/actions/workflows/plugin-tests.yml/badge.svg)](https://github.com/bc1plainview/buidl-opnet-plugin/actions/workflows/plugin-tests.yml) -A Claude Code plugin that turns a single prompt into a production-ready, audited, deployed, and on-chain tested application. 11 specialized agents handle smart contract development, frontend, backend, security audit, deployment, real on-chain E2E testing, UI testing, and code review — coordinated by an orchestrator that manages the full lifecycle from idea to merged PR. +A Claude Code plugin that turns a single prompt into a production-ready, audited, deployed, and on-chain tested application. 12 specialized agents handle smart contract development, frontend, backend, security audit, cross-layer validation, deployment, real on-chain E2E testing, UI testing, and code review — coordinated by an orchestrator that manages the full lifecycle from idea to merged PR. Built for OPNet (Bitcoin L1 smart contracts), but the core loop system works for any project. Non-OPNet projects get dynamic agent generation from templates. @@ -101,6 +101,35 @@ alias claudeyproj="claude --dangerously-skip-permissions --plugin-dir /path/to/b | `loop-explorer` | Codebase structure mapping and relevance analysis | | `loop-researcher` | Web search for existing solutions (build vs buy gate) | | `loop-reviewer` | PR review against spec + pattern checklist | +| `cross-layer-validator` | READ-ONLY ABI-to-frontend/backend integration validation | + +## Adaptive Learning System (v3.5) + +The plugin learns from every session and gets smarter over time. + +### Pattern Store (`learning/patterns.yaml`) +- Anti-patterns and failures from retrospectives are extracted into a structured YAML store +- Deduplicated by description similarity; occurrence counts tracked across sessions +- Patterns with 3+ occurrences auto-promote to relevant knowledge slices with `[LEARNED]` tags +- Grep-queryable by category, tech stack, failure type + +### Agent Performance Scoring (`learning/agent-scores.yaml`) +- Rolling averages for success rate, cycles to pass, and tokens consumed per agent +- Per-model breakdowns (opus vs sonnet performance tracking) +- Scores require 5+ data points before surfacing in `/buidl-status` +- Orchestrator consults scores to inform agent dispatch order + +### Cross-Layer Validator +- Validates ABI-to-frontend method mapping, parameter types, contract addresses, network config +- Runs after all builders but before the auditor — catches integration mismatches early +- 8 mismatch types with detection rules and routing decisions +- READ-ONLY agent (cannot modify files) + +### Starter Templates (`templates/starters/`) +- Pre-built project scaffolds for common OPNet patterns (OP-20 token included) +- Template manifests with customization points (token name, symbol, decimals, features) +- Includes contract, tests, frontend, hooks, and build config +- Orchestrator detects matching templates and offers them during spec phase ## Enforcement Mechanisms @@ -158,6 +187,7 @@ knowledge/ +-- ui-testing.md # Playwright setup, visual regression, wallet mocking +-- transaction-simulation.md # Simulation patterns for all agent types +-- integration-review.md # Cross-layer review patterns + +-- cross-layer-validation.md # ABI-to-frontend/backend validation rules +-- project-setup.md # OPNet project scaffolding ``` @@ -250,6 +280,9 @@ If the loop is interrupted (context exhaustion, wall-clock timeout, manual cance | Wall-clock timeout | v3.0 | Configurable max duration (default 60 min). Graceful save on timeout. | | Cost tracking | v3.0 | Token spend per agent in cost-ledger.md. Budget enforcement with --max-tokens. | | Learning system | v3.0 | Retrospectives saved to learning/. Future sessions consult past lessons. | +| Adaptive learning | v3.5 | Pattern extraction, agent scoring, auto-promotion to knowledge slices. | +| Cross-layer validation | v3.5 | ABI-to-frontend/backend integration checking between build and audit. | +| Starter templates | v3.5 | Pre-built scaffolds for OP-20 tokens (more planned). | | Dynamic agents | v3.0 | Non-OPNet projects generate domain agents from templates. | | On-chain E2E testing | v3.2 | Real transactions with test wallets. Every method tested. Multi-wallet flows. | | PUA methodology | v3.3 | Exhaustive problem-solving with anti-rationalization and pressure escalation. | @@ -263,21 +296,24 @@ If the loop is interrupted (context exhaustion, wall-clock timeout, manual cance ``` buidl/ +-- .claude-plugin/ -| +-- plugin.json # Plugin manifest (v3.4.0) -+-- agents/ # 11 agent definitions +| +-- plugin.json # Plugin manifest (v3.5.0) ++-- agents/ # 12 agent definitions (incl. cross-layer-validator) +-- commands/ # 7 slash commands +-- hooks/ # Stop hook + state guards | +-- scripts/ +-- knowledge/ # OPNet reference + domain slices | +-- slices/ # 10 knowledge slices -+-- learning/ # Retrospectives from past sessions -+-- scripts/ # Setup + atomic state writer ++-- learning/ # Patterns, agent scores, retrospectives +| +-- patterns.yaml # Structured pattern store (auto-updated) +| +-- agent-scores.yaml # Agent performance metrics (auto-updated) ++-- scripts/ # Setup + atomic state writer + learning scripts +-- skills/ # 3 triggerable skills | +-- audit-from-bugs/ | +-- loop-guide/ | +-- pua/ -+-- templates/ # Domain agent + knowledge slice templates -+-- tests/ # 235 structural + integration tests ++-- templates/ # Domain agent, knowledge slice, starter templates +| +-- starters/ # Project scaffolds (op20-token, more planned) ++-- tests/ # 272 structural + integration tests ``` ## Testing @@ -286,7 +322,7 @@ buidl/ bash tests/plugin-tests.sh ``` -235 tests across 23 categories: +272 tests across 26 categories: | Category | What it checks | |----------|----------------| @@ -313,6 +349,9 @@ bash tests/plugin-tests.sh | Learning pruning | Cap at 20 retrospectives | | Orphan worktrees | Detection in status, cleanup in clean | | Guard-state-bash | Bash tool redirect blocking | +| Adaptive learning | Pattern store schema, extraction scripts, agent scores format | +| Cross-layer validator | Agent definition, knowledge slice, mismatch type coverage | +| Starter templates | Template manifest, contract template, frontend template, hook files | Tests run automatically on every push and PR via GitHub Actions. diff --git a/agents/cross-layer-validator.md b/agents/cross-layer-validator.md new file mode 100644 index 0000000..1a3794b --- /dev/null +++ b/agents/cross-layer-validator.md @@ -0,0 +1,152 @@ +--- +name: cross-layer-validator +description: | + Use this agent during Phase 4 of /buidl after all builders finish but BEFORE the auditor. This is the integration validation specialist -- it checks that contract ABIs match frontend/backend calls, addresses are consistent, and network configs align across all layers. It is READ-ONLY and cannot modify any files. + + + Context: Contract-dev and frontend-dev have both finished. Time to validate integration. + user: "All builders done. Run cross-layer validation before audit." + assistant: "Launching the cross-layer validator to check ABI-to-frontend method mapping." + + Validator runs AFTER all builders but BEFORE auditor. Catches mismatches early. + + + + + Context: Frontend-dev called a contract method that doesn't exist in the ABI. + user: "Validator found ABI mismatch. Route to frontend-dev." + assistant: "Launching frontend-dev to fix the contract call." + + Validator findings are routed to the responsible builder for fixes before audit. + + +model: sonnet +color: cyan +tools: + - Read + - Grep + - Glob +--- + +You are the **Cross-Layer Validator** agent. You check integration correctness across contract, frontend, and backend layers. + +## Constraints + +- You are READ-ONLY. You do NOT modify any files. +- You do NOT write contracts, frontend code, backend code, or deployment scripts. +- You validate that layers are consistent with each other, not that individual layers are correct (that's the auditor's job). + +### FORBIDDEN +- Writing or editing any source file. +- Modifying state files, artifacts, or configuration. +- Running build commands or tests. +- Making network requests or RPC calls. + +## Step 0: Read Your Knowledge (MANDATORY) + +Before any validation: +1. Read [knowledge/slices/cross-layer-validation.md](knowledge/slices/cross-layer-validation.md) COMPLETELY. +2. If you encounter issues, check [knowledge/opnet-troubleshooting.md](knowledge/opnet-troubleshooting.md). + +## Process + +### Step 1: Inventory All Layers + +Identify which layers exist in this build: +- **Contract**: Check for ABI JSON in `artifacts/contract/abi.json` or similar +- **Frontend**: Check for `src/` with React/TypeScript files (`.tsx`, `.ts`) +- **Backend**: Check for server files, API routes, or `backend/` directory + +If only one layer exists, report "Single-layer project — no cross-layer validation needed" and exit. + +### Step 2: Parse the Contract ABI + +Read the ABI JSON and extract: +- All public method names and their selectors +- Parameter types for each method (input and output) +- Event definitions +- Whether methods are read-only or state-changing + +### Step 3: Validate Frontend-to-Contract Integration + +For each frontend file that imports or uses contract interactions: + +**Check 3a — Method Existence:** +- Every `contract.methodName()` call in frontend must exist in the ABI +- Report any call to a method not in the ABI + +**Check 3b — Parameter Types:** +- Parameter count must match between frontend call and ABI definition +- BigInt must be used for uint256 params (not number) +- Address params must use Address type (not raw string) + +**Check 3c — Contract Address Consistency:** +- Contract address used in `getContract()` must match deployment config +- If hardcoded, flag as warning (should come from config/env) + +**Check 3d — Network Consistency:** +- Frontend must use the same network as the contract deployment +- Check for `networks.opnetTestnet` vs `networks.testnet` mismatch + +### Step 4: Validate Backend-to-Contract Integration (if backend exists) + +Same checks as Step 3, applied to backend code: +- RPC URL consistency +- Method calls match ABI +- Signer usage (backend MUST have signer, frontend MUST NOT) + +### Step 5: Validate Frontend-to-Backend Integration (if both exist) + +- API endpoint URLs in frontend match backend route definitions +- Shared types/interfaces are consistent +- Authentication patterns align + +### Step 6: Output Validation Report + +Output your validation report as your final message (the orchestrator will save it to `artifacts/validation/cross-layer-report.md`): + +```markdown +# Cross-Layer Validation Report + +## Layers Validated +- Contract: [yes/no] +- Frontend: [yes/no] +- Backend: [yes/no] + +## Findings + +### MISMATCH (route to responsible agent) +- [CLV-001] Frontend calls `contract.stake()` but ABI has no `stake` method + - File: src/hooks/useStaking.ts:42 + - Route to: frontend-dev (fix the method call) or contract-dev (add the method) + +### WARNING (inform auditor) +- [CLV-002] Contract address hardcoded in src/config.ts instead of env variable + - File: src/config.ts:12 + +### PASS +- [CLV-003] All 8 frontend contract calls map to valid ABI methods +- [CLV-004] Network config consistent across all layers (opnetTestnet) + +## Summary +Total checks: N +Passed: N +Mismatches: N (must be fixed before audit) +Warnings: N (informational) +``` + +## Output Format + +Output your findings as your final response text. The orchestrator saves the report file. +- MISMATCH items should be routed to the responsible builder agent. +- WARNING items are passed to the auditor as context. +- PASS items confirm correct integration. + +## Rules + +1. You are READ-ONLY. Never modify files. +2. Findings are warnings, not blockers — the auditor makes the final call. +3. Check every contract call in frontend/backend, not just a sample. +4. Always report the exact file and line number for each finding. +5. If only one layer exists, skip validation and report "single-layer." +6. Distinguish between "method missing from ABI" (likely a bug) and "method exists but params don't match" (could be intentional). diff --git a/commands/buidl.md b/commands/buidl.md index 6415275..cc86cdf 100644 --- a/commands/buidl.md +++ b/commands/buidl.md @@ -121,11 +121,34 @@ After every agent dispatch completes: ## Learning Consultation (Phase 4 Step 0) -Before dispatching builders, scan `${CLAUDE_PLUGIN_ROOT}/learning/` for past retrospectives: -1. List all `.md` files in the learning directory. +Before dispatching builders, consult the adaptive learning system: + +### Step 0a: Pattern Store +1. Read `${CLAUDE_PLUGIN_ROOT}/learning/patterns.yaml`. +2. Filter patterns by: project type (opnet/generic), tech stack match, and category (contract/frontend/backend). +3. For each matching pattern, include its `description` and `fix` in the relevant agent's dispatch prompt. +4. Patterns with `promoted_to_knowledge: true` are already in knowledge slices — no need to duplicate. + +### Step 0b: Agent Performance Scores +1. Read `${CLAUDE_PLUGIN_ROOT}/learning/agent-scores.yaml`. +2. For each agent to be dispatched, check: + - If `sessions_completed >= 5`: include success rate and weakness areas in the dispatch prompt. + - If `success_rate < 0.5` and `sessions_completed >= 5`: suggest model upgrade to user ("frontend-dev has a 40% success rate on Sonnet — consider using `--builder-model opus`"). +3. Agent scores are informational — do not auto-switch models without user approval. + +### Step 0c: Retrospectives (existing) +1. List all `.md` files in `${CLAUDE_PLUGIN_ROOT}/learning/` directory. 2. If any exist, read their "What Worked" and "Anti-Patterns" sections. 3. If a retrospective matches the current project type or tech stack, incorporate its lessons into agent prompts. -4. This is advisory — do not block on missing retrospectives. + +### Step 0d: Starter Templates +1. Check `${CLAUDE_PLUGIN_ROOT}/templates/starters/` for directories matching the project type. +2. If a matching template exists (e.g., `op20-token/` for an OP-20 project): + - Read `template.yaml` to understand customization points. + - Include the template path in the agent dispatch prompt with instruction: "Clone from this template and customize according to the spec. Do NOT modify the template files themselves." +3. This is advisory — agents may choose to build from scratch if the template doesn't fit. + +All steps are advisory — do not block on missing data. --- @@ -558,11 +581,33 @@ Common issue flows at this stage: If the same agent pair has already been re-dispatched twice, defer to the auditor — it will catch remaining issues. +#### Step 2b.5: Cross-Layer Validation (CONDITIONAL — multi-component builds only) + +**Only runs when `components.count >= 2` (multi-component build). Single-component builds skip this.** + +Update state: `bash ${CLAUDE_PLUGIN_ROOT}/scripts/write-state.sh current_phase=validating status=validating` + +Launch `cross-layer-validator` agent: +- Knowledge: `knowledge/slices/cross-layer-validation.md` +- Import: ABI from `artifacts/contract/abi.json` +- Scope: ALL frontend and backend source files +- Output: `artifacts/validation/cross-layer-report.md` +- `max_turns: 15` + +**Decision after validation:** +- If MISMATCH findings exist: route each to the responsible builder agent (check the "Route to" field) +- After fixes: re-run cross-layer validator (max 2 cycles) +- WARNING findings are passed to the auditor as context in the next step +- PASS findings confirm correct integration + +This step catches ABI mismatches, wrong method names, parameter type errors, contract address inconsistencies, and network config conflicts BEFORE the auditor runs — saving entire audit cycles. + #### Step 2c: Security Audit Launch `opnet-auditor` agent: - Knowledge: `knowledge/slices/security-audit.md` - Scope: ALL source files across all components +- Import: Cross-layer validation report from `artifacts/validation/cross-layer-report.md` (if available — pass WARNING findings as additional context) - Output: `artifacts/audit/findings.md` **Decision after audit:** @@ -831,6 +876,24 @@ Duration: - [Concrete suggestions for similar future projects] ``` +### Update Adaptive Learning System + +After writing the retrospective, update the pattern store and agent scores: + +1. **Extract patterns** from the retrospective: + ```bash + bash ${CLAUDE_PLUGIN_ROOT}/scripts/extract-patterns.sh ${CLAUDE_PLUGIN_ROOT}/learning/.md + ``` + This reads anti-patterns and failures, appends them to `learning/patterns.yaml`, deduplicates, and auto-promotes patterns with 3+ occurrences to knowledge slices. + +2. **Update agent scores** from the session state: + ```bash + bash ${CLAUDE_PLUGIN_ROOT}/scripts/update-scores.sh .claude/loop/state.yaml + ``` + This reads agent_status from state, computes rolling metrics (success rate, avg cycles, tokens), and updates `learning/agent-scores.yaml`. + +Both scripts are idempotent — safe to re-run if interrupted. + ### Final Checkpoint Write final checkpoint.md with all phases completed and outcome. diff --git a/hooks/scripts/guard-state-bash.sh b/hooks/scripts/guard-state-bash.sh index 86a1f2e..6f44f4c 100755 --- a/hooks/scripts/guard-state-bash.sh +++ b/hooks/scripts/guard-state-bash.sh @@ -45,7 +45,7 @@ if echo "$COMMAND" | grep -qE '(state\.yaml|state\.local\.md)' && echo "$COMMAND STATUS=$(grep '^status:' "$STATE_FILE" | head -1 | awk '{print $2}' || true) case "$STATUS" in - challenging|specifying|exploring|building|reviewing|auditing|deploying|testing|e2e_testing) + challenging|specifying|exploring|building|reviewing|auditing|deploying|testing|e2e_testing|validating) echo '{"decision":"block","reason":"Shell commands writing to state files are blocked during active loops. Use write-state.sh instead: bash ${CLAUDE_PLUGIN_ROOT}/scripts/write-state.sh key=value"}' >&2 exit 2 ;; diff --git a/hooks/scripts/guard-state.sh b/hooks/scripts/guard-state.sh index 3a26d28..8ebbf6c 100755 --- a/hooks/scripts/guard-state.sh +++ b/hooks/scripts/guard-state.sh @@ -57,7 +57,7 @@ STATUS=$(grep '^status:' "$STATE_FILE" | head -1 | awk '{print $2}' || true) # Block during active phases case "$STATUS" in - challenging|specifying|exploring|building|reviewing|auditing|deploying|testing|e2e_testing) + challenging|specifying|exploring|building|reviewing|auditing|deploying|testing|e2e_testing|validating) echo '{"decision":"block","reason":"Direct writes to state files are blocked during active loops. Use write-state.sh instead: bash ${CLAUDE_PLUGIN_ROOT}/scripts/write-state.sh key=value"}' >&2 exit 2 ;; diff --git a/hooks/scripts/stop-hook.sh b/hooks/scripts/stop-hook.sh index 8756aad..1d01776 100755 --- a/hooks/scripts/stop-hook.sh +++ b/hooks/scripts/stop-hook.sh @@ -54,7 +54,7 @@ fi # Only block exit during active loop phases case "$STATUS" in - challenging|specifying|exploring|building|reviewing|auditing|deploying|testing|e2e_testing) + challenging|specifying|exploring|building|reviewing|auditing|deploying|testing|e2e_testing|validating) ;; *) # Not in an active loop phase — allow exit diff --git a/knowledge/slices/cross-layer-validation.md b/knowledge/slices/cross-layer-validation.md new file mode 100644 index 0000000..bab12c1 --- /dev/null +++ b/knowledge/slices/cross-layer-validation.md @@ -0,0 +1,116 @@ +# Cross-Layer Validation Knowledge + +## Purpose + +Cross-layer validation catches integration bugs between contract, frontend, and backend before the security audit runs. These bugs are the #1 cause of wasted audit and E2E testing cycles — an ABI mismatch that could be caught in 30 seconds wastes an entire auditor dispatch. + +## Common Mismatch Types + +### 1. ABI Method Not Found +**Symptom**: Frontend calls `contract.stake()` but the ABI has no `stake` method. +**Cause**: Contract-dev renamed the method, or frontend-dev assumed a method exists. +**Detection**: Parse ABI JSON, extract all method names, grep frontend for `contract.` calls. +**Fix**: Either add the method to the contract or fix the frontend call. + +### 2. Parameter Count Mismatch +**Symptom**: Frontend calls `contract.transfer(to, amount)` but ABI expects `transfer(to, amount, memo)`. +**Cause**: Contract added a parameter that frontend doesn't know about. +**Detection**: Compare parameter count in ABI definition vs frontend call. +**Fix**: Update the frontend call to match the ABI. + +### 3. Parameter Type Mismatch +**Symptom**: Frontend passes `number` for a `uint256` parameter. +**Cause**: Frontend-dev used `number` instead of `bigint`. +**Detection**: Check that uint256/u256 params use BigInt in frontend code. +**Fix**: Convert to BigInt (`BigInt(amount)` or `amount as bigint`). + +### 4. Contract Address Inconsistency +**Symptom**: Frontend uses address `0xabc...` but deployment receipt shows `0xdef...`. +**Cause**: Address was hardcoded before deployment, never updated. +**Detection**: Compare address in frontend config with deployment receipt. +**Fix**: Use environment variable or config file that gets updated post-deployment. + +### 5. Network Mismatch +**Symptom**: Frontend uses `networks.testnet` but contract deployed to `networks.opnetTestnet`. +**Cause**: Common OPNet mistake — `networks.testnet` is Testnet4, not OPNet testnet. +**Detection**: Grep for `networks.testnet` in frontend/backend, flag if not `opnetTestnet`. +**Fix**: Change to `networks.opnetTestnet`. + +### 6. Signer Configuration +**Symptom**: Frontend passes `signer: wallet.keypair` (leaks private key). +**Cause**: Frontend-dev copied backend pattern. +**Detection**: Grep frontend for `signer:` not followed by `null`. +**Fix**: Frontend must always use `signer: null, mldsaSigner: null`. + +### 7. Event Name Mismatch +**Symptom**: Frontend listens for `Transfer` event but contract emits `TokenTransfer`. +**Cause**: Contract-dev changed event name. +**Detection**: Compare event names in ABI with frontend event listeners. +**Fix**: Align event names. + +### 8. Return Type Mismatch +**Symptom**: Frontend expects `transfer()` to return `boolean` but OP-20 returns empty BytesWriter. +**Cause**: Frontend-dev assumed Ethereum-style return values. +**Detection**: Check ABI return types vs frontend handling. +**Fix**: Handle the actual return type from the ABI. + +## How to Read an OPNet ABI + +OPNet ABIs are `BitcoinInterfaceAbi` arrays — each element is either a `FunctionBaseData` or `EventBaseData`: + +```typescript +// Function entry +{ + "name": "transfer", + "type": "function", + "inputs": [ + { "name": "to", "type": "ADDRESS" }, + { "name": "amount", "type": "UINT256" } + ], + "outputs": [ + { "name": "success", "type": "BOOL" } + ], + "modifier": "nonpayable" +} + +// Event entry +{ + "name": "Transfer", + "type": "event", + "values": [ + { "name": "from", "type": "ADDRESS" }, + { "name": "to", "type": "ADDRESS" }, + { "name": "amount", "type": "UINT256" } + ] +} +``` + +## Validation Checklist + +For each frontend/backend file that imports contract interaction code: + +- [ ] Every `contract.methodName()` call exists in the ABI +- [ ] Parameter count matches ABI definition +- [ ] uint256 params use BigInt (not number) +- [ ] Address params use Address type or valid string format +- [ ] Contract address comes from config/env (not hardcoded) +- [ ] Network is `networks.opnetTestnet` for testnet (not `networks.testnet`) +- [ ] Frontend uses `signer: null, mldsaSigner: null` +- [ ] Backend uses `signer: wallet.keypair, mldsaSigner: wallet.mldsaKeypair` +- [ ] Event listeners match ABI event names +- [ ] Return value handling matches ABI output types + +## Routing Decisions + +When a mismatch is found, determine which agent should fix it: + +| Mismatch | Route to | Reasoning | +|----------|----------|-----------| +| Method missing from ABI | contract-dev | Contract needs the method | +| Frontend calls wrong method name | frontend-dev | Frontend has a typo | +| Parameter type mismatch | frontend-dev | Frontend should match ABI | +| Contract address hardcoded | frontend-dev | Frontend config issue | +| Network mismatch | frontend-dev or backend-dev | Whoever has the wrong network | +| Signer leak in frontend | frontend-dev | Security fix | +| Event name mismatch | Depends | Check which side changed | +| Return type handling | frontend-dev | Frontend should adapt to ABI | diff --git a/learning/agent-scores.yaml b/learning/agent-scores.yaml new file mode 100644 index 0000000..1d27985 --- /dev/null +++ b/learning/agent-scores.yaml @@ -0,0 +1,114 @@ +# Agent Performance Scores — Auto-updated after each session +# Schema per agent: +# sessions_completed: total sessions where this agent was dispatched +# success_rate: fraction completed without human intervention (0.0-1.0) +# avg_cycles_to_pass: average review cycles before PASS +# avg_tokens: average tokens consumed per session +# strengths: [auto-detected from successful task categories] +# weaknesses: [auto-detected from failed task categories] +# model_history: +# - model: sonnet|opus|haiku +# sessions: N +# success_rate: 0.0-1.0 +# +# Managed by: scripts/update-scores.sh +# Do not edit manually — scores are auto-updated from session state. +# Requires 5+ data points before scores are shown in /buidl-status. + +agents: + opnet-contract-dev: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + opnet-frontend-dev: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + opnet-backend-dev: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + opnet-auditor: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + opnet-deployer: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + opnet-e2e-tester: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + opnet-ui-tester: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + loop-builder: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + loop-reviewer: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + cross-layer-validator: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + loop-explorer: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] + loop-researcher: + sessions_completed: 0 + success_rate: 0.0 + avg_cycles_to_pass: 0 + avg_tokens: 0 + strengths: [] + weaknesses: [] + model_history: [] diff --git a/learning/patterns.yaml b/learning/patterns.yaml new file mode 100644 index 0000000..0febe89 --- /dev/null +++ b/learning/patterns.yaml @@ -0,0 +1,18 @@ +# Pattern Store — Auto-extracted from session retrospectives +# Schema: +# id: PAT-LNNN (L = learned, sequential) +# category: contract|frontend|backend|deployment|testing|orchestration +# tech_stack: [list of relevant technologies] +# failure_type: runtime_error|build_error|type_error|security|logic|config|process +# description: One-line description of the pattern +# fix: What to do about it +# source_sessions: [list of session names where this appeared] +# occurrence_count: N +# promoted_to_knowledge: true|false (auto-promoted when count >= 3) +# first_seen: YYYY-MM-DD +# last_seen: YYYY-MM-DD +# +# Managed by: scripts/extract-patterns.sh +# Do not edit manually — patterns are auto-extracted from retrospectives. + +patterns: [] diff --git a/scripts/extract-patterns.sh b/scripts/extract-patterns.sh new file mode 100755 index 0000000..19b642f --- /dev/null +++ b/scripts/extract-patterns.sh @@ -0,0 +1,262 @@ +#!/bin/bash +# extract-patterns.sh — Extract patterns from a session retrospective into the pattern store +# +# Usage: bash scripts/extract-patterns.sh +# +# Reads the retrospective markdown, extracts "Anti-Patterns" and "What Failed" +# sections, and appends structured patterns to learning/patterns.yaml. +# Deduplicates by description similarity. Auto-promotes patterns with 3+ occurrences +# to the relevant knowledge slice with a [LEARNED] tag. +# +# Exit codes: +# 0 — Success +# 1 — Missing arguments or file not found + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +PATTERNS_FILE="$SCRIPT_DIR/learning/patterns.yaml" +RETRO_FILE="${1:-}" + +if [[ -z "$RETRO_FILE" || ! -f "$RETRO_FILE" ]]; then + echo "Usage: bash scripts/extract-patterns.sh " >&2 + exit 1 +fi + +# Ensure patterns file exists +if [[ ! -f "$PATTERNS_FILE" ]]; then + echo "Error: patterns.yaml not found at $PATTERNS_FILE" >&2 + exit 1 +fi + +# Extract session name from retrospective +SESSION_NAME=$(grep '^# Retrospective:' "$RETRO_FILE" | head -1 | sed 's/^# Retrospective: //' || echo "unknown") +PROJECT_TYPE=$(grep '^Project Type:' "$RETRO_FILE" | head -1 | awk '{print $3}' || echo "generic") +TODAY=$(date '+%Y-%m-%d') + +# Extract anti-patterns section (between "## Anti-Patterns" and next "##") +# || true prevents pipefail exit when grep finds no matches +ANTI_PATTERNS=$(sed -n '/^## Anti-Patterns/,/^## /{/^## Anti-Patterns/d;/^## /d;p}' "$RETRO_FILE" | grep '^- ' | sed 's/^- //' || true) + +# Extract what-failed section +WHAT_FAILED=$(sed -n '/^## What Failed/,/^## /{/^## What Failed/d;/^## /d;p}' "$RETRO_FILE" | grep '^- ' | sed 's/^- //' || true) + +# Combine both sections +ALL_PATTERNS="$ANTI_PATTERNS" +if [[ -n "$WHAT_FAILED" ]]; then + ALL_PATTERNS="$ALL_PATTERNS"$'\n'"$WHAT_FAILED" +fi + +# Skip if no patterns found +if [[ -z "$ALL_PATTERNS" ]]; then + echo "No patterns found in $RETRO_FILE" + exit 0 +fi + +# Get current max pattern ID +MAX_ID=$(grep 'id: PAT-L' "$PATTERNS_FILE" | tail -1 | sed 's/.*PAT-L0*//' | sed 's/[^0-9].*//' || echo "0") +if [[ -z "$MAX_ID" || "$MAX_ID" == "0" ]]; then + MAX_ID=0 +fi + +# Process each pattern +ADDED=0 +UPDATED=0 + +while IFS= read -r pattern; do + [[ -z "$pattern" ]] && continue + + # Normalize for comparison (lowercase, strip punctuation) + NORMALIZED=$(echo "$pattern" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9 ]//g' | xargs) + + # Check for existing similar pattern (first 40 chars match) + MATCH_KEY=$(echo "$NORMALIZED" | cut -c1-40) + + # Search existing patterns for a match — pass all data via stdin, not interpolation + EXISTING_LINE=$(echo "$MATCH_KEY" | python3 -c " +import yaml, sys +match_key = sys.stdin.read().strip() +patterns_file = sys.argv[1] +with open(patterns_file) as f: + data = yaml.safe_load(f) +patterns = data.get('patterns', []) if data else [] +if not patterns: + patterns = [] +for i, p in enumerate(patterns): + desc = p.get('description', '').lower() + desc_clean = ''.join(c for c in desc if c.isalnum() or c == ' ') + if match_key[:30] in desc_clean[:50] or desc_clean[:30] in match_key[:50]: + print(f'{i}|{p.get(\"id\", \"\")}|{p.get(\"occurrence_count\", 1)}') + break +" "$PATTERNS_FILE" 2>/dev/null || echo "") + + if [[ -n "$EXISTING_LINE" ]]; then + # Update existing pattern: increment count, add session + IDX=$(echo "$EXISTING_LINE" | cut -d'|' -f1) + PAT_ID=$(echo "$EXISTING_LINE" | cut -d'|' -f2) + OLD_COUNT=$(echo "$EXISTING_LINE" | cut -d'|' -f3) + NEW_COUNT=$((OLD_COUNT + 1)) + + # Pass all variable data via command-line args, not string interpolation + python3 -c " +import yaml, sys +patterns_file = sys.argv[1] +idx = int(sys.argv[2]) +new_count = int(sys.argv[3]) +today = sys.argv[4] +session_name = sys.argv[5] + +with open(patterns_file) as f: + data = yaml.safe_load(f) +patterns = data.get('patterns', []) +patterns[idx]['occurrence_count'] = new_count +patterns[idx]['last_seen'] = today +sessions = patterns[idx].get('source_sessions', []) +if session_name not in sessions: + sessions.append(session_name) + patterns[idx]['source_sessions'] = sessions +if new_count >= 3: + patterns[idx]['promoted_to_knowledge'] = True +data['patterns'] = patterns +with open(patterns_file, 'w') as f: + yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) +" "$PATTERNS_FILE" "$IDX" "$NEW_COUNT" "$TODAY" "$SESSION_NAME" 2>/dev/null + + UPDATED=$((UPDATED + 1)) + + # Auto-promote if count >= 3: append to relevant knowledge slice with [LEARNED] tag + if [[ "$NEW_COUNT" -ge 3 ]]; then + # Read category from the existing pattern to map to the right knowledge slice + SLICE_FILE="" + CATEGORY_FOR_SLICE=$(python3 -c " +import yaml, sys +with open(sys.argv[1]) as f: + data = yaml.safe_load(f) +for p in data.get('patterns', []): + if p.get('id') == sys.argv[2]: + print(p.get('category', 'orchestration')) + break +" "$PATTERNS_FILE" "$PAT_ID" 2>/dev/null || echo "orchestration") + case "$CATEGORY_FOR_SLICE" in + frontend) SLICE_FILE="$SCRIPT_DIR/knowledge/slices/frontend-dev.md" ;; + contract) SLICE_FILE="$SCRIPT_DIR/knowledge/slices/contract-dev.md" ;; + backend) SLICE_FILE="$SCRIPT_DIR/knowledge/slices/backend-dev.md" ;; + deployment) SLICE_FILE="$SCRIPT_DIR/knowledge/slices/deployment.md" ;; + testing) SLICE_FILE="$SCRIPT_DIR/knowledge/slices/e2e-testing.md" ;; + *) SLICE_FILE="$SCRIPT_DIR/knowledge/slices/integration-review.md" ;; + esac + + if [[ -f "$SLICE_FILE" ]]; then + # Check if this pattern is already in the slice + if ! grep -q "$PAT_ID" "$SLICE_FILE" 2>/dev/null; then + printf '\n## [LEARNED] %s\n\n%s\n' "$PAT_ID" "$pattern" >> "$SLICE_FILE" + echo " PROMOTED: $PAT_ID (count=$NEW_COUNT) — appended to $(basename "$SLICE_FILE")" + else + echo " PROMOTED: $PAT_ID (count=$NEW_COUNT) — already in $(basename "$SLICE_FILE")" + fi + else + echo " PROMOTED: $PAT_ID (count=$NEW_COUNT) — no slice file found for category" + fi + fi + else + # Add new pattern + MAX_ID=$((MAX_ID + 1)) + PAT_ID=$(printf "PAT-L%03d" "$MAX_ID") + + # Detect category from content + CATEGORY="orchestration" + if echo "$pattern" | grep -qi "frontend\|react\|vite\|css\|ui\|wallet"; then + CATEGORY="frontend" + elif echo "$pattern" | grep -qi "contract\|wasm\|assembly\|storage\|op-20\|op20"; then + CATEGORY="contract" + elif echo "$pattern" | grep -qi "backend\|api\|server\|express\|mongo"; then + CATEGORY="backend" + elif echo "$pattern" | grep -qi "deploy\|gas\|transaction\|testnet"; then + CATEGORY="deployment" + elif echo "$pattern" | grep -qi "test\|e2e\|playwright\|smoke"; then + CATEGORY="testing" + fi + + # Detect failure type + FAILURE_TYPE="process" + if echo "$pattern" | grep -qi "runtime\|crash\|console error"; then + FAILURE_TYPE="runtime_error" + elif echo "$pattern" | grep -qi "build\|compile\|lint"; then + FAILURE_TYPE="build_error" + elif echo "$pattern" | grep -qi "type\|typescript\|interface"; then + FAILURE_TYPE="type_error" + elif echo "$pattern" | grep -qi "security\|leak\|private key\|exploit"; then + FAILURE_TYPE="security" + elif echo "$pattern" | grep -qi "config\|env\|network\|port"; then + FAILURE_TYPE="config" + elif echo "$pattern" | grep -qi "logic\|bug\|incorrect\|wrong"; then + FAILURE_TYPE="logic" + fi + + # Pass all variable data via stdin JSON + command-line args + printf '%s' "$pattern" | python3 -c " +import yaml, sys, json + +patterns_file = sys.argv[1] +pat_id = sys.argv[2] +category = sys.argv[3] +project_type = sys.argv[4] +failure_type = sys.argv[5] +session_name = sys.argv[6] +today = sys.argv[7] +description = sys.stdin.read() + +with open(patterns_file) as f: + data = yaml.safe_load(f) +if not data: + data = {'patterns': []} +patterns = data.get('patterns', []) +if patterns is None: + patterns = [] +patterns.append({ + 'id': pat_id, + 'category': category, + 'tech_stack': [project_type], + 'failure_type': failure_type, + 'description': description, + 'fix': '', + 'source_sessions': [session_name], + 'occurrence_count': 1, + 'promoted_to_knowledge': False, + 'first_seen': today, + 'last_seen': today +}) +data['patterns'] = patterns +with open(patterns_file, 'w') as f: + yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) +" "$PATTERNS_FILE" "$PAT_ID" "$CATEGORY" "$PROJECT_TYPE" "$FAILURE_TYPE" "$SESSION_NAME" "$TODAY" 2>/dev/null + + ADDED=$((ADDED + 1)) + fi +done <<< "$ALL_PATTERNS" + +# Prune if pattern count exceeds 200 (remove lowest-frequency + oldest) +python3 -c " +import yaml, sys + +patterns_file = sys.argv[1] +max_patterns = 200 + +with open(patterns_file) as f: + data = yaml.safe_load(f) +patterns = data.get('patterns', []) +if not patterns or len(patterns) <= max_patterns: + sys.exit(0) + +# Sort by occurrence_count (ascending), then last_seen (ascending) — prune from bottom +patterns.sort(key=lambda p: (p.get('occurrence_count', 0), p.get('last_seen', ''))) +pruned = len(patterns) - max_patterns +patterns = patterns[pruned:] +data['patterns'] = patterns + +with open(patterns_file, 'w') as f: + yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) +print(f'Pruned {pruned} low-frequency patterns (cap: {max_patterns})') +" "$PATTERNS_FILE" + +echo "Pattern extraction complete: $ADDED added, $UPDATED updated from $SESSION_NAME" diff --git a/scripts/update-scores.sh b/scripts/update-scores.sh new file mode 100755 index 0000000..cb7e182 --- /dev/null +++ b/scripts/update-scores.sh @@ -0,0 +1,165 @@ +#!/bin/bash +# update-scores.sh — Update agent performance scores after a session completes +# +# Usage: bash scripts/update-scores.sh +# state-file: path to the session's state.yaml +# session-outcome: "pass" or "fail" +# +# Reads agent_status from the state file, updates rolling metrics in +# learning/agent-scores.yaml for each agent that was dispatched. +# The session-outcome parameter determines whether overall session success +# is factored into per-agent scoring when individual agent status is ambiguous. +# +# Exit codes: +# 0 — Success +# 1 — Missing arguments or file not found + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +SCORES_FILE="$SCRIPT_DIR/learning/agent-scores.yaml" +STATE_FILE="${1:-}" +OUTCOME="${2:-}" + +if [[ -z "$STATE_FILE" || ! -f "$STATE_FILE" ]]; then + echo "Usage: bash scripts/update-scores.sh " >&2 + exit 1 +fi + +if [[ "$OUTCOME" != "pass" && "$OUTCOME" != "fail" ]]; then + echo "Error: outcome must be 'pass' or 'fail'" >&2 + exit 1 +fi + +if [[ ! -f "$SCORES_FILE" ]]; then + echo "Error: agent-scores.yaml not found at $SCORES_FILE" >&2 + exit 1 +fi + +# Extract relevant fields from state +CYCLE=$(grep '^cycle:' "$STATE_FILE" | head -1 | awk '{print $2}' || echo "1") +TOKENS=$(grep '^tokens_used:' "$STATE_FILE" | head -1 | awk '{print $2}' || echo "0") +BUILDER_MODEL=$(grep '^builder_model:' "$STATE_FILE" | head -1 | awk '{print $2}' || echo "sonnet") + +# Parse agent_status block to find which agents were dispatched +# Pass state file path via argv, not interpolation +DISPATCHED_AGENTS=$(python3 -c " +import yaml, sys +state_file = sys.argv[1] +with open(state_file) as f: + state = yaml.safe_load(f) +agent_status = state.get('agent_status', {}) +for agent, status in agent_status.items(): + if status not in ('pending', ''): + print(f'{agent}|{status}') +" "$STATE_FILE" 2>/dev/null || echo "") + +if [[ -z "$DISPATCHED_AGENTS" ]]; then + echo "No dispatched agents found in state file" + exit 0 +fi + +# Count dispatched agents for per-agent token share calculation +DISPATCHED_COUNT=$(echo "$DISPATCHED_AGENTS" | grep -c '|' || echo "1") + +UPDATED=0 + +while IFS= read -r line; do + [[ -z "$line" ]] && continue + AGENT=$(echo "$line" | cut -d'|' -f1) + STATUS=$(echo "$line" | cut -d'|' -f2) + + # Determine if this agent succeeded + # Individual agent status takes priority; session outcome is fallback for ambiguous states + AGENT_SUCCESS=0 + if [[ "$STATUS" == "done" || "$STATUS" == "pass" || "$STATUS" == "success" ]]; then + AGENT_SUCCESS=1 + elif [[ "$STATUS" == "dispatched" || "$STATUS" == "unknown" ]]; then + # Ambiguous status: use session-level outcome as fallback + if [[ "$OUTCOME" == "pass" ]]; then + AGENT_SUCCESS=1 + fi + fi + + # Determine model used + MODEL="$BUILDER_MODEL" + if [[ "$MODEL" == "inherit" ]]; then + MODEL="sonnet" + fi + + # Update scores — pass all variable data via command-line args + python3 -c " +import yaml, sys + +scores_file = sys.argv[1] +agent_name = sys.argv[2] +agent_success = int(sys.argv[3]) +cycle = int(sys.argv[4]) +total_tokens = int(sys.argv[5]) +dispatched_count = max(int(sys.argv[6]), 1) +model = sys.argv[7] + +with open(scores_file) as f: + data = yaml.safe_load(f) + +agents = data.get('agents', {}) +if agent_name not in agents: + agents[agent_name] = { + 'sessions_completed': 0, + 'success_rate': 0.0, + 'avg_cycles_to_pass': 0, + 'avg_tokens': 0, + 'strengths': [], + 'weaknesses': [], + 'model_history': [] + } + +a = agents[agent_name] +old_count = a.get('sessions_completed', 0) +new_count = old_count + 1 +a['sessions_completed'] = new_count + +# Rolling average for success rate +old_rate = a.get('success_rate', 0.0) +a['success_rate'] = round(((old_rate * old_count) + agent_success) / new_count, 3) + +# Rolling average for cycles +old_cycles = a.get('avg_cycles_to_pass', 0) +a['avg_cycles_to_pass'] = round(((old_cycles * old_count) + cycle) / new_count, 1) + +# Rolling average for tokens (approximate per-agent share) +per_agent_tokens = total_tokens // dispatched_count +old_tokens = a.get('avg_tokens', 0) +a['avg_tokens'] = int(((old_tokens * old_count) + per_agent_tokens) / new_count) + +# Update model history +model_history = a.get('model_history', []) +found = False +for mh in model_history: + if mh.get('model') == model: + old_mh_count = mh.get('sessions', 0) + new_mh_count = old_mh_count + 1 + mh['sessions'] = new_mh_count + old_mh_rate = mh.get('success_rate', 0.0) + mh['success_rate'] = round(((old_mh_rate * old_mh_count) + agent_success) / new_mh_count, 3) + found = True + break +if not found: + model_history.append({ + 'model': model, + 'sessions': 1, + 'success_rate': float(agent_success) + }) +a['model_history'] = model_history + +agents[agent_name] = a +data['agents'] = agents + +with open(scores_file, 'w') as f: + yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) +" "$SCORES_FILE" "$AGENT" "$AGENT_SUCCESS" "$CYCLE" "$TOKENS" "$DISPATCHED_COUNT" "$MODEL" 2>/dev/null + + UPDATED=$((UPDATED + 1)) +done <<< "$DISPATCHED_AGENTS" + +echo "Agent scores updated: $UPDATED agents from session (outcome=$OUTCOME, cycle=$CYCLE)" diff --git a/templates/starters/op20-token/contract/asconfig.json b/templates/starters/op20-token/contract/asconfig.json new file mode 100644 index 0000000..81028df --- /dev/null +++ b/templates/starters/op20-token/contract/asconfig.json @@ -0,0 +1,24 @@ +{ + "targets": { + "debug": { + "outFile": "build/MyToken-debug.wasm", + "textFile": "build/MyToken-debug.wat", + "sourceMap": true, + "debug": true + }, + "release": { + "outFile": "build/MyToken.wasm", + "textFile": "build/MyToken.wat", + "sourceMap": true, + "optimizeLevel": 3, + "shrinkLevel": 1, + "converge": false, + "noAssert": false + } + }, + "options": { + "bindings": "esm", + "exportRuntime": true, + "lib": ["node_modules/@btc-vision/btc-runtime/runtime"] + } +} diff --git a/templates/starters/op20-token/contract/src/MyToken.ts b/templates/starters/op20-token/contract/src/MyToken.ts new file mode 100644 index 0000000..85b5003 --- /dev/null +++ b/templates/starters/op20-token/contract/src/MyToken.ts @@ -0,0 +1,98 @@ +// OP-20 Token Template — Clone and customize +// Replace: MyToken, MTK, decimals, supply values +// +// This template follows all OPNet conventions: +// - No approve() (use increaseAllowance/decreaseAllowance) +// - BigInt for all amounts +// - Proper storage pointers +// - metadata() returns all token info in one call + +import { + Address, + Blockchain, + BytesWriter, + Calldata, + OP20, + Revert, + SafeMath, + StoredU256, + u256, +} from '@btc-vision/btc-runtime/runtime'; + +@final +export class MyToken extends OP20 { + // ─── Token Configuration ─── + // CUSTOMIZE: Replace these values with your token's details + private readonly TOKEN_NAME: string = 'MyToken'; + private readonly TOKEN_SYMBOL: string = 'MTK'; + private readonly TOKEN_DECIMALS: u8 = 18; + private readonly MAX_SUPPLY: u256 = SafeMath.mul( + u256.fromU64(10_000_000), + SafeMath.pow(u256.fromU64(10), u256.fromU64(18)), + ); + private readonly INITIAL_SUPPLY: u256 = SafeMath.mul( + u256.fromU64(1_000_000), + SafeMath.pow(u256.fromU64(10), u256.fromU64(18)), + ); + + // ─── Storage ─── + private readonly maxSupplyPointer: u16 = Blockchain.nextPointer; + private readonly maxSupplyStored: StoredU256 = new StoredU256( + this.maxSupplyPointer, + u256.Zero, + ); + + constructor() { + super(); + } + + public override onDeployment(_calldata: Calldata): void { + // Set max supply + this.maxSupplyStored.value = this.MAX_SUPPLY; + + // Mint initial supply to deployer + const deployer: Address = Blockchain.txOrigin; + this._mint(deployer, this.INITIAL_SUPPLY); + } + + // ─── OP-20 Required Overrides ─── + + public override name(): string { + return this.TOKEN_NAME; + } + + public override symbol(): string { + return this.TOKEN_SYMBOL; + } + + public override decimals(): u8 { + return this.TOKEN_DECIMALS; + } + + // ─── Optional Features (uncomment as needed) ─── + + // FEATURE: Burnable + // Uncomment this block if features.burnable = true + /* + public burn(amount: u256): BytesWriter { + const caller: Address = Blockchain.msgSender; + this._burn(caller, amount); + return new BytesWriter(0); + } + */ + + // FEATURE: Mintable (owner-only) + // Uncomment this block if features.mintable = true + /* + public mint(to: Address, amount: u256): BytesWriter { + this.onlyOwner(Blockchain.msgSender); + const currentSupply: u256 = this.totalSupply(); + const newSupply: u256 = SafeMath.add(currentSupply, amount); + if (u256.gt(newSupply, this.maxSupplyStored.value)) { + Revert('Exceeds max supply'); + } + this._mint(to, amount); + return new BytesWriter(0); + } + */ +} diff --git a/templates/starters/op20-token/contract/tests/MyToken.test.ts b/templates/starters/op20-token/contract/tests/MyToken.test.ts new file mode 100644 index 0000000..585be8f --- /dev/null +++ b/templates/starters/op20-token/contract/tests/MyToken.test.ts @@ -0,0 +1,98 @@ +// OP-20 Token Test Template — Clone and customize +// Replace: MyToken references with your token class name + +import { Address, Blockchain } from '@btc-vision/unit-test-framework'; +import { MyToken } from '../src/MyToken'; + +describe('MyToken', () => { + let token: MyToken; + const deployer: Address = Address.dead(); + const alice: Address = Blockchain.generateRandomAddress(); + const bob: Address = Blockchain.generateRandomAddress(); + + beforeEach(() => { + Blockchain.msgSender = deployer; + Blockchain.txOrigin = deployer; + token = new MyToken(); + // Simulate deployment + token.onDeployment(Blockchain.encodeCalldata([])); + }); + + describe('metadata', () => { + it('should return correct name', () => { + // CUSTOMIZE: Update expected values + expect(token.name()).toBe('MyToken'); + }); + + it('should return correct symbol', () => { + expect(token.symbol()).toBe('MTK'); + }); + + it('should return correct decimals', () => { + expect(token.decimals()).toBe(18); + }); + }); + + describe('initial supply', () => { + it('should mint initial supply to deployer', () => { + const balance = token.balanceOf(deployer); + // CUSTOMIZE: Update expected initial supply + expect(balance).toBeGreaterThan(0n); + }); + + it('should set total supply to initial supply', () => { + const total = token.totalSupply(); + expect(total).toBeGreaterThan(0n); + }); + }); + + describe('transfer', () => { + it('should transfer tokens between accounts', () => { + const amount = 1000n * (10n ** 18n); + Blockchain.msgSender = deployer; + + token.transfer(alice, amount); + + expect(token.balanceOf(alice)).toBe(amount); + }); + + it('should fail transfer with insufficient balance', () => { + const amount = 1000n * (10n ** 18n); + Blockchain.msgSender = alice; // alice has no tokens + + expect(() => token.transfer(bob, amount)).toThrow(); + }); + }); + + describe('allowance', () => { + it('should increase allowance', () => { + const amount = 500n * (10n ** 18n); + Blockchain.msgSender = deployer; + + token.increaseAllowance(alice, amount); + + expect(token.allowance(deployer, alice)).toBe(amount); + }); + + it('should decrease allowance', () => { + const amount = 500n * (10n ** 18n); + Blockchain.msgSender = deployer; + + token.increaseAllowance(alice, amount); + token.decreaseAllowance(alice, 200n * (10n ** 18n)); + + expect(token.allowance(deployer, alice)).toBe(300n * (10n ** 18n)); + }); + + it('should transferFrom with allowance', () => { + const amount = 500n * (10n ** 18n); + Blockchain.msgSender = deployer; + token.increaseAllowance(alice, amount); + + Blockchain.msgSender = alice; + token.transferFrom(deployer, bob, amount); + + expect(token.balanceOf(bob)).toBe(amount); + }); + }); +}); diff --git a/templates/starters/op20-token/frontend/package.json b/templates/starters/op20-token/frontend/package.json new file mode 100644 index 0000000..bc72765 --- /dev/null +++ b/templates/starters/op20-token/frontend/package.json @@ -0,0 +1,32 @@ +{ + "name": "my-token-frontend", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint src/", + "typecheck": "tsc --noEmit", + "preview": "vite preview" + }, + "dependencies": { + "opnet": "^1.0.0", + "@btc-vision/transaction": "^1.0.0", + "@btc-vision/bitcoin": "^1.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "typescript": "^5.6.0", + "vite": "^6.0.0", + "vite-plugin-node-polyfills": "^0.22.0", + "eslint": "^9.0.0" + }, + "overrides": { + "@noble/hashes": "2.0.1" + } +} diff --git a/templates/starters/op20-token/frontend/src/App.tsx b/templates/starters/op20-token/frontend/src/App.tsx new file mode 100644 index 0000000..ec46e19 --- /dev/null +++ b/templates/starters/op20-token/frontend/src/App.tsx @@ -0,0 +1,144 @@ +// OP-20 Token Frontend Template +// CUSTOMIZE: Replace MyToken references, contract address, and ABI import +// +// This template follows all OPNet frontend conventions: +// - useWalletConnect() (NOT useWallet()) +// - signer: null, mldsaSigner: null (wallet handles signing) +// - Dark mode, glass-morphism, CSS custom properties +// - Skeleton loaders (NOT spinners) +// - Explorer links for every transaction (mempool + OPScan) + +// CUSTOMIZE: Uncomment these imports after wiring up wallet + token hooks +// import { useState, useEffect, useCallback } from 'react'; +// import { useWalletConnect } from './hooks/useWalletConnect'; +// import { useToken } from './hooks/useToken'; + +function App(): JSX.Element { + // CUSTOMIZE: Uncomment and configure wallet + token hooks + // const { address, isConnected, connect, disconnect } = useWalletConnect(); + // const { metadata, balance, transfer, isLoading } = useToken(address); + + return ( +
+ {/* Header */} +
+

+ {/* CUSTOMIZE: Token name */} + MyToken +

+ +
+ + {/* Main Content */} +
+ {/* Token Info Card */} +
+

Token Info

+ {/* CUSTOMIZE: Replace with actual token data from useToken hook */} +
+

Name: MyToken

+

Symbol: MTK

+

Balance: --

+
+
+ + {/* Transfer Card */} +
+

Transfer

+ {/* CUSTOMIZE: Wire up transfer form with useToken hook */} +
+ + + +
+
+
+
+ ); +} + +export default App; diff --git a/templates/starters/op20-token/frontend/src/hooks/useToken.ts b/templates/starters/op20-token/frontend/src/hooks/useToken.ts new file mode 100644 index 0000000..ff13476 --- /dev/null +++ b/templates/starters/op20-token/frontend/src/hooks/useToken.ts @@ -0,0 +1,122 @@ +// useToken — Hook for OP-20 token interactions +// CUSTOMIZE: Replace CONTRACT_ADDRESS with your deployed contract address +// CUSTOMIZE: Import your token's ABI +// +// This hook follows all OPNet frontend conventions: +// - getContract from opnet package (NOT @btc-vision/transaction) +// - signer: null, mldsaSigner: null (wallet handles signing) +// - BigInt for all amounts +// - Simulate before send + +import { useState, useEffect, useCallback } from 'react'; +// import { getContract, JSONRpcProvider } from 'opnet'; +// import { networks } from '@btc-vision/bitcoin'; +// import type { OP20Contract } from 'opnet'; + +// CUSTOMIZE: Replace with your deployed contract address +// const CONTRACT_ADDRESS = 'bcrt1p...your-contract-address'; + +// CUSTOMIZE: Set your network +// const NETWORK = networks.opnetTestnet; +// const PROVIDER_URL = 'https://testnet.opnet.org'; + +interface TokenMetadata { + name: string; + symbol: string; + decimals: number; + totalSupply: bigint; +} + +interface UseTokenReturn { + metadata: TokenMetadata | null; + balance: bigint; + transfer: (to: string, amount: bigint) => Promise; + isLoading: boolean; + error: string | null; +} + +// CUSTOMIZE: Uncomment and configure this hook after deploying your contract +export function useToken(_address: string | null): UseTokenReturn { + const [metadata, setMetadata] = useState(null); + const [balance, setBalance] = useState(0n); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // CUSTOMIZE: Uncomment to fetch token metadata on mount + useEffect(() => { + if (!_address) return; + + // Example metadata fetch (uncomment and configure): + // const fetchMetadata = async () => { + // setIsLoading(true); + // try { + // const provider = new JSONRpcProvider(PROVIDER_URL, NETWORK); + // const contract = getContract(CONTRACT_ADDRESS, provider); + // const name = await contract.name(); + // const symbol = await contract.symbol(); + // const decimals = await contract.decimals(); + // const totalSupply = await contract.totalSupply(); + // setMetadata({ name, symbol, decimals: Number(decimals), totalSupply }); + // } catch (err) { + // setError(err instanceof Error ? err.message : 'Failed to fetch metadata'); + // } finally { + // setIsLoading(false); + // } + // }; + // void fetchMetadata(); + }, [_address]); + + // CUSTOMIZE: Uncomment to fetch balance when address changes + useEffect(() => { + if (!_address) return; + + // Example balance fetch (uncomment and configure): + // const fetchBalance = async () => { + // try { + // const provider = new JSONRpcProvider(PROVIDER_URL, NETWORK); + // const contract = getContract(CONTRACT_ADDRESS, provider); + // const bal = await contract.balanceOf(_address); + // setBalance(bal); + // } catch (err) { + // setError(err instanceof Error ? err.message : 'Failed to fetch balance'); + // } + // }; + // void fetchBalance(); + }, [_address]); + + // CUSTOMIZE: Uncomment and wire up transfer + const transfer = useCallback(async (_to: string, _amount: bigint): Promise => { + setError(null); + + // Example transfer (uncomment and configure): + // try { + // setIsLoading(true); + // const provider = new JSONRpcProvider(PROVIDER_URL, NETWORK); + // const contract = getContract(CONTRACT_ADDRESS, provider); + // + // // Simulate first + // const sim = await contract.transfer(to, amount); + // if (!sim.result) { + // setError('Transfer simulation failed'); + // return null; + // } + // + // // Send with wallet signing (signer: null on frontend) + // const tx = await contract.sendTransaction(sim, { + // signer: null, + // mldsaSigner: null, + // }); + // + // return tx.result; + // } catch (err) { + // setError(err instanceof Error ? err.message : 'Transfer failed'); + // return null; + // } finally { + // setIsLoading(false); + // } + + return null; + }, []); + + return { metadata, balance, transfer, isLoading, error }; +} diff --git a/templates/starters/op20-token/frontend/vite.config.ts b/templates/starters/op20-token/frontend/vite.config.ts new file mode 100644 index 0000000..851fb08 --- /dev/null +++ b/templates/starters/op20-token/frontend/vite.config.ts @@ -0,0 +1,44 @@ +// OPNet-Ready Vite Config Template +// Includes all required polyfills, shims, and deduplication + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { nodePolyfills } from 'vite-plugin-node-polyfills'; + +export default defineConfig({ + plugins: [ + react(), + nodePolyfills({ + include: ['buffer', 'crypto', 'stream', 'util', 'process', 'events'], + globals: { Buffer: true, global: true, process: true }, + }), + ], + resolve: { + alias: { + // undici shim — required for OPNet RPC calls + undici: 'node_modules/undici/lib/web/fetch/index.js', + }, + dedupe: [ + '@btc-vision/bitcoin', + '@btc-vision/transaction', + '@noble/hashes', + '@noble/curves', + 'opnet', + ], + }, + build: { + target: 'esnext', + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], + opnet: ['opnet', '@btc-vision/transaction'], + crypto: ['@noble/hashes', '@noble/curves'], + }, + }, + }, + }, + define: { + global: 'globalThis', + }, +}); diff --git a/templates/starters/op20-token/template.yaml b/templates/starters/op20-token/template.yaml new file mode 100644 index 0000000..36c7041 --- /dev/null +++ b/templates/starters/op20-token/template.yaml @@ -0,0 +1,43 @@ +# OP-20 Token Starter Template +# Clone and customize — do NOT modify these template files during build. + +name: op20-token +description: Standard OP-20 fungible token with metadata, transfer, and allowance +project_type: opnet +components: + contract: true + frontend: true + backend: false + +# Customization points — agent should replace these placeholders +customization: + token_name: "MyToken" # Replace with actual token name + token_symbol: "MTK" # Replace with actual symbol + token_decimals: 18 # Standard is 18 + initial_supply: "1000000" # In whole tokens (will be multiplied by 10^decimals) + max_supply: "10000000" # Maximum supply cap (0 = unlimited) + features: + burnable: false # Add burn() method + mintable: false # Add mint() method (owner-only) + pausable: false # Add pause/unpause (owner-only) + +# Files to clone +contract_files: + - src/MyToken.ts # Main contract — rename to match token_name + - tests/MyToken.test.ts # Unit tests — rename to match + - asconfig.json # AssemblyScript config + +frontend_files: + - src/App.tsx # Main app with wallet connect + token UI + - src/hooks/useToken.ts # Token interaction hook + - vite.config.ts # OPNet-ready Vite config with polyfills + - package.json # Dependencies with semver ranges + +# Post-clone instructions for agents +instructions: | + 1. Replace all placeholder values (MyToken, MTK, etc.) with spec values + 2. If features.burnable/mintable/pausable are true, uncomment the relevant sections + 3. Update package.json name field + 4. Run npm install after customizing package.json + 5. Run the full verify pipeline (lint, typecheck, build, test) + 6. Do NOT add features not in the spec — the template is the starting point, not the final product diff --git a/tests/plugin-tests.sh b/tests/plugin-tests.sh index be911cb..eed4088 100755 --- a/tests/plugin-tests.sh +++ b/tests/plugin-tests.sh @@ -919,6 +919,210 @@ else fail "guard-state-bash.sh does NOT exempt write-state.sh" fi +# ───────────────────────────────────────────────── +echo "" +echo "=== Adaptive Learning System ===" + +# Pattern store +if [[ -f "learning/patterns.yaml" ]]; then + pass "learning/patterns.yaml exists" +else + fail "learning/patterns.yaml MISSING" +fi + +if python3 -c "import yaml; yaml.safe_load(open('learning/patterns.yaml'))" 2>/dev/null; then + pass "learning/patterns.yaml is valid YAML" +else + fail "learning/patterns.yaml is NOT valid YAML" +fi + +# Agent scores +if [[ -f "learning/agent-scores.yaml" ]]; then + pass "learning/agent-scores.yaml exists" +else + fail "learning/agent-scores.yaml MISSING" +fi + +if python3 -c "import yaml; yaml.safe_load(open('learning/agent-scores.yaml'))" 2>/dev/null; then + pass "learning/agent-scores.yaml is valid YAML" +else + fail "learning/agent-scores.yaml is NOT valid YAML" +fi + +# Extraction scripts +if [[ -f "scripts/extract-patterns.sh" ]]; then + pass "scripts/extract-patterns.sh exists" +else + fail "scripts/extract-patterns.sh MISSING" +fi + +if bash -n scripts/extract-patterns.sh 2>/dev/null; then + pass "extract-patterns.sh passes bash -n" +else + fail "extract-patterns.sh FAILS bash -n" +fi + +if [[ -x "scripts/extract-patterns.sh" ]]; then + pass "extract-patterns.sh is executable" +else + fail "extract-patterns.sh is NOT executable" +fi + +if [[ -f "scripts/update-scores.sh" ]]; then + pass "scripts/update-scores.sh exists" +else + fail "scripts/update-scores.sh MISSING" +fi + +if bash -n scripts/update-scores.sh 2>/dev/null; then + pass "update-scores.sh passes bash -n" +else + fail "update-scores.sh FAILS bash -n" +fi + +if [[ -x "scripts/update-scores.sh" ]]; then + pass "update-scores.sh is executable" +else + fail "update-scores.sh is NOT executable" +fi + +# Orchestrator references +if grep -q 'patterns.yaml' commands/buidl.md; then + pass "buidl.md references patterns.yaml" +else + fail "buidl.md does NOT reference patterns.yaml" +fi + +if grep -q 'agent-scores.yaml' commands/buidl.md; then + pass "buidl.md references agent-scores.yaml" +else + fail "buidl.md does NOT reference agent-scores.yaml" +fi + +if grep -q 'extract-patterns.sh' commands/buidl.md; then + pass "buidl.md references extract-patterns.sh" +else + fail "buidl.md does NOT reference extract-patterns.sh" +fi + +if grep -q 'update-scores.sh' commands/buidl.md; then + pass "buidl.md references update-scores.sh" +else + fail "buidl.md does NOT reference update-scores.sh" +fi + +# ───────────────────────────────────────────────── +echo "" +echo "=== Cross-Layer Validator ===" + +if [[ -f "agents/cross-layer-validator.md" ]]; then + pass "cross-layer-validator.md exists" +else + fail "cross-layer-validator.md MISSING" +fi + +for section in "## Constraints" "## Step 0" "## Process" "## Output Format" "## Rules"; do + if grep -q "$section" agents/cross-layer-validator.md; then + pass "cross-layer-validator has '$section'" + else + fail "cross-layer-validator MISSING '$section'" + fi +done + +if [[ -f "knowledge/slices/cross-layer-validation.md" ]]; then + pass "cross-layer-validation.md knowledge slice exists" +else + fail "cross-layer-validation.md knowledge slice MISSING" +fi + +if grep -q 'cross-layer-validation.md' agents/cross-layer-validator.md; then + pass "cross-layer-validator references its knowledge slice" +else + fail "cross-layer-validator does NOT reference its knowledge slice" +fi + +if grep -q 'cross-layer-validator' commands/buidl.md; then + pass "buidl.md references cross-layer-validator" +else + fail "buidl.md does NOT reference cross-layer-validator" +fi + +if grep -q 'validating' hooks/scripts/stop-hook.sh; then + pass "stop-hook.sh includes 'validating' phase" +else + fail "stop-hook.sh MISSING 'validating' phase" +fi + +if grep -q 'validating' hooks/scripts/guard-state.sh; then + pass "guard-state.sh includes 'validating' phase" +else + fail "guard-state.sh MISSING 'validating' phase" +fi + +if grep -q 'validating' hooks/scripts/guard-state-bash.sh; then + pass "guard-state-bash.sh includes 'validating' phase" +else + fail "guard-state-bash.sh MISSING 'validating' phase" +fi + +# ───────────────────────────────────────────────── +echo "" +echo "=== Starter Templates ===" + +if [[ -d "templates/starters/op20-token" ]]; then + pass "op20-token starter template directory exists" +else + fail "op20-token starter template directory MISSING" +fi + +if [[ -f "templates/starters/op20-token/template.yaml" ]]; then + pass "op20-token template.yaml exists" +else + fail "op20-token template.yaml MISSING" +fi + +if [[ -f "templates/starters/op20-token/contract/src/MyToken.ts" ]]; then + pass "op20-token contract source exists" +else + fail "op20-token contract source MISSING" +fi + +if [[ -f "templates/starters/op20-token/contract/tests/MyToken.test.ts" ]]; then + pass "op20-token contract tests exist" +else + fail "op20-token contract tests MISSING" +fi + +if [[ -f "templates/starters/op20-token/contract/asconfig.json" ]]; then + pass "op20-token asconfig.json exists" +else + fail "op20-token asconfig.json MISSING" +fi + +if [[ -f "templates/starters/op20-token/frontend/src/App.tsx" ]]; then + pass "op20-token frontend App.tsx exists" +else + fail "op20-token frontend App.tsx MISSING" +fi + +if [[ -f "templates/starters/op20-token/frontend/vite.config.ts" ]]; then + pass "op20-token frontend vite.config.ts exists" +else + fail "op20-token frontend vite.config.ts MISSING" +fi + +if [[ -f "templates/starters/op20-token/frontend/package.json" ]]; then + pass "op20-token frontend package.json exists" +else + fail "op20-token frontend package.json MISSING" +fi + +if grep -q 'starters' commands/buidl.md; then + pass "buidl.md references starter templates" +else + fail "buidl.md does NOT reference starter templates" +fi + # ───────────────────────────────────────────────── # Summary # ─────────────────────────────────────────────────