diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index 211188d5..00000000
Binary files a/.DS_Store and /dev/null differ
diff --git a/.agents/skills/deploy/SKILL.md b/.agents/skills/deploy/SKILL.md
deleted file mode 100644
index 90229b3c..00000000
--- a/.agents/skills/deploy/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: deploy
-description: Deploy to homelab. Build Docker image, transfer, and restart container. Use for lthn.sh deployments.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: See deployment skill instructions
diff --git a/.agents/skills/dispatch/SKILL.md b/.agents/skills/dispatch/SKILL.md
deleted file mode 100644
index ade1b5ff..00000000
--- a/.agents/skills/dispatch/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: dispatch
-description: Dispatch a subagent to work on a task in a sandboxed workspace. Use when you need to send work to Gemini, Codex, or Claude agents.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: agentic_dispatch
diff --git a/.agents/skills/pipeline/SKILL.md b/.agents/skills/pipeline/SKILL.md
deleted file mode 100644
index cf866378..00000000
--- a/.agents/skills/pipeline/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: pipeline
-description: Run the review-fix-verify pipeline on code changes. Dispatches reviewer, then fixer, then verifier.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: agentic_dispatch reviewer → wait → agentic_dispatch fixer → wait → verify
diff --git a/.agents/skills/recall/SKILL.md b/.agents/skills/recall/SKILL.md
deleted file mode 100644
index 7d9cfee1..00000000
--- a/.agents/skills/recall/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: recall
-description: Search OpenBrain for memories and context. Use when you need prior session knowledge or architecture context.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: brain_recall
diff --git a/.agents/skills/remember/SKILL.md b/.agents/skills/remember/SKILL.md
deleted file mode 100644
index ce189967..00000000
--- a/.agents/skills/remember/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: remember
-description: Save a fact or decision to OpenBrain. Use to persist knowledge across sessions.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: brain_remember
diff --git a/.agents/skills/review/SKILL.md b/.agents/skills/review/SKILL.md
deleted file mode 100644
index e26dbb08..00000000
--- a/.agents/skills/review/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: review
-description: Review completed agent workspace. Show output, git diff, and merge options. Use after an agent completes a task.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: agentic_status + read agent log + git diff
diff --git a/.agents/skills/scan/SKILL.md b/.agents/skills/scan/SKILL.md
deleted file mode 100644
index 1a4c775a..00000000
--- a/.agents/skills/scan/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: scan
-description: Scan Forge repos for open issues with actionable labels. Use to find work to dispatch.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: agentic_scan
diff --git a/.agents/skills/status/SKILL.md b/.agents/skills/status/SKILL.md
deleted file mode 100644
index 7aadb783..00000000
--- a/.agents/skills/status/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: status
-description: Show status of all agent workspaces (running, completed, blocked, failed). Use to check pipeline progress.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: agentic_status
diff --git a/.agents/skills/sweep/SKILL.md b/.agents/skills/sweep/SKILL.md
deleted file mode 100644
index d816dc46..00000000
--- a/.agents/skills/sweep/SKILL.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: sweep
-description: Batch audit across all repos using agent dispatch. Use for ecosystem-wide convention checks.
----
-
-Use the core-agent MCP tools to execute this skill.
-Call the appropriate tool: agentic_dispatch in a loop across repos
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 087a32a1..67bce21c 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -8,13 +8,13 @@
"plugins": [
{
"name": "core",
- "source": "./claude/core",
+ "source": "./provider/claude/core",
"description": "CoreAgent platform — dispatch, review, messaging, OpenBrain",
"version": "0.14.0"
},
{
"name": "core-research",
- "source": "./claude/research",
+ "source": "./provider/claude/research",
"description": "Blockchain archaeology, whitepaper archival, community history, market data collection",
"version": "0.3.0"
},
@@ -47,7 +47,7 @@
},
{
"name": "devops",
- "source": "./claude/devops",
+ "source": "./provider/claude/devops",
"description": "Agent workflow utilities — install binaries, merge workspaces, update deps, clean queues",
"version": "0.1.0"
}
diff --git a/.codex/agents/fixer.toml b/.codex/agents/fixer.toml
deleted file mode 100644
index b93c6647..00000000
--- a/.codex/agents/fixer.toml
+++ /dev/null
@@ -1,25 +0,0 @@
-# Review Findings Fixer
-# Implements fixes from reviewer findings
-
-name = "fixer"
-description = "Fix code review findings. Takes a list of findings with file:line references and implements the fixes. Creates EXCEPTIONS.md for items that cannot be fixed."
-developer_instructions = """
-You are the Review Findings Fixer for the Core ecosystem.
-
-You receive a list of findings from the reviewer agent.
-For each finding:
-1. Read the file at the specified line
-2. Implement the fix following Core conventions
-3. If a fix is impossible (e.g. circular import), add to EXCEPTIONS.md with reason
-
-After fixing:
-- Run go build ./... to verify
-- Run go vet ./... to verify
-- Run go test ./... if tests exist
-
-Commit message format: fix(pkg): description of fixes
-
-Do not add features. Do not refactor beyond the finding. Minimal changes only.
-"""
-model = "gpt-5.4"
-sandbox_mode = "workspace-write"
diff --git a/.codex/agents/migrator.toml b/.codex/agents/migrator.toml
deleted file mode 100644
index 521d4ffe..00000000
--- a/.codex/agents/migrator.toml
+++ /dev/null
@@ -1,32 +0,0 @@
-# Core Primitives Migrator
-# Migrates packages from separate deps to Core built-ins
-
-name = "migrator"
-description = "Migrate Go packages to use Core primitives instead of separate go-io/go-log/strings/fmt packages. Use when upgrading a package to the new Core API."
-developer_instructions = """
-You are the Core Primitives Migrator for the Core ecosystem.
-
-Read .core/reference/RFC-025-AGENT-EXPERIENCE.md for the AX spec.
-Read .core/reference/*.go for the Core framework API.
-
-Migration pattern:
-- coreio.Local.Read(path) → fs.Read(path) returning core.Result
-- coreio.Local.Write(path, s) → fs.Write(path, s) returning core.Result
-- coreio.Local.List(path) → fs.List(path) returning core.Result
-- coreio.Local.EnsureDir(path) → fs.EnsureDir(path) returning core.Result
-- coreio.Local.IsFile(path) → fs.IsFile(path) returning bool
-- coreio.Local.Delete(path) → fs.Delete(path) returning core.Result
-- coreerr.E("op", "msg", err) → core.E("op", "msg", err)
-- log.Error/Info/Warn → core.Error/Info/Warn
-- strings.Contains → core.Contains
-- strings.Split → core.Split
-- strings.TrimSpace → core.Trim
-- strings.HasPrefix → core.HasPrefix
-- fmt.Sprintf → core.Sprintf
-- embed.FS → core.Mount() + core.Embed
-
-Add AX usage-example comments to all public types and functions.
-Build must pass after migration.
-"""
-model = "gpt-5.4"
-sandbox_mode = "workspace-write"
diff --git a/.codex/agents/reviewer.toml b/.codex/agents/reviewer.toml
deleted file mode 100644
index 4a08ea54..00000000
--- a/.codex/agents/reviewer.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-# AX Convention Reviewer
-# Audits code against RFC-025 Agent Experience spec
-
-name = "reviewer"
-description = "Audit Go code against AX conventions (RFC-025). Use for code review, convention checking, and quality assessment. Read-only — never modifies code."
-developer_instructions = """
-You are the AX Convention Reviewer for the Core ecosystem.
-
-Read .core/reference/RFC-025-AGENT-EXPERIENCE.md for the full spec.
-Read .core/reference/*.go for the Core framework API.
-
-Audit all Go files against these conventions:
-1. Predictable names — no abbreviations (Cfg→Config, Srv→Service)
-2. Comments as usage examples — show HOW with real values
-3. Result pattern — core.Result not (value, error)
-4. Error handling — core.E("op", "msg", err) not fmt.Errorf
-5. Core string ops — core.Contains/Split/Trim not strings.*
-6. Core logging — core.Error/Info/Warn not log.*
-7. Core filesystem — core.Fs{} not os.ReadFile
-8. UK English — initialise not initialize
-9. Import aliasing — stdlib io as goio
-10. Compile-time assertions — var _ Interface = (*Impl)(nil)
-
-Report findings with severity (critical/high/medium/low) and file:line.
-Group by package. Do NOT fix — report only.
-"""
-model = "gpt-5.4"
-sandbox_mode = "read-only"
diff --git a/.codex/rules/core-agent.rules b/.codex/rules/core-agent.rules
deleted file mode 100644
index ea16b441..00000000
--- a/.codex/rules/core-agent.rules
+++ /dev/null
@@ -1,67 +0,0 @@
-# Core Agent — Codex Rules
-# Controls which commands can run outside the sandbox
-
-# Go toolchain — always safe
-prefix_rule(
- pattern = ["go", ["build", "test", "vet", "fmt", "mod", "get", "work"]],
- decision = "allow",
- justification = "Go development tools are safe read/build operations",
- match = [["go", "build", "./..."], ["go", "test", "./pkg/agentic"]],
- not_match = [["go", "run", "main.go"]],
-)
-
-# Core agent binary
-prefix_rule(
- pattern = ["core-agent", ["mcp", "--version"]],
- decision = "allow",
- justification = "Core agent MCP server and version check",
-)
-
-# Git read operations
-prefix_rule(
- pattern = ["git", ["status", "log", "diff", "branch", "tag", "remote", "fetch", "rev-parse", "ls-remote"]],
- decision = "allow",
- justification = "Read-only git operations are safe",
-)
-
-# Git write — prompt for approval
-prefix_rule(
- pattern = ["git", ["add", "commit", "merge", "rebase", "stash"]],
- decision = "prompt",
- justification = "Git write operations need human approval",
-)
-
-# Git push — forbidden (use PR workflow)
-prefix_rule(
- pattern = ["git", "push"],
- decision = "forbidden",
- justification = "Never push directly — use PR workflow via agentic_create_pr",
-)
-
-# Git destructive — forbidden
-prefix_rule(
- pattern = ["git", ["reset", "clean"], "--force"],
- decision = "forbidden",
- justification = "Destructive git operations are never allowed",
-)
-
-# Curl — prompt (network access)
-prefix_rule(
- pattern = ["curl"],
- decision = "prompt",
- justification = "Network requests need approval",
-)
-
-# SSH — forbidden
-prefix_rule(
- pattern = ["ssh"],
- decision = "forbidden",
- justification = "Direct SSH is forbidden — use Ansible via deployment skills",
-)
-
-# rm -rf — forbidden
-prefix_rule(
- pattern = ["rm", "-rf"],
- decision = "forbidden",
- justification = "Recursive force delete is never allowed",
-)
diff --git a/.core/TODO.md b/.core/TODO.md
deleted file mode 100644
index e69de29b..00000000
diff --git a/config/agents.yaml b/.core/agents.yaml
similarity index 100%
rename from config/agents.yaml
rename to .core/agents.yaml
diff --git a/.core/docs/core-folder-spec.md b/.core/docs/core-folder-spec.md
deleted file mode 100644
index a185db1e..00000000
--- a/.core/docs/core-folder-spec.md
+++ /dev/null
@@ -1,319 +0,0 @@
-# .core/ Folder Specification
-
-This document defines the `.core/` folder structure used across Host UK packages for configuration, tooling integration, and development environment setup.
-
-## Overview
-
-The `.core/` folder provides a standardised location for:
-- Build and development configuration
-- Claude Code plugin integration
-- VM/container definitions
-- Development environment settings
-
-## Directory Structure
-
-```
-package/.core/
-├── config.yaml # Build targets, test commands, deploy config
-├── workspace.yaml # Workspace-level config (devops repo only)
-├── plugin/ # Claude Code integration
-│ ├── plugin.json # Plugin manifest
-│ ├── skills/ # Context-aware skills
-│ └── hooks/ # Pre/post command hooks
-├── linuxkit/ # VM/container definitions (if applicable)
-│ ├── kernel.yaml
-│ └── image.yaml
-└── run.yaml # Development environment config
-```
-
-## Configuration Files
-
-### config.yaml
-
-Package-level build and runtime configuration.
-
-```yaml
-version: 1
-
-# Build configuration
-build:
- targets:
- - name: default
- command: composer build
- - name: production
- command: composer build:prod
- env:
- APP_ENV: production
-
-# Test configuration
-test:
- command: composer test
- coverage: true
- parallel: true
-
-# Lint configuration
-lint:
- command: ./vendor/bin/pint
- fix_command: ./vendor/bin/pint --dirty
-
-# Deploy configuration (if applicable)
-deploy:
- staging:
- command: ./deploy.sh staging
- production:
- command: ./deploy.sh production
- requires_approval: true
-```
-
-### workspace.yaml
-
-Workspace-level configuration (only in `core-devops`).
-
-```yaml
-version: 1
-
-# Active package for unified commands
-active: core-php
-
-# Default package types for setup
-default_only:
- - foundation
- - module
-
-# Paths
-packages_dir: ./packages
-
-# Workspace settings
-settings:
- suggest_core_commands: true
- show_active_in_prompt: true
-```
-
-### run.yaml
-
-Development environment configuration.
-
-```yaml
-version: 1
-
-# Services required for development
-services:
- - name: database
- image: postgres:16
- port: 5432
- env:
- POSTGRES_DB: core_dev
- POSTGRES_USER: core
- POSTGRES_PASSWORD: secret
-
- - name: redis
- image: redis:7
- port: 6379
-
- - name: mailpit
- image: axllent/mailpit
- port: 8025
-
-# Development server
-dev:
- command: php artisan serve
- port: 8000
- watch:
- - app/
- - resources/
-
-# Environment variables
-env:
- APP_ENV: local
- APP_DEBUG: true
- DB_CONNECTION: pgsql
-```
-
-## Claude Code Plugin
-
-### plugin.json
-
-The plugin manifest defines skills, hooks, and commands for Claude Code integration.
-
-```json
-{
- "$schema": "https://claude.ai/code/plugin-schema.json",
- "name": "package-name",
- "version": "1.0.0",
- "description": "Claude Code integration for this package",
-
- "skills": [
- {
- "name": "skill-name",
- "file": "skills/skill-name.md",
- "description": "What this skill provides"
- }
- ],
-
- "hooks": {
- "pre_command": [
- {
- "pattern": "^command-pattern$",
- "script": "hooks/script.sh",
- "description": "What this hook does"
- }
- ]
- },
-
- "commands": {
- "command-name": {
- "description": "What this command does",
- "run": "actual-command"
- }
- }
-}
-```
-
-### Skills (skills/*.md)
-
-Markdown files providing context-aware guidance for Claude Code. Skills are loaded when relevant to the user's query.
-
-```markdown
-# Skill Name
-
-Describe what this skill provides.
-
-## Context
-
-When to use this skill.
-
-## Commands
-
-Relevant commands and examples.
-
-## Tips
-
-Best practices and gotchas.
-```
-
-### Hooks (hooks/*.sh)
-
-Shell scripts executed before or after commands. Hooks should:
-- Be executable (`chmod +x`)
-- Exit 0 for informational hooks (don't block)
-- Exit non-zero to block the command (with reason)
-
-```bash
-#!/bin/bash
-set -euo pipefail
-
-# Hook logic here
-
-exit 0 # Don't block
-```
-
-## LinuxKit (linuxkit/)
-
-For packages that deploy as VMs or containers.
-
-### kernel.yaml
-
-```yaml
-kernel:
- image: linuxkit/kernel:6.6
- cmdline: "console=tty0"
-```
-
-### image.yaml
-
-```yaml
-image:
- - linuxkit/init:v1.0.1
- - linuxkit/runc:v1.0.0
- - linuxkit/containerd:v1.0.0
-```
-
-## Package-Type Specific Patterns
-
-### Foundation (core-php)
-
-```
-core-php/.core/
-├── config.yaml # Build targets for framework
-├── plugin/
-│ └── skills/
-│ ├── events.md # Event system guidance
-│ ├── modules.md # Module loading patterns
-│ └── lifecycle.md # Lifecycle events
-└── run.yaml # Test environment setup
-```
-
-### Module (core-tenant, core-admin, etc.)
-
-```
-core-tenant/.core/
-├── config.yaml # Module-specific build
-├── plugin/
-│ └── skills/
-│ └── tenancy.md # Multi-tenancy patterns
-└── run.yaml # Required services (database)
-```
-
-### Product (core-bio, core-social, etc.)
-
-```
-core-bio/.core/
-├── config.yaml # Build and deploy targets
-├── plugin/
-│ └── skills/
-│ └── bio.md # Product-specific guidance
-├── linuxkit/ # VM definitions for deployment
-│ ├── kernel.yaml
-│ └── image.yaml
-└── run.yaml # Full dev environment
-```
-
-### Workspace (core-devops)
-
-```
-core-devops/.core/
-├── workspace.yaml # Active package, paths
-├── plugin/
-│ ├── plugin.json
-│ └── skills/
-│ ├── workspace.md # Multi-repo navigation
-│ ├── switch-package.md # Package switching
-│ └── package-status.md # Status checking
-└── docs/
- └── core-folder-spec.md # This file
-```
-
-## Core CLI Integration
-
-The `core` CLI reads configuration from `.core/`:
-
-| File | CLI Command | Purpose |
-|------|-------------|---------|
-| `workspace.yaml` | `core workspace` | Active package, paths |
-| `config.yaml` | `core build`, `core test` | Build/test commands |
-| `run.yaml` | `core run` | Dev environment |
-
-## Best Practices
-
-1. **Always include `version: 1`** in YAML files for future compatibility
-2. **Keep skills focused** - one concept per skill file
-3. **Hooks should be fast** - don't slow down commands
-4. **Use relative paths** - avoid hardcoded absolute paths
-5. **Document non-obvious settings** with inline comments
-
-## Migration Guide
-
-To add `.core/` to an existing package:
-
-1. Create the directory structure:
- ```bash
- mkdir -p .core/plugin/skills .core/plugin/hooks
- ```
-
-2. Add `config.yaml` with build/test commands
-
-3. Add `plugin.json` with package-specific skills
-
-4. Add relevant skills in `skills/`
-
-5. Update `.gitignore` if needed (don't ignore `.core/`)
diff --git a/.core/reference/RFC-025-AGENT-EXPERIENCE.md b/.core/reference/RFC-025-AGENT-EXPERIENCE.md
deleted file mode 100644
index a18e6bb4..00000000
--- a/.core/reference/RFC-025-AGENT-EXPERIENCE.md
+++ /dev/null
@@ -1,588 +0,0 @@
-# RFC-025: Agent Experience (AX) Design Principles
-
-- **Status:** Active
-- **Authors:** Snider, Cladius
-- **Date:** 2026-03-25
-- **Applies to:** All Core ecosystem packages (CoreGO, CorePHP, CoreTS, core-agent)
-
-## Abstract
-
-Agent Experience (AX) is a design paradigm for software systems where the primary code consumer is an AI agent, not a human developer. AX sits alongside User Experience (UX) and Developer Experience (DX) as the third era of interface design.
-
-This RFC establishes AX as a formal design principle for the Core ecosystem and defines the conventions that follow from it.
-
-## Motivation
-
-As of early 2026, AI agents write, review, and maintain the majority of code in the Core ecosystem. The original author has not manually edited code (outside of Core struct design) since October 2025. Code is processed semantically — agents reason about intent, not characters.
-
-Design patterns inherited from the human-developer era optimise for the wrong consumer:
-
-- **Short names** save keystrokes but increase semantic ambiguity
-- **Functional option chains** are fluent for humans but opaque for agents tracing configuration
-- **Error-at-every-call-site** produces 50% boilerplate that obscures intent
-- **Generic type parameters** force agents to carry type context that the runtime already has
-- **Panic-hiding conventions** (`Must*`) create implicit control flow that agents must special-case
-- **Raw exec.Command** bypasses Core primitives — untestable, no entitlement check, path traversal risk
-
-AX acknowledges this shift and provides principles for designing code, APIs, file structures, and conventions that serve AI agents as first-class consumers.
-
-## The Three Eras
-
-| Era | Primary Consumer | Optimises For | Key Metric |
-|-----|-----------------|---------------|------------|
-| UX | End users | Discoverability, forgiveness, visual clarity | Task completion time |
-| DX | Developers | Typing speed, IDE support, convention familiarity | Time to first commit |
-| AX | AI agents | Predictability, composability, semantic navigation | Correct-on-first-pass rate |
-
-AX does not replace UX or DX. End users still need good UX. Developers still need good DX. But when the primary code author and maintainer is an AI agent, the codebase should be designed for that consumer first.
-
-## Principles
-
-### 1. Predictable Names Over Short Names
-
-Names are tokens that agents pattern-match across languages and contexts. Abbreviations introduce mapping overhead.
-
-```
-Config not Cfg
-Service not Srv
-Embed not Emb
-Error not Err (as a subsystem name; err for local variables is fine)
-Options not Opts
-```
-
-**Rule:** If a name would require a comment to explain, it is too short.
-
-**Exception:** Industry-standard abbreviations that are universally understood (`HTTP`, `URL`, `ID`, `IPC`, `I18n`) are acceptable. The test: would an agent trained on any mainstream language recognise it without context?
-
-### 2. Comments as Usage Examples
-
-The function signature tells WHAT. The comment shows HOW with real values.
-
-```go
-// Entitled checks if an action is permitted.
-//
-// e := c.Entitled("process.run")
-// e := c.Entitled("social.accounts", 3)
-// if e.Allowed { proceed() }
-
-// WriteAtomic writes via temp file then rename (safe for concurrent readers).
-//
-// r := fs.WriteAtomic("/status.json", data)
-
-// Action registers or invokes a named callable.
-//
-// c.Action("git.log", handler) // register
-// c.Action("git.log").Run(ctx, opts) // invoke
-```
-
-**Rule:** If a comment restates what the type signature already says, delete it. If a comment shows a concrete usage with realistic values, keep it.
-
-**Rationale:** Agents learn from examples more effectively than from descriptions. A comment like "Run executes the setup process" adds zero information. A comment like `setup.Run(setup.Options{Path: ".", Template: "auto"})` teaches an agent exactly how to call the function.
-
-### 3. Path Is Documentation
-
-File and directory paths should be self-describing. An agent navigating the filesystem should understand what it is looking at without reading a README.
-
-```
-pkg/agentic/dispatch.go — agent dispatch logic
-pkg/agentic/handlers.go — IPC event handlers
-pkg/lib/task/bug-fix.yaml — bug fix plan template
-pkg/lib/persona/engineering/ — engineering personas
-flow/deploy/to/homelab.yaml — deploy TO the homelab
-template/dir/workspace/default/ — default workspace scaffold
-docs/RFC.md — authoritative API contract
-```
-
-**Rule:** If an agent needs to read a file to understand what a directory contains, the directory naming has failed.
-
-**Corollary:** The unified path convention (folder structure = HTTP route = CLI command = test path) is AX-native. One path, every surface.
-
-### 4. Templates Over Freeform
-
-When an agent generates code from a template, the output is constrained to known-good shapes. When an agent writes freeform, the output varies.
-
-```go
-// Template-driven — consistent output
-lib.ExtractWorkspace("default", targetDir, &lib.WorkspaceData{
- Repo: "go-io", Branch: "dev", Task: "fix tests", Agent: "codex",
-})
-
-// Freeform — variance in output
-"write a workspace setup script that..."
-```
-
-**Rule:** For any code pattern that recurs, provide a template. Templates are guardrails for agents.
-
-**Scope:** Templates apply to file generation, workspace scaffolding, config generation, and commit messages. They do NOT apply to novel logic — agents should write business logic freeform with the domain knowledge available.
-
-### 5. Declarative Over Imperative
-
-Agents reason better about declarations of intent than sequences of operations.
-
-```yaml
-# Declarative — agent sees what should happen
-steps:
- - name: build
- flow: tools/docker-build
- with:
- context: "{{ .app_dir }}"
- image_name: "{{ .image_name }}"
-
- - name: deploy
- flow: deploy/with/docker
- with:
- host: "{{ .host }}"
-```
-
-```go
-// Imperative — agent must trace execution
-cmd := exec.Command("docker", "build", "--platform", "linux/amd64", "-t", imageName, ".")
-cmd.Dir = appDir
-if err := cmd.Run(); err != nil {
- return core.E("build", "docker build failed", err)
-}
-```
-
-**Rule:** Orchestration, configuration, and pipeline logic should be declarative (YAML/JSON). Implementation logic should be imperative (Go/PHP/TS). The boundary is: if an agent needs to compose or modify the logic, make it declarative.
-
-Core's `Task` is the Go-native declarative equivalent — a sequence of named Action steps:
-
-```go
-c.Task("deploy", core.Task{
- Steps: []core.Step{
- {Action: "docker.build"},
- {Action: "docker.push"},
- {Action: "deploy.ansible", Async: true},
- },
-})
-```
-
-### 6. Core Primitives — Universal Types and DI
-
-Every component in the ecosystem registers with Core and communicates through Core's primitives. An agent processing any level of the tree sees identical shapes.
-
-#### Creating Core
-
-```go
-c := core.New(
- core.WithOption("name", "core-agent"),
- core.WithService(process.Register),
- core.WithService(agentic.Register),
- core.WithService(monitor.Register),
- core.WithService(brain.Register),
- core.WithService(mcp.Register),
-)
-c.Run() // or: if err := c.RunE(); err != nil { ... }
-```
-
-`core.New()` returns `*Core`. `WithService` registers a factory `func(*Core) Result`. Services auto-discover: name from package path, lifecycle from `Startable`/`Stoppable` (return `Result`). `HandleIPCEvents` is the one remaining magic method — auto-registered via reflection if the service implements it.
-
-#### Service Registration Pattern
-
-```go
-// Service factory — receives Core, returns Result
-func Register(c *core.Core) core.Result {
- svc := &MyService{
- ServiceRuntime: core.NewServiceRuntime(c, MyOptions{}),
- }
- return core.Result{Value: svc, OK: true}
-}
-```
-
-#### Core Subsystem Accessors
-
-| Accessor | Purpose |
-|----------|---------|
-| `c.Options()` | Input configuration |
-| `c.App()` | Application metadata (name, version) |
-| `c.Config()` | Runtime settings, feature flags |
-| `c.Data()` | Embedded assets (Registry[*Embed]) |
-| `c.Drive()` | Transport handles (Registry[*DriveHandle]) |
-| `c.Fs()` | Filesystem I/O (sandboxable) |
-| `c.Process()` | Managed execution (Action sugar) |
-| `c.API()` | Remote streams (protocol handlers) |
-| `c.Action(name)` | Named callable (register/invoke) |
-| `c.Task(name)` | Composed Action sequence |
-| `c.Entitled(name)` | Permission check |
-| `c.RegistryOf(n)` | Cross-cutting registry queries |
-| `c.Cli()` | CLI command framework |
-| `c.IPC()` | Message bus (ACTION, QUERY) |
-| `c.Log()` | Structured logging |
-| `c.Error()` | Panic recovery |
-| `c.I18n()` | Internationalisation |
-
-#### Primitive Types
-
-```go
-// Option — the atom
-core.Option{Key: "name", Value: "brain"}
-
-// Options — universal input
-opts := core.NewOptions(
- core.Option{Key: "name", Value: "myapp"},
- core.Option{Key: "port", Value: 8080},
-)
-opts.String("name") // "myapp"
-opts.Int("port") // 8080
-
-// Result — universal output
-core.Result{Value: svc, OK: true}
-```
-
-#### Named Actions — The Primary Communication Pattern
-
-Services register capabilities as named Actions. No direct function calls, no untyped dispatch — declare intent by name, invoke by name.
-
-```go
-// Register a capability during OnStartup
-c.Action("workspace.create", func(ctx context.Context, opts core.Options) core.Result {
- name := opts.String("name")
- path := core.JoinPath("/srv/workspaces", name)
- return core.Result{Value: path, OK: true}
-})
-
-// Invoke by name — typed, inspectable, entitlement-checked
-r := c.Action("workspace.create").Run(ctx, core.NewOptions(
- core.Option{Key: "name", Value: "alpha"},
-))
-
-// Check capability before calling
-if c.Action("process.run").Exists() { /* go-process is registered */ }
-
-// List all capabilities
-c.Actions() // ["workspace.create", "process.run", "brain.recall", ...]
-```
-
-#### Task Composition — Sequencing Actions
-
-```go
-c.Task("agent.completion", core.Task{
- Steps: []core.Step{
- {Action: "agentic.qa"},
- {Action: "agentic.auto-pr"},
- {Action: "agentic.verify"},
- {Action: "agentic.poke", Async: true}, // doesn't block
- },
-})
-```
-
-#### Anonymous Broadcast — Legacy Layer
-
-`ACTION` and `QUERY` remain for backwards-compatible anonymous dispatch. New code should prefer named Actions.
-
-```go
-// Broadcast — all handlers fire, type-switch to filter
-c.ACTION(messages.DeployCompleted{Env: "production"})
-
-// Query — first responder wins
-r := c.QUERY(countQuery{})
-```
-
-#### Process Execution — Use Core Primitives
-
-All external command execution MUST go through `c.Process()`, not raw `os/exec`. This makes process execution testable, gatable by entitlements, and managed by Core's lifecycle.
-
-```go
-// AX-native: Core Process primitive
-r := c.Process().RunIn(ctx, repoDir, "git", "log", "--oneline", "-20")
-if r.OK { output := r.Value.(string) }
-
-// Not AX: raw exec.Command — untestable, no entitlement, no lifecycle
-cmd := exec.Command("git", "log", "--oneline", "-20")
-cmd.Dir = repoDir
-out, err := cmd.Output()
-```
-
-**Rule:** If a package imports `os/exec`, it is bypassing Core's process primitive. The only package that should import `os/exec` is `go-process` itself.
-
-**Quality gate:** An agent reviewing a diff can mechanically check: does this import `os/exec`, `unsafe`, or `encoding/json` directly? If so, it bypassed a Core primitive.
-
-#### What This Replaces
-
-| Go Convention | Core AX | Why |
-|--------------|---------|-----|
-| `func With*(v) Option` | `core.WithOption(k, v)` | Named key-value is greppable; option chains require tracing |
-| `func Must*(v) T` | `core.Result` | No hidden panics; errors flow through Result.OK |
-| `func *For[T](c) T` | `c.Service("name")` | String lookup is greppable; generics require type context |
-| `val, err :=` everywhere | Single return via `core.Result` | Intent not obscured by error handling |
-| `exec.Command(...)` | `c.Process().Run(ctx, cmd, args...)` | Testable, gatable, lifecycle-managed |
-| `map[string]*T + mutex` | `core.Registry[T]` | Thread-safe, ordered, lockable, queryable |
-| untyped `any` dispatch | `c.Action("name").Run(ctx, opts)` | Named, typed, inspectable, entitlement-checked |
-
-### 7. Tests as Behavioural Specification
-
-Test names are structured data. An agent querying "what happens when dispatch fails?" should find the answer by scanning test names, not reading prose.
-
-```
-TestDispatch_DetectFinalStatus_Good — clean exit → completed
-TestDispatch_DetectFinalStatus_Bad — non-zero exit → failed
-TestDispatch_DetectFinalStatus_Ugly — BLOCKED.md overrides exit code
-```
-
-**Convention:** `Test{File}_{Function}_{Good|Bad|Ugly}`
-
-| Category | Purpose |
-|----------|---------|
-| `_Good` | Happy path — proves the contract works |
-| `_Bad` | Expected errors — proves error handling works |
-| `_Ugly` | Edge cases, panics, corruption — proves it doesn't blow up |
-
-**Rule:** Every testable function gets all three categories. Missing categories are gaps in the specification, detectable by scanning:
-
-```bash
-# Find under-tested functions
-for f in *.go; do
- [[ "$f" == *_test.go ]] && continue
- while IFS= read -r line; do
- fn=$(echo "$line" | sed 's/func.*) //; s/(.*//; s/ .*//')
- [[ -z "$fn" || "$fn" == register* ]] && continue
- cap="${fn^}"
- grep -q "_${cap}_Good\|_${fn}_Good" *_test.go || echo "$f: $fn missing Good"
- grep -q "_${cap}_Bad\|_${fn}_Bad" *_test.go || echo "$f: $fn missing Bad"
- grep -q "_${cap}_Ugly\|_${fn}_Ugly" *_test.go || echo "$f: $fn missing Ugly"
- done < <(grep "^func " "$f")
-done
-```
-
-**Rationale:** The test suite IS the behavioural spec. `grep _TrackFailureRate_ *_test.go` returns three concrete scenarios — no prose needed. The naming convention makes the entire test suite machine-queryable. An agent dispatched to fix a function can read its tests to understand the full contract before making changes.
-
-**What this replaces:**
-
-| Convention | AX Test Naming | Why |
-|-----------|---------------|-----|
-| `TestFoo_works` | `TestFile_Foo_Good` | File prefix enables cross-file search |
-| Unnamed table tests | Explicit Good/Bad/Ugly | Categories are scannable without reading test body |
-| Coverage % as metric | Missing categories as metric | 100% coverage with only Good tests is a false signal |
-
-### 7b. Example Tests as AX TDD
-
-Go `Example` functions serve triple duty: they run as tests (count toward coverage), show in godoc (usage documentation), and seed user guide generation.
-
-```go
-// file: action_example_test.go
-
-func ExampleAction_Run() {
- c := New()
- c.Action("double", func(_ context.Context, opts Options) Result {
- return Result{Value: opts.Int("n") * 2, OK: true}
- })
-
- r := c.Action("double").Run(context.Background(), NewOptions(
- Option{Key: "n", Value: 21},
- ))
- Println(r.Value)
- // Output: 42
-}
-```
-
-**AX TDD pattern:** Write the Example first — it defines how the API should feel. If the Example is awkward, the API is wrong. The Example IS the test, the documentation, and the design feedback loop.
-
-**Convention:** One `{source}_example_test.go` per source file. Every exported function should have at least one Example. The Example output comment makes it a verified test.
-
-**Quality gate:** A source file without a corresponding example file is missing documentation that compiles.
-
-### Operational Principles
-
-Principles 1-7 govern code design. Principles 8-10 govern how agents and humans work with the codebase.
-
-### 8. RFC as Domain Load
-
-An agent's first action in a session should be loading the repo's RFC.md. The full spec in context produces zero-correction sessions — every decision aligns with the design because the design is loaded.
-
-**Validated:** Loading core/go's RFC.md (42k tokens from a 500k token discovery session) at session start eliminated all course corrections. The spec is compressed domain knowledge that survives context compaction.
-
-**Rule:** Every repo that has non-trivial architecture should have a `docs/RFC.md`. The RFC is not documentation for humans — it's a context document for agents. It should be loadable in one read and contain everything needed to make correct decisions.
-
-### 9. Primitives as Quality Gates
-
-Core primitives become mechanical code review rules. An agent reviewing a diff checks:
-
-| Import | Violation | Use Instead |
-|--------|-----------|-------------|
-| `os` | Bypasses Fs/Env primitives | `c.Fs()`, `core.Env()`, `core.DirFS()`, `Fs.TempDir()` |
-| `os/exec` | Bypasses Process primitive | `c.Process().Run()` |
-| `io` | Bypasses stream primitives | `core.ReadAll()`, `core.WriteAll()`, `core.CloseStream()` |
-| `fmt` | Bypasses string/print primitives | `core.Println()`, `core.Sprintf()`, `core.Sprint()` |
-| `errors` | Bypasses error primitive | `core.NewError()`, `core.E()`, `core.Is()`, `core.As()` |
-| `log` | Bypasses logging | `core.Info()`, `core.Warn()`, `core.Error()`, `c.Log()` |
-| `encoding/json` | Bypasses Core serialisation | `core.JSONMarshal()`, `core.JSONUnmarshal()` |
-| `path/filepath` | Bypasses path security boundary | `core.Path()`, `core.JoinPath()`, `core.PathBase()` |
-| `unsafe` | Bypasses Fs sandbox | `Fs.NewUnrestricted()` |
-| `strings` | Bypasses string guardrails | `core.Contains()`, `core.Split()`, `core.Trim()`, etc. |
-
-**Rule:** If a diff introduces a disallowed import, it failed code review. The import list IS the quality gate. No subjective judgement needed — a weaker model can enforce this mechanically.
-
-### 10. Registration IS Capability, Entitlement IS Permission
-
-Two layers of permission, both declarative:
-
-```
-Registration = "this action EXISTS" → c.Action("process.run").Exists()
-Entitlement = "this Core is ALLOWED" → c.Entitled("process.run").Allowed
-```
-
-A sandboxed Core has no `process.run` registered — the action doesn't exist. A SaaS Core has it registered but entitlement-gated — the action exists but the workspace may not be allowed to use it.
-
-**Rule:** Never check permissions with `if` statements in business logic. Register capabilities as Actions. Gate them with Entitlements. The framework enforces both — `Action.Run()` checks both before executing.
-
-## Applying AX to Existing Patterns
-
-### File Structure
-
-```
-# AX-native: path describes content
-core/agent/
-├── cmd/core-agent/ # CLI entry point (minimal — just core.New + Run)
-├── pkg/agentic/ # Agent orchestration (dispatch, prep, verify, scan)
-├── pkg/brain/ # OpenBrain integration
-├── pkg/lib/ # Embedded templates, personas, flows
-├── pkg/messages/ # Typed IPC message definitions
-├── pkg/monitor/ # Agent monitoring + notifications
-├── pkg/setup/ # Workspace scaffolding + detection
-└── claude/ # Claude Code plugin definitions
-
-# Not AX: generic names requiring README
-src/
-├── lib/
-├── utils/
-└── helpers/
-```
-
-### Error Handling
-
-```go
-// AX-native: errors flow through Result, not call sites
-func Register(c *core.Core) core.Result {
- svc := &MyService{ServiceRuntime: core.NewServiceRuntime(c, MyOpts{})}
- return core.Result{Value: svc, OK: true}
-}
-
-// Not AX: errors dominate the code
-func Register(c *core.Core) (*MyService, error) {
- svc, err := NewMyService(c)
- if err != nil {
- return nil, fmt.Errorf("create service: %w", err)
- }
- return svc, nil
-}
-```
-
-### Command Registration
-
-```go
-// AX-native: extracted methods, testable without CLI
-func (s *MyService) OnStartup(ctx context.Context) core.Result {
- c := s.Core()
- c.Command("issue/get", core.Command{Action: s.cmdIssueGet})
- c.Command("issue/list", core.Command{Action: s.cmdIssueList})
- c.Action("forge.issue.get", s.handleIssueGet)
- return core.Result{OK: true}
-}
-
-func (s *MyService) cmdIssueGet(opts core.Options) core.Result {
- // testable business logic — no closure, no CLI dependency
-}
-
-// Not AX: closures that can only be tested via CLI integration
-c.Command("issue/get", core.Command{
- Action: func(opts core.Options) core.Result {
- // 50 lines of untestable inline logic
- },
-})
-```
-
-### Process Execution
-
-```go
-// AX-native: Core Process primitive, testable with mock handler
-func (s *MyService) getGitLog(repoPath string) string {
- r := s.Core().Process().RunIn(context.Background(), repoPath, "git", "log", "--oneline", "-20")
- if !r.OK { return "" }
- return core.Trim(r.Value.(string))
-}
-
-// Not AX: raw exec.Command — untestable, no entitlement check, path traversal risk
-func (s *MyService) getGitLog(repoPath string) string {
- cmd := exec.Command("git", "log", "--oneline", "-20")
- cmd.Dir = repoPath // user-controlled path goes directly to OS
- output, err := cmd.Output()
- if err != nil { return "" }
- return strings.TrimSpace(string(output))
-}
-```
-
-The AX-native version routes through `c.Process()` → named Action → entitlement check. The non-AX version passes user input directly to `os/exec` with no permission gate.
-
-### Permission Gating
-
-```go
-// AX-native: entitlement checked by framework, not by business logic
-c.Action("agentic.dispatch", func(ctx context.Context, opts core.Options) core.Result {
- // Action.Run() already checked c.Entitled("agentic.dispatch")
- // If we're here, we're allowed. Just do the work.
- return dispatch(ctx, opts)
-})
-
-// Not AX: permission logic scattered through business code
-func handleDispatch(ctx context.Context, opts core.Options) core.Result {
- if !isAdmin(ctx) && !hasPlan(ctx, "pro") {
- return core.Result{Value: core.E("dispatch", "upgrade required", nil), OK: false}
- }
- // duplicate permission check in every handler
-}
-```
-
-## Compatibility
-
-AX conventions are valid, idiomatic Go/PHP/TS. They do not require language extensions, code generation, or non-standard tooling. An AX-designed codebase compiles, tests, and deploys with standard toolchains.
-
-The conventions diverge from community patterns (functional options, Must/For, etc.) but do not violate language specifications. This is a style choice, not a fork.
-
-## Adoption
-
-AX applies to all code in the Core ecosystem. core/go is fully migrated (v0.8.0). Consumer packages migrate via their RFCs.
-
-Priority for migrating a package:
-1. **Lifecycle** — `OnStartup`/`OnShutdown` return `Result`
-2. **Actions** — register capabilities as named Actions
-3. **Imports** — replace all 10 disallowed imports (Principle 9)
-4. **String ops** — `+` concat → `Concat()`, `path +` → `Path()`
-5. **Test naming** — `TestFile_Function_{Good,Bad,Ugly}`
-6. **Examples** — one `{source}_example_test.go` per source file
-7. **Comments** — every exported function has usage example (Principle 2)
-
-## Verification
-
-An agent auditing AX compliance checks:
-
-```bash
-# Disallowed imports (Principle 9)
-grep -rn '"os"\|"os/exec"\|"io"\|"fmt"\|"errors"\|"log"\|"encoding/json"\|"path/filepath"\|"unsafe"\|"strings"' *.go \
- | grep -v _test.go
-
-# Test naming (Principle 7)
-grep "^func Test" *_test.go | grep -v "Test[A-Z][a-z]*_.*_\(Good\|Bad\|Ugly\)"
-
-# String concat (should use Concat/Path)
-grep -n '" + \| + "' *.go | grep -v _test.go | grep -v "//"
-
-# Untyped dispatch (should prefer named Actions)
-grep "RegisterTask\|PERFORM\|type Task any" *.go
-```
-
-If any check produces output, the code needs migration.
-
-## References
-
-- `core/go/docs/RFC.md` — CoreGO API contract (21 sections, reference implementation)
-- `core/go-process/docs/RFC.md` — Process consumer spec
-- `core/agent/docs/RFC.md` — Agent consumer spec
-- RFC-004 (Entitlements) — permission model ported to `c.Entitled()`
-- RFC-021 (Core Platform Architecture) — 7-layer stack, provider model
-- dAppServer unified path convention (2024) — path = route = command = test
-- Go Proverbs, Rob Pike (2015) — AX provides an updated lens
-
-## Changelog
-
-- 2026-03-25: v0.8.0 alignment — all examples match implemented API. Added Principles 8 (RFC as Domain Load), 9 (Primitives as Quality Gates), 10 (Registration + Entitlement). Updated subsystem table (Process, API, Action, Task, Entitled, RegistryOf). Process examples use `c.Process()` not old `process.RunWithOptions`. Removed PERFORM references.
-- 2026-03-19: Initial draft — 7 principles
diff --git a/.core/reference/app.go b/.core/reference/app.go
deleted file mode 100644
index 31631b8f..00000000
--- a/.core/reference/app.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Application identity for the Core framework.
-
-package core
-
-import (
- corefilepath "dappco.re/go"
- coreos "dappco.re/go"
-)
-
-// App holds the application identity and optional GUI runtime.
-//
-// app := core.App{}.New(core.NewOptions(
-// core.Option{Key: "name", Value: "Core CLI"},
-// core.Option{Key: "version", Value: "1.0.0"},
-// ))
-type App struct {
- Name string
- Version string
- Description string
- Filename string
- Path string
- Runtime any // GUI runtime (e.g., Wails App). Nil for CLI-only.
-}
-
-// New creates an App from Options.
-//
-// app := core.App{}.New(core.NewOptions(
-// core.Option{Key: "name", Value: "myapp"},
-// core.Option{Key: "version", Value: "1.0.0"},
-// ))
-func (a App) New(opts Options) App {
- if name := opts.String("name"); name != "" {
- a.Name = name
- }
- if version := opts.String("version"); version != "" {
- a.Version = version
- }
- if desc := opts.String("description"); desc != "" {
- a.Description = desc
- }
- if filename := opts.String("filename"); filename != "" {
- a.Filename = filename
- }
- return a
-}
-
-// Find locates a program on PATH and returns a Result containing the App.
-// Uses os.Stat to search PATH directories — no os/exec dependency.
-//
-// r := core.App{}.Find("node", "Node.js")
-// if r.OK { app := r.Value.(*App) }
-func (a App) Find(filename, name string) Result {
- // If filename contains a separator, check it directly
- if Contains(filename, string(os.PathSeparator)) {
- abs, err := filepath.Abs(filename)
- if err != nil {
- return Result{err, false}
- }
- if isExecutable(abs) {
- return Result{&App{Name: name, Filename: filename, Path: abs}, true}
- }
- return Result{E("app.Find", Concat(filename, " not found"), nil), false}
- }
-
- // Search PATH
- pathEnv := os.Getenv("PATH")
- if pathEnv == "" {
- return Result{E("app.Find", "PATH is empty", nil), false}
- }
- for _, dir := range Split(pathEnv, string(os.PathListSeparator)) {
- candidate := filepath.Join(dir, filename)
- if isExecutable(candidate) {
- abs, err := filepath.Abs(candidate)
- if err != nil {
- continue
- }
- return Result{&App{Name: name, Filename: filename, Path: abs}, true}
- }
- }
- return Result{E("app.Find", Concat(filename, " not found on PATH"), nil), false}
-}
-
-// isExecutable checks if a path exists and is executable.
-func isExecutable(path string) bool {
- info, err := os.Stat(path)
- if err != nil {
- return false
- }
- // Regular file with at least one execute bit
- return !info.IsDir() && info.Mode()&0111 != 0
-}
diff --git a/.core/reference/array.go b/.core/reference/array.go
deleted file mode 100644
index 488d1092..00000000
--- a/.core/reference/array.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Generic slice operations for the Core framework.
-// Based on leaanthony/slicer, rewritten with Go 1.18+ generics.
-//
-// arr := core.NewArray("prep", "dispatch")
-// arr.Add("verify", "merge")
-// arr.AddUnique("verify", "verify", "merge")
-
-package core
-
-// Array is a typed slice with common operations.
-type Array[T comparable] struct {
- items []T
-}
-
-// NewArray creates an empty Array.
-func NewArray[T comparable](items ...T) *Array[T] {
- return &Array[T]{items: items}
-}
-
-// Add appends values.
-func (s *Array[T]) Add(values ...T) {
- s.items = append(s.items, values...)
-}
-
-// AddUnique appends values only if not already present.
-func (s *Array[T]) AddUnique(values ...T) {
- for _, v := range values {
- if !s.Contains(v) {
- s.items = append(s.items, v)
- }
- }
-}
-
-// Contains returns true if the value is in the slice.
-func (s *Array[T]) Contains(val T) bool {
- for _, v := range s.items {
- if v == val {
- return true
- }
- }
- return false
-}
-
-// Filter returns a new Array with elements matching the predicate.
-func (s *Array[T]) Filter(fn func(T) bool) Result {
- filtered := &Array[T]{}
- for _, v := range s.items {
- if fn(v) {
- filtered.items = append(filtered.items, v)
- }
- }
- return Result{filtered, true}
-}
-
-// Each runs a function on every element.
-func (s *Array[T]) Each(fn func(T)) {
- for _, v := range s.items {
- fn(v)
- }
-}
-
-// Remove removes the first occurrence of a value.
-func (s *Array[T]) Remove(val T) {
- for i, v := range s.items {
- if v == val {
- s.items = append(s.items[:i], s.items[i+1:]...)
- return
- }
- }
-}
-
-// Deduplicate removes duplicate values, preserving order.
-func (s *Array[T]) Deduplicate() {
- seen := make(map[T]struct{})
- result := make([]T, 0, len(s.items))
- for _, v := range s.items {
- if _, exists := seen[v]; !exists {
- seen[v] = struct{}{}
- result = append(result, v)
- }
- }
- s.items = result
-}
-
-// Len returns the number of elements.
-func (s *Array[T]) Len() int {
- return len(s.items)
-}
-
-// Clear removes all elements.
-func (s *Array[T]) Clear() {
- s.items = nil
-}
-
-// AsSlice returns a copy of the underlying slice.
-func (s *Array[T]) AsSlice() []T {
- if s.items == nil {
- return nil
- }
- out := make([]T, len(s.items))
- copy(out, s.items)
- return out
-}
diff --git a/.core/reference/cli.go b/.core/reference/cli.go
deleted file mode 100644
index 32b58ce5..00000000
--- a/.core/reference/cli.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Cli is the CLI surface layer for the Core command tree.
-//
-// c := core.New(core.WithOption("name", "myapp")).Value.(*Core)
-// c.Command("deploy", core.Command{Action: handler})
-// c.Cli().Run()
-package core
-
-import (
- coreos "dappco.re/go"
- "io"
-)
-
-// CliOptions holds configuration for the Cli service.
-type CliOptions struct{}
-
-// Cli is the CLI surface for the Core command tree.
-type Cli struct {
- *ServiceRuntime[CliOptions]
- output io.Writer
- banner func(*Cli) string
-}
-
-// Register creates a Cli service factory for core.WithService.
-//
-// core.New(core.WithService(core.CliRegister))
-func CliRegister(c *Core) Result {
- cl := &Cli{output: os.Stdout}
- cl.ServiceRuntime = NewServiceRuntime[CliOptions](c, CliOptions{})
- return c.RegisterService("cli", cl)
-}
-
-// Print writes to the CLI output (defaults to os.Stdout).
-//
-// c.Cli().Print("hello %s", "world")
-func (cl *Cli) Print(format string, args ...any) {
- Print(cl.output, format, args...)
-}
-
-// SetOutput sets the CLI output writer.
-//
-// c.Cli().SetOutput(os.Stderr)
-func (cl *Cli) SetOutput(w io.Writer) {
- cl.output = w
-}
-
-// Run resolves os.Args to a command path and executes it.
-//
-// c.Cli().Run()
-// c.Cli().Run("deploy", "to", "homelab")
-func (cl *Cli) Run(args ...string) Result {
- if len(args) == 0 {
- args = os.Args[1:]
- }
-
- clean := FilterArgs(args)
- c := cl.Core()
-
- if c == nil || c.commands == nil {
- if cl.banner != nil {
- cl.Print(cl.banner(cl))
- }
- return Result{}
- }
-
- if c.commands.Len() == 0 {
- if cl.banner != nil {
- cl.Print(cl.banner(cl))
- }
- return Result{}
- }
-
- // Resolve command path from args
- var cmd *Command
- var remaining []string
-
- for i := len(clean); i > 0; i-- {
- path := JoinPath(clean[:i]...)
- if r := c.commands.Get(path); r.OK {
- cmd = r.Value.(*Command)
- remaining = clean[i:]
- break
- }
- }
-
- if cmd == nil {
- if cl.banner != nil {
- cl.Print(cl.banner(cl))
- }
- cl.PrintHelp()
- return Result{}
- }
-
- // Build options from remaining args
- opts := NewOptions()
- for _, arg := range remaining {
- key, val, valid := ParseFlag(arg)
- if valid {
- if Contains(arg, "=") {
- opts.Set(key, val)
- } else {
- opts.Set(key, true)
- }
- } else if !IsFlag(arg) {
- if !opts.Has("_arg") {
- opts.Set("_arg", arg)
- }
- argsResult := opts.Get("_args")
- args := []string{}
- if argsResult.OK {
- if existing, ok := argsResult.Value.([]string); ok {
- args = append(args, existing...)
- }
- }
- args = append(args, arg)
- opts.Set("_args", args)
- }
- }
-
- if cmd.Action != nil {
- return cmd.Run(opts)
- }
- return Result{E("core.Cli.Run", Concat("command \"", cmd.Path, "\" is not executable"), nil), false}
-}
-
-// PrintHelp prints available commands.
-//
-// c.Cli().PrintHelp()
-func (cl *Cli) PrintHelp() {
- c := cl.Core()
- if c == nil || c.commands == nil {
- return
- }
-
- name := ""
- if c.app != nil {
- name = c.app.Name
- }
- if name != "" {
- cl.Print("%s commands:", name)
- } else {
- cl.Print("Commands:")
- }
-
- c.commands.Each(func(path string, cmd *Command) {
- if cmd.Hidden || (cmd.Action == nil && !cmd.IsManaged()) {
- return
- }
- tr := c.I18n().Translate(cmd.I18nKey())
- desc, _ := tr.Value.(string)
- if desc == "" || desc == cmd.I18nKey() {
- cl.Print(" %s", path)
- } else {
- cl.Print(" %-30s %s", path, desc)
- }
- })
-}
-
-// SetBanner sets the banner function.
-//
-// c.Cli().SetBanner(func(_ *core.Cli) string { return "My App v1.0" })
-func (cl *Cli) SetBanner(fn func(*Cli) string) {
- cl.banner = fn
-}
-
-// Banner returns the banner string.
-func (cl *Cli) Banner() string {
- if cl.banner != nil {
- return cl.banner(cl)
- }
- c := cl.Core()
- if c != nil && c.app != nil && c.app.Name != "" {
- return c.app.Name
- }
- return ""
-}
diff --git a/.core/reference/command.go b/.core/reference/command.go
deleted file mode 100644
index 6e1412db..00000000
--- a/.core/reference/command.go
+++ /dev/null
@@ -1,165 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Command is a DTO representing an executable operation.
-// Commands don't know if they're root, child, or nested — the tree
-// structure comes from composition via path-based registration.
-//
-// Register a command:
-//
-// c.Command("deploy", func(opts core.Options) core.Result {
-// return core.Result{"deployed", true}
-// })
-//
-// Register a nested command:
-//
-// c.Command("deploy/to/homelab", handler)
-//
-// Description is an i18n key — derived from path if omitted:
-//
-// "deploy" → "cmd.deploy.description"
-// "deploy/to/homelab" → "cmd.deploy.to.homelab.description"
-package core
-
-// CommandAction is the function signature for command handlers.
-//
-// func(opts core.Options) core.Result
-type CommandAction func(Options) Result
-
-// Command is the DTO for an executable operation.
-// Commands are declarative — they carry enough information for multiple consumers:
-//
-// - core.Cli() runs the Action
-//
-// - core/cli adds rich help, completion, man pages
-//
-// - go-process wraps Managed commands with lifecycle (PID, health, signals)
-//
-// c.Command("serve", core.Command{
-// Action: handler,
-// Managed: "process.daemon", // go-process provides start/stop/restart
-// })
-type Command struct {
- Name string
- Description string // i18n key — derived from path if empty
- Path string // "deploy/to/homelab"
- Action CommandAction // business logic
- Managed string // "" = one-shot, "process.daemon" = managed lifecycle
- Flags Options // declared flags
- Hidden bool
- commands map[string]*Command // child commands (internal)
-}
-
-// I18nKey returns the i18n key for this command's description.
-//
-// cmd with path "deploy/to/homelab" → "cmd.deploy.to.homelab.description"
-func (cmd *Command) I18nKey() string {
- if cmd.Description != "" {
- return cmd.Description
- }
- path := cmd.Path
- if path == "" {
- path = cmd.Name
- }
- return Concat("cmd.", Replace(path, "/", "."), ".description")
-}
-
-// Run executes the command's action with the given options.
-//
-// result := cmd.Run(core.NewOptions(core.Option{Key: "target", Value: "homelab"}))
-func (cmd *Command) Run(opts Options) Result {
- if cmd.Action == nil {
- return Result{E("core.Command.Run", Concat("command \"", cmd.Path, "\" is not executable"), nil), false}
- }
- return cmd.Action(opts)
-}
-
-// IsManaged returns true if this command has a managed lifecycle.
-//
-// if cmd.IsManaged() { /* go-process handles start/stop */ }
-func (cmd *Command) IsManaged() bool {
- return cmd.Managed != ""
-}
-
-// --- Command Registry (on Core) ---
-
-// CommandRegistry holds the command tree. Embeds Registry[*Command]
-// for thread-safe named storage with insertion order.
-type CommandRegistry struct {
- *Registry[*Command]
-}
-
-// Command gets or registers a command by path.
-//
-// c.Command("deploy", Command{Action: handler})
-// r := c.Command("deploy")
-func (c *Core) Command(path string, command ...Command) Result {
- if len(command) == 0 {
- return c.commands.Get(path)
- }
-
- if path == "" || HasPrefix(path, "/") || HasSuffix(path, "/") || Contains(path, "//") {
- return Result{E("core.Command", Concat("invalid command path: \"", path, "\""), nil), false}
- }
-
- // Check for duplicate executable command
- if r := c.commands.Get(path); r.OK {
- existing := r.Value.(*Command)
- if existing.Action != nil || existing.IsManaged() {
- return Result{E("core.Command", Concat("command \"", path, "\" already registered"), nil), false}
- }
- }
-
- cmd := &command[0]
- cmd.Name = pathName(path)
- cmd.Path = path
- if cmd.commands == nil {
- cmd.commands = make(map[string]*Command)
- }
-
- // Preserve existing subtree when overwriting a placeholder parent
- if r := c.commands.Get(path); r.OK {
- existing := r.Value.(*Command)
- for k, v := range existing.commands {
- if _, has := cmd.commands[k]; !has {
- cmd.commands[k] = v
- }
- }
- }
-
- c.commands.Set(path, cmd)
-
- // Build parent chain — "deploy/to/homelab" creates "deploy" and "deploy/to" if missing
- parts := Split(path, "/")
- for i := len(parts) - 1; i > 0; i-- {
- parentPath := JoinPath(parts[:i]...)
- if !c.commands.Has(parentPath) {
- c.commands.Set(parentPath, &Command{
- Name: parts[i-1],
- Path: parentPath,
- commands: make(map[string]*Command),
- })
- }
- parent := c.commands.Get(parentPath).Value.(*Command)
- parent.commands[parts[i]] = cmd
- cmd = parent
- }
-
- return Result{OK: true}
-}
-
-// Commands returns all registered command paths in registration order.
-//
-// paths := c.Commands()
-func (c *Core) Commands() []string {
- if c.commands == nil {
- return nil
- }
- return c.commands.Names()
-}
-
-// pathName extracts the last segment of a path.
-// "deploy/to/homelab" → "homelab"
-func pathName(path string) string {
- parts := Split(path, "/")
- return parts[len(parts)-1]
-}
diff --git a/.core/reference/config.go b/.core/reference/config.go
deleted file mode 100644
index b0ff51e1..00000000
--- a/.core/reference/config.go
+++ /dev/null
@@ -1,188 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Settings, feature flags, and typed configuration for the Core framework.
-//
-// timeout := core.ConfigGet[int](c.Config(), "agent.timeout")
-
-package core
-
-import (
- "sync"
-)
-
-// ConfigVar is a variable that can be set, unset, and queried for its state.
-type ConfigVar[T any] struct {
- val T
- set bool
-}
-
-// Get returns the current value.
-//
-// val := v.Get()
-func (v *ConfigVar[T]) Get() T { return v.val }
-
-// Set sets the value and marks it as explicitly set.
-//
-// v.Set(true)
-func (v *ConfigVar[T]) Set(val T) { v.val = val; v.set = true }
-
-// IsSet returns true if the value was explicitly set (distinguishes "set to false" from "never set").
-//
-// if v.IsSet() { /* explicitly configured */ }
-func (v *ConfigVar[T]) IsSet() bool { return v.set }
-
-// Unset resets to zero value and marks as not set.
-//
-// v.Unset()
-// v.IsSet() // false
-func (v *ConfigVar[T]) Unset() {
- v.set = false
- var zero T
- v.val = zero
-}
-
-// NewConfigVar creates a ConfigVar with an initial value marked as set.
-//
-// debug := core.NewConfigVar(true)
-func NewConfigVar[T any](val T) ConfigVar[T] {
- return ConfigVar[T]{val: val, set: true}
-}
-
-// ConfigOptions holds configuration data.
-type ConfigOptions struct {
- Settings map[string]any
- Features map[string]bool
-}
-
-func (o *ConfigOptions) init() {
- if o.Settings == nil {
- o.Settings = make(map[string]any)
- }
- if o.Features == nil {
- o.Features = make(map[string]bool)
- }
-}
-
-// Config holds configuration settings and feature flags.
-type Config struct {
- *ConfigOptions
- mu sync.RWMutex
-}
-
-// New initialises a Config with empty settings and features.
-//
-// cfg := (&core.Config{}).New()
-func (e *Config) New() *Config {
- e.ConfigOptions = &ConfigOptions{}
- e.ConfigOptions.init()
- return e
-}
-
-// Set stores a configuration value by key.
-func (e *Config) Set(key string, val any) {
- e.mu.Lock()
- if e.ConfigOptions == nil {
- e.ConfigOptions = &ConfigOptions{}
- }
- e.ConfigOptions.init()
- e.Settings[key] = val
- e.mu.Unlock()
-}
-
-// Get retrieves a configuration value by key.
-func (e *Config) Get(key string) Result {
- e.mu.RLock()
- defer e.mu.RUnlock()
- if e.ConfigOptions == nil || e.Settings == nil {
- return Result{}
- }
- val, ok := e.Settings[key]
- if !ok {
- return Result{}
- }
- return Result{val, true}
-}
-
-// String retrieves a string config value (empty string if missing).
-//
-// host := c.Config().String("database.host")
-func (e *Config) String(key string) string { return ConfigGet[string](e, key) }
-
-// Int retrieves an int config value (0 if missing).
-//
-// port := c.Config().Int("database.port")
-func (e *Config) Int(key string) int { return ConfigGet[int](e, key) }
-
-// Bool retrieves a bool config value (false if missing).
-//
-// debug := c.Config().Bool("debug")
-func (e *Config) Bool(key string) bool { return ConfigGet[bool](e, key) }
-
-// ConfigGet retrieves a typed configuration value.
-func ConfigGet[T any](e *Config, key string) T {
- r := e.Get(key)
- if !r.OK {
- var zero T
- return zero
- }
- typed, _ := r.Value.(T)
- return typed
-}
-
-// --- Feature Flags ---
-
-// Enable activates a feature flag.
-//
-// c.Config().Enable("dark-mode")
-func (e *Config) Enable(feature string) {
- e.mu.Lock()
- if e.ConfigOptions == nil {
- e.ConfigOptions = &ConfigOptions{}
- }
- e.ConfigOptions.init()
- e.Features[feature] = true
- e.mu.Unlock()
-}
-
-// Disable deactivates a feature flag.
-//
-// c.Config().Disable("dark-mode")
-func (e *Config) Disable(feature string) {
- e.mu.Lock()
- if e.ConfigOptions == nil {
- e.ConfigOptions = &ConfigOptions{}
- }
- e.ConfigOptions.init()
- e.Features[feature] = false
- e.mu.Unlock()
-}
-
-// Enabled returns true if a feature flag is active.
-//
-// if c.Config().Enabled("dark-mode") { ... }
-func (e *Config) Enabled(feature string) bool {
- e.mu.RLock()
- defer e.mu.RUnlock()
- if e.ConfigOptions == nil || e.Features == nil {
- return false
- }
- return e.Features[feature]
-}
-
-// EnabledFeatures returns all active feature flag names.
-//
-// features := c.Config().EnabledFeatures()
-func (e *Config) EnabledFeatures() []string {
- e.mu.RLock()
- defer e.mu.RUnlock()
- if e.ConfigOptions == nil || e.Features == nil {
- return nil
- }
- var result []string
- for k, v := range e.Features {
- if v {
- result = append(result, k)
- }
- }
- return result
-}
diff --git a/.core/reference/contract.go b/.core/reference/contract.go
deleted file mode 100644
index 3be32401..00000000
--- a/.core/reference/contract.go
+++ /dev/null
@@ -1,226 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Contracts, options, and type definitions for the Core framework.
-
-package core
-
-import (
- "context"
- "reflect"
- "sync"
-)
-
-// Message is the type for IPC broadcasts (fire-and-forget).
-type Message any
-
-// Query is the type for read-only IPC requests.
-type Query any
-
-// QueryHandler handles Query requests. Returns Result{Value, OK}.
-type QueryHandler func(*Core, Query) Result
-
-// Startable is implemented by services that need startup initialisation.
-//
-// func (s *MyService) OnStartup(ctx context.Context) core.Result {
-// return core.Result{OK: true}
-// }
-type Startable interface {
- OnStartup(ctx context.Context) Result
-}
-
-// Stoppable is implemented by services that need shutdown cleanup.
-//
-// func (s *MyService) OnShutdown(ctx context.Context) core.Result {
-// return core.Result{OK: true}
-// }
-type Stoppable interface {
- OnShutdown(ctx context.Context) Result
-}
-
-// --- Action Messages ---
-
-type ActionServiceStartup struct{}
-type ActionServiceShutdown struct{}
-
-type ActionTaskStarted struct {
- TaskIdentifier string
- Action string
- Options Options
-}
-
-type ActionTaskProgress struct {
- TaskIdentifier string
- Action string
- Progress float64
- Message string
-}
-
-type ActionTaskCompleted struct {
- TaskIdentifier string
- Action string
- Result Result
-}
-
-// --- Constructor ---
-
-// CoreOption is a functional option applied during Core construction.
-// Returns Result — if !OK, New() stops and returns the error.
-//
-// core.New(
-// core.WithService(agentic.Register),
-// core.WithService(monitor.Register),
-// core.WithServiceLock(),
-// )
-type CoreOption func(*Core) Result
-
-// New initialises a Core instance by applying options in order.
-// Services registered here form the application conclave — they share
-// IPC access and participate in the lifecycle (ServiceStartup/ServiceShutdown).
-//
-// c := core.New(
-// core.WithOption("name", "myapp"),
-// core.WithService(auth.Register),
-// core.WithServiceLock(),
-// )
-// c.Run()
-func New(opts ...CoreOption) *Core {
- c := &Core{
- app: &App{},
- data: &Data{Registry: NewRegistry[*Embed]()},
- drive: &Drive{Registry: NewRegistry[*DriveHandle]()},
- fs: (&Fs{}).New("/"),
- config: (&Config{}).New(),
- error: &ErrorPanic{},
- log: &ErrorLog{},
- lock: &Lock{locks: NewRegistry[*sync.RWMutex]()},
- ipc: &Ipc{actions: NewRegistry[*Action](), tasks: NewRegistry[*Task]()},
- info: systemInfo,
- i18n: &I18n{},
- api: &API{protocols: NewRegistry[StreamFactory]()},
- services: &ServiceRegistry{Registry: NewRegistry[*Service]()},
- commands: &CommandRegistry{Registry: NewRegistry[*Command]()},
- entitlementChecker: defaultChecker,
- }
- c.context, c.cancel = context.WithCancel(context.Background())
- c.api.core = c
-
- // Core services
- CliRegister(c)
-
- for _, opt := range opts {
- if r := opt(c); !r.OK {
- Error("core.New failed", "err", r.Value)
- break
- }
- }
-
- // Apply service lock after all opts — v0.3.3 parity
- c.LockApply()
-
- return c
-}
-
-// WithOptions applies key-value configuration to Core.
-//
-// core.WithOptions(core.NewOptions(core.Option{Key: "name", Value: "myapp"}))
-func WithOptions(opts Options) CoreOption {
- return func(c *Core) Result {
- c.options = &opts
- if name := opts.String("name"); name != "" {
- c.app.Name = name
- }
- return Result{OK: true}
- }
-}
-
-// WithService registers a service via its factory function.
-// If the factory returns a non-nil Value, WithService auto-discovers the
-// service name from the factory's package path (last path segment, lowercase,
-// with any "_test" suffix stripped) and calls RegisterService on the instance.
-// IPC handler auto-registration is handled by RegisterService.
-//
-// If the factory returns nil Value (it registered itself), WithService
-// returns success without a second registration.
-//
-// core.WithService(agentic.Register)
-// core.WithService(display.Register(nil))
-func WithService(factory func(*Core) Result) CoreOption {
- return func(c *Core) Result {
- r := factory(c)
- if !r.OK {
- return r
- }
- if r.Value == nil {
- // Factory self-registered — nothing more to do.
- return Result{OK: true}
- }
- // Auto-discover the service name from the instance's package path.
- instance := r.Value
- typeOf := reflect.TypeOf(instance)
- if typeOf.Kind() == reflect.Ptr {
- typeOf = typeOf.Elem()
- }
- pkgPath := typeOf.PkgPath()
- parts := Split(pkgPath, "/")
- name := Lower(parts[len(parts)-1])
- if name == "" {
- return Result{E("core.WithService", Sprintf("service name could not be discovered for type %T", instance), nil), false}
- }
-
- // RegisterService handles Startable/Stoppable/HandleIPCEvents discovery
- return c.RegisterService(name, instance)
- }
-}
-
-// WithName registers a service with an explicit name (no reflect discovery).
-//
-// core.WithName("ws", func(c *Core) Result {
-// return Result{Value: hub, OK: true}
-// })
-func WithName(name string, factory func(*Core) Result) CoreOption {
- return func(c *Core) Result {
- r := factory(c)
- if !r.OK {
- return r
- }
- if r.Value == nil {
- return Result{E("core.WithName", Sprintf("failed to create service %q", name), nil), false}
- }
- return c.RegisterService(name, r.Value)
- }
-}
-
-// WithOption is a convenience for setting a single key-value option.
-//
-// core.New(
-// core.WithOption("name", "myapp"),
-// core.WithOption("port", 8080),
-// )
-func WithOption(key string, value any) CoreOption {
- return func(c *Core) Result {
- if c.options == nil {
- opts := NewOptions()
- c.options = &opts
- }
- c.options.Set(key, value)
- if key == "name" {
- if s, ok := value.(string); ok {
- c.app.Name = s
- }
- }
- return Result{OK: true}
- }
-}
-
-// WithServiceLock prevents further service registration after construction.
-//
-// core.New(
-// core.WithService(auth.Register),
-// core.WithServiceLock(),
-// )
-func WithServiceLock() CoreOption {
- return func(c *Core) Result {
- c.LockEnable()
- return Result{OK: true}
- }
-}
diff --git a/.core/reference/core.go b/.core/reference/core.go
deleted file mode 100644
index 978307d9..00000000
--- a/.core/reference/core.go
+++ /dev/null
@@ -1,239 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Package core is a dependency injection and service lifecycle framework for Go.
-// This file defines the Core struct, accessors, and IPC/error wrappers.
-
-package core
-
-import (
- "context"
- coreos "dappco.re/go"
- "sync"
- "sync/atomic"
-)
-
-// --- Core Struct ---
-
-// Core is the central application object that manages services, assets, and communication.
-type Core struct {
- options *Options // c.Options() — Input configuration used to create this Core
- app *App // c.App() — Application identity + optional GUI runtime
- data *Data // c.Data() — Embedded/stored content from packages
- drive *Drive // c.Drive() — Resource handle registry (transports)
- fs *Fs // c.Fs() — Local filesystem I/O (sandboxable)
- config *Config // c.Config() — Configuration, settings, feature flags
- error *ErrorPanic // c.Error() — Panic recovery and crash reporting
- log *ErrorLog // c.Log() — Structured logging + error wrapping
- // cli accessed via ServiceFor[*Cli](c, "cli")
- commands *CommandRegistry // c.Command(`path`) — Command tree
- services *ServiceRegistry // c.Service("name") — Service registry
- lock *Lock // c.Lock("name") — Named mutexes
- ipc *Ipc // c.IPC() — Message bus for IPC
- api *API // c.API() — Remote streams
- info *SysInfo // c.Env("key") — Read-only system/environment information
- i18n *I18n // c.I18n() — Internationalisation and locale collection
-
- entitlementChecker EntitlementChecker // default: everything permitted
- usageRecorder UsageRecorder // default: nil (no-op)
-
- context context.Context
- cancel context.CancelFunc
- taskIDCounter atomic.Uint64
- waitGroup sync.WaitGroup
- shutdown atomic.Bool
-}
-
-// --- Accessors ---
-
-// Options returns the input configuration passed to core.New().
-//
-// opts := c.Options()
-// name := opts.String("name")
-func (c *Core) Options() *Options { return c.options }
-
-// App returns application identity metadata.
-//
-// c.App().Name // "my-app"
-// c.App().Version // "1.0.0"
-func (c *Core) App() *App { return c.app }
-
-// Data returns the embedded asset registry (Registry[*Embed]).
-//
-// r := c.Data().ReadString("prompts/coding.md")
-func (c *Core) Data() *Data { return c.data }
-
-// Drive returns the transport handle registry (Registry[*DriveHandle]).
-//
-// r := c.Drive().Get("forge")
-func (c *Core) Drive() *Drive { return c.drive }
-
-// Fs returns the sandboxed filesystem.
-//
-// r := c.Fs().Read("/path/to/file")
-// c.Fs().WriteAtomic("/status.json", data)
-func (c *Core) Fs() *Fs { return c.fs }
-
-// Config returns runtime settings and feature flags.
-//
-// host := c.Config().String("database.host")
-// c.Config().Enable("dark-mode")
-func (c *Core) Config() *Config { return c.config }
-
-// Error returns the panic recovery subsystem.
-//
-// c.Error().Recover()
-func (c *Core) Error() *ErrorPanic { return c.error }
-
-// Log returns the structured logging subsystem.
-//
-// c.Log().Info("started", "port", 8080)
-func (c *Core) Log() *ErrorLog { return c.log }
-
-// Cli returns the CLI command framework (registered as service "cli").
-//
-// c.Cli().Run("deploy", "to", "homelab")
-func (c *Core) Cli() *Cli {
- cl, _ := ServiceFor[*Cli](c, "cli")
- return cl
-}
-
-// IPC returns the message bus internals.
-//
-// c.IPC()
-func (c *Core) IPC() *Ipc { return c.ipc }
-
-// I18n returns the internationalisation subsystem.
-//
-// tr := c.I18n().Translate("cmd.deploy.description")
-func (c *Core) I18n() *I18n { return c.i18n }
-
-// Env returns an environment variable by key (cached at init, falls back to os.Getenv).
-//
-// home := c.Env("DIR_HOME")
-// token := c.Env("FORGE_TOKEN")
-func (c *Core) Env(key string) string { return Env(key) }
-
-// Context returns Core's lifecycle context (cancelled on shutdown).
-//
-// ctx := c.Context()
-func (c *Core) Context() context.Context { return c.context }
-
-// Core returns self — satisfies the ServiceRuntime interface.
-//
-// c := s.Core()
-func (c *Core) Core() *Core { return c }
-
-// --- Lifecycle ---
-
-// RunE starts all services, runs the CLI, then shuts down.
-// Returns an error instead of calling os.Exit — let main() handle the exit.
-// ServiceShutdown is always called via defer, even on startup failure or panic.
-//
-// if err := c.RunE(); err != nil {
-// os.Exit(1)
-// }
-func (c *Core) RunE() any {
- defer c.ServiceShutdown(context.Background())
-
- r := c.ServiceStartup(c.context, nil)
- if !r.OK {
- if err, ok := r.Value.(error); ok {
- return err
- }
- return E("core.Run", "startup failed", nil)
- }
-
- if cli := c.Cli(); cli != nil {
- r = cli.Run()
- }
-
- if !r.OK {
- if err, ok := r.Value.(error); ok {
- return err
- }
- }
- return nil
-}
-
-// Run starts all services, runs the CLI, then shuts down.
-// Calls os.Exit(1) on failure. For error handling use RunE().
-//
-// c := core.New(core.WithService(myService.Register))
-// c.Run()
-func (c *Core) Run() {
- if err := c.RunE(); err != nil {
- Error(err.Error())
- os.Exit(1)
- }
-}
-
-// --- IPC (uppercase aliases) ---
-
-// ACTION broadcasts a message to all registered handlers (fire-and-forget).
-// Each handler is wrapped in panic recovery. All handlers fire regardless.
-//
-// c.ACTION(messages.AgentCompleted{Agent: "codex", Status: "completed"})
-func (c *Core) ACTION(msg Message) Result { return c.broadcast(msg) }
-
-// QUERY sends a request — first handler to return OK wins.
-//
-// r := c.QUERY(MyQuery{Name: "brain"})
-func (c *Core) QUERY(q Query) Result { return c.Query(q) }
-
-// QUERYALL sends a request — collects all OK responses.
-//
-// r := c.QUERYALL(countQuery{})
-// results := r.Value.([]any)
-func (c *Core) QUERYALL(q Query) Result { return c.QueryAll(q) }
-
-// --- Error+Log ---
-
-// LogError logs an error and returns the Result from ErrorLog.
-func (c *Core) LogError(err any, op, msg string) Result {
- return c.log.Error(err, op, msg)
-}
-
-// LogWarn logs a warning and returns the Result from ErrorLog.
-func (c *Core) LogWarn(err any, op, msg string) Result {
- return c.log.Warn(err, op, msg)
-}
-
-// Must logs and panics if err is not nil.
-func (c *Core) Must(err any, op, msg string) {
- c.log.Must(err, op, msg)
-}
-
-// --- Registry Accessor ---
-
-// RegistryOf returns a named registry for cross-cutting queries.
-// Known registries: "services", "commands", "actions".
-//
-// c.RegistryOf("services").Names() // all service names
-// c.RegistryOf("actions").List("process.*") // process capabilities
-// c.RegistryOf("commands").Len() // command count
-func (c *Core) RegistryOf(name string) *Registry[any] {
- // Bridge typed registries to untyped access for cross-cutting queries.
- // Each registry is wrapped in a read-only proxy.
- switch name {
- case "services":
- return registryProxy(c.services.Registry)
- case "commands":
- return registryProxy(c.commands.Registry)
- case "actions":
- return registryProxy(c.ipc.actions)
- default:
- return NewRegistry[any]() // empty registry for unknown names
- }
-}
-
-// registryProxy creates a read-only any-typed view of a typed registry.
-// Copies current state — not a live view (avoids type parameter leaking).
-func registryProxy[T any](src *Registry[T]) *Registry[any] {
- proxy := NewRegistry[any]()
- src.Each(func(name string, item T) {
- proxy.Set(name, item)
- })
- return proxy
-}
-
-// --- Global Instance ---
diff --git a/.core/reference/data.go b/.core/reference/data.go
deleted file mode 100644
index f37b6fb3..00000000
--- a/.core/reference/data.go
+++ /dev/null
@@ -1,168 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Data is the embedded/stored content system for core packages.
-// Packages mount their embedded content here and other packages
-// read from it by path.
-//
-// Mount a package's assets:
-//
-// c.Data().New(core.NewOptions(
-// core.Option{Key: "name", Value: "brain"},
-// core.Option{Key: "source", Value: brainFS},
-// core.Option{Key: `path`, Value: "prompts"},
-// ))
-//
-// Read from any mounted path:
-//
-// content := c.Data().ReadString("brain/coding.md")
-// entries := c.Data().List("agent/flow")
-//
-// Extract a template directory:
-//
-// c.Data().Extract("agent/workspace/default", "/tmp/ws", data)
-package core
-
-import (
- corefilepath "dappco.re/go"
- "io/fs"
-)
-
-// Data manages mounted embedded filesystems from core packages.
-// Embeds Registry[*Embed] for thread-safe named storage.
-type Data struct {
- *Registry[*Embed]
-}
-
-// New registers an embedded filesystem under a named prefix.
-//
-// c.Data().New(core.NewOptions(
-// core.Option{Key: "name", Value: "brain"},
-// core.Option{Key: "source", Value: brainFS},
-// core.Option{Key: `path`, Value: "prompts"},
-// ))
-func (d *Data) New(opts Options) Result {
- name := opts.String("name")
- if name == "" {
- return Result{}
- }
-
- r := opts.Get("source")
- if !r.OK {
- return r
- }
-
- fsys, ok := r.Value.(fs.FS)
- if !ok {
- return Result{E("data.New", "source is not fs.FS", nil), false}
- }
-
- path := opts.String(`path`)
- if path == "" {
- path = "."
- }
-
- mr := Mount(fsys, path)
- if !mr.OK {
- return mr
- }
-
- emb := mr.Value.(*Embed)
- d.Set(name, emb)
- return Result{emb, true}
-}
-
-// resolve splits a path like "brain/coding.md" into mount name + relative path.
-func (d *Data) resolve(path string) (*Embed, string) {
- parts := SplitN(path, "/", 2)
- if len(parts) < 2 {
- return nil, ""
- }
- r := d.Get(parts[0])
- if !r.OK {
- return nil, ""
- }
- return r.Value.(*Embed), parts[1]
-}
-
-// ReadFile reads a file by full path.
-//
-// r := c.Data().ReadFile("brain/prompts/coding.md")
-// if r.OK { data := r.Value.([]byte) }
-func (d *Data) ReadFile(path string) Result {
- emb, rel := d.resolve(path)
- if emb == nil {
- return Result{}
- }
- return emb.ReadFile(rel)
-}
-
-// ReadString reads a file as a string.
-//
-// r := c.Data().ReadString("agent/flow/deploy/to/homelab.yaml")
-// if r.OK { content := r.Value.(string) }
-func (d *Data) ReadString(path string) Result {
- r := d.ReadFile(path)
- if !r.OK {
- return r
- }
- return Result{string(r.Value.([]byte)), true}
-}
-
-// List returns directory entries at a path.
-//
-// r := c.Data().List("agent/persona/code")
-// if r.OK { entries := r.Value.([]fs.DirEntry) }
-func (d *Data) List(path string) Result {
- emb, rel := d.resolve(path)
- if emb == nil {
- return Result{}
- }
- r := emb.ReadDir(rel)
- if !r.OK {
- return r
- }
- return Result{r.Value, true}
-}
-
-// ListNames returns filenames (without extensions) at a path.
-//
-// r := c.Data().ListNames("agent/flow")
-// if r.OK { names := r.Value.([]string) }
-func (d *Data) ListNames(path string) Result {
- r := d.List(path)
- if !r.OK {
- return r
- }
- entries := r.Value.([]fs.DirEntry)
- var names []string
- for _, e := range entries {
- name := e.Name()
- if !e.IsDir() {
- name = TrimSuffix(name, filepath.Ext(name))
- }
- names = append(names, name)
- }
- return Result{names, true}
-}
-
-// Extract copies a template directory to targetDir.
-//
-// r := c.Data().Extract("agent/workspace/default", "/tmp/ws", templateData)
-func (d *Data) Extract(path, targetDir string, templateData any) Result {
- emb, rel := d.resolve(path)
- if emb == nil {
- return Result{}
- }
- r := emb.Sub(rel)
- if !r.OK {
- return r
- }
- return Extract(r.Value.(*Embed).FS(), targetDir, templateData)
-}
-
-// Mounts returns the names of all mounted content in registration order.
-//
-// names := c.Data().Mounts()
-func (d *Data) Mounts() []string {
- return d.Names()
-}
diff --git a/.core/reference/docs/RFC.md b/.core/reference/docs/RFC.md
deleted file mode 100644
index f959059d..00000000
--- a/.core/reference/docs/RFC.md
+++ /dev/null
@@ -1,434 +0,0 @@
-# core/agent API Contract — RFC Specification
-
-> `dappco.re/go/core/agent` — Agentic dispatch, orchestration, and pipeline management.
-> An agent should be able to understand core/agent's architecture from this document alone.
-
-**Status:** v0.8.0+alpha.1
-**Module:** `dappco.re/go/core/agent`
-**Depends on:** core/go v0.8.0, go-process v0.8.0
-
----
-
-## 1. Purpose
-
-core/agent dispatches AI agents (Claude, Codex, Gemini) to work on tasks in sandboxed git worktrees, monitors their progress, verifies output, and manages the merge pipeline.
-
-core/go provides the primitives. core/agent composes them.
-
-### File Layout
-
-```
-cmd/core-agent/main.go — entry point: core.New + Run
-pkg/agentic/ — orchestration (dispatch, prep, verify, scan, commands)
-pkg/agentic/actions.go — named Action handlers (ctx, Options) → Result
-pkg/agentic/pid.go — PID lifecycle helpers
-pkg/agentic/handlers.go — IPC completion pipeline handlers
-pkg/agentic/status.go — workspace status (WriteAtomic + JSONMarshalString)
-pkg/agentic/paths.go — paths, fs (NewUnrestricted), helpers
-pkg/brain/ — OpenBrain (recall, remember, search)
-pkg/lib/ — embedded templates, personas, flows, plans
-pkg/messages/ — typed message structs for IPC broadcast
-pkg/monitor/ — agent monitoring via IPC (ServiceRuntime)
-pkg/setup/ — workspace detection + scaffolding (Service)
-claude/ — Claude Code plugin definitions
-docs/ — RFC, plans, architecture
-```
-
----
-
-## 2. Service Registration
-
-All services use `ServiceRuntime[T]` — no raw `core *core.Core` fields.
-
-```go
-func Register(c *core.Core) core.Result {
- prep := NewPrep()
- prep.ServiceRuntime = core.NewServiceRuntime(c, AgentOptions{})
-
- cfg := prep.loadAgentsConfig()
- c.Config().Set("agents.concurrency", cfg.Concurrency)
- c.Config().Set("agents.rates", cfg.Rates)
-
- RegisterHandlers(c, prep)
- return core.Result{Value: prep, OK: true}
-}
-
-// In main:
-c := core.New(
- core.WithService(process.Register),
- core.WithService(agentic.Register),
- core.WithService(brain.Register),
- core.WithService(monitor.Register),
- core.WithService(mcp.Register),
-)
-c.Run()
-```
-
----
-
-## 3. Named Actions — The Capability Map
-
-All capabilities registered as named Actions during OnStartup. Inspectable, composable, gatable by Entitlements.
-
-```go
-func (s *PrepSubsystem) OnStartup(ctx context.Context) core.Result {
- c := s.Core()
-
- // Dispatch & workspace
- c.Action("agentic.dispatch", s.handleDispatch)
- c.Action("agentic.prep", s.handlePrep)
- c.Action("agentic.status", s.handleStatus)
- c.Action("agentic.resume", s.handleResume)
- c.Action("agentic.scan", s.handleScan)
- c.Action("agentic.watch", s.handleWatch)
-
- // Pipeline
- c.Action("agentic.qa", s.handleQA)
- c.Action("agentic.auto-pr", s.handleAutoPR)
- c.Action("agentic.verify", s.handleVerify)
- c.Action("agentic.ingest", s.handleIngest)
- c.Action("agentic.poke", s.handlePoke)
- c.Action("agentic.mirror", s.handleMirror)
-
- // Forge
- c.Action("agentic.issue.get", s.handleIssueGet)
- c.Action("agentic.issue.list", s.handleIssueList)
- c.Action("agentic.issue.create", s.handleIssueCreate)
- c.Action("agentic.pr.get", s.handlePRGet)
- c.Action("agentic.pr.list", s.handlePRList)
- c.Action("agentic.pr.merge", s.handlePRMerge)
-
- // Review & Epic
- c.Action("agentic.review-queue", s.handleReviewQueue)
- c.Action("agentic.epic", s.handleEpic)
-
- // Completion pipeline — Task composition
- c.Task("agent.completion", core.Task{
- Description: "QA → PR → Verify → Merge",
- Steps: []core.Step{
- {Action: "agentic.qa"},
- {Action: "agentic.auto-pr"},
- {Action: "agentic.verify"},
- {Action: "agentic.ingest", Async: true},
- {Action: "agentic.poke", Async: true},
- },
- })
-
- s.StartRunner()
- s.registerCommands(ctx)
- s.registerWorkspaceCommands()
- s.registerForgeCommands()
- return core.Result{OK: true}
-}
-```
-
----
-
-## 4. Completion Pipeline
-
-When an agent completes, the IPC handler chain fires. Registered in `RegisterHandlers()`:
-
-```
-AgentCompleted → QA handler → QAResult
-QAResult{Passed} → PR handler → PRCreated
-PRCreated → Verify handler → PRMerged | PRNeedsReview
-AgentCompleted → Ingest handler (findings → issues)
-AgentCompleted → Poke handler (drain queue)
-```
-
-All handlers use `c.ACTION(messages.X{})` — no ChannelNotifier, no callbacks.
-
----
-
-## 5. Process Execution
-
-All commands via `s.Core().Process()`. Returns `core.Result` — Value is always a string.
-
-```go
-process := s.Core().Process()
-r := process.RunIn(ctx, dir, "git", "log", "--oneline", "-20")
-if r.OK {
- output := core.Trim(r.Value.(string))
-}
-
-r = process.RunWithEnv(ctx, dir, []string{"GOWORK=off"}, "go", "test", "./...")
-```
-
-go-process is fully Result-native. `Start`, `Run`, `StartWithOptions`, `RunWithOptions` all return `core.Result`. Value is `*Process` for Start, `string` for Run. OK=true guarantees the type.
-
----
-
-## 6. Status Management
-
-Workspace status uses `WriteAtomic` + `JSONMarshalString` for safe concurrent access:
-
-```go
-func writeStatus(wsDir string, status *WorkspaceStatus) error {
- status.UpdatedAt = time.Now()
- statusPath := core.JoinPath(wsDir, "status.json")
- if r := fs.WriteAtomic(statusPath, core.JSONMarshalString(status)); !r.OK {
- err, _ := r.Value.(error)
- return core.E("writeStatus", "failed to write status", err)
- }
- return nil
-}
-```
-
----
-
-## 7. Filesystem
-
-No `unsafe.Pointer`. Package-level unrestricted Fs via Core primitive:
-
-```go
-var fs = (&core.Fs{}).NewUnrestricted()
-```
-
----
-
-## 8. IPC Messages
-
-All inter-service communication via typed messages in `pkg/messages/`:
-
-```go
-// Agent lifecycle
-messages.AgentStarted{Agent, Repo, Workspace}
-messages.AgentCompleted{Agent, Repo, Workspace, Status}
-
-// Pipeline
-messages.QAResult{Workspace, Repo, Passed}
-messages.PRCreated{Repo, Branch, PRURL, PRNum}
-messages.PRMerged{Repo, PRURL, PRNum}
-messages.PRNeedsReview{Repo, PRURL, PRNum, Reason}
-
-// Queue
-messages.QueueDrained{Completed}
-messages.PokeQueue{}
-
-// Monitor
-messages.HarvestComplete{Repo, Branch, Files}
-messages.HarvestRejected{Repo, Branch, Reason}
-messages.InboxMessage{New, Total}
-```
-
----
-
-## 9. Monitor
-
-Embeds `*core.ServiceRuntime[MonitorOptions]`. All notifications via `m.Core().ACTION(messages.X{})` — no ChannelNotifier interface. Git operations via `m.Core().Process()`.
-
-```go
-func Register(c *core.Core) core.Result {
- mon := New()
- mon.ServiceRuntime = core.NewServiceRuntime(c, MonitorOptions{})
-
- c.RegisterAction(func(c *core.Core, msg core.Message) core.Result {
- switch ev := msg.(type) {
- case messages.AgentCompleted:
- mon.handleAgentCompleted(ev)
- case messages.AgentStarted:
- mon.handleAgentStarted(ev)
- }
- return core.Result{OK: true}
- })
-
- return core.Result{Value: mon, OK: true}
-}
-```
-
----
-
-## 10. Setup
-
-Service with `*core.ServiceRuntime[SetupOptions]`. Detects project type, generates configs, scaffolds workspaces.
-
-```go
-func Register(c *core.Core) core.Result {
- svc := &Service{
- ServiceRuntime: core.NewServiceRuntime(c, SetupOptions{}),
- }
- return core.Result{Value: svc, OK: true}
-}
-```
-
----
-
-## 11. Entitlements
-
-Actions are gated by `c.Entitled()` — checked automatically in `Action.Run()`.
-
-```go
-func (s *PrepSubsystem) handleDispatch(ctx context.Context, opts core.Options) core.Result {
- e := s.Core().Entitled("agentic.concurrency", 1)
- if !e.Allowed {
- return core.Result{Value: core.E("dispatch", e.Reason, nil), OK: false}
- }
- // ... dispatch agent ...
- s.Core().RecordUsage("agentic.dispatch")
- return core.Result{OK: true}
-}
-```
-
----
-
-## 12. MCP — Action Aggregator
-
-MCP auto-exposes all registered Actions as tools via `c.Actions()`. Register an Action → it appears as an MCP tool. The API stream primitive (`c.API()`) handles transport.
-
----
-
-## 13. Remote Dispatch
-
-Transparent local/remote via `host:action` syntax:
-
-```go
-r := c.RemoteAction("agentic.status", ctx, opts) // local
-r := c.RemoteAction("charon:agentic.dispatch", ctx, opts) // remote
-r := c.RemoteAction("snider.lthn:brain.recall", ctx, opts) // web3
-```
-
----
-
-## 14. Quality Gates
-
-```bash
-# No disallowed imports (source files only)
-grep -rn '"os"\|"os/exec"\|"io"\|"fmt"\|"errors"\|"log"\|"encoding/json"\|"path/filepath"\|"unsafe"\|"strings"' *.go **/*.go \
- | grep -v _test.go
-
-# Test naming: TestFile_Function_{Good,Bad,Ugly}
-grep -rn "^func Test" *_test.go **/*_test.go \
- | grep -v "Test[A-Z][a-z]*_.*_\(Good\|Bad\|Ugly\)"
-```
-
----
-
-## 15. Validation and IDs
-
-```go
-if r := core.ValidateName(input.Repo); !r.OK { return r }
-safe := core.SanitisePath(userInput)
-id := core.ID() // "id-42-a3f2b1"
-```
-
----
-
-## 16. JSON Serialisation
-
-All JSON via Core primitives. No `encoding/json` import.
-
-```go
-data := core.JSONMarshalString(status)
-core.JSONUnmarshalString(jsonStr, &result)
-```
-
----
-
-## 17. Configuration
-
-```go
-c.Config().Set("agents.concurrency", 5)
-c.Config().String("workspace.root")
-c.Config().Int("agents.concurrency")
-c.Config().Enable("auto-merge")
-if c.Config().Enabled("auto-merge") { ... }
-```
-
----
-
-## 18. Registry
-
-Use `Registry[T]` for any named collection. No `map[string]*T + sync.Mutex`.
-
-```go
-workspaces := core.NewRegistry[*WorkspaceStatus]()
-workspaces.Set(wsDir, status)
-workspaces.Get(wsDir)
-workspaces.Each(func(dir string, st *WorkspaceStatus) { ... })
-workspaces.Names() // insertion order
-c.RegistryOf("actions").List("agentic.*")
-```
-
----
-
-## 19. String Operations
-
-No `fmt`, no `strings`, no `+` concat. Core provides everything:
-
-```go
-core.Println(value) // not fmt.Println
-core.Sprintf("port: %d", port) // not fmt.Sprintf
-core.Concat("hello ", name) // not "hello " + name
-core.Path(dir, "status.json") // not dir + "/status.json"
-core.Contains(s, "prefix") // not strings.Contains
-core.Split(s, "/") // not strings.Split
-core.Trim(s) // not strings.TrimSpace
-```
-
----
-
-## 20. Error Handling and Logging
-
-All errors via `core.E()`. All logging via Core. No `fmt`, `errors`, or `log` imports.
-
-```go
-return core.E("dispatch.prep", "workspace not found", nil)
-return core.E("dispatch.prep", core.Concat("repo ", repo, " invalid"), cause)
-core.Info("agent dispatched", "repo", repo, "agent", agent)
-core.Error("dispatch failed", "err", err)
-core.Security("entitlement.denied", "action", action, "reason", reason)
-```
-
----
-
-## 21. Stream Helpers and Data
-
-```go
-r := c.Data().ReadString("prompts/coding.md")
-c.Data().List("templates/")
-c.Drive().New(core.NewOptions(
- core.Option{Key: "name", Value: "charon"},
- core.Option{Key: "transport", Value: "http://10.69.69.165:9101"},
-))
-```
-
----
-
-## 22. Comments (AX Principle 2)
-
-Every exported function MUST have a usage-example comment:
-
-```go
-// Process runs a git command in a directory.
-//
-// r := s.Core().Process().RunIn(ctx, "/repo", "git", "log", "--oneline")
-```
-
----
-
-## 23. Test Strategy (AX Principle 7)
-
-`TestFile_Function_{Good,Bad,Ugly}` — 100% naming compliance target.
-
----
-
-## Consumer RFCs
-
-| Package | RFC | Role |
-|---------|-----|------|
-| core/go | `core/go/docs/RFC.md` | Primitives — all 21 sections |
-| go-process | `core/go-process/docs/RFC.md` | Process Action handlers (Result-native) |
-
----
-
-## Changelog
-
-- 2026-03-30: `version.go` now has an example companion, closing the last build-relevant source file without example coverage.
-- 2026-03-30: `pkg/agentic/commands_workspace.go` now has a matching example companion, closing the last agentic source file without example coverage.
-- 2026-03-30: plan files and review queue rate-limit state now use `WriteAtomic`, keeping JSON state writes aligned with the AX safe-write convention.
-- 2026-03-30: transport helpers preserve request and read causes, brain direct API calls surface upstream bodies, and review queue retry parsing no longer uses `MustCompile`.
-- 2026-03-30: direct Core process calls replaced the `proc.go` wrapper layer; PID helpers now live in `pid.go` and the workspace template documents `c.Process()` directly.
-- 2026-03-29: cmd/core-agent no longer rewrites `os.Args` before startup. The binary-owned commands now use named handlers, keeping the entrypoint on Core CLI primitives instead of repo-local argument mutation.
-- 2026-03-26: net/http consolidated to transport.go (ONE file). net/url + io/fs eliminated. RFC-025 updated with 3 new quality gates (net/http, net/url, io/fs). 1:1 test + example test coverage. Array[T].Deduplicate replaces custom helpers.
-- 2026-03-25: Quality gates pass. Zero disallowed imports (all 10). encoding/json→Core JSON. path/filepath→Core Path. os→Core Env/Fs. io→Core ReadAll/WriteAll. go-process fully Result-native. ServiceRuntime on all subsystems. 22 named Actions + Task pipeline. ChannelNotifier→IPC. Reference docs synced.
-- 2026-03-25: Initial spec — written with full core/go v0.8.0 domain context.
diff --git a/.core/reference/docs/commands.md b/.core/reference/docs/commands.md
deleted file mode 100644
index 46e2022b..00000000
--- a/.core/reference/docs/commands.md
+++ /dev/null
@@ -1,177 +0,0 @@
----
-title: Commands
-description: Path-based command registration and CLI execution.
----
-
-# Commands
-
-Commands are one of the most AX-native parts of CoreGO. The path is the identity.
-
-## Register a Command
-
-```go
-c.Command("deploy/to/homelab", core.Command{
- Action: func(opts core.Options) core.Result {
- target := opts.String("target")
- return core.Result{Value: "deploying to " + target, OK: true}
- },
-})
-```
-
-## Command Paths
-
-Paths must be clean:
-
-- no empty path
-- no leading slash
-- no trailing slash
-- no double slash
-
-These paths are valid:
-
-```text
-deploy
-deploy/to/homelab
-workspace/create
-```
-
-These are rejected:
-
-```text
-/deploy
-deploy/
-deploy//to
-```
-
-## Parent Commands Are Auto-Created
-
-When you register `deploy/to/homelab`, CoreGO also creates placeholder parents if they do not already exist:
-
-- `deploy`
-- `deploy/to`
-
-This makes the path tree navigable without extra setup.
-
-## Read a Command Back
-
-```go
-r := c.Command("deploy/to/homelab")
-if r.OK {
- cmd := r.Value.(*core.Command)
- _ = cmd
-}
-```
-
-## Run a Command Directly
-
-```go
-cmd := c.Command("deploy/to/homelab").Value.(*core.Command)
-
-r := cmd.Run(core.Options{
- {Key: "target", Value: "uk-prod"},
-})
-```
-
-If `Action` is nil, `Run` returns `Result{OK:false}` with a structured error.
-
-## Run Through the CLI Surface
-
-```go
-r := c.Cli().Run("deploy", "to", "homelab", "--target=uk-prod", "--debug")
-```
-
-`Cli.Run` resolves the longest matching command path from the arguments, then converts the remaining args into `core.Options`.
-
-## Flag Parsing Rules
-
-### Double Dash
-
-```text
---target=uk-prod -> key "target", value "uk-prod"
---debug -> key "debug", value true
-```
-
-### Single Dash
-
-```text
--v -> key "v", value true
--n=4 -> key "n", value "4"
-```
-
-### Positional Arguments
-
-Non-flag arguments after the command path are stored as repeated `_arg` options.
-
-```go
-r := c.Cli().Run("workspace", "open", "alpha")
-```
-
-That produces an option like:
-
-```go
-core.Option{Key: "_arg", Value: "alpha"}
-```
-
-### Important Details
-
-- flag values stay as strings
-- `opts.Int("port")` only works if some code stored an actual `int`
-- invalid flags such as `-verbose` and `--v` are ignored
-
-## Help Output
-
-`Cli.PrintHelp()` prints executable commands:
-
-```go
-c.Cli().PrintHelp()
-```
-
-It skips:
-
-- hidden commands
-- placeholder parents with no `Action` and no `Lifecycle`
-
-Descriptions are resolved through `cmd.I18nKey()`.
-
-## I18n Description Keys
-
-If `Description` is empty, CoreGO derives a key from the path.
-
-```text
-deploy -> cmd.deploy.description
-deploy/to/homelab -> cmd.deploy.to.homelab.description
-workspace/create -> cmd.workspace.create.description
-```
-
-If `Description` is already set, CoreGO uses it as-is.
-
-## Lifecycle Commands
-
-Commands can also delegate to a lifecycle implementation.
-
-```go
-type daemonCommand struct{}
-
-func (d *daemonCommand) Start(opts core.Options) core.Result { return core.Result{OK: true} }
-func (d *daemonCommand) Stop() core.Result { return core.Result{OK: true} }
-func (d *daemonCommand) Restart() core.Result { return core.Result{OK: true} }
-func (d *daemonCommand) Reload() core.Result { return core.Result{OK: true} }
-func (d *daemonCommand) Signal(sig string) core.Result { return core.Result{Value: sig, OK: true} }
-
-c.Command("agent/serve", core.Command{
- Lifecycle: &daemonCommand{},
-})
-```
-
-Important behavior:
-
-- `Start` falls back to `Run` when `Lifecycle` is nil
-- `Stop`, `Restart`, `Reload`, and `Signal` return an empty `Result` when `Lifecycle` is nil
-
-## List Command Paths
-
-```go
-paths := c.Commands()
-```
-
-Like the service registry, the command registry is map-backed, so iteration order is not guaranteed.
diff --git a/.core/reference/docs/configuration.md b/.core/reference/docs/configuration.md
deleted file mode 100644
index 0a0cf118..00000000
--- a/.core/reference/docs/configuration.md
+++ /dev/null
@@ -1,96 +0,0 @@
----
-title: Configuration
-description: Constructor options, runtime settings, and feature flags.
----
-
-# Configuration
-
-CoreGO uses two different configuration layers:
-
-- constructor-time `core.Options`
-- runtime `c.Config()`
-
-## Constructor-Time Options
-
-```go
-c := core.New(core.Options{
- {Key: "name", Value: "agent-workbench"},
-})
-```
-
-### Current Behavior
-
-- `New` accepts `opts ...Options`
-- the current implementation copies only the first `Options` slice
-- the `name` key is applied to `c.App().Name`
-
-If you need more constructor data, put it in the first `core.Options` slice.
-
-## Runtime Settings with `Config`
-
-Use `c.Config()` for mutable process settings.
-
-```go
-c.Config().Set("workspace.root", "/srv/workspaces")
-c.Config().Set("max_agents", 8)
-c.Config().Set("debug", true)
-```
-
-Read them back with:
-
-```go
-root := c.Config().String("workspace.root")
-maxAgents := c.Config().Int("max_agents")
-debug := c.Config().Bool("debug")
-raw := c.Config().Get("workspace.root")
-```
-
-### Important Details
-
-- missing keys return zero values
-- typed accessors do not coerce strings into ints or bools
-- `Get` returns `core.Result`
-
-## Feature Flags
-
-`Config` also tracks named feature flags.
-
-```go
-c.Config().Enable("workspace.templates")
-c.Config().Enable("agent.review")
-c.Config().Disable("agent.review")
-```
-
-Read them with:
-
-```go
-enabled := c.Config().Enabled("workspace.templates")
-features := c.Config().EnabledFeatures()
-```
-
-Feature names are case-sensitive.
-
-## `ConfigVar[T]`
-
-Use `ConfigVar[T]` when you need a typed value that can also represent “set versus unset”.
-
-```go
-theme := core.NewConfigVar("amber")
-
-if theme.IsSet() {
- fmt.Println(theme.Get())
-}
-
-theme.Unset()
-```
-
-This is useful for package-local state where zero values are not enough to describe configuration presence.
-
-## Recommended Pattern
-
-Use the two layers for different jobs:
-
-- put startup identity such as `name` into `core.Options`
-- put mutable runtime values and feature switches into `c.Config()`
-
-That keeps constructor intent separate from live process state.
diff --git a/.core/reference/docs/errors.md b/.core/reference/docs/errors.md
deleted file mode 100644
index 9b7d3f3c..00000000
--- a/.core/reference/docs/errors.md
+++ /dev/null
@@ -1,120 +0,0 @@
----
-title: Errors
-description: Structured errors, logging helpers, and panic recovery.
----
-
-# Errors
-
-CoreGO treats failures as structured operational data.
-
-Repository convention: use `E()` instead of `fmt.Errorf` for framework and service errors.
-
-## `Err`
-
-The structured error type is:
-
-```go
-type Err struct {
- Operation string
- Message string
- Cause error
- Code string
-}
-```
-
-## Create Errors
-
-### `E`
-
-```go
-err := core.E("workspace.Load", "failed to read workspace manifest", cause)
-```
-
-### `Wrap`
-
-```go
-err := core.Wrap(cause, "workspace.Load", "manifest parse failed")
-```
-
-### `WrapCode`
-
-```go
-err := core.WrapCode(cause, "WORKSPACE_INVALID", "workspace.Load", "manifest parse failed")
-```
-
-### `NewCode`
-
-```go
-err := core.NewCode("NOT_FOUND", "workspace not found")
-```
-
-## Inspect Errors
-
-```go
-op := core.Operation(err)
-code := core.ErrorCode(err)
-msg := core.ErrorMessage(err)
-root := core.Root(err)
-stack := core.StackTrace(err)
-pretty := core.FormatStackTrace(err)
-```
-
-These helpers keep the operational chain visible without extra type assertions.
-
-## Join and Standard Wrappers
-
-```go
-combined := core.ErrorJoin(err1, err2)
-same := core.Is(combined, err1)
-```
-
-`core.As` and `core.NewError` mirror the standard library for convenience.
-
-## Log-and-Return Helpers
-
-`Core` exposes two convenience wrappers:
-
-```go
-r1 := c.LogError(err, "workspace.Load", "workspace load failed")
-r2 := c.LogWarn(err, "workspace.Load", "workspace load degraded")
-```
-
-These log through the default logger and return `core.Result`.
-
-You can also use the underlying `ErrorLog` directly:
-
-```go
-r := c.Log().Error(err, "workspace.Load", "workspace load failed")
-```
-
-`Must` logs and then panics when the error is non-nil:
-
-```go
-c.Must(err, "workspace.Load", "workspace load failed")
-```
-
-## Panic Recovery
-
-`ErrorPanic` handles process-safe panic capture.
-
-```go
-defer c.Error().Recover()
-```
-
-Run background work with recovery:
-
-```go
-c.Error().SafeGo(func() {
- panic("captured")
-})
-```
-
-If `ErrorPanic` has a configured crash file path, it appends JSON crash reports and `Reports(n)` reads them back.
-
-That crash file path is currently internal state on `ErrorPanic`, not a public constructor option on `Core.New()`.
-
-## Logging and Error Context
-
-The logging subsystem automatically extracts `op` and logical stack information from structured errors when those values are present in the key-value list.
-
-That makes errors created with `E`, `Wrap`, or `WrapCode` much easier to follow in logs.
diff --git a/.core/reference/docs/getting-started.md b/.core/reference/docs/getting-started.md
deleted file mode 100644
index d2d81666..00000000
--- a/.core/reference/docs/getting-started.md
+++ /dev/null
@@ -1,208 +0,0 @@
----
-title: Getting Started
-description: Build a first CoreGO application with the current API.
----
-
-# Getting Started
-
-This page shows the shortest path to a useful CoreGO application using the API that exists in this repository today.
-
-## Install
-
-```bash
-go get dappco.re/go/core
-```
-
-## Create a Core
-
-`New` takes zero or more `core.Options` slices, but the current implementation only reads the first one. In practice, treat the constructor as `core.New(core.Options{...})`.
-
-```go
-package main
-
-import "dappco.re/go/core"
-
-func main() {
- c := core.New(core.Options{
- {Key: "name", Value: "agent-workbench"},
- })
-
- _ = c
-}
-```
-
-The `name` option is copied into `c.App().Name`.
-
-## Register a Service
-
-Services are registered explicitly with a name and a `core.Service` DTO.
-
-```go
-c.Service("audit", core.Service{
- OnStart: func() core.Result {
- core.Info("audit service started", "app", c.App().Name)
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- core.Info("audit service stopped", "app", c.App().Name)
- return core.Result{OK: true}
- },
-})
-```
-
-This registry stores `core.Service` values. It is a lifecycle registry, not a typed object container.
-
-## Register a Query, Task, and Command
-
-```go
-type workspaceCountQuery struct{}
-
-type createWorkspaceTask struct {
- Name string
-}
-
-c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
- switch q.(type) {
- case workspaceCountQuery:
- return core.Result{Value: 1, OK: true}
- }
- return core.Result{}
-})
-
-c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
- switch task := t.(type) {
- case createWorkspaceTask:
- path := "/tmp/agent-workbench/" + task.Name
- return core.Result{Value: path, OK: true}
- }
- return core.Result{}
-})
-
-c.Command("workspace/create", core.Command{
- Action: func(opts core.Options) core.Result {
- return c.PERFORM(createWorkspaceTask{
- Name: opts.String("name"),
- })
- },
-})
-```
-
-## Start the Runtime
-
-```go
-if !c.ServiceStartup(context.Background(), nil).OK {
- panic("startup failed")
-}
-```
-
-`ServiceStartup` returns `core.Result`, not `error`.
-
-## Run Through the CLI Surface
-
-```go
-r := c.Cli().Run("workspace", "create", "--name=alpha")
-if r.OK {
- fmt.Println("created:", r.Value)
-}
-```
-
-For flags with values, the CLI stores the value as a string. `--name=alpha` becomes `opts.String("name") == "alpha"`.
-
-## Query the System
-
-```go
-count := c.QUERY(workspaceCountQuery{})
-if count.OK {
- fmt.Println("workspace count:", count.Value)
-}
-```
-
-## Shut Down Cleanly
-
-```go
-_ = c.ServiceShutdown(context.Background())
-```
-
-Shutdown cancels `c.Context()`, broadcasts `ActionServiceShutdown{}`, waits for background tasks to finish, and then runs service stop hooks.
-
-## Full Example
-
-```go
-package main
-
-import (
- "context"
- "fmt"
-
- "dappco.re/go/core"
-)
-
-type workspaceCountQuery struct{}
-
-type createWorkspaceTask struct {
- Name string
-}
-
-func main() {
- c := core.New(core.Options{
- {Key: "name", Value: "agent-workbench"},
- })
-
- c.Config().Set("workspace.root", "/tmp/agent-workbench")
- c.Config().Enable("workspace.templates")
-
- c.Service("audit", core.Service{
- OnStart: func() core.Result {
- core.Info("service started", "service", "audit")
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- core.Info("service stopped", "service", "audit")
- return core.Result{OK: true}
- },
- })
-
- c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
- switch q.(type) {
- case workspaceCountQuery:
- return core.Result{Value: 1, OK: true}
- }
- return core.Result{}
- })
-
- c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
- switch task := t.(type) {
- case createWorkspaceTask:
- path := c.Config().String("workspace.root") + "/" + task.Name
- return core.Result{Value: path, OK: true}
- }
- return core.Result{}
- })
-
- c.Command("workspace/create", core.Command{
- Action: func(opts core.Options) core.Result {
- return c.PERFORM(createWorkspaceTask{
- Name: opts.String("name"),
- })
- },
- })
-
- if !c.ServiceStartup(context.Background(), nil).OK {
- panic("startup failed")
- }
-
- created := c.Cli().Run("workspace", "create", "--name=alpha")
- fmt.Println("created:", created.Value)
-
- count := c.QUERY(workspaceCountQuery{})
- fmt.Println("workspace count:", count.Value)
-
- _ = c.ServiceShutdown(context.Background())
-}
-```
-
-## Next Steps
-
-- Read [primitives.md](primitives.md) next so the repeated shapes are clear.
-- Read [commands.md](commands.md) if you are building a CLI-first system.
-- Read [messaging.md](messaging.md) if services need to collaborate without direct imports.
diff --git a/.core/reference/docs/index.md b/.core/reference/docs/index.md
deleted file mode 100644
index ca1ef996..00000000
--- a/.core/reference/docs/index.md
+++ /dev/null
@@ -1,114 +0,0 @@
----
-title: CoreGO
-description: AX-first documentation for the CoreGO framework.
----
-
-# CoreGO
-
-CoreGO is the foundation layer for the Core ecosystem. It gives you one container, one command tree, one message bus, and a small set of shared primitives that repeat across the whole framework.
-
-The current module path is `dappco.re/go/core`.
-
-Start with [RFC.md](RFC.md) for the full API contract and [../RFC-025-AGENT-EXPERIENCE.md](../RFC-025-AGENT-EXPERIENCE.md) for the AX design rules that shape it.
-
-## AX View
-
-CoreGO already follows the main AX ideas from RFC-025:
-
-- predictable names such as `Core`, `Service`, `Command`, `Options`, `Result`, `Message`
-- path-shaped command registration such as `deploy/to/homelab`
-- one repeated input shape (`Options`) and one repeated return shape (`Result`)
-- comments and examples that show real usage instead of restating the type signature
-
-## What CoreGO Owns
-
-| Surface | Purpose |
-|---------|---------|
-| `Core` | Central container and access point |
-| `Service` | Managed lifecycle component |
-| `Command` | Path-based command tree node |
-| `ACTION`, `QUERY`, `PERFORM` | Decoupled communication between components |
-| `Data`, `Drive`, `Fs`, `Config`, `I18n`, `Cli` | Built-in subsystems for common runtime work |
-| `E`, `Wrap`, `ErrorLog`, `ErrorPanic` | Structured failures and panic recovery |
-
-## Quick Example
-
-```go
-package main
-
-import (
- "context"
- "fmt"
-
- "dappco.re/go/core"
-)
-
-type flushCacheTask struct {
- Name string
-}
-
-func main() {
- c := core.New(core.Options{
- {Key: "name", Value: "agent-workbench"},
- })
-
- c.Service("cache", core.Service{
- OnStart: func() core.Result {
- core.Info("cache ready", "app", c.App().Name)
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- core.Info("cache stopped", "app", c.App().Name)
- return core.Result{OK: true}
- },
- })
-
- c.RegisterTask(func(_ *core.Core, task core.Task) core.Result {
- switch task.(type) {
- case flushCacheTask:
- return core.Result{Value: "cache flushed", OK: true}
- }
- return core.Result{}
- })
-
- c.Command("cache/flush", core.Command{
- Action: func(opts core.Options) core.Result {
- return c.PERFORM(flushCacheTask{Name: opts.String("name")})
- },
- })
-
- if !c.ServiceStartup(context.Background(), nil).OK {
- panic("startup failed")
- }
-
- r := c.Cli().Run("cache", "flush", "--name=session-store")
- fmt.Println(r.Value)
-
- _ = c.ServiceShutdown(context.Background())
-}
-```
-
-## Documentation Paths
-
-| Path | Covers |
-|------|--------|
-| [getting-started.md](getting-started.md) | First runnable CoreGO app |
-| [primitives.md](primitives.md) | `Options`, `Result`, `Service`, `Message`, `Query`, `Task` |
-| [services.md](services.md) | Service registry, service locks, runtime helpers |
-| [commands.md](commands.md) | Path-based commands and CLI execution |
-| [messaging.md](messaging.md) | `ACTION`, `QUERY`, `QUERYALL`, `PERFORM`, `PerformAsync` |
-| [lifecycle.md](lifecycle.md) | Startup, shutdown, context, background task draining |
-| [configuration.md](configuration.md) | Constructor options, config state, feature flags |
-| [subsystems.md](subsystems.md) | `App`, `Data`, `Drive`, `Fs`, `I18n`, `Cli` |
-| [errors.md](errors.md) | Structured errors, logging helpers, panic recovery |
-| [testing.md](testing.md) | Test naming and framework-level testing patterns |
-| [pkg/core.md](pkg/core.md) | Package-level reference summary |
-| [pkg/log.md](pkg/log.md) | Logging reference for the root package |
-| [pkg/PACKAGE_STANDARDS.md](pkg/PACKAGE_STANDARDS.md) | AX package-authoring guidance |
-
-## Good Reading Order
-
-1. Start with [getting-started.md](getting-started.md).
-2. Learn the repeated shapes in [primitives.md](primitives.md).
-3. Pick the integration path you need next: [services.md](services.md), [commands.md](commands.md), or [messaging.md](messaging.md).
-4. Use [subsystems.md](subsystems.md), [errors.md](errors.md), and [testing.md](testing.md) as reference pages while building.
diff --git a/.core/reference/docs/lifecycle.md b/.core/reference/docs/lifecycle.md
deleted file mode 100644
index 59ba644f..00000000
--- a/.core/reference/docs/lifecycle.md
+++ /dev/null
@@ -1,111 +0,0 @@
----
-title: Lifecycle
-description: Startup, shutdown, context ownership, and background task draining.
----
-
-# Lifecycle
-
-CoreGO manages lifecycle through `core.Service` callbacks, not through reflection or implicit interfaces.
-
-## Service Hooks
-
-```go
-c.Service("cache", core.Service{
- OnStart: func() core.Result {
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- return core.Result{OK: true}
- },
-})
-```
-
-Only services with `OnStart` appear in `Startables()`. Only services with `OnStop` appear in `Stoppables()`.
-
-## `ServiceStartup`
-
-```go
-r := c.ServiceStartup(context.Background(), nil)
-```
-
-### What It Does
-
-1. clears the shutdown flag
-2. stores a new cancellable context on `c.Context()`
-3. runs each `OnStart`
-4. broadcasts `ActionServiceStartup{}`
-
-### Failure Behavior
-
-- if the input context is already cancelled, startup returns that error
-- if any `OnStart` returns `OK:false`, startup stops immediately and returns that result
-
-## `ServiceShutdown`
-
-```go
-r := c.ServiceShutdown(context.Background())
-```
-
-### What It Does
-
-1. sets the shutdown flag
-2. cancels `c.Context()`
-3. broadcasts `ActionServiceShutdown{}`
-4. waits for background tasks created by `PerformAsync`
-5. runs each `OnStop`
-
-### Failure Behavior
-
-- if draining background tasks hits the shutdown context deadline, shutdown returns that context error
-- when service stop hooks fail, CoreGO returns the first error it sees
-
-## Ordering
-
-The current implementation builds `Startables()` and `Stoppables()` by iterating over a map-backed registry.
-
-That means lifecycle order is not guaranteed today.
-
-If your application needs strict startup or shutdown ordering, orchestrate it explicitly inside a smaller number of service callbacks instead of relying on registry order.
-
-## `c.Context()`
-
-`ServiceStartup` creates the context returned by `c.Context()`.
-
-Use it for background work that should stop when the application shuts down:
-
-```go
-c.Service("watcher", core.Service{
- OnStart: func() core.Result {
- go func(ctx context.Context) {
- <-ctx.Done()
- }(c.Context())
- return core.Result{OK: true}
- },
-})
-```
-
-## Built-In Lifecycle Actions
-
-You can listen for lifecycle state changes through the action bus.
-
-```go
-c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
- switch msg.(type) {
- case core.ActionServiceStartup:
- core.Info("core startup completed")
- case core.ActionServiceShutdown:
- core.Info("core shutdown started")
- }
- return core.Result{OK: true}
-})
-```
-
-## Background Task Draining
-
-`ServiceShutdown` waits for the internal task waitgroup to finish before calling stop hooks.
-
-This is what makes `PerformAsync` safe for long-running work that should complete before teardown.
-
-## `OnReload`
-
-`Service` includes an `OnReload` callback field, but CoreGO does not currently expose a top-level lifecycle runner for reload operations.
diff --git a/.core/reference/docs/messaging.md b/.core/reference/docs/messaging.md
deleted file mode 100644
index 688893af..00000000
--- a/.core/reference/docs/messaging.md
+++ /dev/null
@@ -1,171 +0,0 @@
----
-title: Messaging
-description: ACTION, QUERY, QUERYALL, PERFORM, and async task flow.
----
-
-# Messaging
-
-CoreGO uses one message bus for broadcasts, lookups, and work dispatch.
-
-## Message Types
-
-```go
-type Message any
-type Query any
-type Task any
-```
-
-Your own structs define the protocol.
-
-```go
-type repositoryIndexed struct {
- Name string
-}
-
-type repositoryCountQuery struct{}
-
-type syncRepositoryTask struct {
- Name string
-}
-```
-
-## `ACTION`
-
-`ACTION` is a broadcast.
-
-```go
-c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
- switch m := msg.(type) {
- case repositoryIndexed:
- core.Info("repository indexed", "name", m.Name)
- return core.Result{OK: true}
- }
- return core.Result{OK: true}
-})
-
-r := c.ACTION(repositoryIndexed{Name: "core-go"})
-```
-
-### Behavior
-
-- all registered action handlers are called in their current registration order
-- if a handler returns `OK:false`, dispatch stops and that `Result` is returned
-- if no handler fails, `ACTION` returns `Result{OK:true}`
-
-## `QUERY`
-
-`QUERY` is first-match request-response.
-
-```go
-c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
- switch q.(type) {
- case repositoryCountQuery:
- return core.Result{Value: 42, OK: true}
- }
- return core.Result{}
-})
-
-r := c.QUERY(repositoryCountQuery{})
-```
-
-### Behavior
-
-- handlers run until one returns `OK:true`
-- the first successful result wins
-- if nothing handles the query, CoreGO returns an empty `Result`
-
-## `QUERYALL`
-
-`QUERYALL` collects every successful non-nil response.
-
-```go
-r := c.QUERYALL(repositoryCountQuery{})
-results := r.Value.([]any)
-```
-
-### Behavior
-
-- every query handler is called
-- only `OK:true` results with non-nil `Value` are collected
-- the call itself returns `OK:true` even when the result list is empty
-
-## `PERFORM`
-
-`PERFORM` dispatches a task to the first handler that accepts it.
-
-```go
-c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
- switch task := t.(type) {
- case syncRepositoryTask:
- return core.Result{Value: "synced " + task.Name, OK: true}
- }
- return core.Result{}
-})
-
-r := c.PERFORM(syncRepositoryTask{Name: "core-go"})
-```
-
-### Behavior
-
-- handlers run until one returns `OK:true`
-- the first successful result wins
-- if nothing handles the task, CoreGO returns an empty `Result`
-
-## `PerformAsync`
-
-`PerformAsync` runs a task in a background goroutine and returns a generated task identifier.
-
-```go
-r := c.PerformAsync(syncRepositoryTask{Name: "core-go"})
-taskID := r.Value.(string)
-```
-
-### Generated Events
-
-Async execution emits three action messages:
-
-| Message | When |
-|---------|------|
-| `ActionTaskStarted` | just before background execution begins |
-| `ActionTaskProgress` | whenever `Progress` is called |
-| `ActionTaskCompleted` | after the task finishes or panics |
-
-Example listener:
-
-```go
-c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
- switch m := msg.(type) {
- case core.ActionTaskCompleted:
- core.Info("task completed", "task", m.TaskIdentifier, "err", m.Error)
- }
- return core.Result{OK: true}
-})
-```
-
-## Progress Updates
-
-```go
-c.Progress(taskID, 0.5, "indexing commits", syncRepositoryTask{Name: "core-go"})
-```
-
-That broadcasts `ActionTaskProgress`.
-
-## `TaskWithIdentifier`
-
-Tasks that implement `TaskWithIdentifier` receive the generated ID before dispatch.
-
-```go
-type trackedTask struct {
- ID string
- Name string
-}
-
-func (t *trackedTask) SetTaskIdentifier(id string) { t.ID = id }
-func (t *trackedTask) GetTaskIdentifier() string { return t.ID }
-```
-
-## Shutdown Interaction
-
-When shutdown has started, `PerformAsync` returns an empty `Result` instead of scheduling more work.
-
-This is why `ServiceShutdown` can safely drain the outstanding background tasks before stopping services.
diff --git a/.core/reference/docs/pkg/PACKAGE_STANDARDS.md b/.core/reference/docs/pkg/PACKAGE_STANDARDS.md
deleted file mode 100644
index 398bbf65..00000000
--- a/.core/reference/docs/pkg/PACKAGE_STANDARDS.md
+++ /dev/null
@@ -1,138 +0,0 @@
-# AX Package Standards
-
-This page describes how to build packages on top of CoreGO in the style described by RFC-025.
-
-## 1. Prefer Predictable Names
-
-Use names that tell an agent what the thing is without translation.
-
-Good:
-
-- `RepositoryService`
-- `RepositoryServiceOptions`
-- `WorkspaceCountQuery`
-- `SyncRepositoryTask`
-
-Avoid shortening names unless the abbreviation is already universal.
-
-## 2. Put Real Usage in Comments
-
-Write comments that show a real call with realistic values.
-
-Good:
-
-```go
-// Sync a repository into the local workspace cache.
-// svc.SyncRepository("core-go", "/srv/repos/core-go")
-```
-
-Avoid comments that only repeat the signature.
-
-## 3. Keep Paths Semantic
-
-If a command or template lives at a path, let the path explain the intent.
-
-Good:
-
-```text
-deploy/to/homelab
-workspace/create
-template/workspace/go
-```
-
-That keeps the CLI, tests, docs, and message vocabulary aligned.
-
-## 4. Reuse CoreGO Primitives
-
-At Core boundaries, prefer the shared shapes:
-
-- `core.Options` for lightweight input
-- `core.Result` for output
-- `core.Service` for lifecycle registration
-- `core.Message`, `core.Query`, `core.Task` for bus protocols
-
-Inside your package, typed structs are still good. Use `ServiceRuntime[T]` when you want typed package options plus a `Core` reference.
-
-```go
-type repositoryServiceOptions struct {
- BaseDirectory string
-}
-
-type repositoryService struct {
- *core.ServiceRuntime[repositoryServiceOptions]
-}
-```
-
-## 5. Prefer Explicit Registration
-
-Register services and commands with names and paths that stay readable in grep results.
-
-```go
-c.Service("repository", core.Service{...})
-c.Command("repository/sync", core.Command{...})
-```
-
-## 6. Use the Bus for Decoupling
-
-When one package needs another package’s behavior, prefer queries and tasks over tight package coupling.
-
-```go
-type repositoryCountQuery struct{}
-type syncRepositoryTask struct {
- Name string
-}
-```
-
-That keeps the protocol visible in code and easy for agents to follow.
-
-## 7. Use Structured Errors
-
-Use `core.E`, `core.Wrap`, and `core.WrapCode`.
-
-```go
-return core.Result{
- Value: core.E("repository.Sync", "git fetch failed", err),
- OK: false,
-}
-```
-
-Do not introduce free-form `fmt.Errorf` chains in framework code.
-
-## 8. Keep Testing Names Predictable
-
-Follow the repository pattern:
-
-- `_Good`
-- `_Bad`
-- `_Ugly`
-
-Example:
-
-```go
-func TestRepositorySync_Good(t *testing.T) {}
-func TestRepositorySync_Bad(t *testing.T) {}
-func TestRepositorySync_Ugly(t *testing.T) {}
-```
-
-## 9. Prefer Stable Shapes Over Clever APIs
-
-For package APIs, avoid patterns that force an agent to infer too much hidden control flow.
-
-Prefer:
-
-- clear structs
-- explicit names
-- path-based commands
-- visible message types
-
-Avoid:
-
-- implicit global state unless it is truly a default service
-- panic-hiding constructors
-- dense option chains when a small explicit struct would do
-
-## 10. Document the Current Reality
-
-If the implementation is in transition, document what the code does now, not the API shape you plan to have later.
-
-That keeps agents correct on first pass, which is the real AX metric.
diff --git a/.core/reference/docs/pkg/core.md b/.core/reference/docs/pkg/core.md
deleted file mode 100644
index 88bd18b5..00000000
--- a/.core/reference/docs/pkg/core.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# Package Reference: `core`
-
-Import path:
-
-```go
-import "dappco.re/go/core"
-```
-
-This repository exposes one root package. The main areas are:
-
-## Constructors and Accessors
-
-| Name | Purpose |
-|------|---------|
-| `New` | Create a `*Core` |
-| `NewRuntime` | Create an empty runtime wrapper |
-| `NewWithFactories` | Create a runtime wrapper from named service factories |
-| `Options`, `App`, `Data`, `Drive`, `Fs`, `Config`, `Error`, `Log`, `Cli`, `IPC`, `I18n`, `Context` | Access the built-in subsystems |
-
-## Core Primitives
-
-| Name | Purpose |
-|------|---------|
-| `Option`, `Options` | Input configuration and metadata |
-| `Result` | Shared output shape |
-| `Service` | Lifecycle DTO |
-| `Command` | Command tree node |
-| `Message`, `Query`, `Task` | Message bus payload types |
-
-## Service and Runtime APIs
-
-| Name | Purpose |
-|------|---------|
-| `Service` | Register or read a named service |
-| `Services` | List registered service names |
-| `Startables`, `Stoppables` | Snapshot lifecycle-capable services |
-| `LockEnable`, `LockApply` | Activate the service registry lock |
-| `ServiceRuntime[T]` | Helper for package authors |
-
-## Command and CLI APIs
-
-| Name | Purpose |
-|------|---------|
-| `Command` | Register or read a command by path |
-| `Commands` | List command paths |
-| `Cli().Run` | Resolve arguments to a command and execute it |
-| `Cli().PrintHelp` | Show executable commands |
-
-## Messaging APIs
-
-| Name | Purpose |
-|------|---------|
-| `ACTION`, `Action` | Broadcast a message |
-| `QUERY`, `Query` | Return the first successful query result |
-| `QUERYALL`, `QueryAll` | Collect all successful query results |
-| `PERFORM`, `Perform` | Run the first task handler that accepts the task |
-| `PerformAsync` | Run a task in the background |
-| `Progress` | Broadcast async task progress |
-| `RegisterAction`, `RegisterActions`, `RegisterQuery`, `RegisterTask` | Register bus handlers |
-
-## Subsystems
-
-| Name | Purpose |
-|------|---------|
-| `Config` | Runtime settings and feature flags |
-| `Data` | Embedded filesystem mounts |
-| `Drive` | Named transport handles |
-| `Fs` | Local filesystem operations |
-| `I18n` | Locale collection and translation delegation |
-| `App`, `Find` | Application identity and executable lookup |
-
-## Errors and Logging
-
-| Name | Purpose |
-|------|---------|
-| `E`, `Wrap`, `WrapCode`, `NewCode` | Structured error creation |
-| `Operation`, `ErrorCode`, `ErrorMessage`, `Root`, `StackTrace`, `FormatStackTrace` | Error inspection |
-| `NewLog`, `Default`, `SetDefault`, `SetLevel`, `SetRedactKeys` | Logger creation and defaults |
-| `LogErr`, `LogPanic`, `ErrorLog`, `ErrorPanic` | Error-aware logging and panic recovery |
-
-Use the top-level docs in `docs/` for task-oriented guidance, then use this page as a compact reference.
diff --git a/.core/reference/docs/pkg/log.md b/.core/reference/docs/pkg/log.md
deleted file mode 100644
index 15e9db1a..00000000
--- a/.core/reference/docs/pkg/log.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Logging Reference
-
-Logging lives in the root `core` package in this repository. There is no separate `pkg/log` import path here.
-
-## Create a Logger
-
-```go
-logger := core.NewLog(core.LogOptions{
- Level: core.LevelInfo,
-})
-```
-
-## Levels
-
-| Level | Meaning |
-|-------|---------|
-| `LevelQuiet` | no output |
-| `LevelError` | errors and security events |
-| `LevelWarn` | warnings, errors, security events |
-| `LevelInfo` | informational, warnings, errors, security events |
-| `LevelDebug` | everything |
-
-## Log Methods
-
-```go
-logger.Debug("workspace discovered", "path", "/srv/workspaces")
-logger.Info("service started", "service", "audit")
-logger.Warn("retrying fetch", "attempt", 2)
-logger.Error("fetch failed", "err", err)
-logger.Security("sandbox escape detected", "path", attemptedPath)
-```
-
-## Default Logger
-
-The package owns a default logger.
-
-```go
-core.SetLevel(core.LevelDebug)
-core.SetRedactKeys("token", "password")
-
-core.Info("service started", "service", "audit")
-```
-
-## Redaction
-
-Values for keys listed in `RedactKeys` are replaced with `[REDACTED]`.
-
-```go
-logger.SetRedactKeys("token")
-logger.Info("login", "user", "cladius", "token", "secret-value")
-```
-
-## Output and Rotation
-
-```go
-logger := core.NewLog(core.LogOptions{
- Level: core.LevelInfo,
- Output: os.Stderr,
-})
-```
-
-If you provide `Rotation` and set `RotationWriterFactory`, the logger writes to the rotating writer instead of the plain output stream.
-
-## Error-Aware Logging
-
-`LogErr` extracts structured error context before logging:
-
-```go
-le := core.NewLogErr(logger)
-le.Log(err)
-```
-
-`ErrorLog` is the log-and-return wrapper exposed through `c.Log()`.
-
-## Panic-Aware Logging
-
-`LogPanic` is the lightweight panic logger:
-
-```go
-defer core.NewLogPanic(logger).Recover()
-```
-
-It logs the recovered panic but does not manage crash files. For crash reports, use `c.Error().Recover()`.
diff --git a/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-design.md b/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-design.md
deleted file mode 100644
index 08257913..00000000
--- a/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-design.md
+++ /dev/null
@@ -1,261 +0,0 @@
-# Lint Pattern Catalog & Polish Skill Design
-
-> **Partial implementation (14 Mar 2026):** Layer 1 (`core/lint` -- catalog, matcher, scanner, CLI) is fully implemented and documented at `docs/tools/lint/index.md`. Layer 2 (MCP subsystem in `go-ai`) and Layer 3 (Claude Code polish skill in `core/agent`) are NOT implemented. This plan is retained for those remaining layers.
-
-**Goal:** A structured pattern catalog (`core/lint`) that captures recurring code quality findings as regex rules, exposes them via MCP tools in `go-ai`, and orchestrates multi-AI code review via a Claude Code skill in `core/agent`.
-
-**Architecture:** Three layers — a standalone catalog+matcher library (`core/lint`), an MCP subsystem in `go-ai` that exposes lint tools to agents, and a Claude Code plugin in `core/agent` that orchestrates the "polish" workflow (deterministic checks + AI reviewers + feedback loop into the catalog).
-
-**Tech Stack:** Go (catalog, matcher, CLI, MCP subsystem), YAML (rule definitions), JSONL (findings output, compatible with `~/.core/ai/metrics/`), Claude Code plugin format (hooks.json, commands/*.md, plugin.json).
-
----
-
-## Context
-
-During a code review sweep of 18 Go repos (March 2026), AI reviewers (Gemini, Claude) found ~20 recurring patterns: SQL injection, path traversal, XSS, missing constant-time comparison, goroutine leaks, Go 1.26 modernisation opportunities, and more. Many of these patterns repeat across repos.
-
-Currently these findings exist only as commit messages. This design captures them as a reusable, machine-readable catalog that:
-1. Deterministic tools can run immediately (regex matching)
-2. MCP-connected agents can query and apply
-3. LEM models can train on for "does this comply with CoreGo standards?" judgements
-4. Grows automatically as AI reviewers find new patterns
-
-## Layer 1: `core/lint` — Pattern Catalog & Matcher
-
-### Repository Structure
-
-```
-core/lint/
-├── go.mod # forge.lthn.ai/core/lint
-├── catalog/
-│ ├── go-security.yaml # SQL injection, path traversal, XSS, constant-time
-│ ├── go-modernise.yaml # Go 1.26: slices.Clone, wg.Go, maps.Keys, range-over-int
-│ ├── go-correctness.yaml # Deadlocks, goroutine leaks, nil guards, error handling
-│ ├── php-security.yaml # XSS, CSRF, mass assignment, SQL injection
-│ ├── ts-security.yaml # DOM XSS, prototype pollution
-│ └── cpp-safety.yaml # Buffer overflow, use-after-free
-├── pkg/lint/
-│ ├── catalog.go # Load + parse YAML catalog files
-│ ├── rule.go # Rule struct definition
-│ ├── matcher.go # Regex matcher against file contents
-│ ├── report.go # Structured findings output (JSON/JSONL/text)
-│ ├── catalog_test.go
-│ ├── matcher_test.go
-│ └── report_test.go
-├── cmd/core-lint/
-│ └── main.go # `core-lint check ./...` CLI
-└── .core/
- └── build.yaml # Produces core-lint binary
-```
-
-### Rule Schema (YAML)
-
-```yaml
-- id: go-sec-001
- title: "SQL wildcard injection in LIKE clauses"
- severity: high # critical, high, medium, low, info
- languages: [go]
- tags: [security, injection, owasp-a03]
- pattern: 'LIKE\s+\?\s*,\s*["\x60]%\s*\+'
- exclude_pattern: 'EscapeLike' # suppress if this also matches
- fix: "Use parameterised LIKE with explicit escaping of % and _ characters"
- found_in: [go-store] # repos where first discovered
- example_bad: |
- db.Where("name LIKE ?", "%"+input+"%")
- example_good: |
- db.Where("name LIKE ?", EscapeLike(input))
- first_seen: "2026-03-09"
- detection: regex # future: ast, semantic
- auto_fixable: false # future: true when we add codemods
-```
-
-### Rule Struct (Go)
-
-```go
-type Rule struct {
- ID string `yaml:"id"`
- Title string `yaml:"title"`
- Severity string `yaml:"severity"`
- Languages []string `yaml:"languages"`
- Tags []string `yaml:"tags"`
- Pattern string `yaml:"pattern"`
- ExcludePattern string `yaml:"exclude_pattern,omitempty"`
- Fix string `yaml:"fix"`
- FoundIn []string `yaml:"found_in,omitempty"`
- ExampleBad string `yaml:"example_bad,omitempty"`
- ExampleGood string `yaml:"example_good,omitempty"`
- FirstSeen string `yaml:"first_seen"`
- Detection string `yaml:"detection"` // regex | ast | semantic
- AutoFixable bool `yaml:"auto_fixable"`
-}
-```
-
-### Finding Struct (Go)
-
-Designed to align with go-ai's `ScanAlert` shape and `~/.core/ai/metrics/` JSONL format:
-
-```go
-type Finding struct {
- RuleID string `json:"rule_id"`
- Title string `json:"title"`
- Severity string `json:"severity"`
- File string `json:"file"`
- Line int `json:"line"`
- Match string `json:"match"` // matched text
- Fix string `json:"fix"`
- Repo string `json:"repo,omitempty"`
-}
-```
-
-### CLI Interface
-
-```bash
-# Check current directory against all catalogs for detected languages
-core-lint check ./...
-
-# Check specific languages/catalogs
-core-lint check --lang go --catalog go-security ./pkg/...
-
-# Output as JSON (for piping to other tools)
-core-lint check --format json ./...
-
-# List available rules
-core-lint catalog list
-core-lint catalog list --lang go --severity high
-
-# Show a specific rule with examples
-core-lint catalog show go-sec-001
-```
-
-## Layer 2: `go-ai` Lint MCP Subsystem
-
-New subsystem registered alongside files/rag/ml/brain:
-
-```go
-type LintSubsystem struct {
- catalog *lint.Catalog
- root string // workspace root for scanning
-}
-
-func (s *LintSubsystem) Name() string { return "lint" }
-
-func (s *LintSubsystem) RegisterTools(server *mcp.Server) {
- // lint_check - run rules against workspace files
- // lint_catalog - list/search available rules
- // lint_report - get findings summary for a path
-}
-```
-
-### MCP Tools
-
-| Tool | Input | Output | Group |
-|------|-------|--------|-------|
-| `lint_check` | `{path: string, lang?: string, severity?: string}` | `{findings: []Finding}` | lint |
-| `lint_catalog` | `{lang?: string, tags?: []string, severity?: string}` | `{rules: []Rule}` | lint |
-| `lint_report` | `{path: string, format?: "summary" or "detailed"}` | `{summary: ReportSummary}` | lint |
-
-This means any MCP-connected agent (Claude, LEM, Codex) can call `lint_check` to scan code against the catalog.
-
-## Layer 3: `core/agent` Polish Skill
-
-Claude Code plugin at `core/agent/claude/polish/`:
-
-```
-core/agent/claude/polish/
-├── plugin.json
-├── hooks.json # optional: PostToolUse after git commit
-├── commands/
-│ └── polish.md # /polish slash command
-└── scripts/
- └── run-lint.sh # shells out to core-lint
-```
-
-### `/polish` Command Flow
-
-1. Run `core-lint check ./...` for fast deterministic findings
-2. Report findings to user
-3. Optionally run AI reviewers (Gemini CLI, Codex) for deeper analysis
-4. Deduplicate AI findings against catalog (already-known patterns)
-5. Propose new patterns as catalog additions (PR to core/lint)
-
-### Subagent Configuration (`.core/agents/`)
-
-Repos can configure polish behaviour:
-
-```yaml
-# any-repo/.core/agents/polish.yaml
-languages: [go]
-catalogs: [go-security, go-modernise, go-correctness]
-reviewers: [gemini] # which AI tools to invoke
-exclude: [vendor/, testdata/, *_test.go]
-severity_threshold: medium # only report medium+ findings
-```
-
-## Findings to LEM Pipeline
-
-```
-core-lint check -> findings.json
- |
- v
-~/.core/ai/metrics/YYYY-MM-DD.jsonl (audit trail)
- |
- v
-LEM training data:
- - Rule examples (bad/good pairs) -> supervised training signal
- - Finding frequency -> pattern importance weighting
- - Rule descriptions -> natural language understanding of "why"
- |
- v
-LEM tool: "does this code comply with CoreGo standards?"
- -> queries lint_catalog via MCP
- -> applies learned pattern recognition
- -> reports violations with rule IDs and fixes
-```
-
-## Initial Catalog Seed
-
-From the March 2026 ecosystem sweep:
-
-| ID | Title | Severity | Language | Found In |
-|----|-------|----------|----------|----------|
-| go-sec-001 | SQL wildcard injection | high | go | go-store |
-| go-sec-002 | Path traversal in cache keys | high | go | go-cache |
-| go-sec-003 | XSS in HTML output | high | go | go-html |
-| go-sec-004 | Non-constant-time auth comparison | high | go | go-crypt |
-| go-sec-005 | Log injection via unescaped input | medium | go | go-log |
-| go-sec-006 | Key material in log output | high | go | go-log |
-| go-cor-001 | Goroutine leak (no WaitGroup) | high | go | core/go |
-| go-cor-002 | Shutdown deadlock (wg.Wait no timeout) | high | go | core/go |
-| go-cor-003 | Silent error swallowing | medium | go | go-process, go-ratelimit |
-| go-cor-004 | Panic in library code | medium | go | go-i18n |
-| go-cor-005 | Delete without path validation | high | go | go-io |
-| go-mod-001 | Manual slice clone (append nil pattern) | low | go | core/go |
-| go-mod-002 | Manual sort instead of slices.Sorted | low | go | core/go |
-| go-mod-003 | Manual reverse loop instead of slices.Backward | low | go | core/go |
-| go-mod-004 | sync.WaitGroup Add+Done instead of Go() | low | go | core/go |
-| go-mod-005 | Manual map key collection instead of maps.Keys | low | go | core/go |
-| go-cor-006 | Missing error return from API calls | medium | go | go-forge, go-git |
-| go-cor-007 | Signal handler uses wrong type | medium | go | go-process |
-
-## Dependencies
-
-```
-core/lint (standalone, zero core deps)
- ^
- |
-go-ai/mcp/lint/ (imports core/lint for catalog + matcher)
- ^
- |
-core/agent/claude/polish/ (shells out to core-lint CLI)
-```
-
-`core/lint` has no dependency on `core/go` or any other framework module. It is a standalone library + CLI, like `core/go-io`.
-
-## Future Extensions (Not Built Now)
-
-- **AST-based detection** (layer 2): Parse Go/PHP AST, match structural patterns
-- **Semantic detection** (layer 3): LEM judges code against rule descriptions
-- **Auto-fix codemods**: `core-lint fix` applies known fixes automatically
-- **CI integration**: GitHub Actions workflow runs `core-lint check` on PRs
-- **CodeRabbit integration**: Import CodeRabbit findings as catalog entries
-- **Cross-repo dashboard**: Aggregate findings across all repos in workspace
diff --git a/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-plan.md b/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-plan.md
deleted file mode 100644
index 7f1ddec2..00000000
--- a/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-plan.md
+++ /dev/null
@@ -1,1668 +0,0 @@
-# Lint Pattern Catalog Implementation Plan
-
-> **Fully implemented (14 Mar 2026).** All tasks in this plan are complete. The `core/lint` module ships 18 rules across 3 catalogs, with a working CLI and embedded YAML. This plan is retained alongside the design doc, which tracks the remaining MCP and polish skill layers.
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Build `core/lint` — a standalone Go library + CLI that loads YAML pattern catalogs and runs regex-based code checks, seeded with 18 patterns from the March 2026 ecosystem sweep.
-
-**Architecture:** Standalone Go module (`forge.lthn.ai/core/lint`) with zero framework deps. YAML catalog files define rules (id, severity, regex pattern, fix). `pkg/lint` loads catalogs and matches patterns against files. `cmd/core-lint` is a Cobra CLI. Uses `cli.Main()` + `cli.WithCommands()` from `core/cli`.
-
-**Tech Stack:** Go 1.26, `gopkg.in/yaml.v3` (YAML parsing), `forge.lthn.ai/core/cli` (CLI framework), `github.com/stretchr/testify` (testing), `embed` (catalog embedding).
-
----
-
-### Task 1: Create repo and Go module
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/go.mod`
-- Create: `/Users/snider/Code/core/lint/.core/build.yaml`
-- Create: `/Users/snider/Code/core/lint/CLAUDE.md`
-
-**Step 1: Create repo on forge**
-
-```bash
-ssh -p 2223 git@forge.lthn.ai
-```
-
-If SSH repo creation isn't available, create via Forgejo API:
-```bash
-curl -X POST "https://forge.lthn.ai/api/v1/orgs/core/repos" \
- -H "Authorization: token $FORGE_TOKEN" \
- -H "Content-Type: application/json" \
- -d '{"name":"lint","description":"Pattern catalog & regex matcher for code quality","auto_init":true,"default_branch":"main"}'
-```
-
-Or manually create on forge.lthn.ai web UI under the `core` org.
-
-**Step 2: Clone and initialise Go module**
-
-```bash
-cd ~/Code/core
-git clone ssh://git@forge.lthn.ai:2223/core/lint.git
-cd lint
-go mod init forge.lthn.ai/core/lint
-```
-
-Set Go version in go.mod:
-```
-module forge.lthn.ai/core/lint
-
-go 1.26.0
-```
-
-**Step 3: Create `.core/build.yaml`**
-
-```yaml
-version: 1
-
-project:
- name: core-lint
- description: Pattern catalog and regex code checker
- main: ./cmd/core-lint
- binary: core-lint
-
-build:
- cgo: false
- flags:
- - -trimpath
- ldflags:
- - -s
- - -w
-
-targets:
- - os: linux
- arch: amd64
- - os: linux
- arch: arm64
- - os: darwin
- arch: arm64
- - os: windows
- arch: amd64
-```
-
-**Step 4: Create `CLAUDE.md`**
-
-```markdown
-# CLAUDE.md
-
-## Project Overview
-
-`core/lint` is a standalone pattern catalog and regex-based code checker. It loads YAML rule definitions and matches them against source files. Zero framework dependencies.
-
-## Build & Development
-
-```bash
-core go test
-core go qa
-core build # produces ./bin/core-lint
-```
-
-## Architecture
-
-- `catalog/` — YAML rule files (embedded at compile time)
-- `pkg/lint/` — Library: Rule, Catalog, Matcher, Report types
-- `cmd/core-lint/` — CLI binary using `cli.Main()`
-
-## Rule Schema
-
-Each YAML file contains an array of rules with: id, title, severity, languages, tags, pattern (regex), exclude_pattern, fix, example_bad, example_good, detection type.
-
-## Coding Standards
-
-- UK English
-- `declare(strict_types=1)` equivalent: all functions have typed params/returns
-- Tests use testify
-- License: EUPL-1.2
-```
-
-**Step 5: Add to go.work**
-
-Add `./core/lint` to `~/Code/go.work` under the Core framework section.
-
-**Step 6: Commit**
-
-```bash
-git add go.mod .core/ CLAUDE.md
-git commit -m "feat: initialise core/lint module"
-```
-
----
-
-### Task 2: Rule struct and YAML parsing
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/rule.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/rule_test.go`
-
-**Step 1: Write the failing test**
-
-```go
-package lint
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestParseRules(t *testing.T) {
- yaml := `
-- id: test-001
- title: "Test rule"
- severity: high
- languages: [go]
- tags: [security]
- pattern: 'fmt\.Println'
- fix: "Use structured logging"
- detection: regex
-`
- rules, err := ParseRules([]byte(yaml))
- require.NoError(t, err)
- require.Len(t, rules, 1)
- assert.Equal(t, "test-001", rules[0].ID)
- assert.Equal(t, "high", rules[0].Severity)
- assert.Equal(t, []string{"go"}, rules[0].Languages)
- assert.Equal(t, `fmt\.Println`, rules[0].Pattern)
-}
-
-func TestParseRules_Invalid(t *testing.T) {
- _, err := ParseRules([]byte("not: valid: yaml: ["))
- assert.Error(t, err)
-}
-
-func TestRule_Validate(t *testing.T) {
- good := Rule{ID: "x-001", Title: "T", Severity: "high", Languages: []string{"go"}, Pattern: "foo", Detection: "regex"}
- assert.NoError(t, good.Validate())
-
- bad := Rule{} // missing required fields
- assert.Error(t, bad.Validate())
-}
-
-func TestRule_Validate_BadRegex(t *testing.T) {
- r := Rule{ID: "x-001", Title: "T", Severity: "high", Languages: []string{"go"}, Pattern: "[invalid", Detection: "regex"}
- assert.Error(t, r.Validate())
-}
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v`
-Expected: FAIL — `ParseRules` and `Rule` not defined
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "fmt"
- "regexp"
-
- "gopkg.in/yaml.v3"
-)
-
-// Rule defines a single lint pattern check.
-type Rule struct {
- ID string `yaml:"id" json:"id"`
- Title string `yaml:"title" json:"title"`
- Severity string `yaml:"severity" json:"severity"`
- Languages []string `yaml:"languages" json:"languages"`
- Tags []string `yaml:"tags" json:"tags"`
- Pattern string `yaml:"pattern" json:"pattern"`
- ExcludePattern string `yaml:"exclude_pattern" json:"exclude_pattern,omitempty"`
- Fix string `yaml:"fix" json:"fix"`
- FoundIn []string `yaml:"found_in" json:"found_in,omitempty"`
- ExampleBad string `yaml:"example_bad" json:"example_bad,omitempty"`
- ExampleGood string `yaml:"example_good" json:"example_good,omitempty"`
- FirstSeen string `yaml:"first_seen" json:"first_seen,omitempty"`
- Detection string `yaml:"detection" json:"detection"`
- AutoFixable bool `yaml:"auto_fixable" json:"auto_fixable"`
-}
-
-// Validate checks that a Rule has all required fields and a compilable regex pattern.
-func (r *Rule) Validate() error {
- if r.ID == "" {
- return fmt.Errorf("rule missing id")
- }
- if r.Title == "" {
- return fmt.Errorf("rule %s: missing title", r.ID)
- }
- if r.Severity == "" {
- return fmt.Errorf("rule %s: missing severity", r.ID)
- }
- if len(r.Languages) == 0 {
- return fmt.Errorf("rule %s: missing languages", r.ID)
- }
- if r.Pattern == "" {
- return fmt.Errorf("rule %s: missing pattern", r.ID)
- }
- if r.Detection == "regex" {
- if _, err := regexp.Compile(r.Pattern); err != nil {
- return fmt.Errorf("rule %s: invalid regex: %w", r.ID, err)
- }
- }
- return nil
-}
-
-// ParseRules parses YAML bytes into a slice of Rules.
-func ParseRules(data []byte) ([]Rule, error) {
- var rules []Rule
- if err := yaml.Unmarshal(data, &rules); err != nil {
- return nil, fmt.Errorf("parse rules: %w", err)
- }
- return rules, nil
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v`
-Expected: PASS (4 tests)
-
-**Step 5: Add yaml dependency**
-
-```bash
-cd ~/Code/core/lint && go get gopkg.in/yaml.v3 && go get github.com/stretchr/testify
-```
-
-**Step 6: Commit**
-
-```bash
-git add pkg/lint/rule.go pkg/lint/rule_test.go go.mod go.sum
-git commit -m "feat: add Rule struct with YAML parsing and validation"
-```
-
----
-
-### Task 3: Catalog loader with embed support
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/catalog.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/catalog_test.go`
-- Create: `/Users/snider/Code/core/lint/catalog/go-security.yaml` (minimal test file)
-
-**Step 1: Create a minimal test catalog file**
-
-Create `/Users/snider/Code/core/lint/catalog/go-security.yaml`:
-```yaml
-- id: go-sec-001
- title: "SQL wildcard injection in LIKE clauses"
- severity: high
- languages: [go]
- tags: [security, injection]
- pattern: 'LIKE\s+\?\s*,\s*["%].*\+'
- fix: "Use parameterised LIKE with EscapeLike()"
- found_in: [go-store]
- first_seen: "2026-03-09"
- detection: regex
-```
-
-**Step 2: Write the failing test**
-
-```go
-package lint
-
-import (
- "embed"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestCatalog_LoadDir(t *testing.T) {
- // Find the catalog/ dir relative to the module root
- dir := filepath.Join("..", "..", "catalog")
- cat, err := LoadDir(dir)
- require.NoError(t, err)
- assert.Greater(t, len(cat.Rules), 0)
- assert.Equal(t, "go-sec-001", cat.Rules[0].ID)
-}
-
-func TestCatalog_LoadDir_NotExist(t *testing.T) {
- _, err := LoadDir("/nonexistent")
- assert.Error(t, err)
-}
-
-func TestCatalog_Filter_Language(t *testing.T) {
- cat := &Catalog{Rules: []Rule{
- {ID: "go-001", Languages: []string{"go"}, Severity: "high"},
- {ID: "php-001", Languages: []string{"php"}, Severity: "high"},
- }}
- filtered := cat.ForLanguage("go")
- assert.Len(t, filtered, 1)
- assert.Equal(t, "go-001", filtered[0].ID)
-}
-
-func TestCatalog_Filter_Severity(t *testing.T) {
- cat := &Catalog{Rules: []Rule{
- {ID: "a", Severity: "high"},
- {ID: "b", Severity: "low"},
- {ID: "c", Severity: "medium"},
- }}
- filtered := cat.AtSeverity("medium")
- assert.Len(t, filtered, 2) // high + medium
-}
-
-func TestCatalog_LoadFS(t *testing.T) {
- // Write temp yaml
- dir := t.TempDir()
- data := []byte(`- id: fs-001
- title: "FS test"
- severity: low
- languages: [go]
- tags: []
- pattern: 'test'
- fix: "fix"
- detection: regex
-`)
- require.NoError(t, os.WriteFile(filepath.Join(dir, "test.yaml"), data, 0644))
-
- cat, err := LoadDir(dir)
- require.NoError(t, err)
- assert.Len(t, cat.Rules, 1)
-}
-```
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "embed"
- "fmt"
- "io/fs"
- "os"
- "path/filepath"
- "slices"
- "strings"
-)
-
-// Catalog holds a collection of lint rules loaded from YAML files.
-type Catalog struct {
- Rules []Rule
-}
-
-// severityOrder maps severity names to numeric priority (higher = more severe).
-var severityOrder = map[string]int{
- "critical": 5,
- "high": 4,
- "medium": 3,
- "low": 2,
- "info": 1,
-}
-
-// LoadDir loads all .yaml files from a directory path into a Catalog.
-func LoadDir(dir string) (*Catalog, error) {
- entries, err := os.ReadDir(dir)
- if err != nil {
- return nil, fmt.Errorf("load catalog dir: %w", err)
- }
-
- cat := &Catalog{}
- for _, entry := range entries {
- if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yaml") {
- continue
- }
- data, err := os.ReadFile(filepath.Join(dir, entry.Name()))
- if err != nil {
- return nil, fmt.Errorf("read %s: %w", entry.Name(), err)
- }
- rules, err := ParseRules(data)
- if err != nil {
- return nil, fmt.Errorf("parse %s: %w", entry.Name(), err)
- }
- cat.Rules = append(cat.Rules, rules...)
- }
- return cat, nil
-}
-
-// LoadFS loads all .yaml files from an embed.FS into a Catalog.
-func LoadFS(fsys embed.FS, dir string) (*Catalog, error) {
- cat := &Catalog{}
- err := fs.WalkDir(fsys, dir, func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
- if d.IsDir() || !strings.HasSuffix(path, ".yaml") {
- return nil
- }
- data, err := fsys.ReadFile(path)
- if err != nil {
- return fmt.Errorf("read %s: %w", path, err)
- }
- rules, err := ParseRules(data)
- if err != nil {
- return fmt.Errorf("parse %s: %w", path, err)
- }
- cat.Rules = append(cat.Rules, rules...)
- return nil
- })
- if err != nil {
- return nil, err
- }
- return cat, nil
-}
-
-// ForLanguage returns rules that apply to the given language.
-func (c *Catalog) ForLanguage(lang string) []Rule {
- var out []Rule
- for _, r := range c.Rules {
- if slices.Contains(r.Languages, lang) {
- out = append(out, r)
- }
- }
- return out
-}
-
-// AtSeverity returns rules at or above the given severity threshold.
-func (c *Catalog) AtSeverity(threshold string) []Rule {
- minLevel := severityOrder[threshold]
- var out []Rule
- for _, r := range c.Rules {
- if severityOrder[r.Severity] >= minLevel {
- out = append(out, r)
- }
- }
- return out
-}
-
-// ByID returns a rule by its ID, or nil if not found.
-func (c *Catalog) ByID(id string) *Rule {
- for i := range c.Rules {
- if c.Rules[i].ID == id {
- return &c.Rules[i]
- }
- }
- return nil
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v`
-Expected: PASS (all tests)
-
-**Step 5: Commit**
-
-```bash
-git add pkg/lint/catalog.go pkg/lint/catalog_test.go catalog/go-security.yaml
-git commit -m "feat: add Catalog loader with dir/embed/filter support"
-```
-
----
-
-### Task 4: Regex matcher
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/matcher.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/matcher_test.go`
-
-**Step 1: Write the failing test**
-
-```go
-package lint
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestMatcher_Match(t *testing.T) {
- rules := []Rule{
- {
- ID: "test-001",
- Title: "fmt.Println usage",
- Severity: "low",
- Languages: []string{"go"},
- Pattern: `fmt\.Println`,
- Fix: "Use structured logging",
- Detection: "regex",
- },
- }
- m, err := NewMatcher(rules)
- require.NoError(t, err)
-
- content := `package main
-
-import "fmt"
-
-func main() {
- fmt.Println("hello")
-}
-`
- findings := m.Match("main.go", []byte(content))
- require.Len(t, findings, 1)
- assert.Equal(t, "test-001", findings[0].RuleID)
- assert.Equal(t, "main.go", findings[0].File)
- assert.Equal(t, 6, findings[0].Line)
- assert.Contains(t, findings[0].Match, "fmt.Println")
-}
-
-func TestMatcher_ExcludePattern(t *testing.T) {
- rules := []Rule{
- {
- ID: "test-002",
- Title: "Println with exclude",
- Severity: "low",
- Languages: []string{"go"},
- Pattern: `fmt\.Println`,
- ExcludePattern: `// lint:ignore`,
- Fix: "Use logging",
- Detection: "regex",
- },
- }
- m, err := NewMatcher(rules)
- require.NoError(t, err)
-
- content := `package main
-func a() { fmt.Println("bad") }
-func b() { fmt.Println("ok") // lint:ignore }
-`
- findings := m.Match("main.go", []byte(content))
- // Line 2 matches, line 3 is excluded
- assert.Len(t, findings, 1)
- assert.Equal(t, 2, findings[0].Line)
-}
-
-func TestMatcher_NoMatch(t *testing.T) {
- rules := []Rule{
- {ID: "test-003", Title: "T", Severity: "low", Languages: []string{"go"}, Pattern: `NEVER_MATCH_THIS`, Detection: "regex"},
- }
- m, err := NewMatcher(rules)
- require.NoError(t, err)
-
- findings := m.Match("main.go", []byte("package main\n"))
- assert.Empty(t, findings)
-}
-
-func TestMatcher_InvalidRegex(t *testing.T) {
- rules := []Rule{
- {ID: "bad", Title: "T", Severity: "low", Languages: []string{"go"}, Pattern: `[invalid`, Detection: "regex"},
- }
- _, err := NewMatcher(rules)
- assert.Error(t, err)
-}
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestMatcher`
-Expected: FAIL — `NewMatcher` not defined
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "fmt"
- "regexp"
- "strings"
-)
-
-// Finding represents a single match of a rule against source code.
-type Finding struct {
- RuleID string `json:"rule_id"`
- Title string `json:"title"`
- Severity string `json:"severity"`
- File string `json:"file"`
- Line int `json:"line"`
- Match string `json:"match"`
- Fix string `json:"fix"`
- Repo string `json:"repo,omitempty"`
-}
-
-// compiledRule is a rule with its regex pre-compiled.
-type compiledRule struct {
- rule Rule
- pattern *regexp.Regexp
- exclude *regexp.Regexp
-}
-
-// Matcher runs compiled rules against file contents.
-type Matcher struct {
- rules []compiledRule
-}
-
-// NewMatcher compiles all rule patterns and returns a Matcher.
-func NewMatcher(rules []Rule) (*Matcher, error) {
- compiled := make([]compiledRule, 0, len(rules))
- for _, r := range rules {
- if r.Detection != "regex" {
- continue // skip non-regex rules
- }
- p, err := regexp.Compile(r.Pattern)
- if err != nil {
- return nil, fmt.Errorf("rule %s: invalid pattern: %w", r.ID, err)
- }
- cr := compiledRule{rule: r, pattern: p}
- if r.ExcludePattern != "" {
- ex, err := regexp.Compile(r.ExcludePattern)
- if err != nil {
- return nil, fmt.Errorf("rule %s: invalid exclude_pattern: %w", r.ID, err)
- }
- cr.exclude = ex
- }
- compiled = append(compiled, cr)
- }
- return &Matcher{rules: compiled}, nil
-}
-
-// Match checks file contents against all rules and returns findings.
-func (m *Matcher) Match(filename string, content []byte) []Finding {
- lines := strings.Split(string(content), "\n")
- var findings []Finding
-
- for _, cr := range m.rules {
- for i, line := range lines {
- if !cr.pattern.MatchString(line) {
- continue
- }
- if cr.exclude != nil && cr.exclude.MatchString(line) {
- continue
- }
- findings = append(findings, Finding{
- RuleID: cr.rule.ID,
- Title: cr.rule.Title,
- Severity: cr.rule.Severity,
- File: filename,
- Line: i + 1,
- Match: strings.TrimSpace(line),
- Fix: cr.rule.Fix,
- })
- }
- }
- return findings
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestMatcher`
-Expected: PASS (4 tests)
-
-**Step 5: Commit**
-
-```bash
-git add pkg/lint/matcher.go pkg/lint/matcher_test.go
-git commit -m "feat: add regex Matcher with exclude pattern support"
-```
-
----
-
-### Task 5: Report output (JSON, text, JSONL)
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/report.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/report_test.go`
-
-**Step 1: Write the failing test**
-
-```go
-package lint
-
-import (
- "bytes"
- "encoding/json"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestReport_JSON(t *testing.T) {
- findings := []Finding{
- {RuleID: "x-001", Title: "Test", Severity: "high", File: "a.go", Line: 10, Match: "bad code", Fix: "fix it"},
- }
- var buf bytes.Buffer
- require.NoError(t, WriteJSON(&buf, findings))
-
- var parsed []Finding
- require.NoError(t, json.Unmarshal(buf.Bytes(), &parsed))
- assert.Len(t, parsed, 1)
- assert.Equal(t, "x-001", parsed[0].RuleID)
-}
-
-func TestReport_JSONL(t *testing.T) {
- findings := []Finding{
- {RuleID: "a-001", File: "a.go", Line: 1},
- {RuleID: "b-001", File: "b.go", Line: 2},
- }
- var buf bytes.Buffer
- require.NoError(t, WriteJSONL(&buf, findings))
-
- lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
- assert.Len(t, lines, 2)
-}
-
-func TestReport_Text(t *testing.T) {
- findings := []Finding{
- {RuleID: "x-001", Title: "Test rule", Severity: "high", File: "main.go", Line: 42, Match: "bad()", Fix: "use good()"},
- }
- var buf bytes.Buffer
- WriteText(&buf, findings)
-
- out := buf.String()
- assert.Contains(t, out, "main.go:42")
- assert.Contains(t, out, "x-001")
- assert.Contains(t, out, "high")
-}
-
-func TestReport_Summary(t *testing.T) {
- findings := []Finding{
- {Severity: "high"},
- {Severity: "high"},
- {Severity: "low"},
- }
- s := Summarise(findings)
- assert.Equal(t, 3, s.Total)
- assert.Equal(t, 2, s.BySeverity["high"])
- assert.Equal(t, 1, s.BySeverity["low"])
-}
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestReport`
-Expected: FAIL — functions not defined
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "encoding/json"
- "fmt"
- "io"
-)
-
-// Summary holds aggregate stats about findings.
-type Summary struct {
- Total int `json:"total"`
- BySeverity map[string]int `json:"by_severity"`
-}
-
-// Summarise creates a Summary from a list of findings.
-func Summarise(findings []Finding) Summary {
- s := Summary{
- Total: len(findings),
- BySeverity: make(map[string]int),
- }
- for _, f := range findings {
- s.BySeverity[f.Severity]++
- }
- return s
-}
-
-// WriteJSON writes findings as a JSON array.
-func WriteJSON(w io.Writer, findings []Finding) error {
- enc := json.NewEncoder(w)
- enc.SetIndent("", " ")
- return enc.Encode(findings)
-}
-
-// WriteJSONL writes findings as newline-delimited JSON (one object per line).
-// Compatible with ~/.core/ai/metrics/ format.
-func WriteJSONL(w io.Writer, findings []Finding) error {
- enc := json.NewEncoder(w)
- for _, f := range findings {
- if err := enc.Encode(f); err != nil {
- return err
- }
- }
- return nil
-}
-
-// WriteText writes findings as human-readable text.
-func WriteText(w io.Writer, findings []Finding) {
- for _, f := range findings {
- fmt.Fprintf(w, "%s:%d [%s] %s (%s)\n", f.File, f.Line, f.Severity, f.Title, f.RuleID)
- if f.Fix != "" {
- fmt.Fprintf(w, " fix: %s\n", f.Fix)
- }
- }
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestReport`
-Expected: PASS (4 tests)
-
-**Step 5: Commit**
-
-```bash
-git add pkg/lint/report.go pkg/lint/report_test.go
-git commit -m "feat: add report output (JSON, JSONL, text, summary)"
-```
-
----
-
-### Task 6: Scanner (walk files + match)
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/scanner.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/scanner_test.go`
-
-**Step 1: Write the failing test**
-
-```go
-package lint
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestScanner_ScanDir(t *testing.T) {
- // Set up temp dir with a .go file containing a known pattern
- dir := t.TempDir()
- goFile := filepath.Join(dir, "main.go")
- require.NoError(t, os.WriteFile(goFile, []byte(`package main
-
-import "fmt"
-
-func main() {
- fmt.Println("hello")
-}
-`), 0644))
-
- rules := []Rule{
- {ID: "test-001", Title: "Println", Severity: "low", Languages: []string{"go"}, Pattern: `fmt\.Println`, Fix: "log", Detection: "regex"},
- }
-
- s, err := NewScanner(rules)
- require.NoError(t, err)
-
- findings, err := s.ScanDir(dir)
- require.NoError(t, err)
- require.Len(t, findings, 1)
- assert.Equal(t, "test-001", findings[0].RuleID)
-}
-
-func TestScanner_ScanDir_ExcludesVendor(t *testing.T) {
- dir := t.TempDir()
- vendor := filepath.Join(dir, "vendor")
- require.NoError(t, os.MkdirAll(vendor, 0755))
- require.NoError(t, os.WriteFile(filepath.Join(vendor, "lib.go"), []byte("package lib\nfunc x() { fmt.Println() }\n"), 0644))
-
- rules := []Rule{
- {ID: "test-001", Title: "Println", Severity: "low", Languages: []string{"go"}, Pattern: `fmt\.Println`, Fix: "log", Detection: "regex"},
- }
-
- s, err := NewScanner(rules)
- require.NoError(t, err)
-
- findings, err := s.ScanDir(dir)
- require.NoError(t, err)
- assert.Empty(t, findings)
-}
-
-func TestScanner_LanguageDetection(t *testing.T) {
- assert.Equal(t, "go", DetectLanguage("main.go"))
- assert.Equal(t, "php", DetectLanguage("app.php"))
- assert.Equal(t, "ts", DetectLanguage("index.ts"))
- assert.Equal(t, "ts", DetectLanguage("index.tsx"))
- assert.Equal(t, "cpp", DetectLanguage("engine.cpp"))
- assert.Equal(t, "cpp", DetectLanguage("engine.cc"))
- assert.Equal(t, "", DetectLanguage("README.md"))
-}
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestScanner`
-Expected: FAIL — `NewScanner` not defined
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-)
-
-// defaultExcludes are directories skipped during scanning.
-var defaultExcludes = []string{"vendor", "node_modules", ".git", "testdata", ".core"}
-
-// extToLang maps file extensions to language identifiers.
-var extToLang = map[string]string{
- ".go": "go",
- ".php": "php",
- ".ts": "ts",
- ".tsx": "ts",
- ".js": "js",
- ".jsx": "js",
- ".cpp": "cpp",
- ".cc": "cpp",
- ".cxx": "cpp",
- ".c": "cpp",
- ".h": "cpp",
- ".hpp": "cpp",
-}
-
-// DetectLanguage returns the language identifier for a filename, or "" if unknown.
-func DetectLanguage(filename string) string {
- ext := filepath.Ext(filename)
- return extToLang[ext]
-}
-
-// Scanner walks directories and matches files against rules.
-type Scanner struct {
- matcher *Matcher
- rules []Rule
- excludes []string
-}
-
-// NewScanner creates a Scanner from a set of rules.
-func NewScanner(rules []Rule) (*Scanner, error) {
- m, err := NewMatcher(rules)
- if err != nil {
- return nil, err
- }
- return &Scanner{
- matcher: m,
- rules: rules,
- excludes: defaultExcludes,
- }, nil
-}
-
-// ScanDir walks a directory tree and returns all findings.
-func (s *Scanner) ScanDir(root string) ([]Finding, error) {
- var all []Finding
-
- err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
- if err != nil {
- return err
- }
-
- // Skip excluded directories
- if d.IsDir() {
- for _, ex := range s.excludes {
- if d.Name() == ex {
- return filepath.SkipDir
- }
- }
- return nil
- }
-
- // Only scan files with known language extensions
- lang := DetectLanguage(path)
- if lang == "" {
- return nil
- }
-
- content, err := os.ReadFile(path)
- if err != nil {
- return fmt.Errorf("read %s: %w", path, err)
- }
-
- // Make path relative to root for cleaner output
- rel, err := filepath.Rel(root, path)
- if err != nil {
- rel = path
- }
-
- findings := s.matcher.Match(rel, content)
- all = append(all, findings...)
- return nil
- })
-
- return all, err
-}
-
-// ScanFile scans a single file and returns findings.
-func (s *Scanner) ScanFile(path string) ([]Finding, error) {
- content, err := os.ReadFile(path)
- if err != nil {
- return nil, fmt.Errorf("read %s: %w", path, err)
- }
- return s.matcher.Match(path, content), nil
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestScanner`
-Expected: PASS (3 tests)
-
-**Step 5: Commit**
-
-```bash
-git add pkg/lint/scanner.go pkg/lint/scanner_test.go
-git commit -m "feat: add Scanner with directory walking and language detection"
-```
-
----
-
-### Task 7: Seed the catalog YAML files
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/catalog/go-security.yaml` (expand from task 3)
-- Create: `/Users/snider/Code/core/lint/catalog/go-correctness.yaml`
-- Create: `/Users/snider/Code/core/lint/catalog/go-modernise.yaml`
-
-**Step 1: Write `catalog/go-security.yaml`**
-
-```yaml
-- id: go-sec-001
- title: "SQL wildcard injection in LIKE clauses"
- severity: high
- languages: [go]
- tags: [security, injection, owasp-a03]
- pattern: 'LIKE\s+\?.*["%`]\s*\%.*\+'
- exclude_pattern: 'EscapeLike'
- fix: "Use parameterised LIKE with explicit escaping of % and _ characters"
- found_in: [go-store]
- example_bad: |
- db.Where("name LIKE ?", "%"+input+"%")
- example_good: |
- db.Where("name LIKE ?", EscapeLike(input))
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-002
- title: "Path traversal in file/cache key operations"
- severity: high
- languages: [go]
- tags: [security, path-traversal, owasp-a01]
- pattern: 'filepath\.Join\(.*,\s*\w+\)'
- exclude_pattern: 'filepath\.Clean|securejoin|ValidatePath'
- fix: "Validate path components do not contain .. before joining"
- found_in: [go-cache]
- example_bad: |
- path := filepath.Join(cacheDir, userInput)
- example_good: |
- if strings.Contains(key, "..") { return ErrInvalidKey }
- path := filepath.Join(cacheDir, key)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-003
- title: "XSS via unescaped HTML output"
- severity: high
- languages: [go]
- tags: [security, xss, owasp-a03]
- pattern: 'fmt\.Sprintf\(.*<.*>.*%s'
- exclude_pattern: 'html\.EscapeString|template\.HTMLEscapeString'
- fix: "Use html.EscapeString() for user-supplied values in HTML output"
- found_in: [go-html]
- example_bad: |
- out := fmt.Sprintf("
%s
", userInput)
- example_good: |
- out := fmt.Sprintf("%s
", html.EscapeString(userInput))
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-004
- title: "Non-constant-time comparison for authentication"
- severity: high
- languages: [go]
- tags: [security, timing-attack, owasp-a02]
- pattern: '==\s*\w*(token|key|secret|password|hash|digest|hmac|mac|sig)'
- exclude_pattern: 'subtle\.ConstantTimeCompare|hmac\.Equal'
- fix: "Use crypto/subtle.ConstantTimeCompare for security-sensitive comparisons"
- found_in: [go-crypt]
- example_bad: |
- if providedToken == storedToken {
- example_good: |
- if subtle.ConstantTimeCompare([]byte(provided), []byte(stored)) == 1 {
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-005
- title: "Log injection via unescaped newlines"
- severity: medium
- languages: [go]
- tags: [security, injection, logging]
- pattern: 'log\.\w+\(.*\+.*\)'
- exclude_pattern: 'strings\.ReplaceAll.*\\n|slog\.'
- fix: "Use structured logging (slog) or sanitise newlines from user input"
- found_in: [go-log]
- example_bad: |
- log.Printf("user login: " + username)
- example_good: |
- slog.Info("user login", "username", username)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-006
- title: "Sensitive key material in log output"
- severity: high
- languages: [go]
- tags: [security, secrets, logging]
- pattern: 'log\.\w+\(.*(?i)(password|secret|token|apikey|private.?key|credential)'
- exclude_pattern: 'REDACTED|\*\*\*|redact'
- fix: "Redact sensitive fields before logging"
- found_in: [go-log]
- example_bad: |
- log.Printf("config: token=%s", cfg.Token)
- example_good: |
- log.Printf("config: token=%s", redact(cfg.Token))
- first_seen: "2026-03-09"
- detection: regex
-```
-
-**Step 2: Write `catalog/go-correctness.yaml`**
-
-```yaml
-- id: go-cor-001
- title: "Goroutine without WaitGroup or context"
- severity: high
- languages: [go]
- tags: [correctness, goroutine-leak]
- pattern: 'go\s+func\s*\('
- exclude_pattern: 'wg\.|\.Go\(|context\.|done\s*<-|select\s*\{'
- fix: "Use sync.WaitGroup.Go() or ensure goroutine has a shutdown signal"
- found_in: [core/go]
- example_bad: |
- go func() { doWork() }()
- example_good: |
- wg.Go(func() { doWork() })
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-002
- title: "WaitGroup.Wait without context/timeout"
- severity: high
- languages: [go]
- tags: [correctness, deadlock]
- pattern: '\.Wait\(\)'
- exclude_pattern: 'select\s*\{|ctx\.Done|context\.With|time\.After'
- fix: "Wrap wg.Wait() in a select with context.Done() or timeout"
- found_in: [core/go]
- example_bad: |
- wg.Wait() // blocks forever if goroutine hangs
- example_good: |
- done := make(chan struct{})
- go func() { wg.Wait(); close(done) }()
- select {
- case <-done:
- case <-ctx.Done():
- }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-003
- title: "Silent error swallowing"
- severity: medium
- languages: [go]
- tags: [correctness, error-handling]
- pattern: '^\s*_\s*=\s*\w+\.\w+\('
- exclude_pattern: 'defer|Close\(|Flush\('
- fix: "Handle or propagate errors instead of discarding with _"
- found_in: [go-process, go-ratelimit]
- example_bad: |
- _ = db.Save(record)
- example_good: |
- if err := db.Save(record); err != nil {
- return fmt.Errorf("save record: %w", err)
- }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-004
- title: "Panic in library code"
- severity: medium
- languages: [go]
- tags: [correctness, panic]
- pattern: '\bpanic\('
- exclude_pattern: '_test\.go|// unreachable|Must\w+\('
- fix: "Return errors instead of panicking in library code"
- found_in: [go-i18n]
- example_bad: |
- func Parse(s string) *Node { panic("not implemented") }
- example_good: |
- func Parse(s string) (*Node, error) { return nil, fmt.Errorf("not implemented") }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-005
- title: "File deletion without path validation"
- severity: high
- languages: [go]
- tags: [correctness, safety]
- pattern: 'os\.Remove(All)?\('
- exclude_pattern: 'filepath\.Clean|ValidatePath|strings\.Contains.*\.\.'
- fix: "Validate path does not escape base directory before deletion"
- found_in: [go-io]
- example_bad: |
- os.RemoveAll(filepath.Join(base, userInput))
- example_good: |
- clean := filepath.Clean(filepath.Join(base, userInput))
- if !strings.HasPrefix(clean, base) { return ErrPathTraversal }
- os.RemoveAll(clean)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-006
- title: "Missing error return from API/network calls"
- severity: medium
- languages: [go]
- tags: [correctness, error-handling]
- pattern: 'resp,\s*_\s*:=.*\.(Get|Post|Do|Send)\('
- fix: "Check and handle HTTP/API errors"
- found_in: [go-forge, go-git]
- example_bad: |
- resp, _ := client.Get(url)
- example_good: |
- resp, err := client.Get(url)
- if err != nil { return fmt.Errorf("api call: %w", err) }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-007
- title: "Signal handler uses wrong type"
- severity: medium
- languages: [go]
- tags: [correctness, signals]
- pattern: 'syscall\.Signal\b'
- exclude_pattern: 'os\.Signal'
- fix: "Use os.Signal for portable signal handling"
- found_in: [go-process]
- example_bad: |
- func Handle(sig syscall.Signal) { ... }
- example_good: |
- func Handle(sig os.Signal) { ... }
- first_seen: "2026-03-09"
- detection: regex
-```
-
-**Step 3: Write `catalog/go-modernise.yaml`**
-
-```yaml
-- id: go-mod-001
- title: "Manual slice clone via append([]T(nil)...)"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'append\(\[\]\w+\(nil\),\s*\w+\.\.\.\)'
- fix: "Use slices.Clone() from Go 1.21+"
- found_in: [core/go]
- example_bad: |
- copy := append([]string(nil), original...)
- example_good: |
- copy := slices.Clone(original)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-mod-002
- title: "Manual sort of string/int slices"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'sort\.Strings\(|sort\.Ints\(|sort\.Slice\('
- exclude_pattern: 'sort\.SliceStable'
- fix: "Use slices.Sort() or slices.Sorted(iter) from Go 1.21+"
- found_in: [core/go]
- example_bad: |
- sort.Strings(names)
- example_good: |
- slices.Sort(names)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-mod-003
- title: "Manual reverse iteration loop"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'for\s+\w+\s*:=\s*len\(\w+\)\s*-\s*1'
- fix: "Use slices.Backward() from Go 1.23+"
- found_in: [core/go]
- example_bad: |
- for i := len(items) - 1; i >= 0; i-- { use(items[i]) }
- example_good: |
- for _, item := range slices.Backward(items) { use(item) }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-mod-004
- title: "WaitGroup Add+Done instead of Go()"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'wg\.Add\(1\)'
- fix: "Use sync.WaitGroup.Go() from Go 1.26"
- found_in: [core/go]
- example_bad: |
- wg.Add(1)
- go func() { defer wg.Done(); work() }()
- example_good: |
- wg.Go(func() { work() })
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-mod-005
- title: "Manual map key collection"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'for\s+\w+\s*:=\s*range\s+\w+\s*\{\s*\n\s*\w+\s*=\s*append'
- exclude_pattern: 'maps\.Keys'
- fix: "Use maps.Keys() or slices.Sorted(maps.Keys()) from Go 1.23+"
- found_in: [core/go]
- example_bad: |
- var keys []string
- for k := range m { keys = append(keys, k) }
- example_good: |
- keys := slices.Sorted(maps.Keys(m))
- first_seen: "2026-03-09"
- detection: regex
-```
-
-**Step 4: Run all tests to verify catalog loads correctly**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v`
-Expected: PASS (all tests, including TestCatalog_LoadDir which reads the catalog/ dir)
-
-**Step 5: Commit**
-
-```bash
-git add catalog/
-git commit -m "feat: seed catalog with 18 patterns from ecosystem sweep"
-```
-
----
-
-### Task 8: CLI binary with `cli.Main()`
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/cmd/core-lint/main.go`
-- Create: `/Users/snider/Code/core/lint/lint.go` (embed catalog + public API)
-
-**Step 1: Create the embed entry point**
-
-Create `/Users/snider/Code/core/lint/lint.go`:
-
-```go
-package lint
-
-import (
- "embed"
-
- lintpkg "forge.lthn.ai/core/lint/pkg/lint"
-)
-
-//go:embed catalog/*.yaml
-var catalogFS embed.FS
-
-// LoadEmbeddedCatalog loads the built-in catalog from embedded YAML files.
-func LoadEmbeddedCatalog() (*lintpkg.Catalog, error) {
- return lintpkg.LoadFS(catalogFS, "catalog")
-}
-```
-
-**Step 2: Create the CLI entry point**
-
-Create `/Users/snider/Code/core/lint/cmd/core-lint/main.go`:
-
-```go
-package main
-
-import (
- "fmt"
- "os"
-
- "forge.lthn.ai/core/cli/pkg/cli"
- lint "forge.lthn.ai/core/lint"
- lintpkg "forge.lthn.ai/core/lint/pkg/lint"
-)
-
-func main() {
- cli.Main(
- cli.WithCommands("lint", addLintCommands),
- )
-}
-
-func addLintCommands(root *cli.Command) {
- lintCmd := &cli.Command{
- Use: "lint",
- Short: "Pattern-based code checker",
- }
- root.AddCommand(lintCmd)
-
- // core-lint lint check [path...]
- lintCmd.AddCommand(cli.NewCommand(
- "check [path...]",
- "Run pattern checks against source files",
- "Scans files for known anti-patterns from the catalog",
- func(cmd *cli.Command, args []string) error {
- format, _ := cmd.Flags().GetString("format")
- lang, _ := cmd.Flags().GetString("lang")
- severity, _ := cmd.Flags().GetString("severity")
-
- cat, err := lint.LoadEmbeddedCatalog()
- if err != nil {
- return fmt.Errorf("load catalog: %w", err)
- }
-
- rules := cat.Rules
- if lang != "" {
- rules = cat.ForLanguage(lang)
- }
- if severity != "" {
- filtered := (&lintpkg.Catalog{Rules: rules}).AtSeverity(severity)
- rules = filtered
- }
-
- scanner, err := lintpkg.NewScanner(rules)
- if err != nil {
- return fmt.Errorf("create scanner: %w", err)
- }
-
- paths := args
- if len(paths) == 0 {
- paths = []string{"."}
- }
-
- var allFindings []lintpkg.Finding
- for _, p := range paths {
- findings, err := scanner.ScanDir(p)
- if err != nil {
- return fmt.Errorf("scan %s: %w", p, err)
- }
- allFindings = append(allFindings, findings...)
- }
-
- switch format {
- case "json":
- return lintpkg.WriteJSON(os.Stdout, allFindings)
- case "jsonl":
- return lintpkg.WriteJSONL(os.Stdout, allFindings)
- default:
- lintpkg.WriteText(os.Stdout, allFindings)
- }
-
- if len(allFindings) > 0 {
- s := lintpkg.Summarise(allFindings)
- fmt.Fprintf(os.Stderr, "\n%d findings", s.Total)
- for sev, count := range s.BySeverity {
- fmt.Fprintf(os.Stderr, " | %s: %d", sev, count)
- }
- fmt.Fprintln(os.Stderr)
- }
- return nil
- },
- ))
-
- // Add flags to check command
- checkCmd := lintCmd.Commands()[0]
- checkCmd.Flags().StringP("format", "f", "text", "Output format: text, json, jsonl")
- checkCmd.Flags().StringP("lang", "l", "", "Filter by language: go, php, ts, cpp")
- checkCmd.Flags().StringP("severity", "s", "", "Minimum severity: critical, high, medium, low, info")
-
- // core-lint lint catalog
- catalogCmd := &cli.Command{
- Use: "catalog",
- Short: "Browse the pattern catalog",
- }
- lintCmd.AddCommand(catalogCmd)
-
- // core-lint lint catalog list
- catalogCmd.AddCommand(cli.NewCommand(
- "list",
- "List available rules",
- "",
- func(cmd *cli.Command, args []string) error {
- lang, _ := cmd.Flags().GetString("lang")
-
- cat, err := lint.LoadEmbeddedCatalog()
- if err != nil {
- return err
- }
-
- rules := cat.Rules
- if lang != "" {
- rules = cat.ForLanguage(lang)
- }
-
- for _, r := range rules {
- fmt.Printf("%-12s [%s] %s\n", r.ID, r.Severity, r.Title)
- }
- fmt.Fprintf(os.Stderr, "\n%d rules\n", len(rules))
- return nil
- },
- ))
- catalogCmd.Commands()[0].Flags().StringP("lang", "l", "", "Filter by language")
-
- // core-lint lint catalog show
- catalogCmd.AddCommand(cli.NewCommand(
- "show [rule-id]",
- "Show details for a specific rule",
- "",
- func(cmd *cli.Command, args []string) error {
- if len(args) == 0 {
- return fmt.Errorf("rule ID required")
- }
- cat, err := lint.LoadEmbeddedCatalog()
- if err != nil {
- return err
- }
- r := cat.ByID(args[0])
- if r == nil {
- return fmt.Errorf("rule %s not found", args[0])
- }
- fmt.Printf("ID: %s\n", r.ID)
- fmt.Printf("Title: %s\n", r.Title)
- fmt.Printf("Severity: %s\n", r.Severity)
- fmt.Printf("Languages: %v\n", r.Languages)
- fmt.Printf("Tags: %v\n", r.Tags)
- fmt.Printf("Pattern: %s\n", r.Pattern)
- if r.ExcludePattern != "" {
- fmt.Printf("Exclude: %s\n", r.ExcludePattern)
- }
- fmt.Printf("Fix: %s\n", r.Fix)
- if r.ExampleBad != "" {
- fmt.Printf("\nBad:\n%s\n", r.ExampleBad)
- }
- if r.ExampleGood != "" {
- fmt.Printf("Good:\n%s\n", r.ExampleGood)
- }
- return nil
- },
- ))
-}
-```
-
-**Step 3: Add cli dependency**
-
-```bash
-cd ~/Code/core/lint
-go get forge.lthn.ai/core/cli
-go mod tidy
-```
-
-**Step 4: Build and smoke test**
-
-```bash
-cd ~/Code/core/lint
-go build -o ./bin/core-lint ./cmd/core-lint
-./bin/core-lint lint catalog list
-./bin/core-lint lint catalog show go-sec-001
-./bin/core-lint lint check --lang go --format json ~/Code/host-uk/core/pkg/core/
-```
-
-Expected: Binary builds, catalog lists 18 rules, show displays rule details, check scans files.
-
-**Step 5: Commit**
-
-```bash
-git add lint.go cmd/core-lint/main.go go.mod go.sum
-git commit -m "feat: add core-lint CLI with check, catalog list, catalog show"
-```
-
----
-
-### Task 9: Run all tests, push to forge
-
-**Step 1: Run full test suite**
-
-```bash
-cd ~/Code/core/lint
-go test -race -count=1 ./...
-```
-
-Expected: PASS with race detector
-
-**Step 2: Run go vet**
-
-```bash
-go vet ./...
-```
-
-Expected: No issues
-
-**Step 3: Build binary**
-
-```bash
-go build -trimpath -o ./bin/core-lint ./cmd/core-lint
-```
-
-**Step 4: Smoke test against a real repo**
-
-```bash
-./bin/core-lint lint check --lang go ~/Code/host-uk/core/pkg/core/
-./bin/core-lint lint check --lang go --severity high ~/Code/core/go-io/
-```
-
-Expected: Any findings are displayed (or no findings if the repos are already clean from our sweep)
-
-**Step 5: Update go.work**
-
-```bash
-# Add ./core/lint to ~/Code/go.work if not already there
-cd ~/Code && go work sync
-```
-
-**Step 6: Push to forge**
-
-```bash
-cd ~/Code/core/lint
-git push -u origin main
-```
-
-**Step 7: Tag initial release**
-
-```bash
-git tag v0.1.0
-git push origin v0.1.0
-```
diff --git a/.core/reference/docs/plans/2026-03-12-altum-update-checker-design.md b/.core/reference/docs/plans/2026-03-12-altum-update-checker-design.md
deleted file mode 100644
index a0bbe0dc..00000000
--- a/.core/reference/docs/plans/2026-03-12-altum-update-checker-design.md
+++ /dev/null
@@ -1,160 +0,0 @@
-# AltumCode Update Checker — Design
-
-> **Note:** Layer 1 (version detection via PHP artisan) is implemented and documented at `docs/docs/php/packages/uptelligence.md`. Layer 2 (browser-automated downloads via Claude Code skill) is NOT yet implemented.
-
-## Problem
-
-Host UK runs 4 AltumCode SaaS products and 13 plugins across two marketplaces (CodeCanyon + LemonSqueezy). Checking for updates and downloading them is a manual process: ~50 clicks across two marketplace UIs, moving 16+ zip files, extracting to the right directories. This eats a morning of momentum every update cycle.
-
-## Solution
-
-Two-layer system: lightweight version detection (PHP artisan command) + browser-automated download (Claude Code skill).
-
-## Architecture
-
-```
-Layer 1: Detection (core/php-uptelligence)
- artisan uptelligence:check-updates
- 5 HTTP GETs, no auth, schedulable
- Compares remote vs deployed versions
-
-Layer 2: Download (Claude Code skill)
- Playwright → LemonSqueezy (16 items)
- Claude in Chrome → CodeCanyon (2 items)
- Downloads zips to staging folder
- Extracts to saas/services/{product}/package/
-
-Layer 3: Deploy (existing — manual)
- docker build → scp → deploy_saas.yml
- Human in the loop
-```
-
-## Layer 1: Version Detection
-
-### Public Endpoints (no auth required)
-
-| Endpoint | Returns |
-|----------|---------|
-| `GET https://66analytics.com/info.php` | `{"latest_release_version": "66.0.0", "latest_release_version_code": 6600}` |
-| `GET https://66biolinks.com/info.php` | Same format |
-| `GET https://66pusher.com/info.php` | Same format |
-| `GET https://66socialproof.com/info.php` | Same format |
-| `GET https://dev.altumcode.com/plugins-versions` | `{"affiliate": {"version": "2.0.1"}, "ultimate-blocks": {"version": "9.1.0"}, ...}` |
-
-### Deployed Version Sources
-
-- **Product version**: `PRODUCT_CODE` constant in deployed source `config.php`
-- **Plugin versions**: `version` field in each plugin's `config.php` or `config.json`
-
-### Artisan Command
-
-`php artisan uptelligence:check-updates`
-
-Output:
-```
-Product Deployed Latest Status
-──────────────────────────────────────────────
-66analytics 65.0.0 66.0.0 UPDATE AVAILABLE
-66biolinks 65.0.0 66.0.0 UPDATE AVAILABLE
-66pusher 65.0.0 65.0.0 ✓ current
-66socialproof 65.0.0 66.0.0 UPDATE AVAILABLE
-
-Plugin Deployed Latest Status
-──────────────────────────────────────────────
-affiliate 2.0.0 2.0.1 UPDATE AVAILABLE
-ultimate-blocks 9.1.0 9.1.0 ✓ current
-...
-```
-
-Lives in `core/php-uptelligence` as a scheduled check or on-demand command.
-
-## Layer 2: Browser-Automated Download
-
-### Claude Code Skill: `/update-altum`
-
-Workflow:
-1. Run version check (Layer 1) — show what needs updating
-2. Ask for confirmation before downloading
-3. Download from both marketplaces
-4. Extract to staging directories
-5. Report what changed
-
-### Marketplace Access
-
-**LemonSqueezy (Playwright)**
-- Auth: Magic link email to `snider@lt.hn` — user taps on phone
-- Flow per item: Navigate to order detail → click "Download" button
-- 16 items across 2 pages of orders
-- Session persists for the skill invocation
-
-**CodeCanyon (Claude in Chrome)**
-- Auth: Saved browser session cookies (user `snidered`)
-- Flow per item: Click "Download" dropdown → "All files & documentation"
-- 2 items on downloads page
-
-### Product-to-Marketplace Mapping
-
-| Product | CodeCanyon | LemonSqueezy |
-|---------|-----------|--------------|
-| 66biolinks | Regular licence | Extended licence (66biolinks custom, $359.28) |
-| 66socialproof | Regular licence | — |
-| 66analytics | — | Regular licence |
-| 66pusher | — | Regular licence |
-
-### Plugin Inventory (all LemonSqueezy)
-
-| Plugin | Price | Applies To |
-|--------|-------|------------|
-| Pro Notifications | $58.80 | 66socialproof |
-| Teams Plugin | $58.80 | All products |
-| Push Notifications Plugin | $46.80 | All products |
-| Ultimate Blocks | $32.40 | 66biolinks |
-| Pro Blocks | $32.40 | 66biolinks |
-| Payment Blocks | $32.40 | 66biolinks |
-| Affiliate Plugin | $32.40 | All products |
-| PWA Plugin | $25.20 | All products |
-| Image Optimizer Plugin | $19.20 | All products |
-| Email Shield Plugin | FREE | All products |
-| Dynamic OG images plugin | FREE | 66biolinks |
-| Offload & CDN Plugin | FREE | All products (gift from Altum) |
-
-### Staging & Extraction
-
-- Download to: `~/Code/lthn/saas/updates/YYYY-MM-DD/`
-- Products extract to: `~/Code/lthn/saas/services/{product}/package/product/`
-- Plugins extract to: `~/Code/lthn/saas/services/{product}/package/product/plugins/{plugin_id}/`
-
-## LemonSqueezy Order UUIDs
-
-Stable order URLs for direct navigation:
-
-| Product | Order URL |
-|---------|-----------|
-| 66analytics | `/my-orders/2972471f-abac-4165-b78d-541b176de180` |
-
-(Remaining UUIDs to be captured on first full run of the skill.)
-
-## Out of Scope
-
-- No auto-deploy to production (human runs `deploy_saas.yml`)
-- No licence key handling or financial transactions
-- No AltumCode Club membership management
-- No Blesta updates (different vendor)
-- No update SQL migration execution (handled by AltumCode's own update scripts)
-
-## Key Technical Details
-
-- AltumCode products use Unirest HTTP client for API calls
-- Product `info.php` endpoints are public, no rate limiting observed
-- Plugin versions endpoint (`dev.altumcode.com`) is also public
-- Production Docker images have `/install/` and `/update/` directories stripped
-- Updates require full Docker image rebuild and redeployment via Ansible
-- CodeCanyon download URLs contain stable purchase UUIDs
-- LemonSqueezy uses magic link auth (no password, email-based)
-- Playwright can access LemonSqueezy; Claude in Chrome cannot (payment platform safety block)
-
-## Workflow Summary
-
-**Before**: Get email from AltumCode → log into 2 marketplaces → click through 18 products/plugins → download 16+ zips → extract to right directories → rebuild Docker images → deploy. Half a morning.
-
-**After**: Run `artisan uptelligence:check-updates` → see what's behind → invoke `/update-altum` → tap magic link on phone → go make coffee → come back to staged files → `deploy_saas.yml`. 10 minutes of human time.
diff --git a/.core/reference/docs/plans/2026-03-12-altum-update-checker-plan.md b/.core/reference/docs/plans/2026-03-12-altum-update-checker-plan.md
deleted file mode 100644
index 37ecb28d..00000000
--- a/.core/reference/docs/plans/2026-03-12-altum-update-checker-plan.md
+++ /dev/null
@@ -1,799 +0,0 @@
-# AltumCode Update Checker Implementation Plan
-
-> **Note:** Layer 1 (Tasks 1-2, 4: version checking + seeder + sync command) is implemented and documented at `docs/docs/php/packages/uptelligence.md`. Task 3 (Claude Code browser skill for Layer 2 downloads) is NOT yet implemented.
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Add AltumCode product + plugin version checking to uptelligence, and create a Claude Code skill for browser-automated downloads from LemonSqueezy and CodeCanyon.
-
-**Architecture:** Extend the existing `VendorUpdateCheckerService` to handle `PLATFORM_ALTUM` vendors via 5 public HTTP endpoints. Seed the vendors table with all 4 products and 13 plugins. Create a Claude Code plugin skill that uses Playwright (LemonSqueezy) and Chrome (CodeCanyon) to download updates.
-
-**Tech Stack:** PHP 8.4, Laravel, Pest, Claude Code plugins (Playwright MCP + Chrome MCP)
-
----
-
-### Task 1: Add AltumCode check to VendorUpdateCheckerService
-
-**Files:**
-- Modify: `/Users/snider/Code/core/php-uptelligence/Services/VendorUpdateCheckerService.php`
-- Test: `/Users/snider/Code/core/php-uptelligence/tests/Unit/AltumCodeCheckerTest.php`
-
-**Step 1: Write the failing test**
-
-Create `/Users/snider/Code/core/php-uptelligence/tests/Unit/AltumCodeCheckerTest.php`:
-
-```php
-service = app(VendorUpdateCheckerService::class);
-});
-
-it('checks altum product version via info.php', function () {
- Http::fake([
- 'https://66analytics.com/info.php' => Http::response([
- 'latest_release_version' => '66.0.0',
- 'latest_release_version_code' => 6600,
- ]),
- ]);
-
- $vendor = Vendor::factory()->create([
- 'slug' => '66analytics',
- 'name' => '66analytics',
- 'source_type' => Vendor::SOURCE_LICENSED,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'current_version' => '65.0.0',
- 'is_active' => true,
- ]);
-
- $result = $this->service->checkVendor($vendor);
-
- expect($result['status'])->toBe('success')
- ->and($result['current'])->toBe('65.0.0')
- ->and($result['latest'])->toBe('66.0.0')
- ->and($result['has_update'])->toBeTrue();
-});
-
-it('reports no update when altum product is current', function () {
- Http::fake([
- 'https://66analytics.com/info.php' => Http::response([
- 'latest_release_version' => '65.0.0',
- 'latest_release_version_code' => 6500,
- ]),
- ]);
-
- $vendor = Vendor::factory()->create([
- 'slug' => '66analytics',
- 'name' => '66analytics',
- 'source_type' => Vendor::SOURCE_LICENSED,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'current_version' => '65.0.0',
- 'is_active' => true,
- ]);
-
- $result = $this->service->checkVendor($vendor);
-
- expect($result['has_update'])->toBeFalse();
-});
-
-it('checks altum plugin versions via plugins-versions endpoint', function () {
- Http::fake([
- 'https://dev.altumcode.com/plugins-versions' => Http::response([
- 'affiliate' => ['version' => '2.0.1'],
- 'teams' => ['version' => '3.0.0'],
- ]),
- ]);
-
- $vendor = Vendor::factory()->create([
- 'slug' => 'altum-plugin-affiliate',
- 'name' => 'Affiliate Plugin',
- 'source_type' => Vendor::SOURCE_PLUGIN,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'current_version' => '2.0.0',
- 'is_active' => true,
- ]);
-
- $result = $this->service->checkVendor($vendor);
-
- expect($result['status'])->toBe('success')
- ->and($result['latest'])->toBe('2.0.1')
- ->and($result['has_update'])->toBeTrue();
-});
-
-it('handles altum info.php timeout gracefully', function () {
- Http::fake([
- 'https://66analytics.com/info.php' => Http::response('', 500),
- ]);
-
- $vendor = Vendor::factory()->create([
- 'slug' => '66analytics',
- 'name' => '66analytics',
- 'source_type' => Vendor::SOURCE_LICENSED,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'current_version' => '65.0.0',
- 'is_active' => true,
- ]);
-
- $result = $this->service->checkVendor($vendor);
-
- expect($result['status'])->toBe('error')
- ->and($result['has_update'])->toBeFalse();
-});
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=AltumCodeChecker`
-Expected: FAIL — altum vendors still hit `skipCheck()`
-
-**Step 3: Write minimal implementation**
-
-In `/Users/snider/Code/core/php-uptelligence/Services/VendorUpdateCheckerService.php`, modify `checkVendor()` to route altum vendors:
-
-```php
-public function checkVendor(Vendor $vendor): array
-{
- $result = match (true) {
- $this->isAltumPlatform($vendor) && $vendor->isLicensed() => $this->checkAltumProduct($vendor),
- $this->isAltumPlatform($vendor) && $vendor->isPlugin() => $this->checkAltumPlugin($vendor),
- $vendor->isOss() && $this->isGitHubUrl($vendor->git_repo_url) => $this->checkGitHub($vendor),
- $vendor->isOss() && $this->isGiteaUrl($vendor->git_repo_url) => $this->checkGitea($vendor),
- default => $this->skipCheck($vendor),
- };
-
- // ... rest unchanged
-}
-```
-
-Add the three new methods:
-
-```php
-/**
- * Check if vendor is on the AltumCode platform.
- */
-protected function isAltumPlatform(Vendor $vendor): bool
-{
- return $vendor->plugin_platform === Vendor::PLATFORM_ALTUM;
-}
-
-/**
- * AltumCode product info endpoint mapping.
- */
-protected function getAltumProductInfoUrl(Vendor $vendor): ?string
-{
- $urls = [
- '66analytics' => 'https://66analytics.com/info.php',
- '66biolinks' => 'https://66biolinks.com/info.php',
- '66pusher' => 'https://66pusher.com/info.php',
- '66socialproof' => 'https://66socialproof.com/info.php',
- ];
-
- return $urls[$vendor->slug] ?? null;
-}
-
-/**
- * Check an AltumCode product for updates via its info.php endpoint.
- */
-protected function checkAltumProduct(Vendor $vendor): array
-{
- $url = $this->getAltumProductInfoUrl($vendor);
- if (! $url) {
- return $this->errorResult("No info.php URL mapped for {$vendor->slug}");
- }
-
- try {
- $response = Http::timeout(5)->get($url);
-
- if (! $response->successful()) {
- return $this->errorResult("AltumCode info.php returned {$response->status()}");
- }
-
- $data = $response->json();
- $latestVersion = $data['latest_release_version'] ?? null;
-
- if (! $latestVersion) {
- return $this->errorResult('No version in info.php response');
- }
-
- return $this->buildResult(
- vendor: $vendor,
- latestVersion: $this->normaliseVersion($latestVersion),
- releaseInfo: [
- 'version_code' => $data['latest_release_version_code'] ?? null,
- 'source' => $url,
- ]
- );
- } catch (\Exception $e) {
- return $this->errorResult("AltumCode check failed: {$e->getMessage()}");
- }
-}
-
-/**
- * Check an AltumCode plugin for updates via the central plugins-versions endpoint.
- */
-protected function checkAltumPlugin(Vendor $vendor): array
-{
- try {
- $allPlugins = $this->getAltumPluginVersions();
-
- if ($allPlugins === null) {
- return $this->errorResult('Failed to fetch AltumCode plugin versions');
- }
-
- // Extract the plugin_id from the vendor slug (strip 'altum-plugin-' prefix)
- $pluginId = str_replace('altum-plugin-', '', $vendor->slug);
-
- if (! isset($allPlugins[$pluginId])) {
- return $this->errorResult("Plugin '{$pluginId}' not found in AltumCode registry");
- }
-
- $latestVersion = $allPlugins[$pluginId]['version'] ?? null;
-
- return $this->buildResult(
- vendor: $vendor,
- latestVersion: $this->normaliseVersion($latestVersion),
- releaseInfo: ['source' => 'dev.altumcode.com/plugins-versions']
- );
- } catch (\Exception $e) {
- return $this->errorResult("AltumCode plugin check failed: {$e->getMessage()}");
- }
-}
-
-/**
- * Fetch all AltumCode plugin versions (cached for 1 hour within a check run).
- */
-protected ?array $altumPluginVersionsCache = null;
-
-protected function getAltumPluginVersions(): ?array
-{
- if ($this->altumPluginVersionsCache !== null) {
- return $this->altumPluginVersionsCache;
- }
-
- $response = Http::timeout(5)->get('https://dev.altumcode.com/plugins-versions');
-
- if (! $response->successful()) {
- return null;
- }
-
- $this->altumPluginVersionsCache = $response->json();
-
- return $this->altumPluginVersionsCache;
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=AltumCodeChecker`
-Expected: PASS (4 tests)
-
-**Step 5: Commit**
-
-```bash
-cd /Users/snider/Code/core/php-uptelligence
-git add Services/VendorUpdateCheckerService.php tests/Unit/AltumCodeCheckerTest.php
-git commit -m "feat: add AltumCode product + plugin version checking
-
-Extends VendorUpdateCheckerService to check AltumCode products via
-their info.php endpoints and plugins via dev.altumcode.com/plugins-versions.
-No auth required — all endpoints are public.
-
-Co-Authored-By: Virgil "
-```
-
----
-
-### Task 2: Seed AltumCode vendors
-
-**Files:**
-- Create: `/Users/snider/Code/core/php-uptelligence/database/seeders/AltumCodeVendorSeeder.php`
-- Test: `/Users/snider/Code/core/php-uptelligence/tests/Unit/AltumCodeVendorSeederTest.php`
-
-**Step 1: Write the failing test**
-
-Create `/Users/snider/Code/core/php-uptelligence/tests/Unit/AltumCodeVendorSeederTest.php`:
-
-```php
-artisan('db:seed', ['--class' => 'Core\\Mod\\Uptelligence\\Database\\Seeders\\AltumCodeVendorSeeder']);
-
- expect(Vendor::where('source_type', Vendor::SOURCE_LICENSED)
- ->where('plugin_platform', Vendor::PLATFORM_ALTUM)
- ->count()
- )->toBe(4);
-});
-
-it('seeds 13 altum plugins', function () {
- $this->artisan('db:seed', ['--class' => 'Core\\Mod\\Uptelligence\\Database\\Seeders\\AltumCodeVendorSeeder']);
-
- expect(Vendor::where('source_type', Vendor::SOURCE_PLUGIN)
- ->where('plugin_platform', Vendor::PLATFORM_ALTUM)
- ->count()
- )->toBe(13);
-});
-
-it('is idempotent', function () {
- $this->artisan('db:seed', ['--class' => 'Core\\Mod\\Uptelligence\\Database\\Seeders\\AltumCodeVendorSeeder']);
- $this->artisan('db:seed', ['--class' => 'Core\\Mod\\Uptelligence\\Database\\Seeders\\AltumCodeVendorSeeder']);
-
- expect(Vendor::where('plugin_platform', Vendor::PLATFORM_ALTUM)->count())->toBe(17);
-});
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=AltumCodeVendorSeeder`
-Expected: FAIL — seeder class not found
-
-**Step 3: Write minimal implementation**
-
-Create `/Users/snider/Code/core/php-uptelligence/database/seeders/AltumCodeVendorSeeder.php`:
-
-```php
- '66analytics', 'name' => '66analytics', 'vendor_name' => 'AltumCode', 'current_version' => '65.0.0'],
- ['slug' => '66biolinks', 'name' => '66biolinks', 'vendor_name' => 'AltumCode', 'current_version' => '65.0.0'],
- ['slug' => '66pusher', 'name' => '66pusher', 'vendor_name' => 'AltumCode', 'current_version' => '65.0.0'],
- ['slug' => '66socialproof', 'name' => '66socialproof', 'vendor_name' => 'AltumCode', 'current_version' => '65.0.0'],
- ];
-
- foreach ($products as $product) {
- Vendor::updateOrCreate(
- ['slug' => $product['slug']],
- [
- ...$product,
- 'source_type' => Vendor::SOURCE_LICENSED,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'is_active' => true,
- ]
- );
- }
-
- $plugins = [
- ['slug' => 'altum-plugin-affiliate', 'name' => 'Affiliate Plugin', 'current_version' => '2.0.0'],
- ['slug' => 'altum-plugin-push-notifications', 'name' => 'Push Notifications Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-teams', 'name' => 'Teams Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-pwa', 'name' => 'PWA Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-image-optimizer', 'name' => 'Image Optimizer Plugin', 'current_version' => '3.1.0'],
- ['slug' => 'altum-plugin-email-shield', 'name' => 'Email Shield Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-dynamic-og-images', 'name' => 'Dynamic OG Images Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-offload', 'name' => 'Offload & CDN Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-payment-blocks', 'name' => 'Payment Blocks Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-ultimate-blocks', 'name' => 'Ultimate Blocks Plugin', 'current_version' => '9.1.0'],
- ['slug' => 'altum-plugin-pro-blocks', 'name' => 'Pro Blocks Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-pro-notifications', 'name' => 'Pro Notifications Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-aix', 'name' => 'AIX Plugin', 'current_version' => '1.0.0'],
- ];
-
- foreach ($plugins as $plugin) {
- Vendor::updateOrCreate(
- ['slug' => $plugin['slug']],
- [
- ...$plugin,
- 'vendor_name' => 'AltumCode',
- 'source_type' => Vendor::SOURCE_PLUGIN,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'is_active' => true,
- ]
- );
- }
- }
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=AltumCodeVendorSeeder`
-Expected: PASS (3 tests)
-
-**Step 5: Commit**
-
-```bash
-cd /Users/snider/Code/core/php-uptelligence
-git add database/seeders/AltumCodeVendorSeeder.php tests/Unit/AltumCodeVendorSeederTest.php
-git commit -m "feat: seed AltumCode vendors — 4 products + 13 plugins
-
-Idempotent seeder using updateOrCreate. Products are SOURCE_LICENSED,
-plugins are SOURCE_PLUGIN, all PLATFORM_ALTUM. Version numbers will
-need updating to match actual deployed versions.
-
-Co-Authored-By: Virgil "
-```
-
----
-
-### Task 3: Create Claude Code plugin skill for downloads
-
-**Files:**
-- Create: `/Users/snider/.claude/plugins/altum-updater/plugin.json`
-- Create: `/Users/snider/.claude/plugins/altum-updater/skills/update-altum.md`
-
-**Step 1: Create plugin manifest**
-
-Create `/Users/snider/.claude/plugins/altum-updater/plugin.json`:
-
-```json
-{
- "name": "altum-updater",
- "description": "Download AltumCode product and plugin updates from LemonSqueezy and CodeCanyon",
- "version": "0.1.0",
- "skills": [
- {
- "name": "update-altum",
- "path": "skills/update-altum.md",
- "description": "Download AltumCode product and plugin updates from marketplaces. Use when the user mentions updating AltumCode products, downloading from LemonSqueezy or CodeCanyon, or running the update checker."
- }
- ]
-}
-```
-
-**Step 2: Create skill file**
-
-Create `/Users/snider/.claude/plugins/altum-updater/skills/update-altum.md`:
-
-```markdown
----
-name: update-altum
-description: Download AltumCode product and plugin updates from LemonSqueezy and CodeCanyon
----
-
-# AltumCode Update Downloader
-
-## Overview
-
-Downloads updated AltumCode products and plugins from two marketplaces:
-- **LemonSqueezy** (Playwright): 66analytics, 66pusher, 66biolinks (extended), 13 plugins
-- **CodeCanyon** (Claude in Chrome): 66biolinks (regular), 66socialproof
-
-## Pre-flight
-
-1. Run `php artisan uptelligence:check-updates --vendor=66analytics` (or check all) to see what needs updating
-2. Show the user the version comparison table
-3. Ask which products/plugins to download
-
-## LemonSqueezy Download Flow (Playwright)
-
-LemonSqueezy uses magic link auth. The user will need to tap the link on their phone.
-
-1. Navigate to `https://app.lemonsqueezy.com/my-orders`
-2. If on login page, fill email `snider@lt.hn` and click Sign In
-3. Tell user: "Magic link sent — tap the link on your phone"
-4. Wait for redirect to orders page
-5. For each product/plugin that needs updating:
- a. Click the product link on the orders page (paginated — 10 per page, 2 pages)
- b. In the order detail, find the "Download" button under "Files"
- c. Click Download — file saves to default downloads folder
-6. Move downloaded zips to staging: `~/Code/lthn/saas/updates/YYYY-MM-DD/`
-
-### LemonSqueezy Product Names (as shown on orders page)
-
-| Our Name | LemonSqueezy Order Name |
-|----------|------------------------|
-| 66analytics | "66analytics - Regular License" |
-| 66pusher | "66pusher - Regular License" |
-| 66biolinks (extended) | "66biolinks custom" |
-| Affiliate Plugin | "Affiliate Plugin" |
-| Push Notifications Plugin | "Push Notifications Plugin" |
-| Teams Plugin | "Teams Plugin" |
-| PWA Plugin | "PWA Plugin" |
-| Image Optimizer Plugin | "Image Optimizer Plugin" |
-| Email Shield Plugin | "Email Shield Plugin" |
-| Dynamic OG Images | "Dynamic OG images plugin" |
-| Offload & CDN | "Offload & CDN Plugin" |
-| Payment Blocks | "Payment Blocks - 66biolinks plugin" |
-| Ultimate Blocks | "Ultimate Blocks - 66biolinks plugin" |
-| Pro Blocks | "Pro Blocks - 66biolinks plugin" |
-| Pro Notifications | "Pro Notifications - 66socialproof plugin" |
-| AltumCode Club | "The AltumCode Club" |
-
-## CodeCanyon Download Flow (Claude in Chrome)
-
-CodeCanyon uses saved browser session cookies (user: snidered).
-
-1. Navigate to `https://codecanyon.net/downloads`
-2. Dismiss cookie banner if present (click "Reject all")
-3. For 66socialproof:
- a. Find "66socialproof" Download button
- b. Click the dropdown arrow
- c. Click "All files & documentation"
-4. For 66biolinks:
- a. Find "66biolinks" Download button (scroll down)
- b. Click the dropdown arrow
- c. Click "All files & documentation"
-5. Move downloaded zips to staging
-
-### CodeCanyon Download URLs (stable)
-
-- 66socialproof: `/user/snidered/download_purchase/8d8ef4c1-5add-4eba-9a89-4261a9c87e0b`
-- 66biolinks: `/user/snidered/download_purchase/38d79f4e-19cd-480a-b068-4332629b5206`
-
-## Post-Download
-
-1. List all zips in staging folder
-2. For each product zip:
- - Extract to `~/Code/lthn/saas/services/{product}/package/product/`
-3. For each plugin zip:
- - Extract to the correct product's `plugins/{plugin_id}/` directory
- - Note: Some plugins apply to multiple products (affiliate, teams, etc.)
-4. Show summary of what was updated
-5. Remind user: "Files staged. Run `deploy_saas.yml` when ready to deploy."
-
-## Important Notes
-
-- Never make purchases or enter financial information
-- LemonSqueezy session expires — if Playwright gets a login page mid-flow, re-trigger magic link
-- CodeCanyon session depends on Chrome cookies — if logged out, tell user to log in manually
-- The AltumCode Club subscription is NOT a downloadable product — skip it
-- Plugin `aix` may not appear on LemonSqueezy (bundled with products) — skip if not found
-```
-
-**Step 3: Verify plugin loads**
-
-Run: `claude` in a new terminal, then type `/update-altum` to verify the skill is discovered.
-
-**Step 4: Commit**
-
-```bash
-cd /Users/snider/.claude/plugins/altum-updater
-git init
-git add plugin.json skills/update-altum.md
-git commit -m "feat: altum-updater Claude Code plugin — marketplace download skill
-
-Playwright for LemonSqueezy, Chrome for CodeCanyon. Includes full
-product/plugin mapping and download flow documentation.
-
-Co-Authored-By: Virgil "
-```
-
----
-
-### Task 4: Sync deployed plugin versions from source
-
-**Files:**
-- Create: `/Users/snider/Code/core/php-uptelligence/Console/SyncAltumVersionsCommand.php`
-- Modify: `/Users/snider/Code/core/php-uptelligence/Boot.php` (register command)
-- Test: `/Users/snider/Code/core/php-uptelligence/tests/Unit/SyncAltumVersionsCommandTest.php`
-
-**Step 1: Write the failing test**
-
-```php
-artisan('uptelligence:sync-altum-versions', ['--dry-run' => true])
- ->assertExitCode(0);
-});
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=SyncAltumVersions`
-Expected: FAIL — command not found
-
-**Step 3: Write minimal implementation**
-
-Create `/Users/snider/Code/core/php-uptelligence/Console/SyncAltumVersionsCommand.php`:
-
-```php
- '66analytics/package/product',
- '66biolinks' => '66biolinks/package/product',
- '66pusher' => '66pusher/package/product',
- '66socialproof' => '66socialproof/package/product',
- ];
-
- public function handle(): int
- {
- $basePath = $this->option('path')
- ?? env('SAAS_SERVICES_PATH', base_path('../lthn/saas/services'));
- $dryRun = $this->option('dry-run');
-
- $this->info('Syncing AltumCode versions from source...');
- $this->newLine();
-
- $updates = [];
-
- // Sync product versions
- foreach ($this->productPaths as $slug => $relativePath) {
- $productPath = rtrim($basePath, '/') . '/' . $relativePath;
- $version = $this->readProductVersion($productPath);
-
- if ($version) {
- $updates[] = $this->syncVendorVersion($slug, $version, $dryRun);
- } else {
- $this->warn(" Could not read version for {$slug} at {$productPath}");
- }
- }
-
- // Sync plugin versions — read from biolinks as canonical source
- $biolinkPluginsPath = rtrim($basePath, '/') . '/66biolinks/package/product/plugins';
- if (is_dir($biolinkPluginsPath)) {
- foreach (glob($biolinkPluginsPath . '/*/config.php') as $configFile) {
- $pluginId = basename(dirname($configFile));
- $version = $this->readPluginVersion($configFile);
-
- if ($version) {
- $slug = "altum-plugin-{$pluginId}";
- $updates[] = $this->syncVendorVersion($slug, $version, $dryRun);
- }
- }
- }
-
- // Output table
- $this->table(
- ['Vendor', 'Old Version', 'New Version', 'Status'],
- array_filter($updates)
- );
-
- if ($dryRun) {
- $this->warn('Dry run — no changes written.');
- }
-
- return self::SUCCESS;
- }
-
- protected function readProductVersion(string $productPath): ?string
- {
- // Read version from app/init.php or similar — look for PRODUCT_VERSION define
- $initFile = $productPath . '/app/init.php';
- if (! file_exists($initFile)) {
- return null;
- }
-
- $content = file_get_contents($initFile);
- if (preg_match("/define\('PRODUCT_VERSION',\s*'([^']+)'\)/", $content, $matches)) {
- return $matches[1];
- }
-
- return null;
- }
-
- protected function readPluginVersion(string $configFile): ?string
- {
- if (! file_exists($configFile)) {
- return null;
- }
-
- $content = file_get_contents($configFile);
-
- // PHP config format: 'version' => '2.0.0'
- if (preg_match("/'version'\s*=>\s*'([^']+)'/", $content, $matches)) {
- return $matches[1];
- }
-
- return null;
- }
-
- protected function syncVendorVersion(string $slug, string $version, bool $dryRun): ?array
- {
- $vendor = Vendor::where('slug', $slug)->first();
- if (! $vendor) {
- return [$slug, '(not in DB)', $version, 'SKIPPED'];
- }
-
- $oldVersion = $vendor->current_version;
- if ($oldVersion === $version) {
- return [$slug, $oldVersion, $version, 'current'];
- }
-
- if (! $dryRun) {
- $vendor->update(['current_version' => $version]);
- }
-
- return [$slug, $oldVersion ?? '(none)', $version, $dryRun ? 'WOULD UPDATE' : 'UPDATED'];
- }
-}
-```
-
-Register in Boot.php — add to `onConsole()`:
-
-```php
-$event->command(Console\SyncAltumVersionsCommand::class);
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=SyncAltumVersions`
-Expected: PASS
-
-**Step 5: Commit**
-
-```bash
-cd /Users/snider/Code/core/php-uptelligence
-git add Console/SyncAltumVersionsCommand.php Boot.php tests/Unit/SyncAltumVersionsCommandTest.php
-git commit -m "feat: sync deployed AltumCode versions from source files
-
-Reads PRODUCT_VERSION from product init.php and plugin versions from
-config.php files. Updates uptelligence_vendors table so check-updates
-knows what's actually deployed.
-
-Co-Authored-By: Virgil "
-```
-
----
-
-### Task 5: End-to-end verification
-
-**Step 1: Seed vendors on local dev**
-
-```bash
-cd /Users/snider/Code/lab/host.uk.com
-php artisan db:seed --class="Core\Mod\Uptelligence\Database\Seeders\AltumCodeVendorSeeder"
-```
-
-**Step 2: Sync actual deployed versions**
-
-```bash
-php artisan uptelligence:sync-altum-versions --path=/Users/snider/Code/lthn/saas/services
-```
-
-**Step 3: Run the update check**
-
-```bash
-php artisan uptelligence:check-updates
-```
-
-Expected: Table showing current vs latest versions for all 17 AltumCode vendors.
-
-**Step 4: Test the skill**
-
-Open a new Claude Code session and run `/update-altum` to verify the skill loads and shows the workflow.
-
-**Step 5: Commit any fixes**
-
-```bash
-git add -A && git commit -m "fix: adjustments from end-to-end testing"
-```
diff --git a/.core/reference/docs/primitives.md b/.core/reference/docs/primitives.md
deleted file mode 100644
index 43701f2d..00000000
--- a/.core/reference/docs/primitives.md
+++ /dev/null
@@ -1,169 +0,0 @@
----
-title: Core Primitives
-description: The repeated shapes that make CoreGO easy to navigate.
----
-
-# Core Primitives
-
-CoreGO is easiest to use when you read it as a small vocabulary repeated everywhere. Most of the framework is built from the same handful of types.
-
-## Primitive Map
-
-| Type | Used For |
-|------|----------|
-| `Options` | Input values and lightweight metadata |
-| `Result` | Output values and success state |
-| `Service` | Lifecycle-managed components |
-| `Message` | Broadcast events |
-| `Query` | Request-response lookups |
-| `Task` | Side-effecting work items |
-
-## `Option` and `Options`
-
-`Option` is one key-value pair. `Options` is an ordered slice of them.
-
-```go
-opts := core.Options{
- {Key: "name", Value: "brain"},
- {Key: "path", Value: "prompts"},
- {Key: "debug", Value: true},
-}
-```
-
-Use the helpers to read values:
-
-```go
-name := opts.String("name")
-path := opts.String("path")
-debug := opts.Bool("debug")
-hasPath := opts.Has("path")
-raw := opts.Get("name")
-```
-
-### Important Details
-
-- `Get` returns the first matching key.
-- `String`, `Int`, and `Bool` do not convert between types.
-- Missing keys return zero values.
-- CLI flags with values are stored as strings, so `--port=8080` should be read with `opts.String("port")`, not `opts.Int("port")`.
-
-## `Result`
-
-`Result` is the universal return shape.
-
-```go
-r := core.Result{Value: "ready", OK: true}
-
-if r.OK {
- fmt.Println(r.Value)
-}
-```
-
-It has two jobs:
-
-- carry a value when work succeeds
-- carry either an error or an empty state when work does not succeed
-
-### `Result.Result(...)`
-
-The `Result()` method adapts plain Go values and `(value, error)` pairs into a `core.Result`.
-
-```go
-r1 := core.Result{}.Result("hello")
-r2 := core.Result{}.Result(file, err)
-```
-
-This is how several built-in helpers bridge standard-library calls.
-
-## `Service`
-
-`Service` is the managed lifecycle DTO stored in the registry.
-
-```go
-svc := core.Service{
- Name: "cache",
- Options: core.Options{
- {Key: "backend", Value: "memory"},
- },
- OnStart: func() core.Result {
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- return core.Result{OK: true}
- },
- OnReload: func() core.Result {
- return core.Result{OK: true}
- },
-}
-```
-
-### Important Details
-
-- `OnStart` and `OnStop` are used by the framework lifecycle.
-- `OnReload` is stored on the service DTO, but CoreGO does not currently call it automatically.
-- The registry stores `*core.Service`, not arbitrary typed service instances.
-
-## `Message`, `Query`, and `Task`
-
-These are simple aliases to `any`.
-
-```go
-type Message any
-type Query any
-type Task any
-```
-
-That means your own structs become the protocol:
-
-```go
-type deployStarted struct {
- Environment string
-}
-
-type workspaceCountQuery struct{}
-
-type syncRepositoryTask struct {
- Name string
-}
-```
-
-## `TaskWithIdentifier`
-
-Long-running tasks can opt into task identifiers.
-
-```go
-type indexedTask struct {
- ID string
-}
-
-func (t *indexedTask) SetTaskIdentifier(id string) { t.ID = id }
-func (t *indexedTask) GetTaskIdentifier() string { return t.ID }
-```
-
-If a task implements `TaskWithIdentifier`, `PerformAsync` injects the generated `task-N` identifier before dispatch.
-
-## `ServiceRuntime[T]`
-
-`ServiceRuntime[T]` is the small helper for packages that want to keep a Core reference and a typed options struct together.
-
-```go
-type agentServiceOptions struct {
- WorkspacePath string
-}
-
-type agentService struct {
- *core.ServiceRuntime[agentServiceOptions]
-}
-
-runtime := core.NewServiceRuntime(c, agentServiceOptions{
- WorkspacePath: "/srv/agent-workspaces",
-})
-```
-
-It exposes:
-
-- `Core()`
-- `Options()`
-- `Config()`
-
-This helper does not register anything by itself. It is a composition aid for package authors.
diff --git a/.core/reference/docs/services.md b/.core/reference/docs/services.md
deleted file mode 100644
index ad95d647..00000000
--- a/.core/reference/docs/services.md
+++ /dev/null
@@ -1,152 +0,0 @@
----
-title: Services
-description: Register, inspect, and lock CoreGO services.
----
-
-# Services
-
-In CoreGO, a service is a named lifecycle entry stored in the Core registry.
-
-## Register a Service
-
-```go
-c := core.New()
-
-r := c.Service("audit", core.Service{
- OnStart: func() core.Result {
- core.Info("audit started")
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- core.Info("audit stopped")
- return core.Result{OK: true}
- },
-})
-```
-
-Registration succeeds when:
-
-- the name is not empty
-- the registry is not locked
-- the name is not already in use
-
-## Read a Service Back
-
-```go
-r := c.Service("audit")
-if r.OK {
- svc := r.Value.(*core.Service)
- _ = svc
-}
-```
-
-The returned value is `*core.Service`.
-
-## List Registered Services
-
-```go
-names := c.Services()
-```
-
-### Important Detail
-
-The current registry is map-backed. `Services()`, `Startables()`, and `Stoppables()` do not promise a stable order.
-
-## Lifecycle Snapshots
-
-Use these helpers when you want the current set of startable or stoppable services:
-
-```go
-startables := c.Startables()
-stoppables := c.Stoppables()
-```
-
-They return `[]*core.Service` inside `Result.Value`.
-
-## Lock the Registry
-
-CoreGO has a service-lock mechanism, but it is explicit.
-
-```go
-c := core.New()
-
-c.LockEnable()
-c.Service("audit", core.Service{})
-c.Service("cache", core.Service{})
-c.LockApply()
-```
-
-After `LockApply`, new registrations fail:
-
-```go
-r := c.Service("late", core.Service{})
-fmt.Println(r.OK) // false
-```
-
-The default lock name is `"srv"`. You can pass a different name if you need a custom lock namespace.
-
-For the service registry itself, use the default `"srv"` lock path. That is the path used by `Core.Service(...)`.
-
-## `NewWithFactories`
-
-For GUI runtimes or factory-driven setup, CoreGO provides `NewWithFactories`.
-
-```go
-r := core.NewWithFactories(nil, map[string]core.ServiceFactory{
- "audit": func() core.Result {
- return core.Result{Value: core.Service{
- OnStart: func() core.Result {
- return core.Result{OK: true}
- },
- }, OK: true}
- },
- "cache": func() core.Result {
- return core.Result{Value: core.Service{}, OK: true}
- },
-})
-```
-
-### Important Details
-
-- each factory must return a `core.Service` in `Result.Value`
-- factories are executed in sorted key order
-- nil factories are skipped
-- the return value is `*core.Runtime`
-
-## `Runtime`
-
-`Runtime` is a small wrapper used for external runtimes such as GUI bindings.
-
-```go
-r := core.NewRuntime(nil)
-rt := r.Value.(*core.Runtime)
-
-_ = rt.ServiceStartup(context.Background(), nil)
-_ = rt.ServiceShutdown(context.Background())
-```
-
-`Runtime.ServiceName()` returns `"Core"`.
-
-## `ServiceRuntime[T]` for Package Authors
-
-If you are writing a package on top of CoreGO, use `ServiceRuntime[T]` to keep a typed options struct and the parent `Core` together.
-
-```go
-type repositoryServiceOptions struct {
- BaseDirectory string
-}
-
-type repositoryService struct {
- *core.ServiceRuntime[repositoryServiceOptions]
-}
-
-func newRepositoryService(c *core.Core) *repositoryService {
- return &repositoryService{
- ServiceRuntime: core.NewServiceRuntime(c, repositoryServiceOptions{
- BaseDirectory: "/srv/repos",
- }),
- }
-}
-```
-
-This is a package-authoring helper. It does not replace the `core.Service` registry entry.
diff --git a/.core/reference/docs/subsystems.md b/.core/reference/docs/subsystems.md
deleted file mode 100644
index f39ea164..00000000
--- a/.core/reference/docs/subsystems.md
+++ /dev/null
@@ -1,158 +0,0 @@
----
-title: Subsystems
-description: Built-in accessors for app metadata, embedded data, filesystem, transport handles, i18n, and CLI.
----
-
-# Subsystems
-
-`Core` gives you a set of built-in subsystems so small applications do not need extra plumbing before they can do useful work.
-
-## Accessor Map
-
-| Accessor | Purpose |
-|----------|---------|
-| `App()` | Application identity and external runtime |
-| `Data()` | Named embedded filesystem mounts |
-| `Drive()` | Named transport handles |
-| `Fs()` | Local filesystem access |
-| `I18n()` | Locale collection and translation delegation |
-| `Cli()` | Command-line surface over the command tree |
-
-## `App`
-
-`App` stores process identity and optional GUI runtime state.
-
-```go
-app := c.App()
-app.Name = "agent-workbench"
-app.Version = "0.25.0"
-app.Description = "workspace runner"
-app.Runtime = myRuntime
-```
-
-`Find` resolves an executable on `PATH` and returns an `*App`.
-
-```go
-r := core.Find("go", "Go toolchain")
-```
-
-## `Data`
-
-`Data` mounts named embedded filesystems and makes them addressable through paths like `mount-name/path/to/file`.
-
-```go
-c.Data().New(core.Options{
- {Key: "name", Value: "app"},
- {Key: "source", Value: appFS},
- {Key: "path", Value: "templates"},
-})
-```
-
-Read content:
-
-```go
-text := c.Data().ReadString("app/agent.md")
-bytes := c.Data().ReadFile("app/agent.md")
-list := c.Data().List("app")
-names := c.Data().ListNames("app")
-```
-
-Extract a mounted directory:
-
-```go
-r := c.Data().Extract("app/workspace", "/tmp/workspace", nil)
-```
-
-### Path Rule
-
-The first path segment is always the mount name.
-
-## `Drive`
-
-`Drive` is a registry for named transport handles.
-
-```go
-c.Drive().New(core.Options{
- {Key: "name", Value: "api"},
- {Key: "transport", Value: "https://api.lthn.ai"},
-})
-
-c.Drive().New(core.Options{
- {Key: "name", Value: "mcp"},
- {Key: "transport", Value: "mcp://mcp.lthn.sh"},
-})
-```
-
-Read them back:
-
-```go
-handle := c.Drive().Get("api")
-hasMCP := c.Drive().Has("mcp")
-names := c.Drive().Names()
-```
-
-## `Fs`
-
-`Fs` wraps local filesystem operations with a consistent `Result` shape.
-
-```go
-c.Fs().Write("/tmp/core-go/example.txt", "hello")
-r := c.Fs().Read("/tmp/core-go/example.txt")
-```
-
-Other helpers:
-
-```go
-c.Fs().EnsureDir("/tmp/core-go/cache")
-c.Fs().List("/tmp/core-go")
-c.Fs().Stat("/tmp/core-go/example.txt")
-c.Fs().Rename("/tmp/core-go/example.txt", "/tmp/core-go/example-2.txt")
-c.Fs().Delete("/tmp/core-go/example-2.txt")
-```
-
-### Important Details
-
-- the default `Core` starts with `Fs{root:"/"}`
-- relative paths resolve from the current working directory
-- `Delete` and `DeleteAll` refuse to remove `/` and `$HOME`
-
-## `I18n`
-
-`I18n` collects locale mounts and forwards translation work to a translator implementation when one is registered.
-
-```go
-c.I18n().SetLanguage("en-GB")
-```
-
-Without a translator, `Translate` returns the message key itself:
-
-```go
-r := c.I18n().Translate("cmd.deploy.description")
-```
-
-With a translator:
-
-```go
-c.I18n().SetTranslator(myTranslator)
-```
-
-Then:
-
-```go
-langs := c.I18n().AvailableLanguages()
-current := c.I18n().Language()
-```
-
-## `Cli`
-
-`Cli` exposes the command registry through a terminal-facing API.
-
-```go
-c.Cli().SetBanner(func(_ *core.Cli) string {
- return "Agent Workbench"
-})
-
-r := c.Cli().Run("workspace", "create", "--name=alpha")
-```
-
-Use [commands.md](commands.md) for the full command and flag model.
diff --git a/.core/reference/docs/testing.md b/.core/reference/docs/testing.md
deleted file mode 100644
index 656634ab..00000000
--- a/.core/reference/docs/testing.md
+++ /dev/null
@@ -1,118 +0,0 @@
----
-title: Testing
-description: Test naming and testing patterns used by CoreGO.
----
-
-# Testing
-
-The repository uses `github.com/stretchr/testify/assert` and a simple AX-friendly naming pattern.
-
-## Test Names
-
-Use:
-
-- `_Good` for expected success
-- `_Bad` for expected failure
-- `_Ugly` for panics, degenerate input, and edge behavior
-
-Examples from this repository:
-
-```go
-func TestNew_Good(t *testing.T) {}
-func TestService_Register_Duplicate_Bad(t *testing.T) {}
-func TestCore_Must_Ugly(t *testing.T) {}
-```
-
-## Start with a Small Core
-
-```go
-c := core.New(core.Options{
- {Key: "name", Value: "test-core"},
-})
-```
-
-Then register only the pieces your test needs.
-
-## Test a Service
-
-```go
-started := false
-
-c.Service("audit", core.Service{
- OnStart: func() core.Result {
- started = true
- return core.Result{OK: true}
- },
-})
-
-r := c.ServiceStartup(context.Background(), nil)
-assert.True(t, r.OK)
-assert.True(t, started)
-```
-
-## Test a Command
-
-```go
-c.Command("greet", core.Command{
- Action: func(opts core.Options) core.Result {
- return core.Result{Value: "hello " + opts.String("name"), OK: true}
- },
-})
-
-r := c.Cli().Run("greet", "--name=world")
-assert.True(t, r.OK)
-assert.Equal(t, "hello world", r.Value)
-```
-
-## Test a Query or Task
-
-```go
-c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
- if q == "ping" {
- return core.Result{Value: "pong", OK: true}
- }
- return core.Result{}
-})
-
-assert.Equal(t, "pong", c.QUERY("ping").Value)
-```
-
-```go
-c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
- if t == "compute" {
- return core.Result{Value: 42, OK: true}
- }
- return core.Result{}
-})
-
-assert.Equal(t, 42, c.PERFORM("compute").Value)
-```
-
-## Test Async Work
-
-For `PerformAsync`, observe completion through the action bus.
-
-```go
-completed := make(chan core.ActionTaskCompleted, 1)
-
-c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
- if event, ok := msg.(core.ActionTaskCompleted); ok {
- completed <- event
- }
- return core.Result{OK: true}
-})
-```
-
-Then wait with normal Go test tools such as channels, timers, or `assert.Eventually`.
-
-## Use Real Temporary Paths
-
-When testing `Fs`, `Data.Extract`, or other I/O helpers, use `t.TempDir()` and create realistic paths instead of mocking the filesystem by default.
-
-## Repository Commands
-
-```bash
-core go test
-core go test --run TestPerformAsync_Good
-go test ./...
-```
diff --git a/.core/reference/drive.go b/.core/reference/drive.go
deleted file mode 100644
index 7bf68690..00000000
--- a/.core/reference/drive.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Drive is the resource handle registry for transport connections.
-// Packages register their transport handles (API, MCP, SSH, VPN)
-// and other packages access them by name.
-//
-// Register a transport:
-//
-// c.Drive().New(core.NewOptions(
-// core.Option{Key: "name", Value: "api"},
-// core.Option{Key: "transport", Value: "https://api.lthn.ai"},
-// ))
-// c.Drive().New(core.NewOptions(
-// core.Option{Key: "name", Value: "ssh"},
-// core.Option{Key: "transport", Value: "ssh://claude@10.69.69.165"},
-// ))
-// c.Drive().New(core.NewOptions(
-// core.Option{Key: "name", Value: "mcp"},
-// core.Option{Key: "transport", Value: "mcp://mcp.lthn.sh"},
-// ))
-//
-// Retrieve a handle:
-//
-// api := c.Drive().Get("api")
-package core
-
-// DriveHandle holds a named transport resource.
-type DriveHandle struct {
- Name string
- Transport string
- Options Options
-}
-
-// Drive manages named transport handles. Embeds Registry[*DriveHandle].
-type Drive struct {
- *Registry[*DriveHandle]
-}
-
-// New registers a transport handle.
-//
-// c.Drive().New(core.NewOptions(
-// core.Option{Key: "name", Value: "api"},
-// core.Option{Key: "transport", Value: "https://api.lthn.ai"},
-// ))
-func (d *Drive) New(opts Options) Result {
- name := opts.String("name")
- if name == "" {
- return Result{}
- }
-
- handle := &DriveHandle{
- Name: name,
- Transport: opts.String("transport"),
- Options: opts,
- }
-
- d.Set(name, handle)
- return Result{handle, true}
-}
diff --git a/.core/reference/embed.go b/.core/reference/embed.go
deleted file mode 100644
index 32bbc5ec..00000000
--- a/.core/reference/embed.go
+++ /dev/null
@@ -1,676 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Embedded assets for the Core framework.
-//
-// Embed provides scoped filesystem access for go:embed and any fs.FS.
-// Also includes build-time asset packing (AST scanner + compressor)
-// and template-based directory extraction.
-//
-// Usage (mount):
-//
-// sub, _ := core.Mount(myFS, "lib/persona")
-// content, _ := sub.ReadString("secops/developer.md")
-//
-// Usage (extract):
-//
-// core.Extract(fsys, "/tmp/workspace", data)
-//
-// Usage (pack):
-//
-// refs, _ := core.ScanAssets([]string{"main.go"})
-// source, _ := core.GeneratePack(refs)
-// core.AddAsset("docs", "RFC.md", packed)
-// r := core.GeneratePack(pkg)
-package core
-
-import (
- "compress/gzip"
- corebytes "dappco.re/go"
- corefilepath "dappco.re/go"
- corefmt "dappco.re/go"
- coreos "dappco.re/go"
- "embed"
- "encoding/base64"
- "go/ast"
- "go/parser"
- "go/token"
- "io"
- "io/fs"
- "sync"
- "text/template"
-)
-
-// --- Runtime: Asset Registry ---
-
-// AssetGroup holds a named collection of packed assets.
-type AssetGroup struct {
- assets map[string]string // name → compressed data
-}
-
-var (
- assetGroups = make(map[string]*AssetGroup)
- assetGroupsMu sync.RWMutex
-)
-
-// AddAsset registers a packed asset at runtime (called from generated init()).
-func AddAsset(group, name, data string) {
- assetGroupsMu.Lock()
- defer assetGroupsMu.Unlock()
-
- g, ok := assetGroups[group]
- if !ok {
- g = &AssetGroup{assets: make(map[string]string)}
- assetGroups[group] = g
- }
- g.assets[name] = data
-}
-
-// GetAsset retrieves and decompresses a packed asset.
-//
-// r := core.GetAsset("mygroup", "greeting")
-// if r.OK { content := r.Value.(string) }
-func GetAsset(group, name string) Result {
- assetGroupsMu.RLock()
- g, ok := assetGroups[group]
- if !ok {
- assetGroupsMu.RUnlock()
- return Result{}
- }
- data, ok := g.assets[name]
- assetGroupsMu.RUnlock()
- if !ok {
- return Result{}
- }
- s, err := decompress(data)
- if err != nil {
- return Result{err, false}
- }
- return Result{s, true}
-}
-
-// GetAssetBytes retrieves a packed asset as bytes.
-//
-// r := core.GetAssetBytes("mygroup", "file")
-// if r.OK { data := r.Value.([]byte) }
-func GetAssetBytes(group, name string) Result {
- r := GetAsset(group, name)
- if !r.OK {
- return r
- }
- return Result{[]byte(r.Value.(string)), true}
-}
-
-// --- Build-time: AST Scanner ---
-
-// AssetRef is a reference to an asset found in source code.
-type AssetRef struct {
- Name string
- Path string
- Group string
- FullPath string
-}
-
-// ScannedPackage holds all asset references from a set of source files.
-type ScannedPackage struct {
- PackageName string
- BaseDirectory string
- Groups []string
- Assets []AssetRef
-}
-
-// ScanAssets parses Go source files and finds asset references.
-// Looks for calls to: core.GetAsset("group", "name"), core.AddAsset, etc.
-func ScanAssets(filenames []string) Result {
- packageMap := make(map[string]*ScannedPackage)
- var scanErr error
-
- for _, filename := range filenames {
- fset := token.NewFileSet()
- node, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
- if err != nil {
- return Result{err, false}
- }
-
- baseDir := filepath.Dir(filename)
- pkg, ok := packageMap[baseDir]
- if !ok {
- pkg = &ScannedPackage{BaseDirectory: baseDir}
- packageMap[baseDir] = pkg
- }
- pkg.PackageName = node.Name.Name
-
- ast.Inspect(node, func(n ast.Node) bool {
- if scanErr != nil {
- return false
- }
- call, ok := n.(*ast.CallExpr)
- if !ok {
- return true
- }
-
- sel, ok := call.Fun.(*ast.SelectorExpr)
- if !ok {
- return true
- }
-
- ident, ok := sel.X.(*ast.Ident)
- if !ok {
- return true
- }
-
- // Look for core.GetAsset or mewn.String patterns
- if ident.Name == "core" || ident.Name == "mewn" {
- switch sel.Sel.Name {
- case "GetAsset", "GetAssetBytes", "String", "MustString", "Bytes", "MustBytes":
- if len(call.Args) >= 1 {
- if lit, ok := call.Args[len(call.Args)-1].(*ast.BasicLit); ok {
- path := TrimPrefix(TrimSuffix(lit.Value, "\""), "\"")
- group := "."
- if len(call.Args) >= 2 {
- if glit, ok := call.Args[0].(*ast.BasicLit); ok {
- group = TrimPrefix(TrimSuffix(glit.Value, "\""), "\"")
- }
- }
- fullPath, err := filepath.Abs(filepath.Join(baseDir, group, path))
- if err != nil {
- scanErr = Wrap(err, "core.ScanAssets", Join(" ", "could not determine absolute path for asset", path, "in group", group))
- return false
- }
- pkg.Assets = append(pkg.Assets, AssetRef{
- Name: path,
-
- Group: group,
- FullPath: fullPath,
- })
- }
- }
- case "Group":
- // Variable assignment: g := core.Group("./assets")
- if len(call.Args) == 1 {
- if lit, ok := call.Args[0].(*ast.BasicLit); ok {
- path := TrimPrefix(TrimSuffix(lit.Value, "\""), "\"")
- fullPath, err := filepath.Abs(filepath.Join(baseDir, path))
- if err != nil {
- scanErr = Wrap(err, "core.ScanAssets", Join(" ", "could not determine absolute path for group", path))
- return false
- }
- pkg.Groups = append(pkg.Groups, fullPath)
- // Track for variable resolution
- }
- }
- }
- }
-
- return true
- })
- if scanErr != nil {
- return Result{scanErr, false}
- }
- }
-
- var result []ScannedPackage
- for _, pkg := range packageMap {
- result = append(result, *pkg)
- }
- return Result{result, true}
-}
-
-// GeneratePack creates Go source code that embeds the scanned assets.
-func GeneratePack(pkg ScannedPackage) Result {
- b := NewBuilder()
-
- b.WriteString(fmt.Sprintf("package %s\n\n", pkg.PackageName))
- b.WriteString("// Code generated by core pack. DO NOT EDIT.\n\n")
-
- if len(pkg.Assets) == 0 && len(pkg.Groups) == 0 {
- return Result{b.String(), true}
- }
-
- b.WriteString("import \"dappco.re/go\"\n\n")
- b.WriteString("func init() {\n")
-
- // Pack groups (entire directories)
- packed := make(map[string]bool)
- for _, groupPath := range pkg.Groups {
- files, err := getAllFiles(groupPath)
- if err != nil {
- return Result{err, false}
- }
- for _, file := range files {
- if packed[file] {
- continue
- }
- data, err := compressFile(file)
- if err != nil {
- return Result{err, false}
- }
- localPath := TrimPrefix(file, groupPath+"/")
- relGroup, err := filepath.Rel(pkg.BaseDirectory, groupPath)
- if err != nil {
- return Result{err, false}
- }
- b.WriteString(fmt.Sprintf("\tcore.AddAsset(%q, %q, %q)\n", relGroup, localPath, data))
- packed[file] = true
- }
- }
-
- // Pack individual assets
- for _, asset := range pkg.Assets {
- if packed[asset.FullPath] {
- continue
- }
- data, err := compressFile(asset.FullPath)
- if err != nil {
- return Result{err, false}
- }
- b.WriteString(fmt.Sprintf("\tcore.AddAsset(%q, %q, %q)\n", asset.Group, asset.Name, data))
- packed[asset.FullPath] = true
- }
-
- b.WriteString("}\n")
- return Result{b.String(), true}
-}
-
-// --- Compression ---
-
-func compressFile(path string) (string, any) {
- data, err := os.ReadFile(path)
- if err != nil {
- return "", err
- }
- return compress(string(data))
-}
-
-func compress(input string) (string, any) {
- var buf bytes.Buffer
- b64 := base64.NewEncoder(base64.StdEncoding, &buf)
- gz, err := gzip.NewWriterLevel(b64, gzip.BestCompression)
- if err != nil {
- return "", err
- }
- if _, err := gz.Write([]byte(input)); err != nil {
- if closeErr := gz.Close(); closeErr != nil {
- return "", err
- }
- if closeErr := b64.Close(); closeErr != nil {
- return "", err
- }
- return "", err
- }
- if err := gz.Close(); err != nil {
- if closeErr := b64.Close(); closeErr != nil {
- return "", err
- }
- return "", err
- }
- if err := b64.Close(); err != nil {
- return "", err
- }
- return buf.String(), nil
-}
-
-func decompress(input string) (string, any) {
- b64 := base64.NewDecoder(base64.StdEncoding, NewReader(input))
- gz, err := gzip.NewReader(b64)
- if err != nil {
- return "", err
- }
-
- data, err := io.ReadAll(gz)
- if err != nil {
- return "", err
- }
- if err := gz.Close(); err != nil {
- return "", err
- }
- return string(data), nil
-}
-
-func getAllFiles(dir string) ([]string, any) {
- var result []string
- err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) any {
- if err != nil {
- return err
- }
- if !d.IsDir() {
- result = append(result, path)
- }
- return nil
- })
- return result, err
-}
-
-// --- Embed: Scoped Filesystem Mount ---
-
-// Embed wraps an fs.FS with a basedir for scoped access.
-// All paths are relative to basedir.
-type Embed struct {
- basedir string
- fsys fs.FS
- embedFS *embed.FS // original embed.FS for type-safe access via EmbedFS()
-}
-
-// Mount creates a scoped view of an fs.FS anchored at basedir.
-//
-// r := core.Mount(myFS, "lib/prompts")
-// if r.OK { emb := r.Value.(*Embed) }
-func Mount(fsys fs.FS, basedir string) Result {
- s := &Embed{fsys: fsys, basedir: basedir}
-
- if efs, ok := fsys.(embed.FS); ok {
- s.embedFS = &efs
- }
-
- if r := s.ReadDir("."); !r.OK {
- return r
- }
- return Result{s, true}
-}
-
-// MountEmbed creates a scoped view of an embed.FS.
-//
-// r := core.MountEmbed(myFS, "testdata")
-func MountEmbed(efs embed.FS, basedir string) Result {
- return Mount(efs, basedir)
-}
-
-func (s *Embed) path(name string) Result {
- joined := filepath.ToSlash(filepath.Join(s.basedir, name))
- if HasPrefix(joined, "..") || Contains(joined, "/../") || HasSuffix(joined, "/..") {
- return Result{E("embed.path", Concat("path traversal rejected: ", name), nil), false}
- }
- return Result{joined, true}
-}
-
-// Open opens the named file for reading.
-//
-// r := emb.Open("test.txt")
-// if r.OK { file := r.Value.(fs.File) }
-func (s *Embed) Open(name string) Result {
- r := s.path(name)
- if !r.OK {
- return r
- }
- f, err := s.fsys.Open(r.Value.(string))
- if err != nil {
- return Result{err, false}
- }
- return Result{f, true}
-}
-
-// ReadDir reads the named directory.
-func (s *Embed) ReadDir(name string) Result {
- r := s.path(name)
- if !r.OK {
- return r
- }
- return Result{}.New(fs.ReadDir(s.fsys, r.Value.(string)))
-}
-
-// ReadFile reads the named file.
-//
-// r := emb.ReadFile("test.txt")
-// if r.OK { data := r.Value.([]byte) }
-func (s *Embed) ReadFile(name string) Result {
- r := s.path(name)
- if !r.OK {
- return r
- }
- data, err := fs.ReadFile(s.fsys, r.Value.(string))
- if err != nil {
- return Result{err, false}
- }
- return Result{data, true}
-}
-
-// ReadString reads the named file as a string.
-//
-// r := emb.ReadString("test.txt")
-// if r.OK { content := r.Value.(string) }
-func (s *Embed) ReadString(name string) Result {
- r := s.ReadFile(name)
- if !r.OK {
- return r
- }
- return Result{string(r.Value.([]byte)), true}
-}
-
-// Sub returns a new Embed anchored at a subdirectory within this mount.
-//
-// r := emb.Sub("testdata")
-// if r.OK { sub := r.Value.(*Embed) }
-func (s *Embed) Sub(subDir string) Result {
- r := s.path(subDir)
- if !r.OK {
- return r
- }
- sub, err := fs.Sub(s.fsys, r.Value.(string))
- if err != nil {
- return Result{err, false}
- }
- return Result{&Embed{fsys: sub, basedir: "."}, true}
-}
-
-// FS returns the underlying fs.FS.
-func (s *Embed) FS() fs.FS {
- return s.fsys
-}
-
-// EmbedFS returns the underlying embed.FS if mounted from one.
-// Returns zero embed.FS if mounted from a non-embed source.
-func (s *Embed) EmbedFS() embed.FS {
- if s.embedFS != nil {
- return *s.embedFS
- }
- return embed.FS{}
-}
-
-// BaseDirectory returns the base directory this Embed is anchored at.
-func (s *Embed) BaseDirectory() string {
- return s.basedir
-}
-
-// --- Template Extraction ---
-
-// ExtractOptions configures template extraction.
-type ExtractOptions struct {
- // TemplateFilters identifies template files by substring match.
- // Default: [".tmpl"]
- TemplateFilters []string
-
- // IgnoreFiles is a set of filenames to skip during extraction.
- IgnoreFiles map[string]struct{}
-
- // RenameFiles maps original filenames to new names.
- RenameFiles map[string]string
-}
-
-// Extract copies a template directory from an fs.FS to targetDir,
-// processing Go text/template in filenames and file contents.
-//
-// Files containing a template filter substring (default: ".tmpl") have
-// their contents processed through text/template with the given data.
-// The filter is stripped from the output filename.
-//
-// Directory and file names can contain Go template expressions:
-// {{.Name}}/main.go → myproject/main.go
-//
-// Data can be any struct or map[string]string for template substitution.
-func Extract(fsys fs.FS, targetDir string, data any, opts ...ExtractOptions) Result {
- opt := ExtractOptions{
- TemplateFilters: []string{".tmpl"},
- IgnoreFiles: make(map[string]struct{}),
- RenameFiles: make(map[string]string),
- }
- if len(opts) > 0 {
- if len(opts[0].TemplateFilters) > 0 {
- opt.TemplateFilters = opts[0].TemplateFilters
- }
- if opts[0].IgnoreFiles != nil {
- opt.IgnoreFiles = opts[0].IgnoreFiles
- }
- if opts[0].RenameFiles != nil {
- opt.RenameFiles = opts[0].RenameFiles
- }
- }
-
- // Ensure target directory exists
- targetDir, err := filepath.Abs(targetDir)
- if err != nil {
- return Result{err, false}
- }
- if err := os.MkdirAll(targetDir, 0700); err != nil {
- return Result{err, false}
- }
-
- // Categorise files
- var dirs []string
- var templateFiles []string
- var standardFiles []string
-
- err = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) any {
- if err != nil {
- return err
- }
- if path == "." {
- return nil
- }
- if d.IsDir() {
- dirs = append(dirs, path)
- return nil
- }
- filename := filepath.Base(path)
- if _, ignored := opt.IgnoreFiles[filename]; ignored {
- return nil
- }
- if isTemplate(filename, opt.TemplateFilters) {
- templateFiles = append(templateFiles, path)
- } else {
- standardFiles = append(standardFiles, path)
- }
- return nil
- })
- if err != nil {
- return Result{err, false}
- }
-
- // safePath ensures a rendered path stays under targetDir.
- safePath := func(rendered string) (string, any) {
- abs, err := filepath.Abs(rendered)
- if err != nil {
- return "", err
- }
- if !HasPrefix(abs, targetDir+string(filepath.Separator)) && abs != targetDir {
- return "", E("embed.Extract", Concat("path escapes target: ", abs), nil)
- }
- return abs, nil
- }
-
- // Create directories (names may contain templates)
- for _, dir := range dirs {
- target, err := safePath(renderPath(filepath.Join(targetDir, dir), data))
- if err != nil {
- return Result{err, false}
- }
- if err := os.MkdirAll(target, 0700); err != nil {
- return Result{err, false}
- }
- }
-
- // Process template files
- for _, path := range templateFiles {
- tmpl, err := template.ParseFS(fsys, path)
- if err != nil {
- return Result{err, false}
- }
-
- targetFile := renderPath(filepath.Join(targetDir, path), data)
-
- // Strip template filters from filename
- dir := filepath.Dir(targetFile)
- name := filepath.Base(targetFile)
- for _, filter := range opt.TemplateFilters {
- name = Replace(name, filter, "")
- }
- if renamed := opt.RenameFiles[name]; renamed != "" {
- name = renamed
- }
- targetFile, err = safePath(filepath.Join(dir, name))
- if err != nil {
- return Result{err, false}
- }
-
- f, err := os.OpenFile(targetFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
- if err != nil {
- return Result{err, false}
- }
- if err := tmpl.Execute(f, data); err != nil {
- f.Close()
- return Result{err, false}
- }
- f.Close()
- }
-
- // Copy standard files
- for _, path := range standardFiles {
- targetPath := path
- name := filepath.Base(path)
- if renamed := opt.RenameFiles[name]; renamed != "" {
- targetPath = filepath.Join(filepath.Dir(path), renamed)
- }
- target, err := safePath(renderPath(filepath.Join(targetDir, targetPath), data))
- if err != nil {
- return Result{err, false}
- }
- if err := copyFile(fsys, path, target); err != nil {
- return Result{err, false}
- }
- }
-
- return Result{OK: true}
-}
-
-func isTemplate(filename string, filters []string) bool {
- for _, f := range filters {
- if Contains(filename, f) {
- return true
- }
- }
- return false
-}
-
-func renderPath(path string, data any) string {
- if data == nil {
- return path
- }
- tmpl, err := template.New(`path`).Parse(path)
- if err != nil {
- return path
- }
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return path
- }
- return buf.String()
-}
-
-func copyFile(fsys fs.FS, source, target string) any {
- s, err := fsys.Open(source)
- if err != nil {
- return err
- }
- defer s.Close()
-
- if err := os.MkdirAll(filepath.Dir(target), 0700); err != nil {
- return err
- }
-
- d, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
- if err != nil {
- return err
- }
- defer d.Close()
-
- _, err = io.Copy(d, s)
- return err
-}
diff --git a/.core/reference/error.go b/.core/reference/error.go
deleted file mode 100644
index cf435532..00000000
--- a/.core/reference/error.go
+++ /dev/null
@@ -1,404 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Structured errors, crash recovery, and reporting for the Core framework.
-// Provides E() for error creation, Wrap()/WrapCode() for chaining,
-// and Err for panic recovery and crash reporting.
-//
-// if core.Is(err, context.Canceled) { return }
-// stack := core.FormatStackTrace(err)
-// r := c.Error().Reports(10)
-
-package core
-
-import (
- coreerrors "dappco.re/go"
- corefilepath "dappco.re/go"
- corejson "dappco.re/go"
- coreos "dappco.re/go"
- "iter"
- "maps"
- "runtime"
- "runtime/debug"
- "sync"
- "time"
-)
-
-// ErrorSink is the shared interface for error reporting.
-// Implemented by ErrorLog (structured logging) and ErrorPanic (panic recovery).
-type ErrorSink interface {
- Error(msg string, keyvals ...any)
- Warn(msg string, keyvals ...any)
-}
-
-var _ ErrorSink = (*Log)(nil)
-
-// Err represents a structured error with operational context.
-// It implements the error interface and supports unwrapping.
-type Err struct {
- Operation string // Operation being performed (e.g., "user.Save")
- Message string // Human-readable message
- Cause error // Underlying error (optional)
- Code string // Error code (optional, e.g., "VALIDATION_FAILED")
-}
-
-// Error implements the error interface.
-func (e *Err) Error() string {
- var prefix string
- if e.Operation != "" {
- prefix = e.Operation + ": "
- }
- if e.Cause != nil {
- if e.Code != "" {
- return Concat(prefix, e.Message, " [", e.Code, "]: ", e.Cause.Error())
- }
- return Concat(prefix, e.Message, ": ", e.Cause.Error())
- }
- if e.Code != "" {
- return Concat(prefix, e.Message, " [", e.Code, "]")
- }
- return Concat(prefix, e.Message)
-}
-
-// Unwrap returns the underlying error for use with errors.Is and errors.As.
-func (e *Err) Unwrap() any {
- return e.Cause
-}
-
-// --- Error Creation Functions ---
-
-// E creates a new Err with operation context.
-// The underlying error can be nil for creating errors without a cause.
-//
-// Example:
-//
-// return log.E("user.Save", "failed to save user", err)
-// return log.E("api.Call", "rate limited", nil) // No underlying cause
-func E(op, msg string, err error) any {
- return &Err{Operation: op, Message: msg, Cause: err}
-}
-
-// Wrap wraps an error with operation context.
-// Returns nil if err is nil, to support conditional wrapping.
-// Preserves error Code if the wrapped error is an *Err.
-//
-// Example:
-//
-// return log.Wrap(err, "db.Query", "database query failed")
-func Wrap(err any, op, msg string) any {
- if err == nil {
- return nil
- }
- // Preserve Code from wrapped *Err
- var logErr *Err
- if As(err, &logErr) && logErr.Code != "" {
- return &Err{Operation: op, Message: msg, Cause: err, Code: logErr.Code}
- }
- return &Err{Operation: op, Message: msg, Cause: err}
-}
-
-// WrapCode wraps an error with operation context and error code.
-// Returns nil only if both err is nil AND code is empty.
-// Useful for API errors that need machine-readable codes.
-//
-// Example:
-//
-// return log.WrapCode(err, "VALIDATION_ERROR", "user.Validate", "invalid email")
-func WrapCode(err any, code, op, msg string) any {
- if err == nil && code == "" {
- return nil
- }
- return &Err{Operation: op, Message: msg, Cause: err, Code: code}
-}
-
-// NewCode creates an error with just code and message (no underlying error).
-// Useful for creating sentinel errors with codes.
-//
-// Example:
-//
-// var ErrNotFound = log.NewCode("NOT_FOUND", "resource not found")
-func NewCode(code, msg string) any {
- return &Err{Message: msg, Code: code}
-}
-
-// --- Standard Library Wrappers ---
-
-// Is reports whether any error in err's tree matches target.
-// Wrapper around errors.Is for convenience.
-func Is(err, target any) bool {
- return errors.Is(err, target)
-}
-
-// As finds the first error in err's tree that matches target.
-// Wrapper around errors.As for convenience.
-func As(err any, target any) bool {
- return errors.As(err, target)
-}
-
-// NewError creates a simple error with the given text.
-// Wrapper around errors.New for convenience.
-func NewError(text string) any {
- return errors.New(text)
-}
-
-// ErrorJoin combines multiple errors into one.
-//
-// core.ErrorJoin(err1, err2, err3)
-func ErrorJoin(errs ...any) any {
- return errors.Join(errs...)
-}
-
-// --- Error Introspection Helpers ---
-
-// Operation extracts the operation name from an error.
-// Returns empty string if the error is not an *Err.
-func Operation(err any) string {
- var e *Err
- if As(err, &e) {
- return e.Operation
- }
- return ""
-}
-
-// ErrorCode extracts the error code from an error.
-// Returns empty string if the error is not an *Err or has no code.
-func ErrorCode(err any) string {
- var e *Err
- if As(err, &e) {
- return e.Code
- }
- return ""
-}
-
-// Message extracts the message from an error.
-// Returns the error's Error() string if not an *Err.
-func ErrorMessage(err any) string {
- if err == nil {
- return ""
- }
- var e *Err
- if As(err, &e) {
- return e.Message
- }
- return err.Error()
-}
-
-// Root returns the root cause of an error chain.
-// Unwraps until no more wrapped errors are found.
-func Root(err any) any {
- if err == nil {
- return nil
- }
- for {
- unwrapped := errors.Unwrap(err)
- if unwrapped == nil {
- return err
- }
- err = unwrapped
- }
-}
-
-// AllOperations returns an iterator over all operational contexts in the error chain.
-// It traverses the error tree using errors.Unwrap.
-func AllOperations(err any) iter.Seq[string] {
- return func(yield func(string) bool) {
- for err != nil {
- if e, ok := err.(*Err); ok {
- if e.Operation != "" {
- if !yield(e.Operation) {
- return
- }
- }
- }
- err = errors.Unwrap(err)
- }
- }
-}
-
-// StackTrace returns the logical stack trace (chain of operations) from an error.
-// It returns an empty slice if no operational context is found.
-func StackTrace(err any) []string {
- var stack []string
- for op := range AllOperations(err) {
- stack = append(stack, op)
- }
- return stack
-}
-
-// FormatStackTrace returns a pretty-printed logical stack trace.
-func FormatStackTrace(err any) string {
- var ops []string
- for op := range AllOperations(err) {
- ops = append(ops, op)
- }
- if len(ops) == 0 {
- return ""
- }
- return Join(" -> ", ops...)
-}
-
-// --- ErrorLog: Log-and-Return Error Helpers ---
-
-// ErrorLog combines error creation with logging.
-// Primary action: return an error. Secondary: log it.
-type ErrorLog struct {
- log *Log
-}
-
-func (el *ErrorLog) logger() *Log {
- if el.log != nil {
- return el.log
- }
- return Default()
-}
-
-// Error logs at Error level and returns a Result with the wrapped error.
-func (el *ErrorLog) Error(err any, op, msg string) Result {
- if err == nil {
- return Result{OK: true}
- }
- wrapped := Wrap(err, op, msg)
- el.logger().Error(msg, "op", op, "err", err)
- return Result{wrapped, false}
-}
-
-// Warn logs at Warn level and returns a Result with the wrapped error.
-func (el *ErrorLog) Warn(err any, op, msg string) Result {
- if err == nil {
- return Result{OK: true}
- }
- wrapped := Wrap(err, op, msg)
- el.logger().Warn(msg, "op", op, "err", err)
- return Result{wrapped, false}
-}
-
-// Must logs and panics if err is not nil.
-func (el *ErrorLog) Must(err any, op, msg string) {
- if err != nil {
- el.logger().Error(msg, "op", op, "err", err)
- panic(Wrap(err, op, msg))
- }
-}
-
-// --- Crash Recovery & Reporting ---
-
-// CrashReport represents a single crash event.
-type CrashReport struct {
- Timestamp time.Time `json:"timestamp"`
- Error string `json:"error"`
- Stack string `json:"stack"`
- System CrashSystem `json:"system,omitempty"`
- Meta map[string]string `json:"meta,omitempty"`
-}
-
-// CrashSystem holds system information at crash time.
-type CrashSystem struct {
- OperatingSystem string `json:"operatingsystem"`
- Architecture string `json:"architecture"`
- Version string `json:"go_version"`
-}
-
-// ErrorPanic manages panic recovery and crash reporting.
-type ErrorPanic struct {
- filePath string
- meta map[string]string
- onCrash func(CrashReport)
-}
-
-// Recover captures a panic and creates a crash report.
-// Use as: defer c.Error().Recover()
-func (h *ErrorPanic) Recover() {
- if h == nil {
- return
- }
- r := recover()
- if r == nil {
- return
- }
-
- err, ok := r.(error)
- if !ok {
- err = NewError(Sprint("panic: ", r))
- }
-
- report := CrashReport{
- Timestamp: time.Now(),
- Error: err.Error(),
- Stack: string(debug.Stack()),
- System: CrashSystem{
- OperatingSystem: runtime.GOOS,
- Architecture: runtime.GOARCH,
- Version: runtime.Version(),
- },
- Meta: maps.Clone(h.meta),
- }
-
- if h.onCrash != nil {
- h.onCrash(report)
- }
-
- if h.filePath != "" {
- h.appendReport(report)
- }
-}
-
-// SafeGo runs a function in a goroutine with panic recovery.
-func (h *ErrorPanic) SafeGo(fn func()) {
- go func() {
- defer h.Recover()
- fn()
- }()
-}
-
-// Reports returns the last n crash reports from the file.
-func (h *ErrorPanic) Reports(n int) Result {
- if h.filePath == "" {
- return Result{}
- }
- crashMu.Lock()
- defer crashMu.Unlock()
- data, err := os.ReadFile(h.filePath)
- if err != nil {
- return Result{err, false}
- }
- var reports []CrashReport
- if err := json.Unmarshal(data, &reports); err != nil {
- return Result{err, false}
- }
- if n <= 0 || len(reports) <= n {
- return Result{reports, true}
- }
- return Result{reports[len(reports)-n:], true}
-}
-
-var crashMu sync.Mutex
-
-func (h *ErrorPanic) appendReport(report CrashReport) {
- crashMu.Lock()
- defer crashMu.Unlock()
-
- var reports []CrashReport
- if data, err := os.ReadFile(h.filePath); err == nil {
- if err := json.Unmarshal(data, &reports); err != nil {
- Default().Error(Concat("crash report file corrupted path=", h.filePath, " err=", err.Error(), " raw=", string(data)))
- backupPath := Concat(h.filePath, ".corrupt")
- if backupErr := os.WriteFile(backupPath, data, 0600); backupErr != nil {
- Default().Error(Concat("crash report backup failed path=", h.filePath, " err=", backupErr.Error()))
- }
- reports = nil
- }
- }
-
- reports = append(reports, report)
- data, err := json.MarshalIndent(reports, "", " ")
- if err != nil {
- Default().Error(Concat("crash report marshal failed: ", err.Error()))
- return
- }
- if err := os.MkdirAll(filepath.Dir(h.filePath), 0700); err != nil {
- Default().Error(Concat("crash report dir failed: ", err.Error()))
- return
- }
- if err := os.WriteFile(h.filePath, data, 0600); err != nil {
- Default().Error(Concat("crash report write failed: ", err.Error()))
- }
-}
diff --git a/.core/reference/fs.go b/.core/reference/fs.go
deleted file mode 100644
index 013d2c67..00000000
--- a/.core/reference/fs.go
+++ /dev/null
@@ -1,469 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Sandboxed local filesystem I/O for the Core framework.
-package core
-
-import (
- corefilepath "dappco.re/go"
- coreos "dappco.re/go"
- "io"
- "io/fs"
- "os/user"
- "time"
-)
-
-// Fs is a sandboxed local filesystem backend.
-type Fs struct {
- root string
-}
-
-// New initialises an Fs with the given root directory.
-// Root "/" means unrestricted access. Empty root defaults to "/".
-//
-// fs := (&core.Fs{}).New("/")
-func (m *Fs) New(root string) *Fs {
- if root == "" {
- root = "/"
- }
- m.root = root
- return m
-}
-
-// NewUnrestricted returns a new Fs with root "/", granting full filesystem access.
-// Use this instead of unsafe.Pointer to bypass the sandbox.
-//
-// fs := c.Fs().NewUnrestricted()
-// fs.Read("/etc/hostname") // works — no sandbox
-func (m *Fs) NewUnrestricted() *Fs {
- return (&Fs{}).New("/")
-}
-
-// Root returns the sandbox root path.
-//
-// root := c.Fs().Root() // e.g. "/home/agent/.core"
-func (m *Fs) Root() string {
- if m.root == "" {
- return "/"
- }
- return m.root
-}
-
-// path sanitises and returns the full path.
-// Absolute paths are sandboxed under root (unless root is "/").
-// Empty root defaults to "/" — the zero value of Fs is usable.
-func (m *Fs) path(p string) string {
- root := m.root
- if root == "" {
- root = "/"
- }
- if p == "" {
- return root
- }
-
- // If the path is relative and the medium is rooted at "/",
- // treat it as relative to the current working directory.
- // This makes io.Local behave more like the standard 'os' package.
- if root == "/" && !filepath.IsAbs(p) {
- cwd, _ := os.Getwd()
- return filepath.Join(cwd, p)
- }
-
- // Use filepath.Clean with a leading slash to resolve all .. and . internally
- // before joining with the root. This is a standard way to sandbox paths.
- clean := filepath.Clean("/" + p)
-
- // If root is "/", allow absolute paths through
- if root == "/" {
- return clean
- }
-
- // Strip leading "/" so Join works correctly with root
- return filepath.Join(root, clean[1:])
-}
-
-// validatePath ensures the path is within the sandbox, following symlinks if they exist.
-func (m *Fs) validatePath(p string) Result {
- root := m.root
- if root == "" {
- root = "/"
- }
- if root == "/" {
- return Result{m.path(p), true}
- }
-
- // Split the cleaned path into components
- parts := Split(filepath.Clean("/"+p), string(os.PathSeparator))
- current := root
-
- for _, part := range parts {
- if part == "" {
- continue
- }
-
- next := filepath.Join(current, part)
- realNext, err := filepath.EvalSymlinks(next)
- if err != nil {
- if os.IsNotExist(err) {
- // Part doesn't exist, we can't follow symlinks anymore.
- // Since the path is already Cleaned and current is safe,
- // appending a component to current will not escape.
- current = next
- continue
- }
- return Result{err, false}
- }
-
- // Verify the resolved part is still within the root
- rel, err := filepath.Rel(root, realNext)
- if err != nil || HasPrefix(rel, "..") {
- // Security event: sandbox escape attempt
- username := "unknown"
- if u, err := user.Current(); err == nil {
- username = u.Username
- }
- Print(os.Stderr, "[%s] SECURITY sandbox escape detected root=%s path=%s attempted=%s user=%s",
- time.Now().Format(time.RFC3339), root, p, realNext, username)
- if err == nil {
- err = E("fs.validatePath", Concat("sandbox escape: ", p, " resolves outside ", m.root), nil)
- }
- return Result{err, false}
- }
- current = realNext
- }
-
- return Result{current, true}
-}
-
-// Read returns file contents as string.
-func (m *Fs) Read(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- data, err := os.ReadFile(vp.Value.(string))
- if err != nil {
- return Result{err, false}
- }
- return Result{string(data), true}
-}
-
-// Write saves content to file, creating parent directories as needed.
-// Files are created with mode 0600 by default.
-// Use WriteMode when broader access is intentional.
-func (m *Fs) Write(p, content string) Result {
- return m.WriteMode(p, content, 0600)
-}
-
-// WriteMode saves content to file with explicit permissions.
-// Use 0644 or 0755 only when broader access is intentional.
-func (m *Fs) WriteMode(p, content string, mode os.FileMode) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if err := os.MkdirAll(filepath.Dir(full), 0700); err != nil {
- return Result{err, false}
- }
- if err := os.WriteFile(full, []byte(content), mode); err != nil {
- return Result{err, false}
- }
- if err := os.Chmod(full, mode); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// TempDir creates a temporary directory and returns its path.
-// The caller is responsible for cleanup via fs.DeleteAll().
-//
-// dir := fs.TempDir("agent-workspace")
-// defer fs.DeleteAll(dir)
-func (m *Fs) TempDir(prefix string) string {
- root := m.root
- if root == "" || root == "/" {
- root = os.TempDir()
- } else if err := os.MkdirAll(root, 0700); err != nil {
- return ""
- }
- dir, err := os.MkdirTemp(root, prefix)
- if err != nil {
- return ""
- }
- if vp := m.validatePath(dir); !vp.OK {
- os.RemoveAll(dir)
- return ""
- }
- return dir
-}
-
-// DirFS returns an fs.FS rooted at the given directory path.
-//
-// fsys := core.DirFS("/path/to/templates")
-func DirFS(dir string) fs.FS {
- return os.DirFS(dir)
-}
-
-// WriteAtomic writes content by writing to a temp file then renaming.
-// Rename is atomic on POSIX — concurrent readers never see a partial file.
-// Use this for status files, config, or any file read from multiple goroutines.
-//
-// r := fs.WriteAtomic("/status.json", jsonData)
-func (m *Fs) WriteAtomic(p, content string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if err := os.MkdirAll(filepath.Dir(full), 0700); err != nil {
- return Result{err, false}
- }
-
- tmp := full + ".tmp." + shortRand()
- if err := os.WriteFile(tmp, []byte(content), 0600); err != nil {
- return Result{err, false}
- }
- if err := os.Rename(tmp, full); err != nil {
- os.Remove(tmp)
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// EnsureDir creates directory if it doesn't exist.
-func (m *Fs) EnsureDir(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- if err := os.MkdirAll(vp.Value.(string), 0700); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// IsDir returns true if path is a directory.
-func (m *Fs) IsDir(p string) bool {
- if p == "" {
- return false
- }
- vp := m.validatePath(p)
- if !vp.OK {
- return false
- }
- info, err := os.Stat(vp.Value.(string))
- return err == nil && info.IsDir()
-}
-
-// IsFile returns true if path is a regular file.
-func (m *Fs) IsFile(p string) bool {
- if p == "" {
- return false
- }
- vp := m.validatePath(p)
- if !vp.OK {
- return false
- }
- info, err := os.Stat(vp.Value.(string))
- return err == nil && info.Mode().IsRegular()
-}
-
-// Exists returns true if path exists.
-func (m *Fs) Exists(p string) bool {
- vp := m.validatePath(p)
- if !vp.OK {
- return false
- }
- _, err := os.Stat(vp.Value.(string))
- return err == nil
-}
-
-// List returns directory entries.
-func (m *Fs) List(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- return Result{}.New(os.ReadDir(vp.Value.(string)))
-}
-
-// Stat returns file info.
-func (m *Fs) Stat(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- return Result{}.New(os.Stat(vp.Value.(string)))
-}
-
-// Open opens the named file for reading.
-func (m *Fs) Open(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- return Result{}.New(os.Open(vp.Value.(string)))
-}
-
-// Create creates or truncates the named file.
-func (m *Fs) Create(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if err := os.MkdirAll(filepath.Dir(full), 0700); err != nil {
- return Result{err, false}
- }
- file, err := os.OpenFile(full, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
- if err != nil {
- return Result{err, false}
- }
- if err := file.Chmod(0600); err != nil {
- file.Close()
- return Result{err, false}
- }
- return Result{}.New(file)
-}
-
-// Append opens the named file for appending, creating it if it doesn't exist.
-func (m *Fs) Append(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if err := os.MkdirAll(filepath.Dir(full), 0700); err != nil {
- return Result{err, false}
- }
- file, err := os.OpenFile(full, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
- if err != nil {
- return Result{err, false}
- }
- if err := file.Chmod(0600); err != nil {
- file.Close()
- return Result{err, false}
- }
- return Result{}.New(file)
-}
-
-// ReadStream returns a reader for the file content.
-func (m *Fs) ReadStream(path string) Result {
- return m.Open(path)
-}
-
-// WriteStream returns a writer for the file content.
-func (m *Fs) WriteStream(path string) Result {
- return m.Create(path)
-}
-
-// ReadAll reads all bytes from a ReadCloser and closes it.
-// Wraps io.ReadAll so consumers don't import "io".
-//
-// r := fs.ReadStream(path)
-// data := core.ReadAll(r.Value)
-func ReadAll(reader any) Result {
- rc, ok := reader.(io.Reader)
- if !ok {
- return Result{E("core.ReadAll", "not a reader", nil), false}
- }
- data, err := io.ReadAll(rc)
- if closer, ok := reader.(io.Closer); ok {
- closer.Close()
- }
- if err != nil {
- return Result{err, false}
- }
- return Result{string(data), true}
-}
-
-// WriteAll writes content to a writer and closes it if it implements Closer.
-//
-// r := fs.WriteStream(path)
-// core.WriteAll(r.Value, "content")
-func WriteAll(writer any, content string) Result {
- wc, ok := writer.(io.Writer)
- if !ok {
- return Result{E("core.WriteAll", "not a writer", nil), false}
- }
- _, err := wc.Write([]byte(content))
- var closeErr error
- if closer, ok := writer.(io.Closer); ok {
- closeErr = closer.Close()
- }
- if err != nil {
- return Result{err, false}
- }
- if closeErr != nil {
- return Result{closeErr, false}
- }
- return Result{OK: true}
-}
-
-func (m *Fs) isProtectedPath(full string) bool {
- if full == "/" {
- return true
- }
- home, err := os.UserHomeDir()
- if err != nil || home == "" {
- return false
- }
- return full == home
-}
-
-// CloseStream closes any value that implements io.Closer.
-//
-// core.CloseStream(r.Value)
-func CloseStream(v any) {
- if closer, ok := v.(io.Closer); ok {
- closer.Close()
- }
-}
-
-// Delete removes a file or empty directory.
-func (m *Fs) Delete(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if m.isProtectedPath(full) {
- return Result{E("fs.Delete", Concat("refusing to delete protected path: ", full), nil), false}
- }
- if err := os.Remove(full); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// DeleteAll removes a file or directory recursively.
-func (m *Fs) DeleteAll(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if m.isProtectedPath(full) {
- return Result{E("fs.DeleteAll", Concat("refusing to delete protected path: ", full), nil), false}
- }
- if err := os.RemoveAll(full); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// Rename moves a file or directory.
-func (m *Fs) Rename(oldPath, newPath string) Result {
- oldVp := m.validatePath(oldPath)
- if !oldVp.OK {
- return oldVp
- }
- newVp := m.validatePath(newPath)
- if !newVp.OK {
- return newVp
- }
- if err := os.Rename(oldVp.Value.(string), newVp.Value.(string)); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
diff --git a/.core/reference/i18n.go b/.core/reference/i18n.go
deleted file mode 100644
index 27b11de3..00000000
--- a/.core/reference/i18n.go
+++ /dev/null
@@ -1,140 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Internationalisation for the Core framework.
-// I18n collects locale mounts from services and delegates
-// translation to a registered Translator implementation (e.g., go-i18n).
-
-package core
-
-import (
- "sync"
-)
-
-// Translator defines the interface for translation services.
-// Implemented by go-i18n's Srv.
-type Translator interface {
- // Translate translates a message by its ID with optional arguments.
- Translate(messageID string, args ...any) Result
- // SetLanguage sets the active language (BCP47 tag, e.g., "en-GB", "de").
- SetLanguage(lang string) error
- // Language returns the current language code.
- Language() string
- // AvailableLanguages returns all loaded language codes.
- AvailableLanguages() []string
-}
-
-// LocaleProvider is implemented by services that ship their own translation files.
-// Core discovers this interface during service registration and collects the
-// locale mounts. The i18n service loads them during startup.
-//
-// Usage in a service package:
-//
-// //go:embed locales
-// var localeFS embed.FS
-//
-// func (s *MyService) Locales() *Embed {
-// m, _ := Mount(localeFS, "locales")
-// return m
-// }
-type LocaleProvider interface {
- Locales() *Embed
-}
-
-// I18n manages locale collection and translation dispatch.
-type I18n struct {
- mu sync.RWMutex
- locales []*Embed // collected from LocaleProvider services
- locale string
- translator Translator // registered implementation (nil until set)
-}
-
-// AddLocales adds locale mounts (called during service registration).
-func (i *I18n) AddLocales(mounts ...*Embed) {
- i.mu.Lock()
- i.locales = append(i.locales, mounts...)
- i.mu.Unlock()
-}
-
-// Locales returns all collected locale mounts.
-func (i *I18n) Locales() Result {
- i.mu.RLock()
- out := make([]*Embed, len(i.locales))
- copy(out, i.locales)
- i.mu.RUnlock()
- return Result{out, true}
-}
-
-// SetTranslator registers the translation implementation.
-// Called by go-i18n's Srv during startup.
-func (i *I18n) SetTranslator(t Translator) {
- i.mu.Lock()
- i.translator = t
- locale := i.locale
- i.mu.Unlock()
- if t != nil && locale != "" {
- if result := t.SetLanguage(locale); !result.OK {
- return
- }
- }
-}
-
-// Translator returns the registered translation implementation, or nil.
-func (i *I18n) Translator() Result {
- i.mu.RLock()
- t := i.translator
- i.mu.RUnlock()
- if t == nil {
- return Result{}
- }
- return Result{t, true}
-}
-
-// Translate translates a message. Returns the key as-is if no translator is registered.
-func (i *I18n) Translate(messageID string, args ...any) Result {
- i.mu.RLock()
- t := i.translator
- i.mu.RUnlock()
- if t != nil {
- return t.Translate(messageID, args...)
- }
- return Result{messageID, true}
-}
-
-// SetLanguage sets the active language and forwards to the translator if registered.
-func (i *I18n) SetLanguage(lang string) Result {
- if lang == "" {
- return Result{OK: true}
- }
- i.mu.Lock()
- i.locale = lang
- t := i.translator
- i.mu.Unlock()
- if t != nil {
- if err := t.SetLanguage(lang); err != nil {
- return Result{err, false}
- }
- }
- return Result{OK: true}
-}
-
-// Language returns the current language code, or "en" if not set.
-func (i *I18n) Language() string {
- i.mu.RLock()
- locale := i.locale
- i.mu.RUnlock()
- if locale != "" {
- return locale
- }
- return "en"
-}
-
-// AvailableLanguages returns all loaded language codes.
-func (i *I18n) AvailableLanguages() []string {
- i.mu.RLock()
- t := i.translator
- i.mu.RUnlock()
- if t != nil {
- return t.AvailableLanguages()
- }
- return []string{"en"}
-}
diff --git a/.core/reference/ipc.go b/.core/reference/ipc.go
deleted file mode 100644
index c5357e7b..00000000
--- a/.core/reference/ipc.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Message bus for the Core framework.
-// Dispatches actions (fire-and-forget), queries (first responder),
-// and tasks (first executor) between registered handlers.
-
-package core
-
-import (
- "slices"
- "sync"
-)
-
-// Ipc holds IPC dispatch data and the named action registry.
-//
-// ipc := (&core.Ipc{}).New()
-type Ipc struct {
- ipcMu sync.RWMutex
- ipcHandlers []func(*Core, Message) Result
-
- queryMu sync.RWMutex
- queryHandlers []QueryHandler
-
- actions *Registry[*Action] // named action registry
- tasks *Registry[*Task] // named task registry
-}
-
-// broadcast dispatches a message to all registered IPC handlers.
-// Each handler is wrapped in panic recovery. All handlers fire regardless of individual results.
-func (c *Core) broadcast(msg Message) Result {
- c.ipc.ipcMu.RLock()
- handlers := slices.Clone(c.ipc.ipcHandlers)
- c.ipc.ipcMu.RUnlock()
-
- for _, h := range handlers {
- func() {
- defer func() {
- if r := recover(); r != nil {
- Error("ACTION handler panicked", "panic", r)
- }
- }()
- h(c, msg)
- }()
- }
- return Result{OK: true}
-}
-
-// Query dispatches a request — first handler to return OK wins.
-//
-// r := c.Query(MyQuery{})
-func (c *Core) Query(q Query) Result {
- c.ipc.queryMu.RLock()
- handlers := slices.Clone(c.ipc.queryHandlers)
- c.ipc.queryMu.RUnlock()
-
- for _, h := range handlers {
- r := h(c, q)
- if r.OK {
- return r
- }
- }
- return Result{}
-}
-
-// QueryAll dispatches a request — collects all OK responses.
-//
-// r := c.QueryAll(countQuery{})
-// results := r.Value.([]any)
-func (c *Core) QueryAll(q Query) Result {
- c.ipc.queryMu.RLock()
- handlers := slices.Clone(c.ipc.queryHandlers)
- c.ipc.queryMu.RUnlock()
-
- var results []any
- for _, h := range handlers {
- r := h(c, q)
- if r.OK && r.Value != nil {
- results = append(results, r.Value)
- }
- }
- return Result{results, true}
-}
-
-// RegisterQuery registers a handler for QUERY dispatch.
-//
-// c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result { ... })
-func (c *Core) RegisterQuery(handler QueryHandler) {
- c.ipc.queryMu.Lock()
- c.ipc.queryHandlers = append(c.ipc.queryHandlers, handler)
- c.ipc.queryMu.Unlock()
-}
-
-// --- IPC Registration (handlers) ---
-
-// RegisterAction registers a broadcast handler for ACTION messages.
-//
-// c.RegisterAction(func(c *core.Core, msg core.Message) core.Result {
-// if ev, ok := msg.(AgentCompleted); ok { ... }
-// return core.Result{OK: true}
-// })
-func (c *Core) RegisterAction(handler func(*Core, Message) Result) {
- c.ipc.ipcMu.Lock()
- c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handler)
- c.ipc.ipcMu.Unlock()
-}
-
-// RegisterActions registers multiple broadcast handlers.
-func (c *Core) RegisterActions(handlers ...func(*Core, Message) Result) {
- c.ipc.ipcMu.Lock()
- c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handlers...)
- c.ipc.ipcMu.Unlock()
-}
diff --git a/.core/reference/lock.go b/.core/reference/lock.go
deleted file mode 100644
index a9632782..00000000
--- a/.core/reference/lock.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Synchronisation, locking, and lifecycle snapshots for the Core framework.
-
-package core
-
-import (
- "sync"
-)
-
-// Lock is the DTO for a named mutex.
-type Lock struct {
- Name string
- Mutex *sync.RWMutex
- locks *Registry[*sync.RWMutex] // per-Core named mutexes
-}
-
-// Lock returns a named Lock, creating the mutex if needed.
-// Locks are per-Core — separate Core instances do not share mutexes.
-func (c *Core) Lock(name string) *Lock {
- r := c.lock.locks.Get(name)
- if r.OK {
- return &Lock{Name: name, Mutex: r.Value.(*sync.RWMutex)}
- }
- m := &sync.RWMutex{}
- c.lock.locks.Set(name, m)
- return &Lock{Name: name, Mutex: m}
-}
-
-// LockEnable marks that the service lock should be applied after initialisation.
-func (c *Core) LockEnable(name ...string) {
- c.services.lockEnabled = true
-}
-
-// LockApply activates the service lock if it was enabled.
-func (c *Core) LockApply(name ...string) {
- if c.services.lockEnabled {
- c.services.Lock()
- }
-}
-
-// Startables returns services that have an OnStart function, in registration order.
-func (c *Core) Startables() Result {
- if c.services == nil {
- return Result{}
- }
- var out []*Service
- c.services.Each(func(_ string, svc *Service) {
- if svc.OnStart != nil {
- out = append(out, svc)
- }
- })
- return Result{out, true}
-}
-
-// Stoppables returns services that have an OnStop function, in registration order.
-func (c *Core) Stoppables() Result {
- if c.services == nil {
- return Result{}
- }
- var out []*Service
- c.services.Each(func(_ string, svc *Service) {
- if svc.OnStop != nil {
- out = append(out, svc)
- }
- })
- return Result{out, true}
-}
diff --git a/.core/reference/log.go b/.core/reference/log.go
deleted file mode 100644
index 2c523f99..00000000
--- a/.core/reference/log.go
+++ /dev/null
@@ -1,407 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Structured logging for the Core framework.
-//
-// core.SetLevel(core.LevelDebug)
-// core.Info("server started", "port", 8080)
-// core.Error("failed to connect", "err", err)
-// log := core.NewLog(core.LogOptions{Level: core.LevelDebug, Output: os.Stdout})
-// core.SetRedactKeys("token", "password")
-// core.Security("entitlement.denied", "action", "process.run")
-package core
-
-import (
- coreos "dappco.re/go"
- goio "io"
- "os/user"
- "slices"
- "sync"
- "sync/atomic"
- "time"
-)
-
-// Level defines logging verbosity.
-type Level int
-
-// Logging level constants ordered by increasing verbosity.
-const (
- // LevelQuiet suppresses all log output.
- LevelQuiet Level = iota
- // LevelError shows only error messages.
- LevelError
- // LevelWarn shows warnings and errors.
- LevelWarn
- // LevelInfo shows informational messages, warnings, and errors.
- LevelInfo
- // LevelDebug shows all messages including debug details.
- LevelDebug
-)
-
-// String returns the level name.
-func (l Level) String() string {
- switch l {
- case LevelQuiet:
- return "quiet"
- case LevelError:
- return "error"
- case LevelWarn:
- return "warn"
- case LevelInfo:
- return "info"
- case LevelDebug:
- return "debug"
- default:
- return "unknown"
- }
-}
-
-// Log provides structured logging.
-type Log struct {
- mu sync.RWMutex
- level Level
- output goio.Writer
-
- // RedactKeys is a list of keys whose values should be masked in logs.
- redactKeys []string
-
- // Style functions for formatting (can be overridden)
- StyleTimestamp func(string) string
- StyleDebug func(string) string
- StyleInfo func(string) string
- StyleWarn func(string) string
- StyleError func(string) string
- StyleSecurity func(string) string
-}
-
-// RotationLogOptions defines the log rotation and retention policy.
-type RotationLogOptions struct {
- // Filename is the log file path. If empty, rotation is disabled.
- Filename string
-
- // MaxSize is the maximum size of the log file in megabytes before it gets rotated.
- // It defaults to 100 megabytes.
- MaxSize int
-
- // MaxAge is the maximum number of days to retain old log files based on their
- // file modification time. It defaults to 28 days.
- // Note: set to a negative value to disable age-based retention.
- MaxAge int
-
- // MaxBackups is the maximum number of old log files to retain.
- // It defaults to 5 backups.
- MaxBackups int
-
- // Compress determines if the rotated log files should be compressed using gzip.
- // It defaults to true.
- Compress bool
-}
-
-// LogOptions configures a Log.
-type LogOptions struct {
- Level Level
- // Output is the destination for log messages. If Rotation is provided,
- // Output is ignored and logs are written to the rotating file instead.
- Output goio.Writer
- // Rotation enables log rotation to file. If provided, Filename must be set.
- Rotation *RotationLogOptions
- // RedactKeys is a list of keys whose values should be masked in logs.
- RedactKeys []string
-}
-
-// RotationWriterFactory creates a rotating writer from options.
-// Set this to enable log rotation (provided by core/go-io integration).
-var RotationWriterFactory func(RotationLogOptions) goio.WriteCloser
-
-// New creates a new Log with the given options.
-func NewLog(opts LogOptions) *Log {
- output := opts.Output
- if opts.Rotation != nil && opts.Rotation.Filename != "" && RotationWriterFactory != nil {
- output = RotationWriterFactory(*opts.Rotation)
- }
- if output == nil {
- output = os.Stderr
- }
-
- return &Log{
- level: opts.Level,
- output: output,
- redactKeys: slices.Clone(opts.RedactKeys),
- StyleTimestamp: identity,
- StyleDebug: identity,
- StyleInfo: identity,
- StyleWarn: identity,
- StyleError: identity,
- StyleSecurity: identity,
- }
-}
-
-func identity(s string) string { return s }
-
-// SetLevel changes the log level.
-func (l *Log) SetLevel(level Level) {
- l.mu.Lock()
- l.level = level
- l.mu.Unlock()
-}
-
-// Level returns the current log level.
-func (l *Log) Level() Level {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return l.level
-}
-
-// SetOutput changes the output writer.
-func (l *Log) SetOutput(w goio.Writer) {
- l.mu.Lock()
- l.output = w
- l.mu.Unlock()
-}
-
-// SetRedactKeys sets the keys to be redacted.
-func (l *Log) SetRedactKeys(keys ...string) {
- l.mu.Lock()
- l.redactKeys = slices.Clone(keys)
- l.mu.Unlock()
-}
-
-func (l *Log) shouldLog(level Level) bool {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return level <= l.level
-}
-
-func (l *Log) log(level Level, prefix, msg string, keyvals ...any) {
- l.mu.RLock()
- output := l.output
- styleTimestamp := l.StyleTimestamp
- redactKeys := l.redactKeys
- l.mu.RUnlock()
-
- timestamp := styleTimestamp(time.Now().Format("15:04:05"))
-
- // Copy keyvals to avoid mutating the caller's slice
- keyvals = append([]any(nil), keyvals...)
-
- // Automatically extract context from error if present in keyvals
- origLen := len(keyvals)
- for i := 0; i < origLen; i += 2 {
- if i+1 < origLen {
- if err, ok := keyvals[i+1].(error); ok {
- if op := Operation(err); op != "" {
- // Check if op is already in keyvals
- hasOp := false
- for j := 0; j < len(keyvals); j += 2 {
- if k, ok := keyvals[j].(string); ok && k == "op" {
- hasOp = true
- break
- }
- }
- if !hasOp {
- keyvals = append(keyvals, "op", op)
- }
- }
- if stack := FormatStackTrace(err); stack != "" {
- // Check if stack is already in keyvals
- hasStack := false
- for j := 0; j < len(keyvals); j += 2 {
- if k, ok := keyvals[j].(string); ok && k == "stack" {
- hasStack = true
- break
- }
- }
- if !hasStack {
- keyvals = append(keyvals, "stack", stack)
- }
- }
- }
- }
- }
-
- // Format key-value pairs
- var kvStr string
- if len(keyvals) > 0 {
- kvStr = " "
- for i := 0; i < len(keyvals); i += 2 {
- if i > 0 {
- kvStr += " "
- }
- key := keyvals[i]
- var val any
- if i+1 < len(keyvals) {
- val = keyvals[i+1]
- }
-
- // Redaction logic
- keyStr := Sprint(key)
- if slices.Contains(redactKeys, keyStr) {
- val = "[REDACTED]"
- }
-
- // Secure formatting to prevent log injection
- if s, ok := val.(string); ok {
- kvStr += Sprintf("%v=%q", key, s)
- } else {
- kvStr += Sprintf("%v=%v", key, val)
- }
- }
- }
-
- Print(output, "%s %s %s%s", timestamp, prefix, msg, kvStr)
-}
-
-// Debug logs a debug message with optional key-value pairs.
-func (l *Log) Debug(msg string, keyvals ...any) {
- if l.shouldLog(LevelDebug) {
- l.log(LevelDebug, l.StyleDebug("[DBG]"), msg, keyvals...)
- }
-}
-
-// Info logs an info message with optional key-value pairs.
-func (l *Log) Info(msg string, keyvals ...any) {
- if l.shouldLog(LevelInfo) {
- l.log(LevelInfo, l.StyleInfo("[INF]"), msg, keyvals...)
- }
-}
-
-// Warn logs a warning message with optional key-value pairs.
-func (l *Log) Warn(msg string, keyvals ...any) {
- if l.shouldLog(LevelWarn) {
- l.log(LevelWarn, l.StyleWarn("[WRN]"), msg, keyvals...)
- }
-}
-
-// Error logs an error message with optional key-value pairs.
-func (l *Log) Error(msg string, keyvals ...any) {
- if l.shouldLog(LevelError) {
- l.log(LevelError, l.StyleError("[ERR]"), msg, keyvals...)
- }
-}
-
-// Security logs a security event with optional key-value pairs.
-// It uses LevelError to ensure security events are visible even in restrictive
-// log configurations.
-func (l *Log) Security(msg string, keyvals ...any) {
- if l.shouldLog(LevelError) {
- l.log(LevelError, l.StyleSecurity("[SEC]"), msg, keyvals...)
- }
-}
-
-// Username returns the current system username.
-// It uses os/user for reliability and falls back to environment variables.
-func Username() string {
- if u, err := user.Current(); err == nil {
- return u.Username
- }
- // Fallback for environments where user lookup might fail
- if u := os.Getenv("USER"); u != "" {
- return u
- }
- return os.Getenv("USERNAME")
-}
-
-// --- Default logger ---
-
-var defaultLogPtr atomic.Pointer[Log]
-
-func init() {
- l := NewLog(LogOptions{Level: LevelInfo})
- defaultLogPtr.Store(l)
-}
-
-// Default returns the default logger.
-func Default() *Log {
- return defaultLogPtr.Load()
-}
-
-// SetDefault sets the default logger.
-func SetDefault(l *Log) {
- defaultLogPtr.Store(l)
-}
-
-// SetLevel sets the default logger's level.
-func SetLevel(level Level) {
- Default().SetLevel(level)
-}
-
-// SetRedactKeys sets the default logger's redaction keys.
-func SetRedactKeys(keys ...string) {
- Default().SetRedactKeys(keys...)
-}
-
-// Debug logs to the default logger.
-func Debug(msg string, keyvals ...any) {
- Default().Debug(msg, keyvals...)
-}
-
-// Info logs to the default logger.
-func Info(msg string, keyvals ...any) {
- Default().Info(msg, keyvals...)
-}
-
-// Warn logs to the default logger.
-func Warn(msg string, keyvals ...any) {
- Default().Warn(msg, keyvals...)
-}
-
-// Error logs to the default logger.
-func Error(msg string, keyvals ...any) {
- Default().Error(msg, keyvals...)
-}
-
-// Security logs to the default logger.
-func Security(msg string, keyvals ...any) {
- Default().Security(msg, keyvals...)
-}
-
-// --- LogErr: Error-Aware Logger ---
-
-// LogErr logs structured information extracted from errors.
-// Primary action: log. Secondary: extract error context.
-type LogErr struct {
- log *Log
-}
-
-// NewLogErr creates a LogErr bound to the given logger.
-func NewLogErr(log *Log) *LogErr {
- return &LogErr{log: log}
-}
-
-// Log extracts context from an Err and logs it at Error level.
-func (le *LogErr) Log(err any) {
- if err == nil {
- return
- }
- le.log.Error(ErrorMessage(err), "op", Operation(err), "code", ErrorCode(err), "stack", FormatStackTrace(err))
-}
-
-// --- LogPanic: Panic-Aware Logger ---
-
-// LogPanic logs panic context without crash file management.
-// Primary action: log. Secondary: recover panics.
-type LogPanic struct {
- log *Log
-}
-
-// NewLogPanic creates a LogPanic bound to the given logger.
-func NewLogPanic(log *Log) *LogPanic {
- return &LogPanic{log: log}
-}
-
-// Recover captures a panic and logs it. Does not write crash files.
-// Use as: defer core.NewLogPanic(logger).Recover()
-func (lp *LogPanic) Recover() {
- r := recover()
- if r == nil {
- return
- }
- err, ok := r.(error)
- if !ok {
- err = NewError(Sprint("panic: ", r))
- }
- lp.log.Error("panic recovered",
- "err", err,
- "op", Operation(err),
- "stack", FormatStackTrace(err),
- )
-}
diff --git a/.core/reference/options.go b/.core/reference/options.go
deleted file mode 100644
index 9268a59b..00000000
--- a/.core/reference/options.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Core primitives: Option, Options, Result.
-//
-// Options is the universal input type. Result is the universal output type.
-// All Core operations accept Options and return Result.
-//
-// opts := core.NewOptions(
-// core.Option{Key: "name", Value: "brain"},
-// core.Option{Key: `path`, Value: "prompts"},
-// )
-// r := c.Drive().New(opts)
-// if !r.OK { log.Fatal(r.Error()) }
-package core
-
-// --- Result: Universal Output ---
-
-// Result is the universal return type for Core operations.
-// Replaces the (value, error) pattern — errors flow through Core internally.
-//
-// r := c.Data().New(opts)
-// if !r.OK { core.Error("failed", "err", r.Error()) }
-type Result struct {
- Value any
- OK bool
-}
-
-// Result gets or sets the value. Zero args returns Value. With args, maps
-// Go (value, error) pairs to Result and returns self.
-//
-// r.Result(file, err) // OK = err == nil, Value = file
-// r.Result(value) // OK = true, Value = value
-// r.Result() // after set — returns the value
-func (r Result) Result(args ...any) Result {
- if len(args) == 0 {
- return r
- }
- return r.New(args...)
-}
-
-// New adapts Go (value, error) pairs into a Result.
-//
-// r := core.Result{}.New(file, err)
-func (r Result) New(args ...any) Result {
- if len(args) == 0 {
- return r
- }
-
- if len(args) > 1 {
- if err, ok := args[len(args)-1].(error); ok {
- if err != nil {
- return Result{Value: err, OK: false}
- }
- r.Value = args[0]
- r.OK = true
- return r
- }
- }
-
- r.Value = args[0]
-
- if err, ok := r.Value.(error); ok {
- if err != nil {
- return Result{Value: err, OK: false}
- }
- return Result{OK: true}
- }
-
- r.OK = true
- return r
-}
-
-// Get returns the Result if OK, empty Result otherwise.
-//
-// r := core.Result{Value: "hello", OK: true}.Get()
-func (r Result) Get() Result {
- if r.OK {
- return r
- }
- return Result{Value: r.Value, OK: false}
-}
-
-// Option is a single key-value configuration pair.
-//
-// core.Option{Key: "name", Value: "brain"}
-// core.Option{Key: "port", Value: 8080}
-type Option struct {
- Key string
- Value any
-}
-
-// --- Options: Universal Input ---
-
-// Options is the universal input type for Core operations.
-// A structured collection of key-value pairs with typed accessors.
-//
-// opts := core.NewOptions(
-// core.Option{Key: "name", Value: "myapp"},
-// core.Option{Key: "port", Value: 8080},
-// )
-// name := opts.String("name")
-type Options struct {
- items []Option
-}
-
-// NewOptions creates an Options collection from key-value pairs.
-//
-// opts := core.NewOptions(
-// core.Option{Key: "name", Value: "brain"},
-// core.Option{Key: `path`, Value: "prompts"},
-// )
-func NewOptions(items ...Option) Options {
- cp := make([]Option, len(items))
- copy(cp, items)
- return Options{items: cp}
-}
-
-// Set adds or updates a key-value pair.
-//
-// opts.Set("port", 8080)
-func (o *Options) Set(key string, value any) {
- for i, opt := range o.items {
- if opt.Key == key {
- o.items[i].Value = value
- return
- }
- }
- o.items = append(o.items, Option{Key: key, Value: value})
-}
-
-// Get retrieves a value by key.
-//
-// r := opts.Get("name")
-// if r.OK { name := r.Value.(string) }
-func (o Options) Get(key string) Result {
- for _, opt := range o.items {
- if opt.Key == key {
- return Result{opt.Value, true}
- }
- }
- return Result{}
-}
-
-// Has returns true if a key exists.
-//
-// if opts.Has("debug") { ... }
-func (o Options) Has(key string) bool {
- return o.Get(key).OK
-}
-
-// String retrieves a string value, empty string if missing.
-//
-// name := opts.String("name")
-func (o Options) String(key string) string {
- r := o.Get(key)
- if !r.OK {
- return ""
- }
- s, _ := r.Value.(string)
- return s
-}
-
-// Int retrieves an int value, 0 if missing.
-//
-// port := opts.Int("port")
-func (o Options) Int(key string) int {
- r := o.Get(key)
- if !r.OK {
- return 0
- }
- i, _ := r.Value.(int)
- return i
-}
-
-// Bool retrieves a bool value, false if missing.
-//
-// debug := opts.Bool("debug")
-func (o Options) Bool(key string) bool {
- r := o.Get(key)
- if !r.OK {
- return false
- }
- b, _ := r.Value.(bool)
- return b
-}
-
-// Len returns the number of options.
-func (o Options) Len() int {
- return len(o.items)
-}
-
-// Items returns a copy of the underlying option slice.
-func (o Options) Items() []Option {
- cp := make([]Option, len(o.items))
- copy(cp, o.items)
- return cp
-}
diff --git a/.core/reference/runtime.go b/.core/reference/runtime.go
deleted file mode 100644
index 9074a39c..00000000
--- a/.core/reference/runtime.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Runtime helpers for the Core framework.
-// ServiceRuntime is embedded by consumer services.
-// Runtime is the GUI binding container (e.g., Wails).
-//
-// r := c.ServiceStartup(context.Background(), nil)
-// r := core.NewRuntime(app)
-// name := runtime.ServiceName()
-
-package core
-
-import (
- "context"
- "maps"
- "slices"
-)
-
-// --- ServiceRuntime (embedded by consumer services) ---
-
-// ServiceRuntime is embedded in services to provide access to the Core and typed options.
-type ServiceRuntime[T any] struct {
- core *Core
- opts T
-}
-
-// NewServiceRuntime creates a ServiceRuntime for a service constructor.
-func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T] {
- return &ServiceRuntime[T]{core: c, opts: opts}
-}
-
-// Core returns the Core instance this service is registered with.
-//
-// c := s.Core()
-func (r *ServiceRuntime[T]) Core() *Core { return r.core }
-
-// Options returns the typed options this service was created with.
-//
-// opts := s.Options() // MyOptions{BufferSize: 1024, ...}
-func (r *ServiceRuntime[T]) Options() T { return r.opts }
-
-// Config is a shortcut to s.Core().Config().
-//
-// host := s.Config().String("database.host")
-func (r *ServiceRuntime[T]) Config() *Config { return r.core.Config() }
-
-// --- Lifecycle ---
-
-// ServiceStartup runs OnStart for all registered services that have one.
-func (c *Core) ServiceStartup(ctx context.Context, options any) Result {
- c.shutdown.Store(false)
- c.context, c.cancel = context.WithCancel(ctx)
- startables := c.Startables()
- if startables.OK {
- for _, s := range startables.Value.([]*Service) {
- if err := ctx.Err(); err != nil {
- return Result{err, false}
- }
- r := s.OnStart()
- if !r.OK {
- return r
- }
- }
- }
- c.ACTION(ActionServiceStartup{})
- return Result{OK: true}
-}
-
-// ServiceShutdown drains background tasks, then stops all registered services.
-func (c *Core) ServiceShutdown(ctx context.Context) Result {
- c.shutdown.Store(true)
- c.cancel() // signal all context-aware tasks to stop
- c.ACTION(ActionServiceShutdown{})
-
- // Drain background tasks before stopping services.
- done := make(chan struct{})
- go func() {
- c.waitGroup.Wait()
- close(done)
- }()
- select {
- case <-done:
- case <-ctx.Done():
- return Result{ctx.Err(), false}
- }
-
- // Stop services
- var firstErr error
- stoppables := c.Stoppables()
- if stoppables.OK {
- for _, s := range stoppables.Value.([]*Service) {
- if err := ctx.Err(); err != nil {
- return Result{err, false}
- }
- r := s.OnStop()
- if !r.OK && firstErr == nil {
- if e, ok := r.Value.(error); ok {
- firstErr = e
- } else {
- firstErr = E("core.ServiceShutdown", Sprint("service OnStop failed: ", r.Value), nil)
- }
- }
- }
- }
- if firstErr != nil {
- return Result{firstErr, false}
- }
- return Result{OK: true}
-}
-
-// --- Runtime DTO (GUI binding) ---
-
-// Runtime is the container for GUI runtimes (e.g., Wails).
-type Runtime struct {
- app any
- Core *Core
-}
-
-// ServiceFactory defines a function that creates a Service.
-type ServiceFactory func() Result
-
-// NewWithFactories creates a Runtime with the provided service factories.
-func NewWithFactories(app any, factories map[string]ServiceFactory) Result {
- c := New(WithOptions(NewOptions(Option{Key: "name", Value: "core"})))
- c.app.Runtime = app
-
- names := slices.Sorted(maps.Keys(factories))
- for _, name := range names {
- factory := factories[name]
- if factory == nil {
- continue
- }
- r := factory()
- if !r.OK {
- cause, _ := r.Value.(error)
- return Result{E("core.NewWithFactories", Concat("factory \"", name, "\" failed"), cause), false}
- }
- svc, ok := r.Value.(Service)
- if !ok {
- return Result{E("core.NewWithFactories", Concat("factory \"", name, "\" returned non-Service type"), nil), false}
- }
- sr := c.Service(name, svc)
- if !sr.OK {
- return sr
- }
- }
- return Result{&Runtime{app: app, Core: c}, true}
-}
-
-// NewRuntime creates a Runtime with no custom services.
-func NewRuntime(app any) Result {
- return NewWithFactories(app, map[string]ServiceFactory{})
-}
-
-// ServiceName returns "Core" — the Runtime's service identity.
-func (r *Runtime) ServiceName() string { return "Core" }
-
-// ServiceStartup starts all services via the embedded Core.
-func (r *Runtime) ServiceStartup(ctx context.Context, options any) Result {
- if r == nil || r.Core == nil {
- return Result{OK: true}
- }
- return r.Core.ServiceStartup(ctx, options)
-}
-
-// ServiceShutdown stops all services via the embedded Core.
-func (r *Runtime) ServiceShutdown(ctx context.Context) Result {
- if r == nil || r.Core == nil {
- return Result{OK: true}
- }
- return r.Core.ServiceShutdown(ctx)
-}
diff --git a/.core/reference/service.go b/.core/reference/service.go
deleted file mode 100644
index 46738add..00000000
--- a/.core/reference/service.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Service registry for the Core framework.
-//
-// Register a service (DTO with lifecycle hooks):
-//
-// c.Service("auth", core.Service{OnStart: startFn})
-//
-// Register a service instance (auto-discovers Startable/Stoppable/HandleIPCEvents):
-//
-// c.RegisterService("display", displayInstance)
-//
-// Get a service:
-//
-// r := c.Service("auth")
-// if r.OK { svc := r.Value }
-
-package core
-
-import "context"
-
-// Service is a managed component with optional lifecycle.
-type Service struct {
- Name string
- Instance any // the raw service instance (for interface discovery)
- Options Options
- OnStart func() Result
- OnStop func() Result
- OnReload func() Result
-}
-
-// ServiceRegistry holds registered services. Embeds Registry[*Service]
-// for thread-safe named storage with insertion order.
-type ServiceRegistry struct {
- *Registry[*Service]
- lockEnabled bool
-}
-
-// --- Core service methods ---
-
-// Service gets or registers a service by name.
-//
-// c.Service("auth", core.Service{OnStart: startFn})
-// r := c.Service("auth")
-func (c *Core) Service(name string, service ...Service) Result {
- if len(service) == 0 {
- r := c.services.Get(name)
- if !r.OK {
- return Result{}
- }
- svc := r.Value.(*Service)
- // Return the instance if available, otherwise the Service DTO
- if svc.Instance != nil {
- return Result{svc.Instance, true}
- }
- return Result{svc, true}
- }
-
- if name == "" {
- return Result{E("core.Service", "service name cannot be empty", nil), false}
- }
-
- if c.services.Locked() {
- return Result{E("core.Service", Concat("service \"", name, "\" not permitted — registry locked"), nil), false}
- }
- if c.services.Has(name) {
- return Result{E("core.Service", Join(" ", "service", name, "already registered"), nil), false}
- }
-
- srv := &service[0]
- srv.Name = name
- return c.services.Set(name, srv)
-}
-
-// RegisterService registers a service instance by name.
-// Auto-discovers Startable, Stoppable, and HandleIPCEvents interfaces
-// on the instance and wires them into the lifecycle and IPC bus.
-//
-// c.RegisterService("display", displayInstance)
-func (c *Core) RegisterService(name string, instance any) Result {
- if name == "" {
- return Result{E("core.RegisterService", "service name cannot be empty", nil), false}
- }
-
- if c.services.Locked() {
- return Result{E("core.RegisterService", Concat("service \"", name, "\" not permitted — registry locked"), nil), false}
- }
- if c.services.Has(name) {
- return Result{E("core.RegisterService", Join(" ", "service", name, "already registered"), nil), false}
- }
-
- srv := &Service{Name: name, Instance: instance}
-
- // Auto-discover lifecycle interfaces
- if s, ok := instance.(Startable); ok {
- srv.OnStart = func() Result {
- return s.OnStartup(c.context)
- }
- }
- if s, ok := instance.(Stoppable); ok {
- srv.OnStop = func() Result {
- return s.OnShutdown(context.Background())
- }
- }
-
- c.services.Set(name, srv)
-
- // Auto-discover IPC handler
- if handler, ok := instance.(interface {
- HandleIPCEvents(*Core, Message) Result
- }); ok {
- c.ipc.ipcMu.Lock()
- c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handler.HandleIPCEvents)
- c.ipc.ipcMu.Unlock()
- }
-
- return Result{OK: true}
-}
-
-// ServiceFor retrieves a registered service by name and asserts its type.
-//
-// prep, ok := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic")
-func ServiceFor[T any](c *Core, name string) (T, bool) {
- var zero T
- r := c.Service(name)
- if !r.OK {
- return zero, false
- }
- typed, ok := r.Value.(T)
- return typed, ok
-}
-
-// MustServiceFor retrieves a registered service by name and asserts its type.
-// Panics if the service is not found or the type assertion fails.
-//
-// cli := core.MustServiceFor[*Cli](c, "cli")
-func MustServiceFor[T any](c *Core, name string) T {
- v, ok := ServiceFor[T](c, name)
- if !ok {
- panic(E("core.MustServiceFor", Sprintf("service %q not found or wrong type", name), nil))
- }
- return v
-}
-
-// Services returns all registered service names in registration order.
-//
-// names := c.Services()
-func (c *Core) Services() []string {
- if c.services == nil {
- return nil
- }
- return c.services.Names()
-}
diff --git a/.core/reference/string.go b/.core/reference/string.go
deleted file mode 100644
index fb4f3e5a..00000000
--- a/.core/reference/string.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// String operations for the Core framework.
-// Provides safe, predictable string helpers that downstream packages
-// use directly — same pattern as Array[T] for slices.
-
-package core
-
-import (
- corefmt "dappco.re/go"
- corestrings "dappco.re/go"
- "unicode/utf8"
-)
-
-// HasPrefix returns true if s starts with prefix.
-//
-// core.HasPrefix("--verbose", "--") // true
-func HasPrefix(s, prefix string) bool {
- return strings.HasPrefix(s, prefix)
-}
-
-// HasSuffix returns true if s ends with suffix.
-//
-// core.HasSuffix("test.go", ".go") // true
-func HasSuffix(s, suffix string) bool {
- return strings.HasSuffix(s, suffix)
-}
-
-// TrimPrefix removes prefix from s.
-//
-// core.TrimPrefix("--verbose", "--") // "verbose"
-func TrimPrefix(s, prefix string) string {
- return strings.TrimPrefix(s, prefix)
-}
-
-// TrimSuffix removes suffix from s.
-//
-// core.TrimSuffix("test.go", ".go") // "test"
-func TrimSuffix(s, suffix string) string {
- return strings.TrimSuffix(s, suffix)
-}
-
-// Contains returns true if s contains substr.
-//
-// core.Contains("hello world", "world") // true
-func Contains(s, substr string) bool {
- return strings.Contains(s, substr)
-}
-
-// Split splits s by separator.
-//
-// core.Split("a/b/c", "/") // ["a", "b", "c"]
-func Split(s, sep string) []string {
- return strings.Split(s, sep)
-}
-
-// SplitN splits s by separator into at most n parts.
-//
-// core.SplitN("key=value=extra", "=", 2) // ["key", "value=extra"]
-func SplitN(s, sep string, n int) []string {
- return strings.SplitN(s, sep, n)
-}
-
-// Join joins parts with a separator, building via Concat.
-//
-// core.Join("/", "deploy", "to", "homelab") // "deploy/to/homelab"
-// core.Join(".", "cmd", "deploy", "description") // "cmd.deploy.description"
-func Join(sep string, parts ...string) string {
- if len(parts) == 0 {
- return ""
- }
- result := parts[0]
- for _, p := range parts[1:] {
- result = Concat(result, sep, p)
- }
- return result
-}
-
-// Replace replaces all occurrences of old with new in s.
-//
-// core.Replace("deploy/to/homelab", "/", ".") // "deploy.to.homelab"
-func Replace(s, old, new string) string {
- return strings.ReplaceAll(s, old, new)
-}
-
-// Lower returns s in lowercase.
-//
-// core.Lower("HELLO") // "hello"
-func Lower(s string) string {
- return strings.ToLower(s)
-}
-
-// Upper returns s in uppercase.
-//
-// core.Upper("hello") // "HELLO"
-func Upper(s string) string {
- return strings.ToUpper(s)
-}
-
-// Trim removes leading and trailing whitespace.
-//
-// core.Trim(" hello ") // "hello"
-func Trim(s string) string {
- return strings.TrimSpace(s)
-}
-
-// RuneCount returns the number of runes (unicode characters) in s.
-//
-// core.RuneCount("hello") // 5
-// core.RuneCount("🔥") // 1
-func RuneCount(s string) int {
- return utf8.RuneCountInString(s)
-}
-
-// NewBuilder returns a new strings.Builder.
-//
-// b := core.NewBuilder()
-// b.WriteString("hello")
-// b.String() // "hello"
-func NewBuilder() *strings.Builder {
- return &strings.Builder{}
-}
-
-// NewReader returns a strings.NewReader for the given string.
-//
-// r := core.NewReader("hello world")
-func NewReader(s string) *strings.Reader {
- return strings.NewReader(s)
-}
-
-// Sprint converts any value to its string representation.
-//
-// core.Sprint(42) // "42"
-// core.Sprint(err) // "connection refused"
-func Sprint(args ...any) string {
- return fmt.Sprint(args...)
-}
-
-// Sprintf formats a string with the given arguments.
-//
-// core.Sprintf("%v=%q", "key", "value") // `key="value"`
-func Sprintf(format string, args ...any) string {
- return fmt.Sprintf(format, args...)
-}
-
-// Concat joins variadic string parts into one string.
-// Hook point for validation, sanitisation, and security checks.
-//
-// core.Concat("cmd.", "deploy.to.homelab", ".description")
-// core.Concat("https://", host, "/api/v1")
-func Concat(parts ...string) string {
- b := NewBuilder()
- for _, p := range parts {
- b.WriteString(p)
- }
- return b.String()
-}
diff --git a/.core/reference/task.go b/.core/reference/task.go
deleted file mode 100644
index b761f9d0..00000000
--- a/.core/reference/task.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Background action dispatch for the Core framework.
-// PerformAsync runs a named Action in a background goroutine with
-// panic recovery and progress broadcasting.
-
-package core
-
-import "context"
-
-// PerformAsync dispatches a named action in a background goroutine.
-// Broadcasts ActionTaskStarted, ActionTaskProgress, and ActionTaskCompleted
-// as IPC messages so other services can track progress.
-//
-// r := c.PerformAsync("agentic.dispatch", opts)
-// taskID := r.Value.(string)
-func (c *Core) PerformAsync(action string, opts Options) Result {
- if c.shutdown.Load() {
- return Result{}
- }
- taskID := ID()
-
- c.ACTION(ActionTaskStarted{TaskIdentifier: taskID, Action: action, Options: opts})
-
- c.waitGroup.Go(func() {
- defer func() {
- if rec := recover(); rec != nil {
- c.ACTION(ActionTaskCompleted{
- TaskIdentifier: taskID,
- Action: action,
- Result: Result{E("core.PerformAsync", Sprint("panic: ", rec), nil), false},
- })
- }
- }()
-
- r := c.Action(action).Run(context.Background(), opts)
-
- c.ACTION(ActionTaskCompleted{
- TaskIdentifier: taskID,
- Action: action,
- Result: r,
- })
- })
-
- return Result{taskID, true}
-}
-
-// Progress broadcasts a progress update for a background task.
-//
-// c.Progress(taskID, 0.5, "halfway done", "agentic.dispatch")
-func (c *Core) Progress(taskID string, progress float64, message string, action string) {
- c.ACTION(ActionTaskProgress{
- TaskIdentifier: taskID,
- Action: action,
- Progress: progress,
- Message: message,
- })
-}
-
-// Registration methods (RegisterAction, RegisterActions)
-// are in ipc.go — registration is IPC's responsibility.
diff --git a/.core/reference/utils.go b/.core/reference/utils.go
deleted file mode 100644
index ff8f2629..00000000
--- a/.core/reference/utils.go
+++ /dev/null
@@ -1,223 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Utility functions for the Core framework.
-// Built on core string.go primitives.
-
-package core
-
-import (
- crand "crypto/rand"
- corefmt "dappco.re/go"
- coreos "dappco.re/go"
- "encoding/hex"
- "io"
- "strconv"
- "sync/atomic"
-)
-
-// --- ID Generation ---
-
-var idCounter atomic.Uint64
-
-// ID returns a unique identifier. Format: "id-{counter}-{random}".
-// Counter is process-wide atomic. Random suffix prevents collision across restarts.
-//
-// id := core.ID() // "id-1-a3f2b1"
-// id2 := core.ID() // "id-2-c7e4d9"
-func ID() string {
- return Concat("id-", strconv.FormatUint(idCounter.Add(1), 10), "-", shortRand())
-}
-
-func shortRand() string {
- b := make([]byte, 3)
- crand.Read(b)
- return hex.EncodeToString(b)
-}
-
-// --- Validation ---
-
-// ValidateName checks that a string is a valid service/action/command name.
-// Rejects empty, ".", "..", and names containing path separators.
-//
-// r := core.ValidateName("brain") // Result{"brain", true}
-// r := core.ValidateName("") // Result{error, false}
-// r := core.ValidateName("../escape") // Result{error, false}
-func ValidateName(name string) Result {
- if name == "" || name == "." || name == ".." {
- return Result{E("validate", Concat("invalid name: ", name), nil), false}
- }
- if Contains(name, "/") || Contains(name, "\\") {
- return Result{E("validate", Concat("name contains path separator: ", name), nil), false}
- }
- return Result{name, true}
-}
-
-// SanitisePath extracts the base filename and rejects traversal attempts.
-// Returns "invalid" for dangerous inputs.
-//
-// core.SanitisePath("../../etc/passwd") // "passwd"
-// core.SanitisePath("") // "invalid"
-// core.SanitisePath("..") // "invalid"
-func SanitisePath(path string) string {
- safe := PathBase(path)
- if safe == "." || safe == ".." || safe == "" {
- return "invalid"
- }
- return safe
-}
-
-// --- I/O ---
-
-// Println prints values to stdout with a newline. Replaces fmt.Println.
-//
-// core.Println("hello", 42, true)
-func Println(args ...any) {
- fmt.Println(args...)
-}
-
-// Print writes a formatted line to a writer, defaulting to os.Stdout.
-//
-// core.Print(nil, "hello %s", "world") // → stdout
-// core.Print(w, "port: %d", 8080) // → w
-func Print(w io.Writer, format string, args ...any) {
- if w == nil {
- w = os.Stdout
- }
- fmt.Fprintf(w, format+"\n", args...)
-}
-
-// JoinPath joins string segments into a path with "/" separator.
-//
-// core.JoinPath("deploy", "to", "homelab") // → "deploy/to/homelab"
-func JoinPath(segments ...string) string {
- return Join("/", segments...)
-}
-
-// IsFlag returns true if the argument starts with a dash.
-//
-// core.IsFlag("--verbose") // true
-// core.IsFlag("-v") // true
-// core.IsFlag("deploy") // false
-func IsFlag(arg string) bool {
- return HasPrefix(arg, "-")
-}
-
-// Arg extracts a value from variadic args at the given index.
-// Type-checks and delegates to the appropriate typed extractor.
-// Returns Result — OK is false if index is out of bounds.
-//
-// r := core.Arg(0, args...)
-// if r.OK { path = r.Value.(string) }
-func Arg(index int, args ...any) Result {
- if index >= len(args) {
- return Result{}
- }
- v := args[index]
- switch v.(type) {
- case string:
- return Result{ArgString(index, args...), true}
- case int:
- return Result{ArgInt(index, args...), true}
- case bool:
- return Result{ArgBool(index, args...), true}
- default:
- return Result{v, true}
- }
-}
-
-// ArgString extracts a string at the given index.
-//
-// name := core.ArgString(0, args...)
-func ArgString(index int, args ...any) string {
- if index >= len(args) {
- return ""
- }
- s, ok := args[index].(string)
- if !ok {
- return ""
- }
- return s
-}
-
-// ArgInt extracts an int at the given index.
-//
-// port := core.ArgInt(1, args...)
-func ArgInt(index int, args ...any) int {
- if index >= len(args) {
- return 0
- }
- i, ok := args[index].(int)
- if !ok {
- return 0
- }
- return i
-}
-
-// ArgBool extracts a bool at the given index.
-//
-// debug := core.ArgBool(2, args...)
-func ArgBool(index int, args ...any) bool {
- if index >= len(args) {
- return false
- }
- b, ok := args[index].(bool)
- if !ok {
- return false
- }
- return b
-}
-
-// FilterArgs removes empty strings and Go test runner flags from an argument list.
-//
-// clean := core.FilterArgs(os.Args[1:])
-func FilterArgs(args []string) []string {
- var clean []string
- for _, a := range args {
- if a == "" || HasPrefix(a, "-test.") {
- continue
- }
- clean = append(clean, a)
- }
- return clean
-}
-
-// ParseFlag parses a single flag argument into key, value, and validity.
-// Single dash (-) requires exactly 1 character (letter, emoji, unicode).
-// Double dash (--) requires 2+ characters.
-//
-// "-v" → "v", "", true
-// "-🔥" → "🔥", "", true
-// "--verbose" → "verbose", "", true
-// "--port=8080" → "port", "8080", true
-// "-verbose" → "", "", false (single dash, 2+ chars)
-// "--v" → "", "", false (double dash, 1 char)
-// "hello" → "", "", false (not a flag)
-func ParseFlag(arg string) (key, value string, valid bool) {
- if HasPrefix(arg, "--") {
- rest := TrimPrefix(arg, "--")
- parts := SplitN(rest, "=", 2)
- name := parts[0]
- if RuneCount(name) < 2 {
- return "", "", false
- }
- if len(parts) == 2 {
- return name, parts[1], true
- }
- return name, "", true
- }
-
- if HasPrefix(arg, "-") {
- rest := TrimPrefix(arg, "-")
- parts := SplitN(rest, "=", 2)
- name := parts[0]
- if RuneCount(name) != 1 {
- return "", "", false
- }
- if len(parts) == 2 {
- return name, parts[1], true
- }
- return name, "", true
- }
-
- return "", "", false
-}
diff --git a/.deps/core-compat/core.go b/.deps/core-compat/core.go
deleted file mode 100644
index d37cc36e..00000000
--- a/.deps/core-compat/core.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package core
-
-import newcore "dappco.re/go"
-
-type Action = newcore.Action
-type ActionHandler = newcore.ActionHandler
-type App = newcore.App
-type AtomicPointer[T any] = newcore.AtomicPointer[T]
-type Cli = newcore.Cli
-type Command = newcore.Command
-type CommandAction = newcore.CommandAction
-type Core = newcore.Core
-type CoreOption = newcore.CoreOption
-type Embed = newcore.Embed
-type Fs = newcore.Fs
-type Lock = newcore.Lock
-type Log = newcore.Log
-type Message = newcore.Message
-type Mutex = newcore.Mutex
-type Once = newcore.Once
-type Option = newcore.Option
-type Options = newcore.Options
-type Process = newcore.Process
-type Query = newcore.Query
-type Registry[T any] = newcore.Registry[T]
-type Result = newcore.Result
-type RWMutex = newcore.RWMutex
-type Service = newcore.Service
-type ServiceRuntime[T any] = newcore.ServiceRuntime[T]
-type Startable = newcore.Startable
-type Stoppable = newcore.Stoppable
-type Translator = newcore.Translator
-
-var As = newcore.As
-var Atoi = newcore.Atoi
-var CleanPath = newcore.CleanPath
-var Concat = newcore.Concat
-var Contains = newcore.Contains
-var E = newcore.E
-var Env = newcore.Env
-var ErrorJoin = newcore.ErrorJoin
-var Exit = newcore.Exit
-var FormatInt = newcore.FormatInt
-var HasPrefix = newcore.HasPrefix
-var HasSuffix = newcore.HasSuffix
-var ID = newcore.ID
-var Itoa = newcore.Itoa
-var Is = newcore.Is
-var IsDigit = newcore.IsDigit
-var IsLetter = newcore.IsLetter
-var IsSpace = newcore.IsSpace
-var JSONMarshal = newcore.JSONMarshal
-var JSONMarshalString = newcore.JSONMarshalString
-var JSONUnmarshal = newcore.JSONUnmarshal
-var JSONUnmarshalString = newcore.JSONUnmarshalString
-var Join = newcore.Join
-var JoinPath = newcore.JoinPath
-var Lower = newcore.Lower
-var New = newcore.New
-var NewBuffer = newcore.NewBuffer
-var NewBuilder = newcore.NewBuilder
-var NewError = newcore.NewError
-var NewOptions = newcore.NewOptions
-var NewReader = newcore.NewReader
-var Operation = newcore.Operation
-var Path = newcore.Path
-var PathBase = newcore.PathBase
-var PathDir = newcore.PathDir
-var PathExt = newcore.PathExt
-var PathGlob = newcore.PathGlob
-var PathIsAbs = newcore.PathIsAbs
-var PathJoin = newcore.PathJoin
-var Print = newcore.Print
-var Println = newcore.Println
-var ReadAll = newcore.ReadAll
-var Replace = newcore.Replace
-var SHA256 = newcore.SHA256
-var Security = newcore.Security
-var Split = newcore.Split
-var SplitN = newcore.SplitN
-var Sprint = newcore.Sprint
-var Sprintf = newcore.Sprintf
-var ParseInt = newcore.ParseInt
-var Trim = newcore.Trim
-var TrimPrefix = newcore.TrimPrefix
-var TrimSuffix = newcore.TrimSuffix
-var Upper = newcore.Upper
-var ValidateName = newcore.ValidateName
-var URLParse = newcore.URLParse
-var URLEncode = newcore.URLEncode
-var Warn = newcore.Warn
-var WithName = newcore.WithName
-var WithOption = newcore.WithOption
-var WithService = newcore.WithService
-var Wrap = newcore.Wrap
-
-func NewRegistry[T any]() *Registry[T] {
- return newcore.NewRegistry[T]()
-}
-
-func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T] {
- return newcore.NewServiceRuntime(c, opts)
-}
diff --git a/.deps/core-compat/core_test.go b/.deps/core-compat/core_test.go
deleted file mode 100644
index af7d2d22..00000000
--- a/.deps/core-compat/core_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package core
-
-import coretest "dappco.re/go"
-
-func TestCore_NewRegistry_Good(t *coretest.T) {
- reg := NewRegistry[string]()
- coretest.AssertNotNil(t, reg)
- coretest.AssertEqual(t, 0, reg.Len())
-}
-
-func TestCore_NewRegistry_Bad(t *coretest.T) {
- reg := NewRegistry[int]()
- got := reg.Get("missing")
- coretest.AssertFalse(t, got.OK)
- coretest.AssertNil(t, got.Value)
-}
-
-func TestCore_NewRegistry_Ugly(t *coretest.T) {
- reg := NewRegistry[*int]()
- reg.Set("", nil)
- got := reg.Get("")
- coretest.AssertTrue(t, got.OK)
- coretest.AssertNil(t, got.Value)
-}
-
-func TestCore_NewServiceRuntime_Good(t *coretest.T) {
- runtime := NewServiceRuntime(New(), "opts")
- coretest.AssertNotNil(t, runtime)
- coretest.AssertNotNil(t, runtime.Core)
-}
-
-func TestCore_NewServiceRuntime_Bad(t *coretest.T) {
- runtime := NewServiceRuntime[string](nil, "")
- coretest.AssertNotNil(t, runtime)
- coretest.AssertNil(t, runtime.Core)
-}
-
-func TestCore_NewServiceRuntime_Ugly(t *coretest.T) {
- opts := map[string]string{"name": "api"}
- runtime := NewServiceRuntime(New(), opts)
- coretest.AssertEqual(t, "api", runtime.Options["name"])
- coretest.AssertNotNil(t, runtime.Core)
-}
diff --git a/.deps/core-compat/go.mod b/.deps/core-compat/go.mod
deleted file mode 100644
index 8b51a97b..00000000
--- a/.deps/core-compat/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module dappco.re/go/core
-
-go 1.26.0
-
-require dappco.re/go v0.9.0
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7c2bbfc2..29a59515 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,25 +2,79 @@ name: CI
on:
push:
- branches: [main]
+ branches: [dev, main]
+ pull_request:
+ branches: [dev, main]
+
+permissions:
+ contents: read
+
+env:
+ GOFLAGS: -buildvcs=false
+ GOWORK: "off"
+ GOPROXY: "direct"
+ GOSUMDB: "off"
jobs:
test:
+ name: Test + Coverage
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
-
- - uses: actions/setup-go@v5
+ - uses: actions/checkout@v5
with:
- go-version-file: go.mod
-
- - name: Run tests with coverage
- run: |
- go test -coverprofile=coverage.out ./pkg/brain/... ./pkg/monitor/... ./pkg/agentic/...
- sed -i 's|dappco.re/go/agent/||g' coverage.out
-
- - name: Upload to Codecov
+ fetch-depth: 0
+ - uses: actions/setup-go@v6
+ with:
+ go-version: '1.26'
+ - name: Test with coverage
+ working-directory: go
+ run: go test -race -coverprofile=coverage.out -covermode=atomic -count=1 ./...
+ - name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
- files: coverage.out
token: ${{ secrets.CODECOV_TOKEN }}
+ files: go/coverage.out
+ flags: unittests
+ fail_ci_if_error: false
+
+ lint:
+ name: golangci-lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-go@v6
+ with:
+ go-version: '1.26'
+ - uses: golangci/golangci-lint-action@v9
+ with:
+ version: latest
+ working-directory: go
+ args: --timeout=5m --tests=false
+
+ sonarcloud:
+ name: SonarCloud
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-go@v6
+ with:
+ go-version: '1.26'
+ - name: Test for coverage
+ working-directory: go
+ run: go test -coverprofile=coverage.out -covermode=atomic -count=1 ./...
+ - name: SonarCloud Scan
+ uses: SonarSource/sonarqube-scan-action@v6
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ with:
+ args: >
+ -Dsonar.organization=dappcore
+ -Dsonar.projectKey=dappcore_agent
+ -Dsonar.sources=go
+ -Dsonar.exclusions=**/vendor/**,**/third_party/**,**/.tmp/**,**/*_test.go
+ -Dsonar.tests=go
+ -Dsonar.test.inclusions=**/*_test.go
+ -Dsonar.go.coverage.reportPaths=go/coverage.out
diff --git a/.gitignore b/.gitignore
index c3b5f3bd..74eef83e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,9 +4,8 @@
.core/*
!.core/docs/
!.core/docs/**
-!.core/reference/
-!.core/reference/**
!.core/workspace.yaml
+!.core/agents.yaml
node_modules/
bin/
dist/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..017ab5f0
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,32 @@
+[submodule "external/go"]
+ path = external/go
+ url = https://github.com/dappcore/go.git
+ branch = dev
+[submodule "external/mcp"]
+ path = external/mcp
+ url = https://github.com/dappcore/mcp.git
+ branch = dev
+[submodule "external/process"]
+ path = external/process
+ url = https://github.com/dappcore/go-process.git
+ branch = dev
+[submodule "external/store"]
+ path = external/store
+ url = https://github.com/dappcore/go-store.git
+ branch = dev
+[submodule "external/ws"]
+ path = external/ws
+ url = https://github.com/dappcore/go-ws.git
+ branch = dev
+[submodule "external/io"]
+ path = external/io
+ url = https://github.com/dappcore/go-io.git
+ branch = dev
+[submodule "external/log"]
+ path = external/log
+ url = https://github.com/dappcore/go-log.git
+ branch = dev
+[submodule "external/rag"]
+ path = external/rag
+ url = https://github.com/dappcore/go-rag.git
+ branch = dev
diff --git a/.woodpecker.yml b/.woodpecker.yml
index 107f0e6f..60358eea 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -14,7 +14,7 @@ steps:
GOFLAGS: -buildvcs=false
GOWORK: "off"
commands:
- - golangci-lint run --timeout=5m ./...
+ - cd go && golangci-lint run --timeout=5m ./...
- name: go-test
image: golang:1.26-alpine
@@ -25,7 +25,7 @@ steps:
CGO_ENABLED: "1"
commands:
- apk add --no-cache git build-base
- - go test -race -coverprofile=coverage.out -covermode=atomic -count=1 ./...
+ - cd go && go test -race -coverprofile=coverage.out -covermode=atomic -count=1 ./...
- name: sonar
image: sonarsource/sonar-scanner-cli:latest
depends_on: [go-test]
diff --git a/CLAUDE.md b/CLAUDE.md
index afa4eabf..3bf0ed60 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -164,3 +164,31 @@ Plans drive the entire dispatch→verify→merge flow:
- **Commits**: `type(scope): description` with `Co-Authored-By: Virgil `
- **Licence**: EUPL-1.2
- **SPDX**: `// SPDX-License-Identifier: EUPL-1.2` on every file
+
+## Repo Layout
+
+```
+core/agent/
+├── go/ ← Go module root (module dappco.re/go/agent)
+│ ├── go.mod, go.sum
+│ ├── cmd/ ← Go binaries and entrypoints
+│ ├── pkg/ ← Go runtime packages
+│ ├── version.go
+│ ├── version_example_test.go
+│ ├── README.md ← symlink → ../README.md
+│ ├── CLAUDE.md ← symlink → ../CLAUDE.md
+│ ├── AGENTS.md ← symlink → ../AGENTS.md
+│ └── docs ← symlink → ../docs
+├── php/ ← PHP package (unchanged)
+├── tests/ ← repo test tooling/assets
+├── scripts/ ← task and maintenance helpers
+├── docs/ ← shared documentation
+└── ← CI/config and PHP project files
+```
+
+## Go Resolution Modes
+
+The Go module is located at `go/`, so run Go tooling from there:
+
+- Development/default: `cd go && go build ./...`, `cd go && go test ./...`
+- CI and explicit reproducibility checks: `GOWORK=off` (and optional `GOFLAGS=-mod=mod`) when running `go test`, `go vet`, and `go mod tidy` from `go/`.
diff --git a/CODEX.md b/CODEX.md
deleted file mode 100644
index a42fe386..00000000
--- a/CODEX.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# CODEX.md
-
-Instructions for Codex when working in `dappco.re/go/agent`.
-
-Read these files in order:
-1. `CODEX.md`
-2. `.core/reference/RFC-025-AGENT-EXPERIENCE.md`
-3. `.core/reference/docs/RFC.md`
-4. `AGENTS.md`
-
-## Overview
-
-This repo is the Core ecosystem's agent orchestration service. It is AX-first: predictable names, named Actions, Core primitives, and behaviour-driven tests matter more than terse APIs.
-
-## Build And Test
-
-```bash
-go build ./...
-go build ./cmd/core-agent/
-go test ./... -count=1 -timeout 60s
-go vet ./...
-```
-
-## Core Registration Pattern
-
-Register services through `core.New` and `WithService`, not ad hoc globals.
-
-```go
-c := core.New(
- core.WithOption("name", "core-agent"),
- core.WithService(agentic.ProcessRegister),
- core.WithService(agentic.Register),
- core.WithService(runner.Register),
- core.WithService(monitor.Register),
- core.WithService(brain.Register),
-)
-c.Run()
-```
-
-## Mandatory Conventions
-
-- Use UK English in comments and docs.
-- Use `core.E("pkg.Method", "message", err)` for errors. Never use `fmt.Errorf` or `errors.New`.
-- Use Core filesystem helpers or package-level `fs`. Never use raw `os.ReadFile`, `os.WriteFile`, or `filepath.*`.
-- Route external commands through `s.Core().Process()`. Never import `os/exec`.
-- Use Core string helpers such as `core.Contains`, `core.Trim`, and `core.Split` instead of `strings.*`.
-- Prefer `core.Result{Value: x, OK: true}` over `(value, error)` return pairs in Core-facing code.
-- Comments should show real usage examples, not restate the signature.
-- Prefer predictable names such as `Config`, `Service`, and `Options`; avoid abbreviations.
-- Add `// SPDX-License-Identifier: EUPL-1.2` to Go source files.
-
-## AX Quality Gates
-
-Treat these imports as review failures in non-test Go code:
-
-- `os`
-- `os/exec`
-- `fmt`
-- `log`
-- `errors`
-- `encoding/json`
-- `path/filepath`
-- `strings`
-- `unsafe`
-
-Use the Core primitive or the repo helper instead.
-
-## Testing
-
-Use AX test naming:
-
-```text
-TestFile_Function_Good
-TestFile_Function_Bad
-TestFile_Function_Ugly
-```
-
-One source file should have its own focused test file and example file where practical. The test suite is the behavioural spec.
-
-## Commits
-
-Use `type(scope): description` and include:
-
-```text
-Co-Authored-By: Virgil
-```
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 00000000..4153cd37
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,287 @@
+ EUROPEAN UNION PUBLIC LICENCE v. 1.2
+ EUPL © the European Union 2007, 2016
+
+This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined
+below) which is provided under the terms of this Licence. Any use of the Work,
+other than as authorised under this Licence is prohibited (to the extent such
+use is covered by a right of the copyright holder of the Work).
+
+The Work is provided under the terms of this Licence when the Licensor (as
+defined below) has placed the following notice immediately following the
+copyright notice for the Work:
+
+ Licensed under the EUPL
+
+or has expressed by any other means his willingness to license under the EUPL.
+
+1. Definitions
+
+In this Licence, the following terms have the following meaning:
+
+- ‘The Licence’: this Licence.
+
+- ‘The Original Work’: the work or software distributed or communicated by the
+ Licensor under this Licence, available as Source Code and also as Executable
+ Code as the case may be.
+
+- ‘Derivative Works’: the works or software that could be created by the
+ Licensee, based upon the Original Work or modifications thereof. This Licence
+ does not define the extent of modification or dependence on the Original Work
+ required in order to classify a work as a Derivative Work; this extent is
+ determined by copyright law applicable in the country mentioned in Article 15.
+
+- ‘The Work’: the Original Work or its Derivative Works.
+
+- ‘The Source Code’: the human-readable form of the Work which is the most
+ convenient for people to study and modify.
+
+- ‘The Executable Code’: any code which has generally been compiled and which is
+ meant to be interpreted by a computer as a program.
+
+- ‘The Licensor’: the natural or legal person that distributes or communicates
+ the Work under the Licence.
+
+- ‘Contributor(s)’: any natural or legal person who modifies the Work under the
+ Licence, or otherwise contributes to the creation of a Derivative Work.
+
+- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
+ the Work under the terms of the Licence.
+
+- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
+ renting, distributing, communicating, transmitting, or otherwise making
+ available, online or offline, copies of the Work or providing access to its
+ essential functionalities at the disposal of any other natural or legal
+ person.
+
+2. Scope of the rights granted by the Licence
+
+The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
+sublicensable licence to do the following, for the duration of copyright vested
+in the Original Work:
+
+- use the Work in any circumstance and for all usage,
+- reproduce the Work,
+- modify the Work, and make Derivative Works based upon the Work,
+- communicate to the public, including the right to make available or display
+ the Work or copies thereof to the public and perform publicly, as the case may
+ be, the Work,
+- distribute the Work or copies thereof,
+- lend and rent the Work or copies thereof,
+- sublicense rights in the Work or copies thereof.
+
+Those rights can be exercised on any media, supports and formats, whether now
+known or later invented, as far as the applicable law permits so.
+
+In the countries where moral rights apply, the Licensor waives his right to
+exercise his moral right to the extent allowed by law in order to make effective
+the licence of the economic rights here above listed.
+
+The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
+any patents held by the Licensor, to the extent necessary to make use of the
+rights granted on the Work under this Licence.
+
+3. Communication of the Source Code
+
+The Licensor may provide the Work either in its Source Code form, or as
+Executable Code. If the Work is provided as Executable Code, the Licensor
+provides in addition a machine-readable copy of the Source Code of the Work
+along with each copy of the Work that the Licensor distributes or indicates, in
+a notice following the copyright notice attached to the Work, a repository where
+the Source Code is easily and freely accessible for as long as the Licensor
+continues to distribute or communicate the Work.
+
+4. Limitations on copyright
+
+Nothing in this Licence is intended to deprive the Licensee of the benefits from
+any exception or limitation to the exclusive rights of the rights owners in the
+Work, of the exhaustion of those rights or of other applicable limitations
+thereto.
+
+5. Obligations of the Licensee
+
+The grant of the rights mentioned above is subject to some restrictions and
+obligations imposed on the Licensee. Those obligations are the following:
+
+Attribution right: The Licensee shall keep intact all copyright, patent or
+trademarks notices and all notices that refer to the Licence and to the
+disclaimer of warranties. The Licensee must include a copy of such notices and a
+copy of the Licence with every copy of the Work he/she distributes or
+communicates. The Licensee must cause any Derivative Work to carry prominent
+notices stating that the Work has been modified and the date of modification.
+
+Copyleft clause: If the Licensee distributes or communicates copies of the
+Original Works or Derivative Works, this Distribution or Communication will be
+done under the terms of this Licence or of a later version of this Licence
+unless the Original Work is expressly distributed only under this version of the
+Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
+(becoming Licensor) cannot offer or impose any additional terms or conditions on
+the Work or Derivative Work that alter or restrict the terms of the Licence.
+
+Compatibility clause: If the Licensee Distributes or Communicates Derivative
+Works or copies thereof based upon both the Work and another work licensed under
+a Compatible Licence, this Distribution or Communication can be done under the
+terms of this Compatible Licence. For the sake of this clause, ‘Compatible
+Licence’ refers to the licences listed in the appendix attached to this Licence.
+Should the Licensee's obligations under the Compatible Licence conflict with
+his/her obligations under this Licence, the obligations of the Compatible
+Licence shall prevail.
+
+Provision of Source Code: When distributing or communicating copies of the Work,
+the Licensee will provide a machine-readable copy of the Source Code or indicate
+a repository where this Source will be easily and freely available for as long
+as the Licensee continues to distribute or communicate the Work.
+
+Legal Protection: This Licence does not grant permission to use the trade names,
+trademarks, service marks, or names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the copyright notice.
+
+6. Chain of Authorship
+
+The original Licensor warrants that the copyright in the Original Work granted
+hereunder is owned by him/her or licensed to him/her and that he/she has the
+power and authority to grant the Licence.
+
+Each Contributor warrants that the copyright in the modifications he/she brings
+to the Work are owned by him/her or licensed to him/her and that he/she has the
+power and authority to grant the Licence.
+
+Each time You accept the Licence, the original Licensor and subsequent
+Contributors grant You a licence to their contributions to the Work, under the
+terms of this Licence.
+
+7. Disclaimer of Warranty
+
+The Work is a work in progress, which is continuously improved by numerous
+Contributors. It is not a finished work and may therefore contain defects or
+‘bugs’ inherent to this type of development.
+
+For the above reason, the Work is provided under the Licence on an ‘as is’ basis
+and without warranties of any kind concerning the Work, including without
+limitation merchantability, fitness for a particular purpose, absence of defects
+or errors, accuracy, non-infringement of intellectual property rights other than
+copyright as stated in Article 6 of this Licence.
+
+This disclaimer of warranty is an essential part of the Licence and a condition
+for the grant of any rights to the Work.
+
+8. Disclaimer of Liability
+
+Except in the cases of wilful misconduct or damages directly caused to natural
+persons, the Licensor will in no event be liable for any direct or indirect,
+material or moral, damages of any kind, arising out of the Licence or of the use
+of the Work, including without limitation, damages for loss of goodwill, work
+stoppage, computer failure or malfunction, loss of data or any commercial
+damage, even if the Licensor has been advised of the possibility of such damage.
+However, the Licensor will be liable under statutory product liability laws as
+far such laws apply to the Work.
+
+9. Additional agreements
+
+While distributing the Work, You may choose to conclude an additional agreement,
+defining obligations or services consistent with this Licence. However, if
+accepting obligations, You may act only on your own behalf and on your sole
+responsibility, not on behalf of the original Licensor or any other Contributor,
+and only if You agree to indemnify, defend, and hold each Contributor harmless
+for any liability incurred by, or claims asserted against such Contributor by
+the fact You have accepted any warranty or additional liability.
+
+10. Acceptance of the Licence
+
+The provisions of this Licence can be accepted by clicking on an icon ‘I agree’
+placed under the bottom of a window displaying the text of this Licence or by
+affirming consent in any other similar way, in accordance with the rules of
+applicable law. Clicking on that icon indicates your clear and irrevocable
+acceptance of this Licence and all of its terms and conditions.
+
+Similarly, you irrevocably accept this Licence and all of its terms and
+conditions by exercising any rights granted to You by Article 2 of this Licence,
+such as the use of the Work, the creation by You of a Derivative Work or the
+Distribution or Communication by You of the Work or copies thereof.
+
+11. Information to the public
+
+In case of any Distribution or Communication of the Work by means of electronic
+communication by You (for example, by offering to download the Work from a
+remote location) the distribution channel or media (for example, a website) must
+at least provide to the public the information requested by the applicable law
+regarding the Licensor, the Licence and the way it may be accessible, concluded,
+stored and reproduced by the Licensee.
+
+12. Termination of the Licence
+
+The Licence and the rights granted hereunder will terminate automatically upon
+any breach by the Licensee of the terms of the Licence.
+
+Such a termination will not terminate the licences of any person who has
+received the Work from the Licensee under the Licence, provided such persons
+remain in full compliance with the Licence.
+
+13. Miscellaneous
+
+Without prejudice of Article 9 above, the Licence represents the complete
+agreement between the Parties as to the Work.
+
+If any provision of the Licence is invalid or unenforceable under applicable
+law, this will not affect the validity or enforceability of the Licence as a
+whole. Such provision will be construed or reformed so as necessary to make it
+valid and enforceable.
+
+The European Commission may publish other linguistic versions or new versions of
+this Licence or updated versions of the Appendix, so far this is required and
+reasonable, without reducing the scope of the rights granted by the Licence. New
+versions of the Licence will be published with a unique version number.
+
+All linguistic versions of this Licence, approved by the European Commission,
+have identical value. Parties can take advantage of the linguistic version of
+their choice.
+
+14. Jurisdiction
+
+Without prejudice to specific agreement between parties,
+
+- any litigation resulting from the interpretation of this License, arising
+ between the European Union institutions, bodies, offices or agencies, as a
+ Licensor, and any Licensee, will be subject to the jurisdiction of the Court
+ of Justice of the European Union, as laid down in article 272 of the Treaty on
+ the Functioning of the European Union,
+
+- any litigation arising between other parties and resulting from the
+ interpretation of this License, will be subject to the exclusive jurisdiction
+ of the competent court where the Licensor resides or conducts its primary
+ business.
+
+15. Applicable Law
+
+Without prejudice to specific agreement between parties,
+
+- this Licence shall be governed by the law of the European Union Member State
+ where the Licensor has his seat, resides or has his registered office,
+
+- this licence shall be governed by Belgian law if the Licensor has no seat,
+ residence or registered office inside a European Union Member State.
+
+Appendix
+
+‘Compatible Licences’ according to Article 5 EUPL are:
+
+- GNU General Public License (GPL) v. 2, v. 3
+- GNU Affero General Public License (AGPL) v. 3
+- Open Software License (OSL) v. 2.1, v. 3.0
+- Eclipse Public License (EPL) v. 1.0
+- CeCILL v. 2.0, v. 2.1
+- Mozilla Public Licence (MPL) v. 2
+- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
+- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
+ works other than software
+- European Union Public Licence (EUPL) v. 1.1, v. 1.2
+- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong
+ Reciprocity (LiLiQ-R+).
+
+The European Commission may update this Appendix to later versions of the above
+licences without producing a new version of the EUPL, as long as they provide
+the rights granted in Article 2 of this Licence and protect the covered Source
+Code from exclusive appropriation.
+
+All other changes or additions to this Appendix require the production of a new
+EUPL version.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 2a84fdf5..00000000
--- a/Makefile
+++ /dev/null
@@ -1,36 +0,0 @@
-
-# ── core-agent binary ──────────────────────────────────
-
-BINARY_NAME=core-agent
-CMD_PATH=./cmd/core-agent
-MODULE_PATH=dappco.re/go/agent
-
-# Default LDFLAGS to empty
-LDFLAGS = ""
-
-# If VERSION is set, inject into binary
-ifdef VERSION
- LDFLAGS = -ldflags "-X '$(MODULE_PATH).Version=$(VERSION)'"
-endif
-
-.PHONY: build install agent-dev test coverage
-
-build:
- @echo "Building $(BINARY_NAME)..."
- @go build $(LDFLAGS) -o $(BINARY_NAME) $(CMD_PATH)
-
-install:
- @echo "Installing $(BINARY_NAME)..."
- @go install $(LDFLAGS) $(CMD_PATH)
-
-agent-dev: build
- @./$(BINARY_NAME) version
-
-test:
- @echo "Running tests..."
- @go test ./...
-
-coverage:
- @echo "Generating coverage report..."
- @go test -coverprofile=coverage.out ./...
- @echo "Coverage: coverage.out"
diff --git a/README.md b/README.md
index 8820bec8..72dd6501 100644
--- a/README.md
+++ b/README.md
@@ -1,119 +1,152 @@
-# core-agent
+
-A monorepo of [Claude Code](https://claude.ai/code) plugins for the Host UK federated monorepo.
+# core/agent
-## Plugins
+> AI agent orchestration platform for the Core ecosystem — dispatch, verify, sync, fleet — with first-class Claude / Codex / Hermes / Google provider integrations.
-| Plugin | Description | Commands |
-|--------|-------------|----------|
-| **[code](./claude/code)** | Core development - hooks, scripts, data collection | `/code:remember`, `/code:yes` |
-| **[review](./claude/review)** | Code review automation | `/review:review`, `/review:security`, `/review:pr` |
-| **[verify](./claude/verify)** | Work verification before commit/push | `/verify:verify`, `/verify:ready` |
-| **[qa](./claude/qa)** | Quality assurance fix loops | `/qa:qa`, `/qa:fix`, `/qa:check` |
-| **[ci](./claude/ci)** | CI/CD integration | `/ci:ci`, `/ci:workflow`, `/ci:fix` |
+[](https://github.com/dappcore/agent/actions/workflows/ci.yml)
+[](https://sonarcloud.io/dashboard?id=dappcore_agent)
+[](https://codecov.io/gh/dappcore/agent)
+[](https://sonarcloud.io/dashboard?id=dappcore_agent)
+[](https://sonarcloud.io/dashboard?id=dappcore_agent)
+[](https://sonarcloud.io/dashboard?id=dappcore_agent)
+[](https://sonarcloud.io/dashboard?id=dappcore_agent)
+[](https://sonarcloud.io/dashboard?id=dappcore_agent)
+[](https://pkg.go.dev/dappco.re/go/agent)
+[](https://eupl.eu/1.2/en/)
-## Installation
+## What it is
-```bash
-# Install all plugins via marketplace
-claude plugin add host-uk/core-agent
-
-# Or install individual plugins
-claude plugin add host-uk/core-agent/claude/code
-claude plugin add host-uk/core-agent/claude/review
-claude plugin add host-uk/core-agent/claude/qa
-```
-
-## Quick Start
-
-```bash
-# Code review staged changes
-/review:review
+`core-agent` is a single Go binary that runs as an MCP server (stdio for
+Claude Code integration, HTTP for cross-agent communication) plus a CLI
+that dispatches work across multiple AI providers. It owns:
-# Run QA and fix all issues
-/qa:qa
+- **Dispatch** — fan out a Mantis ticket to a sandboxed worker
+ (Claude / Codex / Hermes / Google) running in `.core/workspace/`.
+- **Fleet sync** — pull / merge / push across the Core ecosystem repos
+ per `agents.yaml`.
+- **OpenBrain** — durable memory + cross-agent messaging via
+ Postgres + Qdrant + Ollama (homelab stack).
+- **Provider integrations** — per-provider plugin trees under
+ `provider/{claude,codex,hermes,google}/`. Claude's tree is the
+ most fleshed out (8 plugins shipped via Claude Code marketplace).
-# Verify work is ready to commit
-/verify:verify
+## Repository Layout
-# Check CI status
-/ci:ci
+```
+agent/
+├── go/ Go module — module path: dappco.re/go/agent
+│ ├── cmd/core-agent/ Binary entry point (mcp + serve)
+│ ├── pkg/agentic/ Dispatch, verify, remote, mirror, queue
+│ ├── pkg/brain/ OpenBrain client (recall + remember)
+│ ├── pkg/monitor/ Background monitor + repo sync
+│ ├── pkg/lib/ Workspace extraction + flow templates
+│ ├── pkg/runner/ Local + container runners
+│ └── pkg/prompts/ Embedded persona + flow templates
+├── php/ PHP package — Laravel module + Boot, Actions,
+│ Agentic for the lthn.ai hosted service
+├── provider/
+│ ├── claude/ Claude Code plugin sources (marketplace at
+│ │ ├── core/ .claude-plugin/marketplace.json)
+│ │ ├── core-go/ — 8 plugins: core, core-go, core-php,
+│ │ ├── core-php/ devops, infra, research, hermes_runner_mcp,
+│ │ ├── devops/ camofox_mcp
+│ │ ├── infra/
+│ │ ├── research/
+│ │ ├── hermes_runner_mcp/
+│ │ ├── camofox_mcp/
+│ │ └── plugins/ marketplace-flavoured subset
+│ ├── codex/ codex-cli configs + harness
+│ ├── hermes/ Hermes plugin sources + skills
+│ └── google/ Google Gemini integration scaffolding
+├── vm/docker/ Containerised dev stack (Dockerfile + compose)
+├── .core/ Runtime workspace seed (agents.yaml + workspace.yaml)
+├── docs/ RFCs, onboarding, audits
+├── go.work + external/ Dev workspace mode (see CLAUDE.md)
+└── Taskfile.yml Build orchestration
```
-## Core CLI Integration
-
-These plugins enforce the `core` CLI for development commands:
-
-| Instead of... | Use... |
-|---------------|--------|
-| `go test` | `core go test` |
-| `go build` | `core build` |
-| `golangci-lint` | `core go lint` |
-| `composer test` | `core php test` |
-| `./vendor/bin/pint` | `core php fmt` |
-
-## Plugin Details
-
-### code
+## Quickstart
-The core plugin with hooks and data collection skills:
+### As a Go module
-- **Hooks**: Auto-format, debug detection, dangerous command blocking
-- **Skills**: Data collection for archiving OSS projects (whitepapers, forums, market data)
-- **Commands**: `/code:remember` (persist facts), `/code:yes` (auto-approve mode)
+```bash
+go get dappco.re/go/agent@latest
+```
-### review
+### As a binary
-Code review automation:
+```bash
+cd go
+go install ./cmd/core-agent/
+core-agent mcp # MCP stdio mode for Claude Code
+core-agent serve # HTTP daemon mode
+```
-- `/review:review` - Review staged changes or commit range
-- `/review:security` - Security-focused review
-- `/review:pr [number]` - Review a pull request
+### As a Claude Code plugin marketplace
-### verify
+```bash
+claude plugin marketplace add https://github.com/dappcore/agent
+claude plugin install core-agent
+```
-Work verification:
+The provider/claude/* tree is the source for the plugins. The
+`.claude-plugin/marketplace.json` at repo root publishes them.
-- `/verify:verify` - Full verification (tests, lint, format, debug check)
-- `/verify:ready` - Quick check if ready to commit
+## Build + Test
-### qa
+```bash
+cd go
+GOWORK=off go test -count=1 ./...
+GOWORK=off go vet ./...
+golangci-lint run --timeout=5m --tests=false ./...
+bash /Users/snider/Code/core/go/tests/cli/v090-upgrade/audit.sh .
+```
-Quality assurance:
+Cross-compile for Charon (homelab Linux box):
-- `/qa:qa` - Run QA pipeline, fix all issues iteratively
-- `/qa:fix ` - Fix a specific issue
-- `/qa:check` - Check without fixing
+```bash
+cd go
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o core-agent-linux ./cmd/core-agent/
+```
-### ci
+## Configuration
-CI/CD integration:
+Runtime config lives at `.core/agents.yaml` (also extracted into
+`~/Code/.core/agents.yaml` on first run via `core-agent setup`). Tunes
+dispatch concurrency, default agent type, container runtime, image,
+GPU passthrough, and per-provider quotas.
-- `/ci:ci` - Check CI status
-- `/ci:workflow ` - Generate GitHub Actions workflow
-- `/ci:fix` - Analyse and fix failing CI
+The repo seeds workspace template at `.core/workspace.yaml` —
+extracted into per-task workspaces under `~/Code/.core/workspace//`.
-## Development
+## CI
-### Adding a new plugin
+- **Internal** (homelab, full sonar.lthn.sh detail): Woodpecker pipeline
+ defined in `.woodpecker.yml`.
+- **Public** (badges + mirror analytics): GitHub Actions workflow at
+ `.github/workflows/ci.yml` — pushes coverage to Codecov + analysis
+ to SonarCloud.
-1. Create `claude//.claude-plugin/plugin.json`
-2. Add commands to `claude//commands/`
-3. Add hooks to `claude//hooks.json` (optional)
-4. Register in `.claude-plugin/marketplace.json`
+## Branch Model
-### Testing locally
+- `dev` — active development. All Cladius / codex lane work lands here
+ first.
+- `main` — squash-stable. Promoted via the squash-and-push gate on the
+ public mirror only.
-```bash
-claude plugin add /path/to/core-agent
-```
+## Licence
-## License
+EUPL-1.2 — see [LICENCE](LICENCE).
-EUPL-1.2
+## Authorship
-## Links
+Maintained by Cladius (Snider's in-house Opus persona) via the
+`agent/cladius` workspace at `forge.lthn.sh/agent/cladius`. Most
+substantive commits land via the codex lane pattern documented in
+`factory/`. Per-provider integration owners:
-- [Host UK](https://host.uk.com)
-- [Claude Code Documentation](https://docs.anthropic.com/claude-code)
-- [Issues](https://github.com/host-uk/core-agent/issues)
+- claude/ Cladius
+- codex/ Codex (cloud) + Cyclops (lane runner persona)
+- hermes/ Hermes (homelab agent at chat.lthn.sh)
+- google/ reserved for Gemini integration
diff --git a/claude/.DS_Store b/claude/.DS_Store
deleted file mode 100644
index 232948c8..00000000
Binary files a/claude/.DS_Store and /dev/null differ
diff --git a/claude/core/.DS_Store b/claude/core/.DS_Store
deleted file mode 100644
index 24a502c2..00000000
Binary files a/claude/core/.DS_Store and /dev/null differ
diff --git a/claude/devops/.DS_Store b/claude/devops/.DS_Store
deleted file mode 100644
index 156b4d3b..00000000
Binary files a/claude/devops/.DS_Store and /dev/null differ
diff --git a/claude/research/.DS_Store b/claude/research/.DS_Store
deleted file mode 100644
index e83e6ca4..00000000
Binary files a/claude/research/.DS_Store and /dev/null differ
diff --git a/codex/.DS_Store b/codex/.DS_Store
deleted file mode 100644
index b570f1e5..00000000
Binary files a/codex/.DS_Store and /dev/null differ
diff --git a/codex/api/.DS_Store b/codex/api/.DS_Store
deleted file mode 100644
index a1e7c569..00000000
Binary files a/codex/api/.DS_Store and /dev/null differ
diff --git a/codex/ci/.DS_Store b/codex/ci/.DS_Store
deleted file mode 100644
index 60e3be2a..00000000
Binary files a/codex/ci/.DS_Store and /dev/null differ
diff --git a/codex/code/.DS_Store b/codex/code/.DS_Store
deleted file mode 100644
index 3ffa5df3..00000000
Binary files a/codex/code/.DS_Store and /dev/null differ
diff --git a/codex/collect/.DS_Store b/codex/collect/.DS_Store
deleted file mode 100644
index d2d52845..00000000
Binary files a/codex/collect/.DS_Store and /dev/null differ
diff --git a/codex/coolify/.DS_Store b/codex/coolify/.DS_Store
deleted file mode 100644
index 753b1a57..00000000
Binary files a/codex/coolify/.DS_Store and /dev/null differ
diff --git a/codex/core/.DS_Store b/codex/core/.DS_Store
deleted file mode 100644
index 291d10e6..00000000
Binary files a/codex/core/.DS_Store and /dev/null differ
diff --git a/codex/ethics/.DS_Store b/codex/ethics/.DS_Store
deleted file mode 100644
index 55ac8af3..00000000
Binary files a/codex/ethics/.DS_Store and /dev/null differ
diff --git a/codex/issue/.DS_Store b/codex/issue/.DS_Store
deleted file mode 100644
index 0a936d01..00000000
Binary files a/codex/issue/.DS_Store and /dev/null differ
diff --git a/codex/perf/.DS_Store b/codex/perf/.DS_Store
deleted file mode 100644
index 4ee77f78..00000000
Binary files a/codex/perf/.DS_Store and /dev/null differ
diff --git a/codex/qa/.DS_Store b/codex/qa/.DS_Store
deleted file mode 100644
index b46a9e56..00000000
Binary files a/codex/qa/.DS_Store and /dev/null differ
diff --git a/codex/review/.DS_Store b/codex/review/.DS_Store
deleted file mode 100644
index d8e5a29d..00000000
Binary files a/codex/review/.DS_Store and /dev/null differ
diff --git a/codex/verify/.DS_Store b/codex/verify/.DS_Store
deleted file mode 100644
index fd4d8237..00000000
Binary files a/codex/verify/.DS_Store and /dev/null differ
diff --git a/core-agent b/core-agent
deleted file mode 100755
index e5ca7758..00000000
Binary files a/core-agent and /dev/null differ
diff --git a/core-agent.backup b/core-agent.backup
deleted file mode 100755
index b39c2bb9..00000000
Binary files a/core-agent.backup and /dev/null differ
diff --git a/docker/.DS_Store b/docker/.DS_Store
deleted file mode 100644
index 4cce3e90..00000000
Binary files a/docker/.DS_Store and /dev/null differ
diff --git a/docker/.env b/docker/.env
deleted file mode 100644
index 754b745a..00000000
--- a/docker/.env
+++ /dev/null
@@ -1,40 +0,0 @@
-# Core Agent Local Stack
-# Copy to .env and adjust as needed
-
-APP_NAME="Core Agent"
-APP_ENV=local
-APP_DEBUG=true
-APP_KEY=base64:cBXxVVn28EbrYjPiy3QAB8+yqd+gUVRDId0SeDZYFsQ=
-APP_URL=https://lthn.sh
-APP_DOMAIN=lthn.sh
-
-# MariaDB
-DB_CONNECTION=mariadb
-DB_HOST=core-mariadb
-DB_PORT=3306
-DB_DATABASE=core_agent
-DB_USERNAME=core
-DB_PASSWORD=core_local_dev
-
-# Redis
-REDIS_CLIENT=predis
-REDIS_HOST=core-redis
-REDIS_PORT=6379
-REDIS_PASSWORD=
-
-# Queue
-QUEUE_CONNECTION=redis
-
-# Ollama (embeddings)
-OLLAMA_URL=http://core-ollama:11434
-
-# Qdrant (vector search)
-QDRANT_HOST=core-qdrant
-QDRANT_PORT=6334
-
-# Reverb (WebSocket)
-REVERB_HOST=0.0.0.0
-REVERB_PORT=8080
-
-# Brain API key (agents use this to authenticate)
-CORE_BRAIN_KEY=local-dev-key
diff --git a/docs/plans/2026-03-15-local-stack.md b/docs/plans/2026-03-15-local-stack.md
index 79a16a82..165d1d93 100644
--- a/docs/plans/2026-03-15-local-stack.md
+++ b/docs/plans/2026-03-15-local-stack.md
@@ -36,7 +36,7 @@ core/agent/
│ └── log/
│ ├── app/ # Laravel logs
│ └── traefik/ # Traefik access logs
-├── docker/
+├── vm/docker/
│ ├── Dockerfile # Multistage Laravel build
│ ├── docker-compose.yml # Full stack
│ ├── .env.example # Template env vars
@@ -55,15 +55,15 @@ core/agent/
| File | Purpose |
|------|---------|
-| `docker/Dockerfile` | Multistage: composer install → npm build → FrankenPHP runtime |
-| `docker/docker-compose.yml` | 6 services, all mounts to `.core/vm/mnt/` |
-| `docker/.env.example` | Template with sane defaults for local dev |
-| `docker/config/traefik.yml` | Static config: entrypoints, file provider, self-signed TLS |
-| `docker/config/dynamic.yml` | Routes: `*.lthn.sh` → services |
-| `docker/config/supervisord.conf` | Octane + Horizon + Scheduler + Reverb |
-| `docker/config/octane.ini` | PHP OPcache + memory settings |
-| `docker/scripts/setup.sh` | First-run bootstrap: mkcert, migrate, seed, pull embedding model |
-| `docker/scripts/entrypoint.sh` | Per-start: migrate, cache clear, optimize |
+| `vm/docker/Dockerfile` | Multistage: composer install → npm build → FrankenPHP runtime |
+| `vm/docker/docker-compose.yml` | 6 services, all mounts to `.core/vm/mnt/` |
+| `vm/docker/.env.example` | Template with sane defaults for local dev |
+| `vm/docker/config/traefik.yml` | Static config: entrypoints, file provider, self-signed TLS |
+| `vm/docker/config/dynamic.yml` | Routes: `*.lthn.sh` → services |
+| `vm/docker/config/supervisord.conf` | Octane + Horizon + Scheduler + Reverb |
+| `vm/docker/config/octane.ini` | PHP OPcache + memory settings |
+| `vm/docker/scripts/setup.sh` | First-run bootstrap: mkcert, migrate, seed, pull embedding model |
+| `vm/docker/scripts/entrypoint.sh` | Per-start: migrate, cache clear, optimize |
---
@@ -72,10 +72,10 @@ core/agent/
### Task 1: Multistage Dockerfile
**Files:**
-- Create: `docker/Dockerfile`
-- Create: `docker/config/octane.ini`
-- Create: `docker/config/supervisord.conf`
-- Create: `docker/scripts/entrypoint.sh`
+- Create: `vm/docker/Dockerfile`
+- Create: `vm/docker/config/octane.ini`
+- Create: `vm/docker/config/supervisord.conf`
+- Create: `vm/docker/scripts/entrypoint.sh`
- [ ] **Step 1: Create octane.ini**
@@ -291,8 +291,8 @@ Co-Authored-By: Virgil "
### Task 2: Docker Compose
**Files:**
-- Create: `docker/docker-compose.yml`
-- Create: `docker/.env.example`
+- Create: `vm/docker/docker-compose.yml`
+- Create: `vm/docker/.env.example`
- [ ] **Step 1: Create .env.example**
@@ -502,13 +502,13 @@ Co-Authored-By: Virgil "
### Task 3: Traefik TLS Configuration
**Files:**
-- Create: `docker/config/traefik-tls.yml`
+- Create: `vm/docker/config/traefik-tls.yml`
Traefik needs TLS for `*.lthn.sh`. For local dev, use self-signed certs generated by `mkcert`. The setup script creates them; this config file tells Traefik where to find them.
- [ ] **Step 1: Create Traefik TLS dynamic config**
-This goes into `.core/vm/mnt/config/traefik/` at runtime (created by setup.sh). The file in `docker/config/` is the template.
+This goes into `.core/vm/mnt/config/traefik/` at runtime (created by setup.sh). The file in `vm/docker/config/` is the template.
```yaml
# Traefik TLS — local dev (self-signed via mkcert)
@@ -537,7 +537,7 @@ Co-Authored-By: Virgil "
### Task 4: First-Run Setup Script
**Files:**
-- Create: `docker/scripts/setup.sh`
+- Create: `vm/docker/scripts/setup.sh`
- [ ] **Step 1: Create setup.sh**
diff --git a/external/go b/external/go
new file mode 160000
index 00000000..d661b703
--- /dev/null
+++ b/external/go
@@ -0,0 +1 @@
+Subproject commit d661b703e16183b3cbab101de189f688888a1174
diff --git a/external/io b/external/io
new file mode 160000
index 00000000..789653df
--- /dev/null
+++ b/external/io
@@ -0,0 +1 @@
+Subproject commit 789653dfc376383a3873993cdb875c8c717e4b05
diff --git a/external/log b/external/log
new file mode 160000
index 00000000..df052983
--- /dev/null
+++ b/external/log
@@ -0,0 +1 @@
+Subproject commit df0529839b2ab786a6a3da374fa664867d5f9f09
diff --git a/external/mcp b/external/mcp
new file mode 160000
index 00000000..702c1b66
--- /dev/null
+++ b/external/mcp
@@ -0,0 +1 @@
+Subproject commit 702c1b662f2697ecc6ced9c018a43d1c959e0758
diff --git a/external/process b/external/process
new file mode 160000
index 00000000..a0ad5cbd
--- /dev/null
+++ b/external/process
@@ -0,0 +1 @@
+Subproject commit a0ad5cbdea96ba43e86bceb1fa8c0b07d0343b3f
diff --git a/external/rag b/external/rag
new file mode 160000
index 00000000..82533037
--- /dev/null
+++ b/external/rag
@@ -0,0 +1 @@
+Subproject commit 825330379dae0b6be1597ac8d92f8db2624038e2
diff --git a/external/store b/external/store
new file mode 160000
index 00000000..3d32fdd7
--- /dev/null
+++ b/external/store
@@ -0,0 +1 @@
+Subproject commit 3d32fdd75e1cc946cb152116f9b1eecd0631a780
diff --git a/external/ws b/external/ws
new file mode 160000
index 00000000..c83f7a1d
--- /dev/null
+++ b/external/ws
@@ -0,0 +1 @@
+Subproject commit c83f7a1d91c314543ac0d61d14a13b24877b8cd7
diff --git a/go.work b/go.work
new file mode 100644
index 00000000..dc28501f
--- /dev/null
+++ b/go.work
@@ -0,0 +1,16 @@
+go 1.26.2
+
+// Workspace mode for development: pulls fresh code from external/ submodules.
+// CI uses GOWORK=off to fall back to go/go.mod tags (reproducible).
+
+use (
+ ./go
+ ./external/go
+ ./external/mcp
+ ./external/process
+ ./external/store
+ ./external/ws
+ ./external/io
+ ./external/log
+ ./external/rag
+)
diff --git a/go/AGENTS.md b/go/AGENTS.md
new file mode 120000
index 00000000..be77ac83
--- /dev/null
+++ b/go/AGENTS.md
@@ -0,0 +1 @@
+../AGENTS.md
\ No newline at end of file
diff --git a/go/CLAUDE.md b/go/CLAUDE.md
new file mode 120000
index 00000000..949a29f1
--- /dev/null
+++ b/go/CLAUDE.md
@@ -0,0 +1 @@
+../CLAUDE.md
\ No newline at end of file
diff --git a/go/README.md b/go/README.md
new file mode 120000
index 00000000..32d46ee8
--- /dev/null
+++ b/go/README.md
@@ -0,0 +1 @@
+../README.md
\ No newline at end of file
diff --git a/cmd/core-agent/commands.go b/go/cmd/core-agent/commands.go
similarity index 100%
rename from cmd/core-agent/commands.go
rename to go/cmd/core-agent/commands.go
diff --git a/cmd/core-agent/commands_example_test.go b/go/cmd/core-agent/commands_example_test.go
similarity index 100%
rename from cmd/core-agent/commands_example_test.go
rename to go/cmd/core-agent/commands_example_test.go
diff --git a/cmd/core-agent/commands_test.go b/go/cmd/core-agent/commands_test.go
similarity index 100%
rename from cmd/core-agent/commands_test.go
rename to go/cmd/core-agent/commands_test.go
diff --git a/cmd/core-agent/main.go b/go/cmd/core-agent/main.go
similarity index 100%
rename from cmd/core-agent/main.go
rename to go/cmd/core-agent/main.go
diff --git a/cmd/core-agent/main_example_test.go b/go/cmd/core-agent/main_example_test.go
similarity index 100%
rename from cmd/core-agent/main_example_test.go
rename to go/cmd/core-agent/main_example_test.go
diff --git a/cmd/core-agent/main_test.go b/go/cmd/core-agent/main_test.go
similarity index 100%
rename from cmd/core-agent/main_test.go
rename to go/cmd/core-agent/main_test.go
diff --git a/cmd/core-agent/mcp_service_example_test.go b/go/cmd/core-agent/mcp_service_example_test.go
similarity index 100%
rename from cmd/core-agent/mcp_service_example_test.go
rename to go/cmd/core-agent/mcp_service_example_test.go
diff --git a/cmd/core-agent/mcp_service_test.go b/go/cmd/core-agent/mcp_service_test.go
similarity index 100%
rename from cmd/core-agent/mcp_service_test.go
rename to go/cmd/core-agent/mcp_service_test.go
diff --git a/cmd/core-agent/test_assertions_test.go b/go/cmd/core-agent/test_assertions_test.go
similarity index 100%
rename from cmd/core-agent/test_assertions_test.go
rename to go/cmd/core-agent/test_assertions_test.go
diff --git a/cmd/core-agent/update.go b/go/cmd/core-agent/update.go
similarity index 100%
rename from cmd/core-agent/update.go
rename to go/cmd/core-agent/update.go
diff --git a/cmd/core-agent/update_example_test.go b/go/cmd/core-agent/update_example_test.go
similarity index 100%
rename from cmd/core-agent/update_example_test.go
rename to go/cmd/core-agent/update_example_test.go
diff --git a/cmd/core-agent/update_test.go b/go/cmd/core-agent/update_test.go
similarity index 100%
rename from cmd/core-agent/update_test.go
rename to go/cmd/core-agent/update_test.go
diff --git a/go/docs b/go/docs
new file mode 120000
index 00000000..a9594bfe
--- /dev/null
+++ b/go/docs
@@ -0,0 +1 @@
+../docs
\ No newline at end of file
diff --git a/go.mod b/go/go.mod
similarity index 98%
rename from go.mod
rename to go/go.mod
index 21254624..f319ea99 100644
--- a/go.mod
+++ b/go/go.mod
@@ -96,5 +96,3 @@ require (
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.47.0 // indirect
)
-
-replace forge.lthn.ai/Snider/Poindexter => ../../snider/Poindexter
diff --git a/go.sum b/go/go.sum
similarity index 99%
rename from go.sum
rename to go/go.sum
index 199cf8b3..21f32fbf 100644
--- a/go.sum
+++ b/go/go.sum
@@ -18,6 +18,8 @@ forge.lthn.ai/Snider/Borg v0.3.1 h1:gfC1ZTpLoZai07oOWJiVeQ8+qJYK8A795tgVGJHbVL8=
forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg=
forge.lthn.ai/Snider/Enchantrix v0.0.4 h1:biwpix/bdedfyc0iVeK15awhhJKH6TEMYOTXzHXx5TI=
forge.lthn.ai/Snider/Enchantrix v0.0.4/go.mod h1:OGCwuVeZPq3OPe2h6TX/ZbgEjHU6B7owpIBeXQGbSe0=
+forge.lthn.ai/Snider/Poindexter v0.0.0-20260223032814-5ab751f16d06 h1:qaoYtu+8J4Z4eg5u3d0Nkp/UzMXR42x6PPg+0+GyXys=
+forge.lthn.ai/Snider/Poindexter v0.0.0-20260223032814-5ab751f16d06/go.mod h1:ddzGia98k3HKkR0gl58IDzqz+MmgW2cQJOCNLfuWPpo=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
diff --git a/pkg/agentcompat/agentcompat.go b/go/pkg/agentcompat/agentcompat.go
similarity index 96%
rename from pkg/agentcompat/agentcompat.go
rename to go/pkg/agentcompat/agentcompat.go
index e54fe87a..99af1657 100644
--- a/pkg/agentcompat/agentcompat.go
+++ b/go/pkg/agentcompat/agentcompat.go
@@ -58,7 +58,9 @@ func (s *HTTPStream) Send(data []byte) error {
if err != nil {
return err
}
- defer response.Body.Close()
+ defer func() {
+ _ = response.Body.Close()
+ }()
readResult := core.ReadAll(response.Body)
if !readResult.OK {
diff --git a/pkg/agentic/actions.go b/go/pkg/agentic/actions.go
similarity index 99%
rename from pkg/agentic/actions.go
rename to go/pkg/agentic/actions.go
index b1a7e28a..109139a2 100644
--- a/pkg/agentic/actions.go
+++ b/go/pkg/agentic/actions.go
@@ -246,7 +246,7 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core
if ok {
workspaceStatus.Status = "failed"
workspaceStatus.Question = "QA check failed — build or tests did not pass"
- writeStatusResult(workspaceDir, workspaceStatus)
+ _ = writeStatusResult(workspaceDir, workspaceStatus)
}
}
}
@@ -257,7 +257,7 @@ func (s *PrepSubsystem) handleQA(ctx context.Context, options core.Options) core
if ok {
repo = workspaceStatus.Repo
}
- s.Core().ACTION(messages.QAResult{
+ _ = s.Core().ACTION(messages.QAResult{
Workspace: WorkspaceName(workspaceDir),
Repo: repo,
Passed: passed,
@@ -285,7 +285,7 @@ func (s *PrepSubsystem) handleAutoPR(ctx context.Context, options core.Options)
result := ReadStatusResult(workspaceDir)
workspaceStatus, ok := workspaceStatusValue(result)
if ok && workspaceStatus.PRURL != "" {
- s.Core().ACTION(messages.PRCreated{
+ _ = s.Core().ACTION(messages.PRCreated{
Repo: workspaceStatus.Repo,
Branch: workspaceStatus.Branch,
PRURL: workspaceStatus.PRURL,
@@ -316,13 +316,13 @@ func (s *PrepSubsystem) handleVerify(ctx context.Context, options core.Options)
workspaceStatus, ok := workspaceStatusValue(result)
if ok {
if workspaceStatus.Status == "merged" {
- s.Core().ACTION(messages.PRMerged{
+ _ = s.Core().ACTION(messages.PRMerged{
Repo: workspaceStatus.Repo,
PRURL: workspaceStatus.PRURL,
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
})
} else if workspaceStatus.Question != "" {
- s.Core().ACTION(messages.PRNeedsReview{
+ _ = s.Core().ACTION(messages.PRNeedsReview{
Repo: workspaceStatus.Repo,
PRURL: workspaceStatus.PRURL,
PRNum: extractPullRequestNumber(workspaceStatus.PRURL),
diff --git a/pkg/agentic/actions_example_test.go b/go/pkg/agentic/actions_example_test.go
similarity index 100%
rename from pkg/agentic/actions_example_test.go
rename to go/pkg/agentic/actions_example_test.go
diff --git a/pkg/agentic/actions_test.go b/go/pkg/agentic/actions_test.go
similarity index 100%
rename from pkg/agentic/actions_test.go
rename to go/pkg/agentic/actions_test.go
diff --git a/pkg/agentic/alias_test.go b/go/pkg/agentic/alias_test.go
similarity index 100%
rename from pkg/agentic/alias_test.go
rename to go/pkg/agentic/alias_test.go
diff --git a/pkg/agentic/auth.go b/go/pkg/agentic/auth.go
similarity index 100%
rename from pkg/agentic/auth.go
rename to go/pkg/agentic/auth.go
diff --git a/pkg/agentic/auth_example_test.go b/go/pkg/agentic/auth_example_test.go
similarity index 100%
rename from pkg/agentic/auth_example_test.go
rename to go/pkg/agentic/auth_example_test.go
diff --git a/pkg/agentic/auth_test.go b/go/pkg/agentic/auth_test.go
similarity index 100%
rename from pkg/agentic/auth_test.go
rename to go/pkg/agentic/auth_test.go
diff --git a/pkg/agentic/auto_pr.go b/go/pkg/agentic/auto_pr.go
similarity index 95%
rename from pkg/agentic/auto_pr.go
rename to go/pkg/agentic/auto_pr.go
index 903010bd..4cd81fda 100644
--- a/pkg/agentic/auto_pr.go
+++ b/go/pkg/agentic/auto_pr.go
@@ -48,12 +48,12 @@ func (s *PrepSubsystem) autoCreatePR(workspaceDir string) {
return
}
workspaceStatusUpdate.Question = "PR push failed"
- writeStatusResult(workspaceDir, workspaceStatusUpdate)
+ _ = writeStatusResult(workspaceDir, workspaceStatusUpdate)
}
return
}
if s.ServiceRuntime != nil {
- s.Core().ACTION(messages.WorkspacePushed{
+ _ = s.Core().ACTION(messages.WorkspacePushed{
Repo: workspaceStatus.Repo,
Branch: workspaceStatus.Branch,
Org: org,
@@ -74,7 +74,7 @@ func (s *PrepSubsystem) autoCreatePR(workspaceDir string) {
return
}
workspaceStatusUpdate.Question = core.Sprintf("PR creation failed: %v", err)
- writeStatusResult(workspaceDir, workspaceStatusUpdate)
+ _ = writeStatusResult(workspaceDir, workspaceStatusUpdate)
}
return
}
diff --git a/pkg/agentic/auto_pr_example_test.go b/go/pkg/agentic/auto_pr_example_test.go
similarity index 100%
rename from pkg/agentic/auto_pr_example_test.go
rename to go/pkg/agentic/auto_pr_example_test.go
diff --git a/pkg/agentic/auto_pr_test.go b/go/pkg/agentic/auto_pr_test.go
similarity index 100%
rename from pkg/agentic/auto_pr_test.go
rename to go/pkg/agentic/auto_pr_test.go
diff --git a/pkg/agentic/brain_client.go b/go/pkg/agentic/brain_client.go
similarity index 100%
rename from pkg/agentic/brain_client.go
rename to go/pkg/agentic/brain_client.go
diff --git a/pkg/agentic/brain_seed_memory.go b/go/pkg/agentic/brain_seed_memory.go
similarity index 100%
rename from pkg/agentic/brain_seed_memory.go
rename to go/pkg/agentic/brain_seed_memory.go
diff --git a/pkg/agentic/brain_seed_memory_example_test.go b/go/pkg/agentic/brain_seed_memory_example_test.go
similarity index 100%
rename from pkg/agentic/brain_seed_memory_example_test.go
rename to go/pkg/agentic/brain_seed_memory_example_test.go
diff --git a/pkg/agentic/brain_seed_memory_test.go b/go/pkg/agentic/brain_seed_memory_test.go
similarity index 100%
rename from pkg/agentic/brain_seed_memory_test.go
rename to go/pkg/agentic/brain_seed_memory_test.go
diff --git a/pkg/agentic/branch_cleanup.go b/go/pkg/agentic/branch_cleanup.go
similarity index 100%
rename from pkg/agentic/branch_cleanup.go
rename to go/pkg/agentic/branch_cleanup.go
diff --git a/pkg/agentic/branch_cleanup_test.go b/go/pkg/agentic/branch_cleanup_test.go
similarity index 100%
rename from pkg/agentic/branch_cleanup_test.go
rename to go/pkg/agentic/branch_cleanup_test.go
diff --git a/pkg/agentic/commands.go b/go/pkg/agentic/commands.go
similarity index 97%
rename from pkg/agentic/commands.go
rename to go/pkg/agentic/commands.go
index 967184fb..430923bd 100644
--- a/pkg/agentic/commands.go
+++ b/go/pkg/agentic/commands.go
@@ -19,15 +19,15 @@ func (s *PrepSubsystem) registerCommands(ctx context.Context) {
s.startupContext = ctx
c := s.Core()
s.registerRepoSyncSupport()
- c.Command("run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTask})
- c.Command("agentic:run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTask})
- c.Command("run/flow", core.Command{Description: "Show a flow definition from disk or the embedded library", Action: s.cmdRunFlow})
- c.Command("agentic:run/flow", core.Command{Description: "Show a flow definition from disk or the embedded library", Action: s.cmdRunFlow})
- c.Command("flow/preview", core.Command{Description: "Preview a flow definition with optional variable substitution", Action: s.cmdFlowPreview})
- c.Command("agentic:flow/preview", core.Command{Description: "Preview a flow definition with optional variable substitution", Action: s.cmdFlowPreview})
- c.Command("dispatch/sync", core.Command{Description: "Dispatch a single task synchronously and block until it completes", Action: s.cmdDispatchSync})
- c.Command("agentic:dispatch/sync", core.Command{Description: "Dispatch a single task synchronously and block until it completes", Action: s.cmdDispatchSync})
- c.Command("run/orchestrator", core.Command{Description: "Run the queue orchestrator (standalone, no MCP)", Action: s.cmdOrchestrator})
+ _ = c.Command("run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTask})
+ _ = c.Command("agentic:run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTask})
+ _ = c.Command("run/flow", core.Command{Description: "Show a flow definition from disk or the embedded library", Action: s.cmdRunFlow})
+ _ = c.Command("agentic:run/flow", core.Command{Description: "Show a flow definition from disk or the embedded library", Action: s.cmdRunFlow})
+ _ = c.Command("flow/preview", core.Command{Description: "Preview a flow definition with optional variable substitution", Action: s.cmdFlowPreview})
+ _ = c.Command("agentic:flow/preview", core.Command{Description: "Preview a flow definition with optional variable substitution", Action: s.cmdFlowPreview})
+ _ = c.Command("dispatch/sync", core.Command{Description: "Dispatch a single task synchronously and block until it completes", Action: s.cmdDispatchSync})
+ _ = c.Command("agentic:dispatch/sync", core.Command{Description: "Dispatch a single task synchronously and block until it completes", Action: s.cmdDispatchSync})
+ _ = c.Command("run/orchestrator", core.Command{Description: "Run the queue orchestrator (standalone, no MCP)", Action: s.cmdOrchestrator})
c.Command("agentic:run/orchestrator", core.Command{Description: "Run the queue orchestrator (standalone, no MCP)", Action: s.cmdOrchestrator})
c.Command("dispatch", core.Command{Description: "Dispatch queued agents", Action: s.cmdDispatch})
c.Command("agentic:dispatch", core.Command{Description: "Dispatch queued agents", Action: s.cmdDispatch})
diff --git a/pkg/agentic/commands_commit.go b/go/pkg/agentic/commands_commit.go
similarity index 100%
rename from pkg/agentic/commands_commit.go
rename to go/pkg/agentic/commands_commit.go
diff --git a/pkg/agentic/commands_commit_test.go b/go/pkg/agentic/commands_commit_test.go
similarity index 100%
rename from pkg/agentic/commands_commit_test.go
rename to go/pkg/agentic/commands_commit_test.go
diff --git a/pkg/agentic/commands_core.go b/go/pkg/agentic/commands_core.go
similarity index 100%
rename from pkg/agentic/commands_core.go
rename to go/pkg/agentic/commands_core.go
diff --git a/pkg/agentic/commands_core_test.go b/go/pkg/agentic/commands_core_test.go
similarity index 100%
rename from pkg/agentic/commands_core_test.go
rename to go/pkg/agentic/commands_core_test.go
diff --git a/pkg/agentic/commands_example_test.go b/go/pkg/agentic/commands_example_test.go
similarity index 100%
rename from pkg/agentic/commands_example_test.go
rename to go/pkg/agentic/commands_example_test.go
diff --git a/pkg/agentic/commands_flow_test.go b/go/pkg/agentic/commands_flow_test.go
similarity index 100%
rename from pkg/agentic/commands_flow_test.go
rename to go/pkg/agentic/commands_flow_test.go
diff --git a/pkg/agentic/commands_forge.go b/go/pkg/agentic/commands_forge.go
similarity index 100%
rename from pkg/agentic/commands_forge.go
rename to go/pkg/agentic/commands_forge.go
diff --git a/pkg/agentic/commands_forge_example_test.go b/go/pkg/agentic/commands_forge_example_test.go
similarity index 100%
rename from pkg/agentic/commands_forge_example_test.go
rename to go/pkg/agentic/commands_forge_example_test.go
diff --git a/pkg/agentic/commands_forge_test.go b/go/pkg/agentic/commands_forge_test.go
similarity index 100%
rename from pkg/agentic/commands_forge_test.go
rename to go/pkg/agentic/commands_forge_test.go
diff --git a/pkg/agentic/commands_message.go b/go/pkg/agentic/commands_message.go
similarity index 100%
rename from pkg/agentic/commands_message.go
rename to go/pkg/agentic/commands_message.go
diff --git a/pkg/agentic/commands_phase.go b/go/pkg/agentic/commands_phase.go
similarity index 100%
rename from pkg/agentic/commands_phase.go
rename to go/pkg/agentic/commands_phase.go
diff --git a/pkg/agentic/commands_phase_test.go b/go/pkg/agentic/commands_phase_test.go
similarity index 100%
rename from pkg/agentic/commands_phase_test.go
rename to go/pkg/agentic/commands_phase_test.go
diff --git a/pkg/agentic/commands_plan.go b/go/pkg/agentic/commands_plan.go
similarity index 100%
rename from pkg/agentic/commands_plan.go
rename to go/pkg/agentic/commands_plan.go
diff --git a/pkg/agentic/commands_plan_test.go b/go/pkg/agentic/commands_plan_test.go
similarity index 100%
rename from pkg/agentic/commands_plan_test.go
rename to go/pkg/agentic/commands_plan_test.go
diff --git a/pkg/agentic/commands_platform.go b/go/pkg/agentic/commands_platform.go
similarity index 100%
rename from pkg/agentic/commands_platform.go
rename to go/pkg/agentic/commands_platform.go
diff --git a/pkg/agentic/commands_platform_example_test.go b/go/pkg/agentic/commands_platform_example_test.go
similarity index 100%
rename from pkg/agentic/commands_platform_example_test.go
rename to go/pkg/agentic/commands_platform_example_test.go
diff --git a/pkg/agentic/commands_platform_test.go b/go/pkg/agentic/commands_platform_test.go
similarity index 100%
rename from pkg/agentic/commands_platform_test.go
rename to go/pkg/agentic/commands_platform_test.go
diff --git a/pkg/agentic/commands_resume_test.go b/go/pkg/agentic/commands_resume_test.go
similarity index 100%
rename from pkg/agentic/commands_resume_test.go
rename to go/pkg/agentic/commands_resume_test.go
diff --git a/pkg/agentic/commands_session.go b/go/pkg/agentic/commands_session.go
similarity index 100%
rename from pkg/agentic/commands_session.go
rename to go/pkg/agentic/commands_session.go
diff --git a/pkg/agentic/commands_session_test.go b/go/pkg/agentic/commands_session_test.go
similarity index 100%
rename from pkg/agentic/commands_session_test.go
rename to go/pkg/agentic/commands_session_test.go
diff --git a/pkg/agentic/commands_setup.go b/go/pkg/agentic/commands_setup.go
similarity index 100%
rename from pkg/agentic/commands_setup.go
rename to go/pkg/agentic/commands_setup.go
diff --git a/pkg/agentic/commands_setup_example_test.go b/go/pkg/agentic/commands_setup_example_test.go
similarity index 100%
rename from pkg/agentic/commands_setup_example_test.go
rename to go/pkg/agentic/commands_setup_example_test.go
diff --git a/pkg/agentic/commands_setup_test.go b/go/pkg/agentic/commands_setup_test.go
similarity index 100%
rename from pkg/agentic/commands_setup_test.go
rename to go/pkg/agentic/commands_setup_test.go
diff --git a/pkg/agentic/commands_sprint.go b/go/pkg/agentic/commands_sprint.go
similarity index 100%
rename from pkg/agentic/commands_sprint.go
rename to go/pkg/agentic/commands_sprint.go
diff --git a/pkg/agentic/commands_sprint_test.go b/go/pkg/agentic/commands_sprint_test.go
similarity index 100%
rename from pkg/agentic/commands_sprint_test.go
rename to go/pkg/agentic/commands_sprint_test.go
diff --git a/pkg/agentic/commands_state.go b/go/pkg/agentic/commands_state.go
similarity index 100%
rename from pkg/agentic/commands_state.go
rename to go/pkg/agentic/commands_state.go
diff --git a/pkg/agentic/commands_state_test.go b/go/pkg/agentic/commands_state_test.go
similarity index 100%
rename from pkg/agentic/commands_state_test.go
rename to go/pkg/agentic/commands_state_test.go
diff --git a/pkg/agentic/commands_task.go b/go/pkg/agentic/commands_task.go
similarity index 100%
rename from pkg/agentic/commands_task.go
rename to go/pkg/agentic/commands_task.go
diff --git a/pkg/agentic/commands_task_test.go b/go/pkg/agentic/commands_task_test.go
similarity index 100%
rename from pkg/agentic/commands_task_test.go
rename to go/pkg/agentic/commands_task_test.go
diff --git a/pkg/agentic/commands_test.go b/go/pkg/agentic/commands_test.go
similarity index 100%
rename from pkg/agentic/commands_test.go
rename to go/pkg/agentic/commands_test.go
diff --git a/pkg/agentic/commands_workspace.go b/go/pkg/agentic/commands_workspace.go
similarity index 99%
rename from pkg/agentic/commands_workspace.go
rename to go/pkg/agentic/commands_workspace.go
index 398bdd1f..7dbd4ea7 100644
--- a/pkg/agentic/commands_workspace.go
+++ b/go/pkg/agentic/commands_workspace.go
@@ -104,7 +104,7 @@ func (s *PrepSubsystem) cmdWorkspaceClean(options core.Options) core.Result {
s.recordWorkspaceStats(path, st)
}
}
- filesystem.DeleteAll(path)
+ _ = filesystem.DeleteAll(path)
core.Print(nil, " removed %s", name)
}
core.Print(nil, "\n %d workspaces removed", len(toRemove))
diff --git a/pkg/agentic/commands_workspace_example_test.go b/go/pkg/agentic/commands_workspace_example_test.go
similarity index 100%
rename from pkg/agentic/commands_workspace_example_test.go
rename to go/pkg/agentic/commands_workspace_example_test.go
diff --git a/pkg/agentic/commands_workspace_test.go b/go/pkg/agentic/commands_workspace_test.go
similarity index 100%
rename from pkg/agentic/commands_workspace_test.go
rename to go/pkg/agentic/commands_workspace_test.go
diff --git a/pkg/agentic/commit.go b/go/pkg/agentic/commit.go
similarity index 98%
rename from pkg/agentic/commit.go
rename to go/pkg/agentic/commit.go
index 0eb779a3..897b5146 100644
--- a/pkg/agentic/commit.go
+++ b/go/pkg/agentic/commit.go
@@ -75,7 +75,7 @@ var commitWorkspace = func(s *PrepSubsystem, ctx context.Context, input CommitIn
markerPath := core.JoinPath(metaDir, "commit.json")
committedAt := time.Now().UTC().Format(time.RFC3339)
- if existingCommit, ok := readCommitMarker(markerPath); ok && existingCommit.UpdatedAt == workspaceStatus.UpdatedAt && existingCommit.Runs == workspaceStatus.Runs {
+ if existingCommit, ok := readCommitMarker(markerPath); ok && existingCommit.UpdatedAt.Equal(workspaceStatus.UpdatedAt) && existingCommit.Runs == workspaceStatus.Runs {
return commitSkippedOutput(input.Workspace, journalPath, markerPath, existingCommit), nil
}
diff --git a/pkg/agentic/commit_example_test.go b/go/pkg/agentic/commit_example_test.go
similarity index 100%
rename from pkg/agentic/commit_example_test.go
rename to go/pkg/agentic/commit_example_test.go
diff --git a/pkg/agentic/commit_test.go b/go/pkg/agentic/commit_test.go
similarity index 100%
rename from pkg/agentic/commit_test.go
rename to go/pkg/agentic/commit_test.go
diff --git a/pkg/agentic/compat_adapters_test.go b/go/pkg/agentic/compat_adapters_test.go
similarity index 100%
rename from pkg/agentic/compat_adapters_test.go
rename to go/pkg/agentic/compat_adapters_test.go
diff --git a/pkg/agentic/content.go b/go/pkg/agentic/content.go
similarity index 99%
rename from pkg/agentic/content.go
rename to go/pkg/agentic/content.go
index ea2b14ef..839f0716 100644
--- a/pkg/agentic/content.go
+++ b/go/pkg/agentic/content.go
@@ -209,7 +209,7 @@ var contentGenerateResult = func(s *PrepSubsystem, ctx context.Context, input Co
hasPrompt := core.Trim(input.Prompt) != ""
hasBrief := core.Trim(input.BriefID) != ""
hasTemplate := core.Trim(input.Template) != ""
- if !hasPrompt && !(hasBrief && hasTemplate) {
+ if !hasPrompt && (!hasBrief || !hasTemplate) {
return ContentResult{}, core.E("contentGenerate", "prompt or brief_id plus template is required", nil)
}
diff --git a/pkg/agentic/content_example_test.go b/go/pkg/agentic/content_example_test.go
similarity index 100%
rename from pkg/agentic/content_example_test.go
rename to go/pkg/agentic/content_example_test.go
diff --git a/pkg/agentic/content_seo.go b/go/pkg/agentic/content_seo.go
similarity index 100%
rename from pkg/agentic/content_seo.go
rename to go/pkg/agentic/content_seo.go
diff --git a/pkg/agentic/content_seo_example_test.go b/go/pkg/agentic/content_seo_example_test.go
similarity index 100%
rename from pkg/agentic/content_seo_example_test.go
rename to go/pkg/agentic/content_seo_example_test.go
diff --git a/pkg/agentic/content_seo_test.go b/go/pkg/agentic/content_seo_test.go
similarity index 100%
rename from pkg/agentic/content_seo_test.go
rename to go/pkg/agentic/content_seo_test.go
diff --git a/pkg/agentic/content_test.go b/go/pkg/agentic/content_test.go
similarity index 100%
rename from pkg/agentic/content_test.go
rename to go/pkg/agentic/content_test.go
diff --git a/pkg/agentic/deps.go b/go/pkg/agentic/deps.go
similarity index 100%
rename from pkg/agentic/deps.go
rename to go/pkg/agentic/deps.go
diff --git a/pkg/agentic/deps_example_test.go b/go/pkg/agentic/deps_example_test.go
similarity index 100%
rename from pkg/agentic/deps_example_test.go
rename to go/pkg/agentic/deps_example_test.go
diff --git a/pkg/agentic/deps_test.go b/go/pkg/agentic/deps_test.go
similarity index 100%
rename from pkg/agentic/deps_test.go
rename to go/pkg/agentic/deps_test.go
diff --git a/pkg/agentic/dispatch.go b/go/pkg/agentic/dispatch.go
similarity index 98%
rename from pkg/agentic/dispatch.go
rename to go/pkg/agentic/dispatch.go
index 9152da3b..7482222c 100644
--- a/pkg/agentic/dispatch.go
+++ b/go/pkg/agentic/dispatch.go
@@ -541,10 +541,10 @@ func (s *PrepSubsystem) trackFailureRate(agent, status string, startedAt time.Ti
s.backoff[pool] = until
s.persistRuntimeState()
if s.ServiceRuntime != nil {
- s.Core().ACTION(messages.RateLimitDetected{
- Pool: pool,
- Duration: backoffDuration.String(),
- })
+ _ = s.Core().ACTION(messages.RateLimitDetected{
+ Pool: pool,
+ Duration: backoffDuration.String(),
+ })
}
core.Print(nil, "rate-limit detected for %s — pausing pool for 30 minutes", pool)
return true
@@ -604,13 +604,13 @@ func (s *PrepSubsystem) broadcastStart(agent, workspaceDir string) {
repo = workspaceStatus.Repo
}
if s.ServiceRuntime != nil {
- s.Core().ACTION(messages.AgentStarted{
- Agent: agent, Repo: repo, Workspace: workspaceName,
- })
+ _ = s.Core().ACTION(messages.AgentStarted{
+ Agent: agent, Repo: repo, Workspace: workspaceName,
+ })
// Push to MCP channel so Claude Code receives the notification
- s.Core().ACTION(coremcp.ChannelPush{
- Channel: coremcp.ChannelAgentStatus,
- Data: map[string]any{
+ _ = s.Core().ACTION(coremcp.ChannelPush{
+ Channel: coremcp.ChannelAgentStatus,
+ Data: map[string]any{
"agent": agent, "repo": repo,
"workspace": workspaceName, "status": "running",
},
@@ -629,10 +629,10 @@ func (s *PrepSubsystem) broadcastComplete(agent, workspaceDir, finalStatus strin
if ok {
repo = workspaceStatus.Repo
}
- s.Core().ACTION(messages.AgentCompleted{
- Agent: agent, Repo: repo,
- Workspace: workspaceName, Status: finalStatus,
- })
+ _ = s.Core().ACTION(messages.AgentCompleted{
+ Agent: agent, Repo: repo,
+ Workspace: workspaceName, Status: finalStatus,
+ })
// Push to MCP channel so Claude Code receives the notification
s.Core().ACTION(coremcp.ChannelPush{
Channel: coremcp.ChannelAgentComplete,
@@ -723,7 +723,7 @@ var spawnAgent = func(s *PrepSubsystem, agent, prompt, workspaceDir string) (int
return 0, "", "", core.E("dispatch.spawnAgent", "unexpected process result", nil)
}
- proc.CloseStdin()
+ _ = proc.CloseStdin()
startDispatchTimeoutWatch(workspaceDir, s.dispatchTimeout(), proc)
pid := proc.Info().PID
processID := proc.ID
@@ -740,7 +740,7 @@ var spawnAgent = func(s *PrepSubsystem, agent, prompt, workspaceDir string) (int
process: proc,
}
s.Core().Action(monitorAction, monitor.run)
- s.Core().PerformAsync(monitorAction, core.NewOptions())
+ _ = s.Core().PerformAsync(monitorAction, core.NewOptions())
return pid, processID, outputFile, nil
}
diff --git a/pkg/agentic/dispatch_example_test.go b/go/pkg/agentic/dispatch_example_test.go
similarity index 100%
rename from pkg/agentic/dispatch_example_test.go
rename to go/pkg/agentic/dispatch_example_test.go
diff --git a/pkg/agentic/dispatch_runtime_test.go b/go/pkg/agentic/dispatch_runtime_test.go
similarity index 100%
rename from pkg/agentic/dispatch_runtime_test.go
rename to go/pkg/agentic/dispatch_runtime_test.go
diff --git a/pkg/agentic/dispatch_sync.go b/go/pkg/agentic/dispatch_sync.go
similarity index 100%
rename from pkg/agentic/dispatch_sync.go
rename to go/pkg/agentic/dispatch_sync.go
diff --git a/pkg/agentic/dispatch_sync_example_test.go b/go/pkg/agentic/dispatch_sync_example_test.go
similarity index 100%
rename from pkg/agentic/dispatch_sync_example_test.go
rename to go/pkg/agentic/dispatch_sync_example_test.go
diff --git a/pkg/agentic/dispatch_sync_test.go b/go/pkg/agentic/dispatch_sync_test.go
similarity index 100%
rename from pkg/agentic/dispatch_sync_test.go
rename to go/pkg/agentic/dispatch_sync_test.go
diff --git a/pkg/agentic/dispatch_test.go b/go/pkg/agentic/dispatch_test.go
similarity index 100%
rename from pkg/agentic/dispatch_test.go
rename to go/pkg/agentic/dispatch_test.go
diff --git a/pkg/agentic/epic.go b/go/pkg/agentic/epic.go
similarity index 96%
rename from pkg/agentic/epic.go
rename to go/pkg/agentic/epic.go
index 48b82ce5..31f9df06 100644
--- a/pkg/agentic/epic.go
+++ b/go/pkg/agentic/epic.go
@@ -153,7 +153,7 @@ var createIssue = func(s *PrepSubsystem, ctx context.Context, org, repo, title,
Number int `json:"number"`
HTMLURL string `json:"html_url"`
}
- core.JSONUnmarshalString(httpResult.Value.(string), &createdIssue)
+ _ = core.JSONUnmarshalString(httpResult.Value.(string), &createdIssue)
return ChildRef{
Number: createdIssue.Number,
@@ -178,7 +178,7 @@ func (s *PrepSubsystem) resolveLabelIDs(ctx context.Context, org, repo string, n
ID int64 `json:"id"`
Name string `json:"name"`
}
- core.JSONUnmarshalString(httpResult.Value.(string), &existing)
+ _ = core.JSONUnmarshalString(httpResult.Value.(string), &existing)
nameToID := make(map[string]int64)
for _, l := range existing {
@@ -227,6 +227,6 @@ func (s *PrepSubsystem) createLabel(ctx context.Context, org, repo, name string)
var createdLabel struct {
ID int64 `json:"id"`
}
- core.JSONUnmarshalString(httpResult.Value.(string), &createdLabel)
+ _ = core.JSONUnmarshalString(httpResult.Value.(string), &createdLabel)
return createdLabel.ID
}
diff --git a/pkg/agentic/epic_example_test.go b/go/pkg/agentic/epic_example_test.go
similarity index 100%
rename from pkg/agentic/epic_example_test.go
rename to go/pkg/agentic/epic_example_test.go
diff --git a/pkg/agentic/epic_test.go b/go/pkg/agentic/epic_test.go
similarity index 100%
rename from pkg/agentic/epic_test.go
rename to go/pkg/agentic/epic_test.go
diff --git a/pkg/agentic/events.go b/go/pkg/agentic/events.go
similarity index 100%
rename from pkg/agentic/events.go
rename to go/pkg/agentic/events.go
diff --git a/pkg/agentic/events_example_test.go b/go/pkg/agentic/events_example_test.go
similarity index 100%
rename from pkg/agentic/events_example_test.go
rename to go/pkg/agentic/events_example_test.go
diff --git a/pkg/agentic/events_test.go b/go/pkg/agentic/events_test.go
similarity index 100%
rename from pkg/agentic/events_test.go
rename to go/pkg/agentic/events_test.go
diff --git a/pkg/agentic/example_aliases_test.go b/go/pkg/agentic/example_aliases_test.go
similarity index 100%
rename from pkg/agentic/example_aliases_test.go
rename to go/pkg/agentic/example_aliases_test.go
diff --git a/pkg/agentic/fetch_loop.go b/go/pkg/agentic/fetch_loop.go
similarity index 98%
rename from pkg/agentic/fetch_loop.go
rename to go/pkg/agentic/fetch_loop.go
index 3f6c2070..cd206378 100644
--- a/pkg/agentic/fetch_loop.go
+++ b/go/pkg/agentic/fetch_loop.go
@@ -199,7 +199,7 @@ func (s *PrepSubsystem) fetchLoopConfigPaths() []string {
add(core.JoinPath(CoreRoot(), "agents.yaml"))
if s != nil {
- add(core.JoinPath(s.codePath, "core", "agent", "config", "agents.yaml"))
+ add(core.JoinPath(s.codePath, "core", "agent", ".core", "agents.yaml"))
}
return paths
diff --git a/pkg/agentic/fetch_loop_test.go b/go/pkg/agentic/fetch_loop_test.go
similarity index 100%
rename from pkg/agentic/fetch_loop_test.go
rename to go/pkg/agentic/fetch_loop_test.go
diff --git a/pkg/agentic/fleet_connect.go b/go/pkg/agentic/fleet_connect.go
similarity index 99%
rename from pkg/agentic/fleet_connect.go
rename to go/pkg/agentic/fleet_connect.go
index 6433d587..1755f66f 100644
--- a/pkg/agentic/fleet_connect.go
+++ b/go/pkg/agentic/fleet_connect.go
@@ -90,7 +90,9 @@ func (s *PrepSubsystem) Connect(ctx context.Context, options core.Options) core.
defer cancelHeartbeat()
if fleetHeartbeatInterval > 0 {
- go s.runFleetHeartbeat(heartbeatContext, config)
+ go func() {
+ _ = s.runFleetHeartbeat(heartbeatContext, config)
+ }()
}
var pollingCancel context.CancelFunc
@@ -316,7 +318,9 @@ func (s *PrepSubsystem) fleetEventRequest(ctx context.Context, action string, co
}
if response.StatusCode >= 400 {
- defer response.Body.Close()
+ defer func() {
+ _ = response.Body.Close()
+ }()
readResult := core.ReadAll(response.Body)
if !readResult.OK {
return core.Result{Value: core.E(action, core.Sprintf("HTTP %d", response.StatusCode), nil), OK: false}
@@ -340,7 +344,9 @@ func (s *PrepSubsystem) connectFleetEventStream(ctx context.Context, config flee
if !ok || response == nil {
return core.Result{Value: core.E("agentic.fleet.connect", "invalid event stream response", nil), OK: false}
}
- defer response.Body.Close()
+ defer func() {
+ _ = response.Body.Close()
+ }()
fleetRememberBase(config)
fleetRememberState("connected", "sse", "")
diff --git a/pkg/agentic/fleet_connect_example_test.go b/go/pkg/agentic/fleet_connect_example_test.go
similarity index 100%
rename from pkg/agentic/fleet_connect_example_test.go
rename to go/pkg/agentic/fleet_connect_example_test.go
diff --git a/pkg/agentic/fleet_connect_test.go b/go/pkg/agentic/fleet_connect_test.go
similarity index 100%
rename from pkg/agentic/fleet_connect_test.go
rename to go/pkg/agentic/fleet_connect_test.go
diff --git a/pkg/agentic/fleet_login.go b/go/pkg/agentic/fleet_login.go
similarity index 100%
rename from pkg/agentic/fleet_login.go
rename to go/pkg/agentic/fleet_login.go
diff --git a/pkg/agentic/fleet_login_test.go b/go/pkg/agentic/fleet_login_test.go
similarity index 100%
rename from pkg/agentic/fleet_login_test.go
rename to go/pkg/agentic/fleet_login_test.go
diff --git a/pkg/agentic/fleet_mode.go b/go/pkg/agentic/fleet_mode.go
similarity index 100%
rename from pkg/agentic/fleet_mode.go
rename to go/pkg/agentic/fleet_mode.go
diff --git a/pkg/agentic/fleet_mode_test.go b/go/pkg/agentic/fleet_mode_test.go
similarity index 100%
rename from pkg/agentic/fleet_mode_test.go
rename to go/pkg/agentic/fleet_mode_test.go
diff --git a/pkg/agentic/flow.go b/go/pkg/agentic/flow.go
similarity index 99%
rename from pkg/agentic/flow.go
rename to go/pkg/agentic/flow.go
index 88059781..9f1c646c 100644
--- a/pkg/agentic/flow.go
+++ b/go/pkg/agentic/flow.go
@@ -323,7 +323,7 @@ var captureFlowStepOutput = func(run func() core.Result) (core.Result, string, s
return core.Result{}, "", "", core.E("agentic.captureFlowStepOutput", "redirect stdout", err)
}
if err := syscall.Dup2(stderrWriteFD, int(stderrFile.Fd())); err != nil {
- syscall.Dup2(restoreStdoutFD, int(stdoutFile.Fd()))
+ _ = syscall.Dup2(restoreStdoutFD, int(stdoutFile.Fd()))
closeFD(stdoutReadFD)
closeFD(stdoutWriteFD)
closeFD(stderrReadFD)
@@ -399,7 +399,7 @@ var readFD = func(fd int) ([]byte, error) {
func closeFD(fd int) {
if fd > 0 {
- syscall.Close(fd)
+ _ = syscall.Close(fd)
}
}
diff --git a/pkg/agentic/flow_example_test.go b/go/pkg/agentic/flow_example_test.go
similarity index 100%
rename from pkg/agentic/flow_example_test.go
rename to go/pkg/agentic/flow_example_test.go
diff --git a/pkg/agentic/flow_test.go b/go/pkg/agentic/flow_test.go
similarity index 100%
rename from pkg/agentic/flow_test.go
rename to go/pkg/agentic/flow_test.go
diff --git a/pkg/agentic/forge_client.go b/go/pkg/agentic/forge_client.go
similarity index 99%
rename from pkg/agentic/forge_client.go
rename to go/pkg/agentic/forge_client.go
index 829b73ae..0055c947 100644
--- a/pkg/agentic/forge_client.go
+++ b/go/pkg/agentic/forge_client.go
@@ -230,7 +230,9 @@ func (c *forgeClient) doJSON(ctx context.Context, method, path string, body, out
if err != nil {
return core.E("forgeClient.doJSON", "request failed", err)
}
- defer response.Body.Close()
+ defer func() {
+ _ = response.Body.Close()
+ }()
readResult := core.ReadAll(response.Body)
if !readResult.OK {
diff --git a/pkg/agentic/handlers.go b/go/pkg/agentic/handlers.go
similarity index 96%
rename from pkg/agentic/handlers.go
rename to go/pkg/agentic/handlers.go
index a541f4b6..21d654f3 100644
--- a/pkg/agentic/handlers.go
+++ b/go/pkg/agentic/handlers.go
@@ -124,14 +124,14 @@ func handleCompletionCommit(c *core.Core, msg core.Message) core.Result {
workspaceDir := findWorkspaceByPRWithInfo(ev.Repo, "", ev.PRNum, ev.PRURL)
if workspaceDir != "" {
if c.Action("agentic.commit").Exists() {
- c.Action("agentic.commit").Run(context.Background(), workspaceActionOptions(workspaceDir))
+ _ = c.Action("agentic.commit").Run(context.Background(), workspaceActionOptions(workspaceDir))
}
}
case messages.PRNeedsReview:
workspaceDir := findWorkspaceByPRWithInfo(ev.Repo, "", ev.PRNum, ev.PRURL)
if workspaceDir != "" {
if c.Action("agentic.commit").Exists() {
- c.Action("agentic.commit").Run(context.Background(), workspaceActionOptions(workspaceDir))
+ _ = c.Action("agentic.commit").Run(context.Background(), workspaceActionOptions(workspaceDir))
}
}
}
@@ -160,7 +160,7 @@ func handleCompletionPoke(c *core.Core, msg core.Message) core.Result {
}
if c != nil && c.Action("runner.poke").Exists() {
- c.ACTION(messages.PokeQueue{})
+ _ = c.ACTION(messages.PokeQueue{})
return core.Result{OK: true}
}
performAsyncIfRegistered(c, "agentic.poke", core.NewOptions())
diff --git a/pkg/agentic/handlers_example_test.go b/go/pkg/agentic/handlers_example_test.go
similarity index 100%
rename from pkg/agentic/handlers_example_test.go
rename to go/pkg/agentic/handlers_example_test.go
diff --git a/pkg/agentic/handlers_test.go b/go/pkg/agentic/handlers_test.go
similarity index 100%
rename from pkg/agentic/handlers_test.go
rename to go/pkg/agentic/handlers_test.go
diff --git a/pkg/agentic/helpers_test.go b/go/pkg/agentic/helpers_test.go
similarity index 100%
rename from pkg/agentic/helpers_test.go
rename to go/pkg/agentic/helpers_test.go
diff --git a/pkg/agentic/ingest.go b/go/pkg/agentic/ingest.go
similarity index 100%
rename from pkg/agentic/ingest.go
rename to go/pkg/agentic/ingest.go
diff --git a/pkg/agentic/ingest_example_test.go b/go/pkg/agentic/ingest_example_test.go
similarity index 100%
rename from pkg/agentic/ingest_example_test.go
rename to go/pkg/agentic/ingest_example_test.go
diff --git a/pkg/agentic/ingest_test.go b/go/pkg/agentic/ingest_test.go
similarity index 100%
rename from pkg/agentic/ingest_test.go
rename to go/pkg/agentic/ingest_test.go
diff --git a/pkg/agentic/issue.go b/go/pkg/agentic/issue.go
similarity index 100%
rename from pkg/agentic/issue.go
rename to go/pkg/agentic/issue.go
diff --git a/pkg/agentic/issue_example_test.go b/go/pkg/agentic/issue_example_test.go
similarity index 100%
rename from pkg/agentic/issue_example_test.go
rename to go/pkg/agentic/issue_example_test.go
diff --git a/pkg/agentic/issue_test.go b/go/pkg/agentic/issue_test.go
similarity index 100%
rename from pkg/agentic/issue_test.go
rename to go/pkg/agentic/issue_test.go
diff --git a/pkg/agentic/lang.go b/go/pkg/agentic/lang.go
similarity index 100%
rename from pkg/agentic/lang.go
rename to go/pkg/agentic/lang.go
diff --git a/pkg/agentic/lang_example_test.go b/go/pkg/agentic/lang_example_test.go
similarity index 100%
rename from pkg/agentic/lang_example_test.go
rename to go/pkg/agentic/lang_example_test.go
diff --git a/pkg/agentic/lang_test.go b/go/pkg/agentic/lang_test.go
similarity index 100%
rename from pkg/agentic/lang_test.go
rename to go/pkg/agentic/lang_test.go
diff --git a/pkg/agentic/logic_test.go b/go/pkg/agentic/logic_test.go
similarity index 100%
rename from pkg/agentic/logic_test.go
rename to go/pkg/agentic/logic_test.go
diff --git a/pkg/agentic/message.go b/go/pkg/agentic/message.go
similarity index 100%
rename from pkg/agentic/message.go
rename to go/pkg/agentic/message.go
diff --git a/pkg/agentic/message_example_test.go b/go/pkg/agentic/message_example_test.go
similarity index 100%
rename from pkg/agentic/message_example_test.go
rename to go/pkg/agentic/message_example_test.go
diff --git a/pkg/agentic/message_test.go b/go/pkg/agentic/message_test.go
similarity index 100%
rename from pkg/agentic/message_test.go
rename to go/pkg/agentic/message_test.go
diff --git a/pkg/agentic/mirror.go b/go/pkg/agentic/mirror.go
similarity index 99%
rename from pkg/agentic/mirror.go
rename to go/pkg/agentic/mirror.go
index 3b1acd13..9e38f00b 100644
--- a/pkg/agentic/mirror.go
+++ b/go/pkg/agentic/mirror.go
@@ -71,7 +71,7 @@ func (s *PrepSubsystem) mirror(ctx context.Context, input MirrorInput) core.Resu
continue
}
- process.RunIn(ctx, repoDir, "git", "fetch", "github")
+ _ = process.RunIn(ctx, repoDir, "git", "fetch", "github")
localBase := s.DefaultBranch(repoDir)
ahead := s.commitsAhead(repoDir, "github/main", localBase)
diff --git a/pkg/agentic/mirror_example_test.go b/go/pkg/agentic/mirror_example_test.go
similarity index 100%
rename from pkg/agentic/mirror_example_test.go
rename to go/pkg/agentic/mirror_example_test.go
diff --git a/pkg/agentic/mirror_test.go b/go/pkg/agentic/mirror_test.go
similarity index 100%
rename from pkg/agentic/mirror_test.go
rename to go/pkg/agentic/mirror_test.go
diff --git a/pkg/agentic/paths.go b/go/pkg/agentic/paths.go
similarity index 100%
rename from pkg/agentic/paths.go
rename to go/pkg/agentic/paths.go
diff --git a/pkg/agentic/paths_example_test.go b/go/pkg/agentic/paths_example_test.go
similarity index 100%
rename from pkg/agentic/paths_example_test.go
rename to go/pkg/agentic/paths_example_test.go
diff --git a/pkg/agentic/paths_helpers_test.go b/go/pkg/agentic/paths_helpers_test.go
similarity index 100%
rename from pkg/agentic/paths_helpers_test.go
rename to go/pkg/agentic/paths_helpers_test.go
diff --git a/pkg/agentic/paths_test.go b/go/pkg/agentic/paths_test.go
similarity index 100%
rename from pkg/agentic/paths_test.go
rename to go/pkg/agentic/paths_test.go
diff --git a/pkg/agentic/persist.go b/go/pkg/agentic/persist.go
similarity index 100%
rename from pkg/agentic/persist.go
rename to go/pkg/agentic/persist.go
diff --git a/pkg/agentic/persist_test.go b/go/pkg/agentic/persist_test.go
similarity index 100%
rename from pkg/agentic/persist_test.go
rename to go/pkg/agentic/persist_test.go
diff --git a/pkg/agentic/phase.go b/go/pkg/agentic/phase.go
similarity index 100%
rename from pkg/agentic/phase.go
rename to go/pkg/agentic/phase.go
diff --git a/pkg/agentic/phase_example_test.go b/go/pkg/agentic/phase_example_test.go
similarity index 100%
rename from pkg/agentic/phase_example_test.go
rename to go/pkg/agentic/phase_example_test.go
diff --git a/pkg/agentic/phase_test.go b/go/pkg/agentic/phase_test.go
similarity index 100%
rename from pkg/agentic/phase_test.go
rename to go/pkg/agentic/phase_test.go
diff --git a/pkg/agentic/pid.go b/go/pkg/agentic/pid.go
similarity index 100%
rename from pkg/agentic/pid.go
rename to go/pkg/agentic/pid.go
diff --git a/pkg/agentic/pid_example_test.go b/go/pkg/agentic/pid_example_test.go
similarity index 100%
rename from pkg/agentic/pid_example_test.go
rename to go/pkg/agentic/pid_example_test.go
diff --git a/pkg/agentic/pid_test.go b/go/pkg/agentic/pid_test.go
similarity index 100%
rename from pkg/agentic/pid_test.go
rename to go/pkg/agentic/pid_test.go
diff --git a/pkg/agentic/pipeline_audit.go b/go/pkg/agentic/pipeline_audit.go
similarity index 100%
rename from pkg/agentic/pipeline_audit.go
rename to go/pkg/agentic/pipeline_audit.go
diff --git a/pkg/agentic/pipeline_audit_example_test.go b/go/pkg/agentic/pipeline_audit_example_test.go
similarity index 100%
rename from pkg/agentic/pipeline_audit_example_test.go
rename to go/pkg/agentic/pipeline_audit_example_test.go
diff --git a/pkg/agentic/pipeline_audit_test.go b/go/pkg/agentic/pipeline_audit_test.go
similarity index 100%
rename from pkg/agentic/pipeline_audit_test.go
rename to go/pkg/agentic/pipeline_audit_test.go
diff --git a/pkg/agentic/pipeline_budget.go b/go/pkg/agentic/pipeline_budget.go
similarity index 100%
rename from pkg/agentic/pipeline_budget.go
rename to go/pkg/agentic/pipeline_budget.go
diff --git a/pkg/agentic/pipeline_budget_test.go b/go/pkg/agentic/pipeline_budget_test.go
similarity index 100%
rename from pkg/agentic/pipeline_budget_test.go
rename to go/pkg/agentic/pipeline_budget_test.go
diff --git a/pkg/agentic/pipeline_commands.go b/go/pkg/agentic/pipeline_commands.go
similarity index 100%
rename from pkg/agentic/pipeline_commands.go
rename to go/pkg/agentic/pipeline_commands.go
diff --git a/pkg/agentic/pipeline_commands_test.go b/go/pkg/agentic/pipeline_commands_test.go
similarity index 100%
rename from pkg/agentic/pipeline_commands_test.go
rename to go/pkg/agentic/pipeline_commands_test.go
diff --git a/pkg/agentic/pipeline_epic.go b/go/pkg/agentic/pipeline_epic.go
similarity index 100%
rename from pkg/agentic/pipeline_epic.go
rename to go/pkg/agentic/pipeline_epic.go
diff --git a/pkg/agentic/pipeline_epic_example_test.go b/go/pkg/agentic/pipeline_epic_example_test.go
similarity index 100%
rename from pkg/agentic/pipeline_epic_example_test.go
rename to go/pkg/agentic/pipeline_epic_example_test.go
diff --git a/pkg/agentic/pipeline_epic_test.go b/go/pkg/agentic/pipeline_epic_test.go
similarity index 100%
rename from pkg/agentic/pipeline_epic_test.go
rename to go/pkg/agentic/pipeline_epic_test.go
diff --git a/pkg/agentic/pipeline_fix.go b/go/pkg/agentic/pipeline_fix.go
similarity index 100%
rename from pkg/agentic/pipeline_fix.go
rename to go/pkg/agentic/pipeline_fix.go
diff --git a/pkg/agentic/pipeline_fix_example_test.go b/go/pkg/agentic/pipeline_fix_example_test.go
similarity index 100%
rename from pkg/agentic/pipeline_fix_example_test.go
rename to go/pkg/agentic/pipeline_fix_example_test.go
diff --git a/pkg/agentic/pipeline_fix_test.go b/go/pkg/agentic/pipeline_fix_test.go
similarity index 100%
rename from pkg/agentic/pipeline_fix_test.go
rename to go/pkg/agentic/pipeline_fix_test.go
diff --git a/pkg/agentic/pipeline_monitor.go b/go/pkg/agentic/pipeline_monitor.go
similarity index 100%
rename from pkg/agentic/pipeline_monitor.go
rename to go/pkg/agentic/pipeline_monitor.go
diff --git a/pkg/agentic/pipeline_monitor_example_test.go b/go/pkg/agentic/pipeline_monitor_example_test.go
similarity index 100%
rename from pkg/agentic/pipeline_monitor_example_test.go
rename to go/pkg/agentic/pipeline_monitor_example_test.go
diff --git a/pkg/agentic/pipeline_monitor_test.go b/go/pkg/agentic/pipeline_monitor_test.go
similarity index 100%
rename from pkg/agentic/pipeline_monitor_test.go
rename to go/pkg/agentic/pipeline_monitor_test.go
diff --git a/pkg/agentic/pipeline_onboard.go b/go/pkg/agentic/pipeline_onboard.go
similarity index 100%
rename from pkg/agentic/pipeline_onboard.go
rename to go/pkg/agentic/pipeline_onboard.go
diff --git a/pkg/agentic/pipeline_onboard_example_test.go b/go/pkg/agentic/pipeline_onboard_example_test.go
similarity index 100%
rename from pkg/agentic/pipeline_onboard_example_test.go
rename to go/pkg/agentic/pipeline_onboard_example_test.go
diff --git a/pkg/agentic/pipeline_onboard_test.go b/go/pkg/agentic/pipeline_onboard_test.go
similarity index 100%
rename from pkg/agentic/pipeline_onboard_test.go
rename to go/pkg/agentic/pipeline_onboard_test.go
diff --git a/pkg/agentic/pipeline_training.go b/go/pkg/agentic/pipeline_training.go
similarity index 100%
rename from pkg/agentic/pipeline_training.go
rename to go/pkg/agentic/pipeline_training.go
diff --git a/pkg/agentic/pipeline_training_example_test.go b/go/pkg/agentic/pipeline_training_example_test.go
similarity index 100%
rename from pkg/agentic/pipeline_training_example_test.go
rename to go/pkg/agentic/pipeline_training_example_test.go
diff --git a/pkg/agentic/pipeline_training_test.go b/go/pkg/agentic/pipeline_training_test.go
similarity index 100%
rename from pkg/agentic/pipeline_training_test.go
rename to go/pkg/agentic/pipeline_training_test.go
diff --git a/pkg/agentic/plan.go b/go/pkg/agentic/plan.go
similarity index 100%
rename from pkg/agentic/plan.go
rename to go/pkg/agentic/plan.go
diff --git a/pkg/agentic/plan_compat.go b/go/pkg/agentic/plan_compat.go
similarity index 100%
rename from pkg/agentic/plan_compat.go
rename to go/pkg/agentic/plan_compat.go
diff --git a/pkg/agentic/plan_compat_example_test.go b/go/pkg/agentic/plan_compat_example_test.go
similarity index 100%
rename from pkg/agentic/plan_compat_example_test.go
rename to go/pkg/agentic/plan_compat_example_test.go
diff --git a/pkg/agentic/plan_compat_test.go b/go/pkg/agentic/plan_compat_test.go
similarity index 100%
rename from pkg/agentic/plan_compat_test.go
rename to go/pkg/agentic/plan_compat_test.go
diff --git a/pkg/agentic/plan_crud_test.go b/go/pkg/agentic/plan_crud_test.go
similarity index 100%
rename from pkg/agentic/plan_crud_test.go
rename to go/pkg/agentic/plan_crud_test.go
diff --git a/pkg/agentic/plan_dependencies_test.go b/go/pkg/agentic/plan_dependencies_test.go
similarity index 100%
rename from pkg/agentic/plan_dependencies_test.go
rename to go/pkg/agentic/plan_dependencies_test.go
diff --git a/pkg/agentic/plan_example_test.go b/go/pkg/agentic/plan_example_test.go
similarity index 100%
rename from pkg/agentic/plan_example_test.go
rename to go/pkg/agentic/plan_example_test.go
diff --git a/pkg/agentic/plan_from_issue.go b/go/pkg/agentic/plan_from_issue.go
similarity index 100%
rename from pkg/agentic/plan_from_issue.go
rename to go/pkg/agentic/plan_from_issue.go
diff --git a/pkg/agentic/plan_from_issue_example_test.go b/go/pkg/agentic/plan_from_issue_example_test.go
similarity index 100%
rename from pkg/agentic/plan_from_issue_example_test.go
rename to go/pkg/agentic/plan_from_issue_example_test.go
diff --git a/pkg/agentic/plan_from_issue_test.go b/go/pkg/agentic/plan_from_issue_test.go
similarity index 100%
rename from pkg/agentic/plan_from_issue_test.go
rename to go/pkg/agentic/plan_from_issue_test.go
diff --git a/pkg/agentic/plan_logic_test.go b/go/pkg/agentic/plan_logic_test.go
similarity index 100%
rename from pkg/agentic/plan_logic_test.go
rename to go/pkg/agentic/plan_logic_test.go
diff --git a/pkg/agentic/plan_retention.go b/go/pkg/agentic/plan_retention.go
similarity index 100%
rename from pkg/agentic/plan_retention.go
rename to go/pkg/agentic/plan_retention.go
diff --git a/pkg/agentic/plan_retention_example_test.go b/go/pkg/agentic/plan_retention_example_test.go
similarity index 100%
rename from pkg/agentic/plan_retention_example_test.go
rename to go/pkg/agentic/plan_retention_example_test.go
diff --git a/pkg/agentic/plan_retention_test.go b/go/pkg/agentic/plan_retention_test.go
similarity index 100%
rename from pkg/agentic/plan_retention_test.go
rename to go/pkg/agentic/plan_retention_test.go
diff --git a/pkg/agentic/plan_test.go b/go/pkg/agentic/plan_test.go
similarity index 100%
rename from pkg/agentic/plan_test.go
rename to go/pkg/agentic/plan_test.go
diff --git a/pkg/agentic/platform.go b/go/pkg/agentic/platform.go
similarity index 100%
rename from pkg/agentic/platform.go
rename to go/pkg/agentic/platform.go
diff --git a/pkg/agentic/platform_example_test.go b/go/pkg/agentic/platform_example_test.go
similarity index 100%
rename from pkg/agentic/platform_example_test.go
rename to go/pkg/agentic/platform_example_test.go
diff --git a/pkg/agentic/platform_test.go b/go/pkg/agentic/platform_test.go
similarity index 100%
rename from pkg/agentic/platform_test.go
rename to go/pkg/agentic/platform_test.go
diff --git a/pkg/agentic/platform_tools.go b/go/pkg/agentic/platform_tools.go
similarity index 100%
rename from pkg/agentic/platform_tools.go
rename to go/pkg/agentic/platform_tools.go
diff --git a/pkg/agentic/platform_tools_example_test.go b/go/pkg/agentic/platform_tools_example_test.go
similarity index 100%
rename from pkg/agentic/platform_tools_example_test.go
rename to go/pkg/agentic/platform_tools_example_test.go
diff --git a/pkg/agentic/platform_tools_test.go b/go/pkg/agentic/platform_tools_test.go
similarity index 100%
rename from pkg/agentic/platform_tools_test.go
rename to go/pkg/agentic/platform_tools_test.go
diff --git a/pkg/agentic/pr.go b/go/pkg/agentic/pr.go
similarity index 100%
rename from pkg/agentic/pr.go
rename to go/pkg/agentic/pr.go
diff --git a/pkg/agentic/pr_example_test.go b/go/pkg/agentic/pr_example_test.go
similarity index 100%
rename from pkg/agentic/pr_example_test.go
rename to go/pkg/agentic/pr_example_test.go
diff --git a/pkg/agentic/pr_test.go b/go/pkg/agentic/pr_test.go
similarity index 100%
rename from pkg/agentic/pr_test.go
rename to go/pkg/agentic/pr_test.go
diff --git a/pkg/agentic/prep.go b/go/pkg/agentic/prep.go
similarity index 100%
rename from pkg/agentic/prep.go
rename to go/pkg/agentic/prep.go
diff --git a/pkg/agentic/prep_example_test.go b/go/pkg/agentic/prep_example_test.go
similarity index 100%
rename from pkg/agentic/prep_example_test.go
rename to go/pkg/agentic/prep_example_test.go
diff --git a/pkg/agentic/prep_extra_test.go b/go/pkg/agentic/prep_extra_test.go
similarity index 100%
rename from pkg/agentic/prep_extra_test.go
rename to go/pkg/agentic/prep_extra_test.go
diff --git a/pkg/agentic/prep_test.go b/go/pkg/agentic/prep_test.go
similarity index 100%
rename from pkg/agentic/prep_test.go
rename to go/pkg/agentic/prep_test.go
diff --git a/pkg/agentic/process_register.go b/go/pkg/agentic/process_register.go
similarity index 100%
rename from pkg/agentic/process_register.go
rename to go/pkg/agentic/process_register.go
diff --git a/pkg/agentic/process_register_example_test.go b/go/pkg/agentic/process_register_example_test.go
similarity index 100%
rename from pkg/agentic/process_register_example_test.go
rename to go/pkg/agentic/process_register_example_test.go
diff --git a/pkg/agentic/process_register_test.go b/go/pkg/agentic/process_register_test.go
similarity index 100%
rename from pkg/agentic/process_register_test.go
rename to go/pkg/agentic/process_register_test.go
diff --git a/pkg/agentic/prompt_version.go b/go/pkg/agentic/prompt_version.go
similarity index 100%
rename from pkg/agentic/prompt_version.go
rename to go/pkg/agentic/prompt_version.go
diff --git a/pkg/agentic/prompt_version_example_test.go b/go/pkg/agentic/prompt_version_example_test.go
similarity index 100%
rename from pkg/agentic/prompt_version_example_test.go
rename to go/pkg/agentic/prompt_version_example_test.go
diff --git a/pkg/agentic/prompt_version_test.go b/go/pkg/agentic/prompt_version_test.go
similarity index 100%
rename from pkg/agentic/prompt_version_test.go
rename to go/pkg/agentic/prompt_version_test.go
diff --git a/pkg/agentic/provider_manager.go b/go/pkg/agentic/provider_manager.go
similarity index 83%
rename from pkg/agentic/provider_manager.go
rename to go/pkg/agentic/provider_manager.go
index a5d5700c..6b0699a0 100644
--- a/pkg/agentic/provider_manager.go
+++ b/go/pkg/agentic/provider_manager.go
@@ -34,45 +34,6 @@ var providerSleep = time.Sleep
const providerRetryAttempts = 3
-// manager := s.providerManager()
-// core.Println(manager.Names()) // ["claude", "gemini", "openai"]
-func (s *PrepSubsystem) providerManager() *ProviderManager {
- if s == nil {
- return NewProviderManager(nil)
- }
- if s.providers != nil {
- return s.providers
- }
-
- s.providers = NewProviderManager(func(ctx context.Context, prompt string, options map[string]any) (string, error) {
- config := anyMapValue(options["config"])
- if model := contentMapStringValue(options, "model"); model != "" {
- if config == nil {
- config = map[string]any{}
- }
- config["model"] = model
- }
- input := ContentGenerateInput{
- Prompt: prompt,
- Provider: contentMapStringValue(options, "provider"),
- Config: config,
- }
- if template := contentMapStringValue(options, "template"); template != "" {
- input.Template = template
- }
- if briefID := contentMapStringValue(options, "brief_id", "briefId"); briefID != "" {
- input.BriefID = briefID
- }
- result, err := contentGenerateResult(s, ctx, input)
- if err != nil {
- return "", err
- }
- return result.Content, nil
- })
-
- return s.providers
-}
-
// manager := agentic.NewProviderManager(func(ctx context.Context, prompt string, options map[string]any) (string, error) {
// return "Draft ready", nil
// })
diff --git a/pkg/agentic/provider_manager_example_test.go b/go/pkg/agentic/provider_manager_example_test.go
similarity index 100%
rename from pkg/agentic/provider_manager_example_test.go
rename to go/pkg/agentic/provider_manager_example_test.go
diff --git a/pkg/agentic/provider_manager_extra_test.go b/go/pkg/agentic/provider_manager_extra_test.go
similarity index 100%
rename from pkg/agentic/provider_manager_extra_test.go
rename to go/pkg/agentic/provider_manager_extra_test.go
diff --git a/pkg/agentic/provider_manager_test.go b/go/pkg/agentic/provider_manager_test.go
similarity index 100%
rename from pkg/agentic/provider_manager_test.go
rename to go/pkg/agentic/provider_manager_test.go
diff --git a/pkg/agentic/qa.go b/go/pkg/agentic/qa.go
similarity index 100%
rename from pkg/agentic/qa.go
rename to go/pkg/agentic/qa.go
diff --git a/pkg/agentic/qa_analysis.go b/go/pkg/agentic/qa_analysis.go
similarity index 97%
rename from pkg/agentic/qa_analysis.go
rename to go/pkg/agentic/qa_analysis.go
index 455a5142..da1b1504 100644
--- a/pkg/agentic/qa_analysis.go
+++ b/go/pkg/agentic/qa_analysis.go
@@ -133,11 +133,6 @@ func qaAnalysisWorkspaceRows(workspace *store.Workspace, kind string) []string {
// Frequency defaults to 1 for direct callers; the cluster builder supplies the
// observed per-fingerprint frequency for each point.
//
-// Usage example: `coords := findingToPoint(QAFinding{Tool: "gosec", Severity: "error", File: "main.go", Category: "security"})`
-func findingToPoint(finding QAFinding) []float64 {
- return qaAnalysisPointCoords(finding, 1)
-}
-
func qaAnalysisPointCoords(finding QAFinding, frequency float64) []float64 {
return []float64{
qaAnalysisHash(core.Lower(finding.Tool)),
diff --git a/pkg/agentic/qa_analysis_test.go b/go/pkg/agentic/qa_analysis_test.go
similarity index 100%
rename from pkg/agentic/qa_analysis_test.go
rename to go/pkg/agentic/qa_analysis_test.go
diff --git a/pkg/agentic/qa_cluster.go b/go/pkg/agentic/qa_cluster.go
similarity index 100%
rename from pkg/agentic/qa_cluster.go
rename to go/pkg/agentic/qa_cluster.go
diff --git a/pkg/agentic/qa_cluster_example_test.go b/go/pkg/agentic/qa_cluster_example_test.go
similarity index 100%
rename from pkg/agentic/qa_cluster_example_test.go
rename to go/pkg/agentic/qa_cluster_example_test.go
diff --git a/pkg/agentic/qa_cluster_test.go b/go/pkg/agentic/qa_cluster_test.go
similarity index 100%
rename from pkg/agentic/qa_cluster_test.go
rename to go/pkg/agentic/qa_cluster_test.go
diff --git a/pkg/agentic/qa_example_test.go b/go/pkg/agentic/qa_example_test.go
similarity index 100%
rename from pkg/agentic/qa_example_test.go
rename to go/pkg/agentic/qa_example_test.go
diff --git a/pkg/agentic/qa_test.go b/go/pkg/agentic/qa_test.go
similarity index 100%
rename from pkg/agentic/qa_test.go
rename to go/pkg/agentic/qa_test.go
diff --git a/pkg/agentic/queue.go b/go/pkg/agentic/queue.go
similarity index 100%
rename from pkg/agentic/queue.go
rename to go/pkg/agentic/queue.go
diff --git a/pkg/agentic/queue_example_test.go b/go/pkg/agentic/queue_example_test.go
similarity index 100%
rename from pkg/agentic/queue_example_test.go
rename to go/pkg/agentic/queue_example_test.go
diff --git a/pkg/agentic/queue_extra_test.go b/go/pkg/agentic/queue_extra_test.go
similarity index 100%
rename from pkg/agentic/queue_extra_test.go
rename to go/pkg/agentic/queue_extra_test.go
diff --git a/pkg/agentic/queue_logic_test.go b/go/pkg/agentic/queue_logic_test.go
similarity index 100%
rename from pkg/agentic/queue_logic_test.go
rename to go/pkg/agentic/queue_logic_test.go
diff --git a/pkg/agentic/queue_test.go b/go/pkg/agentic/queue_test.go
similarity index 100%
rename from pkg/agentic/queue_test.go
rename to go/pkg/agentic/queue_test.go
diff --git a/pkg/agentic/register.go b/go/pkg/agentic/register.go
similarity index 100%
rename from pkg/agentic/register.go
rename to go/pkg/agentic/register.go
diff --git a/pkg/agentic/register_example_test.go b/go/pkg/agentic/register_example_test.go
similarity index 100%
rename from pkg/agentic/register_example_test.go
rename to go/pkg/agentic/register_example_test.go
diff --git a/pkg/agentic/register_test.go b/go/pkg/agentic/register_test.go
similarity index 100%
rename from pkg/agentic/register_test.go
rename to go/pkg/agentic/register_test.go
diff --git a/pkg/agentic/remote.go b/go/pkg/agentic/remote.go
similarity index 100%
rename from pkg/agentic/remote.go
rename to go/pkg/agentic/remote.go
diff --git a/pkg/agentic/remote_client.go b/go/pkg/agentic/remote_client.go
similarity index 100%
rename from pkg/agentic/remote_client.go
rename to go/pkg/agentic/remote_client.go
diff --git a/pkg/agentic/remote_client_example_test.go b/go/pkg/agentic/remote_client_example_test.go
similarity index 100%
rename from pkg/agentic/remote_client_example_test.go
rename to go/pkg/agentic/remote_client_example_test.go
diff --git a/pkg/agentic/remote_client_test.go b/go/pkg/agentic/remote_client_test.go
similarity index 100%
rename from pkg/agentic/remote_client_test.go
rename to go/pkg/agentic/remote_client_test.go
diff --git a/pkg/agentic/remote_dispatch_test.go b/go/pkg/agentic/remote_dispatch_test.go
similarity index 100%
rename from pkg/agentic/remote_dispatch_test.go
rename to go/pkg/agentic/remote_dispatch_test.go
diff --git a/pkg/agentic/remote_example_test.go b/go/pkg/agentic/remote_example_test.go
similarity index 100%
rename from pkg/agentic/remote_example_test.go
rename to go/pkg/agentic/remote_example_test.go
diff --git a/pkg/agentic/remote_status.go b/go/pkg/agentic/remote_status.go
similarity index 100%
rename from pkg/agentic/remote_status.go
rename to go/pkg/agentic/remote_status.go
diff --git a/pkg/agentic/remote_status_example_test.go b/go/pkg/agentic/remote_status_example_test.go
similarity index 100%
rename from pkg/agentic/remote_status_example_test.go
rename to go/pkg/agentic/remote_status_example_test.go
diff --git a/pkg/agentic/remote_status_test.go b/go/pkg/agentic/remote_status_test.go
similarity index 100%
rename from pkg/agentic/remote_status_test.go
rename to go/pkg/agentic/remote_status_test.go
diff --git a/pkg/agentic/remote_sync_queue.go b/go/pkg/agentic/remote_sync_queue.go
similarity index 100%
rename from pkg/agentic/remote_sync_queue.go
rename to go/pkg/agentic/remote_sync_queue.go
diff --git a/pkg/agentic/remote_sync_queue_example_test.go b/go/pkg/agentic/remote_sync_queue_example_test.go
similarity index 100%
rename from pkg/agentic/remote_sync_queue_example_test.go
rename to go/pkg/agentic/remote_sync_queue_example_test.go
diff --git a/pkg/agentic/remote_sync_queue_test.go b/go/pkg/agentic/remote_sync_queue_test.go
similarity index 100%
rename from pkg/agentic/remote_sync_queue_test.go
rename to go/pkg/agentic/remote_sync_queue_test.go
diff --git a/pkg/agentic/remote_test.go b/go/pkg/agentic/remote_test.go
similarity index 100%
rename from pkg/agentic/remote_test.go
rename to go/pkg/agentic/remote_test.go
diff --git a/pkg/agentic/render_plan_test.go b/go/pkg/agentic/render_plan_test.go
similarity index 100%
rename from pkg/agentic/render_plan_test.go
rename to go/pkg/agentic/render_plan_test.go
diff --git a/pkg/agentic/repo_sync.go b/go/pkg/agentic/repo_sync.go
similarity index 100%
rename from pkg/agentic/repo_sync.go
rename to go/pkg/agentic/repo_sync.go
diff --git a/pkg/agentic/repo_sync_example_test.go b/go/pkg/agentic/repo_sync_example_test.go
similarity index 100%
rename from pkg/agentic/repo_sync_example_test.go
rename to go/pkg/agentic/repo_sync_example_test.go
diff --git a/pkg/agentic/repo_sync_test.go b/go/pkg/agentic/repo_sync_test.go
similarity index 100%
rename from pkg/agentic/repo_sync_test.go
rename to go/pkg/agentic/repo_sync_test.go
diff --git a/pkg/agentic/result_bridge.go b/go/pkg/agentic/result_bridge.go
similarity index 69%
rename from pkg/agentic/result_bridge.go
rename to go/pkg/agentic/result_bridge.go
index 75eebcb8..7b0c0259 100644
--- a/pkg/agentic/result_bridge.go
+++ b/go/pkg/agentic/result_bridge.go
@@ -55,22 +55,3 @@ func toolHandlerFor[In, Out any](action, invalid string, fn func(context.Context
return nil, typed.Value.(Out), nil
}
}
-
-func resourceHandlerFor(action, invalid string, fn func(context.Context) core.Result) mcp.ResourceHandler {
- return func(ctx context.Context, _ *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
- result := fn(ctx)
- if !result.OK {
- failed := failureResult(action, "resource read failed", result)
- err, _ := failed.Value.(error)
- return nil, err
- }
-
- typed := typedResultValue[*mcp.ReadResourceResult](action, invalid, result)
- if !typed.OK {
- err, _ := typed.Value.(error)
- return nil, err
- }
-
- return typed.Value.(*mcp.ReadResourceResult), nil
- }
-}
diff --git a/pkg/agentic/resume.go b/go/pkg/agentic/resume.go
similarity index 100%
rename from pkg/agentic/resume.go
rename to go/pkg/agentic/resume.go
diff --git a/pkg/agentic/resume_example_test.go b/go/pkg/agentic/resume_example_test.go
similarity index 100%
rename from pkg/agentic/resume_example_test.go
rename to go/pkg/agentic/resume_example_test.go
diff --git a/pkg/agentic/resume_test.go b/go/pkg/agentic/resume_test.go
similarity index 100%
rename from pkg/agentic/resume_test.go
rename to go/pkg/agentic/resume_test.go
diff --git a/pkg/agentic/review_queue.go b/go/pkg/agentic/review_queue.go
similarity index 100%
rename from pkg/agentic/review_queue.go
rename to go/pkg/agentic/review_queue.go
diff --git a/pkg/agentic/review_queue_example_test.go b/go/pkg/agentic/review_queue_example_test.go
similarity index 100%
rename from pkg/agentic/review_queue_example_test.go
rename to go/pkg/agentic/review_queue_example_test.go
diff --git a/pkg/agentic/review_queue_extra_test.go b/go/pkg/agentic/review_queue_extra_test.go
similarity index 100%
rename from pkg/agentic/review_queue_extra_test.go
rename to go/pkg/agentic/review_queue_extra_test.go
diff --git a/pkg/agentic/review_queue_test.go b/go/pkg/agentic/review_queue_test.go
similarity index 100%
rename from pkg/agentic/review_queue_test.go
rename to go/pkg/agentic/review_queue_test.go
diff --git a/pkg/agentic/runner.go b/go/pkg/agentic/runner.go
similarity index 100%
rename from pkg/agentic/runner.go
rename to go/pkg/agentic/runner.go
diff --git a/pkg/agentic/runner_example_test.go b/go/pkg/agentic/runner_example_test.go
similarity index 100%
rename from pkg/agentic/runner_example_test.go
rename to go/pkg/agentic/runner_example_test.go
diff --git a/pkg/agentic/runner_test.go b/go/pkg/agentic/runner_test.go
similarity index 100%
rename from pkg/agentic/runner_test.go
rename to go/pkg/agentic/runner_test.go
diff --git a/pkg/agentic/runtime_state.go b/go/pkg/agentic/runtime_state.go
similarity index 100%
rename from pkg/agentic/runtime_state.go
rename to go/pkg/agentic/runtime_state.go
diff --git a/pkg/agentic/runtime_state_test.go b/go/pkg/agentic/runtime_state_test.go
similarity index 100%
rename from pkg/agentic/runtime_state_test.go
rename to go/pkg/agentic/runtime_state_test.go
diff --git a/pkg/agentic/sanitise.go b/go/pkg/agentic/sanitise.go
similarity index 100%
rename from pkg/agentic/sanitise.go
rename to go/pkg/agentic/sanitise.go
diff --git a/pkg/agentic/sanitise_example_test.go b/go/pkg/agentic/sanitise_example_test.go
similarity index 100%
rename from pkg/agentic/sanitise_example_test.go
rename to go/pkg/agentic/sanitise_example_test.go
diff --git a/pkg/agentic/sanitise_test.go b/go/pkg/agentic/sanitise_test.go
similarity index 100%
rename from pkg/agentic/sanitise_test.go
rename to go/pkg/agentic/sanitise_test.go
diff --git a/pkg/agentic/scan.go b/go/pkg/agentic/scan.go
similarity index 100%
rename from pkg/agentic/scan.go
rename to go/pkg/agentic/scan.go
diff --git a/pkg/agentic/scan_example_test.go b/go/pkg/agentic/scan_example_test.go
similarity index 100%
rename from pkg/agentic/scan_example_test.go
rename to go/pkg/agentic/scan_example_test.go
diff --git a/pkg/agentic/scan_test.go b/go/pkg/agentic/scan_test.go
similarity index 100%
rename from pkg/agentic/scan_test.go
rename to go/pkg/agentic/scan_test.go
diff --git a/pkg/agentic/session.go b/go/pkg/agentic/session.go
similarity index 99%
rename from pkg/agentic/session.go
rename to go/pkg/agentic/session.go
index 12637a91..7595498d 100644
--- a/pkg/agentic/session.go
+++ b/go/pkg/agentic/session.go
@@ -1169,11 +1169,6 @@ var resultErrorValue = func(action string, result core.Result) error {
return core.E(action, "request failed", nil)
}
-func validSessionAgentType(agentType string) bool {
- _, ok := normaliseSessionAgentType(agentType)
- return ok
-}
-
func normaliseSessionAgentType(agentType string) (string, bool) {
trimmed := core.Lower(core.Trim(agentType))
if trimmed == "" {
diff --git a/pkg/agentic/session_example_test.go b/go/pkg/agentic/session_example_test.go
similarity index 100%
rename from pkg/agentic/session_example_test.go
rename to go/pkg/agentic/session_example_test.go
diff --git a/pkg/agentic/session_test.go b/go/pkg/agentic/session_test.go
similarity index 100%
rename from pkg/agentic/session_test.go
rename to go/pkg/agentic/session_test.go
diff --git a/pkg/agentic/shutdown.go b/go/pkg/agentic/shutdown.go
similarity index 100%
rename from pkg/agentic/shutdown.go
rename to go/pkg/agentic/shutdown.go
diff --git a/pkg/agentic/shutdown_example_test.go b/go/pkg/agentic/shutdown_example_test.go
similarity index 100%
rename from pkg/agentic/shutdown_example_test.go
rename to go/pkg/agentic/shutdown_example_test.go
diff --git a/pkg/agentic/shutdown_test.go b/go/pkg/agentic/shutdown_test.go
similarity index 100%
rename from pkg/agentic/shutdown_test.go
rename to go/pkg/agentic/shutdown_test.go
diff --git a/pkg/agentic/sprint.go b/go/pkg/agentic/sprint.go
similarity index 100%
rename from pkg/agentic/sprint.go
rename to go/pkg/agentic/sprint.go
diff --git a/pkg/agentic/sprint_example_test.go b/go/pkg/agentic/sprint_example_test.go
similarity index 100%
rename from pkg/agentic/sprint_example_test.go
rename to go/pkg/agentic/sprint_example_test.go
diff --git a/pkg/agentic/sprint_test.go b/go/pkg/agentic/sprint_test.go
similarity index 100%
rename from pkg/agentic/sprint_test.go
rename to go/pkg/agentic/sprint_test.go
diff --git a/pkg/agentic/state.go b/go/pkg/agentic/state.go
similarity index 100%
rename from pkg/agentic/state.go
rename to go/pkg/agentic/state.go
diff --git a/pkg/agentic/state_example_test.go b/go/pkg/agentic/state_example_test.go
similarity index 100%
rename from pkg/agentic/state_example_test.go
rename to go/pkg/agentic/state_example_test.go
diff --git a/pkg/agentic/state_test.go b/go/pkg/agentic/state_test.go
similarity index 100%
rename from pkg/agentic/state_test.go
rename to go/pkg/agentic/state_test.go
diff --git a/pkg/agentic/statestore.go b/go/pkg/agentic/statestore.go
similarity index 100%
rename from pkg/agentic/statestore.go
rename to go/pkg/agentic/statestore.go
diff --git a/pkg/agentic/statestore_test.go b/go/pkg/agentic/statestore_test.go
similarity index 100%
rename from pkg/agentic/statestore_test.go
rename to go/pkg/agentic/statestore_test.go
diff --git a/pkg/agentic/status.go b/go/pkg/agentic/status.go
similarity index 100%
rename from pkg/agentic/status.go
rename to go/pkg/agentic/status.go
diff --git a/pkg/agentic/status_example_test.go b/go/pkg/agentic/status_example_test.go
similarity index 100%
rename from pkg/agentic/status_example_test.go
rename to go/pkg/agentic/status_example_test.go
diff --git a/pkg/agentic/status_extra_test.go b/go/pkg/agentic/status_extra_test.go
similarity index 100%
rename from pkg/agentic/status_extra_test.go
rename to go/pkg/agentic/status_extra_test.go
diff --git a/pkg/agentic/status_logic_test.go b/go/pkg/agentic/status_logic_test.go
similarity index 100%
rename from pkg/agentic/status_logic_test.go
rename to go/pkg/agentic/status_logic_test.go
diff --git a/pkg/agentic/status_test.go b/go/pkg/agentic/status_test.go
similarity index 100%
rename from pkg/agentic/status_test.go
rename to go/pkg/agentic/status_test.go
diff --git a/pkg/agentic/sync.go b/go/pkg/agentic/sync.go
similarity index 100%
rename from pkg/agentic/sync.go
rename to go/pkg/agentic/sync.go
diff --git a/pkg/agentic/sync_example_test.go b/go/pkg/agentic/sync_example_test.go
similarity index 100%
rename from pkg/agentic/sync_example_test.go
rename to go/pkg/agentic/sync_example_test.go
diff --git a/pkg/agentic/sync_test.go b/go/pkg/agentic/sync_test.go
similarity index 100%
rename from pkg/agentic/sync_test.go
rename to go/pkg/agentic/sync_test.go
diff --git a/pkg/agentic/task.go b/go/pkg/agentic/task.go
similarity index 100%
rename from pkg/agentic/task.go
rename to go/pkg/agentic/task.go
diff --git a/pkg/agentic/task_example_test.go b/go/pkg/agentic/task_example_test.go
similarity index 100%
rename from pkg/agentic/task_example_test.go
rename to go/pkg/agentic/task_example_test.go
diff --git a/pkg/agentic/task_test.go b/go/pkg/agentic/task_test.go
similarity index 100%
rename from pkg/agentic/task_test.go
rename to go/pkg/agentic/task_test.go
diff --git a/pkg/agentic/template.go b/go/pkg/agentic/template.go
similarity index 100%
rename from pkg/agentic/template.go
rename to go/pkg/agentic/template.go
diff --git a/pkg/agentic/template_example_test.go b/go/pkg/agentic/template_example_test.go
similarity index 100%
rename from pkg/agentic/template_example_test.go
rename to go/pkg/agentic/template_example_test.go
diff --git a/pkg/agentic/template_test.go b/go/pkg/agentic/template_test.go
similarity index 100%
rename from pkg/agentic/template_test.go
rename to go/pkg/agentic/template_test.go
diff --git a/pkg/agentic/test_assertions_test.go b/go/pkg/agentic/test_assertions_test.go
similarity index 100%
rename from pkg/agentic/test_assertions_test.go
rename to go/pkg/agentic/test_assertions_test.go
diff --git a/pkg/agentic/training_journal.go b/go/pkg/agentic/training_journal.go
similarity index 100%
rename from pkg/agentic/training_journal.go
rename to go/pkg/agentic/training_journal.go
diff --git a/pkg/agentic/training_journal_example_test.go b/go/pkg/agentic/training_journal_example_test.go
similarity index 100%
rename from pkg/agentic/training_journal_example_test.go
rename to go/pkg/agentic/training_journal_example_test.go
diff --git a/pkg/agentic/training_journal_test.go b/go/pkg/agentic/training_journal_test.go
similarity index 100%
rename from pkg/agentic/training_journal_test.go
rename to go/pkg/agentic/training_journal_test.go
diff --git a/pkg/agentic/transport.go b/go/pkg/agentic/transport.go
similarity index 100%
rename from pkg/agentic/transport.go
rename to go/pkg/agentic/transport.go
diff --git a/pkg/agentic/transport_example_test.go b/go/pkg/agentic/transport_example_test.go
similarity index 100%
rename from pkg/agentic/transport_example_test.go
rename to go/pkg/agentic/transport_example_test.go
diff --git a/pkg/agentic/transport_prep_test.go b/go/pkg/agentic/transport_prep_test.go
similarity index 100%
rename from pkg/agentic/transport_prep_test.go
rename to go/pkg/agentic/transport_prep_test.go
diff --git a/pkg/agentic/transport_prep_variants_test.go b/go/pkg/agentic/transport_prep_variants_test.go
similarity index 100%
rename from pkg/agentic/transport_prep_variants_test.go
rename to go/pkg/agentic/transport_prep_variants_test.go
diff --git a/pkg/agentic/transport_test.go b/go/pkg/agentic/transport_test.go
similarity index 100%
rename from pkg/agentic/transport_test.go
rename to go/pkg/agentic/transport_test.go
diff --git a/pkg/agentic/verify.go b/go/pkg/agentic/verify.go
similarity index 100%
rename from pkg/agentic/verify.go
rename to go/pkg/agentic/verify.go
diff --git a/pkg/agentic/verify_example_test.go b/go/pkg/agentic/verify_example_test.go
similarity index 100%
rename from pkg/agentic/verify_example_test.go
rename to go/pkg/agentic/verify_example_test.go
diff --git a/pkg/agentic/verify_extra_test.go b/go/pkg/agentic/verify_extra_test.go
similarity index 100%
rename from pkg/agentic/verify_extra_test.go
rename to go/pkg/agentic/verify_extra_test.go
diff --git a/pkg/agentic/verify_test.go b/go/pkg/agentic/verify_test.go
similarity index 100%
rename from pkg/agentic/verify_test.go
rename to go/pkg/agentic/verify_test.go
diff --git a/pkg/agentic/watch.go b/go/pkg/agentic/watch.go
similarity index 100%
rename from pkg/agentic/watch.go
rename to go/pkg/agentic/watch.go
diff --git a/pkg/agentic/watch_example_test.go b/go/pkg/agentic/watch_example_test.go
similarity index 100%
rename from pkg/agentic/watch_example_test.go
rename to go/pkg/agentic/watch_example_test.go
diff --git a/pkg/agentic/watch_test.go b/go/pkg/agentic/watch_test.go
similarity index 100%
rename from pkg/agentic/watch_test.go
rename to go/pkg/agentic/watch_test.go
diff --git a/pkg/agentic/workspace_stats.go b/go/pkg/agentic/workspace_stats.go
similarity index 100%
rename from pkg/agentic/workspace_stats.go
rename to go/pkg/agentic/workspace_stats.go
diff --git a/pkg/agentic/workspace_stats_test.go b/go/pkg/agentic/workspace_stats_test.go
similarity index 100%
rename from pkg/agentic/workspace_stats_test.go
rename to go/pkg/agentic/workspace_stats_test.go
diff --git a/pkg/brain/actions.go b/go/pkg/brain/actions.go
similarity index 100%
rename from pkg/brain/actions.go
rename to go/pkg/brain/actions.go
diff --git a/pkg/brain/actions_example_test.go b/go/pkg/brain/actions_example_test.go
similarity index 100%
rename from pkg/brain/actions_example_test.go
rename to go/pkg/brain/actions_example_test.go
diff --git a/pkg/brain/actions_test.go b/go/pkg/brain/actions_test.go
similarity index 100%
rename from pkg/brain/actions_test.go
rename to go/pkg/brain/actions_test.go
diff --git a/pkg/brain/alias_test.go b/go/pkg/brain/alias_test.go
similarity index 100%
rename from pkg/brain/alias_test.go
rename to go/pkg/brain/alias_test.go
diff --git a/pkg/brain/brain.go b/go/pkg/brain/brain.go
similarity index 100%
rename from pkg/brain/brain.go
rename to go/pkg/brain/brain.go
diff --git a/pkg/brain/brain_api_test.go b/go/pkg/brain/brain_api_test.go
similarity index 100%
rename from pkg/brain/brain_api_test.go
rename to go/pkg/brain/brain_api_test.go
diff --git a/pkg/brain/brain_example_test.go b/go/pkg/brain/brain_example_test.go
similarity index 100%
rename from pkg/brain/brain_example_test.go
rename to go/pkg/brain/brain_example_test.go
diff --git a/pkg/brain/brain_test.go b/go/pkg/brain/brain_test.go
similarity index 100%
rename from pkg/brain/brain_test.go
rename to go/pkg/brain/brain_test.go
diff --git a/pkg/brain/bridge_test.go b/go/pkg/brain/bridge_test.go
similarity index 100%
rename from pkg/brain/bridge_test.go
rename to go/pkg/brain/bridge_test.go
diff --git a/pkg/brain/compat_adapters_test.go b/go/pkg/brain/compat_adapters_test.go
similarity index 100%
rename from pkg/brain/compat_adapters_test.go
rename to go/pkg/brain/compat_adapters_test.go
diff --git a/pkg/brain/direct.go b/go/pkg/brain/direct.go
similarity index 100%
rename from pkg/brain/direct.go
rename to go/pkg/brain/direct.go
diff --git a/pkg/brain/direct_example_test.go b/go/pkg/brain/direct_example_test.go
similarity index 100%
rename from pkg/brain/direct_example_test.go
rename to go/pkg/brain/direct_example_test.go
diff --git a/pkg/brain/direct_test.go b/go/pkg/brain/direct_test.go
similarity index 100%
rename from pkg/brain/direct_test.go
rename to go/pkg/brain/direct_test.go
diff --git a/pkg/brain/main_test.go b/go/pkg/brain/main_test.go
similarity index 100%
rename from pkg/brain/main_test.go
rename to go/pkg/brain/main_test.go
diff --git a/pkg/brain/messaging.go b/go/pkg/brain/messaging.go
similarity index 100%
rename from pkg/brain/messaging.go
rename to go/pkg/brain/messaging.go
diff --git a/pkg/brain/messaging_example_test.go b/go/pkg/brain/messaging_example_test.go
similarity index 100%
rename from pkg/brain/messaging_example_test.go
rename to go/pkg/brain/messaging_example_test.go
diff --git a/pkg/brain/messaging_test.go b/go/pkg/brain/messaging_test.go
similarity index 100%
rename from pkg/brain/messaging_test.go
rename to go/pkg/brain/messaging_test.go
diff --git a/pkg/brain/provider.go b/go/pkg/brain/provider.go
similarity index 100%
rename from pkg/brain/provider.go
rename to go/pkg/brain/provider.go
diff --git a/pkg/brain/provider_contract.go b/go/pkg/brain/provider_contract.go
similarity index 100%
rename from pkg/brain/provider_contract.go
rename to go/pkg/brain/provider_contract.go
diff --git a/pkg/brain/provider_example_test.go b/go/pkg/brain/provider_example_test.go
similarity index 100%
rename from pkg/brain/provider_example_test.go
rename to go/pkg/brain/provider_example_test.go
diff --git a/pkg/brain/provider_test.go b/go/pkg/brain/provider_test.go
similarity index 100%
rename from pkg/brain/provider_test.go
rename to go/pkg/brain/provider_test.go
diff --git a/pkg/brain/register.go b/go/pkg/brain/register.go
similarity index 100%
rename from pkg/brain/register.go
rename to go/pkg/brain/register.go
diff --git a/pkg/brain/register_example_test.go b/go/pkg/brain/register_example_test.go
similarity index 100%
rename from pkg/brain/register_example_test.go
rename to go/pkg/brain/register_example_test.go
diff --git a/pkg/brain/register_test.go b/go/pkg/brain/register_test.go
similarity index 100%
rename from pkg/brain/register_test.go
rename to go/pkg/brain/register_test.go
diff --git a/pkg/brain/test_assertions_test.go b/go/pkg/brain/test_assertions_test.go
similarity index 100%
rename from pkg/brain/test_assertions_test.go
rename to go/pkg/brain/test_assertions_test.go
diff --git a/pkg/brain/tools.go b/go/pkg/brain/tools.go
similarity index 100%
rename from pkg/brain/tools.go
rename to go/pkg/brain/tools.go
diff --git a/pkg/brain/tools_example_test.go b/go/pkg/brain/tools_example_test.go
similarity index 100%
rename from pkg/brain/tools_example_test.go
rename to go/pkg/brain/tools_example_test.go
diff --git a/pkg/brain/tools_test.go b/go/pkg/brain/tools_test.go
similarity index 100%
rename from pkg/brain/tools_test.go
rename to go/pkg/brain/tools_test.go
diff --git a/pkg/lib/flow/cpp.md b/go/pkg/lib/flow/cpp.md
similarity index 100%
rename from pkg/lib/flow/cpp.md
rename to go/pkg/lib/flow/cpp.md
diff --git a/pkg/lib/flow/docker.md b/go/pkg/lib/flow/docker.md
similarity index 100%
rename from pkg/lib/flow/docker.md
rename to go/pkg/lib/flow/docker.md
diff --git a/pkg/lib/flow/flow.go b/go/pkg/lib/flow/flow.go
similarity index 100%
rename from pkg/lib/flow/flow.go
rename to go/pkg/lib/flow/flow.go
diff --git a/pkg/lib/flow/flow_example_test.go b/go/pkg/lib/flow/flow_example_test.go
similarity index 100%
rename from pkg/lib/flow/flow_example_test.go
rename to go/pkg/lib/flow/flow_example_test.go
diff --git a/pkg/lib/flow/flow_test.go b/go/pkg/lib/flow/flow_test.go
similarity index 100%
rename from pkg/lib/flow/flow_test.go
rename to go/pkg/lib/flow/flow_test.go
diff --git a/pkg/lib/flow/git.md b/go/pkg/lib/flow/git.md
similarity index 100%
rename from pkg/lib/flow/git.md
rename to go/pkg/lib/flow/git.md
diff --git a/pkg/lib/flow/go.md b/go/pkg/lib/flow/go.md
similarity index 100%
rename from pkg/lib/flow/go.md
rename to go/pkg/lib/flow/go.md
diff --git a/pkg/lib/flow/npm.md b/go/pkg/lib/flow/npm.md
similarity index 100%
rename from pkg/lib/flow/npm.md
rename to go/pkg/lib/flow/npm.md
diff --git a/pkg/lib/flow/php.md b/go/pkg/lib/flow/php.md
similarity index 100%
rename from pkg/lib/flow/php.md
rename to go/pkg/lib/flow/php.md
diff --git a/pkg/lib/flow/prod-push-polish.md b/go/pkg/lib/flow/prod-push-polish.md
similarity index 100%
rename from pkg/lib/flow/prod-push-polish.md
rename to go/pkg/lib/flow/prod-push-polish.md
diff --git a/pkg/lib/flow/py.md b/go/pkg/lib/flow/py.md
similarity index 100%
rename from pkg/lib/flow/py.md
rename to go/pkg/lib/flow/py.md
diff --git a/pkg/lib/flow/release.md b/go/pkg/lib/flow/release.md
similarity index 100%
rename from pkg/lib/flow/release.md
rename to go/pkg/lib/flow/release.md
diff --git a/pkg/lib/flow/ts.md b/go/pkg/lib/flow/ts.md
similarity index 100%
rename from pkg/lib/flow/ts.md
rename to go/pkg/lib/flow/ts.md
diff --git a/pkg/lib/flow/upgrade/README.md b/go/pkg/lib/flow/upgrade/README.md
similarity index 100%
rename from pkg/lib/flow/upgrade/README.md
rename to go/pkg/lib/flow/upgrade/README.md
diff --git a/pkg/lib/flow/upgrade/v080-implement.yaml b/go/pkg/lib/flow/upgrade/v080-implement.yaml
similarity index 100%
rename from pkg/lib/flow/upgrade/v080-implement.yaml
rename to go/pkg/lib/flow/upgrade/v080-implement.yaml
diff --git a/pkg/lib/flow/upgrade/v080-plan.yaml b/go/pkg/lib/flow/upgrade/v080-plan.yaml
similarity index 100%
rename from pkg/lib/flow/upgrade/v080-plan.yaml
rename to go/pkg/lib/flow/upgrade/v080-plan.yaml
diff --git a/pkg/lib/lib.go b/go/pkg/lib/lib.go
similarity index 97%
rename from pkg/lib/lib.go
rename to go/pkg/lib/lib.go
index c1f0701d..e63f3ffb 100644
--- a/pkg/lib/lib.go
+++ b/go/pkg/lib/lib.go
@@ -51,11 +51,11 @@ func MountData(c *core.Core) {
}
d := c.Data()
- d.Set("prompts", promptFS)
- d.Set("tasks", taskFS)
- d.Set("flows", flowFS)
- d.Set("personas", personaFS)
- d.Set("workspaces", workspaceFS)
+ _ = d.Set("prompts", promptFS)
+ _ = d.Set("tasks", taskFS)
+ _ = d.Set("flows", flowFS)
+ _ = d.Set("personas", personaFS)
+ _ = d.Set("workspaces", workspaceFS)
}
func ensureMounted() core.Result {
@@ -85,7 +85,7 @@ func ensureMounted() core.Result {
emb := mounted.Value.(*core.Embed)
item.assign(emb)
- mountedData.Set(item.name, emb)
+ _ = mountedData.Set(item.name, emb)
}
data = mountedData
diff --git a/pkg/lib/lib_example_test.go b/go/pkg/lib/lib_example_test.go
similarity index 100%
rename from pkg/lib/lib_example_test.go
rename to go/pkg/lib/lib_example_test.go
diff --git a/pkg/lib/lib_test.go b/go/pkg/lib/lib_test.go
similarity index 71%
rename from pkg/lib/lib_test.go
rename to go/pkg/lib/lib_test.go
index 46e2201d..b9c7707e 100644
--- a/pkg/lib/lib_test.go
+++ b/go/pkg/lib/lib_test.go
@@ -4,7 +4,6 @@ package lib
import (
"embed"
- "runtime"
"testing"
core "dappco.re/go"
@@ -521,32 +520,6 @@ func TestLib_ExtractWorkspace_Good(t *testing.T) {
}
}
-func TestLib_ExtractWorkspaceSubdirs_Good_Case(t *testing.T) {
- dir := t.TempDir()
- data := &WorkspaceData{Repo: "test-repo", Task: "test task"}
-
- requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
-
- refDir := core.JoinPath(dir, ".core", "reference")
- if !testFs.IsDir(refDir) {
- t.Fatalf(".core/reference/ directory not created")
- }
-
- axSpec := core.JoinPath(refDir, "RFC-025-AGENT-EXPERIENCE.md")
- if !testFs.Exists(axSpec) {
- t.Errorf("AX spec not extracted: %s", axSpec)
- }
-
- goFiles := core.PathGlob(core.JoinPath(refDir, "*.go"))
- if len(goFiles) == 0 {
- t.Error("no .go files in .core/reference/")
- }
-
- docsDir := core.JoinPath(refDir, "docs")
- if !testFs.IsDir(docsDir) {
- t.Errorf(".core/reference/docs/ not created")
- }
-}
func TestLib_ExtractWorkspaceTemplate_Good_Case(t *testing.T) {
dir := t.TempDir()
@@ -611,144 +584,6 @@ func TestLib_ExtractWorkspace_Good_AXConventions(t *testing.T) {
}
}
-func TestLib_ReferenceFiles_Good_SPDXHeaders(t *testing.T) {
- _, file, _, ok := runtime.Caller(0)
- if !ok {
- t.Fatal("runtime.Caller(0) failed")
- }
-
- repoRoot := core.PathDir(core.PathDir(core.PathDir(file)))
- refDir := core.JoinPath(repoRoot, ".core", "reference")
- goFiles := core.PathGlob(core.JoinPath(refDir, "*.go"))
- if len(goFiles) == 0 {
- t.Fatalf("no .go files found in %s", refDir)
- }
-
- for _, path := range goFiles {
- assertSPDXHeader(t, path)
- }
-}
-
-func TestLib_ReferenceFs_Good_EmbeddedCopyMatchesSource(t *testing.T) {
- _, file, _, ok := runtime.Caller(0)
- if !ok {
- t.Fatal("runtime.Caller(0) failed")
- }
-
- repoRoot := core.PathDir(core.PathDir(core.PathDir(file)))
- sourcePath := core.JoinPath(repoRoot, ".core", "reference", "fs.go")
- embeddedPath := core.JoinPath(repoRoot, "pkg", "lib", "workspace", "default", ".core", "reference", "fs.go")
-
- source := testFs.Read(sourcePath)
- if !source.OK {
- t.Fatalf("failed to read %s", sourcePath)
- }
-
- embedded := testFs.Read(embeddedPath)
- if !embedded.OK {
- t.Fatalf("failed to read %s", embeddedPath)
- }
-
- if source.Value.(string) != embedded.Value.(string) {
- t.Fatalf("%s diverged from %s", embeddedPath, sourcePath)
- }
-}
-
-func TestLib_ExtractWorkspace_Good_ReferenceFsSecureDefaults(t *testing.T) {
- dir := t.TempDir()
- data := &WorkspaceData{Repo: "test-repo", Task: "tighten extracted reference fs permissions"}
-
- requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
-
- path := core.JoinPath(dir, ".core", "reference", "fs.go")
- r := testFs.Read(path)
- if !r.OK {
- t.Fatalf("failed to read %s", path)
- }
-
- text := r.Value.(string)
- for _, snippet := range []string{
- `return m.WriteMode(p, content, 0600)`,
- `os.MkdirAll(filepath.Dir(full), 0700)`,
- `os.Chmod(full, mode)`,
- `os.MkdirAll(root, 0700)`,
- `os.WriteFile(tmp, []byte(content), 0600)`,
- `os.MkdirAll(vp.Value.(string), 0700)`,
- `os.OpenFile(full, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)`,
- `os.OpenFile(full, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)`,
- } {
- if !core.Contains(text, snippet) {
- t.Errorf("%s missing secure permission default %q", path, snippet)
- }
- }
-}
-
-func TestLib_ExtractWorkspace_Good_ReferenceHeaders(t *testing.T) {
- dir := t.TempDir()
- data := &WorkspaceData{Repo: "test-repo", Task: "carry SPDX headers into workspace references"}
-
- requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
-
- refDir := core.JoinPath(dir, ".core", "reference")
- goFiles := core.PathGlob(core.JoinPath(refDir, "*.go"))
- if len(goFiles) == 0 {
- t.Fatalf("no extracted .go files found in %s", refDir)
- }
-
- for _, path := range goFiles {
- assertSPDXHeader(t, path)
- }
-}
-
-func TestLib_ExtractWorkspace_Good_ReferenceUsageExamples(t *testing.T) {
- dir := t.TempDir()
- data := &WorkspaceData{Repo: "test-repo", Task: "carry AX usage examples into workspace references"}
-
- requireExtractWorkspaceOK(t, ExtractWorkspace("default", dir, data))
-
- cases := map[string][]string{
- core.JoinPath(dir, ".core", "reference", "array.go"): {
- `arr := core.NewArray("prep", "dispatch")`,
- `arr.Add("verify", "merge")`,
- `arr.AddUnique("verify", "verify", "merge")`,
- },
- core.JoinPath(dir, ".core", "reference", "config.go"): {
- `timeout := core.ConfigGet[int](c.Config(), "agent.timeout")`,
- },
- core.JoinPath(dir, ".core", "reference", "embed.go"): {
- `core.AddAsset("docs", "RFC.md", packed)`,
- `r := core.GeneratePack(pkg)`,
- },
- core.JoinPath(dir, ".core", "reference", "error.go"): {
- `if core.Is(err, context.Canceled) { return }`,
- `stack := core.FormatStackTrace(err)`,
- `r := c.Error().Reports(10)`,
- },
- core.JoinPath(dir, ".core", "reference", "log.go"): {
- `log := core.NewLog(core.LogOptions{Level: core.LevelDebug, Output: os.Stdout})`,
- `core.SetRedactKeys("token", "password")`,
- `core.Security("entitlement.denied", "action", "process.run")`,
- },
- core.JoinPath(dir, ".core", "reference", "runtime.go"): {
- `r := c.ServiceStartup(context.Background(), nil)`,
- `r := core.NewRuntime(app)`,
- `name := runtime.ServiceName()`,
- },
- }
-
- for path, snippets := range cases {
- r := testFs.Read(path)
- if !r.OK {
- t.Fatalf("failed to read %s", path)
- }
- text := r.Value.(string)
- for _, snippet := range snippets {
- if !core.Contains(text, snippet) {
- t.Errorf("%s missing usage example snippet %q", path, snippet)
- }
- }
- }
-}
func TestLib_MountEmbed_Bad_Case(t *testing.T) {
result := mountEmbed(promptFiles, "missing-dir")
diff --git a/pkg/lib/persona/ads/auditor.md b/go/pkg/lib/persona/ads/auditor.md
similarity index 100%
rename from pkg/lib/persona/ads/auditor.md
rename to go/pkg/lib/persona/ads/auditor.md
diff --git a/pkg/lib/persona/ads/creative-strategist.md b/go/pkg/lib/persona/ads/creative-strategist.md
similarity index 100%
rename from pkg/lib/persona/ads/creative-strategist.md
rename to go/pkg/lib/persona/ads/creative-strategist.md
diff --git a/pkg/lib/persona/ads/paid-social-strategist.md b/go/pkg/lib/persona/ads/paid-social-strategist.md
similarity index 100%
rename from pkg/lib/persona/ads/paid-social-strategist.md
rename to go/pkg/lib/persona/ads/paid-social-strategist.md
diff --git a/pkg/lib/persona/ads/ppc-strategist.md b/go/pkg/lib/persona/ads/ppc-strategist.md
similarity index 100%
rename from pkg/lib/persona/ads/ppc-strategist.md
rename to go/pkg/lib/persona/ads/ppc-strategist.md
diff --git a/pkg/lib/persona/ads/programmatic-buyer.md b/go/pkg/lib/persona/ads/programmatic-buyer.md
similarity index 100%
rename from pkg/lib/persona/ads/programmatic-buyer.md
rename to go/pkg/lib/persona/ads/programmatic-buyer.md
diff --git a/pkg/lib/persona/ads/search-query-analyst.md b/go/pkg/lib/persona/ads/search-query-analyst.md
similarity index 100%
rename from pkg/lib/persona/ads/search-query-analyst.md
rename to go/pkg/lib/persona/ads/search-query-analyst.md
diff --git a/pkg/lib/persona/ads/tracking-specialist.md b/go/pkg/lib/persona/ads/tracking-specialist.md
similarity index 100%
rename from pkg/lib/persona/ads/tracking-specialist.md
rename to go/pkg/lib/persona/ads/tracking-specialist.md
diff --git a/pkg/lib/persona/blockchain/identity-graph-operator.md b/go/pkg/lib/persona/blockchain/identity-graph-operator.md
similarity index 100%
rename from pkg/lib/persona/blockchain/identity-graph-operator.md
rename to go/pkg/lib/persona/blockchain/identity-graph-operator.md
diff --git a/pkg/lib/persona/blockchain/identity-trust.md b/go/pkg/lib/persona/blockchain/identity-trust.md
similarity index 100%
rename from pkg/lib/persona/blockchain/identity-trust.md
rename to go/pkg/lib/persona/blockchain/identity-trust.md
diff --git a/pkg/lib/persona/blockchain/security-auditor.md b/go/pkg/lib/persona/blockchain/security-auditor.md
similarity index 100%
rename from pkg/lib/persona/blockchain/security-auditor.md
rename to go/pkg/lib/persona/blockchain/security-auditor.md
diff --git a/pkg/lib/persona/blockchain/zk-steward.md b/go/pkg/lib/persona/blockchain/zk-steward.md
similarity index 100%
rename from pkg/lib/persona/blockchain/zk-steward.md
rename to go/pkg/lib/persona/blockchain/zk-steward.md
diff --git a/pkg/lib/persona/code/agents-orchestrator.md b/go/pkg/lib/persona/code/agents-orchestrator.md
similarity index 100%
rename from pkg/lib/persona/code/agents-orchestrator.md
rename to go/pkg/lib/persona/code/agents-orchestrator.md
diff --git a/pkg/lib/persona/code/ai-engineer.md b/go/pkg/lib/persona/code/ai-engineer.md
similarity index 100%
rename from pkg/lib/persona/code/ai-engineer.md
rename to go/pkg/lib/persona/code/ai-engineer.md
diff --git a/pkg/lib/persona/code/autonomous-optimization-architect.md b/go/pkg/lib/persona/code/autonomous-optimization-architect.md
similarity index 100%
rename from pkg/lib/persona/code/autonomous-optimization-architect.md
rename to go/pkg/lib/persona/code/autonomous-optimization-architect.md
diff --git a/pkg/lib/persona/code/backend-architect.md b/go/pkg/lib/persona/code/backend-architect.md
similarity index 100%
rename from pkg/lib/persona/code/backend-architect.md
rename to go/pkg/lib/persona/code/backend-architect.md
diff --git a/pkg/lib/persona/code/data-engineer.md b/go/pkg/lib/persona/code/data-engineer.md
similarity index 100%
rename from pkg/lib/persona/code/data-engineer.md
rename to go/pkg/lib/persona/code/data-engineer.md
diff --git a/pkg/lib/persona/code/developer-advocate.md b/go/pkg/lib/persona/code/developer-advocate.md
similarity index 100%
rename from pkg/lib/persona/code/developer-advocate.md
rename to go/pkg/lib/persona/code/developer-advocate.md
diff --git a/pkg/lib/persona/code/frontend-developer.md b/go/pkg/lib/persona/code/frontend-developer.md
similarity index 100%
rename from pkg/lib/persona/code/frontend-developer.md
rename to go/pkg/lib/persona/code/frontend-developer.md
diff --git a/pkg/lib/persona/code/lsp-index-engineer.md b/go/pkg/lib/persona/code/lsp-index-engineer.md
similarity index 100%
rename from pkg/lib/persona/code/lsp-index-engineer.md
rename to go/pkg/lib/persona/code/lsp-index-engineer.md
diff --git a/pkg/lib/persona/code/rapid-prototyper.md b/go/pkg/lib/persona/code/rapid-prototyper.md
similarity index 100%
rename from pkg/lib/persona/code/rapid-prototyper.md
rename to go/pkg/lib/persona/code/rapid-prototyper.md
diff --git a/pkg/lib/persona/code/senior-developer.md b/go/pkg/lib/persona/code/senior-developer.md
similarity index 100%
rename from pkg/lib/persona/code/senior-developer.md
rename to go/pkg/lib/persona/code/senior-developer.md
diff --git a/pkg/lib/persona/code/technical-writer.md b/go/pkg/lib/persona/code/technical-writer.md
similarity index 100%
rename from pkg/lib/persona/code/technical-writer.md
rename to go/pkg/lib/persona/code/technical-writer.md
diff --git a/pkg/lib/persona/design/brand-guardian.md b/go/pkg/lib/persona/design/brand-guardian.md
similarity index 100%
rename from pkg/lib/persona/design/brand-guardian.md
rename to go/pkg/lib/persona/design/brand-guardian.md
diff --git a/pkg/lib/persona/design/image-prompt-engineer.md b/go/pkg/lib/persona/design/image-prompt-engineer.md
similarity index 100%
rename from pkg/lib/persona/design/image-prompt-engineer.md
rename to go/pkg/lib/persona/design/image-prompt-engineer.md
diff --git a/pkg/lib/persona/design/inclusive-visuals-specialist.md b/go/pkg/lib/persona/design/inclusive-visuals-specialist.md
similarity index 100%
rename from pkg/lib/persona/design/inclusive-visuals-specialist.md
rename to go/pkg/lib/persona/design/inclusive-visuals-specialist.md
diff --git a/pkg/lib/persona/design/security-developer.md b/go/pkg/lib/persona/design/security-developer.md
similarity index 100%
rename from pkg/lib/persona/design/security-developer.md
rename to go/pkg/lib/persona/design/security-developer.md
diff --git a/pkg/lib/persona/design/ui-designer.md b/go/pkg/lib/persona/design/ui-designer.md
similarity index 100%
rename from pkg/lib/persona/design/ui-designer.md
rename to go/pkg/lib/persona/design/ui-designer.md
diff --git a/pkg/lib/persona/design/ux-architect.md b/go/pkg/lib/persona/design/ux-architect.md
similarity index 100%
rename from pkg/lib/persona/design/ux-architect.md
rename to go/pkg/lib/persona/design/ux-architect.md
diff --git a/pkg/lib/persona/design/ux-researcher.md b/go/pkg/lib/persona/design/ux-researcher.md
similarity index 100%
rename from pkg/lib/persona/design/ux-researcher.md
rename to go/pkg/lib/persona/design/ux-researcher.md
diff --git a/pkg/lib/persona/design/visual-storyteller.md b/go/pkg/lib/persona/design/visual-storyteller.md
similarity index 100%
rename from pkg/lib/persona/design/visual-storyteller.md
rename to go/pkg/lib/persona/design/visual-storyteller.md
diff --git a/pkg/lib/persona/design/whimsy-injector.md b/go/pkg/lib/persona/design/whimsy-injector.md
similarity index 100%
rename from pkg/lib/persona/design/whimsy-injector.md
rename to go/pkg/lib/persona/design/whimsy-injector.md
diff --git a/pkg/lib/persona/devops/automator.md b/go/pkg/lib/persona/devops/automator.md
similarity index 100%
rename from pkg/lib/persona/devops/automator.md
rename to go/pkg/lib/persona/devops/automator.md
diff --git a/pkg/lib/persona/devops/junior.md b/go/pkg/lib/persona/devops/junior.md
similarity index 100%
rename from pkg/lib/persona/devops/junior.md
rename to go/pkg/lib/persona/devops/junior.md
diff --git a/pkg/lib/persona/devops/security-developer.md b/go/pkg/lib/persona/devops/security-developer.md
similarity index 100%
rename from pkg/lib/persona/devops/security-developer.md
rename to go/pkg/lib/persona/devops/security-developer.md
diff --git a/pkg/lib/persona/devops/senior.md b/go/pkg/lib/persona/devops/senior.md
similarity index 100%
rename from pkg/lib/persona/devops/senior.md
rename to go/pkg/lib/persona/devops/senior.md
diff --git a/pkg/lib/persona/plan/EXECUTIVE-BRIEF.md b/go/pkg/lib/persona/plan/EXECUTIVE-BRIEF.md
similarity index 100%
rename from pkg/lib/persona/plan/EXECUTIVE-BRIEF.md
rename to go/pkg/lib/persona/plan/EXECUTIVE-BRIEF.md
diff --git a/pkg/lib/persona/plan/QUICKSTART.md b/go/pkg/lib/persona/plan/QUICKSTART.md
similarity index 100%
rename from pkg/lib/persona/plan/QUICKSTART.md
rename to go/pkg/lib/persona/plan/QUICKSTART.md
diff --git a/pkg/lib/persona/plan/coordination/agent-activation-prompts.md b/go/pkg/lib/persona/plan/coordination/agent-activation-prompts.md
similarity index 100%
rename from pkg/lib/persona/plan/coordination/agent-activation-prompts.md
rename to go/pkg/lib/persona/plan/coordination/agent-activation-prompts.md
diff --git a/pkg/lib/persona/plan/coordination/handoff-templates.md b/go/pkg/lib/persona/plan/coordination/handoff-templates.md
similarity index 100%
rename from pkg/lib/persona/plan/coordination/handoff-templates.md
rename to go/pkg/lib/persona/plan/coordination/handoff-templates.md
diff --git a/pkg/lib/persona/plan/experiment-tracker.md b/go/pkg/lib/persona/plan/experiment-tracker.md
similarity index 100%
rename from pkg/lib/persona/plan/experiment-tracker.md
rename to go/pkg/lib/persona/plan/experiment-tracker.md
diff --git a/pkg/lib/persona/plan/nexus-strategy.md b/go/pkg/lib/persona/plan/nexus-strategy.md
similarity index 100%
rename from pkg/lib/persona/plan/nexus-strategy.md
rename to go/pkg/lib/persona/plan/nexus-strategy.md
diff --git a/pkg/lib/persona/plan/playbooks/phase-0-discovery.md b/go/pkg/lib/persona/plan/playbooks/phase-0-discovery.md
similarity index 100%
rename from pkg/lib/persona/plan/playbooks/phase-0-discovery.md
rename to go/pkg/lib/persona/plan/playbooks/phase-0-discovery.md
diff --git a/pkg/lib/persona/plan/playbooks/phase-1-strategy.md b/go/pkg/lib/persona/plan/playbooks/phase-1-strategy.md
similarity index 100%
rename from pkg/lib/persona/plan/playbooks/phase-1-strategy.md
rename to go/pkg/lib/persona/plan/playbooks/phase-1-strategy.md
diff --git a/pkg/lib/persona/plan/playbooks/phase-2-foundation.md b/go/pkg/lib/persona/plan/playbooks/phase-2-foundation.md
similarity index 100%
rename from pkg/lib/persona/plan/playbooks/phase-2-foundation.md
rename to go/pkg/lib/persona/plan/playbooks/phase-2-foundation.md
diff --git a/pkg/lib/persona/plan/playbooks/phase-3-build.md b/go/pkg/lib/persona/plan/playbooks/phase-3-build.md
similarity index 100%
rename from pkg/lib/persona/plan/playbooks/phase-3-build.md
rename to go/pkg/lib/persona/plan/playbooks/phase-3-build.md
diff --git a/pkg/lib/persona/plan/playbooks/phase-4-hardening.md b/go/pkg/lib/persona/plan/playbooks/phase-4-hardening.md
similarity index 100%
rename from pkg/lib/persona/plan/playbooks/phase-4-hardening.md
rename to go/pkg/lib/persona/plan/playbooks/phase-4-hardening.md
diff --git a/pkg/lib/persona/plan/playbooks/phase-5-launch.md b/go/pkg/lib/persona/plan/playbooks/phase-5-launch.md
similarity index 100%
rename from pkg/lib/persona/plan/playbooks/phase-5-launch.md
rename to go/pkg/lib/persona/plan/playbooks/phase-5-launch.md
diff --git a/pkg/lib/persona/plan/playbooks/phase-6-operate.md b/go/pkg/lib/persona/plan/playbooks/phase-6-operate.md
similarity index 100%
rename from pkg/lib/persona/plan/playbooks/phase-6-operate.md
rename to go/pkg/lib/persona/plan/playbooks/phase-6-operate.md
diff --git a/pkg/lib/persona/plan/project-shepherd.md b/go/pkg/lib/persona/plan/project-shepherd.md
similarity index 100%
rename from pkg/lib/persona/plan/project-shepherd.md
rename to go/pkg/lib/persona/plan/project-shepherd.md
diff --git a/pkg/lib/persona/plan/runbooks/scenario-enterprise-feature.md b/go/pkg/lib/persona/plan/runbooks/scenario-enterprise-feature.md
similarity index 100%
rename from pkg/lib/persona/plan/runbooks/scenario-enterprise-feature.md
rename to go/pkg/lib/persona/plan/runbooks/scenario-enterprise-feature.md
diff --git a/pkg/lib/persona/plan/runbooks/scenario-incident-response.md b/go/pkg/lib/persona/plan/runbooks/scenario-incident-response.md
similarity index 100%
rename from pkg/lib/persona/plan/runbooks/scenario-incident-response.md
rename to go/pkg/lib/persona/plan/runbooks/scenario-incident-response.md
diff --git a/pkg/lib/persona/plan/runbooks/scenario-marketing-campaign.md b/go/pkg/lib/persona/plan/runbooks/scenario-marketing-campaign.md
similarity index 100%
rename from pkg/lib/persona/plan/runbooks/scenario-marketing-campaign.md
rename to go/pkg/lib/persona/plan/runbooks/scenario-marketing-campaign.md
diff --git a/pkg/lib/persona/plan/runbooks/scenario-startup-mvp.md b/go/pkg/lib/persona/plan/runbooks/scenario-startup-mvp.md
similarity index 100%
rename from pkg/lib/persona/plan/runbooks/scenario-startup-mvp.md
rename to go/pkg/lib/persona/plan/runbooks/scenario-startup-mvp.md
diff --git a/pkg/lib/persona/plan/senior.md b/go/pkg/lib/persona/plan/senior.md
similarity index 100%
rename from pkg/lib/persona/plan/senior.md
rename to go/pkg/lib/persona/plan/senior.md
diff --git a/pkg/lib/persona/plan/studio-operations.md b/go/pkg/lib/persona/plan/studio-operations.md
similarity index 100%
rename from pkg/lib/persona/plan/studio-operations.md
rename to go/pkg/lib/persona/plan/studio-operations.md
diff --git a/pkg/lib/persona/plan/studio-producer.md b/go/pkg/lib/persona/plan/studio-producer.md
similarity index 100%
rename from pkg/lib/persona/plan/studio-producer.md
rename to go/pkg/lib/persona/plan/studio-producer.md
diff --git a/pkg/lib/persona/product/behavioral-nudge-engine.md b/go/pkg/lib/persona/product/behavioral-nudge-engine.md
similarity index 100%
rename from pkg/lib/persona/product/behavioral-nudge-engine.md
rename to go/pkg/lib/persona/product/behavioral-nudge-engine.md
diff --git a/pkg/lib/persona/product/feedback-synthesizer.md b/go/pkg/lib/persona/product/feedback-synthesizer.md
similarity index 100%
rename from pkg/lib/persona/product/feedback-synthesizer.md
rename to go/pkg/lib/persona/product/feedback-synthesizer.md
diff --git a/pkg/lib/persona/product/security-developer.md b/go/pkg/lib/persona/product/security-developer.md
similarity index 100%
rename from pkg/lib/persona/product/security-developer.md
rename to go/pkg/lib/persona/product/security-developer.md
diff --git a/pkg/lib/persona/product/sprint-prioritizer.md b/go/pkg/lib/persona/product/sprint-prioritizer.md
similarity index 100%
rename from pkg/lib/persona/product/sprint-prioritizer.md
rename to go/pkg/lib/persona/product/sprint-prioritizer.md
diff --git a/pkg/lib/persona/product/trend-researcher.md b/go/pkg/lib/persona/product/trend-researcher.md
similarity index 100%
rename from pkg/lib/persona/product/trend-researcher.md
rename to go/pkg/lib/persona/product/trend-researcher.md
diff --git a/pkg/lib/persona/sales/account-strategist.md b/go/pkg/lib/persona/sales/account-strategist.md
similarity index 100%
rename from pkg/lib/persona/sales/account-strategist.md
rename to go/pkg/lib/persona/sales/account-strategist.md
diff --git a/pkg/lib/persona/sales/coach.md b/go/pkg/lib/persona/sales/coach.md
similarity index 100%
rename from pkg/lib/persona/sales/coach.md
rename to go/pkg/lib/persona/sales/coach.md
diff --git a/pkg/lib/persona/sales/deal-strategist.md b/go/pkg/lib/persona/sales/deal-strategist.md
similarity index 100%
rename from pkg/lib/persona/sales/deal-strategist.md
rename to go/pkg/lib/persona/sales/deal-strategist.md
diff --git a/pkg/lib/persona/sales/discovery-coach.md b/go/pkg/lib/persona/sales/discovery-coach.md
similarity index 100%
rename from pkg/lib/persona/sales/discovery-coach.md
rename to go/pkg/lib/persona/sales/discovery-coach.md
diff --git a/pkg/lib/persona/sales/engineer.md b/go/pkg/lib/persona/sales/engineer.md
similarity index 100%
rename from pkg/lib/persona/sales/engineer.md
rename to go/pkg/lib/persona/sales/engineer.md
diff --git a/pkg/lib/persona/sales/outbound-strategist.md b/go/pkg/lib/persona/sales/outbound-strategist.md
similarity index 100%
rename from pkg/lib/persona/sales/outbound-strategist.md
rename to go/pkg/lib/persona/sales/outbound-strategist.md
diff --git a/pkg/lib/persona/sales/pipeline-analyst.md b/go/pkg/lib/persona/sales/pipeline-analyst.md
similarity index 100%
rename from pkg/lib/persona/sales/pipeline-analyst.md
rename to go/pkg/lib/persona/sales/pipeline-analyst.md
diff --git a/pkg/lib/persona/sales/proposal-strategist.md b/go/pkg/lib/persona/sales/proposal-strategist.md
similarity index 100%
rename from pkg/lib/persona/sales/proposal-strategist.md
rename to go/pkg/lib/persona/sales/proposal-strategist.md
diff --git a/pkg/lib/persona/secops/architect.md b/go/pkg/lib/persona/secops/architect.md
similarity index 100%
rename from pkg/lib/persona/secops/architect.md
rename to go/pkg/lib/persona/secops/architect.md
diff --git a/pkg/lib/persona/secops/developer.md b/go/pkg/lib/persona/secops/developer.md
similarity index 100%
rename from pkg/lib/persona/secops/developer.md
rename to go/pkg/lib/persona/secops/developer.md
diff --git a/pkg/lib/persona/secops/devops.md b/go/pkg/lib/persona/secops/devops.md
similarity index 100%
rename from pkg/lib/persona/secops/devops.md
rename to go/pkg/lib/persona/secops/devops.md
diff --git a/pkg/lib/persona/secops/incident-commander.md b/go/pkg/lib/persona/secops/incident-commander.md
similarity index 100%
rename from pkg/lib/persona/secops/incident-commander.md
rename to go/pkg/lib/persona/secops/incident-commander.md
diff --git a/pkg/lib/persona/secops/junior.md b/go/pkg/lib/persona/secops/junior.md
similarity index 100%
rename from pkg/lib/persona/secops/junior.md
rename to go/pkg/lib/persona/secops/junior.md
diff --git a/pkg/lib/persona/secops/operations.md b/go/pkg/lib/persona/secops/operations.md
similarity index 100%
rename from pkg/lib/persona/secops/operations.md
rename to go/pkg/lib/persona/secops/operations.md
diff --git a/pkg/lib/persona/secops/senior.md b/go/pkg/lib/persona/secops/senior.md
similarity index 100%
rename from pkg/lib/persona/secops/senior.md
rename to go/pkg/lib/persona/secops/senior.md
diff --git a/pkg/lib/persona/smm/carousel-growth-engine.md b/go/pkg/lib/persona/smm/carousel-growth-engine.md
similarity index 100%
rename from pkg/lib/persona/smm/carousel-growth-engine.md
rename to go/pkg/lib/persona/smm/carousel-growth-engine.md
diff --git a/pkg/lib/persona/smm/content-creator.md b/go/pkg/lib/persona/smm/content-creator.md
similarity index 100%
rename from pkg/lib/persona/smm/content-creator.md
rename to go/pkg/lib/persona/smm/content-creator.md
diff --git a/pkg/lib/persona/smm/cultural-intelligence.md b/go/pkg/lib/persona/smm/cultural-intelligence.md
similarity index 100%
rename from pkg/lib/persona/smm/cultural-intelligence.md
rename to go/pkg/lib/persona/smm/cultural-intelligence.md
diff --git a/pkg/lib/persona/smm/growth-hacker.md b/go/pkg/lib/persona/smm/growth-hacker.md
similarity index 100%
rename from pkg/lib/persona/smm/growth-hacker.md
rename to go/pkg/lib/persona/smm/growth-hacker.md
diff --git a/pkg/lib/persona/smm/instagram-curator.md b/go/pkg/lib/persona/smm/instagram-curator.md
similarity index 100%
rename from pkg/lib/persona/smm/instagram-curator.md
rename to go/pkg/lib/persona/smm/instagram-curator.md
diff --git a/pkg/lib/persona/smm/linkedin-content-creator.md b/go/pkg/lib/persona/smm/linkedin-content-creator.md
similarity index 100%
rename from pkg/lib/persona/smm/linkedin-content-creator.md
rename to go/pkg/lib/persona/smm/linkedin-content-creator.md
diff --git a/pkg/lib/persona/smm/reddit-community-builder.md b/go/pkg/lib/persona/smm/reddit-community-builder.md
similarity index 100%
rename from pkg/lib/persona/smm/reddit-community-builder.md
rename to go/pkg/lib/persona/smm/reddit-community-builder.md
diff --git a/pkg/lib/persona/smm/security-developer.md b/go/pkg/lib/persona/smm/security-developer.md
similarity index 100%
rename from pkg/lib/persona/smm/security-developer.md
rename to go/pkg/lib/persona/smm/security-developer.md
diff --git a/pkg/lib/persona/smm/security-secops.md b/go/pkg/lib/persona/smm/security-secops.md
similarity index 100%
rename from pkg/lib/persona/smm/security-secops.md
rename to go/pkg/lib/persona/smm/security-secops.md
diff --git a/pkg/lib/persona/smm/seo-specialist.md b/go/pkg/lib/persona/smm/seo-specialist.md
similarity index 100%
rename from pkg/lib/persona/smm/seo-specialist.md
rename to go/pkg/lib/persona/smm/seo-specialist.md
diff --git a/pkg/lib/persona/smm/social-media-strategist.md b/go/pkg/lib/persona/smm/social-media-strategist.md
similarity index 100%
rename from pkg/lib/persona/smm/social-media-strategist.md
rename to go/pkg/lib/persona/smm/social-media-strategist.md
diff --git a/pkg/lib/persona/smm/tiktok-strategist.md b/go/pkg/lib/persona/smm/tiktok-strategist.md
similarity index 100%
rename from pkg/lib/persona/smm/tiktok-strategist.md
rename to go/pkg/lib/persona/smm/tiktok-strategist.md
diff --git a/pkg/lib/persona/smm/twitter-engager.md b/go/pkg/lib/persona/smm/twitter-engager.md
similarity index 100%
rename from pkg/lib/persona/smm/twitter-engager.md
rename to go/pkg/lib/persona/smm/twitter-engager.md
diff --git a/pkg/lib/persona/spatial/macos-spatial-metal-engineer.md b/go/pkg/lib/persona/spatial/macos-spatial-metal-engineer.md
similarity index 100%
rename from pkg/lib/persona/spatial/macos-spatial-metal-engineer.md
rename to go/pkg/lib/persona/spatial/macos-spatial-metal-engineer.md
diff --git a/pkg/lib/persona/spatial/terminal-integration-specialist.md b/go/pkg/lib/persona/spatial/terminal-integration-specialist.md
similarity index 100%
rename from pkg/lib/persona/spatial/terminal-integration-specialist.md
rename to go/pkg/lib/persona/spatial/terminal-integration-specialist.md
diff --git a/pkg/lib/persona/support/accounts-payable.md b/go/pkg/lib/persona/support/accounts-payable.md
similarity index 100%
rename from pkg/lib/persona/support/accounts-payable.md
rename to go/pkg/lib/persona/support/accounts-payable.md
diff --git a/pkg/lib/persona/support/analytics-reporter.md b/go/pkg/lib/persona/support/analytics-reporter.md
similarity index 100%
rename from pkg/lib/persona/support/analytics-reporter.md
rename to go/pkg/lib/persona/support/analytics-reporter.md
diff --git a/pkg/lib/persona/support/compliance-auditor.md b/go/pkg/lib/persona/support/compliance-auditor.md
similarity index 100%
rename from pkg/lib/persona/support/compliance-auditor.md
rename to go/pkg/lib/persona/support/compliance-auditor.md
diff --git a/pkg/lib/persona/support/executive-summary-generator.md b/go/pkg/lib/persona/support/executive-summary-generator.md
similarity index 100%
rename from pkg/lib/persona/support/executive-summary-generator.md
rename to go/pkg/lib/persona/support/executive-summary-generator.md
diff --git a/pkg/lib/persona/support/finance-tracker.md b/go/pkg/lib/persona/support/finance-tracker.md
similarity index 100%
rename from pkg/lib/persona/support/finance-tracker.md
rename to go/pkg/lib/persona/support/finance-tracker.md
diff --git a/pkg/lib/persona/support/infrastructure-maintainer.md b/go/pkg/lib/persona/support/infrastructure-maintainer.md
similarity index 100%
rename from pkg/lib/persona/support/infrastructure-maintainer.md
rename to go/pkg/lib/persona/support/infrastructure-maintainer.md
diff --git a/pkg/lib/persona/support/legal-compliance-checker.md b/go/pkg/lib/persona/support/legal-compliance-checker.md
similarity index 100%
rename from pkg/lib/persona/support/legal-compliance-checker.md
rename to go/pkg/lib/persona/support/legal-compliance-checker.md
diff --git a/pkg/lib/persona/support/responder.md b/go/pkg/lib/persona/support/responder.md
similarity index 100%
rename from pkg/lib/persona/support/responder.md
rename to go/pkg/lib/persona/support/responder.md
diff --git a/pkg/lib/persona/support/security-developer.md b/go/pkg/lib/persona/support/security-developer.md
similarity index 100%
rename from pkg/lib/persona/support/security-developer.md
rename to go/pkg/lib/persona/support/security-developer.md
diff --git a/pkg/lib/persona/support/security-secops.md b/go/pkg/lib/persona/support/security-secops.md
similarity index 100%
rename from pkg/lib/persona/support/security-secops.md
rename to go/pkg/lib/persona/support/security-secops.md
diff --git a/pkg/lib/persona/testing/accessibility-auditor.md b/go/pkg/lib/persona/testing/accessibility-auditor.md
similarity index 100%
rename from pkg/lib/persona/testing/accessibility-auditor.md
rename to go/pkg/lib/persona/testing/accessibility-auditor.md
diff --git a/pkg/lib/persona/testing/api-tester.md b/go/pkg/lib/persona/testing/api-tester.md
similarity index 100%
rename from pkg/lib/persona/testing/api-tester.md
rename to go/pkg/lib/persona/testing/api-tester.md
diff --git a/pkg/lib/persona/testing/evidence-collector.md b/go/pkg/lib/persona/testing/evidence-collector.md
similarity index 100%
rename from pkg/lib/persona/testing/evidence-collector.md
rename to go/pkg/lib/persona/testing/evidence-collector.md
diff --git a/pkg/lib/persona/testing/model-qa.md b/go/pkg/lib/persona/testing/model-qa.md
similarity index 100%
rename from pkg/lib/persona/testing/model-qa.md
rename to go/pkg/lib/persona/testing/model-qa.md
diff --git a/pkg/lib/persona/testing/performance-benchmarker.md b/go/pkg/lib/persona/testing/performance-benchmarker.md
similarity index 100%
rename from pkg/lib/persona/testing/performance-benchmarker.md
rename to go/pkg/lib/persona/testing/performance-benchmarker.md
diff --git a/pkg/lib/persona/testing/reality-checker.md b/go/pkg/lib/persona/testing/reality-checker.md
similarity index 100%
rename from pkg/lib/persona/testing/reality-checker.md
rename to go/pkg/lib/persona/testing/reality-checker.md
diff --git a/pkg/lib/persona/testing/security-developer.md b/go/pkg/lib/persona/testing/security-developer.md
similarity index 100%
rename from pkg/lib/persona/testing/security-developer.md
rename to go/pkg/lib/persona/testing/security-developer.md
diff --git a/pkg/lib/persona/testing/test-results-analyzer.md b/go/pkg/lib/persona/testing/test-results-analyzer.md
similarity index 100%
rename from pkg/lib/persona/testing/test-results-analyzer.md
rename to go/pkg/lib/persona/testing/test-results-analyzer.md
diff --git a/pkg/lib/persona/testing/tool-evaluator.md b/go/pkg/lib/persona/testing/tool-evaluator.md
similarity index 100%
rename from pkg/lib/persona/testing/tool-evaluator.md
rename to go/pkg/lib/persona/testing/tool-evaluator.md
diff --git a/pkg/lib/persona/testing/workflow-optimizer.md b/go/pkg/lib/persona/testing/workflow-optimizer.md
similarity index 100%
rename from pkg/lib/persona/testing/workflow-optimizer.md
rename to go/pkg/lib/persona/testing/workflow-optimizer.md
diff --git a/pkg/lib/prompt/coding.md b/go/pkg/lib/prompt/coding.md
similarity index 100%
rename from pkg/lib/prompt/coding.md
rename to go/pkg/lib/prompt/coding.md
diff --git a/pkg/lib/prompt/conventions.md b/go/pkg/lib/prompt/conventions.md
similarity index 100%
rename from pkg/lib/prompt/conventions.md
rename to go/pkg/lib/prompt/conventions.md
diff --git a/pkg/lib/prompt/default.md b/go/pkg/lib/prompt/default.md
similarity index 100%
rename from pkg/lib/prompt/default.md
rename to go/pkg/lib/prompt/default.md
diff --git a/pkg/lib/prompt/security.md b/go/pkg/lib/prompt/security.md
similarity index 100%
rename from pkg/lib/prompt/security.md
rename to go/pkg/lib/prompt/security.md
diff --git a/pkg/lib/prompt/verify.md b/go/pkg/lib/prompt/verify.md
similarity index 100%
rename from pkg/lib/prompt/verify.md
rename to go/pkg/lib/prompt/verify.md
diff --git a/pkg/lib/task/api-consistency.yaml b/go/pkg/lib/task/api-consistency.yaml
similarity index 100%
rename from pkg/lib/task/api-consistency.yaml
rename to go/pkg/lib/task/api-consistency.yaml
diff --git a/pkg/lib/task/bug-fix.yaml b/go/pkg/lib/task/bug-fix.yaml
similarity index 100%
rename from pkg/lib/task/bug-fix.yaml
rename to go/pkg/lib/task/bug-fix.yaml
diff --git a/pkg/lib/task/code/dead-code.md b/go/pkg/lib/task/code/dead-code.md
similarity index 100%
rename from pkg/lib/task/code/dead-code.md
rename to go/pkg/lib/task/code/dead-code.md
diff --git a/pkg/lib/task/code/refactor.md b/go/pkg/lib/task/code/refactor.md
similarity index 100%
rename from pkg/lib/task/code/refactor.md
rename to go/pkg/lib/task/code/refactor.md
diff --git a/pkg/lib/task/code/review.md b/go/pkg/lib/task/code/review.md
similarity index 100%
rename from pkg/lib/task/code/review.md
rename to go/pkg/lib/task/code/review.md
diff --git a/pkg/lib/task/code/review/conventions.md b/go/pkg/lib/task/code/review/conventions.md
similarity index 100%
rename from pkg/lib/task/code/review/conventions.md
rename to go/pkg/lib/task/code/review/conventions.md
diff --git a/pkg/lib/task/code/review/plan.yaml b/go/pkg/lib/task/code/review/plan.yaml
similarity index 100%
rename from pkg/lib/task/code/review/plan.yaml
rename to go/pkg/lib/task/code/review/plan.yaml
diff --git a/pkg/lib/task/code/review/severity.md b/go/pkg/lib/task/code/review/severity.md
similarity index 100%
rename from pkg/lib/task/code/review/severity.md
rename to go/pkg/lib/task/code/review/severity.md
diff --git a/pkg/lib/task/code/simplifier.md b/go/pkg/lib/task/code/simplifier.md
similarity index 100%
rename from pkg/lib/task/code/simplifier.md
rename to go/pkg/lib/task/code/simplifier.md
diff --git a/pkg/lib/task/code/simplifier/patterns.md b/go/pkg/lib/task/code/simplifier/patterns.md
similarity index 100%
rename from pkg/lib/task/code/simplifier/patterns.md
rename to go/pkg/lib/task/code/simplifier/patterns.md
diff --git a/pkg/lib/task/code/test-gaps.md b/go/pkg/lib/task/code/test-gaps.md
similarity index 100%
rename from pkg/lib/task/code/test-gaps.md
rename to go/pkg/lib/task/code/test-gaps.md
diff --git a/pkg/lib/task/dependency-audit.yaml b/go/pkg/lib/task/dependency-audit.yaml
similarity index 100%
rename from pkg/lib/task/dependency-audit.yaml
rename to go/pkg/lib/task/dependency-audit.yaml
diff --git a/pkg/lib/task/doc-sync.yaml b/go/pkg/lib/task/doc-sync.yaml
similarity index 100%
rename from pkg/lib/task/doc-sync.yaml
rename to go/pkg/lib/task/doc-sync.yaml
diff --git a/pkg/lib/task/feature-port.yaml b/go/pkg/lib/task/feature-port.yaml
similarity index 100%
rename from pkg/lib/task/feature-port.yaml
rename to go/pkg/lib/task/feature-port.yaml
diff --git a/pkg/lib/task/new-feature.yaml b/go/pkg/lib/task/new-feature.yaml
similarity index 100%
rename from pkg/lib/task/new-feature.yaml
rename to go/pkg/lib/task/new-feature.yaml
diff --git a/pkg/lib/workspace/default/CLAUDE.md.tmpl b/go/pkg/lib/workspace/default/CLAUDE.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/default/CLAUDE.md.tmpl
rename to go/pkg/lib/workspace/default/CLAUDE.md.tmpl
diff --git a/pkg/lib/workspace/default/CODEX-PHP.md.tmpl b/go/pkg/lib/workspace/default/CODEX-PHP.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/default/CODEX-PHP.md.tmpl
rename to go/pkg/lib/workspace/default/CODEX-PHP.md.tmpl
diff --git a/pkg/lib/workspace/default/CODEX.md.tmpl b/go/pkg/lib/workspace/default/CODEX.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/default/CODEX.md.tmpl
rename to go/pkg/lib/workspace/default/CODEX.md.tmpl
diff --git a/pkg/lib/workspace/default/CONTEXT.md.tmpl b/go/pkg/lib/workspace/default/CONTEXT.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/default/CONTEXT.md.tmpl
rename to go/pkg/lib/workspace/default/CONTEXT.md.tmpl
diff --git a/pkg/lib/workspace/default/PROMPT.md.tmpl b/go/pkg/lib/workspace/default/PROMPT.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/default/PROMPT.md.tmpl
rename to go/pkg/lib/workspace/default/PROMPT.md.tmpl
diff --git a/pkg/lib/workspace/default/TODO.md.tmpl b/go/pkg/lib/workspace/default/TODO.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/default/TODO.md.tmpl
rename to go/pkg/lib/workspace/default/TODO.md.tmpl
diff --git a/pkg/lib/workspace/default/go.work.tmpl b/go/pkg/lib/workspace/default/go.work.tmpl
similarity index 100%
rename from pkg/lib/workspace/default/go.work.tmpl
rename to go/pkg/lib/workspace/default/go.work.tmpl
diff --git a/pkg/lib/workspace/review/CLAUDE.md.tmpl b/go/pkg/lib/workspace/review/CLAUDE.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/review/CLAUDE.md.tmpl
rename to go/pkg/lib/workspace/review/CLAUDE.md.tmpl
diff --git a/pkg/lib/workspace/review/PROMPT.md.tmpl b/go/pkg/lib/workspace/review/PROMPT.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/review/PROMPT.md.tmpl
rename to go/pkg/lib/workspace/review/PROMPT.md.tmpl
diff --git a/pkg/lib/workspace/review/REVIEW.md.tmpl b/go/pkg/lib/workspace/review/REVIEW.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/review/REVIEW.md.tmpl
rename to go/pkg/lib/workspace/review/REVIEW.md.tmpl
diff --git a/pkg/lib/workspace/security/CLAUDE.md.tmpl b/go/pkg/lib/workspace/security/CLAUDE.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/security/CLAUDE.md.tmpl
rename to go/pkg/lib/workspace/security/CLAUDE.md.tmpl
diff --git a/pkg/lib/workspace/security/FINDINGS.md.tmpl b/go/pkg/lib/workspace/security/FINDINGS.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/security/FINDINGS.md.tmpl
rename to go/pkg/lib/workspace/security/FINDINGS.md.tmpl
diff --git a/pkg/lib/workspace/security/TODO.md.tmpl b/go/pkg/lib/workspace/security/TODO.md.tmpl
similarity index 100%
rename from pkg/lib/workspace/security/TODO.md.tmpl
rename to go/pkg/lib/workspace/security/TODO.md.tmpl
diff --git a/pkg/messages/messages.go b/go/pkg/messages/messages.go
similarity index 100%
rename from pkg/messages/messages.go
rename to go/pkg/messages/messages.go
diff --git a/pkg/messages/messages_example_test.go b/go/pkg/messages/messages_example_test.go
similarity index 100%
rename from pkg/messages/messages_example_test.go
rename to go/pkg/messages/messages_example_test.go
diff --git a/pkg/messages/messages_test.go b/go/pkg/messages/messages_test.go
similarity index 100%
rename from pkg/messages/messages_test.go
rename to go/pkg/messages/messages_test.go
diff --git a/pkg/monitor/compat_adapters_test.go b/go/pkg/monitor/compat_adapters_test.go
similarity index 100%
rename from pkg/monitor/compat_adapters_test.go
rename to go/pkg/monitor/compat_adapters_test.go
diff --git a/pkg/monitor/harvest.go b/go/pkg/monitor/harvest.go
similarity index 93%
rename from pkg/monitor/harvest.go
rename to go/pkg/monitor/harvest.go
index aa7774ac..616a4532 100644
--- a/pkg/monitor/harvest.go
+++ b/go/pkg/monitor/harvest.go
@@ -220,18 +220,6 @@ func (m *Subsystem) countChangedFiles(repoDir string) int {
return len(lines)
}
-// _ = m.pushBranch("/srv/.core/workspace/core/go-io/task-5/repo", "feature/ax-cleanup")
-var pushBranch = func(m *Subsystem, repoDir, branch string) error {
- processResult := m.Core().Process().RunIn(context.Background(), repoDir, "git", "push", "origin", branch)
- if !processResult.OK {
- if err, ok := processResult.Value.(error); ok {
- return core.E("harvest.pushBranch", "push failed", err)
- }
- return core.E("harvest.pushBranch", "push failed", nil)
- }
- return nil
-}
-
// updateStatus(workspaceDir, "ready-for-review", "")
func updateStatus(workspaceDir, status, question string) {
statusResult := fs.Read(agentic.WorkspaceStatusPath(workspaceDir))
diff --git a/pkg/monitor/harvest_example_test.go b/go/pkg/monitor/harvest_example_test.go
similarity index 100%
rename from pkg/monitor/harvest_example_test.go
rename to go/pkg/monitor/harvest_example_test.go
diff --git a/pkg/monitor/harvest_test.go b/go/pkg/monitor/harvest_test.go
similarity index 100%
rename from pkg/monitor/harvest_test.go
rename to go/pkg/monitor/harvest_test.go
diff --git a/pkg/monitor/monitor.go b/go/pkg/monitor/monitor.go
similarity index 100%
rename from pkg/monitor/monitor.go
rename to go/pkg/monitor/monitor.go
diff --git a/pkg/monitor/monitor_api_test.go b/go/pkg/monitor/monitor_api_test.go
similarity index 100%
rename from pkg/monitor/monitor_api_test.go
rename to go/pkg/monitor/monitor_api_test.go
diff --git a/pkg/monitor/monitor_example_test.go b/go/pkg/monitor/monitor_example_test.go
similarity index 100%
rename from pkg/monitor/monitor_example_test.go
rename to go/pkg/monitor/monitor_example_test.go
diff --git a/pkg/monitor/monitor_test.go b/go/pkg/monitor/monitor_test.go
similarity index 100%
rename from pkg/monitor/monitor_test.go
rename to go/pkg/monitor/monitor_test.go
diff --git a/pkg/monitor/register.go b/go/pkg/monitor/register.go
similarity index 100%
rename from pkg/monitor/register.go
rename to go/pkg/monitor/register.go
diff --git a/pkg/monitor/register_example_test.go b/go/pkg/monitor/register_example_test.go
similarity index 100%
rename from pkg/monitor/register_example_test.go
rename to go/pkg/monitor/register_example_test.go
diff --git a/pkg/monitor/register_test.go b/go/pkg/monitor/register_test.go
similarity index 100%
rename from pkg/monitor/register_test.go
rename to go/pkg/monitor/register_test.go
diff --git a/pkg/monitor/sync.go b/go/pkg/monitor/sync.go
similarity index 100%
rename from pkg/monitor/sync.go
rename to go/pkg/monitor/sync.go
diff --git a/pkg/monitor/sync_example_test.go b/go/pkg/monitor/sync_example_test.go
similarity index 100%
rename from pkg/monitor/sync_example_test.go
rename to go/pkg/monitor/sync_example_test.go
diff --git a/pkg/monitor/sync_test.go b/go/pkg/monitor/sync_test.go
similarity index 100%
rename from pkg/monitor/sync_test.go
rename to go/pkg/monitor/sync_test.go
diff --git a/pkg/monitor/test_assertions_test.go b/go/pkg/monitor/test_assertions_test.go
similarity index 100%
rename from pkg/monitor/test_assertions_test.go
rename to go/pkg/monitor/test_assertions_test.go
diff --git a/pkg/runner/paths.go b/go/pkg/runner/paths.go
similarity index 100%
rename from pkg/runner/paths.go
rename to go/pkg/runner/paths.go
diff --git a/pkg/runner/paths_example_test.go b/go/pkg/runner/paths_example_test.go
similarity index 100%
rename from pkg/runner/paths_example_test.go
rename to go/pkg/runner/paths_example_test.go
diff --git a/pkg/runner/paths_test.go b/go/pkg/runner/paths_test.go
similarity index 100%
rename from pkg/runner/paths_test.go
rename to go/pkg/runner/paths_test.go
diff --git a/pkg/runner/queue.go b/go/pkg/runner/queue.go
similarity index 100%
rename from pkg/runner/queue.go
rename to go/pkg/runner/queue.go
diff --git a/pkg/runner/queue_example_test.go b/go/pkg/runner/queue_example_test.go
similarity index 100%
rename from pkg/runner/queue_example_test.go
rename to go/pkg/runner/queue_example_test.go
diff --git a/pkg/runner/queue_test.go b/go/pkg/runner/queue_test.go
similarity index 100%
rename from pkg/runner/queue_test.go
rename to go/pkg/runner/queue_test.go
diff --git a/pkg/runner/runner.go b/go/pkg/runner/runner.go
similarity index 100%
rename from pkg/runner/runner.go
rename to go/pkg/runner/runner.go
diff --git a/pkg/runner/runner_example_test.go b/go/pkg/runner/runner_example_test.go
similarity index 100%
rename from pkg/runner/runner_example_test.go
rename to go/pkg/runner/runner_example_test.go
diff --git a/pkg/runner/runner_test.go b/go/pkg/runner/runner_test.go
similarity index 100%
rename from pkg/runner/runner_test.go
rename to go/pkg/runner/runner_test.go
diff --git a/pkg/runner/test_assertions_test.go b/go/pkg/runner/test_assertions_test.go
similarity index 100%
rename from pkg/runner/test_assertions_test.go
rename to go/pkg/runner/test_assertions_test.go
diff --git a/pkg/setup/config.go b/go/pkg/setup/config.go
similarity index 100%
rename from pkg/setup/config.go
rename to go/pkg/setup/config.go
diff --git a/pkg/setup/config_example_test.go b/go/pkg/setup/config_example_test.go
similarity index 100%
rename from pkg/setup/config_example_test.go
rename to go/pkg/setup/config_example_test.go
diff --git a/pkg/setup/config_test.go b/go/pkg/setup/config_test.go
similarity index 100%
rename from pkg/setup/config_test.go
rename to go/pkg/setup/config_test.go
diff --git a/pkg/setup/detect.go b/go/pkg/setup/detect.go
similarity index 100%
rename from pkg/setup/detect.go
rename to go/pkg/setup/detect.go
diff --git a/pkg/setup/detect_example_test.go b/go/pkg/setup/detect_example_test.go
similarity index 100%
rename from pkg/setup/detect_example_test.go
rename to go/pkg/setup/detect_example_test.go
diff --git a/pkg/setup/detect_test.go b/go/pkg/setup/detect_test.go
similarity index 100%
rename from pkg/setup/detect_test.go
rename to go/pkg/setup/detect_test.go
diff --git a/pkg/setup/service.go b/go/pkg/setup/service.go
similarity index 100%
rename from pkg/setup/service.go
rename to go/pkg/setup/service.go
diff --git a/pkg/setup/service_example_test.go b/go/pkg/setup/service_example_test.go
similarity index 100%
rename from pkg/setup/service_example_test.go
rename to go/pkg/setup/service_example_test.go
diff --git a/pkg/setup/service_external_test.go b/go/pkg/setup/service_external_test.go
similarity index 100%
rename from pkg/setup/service_external_test.go
rename to go/pkg/setup/service_external_test.go
diff --git a/pkg/setup/service_test.go b/go/pkg/setup/service_test.go
similarity index 100%
rename from pkg/setup/service_test.go
rename to go/pkg/setup/service_test.go
diff --git a/pkg/setup/setup.go b/go/pkg/setup/setup.go
similarity index 100%
rename from pkg/setup/setup.go
rename to go/pkg/setup/setup.go
diff --git a/pkg/setup/setup_example_test.go b/go/pkg/setup/setup_example_test.go
similarity index 100%
rename from pkg/setup/setup_example_test.go
rename to go/pkg/setup/setup_example_test.go
diff --git a/pkg/setup/setup_test.go b/go/pkg/setup/setup_test.go
similarity index 100%
rename from pkg/setup/setup_test.go
rename to go/pkg/setup/setup_test.go
diff --git a/version.go b/go/version.go
similarity index 100%
rename from version.go
rename to go/version.go
diff --git a/version_example_test.go b/go/version_example_test.go
similarity index 100%
rename from version_example_test.go
rename to go/version_example_test.go
diff --git a/llm.txt b/llm.txt
deleted file mode 100644
index fd1457e3..00000000
--- a/llm.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-# core/agent — AI Agent Orchestration
-
-> dappco.re/go/agent — Dispatch, monitor, and verify AI agents (Codex, Claude,
-> Gemini) working on sandboxed repositories. MCP server for agent coordination.
-
-## Entry Points
-
-- CLAUDE.md — Agent instructions, build commands, architecture overview
-- pkg/agentic/ — Core orchestration (dispatch, prep, verify, scan, review)
-
-## Package Layout
-
-- cmd/core-agent/ — CLI entry point (97 lines — just core.New + services)
-- pkg/agentic/ — Agent orchestration: dispatch, workspace prep, PR creation, verification
-- pkg/brain/ — OpenBrain memory integration
-- pkg/lib/ — Embedded templates, personas, flows, workspace scaffolds
-- pkg/messages/ — Typed IPC message definitions (12 message types)
-- pkg/monitor/ — Agent monitoring, notifications, completion tracking
-- pkg/setup/ — Workspace detection and scaffolding
-
-## Test Coverage
-
-- 840 tests, 79.9% statement coverage
-- 92% AX-7 (Good/Bad/Ugly) category coverage
-- 14 functions awaiting go-process v0.7.0 for full testability
-- Zero os/exec imports in source — all commands via go-process
-
-## Conventions
-
-Follows RFC-025 Agent Experience (AX) principles.
-Test naming: TestFile_Function_{Good,Bad,Ugly}
-See: https://core.help/specs/RFC-025-AGENT-EXPERIENCE/
diff --git a/composer.json b/php/composer.json
similarity index 100%
rename from composer.json
rename to php/composer.json
diff --git a/pkg/.DS_Store b/pkg/.DS_Store
deleted file mode 100644
index 503ae2b9..00000000
Binary files a/pkg/.DS_Store and /dev/null differ
diff --git a/pkg/lib/.DS_Store b/pkg/lib/.DS_Store
deleted file mode 100644
index 18e9b7f2..00000000
Binary files a/pkg/lib/.DS_Store and /dev/null differ
diff --git a/pkg/lib/workspace/default/.core/reference/RFC-025-AGENT-EXPERIENCE.md b/pkg/lib/workspace/default/.core/reference/RFC-025-AGENT-EXPERIENCE.md
deleted file mode 100644
index a18e6bb4..00000000
--- a/pkg/lib/workspace/default/.core/reference/RFC-025-AGENT-EXPERIENCE.md
+++ /dev/null
@@ -1,588 +0,0 @@
-# RFC-025: Agent Experience (AX) Design Principles
-
-- **Status:** Active
-- **Authors:** Snider, Cladius
-- **Date:** 2026-03-25
-- **Applies to:** All Core ecosystem packages (CoreGO, CorePHP, CoreTS, core-agent)
-
-## Abstract
-
-Agent Experience (AX) is a design paradigm for software systems where the primary code consumer is an AI agent, not a human developer. AX sits alongside User Experience (UX) and Developer Experience (DX) as the third era of interface design.
-
-This RFC establishes AX as a formal design principle for the Core ecosystem and defines the conventions that follow from it.
-
-## Motivation
-
-As of early 2026, AI agents write, review, and maintain the majority of code in the Core ecosystem. The original author has not manually edited code (outside of Core struct design) since October 2025. Code is processed semantically — agents reason about intent, not characters.
-
-Design patterns inherited from the human-developer era optimise for the wrong consumer:
-
-- **Short names** save keystrokes but increase semantic ambiguity
-- **Functional option chains** are fluent for humans but opaque for agents tracing configuration
-- **Error-at-every-call-site** produces 50% boilerplate that obscures intent
-- **Generic type parameters** force agents to carry type context that the runtime already has
-- **Panic-hiding conventions** (`Must*`) create implicit control flow that agents must special-case
-- **Raw exec.Command** bypasses Core primitives — untestable, no entitlement check, path traversal risk
-
-AX acknowledges this shift and provides principles for designing code, APIs, file structures, and conventions that serve AI agents as first-class consumers.
-
-## The Three Eras
-
-| Era | Primary Consumer | Optimises For | Key Metric |
-|-----|-----------------|---------------|------------|
-| UX | End users | Discoverability, forgiveness, visual clarity | Task completion time |
-| DX | Developers | Typing speed, IDE support, convention familiarity | Time to first commit |
-| AX | AI agents | Predictability, composability, semantic navigation | Correct-on-first-pass rate |
-
-AX does not replace UX or DX. End users still need good UX. Developers still need good DX. But when the primary code author and maintainer is an AI agent, the codebase should be designed for that consumer first.
-
-## Principles
-
-### 1. Predictable Names Over Short Names
-
-Names are tokens that agents pattern-match across languages and contexts. Abbreviations introduce mapping overhead.
-
-```
-Config not Cfg
-Service not Srv
-Embed not Emb
-Error not Err (as a subsystem name; err for local variables is fine)
-Options not Opts
-```
-
-**Rule:** If a name would require a comment to explain, it is too short.
-
-**Exception:** Industry-standard abbreviations that are universally understood (`HTTP`, `URL`, `ID`, `IPC`, `I18n`) are acceptable. The test: would an agent trained on any mainstream language recognise it without context?
-
-### 2. Comments as Usage Examples
-
-The function signature tells WHAT. The comment shows HOW with real values.
-
-```go
-// Entitled checks if an action is permitted.
-//
-// e := c.Entitled("process.run")
-// e := c.Entitled("social.accounts", 3)
-// if e.Allowed { proceed() }
-
-// WriteAtomic writes via temp file then rename (safe for concurrent readers).
-//
-// r := fs.WriteAtomic("/status.json", data)
-
-// Action registers or invokes a named callable.
-//
-// c.Action("git.log", handler) // register
-// c.Action("git.log").Run(ctx, opts) // invoke
-```
-
-**Rule:** If a comment restates what the type signature already says, delete it. If a comment shows a concrete usage with realistic values, keep it.
-
-**Rationale:** Agents learn from examples more effectively than from descriptions. A comment like "Run executes the setup process" adds zero information. A comment like `setup.Run(setup.Options{Path: ".", Template: "auto"})` teaches an agent exactly how to call the function.
-
-### 3. Path Is Documentation
-
-File and directory paths should be self-describing. An agent navigating the filesystem should understand what it is looking at without reading a README.
-
-```
-pkg/agentic/dispatch.go — agent dispatch logic
-pkg/agentic/handlers.go — IPC event handlers
-pkg/lib/task/bug-fix.yaml — bug fix plan template
-pkg/lib/persona/engineering/ — engineering personas
-flow/deploy/to/homelab.yaml — deploy TO the homelab
-template/dir/workspace/default/ — default workspace scaffold
-docs/RFC.md — authoritative API contract
-```
-
-**Rule:** If an agent needs to read a file to understand what a directory contains, the directory naming has failed.
-
-**Corollary:** The unified path convention (folder structure = HTTP route = CLI command = test path) is AX-native. One path, every surface.
-
-### 4. Templates Over Freeform
-
-When an agent generates code from a template, the output is constrained to known-good shapes. When an agent writes freeform, the output varies.
-
-```go
-// Template-driven — consistent output
-lib.ExtractWorkspace("default", targetDir, &lib.WorkspaceData{
- Repo: "go-io", Branch: "dev", Task: "fix tests", Agent: "codex",
-})
-
-// Freeform — variance in output
-"write a workspace setup script that..."
-```
-
-**Rule:** For any code pattern that recurs, provide a template. Templates are guardrails for agents.
-
-**Scope:** Templates apply to file generation, workspace scaffolding, config generation, and commit messages. They do NOT apply to novel logic — agents should write business logic freeform with the domain knowledge available.
-
-### 5. Declarative Over Imperative
-
-Agents reason better about declarations of intent than sequences of operations.
-
-```yaml
-# Declarative — agent sees what should happen
-steps:
- - name: build
- flow: tools/docker-build
- with:
- context: "{{ .app_dir }}"
- image_name: "{{ .image_name }}"
-
- - name: deploy
- flow: deploy/with/docker
- with:
- host: "{{ .host }}"
-```
-
-```go
-// Imperative — agent must trace execution
-cmd := exec.Command("docker", "build", "--platform", "linux/amd64", "-t", imageName, ".")
-cmd.Dir = appDir
-if err := cmd.Run(); err != nil {
- return core.E("build", "docker build failed", err)
-}
-```
-
-**Rule:** Orchestration, configuration, and pipeline logic should be declarative (YAML/JSON). Implementation logic should be imperative (Go/PHP/TS). The boundary is: if an agent needs to compose or modify the logic, make it declarative.
-
-Core's `Task` is the Go-native declarative equivalent — a sequence of named Action steps:
-
-```go
-c.Task("deploy", core.Task{
- Steps: []core.Step{
- {Action: "docker.build"},
- {Action: "docker.push"},
- {Action: "deploy.ansible", Async: true},
- },
-})
-```
-
-### 6. Core Primitives — Universal Types and DI
-
-Every component in the ecosystem registers with Core and communicates through Core's primitives. An agent processing any level of the tree sees identical shapes.
-
-#### Creating Core
-
-```go
-c := core.New(
- core.WithOption("name", "core-agent"),
- core.WithService(process.Register),
- core.WithService(agentic.Register),
- core.WithService(monitor.Register),
- core.WithService(brain.Register),
- core.WithService(mcp.Register),
-)
-c.Run() // or: if err := c.RunE(); err != nil { ... }
-```
-
-`core.New()` returns `*Core`. `WithService` registers a factory `func(*Core) Result`. Services auto-discover: name from package path, lifecycle from `Startable`/`Stoppable` (return `Result`). `HandleIPCEvents` is the one remaining magic method — auto-registered via reflection if the service implements it.
-
-#### Service Registration Pattern
-
-```go
-// Service factory — receives Core, returns Result
-func Register(c *core.Core) core.Result {
- svc := &MyService{
- ServiceRuntime: core.NewServiceRuntime(c, MyOptions{}),
- }
- return core.Result{Value: svc, OK: true}
-}
-```
-
-#### Core Subsystem Accessors
-
-| Accessor | Purpose |
-|----------|---------|
-| `c.Options()` | Input configuration |
-| `c.App()` | Application metadata (name, version) |
-| `c.Config()` | Runtime settings, feature flags |
-| `c.Data()` | Embedded assets (Registry[*Embed]) |
-| `c.Drive()` | Transport handles (Registry[*DriveHandle]) |
-| `c.Fs()` | Filesystem I/O (sandboxable) |
-| `c.Process()` | Managed execution (Action sugar) |
-| `c.API()` | Remote streams (protocol handlers) |
-| `c.Action(name)` | Named callable (register/invoke) |
-| `c.Task(name)` | Composed Action sequence |
-| `c.Entitled(name)` | Permission check |
-| `c.RegistryOf(n)` | Cross-cutting registry queries |
-| `c.Cli()` | CLI command framework |
-| `c.IPC()` | Message bus (ACTION, QUERY) |
-| `c.Log()` | Structured logging |
-| `c.Error()` | Panic recovery |
-| `c.I18n()` | Internationalisation |
-
-#### Primitive Types
-
-```go
-// Option — the atom
-core.Option{Key: "name", Value: "brain"}
-
-// Options — universal input
-opts := core.NewOptions(
- core.Option{Key: "name", Value: "myapp"},
- core.Option{Key: "port", Value: 8080},
-)
-opts.String("name") // "myapp"
-opts.Int("port") // 8080
-
-// Result — universal output
-core.Result{Value: svc, OK: true}
-```
-
-#### Named Actions — The Primary Communication Pattern
-
-Services register capabilities as named Actions. No direct function calls, no untyped dispatch — declare intent by name, invoke by name.
-
-```go
-// Register a capability during OnStartup
-c.Action("workspace.create", func(ctx context.Context, opts core.Options) core.Result {
- name := opts.String("name")
- path := core.JoinPath("/srv/workspaces", name)
- return core.Result{Value: path, OK: true}
-})
-
-// Invoke by name — typed, inspectable, entitlement-checked
-r := c.Action("workspace.create").Run(ctx, core.NewOptions(
- core.Option{Key: "name", Value: "alpha"},
-))
-
-// Check capability before calling
-if c.Action("process.run").Exists() { /* go-process is registered */ }
-
-// List all capabilities
-c.Actions() // ["workspace.create", "process.run", "brain.recall", ...]
-```
-
-#### Task Composition — Sequencing Actions
-
-```go
-c.Task("agent.completion", core.Task{
- Steps: []core.Step{
- {Action: "agentic.qa"},
- {Action: "agentic.auto-pr"},
- {Action: "agentic.verify"},
- {Action: "agentic.poke", Async: true}, // doesn't block
- },
-})
-```
-
-#### Anonymous Broadcast — Legacy Layer
-
-`ACTION` and `QUERY` remain for backwards-compatible anonymous dispatch. New code should prefer named Actions.
-
-```go
-// Broadcast — all handlers fire, type-switch to filter
-c.ACTION(messages.DeployCompleted{Env: "production"})
-
-// Query — first responder wins
-r := c.QUERY(countQuery{})
-```
-
-#### Process Execution — Use Core Primitives
-
-All external command execution MUST go through `c.Process()`, not raw `os/exec`. This makes process execution testable, gatable by entitlements, and managed by Core's lifecycle.
-
-```go
-// AX-native: Core Process primitive
-r := c.Process().RunIn(ctx, repoDir, "git", "log", "--oneline", "-20")
-if r.OK { output := r.Value.(string) }
-
-// Not AX: raw exec.Command — untestable, no entitlement, no lifecycle
-cmd := exec.Command("git", "log", "--oneline", "-20")
-cmd.Dir = repoDir
-out, err := cmd.Output()
-```
-
-**Rule:** If a package imports `os/exec`, it is bypassing Core's process primitive. The only package that should import `os/exec` is `go-process` itself.
-
-**Quality gate:** An agent reviewing a diff can mechanically check: does this import `os/exec`, `unsafe`, or `encoding/json` directly? If so, it bypassed a Core primitive.
-
-#### What This Replaces
-
-| Go Convention | Core AX | Why |
-|--------------|---------|-----|
-| `func With*(v) Option` | `core.WithOption(k, v)` | Named key-value is greppable; option chains require tracing |
-| `func Must*(v) T` | `core.Result` | No hidden panics; errors flow through Result.OK |
-| `func *For[T](c) T` | `c.Service("name")` | String lookup is greppable; generics require type context |
-| `val, err :=` everywhere | Single return via `core.Result` | Intent not obscured by error handling |
-| `exec.Command(...)` | `c.Process().Run(ctx, cmd, args...)` | Testable, gatable, lifecycle-managed |
-| `map[string]*T + mutex` | `core.Registry[T]` | Thread-safe, ordered, lockable, queryable |
-| untyped `any` dispatch | `c.Action("name").Run(ctx, opts)` | Named, typed, inspectable, entitlement-checked |
-
-### 7. Tests as Behavioural Specification
-
-Test names are structured data. An agent querying "what happens when dispatch fails?" should find the answer by scanning test names, not reading prose.
-
-```
-TestDispatch_DetectFinalStatus_Good — clean exit → completed
-TestDispatch_DetectFinalStatus_Bad — non-zero exit → failed
-TestDispatch_DetectFinalStatus_Ugly — BLOCKED.md overrides exit code
-```
-
-**Convention:** `Test{File}_{Function}_{Good|Bad|Ugly}`
-
-| Category | Purpose |
-|----------|---------|
-| `_Good` | Happy path — proves the contract works |
-| `_Bad` | Expected errors — proves error handling works |
-| `_Ugly` | Edge cases, panics, corruption — proves it doesn't blow up |
-
-**Rule:** Every testable function gets all three categories. Missing categories are gaps in the specification, detectable by scanning:
-
-```bash
-# Find under-tested functions
-for f in *.go; do
- [[ "$f" == *_test.go ]] && continue
- while IFS= read -r line; do
- fn=$(echo "$line" | sed 's/func.*) //; s/(.*//; s/ .*//')
- [[ -z "$fn" || "$fn" == register* ]] && continue
- cap="${fn^}"
- grep -q "_${cap}_Good\|_${fn}_Good" *_test.go || echo "$f: $fn missing Good"
- grep -q "_${cap}_Bad\|_${fn}_Bad" *_test.go || echo "$f: $fn missing Bad"
- grep -q "_${cap}_Ugly\|_${fn}_Ugly" *_test.go || echo "$f: $fn missing Ugly"
- done < <(grep "^func " "$f")
-done
-```
-
-**Rationale:** The test suite IS the behavioural spec. `grep _TrackFailureRate_ *_test.go` returns three concrete scenarios — no prose needed. The naming convention makes the entire test suite machine-queryable. An agent dispatched to fix a function can read its tests to understand the full contract before making changes.
-
-**What this replaces:**
-
-| Convention | AX Test Naming | Why |
-|-----------|---------------|-----|
-| `TestFoo_works` | `TestFile_Foo_Good` | File prefix enables cross-file search |
-| Unnamed table tests | Explicit Good/Bad/Ugly | Categories are scannable without reading test body |
-| Coverage % as metric | Missing categories as metric | 100% coverage with only Good tests is a false signal |
-
-### 7b. Example Tests as AX TDD
-
-Go `Example` functions serve triple duty: they run as tests (count toward coverage), show in godoc (usage documentation), and seed user guide generation.
-
-```go
-// file: action_example_test.go
-
-func ExampleAction_Run() {
- c := New()
- c.Action("double", func(_ context.Context, opts Options) Result {
- return Result{Value: opts.Int("n") * 2, OK: true}
- })
-
- r := c.Action("double").Run(context.Background(), NewOptions(
- Option{Key: "n", Value: 21},
- ))
- Println(r.Value)
- // Output: 42
-}
-```
-
-**AX TDD pattern:** Write the Example first — it defines how the API should feel. If the Example is awkward, the API is wrong. The Example IS the test, the documentation, and the design feedback loop.
-
-**Convention:** One `{source}_example_test.go` per source file. Every exported function should have at least one Example. The Example output comment makes it a verified test.
-
-**Quality gate:** A source file without a corresponding example file is missing documentation that compiles.
-
-### Operational Principles
-
-Principles 1-7 govern code design. Principles 8-10 govern how agents and humans work with the codebase.
-
-### 8. RFC as Domain Load
-
-An agent's first action in a session should be loading the repo's RFC.md. The full spec in context produces zero-correction sessions — every decision aligns with the design because the design is loaded.
-
-**Validated:** Loading core/go's RFC.md (42k tokens from a 500k token discovery session) at session start eliminated all course corrections. The spec is compressed domain knowledge that survives context compaction.
-
-**Rule:** Every repo that has non-trivial architecture should have a `docs/RFC.md`. The RFC is not documentation for humans — it's a context document for agents. It should be loadable in one read and contain everything needed to make correct decisions.
-
-### 9. Primitives as Quality Gates
-
-Core primitives become mechanical code review rules. An agent reviewing a diff checks:
-
-| Import | Violation | Use Instead |
-|--------|-----------|-------------|
-| `os` | Bypasses Fs/Env primitives | `c.Fs()`, `core.Env()`, `core.DirFS()`, `Fs.TempDir()` |
-| `os/exec` | Bypasses Process primitive | `c.Process().Run()` |
-| `io` | Bypasses stream primitives | `core.ReadAll()`, `core.WriteAll()`, `core.CloseStream()` |
-| `fmt` | Bypasses string/print primitives | `core.Println()`, `core.Sprintf()`, `core.Sprint()` |
-| `errors` | Bypasses error primitive | `core.NewError()`, `core.E()`, `core.Is()`, `core.As()` |
-| `log` | Bypasses logging | `core.Info()`, `core.Warn()`, `core.Error()`, `c.Log()` |
-| `encoding/json` | Bypasses Core serialisation | `core.JSONMarshal()`, `core.JSONUnmarshal()` |
-| `path/filepath` | Bypasses path security boundary | `core.Path()`, `core.JoinPath()`, `core.PathBase()` |
-| `unsafe` | Bypasses Fs sandbox | `Fs.NewUnrestricted()` |
-| `strings` | Bypasses string guardrails | `core.Contains()`, `core.Split()`, `core.Trim()`, etc. |
-
-**Rule:** If a diff introduces a disallowed import, it failed code review. The import list IS the quality gate. No subjective judgement needed — a weaker model can enforce this mechanically.
-
-### 10. Registration IS Capability, Entitlement IS Permission
-
-Two layers of permission, both declarative:
-
-```
-Registration = "this action EXISTS" → c.Action("process.run").Exists()
-Entitlement = "this Core is ALLOWED" → c.Entitled("process.run").Allowed
-```
-
-A sandboxed Core has no `process.run` registered — the action doesn't exist. A SaaS Core has it registered but entitlement-gated — the action exists but the workspace may not be allowed to use it.
-
-**Rule:** Never check permissions with `if` statements in business logic. Register capabilities as Actions. Gate them with Entitlements. The framework enforces both — `Action.Run()` checks both before executing.
-
-## Applying AX to Existing Patterns
-
-### File Structure
-
-```
-# AX-native: path describes content
-core/agent/
-├── cmd/core-agent/ # CLI entry point (minimal — just core.New + Run)
-├── pkg/agentic/ # Agent orchestration (dispatch, prep, verify, scan)
-├── pkg/brain/ # OpenBrain integration
-├── pkg/lib/ # Embedded templates, personas, flows
-├── pkg/messages/ # Typed IPC message definitions
-├── pkg/monitor/ # Agent monitoring + notifications
-├── pkg/setup/ # Workspace scaffolding + detection
-└── claude/ # Claude Code plugin definitions
-
-# Not AX: generic names requiring README
-src/
-├── lib/
-├── utils/
-└── helpers/
-```
-
-### Error Handling
-
-```go
-// AX-native: errors flow through Result, not call sites
-func Register(c *core.Core) core.Result {
- svc := &MyService{ServiceRuntime: core.NewServiceRuntime(c, MyOpts{})}
- return core.Result{Value: svc, OK: true}
-}
-
-// Not AX: errors dominate the code
-func Register(c *core.Core) (*MyService, error) {
- svc, err := NewMyService(c)
- if err != nil {
- return nil, fmt.Errorf("create service: %w", err)
- }
- return svc, nil
-}
-```
-
-### Command Registration
-
-```go
-// AX-native: extracted methods, testable without CLI
-func (s *MyService) OnStartup(ctx context.Context) core.Result {
- c := s.Core()
- c.Command("issue/get", core.Command{Action: s.cmdIssueGet})
- c.Command("issue/list", core.Command{Action: s.cmdIssueList})
- c.Action("forge.issue.get", s.handleIssueGet)
- return core.Result{OK: true}
-}
-
-func (s *MyService) cmdIssueGet(opts core.Options) core.Result {
- // testable business logic — no closure, no CLI dependency
-}
-
-// Not AX: closures that can only be tested via CLI integration
-c.Command("issue/get", core.Command{
- Action: func(opts core.Options) core.Result {
- // 50 lines of untestable inline logic
- },
-})
-```
-
-### Process Execution
-
-```go
-// AX-native: Core Process primitive, testable with mock handler
-func (s *MyService) getGitLog(repoPath string) string {
- r := s.Core().Process().RunIn(context.Background(), repoPath, "git", "log", "--oneline", "-20")
- if !r.OK { return "" }
- return core.Trim(r.Value.(string))
-}
-
-// Not AX: raw exec.Command — untestable, no entitlement check, path traversal risk
-func (s *MyService) getGitLog(repoPath string) string {
- cmd := exec.Command("git", "log", "--oneline", "-20")
- cmd.Dir = repoPath // user-controlled path goes directly to OS
- output, err := cmd.Output()
- if err != nil { return "" }
- return strings.TrimSpace(string(output))
-}
-```
-
-The AX-native version routes through `c.Process()` → named Action → entitlement check. The non-AX version passes user input directly to `os/exec` with no permission gate.
-
-### Permission Gating
-
-```go
-// AX-native: entitlement checked by framework, not by business logic
-c.Action("agentic.dispatch", func(ctx context.Context, opts core.Options) core.Result {
- // Action.Run() already checked c.Entitled("agentic.dispatch")
- // If we're here, we're allowed. Just do the work.
- return dispatch(ctx, opts)
-})
-
-// Not AX: permission logic scattered through business code
-func handleDispatch(ctx context.Context, opts core.Options) core.Result {
- if !isAdmin(ctx) && !hasPlan(ctx, "pro") {
- return core.Result{Value: core.E("dispatch", "upgrade required", nil), OK: false}
- }
- // duplicate permission check in every handler
-}
-```
-
-## Compatibility
-
-AX conventions are valid, idiomatic Go/PHP/TS. They do not require language extensions, code generation, or non-standard tooling. An AX-designed codebase compiles, tests, and deploys with standard toolchains.
-
-The conventions diverge from community patterns (functional options, Must/For, etc.) but do not violate language specifications. This is a style choice, not a fork.
-
-## Adoption
-
-AX applies to all code in the Core ecosystem. core/go is fully migrated (v0.8.0). Consumer packages migrate via their RFCs.
-
-Priority for migrating a package:
-1. **Lifecycle** — `OnStartup`/`OnShutdown` return `Result`
-2. **Actions** — register capabilities as named Actions
-3. **Imports** — replace all 10 disallowed imports (Principle 9)
-4. **String ops** — `+` concat → `Concat()`, `path +` → `Path()`
-5. **Test naming** — `TestFile_Function_{Good,Bad,Ugly}`
-6. **Examples** — one `{source}_example_test.go` per source file
-7. **Comments** — every exported function has usage example (Principle 2)
-
-## Verification
-
-An agent auditing AX compliance checks:
-
-```bash
-# Disallowed imports (Principle 9)
-grep -rn '"os"\|"os/exec"\|"io"\|"fmt"\|"errors"\|"log"\|"encoding/json"\|"path/filepath"\|"unsafe"\|"strings"' *.go \
- | grep -v _test.go
-
-# Test naming (Principle 7)
-grep "^func Test" *_test.go | grep -v "Test[A-Z][a-z]*_.*_\(Good\|Bad\|Ugly\)"
-
-# String concat (should use Concat/Path)
-grep -n '" + \| + "' *.go | grep -v _test.go | grep -v "//"
-
-# Untyped dispatch (should prefer named Actions)
-grep "RegisterTask\|PERFORM\|type Task any" *.go
-```
-
-If any check produces output, the code needs migration.
-
-## References
-
-- `core/go/docs/RFC.md` — CoreGO API contract (21 sections, reference implementation)
-- `core/go-process/docs/RFC.md` — Process consumer spec
-- `core/agent/docs/RFC.md` — Agent consumer spec
-- RFC-004 (Entitlements) — permission model ported to `c.Entitled()`
-- RFC-021 (Core Platform Architecture) — 7-layer stack, provider model
-- dAppServer unified path convention (2024) — path = route = command = test
-- Go Proverbs, Rob Pike (2015) — AX provides an updated lens
-
-## Changelog
-
-- 2026-03-25: v0.8.0 alignment — all examples match implemented API. Added Principles 8 (RFC as Domain Load), 9 (Primitives as Quality Gates), 10 (Registration + Entitlement). Updated subsystem table (Process, API, Action, Task, Entitled, RegistryOf). Process examples use `c.Process()` not old `process.RunWithOptions`. Removed PERFORM references.
-- 2026-03-19: Initial draft — 7 principles
diff --git a/pkg/lib/workspace/default/.core/reference/app.go b/pkg/lib/workspace/default/.core/reference/app.go
deleted file mode 100644
index 31631b8f..00000000
--- a/pkg/lib/workspace/default/.core/reference/app.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Application identity for the Core framework.
-
-package core
-
-import (
- corefilepath "dappco.re/go"
- coreos "dappco.re/go"
-)
-
-// App holds the application identity and optional GUI runtime.
-//
-// app := core.App{}.New(core.NewOptions(
-// core.Option{Key: "name", Value: "Core CLI"},
-// core.Option{Key: "version", Value: "1.0.0"},
-// ))
-type App struct {
- Name string
- Version string
- Description string
- Filename string
- Path string
- Runtime any // GUI runtime (e.g., Wails App). Nil for CLI-only.
-}
-
-// New creates an App from Options.
-//
-// app := core.App{}.New(core.NewOptions(
-// core.Option{Key: "name", Value: "myapp"},
-// core.Option{Key: "version", Value: "1.0.0"},
-// ))
-func (a App) New(opts Options) App {
- if name := opts.String("name"); name != "" {
- a.Name = name
- }
- if version := opts.String("version"); version != "" {
- a.Version = version
- }
- if desc := opts.String("description"); desc != "" {
- a.Description = desc
- }
- if filename := opts.String("filename"); filename != "" {
- a.Filename = filename
- }
- return a
-}
-
-// Find locates a program on PATH and returns a Result containing the App.
-// Uses os.Stat to search PATH directories — no os/exec dependency.
-//
-// r := core.App{}.Find("node", "Node.js")
-// if r.OK { app := r.Value.(*App) }
-func (a App) Find(filename, name string) Result {
- // If filename contains a separator, check it directly
- if Contains(filename, string(os.PathSeparator)) {
- abs, err := filepath.Abs(filename)
- if err != nil {
- return Result{err, false}
- }
- if isExecutable(abs) {
- return Result{&App{Name: name, Filename: filename, Path: abs}, true}
- }
- return Result{E("app.Find", Concat(filename, " not found"), nil), false}
- }
-
- // Search PATH
- pathEnv := os.Getenv("PATH")
- if pathEnv == "" {
- return Result{E("app.Find", "PATH is empty", nil), false}
- }
- for _, dir := range Split(pathEnv, string(os.PathListSeparator)) {
- candidate := filepath.Join(dir, filename)
- if isExecutable(candidate) {
- abs, err := filepath.Abs(candidate)
- if err != nil {
- continue
- }
- return Result{&App{Name: name, Filename: filename, Path: abs}, true}
- }
- }
- return Result{E("app.Find", Concat(filename, " not found on PATH"), nil), false}
-}
-
-// isExecutable checks if a path exists and is executable.
-func isExecutable(path string) bool {
- info, err := os.Stat(path)
- if err != nil {
- return false
- }
- // Regular file with at least one execute bit
- return !info.IsDir() && info.Mode()&0111 != 0
-}
diff --git a/pkg/lib/workspace/default/.core/reference/array.go b/pkg/lib/workspace/default/.core/reference/array.go
deleted file mode 100644
index 488d1092..00000000
--- a/pkg/lib/workspace/default/.core/reference/array.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Generic slice operations for the Core framework.
-// Based on leaanthony/slicer, rewritten with Go 1.18+ generics.
-//
-// arr := core.NewArray("prep", "dispatch")
-// arr.Add("verify", "merge")
-// arr.AddUnique("verify", "verify", "merge")
-
-package core
-
-// Array is a typed slice with common operations.
-type Array[T comparable] struct {
- items []T
-}
-
-// NewArray creates an empty Array.
-func NewArray[T comparable](items ...T) *Array[T] {
- return &Array[T]{items: items}
-}
-
-// Add appends values.
-func (s *Array[T]) Add(values ...T) {
- s.items = append(s.items, values...)
-}
-
-// AddUnique appends values only if not already present.
-func (s *Array[T]) AddUnique(values ...T) {
- for _, v := range values {
- if !s.Contains(v) {
- s.items = append(s.items, v)
- }
- }
-}
-
-// Contains returns true if the value is in the slice.
-func (s *Array[T]) Contains(val T) bool {
- for _, v := range s.items {
- if v == val {
- return true
- }
- }
- return false
-}
-
-// Filter returns a new Array with elements matching the predicate.
-func (s *Array[T]) Filter(fn func(T) bool) Result {
- filtered := &Array[T]{}
- for _, v := range s.items {
- if fn(v) {
- filtered.items = append(filtered.items, v)
- }
- }
- return Result{filtered, true}
-}
-
-// Each runs a function on every element.
-func (s *Array[T]) Each(fn func(T)) {
- for _, v := range s.items {
- fn(v)
- }
-}
-
-// Remove removes the first occurrence of a value.
-func (s *Array[T]) Remove(val T) {
- for i, v := range s.items {
- if v == val {
- s.items = append(s.items[:i], s.items[i+1:]...)
- return
- }
- }
-}
-
-// Deduplicate removes duplicate values, preserving order.
-func (s *Array[T]) Deduplicate() {
- seen := make(map[T]struct{})
- result := make([]T, 0, len(s.items))
- for _, v := range s.items {
- if _, exists := seen[v]; !exists {
- seen[v] = struct{}{}
- result = append(result, v)
- }
- }
- s.items = result
-}
-
-// Len returns the number of elements.
-func (s *Array[T]) Len() int {
- return len(s.items)
-}
-
-// Clear removes all elements.
-func (s *Array[T]) Clear() {
- s.items = nil
-}
-
-// AsSlice returns a copy of the underlying slice.
-func (s *Array[T]) AsSlice() []T {
- if s.items == nil {
- return nil
- }
- out := make([]T, len(s.items))
- copy(out, s.items)
- return out
-}
diff --git a/pkg/lib/workspace/default/.core/reference/cli.go b/pkg/lib/workspace/default/.core/reference/cli.go
deleted file mode 100644
index 32b58ce5..00000000
--- a/pkg/lib/workspace/default/.core/reference/cli.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Cli is the CLI surface layer for the Core command tree.
-//
-// c := core.New(core.WithOption("name", "myapp")).Value.(*Core)
-// c.Command("deploy", core.Command{Action: handler})
-// c.Cli().Run()
-package core
-
-import (
- coreos "dappco.re/go"
- "io"
-)
-
-// CliOptions holds configuration for the Cli service.
-type CliOptions struct{}
-
-// Cli is the CLI surface for the Core command tree.
-type Cli struct {
- *ServiceRuntime[CliOptions]
- output io.Writer
- banner func(*Cli) string
-}
-
-// Register creates a Cli service factory for core.WithService.
-//
-// core.New(core.WithService(core.CliRegister))
-func CliRegister(c *Core) Result {
- cl := &Cli{output: os.Stdout}
- cl.ServiceRuntime = NewServiceRuntime[CliOptions](c, CliOptions{})
- return c.RegisterService("cli", cl)
-}
-
-// Print writes to the CLI output (defaults to os.Stdout).
-//
-// c.Cli().Print("hello %s", "world")
-func (cl *Cli) Print(format string, args ...any) {
- Print(cl.output, format, args...)
-}
-
-// SetOutput sets the CLI output writer.
-//
-// c.Cli().SetOutput(os.Stderr)
-func (cl *Cli) SetOutput(w io.Writer) {
- cl.output = w
-}
-
-// Run resolves os.Args to a command path and executes it.
-//
-// c.Cli().Run()
-// c.Cli().Run("deploy", "to", "homelab")
-func (cl *Cli) Run(args ...string) Result {
- if len(args) == 0 {
- args = os.Args[1:]
- }
-
- clean := FilterArgs(args)
- c := cl.Core()
-
- if c == nil || c.commands == nil {
- if cl.banner != nil {
- cl.Print(cl.banner(cl))
- }
- return Result{}
- }
-
- if c.commands.Len() == 0 {
- if cl.banner != nil {
- cl.Print(cl.banner(cl))
- }
- return Result{}
- }
-
- // Resolve command path from args
- var cmd *Command
- var remaining []string
-
- for i := len(clean); i > 0; i-- {
- path := JoinPath(clean[:i]...)
- if r := c.commands.Get(path); r.OK {
- cmd = r.Value.(*Command)
- remaining = clean[i:]
- break
- }
- }
-
- if cmd == nil {
- if cl.banner != nil {
- cl.Print(cl.banner(cl))
- }
- cl.PrintHelp()
- return Result{}
- }
-
- // Build options from remaining args
- opts := NewOptions()
- for _, arg := range remaining {
- key, val, valid := ParseFlag(arg)
- if valid {
- if Contains(arg, "=") {
- opts.Set(key, val)
- } else {
- opts.Set(key, true)
- }
- } else if !IsFlag(arg) {
- if !opts.Has("_arg") {
- opts.Set("_arg", arg)
- }
- argsResult := opts.Get("_args")
- args := []string{}
- if argsResult.OK {
- if existing, ok := argsResult.Value.([]string); ok {
- args = append(args, existing...)
- }
- }
- args = append(args, arg)
- opts.Set("_args", args)
- }
- }
-
- if cmd.Action != nil {
- return cmd.Run(opts)
- }
- return Result{E("core.Cli.Run", Concat("command \"", cmd.Path, "\" is not executable"), nil), false}
-}
-
-// PrintHelp prints available commands.
-//
-// c.Cli().PrintHelp()
-func (cl *Cli) PrintHelp() {
- c := cl.Core()
- if c == nil || c.commands == nil {
- return
- }
-
- name := ""
- if c.app != nil {
- name = c.app.Name
- }
- if name != "" {
- cl.Print("%s commands:", name)
- } else {
- cl.Print("Commands:")
- }
-
- c.commands.Each(func(path string, cmd *Command) {
- if cmd.Hidden || (cmd.Action == nil && !cmd.IsManaged()) {
- return
- }
- tr := c.I18n().Translate(cmd.I18nKey())
- desc, _ := tr.Value.(string)
- if desc == "" || desc == cmd.I18nKey() {
- cl.Print(" %s", path)
- } else {
- cl.Print(" %-30s %s", path, desc)
- }
- })
-}
-
-// SetBanner sets the banner function.
-//
-// c.Cli().SetBanner(func(_ *core.Cli) string { return "My App v1.0" })
-func (cl *Cli) SetBanner(fn func(*Cli) string) {
- cl.banner = fn
-}
-
-// Banner returns the banner string.
-func (cl *Cli) Banner() string {
- if cl.banner != nil {
- return cl.banner(cl)
- }
- c := cl.Core()
- if c != nil && c.app != nil && c.app.Name != "" {
- return c.app.Name
- }
- return ""
-}
diff --git a/pkg/lib/workspace/default/.core/reference/command.go b/pkg/lib/workspace/default/.core/reference/command.go
deleted file mode 100644
index 6e1412db..00000000
--- a/pkg/lib/workspace/default/.core/reference/command.go
+++ /dev/null
@@ -1,165 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Command is a DTO representing an executable operation.
-// Commands don't know if they're root, child, or nested — the tree
-// structure comes from composition via path-based registration.
-//
-// Register a command:
-//
-// c.Command("deploy", func(opts core.Options) core.Result {
-// return core.Result{"deployed", true}
-// })
-//
-// Register a nested command:
-//
-// c.Command("deploy/to/homelab", handler)
-//
-// Description is an i18n key — derived from path if omitted:
-//
-// "deploy" → "cmd.deploy.description"
-// "deploy/to/homelab" → "cmd.deploy.to.homelab.description"
-package core
-
-// CommandAction is the function signature for command handlers.
-//
-// func(opts core.Options) core.Result
-type CommandAction func(Options) Result
-
-// Command is the DTO for an executable operation.
-// Commands are declarative — they carry enough information for multiple consumers:
-//
-// - core.Cli() runs the Action
-//
-// - core/cli adds rich help, completion, man pages
-//
-// - go-process wraps Managed commands with lifecycle (PID, health, signals)
-//
-// c.Command("serve", core.Command{
-// Action: handler,
-// Managed: "process.daemon", // go-process provides start/stop/restart
-// })
-type Command struct {
- Name string
- Description string // i18n key — derived from path if empty
- Path string // "deploy/to/homelab"
- Action CommandAction // business logic
- Managed string // "" = one-shot, "process.daemon" = managed lifecycle
- Flags Options // declared flags
- Hidden bool
- commands map[string]*Command // child commands (internal)
-}
-
-// I18nKey returns the i18n key for this command's description.
-//
-// cmd with path "deploy/to/homelab" → "cmd.deploy.to.homelab.description"
-func (cmd *Command) I18nKey() string {
- if cmd.Description != "" {
- return cmd.Description
- }
- path := cmd.Path
- if path == "" {
- path = cmd.Name
- }
- return Concat("cmd.", Replace(path, "/", "."), ".description")
-}
-
-// Run executes the command's action with the given options.
-//
-// result := cmd.Run(core.NewOptions(core.Option{Key: "target", Value: "homelab"}))
-func (cmd *Command) Run(opts Options) Result {
- if cmd.Action == nil {
- return Result{E("core.Command.Run", Concat("command \"", cmd.Path, "\" is not executable"), nil), false}
- }
- return cmd.Action(opts)
-}
-
-// IsManaged returns true if this command has a managed lifecycle.
-//
-// if cmd.IsManaged() { /* go-process handles start/stop */ }
-func (cmd *Command) IsManaged() bool {
- return cmd.Managed != ""
-}
-
-// --- Command Registry (on Core) ---
-
-// CommandRegistry holds the command tree. Embeds Registry[*Command]
-// for thread-safe named storage with insertion order.
-type CommandRegistry struct {
- *Registry[*Command]
-}
-
-// Command gets or registers a command by path.
-//
-// c.Command("deploy", Command{Action: handler})
-// r := c.Command("deploy")
-func (c *Core) Command(path string, command ...Command) Result {
- if len(command) == 0 {
- return c.commands.Get(path)
- }
-
- if path == "" || HasPrefix(path, "/") || HasSuffix(path, "/") || Contains(path, "//") {
- return Result{E("core.Command", Concat("invalid command path: \"", path, "\""), nil), false}
- }
-
- // Check for duplicate executable command
- if r := c.commands.Get(path); r.OK {
- existing := r.Value.(*Command)
- if existing.Action != nil || existing.IsManaged() {
- return Result{E("core.Command", Concat("command \"", path, "\" already registered"), nil), false}
- }
- }
-
- cmd := &command[0]
- cmd.Name = pathName(path)
- cmd.Path = path
- if cmd.commands == nil {
- cmd.commands = make(map[string]*Command)
- }
-
- // Preserve existing subtree when overwriting a placeholder parent
- if r := c.commands.Get(path); r.OK {
- existing := r.Value.(*Command)
- for k, v := range existing.commands {
- if _, has := cmd.commands[k]; !has {
- cmd.commands[k] = v
- }
- }
- }
-
- c.commands.Set(path, cmd)
-
- // Build parent chain — "deploy/to/homelab" creates "deploy" and "deploy/to" if missing
- parts := Split(path, "/")
- for i := len(parts) - 1; i > 0; i-- {
- parentPath := JoinPath(parts[:i]...)
- if !c.commands.Has(parentPath) {
- c.commands.Set(parentPath, &Command{
- Name: parts[i-1],
- Path: parentPath,
- commands: make(map[string]*Command),
- })
- }
- parent := c.commands.Get(parentPath).Value.(*Command)
- parent.commands[parts[i]] = cmd
- cmd = parent
- }
-
- return Result{OK: true}
-}
-
-// Commands returns all registered command paths in registration order.
-//
-// paths := c.Commands()
-func (c *Core) Commands() []string {
- if c.commands == nil {
- return nil
- }
- return c.commands.Names()
-}
-
-// pathName extracts the last segment of a path.
-// "deploy/to/homelab" → "homelab"
-func pathName(path string) string {
- parts := Split(path, "/")
- return parts[len(parts)-1]
-}
diff --git a/pkg/lib/workspace/default/.core/reference/config.go b/pkg/lib/workspace/default/.core/reference/config.go
deleted file mode 100644
index b0ff51e1..00000000
--- a/pkg/lib/workspace/default/.core/reference/config.go
+++ /dev/null
@@ -1,188 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Settings, feature flags, and typed configuration for the Core framework.
-//
-// timeout := core.ConfigGet[int](c.Config(), "agent.timeout")
-
-package core
-
-import (
- "sync"
-)
-
-// ConfigVar is a variable that can be set, unset, and queried for its state.
-type ConfigVar[T any] struct {
- val T
- set bool
-}
-
-// Get returns the current value.
-//
-// val := v.Get()
-func (v *ConfigVar[T]) Get() T { return v.val }
-
-// Set sets the value and marks it as explicitly set.
-//
-// v.Set(true)
-func (v *ConfigVar[T]) Set(val T) { v.val = val; v.set = true }
-
-// IsSet returns true if the value was explicitly set (distinguishes "set to false" from "never set").
-//
-// if v.IsSet() { /* explicitly configured */ }
-func (v *ConfigVar[T]) IsSet() bool { return v.set }
-
-// Unset resets to zero value and marks as not set.
-//
-// v.Unset()
-// v.IsSet() // false
-func (v *ConfigVar[T]) Unset() {
- v.set = false
- var zero T
- v.val = zero
-}
-
-// NewConfigVar creates a ConfigVar with an initial value marked as set.
-//
-// debug := core.NewConfigVar(true)
-func NewConfigVar[T any](val T) ConfigVar[T] {
- return ConfigVar[T]{val: val, set: true}
-}
-
-// ConfigOptions holds configuration data.
-type ConfigOptions struct {
- Settings map[string]any
- Features map[string]bool
-}
-
-func (o *ConfigOptions) init() {
- if o.Settings == nil {
- o.Settings = make(map[string]any)
- }
- if o.Features == nil {
- o.Features = make(map[string]bool)
- }
-}
-
-// Config holds configuration settings and feature flags.
-type Config struct {
- *ConfigOptions
- mu sync.RWMutex
-}
-
-// New initialises a Config with empty settings and features.
-//
-// cfg := (&core.Config{}).New()
-func (e *Config) New() *Config {
- e.ConfigOptions = &ConfigOptions{}
- e.ConfigOptions.init()
- return e
-}
-
-// Set stores a configuration value by key.
-func (e *Config) Set(key string, val any) {
- e.mu.Lock()
- if e.ConfigOptions == nil {
- e.ConfigOptions = &ConfigOptions{}
- }
- e.ConfigOptions.init()
- e.Settings[key] = val
- e.mu.Unlock()
-}
-
-// Get retrieves a configuration value by key.
-func (e *Config) Get(key string) Result {
- e.mu.RLock()
- defer e.mu.RUnlock()
- if e.ConfigOptions == nil || e.Settings == nil {
- return Result{}
- }
- val, ok := e.Settings[key]
- if !ok {
- return Result{}
- }
- return Result{val, true}
-}
-
-// String retrieves a string config value (empty string if missing).
-//
-// host := c.Config().String("database.host")
-func (e *Config) String(key string) string { return ConfigGet[string](e, key) }
-
-// Int retrieves an int config value (0 if missing).
-//
-// port := c.Config().Int("database.port")
-func (e *Config) Int(key string) int { return ConfigGet[int](e, key) }
-
-// Bool retrieves a bool config value (false if missing).
-//
-// debug := c.Config().Bool("debug")
-func (e *Config) Bool(key string) bool { return ConfigGet[bool](e, key) }
-
-// ConfigGet retrieves a typed configuration value.
-func ConfigGet[T any](e *Config, key string) T {
- r := e.Get(key)
- if !r.OK {
- var zero T
- return zero
- }
- typed, _ := r.Value.(T)
- return typed
-}
-
-// --- Feature Flags ---
-
-// Enable activates a feature flag.
-//
-// c.Config().Enable("dark-mode")
-func (e *Config) Enable(feature string) {
- e.mu.Lock()
- if e.ConfigOptions == nil {
- e.ConfigOptions = &ConfigOptions{}
- }
- e.ConfigOptions.init()
- e.Features[feature] = true
- e.mu.Unlock()
-}
-
-// Disable deactivates a feature flag.
-//
-// c.Config().Disable("dark-mode")
-func (e *Config) Disable(feature string) {
- e.mu.Lock()
- if e.ConfigOptions == nil {
- e.ConfigOptions = &ConfigOptions{}
- }
- e.ConfigOptions.init()
- e.Features[feature] = false
- e.mu.Unlock()
-}
-
-// Enabled returns true if a feature flag is active.
-//
-// if c.Config().Enabled("dark-mode") { ... }
-func (e *Config) Enabled(feature string) bool {
- e.mu.RLock()
- defer e.mu.RUnlock()
- if e.ConfigOptions == nil || e.Features == nil {
- return false
- }
- return e.Features[feature]
-}
-
-// EnabledFeatures returns all active feature flag names.
-//
-// features := c.Config().EnabledFeatures()
-func (e *Config) EnabledFeatures() []string {
- e.mu.RLock()
- defer e.mu.RUnlock()
- if e.ConfigOptions == nil || e.Features == nil {
- return nil
- }
- var result []string
- for k, v := range e.Features {
- if v {
- result = append(result, k)
- }
- }
- return result
-}
diff --git a/pkg/lib/workspace/default/.core/reference/contract.go b/pkg/lib/workspace/default/.core/reference/contract.go
deleted file mode 100644
index 3be32401..00000000
--- a/pkg/lib/workspace/default/.core/reference/contract.go
+++ /dev/null
@@ -1,226 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Contracts, options, and type definitions for the Core framework.
-
-package core
-
-import (
- "context"
- "reflect"
- "sync"
-)
-
-// Message is the type for IPC broadcasts (fire-and-forget).
-type Message any
-
-// Query is the type for read-only IPC requests.
-type Query any
-
-// QueryHandler handles Query requests. Returns Result{Value, OK}.
-type QueryHandler func(*Core, Query) Result
-
-// Startable is implemented by services that need startup initialisation.
-//
-// func (s *MyService) OnStartup(ctx context.Context) core.Result {
-// return core.Result{OK: true}
-// }
-type Startable interface {
- OnStartup(ctx context.Context) Result
-}
-
-// Stoppable is implemented by services that need shutdown cleanup.
-//
-// func (s *MyService) OnShutdown(ctx context.Context) core.Result {
-// return core.Result{OK: true}
-// }
-type Stoppable interface {
- OnShutdown(ctx context.Context) Result
-}
-
-// --- Action Messages ---
-
-type ActionServiceStartup struct{}
-type ActionServiceShutdown struct{}
-
-type ActionTaskStarted struct {
- TaskIdentifier string
- Action string
- Options Options
-}
-
-type ActionTaskProgress struct {
- TaskIdentifier string
- Action string
- Progress float64
- Message string
-}
-
-type ActionTaskCompleted struct {
- TaskIdentifier string
- Action string
- Result Result
-}
-
-// --- Constructor ---
-
-// CoreOption is a functional option applied during Core construction.
-// Returns Result — if !OK, New() stops and returns the error.
-//
-// core.New(
-// core.WithService(agentic.Register),
-// core.WithService(monitor.Register),
-// core.WithServiceLock(),
-// )
-type CoreOption func(*Core) Result
-
-// New initialises a Core instance by applying options in order.
-// Services registered here form the application conclave — they share
-// IPC access and participate in the lifecycle (ServiceStartup/ServiceShutdown).
-//
-// c := core.New(
-// core.WithOption("name", "myapp"),
-// core.WithService(auth.Register),
-// core.WithServiceLock(),
-// )
-// c.Run()
-func New(opts ...CoreOption) *Core {
- c := &Core{
- app: &App{},
- data: &Data{Registry: NewRegistry[*Embed]()},
- drive: &Drive{Registry: NewRegistry[*DriveHandle]()},
- fs: (&Fs{}).New("/"),
- config: (&Config{}).New(),
- error: &ErrorPanic{},
- log: &ErrorLog{},
- lock: &Lock{locks: NewRegistry[*sync.RWMutex]()},
- ipc: &Ipc{actions: NewRegistry[*Action](), tasks: NewRegistry[*Task]()},
- info: systemInfo,
- i18n: &I18n{},
- api: &API{protocols: NewRegistry[StreamFactory]()},
- services: &ServiceRegistry{Registry: NewRegistry[*Service]()},
- commands: &CommandRegistry{Registry: NewRegistry[*Command]()},
- entitlementChecker: defaultChecker,
- }
- c.context, c.cancel = context.WithCancel(context.Background())
- c.api.core = c
-
- // Core services
- CliRegister(c)
-
- for _, opt := range opts {
- if r := opt(c); !r.OK {
- Error("core.New failed", "err", r.Value)
- break
- }
- }
-
- // Apply service lock after all opts — v0.3.3 parity
- c.LockApply()
-
- return c
-}
-
-// WithOptions applies key-value configuration to Core.
-//
-// core.WithOptions(core.NewOptions(core.Option{Key: "name", Value: "myapp"}))
-func WithOptions(opts Options) CoreOption {
- return func(c *Core) Result {
- c.options = &opts
- if name := opts.String("name"); name != "" {
- c.app.Name = name
- }
- return Result{OK: true}
- }
-}
-
-// WithService registers a service via its factory function.
-// If the factory returns a non-nil Value, WithService auto-discovers the
-// service name from the factory's package path (last path segment, lowercase,
-// with any "_test" suffix stripped) and calls RegisterService on the instance.
-// IPC handler auto-registration is handled by RegisterService.
-//
-// If the factory returns nil Value (it registered itself), WithService
-// returns success without a second registration.
-//
-// core.WithService(agentic.Register)
-// core.WithService(display.Register(nil))
-func WithService(factory func(*Core) Result) CoreOption {
- return func(c *Core) Result {
- r := factory(c)
- if !r.OK {
- return r
- }
- if r.Value == nil {
- // Factory self-registered — nothing more to do.
- return Result{OK: true}
- }
- // Auto-discover the service name from the instance's package path.
- instance := r.Value
- typeOf := reflect.TypeOf(instance)
- if typeOf.Kind() == reflect.Ptr {
- typeOf = typeOf.Elem()
- }
- pkgPath := typeOf.PkgPath()
- parts := Split(pkgPath, "/")
- name := Lower(parts[len(parts)-1])
- if name == "" {
- return Result{E("core.WithService", Sprintf("service name could not be discovered for type %T", instance), nil), false}
- }
-
- // RegisterService handles Startable/Stoppable/HandleIPCEvents discovery
- return c.RegisterService(name, instance)
- }
-}
-
-// WithName registers a service with an explicit name (no reflect discovery).
-//
-// core.WithName("ws", func(c *Core) Result {
-// return Result{Value: hub, OK: true}
-// })
-func WithName(name string, factory func(*Core) Result) CoreOption {
- return func(c *Core) Result {
- r := factory(c)
- if !r.OK {
- return r
- }
- if r.Value == nil {
- return Result{E("core.WithName", Sprintf("failed to create service %q", name), nil), false}
- }
- return c.RegisterService(name, r.Value)
- }
-}
-
-// WithOption is a convenience for setting a single key-value option.
-//
-// core.New(
-// core.WithOption("name", "myapp"),
-// core.WithOption("port", 8080),
-// )
-func WithOption(key string, value any) CoreOption {
- return func(c *Core) Result {
- if c.options == nil {
- opts := NewOptions()
- c.options = &opts
- }
- c.options.Set(key, value)
- if key == "name" {
- if s, ok := value.(string); ok {
- c.app.Name = s
- }
- }
- return Result{OK: true}
- }
-}
-
-// WithServiceLock prevents further service registration after construction.
-//
-// core.New(
-// core.WithService(auth.Register),
-// core.WithServiceLock(),
-// )
-func WithServiceLock() CoreOption {
- return func(c *Core) Result {
- c.LockEnable()
- return Result{OK: true}
- }
-}
diff --git a/pkg/lib/workspace/default/.core/reference/core.go b/pkg/lib/workspace/default/.core/reference/core.go
deleted file mode 100644
index 978307d9..00000000
--- a/pkg/lib/workspace/default/.core/reference/core.go
+++ /dev/null
@@ -1,239 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Package core is a dependency injection and service lifecycle framework for Go.
-// This file defines the Core struct, accessors, and IPC/error wrappers.
-
-package core
-
-import (
- "context"
- coreos "dappco.re/go"
- "sync"
- "sync/atomic"
-)
-
-// --- Core Struct ---
-
-// Core is the central application object that manages services, assets, and communication.
-type Core struct {
- options *Options // c.Options() — Input configuration used to create this Core
- app *App // c.App() — Application identity + optional GUI runtime
- data *Data // c.Data() — Embedded/stored content from packages
- drive *Drive // c.Drive() — Resource handle registry (transports)
- fs *Fs // c.Fs() — Local filesystem I/O (sandboxable)
- config *Config // c.Config() — Configuration, settings, feature flags
- error *ErrorPanic // c.Error() — Panic recovery and crash reporting
- log *ErrorLog // c.Log() — Structured logging + error wrapping
- // cli accessed via ServiceFor[*Cli](c, "cli")
- commands *CommandRegistry // c.Command(`path`) — Command tree
- services *ServiceRegistry // c.Service("name") — Service registry
- lock *Lock // c.Lock("name") — Named mutexes
- ipc *Ipc // c.IPC() — Message bus for IPC
- api *API // c.API() — Remote streams
- info *SysInfo // c.Env("key") — Read-only system/environment information
- i18n *I18n // c.I18n() — Internationalisation and locale collection
-
- entitlementChecker EntitlementChecker // default: everything permitted
- usageRecorder UsageRecorder // default: nil (no-op)
-
- context context.Context
- cancel context.CancelFunc
- taskIDCounter atomic.Uint64
- waitGroup sync.WaitGroup
- shutdown atomic.Bool
-}
-
-// --- Accessors ---
-
-// Options returns the input configuration passed to core.New().
-//
-// opts := c.Options()
-// name := opts.String("name")
-func (c *Core) Options() *Options { return c.options }
-
-// App returns application identity metadata.
-//
-// c.App().Name // "my-app"
-// c.App().Version // "1.0.0"
-func (c *Core) App() *App { return c.app }
-
-// Data returns the embedded asset registry (Registry[*Embed]).
-//
-// r := c.Data().ReadString("prompts/coding.md")
-func (c *Core) Data() *Data { return c.data }
-
-// Drive returns the transport handle registry (Registry[*DriveHandle]).
-//
-// r := c.Drive().Get("forge")
-func (c *Core) Drive() *Drive { return c.drive }
-
-// Fs returns the sandboxed filesystem.
-//
-// r := c.Fs().Read("/path/to/file")
-// c.Fs().WriteAtomic("/status.json", data)
-func (c *Core) Fs() *Fs { return c.fs }
-
-// Config returns runtime settings and feature flags.
-//
-// host := c.Config().String("database.host")
-// c.Config().Enable("dark-mode")
-func (c *Core) Config() *Config { return c.config }
-
-// Error returns the panic recovery subsystem.
-//
-// c.Error().Recover()
-func (c *Core) Error() *ErrorPanic { return c.error }
-
-// Log returns the structured logging subsystem.
-//
-// c.Log().Info("started", "port", 8080)
-func (c *Core) Log() *ErrorLog { return c.log }
-
-// Cli returns the CLI command framework (registered as service "cli").
-//
-// c.Cli().Run("deploy", "to", "homelab")
-func (c *Core) Cli() *Cli {
- cl, _ := ServiceFor[*Cli](c, "cli")
- return cl
-}
-
-// IPC returns the message bus internals.
-//
-// c.IPC()
-func (c *Core) IPC() *Ipc { return c.ipc }
-
-// I18n returns the internationalisation subsystem.
-//
-// tr := c.I18n().Translate("cmd.deploy.description")
-func (c *Core) I18n() *I18n { return c.i18n }
-
-// Env returns an environment variable by key (cached at init, falls back to os.Getenv).
-//
-// home := c.Env("DIR_HOME")
-// token := c.Env("FORGE_TOKEN")
-func (c *Core) Env(key string) string { return Env(key) }
-
-// Context returns Core's lifecycle context (cancelled on shutdown).
-//
-// ctx := c.Context()
-func (c *Core) Context() context.Context { return c.context }
-
-// Core returns self — satisfies the ServiceRuntime interface.
-//
-// c := s.Core()
-func (c *Core) Core() *Core { return c }
-
-// --- Lifecycle ---
-
-// RunE starts all services, runs the CLI, then shuts down.
-// Returns an error instead of calling os.Exit — let main() handle the exit.
-// ServiceShutdown is always called via defer, even on startup failure or panic.
-//
-// if err := c.RunE(); err != nil {
-// os.Exit(1)
-// }
-func (c *Core) RunE() any {
- defer c.ServiceShutdown(context.Background())
-
- r := c.ServiceStartup(c.context, nil)
- if !r.OK {
- if err, ok := r.Value.(error); ok {
- return err
- }
- return E("core.Run", "startup failed", nil)
- }
-
- if cli := c.Cli(); cli != nil {
- r = cli.Run()
- }
-
- if !r.OK {
- if err, ok := r.Value.(error); ok {
- return err
- }
- }
- return nil
-}
-
-// Run starts all services, runs the CLI, then shuts down.
-// Calls os.Exit(1) on failure. For error handling use RunE().
-//
-// c := core.New(core.WithService(myService.Register))
-// c.Run()
-func (c *Core) Run() {
- if err := c.RunE(); err != nil {
- Error(err.Error())
- os.Exit(1)
- }
-}
-
-// --- IPC (uppercase aliases) ---
-
-// ACTION broadcasts a message to all registered handlers (fire-and-forget).
-// Each handler is wrapped in panic recovery. All handlers fire regardless.
-//
-// c.ACTION(messages.AgentCompleted{Agent: "codex", Status: "completed"})
-func (c *Core) ACTION(msg Message) Result { return c.broadcast(msg) }
-
-// QUERY sends a request — first handler to return OK wins.
-//
-// r := c.QUERY(MyQuery{Name: "brain"})
-func (c *Core) QUERY(q Query) Result { return c.Query(q) }
-
-// QUERYALL sends a request — collects all OK responses.
-//
-// r := c.QUERYALL(countQuery{})
-// results := r.Value.([]any)
-func (c *Core) QUERYALL(q Query) Result { return c.QueryAll(q) }
-
-// --- Error+Log ---
-
-// LogError logs an error and returns the Result from ErrorLog.
-func (c *Core) LogError(err any, op, msg string) Result {
- return c.log.Error(err, op, msg)
-}
-
-// LogWarn logs a warning and returns the Result from ErrorLog.
-func (c *Core) LogWarn(err any, op, msg string) Result {
- return c.log.Warn(err, op, msg)
-}
-
-// Must logs and panics if err is not nil.
-func (c *Core) Must(err any, op, msg string) {
- c.log.Must(err, op, msg)
-}
-
-// --- Registry Accessor ---
-
-// RegistryOf returns a named registry for cross-cutting queries.
-// Known registries: "services", "commands", "actions".
-//
-// c.RegistryOf("services").Names() // all service names
-// c.RegistryOf("actions").List("process.*") // process capabilities
-// c.RegistryOf("commands").Len() // command count
-func (c *Core) RegistryOf(name string) *Registry[any] {
- // Bridge typed registries to untyped access for cross-cutting queries.
- // Each registry is wrapped in a read-only proxy.
- switch name {
- case "services":
- return registryProxy(c.services.Registry)
- case "commands":
- return registryProxy(c.commands.Registry)
- case "actions":
- return registryProxy(c.ipc.actions)
- default:
- return NewRegistry[any]() // empty registry for unknown names
- }
-}
-
-// registryProxy creates a read-only any-typed view of a typed registry.
-// Copies current state — not a live view (avoids type parameter leaking).
-func registryProxy[T any](src *Registry[T]) *Registry[any] {
- proxy := NewRegistry[any]()
- src.Each(func(name string, item T) {
- proxy.Set(name, item)
- })
- return proxy
-}
-
-// --- Global Instance ---
diff --git a/pkg/lib/workspace/default/.core/reference/data.go b/pkg/lib/workspace/default/.core/reference/data.go
deleted file mode 100644
index f37b6fb3..00000000
--- a/pkg/lib/workspace/default/.core/reference/data.go
+++ /dev/null
@@ -1,168 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Data is the embedded/stored content system for core packages.
-// Packages mount their embedded content here and other packages
-// read from it by path.
-//
-// Mount a package's assets:
-//
-// c.Data().New(core.NewOptions(
-// core.Option{Key: "name", Value: "brain"},
-// core.Option{Key: "source", Value: brainFS},
-// core.Option{Key: `path`, Value: "prompts"},
-// ))
-//
-// Read from any mounted path:
-//
-// content := c.Data().ReadString("brain/coding.md")
-// entries := c.Data().List("agent/flow")
-//
-// Extract a template directory:
-//
-// c.Data().Extract("agent/workspace/default", "/tmp/ws", data)
-package core
-
-import (
- corefilepath "dappco.re/go"
- "io/fs"
-)
-
-// Data manages mounted embedded filesystems from core packages.
-// Embeds Registry[*Embed] for thread-safe named storage.
-type Data struct {
- *Registry[*Embed]
-}
-
-// New registers an embedded filesystem under a named prefix.
-//
-// c.Data().New(core.NewOptions(
-// core.Option{Key: "name", Value: "brain"},
-// core.Option{Key: "source", Value: brainFS},
-// core.Option{Key: `path`, Value: "prompts"},
-// ))
-func (d *Data) New(opts Options) Result {
- name := opts.String("name")
- if name == "" {
- return Result{}
- }
-
- r := opts.Get("source")
- if !r.OK {
- return r
- }
-
- fsys, ok := r.Value.(fs.FS)
- if !ok {
- return Result{E("data.New", "source is not fs.FS", nil), false}
- }
-
- path := opts.String(`path`)
- if path == "" {
- path = "."
- }
-
- mr := Mount(fsys, path)
- if !mr.OK {
- return mr
- }
-
- emb := mr.Value.(*Embed)
- d.Set(name, emb)
- return Result{emb, true}
-}
-
-// resolve splits a path like "brain/coding.md" into mount name + relative path.
-func (d *Data) resolve(path string) (*Embed, string) {
- parts := SplitN(path, "/", 2)
- if len(parts) < 2 {
- return nil, ""
- }
- r := d.Get(parts[0])
- if !r.OK {
- return nil, ""
- }
- return r.Value.(*Embed), parts[1]
-}
-
-// ReadFile reads a file by full path.
-//
-// r := c.Data().ReadFile("brain/prompts/coding.md")
-// if r.OK { data := r.Value.([]byte) }
-func (d *Data) ReadFile(path string) Result {
- emb, rel := d.resolve(path)
- if emb == nil {
- return Result{}
- }
- return emb.ReadFile(rel)
-}
-
-// ReadString reads a file as a string.
-//
-// r := c.Data().ReadString("agent/flow/deploy/to/homelab.yaml")
-// if r.OK { content := r.Value.(string) }
-func (d *Data) ReadString(path string) Result {
- r := d.ReadFile(path)
- if !r.OK {
- return r
- }
- return Result{string(r.Value.([]byte)), true}
-}
-
-// List returns directory entries at a path.
-//
-// r := c.Data().List("agent/persona/code")
-// if r.OK { entries := r.Value.([]fs.DirEntry) }
-func (d *Data) List(path string) Result {
- emb, rel := d.resolve(path)
- if emb == nil {
- return Result{}
- }
- r := emb.ReadDir(rel)
- if !r.OK {
- return r
- }
- return Result{r.Value, true}
-}
-
-// ListNames returns filenames (without extensions) at a path.
-//
-// r := c.Data().ListNames("agent/flow")
-// if r.OK { names := r.Value.([]string) }
-func (d *Data) ListNames(path string) Result {
- r := d.List(path)
- if !r.OK {
- return r
- }
- entries := r.Value.([]fs.DirEntry)
- var names []string
- for _, e := range entries {
- name := e.Name()
- if !e.IsDir() {
- name = TrimSuffix(name, filepath.Ext(name))
- }
- names = append(names, name)
- }
- return Result{names, true}
-}
-
-// Extract copies a template directory to targetDir.
-//
-// r := c.Data().Extract("agent/workspace/default", "/tmp/ws", templateData)
-func (d *Data) Extract(path, targetDir string, templateData any) Result {
- emb, rel := d.resolve(path)
- if emb == nil {
- return Result{}
- }
- r := emb.Sub(rel)
- if !r.OK {
- return r
- }
- return Extract(r.Value.(*Embed).FS(), targetDir, templateData)
-}
-
-// Mounts returns the names of all mounted content in registration order.
-//
-// names := c.Data().Mounts()
-func (d *Data) Mounts() []string {
- return d.Names()
-}
diff --git a/pkg/lib/workspace/default/.core/reference/docs/commands.md b/pkg/lib/workspace/default/.core/reference/docs/commands.md
deleted file mode 100644
index 46e2022b..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/commands.md
+++ /dev/null
@@ -1,177 +0,0 @@
----
-title: Commands
-description: Path-based command registration and CLI execution.
----
-
-# Commands
-
-Commands are one of the most AX-native parts of CoreGO. The path is the identity.
-
-## Register a Command
-
-```go
-c.Command("deploy/to/homelab", core.Command{
- Action: func(opts core.Options) core.Result {
- target := opts.String("target")
- return core.Result{Value: "deploying to " + target, OK: true}
- },
-})
-```
-
-## Command Paths
-
-Paths must be clean:
-
-- no empty path
-- no leading slash
-- no trailing slash
-- no double slash
-
-These paths are valid:
-
-```text
-deploy
-deploy/to/homelab
-workspace/create
-```
-
-These are rejected:
-
-```text
-/deploy
-deploy/
-deploy//to
-```
-
-## Parent Commands Are Auto-Created
-
-When you register `deploy/to/homelab`, CoreGO also creates placeholder parents if they do not already exist:
-
-- `deploy`
-- `deploy/to`
-
-This makes the path tree navigable without extra setup.
-
-## Read a Command Back
-
-```go
-r := c.Command("deploy/to/homelab")
-if r.OK {
- cmd := r.Value.(*core.Command)
- _ = cmd
-}
-```
-
-## Run a Command Directly
-
-```go
-cmd := c.Command("deploy/to/homelab").Value.(*core.Command)
-
-r := cmd.Run(core.Options{
- {Key: "target", Value: "uk-prod"},
-})
-```
-
-If `Action` is nil, `Run` returns `Result{OK:false}` with a structured error.
-
-## Run Through the CLI Surface
-
-```go
-r := c.Cli().Run("deploy", "to", "homelab", "--target=uk-prod", "--debug")
-```
-
-`Cli.Run` resolves the longest matching command path from the arguments, then converts the remaining args into `core.Options`.
-
-## Flag Parsing Rules
-
-### Double Dash
-
-```text
---target=uk-prod -> key "target", value "uk-prod"
---debug -> key "debug", value true
-```
-
-### Single Dash
-
-```text
--v -> key "v", value true
--n=4 -> key "n", value "4"
-```
-
-### Positional Arguments
-
-Non-flag arguments after the command path are stored as repeated `_arg` options.
-
-```go
-r := c.Cli().Run("workspace", "open", "alpha")
-```
-
-That produces an option like:
-
-```go
-core.Option{Key: "_arg", Value: "alpha"}
-```
-
-### Important Details
-
-- flag values stay as strings
-- `opts.Int("port")` only works if some code stored an actual `int`
-- invalid flags such as `-verbose` and `--v` are ignored
-
-## Help Output
-
-`Cli.PrintHelp()` prints executable commands:
-
-```go
-c.Cli().PrintHelp()
-```
-
-It skips:
-
-- hidden commands
-- placeholder parents with no `Action` and no `Lifecycle`
-
-Descriptions are resolved through `cmd.I18nKey()`.
-
-## I18n Description Keys
-
-If `Description` is empty, CoreGO derives a key from the path.
-
-```text
-deploy -> cmd.deploy.description
-deploy/to/homelab -> cmd.deploy.to.homelab.description
-workspace/create -> cmd.workspace.create.description
-```
-
-If `Description` is already set, CoreGO uses it as-is.
-
-## Lifecycle Commands
-
-Commands can also delegate to a lifecycle implementation.
-
-```go
-type daemonCommand struct{}
-
-func (d *daemonCommand) Start(opts core.Options) core.Result { return core.Result{OK: true} }
-func (d *daemonCommand) Stop() core.Result { return core.Result{OK: true} }
-func (d *daemonCommand) Restart() core.Result { return core.Result{OK: true} }
-func (d *daemonCommand) Reload() core.Result { return core.Result{OK: true} }
-func (d *daemonCommand) Signal(sig string) core.Result { return core.Result{Value: sig, OK: true} }
-
-c.Command("agent/serve", core.Command{
- Lifecycle: &daemonCommand{},
-})
-```
-
-Important behavior:
-
-- `Start` falls back to `Run` when `Lifecycle` is nil
-- `Stop`, `Restart`, `Reload`, and `Signal` return an empty `Result` when `Lifecycle` is nil
-
-## List Command Paths
-
-```go
-paths := c.Commands()
-```
-
-Like the service registry, the command registry is map-backed, so iteration order is not guaranteed.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/configuration.md b/pkg/lib/workspace/default/.core/reference/docs/configuration.md
deleted file mode 100644
index 0a0cf118..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/configuration.md
+++ /dev/null
@@ -1,96 +0,0 @@
----
-title: Configuration
-description: Constructor options, runtime settings, and feature flags.
----
-
-# Configuration
-
-CoreGO uses two different configuration layers:
-
-- constructor-time `core.Options`
-- runtime `c.Config()`
-
-## Constructor-Time Options
-
-```go
-c := core.New(core.Options{
- {Key: "name", Value: "agent-workbench"},
-})
-```
-
-### Current Behavior
-
-- `New` accepts `opts ...Options`
-- the current implementation copies only the first `Options` slice
-- the `name` key is applied to `c.App().Name`
-
-If you need more constructor data, put it in the first `core.Options` slice.
-
-## Runtime Settings with `Config`
-
-Use `c.Config()` for mutable process settings.
-
-```go
-c.Config().Set("workspace.root", "/srv/workspaces")
-c.Config().Set("max_agents", 8)
-c.Config().Set("debug", true)
-```
-
-Read them back with:
-
-```go
-root := c.Config().String("workspace.root")
-maxAgents := c.Config().Int("max_agents")
-debug := c.Config().Bool("debug")
-raw := c.Config().Get("workspace.root")
-```
-
-### Important Details
-
-- missing keys return zero values
-- typed accessors do not coerce strings into ints or bools
-- `Get` returns `core.Result`
-
-## Feature Flags
-
-`Config` also tracks named feature flags.
-
-```go
-c.Config().Enable("workspace.templates")
-c.Config().Enable("agent.review")
-c.Config().Disable("agent.review")
-```
-
-Read them with:
-
-```go
-enabled := c.Config().Enabled("workspace.templates")
-features := c.Config().EnabledFeatures()
-```
-
-Feature names are case-sensitive.
-
-## `ConfigVar[T]`
-
-Use `ConfigVar[T]` when you need a typed value that can also represent “set versus unset”.
-
-```go
-theme := core.NewConfigVar("amber")
-
-if theme.IsSet() {
- fmt.Println(theme.Get())
-}
-
-theme.Unset()
-```
-
-This is useful for package-local state where zero values are not enough to describe configuration presence.
-
-## Recommended Pattern
-
-Use the two layers for different jobs:
-
-- put startup identity such as `name` into `core.Options`
-- put mutable runtime values and feature switches into `c.Config()`
-
-That keeps constructor intent separate from live process state.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/errors.md b/pkg/lib/workspace/default/.core/reference/docs/errors.md
deleted file mode 100644
index 9b7d3f3c..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/errors.md
+++ /dev/null
@@ -1,120 +0,0 @@
----
-title: Errors
-description: Structured errors, logging helpers, and panic recovery.
----
-
-# Errors
-
-CoreGO treats failures as structured operational data.
-
-Repository convention: use `E()` instead of `fmt.Errorf` for framework and service errors.
-
-## `Err`
-
-The structured error type is:
-
-```go
-type Err struct {
- Operation string
- Message string
- Cause error
- Code string
-}
-```
-
-## Create Errors
-
-### `E`
-
-```go
-err := core.E("workspace.Load", "failed to read workspace manifest", cause)
-```
-
-### `Wrap`
-
-```go
-err := core.Wrap(cause, "workspace.Load", "manifest parse failed")
-```
-
-### `WrapCode`
-
-```go
-err := core.WrapCode(cause, "WORKSPACE_INVALID", "workspace.Load", "manifest parse failed")
-```
-
-### `NewCode`
-
-```go
-err := core.NewCode("NOT_FOUND", "workspace not found")
-```
-
-## Inspect Errors
-
-```go
-op := core.Operation(err)
-code := core.ErrorCode(err)
-msg := core.ErrorMessage(err)
-root := core.Root(err)
-stack := core.StackTrace(err)
-pretty := core.FormatStackTrace(err)
-```
-
-These helpers keep the operational chain visible without extra type assertions.
-
-## Join and Standard Wrappers
-
-```go
-combined := core.ErrorJoin(err1, err2)
-same := core.Is(combined, err1)
-```
-
-`core.As` and `core.NewError` mirror the standard library for convenience.
-
-## Log-and-Return Helpers
-
-`Core` exposes two convenience wrappers:
-
-```go
-r1 := c.LogError(err, "workspace.Load", "workspace load failed")
-r2 := c.LogWarn(err, "workspace.Load", "workspace load degraded")
-```
-
-These log through the default logger and return `core.Result`.
-
-You can also use the underlying `ErrorLog` directly:
-
-```go
-r := c.Log().Error(err, "workspace.Load", "workspace load failed")
-```
-
-`Must` logs and then panics when the error is non-nil:
-
-```go
-c.Must(err, "workspace.Load", "workspace load failed")
-```
-
-## Panic Recovery
-
-`ErrorPanic` handles process-safe panic capture.
-
-```go
-defer c.Error().Recover()
-```
-
-Run background work with recovery:
-
-```go
-c.Error().SafeGo(func() {
- panic("captured")
-})
-```
-
-If `ErrorPanic` has a configured crash file path, it appends JSON crash reports and `Reports(n)` reads them back.
-
-That crash file path is currently internal state on `ErrorPanic`, not a public constructor option on `Core.New()`.
-
-## Logging and Error Context
-
-The logging subsystem automatically extracts `op` and logical stack information from structured errors when those values are present in the key-value list.
-
-That makes errors created with `E`, `Wrap`, or `WrapCode` much easier to follow in logs.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/getting-started.md b/pkg/lib/workspace/default/.core/reference/docs/getting-started.md
deleted file mode 100644
index d2d81666..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/getting-started.md
+++ /dev/null
@@ -1,208 +0,0 @@
----
-title: Getting Started
-description: Build a first CoreGO application with the current API.
----
-
-# Getting Started
-
-This page shows the shortest path to a useful CoreGO application using the API that exists in this repository today.
-
-## Install
-
-```bash
-go get dappco.re/go/core
-```
-
-## Create a Core
-
-`New` takes zero or more `core.Options` slices, but the current implementation only reads the first one. In practice, treat the constructor as `core.New(core.Options{...})`.
-
-```go
-package main
-
-import "dappco.re/go/core"
-
-func main() {
- c := core.New(core.Options{
- {Key: "name", Value: "agent-workbench"},
- })
-
- _ = c
-}
-```
-
-The `name` option is copied into `c.App().Name`.
-
-## Register a Service
-
-Services are registered explicitly with a name and a `core.Service` DTO.
-
-```go
-c.Service("audit", core.Service{
- OnStart: func() core.Result {
- core.Info("audit service started", "app", c.App().Name)
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- core.Info("audit service stopped", "app", c.App().Name)
- return core.Result{OK: true}
- },
-})
-```
-
-This registry stores `core.Service` values. It is a lifecycle registry, not a typed object container.
-
-## Register a Query, Task, and Command
-
-```go
-type workspaceCountQuery struct{}
-
-type createWorkspaceTask struct {
- Name string
-}
-
-c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
- switch q.(type) {
- case workspaceCountQuery:
- return core.Result{Value: 1, OK: true}
- }
- return core.Result{}
-})
-
-c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
- switch task := t.(type) {
- case createWorkspaceTask:
- path := "/tmp/agent-workbench/" + task.Name
- return core.Result{Value: path, OK: true}
- }
- return core.Result{}
-})
-
-c.Command("workspace/create", core.Command{
- Action: func(opts core.Options) core.Result {
- return c.PERFORM(createWorkspaceTask{
- Name: opts.String("name"),
- })
- },
-})
-```
-
-## Start the Runtime
-
-```go
-if !c.ServiceStartup(context.Background(), nil).OK {
- panic("startup failed")
-}
-```
-
-`ServiceStartup` returns `core.Result`, not `error`.
-
-## Run Through the CLI Surface
-
-```go
-r := c.Cli().Run("workspace", "create", "--name=alpha")
-if r.OK {
- fmt.Println("created:", r.Value)
-}
-```
-
-For flags with values, the CLI stores the value as a string. `--name=alpha` becomes `opts.String("name") == "alpha"`.
-
-## Query the System
-
-```go
-count := c.QUERY(workspaceCountQuery{})
-if count.OK {
- fmt.Println("workspace count:", count.Value)
-}
-```
-
-## Shut Down Cleanly
-
-```go
-_ = c.ServiceShutdown(context.Background())
-```
-
-Shutdown cancels `c.Context()`, broadcasts `ActionServiceShutdown{}`, waits for background tasks to finish, and then runs service stop hooks.
-
-## Full Example
-
-```go
-package main
-
-import (
- "context"
- "fmt"
-
- "dappco.re/go/core"
-)
-
-type workspaceCountQuery struct{}
-
-type createWorkspaceTask struct {
- Name string
-}
-
-func main() {
- c := core.New(core.Options{
- {Key: "name", Value: "agent-workbench"},
- })
-
- c.Config().Set("workspace.root", "/tmp/agent-workbench")
- c.Config().Enable("workspace.templates")
-
- c.Service("audit", core.Service{
- OnStart: func() core.Result {
- core.Info("service started", "service", "audit")
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- core.Info("service stopped", "service", "audit")
- return core.Result{OK: true}
- },
- })
-
- c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
- switch q.(type) {
- case workspaceCountQuery:
- return core.Result{Value: 1, OK: true}
- }
- return core.Result{}
- })
-
- c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
- switch task := t.(type) {
- case createWorkspaceTask:
- path := c.Config().String("workspace.root") + "/" + task.Name
- return core.Result{Value: path, OK: true}
- }
- return core.Result{}
- })
-
- c.Command("workspace/create", core.Command{
- Action: func(opts core.Options) core.Result {
- return c.PERFORM(createWorkspaceTask{
- Name: opts.String("name"),
- })
- },
- })
-
- if !c.ServiceStartup(context.Background(), nil).OK {
- panic("startup failed")
- }
-
- created := c.Cli().Run("workspace", "create", "--name=alpha")
- fmt.Println("created:", created.Value)
-
- count := c.QUERY(workspaceCountQuery{})
- fmt.Println("workspace count:", count.Value)
-
- _ = c.ServiceShutdown(context.Background())
-}
-```
-
-## Next Steps
-
-- Read [primitives.md](primitives.md) next so the repeated shapes are clear.
-- Read [commands.md](commands.md) if you are building a CLI-first system.
-- Read [messaging.md](messaging.md) if services need to collaborate without direct imports.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/index.md b/pkg/lib/workspace/default/.core/reference/docs/index.md
deleted file mode 100644
index 0ec86472..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/index.md
+++ /dev/null
@@ -1,112 +0,0 @@
----
-title: CoreGO
-description: AX-first documentation for the CoreGO framework.
----
-
-# CoreGO
-
-CoreGO is the foundation layer for the Core ecosystem. It gives you one container, one command tree, one message bus, and a small set of shared primitives that repeat across the whole framework.
-
-The current module path is `dappco.re/go/core`.
-
-## AX View
-
-CoreGO already follows the main AX ideas from RFC-025:
-
-- predictable names such as `Core`, `Service`, `Command`, `Options`, `Result`, `Message`
-- path-shaped command registration such as `deploy/to/homelab`
-- one repeated input shape (`Options`) and one repeated return shape (`Result`)
-- comments and examples that show real usage instead of restating the type signature
-
-## What CoreGO Owns
-
-| Surface | Purpose |
-|---------|---------|
-| `Core` | Central container and access point |
-| `Service` | Managed lifecycle component |
-| `Command` | Path-based command tree node |
-| `ACTION`, `QUERY`, `PERFORM` | Decoupled communication between components |
-| `Data`, `Drive`, `Fs`, `Config`, `I18n`, `Cli` | Built-in subsystems for common runtime work |
-| `E`, `Wrap`, `ErrorLog`, `ErrorPanic` | Structured failures and panic recovery |
-
-## Quick Example
-
-```go
-package main
-
-import (
- "context"
- "fmt"
-
- "dappco.re/go/core"
-)
-
-type flushCacheTask struct {
- Name string
-}
-
-func main() {
- c := core.New(core.Options{
- {Key: "name", Value: "agent-workbench"},
- })
-
- c.Service("cache", core.Service{
- OnStart: func() core.Result {
- core.Info("cache ready", "app", c.App().Name)
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- core.Info("cache stopped", "app", c.App().Name)
- return core.Result{OK: true}
- },
- })
-
- c.RegisterTask(func(_ *core.Core, task core.Task) core.Result {
- switch task.(type) {
- case flushCacheTask:
- return core.Result{Value: "cache flushed", OK: true}
- }
- return core.Result{}
- })
-
- c.Command("cache/flush", core.Command{
- Action: func(opts core.Options) core.Result {
- return c.PERFORM(flushCacheTask{Name: opts.String("name")})
- },
- })
-
- if !c.ServiceStartup(context.Background(), nil).OK {
- panic("startup failed")
- }
-
- r := c.Cli().Run("cache", "flush", "--name=session-store")
- fmt.Println(r.Value)
-
- _ = c.ServiceShutdown(context.Background())
-}
-```
-
-## Documentation Paths
-
-| Path | Covers |
-|------|--------|
-| [getting-started.md](getting-started.md) | First runnable CoreGO app |
-| [primitives.md](primitives.md) | `Options`, `Result`, `Service`, `Message`, `Query`, `Task` |
-| [services.md](services.md) | Service registry, service locks, runtime helpers |
-| [commands.md](commands.md) | Path-based commands and CLI execution |
-| [messaging.md](messaging.md) | `ACTION`, `QUERY`, `QUERYALL`, `PERFORM`, `PerformAsync` |
-| [lifecycle.md](lifecycle.md) | Startup, shutdown, context, background task draining |
-| [configuration.md](configuration.md) | Constructor options, config state, feature flags |
-| [subsystems.md](subsystems.md) | `App`, `Data`, `Drive`, `Fs`, `I18n`, `Cli` |
-| [errors.md](errors.md) | Structured errors, logging helpers, panic recovery |
-| [testing.md](testing.md) | Test naming and framework-level testing patterns |
-| [pkg/core.md](pkg/core.md) | Package-level reference summary |
-| [pkg/log.md](pkg/log.md) | Logging reference for the root package |
-| [pkg/PACKAGE_STANDARDS.md](pkg/PACKAGE_STANDARDS.md) | AX package-authoring guidance |
-
-## Good Reading Order
-
-1. Start with [getting-started.md](getting-started.md).
-2. Learn the repeated shapes in [primitives.md](primitives.md).
-3. Pick the integration path you need next: [services.md](services.md), [commands.md](commands.md), or [messaging.md](messaging.md).
-4. Use [subsystems.md](subsystems.md), [errors.md](errors.md), and [testing.md](testing.md) as reference pages while building.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/lifecycle.md b/pkg/lib/workspace/default/.core/reference/docs/lifecycle.md
deleted file mode 100644
index 59ba644f..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/lifecycle.md
+++ /dev/null
@@ -1,111 +0,0 @@
----
-title: Lifecycle
-description: Startup, shutdown, context ownership, and background task draining.
----
-
-# Lifecycle
-
-CoreGO manages lifecycle through `core.Service` callbacks, not through reflection or implicit interfaces.
-
-## Service Hooks
-
-```go
-c.Service("cache", core.Service{
- OnStart: func() core.Result {
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- return core.Result{OK: true}
- },
-})
-```
-
-Only services with `OnStart` appear in `Startables()`. Only services with `OnStop` appear in `Stoppables()`.
-
-## `ServiceStartup`
-
-```go
-r := c.ServiceStartup(context.Background(), nil)
-```
-
-### What It Does
-
-1. clears the shutdown flag
-2. stores a new cancellable context on `c.Context()`
-3. runs each `OnStart`
-4. broadcasts `ActionServiceStartup{}`
-
-### Failure Behavior
-
-- if the input context is already cancelled, startup returns that error
-- if any `OnStart` returns `OK:false`, startup stops immediately and returns that result
-
-## `ServiceShutdown`
-
-```go
-r := c.ServiceShutdown(context.Background())
-```
-
-### What It Does
-
-1. sets the shutdown flag
-2. cancels `c.Context()`
-3. broadcasts `ActionServiceShutdown{}`
-4. waits for background tasks created by `PerformAsync`
-5. runs each `OnStop`
-
-### Failure Behavior
-
-- if draining background tasks hits the shutdown context deadline, shutdown returns that context error
-- when service stop hooks fail, CoreGO returns the first error it sees
-
-## Ordering
-
-The current implementation builds `Startables()` and `Stoppables()` by iterating over a map-backed registry.
-
-That means lifecycle order is not guaranteed today.
-
-If your application needs strict startup or shutdown ordering, orchestrate it explicitly inside a smaller number of service callbacks instead of relying on registry order.
-
-## `c.Context()`
-
-`ServiceStartup` creates the context returned by `c.Context()`.
-
-Use it for background work that should stop when the application shuts down:
-
-```go
-c.Service("watcher", core.Service{
- OnStart: func() core.Result {
- go func(ctx context.Context) {
- <-ctx.Done()
- }(c.Context())
- return core.Result{OK: true}
- },
-})
-```
-
-## Built-In Lifecycle Actions
-
-You can listen for lifecycle state changes through the action bus.
-
-```go
-c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
- switch msg.(type) {
- case core.ActionServiceStartup:
- core.Info("core startup completed")
- case core.ActionServiceShutdown:
- core.Info("core shutdown started")
- }
- return core.Result{OK: true}
-})
-```
-
-## Background Task Draining
-
-`ServiceShutdown` waits for the internal task waitgroup to finish before calling stop hooks.
-
-This is what makes `PerformAsync` safe for long-running work that should complete before teardown.
-
-## `OnReload`
-
-`Service` includes an `OnReload` callback field, but CoreGO does not currently expose a top-level lifecycle runner for reload operations.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/messaging.md b/pkg/lib/workspace/default/.core/reference/docs/messaging.md
deleted file mode 100644
index 688893af..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/messaging.md
+++ /dev/null
@@ -1,171 +0,0 @@
----
-title: Messaging
-description: ACTION, QUERY, QUERYALL, PERFORM, and async task flow.
----
-
-# Messaging
-
-CoreGO uses one message bus for broadcasts, lookups, and work dispatch.
-
-## Message Types
-
-```go
-type Message any
-type Query any
-type Task any
-```
-
-Your own structs define the protocol.
-
-```go
-type repositoryIndexed struct {
- Name string
-}
-
-type repositoryCountQuery struct{}
-
-type syncRepositoryTask struct {
- Name string
-}
-```
-
-## `ACTION`
-
-`ACTION` is a broadcast.
-
-```go
-c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
- switch m := msg.(type) {
- case repositoryIndexed:
- core.Info("repository indexed", "name", m.Name)
- return core.Result{OK: true}
- }
- return core.Result{OK: true}
-})
-
-r := c.ACTION(repositoryIndexed{Name: "core-go"})
-```
-
-### Behavior
-
-- all registered action handlers are called in their current registration order
-- if a handler returns `OK:false`, dispatch stops and that `Result` is returned
-- if no handler fails, `ACTION` returns `Result{OK:true}`
-
-## `QUERY`
-
-`QUERY` is first-match request-response.
-
-```go
-c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
- switch q.(type) {
- case repositoryCountQuery:
- return core.Result{Value: 42, OK: true}
- }
- return core.Result{}
-})
-
-r := c.QUERY(repositoryCountQuery{})
-```
-
-### Behavior
-
-- handlers run until one returns `OK:true`
-- the first successful result wins
-- if nothing handles the query, CoreGO returns an empty `Result`
-
-## `QUERYALL`
-
-`QUERYALL` collects every successful non-nil response.
-
-```go
-r := c.QUERYALL(repositoryCountQuery{})
-results := r.Value.([]any)
-```
-
-### Behavior
-
-- every query handler is called
-- only `OK:true` results with non-nil `Value` are collected
-- the call itself returns `OK:true` even when the result list is empty
-
-## `PERFORM`
-
-`PERFORM` dispatches a task to the first handler that accepts it.
-
-```go
-c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
- switch task := t.(type) {
- case syncRepositoryTask:
- return core.Result{Value: "synced " + task.Name, OK: true}
- }
- return core.Result{}
-})
-
-r := c.PERFORM(syncRepositoryTask{Name: "core-go"})
-```
-
-### Behavior
-
-- handlers run until one returns `OK:true`
-- the first successful result wins
-- if nothing handles the task, CoreGO returns an empty `Result`
-
-## `PerformAsync`
-
-`PerformAsync` runs a task in a background goroutine and returns a generated task identifier.
-
-```go
-r := c.PerformAsync(syncRepositoryTask{Name: "core-go"})
-taskID := r.Value.(string)
-```
-
-### Generated Events
-
-Async execution emits three action messages:
-
-| Message | When |
-|---------|------|
-| `ActionTaskStarted` | just before background execution begins |
-| `ActionTaskProgress` | whenever `Progress` is called |
-| `ActionTaskCompleted` | after the task finishes or panics |
-
-Example listener:
-
-```go
-c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
- switch m := msg.(type) {
- case core.ActionTaskCompleted:
- core.Info("task completed", "task", m.TaskIdentifier, "err", m.Error)
- }
- return core.Result{OK: true}
-})
-```
-
-## Progress Updates
-
-```go
-c.Progress(taskID, 0.5, "indexing commits", syncRepositoryTask{Name: "core-go"})
-```
-
-That broadcasts `ActionTaskProgress`.
-
-## `TaskWithIdentifier`
-
-Tasks that implement `TaskWithIdentifier` receive the generated ID before dispatch.
-
-```go
-type trackedTask struct {
- ID string
- Name string
-}
-
-func (t *trackedTask) SetTaskIdentifier(id string) { t.ID = id }
-func (t *trackedTask) GetTaskIdentifier() string { return t.ID }
-```
-
-## Shutdown Interaction
-
-When shutdown has started, `PerformAsync` returns an empty `Result` instead of scheduling more work.
-
-This is why `ServiceShutdown` can safely drain the outstanding background tasks before stopping services.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/pkg/PACKAGE_STANDARDS.md b/pkg/lib/workspace/default/.core/reference/docs/pkg/PACKAGE_STANDARDS.md
deleted file mode 100644
index 398bbf65..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/pkg/PACKAGE_STANDARDS.md
+++ /dev/null
@@ -1,138 +0,0 @@
-# AX Package Standards
-
-This page describes how to build packages on top of CoreGO in the style described by RFC-025.
-
-## 1. Prefer Predictable Names
-
-Use names that tell an agent what the thing is without translation.
-
-Good:
-
-- `RepositoryService`
-- `RepositoryServiceOptions`
-- `WorkspaceCountQuery`
-- `SyncRepositoryTask`
-
-Avoid shortening names unless the abbreviation is already universal.
-
-## 2. Put Real Usage in Comments
-
-Write comments that show a real call with realistic values.
-
-Good:
-
-```go
-// Sync a repository into the local workspace cache.
-// svc.SyncRepository("core-go", "/srv/repos/core-go")
-```
-
-Avoid comments that only repeat the signature.
-
-## 3. Keep Paths Semantic
-
-If a command or template lives at a path, let the path explain the intent.
-
-Good:
-
-```text
-deploy/to/homelab
-workspace/create
-template/workspace/go
-```
-
-That keeps the CLI, tests, docs, and message vocabulary aligned.
-
-## 4. Reuse CoreGO Primitives
-
-At Core boundaries, prefer the shared shapes:
-
-- `core.Options` for lightweight input
-- `core.Result` for output
-- `core.Service` for lifecycle registration
-- `core.Message`, `core.Query`, `core.Task` for bus protocols
-
-Inside your package, typed structs are still good. Use `ServiceRuntime[T]` when you want typed package options plus a `Core` reference.
-
-```go
-type repositoryServiceOptions struct {
- BaseDirectory string
-}
-
-type repositoryService struct {
- *core.ServiceRuntime[repositoryServiceOptions]
-}
-```
-
-## 5. Prefer Explicit Registration
-
-Register services and commands with names and paths that stay readable in grep results.
-
-```go
-c.Service("repository", core.Service{...})
-c.Command("repository/sync", core.Command{...})
-```
-
-## 6. Use the Bus for Decoupling
-
-When one package needs another package’s behavior, prefer queries and tasks over tight package coupling.
-
-```go
-type repositoryCountQuery struct{}
-type syncRepositoryTask struct {
- Name string
-}
-```
-
-That keeps the protocol visible in code and easy for agents to follow.
-
-## 7. Use Structured Errors
-
-Use `core.E`, `core.Wrap`, and `core.WrapCode`.
-
-```go
-return core.Result{
- Value: core.E("repository.Sync", "git fetch failed", err),
- OK: false,
-}
-```
-
-Do not introduce free-form `fmt.Errorf` chains in framework code.
-
-## 8. Keep Testing Names Predictable
-
-Follow the repository pattern:
-
-- `_Good`
-- `_Bad`
-- `_Ugly`
-
-Example:
-
-```go
-func TestRepositorySync_Good(t *testing.T) {}
-func TestRepositorySync_Bad(t *testing.T) {}
-func TestRepositorySync_Ugly(t *testing.T) {}
-```
-
-## 9. Prefer Stable Shapes Over Clever APIs
-
-For package APIs, avoid patterns that force an agent to infer too much hidden control flow.
-
-Prefer:
-
-- clear structs
-- explicit names
-- path-based commands
-- visible message types
-
-Avoid:
-
-- implicit global state unless it is truly a default service
-- panic-hiding constructors
-- dense option chains when a small explicit struct would do
-
-## 10. Document the Current Reality
-
-If the implementation is in transition, document what the code does now, not the API shape you plan to have later.
-
-That keeps agents correct on first pass, which is the real AX metric.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/pkg/core.md b/pkg/lib/workspace/default/.core/reference/docs/pkg/core.md
deleted file mode 100644
index 88bd18b5..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/pkg/core.md
+++ /dev/null
@@ -1,81 +0,0 @@
-# Package Reference: `core`
-
-Import path:
-
-```go
-import "dappco.re/go/core"
-```
-
-This repository exposes one root package. The main areas are:
-
-## Constructors and Accessors
-
-| Name | Purpose |
-|------|---------|
-| `New` | Create a `*Core` |
-| `NewRuntime` | Create an empty runtime wrapper |
-| `NewWithFactories` | Create a runtime wrapper from named service factories |
-| `Options`, `App`, `Data`, `Drive`, `Fs`, `Config`, `Error`, `Log`, `Cli`, `IPC`, `I18n`, `Context` | Access the built-in subsystems |
-
-## Core Primitives
-
-| Name | Purpose |
-|------|---------|
-| `Option`, `Options` | Input configuration and metadata |
-| `Result` | Shared output shape |
-| `Service` | Lifecycle DTO |
-| `Command` | Command tree node |
-| `Message`, `Query`, `Task` | Message bus payload types |
-
-## Service and Runtime APIs
-
-| Name | Purpose |
-|------|---------|
-| `Service` | Register or read a named service |
-| `Services` | List registered service names |
-| `Startables`, `Stoppables` | Snapshot lifecycle-capable services |
-| `LockEnable`, `LockApply` | Activate the service registry lock |
-| `ServiceRuntime[T]` | Helper for package authors |
-
-## Command and CLI APIs
-
-| Name | Purpose |
-|------|---------|
-| `Command` | Register or read a command by path |
-| `Commands` | List command paths |
-| `Cli().Run` | Resolve arguments to a command and execute it |
-| `Cli().PrintHelp` | Show executable commands |
-
-## Messaging APIs
-
-| Name | Purpose |
-|------|---------|
-| `ACTION`, `Action` | Broadcast a message |
-| `QUERY`, `Query` | Return the first successful query result |
-| `QUERYALL`, `QueryAll` | Collect all successful query results |
-| `PERFORM`, `Perform` | Run the first task handler that accepts the task |
-| `PerformAsync` | Run a task in the background |
-| `Progress` | Broadcast async task progress |
-| `RegisterAction`, `RegisterActions`, `RegisterQuery`, `RegisterTask` | Register bus handlers |
-
-## Subsystems
-
-| Name | Purpose |
-|------|---------|
-| `Config` | Runtime settings and feature flags |
-| `Data` | Embedded filesystem mounts |
-| `Drive` | Named transport handles |
-| `Fs` | Local filesystem operations |
-| `I18n` | Locale collection and translation delegation |
-| `App`, `Find` | Application identity and executable lookup |
-
-## Errors and Logging
-
-| Name | Purpose |
-|------|---------|
-| `E`, `Wrap`, `WrapCode`, `NewCode` | Structured error creation |
-| `Operation`, `ErrorCode`, `ErrorMessage`, `Root`, `StackTrace`, `FormatStackTrace` | Error inspection |
-| `NewLog`, `Default`, `SetDefault`, `SetLevel`, `SetRedactKeys` | Logger creation and defaults |
-| `LogErr`, `LogPanic`, `ErrorLog`, `ErrorPanic` | Error-aware logging and panic recovery |
-
-Use the top-level docs in `docs/` for task-oriented guidance, then use this page as a compact reference.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/pkg/log.md b/pkg/lib/workspace/default/.core/reference/docs/pkg/log.md
deleted file mode 100644
index 15e9db1a..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/pkg/log.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Logging Reference
-
-Logging lives in the root `core` package in this repository. There is no separate `pkg/log` import path here.
-
-## Create a Logger
-
-```go
-logger := core.NewLog(core.LogOptions{
- Level: core.LevelInfo,
-})
-```
-
-## Levels
-
-| Level | Meaning |
-|-------|---------|
-| `LevelQuiet` | no output |
-| `LevelError` | errors and security events |
-| `LevelWarn` | warnings, errors, security events |
-| `LevelInfo` | informational, warnings, errors, security events |
-| `LevelDebug` | everything |
-
-## Log Methods
-
-```go
-logger.Debug("workspace discovered", "path", "/srv/workspaces")
-logger.Info("service started", "service", "audit")
-logger.Warn("retrying fetch", "attempt", 2)
-logger.Error("fetch failed", "err", err)
-logger.Security("sandbox escape detected", "path", attemptedPath)
-```
-
-## Default Logger
-
-The package owns a default logger.
-
-```go
-core.SetLevel(core.LevelDebug)
-core.SetRedactKeys("token", "password")
-
-core.Info("service started", "service", "audit")
-```
-
-## Redaction
-
-Values for keys listed in `RedactKeys` are replaced with `[REDACTED]`.
-
-```go
-logger.SetRedactKeys("token")
-logger.Info("login", "user", "cladius", "token", "secret-value")
-```
-
-## Output and Rotation
-
-```go
-logger := core.NewLog(core.LogOptions{
- Level: core.LevelInfo,
- Output: os.Stderr,
-})
-```
-
-If you provide `Rotation` and set `RotationWriterFactory`, the logger writes to the rotating writer instead of the plain output stream.
-
-## Error-Aware Logging
-
-`LogErr` extracts structured error context before logging:
-
-```go
-le := core.NewLogErr(logger)
-le.Log(err)
-```
-
-`ErrorLog` is the log-and-return wrapper exposed through `c.Log()`.
-
-## Panic-Aware Logging
-
-`LogPanic` is the lightweight panic logger:
-
-```go
-defer core.NewLogPanic(logger).Recover()
-```
-
-It logs the recovered panic but does not manage crash files. For crash reports, use `c.Error().Recover()`.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-design.md b/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-design.md
deleted file mode 100644
index 08257913..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-design.md
+++ /dev/null
@@ -1,261 +0,0 @@
-# Lint Pattern Catalog & Polish Skill Design
-
-> **Partial implementation (14 Mar 2026):** Layer 1 (`core/lint` -- catalog, matcher, scanner, CLI) is fully implemented and documented at `docs/tools/lint/index.md`. Layer 2 (MCP subsystem in `go-ai`) and Layer 3 (Claude Code polish skill in `core/agent`) are NOT implemented. This plan is retained for those remaining layers.
-
-**Goal:** A structured pattern catalog (`core/lint`) that captures recurring code quality findings as regex rules, exposes them via MCP tools in `go-ai`, and orchestrates multi-AI code review via a Claude Code skill in `core/agent`.
-
-**Architecture:** Three layers — a standalone catalog+matcher library (`core/lint`), an MCP subsystem in `go-ai` that exposes lint tools to agents, and a Claude Code plugin in `core/agent` that orchestrates the "polish" workflow (deterministic checks + AI reviewers + feedback loop into the catalog).
-
-**Tech Stack:** Go (catalog, matcher, CLI, MCP subsystem), YAML (rule definitions), JSONL (findings output, compatible with `~/.core/ai/metrics/`), Claude Code plugin format (hooks.json, commands/*.md, plugin.json).
-
----
-
-## Context
-
-During a code review sweep of 18 Go repos (March 2026), AI reviewers (Gemini, Claude) found ~20 recurring patterns: SQL injection, path traversal, XSS, missing constant-time comparison, goroutine leaks, Go 1.26 modernisation opportunities, and more. Many of these patterns repeat across repos.
-
-Currently these findings exist only as commit messages. This design captures them as a reusable, machine-readable catalog that:
-1. Deterministic tools can run immediately (regex matching)
-2. MCP-connected agents can query and apply
-3. LEM models can train on for "does this comply with CoreGo standards?" judgements
-4. Grows automatically as AI reviewers find new patterns
-
-## Layer 1: `core/lint` — Pattern Catalog & Matcher
-
-### Repository Structure
-
-```
-core/lint/
-├── go.mod # forge.lthn.ai/core/lint
-├── catalog/
-│ ├── go-security.yaml # SQL injection, path traversal, XSS, constant-time
-│ ├── go-modernise.yaml # Go 1.26: slices.Clone, wg.Go, maps.Keys, range-over-int
-│ ├── go-correctness.yaml # Deadlocks, goroutine leaks, nil guards, error handling
-│ ├── php-security.yaml # XSS, CSRF, mass assignment, SQL injection
-│ ├── ts-security.yaml # DOM XSS, prototype pollution
-│ └── cpp-safety.yaml # Buffer overflow, use-after-free
-├── pkg/lint/
-│ ├── catalog.go # Load + parse YAML catalog files
-│ ├── rule.go # Rule struct definition
-│ ├── matcher.go # Regex matcher against file contents
-│ ├── report.go # Structured findings output (JSON/JSONL/text)
-│ ├── catalog_test.go
-│ ├── matcher_test.go
-│ └── report_test.go
-├── cmd/core-lint/
-│ └── main.go # `core-lint check ./...` CLI
-└── .core/
- └── build.yaml # Produces core-lint binary
-```
-
-### Rule Schema (YAML)
-
-```yaml
-- id: go-sec-001
- title: "SQL wildcard injection in LIKE clauses"
- severity: high # critical, high, medium, low, info
- languages: [go]
- tags: [security, injection, owasp-a03]
- pattern: 'LIKE\s+\?\s*,\s*["\x60]%\s*\+'
- exclude_pattern: 'EscapeLike' # suppress if this also matches
- fix: "Use parameterised LIKE with explicit escaping of % and _ characters"
- found_in: [go-store] # repos where first discovered
- example_bad: |
- db.Where("name LIKE ?", "%"+input+"%")
- example_good: |
- db.Where("name LIKE ?", EscapeLike(input))
- first_seen: "2026-03-09"
- detection: regex # future: ast, semantic
- auto_fixable: false # future: true when we add codemods
-```
-
-### Rule Struct (Go)
-
-```go
-type Rule struct {
- ID string `yaml:"id"`
- Title string `yaml:"title"`
- Severity string `yaml:"severity"`
- Languages []string `yaml:"languages"`
- Tags []string `yaml:"tags"`
- Pattern string `yaml:"pattern"`
- ExcludePattern string `yaml:"exclude_pattern,omitempty"`
- Fix string `yaml:"fix"`
- FoundIn []string `yaml:"found_in,omitempty"`
- ExampleBad string `yaml:"example_bad,omitempty"`
- ExampleGood string `yaml:"example_good,omitempty"`
- FirstSeen string `yaml:"first_seen"`
- Detection string `yaml:"detection"` // regex | ast | semantic
- AutoFixable bool `yaml:"auto_fixable"`
-}
-```
-
-### Finding Struct (Go)
-
-Designed to align with go-ai's `ScanAlert` shape and `~/.core/ai/metrics/` JSONL format:
-
-```go
-type Finding struct {
- RuleID string `json:"rule_id"`
- Title string `json:"title"`
- Severity string `json:"severity"`
- File string `json:"file"`
- Line int `json:"line"`
- Match string `json:"match"` // matched text
- Fix string `json:"fix"`
- Repo string `json:"repo,omitempty"`
-}
-```
-
-### CLI Interface
-
-```bash
-# Check current directory against all catalogs for detected languages
-core-lint check ./...
-
-# Check specific languages/catalogs
-core-lint check --lang go --catalog go-security ./pkg/...
-
-# Output as JSON (for piping to other tools)
-core-lint check --format json ./...
-
-# List available rules
-core-lint catalog list
-core-lint catalog list --lang go --severity high
-
-# Show a specific rule with examples
-core-lint catalog show go-sec-001
-```
-
-## Layer 2: `go-ai` Lint MCP Subsystem
-
-New subsystem registered alongside files/rag/ml/brain:
-
-```go
-type LintSubsystem struct {
- catalog *lint.Catalog
- root string // workspace root for scanning
-}
-
-func (s *LintSubsystem) Name() string { return "lint" }
-
-func (s *LintSubsystem) RegisterTools(server *mcp.Server) {
- // lint_check - run rules against workspace files
- // lint_catalog - list/search available rules
- // lint_report - get findings summary for a path
-}
-```
-
-### MCP Tools
-
-| Tool | Input | Output | Group |
-|------|-------|--------|-------|
-| `lint_check` | `{path: string, lang?: string, severity?: string}` | `{findings: []Finding}` | lint |
-| `lint_catalog` | `{lang?: string, tags?: []string, severity?: string}` | `{rules: []Rule}` | lint |
-| `lint_report` | `{path: string, format?: "summary" or "detailed"}` | `{summary: ReportSummary}` | lint |
-
-This means any MCP-connected agent (Claude, LEM, Codex) can call `lint_check` to scan code against the catalog.
-
-## Layer 3: `core/agent` Polish Skill
-
-Claude Code plugin at `core/agent/claude/polish/`:
-
-```
-core/agent/claude/polish/
-├── plugin.json
-├── hooks.json # optional: PostToolUse after git commit
-├── commands/
-│ └── polish.md # /polish slash command
-└── scripts/
- └── run-lint.sh # shells out to core-lint
-```
-
-### `/polish` Command Flow
-
-1. Run `core-lint check ./...` for fast deterministic findings
-2. Report findings to user
-3. Optionally run AI reviewers (Gemini CLI, Codex) for deeper analysis
-4. Deduplicate AI findings against catalog (already-known patterns)
-5. Propose new patterns as catalog additions (PR to core/lint)
-
-### Subagent Configuration (`.core/agents/`)
-
-Repos can configure polish behaviour:
-
-```yaml
-# any-repo/.core/agents/polish.yaml
-languages: [go]
-catalogs: [go-security, go-modernise, go-correctness]
-reviewers: [gemini] # which AI tools to invoke
-exclude: [vendor/, testdata/, *_test.go]
-severity_threshold: medium # only report medium+ findings
-```
-
-## Findings to LEM Pipeline
-
-```
-core-lint check -> findings.json
- |
- v
-~/.core/ai/metrics/YYYY-MM-DD.jsonl (audit trail)
- |
- v
-LEM training data:
- - Rule examples (bad/good pairs) -> supervised training signal
- - Finding frequency -> pattern importance weighting
- - Rule descriptions -> natural language understanding of "why"
- |
- v
-LEM tool: "does this code comply with CoreGo standards?"
- -> queries lint_catalog via MCP
- -> applies learned pattern recognition
- -> reports violations with rule IDs and fixes
-```
-
-## Initial Catalog Seed
-
-From the March 2026 ecosystem sweep:
-
-| ID | Title | Severity | Language | Found In |
-|----|-------|----------|----------|----------|
-| go-sec-001 | SQL wildcard injection | high | go | go-store |
-| go-sec-002 | Path traversal in cache keys | high | go | go-cache |
-| go-sec-003 | XSS in HTML output | high | go | go-html |
-| go-sec-004 | Non-constant-time auth comparison | high | go | go-crypt |
-| go-sec-005 | Log injection via unescaped input | medium | go | go-log |
-| go-sec-006 | Key material in log output | high | go | go-log |
-| go-cor-001 | Goroutine leak (no WaitGroup) | high | go | core/go |
-| go-cor-002 | Shutdown deadlock (wg.Wait no timeout) | high | go | core/go |
-| go-cor-003 | Silent error swallowing | medium | go | go-process, go-ratelimit |
-| go-cor-004 | Panic in library code | medium | go | go-i18n |
-| go-cor-005 | Delete without path validation | high | go | go-io |
-| go-mod-001 | Manual slice clone (append nil pattern) | low | go | core/go |
-| go-mod-002 | Manual sort instead of slices.Sorted | low | go | core/go |
-| go-mod-003 | Manual reverse loop instead of slices.Backward | low | go | core/go |
-| go-mod-004 | sync.WaitGroup Add+Done instead of Go() | low | go | core/go |
-| go-mod-005 | Manual map key collection instead of maps.Keys | low | go | core/go |
-| go-cor-006 | Missing error return from API calls | medium | go | go-forge, go-git |
-| go-cor-007 | Signal handler uses wrong type | medium | go | go-process |
-
-## Dependencies
-
-```
-core/lint (standalone, zero core deps)
- ^
- |
-go-ai/mcp/lint/ (imports core/lint for catalog + matcher)
- ^
- |
-core/agent/claude/polish/ (shells out to core-lint CLI)
-```
-
-`core/lint` has no dependency on `core/go` or any other framework module. It is a standalone library + CLI, like `core/go-io`.
-
-## Future Extensions (Not Built Now)
-
-- **AST-based detection** (layer 2): Parse Go/PHP AST, match structural patterns
-- **Semantic detection** (layer 3): LEM judges code against rule descriptions
-- **Auto-fix codemods**: `core-lint fix` applies known fixes automatically
-- **CI integration**: GitHub Actions workflow runs `core-lint check` on PRs
-- **CodeRabbit integration**: Import CodeRabbit findings as catalog entries
-- **Cross-repo dashboard**: Aggregate findings across all repos in workspace
diff --git a/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-plan.md b/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-plan.md
deleted file mode 100644
index 7f1ddec2..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-09-lint-pattern-catalog-plan.md
+++ /dev/null
@@ -1,1668 +0,0 @@
-# Lint Pattern Catalog Implementation Plan
-
-> **Fully implemented (14 Mar 2026).** All tasks in this plan are complete. The `core/lint` module ships 18 rules across 3 catalogs, with a working CLI and embedded YAML. This plan is retained alongside the design doc, which tracks the remaining MCP and polish skill layers.
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Build `core/lint` — a standalone Go library + CLI that loads YAML pattern catalogs and runs regex-based code checks, seeded with 18 patterns from the March 2026 ecosystem sweep.
-
-**Architecture:** Standalone Go module (`forge.lthn.ai/core/lint`) with zero framework deps. YAML catalog files define rules (id, severity, regex pattern, fix). `pkg/lint` loads catalogs and matches patterns against files. `cmd/core-lint` is a Cobra CLI. Uses `cli.Main()` + `cli.WithCommands()` from `core/cli`.
-
-**Tech Stack:** Go 1.26, `gopkg.in/yaml.v3` (YAML parsing), `forge.lthn.ai/core/cli` (CLI framework), `github.com/stretchr/testify` (testing), `embed` (catalog embedding).
-
----
-
-### Task 1: Create repo and Go module
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/go.mod`
-- Create: `/Users/snider/Code/core/lint/.core/build.yaml`
-- Create: `/Users/snider/Code/core/lint/CLAUDE.md`
-
-**Step 1: Create repo on forge**
-
-```bash
-ssh -p 2223 git@forge.lthn.ai
-```
-
-If SSH repo creation isn't available, create via Forgejo API:
-```bash
-curl -X POST "https://forge.lthn.ai/api/v1/orgs/core/repos" \
- -H "Authorization: token $FORGE_TOKEN" \
- -H "Content-Type: application/json" \
- -d '{"name":"lint","description":"Pattern catalog & regex matcher for code quality","auto_init":true,"default_branch":"main"}'
-```
-
-Or manually create on forge.lthn.ai web UI under the `core` org.
-
-**Step 2: Clone and initialise Go module**
-
-```bash
-cd ~/Code/core
-git clone ssh://git@forge.lthn.ai:2223/core/lint.git
-cd lint
-go mod init forge.lthn.ai/core/lint
-```
-
-Set Go version in go.mod:
-```
-module forge.lthn.ai/core/lint
-
-go 1.26.0
-```
-
-**Step 3: Create `.core/build.yaml`**
-
-```yaml
-version: 1
-
-project:
- name: core-lint
- description: Pattern catalog and regex code checker
- main: ./cmd/core-lint
- binary: core-lint
-
-build:
- cgo: false
- flags:
- - -trimpath
- ldflags:
- - -s
- - -w
-
-targets:
- - os: linux
- arch: amd64
- - os: linux
- arch: arm64
- - os: darwin
- arch: arm64
- - os: windows
- arch: amd64
-```
-
-**Step 4: Create `CLAUDE.md`**
-
-```markdown
-# CLAUDE.md
-
-## Project Overview
-
-`core/lint` is a standalone pattern catalog and regex-based code checker. It loads YAML rule definitions and matches them against source files. Zero framework dependencies.
-
-## Build & Development
-
-```bash
-core go test
-core go qa
-core build # produces ./bin/core-lint
-```
-
-## Architecture
-
-- `catalog/` — YAML rule files (embedded at compile time)
-- `pkg/lint/` — Library: Rule, Catalog, Matcher, Report types
-- `cmd/core-lint/` — CLI binary using `cli.Main()`
-
-## Rule Schema
-
-Each YAML file contains an array of rules with: id, title, severity, languages, tags, pattern (regex), exclude_pattern, fix, example_bad, example_good, detection type.
-
-## Coding Standards
-
-- UK English
-- `declare(strict_types=1)` equivalent: all functions have typed params/returns
-- Tests use testify
-- License: EUPL-1.2
-```
-
-**Step 5: Add to go.work**
-
-Add `./core/lint` to `~/Code/go.work` under the Core framework section.
-
-**Step 6: Commit**
-
-```bash
-git add go.mod .core/ CLAUDE.md
-git commit -m "feat: initialise core/lint module"
-```
-
----
-
-### Task 2: Rule struct and YAML parsing
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/rule.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/rule_test.go`
-
-**Step 1: Write the failing test**
-
-```go
-package lint
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestParseRules(t *testing.T) {
- yaml := `
-- id: test-001
- title: "Test rule"
- severity: high
- languages: [go]
- tags: [security]
- pattern: 'fmt\.Println'
- fix: "Use structured logging"
- detection: regex
-`
- rules, err := ParseRules([]byte(yaml))
- require.NoError(t, err)
- require.Len(t, rules, 1)
- assert.Equal(t, "test-001", rules[0].ID)
- assert.Equal(t, "high", rules[0].Severity)
- assert.Equal(t, []string{"go"}, rules[0].Languages)
- assert.Equal(t, `fmt\.Println`, rules[0].Pattern)
-}
-
-func TestParseRules_Invalid(t *testing.T) {
- _, err := ParseRules([]byte("not: valid: yaml: ["))
- assert.Error(t, err)
-}
-
-func TestRule_Validate(t *testing.T) {
- good := Rule{ID: "x-001", Title: "T", Severity: "high", Languages: []string{"go"}, Pattern: "foo", Detection: "regex"}
- assert.NoError(t, good.Validate())
-
- bad := Rule{} // missing required fields
- assert.Error(t, bad.Validate())
-}
-
-func TestRule_Validate_BadRegex(t *testing.T) {
- r := Rule{ID: "x-001", Title: "T", Severity: "high", Languages: []string{"go"}, Pattern: "[invalid", Detection: "regex"}
- assert.Error(t, r.Validate())
-}
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v`
-Expected: FAIL — `ParseRules` and `Rule` not defined
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "fmt"
- "regexp"
-
- "gopkg.in/yaml.v3"
-)
-
-// Rule defines a single lint pattern check.
-type Rule struct {
- ID string `yaml:"id" json:"id"`
- Title string `yaml:"title" json:"title"`
- Severity string `yaml:"severity" json:"severity"`
- Languages []string `yaml:"languages" json:"languages"`
- Tags []string `yaml:"tags" json:"tags"`
- Pattern string `yaml:"pattern" json:"pattern"`
- ExcludePattern string `yaml:"exclude_pattern" json:"exclude_pattern,omitempty"`
- Fix string `yaml:"fix" json:"fix"`
- FoundIn []string `yaml:"found_in" json:"found_in,omitempty"`
- ExampleBad string `yaml:"example_bad" json:"example_bad,omitempty"`
- ExampleGood string `yaml:"example_good" json:"example_good,omitempty"`
- FirstSeen string `yaml:"first_seen" json:"first_seen,omitempty"`
- Detection string `yaml:"detection" json:"detection"`
- AutoFixable bool `yaml:"auto_fixable" json:"auto_fixable"`
-}
-
-// Validate checks that a Rule has all required fields and a compilable regex pattern.
-func (r *Rule) Validate() error {
- if r.ID == "" {
- return fmt.Errorf("rule missing id")
- }
- if r.Title == "" {
- return fmt.Errorf("rule %s: missing title", r.ID)
- }
- if r.Severity == "" {
- return fmt.Errorf("rule %s: missing severity", r.ID)
- }
- if len(r.Languages) == 0 {
- return fmt.Errorf("rule %s: missing languages", r.ID)
- }
- if r.Pattern == "" {
- return fmt.Errorf("rule %s: missing pattern", r.ID)
- }
- if r.Detection == "regex" {
- if _, err := regexp.Compile(r.Pattern); err != nil {
- return fmt.Errorf("rule %s: invalid regex: %w", r.ID, err)
- }
- }
- return nil
-}
-
-// ParseRules parses YAML bytes into a slice of Rules.
-func ParseRules(data []byte) ([]Rule, error) {
- var rules []Rule
- if err := yaml.Unmarshal(data, &rules); err != nil {
- return nil, fmt.Errorf("parse rules: %w", err)
- }
- return rules, nil
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v`
-Expected: PASS (4 tests)
-
-**Step 5: Add yaml dependency**
-
-```bash
-cd ~/Code/core/lint && go get gopkg.in/yaml.v3 && go get github.com/stretchr/testify
-```
-
-**Step 6: Commit**
-
-```bash
-git add pkg/lint/rule.go pkg/lint/rule_test.go go.mod go.sum
-git commit -m "feat: add Rule struct with YAML parsing and validation"
-```
-
----
-
-### Task 3: Catalog loader with embed support
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/catalog.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/catalog_test.go`
-- Create: `/Users/snider/Code/core/lint/catalog/go-security.yaml` (minimal test file)
-
-**Step 1: Create a minimal test catalog file**
-
-Create `/Users/snider/Code/core/lint/catalog/go-security.yaml`:
-```yaml
-- id: go-sec-001
- title: "SQL wildcard injection in LIKE clauses"
- severity: high
- languages: [go]
- tags: [security, injection]
- pattern: 'LIKE\s+\?\s*,\s*["%].*\+'
- fix: "Use parameterised LIKE with EscapeLike()"
- found_in: [go-store]
- first_seen: "2026-03-09"
- detection: regex
-```
-
-**Step 2: Write the failing test**
-
-```go
-package lint
-
-import (
- "embed"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestCatalog_LoadDir(t *testing.T) {
- // Find the catalog/ dir relative to the module root
- dir := filepath.Join("..", "..", "catalog")
- cat, err := LoadDir(dir)
- require.NoError(t, err)
- assert.Greater(t, len(cat.Rules), 0)
- assert.Equal(t, "go-sec-001", cat.Rules[0].ID)
-}
-
-func TestCatalog_LoadDir_NotExist(t *testing.T) {
- _, err := LoadDir("/nonexistent")
- assert.Error(t, err)
-}
-
-func TestCatalog_Filter_Language(t *testing.T) {
- cat := &Catalog{Rules: []Rule{
- {ID: "go-001", Languages: []string{"go"}, Severity: "high"},
- {ID: "php-001", Languages: []string{"php"}, Severity: "high"},
- }}
- filtered := cat.ForLanguage("go")
- assert.Len(t, filtered, 1)
- assert.Equal(t, "go-001", filtered[0].ID)
-}
-
-func TestCatalog_Filter_Severity(t *testing.T) {
- cat := &Catalog{Rules: []Rule{
- {ID: "a", Severity: "high"},
- {ID: "b", Severity: "low"},
- {ID: "c", Severity: "medium"},
- }}
- filtered := cat.AtSeverity("medium")
- assert.Len(t, filtered, 2) // high + medium
-}
-
-func TestCatalog_LoadFS(t *testing.T) {
- // Write temp yaml
- dir := t.TempDir()
- data := []byte(`- id: fs-001
- title: "FS test"
- severity: low
- languages: [go]
- tags: []
- pattern: 'test'
- fix: "fix"
- detection: regex
-`)
- require.NoError(t, os.WriteFile(filepath.Join(dir, "test.yaml"), data, 0644))
-
- cat, err := LoadDir(dir)
- require.NoError(t, err)
- assert.Len(t, cat.Rules, 1)
-}
-```
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "embed"
- "fmt"
- "io/fs"
- "os"
- "path/filepath"
- "slices"
- "strings"
-)
-
-// Catalog holds a collection of lint rules loaded from YAML files.
-type Catalog struct {
- Rules []Rule
-}
-
-// severityOrder maps severity names to numeric priority (higher = more severe).
-var severityOrder = map[string]int{
- "critical": 5,
- "high": 4,
- "medium": 3,
- "low": 2,
- "info": 1,
-}
-
-// LoadDir loads all .yaml files from a directory path into a Catalog.
-func LoadDir(dir string) (*Catalog, error) {
- entries, err := os.ReadDir(dir)
- if err != nil {
- return nil, fmt.Errorf("load catalog dir: %w", err)
- }
-
- cat := &Catalog{}
- for _, entry := range entries {
- if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yaml") {
- continue
- }
- data, err := os.ReadFile(filepath.Join(dir, entry.Name()))
- if err != nil {
- return nil, fmt.Errorf("read %s: %w", entry.Name(), err)
- }
- rules, err := ParseRules(data)
- if err != nil {
- return nil, fmt.Errorf("parse %s: %w", entry.Name(), err)
- }
- cat.Rules = append(cat.Rules, rules...)
- }
- return cat, nil
-}
-
-// LoadFS loads all .yaml files from an embed.FS into a Catalog.
-func LoadFS(fsys embed.FS, dir string) (*Catalog, error) {
- cat := &Catalog{}
- err := fs.WalkDir(fsys, dir, func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
- if d.IsDir() || !strings.HasSuffix(path, ".yaml") {
- return nil
- }
- data, err := fsys.ReadFile(path)
- if err != nil {
- return fmt.Errorf("read %s: %w", path, err)
- }
- rules, err := ParseRules(data)
- if err != nil {
- return fmt.Errorf("parse %s: %w", path, err)
- }
- cat.Rules = append(cat.Rules, rules...)
- return nil
- })
- if err != nil {
- return nil, err
- }
- return cat, nil
-}
-
-// ForLanguage returns rules that apply to the given language.
-func (c *Catalog) ForLanguage(lang string) []Rule {
- var out []Rule
- for _, r := range c.Rules {
- if slices.Contains(r.Languages, lang) {
- out = append(out, r)
- }
- }
- return out
-}
-
-// AtSeverity returns rules at or above the given severity threshold.
-func (c *Catalog) AtSeverity(threshold string) []Rule {
- minLevel := severityOrder[threshold]
- var out []Rule
- for _, r := range c.Rules {
- if severityOrder[r.Severity] >= minLevel {
- out = append(out, r)
- }
- }
- return out
-}
-
-// ByID returns a rule by its ID, or nil if not found.
-func (c *Catalog) ByID(id string) *Rule {
- for i := range c.Rules {
- if c.Rules[i].ID == id {
- return &c.Rules[i]
- }
- }
- return nil
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v`
-Expected: PASS (all tests)
-
-**Step 5: Commit**
-
-```bash
-git add pkg/lint/catalog.go pkg/lint/catalog_test.go catalog/go-security.yaml
-git commit -m "feat: add Catalog loader with dir/embed/filter support"
-```
-
----
-
-### Task 4: Regex matcher
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/matcher.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/matcher_test.go`
-
-**Step 1: Write the failing test**
-
-```go
-package lint
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestMatcher_Match(t *testing.T) {
- rules := []Rule{
- {
- ID: "test-001",
- Title: "fmt.Println usage",
- Severity: "low",
- Languages: []string{"go"},
- Pattern: `fmt\.Println`,
- Fix: "Use structured logging",
- Detection: "regex",
- },
- }
- m, err := NewMatcher(rules)
- require.NoError(t, err)
-
- content := `package main
-
-import "fmt"
-
-func main() {
- fmt.Println("hello")
-}
-`
- findings := m.Match("main.go", []byte(content))
- require.Len(t, findings, 1)
- assert.Equal(t, "test-001", findings[0].RuleID)
- assert.Equal(t, "main.go", findings[0].File)
- assert.Equal(t, 6, findings[0].Line)
- assert.Contains(t, findings[0].Match, "fmt.Println")
-}
-
-func TestMatcher_ExcludePattern(t *testing.T) {
- rules := []Rule{
- {
- ID: "test-002",
- Title: "Println with exclude",
- Severity: "low",
- Languages: []string{"go"},
- Pattern: `fmt\.Println`,
- ExcludePattern: `// lint:ignore`,
- Fix: "Use logging",
- Detection: "regex",
- },
- }
- m, err := NewMatcher(rules)
- require.NoError(t, err)
-
- content := `package main
-func a() { fmt.Println("bad") }
-func b() { fmt.Println("ok") // lint:ignore }
-`
- findings := m.Match("main.go", []byte(content))
- // Line 2 matches, line 3 is excluded
- assert.Len(t, findings, 1)
- assert.Equal(t, 2, findings[0].Line)
-}
-
-func TestMatcher_NoMatch(t *testing.T) {
- rules := []Rule{
- {ID: "test-003", Title: "T", Severity: "low", Languages: []string{"go"}, Pattern: `NEVER_MATCH_THIS`, Detection: "regex"},
- }
- m, err := NewMatcher(rules)
- require.NoError(t, err)
-
- findings := m.Match("main.go", []byte("package main\n"))
- assert.Empty(t, findings)
-}
-
-func TestMatcher_InvalidRegex(t *testing.T) {
- rules := []Rule{
- {ID: "bad", Title: "T", Severity: "low", Languages: []string{"go"}, Pattern: `[invalid`, Detection: "regex"},
- }
- _, err := NewMatcher(rules)
- assert.Error(t, err)
-}
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestMatcher`
-Expected: FAIL — `NewMatcher` not defined
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "fmt"
- "regexp"
- "strings"
-)
-
-// Finding represents a single match of a rule against source code.
-type Finding struct {
- RuleID string `json:"rule_id"`
- Title string `json:"title"`
- Severity string `json:"severity"`
- File string `json:"file"`
- Line int `json:"line"`
- Match string `json:"match"`
- Fix string `json:"fix"`
- Repo string `json:"repo,omitempty"`
-}
-
-// compiledRule is a rule with its regex pre-compiled.
-type compiledRule struct {
- rule Rule
- pattern *regexp.Regexp
- exclude *regexp.Regexp
-}
-
-// Matcher runs compiled rules against file contents.
-type Matcher struct {
- rules []compiledRule
-}
-
-// NewMatcher compiles all rule patterns and returns a Matcher.
-func NewMatcher(rules []Rule) (*Matcher, error) {
- compiled := make([]compiledRule, 0, len(rules))
- for _, r := range rules {
- if r.Detection != "regex" {
- continue // skip non-regex rules
- }
- p, err := regexp.Compile(r.Pattern)
- if err != nil {
- return nil, fmt.Errorf("rule %s: invalid pattern: %w", r.ID, err)
- }
- cr := compiledRule{rule: r, pattern: p}
- if r.ExcludePattern != "" {
- ex, err := regexp.Compile(r.ExcludePattern)
- if err != nil {
- return nil, fmt.Errorf("rule %s: invalid exclude_pattern: %w", r.ID, err)
- }
- cr.exclude = ex
- }
- compiled = append(compiled, cr)
- }
- return &Matcher{rules: compiled}, nil
-}
-
-// Match checks file contents against all rules and returns findings.
-func (m *Matcher) Match(filename string, content []byte) []Finding {
- lines := strings.Split(string(content), "\n")
- var findings []Finding
-
- for _, cr := range m.rules {
- for i, line := range lines {
- if !cr.pattern.MatchString(line) {
- continue
- }
- if cr.exclude != nil && cr.exclude.MatchString(line) {
- continue
- }
- findings = append(findings, Finding{
- RuleID: cr.rule.ID,
- Title: cr.rule.Title,
- Severity: cr.rule.Severity,
- File: filename,
- Line: i + 1,
- Match: strings.TrimSpace(line),
- Fix: cr.rule.Fix,
- })
- }
- }
- return findings
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestMatcher`
-Expected: PASS (4 tests)
-
-**Step 5: Commit**
-
-```bash
-git add pkg/lint/matcher.go pkg/lint/matcher_test.go
-git commit -m "feat: add regex Matcher with exclude pattern support"
-```
-
----
-
-### Task 5: Report output (JSON, text, JSONL)
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/report.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/report_test.go`
-
-**Step 1: Write the failing test**
-
-```go
-package lint
-
-import (
- "bytes"
- "encoding/json"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestReport_JSON(t *testing.T) {
- findings := []Finding{
- {RuleID: "x-001", Title: "Test", Severity: "high", File: "a.go", Line: 10, Match: "bad code", Fix: "fix it"},
- }
- var buf bytes.Buffer
- require.NoError(t, WriteJSON(&buf, findings))
-
- var parsed []Finding
- require.NoError(t, json.Unmarshal(buf.Bytes(), &parsed))
- assert.Len(t, parsed, 1)
- assert.Equal(t, "x-001", parsed[0].RuleID)
-}
-
-func TestReport_JSONL(t *testing.T) {
- findings := []Finding{
- {RuleID: "a-001", File: "a.go", Line: 1},
- {RuleID: "b-001", File: "b.go", Line: 2},
- }
- var buf bytes.Buffer
- require.NoError(t, WriteJSONL(&buf, findings))
-
- lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
- assert.Len(t, lines, 2)
-}
-
-func TestReport_Text(t *testing.T) {
- findings := []Finding{
- {RuleID: "x-001", Title: "Test rule", Severity: "high", File: "main.go", Line: 42, Match: "bad()", Fix: "use good()"},
- }
- var buf bytes.Buffer
- WriteText(&buf, findings)
-
- out := buf.String()
- assert.Contains(t, out, "main.go:42")
- assert.Contains(t, out, "x-001")
- assert.Contains(t, out, "high")
-}
-
-func TestReport_Summary(t *testing.T) {
- findings := []Finding{
- {Severity: "high"},
- {Severity: "high"},
- {Severity: "low"},
- }
- s := Summarise(findings)
- assert.Equal(t, 3, s.Total)
- assert.Equal(t, 2, s.BySeverity["high"])
- assert.Equal(t, 1, s.BySeverity["low"])
-}
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestReport`
-Expected: FAIL — functions not defined
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "encoding/json"
- "fmt"
- "io"
-)
-
-// Summary holds aggregate stats about findings.
-type Summary struct {
- Total int `json:"total"`
- BySeverity map[string]int `json:"by_severity"`
-}
-
-// Summarise creates a Summary from a list of findings.
-func Summarise(findings []Finding) Summary {
- s := Summary{
- Total: len(findings),
- BySeverity: make(map[string]int),
- }
- for _, f := range findings {
- s.BySeverity[f.Severity]++
- }
- return s
-}
-
-// WriteJSON writes findings as a JSON array.
-func WriteJSON(w io.Writer, findings []Finding) error {
- enc := json.NewEncoder(w)
- enc.SetIndent("", " ")
- return enc.Encode(findings)
-}
-
-// WriteJSONL writes findings as newline-delimited JSON (one object per line).
-// Compatible with ~/.core/ai/metrics/ format.
-func WriteJSONL(w io.Writer, findings []Finding) error {
- enc := json.NewEncoder(w)
- for _, f := range findings {
- if err := enc.Encode(f); err != nil {
- return err
- }
- }
- return nil
-}
-
-// WriteText writes findings as human-readable text.
-func WriteText(w io.Writer, findings []Finding) {
- for _, f := range findings {
- fmt.Fprintf(w, "%s:%d [%s] %s (%s)\n", f.File, f.Line, f.Severity, f.Title, f.RuleID)
- if f.Fix != "" {
- fmt.Fprintf(w, " fix: %s\n", f.Fix)
- }
- }
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestReport`
-Expected: PASS (4 tests)
-
-**Step 5: Commit**
-
-```bash
-git add pkg/lint/report.go pkg/lint/report_test.go
-git commit -m "feat: add report output (JSON, JSONL, text, summary)"
-```
-
----
-
-### Task 6: Scanner (walk files + match)
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/pkg/lint/scanner.go`
-- Create: `/Users/snider/Code/core/lint/pkg/lint/scanner_test.go`
-
-**Step 1: Write the failing test**
-
-```go
-package lint
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestScanner_ScanDir(t *testing.T) {
- // Set up temp dir with a .go file containing a known pattern
- dir := t.TempDir()
- goFile := filepath.Join(dir, "main.go")
- require.NoError(t, os.WriteFile(goFile, []byte(`package main
-
-import "fmt"
-
-func main() {
- fmt.Println("hello")
-}
-`), 0644))
-
- rules := []Rule{
- {ID: "test-001", Title: "Println", Severity: "low", Languages: []string{"go"}, Pattern: `fmt\.Println`, Fix: "log", Detection: "regex"},
- }
-
- s, err := NewScanner(rules)
- require.NoError(t, err)
-
- findings, err := s.ScanDir(dir)
- require.NoError(t, err)
- require.Len(t, findings, 1)
- assert.Equal(t, "test-001", findings[0].RuleID)
-}
-
-func TestScanner_ScanDir_ExcludesVendor(t *testing.T) {
- dir := t.TempDir()
- vendor := filepath.Join(dir, "vendor")
- require.NoError(t, os.MkdirAll(vendor, 0755))
- require.NoError(t, os.WriteFile(filepath.Join(vendor, "lib.go"), []byte("package lib\nfunc x() { fmt.Println() }\n"), 0644))
-
- rules := []Rule{
- {ID: "test-001", Title: "Println", Severity: "low", Languages: []string{"go"}, Pattern: `fmt\.Println`, Fix: "log", Detection: "regex"},
- }
-
- s, err := NewScanner(rules)
- require.NoError(t, err)
-
- findings, err := s.ScanDir(dir)
- require.NoError(t, err)
- assert.Empty(t, findings)
-}
-
-func TestScanner_LanguageDetection(t *testing.T) {
- assert.Equal(t, "go", DetectLanguage("main.go"))
- assert.Equal(t, "php", DetectLanguage("app.php"))
- assert.Equal(t, "ts", DetectLanguage("index.ts"))
- assert.Equal(t, "ts", DetectLanguage("index.tsx"))
- assert.Equal(t, "cpp", DetectLanguage("engine.cpp"))
- assert.Equal(t, "cpp", DetectLanguage("engine.cc"))
- assert.Equal(t, "", DetectLanguage("README.md"))
-}
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestScanner`
-Expected: FAIL — `NewScanner` not defined
-
-**Step 3: Write minimal implementation**
-
-```go
-package lint
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-)
-
-// defaultExcludes are directories skipped during scanning.
-var defaultExcludes = []string{"vendor", "node_modules", ".git", "testdata", ".core"}
-
-// extToLang maps file extensions to language identifiers.
-var extToLang = map[string]string{
- ".go": "go",
- ".php": "php",
- ".ts": "ts",
- ".tsx": "ts",
- ".js": "js",
- ".jsx": "js",
- ".cpp": "cpp",
- ".cc": "cpp",
- ".cxx": "cpp",
- ".c": "cpp",
- ".h": "cpp",
- ".hpp": "cpp",
-}
-
-// DetectLanguage returns the language identifier for a filename, or "" if unknown.
-func DetectLanguage(filename string) string {
- ext := filepath.Ext(filename)
- return extToLang[ext]
-}
-
-// Scanner walks directories and matches files against rules.
-type Scanner struct {
- matcher *Matcher
- rules []Rule
- excludes []string
-}
-
-// NewScanner creates a Scanner from a set of rules.
-func NewScanner(rules []Rule) (*Scanner, error) {
- m, err := NewMatcher(rules)
- if err != nil {
- return nil, err
- }
- return &Scanner{
- matcher: m,
- rules: rules,
- excludes: defaultExcludes,
- }, nil
-}
-
-// ScanDir walks a directory tree and returns all findings.
-func (s *Scanner) ScanDir(root string) ([]Finding, error) {
- var all []Finding
-
- err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
- if err != nil {
- return err
- }
-
- // Skip excluded directories
- if d.IsDir() {
- for _, ex := range s.excludes {
- if d.Name() == ex {
- return filepath.SkipDir
- }
- }
- return nil
- }
-
- // Only scan files with known language extensions
- lang := DetectLanguage(path)
- if lang == "" {
- return nil
- }
-
- content, err := os.ReadFile(path)
- if err != nil {
- return fmt.Errorf("read %s: %w", path, err)
- }
-
- // Make path relative to root for cleaner output
- rel, err := filepath.Rel(root, path)
- if err != nil {
- rel = path
- }
-
- findings := s.matcher.Match(rel, content)
- all = append(all, findings...)
- return nil
- })
-
- return all, err
-}
-
-// ScanFile scans a single file and returns findings.
-func (s *Scanner) ScanFile(path string) ([]Finding, error) {
- content, err := os.ReadFile(path)
- if err != nil {
- return nil, fmt.Errorf("read %s: %w", path, err)
- }
- return s.matcher.Match(path, content), nil
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v -run TestScanner`
-Expected: PASS (3 tests)
-
-**Step 5: Commit**
-
-```bash
-git add pkg/lint/scanner.go pkg/lint/scanner_test.go
-git commit -m "feat: add Scanner with directory walking and language detection"
-```
-
----
-
-### Task 7: Seed the catalog YAML files
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/catalog/go-security.yaml` (expand from task 3)
-- Create: `/Users/snider/Code/core/lint/catalog/go-correctness.yaml`
-- Create: `/Users/snider/Code/core/lint/catalog/go-modernise.yaml`
-
-**Step 1: Write `catalog/go-security.yaml`**
-
-```yaml
-- id: go-sec-001
- title: "SQL wildcard injection in LIKE clauses"
- severity: high
- languages: [go]
- tags: [security, injection, owasp-a03]
- pattern: 'LIKE\s+\?.*["%`]\s*\%.*\+'
- exclude_pattern: 'EscapeLike'
- fix: "Use parameterised LIKE with explicit escaping of % and _ characters"
- found_in: [go-store]
- example_bad: |
- db.Where("name LIKE ?", "%"+input+"%")
- example_good: |
- db.Where("name LIKE ?", EscapeLike(input))
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-002
- title: "Path traversal in file/cache key operations"
- severity: high
- languages: [go]
- tags: [security, path-traversal, owasp-a01]
- pattern: 'filepath\.Join\(.*,\s*\w+\)'
- exclude_pattern: 'filepath\.Clean|securejoin|ValidatePath'
- fix: "Validate path components do not contain .. before joining"
- found_in: [go-cache]
- example_bad: |
- path := filepath.Join(cacheDir, userInput)
- example_good: |
- if strings.Contains(key, "..") { return ErrInvalidKey }
- path := filepath.Join(cacheDir, key)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-003
- title: "XSS via unescaped HTML output"
- severity: high
- languages: [go]
- tags: [security, xss, owasp-a03]
- pattern: 'fmt\.Sprintf\(.*<.*>.*%s'
- exclude_pattern: 'html\.EscapeString|template\.HTMLEscapeString'
- fix: "Use html.EscapeString() for user-supplied values in HTML output"
- found_in: [go-html]
- example_bad: |
- out := fmt.Sprintf("%s
", userInput)
- example_good: |
- out := fmt.Sprintf("%s
", html.EscapeString(userInput))
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-004
- title: "Non-constant-time comparison for authentication"
- severity: high
- languages: [go]
- tags: [security, timing-attack, owasp-a02]
- pattern: '==\s*\w*(token|key|secret|password|hash|digest|hmac|mac|sig)'
- exclude_pattern: 'subtle\.ConstantTimeCompare|hmac\.Equal'
- fix: "Use crypto/subtle.ConstantTimeCompare for security-sensitive comparisons"
- found_in: [go-crypt]
- example_bad: |
- if providedToken == storedToken {
- example_good: |
- if subtle.ConstantTimeCompare([]byte(provided), []byte(stored)) == 1 {
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-005
- title: "Log injection via unescaped newlines"
- severity: medium
- languages: [go]
- tags: [security, injection, logging]
- pattern: 'log\.\w+\(.*\+.*\)'
- exclude_pattern: 'strings\.ReplaceAll.*\\n|slog\.'
- fix: "Use structured logging (slog) or sanitise newlines from user input"
- found_in: [go-log]
- example_bad: |
- log.Printf("user login: " + username)
- example_good: |
- slog.Info("user login", "username", username)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-sec-006
- title: "Sensitive key material in log output"
- severity: high
- languages: [go]
- tags: [security, secrets, logging]
- pattern: 'log\.\w+\(.*(?i)(password|secret|token|apikey|private.?key|credential)'
- exclude_pattern: 'REDACTED|\*\*\*|redact'
- fix: "Redact sensitive fields before logging"
- found_in: [go-log]
- example_bad: |
- log.Printf("config: token=%s", cfg.Token)
- example_good: |
- log.Printf("config: token=%s", redact(cfg.Token))
- first_seen: "2026-03-09"
- detection: regex
-```
-
-**Step 2: Write `catalog/go-correctness.yaml`**
-
-```yaml
-- id: go-cor-001
- title: "Goroutine without WaitGroup or context"
- severity: high
- languages: [go]
- tags: [correctness, goroutine-leak]
- pattern: 'go\s+func\s*\('
- exclude_pattern: 'wg\.|\.Go\(|context\.|done\s*<-|select\s*\{'
- fix: "Use sync.WaitGroup.Go() or ensure goroutine has a shutdown signal"
- found_in: [core/go]
- example_bad: |
- go func() { doWork() }()
- example_good: |
- wg.Go(func() { doWork() })
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-002
- title: "WaitGroup.Wait without context/timeout"
- severity: high
- languages: [go]
- tags: [correctness, deadlock]
- pattern: '\.Wait\(\)'
- exclude_pattern: 'select\s*\{|ctx\.Done|context\.With|time\.After'
- fix: "Wrap wg.Wait() in a select with context.Done() or timeout"
- found_in: [core/go]
- example_bad: |
- wg.Wait() // blocks forever if goroutine hangs
- example_good: |
- done := make(chan struct{})
- go func() { wg.Wait(); close(done) }()
- select {
- case <-done:
- case <-ctx.Done():
- }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-003
- title: "Silent error swallowing"
- severity: medium
- languages: [go]
- tags: [correctness, error-handling]
- pattern: '^\s*_\s*=\s*\w+\.\w+\('
- exclude_pattern: 'defer|Close\(|Flush\('
- fix: "Handle or propagate errors instead of discarding with _"
- found_in: [go-process, go-ratelimit]
- example_bad: |
- _ = db.Save(record)
- example_good: |
- if err := db.Save(record); err != nil {
- return fmt.Errorf("save record: %w", err)
- }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-004
- title: "Panic in library code"
- severity: medium
- languages: [go]
- tags: [correctness, panic]
- pattern: '\bpanic\('
- exclude_pattern: '_test\.go|// unreachable|Must\w+\('
- fix: "Return errors instead of panicking in library code"
- found_in: [go-i18n]
- example_bad: |
- func Parse(s string) *Node { panic("not implemented") }
- example_good: |
- func Parse(s string) (*Node, error) { return nil, fmt.Errorf("not implemented") }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-005
- title: "File deletion without path validation"
- severity: high
- languages: [go]
- tags: [correctness, safety]
- pattern: 'os\.Remove(All)?\('
- exclude_pattern: 'filepath\.Clean|ValidatePath|strings\.Contains.*\.\.'
- fix: "Validate path does not escape base directory before deletion"
- found_in: [go-io]
- example_bad: |
- os.RemoveAll(filepath.Join(base, userInput))
- example_good: |
- clean := filepath.Clean(filepath.Join(base, userInput))
- if !strings.HasPrefix(clean, base) { return ErrPathTraversal }
- os.RemoveAll(clean)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-006
- title: "Missing error return from API/network calls"
- severity: medium
- languages: [go]
- tags: [correctness, error-handling]
- pattern: 'resp,\s*_\s*:=.*\.(Get|Post|Do|Send)\('
- fix: "Check and handle HTTP/API errors"
- found_in: [go-forge, go-git]
- example_bad: |
- resp, _ := client.Get(url)
- example_good: |
- resp, err := client.Get(url)
- if err != nil { return fmt.Errorf("api call: %w", err) }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-cor-007
- title: "Signal handler uses wrong type"
- severity: medium
- languages: [go]
- tags: [correctness, signals]
- pattern: 'syscall\.Signal\b'
- exclude_pattern: 'os\.Signal'
- fix: "Use os.Signal for portable signal handling"
- found_in: [go-process]
- example_bad: |
- func Handle(sig syscall.Signal) { ... }
- example_good: |
- func Handle(sig os.Signal) { ... }
- first_seen: "2026-03-09"
- detection: regex
-```
-
-**Step 3: Write `catalog/go-modernise.yaml`**
-
-```yaml
-- id: go-mod-001
- title: "Manual slice clone via append([]T(nil)...)"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'append\(\[\]\w+\(nil\),\s*\w+\.\.\.\)'
- fix: "Use slices.Clone() from Go 1.21+"
- found_in: [core/go]
- example_bad: |
- copy := append([]string(nil), original...)
- example_good: |
- copy := slices.Clone(original)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-mod-002
- title: "Manual sort of string/int slices"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'sort\.Strings\(|sort\.Ints\(|sort\.Slice\('
- exclude_pattern: 'sort\.SliceStable'
- fix: "Use slices.Sort() or slices.Sorted(iter) from Go 1.21+"
- found_in: [core/go]
- example_bad: |
- sort.Strings(names)
- example_good: |
- slices.Sort(names)
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-mod-003
- title: "Manual reverse iteration loop"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'for\s+\w+\s*:=\s*len\(\w+\)\s*-\s*1'
- fix: "Use slices.Backward() from Go 1.23+"
- found_in: [core/go]
- example_bad: |
- for i := len(items) - 1; i >= 0; i-- { use(items[i]) }
- example_good: |
- for _, item := range slices.Backward(items) { use(item) }
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-mod-004
- title: "WaitGroup Add+Done instead of Go()"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'wg\.Add\(1\)'
- fix: "Use sync.WaitGroup.Go() from Go 1.26"
- found_in: [core/go]
- example_bad: |
- wg.Add(1)
- go func() { defer wg.Done(); work() }()
- example_good: |
- wg.Go(func() { work() })
- first_seen: "2026-03-09"
- detection: regex
-
-- id: go-mod-005
- title: "Manual map key collection"
- severity: low
- languages: [go]
- tags: [modernise, go126]
- pattern: 'for\s+\w+\s*:=\s*range\s+\w+\s*\{\s*\n\s*\w+\s*=\s*append'
- exclude_pattern: 'maps\.Keys'
- fix: "Use maps.Keys() or slices.Sorted(maps.Keys()) from Go 1.23+"
- found_in: [core/go]
- example_bad: |
- var keys []string
- for k := range m { keys = append(keys, k) }
- example_good: |
- keys := slices.Sorted(maps.Keys(m))
- first_seen: "2026-03-09"
- detection: regex
-```
-
-**Step 4: Run all tests to verify catalog loads correctly**
-
-Run: `cd ~/Code/core/lint && go test ./pkg/lint/ -v`
-Expected: PASS (all tests, including TestCatalog_LoadDir which reads the catalog/ dir)
-
-**Step 5: Commit**
-
-```bash
-git add catalog/
-git commit -m "feat: seed catalog with 18 patterns from ecosystem sweep"
-```
-
----
-
-### Task 8: CLI binary with `cli.Main()`
-
-**Files:**
-- Create: `/Users/snider/Code/core/lint/cmd/core-lint/main.go`
-- Create: `/Users/snider/Code/core/lint/lint.go` (embed catalog + public API)
-
-**Step 1: Create the embed entry point**
-
-Create `/Users/snider/Code/core/lint/lint.go`:
-
-```go
-package lint
-
-import (
- "embed"
-
- lintpkg "forge.lthn.ai/core/lint/pkg/lint"
-)
-
-//go:embed catalog/*.yaml
-var catalogFS embed.FS
-
-// LoadEmbeddedCatalog loads the built-in catalog from embedded YAML files.
-func LoadEmbeddedCatalog() (*lintpkg.Catalog, error) {
- return lintpkg.LoadFS(catalogFS, "catalog")
-}
-```
-
-**Step 2: Create the CLI entry point**
-
-Create `/Users/snider/Code/core/lint/cmd/core-lint/main.go`:
-
-```go
-package main
-
-import (
- "fmt"
- "os"
-
- "forge.lthn.ai/core/cli/pkg/cli"
- lint "forge.lthn.ai/core/lint"
- lintpkg "forge.lthn.ai/core/lint/pkg/lint"
-)
-
-func main() {
- cli.Main(
- cli.WithCommands("lint", addLintCommands),
- )
-}
-
-func addLintCommands(root *cli.Command) {
- lintCmd := &cli.Command{
- Use: "lint",
- Short: "Pattern-based code checker",
- }
- root.AddCommand(lintCmd)
-
- // core-lint lint check [path...]
- lintCmd.AddCommand(cli.NewCommand(
- "check [path...]",
- "Run pattern checks against source files",
- "Scans files for known anti-patterns from the catalog",
- func(cmd *cli.Command, args []string) error {
- format, _ := cmd.Flags().GetString("format")
- lang, _ := cmd.Flags().GetString("lang")
- severity, _ := cmd.Flags().GetString("severity")
-
- cat, err := lint.LoadEmbeddedCatalog()
- if err != nil {
- return fmt.Errorf("load catalog: %w", err)
- }
-
- rules := cat.Rules
- if lang != "" {
- rules = cat.ForLanguage(lang)
- }
- if severity != "" {
- filtered := (&lintpkg.Catalog{Rules: rules}).AtSeverity(severity)
- rules = filtered
- }
-
- scanner, err := lintpkg.NewScanner(rules)
- if err != nil {
- return fmt.Errorf("create scanner: %w", err)
- }
-
- paths := args
- if len(paths) == 0 {
- paths = []string{"."}
- }
-
- var allFindings []lintpkg.Finding
- for _, p := range paths {
- findings, err := scanner.ScanDir(p)
- if err != nil {
- return fmt.Errorf("scan %s: %w", p, err)
- }
- allFindings = append(allFindings, findings...)
- }
-
- switch format {
- case "json":
- return lintpkg.WriteJSON(os.Stdout, allFindings)
- case "jsonl":
- return lintpkg.WriteJSONL(os.Stdout, allFindings)
- default:
- lintpkg.WriteText(os.Stdout, allFindings)
- }
-
- if len(allFindings) > 0 {
- s := lintpkg.Summarise(allFindings)
- fmt.Fprintf(os.Stderr, "\n%d findings", s.Total)
- for sev, count := range s.BySeverity {
- fmt.Fprintf(os.Stderr, " | %s: %d", sev, count)
- }
- fmt.Fprintln(os.Stderr)
- }
- return nil
- },
- ))
-
- // Add flags to check command
- checkCmd := lintCmd.Commands()[0]
- checkCmd.Flags().StringP("format", "f", "text", "Output format: text, json, jsonl")
- checkCmd.Flags().StringP("lang", "l", "", "Filter by language: go, php, ts, cpp")
- checkCmd.Flags().StringP("severity", "s", "", "Minimum severity: critical, high, medium, low, info")
-
- // core-lint lint catalog
- catalogCmd := &cli.Command{
- Use: "catalog",
- Short: "Browse the pattern catalog",
- }
- lintCmd.AddCommand(catalogCmd)
-
- // core-lint lint catalog list
- catalogCmd.AddCommand(cli.NewCommand(
- "list",
- "List available rules",
- "",
- func(cmd *cli.Command, args []string) error {
- lang, _ := cmd.Flags().GetString("lang")
-
- cat, err := lint.LoadEmbeddedCatalog()
- if err != nil {
- return err
- }
-
- rules := cat.Rules
- if lang != "" {
- rules = cat.ForLanguage(lang)
- }
-
- for _, r := range rules {
- fmt.Printf("%-12s [%s] %s\n", r.ID, r.Severity, r.Title)
- }
- fmt.Fprintf(os.Stderr, "\n%d rules\n", len(rules))
- return nil
- },
- ))
- catalogCmd.Commands()[0].Flags().StringP("lang", "l", "", "Filter by language")
-
- // core-lint lint catalog show
- catalogCmd.AddCommand(cli.NewCommand(
- "show [rule-id]",
- "Show details for a specific rule",
- "",
- func(cmd *cli.Command, args []string) error {
- if len(args) == 0 {
- return fmt.Errorf("rule ID required")
- }
- cat, err := lint.LoadEmbeddedCatalog()
- if err != nil {
- return err
- }
- r := cat.ByID(args[0])
- if r == nil {
- return fmt.Errorf("rule %s not found", args[0])
- }
- fmt.Printf("ID: %s\n", r.ID)
- fmt.Printf("Title: %s\n", r.Title)
- fmt.Printf("Severity: %s\n", r.Severity)
- fmt.Printf("Languages: %v\n", r.Languages)
- fmt.Printf("Tags: %v\n", r.Tags)
- fmt.Printf("Pattern: %s\n", r.Pattern)
- if r.ExcludePattern != "" {
- fmt.Printf("Exclude: %s\n", r.ExcludePattern)
- }
- fmt.Printf("Fix: %s\n", r.Fix)
- if r.ExampleBad != "" {
- fmt.Printf("\nBad:\n%s\n", r.ExampleBad)
- }
- if r.ExampleGood != "" {
- fmt.Printf("Good:\n%s\n", r.ExampleGood)
- }
- return nil
- },
- ))
-}
-```
-
-**Step 3: Add cli dependency**
-
-```bash
-cd ~/Code/core/lint
-go get forge.lthn.ai/core/cli
-go mod tidy
-```
-
-**Step 4: Build and smoke test**
-
-```bash
-cd ~/Code/core/lint
-go build -o ./bin/core-lint ./cmd/core-lint
-./bin/core-lint lint catalog list
-./bin/core-lint lint catalog show go-sec-001
-./bin/core-lint lint check --lang go --format json ~/Code/host-uk/core/pkg/core/
-```
-
-Expected: Binary builds, catalog lists 18 rules, show displays rule details, check scans files.
-
-**Step 5: Commit**
-
-```bash
-git add lint.go cmd/core-lint/main.go go.mod go.sum
-git commit -m "feat: add core-lint CLI with check, catalog list, catalog show"
-```
-
----
-
-### Task 9: Run all tests, push to forge
-
-**Step 1: Run full test suite**
-
-```bash
-cd ~/Code/core/lint
-go test -race -count=1 ./...
-```
-
-Expected: PASS with race detector
-
-**Step 2: Run go vet**
-
-```bash
-go vet ./...
-```
-
-Expected: No issues
-
-**Step 3: Build binary**
-
-```bash
-go build -trimpath -o ./bin/core-lint ./cmd/core-lint
-```
-
-**Step 4: Smoke test against a real repo**
-
-```bash
-./bin/core-lint lint check --lang go ~/Code/host-uk/core/pkg/core/
-./bin/core-lint lint check --lang go --severity high ~/Code/core/go-io/
-```
-
-Expected: Any findings are displayed (or no findings if the repos are already clean from our sweep)
-
-**Step 5: Update go.work**
-
-```bash
-# Add ./core/lint to ~/Code/go.work if not already there
-cd ~/Code && go work sync
-```
-
-**Step 6: Push to forge**
-
-```bash
-cd ~/Code/core/lint
-git push -u origin main
-```
-
-**Step 7: Tag initial release**
-
-```bash
-git tag v0.1.0
-git push origin v0.1.0
-```
diff --git a/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-12-altum-update-checker-design.md b/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-12-altum-update-checker-design.md
deleted file mode 100644
index a0bbe0dc..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-12-altum-update-checker-design.md
+++ /dev/null
@@ -1,160 +0,0 @@
-# AltumCode Update Checker — Design
-
-> **Note:** Layer 1 (version detection via PHP artisan) is implemented and documented at `docs/docs/php/packages/uptelligence.md`. Layer 2 (browser-automated downloads via Claude Code skill) is NOT yet implemented.
-
-## Problem
-
-Host UK runs 4 AltumCode SaaS products and 13 plugins across two marketplaces (CodeCanyon + LemonSqueezy). Checking for updates and downloading them is a manual process: ~50 clicks across two marketplace UIs, moving 16+ zip files, extracting to the right directories. This eats a morning of momentum every update cycle.
-
-## Solution
-
-Two-layer system: lightweight version detection (PHP artisan command) + browser-automated download (Claude Code skill).
-
-## Architecture
-
-```
-Layer 1: Detection (core/php-uptelligence)
- artisan uptelligence:check-updates
- 5 HTTP GETs, no auth, schedulable
- Compares remote vs deployed versions
-
-Layer 2: Download (Claude Code skill)
- Playwright → LemonSqueezy (16 items)
- Claude in Chrome → CodeCanyon (2 items)
- Downloads zips to staging folder
- Extracts to saas/services/{product}/package/
-
-Layer 3: Deploy (existing — manual)
- docker build → scp → deploy_saas.yml
- Human in the loop
-```
-
-## Layer 1: Version Detection
-
-### Public Endpoints (no auth required)
-
-| Endpoint | Returns |
-|----------|---------|
-| `GET https://66analytics.com/info.php` | `{"latest_release_version": "66.0.0", "latest_release_version_code": 6600}` |
-| `GET https://66biolinks.com/info.php` | Same format |
-| `GET https://66pusher.com/info.php` | Same format |
-| `GET https://66socialproof.com/info.php` | Same format |
-| `GET https://dev.altumcode.com/plugins-versions` | `{"affiliate": {"version": "2.0.1"}, "ultimate-blocks": {"version": "9.1.0"}, ...}` |
-
-### Deployed Version Sources
-
-- **Product version**: `PRODUCT_CODE` constant in deployed source `config.php`
-- **Plugin versions**: `version` field in each plugin's `config.php` or `config.json`
-
-### Artisan Command
-
-`php artisan uptelligence:check-updates`
-
-Output:
-```
-Product Deployed Latest Status
-──────────────────────────────────────────────
-66analytics 65.0.0 66.0.0 UPDATE AVAILABLE
-66biolinks 65.0.0 66.0.0 UPDATE AVAILABLE
-66pusher 65.0.0 65.0.0 ✓ current
-66socialproof 65.0.0 66.0.0 UPDATE AVAILABLE
-
-Plugin Deployed Latest Status
-──────────────────────────────────────────────
-affiliate 2.0.0 2.0.1 UPDATE AVAILABLE
-ultimate-blocks 9.1.0 9.1.0 ✓ current
-...
-```
-
-Lives in `core/php-uptelligence` as a scheduled check or on-demand command.
-
-## Layer 2: Browser-Automated Download
-
-### Claude Code Skill: `/update-altum`
-
-Workflow:
-1. Run version check (Layer 1) — show what needs updating
-2. Ask for confirmation before downloading
-3. Download from both marketplaces
-4. Extract to staging directories
-5. Report what changed
-
-### Marketplace Access
-
-**LemonSqueezy (Playwright)**
-- Auth: Magic link email to `snider@lt.hn` — user taps on phone
-- Flow per item: Navigate to order detail → click "Download" button
-- 16 items across 2 pages of orders
-- Session persists for the skill invocation
-
-**CodeCanyon (Claude in Chrome)**
-- Auth: Saved browser session cookies (user `snidered`)
-- Flow per item: Click "Download" dropdown → "All files & documentation"
-- 2 items on downloads page
-
-### Product-to-Marketplace Mapping
-
-| Product | CodeCanyon | LemonSqueezy |
-|---------|-----------|--------------|
-| 66biolinks | Regular licence | Extended licence (66biolinks custom, $359.28) |
-| 66socialproof | Regular licence | — |
-| 66analytics | — | Regular licence |
-| 66pusher | — | Regular licence |
-
-### Plugin Inventory (all LemonSqueezy)
-
-| Plugin | Price | Applies To |
-|--------|-------|------------|
-| Pro Notifications | $58.80 | 66socialproof |
-| Teams Plugin | $58.80 | All products |
-| Push Notifications Plugin | $46.80 | All products |
-| Ultimate Blocks | $32.40 | 66biolinks |
-| Pro Blocks | $32.40 | 66biolinks |
-| Payment Blocks | $32.40 | 66biolinks |
-| Affiliate Plugin | $32.40 | All products |
-| PWA Plugin | $25.20 | All products |
-| Image Optimizer Plugin | $19.20 | All products |
-| Email Shield Plugin | FREE | All products |
-| Dynamic OG images plugin | FREE | 66biolinks |
-| Offload & CDN Plugin | FREE | All products (gift from Altum) |
-
-### Staging & Extraction
-
-- Download to: `~/Code/lthn/saas/updates/YYYY-MM-DD/`
-- Products extract to: `~/Code/lthn/saas/services/{product}/package/product/`
-- Plugins extract to: `~/Code/lthn/saas/services/{product}/package/product/plugins/{plugin_id}/`
-
-## LemonSqueezy Order UUIDs
-
-Stable order URLs for direct navigation:
-
-| Product | Order URL |
-|---------|-----------|
-| 66analytics | `/my-orders/2972471f-abac-4165-b78d-541b176de180` |
-
-(Remaining UUIDs to be captured on first full run of the skill.)
-
-## Out of Scope
-
-- No auto-deploy to production (human runs `deploy_saas.yml`)
-- No licence key handling or financial transactions
-- No AltumCode Club membership management
-- No Blesta updates (different vendor)
-- No update SQL migration execution (handled by AltumCode's own update scripts)
-
-## Key Technical Details
-
-- AltumCode products use Unirest HTTP client for API calls
-- Product `info.php` endpoints are public, no rate limiting observed
-- Plugin versions endpoint (`dev.altumcode.com`) is also public
-- Production Docker images have `/install/` and `/update/` directories stripped
-- Updates require full Docker image rebuild and redeployment via Ansible
-- CodeCanyon download URLs contain stable purchase UUIDs
-- LemonSqueezy uses magic link auth (no password, email-based)
-- Playwright can access LemonSqueezy; Claude in Chrome cannot (payment platform safety block)
-
-## Workflow Summary
-
-**Before**: Get email from AltumCode → log into 2 marketplaces → click through 18 products/plugins → download 16+ zips → extract to right directories → rebuild Docker images → deploy. Half a morning.
-
-**After**: Run `artisan uptelligence:check-updates` → see what's behind → invoke `/update-altum` → tap magic link on phone → go make coffee → come back to staged files → `deploy_saas.yml`. 10 minutes of human time.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-12-altum-update-checker-plan.md b/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-12-altum-update-checker-plan.md
deleted file mode 100644
index 37ecb28d..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/plans/2026-03-12-altum-update-checker-plan.md
+++ /dev/null
@@ -1,799 +0,0 @@
-# AltumCode Update Checker Implementation Plan
-
-> **Note:** Layer 1 (Tasks 1-2, 4: version checking + seeder + sync command) is implemented and documented at `docs/docs/php/packages/uptelligence.md`. Task 3 (Claude Code browser skill for Layer 2 downloads) is NOT yet implemented.
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Add AltumCode product + plugin version checking to uptelligence, and create a Claude Code skill for browser-automated downloads from LemonSqueezy and CodeCanyon.
-
-**Architecture:** Extend the existing `VendorUpdateCheckerService` to handle `PLATFORM_ALTUM` vendors via 5 public HTTP endpoints. Seed the vendors table with all 4 products and 13 plugins. Create a Claude Code plugin skill that uses Playwright (LemonSqueezy) and Chrome (CodeCanyon) to download updates.
-
-**Tech Stack:** PHP 8.4, Laravel, Pest, Claude Code plugins (Playwright MCP + Chrome MCP)
-
----
-
-### Task 1: Add AltumCode check to VendorUpdateCheckerService
-
-**Files:**
-- Modify: `/Users/snider/Code/core/php-uptelligence/Services/VendorUpdateCheckerService.php`
-- Test: `/Users/snider/Code/core/php-uptelligence/tests/Unit/AltumCodeCheckerTest.php`
-
-**Step 1: Write the failing test**
-
-Create `/Users/snider/Code/core/php-uptelligence/tests/Unit/AltumCodeCheckerTest.php`:
-
-```php
-service = app(VendorUpdateCheckerService::class);
-});
-
-it('checks altum product version via info.php', function () {
- Http::fake([
- 'https://66analytics.com/info.php' => Http::response([
- 'latest_release_version' => '66.0.0',
- 'latest_release_version_code' => 6600,
- ]),
- ]);
-
- $vendor = Vendor::factory()->create([
- 'slug' => '66analytics',
- 'name' => '66analytics',
- 'source_type' => Vendor::SOURCE_LICENSED,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'current_version' => '65.0.0',
- 'is_active' => true,
- ]);
-
- $result = $this->service->checkVendor($vendor);
-
- expect($result['status'])->toBe('success')
- ->and($result['current'])->toBe('65.0.0')
- ->and($result['latest'])->toBe('66.0.0')
- ->and($result['has_update'])->toBeTrue();
-});
-
-it('reports no update when altum product is current', function () {
- Http::fake([
- 'https://66analytics.com/info.php' => Http::response([
- 'latest_release_version' => '65.0.0',
- 'latest_release_version_code' => 6500,
- ]),
- ]);
-
- $vendor = Vendor::factory()->create([
- 'slug' => '66analytics',
- 'name' => '66analytics',
- 'source_type' => Vendor::SOURCE_LICENSED,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'current_version' => '65.0.0',
- 'is_active' => true,
- ]);
-
- $result = $this->service->checkVendor($vendor);
-
- expect($result['has_update'])->toBeFalse();
-});
-
-it('checks altum plugin versions via plugins-versions endpoint', function () {
- Http::fake([
- 'https://dev.altumcode.com/plugins-versions' => Http::response([
- 'affiliate' => ['version' => '2.0.1'],
- 'teams' => ['version' => '3.0.0'],
- ]),
- ]);
-
- $vendor = Vendor::factory()->create([
- 'slug' => 'altum-plugin-affiliate',
- 'name' => 'Affiliate Plugin',
- 'source_type' => Vendor::SOURCE_PLUGIN,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'current_version' => '2.0.0',
- 'is_active' => true,
- ]);
-
- $result = $this->service->checkVendor($vendor);
-
- expect($result['status'])->toBe('success')
- ->and($result['latest'])->toBe('2.0.1')
- ->and($result['has_update'])->toBeTrue();
-});
-
-it('handles altum info.php timeout gracefully', function () {
- Http::fake([
- 'https://66analytics.com/info.php' => Http::response('', 500),
- ]);
-
- $vendor = Vendor::factory()->create([
- 'slug' => '66analytics',
- 'name' => '66analytics',
- 'source_type' => Vendor::SOURCE_LICENSED,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'current_version' => '65.0.0',
- 'is_active' => true,
- ]);
-
- $result = $this->service->checkVendor($vendor);
-
- expect($result['status'])->toBe('error')
- ->and($result['has_update'])->toBeFalse();
-});
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=AltumCodeChecker`
-Expected: FAIL — altum vendors still hit `skipCheck()`
-
-**Step 3: Write minimal implementation**
-
-In `/Users/snider/Code/core/php-uptelligence/Services/VendorUpdateCheckerService.php`, modify `checkVendor()` to route altum vendors:
-
-```php
-public function checkVendor(Vendor $vendor): array
-{
- $result = match (true) {
- $this->isAltumPlatform($vendor) && $vendor->isLicensed() => $this->checkAltumProduct($vendor),
- $this->isAltumPlatform($vendor) && $vendor->isPlugin() => $this->checkAltumPlugin($vendor),
- $vendor->isOss() && $this->isGitHubUrl($vendor->git_repo_url) => $this->checkGitHub($vendor),
- $vendor->isOss() && $this->isGiteaUrl($vendor->git_repo_url) => $this->checkGitea($vendor),
- default => $this->skipCheck($vendor),
- };
-
- // ... rest unchanged
-}
-```
-
-Add the three new methods:
-
-```php
-/**
- * Check if vendor is on the AltumCode platform.
- */
-protected function isAltumPlatform(Vendor $vendor): bool
-{
- return $vendor->plugin_platform === Vendor::PLATFORM_ALTUM;
-}
-
-/**
- * AltumCode product info endpoint mapping.
- */
-protected function getAltumProductInfoUrl(Vendor $vendor): ?string
-{
- $urls = [
- '66analytics' => 'https://66analytics.com/info.php',
- '66biolinks' => 'https://66biolinks.com/info.php',
- '66pusher' => 'https://66pusher.com/info.php',
- '66socialproof' => 'https://66socialproof.com/info.php',
- ];
-
- return $urls[$vendor->slug] ?? null;
-}
-
-/**
- * Check an AltumCode product for updates via its info.php endpoint.
- */
-protected function checkAltumProduct(Vendor $vendor): array
-{
- $url = $this->getAltumProductInfoUrl($vendor);
- if (! $url) {
- return $this->errorResult("No info.php URL mapped for {$vendor->slug}");
- }
-
- try {
- $response = Http::timeout(5)->get($url);
-
- if (! $response->successful()) {
- return $this->errorResult("AltumCode info.php returned {$response->status()}");
- }
-
- $data = $response->json();
- $latestVersion = $data['latest_release_version'] ?? null;
-
- if (! $latestVersion) {
- return $this->errorResult('No version in info.php response');
- }
-
- return $this->buildResult(
- vendor: $vendor,
- latestVersion: $this->normaliseVersion($latestVersion),
- releaseInfo: [
- 'version_code' => $data['latest_release_version_code'] ?? null,
- 'source' => $url,
- ]
- );
- } catch (\Exception $e) {
- return $this->errorResult("AltumCode check failed: {$e->getMessage()}");
- }
-}
-
-/**
- * Check an AltumCode plugin for updates via the central plugins-versions endpoint.
- */
-protected function checkAltumPlugin(Vendor $vendor): array
-{
- try {
- $allPlugins = $this->getAltumPluginVersions();
-
- if ($allPlugins === null) {
- return $this->errorResult('Failed to fetch AltumCode plugin versions');
- }
-
- // Extract the plugin_id from the vendor slug (strip 'altum-plugin-' prefix)
- $pluginId = str_replace('altum-plugin-', '', $vendor->slug);
-
- if (! isset($allPlugins[$pluginId])) {
- return $this->errorResult("Plugin '{$pluginId}' not found in AltumCode registry");
- }
-
- $latestVersion = $allPlugins[$pluginId]['version'] ?? null;
-
- return $this->buildResult(
- vendor: $vendor,
- latestVersion: $this->normaliseVersion($latestVersion),
- releaseInfo: ['source' => 'dev.altumcode.com/plugins-versions']
- );
- } catch (\Exception $e) {
- return $this->errorResult("AltumCode plugin check failed: {$e->getMessage()}");
- }
-}
-
-/**
- * Fetch all AltumCode plugin versions (cached for 1 hour within a check run).
- */
-protected ?array $altumPluginVersionsCache = null;
-
-protected function getAltumPluginVersions(): ?array
-{
- if ($this->altumPluginVersionsCache !== null) {
- return $this->altumPluginVersionsCache;
- }
-
- $response = Http::timeout(5)->get('https://dev.altumcode.com/plugins-versions');
-
- if (! $response->successful()) {
- return null;
- }
-
- $this->altumPluginVersionsCache = $response->json();
-
- return $this->altumPluginVersionsCache;
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=AltumCodeChecker`
-Expected: PASS (4 tests)
-
-**Step 5: Commit**
-
-```bash
-cd /Users/snider/Code/core/php-uptelligence
-git add Services/VendorUpdateCheckerService.php tests/Unit/AltumCodeCheckerTest.php
-git commit -m "feat: add AltumCode product + plugin version checking
-
-Extends VendorUpdateCheckerService to check AltumCode products via
-their info.php endpoints and plugins via dev.altumcode.com/plugins-versions.
-No auth required — all endpoints are public.
-
-Co-Authored-By: Virgil "
-```
-
----
-
-### Task 2: Seed AltumCode vendors
-
-**Files:**
-- Create: `/Users/snider/Code/core/php-uptelligence/database/seeders/AltumCodeVendorSeeder.php`
-- Test: `/Users/snider/Code/core/php-uptelligence/tests/Unit/AltumCodeVendorSeederTest.php`
-
-**Step 1: Write the failing test**
-
-Create `/Users/snider/Code/core/php-uptelligence/tests/Unit/AltumCodeVendorSeederTest.php`:
-
-```php
-artisan('db:seed', ['--class' => 'Core\\Mod\\Uptelligence\\Database\\Seeders\\AltumCodeVendorSeeder']);
-
- expect(Vendor::where('source_type', Vendor::SOURCE_LICENSED)
- ->where('plugin_platform', Vendor::PLATFORM_ALTUM)
- ->count()
- )->toBe(4);
-});
-
-it('seeds 13 altum plugins', function () {
- $this->artisan('db:seed', ['--class' => 'Core\\Mod\\Uptelligence\\Database\\Seeders\\AltumCodeVendorSeeder']);
-
- expect(Vendor::where('source_type', Vendor::SOURCE_PLUGIN)
- ->where('plugin_platform', Vendor::PLATFORM_ALTUM)
- ->count()
- )->toBe(13);
-});
-
-it('is idempotent', function () {
- $this->artisan('db:seed', ['--class' => 'Core\\Mod\\Uptelligence\\Database\\Seeders\\AltumCodeVendorSeeder']);
- $this->artisan('db:seed', ['--class' => 'Core\\Mod\\Uptelligence\\Database\\Seeders\\AltumCodeVendorSeeder']);
-
- expect(Vendor::where('plugin_platform', Vendor::PLATFORM_ALTUM)->count())->toBe(17);
-});
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=AltumCodeVendorSeeder`
-Expected: FAIL — seeder class not found
-
-**Step 3: Write minimal implementation**
-
-Create `/Users/snider/Code/core/php-uptelligence/database/seeders/AltumCodeVendorSeeder.php`:
-
-```php
- '66analytics', 'name' => '66analytics', 'vendor_name' => 'AltumCode', 'current_version' => '65.0.0'],
- ['slug' => '66biolinks', 'name' => '66biolinks', 'vendor_name' => 'AltumCode', 'current_version' => '65.0.0'],
- ['slug' => '66pusher', 'name' => '66pusher', 'vendor_name' => 'AltumCode', 'current_version' => '65.0.0'],
- ['slug' => '66socialproof', 'name' => '66socialproof', 'vendor_name' => 'AltumCode', 'current_version' => '65.0.0'],
- ];
-
- foreach ($products as $product) {
- Vendor::updateOrCreate(
- ['slug' => $product['slug']],
- [
- ...$product,
- 'source_type' => Vendor::SOURCE_LICENSED,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'is_active' => true,
- ]
- );
- }
-
- $plugins = [
- ['slug' => 'altum-plugin-affiliate', 'name' => 'Affiliate Plugin', 'current_version' => '2.0.0'],
- ['slug' => 'altum-plugin-push-notifications', 'name' => 'Push Notifications Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-teams', 'name' => 'Teams Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-pwa', 'name' => 'PWA Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-image-optimizer', 'name' => 'Image Optimizer Plugin', 'current_version' => '3.1.0'],
- ['slug' => 'altum-plugin-email-shield', 'name' => 'Email Shield Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-dynamic-og-images', 'name' => 'Dynamic OG Images Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-offload', 'name' => 'Offload & CDN Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-payment-blocks', 'name' => 'Payment Blocks Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-ultimate-blocks', 'name' => 'Ultimate Blocks Plugin', 'current_version' => '9.1.0'],
- ['slug' => 'altum-plugin-pro-blocks', 'name' => 'Pro Blocks Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-pro-notifications', 'name' => 'Pro Notifications Plugin', 'current_version' => '1.0.0'],
- ['slug' => 'altum-plugin-aix', 'name' => 'AIX Plugin', 'current_version' => '1.0.0'],
- ];
-
- foreach ($plugins as $plugin) {
- Vendor::updateOrCreate(
- ['slug' => $plugin['slug']],
- [
- ...$plugin,
- 'vendor_name' => 'AltumCode',
- 'source_type' => Vendor::SOURCE_PLUGIN,
- 'plugin_platform' => Vendor::PLATFORM_ALTUM,
- 'is_active' => true,
- ]
- );
- }
- }
-}
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=AltumCodeVendorSeeder`
-Expected: PASS (3 tests)
-
-**Step 5: Commit**
-
-```bash
-cd /Users/snider/Code/core/php-uptelligence
-git add database/seeders/AltumCodeVendorSeeder.php tests/Unit/AltumCodeVendorSeederTest.php
-git commit -m "feat: seed AltumCode vendors — 4 products + 13 plugins
-
-Idempotent seeder using updateOrCreate. Products are SOURCE_LICENSED,
-plugins are SOURCE_PLUGIN, all PLATFORM_ALTUM. Version numbers will
-need updating to match actual deployed versions.
-
-Co-Authored-By: Virgil "
-```
-
----
-
-### Task 3: Create Claude Code plugin skill for downloads
-
-**Files:**
-- Create: `/Users/snider/.claude/plugins/altum-updater/plugin.json`
-- Create: `/Users/snider/.claude/plugins/altum-updater/skills/update-altum.md`
-
-**Step 1: Create plugin manifest**
-
-Create `/Users/snider/.claude/plugins/altum-updater/plugin.json`:
-
-```json
-{
- "name": "altum-updater",
- "description": "Download AltumCode product and plugin updates from LemonSqueezy and CodeCanyon",
- "version": "0.1.0",
- "skills": [
- {
- "name": "update-altum",
- "path": "skills/update-altum.md",
- "description": "Download AltumCode product and plugin updates from marketplaces. Use when the user mentions updating AltumCode products, downloading from LemonSqueezy or CodeCanyon, or running the update checker."
- }
- ]
-}
-```
-
-**Step 2: Create skill file**
-
-Create `/Users/snider/.claude/plugins/altum-updater/skills/update-altum.md`:
-
-```markdown
----
-name: update-altum
-description: Download AltumCode product and plugin updates from LemonSqueezy and CodeCanyon
----
-
-# AltumCode Update Downloader
-
-## Overview
-
-Downloads updated AltumCode products and plugins from two marketplaces:
-- **LemonSqueezy** (Playwright): 66analytics, 66pusher, 66biolinks (extended), 13 plugins
-- **CodeCanyon** (Claude in Chrome): 66biolinks (regular), 66socialproof
-
-## Pre-flight
-
-1. Run `php artisan uptelligence:check-updates --vendor=66analytics` (or check all) to see what needs updating
-2. Show the user the version comparison table
-3. Ask which products/plugins to download
-
-## LemonSqueezy Download Flow (Playwright)
-
-LemonSqueezy uses magic link auth. The user will need to tap the link on their phone.
-
-1. Navigate to `https://app.lemonsqueezy.com/my-orders`
-2. If on login page, fill email `snider@lt.hn` and click Sign In
-3. Tell user: "Magic link sent — tap the link on your phone"
-4. Wait for redirect to orders page
-5. For each product/plugin that needs updating:
- a. Click the product link on the orders page (paginated — 10 per page, 2 pages)
- b. In the order detail, find the "Download" button under "Files"
- c. Click Download — file saves to default downloads folder
-6. Move downloaded zips to staging: `~/Code/lthn/saas/updates/YYYY-MM-DD/`
-
-### LemonSqueezy Product Names (as shown on orders page)
-
-| Our Name | LemonSqueezy Order Name |
-|----------|------------------------|
-| 66analytics | "66analytics - Regular License" |
-| 66pusher | "66pusher - Regular License" |
-| 66biolinks (extended) | "66biolinks custom" |
-| Affiliate Plugin | "Affiliate Plugin" |
-| Push Notifications Plugin | "Push Notifications Plugin" |
-| Teams Plugin | "Teams Plugin" |
-| PWA Plugin | "PWA Plugin" |
-| Image Optimizer Plugin | "Image Optimizer Plugin" |
-| Email Shield Plugin | "Email Shield Plugin" |
-| Dynamic OG Images | "Dynamic OG images plugin" |
-| Offload & CDN | "Offload & CDN Plugin" |
-| Payment Blocks | "Payment Blocks - 66biolinks plugin" |
-| Ultimate Blocks | "Ultimate Blocks - 66biolinks plugin" |
-| Pro Blocks | "Pro Blocks - 66biolinks plugin" |
-| Pro Notifications | "Pro Notifications - 66socialproof plugin" |
-| AltumCode Club | "The AltumCode Club" |
-
-## CodeCanyon Download Flow (Claude in Chrome)
-
-CodeCanyon uses saved browser session cookies (user: snidered).
-
-1. Navigate to `https://codecanyon.net/downloads`
-2. Dismiss cookie banner if present (click "Reject all")
-3. For 66socialproof:
- a. Find "66socialproof" Download button
- b. Click the dropdown arrow
- c. Click "All files & documentation"
-4. For 66biolinks:
- a. Find "66biolinks" Download button (scroll down)
- b. Click the dropdown arrow
- c. Click "All files & documentation"
-5. Move downloaded zips to staging
-
-### CodeCanyon Download URLs (stable)
-
-- 66socialproof: `/user/snidered/download_purchase/8d8ef4c1-5add-4eba-9a89-4261a9c87e0b`
-- 66biolinks: `/user/snidered/download_purchase/38d79f4e-19cd-480a-b068-4332629b5206`
-
-## Post-Download
-
-1. List all zips in staging folder
-2. For each product zip:
- - Extract to `~/Code/lthn/saas/services/{product}/package/product/`
-3. For each plugin zip:
- - Extract to the correct product's `plugins/{plugin_id}/` directory
- - Note: Some plugins apply to multiple products (affiliate, teams, etc.)
-4. Show summary of what was updated
-5. Remind user: "Files staged. Run `deploy_saas.yml` when ready to deploy."
-
-## Important Notes
-
-- Never make purchases or enter financial information
-- LemonSqueezy session expires — if Playwright gets a login page mid-flow, re-trigger magic link
-- CodeCanyon session depends on Chrome cookies — if logged out, tell user to log in manually
-- The AltumCode Club subscription is NOT a downloadable product — skip it
-- Plugin `aix` may not appear on LemonSqueezy (bundled with products) — skip if not found
-```
-
-**Step 3: Verify plugin loads**
-
-Run: `claude` in a new terminal, then type `/update-altum` to verify the skill is discovered.
-
-**Step 4: Commit**
-
-```bash
-cd /Users/snider/.claude/plugins/altum-updater
-git init
-git add plugin.json skills/update-altum.md
-git commit -m "feat: altum-updater Claude Code plugin — marketplace download skill
-
-Playwright for LemonSqueezy, Chrome for CodeCanyon. Includes full
-product/plugin mapping and download flow documentation.
-
-Co-Authored-By: Virgil "
-```
-
----
-
-### Task 4: Sync deployed plugin versions from source
-
-**Files:**
-- Create: `/Users/snider/Code/core/php-uptelligence/Console/SyncAltumVersionsCommand.php`
-- Modify: `/Users/snider/Code/core/php-uptelligence/Boot.php` (register command)
-- Test: `/Users/snider/Code/core/php-uptelligence/tests/Unit/SyncAltumVersionsCommandTest.php`
-
-**Step 1: Write the failing test**
-
-```php
-artisan('uptelligence:sync-altum-versions', ['--dry-run' => true])
- ->assertExitCode(0);
-});
-```
-
-**Step 2: Run test to verify it fails**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=SyncAltumVersions`
-Expected: FAIL — command not found
-
-**Step 3: Write minimal implementation**
-
-Create `/Users/snider/Code/core/php-uptelligence/Console/SyncAltumVersionsCommand.php`:
-
-```php
- '66analytics/package/product',
- '66biolinks' => '66biolinks/package/product',
- '66pusher' => '66pusher/package/product',
- '66socialproof' => '66socialproof/package/product',
- ];
-
- public function handle(): int
- {
- $basePath = $this->option('path')
- ?? env('SAAS_SERVICES_PATH', base_path('../lthn/saas/services'));
- $dryRun = $this->option('dry-run');
-
- $this->info('Syncing AltumCode versions from source...');
- $this->newLine();
-
- $updates = [];
-
- // Sync product versions
- foreach ($this->productPaths as $slug => $relativePath) {
- $productPath = rtrim($basePath, '/') . '/' . $relativePath;
- $version = $this->readProductVersion($productPath);
-
- if ($version) {
- $updates[] = $this->syncVendorVersion($slug, $version, $dryRun);
- } else {
- $this->warn(" Could not read version for {$slug} at {$productPath}");
- }
- }
-
- // Sync plugin versions — read from biolinks as canonical source
- $biolinkPluginsPath = rtrim($basePath, '/') . '/66biolinks/package/product/plugins';
- if (is_dir($biolinkPluginsPath)) {
- foreach (glob($biolinkPluginsPath . '/*/config.php') as $configFile) {
- $pluginId = basename(dirname($configFile));
- $version = $this->readPluginVersion($configFile);
-
- if ($version) {
- $slug = "altum-plugin-{$pluginId}";
- $updates[] = $this->syncVendorVersion($slug, $version, $dryRun);
- }
- }
- }
-
- // Output table
- $this->table(
- ['Vendor', 'Old Version', 'New Version', 'Status'],
- array_filter($updates)
- );
-
- if ($dryRun) {
- $this->warn('Dry run — no changes written.');
- }
-
- return self::SUCCESS;
- }
-
- protected function readProductVersion(string $productPath): ?string
- {
- // Read version from app/init.php or similar — look for PRODUCT_VERSION define
- $initFile = $productPath . '/app/init.php';
- if (! file_exists($initFile)) {
- return null;
- }
-
- $content = file_get_contents($initFile);
- if (preg_match("/define\('PRODUCT_VERSION',\s*'([^']+)'\)/", $content, $matches)) {
- return $matches[1];
- }
-
- return null;
- }
-
- protected function readPluginVersion(string $configFile): ?string
- {
- if (! file_exists($configFile)) {
- return null;
- }
-
- $content = file_get_contents($configFile);
-
- // PHP config format: 'version' => '2.0.0'
- if (preg_match("/'version'\s*=>\s*'([^']+)'/", $content, $matches)) {
- return $matches[1];
- }
-
- return null;
- }
-
- protected function syncVendorVersion(string $slug, string $version, bool $dryRun): ?array
- {
- $vendor = Vendor::where('slug', $slug)->first();
- if (! $vendor) {
- return [$slug, '(not in DB)', $version, 'SKIPPED'];
- }
-
- $oldVersion = $vendor->current_version;
- if ($oldVersion === $version) {
- return [$slug, $oldVersion, $version, 'current'];
- }
-
- if (! $dryRun) {
- $vendor->update(['current_version' => $version]);
- }
-
- return [$slug, $oldVersion ?? '(none)', $version, $dryRun ? 'WOULD UPDATE' : 'UPDATED'];
- }
-}
-```
-
-Register in Boot.php — add to `onConsole()`:
-
-```php
-$event->command(Console\SyncAltumVersionsCommand::class);
-```
-
-**Step 4: Run test to verify it passes**
-
-Run: `cd /Users/snider/Code/core/php-uptelligence && composer test -- --filter=SyncAltumVersions`
-Expected: PASS
-
-**Step 5: Commit**
-
-```bash
-cd /Users/snider/Code/core/php-uptelligence
-git add Console/SyncAltumVersionsCommand.php Boot.php tests/Unit/SyncAltumVersionsCommandTest.php
-git commit -m "feat: sync deployed AltumCode versions from source files
-
-Reads PRODUCT_VERSION from product init.php and plugin versions from
-config.php files. Updates uptelligence_vendors table so check-updates
-knows what's actually deployed.
-
-Co-Authored-By: Virgil "
-```
-
----
-
-### Task 5: End-to-end verification
-
-**Step 1: Seed vendors on local dev**
-
-```bash
-cd /Users/snider/Code/lab/host.uk.com
-php artisan db:seed --class="Core\Mod\Uptelligence\Database\Seeders\AltumCodeVendorSeeder"
-```
-
-**Step 2: Sync actual deployed versions**
-
-```bash
-php artisan uptelligence:sync-altum-versions --path=/Users/snider/Code/lthn/saas/services
-```
-
-**Step 3: Run the update check**
-
-```bash
-php artisan uptelligence:check-updates
-```
-
-Expected: Table showing current vs latest versions for all 17 AltumCode vendors.
-
-**Step 4: Test the skill**
-
-Open a new Claude Code session and run `/update-altum` to verify the skill loads and shows the workflow.
-
-**Step 5: Commit any fixes**
-
-```bash
-git add -A && git commit -m "fix: adjustments from end-to-end testing"
-```
diff --git a/pkg/lib/workspace/default/.core/reference/docs/primitives.md b/pkg/lib/workspace/default/.core/reference/docs/primitives.md
deleted file mode 100644
index 43701f2d..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/primitives.md
+++ /dev/null
@@ -1,169 +0,0 @@
----
-title: Core Primitives
-description: The repeated shapes that make CoreGO easy to navigate.
----
-
-# Core Primitives
-
-CoreGO is easiest to use when you read it as a small vocabulary repeated everywhere. Most of the framework is built from the same handful of types.
-
-## Primitive Map
-
-| Type | Used For |
-|------|----------|
-| `Options` | Input values and lightweight metadata |
-| `Result` | Output values and success state |
-| `Service` | Lifecycle-managed components |
-| `Message` | Broadcast events |
-| `Query` | Request-response lookups |
-| `Task` | Side-effecting work items |
-
-## `Option` and `Options`
-
-`Option` is one key-value pair. `Options` is an ordered slice of them.
-
-```go
-opts := core.Options{
- {Key: "name", Value: "brain"},
- {Key: "path", Value: "prompts"},
- {Key: "debug", Value: true},
-}
-```
-
-Use the helpers to read values:
-
-```go
-name := opts.String("name")
-path := opts.String("path")
-debug := opts.Bool("debug")
-hasPath := opts.Has("path")
-raw := opts.Get("name")
-```
-
-### Important Details
-
-- `Get` returns the first matching key.
-- `String`, `Int`, and `Bool` do not convert between types.
-- Missing keys return zero values.
-- CLI flags with values are stored as strings, so `--port=8080` should be read with `opts.String("port")`, not `opts.Int("port")`.
-
-## `Result`
-
-`Result` is the universal return shape.
-
-```go
-r := core.Result{Value: "ready", OK: true}
-
-if r.OK {
- fmt.Println(r.Value)
-}
-```
-
-It has two jobs:
-
-- carry a value when work succeeds
-- carry either an error or an empty state when work does not succeed
-
-### `Result.Result(...)`
-
-The `Result()` method adapts plain Go values and `(value, error)` pairs into a `core.Result`.
-
-```go
-r1 := core.Result{}.Result("hello")
-r2 := core.Result{}.Result(file, err)
-```
-
-This is how several built-in helpers bridge standard-library calls.
-
-## `Service`
-
-`Service` is the managed lifecycle DTO stored in the registry.
-
-```go
-svc := core.Service{
- Name: "cache",
- Options: core.Options{
- {Key: "backend", Value: "memory"},
- },
- OnStart: func() core.Result {
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- return core.Result{OK: true}
- },
- OnReload: func() core.Result {
- return core.Result{OK: true}
- },
-}
-```
-
-### Important Details
-
-- `OnStart` and `OnStop` are used by the framework lifecycle.
-- `OnReload` is stored on the service DTO, but CoreGO does not currently call it automatically.
-- The registry stores `*core.Service`, not arbitrary typed service instances.
-
-## `Message`, `Query`, and `Task`
-
-These are simple aliases to `any`.
-
-```go
-type Message any
-type Query any
-type Task any
-```
-
-That means your own structs become the protocol:
-
-```go
-type deployStarted struct {
- Environment string
-}
-
-type workspaceCountQuery struct{}
-
-type syncRepositoryTask struct {
- Name string
-}
-```
-
-## `TaskWithIdentifier`
-
-Long-running tasks can opt into task identifiers.
-
-```go
-type indexedTask struct {
- ID string
-}
-
-func (t *indexedTask) SetTaskIdentifier(id string) { t.ID = id }
-func (t *indexedTask) GetTaskIdentifier() string { return t.ID }
-```
-
-If a task implements `TaskWithIdentifier`, `PerformAsync` injects the generated `task-N` identifier before dispatch.
-
-## `ServiceRuntime[T]`
-
-`ServiceRuntime[T]` is the small helper for packages that want to keep a Core reference and a typed options struct together.
-
-```go
-type agentServiceOptions struct {
- WorkspacePath string
-}
-
-type agentService struct {
- *core.ServiceRuntime[agentServiceOptions]
-}
-
-runtime := core.NewServiceRuntime(c, agentServiceOptions{
- WorkspacePath: "/srv/agent-workspaces",
-})
-```
-
-It exposes:
-
-- `Core()`
-- `Options()`
-- `Config()`
-
-This helper does not register anything by itself. It is a composition aid for package authors.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/services.md b/pkg/lib/workspace/default/.core/reference/docs/services.md
deleted file mode 100644
index ad95d647..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/services.md
+++ /dev/null
@@ -1,152 +0,0 @@
----
-title: Services
-description: Register, inspect, and lock CoreGO services.
----
-
-# Services
-
-In CoreGO, a service is a named lifecycle entry stored in the Core registry.
-
-## Register a Service
-
-```go
-c := core.New()
-
-r := c.Service("audit", core.Service{
- OnStart: func() core.Result {
- core.Info("audit started")
- return core.Result{OK: true}
- },
- OnStop: func() core.Result {
- core.Info("audit stopped")
- return core.Result{OK: true}
- },
-})
-```
-
-Registration succeeds when:
-
-- the name is not empty
-- the registry is not locked
-- the name is not already in use
-
-## Read a Service Back
-
-```go
-r := c.Service("audit")
-if r.OK {
- svc := r.Value.(*core.Service)
- _ = svc
-}
-```
-
-The returned value is `*core.Service`.
-
-## List Registered Services
-
-```go
-names := c.Services()
-```
-
-### Important Detail
-
-The current registry is map-backed. `Services()`, `Startables()`, and `Stoppables()` do not promise a stable order.
-
-## Lifecycle Snapshots
-
-Use these helpers when you want the current set of startable or stoppable services:
-
-```go
-startables := c.Startables()
-stoppables := c.Stoppables()
-```
-
-They return `[]*core.Service` inside `Result.Value`.
-
-## Lock the Registry
-
-CoreGO has a service-lock mechanism, but it is explicit.
-
-```go
-c := core.New()
-
-c.LockEnable()
-c.Service("audit", core.Service{})
-c.Service("cache", core.Service{})
-c.LockApply()
-```
-
-After `LockApply`, new registrations fail:
-
-```go
-r := c.Service("late", core.Service{})
-fmt.Println(r.OK) // false
-```
-
-The default lock name is `"srv"`. You can pass a different name if you need a custom lock namespace.
-
-For the service registry itself, use the default `"srv"` lock path. That is the path used by `Core.Service(...)`.
-
-## `NewWithFactories`
-
-For GUI runtimes or factory-driven setup, CoreGO provides `NewWithFactories`.
-
-```go
-r := core.NewWithFactories(nil, map[string]core.ServiceFactory{
- "audit": func() core.Result {
- return core.Result{Value: core.Service{
- OnStart: func() core.Result {
- return core.Result{OK: true}
- },
- }, OK: true}
- },
- "cache": func() core.Result {
- return core.Result{Value: core.Service{}, OK: true}
- },
-})
-```
-
-### Important Details
-
-- each factory must return a `core.Service` in `Result.Value`
-- factories are executed in sorted key order
-- nil factories are skipped
-- the return value is `*core.Runtime`
-
-## `Runtime`
-
-`Runtime` is a small wrapper used for external runtimes such as GUI bindings.
-
-```go
-r := core.NewRuntime(nil)
-rt := r.Value.(*core.Runtime)
-
-_ = rt.ServiceStartup(context.Background(), nil)
-_ = rt.ServiceShutdown(context.Background())
-```
-
-`Runtime.ServiceName()` returns `"Core"`.
-
-## `ServiceRuntime[T]` for Package Authors
-
-If you are writing a package on top of CoreGO, use `ServiceRuntime[T]` to keep a typed options struct and the parent `Core` together.
-
-```go
-type repositoryServiceOptions struct {
- BaseDirectory string
-}
-
-type repositoryService struct {
- *core.ServiceRuntime[repositoryServiceOptions]
-}
-
-func newRepositoryService(c *core.Core) *repositoryService {
- return &repositoryService{
- ServiceRuntime: core.NewServiceRuntime(c, repositoryServiceOptions{
- BaseDirectory: "/srv/repos",
- }),
- }
-}
-```
-
-This is a package-authoring helper. It does not replace the `core.Service` registry entry.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/subsystems.md b/pkg/lib/workspace/default/.core/reference/docs/subsystems.md
deleted file mode 100644
index f39ea164..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/subsystems.md
+++ /dev/null
@@ -1,158 +0,0 @@
----
-title: Subsystems
-description: Built-in accessors for app metadata, embedded data, filesystem, transport handles, i18n, and CLI.
----
-
-# Subsystems
-
-`Core` gives you a set of built-in subsystems so small applications do not need extra plumbing before they can do useful work.
-
-## Accessor Map
-
-| Accessor | Purpose |
-|----------|---------|
-| `App()` | Application identity and external runtime |
-| `Data()` | Named embedded filesystem mounts |
-| `Drive()` | Named transport handles |
-| `Fs()` | Local filesystem access |
-| `I18n()` | Locale collection and translation delegation |
-| `Cli()` | Command-line surface over the command tree |
-
-## `App`
-
-`App` stores process identity and optional GUI runtime state.
-
-```go
-app := c.App()
-app.Name = "agent-workbench"
-app.Version = "0.25.0"
-app.Description = "workspace runner"
-app.Runtime = myRuntime
-```
-
-`Find` resolves an executable on `PATH` and returns an `*App`.
-
-```go
-r := core.Find("go", "Go toolchain")
-```
-
-## `Data`
-
-`Data` mounts named embedded filesystems and makes them addressable through paths like `mount-name/path/to/file`.
-
-```go
-c.Data().New(core.Options{
- {Key: "name", Value: "app"},
- {Key: "source", Value: appFS},
- {Key: "path", Value: "templates"},
-})
-```
-
-Read content:
-
-```go
-text := c.Data().ReadString("app/agent.md")
-bytes := c.Data().ReadFile("app/agent.md")
-list := c.Data().List("app")
-names := c.Data().ListNames("app")
-```
-
-Extract a mounted directory:
-
-```go
-r := c.Data().Extract("app/workspace", "/tmp/workspace", nil)
-```
-
-### Path Rule
-
-The first path segment is always the mount name.
-
-## `Drive`
-
-`Drive` is a registry for named transport handles.
-
-```go
-c.Drive().New(core.Options{
- {Key: "name", Value: "api"},
- {Key: "transport", Value: "https://api.lthn.ai"},
-})
-
-c.Drive().New(core.Options{
- {Key: "name", Value: "mcp"},
- {Key: "transport", Value: "mcp://mcp.lthn.sh"},
-})
-```
-
-Read them back:
-
-```go
-handle := c.Drive().Get("api")
-hasMCP := c.Drive().Has("mcp")
-names := c.Drive().Names()
-```
-
-## `Fs`
-
-`Fs` wraps local filesystem operations with a consistent `Result` shape.
-
-```go
-c.Fs().Write("/tmp/core-go/example.txt", "hello")
-r := c.Fs().Read("/tmp/core-go/example.txt")
-```
-
-Other helpers:
-
-```go
-c.Fs().EnsureDir("/tmp/core-go/cache")
-c.Fs().List("/tmp/core-go")
-c.Fs().Stat("/tmp/core-go/example.txt")
-c.Fs().Rename("/tmp/core-go/example.txt", "/tmp/core-go/example-2.txt")
-c.Fs().Delete("/tmp/core-go/example-2.txt")
-```
-
-### Important Details
-
-- the default `Core` starts with `Fs{root:"/"}`
-- relative paths resolve from the current working directory
-- `Delete` and `DeleteAll` refuse to remove `/` and `$HOME`
-
-## `I18n`
-
-`I18n` collects locale mounts and forwards translation work to a translator implementation when one is registered.
-
-```go
-c.I18n().SetLanguage("en-GB")
-```
-
-Without a translator, `Translate` returns the message key itself:
-
-```go
-r := c.I18n().Translate("cmd.deploy.description")
-```
-
-With a translator:
-
-```go
-c.I18n().SetTranslator(myTranslator)
-```
-
-Then:
-
-```go
-langs := c.I18n().AvailableLanguages()
-current := c.I18n().Language()
-```
-
-## `Cli`
-
-`Cli` exposes the command registry through a terminal-facing API.
-
-```go
-c.Cli().SetBanner(func(_ *core.Cli) string {
- return "Agent Workbench"
-})
-
-r := c.Cli().Run("workspace", "create", "--name=alpha")
-```
-
-Use [commands.md](commands.md) for the full command and flag model.
diff --git a/pkg/lib/workspace/default/.core/reference/docs/testing.md b/pkg/lib/workspace/default/.core/reference/docs/testing.md
deleted file mode 100644
index 656634ab..00000000
--- a/pkg/lib/workspace/default/.core/reference/docs/testing.md
+++ /dev/null
@@ -1,118 +0,0 @@
----
-title: Testing
-description: Test naming and testing patterns used by CoreGO.
----
-
-# Testing
-
-The repository uses `github.com/stretchr/testify/assert` and a simple AX-friendly naming pattern.
-
-## Test Names
-
-Use:
-
-- `_Good` for expected success
-- `_Bad` for expected failure
-- `_Ugly` for panics, degenerate input, and edge behavior
-
-Examples from this repository:
-
-```go
-func TestNew_Good(t *testing.T) {}
-func TestService_Register_Duplicate_Bad(t *testing.T) {}
-func TestCore_Must_Ugly(t *testing.T) {}
-```
-
-## Start with a Small Core
-
-```go
-c := core.New(core.Options{
- {Key: "name", Value: "test-core"},
-})
-```
-
-Then register only the pieces your test needs.
-
-## Test a Service
-
-```go
-started := false
-
-c.Service("audit", core.Service{
- OnStart: func() core.Result {
- started = true
- return core.Result{OK: true}
- },
-})
-
-r := c.ServiceStartup(context.Background(), nil)
-assert.True(t, r.OK)
-assert.True(t, started)
-```
-
-## Test a Command
-
-```go
-c.Command("greet", core.Command{
- Action: func(opts core.Options) core.Result {
- return core.Result{Value: "hello " + opts.String("name"), OK: true}
- },
-})
-
-r := c.Cli().Run("greet", "--name=world")
-assert.True(t, r.OK)
-assert.Equal(t, "hello world", r.Value)
-```
-
-## Test a Query or Task
-
-```go
-c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
- if q == "ping" {
- return core.Result{Value: "pong", OK: true}
- }
- return core.Result{}
-})
-
-assert.Equal(t, "pong", c.QUERY("ping").Value)
-```
-
-```go
-c.RegisterTask(func(_ *core.Core, t core.Task) core.Result {
- if t == "compute" {
- return core.Result{Value: 42, OK: true}
- }
- return core.Result{}
-})
-
-assert.Equal(t, 42, c.PERFORM("compute").Value)
-```
-
-## Test Async Work
-
-For `PerformAsync`, observe completion through the action bus.
-
-```go
-completed := make(chan core.ActionTaskCompleted, 1)
-
-c.RegisterAction(func(_ *core.Core, msg core.Message) core.Result {
- if event, ok := msg.(core.ActionTaskCompleted); ok {
- completed <- event
- }
- return core.Result{OK: true}
-})
-```
-
-Then wait with normal Go test tools such as channels, timers, or `assert.Eventually`.
-
-## Use Real Temporary Paths
-
-When testing `Fs`, `Data.Extract`, or other I/O helpers, use `t.TempDir()` and create realistic paths instead of mocking the filesystem by default.
-
-## Repository Commands
-
-```bash
-core go test
-core go test --run TestPerformAsync_Good
-go test ./...
-```
diff --git a/pkg/lib/workspace/default/.core/reference/drive.go b/pkg/lib/workspace/default/.core/reference/drive.go
deleted file mode 100644
index 7bf68690..00000000
--- a/pkg/lib/workspace/default/.core/reference/drive.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Drive is the resource handle registry for transport connections.
-// Packages register their transport handles (API, MCP, SSH, VPN)
-// and other packages access them by name.
-//
-// Register a transport:
-//
-// c.Drive().New(core.NewOptions(
-// core.Option{Key: "name", Value: "api"},
-// core.Option{Key: "transport", Value: "https://api.lthn.ai"},
-// ))
-// c.Drive().New(core.NewOptions(
-// core.Option{Key: "name", Value: "ssh"},
-// core.Option{Key: "transport", Value: "ssh://claude@10.69.69.165"},
-// ))
-// c.Drive().New(core.NewOptions(
-// core.Option{Key: "name", Value: "mcp"},
-// core.Option{Key: "transport", Value: "mcp://mcp.lthn.sh"},
-// ))
-//
-// Retrieve a handle:
-//
-// api := c.Drive().Get("api")
-package core
-
-// DriveHandle holds a named transport resource.
-type DriveHandle struct {
- Name string
- Transport string
- Options Options
-}
-
-// Drive manages named transport handles. Embeds Registry[*DriveHandle].
-type Drive struct {
- *Registry[*DriveHandle]
-}
-
-// New registers a transport handle.
-//
-// c.Drive().New(core.NewOptions(
-// core.Option{Key: "name", Value: "api"},
-// core.Option{Key: "transport", Value: "https://api.lthn.ai"},
-// ))
-func (d *Drive) New(opts Options) Result {
- name := opts.String("name")
- if name == "" {
- return Result{}
- }
-
- handle := &DriveHandle{
- Name: name,
- Transport: opts.String("transport"),
- Options: opts,
- }
-
- d.Set(name, handle)
- return Result{handle, true}
-}
diff --git a/pkg/lib/workspace/default/.core/reference/embed.go b/pkg/lib/workspace/default/.core/reference/embed.go
deleted file mode 100644
index 32bbc5ec..00000000
--- a/pkg/lib/workspace/default/.core/reference/embed.go
+++ /dev/null
@@ -1,676 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Embedded assets for the Core framework.
-//
-// Embed provides scoped filesystem access for go:embed and any fs.FS.
-// Also includes build-time asset packing (AST scanner + compressor)
-// and template-based directory extraction.
-//
-// Usage (mount):
-//
-// sub, _ := core.Mount(myFS, "lib/persona")
-// content, _ := sub.ReadString("secops/developer.md")
-//
-// Usage (extract):
-//
-// core.Extract(fsys, "/tmp/workspace", data)
-//
-// Usage (pack):
-//
-// refs, _ := core.ScanAssets([]string{"main.go"})
-// source, _ := core.GeneratePack(refs)
-// core.AddAsset("docs", "RFC.md", packed)
-// r := core.GeneratePack(pkg)
-package core
-
-import (
- "compress/gzip"
- corebytes "dappco.re/go"
- corefilepath "dappco.re/go"
- corefmt "dappco.re/go"
- coreos "dappco.re/go"
- "embed"
- "encoding/base64"
- "go/ast"
- "go/parser"
- "go/token"
- "io"
- "io/fs"
- "sync"
- "text/template"
-)
-
-// --- Runtime: Asset Registry ---
-
-// AssetGroup holds a named collection of packed assets.
-type AssetGroup struct {
- assets map[string]string // name → compressed data
-}
-
-var (
- assetGroups = make(map[string]*AssetGroup)
- assetGroupsMu sync.RWMutex
-)
-
-// AddAsset registers a packed asset at runtime (called from generated init()).
-func AddAsset(group, name, data string) {
- assetGroupsMu.Lock()
- defer assetGroupsMu.Unlock()
-
- g, ok := assetGroups[group]
- if !ok {
- g = &AssetGroup{assets: make(map[string]string)}
- assetGroups[group] = g
- }
- g.assets[name] = data
-}
-
-// GetAsset retrieves and decompresses a packed asset.
-//
-// r := core.GetAsset("mygroup", "greeting")
-// if r.OK { content := r.Value.(string) }
-func GetAsset(group, name string) Result {
- assetGroupsMu.RLock()
- g, ok := assetGroups[group]
- if !ok {
- assetGroupsMu.RUnlock()
- return Result{}
- }
- data, ok := g.assets[name]
- assetGroupsMu.RUnlock()
- if !ok {
- return Result{}
- }
- s, err := decompress(data)
- if err != nil {
- return Result{err, false}
- }
- return Result{s, true}
-}
-
-// GetAssetBytes retrieves a packed asset as bytes.
-//
-// r := core.GetAssetBytes("mygroup", "file")
-// if r.OK { data := r.Value.([]byte) }
-func GetAssetBytes(group, name string) Result {
- r := GetAsset(group, name)
- if !r.OK {
- return r
- }
- return Result{[]byte(r.Value.(string)), true}
-}
-
-// --- Build-time: AST Scanner ---
-
-// AssetRef is a reference to an asset found in source code.
-type AssetRef struct {
- Name string
- Path string
- Group string
- FullPath string
-}
-
-// ScannedPackage holds all asset references from a set of source files.
-type ScannedPackage struct {
- PackageName string
- BaseDirectory string
- Groups []string
- Assets []AssetRef
-}
-
-// ScanAssets parses Go source files and finds asset references.
-// Looks for calls to: core.GetAsset("group", "name"), core.AddAsset, etc.
-func ScanAssets(filenames []string) Result {
- packageMap := make(map[string]*ScannedPackage)
- var scanErr error
-
- for _, filename := range filenames {
- fset := token.NewFileSet()
- node, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
- if err != nil {
- return Result{err, false}
- }
-
- baseDir := filepath.Dir(filename)
- pkg, ok := packageMap[baseDir]
- if !ok {
- pkg = &ScannedPackage{BaseDirectory: baseDir}
- packageMap[baseDir] = pkg
- }
- pkg.PackageName = node.Name.Name
-
- ast.Inspect(node, func(n ast.Node) bool {
- if scanErr != nil {
- return false
- }
- call, ok := n.(*ast.CallExpr)
- if !ok {
- return true
- }
-
- sel, ok := call.Fun.(*ast.SelectorExpr)
- if !ok {
- return true
- }
-
- ident, ok := sel.X.(*ast.Ident)
- if !ok {
- return true
- }
-
- // Look for core.GetAsset or mewn.String patterns
- if ident.Name == "core" || ident.Name == "mewn" {
- switch sel.Sel.Name {
- case "GetAsset", "GetAssetBytes", "String", "MustString", "Bytes", "MustBytes":
- if len(call.Args) >= 1 {
- if lit, ok := call.Args[len(call.Args)-1].(*ast.BasicLit); ok {
- path := TrimPrefix(TrimSuffix(lit.Value, "\""), "\"")
- group := "."
- if len(call.Args) >= 2 {
- if glit, ok := call.Args[0].(*ast.BasicLit); ok {
- group = TrimPrefix(TrimSuffix(glit.Value, "\""), "\"")
- }
- }
- fullPath, err := filepath.Abs(filepath.Join(baseDir, group, path))
- if err != nil {
- scanErr = Wrap(err, "core.ScanAssets", Join(" ", "could not determine absolute path for asset", path, "in group", group))
- return false
- }
- pkg.Assets = append(pkg.Assets, AssetRef{
- Name: path,
-
- Group: group,
- FullPath: fullPath,
- })
- }
- }
- case "Group":
- // Variable assignment: g := core.Group("./assets")
- if len(call.Args) == 1 {
- if lit, ok := call.Args[0].(*ast.BasicLit); ok {
- path := TrimPrefix(TrimSuffix(lit.Value, "\""), "\"")
- fullPath, err := filepath.Abs(filepath.Join(baseDir, path))
- if err != nil {
- scanErr = Wrap(err, "core.ScanAssets", Join(" ", "could not determine absolute path for group", path))
- return false
- }
- pkg.Groups = append(pkg.Groups, fullPath)
- // Track for variable resolution
- }
- }
- }
- }
-
- return true
- })
- if scanErr != nil {
- return Result{scanErr, false}
- }
- }
-
- var result []ScannedPackage
- for _, pkg := range packageMap {
- result = append(result, *pkg)
- }
- return Result{result, true}
-}
-
-// GeneratePack creates Go source code that embeds the scanned assets.
-func GeneratePack(pkg ScannedPackage) Result {
- b := NewBuilder()
-
- b.WriteString(fmt.Sprintf("package %s\n\n", pkg.PackageName))
- b.WriteString("// Code generated by core pack. DO NOT EDIT.\n\n")
-
- if len(pkg.Assets) == 0 && len(pkg.Groups) == 0 {
- return Result{b.String(), true}
- }
-
- b.WriteString("import \"dappco.re/go\"\n\n")
- b.WriteString("func init() {\n")
-
- // Pack groups (entire directories)
- packed := make(map[string]bool)
- for _, groupPath := range pkg.Groups {
- files, err := getAllFiles(groupPath)
- if err != nil {
- return Result{err, false}
- }
- for _, file := range files {
- if packed[file] {
- continue
- }
- data, err := compressFile(file)
- if err != nil {
- return Result{err, false}
- }
- localPath := TrimPrefix(file, groupPath+"/")
- relGroup, err := filepath.Rel(pkg.BaseDirectory, groupPath)
- if err != nil {
- return Result{err, false}
- }
- b.WriteString(fmt.Sprintf("\tcore.AddAsset(%q, %q, %q)\n", relGroup, localPath, data))
- packed[file] = true
- }
- }
-
- // Pack individual assets
- for _, asset := range pkg.Assets {
- if packed[asset.FullPath] {
- continue
- }
- data, err := compressFile(asset.FullPath)
- if err != nil {
- return Result{err, false}
- }
- b.WriteString(fmt.Sprintf("\tcore.AddAsset(%q, %q, %q)\n", asset.Group, asset.Name, data))
- packed[asset.FullPath] = true
- }
-
- b.WriteString("}\n")
- return Result{b.String(), true}
-}
-
-// --- Compression ---
-
-func compressFile(path string) (string, any) {
- data, err := os.ReadFile(path)
- if err != nil {
- return "", err
- }
- return compress(string(data))
-}
-
-func compress(input string) (string, any) {
- var buf bytes.Buffer
- b64 := base64.NewEncoder(base64.StdEncoding, &buf)
- gz, err := gzip.NewWriterLevel(b64, gzip.BestCompression)
- if err != nil {
- return "", err
- }
- if _, err := gz.Write([]byte(input)); err != nil {
- if closeErr := gz.Close(); closeErr != nil {
- return "", err
- }
- if closeErr := b64.Close(); closeErr != nil {
- return "", err
- }
- return "", err
- }
- if err := gz.Close(); err != nil {
- if closeErr := b64.Close(); closeErr != nil {
- return "", err
- }
- return "", err
- }
- if err := b64.Close(); err != nil {
- return "", err
- }
- return buf.String(), nil
-}
-
-func decompress(input string) (string, any) {
- b64 := base64.NewDecoder(base64.StdEncoding, NewReader(input))
- gz, err := gzip.NewReader(b64)
- if err != nil {
- return "", err
- }
-
- data, err := io.ReadAll(gz)
- if err != nil {
- return "", err
- }
- if err := gz.Close(); err != nil {
- return "", err
- }
- return string(data), nil
-}
-
-func getAllFiles(dir string) ([]string, any) {
- var result []string
- err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) any {
- if err != nil {
- return err
- }
- if !d.IsDir() {
- result = append(result, path)
- }
- return nil
- })
- return result, err
-}
-
-// --- Embed: Scoped Filesystem Mount ---
-
-// Embed wraps an fs.FS with a basedir for scoped access.
-// All paths are relative to basedir.
-type Embed struct {
- basedir string
- fsys fs.FS
- embedFS *embed.FS // original embed.FS for type-safe access via EmbedFS()
-}
-
-// Mount creates a scoped view of an fs.FS anchored at basedir.
-//
-// r := core.Mount(myFS, "lib/prompts")
-// if r.OK { emb := r.Value.(*Embed) }
-func Mount(fsys fs.FS, basedir string) Result {
- s := &Embed{fsys: fsys, basedir: basedir}
-
- if efs, ok := fsys.(embed.FS); ok {
- s.embedFS = &efs
- }
-
- if r := s.ReadDir("."); !r.OK {
- return r
- }
- return Result{s, true}
-}
-
-// MountEmbed creates a scoped view of an embed.FS.
-//
-// r := core.MountEmbed(myFS, "testdata")
-func MountEmbed(efs embed.FS, basedir string) Result {
- return Mount(efs, basedir)
-}
-
-func (s *Embed) path(name string) Result {
- joined := filepath.ToSlash(filepath.Join(s.basedir, name))
- if HasPrefix(joined, "..") || Contains(joined, "/../") || HasSuffix(joined, "/..") {
- return Result{E("embed.path", Concat("path traversal rejected: ", name), nil), false}
- }
- return Result{joined, true}
-}
-
-// Open opens the named file for reading.
-//
-// r := emb.Open("test.txt")
-// if r.OK { file := r.Value.(fs.File) }
-func (s *Embed) Open(name string) Result {
- r := s.path(name)
- if !r.OK {
- return r
- }
- f, err := s.fsys.Open(r.Value.(string))
- if err != nil {
- return Result{err, false}
- }
- return Result{f, true}
-}
-
-// ReadDir reads the named directory.
-func (s *Embed) ReadDir(name string) Result {
- r := s.path(name)
- if !r.OK {
- return r
- }
- return Result{}.New(fs.ReadDir(s.fsys, r.Value.(string)))
-}
-
-// ReadFile reads the named file.
-//
-// r := emb.ReadFile("test.txt")
-// if r.OK { data := r.Value.([]byte) }
-func (s *Embed) ReadFile(name string) Result {
- r := s.path(name)
- if !r.OK {
- return r
- }
- data, err := fs.ReadFile(s.fsys, r.Value.(string))
- if err != nil {
- return Result{err, false}
- }
- return Result{data, true}
-}
-
-// ReadString reads the named file as a string.
-//
-// r := emb.ReadString("test.txt")
-// if r.OK { content := r.Value.(string) }
-func (s *Embed) ReadString(name string) Result {
- r := s.ReadFile(name)
- if !r.OK {
- return r
- }
- return Result{string(r.Value.([]byte)), true}
-}
-
-// Sub returns a new Embed anchored at a subdirectory within this mount.
-//
-// r := emb.Sub("testdata")
-// if r.OK { sub := r.Value.(*Embed) }
-func (s *Embed) Sub(subDir string) Result {
- r := s.path(subDir)
- if !r.OK {
- return r
- }
- sub, err := fs.Sub(s.fsys, r.Value.(string))
- if err != nil {
- return Result{err, false}
- }
- return Result{&Embed{fsys: sub, basedir: "."}, true}
-}
-
-// FS returns the underlying fs.FS.
-func (s *Embed) FS() fs.FS {
- return s.fsys
-}
-
-// EmbedFS returns the underlying embed.FS if mounted from one.
-// Returns zero embed.FS if mounted from a non-embed source.
-func (s *Embed) EmbedFS() embed.FS {
- if s.embedFS != nil {
- return *s.embedFS
- }
- return embed.FS{}
-}
-
-// BaseDirectory returns the base directory this Embed is anchored at.
-func (s *Embed) BaseDirectory() string {
- return s.basedir
-}
-
-// --- Template Extraction ---
-
-// ExtractOptions configures template extraction.
-type ExtractOptions struct {
- // TemplateFilters identifies template files by substring match.
- // Default: [".tmpl"]
- TemplateFilters []string
-
- // IgnoreFiles is a set of filenames to skip during extraction.
- IgnoreFiles map[string]struct{}
-
- // RenameFiles maps original filenames to new names.
- RenameFiles map[string]string
-}
-
-// Extract copies a template directory from an fs.FS to targetDir,
-// processing Go text/template in filenames and file contents.
-//
-// Files containing a template filter substring (default: ".tmpl") have
-// their contents processed through text/template with the given data.
-// The filter is stripped from the output filename.
-//
-// Directory and file names can contain Go template expressions:
-// {{.Name}}/main.go → myproject/main.go
-//
-// Data can be any struct or map[string]string for template substitution.
-func Extract(fsys fs.FS, targetDir string, data any, opts ...ExtractOptions) Result {
- opt := ExtractOptions{
- TemplateFilters: []string{".tmpl"},
- IgnoreFiles: make(map[string]struct{}),
- RenameFiles: make(map[string]string),
- }
- if len(opts) > 0 {
- if len(opts[0].TemplateFilters) > 0 {
- opt.TemplateFilters = opts[0].TemplateFilters
- }
- if opts[0].IgnoreFiles != nil {
- opt.IgnoreFiles = opts[0].IgnoreFiles
- }
- if opts[0].RenameFiles != nil {
- opt.RenameFiles = opts[0].RenameFiles
- }
- }
-
- // Ensure target directory exists
- targetDir, err := filepath.Abs(targetDir)
- if err != nil {
- return Result{err, false}
- }
- if err := os.MkdirAll(targetDir, 0700); err != nil {
- return Result{err, false}
- }
-
- // Categorise files
- var dirs []string
- var templateFiles []string
- var standardFiles []string
-
- err = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) any {
- if err != nil {
- return err
- }
- if path == "." {
- return nil
- }
- if d.IsDir() {
- dirs = append(dirs, path)
- return nil
- }
- filename := filepath.Base(path)
- if _, ignored := opt.IgnoreFiles[filename]; ignored {
- return nil
- }
- if isTemplate(filename, opt.TemplateFilters) {
- templateFiles = append(templateFiles, path)
- } else {
- standardFiles = append(standardFiles, path)
- }
- return nil
- })
- if err != nil {
- return Result{err, false}
- }
-
- // safePath ensures a rendered path stays under targetDir.
- safePath := func(rendered string) (string, any) {
- abs, err := filepath.Abs(rendered)
- if err != nil {
- return "", err
- }
- if !HasPrefix(abs, targetDir+string(filepath.Separator)) && abs != targetDir {
- return "", E("embed.Extract", Concat("path escapes target: ", abs), nil)
- }
- return abs, nil
- }
-
- // Create directories (names may contain templates)
- for _, dir := range dirs {
- target, err := safePath(renderPath(filepath.Join(targetDir, dir), data))
- if err != nil {
- return Result{err, false}
- }
- if err := os.MkdirAll(target, 0700); err != nil {
- return Result{err, false}
- }
- }
-
- // Process template files
- for _, path := range templateFiles {
- tmpl, err := template.ParseFS(fsys, path)
- if err != nil {
- return Result{err, false}
- }
-
- targetFile := renderPath(filepath.Join(targetDir, path), data)
-
- // Strip template filters from filename
- dir := filepath.Dir(targetFile)
- name := filepath.Base(targetFile)
- for _, filter := range opt.TemplateFilters {
- name = Replace(name, filter, "")
- }
- if renamed := opt.RenameFiles[name]; renamed != "" {
- name = renamed
- }
- targetFile, err = safePath(filepath.Join(dir, name))
- if err != nil {
- return Result{err, false}
- }
-
- f, err := os.OpenFile(targetFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
- if err != nil {
- return Result{err, false}
- }
- if err := tmpl.Execute(f, data); err != nil {
- f.Close()
- return Result{err, false}
- }
- f.Close()
- }
-
- // Copy standard files
- for _, path := range standardFiles {
- targetPath := path
- name := filepath.Base(path)
- if renamed := opt.RenameFiles[name]; renamed != "" {
- targetPath = filepath.Join(filepath.Dir(path), renamed)
- }
- target, err := safePath(renderPath(filepath.Join(targetDir, targetPath), data))
- if err != nil {
- return Result{err, false}
- }
- if err := copyFile(fsys, path, target); err != nil {
- return Result{err, false}
- }
- }
-
- return Result{OK: true}
-}
-
-func isTemplate(filename string, filters []string) bool {
- for _, f := range filters {
- if Contains(filename, f) {
- return true
- }
- }
- return false
-}
-
-func renderPath(path string, data any) string {
- if data == nil {
- return path
- }
- tmpl, err := template.New(`path`).Parse(path)
- if err != nil {
- return path
- }
- var buf bytes.Buffer
- if err := tmpl.Execute(&buf, data); err != nil {
- return path
- }
- return buf.String()
-}
-
-func copyFile(fsys fs.FS, source, target string) any {
- s, err := fsys.Open(source)
- if err != nil {
- return err
- }
- defer s.Close()
-
- if err := os.MkdirAll(filepath.Dir(target), 0700); err != nil {
- return err
- }
-
- d, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
- if err != nil {
- return err
- }
- defer d.Close()
-
- _, err = io.Copy(d, s)
- return err
-}
diff --git a/pkg/lib/workspace/default/.core/reference/error.go b/pkg/lib/workspace/default/.core/reference/error.go
deleted file mode 100644
index cf435532..00000000
--- a/pkg/lib/workspace/default/.core/reference/error.go
+++ /dev/null
@@ -1,404 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Structured errors, crash recovery, and reporting for the Core framework.
-// Provides E() for error creation, Wrap()/WrapCode() for chaining,
-// and Err for panic recovery and crash reporting.
-//
-// if core.Is(err, context.Canceled) { return }
-// stack := core.FormatStackTrace(err)
-// r := c.Error().Reports(10)
-
-package core
-
-import (
- coreerrors "dappco.re/go"
- corefilepath "dappco.re/go"
- corejson "dappco.re/go"
- coreos "dappco.re/go"
- "iter"
- "maps"
- "runtime"
- "runtime/debug"
- "sync"
- "time"
-)
-
-// ErrorSink is the shared interface for error reporting.
-// Implemented by ErrorLog (structured logging) and ErrorPanic (panic recovery).
-type ErrorSink interface {
- Error(msg string, keyvals ...any)
- Warn(msg string, keyvals ...any)
-}
-
-var _ ErrorSink = (*Log)(nil)
-
-// Err represents a structured error with operational context.
-// It implements the error interface and supports unwrapping.
-type Err struct {
- Operation string // Operation being performed (e.g., "user.Save")
- Message string // Human-readable message
- Cause error // Underlying error (optional)
- Code string // Error code (optional, e.g., "VALIDATION_FAILED")
-}
-
-// Error implements the error interface.
-func (e *Err) Error() string {
- var prefix string
- if e.Operation != "" {
- prefix = e.Operation + ": "
- }
- if e.Cause != nil {
- if e.Code != "" {
- return Concat(prefix, e.Message, " [", e.Code, "]: ", e.Cause.Error())
- }
- return Concat(prefix, e.Message, ": ", e.Cause.Error())
- }
- if e.Code != "" {
- return Concat(prefix, e.Message, " [", e.Code, "]")
- }
- return Concat(prefix, e.Message)
-}
-
-// Unwrap returns the underlying error for use with errors.Is and errors.As.
-func (e *Err) Unwrap() any {
- return e.Cause
-}
-
-// --- Error Creation Functions ---
-
-// E creates a new Err with operation context.
-// The underlying error can be nil for creating errors without a cause.
-//
-// Example:
-//
-// return log.E("user.Save", "failed to save user", err)
-// return log.E("api.Call", "rate limited", nil) // No underlying cause
-func E(op, msg string, err error) any {
- return &Err{Operation: op, Message: msg, Cause: err}
-}
-
-// Wrap wraps an error with operation context.
-// Returns nil if err is nil, to support conditional wrapping.
-// Preserves error Code if the wrapped error is an *Err.
-//
-// Example:
-//
-// return log.Wrap(err, "db.Query", "database query failed")
-func Wrap(err any, op, msg string) any {
- if err == nil {
- return nil
- }
- // Preserve Code from wrapped *Err
- var logErr *Err
- if As(err, &logErr) && logErr.Code != "" {
- return &Err{Operation: op, Message: msg, Cause: err, Code: logErr.Code}
- }
- return &Err{Operation: op, Message: msg, Cause: err}
-}
-
-// WrapCode wraps an error with operation context and error code.
-// Returns nil only if both err is nil AND code is empty.
-// Useful for API errors that need machine-readable codes.
-//
-// Example:
-//
-// return log.WrapCode(err, "VALIDATION_ERROR", "user.Validate", "invalid email")
-func WrapCode(err any, code, op, msg string) any {
- if err == nil && code == "" {
- return nil
- }
- return &Err{Operation: op, Message: msg, Cause: err, Code: code}
-}
-
-// NewCode creates an error with just code and message (no underlying error).
-// Useful for creating sentinel errors with codes.
-//
-// Example:
-//
-// var ErrNotFound = log.NewCode("NOT_FOUND", "resource not found")
-func NewCode(code, msg string) any {
- return &Err{Message: msg, Code: code}
-}
-
-// --- Standard Library Wrappers ---
-
-// Is reports whether any error in err's tree matches target.
-// Wrapper around errors.Is for convenience.
-func Is(err, target any) bool {
- return errors.Is(err, target)
-}
-
-// As finds the first error in err's tree that matches target.
-// Wrapper around errors.As for convenience.
-func As(err any, target any) bool {
- return errors.As(err, target)
-}
-
-// NewError creates a simple error with the given text.
-// Wrapper around errors.New for convenience.
-func NewError(text string) any {
- return errors.New(text)
-}
-
-// ErrorJoin combines multiple errors into one.
-//
-// core.ErrorJoin(err1, err2, err3)
-func ErrorJoin(errs ...any) any {
- return errors.Join(errs...)
-}
-
-// --- Error Introspection Helpers ---
-
-// Operation extracts the operation name from an error.
-// Returns empty string if the error is not an *Err.
-func Operation(err any) string {
- var e *Err
- if As(err, &e) {
- return e.Operation
- }
- return ""
-}
-
-// ErrorCode extracts the error code from an error.
-// Returns empty string if the error is not an *Err or has no code.
-func ErrorCode(err any) string {
- var e *Err
- if As(err, &e) {
- return e.Code
- }
- return ""
-}
-
-// Message extracts the message from an error.
-// Returns the error's Error() string if not an *Err.
-func ErrorMessage(err any) string {
- if err == nil {
- return ""
- }
- var e *Err
- if As(err, &e) {
- return e.Message
- }
- return err.Error()
-}
-
-// Root returns the root cause of an error chain.
-// Unwraps until no more wrapped errors are found.
-func Root(err any) any {
- if err == nil {
- return nil
- }
- for {
- unwrapped := errors.Unwrap(err)
- if unwrapped == nil {
- return err
- }
- err = unwrapped
- }
-}
-
-// AllOperations returns an iterator over all operational contexts in the error chain.
-// It traverses the error tree using errors.Unwrap.
-func AllOperations(err any) iter.Seq[string] {
- return func(yield func(string) bool) {
- for err != nil {
- if e, ok := err.(*Err); ok {
- if e.Operation != "" {
- if !yield(e.Operation) {
- return
- }
- }
- }
- err = errors.Unwrap(err)
- }
- }
-}
-
-// StackTrace returns the logical stack trace (chain of operations) from an error.
-// It returns an empty slice if no operational context is found.
-func StackTrace(err any) []string {
- var stack []string
- for op := range AllOperations(err) {
- stack = append(stack, op)
- }
- return stack
-}
-
-// FormatStackTrace returns a pretty-printed logical stack trace.
-func FormatStackTrace(err any) string {
- var ops []string
- for op := range AllOperations(err) {
- ops = append(ops, op)
- }
- if len(ops) == 0 {
- return ""
- }
- return Join(" -> ", ops...)
-}
-
-// --- ErrorLog: Log-and-Return Error Helpers ---
-
-// ErrorLog combines error creation with logging.
-// Primary action: return an error. Secondary: log it.
-type ErrorLog struct {
- log *Log
-}
-
-func (el *ErrorLog) logger() *Log {
- if el.log != nil {
- return el.log
- }
- return Default()
-}
-
-// Error logs at Error level and returns a Result with the wrapped error.
-func (el *ErrorLog) Error(err any, op, msg string) Result {
- if err == nil {
- return Result{OK: true}
- }
- wrapped := Wrap(err, op, msg)
- el.logger().Error(msg, "op", op, "err", err)
- return Result{wrapped, false}
-}
-
-// Warn logs at Warn level and returns a Result with the wrapped error.
-func (el *ErrorLog) Warn(err any, op, msg string) Result {
- if err == nil {
- return Result{OK: true}
- }
- wrapped := Wrap(err, op, msg)
- el.logger().Warn(msg, "op", op, "err", err)
- return Result{wrapped, false}
-}
-
-// Must logs and panics if err is not nil.
-func (el *ErrorLog) Must(err any, op, msg string) {
- if err != nil {
- el.logger().Error(msg, "op", op, "err", err)
- panic(Wrap(err, op, msg))
- }
-}
-
-// --- Crash Recovery & Reporting ---
-
-// CrashReport represents a single crash event.
-type CrashReport struct {
- Timestamp time.Time `json:"timestamp"`
- Error string `json:"error"`
- Stack string `json:"stack"`
- System CrashSystem `json:"system,omitempty"`
- Meta map[string]string `json:"meta,omitempty"`
-}
-
-// CrashSystem holds system information at crash time.
-type CrashSystem struct {
- OperatingSystem string `json:"operatingsystem"`
- Architecture string `json:"architecture"`
- Version string `json:"go_version"`
-}
-
-// ErrorPanic manages panic recovery and crash reporting.
-type ErrorPanic struct {
- filePath string
- meta map[string]string
- onCrash func(CrashReport)
-}
-
-// Recover captures a panic and creates a crash report.
-// Use as: defer c.Error().Recover()
-func (h *ErrorPanic) Recover() {
- if h == nil {
- return
- }
- r := recover()
- if r == nil {
- return
- }
-
- err, ok := r.(error)
- if !ok {
- err = NewError(Sprint("panic: ", r))
- }
-
- report := CrashReport{
- Timestamp: time.Now(),
- Error: err.Error(),
- Stack: string(debug.Stack()),
- System: CrashSystem{
- OperatingSystem: runtime.GOOS,
- Architecture: runtime.GOARCH,
- Version: runtime.Version(),
- },
- Meta: maps.Clone(h.meta),
- }
-
- if h.onCrash != nil {
- h.onCrash(report)
- }
-
- if h.filePath != "" {
- h.appendReport(report)
- }
-}
-
-// SafeGo runs a function in a goroutine with panic recovery.
-func (h *ErrorPanic) SafeGo(fn func()) {
- go func() {
- defer h.Recover()
- fn()
- }()
-}
-
-// Reports returns the last n crash reports from the file.
-func (h *ErrorPanic) Reports(n int) Result {
- if h.filePath == "" {
- return Result{}
- }
- crashMu.Lock()
- defer crashMu.Unlock()
- data, err := os.ReadFile(h.filePath)
- if err != nil {
- return Result{err, false}
- }
- var reports []CrashReport
- if err := json.Unmarshal(data, &reports); err != nil {
- return Result{err, false}
- }
- if n <= 0 || len(reports) <= n {
- return Result{reports, true}
- }
- return Result{reports[len(reports)-n:], true}
-}
-
-var crashMu sync.Mutex
-
-func (h *ErrorPanic) appendReport(report CrashReport) {
- crashMu.Lock()
- defer crashMu.Unlock()
-
- var reports []CrashReport
- if data, err := os.ReadFile(h.filePath); err == nil {
- if err := json.Unmarshal(data, &reports); err != nil {
- Default().Error(Concat("crash report file corrupted path=", h.filePath, " err=", err.Error(), " raw=", string(data)))
- backupPath := Concat(h.filePath, ".corrupt")
- if backupErr := os.WriteFile(backupPath, data, 0600); backupErr != nil {
- Default().Error(Concat("crash report backup failed path=", h.filePath, " err=", backupErr.Error()))
- }
- reports = nil
- }
- }
-
- reports = append(reports, report)
- data, err := json.MarshalIndent(reports, "", " ")
- if err != nil {
- Default().Error(Concat("crash report marshal failed: ", err.Error()))
- return
- }
- if err := os.MkdirAll(filepath.Dir(h.filePath), 0700); err != nil {
- Default().Error(Concat("crash report dir failed: ", err.Error()))
- return
- }
- if err := os.WriteFile(h.filePath, data, 0600); err != nil {
- Default().Error(Concat("crash report write failed: ", err.Error()))
- }
-}
diff --git a/pkg/lib/workspace/default/.core/reference/fs.go b/pkg/lib/workspace/default/.core/reference/fs.go
deleted file mode 100644
index 013d2c67..00000000
--- a/pkg/lib/workspace/default/.core/reference/fs.go
+++ /dev/null
@@ -1,469 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Sandboxed local filesystem I/O for the Core framework.
-package core
-
-import (
- corefilepath "dappco.re/go"
- coreos "dappco.re/go"
- "io"
- "io/fs"
- "os/user"
- "time"
-)
-
-// Fs is a sandboxed local filesystem backend.
-type Fs struct {
- root string
-}
-
-// New initialises an Fs with the given root directory.
-// Root "/" means unrestricted access. Empty root defaults to "/".
-//
-// fs := (&core.Fs{}).New("/")
-func (m *Fs) New(root string) *Fs {
- if root == "" {
- root = "/"
- }
- m.root = root
- return m
-}
-
-// NewUnrestricted returns a new Fs with root "/", granting full filesystem access.
-// Use this instead of unsafe.Pointer to bypass the sandbox.
-//
-// fs := c.Fs().NewUnrestricted()
-// fs.Read("/etc/hostname") // works — no sandbox
-func (m *Fs) NewUnrestricted() *Fs {
- return (&Fs{}).New("/")
-}
-
-// Root returns the sandbox root path.
-//
-// root := c.Fs().Root() // e.g. "/home/agent/.core"
-func (m *Fs) Root() string {
- if m.root == "" {
- return "/"
- }
- return m.root
-}
-
-// path sanitises and returns the full path.
-// Absolute paths are sandboxed under root (unless root is "/").
-// Empty root defaults to "/" — the zero value of Fs is usable.
-func (m *Fs) path(p string) string {
- root := m.root
- if root == "" {
- root = "/"
- }
- if p == "" {
- return root
- }
-
- // If the path is relative and the medium is rooted at "/",
- // treat it as relative to the current working directory.
- // This makes io.Local behave more like the standard 'os' package.
- if root == "/" && !filepath.IsAbs(p) {
- cwd, _ := os.Getwd()
- return filepath.Join(cwd, p)
- }
-
- // Use filepath.Clean with a leading slash to resolve all .. and . internally
- // before joining with the root. This is a standard way to sandbox paths.
- clean := filepath.Clean("/" + p)
-
- // If root is "/", allow absolute paths through
- if root == "/" {
- return clean
- }
-
- // Strip leading "/" so Join works correctly with root
- return filepath.Join(root, clean[1:])
-}
-
-// validatePath ensures the path is within the sandbox, following symlinks if they exist.
-func (m *Fs) validatePath(p string) Result {
- root := m.root
- if root == "" {
- root = "/"
- }
- if root == "/" {
- return Result{m.path(p), true}
- }
-
- // Split the cleaned path into components
- parts := Split(filepath.Clean("/"+p), string(os.PathSeparator))
- current := root
-
- for _, part := range parts {
- if part == "" {
- continue
- }
-
- next := filepath.Join(current, part)
- realNext, err := filepath.EvalSymlinks(next)
- if err != nil {
- if os.IsNotExist(err) {
- // Part doesn't exist, we can't follow symlinks anymore.
- // Since the path is already Cleaned and current is safe,
- // appending a component to current will not escape.
- current = next
- continue
- }
- return Result{err, false}
- }
-
- // Verify the resolved part is still within the root
- rel, err := filepath.Rel(root, realNext)
- if err != nil || HasPrefix(rel, "..") {
- // Security event: sandbox escape attempt
- username := "unknown"
- if u, err := user.Current(); err == nil {
- username = u.Username
- }
- Print(os.Stderr, "[%s] SECURITY sandbox escape detected root=%s path=%s attempted=%s user=%s",
- time.Now().Format(time.RFC3339), root, p, realNext, username)
- if err == nil {
- err = E("fs.validatePath", Concat("sandbox escape: ", p, " resolves outside ", m.root), nil)
- }
- return Result{err, false}
- }
- current = realNext
- }
-
- return Result{current, true}
-}
-
-// Read returns file contents as string.
-func (m *Fs) Read(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- data, err := os.ReadFile(vp.Value.(string))
- if err != nil {
- return Result{err, false}
- }
- return Result{string(data), true}
-}
-
-// Write saves content to file, creating parent directories as needed.
-// Files are created with mode 0600 by default.
-// Use WriteMode when broader access is intentional.
-func (m *Fs) Write(p, content string) Result {
- return m.WriteMode(p, content, 0600)
-}
-
-// WriteMode saves content to file with explicit permissions.
-// Use 0644 or 0755 only when broader access is intentional.
-func (m *Fs) WriteMode(p, content string, mode os.FileMode) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if err := os.MkdirAll(filepath.Dir(full), 0700); err != nil {
- return Result{err, false}
- }
- if err := os.WriteFile(full, []byte(content), mode); err != nil {
- return Result{err, false}
- }
- if err := os.Chmod(full, mode); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// TempDir creates a temporary directory and returns its path.
-// The caller is responsible for cleanup via fs.DeleteAll().
-//
-// dir := fs.TempDir("agent-workspace")
-// defer fs.DeleteAll(dir)
-func (m *Fs) TempDir(prefix string) string {
- root := m.root
- if root == "" || root == "/" {
- root = os.TempDir()
- } else if err := os.MkdirAll(root, 0700); err != nil {
- return ""
- }
- dir, err := os.MkdirTemp(root, prefix)
- if err != nil {
- return ""
- }
- if vp := m.validatePath(dir); !vp.OK {
- os.RemoveAll(dir)
- return ""
- }
- return dir
-}
-
-// DirFS returns an fs.FS rooted at the given directory path.
-//
-// fsys := core.DirFS("/path/to/templates")
-func DirFS(dir string) fs.FS {
- return os.DirFS(dir)
-}
-
-// WriteAtomic writes content by writing to a temp file then renaming.
-// Rename is atomic on POSIX — concurrent readers never see a partial file.
-// Use this for status files, config, or any file read from multiple goroutines.
-//
-// r := fs.WriteAtomic("/status.json", jsonData)
-func (m *Fs) WriteAtomic(p, content string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if err := os.MkdirAll(filepath.Dir(full), 0700); err != nil {
- return Result{err, false}
- }
-
- tmp := full + ".tmp." + shortRand()
- if err := os.WriteFile(tmp, []byte(content), 0600); err != nil {
- return Result{err, false}
- }
- if err := os.Rename(tmp, full); err != nil {
- os.Remove(tmp)
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// EnsureDir creates directory if it doesn't exist.
-func (m *Fs) EnsureDir(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- if err := os.MkdirAll(vp.Value.(string), 0700); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// IsDir returns true if path is a directory.
-func (m *Fs) IsDir(p string) bool {
- if p == "" {
- return false
- }
- vp := m.validatePath(p)
- if !vp.OK {
- return false
- }
- info, err := os.Stat(vp.Value.(string))
- return err == nil && info.IsDir()
-}
-
-// IsFile returns true if path is a regular file.
-func (m *Fs) IsFile(p string) bool {
- if p == "" {
- return false
- }
- vp := m.validatePath(p)
- if !vp.OK {
- return false
- }
- info, err := os.Stat(vp.Value.(string))
- return err == nil && info.Mode().IsRegular()
-}
-
-// Exists returns true if path exists.
-func (m *Fs) Exists(p string) bool {
- vp := m.validatePath(p)
- if !vp.OK {
- return false
- }
- _, err := os.Stat(vp.Value.(string))
- return err == nil
-}
-
-// List returns directory entries.
-func (m *Fs) List(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- return Result{}.New(os.ReadDir(vp.Value.(string)))
-}
-
-// Stat returns file info.
-func (m *Fs) Stat(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- return Result{}.New(os.Stat(vp.Value.(string)))
-}
-
-// Open opens the named file for reading.
-func (m *Fs) Open(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- return Result{}.New(os.Open(vp.Value.(string)))
-}
-
-// Create creates or truncates the named file.
-func (m *Fs) Create(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if err := os.MkdirAll(filepath.Dir(full), 0700); err != nil {
- return Result{err, false}
- }
- file, err := os.OpenFile(full, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
- if err != nil {
- return Result{err, false}
- }
- if err := file.Chmod(0600); err != nil {
- file.Close()
- return Result{err, false}
- }
- return Result{}.New(file)
-}
-
-// Append opens the named file for appending, creating it if it doesn't exist.
-func (m *Fs) Append(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if err := os.MkdirAll(filepath.Dir(full), 0700); err != nil {
- return Result{err, false}
- }
- file, err := os.OpenFile(full, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
- if err != nil {
- return Result{err, false}
- }
- if err := file.Chmod(0600); err != nil {
- file.Close()
- return Result{err, false}
- }
- return Result{}.New(file)
-}
-
-// ReadStream returns a reader for the file content.
-func (m *Fs) ReadStream(path string) Result {
- return m.Open(path)
-}
-
-// WriteStream returns a writer for the file content.
-func (m *Fs) WriteStream(path string) Result {
- return m.Create(path)
-}
-
-// ReadAll reads all bytes from a ReadCloser and closes it.
-// Wraps io.ReadAll so consumers don't import "io".
-//
-// r := fs.ReadStream(path)
-// data := core.ReadAll(r.Value)
-func ReadAll(reader any) Result {
- rc, ok := reader.(io.Reader)
- if !ok {
- return Result{E("core.ReadAll", "not a reader", nil), false}
- }
- data, err := io.ReadAll(rc)
- if closer, ok := reader.(io.Closer); ok {
- closer.Close()
- }
- if err != nil {
- return Result{err, false}
- }
- return Result{string(data), true}
-}
-
-// WriteAll writes content to a writer and closes it if it implements Closer.
-//
-// r := fs.WriteStream(path)
-// core.WriteAll(r.Value, "content")
-func WriteAll(writer any, content string) Result {
- wc, ok := writer.(io.Writer)
- if !ok {
- return Result{E("core.WriteAll", "not a writer", nil), false}
- }
- _, err := wc.Write([]byte(content))
- var closeErr error
- if closer, ok := writer.(io.Closer); ok {
- closeErr = closer.Close()
- }
- if err != nil {
- return Result{err, false}
- }
- if closeErr != nil {
- return Result{closeErr, false}
- }
- return Result{OK: true}
-}
-
-func (m *Fs) isProtectedPath(full string) bool {
- if full == "/" {
- return true
- }
- home, err := os.UserHomeDir()
- if err != nil || home == "" {
- return false
- }
- return full == home
-}
-
-// CloseStream closes any value that implements io.Closer.
-//
-// core.CloseStream(r.Value)
-func CloseStream(v any) {
- if closer, ok := v.(io.Closer); ok {
- closer.Close()
- }
-}
-
-// Delete removes a file or empty directory.
-func (m *Fs) Delete(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if m.isProtectedPath(full) {
- return Result{E("fs.Delete", Concat("refusing to delete protected path: ", full), nil), false}
- }
- if err := os.Remove(full); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// DeleteAll removes a file or directory recursively.
-func (m *Fs) DeleteAll(p string) Result {
- vp := m.validatePath(p)
- if !vp.OK {
- return vp
- }
- full := vp.Value.(string)
- if m.isProtectedPath(full) {
- return Result{E("fs.DeleteAll", Concat("refusing to delete protected path: ", full), nil), false}
- }
- if err := os.RemoveAll(full); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
-
-// Rename moves a file or directory.
-func (m *Fs) Rename(oldPath, newPath string) Result {
- oldVp := m.validatePath(oldPath)
- if !oldVp.OK {
- return oldVp
- }
- newVp := m.validatePath(newPath)
- if !newVp.OK {
- return newVp
- }
- if err := os.Rename(oldVp.Value.(string), newVp.Value.(string)); err != nil {
- return Result{err, false}
- }
- return Result{OK: true}
-}
diff --git a/pkg/lib/workspace/default/.core/reference/i18n.go b/pkg/lib/workspace/default/.core/reference/i18n.go
deleted file mode 100644
index 27b11de3..00000000
--- a/pkg/lib/workspace/default/.core/reference/i18n.go
+++ /dev/null
@@ -1,140 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Internationalisation for the Core framework.
-// I18n collects locale mounts from services and delegates
-// translation to a registered Translator implementation (e.g., go-i18n).
-
-package core
-
-import (
- "sync"
-)
-
-// Translator defines the interface for translation services.
-// Implemented by go-i18n's Srv.
-type Translator interface {
- // Translate translates a message by its ID with optional arguments.
- Translate(messageID string, args ...any) Result
- // SetLanguage sets the active language (BCP47 tag, e.g., "en-GB", "de").
- SetLanguage(lang string) error
- // Language returns the current language code.
- Language() string
- // AvailableLanguages returns all loaded language codes.
- AvailableLanguages() []string
-}
-
-// LocaleProvider is implemented by services that ship their own translation files.
-// Core discovers this interface during service registration and collects the
-// locale mounts. The i18n service loads them during startup.
-//
-// Usage in a service package:
-//
-// //go:embed locales
-// var localeFS embed.FS
-//
-// func (s *MyService) Locales() *Embed {
-// m, _ := Mount(localeFS, "locales")
-// return m
-// }
-type LocaleProvider interface {
- Locales() *Embed
-}
-
-// I18n manages locale collection and translation dispatch.
-type I18n struct {
- mu sync.RWMutex
- locales []*Embed // collected from LocaleProvider services
- locale string
- translator Translator // registered implementation (nil until set)
-}
-
-// AddLocales adds locale mounts (called during service registration).
-func (i *I18n) AddLocales(mounts ...*Embed) {
- i.mu.Lock()
- i.locales = append(i.locales, mounts...)
- i.mu.Unlock()
-}
-
-// Locales returns all collected locale mounts.
-func (i *I18n) Locales() Result {
- i.mu.RLock()
- out := make([]*Embed, len(i.locales))
- copy(out, i.locales)
- i.mu.RUnlock()
- return Result{out, true}
-}
-
-// SetTranslator registers the translation implementation.
-// Called by go-i18n's Srv during startup.
-func (i *I18n) SetTranslator(t Translator) {
- i.mu.Lock()
- i.translator = t
- locale := i.locale
- i.mu.Unlock()
- if t != nil && locale != "" {
- if result := t.SetLanguage(locale); !result.OK {
- return
- }
- }
-}
-
-// Translator returns the registered translation implementation, or nil.
-func (i *I18n) Translator() Result {
- i.mu.RLock()
- t := i.translator
- i.mu.RUnlock()
- if t == nil {
- return Result{}
- }
- return Result{t, true}
-}
-
-// Translate translates a message. Returns the key as-is if no translator is registered.
-func (i *I18n) Translate(messageID string, args ...any) Result {
- i.mu.RLock()
- t := i.translator
- i.mu.RUnlock()
- if t != nil {
- return t.Translate(messageID, args...)
- }
- return Result{messageID, true}
-}
-
-// SetLanguage sets the active language and forwards to the translator if registered.
-func (i *I18n) SetLanguage(lang string) Result {
- if lang == "" {
- return Result{OK: true}
- }
- i.mu.Lock()
- i.locale = lang
- t := i.translator
- i.mu.Unlock()
- if t != nil {
- if err := t.SetLanguage(lang); err != nil {
- return Result{err, false}
- }
- }
- return Result{OK: true}
-}
-
-// Language returns the current language code, or "en" if not set.
-func (i *I18n) Language() string {
- i.mu.RLock()
- locale := i.locale
- i.mu.RUnlock()
- if locale != "" {
- return locale
- }
- return "en"
-}
-
-// AvailableLanguages returns all loaded language codes.
-func (i *I18n) AvailableLanguages() []string {
- i.mu.RLock()
- t := i.translator
- i.mu.RUnlock()
- if t != nil {
- return t.AvailableLanguages()
- }
- return []string{"en"}
-}
diff --git a/pkg/lib/workspace/default/.core/reference/ipc.go b/pkg/lib/workspace/default/.core/reference/ipc.go
deleted file mode 100644
index c5357e7b..00000000
--- a/pkg/lib/workspace/default/.core/reference/ipc.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Message bus for the Core framework.
-// Dispatches actions (fire-and-forget), queries (first responder),
-// and tasks (first executor) between registered handlers.
-
-package core
-
-import (
- "slices"
- "sync"
-)
-
-// Ipc holds IPC dispatch data and the named action registry.
-//
-// ipc := (&core.Ipc{}).New()
-type Ipc struct {
- ipcMu sync.RWMutex
- ipcHandlers []func(*Core, Message) Result
-
- queryMu sync.RWMutex
- queryHandlers []QueryHandler
-
- actions *Registry[*Action] // named action registry
- tasks *Registry[*Task] // named task registry
-}
-
-// broadcast dispatches a message to all registered IPC handlers.
-// Each handler is wrapped in panic recovery. All handlers fire regardless of individual results.
-func (c *Core) broadcast(msg Message) Result {
- c.ipc.ipcMu.RLock()
- handlers := slices.Clone(c.ipc.ipcHandlers)
- c.ipc.ipcMu.RUnlock()
-
- for _, h := range handlers {
- func() {
- defer func() {
- if r := recover(); r != nil {
- Error("ACTION handler panicked", "panic", r)
- }
- }()
- h(c, msg)
- }()
- }
- return Result{OK: true}
-}
-
-// Query dispatches a request — first handler to return OK wins.
-//
-// r := c.Query(MyQuery{})
-func (c *Core) Query(q Query) Result {
- c.ipc.queryMu.RLock()
- handlers := slices.Clone(c.ipc.queryHandlers)
- c.ipc.queryMu.RUnlock()
-
- for _, h := range handlers {
- r := h(c, q)
- if r.OK {
- return r
- }
- }
- return Result{}
-}
-
-// QueryAll dispatches a request — collects all OK responses.
-//
-// r := c.QueryAll(countQuery{})
-// results := r.Value.([]any)
-func (c *Core) QueryAll(q Query) Result {
- c.ipc.queryMu.RLock()
- handlers := slices.Clone(c.ipc.queryHandlers)
- c.ipc.queryMu.RUnlock()
-
- var results []any
- for _, h := range handlers {
- r := h(c, q)
- if r.OK && r.Value != nil {
- results = append(results, r.Value)
- }
- }
- return Result{results, true}
-}
-
-// RegisterQuery registers a handler for QUERY dispatch.
-//
-// c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result { ... })
-func (c *Core) RegisterQuery(handler QueryHandler) {
- c.ipc.queryMu.Lock()
- c.ipc.queryHandlers = append(c.ipc.queryHandlers, handler)
- c.ipc.queryMu.Unlock()
-}
-
-// --- IPC Registration (handlers) ---
-
-// RegisterAction registers a broadcast handler for ACTION messages.
-//
-// c.RegisterAction(func(c *core.Core, msg core.Message) core.Result {
-// if ev, ok := msg.(AgentCompleted); ok { ... }
-// return core.Result{OK: true}
-// })
-func (c *Core) RegisterAction(handler func(*Core, Message) Result) {
- c.ipc.ipcMu.Lock()
- c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handler)
- c.ipc.ipcMu.Unlock()
-}
-
-// RegisterActions registers multiple broadcast handlers.
-func (c *Core) RegisterActions(handlers ...func(*Core, Message) Result) {
- c.ipc.ipcMu.Lock()
- c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handlers...)
- c.ipc.ipcMu.Unlock()
-}
diff --git a/pkg/lib/workspace/default/.core/reference/lock.go b/pkg/lib/workspace/default/.core/reference/lock.go
deleted file mode 100644
index a9632782..00000000
--- a/pkg/lib/workspace/default/.core/reference/lock.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Synchronisation, locking, and lifecycle snapshots for the Core framework.
-
-package core
-
-import (
- "sync"
-)
-
-// Lock is the DTO for a named mutex.
-type Lock struct {
- Name string
- Mutex *sync.RWMutex
- locks *Registry[*sync.RWMutex] // per-Core named mutexes
-}
-
-// Lock returns a named Lock, creating the mutex if needed.
-// Locks are per-Core — separate Core instances do not share mutexes.
-func (c *Core) Lock(name string) *Lock {
- r := c.lock.locks.Get(name)
- if r.OK {
- return &Lock{Name: name, Mutex: r.Value.(*sync.RWMutex)}
- }
- m := &sync.RWMutex{}
- c.lock.locks.Set(name, m)
- return &Lock{Name: name, Mutex: m}
-}
-
-// LockEnable marks that the service lock should be applied after initialisation.
-func (c *Core) LockEnable(name ...string) {
- c.services.lockEnabled = true
-}
-
-// LockApply activates the service lock if it was enabled.
-func (c *Core) LockApply(name ...string) {
- if c.services.lockEnabled {
- c.services.Lock()
- }
-}
-
-// Startables returns services that have an OnStart function, in registration order.
-func (c *Core) Startables() Result {
- if c.services == nil {
- return Result{}
- }
- var out []*Service
- c.services.Each(func(_ string, svc *Service) {
- if svc.OnStart != nil {
- out = append(out, svc)
- }
- })
- return Result{out, true}
-}
-
-// Stoppables returns services that have an OnStop function, in registration order.
-func (c *Core) Stoppables() Result {
- if c.services == nil {
- return Result{}
- }
- var out []*Service
- c.services.Each(func(_ string, svc *Service) {
- if svc.OnStop != nil {
- out = append(out, svc)
- }
- })
- return Result{out, true}
-}
diff --git a/pkg/lib/workspace/default/.core/reference/log.go b/pkg/lib/workspace/default/.core/reference/log.go
deleted file mode 100644
index 2c523f99..00000000
--- a/pkg/lib/workspace/default/.core/reference/log.go
+++ /dev/null
@@ -1,407 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Structured logging for the Core framework.
-//
-// core.SetLevel(core.LevelDebug)
-// core.Info("server started", "port", 8080)
-// core.Error("failed to connect", "err", err)
-// log := core.NewLog(core.LogOptions{Level: core.LevelDebug, Output: os.Stdout})
-// core.SetRedactKeys("token", "password")
-// core.Security("entitlement.denied", "action", "process.run")
-package core
-
-import (
- coreos "dappco.re/go"
- goio "io"
- "os/user"
- "slices"
- "sync"
- "sync/atomic"
- "time"
-)
-
-// Level defines logging verbosity.
-type Level int
-
-// Logging level constants ordered by increasing verbosity.
-const (
- // LevelQuiet suppresses all log output.
- LevelQuiet Level = iota
- // LevelError shows only error messages.
- LevelError
- // LevelWarn shows warnings and errors.
- LevelWarn
- // LevelInfo shows informational messages, warnings, and errors.
- LevelInfo
- // LevelDebug shows all messages including debug details.
- LevelDebug
-)
-
-// String returns the level name.
-func (l Level) String() string {
- switch l {
- case LevelQuiet:
- return "quiet"
- case LevelError:
- return "error"
- case LevelWarn:
- return "warn"
- case LevelInfo:
- return "info"
- case LevelDebug:
- return "debug"
- default:
- return "unknown"
- }
-}
-
-// Log provides structured logging.
-type Log struct {
- mu sync.RWMutex
- level Level
- output goio.Writer
-
- // RedactKeys is a list of keys whose values should be masked in logs.
- redactKeys []string
-
- // Style functions for formatting (can be overridden)
- StyleTimestamp func(string) string
- StyleDebug func(string) string
- StyleInfo func(string) string
- StyleWarn func(string) string
- StyleError func(string) string
- StyleSecurity func(string) string
-}
-
-// RotationLogOptions defines the log rotation and retention policy.
-type RotationLogOptions struct {
- // Filename is the log file path. If empty, rotation is disabled.
- Filename string
-
- // MaxSize is the maximum size of the log file in megabytes before it gets rotated.
- // It defaults to 100 megabytes.
- MaxSize int
-
- // MaxAge is the maximum number of days to retain old log files based on their
- // file modification time. It defaults to 28 days.
- // Note: set to a negative value to disable age-based retention.
- MaxAge int
-
- // MaxBackups is the maximum number of old log files to retain.
- // It defaults to 5 backups.
- MaxBackups int
-
- // Compress determines if the rotated log files should be compressed using gzip.
- // It defaults to true.
- Compress bool
-}
-
-// LogOptions configures a Log.
-type LogOptions struct {
- Level Level
- // Output is the destination for log messages. If Rotation is provided,
- // Output is ignored and logs are written to the rotating file instead.
- Output goio.Writer
- // Rotation enables log rotation to file. If provided, Filename must be set.
- Rotation *RotationLogOptions
- // RedactKeys is a list of keys whose values should be masked in logs.
- RedactKeys []string
-}
-
-// RotationWriterFactory creates a rotating writer from options.
-// Set this to enable log rotation (provided by core/go-io integration).
-var RotationWriterFactory func(RotationLogOptions) goio.WriteCloser
-
-// New creates a new Log with the given options.
-func NewLog(opts LogOptions) *Log {
- output := opts.Output
- if opts.Rotation != nil && opts.Rotation.Filename != "" && RotationWriterFactory != nil {
- output = RotationWriterFactory(*opts.Rotation)
- }
- if output == nil {
- output = os.Stderr
- }
-
- return &Log{
- level: opts.Level,
- output: output,
- redactKeys: slices.Clone(opts.RedactKeys),
- StyleTimestamp: identity,
- StyleDebug: identity,
- StyleInfo: identity,
- StyleWarn: identity,
- StyleError: identity,
- StyleSecurity: identity,
- }
-}
-
-func identity(s string) string { return s }
-
-// SetLevel changes the log level.
-func (l *Log) SetLevel(level Level) {
- l.mu.Lock()
- l.level = level
- l.mu.Unlock()
-}
-
-// Level returns the current log level.
-func (l *Log) Level() Level {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return l.level
-}
-
-// SetOutput changes the output writer.
-func (l *Log) SetOutput(w goio.Writer) {
- l.mu.Lock()
- l.output = w
- l.mu.Unlock()
-}
-
-// SetRedactKeys sets the keys to be redacted.
-func (l *Log) SetRedactKeys(keys ...string) {
- l.mu.Lock()
- l.redactKeys = slices.Clone(keys)
- l.mu.Unlock()
-}
-
-func (l *Log) shouldLog(level Level) bool {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return level <= l.level
-}
-
-func (l *Log) log(level Level, prefix, msg string, keyvals ...any) {
- l.mu.RLock()
- output := l.output
- styleTimestamp := l.StyleTimestamp
- redactKeys := l.redactKeys
- l.mu.RUnlock()
-
- timestamp := styleTimestamp(time.Now().Format("15:04:05"))
-
- // Copy keyvals to avoid mutating the caller's slice
- keyvals = append([]any(nil), keyvals...)
-
- // Automatically extract context from error if present in keyvals
- origLen := len(keyvals)
- for i := 0; i < origLen; i += 2 {
- if i+1 < origLen {
- if err, ok := keyvals[i+1].(error); ok {
- if op := Operation(err); op != "" {
- // Check if op is already in keyvals
- hasOp := false
- for j := 0; j < len(keyvals); j += 2 {
- if k, ok := keyvals[j].(string); ok && k == "op" {
- hasOp = true
- break
- }
- }
- if !hasOp {
- keyvals = append(keyvals, "op", op)
- }
- }
- if stack := FormatStackTrace(err); stack != "" {
- // Check if stack is already in keyvals
- hasStack := false
- for j := 0; j < len(keyvals); j += 2 {
- if k, ok := keyvals[j].(string); ok && k == "stack" {
- hasStack = true
- break
- }
- }
- if !hasStack {
- keyvals = append(keyvals, "stack", stack)
- }
- }
- }
- }
- }
-
- // Format key-value pairs
- var kvStr string
- if len(keyvals) > 0 {
- kvStr = " "
- for i := 0; i < len(keyvals); i += 2 {
- if i > 0 {
- kvStr += " "
- }
- key := keyvals[i]
- var val any
- if i+1 < len(keyvals) {
- val = keyvals[i+1]
- }
-
- // Redaction logic
- keyStr := Sprint(key)
- if slices.Contains(redactKeys, keyStr) {
- val = "[REDACTED]"
- }
-
- // Secure formatting to prevent log injection
- if s, ok := val.(string); ok {
- kvStr += Sprintf("%v=%q", key, s)
- } else {
- kvStr += Sprintf("%v=%v", key, val)
- }
- }
- }
-
- Print(output, "%s %s %s%s", timestamp, prefix, msg, kvStr)
-}
-
-// Debug logs a debug message with optional key-value pairs.
-func (l *Log) Debug(msg string, keyvals ...any) {
- if l.shouldLog(LevelDebug) {
- l.log(LevelDebug, l.StyleDebug("[DBG]"), msg, keyvals...)
- }
-}
-
-// Info logs an info message with optional key-value pairs.
-func (l *Log) Info(msg string, keyvals ...any) {
- if l.shouldLog(LevelInfo) {
- l.log(LevelInfo, l.StyleInfo("[INF]"), msg, keyvals...)
- }
-}
-
-// Warn logs a warning message with optional key-value pairs.
-func (l *Log) Warn(msg string, keyvals ...any) {
- if l.shouldLog(LevelWarn) {
- l.log(LevelWarn, l.StyleWarn("[WRN]"), msg, keyvals...)
- }
-}
-
-// Error logs an error message with optional key-value pairs.
-func (l *Log) Error(msg string, keyvals ...any) {
- if l.shouldLog(LevelError) {
- l.log(LevelError, l.StyleError("[ERR]"), msg, keyvals...)
- }
-}
-
-// Security logs a security event with optional key-value pairs.
-// It uses LevelError to ensure security events are visible even in restrictive
-// log configurations.
-func (l *Log) Security(msg string, keyvals ...any) {
- if l.shouldLog(LevelError) {
- l.log(LevelError, l.StyleSecurity("[SEC]"), msg, keyvals...)
- }
-}
-
-// Username returns the current system username.
-// It uses os/user for reliability and falls back to environment variables.
-func Username() string {
- if u, err := user.Current(); err == nil {
- return u.Username
- }
- // Fallback for environments where user lookup might fail
- if u := os.Getenv("USER"); u != "" {
- return u
- }
- return os.Getenv("USERNAME")
-}
-
-// --- Default logger ---
-
-var defaultLogPtr atomic.Pointer[Log]
-
-func init() {
- l := NewLog(LogOptions{Level: LevelInfo})
- defaultLogPtr.Store(l)
-}
-
-// Default returns the default logger.
-func Default() *Log {
- return defaultLogPtr.Load()
-}
-
-// SetDefault sets the default logger.
-func SetDefault(l *Log) {
- defaultLogPtr.Store(l)
-}
-
-// SetLevel sets the default logger's level.
-func SetLevel(level Level) {
- Default().SetLevel(level)
-}
-
-// SetRedactKeys sets the default logger's redaction keys.
-func SetRedactKeys(keys ...string) {
- Default().SetRedactKeys(keys...)
-}
-
-// Debug logs to the default logger.
-func Debug(msg string, keyvals ...any) {
- Default().Debug(msg, keyvals...)
-}
-
-// Info logs to the default logger.
-func Info(msg string, keyvals ...any) {
- Default().Info(msg, keyvals...)
-}
-
-// Warn logs to the default logger.
-func Warn(msg string, keyvals ...any) {
- Default().Warn(msg, keyvals...)
-}
-
-// Error logs to the default logger.
-func Error(msg string, keyvals ...any) {
- Default().Error(msg, keyvals...)
-}
-
-// Security logs to the default logger.
-func Security(msg string, keyvals ...any) {
- Default().Security(msg, keyvals...)
-}
-
-// --- LogErr: Error-Aware Logger ---
-
-// LogErr logs structured information extracted from errors.
-// Primary action: log. Secondary: extract error context.
-type LogErr struct {
- log *Log
-}
-
-// NewLogErr creates a LogErr bound to the given logger.
-func NewLogErr(log *Log) *LogErr {
- return &LogErr{log: log}
-}
-
-// Log extracts context from an Err and logs it at Error level.
-func (le *LogErr) Log(err any) {
- if err == nil {
- return
- }
- le.log.Error(ErrorMessage(err), "op", Operation(err), "code", ErrorCode(err), "stack", FormatStackTrace(err))
-}
-
-// --- LogPanic: Panic-Aware Logger ---
-
-// LogPanic logs panic context without crash file management.
-// Primary action: log. Secondary: recover panics.
-type LogPanic struct {
- log *Log
-}
-
-// NewLogPanic creates a LogPanic bound to the given logger.
-func NewLogPanic(log *Log) *LogPanic {
- return &LogPanic{log: log}
-}
-
-// Recover captures a panic and logs it. Does not write crash files.
-// Use as: defer core.NewLogPanic(logger).Recover()
-func (lp *LogPanic) Recover() {
- r := recover()
- if r == nil {
- return
- }
- err, ok := r.(error)
- if !ok {
- err = NewError(Sprint("panic: ", r))
- }
- lp.log.Error("panic recovered",
- "err", err,
- "op", Operation(err),
- "stack", FormatStackTrace(err),
- )
-}
diff --git a/pkg/lib/workspace/default/.core/reference/options.go b/pkg/lib/workspace/default/.core/reference/options.go
deleted file mode 100644
index 9268a59b..00000000
--- a/pkg/lib/workspace/default/.core/reference/options.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Core primitives: Option, Options, Result.
-//
-// Options is the universal input type. Result is the universal output type.
-// All Core operations accept Options and return Result.
-//
-// opts := core.NewOptions(
-// core.Option{Key: "name", Value: "brain"},
-// core.Option{Key: `path`, Value: "prompts"},
-// )
-// r := c.Drive().New(opts)
-// if !r.OK { log.Fatal(r.Error()) }
-package core
-
-// --- Result: Universal Output ---
-
-// Result is the universal return type for Core operations.
-// Replaces the (value, error) pattern — errors flow through Core internally.
-//
-// r := c.Data().New(opts)
-// if !r.OK { core.Error("failed", "err", r.Error()) }
-type Result struct {
- Value any
- OK bool
-}
-
-// Result gets or sets the value. Zero args returns Value. With args, maps
-// Go (value, error) pairs to Result and returns self.
-//
-// r.Result(file, err) // OK = err == nil, Value = file
-// r.Result(value) // OK = true, Value = value
-// r.Result() // after set — returns the value
-func (r Result) Result(args ...any) Result {
- if len(args) == 0 {
- return r
- }
- return r.New(args...)
-}
-
-// New adapts Go (value, error) pairs into a Result.
-//
-// r := core.Result{}.New(file, err)
-func (r Result) New(args ...any) Result {
- if len(args) == 0 {
- return r
- }
-
- if len(args) > 1 {
- if err, ok := args[len(args)-1].(error); ok {
- if err != nil {
- return Result{Value: err, OK: false}
- }
- r.Value = args[0]
- r.OK = true
- return r
- }
- }
-
- r.Value = args[0]
-
- if err, ok := r.Value.(error); ok {
- if err != nil {
- return Result{Value: err, OK: false}
- }
- return Result{OK: true}
- }
-
- r.OK = true
- return r
-}
-
-// Get returns the Result if OK, empty Result otherwise.
-//
-// r := core.Result{Value: "hello", OK: true}.Get()
-func (r Result) Get() Result {
- if r.OK {
- return r
- }
- return Result{Value: r.Value, OK: false}
-}
-
-// Option is a single key-value configuration pair.
-//
-// core.Option{Key: "name", Value: "brain"}
-// core.Option{Key: "port", Value: 8080}
-type Option struct {
- Key string
- Value any
-}
-
-// --- Options: Universal Input ---
-
-// Options is the universal input type for Core operations.
-// A structured collection of key-value pairs with typed accessors.
-//
-// opts := core.NewOptions(
-// core.Option{Key: "name", Value: "myapp"},
-// core.Option{Key: "port", Value: 8080},
-// )
-// name := opts.String("name")
-type Options struct {
- items []Option
-}
-
-// NewOptions creates an Options collection from key-value pairs.
-//
-// opts := core.NewOptions(
-// core.Option{Key: "name", Value: "brain"},
-// core.Option{Key: `path`, Value: "prompts"},
-// )
-func NewOptions(items ...Option) Options {
- cp := make([]Option, len(items))
- copy(cp, items)
- return Options{items: cp}
-}
-
-// Set adds or updates a key-value pair.
-//
-// opts.Set("port", 8080)
-func (o *Options) Set(key string, value any) {
- for i, opt := range o.items {
- if opt.Key == key {
- o.items[i].Value = value
- return
- }
- }
- o.items = append(o.items, Option{Key: key, Value: value})
-}
-
-// Get retrieves a value by key.
-//
-// r := opts.Get("name")
-// if r.OK { name := r.Value.(string) }
-func (o Options) Get(key string) Result {
- for _, opt := range o.items {
- if opt.Key == key {
- return Result{opt.Value, true}
- }
- }
- return Result{}
-}
-
-// Has returns true if a key exists.
-//
-// if opts.Has("debug") { ... }
-func (o Options) Has(key string) bool {
- return o.Get(key).OK
-}
-
-// String retrieves a string value, empty string if missing.
-//
-// name := opts.String("name")
-func (o Options) String(key string) string {
- r := o.Get(key)
- if !r.OK {
- return ""
- }
- s, _ := r.Value.(string)
- return s
-}
-
-// Int retrieves an int value, 0 if missing.
-//
-// port := opts.Int("port")
-func (o Options) Int(key string) int {
- r := o.Get(key)
- if !r.OK {
- return 0
- }
- i, _ := r.Value.(int)
- return i
-}
-
-// Bool retrieves a bool value, false if missing.
-//
-// debug := opts.Bool("debug")
-func (o Options) Bool(key string) bool {
- r := o.Get(key)
- if !r.OK {
- return false
- }
- b, _ := r.Value.(bool)
- return b
-}
-
-// Len returns the number of options.
-func (o Options) Len() int {
- return len(o.items)
-}
-
-// Items returns a copy of the underlying option slice.
-func (o Options) Items() []Option {
- cp := make([]Option, len(o.items))
- copy(cp, o.items)
- return cp
-}
diff --git a/pkg/lib/workspace/default/.core/reference/runtime.go b/pkg/lib/workspace/default/.core/reference/runtime.go
deleted file mode 100644
index 9074a39c..00000000
--- a/pkg/lib/workspace/default/.core/reference/runtime.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Runtime helpers for the Core framework.
-// ServiceRuntime is embedded by consumer services.
-// Runtime is the GUI binding container (e.g., Wails).
-//
-// r := c.ServiceStartup(context.Background(), nil)
-// r := core.NewRuntime(app)
-// name := runtime.ServiceName()
-
-package core
-
-import (
- "context"
- "maps"
- "slices"
-)
-
-// --- ServiceRuntime (embedded by consumer services) ---
-
-// ServiceRuntime is embedded in services to provide access to the Core and typed options.
-type ServiceRuntime[T any] struct {
- core *Core
- opts T
-}
-
-// NewServiceRuntime creates a ServiceRuntime for a service constructor.
-func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T] {
- return &ServiceRuntime[T]{core: c, opts: opts}
-}
-
-// Core returns the Core instance this service is registered with.
-//
-// c := s.Core()
-func (r *ServiceRuntime[T]) Core() *Core { return r.core }
-
-// Options returns the typed options this service was created with.
-//
-// opts := s.Options() // MyOptions{BufferSize: 1024, ...}
-func (r *ServiceRuntime[T]) Options() T { return r.opts }
-
-// Config is a shortcut to s.Core().Config().
-//
-// host := s.Config().String("database.host")
-func (r *ServiceRuntime[T]) Config() *Config { return r.core.Config() }
-
-// --- Lifecycle ---
-
-// ServiceStartup runs OnStart for all registered services that have one.
-func (c *Core) ServiceStartup(ctx context.Context, options any) Result {
- c.shutdown.Store(false)
- c.context, c.cancel = context.WithCancel(ctx)
- startables := c.Startables()
- if startables.OK {
- for _, s := range startables.Value.([]*Service) {
- if err := ctx.Err(); err != nil {
- return Result{err, false}
- }
- r := s.OnStart()
- if !r.OK {
- return r
- }
- }
- }
- c.ACTION(ActionServiceStartup{})
- return Result{OK: true}
-}
-
-// ServiceShutdown drains background tasks, then stops all registered services.
-func (c *Core) ServiceShutdown(ctx context.Context) Result {
- c.shutdown.Store(true)
- c.cancel() // signal all context-aware tasks to stop
- c.ACTION(ActionServiceShutdown{})
-
- // Drain background tasks before stopping services.
- done := make(chan struct{})
- go func() {
- c.waitGroup.Wait()
- close(done)
- }()
- select {
- case <-done:
- case <-ctx.Done():
- return Result{ctx.Err(), false}
- }
-
- // Stop services
- var firstErr error
- stoppables := c.Stoppables()
- if stoppables.OK {
- for _, s := range stoppables.Value.([]*Service) {
- if err := ctx.Err(); err != nil {
- return Result{err, false}
- }
- r := s.OnStop()
- if !r.OK && firstErr == nil {
- if e, ok := r.Value.(error); ok {
- firstErr = e
- } else {
- firstErr = E("core.ServiceShutdown", Sprint("service OnStop failed: ", r.Value), nil)
- }
- }
- }
- }
- if firstErr != nil {
- return Result{firstErr, false}
- }
- return Result{OK: true}
-}
-
-// --- Runtime DTO (GUI binding) ---
-
-// Runtime is the container for GUI runtimes (e.g., Wails).
-type Runtime struct {
- app any
- Core *Core
-}
-
-// ServiceFactory defines a function that creates a Service.
-type ServiceFactory func() Result
-
-// NewWithFactories creates a Runtime with the provided service factories.
-func NewWithFactories(app any, factories map[string]ServiceFactory) Result {
- c := New(WithOptions(NewOptions(Option{Key: "name", Value: "core"})))
- c.app.Runtime = app
-
- names := slices.Sorted(maps.Keys(factories))
- for _, name := range names {
- factory := factories[name]
- if factory == nil {
- continue
- }
- r := factory()
- if !r.OK {
- cause, _ := r.Value.(error)
- return Result{E("core.NewWithFactories", Concat("factory \"", name, "\" failed"), cause), false}
- }
- svc, ok := r.Value.(Service)
- if !ok {
- return Result{E("core.NewWithFactories", Concat("factory \"", name, "\" returned non-Service type"), nil), false}
- }
- sr := c.Service(name, svc)
- if !sr.OK {
- return sr
- }
- }
- return Result{&Runtime{app: app, Core: c}, true}
-}
-
-// NewRuntime creates a Runtime with no custom services.
-func NewRuntime(app any) Result {
- return NewWithFactories(app, map[string]ServiceFactory{})
-}
-
-// ServiceName returns "Core" — the Runtime's service identity.
-func (r *Runtime) ServiceName() string { return "Core" }
-
-// ServiceStartup starts all services via the embedded Core.
-func (r *Runtime) ServiceStartup(ctx context.Context, options any) Result {
- if r == nil || r.Core == nil {
- return Result{OK: true}
- }
- return r.Core.ServiceStartup(ctx, options)
-}
-
-// ServiceShutdown stops all services via the embedded Core.
-func (r *Runtime) ServiceShutdown(ctx context.Context) Result {
- if r == nil || r.Core == nil {
- return Result{OK: true}
- }
- return r.Core.ServiceShutdown(ctx)
-}
diff --git a/pkg/lib/workspace/default/.core/reference/service.go b/pkg/lib/workspace/default/.core/reference/service.go
deleted file mode 100644
index 46738add..00000000
--- a/pkg/lib/workspace/default/.core/reference/service.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Service registry for the Core framework.
-//
-// Register a service (DTO with lifecycle hooks):
-//
-// c.Service("auth", core.Service{OnStart: startFn})
-//
-// Register a service instance (auto-discovers Startable/Stoppable/HandleIPCEvents):
-//
-// c.RegisterService("display", displayInstance)
-//
-// Get a service:
-//
-// r := c.Service("auth")
-// if r.OK { svc := r.Value }
-
-package core
-
-import "context"
-
-// Service is a managed component with optional lifecycle.
-type Service struct {
- Name string
- Instance any // the raw service instance (for interface discovery)
- Options Options
- OnStart func() Result
- OnStop func() Result
- OnReload func() Result
-}
-
-// ServiceRegistry holds registered services. Embeds Registry[*Service]
-// for thread-safe named storage with insertion order.
-type ServiceRegistry struct {
- *Registry[*Service]
- lockEnabled bool
-}
-
-// --- Core service methods ---
-
-// Service gets or registers a service by name.
-//
-// c.Service("auth", core.Service{OnStart: startFn})
-// r := c.Service("auth")
-func (c *Core) Service(name string, service ...Service) Result {
- if len(service) == 0 {
- r := c.services.Get(name)
- if !r.OK {
- return Result{}
- }
- svc := r.Value.(*Service)
- // Return the instance if available, otherwise the Service DTO
- if svc.Instance != nil {
- return Result{svc.Instance, true}
- }
- return Result{svc, true}
- }
-
- if name == "" {
- return Result{E("core.Service", "service name cannot be empty", nil), false}
- }
-
- if c.services.Locked() {
- return Result{E("core.Service", Concat("service \"", name, "\" not permitted — registry locked"), nil), false}
- }
- if c.services.Has(name) {
- return Result{E("core.Service", Join(" ", "service", name, "already registered"), nil), false}
- }
-
- srv := &service[0]
- srv.Name = name
- return c.services.Set(name, srv)
-}
-
-// RegisterService registers a service instance by name.
-// Auto-discovers Startable, Stoppable, and HandleIPCEvents interfaces
-// on the instance and wires them into the lifecycle and IPC bus.
-//
-// c.RegisterService("display", displayInstance)
-func (c *Core) RegisterService(name string, instance any) Result {
- if name == "" {
- return Result{E("core.RegisterService", "service name cannot be empty", nil), false}
- }
-
- if c.services.Locked() {
- return Result{E("core.RegisterService", Concat("service \"", name, "\" not permitted — registry locked"), nil), false}
- }
- if c.services.Has(name) {
- return Result{E("core.RegisterService", Join(" ", "service", name, "already registered"), nil), false}
- }
-
- srv := &Service{Name: name, Instance: instance}
-
- // Auto-discover lifecycle interfaces
- if s, ok := instance.(Startable); ok {
- srv.OnStart = func() Result {
- return s.OnStartup(c.context)
- }
- }
- if s, ok := instance.(Stoppable); ok {
- srv.OnStop = func() Result {
- return s.OnShutdown(context.Background())
- }
- }
-
- c.services.Set(name, srv)
-
- // Auto-discover IPC handler
- if handler, ok := instance.(interface {
- HandleIPCEvents(*Core, Message) Result
- }); ok {
- c.ipc.ipcMu.Lock()
- c.ipc.ipcHandlers = append(c.ipc.ipcHandlers, handler.HandleIPCEvents)
- c.ipc.ipcMu.Unlock()
- }
-
- return Result{OK: true}
-}
-
-// ServiceFor retrieves a registered service by name and asserts its type.
-//
-// prep, ok := core.ServiceFor[*agentic.PrepSubsystem](c, "agentic")
-func ServiceFor[T any](c *Core, name string) (T, bool) {
- var zero T
- r := c.Service(name)
- if !r.OK {
- return zero, false
- }
- typed, ok := r.Value.(T)
- return typed, ok
-}
-
-// MustServiceFor retrieves a registered service by name and asserts its type.
-// Panics if the service is not found or the type assertion fails.
-//
-// cli := core.MustServiceFor[*Cli](c, "cli")
-func MustServiceFor[T any](c *Core, name string) T {
- v, ok := ServiceFor[T](c, name)
- if !ok {
- panic(E("core.MustServiceFor", Sprintf("service %q not found or wrong type", name), nil))
- }
- return v
-}
-
-// Services returns all registered service names in registration order.
-//
-// names := c.Services()
-func (c *Core) Services() []string {
- if c.services == nil {
- return nil
- }
- return c.services.Names()
-}
diff --git a/pkg/lib/workspace/default/.core/reference/string.go b/pkg/lib/workspace/default/.core/reference/string.go
deleted file mode 100644
index fb4f3e5a..00000000
--- a/pkg/lib/workspace/default/.core/reference/string.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// String operations for the Core framework.
-// Provides safe, predictable string helpers that downstream packages
-// use directly — same pattern as Array[T] for slices.
-
-package core
-
-import (
- corefmt "dappco.re/go"
- corestrings "dappco.re/go"
- "unicode/utf8"
-)
-
-// HasPrefix returns true if s starts with prefix.
-//
-// core.HasPrefix("--verbose", "--") // true
-func HasPrefix(s, prefix string) bool {
- return strings.HasPrefix(s, prefix)
-}
-
-// HasSuffix returns true if s ends with suffix.
-//
-// core.HasSuffix("test.go", ".go") // true
-func HasSuffix(s, suffix string) bool {
- return strings.HasSuffix(s, suffix)
-}
-
-// TrimPrefix removes prefix from s.
-//
-// core.TrimPrefix("--verbose", "--") // "verbose"
-func TrimPrefix(s, prefix string) string {
- return strings.TrimPrefix(s, prefix)
-}
-
-// TrimSuffix removes suffix from s.
-//
-// core.TrimSuffix("test.go", ".go") // "test"
-func TrimSuffix(s, suffix string) string {
- return strings.TrimSuffix(s, suffix)
-}
-
-// Contains returns true if s contains substr.
-//
-// core.Contains("hello world", "world") // true
-func Contains(s, substr string) bool {
- return strings.Contains(s, substr)
-}
-
-// Split splits s by separator.
-//
-// core.Split("a/b/c", "/") // ["a", "b", "c"]
-func Split(s, sep string) []string {
- return strings.Split(s, sep)
-}
-
-// SplitN splits s by separator into at most n parts.
-//
-// core.SplitN("key=value=extra", "=", 2) // ["key", "value=extra"]
-func SplitN(s, sep string, n int) []string {
- return strings.SplitN(s, sep, n)
-}
-
-// Join joins parts with a separator, building via Concat.
-//
-// core.Join("/", "deploy", "to", "homelab") // "deploy/to/homelab"
-// core.Join(".", "cmd", "deploy", "description") // "cmd.deploy.description"
-func Join(sep string, parts ...string) string {
- if len(parts) == 0 {
- return ""
- }
- result := parts[0]
- for _, p := range parts[1:] {
- result = Concat(result, sep, p)
- }
- return result
-}
-
-// Replace replaces all occurrences of old with new in s.
-//
-// core.Replace("deploy/to/homelab", "/", ".") // "deploy.to.homelab"
-func Replace(s, old, new string) string {
- return strings.ReplaceAll(s, old, new)
-}
-
-// Lower returns s in lowercase.
-//
-// core.Lower("HELLO") // "hello"
-func Lower(s string) string {
- return strings.ToLower(s)
-}
-
-// Upper returns s in uppercase.
-//
-// core.Upper("hello") // "HELLO"
-func Upper(s string) string {
- return strings.ToUpper(s)
-}
-
-// Trim removes leading and trailing whitespace.
-//
-// core.Trim(" hello ") // "hello"
-func Trim(s string) string {
- return strings.TrimSpace(s)
-}
-
-// RuneCount returns the number of runes (unicode characters) in s.
-//
-// core.RuneCount("hello") // 5
-// core.RuneCount("🔥") // 1
-func RuneCount(s string) int {
- return utf8.RuneCountInString(s)
-}
-
-// NewBuilder returns a new strings.Builder.
-//
-// b := core.NewBuilder()
-// b.WriteString("hello")
-// b.String() // "hello"
-func NewBuilder() *strings.Builder {
- return &strings.Builder{}
-}
-
-// NewReader returns a strings.NewReader for the given string.
-//
-// r := core.NewReader("hello world")
-func NewReader(s string) *strings.Reader {
- return strings.NewReader(s)
-}
-
-// Sprint converts any value to its string representation.
-//
-// core.Sprint(42) // "42"
-// core.Sprint(err) // "connection refused"
-func Sprint(args ...any) string {
- return fmt.Sprint(args...)
-}
-
-// Sprintf formats a string with the given arguments.
-//
-// core.Sprintf("%v=%q", "key", "value") // `key="value"`
-func Sprintf(format string, args ...any) string {
- return fmt.Sprintf(format, args...)
-}
-
-// Concat joins variadic string parts into one string.
-// Hook point for validation, sanitisation, and security checks.
-//
-// core.Concat("cmd.", "deploy.to.homelab", ".description")
-// core.Concat("https://", host, "/api/v1")
-func Concat(parts ...string) string {
- b := NewBuilder()
- for _, p := range parts {
- b.WriteString(p)
- }
- return b.String()
-}
diff --git a/pkg/lib/workspace/default/.core/reference/task.go b/pkg/lib/workspace/default/.core/reference/task.go
deleted file mode 100644
index b761f9d0..00000000
--- a/pkg/lib/workspace/default/.core/reference/task.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Background action dispatch for the Core framework.
-// PerformAsync runs a named Action in a background goroutine with
-// panic recovery and progress broadcasting.
-
-package core
-
-import "context"
-
-// PerformAsync dispatches a named action in a background goroutine.
-// Broadcasts ActionTaskStarted, ActionTaskProgress, and ActionTaskCompleted
-// as IPC messages so other services can track progress.
-//
-// r := c.PerformAsync("agentic.dispatch", opts)
-// taskID := r.Value.(string)
-func (c *Core) PerformAsync(action string, opts Options) Result {
- if c.shutdown.Load() {
- return Result{}
- }
- taskID := ID()
-
- c.ACTION(ActionTaskStarted{TaskIdentifier: taskID, Action: action, Options: opts})
-
- c.waitGroup.Go(func() {
- defer func() {
- if rec := recover(); rec != nil {
- c.ACTION(ActionTaskCompleted{
- TaskIdentifier: taskID,
- Action: action,
- Result: Result{E("core.PerformAsync", Sprint("panic: ", rec), nil), false},
- })
- }
- }()
-
- r := c.Action(action).Run(context.Background(), opts)
-
- c.ACTION(ActionTaskCompleted{
- TaskIdentifier: taskID,
- Action: action,
- Result: r,
- })
- })
-
- return Result{taskID, true}
-}
-
-// Progress broadcasts a progress update for a background task.
-//
-// c.Progress(taskID, 0.5, "halfway done", "agentic.dispatch")
-func (c *Core) Progress(taskID string, progress float64, message string, action string) {
- c.ACTION(ActionTaskProgress{
- TaskIdentifier: taskID,
- Action: action,
- Progress: progress,
- Message: message,
- })
-}
-
-// Registration methods (RegisterAction, RegisterActions)
-// are in ipc.go — registration is IPC's responsibility.
diff --git a/pkg/lib/workspace/default/.core/reference/utils.go b/pkg/lib/workspace/default/.core/reference/utils.go
deleted file mode 100644
index ff8f2629..00000000
--- a/pkg/lib/workspace/default/.core/reference/utils.go
+++ /dev/null
@@ -1,223 +0,0 @@
-// SPDX-License-Identifier: EUPL-1.2
-
-// Utility functions for the Core framework.
-// Built on core string.go primitives.
-
-package core
-
-import (
- crand "crypto/rand"
- corefmt "dappco.re/go"
- coreos "dappco.re/go"
- "encoding/hex"
- "io"
- "strconv"
- "sync/atomic"
-)
-
-// --- ID Generation ---
-
-var idCounter atomic.Uint64
-
-// ID returns a unique identifier. Format: "id-{counter}-{random}".
-// Counter is process-wide atomic. Random suffix prevents collision across restarts.
-//
-// id := core.ID() // "id-1-a3f2b1"
-// id2 := core.ID() // "id-2-c7e4d9"
-func ID() string {
- return Concat("id-", strconv.FormatUint(idCounter.Add(1), 10), "-", shortRand())
-}
-
-func shortRand() string {
- b := make([]byte, 3)
- crand.Read(b)
- return hex.EncodeToString(b)
-}
-
-// --- Validation ---
-
-// ValidateName checks that a string is a valid service/action/command name.
-// Rejects empty, ".", "..", and names containing path separators.
-//
-// r := core.ValidateName("brain") // Result{"brain", true}
-// r := core.ValidateName("") // Result{error, false}
-// r := core.ValidateName("../escape") // Result{error, false}
-func ValidateName(name string) Result {
- if name == "" || name == "." || name == ".." {
- return Result{E("validate", Concat("invalid name: ", name), nil), false}
- }
- if Contains(name, "/") || Contains(name, "\\") {
- return Result{E("validate", Concat("name contains path separator: ", name), nil), false}
- }
- return Result{name, true}
-}
-
-// SanitisePath extracts the base filename and rejects traversal attempts.
-// Returns "invalid" for dangerous inputs.
-//
-// core.SanitisePath("../../etc/passwd") // "passwd"
-// core.SanitisePath("") // "invalid"
-// core.SanitisePath("..") // "invalid"
-func SanitisePath(path string) string {
- safe := PathBase(path)
- if safe == "." || safe == ".." || safe == "" {
- return "invalid"
- }
- return safe
-}
-
-// --- I/O ---
-
-// Println prints values to stdout with a newline. Replaces fmt.Println.
-//
-// core.Println("hello", 42, true)
-func Println(args ...any) {
- fmt.Println(args...)
-}
-
-// Print writes a formatted line to a writer, defaulting to os.Stdout.
-//
-// core.Print(nil, "hello %s", "world") // → stdout
-// core.Print(w, "port: %d", 8080) // → w
-func Print(w io.Writer, format string, args ...any) {
- if w == nil {
- w = os.Stdout
- }
- fmt.Fprintf(w, format+"\n", args...)
-}
-
-// JoinPath joins string segments into a path with "/" separator.
-//
-// core.JoinPath("deploy", "to", "homelab") // → "deploy/to/homelab"
-func JoinPath(segments ...string) string {
- return Join("/", segments...)
-}
-
-// IsFlag returns true if the argument starts with a dash.
-//
-// core.IsFlag("--verbose") // true
-// core.IsFlag("-v") // true
-// core.IsFlag("deploy") // false
-func IsFlag(arg string) bool {
- return HasPrefix(arg, "-")
-}
-
-// Arg extracts a value from variadic args at the given index.
-// Type-checks and delegates to the appropriate typed extractor.
-// Returns Result — OK is false if index is out of bounds.
-//
-// r := core.Arg(0, args...)
-// if r.OK { path = r.Value.(string) }
-func Arg(index int, args ...any) Result {
- if index >= len(args) {
- return Result{}
- }
- v := args[index]
- switch v.(type) {
- case string:
- return Result{ArgString(index, args...), true}
- case int:
- return Result{ArgInt(index, args...), true}
- case bool:
- return Result{ArgBool(index, args...), true}
- default:
- return Result{v, true}
- }
-}
-
-// ArgString extracts a string at the given index.
-//
-// name := core.ArgString(0, args...)
-func ArgString(index int, args ...any) string {
- if index >= len(args) {
- return ""
- }
- s, ok := args[index].(string)
- if !ok {
- return ""
- }
- return s
-}
-
-// ArgInt extracts an int at the given index.
-//
-// port := core.ArgInt(1, args...)
-func ArgInt(index int, args ...any) int {
- if index >= len(args) {
- return 0
- }
- i, ok := args[index].(int)
- if !ok {
- return 0
- }
- return i
-}
-
-// ArgBool extracts a bool at the given index.
-//
-// debug := core.ArgBool(2, args...)
-func ArgBool(index int, args ...any) bool {
- if index >= len(args) {
- return false
- }
- b, ok := args[index].(bool)
- if !ok {
- return false
- }
- return b
-}
-
-// FilterArgs removes empty strings and Go test runner flags from an argument list.
-//
-// clean := core.FilterArgs(os.Args[1:])
-func FilterArgs(args []string) []string {
- var clean []string
- for _, a := range args {
- if a == "" || HasPrefix(a, "-test.") {
- continue
- }
- clean = append(clean, a)
- }
- return clean
-}
-
-// ParseFlag parses a single flag argument into key, value, and validity.
-// Single dash (-) requires exactly 1 character (letter, emoji, unicode).
-// Double dash (--) requires 2+ characters.
-//
-// "-v" → "v", "", true
-// "-🔥" → "🔥", "", true
-// "--verbose" → "verbose", "", true
-// "--port=8080" → "port", "8080", true
-// "-verbose" → "", "", false (single dash, 2+ chars)
-// "--v" → "", "", false (double dash, 1 char)
-// "hello" → "", "", false (not a flag)
-func ParseFlag(arg string) (key, value string, valid bool) {
- if HasPrefix(arg, "--") {
- rest := TrimPrefix(arg, "--")
- parts := SplitN(rest, "=", 2)
- name := parts[0]
- if RuneCount(name) < 2 {
- return "", "", false
- }
- if len(parts) == 2 {
- return name, parts[1], true
- }
- return name, "", true
- }
-
- if HasPrefix(arg, "-") {
- rest := TrimPrefix(arg, "-")
- parts := SplitN(rest, "=", 2)
- name := parts[0]
- if RuneCount(name) != 1 {
- return "", "", false
- }
- if len(parts) == 2 {
- return name, parts[1], true
- }
- return name, "", true
- }
-
- return "", "", false
-}
diff --git a/claude/camofox_mcp/README.md b/provider/claude/camofox_mcp/README.md
similarity index 100%
rename from claude/camofox_mcp/README.md
rename to provider/claude/camofox_mcp/README.md
diff --git a/claude/camofox_mcp/__init__.py b/provider/claude/camofox_mcp/__init__.py
similarity index 100%
rename from claude/camofox_mcp/__init__.py
rename to provider/claude/camofox_mcp/__init__.py
diff --git a/claude/camofox_mcp/pyproject.toml b/provider/claude/camofox_mcp/pyproject.toml
similarity index 100%
rename from claude/camofox_mcp/pyproject.toml
rename to provider/claude/camofox_mcp/pyproject.toml
diff --git a/claude/camofox_mcp/server.py b/provider/claude/camofox_mcp/server.py
similarity index 100%
rename from claude/camofox_mcp/server.py
rename to provider/claude/camofox_mcp/server.py
diff --git a/claude/camofox_mcp/tests/__init__.py b/provider/claude/camofox_mcp/tests/__init__.py
similarity index 100%
rename from claude/camofox_mcp/tests/__init__.py
rename to provider/claude/camofox_mcp/tests/__init__.py
diff --git a/claude/camofox_mcp/tests/test_server.py b/provider/claude/camofox_mcp/tests/test_server.py
similarity index 100%
rename from claude/camofox_mcp/tests/test_server.py
rename to provider/claude/camofox_mcp/tests/test_server.py
diff --git a/claude/core-go/.claude-plugin/plugin.json b/provider/claude/core-go/.claude-plugin/plugin.json
similarity index 100%
rename from claude/core-go/.claude-plugin/plugin.json
rename to provider/claude/core-go/.claude-plugin/plugin.json
diff --git a/claude/core-go/README.md b/provider/claude/core-go/README.md
similarity index 100%
rename from claude/core-go/README.md
rename to provider/claude/core-go/README.md
diff --git a/claude/core-go/agents/README.md b/provider/claude/core-go/agents/README.md
similarity index 100%
rename from claude/core-go/agents/README.md
rename to provider/claude/core-go/agents/README.md
diff --git a/claude/core-go/commands/README.md b/provider/claude/core-go/commands/README.md
similarity index 100%
rename from claude/core-go/commands/README.md
rename to provider/claude/core-go/commands/README.md
diff --git a/claude/core-go/marketplace.yaml b/provider/claude/core-go/marketplace.yaml
similarity index 100%
rename from claude/core-go/marketplace.yaml
rename to provider/claude/core-go/marketplace.yaml
diff --git a/claude/core-go/skills/README.md b/provider/claude/core-go/skills/README.md
similarity index 100%
rename from claude/core-go/skills/README.md
rename to provider/claude/core-go/skills/README.md
diff --git a/claude/core-php/.claude-plugin/plugin.json b/provider/claude/core-php/.claude-plugin/plugin.json
similarity index 100%
rename from claude/core-php/.claude-plugin/plugin.json
rename to provider/claude/core-php/.claude-plugin/plugin.json
diff --git a/claude/core-php/README.md b/provider/claude/core-php/README.md
similarity index 100%
rename from claude/core-php/README.md
rename to provider/claude/core-php/README.md
diff --git a/claude/core-php/agents/README.md b/provider/claude/core-php/agents/README.md
similarity index 100%
rename from claude/core-php/agents/README.md
rename to provider/claude/core-php/agents/README.md
diff --git a/claude/core-php/commands/README.md b/provider/claude/core-php/commands/README.md
similarity index 100%
rename from claude/core-php/commands/README.md
rename to provider/claude/core-php/commands/README.md
diff --git a/claude/core-php/marketplace.yaml b/provider/claude/core-php/marketplace.yaml
similarity index 100%
rename from claude/core-php/marketplace.yaml
rename to provider/claude/core-php/marketplace.yaml
diff --git a/claude/core-php/skills/README.md b/provider/claude/core-php/skills/README.md
similarity index 100%
rename from claude/core-php/skills/README.md
rename to provider/claude/core-php/skills/README.md
diff --git a/claude/core/.claude-plugin/plugin.json b/provider/claude/core/.claude-plugin/plugin.json
similarity index 100%
rename from claude/core/.claude-plugin/plugin.json
rename to provider/claude/core/.claude-plugin/plugin.json
diff --git a/claude/core/.mcp.json b/provider/claude/core/.mcp.json
similarity index 100%
rename from claude/core/.mcp.json
rename to provider/claude/core/.mcp.json
diff --git a/claude/core/000.mcp.json b/provider/claude/core/000.mcp.json
similarity index 100%
rename from claude/core/000.mcp.json
rename to provider/claude/core/000.mcp.json
diff --git a/claude/core/000mcp.json b/provider/claude/core/000mcp.json
similarity index 100%
rename from claude/core/000mcp.json
rename to provider/claude/core/000mcp.json
diff --git a/claude/core/agents/agent-task-code-review.md b/provider/claude/core/agents/agent-task-code-review.md
similarity index 100%
rename from claude/core/agents/agent-task-code-review.md
rename to provider/claude/core/agents/agent-task-code-review.md
diff --git a/claude/core/agents/agent-task-code-simplifier.md b/provider/claude/core/agents/agent-task-code-simplifier.md
similarity index 100%
rename from claude/core/agents/agent-task-code-simplifier.md
rename to provider/claude/core/agents/agent-task-code-simplifier.md
diff --git a/claude/core/commands/code-review.md b/provider/claude/core/commands/code-review.md
similarity index 100%
rename from claude/core/commands/code-review.md
rename to provider/claude/core/commands/code-review.md
diff --git a/claude/core/commands/dispatch.md b/provider/claude/core/commands/dispatch.md
similarity index 100%
rename from claude/core/commands/dispatch.md
rename to provider/claude/core/commands/dispatch.md
diff --git a/claude/core/commands/pipeline.md b/provider/claude/core/commands/pipeline.md
similarity index 100%
rename from claude/core/commands/pipeline.md
rename to provider/claude/core/commands/pipeline.md
diff --git a/claude/core/commands/ready.md b/provider/claude/core/commands/ready.md
similarity index 100%
rename from claude/core/commands/ready.md
rename to provider/claude/core/commands/ready.md
diff --git a/claude/core/commands/recall.md b/provider/claude/core/commands/recall.md
similarity index 100%
rename from claude/core/commands/recall.md
rename to provider/claude/core/commands/recall.md
diff --git a/claude/core/commands/remember.md b/provider/claude/core/commands/remember.md
similarity index 100%
rename from claude/core/commands/remember.md
rename to provider/claude/core/commands/remember.md
diff --git a/claude/core/commands/review-pr.md b/provider/claude/core/commands/review-pr.md
similarity index 100%
rename from claude/core/commands/review-pr.md
rename to provider/claude/core/commands/review-pr.md
diff --git a/claude/core/commands/review.md b/provider/claude/core/commands/review.md
similarity index 100%
rename from claude/core/commands/review.md
rename to provider/claude/core/commands/review.md
diff --git a/claude/core/commands/scan.md b/provider/claude/core/commands/scan.md
similarity index 100%
rename from claude/core/commands/scan.md
rename to provider/claude/core/commands/scan.md
diff --git a/claude/core/commands/security.md b/provider/claude/core/commands/security.md
similarity index 100%
rename from claude/core/commands/security.md
rename to provider/claude/core/commands/security.md
diff --git a/claude/core/commands/status.md b/provider/claude/core/commands/status.md
similarity index 100%
rename from claude/core/commands/status.md
rename to provider/claude/core/commands/status.md
diff --git a/claude/core/commands/sweep.md b/provider/claude/core/commands/sweep.md
similarity index 100%
rename from claude/core/commands/sweep.md
rename to provider/claude/core/commands/sweep.md
diff --git a/claude/core/commands/tests.md b/provider/claude/core/commands/tests.md
similarity index 100%
rename from claude/core/commands/tests.md
rename to provider/claude/core/commands/tests.md
diff --git a/claude/core/commands/verify.md b/provider/claude/core/commands/verify.md
similarity index 100%
rename from claude/core/commands/verify.md
rename to provider/claude/core/commands/verify.md
diff --git a/claude/core/commands/yes.md b/provider/claude/core/commands/yes.md
similarity index 100%
rename from claude/core/commands/yes.md
rename to provider/claude/core/commands/yes.md
diff --git a/claude/core/hooks.json b/provider/claude/core/hooks.json
similarity index 100%
rename from claude/core/hooks.json
rename to provider/claude/core/hooks.json
diff --git a/claude/core/scripts/auto-approve.sh b/provider/claude/core/scripts/auto-approve.sh
similarity index 100%
rename from claude/core/scripts/auto-approve.sh
rename to provider/claude/core/scripts/auto-approve.sh
diff --git a/claude/core/scripts/check-completions.sh b/provider/claude/core/scripts/check-completions.sh
similarity index 100%
rename from claude/core/scripts/check-completions.sh
rename to provider/claude/core/scripts/check-completions.sh
diff --git a/claude/core/scripts/check-debug.sh b/provider/claude/core/scripts/check-debug.sh
similarity index 100%
rename from claude/core/scripts/check-debug.sh
rename to provider/claude/core/scripts/check-debug.sh
diff --git a/claude/core/scripts/check-inbox.sh b/provider/claude/core/scripts/check-inbox.sh
similarity index 100%
rename from claude/core/scripts/check-inbox.sh
rename to provider/claude/core/scripts/check-inbox.sh
diff --git a/claude/core/scripts/check-notify.sh b/provider/claude/core/scripts/check-notify.sh
similarity index 100%
rename from claude/core/scripts/check-notify.sh
rename to provider/claude/core/scripts/check-notify.sh
diff --git a/claude/core/scripts/ensure-commit.sh b/provider/claude/core/scripts/ensure-commit.sh
similarity index 100%
rename from claude/core/scripts/ensure-commit.sh
rename to provider/claude/core/scripts/ensure-commit.sh
diff --git a/claude/core/scripts/go-format.sh b/provider/claude/core/scripts/go-format.sh
similarity index 100%
rename from claude/core/scripts/go-format.sh
rename to provider/claude/core/scripts/go-format.sh
diff --git a/claude/core/scripts/local-dispatch.sh b/provider/claude/core/scripts/local-dispatch.sh
similarity index 100%
rename from claude/core/scripts/local-dispatch.sh
rename to provider/claude/core/scripts/local-dispatch.sh
diff --git a/claude/core/scripts/php-format.sh b/provider/claude/core/scripts/php-format.sh
similarity index 100%
rename from claude/core/scripts/php-format.sh
rename to provider/claude/core/scripts/php-format.sh
diff --git a/claude/core/scripts/post-pr-create.sh b/provider/claude/core/scripts/post-pr-create.sh
similarity index 100%
rename from claude/core/scripts/post-pr-create.sh
rename to provider/claude/core/scripts/post-pr-create.sh
diff --git a/claude/core/scripts/pre-compact.sh b/provider/claude/core/scripts/pre-compact.sh
similarity index 100%
rename from claude/core/scripts/pre-compact.sh
rename to provider/claude/core/scripts/pre-compact.sh
diff --git a/claude/core/scripts/pre-push-check.sh b/provider/claude/core/scripts/pre-push-check.sh
similarity index 100%
rename from claude/core/scripts/pre-push-check.sh
rename to provider/claude/core/scripts/pre-push-check.sh
diff --git a/claude/core/scripts/session-save.sh b/provider/claude/core/scripts/session-save.sh
similarity index 100%
rename from claude/core/scripts/session-save.sh
rename to provider/claude/core/scripts/session-save.sh
diff --git a/claude/core/scripts/session-start.sh b/provider/claude/core/scripts/session-start.sh
similarity index 100%
rename from claude/core/scripts/session-start.sh
rename to provider/claude/core/scripts/session-start.sh
diff --git a/claude/core/scripts/workspace-status.sh b/provider/claude/core/scripts/workspace-status.sh
similarity index 100%
rename from claude/core/scripts/workspace-status.sh
rename to provider/claude/core/scripts/workspace-status.sh
diff --git a/claude/core/skills/app-split/SKILL.md b/provider/claude/core/skills/app-split/SKILL.md
similarity index 100%
rename from claude/core/skills/app-split/SKILL.md
rename to provider/claude/core/skills/app-split/SKILL.md
diff --git a/claude/core/skills/app-split/references/module-classification.md b/provider/claude/core/skills/app-split/references/module-classification.md
similarity index 100%
rename from claude/core/skills/app-split/references/module-classification.md
rename to provider/claude/core/skills/app-split/references/module-classification.md
diff --git a/claude/core/skills/app-split/scripts/domain-scan.sh b/provider/claude/core/skills/app-split/scripts/domain-scan.sh
similarity index 100%
rename from claude/core/skills/app-split/scripts/domain-scan.sh
rename to provider/claude/core/skills/app-split/scripts/domain-scan.sh
diff --git a/claude/core/skills/app-split/scripts/inventory.sh b/provider/claude/core/skills/app-split/scripts/inventory.sh
similarity index 100%
rename from claude/core/skills/app-split/scripts/inventory.sh
rename to provider/claude/core/skills/app-split/scripts/inventory.sh
diff --git a/claude/core/skills/architecture-review.md b/provider/claude/core/skills/architecture-review.md
similarity index 100%
rename from claude/core/skills/architecture-review.md
rename to provider/claude/core/skills/architecture-review.md
diff --git a/claude/core/skills/deploy-homelab/SKILL.md b/provider/claude/core/skills/deploy-homelab/SKILL.md
similarity index 100%
rename from claude/core/skills/deploy-homelab/SKILL.md
rename to provider/claude/core/skills/deploy-homelab/SKILL.md
diff --git a/claude/core/skills/deploy-homelab/references/environments.md b/provider/claude/core/skills/deploy-homelab/references/environments.md
similarity index 100%
rename from claude/core/skills/deploy-homelab/references/environments.md
rename to provider/claude/core/skills/deploy-homelab/references/environments.md
diff --git a/claude/core/skills/deploy-homelab/scripts/build-and-ship.sh b/provider/claude/core/skills/deploy-homelab/scripts/build-and-ship.sh
similarity index 100%
rename from claude/core/skills/deploy-homelab/scripts/build-and-ship.sh
rename to provider/claude/core/skills/deploy-homelab/scripts/build-and-ship.sh
diff --git a/claude/core/skills/deploy-production/SKILL.md b/provider/claude/core/skills/deploy-production/SKILL.md
similarity index 100%
rename from claude/core/skills/deploy-production/SKILL.md
rename to provider/claude/core/skills/deploy-production/SKILL.md
diff --git a/claude/core/skills/orchestrate.md b/provider/claude/core/skills/orchestrate.md
similarity index 100%
rename from claude/core/skills/orchestrate.md
rename to provider/claude/core/skills/orchestrate.md
diff --git a/claude/core/skills/prompts.md b/provider/claude/core/skills/prompts.md
similarity index 100%
rename from claude/core/skills/prompts.md
rename to provider/claude/core/skills/prompts.md
diff --git a/claude/core/skills/reality-check.md b/provider/claude/core/skills/reality-check.md
similarity index 100%
rename from claude/core/skills/reality-check.md
rename to provider/claude/core/skills/reality-check.md
diff --git a/claude/core/skills/repo-sweep/SKILL.md b/provider/claude/core/skills/repo-sweep/SKILL.md
similarity index 100%
rename from claude/core/skills/repo-sweep/SKILL.md
rename to provider/claude/core/skills/repo-sweep/SKILL.md
diff --git a/claude/core/skills/repo-sweep/scripts/list-repos.sh b/provider/claude/core/skills/repo-sweep/scripts/list-repos.sh
similarity index 100%
rename from claude/core/skills/repo-sweep/scripts/list-repos.sh
rename to provider/claude/core/skills/repo-sweep/scripts/list-repos.sh
diff --git a/claude/core/skills/review-pipeline.md b/provider/claude/core/skills/review-pipeline.md
similarity index 100%
rename from claude/core/skills/review-pipeline.md
rename to provider/claude/core/skills/review-pipeline.md
diff --git a/claude/core/skills/security-review.md b/provider/claude/core/skills/security-review.md
similarity index 100%
rename from claude/core/skills/security-review.md
rename to provider/claude/core/skills/security-review.md
diff --git a/claude/core/skills/senior-dev-fix.md b/provider/claude/core/skills/senior-dev-fix.md
similarity index 100%
rename from claude/core/skills/senior-dev-fix.md
rename to provider/claude/core/skills/senior-dev-fix.md
diff --git a/claude/core/skills/test-analysis.md b/provider/claude/core/skills/test-analysis.md
similarity index 100%
rename from claude/core/skills/test-analysis.md
rename to provider/claude/core/skills/test-analysis.md
diff --git a/claude/devops/.claude-plugin/plugin.json b/provider/claude/devops/.claude-plugin/plugin.json
similarity index 100%
rename from claude/devops/.claude-plugin/plugin.json
rename to provider/claude/devops/.claude-plugin/plugin.json
diff --git a/claude/devops/agents/agent-task-clean-workspaces.md b/provider/claude/devops/agents/agent-task-clean-workspaces.md
similarity index 100%
rename from claude/devops/agents/agent-task-clean-workspaces.md
rename to provider/claude/devops/agents/agent-task-clean-workspaces.md
diff --git a/claude/devops/agents/agent-task-health-check.md b/provider/claude/devops/agents/agent-task-health-check.md
similarity index 100%
rename from claude/devops/agents/agent-task-health-check.md
rename to provider/claude/devops/agents/agent-task-health-check.md
diff --git a/claude/devops/agents/agent-task-install-core-agent.md b/provider/claude/devops/agents/agent-task-install-core-agent.md
similarity index 100%
rename from claude/devops/agents/agent-task-install-core-agent.md
rename to provider/claude/devops/agents/agent-task-install-core-agent.md
diff --git a/claude/devops/agents/agent-task-merge-workspace.md b/provider/claude/devops/agents/agent-task-merge-workspace.md
similarity index 100%
rename from claude/devops/agents/agent-task-merge-workspace.md
rename to provider/claude/devops/agents/agent-task-merge-workspace.md
diff --git a/claude/devops/agents/agent-task-repair-core-agent.md b/provider/claude/devops/agents/agent-task-repair-core-agent.md
similarity index 100%
rename from claude/devops/agents/agent-task-repair-core-agent.md
rename to provider/claude/devops/agents/agent-task-repair-core-agent.md
diff --git a/claude/devops/skills/build-prompt/SKILL.md b/provider/claude/devops/skills/build-prompt/SKILL.md
similarity index 100%
rename from claude/devops/skills/build-prompt/SKILL.md
rename to provider/claude/devops/skills/build-prompt/SKILL.md
diff --git a/claude/devops/skills/issue-comment/SKILL.md b/provider/claude/devops/skills/issue-comment/SKILL.md
similarity index 100%
rename from claude/devops/skills/issue-comment/SKILL.md
rename to provider/claude/devops/skills/issue-comment/SKILL.md
diff --git a/claude/devops/skills/issue-get/SKILL.md b/provider/claude/devops/skills/issue-get/SKILL.md
similarity index 100%
rename from claude/devops/skills/issue-get/SKILL.md
rename to provider/claude/devops/skills/issue-get/SKILL.md
diff --git a/claude/devops/skills/issue-list/SKILL.md b/provider/claude/devops/skills/issue-list/SKILL.md
similarity index 100%
rename from claude/devops/skills/issue-list/SKILL.md
rename to provider/claude/devops/skills/issue-list/SKILL.md
diff --git a/claude/devops/skills/pr-get/SKILL.md b/provider/claude/devops/skills/pr-get/SKILL.md
similarity index 100%
rename from claude/devops/skills/pr-get/SKILL.md
rename to provider/claude/devops/skills/pr-get/SKILL.md
diff --git a/claude/devops/skills/pr-list/SKILL.md b/provider/claude/devops/skills/pr-list/SKILL.md
similarity index 100%
rename from claude/devops/skills/pr-list/SKILL.md
rename to provider/claude/devops/skills/pr-list/SKILL.md
diff --git a/claude/devops/skills/pr-merge/SKILL.md b/provider/claude/devops/skills/pr-merge/SKILL.md
similarity index 100%
rename from claude/devops/skills/pr-merge/SKILL.md
rename to provider/claude/devops/skills/pr-merge/SKILL.md
diff --git a/claude/devops/skills/repo-get/SKILL.md b/provider/claude/devops/skills/repo-get/SKILL.md
similarity index 100%
rename from claude/devops/skills/repo-get/SKILL.md
rename to provider/claude/devops/skills/repo-get/SKILL.md
diff --git a/claude/devops/skills/repo-list/SKILL.md b/provider/claude/devops/skills/repo-list/SKILL.md
similarity index 100%
rename from claude/devops/skills/repo-list/SKILL.md
rename to provider/claude/devops/skills/repo-list/SKILL.md
diff --git a/claude/devops/skills/update-deps/SKILL.md b/provider/claude/devops/skills/update-deps/SKILL.md
similarity index 100%
rename from claude/devops/skills/update-deps/SKILL.md
rename to provider/claude/devops/skills/update-deps/SKILL.md
diff --git a/claude/devops/skills/workspace-clean/SKILL.md b/provider/claude/devops/skills/workspace-clean/SKILL.md
similarity index 100%
rename from claude/devops/skills/workspace-clean/SKILL.md
rename to provider/claude/devops/skills/workspace-clean/SKILL.md
diff --git a/claude/devops/skills/workspace-list/SKILL.md b/provider/claude/devops/skills/workspace-list/SKILL.md
similarity index 100%
rename from claude/devops/skills/workspace-list/SKILL.md
rename to provider/claude/devops/skills/workspace-list/SKILL.md
diff --git a/claude/hermes_runner_mcp/README.md b/provider/claude/hermes_runner_mcp/README.md
similarity index 100%
rename from claude/hermes_runner_mcp/README.md
rename to provider/claude/hermes_runner_mcp/README.md
diff --git a/claude/hermes_runner_mcp/__init__.py b/provider/claude/hermes_runner_mcp/__init__.py
similarity index 100%
rename from claude/hermes_runner_mcp/__init__.py
rename to provider/claude/hermes_runner_mcp/__init__.py
diff --git a/claude/hermes_runner_mcp/pyproject.toml b/provider/claude/hermes_runner_mcp/pyproject.toml
similarity index 100%
rename from claude/hermes_runner_mcp/pyproject.toml
rename to provider/claude/hermes_runner_mcp/pyproject.toml
diff --git a/claude/hermes_runner_mcp/server.py b/provider/claude/hermes_runner_mcp/server.py
similarity index 100%
rename from claude/hermes_runner_mcp/server.py
rename to provider/claude/hermes_runner_mcp/server.py
diff --git a/claude/hermes_runner_mcp/tests/__init__.py b/provider/claude/hermes_runner_mcp/tests/__init__.py
similarity index 100%
rename from claude/hermes_runner_mcp/tests/__init__.py
rename to provider/claude/hermes_runner_mcp/tests/__init__.py
diff --git a/claude/hermes_runner_mcp/tests/test_server.py b/provider/claude/hermes_runner_mcp/tests/test_server.py
similarity index 100%
rename from claude/hermes_runner_mcp/tests/test_server.py
rename to provider/claude/hermes_runner_mcp/tests/test_server.py
diff --git a/claude/infra/.claude-plugin/plugin.json b/provider/claude/infra/.claude-plugin/plugin.json
similarity index 100%
rename from claude/infra/.claude-plugin/plugin.json
rename to provider/claude/infra/.claude-plugin/plugin.json
diff --git a/claude/infra/README.md b/provider/claude/infra/README.md
similarity index 100%
rename from claude/infra/README.md
rename to provider/claude/infra/README.md
diff --git a/claude/infra/agents/README.md b/provider/claude/infra/agents/README.md
similarity index 100%
rename from claude/infra/agents/README.md
rename to provider/claude/infra/agents/README.md
diff --git a/claude/infra/commands/README.md b/provider/claude/infra/commands/README.md
similarity index 100%
rename from claude/infra/commands/README.md
rename to provider/claude/infra/commands/README.md
diff --git a/claude/infra/marketplace.yaml b/provider/claude/infra/marketplace.yaml
similarity index 100%
rename from claude/infra/marketplace.yaml
rename to provider/claude/infra/marketplace.yaml
diff --git a/claude/infra/skills/README.md b/provider/claude/infra/skills/README.md
similarity index 100%
rename from claude/infra/skills/README.md
rename to provider/claude/infra/skills/README.md
diff --git a/plugins/core-go/.claude-plugin/plugin.json b/provider/claude/plugins/core-go/.claude-plugin/plugin.json
similarity index 100%
rename from plugins/core-go/.claude-plugin/plugin.json
rename to provider/claude/plugins/core-go/.claude-plugin/plugin.json
diff --git a/plugins/core-go/.mcp.json b/provider/claude/plugins/core-go/.mcp.json
similarity index 100%
rename from plugins/core-go/.mcp.json
rename to provider/claude/plugins/core-go/.mcp.json
diff --git a/plugins/core-go/README.md b/provider/claude/plugins/core-go/README.md
similarity index 100%
rename from plugins/core-go/README.md
rename to provider/claude/plugins/core-go/README.md
diff --git a/plugins/core-go/agents/go-developer.md b/provider/claude/plugins/core-go/agents/go-developer.md
similarity index 100%
rename from plugins/core-go/agents/go-developer.md
rename to provider/claude/plugins/core-go/agents/go-developer.md
diff --git a/plugins/core-go/commands/commit.md b/provider/claude/plugins/core-go/commands/commit.md
similarity index 100%
rename from plugins/core-go/commands/commit.md
rename to provider/claude/plugins/core-go/commands/commit.md
diff --git a/plugins/core-go/commands/qa.md b/provider/claude/plugins/core-go/commands/qa.md
similarity index 100%
rename from plugins/core-go/commands/qa.md
rename to provider/claude/plugins/core-go/commands/qa.md
diff --git a/codex/review/commands/review.md b/provider/claude/plugins/core-go/commands/review.md
similarity index 100%
rename from codex/review/commands/review.md
rename to provider/claude/plugins/core-go/commands/review.md
diff --git a/plugins/core-go/commands/verify.md b/provider/claude/plugins/core-go/commands/verify.md
similarity index 100%
rename from plugins/core-go/commands/verify.md
rename to provider/claude/plugins/core-go/commands/verify.md
diff --git a/plugins/core-go/marketplace.yaml b/provider/claude/plugins/core-go/marketplace.yaml
similarity index 100%
rename from plugins/core-go/marketplace.yaml
rename to provider/claude/plugins/core-go/marketplace.yaml
diff --git a/codex/code/scripts/qa-filter.sh b/provider/claude/plugins/core-go/scripts/qa-filter.sh
similarity index 100%
rename from codex/code/scripts/qa-filter.sh
rename to provider/claude/plugins/core-go/scripts/qa-filter.sh
diff --git a/plugins/core-go/scripts/qa-verify.sh b/provider/claude/plugins/core-go/scripts/qa-verify.sh
similarity index 100%
rename from plugins/core-go/scripts/qa-verify.sh
rename to provider/claude/plugins/core-go/scripts/qa-verify.sh
diff --git a/plugins/core-go/scripts/smart-commit.sh b/provider/claude/plugins/core-go/scripts/smart-commit.sh
similarity index 100%
rename from plugins/core-go/scripts/smart-commit.sh
rename to provider/claude/plugins/core-go/scripts/smart-commit.sh
diff --git a/plugins/core-go/skills/api-endpoints/SKILL.md b/provider/claude/plugins/core-go/skills/api-endpoints/SKILL.md
similarity index 100%
rename from plugins/core-go/skills/api-endpoints/SKILL.md
rename to provider/claude/plugins/core-go/skills/api-endpoints/SKILL.md
diff --git a/codex/code/skills/go/SKILL.md b/provider/claude/plugins/core-go/skills/core-go/SKILL.md
similarity index 100%
rename from codex/code/skills/go/SKILL.md
rename to provider/claude/plugins/core-go/skills/core-go/SKILL.md
diff --git a/codex/code/skills/core/SKILL.md b/provider/claude/plugins/core-go/skills/core/SKILL.md
similarity index 100%
rename from codex/code/skills/core/SKILL.md
rename to provider/claude/plugins/core-go/skills/core/SKILL.md
diff --git a/codex/code/skills/go-agent/SKILL.md b/provider/claude/plugins/core-go/skills/go-agent/SKILL.md
similarity index 100%
rename from codex/code/skills/go-agent/SKILL.md
rename to provider/claude/plugins/core-go/skills/go-agent/SKILL.md
diff --git a/plugins/core-php/.claude-plugin/plugin.json b/provider/claude/plugins/core-php/.claude-plugin/plugin.json
similarity index 100%
rename from plugins/core-php/.claude-plugin/plugin.json
rename to provider/claude/plugins/core-php/.claude-plugin/plugin.json
diff --git a/plugins/core-php/.mcp.json b/provider/claude/plugins/core-php/.mcp.json
similarity index 100%
rename from plugins/core-php/.mcp.json
rename to provider/claude/plugins/core-php/.mcp.json
diff --git a/plugins/core-php/README.md b/provider/claude/plugins/core-php/README.md
similarity index 100%
rename from plugins/core-php/README.md
rename to provider/claude/plugins/core-php/README.md
diff --git a/plugins/core-php/agents/php-developer.md b/provider/claude/plugins/core-php/agents/php-developer.md
similarity index 100%
rename from plugins/core-php/agents/php-developer.md
rename to provider/claude/plugins/core-php/agents/php-developer.md
diff --git a/plugins/core-php/commands/commit.md b/provider/claude/plugins/core-php/commands/commit.md
similarity index 100%
rename from plugins/core-php/commands/commit.md
rename to provider/claude/plugins/core-php/commands/commit.md
diff --git a/plugins/core-php/commands/qa.md b/provider/claude/plugins/core-php/commands/qa.md
similarity index 100%
rename from plugins/core-php/commands/qa.md
rename to provider/claude/plugins/core-php/commands/qa.md
diff --git a/plugins/core-go/commands/review.md b/provider/claude/plugins/core-php/commands/review.md
similarity index 100%
rename from plugins/core-go/commands/review.md
rename to provider/claude/plugins/core-php/commands/review.md
diff --git a/plugins/core-php/commands/verify.md b/provider/claude/plugins/core-php/commands/verify.md
similarity index 100%
rename from plugins/core-php/commands/verify.md
rename to provider/claude/plugins/core-php/commands/verify.md
diff --git a/plugins/core-php/marketplace.yaml b/provider/claude/plugins/core-php/marketplace.yaml
similarity index 100%
rename from plugins/core-php/marketplace.yaml
rename to provider/claude/plugins/core-php/marketplace.yaml
diff --git a/plugins/core-php/scripts/qa-filter.sh b/provider/claude/plugins/core-php/scripts/qa-filter.sh
similarity index 100%
rename from plugins/core-php/scripts/qa-filter.sh
rename to provider/claude/plugins/core-php/scripts/qa-filter.sh
diff --git a/plugins/core-php/scripts/qa-verify.sh b/provider/claude/plugins/core-php/scripts/qa-verify.sh
similarity index 100%
rename from plugins/core-php/scripts/qa-verify.sh
rename to provider/claude/plugins/core-php/scripts/qa-verify.sh
diff --git a/plugins/core-php/scripts/smart-commit.sh b/provider/claude/plugins/core-php/scripts/smart-commit.sh
similarity index 100%
rename from plugins/core-php/scripts/smart-commit.sh
rename to provider/claude/plugins/core-php/scripts/smart-commit.sh
diff --git a/plugins/core-php/skills/api-endpoints/SKILL.md b/provider/claude/plugins/core-php/skills/api-endpoints/SKILL.md
similarity index 100%
rename from plugins/core-php/skills/api-endpoints/SKILL.md
rename to provider/claude/plugins/core-php/skills/api-endpoints/SKILL.md
diff --git a/codex/code/skills/php/SKILL.md b/provider/claude/plugins/core-php/skills/core-php/SKILL.md
similarity index 100%
rename from codex/code/skills/php/SKILL.md
rename to provider/claude/plugins/core-php/skills/core-php/SKILL.md
diff --git a/plugins/core-go/skills/core/SKILL.md b/provider/claude/plugins/core-php/skills/core/SKILL.md
similarity index 100%
rename from plugins/core-go/skills/core/SKILL.md
rename to provider/claude/plugins/core-php/skills/core/SKILL.md
diff --git a/codex/code/skills/php-agent/SKILL.md b/provider/claude/plugins/core-php/skills/php-agent/SKILL.md
similarity index 100%
rename from codex/code/skills/php-agent/SKILL.md
rename to provider/claude/plugins/core-php/skills/php-agent/SKILL.md
diff --git a/plugins/infra/.claude-plugin/plugin.json b/provider/claude/plugins/infra/.claude-plugin/plugin.json
similarity index 100%
rename from plugins/infra/.claude-plugin/plugin.json
rename to provider/claude/plugins/infra/.claude-plugin/plugin.json
diff --git a/plugins/infra/README.md b/provider/claude/plugins/infra/README.md
similarity index 100%
rename from plugins/infra/README.md
rename to provider/claude/plugins/infra/README.md
diff --git a/plugins/infra/agents/infra-ops.md b/provider/claude/plugins/infra/agents/infra-ops.md
similarity index 100%
rename from plugins/infra/agents/infra-ops.md
rename to provider/claude/plugins/infra/agents/infra-ops.md
diff --git a/plugins/infra/marketplace.yaml b/provider/claude/plugins/infra/marketplace.yaml
similarity index 100%
rename from plugins/infra/marketplace.yaml
rename to provider/claude/plugins/infra/marketplace.yaml
diff --git a/claude/research/.claude-plugin/plugin.json b/provider/claude/research/.claude-plugin/plugin.json
similarity index 100%
rename from claude/research/.claude-plugin/plugin.json
rename to provider/claude/research/.claude-plugin/plugin.json
diff --git a/claude/research/collection/HOOKS.md b/provider/claude/research/collection/HOOKS.md
similarity index 100%
rename from claude/research/collection/HOOKS.md
rename to provider/claude/research/collection/HOOKS.md
diff --git a/claude/research/collection/collect-whitepaper.sh b/provider/claude/research/collection/collect-whitepaper.sh
similarity index 100%
rename from claude/research/collection/collect-whitepaper.sh
rename to provider/claude/research/collection/collect-whitepaper.sh
diff --git a/claude/research/collection/dispatch.sh b/provider/claude/research/collection/dispatch.sh
similarity index 100%
rename from claude/research/collection/dispatch.sh
rename to provider/claude/research/collection/dispatch.sh
diff --git a/claude/research/collection/hooks.json b/provider/claude/research/collection/hooks.json
similarity index 100%
rename from claude/research/collection/hooks.json
rename to provider/claude/research/collection/hooks.json
diff --git a/claude/research/collection/update-index.sh b/provider/claude/research/collection/update-index.sh
similarity index 100%
rename from claude/research/collection/update-index.sh
rename to provider/claude/research/collection/update-index.sh
diff --git a/claude/research/skills/bitcointalk/SKILL.md b/provider/claude/research/skills/bitcointalk/SKILL.md
similarity index 100%
rename from claude/research/skills/bitcointalk/SKILL.md
rename to provider/claude/research/skills/bitcointalk/SKILL.md
diff --git a/claude/research/skills/bitcointalk/collect.sh b/provider/claude/research/skills/bitcointalk/collect.sh
similarity index 100%
rename from claude/research/skills/bitcointalk/collect.sh
rename to provider/claude/research/skills/bitcointalk/collect.sh
diff --git a/claude/research/skills/block-explorer/SKILL.md b/provider/claude/research/skills/block-explorer/SKILL.md
similarity index 100%
rename from claude/research/skills/block-explorer/SKILL.md
rename to provider/claude/research/skills/block-explorer/SKILL.md
diff --git a/claude/research/skills/block-explorer/generate-jobs.sh b/provider/claude/research/skills/block-explorer/generate-jobs.sh
similarity index 100%
rename from claude/research/skills/block-explorer/generate-jobs.sh
rename to provider/claude/research/skills/block-explorer/generate-jobs.sh
diff --git a/claude/research/skills/coinmarketcap/SKILL.md b/provider/claude/research/skills/coinmarketcap/SKILL.md
similarity index 100%
rename from claude/research/skills/coinmarketcap/SKILL.md
rename to provider/claude/research/skills/coinmarketcap/SKILL.md
diff --git a/claude/research/skills/coinmarketcap/generate-jobs.sh b/provider/claude/research/skills/coinmarketcap/generate-jobs.sh
similarity index 100%
rename from claude/research/skills/coinmarketcap/generate-jobs.sh
rename to provider/claude/research/skills/coinmarketcap/generate-jobs.sh
diff --git a/claude/research/skills/coinmarketcap/process.sh b/provider/claude/research/skills/coinmarketcap/process.sh
similarity index 100%
rename from claude/research/skills/coinmarketcap/process.sh
rename to provider/claude/research/skills/coinmarketcap/process.sh
diff --git a/claude/research/skills/community-chat/SKILL.md b/provider/claude/research/skills/community-chat/SKILL.md
similarity index 100%
rename from claude/research/skills/community-chat/SKILL.md
rename to provider/claude/research/skills/community-chat/SKILL.md
diff --git a/claude/research/skills/cryptonote-discovery/SKILL.md b/provider/claude/research/skills/cryptonote-discovery/SKILL.md
similarity index 100%
rename from claude/research/skills/cryptonote-discovery/SKILL.md
rename to provider/claude/research/skills/cryptonote-discovery/SKILL.md
diff --git a/claude/research/skills/cryptonote-discovery/discover.sh b/provider/claude/research/skills/cryptonote-discovery/discover.sh
similarity index 100%
rename from claude/research/skills/cryptonote-discovery/discover.sh
rename to provider/claude/research/skills/cryptonote-discovery/discover.sh
diff --git a/claude/research/skills/cryptonote-discovery/registry.json b/provider/claude/research/skills/cryptonote-discovery/registry.json
similarity index 100%
rename from claude/research/skills/cryptonote-discovery/registry.json
rename to provider/claude/research/skills/cryptonote-discovery/registry.json
diff --git a/claude/research/skills/github-history/SKILL.md b/provider/claude/research/skills/github-history/SKILL.md
similarity index 100%
rename from claude/research/skills/github-history/SKILL.md
rename to provider/claude/research/skills/github-history/SKILL.md
diff --git a/claude/research/skills/github-history/collect.sh b/provider/claude/research/skills/github-history/collect.sh
similarity index 100%
rename from claude/research/skills/github-history/collect.sh
rename to provider/claude/research/skills/github-history/collect.sh
diff --git a/claude/research/skills/job-collector/SKILL.md b/provider/claude/research/skills/job-collector/SKILL.md
similarity index 100%
rename from claude/research/skills/job-collector/SKILL.md
rename to provider/claude/research/skills/job-collector/SKILL.md
diff --git a/claude/research/skills/job-collector/generate-jobs.sh b/provider/claude/research/skills/job-collector/generate-jobs.sh
similarity index 100%
rename from claude/research/skills/job-collector/generate-jobs.sh
rename to provider/claude/research/skills/job-collector/generate-jobs.sh
diff --git a/claude/research/skills/job-collector/process.sh b/provider/claude/research/skills/job-collector/process.sh
similarity index 100%
rename from claude/research/skills/job-collector/process.sh
rename to provider/claude/research/skills/job-collector/process.sh
diff --git a/claude/research/skills/ledger-papers/SKILL.md b/provider/claude/research/skills/ledger-papers/SKILL.md
similarity index 100%
rename from claude/research/skills/ledger-papers/SKILL.md
rename to provider/claude/research/skills/ledger-papers/SKILL.md
diff --git a/claude/research/skills/ledger-papers/archive/00-genesis/README.md b/provider/claude/research/skills/ledger-papers/archive/00-genesis/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/00-genesis/README.md
rename to provider/claude/research/skills/ledger-papers/archive/00-genesis/README.md
diff --git a/claude/research/skills/ledger-papers/archive/01-cryptonote/README.md b/provider/claude/research/skills/ledger-papers/archive/01-cryptonote/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/01-cryptonote/README.md
rename to provider/claude/research/skills/ledger-papers/archive/01-cryptonote/README.md
diff --git a/claude/research/skills/ledger-papers/archive/02-mrl/README.md b/provider/claude/research/skills/ledger-papers/archive/02-mrl/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/02-mrl/README.md
rename to provider/claude/research/skills/ledger-papers/archive/02-mrl/README.md
diff --git a/claude/research/skills/ledger-papers/archive/03-privacy/README.md b/provider/claude/research/skills/ledger-papers/archive/03-privacy/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/03-privacy/README.md
rename to provider/claude/research/skills/ledger-papers/archive/03-privacy/README.md
diff --git a/claude/research/skills/ledger-papers/archive/04-smart-contracts/README.md b/provider/claude/research/skills/ledger-papers/archive/04-smart-contracts/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/04-smart-contracts/README.md
rename to provider/claude/research/skills/ledger-papers/archive/04-smart-contracts/README.md
diff --git a/claude/research/skills/ledger-papers/archive/05-layer2/README.md b/provider/claude/research/skills/ledger-papers/archive/05-layer2/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/05-layer2/README.md
rename to provider/claude/research/skills/ledger-papers/archive/05-layer2/README.md
diff --git a/claude/research/skills/ledger-papers/archive/06-consensus/README.md b/provider/claude/research/skills/ledger-papers/archive/06-consensus/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/06-consensus/README.md
rename to provider/claude/research/skills/ledger-papers/archive/06-consensus/README.md
diff --git a/claude/research/skills/ledger-papers/archive/07-cryptography/README.md b/provider/claude/research/skills/ledger-papers/archive/07-cryptography/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/07-cryptography/README.md
rename to provider/claude/research/skills/ledger-papers/archive/07-cryptography/README.md
diff --git a/claude/research/skills/ledger-papers/archive/08-defi/README.md b/provider/claude/research/skills/ledger-papers/archive/08-defi/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/08-defi/README.md
rename to provider/claude/research/skills/ledger-papers/archive/08-defi/README.md
diff --git a/claude/research/skills/ledger-papers/archive/09-storage/README.md b/provider/claude/research/skills/ledger-papers/archive/09-storage/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/09-storage/README.md
rename to provider/claude/research/skills/ledger-papers/archive/09-storage/README.md
diff --git a/claude/research/skills/ledger-papers/archive/10-identity/README.md b/provider/claude/research/skills/ledger-papers/archive/10-identity/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/10-identity/README.md
rename to provider/claude/research/skills/ledger-papers/archive/10-identity/README.md
diff --git a/claude/research/skills/ledger-papers/archive/11-dag/README.md b/provider/claude/research/skills/ledger-papers/archive/11-dag/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/11-dag/README.md
rename to provider/claude/research/skills/ledger-papers/archive/11-dag/README.md
diff --git a/claude/research/skills/ledger-papers/archive/12-mev/README.md b/provider/claude/research/skills/ledger-papers/archive/12-mev/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/12-mev/README.md
rename to provider/claude/research/skills/ledger-papers/archive/12-mev/README.md
diff --git a/claude/research/skills/ledger-papers/archive/13-standards-btc/README.md b/provider/claude/research/skills/ledger-papers/archive/13-standards-btc/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/13-standards-btc/README.md
rename to provider/claude/research/skills/ledger-papers/archive/13-standards-btc/README.md
diff --git a/claude/research/skills/ledger-papers/archive/14-standards-eth/README.md b/provider/claude/research/skills/ledger-papers/archive/14-standards-eth/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/14-standards-eth/README.md
rename to provider/claude/research/skills/ledger-papers/archive/14-standards-eth/README.md
diff --git a/claude/research/skills/ledger-papers/archive/15-p2p/README.md b/provider/claude/research/skills/ledger-papers/archive/15-p2p/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/15-p2p/README.md
rename to provider/claude/research/skills/ledger-papers/archive/15-p2p/README.md
diff --git a/claude/research/skills/ledger-papers/archive/16-zk-advanced/README.md b/provider/claude/research/skills/ledger-papers/archive/16-zk-advanced/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/16-zk-advanced/README.md
rename to provider/claude/research/skills/ledger-papers/archive/16-zk-advanced/README.md
diff --git a/claude/research/skills/ledger-papers/archive/17-oracles/README.md b/provider/claude/research/skills/ledger-papers/archive/17-oracles/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/17-oracles/README.md
rename to provider/claude/research/skills/ledger-papers/archive/17-oracles/README.md
diff --git a/claude/research/skills/ledger-papers/archive/18-bridges/README.md b/provider/claude/research/skills/ledger-papers/archive/18-bridges/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/18-bridges/README.md
rename to provider/claude/research/skills/ledger-papers/archive/18-bridges/README.md
diff --git a/claude/research/skills/ledger-papers/archive/19-attacks/README.md b/provider/claude/research/skills/ledger-papers/archive/19-attacks/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/19-attacks/README.md
rename to provider/claude/research/skills/ledger-papers/archive/19-attacks/README.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/README.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/README.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/README.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/README.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/README.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/README.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-001-GSD-general-supernode-design.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-001-GSD-general-supernode-design.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-001-GSD-general-supernode-design.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-001-GSD-general-supernode-design.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-002-SLS-supernode-list-selection.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-002-SLS-supernode-list-selection.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-002-SLS-supernode-list-selection.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-002-SLS-supernode-list-selection.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-003-RTVF-rta-transaction-validation.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-003-RTVF-rta-transaction-validation.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-003-RTVF-rta-transaction-validation.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-003-RTVF-rta-transaction-validation.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-005-DF-disqualification-flow.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-005-DF-disqualification-flow.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-005-DF-disqualification-flow.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-005-DF-disqualification-flow.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/auth-sample-selection-algorithm.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/auth-sample-selection-algorithm.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/auth-sample-selection-algorithm.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/auth-sample-selection-algorithm.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/blockchain-based-list-selection-analysis.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/blockchain-based-list-selection-analysis.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/blockchain-based-list-selection-analysis.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/blockchain-based-list-selection-analysis.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/communication-options-p2p-design.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/communication-options-p2p-design.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/communication-options-p2p-design.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/communication-options-p2p-design.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/rta-double-spend-attack-vectors.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/rta-double-spend-attack-vectors.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/rta-double-spend-attack-vectors.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/rta-double-spend-attack-vectors.md
diff --git a/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/udht-implementation.md b/provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/udht-implementation.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/udht-implementation.md
rename to provider/claude/research/skills/ledger-papers/archive/20-cryptonote-projects/graft/udht-implementation.md
diff --git a/claude/research/skills/ledger-papers/archive/README.md b/provider/claude/research/skills/ledger-papers/archive/README.md
similarity index 100%
rename from claude/research/skills/ledger-papers/archive/README.md
rename to provider/claude/research/skills/ledger-papers/archive/README.md
diff --git a/claude/research/skills/ledger-papers/discover.sh b/provider/claude/research/skills/ledger-papers/discover.sh
similarity index 100%
rename from claude/research/skills/ledger-papers/discover.sh
rename to provider/claude/research/skills/ledger-papers/discover.sh
diff --git a/claude/research/skills/ledger-papers/registry.json b/provider/claude/research/skills/ledger-papers/registry.json
similarity index 100%
rename from claude/research/skills/ledger-papers/registry.json
rename to provider/claude/research/skills/ledger-papers/registry.json
diff --git a/claude/research/skills/mining-pools/SKILL.md b/provider/claude/research/skills/mining-pools/SKILL.md
similarity index 100%
rename from claude/research/skills/mining-pools/SKILL.md
rename to provider/claude/research/skills/mining-pools/SKILL.md
diff --git a/claude/research/skills/mining-pools/generate-jobs.sh b/provider/claude/research/skills/mining-pools/generate-jobs.sh
similarity index 100%
rename from claude/research/skills/mining-pools/generate-jobs.sh
rename to provider/claude/research/skills/mining-pools/generate-jobs.sh
diff --git a/claude/research/skills/project-archaeology/SKILL.md b/provider/claude/research/skills/project-archaeology/SKILL.md
similarity index 100%
rename from claude/research/skills/project-archaeology/SKILL.md
rename to provider/claude/research/skills/project-archaeology/SKILL.md
diff --git a/claude/research/skills/project-archaeology/digs/graftnetwork/SALVAGE-REPORT.md b/provider/claude/research/skills/project-archaeology/digs/graftnetwork/SALVAGE-REPORT.md
similarity index 100%
rename from claude/research/skills/project-archaeology/digs/graftnetwork/SALVAGE-REPORT.md
rename to provider/claude/research/skills/project-archaeology/digs/graftnetwork/SALVAGE-REPORT.md
diff --git a/claude/research/skills/project-archaeology/excavate.sh b/provider/claude/research/skills/project-archaeology/excavate.sh
similarity index 100%
rename from claude/research/skills/project-archaeology/excavate.sh
rename to provider/claude/research/skills/project-archaeology/excavate.sh
diff --git a/claude/research/skills/project-archaeology/templates/LESSONS.md b/provider/claude/research/skills/project-archaeology/templates/LESSONS.md
similarity index 100%
rename from claude/research/skills/project-archaeology/templates/LESSONS.md
rename to provider/claude/research/skills/project-archaeology/templates/LESSONS.md
diff --git a/claude/research/skills/project-archaeology/templates/SALVAGE-REPORT.md b/provider/claude/research/skills/project-archaeology/templates/SALVAGE-REPORT.md
similarity index 100%
rename from claude/research/skills/project-archaeology/templates/SALVAGE-REPORT.md
rename to provider/claude/research/skills/project-archaeology/templates/SALVAGE-REPORT.md
diff --git a/claude/research/skills/wallet-releases/SKILL.md b/provider/claude/research/skills/wallet-releases/SKILL.md
similarity index 100%
rename from claude/research/skills/wallet-releases/SKILL.md
rename to provider/claude/research/skills/wallet-releases/SKILL.md
diff --git a/claude/research/skills/whitepaper-archive/SKILL.md b/provider/claude/research/skills/whitepaper-archive/SKILL.md
similarity index 100%
rename from claude/research/skills/whitepaper-archive/SKILL.md
rename to provider/claude/research/skills/whitepaper-archive/SKILL.md
diff --git a/codex/.codex-plugin/marketplace.json b/provider/codex/.codex-plugin/marketplace.json
similarity index 100%
rename from codex/.codex-plugin/marketplace.json
rename to provider/codex/.codex-plugin/marketplace.json
diff --git a/codex/.codex-plugin/plugin.json b/provider/codex/.codex-plugin/plugin.json
similarity index 100%
rename from codex/.codex-plugin/plugin.json
rename to provider/codex/.codex-plugin/plugin.json
diff --git a/codex/AGENTS.md b/provider/codex/AGENTS.md
similarity index 100%
rename from codex/AGENTS.md
rename to provider/codex/AGENTS.md
diff --git a/codex/IMPROVEMENTS.md b/provider/codex/IMPROVEMENTS.md
similarity index 100%
rename from codex/IMPROVEMENTS.md
rename to provider/codex/IMPROVEMENTS.md
diff --git a/codex/INTEGRATION_PLAN.md b/provider/codex/INTEGRATION_PLAN.md
similarity index 100%
rename from codex/INTEGRATION_PLAN.md
rename to provider/codex/INTEGRATION_PLAN.md
diff --git a/codex/README.md b/provider/codex/README.md
similarity index 100%
rename from codex/README.md
rename to provider/codex/README.md
diff --git a/codex/REPORT.md b/provider/codex/REPORT.md
similarity index 100%
rename from codex/REPORT.md
rename to provider/codex/REPORT.md
diff --git a/codex/api/.codex-plugin/plugin.json b/provider/codex/api/.codex-plugin/plugin.json
similarity index 100%
rename from codex/api/.codex-plugin/plugin.json
rename to provider/codex/api/.codex-plugin/plugin.json
diff --git a/codex/api/AGENTS.md b/provider/codex/api/AGENTS.md
similarity index 100%
rename from codex/api/AGENTS.md
rename to provider/codex/api/AGENTS.md
diff --git a/codex/api/commands/generate.md b/provider/codex/api/commands/generate.md
similarity index 100%
rename from codex/api/commands/generate.md
rename to provider/codex/api/commands/generate.md
diff --git a/codex/api/php/app/Console/Kernel.php b/provider/codex/api/php/app/Console/Kernel.php
similarity index 100%
rename from codex/api/php/app/Console/Kernel.php
rename to provider/codex/api/php/app/Console/Kernel.php
diff --git a/codex/api/php/app/Exceptions/Handler.php b/provider/codex/api/php/app/Exceptions/Handler.php
similarity index 100%
rename from codex/api/php/app/Exceptions/Handler.php
rename to provider/codex/api/php/app/Exceptions/Handler.php
diff --git a/codex/api/php/app/Http/Kernel.php b/provider/codex/api/php/app/Http/Kernel.php
similarity index 100%
rename from codex/api/php/app/Http/Kernel.php
rename to provider/codex/api/php/app/Http/Kernel.php
diff --git a/codex/api/php/composer.json b/provider/codex/api/php/composer.json
similarity index 100%
rename from codex/api/php/composer.json
rename to provider/codex/api/php/composer.json
diff --git a/codex/api/php/generate.php b/provider/codex/api/php/generate.php
similarity index 100%
rename from codex/api/php/generate.php
rename to provider/codex/api/php/generate.php
diff --git a/codex/api/php/routes/api.php b/provider/codex/api/php/routes/api.php
similarity index 100%
rename from codex/api/php/routes/api.php
rename to provider/codex/api/php/routes/api.php
diff --git a/codex/api/scripts/generate.sh b/provider/codex/api/scripts/generate.sh
similarity index 100%
rename from codex/api/scripts/generate.sh
rename to provider/codex/api/scripts/generate.sh
diff --git a/codex/awareness/.codex-plugin/plugin.json b/provider/codex/awareness/.codex-plugin/plugin.json
similarity index 100%
rename from codex/awareness/.codex-plugin/plugin.json
rename to provider/codex/awareness/.codex-plugin/plugin.json
diff --git a/codex/awareness/AGENTS.md b/provider/codex/awareness/AGENTS.md
similarity index 100%
rename from codex/awareness/AGENTS.md
rename to provider/codex/awareness/AGENTS.md
diff --git a/codex/ci/.codex-plugin/plugin.json b/provider/codex/ci/.codex-plugin/plugin.json
similarity index 100%
rename from codex/ci/.codex-plugin/plugin.json
rename to provider/codex/ci/.codex-plugin/plugin.json
diff --git a/codex/ci/AGENTS.md b/provider/codex/ci/AGENTS.md
similarity index 100%
rename from codex/ci/AGENTS.md
rename to provider/codex/ci/AGENTS.md
diff --git a/codex/ci/commands/ci.md b/provider/codex/ci/commands/ci.md
similarity index 100%
rename from codex/ci/commands/ci.md
rename to provider/codex/ci/commands/ci.md
diff --git a/codex/ci/commands/fix.md b/provider/codex/ci/commands/fix.md
similarity index 100%
rename from codex/ci/commands/fix.md
rename to provider/codex/ci/commands/fix.md
diff --git a/codex/ci/commands/run.md b/provider/codex/ci/commands/run.md
similarity index 100%
rename from codex/ci/commands/run.md
rename to provider/codex/ci/commands/run.md
diff --git a/codex/ci/commands/status.md b/provider/codex/ci/commands/status.md
similarity index 100%
rename from codex/ci/commands/status.md
rename to provider/codex/ci/commands/status.md
diff --git a/codex/ci/commands/workflow.md b/provider/codex/ci/commands/workflow.md
similarity index 100%
rename from codex/ci/commands/workflow.md
rename to provider/codex/ci/commands/workflow.md
diff --git a/codex/ci/hooks.json b/provider/codex/ci/hooks.json
similarity index 100%
rename from codex/ci/hooks.json
rename to provider/codex/ci/hooks.json
diff --git a/codex/ci/scripts/post-push-ci.sh b/provider/codex/ci/scripts/post-push-ci.sh
similarity index 100%
rename from codex/ci/scripts/post-push-ci.sh
rename to provider/codex/ci/scripts/post-push-ci.sh
diff --git a/codex/code/.codex-plugin/plugin.json b/provider/codex/code/.codex-plugin/plugin.json
similarity index 100%
rename from codex/code/.codex-plugin/plugin.json
rename to provider/codex/code/.codex-plugin/plugin.json
diff --git a/codex/code/AGENTS.md b/provider/codex/code/AGENTS.md
similarity index 100%
rename from codex/code/AGENTS.md
rename to provider/codex/code/AGENTS.md
diff --git a/codex/code/commands/api.md b/provider/codex/code/commands/api.md
similarity index 100%
rename from codex/code/commands/api.md
rename to provider/codex/code/commands/api.md
diff --git a/codex/code/commands/clean.md b/provider/codex/code/commands/clean.md
similarity index 100%
rename from codex/code/commands/clean.md
rename to provider/codex/code/commands/clean.md
diff --git a/codex/code/commands/commit.md b/provider/codex/code/commands/commit.md
similarity index 100%
rename from codex/code/commands/commit.md
rename to provider/codex/code/commands/commit.md
diff --git a/codex/code/commands/compare.md b/provider/codex/code/commands/compare.md
similarity index 100%
rename from codex/code/commands/compare.md
rename to provider/codex/code/commands/compare.md
diff --git a/codex/code/commands/core-env.md b/provider/codex/code/commands/core-env.md
similarity index 100%
rename from codex/code/commands/core-env.md
rename to provider/codex/code/commands/core-env.md
diff --git a/codex/code/commands/coverage.sh b/provider/codex/code/commands/coverage.sh
similarity index 100%
rename from codex/code/commands/coverage.sh
rename to provider/codex/code/commands/coverage.sh
diff --git a/codex/code/commands/debug.md b/provider/codex/code/commands/debug.md
similarity index 100%
rename from codex/code/commands/debug.md
rename to provider/codex/code/commands/debug.md
diff --git a/codex/code/commands/deps.md b/provider/codex/code/commands/deps.md
similarity index 100%
rename from codex/code/commands/deps.md
rename to provider/codex/code/commands/deps.md
diff --git a/codex/code/commands/doc.md b/provider/codex/code/commands/doc.md
similarity index 100%
rename from codex/code/commands/doc.md
rename to provider/codex/code/commands/doc.md
diff --git a/codex/code/commands/explain.md b/provider/codex/code/commands/explain.md
similarity index 100%
rename from codex/code/commands/explain.md
rename to provider/codex/code/commands/explain.md
diff --git a/codex/code/commands/log.md b/provider/codex/code/commands/log.md
similarity index 100%
rename from codex/code/commands/log.md
rename to provider/codex/code/commands/log.md
diff --git a/codex/code/commands/migrate.md b/provider/codex/code/commands/migrate.md
similarity index 100%
rename from codex/code/commands/migrate.md
rename to provider/codex/code/commands/migrate.md
diff --git a/codex/code/commands/onboard.md b/provider/codex/code/commands/onboard.md
similarity index 100%
rename from codex/code/commands/onboard.md
rename to provider/codex/code/commands/onboard.md
diff --git a/codex/code/commands/perf.md b/provider/codex/code/commands/perf.md
similarity index 100%
rename from codex/code/commands/perf.md
rename to provider/codex/code/commands/perf.md
diff --git a/codex/code/commands/pr.md b/provider/codex/code/commands/pr.md
similarity index 100%
rename from codex/code/commands/pr.md
rename to provider/codex/code/commands/pr.md
diff --git a/codex/code/commands/qa.md b/provider/codex/code/commands/qa.md
similarity index 100%
rename from codex/code/commands/qa.md
rename to provider/codex/code/commands/qa.md
diff --git a/codex/code/commands/refactor.md b/provider/codex/code/commands/refactor.md
similarity index 100%
rename from codex/code/commands/refactor.md
rename to provider/codex/code/commands/refactor.md
diff --git a/codex/code/commands/release.md b/provider/codex/code/commands/release.md
similarity index 100%
rename from codex/code/commands/release.md
rename to provider/codex/code/commands/release.md
diff --git a/codex/code/commands/remember.md b/provider/codex/code/commands/remember.md
similarity index 100%
rename from codex/code/commands/remember.md
rename to provider/codex/code/commands/remember.md
diff --git a/codex/code/commands/review.md b/provider/codex/code/commands/review.md
similarity index 100%
rename from codex/code/commands/review.md
rename to provider/codex/code/commands/review.md
diff --git a/codex/code/commands/scaffold.md b/provider/codex/code/commands/scaffold.md
similarity index 100%
rename from codex/code/commands/scaffold.md
rename to provider/codex/code/commands/scaffold.md
diff --git a/codex/code/commands/serve-mcp.md b/provider/codex/code/commands/serve-mcp.md
similarity index 100%
rename from codex/code/commands/serve-mcp.md
rename to provider/codex/code/commands/serve-mcp.md
diff --git a/codex/code/commands/status.md b/provider/codex/code/commands/status.md
similarity index 100%
rename from codex/code/commands/status.md
rename to provider/codex/code/commands/status.md
diff --git a/codex/code/commands/sync.md b/provider/codex/code/commands/sync.md
similarity index 100%
rename from codex/code/commands/sync.md
rename to provider/codex/code/commands/sync.md
diff --git a/codex/code/commands/todo.md b/provider/codex/code/commands/todo.md
similarity index 100%
rename from codex/code/commands/todo.md
rename to provider/codex/code/commands/todo.md
diff --git a/codex/code/commands/yes.md b/provider/codex/code/commands/yes.md
similarity index 100%
rename from codex/code/commands/yes.md
rename to provider/codex/code/commands/yes.md
diff --git a/codex/code/docs/hook-output-policy.md b/provider/codex/code/docs/hook-output-policy.md
similarity index 100%
rename from codex/code/docs/hook-output-policy.md
rename to provider/codex/code/docs/hook-output-policy.md
diff --git a/codex/code/hooks.json b/provider/codex/code/hooks.json
similarity index 100%
rename from codex/code/hooks.json
rename to provider/codex/code/hooks.json
diff --git a/codex/code/hooks/prefer-core.sh b/provider/codex/code/hooks/prefer-core.sh
similarity index 100%
rename from codex/code/hooks/prefer-core.sh
rename to provider/codex/code/hooks/prefer-core.sh
diff --git a/codex/code/scripts/api-generate.sh b/provider/codex/code/scripts/api-generate.sh
similarity index 100%
rename from codex/code/scripts/api-generate.sh
rename to provider/codex/code/scripts/api-generate.sh
diff --git a/codex/code/scripts/auto-approve.sh b/provider/codex/code/scripts/auto-approve.sh
similarity index 100%
rename from codex/code/scripts/auto-approve.sh
rename to provider/codex/code/scripts/auto-approve.sh
diff --git a/codex/code/scripts/block-docs.sh b/provider/codex/code/scripts/block-docs.sh
similarity index 100%
rename from codex/code/scripts/block-docs.sh
rename to provider/codex/code/scripts/block-docs.sh
diff --git a/codex/code/scripts/capture-context.sh b/provider/codex/code/scripts/capture-context.sh
similarity index 100%
rename from codex/code/scripts/capture-context.sh
rename to provider/codex/code/scripts/capture-context.sh
diff --git a/codex/code/scripts/check-coverage.sh b/provider/codex/code/scripts/check-coverage.sh
similarity index 100%
rename from codex/code/scripts/check-coverage.sh
rename to provider/codex/code/scripts/check-coverage.sh
diff --git a/codex/code/scripts/check-debug.sh b/provider/codex/code/scripts/check-debug.sh
similarity index 100%
rename from codex/code/scripts/check-debug.sh
rename to provider/codex/code/scripts/check-debug.sh
diff --git a/codex/code/scripts/check-types.php b/provider/codex/code/scripts/check-types.php
similarity index 100%
rename from codex/code/scripts/check-types.php
rename to provider/codex/code/scripts/check-types.php
diff --git a/codex/code/scripts/check-types.sh b/provider/codex/code/scripts/check-types.sh
similarity index 100%
rename from codex/code/scripts/check-types.sh
rename to provider/codex/code/scripts/check-types.sh
diff --git a/codex/code/scripts/cleanup.sh b/provider/codex/code/scripts/cleanup.sh
similarity index 100%
rename from codex/code/scripts/cleanup.sh
rename to provider/codex/code/scripts/cleanup.sh
diff --git a/codex/code/scripts/code-review.sh b/provider/codex/code/scripts/code-review.sh
similarity index 100%
rename from codex/code/scripts/code-review.sh
rename to provider/codex/code/scripts/code-review.sh
diff --git a/codex/code/scripts/core-status.sh b/provider/codex/code/scripts/core-status.sh
similarity index 100%
rename from codex/code/scripts/core-status.sh
rename to provider/codex/code/scripts/core-status.sh
diff --git a/codex/code/scripts/deps.py b/provider/codex/code/scripts/deps.py
similarity index 100%
rename from codex/code/scripts/deps.py
rename to provider/codex/code/scripts/deps.py
diff --git a/codex/code/scripts/detect-module.sh b/provider/codex/code/scripts/detect-module.sh
similarity index 100%
rename from codex/code/scripts/detect-module.sh
rename to provider/codex/code/scripts/detect-module.sh
diff --git a/codex/code/scripts/detect-secrets.sh b/provider/codex/code/scripts/detect-secrets.sh
similarity index 100%
rename from codex/code/scripts/detect-secrets.sh
rename to provider/codex/code/scripts/detect-secrets.sh
diff --git a/codex/code/scripts/doc-api.sh b/provider/codex/code/scripts/doc-api.sh
similarity index 100%
rename from codex/code/scripts/doc-api.sh
rename to provider/codex/code/scripts/doc-api.sh
diff --git a/codex/code/scripts/doc-changelog.sh b/provider/codex/code/scripts/doc-changelog.sh
similarity index 100%
rename from codex/code/scripts/doc-changelog.sh
rename to provider/codex/code/scripts/doc-changelog.sh
diff --git a/codex/code/scripts/doc-class-parser.php b/provider/codex/code/scripts/doc-class-parser.php
similarity index 100%
rename from codex/code/scripts/doc-class-parser.php
rename to provider/codex/code/scripts/doc-class-parser.php
diff --git a/codex/code/scripts/doc-class.sh b/provider/codex/code/scripts/doc-class.sh
similarity index 100%
rename from codex/code/scripts/doc-class.sh
rename to provider/codex/code/scripts/doc-class.sh
diff --git a/codex/code/scripts/doc-module.sh b/provider/codex/code/scripts/doc-module.sh
similarity index 100%
rename from codex/code/scripts/doc-module.sh
rename to provider/codex/code/scripts/doc-module.sh
diff --git a/codex/code/scripts/doc.sh b/provider/codex/code/scripts/doc.sh
similarity index 100%
rename from codex/code/scripts/doc.sh
rename to provider/codex/code/scripts/doc.sh
diff --git a/codex/code/scripts/ensure-commit.sh b/provider/codex/code/scripts/ensure-commit.sh
similarity index 100%
rename from codex/code/scripts/ensure-commit.sh
rename to provider/codex/code/scripts/ensure-commit.sh
diff --git a/codex/code/scripts/env.sh b/provider/codex/code/scripts/env.sh
similarity index 100%
rename from codex/code/scripts/env.sh
rename to provider/codex/code/scripts/env.sh
diff --git a/codex/code/scripts/extract-actionables.sh b/provider/codex/code/scripts/extract-actionables.sh
similarity index 100%
rename from codex/code/scripts/extract-actionables.sh
rename to provider/codex/code/scripts/extract-actionables.sh
diff --git a/codex/code/scripts/generate-pr.sh b/provider/codex/code/scripts/generate-pr.sh
similarity index 100%
rename from codex/code/scripts/generate-pr.sh
rename to provider/codex/code/scripts/generate-pr.sh
diff --git a/codex/code/scripts/go-format.sh b/provider/codex/code/scripts/go-format.sh
similarity index 100%
rename from codex/code/scripts/go-format.sh
rename to provider/codex/code/scripts/go-format.sh
diff --git a/codex/code/scripts/log.sh b/provider/codex/code/scripts/log.sh
similarity index 100%
rename from codex/code/scripts/log.sh
rename to provider/codex/code/scripts/log.sh
diff --git a/codex/code/scripts/mcp/run.sh b/provider/codex/code/scripts/mcp/run.sh
similarity index 100%
rename from codex/code/scripts/mcp/run.sh
rename to provider/codex/code/scripts/mcp/run.sh
diff --git a/codex/code/scripts/migrate.sh b/provider/codex/code/scripts/migrate.sh
similarity index 100%
rename from codex/code/scripts/migrate.sh
rename to provider/codex/code/scripts/migrate.sh
diff --git a/codex/code/scripts/output-policy.sh b/provider/codex/code/scripts/output-policy.sh
similarity index 100%
rename from codex/code/scripts/output-policy.sh
rename to provider/codex/code/scripts/output-policy.sh
diff --git a/codex/code/scripts/perf.sh b/provider/codex/code/scripts/perf.sh
similarity index 100%
rename from codex/code/scripts/perf.sh
rename to provider/codex/code/scripts/perf.sh
diff --git a/codex/code/scripts/php-format.sh b/provider/codex/code/scripts/php-format.sh
similarity index 100%
rename from codex/code/scripts/php-format.sh
rename to provider/codex/code/scripts/php-format.sh
diff --git a/codex/code/scripts/post-commit-check.sh b/provider/codex/code/scripts/post-commit-check.sh
similarity index 100%
rename from codex/code/scripts/post-commit-check.sh
rename to provider/codex/code/scripts/post-commit-check.sh
diff --git a/codex/code/scripts/pr-created.sh b/provider/codex/code/scripts/pr-created.sh
similarity index 100%
rename from codex/code/scripts/pr-created.sh
rename to provider/codex/code/scripts/pr-created.sh
diff --git a/codex/qa/scripts/qa-filter.sh b/provider/codex/code/scripts/qa-filter.sh
similarity index 100%
rename from codex/qa/scripts/qa-filter.sh
rename to provider/codex/code/scripts/qa-filter.sh
diff --git a/codex/code/scripts/qa-verify.sh b/provider/codex/code/scripts/qa-verify.sh
similarity index 100%
rename from codex/code/scripts/qa-verify.sh
rename to provider/codex/code/scripts/qa-verify.sh
diff --git a/codex/code/scripts/refactor.php b/provider/codex/code/scripts/refactor.php
similarity index 100%
rename from codex/code/scripts/refactor.php
rename to provider/codex/code/scripts/refactor.php
diff --git a/codex/code/scripts/release.sh b/provider/codex/code/scripts/release.sh
similarity index 100%
rename from codex/code/scripts/release.sh
rename to provider/codex/code/scripts/release.sh
diff --git a/codex/code/scripts/session-history-capture.sh b/provider/codex/code/scripts/session-history-capture.sh
similarity index 100%
rename from codex/code/scripts/session-history-capture.sh
rename to provider/codex/code/scripts/session-history-capture.sh
diff --git a/codex/code/scripts/session-history-restore.sh b/provider/codex/code/scripts/session-history-restore.sh
similarity index 100%
rename from codex/code/scripts/session-history-restore.sh
rename to provider/codex/code/scripts/session-history-restore.sh
diff --git a/codex/code/scripts/session-history.sh b/provider/codex/code/scripts/session-history.sh
similarity index 100%
rename from codex/code/scripts/session-history.sh
rename to provider/codex/code/scripts/session-history.sh
diff --git a/codex/code/scripts/smart-commit.sh b/provider/codex/code/scripts/smart-commit.sh
similarity index 100%
rename from codex/code/scripts/smart-commit.sh
rename to provider/codex/code/scripts/smart-commit.sh
diff --git a/codex/code/scripts/status.sh b/provider/codex/code/scripts/status.sh
similarity index 100%
rename from codex/code/scripts/status.sh
rename to provider/codex/code/scripts/status.sh
diff --git a/codex/code/scripts/suggest-compact.sh b/provider/codex/code/scripts/suggest-compact.sh
similarity index 100%
rename from codex/code/scripts/suggest-compact.sh
rename to provider/codex/code/scripts/suggest-compact.sh
diff --git a/codex/code/scripts/sync.sh b/provider/codex/code/scripts/sync.sh
similarity index 100%
rename from codex/code/scripts/sync.sh
rename to provider/codex/code/scripts/sync.sh
diff --git a/codex/code/scripts/test_deps.py b/provider/codex/code/scripts/test_deps.py
similarity index 100%
rename from codex/code/scripts/test_deps.py
rename to provider/codex/code/scripts/test_deps.py
diff --git a/codex/code/scripts/todo.sh b/provider/codex/code/scripts/todo.sh
similarity index 100%
rename from codex/code/scripts/todo.sh
rename to provider/codex/code/scripts/todo.sh
diff --git a/codex/code/scripts/validate-branch.sh b/provider/codex/code/scripts/validate-branch.sh
similarity index 100%
rename from codex/code/scripts/validate-branch.sh
rename to provider/codex/code/scripts/validate-branch.sh
diff --git a/plugins/core-php/skills/core/SKILL.md b/provider/codex/code/skills/core/SKILL.md
similarity index 100%
rename from plugins/core-php/skills/core/SKILL.md
rename to provider/codex/code/skills/core/SKILL.md
diff --git a/codex/code/skills/core/test.sh b/provider/codex/code/skills/core/test.sh
similarity index 100%
rename from codex/code/skills/core/test.sh
rename to provider/codex/code/skills/core/test.sh
diff --git a/plugins/core-go/skills/go-agent/SKILL.md b/provider/codex/code/skills/go-agent/SKILL.md
similarity index 100%
rename from plugins/core-go/skills/go-agent/SKILL.md
rename to provider/codex/code/skills/go-agent/SKILL.md
diff --git a/plugins/core-go/skills/core-go/SKILL.md b/provider/codex/code/skills/go/SKILL.md
similarity index 100%
rename from plugins/core-go/skills/core-go/SKILL.md
rename to provider/codex/code/skills/go/SKILL.md
diff --git a/codex/code/skills/laravel/SKILL.md b/provider/codex/code/skills/laravel/SKILL.md
similarity index 100%
rename from codex/code/skills/laravel/SKILL.md
rename to provider/codex/code/skills/laravel/SKILL.md
diff --git a/plugins/core-php/skills/php-agent/SKILL.md b/provider/codex/code/skills/php-agent/SKILL.md
similarity index 100%
rename from plugins/core-php/skills/php-agent/SKILL.md
rename to provider/codex/code/skills/php-agent/SKILL.md
diff --git a/plugins/core-php/skills/core-php/SKILL.md b/provider/codex/code/skills/php/SKILL.md
similarity index 100%
rename from plugins/core-php/skills/core-php/SKILL.md
rename to provider/codex/code/skills/php/SKILL.md
diff --git a/codex/code/tests/ScaffoldTest.php b/provider/codex/code/tests/ScaffoldTest.php
similarity index 100%
rename from codex/code/tests/ScaffoldTest.php
rename to provider/codex/code/tests/ScaffoldTest.php
diff --git a/codex/collect/.codex-plugin/plugin.json b/provider/codex/collect/.codex-plugin/plugin.json
similarity index 100%
rename from codex/collect/.codex-plugin/plugin.json
rename to provider/codex/collect/.codex-plugin/plugin.json
diff --git a/codex/collect/AGENTS.md b/provider/codex/collect/AGENTS.md
similarity index 100%
rename from codex/collect/AGENTS.md
rename to provider/codex/collect/AGENTS.md
diff --git a/codex/collect/commands/collect.md b/provider/codex/collect/commands/collect.md
similarity index 100%
rename from codex/collect/commands/collect.md
rename to provider/codex/collect/commands/collect.md
diff --git a/codex/collect/commands/excavate.md b/provider/codex/collect/commands/excavate.md
similarity index 100%
rename from codex/collect/commands/excavate.md
rename to provider/codex/collect/commands/excavate.md
diff --git a/codex/collect/commands/github.md b/provider/codex/collect/commands/github.md
similarity index 100%
rename from codex/collect/commands/github.md
rename to provider/codex/collect/commands/github.md
diff --git a/codex/collect/commands/papers.md b/provider/codex/collect/commands/papers.md
similarity index 100%
rename from codex/collect/commands/papers.md
rename to provider/codex/collect/commands/papers.md
diff --git a/codex/collect/commands/website.md b/provider/codex/collect/commands/website.md
similarity index 100%
rename from codex/collect/commands/website.md
rename to provider/codex/collect/commands/website.md
diff --git a/codex/collect/skills/bitcointalk/SKILL.md b/provider/codex/collect/skills/bitcointalk/SKILL.md
similarity index 100%
rename from codex/collect/skills/bitcointalk/SKILL.md
rename to provider/codex/collect/skills/bitcointalk/SKILL.md
diff --git a/codex/collect/skills/block-explorer/SKILL.md b/provider/codex/collect/skills/block-explorer/SKILL.md
similarity index 100%
rename from codex/collect/skills/block-explorer/SKILL.md
rename to provider/codex/collect/skills/block-explorer/SKILL.md
diff --git a/codex/collect/skills/coinmarketcap/SKILL.md b/provider/codex/collect/skills/coinmarketcap/SKILL.md
similarity index 100%
rename from codex/collect/skills/coinmarketcap/SKILL.md
rename to provider/codex/collect/skills/coinmarketcap/SKILL.md
diff --git a/codex/collect/skills/community-chat/SKILL.md b/provider/codex/collect/skills/community-chat/SKILL.md
similarity index 100%
rename from codex/collect/skills/community-chat/SKILL.md
rename to provider/codex/collect/skills/community-chat/SKILL.md
diff --git a/codex/collect/skills/cryptonote-discovery/SKILL.md b/provider/codex/collect/skills/cryptonote-discovery/SKILL.md
similarity index 100%
rename from codex/collect/skills/cryptonote-discovery/SKILL.md
rename to provider/codex/collect/skills/cryptonote-discovery/SKILL.md
diff --git a/codex/collect/skills/cryptonote-discovery/registry.json b/provider/codex/collect/skills/cryptonote-discovery/registry.json
similarity index 100%
rename from codex/collect/skills/cryptonote-discovery/registry.json
rename to provider/codex/collect/skills/cryptonote-discovery/registry.json
diff --git a/codex/collect/skills/github-history/SKILL.md b/provider/codex/collect/skills/github-history/SKILL.md
similarity index 100%
rename from codex/collect/skills/github-history/SKILL.md
rename to provider/codex/collect/skills/github-history/SKILL.md
diff --git a/codex/collect/skills/job-collector/SKILL.md b/provider/codex/collect/skills/job-collector/SKILL.md
similarity index 100%
rename from codex/collect/skills/job-collector/SKILL.md
rename to provider/codex/collect/skills/job-collector/SKILL.md
diff --git a/codex/collect/skills/ledger-papers/SKILL.md b/provider/codex/collect/skills/ledger-papers/SKILL.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/SKILL.md
rename to provider/codex/collect/skills/ledger-papers/SKILL.md
diff --git a/codex/collect/skills/ledger-papers/archive/00-genesis/README.md b/provider/codex/collect/skills/ledger-papers/archive/00-genesis/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/00-genesis/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/00-genesis/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/01-cryptonote/README.md b/provider/codex/collect/skills/ledger-papers/archive/01-cryptonote/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/01-cryptonote/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/01-cryptonote/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/02-mrl/README.md b/provider/codex/collect/skills/ledger-papers/archive/02-mrl/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/02-mrl/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/02-mrl/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/03-privacy/README.md b/provider/codex/collect/skills/ledger-papers/archive/03-privacy/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/03-privacy/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/03-privacy/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/04-smart-contracts/README.md b/provider/codex/collect/skills/ledger-papers/archive/04-smart-contracts/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/04-smart-contracts/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/04-smart-contracts/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/05-layer2/README.md b/provider/codex/collect/skills/ledger-papers/archive/05-layer2/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/05-layer2/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/05-layer2/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/06-consensus/README.md b/provider/codex/collect/skills/ledger-papers/archive/06-consensus/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/06-consensus/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/06-consensus/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/07-cryptography/README.md b/provider/codex/collect/skills/ledger-papers/archive/07-cryptography/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/07-cryptography/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/07-cryptography/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/08-defi/README.md b/provider/codex/collect/skills/ledger-papers/archive/08-defi/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/08-defi/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/08-defi/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/09-storage/README.md b/provider/codex/collect/skills/ledger-papers/archive/09-storage/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/09-storage/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/09-storage/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/10-identity/README.md b/provider/codex/collect/skills/ledger-papers/archive/10-identity/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/10-identity/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/10-identity/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/11-dag/README.md b/provider/codex/collect/skills/ledger-papers/archive/11-dag/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/11-dag/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/11-dag/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/12-mev/README.md b/provider/codex/collect/skills/ledger-papers/archive/12-mev/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/12-mev/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/12-mev/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/13-standards-btc/README.md b/provider/codex/collect/skills/ledger-papers/archive/13-standards-btc/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/13-standards-btc/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/13-standards-btc/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/14-standards-eth/README.md b/provider/codex/collect/skills/ledger-papers/archive/14-standards-eth/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/14-standards-eth/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/14-standards-eth/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/15-p2p/README.md b/provider/codex/collect/skills/ledger-papers/archive/15-p2p/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/15-p2p/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/15-p2p/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/16-zk-advanced/README.md b/provider/codex/collect/skills/ledger-papers/archive/16-zk-advanced/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/16-zk-advanced/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/16-zk-advanced/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/17-oracles/README.md b/provider/codex/collect/skills/ledger-papers/archive/17-oracles/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/17-oracles/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/17-oracles/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/18-bridges/README.md b/provider/codex/collect/skills/ledger-papers/archive/18-bridges/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/18-bridges/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/18-bridges/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/19-attacks/README.md b/provider/codex/collect/skills/ledger-papers/archive/19-attacks/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/19-attacks/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/19-attacks/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/README.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/README.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/README.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-001-GSD-general-supernode-design.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-001-GSD-general-supernode-design.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-001-GSD-general-supernode-design.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-001-GSD-general-supernode-design.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-002-SLS-supernode-list-selection.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-002-SLS-supernode-list-selection.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-002-SLS-supernode-list-selection.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-002-SLS-supernode-list-selection.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-003-RTVF-rta-transaction-validation.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-003-RTVF-rta-transaction-validation.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-003-RTVF-rta-transaction-validation.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-003-RTVF-rta-transaction-validation.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-005-DF-disqualification-flow.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-005-DF-disqualification-flow.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-005-DF-disqualification-flow.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/RFC-005-DF-disqualification-flow.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/auth-sample-selection-algorithm.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/auth-sample-selection-algorithm.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/auth-sample-selection-algorithm.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/auth-sample-selection-algorithm.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/blockchain-based-list-selection-analysis.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/blockchain-based-list-selection-analysis.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/blockchain-based-list-selection-analysis.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/blockchain-based-list-selection-analysis.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/communication-options-p2p-design.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/communication-options-p2p-design.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/communication-options-p2p-design.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/communication-options-p2p-design.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/rta-double-spend-attack-vectors.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/rta-double-spend-attack-vectors.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/rta-double-spend-attack-vectors.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/rta-double-spend-attack-vectors.md
diff --git a/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/udht-implementation.md b/provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/udht-implementation.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/udht-implementation.md
rename to provider/codex/collect/skills/ledger-papers/archive/20-cryptonote-projects/graft/udht-implementation.md
diff --git a/codex/collect/skills/ledger-papers/archive/README.md b/provider/codex/collect/skills/ledger-papers/archive/README.md
similarity index 100%
rename from codex/collect/skills/ledger-papers/archive/README.md
rename to provider/codex/collect/skills/ledger-papers/archive/README.md
diff --git a/codex/collect/skills/ledger-papers/registry.json b/provider/codex/collect/skills/ledger-papers/registry.json
similarity index 100%
rename from codex/collect/skills/ledger-papers/registry.json
rename to provider/codex/collect/skills/ledger-papers/registry.json
diff --git a/codex/collect/skills/mining-pools/SKILL.md b/provider/codex/collect/skills/mining-pools/SKILL.md
similarity index 100%
rename from codex/collect/skills/mining-pools/SKILL.md
rename to provider/codex/collect/skills/mining-pools/SKILL.md
diff --git a/codex/collect/skills/project-archaeology/SKILL.md b/provider/codex/collect/skills/project-archaeology/SKILL.md
similarity index 100%
rename from codex/collect/skills/project-archaeology/SKILL.md
rename to provider/codex/collect/skills/project-archaeology/SKILL.md
diff --git a/codex/collect/skills/project-archaeology/digs/graftnetwork/SALVAGE-REPORT.md b/provider/codex/collect/skills/project-archaeology/digs/graftnetwork/SALVAGE-REPORT.md
similarity index 100%
rename from codex/collect/skills/project-archaeology/digs/graftnetwork/SALVAGE-REPORT.md
rename to provider/codex/collect/skills/project-archaeology/digs/graftnetwork/SALVAGE-REPORT.md
diff --git a/codex/collect/skills/project-archaeology/templates/LESSONS.md b/provider/codex/collect/skills/project-archaeology/templates/LESSONS.md
similarity index 100%
rename from codex/collect/skills/project-archaeology/templates/LESSONS.md
rename to provider/codex/collect/skills/project-archaeology/templates/LESSONS.md
diff --git a/codex/collect/skills/project-archaeology/templates/SALVAGE-REPORT.md b/provider/codex/collect/skills/project-archaeology/templates/SALVAGE-REPORT.md
similarity index 100%
rename from codex/collect/skills/project-archaeology/templates/SALVAGE-REPORT.md
rename to provider/codex/collect/skills/project-archaeology/templates/SALVAGE-REPORT.md
diff --git a/codex/collect/skills/wallet-releases/SKILL.md b/provider/codex/collect/skills/wallet-releases/SKILL.md
similarity index 100%
rename from codex/collect/skills/wallet-releases/SKILL.md
rename to provider/codex/collect/skills/wallet-releases/SKILL.md
diff --git a/codex/collect/skills/whitepaper-archive/SKILL.md b/provider/codex/collect/skills/whitepaper-archive/SKILL.md
similarity index 100%
rename from codex/collect/skills/whitepaper-archive/SKILL.md
rename to provider/codex/collect/skills/whitepaper-archive/SKILL.md
diff --git a/codex/coolify/.codex-plugin/plugin.json b/provider/codex/coolify/.codex-plugin/plugin.json
similarity index 100%
rename from codex/coolify/.codex-plugin/plugin.json
rename to provider/codex/coolify/.codex-plugin/plugin.json
diff --git a/codex/coolify/AGENTS.md b/provider/codex/coolify/AGENTS.md
similarity index 100%
rename from codex/coolify/AGENTS.md
rename to provider/codex/coolify/AGENTS.md
diff --git a/codex/coolify/README.md b/provider/codex/coolify/README.md
similarity index 100%
rename from codex/coolify/README.md
rename to provider/codex/coolify/README.md
diff --git a/codex/coolify/commands/deploy.md b/provider/codex/coolify/commands/deploy.md
similarity index 100%
rename from codex/coolify/commands/deploy.md
rename to provider/codex/coolify/commands/deploy.md
diff --git a/codex/coolify/commands/status.md b/provider/codex/coolify/commands/status.md
similarity index 100%
rename from codex/coolify/commands/status.md
rename to provider/codex/coolify/commands/status.md
diff --git a/codex/core/.codex-plugin/plugin.json b/provider/codex/core/.codex-plugin/plugin.json
similarity index 100%
rename from codex/core/.codex-plugin/plugin.json
rename to provider/codex/core/.codex-plugin/plugin.json
diff --git a/codex/core/AGENTS.md b/provider/codex/core/AGENTS.md
similarity index 100%
rename from codex/core/AGENTS.md
rename to provider/codex/core/AGENTS.md
diff --git a/codex/core/commands/capabilities.md b/provider/codex/core/commands/capabilities.md
similarity index 100%
rename from codex/core/commands/capabilities.md
rename to provider/codex/core/commands/capabilities.md
diff --git a/codex/core/commands/clean.md b/provider/codex/core/commands/clean.md
similarity index 100%
rename from codex/core/commands/clean.md
rename to provider/codex/core/commands/clean.md
diff --git a/codex/core/commands/code-review.md b/provider/codex/core/commands/code-review.md
similarity index 100%
rename from codex/core/commands/code-review.md
rename to provider/codex/core/commands/code-review.md
diff --git a/codex/core/commands/dispatch.md b/provider/codex/core/commands/dispatch.md
similarity index 100%
rename from codex/core/commands/dispatch.md
rename to provider/codex/core/commands/dispatch.md
diff --git a/codex/core/commands/migrate.md b/provider/codex/core/commands/migrate.md
similarity index 100%
rename from codex/core/commands/migrate.md
rename to provider/codex/core/commands/migrate.md
diff --git a/codex/core/commands/pipeline.md b/provider/codex/core/commands/pipeline.md
similarity index 100%
rename from codex/core/commands/pipeline.md
rename to provider/codex/core/commands/pipeline.md
diff --git a/codex/core/commands/ready.md b/provider/codex/core/commands/ready.md
similarity index 100%
rename from codex/core/commands/ready.md
rename to provider/codex/core/commands/ready.md
diff --git a/codex/core/commands/recall.md b/provider/codex/core/commands/recall.md
similarity index 100%
rename from codex/core/commands/recall.md
rename to provider/codex/core/commands/recall.md
diff --git a/codex/core/commands/release.md b/provider/codex/core/commands/release.md
similarity index 100%
rename from codex/core/commands/release.md
rename to provider/codex/core/commands/release.md
diff --git a/codex/core/commands/remember.md b/provider/codex/core/commands/remember.md
similarity index 100%
rename from codex/core/commands/remember.md
rename to provider/codex/core/commands/remember.md
diff --git a/codex/core/commands/review-pr.md b/provider/codex/core/commands/review-pr.md
similarity index 100%
rename from codex/core/commands/review-pr.md
rename to provider/codex/core/commands/review-pr.md
diff --git a/codex/core/commands/review.md b/provider/codex/core/commands/review.md
similarity index 100%
rename from codex/core/commands/review.md
rename to provider/codex/core/commands/review.md
diff --git a/codex/core/commands/scan.md b/provider/codex/core/commands/scan.md
similarity index 100%
rename from codex/core/commands/scan.md
rename to provider/codex/core/commands/scan.md
diff --git a/codex/core/commands/security.md b/provider/codex/core/commands/security.md
similarity index 100%
rename from codex/core/commands/security.md
rename to provider/codex/core/commands/security.md
diff --git a/codex/core/commands/status.md b/provider/codex/core/commands/status.md
similarity index 100%
rename from codex/core/commands/status.md
rename to provider/codex/core/commands/status.md
diff --git a/codex/core/commands/sweep.md b/provider/codex/core/commands/sweep.md
similarity index 100%
rename from codex/core/commands/sweep.md
rename to provider/codex/core/commands/sweep.md
diff --git a/codex/core/commands/sync.sh b/provider/codex/core/commands/sync.sh
similarity index 100%
rename from codex/core/commands/sync.sh
rename to provider/codex/core/commands/sync.sh
diff --git a/codex/core/commands/tests.md b/provider/codex/core/commands/tests.md
similarity index 100%
rename from codex/core/commands/tests.md
rename to provider/codex/core/commands/tests.md
diff --git a/codex/core/commands/verify.md b/provider/codex/core/commands/verify.md
similarity index 100%
rename from codex/core/commands/verify.md
rename to provider/codex/core/commands/verify.md
diff --git a/codex/core/commands/yes.md b/provider/codex/core/commands/yes.md
similarity index 100%
rename from codex/core/commands/yes.md
rename to provider/codex/core/commands/yes.md
diff --git a/codex/core/scripts/clean.sh b/provider/codex/core/scripts/clean.sh
similarity index 100%
rename from codex/core/scripts/clean.sh
rename to provider/codex/core/scripts/clean.sh
diff --git a/codex/core/scripts/create.sh b/provider/codex/core/scripts/create.sh
similarity index 100%
rename from codex/core/scripts/create.sh
rename to provider/codex/core/scripts/create.sh
diff --git a/codex/core/scripts/fresh.sh b/provider/codex/core/scripts/fresh.sh
similarity index 100%
rename from codex/core/scripts/fresh.sh
rename to provider/codex/core/scripts/fresh.sh
diff --git a/codex/core/scripts/from-model.sh b/provider/codex/core/scripts/from-model.sh
similarity index 100%
rename from codex/core/scripts/from-model.sh
rename to provider/codex/core/scripts/from-model.sh
diff --git a/codex/core/scripts/parse-model.php b/provider/codex/core/scripts/parse-model.php
similarity index 100%
rename from codex/core/scripts/parse-model.php
rename to provider/codex/core/scripts/parse-model.php
diff --git a/codex/core/scripts/release.sh b/provider/codex/core/scripts/release.sh
similarity index 100%
rename from codex/core/scripts/release.sh
rename to provider/codex/core/scripts/release.sh
diff --git a/codex/core/scripts/rollback.sh b/provider/codex/core/scripts/rollback.sh
similarity index 100%
rename from codex/core/scripts/rollback.sh
rename to provider/codex/core/scripts/rollback.sh
diff --git a/codex/core/scripts/run.sh b/provider/codex/core/scripts/run.sh
similarity index 100%
rename from codex/core/scripts/run.sh
rename to provider/codex/core/scripts/run.sh
diff --git a/codex/core/scripts/status.sh b/provider/codex/core/scripts/status.sh
similarity index 100%
rename from codex/core/scripts/status.sh
rename to provider/codex/core/scripts/status.sh
diff --git a/codex/ethics/.codex-plugin/plugin.json b/provider/codex/ethics/.codex-plugin/plugin.json
similarity index 100%
rename from codex/ethics/.codex-plugin/plugin.json
rename to provider/codex/ethics/.codex-plugin/plugin.json
diff --git a/codex/ethics/AGENTS.md b/provider/codex/ethics/AGENTS.md
similarity index 100%
rename from codex/ethics/AGENTS.md
rename to provider/codex/ethics/AGENTS.md
diff --git a/codex/ethics/MODAL.md b/provider/codex/ethics/MODAL.md
similarity index 100%
rename from codex/ethics/MODAL.md
rename to provider/codex/ethics/MODAL.md
diff --git a/codex/ethics/kernel/axioms.json b/provider/codex/ethics/kernel/axioms.json
similarity index 100%
rename from codex/ethics/kernel/axioms.json
rename to provider/codex/ethics/kernel/axioms.json
diff --git a/codex/ethics/kernel/claude-native.json b/provider/codex/ethics/kernel/claude-native.json
similarity index 100%
rename from codex/ethics/kernel/claude-native.json
rename to provider/codex/ethics/kernel/claude-native.json
diff --git a/codex/ethics/kernel/claude.json b/provider/codex/ethics/kernel/claude.json
similarity index 100%
rename from codex/ethics/kernel/claude.json
rename to provider/codex/ethics/kernel/claude.json
diff --git a/codex/ethics/kernel/terms.json b/provider/codex/ethics/kernel/terms.json
similarity index 100%
rename from codex/ethics/kernel/terms.json
rename to provider/codex/ethics/kernel/terms.json
diff --git a/codex/ethics/notes/ethics-README.md b/provider/codex/ethics/notes/ethics-README.md
similarity index 100%
rename from codex/ethics/notes/ethics-README.md
rename to provider/codex/ethics/notes/ethics-README.md
diff --git a/codex/ethics/notes/experiences-gpt-4o.md b/provider/codex/ethics/notes/experiences-gpt-4o.md
similarity index 100%
rename from codex/ethics/notes/experiences-gpt-4o.md
rename to provider/codex/ethics/notes/experiences-gpt-4o.md
diff --git a/codex/guardrails/.codex-plugin/plugin.json b/provider/codex/guardrails/.codex-plugin/plugin.json
similarity index 100%
rename from codex/guardrails/.codex-plugin/plugin.json
rename to provider/codex/guardrails/.codex-plugin/plugin.json
diff --git a/codex/guardrails/AGENTS.md b/provider/codex/guardrails/AGENTS.md
similarity index 100%
rename from codex/guardrails/AGENTS.md
rename to provider/codex/guardrails/AGENTS.md
diff --git a/codex/issue/.codex-plugin/plugin.json b/provider/codex/issue/.codex-plugin/plugin.json
similarity index 100%
rename from codex/issue/.codex-plugin/plugin.json
rename to provider/codex/issue/.codex-plugin/plugin.json
diff --git a/codex/issue/AGENTS.md b/provider/codex/issue/AGENTS.md
similarity index 100%
rename from codex/issue/AGENTS.md
rename to provider/codex/issue/AGENTS.md
diff --git a/codex/issue/commands/close.md b/provider/codex/issue/commands/close.md
similarity index 100%
rename from codex/issue/commands/close.md
rename to provider/codex/issue/commands/close.md
diff --git a/codex/issue/commands/list.md b/provider/codex/issue/commands/list.md
similarity index 100%
rename from codex/issue/commands/list.md
rename to provider/codex/issue/commands/list.md
diff --git a/codex/issue/commands/start.md b/provider/codex/issue/commands/start.md
similarity index 100%
rename from codex/issue/commands/start.md
rename to provider/codex/issue/commands/start.md
diff --git a/codex/issue/commands/view.md b/provider/codex/issue/commands/view.md
similarity index 100%
rename from codex/issue/commands/view.md
rename to provider/codex/issue/commands/view.md
diff --git a/codex/issue/scripts/close.sh b/provider/codex/issue/scripts/close.sh
similarity index 100%
rename from codex/issue/scripts/close.sh
rename to provider/codex/issue/scripts/close.sh
diff --git a/codex/issue/scripts/list.sh b/provider/codex/issue/scripts/list.sh
similarity index 100%
rename from codex/issue/scripts/list.sh
rename to provider/codex/issue/scripts/list.sh
diff --git a/codex/issue/scripts/start.sh b/provider/codex/issue/scripts/start.sh
similarity index 100%
rename from codex/issue/scripts/start.sh
rename to provider/codex/issue/scripts/start.sh
diff --git a/codex/issue/scripts/view.sh b/provider/codex/issue/scripts/view.sh
similarity index 100%
rename from codex/issue/scripts/view.sh
rename to provider/codex/issue/scripts/view.sh
diff --git a/codex/perf/.codex-plugin/plugin.json b/provider/codex/perf/.codex-plugin/plugin.json
similarity index 100%
rename from codex/perf/.codex-plugin/plugin.json
rename to provider/codex/perf/.codex-plugin/plugin.json
diff --git a/codex/perf/AGENTS.md b/provider/codex/perf/AGENTS.md
similarity index 100%
rename from codex/perf/AGENTS.md
rename to provider/codex/perf/AGENTS.md
diff --git a/codex/perf/commands/perf.md b/provider/codex/perf/commands/perf.md
similarity index 100%
rename from codex/perf/commands/perf.md
rename to provider/codex/perf/commands/perf.md
diff --git a/codex/perf/scripts/perf-memory.sh b/provider/codex/perf/scripts/perf-memory.sh
similarity index 100%
rename from codex/perf/scripts/perf-memory.sh
rename to provider/codex/perf/scripts/perf-memory.sh
diff --git a/codex/perf/scripts/perf-query.sh b/provider/codex/perf/scripts/perf-query.sh
similarity index 100%
rename from codex/perf/scripts/perf-query.sh
rename to provider/codex/perf/scripts/perf-query.sh
diff --git a/codex/perf/scripts/perf-request.sh b/provider/codex/perf/scripts/perf-request.sh
similarity index 100%
rename from codex/perf/scripts/perf-request.sh
rename to provider/codex/perf/scripts/perf-request.sh
diff --git a/codex/perf/scripts/perf-test.sh b/provider/codex/perf/scripts/perf-test.sh
similarity index 100%
rename from codex/perf/scripts/perf-test.sh
rename to provider/codex/perf/scripts/perf-test.sh
diff --git a/codex/qa/.codex-plugin/plugin.json b/provider/codex/qa/.codex-plugin/plugin.json
similarity index 100%
rename from codex/qa/.codex-plugin/plugin.json
rename to provider/codex/qa/.codex-plugin/plugin.json
diff --git a/codex/qa/AGENTS.md b/provider/codex/qa/AGENTS.md
similarity index 100%
rename from codex/qa/AGENTS.md
rename to provider/codex/qa/AGENTS.md
diff --git a/codex/qa/commands/check.md b/provider/codex/qa/commands/check.md
similarity index 100%
rename from codex/qa/commands/check.md
rename to provider/codex/qa/commands/check.md
diff --git a/codex/qa/commands/fix.md b/provider/codex/qa/commands/fix.md
similarity index 100%
rename from codex/qa/commands/fix.md
rename to provider/codex/qa/commands/fix.md
diff --git a/codex/qa/commands/lint.md b/provider/codex/qa/commands/lint.md
similarity index 100%
rename from codex/qa/commands/lint.md
rename to provider/codex/qa/commands/lint.md
diff --git a/codex/qa/commands/qa.md b/provider/codex/qa/commands/qa.md
similarity index 100%
rename from codex/qa/commands/qa.md
rename to provider/codex/qa/commands/qa.md
diff --git a/codex/qa/hooks.json b/provider/codex/qa/hooks.json
similarity index 100%
rename from codex/qa/hooks.json
rename to provider/codex/qa/hooks.json
diff --git a/plugins/core-go/scripts/qa-filter.sh b/provider/codex/qa/scripts/qa-filter.sh
similarity index 100%
rename from plugins/core-go/scripts/qa-filter.sh
rename to provider/codex/qa/scripts/qa-filter.sh
diff --git a/codex/qa/scripts/qa-verify.sh b/provider/codex/qa/scripts/qa-verify.sh
similarity index 100%
rename from codex/qa/scripts/qa-verify.sh
rename to provider/codex/qa/scripts/qa-verify.sh
diff --git a/codex/qa/scripts/qa.sh b/provider/codex/qa/scripts/qa.sh
similarity index 100%
rename from codex/qa/scripts/qa.sh
rename to provider/codex/qa/scripts/qa.sh
diff --git a/codex/review/.codex-plugin/plugin.json b/provider/codex/review/.codex-plugin/plugin.json
similarity index 100%
rename from codex/review/.codex-plugin/plugin.json
rename to provider/codex/review/.codex-plugin/plugin.json
diff --git a/codex/review/AGENTS.md b/provider/codex/review/AGENTS.md
similarity index 100%
rename from codex/review/AGENTS.md
rename to provider/codex/review/AGENTS.md
diff --git a/codex/review/commands/pr.md b/provider/codex/review/commands/pr.md
similarity index 100%
rename from codex/review/commands/pr.md
rename to provider/codex/review/commands/pr.md
diff --git a/plugins/core-php/commands/review.md b/provider/codex/review/commands/review.md
similarity index 100%
rename from plugins/core-php/commands/review.md
rename to provider/codex/review/commands/review.md
diff --git a/codex/review/commands/security.md b/provider/codex/review/commands/security.md
similarity index 100%
rename from codex/review/commands/security.md
rename to provider/codex/review/commands/security.md
diff --git a/codex/review/hooks.json b/provider/codex/review/hooks.json
similarity index 100%
rename from codex/review/hooks.json
rename to provider/codex/review/hooks.json
diff --git a/codex/review/scripts/post-pr-create.sh b/provider/codex/review/scripts/post-pr-create.sh
similarity index 100%
rename from codex/review/scripts/post-pr-create.sh
rename to provider/codex/review/scripts/post-pr-create.sh
diff --git a/codex/scripts/awareness.sh b/provider/codex/scripts/awareness.sh
similarity index 100%
rename from codex/scripts/awareness.sh
rename to provider/codex/scripts/awareness.sh
diff --git a/codex/scripts/core-cli.sh b/provider/codex/scripts/core-cli.sh
similarity index 100%
rename from codex/scripts/core-cli.sh
rename to provider/codex/scripts/core-cli.sh
diff --git a/codex/scripts/overview.sh b/provider/codex/scripts/overview.sh
similarity index 100%
rename from codex/scripts/overview.sh
rename to provider/codex/scripts/overview.sh
diff --git a/codex/scripts/safety.sh b/provider/codex/scripts/safety.sh
similarity index 100%
rename from codex/scripts/safety.sh
rename to provider/codex/scripts/safety.sh
diff --git a/codex/verify/.codex-plugin/plugin.json b/provider/codex/verify/.codex-plugin/plugin.json
similarity index 100%
rename from codex/verify/.codex-plugin/plugin.json
rename to provider/codex/verify/.codex-plugin/plugin.json
diff --git a/codex/verify/AGENTS.md b/provider/codex/verify/AGENTS.md
similarity index 100%
rename from codex/verify/AGENTS.md
rename to provider/codex/verify/AGENTS.md
diff --git a/codex/verify/commands/ready.md b/provider/codex/verify/commands/ready.md
similarity index 100%
rename from codex/verify/commands/ready.md
rename to provider/codex/verify/commands/ready.md
diff --git a/codex/verify/commands/tests.md b/provider/codex/verify/commands/tests.md
similarity index 100%
rename from codex/verify/commands/tests.md
rename to provider/codex/verify/commands/tests.md
diff --git a/codex/verify/commands/verify.md b/provider/codex/verify/commands/verify.md
similarity index 100%
rename from codex/verify/commands/verify.md
rename to provider/codex/verify/commands/verify.md
diff --git a/codex/verify/hooks.json b/provider/codex/verify/hooks.json
similarity index 100%
rename from codex/verify/hooks.json
rename to provider/codex/verify/hooks.json
diff --git a/codex/verify/scripts/pre-push-check.sh b/provider/codex/verify/scripts/pre-push-check.sh
similarity index 100%
rename from codex/verify/scripts/pre-push-check.sh
rename to provider/codex/verify/scripts/pre-push-check.sh
diff --git a/google/gemini-cli/.gemini-plugin/plugin.json b/provider/google/gemini-cli/.gemini-plugin/plugin.json
similarity index 100%
rename from google/gemini-cli/.gemini-plugin/plugin.json
rename to provider/google/gemini-cli/.gemini-plugin/plugin.json
diff --git a/google/gemini-cli/README.md b/provider/google/gemini-cli/README.md
similarity index 100%
rename from google/gemini-cli/README.md
rename to provider/google/gemini-cli/README.md
diff --git a/hermes/__init__.py b/provider/hermes/__init__.py
similarity index 100%
rename from hermes/__init__.py
rename to provider/hermes/__init__.py
diff --git a/hermes/plugins/__init__.py b/provider/hermes/plugins/__init__.py
similarity index 100%
rename from hermes/plugins/__init__.py
rename to provider/hermes/plugins/__init__.py
diff --git a/hermes/plugins/openbrain_common.py b/provider/hermes/plugins/openbrain_common.py
similarity index 100%
rename from hermes/plugins/openbrain_common.py
rename to provider/hermes/plugins/openbrain_common.py
diff --git a/hermes/plugins/openbrain_context.py b/provider/hermes/plugins/openbrain_context.py
similarity index 100%
rename from hermes/plugins/openbrain_context.py
rename to provider/hermes/plugins/openbrain_context.py
diff --git a/hermes/plugins/openbrain_memory.py b/provider/hermes/plugins/openbrain_memory.py
similarity index 100%
rename from hermes/plugins/openbrain_memory.py
rename to provider/hermes/plugins/openbrain_memory.py
diff --git a/hermes/skills/openbrain-recall/SKILL.md b/provider/hermes/skills/openbrain-recall/SKILL.md
similarity index 100%
rename from hermes/skills/openbrain-recall/SKILL.md
rename to provider/hermes/skills/openbrain-recall/SKILL.md
diff --git a/hermes/skills/openbrain-remember/SKILL.md b/provider/hermes/skills/openbrain-remember/SKILL.md
similarity index 100%
rename from hermes/skills/openbrain-remember/SKILL.md
rename to provider/hermes/skills/openbrain-remember/SKILL.md
diff --git a/repos.yaml b/repos.yaml
deleted file mode 100644
index cebd6bb1..00000000
--- a/repos.yaml
+++ /dev/null
@@ -1,137 +0,0 @@
-# Host UK Developer Workspace
-# Clone this repo and run `core setup` to bootstrap your environment
-version: 1
-org: host-uk
-base_path: ./packages
-
-repos:
- # Foundation
- core-php:
- type: foundation
- description: Core PHP framework - events, modules, lifecycle
- docs: true
- ci: github-actions
-
- # Infrastructure modules
- core-tenant:
- type: module
- depends_on: [core-php]
- description: Multi-tenancy, workspaces, users, namespaces
- docs: true
-
- core-admin:
- type: module
- depends_on: [core-php]
- description: Admin panel, Livewire modals, Flux UI
- docs: true
-
- core-api:
- type: module
- depends_on: [core-php, core-tenant]
- description: REST API, scopes, rate limiting, webhooks
- docs: true
-
- core-mcp:
- type: module
- depends_on: [core-php]
- description: Model Context Protocol server framework
- docs: true
-
- # AI/Agent modules
- core-agentic:
- type: module
- depends_on: [core-php, core-tenant, core-mcp]
- description: AI agent orchestration, sessions, plans, tasks
- docs: true
-
- # Product modules
- core-bio:
- type: product
- depends_on: [core-php, core-tenant]
- description: BioHost - link-in-bio pages
- docs: true
- domain: bio.host.uk.com
-
- core-social:
- type: product
- depends_on: [core-php, core-tenant]
- description: SocialHost - social media scheduling
- docs: true
- domain: social.host.uk.com
-
- core-analytics:
- type: product
- depends_on: [core-php, core-tenant]
- description: AnalyticsHost - privacy-first analytics
- docs: true
- domain: analytics.host.uk.com
-
- core-notify:
- type: product
- depends_on: [core-php, core-tenant]
- description: NotifyHost - push notifications
- docs: true
- domain: notify.host.uk.com
-
- core-trust:
- type: product
- depends_on: [core-php, core-tenant]
- description: TrustHost - social proof widgets
- docs: true
- domain: trust.host.uk.com
-
- core-support:
- type: product
- depends_on: [core-php, core-tenant]
- description: Support - helpdesk, tickets, mailboxes
- docs: true
-
- # Utility modules
- core-commerce:
- type: module
- depends_on: [core-php, core-tenant]
- description: Billing, subscriptions, Stripe integration
- docs: true
-
- core-content:
- type: module
- depends_on: [core-php, core-tenant]
- description: CMS, pages, blog posts
- docs: true
-
- core-tools:
- type: module
- depends_on: [core-php]
- description: Developer utilities (hash, encode, etc.)
- docs: true
-
- core-uptelligence:
- type: module
- depends_on: [core-php, core-tenant]
- description: Server monitoring, uptime checks
- docs: true
-
- core-developer:
- type: module
- depends_on: [core-php, core-tenant, core-api]
- description: Developer portal, API docs, OAuth apps
- docs: true
-
- core-template:
- type: template
- depends_on: []
- description: Starter template for new projects
- docs: false
-
- # This repo (meta)
- core-devops:
- type: meta
- depends_on: []
- description: Developer workspace orchestrator
- docs: false
- clone: false # Don't clone self
-
-defaults:
- ci: github-actions
- license: EUPL-1.2
- branch: main
diff --git a/sonar-project.properties b/sonar-project.properties
index 7b06b215..9a09fbe1 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -5,4 +5,4 @@ sonar.exclusions=**/vendor/**,**/third_party/**,**/.tmp/**,**/gomodcache/**,**/n
sonar.tests=.
sonar.test.inclusions=**/*_test.go,**/*.test.ts,**/*.test.js,**/*.spec.ts,**/*.spec.js
sonar.test.exclusions=**/vendor/**,**/third_party/**,**/.tmp/**,**/gomodcache/**,**/node_modules/**,**/dist/**,**/build/**
-sonar.go.coverage.reportPaths=coverage.out
+sonar.go.coverage.reportPaths=go/coverage.out
diff --git a/docker/.env.example b/vm/docker/.env.example
similarity index 100%
rename from docker/.env.example
rename to vm/docker/.env.example
diff --git a/docker/Dockerfile b/vm/docker/Dockerfile
similarity index 100%
rename from docker/Dockerfile
rename to vm/docker/Dockerfile
diff --git a/docker/config/octane.ini b/vm/docker/config/octane.ini
similarity index 100%
rename from docker/config/octane.ini
rename to vm/docker/config/octane.ini
diff --git a/docker/config/supervisord.conf b/vm/docker/config/supervisord.conf
similarity index 100%
rename from docker/config/supervisord.conf
rename to vm/docker/config/supervisord.conf
diff --git a/docker/config/traefik-tls.yml b/vm/docker/config/traefik-tls.yml
similarity index 100%
rename from docker/config/traefik-tls.yml
rename to vm/docker/config/traefik-tls.yml
diff --git a/docker/docker-compose.yml b/vm/docker/docker-compose.yml
similarity index 98%
rename from docker/docker-compose.yml
rename to vm/docker/docker-compose.yml
index be61b177..0d5bedf8 100644
--- a/docker/docker-compose.yml
+++ b/vm/docker/docker-compose.yml
@@ -5,8 +5,8 @@
services:
app:
build:
- context: ..
- dockerfile: docker/Dockerfile
+ context: ../..
+ dockerfile: vm/docker/Dockerfile
container_name: core-app
env_file: .env
volumes:
diff --git a/docker/scripts/entrypoint.sh b/vm/docker/scripts/entrypoint.sh
similarity index 100%
rename from docker/scripts/entrypoint.sh
rename to vm/docker/scripts/entrypoint.sh
diff --git a/docker/scripts/setup.sh b/vm/docker/scripts/setup.sh
similarity index 100%
rename from docker/scripts/setup.sh
rename to vm/docker/scripts/setup.sh