@genn-inc/cluebase-cli 0.0.1 → 0.0.3
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 +10 -4
- package/package.json +1 -1
- package/src/cli-command.mjs +38 -46
- package/src/cluebase-env-file.mjs +177 -0
- package/src/code-evidence-analyzer.mjs +2 -1
- package/src/path-policy.mjs +1 -1
- package/src/setup-ai-contract.mjs +6 -0
- package/src/setup-discover-check.mjs +3 -15
- package/src/setup-doctor-env.mjs +19 -27
- package/src/setup-doctor-probe.mjs +4 -0
- package/src/setup-doctor-quality.mjs +11 -34
- package/src/setup-prepare.mjs +32 -32
- package/src/setup-step-builders-discover.mjs +9 -67
- package/src/setup-step-builders-implement.mjs +33 -68
- package/src/setup-step-commands.mjs +10 -10
package/README.md
CHANGED
|
@@ -27,8 +27,9 @@ preparation:
|
|
|
27
27
|
Node: Express/NestJS/Fastify/Koa) from dependency manifests and framework
|
|
28
28
|
import signals — language, framework, and service root only
|
|
29
29
|
- writes `.cluebase/setup-manifest.json`
|
|
30
|
-
-
|
|
31
|
-
|
|
30
|
+
- writes the single root `.env.cluebase` file with `[app]`, `[frontend]`, and
|
|
31
|
+
`[frontend_user]` sections when all three Cluebase values are supplied;
|
|
32
|
+
existing `.env*` files are never modified
|
|
32
33
|
|
|
33
34
|
A frontend-only project (single-page app plus serverless/static hosting, with no
|
|
34
35
|
detectable backend) is a supported setup path and continues as a frontend SDK
|
|
@@ -85,8 +86,8 @@ local customer frontend/backend.
|
|
|
85
86
|
|
|
86
87
|
`npx -y @genn-inc/cluebase-cli setup` reads the Cluebase API base URL, project key, and API
|
|
87
88
|
key from setup screen flags, detects local services, writes
|
|
88
|
-
`.cluebase/setup-manifest.json`, and
|
|
89
|
-
|
|
89
|
+
`.cluebase/setup-manifest.json`, and writes the same values to the root
|
|
90
|
+
`.env.cluebase`. The file is a setup artifact and is automatically added to `.gitignore`.
|
|
90
91
|
|
|
91
92
|
## Required Environment
|
|
92
93
|
|
|
@@ -94,6 +95,11 @@ screen Step 2.
|
|
|
94
95
|
- `CLUEBASE_PROJECT_KEY`: Cluebase setup screen issues this value.
|
|
95
96
|
- `CLUEBASE_API_BASE_URL`: Cluebase API base URL shown by the setup screen.
|
|
96
97
|
|
|
98
|
+
`.env.cluebase` stores `CLUEBASE_INGEST_ENDPOINT`, `CLUEBASE_PROJECT_KEY`, and
|
|
99
|
+
`CLUEBASE_API_KEY` under `[app]`; framework-prefixed public values under `[frontend]`;
|
|
100
|
+
and generic public values under `[frontend_user]`. The API key must never be copied into
|
|
101
|
+
either frontend section.
|
|
102
|
+
|
|
97
103
|
## Boundaries
|
|
98
104
|
|
|
99
105
|
- The tool may read allowed source paths in the client repository.
|
package/package.json
CHANGED
package/src/cli-command.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
@@ -85,6 +85,7 @@ const CLUEBASE_ARTIFACT_GITIGNORE_ENTRIES = [
|
|
|
85
85
|
".cluebase/setup-diff.patch",
|
|
86
86
|
".cluebase/setup-review-findings.md",
|
|
87
87
|
".cluebase/secrets.json",
|
|
88
|
+
".env.cluebase",
|
|
88
89
|
];
|
|
89
90
|
|
|
90
91
|
const protectCluebaseArtifacts = async ({ repoRoot }) => {
|
|
@@ -102,26 +103,6 @@ const protectCluebaseArtifacts = async ({ repoRoot }) => {
|
|
|
102
103
|
return results;
|
|
103
104
|
};
|
|
104
105
|
|
|
105
|
-
const SECRETS_FILE_PATH = ".cluebase/secrets.json";
|
|
106
|
-
|
|
107
|
-
const writeSecretsFile = async ({ repoRoot, apiKey }) => {
|
|
108
|
-
if (typeof apiKey !== "string" || apiKey.trim().length === 0) {
|
|
109
|
-
return { status: "skipped", reason: "no_api_key_provided" };
|
|
110
|
-
}
|
|
111
|
-
const absolutePath = resolve(repoRoot, SECRETS_FILE_PATH);
|
|
112
|
-
const body = {
|
|
113
|
-
cluebase_api_key: apiKey.trim(),
|
|
114
|
-
_warning:
|
|
115
|
-
"DO NOT COMMIT. This file contains the Cluebase API key. The Cluebase setup CLI auto-adds `.cluebase/secrets.json` to .gitignore — keep it that way.",
|
|
116
|
-
};
|
|
117
|
-
await mkdir(dirname(absolutePath), { recursive: true });
|
|
118
|
-
await writeFile(absolutePath, `${JSON.stringify(body, null, 2)}\n`, {
|
|
119
|
-
encoding: "utf8",
|
|
120
|
-
mode: 0o600,
|
|
121
|
-
});
|
|
122
|
-
return { status: "written", path: SECRETS_FILE_PATH };
|
|
123
|
-
};
|
|
124
|
-
|
|
125
106
|
const usage = () =>
|
|
126
107
|
[
|
|
127
108
|
"Cluebase CLI:",
|
|
@@ -141,7 +122,7 @@ const usage = () =>
|
|
|
141
122
|
].join("\n");
|
|
142
123
|
|
|
143
124
|
const renderEnvironmentInstructions = (instructions) => {
|
|
144
|
-
if (!instructions || instructions.status !== "
|
|
125
|
+
if (!instructions || instructions.status !== "written_to_cluebase_env") {
|
|
145
126
|
return "";
|
|
146
127
|
}
|
|
147
128
|
const services = Array.isArray(instructions.detected_services)
|
|
@@ -154,8 +135,9 @@ const renderEnvironmentInstructions = (instructions) => {
|
|
|
154
135
|
});
|
|
155
136
|
const lines = [
|
|
156
137
|
"",
|
|
157
|
-
|
|
158
|
-
"
|
|
138
|
+
`環境変数を ${instructions.path} に保存しました。`,
|
|
139
|
+
"[app] はサーバー用、[frontend] はframework用、[frontend_user] は公開値の確認用です。",
|
|
140
|
+
"API key は [app] のみに保存しています。既存の .env / .env.local は変更していません。",
|
|
159
141
|
"",
|
|
160
142
|
"検出したサービス:",
|
|
161
143
|
...(serviceLines.length > 0 ? serviceLines : [" (なし)"]),
|
|
@@ -167,20 +149,35 @@ const renderEnvironmentInstructions = (instructions) => {
|
|
|
167
149
|
const renderSetupResult = ({ preparation }) => {
|
|
168
150
|
if (preparation?.status === "ready_for_ai") {
|
|
169
151
|
const manifestPath =
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
152
|
+
preparation.artifacts?.setup_manifest_path ??
|
|
153
|
+
".cluebase/setup-manifest.json";
|
|
154
|
+
const environmentFilePath =
|
|
155
|
+
preparation.environment_instructions?.status ===
|
|
156
|
+
"written_to_cluebase_env"
|
|
157
|
+
? preparation.environment_instructions.path ??
|
|
158
|
+
preparation.artifacts?.environment_file_path ??
|
|
159
|
+
".env.cluebase"
|
|
160
|
+
: null;
|
|
161
|
+
const missingEnvironmentFlags = Array.isArray(
|
|
162
|
+
preparation.environment_instructions?.required_flags,
|
|
163
|
+
)
|
|
164
|
+
? preparation.environment_instructions.required_flags.join(", ")
|
|
165
|
+
: "--cluebase-api-key, --cluebase-api-base-url, --project-key";
|
|
166
|
+
return [
|
|
167
|
+
"Cluebase セットアップの準備が完了しました。",
|
|
168
|
+
"",
|
|
169
|
+
"作成したファイル:",
|
|
170
|
+
`- ${manifestPath}`,
|
|
171
|
+
...(environmentFilePath ? [`- ${environmentFilePath}`] : []),
|
|
172
|
+
"",
|
|
173
|
+
"次にやること:",
|
|
174
|
+
"1. Claude Code を使う場合: `claude` を起動し、`/cluebase-discover` -> `/cluebase-discover-review` -> `/cluebase-discover-context` -> `/cluebase-discover-check` -> `/cluebase-implement` -> `/cluebase-implement-check` -> `/cluebase-implement-review` -> `/cluebase-doctor` の順に実行してください。",
|
|
175
|
+
"2. Codex を使う場合: `codex` を起動し、`$cluebase-discover` -> `$cluebase-discover-review` -> `$cluebase-discover-context` -> `$cluebase-discover-check` -> `$cluebase-implement` -> `$cluebase-implement-check` -> `$cluebase-implement-review` -> `$cluebase-doctor` の順に実行してください。",
|
|
176
|
+
"",
|
|
177
|
+
environmentFilePath
|
|
178
|
+
? `環境変数はルートの ${environmentFilePath} に保存済みです。既存の .env / .env.local は変更していません。`
|
|
179
|
+
: `環境変数は未作成です。${missingEnvironmentFlags} を指定して再実行してください。`,
|
|
180
|
+
].join("\n");
|
|
184
181
|
}
|
|
185
182
|
|
|
186
183
|
const blockers = Array.isArray(preparation?.blockers)
|
|
@@ -312,6 +309,9 @@ export const runCli = async (argv, io = defaultIo) => {
|
|
|
312
309
|
repoRoot,
|
|
313
310
|
documentsUrl: flags.get("documents-url"),
|
|
314
311
|
});
|
|
312
|
+
const cluebaseArtifactProtection = await protectCluebaseArtifacts({
|
|
313
|
+
repoRoot,
|
|
314
|
+
});
|
|
315
315
|
const preparation = flags.has("skills-only")
|
|
316
316
|
? {
|
|
317
317
|
status: "skipped",
|
|
@@ -326,15 +326,7 @@ export const runCli = async (argv, io = defaultIo) => {
|
|
|
326
326
|
projectKey: flags.get("project-key"),
|
|
327
327
|
},
|
|
328
328
|
});
|
|
329
|
-
const cluebaseArtifactProtection = await protectCluebaseArtifacts({
|
|
330
|
-
repoRoot,
|
|
331
|
-
});
|
|
332
329
|
preparation.cluebase_artifact_protection = cluebaseArtifactProtection;
|
|
333
|
-
const secretsProtection = await writeSecretsFile({
|
|
334
|
-
repoRoot,
|
|
335
|
-
apiKey: flags.get("cluebase-api-key"),
|
|
336
|
-
});
|
|
337
|
-
preparation.secrets_protection = secretsProtection;
|
|
338
330
|
const environmentInstructions = renderEnvironmentInstructions(
|
|
339
331
|
preparation.environment_instructions,
|
|
340
332
|
);
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { chmod, writeFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export const CLUEBASE_ENV_FILE_PATH = ".env.cluebase";
|
|
5
|
+
export const CLUEBASE_ENV_SECTIONS = Object.freeze([
|
|
6
|
+
"app",
|
|
7
|
+
"frontend",
|
|
8
|
+
"frontend_user",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export const FRONTEND_PUBLIC_ENV_NAMES = Object.freeze([
|
|
12
|
+
"CLUEBASE_API_BASE_URL",
|
|
13
|
+
"CLUEBASE_PROJECT_KEY",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export const FRONTEND_PUBLIC_ENV_PREFIX_BY_FRAMEWORK = new Map([
|
|
17
|
+
["nextjs", "NEXT_PUBLIC_"],
|
|
18
|
+
["vite", "VITE_"],
|
|
19
|
+
["vue", "VITE_"],
|
|
20
|
+
["react", "REACT_APP_"],
|
|
21
|
+
["sveltekit", "PUBLIC_"],
|
|
22
|
+
["nuxt", "NUXT_PUBLIC_"],
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
26
|
+
const SECTION_PATTERN = /^\[([a-z_]+)\]$/;
|
|
27
|
+
|
|
28
|
+
const optionalString = (value) =>
|
|
29
|
+
typeof value === "string" && value.trim() ? value.trim() : null;
|
|
30
|
+
|
|
31
|
+
const trimTrailingSlash = (value) => String(value).replace(/\/+$/, "");
|
|
32
|
+
|
|
33
|
+
const formatEnvValue = (value) => {
|
|
34
|
+
const normalized = optionalString(value);
|
|
35
|
+
if (normalized === null) return null;
|
|
36
|
+
if (/[\r\n]/.test(normalized)) {
|
|
37
|
+
throw new Error(".env.cluebase values must not contain newlines");
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(normalized);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const envLine = (name, value) => {
|
|
43
|
+
if (!ENV_NAME_PATTERN.test(name)) {
|
|
44
|
+
throw new Error(`invalid .env.cluebase key: ${name}`);
|
|
45
|
+
}
|
|
46
|
+
const formatted = formatEnvValue(value);
|
|
47
|
+
return formatted === null ? null : `${name}=${formatted}`;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const section = (name, entries) => {
|
|
51
|
+
const lines = [`[${name}]`];
|
|
52
|
+
for (const [key, value] of entries) {
|
|
53
|
+
const line = envLine(key, value);
|
|
54
|
+
if (line) lines.push(line);
|
|
55
|
+
}
|
|
56
|
+
if (lines.length === 1) lines.push("# no values");
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const frontendEnvName = ({ framework, name }) => {
|
|
61
|
+
const prefix = FRONTEND_PUBLIC_ENV_PREFIX_BY_FRAMEWORK.get(
|
|
62
|
+
String(framework ?? "").toLowerCase(),
|
|
63
|
+
);
|
|
64
|
+
return prefix === undefined ? null : `${prefix}${name}`;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const buildCluebaseEnvFile = ({
|
|
68
|
+
cluebaseApiBaseUrl,
|
|
69
|
+
cluebaseApiKey,
|
|
70
|
+
detectedServices = [],
|
|
71
|
+
projectKey,
|
|
72
|
+
}) => {
|
|
73
|
+
const apiBaseUrlValue = optionalString(cluebaseApiBaseUrl);
|
|
74
|
+
const apiBaseUrl = apiBaseUrlValue
|
|
75
|
+
? trimTrailingSlash(apiBaseUrlValue)
|
|
76
|
+
: null;
|
|
77
|
+
const normalizedProjectKey = optionalString(projectKey);
|
|
78
|
+
const ingestEndpoint = apiBaseUrl
|
|
79
|
+
? `${trimTrailingSlash(apiBaseUrl)}/api/v1/ingest/backend`
|
|
80
|
+
: null;
|
|
81
|
+
const frontendServices = Array.isArray(detectedServices)
|
|
82
|
+
? detectedServices.filter((service) => service?.kind === "frontend")
|
|
83
|
+
: [];
|
|
84
|
+
const frontendEntries = [];
|
|
85
|
+
for (const service of frontendServices) {
|
|
86
|
+
for (const name of FRONTEND_PUBLIC_ENV_NAMES) {
|
|
87
|
+
const prefixedName = frontendEnvName({
|
|
88
|
+
framework: service.framework,
|
|
89
|
+
name,
|
|
90
|
+
});
|
|
91
|
+
if (prefixedName) {
|
|
92
|
+
frontendEntries.push([
|
|
93
|
+
prefixedName,
|
|
94
|
+
name === "CLUEBASE_API_BASE_URL" ? apiBaseUrl : normalizedProjectKey,
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const uniqueFrontendEntries = [
|
|
100
|
+
...new Map(frontendEntries.map(([key, value]) => [key, value])).entries(),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
return [
|
|
104
|
+
section("app", [
|
|
105
|
+
["CLUEBASE_INGEST_ENDPOINT", ingestEndpoint],
|
|
106
|
+
["CLUEBASE_PROJECT_KEY", normalizedProjectKey],
|
|
107
|
+
["CLUEBASE_API_KEY", cluebaseApiKey],
|
|
108
|
+
]),
|
|
109
|
+
section("frontend", uniqueFrontendEntries),
|
|
110
|
+
section("frontend_user", [
|
|
111
|
+
["CLUEBASE_API_BASE_URL", apiBaseUrl],
|
|
112
|
+
["CLUEBASE_PROJECT_KEY", normalizedProjectKey],
|
|
113
|
+
]),
|
|
114
|
+
].join("\n\n") + "\n";
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const unquoteValue = (value) => {
|
|
118
|
+
const trimmed = value.trim();
|
|
119
|
+
if (trimmed.length < 2) return trimmed;
|
|
120
|
+
const quote = trimmed[0];
|
|
121
|
+
if ((quote !== "'" && quote !== '"') || trimmed.at(-1) !== quote) {
|
|
122
|
+
return trimmed;
|
|
123
|
+
}
|
|
124
|
+
const inner = trimmed.slice(1, -1);
|
|
125
|
+
if (quote === "'") return inner;
|
|
126
|
+
return inner
|
|
127
|
+
.replace(/\\n/g, "\n")
|
|
128
|
+
.replace(/\\r/g, "\r")
|
|
129
|
+
.replace(/\\t/g, "\t")
|
|
130
|
+
.replace(/\\"/g, '"')
|
|
131
|
+
.replace(/\\\\/g, "\\");
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const parseCluebaseEnvFile = (content) => {
|
|
135
|
+
const sections = Object.fromEntries(
|
|
136
|
+
CLUEBASE_ENV_SECTIONS.map((name) => [name, {}]),
|
|
137
|
+
);
|
|
138
|
+
let currentSection = null;
|
|
139
|
+
for (const rawLine of String(content).split(/\r?\n/)) {
|
|
140
|
+
const line = rawLine.trim();
|
|
141
|
+
if (!line || line.startsWith("#")) continue;
|
|
142
|
+
const sectionMatch = SECTION_PATTERN.exec(line);
|
|
143
|
+
if (sectionMatch) {
|
|
144
|
+
currentSection = CLUEBASE_ENV_SECTIONS.includes(sectionMatch[1])
|
|
145
|
+
? sectionMatch[1]
|
|
146
|
+
: null;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!currentSection) continue;
|
|
150
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
151
|
+
if (match) sections[currentSection][match[1]] = unquoteValue(match[2]);
|
|
152
|
+
}
|
|
153
|
+
return sections;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const writeCluebaseEnvFile = async ({
|
|
157
|
+
repoRoot,
|
|
158
|
+
cluebaseApiBaseUrl,
|
|
159
|
+
cluebaseApiKey,
|
|
160
|
+
detectedServices,
|
|
161
|
+
projectKey,
|
|
162
|
+
}) => {
|
|
163
|
+
const absolutePath = resolve(repoRoot, CLUEBASE_ENV_FILE_PATH);
|
|
164
|
+
const content = buildCluebaseEnvFile({
|
|
165
|
+
cluebaseApiBaseUrl,
|
|
166
|
+
cluebaseApiKey,
|
|
167
|
+
detectedServices,
|
|
168
|
+
projectKey,
|
|
169
|
+
});
|
|
170
|
+
await writeFile(absolutePath, content, { encoding: "utf8", mode: 0o600 });
|
|
171
|
+
await chmod(absolutePath, 0o600);
|
|
172
|
+
return {
|
|
173
|
+
status: "written",
|
|
174
|
+
path: CLUEBASE_ENV_FILE_PATH,
|
|
175
|
+
sections: [...CLUEBASE_ENV_SECTIONS],
|
|
176
|
+
};
|
|
177
|
+
};
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
SOURCE_EXTENSIONS,
|
|
17
17
|
sourceMatchesBackendInit,
|
|
18
18
|
} from "./setup-check-constants.mjs";
|
|
19
|
+
import { RECOMMENDED_BACKEND_SDK_VERSION } from "./generated-sdk-version-contract.mjs";
|
|
19
20
|
|
|
20
21
|
const HTTP_METHODS = new Set(CODE_EVIDENCE_ROUTE_METHODS);
|
|
21
22
|
const [DATA_READ, DATA_CREATE, DATA_UPDATE, DATA_DELETE, DATA_UNKNOWN] =
|
|
@@ -946,7 +947,7 @@ const packageRepairFile = ({
|
|
|
946
947
|
? { ...packageManifest[targetField] }
|
|
947
948
|
: {};
|
|
948
949
|
dependencies[sdkPackageName] = sdkPackageName.startsWith("@")
|
|
949
|
-
?
|
|
950
|
+
? RECOMMENDED_BACKEND_SDK_VERSION
|
|
950
951
|
: sdkPackageName;
|
|
951
952
|
return {
|
|
952
953
|
path: packageManifestPath,
|
package/src/path-policy.mjs
CHANGED
|
@@ -47,7 +47,7 @@ export const listAllowedSourceFiles = async ({
|
|
|
47
47
|
}
|
|
48
48
|
const currentStat = await lstat(absolutePath);
|
|
49
49
|
if (currentStat.isSymbolicLink()) {
|
|
50
|
-
|
|
50
|
+
return;
|
|
51
51
|
}
|
|
52
52
|
if (currentStat.isDirectory()) {
|
|
53
53
|
const entries = await readdir(absolutePath);
|
|
@@ -6,6 +6,10 @@ export const SETUP_DOCTRINE = {
|
|
|
6
6
|
"Cluebase setup installs an external SDK integration so observed product facts can reach Cluebase's Customer Value Understanding Engine. It is not an opportunity to improve, refactor, redesign, or reinterpret the host application.",
|
|
7
7
|
minimal_diff_reason:
|
|
8
8
|
"The customer must be able to review and merge the setup diff with confidence. Extra formatting, refactors, auth rewrites, UI changes, or unrelated cleanup make the integration harder to trust.",
|
|
9
|
+
allowed_change_scope:
|
|
10
|
+
"Only files and lines required for Cluebase SDK dependency wiring, lifecycle calls, Cluebase environment loading, and setup verification may change.",
|
|
11
|
+
forbidden_change_scope:
|
|
12
|
+
"Do not fix pre-existing defects, move unrelated code, reformat files, rewrite host authentication or API behavior, or add files that are not required for the Cluebase setup path.",
|
|
9
13
|
ai_decision_boundary:
|
|
10
14
|
"The AI should use repository understanding only to choose existing lifecycle boundaries for cluebase.init, cluebase.identify, cluebase.group, and cluebase.reset.",
|
|
11
15
|
deterministic_control_boundary:
|
|
@@ -207,6 +211,8 @@ export const OFFICIAL_SDK_CONTRACT = {
|
|
|
207
211
|
export const setupDoctrineSkillLines = () => [
|
|
208
212
|
`- Purpose: ${SETUP_DOCTRINE.purpose}`,
|
|
209
213
|
`- Minimal diff reason: ${SETUP_DOCTRINE.minimal_diff_reason}`,
|
|
214
|
+
`- Allowed change scope: ${SETUP_DOCTRINE.allowed_change_scope}`,
|
|
215
|
+
`- Forbidden change scope: ${SETUP_DOCTRINE.forbidden_change_scope}`,
|
|
210
216
|
`- AI decision boundary: ${SETUP_DOCTRINE.ai_decision_boundary}`,
|
|
211
217
|
`- Static control boundary: ${SETUP_DOCTRINE.deterministic_control_boundary}`,
|
|
212
218
|
`- Documentation reason: ${SETUP_DOCTRINE.documentation_reason}`,
|
|
@@ -553,7 +553,7 @@ const validateSecretsGitignore = async ({ repoRoot }) => {
|
|
|
553
553
|
return errors;
|
|
554
554
|
};
|
|
555
555
|
|
|
556
|
-
const buildWarnings = (discoveries
|
|
556
|
+
const buildWarnings = (discoveries) => {
|
|
557
557
|
if (!isPlainObject(discoveries)) return [];
|
|
558
558
|
const warnings = [];
|
|
559
559
|
if (
|
|
@@ -619,19 +619,7 @@ const buildWarnings = (discoveries, { repoRoot } = {}) => {
|
|
|
619
619
|
message: `STEP 3 context outputs are missing: ${missingFields.join(", ")}. /cluebase-implement will hard stop without them — re-run /cluebase-discover so it can resume from step3_context.`,
|
|
620
620
|
});
|
|
621
621
|
}
|
|
622
|
-
|
|
623
|
-
// するための値の source (= `.cluebase/secrets.json`) が無い場合。
|
|
624
|
-
if (repoRoot) {
|
|
625
|
-
const secretsPath = resolve(repoRoot, ".cluebase/secrets.json");
|
|
626
|
-
if (!existsSync(secretsPath)) {
|
|
627
|
-
warnings.push({
|
|
628
|
-
code: "SECRETS_FILE_MISSING",
|
|
629
|
-
message:
|
|
630
|
-
".cluebase/secrets.json was not generated. STEP 5 will not auto-write CLUEBASE_API_KEY into the backend env file. Re-run `npx -y @genn-inc/cluebase-cli setup ... --cluebase-api-key <key>` with the value from the setup screen, or set CLUEBASE_API_KEY manually in the backend env file.",
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
return warnings;
|
|
622
|
+
return warnings;
|
|
635
623
|
};
|
|
636
624
|
|
|
637
625
|
const pickExitCode = (errors) => {
|
|
@@ -667,7 +655,7 @@ export const validateDiscoveries = async ({
|
|
|
667
655
|
errors.push(...validateNoDuplicates(discoveries));
|
|
668
656
|
errors.push(...(await validateSecretsGitignore({ repoRoot })));
|
|
669
657
|
}
|
|
670
|
-
const warnings = buildWarnings(discoveries
|
|
658
|
+
const warnings = buildWarnings(discoveries);
|
|
671
659
|
const passed = errors.length === 0;
|
|
672
660
|
return {
|
|
673
661
|
passed,
|
package/src/setup-doctor-env.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// setup-doctor env / URL / dotenv helpers.
|
|
2
|
-
//
|
|
2
|
+
// setup-doctor の正規化された .env.cluebase 読み込みをここで一元管理する。
|
|
3
3
|
|
|
4
|
-
import { dirname, isAbsolute,
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
5
5
|
import { API_CONNECTIVITY_CONTRACT } from "./setup-ai-contract.mjs";
|
|
6
6
|
import {
|
|
7
7
|
readSetupDoctorFile,
|
|
8
8
|
readSetupDoctorJson,
|
|
9
9
|
} from "./setup-doctor-file-boundary.mjs";
|
|
10
|
+
import {
|
|
11
|
+
CLUEBASE_ENV_FILE_PATH,
|
|
12
|
+
parseCluebaseEnvFile,
|
|
13
|
+
} from "./cluebase-env-file.mjs";
|
|
10
14
|
|
|
11
15
|
export const DEFAULT_SETUP_MANIFEST_PATH = ".cluebase/setup-manifest.json";
|
|
12
16
|
export const CLUEBASE_BROWSER_TOKEN_PATH =
|
|
@@ -18,12 +22,7 @@ export const CLUEBASE_TEST_SETUP_SDK_VERSION = "cluebase_test_setup";
|
|
|
18
22
|
export const FRONTEND_SOURCE_IDENTIFIER = "frontend";
|
|
19
23
|
export const DEFAULT_BATCH_VISIBILITY_TIMEOUT_MS = 60_000;
|
|
20
24
|
export const BATCH_VISIBILITY_POLL_INTERVAL_MS = 500;
|
|
21
|
-
export const DOCTOR_ENV_FILE_NAMES = [
|
|
22
|
-
".env",
|
|
23
|
-
".env.development",
|
|
24
|
-
".env.local",
|
|
25
|
-
".env.development.local",
|
|
26
|
-
];
|
|
25
|
+
export const DOCTOR_ENV_FILE_NAMES = [CLUEBASE_ENV_FILE_PATH];
|
|
27
26
|
|
|
28
27
|
// Frontend SDK calls Cluebase backend directly. Customer backends must not expose
|
|
29
28
|
// Cluebase-specific route handlers.
|
|
@@ -96,14 +95,16 @@ export const publicCluebaseApiBaseUrl = (env) =>
|
|
|
96
95
|
optionalString(env.VITE_CLUEBASE_API_BASE_URL) ??
|
|
97
96
|
optionalString(env.REACT_APP_CLUEBASE_API_BASE_URL) ??
|
|
98
97
|
optionalString(env.PUBLIC_CLUEBASE_API_BASE_URL) ??
|
|
99
|
-
optionalString(env.NUXT_PUBLIC_CLUEBASE_API_BASE_URL)
|
|
98
|
+
optionalString(env.NUXT_PUBLIC_CLUEBASE_API_BASE_URL) ??
|
|
99
|
+
optionalString(env.CLUEBASE_API_BASE_URL);
|
|
100
100
|
|
|
101
101
|
export const publicProjectKeyFromEnv = (env) =>
|
|
102
102
|
optionalString(env.NEXT_PUBLIC_CLUEBASE_PROJECT_KEY) ??
|
|
103
103
|
optionalString(env.VITE_CLUEBASE_PROJECT_KEY) ??
|
|
104
104
|
optionalString(env.REACT_APP_CLUEBASE_PROJECT_KEY) ??
|
|
105
105
|
optionalString(env.PUBLIC_CLUEBASE_PROJECT_KEY) ??
|
|
106
|
-
optionalString(env.NUXT_PUBLIC_CLUEBASE_PROJECT_KEY)
|
|
106
|
+
optionalString(env.NUXT_PUBLIC_CLUEBASE_PROJECT_KEY) ??
|
|
107
|
+
optionalString(env.CLUEBASE_PROJECT_KEY);
|
|
107
108
|
|
|
108
109
|
export const cluebaseApiBaseUrlFromIngestEndpoint = (endpoint) => {
|
|
109
110
|
const raw = optionalString(endpoint);
|
|
@@ -252,22 +253,7 @@ export const envRootsFromManifest = (manifest, kind) => {
|
|
|
252
253
|
};
|
|
253
254
|
|
|
254
255
|
export const envFileCandidates = ({ discoveries, kind, manifest, repoRoot }) => {
|
|
255
|
-
|
|
256
|
-
if (isPlainObject(discoveries?.env_files)) {
|
|
257
|
-
const entry = discoveries.env_files[kind];
|
|
258
|
-
if (isPlainObject(entry)) {
|
|
259
|
-
addEnvCandidate({ candidates, path: entry.path, repoRoot });
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
for (const name of DOCTOR_ENV_FILE_NAMES) {
|
|
263
|
-
addEnvCandidate({ candidates, path: name, repoRoot });
|
|
264
|
-
}
|
|
265
|
-
for (const root of envRootsFromManifest(manifest, kind)) {
|
|
266
|
-
for (const name of DOCTOR_ENV_FILE_NAMES) {
|
|
267
|
-
addEnvCandidate({ candidates, path: join(root, name), repoRoot });
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return [...candidates];
|
|
256
|
+
return [CLUEBASE_ENV_FILE_PATH];
|
|
271
257
|
};
|
|
272
258
|
|
|
273
259
|
export const loadDoctorEnvFiles = async ({
|
|
@@ -292,7 +278,13 @@ export const loadDoctorEnvFiles = async ({
|
|
|
292
278
|
optional: true,
|
|
293
279
|
});
|
|
294
280
|
if (content === null) continue;
|
|
295
|
-
|
|
281
|
+
const sections = parseCluebaseEnvFile(content);
|
|
282
|
+
Object.assign(
|
|
283
|
+
fileEnv,
|
|
284
|
+
kind === "backend"
|
|
285
|
+
? sections.app
|
|
286
|
+
: { ...sections.frontend_user, ...sections.frontend },
|
|
287
|
+
);
|
|
296
288
|
loadedFiles.push(relPath);
|
|
297
289
|
}
|
|
298
290
|
return { env: fileEnv, loadedFiles };
|
|
@@ -596,6 +596,10 @@ export const buildBackendEventPayload = ({
|
|
|
596
596
|
};
|
|
597
597
|
const resource = {
|
|
598
598
|
"service.name": backendServiceKey,
|
|
599
|
+
"cluebase.project_key": projectKey,
|
|
600
|
+
"cluebase.runtime_language": "nodejs",
|
|
601
|
+
"cluebase.service_type": "backend",
|
|
602
|
+
"cluebase.producer_id": backendServiceKey,
|
|
599
603
|
"telemetry.sdk.language": "nodejs",
|
|
600
604
|
"service.version": CLUEBASE_TEST_SETUP_SDK_VERSION,
|
|
601
605
|
};
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
checkC7,
|
|
41
41
|
checkC8,
|
|
42
42
|
} from "./setup-doctor-quality-checks-b.mjs";
|
|
43
|
-
import {
|
|
43
|
+
import { CLUEBASE_ENV_FILE_PATH } from "./cluebase-env-file.mjs";
|
|
44
44
|
import {
|
|
45
45
|
readSetupDoctorFile,
|
|
46
46
|
readSetupDoctorJson,
|
|
@@ -163,7 +163,7 @@ export const loadQualityInputs = async ({ repoRoot = ".", signal } = {}) => {
|
|
|
163
163
|
"package.json",
|
|
164
164
|
signal,
|
|
165
165
|
);
|
|
166
|
-
const envFiles = await readEnvFiles({ repoRoot,
|
|
166
|
+
const envFiles = await readEnvFiles({ repoRoot, signal });
|
|
167
167
|
return { discoveries, packageJson, envFiles };
|
|
168
168
|
};
|
|
169
169
|
|
|
@@ -171,39 +171,16 @@ const readJsonIfExists = async (repoRoot, path, signal) => {
|
|
|
171
171
|
return readSetupDoctorJson({ repoRoot, path, signal, optional: true });
|
|
172
172
|
};
|
|
173
173
|
|
|
174
|
-
const
|
|
175
|
-
".env",
|
|
176
|
-
".env.local",
|
|
177
|
-
".env.development",
|
|
178
|
-
".env.production",
|
|
179
|
-
];
|
|
180
|
-
|
|
181
|
-
const readEnvFiles = async ({ repoRoot, discoveries, signal }) => {
|
|
174
|
+
const readEnvFiles = async ({ repoRoot, signal }) => {
|
|
182
175
|
const result = {};
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
// よくある場所
|
|
194
|
-
for (const name of COMMON_ENV_FILE_NAMES) {
|
|
195
|
-
candidates.add(name);
|
|
196
|
-
}
|
|
197
|
-
for (const candidate of candidates) {
|
|
198
|
-
const content = await readSetupDoctorFile({
|
|
199
|
-
repoRoot,
|
|
200
|
-
path: candidate,
|
|
201
|
-
signal,
|
|
202
|
-
optional: true,
|
|
203
|
-
});
|
|
204
|
-
if (content !== null) {
|
|
205
|
-
result[candidate] = content;
|
|
206
|
-
}
|
|
176
|
+
const content = await readSetupDoctorFile({
|
|
177
|
+
repoRoot,
|
|
178
|
+
path: CLUEBASE_ENV_FILE_PATH,
|
|
179
|
+
signal,
|
|
180
|
+
optional: true,
|
|
181
|
+
});
|
|
182
|
+
if (content !== null) {
|
|
183
|
+
result[CLUEBASE_ENV_FILE_PATH] = content;
|
|
207
184
|
}
|
|
208
185
|
return result;
|
|
209
186
|
};
|
package/src/setup-prepare.mjs
CHANGED
|
@@ -11,26 +11,17 @@ import {
|
|
|
11
11
|
} from "./setup-ai-contract.mjs";
|
|
12
12
|
import { buildSetupDocumentationContract } from "./setup-documents.mjs";
|
|
13
13
|
import { discoverSetupRepository } from "./setup-repository-discovery.mjs";
|
|
14
|
+
import {
|
|
15
|
+
CLUEBASE_ENV_FILE_PATH,
|
|
16
|
+
FRONTEND_PUBLIC_ENV_NAMES,
|
|
17
|
+
FRONTEND_PUBLIC_ENV_PREFIX_BY_FRAMEWORK,
|
|
18
|
+
writeCluebaseEnvFile,
|
|
19
|
+
} from "./cluebase-env-file.mjs";
|
|
14
20
|
|
|
15
21
|
const DEFAULT_SETUP_MANIFEST_PATH = ".cluebase/setup-manifest.json";
|
|
16
22
|
const BROWSER_INGEST_PATH =
|
|
17
23
|
API_CONNECTIVITY_CONTRACT.hops.browser_ingest.path;
|
|
18
24
|
const BACKEND_INGEST_PATH = "/api/v1/ingest/backend";
|
|
19
|
-
// setup runtime で各 service が読む env 名一覧。
|
|
20
|
-
// 実際の値の提示は setup 画面 (web app の setup wizard) が担当する。
|
|
21
|
-
// CLI はサービス検出と manifest 出力のみを担う。
|
|
22
|
-
const FRONTEND_PUBLIC_ENV_NAMES = [
|
|
23
|
-
"CLUEBASE_API_BASE_URL",
|
|
24
|
-
"CLUEBASE_PROJECT_KEY",
|
|
25
|
-
];
|
|
26
|
-
const FRONTEND_PUBLIC_ENV_PREFIX_BY_FRAMEWORK = new Map([
|
|
27
|
-
["nextjs", "NEXT_PUBLIC_"],
|
|
28
|
-
["vite", "VITE_"],
|
|
29
|
-
["vue", "VITE_"],
|
|
30
|
-
["react", "REACT_APP_"],
|
|
31
|
-
["sveltekit", "PUBLIC_"],
|
|
32
|
-
["nuxt", "NUXT_PUBLIC_"],
|
|
33
|
-
]);
|
|
34
25
|
const BACKEND_RUNTIME_ENV_NAMES = [
|
|
35
26
|
"CLUEBASE_PROJECT_KEY",
|
|
36
27
|
"CLUEBASE_INGEST_ENDPOINT",
|
|
@@ -108,11 +99,11 @@ const requiredFrontendEnvNames = (detectedServices) => [
|
|
|
108
99
|
|
|
109
100
|
const requiredBackendEnvNames = () => [...BACKEND_RUNTIME_ENV_NAMES];
|
|
110
101
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
102
|
+
const buildEnvironmentInstructions = ({
|
|
103
|
+
environmentFile,
|
|
104
|
+
manifest,
|
|
105
|
+
setupContext,
|
|
106
|
+
}) => {
|
|
116
107
|
const missingFlags = [
|
|
117
108
|
["cluebase_api_key", "--cluebase-api-key"],
|
|
118
109
|
["cluebase_api_base_url", "--cluebase-api-base-url"],
|
|
@@ -134,9 +125,11 @@ const buildEnvironmentInstructions = ({ manifest, setupContext }) => {
|
|
|
134
125
|
? manifest.detected_services
|
|
135
126
|
: [];
|
|
136
127
|
return {
|
|
137
|
-
status: "
|
|
128
|
+
status: "written_to_cluebase_env",
|
|
138
129
|
message:
|
|
139
|
-
|
|
130
|
+
`Cluebase の環境変数をルートの ${CLUEBASE_ENV_FILE_PATH} に保存しました。既存の .env / .env.local は変更していません。`,
|
|
131
|
+
path: environmentFile?.path ?? CLUEBASE_ENV_FILE_PATH,
|
|
132
|
+
sections: environmentFile?.sections ?? ["app", "frontend", "frontend_user"],
|
|
140
133
|
detected_services: detectedServices.map((target) => ({
|
|
141
134
|
kind: target.kind,
|
|
142
135
|
framework: target.framework,
|
|
@@ -146,14 +139,7 @@ const buildEnvironmentInstructions = ({ manifest, setupContext }) => {
|
|
|
146
139
|
};
|
|
147
140
|
|
|
148
141
|
const summarizeEnvironmentInstructions = (instructions) => {
|
|
149
|
-
|
|
150
|
-
return instructions;
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
status: "deferred_to_setup_wizard",
|
|
154
|
-
message: instructions.message,
|
|
155
|
-
detected_services: instructions.detected_services,
|
|
156
|
-
};
|
|
142
|
+
return instructions;
|
|
157
143
|
};
|
|
158
144
|
|
|
159
145
|
export const runSetupPrepare = async ({
|
|
@@ -272,9 +258,10 @@ export const runSetupPrepare = async ({
|
|
|
272
258
|
},
|
|
273
259
|
artifacts: {
|
|
274
260
|
setup_manifest_path: setupManifestPath,
|
|
261
|
+
environment_file_path: CLUEBASE_ENV_FILE_PATH,
|
|
275
262
|
},
|
|
276
|
-
machine_owned_artifacts: [setupManifestPath],
|
|
277
|
-
ai_must_not_edit: [],
|
|
263
|
+
machine_owned_artifacts: [setupManifestPath, CLUEBASE_ENV_FILE_PATH],
|
|
264
|
+
ai_must_not_edit: [CLUEBASE_ENV_FILE_PATH],
|
|
278
265
|
ai_owned_workstreams: ["sdk_lifecycle_placement"],
|
|
279
266
|
ai_implementation_scope: {
|
|
280
267
|
rule: "AI implementation is limited to placing cluebase.init, cluebase.identify, cluebase.group, and cluebase.reset in existing lifecycle boundaries plus the minimal SDK wiring required for those calls.",
|
|
@@ -342,7 +329,20 @@ export const runSetupPrepare = async ({
|
|
|
342
329
|
backend_runtime: backendRuntimeEnvNames,
|
|
343
330
|
},
|
|
344
331
|
};
|
|
332
|
+
const environmentFile =
|
|
333
|
+
setupContext.cluebase_api_key &&
|
|
334
|
+
setupContext.cluebase_api_base_url &&
|
|
335
|
+
setupContext.project_key
|
|
336
|
+
? await writeCluebaseEnvFile({
|
|
337
|
+
repoRoot: resolvedRepoRoot,
|
|
338
|
+
cluebaseApiBaseUrl: setupContext.cluebase_api_base_url,
|
|
339
|
+
cluebaseApiKey: setupContext.cluebase_api_key,
|
|
340
|
+
detectedServices,
|
|
341
|
+
projectKey: setupContext.project_key,
|
|
342
|
+
})
|
|
343
|
+
: null;
|
|
345
344
|
const environmentInstructions = buildEnvironmentInstructions({
|
|
345
|
+
environmentFile,
|
|
346
346
|
manifest,
|
|
347
347
|
setupContext,
|
|
348
348
|
});
|
|
@@ -105,27 +105,15 @@ Steps:
|
|
|
105
105
|
- Discriminator skip: \`status !== "success"\`, \`result.kind === "error"\`, \`type !== "real"\`.
|
|
106
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
107
|
|
|
108
|
-
4.5. ENV
|
|
108
|
+
4.5. ENV CONFIGURATION BOUNDARY. The CLI has already written the single Cluebase
|
|
109
|
+
environment source at the repository root: \`.env.cluebase\`. Do not search for,
|
|
110
|
+
create, edit, or record any customer \`.env\`, \`.env.local\`, or other service-local
|
|
111
|
+
environment file. Do not copy values into another file. The sections are fixed:
|
|
112
|
+
\`[app]\` for server-only values, \`[frontend]\` for framework-prefixed public values,
|
|
113
|
+
and \`[frontend_user]\` for the generic public values a user can inspect.
|
|
109
114
|
|
|
110
|
-
|
|
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).
|
|
115
|
+
If \`.env.cluebase\` is absent, record one \`unclear_points\` entry asking the user
|
|
116
|
+
to re-run \`npx -y @genn-inc/cluebase-cli setup\` with the three Cluebase values.
|
|
129
117
|
|
|
130
118
|
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
119
|
|
|
@@ -274,10 +262,6 @@ Output JSON schema (output exactly this shape; arrays may be empty []):
|
|
|
274
262
|
"mastra_sites": [ { "file": "<rel path>", "line": <int>, "mastra_variable_name": "<identifier>", "evidence": "<1-line snippet>" }, ... ],
|
|
275
263
|
"existing_cluebase_calls": [ { "api": "cluebase.init|cluebase.identify|cluebase.group|cluebase.reset", "file": "<rel path>", "line": <int> }, ... ],
|
|
276
264
|
"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
265
|
"db_schema": {
|
|
282
266
|
"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
267
|
"entities": [
|
|
@@ -530,44 +514,7 @@ Steps:
|
|
|
530
514
|
|
|
531
515
|
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
516
|
|
|
533
|
-
|
|
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.
|
|
517
|
+
4. Use the Write tool to overwrite \`.cluebase/discoveries.json\` with the enriched JSON. Preserve all existing discovery keys and add the new per-site \`available_fields\` + \`field_acquisition_notes\` keys, \`organization_context\`, and \`group_owner_kind\` on every \`group_sites\` entry. Do not add environment file paths or copied environment values to discoveries.json. Output only the \`{\` ... \`}\` object content; nothing else.
|
|
571
518
|
|
|
572
519
|
5. Respond with exactly ONE Japanese line and stop. Substitute N (identify_sites length), M (group_sites length), and the chosen organization label:
|
|
573
520
|
\`.cluebase/discoveries.json の context 解析を完了しました(識別 N 件、organization M 件、organization_label = <primary_organization_label>)。次は Claude Code で /cluebase-discover-check と打って STEP 4 を実行してください。\`
|
|
@@ -675,11 +622,6 @@ Respond with exactly this Japanese line:
|
|
|
675
622
|
SUBSTEP_TO_RESTART = step3_context
|
|
676
623
|
REASON = cluebase.identify / cluebase.group に渡す引数情報 (available_fields / organization_context) がまだ記録できていません
|
|
677
624
|
|
|
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
625
|
(f) Otherwise (site shape / file path / duplicate / unclear points など STEP 2 で直すべき問題):
|
|
684
626
|
NEXT_ACTION = RESUME_FROM_SUBSTEP
|
|
685
627
|
SUBSTEP_TO_RESTART = step2_review
|
|
@@ -58,52 +58,20 @@ Steps:
|
|
|
58
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
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
60
|
|
|
61
|
-
4.5. ENV
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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.
|
|
61
|
+
4.5. ENV CONFIGURATION CHECK. The CLI owns one root file: \`.env.cluebase\`.
|
|
62
|
+
|
|
63
|
+
- Read \`.env.cluebase\` and use its fixed sections: \`[app]\` for server-only
|
|
64
|
+
values, \`[frontend]\` for framework-prefixed public values, and
|
|
65
|
+
\`[frontend_user]\` for generic public values.
|
|
66
|
+
- Do not create, edit, or copy Cluebase values into any \`.env\`,
|
|
67
|
+
\`.env.local\`, \`.env.development\`, or other service-local environment file.
|
|
68
|
+
- Do not edit \`.env.cluebase\` in this AI step. The CLI created it with mode
|
|
69
|
+
0600, and it is a machine-owned setup artifact.
|
|
70
|
+
- Verify that \`[app]\` contains \`CLUEBASE_INGEST_ENDPOINT\`,
|
|
71
|
+
\`CLUEBASE_PROJECT_KEY\`, and \`CLUEBASE_API_KEY\`. Verify that
|
|
72
|
+
\`[frontend]\` contains only the framework-prefixed public base URL and
|
|
73
|
+
project key. Verify that \`CLUEBASE_API_KEY\` is absent from both frontend
|
|
74
|
+
sections.
|
|
107
75
|
|
|
108
76
|
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
77
|
|
|
@@ -433,10 +401,10 @@ STEP 5(実装 + 自己レビュー)完了
|
|
|
433
401
|
cluebase.init / cluebase.identify / cluebase.group / cluebase.reset 挿入数: <counts>
|
|
434
402
|
依存追加: <dependency files>
|
|
435
403
|
install 実行: <command>
|
|
436
|
-
env
|
|
404
|
+
環境設定: ルートの .env.cluebase を確認済み(既存の .env / .env.local は変更していません)
|
|
437
405
|
自己修正: <N> 件
|
|
438
406
|
|
|
439
|
-
重要:
|
|
407
|
+
重要: 顧客アプリが参照する既存の環境変数ローダーで .env.cluebase の該当sectionを読み込めることを確認してください。新しい環境変数ファイルを追加してはいけません。
|
|
440
408
|
|
|
441
409
|
次は Claude Code で /cluebase-implement-check と打って STEP 6(静的検証)を実行してください。
|
|
442
410
|
==========================================
|
|
@@ -519,9 +487,8 @@ Process (loop up to 3 iterations; track iteration index N starting at 1 and cumu
|
|
|
519
487
|
|
|
520
488
|
P0 rubric (must block release):
|
|
521
489
|
- 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
|
-
-
|
|
523
|
-
-
|
|
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.
|
|
490
|
+
- Root \`.env.cluebase\` is missing any required value or section. Do not create a second env file and do not edit an existing \`.env*\`; ask the user to re-run the CLI setup so the machine-owned file is regenerated.
|
|
491
|
+
- \`.env.cluebase\` contains \`CLUEBASE_API_KEY\` in \`[frontend]\` or \`[frontend_user]\`. Remove the leaked value from those public sections and re-run the CLI setup; the key may exist only in \`[app]\`.
|
|
525
492
|
- CLUEBASE_API_KEY referenced from any browser/client-bundled file.
|
|
526
493
|
- await on cluebase.init / cluebase.identify / cluebase.group / cluebase.reset in a blocking path.
|
|
527
494
|
- try/catch / try/except / .catch wrapping lifecycle calls solely for Cluebase.
|
|
@@ -613,7 +580,7 @@ export function buildStep9SetupDoctor() {
|
|
|
613
580
|
|
|
614
581
|
Before running the command, briefly explain to the user (1-2 lines) what this step does:
|
|
615
582
|
|
|
616
|
-
STEP 9 は Cluebase
|
|
583
|
+
STEP 9 は Cluebase の疎通確認です。ルートの \`.env.cluebase\` の \`[frontend]\` / \`[app]\` を読み、Cluebase token 発行 / browser ingest / backend ingest を 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
584
|
|
|
618
585
|
Then run the following command via the Bash tool:
|
|
619
586
|
|
|
@@ -638,17 +605,17 @@ Respond with exactly:
|
|
|
638
605
|
• 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
606
|
|
|
640
607
|
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
|
|
608
|
+
• Required env (set in the \`[frontend]\` section of root \`.env.cluebase\`, using the framework's public-env prefix):
|
|
642
609
|
<frontend public prefix>CLUEBASE_API_BASE_URL=<from setup screen Step 2>
|
|
643
610
|
<frontend public prefix>CLUEBASE_PROJECT_KEY=<from setup screen Step 2>
|
|
644
|
-
• After
|
|
611
|
+
• After the existing runtime loader reads \`.env.cluebase\`, RESTART the frontend dev server.
|
|
645
612
|
|
|
646
613
|
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
|
|
614
|
+
• Required env (set in the \`[app]\` section of root \`.env.cluebase\`):
|
|
648
615
|
CLUEBASE_API_KEY=<from setup screen Step 2>
|
|
649
616
|
CLUEBASE_PROJECT_KEY=<from setup screen Step 2>
|
|
650
617
|
CLUEBASE_INGEST_ENDPOINT=<from setup screen Step 2>
|
|
651
|
-
• After
|
|
618
|
+
• After the existing runtime loader reads \`.env.cluebase\`, RESTART the backend dev server.
|
|
652
619
|
|
|
653
620
|
Bucket UPSTREAM_FAILURE — when check.error mentions "browser token" on the \`browser_ingest\` check (= caused by FRONTEND_ENV_MISSING; do not bucket separately).
|
|
654
621
|
|
|
@@ -662,23 +629,21 @@ Respond with exactly:
|
|
|
662
629
|
|
|
663
630
|
3. AUTO-FIX (offer once before falling back to manual instructions):
|
|
664
631
|
|
|
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.
|
|
632
|
+
For each missing env name in FRONTEND_ENV_MISSING / BACKEND_ENV_MISSING buckets, resolve the value from the matching section of root \`.env.cluebase\` or from the original setup arguments. If the value is absent, ask the user to re-run the CLI setup; do not create another env file and do not print the API key.
|
|
668
633
|
|
|
669
634
|
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
635
|
|
|
671
636
|
STEP 9 で env 不足を検出しました。 以下を自動で書き込みますか?
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
637
|
+
.env.cluebase [frontend]: <list of NAME=value keys>
|
|
638
|
+
.env.cluebase [app]: <list of NAME=value keys>
|
|
639
|
+
設定後、 frontend / backend dev server を再起動して /cluebase-doctor を再実行してください。
|
|
675
640
|
OK の場合: 「OK」 と返信してください。 NG の場合: 「NG」 と返信してください。
|
|
676
641
|
|
|
677
642
|
When the user replies "OK":
|
|
678
|
-
-
|
|
679
|
-
-
|
|
643
|
+
- Do not use Edit to write any \`.env*\` file. Ask the user to re-run the CLI setup so it owns the update of \`.env.cluebase\`.
|
|
644
|
+
- Do not edit \`.env.cluebase\` from the AI step and do not restart the dev server.
|
|
680
645
|
- Respond with:
|
|
681
|
-
env
|
|
646
|
+
\`.env.cluebase\` の再生成が必要です。CLI setupを再実行し、frontend / backend dev server を再起動してから、もう一度 /cluebase-doctor を実行してください。
|
|
682
647
|
|
|
683
648
|
When the user replies "NG" or you could not resolve all values:
|
|
684
649
|
- Skip the auto-fix and fall through to manual instructions (step 4).
|
|
@@ -690,12 +655,12 @@ Respond with exactly:
|
|
|
690
655
|
STEP 9(疎通確認)で問題が見つかりました(checks N_FAILED 件 / passed N_PASSED 件)。 以下を順番に直してください:
|
|
691
656
|
|
|
692
657
|
[For each non-empty bucket, print one numbered line in this exact form:]
|
|
693
|
-
<N>. <bucket-specific human-readable instruction in Japanese — e.g. "
|
|
658
|
+
<N>. <bucket-specific human-readable instruction in Japanese — e.g. "CLI setupを再実行して .env.cluebase の [frontend] に framework に合う public env 名を生成し、frontend dev server を再起動してください">
|
|
694
659
|
|
|
695
|
-
修正後にもう一度 /cluebase-doctor
|
|
660
|
+
修正後にもう一度 /cluebase-doctor を実行すると再チェックされます。値が不明な場合は setup 画面の値を使ってCLI setupを再実行してください。
|
|
696
661
|
|
|
697
662
|
Hard rules:
|
|
698
|
-
- Do not edit \`.cluebase/setup-manifest.json\` or \`.cluebase
|
|
663
|
+
- Do not edit \`.cluebase/setup-manifest.json\` or \`.env.cluebase\` in this STEP — STEP 9 is verification only.
|
|
699
664
|
- 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
665
|
- Never restart dev servers from this STEP. Always ask the user to restart manually after auto-fix.
|
|
701
666
|
- TRANSPORT_FAILURE buckets are NOT auto-fixable — instruct the user to start the affected dev server / verify the URL and re-run /cluebase-doctor.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// - cluebase-discover-review.md — STEP 2: self-review of STEP 1 output (AI)
|
|
7
7
|
// - cluebase-discover-context.md — STEP 3: field-semantic enrichment (AI)
|
|
8
8
|
// - cluebase-discover-check.md — STEP 4: bash validation of discoveries.json
|
|
9
|
-
// - cluebase-implement.md — STEP 5: SDK call insertion + env
|
|
9
|
+
// - cluebase-implement.md — STEP 5: SDK call insertion + env config check
|
|
10
10
|
// - cluebase-implement-check.md — STEP 6: bash static check + diff snapshot
|
|
11
11
|
// - cluebase-implement-review.md — STEP 7: self-review of inserted code (AI)
|
|
12
12
|
// - cluebase-events.md — STEP 8: guided business value event track (AI)
|
|
@@ -88,7 +88,7 @@ const STEP_FILES = [
|
|
|
88
88
|
id: "step1_discover",
|
|
89
89
|
title: "STEP 1 — Discover (boundary enumeration)",
|
|
90
90
|
singleGoal:
|
|
91
|
-
"The ONLY goal of this command is to grep + Read the customer codebase exhaustively and produce the initial `.cluebase/discoveries.json` (lifecycle boundary candidates + db_schema grounding
|
|
91
|
+
"The ONLY goal of this command is to grep + Read the customer codebase exhaustively and produce the initial `.cluebase/discoveries.json` (lifecycle boundary candidates + db_schema grounding). SDK call insertion, environment file writes, code changes, and connectivity checks are ALL out of scope.",
|
|
92
92
|
scopeOut: [
|
|
93
93
|
"STEP 2 (= /cluebase-discover-review): self-review / fix of discoveries.json",
|
|
94
94
|
"STEP 3 (= /cluebase-discover-context): field-semantic enrichment (`available_fields` / `organization_context`)",
|
|
@@ -96,7 +96,7 @@ const STEP_FILES = [
|
|
|
96
96
|
"STEP 5+ (= /cluebase-implement and later): any source code change",
|
|
97
97
|
],
|
|
98
98
|
completion:
|
|
99
|
-
"`.cluebase/discoveries.json` is written with all boundaries + db_schema
|
|
99
|
+
"`.cluebase/discoveries.json` is written with all boundaries + db_schema, `_progress.completed_substeps` is patched with `step1_discover`, and the Japanese hand-off line for STEP 2 is printed.",
|
|
100
100
|
nextHandoff: "/cluebase-discover-review",
|
|
101
101
|
},
|
|
102
102
|
{
|
|
@@ -125,15 +125,15 @@ const STEP_FILES = [
|
|
|
125
125
|
id: "step3_context",
|
|
126
126
|
title: "STEP 3 — Field semantic context enrichment",
|
|
127
127
|
singleGoal:
|
|
128
|
-
"The ONLY goal of this command is to enrich `.cluebase/discoveries.json` with
|
|
128
|
+
"The ONLY goal of this command is to enrich `.cluebase/discoveries.json` with per-site `available_fields` (concrete variable paths reachable at each insertion line) and top-level `organization_context` (the customer's company/organization label). The customer code and `.env.cluebase` are NOT modified.",
|
|
129
129
|
scopeOut: [
|
|
130
130
|
"STEP 1 (= /cluebase-discover): boundary discovery",
|
|
131
131
|
"STEP 2 (= /cluebase-discover-review): rubric-based self-review",
|
|
132
132
|
"STEP 4 (= /cluebase-discover-check): bash validation",
|
|
133
|
-
"STEP 5+ (= /cluebase-implement and later): code edits, env
|
|
133
|
+
"STEP 5+ (= /cluebase-implement and later): code edits, `.env.cluebase` handling, and connectivity checks",
|
|
134
134
|
],
|
|
135
135
|
completion:
|
|
136
|
-
"`.cluebase/discoveries.json` gains `available_fields` / `organization_context
|
|
136
|
+
"`.cluebase/discoveries.json` gains `available_fields` / `organization_context`, `_progress.completed_substeps` is patched with `step3_context`, and the Japanese hand-off line for STEP 4 is printed.",
|
|
137
137
|
nextHandoff: "/cluebase-discover-check",
|
|
138
138
|
},
|
|
139
139
|
{
|
|
@@ -147,7 +147,7 @@ const STEP_FILES = [
|
|
|
147
147
|
"The ONLY goal of this command is to mechanically validate `.cluebase/discoveries.json` via the `setup-discover-check` CLI helper, surface any error/warning, and (when an error is found) instruct the user which earlier STEP to re-run by removing the matching substepId from `_progress.completed_substeps`. No AI judgment, no code edits.",
|
|
148
148
|
scopeOut: [
|
|
149
149
|
"STEP 1-3: writing or modifying `.cluebase/discoveries.json` itself (only substepId removal allowed when re-running an earlier STEP)",
|
|
150
|
-
"STEP 5+ (= /cluebase-implement and later): code edits, env
|
|
150
|
+
"STEP 5+ (= /cluebase-implement and later): code edits, `.env.cluebase` handling, and connectivity checks",
|
|
151
151
|
],
|
|
152
152
|
completion:
|
|
153
153
|
"`setup-discover-check` reports passed=true, `_progress.completed_substeps` is patched with `step4_check`, and the Japanese hand-off line for STEP 5 is printed. On failure: the user is told exactly which earlier STEP to re-run.",
|
|
@@ -161,7 +161,7 @@ const STEP_FILES = [
|
|
|
161
161
|
id: "step5_implement",
|
|
162
162
|
title: "STEP 5 — Implement (SDK call insertion + env file write)",
|
|
163
163
|
singleGoal:
|
|
164
|
-
"The ONLY goal of this command is to insert Cluebase SDK lifecycle calls (cluebase.init / cluebase.identify / cluebase.group / cluebase.reset + observer wire-ups) at the file:line positions recorded in `.cluebase/discoveries.json`, add the SDK as a package-manifest dependency, run the install command, and
|
|
164
|
+
"The ONLY goal of this command is to insert Cluebase SDK lifecycle calls (cluebase.init / cluebase.identify / cluebase.group / cluebase.reset + observer wire-ups) at the file:line positions recorded in `.cluebase/discoveries.json`, add the SDK as a package-manifest dependency, run the install command, and verify the CLI-owned `.env.cluebase` configuration. Do not create or edit service-local `.env*` files. NO discoveries.json edits, NO bash validation, NO connectivity checks.",
|
|
165
165
|
scopeOut: [
|
|
166
166
|
"STEP 1-4: modifying `.cluebase/discoveries.json` (it is read-only here)",
|
|
167
167
|
"STEP 6 (= /cluebase-implement-check): bash static check + diff snapshot generation",
|
|
@@ -170,7 +170,7 @@ const STEP_FILES = [
|
|
|
170
170
|
"STEP 9 (= /cluebase-doctor): real-server connectivity check",
|
|
171
171
|
],
|
|
172
172
|
completion:
|
|
173
|
-
"All lifecycle / observer call sites in discoveries.json are inserted via Edit, SDK install is run successfully, env
|
|
173
|
+
"All lifecycle / observer call sites in discoveries.json are inserted via Edit, SDK install is run successfully, `.env.cluebase` is verified without exposing its secret, `_progress.completed_substeps` is patched with `step5_implement`, and the Japanese hand-off line for STEP 6 is printed.",
|
|
174
174
|
nextHandoff: "/cluebase-implement-check",
|
|
175
175
|
},
|
|
176
176
|
{
|
|
@@ -237,7 +237,7 @@ const STEP_FILES = [
|
|
|
237
237
|
id: "step9_doctor",
|
|
238
238
|
title: "STEP 9 — Production-level setup verification",
|
|
239
239
|
singleGoal:
|
|
240
|
-
"The ONLY goal of this command is to run `setup-doctor --local` via Bash. It probes Cluebase token issue, browser ingest, and backend ingest with SDK-equivalent payloads
|
|
240
|
+
"The ONLY goal of this command is to run `setup-doctor --local` via Bash. It reads the root `.env.cluebase` sections, probes Cluebase token issue, browser ingest, and backend ingest with SDK-equivalent payloads, checks downstream browser/backend batch publish evidence, scans for forbidden customer-backend Cluebase routes, and runs 30 data-quality checks. `/cluebase-doctor` is intentionally STATELESS — re-run any time after fixes.",
|
|
241
241
|
scopeOut: [
|
|
242
242
|
"STEP 1-8 (= /cluebase-discover .. /cluebase-events): anything that modifies discoveries.json, customer code, business-events.json, or runs the static check",
|
|
243
243
|
"Persisting progress (this STEP is stateless — DO NOT write to the `_progress` array; it is intentionally re-runnable)",
|