Commit fd86ebf1cc61c45a0777e5103ae2993c46a278b8

Authored by yibo
1 parent f4cb12bd

docs(prd): add 10-chapter compact PRD set (parallel to newfunction/)

Earlier PRD attempt scoped to LemonBoss-only (excludes ArenaSK Skill
marketplace / Arena / Master / Innovator / Open API / Evolution).
Generated 2026-05-12, exists side-by-side with the later 50-page
DevLoop PRD under docs/prd/newfunction/.

Structure:
- index.html (single-page shell with collapsible sidebar + lunr search)
- assets/ (app.css/js, chapter.css, menu.js, search-index.json)
- chapters/01-overview ~ 10-reproducibility (10 chapters)
- _scan-manifest.json (scope manifest pinning what's in/out)
- scripts/build-search-index.js (chapter HTML → lunr index)

This commit just brings the existing files under version control;
no edits to content.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
docs/prd/_scan-manifest.json 0 → 100644
  1 +{
  2 + "manifestVersion": "1.0",
  3 + "generatedAt": "2026-05-12",
  4 + "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.",
  5 +
  6 + "project": {
  7 + "name": "LemonBoss",
  8 + "legacyName": "ArenaSK",
  9 + "rootPath": "/Users/yibo/Documents/lemonAI/lemonboss",
  10 + "nodeVersion": ">=18",
  11 + "packageManager": "pnpm (workspace)",
  12 + "moduleAlias": { "@server": "server" }
  13 + },
  14 +
  15 + "entryPoints": {
  16 + "agentServer": {
  17 + "file": "server/agent-server.js",
  18 + "port": 3010,
  19 + "startCommand": "bash start.sh",
  20 + "responsibilities": [
  21 + "express app with body-sanitizer middleware (strips \\x00-\\x1F before JSON.parse)",
  22 + "CORS allow http://localhost:5173",
  23 + "Mounts /api/agent /api/company /api/goals /api/tasks /api/health"
  24 + ],
  25 + "mountedRoutes": [
  26 + { "prefix": "/api/agent", "file": "server/routes/agent-chat.js" },
  27 + { "prefix": "/api/company", "file": "server/routes/company.js" },
  28 + { "prefix": "/api/goals", "file": "server/routes/goals.js" },
  29 + { "prefix": "/api/tasks", "file": "server/routes/tasks.js" },
  30 + { "prefix": "/api/health", "inline": "GET → { status: ok, service: agent-chat }" }
  31 + ],
  32 + "notMounted": "auth, users, plugins (only in server/index.js, out of LemonBoss-subset scope)"
  33 + },
  34 + "binAutoDev": {
  35 + "file": "bin/auto-dev or server/src/cli/auto-dev.js",
  36 + "purpose": "Standalone CLI to run an end-to-end autonomous dev task (e.g. `auto-dev \"implement fibonacci\"`)"
  37 + },
  38 + "startScript": {
  39 + "file": "start.sh",
  40 + "steps": [
  41 + "load nvm",
  42 + "kill any process on :3010 and :5173",
  43 + "PORT=3010 node server/agent-server.js &",
  44 + "cd web && npx vite --host &"
  45 + ],
  46 + "note": "Does NOT build frontend, sync skills, or start bots — those are separate scripts."
  47 + }
  48 + },
  49 +
  50 + "configFiles": {
  51 + "rootPackage": "package.json (workspaces: packages/*, agent-bridge, server, web)",
  52 + "scripts": {
  53 + "setup": "cp -n .env.example .env && npm install && cd server && npm install && cd ../web && npm install",
  54 + "dev": "concurrently 'npm run server' 'npm run web' (full server/index.js — NOT LemonBoss-only)",
  55 + "server": "cd server && npm start",
  56 + "web": "cd web && npm run dev",
  57 + "test:unit": "vitest run",
  58 + "test:multi-cli": "node server/src/tests/scripts/test-multi-cli.js",
  59 + "test:browser-logger": "node server/src/modules/browser-logger/__tests__/test-runner.js",
  60 + "test:design-validator": "node server/src/modules/design-validator/__tests__/test-runner.js",
  61 + "example": "node server/src/cli/auto-dev.js '...'"
  62 + },
  63 + "serverConfig": {
  64 + "file": "server/config.js",
  65 + "fields": ["port (3010)", "host (0.0.0.0)", "workspacesDir", "skillsCodeDir (LemonBoss IGNORES — ArenaSK)", "trainedSkillsDir", "conversationsDir", "cors (5173, 3010)", "openApi.apiKeys (ArenaSK)"]
  66 + },
  67 + "env": ".env from .env.example (port, CORS, GITHUB/GOOGLE OAuth client IDs, OPEN_API_KEYS, dir overrides)"
  68 + },
  69 +
  70 + "routes": {
  71 + "_summary": "LemonBoss-relevant prefixes only. Full route listing per file below.",
  72 +
  73 + "agentChat": {
  74 + "file": "server/routes/agent-chat.js",
  75 + "prefix": "/api/agent",
  76 + "endpoints": [
  77 + { "method": "GET", "path": "/list", "purpose": "list all agents (system + per company)" },
  78 + { "method": "GET", "path": "/quota", "purpose": "token consumption & cost stats" },
  79 + { "method": "POST", "path": "/:agentId/chat", "purpose": "chat with agent (SSE stream)" },
  80 + { "method": "POST", "path": "/:agentId/chat/:convId/stop", "purpose": "interrupt running chat" },
  81 + { "method": "GET", "path": "/:agentId/info", "purpose": "agent metadata + config" },
  82 + { "method": "PUT", "path": "/:agentId/heartbeat", "purpose": "update heartbeat config" },
  83 + { "method": "GET", "path": "/:agentId/cron", "purpose": "read cron.json" },
  84 + { "method": "PUT", "path": "/:agentId/cron", "purpose": "update cron.json" },
  85 + { "method": "GET", "path": "/:agentId/todo", "purpose": "read todo.md" },
  86 + { "method": "PUT", "path": "/:agentId/todo", "purpose": "update todo.md" },
  87 + { "method": "GET", "path": "/:agentId/plan", "purpose": "read plan.md" },
  88 + { "method": "PUT", "path": "/:agentId/plan", "purpose": "update plan.md" },
  89 + { "method": "GET", "path": "/:agentId/conversations", "purpose": "list conversations" },
  90 + { "method": "GET", "path": "/:agentId/conversations/:convId", "purpose": "get conversation history" },
  91 + { "method": "DELETE", "path": "/:agentId/conversations/:convId", "purpose": "delete conversation" },
  92 + { "method": "GET", "path": "/:agentId/conversations/:convId/sub-logs", "purpose": "list sub-agent logs" },
  93 + { "method": "GET", "path": "/:agentId/conversations/:convId/sub-logs/:logFile", "purpose": "fetch a sub-log file" },
  94 + { "method": "GET", "path": "/:agentId/files", "purpose": "list agent dir files" },
  95 + { "method": "GET", "path": "/:agentId/file/*", "purpose": "read a file in agent dir" },
  96 + { "method": "PUT", "path": "/:agentId/file/*", "purpose": "write a file in agent dir" },
  97 + { "method": "DELETE", "path": "/:agentId/file/*", "purpose": "delete a file in agent dir" },
  98 + { "method": "GET", "path": "/:agentId/bootstrap/:filename", "purpose": "read bootstrap file (agent.md, soul.md, ...)" },
  99 + { "method": "POST", "path": "/:agentId/memory/cerebellum", "purpose": "save cerebellum memory snippet" },
  100 + { "method": "POST", "path": "/:agentId/memory/consolidate", "purpose": "consolidate session into long-term memory" },
  101 + { "method": "POST", "path": "/:agentId/memory/daily-report", "purpose": "generate daily memory report" },
  102 + { "method": "POST", "path": "/:agentId/upload", "purpose": "multipart upload (multer, ≤20 files)" },
  103 + { "method": "GET", "path": "/:agentId/uploads/:convId", "purpose": "list uploads in conversation" },
  104 + { "method": "GET", "path": "/:agentId/uploads/:convId/:filename", "purpose": "download upload" },
  105 + { "method": "DELETE", "path": "/:agentId/uploads/:convId/:filename", "purpose": "delete upload" },
  106 + { "method": "POST", "path": "/scaffold", "purpose": "scaffold a new agent skeleton" },
  107 + { "method": "POST", "path": "/task/dispatch", "purpose": "Master dispatches a task to a sub-agent" },
  108 + { "method": "POST", "path": "/task/report", "purpose": "agent reports task done/failed" },
  109 + { "method": "POST", "path": "/:agentId/task/transition", "purpose": "agent transitions to a stage state" },
  110 + { "method": "POST", "path": "/task/reject", "purpose": "supervisor rejects a task output" },
  111 + { "method": "POST", "path": "/human-request", "purpose": "agent escalates to human" }
  112 + ],
  113 + "notes": [
  114 + "Heavy file ~2470 lines — many endpoints share helpers (resolveAgentDir, getConvDir).",
  115 + "Chat streams as SSE with token-level events.",
  116 + "Task dispatch/report endpoints bridge into PipelineService.advanceStep when callbacks match a pipeline run."
  117 + ]
  118 + },
  119 +
  120 + "company": {
  121 + "file": "server/routes/company.js",
  122 + "prefix": "/api/company",
  123 + "endpoints": [
  124 + { "method": "POST", "path": "/create", "purpose": "create company" },
  125 + { "method": "GET", "path": "/list", "purpose": "list companies" },
  126 + { "method": "GET", "path": "/templates", "purpose": "list agent templates" },
  127 + { "method": "POST", "path": "/templates", "purpose": "create template" },
  128 + { "method": "PATCH", "path": "/templates/:templateId", "purpose": "update template" },
  129 + { "method": "DELETE", "path": "/templates/:templateId", "purpose": "delete template" },
  130 + { "method": "POST", "path": "/templates/:templateId/generate", "purpose": "scaffold from template" },
  131 + { "method": "POST", "path": "/templates/:templateId/train", "purpose": "train template (skill evolution loop)" },
  132 + { "method": "POST", "path": "/templates/:templateId/skills/upload", "purpose": "upload skill files to template (≤50)" },
  133 + { "method": "POST", "path": "/templates/:templateId/provision", "purpose": "provision template runtime" },
  134 + { "method": "GET", "path": "/templates/:templateId/logs", "purpose": "fetch training logs" },
  135 + { "method": "GET", "path": "/templates/:templateId/files", "purpose": "list template files" },
  136 + { "method": "GET", "path": "/templates/:templateId/files/content", "purpose": "read template file" },
  137 + { "method": "PUT", "path": "/templates/:templateId/files/content", "purpose": "write template file" },
  138 + { "method": "GET", "path": "/:companyId", "purpose": "company detail" },
  139 + { "method": "PUT", "path": "/:companyId", "purpose": "update company" },
  140 + { "method": "DELETE", "path": "/:companyId", "purpose": "delete company" },
  141 + { "method": "POST", "path": "/:companyId/shutdown", "purpose": "soft-shutdown company" },
  142 + { "method": "POST", "path": "/:companyId/start", "purpose": "start running" },
  143 + { "method": "GET", "path": "/:companyId/status", "purpose": "status + heartbeat info" },
  144 + { "method": "PUT", "path": "/:companyId/agents/:agentId/heartbeat", "purpose": "update agent heartbeat config" },
  145 + { "method": "POST", "path": "/:companyId/agents/:agentId/heartbeat/start", "purpose": "start agent heartbeat" },
  146 + { "method": "POST", "path": "/:companyId/agents/:agentId/heartbeat/stop", "purpose": "stop agent heartbeat" },
  147 + { "method": "GET", "path": "/:companyId/agents/:agentId/skills", "purpose": "list agent skills" },
  148 + { "method": "POST", "path": "/:companyId/hire", "purpose": "hire agent from template or scaffold" },
  149 + { "method": "POST", "path": "/:companyId/fire", "purpose": "suspend agent" },
  150 + { "method": "POST", "path": "/:companyId/deposit-template", "purpose": "deposit trained agent (≥95) as new template" },
  151 + { "method": "GET", "path": "/:companyId/agents", "purpose": "list company agents" },
  152 + { "method": "GET", "path": "/:companyId/goals", "purpose": "company OKR list" },
  153 + { "method": "POST", "path": "/:companyId/goals", "purpose": "create goal" },
  154 + { "method": "GET", "path": "/:companyId/goals/:goalId", "purpose": "goal detail" },
  155 + { "method": "PUT", "path": "/:companyId/goals/:goalId", "purpose": "update goal" },
  156 + { "method": "POST", "path": "/:companyId/goals/:goalId/nodes", "purpose": "add OKR node" },
  157 + { "method": "PUT", "path": "/:companyId/goals/:goalId/nodes/:nodeId","purpose": "update OKR node" },
  158 + { "method": "GET", "path": "/:companyId/goals/:goalId/progress", "purpose": "OKR progress %" },
  159 + { "method": "POST", "path": "/:companyId/pipelines", "purpose": "create pipeline" },
  160 + { "method": "GET", "path": "/:companyId/pipelines", "purpose": "list pipelines" },
  161 + { "method": "GET", "path": "/:companyId/pipelines/:pipelineId", "purpose": "pipeline detail" },
  162 + { "method": "PUT", "path": "/:companyId/pipelines/:pipelineId", "purpose": "update pipeline" },
  163 + { "method": "POST", "path": "/:companyId/pipelines/:pipelineId/run", "purpose": "start a run" },
  164 + { "method": "GET", "path": "/:companyId/pipelines/runs/list", "purpose": "list runs" },
  165 + { "method": "GET", "path": "/:companyId/pipelines/runs/:runId", "purpose": "run detail" },
  166 + { "method": "POST", "path": "/:companyId/pipelines/runs/:runId/advance", "purpose": "advance to next step" },
  167 + { "method": "GET", "path": "/:companyId/pipelines/runs/:runId/current", "purpose": "current ready step" },
  168 + { "method": "POST", "path": "/:companyId/pipelines/runs/:runId/reject", "purpose": "COO rejects a step output" },
  169 + { "method": "GET", "path": "/:companyId/schedule", "purpose": "list schedule slots" },
  170 + { "method": "POST", "path": "/:companyId/schedule/slots", "purpose": "add slot" },
  171 + { "method": "PUT", "path": "/:companyId/schedule/slots/:slotId", "purpose": "update slot" },
  172 + { "method": "DELETE", "path": "/:companyId/schedule/slots/:slotId", "purpose": "delete slot" },
  173 + { "method": "GET", "path": "/:companyId/data/metrics", "purpose": "raw metrics" },
  174 + { "method": "GET", "path": "/:companyId/data/trending", "purpose": "trending" },
  175 + { "method": "GET", "path": "/:companyId/data/attribution", "purpose": "attribution" },
  176 + { "method": "GET", "path": "/:companyId/files", "purpose": "company file list" },
  177 + { "method": "GET", "path": "/:companyId/files/content", "purpose": "read file" },
  178 + { "method": "PUT", "path": "/:companyId/files/content", "purpose": "write file" },
  179 + { "method": "GET", "path": "/:companyId/workspace/files", "purpose": "workspace file list" },
  180 + { "method": "GET", "path": "/:companyId/workspace/files/content", "purpose": "workspace read" },
  181 + { "method": "PUT", "path": "/:companyId/workspace/files/content", "purpose": "workspace write" },
  182 + { "method": "GET", "path": "/:companyId/messages", "purpose": "activity / message log" },
  183 + { "method": "GET", "path": "/:companyId/stats", "purpose": "company stats" },
  184 + { "method": "GET", "path": "/:companyId/jobs", "purpose": "list pipeline-run-derived jobs (paged, filtered)" },
  185 + { "method": "GET", "path": "/:companyId/jobs/:jobId", "purpose": "job detail" }
  186 + ]
  187 + },
  188 +
  189 + "goals": {
  190 + "file": "server/routes/goals.js",
  191 + "prefix": "/api/goals",
  192 + "endpoints": [
  193 + { "method": "GET", "path": "/", "purpose": "list goals (global / index.json)" },
  194 + { "method": "POST", "path": "/", "purpose": "create goal" },
  195 + { "method": "GET", "path": "/:goalId", "purpose": "full goal tree" },
  196 + { "method": "PUT", "path": "/:goalId", "purpose": "update goal" },
  197 + { "method": "POST", "path": "/:goalId/nodes", "purpose": "add node" },
  198 + { "method": "GET", "path": "/:goalId/nodes/:nodeId", "purpose": "get node + parent context" },
  199 + { "method": "PUT", "path": "/:goalId/nodes/:nodeId", "purpose": "update node status" },
  200 + { "method": "GET", "path": "/:goalId/progress", "purpose": "leaf-aggregated progress" }
  201 + ],
  202 + "idScheme": "Dot-separated hierarchy: G-001 → G-001.1 (Objective) → G-001.1.1 (KeyResult) → G-001.1.1.1 (SubTask)"
  203 + },
  204 +
  205 + "tasks": {
  206 + "file": "server/routes/tasks.js",
  207 + "prefix": "/api/tasks",
  208 + "endpoints": [
  209 + { "method": "GET", "path": "/", "purpose": "aggregated cross-agent task list (10s TTL cache)" },
  210 + { "method": "GET", "path": "/:taskId/timeline", "purpose": "merged timeline of conversation events for task (and optional children)" }
  211 + ]
  212 + }
  213 + },
  214 +
  215 + "services": {
  216 + "_lemonbossRelevant": {
  217 + "company-service.js": "Company CRUD + Agent hiring (template-first → scaffold fallback) + depositTemplate + resolveAgentDir static",
  218 + "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",
  219 + "heartbeat-service.js": "Agent-level heartbeat: setInterval(intervalMinutes) → POST /api/agent/{id}/chat with system heartbeat payload",
  220 + "goals.js": "GoalsService: file-backed goal tree (data/companies/{id}/goals/ or data/goals/), progress recalc, change log",
  221 + "pipeline-service.js": "Pipeline CRUD + Run lifecycle (startRun → markStepStarted → advanceStep → loop|completed) + rejectStep + findActiveRunStep",
  222 + "task-service.js": "Aggregates conversations across system/agents + data/companies/*/agents into task groups; infers parent/child via 4 strategies",
  223 + "schedule-service.js": "Schedule slot CRUD + getDueSlots(companyId)",
  224 + "memory-service.js": "Cerebellum + consolidation + daily-report endpoints",
  225 + "datasource-service.js": "External data source connections",
  226 + "file-parser.js": "Markdown/PDF/Docx parsing for uploads",
  227 + "conversation-logger.js": "Append-only messages.log writer",
  228 + "user-db.js": "users.sqlite — single users table (used by auth/users routes — only via server/index.js, not agent-server.js)",
  229 + "job-manager.js": "Job execution queue (LemonBoss: pipeline-run-jobs)",
  230 + "schedule-service.js": "Cron / schedule slot storage",
  231 + "task-queue.js": "Queue primitive used by job-manager",
  232 + "skill-task-manager.js": "[ArenaSK-leaning] tracks skill execution tasks — touched if /api/company/{id}/agents/{id}/skills is hit"
  233 + },
  234 + "_arenaSkOnly_skipInDocs": [
  235 + "skill-db.js", "skill-registry.js", "skill-dir-manager.js", "skill-packager.js",
  236 + "benchmark-db.js", "benchmark-runner.js", "evolution-loop.js",
  237 + "retrain-pipeline.js", "retrain-watchdog.js", "innovator-service.js",
  238 + "claude-chat-service.js"
  239 + ]
  240 + },
  241 +
  242 + "core": {
  243 + "_dir": "server/src/core/",
  244 + "orchestrator.js": "Multi-agent loop: Master ↔ Sub-agent, format validation, maxRepairs, completion detection",
  245 + "agent-manager.js": "Unified Agent call gateway: config lookup → hook orchestration → protocol → retry",
  246 + "scheduler.js": "Task scheduling / timing primitive",
  247 + "session-pool.js": "Acquire / release / cleanup persistent ACP sessions",
  248 + "prompt-builder.js": "Build system prompt from agent.md + soul.md + identity.md + user.md + tools.md + heartbeat.md + memory + cron",
  249 + "runtime-resolver.js": "Dynamic agent type + env inference at run-time",
  250 + "master-session.js": "Master Agent persistent Claude CLI session — multi-turn conversation + tool dispatch (call_agent, orchestrate, ...)",
  251 + "stream-executor.js": "Stream prompt through chosen ACP protocol",
  252 + "parser.js": "Parse [TOOL_CALL:], JSON, markdown agent output",
  253 + "result-tracker.js": "Execution state machine + checkpoint",
  254 + "hook-runner.js": "Bootstrap hooks (on_init / on_failure / on_complete)",
  255 + "protocol-factory.js": "Create ClaudeCliProtocol / WebSocketProtocol instances",
  256 + "skill-config-loader.js": "Scan skill dir + merge config.json + inject into prompt",
  257 + "prompts.js": "Master/Sub-agent prompt templates",
  258 + "evolution-memory.js": "Training-history store (shared with ArenaSK)",
  259 + "logging/": {
  260 + "LogManager.js": "Multi-session log dispatch",
  261 + "LogSession.js": "Per-session log buffer",
  262 + "backends": ["BaseBackend.js", "ConsoleBackend.js", "FileBackend.js", "CompositeBackend.js"]
  263 + }
  264 + },
  265 +
  266 + "engine": {
  267 + "_dir": "server/src/engine/",
  268 + "index.js": "Barrel exports",
  269 + "process-guard.js": "Two-layer timeout guard (fresh / resume defaults) for Claude CLI subprocess",
  270 + "run-registry.js": "Active run table (runId → meta, output stream refs, status); DEFAULT_MAX_EXITED retention",
  271 + "command-queue.js": "Multi-lane FIFO queue with generation-based cancellation (CommandLaneClearedError on clearAll)",
  272 + "failover-error.js": "Error classification → FailoverReason, RETRYABLE_REASONS",
  273 + "idle-timer.js": "Streaming idle-watchdog (per-byte reset)"
  274 + },
  275 +
  276 + "plugins": {
  277 + "_dir": "server/src/plugins/",
  278 + "plugin-manager.js": "Discover / load / enable / disable plugins; emit hooks",
  279 + "agent-bridge.js": "Cross-plugin agent message relay",
  280 + "config-bridge.js": "Plugin config merge + validation",
  281 + "bootstrap.js": "Plugin init sequence",
  282 + "openclaw.plugin.json": "OpenClaw plugin manifest (sibling to plugins/)"
  283 + },
  284 +
  285 + "tools": {
  286 + "_dir": "server/src/tools/",
  287 + "call-agent-tool.js": "Master tool: call_sub_agent / list_agents",
  288 + "orchestrate-tool.js": "Master tool: orchestrate (multi-step plan)",
  289 + "train-skill-tool.js": "Master tool: trigger skill training (Hunt → Digest → Evolving) [ArenaSK-leaning]",
  290 + "read-tools.js": "read_checkpoint / read_log / list_retrain_jobs",
  291 + "tool-registry.js": "Tool register & dispatch",
  292 + "marketplace-tools.js":"[ArenaSK only — skip in LemonBoss docs]"
  293 + },
  294 +
  295 + "packages": {
  296 + "acp-core": "ACP Protocol Stack — Protocol × Transport × Launcher 4D architecture (ClaudeCliProtocol over stdio, WebSocketProtocol, transports, launchers).",
  297 + "agent-core": "Agent class + AgentScaffold; directory-driven agent bootstrap unified across system/agents and data/companies/*/agents (ESM).",
  298 + "plugin-bootstrap": "OpenClaw plugin loader — discovery + load sequence.",
  299 + "evolution": "[ArenaSK only — skip]"
  300 + },
  301 +
  302 + "middleware": {
  303 + "_dir": "server/middleware/",
  304 + "jwt-auth.js": "requireAuth + optionalAuth + requireAdmin guards; signAndSetCookie / clearCookie",
  305 + "api-key-auth.js": "X-API-Key validation (ArenaSK Open API)",
  306 + "api-access-logger.js": "Request/response log line"
  307 + },
  308 +
  309 + "frontend": {
  310 + "_dir": "web/src/",
  311 + "entry": "main.js → createApp(App) → router",
  312 + "router": {
  313 + "file": "web/src/router/index.js",
  314 + "routes": [
  315 + { "path": "/", "redirect": "/agent/master" },
  316 + { "path": "/agents/:agentId?", "redirect": "/agent/$agentId or /agent/agent-creator" },
  317 + { "path": "/agent/:agentId/:convId?", "name": "AgentChat", "component": "AgentChat.vue" },
  318 + { "path": "/:id/agents/:agentId?/:convId?", "name": "CompanyAgentChat", "component": "AgentChat.vue" },
  319 + { "path": "/templates/:agentId?", "name": "TemplateAgents", "component": "TemplateAgents.vue" },
  320 + { "path": "/company", "name": "CompanyList", "component": "CompanyDashboard.vue" },
  321 + { "path": "/:id/:tab(overview|agents|pipelines|goals|sop|okr|team|files|messages|skills|jobs|workspace)", "name": "CompanyDetail", "component": "CompanyDashboard.vue" },
  322 + { "path": "/okr", "name": "OkrDashboard", "component": "OkrDashboard.vue" },
  323 + { "path": "/skills", "name": "SkillsMarket", "component": "SkillsMarket.vue", "lemonBoss": false },
  324 + { "path": "/jobs", "name": "JobsPanel", "component": "JobsPanel.vue" },
  325 + { "path": "/tasks/:taskId?", "name": "TaskTimeline", "component": "TaskTimeline.vue" },
  326 + { "path": "/pipelines", "name": "PipelineEditor", "component": "PipelineEditor.vue" },
  327 + { "path": "/quota", "name": "QuotaMonitor", "component": "QuotaMonitor.vue" }
  328 + ]
  329 + },
  330 + "composables": [
  331 + "useTheme.js — dark / light theme state",
  332 + "useCompanies.js — companies list cache + active company selection"
  333 + ],
  334 + "i18n": "[N/A] No vue-i18n integration found; UI strings are inline Chinese.",
  335 + "views": {
  336 + "_pagesInScope": 9,
  337 + "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"] },
  338 + "AgentList.vue": { "lines": 300, "purpose": "Agent registry browser with file tree + preview", "apis": ["/api/agent/list", "/api/agent/{id}/files", "/api/agent/{id}/file/*"] },
  339 + "CompanyDashboard.vue": { "lines": 2396, "purpose": "Company control center — tabs: Overview / Agents / Pipelines / Goals / SOP / OKR / Team / Files / Messages / Skills / Jobs / Workspace", "apis": ["/api/company/*"] },
  340 + "JobsPanel.vue": { "lines": 710, "purpose": "Job queue (pipeline-derived) with filters & detail modal", "apis": ["/api/company/{id}/jobs", "/api/company/{id}/jobs/:jobId"] },
  341 + "OkrDashboard.vue": { "lines": 352, "purpose": "OKR list + decomposition tree with progress", "apis": ["/api/goals", "/api/company/{id}/goals"] },
  342 + "PipelineEditor.vue": { "lines": 791, "purpose": "Pipeline definition grid + run controls", "apis": ["/api/company/{id}/pipelines", "/api/company/{id}/pipelines/runs/*"] },
  343 + "QuotaMonitor.vue": { "lines": 526, "purpose": "Token / cost analytics with trend charts + agent leaderboard", "apis": ["/api/agent/quota"] },
  344 + "SkillsMarket.vue": { "lines": 571, "purpose": "[ArenaSK — out of scope]" },
  345 + "TaskTimeline.vue": { "lines": 736, "purpose": "Cross-agent task list + merged event timeline (children expandable)", "apis": ["/api/tasks", "/api/tasks/:taskId/timeline"] },
  346 + "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/*"] }
  347 + }
  348 + },
  349 +
  350 + "data": {
  351 + "users.sqlite": {
  352 + "table": "users",
  353 + "columns": [
  354 + "id INTEGER PRIMARY KEY",
  355 + "email TEXT UNIQUE",
  356 + "password_hash TEXT",
  357 + "display_name TEXT",
  358 + "avatar_url TEXT",
  359 + "provider TEXT (local | github | google)",
  360 + "provider_id TEXT",
  361 + "github_login TEXT",
  362 + "role TEXT (user | admin)",
  363 + "api_key TEXT",
  364 + "created_at TEXT",
  365 + "last_login TEXT"
  366 + ],
  367 + "indexes": ["email", "api_key", "(provider, provider_id)"]
  368 + },
  369 + "fileSystem": {
  370 + "data/companies/{companyId}/": {
  371 + "company.json": "Company metadata (id, name, status, ceo, agents[], heartbeat, config)",
  372 + "agents/{agentId}/": "Per-agent dir (bootstrap files + memory + conversations)",
  373 + "goals/{G-id}/": "OKR tree (goal.json + progress.json + history.log)",
  374 + "pipelines/": "Pipeline definitions + runs/",
  375 + "pipelines/runs/{R-id}.json": "Pipeline run state",
  376 + "workspace/": "Company shared workspace (assets/ + output/)",
  377 + "docs/": "Company documentation",
  378 + "logs/pipeline_runs/":"Pipeline execution traces",
  379 + "logs/audit/": "State change audit"
  380 + },
  381 + "data/goals/{G-id}/": "Global (companyId-less) goals (back-compat)",
  382 + "system/agents/{agentId}/": "System-level agent (master, hunter, digester, evolver, agent-creator, innovator, screenwriter, ...)",
  383 + "system/agents/.tasks/{taskId}.json": "Task metadata used by TaskService",
  384 + "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/)",
  385 + "system/templates/_registry.json": "Template registry (benchmarkScore, usedCount, ...)",
  386 + "server/conversations/": "Reserved — actual conversations stored under each agent dir"
  387 + },
  388 + "agentDir": {
  389 + "bootstrapFiles": ["agent.md", "soul.md", "identity.md", "user.md", "tools.md", "heartbeat.md", "plan.md", "todo.md", "cron.json", "config.json"],
  390 + "runtimeSubdirs": ["memory/", "conversations/", "skills/", ".claude/", "workspace/"],
  391 + "memoryStructure": ["memory.md (working memory)", "evolving/v{n}/ (versioned skill iterations)", "benchmark/v{n}/ (benchmark snapshots)"],
  392 + "conversationLayout": "{convId}/{meta.json, history.json, messages.log, sub-logs/, uploads/}"
  393 + }
  394 + },
  395 +
  396 + "protocols": {
  397 + "agentChatSSE": {
  398 + "path": "POST /api/agent/:agentId/chat",
  399 + "transport": "Server-Sent Events (text/event-stream)",
  400 + "frames": [
  401 + "{ type: 'start', conversationId }",
  402 + "{ type: 'token', delta }",
  403 + "{ type: 'tool', name, input }",
  404 + "{ type: 'result', output }",
  405 + "{ type: 'done', reason }",
  406 + "{ type: 'error', message }"
  407 + ],
  408 + "interrupt": "POST /api/agent/:agentId/chat/:convId/stop"
  409 + },
  410 + "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."
  411 + },
  412 +
  413 + "engineSubsystems": {
  414 + "agenticLoop": "Master agent → propose plan → call_sub_agent → sub-agent runs → result → Master decides next → completion",
  415 + "hiringPipeline": "Template found → copy skeleton + inject company context → mark active. Template not found → scaffold (AgentScaffold.create) → status=training. After score ≥95 → depositTemplate.",
  416 + "companyHeartbeat": "setInterval(intervalMinutes) → _onHeartbeat → (a) _checkAndStartPipelines (cron) (b) _driveActiveRuns (trigger ready steps & timeout) (c) _executeScheduledTasks (d) _triggerCeoHeartbeat (e) _checkDailyReport (08:00). Errors → _sendAlertToMaster.",
  417 + "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)",
  418 + "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}.",
  419 + "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-*."
  420 + },
  421 +
  422 + "architectureDocs": {
  423 + "docs/LEMONBOSS-SYSTEM-DESIGN.md": [
  424 + "System Vision (道-法-术-器)",
  425 + "Four-layer Agent Architecture (Master → CXO → Role → Skill)",
  426 + "Agent Atomic Model (6 bootstrap files)",
  427 + "Agentic Loop Engine",
  428 + "Stratified Meta-Capabilities",
  429 + "Agent Sandbox Autonomy",
  430 + "Inter-Agent Communication",
  431 + "Self-Iteration Closed Loop",
  432 + "Initialization Flow",
  433 + "Runtime Concurrency",
  434 + "Directory Structure",
  435 + "Data Model",
  436 + "API Design",
  437 + "Innovator Meta-Agent",
  438 + "Agent Conversation Architecture",
  439 + "Skill Orchestration",
  440 + "Local-File-First Encapsulation",
  441 + "Tool Isolation",
  442 + "Scaffold-First Agent Creation",
  443 + "FDE Training Pipeline (Hunt→Digest→Evolving)",
  444 + "Agent Memory System",
  445 + "Active Heartbeat",
  446 + "Company Layer",
  447 + "Bootstrap Mechanism",
  448 + "Automated Hiring Orchestration"
  449 + ],
  450 + "docs/architecture-v5.md": [
  451 + "Cognitive Stack (Cerebrum / Cerebellum / Hippocampus)",
  452 + "OKR Goal Management (dot-separated ID hierarchy)",
  453 + "Multi-Tenant Company Architecture",
  454 + "Phase 1-6 status (Phase 7 Cognitive pending)"
  455 + ],
  456 + "docs/task_timeline_design.md": "Task timeline cross-agent merge design",
  457 + "docs/system-design/": "Subsystem deep-dives",
  458 + "docs/user-guides/": "User-facing docs"
  459 + },
  460 +
  461 + "outputPlan": {
  462 + "siteRoot": "docs/prd/",
  463 + "chapters": [
  464 + "01-overview",
  465 + "02-prd",
  466 + "03-feature-tree",
  467 + "04-workflows",
  468 + "05-prototype",
  469 + "06-sdd-model",
  470 + "07-sdd-view",
  471 + "08-sdd-control",
  472 + "09-sdd-process",
  473 + "10-reproducibility"
  474 + ],
  475 + "indexShell": "index.html + assets/{app.css, app.js, lunr.min.js, lunr.zh.js, search-index.json}",
  476 + "interaction": "left 280px 3-level menu + right iframe + top search → results tab; hash router (#/06-sdd-model/company-entity); dark/light toggle; mobile burger",
  477 + "diagrams": "Mermaid inline + SVG (no CDN)",
  478 + "search": "lunr.js with Chinese bigram tokenizer; index built from chapter HTML titles + body text",
  479 + "verification": "python3 -m http.server 8080 -d docs/prd → manual click-through 9 page menus; search 'Company / OKR / Pipeline / Agent / Task' all hit"
  480 + },
  481 +
  482 + "scopeGuard_DoNotMention": [
  483 + "skill-db / skill-registry / skill-dir-manager / skill-packager",
  484 + "benchmark-db / benchmark-runner",
  485 + "evolution-loop / retrain-pipeline / retrain-watchdog",
  486 + "/api/open/* /api/arena/* /api/innovator/* /api/master/* /api/evolution",
  487 + "Skill marketplace UI (SkillsMarket.vue)",
  488 + "ELO ranking, six-dimensional benchmark",
  489 + "WhatsApp bot, Lark integration",
  490 + "Master Agent chat at /api/master/chat (Master tools still used internally via agent-server's /api/agent/master/chat — keep that)"
  491 + ]
  492 +}
