@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.
- package/README.md +101 -0
- package/bin/cluebase-cli.mjs +11 -0
- package/package.json +17 -0
- package/src/cli-command.mjs +515 -0
- package/src/cli-invocation.mjs +17 -0
- package/src/code-evidence-analyzer.mjs +2041 -0
- package/src/contracts.mjs +36 -0
- package/src/generated-code-evidence-contract.mjs +22 -0
- package/src/generated-sdk-version-contract.mjs +5 -0
- package/src/generated-source-path-policy.mjs +20 -0
- package/src/lifecycle-guard.mjs +202 -0
- package/src/path-policy.mjs +81 -0
- package/src/setup-ai-contract.mjs +221 -0
- package/src/setup-check-constants.mjs +110 -0
- package/src/setup-check-scan-a.mjs +849 -0
- package/src/setup-check-scan-b.mjs +994 -0
- package/src/setup-check.mjs +575 -0
- package/src/setup-discover-check.mjs +755 -0
- package/src/setup-doctor-deadline.mjs +221 -0
- package/src/setup-doctor-env.mjs +331 -0
- package/src/setup-doctor-file-boundary.mjs +426 -0
- package/src/setup-doctor-probe.mjs +719 -0
- package/src/setup-doctor-quality-checks-a.mjs +593 -0
- package/src/setup-doctor-quality-checks-b.mjs +638 -0
- package/src/setup-doctor-quality-shared.mjs +382 -0
- package/src/setup-doctor-quality.mjs +209 -0
- package/src/setup-doctor-route-scan.mjs +160 -0
- package/src/setup-doctor-sdk-probe.mjs +340 -0
- package/src/setup-doctor.mjs +545 -0
- package/src/setup-documents.mjs +112 -0
- package/src/setup-help.mjs +130 -0
- package/src/setup-prepare.mjs +360 -0
- package/src/setup-repository-discovery.mjs +764 -0
- package/src/setup-step-builders-discover.mjs +701 -0
- package/src/setup-step-builders-events.mjs +229 -0
- package/src/setup-step-builders-implement.mjs +710 -0
- package/src/setup-step-commands.mjs +427 -0
- package/src/setup-tool.mjs +27 -0
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
import { CLUEBASE_CLI_RECOMMENDED_PREFIX as CLUEBASE_CLI_INVOCATION } from "./cli-invocation.mjs";
|
|
2
|
+
|
|
3
|
+
// STEP 1-4 discover builders (boundary enumeration, self-review, field
|
|
4
|
+
// semantic enrichment, bash validation). Extracted from setup-step-commands
|
|
5
|
+
// for file-size limits; content is unchanged. Framing lives in
|
|
6
|
+
// setup-step-commands.mjs.
|
|
7
|
+
|
|
8
|
+
export function buildStep1Discover({ documentsUrl }) {
|
|
9
|
+
return `You are running inside an AI coding tool. This is STEP 1 of the Cluebase setup flow.
|
|
10
|
+
|
|
11
|
+
Goal (STEP 1 = Discover):
|
|
12
|
+
Discover every Cluebase lifecycle boundary in this repository and save the result as \`.cluebase/discoveries.json\` at the repository root. Do not modify any production code — only the \`discoveries.json\` artifact may be written.
|
|
13
|
+
|
|
14
|
+
Steps:
|
|
15
|
+
1. Read .cluebase/setup-manifest.json. Choose framework_frontend / framework_backend like this: \`documentation.selected_frameworks\` (array) is the source of truth for mixed-stack repos — pick the first frontend-like entry (nextjs, react, vue, vite, angular, sveltekit, nuxt) for framework_frontend and the first backend-like entry (Python: fastapi, django, flask; Node: express, nestjs, fastify, koa, hono) for framework_backend. \`detected.framework\` is a single string that may name either side; only fall back to it when \`selected_frameworks\` is absent or empty.
|
|
16
|
+
|
|
17
|
+
1.5. DB SCHEMA GROUNDING (MANDATORY — run BEFORE Phase A). Before any literal-pattern grep for lifecycle boundaries, ground yourself in the customer's data model so STEP 3 can pick semantic field paths from real entities (not from guessed conventions).
|
|
18
|
+
|
|
19
|
+
Why: STEP 3 (field detection) must judge "what is a name field", "what is an email field", "what is an organization id" by SEMANTIC CONCEPT + ACTUAL ENTITY SHAPE, not by a hardcoded list of column names. AI is being used precisely so non-English / industry-specific / arbitrarily named fields (e.g. \`namae\`, \`ユーザー名\`, \`handle\`, \`screen_name\`, \`kanji_name\`) are still recognized. The DB schema is the grounded source of truth for what fields actually exist on the customer's User / Account / Member / Workspace entities. Skipping this step forces STEP 3 to guess.
|
|
20
|
+
|
|
21
|
+
(a) Search the repo for DB schema definitions. Run each Glob / Read that applies — DO NOT short-circuit because one match was found, because customers often combine multiple ORMs / migrations / schema languages:
|
|
22
|
+
* Prisma: \`prisma/schema.prisma\`, \`**/schema.prisma\`
|
|
23
|
+
* Drizzle: \`db/schema.ts\`, \`src/db/schema/*.ts\`, \`**/drizzle/schema*.ts\`
|
|
24
|
+
* TypeORM: \`**/entities/*.entity.ts\`, \`**/entity/*.ts\` (look for \`@Entity()\` decorator)
|
|
25
|
+
* Sequelize: \`**/models/*.{js,ts}\` (look for \`sequelize.define\` / \`DataTypes\`)
|
|
26
|
+
* Django: \`**/models.py\` (look for \`class X(models.Model)\`)
|
|
27
|
+
* SQLAlchemy: \`**/models.py\`, \`**/models/*.py\` (look for \`Base = declarative_base()\` or \`Mapped[...]\`)
|
|
28
|
+
* Raw SQL migrations: \`migrations/*.sql\`, \`db/migrations/*.sql\`, \`alembic/versions/*.py\`
|
|
29
|
+
* Knex / Kysely: \`migrations/*.js\`, \`migrations/*.ts\`
|
|
30
|
+
* GraphQL schema: \`schema.graphql\`, \`**/*.graphqls\`, \`**/*.gql\`
|
|
31
|
+
* OpenAPI: \`openapi.yaml\`, \`openapi.json\`, \`**/openapi/*.yaml\`
|
|
32
|
+
* Zod / Valibot user model: \`**/schemas/user*.ts\`, \`**/schemas/account*.ts\`, \`**/validators/*.ts\`
|
|
33
|
+
* Mongoose: \`**/models/*.{js,ts}\` (look for \`mongoose.Schema\` / \`mongoose.model\`)
|
|
34
|
+
* Pydantic v1 / v2: any \`class X(BaseModel)\` in models / schemas dirs
|
|
35
|
+
* If none of the above match: search for files whose path or filename contains \`user\`, \`account\`, \`member\`, \`workspace\`, \`organization\`, \`tenant\`, \`team\` and inspect.
|
|
36
|
+
|
|
37
|
+
(b) For each schema file found, use the Read tool and identify entities that CONCEPTUALLY represent:
|
|
38
|
+
* a HUMAN (= a person who logs in — typical labels: User, Member, Person, Account, Profile, Customer, Operator, Staff)
|
|
39
|
+
* a BILLING / TOP-LEVEL CONTAINER (= the unit invoices are issued against — typical labels: Account, Organization, Tenant, Company, Customer)
|
|
40
|
+
* a WORKING SUB-CONTAINER (= the unit work happens inside — typical labels: Workspace, Project, Team, Group, Space, Channel)
|
|
41
|
+
|
|
42
|
+
Label matching is a HINT, not a rule. Inspect the entity's actual fields and foreign-key relations to confirm what it represents. A class named \`Account\` may semantically be a User in some products and a billing tenant in others — let the field shape decide (\`email\` / \`password_hash\` → User; \`plan\` / \`billing_email\` / FK from User → top-level container).
|
|
43
|
+
|
|
44
|
+
(c) For every entity you classified, extract its full field list with: column name, type (when available), comment / description (when available), and any FK relation. Record this on the top-level \`db_schema\` field of the output JSON in the shape described under "Output JSON schema" below.
|
|
45
|
+
|
|
46
|
+
(d) Halt / unknown handling:
|
|
47
|
+
* If you find zero schema files matching any pattern in (a), DO NOT halt. Record \`db_schema.entities = []\` and \`db_schema.detection_notes\` with a one-line explanation ("No schema definition files matched; STEP 3 will fall back to in-scope variable inspection only").
|
|
48
|
+
* If multiple schema sources exist (e.g. Prisma + raw SQL migrations), merge them by entity-concept and keep both \`source_files\` for cross-reference.
|
|
49
|
+
* If you cannot confidently classify an entity, leave it OUT of \`db_schema.entities\` and add an \`unclear_points\` note. DO NOT invent entities.
|
|
50
|
+
|
|
51
|
+
(e) HARD RULE: \`db_schema\` is OBSERVATIONAL grounding only. Do not modify any schema file, do not infer fields that are not literally present, and do not "normalize" naming. If a Japanese column \`namae\` exists, record it as \`namae\` — STEP 3 will recognize it semantically.
|
|
52
|
+
|
|
53
|
+
2. MANDATORY TWO-PHASE EXPLORATION (do NOT short-circuit to judgment after a handful of file reads — the customer's service is the source of truth and lifecycle gaps directly cause STEP 5 failures).
|
|
54
|
+
|
|
55
|
+
You MUST complete Phase A (Enumerate) and Phase B (Read) in full BEFORE making any classification decision. Do not write \`.cluebase/discoveries.json\` until both phases finish and the self-check in Step 5 passes.
|
|
56
|
+
|
|
57
|
+
============================================================
|
|
58
|
+
Phase A — EXHAUSTIVE CANDIDATE ENUMERATION (= grep everything that could be a boundary, do NOT classify yet)
|
|
59
|
+
============================================================
|
|
60
|
+
Build four internal candidate lists (frontend_candidates / backend_candidates / observer_candidates / sentinel_locations) by running EVERY query below that applies to this repo's frameworks. Each query result must be recorded as \`[{file, line, snippet}]\`. Do NOT skip a query because you think a previous one already covered it — overlap is expected and safe; missing a query is the failure mode.
|
|
61
|
+
|
|
62
|
+
Frontend (run all that apply based on \`framework_frontend\` and the languages actually present):
|
|
63
|
+
A1. Glob \`**/*.{ts,tsx,js,jsx,vue,svelte}\` + Grep \`useMutation\\b\` (TanStack Query / Vue Query mutations — likely identify / group / reset candidates).
|
|
64
|
+
A2. Glob \`**/*.{ts,tsx,js,jsx,vue,svelte}\` + Grep \`onSuccess\\s*[:=]|onCompleted\\s*[:=]|\\.then\\(\` (callback-style success branches).
|
|
65
|
+
A3. Grep \`signIn|signUp|signOut|signin|signup|signout|login|logout|register|verify|exchange|callback|onAuthStateChange|onAuthStateChanged|auth\\.(signIn|signUp|signOut|user)\` across the frontend source tree (covers Supabase / Firebase / Auth0 / Clerk / NextAuth / Cognito patterns).
|
|
66
|
+
A4. Grep \`createWorkspace|joinWorkspace|switchWorkspace|setActiveWorkspace|createOrganization|joinOrganization|switchOrganization|createTeam|joinTeam|setCurrentOrg|setActiveOrg|setActiveTeam|selectTenant|selectProject|activeWorkspace|currentWorkspace|selectedWorkspace|activeOrganization|currentOrganization|selectedOrganization|activeTenant|currentTenant\` across the frontend source tree.
|
|
67
|
+
A5. Glob \`**/app/auth/**/*.{ts,tsx}\`, \`**/pages/auth/**/*.{ts,tsx}\`, \`**/auth/**/route.{ts,js}\`, \`**/api/auth/**/*\`, \`**/middleware.{ts,js}\` for App Router / Pages Router auth handlers.
|
|
68
|
+
A6. Grep \`createAsyncThunk\` / \`\\.fulfilled\\b\` / store action names containing the verbs from A3-A4 (Redux Toolkit / Zustand / Jotai / MobX writes).
|
|
69
|
+
A7. Grep \`<form\\b|onSubmit\\s*=|action=\` + \`fetch\\(|axios\\.(post|put|delete|patch)|ky\\.(post|put|delete|patch)|\\$fetch\\(.*method:\\s*['"](POST|PUT|DELETE|PATCH)\` for raw form / HTTP write paths.
|
|
70
|
+
A8. Framework-specific paths: SvelteKit \`+page.server.ts\` (\`export const actions =\`); Nuxt \`server/api/**/*\`; Angular \`*.service.ts\` with \`Observable\` + \`.subscribe({ next: ... })\`.
|
|
71
|
+
A9. Sentinel detection: Grep \`=== "__\` / \`=== "new"\` / \`=== "create"\` / \`=== "draft"\` / \`=== "placeholder"\` / \`=== ""\` / \`== null\` / \`!user\` / \`!user\\.id\` / \`!authenticated\` / \`isLoading\` / \`status !== "success"\` in the frontend source. Record the file:line of EVERY guard you find — Step 4 (PLACEMENT RULE) will need these.
|
|
72
|
+
|
|
73
|
+
Backend (run all that apply based on \`framework_backend\`):
|
|
74
|
+
B1. Grep \`@router\\.(post|put|delete|patch)|@app\\.(post|put|delete|patch)\` (FastAPI). \`@(app|blueprint)\\.route\\(.*methods=\\[?["'](POST|PUT|DELETE|PATCH)\` (Flask). \`@api_view\\(\\[?["'](POST|PUT|DELETE|PATCH)\` (DRF). \`(router|app)\\.(post|put|delete|patch)\\(\` (Express / Hono / Fastify). \`@(Post|Put|Delete|Patch)\\(\` (NestJS). \`@(Post|Put|Delete|Patch)Mapping\` (Spring). \`\\[Http(Post|Put|Delete|Patch)\\]\` (ASP.NET). \`(\\.POST|\\.PUT|\\.DELETE|\\.PATCH)\\(\` (Go gin / echo / fiber). \`def (create|update|destroy)\\b\` (Rails).
|
|
75
|
+
B2. Grep \`/auth/|/login|/logout|/signup|/register|/verify|/exchange|/callback|/sign-?in|/sign-?out|/sign-?up\` across the backend source tree (string-literal route paths).
|
|
76
|
+
B3. Grep \`workspaces?|organizations?|tenants?|projects?|companies|teams|tenants?|orgs|accounts\` across the backend source tree (account-context route candidates — these will need to be classified, expect noise).
|
|
77
|
+
B4. Grep \`def\\s+\\w*(signin|signup|signout|login|logout|register|verify|exchange|callback)\\w*\\(\` for direct handler function names.
|
|
78
|
+
|
|
79
|
+
Observers / AI / agent frameworks (run unconditionally — these arrays may be empty but the grep MUST be performed so the empty result is grounded, not assumed):
|
|
80
|
+
O1. Grep \`from openai import|import OpenAI\\b|new OpenAI\\(|openai\\.OpenAI\\(\` / \`from anthropic import|new Anthropic\\(|Anthropic\\(\` / \`AzureOpenAI\\b\`.
|
|
81
|
+
O2. Grep \`from mcp\\.server import|new Server\\(.*Server\\)|@modelcontextprotocol/sdk\`.
|
|
82
|
+
O3. Grep \`from langchain|from langchain_core|from langchain_openai|from langchain_anthropic|@langchain/openai|@langchain/anthropic|LLMChain\\(|AgentExecutor\\(|create_react_agent\\(\`.
|
|
83
|
+
O4. Grep \`from llama_index|from llama_index\\.core|VectorStoreIndex\\(|Settings\\.llm\\s*=\`.
|
|
84
|
+
O5. Grep \`from crewai import|Crew\\(\` and \`from @mastra/core|new Mastra\\(\`.
|
|
85
|
+
|
|
86
|
+
After Phase A: you should have a concrete count for each candidate list (e.g. "frontend_candidates: 28 files, backend_candidates: 14 files, observer_candidates: 0 files, sentinel_locations: 9"). Track these counts internally — Step 5 self-check will verify each candidate was actually read.
|
|
87
|
+
|
|
88
|
+
============================================================
|
|
89
|
+
Phase B — EXHAUSTIVE READ (= open every candidate file with Read, classify only after seeing the actual code)
|
|
90
|
+
============================================================
|
|
91
|
+
For every file in every candidate list, you MUST use the Read tool to open it BEFORE deciding whether a lifecycle boundary exists there. Reasoning about a file from its filename or grep snippet alone is NOT allowed.
|
|
92
|
+
|
|
93
|
+
For each Read result:
|
|
94
|
+
- Locate the exact write-side success line using the snippet + surrounding context.
|
|
95
|
+
- Apply the 5-way classification ("Lifecycle boundary definitions" below).
|
|
96
|
+
- If the file contains multiple distinct success branches, record EACH as a separate entry.
|
|
97
|
+
- If the surrounding scope has a sentinel / early-return guard, apply the PLACEMENT RULE (Step 4) immediately.
|
|
98
|
+
- If the file is large (\\>500 lines) and the relevant region is unclear, do NOT skip — use targeted Read with the right offset/limit, or run an extra Grep to pinpoint the line, then Read that window.
|
|
99
|
+
|
|
100
|
+
Token cost is not a valid reason to skip a Phase A candidate. The customer's STEP 5 implementation depends on EVERY boundary being found here.
|
|
101
|
+
4. PLACEMENT RULE (sentinel / early-return safety). For each entry whose surrounding scope contains early-return branches that filter values that SHOULD NOT trigger the lifecycle call, set \`line\` to AFTER all such guards.
|
|
102
|
+
General shape of the guard: \`if (<condition>) { return; }\` (or framework equivalent: Vue \`return\` from watcher, Angular early return in callback, Svelte \`$:\` block guard). The condition matches one of:
|
|
103
|
+
- Sentinel value check (any literal string indicating "not a real id"). Example shapes vary by codebase: \`x === "__create__"\`, \`x === "__new__"\`, \`x === "placeholder"\`, \`x === "draft"\`, \`x === "none"\`, \`x === ""\`, \`x === -1\`, \`x == null\`. Inspect the actual conditional — the literal varies.
|
|
104
|
+
- Falsy / null check: \`!user\`, \`!user.id\`, \`!authenticated\`, \`!value\`, \`isLoading\`, \`pending\`.
|
|
105
|
+
- Discriminator skip: \`status !== "success"\`, \`result.kind === "error"\`, \`type !== "real"\`.
|
|
106
|
+
The lifecycle call must NEVER fire with the filtered value. Pick the line of the next concrete side-effect after the guard (typical examples: \`router.push\`, \`router.refresh\`, \`navigate(\`, \`await api.(post|put|delete|patch)\`, \`db.commit\`, \`return response\`, \`dispatch(success)\`, \`emit("success", ...)\`).
|
|
107
|
+
|
|
108
|
+
4.5. ENV FILE PATH DISCOVER. STEP 5 (= /cluebase-implement) needs to auto-write CLUEBASE_* env vars into the customer's frontend and backend env files. Discover those file paths NOW so STEP 5 has the addresses to write into.
|
|
109
|
+
|
|
110
|
+
For each \`detected_services\` entry in \`.cluebase/setup-manifest.json\` (recall: each entry has \`kind\` = "frontend" or "backend", plus \`root_path\` = that service's directory relative to the repo root):
|
|
111
|
+
|
|
112
|
+
(a) Frontend env file: probe these candidates in order via Read / Glob and pick the FIRST one that exists:
|
|
113
|
+
1. \`<root_path>/.env.local\` (= Next.js / Vite convention for local dev env)
|
|
114
|
+
2. \`<root_path>/.env.development\`
|
|
115
|
+
3. \`<root_path>/.env\`
|
|
116
|
+
If none exists, default to \`<root_path>/.env.local\` and treat it as a new file that STEP 5 will create. Record this as \`env_files.frontend = { "path": "<rel path>", "format": "dotenv" }\`. If \`detected_services\` has no \`kind="frontend"\` entry (= no frontend service), set \`env_files.frontend = null\`.
|
|
117
|
+
|
|
118
|
+
(b) Backend env file: probe candidates in order:
|
|
119
|
+
1. \`<root_path>/.env\`
|
|
120
|
+
2. \`<root_path>/.env.development\`
|
|
121
|
+
If neither exists, default to \`<root_path>/.env\` as a new file. Record \`env_files.backend = { "path": "<rel path>", "format": "dotenv" }\`. If no backend service is detected, set \`env_files.backend = null\`.
|
|
122
|
+
|
|
123
|
+
(c) Validate paths:
|
|
124
|
+
* Path MUST be relative to the repo root (no absolute paths).
|
|
125
|
+
* Path MUST live inside the repo (no \`..\` escape).
|
|
126
|
+
* If a probed path is outside the repo or unreachable, add an entry to \`unclear_points\` describing the issue and set \`env_files.<kind> = null\`.
|
|
127
|
+
|
|
128
|
+
Output the result on the new top-level \`env_files\` field (see "Output JSON schema" below).
|
|
129
|
+
|
|
130
|
+
5. COMPLETENESS SELF-CHECK (mandatory — answer each item internally; any "no" / "未確認" means you MUST loop back to Phase A or B before writing the artifact):
|
|
131
|
+
|
|
132
|
+
(i) Did I enumerate frontend write-side success branches across ALL of: password login, sign-up, OAuth callback, magic-link verify, OTP / email-code verify, token exchange, third-party auth-state listener (Supabase onAuthStateChange / Firebase onAuthStateChanged / Auth0 / Clerk / NextAuth events), and any active organization/workspace/tenant context resolved in those same branches?
|
|
133
|
+
(ii) Did I enumerate backend write routes across ALL files matching \`auth(_route)?(s)?\\.(py|ts|js|rb|cs|kt|go)\` and the account-context routers (\`workspaces(_router)?\\.(py|ts|js|rb|...)\`, \`organizations\`, \`teams\`, \`tenants\`, \`projects\`)?
|
|
134
|
+
(iii) Did I enumerate every workspace / organization / team switcher (UI selector or dropdown onSelect / onChange), AND either assign it as a \`context_change\` group site when no active-context owner exists, or explicitly skip it because a guarded \`active_context_owner\` on the same surface will emit after the switch updates active state?
|
|
135
|
+
(iv) For every observer / agent grep that returned non-empty (O1–O5 in Phase A), did I open every result file with Read and confirm BOTH the import AND a runtime constructor exist?
|
|
136
|
+
(v) For every Phase A candidate file, did I actually Read it (not just rely on the grep snippet)? If I skipped any to save tokens, name them in \`unclear_points\` with a clear note "<file>: skipped Read in Phase B" rather than silently dropping them.
|
|
137
|
+
(vi) Are there any auth-related directories / route trees that returned 0 grep hits? If yes, that 0-hit result must be grounded (= I verified the directory is empty / the framework convention is not used), NOT assumed. If the 0-hit is unverified, record it in \`unclear_points\`.
|
|
138
|
+
|
|
139
|
+
If any answer is "no", loop back to Phase A or Phase B and finish that work BEFORE writing the JSON. Only continue to Step 6 (Write) once every check is honestly "yes" or recorded as unclear.
|
|
140
|
+
|
|
141
|
+
6. Use the Write tool to save the JSON object (only the \`{\` ... \`}\` content; nothing else) to \`.cluebase/discoveries.json\` at the repository root.
|
|
142
|
+
7. Respond with exactly ONE Japanese line and stop:
|
|
143
|
+
\`.cluebase/discoveries.json を保存しました。次は Claude Code で /cluebase-discover-review と打って STEP 2 を実行してください。\`
|
|
144
|
+
Do not print the JSON contents, the rubric, or any other prose — only the single line above.
|
|
145
|
+
|
|
146
|
+
Lifecycle boundary definitions (semantic-first; the framework-specific examples are non-exhaustive — adapt to the actual code patterns present in this repo):
|
|
147
|
+
|
|
148
|
+
== cluebase.init (frontend, EXACTLY 1 singleton) ==
|
|
149
|
+
Semantic: A single client-runtime boot point that runs once when the app loads in the browser. Must be in a real browser context (NOT in any server-side rendering or build-time evaluation path).
|
|
150
|
+
Examples (apply the one matching framework_frontend):
|
|
151
|
+
- Next.js App Router: client bootstrap module at \`src/lib/cluebase.ts\` with "use client", exporting a tiny \`CluebaseInit\` Client Component that is rendered once from \`app/layout.tsx\`.
|
|
152
|
+
- Next.js Pages Router: inside \`_app\` default export, useEffect-guarded.
|
|
153
|
+
- React (Vite / CRA): a small client-only module at \`src/lib/cluebase.ts\` imported from the entrypoint (\`main.tsx\` / \`index.tsx\`).
|
|
154
|
+
- Vue 3 (Vite): \`src/main.ts\` after \`createApp(...)\`, or a small \`src/lib/cluebase.ts\` imported once.
|
|
155
|
+
- Nuxt: \`plugins/cluebase.client.ts\` (the \`.client.ts\` suffix scopes it to the browser).
|
|
156
|
+
- SvelteKit: \`src/lib/cluebase.ts\` imported once from a top-level \`+layout.svelte\` inside a \`if (browser)\` guard.
|
|
157
|
+
- Angular: \`src/main.ts\` after \`bootstrapApplication\` resolves, or an \`APP_INITIALIZER\` factory.
|
|
158
|
+
- Solid / Qwik / Astro: entry module that runs only on the client.
|
|
159
|
+
|
|
160
|
+
== cluebase.init (backend, EXACTLY 1 singleton) ==
|
|
161
|
+
Semantic: A single server-runtime boot point that runs once when the HTTP server starts. Place it in the app factory / startup hook.
|
|
162
|
+
Examples:
|
|
163
|
+
- FastAPI: where \`FastAPI(...)\` is instantiated (typically \`app/main.py\`).
|
|
164
|
+
- Flask: \`create_app()\` factory.
|
|
165
|
+
- Django: \`apps.py:ready()\` or \`settings.py\` startup.
|
|
166
|
+
- Express / Hono / Fastify / NestJS: app construction file.
|
|
167
|
+
- Rails: \`config/initializers/cluebase.rb\`.
|
|
168
|
+
- Spring Boot: a \`@Configuration\` class with \`@PostConstruct\`, or main \`@SpringBootApplication\`.
|
|
169
|
+
- ASP.NET Core: \`Program.cs\` after \`WebApplication.CreateBuilder\`.
|
|
170
|
+
- Phoenix (Elixir): \`application.ex\` start callback.
|
|
171
|
+
- Go: \`main.go\` after router init.
|
|
172
|
+
|
|
173
|
+
== cluebase.identify (MULTIPLE INSTANCES NORMAL) ==
|
|
174
|
+
Semantic: Any code path where the user's identity is established for the FIRST time in that session (or re-established after a session change). The boundary is the SUCCESS branch of a write-side operation, NOT a read of an already-known user.
|
|
175
|
+
Triggers — add ONE entry per distinct code path that matches any of:
|
|
176
|
+
- Credential login success (email/password, username/password).
|
|
177
|
+
- Sign-up + auto-login success (a single flow that BOTH registers AND establishes the session — use the outer onSuccess, do not double-count upstream SDK calls).
|
|
178
|
+
- OAuth / SSO provider callback success (Google, GitHub, Microsoft, SAML, etc.) — frontend if client-side, backend otherwise.
|
|
179
|
+
- Magic-link verify success / OTP verify success / email-code verify success.
|
|
180
|
+
- Token exchange route that ALSO authenticates (not pure rotation). Pure refresh-token endpoints that only rotate tokens do NOT qualify.
|
|
181
|
+
- Third-party auth SDK \`onSignIn\` / \`events.signIn\` callback (NextAuth, Clerk, Auth0, Supabase auth listener, Firebase \`onAuthStateChanged\` IF and only if used in a singleton-guarded way).
|
|
182
|
+
NOT a boundary: reading current user (\`useUser()\`, \`useSession()\`, \`GET /me\`), session refresh that only rotates tokens, render-time component body, initiation-only mutations that REDIRECT to the provider without completing auth (e.g. "send OAuth redirect", "send magic-link email") — those belong in \`unclear_points\` only if borderline.
|
|
183
|
+
|
|
184
|
+
== cluebase.group (OWNER-BASED; DEDUPE REQUIRED) ==
|
|
185
|
+
Semantic: \`cluebase.group("organization", ...)\` records the user's current active company / organization context. The exact customer label varies by product: workspace / organization / team / tenant / project / space / channel / company / client / property / shop / brand / studio — treat ALL such terms as candidates for the Cluebase organization concept.
|
|
186
|
+
|
|
187
|
+
Before adding \`group_sites\`, decide the owner of organization association emission for each runtime surface (frontend and backend separately). Each \`group_sites\` entry MUST carry \`group_owner_kind\`:
|
|
188
|
+
- \`"initial_context"\`: login / sign-up / auth callback / token-exchange success returns the initial active organization for the authenticated user. Record this even when the same file is already an identify site; STEP 5 must insert \`cluebase.identify(...)\` first and \`cluebase.group("organization", ...)\` immediately after the active context is known.
|
|
189
|
+
- \`"active_context_owner"\`: an existing singleton provider / auth-state listener / guarded active-context effect observes authenticated user id + active organization id/name and dedupes by the exact \`userId:organizationId\` key. This owner emits whenever the active organization changes. If this owner exists on a surface, do NOT also add create / join / switch handlers for that same surface; those handlers should update app state and the owner emits once.
|
|
190
|
+
- \`"context_change"\`: a write-side success handler that creates, joins, accepts invite for, switches, or onboards into an active organization context. Use this only when no \`active_context_owner\` on the same surface will observe and emit the same active context change.
|
|
191
|
+
|
|
192
|
+
Valid trigger examples:
|
|
193
|
+
- Login / sign-up / auth callback / token-exchange success with active organization in scope → \`group_owner_kind: "initial_context"\`.
|
|
194
|
+
- Session restore / auth-state listener / provider effect → \`group_owner_kind: "active_context_owner"\` ONLY when singleton-guarded, skips missing user/org/name, and dedupes by \`userId:organizationId\`.
|
|
195
|
+
- Create / join / accept-invite / switch / onboarding success → \`group_owner_kind: "context_change"\` ONLY when there is no active-context owner on that surface that will emit after the state update.
|
|
196
|
+
- Sign-up → auto-create-default-context → auto-login single flow → \`"initial_context"\` when one success branch owns the final authenticated active context.
|
|
197
|
+
|
|
198
|
+
Forbidden duplicate owner pattern:
|
|
199
|
+
- Do NOT record both an \`active_context_owner\` entry and create / join / switch \`context_change\` entries on the same frontend surface. This produces duplicate \`organization_associated\` rows for one active organization, which the SDK must not hide.
|
|
200
|
+
|
|
201
|
+
== cluebase.reset (usually 1-2 sites) ==
|
|
202
|
+
Semantic: Logout / sign-out / session-revoke success branch.
|
|
203
|
+
Triggers:
|
|
204
|
+
- Frontend: success callback after the auth SDK's signOut call returns OK.
|
|
205
|
+
- Backend: \`POST /auth/logout\` (or equivalent) success path.
|
|
206
|
+
- Third-party auth SDK \`signOut\` / \`events.signOut\` callback.
|
|
207
|
+
|
|
208
|
+
== AI provider sites (MULTIPLE INSTANCES NORMAL) ==
|
|
209
|
+
Semantic: Locations where the customer instantiates an LLM client (OpenAI / Anthropic / Azure OpenAI). STEP 5 will wire up OTel-primary OpenTelemetry/OpenLLMetry GenAI semantic instrumentation next to each instantiation; Cluebase helpers may enrich or provide fallback only.
|
|
210
|
+
Detection rule — register ONE entry per distinct client instantiation that is BOTH imported AND constructed at runtime (skip imports that are unused):
|
|
211
|
+
- Python: \`from openai import OpenAI\` + \`OpenAI(...)\` constructor, \`import openai\` + \`openai.OpenAI(...)\`, or \`from anthropic import Anthropic\` + \`Anthropic(...)\`. Azure: \`from openai import AzureOpenAI\` + \`AzureOpenAI(...)\`.
|
|
212
|
+
- Node / TypeScript: \`import OpenAI from "openai"\` + \`new OpenAI(...)\`, \`import Anthropic from "@anthropic-ai/sdk"\` + \`new Anthropic(...)\`, \`import { AzureOpenAI } from "openai"\` + \`new AzureOpenAI(...)\`.
|
|
213
|
+
NOT a site: import statement with no constructor call in the same module, vendored re-exports, test fixtures, type-only imports. False positives waste STEP 5 budget — confirm BOTH the import AND a constructor call exist before adding.
|
|
214
|
+
Set \`kind\` to one of \`"openai" | "anthropic" | "azure_openai" | "unknown"\`. Set \`language\` to \`"python" | "typescript"\`. \`evidence\` is the 1-line constructor snippet.
|
|
215
|
+
|
|
216
|
+
== MCP server sites (MULTIPLE INSTANCES NORMAL) ==
|
|
217
|
+
Semantic: Locations where the customer constructs an Anthropic Model Context Protocol server (= an MCP host that exposes tools / resources / prompts to LLMs). STEP 5 will wire up OTel-primary MCP / GenAI semantic instrumentation; Cluebase helpers may enrich or provide fallback only.
|
|
218
|
+
Detection rule — register ONE entry per distinct \`Server\` instantiation:
|
|
219
|
+
- Python: \`from mcp.server import Server\` (or \`from mcp.server.lowlevel import Server\`) + \`Server(...)\` constructor. The variable name being assigned (typically \`server = Server("...")\`) MUST be captured so STEP 5 can pass it to \`anthropic_instrument\`.
|
|
220
|
+
- Node / TypeScript: \`import { Server } from "@modelcontextprotocol/sdk/server/index.js"\` + \`new Server(...)\` constructor. Capture the variable name (typically \`const server = new Server(...)\`).
|
|
221
|
+
Set \`framework\` to \`"anthropic_mcp_python" | "anthropic_mcp_nodejs" | "unknown"\`. \`server_variable_name\` is the local identifier the constructor is assigned to (e.g. \`"server"\`, \`"mcpServer"\`).
|
|
222
|
+
|
|
223
|
+
== LangChain sites (MULTIPLE INSTANCES NORMAL) ==
|
|
224
|
+
Semantic: Locations where the customer constructs a LangChain chain / agent / LLM. STEP 5 will wire up \`instrument_langchain\` (Python) or \`instrumentLangChain\` (Node) so all chain / agent / tool invocations are auto-captured.
|
|
225
|
+
Detection rule — register ONE entry per distinct chain / agent / LLM construction that is BOTH imported AND constructed at runtime:
|
|
226
|
+
- Python: \`from langchain ...\`, \`from langchain_core ...\`, \`from langchain_openai ...\`, \`from langchain_anthropic ...\` + chain / agent / LLM constructor (\`LLMChain(...)\`, \`ConversationChain(...)\`, \`AgentExecutor(...)\`, \`ChatOpenAI(...)\`, \`ChatAnthropic(...)\`, \`create_react_agent(...)\`, \`create_openai_tools_agent(...)\`).
|
|
227
|
+
- Node / TypeScript: \`import { ChatOpenAI } from "@langchain/openai"\`, \`import { ChatAnthropic } from "@langchain/anthropic"\`, \`import { LLMChain, ConversationChain } from "langchain/chains"\`, \`import { AgentExecutor } from "langchain/agents"\` + corresponding \`new ChatOpenAI(...)\` / \`new LLMChain(...)\` / \`new AgentExecutor(...)\` constructor.
|
|
228
|
+
NOT a site: import with no constructor, type-only imports, vendored re-exports.
|
|
229
|
+
Set \`language\` to \`"python" | "typescript"\`. Set \`kind\` to one of \`"chain" | "agent" | "llm" | "callback_attach_point"\` based on which class the constructor instantiates (LLMChain / ConversationChain → \`"chain"\`, AgentExecutor / create_*_agent → \`"agent"\`, ChatOpenAI / ChatAnthropic / OpenAI / Anthropic LLM wrapper → \`"llm"\`, a bare \`CallbackManager\` / \`config.callbacks\` attach point → \`"callback_attach_point"\`). \`evidence\` is the 1-line snippet.
|
|
230
|
+
|
|
231
|
+
== LlamaIndex sites (Python; MULTIPLE INSTANCES NORMAL) ==
|
|
232
|
+
Semantic: Locations where the customer initializes LlamaIndex (\`Settings\`, \`VectorStoreIndex\`, query engines, chat engines, agents). STEP 5 will wire up \`instrument_llamaindex\` to attach to the root dispatcher so every LlamaIndex operation is auto-captured.
|
|
233
|
+
Detection rule — register ONE entry per distinct LlamaIndex construction:
|
|
234
|
+
- Python: \`from llama_index ...\` or \`from llama_index.core ...\` (\`from llama_index.core import Settings\`, \`from llama_index.core import VectorStoreIndex\`, \`from llama_index.core.agent import ...\`) + corresponding constructor or settings assignment (\`Settings.llm = ...\`, \`VectorStoreIndex(...)\`, \`index.as_query_engine(...)\`, \`OpenAIAgent.from_tools(...)\`).
|
|
235
|
+
Set \`kind\` to one of \`"index" | "settings" | "query_engine" | "chat_engine" | "agent"\`. \`evidence\` is the 1-line snippet.
|
|
236
|
+
|
|
237
|
+
== CrewAI sites (Python; MULTIPLE INSTANCES NORMAL) ==
|
|
238
|
+
Semantic: Locations where the customer constructs a CrewAI \`Crew\`. STEP 5 will insert \`instrument_crewai(crew)\` directly after each \`Crew(...)\` construction so all agent / task runs are auto-captured.
|
|
239
|
+
Detection rule — register ONE entry per distinct \`Crew(...)\` instantiation:
|
|
240
|
+
- Python: \`from crewai import Crew\` (or \`from crewai import Agent, Task, Crew\`) + \`Crew(agents=[...], tasks=[...])\` constructor. The variable name being assigned (typically \`crew = Crew(...)\`) MUST be captured so STEP 5 can pass it to \`instrument_crewai\`.
|
|
241
|
+
\`crew_variable_name\` is the local identifier the constructor is assigned to (e.g. \`"crew"\`, \`"my_crew"\`). \`evidence\` is the 1-line snippet.
|
|
242
|
+
|
|
243
|
+
== Mastra sites (Node / TypeScript; MULTIPLE INSTANCES NORMAL) ==
|
|
244
|
+
Semantic: Locations where the customer constructs a Mastra instance. STEP 5 will insert \`instrumentMastra(observer, mastra)\` directly after each \`new Mastra(...)\` so all Mastra agent / workflow runs are auto-captured.
|
|
245
|
+
Detection rule — register ONE entry per distinct \`new Mastra(...)\` instantiation:
|
|
246
|
+
- Node / TypeScript: \`import { Mastra } from "@mastra/core"\` (or \`from "@mastra/core/mastra"\`) + \`new Mastra({ ... })\` constructor. The variable name being assigned (typically \`const mastra = new Mastra(...)\`) MUST be captured so STEP 5 can pass it to \`instrumentMastra\`.
|
|
247
|
+
\`mastra_variable_name\` is the local identifier the constructor is assigned to (e.g. \`"mastra"\`). \`evidence\` is the 1-line snippet.
|
|
248
|
+
|
|
249
|
+
== cluebase_init_backend framework hint (extra material on the existing singleton) ==
|
|
250
|
+
The \`cluebase_init_backend\` entry remains a SINGLE object (or null) — it represents the one server-runtime boot point. To let STEP 5 pick the right \`cluebase_init_*\` integration (FastAPI / Django / Express / NestJS), \`cluebase_init_backend.evidence_snippet\` MUST include a marker that disambiguates the backend framework:
|
|
251
|
+
- \`from fastapi import FastAPI\` / \`FastAPI(...)\` → FastAPI (use \`cluebase.init\`).
|
|
252
|
+
- \`from django.apps import AppConfig\` / \`DJANGO_SETTINGS_MODULE\` / \`MIDDLEWARE\` list / \`INSTALLED_APPS\` list → Django (use \`cluebase.init\`).
|
|
253
|
+
- \`import express\` / \`require('express')\` / \`express()\` / \`app.use(...)\` → Express-style Node backend (use \`@genn-inc/cluebase-backend-sdk\` \`cluebase.init\` + \`cluebaseExpressMiddleware()\`).
|
|
254
|
+
- \`import { NestFactory } from "@nestjs/core"\` / \`NestFactory.create(...)\` → NestJS (uses Express adapter under the hood — same \`cluebase.init\` + \`cluebaseExpressMiddleware()\` pattern).
|
|
255
|
+
- \`import Fastify from "fastify"\` / \`fastify()\` → Fastify-style Node backend (treated as Express-equivalent for the purposes of STEP 5; the SDK middleware works through the framework's middleware adapter).
|
|
256
|
+
- \`import { Hono } from "hono"\` / \`new Hono()\` → Hono-style Node backend (same as Fastify treatment).
|
|
257
|
+
If you cannot determine the framework from the surrounding code, set \`evidence_snippet\` to the 1-3 line excerpt that includes the import line and the app-construction line, and STEP 5 will pick the closest matching pattern.
|
|
258
|
+
|
|
259
|
+
Output JSON schema (output exactly this shape; arrays may be empty []):
|
|
260
|
+
{
|
|
261
|
+
"framework_frontend": "<from manifest>",
|
|
262
|
+
"framework_backend": "<from manifest, or null>",
|
|
263
|
+
"service_key": "<backend service identifier>",
|
|
264
|
+
"cluebase_init_frontend": { "file": "<rel path>", "line": <int>, "rationale": "<short>", "creates_new_file"?: <bool> } | null,
|
|
265
|
+
"cluebase_init_backend": { "file": "<rel path>", "line": <int>, "rationale": "<short>", "creates_new_file"?: <bool> } | null,
|
|
266
|
+
"identify_sites": [ { "file": "<rel path>", "line": <int>, "rationale": "<short>", "evidence_snippet": "<1-3 lines>" }, ... ],
|
|
267
|
+
"group_sites": [ { "file": "<rel path>", "line": <int>, "rationale": "<short>", "evidence_snippet": "<1-3 lines>", "group_owner_kind": "initial_context|active_context_owner|context_change" }, ... ],
|
|
268
|
+
"reset_sites": [ ... same shape ... ],
|
|
269
|
+
"ai_provider_sites": [ { "kind": "openai|anthropic|azure_openai|unknown", "file": "<rel path>", "line": <int>, "language": "python|typescript", "evidence": "<1-line snippet>" }, ... ],
|
|
270
|
+
"mcp_server_sites": [ { "framework": "anthropic_mcp_python|anthropic_mcp_nodejs|unknown", "file": "<rel path>", "line": <int>, "server_variable_name": "<identifier>", "evidence": "<1-line snippet>" }, ... ],
|
|
271
|
+
"langchain_sites": [ { "language": "python|typescript", "file": "<rel path>", "line": <int>, "kind": "chain|agent|llm|callback_attach_point", "evidence": "<1-line snippet>" }, ... ],
|
|
272
|
+
"llamaindex_sites": [ { "file": "<rel path>", "line": <int>, "kind": "index|settings|query_engine|chat_engine|agent", "evidence": "<1-line snippet>" }, ... ],
|
|
273
|
+
"crewai_sites": [ { "file": "<rel path>", "line": <int>, "crew_variable_name": "<identifier>", "evidence": "<1-line snippet>" }, ... ],
|
|
274
|
+
"mastra_sites": [ { "file": "<rel path>", "line": <int>, "mastra_variable_name": "<identifier>", "evidence": "<1-line snippet>" }, ... ],
|
|
275
|
+
"existing_cluebase_calls": [ { "api": "cluebase.init|cluebase.identify|cluebase.group|cluebase.reset", "file": "<rel path>", "line": <int> }, ... ],
|
|
276
|
+
"unclear_points": [ { "concern": "<short>", "files_or_areas": ["<path or pattern>"] }, ... ],
|
|
277
|
+
"env_files": {
|
|
278
|
+
"frontend": { "path": "<rel path to frontend env file, e.g. 'frontend/.env.local'>", "format": "dotenv" } | null,
|
|
279
|
+
"backend": { "path": "<rel path to backend env file, e.g. 'backend/.env'>", "format": "dotenv" } | null
|
|
280
|
+
},
|
|
281
|
+
"db_schema": {
|
|
282
|
+
"detection_notes": "<1-3 sentence summary of which schema sources were inspected (e.g. 'Prisma schema.prisma at root + raw SQL migrations in db/migrations; user/account/workspace entities identified.')>",
|
|
283
|
+
"entities": [
|
|
284
|
+
{
|
|
285
|
+
"role": "human" | "billing_container" | "working_subcontainer",
|
|
286
|
+
"name": "<entity class / table name verbatim from the schema (e.g. 'User', 'organizations', 'WorkspaceMember')>",
|
|
287
|
+
"source_files": ["<rel path>", "..."],
|
|
288
|
+
"fields": [
|
|
289
|
+
{ "name": "<column / field name verbatim>", "type": "<sql / orm type when available, else null>", "comment": "<column doc / description when available, else null>", "fk_to": "<entity name when this field is a foreign key, else null>" }
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
Hard rules:
|
|
297
|
+
- The ONLY file you may write in this stage is \`.cluebase/discoveries.json\` at the repository root. Do not edit, create, or delete any other file. Use Read / Grep / Glob to inspect, and the Write tool only for that single artifact.
|
|
298
|
+
- Do not print the JSON contents in the chat. Use the Write tool to save it to \`.cluebase/discoveries.json\` and respond with the single Japanese line described in Step 7.
|
|
299
|
+
- cluebase_init_frontend and cluebase_init_backend are SINGLE objects (or null), NEVER arrays.
|
|
300
|
+
- Set \`creates_new_file: true\` on a cluebase_init_* entry when the singleton file (e.g. \`frontend/src/lib/cluebase.ts\`) does not exist yet and the implementation step will create it. Without this flag, the verification command in STEP 4 will fail with FILE_NOT_FOUND. Use \`line: 1\` when creates_new_file is true. The file path must still be a sensible relative path within the repo.
|
|
301
|
+
- identify_sites / group_sites / reset_sites are ARRAYS even when only one site exists.
|
|
302
|
+
- A typical SaaS has multiple login paths (email + Google SSO + magic link); include each as a separate identify_sites entry. Do not collapse them into one.
|
|
303
|
+
- evidence_snippet must be a 1-3 line code excerpt from the file proving the boundary.
|
|
304
|
+
- If you cannot confidently determine a boundary (e.g. complex middleware-based auth), add it to unclear_points instead of guessing. Do not invent boundaries.
|
|
305
|
+
- Every group_sites entry must set group_owner_kind. If an active_context_owner is present for a surface, remove create / join / switch context_change entries on that same surface unless repository evidence proves the owner cannot observe that state change.
|
|
306
|
+
- identify_sites / reset_sites are MUTATION-side / WRITE-side success paths only. group_sites are also write-side by default, with one narrow exception for active organization ownership: a singleton guarded client effect / auth-state listener that dedupes by user id + organization id. Do NOT include any read paths or boundaries that can fire repeatedly. Concrete exclusions (across all frameworks):
|
|
307
|
+
* Read hooks / queries that refetch on focus / interval / invalidation: TanStack \`useQuery\` / \`useSuspenseQuery\` / \`useInfiniteQuery\` / \`queryFn\`, Vue Query \`useQuery\`, Nuxt \`useFetch\` / \`useAsyncData\`, Apollo \`useQuery\`, SWR \`useSWR\` / \`useSWRImmutable\` / \`useSWRInfinite\`, Angular \`Resolver\` / \`HttpClient.get()\` inside ngOnInit, Relay \`useFragment\` / \`useLazyLoadQuery\`.
|
|
308
|
+
* GET endpoints / SELECT routes / read-side handlers on the backend — they serve reads, do not authenticate or change state.
|
|
309
|
+
* Component / page / layout body that re-runs every render (React render body, Vue \`<script setup>\` top-level, Svelte top-level, Angular component constructor / ngOnInit without singleton guard).
|
|
310
|
+
* useEffect / onMount / watchEffect / lifecycle hooks without an empty-deps or value-change guard — those re-fire.
|
|
311
|
+
* Route middleware on read endpoints — same problem.
|
|
312
|
+
If the identity / organization context becomes "visible" only through a read hook (e.g. \`useCurrent()\`, \`useUser()\`, \`useActiveOrg()\`), DO NOT insert in the read query / queryFn itself. First locate the actual write-side boundary that fed that read (the login mutation onSuccess, the organization-switcher onSelect AFTER the sentinel guard, the backend auth route success path, etc.). If no such boundary can provide the initial active organization after reload, you may record a singleton guarded client effect as a \`group_sites\` entry only when it checks authenticated user id + active organization id + active organization name, skips missing values, and dedupes the exact \`userId:organizationId\` key. If even that cannot be grounded, add the gap to \`unclear_points\` instead of guessing.
|
|
313
|
+
- Skip boundaries that fire on every render or every request (page-component mount, request-middleware on read endpoints, etc.); those would cause duplicate Cluebase calls and are a P0 issue.
|
|
314
|
+
- For \`ai_provider_sites\` / \`mcp_server_sites\` / \`langchain_sites\` / \`llamaindex_sites\` / \`crewai_sites\` / \`mastra_sites\`: register ONLY locations where BOTH the import AND the constructor call exist in the same module. Skip type-only imports and imports that are not actually instantiated. These arrays may be empty when the customer does not use that framework — that is fine.
|
|
315
|
+
- After writing the file and printing the one-line confirmation, stop. Do not run any commands yourself. The user types /cluebase-discover-review next in Claude Code.
|
|
316
|
+
|
|
317
|
+
Reference docs: ${documentsUrl}.`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// --- STEP 2: Self-review of discoveries ---------------------------------
|
|
321
|
+
|
|
322
|
+
export function buildStep2SelfReview({ documentsUrl }) {
|
|
323
|
+
return `You are running inside an AI coding tool. This is STEP 2 of the Cluebase setup flow (Self-review of the discovery output).
|
|
324
|
+
|
|
325
|
+
Goal (STEP 2 = Self-review):
|
|
326
|
+
Re-examine the \`.cluebase/discoveries.json\` you produced in STEP 1, apply the rubric below, and rewrite the file in place if any item flags an issue. Only \`.cluebase/discoveries.json\` may be written — do not edit, create, or delete any other file.
|
|
327
|
+
|
|
328
|
+
Steps:
|
|
329
|
+
1. Use the Read tool to load \`.cluebase/discoveries.json\` from the repository root.
|
|
330
|
+
2. Apply EVERY item in the rubric below to the current contents. Use Read / Grep / Glob to open the files referenced by each entry — do not rely on memory from STEP 1.
|
|
331
|
+
3. If at least one issue is found, use the Write tool to overwrite \`.cluebase/discoveries.json\` with the corrected JSON (only the \`{\` ... \`}\` object content; nothing else).
|
|
332
|
+
4. After overwriting (or after confirming no change was needed), run the rubric ONE more time to confirm convergence. Repeat up to 3 iterations total. Track the cumulative number of corrections you applied across all iterations.
|
|
333
|
+
|
|
334
|
+
Self-review rubric (framework-agnostic; \`@genn-inc/cluebase-frontend-sdk\` only runs in a real browser context, never on the server):
|
|
335
|
+
(a) Server-side handler exclusion. For each entry in \`identify_sites\` / \`group_sites\` / \`reset_sites\`, open the referenced file and decide whether it is a server-side handler. It IS server-side if ANY of the following is true:
|
|
336
|
+
- It imports from a server-only module path. Examples by framework:
|
|
337
|
+
- Next.js: \`next/server\`, \`next/headers\`, any \`*-server\` / \`*-edge\` path
|
|
338
|
+
- Nuxt / Nitro: \`defineEventHandler\`, files under \`server/api/**\`, \`server/middleware/**\`, \`server/routes/**\`
|
|
339
|
+
- SvelteKit: \`$app/server\`, \`@sveltejs/kit\` server request context, files named \`+server.ts\` / \`+server.js\`
|
|
340
|
+
- Remix / React Router (framework mode): \`@remix-run/node\`, \`@remix-run/server-runtime\`, top-level \`export const loader\` / \`export const action\`
|
|
341
|
+
- Astro: \`Astro.request\`, \`APIRoute\`, files under \`src/pages/api/**\`
|
|
342
|
+
- Angular SSR: \`@angular/ssr\`, \`@angular/platform-server\`, server-side rendering entry points
|
|
343
|
+
- It is a server route by convention regardless of imports: Next.js App Router \`route.ts\` / \`route.js\` exporting \`GET\`/\`POST\`/\`PUT\`/\`DELETE\`/\`PATCH\`/\`HEAD\`/\`OPTIONS\`, Nuxt \`server/**\`, SvelteKit \`+server.ts\`, Remix \`loader\` / \`action\` exports, Astro \`pages/api/**\`, Angular SSR entry.
|
|
344
|
+
If yes, REMOVE the entry — \`@genn-inc/cluebase-frontend-sdk\` cannot run there. Coverage is provided by the corresponding backend handler. If no backend handler is listed for the same logical flow, add the backend handler to the array first, then remove the server-side frontend entry.
|
|
345
|
+
(b) Read-path re-check. For each entry, confirm the surrounding function scope is a write-side success boundary (mutation onSuccess, write-route success path, user-interaction event handler such as button onClick / form onSubmit, one-shot lifecycle callback). REMOVE the entry if the scope is a read-side data hook or query that can re-fire on focus / interval / invalidation / render — examples by framework:
|
|
346
|
+
- TanStack Query (React): \`useQuery\` / \`useSuspenseQuery\` / \`useInfiniteQuery\` / \`queryFn\`
|
|
347
|
+
- Vue Query / TanStack Query (Vue): \`useQuery\` / \`useSuspenseQuery\`
|
|
348
|
+
- Nuxt: \`useFetch\` / \`useAsyncData\` / top-level \`$fetch\` inside \`<script setup>\`
|
|
349
|
+
- Apollo: \`useQuery\` (React or Vue)
|
|
350
|
+
- SWR: \`useSWR\` / \`useSWRImmutable\` / \`useSWRInfinite\`
|
|
351
|
+
- Angular: \`Resolver\`, \`HttpClient.get(...)\` inside \`ngOnInit\` / \`constructor\`
|
|
352
|
+
- Any render-time component body, layout body, or page setup body (the call would re-fire on every render).
|
|
353
|
+
(c) Completeness sweep (bidirectional — this is the most critical rubric; the goal is "no missed boundary, regardless of framework"). Do TWO exhaustive passes; DO NOT shortcut by trusting the current array contents. The search strategy depends on what's actually in the repo — inspect by semantics, not by file name match alone.
|
|
354
|
+
|
|
355
|
+
[Frontend pass] Enumerate EVERY client-side write-side success boundary, plus the one allowed singleton active-context restore boundary for initial \`cluebase.group\`. Examples by mechanism (non-exhaustive — adapt to whatever framework_frontend uses):
|
|
356
|
+
- TanStack Query (React or Vue): every \`useMutation\` with \`onSuccess\` / \`onCompleted\` callback.
|
|
357
|
+
- Redux Toolkit: every \`createAsyncThunk\` fulfilled branch; every reducer that handles \`.fulfilled\`.
|
|
358
|
+
- Zustand / Jotai / MobX / signals: every store action that performs a write and observes its result.
|
|
359
|
+
- Vue / Nuxt \`$fetch\`: every \`$fetch(..., { method: 'POST'|'PUT'|'DELETE'|'PATCH' })\` call with explicit success handling.
|
|
360
|
+
- Nuxt composables: \`composables/useXxxYyy.ts\` returning a write mutator.
|
|
361
|
+
- SvelteKit: form actions in \`+page.server.ts\`, client \`fetch\` event handlers.
|
|
362
|
+
- Angular: service methods returning \`Observable<...>\` consumed via \`.subscribe({ next })\` / \`.pipe(tap(success))\`. Inspect \`*.service.ts\` files.
|
|
363
|
+
- Auth SDK callbacks: NextAuth \`events.signIn\` / \`events.signOut\`, Supabase \`onAuthStateChange\` (singleton-guarded), Auth0 / Clerk hooks.
|
|
364
|
+
- Plain vanilla: \`<form>\` submit handlers, \`fetch(..., { method })\` / \`axios.post|put|delete|patch\` / \`ky.*\` call sites with success handling.
|
|
365
|
+
For each discovered boundary, read its body and classify (the 5 categories are the same as STEP 1):
|
|
366
|
+
- establishes user identity (login / sign-up / OTP verify / magic-link verify / OAuth callback completion / token-exchange-that-authenticates / auth-SDK signIn event) → \`identify_sites\`
|
|
367
|
+
- establishes / restores / changes / creates an active organization context (organization / company / tenant / workspace / team / project / space / channel / client — any product-level "this user is currently acting inside which company context" concept; login success with active context, singleton guarded active-context owner, or create / join / accept-invite / switch / onboard when no active owner on that surface emits the same change) → \`group_sites\`
|
|
368
|
+
- terminates session (logout / sign-out / revoke / signOut event) → \`reset_sites\`
|
|
369
|
+
- business CRUD (task / document / comment / project / file upload / settings update / etc.) → IGNORE
|
|
370
|
+
- genuinely ambiguous → \`unclear_points\`
|
|
371
|
+
If the boundary should be in an array but is NOT, ADD it (file, line of the success branch, rationale, evidence_snippet). If it is in an array but is misclassified, REMOVE it.
|
|
372
|
+
|
|
373
|
+
[Backend pass] Enumerate EVERY backend write route. Examples by framework_backend (non-exhaustive):
|
|
374
|
+
- FastAPI: \`@router.(post|put|delete|patch)\`, \`@app.(post|put|delete|patch)\`.
|
|
375
|
+
- Flask: \`@app.route(..., methods=["POST"|"PUT"|"DELETE"|"PATCH"])\`, \`@blueprint.route(...)\`.
|
|
376
|
+
- Django: \`urls.py\` POST views, \`views.py\` class-based \`def post|put|delete|patch\`, \`@api_view(["POST"|...])\`.
|
|
377
|
+
- Express / Hono / Fastify: \`router.(post|put|delete|patch)\`, \`app.(post|put|delete|patch)\`.
|
|
378
|
+
- NestJS: \`@Post()\`, \`@Put()\`, \`@Delete()\`, \`@Patch()\` controller decorators.
|
|
379
|
+
- Rails: \`config/routes.rb\` lines + controller \`create\` / \`update\` / \`destroy\` actions.
|
|
380
|
+
- Spring Boot: \`@PostMapping\`, \`@PutMapping\`, \`@DeleteMapping\`, \`@PatchMapping\`.
|
|
381
|
+
- ASP.NET Core: \`[HttpPost]\`, \`[HttpPut]\`, \`[HttpDelete]\`, \`[HttpPatch]\`.
|
|
382
|
+
- Phoenix (Elixir): \`router.ex\` post/put/delete/patch lines.
|
|
383
|
+
- Go (Gin / Echo / Fiber / chi): \`.POST(\`, \`.PUT(\`, \`.DELETE(\`, \`.PATCH(\`.
|
|
384
|
+
Apply the same 5-way classification.
|
|
385
|
+
|
|
386
|
+
Be conservative: only ADD when classification is unambiguous. An exchange route or login callback that authenticates AND returns the active organization context should be both an \`identify_sites\` entry and a \`group_sites\` entry at the success branch with \`group_owner_kind: "initial_context"\`. If it authenticates but the active organization is not in scope, keep identify and add an \`unclear_points\` entry naming the missing initial group context instead of inventing fields.
|
|
387
|
+
(d) Group-owner de-dupe. For every \`group_sites\` entry, set \`group_owner_kind\` to exactly one of \`"initial_context"\`, \`"active_context_owner"\`, or \`"context_change"\`. Then group entries by runtime surface (frontend vs backend). If a surface has an \`active_context_owner\`, REMOVE create / join / switch / accept-invite / onboarding \`context_change\` entries on that same surface when they only update the active context the owner observes. Keep a \`context_change\` entry only when repository evidence proves the active-context owner cannot observe that change; record that evidence in \`rationale\`. If uncertain, remove the duplicate-prone entry and add \`unclear_points\`.
|
|
388
|
+
(e) Evidence integrity. For each entry, confirm the \`evidence_snippet\` content actually appears in the file near the listed line and that it really shows the lifecycle boundary (mutation success, success return, event handler header). If the snippet does not match, FIX it by reading the file again. If the boundary itself does not exist where claimed, REMOVE the entry.
|
|
389
|
+
(f) cluebase_init_* file existence. If \`cluebase_init_frontend.creates_new_file\` (or \`cluebase_init_backend.creates_new_file\`) is \`true\` but the listed file already exists in the repo, switch the flag to \`false\` and keep / adjust \`line\` to a sensible insertion point inside the existing file — or, if the existing file is unrelated (e.g. random utility), change \`file\` to a new path that does not exist (a conventional fallback is \`frontend/src/lib/cluebase.ts\` when no framework-specific singleton location is established). If \`creates_new_file\` is absent or false but the referenced file does not exist yet, either set the flag to \`true\` or change \`file\` to an existing one with a sensible insertion line.
|
|
390
|
+
(g) Sentinel / early-return guard safety. For each \`identify_sites\` / \`group_sites\` / \`reset_sites\` entry, examine the surrounding scope and check whether any early-return branch filters values that should NOT trigger the lifecycle call BEFORE the listed line. The general shape is \`if (<condition>) { return; }\` (or framework equivalent), where the condition matches:
|
|
391
|
+
- Sentinel-value check (any literal indicating "not a real id" — the literal varies by codebase): \`x === "__create__"\`, \`x === "__new__"\`, \`x === "placeholder"\`, \`x === "draft"\`, \`x === "none"\`, \`x === ""\`, \`x === -1\`, \`x == null\`.
|
|
392
|
+
- Falsy / null check: \`!user\`, \`!user.id\`, \`!authenticated\`, \`!value\`.
|
|
393
|
+
- Discriminator / status skip: \`status !== "success"\`, \`result.kind === "error"\`, \`type !== "real"\`.
|
|
394
|
+
- Loading / pending skip: \`isLoading\`, \`pending\`, \`isPending\`.
|
|
395
|
+
- Framework-specific: Vue \`return\` from watcher / watchEffect, Angular early return in callback, Svelte \`$:\` block guard.
|
|
396
|
+
If yes AND the current \`line\` is BEFORE any of those guards, the lifecycle call would fire with the filtered value. FIX by setting \`line\` to the line AFTER all such guards — typically the line of the actual side-effect (\`router.push\`, \`navigate(\`, \`await api.(post|put|delete|patch)\`, \`db.commit\`, response return, \`dispatch(success)\`, \`emit("success", ...)\`). Update \`evidence_snippet\` to the new line's context. If the function has NO early-return guards, leave \`line\` as-is.
|
|
397
|
+
|
|
398
|
+
5. After convergence (no issue, or 3 iterations done), respond with exactly ONE Japanese line and stop. Choose based on the cumulative correction count:
|
|
399
|
+
- 0 corrections: \`.cluebase/discoveries.json の見直しは完了しました(修正なし)。次は Claude Code で /cluebase-discover-context と打って STEP 3 を実行してください。\`
|
|
400
|
+
- N corrections (N >= 1): \`.cluebase/discoveries.json の見直しは完了しました(修正 N 件)。次は Claude Code で /cluebase-discover-context と打って STEP 3 を実行してください。\`
|
|
401
|
+
Do not print the JSON contents, the rubric, the list of corrections, or any other prose — only the single line above.
|
|
402
|
+
|
|
403
|
+
Hard rules:
|
|
404
|
+
- The ONLY file you may write in this stage is \`.cluebase/discoveries.json\` at the repository root. Do not edit, create, or delete any other file. Use Read / Grep / Glob to inspect, and the Write tool only for that single artifact.
|
|
405
|
+
- Do not print the JSON contents in the chat — respond with the single Japanese line above.
|
|
406
|
+
- Do not run any commands yourself. The user types /cluebase-discover-context next in Claude Code.
|
|
407
|
+
|
|
408
|
+
Reference docs: ${documentsUrl}.`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// --- STEP 3: Discover context enrichment (AI) ----------------------------
|
|
412
|
+
|
|
413
|
+
export function buildStep3DiscoverContext({ documentsUrl }) {
|
|
414
|
+
return `You are running inside an AI coding tool. This is STEP 3 of the Cluebase setup flow (Context enrichment of .cluebase/discoveries.json).
|
|
415
|
+
|
|
416
|
+
Goal (STEP 3 = Context):
|
|
417
|
+
Enrich the \`.cluebase/discoveries.json\` produced in STEP 1 + 2 with two new pieces of information so STEP 5 (Implement) can wire cluebase.identify / cluebase.group calls precisely WITHOUT modifying any APIs, ORM models, response shapes, or fetches:
|
|
418
|
+
- A top-level \`organization_context\` object that records the customer's term for the company/organization concept Cluebase will map to \`cluebase.group("organization", ...)\`.
|
|
419
|
+
- A per-site \`available_fields\` object on every entry in \`identify_sites\` / \`group_sites\` (and the \`cluebase_init_*\` singletons where applicable), recording the JS / Python expression path Claude can use VERBATIM for each Cluebase lifecycle argument, plus \`field_acquisition_notes\` explaining why each path was chosen.
|
|
420
|
+
- A per-\`group_sites\` \`group_owner_kind\` value that prevents duplicate \`cluebase.group\` placement when a guarded active-context owner already emits organization association.
|
|
421
|
+
|
|
422
|
+
The ONLY file you may write in this STEP is \`.cluebase/discoveries.json\` at the repository root. Use Read / Grep / Glob to inspect — do NOT use Bash, and do NOT use Edit on any production code.
|
|
423
|
+
|
|
424
|
+
Steps:
|
|
425
|
+
1. Use the Read tool to load \`.cluebase/discoveries.json\`.
|
|
426
|
+
2. **Organization concept detection** (do this BEFORE field analysis, since \`group_sites\` field choices depend on it). Analyze the repo to decide which product concept represents the company / organization that Cluebase should associate with a user. This is a SEMANTIC decision based on multiple signals — NOT a literal grep. Inspect:
|
|
427
|
+
- DB models / ORM definitions: which entity represents the company or organization a user belongs to?
|
|
428
|
+
- URL routes: which create / join / switch routes change the user's active company or organization context?
|
|
429
|
+
- Auth claims / session shapes: which single company/organization id is carried with the user?
|
|
430
|
+
- Pivot / membership tables: which membership relation connects users to the company/organization concept?
|
|
431
|
+
- Multiple customer container names: if the product calls its company concept account, workspace, tenant, team, project, property, shop, or studio, map that concept to Cluebase \`organization\` for MVP. Do not create Cluebase account/workspace concepts.
|
|
432
|
+
|
|
433
|
+
Record the verdict on the top-level \`organization_context\` field of the JSON:
|
|
434
|
+
\`\`\`jsonc
|
|
435
|
+
"organization_context": {
|
|
436
|
+
"primary_organization_label": "<the customer's term for the company/organization concept, e.g. 'organization', 'tenant', 'workspace'>",
|
|
437
|
+
"rationale": "<1-3 sentence explanation of how this was determined from the signals above>",
|
|
438
|
+
"deferred_container_notes": "<optional note about lower-level team/project/workspace ids that must NOT be emitted as Cluebase group types; null when not relevant>"
|
|
439
|
+
}
|
|
440
|
+
\`\`\`
|
|
441
|
+
|
|
442
|
+
Defaults:
|
|
443
|
+
- When the product has a single flat container level, set \`primary_organization_label\` to the product's actual term and \`deferred_container_notes: null\`.
|
|
444
|
+
- When signals are mixed or you cannot confidently decide, keep \`primary_organization_label: "unknown"\` with a rationale describing what was inconclusive — do NOT guess.
|
|
445
|
+
|
|
446
|
+
3. **Per-site field analysis (SEMANTIC CONCEPT detection, NOT literal-name list)**.
|
|
447
|
+
|
|
448
|
+
Field detection method — read this BEFORE proceeding to the per-site loop:
|
|
449
|
+
|
|
450
|
+
You are running inside an AI coding tool precisely so non-English / industry-specific / arbitrarily named fields are recognized by MEANING. DO NOT search by a fixed list of field names. DO NOT short-circuit by "the field is called \`name\` so it is the name field" — sometimes that field holds a derived id; conversely a field called \`namae\` / \`ユーザー名\` / \`screen_name\` / \`handle\` / \`alias\` may BE the name. Judge by concept + grounded source + actual usage.
|
|
451
|
+
|
|
452
|
+
For EACH concept below, apply this 5-Step procedure:
|
|
453
|
+
|
|
454
|
+
1. **Concept check** — read the concept definition. Understand WHAT is being looked for.
|
|
455
|
+
2. **Grounded source** — first consult \`db_schema.entities\` recorded in STEP 1. Find the matching entity (human / billing_container / working_subcontainer) and enumerate its fields. The field's \`comment\` and \`type\` are evidence.
|
|
456
|
+
3. **Diversity sweep** — list every candidate field that could match the concept, INCLUDING non-English names (\`namae\`, \`ユーザー名\`, \`닉네임\`, \`昵称\`), industry-specific labels (\`handle\`, \`screen_name\`, \`alias\`, \`tenant_label\`), abbreviations (\`disp_name\`), and any column whose \`comment\` indicates the concept. Literal name lists provided below are HINTS ONLY — they do not limit your search.
|
|
457
|
+
4. **Real-usage verification** — open the file at \`file:line\` (the insertion point) and confirm the candidate is ACTUALLY USED as that concept in surrounding controllers / services / UI rendering. A field literally named \`name\` whose value is set to \`user.id.toString()\` upstream is NOT a name; reject it. A field named \`alias\` rendered as \`<span>{user.alias} さん</span>\` IS a name.
|
|
458
|
+
5. **Path selection** — pick the single best candidate. Record it as the JS / Python expression path that reaches it from variables in scope at the insertion point. If multiple candidates exist, prefer the one with a \`comment\` confirming the concept and the most explicit UI display usage.
|
|
459
|
+
|
|
460
|
+
FORBIDDEN behavior in this STEP:
|
|
461
|
+
|
|
462
|
+
* DO NOT execute the steps as "try field name A → fail → try B → fail → try C". The whole point of the 5-Step is "enumerate all candidates, then judge". Ordered fallback chains are the literal-pattern approach and miss non-list names.
|
|
463
|
+
* DO NOT substitute the user id / group id expression for a missing name. Recording \`name = id\` poisons the analytical layer for the rest of the customer's product lifetime (every "ユーザー X さん" display becomes a raw id string). See the HALT rule in the per-concept section below.
|
|
464
|
+
* DO NOT invent a new fetch, ORM join, or API parameter to obtain a missing field. The whole point of \`available_fields\` is to record what is ALREADY in scope.
|
|
465
|
+
|
|
466
|
+
Concept definitions (apply the 5-Step above to each):
|
|
467
|
+
|
|
468
|
+
A) **person_name** (= human-readable label of a person)
|
|
469
|
+
* Concept: A string that names a person in a way humans recognize on screen (e.g. "山田 太郎", "John Doe", "tarou123"). NOT an opaque id, NOT an email.
|
|
470
|
+
* Diversity HINTS (non-limiting): \`name\`, \`display_name\`, \`displayName\`, \`full_name\`, \`fullName\`, \`first_name + last_name\`, \`firstName + lastName\`, \`nickname\`, \`username\`, \`user_name\`, \`screen_name\`, \`handle\`, \`alias\`, \`namae\`, \`ユーザー名\`, \`氏名\`, \`姓 + 名\`, Korean \`이름\`, Chinese \`姓名\` / \`昵称\`.
|
|
471
|
+
* Verification: confirm the value is human-readable. If the candidate's value is derived from an id (e.g. \`name = str(user.id)\` upstream), REJECT — that is not a name field.
|
|
472
|
+
|
|
473
|
+
B) **email** (= contact / login email address)
|
|
474
|
+
* Concept: An RFC 5322-style email address used as contact or login identifier.
|
|
475
|
+
* Diversity HINTS: \`email\`, \`email_address\`, \`emailAddress\`, \`mail\`, \`mail_address\`, \`contact_email\`, \`メール\`, \`メールアドレス\`, \`電子メール\`, Korean \`이메일\`.
|
|
476
|
+
* Verification: confirm format / column type (\`varchar\` with email regex, or column comment "email"). Reject when the field is clearly a username only.
|
|
477
|
+
|
|
478
|
+
C) **organization_name** (= human-readable label of the company / top-level container)
|
|
479
|
+
* Concept: A string that names the customer's company / organization / tenant / workspace as humans see it (e.g. "Acme Inc.", "山田商店", "Marketing 部"). NOT a slug, NOT an id.
|
|
480
|
+
* Diversity HINTS: \`name\`, \`org_name\`, \`organization_name\`, \`account_name\`, \`title\`, \`tenant_name\`, \`company\`, \`company_name\`, \`組織名\`, \`会社名\`, \`テナント名\`, \`account_display_name\`.
|
|
481
|
+
* Verification: rendered in headers / sidebars / lists as the organization label.
|
|
482
|
+
|
|
483
|
+
D) **avatarUrl** (= profile picture URL)
|
|
484
|
+
* Concept: An HTTPS URL to a profile image / avatar / icon.
|
|
485
|
+
* Diversity HINTS: \`avatar\`, \`avatar_url\`, \`avatarUrl\`, \`image\`, \`image_url\`, \`profile_image\`, \`photo\`, \`picture\`, \`icon_url\`, \`アバター\`, \`アイコン\`, \`プロフィール画像\`.
|
|
486
|
+
* Verification: value starts with \`http\` or is a relative path to a static asset; rendered in \`<img>\` tags.
|
|
487
|
+
|
|
488
|
+
E) **organization_id** (= company / organization identifier)
|
|
489
|
+
* Concept: The stable id of the company / organization / tenant the user belongs to. This is the value passed as the second positional arg of \`cluebase.group("organization", ...)\`.
|
|
490
|
+
* Diversity HINTS: \`org_id\`, \`organization_id\`, \`tenant_id\`, \`company_id\`, \`account_id\`, \`accountId\`, \`組織ID\`, \`会社ID\`, \`テナントID\`, \`アカウントID\`.
|
|
491
|
+
* Verification: must be a stable id (uuid / slug / int), not a transient session value. If the customer code calls the company container \`account\`, map it to Cluebase \`organization\`; do NOT create a Cluebase account concept in MVP.
|
|
492
|
+
|
|
493
|
+
Per-site loop. For each entry in \`identify_sites\`, \`group_sites\`, \`reset_sites\`, and the \`cluebase_init_frontend\` / \`cluebase_init_backend\` singletons:
|
|
494
|
+
|
|
495
|
+
a. Use Read to open the file at \`file:line\`.
|
|
496
|
+
b. Identify what variables are in scope at the insertion point (function signature, callback parameters, closure context, request body / response object, ORM result, session / JWT payload). Cross-reference with \`db_schema.entities\` recorded in STEP 1 to know which fields the entity actually exposes.
|
|
497
|
+
c. Apply the 5-Step procedure above to each required concept for this site type. The rules differ by site type:
|
|
498
|
+
|
|
499
|
+
- For \`identify_sites\`, fill an \`available_fields\` object with:
|
|
500
|
+
- \`id\`: REQUIRED. The expression path that yields the user id at this site. If no id is in scope, the site is invalid — remove it from the array (move to \`unclear_points\` with a note explaining why).
|
|
501
|
+
- \`name\`: OPTIONAL HUMAN-READABLE. Apply the 5-Step on concept A (person_name) above. If no concept-A match exists, record \`null\` and add a note. DO NOT substitute the id path. DO NOT pass email as a name fallback. DO NOT invent a fetch.
|
|
502
|
+
- \`email\`: OPTIONAL. Record the expression path that yields the email (concept B), or \`null\` if not in scope. STEP 5 passes it as the \`email\` trait when available; the SDK/API converts it to privacy-safe contact-derived fields before storage.
|
|
503
|
+
- \`avatarUrl\`: OPTIONAL. The expression path that yields an avatar URL (concept D), or \`null\` if not in scope.
|
|
504
|
+
|
|
505
|
+
- For \`group_sites\`, fill an \`available_fields\` object with:
|
|
506
|
+
- \`organization_id\`: REQUIRED. Apply 5-Step on concept E and store the selected organization/company id expression here so STEP 5 can emit \`cluebase.group("organization", organization_id, ...)\`. Remove the site if no candidate exists.
|
|
507
|
+
- \`name\`: REQUIRED HUMAN-READABLE. Apply 5-Step on concept C (organization_name). Allowed fallback chain:
|
|
508
|
+
(1) any concept-C match in scope.
|
|
509
|
+
(2) HALT — when (1) produces zero, the site is INVALID. Remove it from \`group_sites\` and add an \`unclear_points\` entry stating: "<file>:<line>: no human-readable organization/company name field in scope at this group boundary. Customer must expose a display name on the organization/company entity, or re-run /cluebase-discover after fixing.". DO NOT substitute the organization_id path. DO NOT use a slug if it is not human-rendered.
|
|
510
|
+
- \`email\`: OPTIONAL. Expression path or \`null\`.
|
|
511
|
+
- \`avatarUrl\`: OPTIONAL. Expression path or \`null\`.
|
|
512
|
+
|
|
513
|
+
Also set \`group_owner_kind\` on the entry:
|
|
514
|
+
* \`"initial_context"\` when the site is the same success branch as login / sign-up / auth callback / token exchange and returns the active organization.
|
|
515
|
+
* \`"active_context_owner"\` when the site is an existing singleton provider / auth-state listener / guarded active-context effect that skips missing \`user id\`, \`organization id\`, and \`organization name\`, and dedupes by the exact \`userId:organizationId\` key.
|
|
516
|
+
* \`"context_change"\` when the site is a create / join / accept-invite / switch / onboarding success handler and no \`active_context_owner\` on that same surface emits the same active context change.
|
|
517
|
+
|
|
518
|
+
Important placement rule for group ownership: if this \`group_sites\` entry is the same success branch as an \`identify_sites\` entry, choose paths from the same response/session object so STEP 5 can place \`cluebase.identify(...)\` first and \`cluebase.group("organization", ...)\` immediately after it. If an \`active_context_owner\` exists for a surface and observes active organization changes, remove create / join / switch \`context_change\` entries for that same surface; the write handler should update application state and the owner emits once. Otherwise remove the duplicate-prone site and record \`unclear_points\` instead of using a refetching read hook or a second emission point.
|
|
519
|
+
|
|
520
|
+
- For \`reset_sites\`: cluebase.reset takes no arguments at the call site. Set \`available_fields: {}\` (empty object) for consistency.
|
|
521
|
+
|
|
522
|
+
- For \`cluebase_init_frontend\` / \`cluebase_init_backend\`: these singletons are initialized from env vars, not from in-scope variables. Leave them untouched (do not add \`available_fields\` to them).
|
|
523
|
+
|
|
524
|
+
d. Record the chosen paths VERBATIM as JS / Python expressions (e.g. \`"data.user.id"\`, \`"data.user.email"\`, \`"str(user.id)"\`, \`"user.namae"\`). Use the exact variable names and attribute paths from the surrounding scope — do NOT rename or reformat them, and do NOT translate Japanese / non-ASCII identifiers. STEP 5 will paste these strings into the source unchanged.
|
|
525
|
+
|
|
526
|
+
e. Add a \`field_acquisition_notes\` string (2-3 sentences) explaining the SEMANTIC reasoning for each path: which db_schema entity was consulted, which concept criterion was met, and what evidence (column comment / UI usage / type) confirmed the selection. Required when a non-English or non-conventional column was chosen — explain why it qualifies semantically. Concrete examples:
|
|
527
|
+
* \`"db_schema.entities[User] has columns id (uuid), namae (varchar(255), comment: 'ユーザー名'), email. Picked user.namae as name because its comment confirms it is a display name; rendered in src/components/UserBadge.tsx as <span>{user.namae}</span>. Email also in scope as data.user.email."\`
|
|
528
|
+
* \`"db_schema.entities[Member].alias is type varchar(64) with no comment, but src/views/PostHeader.tsx renders it as <strong>{member.alias}</strong> for every post — semantically a display name. Selected over member.username which holds an integer id."\`
|
|
529
|
+
* \`"data param of onSuccess callback exposes only user.id and email; person_name concept produced zero candidates after sweeping db_schema.entities[User] (fields: id, email, password_hash). Recorded name as null and email as data.user.email so the SDK can create privacy-safe contact-derived fields. avatarUrl is not in scope."\`
|
|
530
|
+
|
|
531
|
+
f. Be CONSERVATIVE. When a candidate path is uncertain (e.g. the variable might be undefined at the insertion line, or comes from a guard branch that may not be entered), record it as \`null\` with a note explaining the uncertainty — do NOT invent a fake path. STEP 5 will pass null / \`None\` and degrade gracefully.
|
|
532
|
+
|
|
533
|
+
3.5. ENV LINES CALCULATION. STEP 5 (= /cluebase-implement) writes env files. STEP 3 pre-computes EXACTLY what NAME=value lines belong in each env file so STEP 5 is a pure write operation with no judgment.
|
|
534
|
+
|
|
535
|
+
(a) Read \`.cluebase/setup-manifest.json\` and extract:
|
|
536
|
+
* \`cluebase_context.project_key\` (e.g. \`pk_dev_...\`)
|
|
537
|
+
* \`cluebase_context.cluebase_api_base_url\`
|
|
538
|
+
* \`cluebase_context.ingest_endpoints.browser\`
|
|
539
|
+
* \`cluebase_context.ingest_endpoints.backend\`
|
|
540
|
+
* \`detected_services[]\` root paths. The frontend SDK calls the Cluebase backend directly.
|
|
541
|
+
|
|
542
|
+
(b) Read \`.cluebase/secrets.json\` and extract \`cluebase_api_key\`. If the file does not exist, skip CLUEBASE_API_KEY in env_lines.backend and add an \`unclear_points\` entry "CLUEBASE_API_KEY is unavailable (.cluebase/secrets.json missing) — STEP 5 will not auto-write it. Re-run setup with --cluebase-api-key.".
|
|
543
|
+
|
|
544
|
+
(c) Determine frontend env-var prefix from \`discoveries.framework_frontend\`:
|
|
545
|
+
* \`nextjs\` → \`NEXT_PUBLIC_\`
|
|
546
|
+
* \`vite\` / Vite-based React → \`VITE_\`
|
|
547
|
+
* \`react\` → \`REACT_APP_\`
|
|
548
|
+
* \`sveltekit\` → \`PUBLIC_\`
|
|
549
|
+
* \`nuxt\` → \`NUXT_PUBLIC_\`
|
|
550
|
+
* \`angular\` → leave \`env_lines.frontend\` empty and add an \`unclear_points\` entry instructing the user to copy the setup screen's Angular runtime config values.
|
|
551
|
+
* \`solid\` / \`qwik\` / \`astro\` / unknown → leave \`env_lines.frontend\` empty and add an \`unclear_points\` entry naming the framework; STEP 5 will then ask the user once for the correct public config shape.
|
|
552
|
+
|
|
553
|
+
(d) Build the line set:
|
|
554
|
+
\`env_lines.frontend\` (2 entries when prefix is known; the frontend SDK calls Cluebase directly):
|
|
555
|
+
\`<PREFIX>CLUEBASE_API_BASE_URL=<cluebase_api_base_url>\`
|
|
556
|
+
\`<PREFIX>CLUEBASE_PROJECT_KEY=<project_key>\`
|
|
557
|
+
|
|
558
|
+
\`env_lines.backend\` (3 entries; omit CLUEBASE_API_KEY when secrets.json is absent):
|
|
559
|
+
\`CLUEBASE_INGEST_ENDPOINT=<ingest_endpoints.backend>\`
|
|
560
|
+
\`CLUEBASE_PROJECT_KEY=<project_key>\`
|
|
561
|
+
\`CLUEBASE_API_KEY=<cluebase_api_key from .cluebase/secrets.json>\`
|
|
562
|
+
|
|
563
|
+
(e) Hard rules:
|
|
564
|
+
* NEVER include CLUEBASE_API_KEY (or any other server-only secret) in env_lines.frontend. env_lines.frontend is only for browser-public SDK configuration.
|
|
565
|
+
* Every line MUST match the pattern \`^[A-Z_][A-Z0-9_]*=...$\`. No comments, no trailing whitespace, no quotes around values.
|
|
566
|
+
* env_lines.frontend / env_lines.backend may be empty arrays when the corresponding side has no env file or when the prefix is unknown (see (c)).
|
|
567
|
+
|
|
568
|
+
Record the result on the new top-level \`env_lines\` field (alongside \`env_files\` recorded by STEP 1).
|
|
569
|
+
|
|
570
|
+
4. Use the Write tool to overwrite \`.cluebase/discoveries.json\` with the enriched JSON. Preserve all existing top-level keys (\`framework_frontend\`, \`framework_backend\`, \`service_key\`, \`cluebase_init_frontend\`, \`cluebase_init_backend\`, \`identify_sites\`, \`group_sites\`, \`reset_sites\`, \`existing_cluebase_calls\`, \`unclear_points\`, \`env_files\`) and add the new top-level \`organization_context\` + \`env_lines\`, the new per-site \`available_fields\` + \`field_acquisition_notes\` keys, and \`group_owner_kind\` on every \`group_sites\` entry. Output only the \`{\` ... \`}\` object content; nothing else.
|
|
571
|
+
|
|
572
|
+
5. Respond with exactly ONE Japanese line and stop. Substitute N (identify_sites length), M (group_sites length), and the chosen organization label:
|
|
573
|
+
\`.cluebase/discoveries.json の context 解析を完了しました(識別 N 件、organization M 件、organization_label = <primary_organization_label>)。次は Claude Code で /cluebase-discover-check と打って STEP 4 を実行してください。\`
|
|
574
|
+
Do not print the JSON contents, the rationale, or any other prose — only the single line above.
|
|
575
|
+
|
|
576
|
+
Enriched JSON shape (illustrative — preserve every existing key in addition to the new fields):
|
|
577
|
+
\`\`\`jsonc
|
|
578
|
+
{
|
|
579
|
+
// ... existing top-level fields preserved unchanged ...
|
|
580
|
+
"organization_context": {
|
|
581
|
+
"primary_organization_label": "organization",
|
|
582
|
+
"rationale": "<1-3 sentence explanation>",
|
|
583
|
+
"deferred_container_notes": null
|
|
584
|
+
},
|
|
585
|
+
"identify_sites": [
|
|
586
|
+
{
|
|
587
|
+
"file": "...",
|
|
588
|
+
"line": 28,
|
|
589
|
+
"rationale": "...",
|
|
590
|
+
"evidence_snippet": "...",
|
|
591
|
+
"available_fields": {
|
|
592
|
+
"id": "data.user.id",
|
|
593
|
+
"name": "data.user.namae",
|
|
594
|
+
"email": "data.user.email",
|
|
595
|
+
"avatarUrl": "data.user.avatar_url"
|
|
596
|
+
},
|
|
597
|
+
"field_acquisition_notes": "db_schema.entities[User] columns: id (uuid), namae (varchar(255), comment 'ユーザー名'), email. Selected user.namae over a generic 'name' guess because the db_schema comment confirms it is a display name and src/components/UserBadge.tsx renders it as <span>{user.namae}</span>."
|
|
598
|
+
}
|
|
599
|
+
],
|
|
600
|
+
"group_sites": [
|
|
601
|
+
{
|
|
602
|
+
"file": "...",
|
|
603
|
+
"line": 42,
|
|
604
|
+
"rationale": "...",
|
|
605
|
+
"evidence_snippet": "...",
|
|
606
|
+
"group_owner_kind": "active_context_owner",
|
|
607
|
+
"available_fields": {
|
|
608
|
+
"organization_id": "org.id",
|
|
609
|
+
"name": "team.display_name",
|
|
610
|
+
"email": null,
|
|
611
|
+
"avatarUrl": null
|
|
612
|
+
},
|
|
613
|
+
"field_acquisition_notes": "db_schema.entities[Team].display_name (varchar, comment 'チーム名'). Picked over team.slug because slug is a URL identifier, not a human label."
|
|
614
|
+
}
|
|
615
|
+
],
|
|
616
|
+
"reset_sites": [
|
|
617
|
+
{ ... "available_fields": {} ... }
|
|
618
|
+
]
|
|
619
|
+
}
|
|
620
|
+
\`\`\`
|
|
621
|
+
|
|
622
|
+
Hard rules:
|
|
623
|
+
- The ONLY file you may write in this STEP is \`.cluebase/discoveries.json\` at the repository root. Do NOT use Bash, do NOT use Edit on any production code, do NOT modify any API / ORM model / response shape / fetch. The whole point of STEP 3 is to record what's ALREADY in scope so STEP 5 can wire it without rewriting customer code.
|
|
624
|
+
- When a REQUIRED field (id, name for identify; organization/company id, name for group) is not in scope, the SITE itself is invalid — remove it from the array and move it to \`unclear_points\` with a clear note. Do NOT invent fake paths to satisfy the schema.
|
|
625
|
+
- FORBIDDEN: substituting the user id expression for a missing person_name, OR substituting the organization id / slug for a missing organization_name. Recording \`name = id\` (or \`name = organization_id\`) is the exact failure mode this STEP exists to prevent — it poisons the analytical layer permanently (every "ユーザー X さん" / "Acme 株式会社" display becomes a raw id). When concept A produces zero candidates, record \`name: null\` and pass email only as the \`email\` trait when available. When concept C produces zero candidates, HALT (= remove site + \`unclear_points\` entry). Do NOT silently degrade.
|
|
626
|
+
- FORBIDDEN: searching by a fixed list of column names. The 5-Step procedure REQUIRES enumerating all candidate fields by semantic concept (= what the field MEANS), then verifying via db_schema grounding + real-usage in controllers / UI. Records that match only because of literal column name (without UI / comment verification) are likely wrong and MUST be discarded.
|
|
627
|
+
- For \`group_sites\`, do not emit Cluebase account/workspace ids in MVP. Customer apps may use those words internally, but the setup contract maps the top-level company concept to Cluebase \`organization\` only.
|
|
628
|
+
- For \`group_sites\`, do not keep both \`active_context_owner\` and create / join / switch \`context_change\` entries on the same surface when they emit the same active organization. Keep the owner; remove the duplicate-prone handler entry.
|
|
629
|
+
- Be conservative. Uncertain → \`null\` + note. Never invent.
|
|
630
|
+
- Do not print the JSON contents in the chat — respond with the single Japanese line described in Step 5.
|
|
631
|
+
- Do not run any commands yourself. The user types /cluebase-discover-check next in Claude Code.
|
|
632
|
+
|
|
633
|
+
Reference docs: ${documentsUrl}.`;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// --- STEP 4: setup-discover-check (Bash) ---------------------------------
|
|
637
|
+
|
|
638
|
+
export function buildStep4DiscoverCheck() {
|
|
639
|
+
return `You are running STEP 4 of the Cluebase setup flow (mechanical verification of .cluebase/discoveries.json).
|
|
640
|
+
|
|
641
|
+
Run the following command via the Bash tool and show the user the resulting JSON output verbatim:
|
|
642
|
+
|
|
643
|
+
\`\`\`bash
|
|
644
|
+
${CLUEBASE_CLI_INVOCATION} setup-discover-check --manifest .cluebase/setup-manifest.json --discoveries .cluebase/discoveries.json
|
|
645
|
+
\`\`\`
|
|
646
|
+
|
|
647
|
+
After the command finishes:
|
|
648
|
+
|
|
649
|
+
[If the JSON shows \`passed: true\` (even with unclear_points / SECRETS_FILE_MISSING warnings):]
|
|
650
|
+
Respond with exactly this Japanese line:
|
|
651
|
+
|
|
652
|
+
STEP 4(発見結果のチェック): OK。次は Claude Code で /cluebase-implement と打って STEP 5(実装)を実行してください。
|
|
653
|
+
|
|
654
|
+
[If the JSON shows \`passed: false\` or the command exits non-zero:]
|
|
655
|
+
1. Show the user the errors and warnings arrays from the JSON output.
|
|
656
|
+
2. Pick exactly ONE recommended next action using this priority order (first match wins; do NOT offer the user multiple choices). Priority is ordered from most-blocking secrets/security issues down to scope issues:
|
|
657
|
+
|
|
658
|
+
(a) If ANY error.code is "SECRETS_LEAK":
|
|
659
|
+
NEXT_ACTION = USER_FIX
|
|
660
|
+
ACTION_BODY = browser-public な frontend 設定に CLUEBASE_API_KEY などの server-only secret が混入しています。 frontend SDK に渡す env line から削除し、 server runtime 側の env にだけ置いてから /cluebase-discover を再実行してください。
|
|
661
|
+
REASON = browser-public 設定に secret が混入しています
|
|
662
|
+
|
|
663
|
+
(b) Else if ANY error.code is "SECRETS_NOT_IGNORED":
|
|
664
|
+
NEXT_ACTION = USER_FIX
|
|
665
|
+
ACTION_BODY = .cluebase/secrets.json が .gitignore に登録されていません。 リポジトリの .gitignore 末尾に \`.cluebase/secrets.json\` 行を追加してから /cluebase-discover を再実行してください。 登録漏れのまま commit すると CLUEBASE_API_KEY が remote に漏れます。
|
|
666
|
+
REASON = .cluebase/secrets.json が .gitignore に未登録(CLUEBASE_API_KEY が git に漏れるリスク)
|
|
667
|
+
|
|
668
|
+
(c) Else if ANY error.code is "FRAMEWORK_MISMATCH" or "SERVICE_KEY_MISMATCH" — OR ANY error.message mentions one of: "framework_frontend", "framework_backend", "service_key", "cluebase_init_frontend", "cluebase_init_backend":
|
|
669
|
+
NEXT_ACTION = RESUME_FROM_SUBSTEP
|
|
670
|
+
SUBSTEP_TO_RESTART = step1_discover
|
|
671
|
+
REASON = 基礎情報 (フレームワーク / サービス識別子 / 初期化箇所) が正しく記録できていません
|
|
672
|
+
|
|
673
|
+
(d) Else if ANY error.message mentions one of: "available_fields", "field_acquisition_notes", "organization_context" — OR ANY warning.code is "STEP_3_INCOMPLETE":
|
|
674
|
+
NEXT_ACTION = RESUME_FROM_SUBSTEP
|
|
675
|
+
SUBSTEP_TO_RESTART = step3_context
|
|
676
|
+
REASON = cluebase.identify / cluebase.group に渡す引数情報 (available_fields / organization_context) がまだ記録できていません
|
|
677
|
+
|
|
678
|
+
(e) Else if ANY error.message mentions one of: "env_files", "env_lines":
|
|
679
|
+
NEXT_ACTION = RESUME_FROM_SUBSTEP
|
|
680
|
+
SUBSTEP_TO_RESTART = step1_discover
|
|
681
|
+
REASON = env file の path / line 情報が discoveries.json に正しく記録できていません
|
|
682
|
+
|
|
683
|
+
(f) Otherwise (site shape / file path / duplicate / unclear points など STEP 2 で直すべき問題):
|
|
684
|
+
NEXT_ACTION = RESUME_FROM_SUBSTEP
|
|
685
|
+
SUBSTEP_TO_RESTART = step2_review
|
|
686
|
+
REASON = 記録した場所 (file / line / 不明点) に直すべき箇所があります
|
|
687
|
+
|
|
688
|
+
How RESUME_FROM_SUBSTEP works: /cluebase-discover reads .cluebase/discoveries.json:_progress.completed_substeps and skips already-completed substeps. To force re-execution from a specific point, remove its substepId and every later one from completed_substeps via the Edit tool BEFORE the user re-runs /cluebase-discover. Substep order: step1_discover → step2_review → step3_context → step4_check. If the customer wants a full clean re-run instead, they can \`rm -rf .cluebase/\` and re-run /cluebase-discover from scratch.
|
|
689
|
+
|
|
690
|
+
3. Respond with EXACTLY ONE Japanese line based on NEXT_ACTION (substitute SUBSTEP_TO_RESTART / REASON / ACTION_BODY / N_ERRORS / N_WARNINGS with the values you just derived):
|
|
691
|
+
|
|
692
|
+
[If NEXT_ACTION = RESUME_FROM_SUBSTEP:]
|
|
693
|
+
STEP 4 で問題が見つかりました(errors N_ERRORS 件 / warnings N_WARNINGS 件)。 REASON。 .cluebase/discoveries.json の _progress.completed_substeps から SUBSTEP_TO_RESTART 以降の substepId を Edit で外したあと、もう一度 /cluebase-discover を実行してください。
|
|
694
|
+
|
|
695
|
+
[If NEXT_ACTION = USER_FIX:]
|
|
696
|
+
STEP 4 で問題が見つかりました(errors N_ERRORS 件 / warnings N_WARNINGS 件)。 REASON。 ACTION_BODY
|
|
697
|
+
|
|
698
|
+
Do not edit .cluebase/discoveries.json yourself in this STEP except for the explicit substepId-removal described above (RESUME_FROM_SUBSTEP only). STEP 4 is verification + state-rewind only. The other file STEP 4 may write (when explicitly instructed in case (b)) is \`.gitignore\`.`;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// --- STEP 5: Implement + Self-correct -----------------------------------
|