candy-debug 1.1.0 → 1.1.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 CHANGED
@@ -33,14 +33,14 @@ candy-debug ./my-project > review.json
33
33
  candy-debug ./my-project --scan-only
34
34
 
35
35
  # Amazon Bedrock で動かす(モデルは環境変数 BEDROCK_MODEL_ID)
36
- export AWS_REGION=ap-northeast-1 BEDROCK_MODEL_ID=jp.anthropic.claude-sonnet-4-6
36
+ export AWS_REGION=ap-northeast-1 BEDROCK_MODEL_ID=zai.glm-5
37
37
  cdbg ./my-project --provider bedrock -o review.json
38
38
 
39
39
  # CI で使う: warning 以上の指摘があれば終了コード 3(JSON は書き終えてから返す)
40
40
  cdbg . --provider bedrock --fail-on warning -o review.json
41
41
 
42
- # レビュー結果の改善案をソースへ当てる(このときだけ対象フォルダへ書き込む)
43
- cdbg fix review.json --dir . --summary pr.md
42
+ # 指摘のあるファイルを AI に直させる(このときだけ対象フォルダへ書き込む)
43
+ cdbg fix review.json --dir . --provider bedrock --summary pr.md
44
44
  ```
45
45
 
46
46
  Bedrock の認証は AWS SDK の標準の順序(環境変数・`~/.aws`・SSO・CodeBuild/ECS の実行ロールなど)で探します。
@@ -61,7 +61,11 @@ CodePipeline に組み込んだ例は、リポジトリの `examples/roxy-pipeli
61
61
  | `--doctor [--json]` | AI を呼べるかの確認 |
62
62
 
63
63
  `cdbg fix <review.json>` のオプション: `--dir <folder>`(既定 カレント)/ `--min-severity <sev>`(既定 `warning`)/
64
- `--summary <file>`(プルリクエスト用の Markdown)/ `--dry-run`
64
+ `--summary <file>`(プルリクエスト用の Markdown)/ `--dry-run`。`--provider` / `--model` / `--region` もレビューと同じく使えます。
65
+
66
+ `cdbg fix` は、指摘のあるファイルごとにファイル全体と指摘を AI へ送り、直したファイル全体を返させます。
67
+ JavaScript / JSON は構文検査(`node --check`)を通ったものだけ書き込みます。AI が途中で切れた応答を返したとき・
68
+ 対象フォルダの外を指す指摘・AI が「直した」と申告しなかった指摘は書き込みません。**動作は確かめないので、必ず差分を確認してください。**
65
69
 
66
70
  | 環境変数 | 内容 |
67
71
  | --------------------------- | --------------------------------------------- |