... ...
docs/prd/assets/app.css 0 → 100644
  1 +/* ============================================================
  2 + LemonBoss PRD/SDD doc site — global styles
  3 + ============================================================ */
  4 +
  5 +:root {
  6 + --bg: #fafaf7;
  7 + --bg-2: #ffffff;
  8 + --bg-3: #f3f3ee;
  9 + --fg: #1d1d1f;
  10 + --fg-2: #5a5a60;
  11 + --fg-3: #8a8a92;
  12 + --border: #e3e3dd;
  13 + --border-2: #d4d4cd;
  14 + --accent: #f5a524; /* lemon */
  15 + --accent-2: #e69500;
  16 + --accent-soft: #fff4dc;
  17 + --link: #0a66c2;
  18 + --code-bg: #f5f5ef;
  19 + --code-fg: #2d2d33;
  20 + --shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 12px rgba(0,0,0,.04);
  21 + --shadow-lg: 0 12px 32px rgba(0,0,0,.08);
  22 + --radius: 8px;
  23 + --radius-sm: 4px;
  24 + --side-w: 280px;
  25 + --top-h: 52px;
  26 + --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Roboto Mono", monospace;
  27 + --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
  28 + "Hiragino Sans GB", "Microsoft YaHei", Helvetica, Arial, sans-serif;
  29 +}
  30 +
  31 +[data-theme="dark"] {
  32 + --bg: #16161a;
  33 + --bg-2: #1d1d22;
  34 + --bg-3: #25252b;
  35 + --fg: #e8e8ea;
  36 + --fg-2: #b0b0b8;
  37 + --fg-3: #76767e;
  38 + --border: #2d2d34;
  39 + --border-2: #3a3a42;
  40 + --accent: #f5a524;
  41 + --accent-2: #ffbe3d;
  42 + --accent-soft: #3a2f15;
  43 + --link: #6db4ff;
  44 + --code-bg: #25252b;
  45 + --code-fg: #e6e6e8;
  46 + --shadow: 0 1px 2px rgba(0,0,0,.4), 0 4px 12px rgba(0,0,0,.3);
  47 + --shadow-lg: 0 12px 32px rgba(0,0,0,.6);
  48 +}
  49 +
  50 +* { box-sizing: border-box; }
  51 +
  52 +html, body {
  53 + margin: 0;
  54 + padding: 0;
  55 + height: 100%;
  56 + font-family: var(--sans);
  57 + font-size: 14px;
  58 + background: var(--bg);
  59 + color: var(--fg);
  60 + -webkit-font-smoothing: antialiased;
  61 + -moz-osx-font-smoothing: grayscale;
  62 +}
  63 +
  64 +a { color: var(--link); text-decoration: none; }
  65 +a:hover { text-decoration: underline; }
  66 +
  67 +button {
  68 + background: none;
  69 + border: none;
  70 + color: inherit;
  71 + font: inherit;
  72 + cursor: pointer;
  73 +}
  74 +
  75 +/* ─── Topbar ─── */
  76 +.topbar {
  77 + position: fixed;
  78 + top: 0; left: 0; right: 0;
  79 + height: var(--top-h);
  80 + background: var(--bg-2);
  81 + border-bottom: 1px solid var(--border);
  82 + display: flex;
  83 + align-items: center;
  84 + gap: 12px;
  85 + padding: 0 16px;
  86 + z-index: 50;
  87 +}
  88 +
  89 +.burger {
  90 + display: none;
  91 + padding: 6px;
  92 + border-radius: var(--radius-sm);
  93 + color: var(--fg-2);
  94 +}
  95 +.burger:hover { background: var(--bg-3); color: var(--fg); }
  96 +
  97 +.brand {
  98 + display: flex;
  99 + align-items: center;
  100 + gap: 8px;
  101 + font-weight: 600;
  102 + color: var(--fg);
  103 + flex-shrink: 0;
  104 +}
  105 +.brand .lemon { font-size: 20px; }
  106 +.brand .title { font-size: 15px; }
  107 +.brand .subtitle {
  108 + font-size: 12px;
  109 + font-weight: 400;
  110 + color: var(--fg-3);
  111 + margin-left: 6px;
  112 + padding-left: 10px;
  113 + border-left: 1px solid var(--border);
  114 +}
  115 +
  116 +.search-wrap {
  117 + flex: 1;
  118 + max-width: 520px;
  119 + margin: 0 auto;
  120 + position: relative;
  121 +}
  122 +.search-wrap input {
  123 + width: 100%;
  124 + height: 32px;
  125 + padding: 0 36px 0 12px;
  126 + border: 1px solid var(--border);
  127 + background: var(--bg);
  128 + border-radius: var(--radius-sm);
  129 + color: var(--fg);
  130 + font-family: var(--sans);
  131 + outline: none;
  132 + transition: border-color .15s, box-shadow .15s;
  133 +}
  134 +.search-wrap input:focus {
  135 + border-color: var(--accent);
  136 + box-shadow: 0 0 0 3px var(--accent-soft);
  137 +}
  138 +.kbd {
  139 + position: absolute;
  140 + right: 8px;
  141 + top: 50%;
  142 + transform: translateY(-50%);
  143 + font-family: var(--mono);
  144 + font-size: 11px;
  145 + color: var(--fg-3);
  146 + background: var(--bg-3);
  147 + border: 1px solid var(--border);
  148 + border-radius: 3px;
  149 + padding: 1px 5px;
  150 + pointer-events: none;
  151 +}
  152 +
  153 +.theme-toggle {
  154 + padding: 6px;
  155 + border-radius: var(--radius-sm);
  156 + color: var(--fg-2);
  157 +}
  158 +.theme-toggle:hover { background: var(--bg-3); color: var(--fg); }
  159 +.theme-toggle .ico-moon { display: none; }
  160 +[data-theme="dark"] .theme-toggle .ico-sun { display: none; }
  161 +[data-theme="dark"] .theme-toggle .ico-moon { display: block; }
  162 +
  163 +/* ─── Layout ─── */
  164 +.layout {
  165 + position: fixed;
  166 + top: var(--top-h);
  167 + left: 0; right: 0; bottom: 0;
  168 + display: flex;
  169 +}
  170 +
  171 +/* ─── Sidebar ─── */
  172 +.sidebar {
  173 + width: var(--side-w);
  174 + background: var(--bg-2);
  175 + border-right: 1px solid var(--border);
  176 + overflow-y: auto;
  177 + overflow-x: hidden;
  178 + flex-shrink: 0;
  179 +}
  180 +.sidebar::-webkit-scrollbar { width: 8px; }
  181 +.sidebar::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 4px; }
  182 +
  183 +.menu { padding: 8px 0; }
  184 +.menu-chapter { margin-bottom: 2px; }
  185 +.menu-chapter > .menu-link {
  186 + font-weight: 600;
  187 + color: var(--fg);
  188 +}
  189 +.menu-section > .menu-link {
  190 + font-weight: 500;
  191 + color: var(--fg-2);
  192 + padding-left: 28px;
  193 +}
  194 +.menu-sub > .menu-link {
  195 + font-weight: 400;
  196 + color: var(--fg-3);
  197 + padding-left: 44px;
  198 + font-size: 13px;
  199 +}
  200 +
  201 +.menu-link {
  202 + display: flex;
  203 + align-items: center;
  204 + gap: 6px;
  205 + padding: 6px 12px;
  206 + cursor: pointer;
  207 + text-decoration: none;
  208 + user-select: none;
  209 + border-radius: 4px;
  210 + margin: 1px 6px;
  211 + transition: background .12s;
  212 + line-height: 1.4;
  213 +}
  214 +.menu-link:hover {
  215 + background: var(--bg-3);
  216 + text-decoration: none;
  217 +}
  218 +.menu-link.active {
  219 + background: var(--accent-soft);
  220 + color: var(--accent-2);
  221 + font-weight: 600;
  222 +}
  223 +[data-theme="dark"] .menu-link.active { color: var(--accent); }
  224 +
  225 +.caret {
  226 + width: 12px;
  227 + text-align: center;
  228 + font-size: 10px;
  229 + color: var(--fg-3);
  230 + flex-shrink: 0;
  231 + transition: transform .15s;
  232 +}
  233 +.menu-chapter.collapsed > .menu-link .caret,
  234 +.menu-section.collapsed > .menu-link .caret {
  235 + transform: rotate(-90deg);
  236 +}
  237 +
  238 +.menu-chapter > .children,
  239 +.menu-section > .children {
  240 + overflow: hidden;
  241 + max-height: 4000px;
  242 + transition: max-height .2s ease-out;
  243 +}
  244 +.menu-chapter.collapsed > .children,
  245 +.menu-section.collapsed > .children {
  246 + max-height: 0;
  247 +}
  248 +
  249 +.menu-num {
  250 + font-family: var(--mono);
  251 + font-size: 11px;
  252 + color: var(--fg-3);
  253 + margin-right: 4px;
  254 + flex-shrink: 0;
  255 +}
  256 +.menu-link.active .menu-num { color: var(--accent-2); }
  257 +
  258 +/* ─── Content ─── */
  259 +.content {
  260 + flex: 1;
  261 + display: flex;
  262 + flex-direction: column;
  263 + background: var(--bg);
  264 + min-width: 0;
  265 +}
  266 +
  267 +.tabs {
  268 + height: 36px;
  269 + display: flex;
  270 + border-bottom: 1px solid var(--border);
  271 + background: var(--bg-2);
  272 + padding: 0 12px;
  273 + gap: 4px;
  274 + flex-shrink: 0;
  275 +}
  276 +.tab {
  277 + padding: 0 14px;
  278 + height: 100%;
  279 + display: flex;
  280 + align-items: center;
  281 + gap: 6px;
  282 + border-bottom: 2px solid transparent;
  283 + color: var(--fg-2);
  284 + font-size: 13px;
  285 + transition: color .12s, border-color .12s;
  286 +}
  287 +.tab:hover { color: var(--fg); }
  288 +.tab.active {
  289 + color: var(--accent-2);
  290 + border-bottom-color: var(--accent);
  291 + font-weight: 500;
  292 +}
  293 +[data-theme="dark"] .tab.active { color: var(--accent); }
  294 +.tab .badge {
  295 + background: var(--bg-3);
  296 + color: var(--fg-3);
  297 + font-size: 11px;
  298 + padding: 1px 6px;
  299 + border-radius: 9px;
  300 + font-family: var(--mono);
  301 +}
  302 +.tab.active .badge {
  303 + background: var(--accent);
  304 + color: #fff;
  305 +}
  306 +
  307 +.pane {
  308 + display: none;
  309 + flex: 1;
  310 + overflow: hidden;
  311 +}
  312 +.pane.active { display: flex; flex-direction: column; }
  313 +
  314 +.pane-reader iframe {
  315 + flex: 1;
  316 + width: 100%;
  317 + border: none;
  318 + background: var(--bg);
  319 +}
  320 +
  321 +/* ─── Search results pane ─── */
  322 +.search-header {
  323 + display: flex;
  324 + justify-content: space-between;
  325 + align-items: center;
  326 + padding: 12px 20px;
  327 + border-bottom: 1px solid var(--border);
  328 + background: var(--bg-2);
  329 + color: var(--fg-2);
  330 + font-size: 13px;
  331 +}
  332 +.clear-btn {
  333 + padding: 4px 10px;
  334 + border-radius: var(--radius-sm);
  335 + border: 1px solid var(--border);
  336 + color: var(--fg-2);
  337 + font-size: 12px;
  338 +}
  339 +.clear-btn:hover { background: var(--bg-3); color: var(--fg); }
  340 +
  341 +.results {
  342 + list-style: none;
  343 + margin: 0;
  344 + padding: 8px 0;
  345 + overflow-y: auto;
  346 + flex: 1;
  347 +}
  348 +.result-item {
  349 + padding: 10px 20px;
  350 + border-bottom: 1px solid var(--border);
  351 + cursor: pointer;
  352 + transition: background .1s;
  353 +}
  354 +.result-item:hover { background: var(--bg-3); }
  355 +.result-path {
  356 + font-size: 11px;
  357 + color: var(--fg-3);
  358 + font-family: var(--mono);
  359 + margin-bottom: 4px;
  360 +}
  361 +.result-title {
  362 + font-size: 14px;
  363 + font-weight: 600;
  364 + color: var(--fg);
  365 + margin-bottom: 4px;
  366 +}
  367 +.result-snippet {
  368 + font-size: 12px;
  369 + color: var(--fg-2);
  370 + line-height: 1.5;
  371 +}
  372 +.result-snippet mark {
  373 + background: var(--accent-soft);
  374 + color: var(--accent-2);
  375 + padding: 0 2px;
  376 + border-radius: 2px;
  377 + font-weight: 600;
  378 +}
  379 +[data-theme="dark"] .result-snippet mark { color: var(--accent); }
  380 +
  381 +/* ─── Responsive ─── */
  382 +@media (max-width: 768px) {
  383 + .burger { display: flex; }
  384 + .brand .subtitle { display: none; }
  385 + .sidebar {
  386 + position: fixed;
  387 + left: 0; top: var(--top-h); bottom: 0;
  388 + z-index: 40;
  389 + transform: translateX(-100%);
  390 + transition: transform .2s;
  391 + box-shadow: var(--shadow-lg);
  392 + }
  393 + body.sidebar-open .sidebar { transform: translateX(0); }
  394 + .search-wrap { max-width: none; }
  395 +}
