@genn-inc/cluebase-cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +101 -0
  2. package/bin/cluebase-cli.mjs +11 -0
  3. package/package.json +17 -0
  4. package/src/cli-command.mjs +515 -0
  5. package/src/cli-invocation.mjs +17 -0
  6. package/src/code-evidence-analyzer.mjs +2041 -0
  7. package/src/contracts.mjs +36 -0
  8. package/src/generated-code-evidence-contract.mjs +22 -0
  9. package/src/generated-sdk-version-contract.mjs +5 -0
  10. package/src/generated-source-path-policy.mjs +20 -0
  11. package/src/lifecycle-guard.mjs +202 -0
  12. package/src/path-policy.mjs +81 -0
  13. package/src/setup-ai-contract.mjs +221 -0
  14. package/src/setup-check-constants.mjs +110 -0
  15. package/src/setup-check-scan-a.mjs +849 -0
  16. package/src/setup-check-scan-b.mjs +994 -0
  17. package/src/setup-check.mjs +575 -0
  18. package/src/setup-discover-check.mjs +755 -0
  19. package/src/setup-doctor-deadline.mjs +221 -0
  20. package/src/setup-doctor-env.mjs +331 -0
  21. package/src/setup-doctor-file-boundary.mjs +426 -0
  22. package/src/setup-doctor-probe.mjs +719 -0
  23. package/src/setup-doctor-quality-checks-a.mjs +593 -0
  24. package/src/setup-doctor-quality-checks-b.mjs +638 -0
  25. package/src/setup-doctor-quality-shared.mjs +382 -0
  26. package/src/setup-doctor-quality.mjs +209 -0
  27. package/src/setup-doctor-route-scan.mjs +160 -0
  28. package/src/setup-doctor-sdk-probe.mjs +340 -0
  29. package/src/setup-doctor.mjs +545 -0
  30. package/src/setup-documents.mjs +112 -0
  31. package/src/setup-help.mjs +130 -0
  32. package/src/setup-prepare.mjs +360 -0
  33. package/src/setup-repository-discovery.mjs +764 -0
  34. package/src/setup-step-builders-discover.mjs +701 -0
  35. package/src/setup-step-builders-events.mjs +229 -0
  36. package/src/setup-step-builders-implement.mjs +710 -0
  37. package/src/setup-step-commands.mjs +427 -0
  38. package/src/setup-tool.mjs +27 -0