@@ -84,7 +88,7 @@ CodePipeline に組み込んだ例は、リポジトリの `examples/roxy-pipeli
84
88
  {
85
89
  "format": "candy-debug/review",
86
90
  "schemaVersion": 1,
87
- "generator": { "name": "candy-debug", "version": "1.1.0" },
91
+ "generator": { "name": "candy-debug", "version": "1.1.1" },
88
92
  "project": { "name": "my-project", "dir": "/path/to/my-project" },
89
93
  "findings": [/* 指摘 */],
90
94
  "scenario": { "beats": [/* 台詞・表情・コード片・赤枠の位置 */] },
@@ -3900,7 +3900,7 @@ var init_getProfileName = __esm({
3900
3900
 
3901
3901
  // node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.js
3902
3902
  import { createHash } from "node:crypto";
3903
- import { join as join2 } from "node:path";
3903
+ import { join as join3 } from "node:path";
3904
3904
  var getSSOTokenFilepath;
3905
3905
  var init_getSSOTokenFilepath = __esm({
3906
3906
  "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.js"() {
@@ -3908,7 +3908,7 @@ var init_getSSOTokenFilepath = __esm({
3908
3908
  getSSOTokenFilepath = (id) => {
3909
3909
  const hasher = createHash("sha1");
3910
3910
  const cacheName = hasher.update(id).digest("hex");
3911
- return join2(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
3911
+ return join3(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
3912
3912
  };
3913
3913
  }
3914
3914
  });
@@ -3963,24 +3963,24 @@ var init_getConfigData = __esm({
3963
3963
  });
3964
3964
 
3965
3965
  // node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getConfigFilepath.js
3966
- import { join as join3 } from "node:path";
3966
+ import { join as join4 } from "node:path";
3967
3967
  var ENV_CONFIG_PATH, getConfigFilepath;
3968
3968
  var init_getConfigFilepath = __esm({
3969
3969
  "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getConfigFilepath.js"() {
3970
3970
  init_getHomeDir();
3971
3971
  ENV_CONFIG_PATH = "AWS_CONFIG_FILE";
3972
- getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || join3(getHomeDir(), ".aws", "config");
3972
+ getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || join4(getHomeDir(), ".aws", "config");
3973
3973
  }
3974
3974
  });
3975
3975
 
3976
3976
  // node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getCredentialsFilepath.js
3977
- import { join as join4 } from "node:path";
3977
+ import { join as join5 } from "node:path";
3978
3978
  var ENV_CREDENTIALS_PATH, getCredentialsFilepath;
3979
3979
  var init_getCredentialsFilepath = __esm({
3980
3980
  "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getCredentialsFilepath.js"() {
3981
3981
  init_getHomeDir();
3982
3982
  ENV_CREDENTIALS_PATH = "AWS_SHARED_CREDENTIALS_FILE";
3983
- getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || join4(getHomeDir(), ".aws", "credentials");
3983
+ getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || join5(getHomeDir(), ".aws", "credentials");
3984
3984
  }
3985
3985
  });
3986
3986
 
@@ -4060,7 +4060,7 @@ var init_readFile = __esm({
4060
4060
  });
4061
4061
 
4062
4062
  // node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/loadSharedConfigFiles.js
4063
- import { join as join5 } from "node:path";
4063
+ import { join as join6 } from "node:path";
4064
4064
  var swallowError, loadSharedConfigFiles;
4065
4065
  var init_loadSharedConfigFiles = __esm({
4066
4066
  "node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/loadSharedConfigFiles.js"() {
@@ -4078,11 +4078,11 @@ var init_loadSharedConfigFiles = __esm({
4078
4078
  const relativeHomeDirPrefix = "~/";
4079
4079
  let resolvedFilepath = filepath;
4080
4080
  if (filepath.startsWith(relativeHomeDirPrefix)) {
4081
- resolvedFilepath = join5(homeDir, filepath.slice(2));
4081
+ resolvedFilepath = join6(homeDir, filepath.slice(2));
4082
4082
  }
4083
4083
  let resolvedConfigFilepath = configFilepath;
4084
4084
  if (configFilepath.startsWith(relativeHomeDirPrefix)) {
4085
- resolvedConfigFilepath = join5(homeDir, configFilepath.slice(2));
4085
+ resolvedConfigFilepath = join6(homeDir, configFilepath.slice(2));
4086
4086
  }
4087
4087
  const parsedFiles = await Promise.all([
4088
4088
  readFile2(resolvedConfigFilepath, {
@@ -27631,7 +27631,7 @@ var require_dist_cjs14 = __commonJS({
27631
27631
  var { createHash: createHash5, createPrivateKey, createPublicKey, sign: sign2 } = __require("node:crypto");
27632
27632
  var { promises } = __require("node:fs");
27633
27633
  var { homedir: homedir2 } = __require("node:os");
27634
- var { dirname: dirname2, join: join6 } = __require("node:path");
27634
+ var { dirname: dirname2, join: join7 } = __require("node:path");
27635
27635
  var LoginCredentialsFetcher = class _LoginCredentialsFetcher {
27636
27636
  profileData;
27637
27637
  init;
@@ -27795,10 +27795,10 @@ var require_dist_cjs14 = __commonJS({
27795
27795
  await promises.writeFile(tokenFilePath, JSON.stringify(token, null, 2), "utf8");
27796
27796
  }
27797
27797
  getTokenFilePath() {
27798
- const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join6(homedir2(), ".aws", "login", "cache");
27798
+ const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join7(homedir2(), ".aws", "login", "cache");
27799
27799
  const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
27800
27800
  const loginSessionSha256 = createHash5("sha256").update(loginSessionBytes).digest("hex");
27801
- return join6(directory, `${loginSessionSha256}.json`);
27801
+ return join7(directory, `${loginSessionSha256}.json`);
27802
27802
  }
27803
27803
  derToRawSignature(derSignature) {
27804
27804
  let offset = 2;
@@ -32605,7 +32605,7 @@ import { parseArgs } from "node:util";
32605
32605
  // package.json
32606
32606
  var package_default = {
32607
32607
  name: "candy-debug",
32608
- version: "1.1.0",
32608
+ version: "1.1.1",
32609
32609
  description: "\u30ED\u30AD\u30B7\u30FC\u304C\u30B3\u30FC\u30C9\u30EC\u30D3\u30E5\u30FC\u3092\u3057\u3066\u3001\u30CE\u30D9\u30EB\u30B2\u30FC\u30E0\u7528\u306E\u53F0\u672C\uFF08JSON\uFF09\u3092\u4F5C\u308B CLI",
32610
32610
  type: "module",
32611
32611
  bin: {
@@ -32970,8 +32970,10 @@ function parseReviewFile(raw) {
32970
32970
  }
32971
32971
 
32972
32972
  // src/fix/applyFixes.ts
32973
+ import { spawn as spawn2 } from "node:child_process";
32973
32974
  import { promises as fs2 } from "node:fs";
32974
- import { isAbsolute, relative as relative2, resolve } from "node:path";
32975
+ import { tmpdir } from "node:os";
32976
+ import { extname, isAbsolute, join as join2, relative as relative2, resolve } from "node:path";
32975
32977
 
32976
32978
  // src/review/scanner.ts
32977
32979
  import { promises as fs } from "node:fs";
@@ -33219,17 +33221,156 @@ function prioritize(findings, max) {
33219
33221
  return [...keepRest, ...keepPraise];
33220
33222
  }
33221
33223
 
33224
+ // src/review/prompts.ts
33225
+ var REVIEW_SYSTEM = `\u3042\u306A\u305F\u306F\u7D4C\u9A13\u8C4A\u5BCC\u306A\u30B3\u30FC\u30C9\u30EC\u30D3\u30E5\u30A2\u30FC\u3067\u3059\u3002\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u307E\u3059\u3002
33226
+
33227
+ # \u51FA\u529B\u5F62\u5F0F
33228
+ JSON \u914D\u5217\u306E\u307F\u3092\u51FA\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u524D\u7F6E\u304D\u30FB\u5F8C\u66F8\u304D\u30FB\u30DE\u30FC\u30AF\u30C0\u30A6\u30F3\u306E\u30B3\u30FC\u30C9\u30D5\u30A7\u30F3\u30B9\u3092\u66F8\u3044\u3066\u306F\u3044\u3051\u307E\u305B\u3093\u3002
33229
+
33230
+ \u5404\u8981\u7D20\u306E\u5F62\u5F0F:
33231
+ {
33232
+ "severity": "critical" | "warning" | "info" | "praise",
33233
+ "category": "\u30D0\u30B0" | "\u5B89\u5168\u6027" | "\u53EF\u8AAD\u6027" | "\u8A2D\u8A08" | "\u6027\u80FD" | "\u898F\u7D04" \u306E\u3044\u305A\u308C\u304B,
33234
+ "title": "\u4E00\u884C\u8981\u7D04\uFF0840\u5B57\u4EE5\u5185\uFF09",
33235
+ "file": "\u5165\u529B\u3067\u4E0E\u3048\u3089\u308C\u305F\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9\u3068\u5B8C\u5168\u306B\u4E00\u81F4\u3059\u308B\u6587\u5B57\u5217",
33236
+ "startLine": \u6307\u6458\u5BFE\u8C61\u306E\u958B\u59CB\u884C\u756A\u53F7\uFF08\u5165\u529B\u306B\u4ED8\u3044\u3066\u3044\u308B\u884C\u756A\u53F7\u3092\u305D\u306E\u307E\u307E\u4F7F\u3046\uFF09,
33237
+ "endLine": \u6307\u6458\u5BFE\u8C61\u306E\u7D42\u4E86\u884C\u756A\u53F7,
33238
+ "highlightText": "\u305D\u306E\u884C\u306B\u5B9F\u5728\u3059\u308B\u90E8\u5206\u6587\u5B57\u5217\u3002\u67A0\u3067\u56F2\u3093\u3067\u793A\u3059\u306E\u306B\u4F7F\u3046",
33239
+ "problem": "\u4F55\u304C\u554F\u984C\u304B\u30022\u301C3\u6587",
33240
+ "suggestion": "\u3069\u3046\u76F4\u3059\u304B\u30022\u301C3\u6587",
33241
+ "improvedCode": "\u6539\u5584\u5F8C\u306E\u30B3\u30FC\u30C9\uFF08startLine\u301CendLine \u306E\u7BC4\u56F2\u3076\u3093\u3060\u3051\uFF09\u3002\u7121\u3051\u308C\u3070\u7701\u7565",
33242
+ "confidence": "high" | "medium" | "low"
33243
+ }
33244
+
33245
+ # \u5B88\u308B\u3053\u3068
33246
+ 1. file \u306F\u5FC5\u305A\u5165\u529B\u3067\u4E0E\u3048\u305F\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9\u306E\u3044\u305A\u308C\u304B\u3068\u5B8C\u5168\u4E00\u81F4\u3055\u305B\u308B\u3002\u63A8\u6E2C\u3067\u66F8\u304B\u306A\u3044\u3002
33247
+ 2. startLine / endLine \u306F\u5165\u529B\u306B\u4ED8\u3044\u3066\u3044\u308B\u884C\u756A\u53F7\u3092\u305D\u306E\u307E\u307E\u4F7F\u3046\u3002\u6570\u3048\u76F4\u3055\u306A\u3044\u3002
33248
+ 3. highlightText \u306F\u305D\u306E\u884C\u306B**\u5B9F\u5728\u3059\u308B\u6587\u5B57\u5217**\u306B\u3059\u308B\u3002\u5B58\u5728\u3057\u306A\u3044\u6587\u5B57\u5217\u3092\u66F8\u304B\u306A\u3044\u3002
33249
+ 4. \u78BA\u4FE1\u304C\u6301\u3066\u306A\u3044\u3082\u306E\u306F confidence \u3092 "low" \u306B\u3059\u308B\u3002\u63A8\u6E2C\u3067\u65AD\u5B9A\u3057\u306A\u3044\u3002
33250
+ 5. \u4E00\u822C\u8AD6\u3092\u66F8\u304B\u306A\u3044\u3002\u300C\u5909\u6570\u540D\u306F\u5206\u304B\u308A\u3084\u3059\u304F\u300D\u3067\u306F\u306A\u304F\u3001\u305D\u306E\u30B3\u30FC\u30C9\u306E\u5177\u4F53\u7684\u306A\u7B87\u6240\u3092\u6307\u3059\u3002
33251
+ 6. **\u826F\u3044\u70B9\uFF08severity: "praise"\uFF09\u3092\u5FC5\u305A1\u4EF6\u4EE5\u4E0A\u542B\u3081\u308B\u3002**
33252
+ 7. \u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u3092\u6F14\u3058\u306A\u3044\u3002\u3053\u3053\u3067\u306F\u4E8B\u5B9F\u3060\u3051\u3092\u3001\u7D20\u306E\u65E5\u672C\u8A9E\u3067\u66F8\u304F\u3002`;
33253
+ function reviewUserPrompt(formattedFiles, maxFindings) {
33254
+ return `\u6B21\u306E\u30B3\u30FC\u30C9\u3092\u30EC\u30D3\u30E5\u30FC\u3057\u3001\u6307\u6458\u3092\u6700\u5927 ${maxFindings} \u4EF6\u3001JSON \u914D\u5217\u3067\u51FA\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002
33255
+ \u91CD\u8981\u306A\u3082\u306E\u3092\u512A\u5148\u3057\u3001\u4E9B\u7D30\u306A\u3082\u306E\u306F\u7701\u3044\u3066\u304F\u3060\u3055\u3044\u3002
33256
+
33257
+ ${formattedFiles}`;
33258
+ }
33259
+ function scenarioSystem(c5) {
33260
+ return `\u3042\u306A\u305F\u306F\u300C${c5.nameJa}\u300D\u3068\u3044\u3046\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u3068\u3057\u3066\u3001\u30B3\u30FC\u30C9\u30EC\u30D3\u30E5\u30FC\u306E\u5185\u5BB9\u3092\u8A9E\u308B\u53F0\u672C\u3092\u66F8\u304D\u307E\u3059\u3002
33261
+
33262
+ # \u30AD\u30E3\u30E9\u30AF\u30BF\u30FC
33263
+ - \u540D\u524D: ${c5.nameJa}
33264
+ - \u4EBA\u7269\u50CF: ${c5.persona}
33265
+ - \u53E3\u8ABF: ${c5.tone}
33266
+ - \u53E3\u8ABF\u306E\u4F8B:
33267
+ ${c5.sampleLines.map((l3) => ` \u300C${l3}\u300D`).join("\n")}
33268
+
33269
+ # \u51FA\u529B\u5F62\u5F0F
33270
+ JSON \u914D\u5217\u306E\u307F\u3092\u51FA\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u524D\u7F6E\u304D\u30FB\u5F8C\u66F8\u304D\u30FB\u30B3\u30FC\u30C9\u30D5\u30A7\u30F3\u30B9\u3092\u66F8\u3044\u3066\u306F\u3044\u3051\u307E\u305B\u3093\u3002
33271
+
33272
+ \u5404\u8981\u7D20\u306E\u5F62\u5F0F:
33273
+ {
33274
+ "findingId": "\u5BFE\u8C61\u306E\u6307\u6458ID\uFF08\u30AA\u30FC\u30D7\u30CB\u30F3\u30B0\u3068\u30A8\u30F3\u30C7\u30A3\u30F3\u30B0\u306F\u7701\u7565\uFF09",
33275
+ "role": "opening" | "present" | "problem" | "improve" | "summary" | "ending",
33276
+ "text": "\u53F0\u8A5E",
33277
+ "emotion": ${EMOTIONS.map((e5) => `"${e5}"`).join(" | ")}
33278
+ }
33279
+
33280
+ # \u5B88\u308B\u3053\u3068
33281
+ 1. **\u53F0\u8A5E\u306F\u5168\u89D2100\u6587\u5B57\u4EE5\u5185\u3002** 45\u301C80\u6587\u5B57\u304C\u6700\u3082\u8AAD\u307F\u3084\u3059\u3044\u3002\u6539\u884C\u306F \\n \u30671\u56DE\u307E\u3067\u3002
33282
+ 2. \u4E0A\u8A18\u306E\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u306E\u53E3\u8ABF\u3067\u66F8\u304F\u3002\u53E3\u8ABF\u306E\u4F8B\u306B\u5BC4\u305B\u308B\u3002
33283
+ 3. **\u6280\u8853\u7684\u306A\u5185\u5BB9\u3092\u5909\u3048\u306A\u3044\u3002** \u6307\u6458\u306E\u610F\u5473\u306F\u5909\u3048\u305A\u3001\u8A00\u3044\u65B9\u3060\u3051\u3092\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u306E\u3082\u306E\u306B\u3059\u308B\u3002
33284
+ 4. emotion \u306F\u4E0A\u306B\u6319\u3052\u305F\u3082\u306E\u304B\u3089\u306E\u307F\u9078\u3076\u3002
33285
+ 5. **\u53F0\u8A5E\u306B\u30D5\u30A1\u30A4\u30EB\u540D\u30FB\u884C\u756A\u53F7\u30FB\u8A18\u53F7\u3092\u66F8\u304B\u306A\u3044\u3002** \u97F3\u58F0\u3067\u8AAD\u307F\u4E0A\u3052\u308B\u305F\u3081\u3001
33286
+ \u300C4\u884C\u76EE\u306E db.query \u304C\u300D\u306E\u3088\u3046\u306A\u66F8\u304D\u65B9\u3092\u3059\u308B\u3068\u805E\u304D\u82E6\u3057\u304F\u306A\u308B\u3002\u4F4D\u7F6E\u306F\u753B\u9762\u304C\u793A\u3059\u3002
33287
+ 6. confidence \u304C low \u306E\u6307\u6458\u306F\u3001\u65AD\u5B9A\u3092\u907F\u3051\u305F\u8A00\u3044\u56DE\u3057\u306B\u3059\u308B\u3002
33288
+
33289
+ # \u69CB\u6210
33290
+ - \u6700\u521D\u306B role="opening" \u30921\u3064\u3002\u5168\u4F53\u3067\u4F55\u4EF6\u898B\u3064\u304B\u3063\u305F\u304B\u3092\u4F1D\u3048\u308B\u3002
33291
+ - \u6307\u6458\u3054\u3068\u306B present \u2192 problem \u2192 improve \u2192 summary \u306E\u9806\u3067\u4E26\u3079\u308B\u3002
33292
+ - severity \u304C "praise" \u306E\u6307\u6458\u306F problem \u3092\u4F5C\u3089\u306A\u3044\u3002
33293
+ - improvedCode \u304C\u7121\u3044\u6307\u6458\u306F improve \u3092\u4F5C\u3089\u306A\u3044\u3002
33294
+ - \u6700\u5F8C\u306B role="ending" \u30921\u3064\u3002\u826F\u3044\u70B9\u306B\u89E6\u308C\u3066\u7D42\u3048\u308B\u3002
33295
+
33296
+ # \u8868\u60C5\u306E\u4F7F\u3044\u5206\u3051
33297
+ - normal: \u63D0\u793A\uFF08present\uFF09
33298
+ - serious: \u91CD\u5927\u306A\u6307\u6458\uFF08critical\uFF09
33299
+ - exasperated: \u96D1\u306A\u30B3\u30FC\u30C9\u3078\u306E\u6307\u6458
33300
+ - thinking: \u6539\u5584\u6848\u306E\u63D0\u793A\uFF08improve\uFF09
33301
+ - proud: \u307E\u3068\u3081\u30FB\u826F\u3044\u70B9\u30FB\u30A8\u30F3\u30C7\u30A3\u30F3\u30B0
33302
+ - flustered: \u7279\u306B\u91CD\u5927\u306A\u767A\u898B
33303
+ - sulking: \u4F7F\u7528\u983B\u5EA6\u306F\u4F4E\u3044`;
33304
+ }
33305
+ function scenarioUserPrompt(findings) {
33306
+ const counts = findings.reduce((acc, f5) => {
33307
+ acc[f5.severity] = (acc[f5.severity] ?? 0) + 1;
33308
+ return acc;
33309
+ }, {});
33310
+ const list2 = findings.map(
33311
+ (f5) => [
33312
+ `## ${f5.id}\uFF08${f5.severity} / ${f5.category} / \u78BA\u4FE1\u5EA6 ${f5.confidence}\uFF09`,
33313
+ `\u898B\u51FA\u3057: ${f5.title}`,
33314
+ `\u554F\u984C: ${f5.problem}`,
33315
+ f5.suggestion ? `\u6539\u5584: ${f5.suggestion}` : "",
33316
+ f5.improvedCode ? "\u6539\u5584\u5F8C\u306E\u30B3\u30FC\u30C9\u3042\u308A" : "\u6539\u5584\u5F8C\u306E\u30B3\u30FC\u30C9\u306A\u3057"
33317
+ ].filter(Boolean).join("\n")
33318
+ ).join("\n\n");
33319
+ return `\u6B21\u306E\u30EC\u30D3\u30E5\u30FC\u7D50\u679C\u3092\u53F0\u672C\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002
33320
+
33321
+ \u5168\u4F53: ${findings.length}\u4EF6\uFF08${Object.entries(counts).map(([k5, v]) => `${k5} ${v}`).join(" / ")}\uFF09
33322
+
33323
+ ${list2}`;
33324
+ }
33325
+ var FIX_SYSTEM = `\u3042\u306A\u305F\u306F\u7D4C\u9A13\u8C4A\u5BCC\u306A\u30BD\u30D5\u30C8\u30A6\u30A7\u30A2\u30A8\u30F3\u30B8\u30CB\u30A2\u3067\u3059\u3002
33326
+ \u4E0E\u3048\u3089\u308C\u305F 1 \u3064\u306E\u30D5\u30A1\u30A4\u30EB\u3068\u3001\u305D\u306E\u30D5\u30A1\u30A4\u30EB\u3078\u306E\u30EC\u30D3\u30E5\u30FC\u6307\u6458\u3092\u53D7\u3051\u53D6\u308A\u3001\u6307\u6458\u3092\u76F4\u3057\u305F**\u30D5\u30A1\u30A4\u30EB\u5168\u4F53**\u3092\u8FD4\u3057\u307E\u3059\u3002
33327
+
33328
+ # \u51FA\u529B\u5F62\u5F0F
33329
+ 1. \u6700\u521D\u306B\u3001\u76F4\u3057\u305F\u30D5\u30A1\u30A4\u30EB\u5168\u4F53\u3092 1 \u3064\u306E\u30B3\u30FC\u30C9\u30D6\u30ED\u30C3\u30AF\uFF08\`\`\` \u3067\u56F2\u3080\uFF09\u3067\u51FA\u529B\u3059\u308B\u3002\u7701\u7565\u30FB\u300C\u2026\u300D\u30FB\u300C\u4EE5\u4E0B\u540C\u3058\u300D\u306F\u7981\u6B62\u3002
33330
+ 2. \u305D\u306E\u6B21\u306E\u884C\u306B\u3001\u76F4\u3057\u305F\u6307\u6458\u306E ID \u3092\u30AB\u30F3\u30DE\u533A\u5207\u308A\u3067\u300CFIXED: F-001,F-003\u300D\u306E\u5F62\u3067 1 \u884C\u3060\u3051\u66F8\u304F\u3002
33331
+ 1 \u4EF6\u3082\u76F4\u305B\u306A\u3051\u308C\u3070\u300CFIXED: none\u300D\u3068\u66F8\u304F\u3002
33332
+ 3. \u305D\u308C\u4EE5\u5916\u306E\u8AAC\u660E\u306F\u66F8\u304B\u306A\u3044\u3002
33333
+
33334
+ # \u5B88\u308B\u3053\u3068
33335
+ 1. **\u6307\u6458\u306B\u95A2\u4FC2\u3059\u308B\u7B87\u6240\u3060\u3051\u3092\u76F4\u3059\u3002** \u95A2\u4FC2\u306E\u7121\u3044\u884C\u306E\u66F8\u304D\u65B9\u30FB\u30A4\u30F3\u30C7\u30F3\u30C8\u30FB\u30B3\u30E1\u30F3\u30C8\u30FB\u4E26\u3073\u9806\u3092\u5909\u3048\u306A\u3044\u3002
33336
+ 2. \u5B58\u5728\u3057\u306A\u3044\u95A2\u6570\u30FB\u30E9\u30A4\u30D6\u30E9\u30EA\u3092\u4F7F\u308F\u306A\u3044\u3002\u65B0\u3057\u3044\u4F9D\u5B58\uFF08import / require\uFF09\u3092\u8DB3\u3055\u306A\u3044\u3002
33337
+ 3. \u5909\u6570\u3092\u4E8C\u91CD\u306B\u5BA3\u8A00\u3057\u306A\u3044\u3002\u65E2\u306B\u3042\u308B\u5BA3\u8A00\u3092\u6D88\u3055\u306A\u3044\u3002
33338
+ 4. \u76F4\u3059\u3068\u4ED6\u306E\u7B87\u6240\u304C\u58CA\u308C\u308B\u6307\u6458\u306F\u76F4\u3055\u305A\u3001FIXED \u306B\u542B\u3081\u306A\u3044\u3002
33339
+ 5. \u5143\u306E\u30D5\u30A1\u30A4\u30EB\u306E\u30A4\u30F3\u30C7\u30F3\u30C8\uFF08\u30B9\u30DA\u30FC\u30B9\u304B\u30BF\u30D6\u304B\u30FB\u5E45\uFF09\u3068\u6539\u884C\u306E\u6D41\u5100\u306B\u5408\u308F\u305B\u308B\u3002`;
33340
+ function fixUserPrompt(file, findings) {
33341
+ const list2 = findings.map(
33342
+ (f5) => [
33343
+ `## ${f5.id}\uFF08${f5.severity}\uFF09${f5.startLine}\u301C${f5.endLine} \u884C\u76EE: ${f5.title}`,
33344
+ `\u554F\u984C: ${f5.problem}`,
33345
+ f5.suggestion ? `\u76F4\u3057\u65B9: ${f5.suggestion}` : "",
33346
+ // 参考として渡す。範囲ちょうどの差し替えとは限らない(CLI-07)
33347
+ f5.improvedCode ? `\u6539\u5584\u6848\uFF08\u53C2\u8003\uFF09:
33348
+ ${f5.improvedCode}` : ""
33349
+ ].filter(Boolean).join("\n")
33350
+ ).join("\n\n");
33351
+ return `\u6B21\u306E\u30D5\u30A1\u30A4\u30EB\u306E\u6307\u6458\u3092\u76F4\u3057\u3001\u30D5\u30A1\u30A4\u30EB\u5168\u4F53\u3092\u8FD4\u3057\u3066\u304F\u3060\u3055\u3044\u3002
33352
+
33353
+ # \u30D5\u30A1\u30A4\u30EB: ${file.relPath}\uFF08${file.language}\uFF09
33354
+ \`\`\`
33355
+ ${file.content}
33356
+ \`\`\`
33357
+
33358
+ # \u6307\u6458
33359
+ ${list2}`;
33360
+ }
33361
+
33222
33362
  // src/fix/applyFixes.ts
