Commit fd86ebf1cc61c45a0777e5103ae2993c46a278b8
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>
Showing
18 changed files
with
1996 additions
and
0 deletions
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 => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[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(/ /g, ' ') | |
| 43 | + .replace(/&/g, '&') | |
| 44 | + .replace(/</g, '<') | |
| 45 | + .replace(/>/g, '>') | |
| 46 | + .replace(/"/g, '"') | |
| 47 | + .replace(/'/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(); | ... | ... |