diff --git a/README.md b/README.md index 9309355..a0196e5 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ The keyword heuristic in `model-selector.ts` serves as a **fallback only**, used ## State management -All project state lives in a single `memory/projects.json` file in the orchestrator's workspace, keyed by Telegram group ID: +All project state lives in a single `projects/projects.json` file in the orchestrator's workspace, keyed by Telegram group ID: ```json { @@ -235,7 +235,7 @@ Pick up a task from the issue queue for a DEV or QA worker. 2. Validates no active worker for this role 3. Fetches issue from issue tracker, verifies correct label state 4. Assigns tier (LLM-chosen via `model` param, keyword heuristic fallback) -5. Loads role instructions from `roles//.md` (fallback: `roles/default/.md`) +5. Loads prompt instructions from `projects/prompts//.md` 6. Looks up existing session for assigned tier (session-per-tier) 7. Transitions label (e.g. `To Do` → `Doing`) 8. Creates session via Gateway RPC if new (`sessions.patch`) @@ -357,13 +357,13 @@ Register a new project with DevClaw. Creates all required issue tracker labels ( 2. Resolves repo path, auto-detects GitHub/GitLab, and verifies access 3. Creates all 8 state labels (idempotent — safe to run on existing projects) 4. Adds project entry to `projects.json` with empty worker state and `autoChain: false` -5. Scaffolds role instruction files: `roles//dev.md` and `roles//qa.md` (copied from `roles/default/`) +5. Scaffolds prompt instruction files: `projects/prompts//dev.md` and `projects/prompts//qa.md` 6. Writes audit log entry 7. Returns announcement text ## Audit logging -Every tool call automatically appends an NDJSON entry to `memory/audit.log`. No manual logging required from the orchestrator agent. +Every tool call automatically appends an NDJSON entry to `log/audit.log`. No manual logging required from the orchestrator agent. ```jsonl {"ts":"2026-02-08T10:30:00Z","event":"task_pickup","project":"my-webapp","issue":42,"role":"dev","tier":"medior","sessionAction":"send"} @@ -438,25 +438,26 @@ Restrict tools to your orchestrator agent only: > DevClaw uses an `IssueProvider` interface to abstract issue tracker operations. GitLab (via `glab` CLI) and GitHub (via `gh` CLI) are supported — the provider is auto-detected from the git remote URL. Jira is planned. -## Role instructions +## Prompt instructions Workers receive role-specific instructions appended to their task message. `project_register` scaffolds editable files: ``` workspace/ -├── roles/ -│ ├── default/ ← sensible defaults (created once) -│ │ ├── dev.md -│ │ └── qa.md -│ ├── my-webapp/ ← per-project overrides (edit to customize) -│ │ ├── dev.md -│ │ └── qa.md -│ └── another-project/ -│ ├── dev.md -│ └── qa.md +├── projects/ +│ ├── projects.json ← project state +│ └── prompts/ +│ ├── my-webapp/ ← per-project prompts (edit to customize) +│ │ ├── dev.md +│ │ └── qa.md +│ └── another-project/ +│ ├── dev.md +│ └── qa.md +├── log/ +│ └── audit.log ← NDJSON event log ``` -`task_pickup` loads `roles//.md` with fallback to `roles/default/.md`. Edit the per-project files to customize worker behavior — for example, adding project-specific deployment steps or test commands. +`task_pickup` loads `projects/prompts//.md`. Edit these files to customize worker behavior per project — for example, adding project-specific deployment steps or test commands. ## Requirements diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 45a4908..fa102a2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -473,11 +473,11 @@ Every piece of data and where it lives: │ task_create → create issue in tracker │ │ queue_status → read labels + read state │ │ session_health → check sessions + fix zombies │ -│ project_register → labels + roles + state init (one-time) │ +│ project_register → labels + prompts + state init (one-time) │ └─────────────────────────────────────────────────────────────────┘ ↕ atomic file I/O ↕ OpenClaw CLI (plugin shells out) ┌────────────────────────────────┐ ┌──────────────────────────────┐ -│ memory/projects.json │ │ OpenClaw Gateway + CLI │ +│ projects/projects.json │ │ OpenClaw Gateway + CLI │ │ │ │ (called by plugin, not agent)│ │ Per project: │ │ │ │ dev: │ │ openclaw gateway call │ @@ -493,7 +493,7 @@ Every piece of data and where it lives: └────────────────────────────────┘ └──────────────────────────────┘ ↕ append-only ┌─────────────────────────────────────────────────────────────────┐ -│ memory/audit.log (observability) │ +│ log/audit.log (observability) │ │ │ │ NDJSON, one line per event: │ │ task_pickup, task_complete, model_selection, │ @@ -607,7 +607,7 @@ Provider selection is handled by `createProvider()` in `lib/providers/index.ts`. | Plugin source | `~/.openclaw/extensions/devclaw/` | Plugin code | | Plugin manifest | `~/.openclaw/extensions/devclaw/openclaw.plugin.json` | Plugin registration | | Agent config | `~/.openclaw/openclaw.json` | Agent definition + tool permissions + tier config | -| Worker state | `~/.openclaw/workspace-/memory/projects.json` | Per-project DEV/QA state | -| Audit log | `~/.openclaw/workspace-/memory/audit.log` | NDJSON event log | +| Worker state | `~/.openclaw/workspace-/projects/projects.json` | Per-project DEV/QA state | +| Audit log | `~/.openclaw/workspace-/log/audit.log` | NDJSON event log | | Session transcripts | `~/.openclaw/agents//sessions/.jsonl` | Conversation history per session | | Git repos | `~/git//` | Project source code | diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index c25c005..acc0a45 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -147,7 +147,7 @@ Tell the orchestrator agent to register a new project: The agent calls `project_register`, which atomically: - Validates the repo and auto-detects GitHub/GitLab from remote - Creates all 8 state labels (idempotent) -- Scaffolds role instruction files (`roles//dev.md` and `qa.md`) +- Scaffolds prompt instruction files (`projects/prompts//dev.md` and `qa.md`) - Adds the project entry to `projects.json` with `autoChain: false` - Logs the registration event @@ -251,7 +251,7 @@ Change which model powers each tier in `openclaw.json`: | Channel binding analysis | Plugin (`analyze_channel_bindings`) | Detects channel conflicts, validates channel configuration | | Channel binding migration | Plugin (`devclaw_setup` with `migrateFrom`) | Automatically moves channel-wide bindings between agents | | Label setup | Plugin (`project_register`) | 8 labels, created idempotently via `IssueProvider` | -| Role file scaffolding | Plugin (`project_register`) | Creates `roles//dev.md` and `qa.md` from defaults | +| Prompt file scaffolding | Plugin (`project_register`) | Creates `projects/prompts//dev.md` and `qa.md` | | Project registration | Plugin (`project_register`) | Entry in `projects.json` with empty worker state | | Telegram group setup | You (once per project) | Add bot to group | | Issue creation | Plugin (`task_create`) | Orchestrator or workers create issues from chat | @@ -260,7 +260,7 @@ Change which model powers each tier in `openclaw.json`: | State management | Plugin | Atomic read/write to `projects.json` | | Session management | Plugin | Creates, reuses, and dispatches to sessions via CLI. Agent never touches session tools. | | Task completion | Plugin (`task_complete`) | Workers self-report. Auto-chains if enabled. | -| Role instructions | Plugin (`task_pickup`) | Loaded from `roles//.md`, appended to task message | +| Prompt instructions | Plugin (`task_pickup`) | Loaded from `projects/prompts//.md`, appended to task message | | Audit logging | Plugin | Automatic NDJSON append per tool call | | Zombie detection | Plugin | `session_health` checks active vs alive | | Queue scanning | Plugin | `queue_status` queries issue tracker per project | diff --git a/docs/QA_WORKFLOW.md b/docs/QA_WORKFLOW.md index 5f12ba4..d0abfe9 100644 --- a/docs/QA_WORKFLOW.md +++ b/docs/QA_WORKFLOW.md @@ -95,9 +95,8 @@ As of [current date], QA workers are instructed via role templates to: - Include specific details about what was tested - Document results, environment, and any notes -Role templates affected: -- `roles/default/qa.md` -- `roles/devclaw/qa.md` +Prompt templates affected: +- `projects/prompts//qa.md` - All project-specific QA templates should follow this pattern ## Best Practices diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 14513ae..bebb933 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -35,7 +35,7 @@ The pipeline definition replaces the hardcoded `Doing → To Test → Testing ### Open questions - How do custom labels map? Generate from role names, or let users define? -- Should roles have their own instruction files (`roles//.md`) — yes, this already works +- Should roles have their own instruction files (`projects/prompts//.md`) — yes, this already works - How to handle parallel roles (e.g. frontend + backend DEV in parallel before QA)? --- diff --git a/docs/TESTING.md b/docs/TESTING.md index 7d393bf..35a2837 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -29,7 +29,7 @@ npm run test:ui **What's tested:** - First-time agent creation with default models - Channel binding creation (telegram/whatsapp) -- Workspace file generation (AGENTS.md, HEARTBEAT.md, roles/, memory/) +- Workspace file generation (AGENTS.md, HEARTBEAT.md, projects/, log/) - Plugin configuration initialization - Error handling: channel not configured - Error handling: channel disabled diff --git a/lib/audit.ts b/lib/audit.ts index 3741c36..4aaea20 100644 --- a/lib/audit.ts +++ b/lib/audit.ts @@ -10,7 +10,7 @@ export async function log( event: string, data: Record, ): Promise { - const filePath = join(workspaceDir, "memory", "audit.log"); + const filePath = join(workspaceDir, "log", "audit.log"); const entry = JSON.stringify({ ts: new Date().toISOString(), event, diff --git a/lib/context-guard.ts b/lib/context-guard.ts index c4d3118..e2671f5 100644 --- a/lib/context-guard.ts +++ b/lib/context-guard.ts @@ -140,7 +140,7 @@ You're in a **Telegram/WhatsApp group** bound to ${context.projectName ? `projec } /** - * Find project name by matching groupId in memory/projects.json. + * Find project name by matching groupId in projects/projects.json. * The groupId (Telegram or WhatsApp) is the KEY in the projects Record. */ async function findProjectByGroupId( @@ -150,7 +150,7 @@ async function findProjectByGroupId( if (!workspaceDir) return undefined; try { - const projectsPath = path.join(workspaceDir, "memory", "projects.json"); + const projectsPath = path.join(workspaceDir, "projects", "projects.json"); const raw = await fs.readFile(projectsPath, "utf-8"); const data = JSON.parse(raw) as { projects: Record; diff --git a/lib/dispatch.ts b/lib/dispatch.ts index e881794..90678b7 100644 --- a/lib/dispatch.ts +++ b/lib/dispatch.ts @@ -53,8 +53,7 @@ export type DispatchResult = { /** * Build the task message sent to a worker session. - * Reads role-specific instructions from workspace/roles//.md - * with fallback to workspace/roles/default/.md. + * Reads role-specific instructions from workspace/projects/prompts//.md. */ export async function buildTaskMessage(opts: { workspaceDir: string; @@ -196,10 +195,8 @@ export async function dispatchTask( async function loadRoleInstructions( workspaceDir: string, projectName: string, role: "dev" | "qa", ): Promise { - const projectFile = path.join(workspaceDir, "roles", projectName, `${role}.md`); - const defaultFile = path.join(workspaceDir, "roles", "default", `${role}.md`); - try { return await fs.readFile(projectFile, "utf-8"); } catch { /* fallback */ } - try { return await fs.readFile(defaultFile, "utf-8"); } catch { /* none */ } + const projectFile = path.join(workspaceDir, "projects", "prompts", projectName, `${role}.md`); + try { return await fs.readFile(projectFile, "utf-8"); } catch { /* none */ } return ""; } diff --git a/lib/projects.ts b/lib/projects.ts index ff77f5c..eb5d51b 100644 --- a/lib/projects.ts +++ b/lib/projects.ts @@ -72,7 +72,7 @@ export function getSessionForTier( } function projectsPath(workspaceDir: string): string { - return path.join(workspaceDir, "memory", "projects.json"); + return path.join(workspaceDir, "projects", "projects.json"); } export async function readProjects(workspaceDir: string): Promise { diff --git a/lib/setup/workspace.ts b/lib/setup/workspace.ts index 36582c4..650112e 100644 --- a/lib/setup/workspace.ts +++ b/lib/setup/workspace.ts @@ -8,8 +8,6 @@ import path from "node:path"; import { AGENTS_MD_TEMPLATE, HEARTBEAT_MD_TEMPLATE, - DEFAULT_DEV_INSTRUCTIONS, - DEFAULT_QA_INSTRUCTIONS, } from "../templates.js"; /** @@ -27,31 +25,19 @@ export async function scaffoldWorkspace(workspacePath: string): Promise/.md\` in the workspace (with fallback to \`roles/default/.md\`). \`project_register\` scaffolds these files automatically — edit them to customize worker behavior per project. +Workers receive role-specific instructions appended to their task message. These are loaded from \`projects/prompts//.md\` in the workspace. \`project_register\` scaffolds these files automatically — edit them to customize worker behavior per project. ### Heartbeats diff --git a/lib/tools/project-register.ts b/lib/tools/project-register.ts index e304905..832972b 100644 --- a/lib/tools/project-register.ts +++ b/lib/tools/project-register.ts @@ -20,32 +20,11 @@ import { DEFAULT_DEV_INSTRUCTIONS, DEFAULT_QA_INSTRUCTIONS } from "../templates. import { detectContext, generateGuardrails } from "../context-guard.js"; /** - * Ensure default role files exist, then copy them into the project's role directory. + * Scaffold project-specific prompt files. * Returns true if files were created, false if they already existed. */ -async function scaffoldRoleFiles(workspaceDir: string, projectName: string): Promise { - const defaultDir = path.join(workspaceDir, "roles", "default"); - const projectDir = path.join(workspaceDir, "roles", projectName); - - // Ensure default role files exist - await fs.mkdir(defaultDir, { recursive: true }); - - const defaultDev = path.join(defaultDir, "dev.md"); - const defaultQa = path.join(defaultDir, "qa.md"); - - try { - await fs.access(defaultDev); - } catch { - await fs.writeFile(defaultDev, DEFAULT_DEV_INSTRUCTIONS, "utf-8"); - } - - try { - await fs.access(defaultQa); - } catch { - await fs.writeFile(defaultQa, DEFAULT_QA_INSTRUCTIONS, "utf-8"); - } - - // Create project-specific role files (copy from default if not exist) +async function scaffoldPromptFiles(workspaceDir: string, projectName: string): Promise { + const projectDir = path.join(workspaceDir, "projects", "prompts", projectName); await fs.mkdir(projectDir, { recursive: true }); const projectDev = path.join(projectDir, "dev.md"); @@ -55,14 +34,14 @@ async function scaffoldRoleFiles(workspaceDir: string, projectName: string): Pro try { await fs.access(projectDev); } catch { - await fs.copyFile(defaultDev, projectDev); + await fs.writeFile(projectDev, DEFAULT_DEV_INSTRUCTIONS, "utf-8"); created = true; } try { await fs.access(projectQa); } catch { - await fs.copyFile(defaultQa, projectQa); + await fs.writeFile(projectQa, DEFAULT_QA_INSTRUCTIONS, "utf-8"); created = true; } @@ -212,8 +191,8 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) { await writeProjects(workspaceDir, data); - // 6. Scaffold role files - const rolesCreated = await scaffoldRoleFiles(workspaceDir, name); + // 6. Scaffold prompt files + const promptsCreated = await scaffoldPromptFiles(workspaceDir, name); // 7. Audit log await auditLog(workspaceDir, "project_register", { @@ -226,8 +205,8 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) { }); // 8. Return announcement - const rolesNote = rolesCreated ? " Role files scaffolded." : ""; - const announcement = `📋 Project "${name}" registered for group ${groupName}. Labels created.${rolesNote} Ready for tasks.`; + const promptsNote = promptsCreated ? " Prompt files scaffolded." : ""; + const announcement = `📋 Project "${name}" registered for group ${groupName}. Labels created.${promptsNote} Ready for tasks.`; return jsonResult({ success: true, @@ -237,7 +216,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) { baseBranch, deployBranch, labelsCreated: 8, - rolesScaffolded: rolesCreated, + promptsScaffolded: promptsCreated, announcement, ...(contextInfo && { contextInfo }), contextGuidance: generateGuardrails(context), diff --git a/lib/tools/setup.ts b/lib/tools/setup.ts index 8dba277..eab0d4d 100644 --- a/lib/tools/setup.ts +++ b/lib/tools/setup.ts @@ -14,7 +14,7 @@ export function createSetupTool(api: OpenClawPluginApi) { return (ctx: ToolContext) => ({ name: "setup", label: "Setup", - description: `Execute DevClaw setup. Creates AGENTS.md, HEARTBEAT.md, roles, memory/projects.json, and model tier config. Optionally creates a new agent with channel binding. Called after onboard collects configuration.`, + description: `Execute DevClaw setup. Creates AGENTS.md, HEARTBEAT.md, projects/projects.json, and model tier config. Optionally creates a new agent with channel binding. Called after onboard collects configuration.`, parameters: { type: "object", properties: {