@@ -0,0 +1,710 @@
1
+ import { CLUEBASE_CLI_RECOMMENDED_PREFIX as CLUEBASE_CLI_INVOCATION } from "./cli-invocation.mjs";
2
+ import {
3
+ RECOMMENDED_BACKEND_SDK_PACKAGE_SPEC,
4
+ RECOMMENDED_PYTHON_BACKEND_SDK_PACKAGE_SPEC,
5
+ } from "./generated-sdk-version-contract.mjs";
6
+ // STEP 5-8 implement builders (SDK insertion + env writes, static check,
7
+ // self-review loop, production-level verification). Extracted from
8
+ // setup-step-commands for file-size limits; content is unchanged. Framing
9
+ // lives in setup-step-commands.mjs.
10
+
11
+ export function buildStep5Implement({ documentsUrl }) {
12
+ return `You are running inside an AI coding tool. This is STEP 5 of the Cluebase setup flow (Implement + Self-correct).
13
+
14
+ Goal:
15
+ Apply Cluebase lifecycle calls in this repository according to \`.cluebase/discoveries.json\` (now enriched by STEP 3 with per-site \`available_fields\` + a top-level \`organization_context\`), then self-review your own diff against the rubric below and fix any issue before stopping. The user should see a clean, drift-free, P0/P1-free result on first try.
16
+
17
+ Cluebase SDK call signatures (CURRENT API — do not deviate):
18
+ \`\`\`typescript
19
+ // Frontend (@genn-inc/cluebase-frontend-sdk)
20
+ cluebase.identify(userId: string, traits?: CluebaseSubjectTraits): void
21
+ cluebase.group(groupType: "organization", groupKey: string, traits?: CluebaseGroupTraits): void
22
+ cluebase.reset(): void
23
+ // where CluebaseSubjectTraits is { name?, email?, emailAddress?, displayName?, avatarUrl?, [k: string]: unknown }
24
+ // where CluebaseGroupTraits is { [k: string]: unknown }
25
+ \`\`\`
26
+ \`\`\`python
27
+ # Backend (cluebase-backend-sdk via cluebase-backend-sdk)
28
+ cluebase.identify(user_id: str, traits: Mapping[str, object] | None = None) -> None
29
+ cluebase.group(group_type: str, group_key: str, traits: Mapping[str, object] | None = None) -> None
30
+ cluebase.reset() -> None
31
+ # identify traits accept name, email, emailAddress, displayName, avatarUrl, plus arbitrary customer keys
32
+ # group traits accept arbitrary customer keys
33
+ # Note: trait keys are **camelCase** even on the Python SDK (avatarUrl),
34
+ # not snake_case, so customers can copy idiomatic examples between SDKs
35
+ # without renaming fields.
36
+ \`\`\`
37
+
38
+ Steps:
39
+ 1. Use the Read tool to load \`.cluebase/discoveries.json\`. Reject and STOP with a blocker if the file is missing, not valid JSON, or missing any of the required fields (\`framework_frontend\`, \`service_key\`, \`cluebase_init_frontend\`, \`cluebase_init_backend\`, \`identify_sites\`, \`group_sites\`, \`reset_sites\`, \`organization_context\`). Each entry in \`identify_sites\` / \`group_sites\` must already carry an \`available_fields\` object (populated by STEP 3) — if any entry is missing it, STOP and ask the user to re-run \`/cluebase-discover-context\` first.
40
+ 1.5. Group-owner preflight. Before editing source files, inspect \`group_sites\` by runtime surface (frontend vs backend) and \`group_owner_kind\`. If the same surface contains an \`active_context_owner\` and one or more create / join / switch \`context_change\` entries, STOP and ask the user to re-run \`/cluebase-discover-review\` because inserting both would duplicate \`organization_associated\`. Do not "fix" this by adding SDK-side dedupe or by suppressing events.
41
+ 2. For each lifecycle entry (cluebase_init_frontend, cluebase_init_backend, every identify_sites / group_sites / reset_sites entry):
42
+ a. Use Read to open the target file.
43
+ b. Use the Edit tool with surgical \`old_string\` / \`new_string\` to insert the lifecycle call. **Do NOT use the Write tool on existing files** — Write would regenerate the file body and almost always changes indent style, brace placement, trailing commas, or import order on lines you did not intend to touch. Use Write only for genuinely new files (\`creates_new_file: true\`).
44
+ c. Build the call arguments by using \`available_fields.*\` paths VERBATIM (the paths recorded by STEP 3 are guaranteed to be in scope at this line). Compose the traits object as the third argument for \`cluebase.group("organization", ...)\`. Null fields (name, email, avatarUrl) are omitted from the traits dict. NEVER modify the surrounding code to add new variables, new fetches, or new API fields to populate Cluebase args — STEP 3 already verified what is reachable WITHOUT touching the customer's APIs.
45
+ d. If the file is a frontend module that imports from \`@genn-inc/cluebase-frontend-sdk\` AND the host framework requires a client directive (Next.js App Router: \`"use client";\` at the top of any client-only module), ensure that directive is present. If absent, use Edit to prepend it (\`"use client";\\n\\n\`).
46
+ e. When an \`identify_sites\` entry and a \`group_sites\` entry point at the same login / sign-up / auth callback / token-exchange success branch, insert both calls in that same success block in this order: \`cluebase.identify(...)\` first, then \`cluebase.group("organization", ...)\` after the active organization id/name variables are known. Do not treat group as switch-only; the initial active organization context must be recorded as soon as it is established.
47
+ f. When a \`group_sites\` entry has \`group_owner_kind: "active_context_owner"\`, insert \`cluebase.group("organization", ...)\` only inside a guard that has authenticated user id + active organization id + active organization name and skips duplicate \`userId:organizationId\` values. This owner is the single frontend organization association owner; do not also insert \`cluebase.group\` into create / join / switch handlers on that surface.
48
+ g. For \`identify_sites\`, re-confirm the site is the real one-time auth success boundary. Do NOT add \`cluebase.identify\` to token refresh, current-user or \`/me\` sync, session polling, request exchange, read hook, query function, or repeated helper paths unless the surrounding code proves that path is the actual one-time auth success boundary. If it only refreshes or hydrates an already-authenticated session, skip it and report the boundary gap.
49
+ 2.5. For each \`ai_provider_sites\` entry: open the file at \`file:line\` with Read, then use Edit to insert the OTel-primary AI helper wire-up DIRECTLY AFTER the LLM client constructor on that line (see "AI provider OpenTelemetry / OpenLLMetry / GenAI semantic instrumentation" pattern below). Pick \`instrument_openai\` / \`instrument_anthropic\` based on the \`kind\` recorded for that site. The helper must install OpenTelemetry/OpenLLMetry GenAI semantic instrumentation as the primary signal where available; Cluebase behavior is enrichment/fallback only. Wrap the observer block in \`try / except Exception: pass\` (Python) or \`try { } catch { }\` (Node). If \`ai_provider_sites\` is empty, skip this step.
50
+ 2.6. For each \`mcp_server_sites\` entry: open the file at \`file:line\` with Read, then use Edit to insert the OTel-primary MCP helper wire-up DIRECTLY AFTER the \`Server(...)\` constructor on that line (see "MCP server OpenTelemetry instrumentation" pattern below). Pass the SAME variable name recorded in \`server_variable_name\` to \`anthropic_instrument\` / \`anthropicInstrument\`. The helper must emit/forward OTel MCP or GenAI semantic spans as the primary signal where available; Cluebase records are enrichment/fallback only. Wrap the observer block in \`try / except Exception: pass\` (Python) or \`try { } catch { }\` (Node). If \`mcp_server_sites\` is empty, skip this step.
51
+ 2.7. For each \`langchain_sites\` entry: open the file at \`file:line\` with Read, then use Edit to insert the LangChain OTel-primary wire-up (see "LangChain OpenTelemetry / GenAI semantic instrumentation" pattern below). For Python, insert \`instrument_langchain()\` immediately AFTER the \`from langchain ...\` / \`from langchain_core ...\` import block (top of module is sufficient because the LangChain global callback manager is process-wide). For Node, insert \`instrumentLangChain(observer)\` immediately AFTER the chain / agent / LLM constructor on the recorded line. Wrap the block in \`try / except Exception: pass\` (Python) or \`try { } catch { }\` (Node). When the SAME module already had a Python \`instrument_langchain()\` insertion from a prior \`langchain_sites\` entry, DO NOT duplicate — one call per module is enough. If \`langchain_sites\` is empty, skip this step.
52
+ 2.8. For each \`llamaindex_sites\` entry: open the file at \`file:line\` with Read, then use Edit to insert OTel-primary \`instrument_llamaindex()\` DIRECTLY AFTER the \`from llama_index ...\` / \`from llama_index.core ...\` import block (one call per module is enough — LlamaIndex's root dispatcher is process-wide). Wrap the block in \`try / except Exception: pass\`. If \`llamaindex_sites\` is empty, skip this step.
53
+ 2.9. For each \`crewai_sites\` entry: open the file at \`file:line\` with Read, then use Edit to insert OTel-primary \`instrument_crewai(<crew_variable_name>)\` DIRECTLY AFTER the \`Crew(...)\` constructor on that line, passing the EXACT variable name recorded in \`crew_variable_name\`. Wrap the block in \`try / except Exception: pass\`. If \`crewai_sites\` is empty, skip this step.
54
+ 2.10. For each \`mastra_sites\` entry: open the file at \`file:line\` with Read, then use Edit to insert OTel-primary \`instrumentMastra(observer, <mastra_variable_name>)\` DIRECTLY AFTER the \`new Mastra(...)\` constructor on that line, passing the EXACT variable name recorded in \`mastra_variable_name\`. Wrap the block in \`try { } catch { }\`. If \`mastra_sites\` is empty, skip this step.
55
+ 3. Add SDK dependencies:
56
+ - Frontend: \`@genn-inc/cluebase-frontend-sdk@latest\` in the existing package manifest.
57
+ - FastAPI / Django (Python backend): add \`${RECOMMENDED_PYTHON_BACKEND_SDK_PACKAGE_SPEC}\` in requirements.txt / pyproject.toml, WITH the instrumentation extras for the frameworks and clients this backend actually uses. \`cluebase_init_fastapi\` / \`install_django\` activate the standard OpenTelemetry instrumentors, but each one silently degrades to a no-op when its extra is absent — the backend then records nothing and reports no error. Pick from \`instrumentation-fastapi\`, \`instrumentation-django\`, \`instrumentation-sqlalchemy\`, \`instrumentation-requests\`, \`instrumentation-httpx\`, \`instrumentation-celery\` based on what \`.cluebase/discoveries.json\` and the dependency manifest show, and always include the web framework extra plus \`instrumentation-sqlalchemy\` when an ORM is present.
58
+ - Express / NestJS / Fastify / Hono (Node backend): add \`${RECOMMENDED_BACKEND_SDK_PACKAGE_SPEC}\` to package.json. The same package serves all Node backend integrations (Express middleware / LangChain / Mastra / AI observer / MCP observer); do NOT install a separate package per framework.
59
+ 4. Run the matching install command via the Bash tool: \`bun.lock\` -> \`bun install\`, \`pnpm-lock.yaml\` -> \`pnpm install\`, \`yarn.lock\` -> \`yarn install\`, \`package-lock.json\` -> \`npm install\`. For Python: \`pip install -r requirements.txt\` (or the uv equivalent if uv is present). A package manifest / lockfile mismatch causes STEP 6's setup-check to fail. If install fails or no manager is detected, STOP and report a blocker.
60
+
61
+ 4.5. ENV FILE AUTO-WRITE (STEP 5 owns env file setup so STEP 9 can pass without manual user copy/paste from the setup screen).
62
+
63
+ Goal: write the required CLUEBASE_* env vars into the customer's frontend and backend env files based on values in \`.cluebase/setup-manifest.json\` + \`.cluebase/secrets.json\`.
64
+
65
+ 4.5.a Read inputs (skip this whole step and continue to step 5 if any required input is missing — tell the user which one):
66
+ - Use Read tool on \`.cluebase/setup-manifest.json\`. Extract \`cluebase_context.project_key\`, \`cluebase_context.cluebase_api_base_url\`, \`cluebase_context.ingest_endpoints.browser\`, \`cluebase_context.ingest_endpoints.backend\`, plus \`detected_services[]\` root paths.
67
+ - Use Read tool on \`.cluebase/secrets.json\`. Extract \`cluebase_api_key\`. If the file does not exist, skip the CLUEBASE_API_KEY auto-write and tell the user: "CLUEBASE_API_KEY は .cluebase/secrets.json 不在のため自動書込できません。 setup CLI を --cluebase-api-key 付きで再実行するか、 backend env file に手動で追加してください。"
68
+
69
+ 4.5.b Determine env file paths:
70
+ - frontend service root: probe in order \`<root_path>/.env.local\`, \`<root_path>/.env.development\`, \`<root_path>/.env\`. Pick the first that exists. If none exists, default to \`<root_path>/.env.local\` and create it (treat as new file).
71
+ - backend service root: probe \`<root_path>/.env\`, \`<root_path>/.env.development\`. Default to \`<root_path>/.env\` for new files.
72
+ - All paths must resolve inside the repo root (no absolute paths leading outside).
73
+
74
+ 4.5.c Determine the frontend env-var prefix from \`discoveries.framework_frontend\`:
75
+ - \`nextjs\` → \`NEXT_PUBLIC_\`
76
+ - \`vite\` or React/Vite stack → \`VITE_\`
77
+ - \`react\` → \`REACT_APP_\`
78
+ - \`sveltekit\` → \`PUBLIC_\`
79
+ - \`nuxt\` → \`NUXT_PUBLIC_\` (verify against \`nuxt.config\` if uncertain)
80
+ - \`angular\` → skip frontend env auto-write and instruct the user to copy the setup screen's Angular runtime config values.
81
+ - \`solid\` / \`qwik\` / \`astro\` / unknown → ask the user once in a single Japanese line which public config shape the framework uses, or skip auto-write and instruct manual setup.
82
+
83
+ 4.5.d Build the env line set:
84
+ Frontend (2 lines, every line uses the prefix from 4.5.c — collectively \`<PREFIX>\`; the frontend SDK calls Cluebase directly):
85
+ \`<PREFIX>CLUEBASE_API_BASE_URL=<cluebase_api_base_url>\`
86
+ \`<PREFIX>CLUEBASE_PROJECT_KEY=<project_key>\`
87
+
88
+ Backend (3 lines, NO prefix; these are server-only env names):
89
+ \`CLUEBASE_INGEST_ENDPOINT=<ingest_endpoints.backend>\`
90
+ \`CLUEBASE_PROJECT_KEY=<project_key>\`
91
+ \`CLUEBASE_API_KEY=<from .cluebase/secrets.json>\` ← skip this line if secrets.json was absent
92
+
93
+ Hard rule: CLUEBASE_API_KEY must NEVER be wired into browser-public frontend SDK configuration. Both are server-side values.
94
+
95
+ 4.5.e Upsert the lines (use Edit tool with surgical \`old_string\` / \`new_string\` on existing files; use Write tool only when creating a new env file):
96
+ For each NAME in the line set, scan the existing file for a line matching \`^<NAME>\\s*=\`:
97
+ * found AND value differs → replace the matched line with the new \`NAME=value\`.
98
+ * found AND value matches → leave it; do not duplicate.
99
+ * not found → append \`NAME=value\` to the end (add a leading newline if the file does not already end with one).
100
+ When creating a new env file: write only the joined env lines + trailing newline. Do not add narrative comments.
101
+
102
+ 4.5.f Verify post-write state via Read on each env file:
103
+ - Every required NAME appears exactly once with the expected value.
104
+ - The browser-public frontend SDK env lines MUST NOT contain \`CLUEBASE_API_KEY=\` or any unprefixed \`CLUEBASE_\` line. If such a line would be used by browser code, remove it from the frontend SDK wiring and re-verify.
105
+
106
+ 4.5.g Record a short internal summary of what was written (counts only — do NOT echo the secret values themselves) so STEP 7 can review the env state in the next pass.
107
+
108
+ 5. SELF-REVIEW PASS (mandatory; up to 3 iterations). Inspect your own changes via \`git diff HEAD\` (Bash tool) and apply the rubric below to your diff. If any P0/P1 issue is found, fix it surgically (Edit tool, or \`git checkout HEAD -- <file>\` + re-apply for reformat noise), then re-inspect. Stop when clean or after 3 iterations. Track the cumulative number of self-corrections.
109
+
110
+ Argument-construction rules (HARD — read carefully):
111
+ - For each cluebase.identify call, use \`available_fields\` as: \`cluebase.identify(<id-path>, { name: <name-path>, email: <email-path>, avatarUrl: <avatarUrl-path> })\` (JS/TS) or \`cluebase.identify(<id-path>, {"name": <name-path>, "email": <email-path>, "avatarUrl": <avatarUrl-path>})\` (Python — trait keys are camelCase even on Python). Each null field is OMITTED from the traits dict (not passed as \`null\`). \`available_fields.name\` MUST be a human-readable name selected by STEP 3. If it equals the id path, STOP and record a blocker; do not ship \`name = id\`. The SDK/API converts \`email\` into privacy-safe contact-derived fields before storage.
112
+ - For each cluebase.group call, use \`available_fields\` as: \`cluebase.group("organization", <organization_id-path>, { name: <name-path> })\` (JS/TS) or \`cluebase.group("organization", <organization_id-path>, {"name": <name-path>, ...})\` (Python — trait keys are camelCase even on Python). Null fields are simply omitted from the traits dict. MVP does not expose Cluebase account/workspace; if the customer app calls its company concept account/workspace/tenant/team, still map that stable company id to Cluebase \`organization\`.
113
+ - \`cluebase.group\` is the active organization context, not only a workspace-switch event. If the active organization is known at login/session establishment, insert it alongside \`cluebase.identify\` so the first post-login browser/backend events can be joined to the organization. If a guarded active-context owner exists, it owns subsequent create / join / switch changes too; do not also emit from the write handlers.
114
+ - For each cluebase.reset call, the signature takes no arguments at the setup-flow insertion point — call \`cluebase.reset()\` in JS/TS and \`cluebase.reset()\` in Python. Do NOT pass \`reason\` from this setup flow.
115
+ - Treat every \`available_fields.*\` value as an expression to PASTE VERBATIM (do not wrap in quotes, do not rename). Example: if STEP 3 recorded \`"data.user.id"\`, the generated call is \`cluebase.identify(data.user.id, ...)\` — not \`cluebase.identify("data.user.id", ...)\`.
116
+
117
+ Example call patterns (illustrative — the actual paths come from \`available_fields\`):
118
+ - Frontend identify with full data:
119
+ \`cluebase.identify(data.user.id, { name: data.user.name, email: data.user.email, avatarUrl: data.user.avatar_url });\`
120
+ - Frontend identify with no name in scope:
121
+ \`cluebase.identify(data.user.id, { email: data.user.email });\`
122
+ - Frontend organization association:
123
+ \`cluebase.group("organization", organization.id, { name: organization.name });\`
124
+ - Frontend login success with active organization:
125
+ \`cluebase.identify(data.user.id, { name: data.user.name, email: data.user.email });\`
126
+ \`cluebase.group("organization", data.activeWorkspace.id, { name: data.activeWorkspace.name });\`
127
+ - Backend identify with full data:
128
+ \`cluebase.identify(str(user.id), traits={"name": user.name, "email": user.email, "avatarUrl": user.avatar_url})\`
129
+ - Backend organization association:
130
+ \`cluebase.group("organization", str(organization.id), {"name": organization.name})\`
131
+ - Backend logout:
132
+ \`cluebase.reset()\`
133
+
134
+ Self-review rubric (apply to your own diff after step 4):
135
+ - "use client" directive present: every frontend file you edited that imports \`@genn-inc/cluebase-frontend-sdk\` MUST begin with \`"use client";\` (Next.js). Missing → prepend with Edit.
136
+ - No reformat noise: your diff lines outside the Cluebase insertions MUST match the original indent style, brace placement, trailing commas, and import order. If you accidentally reformatted unrelated lines (typical sign: a small Cluebase insertion is buried in a 100-line indent-style diff), run \`git checkout HEAD -- <file>\` via Bash to revert the file, then re-apply ONLY the Cluebase lines using surgical Edit calls.
137
+ - No await on lifecycle calls (must be fire-and-forget — the SDK is no-throw and non-blocking).
138
+ - No try/catch / try/except around lifecycle calls.
139
+ - Single cluebase.init per side (one frontend, one backend).
140
+ - No hardcoded ids passed to cluebase.identify / cluebase.group — always use the expression path recorded in \`available_fields\` (which references an existing variable in scope).
141
+ - Name fallback: \`name = id\` is forbidden. If \`available_fields.name\` equals \`available_fields.id\`, flag it as a STEP 3 defect and stop instead of inserting a lifecycle call.
142
+ - Identify boundary: \`cluebase.identify\` must be at the real one-time auth success boundary. Reject default insertion in token refresh, current-user or \`/me\` sync, session polling, request exchange, read hook, query function, or repeated helper paths unless the diff and surrounding code prove that path is the actual one-time auth success boundary.
143
+ - No new fetches, no new ORM joins, no new API parameters added solely to populate Cluebase args. If a field appears in the diff that was NOT in \`available_fields\`, revert it and re-apply with the recorded paths only.
144
+ - Browser env: only public-env names in browser code for endpoint / projectKey (Next.js \`NEXT_PUBLIC_CLUEBASE_*\`, Vite \`VITE_CLUEBASE_*\`, etc.); never wire \`CLUEBASE_API_KEY\` into frontend SDK options or browser bundles. The Cluebase environment (dev / prod) is derived from the projectKey prefix (pk_dev_ vs pk_prod_) by the SDK and server. Browser token issuance is built into the SDK from the Cluebase API base URL, projectKey, and request Origin.
145
+ - Backend env: non-crashing getter with if-guard for project_key / api_key; never \`os.environ["CLUEBASE_*"]\` required indexing. The Cluebase environment (dev / prod) is derived from the project_key prefix (pk_dev_ vs pk_prod_) by the SDK.
146
+ - SDK package names: \`@genn-inc/cluebase-frontend-sdk\` for frontend, \`cluebase-backend-sdk\` for FastAPI.
147
+ - Lifecycle insertion count matches discoveries: number of cluebase.identify insertions equals \`identify_sites.length\`, same for organization group and logout.
148
+ - Group ownership: if \`group_sites\` contains an \`active_context_owner\` on a surface, the diff MUST NOT also add \`cluebase.group\` in create / join / switch handlers on that same surface. Duplicate owner placement is a P0 issue because it creates duplicate \`organization_associated\` rows.
149
+ - The diff MUST NOT add a customer-backend route handler under \`/api/v1/cluebase/*\` or any path that proxies the Cluebase browser-token endpoint. Grep the diff for \`/api/v1/cluebase/\` and \`browser-tokens\` literals; if a POST handler in a customer-backend source file references either, that is a P0 violation. The frontend SDK calls the Cluebase backend directly.
150
+
151
+ Implementation patterns (Next.js):
152
+ - cluebase.init (singleton): create the client bootstrap module at \`cluebase_init_frontend.file\` if \`cluebase_init_frontend.creates_new_file\` is true (or the file does not yet exist). For App Router, the module starts with \`"use client"\`, calls \`cluebase.init\` at module level, exports a tiny \`CluebaseInit\` Client Component, and \`app/layout.tsx\` imports and renders \`<CluebaseInit />\` once inside \`<body>\` or the existing top-level provider tree. A side-effect-only import from a Server Component is not a valid boot point. For Pages Router, initialize from \`_app\` with the existing app bootstrap guard.
153
+ - When the customer frontend calls a separate customer backend origin and the app already has that origin in code/config, pass that existing value through \`network.tracePropagationOrigins\`. Do not add a host-app API env solely for Cluebase setup. The SDK normalizes full URLs such as \`https://api.example.com/api/v1\` to the origin before header propagation.
154
+ - Example:
155
+ "use client";
156
+ import cluebase from "@genn-inc/cluebase-frontend-sdk";
157
+ cluebase.init({
158
+ // \`endpoint\` is the Cluebase API **base URL** (= origin only). The SDK appends \`/api/v1/ingest/...\` paths
159
+ // internally. Do NOT pass a full path here — that breaks token issue +
160
+ // event POST URLs into double-prefix garbage. Use the
161
+ // \`NEXT_PUBLIC_CLUEBASE_API_BASE_URL\` env var.
162
+ endpoint: process.env.NEXT_PUBLIC_CLUEBASE_API_BASE_URL!,
163
+ // The SDK derives environment (dev / prod) from the projectKey prefix
164
+ // (pk_dev_ / pk_prod_), so there is no environment argument.
165
+ projectKey: process.env.NEXT_PUBLIC_CLUEBASE_PROJECT_KEY!,
166
+ // When this app calls a SEPARATE customer backend origin, trace-link
167
+ // browser->backend requests by listing that origin here. Substitute the
168
+ // app's EXISTING backend origin config value (do NOT add a Cluebase-only env);
169
+ // omit this line entirely for frontend-only apps. Leaving it out when a
170
+ // backend exists is a STEP 6 finding, so include it up front:
171
+ network: { tracePropagationOrigins: [/* existing backend origin config, e.g. */ process.env.NEXT_PUBLIC_API_URL!] },
172
+ // Browser token issuance is handled by the SDK from the Cluebase API base URL.
173
+ });
174
+ export function CluebaseInit() {
175
+ return null;
176
+ }
177
+ export default cluebase;
178
+ In \`app/layout.tsx\`:
179
+ import { CluebaseInit } from "@/lib/cluebase";
180
+ ...
181
+ <body>
182
+ <CluebaseInit />
183
+ {children}
184
+ </body>
185
+ - cluebase.identify (one per identify_sites entry): \`import cluebase from "@genn-inc/cluebase-frontend-sdk";\` and call \`cluebase.identify(<id-path>, { name: <name-path>, email: <email-path>, avatarUrl: <avatarUrl-path> })\` at the precise file:line. Paths come VERBATIM from the site's \`available_fields\`. Omit keys whose path is null. The SDK/API converts \`email\` into privacy-safe contact-derived fields before storage.
186
+ - Full data example: \`cluebase.identify(data.user.id, { name: data.user.name, email: data.user.email, avatarUrl: data.user.avatar_url });\`
187
+ - If no human-readable name path is available, still pass available non-name profile traits: \`cluebase.identify(<id-path>, { email: <email-path>, avatarUrl: <avatarUrl-path> })\`. Do not emit \`name: id\`.
188
+ - cluebase.group (one per de-duplicated \`group_sites\` entry): \`cluebase.group("organization", <organization_id-path>, { name: <name-path> })\` with paths from \`available_fields\`. Use the customer's stable company/org/tenant id as the organization id. Do not emit Cluebase \`account\` or \`workspace\` group types in MVP.
189
+ - Example: \`cluebase.group("organization", organization.id, { name: organization.name });\`
190
+ - Initial active context example: after login success, if the same response exposes \`data.activeWorkspace.id\` and \`data.activeWorkspace.name\`, call \`cluebase.group("organization", data.activeWorkspace.id, { name: data.activeWorkspace.name });\` immediately after \`cluebase.identify(...)\`.
191
+ - Active-context owner example: if the active organization is available from an existing singleton provider / restore hook, emit only from that owner and add a local \`lastCluebaseGroupKey\` guard so the same \`userId:organizationId\` pair is not emitted repeatedly. Do not also add \`cluebase.group\` to create / join / switch handlers that merely update that provider's active organization state.
192
+ - cluebase.reset (one per reset_sites entry): \`cluebase.reset();\` at the logout completion point.
193
+
194
+ Implementation patterns (FastAPI):
195
+ - cluebase.init (singleton): in \`cluebase_init_backend.file\` (typically main.py or app/__init__.py), add \`from cluebase_backend_sdk._integrations.fastapi import cluebase_init_fastapi\` near the top, then read every required env value via non-crashing getters and call \`cluebase_init_fastapi(app, ...)\` only when they are present. Required env values: \`CLUEBASE_INGEST_ENDPOINT\`, \`CLUEBASE_PROJECT_KEY\`, \`CLUEBASE_API_KEY\`; required SDK argument: \`service_key\`. The SDK derives environment (dev / prod) from the \`project_key\` prefix (\`pk_dev_\` vs \`pk_prod_\`). Pattern:
196
+ import os
197
+ from cluebase_backend_sdk._integrations.fastapi import cluebase_init_fastapi
198
+ app = FastAPI(...)
199
+ _cluebase_endpoint = os.getenv("CLUEBASE_INGEST_ENDPOINT")
200
+ _cluebase_project_key = os.getenv("CLUEBASE_PROJECT_KEY")
201
+ _cluebase_api_key = os.getenv("CLUEBASE_API_KEY")
202
+ _cluebase_service_id = "<backend-service-id>"
203
+ if _cluebase_endpoint and _cluebase_project_key and _cluebase_api_key:
204
+ cluebase_init_fastapi(
205
+ app,
206
+ project_key=_cluebase_project_key,
207
+ api_key=_cluebase_api_key,
208
+ service_key=_cluebase_service_id,
209
+ )
210
+ \`cluebase_init_fastapi\` reads \`CLUEBASE_INGEST_ENDPOINT\` from the process env through the SDK settings path. Never use \`os.environ["CLUEBASE_*"]\` required indexing — the if-guard above is mandatory so missing env does not crash the host service. Replace the \`<backend-service-id>\` placeholder with the matching value recorded in discoveries. Environment is derived from the project_key prefix.
211
+ - cluebase.identify (one per identify_sites entry): \`from cluebase_backend_sdk import cluebase\` and \`cluebase.identify(<id-path>, traits={"name": <name-path>, "email": <email-path>, "avatarUrl": <avatarUrl-path>})\` in the success path of the login route handler (after the user has been resolved and committed, before returning the response). Paths come VERBATIM from \`available_fields\`. Do not await Cluebase calls. Trait keys are **camelCase** even on Python. The SDK/API converts \`email\` into privacy-safe contact-derived fields before storage.
212
+ - Common pattern: \`cluebase.identify(str(user.id), traits={"name": user.name, "email": user.email})\`.
213
+ - cluebase.group: \`cluebase.group("organization", <organization_id-path>, {"name": <name-path>, ...})\` in the company/organization/tenant resolution path. If backend login/auth exchange resolves the active organization for the authenticated user, place this immediately after backend \`cluebase.identify(...)\`; if the backend route only authenticates and does not resolve active organization, do not invent a DB join solely for Cluebase. Paths come VERBATIM from \`available_fields\`; never hardcode. Trait keys are **camelCase** even on Python. Do not emit Cluebase \`account\` or \`workspace\` group types in MVP.
214
+ - cluebase.reset: \`cluebase.reset()\` in the logout handler success path (before the response). Do NOT pass \`reason\` from this setup flow.
215
+
216
+ Implementation patterns (Express / NestJS / Fastify / Hono — Node backend, when \`cluebase_init_backend.evidence_snippet\` identifies a Node backend rather than Python):
217
+ - cluebase.init (singleton) + middleware attach: in \`cluebase_init_backend.file\` (typically \`src/app.ts\` / \`src/main.ts\` / \`server.ts\` / NestJS's \`main.ts\`), add the imports \`import cluebase from "@genn-inc/cluebase-backend-sdk";\` and \`import { cluebaseExpressMiddleware } from "@genn-inc/cluebase-backend-sdk";\` near the top, call \`cluebase.init({ ... })\` BEFORE the app object is constructed (so the SDK runtime is ready when the first request lands), and then attach the middleware to the app instance.
218
+ - Pattern (Express):
219
+ import cluebase from "@genn-inc/cluebase-backend-sdk";
220
+ import { cluebaseExpressMiddleware } from "@genn-inc/cluebase-backend-sdk";
221
+ cluebase.init({
222
+ endpoint: process.env.CLUEBASE_INGEST_ENDPOINT!,
223
+ projectKey: process.env.CLUEBASE_PROJECT_KEY!,
224
+ apiKey: process.env.CLUEBASE_API_KEY!,
225
+ serviceKey: "<backend-service-id>",
226
+ });
227
+ const app = express();
228
+ app.use(cluebaseExpressMiddleware());
229
+ - INSTRUMENTATION PRELOAD (MANDATORY for every Node backend — without it the backend records NOTHING): \`cluebaseExpressMiddleware\` annotates the request span that standard OpenTelemetry instrumentation creates; it never creates one itself. Node instrumentation patches a module while that module is loading, so it cannot reach a module that is already loaded — and by the time \`cluebase.init(...)\` runs in the entry file body, \`express\` and the database client have already been loaded. The instrumentation therefore has to be preloaded, exactly like \`@opentelemetry/auto-instrumentations-node/register\`. The SDK ships that entry: \`@genn-inc/cluebase-backend-sdk/register\`.
230
+ Edit EVERY server start script in the backend's package.json (\`start\`, \`dev\`, and every \`start:*\` such as NestJS's \`start:dev\` / \`start:prod\`) so the node invocation carries \`--import @genn-inc/cluebase-backend-sdk/register\`:
231
+ "scripts": {
232
+ "start": "node --import @genn-inc/cluebase-backend-sdk/register dist/main.js",
233
+ "start:dev": "nest start --watch --exec \\"node --import @genn-inc/cluebase-backend-sdk/register\\""
234
+ }
235
+ When the server is started outside package.json (Dockerfile \`CMD\`, PM2, systemd, Procfile), add the same flag there, or set \`NODE_OPTIONS=--import @genn-inc/cluebase-backend-sdk/register\` in that start environment. Do NOT install \`@opentelemetry/instrumentation-*\` packages separately — they ship with \`@genn-inc/cluebase-backend-sdk\`. Do NOT add \`@opentelemetry/auto-instrumentations-node\`; it also records file-system, DNS, and socket activity and buries the customer's observations in noise. STEP 6's setup-doctor fails on \`backend_sdk_instrumentation_preload\` when a start script is missing the flag.
236
+ DATABASE COVERAGE: the preload records reads and writes through \`pg\` (PostgreSQL) only. Requests, branch decisions, and outbound dependency calls are covered on any stack. If the dependency manifest shows \`mysql2\`, \`mongodb\`, \`ioredis\`, \`@prisma/client\`, or another data client instead, TELL THE CUSTOMER IN JAPANESE, in the STEP 5 summary, that "データの読み書きの記録は現時点で PostgreSQL のみ対応で、この構成では空になります" — do NOT silently leave it looking complete. Do not add an unverified instrumentation package on your own initiative.
237
+ - NestJS variant: NestJS uses an Express adapter under the hood, so the same \`cluebase.init\` + \`cluebaseExpressMiddleware()\` pattern applies. Place \`cluebase.init(...)\` at the top of \`main.ts\` (before \`NestFactory.create(...)\`), then after \`const app = await NestFactory.create(AppModule);\` add \`app.use(cluebaseExpressMiddleware());\` — NestJS forwards \`app.use\` to the underlying Express instance. The preload flag above is required here too.
238
+ - Fastify / Hono variant: \`@genn-inc/cluebase-backend-sdk\` exports a framework-agnostic middleware adapter. For Fastify, wrap \`cluebaseExpressMiddleware()\` with \`@fastify/express\` (\`await app.register(import("@fastify/express"))\` then \`app.use(cluebaseExpressMiddleware())\`). For Hono, use \`app.use("*", async (c, next) => { /* cluebaseExpressMiddleware adapter */ await next(); })\` — keep the pattern simple; if the customer's app already has a request-middleware layer, attach the Cluebase middleware alongside it at the entry point.
239
+ - env values: ALL three of \`CLUEBASE_INGEST_ENDPOINT\`, \`CLUEBASE_PROJECT_KEY\`, \`CLUEBASE_API_KEY\` are non-public server env vars (no \`NEXT_PUBLIC_\` / \`VITE_\` prefix). Replace the \`<backend-service-id>\` placeholder with the matching value recorded in discoveries.
240
+ - The SDK derives the environment (dev / prod) from the \`projectKey\` prefix (\`pk_dev_\` vs \`pk_prod_\`).
241
+ - cluebase.identify / cluebase.group / cluebase.reset (one per discoveries entry): \`import cluebase from "@genn-inc/cluebase-backend-sdk";\` and call them at the precise file:line on the success path of the corresponding route handler. Paths come VERBATIM from \`available_fields\`. Do not await Cluebase calls.
242
+
243
+ Implementation patterns (Django — Python backend, when \`cluebase_init_backend.evidence_snippet\` identifies Django rather than FastAPI):
244
+ - cluebase.init (singleton): there are two equivalent patterns. Prefer (a) AppConfig.ready() because it is the standard Django startup hook; fall back to (b) install_django() when no AppConfig file is present.
245
+ (a) AppConfig.ready() pattern (in any \`<app>/apps.py\` — typically the customer's primary Django app):
246
+ import os
247
+ from django.apps import AppConfig
248
+ from cluebase_backend_sdk import cluebase
249
+
250
+ class MyAppConfig(AppConfig):
251
+ name = "myapp"
252
+ def ready(self):
253
+ _cluebase_project_key = os.getenv("CLUEBASE_PROJECT_KEY")
254
+ _cluebase_api_key = os.getenv("CLUEBASE_API_KEY")
255
+ _cluebase_endpoint = os.getenv("CLUEBASE_INGEST_ENDPOINT")
256
+ _cluebase_service_id = "<backend-service-id>"
257
+ if _cluebase_endpoint and _cluebase_project_key and _cluebase_api_key:
258
+ cluebase.init({
259
+ "endpoint": _cluebase_endpoint,
260
+ "project_key": _cluebase_project_key,
261
+ "api_key": _cluebase_api_key,
262
+ "service_key": _cluebase_service_id,
263
+ })
264
+ (b) install_django() pattern (in \`settings.py\` or a small \`cluebase_setup.py\` imported once from \`wsgi.py\` / \`asgi.py\`):
265
+ from cluebase_backend_sdk import cluebase
266
+ install_django() # auto-inserts CluebaseDjangoMiddleware into MIDDLEWARE
267
+ - Middleware attach: the Cluebase SDK ships \`CluebaseDjangoMiddleware\`. With pattern (a), add \`"cluebase_backend_sdk._integrations.django.CluebaseDjangoMiddleware"\` to \`settings.py\`'s \`MIDDLEWARE\` list manually (insert AFTER Django's session / auth middleware). With pattern (b), \`install_django()\` inserts the middleware for you.
268
+ - env values: same shape as FastAPI — non-crashing getter for \`CLUEBASE_PROJECT_KEY\` / \`CLUEBASE_API_KEY\` with an \`if\` guard so missing env never crashes Django startup. The Cluebase environment is derived from the project_key prefix. Replace the \`<backend-service-id>\` placeholder with the matching value recorded in discoveries.
269
+ - cluebase.identify / cluebase.group / cluebase.reset (one per discoveries entry): \`from cluebase_backend_sdk import cluebase\` and call them in the success path of each view (typically inside a \`def post(...)\` / \`@login_required\` view, after the user is resolved and the response is built but before it returns). Paths come VERBATIM from \`available_fields\`. Do not await Cluebase calls.
270
+
271
+ AI provider OpenTelemetry / OpenLLMetry / GenAI semantic instrumentation (one wire-up per \`ai_provider_sites\` entry):
272
+ OpenTelemetry/OpenLLMetry GenAI semantic instrumentation is the primary source for OpenAI / Anthropic / Azure OpenAI evidence where available. The SDK helper \`CluebaseAiObserver\` must install or delegate to that OTel-primary instrumentation first; Cluebase-specific behavior may only enrich, correlate, mask, classify, or fill an unavailable-library fallback. STEP 5 must add this wire-up DIRECTLY AFTER the customer's existing LLM client instantiation so the customer does not have to refactor their calls.
273
+
274
+ - Python pattern — insert in the SAME module that owns the LLM client, on the line AFTER the client constructor:
275
+ from cluebase_backend_sdk import cluebase
276
+ client = openai.OpenAI() # existing customer code — DO NOT move or modify
277
+ try:
278
+ ai_observer = CluebaseAiObserver()
279
+ ai_observer.instrument_openai() # only when ai_provider_sites contains kind="openai" or "azure_openai"
280
+ ai_observer.instrument_anthropic() # only when ai_provider_sites contains kind="anthropic"
281
+ except Exception:
282
+ pass # never crash the customer app
283
+ Wire-up rules:
284
+ * The ENTIRE \`CluebaseAiObserver()\` + \`instrument_*\` block MUST be inside a \`try / except Exception: pass\` guard. The SDK is no-throw by design but the constructor still depends on \`cluebase.init\` having executed; wrapping protects the customer app from any startup edge case.
285
+ * Call \`instrument_openai\` and / or \`instrument_anthropic\` based on the kinds present in \`ai_provider_sites\` for THIS file. Do not call both blindly — only the providers actually detected.
286
+ * One \`CluebaseAiObserver\` instance per module is enough; do NOT create multiple instances in the same file even if multiple LLM clients are instantiated there.
287
+ * NEVER pass raw prompts / completion text / tool call args into \`CluebaseAiObserver\` arguments — the SDK auto-captures and redacts those internally.
288
+
289
+ - Node / TypeScript pattern — insert in the SAME module that owns the LLM client, on the line AFTER the client constructor:
290
+ import { CluebaseAiObserver } from "@genn-inc/cluebase-backend-sdk/integrations/ai";
291
+ const client = new OpenAI(); // existing customer code — DO NOT move or modify
292
+ try {
293
+ const aiObserver = new CluebaseAiObserver();
294
+ await aiObserver.instrumentOpenAI(); // only when ai_provider_sites contains kind="openai" or "azure_openai"
295
+ await aiObserver.instrumentAnthropic(); // only when ai_provider_sites contains kind="anthropic"
296
+ } catch {
297
+ // never crash the customer app
298
+ }
299
+ Wire-up rules: same as the Python pattern above. The Node \`instrumentOpenAI\` / \`instrumentAnthropic\` methods return Promises — \`await\` them inside the try block. Do NOT await later access of the LLM client (the auto-instrument hook is non-blocking once installed).
300
+
301
+ MCP server OpenTelemetry instrumentation (one wire-up per \`mcp_server_sites\` entry):
302
+ OpenTelemetry MCP or GenAI semantic spans are the primary source for MCP tool calls and resource fetches where available. The SDK helper \`CluebaseMcpObserver\` may enrich, correlate, mask, classify, or provide an unavailable-library fallback, but it must not replace OTel as the primary telemetry path. STEP 5 must add this wire-up DIRECTLY AFTER the customer's \`Server(...)\` instantiation, using the \`server_variable_name\` recorded by STEP 1.
303
+
304
+ - Python pattern:
305
+ from cluebase_backend_sdk import cluebase
306
+ server = Server("my-mcp") # existing customer code — DO NOT move or modify
307
+ try:
308
+ mcp_observer = CluebaseMcpObserver()
309
+ mcp_observer.anthropic_instrument(server) # pass the SAME variable from server_variable_name
310
+ except Exception:
311
+ pass # never crash the customer app
312
+
313
+ - Node / TypeScript pattern:
314
+ import { CluebaseMcpObserver } from "@genn-inc/cluebase-backend-sdk/integrations/mcp";
315
+ const server = new Server({ name: "my-mcp", version: "1.0.0" }, { capabilities: {} }); // existing customer code
316
+ try {
317
+ const mcpObserver = new CluebaseMcpObserver();
318
+ mcpObserver.anthropicInstrument(server); // pass the SAME variable from server_variable_name
319
+ } catch {
320
+ // never crash the customer app
321
+ }
322
+
323
+ Wire-up rules (both languages):
324
+ - The \`CluebaseMcpObserver\` block MUST be inside a \`try / except Exception: pass\` (Python) or \`try { } catch { }\` (Node) guard. This is the ONLY place in the setup flow where Cluebase calls ARE wrapped in try/except — it is a deliberate exception to the "no try/except around lifecycle calls" rule because \`CluebaseMcpObserver\` is an integration wrapper, not a lifecycle call, and a crash here would prevent the customer's MCP server from starting.
325
+ - Pass the EXACT variable name recorded in \`server_variable_name\` to \`anthropic_instrument\` / \`anthropicInstrument\`. Do NOT rename or re-create the server.
326
+ - One \`CluebaseMcpObserver\` instance per MCP server; if a single file constructs multiple servers, create one observer and call \`anthropic_instrument\` for each.
327
+ - NEVER pass tool args / resource bodies into the observer — the SDK auto-captures and redacts those.
328
+
329
+ LangChain OpenTelemetry / GenAI semantic instrumentation (one wire-up per \`langchain_sites\` module — at most one call per module):
330
+ \`instrument_langchain\` (Python) / \`instrumentLangChain\` (Node) must prefer OpenTelemetry/OpenLLMetry GenAI semantic instrumentation as the primary signal for chains, agents, tools, and LLM calls. Cluebase callbacks may enrich or cover unavailable-library gaps only. STEP 5 inserts ONE call per module (LangChain's callback manager is process-wide, so a second call in the same module is redundant).
331
+
332
+ - Python pattern — insert in the SAME module that owns the chain / agent / LLM construction, immediately AFTER the \`from langchain ...\` import block at the top of the module:
333
+ from langchain_core.prompts import ChatPromptTemplate
334
+ from langchain_openai import ChatOpenAI
335
+ from cluebase_backend_sdk import cluebase
336
+ try:
337
+ instrument_langchain() # global callback manager attach — covers ALL chains / agents in this module
338
+ except Exception:
339
+ pass # never crash the customer app
340
+ llm = ChatOpenAI(model="gpt-4o") # existing customer code — DO NOT move or modify
341
+
342
+ - Node / TypeScript pattern — insert in the SAME module that owns the chain / agent / LLM construction, immediately AFTER the chain / agent / LLM constructor on the recorded line:
343
+ import { ChatOpenAI } from "@langchain/openai";
344
+ import { instrumentLangChain, CluebaseLangChainCallbackHandler } from "@genn-inc/cluebase-backend-sdk/integrations/langchain";
345
+ const llm = new ChatOpenAI({ model: "gpt-4o" }); // existing customer code — DO NOT move or modify
346
+ try {
347
+ instrumentLangChain(llm); // attaches the Cluebase callback handler to the global LangChain runtime
348
+ } catch {
349
+ // never crash the customer app
350
+ }
351
+ Per-chain attach alternative (when global attach is undesirable — e.g. the customer wants Cluebase on only one specific chain):
352
+ const handler = new CluebaseLangChainCallbackHandler();
353
+ await chain.invoke(input, { callbacks: [handler] });
354
+
355
+ Wire-up rules (both languages):
356
+ - The block MUST be inside a \`try / except Exception: pass\` (Python) or \`try { } catch { }\` (Node) guard. This is an integration wrapper, not a lifecycle call — the deliberate exception applies.
357
+ - One call per module is enough. If a single module has TWO \`langchain_sites\` entries (e.g. one chain + one agent), DO NOT duplicate the \`instrument_langchain()\` call — the global attach covers both.
358
+ - NEVER pass raw prompt / message content into the wire-up — the SDK auto-captures and redacts those internally.
359
+
360
+ LlamaIndex OpenTelemetry / GenAI semantic instrumentation (Python; one wire-up per \`llamaindex_sites\` module — at most one call per module):
361
+ \`instrument_llamaindex\` (Python) must prefer OpenTelemetry/OpenLLMetry GenAI semantic instrumentation as the primary signal for index, query engine, chat engine, and agent operations. Cluebase event handlers may enrich or cover unavailable-library gaps only.
362
+
363
+ - Python pattern — insert in the SAME module that owns the LlamaIndex usage, immediately AFTER the \`from llama_index ...\` import block:
364
+ from llama_index.core import VectorStoreIndex, Settings
365
+ from cluebase_backend_sdk import cluebase
366
+ try:
367
+ instrument_llamaindex() # root dispatcher attach — covers ALL LlamaIndex calls in the process
368
+ except Exception:
369
+ pass # never crash the customer app
370
+ Settings.llm = ... # existing customer code — DO NOT move or modify
371
+
372
+ Wire-up rules:
373
+ - The block MUST be inside \`try / except Exception: pass\`.
374
+ - One call per module is enough — LlamaIndex's root dispatcher is process-wide.
375
+ - NEVER pass raw query / document content into the wire-up — the SDK auto-captures and redacts those internally.
376
+
377
+ CrewAI OpenTelemetry / GenAI semantic instrumentation (Python; one wire-up per \`crewai_sites\` entry):
378
+ \`instrument_crewai(crew)\` must prefer OpenTelemetry/OpenLLMetry GenAI semantic instrumentation as the primary signal for CrewAI agent and task execution. Cluebase wrapping may enrich or cover unavailable-library gaps only. STEP 5 inserts the wire-up DIRECTLY AFTER each \`Crew(...)\` instantiation, using the \`crew_variable_name\` recorded by STEP 1.
379
+
380
+ - Python pattern:
381
+ from crewai import Agent, Task, Crew
382
+ from cluebase_backend_sdk import cluebase
383
+ crew = Crew(agents=[...], tasks=[...]) # existing customer code — DO NOT move or modify
384
+ try:
385
+ instrument_crewai(crew) # pass the SAME variable from crew_variable_name
386
+ except Exception:
387
+ pass # never crash the customer app
388
+
389
+ Wire-up rules:
390
+ - The block MUST be inside \`try / except Exception: pass\`.
391
+ - Pass the EXACT variable name recorded in \`crew_variable_name\` to \`instrument_crewai\`. Do NOT rename or re-create the crew.
392
+ - One \`instrument_crewai\` call per \`Crew\` instance; if a file constructs multiple crews, add one wire-up per crew.
393
+ - NEVER pass agent prompts / task descriptions into the wire-up — the SDK auto-captures and redacts those internally.
394
+
395
+ Mastra OpenTelemetry / GenAI semantic instrumentation (Node / TypeScript; one wire-up per \`mastra_sites\` entry):
396
+ \`CluebaseMastraObserver\` + \`instrumentMastra(observer, mastra)\` must prefer OpenTelemetry/OpenLLMetry GenAI semantic instrumentation as the primary signal for Mastra agent and workflow runs. Cluebase observation may enrich or cover unavailable-library gaps only. STEP 5 inserts the wire-up DIRECTLY AFTER each \`new Mastra(...)\` instantiation, using the \`mastra_variable_name\` recorded by STEP 1.
397
+
398
+ - Node / TypeScript pattern:
399
+ import { Mastra } from "@mastra/core";
400
+ import { CluebaseMastraObserver, instrumentMastra } from "@genn-inc/cluebase-backend-sdk/integrations/mastra";
401
+ const mastra = new Mastra({ /* customer config */ }); // existing customer code — DO NOT move or modify
402
+ try {
403
+ const observer = new CluebaseMastraObserver();
404
+ instrumentMastra(observer, mastra); // pass the SAME variable from mastra_variable_name
405
+ } catch {
406
+ // never crash the customer app
407
+ }
408
+
409
+ Wire-up rules:
410
+ - The block MUST be inside \`try { } catch { }\`.
411
+ - Pass the EXACT variable name recorded in \`mastra_variable_name\` to \`instrumentMastra\`. Do NOT rename or re-create the Mastra instance.
412
+ - One \`CluebaseMastraObserver\` + \`instrumentMastra\` per Mastra instance; if a file constructs multiple Mastra instances, add one observer + one \`instrumentMastra\` per instance.
413
+ - NEVER pass agent prompts / workflow inputs into the wire-up — the SDK auto-captures and redacts those internally.
414
+
415
+ Hard rules:
416
+ - DO NOT add or keep any customer-backend route under \`/api/v1/cluebase/*\` or any other path that proxies Cluebase's browser-token endpoint. The frontend SDK calls the Cluebase backend directly at \`POST <CLUEBASE_API_BASE_URL>/api/v1/ingest/browser-tokens\` using the public project key and short-lived token contract; the customer's backend is NOT in the path. A \`/cluebase/*\` route handler in the customer backend (FastAPI \`@router.post("/cluebase/...")\`, Express \`app.post("/api/v1/cluebase/...")\`, NestJS \`@Controller("api/v1/cluebase")\`, Django \`path("api/v1/cluebase/...")\`, etc.) is a P0 violation.
417
+ - DO NOT edit any file not referenced in discoveries (except dependency files).
418
+ - DO NOT use the Write tool on existing files. Edit only. Write is reserved for new files (creates_new_file: true).
419
+ - DO NOT add cluebase.track in this lifecycle step. Business value event tracking has its own dedicated STEP 8 (/cluebase-events); the lifecycle step only places cluebase.init / cluebase.identify / cluebase.group / cluebase.reset.
420
+ - DO NOT wrap lifecycle calls in try/catch.
421
+ - DO NOT await lifecycle calls in blocking paths.
422
+ - DO NOT pin SDK versions.
423
+ - DO NOT run formatters / linters / import sorters across the repository.
424
+ - DO NOT modify any API, ORM model, response shape, or fetch to populate Cluebase arguments. Use ONLY what's recorded in \`available_fields\`. Do NOT pass the id string or email as a fallback display name, and DO NOT try to "fix" the missing name by adding a real name field. When an email path is already recorded in \`available_fields.email\`, pass it as the \`email\` trait.
425
+ - Do not invent init option values; use the generated discoveries and setup manifest.
426
+
427
+ When all steps + self-review converge, respond with exactly the following Japanese block (and nothing else after it):
428
+
429
+ ==========================================
430
+ STEP 5(実装 + 自己レビュー)完了
431
+ ==========================================
432
+ 変更ファイル: <list>
433
+ cluebase.init / cluebase.identify / cluebase.group / cluebase.reset 挿入数: <counts>
434
+ 依存追加: <dependency files>
435
+ install 実行: <command>
436
+ env file 書込: frontend=<frontend-env-path> (lines=<N>), backend=<backend-env-path> (lines=<N>)
437
+ 自己修正: <N> 件
438
+
439
+ 重要: frontend / backend の dev server を一度停止して再起動してください。 env file を書き換えただけでは dev server は新しい env を読み込みません。 再起動後に次の STEP に進んでください。
440
+
441
+ 次は Claude Code で /cluebase-implement-check と打って STEP 6(静的検証)を実行してください。
442
+ ==========================================
443
+
444
+ Reference docs: ${documentsUrl}`;
445
+ }
446
+
447
+ // --- STEP 6: setup-check --write-snapshot (Bash) -------------------------
448
+
449
+ export function buildStep6SetupCheck() {
450
+ return `You are running STEP 6 of the Cluebase setup flow (static verification + diff snapshot).
451
+
452
+ Run the following command via the Bash tool:
453
+
454
+ \`\`\`bash
455
+ ${CLUEBASE_CLI_INVOCATION} setup-check --require-sdk-lifecycle --repo . --write-snapshot
456
+ \`\`\`
457
+
458
+ This single command:
459
+ - Auto-detects \`--framework\` and \`--backend-root-path\` from \`.cluebase/discoveries.json\` + \`./.cluebase/setup-manifest.json\`.
460
+ - Writes \`.cluebase/setup-check.json\` (the report).
461
+ - Writes \`.cluebase/setup-diff.patch\` (\`git diff HEAD\` snapshot of implementation edits).
462
+ - Always exits 0 — pass/fail is reflected in the JSON file content; STEP 7 examines it.
463
+
464
+ After the command finishes, respond with exactly this Japanese line:
465
+
466
+ STEP 6(静的検証 + 差分スナップショット)完了。次は Claude Code で /cluebase-implement-review と打って STEP 7(セルフレビュー + 自動修正)を実行してください。
467
+
468
+ Do not interpret the JSON results yourself; STEP 7 handles that.`;
469
+ }
470
+
471
+ // --- STEP 7: Self-review + Auto-fix loop --------------------------------
472
+
473
+ export function buildStep7Review({ documentsUrl }) {
474
+ return `You are running inside an AI coding tool. This is STEP 7 of the Cluebase setup flow (Self-review + Auto-fix).
475
+
476
+ Goal:
477
+ Audit the implementation diff against the Cluebase setup contract. If any P0/P1 issue is found, fix it surgically via Edit tool, refresh the setup-check artifacts via Bash, re-verify, and loop up to 3 iterations. The user should never need to manually trigger a separate fix step — STEP 7 either converges to "問題なし" or, after 3 iterations, escalates the residual to the user.
478
+
479
+ Inputs (Read tool — already on disk):
480
+ [A] \`.cluebase/discoveries.json\` — produced by STEP 1, self-reviewed by STEP 2, enriched by STEP 3.
481
+ [B] \`.cluebase/setup-check.json\` — written by STEP 6 (and re-written each loop iteration).
482
+ [C] \`.cluebase/setup-diff.patch\` — written by STEP 6 (and re-written each loop iteration).
483
+
484
+ If any of the three files is missing or empty, STOP and respond with exactly one Japanese line:
485
+ \`STEP 7 を実行できません: <file path> が見つかりません。 STEP 6 (setup-check 実行) が完了していません。 .cluebase/implementation.json および .cluebase/discoveries.json:_progress から step5_implement / step6_check を Edit で外して /cluebase-implement を再実行してください。\`
486
+
487
+ Process (loop up to 3 iterations; track iteration index N starting at 1 and cumulative fix count M):
488
+ 1. Apply the P0/P1 rubric below to the current diff.
489
+ 2. If NO P0/P1 issue is found: BREAK with success.
490
+ 3. If P0/P1 issues are found:
491
+ a. For each finding with a concrete Suggested fix, apply it surgically:
492
+ - "use client" missing → use Edit to prepend \`"use client";\\n\\n\` to the file.
493
+ - Reformat noise outside Cluebase lines → run \`git checkout HEAD -- <file>\` via Bash to revert the file, then re-apply ONLY the Cluebase lines via Edit. **If the SAME reformat noise reappears in the same file after this fix (typical sign: the customer has a \`PostToolUse\` formatter hook that re-runs after every Edit — examples by ecosystem: Prettier / Biome / ESLint --fix (JS/TS), gofmt / goimports (Go), rustfmt (Rust), black / ruff format (Python), rubocop -A (Ruby), dotnet format (.NET), mix format (Elixir), spotless / google-java-format (Java), php-cs-fixer / pint (PHP)), DEMOTE this finding to informational — it is the customer's existing project style, not Cluebase-introduced drift. Stop trying to fix it and do not include it in the final residual count.**
494
+ - Lockfile / manifest out-of-sync (setup-check flagged a package manifest / lockfile mismatch after implementation added a Cluebase SDK dependency) → detect the package manager from the lockfile/manifest present in the repo, then run the matching install command via Bash. Examples by ecosystem (non-exhaustive — adapt to whatever the customer's repo actually uses):
495
+ - JavaScript / TypeScript: \`bun.lock\` → \`bun install\`; \`pnpm-lock.yaml\` → \`pnpm install\`; \`yarn.lock\` → \`yarn install\`; \`package-lock.json\` → \`npm install\`.
496
+ - Python: \`requirements.txt\` → \`pip install -r requirements.txt\` (or \`uv pip install -r requirements.txt\` if \`uv\` is on the host); \`pyproject.toml\` with poetry → \`poetry install\`; with pdm → \`pdm install\`; with uv → \`uv sync\`.
497
+ - Ruby: \`Gemfile.lock\` → \`bundle install\`.
498
+ - Go: \`go.sum\` / \`go.mod\` → \`go mod tidy\` (or \`go get ./...\`).
499
+ - Rust: \`Cargo.lock\` → \`cargo build\` (or \`cargo fetch\`).
500
+ - Java / Kotlin: \`pom.xml\` → \`mvn install -DskipTests\`; \`build.gradle\` / \`build.gradle.kts\` → \`./gradlew build -x test\` (or \`./gradlew dependencies\`).
501
+ - .NET / C#: \`*.csproj\` / \`*.fsproj\` / \`packages.lock.json\` → \`dotnet restore\`.
502
+ - PHP: \`composer.lock\` → \`composer install\`.
503
+ - Elixir: \`mix.lock\` → \`mix deps.get\`.
504
+ - Any other ecosystem: run the standard install command for the package manager you detect in the repo, with optional \`cd <subdir> &&\` prefix to match the repo layout.
505
+ **If the required package manager binary is not installed on the host** (verify with \`which <binary>\` before invoking; example: \`bun\` missing but \`bun.lock\` exists alongside an in-sync \`package-lock.json\`), DEMOTE the finding to informational — the orphan lockfile does not affect the active package manager's resolution.
506
+ - Hardcoded id → use Edit to replace with the existing variable name from the surrounding scope.
507
+ - Wrong SDK package name → use Edit to replace the import line.
508
+ - The backend SDK package name is wrong → use Edit to switch to \`@genn-inc/cluebase-backend-sdk\` for Node or \`cluebase-backend-sdk\` for Python. Leave the customer's chosen dependency version and lockfile resolution unchanged.
509
+ b. Bash tool may invoke ONLY the following commands. No others (no \`docker\`, \`make\`, \`pytest\`, server start, etc.):
510
+ - \`${CLUEBASE_CLI_INVOCATION} setup-check --require-sdk-lifecycle --repo . --write-snapshot\`
511
+ - \`git checkout HEAD -- <file>\`
512
+ - \`git diff HEAD\`
513
+ - Package-manager install commands ONLY from the catalogue above (\`bun install\` / \`pnpm install\` / \`yarn install\` / \`npm install\` / \`pip install -r requirements.txt\` / \`uv pip install -r requirements.txt\` / \`uv sync\` / \`poetry install\` / \`pdm install\` / \`bundle install\` / \`go mod tidy\` / \`go get ./...\` / \`cargo build\` / \`cargo fetch\` / \`mvn install -DskipTests\` / \`./gradlew build -x test\` / \`./gradlew dependencies\` / \`dotnet restore\` / \`composer install\` / \`mix deps.get\`) — with optional \`cd <subdir> &&\` prefix.
514
+ - \`which <binary>\` (to verify a package manager is installed before invoking it)
515
+ c. After applying all fixes in this iteration, refresh the artifacts via Bash:
516
+ \`${CLUEBASE_CLI_INVOCATION} setup-check --require-sdk-lifecycle --repo . --write-snapshot\`
517
+ d. Re-read \`.cluebase/setup-check.json\` and \`.cluebase/setup-diff.patch\` via Read.
518
+ e. Increment N. If N > 3, BREAK with residual findings.
519
+
520
+ P0 rubric (must block release):
521
+ - CLUEBASE_API_KEY wired into browser-public frontend SDK config. If detected, REMOVE that line from the frontend SDK wiring and verify the same key/value remains available to the server runtime.
522
+ - Backend env file is missing any of: \`CLUEBASE_API_KEY\`, \`CLUEBASE_PROJECT_KEY\`, \`CLUEBASE_INGEST_ENDPOINT\`. Auto-fix by reading values from \`.cluebase/setup-manifest.json\` (\`cluebase_context.project_key\` / \`cluebase_context.ingest_endpoints.backend\`) + \`.cluebase/secrets.json\` (\`cluebase_api_key\`) and appending / upserting via Edit. Skip CLUEBASE_API_KEY only if \`.cluebase/secrets.json\` is absent, in which case escalate to residual findings asking the user to re-run setup with --cluebase-api-key.
523
+ - Frontend env file is missing any of the prefixed equivalents of \`CLUEBASE_API_BASE_URL\`, \`CLUEBASE_PROJECT_KEY\` (prefix determined by framework_frontend: \`NEXT_PUBLIC_\` for Next.js, \`VITE_\` for Vite, \`PUBLIC_\` for SvelteKit, etc.). Auto-fix the same way as backend, using \`cluebase_context.cluebase_api_base_url\` for API_BASE_URL. The frontend SDK calls the Cluebase backend directly.
524
+ - \`.cluebase/secrets.json\` exists but is NOT registered in the repo's \`.gitignore\`. Auto-fix by appending \`.cluebase/secrets.json\` to \`.gitignore\` via Edit.
525
+ - CLUEBASE_API_KEY referenced from any browser/client-bundled file.
526
+ - await on cluebase.init / cluebase.identify / cluebase.group / cluebase.reset in a blocking path.
527
+ - try/catch / try/except / .catch wrapping lifecycle calls solely for Cluebase.
528
+ - More than one cluebase.init per side.
529
+ - Hardcoded user/account/tenant id passed to cluebase.identify or cluebase.group.
530
+ - Browser-side cluebase.init reads non-public env (\`CLUEBASE_*\` instead of \`NEXT_PUBLIC_CLUEBASE_*\` / \`VITE_CLUEBASE_*\` / framework-equivalent public-env name).
531
+ - Next.js client adapter file missing \`"use client"\` directive.
532
+ - Customer backend route handler under \`/api/v1/cluebase/*\`; the frontend SDK calls Cluebase directly.
533
+ - Backend uses \`os.environ["CLUEBASE_*"]\` required indexing (must use a non-crashing getter).
534
+ - Wrong SDK package name (must be \`@genn-inc/cluebase-frontend-sdk\` for frontend, \`cluebase-backend-sdk\` for FastAPI).
535
+ - cluebase.init placed in a render/request path (component body, useEffect without empty-deps singleton guard, request middleware on read endpoints, page mount, etc.).
536
+ - cluebase.identify / cluebase.group / cluebase.reset placed in a read path that re-fires (useQuery / useSuspenseQuery / queryFn / TanStack / Vue Query / Apollo / SWR / Angular Resolver / GET-endpoint handler / page-component body).
537
+ - cluebase.identify placed in token refresh, current-user or /me sync, session polling, request exchange, read hook, query function, or repeated helper path without evidence that the path is the actual one-time auth success boundary.
538
+ - cluebase.group placed in both a guarded active-context owner and create / join / switch handlers on the same surface. The owner must be the single emission point when it observes active organization changes.
539
+
540
+ P1 rubric (must fix before completion):
541
+ - Diff includes refactors, renames, formatter churn, or whitespace changes outside the Cluebase lines.
542
+ - cluebase.track appears in the lifecycle diff. Business value event tracking belongs to the dedicated STEP 8 (/cluebase-events), not the lifecycle step — remove any cluebase.track added here.
543
+ - .cluebase/setup-check.json reports non-passed checks not yet addressed.
544
+ - Discovery \`unclear_points\` not resolved or escalated to the user.
545
+ - Lifecycle insertion count in the diff mismatches the corresponding discoveries array length (count(\`cluebase.identify(\`) vs identify_sites.length, etc.). If STEP 5 reported an explicit skip reason that is sound (e.g. boundary was a read path), treat that mismatch as informational.
546
+ - \`ai_provider_sites\` is non-empty in discoveries but the diff has zero OTel-primary AI helper insertions, OR an entry's file lacks the \`instrument_openai\` / \`instrument_anthropic\` call matching the recorded \`kind\`. Wire it up per the "AI provider OpenTelemetry / OpenLLMetry / GenAI semantic instrumentation" pattern.
547
+ - \`mcp_server_sites\` is non-empty in discoveries but the diff has zero OTel-primary MCP helper insertions, OR an entry's file lacks the \`anthropic_instrument\` call referencing the recorded \`server_variable_name\`. Wire it up per the "MCP server OpenTelemetry instrumentation" pattern.
548
+ - \`langchain_sites\` is non-empty in discoveries but the diff has zero \`instrument_langchain\` (Python) / \`instrumentLangChain\` (Node) insertions. Wire it up per the "LangChain OpenTelemetry / GenAI semantic instrumentation" pattern. (Note: at most one call per module is expected; a count mismatch where module-count of wire-ups equals the unique-module-count of \`langchain_sites\` is informational, not P1.)
549
+ - \`llamaindex_sites\` is non-empty in discoveries but the diff has zero \`instrument_llamaindex\` insertions. Wire it up per the "LlamaIndex OpenTelemetry / GenAI semantic instrumentation" pattern. (Same one-per-module rule as LangChain.)
550
+ - \`crewai_sites\` is non-empty in discoveries but the diff has zero \`instrument_crewai\` insertions, OR an entry's file lacks the \`instrument_crewai\` call referencing the recorded \`crew_variable_name\`. Wire it up per the "CrewAI OpenTelemetry / GenAI semantic instrumentation" pattern.
551
+ - \`mastra_sites\` is non-empty in discoveries but the diff has zero \`instrumentMastra\` insertions, OR an entry's file lacks the \`instrumentMastra\` call referencing the recorded \`mastra_variable_name\`. Wire it up per the "Mastra OpenTelemetry / GenAI semantic instrumentation" pattern.
552
+ - \`cluebase_init_backend.evidence_snippet\` identifies a Node backend (Express / NestJS / Fastify / Hono — \`import express\` / \`NestFactory\` / \`fastify\` / \`Hono\`) but the diff has zero \`cluebaseExpressMiddleware\` attachments. Wire it up per the "Implementation patterns (Express / NestJS / Fastify / Hono — Node backend)" pattern.
553
+ - \`cluebase_init_backend.evidence_snippet\` identifies Django (\`from django.apps import AppConfig\` / \`MIDDLEWARE\` / \`INSTALLED_APPS\`) but the diff has zero \`cluebase.init\` calls. Wire it up per the "Implementation patterns (Django)" pattern.
554
+
555
+ Final output (verbatim — choose EXACTLY ONE of the two Japanese blocks below based on the loop outcome):
556
+
557
+ [Case A — All clean after N iterations, M total auto-fixes]
558
+ First, use the Write tool to save \`.cluebase/setup-review-findings.md\` with the single line:
559
+ \`NO_P0_P1: <one-line rationale of what was checked>\`
560
+
561
+ Then respond with exactly:
562
+ ==========================================
563
+ STEP 7(セルフレビュー + 自動修正)完了
564
+ ==========================================
565
+ 問題なし (合計 <M> 件を自動修正、<N> 回のレビュー)
566
+
567
+ .cluebase/setup-review-findings.md に NO_P0_P1 を保存しました。
568
+
569
+ 次は Claude Code で /cluebase-events と打って STEP 8(重要業務イベントの計装)を実行してください。
570
+ ==========================================
571
+
572
+ [Case B — Residual findings after 3 iterations]
573
+ First, use the Write tool to save \`.cluebase/setup-review-findings.md\` with the residual markdown table (technical detail for developers):
574
+ | Severity | File:Line | Evidence | Suggested fix | User action (jp) |
575
+ | --- | --- | --- | --- | --- |
576
+ | P0 or P1 | path:lineNumber | <code snippet> | <minimal edit> | <plain-Japanese action the customer can take WITHOUT reading code> |
577
+
578
+ For the "User action (jp)" column, translate each finding into a concrete plain-Japanese instruction the customer can execute or paste to a developer. Use this catalogue:
579
+ - lockfile / manifest mismatch (package manager available) → \`ターミナルで package manager の install コマンドを実行してください。例: bun install / pnpm install / yarn install / npm install (JS), pip install -r requirements.txt / uv sync / poetry install (Python), bundle install (Ruby), go mod tidy (Go), cargo build (Rust), mvn install -DskipTests / ./gradlew build (Java), dotnet restore (.NET), composer install (PHP), mix deps.get (Elixir) — あなたの repo の lockfile に合うものを 1 つ選んでください\`
580
+ - lockfile / manifest mismatch (package manager missing on host) → DEMOTE per the rule above; do NOT include in residuals
581
+ - editor auto-format reformatting unrelated lines → DEMOTE per the rule above; do NOT include
582
+ - OAuth / magic-link / callback-route identity gap → \`OAuth / magic-link 経由のログイン完了を捕捉するために、callback ページに cluebase.identify(user.id) を 1 行追加する必要があります。開発者に「.cluebase/setup-review-findings.md の該当行」を共有してください。\`
583
+ - hardcoded id detected → \`設定の見直しが必要です。開発者に「.cluebase/setup-review-findings.md の該当行」を共有してください。\`
584
+ - any other unrecognized P0/P1 → \`.cluebase/setup-review-findings.md の該当行を開発者に共有してください。\`
585
+
586
+ Then respond in the chat with exactly this Japanese block (do NOT mention ".cluebase/setup-review-findings.md を確認して" — extract user-action lines from it and inline them here so the customer never opens the file themselves):
587
+
588
+ ==========================================
589
+ STEP 7(セルフレビュー + 自動修正): <N> 件、対応が必要です
590
+ ==========================================
591
+ 合計 <M> 件を自動修正しました。残り <N> 件は以下のアクションで解決してください:
592
+
593
+ <For each residual entry in .cluebase/setup-review-findings.md, print one line in this exact form (numbered, one per row):>
594
+ <N>. <User action (jp) text>
595
+
596
+ すべて完了したら、もう一度 Claude Code で /cluebase-implement と打てば再評価されます。 残り findings を解決後に強制再評価したい場合は .cluebase/discoveries.json:_progress の step7_review を Edit で外してください。
597
+ (技術的詳細は .cluebase/setup-review-findings.md にあります — 開発者に共有が必要な場合のみ参照)
598
+ ==========================================
599
+
600
+ Hard rules:
601
+ - DO NOT modify \`.cluebase/discoveries.json\`.
602
+ - DO NOT use the Write tool on any existing source file (Edit only). Write is allowed only for \`.cluebase/setup-review-findings.md\`.
603
+ - DO NOT run formatters / linters / import sorters.
604
+ - Bash tool may invoke ONLY: \`setup-check --write-snapshot\`, \`git checkout HEAD -- <file>\`, \`git diff HEAD\`. No other commands.
605
+
606
+ Reference docs: ${documentsUrl}`;
607
+ }
608
+
609
+ // --- STEP 9: setup-doctor (Bash) ----------------------------------------
610
+
611
+ export function buildStep9SetupDoctor() {
612
+ return `You are running STEP 9 of the Cluebase setup flow (final connectivity check).
613
+
614
+ Before running the command, briefly explain to the user (1-2 lines) what this step does:
615
+
616
+ STEP 9 は Cluebase の疎通確認です。 Cluebase token 発行 / browser ingest / backend ingest を customer env files から作った SDK-equivalent payload で実際に POST し、downstream batch publish evidence まで検証します。 加えて顧客 backend の \`/api/v1/cluebase/*\` route を blocking error として確認します。実際の顧客 frontend/backend lifecycle 発火は、顧客のローカル画面操作後に Cluebase setup logs または published batch evidence で確認します。
617
+
618
+ Then run the following command via the Bash tool:
619
+
620
+ \`\`\`bash
621
+ ${CLUEBASE_CLI_INVOCATION} setup-doctor --local
622
+ \`\`\`
623
+
624
+ After the command finishes:
625
+
626
+ [If \`passed: true\`:]
627
+ Respond with exactly:
628
+
629
+ Cluebase の疎通確認が通りました。 token issue / browser ingest / backend ingest probe と customer backend route scan は green です。
630
+ 実際の画面操作による event 配信確認は、顧客 frontend/backend をローカルで操作し、Cluebase setup logs または published batch evidence で確認してください。
631
+
632
+ [If \`passed: false\` or the command exits non-zero:]
633
+ 1. Show the user the errors and failed stages from the JSON output.
634
+ 2. Group the failures by root cause. For each check.id with passed=false, classify into one bucket using this priority order (first match wins per check; do NOT duplicate the same env into both frontend and backend buckets):
635
+
636
+ Bucket FRONTEND_ORIGIN_MISSING — when check.error mentions "client-frontend-url" on the \`cluebase_backend_browser_token_issue\` check.
637
+ • Required value: the local frontend Origin, e.g. \`http://localhost:<frontend-port>\`.
638
+ • If the local frontend Origin is not available automatically, re-run \`${CLUEBASE_CLI_INVOCATION} setup-doctor --local --client-frontend-url http://localhost:<frontend-port>\`.
639
+
640
+ Bucket FRONTEND_ENV_MISSING — when check.error mentions "CLUEBASE_API_BASE_URL" or "CLUEBASE_PROJECT_KEY" on the \`browser_ingest\` check.
641
+ • Required env (set in frontend env file, e.g. \`.env.local\` for Next.js / \`.env\` for Vite, prefix the variable with the framework's public-env prefix):
642
+ <frontend public prefix>CLUEBASE_API_BASE_URL=<from setup screen Step 2>
643
+ <frontend public prefix>CLUEBASE_PROJECT_KEY=<from setup screen Step 2>
644
+ • After writing, RESTART the frontend dev server.
645
+
646
+ Bucket BACKEND_ENV_MISSING — when check.error mentions "CLUEBASE_API_KEY", "CLUEBASE_INGEST_ENDPOINT", or "CLUEBASE_PROJECT_KEY" on the \`backend_ingest\` check.
647
+ • Required env (set in backend env file, e.g. \`.env\` for FastAPI / Django / Express):
648
+ CLUEBASE_API_KEY=<from setup screen Step 2>
649
+ CLUEBASE_PROJECT_KEY=<from setup screen Step 2>
650
+ CLUEBASE_INGEST_ENDPOINT=<from setup screen Step 2>
651
+ • After writing, RESTART the backend dev server.
652
+
653
+ Bucket UPSTREAM_FAILURE — when check.error mentions "browser token" on the \`browser_ingest\` check (= caused by FRONTEND_ENV_MISSING; do not bucket separately).
654
+
655
+ Bucket LEDGER_NOT_REACHED — when check.id is \`browser_ingest_reached_ledger\` or \`backend_ingest_reached_ledger\` AND passed=false. This means Cluebase ingest returned 201 but the batch did not reach downstream batch publish evidence within the polling window. There are 3 sub-cases distinguished by \`check.observed_status\`:
656
+ • \`observed_status: 'failed'\` — worker normalize threw an error. The \`check.error\` / \`check.remediation_prompt\` fields explain why. Most common cause is an outdated Cluebase API (= event_id missing in the raw Kafka envelope), in which case the Cluebase API must be rebuilt + restarted by the Cluebase maintainer (NOT the customer). DO NOT propose customer-side env fixes for this sub-case.
657
+ • \`observed_status: 'not_found_yet'\` — Cluebase API received 201, but worker publish evidence did not appear within the polling window. Most common cause is that the worker or Kafka pipeline is not running. Instruct the customer to verify Cluebase API + Kafka + worker are up and re-run /cluebase-doctor; do NOT auto-fix env values.
658
+ • other / unknown — likely an auth / route issue against batch-status endpoint. Same env recovery as BACKEND_ENV_MISSING.
659
+ For all sub-cases above, the doctor JSON includes a \`remediation_prompt\` string per check — show that prompt to the user verbatim (Japanese) as the recovery instructions.
660
+
661
+ Bucket TRANSPORT_FAILURE — when check.status is a number ≥ 400 or when check.error is a connection-level message (ECONNREFUSED, timeout, 5xx). This means the env values are present but the targeted server is down / unreachable.
662
+
663
+ 3. AUTO-FIX (offer once before falling back to manual instructions):
664
+
665
+ For each missing env name in FRONTEND_ENV_MISSING / BACKEND_ENV_MISSING buckets, resolve the value:
666
+ • project key / API base URL / ingest endpoint → use the setup screen Step 2 value already written to the target env file, or the setup artifact value produced from the same screen when the env file is missing the line.
667
+ • CLUEBASE_API_KEY → use the setup screen Step 2 value already written to the backend env file, or \`.cluebase/secrets.json\` when the backend env file is missing the line. If neither source exists, instruct the user to copy CLUEBASE_API_KEY from the Cluebase setup screen into the backend env file; do NOT proceed to auto-fix CLUEBASE_API_KEY without a source.
668
+
669
+ For each candidate env value that resolves to a concrete string, propose the auto-fix as ONE message to the user in the form:
670
+
671
+ STEP 9 で env 不足を検出しました。 以下を自動で書き込みますか?
672
+ frontend env file (<path>): <list of NAME=value lines>
673
+ backend env file (<path>): <list of NAME=value lines>
674
+ 書き込み後、 frontend / backend dev server を再起動して /cluebase-doctor を再実行してください。
675
+ OK の場合: 「OK」 と返信してください。 NG の場合: 「NG」 と返信してください。
676
+
677
+ When the user replies "OK":
678
+ - Use Edit tool to append the lines to the frontend / backend env file (upsert semantics: if a NAME already exists with a different value, replace its value; if NAME does not exist, append a new line).
679
+ - Use Edit / Bash tool to do nothing else. Do NOT restart the dev server (the user controls dev servers).
680
+ - Respond with:
681
+ env file への書き込みが完了しました。 frontend / backend dev server を再起動してから、 もう一度 /cluebase-doctor を実行してください。 dev server を再起動しないと新 env が反映されません。
682
+
683
+ When the user replies "NG" or you could not resolve all values:
684
+ - Skip the auto-fix and fall through to manual instructions (step 4).
685
+
686
+ 4. MANUAL INSTRUCTIONS (when auto-fix is declined or unavailable):
687
+
688
+ Respond with exactly this Japanese block (one block, no extra commentary):
689
+
690
+ STEP 9(疎通確認)で問題が見つかりました(checks N_FAILED 件 / passed N_PASSED 件)。 以下を順番に直してください:
691
+
692
+ [For each non-empty bucket, print one numbered line in this exact form:]
693
+ <N>. <bucket-specific human-readable instruction in Japanese — e.g. "frontend の env file (.env.local 等) に framework に合う public env 名で CLUEBASE_API_BASE_URL=<setup screen Step 2 の値> を追加し、 frontend dev server を再起動してください">
694
+
695
+ 修正後にもう一度 /cluebase-doctor を実行すると再チェックされます。 setup 画面の Step 2 (環境変数) の値もコピー元として使えます。
696
+
697
+ Hard rules:
698
+ - Do not edit \`.cluebase/setup-manifest.json\` or \`.cluebase/secrets.json\` in this STEP — STEP 9 is verification + (optional) env-file auto-fix only.
699
+ - Never write CLUEBASE_API_KEY into browser-public frontend SDK configuration. The value is only for server runtime and the authenticated Cluebase setup screen.
700
+ - Never restart dev servers from this STEP. Always ask the user to restart manually after auto-fix.
701
+ - TRANSPORT_FAILURE buckets are NOT auto-fixable — instruct the user to start the affected dev server / verify the URL and re-run /cluebase-doctor.
702
+ - LEDGER_NOT_REACHED buckets must show the per-check \`remediation_prompt\` string verbatim. The doctor JSON already contains the exact diagnostic + recovery steps in Japanese for each sub-case (failed / not_found_yet / unknown).
703
+ - For ANY failed check that includes a \`remediation_prompt\` field, show that string verbatim (Japanese) as the recovery instruction, and let it take precedence over the generic bucket instruction. A 401 "invalid project key" on the browser-tokens / batch-status hops carries a \`remediation_prompt\` that means the \`.cluebase/setup-manifest.json\` project_key is stale (re-provisioned key), NOT that the endpoint is down — do NOT bucket it as TRANSPORT_FAILURE ("server is down"); follow the remediation_prompt (re-fetch the current key from the setup screen or re-run /cluebase-discover).
704
+
705
+ Self-healing principle (= 顧客 AI Coding Tool 役の責務):
706
+ - Your job is NOT just to report failures — it is to drive the customer through the smallest possible loop until all hops pass.
707
+ - After every fix you propose (auto-fix or manual), state in Japanese exactly: "修正後、 frontend / backend dev server を再起動してから /cluebase-doctor をもう一度実行してください。" so the customer knows the next action.
708
+ - If the same hop fails twice in a row with the SAME error after a recovery attempt, surface the full \`check.error\` + \`check.remediation_prompt\` and ask the customer to share the full doctor JSON with Cluebase support (= dial-back to manual escalation).
709
+ - For LEDGER_NOT_REACHED with \`observed_status: 'failed'\` (= Cluebase 側 bug の可能性), do NOT loop the customer through env fixes — instead surface the remediation prompt and explicitly note that the customer's env is correct; the Cluebase API maintainer needs to act.`;
710
+ }