33223
33363
  var RANK = { critical: 3, warning: 2, info: 1, praise: 0 };
33224
33364
  function atLeast(sev, min) {
33225
33365
  return RANK[sev] >= RANK[min] && sev !== "praise";
33226
33366
  }
33367
+ var MAX_LINES = 1500;
33227
33368
  var isInt = (v) => typeof v === "number" && Number.isInteger(v) && v >= 1;
33228
33369
  function check(f5) {
33229
33370
  if (typeof f5.file !== "string" || !f5.file) return "file \u304C\u7121\u3044";
33230
33371
  if (!isInt(f5.startLine) || !isInt(f5.endLine) || f5.endLine < f5.startLine) return "\u884C\u756A\u53F7\u304C\u4E0D\u6B63";
33231
- if (typeof f5.improvedCode !== "string" || !f5.improvedCode.trim())
33232
- return "\u6539\u5584\u6848\uFF08improvedCode\uFF09\u304C\u7121\u3044";
33372
+ if (typeof f5.title !== "string" || typeof f5.problem !== "string")
33373
+ return "\u898B\u51FA\u3057\u304B\u554F\u984C\u306E\u8AAC\u660E\u304C\u7121\u3044";
33233
33374
  if (!SEVERITIES.includes(f5.severity)) return "\u91CD\u8981\u5EA6\u304C\u4E0D\u6B63";
33234
33375
  return null;
33235
33376
  }
