diff --git a/docs/prd/_scan-manifest.json b/docs/prd/_scan-manifest.json new file mode 100644 index 0000000..009d3a0 --- /dev/null +++ b/docs/prd/_scan-manifest.json @@ -0,0 +1,492 @@ +{ + "manifestVersion": "1.0", + "generatedAt": "2026-05-12", + "scope": "LemonBoss only — multi-agent autonomous dev system (Company / OKR / Jobs / Pipeline / Tasks / Agent Chat / Templates / Quota). ArenaSK content (Skill marketplace, Arena ELO, Benchmark, Master /api/master, Innovator, Open API, Evolution) is EXCLUDED.", + + "project": { + "name": "LemonBoss", + "legacyName": "ArenaSK", + "rootPath": "/Users/yibo/Documents/lemonAI/lemonboss", + "nodeVersion": ">=18", + "packageManager": "pnpm (workspace)", + "moduleAlias": { "@server": "server" } + }, + + "entryPoints": { + "agentServer": { + "file": "server/agent-server.js", + "port": 3010, + "startCommand": "bash start.sh", + "responsibilities": [ + "express app with body-sanitizer middleware (strips \\x00-\\x1F before JSON.parse)", + "CORS allow http://localhost:5173", + "Mounts /api/agent /api/company /api/goals /api/tasks /api/health" + ], + "mountedRoutes": [ + { "prefix": "/api/agent", "file": "server/routes/agent-chat.js" }, + { "prefix": "/api/company", "file": "server/routes/company.js" }, + { "prefix": "/api/goals", "file": "server/routes/goals.js" }, + { "prefix": "/api/tasks", "file": "server/routes/tasks.js" }, + { "prefix": "/api/health", "inline": "GET → { status: ok, service: agent-chat }" } + ], + "notMounted": "auth, users, plugins (only in server/index.js, out of LemonBoss-subset scope)" + }, + "binAutoDev": { + "file": "bin/auto-dev or server/src/cli/auto-dev.js", + "purpose": "Standalone CLI to run an end-to-end autonomous dev task (e.g. `auto-dev \"implement fibonacci\"`)" + }, + "startScript": { + "file": "start.sh", + "steps": [ + "load nvm", + "kill any process on :3010 and :5173", + "PORT=3010 node server/agent-server.js &", + "cd web && npx vite --host &" + ], + "note": "Does NOT build frontend, sync skills, or start bots — those are separate scripts." + } + }, + + "configFiles": { + "rootPackage": "package.json (workspaces: packages/*, agent-bridge, server, web)", + "scripts": { + "setup": "cp -n .env.example .env && npm install && cd server && npm install && cd ../web && npm install", + "dev": "concurrently 'npm run server' 'npm run web' (full server/index.js — NOT LemonBoss-only)", + "server": "cd server && npm start", + "web": "cd web && npm run dev", + "test:unit": "vitest run", + "test:multi-cli": "node server/src/tests/scripts/test-multi-cli.js", + "test:browser-logger": "node server/src/modules/browser-logger/__tests__/test-runner.js", + "test:design-validator": "node server/src/modules/design-validator/__tests__/test-runner.js", + "example": "node server/src/cli/auto-dev.js '...'" + }, + "serverConfig": { + "file": "server/config.js", + "fields": ["port (3010)", "host (0.0.0.0)", "workspacesDir", "skillsCodeDir (LemonBoss IGNORES — ArenaSK)", "trainedSkillsDir", "conversationsDir", "cors (5173, 3010)", "openApi.apiKeys (ArenaSK)"] + }, + "env": ".env from .env.example (port, CORS, GITHUB/GOOGLE OAuth client IDs, OPEN_API_KEYS, dir overrides)" + }, + + "routes": { + "_summary": "LemonBoss-relevant prefixes only. Full route listing per file below.", + + "agentChat": { + "file": "server/routes/agent-chat.js", + "prefix": "/api/agent", + "endpoints": [ + { "method": "GET", "path": "/list", "purpose": "list all agents (system + per company)" }, + { "method": "GET", "path": "/quota", "purpose": "token consumption & cost stats" }, + { "method": "POST", "path": "/:agentId/chat", "purpose": "chat with agent (SSE stream)" }, + { "method": "POST", "path": "/:agentId/chat/:convId/stop", "purpose": "interrupt running chat" }, + { "method": "GET", "path": "/:agentId/info", "purpose": "agent metadata + config" }, + { "method": "PUT", "path": "/:agentId/heartbeat", "purpose": "update heartbeat config" }, + { "method": "GET", "path": "/:agentId/cron", "purpose": "read cron.json" }, + { "method": "PUT", "path": "/:agentId/cron", "purpose": "update cron.json" }, + { "method": "GET", "path": "/:agentId/todo", "purpose": "read todo.md" }, + { "method": "PUT", "path": "/:agentId/todo", "purpose": "update todo.md" }, + { "method": "GET", "path": "/:agentId/plan", "purpose": "read plan.md" }, + { "method": "PUT", "path": "/:agentId/plan", "purpose": "update plan.md" }, + { "method": "GET", "path": "/:agentId/conversations", "purpose": "list conversations" }, + { "method": "GET", "path": "/:agentId/conversations/:convId", "purpose": "get conversation history" }, + { "method": "DELETE", "path": "/:agentId/conversations/:convId", "purpose": "delete conversation" }, + { "method": "GET", "path": "/:agentId/conversations/:convId/sub-logs", "purpose": "list sub-agent logs" }, + { "method": "GET", "path": "/:agentId/conversations/:convId/sub-logs/:logFile", "purpose": "fetch a sub-log file" }, + { "method": "GET", "path": "/:agentId/files", "purpose": "list agent dir files" }, + { "method": "GET", "path": "/:agentId/file/*", "purpose": "read a file in agent dir" }, + { "method": "PUT", "path": "/:agentId/file/*", "purpose": "write a file in agent dir" }, + { "method": "DELETE", "path": "/:agentId/file/*", "purpose": "delete a file in agent dir" }, + { "method": "GET", "path": "/:agentId/bootstrap/:filename", "purpose": "read bootstrap file (agent.md, soul.md, ...)" }, + { "method": "POST", "path": "/:agentId/memory/cerebellum", "purpose": "save cerebellum memory snippet" }, + { "method": "POST", "path": "/:agentId/memory/consolidate", "purpose": "consolidate session into long-term memory" }, + { "method": "POST", "path": "/:agentId/memory/daily-report", "purpose": "generate daily memory report" }, + { "method": "POST", "path": "/:agentId/upload", "purpose": "multipart upload (multer, ≤20 files)" }, + { "method": "GET", "path": "/:agentId/uploads/:convId", "purpose": "list uploads in conversation" }, + { "method": "GET", "path": "/:agentId/uploads/:convId/:filename", "purpose": "download upload" }, + { "method": "DELETE", "path": "/:agentId/uploads/:convId/:filename", "purpose": "delete upload" }, + { "method": "POST", "path": "/scaffold", "purpose": "scaffold a new agent skeleton" }, + { "method": "POST", "path": "/task/dispatch", "purpose": "Master dispatches a task to a sub-agent" }, + { "method": "POST", "path": "/task/report", "purpose": "agent reports task done/failed" }, + { "method": "POST", "path": "/:agentId/task/transition", "purpose": "agent transitions to a stage state" }, + { "method": "POST", "path": "/task/reject", "purpose": "supervisor rejects a task output" }, + { "method": "POST", "path": "/human-request", "purpose": "agent escalates to human" } + ], + "notes": [ + "Heavy file ~2470 lines — many endpoints share helpers (resolveAgentDir, getConvDir).", + "Chat streams as SSE with token-level events.", + "Task dispatch/report endpoints bridge into PipelineService.advanceStep when callbacks match a pipeline run." + ] + }, + + "company": { + "file": "server/routes/company.js", + "prefix": "/api/company", + "endpoints": [ + { "method": "POST", "path": "/create", "purpose": "create company" }, + { "method": "GET", "path": "/list", "purpose": "list companies" }, + { "method": "GET", "path": "/templates", "purpose": "list agent templates" }, + { "method": "POST", "path": "/templates", "purpose": "create template" }, + { "method": "PATCH", "path": "/templates/:templateId", "purpose": "update template" }, + { "method": "DELETE", "path": "/templates/:templateId", "purpose": "delete template" }, + { "method": "POST", "path": "/templates/:templateId/generate", "purpose": "scaffold from template" }, + { "method": "POST", "path": "/templates/:templateId/train", "purpose": "train template (skill evolution loop)" }, + { "method": "POST", "path": "/templates/:templateId/skills/upload", "purpose": "upload skill files to template (≤50)" }, + { "method": "POST", "path": "/templates/:templateId/provision", "purpose": "provision template runtime" }, + { "method": "GET", "path": "/templates/:templateId/logs", "purpose": "fetch training logs" }, + { "method": "GET", "path": "/templates/:templateId/files", "purpose": "list template files" }, + { "method": "GET", "path": "/templates/:templateId/files/content", "purpose": "read template file" }, + { "method": "PUT", "path": "/templates/:templateId/files/content", "purpose": "write template file" }, + { "method": "GET", "path": "/:companyId", "purpose": "company detail" }, + { "method": "PUT", "path": "/:companyId", "purpose": "update company" }, + { "method": "DELETE", "path": "/:companyId", "purpose": "delete company" }, + { "method": "POST", "path": "/:companyId/shutdown", "purpose": "soft-shutdown company" }, + { "method": "POST", "path": "/:companyId/start", "purpose": "start running" }, + { "method": "GET", "path": "/:companyId/status", "purpose": "status + heartbeat info" }, + { "method": "PUT", "path": "/:companyId/agents/:agentId/heartbeat", "purpose": "update agent heartbeat config" }, + { "method": "POST", "path": "/:companyId/agents/:agentId/heartbeat/start", "purpose": "start agent heartbeat" }, + { "method": "POST", "path": "/:companyId/agents/:agentId/heartbeat/stop", "purpose": "stop agent heartbeat" }, + { "method": "GET", "path": "/:companyId/agents/:agentId/skills", "purpose": "list agent skills" }, + { "method": "POST", "path": "/:companyId/hire", "purpose": "hire agent from template or scaffold" }, + { "method": "POST", "path": "/:companyId/fire", "purpose": "suspend agent" }, + { "method": "POST", "path": "/:companyId/deposit-template", "purpose": "deposit trained agent (≥95) as new template" }, + { "method": "GET", "path": "/:companyId/agents", "purpose": "list company agents" }, + { "method": "GET", "path": "/:companyId/goals", "purpose": "company OKR list" }, + { "method": "POST", "path": "/:companyId/goals", "purpose": "create goal" }, + { "method": "GET", "path": "/:companyId/goals/:goalId", "purpose": "goal detail" }, + { "method": "PUT", "path": "/:companyId/goals/:goalId", "purpose": "update goal" }, + { "method": "POST", "path": "/:companyId/goals/:goalId/nodes", "purpose": "add OKR node" }, + { "method": "PUT", "path": "/:companyId/goals/:goalId/nodes/:nodeId","purpose": "update OKR node" }, + { "method": "GET", "path": "/:companyId/goals/:goalId/progress", "purpose": "OKR progress %" }, + { "method": "POST", "path": "/:companyId/pipelines", "purpose": "create pipeline" }, + { "method": "GET", "path": "/:companyId/pipelines", "purpose": "list pipelines" }, + { "method": "GET", "path": "/:companyId/pipelines/:pipelineId", "purpose": "pipeline detail" }, + { "method": "PUT", "path": "/:companyId/pipelines/:pipelineId", "purpose": "update pipeline" }, + { "method": "POST", "path": "/:companyId/pipelines/:pipelineId/run", "purpose": "start a run" }, + { "method": "GET", "path": "/:companyId/pipelines/runs/list", "purpose": "list runs" }, + { "method": "GET", "path": "/:companyId/pipelines/runs/:runId", "purpose": "run detail" }, + { "method": "POST", "path": "/:companyId/pipelines/runs/:runId/advance", "purpose": "advance to next step" }, + { "method": "GET", "path": "/:companyId/pipelines/runs/:runId/current", "purpose": "current ready step" }, + { "method": "POST", "path": "/:companyId/pipelines/runs/:runId/reject", "purpose": "COO rejects a step output" }, + { "method": "GET", "path": "/:companyId/schedule", "purpose": "list schedule slots" }, + { "method": "POST", "path": "/:companyId/schedule/slots", "purpose": "add slot" }, + { "method": "PUT", "path": "/:companyId/schedule/slots/:slotId", "purpose": "update slot" }, + { "method": "DELETE", "path": "/:companyId/schedule/slots/:slotId", "purpose": "delete slot" }, + { "method": "GET", "path": "/:companyId/data/metrics", "purpose": "raw metrics" }, + { "method": "GET", "path": "/:companyId/data/trending", "purpose": "trending" }, + { "method": "GET", "path": "/:companyId/data/attribution", "purpose": "attribution" }, + { "method": "GET", "path": "/:companyId/files", "purpose": "company file list" }, + { "method": "GET", "path": "/:companyId/files/content", "purpose": "read file" }, + { "method": "PUT", "path": "/:companyId/files/content", "purpose": "write file" }, + { "method": "GET", "path": "/:companyId/workspace/files", "purpose": "workspace file list" }, + { "method": "GET", "path": "/:companyId/workspace/files/content", "purpose": "workspace read" }, + { "method": "PUT", "path": "/:companyId/workspace/files/content", "purpose": "workspace write" }, + { "method": "GET", "path": "/:companyId/messages", "purpose": "activity / message log" }, + { "method": "GET", "path": "/:companyId/stats", "purpose": "company stats" }, + { "method": "GET", "path": "/:companyId/jobs", "purpose": "list pipeline-run-derived jobs (paged, filtered)" }, + { "method": "GET", "path": "/:companyId/jobs/:jobId", "purpose": "job detail" } + ] + }, + + "goals": { + "file": "server/routes/goals.js", + "prefix": "/api/goals", + "endpoints": [ + { "method": "GET", "path": "/", "purpose": "list goals (global / index.json)" }, + { "method": "POST", "path": "/", "purpose": "create goal" }, + { "method": "GET", "path": "/:goalId", "purpose": "full goal tree" }, + { "method": "PUT", "path": "/:goalId", "purpose": "update goal" }, + { "method": "POST", "path": "/:goalId/nodes", "purpose": "add node" }, + { "method": "GET", "path": "/:goalId/nodes/:nodeId", "purpose": "get node + parent context" }, + { "method": "PUT", "path": "/:goalId/nodes/:nodeId", "purpose": "update node status" }, + { "method": "GET", "path": "/:goalId/progress", "purpose": "leaf-aggregated progress" } + ], + "idScheme": "Dot-separated hierarchy: G-001 → G-001.1 (Objective) → G-001.1.1 (KeyResult) → G-001.1.1.1 (SubTask)" + }, + + "tasks": { + "file": "server/routes/tasks.js", + "prefix": "/api/tasks", + "endpoints": [ + { "method": "GET", "path": "/", "purpose": "aggregated cross-agent task list (10s TTL cache)" }, + { "method": "GET", "path": "/:taskId/timeline", "purpose": "merged timeline of conversation events for task (and optional children)" } + ] + } + }, + + "services": { + "_lemonbossRelevant": { + "company-service.js": "Company CRUD + Agent hiring (template-first → scaffold fallback) + depositTemplate + resolveAgentDir static", + "company-heartbeat-service.js":"Company-level heartbeat: scans active pipelines (cron), drives running pipeline steps, executes schedule slots, triggers CEO heartbeat, generates daily report, sends Master alerts", + "heartbeat-service.js": "Agent-level heartbeat: setInterval(intervalMinutes) → POST /api/agent/{id}/chat with system heartbeat payload", + "goals.js": "GoalsService: file-backed goal tree (data/companies/{id}/goals/ or data/goals/), progress recalc, change log", + "pipeline-service.js": "Pipeline CRUD + Run lifecycle (startRun → markStepStarted → advanceStep → loop|completed) + rejectStep + findActiveRunStep", + "task-service.js": "Aggregates conversations across system/agents + data/companies/*/agents into task groups; infers parent/child via 4 strategies", + "schedule-service.js": "Schedule slot CRUD + getDueSlots(companyId)", + "memory-service.js": "Cerebellum + consolidation + daily-report endpoints", + "datasource-service.js": "External data source connections", + "file-parser.js": "Markdown/PDF/Docx parsing for uploads", + "conversation-logger.js": "Append-only messages.log writer", + "user-db.js": "users.sqlite — single users table (used by auth/users routes — only via server/index.js, not agent-server.js)", + "job-manager.js": "Job execution queue (LemonBoss: pipeline-run-jobs)", + "schedule-service.js": "Cron / schedule slot storage", + "task-queue.js": "Queue primitive used by job-manager", + "skill-task-manager.js": "[ArenaSK-leaning] tracks skill execution tasks — touched if /api/company/{id}/agents/{id}/skills is hit" + }, + "_arenaSkOnly_skipInDocs": [ + "skill-db.js", "skill-registry.js", "skill-dir-manager.js", "skill-packager.js", + "benchmark-db.js", "benchmark-runner.js", "evolution-loop.js", + "retrain-pipeline.js", "retrain-watchdog.js", "innovator-service.js", + "claude-chat-service.js" + ] + }, + + "core": { + "_dir": "server/src/core/", + "orchestrator.js": "Multi-agent loop: Master ↔ Sub-agent, format validation, maxRepairs, completion detection", + "agent-manager.js": "Unified Agent call gateway: config lookup → hook orchestration → protocol → retry", + "scheduler.js": "Task scheduling / timing primitive", + "session-pool.js": "Acquire / release / cleanup persistent ACP sessions", + "prompt-builder.js": "Build system prompt from agent.md + soul.md + identity.md + user.md + tools.md + heartbeat.md + memory + cron", + "runtime-resolver.js": "Dynamic agent type + env inference at run-time", + "master-session.js": "Master Agent persistent Claude CLI session — multi-turn conversation + tool dispatch (call_agent, orchestrate, ...)", + "stream-executor.js": "Stream prompt through chosen ACP protocol", + "parser.js": "Parse [TOOL_CALL:], JSON, markdown agent output", + "result-tracker.js": "Execution state machine + checkpoint", + "hook-runner.js": "Bootstrap hooks (on_init / on_failure / on_complete)", + "protocol-factory.js": "Create ClaudeCliProtocol / WebSocketProtocol instances", + "skill-config-loader.js": "Scan skill dir + merge config.json + inject into prompt", + "prompts.js": "Master/Sub-agent prompt templates", + "evolution-memory.js": "Training-history store (shared with ArenaSK)", + "logging/": { + "LogManager.js": "Multi-session log dispatch", + "LogSession.js": "Per-session log buffer", + "backends": ["BaseBackend.js", "ConsoleBackend.js", "FileBackend.js", "CompositeBackend.js"] + } + }, + + "engine": { + "_dir": "server/src/engine/", + "index.js": "Barrel exports", + "process-guard.js": "Two-layer timeout guard (fresh / resume defaults) for Claude CLI subprocess", + "run-registry.js": "Active run table (runId → meta, output stream refs, status); DEFAULT_MAX_EXITED retention", + "command-queue.js": "Multi-lane FIFO queue with generation-based cancellation (CommandLaneClearedError on clearAll)", + "failover-error.js": "Error classification → FailoverReason, RETRYABLE_REASONS", + "idle-timer.js": "Streaming idle-watchdog (per-byte reset)" + }, + + "plugins": { + "_dir": "server/src/plugins/", + "plugin-manager.js": "Discover / load / enable / disable plugins; emit hooks", + "agent-bridge.js": "Cross-plugin agent message relay", + "config-bridge.js": "Plugin config merge + validation", + "bootstrap.js": "Plugin init sequence", + "openclaw.plugin.json": "OpenClaw plugin manifest (sibling to plugins/)" + }, + + "tools": { + "_dir": "server/src/tools/", + "call-agent-tool.js": "Master tool: call_sub_agent / list_agents", + "orchestrate-tool.js": "Master tool: orchestrate (multi-step plan)", + "train-skill-tool.js": "Master tool: trigger skill training (Hunt → Digest → Evolving) [ArenaSK-leaning]", + "read-tools.js": "read_checkpoint / read_log / list_retrain_jobs", + "tool-registry.js": "Tool register & dispatch", + "marketplace-tools.js":"[ArenaSK only — skip in LemonBoss docs]" + }, + + "packages": { + "acp-core": "ACP Protocol Stack — Protocol × Transport × Launcher 4D architecture (ClaudeCliProtocol over stdio, WebSocketProtocol, transports, launchers).", + "agent-core": "Agent class + AgentScaffold; directory-driven agent bootstrap unified across system/agents and data/companies/*/agents (ESM).", + "plugin-bootstrap": "OpenClaw plugin loader — discovery + load sequence.", + "evolution": "[ArenaSK only — skip]" + }, + + "middleware": { + "_dir": "server/middleware/", + "jwt-auth.js": "requireAuth + optionalAuth + requireAdmin guards; signAndSetCookie / clearCookie", + "api-key-auth.js": "X-API-Key validation (ArenaSK Open API)", + "api-access-logger.js": "Request/response log line" + }, + + "frontend": { + "_dir": "web/src/", + "entry": "main.js → createApp(App) → router", + "router": { + "file": "web/src/router/index.js", + "routes": [ + { "path": "/", "redirect": "/agent/master" }, + { "path": "/agents/:agentId?", "redirect": "/agent/$agentId or /agent/agent-creator" }, + { "path": "/agent/:agentId/:convId?", "name": "AgentChat", "component": "AgentChat.vue" }, + { "path": "/:id/agents/:agentId?/:convId?", "name": "CompanyAgentChat", "component": "AgentChat.vue" }, + { "path": "/templates/:agentId?", "name": "TemplateAgents", "component": "TemplateAgents.vue" }, + { "path": "/company", "name": "CompanyList", "component": "CompanyDashboard.vue" }, + { "path": "/:id/:tab(overview|agents|pipelines|goals|sop|okr|team|files|messages|skills|jobs|workspace)", "name": "CompanyDetail", "component": "CompanyDashboard.vue" }, + { "path": "/okr", "name": "OkrDashboard", "component": "OkrDashboard.vue" }, + { "path": "/skills", "name": "SkillsMarket", "component": "SkillsMarket.vue", "lemonBoss": false }, + { "path": "/jobs", "name": "JobsPanel", "component": "JobsPanel.vue" }, + { "path": "/tasks/:taskId?", "name": "TaskTimeline", "component": "TaskTimeline.vue" }, + { "path": "/pipelines", "name": "PipelineEditor", "component": "PipelineEditor.vue" }, + { "path": "/quota", "name": "QuotaMonitor", "component": "QuotaMonitor.vue" } + ] + }, + "composables": [ + "useTheme.js — dark / light theme state", + "useCompanies.js — companies list cache + active company selection" + ], + "i18n": "[N/A] No vue-i18n integration found; UI strings are inline Chinese.", + "views": { + "_pagesInScope": 9, + "AgentChat.vue": { "lines": 1289, "purpose": "Multi-agent chat — 3-col (agent sidebar / conv list / message stream)", "apis": ["/api/agent/list", "/api/agent/{id}/chat", "/api/agent/{id}/conversations", "/api/agent/{id}/info"] }, + "AgentList.vue": { "lines": 300, "purpose": "Agent registry browser with file tree + preview", "apis": ["/api/agent/list", "/api/agent/{id}/files", "/api/agent/{id}/file/*"] }, + "CompanyDashboard.vue": { "lines": 2396, "purpose": "Company control center — tabs: Overview / Agents / Pipelines / Goals / SOP / OKR / Team / Files / Messages / Skills / Jobs / Workspace", "apis": ["/api/company/*"] }, + "JobsPanel.vue": { "lines": 710, "purpose": "Job queue (pipeline-derived) with filters & detail modal", "apis": ["/api/company/{id}/jobs", "/api/company/{id}/jobs/:jobId"] }, + "OkrDashboard.vue": { "lines": 352, "purpose": "OKR list + decomposition tree with progress", "apis": ["/api/goals", "/api/company/{id}/goals"] }, + "PipelineEditor.vue": { "lines": 791, "purpose": "Pipeline definition grid + run controls", "apis": ["/api/company/{id}/pipelines", "/api/company/{id}/pipelines/runs/*"] }, + "QuotaMonitor.vue": { "lines": 526, "purpose": "Token / cost analytics with trend charts + agent leaderboard", "apis": ["/api/agent/quota"] }, + "SkillsMarket.vue": { "lines": 571, "purpose": "[ArenaSK — out of scope]" }, + "TaskTimeline.vue": { "lines": 736, "purpose": "Cross-agent task list + merged event timeline (children expandable)", "apis": ["/api/tasks", "/api/tasks/:taskId/timeline"] }, + "TemplateAgents.vue": { "lines": 1019, "purpose": "Template library — list, search, filter, sort; detail viewer with editable bootstrap files", "apis": ["/api/company/templates", "/api/company/templates/:templateId/*"] } + } + }, + + "data": { + "users.sqlite": { + "table": "users", + "columns": [ + "id INTEGER PRIMARY KEY", + "email TEXT UNIQUE", + "password_hash TEXT", + "display_name TEXT", + "avatar_url TEXT", + "provider TEXT (local | github | google)", + "provider_id TEXT", + "github_login TEXT", + "role TEXT (user | admin)", + "api_key TEXT", + "created_at TEXT", + "last_login TEXT" + ], + "indexes": ["email", "api_key", "(provider, provider_id)"] + }, + "fileSystem": { + "data/companies/{companyId}/": { + "company.json": "Company metadata (id, name, status, ceo, agents[], heartbeat, config)", + "agents/{agentId}/": "Per-agent dir (bootstrap files + memory + conversations)", + "goals/{G-id}/": "OKR tree (goal.json + progress.json + history.log)", + "pipelines/": "Pipeline definitions + runs/", + "pipelines/runs/{R-id}.json": "Pipeline run state", + "workspace/": "Company shared workspace (assets/ + output/)", + "docs/": "Company documentation", + "logs/pipeline_runs/":"Pipeline execution traces", + "logs/audit/": "State change audit" + }, + "data/goals/{G-id}/": "Global (companyId-less) goals (back-compat)", + "system/agents/{agentId}/": "System-level agent (master, hunter, digester, evolver, agent-creator, innovator, screenwriter, ...)", + "system/agents/.tasks/{taskId}.json": "Task metadata used by TaskService", + "system/templates/{templateId}/": "Agent skeleton template (agent.md, soul.md, identity.md, user.md, tools.md, heartbeat.md, plan.md, todo.md, cron.json, config.json + skills/ + .claude/)", + "system/templates/_registry.json": "Template registry (benchmarkScore, usedCount, ...)", + "server/conversations/": "Reserved — actual conversations stored under each agent dir" + }, + "agentDir": { + "bootstrapFiles": ["agent.md", "soul.md", "identity.md", "user.md", "tools.md", "heartbeat.md", "plan.md", "todo.md", "cron.json", "config.json"], + "runtimeSubdirs": ["memory/", "conversations/", "skills/", ".claude/", "workspace/"], + "memoryStructure": ["memory.md (working memory)", "evolving/v{n}/ (versioned skill iterations)", "benchmark/v{n}/ (benchmark snapshots)"], + "conversationLayout": "{convId}/{meta.json, history.json, messages.log, sub-logs/, uploads/}" + } + }, + + "protocols": { + "agentChatSSE": { + "path": "POST /api/agent/:agentId/chat", + "transport": "Server-Sent Events (text/event-stream)", + "frames": [ + "{ type: 'start', conversationId }", + "{ type: 'token', delta }", + "{ type: 'tool', name, input }", + "{ type: 'result', output }", + "{ type: 'done', reason }", + "{ type: 'error', message }" + ], + "interrupt": "POST /api/agent/:agentId/chat/:convId/stop" + }, + "bodySanitizer": "Strips \\x00-\\x1F (preserves \\n/\\r/\\t as escapes) from JSON body before JSON.parse. Reasons: external agents posting curl payloads with raw control chars." + }, + + "engineSubsystems": { + "agenticLoop": "Master agent → propose plan → call_sub_agent → sub-agent runs → result → Master decides next → completion", + "hiringPipeline": "Template found → copy skeleton + inject company context → mark active. Template not found → scaffold (AgentScaffold.create) → status=training. After score ≥95 → depositTemplate.", + "companyHeartbeat": "setInterval(intervalMinutes) → _onHeartbeat → (a) _checkAndStartPipelines (cron) (b) _driveActiveRuns (trigger ready steps & timeout) (c) _executeScheduledTasks (d) _triggerCeoHeartbeat (e) _checkDailyReport (08:00). Errors → _sendAlertToMaster.", + "pipelineLifecycle": "startRun (step[0]=ready, rest=waiting) → markStepStarted (in_progress) → advanceStep (done → next ready / completed; failed → run.status=failed; loop → new run) → rejectStep (step.rejections.push, reset to ready, inject __rejection.instruction)", + "okrIdScheme": "Dot-separated: G-001 (Goal, L1) → G-001.1 (Objective, L2) → G-001.1.1 (KeyResult, L3) → G-001.1.1.1 (SubTask, L4+). parseNodeId returns {goalId, level, parentId, levelName}.", + "taskInference": "TaskService._inferParentChild — 4 strategies: (1) explicit .tasks meta parentTaskId (2) T-* from active fromAgent's tpl-train (3) T-* from master via recent conv-* (4) tpl-train-*/tpl-gen-* from time-adjacent master conv-*." + }, + + "architectureDocs": { + "docs/LEMONBOSS-SYSTEM-DESIGN.md": [ + "System Vision (道-法-术-器)", + "Four-layer Agent Architecture (Master → CXO → Role → Skill)", + "Agent Atomic Model (6 bootstrap files)", + "Agentic Loop Engine", + "Stratified Meta-Capabilities", + "Agent Sandbox Autonomy", + "Inter-Agent Communication", + "Self-Iteration Closed Loop", + "Initialization Flow", + "Runtime Concurrency", + "Directory Structure", + "Data Model", + "API Design", + "Innovator Meta-Agent", + "Agent Conversation Architecture", + "Skill Orchestration", + "Local-File-First Encapsulation", + "Tool Isolation", + "Scaffold-First Agent Creation", + "FDE Training Pipeline (Hunt→Digest→Evolving)", + "Agent Memory System", + "Active Heartbeat", + "Company Layer", + "Bootstrap Mechanism", + "Automated Hiring Orchestration" + ], + "docs/architecture-v5.md": [ + "Cognitive Stack (Cerebrum / Cerebellum / Hippocampus)", + "OKR Goal Management (dot-separated ID hierarchy)", + "Multi-Tenant Company Architecture", + "Phase 1-6 status (Phase 7 Cognitive pending)" + ], + "docs/task_timeline_design.md": "Task timeline cross-agent merge design", + "docs/system-design/": "Subsystem deep-dives", + "docs/user-guides/": "User-facing docs" + }, + + "outputPlan": { + "siteRoot": "docs/prd/", + "chapters": [ + "01-overview", + "02-prd", + "03-feature-tree", + "04-workflows", + "05-prototype", + "06-sdd-model", + "07-sdd-view", + "08-sdd-control", + "09-sdd-process", + "10-reproducibility" + ], + "indexShell": "index.html + assets/{app.css, app.js, lunr.min.js, lunr.zh.js, search-index.json}", + "interaction": "left 280px 3-level menu + right iframe + top search → results tab; hash router (#/06-sdd-model/company-entity); dark/light toggle; mobile burger", + "diagrams": "Mermaid inline + SVG (no CDN)", + "search": "lunr.js with Chinese bigram tokenizer; index built from chapter HTML titles + body text", + "verification": "python3 -m http.server 8080 -d docs/prd → manual click-through 9 page menus; search 'Company / OKR / Pipeline / Agent / Task' all hit" + }, + + "scopeGuard_DoNotMention": [ + "skill-db / skill-registry / skill-dir-manager / skill-packager", + "benchmark-db / benchmark-runner", + "evolution-loop / retrain-pipeline / retrain-watchdog", + "/api/open/* /api/arena/* /api/innovator/* /api/master/* /api/evolution", + "Skill marketplace UI (SkillsMarket.vue)", + "ELO ranking, six-dimensional benchmark", + "WhatsApp bot, Lark integration", + "Master Agent chat at /api/master/chat (Master tools still used internally via agent-server's /api/agent/master/chat — keep that)" + ] +} diff --git a/docs/prd/assets/app.css b/docs/prd/assets/app.css new file mode 100644 index 0000000..c18b6a9 --- /dev/null +++ b/docs/prd/assets/app.css @@ -0,0 +1,395 @@ +/* ============================================================ + LemonBoss PRD/SDD doc site — global styles + ============================================================ */ + +:root { + --bg: #fafaf7; + --bg-2: #ffffff; + --bg-3: #f3f3ee; + --fg: #1d1d1f; + --fg-2: #5a5a60; + --fg-3: #8a8a92; + --border: #e3e3dd; + --border-2: #d4d4cd; + --accent: #f5a524; /* lemon */ + --accent-2: #e69500; + --accent-soft: #fff4dc; + --link: #0a66c2; + --code-bg: #f5f5ef; + --code-fg: #2d2d33; + --shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 12px rgba(0,0,0,.04); + --shadow-lg: 0 12px 32px rgba(0,0,0,.08); + --radius: 8px; + --radius-sm: 4px; + --side-w: 280px; + --top-h: 52px; + --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Roboto Mono", monospace; + --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", + "Hiragino Sans GB", "Microsoft YaHei", Helvetica, Arial, sans-serif; +} + +[data-theme="dark"] { + --bg: #16161a; + --bg-2: #1d1d22; + --bg-3: #25252b; + --fg: #e8e8ea; + --fg-2: #b0b0b8; + --fg-3: #76767e; + --border: #2d2d34; + --border-2: #3a3a42; + --accent: #f5a524; + --accent-2: #ffbe3d; + --accent-soft: #3a2f15; + --link: #6db4ff; + --code-bg: #25252b; + --code-fg: #e6e6e8; + --shadow: 0 1px 2px rgba(0,0,0,.4), 0 4px 12px rgba(0,0,0,.3); + --shadow-lg: 0 12px 32px rgba(0,0,0,.6); +} + +* { box-sizing: border-box; } + +html, body { + margin: 0; + padding: 0; + height: 100%; + font-family: var(--sans); + font-size: 14px; + background: var(--bg); + color: var(--fg); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { color: var(--link); text-decoration: none; } +a:hover { text-decoration: underline; } + +button { + background: none; + border: none; + color: inherit; + font: inherit; + cursor: pointer; +} + +/* ─── Topbar ─── */ +.topbar { + position: fixed; + top: 0; left: 0; right: 0; + height: var(--top-h); + background: var(--bg-2); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + gap: 12px; + padding: 0 16px; + z-index: 50; +} + +.burger { + display: none; + padding: 6px; + border-radius: var(--radius-sm); + color: var(--fg-2); +} +.burger:hover { background: var(--bg-3); color: var(--fg); } + +.brand { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + color: var(--fg); + flex-shrink: 0; +} +.brand .lemon { font-size: 20px; } +.brand .title { font-size: 15px; } +.brand .subtitle { + font-size: 12px; + font-weight: 400; + color: var(--fg-3); + margin-left: 6px; + padding-left: 10px; + border-left: 1px solid var(--border); +} + +.search-wrap { + flex: 1; + max-width: 520px; + margin: 0 auto; + position: relative; +} +.search-wrap input { + width: 100%; + height: 32px; + padding: 0 36px 0 12px; + border: 1px solid var(--border); + background: var(--bg); + border-radius: var(--radius-sm); + color: var(--fg); + font-family: var(--sans); + outline: none; + transition: border-color .15s, box-shadow .15s; +} +.search-wrap input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft); +} +.kbd { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + font-family: var(--mono); + font-size: 11px; + color: var(--fg-3); + background: var(--bg-3); + border: 1px solid var(--border); + border-radius: 3px; + padding: 1px 5px; + pointer-events: none; +} + +.theme-toggle { + padding: 6px; + border-radius: var(--radius-sm); + color: var(--fg-2); +} +.theme-toggle:hover { background: var(--bg-3); color: var(--fg); } +.theme-toggle .ico-moon { display: none; } +[data-theme="dark"] .theme-toggle .ico-sun { display: none; } +[data-theme="dark"] .theme-toggle .ico-moon { display: block; } + +/* ─── Layout ─── */ +.layout { + position: fixed; + top: var(--top-h); + left: 0; right: 0; bottom: 0; + display: flex; +} + +/* ─── Sidebar ─── */ +.sidebar { + width: var(--side-w); + background: var(--bg-2); + border-right: 1px solid var(--border); + overflow-y: auto; + overflow-x: hidden; + flex-shrink: 0; +} +.sidebar::-webkit-scrollbar { width: 8px; } +.sidebar::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; } + +.menu { padding: 8px 0; } +.menu-chapter { margin-bottom: 2px; } +.menu-chapter > .menu-link { + font-weight: 600; + color: var(--fg); +} +.menu-section > .menu-link { + font-weight: 500; + color: var(--fg-2); + padding-left: 28px; +} +.menu-sub > .menu-link { + font-weight: 400; + color: var(--fg-3); + padding-left: 44px; + font-size: 13px; +} + +.menu-link { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + cursor: pointer; + text-decoration: none; + user-select: none; + border-radius: 4px; + margin: 1px 6px; + transition: background .12s; + line-height: 1.4; +} +.menu-link:hover { + background: var(--bg-3); + text-decoration: none; +} +.menu-link.active { + background: var(--accent-soft); + color: var(--accent-2); + font-weight: 600; +} +[data-theme="dark"] .menu-link.active { color: var(--accent); } + +.caret { + width: 12px; + text-align: center; + font-size: 10px; + color: var(--fg-3); + flex-shrink: 0; + transition: transform .15s; +} +.menu-chapter.collapsed > .menu-link .caret, +.menu-section.collapsed > .menu-link .caret { + transform: rotate(-90deg); +} + +.menu-chapter > .children, +.menu-section > .children { + overflow: hidden; + max-height: 4000px; + transition: max-height .2s ease-out; +} +.menu-chapter.collapsed > .children, +.menu-section.collapsed > .children { + max-height: 0; +} + +.menu-num { + font-family: var(--mono); + font-size: 11px; + color: var(--fg-3); + margin-right: 4px; + flex-shrink: 0; +} +.menu-link.active .menu-num { color: var(--accent-2); } + +/* ─── Content ─── */ +.content { + flex: 1; + display: flex; + flex-direction: column; + background: var(--bg); + min-width: 0; +} + +.tabs { + height: 36px; + display: flex; + border-bottom: 1px solid var(--border); + background: var(--bg-2); + padding: 0 12px; + gap: 4px; + flex-shrink: 0; +} +.tab { + padding: 0 14px; + height: 100%; + display: flex; + align-items: center; + gap: 6px; + border-bottom: 2px solid transparent; + color: var(--fg-2); + font-size: 13px; + transition: color .12s, border-color .12s; +} +.tab:hover { color: var(--fg); } +.tab.active { + color: var(--accent-2); + border-bottom-color: var(--accent); + font-weight: 500; +} +[data-theme="dark"] .tab.active { color: var(--accent); } +.tab .badge { + background: var(--bg-3); + color: var(--fg-3); + font-size: 11px; + padding: 1px 6px; + border-radius: 9px; + font-family: var(--mono); +} +.tab.active .badge { + background: var(--accent); + color: #fff; +} + +.pane { + display: none; + flex: 1; + overflow: hidden; +} +.pane.active { display: flex; flex-direction: column; } + +.pane-reader iframe { + flex: 1; + width: 100%; + border: none; + background: var(--bg); +} + +/* ─── Search results pane ─── */ +.search-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 20px; + border-bottom: 1px solid var(--border); + background: var(--bg-2); + color: var(--fg-2); + font-size: 13px; +} +.clear-btn { + padding: 4px 10px; + border-radius: var(--radius-sm); + border: 1px solid var(--border); + color: var(--fg-2); + font-size: 12px; +} +.clear-btn:hover { background: var(--bg-3); color: var(--fg); } + +.results { + list-style: none; + margin: 0; + padding: 8px 0; + overflow-y: auto; + flex: 1; +} +.result-item { + padding: 10px 20px; + border-bottom: 1px solid var(--border); + cursor: pointer; + transition: background .1s; +} +.result-item:hover { background: var(--bg-3); } +.result-path { + font-size: 11px; + color: var(--fg-3); + font-family: var(--mono); + margin-bottom: 4px; +} +.result-title { + font-size: 14px; + font-weight: 600; + color: var(--fg); + margin-bottom: 4px; +} +.result-snippet { + font-size: 12px; + color: var(--fg-2); + line-height: 1.5; +} +.result-snippet mark { + background: var(--accent-soft); + color: var(--accent-2); + padding: 0 2px; + border-radius: 2px; + font-weight: 600; +} +[data-theme="dark"] .result-snippet mark { color: var(--accent); } + +/* ─── Responsive ─── */ +@media (max-width: 768px) { + .burger { display: flex; } + .brand .subtitle { display: none; } + .sidebar { + position: fixed; + left: 0; top: var(--top-h); bottom: 0; + z-index: 40; + transform: translateX(-100%); + transition: transform .2s; + box-shadow: var(--shadow-lg); + } + body.sidebar-open .sidebar { transform: translateX(0); } + .search-wrap { max-width: none; } +} diff --git a/docs/prd/assets/app.js b/docs/prd/assets/app.js new file mode 100644 index 0000000..03a61c6 --- /dev/null +++ b/docs/prd/assets/app.js @@ -0,0 +1,325 @@ +/* ============================================================ + LemonBoss doc-site shell — menu + hash router + search + ============================================================ */ + +(async () => { + // ── Menu (loaded synchronously via separate script tag if available) ── + await loadScript('./assets/menu.js'); + + const MENU = window.LEMONBOSS_MENU || []; + const menuEl = document.getElementById('menu'); + const readerEl = document.getElementById('reader'); + const searchEl = document.getElementById('search'); + const resultsEl = document.getElementById('results'); + const summaryEl = document.getElementById('search-summary'); + const tabBtns = document.querySelectorAll('.tab'); + const tabSearch = document.getElementById('tab-search'); + const countEl = document.getElementById('search-count'); + const burger = document.getElementById('burger'); + const themeBtn = document.getElementById('theme'); + + // ────────────────────────────────────────────── + // THEME + // ────────────────────────────────────────────── + const storedTheme = localStorage.getItem('lemonboss-theme'); + if (storedTheme) document.documentElement.dataset.theme = storedTheme; + themeBtn.addEventListener('click', () => { + const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; + document.documentElement.dataset.theme = next; + localStorage.setItem('lemonboss-theme', next); + }); + + // ────────────────────────────────────────────── + // MENU RENDER + // ────────────────────────────────────────────── + function renderMenu() { + MENU.forEach(ch => menuEl.appendChild(renderNode(ch, 'chapter'))); + } + function renderNode(node, kind) { + const wrap = document.createElement('div'); + wrap.className = `menu-${kind}`; + wrap.dataset.id = node.id; + + const link = document.createElement('a'); + link.className = 'menu-link'; + link.href = '#/' + node.href.replace(/#.*$/, '') + (node.href.includes('#') ? node.href.slice(node.href.indexOf('#')) : ''); + link.dataset.href = node.href; + + if (node.children) { + const caret = document.createElement('span'); + caret.className = 'caret'; + caret.textContent = '▾'; + caret.addEventListener('click', (e) => { + e.preventDefault(); e.stopPropagation(); + wrap.classList.toggle('collapsed'); + }); + link.appendChild(caret); + } else { + const dot = document.createElement('span'); + dot.className = 'caret'; + dot.textContent = '·'; + link.appendChild(dot); + } + + const num = document.createElement('span'); + num.className = 'menu-num'; + num.textContent = node.num; + link.appendChild(num); + + const txt = document.createElement('span'); + txt.textContent = node.title; + link.appendChild(txt); + + link.addEventListener('click', (e) => { + e.preventDefault(); + navigateTo(node.href); + if (window.innerWidth < 768) document.body.classList.remove('sidebar-open'); + }); + + wrap.appendChild(link); + + if (node.children) { + const cs = document.createElement('div'); + cs.className = 'children'; + const childKind = kind === 'chapter' ? 'section' : 'sub'; + node.children.forEach(c => cs.appendChild(renderNode(c, childKind))); + wrap.appendChild(cs); + } + return wrap; + } + + function highlightMenu(href) { + document.querySelectorAll('.menu-link').forEach(a => a.classList.remove('active')); + const target = stripHash(href); + let best = null; + document.querySelectorAll('.menu-link').forEach(a => { + const ah = a.dataset.href || ''; + if (stripHash(ah) === target) { + if (!best || (ah.includes('#') && !best.dataset.href.includes('#'))) best = a; + if (ah === href) best = a; // exact match wins + } + }); + if (best) { + best.classList.add('active'); + // expand all ancestors + let p = best.parentElement; + while (p && p !== menuEl) { + if (p.classList.contains('collapsed')) p.classList.remove('collapsed'); + p = p.parentElement; + } + best.scrollIntoView({ block: 'nearest' }); + } + } + const stripHash = (h) => h.split('#')[0]; + + // ────────────────────────────────────────────── + // HASH ROUTER + // ────────────────────────────────────────────── + function navigateTo(href) { + location.hash = '#/' + href; + } + function onHashChange() { + const raw = location.hash.replace(/^#\//, ''); + const href = raw || '01-overview/index.html'; + readerEl.src = './chapters/' + href; + highlightMenu(href); + // switch back to reader pane + switchTab('reader'); + } + window.addEventListener('hashchange', onHashChange); + + // ────────────────────────────────────────────── + // TABS + // ────────────────────────────────────────────── + function switchTab(name) { + tabBtns.forEach(b => b.classList.toggle('active', b.dataset.pane === name)); + document.querySelectorAll('.pane').forEach(p => + p.classList.toggle('active', p.classList.contains('pane-' + name)) + ); + } + tabBtns.forEach(b => b.addEventListener('click', () => switchTab(b.dataset.pane))); + + // ────────────────────────────────────────────── + // SEARCH + // ─ Loads search-index.json (built by scripts/build-search-index.js) + // ─ Tokenization: lowercase, split on whitespace+punct, + Chinese bigram + // ────────────────────────────────────────────── + let INDEX = null; + async function loadIndex() { + if (INDEX) return INDEX; + try { + const r = await fetch('./assets/search-index.json'); + INDEX = await r.json(); + } catch (e) { + INDEX = { docs: [], invIndex: {} }; + console.warn('[search] no index available — run scripts/build-search-index.js'); + } + return INDEX; + } + + function tokenize(text) { + const tokens = []; + const lower = text.toLowerCase(); + // ASCII words / digits + (lower.match(/[a-z0-9_]+/g) || []).forEach(w => tokens.push(w)); + // CJK bigrams (一-鿿 plus extensions) + const cjk = lower.match(/[㐀-鿿]+/g) || []; + cjk.forEach(run => { + if (run.length === 1) tokens.push(run); + else { + for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2)); + } + }); + return tokens; + } + + async function runSearch(query) { + if (!query || query.trim().length === 0) { + resultsEl.innerHTML = ''; + summaryEl.textContent = '输入关键词开始搜索'; + countEl.textContent = '0'; + return; + } + const idx = await loadIndex(); + const terms = tokenize(query); + if (terms.length === 0) { + resultsEl.innerHTML = ''; + summaryEl.textContent = `「${query}」无可识别 token`; + countEl.textContent = '0'; + return; + } + + // score docs by term frequency sum + const scores = new Map(); + let hitDocs = null; + for (const t of terms) { + const posting = idx.invIndex[t]; + if (!posting) continue; + const docs = new Set(); + for (const [docId, tf] of posting) { + scores.set(docId, (scores.get(docId) || 0) + tf); + docs.add(docId); + } + hitDocs = hitDocs ? new Set([...hitDocs].filter(d => docs.has(d))) : docs; + } + // fallback: if intersection empty, use union + const candidates = (hitDocs && hitDocs.size > 0) ? [...hitDocs] : [...scores.keys()]; + candidates.sort((a, b) => scores.get(b) - scores.get(a)); + const top = candidates.slice(0, 50); + + renderResults(top, query, idx); + } + + function renderResults(docIds, query, idx) { + resultsEl.innerHTML = ''; + const docs = idx.docs; + if (docIds.length === 0) { + summaryEl.textContent = `「${query}」没有命中`; + countEl.textContent = '0'; + return; + } + summaryEl.textContent = `「${query}」命中 ${docIds.length} 条`; + countEl.textContent = String(docIds.length); + + docIds.forEach(id => { + const d = docs[id]; + if (!d) return; + const li = document.createElement('li'); + li.className = 'result-item'; + li.innerHTML = ` +
${d.crumbs.join(' › ')}
+
${escapeHtml(d.title)}
+
${snippet(d.text, query)}
+ `; + li.addEventListener('click', () => { + navigateTo(d.href); + }); + resultsEl.appendChild(li); + }); + } + + function escapeHtml(s) { + return String(s).replace(/[&<>"']/g, c => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[c])); + } + function snippet(text, query) { + const lower = text.toLowerCase(); + const q = query.toLowerCase(); + let i = lower.indexOf(q); + if (i < 0) { + const t = tokenize(query)[0]; + if (t) i = lower.indexOf(t); + } + if (i < 0) i = 0; + const start = Math.max(0, i - 60); + const end = Math.min(text.length, i + 140); + let frag = text.slice(start, end); + if (start > 0) frag = '…' + frag; + if (end < text.length) frag = frag + '…'; + // highlight + const re = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); + return escapeHtml(frag).replace(re, m => `${escapeHtml(m)}`); + } + + let timer = null; + searchEl.addEventListener('input', () => { + clearTimeout(timer); + const v = searchEl.value; + timer = setTimeout(async () => { + await runSearch(v); + if (v.trim()) switchTab('search'); + }, 120); + }); + searchEl.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + runSearch(searchEl.value); + switchTab('search'); + } else if (e.key === 'Escape') { + searchEl.value = ''; + runSearch(''); + switchTab('reader'); + } + }); + document.getElementById('clear-search').addEventListener('click', () => { + searchEl.value = ''; + runSearch(''); + switchTab('reader'); + searchEl.focus(); + }); + + // ────────────────────────────────────────────── + // GLOBAL HOTKEYS + // ────────────────────────────────────────────── + document.addEventListener('keydown', (e) => { + const tag = (e.target.tagName || '').toLowerCase(); + if (tag === 'input' || tag === 'textarea') return; + if (e.key === '/') { + e.preventDefault(); + searchEl.focus(); + searchEl.select(); + } + }); + + // ────────────────────────────────────────────── + // MOBILE + // ────────────────────────────────────────────── + burger.addEventListener('click', () => document.body.classList.toggle('sidebar-open')); + + // ────────────────────────────────────────────── + // BOOT + // ────────────────────────────────────────────── + renderMenu(); + if (!location.hash) location.hash = '#/01-overview/index.html'; + else onHashChange(); + onHashChange(); + + // helper: load script + function loadScript(src) { + return new Promise((res, rej) => { + const s = document.createElement('script'); + s.src = src; + s.onload = () => res(); + s.onerror = () => rej(new Error('Failed to load ' + src)); + document.head.appendChild(s); + }); + } +})(); diff --git a/docs/prd/assets/chapter.css b/docs/prd/assets/chapter.css new file mode 100644 index 0000000..f07f4fa --- /dev/null +++ b/docs/prd/assets/chapter.css @@ -0,0 +1,342 @@ +/* ============================================================ + Chapter content styles — applied inside iframe + ============================================================ */ + +:root { + --bg: #fafaf7; + --bg-2: #ffffff; + --bg-3: #f3f3ee; + --fg: #1d1d1f; + --fg-2: #5a5a60; + --fg-3: #8a8a92; + --border: #e3e3dd; + --border-2: #d4d4cd; + --accent: #f5a524; + --accent-2: #e69500; + --accent-soft:#fff4dc; + --link: #0a66c2; + --code-bg: #f5f5ef; + --code-fg: #2d2d33; + --shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 12px rgba(0,0,0,.04); + --radius: 8px; + --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Roboto Mono", monospace; + --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", + "Hiragino Sans GB", "Microsoft YaHei", Helvetica, Arial, sans-serif; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #16161a; + --bg-2: #1d1d22; + --bg-3: #25252b; + --fg: #e8e8ea; + --fg-2: #b0b0b8; + --fg-3: #76767e; + --border: #2d2d34; + --border-2: #3a3a42; + --accent: #f5a524; + --accent-2: #ffbe3d; + --accent-soft:#3a2f15; + --link: #6db4ff; + --code-bg: #25252b; + --code-fg: #e6e6e8; + } +} + +* { box-sizing: border-box; } + +html, body { + margin: 0; + padding: 0; + font-family: var(--sans); + font-size: 14px; + line-height: 1.65; + background: var(--bg); + color: var(--fg); + -webkit-font-smoothing: antialiased; +} + +.page { + max-width: 980px; + margin: 0 auto; + padding: 40px 48px 80px; +} + +/* breadcrumbs */ +.crumbs { + font-family: var(--mono); + font-size: 12px; + color: var(--fg-3); + margin-bottom: 8px; +} +.crumbs span { margin: 0 4px; color: var(--fg-3); } + +/* headings */ +h1, h2, h3, h4 { + color: var(--fg); + line-height: 1.3; + scroll-margin-top: 20px; +} +h1 { + font-size: 30px; + font-weight: 700; + margin: 0 0 8px; + letter-spacing: -.02em; +} +h1 .h-num { + font-family: var(--mono); + font-size: 18px; + color: var(--accent-2); + margin-right: 8px; + font-weight: 500; +} +h2 { + font-size: 22px; + font-weight: 600; + margin: 48px 0 12px; + padding-top: 12px; + border-top: 1px solid var(--border); +} +h2 .h-num { + font-family: var(--mono); + font-size: 14px; + color: var(--accent-2); + margin-right: 6px; + font-weight: 500; +} +h3 { + font-size: 17px; + font-weight: 600; + margin: 28px 0 8px; +} +h3 .h-num { + font-family: var(--mono); + font-size: 13px; + color: var(--fg-3); + margin-right: 6px; +} +h4 { + font-size: 14px; + font-weight: 600; + margin: 20px 0 6px; + color: var(--fg-2); +} + +.lede { + font-size: 16px; + color: var(--fg-2); + margin: 0 0 24px; + line-height: 1.6; +} + +/* paragraphs */ +p { margin: 8px 0 14px; color: var(--fg); } + +/* links */ +a { color: var(--link); text-decoration: none; } +a:hover { text-decoration: underline; } + +/* lists */ +ul, ol { padding-left: 24px; } +li { margin: 3px 0; } + +/* inline code */ +code { + font-family: var(--mono); + font-size: 12.5px; + background: var(--code-bg); + color: var(--code-fg); + padding: 1px 6px; + border-radius: 3px; + border: 1px solid var(--border); +} + +/* code block */ +pre { + background: var(--code-bg); + color: var(--code-fg); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 14px 16px; + overflow-x: auto; + font-family: var(--mono); + font-size: 12.5px; + line-height: 1.55; +} +pre code { + background: none; + border: none; + padding: 0; + font-size: inherit; +} + +/* keyword colors for code */ +.k-kw { color: #c0392b; } +.k-str { color: #16a085; } +.k-num { color: #8e44ad; } +.k-cmt { color: #95a5a6; font-style: italic; } +.k-fn { color: #2980b9; } +[data-theme="dark"] .k-kw { color: #ff7b7b; } +[data-theme="dark"] .k-str { color: #6cdcc0; } +[data-theme="dark"] .k-cmt { color: #707078; } + +/* tables */ +table { + width: 100%; + border-collapse: collapse; + margin: 14px 0 20px; + font-size: 13px; +} +th, td { + text-align: left; + padding: 8px 12px; + border-bottom: 1px solid var(--border); + vertical-align: top; +} +th { + background: var(--bg-3); + color: var(--fg); + font-weight: 600; + font-size: 12px; + text-transform: uppercase; + letter-spacing: .04em; +} +tbody tr:hover { background: var(--bg-3); } +td code { + font-size: 12px; + padding: 0 4px; +} +td.method-GET { color: #16a085; font-family: var(--mono); font-weight: 600; } +td.method-POST { color: #2980b9; font-family: var(--mono); font-weight: 600; } +td.method-PUT { color: #e67e22; font-family: var(--mono); font-weight: 600; } +td.method-DELETE { color: #c0392b; font-family: var(--mono); font-weight: 600; } +td.method-PATCH { color: #8e44ad; font-family: var(--mono); font-weight: 600; } + +/* callout */ +.callout { + border-left: 3px solid var(--accent); + background: var(--accent-soft); + padding: 10px 14px; + border-radius: 0 var(--radius) var(--radius) 0; + margin: 14px 0 18px; + font-size: 13px; + color: var(--fg); +} +.callout-info { border-left-color: var(--link); background: rgba(10, 102, 194, .08); } +.callout-warn { border-left-color: #e67e22; background: rgba(230, 126, 34, .1); } +.callout-danger{ border-left-color: #c0392b; background: rgba(192, 57, 43, .1); } + +/* cards grid */ +.cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 12px; + margin: 16px 0; +} +.card { + background: var(--bg-2); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 14px; + box-shadow: var(--shadow); +} +.card-title { + font-weight: 600; + margin-bottom: 4px; + font-size: 14px; +} +.card-meta { + font-family: var(--mono); + font-size: 11px; + color: var(--fg-3); + margin-bottom: 6px; +} +.card-body { color: var(--fg-2); font-size: 13px; } + +/* badges */ +.badge { + display: inline-block; + font-family: var(--mono); + font-size: 11px; + padding: 1px 7px; + border-radius: 9px; + background: var(--bg-3); + color: var(--fg-2); + border: 1px solid var(--border); + margin: 0 2px; +} +.badge-p0 { background: #fce4e4; color: #c0392b; border-color: #f5c6c6; } +.badge-p1 { background: #fef0d4; color: #c87f0a; border-color: #f6d999; } +.badge-p2 { background: #e6f0fa; color: #2364ab; border-color: #b9d4ee; } +.badge-ok { background: #def5e3; color: #1e8449; border-color: #a9e0b8; } +.badge-na { background: var(--bg-3); color: var(--fg-3); border-color: var(--border); } +[data-theme="dark"] .badge-p0 { background: #4d2222; color: #ff8e85; } +[data-theme="dark"] .badge-p1 { background: #4d3a14; color: #ffc966; } +[data-theme="dark"] .badge-p2 { background: #1b3650; color: #8db8e0; } +[data-theme="dark"] .badge-ok { background: #1f3d27; color: #8edf9e; } + +/* mermaid containers */ +.mermaid { + background: var(--bg-2); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px; + margin: 14px 0; + overflow-x: auto; +} + +/* svg blocks */ +.svg-block { + background: var(--bg-2); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 12px; + margin: 14px 0; + text-align: center; + overflow-x: auto; +} +.svg-block svg { max-width: 100%; height: auto; } +.caption { + font-size: 12px; + color: var(--fg-3); + text-align: center; + margin-top: 6px; +} + +/* prototype mock */ +.mock { + background: var(--bg-2); + border: 1px solid var(--border-2); + border-radius: var(--radius); + box-shadow: var(--shadow); + overflow: hidden; + margin: 14px 0; +} +.mock-bar { + height: 28px; + background: var(--bg-3); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 10px; + gap: 4px; + font-family: var(--mono); + font-size: 11px; + color: var(--fg-3); +} +.mock-bar::before { + content: ''; + width: 10px; height: 10px; + border-radius: 50%; + background: #ff5f56; + box-shadow: 16px 0 #ffbd2e, 32px 0 #27c93f; + margin-right: 36px; +} +.mock-body { padding: 0; } + +/* responsive */ +@media (max-width: 720px) { + .page { padding: 24px 18px 60px; } + h1 { font-size: 24px; } + h2 { font-size: 18px; } +} diff --git a/docs/prd/assets/menu.js b/docs/prd/assets/menu.js new file mode 100644 index 0000000..12d97d6 --- /dev/null +++ b/docs/prd/assets/menu.js @@ -0,0 +1,102 @@ +/* LemonBoss doc-site menu tree (3 levels) */ +window.LEMONBOSS_MENU = [ + { + id: '01', num: '01', title: '项目概览', href: '01-overview/index.html', children: [ + { id: '1.1', num: '1.1', title: '项目定位与愿景', href: '01-overview/index.html#vision' }, + { id: '1.2', num: '1.2', title: '用户画像', href: '01-overview/index.html#personas' }, + { id: '1.3', num: '1.3', title: '技术栈全景', href: '01-overview/index.html#stack' }, + { id: '1.4', num: '1.4', title: '名词术语表', href: '01-overview/index.html#glossary' }, + ], + }, + { + id: '02', num: '02', title: 'PRD 产品需求', href: '02-prd/index.html', children: [ + { id: '2.1', num: '2.1', title: '核心场景', href: '02-prd/index.html#scenarios' }, + { id: '2.2', num: '2.2', title: '用户故事', href: '02-prd/index.html#stories' }, + { + id: '2.3', num: '2.3', title: '功能详述', href: '02-prd/index.html#features', children: [ + { id: '2.3.1', num: '2.3.1', title: 'Company 管理', href: '02-prd/index.html#feat-company' }, + { id: '2.3.2', num: '2.3.2', title: 'OKR 目标管理', href: '02-prd/index.html#feat-okr' }, + { id: '2.3.3', num: '2.3.3', title: '招聘 / 模板', href: '02-prd/index.html#feat-hire' }, + { id: '2.3.4', num: '2.3.4', title: 'Pipeline 流水线', href: '02-prd/index.html#feat-pipeline' }, + { id: '2.3.5', num: '2.3.5', title: 'Task Timeline', href: '02-prd/index.html#feat-task' }, + { id: '2.3.6', num: '2.3.6', title: 'Agent Chat', href: '02-prd/index.html#feat-chat' }, + { id: '2.3.7', num: '2.3.7', title: 'Quota 监控', href: '02-prd/index.html#feat-quota' }, + ], + }, + { id: '2.4', num: '2.4', title: '非功能需求', href: '02-prd/index.html#nfr' }, + { id: '2.5', num: '2.5', title: '路线图', href: '02-prd/index.html#roadmap' }, + ], + }, + { + id: '03', num: '03', title: '功能树', href: '03-feature-tree/index.html', children: [ + { id: '3.1', num: '3.1', title: '全局功能树', href: '03-feature-tree/index.html#tree' }, + { id: '3.2', num: '3.2', title: '模块拆解', href: '03-feature-tree/index.html#modules' }, + { id: '3.3', num: '3.3', title: '优先级矩阵', href: '03-feature-tree/index.html#priority' }, + ], + }, + { + id: '04', num: '04', title: '业务流程', href: '04-workflows/index.html', children: [ + { id: '4.1', num: '4.1', title: '创建公司流', href: '04-workflows/index.html#wf-create-company' }, + { id: '4.2', num: '4.2', title: 'OKR 拆解流', href: '04-workflows/index.html#wf-okr' }, + { id: '4.3', num: '4.3', title: '招聘流', href: '04-workflows/index.html#wf-hire' }, + { id: '4.4', num: '4.4', title: 'Pipeline 执行流', href: '04-workflows/index.html#wf-pipeline' }, + { id: '4.5', num: '4.5', title: 'Agent 协作调用链', href: '04-workflows/index.html#wf-agent' }, + { id: '4.6', num: '4.6', title: 'Auto-dev CLI 流', href: '04-workflows/index.html#wf-autodev' }, + { id: '4.7', num: '4.7', title: '鉴权流', href: '04-workflows/index.html#wf-auth' }, + ], + }, + { + id: '05', num: '05', title: '原型设计', href: '05-prototype/index.html', children: [ + { id: '5.1', num: '5.1', title: '设计规范', href: '05-prototype/index.html#design-system' }, + { id: '5.2', num: '5.2', title: 'AgentChat', href: '05-prototype/agent-chat.html' }, + { id: '5.3', num: '5.3', title: 'AgentList', href: '05-prototype/agent-list.html' }, + { id: '5.4', num: '5.4', title: 'CompanyDashboard', href: '05-prototype/company-dashboard.html' }, + { id: '5.5', num: '5.5', title: 'JobsPanel', href: '05-prototype/jobs-panel.html' }, + { id: '5.6', num: '5.6', title: 'OkrDashboard', href: '05-prototype/okr-dashboard.html' }, + { id: '5.7', num: '5.7', title: 'PipelineEditor', href: '05-prototype/pipeline-editor.html' }, + { id: '5.8', num: '5.8', title: 'QuotaMonitor', href: '05-prototype/quota-monitor.html' }, + { id: '5.9', num: '5.9', title: 'TaskTimeline', href: '05-prototype/task-timeline.html' }, + { id: '5.10', num: '5.10', title: 'TemplateAgents', href: '05-prototype/template-agents.html' }, + ], + }, + { + id: '06', num: '06', title: 'SDD · Model 数据模型', href: '06-sdd-model/index.html', children: [ + { id: '6.1', num: '6.1', title: 'ER 图', href: '06-sdd-model/index.html#er' }, + { id: '6.2', num: '6.2', title: 'SQLite Schema', href: '06-sdd-model/index.html#sqlite' }, + { id: '6.3', num: '6.3', title: '文件系统数据', href: '06-sdd-model/index.html#fs' }, + { id: '6.4', num: '6.4', title: '内存数据结构', href: '06-sdd-model/index.html#memory' }, + ], + }, + { + id: '07', num: '07', title: 'SDD · View 视图层', href: '07-sdd-view/index.html', children: [ + { id: '7.1', num: '7.1', title: 'Vue 路由表', href: '07-sdd-view/index.html#routes' }, + { id: '7.2', num: '7.2', title: '组件清单', href: '07-sdd-view/index.html#components' }, + { id: '7.3', num: '7.3', title: '状态管理', href: '07-sdd-view/index.html#state' }, + { id: '7.4', num: '7.4', title: '主题与样式', href: '07-sdd-view/index.html#theme' }, + ], + }, + { + id: '08', num: '08', title: 'SDD · Control API 协议', href: '08-sdd-control/index.html', children: [ + { id: '8.1', num: '8.1', title: 'API 端点总表', href: '08-sdd-control/index.html#endpoints' }, + { id: '8.2', num: '8.2', title: '中间件链', href: '08-sdd-control/index.html#middleware' }, + { id: '8.3', num: '8.3', title: 'SSE 协议', href: '08-sdd-control/index.html#sse' }, + { id: '8.4', num: '8.4', title: '鉴权与权限矩阵', href: '08-sdd-control/index.html#auth' }, + ], + }, + { + id: '09', num: '09', title: 'SDD · Process 进程层', href: '09-sdd-process/index.html', children: [ + { id: '9.1', num: '9.1', title: '入口与启动', href: '09-sdd-process/index.html#bootstrap' }, + { id: '9.2', num: '9.2', title: 'Engine 进程层', href: '09-sdd-process/index.html#engine' }, + { id: '9.3', num: '9.3', title: 'Agent 协作引擎', href: '09-sdd-process/index.html#agent' }, + { id: '9.4', num: '9.4', title: '心跳 / 调度', href: '09-sdd-process/index.html#heartbeat' }, + { id: '9.5', num: '9.5', title: '部署运维', href: '09-sdd-process/index.html#deploy' }, + ], + }, + { + id: '10', num: '10', title: '复现指南', href: '10-reproducibility/index.html', children: [ + { id: '10.1', num: '10.1', title: '从零搭建', href: '10-reproducibility/index.html#setup' }, + { id: '10.2', num: '10.2', title: '文件清单', href: '10-reproducibility/index.html#files' }, + { id: '10.3', num: '10.3', title: '验收 Checklist', href: '10-reproducibility/index.html#checklist' }, + ], + }, +]; diff --git a/docs/prd/assets/search-index.json b/docs/prd/assets/search-index.json new file mode 100644 index 0000000..aaa6e5e --- /dev/null +++ b/docs/prd/assets/search-index.json @@ -0,0 +1 @@ +{"docs":[{"id":0,"title":"01 项目概览","href":"01-overview/index.html","crumbs":["LemonBoss","第 1 章","项目概览"],"text":"01 · 项目概览 — LemonBoss LemonBoss › 第 1 章 › 项目概览 01 项目概览 LemonBoss 是一个 多智能体自主开发系统 —— 用户像\"开公司\"一样组织 AI Agent:创建公司、招聘成员、设定 OKR、编排 Pipeline、追踪任务时间线,最终让 Agent 自主把目标推进到落地。 覆盖范围。 本文档站只描述 LemonBoss 子系统( server/agent-server.js 入口及其挂载的 /api/agent /api/company /api/goals /api/tasks 路由族 + 9 个前端页面)。 历史品牌 ArenaSK 的 Skill 市场 / Arena ELO / Benchmark / Master Agent 不在本文档范围。 1.1 项目定位与愿景 占位中。完整内容将在下一轮章节生成时由 subagent 并行写入。 1.2 用户画像 占位中。 1.3 技术栈全景 占位中。 1.4 名词术语表 占位中。"},{"id":1,"title":"02 PRD 产品需求","href":"02-prd/index.html","crumbs":["LemonBoss","第 02 章","PRD 产品需求"],"text":"02 · PRD 产品需求 — LemonBoss LemonBoss › 第 02 章 › PRD 产品需求 02 PRD 产品需求 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"},{"id":2,"title":"03 功能树","href":"03-feature-tree/index.html","crumbs":["LemonBoss","第 03 章","功能树"],"text":"03 · 功能树 — LemonBoss LemonBoss › 第 03 章 › 功能树 03 功能树 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"},{"id":3,"title":"04 业务流程","href":"04-workflows/index.html","crumbs":["LemonBoss","第 04 章","业务流程"],"text":"04 · 业务流程 — LemonBoss LemonBoss › 第 04 章 › 业务流程 04 业务流程 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"},{"id":4,"title":"05 原型设计","href":"05-prototype/index.html","crumbs":["LemonBoss","第 05 章","原型设计"],"text":"05 · 原型设计 — LemonBoss LemonBoss › 第 05 章 › 原型设计 05 原型设计 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"},{"id":5,"title":"06 SDD · Model 数据模型","href":"06-sdd-model/index.html","crumbs":["LemonBoss","第 06 章","SDD · Model 数据模型"],"text":"06 · SDD · Model 数据模型 — LemonBoss LemonBoss › 第 06 章 › SDD · Model 数据模型 06 SDD · Model 数据模型 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"},{"id":6,"title":"07 SDD · View 视图层","href":"07-sdd-view/index.html","crumbs":["LemonBoss","第 07 章","SDD · View 视图层"],"text":"07 · SDD · View 视图层 — LemonBoss LemonBoss › 第 07 章 › SDD · View 视图层 07 SDD · View 视图层 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"},{"id":7,"title":"08 SDD · Control API 协议","href":"08-sdd-control/index.html","crumbs":["LemonBoss","第 08 章","SDD · Control API 协议"],"text":"08 · SDD · Control API 协议 — LemonBoss LemonBoss › 第 08 章 › SDD · Control API 协议 08 SDD · Control API 协议 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"},{"id":8,"title":"09 SDD · Process 进程层","href":"09-sdd-process/index.html","crumbs":["LemonBoss","第 09 章","SDD · Process 进程层"],"text":"09 · SDD · Process 进程层 — LemonBoss LemonBoss › 第 09 章 › SDD · Process 进程层 09 SDD · Process 进程层 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"},{"id":9,"title":"10 复现指南","href":"10-reproducibility/index.html","crumbs":["LemonBoss","第 10 章","复现指南"],"text":"10 · 复现指南 — LemonBoss LemonBoss › 第 10 章 › 复现指南 10 复现指南 本章占位中 — 完整内容将在下一阶段生成。 扫描清单见 docs/prd/_scan-manifest.json 。"}],"invIndex":{"1":[[0,6]],"2":[[0,1]],"3":[[0,1]],"4":[[0,1]],"9":[[0,1]],"10":[[9,3]],"01":[[0,2]],"lemonboss":[[0,4],[1,2],[2,2],[3,2],[4,2],[5,2],[6,2],[7,2],[8,2],[9,2]],"ai":[[0,1]],"agent":[[0,5]],"okr":[[0,1]],"pipeline":[[0,1]],"server":[[0,2]],"js":[[0,1]],"api":[[0,4],[7,3]],"company":[[0,1]],"goals":[[0,1]],"tasks":[[0,1]],"arenask":[[0,1]],"skill":[[0,1]],"arena":[[0,1]],"elo":[[0,1]],"benchmark":[[0,1]],"master":[[0,1]],"subagent":[[0,1]],"项目":[[0,4]],"目概":[[0,3]],"概览":[[0,3]],"第":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"章":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"是一":[[0,1]],"一个":[[0,1]],"多智":[[0,1]],"智能":[[0,1]],"能体":[[0,1]],"体自":[[0,1]],"自主":[[0,2]],"主开":[[0,1]],"开发":[[0,1]],"发系":[[0,1]],"系统":[[0,2]],"用户":[[0,2]],"户像":[[0,1]],"开公":[[0,1]],"公司":[[0,2]],"一样":[[0,1]],"样组":[[0,1]],"组织":[[0,1]],"创建":[[0,1]],"建公":[[0,1]],"招聘":[[0,1]],"聘成":[[0,1]],"成员":[[0,1]],"设定":[[0,1]],"编排":[[0,1]],"追踪":[[0,1]],"踪任":[[0,1]],"任务":[[0,1]],"务时":[[0,1]],"时间":[[0,1]],"间线":[[0,1]],"最终":[[0,1]],"终让":[[0,1]],"主把":[[0,1]],"把目":[[0,1]],"目标":[[0,1]],"标推":[[0,1]],"推进":[[0,1]],"进到":[[0,1]],"到落":[[0,1]],"落地":[[0,1]],"覆盖":[[0,1]],"盖范":[[0,1]],"范围":[[0,2]],"本文":[[0,2]],"文档":[[0,2]],"档站":[[0,1]],"站只":[[0,1]],"只描":[[0,1]],"描述":[[0,1]],"子系":[[0,1]],"入口":[[0,1]],"口及":[[0,1]],"及其":[[0,1]],"其挂":[[0,1]],"挂载":[[0,1]],"载的":[[0,1]],"路由":[[0,1]],"由族":[[0,1]],"个前":[[0,1]],"前端":[[0,1]],"端页":[[0,1]],"页面":[[0,1]],"历史":[[0,1]],"史品":[[0,1]],"品牌":[[0,1]],"的":[[0,1]],"市场":[[0,1]],"不在":[[0,1]],"在本":[[0,1]],"档范":[[0,1]],"目定":[[0,1]],"定位":[[0,1]],"位与":[[0,1]],"与愿":[[0,1]],"愿景":[[0,1]],"占位":[[0,4],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"位中":[[0,4],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"完整":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"整内":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"内容":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"容将":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"将在":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"在下":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"下一":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"一轮":[[0,1]],"轮章":[[0,1]],"章节":[[0,1]],"节生":[[0,1]],"生成":[[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"成时":[[0,1]],"时由":[[0,1]],"并行":[[0,1]],"行写":[[0,1]],"写入":[[0,1]],"户画":[[0,1]],"画像":[[0,1]],"技术":[[0,1]],"术栈":[[0,1]],"栈全":[[0,1]],"全景":[[0,1]],"名词":[[0,1]],"词术":[[0,1]],"术语":[[0,1]],"语表":[[0,1]],"02":[[1,3]],"prd":[[1,4],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"docs":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"_scan":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"manifest":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"json":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"产品":[[1,3]],"品需":[[1,3]],"需求":[[1,3]],"本章":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"章占":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"一阶":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"阶段":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"段生":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"扫描":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"描清":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"清单":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"单见":[[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1]],"03":[[2,3]],"功能":[[2,3]],"能树":[[2,3]],"04":[[3,3]],"业务":[[3,3]],"务流":[[3,3]],"流程":[[3,3]],"05":[[4,3]],"原型":[[4,3]],"型设":[[4,3]],"设计":[[4,3]],"06":[[5,3]],"sdd":[[5,3],[6,3],[7,3],[8,3]],"model":[[5,3]],"数据":[[5,3]],"据模":[[5,3]],"模型":[[5,3]],"07":[[6,3]],"view":[[6,3]],"视图":[[6,3]],"图层":[[6,3]],"08":[[7,3]],"control":[[7,3]],"协议":[[7,3]],"09":[[8,3]],"process":[[8,3]],"进程":[[8,3]],"程层":[[8,3]],"复现":[[9,3]],"现指":[[9,3]],"指南":[[9,3]]}} \ No newline at end of file diff --git a/docs/prd/chapters/01-overview/index.html b/docs/prd/chapters/01-overview/index.html new file mode 100644 index 0000000..b7527b7 --- /dev/null +++ b/docs/prd/chapters/01-overview/index.html @@ -0,0 +1,33 @@ + + + + + 01 · 项目概览 — LemonBoss + + + +
+
LemonBoss›第 1 章›项目概览
+

01项目概览

+

LemonBoss 是一个 多智能体自主开发系统 —— 用户像"开公司"一样组织 AI Agent:创建公司、招聘成员、设定 OKR、编排 Pipeline、追踪任务时间线,最终让 Agent 自主把目标推进到落地。

+ +
+ 覆盖范围。 本文档站只描述 LemonBoss 子系统(server/agent-server.js 入口及其挂载的 + /api/agent /api/company /api/goals /api/tasks 路由族 + 9 个前端页面)。 + 历史品牌 ArenaSK 的 Skill 市场 / Arena ELO / Benchmark / Master Agent 不在本文档范围。 +
+ +

1.1项目定位与愿景

+

占位中。完整内容将在下一轮章节生成时由 subagent 并行写入。

+ +

1.2用户画像

+

占位中。

+ +

1.3技术栈全景

+

占位中。

+ +

1.4名词术语表

+

占位中。

+
+ + diff --git a/docs/prd/chapters/02-prd/index.html b/docs/prd/chapters/02-prd/index.html new file mode 100644 index 0000000..e50193d --- /dev/null +++ b/docs/prd/chapters/02-prd/index.html @@ -0,0 +1,16 @@ + + + + + 02 · PRD 产品需求 — LemonBoss + + + +
+
LemonBoss›第 02 章›PRD 产品需求
+

02PRD 产品需求

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/chapters/03-feature-tree/index.html b/docs/prd/chapters/03-feature-tree/index.html new file mode 100644 index 0000000..276100a --- /dev/null +++ b/docs/prd/chapters/03-feature-tree/index.html @@ -0,0 +1,16 @@ + + + + + 03 · 功能树 — LemonBoss + + + +
+
LemonBoss›第 03 章›功能树
+

03功能树

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/chapters/04-workflows/index.html b/docs/prd/chapters/04-workflows/index.html new file mode 100644 index 0000000..272503b --- /dev/null +++ b/docs/prd/chapters/04-workflows/index.html @@ -0,0 +1,16 @@ + + + + + 04 · 业务流程 — LemonBoss + + + +
+
LemonBoss›第 04 章›业务流程
+

04业务流程

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/chapters/05-prototype/index.html b/docs/prd/chapters/05-prototype/index.html new file mode 100644 index 0000000..bf8646c --- /dev/null +++ b/docs/prd/chapters/05-prototype/index.html @@ -0,0 +1,16 @@ + + + + + 05 · 原型设计 — LemonBoss + + + +
+
LemonBoss›第 05 章›原型设计
+

05原型设计

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/chapters/06-sdd-model/index.html b/docs/prd/chapters/06-sdd-model/index.html new file mode 100644 index 0000000..c413457 --- /dev/null +++ b/docs/prd/chapters/06-sdd-model/index.html @@ -0,0 +1,16 @@ + + + + + 06 · SDD · Model 数据模型 — LemonBoss + + + +
+
LemonBoss›第 06 章›SDD · Model 数据模型
+

06SDD · Model 数据模型

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/chapters/07-sdd-view/index.html b/docs/prd/chapters/07-sdd-view/index.html new file mode 100644 index 0000000..42a355b --- /dev/null +++ b/docs/prd/chapters/07-sdd-view/index.html @@ -0,0 +1,16 @@ + + + + + 07 · SDD · View 视图层 — LemonBoss + + + +
+
LemonBoss›第 07 章›SDD · View 视图层
+

07SDD · View 视图层

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/chapters/08-sdd-control/index.html b/docs/prd/chapters/08-sdd-control/index.html new file mode 100644 index 0000000..fd7c024 --- /dev/null +++ b/docs/prd/chapters/08-sdd-control/index.html @@ -0,0 +1,16 @@ + + + + + 08 · SDD · Control API 协议 — LemonBoss + + + +
+
LemonBoss›第 08 章›SDD · Control API 协议
+

08SDD · Control API 协议

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/chapters/09-sdd-process/index.html b/docs/prd/chapters/09-sdd-process/index.html new file mode 100644 index 0000000..36ce4af --- /dev/null +++ b/docs/prd/chapters/09-sdd-process/index.html @@ -0,0 +1,16 @@ + + + + + 09 · SDD · Process 进程层 — LemonBoss + + + +
+
LemonBoss›第 09 章›SDD · Process 进程层
+

09SDD · Process 进程层

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/chapters/10-reproducibility/index.html b/docs/prd/chapters/10-reproducibility/index.html new file mode 100644 index 0000000..18084ff --- /dev/null +++ b/docs/prd/chapters/10-reproducibility/index.html @@ -0,0 +1,16 @@ + + + + + 10 · 复现指南 — LemonBoss + + + +
+
LemonBoss›第 10 章›复现指南
+

10复现指南

+

本章占位中 — 完整内容将在下一阶段生成。

+
扫描清单见 docs/prd/_scan-manifest.json。
+
+ + diff --git a/docs/prd/index.html b/docs/prd/index.html new file mode 100644 index 0000000..b1e2d9f --- /dev/null +++ b/docs/prd/index.html @@ -0,0 +1,54 @@ + + + + + + LemonBoss — PRD + SDD + + + +
+ +
+ 🍋 + LemonBoss + PRD + SDD · 多智能体自主开发系统 +
+
+ + / +
+ +
+ +
+ + +
+
+ + +
+
+ +
+ +
+
+ + + + diff --git a/docs/prd/scripts/build-search-index.js b/docs/prd/scripts/build-search-index.js new file mode 100644 index 0000000..60078b2 --- /dev/null +++ b/docs/prd/scripts/build-search-index.js @@ -0,0 +1,108 @@ +#!/usr/bin/env node +/** + * Build search-index.json from chapter HTML files. + * + * docs/prd/chapters/{ch}/**.html → assets/search-index.json + * + * Tokenization rules (must match runtime in app.js): + * - lowercase + * - ASCII words/digits: [a-z0-9_]+ + * - CJK bigrams: [一-鿿]+ → for each run, push every 2-char window + * + * Output: + * { + * docs: [ { id, title, href, crumbs:[…], text } ], + * invIndex: { token: [ [docId, tf], … ] } + * } + */ + +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.resolve(__dirname, '..'); +const CHAPT_DIR = path.join(ROOT, 'chapters'); +const OUT = path.join(ROOT, 'assets', 'search-index.json'); + +function walk(dir) { + const out = []; + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + const p = path.join(dir, e.name); + if (e.isDirectory()) out.push(...walk(p)); + else if (e.isFile() && e.name.endsWith('.html')) out.push(p); + } + return out; +} + +function stripTags(html) { + return html + .replace(//gi, ' ') + .replace(//gi, ' ') + .replace(//g, ' ') + .replace(/<[^>]+>/g, ' ') + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/\s+/g, ' ') + .trim(); +} + +function pick(html, re) { + const m = re.exec(html); + return m ? stripTags(m[1]) : null; +} + +function extractTitle(html) { + return pick(html, /]*>([\s\S]*?)<\/h1>/i) || pick(html, /]*>([\s\S]*?)<\/title>/i) || '(无标题)'; +} + +function extractCrumbs(html) { + const m = /
]*>([\s\S]*?)<\/div>/i.exec(html); + if (!m) return []; + return m[1].replace(/]*>›<\/span>/g, '|').split('|').map(s => stripTags(s)).filter(Boolean); +} + +function tokenize(text) { + const tokens = []; + const lower = text.toLowerCase(); + (lower.match(/[a-z0-9_]+/g) || []).forEach(w => tokens.push(w)); + const cjk = lower.match(/[㐀-鿿]+/g) || []; + cjk.forEach(run => { + if (run.length === 1) tokens.push(run); + else for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2)); + }); + return tokens; +} + +function build() { + const files = walk(CHAPT_DIR); + const docs = []; + const inv = {}; // token → Map + + for (const file of files) { + const html = fs.readFileSync(file, 'utf-8'); + const title = extractTitle(html); + const crumbs = extractCrumbs(html); + const text = stripTags(html); + const href = path.relative(CHAPT_DIR, file); + const id = docs.length; + docs.push({ id, title, href, crumbs, text: text.slice(0, 2000) }); + + const tokens = tokenize(text); + const tf = {}; + for (const t of tokens) tf[t] = (tf[t] || 0) + 1; + for (const [t, c] of Object.entries(tf)) { + (inv[t] = inv[t] || []).push([id, c]); + } + } + + const out = { docs, invIndex: inv }; + fs.mkdirSync(path.dirname(OUT), { recursive: true }); + fs.writeFileSync(OUT, JSON.stringify(out)); + const kb = (fs.statSync(OUT).size / 1024).toFixed(1); + console.log(`✅ search index: ${docs.length} docs, ${Object.keys(inv).length} tokens, ${kb} KB → ${path.relative(ROOT, OUT)}`); +} + +build();