@clue-ai/cli 0.0.6 → 0.0.8
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 +11 -14
- package/bin/clue-cli.mjs +114 -21
- package/commands/claude-code/clue-init.md +6 -5
- package/commands/codex/clue-init.md +6 -5
- package/package.json +1 -1
- package/src/ai-provider.mjs +3 -2
- package/src/command-spec.mjs +2 -0
- package/src/contracts.mjs +5 -4
- package/src/init-tool.mjs +5 -6
- package/src/public-schema.cjs +47 -0
- package/src/semantic-agent-runner.mjs +157 -0
- package/src/semantic-ai-config.mjs +17 -0
- package/src/semantic-ci.mjs +419 -57
- package/src/setup-check.mjs +28 -2
- package/src/setup-prepare.mjs +181 -14
- package/src/setup-tool.mjs +159 -11
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ clue-ai lifecycle-apply --plan clue-lifecycle-plan.json --repo .
|
|
|
16
16
|
clue-ai setup-check --framework fastapi --backend-root-path backend --repo . --target codex --require-sdk-lifecycle
|
|
17
17
|
clue-ai setup-watch --project-key <key> --environment dev --clue-api-base-url <clue-api-base-url> --watch-targets frontend:web[init,identify,set-account,event-sent]=<frontend-url>,backend:api[init,identify,set-account,logout,event-sent]=<backend-url>
|
|
18
18
|
clue-ai init --request clue-init-request.json --repo .
|
|
19
|
-
clue-ai semantic-
|
|
19
|
+
clue-ai semantic-gen --request clue-semantic-request.json --repo .
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
`/clue-init` is the standard user-facing command for Codex and Claude Code
|
|
@@ -25,7 +25,7 @@ tool request/report contract as `clue-ai init`.
|
|
|
25
25
|
|
|
26
26
|
`clue-ai semantic-workflow` mechanically writes the GitHub Actions workflow that
|
|
27
27
|
generates `.clue/semantic-request.runtime.json` only inside CI and then runs
|
|
28
|
-
`clue-ai semantic-
|
|
28
|
+
`clue-ai semantic-gen`. Do not commit runtime semantic request files to client
|
|
29
29
|
repositories.
|
|
30
30
|
|
|
31
31
|
`clue-ai setup` installs the target AI-tool skills and, when backend routes are
|
|
@@ -66,22 +66,19 @@ detected service.
|
|
|
66
66
|
|
|
67
67
|
## Required Environment
|
|
68
68
|
|
|
69
|
-
- `CLUE_API_KEY
|
|
70
|
-
- `
|
|
71
|
-
- `
|
|
72
|
-
- `CLUE_AI_MODEL`
|
|
69
|
+
- `CLUE_API_KEY`: Clue setup screen issues this value.
|
|
70
|
+
- `CLUE_AI_PROVIDER_API_KEY`: the customer's AI provider key, not a Clue key. For Codex/OpenAI, create it at `https://platform.openai.com/api-keys`. For Claude Code/Anthropic, create it at `https://console.anthropic.com/settings/keys`.
|
|
71
|
+
- `CLUE_AI_PROVIDER`: generated from setup target when possible: `codex -> openai`, `claude_code -> anthropic`.
|
|
72
|
+
- `CLUE_AI_MODEL`: generated with a concrete default. OpenAI examples: `gpt-5.4-mini`, `gpt-5.5`, `gpt-5.4-nano`. Anthropic examples: `claude-sonnet-4-6`, `claude-opus-4-7`, `claude-haiku-4-5-20251001`.
|
|
73
73
|
|
|
74
|
-
`
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
must match the selected provider. `CLUE_AI_MODEL` is the selected provider's model
|
|
78
|
-
name. Keep it in environment configuration so provider/model upgrades do not
|
|
79
|
-
require CLI code changes.
|
|
74
|
+
`clue-ai semantic-gen` runs semantic AI inside the customer repository CI with
|
|
75
|
+
the customer-configured AI provider key. Clue receives only the final
|
|
76
|
+
privacy-safe semantic snapshot.
|
|
80
77
|
|
|
81
78
|
## Boundaries
|
|
82
79
|
|
|
83
80
|
- The tool may read allowed source paths in the client repository.
|
|
84
81
|
- The tool must not read `.env`, secrets, logs, dumps, build output, or vendor directories.
|
|
85
|
-
-
|
|
86
|
-
- Raw source code, raw SQL, bind values, function names, class names, file paths, and import graphs are not sent to Clue
|
|
82
|
+
- Semantic generation calls the configured AI provider from customer CI.
|
|
83
|
+
- Raw source code, raw SQL, bind values, function names, class names, file paths, and import graphs are not sent to Clue.
|
|
87
84
|
- `clue-layer.yaml` and `clue-semantic.yaml` are not committed to client repositories.
|
package/bin/clue-cli.mjs
CHANGED
|
@@ -15,6 +15,10 @@ import { runSetupCheck } from "../src/setup-check.mjs";
|
|
|
15
15
|
import { runSetupDetect } from "../src/setup-detect.mjs";
|
|
16
16
|
import { runSetupPrepare } from "../src/setup-prepare.mjs";
|
|
17
17
|
import { installSetupSkills } from "../src/setup-tool.mjs";
|
|
18
|
+
import {
|
|
19
|
+
semanticAgentSkillBundle,
|
|
20
|
+
validateSemanticAgentSkillBundle,
|
|
21
|
+
} from "../src/semantic-agent-runner.mjs";
|
|
18
22
|
|
|
19
23
|
const parseArgs = (argv) => {
|
|
20
24
|
const [command = "help", ...tokens] = argv;
|
|
@@ -36,6 +40,15 @@ const parseArgs = (argv) => {
|
|
|
36
40
|
|
|
37
41
|
const readJson = async (path) => JSON.parse(await readFile(path, "utf8"));
|
|
38
42
|
|
|
43
|
+
const readTextIfExists = async (path) => {
|
|
44
|
+
try {
|
|
45
|
+
return await readFile(path, "utf8");
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error?.code === "ENOENT") return "";
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
39
52
|
const writeJsonIfRequested = async ({ repoRoot, outputPath, value }) => {
|
|
40
53
|
if (typeof outputPath !== "string") return false;
|
|
41
54
|
const resolvedRepoRoot = resolve(repoRoot);
|
|
@@ -219,6 +232,73 @@ const resolveTargetUrlFromEnv = ({ target, env }) => {
|
|
|
219
232
|
const shouldAskQuestions = ({ flags }) =>
|
|
220
233
|
!flags.has("yes") && process.stdin.isTTY && process.stdout.isTTY;
|
|
221
234
|
|
|
235
|
+
const isGitignoreEntryPresent = (content, entry) =>
|
|
236
|
+
content
|
|
237
|
+
.split(/\r?\n/)
|
|
238
|
+
.map((line) => line.trim())
|
|
239
|
+
.includes(entry);
|
|
240
|
+
|
|
241
|
+
const appendGitignoreEntry = async ({ repoRoot, entry }) => {
|
|
242
|
+
const gitignorePath = resolve(repoRoot, ".gitignore");
|
|
243
|
+
const current = await readTextIfExists(gitignorePath);
|
|
244
|
+
if (isGitignoreEntryPresent(current, entry)) {
|
|
245
|
+
return "already_ignored";
|
|
246
|
+
}
|
|
247
|
+
const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
|
|
248
|
+
await writeFile(gitignorePath, `${current}${prefix}${entry}\n`, "utf8");
|
|
249
|
+
return "added";
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const maybeProtectEnvironmentGuide = async ({
|
|
253
|
+
repoRoot,
|
|
254
|
+
envFilePath,
|
|
255
|
+
flags,
|
|
256
|
+
}) => {
|
|
257
|
+
if (typeof envFilePath !== "string" || !envFilePath.trim()) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const gitignorePath = resolve(repoRoot, ".gitignore");
|
|
261
|
+
const current = await readTextIfExists(gitignorePath);
|
|
262
|
+
if (isGitignoreEntryPresent(current, envFilePath)) {
|
|
263
|
+
return {
|
|
264
|
+
env_file_path: envFilePath,
|
|
265
|
+
gitignore_status: "already_ignored",
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (flags.has("yes")) {
|
|
269
|
+
await appendGitignoreEntry({ repoRoot, entry: envFilePath });
|
|
270
|
+
return {
|
|
271
|
+
env_file_path: envFilePath,
|
|
272
|
+
gitignore_status: "added",
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (shouldAskQuestions({ flags })) {
|
|
276
|
+
const rl = createInterface({
|
|
277
|
+
input: process.stdin,
|
|
278
|
+
output: process.stdout,
|
|
279
|
+
});
|
|
280
|
+
try {
|
|
281
|
+
const answer = await rl.question(
|
|
282
|
+
`${envFilePath} は秘密情報を含みます。.gitignore に追加しますか? [Y/n] `,
|
|
283
|
+
);
|
|
284
|
+
if (!answer.trim() || /^y(es)?$/i.test(answer.trim())) {
|
|
285
|
+
await appendGitignoreEntry({ repoRoot, entry: envFilePath });
|
|
286
|
+
return {
|
|
287
|
+
env_file_path: envFilePath,
|
|
288
|
+
gitignore_status: "added",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
} finally {
|
|
292
|
+
rl.close();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
env_file_path: envFilePath,
|
|
297
|
+
gitignore_status: "not_ignored",
|
|
298
|
+
warning: `${envFilePath} contains secrets. Do not commit it.`,
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
|
|
222
302
|
const confirmTargetUrls = async ({ flags, watchTargets, env }) => {
|
|
223
303
|
const initialTargets = watchTargets.map((target) => ({
|
|
224
304
|
...target,
|
|
@@ -670,40 +750,28 @@ const usage = () =>
|
|
|
670
750
|
" clue-ai setup --clue-api-key <key> --clue-api-base-url <url> --project-key <key> --environment dev",
|
|
671
751
|
" clue-ai setup-detect --repo .",
|
|
672
752
|
" clue-ai semantic-inventory --framework fastapi --backend-root-path backend --repo . --output .clue/semantic-routes.json",
|
|
753
|
+
" clue-ai semantic-agent-skills --output .clue/semantic-agent-skills.json",
|
|
673
754
|
" clue-ai semantic-workflow --framework fastapi --backend-root-path backend --repo .",
|
|
674
755
|
" clue-ai lifecycle-apply --plan clue-lifecycle-plan.json --repo .",
|
|
675
756
|
" clue-ai setup-check --framework fastapi --backend-root-path backend --repo .",
|
|
676
757
|
" clue-ai setup-watch --local",
|
|
677
758
|
" clue-ai setup-watch --project-key <key> --environment dev --clue-api-base-url <clue-api-base-url> --watch-targets frontend:web[init,identify,set-account,logout,event-sent]=<frontend-url>,backend:api[init,identify,set-account,logout,event-sent]=<backend-url>",
|
|
678
759
|
" clue-ai init --request clue-init-request.json --repo .",
|
|
679
|
-
" clue-ai semantic-
|
|
680
|
-
" clue-ai semantic-
|
|
760
|
+
" clue-ai semantic-gen --request clue-semantic-request.json --repo . [--previous-snapshot-file previous.json]",
|
|
761
|
+
" clue-ai semantic-gen --request-env CLUE_SEMANTIC_REQUEST_JSON --repo .",
|
|
681
762
|
].join("\n");
|
|
682
763
|
|
|
683
764
|
const renderEnvironmentInstructions = (instructions) => {
|
|
684
765
|
if (!instructions || instructions.status !== "ready") {
|
|
685
766
|
return "";
|
|
686
767
|
}
|
|
687
|
-
const lines = [
|
|
688
|
-
for (const block of instructions.service_env_blocks) {
|
|
689
|
-
lines.push(
|
|
690
|
-
`[${block.kind}] ${block.root_path} (${block.env_file_candidates.join(" or ")})`,
|
|
691
|
-
block.env_block,
|
|
692
|
-
"",
|
|
693
|
-
);
|
|
694
|
-
}
|
|
695
|
-
lines.push(
|
|
696
|
-
"GitHub Secrets",
|
|
697
|
-
...instructions.ci_github.secrets.map(
|
|
698
|
-
(entry) => `${entry.name}=${entry.value}`,
|
|
699
|
-
),
|
|
768
|
+
const lines = [
|
|
700
769
|
"",
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
),
|
|
770
|
+
`環境変数の設定内容を ${instructions.env_file_path} に書き出しました。`,
|
|
771
|
+
`Step2で ${instructions.env_file_path} を開き、各サービスの env と GitHub Secrets/Variables に反映してください。`,
|
|
772
|
+
`${instructions.env_file_path} は秘密情報を含むため、コミットしないでください。`,
|
|
705
773
|
"",
|
|
706
|
-
|
|
774
|
+
];
|
|
707
775
|
return `${lines.join("\n")}\n`;
|
|
708
776
|
};
|
|
709
777
|
|
|
@@ -754,6 +822,14 @@ const main = async () => {
|
|
|
754
822
|
environment: flags.get("environment"),
|
|
755
823
|
},
|
|
756
824
|
});
|
|
825
|
+
const environmentFileProtection = await maybeProtectEnvironmentGuide({
|
|
826
|
+
repoRoot,
|
|
827
|
+
envFilePath: preparation.environment_instructions?.env_file_path,
|
|
828
|
+
flags,
|
|
829
|
+
});
|
|
830
|
+
if (environmentFileProtection) {
|
|
831
|
+
preparation.environment_file_protection = environmentFileProtection;
|
|
832
|
+
}
|
|
757
833
|
const environmentInstructions = renderEnvironmentInstructions(
|
|
758
834
|
preparation.environment_instructions,
|
|
759
835
|
);
|
|
@@ -818,6 +894,17 @@ const main = async () => {
|
|
|
818
894
|
return;
|
|
819
895
|
}
|
|
820
896
|
|
|
897
|
+
if (command === "semantic-agent-skills") {
|
|
898
|
+
const bundle = semanticAgentSkillBundle();
|
|
899
|
+
await writeJsonIfRequested({
|
|
900
|
+
repoRoot,
|
|
901
|
+
outputPath: flags.get("output"),
|
|
902
|
+
value: bundle,
|
|
903
|
+
});
|
|
904
|
+
process.stdout.write(`${JSON.stringify(bundle, null, 2)}\n`);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
|
|
821
908
|
if (command === "lifecycle-apply") {
|
|
822
909
|
const planPath = flags.get("plan");
|
|
823
910
|
if (typeof planPath !== "string") {
|
|
@@ -874,18 +961,24 @@ const main = async () => {
|
|
|
874
961
|
return;
|
|
875
962
|
}
|
|
876
963
|
|
|
877
|
-
if (command === "semantic-
|
|
964
|
+
if (command === "semantic-gen") {
|
|
878
965
|
const previousSnapshotPath =
|
|
879
966
|
flags.get("previous-snapshot") ?? flags.get("previous-snapshot-file");
|
|
880
967
|
const previousSnapshot =
|
|
881
968
|
typeof previousSnapshotPath === "string"
|
|
882
969
|
? await readJson(resolve(previousSnapshotPath))
|
|
883
970
|
: undefined;
|
|
971
|
+
const agentSkillsPath = flags.get("agent-skills-file");
|
|
972
|
+
const agentSkills =
|
|
973
|
+
typeof agentSkillsPath === "string"
|
|
974
|
+
? validateSemanticAgentSkillBundle(await readJson(resolve(agentSkillsPath)))
|
|
975
|
+
: undefined;
|
|
884
976
|
const result = await runSemanticCi({
|
|
885
977
|
repoRoot,
|
|
886
978
|
request,
|
|
887
979
|
env: process.env,
|
|
888
980
|
previousSnapshot,
|
|
981
|
+
agentSkills,
|
|
889
982
|
});
|
|
890
983
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
891
984
|
return;
|
|
@@ -14,13 +14,14 @@ Required fields:
|
|
|
14
14
|
Required environment:
|
|
15
15
|
|
|
16
16
|
- `CLUE_API_KEY`
|
|
17
|
-
- `
|
|
18
|
-
- `
|
|
17
|
+
- `CLUE_AI_PROVIDER_API_KEY`
|
|
18
|
+
- `CLUE_AI_PROVIDER`
|
|
19
19
|
- `CLUE_AI_MODEL`
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
Semantic snapshot generation runs inside the customer repository CI with the
|
|
22
|
+
customer-configured AI provider key. Clue must receive only the final
|
|
23
|
+
privacy-safe semantic snapshot, not raw source code, prompts, completions, or AI
|
|
24
|
+
provider keys.
|
|
24
25
|
|
|
25
26
|
Behavior:
|
|
26
27
|
|
|
@@ -14,13 +14,14 @@ Required fields:
|
|
|
14
14
|
Required environment:
|
|
15
15
|
|
|
16
16
|
- `CLUE_API_KEY`
|
|
17
|
-
- `
|
|
18
|
-
- `
|
|
17
|
+
- `CLUE_AI_PROVIDER_API_KEY`
|
|
18
|
+
- `CLUE_AI_PROVIDER`
|
|
19
19
|
- `CLUE_AI_MODEL`
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
Semantic snapshot generation runs inside the customer repository CI with the
|
|
22
|
+
customer-configured AI provider key. Clue must receive only the final
|
|
23
|
+
privacy-safe semantic snapshot, not raw source code, prompts, completions, or AI
|
|
24
|
+
provider keys.
|
|
24
25
|
|
|
25
26
|
Behavior:
|
|
26
27
|
|
package/package.json
CHANGED
package/src/ai-provider.mjs
CHANGED
|
@@ -15,8 +15,7 @@ const normalizeProvider = (value) => {
|
|
|
15
15
|
const trimTrailingSlash = (value) => String(value).replace(/\/+$/, "");
|
|
16
16
|
|
|
17
17
|
const providerBaseUrl = ({ provider, env = {}, request = {} }) => {
|
|
18
|
-
const explicit =
|
|
19
|
-
request.ai_provider_base_url || env.CLUE_AI_PROVIDER_BASE_URL || null;
|
|
18
|
+
const explicit = env.CLUE_AI_PROVIDER_BASE_URL || null;
|
|
20
19
|
if (explicit) {
|
|
21
20
|
return trimTrailingSlash(explicit);
|
|
22
21
|
}
|
|
@@ -106,6 +105,7 @@ export const callJsonAiProvider = async ({
|
|
|
106
105
|
body: JSON.stringify({
|
|
107
106
|
model: config.model,
|
|
108
107
|
max_tokens: 4096,
|
|
108
|
+
temperature: 0,
|
|
109
109
|
system,
|
|
110
110
|
messages: [{ role: "user", content: user }],
|
|
111
111
|
tools: [
|
|
@@ -129,6 +129,7 @@ export const callJsonAiProvider = async ({
|
|
|
129
129
|
},
|
|
130
130
|
body: JSON.stringify({
|
|
131
131
|
model: config.model,
|
|
132
|
+
temperature: 0,
|
|
132
133
|
messages: [
|
|
133
134
|
{ role: "system", content: system },
|
|
134
135
|
{ role: "user", content: user },
|
package/src/command-spec.mjs
CHANGED
|
@@ -4,6 +4,7 @@ export const REQUIRED_SECRET_NAMES = [
|
|
|
4
4
|
"CLUE_API_KEY",
|
|
5
5
|
"CLUE_AI_PROVIDER_API_KEY",
|
|
6
6
|
];
|
|
7
|
+
export const REQUIRED_VARIABLE_NAMES = ["CLUE_AI_PROVIDER", "CLUE_AI_MODEL"];
|
|
7
8
|
|
|
8
9
|
export const CLUE_INIT_COMMAND_FIELDS = [
|
|
9
10
|
"project_key",
|
|
@@ -22,6 +23,7 @@ export const CLUE_INIT_COMMAND = {
|
|
|
22
23
|
report_contract: "clueInitToolReportSchema",
|
|
23
24
|
required_fields: CLUE_INIT_COMMAND_FIELDS,
|
|
24
25
|
required_secret_names: REQUIRED_SECRET_NAMES,
|
|
26
|
+
required_variable_names: REQUIRED_VARIABLE_NAMES,
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
export const commandSpecs = [CLUE_INIT_COMMAND];
|
package/src/contracts.mjs
CHANGED
|
@@ -2,7 +2,6 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
|
|
3
3
|
const require = createRequire(import.meta.url);
|
|
4
4
|
const schemaPackage = require("./public-schema.cjs");
|
|
5
|
-
|
|
6
5
|
const {
|
|
7
6
|
clueInitToolRequestSchema,
|
|
8
7
|
clueInitToolReportSchema,
|
|
@@ -63,6 +62,7 @@ export const validateInitRequest = (input) => {
|
|
|
63
62
|
export const buildInitReport = ({
|
|
64
63
|
request,
|
|
65
64
|
lifecycleInsertions,
|
|
65
|
+
requiredVariables = [],
|
|
66
66
|
warnings = [],
|
|
67
67
|
}) =>
|
|
68
68
|
parseWithSchema(
|
|
@@ -72,6 +72,7 @@ export const buildInitReport = ({
|
|
|
72
72
|
ci_workflow_path: request.ci_workflow_path,
|
|
73
73
|
ci_workflow_added: true,
|
|
74
74
|
required_secrets: ["CLUE_API_KEY", "CLUE_AI_PROVIDER_API_KEY"],
|
|
75
|
+
required_variables: requiredVariables,
|
|
75
76
|
lifecycle_insertions: lifecycleInsertions,
|
|
76
77
|
semantic_generation_timing: "after_merge_ci",
|
|
77
78
|
semantic_preview_generated: false,
|
|
@@ -89,7 +90,6 @@ export const validateSemanticCiRequest = (input) => ({
|
|
|
89
90
|
project_key: nonEmpty(input.project_key, "project_key"),
|
|
90
91
|
environment: nonEmpty(input.environment, "environment"),
|
|
91
92
|
service_key: nonEmpty(input.service_key, "service_key"),
|
|
92
|
-
ai_provider: optionalNonEmpty(input.ai_provider) ?? "openai",
|
|
93
93
|
repository: {
|
|
94
94
|
provider: nonEmpty(input.repository?.provider, "repository.provider"),
|
|
95
95
|
repository_id: nonEmpty(
|
|
@@ -139,8 +139,9 @@ export const validateSemanticCiRequest = (input) => ({
|
|
|
139
139
|
"excluded_source_paths",
|
|
140
140
|
),
|
|
141
141
|
clue_api_base_url: nonEmpty(input.clue_api_base_url, "clue_api_base_url"),
|
|
142
|
-
ai_provider_base_url:
|
|
143
|
-
|
|
142
|
+
ai_provider_base_url: null,
|
|
143
|
+
ai_provider: optionalNonEmpty(input.ai_provider),
|
|
144
|
+
ai_model: optionalNonEmpty(input.ai_model),
|
|
144
145
|
});
|
|
145
146
|
|
|
146
147
|
export const buildOperationSourceKey = (method, pathTemplate) =>
|
package/src/init-tool.mjs
CHANGED
|
@@ -110,7 +110,6 @@ const workflowRequestPayload = (request) =>
|
|
|
110
110
|
},
|
|
111
111
|
service: {
|
|
112
112
|
service_key: request.service_key,
|
|
113
|
-
root_path: request.allowed_source_paths[0],
|
|
114
113
|
framework: request.framework,
|
|
115
114
|
language: "python",
|
|
116
115
|
},
|
|
@@ -118,8 +117,6 @@ const workflowRequestPayload = (request) =>
|
|
|
118
117
|
excluded_source_paths: request.excluded_source_paths,
|
|
119
118
|
clue_api_base_url:
|
|
120
119
|
request.clue_api_base_url ?? "${{ vars.CLUE_API_BASE_URL }}",
|
|
121
|
-
ai_provider: "${{ vars.CLUE_AI_PROVIDER }}",
|
|
122
|
-
ai_model: "${{ vars.CLUE_AI_MODEL }}",
|
|
123
120
|
},
|
|
124
121
|
null,
|
|
125
122
|
10,
|
|
@@ -157,10 +154,11 @@ jobs:
|
|
|
157
154
|
CLUE_API_KEY: \${{ secrets.CLUE_API_KEY }}
|
|
158
155
|
CLUE_AI_PROVIDER_API_KEY: \${{ secrets.CLUE_AI_PROVIDER_API_KEY }}
|
|
159
156
|
CLUE_AI_PROVIDER: \${{ vars.CLUE_AI_PROVIDER }}
|
|
157
|
+
CLUE_AI_MODEL: \${{ vars.CLUE_AI_MODEL }}
|
|
160
158
|
CLUE_SEMANTIC_REQUEST_JSON: |
|
|
161
159
|
${indentMultiline(workflowRequestPayload(request), 12)}
|
|
162
160
|
run: |
|
|
163
|
-
npx @clue-ai/cli semantic-
|
|
161
|
+
npx @clue-ai/cli semantic-gen --request-env CLUE_SEMANTIC_REQUEST_JSON --repo .
|
|
164
162
|
`;
|
|
165
163
|
|
|
166
164
|
export const writeSemanticWorkflow = async ({ repoRoot, request }) => {
|
|
@@ -173,6 +171,7 @@ export const writeSemanticWorkflow = async ({ repoRoot, request }) => {
|
|
|
173
171
|
required_secrets: ["CLUE_API_KEY", "CLUE_AI_PROVIDER_API_KEY"],
|
|
174
172
|
required_variables: [
|
|
175
173
|
"CLUE_AI_PROVIDER",
|
|
174
|
+
"CLUE_AI_MODEL",
|
|
176
175
|
...(usesGithubVariable(
|
|
177
176
|
request.project_key ?? "${{ vars.CLUE_PROJECT_KEY }}",
|
|
178
177
|
)
|
|
@@ -188,7 +187,6 @@ export const writeSemanticWorkflow = async ({ repoRoot, request }) => {
|
|
|
188
187
|
)
|
|
189
188
|
? ["CLUE_API_BASE_URL"]
|
|
190
189
|
: []),
|
|
191
|
-
"CLUE_AI_MODEL",
|
|
192
190
|
],
|
|
193
191
|
runtime_request_committed: false,
|
|
194
192
|
semantic_generation_timing: "after_merge_ci",
|
|
@@ -204,7 +202,7 @@ export const runInitTool = async ({
|
|
|
204
202
|
lifecyclePlanner = planLifecycleInsertions,
|
|
205
203
|
}) => {
|
|
206
204
|
const request = validateInitRequest(rawRequest);
|
|
207
|
-
await writeSemanticWorkflow({ repoRoot, request });
|
|
205
|
+
const workflowResult = await writeSemanticWorkflow({ repoRoot, request });
|
|
208
206
|
const lifecyclePlan = await lifecyclePlanner({ repoRoot, request, env });
|
|
209
207
|
const lifecycleResult = await applyLifecyclePlan({
|
|
210
208
|
repoRoot,
|
|
@@ -214,6 +212,7 @@ export const runInitTool = async ({
|
|
|
214
212
|
return buildInitReport({
|
|
215
213
|
request,
|
|
216
214
|
lifecycleInsertions: lifecycleResult.lifecycleInsertions,
|
|
215
|
+
requiredVariables: workflowResult.required_variables,
|
|
217
216
|
warnings: [...lifecycleResult.warnings],
|
|
218
217
|
});
|
|
219
218
|
};
|
package/src/public-schema.cjs
CHANGED
|
@@ -22,6 +22,29 @@ const clueInitToolRequiredSecretsSchema = zod_1.z
|
|
|
22
22
|
}
|
|
23
23
|
})
|
|
24
24
|
.transform((value) => Array.from(new Set(value)));
|
|
25
|
+
const clueInitToolRequiredVariableSchema = zod_1.z.enum([
|
|
26
|
+
"CLUE_AI_PROVIDER",
|
|
27
|
+
"CLUE_AI_MODEL",
|
|
28
|
+
"CLUE_PROJECT_KEY",
|
|
29
|
+
"CLUE_ENVIRONMENT",
|
|
30
|
+
"CLUE_API_BASE_URL",
|
|
31
|
+
]);
|
|
32
|
+
const clueInitToolRequiredVariablesSchema = zod_1.z
|
|
33
|
+
.array(clueInitToolRequiredVariableSchema)
|
|
34
|
+
.superRefine((value, context) => {
|
|
35
|
+
for (const variable of [
|
|
36
|
+
"CLUE_AI_PROVIDER",
|
|
37
|
+
"CLUE_AI_MODEL",
|
|
38
|
+
]) {
|
|
39
|
+
if (!value.includes(variable)) {
|
|
40
|
+
context.addIssue({
|
|
41
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
42
|
+
message: `${variable} is required`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.transform((value) => Array.from(new Set(value)));
|
|
25
48
|
const clueInitToolRequestSchema = zod_1.z.object({
|
|
26
49
|
target_tool: clueInitToolTargetSchema,
|
|
27
50
|
project_key: nonEmptyStringSchema,
|
|
@@ -48,6 +71,7 @@ const clueInitToolReportSchema = zod_1.z.object({
|
|
|
48
71
|
ci_workflow_path: nonEmptyStringSchema,
|
|
49
72
|
ci_workflow_added: zod_1.z.literal(true),
|
|
50
73
|
required_secrets: clueInitToolRequiredSecretsSchema,
|
|
74
|
+
required_variables: clueInitToolRequiredVariablesSchema,
|
|
51
75
|
lifecycle_insertions: zod_1.z.array(clueInitToolLifecycleInsertionSchema),
|
|
52
76
|
semantic_generation_timing: zod_1.z.literal("after_merge_ci"),
|
|
53
77
|
semantic_preview_generated: zod_1.z.literal(false),
|
|
@@ -211,6 +235,27 @@ const semanticSnapshotAnalysisSummarySchema = zod_1.z
|
|
|
211
235
|
.default(0),
|
|
212
236
|
})
|
|
213
237
|
.strict();
|
|
238
|
+
const semanticSnapshotGenerationContractSchema = zod_1.z
|
|
239
|
+
.object({
|
|
240
|
+
schema_version: zod_1.z.number().int().positive(),
|
|
241
|
+
analyzer_version: nonEmptyStringSchema,
|
|
242
|
+
route_prompt_contract_version: nonEmptyStringSchema,
|
|
243
|
+
reuse_prompt_contract_version: nonEmptyStringSchema,
|
|
244
|
+
selector_prompt_contract_version: nonEmptyStringSchema,
|
|
245
|
+
privacy_sanitizer_version: nonEmptyStringSchema,
|
|
246
|
+
})
|
|
247
|
+
.strict();
|
|
248
|
+
const semanticSnapshotAiRuntimeSchema = zod_1.z
|
|
249
|
+
.object({
|
|
250
|
+
mode: zod_1.z.enum(["client_ci"]),
|
|
251
|
+
provider: nonEmptyStringSchema,
|
|
252
|
+
model: nonEmptyStringSchema,
|
|
253
|
+
model_source: zod_1.z.literal("customer_ci_secret"),
|
|
254
|
+
runner_version: nonEmptyStringSchema,
|
|
255
|
+
role_ids: zod_1.z.array(nonEmptyStringSchema).min(1),
|
|
256
|
+
temperature: zod_1.z.literal(0),
|
|
257
|
+
})
|
|
258
|
+
.strict();
|
|
214
259
|
const semanticCandidateSchema = zod_1.z
|
|
215
260
|
.object({
|
|
216
261
|
label: nonEmptyStringSchema,
|
|
@@ -460,6 +505,8 @@ const semanticSnapshotRequestSchema = zod_1.z
|
|
|
460
505
|
idempotency_key: nonEmptyStringSchema,
|
|
461
506
|
schema_version: zod_1.z.number().int().positive().default(1),
|
|
462
507
|
semantic_snapshot_version: nonEmptyStringSchema.optional(),
|
|
508
|
+
generation_contract: semanticSnapshotGenerationContractSchema.optional(),
|
|
509
|
+
ai_runtime: semanticSnapshotAiRuntimeSchema.optional(),
|
|
463
510
|
generated_at: zod_1.z.string().datetime({ offset: true }),
|
|
464
511
|
repository: semanticSnapshotRepositorySchema,
|
|
465
512
|
service: semanticSnapshotServiceSchema,
|