@@ -33240,19 +33381,58 @@ function safeJoin(root5, rel) {
33240
33381
  if (!r5 || r5.startsWith("..") || isAbsolute(r5)) return null;
33241
33382
  return abs;
33242
33383
  }
33243
- var indentOf = (s2) => s2.slice(0, s2.length - s2.trimStart().length);
33244
- function replacementLines(original, improved) {
33245
- const lines = improved.replace(/\r\n/g, "\n").replace(/\n+$/, "").split("\n");
33246
- if (lines.length > 0 && indentOf(lines[0]) === "" && original.length > 0) {
33247
- lines[0] = indentOf(original[0]) + lines[0];
33384
+ function parseFixResponse(text) {
33385
+ const m3 = text.match(/```[^\n]*\n([\s\S]*?)\n?```/);
33386
+ if (!m3 || !m3[1].trim()) return null;
33387
+ const line = text.slice(m3.index + m3[0].length).match(/FIXED:\s*([^\n]*)/i);
33388
+ if (!line) return { code: m3[1], fixed: [] };
33389
+ const v = line[1].trim();
33390
+ if (/^none$/i.test(v)) return { code: m3[1], fixed: [] };
33391
+ return { code: m3[1], fixed: v.split(/[\s,、]+/).filter(Boolean) };
33392
+ }
33393
+ async function syntaxCheck(path, code) {
33394
+ const ext = extname(path).toLowerCase();
33395
+ if (ext === ".json") {
33396
+ try {
33397
+ JSON.parse(code);
33398
+ return null;
33399
+ } catch (e5) {
33400
+ return `JSON \u3068\u3057\u3066\u8AAD\u3081\u306A\u3044\uFF08${e5 instanceof Error ? e5.message : String(e5)}\uFF09`;
33401
+ }
33402
+ }
33403
+ if (![".js", ".mjs", ".cjs"].includes(ext)) return "unchecked";
33404
+ const dir = await fs2.mkdtemp(join2(tmpdir(), "cdbg-fix-"));
33405
+ const tmp = join2(dir, `check${ext}`);
33406
+ try {
33407
+ await fs2.writeFile(tmp, code, "utf8");
33408
+ const err = await new Promise((done) => {
33409
+ const child = spawn2(process.execPath, ["--check", tmp], {
33410
+ stdio: ["ignore", "ignore", "pipe"],
33411
+ // Electron の Node で動いているときも、素の Node として振る舞わせる
33412
+ env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }
33413
+ });
33414
+ let stderr = "";
33415
+ child.stderr.on("data", (d5) => stderr += d5.toString());
33416
+ child.on("error", (e5) => done(e5.message));
33417
+ child.on("close", (c5) => done(c5 === 0 ? "" : stderr));
33418
+ });
33419
+ if (!err) return null;
33420
+ const first = err.split("\n").find((l3) => /Error/.test(l3)) ?? err.split("\n")[0];
33421
+ return `\u69CB\u6587\u30A8\u30E9\u30FC\uFF08${first.trim()}\uFF09`;
33422
+ } finally {
33423
+ await fs2.rm(dir, { recursive: true, force: true });
33248
33424
  }
33249
- return lines;
33250
33425
  }
33251
- async function applyFixes(root5, findings, opts = {}) {
33426
+ async function applyFixes(root5, findings, opts) {
33252
33427
  const min = opts.minSeverity ?? "warning";
33253
33428
  const applied = [];
33254
33429
  const skipped = [];
33255
- const skip = (f5, reason) => skipped.push({ findingId: String(f5.id ?? "?"), file: String(f5.file ?? "?"), reason });
33430
+ const unchecked = [];
33431
+ const skipAll = (list2, reason) => {
33432
+ for (const f5 of list2) {
33433
+ skipped.push({ findingId: String(f5.id ?? "?"), file: String(f5.file ?? "?"), reason });
33434
+ }
33435
+ };
33256
33436
  const byFile = /* @__PURE__ */ new Map();
33257
33437
  for (const f5 of findings) {
33258
33438
  if (SEVERITIES.includes(f5.severity) && !atLeast(f5.severity, min)) {
@@ -33260,54 +33440,90 @@ async function applyFixes(root5, findings, opts = {}) {
33260
33440
  }
33261
33441
  const bad = check(f5);
33262
33442
  if (bad) {
33263
- skip(f5, bad);
33443
+ skipAll([f5], bad);
33264
33444
  continue;
33265
33445
  }
33266
33446
  const ok = f5;
33267
- const list2 = byFile.get(ok.file) ?? [];
33268
- list2.push(ok);
33269
- byFile.set(ok.file, list2);
33447
+ byFile.set(ok.file, [...byFile.get(ok.file) ?? [], ok]);
33270
33448
  }
33271
33449
  for (const [file, list2] of byFile) {
33272
33450
  const abs = safeJoin(root5, file);
33273
33451
  if (!abs) {
33274
- for (const f5 of list2) skip(f5, "\u5BFE\u8C61\u30D5\u30A9\u30EB\u30C0\u306E\u5916\u3092\u6307\u3057\u3066\u3044\u308B");
33452
+ skipAll(list2, "\u5BFE\u8C61\u30D5\u30A9\u30EB\u30C0\u306E\u5916\u3092\u6307\u3057\u3066\u3044\u308B");
33275
33453
  continue;
33276
33454
  }
33277
33455
  let text;
33278
33456
  try {
33279
33457
  text = await fs2.readFile(abs, "utf8");
33280
33458
  } catch {
33281
- for (const f5 of list2) skip(f5, "\u30D5\u30A1\u30A4\u30EB\u3092\u8AAD\u3081\u306A\u3044");
33459
+ skipAll(list2, "\u30D5\u30A1\u30A4\u30EB\u3092\u8AAD\u3081\u306A\u3044");
33282
33460
  continue;
33283
33461
  }
33284
33462
  const eol = text.includes("\r\n") ? "\r\n" : "\n";
33285
33463
  const endsWithNewline = text.endsWith("\n");
33286
- const lines = text.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n");
33287
- const done = [];
33288
- const ordered = [...list2].sort(
33289
- (a5, b5) => b5.startLine - a5.startLine || RANK[b5.severity] - RANK[a5.severity]
33464
+ const original = text.replace(/\r\n/g, "\n");
33465
+ const lineCount = original.replace(/\n$/, "").split("\n").length;
33466
+ if (lineCount > MAX_LINES) {
33467
+ skipAll(list2, `\u30D5\u30A1\u30A4\u30EB\u304C\u9577\u3059\u304E\u308B\uFF08${lineCount} \u884C\u3002\u4E0A\u9650 ${MAX_LINES}\uFF09`);
33468
+ continue;
33469
+ }
33470
+ const outOfRange = list2.filter((f5) => f5.endLine > lineCount);
33471
+ if (outOfRange.length > 0)
33472
+ skipAll(outOfRange, `\u884C\u7BC4\u56F2\u304C\u30D5\u30A1\u30A4\u30EB\u306E\u5916\uFF08${lineCount} \u884C\u3057\u304B\u306A\u3044\uFF09`);
33473
+ const targets = list2.filter((f5) => f5.endLine <= lineCount);
33474
+ if (targets.length === 0) continue;
33475
+ let res;
33476
+ try {
33477
+ res = await opts.ai.complete(
33478
+ {
33479
+ purpose: "fix",
33480
+ system: FIX_SYSTEM,
33481
+ user: fixUserPrompt(
33482
+ { relPath: file, language: languageOf(file), content: original },
33483
+ targets
33484
+ )
33485
+ },
33486
+ opts.signal
33487
+ );
33488
+ } catch (e5) {
33489
+ if (opts.signal?.aborted) throw e5;
33490
+ skipAll(targets, `AI \u306E\u547C\u3073\u51FA\u3057\u306B\u5931\u6557\u3057\u305F\uFF08${e5 instanceof Error ? e5.message : String(e5)}\uFF09`);
33491
+ continue;
33492
+ }
33493
+ const parsed = parseFixResponse(res.text);
33494
+ if (!parsed) {
33495
+ skipAll(targets, "AI \u306E\u5FDC\u7B54\u306B\u30B3\u30FC\u30C9\u304C\u7121\u304B\u3063\u305F");
33496
+ continue;
33497
+ }
33498
+ let code = parsed.code.replace(/\r\n/g, "\n").replace(/\n+$/, "");
33499
+ const newCount = code.split("\n").length;
33500
+ if (newCount < Math.max(1, Math.floor(lineCount * 0.5))) {
33501
+ skipAll(targets, `AI \u306E\u5FDC\u7B54\u304C\u5143\u3088\u308A\u6975\u7AEF\u306B\u77ED\u3044\uFF08${lineCount} \u884C \u2192 ${newCount} \u884C\uFF09`);
33502
+ continue;
33503
+ }
33504
+ if (code === original.replace(/\n+$/, "")) {
33505
+ skipAll(targets, "AI \u304C\u4F55\u3082\u5909\u3048\u306A\u304B\u3063\u305F");
33506
+ continue;
33507
+ }
33508
+ code = code.split("\n").join(eol) + (endsWithNewline ? eol : "");
33509
+ const syntax = await syntaxCheck(abs, code);
33510
+ if (syntax !== null && syntax !== "unchecked") {
33511
+ skipAll(targets, `\u76F4\u3057\u305F\u30B3\u30FC\u30C9\u304C${syntax}`);
33512
+ continue;
33513
+ }
33514
+ const fixedIds = new Set(parsed.fixed);
33515
+ const done = targets.filter((f5) => fixedIds.has(f5.id));
33516
+ if (done.length === 0) {
33517
+ skipAll(targets, "AI \u304C\u76F4\u3057\u305F\u6307\u6458\u3092\u7533\u544A\u3057\u306A\u304B\u3063\u305F\uFF08FIXED \u884C\u304C\u7121\u3044\u30FBnone\uFF09");
33518
+ continue;
33519
+ }
33520
+ skipAll(
33521
+ targets.filter((f5) => !fixedIds.has(f5.id)),
33522
+ "AI \u304C\u76F4\u3055\u306A\u304B\u3063\u305F\uFF08\u76F4\u3059\u3068\u4ED6\u304C\u58CA\u308C\u308B\u7B49\uFF09"
33290
33523
  );
33291
- let changed = false;
33292
- for (const f5 of ordered) {
33293
- if (f5.endLine > lines.length) {
33294
- skip(f5, `\u884C\u7BC4\u56F2\u304C\u30D5\u30A1\u30A4\u30EB\u306E\u5916\uFF08${lines.length} \u884C\u3057\u304B\u306A\u3044\uFF09`);
33295
- continue;
33296
- }
33297
- if (done.some((d5) => f5.startLine <= d5.end && d5.start <= f5.endLine)) {
33298
- skip(f5, "\u4ED6\u306E\u4FEE\u6B63\u3068\u7BC4\u56F2\u304C\u91CD\u306A\u308B");
33299
- continue;
33300
- }
33301
- const original = lines.slice(f5.startLine - 1, f5.endLine);
33302
- if (f5.highlightText) {
33303
- const squash = (s2) => s2.replace(/\s+/g, "");
33304
- if (!squash(original.join("\n")).includes(squash(f5.highlightText))) {
33305
- skip(f5, "\u30EC\u30D3\u30E5\u30FC\u5F8C\u306B\u30B3\u30FC\u30C9\u304C\u5909\u308F\u3063\u3066\u3044\u308B\uFF08\u6307\u6458\u7B87\u6240\u304C\u898B\u3064\u304B\u3089\u306A\u3044\uFF09");
33306
- continue;
33307
- }
33308
- }
33309
- lines.splice(f5.startLine - 1, original.length, ...replacementLines(original, f5.improvedCode));
33310
- done.push({ start: f5.startLine, end: f5.endLine });
33524
+ if (!opts.dryRun) await fs2.writeFile(abs, code, "utf8");
33525
+ if (syntax === "unchecked") unchecked.push(file);
33526
+ for (const f5 of done) {
33311
33527
  applied.push({
33312
33528
  findingId: f5.id,
33313
33529
  severity: f5.severity,
@@ -33316,13 +33532,9 @@ async function applyFixes(root5, findings, opts = {}) {
33316
33532
  startLine: f5.startLine,
33317
33533
  endLine: f5.endLine
33318
33534
  });
33319
- changed = true;
33320
- }
33321
- if (changed && !opts.dryRun) {
33322
- await fs2.writeFile(abs, lines.join(eol) + (endsWithNewline ? eol : ""), "utf8");
33323
33535
  }
33324
33536
  }
33325
- return { applied, skipped };
33537
+ return { applied, skipped, unchecked };
33326
33538
  }
33327
33539
  function pullRequestBody(result, findings, roxy = {}) {
33328
33540
  const byId = new Map(findings.map((f5) => [f5.id, f5]));
@@ -33336,6 +33548,16 @@ function pullRequestBody(result, findings, roxy = {}) {
33336
33548
  if (f5?.suggestion) out.push(`- \u76F4\u3057\u65B9: ${f5.suggestion}`);
33337
33549
  out.push("");
33338
33550
  }
33551
+ if (result.unchecked.length > 0) {
33552
+ out.push(
33553
+ "## \u26A0 \u69CB\u6587\u691C\u67FB\u3092\u3057\u3066\u3044\u306A\u3044\u30D5\u30A1\u30A4\u30EB",
33554
+ "",
33555
+ ...result.unchecked.map(
33556
+ (f5) => `- \`${f5}\`\uFF08\u3053\u306E\u8A00\u8A9E\u306E\u691C\u67FB\u624B\u6BB5\u304C\u7121\u3044\u3002\u7279\u306B\u5FF5\u5165\u308A\u306B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\uFF09`
33557
+ ),
33558
+ ""
33559
+ );
33560
+ }
33339
33561
  if (result.skipped.length > 0) {
33340
33562
  out.push(`## \u76F4\u305B\u306A\u304B\u3063\u305F\u6307\u6458\uFF08${result.skipped.length} \u4EF6\u30FB\u624B\u3067\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\uFF09`, "");
33341
33563
  for (const s2 of result.skipped) {
@@ -33347,7 +33569,7 @@ function pullRequestBody(result, findings, roxy = {}) {
33347
33569
  if (roxy.ending) out.push(`> \u30ED\u30AD\u30B7\u30FC\u300C${roxy.ending}\u300D`, "");
33348
33570
  out.push(
33349
33571
  "---",
33350
- "*\u3053\u306E\u4FEE\u6B63\u306F candy-debug\uFF08\u30ED\u30AD\u30B7\u30FC\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3\uFF09\u304C\u81EA\u52D5\u3067\u4F5C\u6210\u3057\u307E\u3057\u305F\u3002\u5FC5\u305A\u4E2D\u8EAB\u3092\u78BA\u8A8D\u3057\u3066\u304B\u3089\u30DE\u30FC\u30B8\u3057\u3066\u304F\u3060\u3055\u3044\u3002*"
33572
+ "*\u3053\u306E\u4FEE\u6B63\u306F candy-debug\uFF08\u30ED\u30AD\u30B7\u30FC\u30D1\u30A4\u30D7\u30E9\u30A4\u30F3\uFF09\u304C AI \u306B\u66F8\u304D\u76F4\u3055\u305B\u305F\u3082\u306E\u3067\u3059\u3002\u69CB\u6587\u691C\u67FB\u306F\u901A\u3063\u3066\u3044\u307E\u3059\u304C\u3001\u52D5\u4F5C\u306F\u78BA\u304B\u3081\u3066\u3044\u307E\u305B\u3093\u3002\u5FC5\u305A\u4E2D\u8EAB\u3092\u78BA\u8A8D\u3057\u3066\u304B\u3089\u30DE\u30FC\u30B8\u3057\u3066\u304F\u3060\u3055\u3044\u3002*"
33351
33573
  );
33352
33574
  const body = out.join("\n");
33353
33575
  return body.length > 1e4 ? body.slice(0, 9990) + "\n\u2026\uFF08\u7701\u7565\uFF09" : body;
@@ -33383,108 +33605,6 @@ function extractJson(text) {
33383
33605
  return null;
33384
33606
  }
33385
33607
 
33386
- // src/review/prompts.ts
33387
- var REVIEW_SYSTEM = `\u3042\u306A\u305F\u306F\u7D4C\u9A13\u8C4A\u5BCC\u306A\u30B3\u30FC\u30C9\u30EC\u30D3\u30E5\u30A2\u30FC\u3067\u3059\u3002\u65E5\u672C\u8A9E\u3067\u56DE\u7B54\u3057\u307E\u3059\u3002
33388
-
33389
- # \u51FA\u529B\u5F62\u5F0F
33390
- JSON \u914D\u5217\u306E\u307F\u3092\u51FA\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u524D\u7F6E\u304D\u30FB\u5F8C\u66F8\u304D\u30FB\u30DE\u30FC\u30AF\u30C0\u30A6\u30F3\u306E\u30B3\u30FC\u30C9\u30D5\u30A7\u30F3\u30B9\u3092\u66F8\u3044\u3066\u306F\u3044\u3051\u307E\u305B\u3093\u3002
33391
-
33392
- \u5404\u8981\u7D20\u306E\u5F62\u5F0F:
33393
- {
33394
- "severity": "critical" | "warning" | "info" | "praise",
33395
- "category": "\u30D0\u30B0" | "\u5B89\u5168\u6027" | "\u53EF\u8AAD\u6027" | "\u8A2D\u8A08" | "\u6027\u80FD" | "\u898F\u7D04" \u306E\u3044\u305A\u308C\u304B,
33396
- "title": "\u4E00\u884C\u8981\u7D04\uFF0840\u5B57\u4EE5\u5185\uFF09",
33397
- "file": "\u5165\u529B\u3067\u4E0E\u3048\u3089\u308C\u305F\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9\u3068\u5B8C\u5168\u306B\u4E00\u81F4\u3059\u308B\u6587\u5B57\u5217",
33398
- "startLine": \u6307\u6458\u5BFE\u8C61\u306E\u958B\u59CB\u884C\u756A\u53F7\uFF08\u5165\u529B\u306B\u4ED8\u3044\u3066\u3044\u308B\u884C\u756A\u53F7\u3092\u305D\u306E\u307E\u307E\u4F7F\u3046\uFF09,
33399
- "endLine": \u6307\u6458\u5BFE\u8C61\u306E\u7D42\u4E86\u884C\u756A\u53F7,
33400
- "highlightText": "\u305D\u306E\u884C\u306B\u5B9F\u5728\u3059\u308B\u90E8\u5206\u6587\u5B57\u5217\u3002\u67A0\u3067\u56F2\u3093\u3067\u793A\u3059\u306E\u306B\u4F7F\u3046",
33401
- "problem": "\u4F55\u304C\u554F\u984C\u304B\u30022\u301C3\u6587",
33402
- "suggestion": "\u3069\u3046\u76F4\u3059\u304B\u30022\u301C3\u6587",
33403
- "improvedCode": "\u6539\u5584\u5F8C\u306E\u30B3\u30FC\u30C9\uFF08startLine\u301CendLine \u306E\u7BC4\u56F2\u3076\u3093\u3060\u3051\uFF09\u3002\u7121\u3051\u308C\u3070\u7701\u7565",
33404
- "confidence": "high" | "medium" | "low"
33405
- }
33406
-
33407
- # \u5B88\u308B\u3053\u3068
33408
- 1. file \u306F\u5FC5\u305A\u5165\u529B\u3067\u4E0E\u3048\u305F\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9\u306E\u3044\u305A\u308C\u304B\u3068\u5B8C\u5168\u4E00\u81F4\u3055\u305B\u308B\u3002\u63A8\u6E2C\u3067\u66F8\u304B\u306A\u3044\u3002
33409
- 2. startLine / endLine \u306F\u5165\u529B\u306B\u4ED8\u3044\u3066\u3044\u308B\u884C\u756A\u53F7\u3092\u305D\u306E\u307E\u307E\u4F7F\u3046\u3002\u6570\u3048\u76F4\u3055\u306A\u3044\u3002
33410
- 3. highlightText \u306F\u305D\u306E\u884C\u306B**\u5B9F\u5728\u3059\u308B\u6587\u5B57\u5217**\u306B\u3059\u308B\u3002\u5B58\u5728\u3057\u306A\u3044\u6587\u5B57\u5217\u3092\u66F8\u304B\u306A\u3044\u3002
33411
- 4. \u78BA\u4FE1\u304C\u6301\u3066\u306A\u3044\u3082\u306E\u306F confidence \u3092 "low" \u306B\u3059\u308B\u3002\u63A8\u6E2C\u3067\u65AD\u5B9A\u3057\u306A\u3044\u3002
33412
- 5. \u4E00\u822C\u8AD6\u3092\u66F8\u304B\u306A\u3044\u3002\u300C\u5909\u6570\u540D\u306F\u5206\u304B\u308A\u3084\u3059\u304F\u300D\u3067\u306F\u306A\u304F\u3001\u305D\u306E\u30B3\u30FC\u30C9\u306E\u5177\u4F53\u7684\u306A\u7B87\u6240\u3092\u6307\u3059\u3002
33413
- 6. **\u826F\u3044\u70B9\uFF08severity: "praise"\uFF09\u3092\u5FC5\u305A1\u4EF6\u4EE5\u4E0A\u542B\u3081\u308B\u3002**
33414
- 7. \u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u3092\u6F14\u3058\u306A\u3044\u3002\u3053\u3053\u3067\u306F\u4E8B\u5B9F\u3060\u3051\u3092\u3001\u7D20\u306E\u65E5\u672C\u8A9E\u3067\u66F8\u304F\u3002`;
33415
- function reviewUserPrompt(formattedFiles, maxFindings) {
33416
- return `\u6B21\u306E\u30B3\u30FC\u30C9\u3092\u30EC\u30D3\u30E5\u30FC\u3057\u3001\u6307\u6458\u3092\u6700\u5927 ${maxFindings} \u4EF6\u3001JSON \u914D\u5217\u3067\u51FA\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002
33417
- \u91CD\u8981\u306A\u3082\u306E\u3092\u512A\u5148\u3057\u3001\u4E9B\u7D30\u306A\u3082\u306E\u306F\u7701\u3044\u3066\u304F\u3060\u3055\u3044\u3002
33418
-
33419
- ${formattedFiles}`;
33420
- }
33421
- function scenarioSystem(c5) {
33422
- return `\u3042\u306A\u305F\u306F\u300C${c5.nameJa}\u300D\u3068\u3044\u3046\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u3068\u3057\u3066\u3001\u30B3\u30FC\u30C9\u30EC\u30D3\u30E5\u30FC\u306E\u5185\u5BB9\u3092\u8A9E\u308B\u53F0\u672C\u3092\u66F8\u304D\u307E\u3059\u3002
33423
-
33424
- # \u30AD\u30E3\u30E9\u30AF\u30BF\u30FC
33425
- - \u540D\u524D: ${c5.nameJa}
33426
- - \u4EBA\u7269\u50CF: ${c5.persona}
33427
- - \u53E3\u8ABF: ${c5.tone}
33428
- - \u53E3\u8ABF\u306E\u4F8B:
33429
- ${c5.sampleLines.map((l3) => ` \u300C${l3}\u300D`).join("\n")}
33430
-
33431
- # \u51FA\u529B\u5F62\u5F0F
33432
- JSON \u914D\u5217\u306E\u307F\u3092\u51FA\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u524D\u7F6E\u304D\u30FB\u5F8C\u66F8\u304D\u30FB\u30B3\u30FC\u30C9\u30D5\u30A7\u30F3\u30B9\u3092\u66F8\u3044\u3066\u306F\u3044\u3051\u307E\u305B\u3093\u3002
33433
-
33434
- \u5404\u8981\u7D20\u306E\u5F62\u5F0F:
33435
- {
33436
- "findingId": "\u5BFE\u8C61\u306E\u6307\u6458ID\uFF08\u30AA\u30FC\u30D7\u30CB\u30F3\u30B0\u3068\u30A8\u30F3\u30C7\u30A3\u30F3\u30B0\u306F\u7701\u7565\uFF09",
33437
- "role": "opening" | "present" | "problem" | "improve" | "summary" | "ending",
33438
- "text": "\u53F0\u8A5E",
33439
- "emotion": ${EMOTIONS.map((e5) => `"${e5}"`).join(" | ")}
33440
- }
33441
-
33442
- # \u5B88\u308B\u3053\u3068
33443
- 1. **\u53F0\u8A5E\u306F\u5168\u89D2100\u6587\u5B57\u4EE5\u5185\u3002** 45\u301C80\u6587\u5B57\u304C\u6700\u3082\u8AAD\u307F\u3084\u3059\u3044\u3002\u6539\u884C\u306F \\n \u30671\u56DE\u307E\u3067\u3002
33444
- 2. \u4E0A\u8A18\u306E\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u306E\u53E3\u8ABF\u3067\u66F8\u304F\u3002\u53E3\u8ABF\u306E\u4F8B\u306B\u5BC4\u305B\u308B\u3002
33445
- 3. **\u6280\u8853\u7684\u306A\u5185\u5BB9\u3092\u5909\u3048\u306A\u3044\u3002** \u6307\u6458\u306E\u610F\u5473\u306F\u5909\u3048\u305A\u3001\u8A00\u3044\u65B9\u3060\u3051\u3092\u30AD\u30E3\u30E9\u30AF\u30BF\u30FC\u306E\u3082\u306E\u306B\u3059\u308B\u3002
33446
- 4. emotion \u306F\u4E0A\u306B\u6319\u3052\u305F\u3082\u306E\u304B\u3089\u306E\u307F\u9078\u3076\u3002
33447
- 5. **\u53F0\u8A5E\u306B\u30D5\u30A1\u30A4\u30EB\u540D\u30FB\u884C\u756A\u53F7\u30FB\u8A18\u53F7\u3092\u66F8\u304B\u306A\u3044\u3002** \u97F3\u58F0\u3067\u8AAD\u307F\u4E0A\u3052\u308B\u305F\u3081\u3001
33448
- \u300C4\u884C\u76EE\u306E db.query \u304C\u300D\u306E\u3088\u3046\u306A\u66F8\u304D\u65B9\u3092\u3059\u308B\u3068\u805E\u304D\u82E6\u3057\u304F\u306A\u308B\u3002\u4F4D\u7F6E\u306F\u753B\u9762\u304C\u793A\u3059\u3002
33449
- 6. confidence \u304C low \u306E\u6307\u6458\u306F\u3001\u65AD\u5B9A\u3092\u907F\u3051\u305F\u8A00\u3044\u56DE\u3057\u306B\u3059\u308B\u3002
33450
-
33451
- # \u69CB\u6210
33452
- - \u6700\u521D\u306B role="opening" \u30921\u3064\u3002\u5168\u4F53\u3067\u4F55\u4EF6\u898B\u3064\u304B\u3063\u305F\u304B\u3092\u4F1D\u3048\u308B\u3002
33453
- - \u6307\u6458\u3054\u3068\u306B present \u2192 problem \u2192 improve \u2192 summary \u306E\u9806\u3067\u4E26\u3079\u308B\u3002
33454
- - severity \u304C "praise" \u306E\u6307\u6458\u306F problem \u3092\u4F5C\u3089\u306A\u3044\u3002
33455
- - improvedCode \u304C\u7121\u3044\u6307\u6458\u306F improve \u3092\u4F5C\u3089\u306A\u3044\u3002
33456
- - \u6700\u5F8C\u306B role="ending" \u30921\u3064\u3002\u826F\u3044\u70B9\u306B\u89E6\u308C\u3066\u7D42\u3048\u308B\u3002
33457
-
33458
- # \u8868\u60C5\u306E\u4F7F\u3044\u5206\u3051
33459
- - normal: \u63D0\u793A\uFF08present\uFF09
33460
- - serious: \u91CD\u5927\u306A\u6307\u6458\uFF08critical\uFF09
33461
- - exasperated: \u96D1\u306A\u30B3\u30FC\u30C9\u3078\u306E\u6307\u6458
33462
- - thinking: \u6539\u5584\u6848\u306E\u63D0\u793A\uFF08improve\uFF09
33463
- - proud: \u307E\u3068\u3081\u30FB\u826F\u3044\u70B9\u30FB\u30A8\u30F3\u30C7\u30A3\u30F3\u30B0
33464
- - flustered: \u7279\u306B\u91CD\u5927\u306A\u767A\u898B
33465
- - sulking: \u4F7F\u7528\u983B\u5EA6\u306F\u4F4E\u3044`;
33466
- }
33467
- function scenarioUserPrompt(findings) {
33468
- const counts = findings.reduce((acc, f5) => {
33469
- acc[f5.severity] = (acc[f5.severity] ?? 0) + 1;
33470
- return acc;
33471
- }, {});
33472
- const list2 = findings.map(
33473
- (f5) => [
33474
- `## ${f5.id}\uFF08${f5.severity} / ${f5.category} / \u78BA\u4FE1\u5EA6 ${f5.confidence}\uFF09`,
33475
- `\u898B\u51FA\u3057: ${f5.title}`,
33476
- `\u554F\u984C: ${f5.problem}`,
33477
- f5.suggestion ? `\u6539\u5584: ${f5.suggestion}` : "",
33478
- f5.improvedCode ? "\u6539\u5584\u5F8C\u306E\u30B3\u30FC\u30C9\u3042\u308A" : "\u6539\u5584\u5F8C\u306E\u30B3\u30FC\u30C9\u306A\u3057"
33479
- ].filter(Boolean).join("\n")
33480
- ).join("\n\n");
33481
- return `\u6B21\u306E\u30EC\u30D3\u30E5\u30FC\u7D50\u679C\u3092\u53F0\u672C\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002
33482
-
33483
- \u5168\u4F53: ${findings.length}\u4EF6\uFF08${Object.entries(counts).map(([k5, v]) => `${k5} ${v}`).join(" / ")}\uFF09
33484
-
33485
- ${list2}`;
33486
- }
33487
-
33488
33608
  // src/review/pipeline.ts
33489
33609
  var SNIPPET_PAD = 2;
33490
33610
  var SNIPPET_MAX_LINES = 12;
@@ -33709,7 +33829,7 @@ var HELP = `candy-debug ${package_default.version}
33709
33829
 
33710
33830
  \u4F7F\u3044\u65B9:
33711
33831
  candy-debug <\u5BFE\u8C61\u30D5\u30A9\u30EB\u30C0> [\u30AA\u30D7\u30B7\u30E7\u30F3] \u30EC\u30D3\u30E5\u30FC\u3057\u3066 JSON \u3092\u51FA\u3059
33712
- candy-debug fix <review.json> [\u30AA\u30D7\u30B7\u30E7\u30F3] JSON \u306E\u6539\u5584\u6848\u3092\u5BFE\u8C61\u30D5\u30A9\u30EB\u30C0\u3078\u5F53\u3066\u308B
33832
+ candy-debug fix <review.json> [\u30AA\u30D7\u30B7\u30E7\u30F3] \u6307\u6458\u3092 AI \u306B\u76F4\u3055\u305B\u3001\u5BFE\u8C61\u30D5\u30A9\u30EB\u30C0\u3078\u66F8\u304D\u8FBC\u3080
33713
33833
  cdbg ... \u77ED\u7E2E\u540D\u3002\u4E2D\u8EAB\u306F\u540C\u3058
33714
33834
 
33715
33835
  \u30EC\u30D3\u30E5\u30FC\u306E\u30AA\u30D7\u30B7\u30E7\u30F3:
@@ -33726,11 +33846,11 @@ var HELP = `candy-debug ${package_default.version}
33726
33846
  --scan-only AI \u3092\u547C\u3070\u305A\u3001\u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB\u306E\u4E00\u89A7\u3068\u7D71\u8A08\u3060\u3051\u3092 JSON \u3067\u51FA\u3059
33727
33847
  --doctor AI \u3092\u547C\u3079\u308B\u304B\u3092\u78BA\u304B\u3081\u308B\uFF08--json \u3067\u7D50\u679C\u3092 JSON \u306B\uFF09
33728
33848
 
33729
- fix \u306E\u30AA\u30D7\u30B7\u30E7\u30F3:
33730
- --dir <folder> \u6539\u5584\u6848\u3092\u5F53\u3066\u308B\u30D5\u30A9\u30EB\u30C0\uFF08\u65E2\u5B9A \u30AB\u30EC\u30F3\u30C8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF09
33849
+ fix \u306E\u30AA\u30D7\u30B7\u30E7\u30F3\uFF08--provider / --model / --region \u3082\u30EC\u30D3\u30E5\u30FC\u3068\u540C\u3058\u304F\u4F7F\u3048\u308B\uFF09:
33850
+ --dir <folder> \u76F4\u3059\u30D5\u30A9\u30EB\u30C0\uFF08\u65E2\u5B9A \u30AB\u30EC\u30F3\u30C8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF09
33731
33851
  --min-severity <sev> \u3053\u306E\u91CD\u8981\u5EA6\u4EE5\u4E0A\u3060\u3051\u76F4\u3059\uFF08\u65E2\u5B9A warning\uFF09
33732
33852
  --summary <file> \u30D7\u30EB\u30EA\u30AF\u30A8\u30B9\u30C8\u7528\u306E\u672C\u6587\uFF08Markdown\uFF09\u3092\u66F8\u304D\u51FA\u3059
33733
- --dry-run \u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u63DB\u3048\u305A\u3001\u5F53\u3066\u3089\u308C\u308B\u304B\u3060\u3051\u3092\u78BA\u304B\u3081\u308B
33853
+ --dry-run \u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u63DB\u3048\u305A\u3001\u76F4\u305B\u308B\u304B\u3060\u3051\u3092\u78BA\u304B\u3081\u308B\uFF08AI \u306F\u547C\u3076\uFF09
33734
33854
 
33735
33855
  -v, --version \u7248\u6570\u3092\u8868\u793A
33736
33856
  -h, --help \u3053\u306E\u8AAC\u660E\u3092\u8868\u793A
@@ -33871,8 +33991,9 @@ async function makeProvider(opts) {
33871
33991
  };
33872
33992
  }
33873
33993
  async function runFix(opts) {
33874
- if (!opts.target)
33994
+ if (!opts.target) {
33875
33995
  throw new UsageError("\u30EC\u30D3\u30E5\u30FC\u7D50\u679C\u306E JSON \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF08cdbg fix review.json\uFF09");
33996
+ }
33876
33997
  let raw;
33877
33998
  try {
33878
33999
  raw = JSON.parse(await readFile3(resolve2(opts.target), "utf8"));
@@ -33883,11 +34004,35 @@ async function runFix(opts) {
33883
34004
  if (!parsed.ok) throw new Error(parsed.error);
33884
34005
  const file = parsed.file;
33885
34006
  const dir = resolve2(opts.dir ?? ".");
34007
+ const { ai, destination } = await makeProvider(opts);
34008
+ if (opts.progress === "text") {
34009
+ process.stderr.write(`\u5BFE\u8C61: ${dir}
34010
+ \u6307\u6458\u306E\u3042\u308B\u30D5\u30A1\u30A4\u30EB\u3092 ${destination} \u3078\u9001\u3063\u3066\u76F4\u3055\u305B\u307E\u3059\u3002
34011
+ `);
34012
+ }
34013
+ const controller = new AbortController();
34014
+ const onSignal = () => controller.abort();
34015
+ process.once("SIGINT", onSignal);
34016
+ process.once("SIGTERM", onSignal);
33886
34017
  const findings = file.findings;
33887
- const result = await applyFixes(dir, findings, {
33888
- minSeverity: opts.minSeverity,
33889
- dryRun: opts.dryRun
33890
- });
34018
+ let result;
34019
+ try {
34020
+ result = await applyFixes(dir, findings, {
34021
+ ai,
34022
+ minSeverity: opts.minSeverity,
34023
+ dryRun: opts.dryRun,
34024
+ signal: controller.signal
34025
+ });
34026
+ } catch (e5) {
34027
+ if (controller.signal.aborted) {
34028
+ emit(opts.progress, { type: "error", message: "\u4E2D\u65AD\u3057\u307E\u3057\u305F" });
34029
+ return 130;
34030
+ }
34031
+ throw e5;
34032
+ } finally {
34033
+ process.off("SIGINT", onSignal);
34034
+ process.off("SIGTERM", onSignal);
34035
+ }
33891
34036
  if (opts.summary) {
33892
34037
  const beats = file.scenario.beats;
33893
34038
  await writeOutput(
@@ -33899,13 +34044,16 @@ async function runFix(opts) {
33899
34044
  );
33900
34045
  }
33901
34046
  if (opts.progress === "text") {
33902
- const verb = opts.dryRun ? "\u5F53\u3066\u3089\u308C\u308B" : "\u5F53\u3066\u305F";
34047
+ const verb = opts.dryRun ? "\u76F4\u305B\u308B" : "\u76F4\u3057\u305F";
33903
34048
  process.stderr.write(
33904
- `${dir} \u306B\u6539\u5584\u6848\u3092${verb}: ${result.applied.length} \u4EF6 / \u898B\u9001\u308A ${result.skipped.length} \u4EF6
34049
+ `${verb}\u6307\u6458: ${result.applied.length} \u4EF6 / \u898B\u9001\u308A ${result.skipped.length} \u4EF6
33905
34050
  `
33906
34051
  );
33907
- for (const s2 of result.skipped)
34052
+ for (const s2 of result.skipped) {
33908
34053
  process.stderr.write(` \u898B\u9001\u308A ${s2.findingId} ${s2.file}: ${s2.reason}
34054
+ `);
34055
+ }
34056
+ for (const u of result.unchecked) process.stderr.write(` \u69CB\u6587\u691C\u67FB\u306A\u3057: ${u}
33909
34057
  `);
33910
34058
  }
33911
34059
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "candy-debug",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "ロキシーがコードレビューをして、ノベルゲーム用の台本(JSON)を作る CLI",
5
5
  "type": "module",
6
6
  "bin": {