... ...
docs/prd/assets/app.js 0 → 100644
  1 +/* ============================================================
  2 + LemonBoss doc-site shell — menu + hash router + search
  3 + ============================================================ */
  4 +
  5 +(async () => {
  6 + // ── Menu (loaded synchronously via separate script tag if available) ──
  7 + await loadScript('./assets/menu.js');
  8 +
  9 + const MENU = window.LEMONBOSS_MENU || [];
  10 + const menuEl = document.getElementById('menu');
  11 + const readerEl = document.getElementById('reader');
  12 + const searchEl = document.getElementById('search');
  13 + const resultsEl = document.getElementById('results');
  14 + const summaryEl = document.getElementById('search-summary');
  15 + const tabBtns = document.querySelectorAll('.tab');
  16 + const tabSearch = document.getElementById('tab-search');
  17 + const countEl = document.getElementById('search-count');
  18 + const burger = document.getElementById('burger');
  19 + const themeBtn = document.getElementById('theme');
  20 +
  21 + // ──────────────────────────────────────────────
  22 + // THEME
  23 + // ──────────────────────────────────────────────
  24 + const storedTheme = localStorage.getItem('lemonboss-theme');
  25 + if (storedTheme) document.documentElement.dataset.theme = storedTheme;
  26 + themeBtn.addEventListener('click', () => {
  27 + const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
  28 + document.documentElement.dataset.theme = next;
  29 + localStorage.setItem('lemonboss-theme', next);
  30 + });
  31 +
  32 + // ──────────────────────────────────────────────
  33 + // MENU RENDER
  34 + // ──────────────────────────────────────────────
  35 + function renderMenu() {
  36 + MENU.forEach(ch => menuEl.appendChild(renderNode(ch, 'chapter')));
  37 + }
  38 + function renderNode(node, kind) {
  39 + const wrap = document.createElement('div');
  40 + wrap.className = `menu-${kind}`;
  41 + wrap.dataset.id = node.id;
  42 +
  43 + const link = document.createElement('a');
  44 + link.className = 'menu-link';
  45 + link.href = '#/' + node.href.replace(/#.*$/, '') + (node.href.includes('#') ? node.href.slice(node.href.indexOf('#')) : '');
  46 + link.dataset.href = node.href;
  47 +
  48 + if (node.children) {
  49 + const caret = document.createElement('span');
  50 + caret.className = 'caret';
  51 + caret.textContent = '▾';
  52 + caret.addEventListener('click', (e) => {
  53 + e.preventDefault(); e.stopPropagation();
  54 + wrap.classList.toggle('collapsed');
  55 + });
  56 + link.appendChild(caret);
  57 + } else {
  58 + const dot = document.createElement('span');
  59 + dot.className = 'caret';
  60 + dot.textContent = '·';
  61 + link.appendChild(dot);
  62 + }
  63 +
  64 + const num = document.createElement('span');
  65 + num.className = 'menu-num';
  66 + num.textContent = node.num;
  67 + link.appendChild(num);
  68 +
  69 + const txt = document.createElement('span');
  70 + txt.textContent = node.title;
  71 + link.appendChild(txt);
  72 +
  73 + link.addEventListener('click', (e) => {
  74 + e.preventDefault();
  75 + navigateTo(node.href);
  76 + if (window.innerWidth < 768) document.body.classList.remove('sidebar-open');
  77 + });
  78 +
  79 + wrap.appendChild(link);
  80 +
  81 + if (node.children) {
  82 + const cs = document.createElement('div');
  83 + cs.className = 'children';
  84 + const childKind = kind === 'chapter' ? 'section' : 'sub';
  85 + node.children.forEach(c => cs.appendChild(renderNode(c, childKind)));
  86 + wrap.appendChild(cs);
  87 + }
  88 + return wrap;
  89 + }
  90 +
  91 + function highlightMenu(href) {
  92 + document.querySelectorAll('.menu-link').forEach(a => a.classList.remove('active'));
  93 + const target = stripHash(href);
  94 + let best = null;
  95 + document.querySelectorAll('.menu-link').forEach(a => {
  96 + const ah = a.dataset.href || '';
  97 + if (stripHash(ah) === target) {
  98 + if (!best || (ah.includes('#') && !best.dataset.href.includes('#'))) best = a;
  99 + if (ah === href) best = a; // exact match wins
  100 + }
  101 + });
  102 + if (best) {
  103 + best.classList.add('active');
  104 + // expand all ancestors
  105 + let p = best.parentElement;
  106 + while (p && p !== menuEl) {
  107 + if (p.classList.contains('collapsed')) p.classList.remove('collapsed');
  108 + p = p.parentElement;
  109 + }
  110 + best.scrollIntoView({ block: 'nearest' });
  111 + }
  112 + }
  113 + const stripHash = (h) => h.split('#')[0];
  114 +
  115 + // ──────────────────────────────────────────────
  116 + // HASH ROUTER
  117 + // ──────────────────────────────────────────────
  118 + function navigateTo(href) {
  119 + location.hash = '#/' + href;
  120 + }
  121 + function onHashChange() {
  122 + const raw = location.hash.replace(/^#\//, '');
  123 + const href = raw || '01-overview/index.html';
  124 + readerEl.src = './chapters/' + href;
  125 + highlightMenu(href);
  126 + // switch back to reader pane
  127 + switchTab('reader');
  128 + }
  129 + window.addEventListener('hashchange', onHashChange);
  130 +
  131 + // ──────────────────────────────────────────────
  132 + // TABS
  133 + // ──────────────────────────────────────────────
  134 + function switchTab(name) {
  135 + tabBtns.forEach(b => b.classList.toggle('active', b.dataset.pane === name));
  136 + document.querySelectorAll('.pane').forEach(p =>
  137 + p.classList.toggle('active', p.classList.contains('pane-' + name))
  138 + );
  139 + }
  140 + tabBtns.forEach(b => b.addEventListener('click', () => switchTab(b.dataset.pane)));
  141 +
  142 + // ──────────────────────────────────────────────
  143 + // SEARCH
  144 + // ─ Loads search-index.json (built by scripts/build-search-index.js)
  145 + // ─ Tokenization: lowercase, split on whitespace+punct, + Chinese bigram
  146 + // ──────────────────────────────────────────────
  147 + let INDEX = null;
  148 + async function loadIndex() {
  149 + if (INDEX) return INDEX;
  150 + try {
  151 + const r = await fetch('./assets/search-index.json');
  152 + INDEX = await r.json();
  153 + } catch (e) {
  154 + INDEX = { docs: [], invIndex: {} };
  155 + console.warn('[search] no index available — run scripts/build-search-index.js');
  156 + }
  157 + return INDEX;
  158 + }
  159 +
  160 + function tokenize(text) {
  161 + const tokens = [];
  162 + const lower = text.toLowerCase();
  163 + // ASCII words / digits
  164 + (lower.match(/[a-z0-9_]+/g) || []).forEach(w => tokens.push(w));
  165 + // CJK bigrams (一-鿿 plus extensions)
  166 + const cjk = lower.match(/[㐀-鿿]+/g) || [];
  167 + cjk.forEach(run => {
  168 + if (run.length === 1) tokens.push(run);
  169 + else {
  170 + for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2));
  171 + }
  172 + });
  173 + return tokens;
  174 + }
  175 +
  176 + async function runSearch(query) {
  177 + if (!query || query.trim().length === 0) {
  178 + resultsEl.innerHTML = '';
  179 + summaryEl.textContent = '输入关键词开始搜索';
  180 + countEl.textContent = '0';
  181 + return;
  182 + }
  183 + const idx = await loadIndex();
  184 + const terms = tokenize(query);
  185 + if (terms.length === 0) {
  186 + resultsEl.innerHTML = '';
  187 + summaryEl.textContent = `「${query}」无可识别 token`;
  188 + countEl.textContent = '0';
  189 + return;
  190 + }
  191 +
  192 + // score docs by term frequency sum
  193 + const scores = new Map();
  194 + let hitDocs = null;
  195 + for (const t of terms) {
  196 + const posting = idx.invIndex[t];
  197 + if (!posting) continue;
  198 + const docs = new Set();
  199 + for (const [docId, tf] of posting) {
  200 + scores.set(docId, (scores.get(docId) || 0) + tf);
  201 + docs.add(docId);
  202 + }
  203 + hitDocs = hitDocs ? new Set([...hitDocs].filter(d => docs.has(d))) : docs;
  204 + }
  205 + // fallback: if intersection empty, use union
  206 + const candidates = (hitDocs && hitDocs.size > 0) ? [...hitDocs] : [...scores.keys()];
  207 + candidates.sort((a, b) => scores.get(b) - scores.get(a));
  208 + const top = candidates.slice(0, 50);
  209 +
  210 + renderResults(top, query, idx);
  211 + }
  212 +
  213 + function renderResults(docIds, query, idx) {
  214 + resultsEl.innerHTML = '';
  215 + const docs = idx.docs;
  216 + if (docIds.length === 0) {
  217 + summaryEl.textContent = `「${query}」没有命中`;
  218 + countEl.textContent = '0';
  219 + return;
  220 + }
  221 + summaryEl.textContent = `「${query}」命中 ${docIds.length} 条`;
  222 + countEl.textContent = String(docIds.length);
  223 +
  224 + docIds.forEach(id => {
  225 + const d = docs[id];
  226 + if (!d) return;
  227 + const li = document.createElement('li');
  228 + li.className = 'result-item';
  229 + li.innerHTML = `
  230 + <div class="result-path">${d.crumbs.join(' › ')}</div>
  231 + <div class="result-title">${escapeHtml(d.title)}</div>
  232 + <div class="result-snippet">${snippet(d.text, query)}</div>
  233 + `;
  234 + li.addEventListener('click', () => {
  235 + navigateTo(d.href);
  236 + });
  237 + resultsEl.appendChild(li);
  238 + });
  239 + }
  240 +
  241 + function escapeHtml(s) {
  242 + return String(s).replace(/[&<>"']/g, c => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;' }[c]));
  243 + }
  244 + function snippet(text, query) {
  245 + const lower = text.toLowerCase();
  246 + const q = query.toLowerCase();
  247 + let i = lower.indexOf(q);
  248 + if (i < 0) {
  249 + const t = tokenize(query)[0];
  250 + if (t) i = lower.indexOf(t);
  251 + }
  252 + if (i < 0) i = 0;
  253 + const start = Math.max(0, i - 60);
  254 + const end = Math.min(text.length, i + 140);
  255 + let frag = text.slice(start, end);
  256 + if (start > 0) frag = '…' + frag;
  257 + if (end < text.length) frag = frag + '…';
  258 + // highlight
  259 + const re = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
  260 + return escapeHtml(frag).replace(re, m => `<mark>${escapeHtml(m)}</mark>`);
  261 + }
  262 +
  263 + let timer = null;
  264 + searchEl.addEventListener('input', () => {
  265 + clearTimeout(timer);
  266 + const v = searchEl.value;
  267 + timer = setTimeout(async () => {
  268 + await runSearch(v);
  269 + if (v.trim()) switchTab('search');
  270 + }, 120);
  271 + });
  272 + searchEl.addEventListener('keydown', (e) => {
  273 + if (e.key === 'Enter') {
  274 + runSearch(searchEl.value);
  275 + switchTab('search');
  276 + } else if (e.key === 'Escape') {
  277 + searchEl.value = '';
  278 + runSearch('');
  279 + switchTab('reader');
  280 + }
  281 + });
  282 + document.getElementById('clear-search').addEventListener('click', () => {
  283 + searchEl.value = '';
  284 + runSearch('');
  285 + switchTab('reader');
  286 + searchEl.focus();
  287 + });
  288 +
  289 + // ──────────────────────────────────────────────
  290 + // GLOBAL HOTKEYS
  291 + // ──────────────────────────────────────────────
  292 + document.addEventListener('keydown', (e) => {
  293 + const tag = (e.target.tagName || '').toLowerCase();
  294 + if (tag === 'input' || tag === 'textarea') return;
  295 + if (e.key === '/') {
  296 + e.preventDefault();
  297 + searchEl.focus();
  298 + searchEl.select();
  299 + }
  300 + });
  301 +
  302 + // ──────────────────────────────────────────────
  303 + // MOBILE
  304 + // ──────────────────────────────────────────────
  305 + burger.addEventListener('click', () => document.body.classList.toggle('sidebar-open'));
  306 +
  307 + // ──────────────────────────────────────────────
  308 + // BOOT
  309 + // ──────────────────────────────────────────────
  310 + renderMenu();
  311 + if (!location.hash) location.hash = '#/01-overview/index.html';
  312 + else onHashChange();
  313 + onHashChange();
  314 +
  315 + // helper: load script
  316 + function loadScript(src) {
  317 + return new Promise((res, rej) => {
  318 + const s = document.createElement('script');
  319 + s.src = src;
  320 + s.onload = () => res();
  321 + s.onerror = () => rej(new Error('Failed to load ' + src));
  322 + document.head.appendChild(s);
  323 + });
  324 + }
  325 +})();
... ...
docs/prd/assets/chapter.css 0 → 100644
  1 +/* ============================================================
  2 + Chapter content styles — applied inside iframe
  3 + ============================================================ */
  4 +
  5 +:root {
  6 + --bg: #fafaf7;
  7 + --bg-2: #ffffff;
  8 + --bg-3: #f3f3ee;
  9 + --fg: #1d1d1f;
  10 + --fg-2: #5a5a60;
  11 + --fg-3: #8a8a92;
  12 + --border: #e3e3dd;
  13 + --border-2: #d4d4cd;
  14 + --accent: #f5a524;
  15 + --accent-2: #e69500;
  16 + --accent-soft:#fff4dc;
  17 + --link: #0a66c2;
  18 + --code-bg: #f5f5ef;
  19 + --code-fg: #2d2d33;
  20 + --shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 12px rgba(0,0,0,.04);
  21 + --radius: 8px;
  22 + --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Roboto Mono", monospace;
  23 + --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
  24 + "Hiragino Sans GB", "Microsoft YaHei", Helvetica, Arial, sans-serif;
  25 +}
  26 +
  27 +@media (prefers-color-scheme: dark) {
  28 + :root {
  29 + --bg: #16161a;
  30 + --bg-2: #1d1d22;
  31 + --bg-3: #25252b;
  32 + --fg: #e8e8ea;
  33 + --fg-2: #b0b0b8;
  34 + --fg-3: #76767e;
  35 + --border: #2d2d34;
  36 + --border-2: #3a3a42;
  37 + --accent: #f5a524;
  38 + --accent-2: #ffbe3d;
  39 + --accent-soft:#3a2f15;
  40 + --link: #6db4ff;
  41 + --code-bg: #25252b;
  42 + --code-fg: #e6e6e8;
  43 + }
  44 +}
  45 +
  46 +* { box-sizing: border-box; }
  47 +
  48 +html, body {
  49 + margin: 0;
  50 + padding: 0;
  51 + font-family: var(--sans);
  52 + font-size: 14px;
  53 + line-height: 1.65;
  54 + background: var(--bg);
  55 + color: var(--fg);
  56 + -webkit-font-smoothing: antialiased;
  57 +}
  58 +
  59 +.page {
  60 + max-width: 980px;
  61 + margin: 0 auto;
  62 + padding: 40px 48px 80px;
  63 +}
  64 +
  65 +/* breadcrumbs */
  66 +.crumbs {
  67 + font-family: var(--mono);
  68 + font-size: 12px;
  69 + color: var(--fg-3);
  70 + margin-bottom: 8px;
  71 +}
  72 +.crumbs span { margin: 0 4px; color: var(--fg-3); }
  73 +
  74 +/* headings */
  75 +h1, h2, h3, h4 {
  76 + color: var(--fg);
  77 + line-height: 1.3;
  78 + scroll-margin-top: 20px;
  79 +}
  80 +h1 {
  81 + font-size: 30px;
  82 + font-weight: 700;
  83 + margin: 0 0 8px;
  84 + letter-spacing: -.02em;
  85 +}
  86 +h1 .h-num {
  87 + font-family: var(--mono);
  88 + font-size: 18px;
  89 + color: var(--accent-2);
  90 + margin-right: 8px;
  91 + font-weight: 500;
  92 +}
  93 +h2 {
  94 + font-size: 22px;
  95 + font-weight: 600;
  96 + margin: 48px 0 12px;
  97 + padding-top: 12px;
  98 + border-top: 1px solid var(--border);
  99 +}
  100 +h2 .h-num {
  101 + font-family: var(--mono);
  102 + font-size: 14px;
  103 + color: var(--accent-2);
  104 + margin-right: 6px;
  105 + font-weight: 500;
  106 +}
  107 +h3 {
  108 + font-size: 17px;
  109 + font-weight: 600;
  110 + margin: 28px 0 8px;
  111 +}
  112 +h3 .h-num {
  113 + font-family: var(--mono);
  114 + font-size: 13px;
  115 + color: var(--fg-3);
  116 + margin-right: 6px;
  117 +}
  118 +h4 {
  119 + font-size: 14px;
  120 + font-weight: 600;
  121 + margin: 20px 0 6px;
  122 + color: var(--fg-2);
  123 +}
  124 +
  125 +.lede {
  126 + font-size: 16px;
  127 + color: var(--fg-2);
  128 + margin: 0 0 24px;
  129 + line-height: 1.6;
  130 +}
  131 +
  132 +/* paragraphs */
  133 +p { margin: 8px 0 14px; color: var(--fg); }
  134 +
  135 +/* links */
  136 +a { color: var(--link); text-decoration: none; }
  137 +a:hover { text-decoration: underline; }
  138 +
  139 +/* lists */
  140 +ul, ol { padding-left: 24px; }
  141 +li { margin: 3px 0; }
  142 +
  143 +/* inline code */
  144 +code {
  145 + font-family: var(--mono);
  146 + font-size: 12.5px;
  147 + background: var(--code-bg);
  148 + color: var(--code-fg);
  149 + padding: 1px 6px;
  150 + border-radius: 3px;
  151 + border: 1px solid var(--border);
  152 +}
  153 +
  154 +/* code block */
  155 +pre {
  156 + background: var(--code-bg);
  157 + color: var(--code-fg);
  158 + border: 1px solid var(--border);
  159 + border-radius: var(--radius);
  160 + padding: 14px 16px;
  161 + overflow-x: auto;
  162 + font-family: var(--mono);
  163 + font-size: 12.5px;
  164 + line-height: 1.55;
  165 +}
  166 +pre code {
  167 + background: none;
  168 + border: none;
  169 + padding: 0;
  170 + font-size: inherit;
  171 +}
  172 +
  173 +/* keyword colors for code */
  174 +.k-kw { color: #c0392b; }
  175 +.k-str { color: #16a085; }
  176 +.k-num { color: #8e44ad; }
  177 +.k-cmt { color: #95a5a6; font-style: italic; }
  178 +.k-fn { color: #2980b9; }
  179 +[data-theme="dark"] .k-kw { color: #ff7b7b; }
  180 +[data-theme="dark"] .k-str { color: #6cdcc0; }
  181 +[data-theme="dark"] .k-cmt { color: #707078; }
  182 +
  183 +/* tables */
  184 +table {
  185 + width: 100%;
  186 + border-collapse: collapse;
  187 + margin: 14px 0 20px;
  188 + font-size: 13px;
  189 +}
  190 +th, td {
  191 + text-align: left;
  192 + padding: 8px 12px;
  193 + border-bottom: 1px solid var(--border);
  194 + vertical-align: top;
  195 +}
  196 +th {
  197 + background: var(--bg-3);
  198 + color: var(--fg);
  199 + font-weight: 600;
  200 + font-size: 12px;
  201 + text-transform: uppercase;
  202 + letter-spacing: .04em;
  203 +}
  204 +tbody tr:hover { background: var(--bg-3); }
  205 +td code {
  206 + font-size: 12px;
  207 + padding: 0 4px;
  208 +}
  209 +td.method-GET { color: #16a085; font-family: var(--mono); font-weight: 600; }
  210 +td.method-POST { color: #2980b9; font-family: var(--mono); font-weight: 600; }
  211 +td.method-PUT { color: #e67e22; font-family: var(--mono); font-weight: 600; }
  212 +td.method-DELETE { color: #c0392b; font-family: var(--mono); font-weight: 600; }
  213 +td.method-PATCH { color: #8e44ad; font-family: var(--mono); font-weight: 600; }
  214 +
  215 +/* callout */
  216 +.callout {
  217 + border-left: 3px solid var(--accent);
  218 + background: var(--accent-soft);
  219 + padding: 10px 14px;
  220 + border-radius: 0 var(--radius) var(--radius) 0;
  221 + margin: 14px 0 18px;
  222 + font-size: 13px;
  223 + color: var(--fg);
  224 +}
  225 +.callout-info { border-left-color: var(--link); background: rgba(10, 102, 194, .08); }
  226 +.callout-warn { border-left-color: #e67e22; background: rgba(230, 126, 34, .1); }
  227 +.callout-danger{ border-left-color: #c0392b; background: rgba(192, 57, 43, .1); }
  228 +
  229 +/* cards grid */
  230 +.cards {
  231 + display: grid;
  232 + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  233 + gap: 12px;
  234 + margin: 16px 0;
  235 +}
  236 +.card {
  237 + background: var(--bg-2);
  238 + border: 1px solid var(--border);
  239 + border-radius: var(--radius);
  240 + padding: 14px;
  241 + box-shadow: var(--shadow);
  242 +}
  243 +.card-title {
  244 + font-weight: 600;
  245 + margin-bottom: 4px;
  246 + font-size: 14px;
  247 +}
  248 +.card-meta {
  249 + font-family: var(--mono);
  250 + font-size: 11px;
  251 + color: var(--fg-3);
  252 + margin-bottom: 6px;
  253 +}
  254 +.card-body { color: var(--fg-2); font-size: 13px; }
  255 +
  256 +/* badges */
  257 +.badge {
  258 + display: inline-block;
  259 + font-family: var(--mono);
  260 + font-size: 11px;
  261 + padding: 1px 7px;
  262 + border-radius: 9px;
  263 + background: var(--bg-3);
  264 + color: var(--fg-2);
  265 + border: 1px solid var(--border);
  266 + margin: 0 2px;
  267 +}
  268 +.badge-p0 { background: #fce4e4; color: #c0392b; border-color: #f5c6c6; }
  269 +.badge-p1 { background: #fef0d4; color: #c87f0a; border-color: #f6d999; }
  270 +.badge-p2 { background: #e6f0fa; color: #2364ab; border-color: #b9d4ee; }
  271 +.badge-ok { background: #def5e3; color: #1e8449; border-color: #a9e0b8; }
  272 +.badge-na { background: var(--bg-3); color: var(--fg-3); border-color: var(--border); }
  273 +[data-theme="dark"] .badge-p0 { background: #4d2222; color: #ff8e85; }
  274 +[data-theme="dark"] .badge-p1 { background: #4d3a14; color: #ffc966; }
  275 +[data-theme="dark"] .badge-p2 { background: #1b3650; color: #8db8e0; }
  276 +[data-theme="dark"] .badge-ok { background: #1f3d27; color: #8edf9e; }
  277 +
  278 +/* mermaid containers */
  279 +.mermaid {
  280 + background: var(--bg-2);
  281 + border: 1px solid var(--border);
  282 + border-radius: var(--radius);
  283 + padding: 16px;
  284 + margin: 14px 0;
  285 + overflow-x: auto;
  286 +}
  287 +
  288 +/* svg blocks */
  289 +.svg-block {
  290 + background: var(--bg-2);
  291 + border: 1px solid var(--border);
  292 + border-radius: var(--radius);
  293 + padding: 12px;
  294 + margin: 14px 0;
  295 + text-align: center;
  296 + overflow-x: auto;
  297 +}
  298 +.svg-block svg { max-width: 100%; height: auto; }
  299 +.caption {
  300 + font-size: 12px;
  301 + color: var(--fg-3);
  302 + text-align: center;
  303 + margin-top: 6px;
  304 +}
  305 +
  306 +/* prototype mock */
  307 +.mock {
  308 + background: var(--bg-2);
  309 + border: 1px solid var(--border-2);
  310 + border-radius: var(--radius);
  311 + box-shadow: var(--shadow);
  312 + overflow: hidden;
  313 + margin: 14px 0;
  314 +}
  315 +.mock-bar {
  316 + height: 28px;
  317 + background: var(--bg-3);
  318 + border-bottom: 1px solid var(--border);
  319 + display: flex;
  320 + align-items: center;
  321 + padding: 0 10px;
  322 + gap: 4px;
  323 + font-family: var(--mono);
  324 + font-size: 11px;
  325 + color: var(--fg-3);
  326 +}
  327 +.mock-bar::before {
  328 + content: '';
  329 + width: 10px; height: 10px;
  330 + border-radius: 50%;
  331 + background: #ff5f56;
  332 + box-shadow: 16px 0 #ffbd2e, 32px 0 #27c93f;
  333 + margin-right: 36px;
  334 +}
  335 +.mock-body { padding: 0; }
  336 +
  337 +/* responsive */
  338 +@media (max-width: 720px) {
  339 + .page { padding: 24px 18px 60px; }
  340 + h1 { font-size: 24px; }
  341 + h2 { font-size: 18px; }
  342 +}
... ...
docs/prd/assets/menu.js 0 → 100644
  1 +/* LemonBoss doc-site menu tree (3 levels) */
  2 +window.LEMONBOSS_MENU = [
  3 + {
  4 + id: '01', num: '01', title: '项目概览', href: '01-overview/index.html', children: [
  5 + { id: '1.1', num: '1.1', title: '项目定位与愿景', href: '01-overview/index.html#vision' },
  6 + { id: '1.2', num: '1.2', title: '用户画像', href: '01-overview/index.html#personas' },
  7 + { id: '1.3', num: '1.3', title: '技术栈全景', href: '01-overview/index.html#stack' },
  8 + { id: '1.4', num: '1.4', title: '名词术语表', href: '01-overview/index.html#glossary' },
  9 + ],
  10 + },
  11 + {
  12 + id: '02', num: '02', title: 'PRD 产品需求', href: '02-prd/index.html', children: [
  13 + { id: '2.1', num: '2.1', title: '核心场景', href: '02-prd/index.html#scenarios' },
  14 + { id: '2.2', num: '2.2', title: '用户故事', href: '02-prd/index.html#stories' },
  15 + {
  16 + id: '2.3', num: '2.3', title: '功能详述', href: '02-prd/index.html#features', children: [
  17 + { id: '2.3.1', num: '2.3.1', title: 'Company 管理', href: '02-prd/index.html#feat-company' },
  18 + { id: '2.3.2', num: '2.3.2', title: 'OKR 目标管理', href: '02-prd/index.html#feat-okr' },
  19 + { id: '2.3.3', num: '2.3.3', title: '招聘 / 模板', href: '02-prd/index.html#feat-hire' },
  20 + { id: '2.3.4', num: '2.3.4', title: 'Pipeline 流水线', href: '02-prd/index.html#feat-pipeline' },
  21 + { id: '2.3.5', num: '2.3.5', title: 'Task Timeline', href: '02-prd/index.html#feat-task' },
  22 + { id: '2.3.6', num: '2.3.6', title: 'Agent Chat', href: '02-prd/index.html#feat-chat' },
  23 + { id: '2.3.7', num: '2.3.7', title: 'Quota 监控', href: '02-prd/index.html#feat-quota' },
  24 + ],
  25 + },
  26 + { id: '2.4', num: '2.4', title: '非功能需求', href: '02-prd/index.html#nfr' },
  27 + { id: '2.5', num: '2.5', title: '路线图', href: '02-prd/index.html#roadmap' },
  28 + ],
  29 + },
  30 + {
  31 + id: '03', num: '03', title: '功能树', href: '03-feature-tree/index.html', children: [
  32 + { id: '3.1', num: '3.1', title: '全局功能树', href: '03-feature-tree/index.html#tree' },
  33 + { id: '3.2', num: '3.2', title: '模块拆解', href: '03-feature-tree/index.html#modules' },
  34 + { id: '3.3', num: '3.3', title: '优先级矩阵', href: '03-feature-tree/index.html#priority' },
  35 + ],
  36 + },
  37 + {
  38 + id: '04', num: '04', title: '业务流程', href: '04-workflows/index.html', children: [
  39 + { id: '4.1', num: '4.1', title: '创建公司流', href: '04-workflows/index.html#wf-create-company' },
  40 + { id: '4.2', num: '4.2', title: 'OKR 拆解流', href: '04-workflows/index.html#wf-okr' },
  41 + { id: '4.3', num: '4.3', title: '招聘流', href: '04-workflows/index.html#wf-hire' },
  42 + { id: '4.4', num: '4.4', title: 'Pipeline 执行流', href: '04-workflows/index.html#wf-pipeline' },
  43 + { id: '4.5', num: '4.5', title: 'Agent 协作调用链', href: '04-workflows/index.html#wf-agent' },
  44 + { id: '4.6', num: '4.6', title: 'Auto-dev CLI 流', href: '04-workflows/index.html#wf-autodev' },
  45 + { id: '4.7', num: '4.7', title: '鉴权流', href: '04-workflows/index.html#wf-auth' },
  46 + ],
  47 + },
  48 + {
  49 + id: '05', num: '05', title: '原型设计', href: '05-prototype/index.html', children: [
  50 + { id: '5.1', num: '5.1', title: '设计规范', href: '05-prototype/index.html#design-system' },
  51 + { id: '5.2', num: '5.2', title: 'AgentChat', href: '05-prototype/agent-chat.html' },
  52 + { id: '5.3', num: '5.3', title: 'AgentList', href: '05-prototype/agent-list.html' },
  53 + { id: '5.4', num: '5.4', title: 'CompanyDashboard', href: '05-prototype/company-dashboard.html' },
  54 + { id: '5.5', num: '5.5', title: 'JobsPanel', href: '05-prototype/jobs-panel.html' },
  55 + { id: '5.6', num: '5.6', title: 'OkrDashboard', href: '05-prototype/okr-dashboard.html' },
  56 + { id: '5.7', num: '5.7', title: 'PipelineEditor', href: '05-prototype/pipeline-editor.html' },
  57 + { id: '5.8', num: '5.8', title: 'QuotaMonitor', href: '05-prototype/quota-monitor.html' },
  58 + { id: '5.9', num: '5.9', title: 'TaskTimeline', href: '05-prototype/task-timeline.html' },
  59 + { id: '5.10', num: '5.10', title: 'TemplateAgents', href: '05-prototype/template-agents.html' },
  60 + ],
  61 + },
  62 + {
  63 + id: '06', num: '06', title: 'SDD · Model 数据模型', href: '06-sdd-model/index.html', children: [
  64 + { id: '6.1', num: '6.1', title: 'ER 图', href: '06-sdd-model/index.html#er' },
  65 + { id: '6.2', num: '6.2', title: 'SQLite Schema', href: '06-sdd-model/index.html#sqlite' },
  66 + { id: '6.3', num: '6.3', title: '文件系统数据', href: '06-sdd-model/index.html#fs' },
  67 + { id: '6.4', num: '6.4', title: '内存数据结构', href: '06-sdd-model/index.html#memory' },
  68 + ],
  69 + },
  70 + {
  71 + id: '07', num: '07', title: 'SDD · View 视图层', href: '07-sdd-view/index.html', children: [
  72 + { id: '7.1', num: '7.1', title: 'Vue 路由表', href: '07-sdd-view/index.html#routes' },
  73 + { id: '7.2', num: '7.2', title: '组件清单', href: '07-sdd-view/index.html#components' },
  74 + { id: '7.3', num: '7.3', title: '状态管理', href: '07-sdd-view/index.html#state' },
  75 + { id: '7.4', num: '7.4', title: '主题与样式', href: '07-sdd-view/index.html#theme' },
  76 + ],
  77 + },
  78 + {
  79 + id: '08', num: '08', title: 'SDD · Control API 协议', href: '08-sdd-control/index.html', children: [
  80 + { id: '8.1', num: '8.1', title: 'API 端点总表', href: '08-sdd-control/index.html#endpoints' },
  81 + { id: '8.2', num: '8.2', title: '中间件链', href: '08-sdd-control/index.html#middleware' },
  82 + { id: '8.3', num: '8.3', title: 'SSE 协议', href: '08-sdd-control/index.html#sse' },
  83 + { id: '8.4', num: '8.4', title: '鉴权与权限矩阵', href: '08-sdd-control/index.html#auth' },
  84 + ],
  85 + },
  86 + {
  87 + id: '09', num: '09', title: 'SDD · Process 进程层', href: '09-sdd-process/index.html', children: [
  88 + { id: '9.1', num: '9.1', title: '入口与启动', href: '09-sdd-process/index.html#bootstrap' },
  89 + { id: '9.2', num: '9.2', title: 'Engine 进程层', href: '09-sdd-process/index.html#engine' },
  90 + { id: '9.3', num: '9.3', title: 'Agent 协作引擎', href: '09-sdd-process/index.html#agent' },
  91 + { id: '9.4', num: '9.4', title: '心跳 / 调度', href: '09-sdd-process/index.html#heartbeat' },
  92 + { id: '9.5', num: '9.5', title: '部署运维', href: '09-sdd-process/index.html#deploy' },
  93 + ],
  94 + },
  95 + {
  96 + id: '10', num: '10', title: '复现指南', href: '10-reproducibility/index.html', children: [
  97 + { id: '10.1', num: '10.1', title: '从零搭建', href: '10-reproducibility/index.html#setup' },
  98 + { id: '10.2', num: '10.2', title: '文件清单', href: '10-reproducibility/index.html#files' },
  99 + { id: '10.3', num: '10.3', title: '验收 Checklist', href: '10-reproducibility/index.html#checklist' },
  100 + ],
  101 + },
  102 +];
... ...
docs/prd/assets/search-index.json 0 → 100644
  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]]}}
0 2 \ No newline at end of file
... ...
docs/prd/chapters/01-overview/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>01 · 项目概览 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 1 章<span>›</span>项目概览</div>
  11 + <h1><span class="h-num">01</span>项目概览</h1>
  12 + <p class="lede">LemonBoss 是一个 <strong>多智能体自主开发系统</strong> —— 用户像"开公司"一样组织 AI Agent:创建公司、招聘成员、设定 OKR、编排 Pipeline、追踪任务时间线,最终让 Agent 自主把目标推进到落地。</p>
  13 +
  14 + <div class="callout callout-info">
  15 + <strong>覆盖范围。</strong> 本文档站只描述 LemonBoss 子系统(<code>server/agent-server.js</code> 入口及其挂载的
  16 + <code>/api/agent</code> <code>/api/company</code> <code>/api/goals</code> <code>/api/tasks</code> 路由族 + 9 个前端页面)。
  17 + 历史品牌 ArenaSK 的 Skill 市场 / Arena ELO / Benchmark / Master Agent 不在本文档范围。
  18 + </div>
  19 +
  20 + <h2 id="vision"><span class="h-num">1.1</span>项目定位与愿景</h2>
  21 + <p>占位中。完整内容将在下一轮章节生成时由 subagent 并行写入。</p>
  22 +
  23 + <h2 id="personas"><span class="h-num">1.2</span>用户画像</h2>
  24 + <p>占位中。</p>
  25 +
  26 + <h2 id="stack"><span class="h-num">1.3</span>技术栈全景</h2>
  27 + <p>占位中。</p>
  28 +
  29 + <h2 id="glossary"><span class="h-num">1.4</span>名词术语表</h2>
  30 + <p>占位中。</p>
  31 +</div>
  32 +</body>
  33 +</html>
... ...
docs/prd/chapters/02-prd/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>02 · PRD 产品需求 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 02 章<span>›</span>PRD 产品需求</div>
  11 + <h1><span class="h-num">02</span>PRD 产品需求</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/chapters/03-feature-tree/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>03 · 功能树 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 03 章<span>›</span>功能树</div>
  11 + <h1><span class="h-num">03</span>功能树</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/chapters/04-workflows/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>04 · 业务流程 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 04 章<span>›</span>业务流程</div>
  11 + <h1><span class="h-num">04</span>业务流程</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/chapters/05-prototype/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>05 · 原型设计 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 05 章<span>›</span>原型设计</div>
  11 + <h1><span class="h-num">05</span>原型设计</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/chapters/06-sdd-model/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>06 · SDD · Model 数据模型 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 06 章<span>›</span>SDD · Model 数据模型</div>
  11 + <h1><span class="h-num">06</span>SDD · Model 数据模型</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/chapters/07-sdd-view/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>07 · SDD · View 视图层 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 07 章<span>›</span>SDD · View 视图层</div>
  11 + <h1><span class="h-num">07</span>SDD · View 视图层</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/chapters/08-sdd-control/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>08 · SDD · Control API 协议 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 08 章<span>›</span>SDD · Control API 协议</div>
  11 + <h1><span class="h-num">08</span>SDD · Control API 协议</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/chapters/09-sdd-process/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>09 · SDD · Process 进程层 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 09 章<span>›</span>SDD · Process 进程层</div>
  11 + <h1><span class="h-num">09</span>SDD · Process 进程层</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/chapters/10-reproducibility/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <title>10 · 复现指南 — LemonBoss</title>
  6 + <link rel="stylesheet" href="../../assets/chapter.css" />
  7 +</head>
  8 +<body>
  9 +<div class="page">
  10 + <div class="crumbs">LemonBoss<span>›</span>第 10 章<span>›</span>复现指南</div>
  11 + <h1><span class="h-num">10</span>复现指南</h1>
  12 + <p class="lede">本章占位中 — 完整内容将在下一阶段生成。</p>
  13 + <div class="callout callout-info">扫描清单见 <code>docs/prd/_scan-manifest.json</code>。</div>
  14 +</div>
  15 +</body>
  16 +</html>
... ...
docs/prd/index.html 0 → 100644
  1 +<!DOCTYPE html>
  2 +<html lang="zh-CN" data-theme="light">
  3 +<head>
  4 + <meta charset="UTF-8" />
  5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6 + <title>LemonBoss — PRD + SDD</title>
  7 + <link rel="stylesheet" href="./assets/app.css" />
  8 +</head>
  9 +<body>
  10 + <header class="topbar">
  11 + <button id="burger" class="burger" aria-label="Toggle menu">
  12 + <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
  13 + </button>
  14 + <div class="brand">
  15 + <span class="lemon">🍋</span>
  16 + <span class="title">LemonBoss</span>
  17 + <span class="subtitle">PRD + SDD · 多智能体自主开发系统</span>
  18 + </div>
  19 + <div class="search-wrap">
  20 + <input id="search" type="search" placeholder="搜索章节、API、组件… (按 / 聚焦)" autocomplete="off" />
  21 + <kbd class="kbd">/</kbd>
  22 + </div>
  23 + <button id="theme" class="theme-toggle" aria-label="Toggle theme">
  24 + <svg class="ico-sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
  25 + <svg class="ico-moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
  26 + </button>
  27 + </header>
  28 +
  29 + <div class="layout">
  30 + <aside class="sidebar" id="sidebar">
  31 + <nav class="menu" id="menu" aria-label="Chapters"></nav>
  32 + </aside>
  33 +
  34 + <main class="content">
  35 + <div class="tabs">
  36 + <button class="tab active" data-pane="reader">📖 阅读</button>
  37 + <button class="tab" data-pane="search" id="tab-search">🔎 搜索结果 <span class="badge" id="search-count">0</span></button>
  38 + </div>
  39 + <div class="pane pane-reader active">
  40 + <iframe id="reader" name="reader" src="./chapters/01-overview/index.html" title="Chapter reader"></iframe>
  41 + </div>
  42 + <div class="pane pane-search">
  43 + <div class="search-header">
  44 + <div id="search-summary">输入关键词开始搜索</div>
  45 + <button class="clear-btn" id="clear-search">清空</button>
  46 + </div>
  47 + <ul class="results" id="results"></ul>
  48 + </div>
  49 + </main>
  50 + </div>
  51 +
  52 + <script src="./assets/app.js" defer></script>
  53 +</body>
  54 +</html>
... ...
docs/prd/scripts/build-search-index.js 0 → 100644
  1 +#!/usr/bin/env node
  2 +/**
  3 + * Build search-index.json from chapter HTML files.
  4 + *
  5 + * docs/prd/chapters/{ch}/**.html → assets/search-index.json
  6 + *
  7 + * Tokenization rules (must match runtime in app.js):
  8 + * - lowercase
  9 + * - ASCII words/digits: [a-z0-9_]+
  10 + * - CJK bigrams: [一-鿿]+ → for each run, push every 2-char window
  11 + *
  12 + * Output:
  13 + * {
  14 + * docs: [ { id, title, href, crumbs:[…], text } ],
  15 + * invIndex: { token: [ [docId, tf], … ] }
  16 + * }
  17 + */
  18 +
  19 +const fs = require('fs');
  20 +const path = require('path');
  21 +
  22 +const ROOT = path.resolve(__dirname, '..');
  23 +const CHAPT_DIR = path.join(ROOT, 'chapters');
  24 +const OUT = path.join(ROOT, 'assets', 'search-index.json');
  25 +
  26 +function walk(dir) {
  27 + const out = [];
  28 + for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
  29 + const p = path.join(dir, e.name);
  30 + if (e.isDirectory()) out.push(...walk(p));
  31 + else if (e.isFile() && e.name.endsWith('.html')) out.push(p);
  32 + }
  33 + return out;
  34 +}
  35 +
  36 +function stripTags(html) {
  37 + return html
  38 + .replace(/<script[\s\S]*?<\/script>/gi, ' ')
  39 + .replace(/<style[\s\S]*?<\/style>/gi, ' ')
  40 + .replace(/<!--[\s\S]*?-->/g, ' ')
  41 + .replace(/<[^>]+>/g, ' ')
  42 + .replace(/&nbsp;/g, ' ')
  43 + .replace(/&amp;/g, '&')
  44 + .replace(/&lt;/g, '<')
  45 + .replace(/&gt;/g, '>')
  46 + .replace(/&quot;/g, '"')
  47 + .replace(/&#39;/g, "'")
  48 + .replace(/\s+/g, ' ')
  49 + .trim();
  50 +}
  51 +
  52 +function pick(html, re) {
  53 + const m = re.exec(html);
  54 + return m ? stripTags(m[1]) : null;
  55 +}
  56 +
  57 +function extractTitle(html) {
  58 + return pick(html, /<h1[^>]*>([\s\S]*?)<\/h1>/i) || pick(html, /<title[^>]*>([\s\S]*?)<\/title>/i) || '(无标题)';
  59 +}
  60 +
  61 +function extractCrumbs(html) {
  62 + const m = /<div class="crumbs"[^>]*>([\s\S]*?)<\/div>/i.exec(html);
  63 + if (!m) return [];
  64 + return m[1].replace(/<span[^>]*>›<\/span>/g, '|').split('|').map(s => stripTags(s)).filter(Boolean);
  65 +}
  66 +
  67 +function tokenize(text) {
  68 + const tokens = [];
  69 + const lower = text.toLowerCase();
  70 + (lower.match(/[a-z0-9_]+/g) || []).forEach(w => tokens.push(w));
  71 + const cjk = lower.match(/[㐀-鿿]+/g) || [];
  72 + cjk.forEach(run => {
  73 + if (run.length === 1) tokens.push(run);
  74 + else for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2));
  75 + });
  76 + return tokens;
  77 +}
  78 +
  79 +function build() {
  80 + const files = walk(CHAPT_DIR);
  81 + const docs = [];
  82 + const inv = {}; // token → Map<docId, tf>
  83 +
  84 + for (const file of files) {
  85 + const html = fs.readFileSync(file, 'utf-8');
  86 + const title = extractTitle(html);
  87 + const crumbs = extractCrumbs(html);
  88 + const text = stripTags(html);
  89 + const href = path.relative(CHAPT_DIR, file);
  90 + const id = docs.length;
  91 + docs.push({ id, title, href, crumbs, text: text.slice(0, 2000) });
  92 +
  93 + const tokens = tokenize(text);
  94 + const tf = {};
  95 + for (const t of tokens) tf[t] = (tf[t] || 0) + 1;
  96 + for (const [t, c] of Object.entries(tf)) {
  97 + (inv[t] = inv[t] || []).push([id, c]);
  98 + }
  99 + }
  100 +
  101 + const out = { docs, invIndex: inv };
  102 + fs.mkdirSync(path.dirname(OUT), { recursive: true });
  103 + fs.writeFileSync(OUT, JSON.stringify(out));
  104 + const kb = (fs.statSync(OUT).size / 1024).toFixed(1);
  105 + console.log(`✅ search index: ${docs.length} docs, ${Object.keys(inv).length} tokens, ${kb} KB → ${path.relative(ROOT, OUT)}`);
  106 +}
  107 +
  108 +build();
... ...